diff --git a/.codespell-ignore-lines b/.codespell-ignore-lines index 68ff6e8ca6987..0679b9e0a4a29 100644 --- a/.codespell-ignore-lines +++ b/.codespell-ignore-lines @@ -18,6 +18,39 @@ Linix 45ZWN24-40 2 0.5 Ohm 0.400 mH 2.34A 24V * [#4526](https://github.com/apache/nuttx/pull/4526) Rearch video * [#6447](https://github.com/apache/nuttx/pull/6447) bcm43xxx: Remove bcmf_txavail_work and resue bcmf_tx_poll_work ans init + * include/nuttx/net/bluetooth/services/ans.h + * @brief Alert Notification Service (ANS) + * @defgroup bt_ans Alert Notification Service (ANS) + * @brief ANS max text string size in octets + * @brief ANS Category ID Enum + * an immediate replay is requested via the ANS control point. + * net/bt/services/ans.c + LOG_INF("ANS initialization complete"); +# net/bt/services/Kconfig.ans +module-str = Alert Notification Service (ANS) +#include +/* Build time ANS supported category bit mask */ +#define BT_ANS_NALRT_CAT_MASK \ +#define BT_ANS_UNALRT_CAT_MASK \ + "At least one ANS New Alert category must be enabled in " + list(APPEND BT_SERVICE_SRCS ${CMAKE_CURRENT_LIST_DIR}/ans.c) +BT_CSRCS += bt/services/ans.c +rsource "../mesh/Kconfig" +rsource "Kconfig.l2cap" +rsource "Kconfig.gatt" +rsource "../services/Kconfig" +rsource "./classic/Kconfig" +rsource "Kconfig.ans" +rsource "Kconfig.dis" +rsource "Kconfig.gap_svc" +rsource "Kconfig.cts" +rsource "Kconfig.ets" +rsource "Kconfig.hrs" +rsource "Kconfig.tps" +rsource "nus/Kconfig.nus" +rsource "ias/Kconfig.ias" +rsource "ots/Kconfig" +rsource "bas/Kconfig.bas" * CAF : Depends on CONFIG_NET_PROMISCUOUS * been lost). If ORE is set along with RXNE then it tells you /* GIR bits must be masked! */ diff --git a/.codespellrc b/.codespellrc index 4ed2e6b695cf6..25967ddb30d55 100644 --- a/.codespellrc +++ b/.codespellrc @@ -22,6 +22,7 @@ ignore-words-list = AFE, afile, als, + ans, ameba, ARCHTYPE, BU, diff --git a/Kconfig b/Kconfig index e8c72ecfc6423..aa01bfdb5a93f 100644 --- a/Kconfig +++ b/Kconfig @@ -3046,6 +3046,7 @@ endmenu menu "Wireless Support" source "wireless/Kconfig" +source "libs/zblue/Kconfig" endmenu menu "Binary Loader" diff --git a/LICENSE b/LICENSE index f6ddcee1a00bd..e92d9b2fe81b1 100644 --- a/LICENSE +++ b/LICENSE @@ -202,6 +202,18 @@ ------------------------------------------------------------------------------ +Zephyr Bluetooth Host Stack +libs/zblue +include/nuttx/net/bluetooth +=========================== + +The Bluetooth host stack and public Bluetooth API headers are derived from the +Zephyr Project Bluetooth host stack, with changes for Apache NuttX integration. +The derived files retain their original copyright and Apache-2.0 SPDX license +notices. + +------------------------------------------------------------------------------ + uIP include/nuttx/net/arp.h include/nuttx/net/can.h diff --git a/arch/sim/Kconfig b/arch/sim/Kconfig index 11024aabf7a33..7507b43480597 100644 --- a/arch/sim/Kconfig +++ b/arch/sim/Kconfig @@ -676,7 +676,7 @@ endmenu config SIM_HCISOCKET bool "Attach Host Bluetooth" default false - depends on HOST_LINUX && (UART_BTH4 || UART_BTH5 || WIRELESS_BLUETOOTH) + depends on HOST_LINUX && (UART_BTH4 || UART_BTH5 || WIRELESS_BLUETOOTH || BT) ---help--- Attached the local bluetooth device to the simulation target via HCI_CHANNEL_USER. This gives NuttX full diff --git a/arch/sim/src/Makefile b/arch/sim/src/Makefile index 1883aee10b392..79078d3384fb5 100644 --- a/arch/sim/src/Makefile +++ b/arch/sim/src/Makefile @@ -277,6 +277,9 @@ endif ifeq ($(CONFIG_SIM_HCISOCKET),y) HOSTSRCS += sim_hosthcisocket.c CSRCS += sim_hcisocket.c + ifeq ($(CONFIG_BT),y) + STDLIBS += -lmbedcrypto + endif endif ifeq ($(CONFIG_SIM_I2CBUS_LINUX),y) diff --git a/boards/sim/sim/sim/configs/bt/defconfig b/boards/sim/sim/sim/configs/bt/defconfig new file mode 100644 index 0000000000000..4f1406c3e1faa --- /dev/null +++ b/boards/sim/sim/sim/configs/bt/defconfig @@ -0,0 +1,135 @@ +# +# This file is autogenerated: PLEASE DO NOT EDIT IT. +# +# You can use "make menuconfig" to make any modifications to the installed .config file. +# You can then do "make savedefconfig" to generate a new defconfig file that includes your +# modifications. +# +# CONFIG_LIBSBC is not set +CONFIG_ARCH="sim" +CONFIG_ARCH_BOARD="sim" +CONFIG_ARCH_BOARD_SIM=y +CONFIG_ARCH_CHIP="sim" +CONFIG_ARCH_SIM=y +CONFIG_BASE_DEFCONFIG="sim/bt" +CONFIG_BOARDCTL_POWEROFF=y +CONFIG_BOARD_LOOPSPERMSEC=0 +CONFIG_BOOT_RUNFROMEXTSRAM=y +CONFIG_BT_A2DP=y +CONFIG_BT_A2DP_SINK=y +CONFIG_BT_ASCS=y +CONFIG_BT_ATT_PREPARE_COUNT=1 +CONFIG_BT_AUDIO=y +CONFIG_BT_AVRCP=y +CONFIG_BT_AVRCP_BROWSING=y +CONFIG_BT_AVRCP_CONTROLLER=y +CONFIG_BT_AVRCP_TARGET=y +CONFIG_BT_BAP_BROADCAST_ASSISTANT=y +CONFIG_BT_BAP_BROADCAST_SINK=y +CONFIG_BT_BAP_BROADCAST_SOURCE=y +CONFIG_BT_BAP_SCAN_DELEGATOR=y +CONFIG_BT_BAP_UNICAST_CLIENT=y +CONFIG_BT_BAP_UNICAST_SERVER=y +CONFIG_BT_BAS=y +CONFIG_BT_BR_MIN_ENC_KEY_SIZE=12 +CONFIG_BT_BUF_ACL_RX_COUNT_EXTRA=7 +CONFIG_BT_BUF_ACL_RX_SIZE=1024 +CONFIG_BT_BUF_ACL_TX_COUNT=22 +CONFIG_BT_BUF_ACL_TX_SIZE=27 +CONFIG_BT_BUF_EVT_RX_COUNT=30 +CONFIG_BT_CAP_ACCEPTOR=y +CONFIG_BT_CAP_COMMANDER=y +CONFIG_BT_CAP_HANDOVER=y +CONFIG_BT_CAP_INITIATOR=y +CONFIG_BT_CLASSIC=y +CONFIG_BT_COD=0x240404 +CONFIG_BT_CSIP_SET_COORDINATOR=y +CONFIG_BT_CSIP_SET_MEMBER=y +CONFIG_BT_DEFAULT_ROLE_SWITCH_ENABLE=y +CONFIG_BT_DEVICE_NAME_DYNAMIC=y +CONFIG_BT_DEVICE_NAME_MAX=64 +CONFIG_BT_DID=y +CONFIG_BT_DIS=y +CONFIG_BT_EXT_ADV_LEGACY_SUPPORT=y +CONFIG_BT_GATT_AUTO_DISCOVER_CCC=y +CONFIG_BT_GATT_AUTO_UPDATE_MTU=y +CONFIG_BT_GMAP=y +CONFIG_BT_HAS=y +CONFIG_BT_HAS_CLIENT=y +CONFIG_BT_HCI_TX_STACK_SIZE=4096 +CONFIG_BT_HCI_TX_STACK_SIZE_WITH_PROMPT=y +CONFIG_BT_ISO_BROADCASTER=y +CONFIG_BT_ISO_CENTRAL=y +CONFIG_BT_ISO_PERIPHERAL=y +CONFIG_BT_ISO_SYNC_RECEIVER=y +CONFIG_BT_L2CAP_TX_BUF_COUNT=12 +CONFIG_BT_LONG_WQ_PRIO=100 +CONFIG_BT_LONG_WQ_STACK_SIZE=5120 +CONFIG_BT_MAX_CONN=6 +CONFIG_BT_MAX_PAIRED=10 +CONFIG_BT_MCC=y +CONFIG_BT_MESH=y +CONFIG_BT_MESH_CDB=y +CONFIG_BT_MESH_CFG_CLI=y +CONFIG_BT_MESH_GATT_PROXY=y +CONFIG_BT_MESH_HEALTH_CLI=y +CONFIG_BT_MESH_PB_GATT=y +CONFIG_BT_MESH_PB_GATT_CLIENT=y +CONFIG_BT_MESH_PROXY_CLIENT=y +CONFIG_BT_MICP_MIC_CTLR=y +CONFIG_BT_MICP_MIC_DEV=y +CONFIG_BT_MONITOR=y +CONFIG_BT_MPL=y +CONFIG_BT_PBP=y +CONFIG_BT_POWER_MODE_CONTROL=y +CONFIG_BT_RFCOMM=y +CONFIG_BT_RFCOMM_L2CAP_MTU=1021 +CONFIG_BT_RX_STACK_SIZE=5120 +CONFIG_BT_SERVICES=y +CONFIG_BT_TBS=y +CONFIG_BT_TMAP=y +CONFIG_BT_TPS=y +CONFIG_BT_TX_PROCESSOR_STACK_SIZE=2048 +CONFIG_BT_VCP_VOL_CTLR=y +CONFIG_BT_VCP_VOL_REND=y +CONFIG_BT_ZEPHYR_NUS=y +CONFIG_BT_ZEPHYR_NUS_AUTO_START_BLUETOOTH=y +CONFIG_BUILTIN=y +CONFIG_CRYPTO_MBEDTLS=y +CONFIG_DEBUG_ASSERTIONS=y +CONFIG_DEBUG_FEATURES=y +CONFIG_DEBUG_SYMBOLS=y +CONFIG_DEV_URANDOM=y +CONFIG_DRIVERS_BLUETOOTH=y +CONFIG_DRIVERS_WIRELESS=y +CONFIG_EXPERIMENTAL=y +CONFIG_FS_PROCFS=y +CONFIG_IDLETHREAD_STACKSIZE=4096 +CONFIG_INIT_ENTRYPOINT="nsh_main" +CONFIG_LIBC_ENVPATH=y +CONFIG_LIBC_EXECFUNCS=y +CONFIG_NDEBUG=y +CONFIG_NET=y +CONFIG_NSH_BUILTIN_APPS=y +CONFIG_NSH_READLINE=y +CONFIG_PIPES=y +CONFIG_RTC=y +CONFIG_RTC_ARCH=y +CONFIG_RTC_DRIVER=y +CONFIG_SCHED_LPNTHREADS=2 +CONFIG_SCHED_LPWORK=y +CONFIG_SCHED_LPWORKSTACKSIZE=3072 +CONFIG_SCHED_WAITPID=y +CONFIG_SIM_HCISOCKET=y +CONFIG_SIM_WALLTIME_SIGNAL=y +CONFIG_START_MONTH=6 +CONFIG_START_YEAR=2008 +CONFIG_SYSLOG_BUFFER=y +CONFIG_SYSLOG_BUFSIZE=128 +CONFIG_SYSLOG_CONSOLE=y +CONFIG_SYSLOG_INTBUFFER=y +CONFIG_SYSLOG_PROCESSID=y +CONFIG_SYSLOG_TIMESTAMP=y +CONFIG_SYSLOG_TIMESTAMP_MS=y +CONFIG_SYSTEM_NSH=y +CONFIG_ZBLUE_ENABLED=y diff --git a/boards/sim/sim/sim/src/sim_bringup.c b/boards/sim/sim/sim/src/sim_bringup.c index 9dc6bcf21d0bc..364319f5dc100 100644 --- a/boards/sim/sim/sim/src/sim_bringup.c +++ b/boards/sim/sim/sim/src/sim_bringup.c @@ -58,6 +58,10 @@ #include #include +#ifdef CONFIG_BT +#include +#endif + #ifdef CONFIG_LCD_DEV #include #endif @@ -430,6 +434,19 @@ int sim_bringup(void) } #endif +#ifdef CONFIG_BT + /* Run NuttX Bluetooth compatibility initializers after the simulated + * board-level Bluetooth HCI drivers have had a chance to register + * themselves. + */ + + ret = bluetooth_initialize(); + if (ret < 0) + { + syslog(LOG_ERR, "ERROR: bluetooth_initialize() failed: %d\n", ret); + } +#endif + #ifdef CONFIG_SIM_I2CBUS /* Initialize the i2c master bus device */ diff --git a/drivers/wireless/bluetooth/bt_driver.c b/drivers/wireless/bluetooth/bt_driver.c index b7d4ce4b2e7fa..3b35eb879db31 100644 --- a/drivers/wireless/bluetooth/bt_driver.c +++ b/drivers/wireless/bluetooth/bt_driver.c @@ -27,6 +27,10 @@ #include #include +#ifdef CONFIG_BT +# include +#endif + #ifdef CONFIG_UART_BTH4 # include #endif @@ -96,6 +100,10 @@ int bt_driver_register_with_id(FAR struct bt_driver_s *driver, int id) } #endif +#ifdef CONFIG_BT + return bt_netdev_register(driver); +#endif + #ifdef CONFIG_BLUETOOTH_BRIDGE ret = bt_bridge_register(driver, &btdrv, &bledrv); if (ret < 0) diff --git a/include/nuttx/net/bluetooth/addr.h b/include/nuttx/net/bluetooth/addr.h new file mode 100644 index 0000000000000..c227b771e859b --- /dev/null +++ b/include/nuttx/net/bluetooth/addr.h @@ -0,0 +1,444 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/addr.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Bluetooth device address definitions and utilities. + */ + +/* Copyright (c) 2019 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_ADDR_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_ADDR_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Bluetooth device address definitions and utilities. + * @defgroup bt_addr Device Address + * @since 1.0 + * @version 1.0.0 + * @ingroup bluetooth + * @{ + */ + +#define BT_ADDR_LE_PUBLIC 0x00 +#define BT_ADDR_LE_RANDOM 0x01 +#define BT_ADDR_LE_PUBLIC_ID 0x02 +#define BT_ADDR_LE_RANDOM_ID 0x03 +#define BT_ADDR_LE_UNRESOLVED 0xFE /* Resolvable Private + * Address (Controller + * unable to resolve) + */ + +#define BT_ADDR_LE_ANONYMOUS 0xFF /* No address provided + * (anonymous advertisement) + */ + +/** Length in bytes of a standard Bluetooth address */ +#define BT_ADDR_SIZE 6 + +/** Bluetooth Device Address */ + +typedef struct +{ + uint8_t val[BT_ADDR_SIZE]; +} bt_addr_t; + +/**/ + +/** Length in bytes of an LE Bluetooth address. Not packed, so no sizeof() */ +#define BT_ADDR_LE_SIZE 7 + +/** Bluetooth LE Device Address */ + +typedef struct +{ + uint8_t type; + bt_addr_t a; +} bt_addr_le_t; + +/* Global Bluetooth address constants defined in bluetooth/common/addr.c */ + +extern const bt_addr_t bt_addr_any; +extern const bt_addr_t bt_addr_none; +extern const bt_addr_le_t bt_addr_le_any; +extern const bt_addr_le_t bt_addr_le_none; + +/** Bluetooth device "any" address, not a valid address */ +#define BT_ADDR_ANY (&bt_addr_any) +/** Bluetooth device "none" address, not a valid address */ +#define BT_ADDR_NONE (&bt_addr_none) +/** Bluetooth LE device "any" address, not a valid address */ +#define BT_ADDR_LE_ANY (&bt_addr_le_any) +/** Bluetooth LE device "none" address, not a valid address */ +#define BT_ADDR_LE_NONE (&bt_addr_le_none) + +/** @brief Compare Bluetooth device addresses. + * + * @param a First Bluetooth device address to compare + * @param b Second Bluetooth device address to compare + * + * @return negative value if @a a < @a b, 0 if @a a == @a b, else positive + */ + +static inline int bt_addr_cmp(const bt_addr_t *a, const bt_addr_t *b) +{ + return memcmp(a, b, sizeof(*a)); +} + +/** @brief Determine equality of two Bluetooth device addresses. + * + * @retval true if the two addresses are equal + * @retval false otherwise + */ + +static inline bool bt_addr_eq(const bt_addr_t *a, const bt_addr_t *b) +{ + return bt_addr_cmp(a, b) == 0; +} + +/** @brief Compare Bluetooth LE device addresses. + * + * @param a First Bluetooth LE device address to compare + * @param b Second Bluetooth LE device address to compare + * + * @return negative value if @a a < @a b, 0 if @a a == @a b, else positive + * + * @sa bt_addr_le_eq + */ + +static inline int bt_addr_le_cmp(const bt_addr_le_t *a, + const bt_addr_le_t *b) +{ + return memcmp(a, b, sizeof(*a)); +} + +/** @brief Determine equality of two Bluetooth LE device addresses. + * + * The Bluetooth LE addresses are equal if and only if both the types and + * the 48-bit addresses are numerically equal. + * + * @retval true if the two addresses are equal + * @retval false otherwise + */ + +static inline bool bt_addr_le_eq(const bt_addr_le_t *a, + const bt_addr_le_t *b) +{ + return bt_addr_le_cmp(a, b) == 0; +} + +/** @brief Copy Bluetooth device address. + * + * @param dst Bluetooth device address destination buffer. + * @param src Bluetooth device address source buffer. + */ + +static inline void bt_addr_copy(bt_addr_t *dst, const bt_addr_t *src) +{ + memcpy(dst, src, sizeof(*dst)); +} + +/** @brief Copy Bluetooth LE device address. + * + * @param dst Bluetooth LE device address destination buffer. + * @param src Bluetooth LE device address source buffer. + */ + +static inline void bt_addr_le_copy(bt_addr_le_t *dst, + const bt_addr_le_t *src) +{ + memcpy(dst, src, sizeof(*dst)); +} + +/** @brief Copy Bluetooth LE device address from Bluetooth device + * address and type. + * + * @param dst Bluetooth LE device address destination buffer. + * @param src Bluetooth device address source buffer. + * @param src_type Bluetooth device address source type. + */ + +static inline void bt_addr_le_copy_addr(bt_addr_le_t *dst, + const bt_addr_t *src, + uint8_t src_type) +{ + bt_addr_copy(&dst->a, src); + dst->type = src_type; +} + +/** Check if a Bluetooth LE random address is resolvable private address. */ +#define BT_ADDR_IS_RPA(a) (((a)->val[5] & 0xc0) == 0x40) +/** Check if a Bluetooth LE random address is a non-resolvable private + * address. + */ +#define BT_ADDR_IS_NRPA(a) (((a)->val[5] & 0xc0) == 0x00) +/** Check if a Bluetooth LE random address is a static address. */ +#define BT_ADDR_IS_STATIC(a) (((a)->val[5] & 0xc0) == 0xc0) + +/** Set a Bluetooth LE random address as a resolvable private address. */ +#define BT_ADDR_SET_RPA(a) ((a)->val[5] = (((a)->val[5] & 0x3f) | 0x40)) +/** Set a Bluetooth LE random address as a non-resolvable private address. */ +#define BT_ADDR_SET_NRPA(a) ((a)->val[5] &= 0x3f) +/** Set a Bluetooth LE random address as a static address. */ +#define BT_ADDR_SET_STATIC(a) ((a)->val[5] |= 0xc0) + +/** @brief Create a Bluetooth LE random non-resolvable private address. */ + +int bt_addr_le_create_nrpa(bt_addr_le_t *addr); + +/** @brief Create a Bluetooth LE random static address. */ + +int bt_addr_le_create_static(bt_addr_le_t *addr); + +/** @brief Check if a Bluetooth LE address is a random private resolvable + * address. + * + * @param addr Bluetooth LE device address. + * + * @return true if address is a random private resolvable address. + */ + +static inline bool bt_addr_le_is_rpa(const bt_addr_le_t *addr) +{ + if (addr->type != BT_ADDR_LE_RANDOM) + { + return false; + } + + return BT_ADDR_IS_RPA(&addr->a); +} + +/** @brief Check if a Bluetooth LE address is valid identity address. + * + * Valid Bluetooth LE identity addresses are either public address or random + * static address. + * + * @param addr Bluetooth LE device address. + * + * @return true if address is a valid identity address. + */ + +static inline bool bt_addr_le_is_identity(const bt_addr_le_t *addr) +{ + if (addr->type == BT_ADDR_LE_PUBLIC) + { + return true; + } + + return BT_ADDR_IS_STATIC(&addr->a); +} + +/** + * @brief Recommended length of user string buffer for Bluetooth address + * + * @details The recommended length guarantee the output of address + * conversion will not lose valuable information about address being + * processed. + */ +#define BT_ADDR_STR_LEN 18 + +/** + * @brief Recommended length of user string buffer for Bluetooth LE address + * + * @details The recommended length guarantee the output of address + * conversion will not lose valuable information about address being + * processed. Format: "P:XX:XX:XX:XX:XX:XX" or "R:XX:XX:XX:XX:XX:XX" + */ +#define BT_ADDR_LE_STR_LEN 20 + +/** @brief Converts binary Bluetooth address to string. + * + * @param addr Address of buffer containing binary Bluetooth address. + * @param str Address of user buffer with enough room to store formatted + * string containing binary address. + * @param len Length of data to be copied to user string buffer. Refer to + * BT_ADDR_STR_LEN about recommended value. + * + * @return Number of successfully formatted bytes from binary address. + */ + +static inline int bt_addr_to_str(const bt_addr_t *addr, + char *str, size_t len) +{ + return snprintf(str, len, "%02X:%02X:%02X:%02X:%02X:%02X", + addr->val[5], addr->val[4], addr->val[3], + addr->val[2], addr->val[1], addr->val[0]); +} + +/** @brief Converts binary LE Bluetooth address to string. + * + * Converts the LE Bluetooth address to a string representation in the + * format: + * - "P:XX:XX:XX:XX:XX:XX" for public addresses + * - "R:XX:XX:XX:XX:XX:XX" for random addresses + * + * Address type values that carry additional HCI-level information, such as + * @c BT_ADDR_LE_PUBLIC_ID and @c BT_ADDR_LE_RANDOM_ID, are formatted + * according to their base type (public or random). The HCI sentinels for + * an unresolved RPA (@c BT_ADDR_LE_UNRESOLVED) and an anonymous + * advertiser (@c BT_ADDR_LE_ANONYMOUS) are formatted as random + * addresses. The additional information is not part of the string and + * has to be conveyed separately by the caller. + * + * @param addr Address of buffer containing binary LE Bluetooth address. + * @param str Address of user buffer with enough room to store formatted + * string containing binary LE address. + * @param len Length of data to be copied to user string buffer. Refer to + * BT_ADDR_LE_STR_LEN about recommended value. + * + * @return Number of successfully formatted bytes from binary address. + */ + +static inline int bt_addr_le_to_str(const bt_addr_le_t *addr, + char *str, size_t len) +{ + char type_char; + + switch (addr->type) + { + case BT_ADDR_LE_PUBLIC: + case BT_ADDR_LE_PUBLIC_ID: + type_char = 'P'; + break; + default: + /* BT_ADDR_LE_RANDOM, BT_ADDR_LE_RANDOM_ID and the unresolved-RPA + * and anonymous sentinels, none of which are public addresses. + */ + + type_char = 'R'; + break; + } + + return snprintf(str, len, "%c:%02X:%02X:%02X:%02X:%02X:%02X", + type_char, addr->a.val[5], addr->a.val[4], + addr->a.val[3], addr->a.val[2], addr->a.val[1], + addr->a.val[0]); +} + +/** @cond INTERNAL_HIDDEN */ + +struct bt_addr_tmp_str +{ + char str[BT_ADDR_STR_LEN]; +}; + +struct bt_addr_tmp_str bt_addr_tmp_str(const bt_addr_t *addr); + +struct bt_addr_le_tmp_str +{ + char str[BT_ADDR_LE_STR_LEN]; +}; + +struct bt_addr_le_tmp_str bt_addr_le_tmp_str(const bt_addr_le_t *addr); + +/** @endcond */ + +/** + * @brief Convert a Bluetooth address to a string + * @def bt_addr_str() + * + * @param _addr Pointer to the Bluetooth address (bt_addr_t) + * + * @return A string pointer which is only valid until the end of the full + * expression. In practice this means that this is primarily useful as an + * input parameter to printk/printf or logging calls. + */ +#define bt_addr_str(_addr) bt_addr_tmp_str(_addr).str + +/** + * @brief Convert a Bluetooth LE address to a string + * @def bt_addr_le_str() + * + * @param _addr Pointer to the Bluetooth LE address (bt_addr_le_t) + * + * @return A string pointer which is only valid until the end of the full + * expression. In practice this means that this is primarily useful as an + * input parameter to printk/printf or logging calls. + */ +#define bt_addr_le_str(_addr) bt_addr_le_tmp_str(_addr).str + +/** @brief Convert Bluetooth address from string to binary. + * + * @param[in] str The string representation of a Bluetooth address. + * @param[out] addr Address of buffer to store the Bluetooth address + * + * @retval 0 Success. The parsed address is stored in @p addr. + * @return -EINVAL Invalid address string. @p str is not a well-formed + * Bluetooth address. + */ + +int bt_addr_from_str(const char *str, bt_addr_t *addr); + +/** @brief Convert LE Bluetooth address from string to binary. + * + * The string must use the format produced by bt_addr_le_to_str(): + * "P:XX:XX:XX:XX:XX:XX" for a public address or "R:XX:XX:XX:XX:XX:XX" for a + * random address. The type prefix and the hexadecimal digits are + * case-insensitive. + * + * @param[in] str The string representation of an LE Bluetooth address. + * @param[out] addr Address of buffer to store the LE Bluetooth address + * + * @retval 0 Success. The parsed address is stored in @p addr. + * @retval -EINVAL Invalid address string. @p str has an unknown type prefix + * or is not a well-formed Bluetooth address. + */ + +int bt_addr_le_from_str(const char *str, bt_addr_le_t *addr); + +/** @brief Resolve a Bluetooth LE Resolvable Private Address (RPA) to its + * identity address. + * + * This function attempts to resolve a RPA to its corresponding identity + * address (public or random static) using the host Identity Resolving Key + * (IRK) database. + * + * @kconfig_dep{CONFIG_BT_PRIVACY} + * + * @param[in] id Local identity whose IRK database is used. Must + * be less than @kconfig{CONFIG_BT_ID_MAX}. + * @param[in] addr The RPA to resolve. + * @param[out] resolved_addr On success, contains the resolved identity + * address. This will be either a public address + * (@ref BT_ADDR_LE_PUBLIC) or random static + * address (@ref BT_ADDR_LE_RANDOM). + * + * @retval 0 Success. The address has been resolved. + * @p resolved_addr contains the identity address. + * @retval -ENOENT @p addr is an RPA but no matching IRK was found in the + * database. The address could not be resolved. + * @retval -EINVAL Invalid arguments: @p addr or @p resolved_addr is NULL, + * @p id is invalid, or @p addr is not an RPA. + */ + +int bt_addr_le_rpa_resolve(uint8_t id, + const bt_addr_le_t *addr, + bt_addr_le_t *resolved_addr); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_ADDR_H */ diff --git a/include/nuttx/net/bluetooth/assigned_numbers.h b/include/nuttx/net/bluetooth/assigned_numbers.h new file mode 100644 index 0000000000000..97a9645934a43 --- /dev/null +++ b/include/nuttx/net/bluetooth/assigned_numbers.h @@ -0,0 +1,2079 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/assigned_numbers.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Bluetooth Assigned Numbers, codes and identifiers. + */ + +/* Copyright (c) 2015-2025 Intel Corporation + * Copyright (c) 2017-2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_ASSIGNED_NUMBERS_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_ASSIGNED_NUMBERS_H + +/** + * @brief Bluetooth Assigned Numbers, codes and identifiers. + * @defgroup bt_assigned_numbers Assigned Numbers. + * @since 1.0 + * @version 1.0.0 + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Core Specification Assigned Numbers + * @defgroup bt_assigned_numbers_core Core Specification Assigned Numbers + * @ingroup bt_assigned_numbers + * @{ + */ + +/** + * @name Appearance Numbers + * + * Last Modified on 2023-01-05 @{ + */ + +/** Generic Unknown */ +#define BT_APPEARANCE_UNKNOWN 0x0000 +/** Generic Phone */ +#define BT_APPEARANCE_GENERIC_PHONE 0x0040 +/** Generic Computer */ +#define BT_APPEARANCE_GENERIC_COMPUTER 0x0080 +/** Desktop Workstation */ +#define BT_APPEARANCE_COMPUTER_DESKTOP_WORKSTATION 0x0081 +/** Server-class Computer */ +#define BT_APPEARANCE_COMPUTER_SERVER_CLASS 0x0082 +/** Laptop */ +#define BT_APPEARANCE_COMPUTER_LAPTOP 0x0083 +/** Handheld PC/PDA (clamshell) */ +#define BT_APPEARANCE_COMPUTER_HANDHELD_PCPDA 0x0084 +/** PalmĀ­size PC/PDA */ +#define BT_APPEARANCE_COMPUTER_PALMSIZE_PCPDA 0x0085 +/** Wearable computer (watch size) */ +#define BT_APPEARANCE_COMPUTER_WEARABLE_COMPUTER 0x0086 +/** Tablet */ +#define BT_APPEARANCE_COMPUTER_TABLET 0x0087 +/** Docking Station */ +#define BT_APPEARANCE_COMPUTER_DOCKING_STATION 0x0088 +/** All in One */ +#define BT_APPEARANCE_COMPUTER_ALL_IN_ONE 0x0089 +/** Blade Server */ +#define BT_APPEARANCE_COMPUTER_BLADE_SERVER 0x008a +/** Convertible */ +#define BT_APPEARANCE_COMPUTER_CONVERTIBLE 0x008b +/** Detachable */ +#define BT_APPEARANCE_COMPUTER_DETACHABLE 0x008c +/** IoT Gateway */ +#define BT_APPEARANCE_COMPUTER_IOT_GATEWAY 0x008d +/** Mini PC */ +#define BT_APPEARANCE_COMPUTER_MINI_PC 0x008e +/** Stick PC */ +#define BT_APPEARANCE_COMPUTER_STICK_PC 0x008f +/** Generic Watch */ +#define BT_APPEARANCE_GENERIC_WATCH 0x00c0 +/** Sports Watch */ +#define BT_APPEARANCE_SPORTS_WATCH 0x00c1 +/** Smartwatch */ +#define BT_APPEARANCE_SMARTWATCH 0x00c2 +/** Generic Clock */ +#define BT_APPEARANCE_GENERIC_CLOCK 0x0100 +/** Generic Display */ +#define BT_APPEARANCE_GENERIC_DISPLAY 0x0140 +/** Generic Remote Control */ +#define BT_APPEARANCE_GENERIC_REMOTE 0x0180 +/** Generic Eye-glasses */ +#define BT_APPEARANCE_GENERIC_EYEGLASSES 0x01c0 +/** Generic Tag */ +#define BT_APPEARANCE_GENERIC_TAG 0x0200 +/** Generic Keyring */ +#define BT_APPEARANCE_GENERIC_KEYRING 0x0240 +/** Generic Media Player */ +#define BT_APPEARANCE_GENERIC_MEDIA_PLAYER 0x0280 +/** Generic Barcode Scanner */ +#define BT_APPEARANCE_GENERIC_BARCODE_SCANNER 0x02c0 +/** Generic Thermometer */ +#define BT_APPEARANCE_GENERIC_THERMOMETER 0x0300 +/** Ear Thermometer */ +#define BT_APPEARANCE_THERMOMETER_EAR 0x0301 +/** Generic Heart Rate Sensor */ +#define BT_APPEARANCE_GENERIC_HEART_RATE 0x0340 +/** Heart Rate Belt */ +#define BT_APPEARANCE_HEART_RATE_BELT 0x0341 +/** Generic Blood Pressure */ +#define BT_APPEARANCE_GENERIC_BLOOD_PRESSURE 0x0380 +/** Arm Blood Pressure */ +#define BT_APPEARANCE_BLOOD_PRESSURE_ARM 0x0381 +/** Wrist Blood Pressure */ +#define BT_APPEARANCE_BLOOD_PRESSURE_WRIST 0x0382 +/** Generic Human Interface Device */ +#define BT_APPEARANCE_GENERIC_HID 0x03c0 +/** Keyboard */ +#define BT_APPEARANCE_HID_KEYBOARD 0x03c1 +/** Mouse */ +#define BT_APPEARANCE_HID_MOUSE 0x03c2 +/** Joystick */ +#define BT_APPEARANCE_HID_JOYSTICK 0x03c3 +/** Gamepad */ +#define BT_APPEARANCE_HID_GAMEPAD 0x03c4 +/** Digitizer Tablet */ +#define BT_APPEARANCE_HID_DIGITIZER_TABLET 0x03c5 +/** Card Reader */ +#define BT_APPEARANCE_HID_CARD_READER 0x03c6 +/** Digital Pen */ +#define BT_APPEARANCE_HID_DIGITAL_PEN 0x03c7 +/** Barcode Scanner */ +#define BT_APPEARANCE_HID_BARCODE_SCANNER 0x03c8 +/** Touchpad */ +#define BT_APPEARANCE_HID_TOUCHPAD 0x03c9 +/** Presentation Remote */ +#define BT_APPEARANCE_HID_PRESENTATION_REMOTE 0x03ca +/** Generic Glucose Meter */ +#define BT_APPEARANCE_GENERIC_GLUCOSE 0x0400 +/** Generic Running Walking Sensor */ +#define BT_APPEARANCE_GENERIC_WALKING 0x0440 +/** In-Shoe Running Walking Sensor */ +#define BT_APPEARANCE_WALKING_IN_SHOE 0x0441 +/** On-Shoe Running Walking Sensor */ +#define BT_APPEARANCE_WALKING_ON_SHOE 0x0442 +/** On-Hip Running Walking Sensor */ +#define BT_APPEARANCE_WALKING_ON_HIP 0x0443 +/** Generic Cycling */ +#define BT_APPEARANCE_GENERIC_CYCLING 0x0480 +/** Cycling Computer */ +#define BT_APPEARANCE_CYCLING_COMPUTER 0x0481 +/** Speed Sensor */ +#define BT_APPEARANCE_CYCLING_SPEED 0x0482 +/** Cadence Sensor */ +#define BT_APPEARANCE_CYCLING_CADENCE 0x0483 +/** Power Sensor */ +#define BT_APPEARANCE_CYCLING_POWER 0x0484 +/** Speed and Cadence Sensor */ +#define BT_APPEARANCE_CYCLING_SPEED_CADENCE 0x0485 +/** Generic Control Device */ +#define BT_APPEARANCE_GENERIC_CONTROL_DEVICE 0x04c0 +/** Switch */ +#define BT_APPEARANCE_CONTROL_SWITCH 0x04c1 +/** Multi-switch */ +#define BT_APPEARANCE_CONTROL_MULTI_SWITCH 0x04c2 +/** Button */ +#define BT_APPEARANCE_CONTROL_BUTTON 0x04c3 +/** Slider */ +#define BT_APPEARANCE_CONTROL_SLIDER 0x04c4 +/** Rotary Switch */ +#define BT_APPEARANCE_CONTROL_ROTARY_SWITCH 0x04c5 +/** Touch Panel */ +#define BT_APPEARANCE_CONTROL_TOUCH_PANEL 0x04c6 +/** Single Switch */ +#define BT_APPEARANCE_CONTROL_SINGLE_SWITCH 0x04c7 +/** Double Switch */ +#define BT_APPEARANCE_CONTROL_DOUBLE_SWITCH 0x04c8 +/** Triple Switch */ +#define BT_APPEARANCE_CONTROL_TRIPLE_SWITCH 0x04c9 +/** Battery Switch */ +#define BT_APPEARANCE_CONTROL_BATTERY_SWITCH 0x04ca +/** Energy Harvesting Switch */ +#define BT_APPEARANCE_CONTROL_ENERGY_HARVESTING_SWITCH 0x04cb +/** Push Button */ +#define BT_APPEARANCE_CONTROL_PUSH_BUTTON 0x04cc +/** Generic Network Device */ +#define BT_APPEARANCE_GENERIC_NETWORK_DEVICE 0x0500 +/** Access Point */ +#define BT_APPEARANCE_NETWORK_ACCESS_POINT 0x0501 +/** Mesh Device */ +#define BT_APPEARANCE_NETWORK_MESH_DEVICE 0x0502 +/** Mesh Network Proxy */ +#define BT_APPEARANCE_NETWORK_MESH_PROXY 0x0503 +/** Generic Sensor */ +#define BT_APPEARANCE_GENERIC_SENSOR 0x0540 +/** Motion Sensor */ +#define BT_APPEARANCE_SENSOR_MOTION 0x0541 +/** Air quality Sensor */ +#define BT_APPEARANCE_SENSOR_AIR_QUALITY 0x0542 +/** Temperature Sensor */ +#define BT_APPEARANCE_SENSOR_TEMPERATURE 0x0543 +/** Humidity Sensor */ +#define BT_APPEARANCE_SENSOR_HUMIDITY 0x0544 +/** Leak Sensor */ +#define BT_APPEARANCE_SENSOR_LEAK 0x0545 +/** Smoke Sensor */ +#define BT_APPEARANCE_SENSOR_SMOKE 0x0546 +/** Occupancy Sensor */ +#define BT_APPEARANCE_SENSOR_OCCUPANCY 0x0547 +/** Contact Sensor */ +#define BT_APPEARANCE_SENSOR_CONTACT 0x0548 +/** Carbon Monoxide Sensor */ +#define BT_APPEARANCE_SENSOR_CARBON_MONOXIDE 0x0549 +/** Carbon Dioxide Sensor */ +#define BT_APPEARANCE_SENSOR_CARBON_DIOXIDE 0x054a +/** Ambient Light Sensor */ +#define BT_APPEARANCE_SENSOR_AMBIENT_LIGHT 0x054b +/** Energy Sensor */ +#define BT_APPEARANCE_SENSOR_ENERGY 0x054c +/** Color Light Sensor */ +#define BT_APPEARANCE_SENSOR_COLOR_LIGHT 0x054d +/** Rain Sensor */ +#define BT_APPEARANCE_SENSOR_RAIN 0x054e +/** Fire Sensor */ +#define BT_APPEARANCE_SENSOR_FIRE 0x054f +/** Wind Sensor */ +#define BT_APPEARANCE_SENSOR_WIND 0x0550 +/** Proximity Sensor */ +#define BT_APPEARANCE_SENSOR_PROXIMITY 0x0551 +/** Multi-Sensor */ +#define BT_APPEARANCE_SENSOR_MULTI 0x0552 +/** Flush Mounted Sensor */ +#define BT_APPEARANCE_SENSOR_FLUSH_MOUNTED 0x0553 +/** Ceiling Mounted Sensor */ +#define BT_APPEARANCE_SENSOR_CEILING_MOUNTED 0x0554 +/** Wall Mounted Sensor */ +#define BT_APPEARANCE_SENSOR_WALL_MOUNTED 0x0555 +/** Multisensor */ +#define BT_APPEARANCE_MULTISENSOR 0x0556 +/** Energy Meter */ +#define BT_APPEARANCE_SENSOR_ENERGY_METER 0x0557 +/** Flame Detector */ +#define BT_APPEARANCE_SENSOR_FLAME_DETECTOR 0x0558 +/** Vehicle Tire Pressure Sensor */ +#define BT_APPEARANCE_SENSOR_VEHICLE_TIRE_PRESSURE 0x0559 +/** Generic Light Fixtures */ +#define BT_APPEARANCE_GENERIC_LIGHT_FIXTURES 0x0580 +/** Wall Light */ +#define BT_APPEARANCE_LIGHT_FIXTURES_WALL 0x0581 +/** Ceiling Light */ +#define BT_APPEARANCE_LIGHT_FIXTURES_CEILING 0x0582 +/** Floor Light */ +#define BT_APPEARANCE_LIGHT_FIXTURES_FLOOR 0x0583 +/** Cabinet Light */ +#define BT_APPEARANCE_LIGHT_FIXTURES_CABINET 0x0584 +/** Desk Light */ +#define BT_APPEARANCE_LIGHT_FIXTURES_DESK 0x0585 +/** Troffer Light */ +#define BT_APPEARANCE_LIGHT_FIXTURES_TROFFER 0x0586 +/** Pendant Light */ +#define BT_APPEARANCE_LIGHT_FIXTURES_PENDANT 0x0587 +/** In-ground Light */ +#define BT_APPEARANCE_LIGHT_FIXTURES_IN_GROUND 0x0588 +/** Flood Light */ +#define BT_APPEARANCE_LIGHT_FIXTURES_FLOOD 0x0589 +/** Underwater Light */ +#define BT_APPEARANCE_LIGHT_FIXTURES_UNDERWATER 0x058a +/** Bollard with Light */ +#define BT_APPEARANCE_LIGHT_FIXTURES_BOLLARD_WITH 0x058b +/** Pathway Light */ +#define BT_APPEARANCE_LIGHT_FIXTURES_PATHWAY 0x058c +/** Garden Light */ +#define BT_APPEARANCE_LIGHT_FIXTURES_GARDEN 0x058d +/** Pole-top Light */ +#define BT_APPEARANCE_LIGHT_FIXTURES_POLE_TOP 0x058e +/** Spotlight */ +#define BT_APPEARANCE_SPOT_LIGHT 0x058f +/** Linear Light */ +#define BT_APPEARANCE_LIGHT_FIXTURES_LINEAR 0x0590 +/** Street Light */ +#define BT_APPEARANCE_LIGHT_FIXTURES_STREET 0x0591 +/** Shelves Light */ +#define BT_APPEARANCE_LIGHT_FIXTURES_SHELVES 0x0592 +/** Bay Light */ +#define BT_APPEARANCE_LIGHT_FIXTURES_BAY 0x0593 +/** Emergency Exit Light */ +#define BT_APPEARANCE_LIGHT_FIXTURES_EMERGENCY_EXIT 0x0594 +/** Light Controller */ +#define BT_APPEARANCE_LIGHT_FIXTURES_CONTROLLER 0x0595 +/** Light Driver */ +#define BT_APPEARANCE_LIGHT_FIXTURES_DRIVER 0x0596 +/** Bulb */ +#define BT_APPEARANCE_LIGHT_FIXTURES_BULB 0x0597 +/** Low-bay Light */ +#define BT_APPEARANCE_LIGHT_FIXTURES_LOW_BAY 0x0598 +/** High-bay Light */ +#define BT_APPEARANCE_LIGHT_FIXTURES_HIGH_BAY 0x0599 +/** Generic Fan */ +#define BT_APPEARANCE_GENERIC_FAN 0x05c0 +/** Ceiling Fan */ +#define BT_APPEARANCE_FAN_CEILING 0x05c1 +/** Axial Fan */ +#define BT_APPEARANCE_FAN_AXIAL 0x05c2 +/** Exhaust Fan */ +#define BT_APPEARANCE_FAN_EXHAUST 0x05c3 +/** Pedestal Fan */ +#define BT_APPEARANCE_FAN_PEDESTAL 0x05c4 +/** Desk Fan */ +#define BT_APPEARANCE_FAN_DESK 0x05c5 +/** Wall Fan */ +#define BT_APPEARANCE_FAN_WALL 0x05c6 +/** Generic HVAC */ +#define BT_APPEARANCE_GENERIC_HVAC 0x0600 +/** Thermostat */ +#define BT_APPEARANCE_HVAC_THERMOSTAT 0x0601 +/** Humidifier */ +#define BT_APPEARANCE_HVAC_HUMIDIFIER 0x0602 +/** De-humidifier */ +#define BT_APPEARANCE_HVAC_DEHUMIDIFIER 0x0603 +/** Heater */ +#define BT_APPEARANCE_HVAC_HEATER 0x0604 +/** Radiator */ +#define BT_APPEARANCE_HVAC_RADIATOR 0x0605 +/** Boiler */ +#define BT_APPEARANCE_HVAC_BOILER 0x0606 +/** Heat Pump */ +#define BT_APPEARANCE_HVAC_HEAT_PUMP 0x0607 +/** Infrared Heater */ +#define BT_APPEARANCE_HVAC_INFRARED_HEATER 0x0608 +/** Radiant Panel Heater */ +#define BT_APPEARANCE_HVAC_RADIANT_PANEL_HEATER 0x0609 +/** Fan Heater */ +#define BT_APPEARANCE_HVAC_FAN_HEATER 0x060a +/** Air Curtain */ +#define BT_APPEARANCE_HVAC_AIR_CURTAIN 0x060b +/** Generic Air Conditioning */ +#define BT_APPEARANCE_GENERIC_AIR_CONDITIONING 0x0640 +/** Generic Humidifier */ +#define BT_APPEARANCE_GENERIC_HUMIDIFIER 0x0680 +/** Generic Heating */ +#define BT_APPEARANCE_GENERIC_HEATING 0x06c0 +/** Radiator */ +#define BT_APPEARANCE_HEATING_RADIATOR 0x06c1 +/** Boiler */ +#define BT_APPEARANCE_HEATING_BOILER 0x06c2 +/** Heat Pump */ +#define BT_APPEARANCE_HEATING_HEAT_PUMP 0x06c3 +/** Infrared Heater */ +#define BT_APPEARANCE_HEATING_INFRARED_HEATER 0x06c4 +/** Radiant Panel Heater */ +#define BT_APPEARANCE_HEATING_RADIANT_PANEL_HEATER 0x06c5 +/** Fan Heater */ +#define BT_APPEARANCE_HEATING_FAN_HEATER 0x06c6 +/** Air Curtain */ +#define BT_APPEARANCE_HEATING_AIR_CURTAIN 0x06c7 +/** Generic Access Control */ +#define BT_APPEARANCE_GENERIC_ACCESS_CONTROL 0x0700 +/** Access Door */ +#define BT_APPEARANCE_CONTROL_ACCESS_DOOR 0x0701 +/** Garage Door */ +#define BT_APPEARANCE_CONTROL_GARAGE_DOOR 0x0702 +/** Emergency Exit Door */ +#define BT_APPEARANCE_CONTROL_EMERGENCY_EXIT_DOOR 0x0703 +/** Access Lock */ +#define BT_APPEARANCE_CONTROL_ACCESS_LOCK 0x0704 +/** Elevator */ +#define BT_APPEARANCE_CONTROL_ELEVATOR 0x0705 +/** Window */ +#define BT_APPEARANCE_CONTROL_WINDOW 0x0706 +/** Entrance Gate */ +#define BT_APPEARANCE_CONTROL_ENTRANCE_GATE 0x0707 +/** Door Lock */ +#define BT_APPEARANCE_CONTROL_DOOR_LOCK 0x0708 +/** Locker */ +#define BT_APPEARANCE_CONTROL_LOCKER 0x0709 +/** Generic Motorized Device */ +#define BT_APPEARANCE_GENERIC_MOTORIZED_DEVICE 0x0740 +/** Motorized Gate */ +#define BT_APPEARANCE_MOTORIZED_GATE 0x0741 +/** Awning */ +#define BT_APPEARANCE_MOTORIZED_AWNING 0x0742 +/** Blinds or Shades */ +#define BT_APPEARANCE_MOTORIZED_BLINDS_OR_SHADES 0x0743 +/** Curtains */ +#define BT_APPEARANCE_MOTORIZED_CURTAINS 0x0744 +/** Screen */ +#define BT_APPEARANCE_MOTORIZED_SCREEN 0x0745 +/** Generic Power Device */ +#define BT_APPEARANCE_GENERIC_POWER_DEVICE 0x0780 +/** Power Outlet */ +#define BT_APPEARANCE_POWER_OUTLET 0x0781 +/** Power Strip */ +#define BT_APPEARANCE_POWER_STRIP 0x0782 +/** Plug */ +#define BT_APPEARANCE_POWER_PLUG 0x0783 +/** Power Supply */ +#define BT_APPEARANCE_POWER_SUPPLY 0x0784 +/** LED Driver */ +#define BT_APPEARANCE_POWER_LED_DRIVER 0x0785 +/** Fluorescent Lamp Gear */ +#define BT_APPEARANCE_POWER_FLUORESCENT_LAMP_GEAR 0x0786 +/** HID Lamp Gear */ +#define BT_APPEARANCE_POWER_HID_LAMP_GEAR 0x0787 +/** Charge Case */ +#define BT_APPEARANCE_POWER_CHARGE_CASE 0x0788 +/** Power Bank */ +#define BT_APPEARANCE_POWER_POWER_BANK 0x0789 +/** Generic Light Source */ +#define BT_APPEARANCE_GENERIC_LIGHT_SOURCE 0x07c0 +/** Incandescent Light Bulb */ +#define BT_APPEARANCE_LIGHT_SOURCE_INCANDESCENT_BULB 0x07c1 +/** LED Lamp */ +#define BT_APPEARANCE_LIGHT_SOURCE_LED_LAMP 0x07c2 +/** HID Lamp */ +#define BT_APPEARANCE_LIGHT_SOURCE_HID_LAMP 0x07c3 +/** Fluorescent Lamp */ +#define BT_APPEARANCE_LIGHT_SOURCE_FLUORESCENT_LAMP 0x07c4 +/** LED Array */ +#define BT_APPEARANCE_LIGHT_SOURCE_LED_ARRAY 0x07c5 +/** Multi-Color LED Array */ +#define BT_APPEARANCE_LIGHT_SOURCE_MULTICOLOR_LED_ARRAY 0x07c6 +/** Low voltage halogen */ +#define BT_APPEARANCE_LIGHT_SOURCE_LOW_VOLTAGE_HALOGEN 0x07c7 +/** Organic light emitting diode */ +#define BT_APPEARANCE_LIGHT_SOURCE_OLED 0x07c8 +/** Generic Window Covering */ +#define BT_APPEARANCE_GENERIC_WINDOW_COVERING 0x0800 +/** Window Shades */ +#define BT_APPEARANCE_WINDOW_SHADES 0x0801 +/** Window Blinds */ +#define BT_APPEARANCE_WINDOW_BLINDS 0x0802 +/** Window Awning */ +#define BT_APPEARANCE_WINDOW_AWNING 0x0803 +/** Window Curtain */ +#define BT_APPEARANCE_WINDOW_CURTAIN 0x0804 +/** Exterior Shutter */ +#define BT_APPEARANCE_WINDOW_EXTERIOR_SHUTTER 0x0805 +/** Exterior Screen */ +#define BT_APPEARANCE_WINDOW_EXTERIOR_SCREEN 0x0806 +/** Generic Audio Sink */ +#define BT_APPEARANCE_GENERIC_AUDIO_SINK 0x0840 +/** Standalone Speaker */ +#define BT_APPEARANCE_AUDIO_SINK_STANDALONE_SPEAKER 0x0841 +/** Soundbar */ +#define BT_APPEARANCE_AUDIO_SINK_SOUNDBAR 0x0842 +/** Bookshelf Speaker */ +#define BT_APPEARANCE_AUDIO_SINK_BOOKSHELF_SPEAKER 0x0843 +/** Standmounted Speaker */ +#define BT_APPEARANCE_AUDIO_SINK_STANDMOUNTED_SPEAKER 0x0844 +/** Speakerphone */ +#define BT_APPEARANCE_AUDIO_SINK_SPEAKERPHONE 0x0845 +/** Generic Audio Source */ +#define BT_APPEARANCE_GENERIC_AUDIO_SOURCE 0x0880 +/** Microphone */ +#define BT_APPEARANCE_AUDIO_SOURCE_MICROPHONE 0x0881 +/** Alarm */ +#define BT_APPEARANCE_AUDIO_SOURCE_ALARM 0x0882 +/** Bell */ +#define BT_APPEARANCE_AUDIO_SOURCE_BELL 0x0883 +/** Horn */ +#define BT_APPEARANCE_AUDIO_SOURCE_HORN 0x0884 +/** Broadcasting Device */ +#define BT_APPEARANCE_AUDIO_SOURCE_BROADCASTING_DEVICE 0x0885 +/** Service Desk */ +#define BT_APPEARANCE_AUDIO_SOURCE_SERVICE_DESK 0x0886 +/** Kiosk */ +#define BT_APPEARANCE_AUDIO_SOURCE_KIOSK 0x0887 +/** Broadcasting Room */ +#define BT_APPEARANCE_AUDIO_SOURCE_BROADCASTING_ROOM 0x0888 +/** Auditorium */ +#define BT_APPEARANCE_AUDIO_SOURCE_AUDITORIUM 0x0889 +/** Generic Motorized Vehicle */ +#define BT_APPEARANCE_GENERIC_MOTORIZED_VEHICLE 0x08c0 +/** Car */ +#define BT_APPEARANCE_VEHICLE_CAR 0x08c1 +/** Large Goods Vehicle */ +#define BT_APPEARANCE_VEHICLE_LARGE_GOODS 0x08c2 +/** 2-Wheeled Vehicle */ +#define BT_APPEARANCE_VEHICLE_TWO_WHEELED 0x08c3 +/** Motorbike */ +#define BT_APPEARANCE_VEHICLE_MOTORBIKE 0x08c4 +/** Scooter */ +#define BT_APPEARANCE_VEHICLE_SCOOTER 0x08c5 +/** Moped */ +#define BT_APPEARANCE_VEHICLE_MOPED 0x08c6 +/** 3-Wheeled Vehicle */ +#define BT_APPEARANCE_VEHICLE_THREE_WHEELED 0x08c7 +/** Light Vehicle */ +#define BT_APPEARANCE_VEHICLE_LIGHT 0x08c8 +/** Quad Bike */ +#define BT_APPEARANCE_VEHICLE_QUAD_BIKE 0x08c9 +/** Minibus */ +#define BT_APPEARANCE_VEHICLE_MINIBUS 0x08ca +/** Bus */ +#define BT_APPEARANCE_VEHICLE_BUS 0x08cb +/** Trolley */ +#define BT_APPEARANCE_VEHICLE_TROLLEY 0x08cc +/** Agricultural Vehicle */ +#define BT_APPEARANCE_VEHICLE_AGRICULTURAL 0x08cd +/** Camper/Caravan */ +#define BT_APPEARANCE_VEHICLE_CAMPER_OR_CARAVAN 0x08ce +/** Recreational Vehicle/Motor Home */ +#define BT_APPEARANCE_VEHICLE_RECREATIONAL 0x08cf +/** Generic Domestic Appliance */ +#define BT_APPEARANCE_GENERIC_DOMESTIC_APPLIANCE 0x0900 +/** Refrigerator */ +#define BT_APPEARANCE_APPLIANCE_REFRIGERATOR 0x0901 +/** Freezer */ +#define BT_APPEARANCE_APPLIANCE_FREEZER 0x0902 +/** Oven */ +#define BT_APPEARANCE_APPLIANCE_OVEN 0x0903 +/** Microwave */ +#define BT_APPEARANCE_APPLIANCE_MICROWAVE 0x0904 +/** Toaster */ +#define BT_APPEARANCE_APPLIANCE_TOASTER 0x0905 +/** Washing Machine */ +#define BT_APPEARANCE_APPLIANCE_WASHING_MACHINE 0x0906 +/** Dryer */ +#define BT_APPEARANCE_APPLIANCE_DRYER 0x0907 +/** Coffee maker */ +#define BT_APPEARANCE_APPLIANCE_COFFEE_MAKER 0x0908 +/** Clothes iron */ +#define BT_APPEARANCE_APPLIANCE_CLOTHES_IRON 0x0909 +/** Curling iron */ +#define BT_APPEARANCE_APPLIANCE_CURLING_IRON 0x090a +/** Hair dryer */ +#define BT_APPEARANCE_APPLIANCE_HAIR_DRYER 0x090b +/** Vacuum cleaner */ +#define BT_APPEARANCE_APPLIANCE_VACUUM_CLEANER 0x090c +/** Robotic vacuum cleaner */ +#define BT_APPEARANCE_APPLIANCE_ROBOTIC_VACUUM_CLEANER 0x090d +/** Rice cooker */ +#define BT_APPEARANCE_APPLIANCE_RICE_COOKER 0x090e +/** Clothes steamer */ +#define BT_APPEARANCE_APPLIANCE_CLOTHES_STEAMER 0x090f +/** Generic Wearable Audio Device */ +#define BT_APPEARANCE_GENERIC_WEARABLE_AUDIO_DEVICE 0x0940 +/** Earbud */ +#define BT_APPEARANCE_WEARABLE_AUDIO_DEVICE_EARBUD 0x0941 +/** Headset */ +#define BT_APPEARANCE_WEARABLE_AUDIO_DEVICE_HEADSET 0x0942 +/** Headphones */ +#define BT_APPEARANCE_WEARABLE_AUDIO_DEVICE_HEADPHONES 0x0943 +/** Neck Band */ +#define BT_APPEARANCE_WEARABLE_AUDIO_DEVICE_NECK_BAND 0x0944 +/** Generic Aircraft */ +#define BT_APPEARANCE_GENERIC_AIRCRAFT 0x0980 +/** Light Aircraft */ +#define BT_APPEARANCE_AIRCRAFT_LIGHT 0x0981 +/** Microlight */ +#define BT_APPEARANCE_AIRCRAFT_MICROLIGHT 0x0982 +/** Paraglider */ +#define BT_APPEARANCE_AIRCRAFT_PARAGLIDER 0x0983 +/** Large Passenger Aircraft */ +#define BT_APPEARANCE_AIRCRAFT_LARGE_PASSENGER 0x0984 +/** Generic AV Equipment */ +#define BT_APPEARANCE_GENERIC_AV_EQUIPMENT 0x09c0 +/** Amplifier */ +#define BT_APPEARANCE_AV_EQUIPMENT_AMPLIFIER 0x09c1 +/** Receiver */ +#define BT_APPEARANCE_AV_EQUIPMENT_RECEIVER 0x09c2 +/** Radio */ +#define BT_APPEARANCE_AV_EQUIPMENT_RADIO 0x09c3 +/** Tuner */ +#define BT_APPEARANCE_AV_EQUIPMENT_TUNER 0x09c4 +/** Turntable */ +#define BT_APPEARANCE_AV_EQUIPMENT_TURNTABLE 0x09c5 +/** CD Player */ +#define BT_APPEARANCE_AV_EQUIPMENT_CD_PLAYER 0x09c6 +/** DVD Player */ +#define BT_APPEARANCE_AV_EQUIPMENT_DVD_PLAYER 0x09c7 +/** Bluray Player */ +#define BT_APPEARANCE_AV_EQUIPMENT_BLURAY_PLAYER 0x09c8 +/** Optical Disc Player */ +#define BT_APPEARANCE_AV_EQUIPMENT_OPTICAL_DISC_PLAYER 0x09c9 +/** Set-Top Box */ +#define BT_APPEARANCE_AV_EQUIPMENT_SET_TOP_BOX 0x09ca +/** Generic Display Equipment */ +#define BT_APPEARANCE_GENERIC_DISPLAY_EQUIPMENT 0x0a00 +/** Television */ +#define BT_APPEARANCE_DISPLAY_EQUIPMENT_TELEVISION 0x0a01 +/** Monitor */ +#define BT_APPEARANCE_DISPLAY_EQUIPMENT_MONITOR 0x0a02 +/** Projector */ +#define BT_APPEARANCE_DISPLAY_EQUIPMENT_PROJECTOR 0x0a03 +/** Generic Hearing aid */ +#define BT_APPEARANCE_GENERIC_HEARING_AID 0x0a40 +/** In-ear hearing aid */ +#define BT_APPEARANCE_HEARING_AID_IN_EAR 0x0a41 +/** Behind-ear hearing aid */ +#define BT_APPEARANCE_HEARING_AID_BEHIND_EAR 0x0a42 +/** Cochlear Implant */ +#define BT_APPEARANCE_HEARING_AID_COCHLEAR_IMPLANT 0x0a43 +/** Generic Gaming */ +#define BT_APPEARANCE_GENERIC_GAMING 0x0a80 +/** Home Video Game Console */ +#define BT_APPEARANCE_HOME_VIDEO_GAME_CONSOLE 0x0a81 +/** Portable handheld console */ +#define BT_APPEARANCE_PORTABLE_HANDHELD_CONSOLE 0x0a82 +/** Generic Signage */ +#define BT_APPEARANCE_GENERIC_SIGNAGE 0x0ac0 +/** Digital Signage */ +#define BT_APPEARANCE_SIGNAGE_DIGITAL 0x0ac1 +/** Electronic Label */ +#define BT_APPEARANCE_SIGNAGE_ELECTRONIC_LABEL 0x0ac2 +/** Generic Pulse Oximeter */ +#define BT_APPEARANCE_GENERIC_PULSE_OXIMETER 0x0c40 +/** Fingertip Pulse Oximeter */ +#define BT_APPEARANCE_PULSE_OXIMETER_FINGERTIP 0x0c41 +/** Wrist Worn Pulse Oximeter */ +#define BT_APPEARANCE_PULSE_OXIMETER_WRIST 0x0c42 +/** Generic Weight Scale */ +#define BT_APPEARANCE_GENERIC_WEIGHT_SCALE 0x0c80 +/** Generic Personal Mobility Device */ +#define BT_APPEARANCE_GENERIC_PERSONAL_MOBILITY_DEVICE 0x0cc0 +/** Powered Wheelchair */ +#define BT_APPEARANCE_MOBILITY_POWERED_WHEELCHAIR 0x0cc1 +/** Mobility Scooter */ +#define BT_APPEARANCE_MOBILITY_SCOOTER 0x0cc2 +/** Continuous Glucose Monitor */ +#define BT_APPEARANCE_CONTINUOUS_GLUCOSE_MONITOR 0x0d00 +/** Generic Insulin Pump */ +#define BT_APPEARANCE_GENERIC_INSULIN_PUMP 0x0d40 +/** Insulin Pump, durable pump */ +#define BT_APPEARANCE_INSULIN_PUMP_DURABLE 0x0d41 +/** Insulin Pump, patch pump */ +#define BT_APPEARANCE_INSULIN_PUMP_PATCH 0x0d44 +/** Insulin Pen */ +#define BT_APPEARANCE_INSULIN_PEN 0x0d48 +/** Generic Medication Delivery */ +#define BT_APPEARANCE_GENERIC_MEDICATION_DELIVERY 0x0d80 +/** Generic Spirometer */ +#define BT_APPEARANCE_GENERIC_SPIROMETER 0x0dc0 +/** Handheld Spirometer */ +#define BT_APPEARANCE_SPIROMETER_HANDHELD 0x0dc1 +/** Generic Outdoor Sports Activity */ +#define BT_APPEARANCE_GENERIC_OUTDOOR_SPORTS 0x1440 +/** Location Display */ +#define BT_APPEARANCE_OUTDOOR_SPORTS_LOCATION 0x1441 +/** Location and Navigation Display */ +#define BT_APPEARANCE_OUTDOOR_SPORTS_LOCATION_AND_NAV 0x1442 +/** Location Pod */ +#define BT_APPEARANCE_OUTDOOR_SPORTS_LOCATION_POD 0x1443 +/** Location and Navigation Pod */ +#define BT_APPEARANCE_OUTDOOR_SPORTS_LOCATION_POD_AND_NAV 0x1444 + +/** @} */ /* end of @name Appearance Numbers */ + +/** + * @name Common Data Types + * @{ + */ +#define BT_DATA_FLAGS 0x01 /**< AD flags */ +#define BT_DATA_UUID16_SOME 0x02 /**< 16-bit UUID, more available */ +#define BT_DATA_UUID16_ALL 0x03 /**< 16-bit UUID, all listed */ +#define BT_DATA_UUID32_SOME 0x04 /**< 32-bit UUID, more available */ +#define BT_DATA_UUID32_ALL 0x05 /**< 32-bit UUID, all listed */ +#define BT_DATA_UUID128_SOME 0x06 /**< 128-bit UUID, more available */ +#define BT_DATA_UUID128_ALL 0x07 /**< 128-bit UUID, all listed */ +#define BT_DATA_NAME_SHORTENED 0x08 /**< Shortened name */ +#define BT_DATA_NAME_COMPLETE 0x09 /**< Complete name */ +#define BT_DATA_TX_POWER 0x0a /**< Tx Power */ +#define BT_DATA_DEVICE_CLASS 0x0d /**< Class of Device */ +#define BT_DATA_SIMPLE_PAIRING_HASH_C192 0x0e /**< Simple Pairing Hash C-192 */ +#define BT_DATA_SIMPLE_PAIRING_RAND_C192 0x0f /**< Simple Pairing Randomizer R-192 */ +#define BT_DATA_DEVICE_ID 0x10 /**< Device ID (Profile) */ +#define BT_DATA_SM_TK_VALUE 0x10 /**< Security Manager TK Value */ +#define BT_DATA_SM_OOB_FLAGS 0x11 /**< Security Manager OOB Flags */ +#define BT_DATA_PERIPHERAL_INT_RANGE 0x12 /**< Peripheral Connection Interval Range */ +#define BT_DATA_SOLICIT16 0x14 /**< Solicit UUIDs, 16-bit */ +#define BT_DATA_SOLICIT128 0x15 /**< Solicit UUIDs, 128-bit */ +#define BT_DATA_SVC_DATA16 0x16 /**< Service data, 16-bit UUID */ +#define BT_DATA_PUB_TARGET_ADDR 0x17 /**< Public Target Address */ +#define BT_DATA_RAND_TARGET_ADDR 0x18 /**< Random Target Address */ +#define BT_DATA_GAP_APPEARANCE 0x19 /**< GAP appearance */ +#define BT_DATA_ADV_INT 0x1a /**< Advertising Interval */ +#define BT_DATA_LE_BT_DEVICE_ADDRESS 0x1b /**< LE Bluetooth Device Address */ +#define BT_DATA_LE_ROLE 0x1c /**< LE Role */ +#define BT_DATA_SIMPLE_PAIRING_HASH 0x1d /**< Simple Pairing Hash C256 */ +#define BT_DATA_SIMPLE_PAIRING_RAND 0x1e /**< Simple Pairing Randomizer R256 */ +#define BT_DATA_SOLICIT32 0x1f /**< Solicit UUIDs, 32-bit */ +#define BT_DATA_SVC_DATA32 0x20 /**< Service data, 32-bit UUID */ +#define BT_DATA_SVC_DATA128 0x21 /**< Service data, 128-bit UUID */ +#define BT_DATA_LE_SC_CONFIRM_VALUE 0x22 /**< LE SC Confirmation Value */ +#define BT_DATA_LE_SC_RANDOM_VALUE 0x23 /**< LE SC Random Value */ +#define BT_DATA_URI 0x24 /**< URI */ +#define BT_DATA_INDOOR_POS 0x25 /**< Indoor Positioning */ +#define BT_DATA_TRANS_DISCOVER_DATA 0x26 /**< Transport Discovery Data */ +#define BT_DATA_LE_SUPPORTED_FEATURES 0x27 /**< LE Supported Features */ +#define BT_DATA_CHANNEL_MAP_UPDATE_IND 0x28 /**< Channel Map Update Indication */ +#define BT_DATA_MESH_PROV 0x29 /**< Mesh Provisioning PDU */ +#define BT_DATA_MESH_MESSAGE 0x2a /**< Mesh Networking PDU */ +#define BT_DATA_MESH_BEACON 0x2b /**< Mesh Beacon */ +#define BT_DATA_BIG_INFO 0x2c /**< BIGInfo */ +#define BT_DATA_BROADCAST_CODE 0x2d /**< Broadcast Code */ +#define BT_DATA_CSIS_RSI 0x2e /**< CSIS Random Set ID type */ +#define BT_DATA_ADV_INT_LONG 0x2f /**< Advertising Interval long */ +#define BT_DATA_BROADCAST_NAME 0x30 /**< Broadcast Name */ +#define BT_DATA_ENCRYPTED_AD_DATA 0x31 /**< Encrypted Advertising Data */ +#define BT_DATA_PAWR_TIMING_INFO 0x32 /**< Periodic Advertising Response Timing Info */ +#define BT_DATA_ESL 0x34 /**< Electronic Shelf Label Profile */ +#define BT_DATA_3D_INFO 0x3d /**< 3D Information Data */ + +#define BT_DATA_MANUFACTURER_DATA 0xff /**< Manufacturer Specific Data */ + +/** @} */ /* end of @name Common Data Types */ + +/** + * @name Flags data type values + * @{ + */ + +#define BT_LE_AD_LIMITED 0x01 /**< Limited Discoverable */ +#define BT_LE_AD_GENERAL 0x02 /**< General Discoverable */ +#define BT_LE_AD_NO_BREDR 0x04 /**< BR/EDR not supported */ + +/** @} */ /* end of @name Flags data type values */ + +/** @} */ /* end of bt_assigned_numbers_core */ + +/** + * @brief Bluetooth Mesh Assigned Numbers + * @defgroup bt_assigned_numbers_mesh Bluetooth Mesh Assigned Numbers + * @ingroup bt_assigned_numbers + * @{ + */ + +/** + * @name Foundation Models + * @{ + */ + +/** Configuration Server */ +#define BT_MESH_MODEL_ID_CFG_SRV 0x0000 +/** Configuration Client */ +#define BT_MESH_MODEL_ID_CFG_CLI 0x0001 +/** Health Server */ +#define BT_MESH_MODEL_ID_HEALTH_SRV 0x0002 +/** Health Client */ +#define BT_MESH_MODEL_ID_HEALTH_CLI 0x0003 +/** Remote Provisioning Server */ +#define BT_MESH_MODEL_ID_REMOTE_PROV_SRV 0x0004 +/** Remote Provisioning Client */ +#define BT_MESH_MODEL_ID_REMOTE_PROV_CLI 0x0005 +/** Bridge Configuration Sever */ +#define BT_MESH_MODEL_ID_BRG_CFG_SRV 0x0008 +/** Bridge Configuration Client */ +#define BT_MESH_MODEL_ID_BRG_CFG_CLI 0x0009 +/** Private Beacon Server */ +#define BT_MESH_MODEL_ID_PRIV_BEACON_SRV 0x000a +/** Private Beacon Client */ +#define BT_MESH_MODEL_ID_PRIV_BEACON_CLI 0x000b +/** SAR Configuration Server */ +#define BT_MESH_MODEL_ID_SAR_CFG_SRV 0x000e +/** SAR Configuration Client */ +#define BT_MESH_MODEL_ID_SAR_CFG_CLI 0x000f +/** Opcodes Aggregator Server */ +#define BT_MESH_MODEL_ID_OP_AGG_SRV 0x0010 +/** Opcodes Aggregator Client */ +#define BT_MESH_MODEL_ID_OP_AGG_CLI 0x0011 +/** Large Composition Data Server */ +#define BT_MESH_MODEL_ID_LARGE_COMP_DATA_SRV 0x0012 +/** Large Composition Data Client */ +#define BT_MESH_MODEL_ID_LARGE_COMP_DATA_CLI 0x0013 +/** Solicitation PDU RPL Configuration Server */ +#define BT_MESH_MODEL_ID_SOL_PDU_RPL_SRV 0x0014 +/** Solicitation PDU RPL Configuration Client */ +#define BT_MESH_MODEL_ID_SOL_PDU_RPL_CLI 0x0015 +/** Private Proxy Server */ +#define BT_MESH_MODEL_ID_ON_DEMAND_PROXY_SRV 0x000c +/** Private Proxy Client */ +#define BT_MESH_MODEL_ID_ON_DEMAND_PROXY_CLI 0x000d +/** + * @} + */ + +/** + * @name Models from the Mesh Model Specification + * @{ + */ + +/** Generic OnOff Server */ +#define BT_MESH_MODEL_ID_GEN_ONOFF_SRV 0x1000 +/** Generic OnOff Client */ +#define BT_MESH_MODEL_ID_GEN_ONOFF_CLI 0x1001 +/** Generic Level Server */ +#define BT_MESH_MODEL_ID_GEN_LEVEL_SRV 0x1002 +/** Generic Level Client */ +#define BT_MESH_MODEL_ID_GEN_LEVEL_CLI 0x1003 +/** Generic Default Transition Time Server */ +#define BT_MESH_MODEL_ID_GEN_DEF_TRANS_TIME_SRV 0x1004 +/** Generic Default Transition Time Client */ +#define BT_MESH_MODEL_ID_GEN_DEF_TRANS_TIME_CLI 0x1005 +/** Generic Power OnOff Server */ +#define BT_MESH_MODEL_ID_GEN_POWER_ONOFF_SRV 0x1006 +/** Generic Power OnOff Setup Server */ +#define BT_MESH_MODEL_ID_GEN_POWER_ONOFF_SETUP_SRV 0x1007 +/** Generic Power OnOff Client */ +#define BT_MESH_MODEL_ID_GEN_POWER_ONOFF_CLI 0x1008 +/** Generic Power Level Server */ +#define BT_MESH_MODEL_ID_GEN_POWER_LEVEL_SRV 0x1009 +/** Generic Power Level Setup Server */ +#define BT_MESH_MODEL_ID_GEN_POWER_LEVEL_SETUP_SRV 0x100a +/** Generic Power Level Client */ +#define BT_MESH_MODEL_ID_GEN_POWER_LEVEL_CLI 0x100b +/** Generic Battery Server */ +#define BT_MESH_MODEL_ID_GEN_BATTERY_SRV 0x100c +/** Generic Battery Client */ +#define BT_MESH_MODEL_ID_GEN_BATTERY_CLI 0x100d +/** Generic Location Server */ +#define BT_MESH_MODEL_ID_GEN_LOCATION_SRV 0x100e +/** Generic Location Setup Server */ +#define BT_MESH_MODEL_ID_GEN_LOCATION_SETUPSRV 0x100f +/** Generic Location Client */ +#define BT_MESH_MODEL_ID_GEN_LOCATION_CLI 0x1010 +/** Generic Admin Property Server */ +#define BT_MESH_MODEL_ID_GEN_ADMIN_PROP_SRV 0x1011 +/** Generic Manufacturer Property Server */ +#define BT_MESH_MODEL_ID_GEN_MANUFACTURER_PROP_SRV 0x1012 +/** Generic User Property Server */ +#define BT_MESH_MODEL_ID_GEN_USER_PROP_SRV 0x1013 +/** Generic Client Property Server */ +#define BT_MESH_MODEL_ID_GEN_CLIENT_PROP_SRV 0x1014 +/** Generic Property Client */ +#define BT_MESH_MODEL_ID_GEN_PROP_CLI 0x1015 +/** Sensor Server */ +#define BT_MESH_MODEL_ID_SENSOR_SRV 0x1100 +/** Sensor Setup Server */ +#define BT_MESH_MODEL_ID_SENSOR_SETUP_SRV 0x1101 +/** Sensor Client */ +#define BT_MESH_MODEL_ID_SENSOR_CLI 0x1102 +/** Time Server */ +#define BT_MESH_MODEL_ID_TIME_SRV 0x1200 +/** Time Setup Server */ +#define BT_MESH_MODEL_ID_TIME_SETUP_SRV 0x1201 +/** Time Client */ +#define BT_MESH_MODEL_ID_TIME_CLI 0x1202 +/** Scene Server */ +#define BT_MESH_MODEL_ID_SCENE_SRV 0x1203 +/** Scene Setup Server */ +#define BT_MESH_MODEL_ID_SCENE_SETUP_SRV 0x1204 +/** Scene Client */ +#define BT_MESH_MODEL_ID_SCENE_CLI 0x1205 +/** Scheduler Server */ +#define BT_MESH_MODEL_ID_SCHEDULER_SRV 0x1206 +/** Scheduler Setup Server */ +#define BT_MESH_MODEL_ID_SCHEDULER_SETUP_SRV 0x1207 +/** Scheduler Client */ +#define BT_MESH_MODEL_ID_SCHEDULER_CLI 0x1208 +/** Light Lightness Server */ +#define BT_MESH_MODEL_ID_LIGHT_LIGHTNESS_SRV 0x1300 +/** Light Lightness Setup Server */ +#define BT_MESH_MODEL_ID_LIGHT_LIGHTNESS_SETUP_SRV 0x1301 +/** Light Lightness Client */ +#define BT_MESH_MODEL_ID_LIGHT_LIGHTNESS_CLI 0x1302 +/** Light CTL Server */ +#define BT_MESH_MODEL_ID_LIGHT_CTL_SRV 0x1303 +/** Light CTL Setup Server */ +#define BT_MESH_MODEL_ID_LIGHT_CTL_SETUP_SRV 0x1304 +/** Light CTL Client */ +#define BT_MESH_MODEL_ID_LIGHT_CTL_CLI 0x1305 +/** Light CTL Temperature Server */ +#define BT_MESH_MODEL_ID_LIGHT_CTL_TEMP_SRV 0x1306 +/** Light HSL Server */ +#define BT_MESH_MODEL_ID_LIGHT_HSL_SRV 0x1307 +/** Light HSL Setup Server */ +#define BT_MESH_MODEL_ID_LIGHT_HSL_SETUP_SRV 0x1308 +/** Light HSL Client */ +#define BT_MESH_MODEL_ID_LIGHT_HSL_CLI 0x1309 +/** Light HSL Hue Server */ +#define BT_MESH_MODEL_ID_LIGHT_HSL_HUE_SRV 0x130a +/** Light HSL Saturation Server */ +#define BT_MESH_MODEL_ID_LIGHT_HSL_SAT_SRV 0x130b +/** Light xyL Server */ +#define BT_MESH_MODEL_ID_LIGHT_XYL_SRV 0x130c +/** Light xyL Setup Server */ +#define BT_MESH_MODEL_ID_LIGHT_XYL_SETUP_SRV 0x130d +/** Light xyL Client */ +#define BT_MESH_MODEL_ID_LIGHT_XYL_CLI 0x130e +/** Light LC Server */ +#define BT_MESH_MODEL_ID_LIGHT_LC_SRV 0x130f +/** Light LC Setup Server */ +#define BT_MESH_MODEL_ID_LIGHT_LC_SETUPSRV 0x1310 +/** Light LC Client */ +#define BT_MESH_MODEL_ID_LIGHT_LC_CLI 0x1311 +/** + * @} + */ + +/** + * @name Models from the Mesh Binary Large Object Transfer Model + * Specification @{ + */ + +/** BLOB Transfer Server */ +#define BT_MESH_MODEL_ID_BLOB_SRV 0x1400 +/** BLOB Transfer Client */ +#define BT_MESH_MODEL_ID_BLOB_CLI 0x1401 +/** + * @} + */ + +/** + * @name Models from the Mesh Device Firmware Update Model Specification + * @{ + */ + +/** Firmware Update Server */ +#define BT_MESH_MODEL_ID_DFU_SRV 0x1402 +/** Firmware Update Client */ +#define BT_MESH_MODEL_ID_DFU_CLI 0x1403 +/** Firmware Distribution Server */ +#define BT_MESH_MODEL_ID_DFD_SRV 0x1404 +/** Firmware Distribution Client */ +#define BT_MESH_MODEL_ID_DFD_CLI 0x1405 +/** + * @} + */ + +/** @} */ /* end of bt_assigned_numbers_mesh */ + +/** + * @brief Generic Audio Assigned Numbers + * @defgroup bt_assigned_numbers_audio Generic Audio Assigned Numbers + * @ingroup bt_assigned_numbers + * @{ + */ + +/** Enum for valid bearer technology values */ + +enum __packed bt_bearer_tech +{ + /** 3G */ + + BT_BEARER_TECH_3G = 0x01, + + /** 4G */ + + BT_BEARER_TECH_4G = 0x02, + + /** Long-term evolution (LTE) */ + + BT_BEARER_TECH_LTE = 0x03, + + /** Wifi */ + + BT_BEARER_TECH_WIFI = 0x04, + + /** 5G */ + + BT_BEARER_TECH_5G = 0x05, + + /** Global System for Mobile Communications (GSM) */ + + BT_BEARER_TECH_GSM = 0x06, + + /** Code-Division Multiple Access (CDMA) */ + + BT_BEARER_TECH_CDMA = 0x07, + + /** 2G */ + + BT_BEARER_TECH_2G = 0x08, + + /** Wideband Code-Division Multiple Access (WCDMA) */ + + BT_BEARER_TECH_WCDMA = 0x09, + + /* Values 0x0a to 0xff are reserved for future use */ +}; + +/** + * @brief Codec capability types + * + * Used to build and parse codec capabilities as specified in the PAC + * specification. Source is assigned numbers for Generic Audio, + * bluetooth.com. + */ + +enum bt_audio_codec_cap_type +{ + /** Supported sampling frequencies */ + + BT_AUDIO_CODEC_CAP_TYPE_FREQ = 0x01, + + /** Supported frame durations */ + + BT_AUDIO_CODEC_CAP_TYPE_DURATION = 0x02, + + /** Supported audio channel counts */ + + BT_AUDIO_CODEC_CAP_TYPE_CHAN_COUNT = 0x03, + + /** Supported octets per codec frame */ + + BT_AUDIO_CODEC_CAP_TYPE_FRAME_LEN = 0x04, + + /** Supported maximum codec frames per SDU */ + + BT_AUDIO_CODEC_CAP_TYPE_FRAME_COUNT = 0x05, +}; + +/** @brief Supported frequencies bitfield */ + +enum bt_audio_codec_cap_freq +{ + /** 8 Khz sampling frequency */ + + BT_AUDIO_CODEC_CAP_FREQ_8KHZ = BIT(0), + + /** 11.025 Khz sampling frequency */ + + BT_AUDIO_CODEC_CAP_FREQ_11KHZ = BIT(1), + + /** 16 Khz sampling frequency */ + + BT_AUDIO_CODEC_CAP_FREQ_16KHZ = BIT(2), + + /** 22.05 Khz sampling frequency */ + + BT_AUDIO_CODEC_CAP_FREQ_22KHZ = BIT(3), + + /** 24 Khz sampling frequency */ + + BT_AUDIO_CODEC_CAP_FREQ_24KHZ = BIT(4), + + /** 32 Khz sampling frequency */ + + BT_AUDIO_CODEC_CAP_FREQ_32KHZ = BIT(5), + + /** 44.1 Khz sampling frequency */ + + BT_AUDIO_CODEC_CAP_FREQ_44KHZ = BIT(6), + + /** 48 Khz sampling frequency */ + + BT_AUDIO_CODEC_CAP_FREQ_48KHZ = BIT(7), + + /** 88.2 Khz sampling frequency */ + + BT_AUDIO_CODEC_CAP_FREQ_88KHZ = BIT(8), + + /** 96 Khz sampling frequency */ + + BT_AUDIO_CODEC_CAP_FREQ_96KHZ = BIT(9), + + /** 176.4 Khz sampling frequency */ + + BT_AUDIO_CODEC_CAP_FREQ_176KHZ = BIT(10), + + /** 192 Khz sampling frequency */ + + BT_AUDIO_CODEC_CAP_FREQ_192KHZ = BIT(11), + + /** 384 Khz sampling frequency */ + + BT_AUDIO_CODEC_CAP_FREQ_384KHZ = BIT(12), + + /** Any frequency capability */ + + BT_AUDIO_CODEC_CAP_FREQ_ANY = (BT_AUDIO_CODEC_CAP_FREQ_8KHZ | + BT_AUDIO_CODEC_CAP_FREQ_11KHZ | + BT_AUDIO_CODEC_CAP_FREQ_16KHZ | + BT_AUDIO_CODEC_CAP_FREQ_22KHZ | + BT_AUDIO_CODEC_CAP_FREQ_24KHZ | + BT_AUDIO_CODEC_CAP_FREQ_32KHZ | + BT_AUDIO_CODEC_CAP_FREQ_44KHZ | + BT_AUDIO_CODEC_CAP_FREQ_48KHZ | + BT_AUDIO_CODEC_CAP_FREQ_88KHZ | + BT_AUDIO_CODEC_CAP_FREQ_96KHZ | + BT_AUDIO_CODEC_CAP_FREQ_176KHZ | + BT_AUDIO_CODEC_CAP_FREQ_192KHZ | + BT_AUDIO_CODEC_CAP_FREQ_384KHZ), +}; + +/** @brief Supported frame durations bitfield */ + +enum bt_audio_codec_cap_frame_dur +{ + /** 7.5 msec frame duration capability */ + + BT_AUDIO_CODEC_CAP_DURATION_7_5 = BIT(0), + + /** 10 msec frame duration capability */ + + BT_AUDIO_CODEC_CAP_DURATION_10 = BIT(1), + + /** Any frame duration capability */ + + BT_AUDIO_CODEC_CAP_DURATION_ANY = + (BT_AUDIO_CODEC_CAP_DURATION_7_5 | BT_AUDIO_CODEC_CAP_DURATION_10), + + /** + * @brief 7.5 msec preferred frame duration capability. + * + * This shall only be set if @ref BT_AUDIO_CODEC_CAP_DURATION_7_5 is also + * set, and if @ref BT_AUDIO_CODEC_CAP_DURATION_PREFER_10 is not set. + */ + + BT_AUDIO_CODEC_CAP_DURATION_PREFER_7_5 = BIT(4), + + /** + * @brief 10 msec preferred frame duration capability + * + * This shall only be set if @ref BT_AUDIO_CODEC_CAP_DURATION_10 is also + * set, and if @ref BT_AUDIO_CODEC_CAP_DURATION_PREFER_7_5 is not set. + */ + + BT_AUDIO_CODEC_CAP_DURATION_PREFER_10 = BIT(5), +}; + +/** Supported audio capabilities channel count bitfield */ + +enum bt_audio_codec_cap_chan_count +{ + /** Supporting 1 channel */ + + BT_AUDIO_CODEC_CAP_CHAN_COUNT_1 = BIT(0), + + /** Supporting 2 channel */ + + BT_AUDIO_CODEC_CAP_CHAN_COUNT_2 = BIT(1), + + /** Supporting 3 channel */ + + BT_AUDIO_CODEC_CAP_CHAN_COUNT_3 = BIT(2), + + /** Supporting 4 channel */ + + BT_AUDIO_CODEC_CAP_CHAN_COUNT_4 = BIT(3), + + /** Supporting 5 channel */ + + BT_AUDIO_CODEC_CAP_CHAN_COUNT_5 = BIT(4), + + /** Supporting 6 channel */ + + BT_AUDIO_CODEC_CAP_CHAN_COUNT_6 = BIT(5), + + /** Supporting 7 channel */ + + BT_AUDIO_CODEC_CAP_CHAN_COUNT_7 = BIT(6), + + /** Supporting 8 channel */ + + BT_AUDIO_CODEC_CAP_CHAN_COUNT_8 = BIT(7), + + /** Supporting all channels */ + + BT_AUDIO_CODEC_CAP_CHAN_COUNT_ANY = + (BT_AUDIO_CODEC_CAP_CHAN_COUNT_1 | + BT_AUDIO_CODEC_CAP_CHAN_COUNT_2 | + BT_AUDIO_CODEC_CAP_CHAN_COUNT_3 | + BT_AUDIO_CODEC_CAP_CHAN_COUNT_4 | + BT_AUDIO_CODEC_CAP_CHAN_COUNT_5 | + BT_AUDIO_CODEC_CAP_CHAN_COUNT_6 | + BT_AUDIO_CODEC_CAP_CHAN_COUNT_7 | + BT_AUDIO_CODEC_CAP_CHAN_COUNT_8), +}; + +/** Minimum supported channel counts */ +#define BT_AUDIO_CODEC_CAP_CHAN_COUNT_MIN 1 +/** Maximum supported channel counts */ +#define BT_AUDIO_CODEC_CAP_CHAN_COUNT_MAX 8 + +/** The minimum size in octets of a Broadcast Name */ +#define BT_AUDIO_BROADCAST_NAME_LEN_MIN 4U +/** The maximum size in octets of a Broadcast Name */ +#define BT_AUDIO_BROADCAST_NAME_LEN_MAX 128U +/** The minimum amount of characters of a Broadcast Name */ +#define BT_AUDIO_BROADCAST_NAME_CHAR_MIN 4U +/** The maximum amount of characters of a Broadcast Name */ +#define BT_AUDIO_BROADCAST_NAME_CHAR_MAX 32U + +/** + * @brief Codec configuration types + * + * Used to build and parse codec configurations as specified in the ASCS and + * BAP specifications. Source is assigned numbers for Generic Audio, + * bluetooth.com. + */ + +enum bt_audio_codec_cfg_type +{ + /** Sampling frequency */ + + BT_AUDIO_CODEC_CFG_FREQ = 0x01, + + /** Frame duration */ + + BT_AUDIO_CODEC_CFG_DURATION = 0x02, + + /** Audio channel allocation */ + + BT_AUDIO_CODEC_CFG_CHAN_ALLOC = 0x03, + + /** Octets per codec frame */ + + BT_AUDIO_CODEC_CFG_FRAME_LEN = 0x04, + + /** Codec frame blocks per SDU */ + + BT_AUDIO_CODEC_CFG_FRAME_BLKS_PER_SDU = 0x05, +}; + +/** Codec configuration sampling frequency */ + +enum bt_audio_codec_cfg_freq +{ + /** 8 Khz codec sampling frequency */ + + BT_AUDIO_CODEC_CFG_FREQ_8KHZ = 0x01, + + /** 11.025 Khz codec sampling frequency */ + + BT_AUDIO_CODEC_CFG_FREQ_11KHZ = 0x02, + + /** 16 Khz codec sampling frequency */ + + BT_AUDIO_CODEC_CFG_FREQ_16KHZ = 0x03, + + /** 22.05 Khz codec sampling frequency */ + + BT_AUDIO_CODEC_CFG_FREQ_22KHZ = 0x04, + + /** 24 Khz codec sampling frequency */ + + BT_AUDIO_CODEC_CFG_FREQ_24KHZ = 0x05, + + /** 32 Khz codec sampling frequency */ + + BT_AUDIO_CODEC_CFG_FREQ_32KHZ = 0x06, + + /** 44.1 Khz codec sampling frequency */ + + BT_AUDIO_CODEC_CFG_FREQ_44KHZ = 0x07, + + /** 48 Khz codec sampling frequency */ + + BT_AUDIO_CODEC_CFG_FREQ_48KHZ = 0x08, + + /** 88.2 Khz codec sampling frequency */ + + BT_AUDIO_CODEC_CFG_FREQ_88KHZ = 0x09, + + /** 96 Khz codec sampling frequency */ + + BT_AUDIO_CODEC_CFG_FREQ_96KHZ = 0x0a, + + /** 176.4 Khz codec sampling frequency */ + + BT_AUDIO_CODEC_CFG_FREQ_176KHZ = 0x0b, + + /** 192 Khz codec sampling frequency */ + + BT_AUDIO_CODEC_CFG_FREQ_192KHZ = 0x0c, + + /** 384 Khz codec sampling frequency */ + + BT_AUDIO_CODEC_CFG_FREQ_384KHZ = 0x0d, +}; + +/** Codec configuration frame duration */ + +enum bt_audio_codec_cfg_frame_dur +{ + /** 7.5 msec Frame Duration configuration */ + + BT_AUDIO_CODEC_CFG_DURATION_7_5 = 0x00, + + /** 10 msec Frame Duration configuration */ + + BT_AUDIO_CODEC_CFG_DURATION_10 = 0x01, +}; + +/** + * @brief Audio Context Type for Generic Audio + * + * These values are defined by the Generic Audio Assigned Numbers, + * bluetooth.com + */ + +enum bt_audio_context +{ + /** No context type */ + + BT_AUDIO_CONTEXT_TYPE_NONE = 0, + /** + * Identifies audio where the use case context does not match any other + * defined value, or where the context is unknown or cannot be determined. + */ + + BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED = BIT(0), + /** + * Conversation between humans, for example, in telephony or video calls, + * including traditional cellular as well as VoIP and Push-to-Talk + */ + + BT_AUDIO_CONTEXT_TYPE_CONVERSATIONAL = BIT(1), + + /** Media, for example, music playback, radio, podcast or movie soundtrack, + * or tv audio + */ + + BT_AUDIO_CONTEXT_TYPE_MEDIA = BIT(2), + /** + * Audio associated with video gaming, for example gaming media; gaming + * effects; music and in-game voice chat between participants; or a mix of + * all the above + */ + + BT_AUDIO_CONTEXT_TYPE_GAME = BIT(3), + + /** Instructional audio, for example, in navigation, announcements, or user + * guidance + */ + + BT_AUDIO_CONTEXT_TYPE_INSTRUCTIONAL = BIT(4), + + /** Man-machine communication, for example, with voice recognition or + * virtual assistants + */ + + BT_AUDIO_CONTEXT_TYPE_VOICE_ASSISTANTS = BIT(5), + /** + * Live audio, for example, from a microphone where audio is perceived both + * through a direct acoustic path and through an LE Audio Stream + */ + + BT_AUDIO_CONTEXT_TYPE_LIVE = BIT(6), + /** + * Sound effects including keyboard and touch feedback; menu and user + * interface sounds; and other system sounds + */ + + BT_AUDIO_CONTEXT_TYPE_SOUND_EFFECTS = BIT(7), + /** + * Notification and reminder sounds; attention-seeking audio, for example, + * in beeps signaling the arrival of a message + */ + + BT_AUDIO_CONTEXT_TYPE_NOTIFICATIONS = BIT(8), + /** + * Alerts the user to an incoming call, for example, an incoming telephony + * or video call, including traditional cellular as well as VoIP and + * Push-to-Talk + */ + + BT_AUDIO_CONTEXT_TYPE_RINGTONE = BIT(9), + /** + * Alarms and timers; immediate alerts, for example, in a critical battery + * alarm, timer expiry or alarm clock, toaster, cooker, kettle, microwave, + * etc. + */ + + BT_AUDIO_CONTEXT_TYPE_ALERTS = BIT(10), + + /** Emergency alarm Emergency sounds, for example, fire alarms or other + * urgent alerts + */ + + BT_AUDIO_CONTEXT_TYPE_EMERGENCY_ALARM = BIT(11), +}; + +/** + * Any known context. + */ +#define BT_AUDIO_CONTEXT_TYPE_ANY \ +(BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED | BT_AUDIO_CONTEXT_TYPE_CONVERSATIONAL | \ + BT_AUDIO_CONTEXT_TYPE_MEDIA | BT_AUDIO_CONTEXT_TYPE_GAME | \ + BT_AUDIO_CONTEXT_TYPE_INSTRUCTIONAL | BT_AUDIO_CONTEXT_TYPE_VOICE_ASSISTANTS | \ + BT_AUDIO_CONTEXT_TYPE_LIVE | BT_AUDIO_CONTEXT_TYPE_SOUND_EFFECTS | \ + BT_AUDIO_CONTEXT_TYPE_NOTIFICATIONS | BT_AUDIO_CONTEXT_TYPE_RINGTONE | \ + BT_AUDIO_CONTEXT_TYPE_ALERTS | BT_AUDIO_CONTEXT_TYPE_EMERGENCY_ALARM) + +/** + * @brief Parental rating defined by the Generic Audio assigned numbers + * (bluetooth.com). + * + * The numbering scheme is aligned with Annex F of EN 300 707 v1.2.1 which + * defined parental rating for viewing. + */ + +enum bt_audio_parental_rating +{ + /** No rating */ + + BT_AUDIO_PARENTAL_RATING_NO_RATING = 0x00, + + /** For all ages */ + + BT_AUDIO_PARENTAL_RATING_AGE_ANY = 0x01, + + /** Recommended for listeners of age 5 and above */ + + BT_AUDIO_PARENTAL_RATING_AGE_5_OR_ABOVE = 0x02, + + /** Recommended for listeners of age 6 and above */ + + BT_AUDIO_PARENTAL_RATING_AGE_6_OR_ABOVE = 0x03, + + /** Recommended for listeners of age 7 and above */ + + BT_AUDIO_PARENTAL_RATING_AGE_7_OR_ABOVE = 0x04, + + /** Recommended for listeners of age 8 and above */ + + BT_AUDIO_PARENTAL_RATING_AGE_8_OR_ABOVE = 0x05, + + /** Recommended for listeners of age 9 and above */ + + BT_AUDIO_PARENTAL_RATING_AGE_9_OR_ABOVE = 0x06, + + /** Recommended for listeners of age 10 and above */ + + BT_AUDIO_PARENTAL_RATING_AGE_10_OR_ABOVE = 0x07, + + /** Recommended for listeners of age 11 and above */ + + BT_AUDIO_PARENTAL_RATING_AGE_11_OR_ABOVE = 0x08, + + /** Recommended for listeners of age 12 and above */ + + BT_AUDIO_PARENTAL_RATING_AGE_12_OR_ABOVE = 0x09, + + /** Recommended for listeners of age 13 and above */ + + BT_AUDIO_PARENTAL_RATING_AGE_13_OR_ABOVE = 0x0a, + + /** Recommended for listeners of age 14 and above */ + + BT_AUDIO_PARENTAL_RATING_AGE_14_OR_ABOVE = 0x0b, + + /** Recommended for listeners of age 15 and above */ + + BT_AUDIO_PARENTAL_RATING_AGE_15_OR_ABOVE = 0x0c, + + /** Recommended for listeners of age 16 and above */ + + BT_AUDIO_PARENTAL_RATING_AGE_16_OR_ABOVE = 0x0d, + + /** Recommended for listeners of age 17 and above */ + + BT_AUDIO_PARENTAL_RATING_AGE_17_OR_ABOVE = 0x0e, + + /** Recommended for listeners of age 18 and above */ + + BT_AUDIO_PARENTAL_RATING_AGE_18_OR_ABOVE = 0x0f +}; + +/** @brief Audio Active State defined by the Generic Audio assigned numbers + * (bluetooth.com). + */ + +enum bt_audio_active_state +{ + /** No audio data is being transmitted */ + + BT_AUDIO_ACTIVE_STATE_DISABLED = 0x00, + + /** Audio data is being transmitted */ + + BT_AUDIO_ACTIVE_STATE_ENABLED = 0x01, +}; + +/** Assisted Listening Stream defined by the Generic Audio assigned numbers + * (bluetooth.com). + */ + +enum bt_audio_assisted_listening_stream +{ + /** Unspecified audio enhancement */ + + BT_AUDIO_ASSISTED_LISTENING_STREAM_UNSPECIFIED = 0x00, +}; + +/** + * @brief Codec metadata type IDs + * + * Metadata types defined by the Generic Audio assigned numbers + * (bluetooth.com). + */ + +enum bt_audio_metadata_type +{ + /** + * @brief Preferred audio context. + * + * Bitfield of preferred audio contexts. + * + * If 0, the context type is not a preferred use case for this codec + * configuration. + * + * See the BT_AUDIO_CONTEXT_* for valid values. + */ + + BT_AUDIO_METADATA_TYPE_PREF_CONTEXT = 0x01, + + /** + * @brief Streaming audio context. + * + * Bitfield of streaming audio contexts. + * + * If 0, the context type is not a preferred use case for this codec + * configuration. + * + * See the BT_AUDIO_CONTEXT_* for valid values. + */ + + BT_AUDIO_METADATA_TYPE_STREAM_CONTEXT = 0x02, + + /** UTF-8 encoded title or summary of stream content */ + + BT_AUDIO_METADATA_TYPE_PROGRAM_INFO = 0x03, + + /** + * @brief Language + * + * 3 octet lower case language code defined by ISO 639-3 + * Possible values can be found at + * https://iso639-3.sil.org/code_tables/639/data + */ + + BT_AUDIO_METADATA_TYPE_LANG = 0x04, + + /** Array of 8-bit CCID values */ + + BT_AUDIO_METADATA_TYPE_CCID_LIST = 0x05, + + /** + * @brief Parental rating + * + * See @ref bt_audio_parental_rating for valid values. + */ + + BT_AUDIO_METADATA_TYPE_PARENTAL_RATING = 0x06, + + /** UTF-8 encoded URI for additional Program information */ + + BT_AUDIO_METADATA_TYPE_PROGRAM_INFO_URI = 0x07, + + /** + * @brief Audio active state + * + * See @ref bt_audio_active_state for valid values. + */ + + BT_AUDIO_METADATA_TYPE_AUDIO_STATE = 0x08, + + /** Broadcast Audio Immediate Rendering flag */ + + BT_AUDIO_METADATA_TYPE_BROADCAST_IMMEDIATE = 0x09, + + /** + * @brief Assisted listening stream + * + * See @ref bt_audio_assisted_listening_stream for valid values. + */ + + BT_AUDIO_METADATA_TYPE_ASSISTED_LISTENING_STREAM = 0x0a, + + /** UTF-8 encoded Broadcast name */ + + BT_AUDIO_METADATA_TYPE_BROADCAST_NAME = 0x0b, + + /** Extended metadata */ + + BT_AUDIO_METADATA_TYPE_EXTENDED = 0xfe, + + /** Vendor specific metadata */ + + BT_AUDIO_METADATA_TYPE_VENDOR = 0xff, +}; + +/** + * @brief Location values for BT Audio. + * + * These values are defined by the Generic Audio Assigned Numbers, + * bluetooth.com + */ + +enum bt_audio_location +{ + /** Mono Audio (no specified Audio Location) */ + + BT_AUDIO_LOCATION_MONO_AUDIO = 0, + + /** Front Left */ + + BT_AUDIO_LOCATION_FRONT_LEFT = BIT(0), + + /** Front Right */ + + BT_AUDIO_LOCATION_FRONT_RIGHT = BIT(1), + + /** Front Center */ + + BT_AUDIO_LOCATION_FRONT_CENTER = BIT(2), + + /** Low Frequency Effects 1 */ + + BT_AUDIO_LOCATION_LOW_FREQ_EFFECTS_1 = BIT(3), + + /** Back Left */ + + BT_AUDIO_LOCATION_BACK_LEFT = BIT(4), + + /** Back Right */ + + BT_AUDIO_LOCATION_BACK_RIGHT = BIT(5), + + /** Front Left of Center */ + + BT_AUDIO_LOCATION_FRONT_LEFT_OF_CENTER = BIT(6), + + /** Front Right of Center */ + + BT_AUDIO_LOCATION_FRONT_RIGHT_OF_CENTER = BIT(7), + + /** Back Center */ + + BT_AUDIO_LOCATION_BACK_CENTER = BIT(8), + + /** Low Frequency Effects 2 */ + + BT_AUDIO_LOCATION_LOW_FREQ_EFFECTS_2 = BIT(9), + + /** Side Left */ + + BT_AUDIO_LOCATION_SIDE_LEFT = BIT(10), + + /** Side Right */ + + BT_AUDIO_LOCATION_SIDE_RIGHT = BIT(11), + + /** Top Front Left */ + + BT_AUDIO_LOCATION_TOP_FRONT_LEFT = BIT(12), + + /** Top Front Right */ + + BT_AUDIO_LOCATION_TOP_FRONT_RIGHT = BIT(13), + + /** Top Front Center */ + + BT_AUDIO_LOCATION_TOP_FRONT_CENTER = BIT(14), + + /** Top Center */ + + BT_AUDIO_LOCATION_TOP_CENTER = BIT(15), + + /** Top Back Left */ + + BT_AUDIO_LOCATION_TOP_BACK_LEFT = BIT(16), + + /** Top Back Right */ + + BT_AUDIO_LOCATION_TOP_BACK_RIGHT = BIT(17), + + /** Top Side Left */ + + BT_AUDIO_LOCATION_TOP_SIDE_LEFT = BIT(18), + + /** Top Side Right */ + + BT_AUDIO_LOCATION_TOP_SIDE_RIGHT = BIT(19), + + /** Top Back Center */ + + BT_AUDIO_LOCATION_TOP_BACK_CENTER = BIT(20), + + /** Bottom Front Center */ + + BT_AUDIO_LOCATION_BOTTOM_FRONT_CENTER = BIT(21), + + /** Bottom Front Left */ + + BT_AUDIO_LOCATION_BOTTOM_FRONT_LEFT = BIT(22), + + /** Bottom Front Right */ + + BT_AUDIO_LOCATION_BOTTOM_FRONT_RIGHT = BIT(23), + + /** Front Left Wide */ + + BT_AUDIO_LOCATION_FRONT_LEFT_WIDE = BIT(24), + + /** Front Right Wide */ + + BT_AUDIO_LOCATION_FRONT_RIGHT_WIDE = BIT(25), + + /** Left Surround */ + + BT_AUDIO_LOCATION_LEFT_SURROUND = BIT(26), + + /** Right Surround */ + + BT_AUDIO_LOCATION_RIGHT_SURROUND = BIT(27), +}; + +/** + * Any known location. + */ +#define BT_AUDIO_LOCATION_ANY \ +(BT_AUDIO_LOCATION_FRONT_LEFT | BT_AUDIO_LOCATION_FRONT_RIGHT | BT_AUDIO_LOCATION_FRONT_CENTER | \ + BT_AUDIO_LOCATION_LOW_FREQ_EFFECTS_1 | BT_AUDIO_LOCATION_BACK_LEFT | \ + BT_AUDIO_LOCATION_BACK_RIGHT | BT_AUDIO_LOCATION_FRONT_LEFT_OF_CENTER | \ + BT_AUDIO_LOCATION_FRONT_RIGHT_OF_CENTER | BT_AUDIO_LOCATION_BACK_CENTER | \ + BT_AUDIO_LOCATION_LOW_FREQ_EFFECTS_2 | BT_AUDIO_LOCATION_SIDE_LEFT | \ + BT_AUDIO_LOCATION_SIDE_RIGHT | BT_AUDIO_LOCATION_TOP_FRONT_LEFT | \ + BT_AUDIO_LOCATION_TOP_FRONT_RIGHT | BT_AUDIO_LOCATION_TOP_FRONT_CENTER | \ + BT_AUDIO_LOCATION_TOP_CENTER | BT_AUDIO_LOCATION_TOP_BACK_LEFT | \ + BT_AUDIO_LOCATION_TOP_BACK_RIGHT | BT_AUDIO_LOCATION_TOP_SIDE_LEFT | \ + BT_AUDIO_LOCATION_TOP_SIDE_RIGHT | BT_AUDIO_LOCATION_TOP_BACK_CENTER | \ + BT_AUDIO_LOCATION_BOTTOM_FRONT_CENTER | BT_AUDIO_LOCATION_BOTTOM_FRONT_LEFT | \ + BT_AUDIO_LOCATION_BOTTOM_FRONT_RIGHT | BT_AUDIO_LOCATION_FRONT_LEFT_WIDE | \ + BT_AUDIO_LOCATION_FRONT_RIGHT_WIDE | BT_AUDIO_LOCATION_LEFT_SURROUND | \ + BT_AUDIO_LOCATION_RIGHT_SURROUND) + +/** @} */ /* end of bt_assigned_numbers_audio */ + +/** + * @name Company Identifiers (see Bluetooth Assigned Numbers) + * @{ + */ + +#define BT_COMP_ID_LF 0x05f1 /**< The Linux Foundation */ + +/** @} */ /* end of @name Company Identifiers */ + +/** + * @name Defined BR Class of Device (CoD) values + * @{ + */ + +#define BT_COD(major_service, major_device, minor_device) \ +(((uint32_t)major_service << 13) | ((uint32_t)major_device << 8) | ((uint32_t)minor_device << 2)) +#define BT_COD_VALID(cod) ((0 == (cod[0] & (BIT(0) | BIT(1)))) ? true : false) +#define BT_COD_MAJOR_SERVICE_CLASSES(cod) \ +((((uint32_t)cod[2] & 0xff) >> 5) | (((uint32_t)cod[1] & 0xd0) >> 5)) +#define BT_COD_MAJOR_DEVICE_CLASS(cod) ((((uint32_t)cod[1]) & 0x1ful)) +#define BT_COD_MINOR_DEVICE_CLASS(cod) (((((uint32_t)cod[0]) & 0xff) >> 2)) + +/** + * @name Major Service Classes + * @{ + */ + +/** Limited Discoverable Mode service class */ +#define BT_COD_MAJOR_SVC_CLASS_LIMITED_DISCOVER BIT(13) +/** LE Audio service class */ +#define BT_COD_MAJOR_SVC_CLASS_LE_AUDIO BIT(14) +/** Reserved service class */ +#define BT_COD_MAJOR_SVC_CLASS_RESERVED BIT(15) +/** Positioning service class */ +#define BT_COD_MAJOR_SVC_CLASS_POSITIONING BIT(16) +/** Networking service class */ +#define BT_COD_MAJOR_SVC_CLASS_NETWORKING BIT(17) +/** Rendering service class */ +#define BT_COD_MAJOR_SVC_CLASS_RENDERING BIT(18) +/** Capturing service class */ +#define BT_COD_MAJOR_SVC_CLASS_CAPTURING BIT(19) +/** Object Transfer service class */ +#define BT_COD_MAJOR_SVC_CLASS_OBJECT_TRANSFER BIT(20) +/** Audio service class */ +#define BT_COD_MAJOR_SVC_CLASS_AUDIO BIT(21) +/** Telephony service class */ +#define BT_COD_MAJOR_SVC_CLASS_TELEPHONY BIT(22) +/** Information service class */ +#define BT_COD_MAJOR_SVC_CLASS_INFORMATION BIT(23) + +/** @} */ /* end of Major Service Classes */ + +/** + * @name Major Device Class + * @{ + */ + +/** Miscellaneous major device class */ +#define BT_COD_MAJOR_DEVICE_CLASS_MISCELLANEOUS 0x00 +/** Computer major device class */ +#define BT_COD_MAJOR_DEVICE_CLASS_COMPUTER 0x01 +/** Phone major device class */ +#define BT_COD_MAJOR_DEVICE_CLASS_PHONE 0x02 +/** LAN/Network Access Point major device class */ +#define BT_COD_MAJOR_DEVICE_CLASS_LAN_NETWORK 0x03 +/** Audio/Video major device class */ +#define BT_COD_MAJOR_DEVICE_CLASS_AUDIO_VIDEO 0x04 +/** Peripheral major device class */ +#define BT_COD_MAJOR_DEVICE_CLASS_PERIPHERAL 0x05 +/** Imaging major device class */ +#define BT_COD_MAJOR_DEVICE_CLASS_IMAGING 0x06 +/** Wearable major device class */ +#define BT_COD_MAJOR_DEVICE_CLASS_WEARABLE 0x07 +/** Toy major device class */ +#define BT_COD_MAJOR_DEVICE_CLASS_TOY 0x08 +/** Health major device class */ +#define BT_COD_MAJOR_DEVICE_CLASS_HEALTH 0x09 +/** Uncategorized major device class */ +#define BT_COD_MAJOR_DEVICE_CLASS_UNCATEGORIZED 0x1f + +/** @} */ /* end of Major Device Class */ + +/** + * @name Minor Device Class - Computer Major Class + * @{ + */ + +/** Uncategorized computer minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_COMPUTER_UNCATEGORIZED 0x00 +/** Desktop workstation computer minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_COMPUTER_DESKTOP_WORKSTATION 0x01 +/** Server-class computer minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_COMPUTER_SERVER 0x02 +/** Laptop computer minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_COMPUTER_LAPTOP 0x03 +/** Handheld PC/PDA computer minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_COMPUTER_HANDHELD_PC_PDA 0x04 +/** Palm-sized PC/PDA computer minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_COMPUTER_PALM_SIZED_PC_PDA 0x05 +/** Wearable computer minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_COMPUTER_WEARABLE_COMPUTER 0x06 +/** Tablet computer minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_COMPUTER_TABLET 0x07 + +/** @} */ /* end of Minor Device Class - Computer Major Class */ + +/** + * @name Minor Device Class - Phone Major Class + * @{ + */ + +/** Uncategorized phone minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_PHONE_UNCATEGORIZED 0x00 +/** Cellular phone minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_PHONE_CELLULAR 0x01 +/** Cordless phone minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_PHONE_CORDLESS 0x02 +/** Smartphone minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_PHONE_SMARTPHONE 0x03 +/** Wired modem or voice gateway phone minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_PHONE_WIRED_MODEM 0x04 +/** Common ISDN access phone minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_PHONE_COMMON_ISDN_ACCESS 0x05 + +/** @} */ /* end of Minor Device Class - Phone Major Class */ + +/** + * @name Minor Device Class - LAN/Network Major Class - Service Utilization + * @{ + */ + +/** LAN service utilization: fully available */ +#define BT_COD_MINOR_DEVICE_CLASS_LAN_NETWORK_SERVICE_FULLY_AVAILABLE 0x00 +/** LAN service utilization: 1-17% utilized */ +#define BT_COD_MINOR_DEVICE_CLASS_LAN_NETWORK_SERVICE_1_17_UTILIZED 0x01 +/** LAN service utilization: 17-33% utilized */ +#define BT_COD_MINOR_DEVICE_CLASS_LAN_NETWORK_SERVICE_17_33_UTILIZED 0x02 +/** LAN service utilization: 33-50% utilized */ +#define BT_COD_MINOR_DEVICE_CLASS_LAN_NETWORK_SERVICE_33_50_UTILIZED 0x03 +/** LAN service utilization: 50-67% utilized */ +#define BT_COD_MINOR_DEVICE_CLASS_LAN_NETWORK_SERVICE_50_67_UTILIZED 0x04 +/** LAN service utilization: 67-83% utilized */ +#define BT_COD_MINOR_DEVICE_CLASS_LAN_NETWORK_SERVICE_67_83_UTILIZED 0x05 +/** LAN service utilization: 83-99% utilized */ +#define BT_COD_MINOR_DEVICE_CLASS_LAN_NETWORK_SERVICE_83_99_UTILIZED 0x06 +/** LAN service utilization: no service available */ +#define BT_COD_MINOR_DEVICE_CLASS_LAN_NETWORK_SERVICE_NO_SERVICE_AVAILABLE 0x07 + +/** @} */ /* end of Minor Device Class - LAN/Network Major Class - Service Utilization */ + +/** + * @name Minor Device Class - LAN/Network Major Class - Device Type + * @{ + */ + +/** Uncategorized LAN/network device type */ +#define BT_COD_MINOR_DEVICE_CLASS_LAN_NETWORK_DEVICE_TYPE_UNCATEGORIZED 0x00 + +/** Minor Device Class - Audio/Video Major Class */ + +/** Uncategorized audio/video minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_AUDIO_VIDEO_UNCATEGORIZED 0x00 +/** Wearable headset audio/video minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_AUDIO_VIDEO_WEARABLE_HEADSET 0x01 +/** Handsfree audio/video minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_AUDIO_VIDEO_HANDSFREE 0x02 +/** Microphone audio/video minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_AUDIO_VIDEO_MICROPHONE 0x04 +/** Loudspeaker audio/video minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_AUDIO_VIDEO_LOUDSPEAKER 0x05 +/** Headphones audio/video minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_AUDIO_VIDEO_HEADPHONES 0x06 +/** Portable audio audio/video minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_AUDIO_VIDEO_PORTABLE_AUDIO 0x07 +/** Car audio audio/video minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_AUDIO_VIDEO_CAR_AUDIO 0x08 +/** Set-top box audio/video minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_AUDIO_VIDEO_SETTOP_BOX 0x09 +/** HiFi audio audio/video minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_AUDIO_VIDEO_HIFI_AUDIO 0x0a +/** VCR audio/video minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_AUDIO_VIDEO_VCR 0x0b +/** Video camera audio/video minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_AUDIO_VIDEO_VIDEO_CAMERA 0x0c +/** Camcorder audio/video minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_AUDIO_VIDEO_CAMCORDER 0x0d +/** Video monitor audio/video minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_AUDIO_VIDEO_VIDEO_MONITOR 0x0e +/** Video display and loudspeaker audio/video minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_AUDIO_VIDEO_VIDEO_DISPLAY_LOUDSPEAKER 0x0f +/** Video conferencing audio/video minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_AUDIO_VIDEO_VIDEO_CONFERENCING 0x10 +/** Gaming toy audio/video minor device class */ +#define BT_COD_MINOR_DEVICE_CLASS_AUDIO_VIDEO_GAMING_TOY 0x12 + +/** @} */ /* end of Minor Device Class - Audio/Video Major Class */ + +/** + * @name Minor Device Class - Peripheral Major Class - Input Device Type + * @{ + */ + +/** Peripheral input: no keyboard, no pointing device */ +#define BT_COD_MINOR_DEVICE_CLASS_PERIPHERAL_INPUT_DEVICE_TYPE_NO_KEYBOARD_NO_POINTING 0x00 +/** Peripheral input: keyboard */ +#define BT_COD_MINOR_DEVICE_CLASS_PERIPHERAL_INPUT_DEVICE_TYPE_KEYBOARD 0x01 +/** Peripheral input: pointing device */ +#define BT_COD_MINOR_DEVICE_CLASS_PERIPHERAL_INPUT_DEVICE_TYPE_POINTING_DEVICE 0x02 +/** Peripheral input: combo keyboard and pointing device */ +#define BT_COD_MINOR_DEVICE_CLASS_PERIPHERAL_INPUT_DEVICE_TYPE_COMBO_KEYBOARD_POINTING 0x03 + +/** @} */ /* end of Minor Device Class - Peripheral Major Class - Input Device Type */ + +/** + * @name Minor Device Class - Peripheral Major Class - Device Type + * @{ + */ + +/** Uncategorized peripheral device type */ +#define BT_COD_MINOR_DEVICE_CLASS_PERIPHERAL_DEVICE_TYPE_UNCATEGORIZED 0x00 +/** Joystick peripheral device type */ +#define BT_COD_MINOR_DEVICE_CLASS_PERIPHERAL_DEVICE_TYPE_JOYSTICK 0x01 +/** Gamepad peripheral device type */ +#define BT_COD_MINOR_DEVICE_CLASS_PERIPHERAL_DEVICE_TYPE_GAMEPAD 0x02 +/** Remote control peripheral device type */ +#define BT_COD_MINOR_DEVICE_CLASS_PERIPHERAL_DEVICE_TYPE_REMOTE_CONTROL 0x03 +/** Sensing device peripheral device type */ +#define BT_COD_MINOR_DEVICE_CLASS_PERIPHERAL_DEVICE_TYPE_SENSING_DEVICE 0x04 +/** Digitizer tablet peripheral device type */ +#define BT_COD_MINOR_DEVICE_CLASS_PERIPHERAL_DEVICE_TYPE_DIGITIZER_TABLET 0x05 +/** Card reader peripheral device type */ +#define BT_COD_MINOR_DEVICE_CLASS_PERIPHERAL_DEVICE_TYPE_CARD_READER 0x06 +/** Digital pen peripheral device type */ +#define BT_COD_MINOR_DEVICE_CLASS_PERIPHERAL_DEVICE_TYPE_DIGITAL_PEN 0x07 +/** Handheld scanner peripheral device type */ +#define BT_COD_MINOR_DEVICE_CLASS_PERIPHERAL_DEVICE_TYPE_HANDHELD_SCANNER 0x08 +/** Handheld gestural input peripheral device type */ +#define BT_COD_MINOR_DEVICE_CLASS_PERIPHERAL_DEVICE_TYPE_HANDHELD_GESTURAL_INPUT 0x09 + +/** @} */ /* end of Minor Device Class - Peripheral Major Class - Device Type */ + +/** + * @name Minor Device Class - Imaging Major Class + * @{ + */ + +/** Minor Device Class - Imaging Major Class */ +#define BT_COD_MINOR_DEVICE_CLASS_IMAGING_DISPLAY 0x01 +/** Imaging camera device */ +#define BT_COD_MINOR_DEVICE_CLASS_IMAGING_CAMERA 0x02 +/** Imaging scanner device */ +#define BT_COD_MINOR_DEVICE_CLASS_IMAGING_SCANNER 0x04 +/** Imaging printer device */ +#define BT_COD_MINOR_DEVICE_CLASS_IMAGING_PRINTER 0x08 + +/** Minor Device Class - Imaging Major Class Uncategorized */ +#define BT_COD_MINOR_DEVICE_CLASS_IMAGING_UNCATEGORIZED 0x00 + +/** @} */ /* end of Minor Device Class - Imaging Major Class */ + +/** + * @name Minor Device Class - Wearable Major Class + * @{ + */ + +/** Wearable headset device */ +#define BT_COD_MINOR_DEVICE_CLASS_WEARABLE_HEADSET 0x01 +/** Wearable pager device */ +#define BT_COD_MINOR_DEVICE_CLASS_WEARABLE_PAGER 0x02 +/** Wearable jacket device */ +#define BT_COD_MINOR_DEVICE_CLASS_WEARABLE_JACKET 0x03 +/** Wearable helmet device */ +#define BT_COD_MINOR_DEVICE_CLASS_WEARABLE_HELMET 0x04 +/** Wearable glasses device */ +#define BT_COD_MINOR_DEVICE_CLASS_WEARABLE_GLASSES 0x05 + +/** @} */ /* end of Minor Device Class - Wearable Major Class */ + +/** + * @name Minor Device Class - Toy Major Class + * @{ + */ + +/** Toy robot device */ +#define BT_COD_MINOR_DEVICE_CLASS_TOY_ROBOT 0x01 +/** Toy vehicle device */ +#define BT_COD_MINOR_DEVICE_CLASS_TOY_VEHICLE 0x02 +/** Toy doll device */ +#define BT_COD_MINOR_DEVICE_CLASS_TOY_DOLL 0x03 +/** Toy controller device */ +#define BT_COD_MINOR_DEVICE_CLASS_TOY_CONTROLLER 0x04 +/** Toy game device */ +#define BT_COD_MINOR_DEVICE_CLASS_TOY_GAME 0x05 + +/** @} */ /* end of Minor Device Class - Toy Major Class */ + +/** + * @name Minor Device Class - Health Major Class + * @{ + */ + +/** Undefined health device */ +#define BT_COD_MINOR_DEVICE_CLASS_HEALTH_UNDEFINED 0x00 +/** Blood pressure monitor health device */ +#define BT_COD_MINOR_DEVICE_CLASS_HEALTH_BLOOD_PRESSURE_MONITOR 0x01 +/** Thermometer health device */ +#define BT_COD_MINOR_DEVICE_CLASS_HEALTH_THERMOMETER 0x02 +/** Weighing scale health device */ +#define BT_COD_MINOR_DEVICE_CLASS_HEALTH_WEIGHING_SCALE 0x03 +/** Glucose meter health device */ +#define BT_COD_MINOR_DEVICE_CLASS_HEALTH_GLUCOSE_METER 0x04 +/** Pulse oximeter health device */ +#define BT_COD_MINOR_DEVICE_CLASS_HEALTH_PULSE_OXIMETER 0x05 +/** Heart/pulse monitor health device */ +#define BT_COD_MINOR_DEVICE_CLASS_HEALTH_HEART_PULSE_MONITOR 0x06 +/** Health data display device */ +#define BT_COD_MINOR_DEVICE_CLASS_HEALTH_HEALTH_DATA_DISPLAY 0x07 +/** Step counter health device */ +#define BT_COD_MINOR_DEVICE_CLASS_HEALTH_STEP_COUNTER 0x08 +/** Body composition analyzer health device */ +#define BT_COD_MINOR_DEVICE_CLASS_HEALTH_BODY_COMPOSITION_ANALYZER 0x09 +/** Peak flow monitor health device */ +#define BT_COD_MINOR_DEVICE_CLASS_HEALTH_PEAK_FLOW_MONITOR 0x0a +/** Medication monitor health device */ +#define BT_COD_MINOR_DEVICE_CLASS_HEALTH_MEDICATION_MONITOR 0x0b +/** Knee prosthesis health device */ +#define BT_COD_MINOR_DEVICE_CLASS_HEALTH_KNEE_PROSTHESIS 0x0c +/** Ankle prosthesis health device */ +#define BT_COD_MINOR_DEVICE_CLASS_HEALTH_ANKLE_PROSTHESIS 0x0d +/** Generic health manager device */ +#define BT_COD_MINOR_DEVICE_CLASS_HEALTH_GENERIC_HEALTH_MANAGER 0x0e +/** Personal mobility health device */ +#define BT_COD_MINOR_DEVICE_CLASS_HEALTH_PERSONAL_MOBILITY_DEVICE 0x0f + +/** @} */ /* end of Minor Device Class - Health Major Class */ + +/** @} */ /* end of @name BR Class of Device */ + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_ASSIGNED_NUMBERS_H */ diff --git a/include/nuttx/net/bluetooth/att.h b/include/nuttx/net/bluetooth/att.h new file mode 100644 index 0000000000000..ed82b6389ce9e --- /dev/null +++ b/include/nuttx/net/bluetooth/att.h @@ -0,0 +1,310 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/att.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Attribute Protocol handling. + */ + +/* Copyright (c) 2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_ATT_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_ATT_H + +/** + * @brief Attribute Protocol (ATT) + * @defgroup bt_att Attribute Protocol (ATT) + * @since 1.1 + * @version 1.1.0 + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Error codes for Error response PDU + * + * Defined by The Bluetooth Core Specification, Version 5.4, Vol 3, Part F, + * Section 3.4.1.1 + */ + +/** The ATT operation was successful */ +#define BT_ATT_ERR_SUCCESS 0x00 +/** The attribute handle given was not valid on the server */ +#define BT_ATT_ERR_INVALID_HANDLE 0x01 +/** The attribute cannot be read */ +#define BT_ATT_ERR_READ_NOT_PERMITTED 0x02 +/** The attribute cannot be written */ +#define BT_ATT_ERR_WRITE_NOT_PERMITTED 0x03 +/** The attribute PDU was invalid */ +#define BT_ATT_ERR_INVALID_PDU 0x04 +/** The attribute requires authentication before it can be read or written */ +#define BT_ATT_ERR_AUTHENTICATION 0x05 +/** The ATT Server does not support the request received from the client */ +#define BT_ATT_ERR_NOT_SUPPORTED 0x06 +/** Offset specified was past the end of the attribute */ +#define BT_ATT_ERR_INVALID_OFFSET 0x07 +/** The attribute requires authorization before it can be read or written */ +#define BT_ATT_ERR_AUTHORIZATION 0x08 +/** Too many prepare writes have been queued */ +#define BT_ATT_ERR_PREPARE_QUEUE_FULL 0x09 +/** No attribute found within the given attribute handle range */ +#define BT_ATT_ERR_ATTRIBUTE_NOT_FOUND 0x0a +/** The attribute cannot be read using the ATT_READ_BLOB_REQ PDU */ +#define BT_ATT_ERR_ATTRIBUTE_NOT_LONG 0x0b +/** The Encryption Key Size used for encrypting this link is too short */ +#define BT_ATT_ERR_ENCRYPTION_KEY_SIZE 0x0c +/** The attribute value length is invalid for the operation */ +#define BT_ATT_ERR_INVALID_ATTRIBUTE_LEN 0x0d +/** + * @brief The attribute request that was requested has encountered an error + * that was unlikely + * + * The attribute request could therefore not be completed as requested + */ +#define BT_ATT_ERR_UNLIKELY 0x0e +/** The attribute requires encryption before it can be read or written */ +#define BT_ATT_ERR_INSUFFICIENT_ENCRYPTION 0x0f +/** + * @brief The attribute type is not a supported grouping attribute + * + * The attribute type is not a supported grouping attribute as defined by a + * higher layer specification. + */ +#define BT_ATT_ERR_UNSUPPORTED_GROUP_TYPE 0x10 +/** Insufficient Resources to complete the request */ +#define BT_ATT_ERR_INSUFFICIENT_RESOURCES 0x11 +/** The server requests the client to rediscover the database */ +#define BT_ATT_ERR_DB_OUT_OF_SYNC 0x12 +/** The attribute parameter value was not allowed */ +#define BT_ATT_ERR_VALUE_NOT_ALLOWED 0x13 + +/* Common Profile Error Codes + * + * Defined by the Supplement to the Bluetooth Core Specification (CSS), v11, + * Part B, Section 1.2. + */ + +/** Write Request Rejected */ +#define BT_ATT_ERR_WRITE_REQ_REJECTED 0xfc +/** Client Characteristic Configuration Descriptor Improperly Configured */ +#define BT_ATT_ERR_CCC_IMPROPER_CONF 0xfd +/** Procedure Already in Progress */ +#define BT_ATT_ERR_PROCEDURE_IN_PROGRESS 0xfe +/** Out of Range */ +#define BT_ATT_ERR_OUT_OF_RANGE 0xff + +/* Version 5.2, Vol 3, Part F, 3.2.9 defines maximum attribute length to + * 512. + */ +#define BT_ATT_MAX_ATTRIBUTE_LEN 512 + +/* Handle 0x0000 is reserved for future use */ +#define BT_ATT_FIRST_ATTRIBUTE_HANDLE 0x0001 +/* 0xffff is defined as the maximum, and thus last, valid attribute handle */ +#define BT_ATT_LAST_ATTRIBUTE_HANDLE 0xffff + +/** Converts a ATT error to string. + * + * The error codes are described in the Bluetooth Core specification, Vol 3, + * Part F, Section 3.4.1.1 and in The Supplement to the Bluetooth Core + * Specification (CSS), v11, Part B, Section 1.2. + * + * The ATT and GATT documentation found in Vol 4, Part F and Part G describe + * when the different error codes are used. + * + * See also the defined BT_ATT_ERR_* macros. + * + * @return The string representation of the ATT error code. + * If @kconfig{CONFIG_BT_ATT_ERR_TO_STR} is not enabled, this just returns + * the empty string + */ +#if defined(CONFIG_BT_ATT_ERR_TO_STR) +const char *bt_att_err_to_str(uint8_t att_err); +#else +#include + +static inline const char *bt_att_err_to_str(uint8_t att_err) +{ +ARG_UNUSED(att_err); + +return ""; +} +#endif + +/** @cond INTERNAL_HIDDEN */ + +/** + * @brief Test function to disconnect a single EATT bearer + * + * @kconfig_dep{CONFIG_BT_EATT,CONFIG_BT_TESTING} + * + * @param conn The connection to disconnect the EATT bearer on + * @return 0 in case of success, else negative errno value + */ + +int bt_eatt_disconnect_one(struct bt_conn *conn); + +/** + * @brief Test function to reconfigure all EATT channels on connection + * + * @kconfig_dep{CONFIG_BT_EATT,CONFIG_BT_TESTING} + * + * @param conn The connection to disconnect the EATT bearer on + * @return 0 in case of success, else negative errno value + */ + +int bt_eatt_reconfigure(struct bt_conn *conn, uint16_t mtu); + +/** @endcond */ + +/** @brief Connect Enhanced ATT channels + * + * Sends a series of Credit Based Connection Requests to connect @p + * num_channels Enhanced ATT channels. The peer may have limited resources + * and fewer channels may be created. + * + * @kconfig_dep{CONFIG_BT_EATT} + * + * @param conn The connection to send the request on + * @param num_channels The number of Enhanced ATT bearers to request. Must be + * in the range 1 - @kconfig{CONFIG_BT_EATT_MAX}, + * inclusive. + * + * @return 0 in case of success or negative value in case of error. + * @retval -EINVAL if @p num_channels is not in the allowed range or @p conn + * is NULL. + * @retval -ENOMEM if less than @p num_channels are allocated. + * @retval 0 in case of success + */ + +int bt_eatt_connect(struct bt_conn *conn, size_t num_channels); + +/** @brief Get number of EATT channels connected. + * + * @kconfig_dep{CONFIG_BT_EATT} + * + * @param conn The connection to get the number of EATT channels for. + * + * @return The number of EATT channels connected. + * Returns 0 if @p conn is NULL or not connected. + */ + +size_t bt_eatt_count(struct bt_conn *conn); + +/** @brief ATT channel option bit field values. + * @note @ref BT_ATT_CHAN_OPT_UNENHANCED_ONLY and @ref + * BT_ATT_CHAN_OPT_ENHANCED_ONLY are mutually exclusive and both bits may not + * be set. + */ + +enum bt_att_chan_opt +{ + /** Both Enhanced and Unenhanced channels can be used */ + + BT_ATT_CHAN_OPT_NONE = 0x0, + + /** Only Unenhanced channels will be used */ + + BT_ATT_CHAN_OPT_UNENHANCED_ONLY = BIT(0), + + /** Only Enhanced channels will be used */ + + BT_ATT_CHAN_OPT_ENHANCED_ONLY = BIT(1), +}; + +/** + * @brief Get the maximum notification value size for a connection + * + * The value of an ATT_HANDLE_VALUE_NTF PDU is limited to ATT_MTU - 3, where + * the 3 octets are the opcode and the attribute handle, as defined by the + * Bluetooth Core Specification, Version 6.1, Vol 3, Part F, Section 3.4.7.1. + * + * Values larger than this either have to be truncated by the sender, or read + * by the client with a read request. + * + * @note The ATT_MTU may change during the lifetime of a connection, so the + * result of this function shall not be cached. + * + * @param conn The LE ACL connection to get the maximum notification + * value size for. + * @param chan_opt Bearer to get the maximum notification value size for. + * Shall be either @ref BT_ATT_CHAN_OPT_NONE, @ref + * BT_ATT_CHAN_OPT_UNENHANCED_ONLY or @ref + * BT_ATT_CHAN_OPT_ENHANCED_ONLY (only if + * @kconfig{CONFIG_BT_EATT} is enabled) + * @ref BT_ATT_CHAN_OPT_NONE will get the value for any + * bearer. + * + * @return The maximum notification value size in octets on success. + * @retval -EINVAL @p conn is NULL, not an ACL connection, or @p chan_opt is + * invalid. + * @retval -ENOTCONN @p conn is not connected. + */ + +int bt_att_get_max_notify_size(struct bt_conn *conn, + enum bt_att_chan_opt chan_opt); + +/** + * @brief Get the maximum indication value size for a connection + * + * The value of an ATT_HANDLE_VALUE_IND PDU is limited to ATT_MTU - 3, where + * the 3 octets are the opcode and the attribute handle, as defined by the + * Bluetooth Core Specification, Version 6.1, Vol 3, Part F, Section 3.4.7.2. + * This is the same limit as for notifications. + * + * Values larger than this either have to be truncated by the sender, or read + * by the client with a read request. + * + * @note The ATT_MTU may change during the lifetime of a connection, so the + * result of this function shall not be cached. + * + * @param conn The LE ACL connection to get the maximum indication value + * size for. + * @param chan_opt Bearer to get the maximum indication value size for. Shall + * be either @ref BT_ATT_CHAN_OPT_NONE, @ref + * BT_ATT_CHAN_OPT_UNENHANCED_ONLY or @ref + * BT_ATT_CHAN_OPT_ENHANCED_ONLY (only if + * @kconfig{CONFIG_BT_EATT} is enabled) + * @ref BT_ATT_CHAN_OPT_NONE will get the value for any + * bearer. + * + * @return The maximum indication value size in octets on success. + * @retval -EINVAL @p conn is NULL, not an ACL connection, or @p chan_opt is + * invalid. + * @retval -ENOTCONN @p conn is not connected. + */ + +static inline int bt_att_get_max_indicate_size(struct bt_conn *conn, + enum bt_att_chan_opt chan_opt) +{ + return bt_att_get_max_notify_size(conn, chan_opt); +} + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_ATT_H */ diff --git a/include/nuttx/net/bluetooth/audio/aics.h b/include/nuttx/net/bluetooth/audio/aics.h new file mode 100644 index 0000000000000..b0a3eccfdb17f --- /dev/null +++ b/include/nuttx/net/bluetooth/audio/aics.h @@ -0,0 +1,696 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/audio/aics.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Bluetooth Audio Input Control Service APIs. + */ + +/* Copyright (c) 2020-2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_AICS_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_AICS_H + +/** + * @brief Audio Input Control Service (AICS) + * + * @defgroup bt_aics Audio Input Control Service (AICS) + * + * @since 2.6 + * @version 0.8.0 + * + * @ingroup bluetooth + * @{ + * + * The Audio Input Control Service is a secondary service, and as such should + * not be used on its own, but rather in the context of another (primary) + * service. + * + * This API implements both the server and client functionality. Note that + * the API abstracts away the change counter in the audio input control state + * and will automatically handle any changes to that. If out of date, the + * client implementation will autonomously read the change counter value when + * executing a write request. + * + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @name Audio Input Control Service mute states + * @{ + */ + +/** The mute state is unmuted */ +#define BT_AICS_STATE_UNMUTED 0x00U +/** The mute state is muted */ +#define BT_AICS_STATE_MUTED 0x01U +/** The mute state is disabled */ +#define BT_AICS_STATE_MUTE_DISABLED 0x02U +/** @} */ + +/** + * @name Audio Input Control Service input modes + * @{ + */ + +/** + * @brief The gain mode is manual only and cannot be changed to automatic. + * + * The gain can be controlled by the client. + */ +#define BT_AICS_MODE_MANUAL_ONLY 0x00U +/** + * @brief The gain mode is automatic only and cannot be changed to manual. + * + * The gain cannot be controlled by the client. + */ +#define BT_AICS_MODE_AUTO_ONLY 0x01U +/** + * @brief The gain mode is manual. + * + * The gain can be controlled by the client. + */ +#define BT_AICS_MODE_MANUAL 0x02U +/** + * @brief The gain mode is automatic. + * + * The gain cannot be controlled by the client. + */ +#define BT_AICS_MODE_AUTO 0x03U +/** @} */ + +/** + * @brief Convert an AICS gain mode to a human-readable string. + * + * @param mode The gain mode (BT_AICS_MODE_*) value. + * + * @return A string representation of the gain mode. + */ + +static inline const char *bt_aics_mode_to_str(uint8_t mode) +{ + switch (mode) + { + case BT_AICS_MODE_MANUAL_ONLY: + return "Manual only"; + case BT_AICS_MODE_AUTO_ONLY: + return "Auto only"; + case BT_AICS_MODE_MANUAL: + return "Manual"; + case BT_AICS_MODE_AUTO: + return "Auto"; + default: + return "Unknown"; + } +} + +/** + * @name Audio Input Control Service input types + * @{ + */ + +/** The input is unspecified */ +#define BT_AICS_INPUT_TYPE_UNSPECIFIED 0x00U +/** The input is a Bluetooth Audio Stream */ +#define BT_AICS_INPUT_TYPE_BLUETOOTH 0x01U +/** The input is a microphone */ +#define BT_AICS_INPUT_TYPE_MICROPHONE 0x02U +/** The input is analog */ +#define BT_AICS_INPUT_TYPE_ANALOG 0x03U +/** The input is digital */ +#define BT_AICS_INPUT_TYPE_DIGITAL 0x04U +/** The input is a radio (AM/FM/XM/etc.) */ +#define BT_AICS_INPUT_TYPE_RADIO 0x05U +/** The input is a Streaming Audio Source */ +#define BT_AICS_INPUT_TYPE_STREAMING 0x06U +/** The input is transparent / pass-through */ +#define BT_AICS_INPUT_TYPE_AMBIENT 0x07U +/** @} */ + +/** + * @name Audio Input Control Service Error codes + * @{ + */ + +/* The Change_Counter operand value does not match the Change_Counter field + * value of the Audio Input State characteristic. + */ +#define BT_AICS_ERR_INVALID_COUNTER 0x80U +/** An invalid opcode has been used in a control point procedure */ +#define BT_AICS_ERR_OP_NOT_SUPPORTED 0x81U +/** Mute/unmute commands are disabled.(see @ref BT_AICS_STATE_MUTE_DISABLED) + */ +#define BT_AICS_ERR_MUTE_DISABLED 0x82U +/** An operand value used in a control point procedure is outside the + * permissible range + */ +#define BT_AICS_ERR_OUT_OF_RANGE 0x83U +/** A requested gain mode change is not allowed */ +#define BT_AICS_ERR_GAIN_MODE_NOT_ALLOWED 0x84U +/** @} */ + +/** + * @struct bt_aics + * @brief Opaque Audio Input Control Service instance. + */ + +struct bt_aics; + +/** @brief Structure for initializing a Audio Input Control Service instance. + */ + +struct bt_aics_register_param +{ + /** Initial audio input gain (-128 to 127) */ + + int8_t gain; + + /** Initial audio input mute state */ + + uint8_t mute; + + /** Initial audio input mode */ + + uint8_t gain_mode; + + /** Initial audio input gain units (N * 0.1 dB) */ + + uint8_t units; + + /** Initial audio input minimum gain */ + + int8_t min_gain; + + /** Initial audio input maximum gain */ + + int8_t max_gain; + + /** Initial audio input type */ + + uint8_t type; + + /** Initial audio input status (active/inactive) */ + + bool status; + + /** Boolean to set whether the description is writable by clients */ + + bool desc_writable; + + /** Initial audio input description */ + + char *description; + + /** Pointer to the callback structure. */ + + struct bt_aics_cb *cb; +}; + +/** @brief Structure for discovering a Audio Input Control Service instance. + */ + +struct bt_aics_discover_param +{ + /* @brief The start handle of the discovering. + * + * Typically the @p start_handle of a @ref bt_gatt_include. + */ + + uint16_t start_handle; + /* @brief The end handle of the discovering. + * + * Typically the @p end_handle of a @ref bt_gatt_include. + */ + + uint16_t end_handle; +}; + +/** + * @brief Get a free instance of Audio Input Control Service from the pool. + * + * @return Audio Input Control Service instance in case of success or NULL in + * case of error. + */ + +struct bt_aics *bt_aics_free_instance_get(void); + +/** + * @brief Get the service declaration attribute. + * + * The first service attribute returned can be included in any other GATT + * service. + * + * @param aics Audio Input Control Service instance. + * + * @return Pointer to the attributes of the service. + */ + +void *bt_aics_svc_decl_get(struct bt_aics *aics); + +/** + * @brief Get the connection pointer of a client instance + * + * Get the Bluetooth connection pointer of a Audio Input Control Service + * client instance. + * + * @param aics Audio Input Control Service client instance pointer. + * @param conn Connection pointer. + * + * @return 0 if success, errno on failure. + */ + +int bt_aics_client_conn_get(const struct bt_aics *aics, + struct bt_conn **conn); + +/** + * @brief Initialize the Audio Input Control Service instance. + * + * @param aics Audio Input Control Service instance. + * @param param Audio Input Control Service register parameters. + * + * @return 0 if success, errno on failure. + */ + +int bt_aics_register(struct bt_aics *aics, + struct bt_aics_register_param *param); + +/** + * @brief Callback function for writes. + * + * @param inst The instance pointer. + * @param err Error value. 0 on success, GATT error on positive + * value or errno on negative value. + */ + +typedef void (*bt_aics_write_cb)(struct bt_aics *inst, int err); + +/** + * @brief Callback function for the input state. + * + * Called when the value is read, or if the value is changed by either the + * server or client. + * + * @param inst The instance pointer. + * @param err Error value. 0 on success, GATT error on positive + * value or errno on negative value. For notifications, + * this will always be 0. + * @param gain The gain setting value. + * @param mute The mute value. + * @param mode The mode value. + */ + +typedef void (*bt_aics_state_cb)( + struct bt_aics *inst, int err, int8_t gain, uint8_t mute, uint8_t mode); + +/** + * @brief Callback function for the gain settings. + * + * Called when the value is read, or if the value is changed by either the + * server or client. + * + * @param inst The instance pointer. + * @param err Error value. 0 on success, GATT error on positive + * value or errno on negative value. For notifications, + * this will always be 0. + * @param units The value that reflect the size of a single increment + * or decrement of the Gain Setting value in 0.1 decibel + * units. + * @param minimum The minimum gain allowed for the gain setting. + * @param maximum The maximum gain allowed for the gain setting. + */ + +typedef void (*bt_aics_gain_setting_cb)( + struct bt_aics *inst, int err, uint8_t units, int8_t minimum, + int8_t maximum); + +/** + * @brief Callback function for the input type. + * + * Called when the value is read, or if the value is changed by either the + * server or client. + * + * @param inst The instance pointer. + * @param err Error value. 0 on success, GATT error on positive + * value or errno on negative value. For notifications, + * this will always be 0. + * @param type The input type. + */ + +typedef void (*bt_aics_type_cb)(struct bt_aics *inst, int err, uint8_t type); + +/** + * @brief Callback function for the input status. + * + * Called when the value is read, or if the value is changed by either the + * server or client. + * + * @param inst The instance pointer. + * @param err Error value. 0 on success, GATT error on positive + * value or errno on negative value. For notifications, + * this will always be 0. + * @param active Whether the instance is active or inactive. + */ + +typedef void (*bt_aics_status_cb)(struct bt_aics *inst, + int err, + bool active); + +/** + * @brief Callback function for the description. + * + * Called when the value is read, or if the value is changed by either the + * server or client. + * + * @param inst The instance pointer. + * @param err Error value. 0 on success, GATT error on positive + * value or errno on negative value. For notifications, + * this will always be 0. + * @param description The description as an UTF-8 encoded string (may have + * been clipped). + */ + +typedef void (*bt_aics_description_cb)( + struct bt_aics *inst, int err, char *description); + +/** + * @brief Callback function for bt_aics_discover. + * + * This callback will usually be overwritten by the primary service that + * includes the Audio Input Control Service client. + * + * @param inst The instance pointer. + * @param err Error value. 0 on success, GATT error on positive + * value or errno on negative value. For notifications, + * this will always be 0. + */ + +typedef void (*bt_aics_discover_cb)(struct bt_aics *inst, int err); + +/** + * @brief Struct to hold callbacks for the Audio Input Control Service. + * + * Used by both clients and servers + */ + +struct bt_aics_cb +{ + /** The audio input state has changed */ + + bt_aics_state_cb state; + + /** The gain setting has changed */ + + bt_aics_gain_setting_cb gain_setting; + + /** The audio input type has changed */ + + bt_aics_type_cb type; + + /** The audio input status has changed */ + + bt_aics_status_cb status; + + /** The audio input description has changed */ + + bt_aics_description_cb description; + +#if defined(CONFIG_BT_AICS_CLIENT) || defined(__DOXYGEN__) + /** The discovery has completed */ + + bt_aics_discover_cb discover; + + /** The set gain operation has completed */ + + bt_aics_write_cb set_gain; + + /** The unmute operation has completed */ + + bt_aics_write_cb unmute; + + /** The mut operation has completed */ + + bt_aics_write_cb mute; + + /** The set manual mode operation has completed */ + + bt_aics_write_cb set_manual_mode; + + /** The set automatic mode has completed */ + + bt_aics_write_cb set_auto_mode; +#endif /* CONFIG_BT_AICS_CLIENT */ +}; + +/** + * @brief Discover a Audio Input Control Service. + * + * Attempts to discover a Audio Input Control Service on a server given the + * @p param. + * + * @param conn Connection to the peer with the Audio Input Control + * Service. + * @param inst The instance pointer. + * @param param Pointer to the parameters. + * + * @return 0 on success, errno on fail. + */ + +int bt_aics_discover(struct bt_conn *conn, struct bt_aics *inst, + const struct bt_aics_discover_param *param); + +/** + * @brief Deactivates a Audio Input Control Service instance. + * + * Audio Input Control Services are activated by default, but this will allow + * the server to deactivate an Audio Input Control Service. + * + * @param inst The instance pointer. + * + * @return 0 if success, errno on failure. + */ + +int bt_aics_deactivate(struct bt_aics *inst); + +/** + * @brief Activates a Audio Input Control Service instance. + * + * Audio Input Control Services are activated by default, but this will allow + * the server reactivate a Audio Input Control Service instance after it has + * been deactivated with @ref bt_aics_deactivate. + * + * @param inst The instance pointer. + * + * @return 0 if success, errno on failure. + */ + +int bt_aics_activate(struct bt_aics *inst); + +/** + * @brief Read the Audio Input Control Service input state. + * + * @param inst The instance pointer. + * + * @return 0 on success, GATT error value on fail. + */ + +int bt_aics_state_get(struct bt_aics *inst); + +/** + * @brief Read the Audio Input Control Service gain settings. + * + * @param inst The instance pointer. + * + * @return 0 on success, GATT error value on fail. + */ + +int bt_aics_gain_setting_get(struct bt_aics *inst); + +/** + * @brief Read the Audio Input Control Service input type. + * + * @param inst The instance pointer. + * + * @return 0 on success, GATT error value on fail. + */ + +int bt_aics_type_get(struct bt_aics *inst); + +/** + * @brief Read the Audio Input Control Service input status. + * + * @param inst The instance pointer. + * + * @return 0 on success, GATT error value on fail. + */ + +int bt_aics_status_get(struct bt_aics *inst); + +/** + * @brief Disable mute in the Audio Input Control Service. + * + * Calling bt_aics_unmute() or bt_aics_mute() will enable mute again and set + * the mute state to either unmuted or muted. + * + * @param inst The instance pointer. + * + * @return 0 on success, errno value on fail. + */ + +int bt_aics_disable_mute(struct bt_aics *inst); + +/** + * @brief Unmute the Audio Input Control Service input. + * + * @param inst The instance pointer. + * + * @return 0 on success, GATT error value on fail. + */ + +int bt_aics_unmute(struct bt_aics *inst); + +/** + * @brief Mute the Audio Input Control Service input. + * + * @param inst The instance pointer. + * + * @return 0 on success, GATT error value on fail. + */ + +int bt_aics_mute(struct bt_aics *inst); + +/** + * @brief Set manual only gain mode in Audio Input Control Service. + * + * @param inst The instance pointer. + * + * @return 0 on success, errno value on fail. + */ + +int bt_aics_gain_set_manual_only(struct bt_aics *inst); + +/** + * @brief Set automatic only gain mode in Audio Input Control Service. + * + * Using this function and enabling automatic only gain disables setting the + * gain with bt_aics_gain_set + * + * @param inst The instance pointer. + * + * @return 0 on success, errno value on fail. + */ + +int bt_aics_gain_set_auto_only(struct bt_aics *inst); + +/** + * @brief Set input gain to manual. + * + * @param inst The instance pointer. + * + * @return 0 on success, GATT error value on fail. + */ + +int bt_aics_manual_gain_set(struct bt_aics *inst); + +/** + * @brief Set the input gain to automatic. + * + * @param inst The instance pointer. + * + * @return 0 on success, GATT error value on fail. + */ + +int bt_aics_automatic_gain_set(struct bt_aics *inst); + +/** + * @brief Set the input gain. + * + * @param inst The instance pointer. + * @param gain The gain to set (-128 to 127) in gain setting units + * (see @ref bt_aics_gain_setting_cb). + * + * @return 0 on success, GATT error value on fail. + */ + +int bt_aics_gain_set(struct bt_aics *inst, int8_t gain); + +/** + * @brief Read the Audio Input Control Service description. + * + * @param inst The instance pointer. + * + * @return 0 on success, GATT error value on fail. + */ + +int bt_aics_description_get(struct bt_aics *inst); + +/** + * @brief Set the Audio Input Control Service description. + * + * @param inst The instance pointer. + * @param description The description as an UTF-8 encoded string. + * + * @return 0 on success, GATT error value on fail. + */ + +int bt_aics_description_set(struct bt_aics *inst, const char *description); + +/** + * @brief Get a new Audio Input Control Service client instance. + * + * @return Pointer to the instance, or NULL if no free instances are left. + */ + +struct bt_aics *bt_aics_client_free_instance_get(void); + +/** + * @brief Free an instance received from bt_aics_client_free_instance_get() + * + * @param inst The instance to free + * + * @retval 0 Success. + * @retval -EINVAL @p inst is NULL or not a client instance. + * @retval -EALREADY @p inst is already free. + * @retval -EBUSY @p inst is busy. + */ + +int bt_aics_client_free_instance(struct bt_aics *inst); + +/** + * @brief Registers the callbacks for the Audio Input Control Service client. + * + * @param inst The instance pointer. + * @param cb Pointer to the callback structure. + */ + +void bt_aics_client_cb_register(struct bt_aics *inst, struct bt_aics_cb *cb); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_AICS_H */ diff --git a/include/nuttx/net/bluetooth/audio/ascs.h b/include/nuttx/net/bluetooth/audio/ascs.h new file mode 100644 index 0000000000000..b259ba35d350c --- /dev/null +++ b/include/nuttx/net/bluetooth/audio/ascs.h @@ -0,0 +1,594 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/audio/ascs.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Bluetooth Audio Stream Control Service (ASCS) APIs. + */ + +/* Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_ASCS_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_ASCS_H + +/** + * @brief Audio Stream Control Service (ASCS) + * + * @defgroup bt_ascs Audio Stream Control Service (ASCS) + * + * @since 4.5 + * @version 0.1.0 + * + * @ingroup bluetooth + * @{ + * + * The Audio Stream Control Service (ASCS) exposes Audio Stream Endpoints + * (ASEs), which enables clients to control the ASEs and their associated + * unicast audio streams. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Response Status Code + * + * These are sent by the server to the client when a stream operation is + * requested. + */ + +enum bt_bap_ascs_rsp_code +{ + /** Server completed operation successfully */ + + BT_BAP_ASCS_RSP_CODE_SUCCESS = 0x00u, + + /** Server did not support operation by client */ + + BT_BAP_ASCS_RSP_CODE_NOT_SUPPORTED = 0x01u, + + /** Server rejected due to invalid operation length */ + + BT_BAP_ASCS_RSP_CODE_INVALID_LENGTH = 0x02u, + + /** Invalid ASE ID */ + + BT_BAP_ASCS_RSP_CODE_INVALID_ASE = 0x03u, + + /** Invalid ASE state */ + + BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE = 0x04u, + + /** Invalid operation for direction */ + + BT_BAP_ASCS_RSP_CODE_INVALID_DIR = 0x05u, + + /** Capabilities not supported by server */ + + BT_BAP_ASCS_RSP_CODE_CAP_UNSUPPORTED = 0x06u, + + /** Configuration parameters not supported by server */ + + BT_BAP_ASCS_RSP_CODE_CONF_UNSUPPORTED = 0x07u, + + /** Configuration parameters rejected by server */ + + BT_BAP_ASCS_RSP_CODE_CONF_REJECTED = 0x08u, + + /** Invalid Configuration parameters */ + + BT_BAP_ASCS_RSP_CODE_CONF_INVALID = 0x09u, + + /** Unsupported metadata */ + + BT_BAP_ASCS_RSP_CODE_METADATA_UNSUPPORTED = 0x0au, + + /** Metadata rejected by server */ + + BT_BAP_ASCS_RSP_CODE_METADATA_REJECTED = 0x0bu, + + /** Invalid metadata */ + + BT_BAP_ASCS_RSP_CODE_METADATA_INVALID = 0x0cu, + + /** Server has insufficient resources */ + + BT_BAP_ASCS_RSP_CODE_NO_MEM = 0x0du, + + /** Unspecified error */ + + BT_BAP_ASCS_RSP_CODE_UNSPECIFIED = 0x0eu, +}; + +/** + * @brief Response Reasons + * + * These are used if the @ref bt_bap_ascs_rsp_code value is + * @ref BT_BAP_ASCS_RSP_CODE_CONF_UNSUPPORTED, @ref + * BT_BAP_ASCS_RSP_CODE_CONF_REJECTED or + * @ref BT_BAP_ASCS_RSP_CODE_CONF_INVALID. + */ + +enum bt_bap_ascs_reason +{ + /** No reason */ + + BT_BAP_ASCS_REASON_NONE = 0x00u, + + /** Codec ID */ + + BT_BAP_ASCS_REASON_CODEC = 0x01u, + + /** Codec configuration */ + + BT_BAP_ASCS_REASON_CODEC_DATA = 0x02u, + + /** SDU interval */ + + BT_BAP_ASCS_REASON_INTERVAL = 0x03u, + + /** Framing */ + + BT_BAP_ASCS_REASON_FRAMING = 0x04u, + + /** PHY */ + + BT_BAP_ASCS_REASON_PHY = 0x05u, + + /** Maximum SDU size */ + + BT_BAP_ASCS_REASON_SDU = 0x06u, + + /** Retransmission number */ + + BT_BAP_ASCS_REASON_RTN = 0x07u, + + /** Max transport latency */ + + BT_BAP_ASCS_REASON_LATENCY = 0x08u, + + /** Presentation delay */ + + BT_BAP_ASCS_REASON_PD = 0x09u, + + /** Invalid CIS mapping */ + + BT_BAP_ASCS_REASON_CIS = 0x0au, +}; + +/** @brief Structure storing values of fields of ASE Control Point + * notification. + */ + +struct bt_bap_ascs_rsp +{ + /* @brief Value of the Response Code field. + * + * The following response codes are accepted: + * - @ref BT_BAP_ASCS_RSP_CODE_SUCCESS + * - @ref BT_BAP_ASCS_RSP_CODE_CAP_UNSUPPORTED + * - @ref BT_BAP_ASCS_RSP_CODE_CONF_UNSUPPORTED + * - @ref BT_BAP_ASCS_RSP_CODE_CONF_REJECTED + * - @ref BT_BAP_ASCS_RSP_CODE_METADATA_UNSUPPORTED + * - @ref BT_BAP_ASCS_RSP_CODE_METADATA_REJECTED + * - @ref BT_BAP_ASCS_RSP_CODE_NO_MEM + * - @ref BT_BAP_ASCS_RSP_CODE_UNSPECIFIED + */ + + enum bt_bap_ascs_rsp_code code; + + /* @brief Value of the Reason field. + * + * The meaning of this value depend on the Response Code field. + */ + + union + { + /** + * @brief Response reason + * + * If the Response Code is one of the following: + * - @ref BT_BAP_ASCS_RSP_CODE_CONF_UNSUPPORTED + * - @ref BT_BAP_ASCS_RSP_CODE_CONF_REJECTED + * all values from @ref bt_bap_ascs_reason can be used. + * + * If the Response Code is one of the following: + * - @ref BT_BAP_ASCS_RSP_CODE_SUCCESS + * - @ref BT_BAP_ASCS_RSP_CODE_CAP_UNSUPPORTED + * - @ref BT_BAP_ASCS_RSP_CODE_NO_MEM + * - @ref BT_BAP_ASCS_RSP_CODE_UNSPECIFIED + * only value @ref BT_BAP_ASCS_REASON_NONE shall be used. + */ + + enum bt_bap_ascs_reason reason; + + /** + * @brief Response metadata type + * + * If the Response Code is one of the following: + * - @ref BT_BAP_ASCS_RSP_CODE_METADATA_UNSUPPORTED + * - @ref BT_BAP_ASCS_RSP_CODE_METADATA_REJECTED + * the value of the Metadata Type shall be used. + */ + + enum bt_audio_metadata_type metadata_type; + }; +}; + +/** + * @brief Macro used to initialise the object storing values of ASE Control + * Point notification. + * + * @param c Response Code field + * @param r Reason field - @ref bt_bap_ascs_reason or @ref + * bt_audio_metadata_type (see notes in + * @ref bt_bap_ascs_rsp). + */ +#define BT_BAP_ASCS_RSP(c, r) \ +(struct bt_bap_ascs_rsp) \ +{ \ + .code = c, .reason = r \ +} + +/** @brief Audio Stream Quality of Service Preference structure. */ + +struct bt_bap_qos_cfg_pref +{ + /* @brief Unframed PDUs supported + * + * Unlike the other fields, this is not a preference but whether the codec + * supports unframed ISOAL PDUs. + */ + + bool unframed_supported; + + /* @brief Preferred PHY bitfield + * + * Bitfield consisting of one or more of @ref BT_GAP_LE_PHY_1M, @ref + * BT_GAP_LE_PHY_2M and + * @ref BT_GAP_LE_PHY_CODED. + */ + + uint8_t phy; + + /* @brief Preferred Retransmission Number + * + * @ref BT_AUDIO_RTN_PREF_NONE indicates no preference. + */ + + uint8_t rtn; + +/* Preferred Transport Latency in milliseconds + * + * Value range @ref BT_ISO_LATENCY_MIN to @ref BT_ISO_LATENCY_MAX + */ + + uint16_t latency; + + /* @brief Minimum Presentation Delay in microseconds + * + * Unlike the other fields, this is not a preference but a minimum + * requirement. + * + * Value range 0 to @ref BT_AUDIO_PD_MAX + */ + + uint32_t pd_min; + + /* @brief Maximum Presentation Delay in microseconds + * + * Unlike the other fields, this is not a preference but a maximum + * requirement. + * + * Value range @ref bt_bap_qos_cfg_pref.pd_min to @ref BT_AUDIO_PD_MAX + */ + + uint32_t pd_max; + + /* @brief Preferred minimum Presentation Delay in microseconds + * + * Value range @ref bt_bap_qos_cfg_pref.pd_min to @ref + * bt_bap_qos_cfg_pref.pd_max, or + * @ref BT_AUDIO_PD_PREF_NONE to indicate no preference. + */ + + uint32_t pref_pd_min; + + /* @brief Preferred maximum Presentation Delay in microseconds + * + * Value range @ref bt_bap_qos_cfg_pref.pd_min to @ref + * bt_bap_qos_cfg_pref.pd_max, and higher than or equal to @ref + * bt_bap_qos_cfg_pref.pref_pd_min, or + * @ref BT_AUDIO_PD_PREF_NONE to indicate no preference. + */ + + uint32_t pref_pd_max; +}; + +/** + * @brief Helper to declare elements of @ref bt_bap_qos_cfg_pref + * + * @param _unframed_supported Unframed PDUs supported + * @param _phy Preferred Target PHY + * @param _rtn Preferred Retransmission number + * @param _latency Preferred Maximum Transport Latency (msec) + * @param _pd_min Minimum Presentation Delay (usec) + * @param _pd_max Maximum Presentation Delay (usec) + * @param _pref_pd_min Preferred Minimum Presentation Delay (usec) + * @param _pref_pd_max Preferred Maximum Presentation Delay (usec) + */ +#define BT_BAP_QOS_CFG_PREF(_unframed_supported, _phy, _rtn, _latency, _pd_min, _pd_max, \ + _pref_pd_min, _pref_pd_max) \ +{ \ + .unframed_supported = _unframed_supported, \ + .phy = _phy, \ + .rtn = _rtn, \ + .latency = _latency, \ + .pd_min = _pd_min, \ + .pd_max = _pd_max, \ + .pref_pd_min = _pref_pd_min, \ + .pref_pd_max = _pref_pd_max, \ +} + +/* Temporary solution to handle circular dependencies between ascs.h and + * bap.h + */ + +struct bt_bap_stream; +struct bt_bap_ep; +struct bt_bap_qos_cfg; + +/** ASCS callback structure */ + +struct bt_ascs_cb +{ + /* @brief Endpoint config request callback + * + * Config callback is called whenever an endpoint is requested to be + * configured + * + * @param[in] conn Connection object. + * @param[in] ep Local Audio Endpoint being configured. + * @param[in] dir Direction of the endpoint. + * @param[in] codec_cfg Codec configuration. + * @param[out] stream Pointer to stream that will be configured for the + * endpoint. + * @param[out] pref Pointer to a QoS preference object that shall be + * populated with values. Invalid values will reject + * the codec configuration request. + * @param[out] rsp Object for the ASE operation response. Only used + * if the return value is non-zero. + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*config)(struct bt_conn *conn, + const struct bt_bap_ep *ep, + enum bt_audio_dir dir, + const struct bt_audio_codec_cfg *codec_cfg, + struct bt_bap_stream **stream, + struct bt_bap_qos_cfg_pref *const pref, + struct bt_bap_ascs_rsp *rsp); + + /* @brief Stream reconfig request callback + * + * Reconfig callback is called whenever an Audio Stream needs to be + * reconfigured with different codec configuration. + * + * @param[in] stream Stream object being reconfigured. + * @param[in] dir Direction of the endpoint. + * @param[in] codec_cfg Codec configuration. + * @param[out] pref Pointer to a QoS preference object that shall be + * populated with values. Invalid values will reject + * the codec configuration request. + * @param[out] rsp Object for the ASE operation response. Only used + * if the return value is non-zero. + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*reconfig)(struct bt_bap_stream *stream, + enum bt_audio_dir dir, + const struct bt_audio_codec_cfg *codec_cfg, + struct bt_bap_qos_cfg_pref *const pref, + struct bt_bap_ascs_rsp *rsp); + + /* @brief Stream QoS request callback + * + * QoS callback is called whenever an Audio Stream Quality of Service needs + * to be configured. + * + * @param[in] stream Stream object being reconfigured. + * @param[in] qos Quality of Service configuration. + * @param[out] rsp Object for the ASE operation response. Only used if + * the return value is non-zero. + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*qos)(struct bt_bap_stream *stream, const struct bt_bap_qos_cfg *qos, + struct bt_bap_ascs_rsp *rsp); + + /* @brief Stream Enable request callback + * + * Enable callback is called whenever an Audio Stream is requested to be + * enabled to stream. + * + * @param[in] stream Stream object being enabled. + * @param[in] meta Metadata entries. + * @param[in] meta_len Length of metadata. + * @param[out] rsp Object for the ASE operation response. Only used + * if the return value is non-zero. + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*enable)(struct bt_bap_stream *stream, + const uint8_t meta[], + size_t meta_len, + struct bt_bap_ascs_rsp *rsp); + + /* @brief Stream Start request callback + * + * Start callback is called whenever an Audio Stream is requested to start + * streaming. + * + * @param[in] stream Stream object. + * @param[out] rsp Object for the ASE operation response. Only used if + * the return value is non-zero. + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*start)(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp); + + /* @brief Stream Metadata update request callback + * + * Metadata callback is called whenever an Audio Stream is requested to + * update its metadata. + * + * @param[in] stream Stream object. + * @param[in] meta Metadata entries. + * @param[in] meta_len Length of metadata. + * @param[out] rsp Object for the ASE operation response. Only + * used if the return value is non-zero. + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*metadata)(struct bt_bap_stream *stream, + const uint8_t meta[], + size_t meta_len, + struct bt_bap_ascs_rsp *rsp); + + /* @brief Stream Disable request callback + * + * Disable callback is called whenever an Audio Stream is requested to + * disable the stream. + * + * @param[in] stream Stream object being disabled. + * @param[out] rsp Object for the ASE operation response. Only used if + * the return value is non-zero. + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*disable)(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp); + + /* @brief Stream Stop callback + * + * Stop callback is called whenever an Audio Stream is requested to stop + * streaming. + * + * @param[in] stream Stream object. + * @param[out] rsp Object for the ASE operation response. Only used if + * the return value is non-zero. + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*stop)(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp); + + /* @brief Stream release callback + * + * Release callback is called whenever a new Audio Stream needs to be + * released and thus deallocated. + * + * @param[in] stream Stream object. + * @param[out] rsp Object for the ASE operation response. Only used if + * the return value is non-zero. + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*release)(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp); +}; + +/** Parameters for registering the Audio Stream Control Service (ASCS) */ + +struct bt_ascs_register_param +{ + /* @brief Sink Count to register. + * + * Should be in range 0 to @kconfig{CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT} + */ + + uint8_t snk_cnt; + + /* @brief Source Count to register. + * + * Should be in range 0 to @kconfig{CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT} + */ + + uint8_t src_cnt; + + /* @brief Callback structure + */ + + const struct bt_ascs_cb *cb; +}; + +/** + * @brief Register the Audio Stream Control Service (ASCS) + * + * This will initialize the service and expose it in the GATT database, as + * well as the provided number of sink and source ASE characteristics. + * + * @param param Parameters for the service + * + * @retval 0 Success. + * @retval -EINVAL @p param is NULL or contains invalid values, or + * bt_gatt_service_register() failed. + * @retval -EALREADY ASCS already registered. + * @retval -EADDRINUSE ISO server is already registered with + * bt_iso_server_register(). + * @retval -ENOTSUP Controller does not support ISO. + */ + +int bt_ascs_register(const struct bt_ascs_register_param *param); + +/** + * @brief Unregister the Audio Stream Control Service (ASCS) + * + * This will release all streams and remove the service from the GATT + * database. + * + * @retval 0 Success. + * @retval -ENOENT if bt_gatt_service_unregister() failed to unregister the + * service. + * @retval -EALREADY ASCS already unregistered. + */ + +int bt_ascs_unregister(void); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_ASCS_H */ diff --git a/include/nuttx/net/bluetooth/audio/audio.h b/include/nuttx/net/bluetooth/audio/audio.h new file mode 100644 index 0000000000000..352875ca901b7 --- /dev/null +++ b/include/nuttx/net/bluetooth/audio/audio.h @@ -0,0 +1,2271 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/audio/audio.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Bluetooth Audio handling + */ + +/* Copyright (c) 2020 Intel Corporation + * Copyright (c) 2020-2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_AUDIO_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_AUDIO_H + +/** + * @brief Bluetooth Audio + * @defgroup bt_audio Bluetooth Audio + * @since 3.1 + * @version 0.8.0 + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Size of the broadcast ID in octets */ +#define BT_AUDIO_BROADCAST_ID_SIZE 3U +/** Maximum broadcast ID value */ +#define BT_AUDIO_BROADCAST_ID_MAX 0xFFFFFFU +/** Indicates that the server have no preference for the presentation delay */ +#define BT_AUDIO_PD_PREF_NONE 0x000000U +/** Maximum presentation delay in microseconds */ +#define BT_AUDIO_PD_MAX 0xFFFFFFU +/** Indicates that the unicast server does not have a preference for any + * retransmission number + */ +#define BT_AUDIO_RTN_PREF_NONE 0xFFU + +/** Size of the stream language value, e.g. "eng" */ +#define BT_AUDIO_LANG_SIZE 3U + +/** + * @brief Channel count support capability + * + * Macro accepts variable number of channel counts. The allowed channel + * counts are defined by specification and have to be in range from + * @ref BT_AUDIO_CODEC_CAP_CHAN_COUNT_MIN to @ref + * BT_AUDIO_CODEC_CAP_CHAN_COUNT_MAX inclusive. + * + * Example to support 1 and 3 channels: + * BT_AUDIO_CODEC_CAP_CHAN_COUNT_SUPPORT(1, 3) + */ +#define BT_AUDIO_CODEC_CAP_CHAN_COUNT_SUPPORT(...) \ +((enum bt_audio_codec_cap_chan_count)( \ + (FOR_EACH(BIT, (|), __VA_ARGS__)) >> 1)) + +/** struct to hold minimum and maximum supported codec frame sizes */ + +struct bt_audio_codec_octets_per_codec_frame +{ + /** Minimum number of octets supported per codec frame */ + + uint16_t min; + + /** Maximum number of octets supported per codec frame */ + + uint16_t max; +}; + +/** + * @brief Helper to check whether metadata type is known by the stack. + * + * @note @p _type is evaluated thrice. + */ +#define BT_AUDIO_METADATA_TYPE_IS_KNOWN(_type) \ +(IN_RANGE((_type), BT_AUDIO_METADATA_TYPE_PREF_CONTEXT, \ + BT_AUDIO_METADATA_TYPE_BROADCAST_IMMEDIATE) || \ + (_type) == BT_AUDIO_METADATA_TYPE_EXTENDED || (_type) == BT_AUDIO_METADATA_TYPE_VENDOR) + +/** + * @name Unicast Announcement Type + * @{ + */ + +/** Unicast Server is connectable and is not requesting a connection. */ +#define BT_AUDIO_UNICAST_ANNOUNCEMENT_GENERAL 0x00U +/** Unicast Server is connectable but is requesting a connection. */ +#define BT_AUDIO_UNICAST_ANNOUNCEMENT_TARGETED 0x01U +/** @} */ + +/** + * @brief Helper to declare elements of bt_audio_codec_cap arrays + * + * This macro is mainly for creating an array of struct bt_audio_codec_cap + * data arrays. + * + * @param _type Type of advertising data field + * @param _bytes Variable number of single-byte parameters + */ +#define BT_AUDIO_CODEC_DATA(_type, _bytes...) \ +(sizeof((uint8_t)_type) + sizeof((uint8_t[]){_bytes})), (_type), _bytes + +/** + * @brief Helper to declare @ref bt_audio_codec_cfg + * + * @param _id Codec ID + * @param _cid Company ID + * @param _vid Vendor ID + * @param _data Codec Specific Data in LVT format + * @param _meta Codec Specific Metadata in LVT format + */ +#define BT_AUDIO_CODEC_CFG(_id, _cid, _vid, _data, _meta) \ +((struct bt_audio_codec_cfg){ \ + /* Use HCI data path as default, can be overwritten by application */ \ + .path_id = BT_ISO_DATA_PATH_HCI, \ + .ctlr_transcode = false, \ + COND_CODE_1(IS_ENABLED(CONFIG_BT_BAP_UNICAST), \ + (.target_latency = BT_AUDIO_CODEC_CFG_TARGET_LATENCY_BALANCED, \ + .target_phy = BT_AUDIO_CODEC_CFG_TARGET_PHY_2M, ), \ + ()) \ + .id = _id, \ + .cid = _cid, \ + .vid = _vid, \ + .data_len = sizeof((uint8_t[])_data), \ + .data = _data, \ + .meta_len = sizeof((uint8_t[])_meta), \ + .meta = _meta, \ +}) + +/** + * @brief Helper to declare @ref bt_audio_codec_cap structure + * + * @param _id Codec ID + * @param _cid Company ID + * @param _vid Vendor ID + * @param _data Codec Specific Data in LVT format + * @param _meta Codec Specific Metadata in LVT format + */ +#define BT_AUDIO_CODEC_CAP(_id, _cid, _vid, _data, _meta) \ +((struct bt_audio_codec_cap){ \ + /* Use HCI data path as default, can be overwritten by application */ \ + .path_id = BT_ISO_DATA_PATH_HCI, \ + .ctlr_transcode = false, \ + .id = (_id), \ + .cid = (_cid), \ + .vid = (_vid), \ + .data_len = sizeof((uint8_t[])_data), \ + .data = _data, \ + .meta_len = sizeof((uint8_t[])_meta), \ + .meta = _meta, \ +}) + +/** @brief Codec capability structure. */ + +struct bt_audio_codec_cap +{ + /** Data path ID + * + * @ref BT_ISO_DATA_PATH_HCI for HCI path, or any other value for + * vendor specific ID. + */ + + uint8_t path_id; + /** Whether or not the local controller should transcode + * + * This effectively sets the coding format for the ISO data path to @ref + * BT_HCI_CODING_FORMAT_TRANSPARENT if false, else uses the @ref + * bt_audio_codec_cfg.id. + */ + + bool ctlr_transcode; + + /** Codec ID */ + + uint8_t id; + + /** Codec Company ID */ + + uint16_t cid; + + /** Codec Company Vendor ID */ + + uint16_t vid; +#if CONFIG_BT_AUDIO_CODEC_CAP_MAX_DATA_SIZE > 0 || defined(__DOXYGEN__) + /** Codec Specific Capabilities Data count */ + + size_t data_len; + + /** Codec Specific Capabilities Data */ + + uint8_t data[CONFIG_BT_AUDIO_CODEC_CAP_MAX_DATA_SIZE]; +#endif /* CONFIG_BT_AUDIO_CODEC_CAP_MAX_DATA_SIZE > 0 */ +#if defined(CONFIG_BT_AUDIO_CODEC_CAP_MAX_METADATA_SIZE) || defined(__DOXYGEN__) + /** Codec Specific Capabilities Metadata count */ + size_t meta_len; + + /** Codec Specific Capabilities Metadata */ + + uint8_t meta[CONFIG_BT_AUDIO_CODEC_CAP_MAX_METADATA_SIZE]; +#endif /* CONFIG_BT_AUDIO_CODEC_CAP_MAX_METADATA_SIZE */ +}; + +/** + * @brief Codec configuration target latency + * + * Set by the BAP Unicast Client to provide context for the BAP Unicast + * Server for the server to set its QoS preferences. + */ + +enum bt_audio_codec_cfg_target_latency +{ + /** Target low latency */ + + BT_AUDIO_CODEC_CFG_TARGET_LATENCY_LOW = 0x01, + + /** Target balanced latency */ + + BT_AUDIO_CODEC_CFG_TARGET_LATENCY_BALANCED = 0x02, + + /** Target high latency */ + + BT_AUDIO_CODEC_CFG_TARGET_LATENCY_HIGH = 0x03, +}; + +/** + * @brief Codec configuration target PHY + * + * The target PHY to achieve the target latency (@ref + * bt_audio_codec_cfg_target_latency). + */ + +enum bt_audio_codec_cfg_target_phy +{ + /** LE 1M PHY */ + + BT_AUDIO_CODEC_CFG_TARGET_PHY_1M = 0x01, + + /** LE 2M PHY */ + + BT_AUDIO_CODEC_CFG_TARGET_PHY_2M = 0x02, + + /** LE Coded PHY */ + + BT_AUDIO_CODEC_CFG_TARGET_PHY_CODED = 0x03, +}; + +/** @brief Codec specific configuration structure. */ + +struct bt_audio_codec_cfg +{ + /** Data path ID + * + * @ref BT_ISO_DATA_PATH_HCI for HCI path, or any other value for + * vendor specific ID. + */ + + uint8_t path_id; + /** Whether or not the local controller should transcode + * + * This effectively sets the coding format for the ISO data path to @ref + * BT_HCI_CODING_FORMAT_TRANSPARENT if false, else uses the @ref + * bt_audio_codec_cfg.id. + */ + + bool ctlr_transcode; +#if defined(CONFIG_BT_BAP_UNICAST) || defined(__DOXYGEN__) + /** Target latency + * + * Unused for broadcast streams. + */ + + enum bt_audio_codec_cfg_target_latency target_latency; + /** Target PHY + * + * Unused for broadcast streams. + */ + + enum bt_audio_codec_cfg_target_phy target_phy; +#endif /* CONFIG_BT_BAP_UNICAST */ + /** Codec ID */ + uint8_t id; + + /** Codec Company ID */ + + uint16_t cid; + + /** Codec Company Vendor ID */ + + uint16_t vid; +#if CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE > 0 || defined(__DOXYGEN__) + /** Codec Specific Capabilities Data count */ + + size_t data_len; + + /** Codec Specific Capabilities Data */ + + uint8_t data[CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE]; +#endif /* CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE > 0 */ +#if CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE > 0 || defined(__DOXYGEN__) + /** Codec Specific Capabilities Metadata count */ + size_t meta_len; + + /** Codec Specific Capabilities Metadata */ + + uint8_t meta[CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE]; +#endif /* CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE > 0 */ +}; + +/** + * @brief Helper for parsing length-type-value data. + * + * @param ltv Length-type-value (LTV) encoded data. + * @param size Size of the @p ltv data. + * @param func Callback function which will be called for each element + * that's found in the data. The callback should return true + * to continue parsing, or false to stop parsing. + * @param user_data User data to be passed to the callback. + * + * @retval 0 All entries were parsed. + * @retval -EINVAL The data is incorrectly encoded + * @retval -ECANCELED Parsing was prematurely cancelled by the callback + */ + +int bt_audio_data_parse(const uint8_t ltv[], size_t size, +bool (*func)(struct bt_data *data, + void *user_data), + void *user_data); + +/** + * @brief Get the value of a specific data type in an length-type-value data + * array + * + * @param[in] ltv_data The array containing the length-type-value tuples + * @param[in] size The size of @p ltv_data + * @param[in] type The type to get the value for. May be any type, but + * typically either + * @ref bt_audio_codec_cap_type, @ref bt_audio_codec_cfg_type or + * @ref bt_audio_metadata_type. + * @param[out] data Pointer to the data-pointer to update when item is found. + * Any found data will be little endian. + * + * @retval length The length of found @p data (may be 0). + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + */ + +int bt_audio_data_get_val( + const uint8_t ltv_data[], size_t size, uint8_t type, const uint8_t **data); + +/** + * @brief Function to get the number of channels from the channel allocation + * + * @param chan_allocation The channel allocation + * + * @return The number of channels + */ + +uint8_t bt_audio_get_chan_count(enum bt_audio_location chan_allocation); + +/** @brief Audio direction from the perspective of the BAP Unicast Server / + * BAP Broadcast Sink + */ + +enum bt_audio_dir +{ + /* @brief Audio direction sink + * + * For a BAP Unicast Client or Broadcast Source this is considered outgoing + * audio (TX). For a BAP Unicast Server or Broadcast Sink this is + * considered incoming audio (RX). + */ + + BT_AUDIO_DIR_SINK = 0x01, + /* @brief Audio direction source + * + * For a BAP Unicast Client or Broadcast Source this is considered incoming + * audio (RX). For a BAP Unicast Server or Broadcast Sink this is + * considered outgoing audio (TX). + */ + + BT_AUDIO_DIR_SOURCE = 0x02, +}; + +/** + * @brief Returns a string representation of a specific @ref bt_audio_dir + * direction + * + * @param dir The direction to convert to a string + * + * @return String representation of the supplied direction + */ + +static inline const char *bt_audio_dir_to_str(enum bt_audio_dir dir) +{ + switch (dir) + { + case BT_AUDIO_DIR_SINK: + return "Sink"; + case BT_AUDIO_DIR_SOURCE: + return "Source"; + default: + return "Unknown"; + } +} + +/** + * @brief Audio codec Config APIs + * @defgroup bt_audio_codec_cfg Codec config parsing APIs + * + * Functions to parse codec config data when formatted as LTV wrapped into + * @ref + * bt_audio_codec_cfg. + * + * @{ + */ + +/** + * @brief Convert assigned numbers frequency to frequency value. + * + * @param freq The assigned numbers frequency to convert. + * + * @retval frequency The converted frequency value in Hz. + * @retval -EINVAL Arguments are invalid. + */ + +int bt_audio_codec_cfg_freq_to_freq_hz(enum bt_audio_codec_cfg_freq freq); + +/** + * @brief Convert frequency value to assigned numbers frequency. + * + * @param freq_hz The frequency value to convert. + * + * @retval frequency The assigned numbers frequency (@ref + * bt_audio_codec_cfg_freq). + * @retval -EINVAL Arguments are invalid. + */ + +int bt_audio_codec_cfg_freq_hz_to_freq(uint32_t freq_hz); + +/** + * @brief Extract the frequency from a codec configuration. + * + * @param codec_cfg The codec configuration to extract data from. + * + * @retval frequency A @ref bt_audio_codec_cfg_freq value + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + * @retval -EBADMSG The found value has invalid size or value + */ + +int bt_audio_codec_cfg_get_freq(const struct bt_audio_codec_cfg *codec_cfg); + +/** + * @brief Set the frequency of a codec configuration. + * + * @param codec_cfg The codec configuration to set data for. + * @param freq The assigned numbers frequency to set. + * + * @retval data_len The @p codec_cfg.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cfg_set_freq(struct bt_audio_codec_cfg *codec_cfg, + enum bt_audio_codec_cfg_freq freq); + +/** + * @brief Convert assigned numbers frame duration to duration in + * microseconds. + * + * @param frame_dur The assigned numbers frame duration to convert. + * + * @retval duration The converted frame duration value in microseconds. + * @retval -EINVAL Arguments are invalid. + */ + +int bt_audio_codec_cfg_frame_dur_to_frame_dur_us( + enum bt_audio_codec_cfg_frame_dur frame_dur); + +/** + * @brief Convert frame duration in microseconds to assigned numbers frame + * duration. + * + * @param frame_dur_us The frame duration in microseconds to convert. + * + * @retval duration The assigned numbers frame duration (@ref + * bt_audio_codec_cfg_frame_dur). + * @retval -EINVAL Arguments are invalid. + */ + +int bt_audio_codec_cfg_frame_dur_us_to_frame_dur(uint32_t frame_dur_us); + +/** + * @brief Extract frame duration from BT codec config + * + * @param codec_cfg The codec configuration to extract data from. + * + * @retval frequency A @ref bt_audio_codec_cfg_frame_dur value + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + * @retval -EBADMSG The found value has invalid size or value + */ + +int bt_audio_codec_cfg_get_frame_dur( + const struct bt_audio_codec_cfg *codec_cfg); + +/** + * @brief Set the frame duration of a codec configuration. + * + * @param codec_cfg The codec configuration to set data for. + * @param frame_dur The assigned numbers frame duration to set. + * + * @retval data_len The @p codec_cfg.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cfg_set_frame_dur(struct bt_audio_codec_cfg *codec_cfg, + enum bt_audio_codec_cfg_frame_dur frame_dur); + +/** + * @brief Extract channel allocation from BT codec config + * + * The value returned is a bit field representing one or more audio locations + * as specified by @ref bt_audio_location Shall match one or more of the bits + * set in BT_PAC_SNK_LOC/BT_PAC_SRC_LOC. + * + * Up to the configured @ref BT_AUDIO_CODEC_CAP_TYPE_CHAN_COUNT number of + * channels can be present. + * + * @param codec_cfg The codec configuration to extract data from. + * @param chan_allocation Pointer to the variable to store the extracted + * value in. + * @param fallback_to_default If true this function will provide the default + * value of + * @ref BT_AUDIO_LOCATION_MONO_AUDIO if the type is not found when @p + * codec_cfg.id is @ref BT_HCI_CODING_FORMAT_LC3. + * + * @retval 0 Value is found and stored in the pointer provided + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + * @retval -EBADMSG The found value has invalid size or value + */ + +int bt_audio_codec_cfg_get_chan_allocation( + const struct bt_audio_codec_cfg *codec_cfg, + enum bt_audio_location *chan_allocation, bool fallback_to_default); + +/** + * @brief Set the channel allocation of a codec configuration. + * + * @param codec_cfg The codec configuration to set data for. + * @param chan_allocation The channel allocation to set. + * + * @retval data_len The @p codec_cfg.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cfg_set_chan_allocation( + struct bt_audio_codec_cfg *codec_cfg, + enum bt_audio_location chan_allocation); + +/** + * @brief Extract frame size in octets from BT codec config + * + * The overall SDU size will be octets_per_frame * blocks_per_sdu. + * + * The Bluetooth specifications are not clear about this value - it does not + * state that the codec shall use this SDU size only. A codec like LC3 + * supports variable bit-rate (per SDU) hence it might be allowed for an + * encoder to reduce the frame size below this value. Hence it is recommended + * to use the received SDU size and divide by blocks_per_sdu rather than + * relying on this octets_per_sdu value to be fixed. + * + * @param codec_cfg The codec configuration to extract data from. + * + * @retval frame_length Frame length in octets + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + * @retval -EBADMSG The found value has invalid size or value + */ + +int bt_audio_codec_cfg_get_octets_per_frame( + const struct bt_audio_codec_cfg *codec_cfg); + +/** + * @brief Set the octets per codec frame of a codec configuration. + * + * @param codec_cfg The codec configuration to set data for. + * @param octets_per_frame The octets per codec frame to set. + * + * @retval data_len The @p codec_cfg.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cfg_set_octets_per_frame( + struct bt_audio_codec_cfg *codec_cfg, uint16_t octets_per_frame); + +/** + * @brief Extract number of audio frame blocks in each SDU from BT codec + * config + * + * The overall SDU size will be octets_per_frame * frame_blocks_per_sdu * + * number-of-channels. + * + * If this value is not present a default value of 1 shall be used. + * + * A frame block is one or more frames that represents data for the same + * period of time but for different channels. If the stream have two audio + * channels and this value is two there will be four frames in the SDU. + * + * @param codec_cfg The codec configuration to extract data from. + * @param fallback_to_default If true this function will return the default + * value of 1 if the type is not found when @p + * codec_cfg.id is @ref BT_HCI_CODING_FORMAT_LC3. + * + * @retval frame_blocks_per_sdu The count of codec frame blocks in each SDU. + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + * @retval -EBADMSG The found value has invalid size or value + */ + +int bt_audio_codec_cfg_get_frame_blocks_per_sdu( + const struct bt_audio_codec_cfg *codec_cfg, bool fallback_to_default); + +/** + * @brief Set the frame blocks per SDU of a codec configuration. + * + * @param codec_cfg The codec configuration to set data for. + * @param frame_blocks The frame blocks per SDU to set. + * + * @retval data_len The @p codec_cfg.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cfg_set_frame_blocks_per_sdu( + struct bt_audio_codec_cfg *codec_cfg, uint8_t frame_blocks); + +/** + * @brief Lookup a specific codec configuration value + * + * @param[in] codec_cfg The codec data to search in. + * @param[in] type The type id to look for + * @param[out] data Pointer to the data-pointer to update when item is + * found + * + * @retval len Length of found @p data (may be 0) + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + */ + +int bt_audio_codec_cfg_get_val(const struct bt_audio_codec_cfg *codec_cfg, + enum bt_audio_codec_cfg_type type, + const uint8_t **data); + +/** + * @brief Set or add a specific codec configuration value + * + * @param codec_cfg The codec data to set the value in. + * @param type The type id to set + * @param data Pointer to the data-pointer to set + * @param data_len Length of @p data + * + * @retval data_len The @p codec_cfg.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cfg_set_val(struct bt_audio_codec_cfg *codec_cfg, + enum bt_audio_codec_cfg_type type, + const uint8_t *data, + size_t data_len); + +/** + * @brief Unset a specific codec configuration value + * + * The type and the value will be removed from the codec configuration. + * + * @param codec_cfg The codec data to set the value in. + * @param type The type id to unset. + * + * @retval data_len The @p codec_cfg.data_len on success + * @retval -EINVAL Arguments are invalid + */ + +int bt_audio_codec_cfg_unset_val(struct bt_audio_codec_cfg *codec_cfg, + enum bt_audio_codec_cfg_type type); + +/** + * @brief Lookup a specific metadata value based on type + * + * + * @param[in] codec_cfg The codec data to search in. + * @param[in] type The type id to look for + * @param[out] data Pointer to the data-pointer to update when item is + * found + * + * @retval len Length of found @p data (may be 0) + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + */ + +int bt_audio_codec_cfg_meta_get_val( + const struct bt_audio_codec_cfg *codec_cfg, uint8_t type, + const uint8_t **data); + +/** + * @brief Set or add a specific codec configuration metadata value. + * + * @param codec_cfg The codec configuration to set the value in. + * @param type The type id to set. + * @param data Pointer to the data-pointer to set. + * @param data_len Length of @p data. + * + * @retval meta_len The @p codec_cfg.meta_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cfg_meta_set_val(struct bt_audio_codec_cfg *codec_cfg, + enum bt_audio_metadata_type type, + const uint8_t *data, + size_t data_len); + +/** + * @brief Unset a specific codec configuration metadata value + * + * The type and the value will be removed from the codec configuration + * metadata. + * + * @param codec_cfg The codec data to set the value in. + * @param type The type id to unset. + * + * @retval meta_len The of @p codec_cfg.meta_len success + * @retval -EINVAL Arguments are invalid + */ + +int bt_audio_codec_cfg_meta_unset_val(struct bt_audio_codec_cfg *codec_cfg, + enum bt_audio_metadata_type type); +/** + * @brief Extract preferred contexts + * + * See @ref BT_AUDIO_METADATA_TYPE_PREF_CONTEXT for more information about + * this value. + * + * @param codec_cfg The codec data to search in. + * @param fallback_to_default If true this function will provide the default + * value of + * @ref BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED if the type is not found + * when @p codec_cfg.id is + * @ref BT_HCI_CODING_FORMAT_LC3. + * + * @retval context The preferred context type if positive or 0 + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + * @retval -EBADMSG The found value has invalid size + */ + +int bt_audio_codec_cfg_meta_get_pref_context( + const struct bt_audio_codec_cfg *codec_cfg, bool fallback_to_default); + +/** + * @brief Set the preferred context of a codec configuration metadata. + * + * @param codec_cfg The codec configuration to set data for. + * @param ctx The preferred context to set. + * + * @retval data_len The @p codec_cfg.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cfg_meta_set_pref_context( + struct bt_audio_codec_cfg *codec_cfg, enum bt_audio_context ctx); + +/** + * @brief Extract stream contexts + * + * See @ref BT_AUDIO_METADATA_TYPE_STREAM_CONTEXT for more information about + * this value. + * + * @param codec_cfg The codec data to search in. + * + * @retval context The stream context type if positive or 0 + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + * @retval -EBADMSG The found value has invalid size + */ + +int bt_audio_codec_cfg_meta_get_stream_context( + const struct bt_audio_codec_cfg *codec_cfg); + +/** + * @brief Set the stream context of a codec configuration metadata. + * + * @param codec_cfg The codec configuration to set data for. + * @param ctx The stream context to set. + * + * @retval data_len The @p codec_cfg.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cfg_meta_set_stream_context( + struct bt_audio_codec_cfg *codec_cfg, enum bt_audio_context ctx); + +/** + * @brief Extract program info + * + * See @ref BT_AUDIO_METADATA_TYPE_PROGRAM_INFO for more information about + * this value. + * + * @param[in] codec_cfg The codec data to search in. + * @param[out] program_info Pointer to the UTF-8 formatted program info. + * + * @retval len The length of the @p program_info (may be 0) + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + */ + +int bt_audio_codec_cfg_meta_get_program_info( + const struct bt_audio_codec_cfg *codec_cfg, const uint8_t **program_info); + +/** + * @brief Set the program info of a codec configuration metadata. + * + * @param codec_cfg The codec configuration to set data for. + * @param program_info The program info to set. + * @param program_info_len The length of @p program_info. + * + * @retval data_len The @p codec_cfg.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cfg_meta_set_program_info( + struct bt_audio_codec_cfg *codec_cfg, const uint8_t *program_info, + size_t program_info_len); + +/** + * @brief Extract language + * + * See @ref BT_AUDIO_METADATA_TYPE_LANG for more information about this + * value. + * + * @param[in] codec_cfg The codec data to search in. + * @param[out] lang Pointer to the language bytes (of length + * BT_AUDIO_LANG_SIZE) + * + * @retval 0 Success + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + * @retval -EBADMSG The found value has invalid size + */ + +int bt_audio_codec_cfg_meta_get_lang( + const struct bt_audio_codec_cfg *codec_cfg, const uint8_t **lang); + +/** + * @brief Set the language of a codec configuration metadata. + * + * @param codec_cfg The codec configuration to set data for. + * @param lang The 24-bit language to set. + * + * @retval data_len The @p codec_cfg.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cfg_meta_set_lang(struct bt_audio_codec_cfg *codec_cfg, + const uint8_t lang[BT_AUDIO_LANG_SIZE]); + +/** + * @brief Extract CCID list + * + * See @ref BT_AUDIO_METADATA_TYPE_CCID_LIST for more information about this + * value. + * + * @param[in] codec_cfg The codec data to search in. + * @param[out] ccid_list Pointer to the array containing 8-bit CCIDs. + * + * @retval len The length of the @p ccid_list (may be 0) + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + */ + +int bt_audio_codec_cfg_meta_get_ccid_list( + const struct bt_audio_codec_cfg *codec_cfg, const uint8_t **ccid_list); + +/** + * @brief Set the CCID list of a codec configuration metadata. + * + * @param codec_cfg The codec configuration to set data for. + * @param ccid_list The program info to set. + * @param ccid_list_len The length of @p ccid_list. + * + * @retval data_len The @p codec_cfg.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cfg_meta_set_ccid_list( + struct bt_audio_codec_cfg *codec_cfg, const uint8_t *ccid_list, + size_t ccid_list_len); + +/** + * @brief Extract parental rating + * + * See @ref BT_AUDIO_METADATA_TYPE_PARENTAL_RATING for more information about + * this value. + * + * @param codec_cfg The codec data to search in. + * + * @retval parental_rating The parental rating if positive or 0 + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + * @retval -EBADMSG The found value has invalid size + */ + +int bt_audio_codec_cfg_meta_get_parental_rating( + const struct bt_audio_codec_cfg *codec_cfg); + +/** + * @brief Set the parental rating of a codec configuration metadata. + * + * @param codec_cfg The codec configuration to set data for. + * @param parental_rating The parental rating to set. + * + * @retval data_len The @p codec_cfg.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cfg_meta_set_parental_rating( + struct bt_audio_codec_cfg *codec_cfg, + enum bt_audio_parental_rating parental_rating); + +/** + * @brief Extract program info URI + * + * See @ref BT_AUDIO_METADATA_TYPE_PROGRAM_INFO_URI for more information + * about this value. + * + * @param[in] codec_cfg The codec data to search in. + * @param[out] program_info_uri Pointer to the UTF-8 formatted program info + * URI. + * + * @retval len The length of the @p program_info_uri (may be 0) + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + */ + +int bt_audio_codec_cfg_meta_get_program_info_uri( + const struct bt_audio_codec_cfg *codec_cfg, + const uint8_t **program_info_uri); + +/** + * @brief Set the program info URI of a codec configuration metadata. + * + * @param codec_cfg The codec configuration to set data for. + * @param program_info_uri The program info URI to set. + * @param program_info_uri_len The length of @p program_info_uri. + * + * @retval data_len The @p codec_cfg.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cfg_meta_set_program_info_uri( + struct bt_audio_codec_cfg *codec_cfg, const uint8_t *program_info_uri, + size_t program_info_uri_len); + +/** + * @brief Extract audio active state + * + * See @ref BT_AUDIO_METADATA_TYPE_AUDIO_STATE for more information about + * this value. + * + * @param codec_cfg The codec data to search in. + * + * @retval context The preferred context type if positive or 0 + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + * @retval -EBADMSG The found value has invalid size + */ + +int bt_audio_codec_cfg_meta_get_audio_active_state( + const struct bt_audio_codec_cfg *codec_cfg); + +/** + * @brief Set the audio active state of a codec configuration metadata. + * + * @param codec_cfg The codec configuration to set data for. + * @param state The audio active state to set. + * + * @retval data_len The @p codec_cfg.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cfg_meta_set_audio_active_state( + struct bt_audio_codec_cfg *codec_cfg, enum bt_audio_active_state state); + +/** + * @brief Extract broadcast audio immediate rendering flag + * + * See @ref BT_AUDIO_METADATA_TYPE_BROADCAST_IMMEDIATE for more information + * about this value. + * + * @param codec_cfg The codec data to search in. + * + * @retval 0 The flag was found + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA The flag was not found + */ + +int bt_audio_codec_cfg_meta_get_bcast_audio_immediate_rend_flag( + const struct bt_audio_codec_cfg *codec_cfg); + +/** + * @brief Set the broadcast audio immediate rendering flag of a codec + * configuration metadata. + * + * @param codec_cfg The codec configuration to set data for. + * + * @retval data_len The @p codec_cfg.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cfg_meta_set_bcast_audio_immediate_rend_flag( + struct bt_audio_codec_cfg *codec_cfg); + +/** + * @brief Extract assisted listening stream + * + * See @ref BT_AUDIO_METADATA_TYPE_ASSISTED_LISTENING_STREAM for more + * information about this value. + * + * @param codec_cfg The codec data to search in. + * + * @retval value The assisted listening stream value if positive or 0 + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + * @retval -EBADMSG The found value has invalid size + */ + +int bt_audio_codec_cfg_meta_get_assisted_listening_stream( + const struct bt_audio_codec_cfg *codec_cfg); + +/** + * @brief Set the assisted listening stream value of a codec configuration + * metadata. + * + * @param codec_cfg The codec configuration to set data for. + * @param val The assisted listening stream value to set. + * + * @retval data_len The @p codec_cap.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cfg_meta_set_assisted_listening_stream( + struct bt_audio_codec_cfg *codec_cfg, + enum bt_audio_assisted_listening_stream val); + +/** + * @brief Extract broadcast name + * + * See @ref BT_AUDIO_METADATA_TYPE_BROADCAST_NAME for more information about + * this value. + * + * @param[in] codec_cfg The codec data to search in. + * @param[out] broadcast_name Pointer to the UTF-8 formatted broadcast name. + * + * @retval length The length of the @p broadcast_name (may be 0) + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + * @retval -EBADMSG Data found, but Invalid broadcast name + */ + +int bt_audio_codec_cfg_meta_get_broadcast_name( + const struct bt_audio_codec_cfg *codec_cfg, + const uint8_t **broadcast_name); + +/** + * @brief Set the broadcast name of a codec configuration metadata. + * + * @param codec_cfg The codec configuration to set data for. + * @param broadcast_name The broadcast name to set. Shall be between @ref + * BT_AUDIO_BROADCAST_NAME_CHAR_MIN and @ref + * BT_AUDIO_BROADCAST_NAME_CHAR_MAX characters. + * @param broadcast_name_len The length of @p broadcast_name. Shall be + * between + * @ref BT_AUDIO_BROADCAST_NAME_LEN_MIN and + * @ref BT_AUDIO_BROADCAST_NAME_LEN_MAX. + * + * @retval data_len The @p codec_cfg.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cfg_meta_set_broadcast_name( + struct bt_audio_codec_cfg *codec_cfg, const uint8_t *broadcast_name, + size_t broadcast_name_len); + +/** + * @brief Extract extended metadata + * + * See @ref BT_AUDIO_METADATA_TYPE_EXTENDED for more information about this + * value. + * + * @param[in] codec_cfg The codec data to search in. + * @param[out] extended_meta Pointer to the extended metadata. + * + * @retval len The length of the @p extended_meta (may be 0) + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + */ + +int bt_audio_codec_cfg_meta_get_extended( + const struct bt_audio_codec_cfg *codec_cfg, const uint8_t **extended_meta); + +/** + * @brief Set the extended metadata of a codec configuration metadata. + * + * @param codec_cfg The codec configuration to set data for. + * @param extended_meta The extended metadata to set. + * @param extended_meta_len The length of @p extended_meta. + * + * @retval data_len The @p codec_cfg.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cfg_meta_set_extended( + struct bt_audio_codec_cfg *codec_cfg, const uint8_t *extended_meta, + size_t extended_meta_len); + +/** + * @brief Extract vendor specific metadata + * + * See @ref BT_AUDIO_METADATA_TYPE_VENDOR for more information about this + * value. + * + * @param[in] codec_cfg The codec data to search in. + * @param[out] vendor_meta Pointer to the vendor specific metadata. + * + * @retval len The length of the @p vendor_meta (may be 0) + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + */ + +int bt_audio_codec_cfg_meta_get_vendor( + const struct bt_audio_codec_cfg *codec_cfg, const uint8_t **vendor_meta); + +/** + * @brief Set the vendor specific metadata of a codec configuration metadata. + * + * @param codec_cfg The codec configuration to set data for. + * @param vendor_meta The vendor specific metadata to set. + * @param vendor_meta_len The length of @p vendor_meta. + * + * @retval data_len The @p codec_cfg.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cfg_meta_set_vendor(struct bt_audio_codec_cfg *codec_cfg, + const uint8_t *vendor_meta, + size_t vendor_meta_len); +/** @} */ /* End of bt_audio_codec_cfg */ + +/** + * @brief Audio codec capabilities APIs + * @defgroup bt_audio_codec_cap Codec capability parsing APIs + * + * Functions to parse codec capability data when formatted as LTV wrapped + * into @ref bt_audio_codec_cap. + * + * @{ + */ + +/** + * @brief Lookup a specific value based on type + * + * @param[in] codec_cap The codec data to search in. + * @param[in] type The type id to look for + * @param[out] data Pointer to the data-pointer to update when item is + * found + * + * @retval len Length of found @p data (may be 0) + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + */ + +int bt_audio_codec_cap_get_val(const struct bt_audio_codec_cap *codec_cap, + enum bt_audio_codec_cap_type type, + const uint8_t **data); + +/** + * @brief Set or add a specific codec capability value + * + * @param codec_cap The codec data to set the value in. + * @param type The type id to set + * @param data Pointer to the data-pointer to set + * @param data_len Length of @p data + * + * @retval data_len The @p codec_cap.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cap_set_val(struct bt_audio_codec_cap *codec_cap, + enum bt_audio_codec_cap_type type, + const uint8_t *data, + size_t data_len); + +/** + * @brief Unset a specific codec capability value + * + * The type and the value will be removed from the codec capability. + * + * @param codec_cap The codec data to set the value in. + * @param type The type id to unset. + * + * @retval data_len The @p codec_cap.data_len on success + * @retval -EINVAL Arguments are invalid + */ + +int bt_audio_codec_cap_unset_val(struct bt_audio_codec_cap *codec_cap, + enum bt_audio_codec_cap_type type); + +/** + * @brief Extract the frequency from a codec capability. + * + * @param codec_cap The codec capabilities to extract data from. + * + * @retval frequencies Bitfield of supported frequencies (@ref + * bt_audio_codec_cap_freq) if 0 or positive + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + * @retval -EBADMSG The found value has invalid size or value + */ + +int bt_audio_codec_cap_get_freq(const struct bt_audio_codec_cap *codec_cap); + +/** + * @brief Set the supported frequencies of a codec capability. + * + * @param codec_cap The codec capabilities to set data for. + * @param freq The supported frequencies to set. + * + * @retval data_len The @p codec_cap.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cap_set_freq(struct bt_audio_codec_cap *codec_cap, + enum bt_audio_codec_cap_freq freq); + +/** + * @brief Extract the frame duration from a codec capability. + * + * @param codec_cap The codec capabilities to extract data from. + * + * @retval durations Bitfield of supported frame durations if 0 or positive + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + * @retval -EBADMSG The found value has invalid size or value + */ + +int bt_audio_codec_cap_get_frame_dur( + const struct bt_audio_codec_cap *codec_cap); + +/** + * @brief Set the frame duration of a codec capability. + * + * @param codec_cap The codec capabilities to set data for. + * @param frame_dur The frame duration to set. + * + * @retval data_len The @p codec_cap.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cap_set_frame_dur(struct bt_audio_codec_cap *codec_cap, + enum bt_audio_codec_cap_frame_dur frame_dur); + +/** + * @brief Extract the supported audio channel counts from a codec capability. + * + * @param codec_cap The codec capabilities to extract data from. + * @param fallback_to_default If true this function will provide the default + * value of 1 if the type is not found when @p + * codec_cap.id is @ref BT_HCI_CODING_FORMAT_LC3. + * + * @retval channel_counts Number of supported channel counts if 0 or positive + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + * @retval -EBADMSG The found value has invalid size or value + */ + +int bt_audio_codec_cap_get_supported_audio_chan_counts( + const struct bt_audio_codec_cap *codec_cap, bool fallback_to_default); + +/** + * @brief Set the channel count of a codec capability. + * + * @param codec_cap The codec capabilities to set data for. + * @param chan_count The channel count frequency to set. + * + * @retval data_len The @p codec_cap.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cap_set_supported_audio_chan_counts( + struct bt_audio_codec_cap *codec_cap, + enum bt_audio_codec_cap_chan_count chan_count); + +/** + * @brief Extract the supported octets per codec frame from a codec + * capability. + * + * @param[in] codec_cap The codec capabilities to extract data from. + * @param[out] codec_frame Struct to place the resulting values in + * + * @retval 0 Success + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + * @retval -EBADMSG The found value has invalid size or value + */ + +int bt_audio_codec_cap_get_octets_per_frame( + const struct bt_audio_codec_cap *codec_cap, + struct bt_audio_codec_octets_per_codec_frame *codec_frame); + +/** + * @brief Set the octets per codec frame of a codec capability. + * + * @param codec_cap The codec capabilities to set data for. + * @param codec_frame The octets per codec frame to set. + * + * @retval data_len The @p codec_cap.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cap_set_octets_per_frame( + struct bt_audio_codec_cap *codec_cap, + const struct bt_audio_codec_octets_per_codec_frame *codec_frame); + +/** + * @brief Extract the maximum codec frames per SDU from a codec capability. + * + * @param codec_cap The codec capabilities to extract data from. + * @param fallback_to_default If true this function will provide the default + * value of 1 if the type is not found when @p + * codec_cap.id is @ref BT_HCI_CODING_FORMAT_LC3. + * + * @retval codec_frames_per_sdu Maximum number of codec frames per SDU + * supported + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + * @retval -EBADMSG The found value has invalid size or value + */ + +int bt_audio_codec_cap_get_max_codec_frames_per_sdu( + const struct bt_audio_codec_cap *codec_cap, bool fallback_to_default); + +/** + * @brief Set the maximum codec frames per SDU of a codec capability. + * + * @param codec_cap The codec capabilities to set data for. + * @param codec_frames_per_sdu The maximum codec frames per SDU to set. + * + * @retval data_len The @p codec_cap.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cap_set_max_codec_frames_per_sdu( + struct bt_audio_codec_cap *codec_cap, uint8_t codec_frames_per_sdu); + +/** + * @brief Lookup a specific metadata value based on type + * + * @param[in] codec_cap The codec data to search in. + * @param[in] type The type id to look for + * @param[out] data Pointer to the data-pointer to update when item is + * found + * + * @retval len Length of found @p data (may be 0) + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + */ + +int bt_audio_codec_cap_meta_get_val( + const struct bt_audio_codec_cap *codec_cap, uint8_t type, + const uint8_t **data); + +/** + * @brief Set or add a specific codec capability metadata value. + * + * @param codec_cap The codec capability to set the value in. + * @param type The type id to set. + * @param data Pointer to the data-pointer to set. + * @param data_len Length of @p data. + * + * @retval meta_len The @p codec_cap.meta_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cap_meta_set_val(struct bt_audio_codec_cap *codec_cap, + enum bt_audio_metadata_type type, + const uint8_t *data, + size_t data_len); + +/** + * @brief Unset a specific codec capability metadata value + * + * The type and the value will be removed from the codec capability metadata. + * + * @param codec_cap The codec data to set the value in. + * @param type The type id to unset. + * + * @retval meta_len The of @p codec_cap.meta_len on success + * @retval -EINVAL Arguments are invalid + */ + +int bt_audio_codec_cap_meta_unset_val(struct bt_audio_codec_cap *codec_cap, + enum bt_audio_metadata_type type); + +/** + * @brief Extract preferred contexts + * + * See @ref BT_AUDIO_METADATA_TYPE_PREF_CONTEXT for more information about + * this value. + * + * @param codec_cap The codec data to search in. + * + * @retval The preferred context type if positive or 0 + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + * @retval -EBADMSG The found value has invalid size + */ + +int bt_audio_codec_cap_meta_get_pref_context( + const struct bt_audio_codec_cap *codec_cap); + +/** + * @brief Set the preferred context of a codec capability metadata. + * + * @param codec_cap The codec capability to set data for. + * @param ctx The preferred context to set. + * + * @retval data_len The @p codec_cap.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cap_meta_set_pref_context( + struct bt_audio_codec_cap *codec_cap, enum bt_audio_context ctx); + +/** + * @brief Extract stream contexts + * + * See @ref BT_AUDIO_METADATA_TYPE_STREAM_CONTEXT for more information about + * this value. + * + * @param codec_cap The codec data to search in. + * + * @retval context The stream context type if positive or 0 + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + * @retval -EBADMSG The found value has invalid size + */ + +int bt_audio_codec_cap_meta_get_stream_context( + const struct bt_audio_codec_cap *codec_cap); + +/** + * @brief Set the stream context of a codec capability metadata. + * + * @param codec_cap The codec capability to set data for. + * @param ctx The stream context to set. + * + * @retval data_len The @p codec_cap.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cap_meta_set_stream_context( + struct bt_audio_codec_cap *codec_cap, enum bt_audio_context ctx); + +/** + * @brief Extract program info + * + * See @ref BT_AUDIO_METADATA_TYPE_PROGRAM_INFO for more information about + * this value. + * + * @param[in] codec_cap The codec data to search in. + * @param[out] program_info Pointer to the UTF-8 formatted program info. + * + * @retval len The length of the @p program_info (may be 0) + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + */ + +int bt_audio_codec_cap_meta_get_program_info( + const struct bt_audio_codec_cap *codec_cap, const uint8_t **program_info); + +/** + * @brief Set the program info of a codec capability metadata. + * + * @param codec_cap The codec capability to set data for. + * @param program_info The program info to set. + * @param program_info_len The length of @p program_info. + * + * @retval data_len The @p codec_cap.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cap_meta_set_program_info( + struct bt_audio_codec_cap *codec_cap, const uint8_t *program_info, + size_t program_info_len); + +/** + * @brief Extract language + * + * See @ref BT_AUDIO_METADATA_TYPE_LANG for more information about this + * value. + * + * @param[in] codec_cap The codec data to search in. + * @param[out] lang Pointer to the language bytes (of length + * BT_AUDIO_LANG_SIZE) + * + * @retval 0 Success + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + * @retval -EBADMSG The found value has invalid size + */ + +int bt_audio_codec_cap_meta_get_lang( + const struct bt_audio_codec_cap *codec_cap, const uint8_t **lang); + +/** + * @brief Set the language of a codec capability metadata. + * + * @param codec_cap The codec capability to set data for. + * @param lang The 24-bit language to set. + * + * @retval data_len The @p codec_cap.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cap_meta_set_lang(struct bt_audio_codec_cap *codec_cap, + const uint8_t lang[BT_AUDIO_LANG_SIZE]); + +/** + * @brief Extract CCID list + * + * See @ref BT_AUDIO_METADATA_TYPE_CCID_LIST for more information about this + * value. + * + * @param[in] codec_cap The codec data to search in. + * @param[out] ccid_list Pointer to the array containing 8-bit CCIDs. + * + * @retval len The length of the @p ccid_list (may be 0) + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + */ + +int bt_audio_codec_cap_meta_get_ccid_list( + const struct bt_audio_codec_cap *codec_cap, const uint8_t **ccid_list); + +/** + * @brief Set the CCID list of a codec capability metadata. + * + * @param codec_cap The codec capability to set data for. + * @param ccid_list The program info to set. + * @param ccid_list_len The length of @p ccid_list. + * + * @retval data_len The @p codec_cap.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cap_meta_set_ccid_list( + struct bt_audio_codec_cap *codec_cap, const uint8_t *ccid_list, + size_t ccid_list_len); + +/** + * @brief Extract parental rating + * + * See @ref BT_AUDIO_METADATA_TYPE_PARENTAL_RATING for more information about + * this value. + * + * @param codec_cap The codec data to search in. + * + * @retval The parental rating if positive or 0 + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + * @retval -EBADMSG The found value has invalid size + */ + +int bt_audio_codec_cap_meta_get_parental_rating( + const struct bt_audio_codec_cap *codec_cap); + +/** + * @brief Set the parental rating of a codec capability metadata. + * + * @param codec_cap The codec capability to set data for. + * @param parental_rating The parental rating to set. + * + * @retval data_len The @p codec_cap.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cap_meta_set_parental_rating( + struct bt_audio_codec_cap *codec_cap, + enum bt_audio_parental_rating parental_rating); + +/** + * @brief Extract program info URI + * + * See @ref BT_AUDIO_METADATA_TYPE_PROGRAM_INFO_URI for more information + * about this value. + * + * @param[in] codec_cap The codec data to search in. + * @param[out] program_info_uri Pointer to the UTF-8 formatted program info + * URI. + * + * @retval len The length of the @p program_info_uri (may be 0) + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + */ + +int bt_audio_codec_cap_meta_get_program_info_uri( + const struct bt_audio_codec_cap *codec_cap, + const uint8_t **program_info_uri); + +/** + * @brief Set the program info URI of a codec capability metadata. + * + * @param codec_cap The codec capability to set data for. + * @param program_info_uri The program info URI to set. + * @param program_info_uri_len The length of @p program_info_uri. + * + * @retval data_len The @p codec_cap.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cap_meta_set_program_info_uri( + struct bt_audio_codec_cap *codec_cap, const uint8_t *program_info_uri, + size_t program_info_uri_len); + +/** + * @brief Extract audio active state + * + * See @ref BT_AUDIO_METADATA_TYPE_AUDIO_STATE for more information about + * this value. + * + * @param codec_cap The codec data to search in. + * + * @retval context The preferred context type if positive or 0 + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + * @retval -EBADMSG The found value has invalid size + */ + +int bt_audio_codec_cap_meta_get_audio_active_state( + const struct bt_audio_codec_cap *codec_cap); + +/** + * @brief Set the audio active state of a codec capability metadata. + * + * @param codec_cap The codec capability to set data for. + * @param state The audio active state to set. + * + * @retval data_len The @p codec_cap.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cap_meta_set_audio_active_state( + struct bt_audio_codec_cap *codec_cap, enum bt_audio_active_state state); + +/** + * @brief Extract broadcast audio immediate rendering flag + * + * See @ref BT_AUDIO_METADATA_TYPE_BROADCAST_IMMEDIATE for more information + * about this value. + * + * @param codec_cap The codec data to search in. + * + * @retval 0 The flag was found + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA The flag was not found + */ + +int bt_audio_codec_cap_meta_get_bcast_audio_immediate_rend_flag( + const struct bt_audio_codec_cap *codec_cap); + +/** + * @brief Set the broadcast audio immediate rendering flag of a codec + * capability metadata. + * + * @param codec_cap The codec capability to set data for. + * + * @retval data_len The @p codec_cap.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cap_meta_set_bcast_audio_immediate_rend_flag( + struct bt_audio_codec_cap *codec_cap); + +/** + * @brief Extract assisted listening stream + * + * See @ref BT_AUDIO_METADATA_TYPE_ASSISTED_LISTENING_STREAM for more + * information about this value. + * + * @param codec_cap The codec data to search in. + * + * @retval value The assisted listening stream value if positive or 0 + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + * @retval -EBADMSG The found value has invalid size + */ + +int bt_audio_codec_cap_meta_get_assisted_listening_stream( + const struct bt_audio_codec_cap *codec_cap); + +/** + * @brief Set the assisted listening stream value of a codec capability + * metadata. + * + * @param codec_cap The codec capability to set data for. + * @param val The assisted listening stream value to set. + * + * @retval data_len The @p codec_cap.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cap_meta_set_assisted_listening_stream( + struct bt_audio_codec_cap *codec_cap, + enum bt_audio_assisted_listening_stream val); + +/** + * @brief Extract broadcast name + * + * See @ref BT_AUDIO_METADATA_TYPE_BROADCAST_NAME for more information about + * this value. + * + * @param[in] codec_cap The codec data to search in. + * @param[out] broadcast_name Pointer to the UTF-8 formatted broadcast name. + * + * @retval length The length of the @p broadcast_name (may be 0) + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + */ + +int bt_audio_codec_cap_meta_get_broadcast_name( + const struct bt_audio_codec_cap *codec_cap, + const uint8_t **broadcast_name); + +/** + * @brief Set the broadcast name of a codec capability metadata. + * + * @param codec_cap The codec capability to set data for. + * @param broadcast_name The broadcast name to set. Shall be between @ref + * BT_AUDIO_BROADCAST_NAME_CHAR_MIN and @ref + * BT_AUDIO_BROADCAST_NAME_CHAR_MAX characters. + * @param broadcast_name_len The length of @p broadcast_name. Shall be + * between + * @ref BT_AUDIO_BROADCAST_NAME_LEN_MIN and + * @ref BT_AUDIO_BROADCAST_NAME_LEN_MAX. + * + * @retval data_len The @p codec_cap.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cap_meta_set_broadcast_name( + struct bt_audio_codec_cap *codec_cap, const uint8_t *broadcast_name, + size_t broadcast_name_len); +/** + * @brief Extract extended metadata + * + * See @ref BT_AUDIO_METADATA_TYPE_EXTENDED for more information about this + * value. + * + * @param[in] codec_cap The codec data to search in. + * @param[out] extended_meta Pointer to the extended metadata. + * + * @retval len The length of the @p extended_meta (may be 0) + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + */ + +int bt_audio_codec_cap_meta_get_extended( + const struct bt_audio_codec_cap *codec_cap, const uint8_t **extended_meta); + +/** + * @brief Set the extended metadata of a codec capability metadata. + * + * @param codec_cap The codec capability to set data for. + * @param extended_meta The extended metadata to set. + * @param extended_meta_len The length of @p extended_meta. + * + * @retval data_len The @p codec_cap.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cap_meta_set_extended( + struct bt_audio_codec_cap *codec_cap, const uint8_t *extended_meta, + size_t extended_meta_len); + +/** + * @brief Extract vendor specific metadata + * + * See @ref BT_AUDIO_METADATA_TYPE_VENDOR for more information about this + * value. + * + * @param[in] codec_cap The codec data to search in. + * @param[out] vendor_meta Pointer to the vendor specific metadata. + * + * @retval len The length of the @p vendor_meta (may be 0) + * @retval -EINVAL Arguments are invalid + * @retval -ENODATA Data not found + */ + +int bt_audio_codec_cap_meta_get_vendor( + const struct bt_audio_codec_cap *codec_cap, const uint8_t **vendor_meta); + +/** + * @brief Set the vendor specific metadata of a codec capability metadata. + * + * @param codec_cap The codec capability to set data for. + * @param vendor_meta The vendor specific metadata to set. + * @param vendor_meta_len The length of @p vendor_meta. + * + * @retval data_len The @p codec_cap.data_len on success + * @retval -EINVAL Arguments are invalid + * @retval -ENOMEM The new value could not be set or added due to lack of + * memory + */ + +int bt_audio_codec_cap_meta_set_vendor(struct bt_audio_codec_cap *codec_cap, + const uint8_t *vendor_meta, + size_t vendor_meta_len); + +/** @} */ /* End of bt_audio_codec_cap */ + +/** + * @brief Assigned numbers to string API + * @defgroup bt_audio_to_str Assigned numbers to string API + * + * Functions to return string representation of Bluetooth Audio assigned + * number values. + * + * @{ + */ + +/** + * @brief Returns a string representation of a specific @ref bt_audio_context + * bit + * + * If @p context contains multiple bits, it will return "Unknown context" + * + * @param context A single context bit + * + * @return String representation of the supplied bit + */ + +static inline char * +bt_audio_context_bit_to_str(enum bt_audio_context context) +{ + switch (context) + { + case BT_AUDIO_CONTEXT_TYPE_NONE: + return "None"; + case BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED: + return "Unspecified"; + case BT_AUDIO_CONTEXT_TYPE_CONVERSATIONAL: + return "Conversational"; + case BT_AUDIO_CONTEXT_TYPE_MEDIA: + return "Media"; + case BT_AUDIO_CONTEXT_TYPE_GAME: + return "Game"; + case BT_AUDIO_CONTEXT_TYPE_INSTRUCTIONAL: + return "Instructional"; + case BT_AUDIO_CONTEXT_TYPE_VOICE_ASSISTANTS: + return "Voice assistant"; + case BT_AUDIO_CONTEXT_TYPE_LIVE: + return "Live"; + case BT_AUDIO_CONTEXT_TYPE_SOUND_EFFECTS: + return "Sound effects"; + case BT_AUDIO_CONTEXT_TYPE_NOTIFICATIONS: + return "Notifications"; + case BT_AUDIO_CONTEXT_TYPE_RINGTONE: + return "Ringtone"; + case BT_AUDIO_CONTEXT_TYPE_ALERTS: + return "Alerts"; + case BT_AUDIO_CONTEXT_TYPE_EMERGENCY_ALARM: + return "Emergency alarm"; + default: + return "Unknown context"; + } +} + +/** + * @brief Returns a string representation of a @ref bt_audio_parental_rating + * value + * + * @param parental_rating The parental rating value + * + * @return String representation of the supplied parental rating value + */ + +static inline char *bt_audio_parental_rating_to_str( + enum bt_audio_parental_rating parental_rating) +{ + switch (parental_rating) + { + case BT_AUDIO_PARENTAL_RATING_NO_RATING: + return "No rating"; + case BT_AUDIO_PARENTAL_RATING_AGE_ANY: + return "Any"; + case BT_AUDIO_PARENTAL_RATING_AGE_5_OR_ABOVE: + return "Age 5 or above"; + case BT_AUDIO_PARENTAL_RATING_AGE_6_OR_ABOVE: + return "Age 6 or above"; + case BT_AUDIO_PARENTAL_RATING_AGE_7_OR_ABOVE: + return "Age 7 or above"; + case BT_AUDIO_PARENTAL_RATING_AGE_8_OR_ABOVE: + return "Age 8 or above"; + case BT_AUDIO_PARENTAL_RATING_AGE_9_OR_ABOVE: + return "Age 9 or above"; + case BT_AUDIO_PARENTAL_RATING_AGE_10_OR_ABOVE: + return "Age 10 or above"; + case BT_AUDIO_PARENTAL_RATING_AGE_11_OR_ABOVE: + return "Age 11 or above"; + case BT_AUDIO_PARENTAL_RATING_AGE_12_OR_ABOVE: + return "Age 12 or above"; + case BT_AUDIO_PARENTAL_RATING_AGE_13_OR_ABOVE: + return "Age 13 or above"; + case BT_AUDIO_PARENTAL_RATING_AGE_14_OR_ABOVE: + return "Age 14 or above"; + case BT_AUDIO_PARENTAL_RATING_AGE_15_OR_ABOVE: + return "Age 15 or above"; + case BT_AUDIO_PARENTAL_RATING_AGE_16_OR_ABOVE: + return "Age 16 or above"; + case BT_AUDIO_PARENTAL_RATING_AGE_17_OR_ABOVE: + return "Age 17 or above"; + case BT_AUDIO_PARENTAL_RATING_AGE_18_OR_ABOVE: + return "Age 18 or above"; + default: + return "Unknown rating"; + } +} + +/** + * @brief Returns a string representation of a @ref bt_audio_active_state + * value + * + * @param state The active state value + * + * @return String representation of the supplied active state value + */ + +static inline char *bt_audio_active_state_to_str( + enum bt_audio_active_state state) +{ + switch (state) + { + case BT_AUDIO_ACTIVE_STATE_DISABLED: + return "Disabled"; + case BT_AUDIO_ACTIVE_STATE_ENABLED: + return "Enabled"; + default: + return "Unknown active state"; + } +} + +/** + * @brief Returns a string representation of a specific @ref + * bt_audio_codec_cap_freq bit + * + * If @p freq contains multiple bits, it will return "Unknown supported + * frequency" + * + * @param freq A single frequency bit + * + * @return String representation of the supplied bit + */ + +static inline char *bt_audio_codec_cap_freq_bit_to_str( + enum bt_audio_codec_cap_freq freq) +{ + switch (freq) + { + case BT_AUDIO_CODEC_CAP_FREQ_8KHZ: + return "8000 Hz"; + case BT_AUDIO_CODEC_CAP_FREQ_11KHZ: + return "11025 Hz"; + case BT_AUDIO_CODEC_CAP_FREQ_16KHZ: + return "16000 Hz"; + case BT_AUDIO_CODEC_CAP_FREQ_22KHZ: + return "22050 Hz"; + case BT_AUDIO_CODEC_CAP_FREQ_24KHZ: + return "24000 Hz"; + case BT_AUDIO_CODEC_CAP_FREQ_32KHZ: + return "32000 Hz"; + case BT_AUDIO_CODEC_CAP_FREQ_44KHZ: + return "44100 Hz"; + case BT_AUDIO_CODEC_CAP_FREQ_48KHZ: + return "48000 Hz"; + case BT_AUDIO_CODEC_CAP_FREQ_88KHZ: + return "88200 Hz"; + case BT_AUDIO_CODEC_CAP_FREQ_96KHZ: + return "96000 Hz"; + case BT_AUDIO_CODEC_CAP_FREQ_176KHZ: + return "176400 Hz"; + case BT_AUDIO_CODEC_CAP_FREQ_192KHZ: + return "192000 Hz"; + case BT_AUDIO_CODEC_CAP_FREQ_384KHZ: + return "384000 Hz"; + default: + return "Unknown supported frequency"; + } +} + +/** + * @brief Returns a string representation of a specific @ref + * bt_audio_codec_cap_frame_dur bit + * + * If @p frame_dur contains multiple bits, it will return "Unknown frame + * duration" + * + * @param frame_dur A single frame duration bit + * + * @return String representation of the supplied bit + */ + +static inline char * +bt_audio_codec_cap_frame_dur_bit_to_str( + enum bt_audio_codec_cap_frame_dur frame_dur) +{ + switch (frame_dur) + { + case BT_AUDIO_CODEC_CAP_DURATION_7_5: + return "7.5 ms"; + case BT_AUDIO_CODEC_CAP_DURATION_10: + return "10 ms"; + case BT_AUDIO_CODEC_CAP_DURATION_PREFER_7_5: + return "7.5 ms preferred"; + case BT_AUDIO_CODEC_CAP_DURATION_PREFER_10: + return "10 ms preferred"; + default: + return "Unknown frame duration"; + } +} + +/** + * @brief Returns a string representation of a specific @ref + * bt_audio_codec_cap_chan_count bit + * + * If @p chan_count contains multiple bits, it will return "Unknown channel + * count" + * + * @param chan_count A single frame channel count bit + * + * @return String representation of the supplied bit + */ + +static inline char * +bt_audio_codec_cap_chan_count_bit_to_str( + enum bt_audio_codec_cap_chan_count chan_count) +{ + switch (chan_count) + { + case BT_AUDIO_CODEC_CAP_CHAN_COUNT_1: + return "1 channel"; + case BT_AUDIO_CODEC_CAP_CHAN_COUNT_2: + return "2 channels"; + case BT_AUDIO_CODEC_CAP_CHAN_COUNT_3: + return "3 channels"; + case BT_AUDIO_CODEC_CAP_CHAN_COUNT_4: + return "4 channels"; + case BT_AUDIO_CODEC_CAP_CHAN_COUNT_5: + return "5 channels"; + case BT_AUDIO_CODEC_CAP_CHAN_COUNT_6: + return "6 channels"; + case BT_AUDIO_CODEC_CAP_CHAN_COUNT_7: + return "7 channels"; + case BT_AUDIO_CODEC_CAP_CHAN_COUNT_8: + return "8 channels"; + default: + return "Unknown channel count"; + } +} + +/** + * @brief Returns a string representation of a specific @ref + * bt_audio_location bit + * + * If @p location contains multiple bits, it will return "Unknown location" + * + * @param location A single location bit + * + * @return String representation of the supplied bit + */ + +static inline char *bt_audio_location_bit_to_str( + enum bt_audio_location location) +{ + switch (location) + { + case BT_AUDIO_LOCATION_MONO_AUDIO: + return "Mono"; + case BT_AUDIO_LOCATION_FRONT_LEFT: + return "Front left"; + case BT_AUDIO_LOCATION_FRONT_RIGHT: + return "Front right"; + case BT_AUDIO_LOCATION_FRONT_CENTER: + return "Front center"; + case BT_AUDIO_LOCATION_LOW_FREQ_EFFECTS_1: + return "Low frequency effects 1"; + case BT_AUDIO_LOCATION_BACK_LEFT: + return "Back left"; + case BT_AUDIO_LOCATION_BACK_RIGHT: + return "Back right"; + case BT_AUDIO_LOCATION_FRONT_LEFT_OF_CENTER: + return "Front left of center"; + case BT_AUDIO_LOCATION_FRONT_RIGHT_OF_CENTER: + return "Front right of center"; + case BT_AUDIO_LOCATION_BACK_CENTER: + return "Back center"; + case BT_AUDIO_LOCATION_LOW_FREQ_EFFECTS_2: + return "Low frequency effects 2"; + case BT_AUDIO_LOCATION_SIDE_LEFT: + return "Side left"; + case BT_AUDIO_LOCATION_SIDE_RIGHT: + return "Side right"; + case BT_AUDIO_LOCATION_TOP_FRONT_LEFT: + return "Top front left"; + case BT_AUDIO_LOCATION_TOP_FRONT_RIGHT: + return "Top front right"; + case BT_AUDIO_LOCATION_TOP_FRONT_CENTER: + return "Top front center"; + case BT_AUDIO_LOCATION_TOP_CENTER: + return "Top center"; + case BT_AUDIO_LOCATION_TOP_BACK_LEFT: + return "Top back left"; + case BT_AUDIO_LOCATION_TOP_BACK_RIGHT: + return "Top back right"; + case BT_AUDIO_LOCATION_TOP_SIDE_LEFT: + return "Top side left"; + case BT_AUDIO_LOCATION_TOP_SIDE_RIGHT: + return "Top side right"; + case BT_AUDIO_LOCATION_TOP_BACK_CENTER: + return "Top back center"; + case BT_AUDIO_LOCATION_BOTTOM_FRONT_CENTER: + return "Bottom front center"; + case BT_AUDIO_LOCATION_BOTTOM_FRONT_LEFT: + return "Bottom front left"; + case BT_AUDIO_LOCATION_BOTTOM_FRONT_RIGHT: + return "Bottom front right"; + case BT_AUDIO_LOCATION_FRONT_LEFT_WIDE: + return "Front left wide"; + case BT_AUDIO_LOCATION_FRONT_RIGHT_WIDE: + return "Front right wde"; + case BT_AUDIO_LOCATION_LEFT_SURROUND: + return "Left surround"; + case BT_AUDIO_LOCATION_RIGHT_SURROUND: + return "Right surround"; + default: + return "Unknown location"; + } +} + +/** @} */ /* End of bt_audio_to_str */ +#ifdef __cplusplus +} +#endif + +/** @} */ /* end of bt_audio */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_AUDIO_H */ diff --git a/include/nuttx/net/bluetooth/audio/bap.h b/include/nuttx/net/bluetooth/audio/bap.h new file mode 100644 index 0000000000000..9ae154e3dfae3 --- /dev/null +++ b/include/nuttx/net/bluetooth/audio/bap.h @@ -0,0 +1,3604 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/audio/bap.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Header for Bluetooth BAP. + * + * Copyright (c) 2020 Bose Corporation Copyright (c) 2021-2026 Nordic + * Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_BAP_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_BAP_H + +/** + * @brief Bluetooth Basic Audio Profile (BAP) + * @defgroup bt_bap Bluetooth Basic Audio Profile + * + * @since 3.0 + * @version 0.8.0 + * + * @ingroup bluetooth + * @{ + * + * The Basic Audio Profile (BAP) allows for both unicast and broadcast Audio + * Stream control. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Maximum number of subgroups supported in the BAP Scan Delegator API */ +#define BT_BAP_BASS_MAX_SUBGROUPS \ +COND_CODE_1(CONFIG_BT_AUDIO, (CONFIG_BT_BAP_BASS_MAX_SUBGROUPS), (0)) + +/** Maximum size of BASE excluding service data header */ +#define BT_BASE_MAX_SIZE (UINT8_MAX - 1 /* type */ - BT_UUID_SIZE_16) + +/** An invalid Broadcast ID */ +#define BT_BAP_INVALID_BROADCAST_ID 0xFFFFFFFFU + +/** Value that represents an unset presentation delay value */ +#define BT_BAP_PD_UNSET 0xFFFFFFFFU + +/** + * @brief Recommended connectable advertising parameters + * + * If connection has not been established after 30 seconds, the device should + * switch to + * @ref BT_BAP_ADV_PARAM_CONN_REDUCED + * + * Defined by Table 8.1 in BAP 1.0.2 + */ +#define BT_BAP_ADV_PARAM_CONN_QUICK \ +BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONN | BT_LE_ADV_OPT_EXT_ADV, BT_GAP_MS_TO_ADV_INTERVAL(20), \ + BT_GAP_MS_TO_ADV_INTERVAL(30), NULL) + +/** + * @brief Reduced connectable advertising parameters + * + * Defined by Table 8.1 in BAP 1.0.2 + */ +#define BT_BAP_ADV_PARAM_CONN_REDUCED \ +BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONN | BT_LE_ADV_OPT_EXT_ADV, BT_GAP_MS_TO_ADV_INTERVAL(150), \ + BT_GAP_MS_TO_ADV_INTERVAL(150), NULL) + +/** + * @brief Recommended connection parameters for initial connection request + * for 7.5 ms SDU interval + * + * Once service discovery has completed and a stream is setup, it is + * recommended to switch to + * @ref BT_BAP_CONN_PARAM_RELAXED + * + * Defined by Table 8.3 in BAP 1.0.2 + */ +#define BT_BAP_CONN_PARAM_SHORT_7_5 \ +BT_LE_CONN_PARAM(BT_GAP_US_TO_CONN_INTERVAL(7500), BT_GAP_MS_TO_CONN_INTERVAL(30), 0, \ + BT_GAP_MS_TO_CONN_TIMEOUT(4000)) + +/** + * @brief Recommended connection parameters for initial connection request + * for 10 ms SDU interval + * + * Once service discovery has completed and a stream is setup, it is + * recommended to switch to + * @ref BT_BAP_CONN_PARAM_RELAXED + * + * Defined by Table 8.3 in BAP 1.0.2 + */ +#define BT_BAP_CONN_PARAM_SHORT_10 \ +BT_LE_CONN_PARAM(BT_GAP_MS_TO_CONN_INTERVAL(10), BT_GAP_MS_TO_CONN_INTERVAL(30), 0, \ + BT_GAP_MS_TO_CONN_TIMEOUT(4000)) + +/** + * @brief Recommended connection parameters for coexistence of ACL and ISO + * + * Defined by Table 8.4 in BAP 1.0.2 + */ +#define BT_BAP_CONN_PARAM_RELAXED \ +BT_LE_CONN_PARAM(BT_GAP_MS_TO_CONN_INTERVAL(50), BT_GAP_MS_TO_CONN_INTERVAL(70), 0, \ + BT_GAP_MS_TO_CONN_TIMEOUT(4000)) + +/** + * @brief Fast advertising parameters for broadcast audio + * + * This is suitable for both 7.5 ms and 10 ms SDU intervals, but prioritizes + * lower time to synchronize over coexistence with ISO and power consumption. + */ +#define BT_BAP_ADV_PARAM_BROADCAST_FAST \ +BT_LE_ADV_PARAM(BT_LE_ADV_OPT_EXT_ADV, BT_GAP_MS_TO_ADV_INTERVAL(60), \ + BT_GAP_MS_TO_ADV_INTERVAL(60), NULL) + +/** + * @brief Slow advertising parameters for broadcast audio + * + * This is suitable for both 7.5 ms and 10 ms SDU intervals, but prioritizes + * coexistence with ISO and power consumption over lower time to synchronize. + */ +#define BT_BAP_ADV_PARAM_BROADCAST_SLOW \ +BT_LE_ADV_PARAM(BT_LE_ADV_OPT_EXT_ADV, BT_GAP_MS_TO_ADV_INTERVAL(150), \ + BT_GAP_MS_TO_ADV_INTERVAL(150), NULL) + +/** + * @brief Fast advertising parameters for broadcast audio + * + * This is suitable for both 7.5 ms and 10 ms SDU intervals, but prioritizes + * lower time to synchronize over coexistence with ISO and power consumption. + */ +#define BT_BAP_PER_ADV_PARAM_BROADCAST_FAST \ +BT_LE_PER_ADV_PARAM(BT_GAP_MS_TO_PER_ADV_INTERVAL(60), BT_GAP_MS_TO_PER_ADV_INTERVAL(60), \ + BT_LE_PER_ADV_OPT_NONE) + +/** + * @brief Slow advertising parameters for broadcast audio + * + * This is suitable for both 7.5 ms and 10 ms SDU intervals, but prioritizes + * coexistence with ISO and power consumption over lower time to synchronize. + */ +#define BT_BAP_PER_ADV_PARAM_BROADCAST_SLOW \ +BT_LE_PER_ADV_PARAM(BT_GAP_MS_TO_PER_ADV_INTERVAL(150), BT_GAP_MS_TO_PER_ADV_INTERVAL(150), \ + BT_LE_PER_ADV_OPT_NONE) + +/** + * @brief Check if a BAP BASS BIS_Sync bitfield is valid + * + * Valid options are either a bitmask of valid BIS indices, including none + * (0x00000000) or @ref BT_BAP_BIS_SYNC_NO_PREF (0xFFFFFFFF). + * + * @param _bis_bitfield BIS_Sync bitfield (uint32) + */ +#define BT_BAP_BASS_VALID_BIT_BITFIELD(_bis_bitfield) \ +((_bis_bitfield) == 0U || (_bis_bitfield) == BT_BAP_BIS_SYNC_NO_PREF || \ + BT_ISO_VALID_BIS_BITFIELD(_bis_bitfield)) + +/** + * @brief Helper to declare elements of bt_bap_qos_cfg + * + * @param _interval SDU interval (usec) + * @param _framing Framing + * @param _phy Target PHY + * @param _sdu Maximum SDU Size + * @param _rtn Retransmission number + * @param _latency Maximum Transport Latency (msec) + * @param _pd Presentation Delay (usec) + */ +#define BT_BAP_QOS_CFG(_interval, _framing, _phy, _sdu, _rtn, _latency, _pd) \ +((struct bt_bap_qos_cfg){ \ + .interval = _interval, \ + .framing = _framing, \ + .phy = _phy, \ + .sdu = _sdu, \ + .rtn = _rtn, \ + IF_ENABLED(UTIL_OR(IS_ENABLED(CONFIG_BT_BAP_BROADCAST_SOURCE), \ + IS_ENABLED(CONFIG_BT_BAP_UNICAST)), \ + (.latency = _latency, )) \ + .pd = _pd, \ +}) + +/** @brief QoS Framing */ + +enum bt_bap_qos_cfg_framing +{ + /** Packets may be framed or unframed */ + + BT_BAP_QOS_CFG_FRAMING_UNFRAMED = 0x00, + + /** Packets are always framed */ + + BT_BAP_QOS_CFG_FRAMING_FRAMED = 0x01, +}; + +/** @brief QoS Preferred PHY */ + +enum +{ + /** LE 1M PHY */ + + BT_BAP_QOS_CFG_1M = BIT(0U), + + /** LE 2M PHY */ + + BT_BAP_QOS_CFG_2M = BIT(1U), + + /** LE Coded PHY */ + + BT_BAP_QOS_CFG_CODED = BIT(2U), +}; + +/** + * @brief Helper to declare Input Unframed bt_bap_qos_cfg + * + * @param _interval SDU interval (usec) + * @param _sdu Maximum SDU Size + * @param _rtn Retransmission number + * @param _latency Maximum Transport Latency (msec) + * @param _pd Presentation Delay (usec) + */ +#define BT_BAP_QOS_CFG_UNFRAMED(_interval, _sdu, _rtn, _latency, _pd) \ +BT_BAP_QOS_CFG(_interval, BT_BAP_QOS_CFG_FRAMING_UNFRAMED, BT_BAP_QOS_CFG_2M, _sdu, _rtn, \ + _latency, _pd) + +/** + * @brief Helper to declare Input Framed bt_bap_qos_cfg + * + * @param _interval SDU interval (usec) + * @param _sdu Maximum SDU Size + * @param _rtn Retransmission number + * @param _latency Maximum Transport Latency (msec) + * @param _pd Presentation Delay (usec) + */ +#define BT_BAP_QOS_CFG_FRAMED(_interval, _sdu, _rtn, _latency, _pd) \ +BT_BAP_QOS_CFG(_interval, BT_BAP_QOS_CFG_FRAMING_FRAMED, BT_BAP_QOS_CFG_2M, _sdu, _rtn, \ + _latency, _pd) + +/** @brief QoS configuration structure. */ + +struct bt_bap_qos_cfg +{ + /* @brief Presentation Delay in microseconds + * + * This value can be changed up and until bt_bap_stream_qos() has been + * called. Once a stream has been QoS configured, modifying this field does + * not modify the value. It is however possible to modify this field and + * call bt_bap_stream_qos() again to update the value, assuming that the + * stream is in the correct state. + * + * Value range 0 to @ref BT_AUDIO_PD_MAX. + */ + + uint32_t pd; + + /* @brief Connected Isochronous Group (CIG) parameters + * + * The fields in this struct affect the value sent to the controller via + * HCI when creating the CIG. Once the group has been created with + * bt_bap_unicast_group_create(), modifying these fields will not affect + * the group. + */ + + struct + { + /** QoS Framing */ + + enum bt_bap_qos_cfg_framing framing; + + /** + * @brief PHY + * + * Allowed values are @ref BT_BAP_QOS_CFG_1M, @ref BT_BAP_QOS_CFG_2M + * and + * @ref BT_BAP_QOS_CFG_CODED. + */ + + uint8_t phy; + + /** + * @brief Retransmission Number + * + * This a recommendation to the controller, and the actual + * retransmission number may be different than this. + */ + + uint8_t rtn; + + /** + * @brief Maximum SDU size + * + * Value range @ref BT_ISO_MIN_SDU to @ref BT_ISO_MAX_SDU. + */ + + uint16_t sdu; + +#if defined(CONFIG_BT_BAP_BROADCAST_SOURCE) || defined(CONFIG_BT_BAP_UNICAST) || \ + defined(__DOXYGEN__) + /** + * @brief Maximum Transport Latency + * + * Not used for the @kconfig{CONFIG_BT_BAP_BROADCAST_SINK} role. + */ + + uint16_t latency; +#endif /* CONFIG_BT_BAP_BROADCAST_SOURCE || CONFIG_BT_BAP_UNICAST */ + + /** + * @brief SDU Interval + * + * Value range @ref BT_ISO_SDU_INTERVAL_MIN to @ref + * BT_ISO_SDU_INTERVAL_MAX + */ + + uint32_t interval; + +#if defined(CONFIG_BT_ISO_TEST_PARAMS) || defined(__DOXYGEN__) + /** + * @brief Maximum PDU size + * + * Maximum size, in octets, of the payload from link layer to link + * layer. + * + * Value range @ref BT_ISO_CONNECTED_PDU_MIN to @ref BT_ISO_PDU_MAX + * for connected ISO. + * + * Value range @ref BT_ISO_BROADCAST_PDU_MIN to @ref BT_ISO_PDU_MAX + * for broadcast ISO. + */ + + uint16_t max_pdu; + + /** + * @brief Burst number + * + * Value range @ref BT_ISO_BN_MIN to @ref BT_ISO_BN_MAX. + */ + + uint8_t burst_number; + + /** + * @brief Number of subevents + * + * Maximum number of subevents in each CIS or BIS event. + * + * Value range @ref BT_ISO_NSE_MIN to @ref BT_ISO_NSE_MAX. + */ + + uint8_t num_subevents; +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ + }; +}; + +/** Periodic advertising state reported by the Scan Delegator */ + +enum bt_bap_pa_state +{ + /** The periodic advertising has not been synchronized */ + + BT_BAP_PA_STATE_NOT_SYNCED = 0x00, + + /** Waiting for SyncInfo from Broadcast Assistant */ + + BT_BAP_PA_STATE_INFO_REQ = 0x01, + + /** Synchronized to periodic advertising */ + + BT_BAP_PA_STATE_SYNCED = 0x02, + + /** Failed to synchronized to periodic advertising */ + + BT_BAP_PA_STATE_FAILED = 0x03, + + /** No periodic advertising sync transfer receiver from Broadcast Assistant + */ + + BT_BAP_PA_STATE_NO_PAST = 0x04, +}; + +/** Broadcast Isochronous Group encryption state reported by the Scan + * Delegator + */ + +enum bt_bap_big_enc_state +{ + /** The Broadcast Isochronous Group not encrypted */ + + BT_BAP_BIG_ENC_STATE_NO_ENC = 0x00, + + /** The Broadcast Isochronous Group broadcast code requested */ + + BT_BAP_BIG_ENC_STATE_BCODE_REQ = 0x01, + + /** The Broadcast Isochronous Group decrypted */ + + BT_BAP_BIG_ENC_STATE_DEC = 0x02, + + /** The Broadcast Isochronous Group bad broadcast code */ + + BT_BAP_BIG_ENC_STATE_BAD_CODE = 0x03, +}; + +/** Broadcast Audio Scan Service (BASS) specific ATT error codes */ + +enum bt_bap_bass_att_err +{ + /** Opcode not supported */ + + BT_BAP_BASS_ERR_OPCODE_NOT_SUPPORTED = 0x80, + + /** Invalid source ID supplied */ + + BT_BAP_BASS_ERR_INVALID_SRC_ID = 0x81, +}; + +/** Value indicating that the periodic advertising interval is unknown */ +#define BT_BAP_PA_INTERVAL_UNKNOWN 0xFFFFU + +/** + * @brief Broadcast Assistant no BIS sync preference + * + * Value indicating that the Broadcast Assistant has no preference to which + * BIS the Scan Delegator syncs to + */ +#define BT_BAP_BIS_SYNC_NO_PREF 0xFFFFFFFFU +/** BIS sync value indicating that the BIG sync has failed for any reason */ +#define BT_BAP_BIS_SYNC_FAILED 0xFFFFFFFFU + +/** Endpoint states */ + +enum bt_bap_ep_state +{ + /** Audio Stream Endpoint Idle state */ + + BT_BAP_EP_STATE_IDLE = 0x00, + + /** Audio Stream Endpoint Codec Configured state */ + + BT_BAP_EP_STATE_CODEC_CONFIGURED = 0x01, + + /** Audio Stream Endpoint QoS Configured state */ + + BT_BAP_EP_STATE_QOS_CONFIGURED = 0x02, + + /** Audio Stream Endpoint Enabling state */ + + BT_BAP_EP_STATE_ENABLING = 0x03, + + /** Audio Stream Endpoint Streaming state */ + + BT_BAP_EP_STATE_STREAMING = 0x04, + + /** Audio Stream Endpoint Disabling state */ + + BT_BAP_EP_STATE_DISABLING = 0x05, + + /** Audio Stream Endpoint Releasing state */ + + BT_BAP_EP_STATE_RELEASING = 0x06, +}; + +/** + * @struct bt_bap_broadcast_source + * @brief Abstract Audio Broadcast Source structure. + */ + +struct bt_bap_broadcast_source; + +/** + * @struct bt_bap_broadcast_sink + * @brief Abstract Audio Broadcast Sink structure. + */ + +struct bt_bap_broadcast_sink; + +/** + * @struct bt_bap_unicast_group + * @brief Abstract Audio Unicast Group structure. + */ + +struct bt_bap_unicast_group; + +/** + * @struct bt_bap_ep + * @brief Abstract Audio Endpoint structure. + */ + +struct bt_bap_ep; + +/** Struct to hold subgroup specific information for the receive state */ + +struct bt_bap_bass_subgroup +{ + /** BIS synced bitfield */ + + uint32_t bis_sync; + + /** Length of the metadata */ + + uint8_t metadata_len; + +#if defined(CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE) || defined(__DOXYGEN__) + /** The metadata */ + + uint8_t metadata[CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE]; +#endif /* CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE */ +}; + +/** Represents the Broadcast Audio Scan Service receive state */ + +struct bt_bap_scan_delegator_recv_state +{ + /** The source ID */ + + uint8_t src_id; + + /** The Bluetooth address */ + + bt_addr_le_t addr; + + /** The advertising set ID */ + + uint8_t adv_sid; + + /** The periodic adverting sync state */ + + enum bt_bap_pa_state pa_sync_state; + + /** The broadcast isochronous group encryption state */ + + enum bt_bap_big_enc_state encrypt_state; + + /** The 24-bit broadcast ID */ + + uint32_t broadcast_id; + + /* @brief The bad broadcast code + * + * Only valid if encrypt_state is @ref BT_BAP_BIG_ENC_STATE_BCODE_REQ + */ + + uint8_t bad_code[BT_ISO_BROADCAST_CODE_SIZE]; + + /** Number of subgroups */ + + uint8_t num_subgroups; + + /** Subgroup specific information + * + * If the @ref bt_bap_bass_subgroup.bis_sync value is @ref + * BT_BAP_BIS_SYNC_FAILED then it indicates that the BIG sync failed. + */ + + struct bt_bap_bass_subgroup subgroups[BT_BAP_BASS_MAX_SUBGROUPS]; +}; + +/** + * @brief Struct to hold the Basic Audio Profile Scan Delegator callbacks + * + * These can be registered for usage with bt_bap_scan_delegator_register(). + */ + +struct bt_bap_scan_delegator_cb +{ + /* @brief Receive state updated + * + * @param conn Pointer to the connection to a remote device if the + * change was caused by it, otherwise NULL. + * @param recv_state Pointer to the receive state that was updated. + */ + + void (*recv_state_updated)(struct bt_conn *conn, + const struct bt_bap_scan_delegator_recv_state *recv_state); + + /* @brief Periodic advertising sync request + * + * Request from peer device to synchronize with the periodic advertiser + * denoted by the @p recv_state. To notify the Broadcast Assistant about + * any pending sync + * + * @param conn Pointer to the connection requesting the periodic + * advertising sync. + * @param recv_state Pointer to the receive state that is being requested + * for periodic advertising sync. + * @param past_avail True if periodic advertising sync transfer is + * available. + * @param pa_interval The periodic advertising interval. + * + * @return 0 in case of accept, or other value to reject. + */ + + int (*pa_sync_req)(struct bt_conn *conn, + const struct bt_bap_scan_delegator_recv_state *recv_state, + bool past_avail, + uint16_t pa_interval); + + /* @brief Periodic advertising sync termination request + * + * Request from peer device to terminate the periodic advertiser sync + * denoted by the @p recv_state. + * + * @param conn Pointer to the connection requesting the periodic + * advertising sync termination. + * @param recv_state Pointer to the receive state that is being requested + * for periodic advertising sync. + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*pa_sync_term_req)(struct bt_conn *conn, + const struct bt_bap_scan_delegator_recv_state *recv_state); + + /* @brief Broadcast code received + * + * Broadcast code received from a broadcast assistant + * + * @param conn Pointer to the connection providing the broadcast + * code. + * @param recv_state Pointer to the receive state the broadcast code is + * being provided for. + * @param broadcast_code The 16-octet broadcast code + */ + + void (*broadcast_code)(struct bt_conn *conn, + const struct bt_bap_scan_delegator_recv_state *recv_state, + const uint8_t broadcast_code[BT_ISO_BROADCAST_CODE_SIZE]); + /* @brief Broadcast Isochronous Stream synchronize request + * + * Request from Broadcast Assistant device to modify the Broadcast + * Isochronous Stream states. The request shall be fulfilled with + * accordance to the @p bis_sync_req within reasonable time. The Broadcast + * Assistant may also request fewer, or none, indexes to be synchronized. + * This callback is always called when any of the @p bis_sync_req values + * are @ref BT_BAP_BIS_SYNC_NO_PREF. + * + * @param[in] conn Pointer to the connection of the Broadcast + * Assistant requesting the sync. + * @param[in] recv_state Pointer to the receive state that is being + * requested for the sync. + * @param[in] bis_sync_req Array of bitfields of which BIS indexes that + * is requested to sync for each subgroup by the + * Broadcast Assistant. A value of 0 indicates a + * request to terminate the BIG sync. + * + * @return 0 in case of accept, or other value to reject. + */ + + int (*bis_sync_req)(struct bt_conn *conn, + const struct + bt_bap_scan_delegator_recv_state *recv_state, + const uint32_t bis_sync_req[BT_BAP_BASS_MAX_SUBGROUPS]); + /* @brief Broadcast Assistant scanning state callback + * + * Callback triggered when a Broadcast Assistant notifies the Scan + * Delegator about the assistants scanning state. + * + * @param conn Pointer to the connection that initiated the scan. + * @param is_scanning true if scanning started, false if scanning stopped. + */ + + void (*scanning_state)(struct bt_conn *conn, bool is_scanning); + /* @brief Add Source operation callback + * + * These callbacks notify the application when a request comes in to add a + * source. The application can return 0 to accept or any other value to + * reject the request. + * + * @param conn Pointer to the connection that initiated the request, + * or NULL if locally triggered. + * @param recv_state Pointer to the requested receive state to be added. + * + * @return 0 in case of accept, or other value to reject. + */ + + int (*add_source)(struct bt_conn *conn, + const struct bt_bap_scan_delegator_recv_state *recv_state); + + /* @brief Modify Source operation callback + * + * These callbacks notify the application when a request comes in to modify + * a source. The application can return 0 to accept or any other value to + * reject the request. + * + * @param conn Pointer to the connection that initiated the request, + * or NULL if locally triggered. + * @param recv_state Pointer to the requested receive state to be modified. + * + * @return 0 in case of accept, or other value to reject. + */ + + int (*modify_source)(struct bt_conn *conn, + const struct bt_bap_scan_delegator_recv_state *recv_state); + + /* @brief Remove Source operation callback + * + * These callbacks notify the application when a request comes in to remove + * a source. The application can return 0 to accept or any other value to + * reject the request. + * + * @param conn Pointer to the connection that initiated the request, or + * NULL if locally triggered. + * @param src_id The Source ID that is requested to be removed. + * + * @return 0 in case of accept, or other value to reject. + */ + + int (*remove_source)(struct bt_conn *conn, uint8_t src_id); +}; + +/** Structure holding information of audio stream endpoint */ + +struct bt_bap_ep_info +{ + /** The ID of the endpoint */ + + uint8_t id; + + /** The state of the endpoint */ + + enum bt_bap_ep_state state; + + /** Capabilities type */ + + enum bt_audio_dir dir; + + /** The isochronous channel associated with the endpoint. */ + + struct bt_iso_chan *iso_chan; + + /** @brief True if the stream associated with the endpoint is able to send + * data + */ + + bool can_send; + + /** @brief True if the stream associated with the endpoint is able to + * receive data + */ + + bool can_recv; + + /** Pointer to paired endpoint if the endpoint is part of a bidirectional + * CIS, otherwise NULL + */ + + struct bt_bap_ep *paired_ep; + + /** Pointer to the preferred QoS settings associated with the endpoint */ + + const struct bt_bap_qos_cfg_pref *qos_pref; +}; + +/** + * @brief Return structure holding information of audio stream endpoint + * + * @param ep The audio stream endpoint object. + * @param info The structure object to be filled with the info. + * + * @retval 0 in case of success + * @retval -EINVAL if @p ep or @p info are NULL + */ + +int bt_bap_ep_get_info(const struct bt_bap_ep *ep, + struct bt_bap_ep_info *info); + +/** + * @brief Get the pointer to the ACL connection of an endpoint + * + * The caller gets a new reference to the connection object, if not NULL, + * which must be released with bt_conn_unref() once done using the object. + * + * @param ep The endpoint to get the ACL connection of + * + * @return The ACL connection pointer, or NULL if: + * - @p ep is NULL + * - @p ep is a broadcast endpoint + * - @p ep is a Unicast Server endpoint not yet configured by a + * remote client + * - @p ep is a Unicast Client endpoint not yet discovered on a + * remote server + */ + +struct bt_conn *bt_bap_ep_get_conn(const struct bt_bap_ep *ep); + +/** + * @brief Basic Audio Profile stream structure. + * + * Streams represents a stream configuration of a Remote Endpoint and a Local + * Capability. + * + * @note Streams are unidirectional but can be paired with other streams to + * use a bidirectional connected isochronous stream. + */ + +struct bt_bap_stream +{ + /** Connection reference */ + + struct bt_conn *conn; + + /** Endpoint reference */ + + struct bt_bap_ep *ep; + + /* @brief Codec Configuration + * + * Only valid if the endpoint for this stream is non-NULL. + */ + + const struct bt_audio_codec_cfg *codec_cfg; + + /** QoS Configuration + * + * Only valid if the endpoint for this stream is non-NULL and the state is + * @ref BT_BAP_EP_STATE_QOS_CONFIGURED or higher. + */ + + const struct bt_bap_qos_cfg *qos; + + /** Audio stream operations */ + + struct bt_bap_stream_ops *ops; + + /** Stream user data */ + + void *user_data; + + /** ISO channel reference + * + * This will become valid once the stream is added to a group + * (bt_bap_unicast_group, bt_bap_broadcast_source or + * bt_bap_broadcast_sink). + */ + + struct bt_iso_chan *iso; + + /** Unicast or Broadcast group - Used internally */ + + void *group; + +#if defined(CONFIG_BT_BAP_DEBUG_STREAM_SEQ_NUM) || defined(__DOXYGEN__) + /** Previously sent sequence number */ + + uint16_t _prev_seq_num; +#endif /* CONFIG_BT_BAP_DEBUG_STREAM_SEQ_NUM */ + + /** @cond INTERNAL_HIDDEN */ + + /** Internally used list node */ + + struct list_node _node; + + /** @endcond */ +}; + +/** @brief Stream operation. */ + +struct bt_bap_stream_ops +{ +#if defined(CONFIG_BT_BAP_UNICAST) || defined(__DOXYGEN__) + /* @brief Stream codec configured callback + * + * Codec configured callback is called whenever an Audio Stream has been + * configured with a codec configuration. + * + * @param stream Stream object that has been configured. + * @param pref Remote QoS preferences. + */ + + void (*codec_configured)(struct bt_bap_stream *stream, + const struct bt_bap_qos_cfg_pref *pref); + + /* @brief Stream QoS configured callback + * + * QoS configured callback is called whenever an Audio Stream Quality of + * Service has been set or updated. + * + * @param stream Stream object that had its QoS updated. + */ + + void (*qos_configured)(struct bt_bap_stream *stream); + + /* @brief Stream enabled callback + * + * Enabled callback is called whenever an Audio Stream has been enabled. + * + * @param stream Stream object that has been enabled. + */ + + void (*enabled)(struct bt_bap_stream *stream); + + /* @brief Stream metadata updated callback + * + * Metadata Updated callback is called whenever an Audio Stream's metadata + * has been updated. + * + * @param stream Stream object that had its metadata updated. + */ + + void (*metadata_updated)(struct bt_bap_stream *stream); + + /* @brief Stream disabled callback + * + * Disabled callback is called whenever an Audio Stream has been disabled. + * + * @param stream Stream object that has been disabled. + */ + + void (*disabled)(struct bt_bap_stream *stream); + + /* @brief Stream released callback + * + * Released callback is called whenever a Audio Stream has been released + * and can be deallocated. + * + * @param stream Stream object that has been released. + */ + + void (*released)(struct bt_bap_stream *stream); +#endif /* CONFIG_BT_BAP_UNICAST */ + + /* @brief Stream started callback + * + * Started callback is called whenever an Audio Stream has been started and + * will be usable for streaming. + * + * @param stream Stream object that has been started. + */ + + void (*started)(struct bt_bap_stream *stream); + + /* @brief Stream stopped callback + * + * Stopped callback is called whenever an Audio Stream has been stopped. + * + * @param stream Stream object that has been stopped. + * @param reason BT_HCI_ERR_* reason for the disconnection. + */ + + void (*stopped)(struct bt_bap_stream *stream, uint8_t reason); + +#if defined(CONFIG_BT_AUDIO_RX) || defined(__DOXYGEN__) + /* @brief Stream audio HCI receive callback. + * + * This callback is only used if the ISO data path is HCI. + * + * @param stream Stream object. + * @param info Pointer to the metadata for the buffer. The lifetime of + * the pointer is linked to the lifetime of the net_buf. + * Metadata such as sequence number and timestamp can be + * provided by the bluetooth controller. + * @param buf Buffer containing incoming audio data. + */ + + void (*recv)(struct bt_bap_stream *stream, + const struct bt_iso_recv_info *info, + struct net_buf *buf); +#endif /* CONFIG_BT_AUDIO_RX */ + +#if defined(CONFIG_BT_AUDIO_TX) || defined(__DOXYGEN__) + /* @brief Stream audio HCI sent callback + * + * This callback will be called once the controller marks the SDU as + * completed. When the controller does so is implementation dependent. It + * could be after the SDU is enqueued for transmission, or after it is sent + * on air or flushed. + * + * This callback is only used if the ISO data path is HCI. + * + * @param stream Stream object. + */ + + void (*sent)(struct bt_bap_stream *stream); +#endif /* CONFIG_BT_AUDIO_TX */ + + /* @brief Isochronous channel connected callback + * + * If this callback is provided it will be called whenever the isochronous + * channel for the stream has been connected. This does not mean that the + * stream is ready to be used, which is indicated by the @ref + * bt_bap_stream_ops.started callback. + * + * If the stream shares an isochronous channel with another stream, then + * this callback may still be called, without the stream going into the + * started state. + * + * @param stream Stream object. + */ + + void (*connected)(struct bt_bap_stream *stream); + + /* @brief Isochronous channel disconnected callback + * + * If this callback is provided it will be called whenever the isochronous + * channel is disconnected, including when a connection gets rejected. + * + * If the stream shares an isochronous channel with another stream, then + * this callback may not be called, even if the stream is leaving the + * streaming state. + * + * @param stream Stream object. + * @param reason BT_HCI_ERR_* reason for the disconnection. + */ + + void (*disconnected)(struct bt_bap_stream *stream, uint8_t reason); +}; + +/** Structure for registering Unicast Server */ + +struct bt_bap_unicast_server_register_param +{ + /* @brief Sink Count to register. + * + * Should be in range 0 to @kconfig{CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT} + */ + + uint8_t snk_cnt; + + /** @brief Source Count to register. + * + * Should be in range 0 to @kconfig{CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT} + */ + + uint8_t src_cnt; +}; + +/** + * @brief Register Audio callbacks for a stream. + * + * Register Audio callbacks for a stream. + * + * @param stream Stream object. + * @param ops Stream operations structure. + */ + +void bt_bap_stream_cb_register(struct bt_bap_stream *stream, + struct bt_bap_stream_ops *ops); + +/** + * @brief Configure Audio Stream + * + * This procedure is used by a client to configure a new stream using the + * remote endpoint, local capability and codec configuration. + * + * @param conn Connection object + * @param stream Stream object being configured + * @param ep Remote Audio Endpoint being configured + * @param codec_cfg Codec configuration + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_bap_stream_config( + struct bt_conn *conn, struct bt_bap_stream *stream, struct bt_bap_ep *ep, + const struct bt_audio_codec_cfg *codec_cfg); + +/** + * @brief Reconfigure Audio Stream + * + * This procedure is used by a unicast client or unicast server to + * reconfigure a stream to use a different local codec configuration. + * + * This can only be done for unicast streams. + * + * @param stream Stream object being reconfigured + * @param codec_cfg Codec configuration + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_bap_stream_reconfig(struct bt_bap_stream *stream, + const struct bt_audio_codec_cfg *codec_cfg); + +/** + * @brief Configure Audio Stream QoS + * + * This procedure is used by a client to configure the Quality of Service of + * streams in a unicast group. All streams in the group for the specified @p + * conn will have the Quality of Service configured. This shall only be used + * to configure unicast streams. + * + * @param conn Connection object + * @param group Unicast group object + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_bap_stream_qos(struct bt_conn *conn, + struct bt_bap_unicast_group *group); + +/** + * @brief Enable Audio Stream + * + * This procedure is used by a client to enable a stream. + * + * This shall only be called for unicast streams, as broadcast streams will + * always be enabled once created. + * + * @param stream Stream object + * @param meta Metadata + * @param meta_len Metadata length + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_bap_stream_enable(struct bt_bap_stream *stream, + const uint8_t meta[], size_t meta_len); + +/** + * @brief Change Audio Stream Metadata + * + * This procedure is used by a unicast client or unicast server to change the + * metadata of a stream. + * + * @param stream Stream object + * @param meta Metadata + * @param meta_len Metadata length + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_bap_stream_metadata(struct bt_bap_stream *stream, + const uint8_t meta[], size_t meta_len); + +/** + * @brief Disable Audio Stream + * + * This procedure is used by a unicast client or unicast server to disable a + * stream. + * + * This shall only be called for unicast streams, as broadcast streams will + * always be enabled once created. + * + * @param stream Stream object + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_bap_stream_disable(struct bt_bap_stream *stream); + +/** + * @brief Connect unicast audio stream + * + * This procedure is used by a unicast client to connect the connected + * isochronous stream (CIS) associated with the audio stream. If two audio + * streams share a CIS, then this only needs to be done once for those + * streams. This can only be done for streams in the QoS configured or + * enabled states. + * + * The bt_bap_stream_ops.connected() callback will be called on the streams + * once this has finished. + * + * This shall only be called for unicast streams, and only as the unicast + * client (@kconfig{CONFIG_BT_BAP_UNICAST_CLIENT}). + * + * @param stream Stream object + * + * @retval 0 in case of success + * @retval -EINVAL if the stream, endpoint, ISO channel or connection is NULL + * @retval -EBADMSG if the stream or ISO channel is in an invalid state for + * connection + * @retval -EOPNOTSUPP if the role of the stream is not @ref + * BT_CONN_ROLE_CENTRAL + * @retval -EALREADY if the ISO channel is already connecting or connected + * @retval -EBUSY if another ISO channel is connecting + * @retval -ENOEXEC if otherwise rejected by the ISO layer + */ + +int bt_bap_stream_connect(struct bt_bap_stream *stream); + +/** + * @brief Start Audio Stream + * + * This procedure is used by a unicast client or unicast server to make a + * stream start streaming. + * + * For the unicast client, this will send the receiver start ready command to + * the unicast server for + * @ref BT_AUDIO_DIR_SOURCE ASEs. The CIS is required to be connected first + * by bt_bap_stream_connect() before the command can be sent. + * + * For the unicast server, this will execute the receiver start ready command + * on the unicast server for @ref BT_AUDIO_DIR_SINK ASEs. If the CIS is not + * connected yet, the stream will go into the streaming state as soon as the + * CIS is connected. + * + * This shall only be called for unicast streams. + * + * Broadcast sinks will always be started once synchronized, and broadcast + * source streams shall be started with bt_bap_broadcast_source_start(). + * + * @param stream Stream object + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_bap_stream_start(struct bt_bap_stream *stream); + +/** + * @brief Stop Audio Stream + * + * This procedure is used by a client to make a stream stop streaming. + * + * This shall only be called for unicast streams. Broadcast sinks cannot be + * stopped. Broadcast sources shall be stopped with + * bt_bap_broadcast_source_stop(). + * + * @param stream Stream object + * + * @retval 0 Success + * @retval -EINVAL The @p stream does not have an endpoint or a connection, + * of the stream's connection's role is not @p BT_CONN_ROLE_CENTRAL + * @retval -EBADMSG The state of the @p stream endpoint is not @ref + * BT_BAP_EP_STATE_DISABLING + * @retval -EALREADY The CIS state of the @p is not in a connected state, and + * thus is already stopping + * @retval -EBUSY The @p stream is busy with another operation + * @retval -ENOTCONN The @p stream ACL connection is not connected + * @retval -ENOMEM No memory to send request + * @retval -ENOEXEC The request was rejected by GATT + * @return 0 in case of success or negative value in case of error. + */ + +int bt_bap_stream_stop(struct bt_bap_stream *stream); + +/** + * @brief Release Audio Stream + * + * This procedure is used by a unicast client or unicast server to release a + * unicast stream. + * + * Broadcast sink streams cannot be released, but can be deleted by + * bt_bap_broadcast_sink_delete(). Broadcast source streams cannot be + * released, but can be deleted by bt_bap_broadcast_source_delete(). + * + * If the stream's endpoint is non-NULL and its state is @ref + * BT_BAP_EP_STATE_IDLE, the function will reset the stream and endpoint + * locally if the return value is 0, but will not send the release command. + * + * @param stream Stream object + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_bap_stream_release(struct bt_bap_stream *stream); + +/** + * @brief Send data to Audio stream without timestamp + * + * Send data from buffer to the stream. + * + * @note Support for sending must be supported, determined by + * @kconfig{CONFIG_BT_AUDIO_TX}. + * + * @param stream Stream object. + * @param buf Buffer containing data to be sent. + * @param seq_num Packet Sequence number. This value shall be incremented + * for each call to this function and at least once per SDU + * interval for a specific channel. + * + * @return Bytes sent in case of success or negative value in case of error. + */ + +int bt_bap_stream_send(struct bt_bap_stream *stream, + struct net_buf *buf, uint16_t seq_num); + +/** + * @brief Send data to Audio stream with timestamp + * + * Send data from buffer to the stream. + * + * @note Support for sending must be supported, determined by + * @kconfig{CONFIG_BT_AUDIO_TX}. + * + * @param stream Stream object. + * @param buf Buffer containing data to be sent. + * @param seq_num Packet Sequence number. This value shall be incremented + * for each call to this function and at least once per SDU + * interval for a specific channel. + * @param ts Timestamp of the SDU in microseconds (us). This value can + * be used to transmit multiple SDUs in the same SDU interval + * in a CIG or BIG. + * + * @return Bytes sent in case of success or negative value in case of error. + */ + +int bt_bap_stream_send_ts( + struct bt_bap_stream *stream, struct net_buf *buf, uint16_t seq_num, + uint32_t ts); + +/** + * @brief Get ISO transmission timing info for a Basic Audio Profile stream + * + * Reads timing information for transmitted ISO packet on an ISO channel. The + * HCI_LE_Read_ISO_TX_Sync HCI command is used to retrieve this information + * from the controller. + * + * @note An SDU must have already been successfully transmitted on the ISO + * channel for this function to return successfully. Support for sending must + * be supported, determined by @kconfig{CONFIG_BT_AUDIO_TX}. + * + * @param[in] stream Stream object. + * @param[out] info Transmit info object. + * + * @retval 0 on success + * @retval -EINVAL if the stream is invalid, if the stream is not configured + * for sending or if it is not connected with a isochronous stream + * @retval Any return value from bt_iso_chan_get_tx_sync() + */ + +int bt_bap_stream_get_tx_sync(struct bt_bap_stream *stream, + struct bt_iso_tx_info *info); + +/** + * @defgroup bt_bap_unicast_server BAP Unicast Server APIs + * @ingroup bt_bap + * @{ + */ + +/** Unicast Server callback structure */ + +struct bt_bap_unicast_server_cb +{ + /* @brief Endpoint config request callback + * + * Config callback is called whenever an endpoint is requested to be + * configured + * + * @param[in] conn Connection object. + * @param[in] ep Local Audio Endpoint being configured. + * @param[in] dir Direction of the endpoint. + * @param[in] codec_cfg Codec configuration. + * @param[out] stream Pointer to stream that will be configured for the + * endpoint. + * @param[out] pref Pointer to a QoS preference object that shall be + * populated with values. Invalid values will reject + * the codec configuration request. + * @param[out] rsp Object for the ASE operation response. Only used + * if the return value is non-zero. + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*config)(struct bt_conn *conn, + const struct bt_bap_ep *ep, + enum bt_audio_dir dir, + const struct bt_audio_codec_cfg *codec_cfg, + struct bt_bap_stream **stream, + struct bt_bap_qos_cfg_pref *const pref, + struct bt_bap_ascs_rsp *rsp); + + /* @brief Stream reconfig request callback + * + * Reconfig callback is called whenever an Audio Stream needs to be + * reconfigured with different codec configuration. + * + * @param[in] stream Stream object being reconfigured. + * @param[in] dir Direction of the endpoint. + * @param[in] codec_cfg Codec configuration. + * @param[out] pref Pointer to a QoS preference object that shall be + * populated with values. Invalid values will reject + * the codec configuration request. + * @param[out] rsp Object for the ASE operation response. Only used + * if the return value is non-zero. + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*reconfig)(struct bt_bap_stream *stream, + enum bt_audio_dir dir, + const struct bt_audio_codec_cfg *codec_cfg, + struct bt_bap_qos_cfg_pref *const pref, + struct bt_bap_ascs_rsp *rsp); + + /* @brief Stream QoS request callback + * + * QoS callback is called whenever an Audio Stream Quality of Service needs + * to be configured. + * + * @param[in] stream Stream object being reconfigured. + * @param[in] qos Quality of Service configuration. + * @param[out] rsp Object for the ASE operation response. Only used if + * the return value is non-zero. + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*qos)(struct bt_bap_stream *stream, const struct bt_bap_qos_cfg *qos, + struct bt_bap_ascs_rsp *rsp); + + /* @brief Stream Enable request callback + * + * Enable callback is called whenever an Audio Stream is requested to be + * enabled to stream. + * + * @param[in] stream Stream object being enabled. + * @param[in] meta Metadata entries. + * @param[in] meta_len Length of metadata. + * @param[out] rsp Object for the ASE operation response. Only used + * if the return value is non-zero. + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*enable)(struct bt_bap_stream *stream, + const uint8_t meta[], + size_t meta_len, + struct bt_bap_ascs_rsp *rsp); + + /* @brief Stream Start request callback + * + * Start callback is called whenever an Audio Stream is requested to start + * streaming. + * + * @param[in] stream Stream object. + * @param[out] rsp Object for the ASE operation response. Only used if + * the return value is non-zero. + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*start)(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp); + + /* @brief Stream Metadata update request callback + * + * Metadata callback is called whenever an Audio Stream is requested to + * update its metadata. + * + * @param[in] stream Stream object. + * @param[in] meta Metadata entries. + * @param[in] meta_len Length of metadata. + * @param[out] rsp Object for the ASE operation response. Only + * used if the return value is non-zero. + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*metadata)(struct bt_bap_stream *stream, + const uint8_t meta[], + size_t meta_len, + struct bt_bap_ascs_rsp *rsp); + + /* @brief Stream Disable request callback + * + * Disable callback is called whenever an Audio Stream is requested to + * disable the stream. + * + * @param[in] stream Stream object being disabled. + * @param[out] rsp Object for the ASE operation response. Only used if + * the return value is non-zero. + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*disable)(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp); + + /* @brief Stream Stop callback + * + * Stop callback is called whenever an Audio Stream is requested to stop + * streaming. + * + * @param[in] stream Stream object. + * @param[out] rsp Object for the ASE operation response. Only used if + * the return value is non-zero. + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*stop)(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp); + + /* @brief Stream release callback + * + * Release callback is called whenever a new Audio Stream needs to be + * released and thus deallocated. + * + * @param[in] stream Stream object. + * @param[out] rsp Object for the ASE operation response. Only used if + * the return value is non-zero. + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*release)(struct bt_bap_stream *stream, struct bt_bap_ascs_rsp *rsp); +}; + +/** + * @brief Register the Unicast Server. + * + * Register the Unicast Server. Only a single Unicast Server can be + * registered at any one time. This will register ASCS in the GATT database. + * + * @param param Registration parameters for ascs. + * + * @return 0 in case of success, negative error code otherwise. + */ + +int bt_bap_unicast_server_register( + const struct bt_bap_unicast_server_register_param *param); + +/** + * @brief Unregister the Unicast Server. + * + * Unregister the Unicast Server. This will unregister ASCS in the GATT + * database. Before calling this function, any callbacks registered through + * bt_bap_unicast_server_register_cb() needs to be unregistered with + * bt_bap_unicast_server_unregister_cb(). + * + * Calling this function will issue an release operation on any ASE in a + * non-idle state. + * + * @return 0 in case of success, negative error code otherwise. + */ + +int bt_bap_unicast_server_unregister(void); + +/** + * @brief Register unicast server callbacks. + * + * Only one callback structure can be registered, and attempting to + * registering more than one will result in an error. + * + * @param cb Unicast server callback structure. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_bap_unicast_server_register_cb( + const struct bt_bap_unicast_server_cb *cb); + +/** + * @brief Unregister unicast server callbacks. + * + * May only unregister a callback structure that has previously been + * registered by bt_bap_unicast_server_register_cb(). + * + * @param cb Unicast server callback structure. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_bap_unicast_server_unregister_cb( + const struct bt_bap_unicast_server_cb *cb); + +/** + * @typedef bt_bap_ep_func_t + * @brief The callback function called for each endpoint. + * + * @param ep The structure object with endpoint info. + * @param user_data Data to pass to the function. + * + * @retval true Continue iterating. + * @retval false Stop iterating. + */ + +typedef bool (*bt_bap_ep_func_t)(struct bt_bap_ep *ep, void *user_data); + +/** + * @brief Iterate through all endpoints of the given connection. + * + * @param conn Connection object + * @param func Function to call for each endpoint. + * @param user_data Data to pass to the callback function. + * + * @retval 0 Success + * @retval -ECANCELED Iteration was stopped by the callback function before + * complete. + * @retval -EINVAL @p conn or @p func were NULL. + */ + +int bt_bap_unicast_server_foreach_ep( + struct bt_conn *conn, bt_bap_ep_func_t func, void *user_data); + +/** + * @brief Initialize and configure a new ASE. + * + * @param conn Connection object + * @param stream Configured stream object to be attached to the ASE + * @param codec_cfg Codec configuration + * @param qos_pref Audio Stream Quality of Service Preference + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_bap_unicast_server_config_ase( + struct bt_conn *conn, struct bt_bap_stream *stream, + const struct bt_audio_codec_cfg *codec_cfg, + const struct bt_bap_qos_cfg_pref *qos_pref); + +/** @} */ /* End of group bt_bap_unicast_server */ + +/** + * @defgroup bt_bap_unicast_client BAP Unicast Client APIs + * @ingroup bt_bap + * @{ + */ + +/** Parameter struct for each stream in the unicast group */ + +struct bt_bap_unicast_group_stream_param +{ + /** Pointer to a stream object. */ + + struct bt_bap_stream *stream; + + /** The QoS settings for the stream object. */ + + const struct bt_bap_qos_cfg *qos; +}; + +/** + * @brief Parameter struct for the unicast group functions + * + * Parameter struct for the bt_bap_unicast_group_create() and + * bt_bap_unicast_group_add_streams() functions. + */ + +struct bt_bap_unicast_group_stream_pair_param +{ + /** Pointer to a receiving stream parameters. */ + + struct bt_bap_unicast_group_stream_param *rx_param; + + /** Pointer to a transmitting stream parameters. */ + + struct bt_bap_unicast_group_stream_param *tx_param; +}; + +/** Parameters for the creating unicast groups with + * bt_bap_unicast_group_create() + */ + +struct bt_bap_unicast_group_param +{ + /** The number of parameters in @p params */ + + size_t params_count; + + /** Array of stream parameters */ + + struct bt_bap_unicast_group_stream_pair_param *params; + + /* @brief Unicast Group packing mode. + * + * @ref BT_ISO_PACKING_SEQUENTIAL or @ref BT_ISO_PACKING_INTERLEAVED. + * + * @note This is a recommendation to the controller, which the controller + * may ignore. + */ + + uint8_t packing; + +#if defined(CONFIG_BT_ISO_TEST_PARAMS) || defined(__DOXYGEN__) + /* @brief Central to Peripheral flush timeout + * + * The flush timeout in multiples of ISO_Interval for each payload sent + * from the Central to Peripheral. + * + * Value range from @ref BT_ISO_FT_MIN to @ref BT_ISO_FT_MAX + */ + + uint8_t c_to_p_ft; + + /* @brief Peripheral to Central flush timeout + * + * The flush timeout in multiples of ISO_Interval for each payload sent + * from the Peripheral to Central. + * + * Value range from @ref BT_ISO_FT_MIN to @ref BT_ISO_FT_MAX. + */ + + uint8_t p_to_c_ft; + + /* @brief ISO interval + * + * Time between consecutive CIS anchor points. + * + * Value range from @ref BT_ISO_ISO_INTERVAL_MIN to @ref + * BT_ISO_ISO_INTERVAL_MAX. + */ + + uint16_t iso_interval; +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ +}; + +/** + * @brief Create unicast group. + * + * Create a new audio unicast group with one or more audio streams as a + * unicast client. All streams shall share the same framing. All streams in + * the same direction shall share the same interval and latency (see + * @ref bt_bap_qos_cfg). + * + * @param[in] param The unicast group create parameters. + * @param[out] unicast_group Pointer to the unicast group created. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_bap_unicast_group_create(struct bt_bap_unicast_group_param *param, + struct bt_bap_unicast_group **unicast_group); + +/** + * @brief Reconfigure unicast group. + * + * Reconfigure a unicast group with one or more audio streams as a unicast + * client. All streams shall share the same framing. All streams in the same + * direction shall share the same interval and latency (see + * @ref bt_bap_qos_cfg). + * All streams in @p param shall already belong to @p unicast_group. Use + * bt_bap_unicast_group_add_streams() to add additional streams. + * + * @param unicast_group Pointer to the unicast group created. + * @param param The unicast group reconfigure parameters. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_bap_unicast_group_reconfig(struct bt_bap_unicast_group *unicast_group, + const struct bt_bap_unicast_group_param *param); + +/** + * @brief Add streams to a unicast group as a unicast client + * + * This function can be used to add additional streams to a + * bt_bap_unicast_group. + * + * This can be called at any time before any of the streams in the group has + * been started (see bt_bap_stream_ops.started()). This can also be called + * after the streams have been stopped (see bt_bap_stream_ops.stopped()). + * + * Once a stream has been added to a unicast group, it cannot be removed. To + * remove a stream from a group, the group must be deleted with + * bt_bap_unicast_group_delete(), but this will require all streams in the + * group to be released first. + * + * @param unicast_group Pointer to the unicast group + * @param params Array of stream parameters with streams being added + * to the group. + * @param num_param Number of parameters in @p params. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_bap_unicast_group_add_streams( + struct bt_bap_unicast_group *unicast_group, + struct bt_bap_unicast_group_stream_pair_param params[], size_t num_param); + +/** + * @brief Delete audio unicast group. + * + * Delete a audio unicast group as a client. All streams in the group shall + * be in the idle or configured state. + * + * @param unicast_group Pointer to the unicast group to delete + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_bap_unicast_group_delete(struct bt_bap_unicast_group *unicast_group); + +/** Callback function for bt_bap_unicast_group_foreach_stream() + * + * @param stream The audio stream + * @param user_data User data + * + * @retval true Continue iterating. + * @retval false Stop iterating. + */ + +typedef bool (*bt_bap_unicast_group_foreach_stream_func_t)( + struct bt_bap_stream *stream, void *user_data); + +/** + * @brief Iterate through all streams in a unicast group + * + * @param unicast_group The unicast group + * @param func The callback function + * @param user_data User specified data that sent to the callback + * function + * + * @retval 0 Success (even if no streams exists in the group). + * @retval -ECANCELED Iteration was stopped by the callback function before + * complete. + * @retval -EINVAL @p unicast_group or @p func were NULL. + */ + +int bt_bap_unicast_group_foreach_stream( + struct bt_bap_unicast_group *unicast_group, + bt_bap_unicast_group_foreach_stream_func_t func, void *user_data); + +/** Structure holding information of a unicast group */ + +struct bt_bap_unicast_group_info +{ + /* @brief Presentation delay for sink ASEs (central to peripheral audio + * direction) + * + * Will be @ref BT_BAP_PD_UNSET if no sink streams have been added to + * group. The value does not reflect what has been configured on any remote + * ASEs, but only the local value from when the group was created or + * reconfigured. + */ + + uint32_t sink_pd; + + /* @brief Presentation delay for source ASEs (peripheral to central audio + * direction) + * + * Will be @ref BT_BAP_PD_UNSET if no source streams have been added to + * group. The value does not reflect what has been configured on any remote + * ASEs, but only the local value from when the group was created or + * reconfigured. + */ + + uint32_t source_pd; + + /* @brief Central to Peripheral SDU interval in microseconds + * + * Will be 0 if no sink streams have been added to the group. + */ + + uint32_t c_to_p_interval; + + /* @brief Peripheral to Central SDU interval in microseconds + * + * Will be 0 if no source streams have been added to the group. + */ + + uint32_t p_to_c_interval; + + /* @brief Central to Peripheral maximum transport latency in milliseconds + * + * Will be 0 if no sink streams have been added to the group. + */ + + uint16_t c_to_p_latency; + + /* @brief Peripheral to Central maximum transport latency in milliseconds + * + * Will be 0 if no source streams have been added to the group. + */ + + uint16_t p_to_c_latency; + + /** @brief The framing of the streams in the group */ + + enum bt_bap_qos_cfg_framing framing; + + /* @brief The packing of the group + * + * @ref BT_ISO_PACKING_SEQUENTIAL or @ref BT_ISO_PACKING_INTERLEAVED. + */ + + uint8_t packing; + + /* @brief Whether any stream in the group has been connected + * + * If this is true, then the group can no longer be modified with e.g. + * bt_bap_unicast_group_reconfig() or bt_bap_unicast_group_add_streams(). + */ + + bool has_been_connected; + +#if defined(CONFIG_BT_ISO_TEST_PARAMS) || defined(__DOXYGEN__) + /* @brief Central to Peripheral flush timeout in multiples of the ISO + * interval + * + * Will be 0 if no sink streams have been added to the group. + */ + + uint8_t c_to_p_ft; + + /* @brief Peripheral to Central flush timeout in multiples of the ISO + * interval + * + * Will be 0 if no source streams have been added to the group. + */ + + uint8_t p_to_c_ft; + + /** @brief ISO interval in 1.25 ms units */ + + uint16_t iso_interval; +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ +}; + +/** + * @brief Return structure holding information of unicast group + * + * @param unicast_group The unicast group object. + * @param info The structure object to be filled with the info. + * + * @retval 0 Success + * @retval -EINVAL @p unicast_group or @p info are NULL + */ + +int bt_bap_unicast_group_get_info( + const struct bt_bap_unicast_group *unicast_group, + struct bt_bap_unicast_group_info *info); + +/** Unicast Client callback structure */ + +struct bt_bap_unicast_client_cb +{ + /* @brief Remote Unicast Server Audio Locations + * + * This callback is called whenever the audio locations is read from the + * server or otherwise notified to the client. + * + * @param conn Connection to the remote unicast server. + * @param dir Direction of the location. + * @param loc The location bitfield value. + */ + + void (*location)(struct bt_conn *conn, enum bt_audio_dir dir, + enum bt_audio_location loc); + + /* @brief Remote Unicast Server Supported Contexts + * + * This callback is called whenever the supported contexts are read from + * the server or otherwise notified to the client. + * + * @param conn Connection to the remote unicast server. + * @param snk_ctx The sink context bitfield value. + * @param src_ctx The source context bitfield value. + */ + + void (*supported_contexts)(struct bt_conn *conn, + enum bt_audio_context snk_ctx, + enum bt_audio_context src_ctx); + + /* @brief Remote Unicast Server Available Contexts + * + * This callback is called whenever the available contexts are read from + * the server or otherwise notified to the client. + * + * @param conn Connection to the remote unicast server. + * @param snk_ctx The sink context bitfield value. + * @param src_ctx The source context bitfield value. + */ + + void (*available_contexts)(struct bt_conn *conn, + enum bt_audio_context snk_ctx, + enum bt_audio_context src_ctx); + + /* @brief Callback function for bt_bap_stream_config() and + * bt_bap_stream_reconfig(). + * + * Called when the codec configure operation is completed on the server. + * + * @param stream Stream the operation was performed on. + * @param rsp_code Response code. + * @param reason Reason code. + */ + + void (*config)(struct bt_bap_stream *stream, + enum bt_bap_ascs_rsp_code rsp_code, + enum bt_bap_ascs_reason reason); + + /* @brief Callback function for bt_bap_stream_qos(). + * + * Called when the QoS configure operation is completed on the server. This + * will be called for each stream in the group that was being QoS + * configured. + * + * @param stream Stream the operation was performed on. May be NULL if + * there is no stream associated with the ASE ID sent by + * the server. + * @param rsp_code Response code. + * @param reason Reason code. + */ + + void (*qos)(struct bt_bap_stream *stream, + enum bt_bap_ascs_rsp_code rsp_code, + enum bt_bap_ascs_reason reason); + + /* @brief Callback function for bt_bap_stream_enable(). + * + * Called when the enable operation is completed on the server. + * + * @param stream Stream the operation was performed on. May be NULL if + * there is no stream associated with the ASE ID sent by + * the server. + * @param rsp_code Response code. + * @param reason Reason code. + */ + + void (*enable)(struct bt_bap_stream *stream, + enum bt_bap_ascs_rsp_code rsp_code, + enum bt_bap_ascs_reason reason); + + /* @brief Callback function for bt_bap_stream_start(). + * + * Called when the start operation is completed on the server. This will + * only be called if the stream supplied to bt_bap_stream_start() is for a + * @ref BT_AUDIO_DIR_SOURCE endpoint. + * + * @param stream Stream the operation was performed on. May be NULL if + * there is no stream associated with the ASE ID sent by + * the server. + * @param rsp_code Response code. + * @param reason Reason code. + */ + + void (*start)(struct bt_bap_stream *stream, + enum bt_bap_ascs_rsp_code rsp_code, + enum bt_bap_ascs_reason reason); + + /* @brief Callback function for bt_bap_stream_stop(). + * + * Called when the stop operation is completed on the server. This will + * only be called if the stream supplied to bt_bap_stream_stop() is for a + * @ref BT_AUDIO_DIR_SOURCE endpoint. + * + * @param stream Stream the operation was performed on. May be NULL if + * there is no stream associated with the ASE ID sent by + * the server. + * @param rsp_code Response code. + * @param reason Reason code. + */ + + void (*stop)(struct bt_bap_stream *stream, + enum bt_bap_ascs_rsp_code rsp_code, + enum bt_bap_ascs_reason reason); + + /* @brief Callback function for bt_bap_stream_disable(). + * + * Called when the disable operation is completed on the server. + * + * @param stream Stream the operation was performed on. May be NULL if + * there is no stream associated with the ASE ID sent by + * the server. + * @param rsp_code Response code. + * @param reason Reason code. + */ + + void (*disable)(struct bt_bap_stream *stream, + enum bt_bap_ascs_rsp_code rsp_code, + enum bt_bap_ascs_reason reason); + + /* @brief Callback function for bt_bap_stream_metadata(). + * + * Called when the metadata operation is completed on the server. + * + * @param stream Stream the operation was performed on. May be NULL if + * there is no stream associated with the ASE ID sent by + * the server. + * @param rsp_code Response code. + * @param reason Reason code. + */ + + void (*metadata)(struct bt_bap_stream *stream, + enum bt_bap_ascs_rsp_code rsp_code, + enum bt_bap_ascs_reason reason); + + /* @brief Callback function for bt_bap_stream_release(). + * + * Called when the release operation is completed on the server. + * + * @param stream Stream the operation was performed on. May be NULL if + * there is no stream associated with the ASE ID sent by + * the server. + * @param rsp_code Response code. + * @param reason Reason code. + */ + + void (*release)(struct bt_bap_stream *stream, + enum bt_bap_ascs_rsp_code rsp_code, + enum bt_bap_ascs_reason reason); + + /* @brief Remote Published Audio Capability (PAC) record discovered + * + * Called when a PAC record has been discovered as part of the discovery + * procedure. + * + * The @p codec is only valid while in the callback, so the values must be + * stored by the receiver if future use is wanted. + * + * @param conn Connection to the remote unicast server. + * @param dir The type of remote endpoints and capabilities + * discovered. + * @param codec_cap Remote capabilities. + * + * If discovery procedure has complete both @p codec and @p ep are set to + * NULL. + */ + + void (*pac_record)(struct bt_conn *conn, enum bt_audio_dir dir, + const struct bt_audio_codec_cap *codec_cap); + + /* @brief Remote Audio Stream Endpoint (ASE) discovered + * + * Called when an ASE has been discovered as part of the discovery + * procedure. + * + * @param conn Connection to the remote unicast server. + * @param dir The type of remote endpoints and capabilities + * discovered. + * @param ep Remote endpoint. + * + * If discovery procedure has complete both @p codec and @p ep are set to + * NULL. + */ + + void (*endpoint)(struct bt_conn *conn, enum bt_audio_dir dir, + struct bt_bap_ep *ep); + + /* @brief BAP discovery callback function. + * + * If discovery procedure has completed @p ep is set to NULL and @p err is + * 0. + * + * @param conn Connection to the remote unicast server. + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param dir The type of remote endpoints and capabilities + * discovered. + * + * If discovery procedure has complete both @p codec and @p ep are set to + * NULL. + */ + + void (*discover)(struct bt_conn *conn, int err, enum bt_audio_dir dir); + + /** @cond INTERNAL_HIDDEN */ + + /** Internally used field for list handling */ + + struct list_node _node; + + /** @endcond */ +}; + +/** + * @brief Register unicast client callbacks. + * + * @param cb Unicast client callback structure to register. + * + * @retval 0 Success + * @retval -EINVAL @p cb is NULL. + * @retval -EEXIST @p cb is already registered. + */ + +int bt_bap_unicast_client_register_cb(struct bt_bap_unicast_client_cb *cb); + +/** + * @brief Unregister unicast client callbacks. + * + * @param cb Unicast client callback structure to unregister. + * + * @retval 0 Success + * @retval -EINVAL @p cb is NULL or @p cb was not registered + */ + +int bt_bap_unicast_client_unregister_cb(struct bt_bap_unicast_client_cb *cb); + +/** + * @brief Discover remote capabilities and endpoints + * + * This procedure is used by a client to discover remote capabilities and + * endpoints and notifies via params callback. + * + * @param conn The ACL connection. The connection must already conform to + * the security requirements of the Basic Audio Profile. + * @param dir The type of remote endpoints and capabilities to discover. + * + * @retval 0 Success + * @retval -EINVAL @p conn is NULL, not a central connection or does not + * conform to security requirements, or @p dir is invalid. + * @retval -EBUSY Another operation is already in progress for this @p conn + * @retval -ENOTCONN @p conn is not connected + * @retval -ENOMEM Could not allocate memory for the request + * @retval -ENOEXEC Unexpected GATT error + */ + +int bt_bap_unicast_client_discover(struct bt_conn *conn, + enum bt_audio_dir dir); + +/** + * @brief Get a copy of the QoS configured for the group of the stream + * + * This may be different from @p stream->qos if the stream has not been QoS + * configured or if group has been reconfigured with + * bt_bap_unicast_group_reconfig(). The QoS returned from this is what will + * be applied when bt_bap_stream_qos() is called for the group. + * + * @param[in] stream The stream to get the QoS configuration information from + * @param[out] qos The copy of the QoS configuration data + * + * @retval 0 Success + * @retval -EINVAL @p stream or @p qos are NULL, or @p stream is not part of + * a group. + */ + +int bt_bap_unicast_client_qos_from_group(const struct bt_bap_stream *stream, + struct bt_bap_qos_cfg *qos); + +/** + * @brief Compare two @ref bt_bap_qos_cfg and return whether they are equal + * + * @param a The first QoS config to compare with + * @param b The second QoS config to compare with + * + * @retval true @p a and @p b points to the same memory (including NULL), or + * all fields are identical. + * @retval false Either @p a or @p b is NULL or any of the fields are not + * identical. + */ + +bool bt_bap_qos_cfg_eq(const struct bt_bap_qos_cfg *a, + const struct bt_bap_qos_cfg *b); + +/** @} */ /* End of group bt_bap_unicast_client */ + +/** + * @brief BAP Broadcast APIs + * @defgroup bt_bap_broadcast BAP Broadcast APIs + * @ingroup bt_bap + * @{ + */ + +/** @brief Abstract Broadcast Audio Source Endpoint (BASE) subgroup + * structure. + */ + +struct bt_bap_base_subgroup; + +/** @brief Abstract Broadcast Audio Source Endpoint (BASE) structure. */ + +struct bt_bap_base; + +/** Codec ID structure for a Broadcast Audio Source Endpoint (BASE) */ + +struct bt_bap_base_codec_id +{ + /** Codec ID */ + + uint8_t id; + + /** Codec Company ID */ + + uint16_t cid; + + /** Codec Company Vendor ID */ + + uint16_t vid; +}; + +/** BIS structure for each BIS in a Broadcast Audio Source Endpoint (BASE) + * subgroup + */ + +struct bt_bap_base_subgroup_bis +{ + /** Unique index of the BIS */ + + uint8_t index; + + /** Codec Specific Data length. */ + + uint8_t data_len; + + /** Codec Specific Data */ + + uint8_t *data; +}; + +/** + * @brief Generate a pointer to a BASE from periodic advertising data + * + * @param ad The periodic advertising data + * + * @retval NULL if the data does not contain a BASE + * @retval Pointer to a bt_bap_base structure + */ + +const struct bt_bap_base *bt_bap_base_get_base_from_ad( + const struct bt_data *ad); + +/** + * @brief Get the size of a BASE + * + * @param base The BASE pointer + * + * @retval -EINVAL if arguments are invalid + * @retval The size of the BASE + */ + +int bt_bap_base_get_size(const struct bt_bap_base *base); + +/** + * @brief Get the presentation delay value of a BASE + * + * @param base The BASE pointer + * + * @retval -EINVAL if arguments are invalid + * @retval The 24-bit presentation delay value + */ + +int bt_bap_base_get_pres_delay(const struct bt_bap_base *base); + +/** + * @brief Get the subgroup count of a BASE + * + * @param base The BASE pointer + * + * @retval -EINVAL if arguments are invalid + * @retval The 8-bit subgroup count value + */ + +int bt_bap_base_get_subgroup_count(const struct bt_bap_base *base); + +/** + * @brief Get all BIS indexes of a BASE + * + * @param[in] base The BASE pointer + * @param[out] bis_indexes 32-bit BIS index bitfield that will be populated + * + * @retval -EINVAL if arguments are invalid + * @retval 0 on success + */ + +int bt_bap_base_get_bis_indexes(const struct bt_bap_base *base, + uint32_t *bis_indexes); + +/** + * @brief Iterate on all subgroups in the BASE + * + * @param base The BASE pointer + * @param func Callback function. Return true to continue iterating, or + * false to stop. + * @param user_data Userdata supplied to @p func + * + * @retval -EINVAL if arguments are invalid + * @retval -ECANCELED if iterating over the subgroups stopped prematurely by + * @p func + * @retval 0 if all subgroups were iterated + */ + +int bt_bap_base_foreach_subgroup(const struct bt_bap_base *base, +bool (*func)(const struct bt_bap_base_subgroup *subgroup, + void *user_data), + void *user_data); + +/** + * @brief Get the codec ID of a subgroup + * + * @param[in] subgroup The subgroup pointer + * @param[out] codec_id Pointer to the struct where the results are placed + * + * @retval -EINVAL if arguments are invalid + * @retval 0 on success + */ + +int bt_bap_base_get_subgroup_codec_id( + const struct bt_bap_base_subgroup *subgroup, + struct bt_bap_base_codec_id *codec_id); + +/** + * @brief Get the codec configuration data of a subgroup + * + * @param[in] subgroup The subgroup pointer + * @param[out] data Pointer that will point to the resulting codec + * configuration data + * + * @retval -EINVAL if arguments are invalid + * @retval 0 on success + */ + +int bt_bap_base_get_subgroup_codec_data( + const struct bt_bap_base_subgroup *subgroup, uint8_t **data); + +/** + * @brief Get the codec metadata of a subgroup + * + * @param[in] subgroup The subgroup pointer + * @param[out] meta Pointer that will point to the resulting codec + * metadata + * + * @return Length of the metadata on success + * @retval -EINVAL if arguments are invalid + */ + +int bt_bap_base_get_subgroup_codec_meta( + const struct bt_bap_base_subgroup *subgroup, uint8_t **meta); + +/** + * @brief Store subgroup codec data in a @ref bt_audio_codec_cfg + * + * @param[in] subgroup The subgroup pointer + * @param[out] codec_cfg Pointer to the struct where the results are placed + * + * @retval -EINVAL if arguments are invalid + * @retval -ENOMEM if the @p codec_cfg cannot store the @p subgroup codec + * data + * @retval 0 on success + */ + +int bt_bap_base_subgroup_codec_to_codec_cfg( + const struct bt_bap_base_subgroup *subgroup, + struct bt_audio_codec_cfg *codec_cfg); + +/** + * @brief Get the BIS count of a subgroup + * + * @param subgroup The subgroup pointer + * + * @retval -EINVAL if arguments are invalid + * @retval The 8-bit BIS count value + */ + +int bt_bap_base_get_subgroup_bis_count( + const struct bt_bap_base_subgroup *subgroup); + +/** + * @brief Get all BIS indexes of a subgroup + * + * @param[in] subgroup The subgroup pointer + * @param[out] bis_indexes 32-bit BIS index bitfield that will be populated + * + * @retval -EINVAL if arguments are invalid + * @retval 0 on success + */ + +int bt_bap_base_subgroup_get_bis_indexes( + const struct bt_bap_base_subgroup *subgroup, uint32_t *bis_indexes); + +/** + * @brief Iterate on all BIS in the subgroup + * + * @param subgroup The subgroup pointer + * @param func Callback function. Return true to continue iterating, or + * false to stop. + * @param user_data Userdata supplied to @p func + * + * @retval -EINVAL if arguments are invalid + * @retval -ECANCELED if iterating over the subgroups stopped prematurely by + * @p func + * @retval 0 if all BIS were iterated + */ + +int bt_bap_base_subgroup_foreach_bis( + const struct bt_bap_base_subgroup *subgroup, + bool (*func)(const struct bt_bap_base_subgroup_bis *bis, void *user_data), + void *user_data); + +/** + * @brief Store BIS codec configuration data in a @ref bt_audio_codec_cfg + * + * This only sets the @ref bt_audio_codec_cfg data and @ref + * bt_audio_codec_cfg data_len, but is useful to use the BIS codec + * configuration data with the bt_audio_codec_cfg_* functions. + * + * @param[in] bis The BIS pointer + * @param[out] codec_cfg Pointer to the struct where the results are placed + * + * @retval -EINVAL if arguments are invalid + * @retval -ENOMEM if the @p codec_cfg cannot store the @p subgroup codec + * data + * @retval 0 on success + */ + +int bt_bap_base_subgroup_bis_codec_to_codec_cfg( + const struct bt_bap_base_subgroup_bis *bis, + struct bt_audio_codec_cfg *codec_cfg); + +/** @} */ /* End of group bt_bap_broadcast */ + +/** + * @brief BAP Broadcast Source APIs + * @defgroup bt_bap_broadcast_source BAP Broadcast Source APIs + * @ingroup bt_bap_broadcast + * @{ + */ + +/** + * @brief Struct to hold the Broadcast Source callbacks + * + * These can be registered for usage with + * bt_bap_broadcast_source_register_cb(). + */ + +struct bt_bap_broadcast_source_cb +{ + /* @brief The Broadcast Source has started and all of the streams are ready + * for audio data + * + * @param source The started Broadcast Source + */ + + void (*started)(struct bt_bap_broadcast_source *source); + + /* @brief The Broadcast Source has stopped and none of the streams are + * ready for audio data + * + * @param source The stopped Broadcast Source + * @param reason The reason why the Broadcast Source stopped (see the + * BT_HCI_ERR_* values) + */ + + void (*stopped)(struct bt_bap_broadcast_source *source, uint8_t reason); + + /** @cond INTERNAL_HIDDEN */ + + /** Internally used field for list handling */ + + struct list_node _node; + + /** @endcond */ +}; + +/** + * @brief Registers callbacks for Broadcast Sources + * + * @param cb Pointer to the callback structure. + * + * @retval 0 on success + * @retval -EINVAL if @p cb is NULL + * @retval -EEXIST if @p cb is already registered + */ + +int bt_bap_broadcast_source_register_cb( + struct bt_bap_broadcast_source_cb *cb); + +/** + * @brief Unregisters callbacks for Broadcast Sources + * + * @param cb Pointer to the callback structure. + * + * @retval 0 on success + * @retval -EINVAL if @p cb is NULL + * @retval -ENOENT if @p cb is not registered + */ + +int bt_bap_broadcast_source_unregister_cb( + struct bt_bap_broadcast_source_cb *cb); + +/** Broadcast Source stream parameters */ + +struct bt_bap_broadcast_source_stream_param +{ + /** Audio stream */ + + struct bt_bap_stream *stream; + +#if CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE > 0 || defined(__DOXYGEN__) + /* @brief The number of elements in the @p data array. + * + * The BIS specific data may be omitted and this set to 0. + */ + + size_t data_len; + + /** BIS Codec Specific Configuration */ + + const uint8_t *data; +#endif /* CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE > 0 */ +}; + +/** Broadcast Source subgroup parameters */ + +struct bt_bap_broadcast_source_subgroup_param +{ + /** The number of parameters in @p stream_params */ + + size_t params_count; + + /** Array of stream parameters */ + + struct bt_bap_broadcast_source_stream_param *params; + + /** Subgroup Codec configuration. */ + + const struct bt_audio_codec_cfg *codec_cfg; +}; + +/** Broadcast Source create parameters */ + +struct bt_bap_broadcast_source_param +{ + /** The number of parameters in @p subgroup_params */ + + size_t params_count; + + /** Array of stream parameters */ + + struct bt_bap_broadcast_source_subgroup_param *params; + + /** Quality of Service configuration. */ + + const struct bt_bap_qos_cfg *qos; + + /* @brief Broadcast Source packing mode. + * + * @ref BT_ISO_PACKING_SEQUENTIAL or @ref BT_ISO_PACKING_INTERLEAVED. + * + * @note This is a recommendation to the controller, which the controller + * may ignore. + */ + + uint8_t packing; + + /** Whether or not to encrypt the streams. */ + + bool encryption; + + /* @brief Broadcast code + * + * If the value is a string or a the value is less than 16 octets, the + * remaining octets shall be 0. + * + * Example: The string "Broadcast Code" shall be [42 72 6F 61 64 63 61 73 + * 74 20 43 6F 64 65 00 00] + */ + + uint8_t broadcast_code[BT_ISO_BROADCAST_CODE_SIZE]; + +#if defined(CONFIG_BT_ISO_TEST_PARAMS) || defined(__DOXYGEN__) + /* @brief Immediate Repetition Count + * + * The number of times the scheduled payloads are transmitted in a given + * event. + * + * Value range from @ref BT_ISO_IRC_MIN to @ref BT_ISO_IRC_MAX. + */ + + uint8_t irc; + + /* @brief Pre-transmission offset + * + * Offset used for pre-transmissions. + * + * Value range from @ref BT_ISO_PTO_MIN to @ref BT_ISO_PTO_MAX. + */ + + uint8_t pto; + + /* @brief ISO interval + * + * Time between consecutive BIS anchor points. + * + * Value range from @ref BT_ISO_ISO_INTERVAL_MIN to @ref + * BT_ISO_ISO_INTERVAL_MAX. + */ + + uint16_t iso_interval; +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ +}; + +/** + * @brief Create audio broadcast source. + * + * Create a new audio broadcast source with one or more audio streams. + * + * The broadcast source will be visible for scanners once this has been + * called, and the device will advertise audio announcements. + * + * No audio data can be sent until bt_bap_broadcast_source_start() has been + * called and no audio information (BIGInfo) will be visible to scanners (see + * @ref bt_le_per_adv_sync_cb). + * + * @param[in] param Pointer to parameters used to create the broadcast + * source. + * @param[out] source Pointer to the broadcast source created + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_bap_broadcast_source_create( + struct bt_bap_broadcast_source_param *param, + struct bt_bap_broadcast_source **source); + +/** + * @brief Reconfigure audio broadcast source. + * + * Reconfigure an audio broadcast source with a new codec and codec quality + * of service parameters. This can only be done when the source is stopped. + * + * Since this may modify the Broadcast Audio Source Endpoint (BASE), + * bt_bap_broadcast_source_get_base() should be called after this to get the + * new BASE information. + * + * If the @p param.params_count is smaller than the number of subgroups that + * have been created in the Broadcast Source, only the first @p + * param.params_count subgroups are updated. If a stream exist in a subgroup + * not part of @p param, then that stream is left as is (i.e. it is not + * removed; the only way to remove a stream from a Broadcast Source is to + * recreate the Broadcast Source). + * + * @param source Pointer to the broadcast source + * @param param Pointer to parameters used to reconfigure the broadcast + * source. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_bap_broadcast_source_reconfig(struct bt_bap_broadcast_source *source, + struct bt_bap_broadcast_source_param *param); + +/** + * @brief Modify the metadata of an audio broadcast source. + * + * Modify the metadata an audio broadcast source. This can only be done when + * the source is started. To update the metadata in the stopped state, use + * bt_bap_broadcast_source_reconfig(). + * + * @param source Pointer to the broadcast source. + * @param meta Metadata. + * @param meta_len Length of metadata. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_bap_broadcast_source_update_metadata( + struct bt_bap_broadcast_source *source, const uint8_t meta[], + size_t meta_len); + +/** + * @brief Start audio broadcast source. + * + * Start an audio broadcast source with one or more audio streams. The + * broadcast source will start advertising BIGInfo, and audio data can be + * streamed. + * + * @param source Pointer to the broadcast source + * @param adv Pointer to an extended advertising set with periodic + * advertising configured. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_bap_broadcast_source_start(struct bt_bap_broadcast_source *source, + struct bt_le_ext_adv *adv); + +/** + * @brief Stop audio broadcast source. + * + * Stop an audio broadcast source. The broadcast source will stop advertising + * BIGInfo, and audio data can no longer be streamed. + * + * @param source Pointer to the broadcast source + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_bap_broadcast_source_stop(struct bt_bap_broadcast_source *source); + +/** + * @brief Delete audio broadcast source. + * + * Delete an audio broadcast source. The broadcast source will stop + * advertising entirely, and the source can no longer be used. + * + * @param source Pointer to the broadcast source + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_bap_broadcast_source_delete(struct bt_bap_broadcast_source *source); + +/** + * @brief Get the Broadcast Audio Stream Endpoint of a broadcast source + * + * This will encode the BASE of a broadcast source into a buffer, that can be + * used for advertisement. The encoded BASE will thus be encoded as + * little-endian. The BASE shall be put into the periodic advertising data + * (see bt_le_per_adv_set_data()). + * + * See table 3.15 in the Basic Audio Profile v1.0.1 for the structure. + * + * @param source Pointer to the broadcast source. + * @param base_buf Pointer to a buffer where the BASE will be inserted. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_bap_broadcast_source_get_base(struct bt_bap_broadcast_source *source, + struct net_buf_simple *base_buf); + +/** + * @brief Callback function for bt_bap_broadcast_source_foreach_stream() + * + * @param stream The audio stream + * @param user_data User data + * + * @retval true Continue iterating. + * @retval false Stop iterating. + */ + +typedef bool (*bt_bap_broadcast_source_foreach_stream_func_t)( + struct bt_bap_stream *stream, void *user_data); + +/** + * @brief Iterate through all streams in a broadcast source + * + * @param source The broadcast source + * @param func The callback function + * @param user_data User specified data that is sent to the callback + * function + * + * @retval 0 Success (even if no streams exists in the broadcast source). + * @retval -ECANCELED The @p func returned false and stopped the iteration. + * @retval -EINVAL @p source or @p func were NULL. + */ + +int bt_bap_broadcast_source_foreach_stream( + struct bt_bap_broadcast_source *source, + bt_bap_broadcast_source_foreach_stream_func_t func, void *user_data); +/** @} */ /* End of bt_bap_broadcast_source */ + +/** + * @brief BAP Broadcast Sink APIs + * @defgroup bt_bap_broadcast_sink BAP Broadcast Sink APIs + * @ingroup bt_bap_broadcast + * @{ + */ + +/** Broadcast Audio Sink callback structure */ + +struct bt_bap_broadcast_sink_cb +{ + /* @brief Broadcast Audio Source Endpoint (BASE) received + * + * Callback for when we receive a BASE from a broadcaster after syncing to + * the broadcaster's periodic advertising. + * + * @param sink Pointer to the sink structure. + * @param base Broadcast Audio Source Endpoint (BASE). + * @param base_size Size of the @p base + */ + + void (*base_recv)(struct bt_bap_broadcast_sink *sink, + const struct bt_bap_base *base, + size_t base_size); + + /* @brief Broadcast sink is syncable + * + * Called whenever a broadcast sink is not synchronized to audio, but the + * audio is synchronizable. This is inferred when a BIGInfo report is + * received. + * + * Once this callback has been called, it is possible to call + * bt_bap_broadcast_sink_sync() to synchronize to the audio stream(s). + * + * @param sink Pointer to the sink structure. + * @param biginfo The BIGInfo report. + */ + + void (*syncable)(struct bt_bap_broadcast_sink *sink, + const struct bt_iso_biginfo *biginfo); + + /* @brief The Broadcast Sink has started and audio data may be received + * from all of the streams + * + * @param sink The started Broadcast Sink + */ + + void (*started)(struct bt_bap_broadcast_sink *sink); + + /* @brief The Broadcast Sink has stopped and none of the streams will + * receive audio data + * + * @param sink The stopped Broadcast Sink + * @param reason The reason why the Broadcast Sink stopped (see the + * BT_HCI_ERR_* values) + */ + + void (*stopped)(struct bt_bap_broadcast_sink *sink, uint8_t reason); + + /** @cond INTERNAL_HIDDEN */ + + /** Internally used list node */ + + struct list_node _node; + + /** @endcond */ +}; + +/** + * @brief Register Broadcast sink callbacks + * + * It is possible to register multiple struct of callbacks, but a single + * struct can only be registered once. Registering the same callback multiple + * times is undefined behavior and may break the stack. + + * @param cb Broadcast sink callback structure. + * + * @retval 0 on success + * @retval -EINVAL if @p cb is NULL + * @retval -EALREADY if @p cb was already registered + */ + +int bt_bap_broadcast_sink_register_cb(struct bt_bap_broadcast_sink_cb *cb); + +/** + * @brief Create a Broadcast Sink from a periodic advertising sync + * + * This should only be done after verifying that the periodic advertising + * sync is from a Broadcast Source. + * + * The created Broadcast Sink will need to be supplied to + * bt_bap_broadcast_sink_sync() in order to synchronize to the broadcast + * audio. + * + * bt_bap_broadcast_sink_cb.pa_synced() will be called with the Broadcast + * Sink object created if this is successful. + * + * @param pa_sync Pointer to the periodic advertising sync object. + * @param broadcast_id 24-bit broadcast ID. + * @param[out] sink Pointer to the Broadcast Sink created. + * + * @return 0 in case of success or errno value in case of error. + */ + +int bt_bap_broadcast_sink_create( + struct bt_le_per_adv_sync *pa_sync, uint32_t broadcast_id, + struct bt_bap_broadcast_sink **sink); + +/** + * @brief Sync to a broadcaster's audio + * + * @param sink Pointer to the sink object from the base_recv + * callback. + * @param indexes_bitfield Bitfield of the BIS index to sync to. To sync to + * e.g. BIS index 1 and 2, this should have the + * value of BIT(1U) | BIT(2U). + * @param streams Stream object pointers to be used for the + * receiver. If multiple BIS indexes shall be + * synchronized, multiple streams shall be + * provided. + * @param broadcast_code The 16-octet broadcast code. Shall be supplied + * if the broadcast is encrypted (see @ref + * bt_bap_broadcast_sink_cb.syncable). If the value + * is a string or a the value is less than 16 + * octets, the remaining octets shall be 0. + * + * Example: The string "Broadcast Code" shall be [42 72 6F 61 64 63 61 73 74 + * 20 43 6F 64 65 00 00] + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_bap_broadcast_sink_sync( + struct bt_bap_broadcast_sink *sink, uint32_t indexes_bitfield, + struct bt_bap_stream *streams[], + const uint8_t broadcast_code[BT_ISO_BROADCAST_CODE_SIZE]); + +/** + * @brief Stop audio broadcast sink. + * + * Stop an audio broadcast sink. The broadcast sink will stop receiving + * BIGInfo, and audio data can no longer be streamed. + * + * @param sink Pointer to the broadcast sink + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_bap_broadcast_sink_stop(struct bt_bap_broadcast_sink *sink); + +/** + * @brief Release a broadcast sink + * + * Once a broadcast sink has been allocated after the pa_synced callback, it + * can be deleted using this function. If the sink has synchronized to any + * broadcast audio streams, these must first be stopped using + * bt_bap_stream_stop. + * + * @param sink Pointer to the sink object to delete. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_bap_broadcast_sink_delete(struct bt_bap_broadcast_sink *sink); + +/** @} */ /* End of group bt_bap_broadcast_sink */ + +/** + * @brief Register the Basic Audio Profile Scan Delegator and BASS. + * + * Register the scan delegator and Broadcast Audio Scan Service (BASS) + * dynamically at runtime. + * + * Only one set of callbacks can be registered at any one time, and calling + * this function multiple times will override any previously registered + * callbacks. + * + * @param cb Pointer to the callback struct + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_bap_scan_delegator_register(struct bt_bap_scan_delegator_cb *cb); + +/** + * @brief unregister the Basic Audio Profile Scan Delegator and BASS. + * + * Unregister the scan delegator and Broadcast Audio Scan Service (BASS) + * dynamically at runtime. + * + * @retval 0 Success + * @retval -EALREADY Already unregistering + * @retval -EAGAIN Not registered + */ + +int bt_bap_scan_delegator_unregister(void); + +/** + * @brief Set the periodic advertising sync state to syncing + * + * Set the periodic advertising sync state for a receive state to syncing, + * notifying Broadcast Assistants. + * + * @param src_id The source id used to identify the receive state. + * @param pa_state The Periodic Advertising sync state to set. + * + * @return int Error value. 0 on success, errno on fail. + */ + +int bt_bap_scan_delegator_set_pa_state(uint8_t src_id, + enum bt_bap_pa_state pa_state); + +/** + * @brief Set the sync state of a receive state in the server + * + * @param src_id The source id used to identify the receive state. + * @param bis_synced Array of bitfields to set the BIS sync state for + * each subgroup. + * @return int Error value. 0 on success, ERRNO on fail. + */ + +int bt_bap_scan_delegator_set_bis_sync_state(uint8_t src_id, + uint32_t bis_synced[BT_BAP_BASS_MAX_SUBGROUPS]); + +/** Parameters for bt_bap_scan_delegator_add_src() */ + +struct bt_bap_scan_delegator_add_src_param +{ + /** Periodic Advertiser Address */ + + bt_addr_le_t addr; + + /** Advertiser SID */ + + uint8_t sid; + + /* @brief Periodic Advertising sync state + * + * This will typically be either @ref BT_BAP_PA_STATE_NOT_SYNCED or + * @ref BT_BAP_PA_STATE_SYNCED. + */ + + enum bt_bap_pa_state pa_state; + + /** The broadcast isochronous group encryption state */ + + enum bt_bap_big_enc_state encrypt_state; + + /** The 24-bit broadcast ID */ + + uint32_t broadcast_id; + + /** Number of subgroups */ + + uint8_t num_subgroups; + + /** Subgroup specific information */ + + struct bt_bap_bass_subgroup subgroups[BT_BAP_BASS_MAX_SUBGROUPS]; +}; + +/** + * @brief Add a receive state source locally + * + * This will notify any connected clients about the new source. This allows + * them to modify and even remove it. + * + * If @kconfig{CONFIG_BT_BAP_BROADCAST_SINK} is enabled, any Broadcast Sink + * sources are autonomously added. + * + * @param param The parameters for adding the new source + * + * @return The source ID of the new state if return value is >= 0 + * @retval -EAGAIN Service not yet registered with + * bt_bap_scan_delegator_register() + * @retval -EINVAL Invalid parameters + * @retval -ENOMEM Could not add any more receive states + * @retval -EALREADY A receive state with the same advertiser address type, + * SID, and broadcast ID already exists + */ + +int bt_bap_scan_delegator_add_src( + const struct bt_bap_scan_delegator_add_src_param *param); + +/** Parameters for bt_bap_scan_delegator_mod_src() */ + +struct bt_bap_scan_delegator_mod_src_param +{ + /** The periodic adverting sync */ + + uint8_t src_id; + + /** The broadcast isochronous group encryption state */ + + enum bt_bap_big_enc_state encrypt_state; + + /** The 24-bit broadcast ID */ + + uint32_t broadcast_id; + + /** Number of subgroups */ + + uint8_t num_subgroups; + + /** Subgroup specific information */ + + struct bt_bap_bass_subgroup subgroups[BT_BAP_BASS_MAX_SUBGROUPS]; +}; + +/** + * @brief Add a receive state source locally + * + * This will notify any connected clients about the new source. This allows + * them to modify and even remove it. + * + * If @kconfig{CONFIG_BT_BAP_BROADCAST_SINK} is enabled, any Broadcast Sink + * sources are autonomously modified. + * + * @param param The parameters for adding the new source + * + * @return int errno on failure, or source ID on success. + */ + +int bt_bap_scan_delegator_mod_src( + const struct bt_bap_scan_delegator_mod_src_param *param); + +/** + * @brief Remove a receive state source + * + * This will remove the receive state. If the receive state periodic + * advertising is synced, bt_bap_scan_delegator_cb.pa_sync_term_req() will be + * called. + * + * If @kconfig{CONFIG_BT_BAP_BROADCAST_SINK} is enabled, any Broadcast Sink + * sources are autonomously removed. + * + * @param src_id The source ID to remove + * + * @return int Error value. 0 on success, errno on fail. + */ + +int bt_bap_scan_delegator_rem_src(uint8_t src_id); + +/** Callback function for Scan Delegator receive state search functions + * + * @param recv_state The receive state. + * @param user_data User data. + * + * @retval true to stop iterating. If this is used in the context of + * bt_bap_scan_delegator_find_state(), the recv_state will be returned by + * bt_bap_scan_delegator_find_state() + * @retval false to continue iterating + */ + +typedef bool (*bt_bap_scan_delegator_state_func_t)( + const struct bt_bap_scan_delegator_recv_state *recv_state, void + *user_data); + +/** + * @brief Iterate through all existing receive states + * + * @param func The callback function + * @param user_data User specified data that sent to the callback function + */ + +void bt_bap_scan_delegator_foreach_state( + bt_bap_scan_delegator_state_func_t func, void *user_data); + +/** + * @brief Find and return a receive state based on a compare function + * + * @param func The compare callback function + * @param user_data User specified data that sent to the callback function + * + * @return The first receive state where the @p func returned true, or NULL + */ + +const struct bt_bap_scan_delegator_recv_state * +bt_bap_scan_delegator_find_state(bt_bap_scan_delegator_state_func_t func, + void *user_data); + +/* Client Api */ + +/** + * @brief Callback function for writes. + * + * @param conn The connection to the peer device. + * @param err Error value. 0 on success, GATT error on fail. + */ + +typedef void (*bt_bap_broadcast_assistant_write_cb)( + struct bt_conn *conn, int err); + +/** + * @brief Struct to hold the Basic Audio Profile Broadcast Assistant + * callbacks + * + * These can be registered for usage with + * bt_bap_broadcast_assistant_register_cb(). + */ + +struct bt_bap_broadcast_assistant_cb +{ + /* @brief Callback function for bt_bap_broadcast_assistant_discover. + * + * @param conn The connection that was used to discover + * Broadcast Audio Scan Service. + * @param err Error value. 0 on success, GATT error or ERRNO + * on fail. + * @param recv_state_count Number of receive states on the server. + */ + + void (*discover)(struct bt_conn *conn, int err, uint8_t recv_state_count); + + /* @brief Callback function for Broadcast Audio Scan Service client scan + * results + * + * Called when the scanner finds an advertiser that advertises the + * BT_UUID_BROADCAST_AUDIO UUID. + * + * @param info Advertiser information. + * @param broadcast_id 24-bit broadcast ID. + */ + + void (*scan)(const struct bt_le_scan_recv_info *info, + uint32_t broadcast_id); + + /* @brief Callback function for when a receive state is read or updated + * + * Called whenever a receive state is read or updated. + * + * @param conn The connection to the Broadcast Audio Scan Service + * server. + * @param err Error value. 0 on success, GATT error on fail. + * @param state The receive state or NULL if the receive state is empty. + */ + + void (*recv_state)(struct bt_conn *conn, int err, + const struct bt_bap_scan_delegator_recv_state *state); + + /* @brief Callback function for when a receive state is removed. + * + * @param conn The connection to the Broadcast Audio Scan Service + * server. + * @param src_id The receive state. + */ + + void (*recv_state_removed)(struct bt_conn *conn, uint8_t src_id); + + /* @brief Callback function for bt_bap_broadcast_assistant_scan_start(). + * + * @param conn The connection to the peer device. + * @param err Error value. 0 on success, GATT error on fail. + */ + + void (*scan_start)(struct bt_conn *conn, int err); + + /* @brief Callback function for bt_bap_broadcast_assistant_scan_stop(). + * + * @param conn The connection to the peer device. + * @param err Error value. 0 on success, GATT error on fail. + */ + + void (*scan_stop)(struct bt_conn *conn, int err); + + /* @brief Callback function for bt_bap_broadcast_assistant_add_src(). + * + * @param conn The connection to the peer device. + * @param err Error value. 0 on success, GATT error on fail. + */ + + void (*add_src)(struct bt_conn *conn, int err); + + /* @brief Callback function for bt_bap_broadcast_assistant_mod_src(). + * + * @param conn The connection to the peer device. + * @param err Error value. 0 on success, GATT error on fail. + */ + + void (*mod_src)(struct bt_conn *conn, int err); + + /* @brief Callback function for + * bt_bap_broadcast_assistant_set_broadcast_code(). + * + * @param conn The connection to the peer device. + * @param err Error value. 0 on success, GATT error on fail. + */ + + void (*broadcast_code)(struct bt_conn *conn, int err); + + /* @brief Callback function for bt_bap_broadcast_assistant_rem_src(). + * + * @param conn The connection to the peer device. + * @param err Error value. 0 on success, GATT error on fail. + */ + + void (*rem_src)(struct bt_conn *conn, int err); + + /** @cond INTERNAL_HIDDEN */ + + /** Internally used list node */ + + struct list_node _node; + + /** @endcond */ +}; + +/** + * @brief Discover Broadcast Audio Scan Service on the server. + * + * Warning: Only one connection can be active at any time; discovering for a + * new connection, will delete all previous data. + * + * @param conn The ACL connection. The connection must already conform to + * the security requirements of the Basic Audio Profile. + * + * @retval 0 Success + * @retval -EINVAL @p conn is NULL, does not conform to security requirements + * @retval -EBUSY Another operation is already in progress for this @p conn + * @retval -ENOTCONN @p conn is not connected + * @retval -ENOMEM Could not allocate memory for the request + * @retval -ENOEXEC Unexpected GATT error + */ + +int bt_bap_broadcast_assistant_discover(struct bt_conn *conn); + +/** + * @brief Scan start for BISes for a remote server. + * + * This will let the Broadcast Audio Scan Service server know that this + * device is actively scanning for broadcast sources. The function can + * optionally also start scanning, if the caller does not want to start + * scanning itself. + * + * Scan results, if @p start_scan is true, is sent to the + * bt_bap_broadcast_assistant_scan_cb callback. + * + * @param conn Connection to the Broadcast Audio Scan Service + * server. Used to let the server know that we are + * scanning. + * @param start_scan Start scanning if true. If false, the application + * should enable scan itself. + + * @retval 0 Success + * @retval -EINVAL @p conn is NULL of if @p conn has not done discovery + * @retval -EBUSY Another operation is already in progress for this @p conn + * @retval -EAGAIN Bluetooth has not been enabled. + * @retval -ENOTCONN @p conn is not connected + * @retval -ENOMEM Could not allocated memory for the request + * @retval -ENOEXEC Unexpected scan or GATT error + */ + +int bt_bap_broadcast_assistant_scan_start(struct bt_conn *conn, + bool start_scan); + +/** + * @brief Stop remote scanning for BISes for a server. + * + * @param conn Connection to the server. + + * @retval 0 Success + * @retval -EINVAL @p conn is NULL of if @p conn has not done discovery + * @retval -EBUSY Another operation is already in progress for this @p conn + * @retval -EAGAIN Bluetooth has not been enabled. + * @retval -ENOTCONN @p conn is not connected + * @retval -ENOMEM Could not allocated memory for the request + * @retval -ENOEXEC Unexpected scan or GATT error + */ + +int bt_bap_broadcast_assistant_scan_stop(struct bt_conn *conn); + +/** + * @brief Registers the callbacks used by Broadcast Audio Scan Service + * client. + * + * @param cb The callback structure. + * + * @retval 0 on success + * @retval -EINVAL if @p cb is NULL + * @retval -EALREADY if @p cb was already registered + */ + +int bt_bap_broadcast_assistant_register_cb( + struct bt_bap_broadcast_assistant_cb *cb); + +/** + * @brief Unregisters the callbacks used by the Broadcast Audio Scan Service + * client. + * + * @param cb The callback structure. + * + * @retval 0 on success + * @retval -EINVAL if @p cb is NULL + * @retval -EALREADY if @p cb was not registered + */ + +int bt_bap_broadcast_assistant_unregister_cb( + struct bt_bap_broadcast_assistant_cb *cb); + +/** Parameters for adding a source to a Broadcast Audio Scan Service server */ + +struct bt_bap_broadcast_assistant_add_src_param +{ + /** Address of the advertiser. */ + + bt_addr_le_t addr; + + /** SID of the advertising set. */ + + uint8_t adv_sid; + + /** Whether to sync to periodic advertisements. */ + + bool pa_sync; + + /** 24-bit broadcast ID */ + + uint32_t broadcast_id; + + /* @brief Periodic advertising interval in milliseconds. + * + * BT_BAP_PA_INTERVAL_UNKNOWN if unknown. + */ + + uint16_t pa_interval; + + /** Number of subgroups */ + + uint8_t num_subgroups; + + /** Pointer to array of subgroups + * + * The @ref bt_bap_bass_subgroup.bis_sync value can be set to + * BT_BAP_BIS_SYNC_NO_PREF to let the broadcast sink decide which BIS to + * synchronize to. + */ + + struct bt_bap_bass_subgroup *subgroups; +}; + +/** + * @brief Add a source on the server. + * + * @param conn Connection to the server. + * @param param Parameter struct. + * + * @retval 0 Success + * @retval -EINVAL @p conn is NULL or %p conn has not done discovery or if @p + * param is invalid + * @retval -EBUSY Another operation is already in progress for this @p conn + * @retval -ENOTCONN @p conn is not connected + * @retval -ENOMEM Could not allocated memory for the request + * @retval -ENOEXEC Unexpected scan or GATT error + */ + +int bt_bap_broadcast_assistant_add_src(struct bt_conn *conn, + const struct bt_bap_broadcast_assistant_add_src_param *param); + +/** Parameters for modifying a source */ + +struct bt_bap_broadcast_assistant_mod_src_param +{ + /** Source ID of the receive state. */ + + uint8_t src_id; + + /** Whether to sync to periodic advertisements. */ + + bool pa_sync; + + /* @brief Periodic advertising interval. + * + * BT_BAP_PA_INTERVAL_UNKNOWN if unknown. + */ + + uint16_t pa_interval; + + /** Number of subgroups */ + + uint8_t num_subgroups; + + /** Pointer to array of subgroups */ + + struct bt_bap_bass_subgroup *subgroups; +}; + +/** + * @brief Modify a source on the server. + * + * @param conn Connection to the server. + * @param param Parameter struct. + * + * @retval 0 Success + * @retval -EINVAL @p conn is NULL or %p conn has not done discovery or if @p + * param is invalid + * @retval -EBUSY Another operation is already in progress for this @p conn + * @retval -ENOTCONN @p conn is not connected + * @retval -ENOMEM Could not allocated memory for the request + * @retval -ENOEXEC Unexpected scan or GATT error + */ + +int bt_bap_broadcast_assistant_mod_src(struct bt_conn *conn, + const struct bt_bap_broadcast_assistant_mod_src_param *param); + +/** + * @brief Set a broadcast code to the specified receive state. + * + * @param conn Connection to the server. + * @param src_id Source ID of the receive state. + * @param broadcast_code The broadcast code. + * + * @retval 0 Success + * @retval -EINVAL @p conn is NULL or %p conn has not done discovery or @p + * src_id is invalid + * @retval -EBUSY Another operation is already in progress for this @p conn + * @retval -ENOTCONN @p conn is not connected + * @retval -ENOMEM Could not allocated memory for the request + * @retval -ENOEXEC Unexpected scan or GATT error + */ + +int bt_bap_broadcast_assistant_set_broadcast_code(struct bt_conn *conn, + uint8_t src_id, + const uint8_t broadcast_code[BT_ISO_BROADCAST_CODE_SIZE]); + +/** + * @brief Remove a source from the server. + * + * @param conn Connection to the server. + * @param src_id Source ID of the receive state. + * + * @retval 0 Success + * @retval -EINVAL @p conn is NULL or %p conn has not done discovery or @p + * src_id is invalid + * @retval -EBUSY Another operation is already in progress for this @p conn + * @retval -ENOTCONN @p conn is not connected + * @retval -ENOMEM Could not allocated memory for the request + * @retval -ENOEXEC Unexpected scan or GATT error + */ + +int bt_bap_broadcast_assistant_rem_src(struct bt_conn *conn, uint8_t src_id); + +/** + * @brief Read the specified receive state from the server. + * + * @param conn Connection to the server. + * @param idx The index of the receive start (0 up to the value from + * bt_bap_broadcast_assistant_discover_cb) + * + * @retval 0 Success + * @retval -EINVAL @p conn is NULL or %p conn has not done discovery or @p + * src_id is invalid + * @retval -EBUSY Another operation is already in progress for this @p conn + * @retval -ENOTCONN @p conn is not connected + * @retval -ENOMEM Could not allocated memory for the request + * @retval -ENOEXEC Unexpected scan or GATT error + */ + +int bt_bap_broadcast_assistant_read_recv_state(struct bt_conn *conn, + uint8_t idx); + +/** @} */ /* end of bt_bap */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_BAP_H */ diff --git a/include/nuttx/net/bluetooth/audio/bap_lc3_preset.h b/include/nuttx/net/bluetooth/audio/bap_lc3_preset.h new file mode 100644 index 0000000000000..60ca3d23945e5 --- /dev/null +++ b/include/nuttx/net/bluetooth/audio/bap_lc3_preset.h @@ -0,0 +1,926 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/audio/bap_lc3_preset.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Header for Bluetooth BAP LC3 presets. + * + * Copyright (c) 2023-2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_BAP_LC3_PRESET_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_BAP_LC3_PRESET_H + +/** + * @brief Basic Audio Profile (BAP) LC3 Presets + * + * @defgroup bt_bap_lc3_preset Basic Audio Profile (BAP) LC3 Presets + * + * @since 3.0 + * @version 0.8.0 + * + * @ingroup bluetooth + * @{ + * + * These APIs provide preset for codec configuration and QoS based on values + * supplied by the codec configuration tables in the BAP specification. + * + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Struct to hold a BAP defined LC3 preset */ + +struct bt_bap_lc3_preset +{ + /** The LC3 Codec */ + + struct bt_audio_codec_cfg codec_cfg; + + /** The BAP spec defined QoS values */ + + struct bt_bap_qos_cfg qos; +}; + +/** Helper to declare an LC3 preset structure */ +#define BT_BAP_LC3_PRESET(_codec, _qos) \ +{ \ + .codec_cfg = _codec, \ + .qos = _qos, \ +} + +/* LC3 Unicast presets defined by table 5.2 in the BAP v1.0 specification */ + +/** + * @brief Helper to declare LC3 Unicast 8_1_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_8_1_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_8KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 26u, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 26u, 2u, 8u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 8_2_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_8_2_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_8KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 30U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 30u, 2u, 10u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 16_1_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_16_1_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_16KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 30U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 30u, 2u, 8u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 16_2_1 codec configuration + * + * Mandatory to support as both unicast client and server + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_16_2_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_16KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 40U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 40u, 2u, 10u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 24_1_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_24_1_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_24KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 45U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 45u, 2u, 8u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 24_2_1 codec configuration + * + * Mandatory to support as unicast server + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_24_2_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_24KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 60U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 60u, 2u, 10u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 32_1_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_32_1_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_32KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 60U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 60u, 2u, 8u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 32_2_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_32_2_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_32KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 80U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 80u, 2u, 10u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 441_1_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_441_1_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_44KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 97U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_FRAMED(8163u, 97u, 5u, 24u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 441_2_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_441_2_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_44KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 130U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_FRAMED(10884u, 130u, 5u, 31u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 48_1_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_48_1_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 75U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 75u, 5u, 15u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 48_2_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_48_2_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 100U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 100u, 5u, 20u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 48_3_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_48_3_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 90U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 90u, 5u, 15u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 48_4_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_48_4_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 120u, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 120u, 5u, 20u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 48_5_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_48_5_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 117u, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 117u, 5u, 15u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 48_6_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_48_6_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 155u, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 155u, 5u, 20u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 8_1_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ + +/* Following presets are for unicast high reliability audio data */ +#define BT_BAP_LC3_UNICAST_PRESET_8_1_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_8KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 26u, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 26u, 13u, 75u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 8_2_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_8_2_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_8KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 30U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 30u, 13u, 95u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 16_1_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_16_1_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_16KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 30U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 30u, 13u, 75u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 16_2_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_16_2_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_16KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 40U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 40u, 13u, 95u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 24_1_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_24_1_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_24KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 45U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 45u, 13u, 75u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 24_2_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_24_2_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_24KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 60U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 60u, 13u, 95u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 32_1_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_32_1_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_32KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 60U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 60u, 13u, 75u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 32_2_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_32_2_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_32KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 80U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 80u, 13u, 95u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 441_1_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_441_1_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_44KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 97U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_FRAMED(8163u, 97u, 13u, 80u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 441_2_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_441_2_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_44KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 130U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_FRAMED(10884u, 130u, 13u, 85u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 48_1_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_48_1_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 75U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 75u, 13u, 75u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 48_2_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_48_2_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 100U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 100u, 13u, 95u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 48_3_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_48_3_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 90U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 90u, 13u, 75u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 48_4_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_48_4_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 120u, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 120u, 13u, 100u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 48_5_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_48_5_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 117u, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 117u, 13u, 75u, 40000u)) + +/** + * @brief Helper to declare LC3 Unicast 48_6_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_UNICAST_PRESET_48_6_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 155u, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 155u, 13u, 100u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 8_1_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ + +/* LC3 Broadcast presets defined by table 6.4 in the BAP v1.0 specification */ +#define BT_BAP_LC3_BROADCAST_PRESET_8_1_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_8KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 26u, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 26u, 2u, 8u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 8_2_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_8_2_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_8KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 30U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 30u, 2u, 10u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 16_1_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_16_1_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_16KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 30U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 30u, 2u, 8u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 16_2_1 codec configuration + * + * Mandatory to support as both broadcast source and sink + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_16_2_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_16KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 40U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 40u, 2u, 10u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 24_1_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_24_1_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_24KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 45U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 45u, 2u, 8u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 24_2_1 codec configuration + * + * Mandatory to support as broadcast sink + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_24_2_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_24KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 60U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 60u, 2u, 10u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 32_1_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_32_1_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_32KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 60U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 60u, 2u, 8u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 32_2_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_32_2_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_32KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 80U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 80u, 2u, 10u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 441_1_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_441_1_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_44KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 97U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_FRAMED(8163u, 97u, 4u, 24u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 441_2_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_441_2_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_44KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 130U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_FRAMED(10884u, 130u, 4u, 31u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 48_1_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_48_1_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 75U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 75u, 4u, 15u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 48_2_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_48_2_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 100U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 100u, 4u, 20u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 48_3_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_48_3_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 90U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 90u, 4u, 15u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 48_4_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_48_4_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 120u, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 120u, 4u, 20u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 48_5_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_48_5_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 117u, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 117u, 4u, 15u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 48_6_1 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_48_6_1(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 155u, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 155u, 4u, 20u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 8_1_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ + +/* Following presets are for broadcast high reliability audio data */ +#define BT_BAP_LC3_BROADCAST_PRESET_8_1_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_8KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 26u, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 26u, 4u, 45u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 8_2_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_8_2_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_8KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 30U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 30u, 4u, 60u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 16_1_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_16_1_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_16KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 30U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 30u, 4u, 45u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 16_2_2 codec configuration + * + * Mandatory to support as both broadcast source and sink + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_16_2_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_16KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 40U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 40u, 4u, 60u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 24_1_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_24_1_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_24KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 45U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 45u, 4u, 45u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 24_2_2 codec configuration + * + * Mandatory to support as broadcast sink + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_24_2_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_24KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 60U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 60u, 4u, 60u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 32_1_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_32_1_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_32KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 60U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 60u, 4u, 45u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 32_2_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_32_2_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_32KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 80U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 80u, 4u, 60u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 441_1_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_441_1_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_44KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 97U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_FRAMED(8163u, 97u, 4u, 54u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 441_2_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_441_2_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_44KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 130U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_FRAMED(10884u, 130u, 4u, 60u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 48_1_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_48_1_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 75U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 75u, 4u, 50u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 48_2_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_48_2_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 100U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 100u, 4u, 65u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 48_3_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_48_3_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 90U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 90u, 4u, 50u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 48_4_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_48_4_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 120u, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 120u, 4u, 65u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 48_5_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_48_5_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 117u, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 117u, 4u, 50u, 40000u)) + +/** + * @brief Helper to declare LC3 Broadcast 48_6_2 codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_BAP_LC3_BROADCAST_PRESET_48_6_2(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 155u, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 155u, 4u, 65u, 40000u)) + +#ifdef __cplusplus +} +#endif +/** @} */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_BAP_LC3_PRESET_H */ diff --git a/include/nuttx/net/bluetooth/audio/cap.h b/include/nuttx/net/bluetooth/audio/cap.h new file mode 100644 index 0000000000000..05a6165d67101 --- /dev/null +++ b/include/nuttx/net/bluetooth/audio/cap.h @@ -0,0 +1,2004 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/audio/cap.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Bluetooth Common Audio Profile (CAP) APIs. + */ + +/* Copyright (c) 2022-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_CAP_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_CAP_H + +/** + * @brief Common Audio Profile (CAP) + * + * @defgroup bt_cap Common Audio Profile (CAP) + * + * @since 3.2 + * @version 0.8.0 + * + * @ingroup bluetooth + * @{ + * + * Common Audio Profile (CAP) provides procedures to start, update, and stop + * unicast and broadcast Audio Streams on individual or groups of devices + * using procedures in the Basic Audio Profile (BAP). This profile also + * provides procedures to control volume and device input on groups of + * devices using procedures in the Volume Control Profile (VCP) and the + * Microphone Control Profile (MICP). This profile specification also refers + * to the Common Audio Service (CAS). + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @struct bt_cap_broadcast_source + * @brief Abstract Audio Broadcast Source structure. + */ + +struct bt_cap_broadcast_source; + +/** + * @struct bt_cap_unicast_group + * @brief Abstract CAP Unicast Group structure. + */ + +struct bt_cap_unicast_group; + +/** + * @brief Register the Common Audio Service. + * + * This will register and enable the service and make it discoverable by + * clients. This will also register a Coordinated Set Identification Service + * instance. + * + * This shall only be done as a server, and requires + * @kconfig{CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER}. If + * @kconfig{CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER} is not enabled, the Common + * Audio Service will by statically registered. + * + * @param[in] param Coordinated Set Identification Service register + * parameters. + * @param[out] svc_inst Pointer to the registered Coordinated Set + * Identification Service. + * + * @return 0 if success, errno on failure. + */ + +int bt_cap_acceptor_register( + const struct bt_csip_set_member_register_param *param, + struct bt_csip_set_member_svc_inst **svc_inst); + +/** Callback structure for CAP procedures */ + +struct bt_cap_initiator_cb +{ +#if defined(CONFIG_BT_BAP_UNICAST_CLIENT) || defined(__DOXYGEN__) + /* @brief Callback for bt_cap_initiator_unicast_discover(). + * + * @param conn The connection pointer supplied to + * bt_cap_initiator_unicast_discover(). + * @param err 0 if Common Audio Service was found else -ENODATA. + * @param member Pointer to the set member. NULL if err != 0. + * @param csis_inst The Coordinated Set Identification Service if Common + * Audio Service was found and includes a Coordinated Set + * Identification Service. NULL on error or if remote + * device does not include Coordinated Set Identification + * Service. NULL if err != 0. + */ + + void (*unicast_discovery_complete)(struct bt_conn *conn, + int err, + const struct bt_csip_set_coordinator_set_member *member, + const struct bt_csip_set_coordinator_csis_inst *csis_inst); + + /* @brief Callback for bt_cap_initiator_unicast_audio_start(). + * + * @param err + * - 0 if success. + * - -ENOEXEC if subprocedure could not be performed. + * - -ENOMEM if subprocedure could not be performed due to lack of memory. + * - -EACCES if request rejected by peer. + * - -EBUSY if subprocedure could not be performed because unicast client + * is already busy. + * - -EINVAL if subprocedure contained invalid parameters. + * - -EBADMSG if unexpected state change happened. + * - -EALREADY if subprocedure could not be performed due to unexpected + * state. + * - -ECONNRESET if connection dropped during procedure. + * - -ENOTCONN if subprocedure could not be performed due to connection. + * - -ECANCELED if cancelled by bt_cap_initiator_unicast_audio_cancel(). + * + * @param conn Pointer to the connection where the error occurred. NULL if + * @p err is 0 or if cancelled by bt_cap_initiator_unicast_audio_cancel() + */ + + void (*unicast_start_complete)(int err, struct bt_conn *conn); + + /* @brief All streams have been codec configured + * + * All streams supplied to bt_cap_initiator_unicast_audio_start() are in + * the + * @ref BT_BAP_EP_STATE_CODEC_CONFIGURED state or higher, with the + * requested codec configuration. + * + * This is only called if at least one Config Codec operation was sent to a + * CAP acceptor, i.e. it is not called if all streams already had the + * requested codec configuration. + * + * The unicast group may be reconfigured with + * bt_cap_unicast_group_reconfig() from this callback, as the QoS + * Configuration subprocedure has not been started yet. This is only + * possible if none of the streams in the unicast group have been connected + * earlier, as the unicast group cannot be reconfigured once a CIS has been + * established. It is not possible to start a new CAP procedure from this + * callback, as the current procedure is still in progress. + */ + + void (*unicast_start_codec_configured)(void); + + /* @brief All streams have been QoS configured + * + * All streams supplied to bt_cap_initiator_unicast_audio_start() are in + * the + * @ref BT_BAP_EP_STATE_QOS_CONFIGURED state or higher, with the QoS + * configuration of the unicast group. + * + * This is only called if at least one Config QoS operation was sent to a + * CAP acceptor, i.e. it is not called if all streams already had the + * requested QoS configuration. + */ + + void (*unicast_start_qos_configured)(void); + + /* @brief All streams have been enabled + * + * All streams supplied to bt_cap_initiator_unicast_audio_start() are in + * the + * @ref BT_BAP_EP_STATE_ENABLING state or higher, with the requested + * metadata. + * + * This is only called if at least one Enable operation was sent to a CAP + * acceptor, i.e. it is not called if all streams were already enabled with + * the requested metadata. + */ + + void (*unicast_start_enabled)(void); + + /* @brief All streams have their CIS connected + * + * All streams supplied to bt_cap_initiator_unicast_audio_start() have an + * established CIS. + * + * This is only called if at least one CIS was established as part of the + * procedure, i.e. it is not called if all CISes were already established. + */ + + void (*unicast_start_connected)(void); + + /* @brief All streams have been started + * + * All streams supplied to bt_cap_initiator_unicast_audio_start() are in + * the + * @ref BT_BAP_EP_STATE_STREAMING state. + * + * This is only called if at least one Receiver Start Ready operation was + * sent to a CAP acceptor, i.e. it is not called if all streams were + * started by the CAP acceptors. + */ + + void (*unicast_start_started)(void); + + /* @brief Callback for bt_cap_initiator_unicast_audio_update(). + * + * @param err + * - 0 if success. + * - -ENOEXEC if subprocedure could not be performed. + * - -ENOMEM if subprocedure could not be performed due to lack of memory. + * - -EACCES if request rejected by peer. + * - -EBUSY if subprocedure could not be performed because unicast client + * is + * - already busy. + * - -EINVAL if subprocedure contained invalid parameters. + * - -EBADMSG if unexpected state change happened. + * - -ECONNRESET if connection dropped during procedure. + * - -ENOTCONN if subprocedure could not be performed due to connection. + * - -ECANCELED if cancelled by bt_cap_initiator_unicast_audio_cancel(). + * + * @param conn Pointer to the connection where the error occurred. NULL if + * @p err is 0 or if cancelled by bt_cap_initiator_unicast_audio_cancel() + */ + + void (*unicast_update_complete)(int err, struct bt_conn *conn); + + /* @brief Callback for bt_cap_initiator_unicast_audio_stop(). + * + * @param err + * - 0 if success. + * - -ENOEXEC if subprocedure could not be performed. + * - -ENOMEM if subprocedure could not be performed due to lack of memory. + * - -EACCES if request rejected by peer. + * - -EBUSY if subprocedure could not be performed because unicast client + * is + * - already busy. + * - -EINVAL if subprocedure contained invalid parameters. + * - -EBADMSG if unexpected state change happened. + * - -EALREADY if subprocedure could not be performed due to unexpected + * state. + * - -ECONNRESET if connection dropped during procedure. + * - -ENOTCONN if subprocedure could not be performed due to connection. + * - -ECANCELED if cancelled by bt_cap_initiator_unicast_audio_cancel(). + * + * @param conn Pointer to the connection where the error occurred. NULL if + * @p err is 0 or if cancelled by bt_cap_initiator_unicast_audio_cancel() + */ + + void (*unicast_stop_complete)(int err, struct bt_conn *conn); + + /* @brief All streams have been disabled + * + * All streams supplied to bt_cap_initiator_unicast_audio_stop() are in the + * @ref BT_BAP_EP_STATE_DISABLING or @ref BT_BAP_EP_STATE_QOS_CONFIGURED + * state. + * + * This is only called if at least one Disable operation was sent to a CAP + * acceptor, i.e. it is not called if none of the streams were enabled or + * streaming. + */ + + void (*unicast_stop_disabled)(void); + + /* @brief All streams have been stopped + * + * All streams supplied to bt_cap_initiator_unicast_audio_stop() are in the + * @ref BT_BAP_EP_STATE_QOS_CONFIGURED state. + * + * This is only called if at least one Receiver Stop Ready operation was + * sent to a CAP acceptor, i.e. it is not called if none of the streams + * were in the + * @ref BT_BAP_EP_STATE_DISABLING state. + */ + + void (*unicast_stop_stopped)(void); + + /* @brief All streams have been released + * + * All streams supplied to bt_cap_initiator_unicast_audio_stop() have been + * released. + * + * This is only called if at least one Release operation was sent to a CAP + * acceptor, i.e. it is not called if the streams were not requested to be + * released. + */ + + void (*unicast_stop_released)(void); +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT */ +#if defined(CONFIG_BT_BAP_BROADCAST_SOURCE) || defined(__DOXYGEN__) + /* @brief The Broadcast Source has started and all of the streams are + * ready for audio data + * + * @param source The started Broadcast Source + */ + + void (*broadcast_started)(struct bt_cap_broadcast_source *source); + + /* @brief The Broadcast Source has stopped and none of the streams are + * ready for audio data + * + * @param source The stopped Broadcast Source + * @param reason The reason why the Broadcast Source stopped (see the + * BT_HCI_ERR_* values) + */ + + void (*broadcast_stopped)(struct bt_cap_broadcast_source *source, + uint8_t reason); +#endif /* CONFIG_BT_BAP_BROADCAST_SOURCE */ +}; + +/** + * @brief Discovers audio support on a remote device. + * + * This will discover the Common Audio Service (CAS) on the remote device, to + * verify if the remote device supports the Common Audio Profile. + * + * @param conn Connection to a remote server. + * + * @retval 0 Success + * @retval -EINVAL @p conn is NULL + * @retval -ENOTCONN @p conn is not connected + * @retval -ENOMEM Could not allocated memory for the request + */ + +int bt_cap_initiator_unicast_discover(struct bt_conn *conn); + +/** Type of CAP set */ + +enum bt_cap_set_type +{ + /** The set is an ad-hoc set */ + + BT_CAP_SET_TYPE_AD_HOC, + + /** The set is a CSIP Coordinated Set */ + + BT_CAP_SET_TYPE_CSIP, +}; + +/** Represents a Common Audio Set member that are either in a Coordinated or + * ad-hoc set + */ + +union bt_cap_set_member +{ + /** Connection pointer if the type is BT_CAP_SET_TYPE_AD_HOC. */ + + struct bt_conn *member; + + /** CSIP Coordinated Set struct used if type is BT_CAP_SET_TYPE_CSIP. */ + + struct bt_csip_set_coordinator_csis_inst *csip; +}; + +/** + * @brief Common Audio Profile stream structure. + * + * Streams represents a Basic Audio Profile (BAP) stream and operation + * callbacks. See @ref bt_bap_stream for additional information. + */ + +struct bt_cap_stream +{ + /** The underlying BAP audio stream */ + + struct bt_bap_stream bap_stream; + + /** Audio stream operations */ + + struct bt_bap_stream_ops *ops; +}; + +/** + * @brief Register Audio operations for a Common Audio Profile stream. + * + * Register Audio operations for a stream. + * + * @param stream Stream object. + * @param ops Stream operations structure. + */ + +void bt_cap_stream_ops_register(struct bt_cap_stream *stream, + struct bt_bap_stream_ops *ops); + +/** + * @brief Send data to Common Audio Profile stream without timestamp + * + * See bt_bap_stream_send() for more information + * + * @note Support for sending must be supported, determined by + * @kconfig{CONFIG_BT_AUDIO_TX}. + * + * @param stream Stream object. + * @param buf Buffer containing data to be sent. + * @param seq_num Packet Sequence number. This value shall be incremented + * for each call to this function and at least once per SDU + * interval for a specific channel. + * + * @retval -EINVAL if stream object is NULL + * @retval Any return value from bt_bap_stream_send() + */ + +int bt_cap_stream_send(struct bt_cap_stream *stream, + struct net_buf *buf, uint16_t seq_num); + +/** + * @brief Send data to Common Audio Profile stream with timestamp + * + * See bt_bap_stream_send() for more information + * + * @note Support for sending must be supported, determined by + * @kconfig{CONFIG_BT_AUDIO_TX}. + * + * @param stream Stream object. + * @param buf Buffer containing data to be sent. + * @param seq_num Packet Sequence number. This value shall be incremented + * for each call to this function and at least once per SDU + * interval for a specific channel. + * @param ts Timestamp of the SDU in microseconds (us). This value can + * be used to transmit multiple SDUs in the same SDU interval + * in a CIG or BIG. + * + * @retval -EINVAL if stream object is NULL + * @retval Any return value from bt_bap_stream_send() + */ + +int bt_cap_stream_send_ts( + struct bt_cap_stream *stream, struct net_buf *buf, uint16_t seq_num, + uint32_t ts); + +/** + * @brief Get ISO transmission timing info for a Common Audio Profile stream + * + * See bt_bap_stream_get_tx_sync() for more information + * + * @note Support for sending must be supported, determined by + * @kconfig{CONFIG_BT_AUDIO_TX}. + * + * @param[in] stream Stream object. + * @param[out] info Transmit info object. + * + * @retval -EINVAL if stream object is NULL + * @retval Any return value from bt_bap_stream_get_tx_sync() + */ + +int bt_cap_stream_get_tx_sync(struct bt_cap_stream *stream, + struct bt_iso_tx_info *info); + +/** Parameter struct for each stream in the unicast group */ + +struct bt_cap_unicast_group_stream_param +{ + /** Pointer to a stream object. */ + + struct bt_cap_stream *stream; + + /** The QoS settings for the stream object. */ + + const struct bt_bap_qos_cfg *qos_cfg; +}; + +/** + * @brief Parameter struct for the unicast group functions + * + * Parameter struct for the bt_cap_unicast_group_create() and + * bt_cap_unicast_group_add_streams() functions. + */ + +struct bt_cap_unicast_group_stream_pair_param +{ + /** Pointer to a receiving stream parameters. */ + + struct bt_cap_unicast_group_stream_param *rx_param; + + /** Pointer to a transmitting stream parameters. */ + + struct bt_cap_unicast_group_stream_param *tx_param; +}; + +/** Parameters for the creating unicast groups with + * bt_cap_unicast_group_create() + */ + +struct bt_cap_unicast_group_param +{ + /** The number of parameters in @p params */ + + size_t params_count; + + /** Array of stream parameters */ + + struct bt_cap_unicast_group_stream_pair_param *params; + + /* @brief Unicast Group packing mode. + * + * @ref BT_ISO_PACKING_SEQUENTIAL or @ref BT_ISO_PACKING_INTERLEAVED. + * + * @note This is a recommendation to the controller, which the controller + * may ignore. + */ + + uint8_t packing; + +#if defined(CONFIG_BT_ISO_TEST_PARAMS) || defined(__DOXYGEN__) + /* @brief Central to Peripheral flush timeout + * + * The flush timeout in multiples of ISO_Interval for each payload sent + * from the Central to Peripheral. + * + * Value range from @ref BT_ISO_FT_MIN to @ref BT_ISO_FT_MAX + */ + + uint8_t c_to_p_ft; + + /* @brief Peripheral to Central flush timeout + * + * The flush timeout in multiples of ISO_Interval for each payload sent + * from the Peripheral to Central. + * + * Value range from @ref BT_ISO_FT_MIN to @ref BT_ISO_FT_MAX. + */ + + uint8_t p_to_c_ft; + + /* @brief ISO interval + * + * Time between consecutive CIS anchor points. + * + * Value range from @ref BT_ISO_ISO_INTERVAL_MIN to @ref + * BT_ISO_ISO_INTERVAL_MAX. + */ + + uint16_t iso_interval; +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ +}; + +/** + * @brief Create unicast group. + * + * Create a new audio unicast group with one or more audio streams as a + * unicast client. All streams shall share the same framing. All streams in + * the same direction shall share the same interval and latency (see + * @ref bt_bap_qos_cfg). + * + * @param[in] param The unicast group create parameters. + * @param[out] out_unicast_group Pointer to the unicast group created. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_cap_unicast_group_create( + const struct bt_cap_unicast_group_param *param, + struct bt_cap_unicast_group **out_unicast_group); + +/** + * @brief Reconfigure unicast group. + * + * Reconfigure a unicast group with one or more audio streams as a unicast + * client. All streams shall share the same framing. All streams in the same + * direction shall share the same interval and latency (see + * @ref bt_bap_qos_cfg). + * All streams in @p param shall already belong to @p unicast_group. Use + * bt_cap_unicast_group_add_streams() to add additional streams. + * + * @param unicast_group Pointer to the unicast group created. + * @param param The unicast group reconfigure parameters. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_cap_unicast_group_reconfig(struct bt_cap_unicast_group *unicast_group, + const struct bt_cap_unicast_group_param *param); + +/** + * @brief Add streams to a unicast group as a unicast client + * + * This function can be used to add additional streams to a + * bt_cap_unicast_group. + * + * This can be called at any time before any of the streams in the group has + * been started (see bt_bap_stream_ops.started()). This can also be called + * after the streams have been stopped (see bt_bap_stream_ops.stopped()). + * + * Once a stream has been added to a unicast group, it cannot be removed. To + * remove a stream from a group, the group must be deleted with + * bt_cap_unicast_group_delete(), but this will require all streams in the + * group to be released first. + * + * @param unicast_group Pointer to the unicast group + * @param params Array of stream parameters with streams being added + * to the group. + * @param num_param Number of parameters in @p params. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_cap_unicast_group_add_streams( + struct bt_cap_unicast_group *unicast_group, + const struct bt_cap_unicast_group_stream_pair_param params[], + size_t num_param); + +/** + * @brief Delete audio unicast group. + * + * Delete a audio unicast group as a client. All streams in the group shall + * be in the idle or configured state. + * + * @param unicast_group Pointer to the unicast group to delete + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_cap_unicast_group_delete(struct bt_cap_unicast_group *unicast_group); + +/** Callback function for bt_cap_unicast_group_foreach_stream() + * + * @param stream The audio stream + * @param user_data User data + * + * @retval true Continue iterating. + * @retval false Stop iterating. + */ + +typedef bool (*bt_cap_unicast_group_foreach_stream_func_t)( + struct bt_cap_stream *stream, void *user_data); + +/** + * @brief Iterate through all streams in a unicast group + * + * @param unicast_group The unicast group + * @param func The callback function + * @param user_data User specified data that is sent to the callback + * function + * + * @retval 0 Success (even if no streams exists in the group). + * @retval -ECANCELED The @p func returned false and stopped the iteration. + * @retval -EINVAL @p unicast_group or @p func were NULL. + */ + +int bt_cap_unicast_group_foreach_stream( + struct bt_cap_unicast_group *unicast_group, + bt_cap_unicast_group_foreach_stream_func_t func, void *user_data); + +/** Structure holding information of audio stream endpoint */ + +struct bt_cap_unicast_group_info +{ + /** Pointer to the underlying Basic Audio Profile unicast group */ + + const struct bt_bap_unicast_group *unicast_group; +}; + +/** + * @brief Return structure holding information of unicast group + * + * @param unicast_group The unicast group object. + * @param info The structure object to be filled with the info. + * + * @retval 0 Success + * @retval -EINVAL @p unicast_group or @p info are NULL + */ + +int bt_cap_unicast_group_get_info( + const struct bt_cap_unicast_group *unicast_group, + struct bt_cap_unicast_group_info *info); + +/** Stream specific parameters for the bt_cap_initiator_unicast_audio_start() + * function + */ + +struct bt_cap_unicast_audio_start_stream_param +{ + /** Coordinated or ad-hoc set member. */ + + union bt_cap_set_member member; + + /** @brief Stream for the @p member */ + + struct bt_cap_stream *stream; + + /** Endpoint reference for the @p stream */ + + struct bt_bap_ep *ep; + + /* @brief Codec configuration. + * + * The @p codec_cfg.meta shall include a list of CCIDs (@ref + * BT_AUDIO_METADATA_TYPE_CCID_LIST) as well as a non-0 stream context + * (@ref BT_AUDIO_METADATA_TYPE_STREAM_CONTEXT) bitfield. + * + * This value is assigned to the @p stream, and shall remain valid while + * the stream is non-idle. + */ + + const struct bt_audio_codec_cfg *codec_cfg; +}; + +/** Parameters for the bt_cap_initiator_unicast_audio_start() function */ + +struct bt_cap_unicast_audio_start_param +{ + /** The type of the set. */ + + enum bt_cap_set_type type; + + /** The number of parameters in @p stream_params */ + + size_t count; + + /** Array of stream parameters */ + + struct bt_cap_unicast_audio_start_stream_param *stream_params; +}; + +/** Stream specific parameters for the + * bt_cap_initiator_unicast_audio_update() function + */ + +struct bt_cap_unicast_audio_update_stream_param +{ + /** Stream to update */ + + struct bt_cap_stream *stream; + + /** The length of @p meta. */ + + size_t meta_len; + + /* @brief The new metadata. + * + * The metadata shall contain a list of CCIDs as well as a non-0 context + * bitfield. + */ + + uint8_t *meta; +}; + +/** Parameters for the bt_cap_initiator_unicast_audio_update() function */ + +struct bt_cap_unicast_audio_update_param +{ + /** The type of the set. */ + + enum bt_cap_set_type type; + + /** The number of parameters in @p stream_params */ + + size_t count; + + /** Array of stream parameters */ + + struct bt_cap_unicast_audio_update_stream_param *stream_params; +}; + +/** Parameters for the bt_cap_initiator_unicast_audio_stop() function */ + +struct bt_cap_unicast_audio_stop_param +{ + /** The type of the set. */ + + enum bt_cap_set_type type; + + /** The number of streams in @p streams */ + + size_t count; + + /** Array of streams to stop */ + + struct bt_cap_stream **streams; + + /** Whether to release the streams after they have stopped */ + + bool release; +}; + +/** + * @brief Register Common Audio Profile Initiator callbacks + * + * @param cb The callback structure. Shall remain static. + * + * @return 0 on success or negative error value on failure. + */ + +int bt_cap_initiator_register_cb(const struct bt_cap_initiator_cb *cb); + +/** + * @brief Unregister Common Audio Profile Initiator callbacks + * + * @param cb The callback structure that was previously registered. + * + * @retval 0 Success + * @retval -EINVAL @p cb is NULL or @p cb was not registered + */ + +int bt_cap_initiator_unregister_cb(const struct bt_cap_initiator_cb *cb); + +/** + * @brief Setup and start unicast audio streams for a set of devices. + * + * The result of this operation is that the streams in @p param will be + * initialized and will be usable for streaming audio data. The @p + * unicast_group value can be used to update and stop the streams. + * + * @note @kconfig{CONFIG_BT_CAP_INITIATOR} and + * @kconfig{CONFIG_BT_BAP_UNICAST_CLIENT} must be enabled for this function + * to be enabled. + * + * @param param Parameters to start the audio streams. + * + * @retval 0 on success + * @retval -EBUSY if a CAP procedure is already in progress + * @retval -EINVAL if any parameter is invalid + * @retval -EALREADY All streams are already in the streaming state + */ + +int bt_cap_initiator_unicast_audio_start( + const struct bt_cap_unicast_audio_start_param *param); + +/** + * @brief Update unicast audio streams. + * + * This will update the metadata of one or more streams. + * + * @note @kconfig{CONFIG_BT_CAP_INITIATOR} and + * @kconfig{CONFIG_BT_BAP_UNICAST_CLIENT} must be enabled for this function + * to be enabled. + * + * @param param Update parameters. + * + * @retval 0 Success + * @retval -EBUSY CAP procedure is already in progress + * @retval -EINVAL @p param contains invalid parameters + * @retval -EALREADY Metadata is already set for all the streams + */ + +int bt_cap_initiator_unicast_audio_update( + const struct bt_cap_unicast_audio_update_param *param); + +/** + * @brief Stop unicast audio streams. + * + * This will stop one or more streams. + * + * @note @kconfig{CONFIG_BT_CAP_INITIATOR} and + * @kconfig{CONFIG_BT_BAP_UNICAST_CLIENT} must be enabled for this function + * to be enabled. + * + * @param param Stop parameters. + * + * @return 0 on success + * @retval -EBUSY if a CAP procedure is already in progress + * @retval -EINVAL if any parameter is invalid + * @retval -EALREADY if no state changes will occur + */ + +int bt_cap_initiator_unicast_audio_stop( + const struct bt_cap_unicast_audio_stop_param *param); + +/** + * @brief Cancel any current Common Audio Profile Initiator procedure + * + * This will stop the current procedure from continuing and making it + * possible to run a new Common Audio Profile procedure. + * + * It is recommended to do this if any existing procedure takes longer time + * than expected, which could indicate a missing response from the Common + * Audio Profile Acceptor. + * + * This does not send any requests to any Common Audio Profile Acceptors + * involved with the current procedure, and thus notifications from the + * Common Audio Profile Acceptors may arrive after this has been called. It + * is thus recommended to either only use this if a procedure has stalled, or + * wait a short while before starting any new Common Audio Profile procedure + * after this has been called to avoid getting notifications from the + * cancelled procedure. The wait time depends on the connection interval, the + * number of devices in the previous procedure and the behavior of the Common + * Audio Profile Acceptors. + * + * The respective callbacks of the procedure will be called as part of this + * with the connection pointer set to 0 and the err value set to -ECANCELED. + * + * Use bt_cap_commander_cancel() to cancel any CAP Commander procedures. Use + * bt_cap_handover_cancel() to cancel any CAP Handover procedures. + * + * @retval 0 on success + * @retval -EALREADY if no procedure is active + * @retval -EOPNOTSUPP A procedure is active, but it is not an Initiator + * procedure. + */ + +int bt_cap_initiator_unicast_audio_cancel(void); + +/** + * Parameters part of @p bt_cap_initiator_broadcast_subgroup_param for + * bt_cap_initiator_broadcast_audio_create() + */ + +struct bt_cap_initiator_broadcast_stream_param +{ + /** Audio stream */ + + struct bt_cap_stream *stream; + + /** The length of the %p data array. + * + * The BIS specific data may be omitted and this set to 0. + */ + + size_t data_len; + + /** BIS Codec Specific Configuration */ + + uint8_t *data; +}; + +/** + * Parameters part of @p bt_cap_initiator_broadcast_create_param for + * bt_cap_initiator_broadcast_audio_create() + */ + +struct bt_cap_initiator_broadcast_subgroup_param +{ + /** The number of parameters in @p stream_params */ + + size_t stream_count; + + /** Array of stream parameters */ + + struct bt_cap_initiator_broadcast_stream_param *stream_params; + + /** Subgroup Codec configuration. */ + + const struct bt_audio_codec_cfg *codec_cfg; +}; + +/** Parameters for * bt_cap_initiator_broadcast_audio_create() */ + +struct bt_cap_initiator_broadcast_create_param +{ + /** The number of parameters in @p subgroup_params */ + + size_t subgroup_count; + + /** Array of stream parameters */ + + struct bt_cap_initiator_broadcast_subgroup_param *subgroup_params; + + /** Quality of Service configuration. */ + + const struct bt_bap_qos_cfg *qos; + + /* @brief Broadcast Source packing mode. + * + * @ref BT_ISO_PACKING_SEQUENTIAL or @ref BT_ISO_PACKING_INTERLEAVED. + * + * @note This is a recommendation to the controller, which the controller + * may ignore. + */ + + uint8_t packing; + + /** Whether or not to encrypt the streams. */ + + bool encryption; + + /* @brief 16-octet broadcast code. + * + * Only valid if @p encrypt is true. + * + * If the value is a string or a the value is less than 16 octets, the + * remaining octets shall be 0. + * + * Example: The string "Broadcast Code" shall be [42 72 6F 61 64 63 61 73 + * 74 20 43 6F 64 65 00 00] + */ + + uint8_t broadcast_code[BT_ISO_BROADCAST_CODE_SIZE]; + +#if defined(CONFIG_BT_ISO_TEST_PARAMS) || defined(__DOXYGEN__) + /* @brief Immediate Repetition Count + * + * The number of times the scheduled payloads are transmitted in a given + * event. + * + * Value range from @ref BT_ISO_IRC_MIN to @ref BT_ISO_IRC_MAX. + */ + + uint8_t irc; + + /* @brief Pre-transmission offset + * + * Offset used for pre-transmissions. + * + * Value range from @ref BT_ISO_PTO_MIN to @ref BT_ISO_PTO_MAX. + */ + + uint8_t pto; + + /* @brief ISO interval + * + * Time between consecutive BIS anchor points. + * + * Value range from @ref BT_ISO_ISO_INTERVAL_MIN to @ref + * BT_ISO_ISO_INTERVAL_MAX. + */ + + uint16_t iso_interval; +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ +}; + +/** + * @brief Create a Common Audio Profile broadcast source. + * + * Create a new audio broadcast source with one or more audio streams. + * + * @note @kconfig{CONFIG_BT_CAP_INITIATOR} and + * @kconfig{CONFIG_BT_BAP_BROADCAST_SOURCE} must be enabled for this function + * to be enabled. + * + * @param[in] param Parameters to start the audio streams. + * @param[out] broadcast_source Pointer to the broadcast source created. + * + * @retval 0 Success + * @retval -EINVAL @p param is invalid or @p broadcast_source is NULL + * @retval -ENOMEM Could not allocate more broadcast sources, subgroups or + * ISO streams, or the provided codec configuration data is too large when + * merging the BIS and subgroup configuration data. + * @retval -ENOEXEC The broadcast source failed to be created for other + * reasons + */ + +int bt_cap_initiator_broadcast_audio_create( + const struct bt_cap_initiator_broadcast_create_param *param, + struct bt_cap_broadcast_source **broadcast_source); + +/** + * @brief Start Common Audio Profile broadcast source. + * + * The broadcast source will be visible for scanners once this has been + * called, and the device will advertise audio announcements. + * + * This will allow the streams in the broadcast source to send audio by + * calling bt_bap_stream_send(). + * + * @note @kconfig{CONFIG_BT_CAP_INITIATOR} and + * @kconfig{CONFIG_BT_BAP_BROADCAST_SOURCE} must be enabled for this function + * to be enabled. + * + * @param broadcast_source Pointer to the broadcast source. + * @param adv Pointer to an extended advertising set with + * periodic advertising configured. + * + * @return 0 on success or negative error value on failure. + */ + +int bt_cap_initiator_broadcast_audio_start( + struct bt_cap_broadcast_source *broadcast_source, + struct bt_le_ext_adv *adv); +/** + * @brief Update broadcast audio streams for a Common Audio Profile broadcast + * source. + * + * @note @kconfig{CONFIG_BT_CAP_INITIATOR} and + * @kconfig{CONFIG_BT_BAP_BROADCAST_SOURCE} must be enabled for this function + * to be enabled. + * + * @param broadcast_source The broadcast source to update. + * @param meta The new metadata. The metadata shall contain a + * list of CCIDs as well as a non-0 context bitfield. + * @param meta_len The length of @p meta. + * + * @return 0 on success or negative error value on failure. + */ + +int bt_cap_initiator_broadcast_audio_update( + struct bt_cap_broadcast_source *broadcast_source, const uint8_t meta[], + size_t meta_len); + +/** + * @brief Stop broadcast audio streams for a Common Audio Profile broadcast + * source. + * + * @note @kconfig{CONFIG_BT_CAP_INITIATOR} and + * @kconfig{CONFIG_BT_BAP_BROADCAST_SOURCE} must be enabled for this function + * to be enabled. + * + * @param broadcast_source The broadcast source to stop. The audio streams in + * this will be stopped and reset. + * + * @return 0 on success or negative error value on failure. + */ + +int bt_cap_initiator_broadcast_audio_stop( + struct bt_cap_broadcast_source *broadcast_source); + +/** + * @brief Delete Common Audio Profile broadcast source + * + * This can only be done after the broadcast source has been stopped by + * calling bt_cap_initiator_broadcast_audio_stop() and after the + * bt_bap_stream_ops.stopped() callback has been called for all streams in + * the broadcast source. + * + * @note @kconfig{CONFIG_BT_CAP_INITIATOR} and + * @kconfig{CONFIG_BT_BAP_BROADCAST_SOURCE} must be enabled for this function + * to be enabled. + * + * @param broadcast_source The broadcast source to delete. The @p + * broadcast_source will be invalidated. + * + * @return 0 on success or negative error value on failure. + */ + +int bt_cap_initiator_broadcast_audio_delete( + struct bt_cap_broadcast_source *broadcast_source); + +/** + * @brief Get the Broadcast Audio Stream Endpoint of a Common Audio Profile + * broadcast source + * + * This will encode the BASE of a broadcast source into a buffer, that can be + * used for advertisement. The encoded BASE will thus be encoded as + * little-endian. The BASE shall be put into the periodic advertising data + * (see bt_le_per_adv_set_data()). + * + * See table 3.15 in the Basic Audio Profile v1.0.1 for the structure. + * + * @param broadcast_source Pointer to the broadcast source. + * @param base_buf Pointer to a buffer where the BASE will be + * inserted. + * + * @return int 0 if on success, errno on error. + */ + +int bt_cap_initiator_broadcast_get_base( + struct bt_cap_broadcast_source *broadcast_source, + struct net_buf_simple *base_buf); + +/** Callback function for bt_cap_initiator_broadcast_foreach_stream() + * + * @param stream The audio stream + * @param user_data User data + * + * @retval true Continue iterating. + * @retval false Stop iterating. + */ + +typedef bool (*bt_cap_initiator_broadcast_foreach_stream_func_t)( + struct bt_cap_stream *stream, void *user_data); + +/** + * @brief Iterate through all streams in a broadcast source + * + * @param broadcast_source The broadcast source. + * @param func The callback function. + * @param user_data User specified data that is sent to the callback + * function. + * + * @retval 0 Success (even if no streams exists in the group). + * @retval -ECANCELED The @p func returned false and stopped the iteration. + * @retval -EINVAL @p broadcast_source or @p func were NULL. + */ + +int bt_cap_initiator_broadcast_foreach_stream( + struct bt_cap_broadcast_source *broadcast_source, + bt_cap_initiator_broadcast_foreach_stream_func_t func, void *user_data); + +/** Parameters for bt_cap_handover_unicast_to_broadcast() */ + +struct bt_cap_handover_unicast_to_broadcast_param +{ + /** The type of the set. */ + + enum bt_cap_set_type type; + + /** The source unicast group with the streams. */ + + struct bt_cap_unicast_group *unicast_group; + + /* @brief The advertising set to use for the broadcast source + * + * This shall remain valid until the procedure has completed. If the + * advertising set is not started at the time of calling + * bt_cap_handover_unicast_to_broadcast(), the procedure will start the + * advertising set with @ref BT_LE_EXT_ADV_START_DEFAULT. + */ + + struct bt_le_ext_adv *ext_adv; + + /** The periodic advertising interval configured for the advertising set. */ + + uint16_t pa_interval; + + /** The broadcast ID the advertising set is, or will be, using. */ + + uint32_t broadcast_id; + + /* @brief Broadcast source parameters. + * + * These parameters shall remain valid until the procedure has completed. + */ + + struct bt_cap_initiator_broadcast_create_param *broadcast_create_param; +}; + +/** Callback structure for CAP procedures */ + +struct bt_cap_handover_cb +{ + /* @brief The broadcast source has been created for handover + * + * When this is called, the broadcast source has been created and the BASE + * can be generated by bt_cap_initiator_broadcast_get_base(). To continue + * with the broadcast reception part of the procedure, the application + * shall enable extended and periodic advertising and set the BASE in the + * periodic advertising data with bt_le_per_adv_set_data(). The procedure + * will finish once the CAP acceptors have synced to the newly created + * broadcast source. + * + * @param broadcast_source Pointer to newly created broadcast source. + */ + + void (*unicast_to_broadcast_created)( + struct bt_cap_broadcast_source *broadcast_source); + + /* @brief The unicast to broadcast handover procedure has finished + * + * @param err 0 if success else a negative errno value. + * @param conn Pointer to the connection where the error + * occurred or NULL if local failure. + * @param unicast_group NULL if the unicast group was deleted during the + * procedure, else pointer to the unicast group + * provided in the parameters. + * @param broadcast_source Pointer to newly created broadcast source, or + * NULL in case of an error happening before it was + * created. + */ + + void (*unicast_to_broadcast_complete)(int err, + struct bt_conn *conn, + struct bt_cap_unicast_group *unicast_group, + struct bt_cap_broadcast_source *broadcast_source); + + /* @brief The broadcast to unicast handover procedure has finished + * + * @param err 0 if success else a negative errno value. + * @param conn Pointer to the connection where the error + * occurred or NULL if local failure. + * @param broadcast_source NULL if the broadcast sourced was deleted during + * the procedure, else pointer to the broadcast + * sourced provided in the parameters. + * @param unicast_group Pointer to newly created unicast group, or NULL + * in case of an error happening before it was + * created. + */ + + void (*broadcast_to_unicast_complete)(int err, + struct bt_conn *conn, + struct bt_cap_broadcast_source *broadcast_source, + struct bt_cap_unicast_group *unicast_group); +}; + +/** + * @brief Register Common Audio Profile Handover callbacks + * + * @param cb The callback structure. Shall remain static. + * + * @return 0 on success or negative error value on failure. + */ + +int bt_cap_handover_register_cb(const struct bt_cap_handover_cb *cb); + +/** + * @brief Unregister Common Audio Profile Handover callbacks + * + * @param cb The callback structure that was previously registered. + * + * @retval 0 Success + * @retval -EINVAL @p cb is NULL or @p cb was not registered + */ + +int bt_cap_handover_unregister_cb(const struct bt_cap_handover_cb *cb); + +/** + * @brief Hands over the sink streams in a unicast group to a broadcast + * source. + * + * All streams in the provided unicast group will be stopped and released. + * The sink streams will be transferred to a broadcast source, and the + * broadcast source information will be shared with all accepters that are + * currently receiving audio. Any stream that is not in the streaming state + * will only be released. + * + * bt_bap_broadcast_assistant_discover() must have been successfully perform + * for all members in @p param before starting this procedure. + * + * @kconfig_dep{CONFIG_BT_CAP_HANDOVER} + * + * @param param The parameters for the handover. + * + * @return 0 on success or negative error value on failure. + */ + +int bt_cap_handover_unicast_to_broadcast( + const struct bt_cap_handover_unicast_to_broadcast_param *param); + +/** Parameters for bt_cap_handover_broadcast_to_unicast() */ + +struct bt_cap_handover_broadcast_to_unicast_param +{ + /* @brief Parameters for stopping broadcast audio reception on acceptors + * + * This parameter is optional as stopping the broadcast audio should + * automatically stop broadcast audio reception on the acceptors. Omitting + * this parameter will rely on the CAP acceptors timing out on the BIG once + * it is stopped. The timeout on the CAP acceptors will be between @ref + * BT_ISO_SYNC_TIMEOUT_MIN and @ref BT_ISO_SYNC_TIMEOUT_MAX. + */ + + struct bt_cap_commander_broadcast_reception_stop_param + *reception_stop_param; + + /** @brief Broadcast ID of the @p broadcast_source + * + * Ignored if @p reception_stop_param is not NULL. + */ + + uint32_t broadcast_id; + + /** @brief Advertising set ID of the @p broadcast_source + * + * Ignored if @p reception_stop_param is not NULL. + */ + + uint8_t adv_sid; + + /** @brief Advertising type of the advertising address of @p + * broadcast_source + * + * Ignored if @p reception_stop_param is not NULL. + */ + + uint8_t adv_type; + + /* @brief The source broadcast source with the streams. + * + * The broadcast source will be stopped and deleted. + */ + + struct bt_cap_broadcast_source *broadcast_source; + + /** Parameters for the unicast group to be created */ + + struct bt_cap_unicast_group_param *unicast_group_param; + + /** Parameters for starting the unicast audio */ + + struct bt_cap_unicast_audio_start_param *unicast_start_param; +}; + +/** + * @brief Hands over the data streams in a broadcast source to a unicast + * group. + * + * The streams in the broadcast source will be stopped and the broadcast + * source will be deleted. + * + * @kconfig_dep{CONFIG_BT_CAP_HANDOVER} + * + * @param[in] param The parameters for the handover. + * + * @return 0 on success or negative error value on failure. + */ + +int bt_cap_handover_broadcast_to_unicast( + const struct bt_cap_handover_broadcast_to_unicast_param *param); + +/** + * @brief Cancel any current Common Audio Profile Handover procedure + * + * This will stop the current procedure from continuing and making it + * possible to run a new Common Audio Profile procedure. + * + * It is recommended to do this if any existing procedure takes longer time + * than expected, which could indicate a missing response from the Common + * Audio Profile Acceptor. + * + * This does not send any requests to any Common Audio Profile Acceptors + * involved with the current procedure, and thus notifications from the + * Common Audio Profile Acceptors may arrive after this has been called. It + * is thus recommended to either only use this if a procedure has stalled, or + * wait a short while before starting any new Common Audio Profile procedure + * after this has been called to avoid getting notifications from the + * cancelled procedure. The wait time depends on the connection interval, the + * number of devices in the previous procedure and the behavior of the Common + * Audio Profile Acceptors. + * + * The respective callbacks of the procedure will be called as part of this + * with the connection pointer set to 0 and the err value set to -ECANCELED. + * + * Use bt_cap_commander_cancel() to cancel any CAP Commander procedures. Use + * bt_cap_initiator_unicast_audio_cancel() to cancel any CAP Initiator + * procedures. + * + * @retval 0 on success + * @retval -EALREADY if no procedure is active + * @retval -EOPNOTSUPP A procedure is active, but it is not a Handover + * procedure. + */ + +int bt_cap_handover_cancel(void); + +/** Callback structure for CAP procedures */ + +struct bt_cap_commander_cb +{ + /* @brief Callback for bt_cap_commander_discover(). + * + * @param conn The connection pointer supplied to + * bt_cap_commander_discover(). + * @param err 0 if Common Audio Service was found else -ENODATA. + * @param member Pointer to the set member. NULL if err != 0. + * @param csis_inst The Coordinated Set Identification Service if Common + * Audio Service was found and includes a Coordinated Set + * Identification Service. NULL on error or if remote + * device does not include Coordinated Set Identification + * Service. NULL if err != 0. + */ + + void (*discovery_complete)(struct bt_conn *conn, + int err, + const struct bt_csip_set_coordinator_set_member *member, + const struct bt_csip_set_coordinator_csis_inst *csis_inst); + +#if defined(CONFIG_BT_VCP_VOL_CTLR) || defined(__DOXYGEN__) + /* @brief Callback for bt_cap_commander_change_volume(). + * + * @param conn Pointer to the connection where the error occurred. NULL if + * @p err is 0 or if cancelled by bt_cap_commander_cancel() + * @param err 0 on success, BT_GATT_ERR() with a specific ATT + * (BT_ATT_ERR_*) error code or -ECANCELED if cancelled by + * bt_cap_commander_cancel(). + */ + + void (*volume_changed)(struct bt_conn *conn, int err); + + /* @brief Callback for bt_cap_commander_change_volume_mute_state(). + * + * @param conn Pointer to the connection where the error occurred. NULL if + * @p err is 0 or if cancelled by bt_cap_commander_cancel() + * @param err 0 on success, BT_GATT_ERR() with a specific ATT + * (BT_ATT_ERR_*) error code or -ECANCELED if cancelled by + * bt_cap_commander_cancel(). + */ + + void (*volume_mute_changed)(struct bt_conn *conn, int err); + +#if defined(CONFIG_BT_VCP_VOL_CTLR_VOCS) || defined(__DOXYGEN__) + /* @brief Callback for bt_cap_commander_change_volume_offset(). + * + * @param conn Pointer to the connection where the error occurred. NULL if + * @p err is 0 or if cancelled by bt_cap_commander_cancel() + * @param err 0 on success, BT_GATT_ERR() with a specific ATT + * (BT_ATT_ERR_*) error code or -ECANCELED if cancelled by + * bt_cap_commander_cancel(). + */ + + void (*volume_offset_changed)(struct bt_conn *conn, int err); +#endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS */ +#endif /* CONFIG_BT_VCP_VOL_CTLR */ +#if defined(CONFIG_BT_MICP_MIC_CTLR) || defined(__DOXYGEN__) + /* @brief Callback for bt_cap_commander_change_microphone_mute_state(). + * + * @param conn Pointer to the connection where the error occurred. NULL if + * @p err is 0 or if cancelled by bt_cap_commander_cancel() + * @param err 0 on success, BT_GATT_ERR() with a specific ATT + * (BT_ATT_ERR_*) error code or -ECANCELED if cancelled by + * bt_cap_commander_cancel(). + */ + + void (*microphone_mute_changed)(struct bt_conn *conn, int err); +#if defined(CONFIG_BT_MICP_MIC_CTLR_AICS) || defined(__DOXYGEN__) + /* @brief Callback for bt_cap_commander_change_microphone_gain_setting(). + * + * @param conn Pointer to the connection where the error occurred. NULL if + * @p err is 0 or if cancelled by bt_cap_commander_cancel() + * @param err 0 on success, BT_GATT_ERR() with a specific ATT + * (BT_ATT_ERR_*) error code or -ECANCELED if cancelled by + * bt_cap_commander_cancel(). + */ + + void (*microphone_gain_changed)(struct bt_conn *conn, int err); +#endif /* CONFIG_BT_MICP_MIC_CTLR_AICS */ +#endif /* CONFIG_BT_MICP_MIC_CTLR */ + +#if defined(CONFIG_BT_BAP_BROADCAST_ASSISTANT) || defined(__DOXYGEN__) + /* @brief Callback for bt_cap_commander_broadcast_reception_start(). + * + * @param conn Pointer to the connection where the error occurred. NULL if + * @p err is 0 or if cancelled by bt_cap_commander_cancel() + * @param err 0 on success, BT_GATT_ERR() with a specific ATT + * (BT_ATT_ERR_*) error code or -ECANCELED if cancelled by + * bt_cap_commander_cancel(). + */ + + void (*broadcast_reception_start)(struct bt_conn *conn, int err); + /* @brief Callback for bt_cap_commander_broadcast_reception_stop(). + * + * @param conn Pointer to the connection where the error occurred. NULL if + * @p err is 0 or if cancelled by bt_cap_commander_cancel() + * @param err 0 on success, BT_GATT_ERR() with a specific ATT + * (BT_ATT_ERR_*) error code or -ECANCELED if cancelled by + * bt_cap_commander_cancel(). + */ + + void (*broadcast_reception_stop)(struct bt_conn *conn, int err); + /* @brief Callback for bt_cap_commander_distribute_broadcast_code(). + * + * @param conn Pointer to the connection where the error occurred. NULL if + * @p err is 0 or if cancelled by bt_cap_commander_cancel() + * @param err 0 on success, BT_GATT_ERR() with a specific ATT + * (BT_ATT_ERR_*) error code or -ECANCELED if cancelled by + * bt_cap_commander_cancel(). + */ + + void (*distribute_broadcast_code)(struct bt_conn *conn, int err); +#endif /* CONFIG_BT_BAP_BROADCAST_ASSISTANT */ +}; + +/** + * @brief Register Common Audio Profile Commander callbacks + * + * @param cb The callback structure. Shall remain static. + * + * @retval 0 Success + * @retval -EINVAL @p cb is NULL + * @retval -EALREADY Callbacks are already registered + */ + +int bt_cap_commander_register_cb(const struct bt_cap_commander_cb *cb); + +/** + * @brief Unregister Common Audio Profile Commander callbacks + * + * @param cb The callback structure that was previously registered. + * + * @retval 0 Success + * @retval -EINVAL @p cb is NULL or @p cb was not registered + */ + +int bt_cap_commander_unregister_cb(const struct bt_cap_commander_cb *cb); + +/** + * @brief Discovers audio support on a remote device. + * + * This will discover the Common Audio Service (CAS) on the remote device, to + * verify if the remote device supports the Common Audio Profile. + * + * @note @kconfig{CONFIG_BT_CAP_COMMANDER} must be enabled for this function. + * If @kconfig{CONFIG_BT_CAP_INITIATOR} is also enabled, it does not matter + * if bt_cap_commander_discover() or bt_cap_initiator_unicast_discover() is + * used. + * + * @param conn Connection to a remote server. + * + * @retval 0 Success + * @retval -EINVAL @p conn is NULL + * @retval -ENOTCONN @p conn is not connected + * @retval -ENOMEM Could not allocated memory for the request + * @retval -EBUSY Already doing discovery for @p conn + */ + +int bt_cap_commander_discover(struct bt_conn *conn); + +/** + * @brief Cancel any current Common Audio Profile commander procedure + * + * This will stop the current procedure from continuing and making it + * possible to run a new Common Audio Profile procedure. + * + * It is recommended to do this if any existing procedure takes longer time + * than expected, which could indicate a missing response from the Common + * Audio Profile Acceptor. + * + * This does not send any requests to any Common Audio Profile Acceptors + * involved with the current procedure, and thus notifications from the + * Common Audio Profile Acceptors may arrive after this has been called. It + * is thus recommended to either only use this if a procedure has stalled, or + * wait a short while before starting any new Common Audio Profile procedure + * after this has been called to avoid getting notifications from the + * cancelled procedure. The wait time depends on the connection interval, the + * number of devices in the previous procedure and the behavior of the Common + * Audio Profile Acceptors. + * + * The respective callbacks of the procedure will be called as part of this + * with the connection pointer set to NULL and the err value set to + * -ECANCELED. + * + * Use bt_cap_initiator_unicast_audio_cancel() to cancel any CAP Initiator + * procedures. Use bt_cap_handover_cancel() to cancel any CAP Handover + * procedures. + * + * @retval 0 on success + * @retval -EALREADY if no procedure is active + * @retval -EOPNOTSUPP A procedure is active, but it is not a Commander + * procedure. + */ + +int bt_cap_commander_cancel(void); + +/** + * Parameters part of @ref bt_cap_commander_broadcast_reception_start_param + * for bt_cap_commander_broadcast_reception_start() + */ + +struct bt_cap_commander_broadcast_reception_start_member_param +{ + /** Coordinated or ad-hoc set member. */ + + union bt_cap_set_member member; + + /** Address of the advertiser. */ + + bt_addr_le_t addr; + + /** SID of the advertising set. */ + + uint8_t adv_sid; + + /* @brief Periodic advertising interval in milliseconds. + * + * BT_BAP_PA_INTERVAL_UNKNOWN if unknown. + */ + + uint16_t pa_interval; + + /** 24-bit broadcast ID */ + + uint32_t broadcast_id; + + /* @brief Pointer to array of subgroups + * + * At least one bit in one of the subgroups bis_sync parameters shall be + * set. + */ + + struct bt_bap_bass_subgroup subgroups[BT_BAP_BASS_MAX_SUBGROUPS]; + + /** Number of subgroups */ + + size_t num_subgroups; +}; + +/** Parameters for starting broadcast reception */ + +struct bt_cap_commander_broadcast_reception_start_param +{ + /** The type of the set. */ + + enum bt_cap_set_type type; + + /** The set of devices for this procedure */ + + struct bt_cap_commander_broadcast_reception_start_member_param *param; + + /** The number of parameters in @p param */ + + size_t count; +}; + +/** + * @brief Starts the reception of broadcast audio on one or more remote + * Common Audio Profile Acceptors + * + * @param param The parameters to start the broadcast audio + * + * @return 0 on success or negative error value on failure. + */ + +int bt_cap_commander_broadcast_reception_start( + const struct bt_cap_commander_broadcast_reception_start_param *param); + +/** Member parameters for stopping broadcast reception */ + +struct bt_cap_commander_broadcast_reception_stop_member_param +{ + /** Coordinated or ad-hoc set member. */ + + union bt_cap_set_member member; + + /** Source ID of the receive state. */ + + uint8_t src_id; + + /** Number of subgroups */ + + size_t num_subgroups; +}; + +/** Parameters for stopping broadcast reception */ + +struct bt_cap_commander_broadcast_reception_stop_param +{ + /** The type of the set. */ + + enum bt_cap_set_type type; + + /** The set of devices for this procedure */ + + struct bt_cap_commander_broadcast_reception_stop_member_param *param; + + /** The number of parameters in @p param */ + + size_t count; +}; + +/** + * @brief Stops the reception of broadcast audio on one or more remote Common + * Audio Profile Acceptors + * + * @param param The parameters to stop the broadcast audio + * + * @return 0 on success or negative error value on failure. + */ + +int bt_cap_commander_broadcast_reception_stop( + const struct bt_cap_commander_broadcast_reception_stop_param *param); + +/** Member parameters for distributing broadcast code */ + +struct bt_cap_commander_distribute_broadcast_code_member_param +{ + /** Coordinated or ad-hoc set member. */ + + union bt_cap_set_member member; + + /** Source ID of the receive state. */ + + uint8_t src_id; +}; + +/** Parameters for distributing broadcast code */ + +struct bt_cap_commander_distribute_broadcast_code_param +{ + /** The type of the set. */ + + enum bt_cap_set_type type; + + /** The set of devices for this procedure */ + + struct bt_cap_commander_distribute_broadcast_code_member_param *param; + + /** The number of parameters in @p param */ + + size_t count; + + /* @brief 16-octet broadcast code. + * + * If the value is a string or a the value is less than 16 octets, the + * remaining octets shall be 0. + * + * Example: The string "Broadcast Code" shall be [42 72 6F 61 64 63 61 73 + * 74 20 43 6F 64 65 00 00] + */ + + uint8_t broadcast_code[BT_ISO_BROADCAST_CODE_SIZE]; +}; + +/** + * @brief Distributes the broadcast code on one or more remote Common Audio + * Profile Acceptors + * + * @param param The parameters for distributing the broadcast code + * + * @return 0 on success or negative error value on failure. + */ + +int bt_cap_commander_distribute_broadcast_code( + const struct bt_cap_commander_distribute_broadcast_code_param *param); + +/** Parameters for changing absolute volume */ + +struct bt_cap_commander_change_volume_param +{ + /** The type of the set. */ + + enum bt_cap_set_type type; + + /** Coordinated or ad-hoc set member. */ + + union bt_cap_set_member *members; + + /** The number of members in @p members */ + + size_t count; + + /** The absolute volume to set */ + + uint8_t volume; +}; + +/** + * @brief Change the volume on one or more Common Audio Profile Acceptors + * + * @param param The parameters for the volume change + * + * @return 0 on success or negative error value on failure. + */ + +int bt_cap_commander_change_volume( + const struct bt_cap_commander_change_volume_param *param); + +/** + * Parameters part of @ref bt_cap_commander_change_volume_offset_param for + * bt_cap_commander_change_volume_offset() + */ + +struct bt_cap_commander_change_volume_offset_member_param +{ + /** Coordinated or ad-hoc set member. */ + + union bt_cap_set_member member; + + /* @brief The offset to set + * + * Value shall be between @ref BT_VOCS_MIN_OFFSET and @ref + * BT_VOCS_MAX_OFFSET + */ + + int16_t offset; +}; + +/** Parameters for changing volume offset */ + +struct bt_cap_commander_change_volume_offset_param +{ + /** The type of the set. */ + + enum bt_cap_set_type type; + + /** The set of devices for this procedure */ + + struct bt_cap_commander_change_volume_offset_member_param *param; + + /** The number of parameters in @p param */ + + size_t count; +}; + +/** + * @brief Change the volume offset on one or more Common Audio Profile + * Acceptors + * + * @param param The parameters for the volume offset change + * + * @return 0 on success or negative error value on failure. + */ + +int bt_cap_commander_change_volume_offset( + const struct bt_cap_commander_change_volume_offset_param *param); + +/** Parameters for changing volume mute state */ + +struct bt_cap_commander_change_volume_mute_state_param +{ + /** The type of the set. */ + + enum bt_cap_set_type type; + + /** Coordinated or ad-hoc set member. */ + + union bt_cap_set_member *members; + + /** The number of members in @p members */ + + size_t count; + + /* @brief The volume mute state to set + * + * true to mute, and false to unmute + */ + + bool mute; +}; + +/** + * @brief Change the volume mute state on one or more Common Audio Profile + * Acceptors + * + * @param param The parameters for the volume mute state change + * + * @return 0 on success or negative error value on failure. + */ + +int bt_cap_commander_change_volume_mute_state( + const struct bt_cap_commander_change_volume_mute_state_param *param); + +/** Parameters for changing microphone mute state */ + +struct bt_cap_commander_change_microphone_mute_state_param +{ + /** The type of the set. */ + + enum bt_cap_set_type type; + + /** Coordinated or ad-hoc set member. */ + + union bt_cap_set_member *members; + + /** The number of members in @p members */ + + size_t count; + + /* @brief The microphone mute state to set + * + * true to mute, and false to unmute + */ + + bool mute; +}; + +/** + * @brief Change the microphone mute state on one or more Common Audio + * Profile Acceptors + * + * @param param The parameters for the microphone mute state change + * + * @return 0 on success or negative error value on failure. + */ + +int bt_cap_commander_change_microphone_mute_state( + const struct bt_cap_commander_change_microphone_mute_state_param *param); + +/** + * Parameters part of @ref + * bt_cap_commander_change_microphone_gain_setting_param for + * bt_cap_commander_change_microphone_gain_setting() + */ + +struct bt_cap_commander_change_microphone_gain_setting_member_param +{ + /** Coordinated or ad-hoc set member. */ + + union bt_cap_set_member member; + + /** @brief The microphone gain setting to set */ + + int8_t gain; +}; + +/** Parameters for changing microphone mute state */ + +struct bt_cap_commander_change_microphone_gain_setting_param +{ + /** The type of the set. */ + + enum bt_cap_set_type type; + + /** The set of devices for this procedure */ + + struct bt_cap_commander_change_microphone_gain_setting_member_param *param; + + /** The number of parameters in @p param */ + + size_t count; +}; + +/** + * @brief Change the microphone gain setting on one or more Common Audio + * Profile Acceptors + * + * @param param The parameters for the microphone gain setting change + * + * @return 0 on success or negative error value on failure. + */ + +int bt_cap_commander_change_microphone_gain_setting( + const struct bt_cap_commander_change_microphone_gain_setting_param *param); +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_CAP_H */ diff --git a/include/nuttx/net/bluetooth/audio/ccid.h b/include/nuttx/net/bluetooth/audio/ccid.h new file mode 100644 index 0000000000000..e58dccfa97876 --- /dev/null +++ b/include/nuttx/net/bluetooth/audio/ccid.h @@ -0,0 +1,91 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/audio/ccid.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Header for Bluetooth Audio Content Control Identifier. + * + * Copyright (c) 2020 Bose Corporation Copyright (c) 2021-2024 Nordic + * Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_CCID_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_CCID_H + +/** + * @brief Bluetooth Content Control Identifier (CCID) + * @defgroup bt_ccid Bluetooth Content Control Identifier + * + * @since 3.7 + * @version 0.8.0 + * + * @ingroup bluetooth + * @{ + * + * The Content Control Identifier (CCID) API manages CCIDs for @ref + * BT_UUID_CCID characteristics. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Minimum CCID value */ +#define BT_CCID_MIN 0U +/** Maximum CCID value */ +#define BT_CCID_MAX 255U + +/** + * @brief Allocates a CCID value. + * + * This should always be called right before registering a GATT service that + * contains a @ref BT_UUID_CCID characteristic. Allocating a CCID without + * registering the characteristic may (in very rare cases) result in + * duplicated CCIDs on the device. + * + * Requires that @kconfig{CONFIG_BT_CONN} is enabled. + * + * @retval ccid 8-bit unsigned CCID value on success + * @retval -ENOMEM No more CCIDs can be allocated + */ + +int bt_ccid_alloc_value(void); + +/** + * @brief Get the GATT attribute of a CCID value + * + * Searches the current GATT database for a CCID characteristic that has the + * supplied CCID value. + * + * Requires that @kconfig{CONFIG_BT_CONN} is enabled. + * + * @param ccid The CCID to search for + * + * @retval NULL None was found + * @retval attr Pointer to a GATT attribute + */ + +const struct bt_gatt_attr *bt_ccid_find_attr(uint8_t ccid); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_CCID_H */ diff --git a/include/nuttx/net/bluetooth/audio/ccp.h b/include/nuttx/net/bluetooth/audio/ccp.h new file mode 100644 index 0000000000000..63deadc046384 --- /dev/null +++ b/include/nuttx/net/bluetooth/audio/ccp.h @@ -0,0 +1,597 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/audio/ccp.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Bluetooth Call Control Profile (CCP) APIs. + */ + +/* Copyright (c) 2024-2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_CCP_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_CCP_H + +/** + * @brief Call Control Profile (CCP) + * + * @defgroup bt_ccp Call Control Profile (CCP) + * + * @since 3.7 + * @version 0.1.0 + * + * @ingroup bluetooth + * @{ + * + * Call Control Profile (CCP) provides procedures to initiate and control + * calls. It provides the Call Control Client and the Call Control Server + * roles, where the former is usually placed on resource constrained devices + * like headphones, and the latter placed on more powerful devices like + * phones and PCs. + * + * The profile is not limited to carrier phone calls and can be used with + * common applications like Discord and Teams. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif +/** + * @defgroup bt_ccp_call_control_server CCP Call Control Server APIs + * @ingroup bt_ccp + * @{ + */ + +/** + * @struct bt_ccp_call_control_server_bearer + * @brief Abstract Call Control Server Telephone Bearer structure. + */ + +struct bt_ccp_call_control_server_bearer; + +/** + * @brief Register a Telephone Bearer + * + * This will register a Telephone Bearer Service (TBS) (or a Generic + * Telephone Bearer service (GTBS)) with the provided parameters. + * + * As per the TBS specification, the GTBS shall be instantiated for the + * feature, and as such a GTBS shall always be registered before any TBS can + * be registered. Similarly, all TBS shall be unregistered before the GTBS + * can be unregistered with bt_ccp_call_control_server_unregister_bearer(). + * + * @param[in] param The parameters to initialize the bearer. + * @param[out] bearer Pointer to the initialized bearer. + * + * @retval 0 Success + * @retval -EINVAL @p param contains invalid data + * @retval -EALREADY @p param.gtbs is true and GTBS has already been + * registered + * @retval -EAGAIN @p param.gtbs is false and GTBS has not been registered + * @retval -ENOMEM @p param.gtbs is false and no more TBS can be registered + * (see @kconfig{CONFIG_BT_TBS_BEARER_COUNT}) or @p param.uri_schemes is + * larger than + * + * @kconfig{CONFIG_BT_CCP_CALL_CONTROL_SERVER_URI_SCHEMES_MAX_LENGTH} + * @retval -ENOEXEC The service failed to be registered + */ + +int bt_ccp_call_control_server_register_bearer( + const struct bt_tbs_register_param *param, + struct bt_ccp_call_control_server_bearer **bearer); + +/** + * @brief Unregister a Telephone Bearer + * + * This will unregister a Telephone Bearer Service (TBS) (or a Generic + * Telephone Bearer service (GTBS)) with the provided parameters. The bearer + * shall be registered first by bt_ccp_call_control_server_register_bearer() + * before it can be unregistered. + * + * All TBS shall be unregistered before the GTBS can be unregistered with. + * + * @param bearer The bearer to unregister. + * + * @retval 0 Success + * @retval -EINVAL @p bearer is NULL + * @retval -EALREADY The bearer is not registered + * @retval -ENOEXEC The service failed to be unregistered + */ + +int bt_ccp_call_control_server_unregister_bearer( + struct bt_ccp_call_control_server_bearer *bearer); + +/** + * @brief Set a new bearer provider name. + * + * @param bearer The bearer to set the name for. + * @param name The new bearer provider name. + * + * @retval 0 Success + * @retval -EINVAL @p bearer or @p name is NULL, or @p name is the empty + * string or @p name is larger than + * @kconfig{CONFIG_BT_CCP_CALL_CONTROL_SERVER_PROVIDER_NAME_MAX_LENGTH} + * @retval -EFAULT @p bearer is not registered + * @retval -EBUSY The TBS instance of @p bearer is busy + * @retval -ENOEXEC The TBS instance of @p bearer returned unexpected error + */ + +int bt_ccp_call_control_server_set_bearer_provider_name( + struct bt_ccp_call_control_server_bearer *bearer, const char *name); + +/** + * @brief Get the bearer provider name. + * + * @param[in] bearer The bearer to get the name for. + * @param[out] name Pointer a buffer that will be populated with the + * bearer provider name. + * @param name_size The size of the @p name buffer. The suggested size is + * + * @kconfig{CONFIG_BT_CCP_CALL_CONTROL_SERVER_PROVIDER_NAME_MAX_LENGTH} + * + 1 to + * ensure that the name always fits. + * + * @retval 0 Success + * @retval -EINVAL @p bearer or @p name is NULL + * @retval -EFAULT @p bearer is not registered + * @retval -ENOMEM @p name_size is insufficient to hold the bearer name + * (including null terminator) + */ + +int bt_ccp_call_control_server_get_bearer_provider_name( + struct bt_ccp_call_control_server_bearer *bearer, char *name, + size_t name_size); + +/** + * @brief Get the bearer UCI. + * + * @param[in] bearer The bearer to get the UCI for. + * @param[out] uci Pointer to a buffer of size @ref BT_TBS_MAX_UCI_SIZE + * that the bearer UCI will be written to. + * + * @retval 0 Success + * @retval -EINVAL @p bearer or @p uci is NULL + * @retval -EFAULT @p bearer is not registered + */ + +int bt_ccp_call_control_server_get_bearer_uci( + struct bt_ccp_call_control_server_bearer *bearer, + char uci[BT_TBS_MAX_UCI_SIZE]); + +/** + * @brief Set a new bearer technology. + * + * @param[out] bearer The bearer to set the name for. + * @param tech The new bearer technology. + * + * @retval 0 Success + * @retval -EINVAL @p bearer or is NULL or @p tech is invalid. + * @retval -EFAULT @p bearer is not registered. + */ + +int bt_ccp_call_control_server_set_bearer_tech( + struct bt_ccp_call_control_server_bearer *bearer, + enum bt_bearer_tech tech); + +/** + * @brief Get the bearer technology. + * + * @param[in] bearer The bearer to get the technology for. + * @param[out] tech Pointer that will be updated to be the bearer + * technology. + * + * @retval 0 Success. + * @retval -EINVAL @p bearer or @p tech is NULL. + * @retval -EFAULT @p bearer is not registered. + */ + +int bt_ccp_call_control_server_get_bearer_tech( + const struct bt_ccp_call_control_server_bearer *bearer, + enum bt_bearer_tech *tech); + +/** + * @brief Set a new bearer URI schemes supported list. + * + * @param bearer The bearer to set the URI schemes supported list for. + * @param uri_schemes The new bearer URI schemes supported list. + * + * @retval 0 New URI schemes supported list set, or if there were no change. + * @retval -EINVAL @p bearer or @p uri_schemes is NULL, @p uri_schemes is the + * empty string or @p uri_schemes contains invalid characters for URI schemes + * @retval -EFAULT @p bearer is not registered + * @retval -ENOMEM @p uri_schemes is larger than + * + * @kconfig{CONFIG_BT_CCP_CALL_CONTROL_SERVER_URI_SCHEMES_MAX_LENGTH} + */ + +int bt_ccp_call_control_server_set_bearer_uri_schemes( + struct bt_ccp_call_control_server_bearer *bearer, const char *uri_schemes); + +/** + * @brief Get the bearer URI schemes supported list. + * + * @param[in] bearer The bearer to get the URI schemes supported list + * for. + * @param[out] uri_schemes Pointer that will be updated to be the bearer URI + * schemes supported list. + * @param uri_schemes_size The size of the @p uri_schemes buffer. The + * suggested size is + * + * @kconfig{CONFIG_BT_CCP_CALL_CONTROL_SERVER_URI_SCHEMES_MAX_LENGTH} + * + 1 + * to ensure that the URI schemes supported list always fits. + * + * @retval 0 Success + * @retval -EINVAL @p bearer or @p uri_schemes is NULL + * @retval -EFAULT @p bearer is not registered + * @retval -ENOMEM @p uri_schemes_size is insufficient to hold the bearer URI + * schemes supported list (including null terminator) + */ + +int bt_ccp_call_control_server_get_bearer_uri_schemes( + struct bt_ccp_call_control_server_bearer *bearer, char *uri_schemes, + size_t uri_schemes_size); +/** @} */ /* End of group bt_ccp_call_control_server */ + +/** + * @defgroup bt_ccp_call_control_client CCP Call Control Client APIs + * @ingroup bt_ccp + * @{ + */ + +/** + * @struct bt_ccp_call_control_client + * @brief Abstract Call Control Client structure. + */ + +struct bt_ccp_call_control_client; + +/** + * @struct bt_ccp_call_control_client_bearer + * @brief Abstract Call Control Client bearer structure. + */ + +struct bt_ccp_call_control_client_bearer; + +/** Struct with information about bearers of a client */ + +struct bt_ccp_call_control_client_bearers +{ +#if defined(CONFIG_BT_TBS_CLIENT_GTBS) || defined(__DOXYGEN__) + /** The GTBS bearer. */ + + struct bt_ccp_call_control_client_bearer *gtbs_bearer; +#endif /* CONFIG_BT_TBS_CLIENT_GTBS */ + +#if defined(CONFIG_BT_TBS_CLIENT_TBS) || defined(__DOXYGEN__) + /** Number of TBS bearers in @p tbs_bearers */ + + size_t tbs_count; + + /** Array of pointers of TBS bearers */ + + struct bt_ccp_call_control_client_bearer + *tbs_bearers[CONFIG_BT_CCP_CALL_CONTROL_CLIENT_BEARER_COUNT]; +#endif /* CONFIG_BT_TBS_CLIENT_TBS */ +}; + +/** + * @brief Struct to hold the Telephone Bearer Service client callbacks + * + * These can be registered for usage with bt_tbs_client_register_cb(). + */ + +struct bt_ccp_call_control_client_cb +{ + /* @brief Callback function for bt_ccp_call_control_client_discover(). + * + * This callback is called once the discovery procedure is completed. + * + * @param client Call Control Client pointer. + * @param err Error value. 0 on success, GATT error on positive + * value or errno on negative value. + * @param bearers The bearers found. Value must be copied if used + * after return. + * @param user_data User data stored in the callback struct. Will always + * be NULL if + * + * @kconfig{CONFIG_BT_CCP_CALL_CONTROL_CLIENT_CB_USER_DATA} is not enabled. + */ + + void (*discover)(struct bt_ccp_call_control_client *client, + int err, + struct bt_ccp_call_control_client_bearers *bearers, + void *user_data); + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) || defined(__DOXYGEN__) + /* @brief Callback function for + * bt_ccp_call_control_client_read_bearer_provider_name(). + * + * This callback is called once the read bearer provider name procedure is + * completed. + * + * @param bearer Call Control Client bearer pointer. + * @param err Error value. 0 on success, GATT error on positive + * value or errno on negative value. + * @param name The bearer provider name. NULL if @p err is not 0. + * Value must be copied if used after return. + * @param user_data User data stored in the callback struct. Will always + * be NULL if + * + * @kconfig{CONFIG_BT_CCP_CALL_CONTROL_CLIENT_CB_USER_DATA} is not enabled. + */ + + void (*bearer_provider_name)( + struct bt_ccp_call_control_client_bearer *bearer, + int err, + const char *name, + void *user_data); +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) || defined(__DOXYGEN__) + /* @brief Callback function for + * bt_ccp_call_control_client_read_bearer_uci(). + * + * This callback is called once the read bearer UCI procedure is completed. + * + * @param bearer Call Control Client bearer pointer. + * @param err Error value. 0 on success, GATT error on positive + * value or errno on negative value. + * @param uci The UCI of the bearer. NULL if @p err is not 0. + * @param user_data User data stored in the callback struct. Will always + * be NULL if + * + * @kconfig{CONFIG_BT_CCP_CALL_CONTROL_CLIENT_CB_USER_DATA} is not enabled. + */ + + void (*bearer_uci)(struct bt_ccp_call_control_client_bearer *bearer, + int err, + const char *uci, + void *user_data); +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_UCI */ + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) || defined(__DOXYGEN__) + /* @brief Callback function for + * bt_ccp_call_control_client_read_bearer_tech(). + * + * This callback is called once the read bearer technology procedure is + * completed. + * + * @param bearer Call Control Client bearer pointer. + * @param err Error value. 0 on success, GATT error on positive + * value or errno on negative value. + * @param tech The technology of the bearer. The value may be outside + * the values of the enum. + * @param user_data User data stored in the callback struct. Will always + * be NULL if + * + * @kconfig{CONFIG_BT_CCP_CALL_CONTROL_CLIENT_CB_USER_DATA} is not enabled. + */ + + void (*bearer_tech)(struct bt_ccp_call_control_client_bearer *bearer, + int err, + enum bt_bearer_tech tech, + void *user_data); +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY */ + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST) || defined(__DOXYGEN__) + /* @brief Callback function for + * bt_ccp_call_control_client_read_bearer_uri_schemes(). + * + * This callback is called once the read bearer URI schemes supported list + * procedure is completed. + * + * @param bearer Call Control Client bearer pointer. + * @param err Error value. 0 on success, GATT error on positive + * value or errno on negative value. + * @param uri_schemes The URI schemes supported list of the bearer if @p + * err is 0. Value must be copied if used after return. + * @param user_data User data stored in the callback struct. Will always + * be NULL if + * + * @kconfig{CONFIG_BT_CCP_CALL_CONTROL_CLIENT_CB_USER_DATA} is not enabled. + */ + + void (*bearer_uri_schemes)( + struct bt_ccp_call_control_client_bearer *bearer, + int err, + const char *uri_schemes, + void *user_data); +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST */ + +#if defined(CONFIG_BT_CCP_CALL_CONTROL_CLIENT_CB_USER_DATA) || defined(__DOXYGEN__) + /** User data that will be supplied to all callbacks */ + + void *user_data; +#endif /* CONFIG_BT_CCP_CALL_CONTROL_CLIENT_CB_USER_DATA */ + + /** @cond INTERNAL_HIDDEN */ + + /** Internally used field for list handling */ + + struct list_node _node; + + /** @endcond */ +}; + +/** + * @brief Discovers the Telephone Bearer Service (TBS) support on a remote + * device. + * + * This will discover the Telephone Bearer Service (TBS) and Generic + * Telephone Bearer Service (GTBS) on the remote device. + * + * @kconfig_dep{CONFIG_BT_CCP_CALL_CONTROL_CLIENT}. + * + * @param conn Connection to a remote server. + * @param out_client Pointer to client instance on success + * + * @retval 0 Success + * @retval -EINVAL @p conn or @p out_client is NULL + * @retval -ENOTCONN @p conn is not connected + * @retval -ENOMEM Could not allocated memory for the request + * @retval -EBUSY Already doing discovery for @p conn + * @retval -ENOEXEC The underlying TBS client returned an unexpected error + */ + +int bt_ccp_call_control_client_discover(struct bt_conn *conn, + struct bt_ccp_call_control_client **out_client); + +/** + * @brief Register callbacks for the Call Control Client + * + * @param cb The callback struct + * + * @retval 0 Success + * @retval -EINVAL @p cb is NULL + * @retval -EEXISTS @p cb is already registered + */ + +int bt_ccp_call_control_client_register_cb( + struct bt_ccp_call_control_client_cb *cb); + +/** + * @brief Unregister callbacks for the Call Control Client + * + * @param cb The callback struct + * + * @retval 0 Success + * @retval -EINVAL @p cb is NULL + * @retval -EALREADY @p cb is not registered + */ + +int bt_ccp_call_control_client_unregister_cb( + struct bt_ccp_call_control_client_cb *cb); + +/** + * @brief Get the bearers of a client instance + * + * @param[in] client The client to get the bearers of. + * @param[out] bearers The bearers struct that will be populated with the + * bearers of @p client. + + * @retval 0 Success + * @retval -EINVAL @p client or @p bearers is NULL + */ + +int bt_ccp_call_control_client_get_bearers( + struct bt_ccp_call_control_client *client, + struct bt_ccp_call_control_client_bearers *bearers); + +/** + * @brief Read the bearer provider name of a remote TBS bearer. + * + * @kconfig_dep{CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME} + * + * @param bearer The bearer to read the name from + * + * @retval 0 Success + * @retval -EINVAL @p bearer is NULL + * @retval -EFAULT @p bearer has not been discovered + * @retval -EEXIST A @ref bt_ccp_call_control_client could not be identified + * for @p bearer + * @retval -EBUSY The @ref bt_ccp_call_control_client identified by @p bearer + * is busy, or the TBS instance of @p bearer is busy. + * @retval -ENOTCONN The @ref bt_ccp_call_control_client identified by @p + * bearer is not connected + * @retval -ENOEXEC The underlying TBS client returned an unexpected error + */ + +int bt_ccp_call_control_client_read_bearer_provider_name( + struct bt_ccp_call_control_client_bearer *bearer); + +/** + * @brief Read the bearer Uniform Caller Identifier (UCI) of a remote TBS + * bearer. + * + * @kconfig_dep{CONFIG_BT_TBS_CLIENT_BEARER_UCI} + * + * @param bearer The bearer to read the UCI from + * + * @retval 0 Success + * @retval -EINVAL @p bearer is NULL + * @retval -EFAULT @p bearer has not been discovered + * @retval -EEXIST A @ref bt_ccp_call_control_client could not be identified + * for @p bearer + * @retval -EBUSY The @ref bt_ccp_call_control_client identified by @p bearer + * is busy, or the TBS instance of @p bearer is busy. + * @retval -ENOTCONN The @ref bt_ccp_call_control_client identified by @p + * bearer is not connected + * @retval -ENOEXEC The underlying TBS client returned an unexpected error + */ + +int bt_ccp_call_control_client_read_bearer_uci( + struct bt_ccp_call_control_client_bearer *bearer); + +/** + * @brief Read the bearer technology of a remote TBS bearer. + * + * @kconfig_dep{CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY} + * + * @param bearer The bearer to read the technology from + * + * @retval 0 Success. + * @retval -EINVAL @p bearer is NULL. + * @retval -EFAULT @p bearer has not been discovered. + * @retval -EEXIST A @ref bt_ccp_call_control_client could not be identified + * for @p bearer. + * @retval -EBUSY The @ref bt_ccp_call_control_client identified by @p bearer + * is busy, or the TBS instance of @p bearer is busy. + * @retval -ENOTCONN The @ref bt_ccp_call_control_client identified by @p + * bearer is not connected. + * @retval -ENOEXEC The underlying TBS client returned an unexpected error. + */ + +int bt_ccp_call_control_client_read_bearer_tech( + struct bt_ccp_call_control_client_bearer *bearer); + +/** + * @brief Read the bearer URI schemes supported list of a remote TBS bearer. + * + * @kconfig_dep{CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST} + * + * @param bearer The bearer to read the URI schemes supported list from + * + * @retval 0 Success. + * @retval -EINVAL @p bearer is NULL. + * @retval -EFAULT @p bearer has not been discovered. + * @retval -EEXIST A @ref bt_ccp_call_control_client could not be identified + * for @p bearer. + * @retval -EBUSY The @ref bt_ccp_call_control_client identified by @p bearer + * is busy, or the TBS instance of @p bearer is busy. + * @retval -ENOTCONN The @ref bt_ccp_call_control_client identified by @p + * bearer is not connected. + * @retval -ENOEXEC The underlying TBS client returned an unexpected error. + */ + +int bt_ccp_call_control_client_read_bearer_uri_schemes( + struct bt_ccp_call_control_client_bearer *bearer); +/** @} */ /* End of group bt_ccp_call_control_client */ +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_CCP_H */ diff --git a/include/nuttx/net/bluetooth/audio/csip.h b/include/nuttx/net/bluetooth/audio/csip.h new file mode 100644 index 0000000000000..5b78b8b502689 --- /dev/null +++ b/include/nuttx/net/bluetooth/audio/csip.h @@ -0,0 +1,745 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/audio/csip.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Bluetooth Coordinated Set Identification Profile (CSIP) APIs. + */ + +/* Copyright (c) 2021-2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_CSIP_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_CSIP_H + +/** + * @brief Coordinated Set Identification Profile (CSIP) + * + * @defgroup bt_csip Coordinated Set Identification Profile (CSIP) + * + * @since 3.0 + * @version 0.8.0 + * + * @ingroup bluetooth + * @{ + * + * The Coordinated Set Identification Profile (CSIP) provides procedures to + * discover and coordinate sets of devices. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Recommended timer for member discovery */ +#define BT_CSIP_SET_COORDINATOR_DISCOVER_TIMER_VALUE SEC2TICK(10) + +/** + * Defines the maximum number of Coordinated Set Identification service + * instances for the Coordinated Set Identification Set Coordinator + */ +#if defined(CONFIG_BT_CSIP_SET_COORDINATOR) +#define BT_CSIP_SET_COORDINATOR_MAX_CSIS_INSTANCES CONFIG_BT_CSIP_SET_COORDINATOR_MAX_CSIS_INSTANCES +#else +#define BT_CSIP_SET_COORDINATOR_MAX_CSIS_INSTANCES 0U +#endif /* CONFIG_BT_CSIP_SET_COORDINATOR */ + +/** Accept the request to read the SIRK as plaintext */ +#define BT_CSIP_READ_SIRK_REQ_RSP_ACCEPT 0x00U +/** Accept the request to read the SIRK, but return encrypted SIRK */ +#define BT_CSIP_READ_SIRK_REQ_RSP_ACCEPT_ENC 0x01U +/** Reject the request to read the SIRK */ +#define BT_CSIP_READ_SIRK_REQ_RSP_REJECT 0x02U +/** SIRK is available only via an OOB procedure */ +#define BT_CSIP_READ_SIRK_REQ_RSP_OOB_ONLY 0x03U + +/** Size of the Set Identification Resolving Key (SIRK) */ +#define BT_CSIP_SIRK_SIZE 16U + +/** Size of the Resolvable Set Identifier (RSI) */ +#define BT_CSIP_RSI_SIZE 6U + +/* Coordinate Set Identification Service Error codes */ + +/** Service is already locked */ +#define BT_CSIP_ERROR_LOCK_DENIED 0x80U +/** Service is not locked */ +#define BT_CSIP_ERROR_LOCK_RELEASE_DENIED 0x81U +/** Invalid lock value */ +#define BT_CSIP_ERROR_LOCK_INVAL_VALUE 0x82U +/** SIRK only available out-of-band */ +#define BT_CSIP_ERROR_SIRK_OOB_ONLY 0x83U +/** Client is already owner of the lock */ +#define BT_CSIP_ERROR_LOCK_ALREADY_GRANTED 0x84U + +/** + * @brief Helper to declare bt_data array including RSI + * + * This macro is mainly for creating an array of struct bt_data elements + * which is then passed to e.g. @ref bt_le_ext_adv_start(). + * + * @param _rsi Pointer to the RSI value + */ +#define BT_CSIP_DATA_RSI(_rsi) BT_DATA(BT_DATA_CSIS_RSI, _rsi, BT_CSIP_RSI_SIZE) + +/** + * @struct bt_csip_set_member_svc_inst + * @brief Opaque Coordinated Set Identification Service instance. + */ + +struct bt_csip_set_member_svc_inst; + +/** Callback structure for the Coordinated Set Identification Service */ + +struct bt_csip_set_member_cb +{ + /* @brief Callback whenever the lock changes on the server. + * + * @param conn The connection to the client that changed the lock. + * NULL if server changed it, either by calling + * bt_csip_set_member_lock() or by timeout. + * @param svc_inst Pointer to the Coordinated Set Identification Service. + * @param locked Whether the lock was locked or released. + * + */ + + void (*lock_changed)(struct bt_conn *conn, + struct bt_csip_set_member_svc_inst *svc_inst, + bool locked); + + /* @brief Request from a peer device to read the sirk. + * + * If this callback is not set, all clients will be allowed to read the + * SIRK unencrypted. + * + * @param conn The connection to the client that requested to read the + * SIRK. + * @param svc_inst Pointer to the Coordinated Set Identification Service. + * + * @return A BT_CSIP_READ_SIRK_REQ_RSP_* response code. + */ + + uint8_t (*sirk_read_req)(struct bt_conn *conn, + struct bt_csip_set_member_svc_inst *svc_inst); +}; + +/** Register structure for Coordinated Set Identification Service */ + +struct bt_csip_set_member_register_param +{ + /* @brief Size of the set. + * + * If set to 0, the set size characteristic won't be initialized. If + * @kconfig{CONFIG_BT_CSIP_SET_MEMBER_SIZE_SUPPORT} is not enabled, this + * will be ignored. + */ + + uint8_t set_size; + + /* @brief The unique Set Identity Resolving Key (SIRK) + * + * This shall be unique between different sets, and shall be the same for + * each set member for each set. + */ + + uint8_t sirk[BT_CSIP_SIRK_SIZE]; + + /* @brief Boolean to set whether the set is lockable by clients + * + * Setting this to false will disable the lock characteristic. If + * @kconfig{CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT} is not enabled, this + * will be ignored. + */ + + bool lockable; + + /* @brief Rank of this device in this set. + * + * If the lockable parameter is set to true, this shall be > 0 and <= to + * the set_size (if set size support is enabled). If the lockable parameter + * is set to false, this may be set to 0 to disable the rank + * characteristic. + * + * If @kconfig{CONFIG_BT_CSIP_SET_MEMBER_RANK_SUPPORT} is not enabled, this + * will be ignored. + */ + + uint8_t rank; + + /** Pointer to the callback structure. */ + + struct bt_csip_set_member_cb *cb; + +#if CONFIG_BT_CSIP_SET_MEMBER_MAX_INSTANCE_COUNT > 1 || defined(__DOXYGEN__) + /* @brief Parent service pointer + * + * Mandatory parent service pointer if this CSIS instance is included by + * another service. All CSIS instances when + * @kconfig{CONFIG_BT_CSIP_SET_MEMBER_MAX_INSTANCE_COUNT} is above 1 shall + * be included by another service, as per the Coordinated Set + * Identification Profile (CSIP). + */ + + const struct bt_gatt_service *parent; +#endif /* CONFIG_BT_CSIP_SET_MEMBER_MAX_INSTANCE_COUNT > 1 */ +}; + +/** + * @brief Get the service declaration attribute. + * + * The first service attribute can be included in any other GATT service. + * + * @param svc_inst Pointer to the Coordinated Set Identification Service. + * + * @return The first CSIS attribute instance. + */ + +void *bt_csip_set_member_svc_decl_get( + const struct bt_csip_set_member_svc_inst *svc_inst); + +/** + * @brief Register a Coordinated Set Identification Service instance. + * + * This will register and enable the service and make it discoverable by + * clients. + * + * This shall only be done as a server. + * + * @param param Coordinated Set Identification Service register + * parameters. + * @param[out] svc_inst Pointer to the registered Coordinated Set + * Identification Service. + * + * @return 0 if success, errno on failure. + */ + +int bt_csip_set_member_register( + const struct bt_csip_set_member_register_param *param, + struct bt_csip_set_member_svc_inst **svc_inst); + +/** + * @brief Unregister a Coordinated Set Identification Service instance. + * + * This will unregister and disable the service instance. + * + * @param svc_inst Pointer to the registered Coordinated Set Identification + * Service. + * + * @return 0 if success, errno on failure. + */ + +int bt_csip_set_member_unregister( + struct bt_csip_set_member_svc_inst *svc_inst); + +/** + * @brief Set the SIRK of a service instance + * + * @param svc_inst Pointer to the registered Coordinated Set Identification + * Service. + * @param sirk The new SIRK. + */ + +int bt_csip_set_member_sirk(struct bt_csip_set_member_svc_inst *svc_inst, + const uint8_t sirk[BT_CSIP_SIRK_SIZE]); + +/** + * @brief Set a new size and rank for a service instance + * + * This function can be used to dynamically change the size and rank of a + * service instance. It is important to note that a set cannot have multiple + * devices with the same rank in a set, and it is up to the caller of this + * function to ensure that. Similarly, it is important that the size is + * updated on all devices in the set at the same time. The rank of a device + * cannot be modified on its own, and a new rank can only be set if the @p + * size is different from the current set size. + * + * If @kconfig{CONFIG_BT_CSIP_SET_MEMBER_SIZE_NOTIFIABLE} is enabled, this + * will also send a notification to all connected or bonded clients. + * + * Available if either @kconfig{CONFIG_BT_CSIP_SET_MEMBER_SIZE_SUPPORT} or + * @kconfig{CONFIG_BT_CSIP_SET_MEMBER_RANK_SUPPORT} is enabled. The disabled + * parameter (i.e. @p rank or @p size) will not be set. + * + * @param svc_inst The service instance. + * @param size The new set size. + * @param rank The new rank. + * + * @retval -EINVAL @p svc_inst is NULL, @p size is less than 1, @p rank is + * less than 1 or higher than @p size for a lockable @p svc_inst. + * @retval -EALREADY @p size is already set. + * @retval 0 Success. + */ + +int bt_csip_set_member_set_size_and_rank( + struct bt_csip_set_member_svc_inst *svc_inst, uint8_t size, uint8_t rank); + +/** Struct to hold information about a service instance */ + +struct bt_csip_set_member_set_info +{ + /** The 16-octet SIRK */ + + uint8_t sirk[BT_CSIP_SIRK_SIZE]; + + /* @brief The set size + * + * Will always be 0 if @kconfig{CONFIG_BT_CSIP_SET_MEMBER_SIZE_SUPPORT} is + * not enabled. + */ + + uint8_t set_size; + + /* @brief The rank + * + * Will always be 0 if the set is not lockable or the option + * @kconfig{CONFIG_BT_CSIP_SET_MEMBER_RANK_SUPPORT} is not enabled. + */ + + uint8_t rank; + + /* @brief Whether the set is lockable + * + * Will always be false if @kconfig{CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT} + * is not enabled. + */ + + bool lockable : 1; + + /* @brief Whether the set is currently locked + * + * Will always be false if @kconfig{CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT} + * is not enabled. + */ + + bool locked : 1; + + /* @brief The address of the client that currently holds the lock + * + * Will be @ref BT_ADDR_LE_NONE if the server holds the lock + */ + + bt_addr_le_t lock_client_addr; +}; + +/** + * @brief Get information about a service instances + * + * @param svc_inst The service instance. + * @param info Pointer to a struct to store the information in. + * + * @retval -EINVAL @p svc_inst or @p info is NULL. + * @retval 0 Success. + */ + +int bt_csip_set_member_get_info( + const struct bt_csip_set_member_svc_inst *svc_inst, + struct bt_csip_set_member_set_info *info); + +/** + * @brief Generate the Resolvable Set Identifier (RSI) value. + * + * This will generate RSI for given @p svc_inst instance. + * + * @param svc_inst Pointer to the Coordinated Set Identification Service. + * @param rsi Pointer to the 6-octet newly generated RSI data in + * little-endian. + * + * @return int 0 if on success, errno on error. + */ + +int bt_csip_set_member_generate_rsi( + const struct bt_csip_set_member_svc_inst *svc_inst, + uint8_t rsi[BT_CSIP_RSI_SIZE]); + +/** + * @brief Locks a specific Coordinated Set Identification Service instance on + * the server. + * + * @kconfig_dep{CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT} + * + * @param svc_inst Pointer to the Coordinated Set Identification Service. + * @param lock If true lock the set, if false release the set. + * @param force This argument only have meaning when @p lock is false + * (release) and will force release the lock, regardless of + * who took the lock. + * + * @return 0 on success, GATT error on error. + */ + +int bt_csip_set_member_lock(struct bt_csip_set_member_svc_inst *svc_inst, + bool lock, bool force); + +/** Information about a specific set */ + +struct bt_csip_set_coordinator_set_info +{ + /* @brief The 16 octet set Set Identity Resolving Key (SIRK) + * + * The SIRK may not be exposed by the server over Bluetooth, and may + * require an out-of-band solution. + */ + + uint8_t sirk[BT_CSIP_SIRK_SIZE]; + + /* @brief The size of the set + * + * Will be 0 if not exposed by the server. + */ + + uint8_t set_size; + + /* @brief The rank of the set on the remote device + * + * Will be 0 if not exposed by the server. + */ + + uint8_t rank; + + /** Whether or not the set can be locked on this device */ + + bool lockable; +}; + +/** + * @brief Struct representing a coordinated set instance on a remote device + * + * The values in this struct will be populated during discovery of sets + * (bt_csip_set_coordinator_discover()). + */ + +struct bt_csip_set_coordinator_csis_inst +{ + /** Information about the coordinated set */ + + struct bt_csip_set_coordinator_set_info info; + + /** Internally used pointer value */ + + void *svc_inst; +}; + +/** Struct representing a remote device as a set member */ + +struct bt_csip_set_coordinator_set_member +{ + /** Array of Coordinated Set Identification Service instances for the + * remote device + */ + + struct bt_csip_set_coordinator_csis_inst + insts[BT_CSIP_SET_COORDINATOR_MAX_CSIS_INSTANCES]; +}; + +/** + * @typedef bt_csip_set_coordinator_discover_cb + * @brief Callback for discovering Coordinated Set Identification Services. + * + * @param conn Pointer to the remote device. + * @param member Pointer to the set member. + * @param err 0 on success, or an errno value on error. + * @param set_count Number of sets on the member. + */ + +typedef void (*bt_csip_set_coordinator_discover_cb)( + struct bt_conn *conn, + const struct bt_csip_set_coordinator_set_member *member, int err, + size_t set_count); + +/** + * @brief Initialise the csip_set_coordinator instance for a connection. This + * will do a discovery on the device and prepare the instance for following + * commands. + * + * @param conn Pointer to remote device to perform discovery on. + * + * @return int Return 0 on success, or an errno value on error. + */ + +int bt_csip_set_coordinator_discover(struct bt_conn *conn); + +/** + * @brief Get the set member from a connection pointer + * + * Get the Coordinated Set Identification Profile Set Coordinator pointer + * from a connection pointer. Only Set Coordinators that have been initiated + * via bt_csip_set_coordinator_discover() can be retrieved. + * + * @param conn Connection pointer. + * + * @retval Pointer to a Coordinated Set Identification Profile Set + * Coordinator instance + * @retval NULL if @p conn is NULL or if the connection has not done + * discovery yet + */ + +struct bt_csip_set_coordinator_set_member * +bt_csip_set_coordinator_set_member_by_conn(const struct bt_conn *conn); + +/** + * @typedef bt_csip_set_coordinator_lock_set_cb + * @brief Callback for locking a set across one or more devices + * + * @param err 0 on success, or an errno value on error. + */ + +typedef void (*bt_csip_set_coordinator_lock_set_cb)(int err); + +/** + * @typedef bt_csip_set_coordinator_lock_changed_cb + * @brief Callback when the lock value on a set of a connected device + * changes. + * + * @param inst The Coordinated Set Identification Service instance that + * was changed. + * @param locked Whether the lock is locked or release. + */ + +typedef void (*bt_csip_set_coordinator_lock_changed_cb)( + struct bt_csip_set_coordinator_csis_inst *inst, bool locked); + +/** + * @typedef bt_csip_set_coordinator_sirk_changed_cb + * @brief Callback when the SIRK value of a set of a connected device + * changes. + * + * @param inst The Coordinated Set Identification Service instance that + * was changed. The new SIRK can be accessed via the @p + * inst.info. + */ + +typedef void (*bt_csip_set_coordinator_sirk_changed_cb)( + struct bt_csip_set_coordinator_csis_inst *inst); + +/** + * @typedef bt_csip_set_coordinator_size_changed_cb + * @brief Callback when the size of a set of a connected device changes. + * + * Since all devices in a set shall have the same set size value. Each + * connected device may send the same new size set in a notification, + * assuming that the remote device supports notifications of the set size. + * + * The rank of each device in the set may also change as part of this, so it + * is advisable to call bt_csip_set_coordinator_discover() to rediscover and + * read the characteristic values of the sets on each device. + * + * @param conn Pointer to the remote device. + * @param inst The Coordinated Set Identification Service instance that + * was changed. The new size is stored in the @p + * inst->info.size. + */ + +typedef void (*bt_csip_set_coordinator_size_changed_cb)( + struct bt_conn *conn, const struct bt_csip_set_coordinator_csis_inst + *inst); + +/** + * @typedef bt_csip_set_coordinator_ordered_access_cb_t + * @brief Callback for bt_csip_set_coordinator_ordered_access() + * + * If any of the set members supplied to + * bt_csip_set_coordinator_ordered_access() is in the locked state, this will + * be called with @p locked true and @p member will be the locked member, and + * the ordered access procedure is cancelled. Likewise, if any error occurs, + * the procedure will also be aborted. + * + * @param set_info Pointer to the a specific set_info struct. + * @param err Error value. 0 on success, GATT error or errno on fail. + * @param locked Whether the lock is locked or release. + * @param member The locked member if @p locked is true, otherwise NULL. + */ + +typedef void (*bt_csip_set_coordinator_ordered_access_cb_t)( + const struct bt_csip_set_coordinator_set_info *set_info, int err, + bool locked, struct bt_csip_set_coordinator_set_member *member); + +/** + * @brief Struct to hold the Coordinated Set Identification Profile Set + * Coordinator callbacks + * + * These can be registered for usage with + * bt_csip_set_coordinator_register_cb(). + */ + +struct bt_csip_set_coordinator_cb +{ + /** Callback when discovery has finished */ + + bt_csip_set_coordinator_discover_cb discover; + + /** Callback when locking a set has finished */ + + bt_csip_set_coordinator_lock_set_cb lock_set; + + /** Callback when unlocking a set has finished */ + + bt_csip_set_coordinator_lock_set_cb release_set; + + /** Callback when a set's lock state has changed */ + + bt_csip_set_coordinator_lock_changed_cb lock_changed; + + /** Callback when a set's SIRK has changed */ + + bt_csip_set_coordinator_sirk_changed_cb sirk_changed; + + /** Callback when a set's size has changed */ + + bt_csip_set_coordinator_size_changed_cb size_changed; + + /** Callback for the ordered access procedure */ + + bt_csip_set_coordinator_ordered_access_cb_t ordered_access; + + /** @cond INTERNAL_HIDDEN */ + + /** Internally used field for list handling */ + + struct list_node _node; + + /** @endcond */ +}; + +/** + * @brief Check if advertising data indicates a set member + * + * @param sirk The SIRK of the set to check against + * @param data The advertising data + * + * @return true if the advertising data indicates a set member, false + * otherwise + */ + +bool bt_csip_set_coordinator_is_set_member( + const uint8_t sirk[BT_CSIP_SIRK_SIZE], struct bt_data *data); + +/** + * @brief Registers callbacks for csip_set_coordinator. + * + * @param cb Pointer to the callback structure. + * + * @return Return 0 on success, or an errno value on error. + */ + +int bt_csip_set_coordinator_register_cb( + struct bt_csip_set_coordinator_cb *cb); + +/** + * @brief Callback function definition for + * bt_csip_set_coordinator_ordered_access() + * + * @param set_info Pointer to the a specific set_info struct. + * @param members Array of members ordered by rank. The procedure shall be + * done on the members in ascending order. + * @param count Number of members in @p members. + * + * @return true if the procedures can be successfully done, or false to stop + * the procedure. + */ + +typedef bool (*bt_csip_set_coordinator_ordered_access_t)( + const struct bt_csip_set_coordinator_set_info *set_info, + struct bt_csip_set_coordinator_set_member *members[], size_t count); + +/** + * @brief Access Coordinated Set devices in an ordered manner as a client + * + * This function will read the lock state of all devices and if all devices + * are in the unlocked state, then @p cb will be called with the same members + * as provided by @p members, but where the members are ordered by rank (if + * present). Once this procedure is finished or an error occurs, + * @ref bt_csip_set_coordinator_cb.ordered_access will be called. + * + * This procedure only works if all the members have the lock characteristic, + * and all either has rank = 0 or unique ranks. + * + * If any of the members are in the locked state, the procedure will be + * cancelled. + * + * This can only be done on members that are bonded. + * + * @param members Array of set members to access. + * @param count Number of set members in @p members. + * @param set_info Pointer to the a specific set_info struct, as a member + * may be part of multiple sets. + * @param cb The callback function to be called for each member. + */ + +int bt_csip_set_coordinator_ordered_access( + const struct bt_csip_set_coordinator_set_member *members[], uint8_t count, + const struct bt_csip_set_coordinator_set_info *set_info, + bt_csip_set_coordinator_ordered_access_t cb); + +/** + * @brief Lock an array of set members + * + * The members will be locked starting from lowest rank going up. + * + * @kconfig_dep{CONFIG_BT_CSIP_SET_COORDINATOR,CONFIG_BT_BONDABLE} + * + * TODO: If locking fails, the already locked members will not be unlocked. + * + * @param members Array of set members to lock. + * @param count Number of set members in @p members. + * @param set_info Pointer to the a specific set_info struct, as a member + * may be part of multiple sets. + * + * @return Return 0 on success, or an errno value on error. + */ + +int bt_csip_set_coordinator_lock( + const struct bt_csip_set_coordinator_set_member **members, uint8_t count, + const struct bt_csip_set_coordinator_set_info *set_info); + +/** + * @brief Release an array of set members + * + * The members will be released starting from highest rank going down. + * + * @kconfig_dep{CONFIG_BT_CSIP_SET_COORDINATOR,CONFIG_BT_BONDABLE} + * + * @param members Array of set members to lock. + * @param count Number of set members in @p members. + * @param set_info Pointer to the a specific set_info struct, as a member + * may be part of multiple sets. + * + * @return Return 0 on success, or an errno value on error. + */ + +int bt_csip_set_coordinator_release( + const struct bt_csip_set_coordinator_set_member **members, uint8_t count, + const struct bt_csip_set_coordinator_set_info *set_info); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_CSIP_H */ diff --git a/include/nuttx/net/bluetooth/audio/gmap.h b/include/nuttx/net/bluetooth/audio/gmap.h new file mode 100644 index 0000000000000..255b569844cb1 --- /dev/null +++ b/include/nuttx/net/bluetooth/audio/gmap.h @@ -0,0 +1,321 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/audio/gmap.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Header for Bluetooth Gaming Audio Profile (GMAP). + * + * Copyright (c) 2023-2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_GMAP_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_GMAP_H + +/** + * @brief Bluetooth Gaming Audio Profile (GMAP) + * + * @defgroup bt_gmap Bluetooth Gaming Audio Profile + * + * @since 3.5 + * @version 0.8.0 + * + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Gaming Role bitfield */ + +enum bt_gmap_role +{ + /** + * @brief Gaming Role Unicast Game Gateway + * + * Requires @kconfig{CONFIG_BT_CAP_INITIATOR}, + * @kconfig{CONFIG_BT_BAP_UNICAST_CLIENT} and + * @kconfig{CONFIG_BT_VCP_VOL_CTLR} to be enabled. + */ + + BT_GMAP_ROLE_UGG = BIT(0U), + /** + * @brief Gaming Role Unicast Game Terminal + * + * Requires @kconfig{CONFIG_BT_CAP_ACCEPTOR} and + * @kconfig{CONFIG_BT_BAP_UNICAST_SERVER} to be enabled. + */ + + BT_GMAP_ROLE_UGT = BIT(1U), + /** + * @brief Gaming Role Broadcast Game Sender + * + * Requires @kconfig{CONFIG_BT_CAP_INITIATOR} and + * @kconfig{CONFIG_BT_BAP_BROADCAST_SOURCE} to be enabled. + */ + + BT_GMAP_ROLE_BGS = BIT(2U), + /** + * @brief Gaming Role Broadcast Game Receiver + * + * Requires @kconfig{CONFIG_BT_CAP_ACCEPTOR}, + * @kconfig{CONFIG_BT_BAP_BROADCAST_SINK} and + * @kconfig{CONFIG_BT_VCP_VOL_REND} to be enabled. + */ + + BT_GMAP_ROLE_BGR = BIT(3U), +}; + +/** Unicast Game Gateway Feature bitfield */ + +enum bt_gmap_ugg_feat +{ + /** + * @brief Support transmitting multiple LC3 codec frames per block in an + * SDU + * + * Requires @kconfig{CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT} > 0 + */ + + BT_GMAP_UGG_FEAT_MULTIPLEX = BIT(0U), + /** + * @brief 96 kbps source support + * + * Requires @kconfig{CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT} > 0 + */ + + BT_GMAP_UGG_FEAT_96KBPS_SOURCE = BIT(1U), + /** + * @brief Support for receiving at least two channels of audio, each in a + * separate CIS + * + * Requires @kconfig{CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT} > 1 and + * @kconfig{CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT} > 1 + */ + + BT_GMAP_UGG_FEAT_MULTISINK = BIT(2U), +}; + +/** Unicast Game Terminal Feature bitfield */ + +enum bt_gmap_ugt_feat +{ + /** + * @brief Source support + * + * Requires @kconfig{CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT} > 0 + */ + + BT_GMAP_UGT_FEAT_SOURCE = BIT(0U), + /** + * @brief 80 kbps source support + * + * Requires BT_GMAP_UGT_FEAT_SOURCE to be set as well + */ + + BT_GMAP_UGT_FEAT_80KBPS_SOURCE = BIT(1U), + /** + * @brief Sink support + * + * Requires @kconfig{CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT} > 0 + */ + + BT_GMAP_UGT_FEAT_SINK = BIT(2U), + /** + * @brief 64 kbps sink support + * + * Requires BT_GMAP_UGT_FEAT_SINK to be set as well + */ + + BT_GMAP_UGT_FEAT_64KBPS_SINK = BIT(3U), + /** + * @brief Support for receiving multiple LC3 codec frames per block in an + * SDU + * + * Requires BT_GMAP_UGT_FEAT_SINK to be set as well + */ + + BT_GMAP_UGT_FEAT_MULTIPLEX = BIT(4U), + /** + * @brief Support for receiving at least two audio channels, each in a + * separate CIS + * + * Requires @kconfig{CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT} > 1 and + * @kconfig{CONFIG_BT_ASCS_MAX_ACTIVE_ASES} > 1, and BT_GMAP_UGT_FEAT_SINK + * to be set as well + */ + + BT_GMAP_UGT_FEAT_MULTISINK = BIT(5U), + /** + * @brief Support for sending at least two audio channels, each in a + * separate CIS + * + * Requires @kconfig{CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT} > 1 and + * @kconfig{CONFIG_BT_ASCS_MAX_ACTIVE_ASES} > 1, and + * BT_GMAP_UGT_FEAT_SOURCE to be set as well + */ + + BT_GMAP_UGT_FEAT_MULTISOURCE = BIT(6U), +}; + +/** Broadcast Game Sender Feature bitfield */ + +enum bt_gmap_bgs_feat +{ + /** 96 kbps support */ + + BT_GMAP_BGS_FEAT_96KBPS = BIT(0U), +}; + +/** Broadcast Game Receiver Feature bitfield */ + +enum bt_gmap_bgr_feat +{ + /** + * @brief Support for receiving at least two audio channels, each in a + * separate BIS + * + * Requires @kconfig{CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT} > 1 + */ + + BT_GMAP_BGR_FEAT_MULTISINK = BIT(0U), + + /** @brief Support for receiving multiple LC3 codec frames per block in an + * SDU + */ + + BT_GMAP_BGR_FEAT_MULTIPLEX = BIT(1U), +}; + +/** Broadcast Game Receiver Feature bitfield */ + +struct bt_gmap_feat +{ + /** Unicast Game Gateway features */ + + enum bt_gmap_ugg_feat ugg_feat; + + /** Unicast Game Terminal features */ + + enum bt_gmap_ugt_feat ugt_feat; + + /** Remote Broadcast Game Sender features */ + + enum bt_gmap_bgs_feat bgs_feat; + + /** Remote Broadcast Game Receiver features */ + + enum bt_gmap_bgr_feat bgr_feat; +}; + +/** @brief Gaming Audio Profile callback structure. */ + +struct bt_gmap_cb +{ + /** + * @brief Callback function for bt_gmap_discover(). + * + * This callback is called when discovery procedure is complete. + * + * @param conn Bluetooth connection object. + * @param err 0 on success, ATT error or negative errno otherwise. + * @param role Role of remote device. 0 on failure. + * @param features Remote features. + */ + + void (*discover)(struct bt_conn *conn, int err, enum bt_gmap_role role, + struct bt_gmap_feat features); +}; + +/** + * @brief Registers the callbacks used by the Gaming Audio Profile. + * + * @param cb The callback structure. + * + * @retval -EINVAL if @p cb is NULL + * @retval -EALREADY if callbacks have already be registered + * @retval 0 on success + */ + +int bt_gmap_cb_register(const struct bt_gmap_cb *cb); + +/** + * @brief Discover Gaming Service on a remote device. + * + * Procedure to find a Gaming Service on a server identified by @p conn. The + * @ref bt_gmap_cb.discover callback is called when the discovery procedure + * completes of fails. On discovery success the callback contains information + * about the remote device. + * + * @param conn Bluetooth connection object. + * + * @retval -EINVAL if @p conn is NULL + * @retval -EBUSY if discovery is already in progress for @p conn + * @retval -ENOTCONN if @p conn is not connected + * @retval -ENOEXEC if discovery failed to initiate + * @retval 0 on success + */ + +int bt_gmap_discover(struct bt_conn *conn); + +/** + * @brief Adds GMAS instance to database and sets the received Gaming Audio + * Profile role(s). + * + * @param role Gaming Audio Profile role(s) of the device (one or + * multiple). + * @param features Features of the roles. If a role is not in the @p role + * parameter, then the feature value for that role is simply + * ignored. + * + * @retval -EINVAL on invalid arguments + * @retval -ENOEXEC on service register failure + * @retval 0 on success + */ + +int bt_gmap_register(enum bt_gmap_role role, struct bt_gmap_feat features); + +/** + * @brief Set one or multiple Gaming Audio Profile roles and features + * dynamically. + * + * Previously registered value will be overwritten. If there is a role + * change, this will trigger a Gaming Audio Service (GMAS) service change. If + * there is only a feature change, no service change will happen. + * + * @param role Gaming Audio Profile role(s). + * @param features Features of the roles. If a role is not in the @p role + * parameter, then the feature value for that role is simply + * ignored. + * + * @retval -ENOEXEC if the service has not yet been registered + * @retval -EINVAL on invalid arguments + * @retval -EALREADY if the @p role and @p features are the same as existing + * ones + * @retval -ENOENT on service unregister failure + * @retval -ECANCELED on service re-register failure + * @retval 0 on success + */ + +int bt_gmap_set_role(enum bt_gmap_role role, struct bt_gmap_feat features); + +#ifdef __cplusplus +} +#endif +/** @} */ /* end of bt_gmap */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_GMAP_H */ diff --git a/include/nuttx/net/bluetooth/audio/gmap_lc3_preset.h b/include/nuttx/net/bluetooth/audio/gmap_lc3_preset.h new file mode 100644 index 0000000000000..181fdd22ea3d4 --- /dev/null +++ b/include/nuttx/net/bluetooth/audio/gmap_lc3_preset.h @@ -0,0 +1,271 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/audio/gmap_lc3_preset.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Header for Bluetooth GMAP LC3 presets. + * + * Copyright (c) 2023-2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_GMAP_LC3_PRESET_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_GMAP_LC3_PRESET_H + +/** + * @brief Gaming Audio Profile (GMAP) LC3 Presets + * + * @defgroup bt_gmap_lc3_preset Gaming Audio Profile (GMAP) LC3 Presets + * + * @since 3.5 + * @version 0.8.0 + * + * @ingroup bluetooth + * @{ + * + * These APIs provide presets for codec configuration and QoS based on values + * supplied by the codec configurations from table 3.16 in the GMAP v1.0 + * specification + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Helper to declare LC3 32_1_gr codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_GMAP_LC3_PRESET_32_1_GR(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_32KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 60U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 60U, 1U, 15U, 10000U)) + +/** + * @brief Helper to declare LC3 32_2_gr codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_GMAP_LC3_PRESET_32_2_GR(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_32KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 80U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 80U, 1U, 20U, 10000U)) + +/** + * @brief Helper to declare LC3 48_1_gr codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_GMAP_LC3_PRESET_48_1_GR(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 75U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 75U, 1U, 15U, 10000U)) + +/** + * @brief Helper to declare LC3 48_2_gr codec configuration + * + * Mandatory to support as both unicast client and server + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_GMAP_LC3_PRESET_48_2_GR(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 100U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 100U, 1U, 20U, 10000U)) + +/** + * @brief Helper to declare LC3 48_3_gr codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_GMAP_LC3_PRESET_48_3_GR(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 90U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 90U, 1U, 15U, 10000U)) + +/** + * @brief Helper to declare LC3 48_4_gr codec configuration + * + * Mandatory to support as unicast server + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_GMAP_LC3_PRESET_48_4_GR(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 120u, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 120U, 1U, 20U, 10000U)) + +/** + * @brief Helper to declare LC3 16_1_gs codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_GMAP_LC3_PRESET_16_1_GS(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_16KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 30U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 30U, 1U, 15U, 60000U)) + +/** + * @brief Helper to declare LC3 16_2_gs codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_GMAP_LC3_PRESET_16_2_GS(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_16KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 40U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 40U, 1U, 20U, 60000U)) + +/** + * @brief Helper to declare LC3 32_1_gs codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_GMAP_LC3_PRESET_32_1_GS(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_32KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 60U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 60U, 1U, 15U, 60000U)) + +/** + * @brief Helper to declare LC3 32_2_gs codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_GMAP_LC3_PRESET_32_2_GS(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_32KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 80U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 80U, 1U, 20U, 60000U)) + +/** + * @brief Helper to declare LC3 48_1_gs codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_GMAP_LC3_PRESET_48_1_GS(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 75U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 75U, 1U, 15U, 60000U)) + +/** + * @brief Helper to declare LC3 48_2_gs codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_GMAP_LC3_PRESET_48_2_GS(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 100U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 100U, 1U, 20U, 60000U)) + +/* GMAP LC3 broadcast presets defined by table 3.22 in the GMAP v1.0 + * specification + */ + +/** + * @brief Helper to declare LC3 48_1_g codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_GMAP_LC3_PRESET_48_1_G(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 75U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 75U, 1U, 8U, 10000U)) + +/** + * @brief Helper to declare LC3 48_2_g codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_GMAP_LC3_PRESET_48_2_G(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 100U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 100U, 1U, 10U, 10000U)) + +/** + * @brief Helper to declare LC3 48_3_g codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_GMAP_LC3_PRESET_48_3_G(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_7_5, _loc, 90U, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(7500u, 90U, 1U, 8U, 10000U)) + +/** + * @brief Helper to declare LC3 48_4_g codec configuration + * + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_GMAP_LC3_PRESET_48_4_G(_loc, _stream_context) \ +BT_BAP_LC3_PRESET(BT_AUDIO_CODEC_LC3_CONFIG(BT_AUDIO_CODEC_CFG_FREQ_48KHZ, \ + BT_AUDIO_CODEC_CFG_DURATION_10, _loc, 120u, 1, \ + _stream_context), \ + BT_BAP_QOS_CFG_UNFRAMED(10000u, 120U, 1U, 10U, 10000U)) + +#ifdef __cplusplus +} +#endif +/** @} */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_GMAP_LC3_PRESET_H */ diff --git a/include/nuttx/net/bluetooth/audio/has.h b/include/nuttx/net/bluetooth/audio/has.h new file mode 100644 index 0000000000000..586ac77e92f7d --- /dev/null +++ b/include/nuttx/net/bluetooth/audio/has.h @@ -0,0 +1,626 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/audio/has.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Bluetooth Hearing Access Service (HAS) APIs. + */ + +/* Copyright (c) 2022 Codecoup + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_HAS_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_HAS_H + +/** + * @brief Hearing Access Service (HAS) + * + * @defgroup bt_has Hearing Access Service (HAS) + * + * @since 3.1 + * @version 0.8.0 + * + * @ingroup bluetooth + * @{ + * + * The Hearing Access Service is used to identify a hearing aid and + * optionally to control hearing aid presets. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @name Preset index definitions + * @{ + */ + +/** No index */ +#define BT_HAS_PRESET_INDEX_NONE 0x00U +/** First preset index */ +#define BT_HAS_PRESET_INDEX_FIRST 0x01U +/** Last preset index */ +#define BT_HAS_PRESET_INDEX_LAST 0xFFU +/** @} */ + +/** Preset name minimum length */ +#define BT_HAS_PRESET_NAME_MIN 1U +/** Preset name maximum length */ +#define BT_HAS_PRESET_NAME_MAX 40U + +/** + * @struct bt_has + * @brief Opaque Hearing Access Service object. + */ + +struct bt_has; + +/** Hearing Aid device type */ + +enum bt_has_hearing_aid_type +{ + /** + * Two hearing aids that form a Coordinated Set, one for the right ear and + * one for the left ear of the user. Typically used by a user with + * bilateral hearing loss. + */ + + BT_HAS_HEARING_AID_TYPE_BINAURAL = 0x00, + /** + * A single hearing aid for the left or the right ear. Typically used by a + * user with unilateral hearing loss. + */ + + BT_HAS_HEARING_AID_TYPE_MONAURAL = 0x01, + /** + * Two hearing aids with a connection to one another that expose a single + * Bluetooth radio interface. + */ + + BT_HAS_HEARING_AID_TYPE_BANDED = 0x02, +}; + +/** Preset Properties values */ + +enum bt_has_properties +{ + /** No properties set */ + + BT_HAS_PROP_NONE = 0, + + /** Preset name can be written by the client */ + + BT_HAS_PROP_WRITABLE = BIT(0U), + + /** Preset availability */ + + BT_HAS_PROP_AVAILABLE = BIT(1U), +}; + +/** Hearing Aid device capabilities */ + +enum bt_has_capabilities +{ + /** Indicate support for presets */ + + BT_HAS_PRESET_SUPPORT = BIT(0U), +}; + +/** @brief Structure for registering features of a Hearing Access Service + * instance. + */ + +struct bt_has_features_param +{ + /** Hearing Aid Type value */ + + enum bt_has_hearing_aid_type type; + + /** + * @brief Preset Synchronization Support. + * + * Only applicable if @p type is @ref BT_HAS_HEARING_AID_TYPE_BINAURAL and + * @kconfig{CONFIG_BT_HAS_PRESET_COUNT} is non-zero. + */ + + bool preset_sync_support; + + /** + * @brief Independent Presets. + * + * Only applicable if @p type is @ref BT_HAS_HEARING_AID_TYPE_BINAURAL and + * @kconfig{CONFIG_BT_HAS_PRESET_COUNT} is non-zero. + */ + + bool independent_presets; +}; + +/** @brief Preset record definition */ + +struct bt_has_preset_record +{ + /** Unique preset index. */ + + uint8_t index; + + /** Bitfield of preset properties. */ + + enum bt_has_properties properties; + + /** Preset name. */ + + const char *name; +}; + +/** @brief Hearing Access Service Client callback structure. */ + +struct bt_has_client_cb +{ + /** + * @brief Callback function for bt_has_discover. + * + * This callback is called when discovery procedure is complete. + * + * @param conn Bluetooth connection object. + * @param err 0 on success, ATT error or negative errno otherwise. + * @param has Pointer to the Hearing Access Service object or NULL on + * errors. + * @param type Hearing Aid type. + * @param caps Hearing Aid capabilities. + */ + +void (*discover)(struct bt_conn *conn, + int err, + struct bt_has *has, + enum bt_has_hearing_aid_type type, + enum bt_has_capabilities caps); + + /** + * @brief Callback function for Hearing Access Service active preset + * changes. + * + * Optional callback called when the active preset is changed by the remote + * server when the preset switch procedure is complete. The callback must + * be set to receive active preset changes and enable support for switching + * presets. If the callback is not set, the Active Index and Control Point + * characteristics will not be discovered by + * @ref bt_has_client_discover. + * + * @param has Pointer to the Hearing Access Service object. + * @param err 0 on success, ATT error or negative errno otherwise. + * @param index Active preset index. + */ + + void (*preset_switch)(struct bt_has *has, int err, uint8_t index); + + /** + * @brief Callback function for presets read operation. + * + * The callback is called when the preset read response is sent by the + * remote server. The record object as well as its members are temporary + * and must be copied to in order to cache its information. + * + * @param has Pointer to the Hearing Access Service object. + * @param err 0 on success, ATT error or negative errno otherwise. + * @param record Preset record or NULL on errors. + * @param is_last True if Read Presets operation can be considered + * concluded. + */ + +void (*preset_read_rsp)(struct bt_has *has, + int err, + const struct bt_has_preset_record *record, + bool is_last); + + /** + * @brief Callback function for preset update notifications. + * + * The callback is called when the preset record update is notified by the + * remote server. The record object as well as its objects are temporary + * and must be copied to in order to cache its information. + * + * @param has Pointer to the Hearing Access Service object. + * @param index_prev Index of the previous preset in the list. + * @param record Preset record. + * @param is_last True if preset list update operation can be considered + * concluded. + */ + +void (*preset_update)(struct bt_has *has, + uint8_t index_prev, + const struct bt_has_preset_record *record, + bool is_last); + + /** + * @brief Callback function for preset deletion notifications. + * + * The callback is called when the preset has been deleted by the remote + * server. + * + * @param has Pointer to the Hearing Access Service object. + * @param index Preset index. + * @param is_last True if preset list update operation can be considered + * concluded. + */ + + void (*preset_deleted)(struct bt_has *has, uint8_t index, bool is_last); + + /** + * @brief Callback function for preset availability notifications. + * + * The callback is called when the preset availability change is notified + * by the remote server. + * + * @param has Pointer to the Hearing Access Service object. + * @param index Preset index. + * @param available True if available, false otherwise. + * @param is_last True if preset list update operation can be considered + * concluded. + */ + + void (*preset_availability)(struct bt_has *has, uint8_t index, + bool available, bool is_last); +}; + +/** + * @brief Registers the callbacks used by the Hearing Access Service client. + * + * @param cb The callback structure. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_has_client_cb_register(const struct bt_has_client_cb *cb); + +/** + * @brief Discover Hearing Access Service on a remote device. + * + * Client method to find a Hearing Access Service on a server identified by + * @p conn. The @ref bt_has_client_cb.discover callback will be called when + * the discovery procedure is complete to provide user a @ref bt_has object. + * + * @param conn Bluetooth connection object. + * + * @return 0 if success, errno on failure. + */ + +int bt_has_client_discover(struct bt_conn *conn); + +/** + * @brief Get the Bluetooth connection object of the service object. + * + * The caller gets a new reference to the connection object which must be + * released with bt_conn_unref() once done using the object. + * + * @param[in] has Pointer to the Hearing Access Service object. + * @param[out] conn Connection object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_has_client_conn_get(const struct bt_has *has, struct bt_conn **conn); + +/** + * @brief Read Preset Records. + * + * Client method to read up to @p max_count presets starting from given @p + * index. The preset records are returned in the @ref + * bt_has_client_cb.preset_read_rsp callback (called once for each preset). + * + * @param has Pointer to the Hearing Access Service object. + * @param index The index to start with. + * @param max_count Maximum number of presets to read. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_has_client_presets_read(struct bt_has *has, + uint8_t index, uint8_t max_count); + +/** + * @brief Set Active Preset. + * + * Client procedure to set preset identified by @p index as active. The + * status is returned in the @ref bt_has_client_cb.preset_switch callback. + * + * @param has Pointer to the Hearing Access Service object. + * @param index Preset index to activate. + * @param sync Request active preset synchronization in set. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_has_client_preset_set(struct bt_has *has, uint8_t index, bool sync); + +/** + * @brief Activate Next Preset. + * + * Client procedure to set next available preset as active. The status is + * returned in the @ref bt_has_client_cb.preset_switch callback. + * + * @param has Pointer to the Hearing Access Service object. + * @param sync Request active preset synchronization in set. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_has_client_preset_next(struct bt_has *has, bool sync); + +/** + * @brief Activate Previous Preset. + * + * Client procedure to set previous available preset as active. The status is + * returned in the @ref bt_has_client_cb.preset_switch callback. + * + * @param has Pointer to the Hearing Access Service object. + * @param sync Request active preset synchronization in set. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_has_client_preset_prev(struct bt_has *has, bool sync); + +/** @brief Preset operations structure. */ + +struct bt_has_preset_ops +{ + /** + * @brief Preset select callback. + * + * This callback is called when the client requests to select preset + * identified by @p index. + * + * @param index Preset index requested to activate. + * @param sync Whether the server must relay this change to the other + * member of the Binaural Hearing Aid Set. + * + * @return 0 in case of success or negative value in case of error. + * @return -EBUSY if operation cannot be performed at the time. + * @return -EINPROGRESS in case where user has to confirm once the + * requested preset becomes active by calling @ref + * bt_has_preset_active_set. + */ + + int (*select)(uint8_t index, bool sync); + + /** + * @brief Preset name changed callback + * + * This callback is called when the name of the preset identified by @p + * index has changed. + * + * @param index Preset index that name has been changed. + * @param name Preset current name. + */ + + void (*name_changed)(uint8_t index, const char *name); +}; + +/** @brief Register structure for preset. */ + +struct bt_has_preset_register_param +{ + /** + * @brief Preset index. + * + * Unique preset identifier. The value shall be other than @ref + * BT_HAS_PRESET_INDEX_NONE. + */ + + uint8_t index; + + /** + * @brief Preset properties. + * + * Bitfield of preset properties. + */ + + enum bt_has_properties properties; + + /** + * @brief Preset name. + * + * Preset name that further can be changed by either server or client if + * @kconfig{CONFIG_BT_HAS_PRESET_NAME_DYNAMIC} configuration option has + * been enabled. It's length shall be greater than @ref + * BT_HAS_PRESET_NAME_MIN. If the length exceeds + * @ref BT_HAS_PRESET_NAME_MAX, the name will be truncated. + */ + + const char *name; + + /** Preset operations structure. */ + + const struct bt_has_preset_ops *ops; +}; + +/** + * @brief Register the Hearing Access Service instance. + * + * @param features Hearing Access Service register parameters. + * + * @return 0 if success, errno on failure. + */ + +int bt_has_register(const struct bt_has_features_param *features); + +/** + * @brief Register preset. + * + * Register preset. The preset will be a added to the list of exposed preset + * records. This symbol is linkable if @kconfig{CONFIG_BT_HAS_PRESET_COUNT} + * is non-zero. + * + * @param param Preset registration parameter. + * + * @return 0 if success, errno on failure. + */ + +int bt_has_preset_register(const struct bt_has_preset_register_param *param); + +/** + * @brief Unregister Preset. + * + * Unregister preset. The preset will be removed from the list of preset + * records. + * + * @param index The index of preset that's being requested to unregister. + * + * @return 0 if success, errno on failure. + */ + +int bt_has_preset_unregister(uint8_t index); + +/** + * @brief Set the preset as available. + * + * Set the @ref BT_HAS_PROP_AVAILABLE property bit. This will notify preset + * availability to peer devices. Only available preset can be selected as + * active preset. + * + * @param index The index of preset that's became available. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_has_preset_available(uint8_t index); + +/** + * @brief Set the preset as unavailable. + * + * Clear the @ref BT_HAS_PROP_AVAILABLE property bit. This will notify preset + * availability to peer devices. Unavailable preset cannot be selected as + * active preset. + * + * @param index The index of preset that's became unavailable. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_has_preset_unavailable(uint8_t index); + +/** + * @typedef bt_has_preset_func_t + * @brief Preset iterator callback. + * + * @param index The index of preset found. + * @param properties Preset properties. + * @param name Preset name. + * @param user_data Data given. + * + * @retval true Continue iterating. + * @retval false Stop iterating. + */ + +typedef bool (*bt_has_preset_func_t)( + uint8_t index, enum bt_has_properties properties, const char *name, + void *user_data); + +/** + * @brief Preset iterator. + * + * Iterate presets. Optionally, match non-zero index if given. + * + * @param index Preset index, passing @ref BT_HAS_PRESET_INDEX_NONE skips + * index matching. + * @param func Callback function. + * @param user_data Data to pass to the callback. + * + * @retval 0 Success + * @retval -ECANCELED Iteration was stopped by the callback function before + * complete. + * @retval -EINVAL @p func was NULL. + */ + +int bt_has_preset_foreach(uint8_t index, + bt_has_preset_func_t func, void *user_data); + +/** + * @brief Set active preset. + * + * Function used to set the preset identified by the @p index as the active + * preset. The preset index will be notified to peer devices. + * + * @param index Preset index. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_has_preset_active_set(uint8_t index); + +/** + * @brief Get active preset. + * + * Function used to get the currently active preset index. + * + * @return Active preset index. + */ + +uint8_t bt_has_preset_active_get(void); + +/** + * @brief Clear out active preset. + * + * Used by server to deactivate currently active preset. + * + * @return 0 in case of success or negative value in case of error. + */ + +static inline int bt_has_preset_active_clear(void) +{ + return bt_has_preset_active_set(BT_HAS_PRESET_INDEX_NONE); +} + +/** + * @brief Change the Preset Name. + * + * Change the name of the preset identified by @p index. + * + * @param index The index of the preset to change the name of. + * @param name Name to write. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_has_preset_name_change(uint8_t index, const char *name); + +/** + * @brief Change the Hearing Aid Features. + * + * Change the hearing aid features. + * + * @param features The features to be set. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_has_features_set(const struct bt_has_features_param *features); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_HAS_H */ diff --git a/include/nuttx/net/bluetooth/audio/lc3.h b/include/nuttx/net/bluetooth/audio/lc3.h new file mode 100644 index 0000000000000..ce4faa4c3b1f1 --- /dev/null +++ b/include/nuttx/net/bluetooth/audio/lc3.h @@ -0,0 +1,157 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/audio/lc3.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Bluetooth LC3 codec handling + */ + +/* Copyright (c) 2020 Intel Corporation + * Copyright (c) 2022-2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_LC3_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_LC3_H + +/** + * @brief LC3 + * @defgroup bt_lc3 Bluetooth LC3 codec + + * @since 3.0 + * @version 0.8.0 + + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Helper to declare LC3 codec capability + * + * @p _max_frames_per_sdu is optional and will be included only if != 1 + * + * @ref COND_CODE_1 is used to omit an LTV entry in case the @p + * _max_frames_per_sdu is 1. + * @ref COND_CODE_1 will evaluate to second argument if the flag + * parameter(first argument) is 1 + * - removing one layer of parentheses. + * If the flags argument is != 1 it will evaluate to the third argument which + * inserts a LTV entry for the max_frames_per_sdu value. + + * @param _freq Supported Sampling Frequencies bitfield (see + * ``BT_AUDIO_CODEC_CAP_FREQ_*``) + * @param _duration Supported Frame Durations bitfield (see + * ``BT_AUDIO_CODEC_CAP_DURATION_*``) + * @param _chan_count Supported channels (see @ref + * BT_AUDIO_CODEC_CAP_CHAN_COUNT_SUPPORT) + * @param _len_min Minimum number of octets supported per codec + * frame + * @param _len_max Maximum number of octets supported per codec + * frame + * @param _max_frames_per_sdu Supported maximum codec frames per SDU + */ +#define BT_AUDIO_CODEC_CAP_LC3_DATA(_freq, _duration, _chan_count, _len_min, _len_max, \ + _max_frames_per_sdu) \ +{BT_AUDIO_CODEC_DATA(BT_AUDIO_CODEC_CAP_TYPE_FREQ, BT_BYTES_LIST_LE16(_freq)), \ + BT_AUDIO_CODEC_DATA(BT_AUDIO_CODEC_CAP_TYPE_DURATION, (_duration)), \ + BT_AUDIO_CODEC_DATA(BT_AUDIO_CODEC_CAP_TYPE_CHAN_COUNT, (_chan_count)), \ + BT_AUDIO_CODEC_DATA(BT_AUDIO_CODEC_CAP_TYPE_FRAME_LEN, BT_BYTES_LIST_LE16(_len_min), \ + BT_BYTES_LIST_LE16(_len_max)), \ + COND_CODE_1(_max_frames_per_sdu, (), \ + (BT_AUDIO_CODEC_DATA(BT_AUDIO_CODEC_CAP_TYPE_FRAME_COUNT, (_max_frames_per_sdu))))} + +/** + * @brief Helper to declare LC3 codec metadata + * + * @param _prefer_context Preferred contexts (@ref bt_audio_context) + */ +#define BT_AUDIO_CODEC_CAP_LC3_META(_prefer_context) \ +{BT_AUDIO_CODEC_DATA(BT_AUDIO_METADATA_TYPE_PREF_CONTEXT, BT_BYTES_LIST_LE16(_prefer_context))} + +/** + * @brief Helper to declare LC3 codec + * + * @param _freq Supported Sampling Frequencies bitfield (see + * ``BT_AUDIO_CODEC_CAP_FREQ_*``) + * @param _duration Supported Frame Durations bitfield (see + * ``BT_AUDIO_CODEC_CAP_DURATION_*``) + * @param _chan_count Supported channels (see @ref + * BT_AUDIO_CODEC_CAP_CHAN_COUNT_SUPPORT) + * @param _len_min Minimum number of octets supported per codec + * frame + * @param _len_max Maximum number of octets supported per codec + * frame + * @param _max_frames_per_sdu Supported maximum codec frames per SDU + * @param _prefer_context Preferred contexts (@ref bt_audio_context) + */ +#define BT_AUDIO_CODEC_CAP_LC3(_freq, _duration, _chan_count, _len_min, _len_max, \ + _max_frames_per_sdu, _prefer_context) \ +BT_AUDIO_CODEC_CAP(BT_HCI_CODING_FORMAT_LC3, 0x0000, 0x0000, \ + BT_AUDIO_CODEC_CAP_LC3_DATA(_freq, _duration, _chan_count, _len_min, \ + _len_max, _max_frames_per_sdu), \ + BT_AUDIO_CODEC_CAP_LC3_META(_prefer_context)) + +/** + * @brief Helper to declare LC3 codec data configuration + * + * @param _freq Sampling frequency (``BT_AUDIO_CODEC_CFG_FREQ_*``) + * @param _duration Frame duration (``BT_AUDIO_CODEC_CFG_DURATION_*``) + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _len Octets per frame (16-bit integer) + * @param _frames_per_sdu Frames per SDU (8-bit integer). This value is + * optional and will be included only if != 1 + */ +#define BT_AUDIO_CODEC_CFG_LC3_DATA(_freq, _duration, _loc, _len, _frames_per_sdu) \ +{BT_AUDIO_CODEC_DATA(BT_AUDIO_CODEC_CFG_FREQ, (_freq)), \ + BT_AUDIO_CODEC_DATA(BT_AUDIO_CODEC_CFG_DURATION, (_duration)), \ + BT_AUDIO_CODEC_DATA(BT_AUDIO_CODEC_CFG_CHAN_ALLOC, BT_BYTES_LIST_LE32(_loc)), \ + BT_AUDIO_CODEC_DATA(BT_AUDIO_CODEC_CFG_FRAME_LEN, BT_BYTES_LIST_LE16(_len)), \ + COND_CODE_1(_frames_per_sdu, (), \ + (BT_AUDIO_CODEC_DATA(BT_AUDIO_CODEC_CFG_FRAME_BLKS_PER_SDU, (_frames_per_sdu))))} + +/** @brief Helper to declare LC3 codec metadata configuration */ +#define BT_AUDIO_CODEC_CFG_LC3_META(_stream_context) \ +{BT_AUDIO_CODEC_DATA(BT_AUDIO_METADATA_TYPE_STREAM_CONTEXT, BT_BYTES_LIST_LE16(_stream_context))} + +/** + * @brief Helper to declare LC3 codec configuration. + * + * @param _freq Sampling frequency (``BT_AUDIO_CODEC_CFG_FREQ_*``) + * @param _duration Frame duration (``BT_AUDIO_CODEC_CFG_DURATION_*``) + * @param _loc Audio channel location bitfield (@ref + * bt_audio_location) + * @param _len Octets per frame (16-bit integer) + * @param _frames_per_sdu Frames per SDU (8-bit integer) + * @param _stream_context Stream context (``BT_AUDIO_CONTEXT_*``) + */ +#define BT_AUDIO_CODEC_LC3_CONFIG(_freq, _duration, _loc, _len, _frames_per_sdu, _stream_context) \ +BT_AUDIO_CODEC_CFG(BT_HCI_CODING_FORMAT_LC3, 0x0000, 0x0000, \ + BT_AUDIO_CODEC_CFG_LC3_DATA(_freq, _duration, _loc, _len, _frames_per_sdu), \ + BT_AUDIO_CODEC_CFG_LC3_META(_stream_context)) + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_LC3_H */ diff --git a/include/nuttx/net/bluetooth/audio/mcc.h b/include/nuttx/net/bluetooth/audio/mcc.h new file mode 100644 index 0000000000000..8250b76a586fc --- /dev/null +++ b/include/nuttx/net/bluetooth/audio/mcc.h @@ -0,0 +1,1239 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/audio/mcc.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Bluetooth Media Control Client (MCC) APIs. + */ + +/* Copyright (c) 2019 - 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_MCC_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_MCC_H + +/** + * @brief Bluetooth Media Control Client (MCC) interface + * + * Updated to the Media Control Profile specification revision 1.0 + * + * @defgroup bt_mcc Media Control Client (MCC) + * + * @since 3.0 + * @version 0.8.0 + * + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** ** Callback functions + * ***************************************************** + */ + +/** + * @brief Callback function for bt_mcc_discover_mcs() + * + * Called when a media control server is discovered + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + */ + +typedef void (*bt_mcc_discover_mcs_cb)(struct bt_conn *conn, int err); + +/** + * @brief Callback function for bt_mcc_read_player_name() + * + * Called when the player name is read or notified + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param name Player name + */ + +typedef void (*bt_mcc_read_player_name_cb)( + struct bt_conn *conn, int err, const char *name); + +/** + * @brief Callback function for bt_mcc_read_icon_obj_id() + * + * Called when the icon object ID is read + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param icon_id The ID of the Icon Object. This is a UINT48 in a + * uint64_t + */ + +typedef void (*bt_mcc_read_icon_obj_id_cb)( + struct bt_conn *conn, int err, uint64_t icon_id); + +/** + * @brief Callback function for bt_mcc_read_icon_url() + * + * Called when the icon URL is read + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param icon_url The URL of the Icon + */ + +typedef void (*bt_mcc_read_icon_url_cb)( + struct bt_conn *conn, int err, const char *icon_url); + +/** + * @brief Callback function for track changed notifications + * + * Called when a track change is notified. + * + * The track changed characteristic is a special case. It can not be read or + * set, it can only be notified. + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + */ + +typedef void (*bt_mcc_track_changed_ntf_cb)(struct bt_conn *conn, int err); + +/** + * @brief Callback function for bt_mcc_read_track_title() + * + * Called when the track title is read or notified + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param title The title of the track + */ + +typedef void (*bt_mcc_read_track_title_cb)( + struct bt_conn *conn, int err, const char *title); + +/** + * @brief Callback function for bt_mcc_read_track_duration() + * + * Called when the track duration is read or notified + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param dur The duration of the track + */ + +typedef void (*bt_mcc_read_track_duration_cb)( + struct bt_conn *conn, int err, int32_t dur); + +/** + * @brief Callback function for bt_mcc_read_track_position() + * + * Called when the track position is read or notified + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param pos The Track Position + */ + +typedef void (*bt_mcc_read_track_position_cb)( + struct bt_conn *conn, int err, int32_t pos); + +/** + * @brief Callback function for bt_mcc_set_track_position() + * + * Called when the track position is set + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param pos The Track Position set (or attempted to set) + */ + +typedef void (*bt_mcc_set_track_position_cb)( + struct bt_conn *conn, int err, int32_t pos); + +/** + * @brief Callback function for bt_mcc_read_playback_speed() + * + * Called when the playback speed is read or notified + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param speed The Playback Speed + */ + +typedef void (*bt_mcc_read_playback_speed_cb)( + struct bt_conn *conn, int err, int8_t speed); + +/** + * @brief Callback function for bt_mcc_set_playback_speed() + * + * Called when the playback speed is set + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param speed The Playback Speed set (or attempted to set) + */ + +typedef void (*bt_mcc_set_playback_speed_cb)( + struct bt_conn *conn, int err, int8_t speed); + +/** + * @brief Callback function for bt_mcc_read_seeking_speed() + * + * Called when the seeking speed is read or notified + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param speed The Seeking Speed + */ + +typedef void (*bt_mcc_read_seeking_speed_cb)( + struct bt_conn *conn, int err, int8_t speed); + +/** + * @brief Callback function for bt_mcc_read_segments_obj_id() + * + * Called when the track segments object ID is read + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param id The Track Segments Object ID (UINT48) + */ + +typedef void (*bt_mcc_read_segments_obj_id_cb)( + struct bt_conn *conn, int err, uint64_t id); + +/** + * @brief Callback function for bt_mcc_read_current_track_obj_id() + * + * Called when the current track object ID is read or notified + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param id The Current Track Object ID (UINT48) + */ + +typedef void (*bt_mcc_read_current_track_obj_id_cb)( + struct bt_conn *conn, int err, uint64_t id); + +/** + * @brief Callback function for bt_mcc_set_current_track_obj_id() + * + * Called when the current track object ID is set + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param id The Object ID (UINT48) set (or attempted to set) + */ + +typedef void (*bt_mcc_set_current_track_obj_id_cb)( + struct bt_conn *conn, int err, uint64_t id); + +/** + * @brief Callback function for bt_mcc_read_next_track_obj_id() + * + * Called when the next track object ID is read or notified + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param id The Next Track Object ID (UINT48) + */ + +typedef void (*bt_mcc_read_next_track_obj_id_cb)( + struct bt_conn *conn, int err, uint64_t id); + +/** + * @brief Callback function for bt_mcc_set_next_track_obj_id() + * + * Called when the next track object ID is set + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param id The Object ID (UINT48) set (or attempted to set) + */ + +typedef void (*bt_mcc_set_next_track_obj_id_cb)( + struct bt_conn *conn, int err, uint64_t id); + +/** + * @brief Callback function for bt_mcc_read_parent_group_obj_id() + * + * Called when the parent group object ID is read or notified + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param id The Parent Group Object ID (UINT48) + */ + +typedef void (*bt_mcc_read_parent_group_obj_id_cb)( + struct bt_conn *conn, int err, uint64_t id); + +/** + * @brief Callback function for bt_mcc_read_current_group_obj_id() + * + * Called when the current group object ID is read or notified + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param id The Current Group Object ID (UINT48) + */ + +typedef void (*bt_mcc_read_current_group_obj_id_cb)( + struct bt_conn *conn, int err, uint64_t id); + +/** + * @brief Callback function for bt_mcc_set_current_group_obj_id() + * + * Called when the current group object ID is set + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param obj_id The Object ID (UINT48) set (or attempted to set) + */ + +typedef void (*bt_mcc_set_current_group_obj_id_cb)( + struct bt_conn *conn, int err, uint64_t obj_id); + +/** + * @brief Callback function for bt_mcc_read_playing_order() + * + * Called when the playing order is read or notified + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param order The playback order + */ + +typedef void (*bt_mcc_read_playing_order_cb)( + struct bt_conn *conn, int err, uint8_t order); + +/** + * @brief Callback function for bt_mcc_set_playing_order() + * + * Called when the playing order is set + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param order The Playing Order set (or attempted to set) + */ + +typedef void (*bt_mcc_set_playing_order_cb)( + struct bt_conn *conn, int err, uint8_t order); + +/** + * @brief Callback function for bt_mcc_read_playing_orders_supported() + * + * Called when the supported playing orders are read or notified + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param orders The playing orders supported (bitmap) + */ + +typedef void (*bt_mcc_read_playing_orders_supported_cb)( + struct bt_conn *conn, int err, uint16_t orders); + +/** + * @brief Callback function for bt_mcc_read_media_state() + * + * Called when the media state is read or notified + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param state The Media State + */ + +typedef void (*bt_mcc_read_media_state_cb)( + struct bt_conn *conn, int err, uint8_t state); + +/** + * @brief Callback function for bt_mcc_send_cmd() + * + * Called when a command is sent, i.e. when the media control point is set + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param cmd The command sent + */ + +typedef void (*bt_mcc_send_cmd_cb)( + struct bt_conn *conn, int err, const struct mpl_cmd *cmd); + +/** + * @brief Callback function for command notifications + * + * Called when the media control point is notified + * + * Notifications for commands (i.e. for writes to the media control point) + * use a different parameter structure than what is used for sending commands + * (writing to the media control point) + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param ntf The command notification + */ + +typedef void (*bt_mcc_cmd_ntf_cb)( + struct bt_conn *conn, int err, const struct mpl_cmd_ntf *ntf); + +/** + * @brief Callback function for bt_mcc_read_opcodes_supported() + * + * Called when the supported opcodes (commands) are read or notified + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param opcodes The supported opcodes + */ + +typedef void (*bt_mcc_read_opcodes_supported_cb)( + struct bt_conn *conn, int err, uint32_t opcodes); + +/** + * @brief Callback function for bt_mcc_send_search() + * + * Called when a search is sent, i.e. when the search control point is set + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param search The search set (or attempted to set) + */ + +typedef void (*bt_mcc_send_search_cb)( + struct bt_conn *conn, int err, const struct mpl_search *search); + +/** + * @brief Callback function for search notifications + * + * Called when the search control point is notified + * + * Notifications for searches (i.e. for writes to the search control point) + * use a different parameter structure than what is used for sending searches + * (writing to the search control point) + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param result_code The search notification + */ + +typedef void (*bt_mcc_search_ntf_cb)( + struct bt_conn *conn, int err, uint8_t result_code); + +/** + * @brief Callback function for bt_mcc_read_search_results_obj_id() + * + * Called when the search results object ID is read or notified + * + * Note that the Search Results Object ID value may be zero, in case the + * characteristic does not exist on the server. (This will be the case if + * there has not been a successful search.) + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param id The Search Results Object ID (UINT48) + */ + +typedef void (*bt_mcc_read_search_results_obj_id_cb)( + struct bt_conn *conn, int err, uint64_t id); + +/** + * @brief Callback function for bt_mcc_read_content_control_id() + * + * Called when the content control ID is read + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param ccid The Content Control ID + */ + +typedef void (*bt_mcc_read_content_control_id_cb)( + struct bt_conn *conn, int err, uint8_t ccid); + +/** ** Callback functions for the included Object Transfer service + * ************ + */ + +/** + * @brief Callback function for object selected + * + * Called when an object is selected + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + */ + +typedef void (*bt_mcc_otc_obj_selected_cb)(struct bt_conn *conn, int err); + +/** + * @brief Callback function for bt_mcc_otc_read_object_metadata() + * + * Called when object metadata is read + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + */ + +typedef void (*bt_mcc_otc_obj_metadata_cb)(struct bt_conn *conn, int err); + +/** + * @brief Callback function for bt_mcc_otc_read_icon_object() + * + * Called when the icon object is read + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param buf Buffer containing the object contents + * + * If err is EMSGSIZE, the object contents have been truncated. + */ + +typedef void (*bt_mcc_otc_read_icon_object_cb)( + struct bt_conn *conn, int err, struct net_buf_simple *buf); + +/** + * @brief Callback function for bt_mcc_otc_read_track_segments_object() + * + * Called when the track segments object is read + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param buf Buffer containing the object contents + * + * If err is EMSGSIZE, the object contents have been truncated. + */ + +typedef void (*bt_mcc_otc_read_track_segments_object_cb)( + struct bt_conn *conn, int err, struct net_buf_simple *buf); + +/** + * @brief Callback function for bt_mcc_otc_read_current_track_object() + * + * Called when the current track object is read + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param buf Buffer containing the object contents + * + * If err is EMSGSIZE, the object contents have been truncated. + */ + +typedef void (*bt_mcc_otc_read_current_track_object_cb)( + struct bt_conn *conn, int err, struct net_buf_simple *buf); + +/** + * @brief Callback function for bt_mcc_otc_read_next_track_object() + * + * Called when the next track object is read + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param buf Buffer containing the object contents + * + * If err is EMSGSIZE, the object contents have been truncated. + */ + +typedef void (*bt_mcc_otc_read_next_track_object_cb)( + struct bt_conn *conn, int err, struct net_buf_simple *buf); + +/** + * @brief Callback function for bt_mcc_otc_read_parent_group_object() + * + * Called when the parent group object is read + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param buf Buffer containing the object contents + * + * If err is EMSGSIZE, the object contents have been truncated. + */ + +typedef void (*bt_mcc_otc_read_parent_group_object_cb)( + struct bt_conn *conn, int err, struct net_buf_simple *buf); + +/** + * @brief Callback function for bt_mcc_otc_read_current_group_object() + * + * Called when the current group object is read + * + * @param conn The connection that was used to initialise the media + * control client + * @param err Error value. 0 on success, GATT error or errno on + * fail + * @param buf Buffer containing the object contents + * + * If err is EMSGSIZE, the object contents have been truncated. + */ + +typedef void (*bt_mcc_otc_read_current_group_object_cb)( + struct bt_conn *conn, int err, struct net_buf_simple *buf); + +/** + * @brief Media control client callbacks + */ + +struct bt_mcc_cb +{ + /** Callback when discovery has finished */ + + bt_mcc_discover_mcs_cb discover_mcs; + + /** Callback when reading the player name */ + + bt_mcc_read_player_name_cb read_player_name; +#if defined(CONFIG_BT_OTS_CLIENT) || defined(__DOXYGEN__) + /** Callback when reading the icon object ID */ + + bt_mcc_read_icon_obj_id_cb read_icon_obj_id; +#endif /* CONFIG_BT_OTS_CLIENT */ +#if defined(CONFIG_BT_MCC_READ_MEDIA_PLAYER_ICON_URL) || defined(__DOXYGEN__) + /** Callback when reading the icon URL */ + bt_mcc_read_icon_url_cb read_icon_url; +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_PLAYER_ICON_URL) */ + /** Callback when getting a track changed notification */ + bt_mcc_track_changed_ntf_cb track_changed_ntf; +#if defined(CONFIG_BT_MCC_READ_TRACK_TITLE) || defined(__DOXYGEN__) + /** Callback when reading the track title */ + + bt_mcc_read_track_title_cb read_track_title; +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_TITLE) */ +#if defined(CONFIG_BT_MCC_READ_TRACK_DURATION) || defined(__DOXYGEN__) + /** Callback when reading the track duration */ + bt_mcc_read_track_duration_cb read_track_duration; +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_DURATION) */ +#if defined(CONFIG_BT_MCC_READ_TRACK_POSITION) || defined(__DOXYGEN__) + /** Callback when reading the track position */ + bt_mcc_read_track_position_cb read_track_position; +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_POSITION) */ +#if defined(CONFIG_BT_MCC_SET_TRACK_POSITION) || defined(__DOXYGEN__) + /** Callback when setting the track position */ + bt_mcc_set_track_position_cb set_track_position; +#endif /* defined(CONFIG_BT_MCC_SET_TRACK_POSITION) */ +#if defined(CONFIG_BT_MCC_READ_PLAYBACK_SPEED) || defined(__DOXYGEN__) + /** Callback when reading the playback speed */ + bt_mcc_read_playback_speed_cb read_playback_speed; +#endif /* defined (CONFIG_BT_MCC_READ_PLAYBACK_SPEED) */ +#if defined(CONFIG_BT_MCC_SET_PLAYBACK_SPEED) || defined(__DOXYGEN__) + /** Callback when setting the playback speed */ + bt_mcc_set_playback_speed_cb set_playback_speed; +#endif /* defined (CONFIG_BT_MCC_SET_PLAYBACK_SPEED) */ +#if defined(CONFIG_BT_MCC_READ_SEEKING_SPEED) || defined(__DOXYGEN__) + /** Callback when reading the seeking speed */ + bt_mcc_read_seeking_speed_cb read_seeking_speed; +#endif /* defined (CONFIG_BT_MCC_READ_SEEKING_SPEED) */ +#if defined(CONFIG_BT_OTS_CLIENT) || defined(__DOXYGEN__) + /** Callback when reading the segments object ID */ + bt_mcc_read_segments_obj_id_cb read_segments_obj_id; + + /** Callback when reading the current track object ID */ + + bt_mcc_read_current_track_obj_id_cb read_current_track_obj_id; + + /** Callback when setting the current track object ID */ + + bt_mcc_set_current_track_obj_id_cb set_current_track_obj_id; + + /** Callback when reading the next track object ID */ + + bt_mcc_read_next_track_obj_id_cb read_next_track_obj_id; + + /** Callback when setting the next track object ID */ + + bt_mcc_set_next_track_obj_id_cb set_next_track_obj_id; + + /** Callback when reading the current group object ID */ + + bt_mcc_read_current_group_obj_id_cb read_current_group_obj_id; + + /** Callback when setting the current group object ID */ + + bt_mcc_set_current_group_obj_id_cb set_current_group_obj_id; + + /** Callback when reading the parent group object ID */ + + bt_mcc_read_parent_group_obj_id_cb read_parent_group_obj_id; +#endif /* CONFIG_BT_OTS_CLIENT */ +#if defined(CONFIG_BT_MCC_READ_PLAYING_ORDER) || defined(__DOXYGEN__) + /** Callback when reading the playing order */ + bt_mcc_read_playing_order_cb read_playing_order; +#endif /* defined(CONFIG_BT_MCC_READ_PLAYING_ORDER) */ +#if defined(CONFIG_BT_MCC_SET_PLAYING_ORDER) || defined(__DOXYGEN__) + /** Callback when setting the playing order */ + bt_mcc_set_playing_order_cb set_playing_order; +#endif /* defined(CONFIG_BT_MCC_SET_PLAYING_ORDER) */ +#if defined(CONFIG_BT_MCC_READ_PLAYING_ORDER_SUPPORTED) || defined(__DOXYGEN__) + /** Callback when reading the supported playing orders */ + bt_mcc_read_playing_orders_supported_cb read_playing_orders_supported; +#endif /* defined(CONFIG_BT_MCC_READ_PLAYING_ORDER_SUPPORTED) */ +#if defined(CONFIG_BT_MCC_READ_MEDIA_STATE) || defined(__DOXYGEN__) + /** Callback when reading the media state */ + bt_mcc_read_media_state_cb read_media_state; +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_STATE) */ +#if defined(CONFIG_BT_MCC_SET_MEDIA_CONTROL_POINT) || defined(__DOXYGEN__) + /** Callback when sending a command */ + bt_mcc_send_cmd_cb send_cmd; +#endif /* defined(CONFIG_BT_MCC_SET_MEDIA_CONTROL_POINT) */ + /** Callback command notifications */ + bt_mcc_cmd_ntf_cb cmd_ntf; +#if defined(CONFIG_BT_MCC_READ_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED) || defined(__DOXYGEN__) + /** Callback when reading the supported opcodes */ + + bt_mcc_read_opcodes_supported_cb read_opcodes_supported; +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED) */ +#if defined(CONFIG_BT_OTS_CLIENT) || defined(__DOXYGEN__) + /** Callback when sending the a search query */ + bt_mcc_send_search_cb send_search; + + /** Callback when receiving a search notification */ + + bt_mcc_search_ntf_cb search_ntf; + + /** Callback when reading the search results object ID */ + + bt_mcc_read_search_results_obj_id_cb read_search_results_obj_id; +#endif /* CONFIG_BT_OTS_CLIENT */ +#if defined(CONFIG_BT_MCC_READ_CONTENT_CONTROL_ID) || defined(__DOXYGEN__) + /** Callback when reading the content control ID */ + bt_mcc_read_content_control_id_cb read_content_control_id; +#endif /* defined(CONFIG_BT_MCC_READ_CONTENT_CONTROL_ID) */ +#if defined(CONFIG_BT_OTS_CLIENT) || defined(__DOXYGEN__) + /** Callback when selecting an object */ + bt_mcc_otc_obj_selected_cb otc_obj_selected; + + /** Callback when receiving the current object metadata */ + + bt_mcc_otc_obj_metadata_cb otc_obj_metadata; + + /** Callback when reading the current icon object */ + + bt_mcc_otc_read_icon_object_cb otc_icon_object; + + /** Callback when reading the track segments object */ + + bt_mcc_otc_read_track_segments_object_cb otc_track_segments_object; + + /** Callback when reading the current track object */ + + bt_mcc_otc_read_current_track_object_cb otc_current_track_object; + + /** Callback when reading the next track object */ + + bt_mcc_otc_read_next_track_object_cb otc_next_track_object; + + /** Callback when reading the current group object */ + + bt_mcc_otc_read_current_group_object_cb otc_current_group_object; + + /** Callback when reading the parent group object */ + + bt_mcc_otc_read_parent_group_object_cb otc_parent_group_object; +#endif /* CONFIG_BT_OTS_CLIENT */ +}; + +/** ** Functions + * ************************************************************** + */ + +/** + * @brief Initialize Media Control Client + * + * @param cb Callbacks to be used + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_init(struct bt_mcc_cb *cb); + +/** + * @brief Discover Media Control Service + * + * Discover Media Control Service (MCS) on the server given by the connection + * Optionally subscribe to notifications. + * + * Shall be called once, after media control client initialization and before + * using other media control client functionality. + * + * @param conn Connection to the peer device + * @param subscribe Whether to subscribe to notifications + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_discover_mcs(struct bt_conn *conn, bool subscribe); + +/** + * @brief Read Media Player Name + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_read_player_name(struct bt_conn *conn); + +/** + * @brief Read Icon Object ID + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_read_icon_obj_id(struct bt_conn *conn); + +/** + * @brief Read Icon Object URL + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_read_icon_url(struct bt_conn *conn); + +/** + * @brief Read Track Title + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_read_track_title(struct bt_conn *conn); + +/** + * @brief Read Track Duration + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_read_track_duration(struct bt_conn *conn); + +/** + * @brief Read Track Position + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_read_track_position(struct bt_conn *conn); + +/** + * @brief Set Track position + * + * @param conn Connection to the peer device + * @param pos Track position + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_set_track_position(struct bt_conn *conn, int32_t pos); + +/** + * @brief Read Playback speed + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_read_playback_speed(struct bt_conn *conn); + +/** + * @brief Set Playback Speed + * + * @param conn Connection to the peer device + * @param speed Playback speed + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_set_playback_speed(struct bt_conn *conn, int8_t speed); + +/** + * @brief Read Seeking speed + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_read_seeking_speed(struct bt_conn *conn); + +/** + * @brief Read Track Segments Object ID + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_read_segments_obj_id(struct bt_conn *conn); + +/** + * @brief Read Current Track Object ID + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_read_current_track_obj_id(struct bt_conn *conn); + +/** + * @brief Set Current Track Object ID + * + * Set the Current Track to the track given by the @p id parameter + * + * @param conn Connection to the peer device + * @param id Object Transfer Service ID (UINT48) of the track to set as + * the current track + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_set_current_track_obj_id(struct bt_conn *conn, uint64_t id); + +/** + * @brief Read Next Track Object ID + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_read_next_track_obj_id(struct bt_conn *conn); + +/** + * @brief Set Next Track Object ID + * + * Set the Next Track to the track given by the @p id parameter + * + * @param conn Connection to the peer device + * @param id Object Transfer Service ID (UINT48) of the track to set as + * the next track + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_set_next_track_obj_id(struct bt_conn *conn, uint64_t id); + +/** + * @brief Read Current Group Object ID + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_read_current_group_obj_id(struct bt_conn *conn); + +/** + * @brief Set Current Group Object ID + * + * Set the Current Group to the group given by the @p id parameter + * + * @param conn Connection to the peer device + * @param id Object Transfer Service ID (UINT48) of the group to set as + * the current group + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_set_current_group_obj_id(struct bt_conn *conn, uint64_t id); + +/** + * @brief Read Parent Group Object ID + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_read_parent_group_obj_id(struct bt_conn *conn); + +/** + * @brief Read Playing Order + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_read_playing_order(struct bt_conn *conn); + +/** + * @brief Set Playing Order + * + * @param conn Connection to the peer device + * @param order Playing order + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_set_playing_order(struct bt_conn *conn, uint8_t order); + +/** + * @brief Read Playing Orders Supported + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_read_playing_orders_supported(struct bt_conn *conn); + +/** + * @brief Read Media State + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_read_media_state(struct bt_conn *conn); + +/** + * @brief Send a command + * + * Write a command (e.g. "play", "pause") to the server's media control + * point. + * + * @param conn Connection to the peer device + * @param cmd The command to send + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_send_cmd(struct bt_conn *conn, const struct mpl_cmd *cmd); + +/** + * @brief Read Opcodes Supported + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_read_opcodes_supported(struct bt_conn *conn); + +/** + * @brief Send a Search command + * + * Write a search to the server's search control point. + * + * @param conn Connection to the peer device + * @param search The search + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_send_search(struct bt_conn *conn, + const struct mpl_search *search); + +/** + * @brief Search Results Group Object ID + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_read_search_results_obj_id(struct bt_conn *conn); + +/** + * @brief Read Content Control ID + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_read_content_control_id(struct bt_conn *conn); + +/** + * @brief Read the current object metadata + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_otc_read_object_metadata(struct bt_conn *conn); + +/** + * @brief Read the Icon Object + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_otc_read_icon_object(struct bt_conn *conn); + +/** + * @brief Read the Track Segments Object + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_otc_read_track_segments_object(struct bt_conn *conn); + +/** + * @brief Read the Current Track Object + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_otc_read_current_track_object(struct bt_conn *conn); + +/** + * @brief Read the Next Track Object + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_otc_read_next_track_object(struct bt_conn *conn); + +/** + * @brief Read the Current Group Object + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_otc_read_current_group_object(struct bt_conn *conn); + +/** + * @brief Read the Parent Group Object + * + * @param conn Connection to the peer device + * + * @return 0 if success, errno on failure. + */ + +int bt_mcc_otc_read_parent_group_object(struct bt_conn *conn); + +/** + * @brief Look up MCC OTC instance + * + * @param conn The connection to the MCC server. + * + * @return Pointer to a MCC OTC instance if found else NULL. + * + */ + +struct bt_ots_client *bt_mcc_otc_inst(struct bt_conn *conn); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_MCC_H */ diff --git a/include/nuttx/net/bluetooth/audio/mcs.h b/include/nuttx/net/bluetooth/audio/mcs.h new file mode 100644 index 0000000000000..2af4be3181c3d --- /dev/null +++ b/include/nuttx/net/bluetooth/audio/mcs.h @@ -0,0 +1,400 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/audio/mcs.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Bluetooth Media Control Service (MCS) APIs. + */ + +/* Copyright (c) 2019 - 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_MCS_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_MCS_H + +/** + * @brief Media Control Service (MCS) + * + * @defgroup bt_mcs Media Control Service (MCS) + * + * @since 3.0 + * @version 0.8.0 + * + * @ingroup bluetooth + * @{ + * + * Definitions and types related to the Media Control Service and Media + * Control Profile specifications. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * A characteristic value has changed while a Read Long Value Characteristic + * sub-procedure is in progress. + */ +#define BT_MCS_ERR_LONG_VAL_CHANGED 0x80U + +/** + * @name Playback speeds + * + * The playback speed (s) is calculated by the value of 2 to the power of p + * divided by 64. All values from -128 to 127 allowed, only some examples + * defined. @{ + */ + +/** Minimum playback speed, resulting in 25 % speed */ +#define BT_MCS_PLAYBACK_SPEED_MIN -128 +/** Quarter playback speed, resulting in 25 % speed */ +#define BT_MCS_PLAYBACK_SPEED_QUARTER -128 +/** Half playback speed, resulting in 50 % speed */ +#define BT_MCS_PLAYBACK_SPEED_HALF -64 +/** Unity playback speed, resulting in 100 % speed */ +#define BT_MCS_PLAYBACK_SPEED_UNITY 0 +/** Double playback speed, resulting in 200 % speed */ +#define BT_MCS_PLAYBACK_SPEED_DOUBLE 64 +/** Max playback speed, resulting in 395.7 % speed (nearly 400 %) */ +#define BT_MCS_PLAYBACK_SPEED_MAX 127 +/** @} */ + +/** + * @name Seeking speed + * + * The allowed values for seeking speed are the range -64 to -4 (endpoints + * included), the value 0, and the range 4 to 64 (endpoints included). @{ + */ + +/** Maximum seeking speed - Can be negated */ +#define BT_MCS_SEEKING_SPEED_FACTOR_MAX 64 +/** Minimum seeking speed - Can be negated */ +#define BT_MCS_SEEKING_SPEED_FACTOR_MIN 4 +/** No seeking */ +#define BT_MCS_SEEKING_SPEED_FACTOR_ZERO 0 +/** @} */ + +/** + * @name Playing orders + * @{ + */ + +/** A single track is played once; there is no next track. */ +#define BT_MCS_PLAYING_ORDER_SINGLE_ONCE 0x01U +/** A single track is played repeatedly; the next track is the current track. + */ +#define BT_MCS_PLAYING_ORDER_SINGLE_REPEAT 0x02U +/** The tracks within a group are played once in track order. */ +#define BT_MCS_PLAYING_ORDER_INORDER_ONCE 0x03U +/** The tracks within a group are played in track order repeatedly. */ +#define BT_MCS_PLAYING_ORDER_INORDER_REPEAT 0x04U +/** The tracks within a group are played once only from the oldest first. */ +#define BT_MCS_PLAYING_ORDER_OLDEST_ONCE 0x05U +/** The tracks within a group are played from the oldest first repeatedly. */ +#define BT_MCS_PLAYING_ORDER_OLDEST_REPEAT 0x06U +/** The tracks within a group are played once only from the newest first. */ +#define BT_MCS_PLAYING_ORDER_NEWEST_ONCE 0x07U +/** The tracks within a group are played from the newest first repeatedly. */ +#define BT_MCS_PLAYING_ORDER_NEWEST_REPEAT 0x08U +/** The tracks within a group are played in random order once. */ +#define BT_MCS_PLAYING_ORDER_SHUFFLE_ONCE 0x09U +/** The tracks within a group are played in random order repeatedly. */ +#define BT_MCS_PLAYING_ORDER_SHUFFLE_REPEAT 0x0AU +/** @} */ + +/** @name Playing orders supported + * + * A bitmap, in the same order as the playing orders above. Note that playing + * order 1 corresponds to bit 0, and so on. @{ + */ + +/** A single track is played once; there is no next track. */ +#define BT_MCS_PLAYING_ORDERS_SUPPORTED_SINGLE_ONCE BIT(0U) +/** A single track is played repeatedly; the next track is the current track. + */ +#define BT_MCS_PLAYING_ORDERS_SUPPORTED_SINGLE_REPEAT BIT(1U) +/** The tracks within a group are played once in track order. */ +#define BT_MCS_PLAYING_ORDERS_SUPPORTED_INORDER_ONCE BIT(2U) +/** The tracks within a group are played in track order repeatedly. */ +#define BT_MCS_PLAYING_ORDERS_SUPPORTED_INORDER_REPEAT BIT(3U) +/** The tracks within a group are played once only from the oldest first. */ +#define BT_MCS_PLAYING_ORDERS_SUPPORTED_OLDEST_ONCE BIT(4U) +/** The tracks within a group are played from the oldest first repeatedly. */ +#define BT_MCS_PLAYING_ORDERS_SUPPORTED_OLDEST_REPEAT BIT(5U) +/** The tracks within a group are played once only from the newest first. */ +#define BT_MCS_PLAYING_ORDERS_SUPPORTED_NEWEST_ONCE BIT(6U) +/** The tracks within a group are played from the newest first repeatedly. */ +#define BT_MCS_PLAYING_ORDERS_SUPPORTED_NEWEST_REPEAT BIT(7U) +/** The tracks within a group are played in random order once. */ +#define BT_MCS_PLAYING_ORDERS_SUPPORTED_SHUFFLE_ONCE BIT(8U) +/** The tracks within a group are played in random order repeatedly. */ +#define BT_MCS_PLAYING_ORDERS_SUPPORTED_SHUFFLE_REPEAT BIT(9U) +/** @} */ + +/** + * @name Media states + * @{ + */ + +/** The current track is invalid, and no track has been selected. */ +#define BT_MCS_MEDIA_STATE_INACTIVE 0x00U +/** The media player is playing the current track. */ +#define BT_MCS_MEDIA_STATE_PLAYING 0x01U +/** The current track is paused. The media player has a current track, but it + * is not being played + */ +#define BT_MCS_MEDIA_STATE_PAUSED 0x02U +/** The current track is fast forwarding or fast rewinding. */ +#define BT_MCS_MEDIA_STATE_SEEKING 0x03U +/** @} */ + +/** + * @name Media control point opcodes + * @{ + */ + +/** Start playing the current track. */ +#define BT_MCS_OPC_PLAY 0x01U +/** Pause playing the current track. */ +#define BT_MCS_OPC_PAUSE 0x02U +/** Fast rewind the current track. */ +#define BT_MCS_OPC_FAST_REWIND 0x03U +/** Fast forward the current track. */ +#define BT_MCS_OPC_FAST_FORWARD 0x04U +/** + * Stop current activity and return to the paused state and set the current + * track position to the start of the current track. + */ +#define BT_MCS_OPC_STOP 0x05U + +/** Set a new current track position relative to the current track position. + */ +#define BT_MCS_OPC_MOVE_RELATIVE 0x10U + +/** + * Set the current track position to the starting position of the previous + * segment of the current track. + */ +#define BT_MCS_OPC_PREV_SEGMENT 0x20U +/** + * Set the current track position to the starting position of the next + * segment of the current track. + */ +#define BT_MCS_OPC_NEXT_SEGMENT 0x21U +/** + * Set the current track position to the starting position of the first + * segment of the current track. + */ +#define BT_MCS_OPC_FIRST_SEGMENT 0x22U +/** + * Set the current track position to the starting position of the last + * segment of the current track. + */ +#define BT_MCS_OPC_LAST_SEGMENT 0x23U +/** + * Set the current track position to the starting position of the nth segment + * of the current track. + */ +#define BT_MCS_OPC_GOTO_SEGMENT 0x24U + +/** Set the current track to the previous track based on the playing order. */ +#define BT_MCS_OPC_PREV_TRACK 0x30U +/** Set the current track to the next track based on the playing order. */ +#define BT_MCS_OPC_NEXT_TRACK 0x31U +/** Set the current track to the first track based on the playing order. */ +#define BT_MCS_OPC_FIRST_TRACK 0x32U +/** Set the current track to the last track based on the playing order. */ +#define BT_MCS_OPC_LAST_TRACK 0x33U +/** Set the current track to the nth track based on the playing order. */ +#define BT_MCS_OPC_GOTO_TRACK 0x34U + +/** Set the current group to the previous group in the sequence of groups. */ +#define BT_MCS_OPC_PREV_GROUP 0x40U +/** Set the current group to the next group in the sequence of groups. */ +#define BT_MCS_OPC_NEXT_GROUP 0x41U +/** Set the current group to the first group in the sequence of groups. */ +#define BT_MCS_OPC_FIRST_GROUP 0x42U +/** Set the current group to the last group in the sequence of groups. */ +#define BT_MCS_OPC_LAST_GROUP 0x43U +/** Set the current group to the nth group in the sequence of groups. */ +#define BT_MCS_OPC_GOTO_GROUP 0x44U +/** @} */ + +/** Media control point supported opcodes length */ +#define BT_MCS_OPCODES_SUPPORTED_LEN 4U + +/** + * @name Media control point supported opcodes values + * @{ + */ + +/** Support the Play opcode */ +#define BT_MCS_OPC_SUP_PLAY BIT(0U) +/** Support the Pause opcode */ +#define BT_MCS_OPC_SUP_PAUSE BIT(1U) +/** Support the Fast Rewind opcode */ +#define BT_MCS_OPC_SUP_FAST_REWIND BIT(2U) +/** Support the Fast Forward opcode */ +#define BT_MCS_OPC_SUP_FAST_FORWARD BIT(3U) +/** Support the Stop opcode */ +#define BT_MCS_OPC_SUP_STOP BIT(4U) + +/** Support the Move Relative opcode */ +#define BT_MCS_OPC_SUP_MOVE_RELATIVE BIT(5U) + +/** Support the Previous Segment opcode */ +#define BT_MCS_OPC_SUP_PREV_SEGMENT BIT(6U) +/** Support the Next Segment opcode */ +#define BT_MCS_OPC_SUP_NEXT_SEGMENT BIT(7U) +/** Support the First Segment opcode */ +#define BT_MCS_OPC_SUP_FIRST_SEGMENT BIT(8U) +/** Support the Last Segment opcode */ +#define BT_MCS_OPC_SUP_LAST_SEGMENT BIT(9U) +/** Support the Goto Segment opcode */ +#define BT_MCS_OPC_SUP_GOTO_SEGMENT BIT(10U) + +/** Support the Previous Track opcode */ +#define BT_MCS_OPC_SUP_PREV_TRACK BIT(11U) +/** Support the Next Track opcode */ +#define BT_MCS_OPC_SUP_NEXT_TRACK BIT(12U) +/** Support the First Track opcode */ +#define BT_MCS_OPC_SUP_FIRST_TRACK BIT(13U) +/** Support the Last Track opcode */ +#define BT_MCS_OPC_SUP_LAST_TRACK BIT(14U) +/** Support the Goto Track opcode */ +#define BT_MCS_OPC_SUP_GOTO_TRACK BIT(15U) + +/** Support the Previous Group opcode */ +#define BT_MCS_OPC_SUP_PREV_GROUP BIT(16U) +/** Support the Next Group opcode */ +#define BT_MCS_OPC_SUP_NEXT_GROUP BIT(17U) +/** Support the First Group opcode */ +#define BT_MCS_OPC_SUP_FIRST_GROUP BIT(18U) +/** Support the Last Group opcode */ +#define BT_MCS_OPC_SUP_LAST_GROUP BIT(19U) +/** Support the Goto Group opcode */ +#define BT_MCS_OPC_SUP_GOTO_GROUP BIT(20U) +/** @} */ + +/** + * @name Media control point notification result codes + * @{ + */ + +/** Action requested by the opcode write was completed successfully. */ +#define BT_MCS_OPC_NTF_SUCCESS 0x01U +/** An invalid or unsupported opcode was used for the Media Control Point + * write. + */ +#define BT_MCS_OPC_NTF_NOT_SUPPORTED 0x02U +/** + * The Media Player State characteristic value is Inactive when the opcode is + * received or the result of the requested action of the opcode results in + * the Media Player State characteristic being set to Inactive. + */ +#define BT_MCS_OPC_NTF_PLAYER_INACTIVE 0x03U +/** + * The requested action of any Media Control Point write cannot be completed + * successfully because of a condition within the player. + */ +#define BT_MCS_OPC_NTF_CANNOT_BE_COMPLETED 0x04U +/** @} */ + +/** + * @name Search control point type values + * + * Reference: Media Control Service spec v1.0 section 3.20.2 @{ + */ + +/** Search for Track Name */ +#define BT_MCS_SEARCH_TYPE_TRACK_NAME 0x01U +/** Search for Artist Name */ +#define BT_MCS_SEARCH_TYPE_ARTIST_NAME 0x02U +/** Search for Album Name */ +#define BT_MCS_SEARCH_TYPE_ALBUM_NAME 0x03U +/** Search for Group Name */ +#define BT_MCS_SEARCH_TYPE_GROUP_NAME 0x04U +/** Search for Earliest Year */ +#define BT_MCS_SEARCH_TYPE_EARLIEST_YEAR 0x05U +/** Search for Latest Year */ +#define BT_MCS_SEARCH_TYPE_LATEST_YEAR 0x06U +/** Search for Genre */ +#define BT_MCS_SEARCH_TYPE_GENRE 0x07U +/** Search for Tracks only */ +#define BT_MCS_SEARCH_TYPE_ONLY_TRACKS 0x08U +/** Search for Groups only */ +#define BT_MCS_SEARCH_TYPE_ONLY_GROUPS 0x09U +/** @} */ + +/** + * @brief Search control point minimum length + * + * At least one search control item (SCI), consisting of the length octet and + * the type octet. (The * parameter field may be empty.) + */ +#define SEARCH_LEN_MIN 2U + +/** Search control point maximum length */ +#define SEARCH_LEN_MAX 64U + +/** + * @brief Search control point item (SCI) minimum length + * + * An SCI length can be as little as one byte, for an SCI that has only the + * type field. (The SCI len is the length of type + param.) + */ +#define SEARCH_SCI_LEN_MIN \ +1 /* An SCI length can be as little as one byte, \ + * for an SCI that has only the type field. \ (The SCI len is the length of + * type + param.) \ + */ + +/** Search parameters maximum length */ +#define SEARCH_PARAM_MAX 62U + +/** + * @name Search notification result codes + * + * Reference: Media Control Service spec v1.0 section 3.20.2 @{ + */ + +/** Search request was accepted; search has started. */ +#define BT_MCS_SCP_NTF_SUCCESS 0x01U +/** Search request was invalid; no search started. */ +#define BT_MCS_SCP_NTF_FAILURE 0x02U +/** @} */ + +/** + * @name Group object object types + * + * Reference: Media Control Service spec v1.0 section 4.4.1 @{ + */ + +/** Group object type is track */ +#define BT_MCS_GROUP_OBJECT_TRACK_TYPE 0x00U +/** Group object type is group */ +#define BT_MCS_GROUP_OBJECT_GROUP_TYPE 0x01U +/** @} */ + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_MCS_H */ diff --git a/include/nuttx/net/bluetooth/audio/media_proxy.h b/include/nuttx/net/bluetooth/audio/media_proxy.h new file mode 100644 index 0000000000000..06d39ded628a1 --- /dev/null +++ b/include/nuttx/net/bluetooth/audio/media_proxy.h @@ -0,0 +1,1936 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/audio/media_proxy.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Bluetooth Media Proxy APIs + */ + +/* Copyright (c) 2019 - 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_MEDIA_PROXY_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_MEDIA_PROXY_H + +/** + * @brief Media proxy module + * + * @defgroup bt_media_proxy Media Proxy + * + * @since 3.0 + * @version 0.8.0 + * + * @ingroup bluetooth + * @{ + * + * The media proxy module is the connection point between media players and + * media controllers. + * + * A media player has (access to) media content and knows how to navigate and + * play this content. A media controller reads or gets information from a + * player and controls the player by setting player parameters and giving the + * player commands. + * + * The media proxy module allows media player implementations to make + * themselves available to media controllers. And it allows controllers to + * access, and get updates from, any player. + * + * The media proxy module allows both local and remote control of local + * player instances: A media controller may be a local application, or it may + * be a Media Control Service relaying requests from a remote Media Control + * Client. There may be either local or remote control, or both, or even + * multiple instances of each. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include + +/* TODO: Remove dependency on mcs.h */ +#include "mcs.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Media player command + */ + +struct mpl_cmd +{ + /** The opcode. See the MEDIA_PROXY_OP_* values */ + + uint8_t opcode; + + /** Whether or not the @ref mpl_cmd.param is used */ + + bool use_param; + + /** A 32-bit signed parameter. The parameter value depends on the @ref + * mpl_cmd.opcode + */ + + int32_t param; +}; + +/** + * @brief Media command notification + */ + +struct mpl_cmd_ntf +{ + /** The opcode that was sent */ + + uint8_t requested_opcode; + + /** The result of the operation */ + + uint8_t result_code; +}; + +/** + * @brief Search control item + */ + +struct mpl_sci +{ + uint8_t len; /**< Length of type and parameter */ + uint8_t type; /**< MEDIA_PROXY_SEARCH_TYPE_<...> */ + char param[SEARCH_PARAM_MAX]; /**< Search parameter */ +}; + +/** + * @brief Search + */ + +struct mpl_search +{ + /** The length of the @ref mpl_search.search value */ + + uint8_t len; + + /** Concatenated search control items - (type, length, param) */ + + char search[SEARCH_LEN_MAX]; +}; + +/** + * @name Playback speed parameters + * + * All values from -128 to 127 allowed, only some defined @{ + */ + +/** Minimum playback speed, resulting in 25 % speed */ +#define MEDIA_PROXY_PLAYBACK_SPEED_MIN -128 +/** Quarter playback speed, resulting in 25 % speed */ +#define MEDIA_PROXY_PLAYBACK_SPEED_QUARTER -128 +/** Half playback speed, resulting in 50 % speed */ +#define MEDIA_PROXY_PLAYBACK_SPEED_HALF -64 +/** Unity playback speed, resulting in 100 % speed */ +#define MEDIA_PROXY_PLAYBACK_SPEED_UNITY 0 +/** Double playback speed, resulting in 200 % speed */ +#define MEDIA_PROXY_PLAYBACK_SPEED_DOUBLE 64 +/** Max playback speed, resulting in 395.7 % speed (nearly 400 %) */ +#define MEDIA_PROXY_PLAYBACK_SPEED_MAX 127 +/** @} */ + +/** + * @name Seeking speed factors + * + * The allowed values for seeking speed are the range -64 to -4 (endpoints + * included), the value 0, and the range 4 to 64 (endpoints included). @{ + */ + +/** Maximum seeking speed - Can be negated */ +#define MEDIA_PROXY_SEEKING_SPEED_FACTOR_MAX 64 +/** Minimum seeking speed - Can be negated */ +#define MEDIA_PROXY_SEEKING_SPEED_FACTOR_MIN 4 +/** No seeking */ +#define MEDIA_PROXY_SEEKING_SPEED_FACTOR_ZERO 0 +/** @} */ + +/** + * @name Playing orders + * @{ + */ + +/** A single track is played once; there is no next track. */ +#define MEDIA_PROXY_PLAYING_ORDER_SINGLE_ONCE 0x01U +/** A single track is played repeatedly; the next track is the current track. + */ +#define MEDIA_PROXY_PLAYING_ORDER_SINGLE_REPEAT 0x02U +/** The tracks within a group are played once in track order. */ +#define MEDIA_PROXY_PLAYING_ORDER_INORDER_ONCE 0x03U +/** The tracks within a group are played in track order repeatedly. */ +#define MEDIA_PROXY_PLAYING_ORDER_INORDER_REPEAT 0x04U +/** The tracks within a group are played once only from the oldest first. */ +#define MEDIA_PROXY_PLAYING_ORDER_OLDEST_ONCE 0x05U +/** The tracks within a group are played from the oldest first repeatedly. */ +#define MEDIA_PROXY_PLAYING_ORDER_OLDEST_REPEAT 0x06U +/** The tracks within a group are played once only from the newest first. */ +#define MEDIA_PROXY_PLAYING_ORDER_NEWEST_ONCE 0x07U +/** The tracks within a group are played from the newest first repeatedly. */ +#define MEDIA_PROXY_PLAYING_ORDER_NEWEST_REPEAT 0x08U +/** The tracks within a group are played in random order once. */ +#define MEDIA_PROXY_PLAYING_ORDER_SHUFFLE_ONCE 0x09U +/** The tracks within a group are played in random order repeatedly. */ +#define MEDIA_PROXY_PLAYING_ORDER_SHUFFLE_REPEAT 0x0AU +/** @} */ + +/** + * @name Playing orders supported + * + * A bitmap, in the same order as the playing orders above. Note that playing + * order 1 corresponds to bit 0, and so on. @{ + */ + +/** A single track is played once; there is no next track. */ +#define MEDIA_PROXY_PLAYING_ORDERS_SUPPORTED_SINGLE_ONCE BIT(0U) +/** A single track is played repeatedly; the next track is the current track. + */ +#define MEDIA_PROXY_PLAYING_ORDERS_SUPPORTED_SINGLE_REPEAT BIT(1U) +/** The tracks within a group are played once in track order. */ +#define MEDIA_PROXY_PLAYING_ORDERS_SUPPORTED_INORDER_ONCE BIT(2U) +/** The tracks within a group are played in track order repeatedly. */ +#define MEDIA_PROXY_PLAYING_ORDERS_SUPPORTED_INORDER_REPEAT BIT(3U) +/** The tracks within a group are played once only from the oldest first. */ +#define MEDIA_PROXY_PLAYING_ORDERS_SUPPORTED_OLDEST_ONCE BIT(4U) +/** The tracks within a group are played from the oldest first repeatedly. */ +#define MEDIA_PROXY_PLAYING_ORDERS_SUPPORTED_OLDEST_REPEAT BIT(5U) +/** The tracks within a group are played once only from the newest first. */ +#define MEDIA_PROXY_PLAYING_ORDERS_SUPPORTED_NEWEST_ONCE BIT(6U) +/** The tracks within a group are played from the newest first repeatedly. */ +#define MEDIA_PROXY_PLAYING_ORDERS_SUPPORTED_NEWEST_REPEAT BIT(7U) +/** The tracks within a group are played in random order once. */ +#define MEDIA_PROXY_PLAYING_ORDERS_SUPPORTED_SHUFFLE_ONCE BIT(8U) +/** The tracks within a group are played in random order repeatedly. */ +#define MEDIA_PROXY_PLAYING_ORDERS_SUPPORTED_SHUFFLE_REPEAT BIT(9U) +/** @} */ + +/** + * @name Media player states + * @{ + */ + +/** The current track is invalid, and no track has been selected. */ +#define MEDIA_PROXY_STATE_INACTIVE 0x00U +/** The media player is playing the current track. */ +#define MEDIA_PROXY_STATE_PLAYING 0x01U +/** The current track is paused. The media player has a current track, but it + * is not being played + */ +#define MEDIA_PROXY_STATE_PAUSED 0x02U +/** The current track is fast forwarding or fast rewinding. */ +#define MEDIA_PROXY_STATE_SEEKING 0x03U +/** Used internally as the last state value */ +#define MEDIA_PROXY_STATE_LAST 0x04U +/** @} */ + +/** + * @name Media player command opcodes + * @{ + */ + +/** Start playing the current track. */ +#define MEDIA_PROXY_OP_PLAY 0x01U +/** Pause playing the current track. */ +#define MEDIA_PROXY_OP_PAUSE 0x02U +/** Fast rewind the current track. */ +#define MEDIA_PROXY_OP_FAST_REWIND 0x03U +/** Fast forward the current track. */ +#define MEDIA_PROXY_OP_FAST_FORWARD 0x04U +/** + * Stop current activity and return to the paused state and set the current + * track position to the start of the current track. + */ +#define MEDIA_PROXY_OP_STOP 0x05U + +/** Set a new current track position relative to the current track position. + */ +#define MEDIA_PROXY_OP_MOVE_RELATIVE 0x10U + +/** + * Set the current track position to the starting position of the previous + * segment of the current track. + */ +#define MEDIA_PROXY_OP_PREV_SEGMENT 0x20U +/** + * Set the current track position to the starting position of the next + * segment of the current track. + */ +#define MEDIA_PROXY_OP_NEXT_SEGMENT 0x21U +/** + * Set the current track position to the starting position of the first + * segment of the current track. + */ +#define MEDIA_PROXY_OP_FIRST_SEGMENT 0x22U +/** + * Set the current track position to the starting position of the last + * segment of the current track. + */ +#define MEDIA_PROXY_OP_LAST_SEGMENT 0x23U +/** + * Set the current track position to the starting position of the nth segment + * of the current track. + */ +#define MEDIA_PROXY_OP_GOTO_SEGMENT 0x24U + +/** Set the current track to the previous track based on the playing order. */ +#define MEDIA_PROXY_OP_PREV_TRACK 0x30U +/** Set the current track to the next track based on the playing order. */ +#define MEDIA_PROXY_OP_NEXT_TRACK 0x31U +/** Set the current track to the first track based on the playing order. */ +#define MEDIA_PROXY_OP_FIRST_TRACK 0x32U +/** Set the current track to the last track based on the playing order. */ +#define MEDIA_PROXY_OP_LAST_TRACK 0x33U +/** Set the current track to the nth track based on the playing order. */ +#define MEDIA_PROXY_OP_GOTO_TRACK 0x34U + +/** Set the current group to the previous group in the sequence of groups. */ +#define MEDIA_PROXY_OP_PREV_GROUP 0x40U +/** Set the current group to the next group in the sequence of groups. */ +#define MEDIA_PROXY_OP_NEXT_GROUP 0x41U +/** Set the current group to the first group in the sequence of groups. */ +#define MEDIA_PROXY_OP_FIRST_GROUP 0x42U +/** Set the current group to the last group in the sequence of groups. */ +#define MEDIA_PROXY_OP_LAST_GROUP 0x43U +/** Set the current group to the nth group in the sequence of groups. */ +#define MEDIA_PROXY_OP_GOTO_GROUP 0x44U +/** @} */ + +/** + * @brief Media player supported opcodes length + */ +#define MEDIA_PROXY_OPCODES_SUPPORTED_LEN 4U + +/** + * @brief Media player supported command opcodes + * @{ + */ + +/** Support the Play opcode */ +#define MEDIA_PROXY_OP_SUP_PLAY BIT(0U) +/** Support the Pause opcode */ +#define MEDIA_PROXY_OP_SUP_PAUSE BIT(1U) +/** Support the Fast Rewind opcode */ +#define MEDIA_PROXY_OP_SUP_FAST_REWIND BIT(2U) +/** Support the Fast Forward opcode */ +#define MEDIA_PROXY_OP_SUP_FAST_FORWARD BIT(3U) +/** Support the Stop opcode */ +#define MEDIA_PROXY_OP_SUP_STOP BIT(4U) + +/** Support the Move Relative opcode */ +#define MEDIA_PROXY_OP_SUP_MOVE_RELATIVE BIT(5U) + +/** Support the Previous Segment opcode */ +#define MEDIA_PROXY_OP_SUP_PREV_SEGMENT BIT(6U) +/** Support the Next Segment opcode */ +#define MEDIA_PROXY_OP_SUP_NEXT_SEGMENT BIT(7U) +/** Support the First Segment opcode */ +#define MEDIA_PROXY_OP_SUP_FIRST_SEGMENT BIT(8U) +/** Support the Last Segment opcode */ +#define MEDIA_PROXY_OP_SUP_LAST_SEGMENT BIT(9U) +/** Support the Goto Segment opcode */ +#define MEDIA_PROXY_OP_SUP_GOTO_SEGMENT BIT(10U) + +/** Support the Previous Track opcode */ +#define MEDIA_PROXY_OP_SUP_PREV_TRACK BIT(11U) +/** Support the Next Track opcode */ +#define MEDIA_PROXY_OP_SUP_NEXT_TRACK BIT(12U) +/** Support the First Track opcode */ +#define MEDIA_PROXY_OP_SUP_FIRST_TRACK BIT(13U) +/** Support the Last Track opcode */ +#define MEDIA_PROXY_OP_SUP_LAST_TRACK BIT(14U) +/** Support the Goto Track opcode */ +#define MEDIA_PROXY_OP_SUP_GOTO_TRACK BIT(15U) + +/** Support the Previous Group opcode */ +#define MEDIA_PROXY_OP_SUP_PREV_GROUP BIT(16U) +/** Support the Next Group opcode */ +#define MEDIA_PROXY_OP_SUP_NEXT_GROUP BIT(17U) +/** Support the First Group opcode */ +#define MEDIA_PROXY_OP_SUP_FIRST_GROUP BIT(18U) +/** Support the Last Group opcode */ +#define MEDIA_PROXY_OP_SUP_LAST_GROUP BIT(19U) +/** Support the Goto Group opcode */ +#define MEDIA_PROXY_OP_SUP_GOTO_GROUP BIT(20U) +/** @} */ + +/** + * @name Media player command result codes + * @{ + */ + +/** Action requested by the opcode write was completed successfully. */ +#define MEDIA_PROXY_CMD_SUCCESS 0x01U +/** An invalid or unsupported opcode was used for the Media Control Point + * write. + */ +#define MEDIA_PROXY_CMD_NOT_SUPPORTED 0x02U +/** + * The Media Player State characteristic value is Inactive when the opcode is + * received or the result of the requested action of the opcode results in + * the Media Player State characteristic being set to Inactive. + */ +#define MEDIA_PROXY_CMD_PLAYER_INACTIVE 0x03U +/** + * The requested action of any Media Control Point write cannot be completed + * successfully because of a condition within the player. + */ +#define MEDIA_PROXY_CMD_CANNOT_BE_COMPLETED 0x04U +/** @} */ + +/** + * @name Search operation type values + * @{ + */ + +/** Search for Track Name */ +#define MEDIA_PROXY_SEARCH_TYPE_TRACK_NAME 0x01U +/** Search for Artist Name */ +#define MEDIA_PROXY_SEARCH_TYPE_ARTIST_NAME 0x02U +/** Search for Album Name */ +#define MEDIA_PROXY_SEARCH_TYPE_ALBUM_NAME 0x03U +/** Search for Group Name */ +#define MEDIA_PROXY_SEARCH_TYPE_GROUP_NAME 0x04U +/** Search for Earliest Year */ +#define MEDIA_PROXY_SEARCH_TYPE_EARLIEST_YEAR 0x05U +/** Search for Latest Year */ +#define MEDIA_PROXY_SEARCH_TYPE_LATEST_YEAR 0x06U +/** Search for Genre */ +#define MEDIA_PROXY_SEARCH_TYPE_GENRE 0x07U +/** Search for Tracks only */ +#define MEDIA_PROXY_SEARCH_TYPE_ONLY_TRACKS 0x08U +/** Search for Groups only */ +#define MEDIA_PROXY_SEARCH_TYPE_ONLY_GROUPS 0x09U +/** @} */ + +/** + * @name Search notification result codes + * + * @{ + */ + +/** Search request was accepted; search has started. */ +#define MEDIA_PROXY_SEARCH_SUCCESS 0x01U +/** Search request was invalid; no search started. */ +#define MEDIA_PROXY_SEARCH_FAILURE 0x02U +/** @} */ + +/** + * @name Group object object types + * + * @{ + */ + +/** Group object type is track */ +#define MEDIA_PROXY_GROUP_OBJECT_TRACK_TYPE 0x00U +/** Group object type is group */ +#define MEDIA_PROXY_GROUP_OBJECT_GROUP_TYPE 0x01U +/** @} */ + +/** + * @brief Opaque media player instance + */ + +struct media_player; + +/* PUBLIC API FOR CONTROLLERS */ + +/** + * @brief Callbacks to a controller, from the media proxy + * + * Given by a controller when registering + */ + +struct media_proxy_ctrl_cbs +{ + /* @brief Media Player Instance callback + * + * Called when the local Media Player instance is registered or read + * (TODO). Also called if the local player instance is already registered + * when the controller is registered. Provides the controller with the + * pointer to the local player instance. + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, or errno on negative value. + */ + + void (*local_player_instance)(struct media_player *player, int err); + +#ifdef CONFIG_MCTL_REMOTE_PLAYER_CONTROL + /* @brief Discover Player Instance callback + * + * Called when a remote player instance has been discovered. The instance + * has been discovered, and will be accessed, using Bluetooth, via media + * control client and a remote media control service. + * + * @param player Instance pointer to the remote player + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + */ + + void (*discover_player)(struct media_player *player, int err); +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL */ + + /* @brief Media Player Name receive callback + * + * Called when the Media Player Name is read or changed See also + * media_proxy_ctrl_name_get() + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param name The name of the media player + */ + + void (*player_name_recv)(struct media_player *player, int err, + const char *name); + + /* @brief Media Player Icon Object ID receive callback + * + * Called when the Media Player Icon Object ID is read See also + * media_proxy_ctrl_get_icon_id() + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param id The ID of the Icon object in the Object Transfer Service + * (48 bits) + */ + + void (*icon_id_recv)(struct media_player *player, int err, uint64_t id); + + /* @brief Media Player Icon URL receive callback + * + * Called when the Media Player Icon URL is read See also + * media_proxy_ctrl_get_icon_url() + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param url The URL of the icon + */ + + void (*icon_url_recv)(struct media_player *player, int err, + const char *url); + + /* @brief Track changed receive callback + * + * Called when the Current Track is changed + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + */ + + void (*track_changed_recv)(struct media_player *player, int err); + + /* @brief Track Title receive callback + * + * Called when the Track Title is read or changed See also + * media_proxy_ctrl_get_track_title() + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param title The title of the current track + */ + + void (*track_title_recv)(struct media_player *player, int err, + const char *title); + + /* @brief Track Duration receive callback + * + * Called when the Track Duration is read or changed See also + * media_proxy_ctrl_get_track_duration() + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive + * value or errno on negative value. + * @param duration The duration of the current track + */ + + void (*track_duration_recv)(struct media_player *player, int err, + int32_t duration); + + /* @brief Track Position receive callback + * + * Called when the Track Position is read or changed See also + * media_proxy_ctrl_get_track_position() and + * media_proxy_ctrl_set_track_position() + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive + * value or errno on negative value. + * @param position The player's position in the track + */ + + void (*track_position_recv)(struct media_player *player, int err, + int32_t position); + + /* @brief Track Position write callback + * + * Called when the Track Position is written See also + * media_proxy_ctrl_set_track_position(). + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive + * value or errno on negative value. + * @param position The position given attempted to write + */ + + void (*track_position_write)(struct media_player *player, int err, + int32_t position); + + /* @brief Playback Speed receive callback + * + * Called when the Playback Speed is read or changed See also + * media_proxy_ctrl_get_playback_speed() and + * media_proxy_ctrl_set_playback_speed() + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param speed The playback speed parameter + */ + + void (*playback_speed_recv)(struct media_player *player, int err, + int8_t speed); + + /* @brief Playback Speed write callback + * + * Called when the Playback Speed is written See also + * media_proxy_ctrl_set_playback_speed() + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param speed The playback speed parameter attempted to write + */ + + void (*playback_speed_write)(struct media_player *player, int err, + int8_t speed); + + /* @brief Seeking Speed receive callback + * + * Called when the Seeking Speed is read or changed See also + * media_proxy_ctrl_get_seeking_speed() + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param speed The seeking speed factor + */ + + void (*seeking_speed_recv)(struct media_player *player, int err, + int8_t speed); + + /* @brief Track Segments Object ID receive callback + * + * Called when the Track Segments Object ID is read See also + * media_proxy_ctrl_get_track_segments_id() + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param id The ID of the track segments object in Object Transfer + * Service (48 bits) + */ + + void (*track_segments_id_recv)(struct media_player *player, int err, + uint64_t id); + + /* @brief Current Track Object ID receive callback + * + * Called when the Current Track Object ID is read or changed See also + * media_proxy_ctrl_get_current_track_id() and + * media_proxy_ctrl_set_current_track_id() + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param id The ID of the current track object in Object Transfer + * Service (48 bits) + */ + + void (*current_track_id_recv)(struct media_player *player, int err, + uint64_t id); + + /* @brief Current Track Object ID write callback + * + * Called when the Current Track Object ID is written See also + * media_proxy_ctrl_set_current_track_id() + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param id The ID (48 bits) attempted to write + */ + + void (*current_track_id_write)(struct media_player *player, int err, + uint64_t id); + + /* @brief Next Track Object ID receive callback + * + * Called when the Next Track Object ID is read or changed See also + * media_proxy_ctrl_get_next_track_id() and + * media_proxy_ctrl_set_next_track_id() + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param id The ID of the next track object in Object Transfer + * Service (48 bits) + */ + + void (*next_track_id_recv)(struct media_player *player, int err, + uint64_t id); + + /* @brief Next Track Object ID write callback + * + * Called when the Next Track Object ID is written See also + * media_proxy_ctrl_set_next_track_id() + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param id The ID (48 bits) attempted to write + */ + + void (*next_track_id_write)(struct media_player *player, int err, + uint64_t id); + + /* @brief Parent Group Object ID receive callback + * + * Called when the Parent Group Object ID is read or changed See also + * media_proxy_ctrl_get_parent_group_id() + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param id The ID of the parent group object in Object Transfer + * Service (48 bits) + */ + + void (*parent_group_id_recv)(struct media_player *player, int err, + uint64_t id); + + /* @brief Current Group Object ID receive callback + * + * Called when the Current Group Object ID is read or changed See also + * media_proxy_ctrl_get_current_group_id() and + * media_proxy_ctrl_set_current_group_id() + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param id The ID of the current group object in Object Transfer + * Service (48 bits) + */ + + void (*current_group_id_recv)(struct media_player *player, int err, + uint64_t id); + + /* @brief Current Group Object ID write callback + * + * Called when the Current Group Object ID is written See also + * media_proxy_ctrl_set_current_group_id() + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param id The ID (48 bits) attempted to write + */ + + void (*current_group_id_write)(struct media_player *player, int err, + uint64_t id); + + /* @brief Playing Order receive callback + * + * Called when the Playing Order is read or changed See also + * media_proxy_ctrl_get_playing_order() and + * media_proxy_ctrl_set_playing_order() + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param order The playing order + */ + + void (*playing_order_recv)(struct media_player *player, int err, + uint8_t order); + + /* @brief Playing Order write callback + * + * Called when the Playing Order is written See also + * media_proxy_ctrl_set_playing_order() + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param order The playing order attempted to write + */ + + void (*playing_order_write)(struct media_player *player, int err, + uint8_t order); + + /* @brief Playing Orders Supported receive callback + * + * Called when the Playing Orders Supported is read See also + * media_proxy_ctrl_get_playing_orders_supported() + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param orders The playing orders supported + */ + + void (*playing_orders_supported_recv)(struct media_player *player, + int err, uint16_t orders); + + /* @brief Media State receive callback + * + * Called when the Media State is read or changed See also + * media_proxy_ctrl_get_media_state() and media_proxy_ctrl_send_command() + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param state The media player state + */ + + void (*media_state_recv)(struct media_player *player, int err, + uint8_t state); + + /* @brief Command send callback + * + * Called when a command has been sent See also + * media_proxy_ctrl_send_command() + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param cmd The command sent + */ + + void (*command_send)(struct media_player *player, int err, + const struct mpl_cmd *cmd); + + /* @brief Command result receive callback + * + * Called when a command result has been received See also + * media_proxy_ctrl_send_command() + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param result The result received + */ + + void (*command_recv)(struct media_player *player, int err, + const struct mpl_cmd_ntf *result); + + /* @brief Commands supported receive callback + * + * Called when the Commands Supported is read or changed See also + * media_proxy_ctrl_get_commands_supported() + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive + * value or errno on negative value. + * @param opcodes The supported command opcodes (bitmap) + */ + + void (*commands_supported_recv)(struct media_player *player, int err, + uint32_t opcodes); + + /* @brief Search send callback + * + * Called when a search has been sent See also + * media_proxy_ctrl_send_search() + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive + * value or errno on negative value. + * @param search The search sent + */ + + void (*search_send)(struct media_player *player, int err, + const struct mpl_search *search); + + /* @brief Search result code receive callback + * + * Called when a search result code has been received See also + * media_proxy_ctrl_send_search() + * + * The search result code tells whether the search was successful or not. + * For a successful search, the actual results of the search (i.e. what was + * found as a result of the search)can be accessed using the Search Results + * Object ID. The Search Results Object ID has a separate callback - + * search_results_id_recv(). + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive + * value or errno on negative value. + * @param result_code Search result code + */ + + void (*search_recv)(struct media_player *player, int err, + uint8_t result_code); + + /* @brief Search Results Object ID receive callback + * See also media_proxy_ctrl_get_search_results_id() + * + * Called when the Search Results Object ID is read or changed + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param id The ID of the search results object in Object Transfer + * Service (48 bits) + */ + + void (*search_results_id_recv)(struct media_player *player, int err, + uint64_t id); + + /* @brief Content Control ID receive callback + * + * Called when the Content Control ID is read See also + * media_proxy_ctrl_get_content_ctrl_id() + * + * @param player Media player instance pointer + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param ccid The content control ID + */ + + void (*content_ctrl_id_recv)(struct media_player *player, int err, + uint8_t ccid); +}; + +/** + * @brief Register a controller with the media_proxy + * + * @param ctrl_cbs Callbacks to the controller + * + * @return 0 if success, errno on failure + */ + +int media_proxy_ctrl_register(struct media_proxy_ctrl_cbs *ctrl_cbs); + +/** + * @brief Discover a remote media player + * + * Discover a remote media player instance. The remote player instance will + * be discovered, and accessed, using Bluetooth, via the media control client + * and a remote media control service. This call will start a GATT discovery + * of the Media Control Service on the peer, and setup handles and + * subscriptions. + * + * This shall be called once before any other actions can be executed for the + * remote player. The remote player instance will be returned in the + * discover_player() callback. + * + * @param conn The connection to do discovery for + * + * @return 0 if success, errno on failure + */ + +int media_proxy_ctrl_discover_player(struct bt_conn *conn); + +/** + * @brief Read Media Player Name + * + * @param player Media player instance pointer + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_get_player_name(struct media_player *player); + +/** + * @brief Set Media Player Name + * + * @param player Media player instance pointer + * @param name The new Media Player Name + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_set_player_name(struct media_player *player, + const char *name); + +/** + * @brief Read Icon Object ID + * + * Get an ID (48 bit) that can be used to retrieve the Icon Object from an + * Object Transfer Service + * + * See the Media Control Service spec v1.0 sections 3.2 and 4.1 for a + * description of the Icon Object. + * + * Requires Object Transfer Service + * + * @param player Media player instance pointer + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_get_icon_id(struct media_player *player); + +/** + * @brief Read Icon URL + * + * Get a URL to the media player's icon. + * + * @param player Media player instance pointer + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_get_icon_url(struct media_player *player); + +/** + * @brief Read Track Title + * + * @param player Media player instance pointer + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_get_track_title(struct media_player *player); + +/** + * @brief Read Track Duration + * + * The duration of a track is measured in hundredths of a second. + * + * @param player Media player instance pointer + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_get_track_duration(struct media_player *player); + +/** + * @brief Read Track Position + * + * The position of the player (the playing position) is measured in + * hundredths of a second from the beginning of the track + * + * @param player Media player instance pointer + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_get_track_position(struct media_player *player); + +/** + * @brief Set Track Position + * + * Set the playing position of the media player in the current track. The + * position is given in hundredths of a second, from the beginning of the + * track of the track for positive values, and (backwards) from the end of + * the track for negative values. + * + * @param player Media player instance pointer + * @param position The track position to set + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_set_track_position(struct media_player *player, + int32_t position); + +/** + * @brief Get Playback Speed + * + * The playback speed parameter is related to the actual playback speed as + * follows: actual playback speed = 2^(speed_parameter/64) + * + * A speed parameter of 0 corresponds to unity speed playback (i.e. playback + * at "normal" speed). A speed parameter of + * -128 corresponds to playback at one fourth of normal speed, + * 127 corresponds to playback at almost four times the normal speed. + * + * @param player Media player instance pointer + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_get_playback_speed(struct media_player *player); + +/** + * @brief Set Playback Speed + * + * See the get_playback_speed() function for an explanation of the playback + * speed parameter. + * + * Note that the media player may not support all possible values of the + * playback speed parameter. If the value given is not supported, and is + * higher than the current value, the player should set the playback speed to + * the next higher supported value. (And correspondingly to the next lower + * supported value for given values lower than the current value.) + * + * @param player Media player instance pointer + * @param speed The playback speed parameter to set + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_set_playback_speed(struct media_player *player, + int8_t speed); + +/** + * @brief Get Seeking Speed + * + * The seeking speed gives the speed with which the player is seeking. It is + * a factor, relative to real-time playback speed - a factor four means + * seeking happens at four times the real-time playback speed. Positive + * values are for forward seeking, negative values for backwards seeking. + * + * The seeking speed is not settable - a non-zero seeking speed is the result + * of "fast rewind" of "fast forward" commands. + * + * @param player Media player instance pointer + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_get_seeking_speed(struct media_player *player); + +/** + * @brief Read Current Track Segments Object ID + * + * Get an ID (48 bit) that can be used to retrieve the Current Track Segments + * Object from an Object Transfer Service + * + * See the Media Control Service spec v1.0 sections 3.10 and 4.2 for a + * description of the Track Segments Object. + * + * Requires Object Transfer Service + * + * @param player Media player instance pointer + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_get_track_segments_id(struct media_player *player); + +/** + * @brief Read Current Track Object ID + * + * Get an ID (48 bit) that can be used to retrieve the Current Track Object + * from an Object Transfer Service + * + * See the Media Control Service spec v1.0 sections 3.11 and 4.3 for a + * description of the Current Track Object. + * + * Requires Object Transfer Service + * + * @param player Media player instance pointer + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_get_current_track_id(struct media_player *player); + +/** + * @brief Set Current Track Object ID + * + * Change the player's current track to the track given by the ID. (Behaves + * similarly to the goto track command.) + * + * Requires Object Transfer Service + * + * @param player Media player instance pointer + * @param id The ID of a track object + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_set_current_track_id(struct media_player *player, + uint64_t id); + +/** + * @brief Read Next Track Object ID + * + * Get an ID (48 bit) that can be used to retrieve the Next Track Object from + * an Object Transfer Service + * + * Requires Object Transfer Service + * + * @param player Media player instance pointer + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_get_next_track_id(struct media_player *player); + +/** + * @brief Set Next Track Object ID + * + * Change the player's next track to the track given by the ID. + * + * Requires Object Transfer Service + * + * @param player Media player instance pointer + * @param id The ID of a track object + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_set_next_track_id(struct media_player *player, + uint64_t id); + +/** + * @brief Read Parent Group Object ID + * + * Get an ID (48 bit) that can be used to retrieve the Parent Track Object + * from an Object Transfer Service + * + * The parent group is the parent of the current group. + * + * See the Media Control Service spec v1.0 sections 3.14 and 4.4 for a + * description of the Current Track Object. + * + * Requires Object Transfer Service + * + * @param player Media player instance pointer + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_get_parent_group_id(struct media_player *player); + +/** + * @brief Read Current Group Object ID + * + * Get an ID (48 bit) that can be used to retrieve the Current Track Object + * from an Object Transfer Service + * + * See the Media Control Service spec v1.0 sections 3.14 and 4.4 for a + * description of the Current Group Object. + * + * Requires Object Transfer Service + * + * @param player Media player instance pointer + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_get_current_group_id(struct media_player *player); + +/** + * @brief Set Current Group Object ID + * + * Change the player's current group to the group given by the ID, and the + * current track to the first track in that group. + * + * Requires Object Transfer Service + * + * @param player Media player instance pointer + * @param id The ID of a group object + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_set_current_group_id(struct media_player *player, + uint64_t id); + +/** + * @brief Read Playing Order + * + * @param player Media player instance pointer + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_get_playing_order(struct media_player *player); + +/** + * @brief Set Playing Order + * + * Set the media player's playing order + * + * @param player Media player instance pointer + * @param order The playing order to set + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_set_playing_order(struct media_player *player, + uint8_t order); + +/** + * @brief Read Playing Orders Supported + * + * Read a bitmap containing the media player's supported playing orders. + * + * @param player Media player instance pointer + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_get_playing_orders_supported( + struct media_player *player); + +/** + * @brief Read Media State + * + * Read the media player's state + * + * @param player Media player instance pointer + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_get_media_state(struct media_player *player); + +/** + * @brief Send Command + * + * Send a command to the media player. Commands may cause the media player to + * change its state May result in two callbacks - one for the actual sending + * of the command to the player, one for the result of the command from the + * player. + * + * @param player Media player instance pointer + * @param command The command to send + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_send_command(struct media_player *player, + const struct mpl_cmd *command); + +/** + * @brief Read Commands Supported + * + * Read a bitmap containing the media player's supported command opcodes. + * + * @param player Media player instance pointer + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_get_commands_supported(struct media_player *player); + +/** + * @brief Set Search + * + * Write a search to the media player. If the search is successful, the + * search results will be available as a group object in the Object Transfer + * Service (OTS). + * + * May result in up to three callbacks + * - one for the actual sending of the search to the player + * - one for the result code for the search from the player + * - if the search is successful, one for the search results object ID in the + * OTs + * + * Requires Object Transfer Service + * + * @param player Media player instance pointer + * @param search The search to write + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_send_search(struct media_player *player, + const struct mpl_search *search); + +/** + * @brief Read Search Results Object ID + * + * Get an ID (48 bit) that can be used to retrieve the Search Results Object + * from an Object Transfer Service + * + * The search results object is a group object. The search results object + * only exists if a successful search operation has been done. + * + * Requires Object Transfer Service + * + * @param player Media player instance pointer + * + * @return 0 if success, errno on failure. + */ + +int media_proxy_ctrl_get_search_results_id(struct media_player *player); + +/** + * @brief Read Content Control ID + * + * The content control ID identifies a content control service on a device, + * and links it to the corresponding audio stream. + * + * @param player Media player instance pointer + * + * @return 0 if success, errno on failure. + */ + +uint8_t media_proxy_ctrl_get_content_ctrl_id(struct media_player *player); + +/** + * @brief Available calls in a player, that the media proxy can call + * + * Given by a player when registering. + */ + +struct media_proxy_pl_calls +{ + /* @brief Read Media Player Name + * + * @return The name of the media player + */ + + const char *(*get_player_name)(void); + + /* @brief Set Media Player Name + * + * @param name The new Media Player Name + */ + + void (*set_player_name)(const char *name); + + /* @brief Read Icon Object ID + * + * Get an ID (48 bit) that can be used to retrieve the Icon Object from an + * Object Transfer Service + * + * See the Media Control Service spec v1.0 sections 3.2 and 4.1 for a + * description of the Icon Object. + * + * @return The Icon Object ID + */ + + uint64_t (*get_icon_id)(void); + + /* @brief Read Icon URL + * + * Get a URL to the media player's icon. + * + * @return The URL of the Icon + */ + + const char *(*get_icon_url)(void); + + /* @brief Read Track Title + * + * @return The title of the current track + */ + + const char *(*get_track_title)(void); + + /* @brief Read Track Duration + * + * The duration of a track is measured in hundredths of a second. + * + * @return The duration of the current track + */ + + int32_t (*get_track_duration)(void); + + /* @brief Read Track Position + * + * The position of the player (the playing position) is measured in + * hundredths of a second from the beginning of the track + * + * @return The position of the player in the current track + */ + + int32_t (*get_track_position)(void); + + /* @brief Set Track Position + * + * Set the playing position of the media player in the current track. The + * position is given in hundredths of a second, from the beginning of the + * track of the track for positive values, and (backwards) from the end of + * the track for negative values. + * + * @param position The player position to set + */ + + void (*set_track_position)(int32_t position); + + /* @brief Get Playback Speed + * + * The playback speed parameter is related to the actual playback speed as + * follows: actual playback speed = 2^(speed_parameter/64) + * + * A speed parameter of 0 corresponds to unity speed playback (i.e. + * playback at "normal" speed). A speed parameter of + * -128 corresponds to playback at one fourth of normal speed, + * 127 corresponds to playback at almost four times the normal speed. + * + * @return The playback speed parameter + */ + + int8_t (*get_playback_speed)(void); + + /* @brief Set Playback Speed + * + * See the get_playback_speed() function for an explanation of the playback + * speed parameter. + * + * Note that the media player may not support all possible values of the + * playback speed parameter. If the value given is not supported, and is + * higher than the current value, the player should set the playback speed + * to the next higher supported value. (And correspondingly to the next + * lower supported value for given values lower than the current value.) + * + * @param speed The playback speed parameter to set + */ + + void (*set_playback_speed)(int8_t speed); + + /* @brief Get Seeking Speed + * + * The seeking speed gives the speed with which the player is seeking. It + * is a factor, relative to real-time playback speed - a factor four means + * seeking happens at four times the real-time playback speed. Positive + * values are for forward seeking, negative values for backwards seeking. + * + * The seeking speed is not settable - a non-zero seeking speed is the + * result of "fast rewind" of "fast forward" commands. + * + * @return The seeking speed factor + */ + + int8_t (*get_seeking_speed)(void); + + /* @brief Read Current Track Segments Object ID + * + * Get an ID (48 bit) that can be used to retrieve the Current Track + * Segments Object from an Object Transfer Service + * + * See the Media Control Service spec v1.0 sections 3.10 and 4.2 for a + * description of the Track Segments Object. + * + * @return Current The Track Segments Object ID + */ + + uint64_t (*get_track_segments_id)(void); + + /* @brief Read Current Track Object ID + * + * Get an ID (48 bit) that can be used to retrieve the Current Track Object + * from an Object Transfer Service + * + * See the Media Control Service spec v1.0 sections 3.11 and 4.3 for a + * description of the Current Track Object. + * + * @return The Current Track Object ID + */ + + uint64_t (*get_current_track_id)(void); + + /* @brief Set Current Track Object ID + * + * Change the player's current track to the track given by the ID. (Behaves + * similarly to the goto track command.) + * + * @param id The ID of a track object + */ + + void (*set_current_track_id)(uint64_t id); + + /* @brief Read Next Track Object ID + * + * Get an ID (48 bit) that can be used to retrieve the Next Track Object + * from an Object Transfer Service + * + * @return The Next Track Object ID + */ + + uint64_t (*get_next_track_id)(void); + + /* @brief Set Next Track Object ID + * + * Change the player's next track to the track given by the ID. + * + * @param id The ID of a track object + */ + + void (*set_next_track_id)(uint64_t id); + + /* @brief Read Parent Group Object ID + * + * Get an ID (48 bit) that can be used to retrieve the Parent Track Object + * from an Object Transfer Service + * + * The parent group is the parent of the current group. + * + * See the Media Control Service spec v1.0 sections 3.14 and 4.4 for a + * description of the Current Track Object. + * + * @return The Current Group Object ID + */ + + uint64_t (*get_parent_group_id)(void); + + /* @brief Read Current Group Object ID + * + * Get an ID (48 bit) that can be used to retrieve the Current Track Object + * from an Object Transfer Service + * + * See the Media Control Service spec v1.0 sections 3.14 and 4.4 for a + * description of the Current Group Object. + * + * @return The Current Group Object ID + */ + + uint64_t (*get_current_group_id)(void); + + /* @brief Set Current Group Object ID + * + * Change the player's current group to the group given by the ID, and the + * current track to the first track in that group. + * + * @param id The ID of a group object + */ + + void (*set_current_group_id)(uint64_t id); + + /* @brief Read Playing Order + * + * @return The media player's current playing order + */ + + uint8_t (*get_playing_order)(void); + + /* @brief Set Playing Order + * + * Set the media player's playing order. See the + * MEDIA_PROXY_PLAYING_ORDER_* defines. + * + * @param order The playing order to set + */ + + void (*set_playing_order)(uint8_t order); + + /* @brief Read Playing Orders Supported + * + * Read a bitmap containing the media player's supported playing orders. + * See the MEDIA_PROXY_PLAYING_ORDERS_SUPPORTED_* defines. + * + * @return The media player's supported playing orders + */ + + uint16_t (*get_playing_orders_supported)(void); + + /* @brief Read Media State + * + * Read the media player's state See the MEDIA_PROXY_MEDIA_STATE_* defines. + * + * @return The media player's state + */ + + uint8_t (*get_media_state)(void); + + /* @brief Send Command + * + * Send a command to the media player. For command opcodes (play, pause, + * ...) - see the MEDIA_PROXY_OP_* defines. + * + * @param command The command to send + */ + + void (*send_command)(const struct mpl_cmd *command); + + /* @brief Read Commands Supported + * + * Read a bitmap containing the media player's supported command opcodes. + * See the MEDIA_PROXY_OP_SUP_* defines. + * + * @return The media player's supported command opcodes + */ + + uint32_t (*get_commands_supported)(void); + + /* @brief Set Search + * + * Write a search to the media player. (For the formatting of a search, see + * the Media Control Service spec and the mcs.h file.) + * + * @param search The search to write + */ + + void (*send_search)(const struct mpl_search *search); + + /* @brief Read Search Results Object ID + * + * Get an ID (48 bit) that can be used to retrieve the Search Results + * Object from an Object Transfer Service + * + * The search results object is a group object. The search results object + * only exists if a successful search operation has been done. + * + * @return The Search Results Object ID + */ + + uint64_t (*get_search_results_id)(void); + + /* @brief Read Content Control ID + * + * The content control ID identifies a content control service on a device, + * and links it to the corresponding audio stream. + * + * @return The content control ID for the media player + */ + + uint8_t (*get_content_ctrl_id)(void); +}; + +/** + * @brief Register a player with the media proxy + * + * Register a player with the media proxy module, for use by media + * controllers. + * + * The media proxy may call any non-NULL function pointers in the supplied + * media_proxy_pl_calls structure. + * + * @param pl_calls Function pointers to the media player's calls + * + * @return 0 if success, errno on failure + */ + +int media_proxy_pl_register(struct media_proxy_pl_calls *pl_calls); + +/** + * @brief Initialize player + * + * TODO: Move to player header file + */ + +int media_proxy_pl_init(void); + +/** + * @brief Get the pointer of the Object Transfer Service used by the Media + * Control Service + * + * TODO: Find best location for this call, and move this one also + */ + +struct bt_ots *bt_mcs_get_ots(void); + +/** + * @brief Player name changed callback + * + * To be called when the player's name is changed. + * + * @param name The name of the player + */ + +void media_proxy_pl_name_cb(const char *name); + +/** + * @brief Player icon URL changed callback + * + * To be called when the player's icon URL is changed. + * + * @param url The URL of the player's icon + */ + +void media_proxy_pl_icon_url_cb(const char *url); + +/** + * @brief Track changed callback + * + * To be called when the player's current track is changed + */ + +void media_proxy_pl_track_changed_cb(void); + +/** + * @brief Track title callback + * + * To be called when the player's current track is changed + * + * @param title The title of the track + */ + +void media_proxy_pl_track_title_cb(char *title); + +/** + * @brief Track duration callback + * + * To be called when the current track's duration is changed (e.g. due to a + * track change) + * + * The track duration is given in hundredths of a second. + * + * @param duration The track duration + */ + +void media_proxy_pl_track_duration_cb(int32_t duration); + +/** + * @brief Track position callback + * + * To be called when the media player's position in the track is changed, or + * when the player is paused or similar. + * + * Exception: This callback should not be called when the position changes + * during regular playback, i.e. while the player is playing and playback + * happens at a constant speed. + * + * The track position is given in hundredths of a second from the start of + * the track. + * + * @param position The media player's position in the track + */ + +void media_proxy_pl_track_position_cb(int32_t position); + +/** + * @brief Playback speed callback + * + * To be called when the playback speed is changed. + * + * @param speed The playback speed parameter + */ + +void media_proxy_pl_playback_speed_cb(int8_t speed); + +/** + * @brief Seeking speed callback + * + * To be called when the seeking speed is changed. + * + * @param speed The seeking speed factor + */ + +void media_proxy_pl_seeking_speed_cb(int8_t speed); + +/** + * @brief Current track object ID callback + * + * To be called when the ID of the current track is changed (e.g. due to a + * track change). + * + * @param id The ID of the current track object in the OTS + */ + +void media_proxy_pl_current_track_id_cb(uint64_t id); + +/** + * @brief Next track object ID callback + * + * To be called when the ID of the current track is changes + * + * @param id The ID of the next track object in the OTS + */ + +void media_proxy_pl_next_track_id_cb(uint64_t id); + +/** + * @brief Parent group object ID callback + * + * To be called when the ID of the parent group is changed + * + * @param id The ID of the parent group object in the OTS + */ + +void media_proxy_pl_parent_group_id_cb(uint64_t id); + +/** + * @brief Current group object ID callback + * + * To be called when the ID of the current group is changed + * + * @param id The ID of the current group object in the OTS + */ + +void media_proxy_pl_current_group_id_cb(uint64_t id); + +/** + * @brief Playing order callback + * + * To be called when the playing order is changed + * + * @param order The playing order + */ + +void media_proxy_pl_playing_order_cb(uint8_t order); + +/** + * @brief Media state callback + * + * To be called when the media state is changed + * + * @param state The media player's state + */ + +void media_proxy_pl_media_state_cb(uint8_t state); + +/** + * @brief Command callback + * + * To be called when a command has been sent, to notify whether the command + * was successfully performed or not. See the MEDIA_PROXY_CMD_* result code + * defines. + * + * @param cmd_ntf The result of the command + */ + +void media_proxy_pl_command_cb(const struct mpl_cmd_ntf *cmd_ntf); + +/** + * @brief Commands supported callback + * + * To be called when the set of commands supported is changed + * + * @param opcodes The supported commands opcodes + */ + +void media_proxy_pl_commands_supported_cb(uint32_t opcodes); + +/** + * @brief Search callback + * + * To be called when a search has been set to notify whether the search was + * successfully performed or not. See the MEDIA_PROXY_SEARCH_* result code + * defines. + * + * The actual results of the search, if successful, can be found in the + * search results object. + * + * @param result_code The result (success or failure) of the search + */ + +void media_proxy_pl_search_cb(uint8_t result_code); + +/** + * @brief Search Results object ID callback + * + * To be called when the ID of the search results is changed (typically as + * the result of a new successful search). + * + * @param id The ID of the search results object in the OTS + */ + +void media_proxy_pl_search_results_id_cb(uint64_t id); + +#ifdef __cplusplus +} +#endif + +/** @} */ /* End of group bt_media_proxy */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_MEDIA_PROXY_H */ diff --git a/include/nuttx/net/bluetooth/audio/micp.h b/include/nuttx/net/bluetooth/audio/micp.h new file mode 100644 index 0000000000000..dbc1839b700f3 --- /dev/null +++ b/include/nuttx/net/bluetooth/audio/micp.h @@ -0,0 +1,390 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/audio/micp.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Bluetooth Microphone Control Profile (MICP) APIs. + */ + +/* Copyright (c) 2020-2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_MICP_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_MICP_H + +/** + * @brief Microphone Control Profile (MICP) + * + * @defgroup bt_micp Microphone Control Profile (MICP) + * + * @since 2.7 + * @version 0.8.0 + * + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * Defines the maximum number of Microphone Control Service instances for the + * Microphone Control Profile Microphone Device + */ +#if defined(CONFIG_BT_MICP_MIC_DEV) +#define BT_MICP_MIC_DEV_AICS_CNT CONFIG_BT_MICP_MIC_DEV_AICS_INSTANCE_COUNT +#else +#define BT_MICP_MIC_DEV_AICS_CNT 0U +#endif /* CONFIG_BT_MICP_MIC_DEV */ + +/** + * @name Application error codes + * @{ + */ + +/** Mute/unmute commands are disabled. */ +#define BT_MICP_ERR_MUTE_DISABLED 0x80U +/** @} */ + +/** + * @name Microphone Control Profile mute states + * @{ + */ + +/** The microphone state is unmuted */ +#define BT_MICP_MUTE_UNMUTED 0x00U +/** The microphone state is muted */ +#define BT_MICP_MUTE_MUTED 0x01U +/** The microphone state is disabled and cannot be muted or unmuted */ +#define BT_MICP_MUTE_DISABLED 0x02U +/** @} */ + +/** + * @struct bt_micp_mic_ctlr + * @brief Opaque Microphone Controller instance. + */ + +struct bt_micp_mic_ctlr; + +/** @brief Register parameters structure for Microphone Control Service */ + +struct bt_micp_mic_dev_register_param +{ +#if defined(CONFIG_BT_MICP_MIC_DEV_AICS) || defined(__DOXYGEN__) + /** Register parameter structure for Audio Input Control Services */ + + struct bt_aics_register_param aics_param[BT_MICP_MIC_DEV_AICS_CNT]; +#endif /* CONFIG_BT_MICP_MIC_DEV_AICS */ + + /** Microphone Control Profile callback structure. */ + + struct bt_micp_mic_dev_cb *cb; +}; + +/** + * @brief Microphone Control Profile included services + * + * Used for to represent the Microphone Control Profile included service + * instances, for either a Microphone Controller or a Microphone Device. The + * instance pointers either represent local service instances, or remote + * service instances. + */ + +struct bt_micp_included +{ + /** Number of Audio Input Control Service instances */ + + uint8_t aics_cnt; + + /** Array of pointers to Audio Input Control Service instances */ + + struct bt_aics **aics; +}; + +/** + * @brief Initialize the Microphone Control Profile Microphone Device + * + * This will enable the Microphone Control Service instance and make it + * discoverable by Microphone Controllers. + * + * @param param Pointer to an initialization structure. + * + * @return 0 if success, errno on failure. + */ + +int bt_micp_mic_dev_register(struct bt_micp_mic_dev_register_param *param); + +/** + * @brief Get Microphone Device included services + * + * Returns a pointer to a struct that contains information about the + * Microphone Device included Audio Input Control Service instances. + * + * Requires that @kconfig{CONFIG_BT_MICP_MIC_DEV_AICS} is enabled. + * + * @param included Pointer to store the result in. + * + * @return 0 if success, errno on failure. + */ + +int bt_micp_mic_dev_included_get(struct bt_micp_included *included); + +/** + * @brief Struct to hold the Microphone Device callbacks + * + * These can be registered for usage with bt_micp_mic_dev_register(). + */ + +struct bt_micp_mic_dev_cb +{ + /** + * @brief Callback function for Microphone Device mute. + * + * Called when the value is read with bt_micp_mic_dev_mute_get(), or if the + * value is changed by either the Microphone Device or a Microphone + * Controller. + * + * @param mute The mute setting of the Microphone Control Service. + */ + + void (*mute)(uint8_t mute); +}; + +/** + * @brief Unmute the Microphone Device. + * + * @return 0 on success, GATT error value on fail. + */ + +int bt_micp_mic_dev_unmute(void); + +/** + * @brief Mute the Microphone Device. + * + * @return 0 on success, GATT error value on fail. + */ + +int bt_micp_mic_dev_mute(void); + +/** + * @brief Disable the mute functionality on the Microphone Device. + * + * Can be re-enabled by called @ref bt_micp_mic_dev_mute or @ref + * bt_micp_mic_dev_unmute. + * + * @return 0 on success, GATT error value on fail. + */ + +int bt_micp_mic_dev_mute_disable(void); + +/** + * @brief Read the mute state on the Microphone Device. + * + * @return 0 on success, GATT error value on fail. + */ + +int bt_micp_mic_dev_mute_get(void); + +/** + * @brief Struct to hold the Microphone Controller callbacks + * + * These can be registered for usage with bt_micp_mic_ctlr_cb_register(). + */ + +struct bt_micp_mic_ctlr_cb +{ + /** + * @brief Callback function for Microphone Control Profile mute. + * + * Called when the value is read, or if the value is changed by either the + * Microphone Device or a Microphone Controller. + * + * @param mic_ctlr Microphone Controller instance pointer. + * @param err Error value. 0 on success, GATT error or errno on fail. + * For notifications, this will always be 0. + * @param mute The mute setting of the Microphone Control Service. + */ + + void (*mute)(struct bt_micp_mic_ctlr *mic_ctlr, int err, uint8_t mute); + + /** + * @brief Callback function for bt_micp_mic_ctlr_discover(). + * + * @param mic_ctlr Microphone Controller instance pointer. + * @param err Error value. 0 on success, GATT error or errno on + * fail. + * @param aics_count Number of Audio Input Control Service instances on + * peer device. + */ + + void (*discover)(struct bt_micp_mic_ctlr *mic_ctlr, int err, + uint8_t aics_count); + + /** + * @brief Callback function for Microphone Control Profile mute/unmute. + * + * @param mic_ctlr Microphone Controller instance pointer. + * @param err Error value. 0 on success, GATT error or errno on fail. + */ + + void (*mute_written)(struct bt_micp_mic_ctlr *mic_ctlr, int err); + + /** + * @brief Callback function for Microphone Control Profile mute/unmute. + * + * @param mic_ctlr Microphone Controller instance pointer. + * @param err Error value. 0 on success, GATT error or errno on fail. + */ + + void (*unmute_written)(struct bt_micp_mic_ctlr *mic_ctlr, int err); + +#if defined(CONFIG_BT_MICP_MIC_CTLR_AICS) || defined(__DOXYGEN__) + /** Audio Input Control Service client callback */ + + struct bt_aics_cb aics_cb; +#endif /* CONFIG_BT_MICP_MIC_CTLR_AICS */ + + /** @cond INTERNAL_HIDDEN */ + + /** Internally used field for list handling */ + + struct list_node _node; + + /** @endcond */ +}; + +/** + * @brief Get Microphone Control Profile included services + * + * Returns a pointer to a struct that contains information about the + * Microphone Control Profile included services instances, such as pointers + * to the Audio Input Control Service instances. + * + * Requires that @kconfig{CONFIG_BT_MICP_MIC_CTLR_AICS} is enabled. + * + * @param mic_ctlr Microphone Controller instance pointer. + * @param[out] included Pointer to store the result in. + * + * @return 0 if success, errno on failure. + */ + +int bt_micp_mic_ctlr_included_get(struct bt_micp_mic_ctlr *mic_ctlr, + struct bt_micp_included *included); + +/** + * @brief Get the connection pointer of a Microphone Controller instance + * + * Get the Bluetooth connection pointer of a Microphone Controller instance. + * + * @param mic_ctlr Microphone Controller instance pointer. + * @param conn Connection pointer. + * + * @return 0 if success, errno on failure. + */ + +int bt_micp_mic_ctlr_conn_get(const struct bt_micp_mic_ctlr *mic_ctlr, + struct bt_conn **conn); + +/** + * @brief Get the Microphone Controller from a connection pointer + * + * Get the Microphone Control Profile Microphone Controller pointer from a + * connection pointer. Only microphone controllers that have been initiated + * via bt_micp_mic_ctlr_discover() can be retrieved. + * + * @param conn Connection pointer. + * + * @retval Pointer to a Microphone Control Profile Microphone Controller + * instance + * @retval NULL if @p conn is NULL or if the connection has not done + * discovery yet + */ + +struct bt_micp_mic_ctlr *bt_micp_mic_ctlr_get_by_conn( + const struct bt_conn *conn); + +/** + * @brief Discover Microphone Control Service + * + * This will start a GATT discovery and setup handles and subscriptions. This + * shall be called once before any other actions can be executed for the peer + * device, and the @ref bt_micp_mic_ctlr_cb.discover callback will notify + * when it is possible to start remote operations. + * * + * @param conn The connection to initialize the profile for. + * @param[out] mic_ctlr Valid remote instance object on success. + * + * @return 0 on success, GATT error value on fail. + */ + +int bt_micp_mic_ctlr_discover(struct bt_conn *conn, + struct bt_micp_mic_ctlr **mic_ctlr); + +/** + * @brief Unmute a remote Microphone Device. + * + * @param mic_ctlr Microphone Controller instance pointer. + * + * @return 0 on success, GATT error value on fail. + */ + +int bt_micp_mic_ctlr_unmute(struct bt_micp_mic_ctlr *mic_ctlr); + +/** + * @brief Mute a remote Microphone Device. + * + * @param mic_ctlr Microphone Controller instance pointer. + * + * @return 0 on success, GATT error value on fail. + */ + +int bt_micp_mic_ctlr_mute(struct bt_micp_mic_ctlr *mic_ctlr); + +/** + * @brief Read the mute state of a remote Microphone Device. + * + * @param mic_ctlr Microphone Controller instance pointer. + * + * @return 0 on success, GATT error value on fail. + */ + +int bt_micp_mic_ctlr_mute_get(struct bt_micp_mic_ctlr *mic_ctlr); + +/** + * @brief Registers the callbacks used by Microphone Controller. + * + * This can only be done as the client. + * + * @param cb The callback structure. + * + * @return 0 if success, errno on failure. + */ + +int bt_micp_mic_ctlr_cb_register(struct bt_micp_mic_ctlr_cb *cb); +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_MICP_H */ diff --git a/include/nuttx/net/bluetooth/audio/pacs.h b/include/nuttx/net/bluetooth/audio/pacs.h new file mode 100644 index 0000000000000..a870291d59f09 --- /dev/null +++ b/include/nuttx/net/bluetooth/audio/pacs.h @@ -0,0 +1,281 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/audio/pacs.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Bluetooth Published Audio Capabilities Service (PACS) APIs + */ + +/* Copyright (c) 2021 Intel Corporation + * Copyright (c) 2021-2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_PACS_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_PACS_H + +/** + * @brief Published Audio Capabilities Service (PACS) + * + * @defgroup bt_pacs Published Audio Capabilities Service (PACS) + * + * @since 3.0 + * @version 0.8.0 + * + * @ingroup bluetooth + * @{ + * + * The Published Audio Capabilities Service (PACS) is used to expose + * capabilities to remote devices. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** @brief Published Audio Capability structure. */ + +struct bt_pacs_cap +{ + /** Codec capability reference */ + + const struct bt_audio_codec_cap *codec_cap; + + /** @cond INTERNAL_HIDDEN */ + + /** Internally used field for list handling */ + + struct list_node _node; + + /** @endcond */ +}; + +/** Structure for registering PACS */ + +struct bt_pacs_register_param +{ +#if defined(CONFIG_BT_PAC_SNK) || defined(__DOXYGEN__) + /** + * @brief Enables or disables registration of Sink PAC Characteristic. + */ + + bool snk_pac; +#endif /* CONFIG_BT_PAC_SNK */ + +#if defined(CONFIG_BT_PAC_SNK_LOC) || defined(__DOXYGEN__) + /** + * @brief Enables or disables registration of Sink Location Characteristic. + * + * Registration of Sink Location is dependent on @ref + * bt_pacs_register_param.snk_pac also being set. + */ + + bool snk_loc; +#endif /* CONFIG_BT_PAC_SNK_LOC */ + +#if defined(CONFIG_BT_PAC_SRC) || defined(__DOXYGEN__) + /** + * @brief Enables or disables registration of Source PAC Characteristic. + */ + + bool src_pac; +#endif /* CONFIG_BT_PAC_SRC */ + +#if defined(CONFIG_BT_PAC_SRC_LOC) || defined(__DOXYGEN__) + /** + * @brief Enables or disables registration of Source Location + * Characteristic. + * + * Registration of Source Location is dependent on @ref + * bt_pacs_register_param.src_pac also being set. + */ + + bool src_loc; +#endif /* CONFIG_BT_PAC_SRC_LOC */ +}; + +/** + * @typedef bt_pacs_cap_foreach_func_t + * @brief Published Audio Capability iterator callback. + * + * @param cap Capability found. + * @param user_data Data given. + * + * @return true to continue to the next capability + * @return false to stop the iteration + */ + +typedef bool (*bt_pacs_cap_foreach_func_t)( + const struct bt_pacs_cap *cap, void *user_data); + +/** + * @brief Published Audio Capability iterator. + * + * Iterate capabilities with endpoint direction specified. + * + * @param dir Direction of the endpoint to look capability for. + * @param func Callback function. + * @param user_data Data to pass to the callback. + */ + +void bt_pacs_cap_foreach(enum bt_audio_dir dir, + bt_pacs_cap_foreach_func_t func, void *user_data); + +/** + * @brief Register the Published Audio Capability Service instance. + * + * @param param PACS register parameters. + * + * @retval 0 Success + * @retval -EINVAL @p param is NULL or bad combination of values in @p param + * @retval -EALREADY Already registered + * @retval -ENOEXEC Request was rejected by GATT + */ + +int bt_pacs_register(const struct bt_pacs_register_param *param); + +/** + * @brief Unregister the Published Audio Capability Service instance. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_pacs_unregister(void); + +/** + * @brief Register Published Audio Capability. + * + * Register Audio Local Capability. + * + * @param dir Direction of the endpoint to register capability for. + * @param cap Capability structure. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_pacs_cap_register(enum bt_audio_dir dir, struct bt_pacs_cap *cap); + +/** + * @brief Unregister Published Audio Capability. + * + * Unregister Audio Local Capability. + * + * @param dir Direction of the endpoint to unregister capability for. + * @param cap Capability structure. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_pacs_cap_unregister(enum bt_audio_dir dir, struct bt_pacs_cap *cap); + +/** + * @brief Set the location for an endpoint type + * + * @param dir Direction of the endpoints to change location for. + * @param location The location to be set. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_pacs_set_location(enum bt_audio_dir dir, + enum bt_audio_location location); + +/** + * @brief Set the available contexts for an endpoint type + * + * @param dir Direction of the endpoints to change available contexts + * for. + * @param contexts The contexts to be set. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_pacs_set_available_contexts(enum bt_audio_dir dir, + enum bt_audio_context contexts); + +/** + * @brief Get the available contexts for an endpoint type + * + * @param dir Direction of the endpoints to get contexts for. + * + * @return Bitmask of available contexts. + */ + +enum bt_audio_context bt_pacs_get_available_contexts(enum bt_audio_dir dir); + +/** + * @brief Set the available contexts for a given connection + * + * This function sets the available contexts value for a given @p conn + * connection object. If the @p contexts parameter is NULL the available + * contexts value is reset to default. The default value of the available + * contexts is set using @ref bt_pacs_set_available_contexts function. The + * Available Context Value is reset to default on ACL disconnection. + * + * @param conn Connection object. + * @param dir Direction of the endpoints to change available contexts + * for. + * @param contexts The contexts to be set or NULL to reset to default. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_pacs_conn_set_available_contexts_for_conn( + struct bt_conn *conn, enum bt_audio_dir dir, + enum bt_audio_context *contexts); + +/** + * @brief Get the available contexts for a given connection + * + * This server function returns the available contexts value for a given @p + * conn connection object. The value returned is the one set with @ref + * bt_pacs_conn_set_available_contexts_for_conn function or the default value + * set with @ref bt_pacs_set_available_contexts function. + * + * @param conn Connection object. + * @param dir Direction of the endpoints to get contexts for. + * + * @return Bitmask of available contexts. + * @retval BT_AUDIO_CONTEXT_TYPE_NONE if @p conn or @p dir are invalid + */ + +enum bt_audio_context bt_pacs_get_available_contexts_for_conn( + struct bt_conn *conn, enum bt_audio_dir dir); + +/** + * @brief Set the supported contexts for an endpoint type + * + * @param dir Direction of the endpoints to change available contexts + * for. + * @param contexts The contexts to be set. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_pacs_set_supported_contexts(enum bt_audio_dir dir, + enum bt_audio_context contexts); + +#ifdef __cplusplus +} +#endif + +/** @} */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_PACS_H */ diff --git a/include/nuttx/net/bluetooth/audio/pbp.h b/include/nuttx/net/bluetooth/audio/pbp.h new file mode 100644 index 0000000000000..e7bd573e7d3f9 --- /dev/null +++ b/include/nuttx/net/bluetooth/audio/pbp.h @@ -0,0 +1,131 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/audio/pbp.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Public Broadcast Profile (PBP) APIs. + */ + +/* Copyright 2023 NXP + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_PBP_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_PBP_H + +/** + * @brief Public Broadcast Profile (PBP) + * + * @defgroup bt_pbp Public Broadcast Profile (PBP) + * + * @since 3.5 + * @version 0.8.0 + * + * @ingroup bluetooth + * @{ + * + * The Public Broadcast Profile (PBP) is used for public broadcasts by + * providing additional information in the advertising data. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Minimum size of the Public Broadcast Announcement + * + * It contains the Public Broadcast Announcement UUID (2), the Public + * Broadcast Announcement features (1) and the metadata length (1) + */ +#define BT_PBP_MIN_PBA_SIZE (BT_UUID_SIZE_16 + 1 + 1) + +/** Public Broadcast Announcement features */ + +enum bt_pbp_announcement_feature +{ + /** Broadcast Streams encryption status */ + + BT_PBP_ANNOUNCEMENT_FEATURE_ENCRYPTION = BIT(0U), + + /** Standard Quality Public Broadcast Audio configuration */ + + BT_PBP_ANNOUNCEMENT_FEATURE_STANDARD_QUALITY = BIT(1U), + + /** High Quality Public Broadcast Audio configuration */ + + BT_PBP_ANNOUNCEMENT_FEATURE_HIGH_QUALITY = BIT(2U), +}; + +/** + * @brief Creates a Public Broadcast Announcement based on the information + * received in the features parameter. + * + * @param meta Metadata to be included in the advertising data + * @param meta_len Size of the metadata fields to be included in the + * advertising data + * @param features Public Broadcast Announcement features + * @param pba_data_buf Pointer to store the PBA advertising data. Buffer + * size needs to be meta_len + @ref BT_PBP_MIN_PBA_SIZE. + * + * @return 0 on success or an appropriate error code. + */ + +int bt_pbp_get_announcement(const uint8_t meta[], size_t meta_len, + enum bt_pbp_announcement_feature features, + struct net_buf_simple *pba_data_buf); + +/** + * @brief Parses the received advertising data corresponding to a Public + * Broadcast Announcement. Returns the advertised Public Broadcast + * Announcement features and metadata. + * + * @param[in] data Advertising data to be checked + * @param[out] features Pointer to public broadcast source features to store + * the parsed features in + * @param[out] meta Pointer to the metadata present in the advertising + * data + * + * @return parsed metadata length on success. + * @retval -EINVAL if @p data, @p features or @p meta are NULL. + * @retval -ENOENT if @p data is not of type @ref BT_DATA_SVC_DATA16 or if + * the UUID in the service data is not @ref BT_UUID_PBA. + * @retval -EMSGSIZE if @p data is not large enough to contain a PBP + * announcement. + * @retval -EBADMSG if the @p data contains invalid data. + */ + +int bt_pbp_parse_announcement(struct bt_data *data, + enum bt_pbp_announcement_feature *features, + uint8_t **meta); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_PBP_H */ diff --git a/include/nuttx/net/bluetooth/audio/tbs.h b/include/nuttx/net/bluetooth/audio/tbs.h new file mode 100644 index 0000000000000..729b1f610d3db --- /dev/null +++ b/include/nuttx/net/bluetooth/audio/tbs.h @@ -0,0 +1,1263 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/audio/tbs.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Public APIs for Bluetooth Telephone Bearer Service. + * + * Copyright (c) 2020 Bose Corporation Copyright (c) 2021-2026 Nordic + * Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_TBS_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_TBS_H + +/** + * @brief Telephone Bearer Service (TBS) + * + * @defgroup bt_tbs Telephone Bearer Service (TBS) + * + * @since 3.0 + * @version 0.8.0 + * + * @ingroup bluetooth + * @{ + * + * The Telephone Bearer Service (TBS) provide procedures to discover + * telephone bearers and control calls. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** A characteristic value has changed while a Read Long Value Characteristic + * sub-procedure is in progress + */ +#define BT_TBS_ERR_VAL_CHANGED 0x80U + +/** + * @name Call States + * @{ + */ + +/** A remote party is calling (incoming call). */ +#define BT_TBS_CALL_STATE_INCOMING 0x00U +/* The process to call the remote party has started on the server, but the + * remote party is not being alerted (outgoing call). + */ +#define BT_TBS_CALL_STATE_DIALING 0x01U +/** A remote party is being alerted (outgoing call). */ +#define BT_TBS_CALL_STATE_ALERTING 0x02U +/** The call is in an active conversation. */ +#define BT_TBS_CALL_STATE_ACTIVE 0x03U +/* The call is connected but held locally. Locally Held implies that either + * the server or the client can affect the state. + */ +#define BT_TBS_CALL_STATE_LOCALLY_HELD 0x04U +/* The call is connected but held remotely. Remotely Held means that + * the state is controlled by the + * remote party of a call. + */ +#define BT_TBS_CALL_STATE_REMOTELY_HELD 0x05U +/** The call is connected but held both locally and remotely. */ +#define BT_TBS_CALL_STATE_LOCALLY_AND_REMOTELY_HELD 0x06U +/** @} */ + +/** + * @name Terminate Reason + * @{ + */ + +/** The URI value used to originate a call was formed improperly. */ +#define BT_TBS_REASON_BAD_REMOTE_URI 0x00U +/** The call failed. */ +#define BT_TBS_REASON_CALL_FAILED 0x01U +/** The remote party ended the call. */ +#define BT_TBS_REASON_REMOTE_ENDED_CALL 0x02U +/** The call ended from the server. */ +#define BT_TBS_REASON_SERVER_ENDED_CALL 0x03U +/** The line was busy. */ +#define BT_TBS_REASON_LINE_BUSY 0x04U +/** Network congestion. */ +#define BT_TBS_REASON_NETWORK_CONGESTED 0x05U +/** The client terminated the call. */ +#define BT_TBS_REASON_CLIENT_TERMINATED 0x06U +/** No service. */ +#define BT_TBS_REASON_NO_SERVICE 0x07U +/** No answer. */ +#define BT_TBS_REASON_NO_ANSWER 0x08U +/** Unspecified. */ +#define BT_TBS_REASON_UNSPECIFIED 0x09U +/** @} */ + +/** + * @name Control point error codes + * @{ + */ + +/** The opcode write was successful. */ +#define BT_TBS_RESULT_CODE_SUCCESS 0x00U +/** An invalid opcode was used for the Call Control Point write. */ +#define BT_TBS_RESULT_CODE_OPCODE_NOT_SUPPORTED 0x01U +/** The requested operation cannot be completed. */ +#define BT_TBS_RESULT_CODE_OPERATION_NOT_POSSIBLE 0x02U +/** The Call Index used for the Call Control Point write is invalid. */ +#define BT_TBS_RESULT_CODE_INVALID_CALL_INDEX 0x03U +/* The opcode written to the Call Control Point was received when the current + * Call State for the Call Index was not in the expected state. + */ +#define BT_TBS_RESULT_CODE_STATE_MISMATCH 0x04U +/** Lack of internal resources to complete the requested action. */ +#define BT_TBS_RESULT_CODE_OUT_OF_RESOURCES 0x05U +/** The Outgoing URI is incorrect or invalid when an Originate opcode is + * sent. + */ +#define BT_TBS_RESULT_CODE_INVALID_URI 0x06U +/** @} */ + +/** + * @name Optional opcodes bits + * + * Optional opcodes that can be supported. See + * bt_tbs_client_read_optional_opcodes() on how to read these from a remote + * device @{ + */ + +/** Local Hold and Local Retrieve Call Control Point Opcodes supported */ +#define BT_TBS_OPTIONAL_OPCODE_HOLD BIT(0U) +/** Join Call Control Point Opcode supported */ +#define BT_TBS_OPTIONAL_OPCODE_JOIN BIT(1U) +/** All Control Point Opcodes supported */ +#define BT_TBS_OPTIONAL_OPCODE_ALL (BT_TBS_OPTIONAL_OPCODE_HOLD | BT_TBS_OPTIONAL_OPCODE_JOIN) +/** @} */ + +/** + * @name Signal strength value limits + * @{ + */ + +/** No service */ +#define BT_TBS_SIGNAL_STRENGTH_NO_SERVICE 0U +/** Maximum signal strength */ +#define BT_TBS_SIGNAL_STRENGTH_MAX 100U +/** Signal strength is unknown */ +#define BT_TBS_SIGNAL_STRENGTH_UNKNOWN 255U +/** @} */ + +/** + * @name Call status flags bitfield + * @{ + */ + +/** Inband ringtone enabled */ +#define BT_TBS_STATUS_FLAG_INBAND_RINGTONE BIT(0U) +/** Server is in silent mod */ +#define BT_TBS_STATUS_FLAG_SILENT_MOD BIT(1U) +/** @} */ + +/** + * @name Call flags bitfield + * @{ + */ + +/** If set, call is outgoing else incoming */ +#define BT_TBS_CALL_FLAG_OUTGOING BIT(0U) +/** If set call is withheld, else not withheld */ +#define BT_TBS_CALL_FLAG_WITHHELD BIT(1U) +/** If set call is withheld by network, else provided by network */ +#define BT_TBS_CALL_FLAG_WITHHELD_BY_NETWORK BIT(2U) +/** @} */ + +/** + * @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 0xFFU + +/** Maximum size of bearer uniform caller identifier (UCI) + * + * Includes the NULL terminator. Allowed values are defined by Bluetooth + * Assigned Numbers. + */ +#define BT_TBS_MAX_UCI_SIZE 6U + +/** + * @struct bt_tbs_instance + * @brief Opaque Telephone Bearer Service instance. + */ + +struct bt_tbs_instance; + +/** + * @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. + * + * @param conn The connection used. + * + * @return true if authorized, false otherwise + */ + +typedef bool (*bt_tbs_authorize_cb)(struct bt_conn *conn); + +/** + * @brief Struct to hold the Telephone Bearer Service callbacks + * + * These can be registered for usage with bt_tbs_register_cb(). + */ + +struct bt_tbs_cb +{ + /** Client originating call */ + + bt_tbs_originate_call_cb originate_call; + + /** Client terminating call */ + + bt_tbs_terminate_call_cb terminate_call; + + /** Client holding call */ + + bt_tbs_call_change_cb hold_call; + + /** Client accepting call */ + + bt_tbs_call_change_cb accept_call; + + /** Client retrieving call */ + + bt_tbs_call_change_cb retrieve_call; + + /** Client joining calls */ + + bt_tbs_join_calls_cb join_calls; + + /** Callback to authorize a client */ + + 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, + enum bt_bearer_tech 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_scheme_list Comma-separated list of URI prefixes (e.g. + * "skype,tel"). + * + * @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_scheme_list); +/** + * @brief Register the callbacks for TBS. + * + * @param cbs Pointer to the callback structure. + */ + +void bt_tbs_register_cb(struct bt_tbs_cb *cbs); + +/** Parameters for registering a Telephone Bearer Service */ + +struct bt_tbs_register_param +{ + /** The name of the provider, for example a cellular service provider */ + + char *provider_name; + + /* @brief The Uniform Caller Identifier of the bearer + * + * See the Uniform Caller Identifiers table in Bluetooth Assigned Numbers + */ + + char *uci; + +/* The Uniform Resource Identifiers schemes supported by this bearer as an + * UTF-8 string + * + * See https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml for + * possible values. + * If multiple values are used, these shall be comma separated, e.g. + * "tel,skype". + */ + + char *uri_schemes_supported; + + /** @brief The technology of the bearer */ + + enum bt_bearer_tech technology; + + /* @brief Whether this bearer shall be registered as a Generic Telephone + * Bearer server + * + * A GTBS shall be registered before any non-GTBS services. There can only + * be a single GTBS registered. + */ + + bool gtbs; + + /* @brief Whether the application will need to authorize changes to calls + * + * If set to false then the service will automatically accept write + * requests from clients. + */ + + bool authorization_required; + + /* @brief The optional supported opcodes of the bearer + * + * See the BT_TBS_OPTIONAL_OPCODE_* values. + */ + + uint16_t optional_opcodes; +}; + +/** + * @brief Register a Telephone Bearer + * + * This will register a Telephone Bearer Service (TBS) (or a Generic + * Telephone Bearer service (GTBS)) with the provided parameters. + * + * As per the TBS specification, the GTBS shall be instantiated for the + * feature, and as such a + * GTBS + * shall always be registered before any TBS can be registered. Similarly, + * all TBS shall be unregistered before the GTBS can be unregistered with + * bt_tbs_unregister_bearer(). + * + * @param param The parameters to initialize the bearer. + + * @retval index The bearer index if return value is >= 0 + * @retval -EINVAL @p param contains invalid data + * @retval -EALREADY @p param.gtbs is true and GTBS has already been + * registered + * @retval -EAGAIN @p param.gtbs is false and GTBS has not been registered + * @retval -ENOMEM @p param.gtbs is false and no more TBS can be registered + * (see @kconfig{CONFIG_BT_TBS_BEARER_COUNT}) + * @retval -ENOEXEC The service failed to be registered + */ + +int bt_tbs_register_bearer(const struct bt_tbs_register_param *param); + +/** + * @brief Unregister a Telephone Bearer + * + * This will unregister a Telephone Bearer Service (TBS) (or a Generic + * Telephone Bearer service (GTBS)) with the provided parameters. The bearer + * shall be registered first by bt_tbs_register_bearer() before it can be + * unregistered. + * + * Similarly, all TBS shall be unregistered before the GTBS can be + * unregistered with. + * + * @param bearer_index The index of the bearer to unregister. + * + * @retval 0 Success + * @retval -EINVAL @p bearer_index is invalid + * @retval -EALREADY The bearer identified by @p bearer_index is not + * registered + * @retval -EAGAIN The bearer identified by @p bearer_index is GTBS and there + * are TBS instances registered. + * @retval -ENOEXEC The service failed to be unregistered + */ + +int bt_tbs_unregister_bearer(uint8_t bearer_index); + +/** @brief Prints all calls of all services to the debug log */ + +void bt_tbs_dbg_print_calls(void); + +/** Struct to hold a call state */ + +struct bt_tbs_client_call_state +{ + /** Index of the call */ + + uint8_t index; + + /** State of the call (see BT_TBS_CALL_STATE_*) */ + + uint8_t state; + + /** Call flags (see BT_TBS_CALL_FLAG_*) */ + + uint8_t flags; +} __packed; + +/** Struct to hold a call as the Telephone Bearer Service client */ + +struct bt_tbs_client_call +{ + /** Call information */ + + struct bt_tbs_client_call_state call_info; + + /** The remove URI */ + + char *remote_uri; +}; + +/** + * @brief Callback function for ccp_discover. + * + * @param conn The connection that was used to discover CCP for a + * device. + * @param err Error value. BT_TBS_CLIENT_RESULT_CODE_*, GATT error + * or errno value. + * @param tbs_count Number of TBS instances on peer device. + * @param gtbs_found Whether or not the server has a Generic TBS instance. + */ + +typedef void (*bt_tbs_client_discover_cb)( + struct bt_conn *conn, int err, uint8_t tbs_count, bool gtbs_found); + +/** + * @brief Callback function for writing values to peer device. + * + * @param conn The connection used in the function. + * @param err Error value. BT_TBS_CLIENT_RESULT_CODE_*, GATT error + * or errno value. + * @param inst_index The index of the TBS instance that was updated. + */ + +typedef void (*bt_tbs_client_write_value_cb)( + struct bt_conn *conn, int err, uint8_t inst_index); + +/** + * @brief Callback function for the CCP call control functions. + * + * @param conn The connection used in the function. + * @param err Error value. BT_TBS_CLIENT_RESULT_CODE_*, GATT error + * or errno value. + * @param inst_index The index of the TBS instance that was updated. + * @param call_index The call index. For #bt_tbs_client_originate_call + * this will always be 0, and does not reflect the + * actual call index. + */ + +typedef void (*bt_tbs_client_cp_cb)( + struct bt_conn *conn, int err, uint8_t inst_index, uint8_t call_index); + +/** + * @brief Callback function for functions that read a string value. + * + * @param conn The connection used in the function. + * @param err Error value. BT_TBS_CLIENT_RESULT_CODE_*, GATT error + * or errno value. + * @param inst_index The index of the TBS instance that was updated. + * @param value The Null-terminated string value. The value is not + * kept by the client, so must be copied to be saved. + */ + +typedef void (*bt_tbs_client_read_string_cb)( + struct bt_conn *conn, int err, uint8_t inst_index, const char *value); + +/** + * @brief Callback function for functions that read an integer value. + * + * @param conn The connection used in the function. + * @param err Error value. BT_TBS_CLIENT_RESULT_CODE_*, GATT error + * or errno value. + * @param inst_index The index of the TBS instance that was updated. + * @param value The integer value. + */ + +typedef void (*bt_tbs_client_read_value_cb)( + struct bt_conn *conn, int err, uint8_t inst_index, uint32_t value); + +/** + * @brief Callback function for ccp_read_termination_reason. + * + * @param conn The connection used in the function. + * @param err Error value. BT_TBS_CLIENT_RESULT_CODE_*, GATT error + * or errno value. + * @param inst_index The index of the TBS instance that was updated. + * @param call_index The call index. + * @param reason The termination reason. + */ + +typedef void (*bt_tbs_client_termination_reason_cb)( + struct bt_conn *conn, int err, uint8_t inst_index, uint8_t call_index, + uint8_t reason); + +/** + * @brief Callback function for ccp_read_current_calls. + * + * @param conn The connection used in the function. + * @param err Error value. BT_TBS_CLIENT_RESULT_CODE_*, GATT error + * or errno value. + * @param inst_index The index of the TBS instance that was updated. + * @param call_count Number of calls read. + * @param calls Array of calls. The array is not kept by the client, + * so must be copied to be saved. + */ + +typedef void (*bt_tbs_client_current_calls_cb)( + struct bt_conn *conn, int err, uint8_t inst_index, uint8_t call_count, + const struct bt_tbs_client_call *calls); + +/** + * @brief Callback function for ccp_read_call_state. + * + * @param conn The connection used in the function. + * @param err Error value. BT_TBS_CLIENT_RESULT_CODE_*, GATT error + * or errno value. + * @param inst_index The index of the TBS instance that was updated. + * @param call_count Number of call states read. + * @param call_states Array of call states. The array is not kept by the + * client, so must be copied to be saved. + */ + +typedef void (*bt_tbs_client_call_states_cb)( + struct bt_conn *conn, int err, uint8_t inst_index, uint8_t call_count, + const struct bt_tbs_client_call_state *call_states); + +/** + * @brief Struct to hold the Telephone Bearer Service client callbacks + * + * These can be registered for usage with bt_tbs_client_register_cb(). + */ + +struct bt_tbs_client_cb +{ + /** Discovery has completed */ + + bt_tbs_client_discover_cb discover; +#if defined(CONFIG_BT_TBS_CLIENT_ORIGINATE_CALL) || defined(__DOXYGEN__) + /** Originate call has completed */ + + bt_tbs_client_cp_cb originate_call; +#endif /* defined(CONFIG_BT_TBS_CLIENT_ORIGINATE_CALL) */ +#if defined(CONFIG_BT_TBS_CLIENT_TERMINATE_CALL) || defined(__DOXYGEN__) + /** Terminate call has completed */ + bt_tbs_client_cp_cb terminate_call; +#endif /* defined(CONFIG_BT_TBS_CLIENT_TERMINATE_CALL) */ +#if defined(CONFIG_BT_TBS_CLIENT_HOLD_CALL) || defined(__DOXYGEN__) + /** Hold call has completed */ + bt_tbs_client_cp_cb hold_call; +#endif /* defined(CONFIG_BT_TBS_CLIENT_HOLD_CALL) */ +#if defined(CONFIG_BT_TBS_CLIENT_ACCEPT_CALL) || defined(__DOXYGEN__) + /** Accept call has completed */ + bt_tbs_client_cp_cb accept_call; +#endif /* defined(CONFIG_BT_TBS_CLIENT_ACCEPT_CALL) */ +#if defined(CONFIG_BT_TBS_CLIENT_RETRIEVE_CALL) || defined(__DOXYGEN__) + /** Retrieve call has completed */ + bt_tbs_client_cp_cb retrieve_call; +#endif /* defined(CONFIG_BT_TBS_CLIENT_RETRIEVE_CALL) */ +#if defined(CONFIG_BT_TBS_CLIENT_JOIN_CALLS) || defined(__DOXYGEN__) + /** Join calls has completed */ + bt_tbs_client_cp_cb join_calls; +#endif /* defined(CONFIG_BT_TBS_CLIENT_JOIN_CALLS) */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) || defined(__DOXYGEN__) + /** Bearer provider name has been read */ + bt_tbs_client_read_string_cb bearer_provider_name; +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) || defined(__DOXYGEN__) + /** Bearer UCI has been read */ + bt_tbs_client_read_string_cb bearer_uci; +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) || defined(__DOXYGEN__) + /* @brief Bearer technology has been read. + * + * @param conn The connection used in the function. + * @param err Error value. BT_TBS_CLIENT_RESULT_CODE_* or GATT + * error. + * @param inst_index The index of the TBS instance that was updated. + * @param tech The technology read. The value may be outside the + * values of the enum. + */ + + void (*technology)(struct bt_conn *conn, int err, uint8_t inst_index, + enum bt_bearer_tech tech); +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST) || defined(__DOXYGEN__) + /** Bearer URI list has been read */ + bt_tbs_client_read_string_cb uri_list; +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST) */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) || defined(__DOXYGEN__) + /** Bearer signal strength has been read */ + bt_tbs_client_read_value_cb signal_strength; +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) */ +#if defined(CONFIG_BT_TBS_CLIENT_READ_BEARER_SIGNAL_INTERVAL) || defined(__DOXYGEN__) + /** Bearer signal interval has been read */ + bt_tbs_client_read_value_cb signal_interval; +#endif /* defined(CONFIG_BT_TBS_CLIENT_READ_BEARER_SIGNAL_INTERVAL) */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) || defined(__DOXYGEN__) + /** Bearer current calls has been read */ + bt_tbs_client_current_calls_cb current_calls; +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) */ +#if defined(CONFIG_BT_TBS_CLIENT_CCID) || defined(__DOXYGEN__) + /** Bearer CCID has been read */ + bt_tbs_client_read_value_cb ccid; +#endif /* defined(CONFIG_BT_TBS_CLIENT_CCID) */ +#if defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) || defined(__DOXYGEN__) + /** Bearer call URI has been read */ + bt_tbs_client_read_string_cb call_uri; +#endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) */ +#if defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) || defined(__DOXYGEN__) + /** Bearer status flags has been read */ + bt_tbs_client_read_value_cb status_flags; +#endif /* defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) */ + /** Bearer call states has been read */ + bt_tbs_client_call_states_cb call_state; +#if defined(CONFIG_BT_TBS_CLIENT_OPTIONAL_OPCODES) || defined(__DOXYGEN__) + /** Bearer optional opcodes has been read */ + + bt_tbs_client_read_value_cb optional_opcodes; +#endif /* defined(CONFIG_BT_TBS_CLIENT_OPTIONAL_OPCODES) */ + /** Bearer terminate reason has been read */ + bt_tbs_client_termination_reason_cb termination_reason; +#if defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) || defined(__DOXYGEN__) + /** Bearer remote URI has been read */ + + bt_tbs_client_read_string_cb remote_uri; +#endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) */ +#if defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) || defined(__DOXYGEN__) + /** Bearer friendly name has been read */ + bt_tbs_client_read_string_cb friendly_name; +#endif /* defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) */ + + /** @cond INTERNAL_HIDDEN */ + + /** Internally used field for list handling */ + + struct list_node _node; + + /** @endcond */ +}; + +/** + * @brief Discover TBS for a connection. This will start a GATT + * discover and setup handles and subscriptions. + * + * @param conn The connection to discover TBS for. + * + * @return int 0 on success, GATT error value on fail. + */ + +int bt_tbs_client_discover(struct bt_conn *conn); + +/** + * @brief Set the outgoing URI for a TBS instance on the peer device. + * + * @param conn The connection to the TBS server. + * @param inst_index The index of the TBS instance. + * @param uri The Null-terminated URI string. + * + * @return int 0 on success, errno value on fail. + */ + +int bt_tbs_client_set_outgoing_uri(struct bt_conn *conn, + uint8_t inst_index, const char *uri); + +/** + * @brief Set the signal strength reporting interval for a TBS instance. + * + * @param conn The connection to the TBS server. + * @param inst_index The index of the TBS instance. + * @param interval The interval to write (0-255 seconds). + * + * @return int 0 on success, errno value on fail. + * + * @note @kconfig{CONFIG_BT_TBS_CLIENT_SET_BEARER_SIGNAL_INTERVAL} must be + * set for this function to be effective. + */ + +int bt_tbs_client_set_signal_strength_interval( + struct bt_conn *conn, uint8_t inst_index, uint8_t interval); + +/** + * @brief Request to originate a call. + * + * @param conn The connection to the TBS server. + * @param inst_index The index of the TBS instance. + * @param uri The URI of the callee. + * + * @return int 0 on success, errno value on fail. + * + * @note @kconfig{CONFIG_BT_TBS_CLIENT_ORIGINATE_CALL} must be set + * for this function to be effective. + */ + +int bt_tbs_client_originate_call(struct bt_conn *conn, + uint8_t inst_index, const char *uri); + +/** + * @brief Request to terminate a call + * + * @param conn The connection to the TBS server. + * @param inst_index The index of the TBS instance. + * @param call_index The call index to terminate. + * + * @return int 0 on success, errno value on fail. + * + * @note @kconfig{CONFIG_BT_TBS_CLIENT_TERMINATE_CALL} must be set + * for this function to be effective. + */ + +int bt_tbs_client_terminate_call(struct bt_conn *conn, + uint8_t inst_index, uint8_t call_index); + +/** + * @brief Request to hold a call + * + * @param conn The connection to the TBS server. + * @param inst_index The index of the TBS instance. + * @param call_index The call index to place on hold. + * + * @return int 0 on success, errno value on fail. + * + * @note @kconfig{CONFIG_BT_TBS_CLIENT_HOLD_CALL} must be set + * for this function to be effective. + */ + +int bt_tbs_client_hold_call(struct bt_conn *conn, + uint8_t inst_index, uint8_t call_index); + +/** + * @brief Accept an incoming call + * + * @param conn The connection to the TBS server. + * @param inst_index The index of the TBS instance. + * @param call_index The call index to accept. + * + * @return int 0 on success, errno value on fail. + * + * @note @kconfig{CONFIG_BT_TBS_CLIENT_ACCEPT_CALL} must be set + * for this function to be effective. + */ + +int bt_tbs_client_accept_call(struct bt_conn *conn, + uint8_t inst_index, uint8_t call_index); + +/** + * @brief Retrieve call from (local) hold. + * + * @param conn The connection to the TBS server. + * @param inst_index The index of the TBS instance. + * @param call_index The call index to retrieve. + * + * @return int 0 on success, errno value on fail. + * + * @note @kconfig{CONFIG_BT_TBS_CLIENT_RETRIEVE_CALL} must be set + * for this function to be effective. + */ + +int bt_tbs_client_retrieve_call(struct bt_conn *conn, + uint8_t inst_index, uint8_t call_index); + +/** + * @brief Join multiple calls. + * + * @param conn The connection to the TBS server. + * @param inst_index The index of the TBS instance. + * @param call_indexes Array of call indexes. + * @param count Number of call indexes in the call_indexes array. + * + * @return int 0 on success, errno value on fail. + * + * @note @kconfig{CONFIG_BT_TBS_CLIENT_JOIN_CALLS} must be set + * for this function to be effective. + */ + +int bt_tbs_client_join_calls(struct bt_conn *conn, uint8_t inst_index, + const uint8_t *call_indexes, uint8_t count); + +/** + * @brief Read the bearer provider name of a TBS instance. + * + * @param conn The connection to the TBS server. + * @param inst_index The index of the TBS instance. + * + * @return int 0 on success, errno value on fail. + * + * @note @kconfig{CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME} must be set + * for this function to be effective. + */ + +int bt_tbs_client_read_bearer_provider_name(struct bt_conn *conn, + uint8_t inst_index); + +/** + * @brief Read the UCI of a TBS instance. + * + * @param conn The connection to the TBS server. + * @param inst_index The index of the TBS instance. + * + * @return int 0 on success, errno value on fail. + * + * @note @kconfig{CONFIG_BT_TBS_CLIENT_BEARER_UCI} must be set + * for this function to be effective. + */ + +int bt_tbs_client_read_bearer_uci(struct bt_conn *conn, uint8_t inst_index); + +/** + * @brief Read the technology of a TBS instance. + * + * @param conn The connection to the TBS server. + * @param inst_index The index of the TBS instance. + * + * @return int 0 on success, errno value on fail. + * + * @note @kconfig{CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY} must be set + * for this function to be effective. + */ + +int bt_tbs_client_read_technology(struct bt_conn *conn, uint8_t inst_index); + +/** + * @brief Read the URI schemes list of a TBS instance. + * + * @param conn The connection to the TBS server. + * @param inst_index The index of the TBS instance. + * + * @return int 0 on success, errno value on fail. + * + * @note @kconfig{CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST} + * must be set for this function to be effective. + */ + +int bt_tbs_client_read_uri_list(struct bt_conn *conn, uint8_t inst_index); + +/** + * @brief Read the current signal strength of a TBS instance. + * + * @param conn The connection to the TBS server. + * @param inst_index The index of the TBS instance. + * + * @return int 0 on success, errno value on fail. + * + * @note @kconfig{CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH} must be set + * for this function to be effective. + */ + +int bt_tbs_client_read_signal_strength(struct bt_conn *conn, + uint8_t inst_index); + +/** + * @brief Read the signal strength reporting interval of a TBS instance. + * + * @param conn The connection to the TBS server. + * @param inst_index The index of the TBS instance. + * + * @return int 0 on success, errno value on fail. + * + * @note @kconfig{CONFIG_BT_TBS_CLIENT_READ_BEARER_SIGNAL_INTERVAL} must be + * set for this function to be effective. + */ + +int bt_tbs_client_read_signal_interval(struct bt_conn *conn, + uint8_t inst_index); + +/** + * @brief Read the list of current calls of a TBS instance. + * + * @param conn The connection to the TBS server. + * @param inst_index The index of the TBS instance. + * + * @return int 0 on success, errno value on fail. + * + * @note @kconfig{CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS} must be set + * for this function to be effective. + */ + +int bt_tbs_client_read_current_calls(struct bt_conn *conn, + uint8_t inst_index); + +/** + * @brief Read the content ID of a TBS instance. + * + * @param conn The connection to the TBS server. + * @param inst_index The index of the TBS instance. + * + * @return int 0 on success, errno value on fail. + * + * @note @kconfig{CONFIG_BT_TBS_CLIENT_CCID} must be set + * for this function to be effective. + */ + +int bt_tbs_client_read_ccid(struct bt_conn *conn, uint8_t inst_index); + +/** + * @brief Read the call target URI of a TBS instance. + * + * @param conn The connection to the TBS server. + * @param inst_index The index of the TBS instance. + * + * @return int 0 on success, errno value on fail. + * + * @note @kconfig{CONFIG_BT_TBS_CLIENT_INCOMING_URI} must be set + * for this function to be effective. + */ + +int bt_tbs_client_read_call_uri(struct bt_conn *conn, uint8_t inst_index); + +/** + * @brief Read the feature and status value of a TBS instance. + * + * @param conn The connection to the TBS server. + * @param inst_index The index of the TBS instance. + * + * @return int 0 on success, errno value on fail. + * + * @note @kconfig{CONFIG_BT_TBS_CLIENT_STATUS_FLAGS} must be set + * for this function to be effective. + */ + +int bt_tbs_client_read_status_flags(struct bt_conn *conn, + uint8_t inst_index); + +/** + * @brief Read the states of the current calls of a TBS instance. + * + * @param conn The connection to the TBS server. + * @param inst_index The index of the TBS instance. + * + * @return int 0 on success, errno value on fail. + */ + +int bt_tbs_client_read_call_state(struct bt_conn *conn, uint8_t inst_index); + +/** + * @brief Read the remote URI of a TBS instance. + * + * @param conn The connection to the TBS server. + * @param inst_index The index of the TBS instance. + * + * @return int 0 on success, errno value on fail. + * + * @note @kconfig{CONFIG_BT_TBS_CLIENT_INCOMING_CALL} must be set + * for this function to be effective. + */ + +int bt_tbs_client_read_remote_uri(struct bt_conn *conn, uint8_t inst_index); + +/** + * @brief Read the friendly name of a call for a TBS instance. + * + * @param conn The connection to the TBS server. + * @param inst_index The index of the TBS instance. + * + * @return int 0 on success, errno value on fail. + * + * @note @kconfig{CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME} must be set + * for this function to be effective. + */ + +int bt_tbs_client_read_friendly_name(struct bt_conn *conn, + uint8_t inst_index); + +/** + * @brief Read the supported opcode of a TBS instance. + * + * @param conn The connection to the TBS server. + * @param inst_index The index of the TBS instance. + * + * @return int 0 on success, errno value on fail. + * + * @note @kconfig{CONFIG_BT_TBS_CLIENT_OPTIONAL_OPCODES} must be set + * for this function to be effective. + */ + +int bt_tbs_client_read_optional_opcodes(struct bt_conn *conn, + uint8_t inst_index); + +/** + * @brief Register the callbacks for CCP. + * + * @param cbs Pointer to the callback structure. + * + * @retval 0 Success + * @retval -EINVAL @p cbs is NULL + * @retval -EEXIST @p cbs is already registered + */ + +int bt_tbs_client_register_cb(struct bt_tbs_client_cb *cbs); + +/** + * @brief Look up Telephone Bearer Service instance by CCID + * + * @param conn The connection to the TBS server. + * @param ccid The CCID to lookup a service instance for. + * + * @return Pointer to a Telephone Bearer Service instance if found else NULL. + * + * @note @kconfig{CONFIG_BT_TBS_CLIENT_CCID} must be set + * for this function to be effective. + */ + +struct bt_tbs_instance *bt_tbs_client_get_by_ccid(const struct bt_conn *conn, + uint8_t ccid); + +/** + * @brief Look up Telephone Bearer Service instance by index + * + * @param conn The connection to the TBS server. + * @param index The index to lookup a service instance for. + * + * @return Pointer to a Telephone Bearer Service instance if found else NULL. + */ + +struct bt_tbs_instance *bt_tbs_client_get_by_index( + const struct bt_conn *conn, uint8_t index); + +#ifdef __cplusplus +} +#endif + +/** @} */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_TBS_H */ diff --git a/include/nuttx/net/bluetooth/audio/tmap.h b/include/nuttx/net/bluetooth/audio/tmap.h new file mode 100644 index 0000000000000..623c249ac50be --- /dev/null +++ b/include/nuttx/net/bluetooth/audio/tmap.h @@ -0,0 +1,154 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/audio/tmap.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Header for Bluetooth TMAP. + * + * Copyright 2023 NXP Copyright (c) 2024-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_TMAP_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_TMAP_H + +/** + * @brief Telephone and Media Audio Profile (TMAP) + * + * @defgroup bt_tmap Telephone and Media Audio Profile (TMAP) + * + * @since 3.4 + * @version 0.8.0 + * + * @ingroup bluetooth + * @{ + * + * The Telephone and Media Audio Profile (TMAP) uses a collection of + * Bluetooth features and profiles to enable interoperability between devices + * for telephony and media audio. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +/** @brief TMAP Role characteristic */ + +enum bt_tmap_role +{ + /** + * @brief TMAP Call Gateway role + * + * This role is defined to telephone and VoIP applications using the Call + * Control Profile to control calls on a remote TMAP Call Terminal. Audio + * streams in this role are typically bi-directional. + */ + + BT_TMAP_ROLE_CG = BIT(0U), + /** + * @brief TMAP Call Terminal role + * + * This role is defined to telephone and VoIP applications using the Call + * Control Profile to expose calls to remote TMAP Call Gateways. Audio + * streams in this role are typically bi-directional. + */ + + BT_TMAP_ROLE_CT = BIT(1U), + /** + * @brief TMAP Unicast Media Sender role + * + * This role is defined send media audio to TMAP Unicast Media Receivers. + * Audio streams in this role are typically uni-directional. + */ + + BT_TMAP_ROLE_UMS = BIT(2U), + /** + * @brief TMAP Unicast Media Receiver role + * + * This role is defined receive media audio to TMAP Unicast Media Senders. + * Audio streams in this role are typically uni-directional. + */ + + BT_TMAP_ROLE_UMR = BIT(3U), + /** + * @brief TMAP Broadcast Media Sender role + * + * This role is defined send media audio to TMAP Broadcast Media Receivers. + * Audio streams in this role are always uni-directional. + */ + + BT_TMAP_ROLE_BMS = BIT(4U), + /** + * @brief TMAP Broadcast Media Receiver role + * + * This role is defined to receive media audio from TMAP Broadcast Media + * Senders. Audio streams in this role are always uni-directional. + */ + + BT_TMAP_ROLE_BMR = BIT(5U), +}; + +/** @brief TMAP callback structure. */ + +struct bt_tmap_cb +{ + /** + * @brief TMAP discovery complete callback + * + * This callback notifies the application about the value of the TMAP Role + * characteristic on the peer. + * + * @param role Peer TMAP role(s). + * @param conn Pointer to the connection + * @param err 0 if success, ATT error received from server otherwise. + */ + + void (*discovery_complete)(enum bt_tmap_role role, struct bt_conn *conn, + int err); +}; + +/** + * @brief Adds TMAS instance to database and sets the received TMAP role(s). + * + * @param role TMAP role(s) of the device (one or multiple). + * + * @return 0 on success or negative error value on failure. + */ + +int bt_tmap_register(enum bt_tmap_role role); + +/** + * @brief Perform service discovery as TMAP Client + * + * @param conn Pointer to the connection. + * @param tmap_cb Pointer to struct of TMAP callbacks. + * + * @return 0 on success or negative error value on failure. + */ + +int bt_tmap_discover(struct bt_conn *conn, const struct bt_tmap_cb *tmap_cb); + +/** + * @brief Set one or multiple TMAP roles dynamically. + * Previously registered value will be overwritten. + * + * @param role TMAP role(s). + * + */ + +void bt_tmap_set_role(enum bt_tmap_role role); + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_TMAP_H */ diff --git a/include/nuttx/net/bluetooth/audio/vcp.h b/include/nuttx/net/bluetooth/audio/vcp.h new file mode 100644 index 0000000000000..6a61195a0f040 --- /dev/null +++ b/include/nuttx/net/bluetooth/audio/vcp.h @@ -0,0 +1,664 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/audio/vcp.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Bluetooth Volume Control Profile (VCP) APIs. + */ + +/* Copyright (c) 2020-2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_VCP_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_VCP_H + +/** + * @brief Volume Control Profile (VCP) + * + * @defgroup bt_vcp Volume Control Profile (VCP) + * + * @since 2.7 + * @version 0.8.0 + * + * @ingroup bluetooth + * @{ + * + * The Volume Control Profile (VCP) provides procedures to control the volume + * level and mute state on audio devices. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Defines the maximum number of Volume Offset Control service instances for + * the Volume Control Profile Volume Renderer + */ +#if defined(CONFIG_BT_VCP_VOL_REND) +#define BT_VCP_VOL_REND_VOCS_CNT CONFIG_BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT +#else +#define BT_VCP_VOL_REND_VOCS_CNT 0U +#endif /* CONFIG_BT_VCP_VOL_REND */ + +/* Defines the maximum number of Audio Input Control service instances for + * the Volume Control Profile Volume Renderer + */ +#if defined(CONFIG_BT_VCP_VOL_REND) +#define BT_VCP_VOL_REND_AICS_CNT CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT +#else +#define BT_VCP_VOL_REND_AICS_CNT 0U +#endif /* CONFIG_BT_VCP_VOL_REND */ + +/** + * @name Volume Control Service Error codes + * @{ + */ + +/* The Change_Counter operand value does not match the Change_Counter field + * value of the Volume State characteristic. + */ +#define BT_VCP_ERR_INVALID_COUNTER 0x80U +/** An invalid opcode has been used in a control point procedure. */ +#define BT_VCP_ERR_OP_NOT_SUPPORTED 0x81U +/** @} */ + +/** + * @name Volume Control Service Mute Values + * @{ + */ + +/** The volume state is unmuted */ +#define BT_VCP_STATE_UNMUTED 0x00U +/** The volume state is muted */ +#define BT_VCP_STATE_MUTED 0x01U +/** @} */ + +/** + * @struct bt_vcp_vol_ctlr + * @brief Opaque Volume Control Service instance. + */ + +struct bt_vcp_vol_ctlr; + +/** Register structure for Volume Control Service */ + +struct bt_vcp_vol_rend_register_param +{ + /** Initial step size (1-255) */ + + uint8_t step; + + /** Initial mute state (0-1) */ + + uint8_t mute; + + /** Initial volume level (0-255) */ + + uint8_t volume; + + /** Register parameters for Volume Offset Control Services */ + + struct bt_vocs_register_param vocs_param[BT_VCP_VOL_REND_VOCS_CNT]; + + /** Register parameters for Audio Input Control Services */ + + struct bt_aics_register_param aics_param[BT_VCP_VOL_REND_AICS_CNT]; + + /** Volume Control Service callback structure. */ + + struct bt_vcp_vol_rend_cb *cb; +}; + +/** + * @brief Volume Control Service included services + * + * Used for to represent the Volume Control Service included service + * instances, for either a client or a server. The instance pointers either + * represent local server instances, or remote service instances. + */ + +struct bt_vcp_included +{ + /** Number of Volume Offset Control Service instances */ + + uint8_t vocs_cnt; + + /** Array of pointers to Volume Offset Control Service instances */ + + struct bt_vocs **vocs; + + /** Number of Audio Input Control Service instances */ + + uint8_t aics_cnt; + + /** Array of pointers to Audio Input Control Service instances */ + + struct bt_aics **aics; +}; + +/** + * @brief Get Volume Control Service included services. + * + * Returns a pointer to a struct that contains information about the Volume + * Control Service included service instances, such as pointers to the Volume + * Offset Control Service (Volume Offset Control Service) or Audio Input + * Control Service (AICS) instances. + * + * @param[out] included Pointer to store the result in. + * + * @return 0 if success, errno on failure. + */ + +int bt_vcp_vol_rend_included_get(struct bt_vcp_included *included); + +/** + * @brief Register the Volume Control Service. + * + * This will register and enable the service and make it discoverable by + * clients. + * + * @param param Volume Control Service register parameters. + * + * @return 0 if success, errno on failure. + */ + +int bt_vcp_vol_rend_register(struct bt_vcp_vol_rend_register_param *param); + +/** + * @brief Struct to hold the Volume Renderer callbacks + * + * These can be registered for usage with bt_vcp_vol_rend_register(). + */ + +struct bt_vcp_vol_rend_cb +{ + /* @brief Callback function for Volume Control Service volume state. + * + * Called when the value is locally read with bt_vcp_vol_rend_get_state(), + * or if the state is changed by either the Volume Renderer or a remote + * Volume Controller. + * + * @param conn Pointer to the connection to a remote device if the + * change was caused by it, otherwise NULL. + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param volume The volume of the Volume Control Service server. + * @param mute The mute setting of the Volume Control Service server. + */ + + void (*state)(struct bt_conn *conn, int err, uint8_t volume, uint8_t mute); + + /* @brief Callback function for Volume Control Service flags. + * + * Called when the value is locally read as the server. Called when the + * value is remotely read as the client. Called if the value is changed by + * either the server or client. + * + * @param conn Pointer to the connection to a remote device if the + * change was caused by it, otherwise NULL. + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param flags The flags of the Volume Control Service server. + */ + + void (*flags)(struct bt_conn *conn, int err, uint8_t flags); +}; + +/** + * @brief Set the Volume Control Service volume step size. + * + * Set the value that the volume changes, when changed relatively with e.g. + * @ref bt_vcp_vol_rend_vol_down or @ref bt_vcp_vol_rend_vol_up. + * + * This can only be done as the server. + * + * @param volume_step The volume step size (1-255). + * + * @return 0 if success, errno on failure. + */ + +int bt_vcp_vol_rend_set_step(uint8_t volume_step); + +/** + * @brief Get the Volume Control Service volume state. + * + * @return 0 if success, errno on failure. + */ + +int bt_vcp_vol_rend_get_state(void); + +/** + * @brief Get the Volume Control Service flags. + * + * @return 0 if success, errno on failure. + */ + +int bt_vcp_vol_rend_get_flags(void); + +/** + * @brief Turn the volume down by one step on the server. + * + * @return 0 if success, errno on failure. + */ + +int bt_vcp_vol_rend_vol_down(void); + +/** + * @brief Turn the volume up by one step on the server. + * + * @return 0 if success, errno on failure. + */ + +int bt_vcp_vol_rend_vol_up(void); + +/** + * @brief Turn the volume down and unmute the server. + * + * @return 0 if success, errno on failure. + */ + +int bt_vcp_vol_rend_unmute_vol_down(void); + +/** + * @brief Turn the volume up and unmute the server. + * + * @return 0 if success, errno on failure. + */ + +int bt_vcp_vol_rend_unmute_vol_up(void); + +/** + * @brief Set the volume on the server + * + * @param volume The absolute volume to set. + * + * @return 0 if success, errno on failure. + */ + +int bt_vcp_vol_rend_set_vol(uint8_t volume); + +/** + * @brief Unmute the server. + * + * @return 0 if success, errno on failure. + */ + +int bt_vcp_vol_rend_unmute(void); + +/** + * @brief Mute the server. + * + * @return 0 if success, errno on failure. + */ + +int bt_vcp_vol_rend_mute(void); + +/** + * @brief Struct to hold the Volume Controller callbacks + * + * These can be registered for usage with bt_vcp_vol_ctlr_cb_register(). + */ + +struct bt_vcp_vol_ctlr_cb +{ + /* @brief Callback function for Volume Control Profile volume state. + * + * Called when the value is remotely read as the Volume Controller. Called + * if the value is changed by either the Volume Renderer or Volume + * Controller, and notified to the to Volume Controller. + * + * @param vol_ctlr Volume Controller instance pointer. + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param volume The volume of the Volume Renderer. + * @param mute The mute setting of the Volume Renderer. + */ + + void (*state)(struct bt_vcp_vol_ctlr *vol_ctlr, int err, uint8_t volume, + uint8_t mute); + + /* @brief Callback function for Volume Control Profile volume flags. + * + * Called when the value is remotely read as the Volume Controller. Called + * if the value is changed by the Volume Renderer. + * + * A non-zero value indicates the volume has been changed on the Volume + * Renderer since it was booted. + * + * @param vol_ctlr Volume Controller instance pointer. + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + * @param flags The flags of the Volume Renderer. + */ + + void (*flags)(struct bt_vcp_vol_ctlr *vol_ctlr, int err, uint8_t flags); + + /* @brief Callback function for bt_vcp_vol_ctlr_discover(). + * + * This callback is called once the discovery procedure is completed. + * + * @param vol_ctlr Volume Controller instance pointer. + * @param err Error value. 0 on success, GATT error on positive + * value or errno on negative value. + * @param vocs_count Number of Volume Offset Control Service instances on + * the remote Volume Renderer. + * @param aics_count Number of Audio Input Control Service instances the + * remote Volume Renderer. + */ + + void (*discover)(struct bt_vcp_vol_ctlr *vol_ctlr, + int err, + uint8_t vocs_count, + uint8_t aics_count); + + /* @brief Callback function for bt_vcp_vol_ctlr_vol_down(). + * + * Called when the volume down procedure is completed. + * + * @param vol_ctlr Volume Controller instance pointer. + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + */ + + void (*vol_down)(struct bt_vcp_vol_ctlr *vol_ctlr, int err); + + /* @brief Callback function for bt_vcp_vol_ctlr_vol_up(). + * + * Called when the volume up procedure is completed. + * + * @param vol_ctlr Volume Controller instance pointer. + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + */ + + void (*vol_up)(struct bt_vcp_vol_ctlr *vol_ctlr, int err); + + /* @brief Callback function for bt_vcp_vol_ctlr_mute(). + * + * Called when the mute procedure is completed. + * + * @param vol_ctlr Volume Controller instance pointer. + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + */ + + void (*mute)(struct bt_vcp_vol_ctlr *vol_ctlr, int err); + + /* @brief Callback function for bt_vcp_vol_ctlr_unmute(). + * + * Called when the unmute procedure is completed. + * + * @param vol_ctlr Volume Controller instance pointer. + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + */ + + void (*unmute)(struct bt_vcp_vol_ctlr *vol_ctlr, int err); + + /* @brief Callback function for bt_vcp_vol_ctlr_vol_down_unmute(). + * + * Called when the volume down and unmute procedure is completed. + * + * @param vol_ctlr Volume Controller instance pointer. + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + */ + + void (*vol_down_unmute)(struct bt_vcp_vol_ctlr *vol_ctlr, int err); + + /* @brief Callback function for bt_vcp_vol_ctlr_vol_up_unmute(). + * + * Called when the volume up and unmute procedure is completed. + * + * @param vol_ctlr Volume Controller instance pointer. + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + */ + + void (*vol_up_unmute)(struct bt_vcp_vol_ctlr *vol_ctlr, int err); + + /* @brief Callback function for bt_vcp_vol_ctlr_vol_set(). + * + * Called when the set absolute volume procedure is completed. + * + * @param vol_ctlr Volume Controller instance pointer. + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + */ + + void (*vol_set)(struct bt_vcp_vol_ctlr *vol_ctlr, int err); + + /** Volume Offset Control Service callbacks */ + + struct bt_vocs_cb vocs_cb; + + /** Audio Input Control Service callbacks */ + + struct bt_aics_cb aics_cb; + + /** @cond INTERNAL_HIDDEN */ + + /** Internally used field for list handling */ + + struct list_node _node; + + /** @endcond */ +}; + +/** + * @brief Registers the callbacks used by the Volume Controller. + * + * @param cb The callback structure. + * + * @retval 0 on success + * @retval -EINVAL if @p cb is NULL + * @retval -EALREADY if @p cb was already registered + */ + +int bt_vcp_vol_ctlr_cb_register(struct bt_vcp_vol_ctlr_cb *cb); + +/** + * @brief Unregisters the callbacks used by the Volume Controller. + * + * @param cb The callback structure. + * + * @retval 0 on success + * @retval -EINVAL if @p cb is NULL + * @retval -EALREADY if @p cb was not registered + */ + +int bt_vcp_vol_ctlr_cb_unregister(struct bt_vcp_vol_ctlr_cb *cb); + +/** + * @brief Discover Volume Control Service and included services. + * + * This will start a GATT discovery and setup handles and subscriptions. This + * shall be called once before any other actions can be executed for the peer + * device, and the + * @ref bt_vcp_vol_ctlr_cb.discover callback will notify when it is possible + * to start remote operations. + * + * This shall only be done as the client, + * + * @param conn The connection to discover Volume Control Service + * for. + * @param[out] vol_ctlr Valid remote instance object on success. + * + * @return 0 if success, errno on failure. + */ + +int bt_vcp_vol_ctlr_discover(struct bt_conn *conn, + struct bt_vcp_vol_ctlr **vol_ctlr); + +/** + * @brief Get the volume controller from a connection pointer + * + * Get the Volume Control Profile Volume Controller pointer from a connection + * pointer. Only volume controllers that have been initiated via + * bt_vcp_vol_ctlr_discover() can be retrieved. + * + * @param conn Connection pointer. + * + * @retval Pointer to a Volume Control Profile Volume Controller instance + * @retval NULL if @p conn is NULL or if the connection has not done + * discovery yet + */ + +struct bt_vcp_vol_ctlr *bt_vcp_vol_ctlr_get_by_conn( + const struct bt_conn *conn); + +/** + * @brief Get the connection pointer of a client instance + * + * Get the Bluetooth connection pointer of a Volume Control Service client + * instance. + * + * @param vol_ctlr Volume Controller instance pointer. + * @param[out] conn Connection pointer. + * + * @return 0 if success, errno on failure. + */ + +int bt_vcp_vol_ctlr_conn_get(const struct bt_vcp_vol_ctlr *vol_ctlr, + struct bt_conn **conn); + +/** + * @brief Get Volume Control Service included services. + * + * Returns a pointer to a struct that contains information about the Volume + * Control Service included service instances, such as pointers to the Volume + * Offset Control Service (Volume Offset Control Service) or Audio Input + * Control Service (AICS) instances. + * + * Requires that @kconfig{CONFIG_BT_VCP_VOL_CTLR_VOCS} or + * @kconfig{CONFIG_BT_VCP_VOL_CTLR_AICS} is enabled. + * + * @param vol_ctlr Volume Controller instance pointer. + * @param[out] included Pointer to store the result in. + * + * @return 0 if success, errno on failure. + */ + +int bt_vcp_vol_ctlr_included_get(struct bt_vcp_vol_ctlr *vol_ctlr, + struct bt_vcp_included *included); + +/** + * @brief Read the volume state of a remote Volume Renderer. + * + * @param vol_ctlr Volume Controller instance pointer. + * + * @return 0 if success, errno on failure. + */ + +int bt_vcp_vol_ctlr_read_state(struct bt_vcp_vol_ctlr *vol_ctlr); + +/** + * @brief Read the volume flags of a remote Volume Renderer. + * + * @param vol_ctlr Volume Controller instance pointer. + * + * @return 0 if success, errno on failure. + */ + +int bt_vcp_vol_ctlr_read_flags(struct bt_vcp_vol_ctlr *vol_ctlr); + +/** + * @brief Turn the volume down one step on a remote Volume Renderer + * + * @param vol_ctlr Volume Controller instance pointer. + * + * @return 0 if success, errno on failure. + */ + +int bt_vcp_vol_ctlr_vol_down(struct bt_vcp_vol_ctlr *vol_ctlr); + +/** + * @brief Turn the volume up one step on a remote Volume Renderer + * + * @param vol_ctlr Volume Controller instance pointer. + * + * @return 0 if success, errno on failure. + */ + +int bt_vcp_vol_ctlr_vol_up(struct bt_vcp_vol_ctlr *vol_ctlr); + +/** + * @brief Turn the volume down one step and unmute on a remote Volume + * Renderer + * + * @param vol_ctlr Volume Controller instance pointer. + * + * @return 0 if success, errno on failure. + */ + +int bt_vcp_vol_ctlr_unmute_vol_down(struct bt_vcp_vol_ctlr *vol_ctlr); + +/** + * @brief Turn the volume up one step and unmute on a remote Volume Renderer + * + * @param vol_ctlr Volume Controller instance pointer. + * + * @return 0 if success, errno on failure. + */ + +int bt_vcp_vol_ctlr_unmute_vol_up(struct bt_vcp_vol_ctlr *vol_ctlr); + +/** + * @brief Set the absolute volume on a remote Volume Renderer + * + * @param vol_ctlr Volume Controller instance pointer. + * @param volume The absolute volume to set. + * + * @return 0 if success, errno on failure. + */ + +int bt_vcp_vol_ctlr_set_vol(struct bt_vcp_vol_ctlr *vol_ctlr, + uint8_t volume); + +/** + * @brief Unmute a remote Volume Renderer. + * + * @param vol_ctlr Volume Controller instance pointer. + * + * @return 0 if success, errno on failure. + */ + +int bt_vcp_vol_ctlr_unmute(struct bt_vcp_vol_ctlr *vol_ctlr); + +/** + * @brief Mute a remote Volume Renderer. + * + * @param vol_ctlr Volume Controller instance pointer. + * + * @return 0 if success, errno on failure. + */ + +int bt_vcp_vol_ctlr_mute(struct bt_vcp_vol_ctlr *vol_ctlr); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_VCP_H */ diff --git a/include/nuttx/net/bluetooth/audio/vocs.h b/include/nuttx/net/bluetooth/audio/vocs.h new file mode 100644 index 0000000000000..9536f4c3eb5a6 --- /dev/null +++ b/include/nuttx/net/bluetooth/audio/vocs.h @@ -0,0 +1,437 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/audio/vocs.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Bluetooth Volume Offset Control Service (VOCS) APIs. + */ + +/* Copyright (c) 2020-2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_VOCS_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_VOCS_H + +/** + * @brief Volume Offset Control Service (VOCS) + * + * @defgroup bt_vocs Volume Offset Control Service (VOCS) + * + * @since 2.6 + * @version 0.8.0 + * + * @ingroup bluetooth + * @{ + * + * The Volume Offset Control Service is a secondary service, and as such + * should not be used own its own, but rather in the context of another + * (primary) service. + * + * This API implements both the server and client functionality. Note that + * the API abstracts away the change counter in the volume offset control + * state and will automatically handle any changes to that. If out of date, + * the client implementation will autonomously read the change counter value + * when executing a write request. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @name Volume Offset Control Service Error codes + * @{ + */ + +/* The Change_Counter operand value does not match the Change_Counter field + * value of the Volume Offset State characteristic. + */ +#define BT_VOCS_ERR_INVALID_COUNTER 0x80U +/** An invalid opcode has been used in a control point procedure. */ +#define BT_VOCS_ERR_OP_NOT_SUPPORTED 0x81U +/** An operand value used in a control point procedure is outside the + * permissible range. + */ +#define BT_VOCS_ERR_OUT_OF_RANGE 0x82U +/** @} */ + +/** + * @name Volume Offset Control Service offset limits + * @{ + */ + +/** Minimum offset value */ +#define BT_VOCS_MIN_OFFSET -255 +/** Maximum offset value */ +#define BT_VOCS_MAX_OFFSET 255 +/** @} */ + +/** + * @struct bt_vocs + * @brief Opaque Volume Offset Control Service instance. + */ + +struct bt_vocs; + +/** @brief Structure for registering a Volume Offset Control Service + * instance. + */ + +struct bt_vocs_register_param +{ + /** Audio Location bitmask */ + + uint32_t location; + + /** Boolean to set whether the location is writable by clients */ + + bool location_writable; + + /** Initial volume offset (-255 to 255) */ + + int16_t offset; + + /** Initial audio output description */ + + char *output_desc; + + /** Boolean to set whether the description is writable by clients */ + + bool desc_writable; + + /** Pointer to the callback structure. */ + + struct bt_vocs_cb *cb; +}; + +/** @brief Structure for discovering a Volume Offset Control Service + * instance. + */ + +struct bt_vocs_discover_param +{ + /* @brief The start handle of the discovering. + * + * Typically the @p start_handle of a @ref bt_gatt_include. + */ + + uint16_t start_handle; + /* @brief The end handle of the discovering. + * + * Typically the @p end_handle of a @ref bt_gatt_include. + */ + + uint16_t end_handle; +}; + +/** + * @brief Get a free service instance of Volume Offset Control Service from + * the pool. + * + * @return Volume Offset Control Service instance in case of success or NULL + * in case of error. + */ + +struct bt_vocs *bt_vocs_free_instance_get(void); + +/** + * @brief Get the service declaration attribute. + * + * The first service attribute returned can be included in any other GATT + * service. + * + * @param vocs Volume Offset Control Service instance. + * + * @return Pointer to the attributes of the service. + */ + +void *bt_vocs_svc_decl_get(struct bt_vocs *vocs); + +/** + * @brief Get the connection pointer of a client instance + * + * Get the Bluetooth connection pointer of a Volume Offset Control Service + * client instance. + * + * @param vocs Volume Offset Control Service client instance pointer. + * @param conn Connection pointer. + * + * @return 0 if success, errno on failure. + */ + +int bt_vocs_client_conn_get(const struct bt_vocs *vocs, + struct bt_conn **conn); + +/** + * @brief Register the Volume Offset Control Service instance. + * + * @param vocs Volume Offset Control Service instance. + * @param param Volume Offset Control Service register parameters. + * + * @return 0 if success, errno on failure. + */ + +int bt_vocs_register(struct bt_vocs *vocs, + const struct bt_vocs_register_param *param); + +/** + * @brief Callback function for the offset state. + * + * Called when the value is read, or if the value is changed by either the + * server or client. + * + * @param inst The instance pointer. + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. For notifications, this + * will always be 0. + * @param offset The offset value. + */ + +typedef void (*bt_vocs_state_cb)( + struct bt_vocs *inst, int err, int16_t offset); + +/** + * @brief Callback function for setting offset. + * + * @param inst The instance pointer. + * @param err Error value. 0 on success, GATT error on positive value + * or errno on negative value. + */ + +typedef void (*bt_vocs_set_offset_cb)(struct bt_vocs *inst, int err); + +/** + * @brief Callback function for the location. + * + * Called when the value is read, or if the value is changed by either the + * server or client. + * + * @param inst The instance pointer. + * @param err Error value. 0 on success, GATT error on positive + * value or errno on negative value. For notifications, + * this will always be 0. + * @param location The location value. + */ + +typedef void (*bt_vocs_location_cb)( + struct bt_vocs *inst, int err, uint32_t location); + +/** + * @brief Callback function for the description. + * + * Called when the value is read, or if the value is changed by either the + * server or client. + * + * @param inst The instance pointer. + * @param err Error value. 0 on success, GATT error on positive + * value or errno on negative value. For notifications, + * this will always be 0. + * @param description The description as an UTF-8 encoded string. + */ + +typedef void (*bt_vocs_description_cb)( + struct bt_vocs *inst, int err, char *description); + +/** + * @brief Callback function for bt_vocs_discover. + * + * This callback should be overwritten by the primary service that includes + * the Volume Control Offset Service client, and should thus not be set by + * the application. + * + * @param inst The instance pointer. + * @param err Error value. 0 on success, GATT error on positive + * value or errno on negative value. For notifications, + * this will always be 0. + */ + +typedef void (*bt_vocs_discover_cb)(struct bt_vocs *inst, int err); + +/** + * @brief Struct to hold the Volume Offset Control Service callbacks + * + * These can be registered for usage with bt_vocs_client_cb_register() or + * bt_vocs_register() depending on the role. + */ + +struct bt_vocs_cb +{ + /** The offset state has changed */ + + bt_vocs_state_cb state; + + /** The location has changed */ + + bt_vocs_location_cb location; + + /** The Description has changed */ + + bt_vocs_description_cb description; + +#if defined(CONFIG_BT_VOCS_CLIENT) || defined(__DOXYGEN__) +/* The discovery procedure has completed + * + * Only settable for the client and requires + * enabling@kconfig{CONFIG_BT_VOCS_CLIENT}. + */ + + bt_vocs_discover_cb discover; +/* The set offset procedure has completed + * + * Only settable for the client and requires + * enabling@kconfig{CONFIG_BT_VOCS_CLIENT}. + */ + + bt_vocs_set_offset_cb set_offset; +#endif /* CONFIG_BT_VOCS_CLIENT */ +}; + +/** + * @brief Read the Volume Offset Control Service offset state. + * + * The value is returned in the bt_vocs_cb.state callback. + * + * @param inst Pointer to the Volume Offset Control Service + * instance. + * + * @return 0 on success, GATT error value on fail. + */ + +int bt_vocs_state_get(struct bt_vocs *inst); + +/** + * @brief Set the Volume Offset Control Service offset state. + * + * @param inst Pointer to the Volume Offset Control Service + * instance. + * @param offset The offset to set (-255 to 255). + * + * @return 0 on success, GATT error value on fail. + */ + +int bt_vocs_state_set(struct bt_vocs *inst, int16_t offset); + +/** + * @brief Read the Volume Offset Control Service location. + * + * The value is returned in the bt_vocs_cb.location callback. + * + * @param inst Pointer to the Volume Offset Control Service + * instance. + * + * @return 0 on success, GATT error value on fail. + */ + +int bt_vocs_location_get(struct bt_vocs *inst); + +/** + * @brief Set the Volume Offset Control Service location. + * + * @param inst Pointer to the Volume Offset Control Service + * instance. + * @param location The location to set. + * + * @return 0 on success, GATT error value on fail. + */ + +int bt_vocs_location_set(struct bt_vocs *inst, uint32_t location); + +/** + * @brief Read the Volume Offset Control Service output description. + * + * The value is returned in the bt_vocs_cb.description callback. + * + * @param inst Pointer to the Volume Offset Control Service + * instance. + * + * @return 0 on success, GATT error value on fail. + */ + +int bt_vocs_description_get(struct bt_vocs *inst); + +/** + * @brief Set the Volume Offset Control Service description. + * + * @param inst Pointer to the Volume Offset Control Service + * instance. + * @param description The UTF-8 encoded string description to set. + * + * @return 0 on success, GATT error value on fail. + */ + +int bt_vocs_description_set(struct bt_vocs *inst, const char *description); + +/** + * @brief Registers the callbacks for the Volume Offset Control Service + * client. + * + * @param inst Pointer to the Volume Offset Control Service client instance. + * @param cb Pointer to the callback structure. + */ + +void bt_vocs_client_cb_register(struct bt_vocs *inst, struct bt_vocs_cb *cb); + +/** + * @brief Returns a pointer to a Volume Offset Control Service client + * instance. + * + * @return Pointer to the instance, or NULL if no free instances are left. + */ + +struct bt_vocs *bt_vocs_client_free_instance_get(void); + +/** + * @brief Free an instance received from bt_vocs_client_free_instance_get() + * + * @param inst The instance to free + * + * @retval 0 Success. + * @retval -EINVAL @p inst is NULL or not a client instance. + * @retval -EALREADY @p inst is already free + * @retval -EBUSY @p inst is busy. + */ + +int bt_vocs_client_free_instance(struct bt_vocs *inst); + +/** + * @brief Discover a Volume Offset Control Service. + * + * Attempts to discover a Volume Offset Control Service on a server given the + * @p param. + * + * @param conn Connection to the peer with the Volume Offset Control + * Service. + * @param inst Pointer to the Volume Offset Control Service client instance. + * @param param Pointer to the parameters. + * + * @return 0 on success, errno on fail. + */ + +int bt_vocs_discover(struct bt_conn *conn, struct bt_vocs *inst, + const struct bt_vocs_discover_param *param); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_AUDIO_VOCS_H */ diff --git a/include/nuttx/net/bluetooth/bluetooth.h b/include/nuttx/net/bluetooth/bluetooth.h new file mode 100644 index 0000000000000..cd067888a9ab1 --- /dev/null +++ b/include/nuttx/net/bluetooth/bluetooth.h @@ -0,0 +1,3565 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/bluetooth.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Bluetooth subsystem core APIs. + */ + +/* Copyright (c) 2017 Nordic Semiconductor ASA + * Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_BLUETOOTH_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_BLUETOOTH_H + +/** + * @brief Bluetooth APIs + * + * @details The Bluetooth Subsystem Core APIs provide essential + * functionalities to use and manage Bluetooth based communication. These + * APIs include APIs for Bluetooth stack initialization, device discovery, + * connection management, data transmission, profiles and services. These + * APIs support both classic Bluetooth and Bluetooth Low Energy (LE) + * operations. + * + * @defgroup bluetooth Bluetooth APIs + * @ingroup connectivity + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Generic Access Profile (GAP) + * + * @details The Generic Access Profile (GAP) defines fundamental Bluetooth + * operations, including device discovery, pairing, and connection + * management. Zephyr's GAP implementation supports both classic Bluetooth + * and Bluetooth Low Energy (LE) functionalities, enabling roles such as + * Broadcaster, Observer, Peripheral, and Central. These roles define the + * device's behavior in advertising, scanning, and establishing connections + * within Bluetooth networks. + * + * @defgroup bt_gap Generic Access Profile (GAP) + * @since 1.0 + * @version 1.1.1 + * @ingroup bluetooth + * @{ + */ + +/* Identity handle referring to the first identity address. This is a + * convenience macro for specifying the default identity address. This helps + * make the code more readable, especially when only one identity address is + * supported. + */ +#define BT_ID_DEFAULT 0 + +/** Size of an Identity Resolving Key in octets. */ +#define BT_IRK_SIZE 16U + +/** + * @brief Number of octets for local supported features + * + * The value of 8 correspond to page 0 in the LE Controller supported + * features. 24 bytes are required for all subsequent supported feature + * pages. + */ +#if defined(CONFIG_BT_LE_EXTENDED_FEAT_SET) +#define BT_LE_LOCAL_SUPPORTED_FEATURES_SIZE \ +(BT_HCI_LE_BYTES_PAGE_0_FEATURE_PAGE + \ + (CONFIG_BT_LE_MAX_LOCAL_SUPPORTED_FEATURE_PAGE * BT_HCI_LE_BYTES_PER_FEATURE_PAGE)) +#else +#define BT_LE_LOCAL_SUPPORTED_FEATURES_SIZE BT_HCI_LE_BYTES_PAGE_0_FEATURE_PAGE +#endif +/** + * @struct bt_le_ext_adv + * @brief Opaque type representing an advertiser. + */ + +struct bt_le_ext_adv; + +/** + * @struct bt_le_per_adv_sync + * @brief Opaque type representing a periodic advertising sync. + */ + +struct bt_le_per_adv_sync; + +/* Don't require everyone to include conn.h */ + +struct bt_conn; + +/* Don't require everyone to include iso.h */ + +struct bt_iso_biginfo; + +/* Don't require everyone to include direction.h */ + +struct bt_df_per_adv_sync_iq_samples_report; + +/** + * @brief Info of the advertising sent event. + * + * @note Used in @ref bt_le_ext_adv_cb. + */ + +struct bt_le_ext_adv_sent_info +{ +/* If the advertising set was started with a non-zero + * @ref bt_le_ext_adv_start_param.num_events, this field + * contains the number of times this advertising set has been sent since it + * was enabled. + */ + + uint8_t num_sent; +}; + +/** + * @brief Info of the advertising connected event. + * + * @note Used in @ref bt_le_ext_adv_cb. + */ + +struct bt_le_ext_adv_connected_info +{ + /** Connection object of the new connection */ + + struct bt_conn *conn; +}; + +/** + * @brief Info of the advertising scanned event. + * + * @note Used in @ref bt_le_ext_adv_cb. + */ + +struct bt_le_ext_adv_scanned_info +{ + /** Active scanner LE address and type */ + + bt_addr_le_t *addr; +}; + +/** + * @brief Info of the PAwR subevents. + * + * @details When the Controller indicates it is ready to transmit one or more + * PAwR subevents, + * @ref bt_le_per_adv_data_request holds the information about the first + * subevent data and the number of subevents data can be set for. + * + * @note Used in @ref bt_le_ext_adv_cb. + */ + +struct bt_le_per_adv_data_request +{ + /** The first subevent data can be set for */ + + uint8_t start; + + /** The number of subevents data can be set for */ + + uint8_t count; +}; + +/** + * @brief Info about the PAwR responses received. + * + * @details When the Controller indicates that one or more synced devices + * have responded to a periodic advertising subevent indication, @ref + * bt_le_per_adv_response_info holds the information about the subevent in + * question, its status, TX power, RSSI of the response, the Constant Tone + * Extension of the advertisement, and the slot the response was received in. + * + * @note Used in @ref bt_le_ext_adv_cb. + */ + +struct bt_le_per_adv_response_info +{ + /** The subevent the response was received in */ + + uint8_t subevent; + + /** @brief Status of the subevent indication. + * + * 0 if subevent indication was transmitted. 1 if subevent indication was + * not transmitted. All other values RFU. + */ + + uint8_t tx_status; + + /** The TX power of the response in dBm */ + + int8_t tx_power; + + /** The RSSI of the response in dBm */ + + int8_t rssi; + + /** The Constant Tone Extension (CTE) of the advertisement (@ref + * bt_df_cte_type) + */ + + uint8_t cte_type; + + /** The slot the response was received in */ + + uint8_t response_slot; +}; + +/** + * @brief Callback struct to notify about advertiser activity. + * + * @details The @ref bt_le_ext_adv_cb struct contains callback functions that + * are invoked in response to various events related to the advertising set. + * These events include: + * - Completion of advertising data transmission + * - Acceptance of a new connection + * - Transmission of scan response data + * - If privacy is enabled: + * - Expiration of the advertising set's validity + * - If PAwR (Periodic Advertising with Response) is enabled: + * - Readiness to send one or more PAwR subevents, namely the LE + * Periodic Advertising Subevent Data Request event + * - Response of synced devices to a periodic advertising subevent + * indication has been received, namely the LE Periodic Advertising Response + * Report event + * + * @note Must point to valid memory during the lifetime of the advertising + * set. + * + * @note Used in @ref bt_le_ext_adv_create. + * + * @note The callbacks are invoked from a thread context, never from an + * ISR. Whether a callback is invoked from a context internal to the stack + * or synchronously from within the API call that triggers it, and from + * which context, is not part of the API and may change between releases. + * See @rstref{Callback execution contexts } + * for the hazards of blocking in a callback and their mitigations. + */ + +struct bt_le_ext_adv_cb +{ + /* @brief The advertising set was disabled after reaching limit + * + * This callback is invoked when the limit set in + * @ref bt_le_ext_adv_start_param.timeout or + * @ref bt_le_ext_adv_start_param.num_events is reached. + * + * @param adv The advertising set object. + * @param info Information about the sent event. + */ + + void (*sent)(struct bt_le_ext_adv *adv, + struct bt_le_ext_adv_sent_info *info); + + /* @brief The advertising set has accepted a new connection. + * + * This callback notifies the application that the advertising set has + * accepted a new connection. + * + * @param adv The advertising set object. + * @param info Information about the connected event. + */ + + void (*connected)(struct bt_le_ext_adv *adv, + struct bt_le_ext_adv_connected_info *info); + + /* @brief The advertising set has sent scan response data. + * + * This callback notifies the application that the advertising set has has + * received a Scan Request packet, and has sent a Scan Response packet. + * + * @param adv The advertising set object. + * @param info Information about the scanned event, namely the address. + */ + + void (*scanned)(struct bt_le_ext_adv *adv, + struct bt_le_ext_adv_scanned_info *info); + +#if defined(CONFIG_BT_PRIVACY) || defined(__DOXYGEN__) + /* @brief The RPA validity of the advertising set has expired. + * + * This callback notifies the application that the RPA validity of the + * advertising set has expired. The user can use this callback to + * synchronize the advertising payload update with the RPA rotation by for + * example invoking @ref bt_le_ext_adv_set_data upon callback. + * + * If RPA sharing is enabled (see @kconfig{CONFIG_BT_RPA_SHARING}) and this + * RPA expired callback of any adv-sets belonging to same adv id returns + * false, then adv-sets will continue with the old RPA throughout the RPA + * rotations. + * + * @param adv The advertising set object. + * + * @return true to rotate the current RPA, or false to use it for the + * next rotation period. + */ + + bool (*rpa_expired)(struct bt_le_ext_adv *adv); +#endif /* defined(CONFIG_BT_PRIVACY) */ + +#if defined(CONFIG_BT_PER_ADV_RSP) || defined(__DOXYGEN__) + /* @brief The Controller indicates it is ready to transmit one or more PAwR + * subevents. + * + * This callback notifies the application that the controller has requested + * data for upcoming subevents. + * + * @param adv The advertising set object. + * @param request Information about the upcoming subevents. + * + * @kconfig_dep{CONFIG_BT_PER_ADV_RSP} + */ + + void (*pawr_data_request)(struct bt_le_ext_adv *adv, + const struct bt_le_per_adv_data_request + *request); + /* @brief The Controller indicates that one or more synced devices have + * responded to a periodic advertising subevent indication. + * + * A response may be fragmented across several controller reports. When + * @kconfig{CONFIG_BT_PER_ADV_RSP_REASSEMBLY} is enabled, the host + * reassembles the fragments and delivers the complete response in a single + * callback. If it is disabled, partial reports are discarded and only + * reports already marked as "complete" by the controller are forwarded to + * the application. + * + * @param adv The advertising set object. + * @param info Information about the responses received. + * @param buf The received data. NULL if the controller reported that it + * did not receive any response. + * + * @kconfig_dep{CONFIG_BT_PER_ADV_RSP} + */ + + void (*pawr_response)(struct bt_le_ext_adv *adv, + struct bt_le_per_adv_response_info *info, + struct net_buf_simple *buf); + +#endif /* defined(CONFIG_BT_PER_ADV_RSP) */ +}; + +/** + * @typedef bt_ready_cb_t + * @brief Callback for notifying that Bluetooth has been enabled. + * + * @param err zero on success or (negative) error code otherwise. + */ + +typedef void (*bt_ready_cb_t)(int err); + +/** + * @brief Enable Bluetooth + * + * Enable Bluetooth. Must be the called before any calls that require + * communication with the local Bluetooth hardware. + * + * When @kconfig{CONFIG_BT_SETTINGS} is enabled, the application must load + * the Bluetooth settings after this API call successfully completes before + * Bluetooth APIs can be used. Loading the settings before calling this + * function is insufficient. Bluetooth settings can be loaded with @ref + * settings_load or + * @ref settings_load_subtree with argument "bt". The latter selectively + * loads only Bluetooth settings and is recommended if @ref settings_load has + * been called earlier. + * + * @param cb Callback to notify completion or NULL to perform the enabling + * synchronously. The callback is called from a thread context + * internal to the stack, never from an ISR; see @rstref{Callback + * execution contexts }. + * + * @return 0 on success, negative errno value on failure. + * @retval -EALREADY Bluetooth is already enabled, or being enabled. + * @retval -EAGAIN Bluetooth is being disabled; retry once bt_disable() has + * returned. + * @retval -ENODEV The HCI driver is not ready. + */ + +int bt_enable(bt_ready_cb_t cb); + +/** + * @brief Disable Bluetooth + * + * Disable Bluetooth. Can't be called before bt_enable has completed. + * + * When bt_enable() was called with a ready callback the initialization runs + * asynchronously. If bt_disable() is called before the ready callback fires, + * it returns -EAGAIN. The caller should retry after the ready callback. + * + * This API will clear all configured identity addresses and keys that are + * not persistently stored with @kconfig{CONFIG_BT_SETTINGS}. These can be + * restored with @ref settings_load before reenabling the stack. + * + * This API does _not_ clear previously registered callbacks like @ref + * bt_le_scan_cb_register, @ref bt_conn_cb_register AND @ref + * bt_br_discovery_cb_register. That is, the application shall not + * re-register them when the Bluetooth subsystem is re-enabled later. + * + * Close and release HCI resources. Result is architecture dependent. + * + * If the HCI driver fails to close its transport, the error is returned with + * Bluetooth left not ready, as the Host has torn down its state, connections + * included, by then. bt_disable() can be called again. + * + * @return 0 on success, negative errno value on failure. + * @retval -ENOSYS The HCI driver does not support closing its transport. + * Bluetooth is left as it was. + * @retval -EALREADY Bluetooth is already disabled, or being disabled. + * @retval -EAGAIN Bluetooth is still being enabled, which with + * @kconfig{CONFIG_BT_SETTINGS} includes loading the Bluetooth settings; + * retry once bt_is_ready() returns true. + */ + +int bt_disable(void); + +/** + * @brief Check if Bluetooth is ready + * + * @return true when Bluetooth is ready, false otherwise + */ + +bool bt_is_ready(void); + +/** + * @brief Set Bluetooth Device Name + * + * Set Bluetooth GAP Device Name. + * + * @note The advertising data is not automatically updated. When advertising + * with device name in the advertising data, the name should be updated by + * calling @ref bt_le_adv_update_data or + * @ref bt_le_ext_adv_set_data after the call to this function. + * + * @kconfig_dep{CONFIG_BT_DEVICE_NAME_DYNAMIC} + * + * @sa @kconfig{CONFIG_BT_DEVICE_NAME_MAX}. + * + * @param name New name, must be null terminated + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_set_name(const char *name); + +/** + * @brief Get Bluetooth Device Name + * + * Get Bluetooth GAP Device Name. + * + * @return Bluetooth Device Name + */ + +const char *bt_get_name(void); + +/** + * @brief Get local Bluetooth appearance + * + * Bluetooth Appearance is a description of the external appearance of a + * device in terms of an Appearance Value. + * + * @see Section 2.6 of the Bluetooth SIG Assigned Numbers document. + * + * @returns Appearance Value of local Bluetooth host. + */ + +uint16_t bt_get_appearance(void); + +/** + * @brief Set local Bluetooth appearance + * + * Automatically preserves the new appearance across reboots if + * @kconfig{CONFIG_BT_SETTINGS} is enabled. + * + * @kconfig_dep{CONFIG_BT_DEVICE_APPEARANCE_DYNAMIC} + * + * @param new_appearance Appearance Value + * + * @retval 0 Success. + * @retval other Persistent storage failed. Appearance was not updated. + */ + +int bt_set_appearance(uint16_t new_appearance); + +/** + * @brief Get the currently configured identity addresses. + * + * Returns an array of the currently configured identity addresses. To make + * sure all available identity addresses can be retrieved, the number of + * elements in the @a addrs array should be @kconfig{CONFIG_BT_ID_MAX}. The + * identity handle that some APIs expect (such as @ref bt_le_adv_param) is + * simply the index of the identity address in the @a addrs array. + * + * If @a addrs is passed as NULL, then the returned @a count contains the + * count of all available identity addresses that can be retrieved with a + * subsequent call to this function with non-NULL @a addrs parameter. + * + * @note Deleted identity addresses may show up as @ref BT_ADDR_LE_ANY in the + * returned array. + * + * @param addrs Array where to store the configured identity addresses. + * @param count Should be initialized to the array size. Once the function + * returns it will contain the number of returned identity + * addresses. + */ + +void bt_id_get(bt_addr_le_t *addrs, size_t *count); + +/** + * @brief Create a new identity address. + * + * Create a new identity address using the given address and IRK. This + * function can be called before calling @ref bt_enable. However, the new + * identity address will only be stored persistently in flash when this API + * is used after @ref bt_enable. The reason is that the persistent settings + * are loaded after @ref bt_enable and would therefore cause potential + * conflicts with the stack blindly overwriting what's stored in flash. The + * identity address will also not be written to flash in case a pre-defined + * address is provided, since in such a situation the app clearly has some + * place it got the address from and will be able to repeat the procedure on + * every power cycle, i.e. it would be redundant to also store the + * information in flash. + * + * Generating random static address or random IRK is not supported when + * calling this function before @ref bt_enable. + * + * If the application wants to have the stack randomly generate identity + * addresses and store them in flash for later recovery, the way to do it + * would be to first initialize the stack (using bt_enable), then call @ref + * settings_load, and after that check with @ref bt_id_get how many identity + * addresses were recovered. If an insufficient amount of identity addresses + * were recovered the app may then call this function to create new ones. + * + * @note If @kconfig{CONFIG_BT_HCI_SET_PUBLIC_ADDR} is enabled, the first + * call can set a public address as the controller's identity, but only + * before @ref bt_enable and if no other identities exist. + * + * @param addr Address to use for the new identity address. If NULL or + * initialized to BT_ADDR_LE_ANY the stack will generate a new + * random static address for the identity address and copy it to + * the given parameter upon return from this function (in case + * the parameter was non-NULL). + * @param irk Identity Resolving Key (@ref BT_IRK_SIZE octets) to be used + * with this identity address. If set to all zeroes or NULL, the + * stack will generate a random IRK for the identity address and + * copy it back to the parameter upon return from this function + * (in case the parameter was non-NULL). If privacy + * @kconfig{CONFIG_BT_PRIVACY} is not enabled this parameter must + * be NULL. + * + * @return Identity handle (>= 0) in case of success, or a negative error + * code on failure. + */ + +int bt_id_create(bt_addr_le_t *addr, uint8_t *irk); + +/** + * @brief Reset/reclaim an identity address for reuse. + * + * When given an existing identity handle, this function will disconnect any + * connections (to the corresponding identity address) created using it, + * remove any pairing keys or other data associated with it, and then create + * a new identity address in the same slot, based on the @a addr and @a irk + * parameters. + * + * @note The default identity address (corresponding to @ref BT_ID_DEFAULT) + * cannot be reset, and this API will return an error if asked to do that. + * + * @param id Existing identity handle. + * @param addr Address to use for the new identity address. If NULL or + * initialized to BT_ADDR_LE_ANY the stack will generate a new + * static random address for the identity address and copy it to + * the given parameter upon return from this function. + * @param irk Identity Resolving Key (@ref BT_IRK_SIZE octets) to be used + * with this identity address. If set to all zeroes or NULL, the + * stack will generate a random IRK for the identity address and + * copy it back to the parameter upon return from this function + * (in case the parameter was non-NULL). If privacy + * @kconfig{CONFIG_BT_PRIVACY} is not enabled this parameter must + * be NULL. + * + * @return Identity handle (>= 0) in case of success, or a negative error + * code on failure. + */ + +int bt_id_reset(uint8_t id, bt_addr_le_t *addr, uint8_t *irk); + +/** + * @brief Delete an identity address. + * + * When given a valid identity handle this function will disconnect any + * connections (to the corresponding identity address) created using it, + * remove any pairing keys or other data associated with it, and then flag is + * as deleted, so that it can not be used for any operations. To take back + * into use the slot the identity address was occupying, the @ref bt_id_reset + * API needs to be used. + * + * @note The default identity address (corresponding to @ref BT_ID_DEFAULT) + * cannot be deleted, and this API will return an error if asked to do that. + * + * @param id Existing identity handle. + * + * @return 0 in case of success, or a negative error code on failure. + */ + +int bt_id_delete(uint8_t id); + +/** + * @brief Reset the local Identity Resolving Key (IRK) for an identity. + * + * Replaces the IRK for the given identity and persists it to flash when + * @kconfig{CONFIG_BT_SETTINGS} is enabled. Existing bonds must be removed + * before calling this function because bonded peers retain the old IRK and + * cannot resolve RPAs generated with the new one. Without + * @kconfig{CONFIG_BT_SETTINGS}, the new IRK is lost on reboot. + * + * @note Unlike @ref bt_id_reset, this function also accepts + * @ref BT_ID_DEFAULT because it does not modify the identity address. + * + * @kconfig_dep{CONFIG_BT_PRIVACY}. + * + * @note The Bluetooth stack must be enabled via @ref bt_enable before + * calling this function. + * + * @note With extended advertising, an advertising set assigned to this + * identity must be deleted before calling this function; stopping it is not + * enough. + * + * @param id Identity index, as returned by @ref bt_id_get or @ref + * BT_ID_DEFAULT. Must be less than the number of + * currently configured identities. + * @param[in,out] irk Identity Resolving Key (@ref BT_IRK_SIZE octets) to use + * for the identity. If NULL or all zeroes, the stack + * generates a new random IRK and copies it back to this + * buffer when non-NULL. + * + * @retval 0 Success. + * @retval -EAGAIN Bluetooth stack is not ready. + * @retval -EINVAL @p id is out of range. + * @retval -EALREADY The identity slot is empty. + * @retval -EBUSY An advertising set is active, the identity is used by + * scanning or initiating, or a created extended advertising set is assigned + * to the identity. + * @retval -ENOTEMPTY Bonds exist for this identity; call @ref bt_unpair + * first. + * @retval -EIO Random IRK generation failed. + * @retval -ENOTSUP Random generation is unsupported. + */ + +int bt_id_reset_irk(uint8_t id, uint8_t *irk); + +/** + * @brief Local Bluetooth LE controller features and capabilities. + * + * @details This struct provides details about the Bluetooth LE controller's + * supported features, states, and various other capabilities. It includes + * information on ACL and ISO data packet lengths, the controller's resolving + * list size, and the maximum advertising data length. This information can + * be obtained after enabling the Bluetooth stack with @ref bt_enable + * function. + * + * Refer to the Bluetooth Core Specification, Volume 6, Part B and Volume 4, + * Part E for detailed sections about each field's significance and values. + */ + +struct bt_le_local_features +{ + /* @brief Local LE controller supported features. + * + * Refer to BT_LE_FEAT_BIT_* for values. Refer to the BT_FEAT_LE_* macros + * for value comparionson. See Bluetooth Core Specification, Vol 6, Part B, + * Section 4.6. + */ + + uint8_t features[BT_LE_LOCAL_SUPPORTED_FEATURES_SIZE]; + + /* @brief Local LE controller supported states + * + * Refer to BT_LE_STATES_* for values. See Bluetooth Core Specification + * 6.0, Vol 4, Part E, Section 7.8.27 + */ + + uint64_t states; + + /* @brief ACL data packet length + * + * This represents the maximum ACL HCI Data packet which can be sent from + * the Host to the Controller. The Host may support L2CAP and ATT MTUs + * larger than this value. See Bluetooth Core Specification, Vol 6, Part E, + * Section 7.8.2. + */ + + uint16_t acl_mtu; + + /** Total number of ACL data packets */ + + uint8_t acl_pkts; + + /* @brief ISO data packet length + * + * This represents the maximum ISO HCI Data packet which can be sent from + * the Host to the Controller. ISO SDUs above this size can be fragmented + * assuming that the number of + * @ref bt_le_local_features.iso_pkts support the maximum size. + */ + + uint16_t iso_mtu; + + /** Total number of ISO data packets */ + + uint8_t iso_pkts; + + /* @brief Maximum size of the controller resolving list. + * + * See Bluetooth Core Specification, Vol 6, Part E, Section 7.8.41. + */ + + uint8_t rl_size; + + /* @brief Maximum advertising data length + * + * @note The maximum advertising data length also depends on advertising + * type. + * + * See Bluetooth Core Specification, Vol 6, Part E, Section 7.8.57. + */ + + uint16_t max_adv_data_len; +}; + +/** + * @brief Get local Bluetooth LE controller features + * + * Can only be called after @ref bt_enable. + * + * @param local_features Local features struct to be populated with + * information. + * + * @retval 0 Success + * @retval -EAGAIN The information is not yet available. + * @retval -EINVAL @p local_features is NULL. + */ + +int bt_le_get_local_features(struct bt_le_local_features *local_features); + +/** Advertising options */ + +enum bt_le_adv_opt +{ + /** Convenience value when no options are specified. */ + + BT_LE_ADV_OPT_NONE = 0, + + /* @brief Connectable advertising + * + * Starting connectable advertising preallocates a connection object. If + * this fails, the API returns @c -ENOMEM. Stopping connectable advertising + * will free the connection object, and will trigger a call to @ref + * bt_conn_cb.recycled. + * + * The advertising set stops immediately after it creates a connection. + * This happens automatically in the controller. + * + * @note To continue advertising after a connection is created, + * the application should listen for the @ref bt_conn_cb.connected event + * and start the advertising set again. Note that the advertiser cannot be + * started when all connection objects are in use. In that case, defer + * starting the advertiser until + * @ref bt_conn_cb.recycled. To continue after a disconnection, + * listen for @ref bt_conn_cb.recycled. + * + */ + + BT_LE_ADV_OPT_CONN = BIT(0) | BIT(1), + + /* @brief Advertise using identity address. + * + * Advertise using the identity address as the advertiser address. @warning + * This will compromise the privacy of the device, so care must be taken + * when using this option. + * @note The address used for advertising will not be the same as + * returned by @ref bt_le_oob_get_local, instead @ref bt_id_get should be + * used to get the LE address. + */ + + BT_LE_ADV_OPT_USE_IDENTITY = BIT(2), + + /* @brief Low duty cycle directed advertising. + * + * Use low duty directed advertising mode, otherwise high duty mode will be + * used. + */ + + BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY = BIT(4), + + /* @brief Directed advertising to privacy-enabled peer. + * + * Enable use of Resolvable Private Address (RPA) as the target address in + * directed advertisements. This is required if the remote device is + * privacy-enabled and supports address resolution of the target address in + * directed advertisement. When + * @kconfig{CONFIG_BT_GATT_AUTO_READ_CENTRAL_ADDR_RES} is enabled the host + * reads the peer's Central Address Resolution characteristic when a bond + * is created, advertising towards a peer known not to support address + * resolution fails with -ENOTSUP, and the answer can be queried with @ref + * bt_le_bond_addr_res_support. When the support is not known, it is the + * responsibility of the application to check it by reading that + * characteristic. + */ + + BT_LE_ADV_OPT_DIR_ADDR_RPA = BIT(5), + + /** Use filter accept list to filter devices that can request scan response + * data. + */ + + BT_LE_ADV_OPT_FILTER_SCAN_REQ = BIT(6), + + /** Use filter accept list to filter devices that can connect. */ + + BT_LE_ADV_OPT_FILTER_CONN = BIT(7), + + /** Notify the application when a scan response data has been sent to an + * active scanner. + */ + + BT_LE_ADV_OPT_NOTIFY_SCAN_REQ = BIT(8), + + /* @brief Support scan response data. + * + * When used together with @ref BT_LE_ADV_OPT_EXT_ADV then this option + * cannot be used together with the @ref BT_LE_ADV_OPT_CONN option. When + * used together with @ref BT_LE_ADV_OPT_EXT_ADV then scan response data + * must be set. + */ + + BT_LE_ADV_OPT_SCANNABLE = BIT(9), + + /* @brief Advertise with extended advertising. + * + * This options enables extended advertising in the advertising set. In + * extended advertising the advertising set will send a small header packet + * on the three primary advertising channels. This small header points to + * the advertising data packet that will be sent on one of the 37 secondary + * advertising channels. The advertiser will send primary advertising on LE + * 1M PHY, and secondary advertising on LE 2M PHY. Connections will be + * established on LE 2M PHY. + * + * Without this option the advertiser will send advertising data on the + * three primary advertising channels. + * + * @note Enabling this option requires extended advertising support in + * the peer devices scanning for advertisement packets. + * + * @note This cannot be used with @ref bt_le_adv_start. + */ + + BT_LE_ADV_OPT_EXT_ADV = BIT(10), + + /* @brief Disable use of LE 2M PHY on the secondary advertising + * channel. + * + * Disabling the use of LE 2M PHY could be necessary if scanners don't + * support the LE 2M PHY. The advertiser will send primary advertising on + * LE 1M PHY, and secondary advertising on LE 1M PHY. Connections will be + * established on LE 1M PHY. + * + * @note Cannot be set if BT_LE_ADV_OPT_CODED is set. + * + * @note Requires @ref BT_LE_ADV_OPT_EXT_ADV bit (see @ref bt_le_adv_opt + * field) to be set as @ref bt_le_adv_param.options. + */ + + BT_LE_ADV_OPT_NO_2M = BIT(11), + + /* @brief Advertise on the LE Coded PHY (Long Range). + * + * The advertiser will send both primary and secondary advertising on the + * LE Coded PHY. This gives the advertiser increased range with the + * trade-off of lower data rate and higher power consumption. Connections + * will be established on LE Coded PHY. + * + * @note Requires @ref BT_LE_ADV_OPT_EXT_ADV bit (see @ref bt_le_adv_opt + * field) to be set as @ref bt_le_adv_param.options. + */ + + BT_LE_ADV_OPT_CODED = BIT(12), + + /* @brief Advertise without a device address (identity address or RPA). + * + * @note Requires @ref BT_LE_ADV_OPT_EXT_ADV bit (see @ref bt_le_adv_opt + * field) to be set as @ref bt_le_adv_param.options. + */ + + BT_LE_ADV_OPT_ANONYMOUS = BIT(13), + + /* @brief Advertise with transmit power. + * + * @note Requires @ref BT_LE_ADV_OPT_EXT_ADV bit (see @ref bt_le_adv_opt + * field) to be set as @ref bt_le_adv_param.options. + */ + + BT_LE_ADV_OPT_USE_TX_POWER = BIT(14), + + /** Disable advertising on channel index 37. */ + + BT_LE_ADV_OPT_DISABLE_CHAN_37 = BIT(15), + + /** Disable advertising on channel index 38. */ + + BT_LE_ADV_OPT_DISABLE_CHAN_38 = BIT(16), + + /** Disable advertising on channel index 39. */ + + BT_LE_ADV_OPT_DISABLE_CHAN_39 = BIT(17), + + /* @brief Advertise using a Non-Resolvable Private Address. + * + * A new NRPA is set when updating the advertising parameters. + * + * This is an advanced feature; most users will want to enable + * @kconfig{CONFIG_BT_EXT_ADV} instead. + * + * @note Mutually exclusive with BT_LE_ADV_OPT_USE_IDENTITY. + */ + + BT_LE_ADV_OPT_USE_NRPA = BIT(19), + + /* @brief Configures the advertiser to use the S=2 coding scheme for + * LE Coded PHY. + * + * Sets the advertiser's required coding scheme to S=2, which is one of the + * coding options available for LE Coded PHY. The S=2 coding scheme offers + * higher data rates compared to S=8, with a trade-off of reduced range. + * The coding scheme will only be set if both the primary and secondary + * advertising channels indicate LE Coded Phy. Additionally, the Controller + * must support the LE Feature Advertising Coding Selection. If these + * conditions are not met, it will default to no required coding scheme. + * + * @kconfig_dep{CONFIG_BT_EXT_ADV_CODING_SELECTION} + */ + + BT_LE_ADV_OPT_REQUIRE_S2_CODING = BIT(20), + + /* @brief Configures the advertiser to use the S=8 coding scheme for + * LE Coded PHY. + * + * Sets the advertiser's required coding scheme to S=8, which is one of the + * coding options available for LE Coded PHY. The S=8 coding scheme offers + * increased range compared to S=2, with a trade-off of lower data rates. + * The coding scheme will only be set if both the primary and secondary + * advertising channels indicate LE Coded Phy. Additionally, the Controller + * must support the LE Feature Advertising Coding Selection. If these + * conditions are not met, it will default to no required coding scheme. + * + * @kconfig_dep{CONFIG_BT_EXT_ADV_CODING_SELECTION} + */ + + BT_LE_ADV_OPT_REQUIRE_S8_CODING = BIT(21), + + /* @brief Request a specific TX power level for the advertising set. + * + * When set, the @ref bt_le_adv_param.tx_power field is passed to the + * controller as the desired TX power. The controller selects the closest + * supported level and reports the actual value via + * @ref bt_le_ext_adv_info.tx_power. + * + * When not set, the controller chooses the TX power freely (equivalent to + * @ref BT_HCI_LE_ADV_TX_POWER_NO_PREF). + */ + + BT_LE_ADV_OPT_TX_POWER = BIT(22), +}; + +/** LE Advertising Parameters. */ + +struct bt_le_adv_param +{ + /* @brief Local identity handle. + * + * The index of the identity address in the local Bluetooth controller. + * + * @note When extended advertising @kconfig{CONFIG_BT_EXT_ADV} is not + * enabled or not supported by the controller it is not possible to scan + * and advertise simultaneously using two different random addresses. + */ + + uint8_t id; + + /* @brief Advertising Set Identifier, valid range is @ref BT_GAP_SID_MIN to + * @ref BT_GAP_SID_MAX. + * + * @note If @ref BT_LE_ADV_OPT_EXT_ADV is not set in the options, this + * field is ignored. + */ + + uint8_t sid; + + /* @brief Secondary channel maximum skip count. + * + * Maximum advertising events the advertiser can skip before it must send + * advertising data on the secondary advertising channel. + * + * @note Requires @ref BT_LE_ADV_OPT_EXT_ADV bit (see @ref bt_le_adv_opt + * field) to be set as @ref bt_le_adv_param.options. + */ + + uint8_t secondary_max_skip; + + /* @brief Requested TX power in dBm. + * + * Only used when @ref BT_LE_ADV_OPT_TX_POWER is set in @ref options. Valid + * range is @ref BT_HCI_LE_ADV_TX_POWER_MIN to + * @ref BT_HCI_LE_ADV_TX_POWER_MAX. + * The controller selects the closest supported level and reports the + * actual value via @ref bt_le_ext_adv_info.tx_power. + */ + + int8_t tx_power; + + /** @brief Bit-field of advertising options, see the @ref bt_le_adv_opt + * field. + */ + + uint32_t options; + + /* @brief Minimum Advertising Interval (N * 0.625 milliseconds) + * + * @details The Minimum Advertising Interval shall be less than or equal to + * the Maximum Advertising Interval. The Minimum Advertising Interval and + * Maximum Advertising Interval aren't recommended to be the same value to + * enable the Controller to determine the best advertising interval given + * other activities. (See Bluetooth Core Spec 6.2, Vol 4, Part E, section + * 7.8.5) Range for legacy advertising: 0x0020 to 0x4000 (See Bluetooth + * Core Spec 6.2, Vol 4, Part E, section 7.8.53) Range for extended + * advertising: 0x0020 to 0xFFFFFF + */ + + uint32_t interval_min; + + /* @brief Maximum Advertising Interval (N * 0.625 milliseconds) + * + * @details The Maximum Advertising Interval shall be more than or equal to + * the Minimum Advertising Interval. The Minimum Advertising Interval and + * Maximum Advertising Interval aren't recommended to be the same value to + * enable the Controller to determine the best advertising interval given + * other activities. (See Bluetooth Core Spec 6.2, Vol 4, Part E, section + * 7.8.5) Range for legacy advertising: 0x0020 to 0x4000 (See Bluetooth + * Core Spec 6.2, Vol 4, Part E, section 7.8.53) Range for extended + * advertising: 0x0020 to 0xFFFFFF + */ + + uint32_t interval_max; + + /* @brief Directed advertising to peer + * + * When this parameter is set the advertiser will send directed advertising + * to the remote device. + * + * The advertising type will either be high duty cycle, or low duty cycle + * if the @ref BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY option is enabled. When + * using @ref BT_LE_ADV_OPT_EXT_ADV then only low duty cycle is allowed. + * + * In case of connectable high duty cycle if the connection could not be + * established within the timeout the connected callback will be called + * with the status set to @ref BT_HCI_ERR_ADV_TIMEOUT. + */ + + const bt_addr_le_t *peer; +}; + +/** + * @brief Set the TX power in an advertising parameter struct. + * + * Convenience helper that sets @ref bt_le_adv_param.tx_power and enables + * @ref BT_LE_ADV_OPT_TX_POWER in @ref bt_le_adv_param.options in a single + * call. + * + * @param param Advertising parameters to update. + * @param tx_power Requested TX power in dBm. Valid range is + * @ref BT_HCI_LE_ADV_TX_POWER_MIN to + * @ref BT_HCI_LE_ADV_TX_POWER_MAX. + */ + +static inline void bt_le_adv_param_set_tx_power( + struct bt_le_adv_param *param, int8_t tx_power) +{ + param->options |= BT_LE_ADV_OPT_TX_POWER; + param->tx_power = tx_power; +} + +/** Periodic Advertising options */ + +enum bt_le_per_adv_opt +{ + /** Convenience value when no options are specified. */ + + BT_LE_PER_ADV_OPT_NONE = 0, + + /* @brief Advertise with transmit power. + * + * @note Requires @ref BT_LE_ADV_OPT_EXT_ADV bit (see @ref bt_le_adv_opt + * field) to be set as @ref bt_le_adv_param.options. + */ + + BT_LE_PER_ADV_OPT_USE_TX_POWER = BIT(1), + + /* @brief Advertise with included AdvDataInfo (ADI). + * + * @note Requires @ref BT_LE_ADV_OPT_EXT_ADV bit (see @ref bt_le_adv_opt + * field) to be set as @ref bt_le_adv_param.options. + */ + + BT_LE_PER_ADV_OPT_INCLUDE_ADI = BIT(2), +}; + +/** + * @brief Parameters for configuring periodic advertising. + * + * @details This struct is used to configure the parameters for periodic + * advertising, including the minimum and maximum advertising intervals, + * options, and settings for subevents if periodic advertising responses are + * supported. The intervals are specified in units of 1.25 ms, and the + * options field can be used to modify other advertising behaviors. For + * extended advertisers, the periodic advertising parameters can be set or + * updated using this structure. Some parameters are conditional based on + * whether the device supports periodic advertising responses (configured via + * @kconfig{CONFIG_BT_PER_ADV_RSP}). + * + * @note Used in @ref bt_le_per_adv_set_param function. + */ + +struct bt_le_per_adv_param +{ + /* @brief Minimum Periodic Advertising Interval (N * 1.25 ms) + * + * Shall be greater or equal to BT_GAP_PER_ADV_MIN_INTERVAL and less or + * equal to interval_max. + */ + + uint16_t interval_min; + + /* @brief Maximum Periodic Advertising Interval (N * 1.25 ms) + * + * Shall be less or equal to BT_GAP_PER_ADV_MAX_INTERVAL and greater or + * equal to interval_min. + */ + + uint16_t interval_max; + + /** Bit-field of periodic advertising options, see the @ref + * bt_le_per_adv_opt field. + */ + + uint32_t options; + +#if defined(CONFIG_BT_PER_ADV_RSP) || defined(__DOXYGEN__) + /* @brief Number of subevents + * + * If zero, the periodic advertiser will be a broadcaster, without + * responses. + * + * @kconfig_dep{CONFIG_BT_PER_ADV_RSP} + */ + + uint8_t num_subevents; + + /* @brief Interval between subevents (N * 1.25 ms) + * + * Shall be between 7.5ms and 318.75 ms. + * + * @kconfig_dep{CONFIG_BT_PER_ADV_RSP} + */ + + uint8_t subevent_interval; + + /* @brief Time between the advertising packet in a subevent and the + * first response slot (N * 1.25 ms) + * + * @kconfig_dep{CONFIG_BT_PER_ADV_RSP} + */ + + uint8_t response_slot_delay; + + /* @brief Time between response slots (N * 0.125 ms) + * + * Shall be between 0.25 and 31.875 ms. + * + * @kconfig_dep{CONFIG_BT_PER_ADV_RSP} + */ + + uint8_t response_slot_spacing; + + /* @brief Number of subevent response slots + * + * If zero, response_slot_delay and response_slot_spacing are ignored. + * + * @kconfig_dep{CONFIG_BT_PER_ADV_RSP} + */ + + uint8_t num_response_slots; +#endif /* CONFIG_BT_PER_ADV_RSP */ +}; + +/** + * @brief Initialize advertising parameters + * + * @param _options Advertising Options + * @param _int_min Minimum advertising interval + * @param _int_max Maximum advertising interval + * @param _peer Peer address, set to NULL for undirected advertising or + * address of peer for directed advertising. + */ +#define BT_LE_ADV_PARAM_INIT(_options, _int_min, _int_max, _peer) \ +{ \ + .id = BT_ID_DEFAULT, \ + .sid = 0, \ + .secondary_max_skip = 0, \ + .options = (_options), \ + .interval_min = (_int_min), \ + .interval_max = (_int_max), \ + .peer = (_peer), \ +} + +/** + * @brief Helper to declare advertising parameters inline + * + * @param _options Advertising Options + * @param _int_min Minimum advertising interval + * @param _int_max Maximum advertising interval + * @param _peer Peer address, set to NULL for undirected advertising or + * address of peer for directed advertising. + */ +#define BT_LE_ADV_PARAM(_options, _int_min, _int_max, _peer) \ +((const struct bt_le_adv_param[]){BT_LE_ADV_PARAM_INIT(_options, _int_min, _int_max, _peer)}) + +#define BT_LE_ADV_CONN_DIR(_peer) BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONN, 0, 0, _peer) + +/** + * @brief GAP recommended connectable advertising parameters user-initiated + * + * @details This define sets the recommended default for when an application + * is likely waiting for the device to be connected or discovered. + * + * GAP recommends advertisers use the advertising parameters set by @ref + * BT_LE_ADV_CONN_FAST_1 for user-initiated advertisements. This might mean + * any time a user interacts with a device, a press on a dedicated Bluetooth + * wakeup button, or anything in-between. Interpretation is left to the + * application developer. + * + * Following modes are considered in these parameter settings: + * - Undirected Connectable Mode + * - Limited Discoverable Mode and sending connectable undirected advertising + * events + * - General Discoverable Mode and sending connectable undirected advertising + * events + * - Directed Connectable Mode and sending low duty cycle directed + * advertising events + * + * @note These parameters are merely a recommendation. For example the + * application might use a longer interval to conserve battery, which would + * be at the cost of responsiveness and it should be considered to enter a + * lower power state with longer intervals only after a timeout. + * + * @note This is the recommended setting for limited discoverable mode. + * + * See Bluetooth Core Specification: + * - 6.0 Vol 3, Part C, Appendix A "Timers and Constants", + * T_GAP(adv_fast_interval1) + * - 6.0 Vol 3, Part C, Section 9.3.11 "Connection Establishment Timing + * parameters" + */ + +#define BT_LE_ADV_CONN_FAST_1 \ +BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONN, BT_GAP_ADV_FAST_INT_MIN_1, BT_GAP_ADV_FAST_INT_MAX_1, NULL) + +/** + * @brief GAP recommended connectable advertising parameters non-connectable + * advertising events + * + * @details This define sets the recommended default for user-initiated + * advertisements or sending non-connectable advertising events. + * + * Following modes are considered in these parameter settings: + * - Non-Discoverable Mode + * - Non-Connectable Mode + * - Limited Discoverable Mode + * - General Discoverable Mode + * + * See Bluetooth Core Specification: + * - 6.0 Vol 3, Part C, Appendix A "Timers and Constants", + * T_GAP(adv_fast_interval2) + * - 6.0 Vol 3, Part C, Section 9.3.11 "Connection Establishment Timing + * parameters" + */ +#define BT_LE_ADV_CONN_FAST_2 \ +BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONN, BT_GAP_ADV_FAST_INT_MIN_2, BT_GAP_ADV_FAST_INT_MAX_2, NULL) + +#define BT_LE_ADV_CONN_DIR_LOW_DUTY(_peer) \ +BT_LE_ADV_PARAM(BT_LE_ADV_OPT_CONN | BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY, BT_GAP_ADV_FAST_INT_MIN_2, \ + BT_GAP_ADV_FAST_INT_MAX_2, _peer) + +/** Non-connectable advertising with private address */ +#define BT_LE_ADV_NCONN \ +BT_LE_ADV_PARAM(0, BT_GAP_ADV_FAST_INT_MIN_2, BT_GAP_ADV_FAST_INT_MAX_2, NULL) + +/** Non-connectable advertising with @ref BT_LE_ADV_OPT_USE_IDENTITY */ +#define BT_LE_ADV_NCONN_IDENTITY \ +BT_LE_ADV_PARAM(BT_LE_ADV_OPT_USE_IDENTITY, BT_GAP_ADV_FAST_INT_MIN_2, \ + BT_GAP_ADV_FAST_INT_MAX_2, NULL) + +/** Connectable extended advertising */ +#define BT_LE_EXT_ADV_CONN \ +BT_LE_ADV_PARAM(BT_LE_ADV_OPT_EXT_ADV | BT_LE_ADV_OPT_CONN, BT_GAP_ADV_FAST_INT_MIN_2, \ + BT_GAP_ADV_FAST_INT_MAX_2, NULL) + +/** Scannable extended advertising */ +#define BT_LE_EXT_ADV_SCAN \ +BT_LE_ADV_PARAM(BT_LE_ADV_OPT_EXT_ADV | BT_LE_ADV_OPT_SCANNABLE, BT_GAP_ADV_FAST_INT_MIN_2, \ + BT_GAP_ADV_FAST_INT_MAX_2, NULL) + +/** Non-connectable extended advertising with private address */ +#define BT_LE_EXT_ADV_NCONN \ +BT_LE_ADV_PARAM(BT_LE_ADV_OPT_EXT_ADV, BT_GAP_ADV_FAST_INT_MIN_2, BT_GAP_ADV_FAST_INT_MAX_2, NULL) + +/** Non-connectable extended advertising with @ref BT_LE_ADV_OPT_USE_IDENTITY + */ +#define BT_LE_EXT_ADV_NCONN_IDENTITY \ +BT_LE_ADV_PARAM(BT_LE_ADV_OPT_EXT_ADV | BT_LE_ADV_OPT_USE_IDENTITY, BT_GAP_ADV_FAST_INT_MIN_2, \ + BT_GAP_ADV_FAST_INT_MAX_2, NULL) + +/** Non-connectable extended advertising on coded PHY with private address */ +#define BT_LE_EXT_ADV_CODED_NCONN \ +BT_LE_ADV_PARAM(BT_LE_ADV_OPT_EXT_ADV | BT_LE_ADV_OPT_CODED, BT_GAP_ADV_FAST_INT_MIN_2, \ + BT_GAP_ADV_FAST_INT_MAX_2, NULL) + +/** Non-connectable extended advertising on coded PHY with @ref + * BT_LE_ADV_OPT_USE_IDENTITY + */ +#define BT_LE_EXT_ADV_CODED_NCONN_IDENTITY \ +BT_LE_ADV_PARAM(BT_LE_ADV_OPT_EXT_ADV | BT_LE_ADV_OPT_CODED | BT_LE_ADV_OPT_USE_IDENTITY, \ + BT_GAP_ADV_FAST_INT_MIN_2, BT_GAP_ADV_FAST_INT_MAX_2, NULL) + +/* Helper to initialize extended advertising start parameters inline + * + * @param _timeout Advertiser timeout + * @param _n_evts Number of advertising events + */ +#define BT_LE_EXT_ADV_START_PARAM_INIT(_timeout, _n_evts) \ +{ \ + .timeout = (_timeout), \ + .num_events = (_n_evts), \ +} + +/* Helper to declare extended advertising start parameters inline + * + * @param _timeout Advertiser timeout + * @param _n_evts Number of advertising events + */ +#define BT_LE_EXT_ADV_START_PARAM(_timeout, _n_evts) \ +((const struct bt_le_ext_adv_start_param[]){ \ + BT_LE_EXT_ADV_START_PARAM_INIT((_timeout), (_n_evts))}) + +#define BT_LE_EXT_ADV_START_DEFAULT BT_LE_EXT_ADV_START_PARAM(0, 0) + +/* Helper to declare periodic advertising parameters inline + * + * @param _int_min Minimum periodic advertising interval, N * 1.25 ms + * @param _int_max Maximum periodic advertising interval, N * 1.25 ms + * @param _options Periodic advertising properties bitfield, see @ref + * bt_le_adv_opt field. + */ +#define BT_LE_PER_ADV_PARAM_INIT(_int_min, _int_max, _options) \ +{ \ + .interval_min = (_int_min), \ + .interval_max = (_int_max), \ + .options = (_options), \ +} + +/* Helper to declare periodic advertising parameters inline + * + * @param _int_min Minimum periodic advertising interval, N * 1.25 ms + * @param _int_max Maximum periodic advertising interval, N * 1.25 ms + * @param _options Periodic advertising properties bitfield, see @ref + * bt_le_adv_opt field. + */ +#define BT_LE_PER_ADV_PARAM(_int_min, _int_max, _options) \ +((struct bt_le_per_adv_param[]){BT_LE_PER_ADV_PARAM_INIT(_int_min, _int_max, _options)}) + +#define BT_LE_PER_ADV_DEFAULT \ +BT_LE_PER_ADV_PARAM(BT_GAP_PER_ADV_SLOW_INT_MIN, BT_GAP_PER_ADV_SLOW_INT_MAX, \ + BT_LE_PER_ADV_OPT_NONE) + +/** + * @brief Start advertising + * + * Set advertisement data, scan response data, advertisement parameters and + * start advertising. + * + * When @p param.peer is set, the advertising will be directed to that peer + * device. In this case, the other function parameters are ignored. + * + * This function cannot be used with @ref BT_LE_ADV_OPT_EXT_ADV in the @p + * param.options. For extended advertising, the bt_le_ext_adv_* functions + * must be used. + * + * @param param Advertising parameters. + * @param ad Data to be used in advertisement packets. + * @param ad_len Number of elements in ad + * @param sd Data to be used in scan response packets. + * @param sd_len Number of elements in sd + * + * @return Zero on success or (negative) error code otherwise. + * @return -ENOMEM No free connection objects available for connectable + * advertiser. + * @retval -ENOTSUP @ref BT_LE_ADV_OPT_DIR_ADDR_RPA was used and the peer is + * known not to support address resolution, as reported by + * @ref bt_le_bond_addr_res_support. + * @return -ECONNREFUSED When connectable advertising is requested and there + * is already maximum number of connections established in the controller. + * This error code is only guaranteed when using Zephyr controller, for + * other controllers code returned in this case may be -EIO. + */ + +int bt_le_adv_start( + const struct bt_le_adv_param *param, const struct bt_data *ad, + size_t ad_len, const struct bt_data *sd, size_t sd_len); + +/** + * @brief Update advertising + * + * Update advertisement and scan response data. + * + * @param ad Data to be used in advertisement packets. + * @param ad_len Number of elements in ad + * @param sd Data to be used in scan response packets. + * @param sd_len Number of elements in sd + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_le_adv_update_data( + const struct bt_data *ad, size_t ad_len, const struct bt_data *sd, + size_t sd_len); + +/** + * @brief Stop advertising + * + * Stops ongoing advertising. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_le_adv_stop(void); + +/** + * @brief Create advertising set. + * + * Create an instance of an independent advertising set with its own + * parameters and data. The advertising set remains valid until deleted with + * @ref bt_le_ext_adv_delete. Advertising parameters can be updated with @ref + * bt_le_ext_adv_update_param, and advertising can be started with @ref + * bt_le_ext_adv_start. + * + * @note The number of supported extended advertising sets can be controlled + * by @kconfig{CONFIG_BT_EXT_ADV_MAX_ADV_SET}. + * + * @param[in] param Advertising parameters. + * @param[in] cb Callback struct to notify about advertiser activity. Can + * be NULL. Must point to valid memory during the lifetime + * of the advertising set. + * @param[out] adv Valid advertising set object on success. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_le_ext_adv_create( + const struct bt_le_adv_param *param, const struct bt_le_ext_adv_cb *cb, + struct bt_le_ext_adv **adv); + +/** + * @brief Parameters for starting an extended advertising session. + * + * @details This struct provides the parameters to control the behavior of an + * extended advertising session, including the timeout and the number of + * advertising events to send. The timeout is specified in units of 10 ms, + * and the number of events determines how many times the advertising will be + * sent before stopping. If either the timeout or number of events is + * reached, the advertising session will be stopped, and the application will + * be notified via the advertiser sent callback. If both parameters are + * provided, the advertising session will stop when either limit is reached. + * + * @note Used in @ref bt_le_ext_adv_start function. + */ + +struct bt_le_ext_adv_start_param +{ + /* @brief Maximum advertising set duration (N * 10 ms) + * + * The advertising set can be automatically disabled after a certain amount + * of time has passed since it first appeared on air. + * + * Set to zero for no limit. Set in units of 10 ms. + * + * When the advertising set is automatically disabled because of this + * limit, @ref bt_le_ext_adv_cb.sent will be called. + * + * When using high duty cycle directed connectable advertising then this + * parameters must be set to a non-zero value less than or equal to the + * maximum of + * @ref BT_GAP_ADV_HIGH_DUTY_CYCLE_MAX_TIMEOUT. + * + * If privacy @kconfig{CONFIG_BT_PRIVACY} is enabled then the timeout must + * be less than @kconfig{CONFIG_BT_RPA_TIMEOUT}. + * + * For background information, see parameter "Duration" in Bluetooth Core + * Specification Version 6.0 Vol. 4 Part E, Section 7.8.56. + */ + + uint16_t timeout; + + /* @brief Maximum number of extended advertising events to be + * sent + * + * The advertiser can be automatically disabled once the whole + * advertisement (i.e. extended advertising event) has been sent a certain + * number of times. The number of advertising PDUs sent may be higher and + * is not relevant. + * + * Set to zero for no limit. + * + * When the advertising set is automatically disabled because of this + * limit, @ref bt_le_ext_adv_cb.sent will be called. + * + * For background information, see parameter + * "Max_Extended_Advertising_Events" in Bluetooth Core Specification + * Version 6.0 Vol. 4 Part E, Section 7.8.56. + */ + + uint8_t num_events; +}; + +/** + * @brief Start advertising with the given advertising set + * + * If the advertiser is limited by either the @p param.timeout or @p + * param.num_events, the application will be notified by the @ref + * bt_le_ext_adv_cb.sent callback once the limit is reached. If the + * advertiser is limited by both the timeout and the number of advertising + * events, then the limit that is reached first will stop the advertiser. + * + * @note The advertising set @p adv can be created with @ref + * bt_le_ext_adv_create. + * + * @param adv Advertising set object. + * @param param Advertise start parameters. + */ + +int bt_le_ext_adv_start(struct bt_le_ext_adv *adv, + const struct bt_le_ext_adv_start_param *param); + +/** + * @brief Stop advertising with the given advertising set + * + * Stop advertising with a specific advertising set. When using this function + * the advertising sent callback will not be called. + * + * @param adv Advertising set object. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_le_ext_adv_stop(struct bt_le_ext_adv *adv); + +/** + * @brief Set an advertising set's advertising or scan response data. + * + * Set advertisement data or scan response data. If the advertising set is + * currently advertising then the advertising data will be updated in + * subsequent advertising events. + * + * When both @ref BT_LE_ADV_OPT_EXT_ADV and @ref BT_LE_ADV_OPT_SCANNABLE are + * enabled then advertising data is ignored and only scan response data is + * used. When @ref BT_LE_ADV_OPT_SCANNABLE is not enabled then scan response + * data is ignored and only advertising data is used. + * + * If the advertising set has been configured to send advertising data on the + * primary advertising channels then the maximum data length is + * @ref BT_GAP_ADV_MAX_ADV_DATA_LEN octets. + * If the advertising set has been configured for extended advertising, then + * the maximum data length is defined by the controller with the maximum + * possible of @ref BT_GAP_ADV_MAX_EXT_ADV_DATA_LEN bytes. + * + * @note Extended advertising was introduced in Bluetooth 5.0, and legacy + * scanners will not support reception of any extended advertising packets. + * + * @note When updating the advertising data while advertising the advertising + * data and scan response data length must be smaller or equal to what can + * be fit in a single advertising packet. Otherwise the advertiser must be + * stopped. + * + * @param adv Advertising set object. + * @param ad Data to be used in advertisement packets. + * @param ad_len Number of elements in ad + * @param sd Data to be used in scan response packets. + * @param sd_len Number of elements in sd + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_le_ext_adv_set_data( + struct bt_le_ext_adv *adv, const struct bt_data *ad, size_t ad_len, + const struct bt_data *sd, size_t sd_len); + +/** + * @brief Update advertising parameters. + * + * Update the advertising parameters. The function will return an error if + * the advertiser set is currently advertising. Stop the advertising set + * before calling this function. + * + * @param adv Advertising set object. + * @param param Advertising parameters. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_le_ext_adv_update_param(struct bt_le_ext_adv *adv, + const struct bt_le_adv_param *param); + +/** + * @brief Delete advertising set. + * + * Delete advertising set. This will free up the advertising set and make it + * possible to create a new advertising set if the limit + * @kconfig{CONFIG_BT_EXT_ADV_MAX_ADV_SET} was reached. + * + * @param adv Advertising set object. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_le_ext_adv_delete(struct bt_le_ext_adv *adv); + +/** + * @brief Get array index of an advertising set. + * + * This function is used to map bt_adv to index of an array of advertising + * sets. The array has @kconfig{CONFIG_BT_EXT_ADV_MAX_ADV_SET} elements. + * + * @param adv Advertising set. + * + * @return Index of the advertising set object. + * The range of the returned value is + * 0..@kconfig{CONFIG_BT_EXT_ADV_MAX_ADV_SET}-1 + */ + +uint8_t bt_le_ext_adv_get_index(const struct bt_le_ext_adv *adv); + +/** Advertising states. */ + +enum bt_le_ext_adv_state +{ + /** The advertising set has been created but not enabled. */ + + BT_LE_EXT_ADV_STATE_DISABLED, + + /** The advertising set is enabled. */ + + BT_LE_EXT_ADV_STATE_ENABLED, +}; + +/** Periodic Advertising states. */ + +enum bt_le_per_adv_state +{ + /** Not configured for periodic advertising. */ + + BT_LE_PER_ADV_STATE_NONE, + + /** The advertising set has been configured for periodic advertising, but + * is not enabled. + */ + + BT_LE_PER_ADV_STATE_DISABLED, + + /** Periodic advertising is enabled. */ + + BT_LE_PER_ADV_STATE_ENABLED, +}; + +/** @brief Advertising set info structure. */ + +struct bt_le_ext_adv_info +{ + /** Local identity handle. */ + + uint8_t id; + + /** Currently selected Transmit Power in dBm. Range: -127 to +20. */ + + int8_t tx_power; + + /* @brief Advertising Set ID + * + * Will be @ref BT_GAP_SID_INVALID for advertising sets that were + * configured without + * @ref BT_LE_ADV_OPT_EXT_ADV. + */ + + uint8_t sid; + + /** @brief Current local advertising address used. + * + * The address the set advertises with, whether that is an identity + * address, a static random address, an RPA or an NRPA. For a set + * advertising with an RPA this is the RPA itself, not the identity + * address it resolves to. The value is determined when the advertising + * parameters are set, so it is only meaningful once + * bt_le_ext_adv_create() or bt_le_ext_adv_update_param() has succeeded, + * and reads as @ref BT_ADDR_LE_ANY before that. + * + * The pointer is valid for as long as the advertising set object itself, + * i.e. until bt_le_ext_adv_delete(), and tracks the set across + * reconfiguration and private address rotation. Copy the address if it is + * needed beyond that. + * + * @note If the set was configured to let the controller resolve the + * address against its resolving list, the controller may substitute a + * locally generated RPA that the host cannot observe. The configured + * fallback address is reported in that case. + */ + + const bt_addr_le_t *addr; + + /** Extended advertising state. */ + + enum bt_le_ext_adv_state ext_adv_state; + + /** Periodic advertising state. */ + + enum bt_le_per_adv_state per_adv_state; +}; + +/** + * @brief Get advertising set info + * + * @param adv Advertising set object + * @param info Advertising set info object. The values in this object are + * only valid on success. + * + * @retval 0 Success. + * @retval -EINVAL @p adv is not valid advertising set or @p info is NULL. + */ + +int bt_le_ext_adv_get_info(const struct bt_le_ext_adv *adv, + struct bt_le_ext_adv_info *info); + +/** + * @typedef bt_le_scan_cb_t + * @brief Callback type for reporting LE scan results. + * + * A function of this type is given to the @ref bt_le_scan_start function and + * will be called for any discovered LE device. + * + * @param addr Advertiser LE address and type. + * @param rssi Strength of advertiser signal in dBm. Range: -127 to +20. + * May be set to @ref BT_GAP_RSSI_INVALID when the value is + * not available. + * @param adv_type Type of advertising response from advertiser. Uses the + * @ref + * bt_gap_adv_type values. + * @param buf Buffer containing advertiser data. + */ + +typedef void bt_le_scan_cb_t( + const bt_addr_le_t *addr, int8_t rssi, uint8_t adv_type, + struct net_buf_simple *buf); + +/** + * @brief Set or update the periodic advertising parameters. + * + * The periodic advertising parameters can only be set or updated on an + * extended advertisement set which is neither scannable, connectable nor + * anonymous (meaning, the advertising options @ref BT_LE_ADV_OPT_SCANNABLE, + * @ref BT_LE_ADV_OPT_CONN and @ref BT_LE_ADV_OPT_ANONYMOUS cannot be set for + * @p adv). + * + * @param adv Advertising set object. + * @param param Advertising parameters. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_le_per_adv_set_param(struct bt_le_ext_adv *adv, + const struct bt_le_per_adv_param *param); + +/** + * @brief Set or update the periodic advertising data. + * + * The periodic advertisement data can only be set or updated on an extended + * advertisement set which is neither scannable, connectable nor anonymous + * (meaning, the advertising options @ref BT_LE_ADV_OPT_SCANNABLE, + * @ref BT_LE_ADV_OPT_CONN and @ref BT_LE_ADV_OPT_ANONYMOUS cannot be set for + * @p adv). + * + * @param adv Advertising set object. + * @param ad Advertising data. + * @param ad_len Advertising data length. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_le_per_adv_set_data( + const struct bt_le_ext_adv *adv, const struct bt_data *ad, size_t ad_len); + +/** + * @brief Update the Advertising Data Identifier (DID) for periodic + * advertising without changing the data. + * + * This sends a HCI command with operation set to "unchanged data" which + * causes the controller to update the ADI field (DID) in the periodic + * advertising PDU without modifying the advertising data payload. + * + * @kconfig_dep{CONFIG_BT_PER_ADV} + * + * @note The advertising set must have periodic advertising started + * (via @ref bt_le_per_adv_start), must contain data, and ADI inclusion must + * have been enabled via @ref BT_LE_PER_ADV_OPT_INCLUDE_ADI before calling + * this function. + * + * @param adv Advertising set object. + * + * @return 0 on success, negative errno value on failure. + */ + +int bt_le_per_adv_update_did(const struct bt_le_ext_adv *adv); + +/** + * @brief Parameters for setting data for a specific periodic advertising + * with response subevent. + * + * @details This struct provides the necessary information to set the data + * for a specific subevent in a Periodic Advertising with Response (PAwR) + * scenario. It specifies the subevent number, the range of response slots to + * listen to, and the actual data to send. This is used to respond to data + * request from an advertiser by sending back the data in the specified + * subevent. + * + * @note Used in @ref bt_le_per_adv_set_subevent_data function. + */ + +struct bt_le_per_adv_subevent_data_params +{ + /** The subevent to set data for */ + + uint8_t subevent; + + /** The first response slot to listen to */ + + uint8_t response_slot_start; + + /** The number of response slots to listen to */ + + uint8_t response_slot_count; + + /** The data to send */ + + const struct net_buf_simple *data; +}; + +/** + * @brief Set the periodic advertising with response subevent data. + * + * Set the data for one or more subevents of a Periodic Advertising with + * Responses Advertiser in reply data request. + * + * @pre There are @p num_subevents elements in @p params. @pre The controller + * has requested data for the subevents in @p params. + * + * @param adv The extended advertiser the PAwR train belongs to. + * @param num_subevents The number of subevents to set data for. + * @param params Subevent parameters. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_le_per_adv_set_subevent_data( + const struct bt_le_ext_adv *adv, uint8_t num_subevents, + const struct bt_le_per_adv_subevent_data_params *params); + +/** + * @brief Starts periodic advertising. + * + * Enabling the periodic advertising can be done independently of extended + * advertising, but both periodic advertising and extended advertising shall + * be enabled before any periodic advertising data is sent. The periodic + * advertising and extended advertising can be enabled in any order. + * + * Once periodic advertising has been enabled, it will continue advertising + * until @ref bt_le_per_adv_stop function has been called, or if the + * advertising set is deleted by @ref bt_le_ext_adv_delete function. Calling + * @ref bt_le_ext_adv_stop function will not stop the periodic advertising. + * + * @param adv Advertising set object. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_le_per_adv_start(struct bt_le_ext_adv *adv); + +/** + * @brief Stops periodic advertising. + * + * Disabling the periodic advertising can be done independently of extended + * advertising. Disabling periodic advertising will not disable extended + * advertising. + * + * @param adv Advertising set object. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_le_per_adv_stop(struct bt_le_ext_adv *adv); + +/** + * @brief Information about the successful synchronization with periodic + * advertising. + * + * @details This struct provides information about the periodic advertising + * sync once it has been successfully established. It includes the + * advertiser's address, SID, the advertising interval, PHY, and the + * synchronization state. It also contains details about the sync, such as + * service data and the peer device that transferred the sync. When using + * periodic advertising response (configured via + * @kconfig{CONFIG_BT_PER_ADV_SYNC_RSP}), additional details such as subevent + * information and response timings are provided. + * + * @note Used in @ref bt_le_per_adv_sync_cb structure. + */ + +struct bt_le_per_adv_sync_synced_info +{ + /** Advertiser LE address and type. */ + + const bt_addr_le_t *addr; + + /** Advertising Set Identifier, valid range @ref BT_GAP_SID_MIN to @ref + * BT_GAP_SID_MAX. + */ + + uint8_t sid; + + /** Periodic advertising interval (N * 1.25 ms) */ + + uint16_t interval; + + /** Advertiser PHY (see @ref bt_gap_le_phy). */ + + uint8_t phy; + + /** True if receiving periodic advertisements, false otherwise. */ + + bool recv_enabled; + + /* @brief Service Data provided by the peer when sync is transferred + * + * Will always be 0 when the sync is locally created. + */ + + uint16_t service_data; + + /* @brief Peer that transferred the periodic advertising sync + * + * Will always be NULL when the sync is locally created. + * + */ + + struct bt_conn *conn; +#if defined(CONFIG_BT_PER_ADV_SYNC_RSP) || defined(__DOXYGEN__) + /** Number of subevents */ + + uint8_t num_subevents; + + /** Subevent interval (N * 1.25 ms) */ + + uint8_t subevent_interval; + + /** Response slot delay (N * 1.25 ms) */ + + uint8_t response_slot_delay; + + /** Response slot spacing (N * 1.25 ms) */ + + uint8_t response_slot_spacing; + +#endif /* CONFIG_BT_PER_ADV_SYNC_RSP */ +}; + +/** + * @brief Information about the termination of a periodic advertising sync. + * + * @details This struct provides information about the termination of a + * periodic advertising sync. It includes the advertiser’s address and SID, + * along with the reason for the sync termination. This information is + * provided in the callback when the sync is terminated, either due to a + * local or remote request, or due to missing data (e.g., out of range or + * lost sync). + * + * @note Used in @ref bt_le_per_adv_sync_cb structure. + */ + +struct bt_le_per_adv_sync_term_info +{ + /** Advertiser LE address and type. */ + + const bt_addr_le_t *addr; + + /** Advertising Set Identifier, valid range @ref BT_GAP_SID_MIN to @ref + * BT_GAP_SID_MAX. + */ + + uint8_t sid; + + /** Cause of periodic advertising termination (see the BT_HCI_ERR_* + * values). + */ + + uint8_t reason; +}; + +/** + * @brief Information about a received periodic advertising report. + * + * @details This struct holds information about a periodic advertising event + * that has been received. It contains details such as the advertiser’s + * address, SID, transmit power, RSSI, CTE type, and additional information + * depending on the configuration (e.g., event counter and subevent in case + * of a subevent indication). This information is provided in the callback + * when periodic advertising data is received. + * + * @note Used in @ref bt_le_per_adv_sync_cb structure. + */ + +struct bt_le_per_adv_sync_recv_info +{ + /** Advertiser LE address and type. */ + + const bt_addr_le_t *addr; + + /** Advertising Set Identifier, valid range @ref BT_GAP_SID_MIN to @ref + * BT_GAP_SID_MAX. + */ + + uint8_t sid; + + /** @brief The TX power of the advertisement in dBm. + * + * Range: -127 to +20. May be set to @ref BT_GAP_TX_POWER_INVALID when the + * value is not available. + */ + + int8_t tx_power; + + /** @brief The RSSI of the advertisement (excluding any CTE), in dBm. + * + * Range: -127 to +20. May be set to @ref BT_GAP_RSSI_INVALID when the + * value is not available. + */ + + int8_t rssi; + + /** The Constant Tone Extension (CTE) of the advertisement (@ref + * bt_df_cte_type) + */ + + uint8_t cte_type; +#if defined(CONFIG_BT_PER_ADV_SYNC_RSP) || defined(__DOXYGEN__) + /** The value of the event counter where the subevent indication was + * received. + */ + + uint16_t periodic_event_counter; + + /** The subevent where the subevent indication was received. */ + + uint8_t subevent; +#endif /* CONFIG_BT_PER_ADV_SYNC_RSP */ +}; + +/** + * @brief Information about the state of periodic advertising sync. + * + * @details This struct provides information about the current state of a + * periodic advertising sync. It indicates whether periodic advertising + * reception is enabled or not. It is typically used to report the state + * change via callbacks in the @ref bt_le_per_adv_sync_cb structure. + */ + +struct bt_le_per_adv_sync_state_info +{ + /** True if receiving periodic advertisements, false otherwise. */ + + bool recv_enabled; +}; + +/** + * @brief Callback struct for periodic advertising sync events. + * + * @details This struct defines the callback functions that are invoked for + * various periodic advertising sync events. These include when the sync is + * successfully established, terminated, when data is received, state + * changes, BIG info reports, and IQ samples from the periodic advertising. + * + * @note Used in @ref bt_le_per_adv_sync_cb_register function. + * + * @note The callbacks are invoked from a thread context, never from an + * ISR. Whether a callback is invoked from a context internal to the stack + * or synchronously from within the API call that triggers it, and from + * which context, is not part of the API and may change between releases. + * See @rstref{Callback execution contexts } + * for the hazards of blocking in a callback and their mitigations. + */ + +struct bt_le_per_adv_sync_cb +{ + /* @brief The periodic advertising has been successfully synced. + * + * This callback notifies the application that the periodic advertising set + * has been successfully synced, and will now start to receive periodic + * advertising reports. + * + * @param sync The periodic advertising sync object. + * @param info Information about the sync event. + */ + + void (*synced)(struct bt_le_per_adv_sync *sync, + struct bt_le_per_adv_sync_synced_info *info); + + /* @brief The periodic advertising sync has been terminated. + * + * This callback notifies the application that the periodic advertising + * sync has been terminated, either by local request, remote request or + * because due to missing data, e.g. by being out of range or sync. + * + * @param sync The periodic advertising sync object. + * @param info Information about the termination event. + */ + + void (*term)(struct bt_le_per_adv_sync *sync, + const struct bt_le_per_adv_sync_term_info *info); + + /* @brief Periodic advertising data received. + * + * This callback notifies the application of an periodic advertising + * report. + * + * @param sync The advertising set object. + * @param info Information about the periodic advertising event. + * @param buf Buffer containing the periodic advertising data. NULL if + * the controller failed to receive a subevent indication. + * Only happens if @kconfig{CONFIG_BT_PER_ADV_SYNC_RSP} is + * enabled. + */ + + void (*recv)(struct bt_le_per_adv_sync *sync, + const struct bt_le_per_adv_sync_recv_info *info, + struct net_buf_simple *buf); + + /* @brief The periodic advertising sync state has changed. + * + * This callback notifies the application about changes to the sync state. + * Initialize sync and termination is handled by their individual + * callbacks, and won't be notified here. + * + * @param sync The periodic advertising sync object. + * @param info Information about the state change. + */ + + void (*state_changed)(struct bt_le_per_adv_sync *sync, + const struct bt_le_per_adv_sync_state_info *info); + + /* @brief BIGInfo advertising report received. + * + * This callback notifies the application of a BIGInfo advertising report. + * This is received if the advertiser is broadcasting isochronous streams + * in a BIG. See iso.h for more information. + * + * @param sync The advertising set object. + * @param biginfo The BIGInfo report. + */ + + void (*biginfo)(struct bt_le_per_adv_sync *sync, + const struct bt_iso_biginfo *biginfo); + + /* @brief Callback for IQ samples report collected when sampling + * CTE received with periodic advertising PDU. + * + * @param sync The periodic advertising sync object. + * @param info Information about the sync event. + */ + + void (*cte_report_cb)(struct bt_le_per_adv_sync *sync, + struct bt_df_per_adv_sync_iq_samples_report const *info); + + struct list_node node; +}; + +/** Periodic advertising sync options */ + +enum bt_le_per_adv_sync_opt +{ + /** Convenience value when no options are specified. */ + + BT_LE_PER_ADV_SYNC_OPT_NONE = 0, + + /* @brief Use the periodic advertising list to sync with advertiser + * + * When this option is set, the address and SID of the parameters are + * ignored. + */ + + BT_LE_PER_ADV_SYNC_OPT_USE_PER_ADV_LIST = BIT(0), + + /* @brief Disables periodic advertising reports + * + * No advertisement reports will be handled until enabled. + */ + + BT_LE_PER_ADV_SYNC_OPT_REPORTING_INITIALLY_DISABLED = BIT(1), + + /** Filter duplicate Periodic Advertising reports */ + + BT_LE_PER_ADV_SYNC_OPT_FILTER_DUPLICATE = BIT(2), + + /** Sync with Angle of Arrival (AoA) constant tone extension */ + + BT_LE_PER_ADV_SYNC_OPT_DONT_SYNC_AOA = BIT(3), + + /** Sync with Angle of Departure (AoD) 1 us constant tone extension */ + + BT_LE_PER_ADV_SYNC_OPT_DONT_SYNC_AOD_1US = BIT(4), + + /** Sync with Angle of Departure (AoD) 2 us constant tone extension */ + + BT_LE_PER_ADV_SYNC_OPT_DONT_SYNC_AOD_2US = BIT(5), + + /** Do not sync to packets without a constant tone extension */ + + BT_LE_PER_ADV_SYNC_OPT_SYNC_ONLY_CONST_TONE_EXT = BIT(6), +}; + +/** + * @brief Parameters for creating a periodic advertising sync object. + * + * @details This struct contains the parameters required to create a periodic + * advertising sync object, which allows the system to synchronize with + * periodic advertising reports from an advertiser. It includes the + * advertiser's address, SID, sync options, event skip, and synchronization + * timeout. + * + * @note bt_le_per_adv_sync_param is used as a parameter in the @ref + * bt_le_per_adv_sync_create function to configure synchronization behavior. + */ + +struct bt_le_per_adv_sync_param +{ + /* @brief Periodic Advertiser Address + * + * Only valid if not using the periodic advertising list (@ref + * BT_LE_PER_ADV_SYNC_OPT_USE_PER_ADV_LIST) + */ + + bt_addr_le_t addr; + + /* @brief Advertising Set Identifier. Valid range @ref BT_GAP_SID_MIN to + * @ref BT_GAP_SID_MAX. + * + * Only valid if not using the periodic advertising list (@ref + * BT_LE_PER_ADV_SYNC_OPT_USE_PER_ADV_LIST) + */ + + uint8_t sid; + + /** Bit-field of periodic advertising sync options, see the @ref + * bt_le_adv_opt field. + */ + + uint32_t options; + + /* @brief Maximum event skip + * + * Maximum number of periodic advertising events that can be skipped after + * a successful receive. Range: 0x0000 to 0x01F3 + */ + + uint16_t skip; + + /* @brief Synchronization timeout (N * 10 ms) + * + * Synchronization timeout for the periodic advertising sync. Range 0x000A + * to 0x4000 (100 ms to 163840 ms) + */ + + uint16_t timeout; +}; + +/** + * @brief Get array index of an periodic advertising sync object. + * + * This function is to get the index of an array of periodic advertising sync + * objects. The array has @kconfig{CONFIG_BT_PER_ADV_SYNC_MAX} elements. + * + * @param per_adv_sync The periodic advertising sync object. + * + * @return Index of the periodic advertising sync object. + * The range of the returned value is + * 0..@kconfig{CONFIG_BT_PER_ADV_SYNC_MAX}-1 + */ + +uint8_t +bt_le_per_adv_sync_get_index(struct bt_le_per_adv_sync *per_adv_sync); + +/** + * @brief Get a periodic advertising sync object from the array index. + * + * This function is to get the periodic advertising sync object from the + * array index. The array has @kconfig{CONFIG_BT_PER_ADV_SYNC_MAX} elements. + * + * @param index The index of the periodic advertising sync object. The range + * of the index value is + * 0..@kconfig{CONFIG_BT_PER_ADV_SYNC_MAX}-1 + * + * @return The periodic advertising sync object of the array index or NULL if + * invalid index. + */ + +struct bt_le_per_adv_sync *bt_le_per_adv_sync_lookup_index(uint8_t index); + +/** @brief Periodic advertising set info structure. */ + +struct bt_le_per_adv_sync_info +{ + /** Periodic Advertiser Address */ + + bt_addr_le_t addr; + + /** Advertising Set Identifier, valid range @ref BT_GAP_SID_MIN to @ref + * BT_GAP_SID_MAX. + */ + + uint8_t sid; + + /** Periodic advertising interval (N * 1.25 ms) */ + + uint16_t interval; + + /** Advertiser PHY (see @ref bt_gap_le_phy). */ + + uint8_t phy; +}; + +/** + * @brief Get periodic adv sync information. + * + * @param per_adv_sync Periodic advertising sync object. + * @param info Periodic advertising sync info object + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_le_per_adv_sync_get_info(struct bt_le_per_adv_sync *per_adv_sync, + struct bt_le_per_adv_sync_info *info); + +/** + * @brief Look up an existing periodic advertising sync object by advertiser + * address. + * + * @param adv_addr Advertiser address. + * @param sid The periodic advertising set ID. + * + * @return Periodic advertising sync object or NULL if not found. + */ + +struct bt_le_per_adv_sync *bt_le_per_adv_sync_lookup_addr( + const bt_addr_le_t *adv_addr, uint8_t sid); + +/** + * @brief Create a periodic advertising sync object. + * + * Create a periodic advertising sync object that can try to synchronize to + * periodic advertising reports from an advertiser. Scan shall either be + * disabled or extended scan shall be enabled. + * + * This function does not timeout, and will continue to look for an + * advertiser until it either finds it or @ref bt_le_per_adv_sync_delete is + * called. It is thus suggested to implement a timeout when using this, if it + * is expected to find the advertiser within a reasonable timeframe. + * + * @param[in] param Periodic advertising sync parameters. + * @param[out] out_sync Periodic advertising sync object on. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_le_per_adv_sync_create(const struct bt_le_per_adv_sync_param *param, + struct bt_le_per_adv_sync **out_sync); + +/** + * @brief Delete periodic advertising sync. + * + * Delete the periodic advertising sync object. Can be called regardless of + * the state of the sync. If the syncing is currently syncing, the syncing is + * cancelled. If the sync has been established, it is terminated. The + * periodic advertising sync object will be invalidated afterwards. + * + * If the state of the sync object is syncing, then a new periodic + * advertising sync object cannot be created until the controller has + * finished canceling this object. + * + * @param per_adv_sync The periodic advertising sync object. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_le_per_adv_sync_delete(struct bt_le_per_adv_sync *per_adv_sync); + +/** + * @brief Register periodic advertising sync callbacks. + * + * Adds the callback structure to the list of callback structures for + * periodic advertising syncs. + * + * This callback will be called for all periodic advertising sync activity, + * such as synced, terminated and when data is received. + * + * @param cb Callback struct. Must point to memory that remains valid. + * + * @retval 0 Success. + * @retval -EEXIST if @p cb was already registered. + */ + +int bt_le_per_adv_sync_cb_register(struct bt_le_per_adv_sync_cb *cb); + +/** + * @brief Unregister periodic advertising sync callbacks. + * + * Removes the callback structure from the list of periodic advertising sync + * callbacks. + * + * @param cb Callback struct. + * + * @retval Zero on success or (negative) error code otherwise. + */ + +int bt_le_per_adv_sync_cb_unregister(struct bt_le_per_adv_sync_cb *cb); + +/** + * @brief Enables receiving periodic advertising reports for a sync. + * + * If the sync is already receiving the reports, -EALREADY is returned. + * + * @param per_adv_sync The periodic advertising sync object. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_le_per_adv_sync_recv_enable(struct bt_le_per_adv_sync *per_adv_sync); + +/** + * @brief Disables receiving periodic advertising reports for a sync. + * + * If the sync report receiving is already disabled, -EALREADY is returned. + * + * @param per_adv_sync The periodic advertising sync object. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_le_per_adv_sync_recv_disable(struct bt_le_per_adv_sync *per_adv_sync); + +/** Periodic Advertising Sync Transfer options */ + +enum bt_le_per_adv_sync_transfer_opt +{ + /** Convenience value when no options are specified. */ + + BT_LE_PER_ADV_SYNC_TRANSFER_OPT_NONE = 0, + + /* @brief No Angle of Arrival (AoA) + * + * Do not sync with Angle of Arrival (AoA) constant tone extension + */ + + BT_LE_PER_ADV_SYNC_TRANSFER_OPT_SYNC_NO_AOA = BIT(0), + + /* @brief No Angle of Departure (AoD) 1 us + * + * Do not sync with Angle of Departure (AoD) 1 us constant tone extension + */ + + BT_LE_PER_ADV_SYNC_TRANSFER_OPT_SYNC_NO_AOD_1US = BIT(1), + + /* @brief No Angle of Departure (AoD) 2 + * + * Do not sync with Angle of Departure (AoD) 2 us constant tone extension + */ + + BT_LE_PER_ADV_SYNC_TRANSFER_OPT_SYNC_NO_AOD_2US = BIT(2), + + /** Only sync to packets with constant tone extension */ + + BT_LE_PER_ADV_SYNC_TRANSFER_OPT_SYNC_ONLY_CTE = BIT(3), + + /* @brief Sync to received PAST packets but don't generate sync reports + * + * This option must not be set at the same time as + * @ref BT_LE_PER_ADV_SYNC_TRANSFER_OPT_FILTER_DUPLICATES. + */ + + BT_LE_PER_ADV_SYNC_TRANSFER_OPT_REPORTING_INITIALLY_DISABLED = BIT(4), + + /* @brief Sync to received PAST packets and generate sync reports with + * duplicate filtering + * + * This option must not be set at the same time as + * @ref BT_LE_PER_ADV_SYNC_TRANSFER_OPT_REPORTING_INITIALLY_DISABLED. + */ + + BT_LE_PER_ADV_SYNC_TRANSFER_OPT_FILTER_DUPLICATES = BIT(5), +}; + +/** + * @brief Parameters for periodic advertising sync transfer. + * + * @details This struct defines the parameters for configuring periodic + * advertising sync transfers (PASTs). It includes settings for the maximum + * event skip, synchronization timeout, and options for sync transfer. + * + * @note Used in the @ref bt_le_per_adv_sync_transfer_subscribe function to + * configure sync transfer settings. + */ + +struct bt_le_per_adv_sync_transfer_param +{ + /* @brief Maximum event skip + * + * The number of periodic advertising packets that can be skipped after a + * successful receive. + */ + + uint16_t skip; + + /* @brief Synchronization timeout (N * 10 ms) + * + * Synchronization timeout for the periodic advertising sync. Range 0x000A + * to 0x4000 (100 ms to 163840 ms) + */ + + uint16_t timeout; + + /** Periodic Advertising Sync Transfer options, see @ref + * bt_le_per_adv_sync_transfer_opt. + */ + + uint32_t options; +}; + +/** + * @brief Transfer the periodic advertising sync information to a peer + * device. + * + * This will allow another device to quickly synchronize to the same periodic + * advertising train that this device is currently synced to. + * + * @param per_adv_sync The periodic advertising sync to transfer. + * @param conn The peer device that will receive the sync + * information. + * @param service_data Application service data provided to the remote host. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_le_per_adv_sync_transfer( + const struct bt_le_per_adv_sync *per_adv_sync, const struct bt_conn *conn, + uint16_t service_data); + +/** + * @brief Transfer the information about a periodic advertising set. + * + * This will allow another device to quickly synchronize to periodic + * advertising set from this device. + * + * @param adv The periodic advertising set to transfer info of. + * @param conn The peer device that will receive the information. + * @param service_data Application service data provided to the remote host. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_le_per_adv_set_info_transfer( + const struct bt_le_ext_adv *adv, const struct bt_conn *conn, + uint16_t service_data); + +/** + * @brief Subscribe to periodic advertising sync transfers (PASTs). + * + * Sets the parameters and allow other devices to transfer periodic + * advertising syncs. + * + * @param conn The connection to set the parameters for. If NULL default + * parameters for all connections will be set. Parameters set + * for specific connection will always have precedence. + * @param param The periodic advertising sync transfer parameters. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_le_per_adv_sync_transfer_subscribe(const struct bt_conn *conn, + const struct bt_le_per_adv_sync_transfer_param *param); + +/** + * @brief Unsubscribe from periodic advertising sync transfers (PASTs). + * + * Remove the parameters that allow other devices to transfer periodic + * advertising syncs. + * + * @param conn The connection to remove the parameters for. If NULL + * default parameters for all connections will be removed. + * Unsubscribing for a specific device, will still allow other + * devices to transfer periodic advertising syncs. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_le_per_adv_sync_transfer_unsubscribe(const struct bt_conn *conn); + +/** + * @brief Add a device to the periodic advertising list. + * + * Add peer device LE address to the periodic advertising list. This will + * make it possibly to automatically create a periodic advertising sync to + * this device. + * + * @param addr Bluetooth LE identity address. + * @param sid The advertising set ID. This value is obtained from the + * @ref bt_le_scan_recv_info in the scan callback. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_le_per_adv_list_add(const bt_addr_le_t *addr, uint8_t sid); + +/** + * @brief Remove a device from the periodic advertising list. + * + * Removes peer device LE address from the periodic advertising list. + * + * @param addr Bluetooth LE identity address. + * @param sid The advertising set ID. This value is obtained from the + * @ref bt_le_scan_recv_info in the scan callback. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_le_per_adv_list_remove(const bt_addr_le_t *addr, uint8_t sid); + +/** + * @brief Clear the periodic advertising list. + * + * Clears the entire periodic advertising list. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_le_per_adv_list_clear(void); + +enum bt_le_scan_opt +{ + /** Convenience value when no options are specified. */ + + BT_LE_SCAN_OPT_NONE = 0, + + /** Filter duplicates. */ + + BT_LE_SCAN_OPT_FILTER_DUPLICATE = BIT(0), + + /** Filter using filter accept list. */ + + BT_LE_SCAN_OPT_FILTER_ACCEPT_LIST = BIT(1), + + /** Enable scan on coded PHY (Long Range). */ + + BT_LE_SCAN_OPT_CODED = BIT(2), + + /* @brief Disable scan on 1M phy. + * + * @note Requires @ref BT_LE_SCAN_OPT_CODED. + */ + + BT_LE_SCAN_OPT_NO_1M = BIT(3), + + /* @brief Use the extended scanner filter policy. + * + * Also report directed advertisements whose target address is a resolvable + * private address that the Controller was unable to resolve. The target + * address is reported in @ref bt_le_scan_recv_info.direct_addr. + * + * @note Requires @kconfig{CONFIG_BT_SCAN_EXT_FILTER_POLICY}. @ref + * bt_le_scan_start returns @c -EINVAL if this option is set while it is + * disabled. + * + * @note Requires a Controller that supports the Extended Scanner Filter + * Policies. + * @ref bt_le_scan_start returns @c -ENOTSUP otherwise. + */ + + BT_LE_SCAN_OPT_EXT_FILTER_POLICY = BIT(4), +}; + +enum bt_le_scan_type +{ + /** Scan without requesting additional information from advertisers. */ + + BT_LE_SCAN_TYPE_PASSIVE = 0x00, + + /* @brief Scan and request additional information from advertisers. + * + * Using this scan type will automatically send scan requests to all + * devices. Scan responses are received in the same manner and using the + * same callbacks as advertising reports. + */ + + BT_LE_SCAN_TYPE_ACTIVE = 0x01, +}; + +/** LE scan parameters */ + +struct bt_le_scan_param +{ + /** Scan type. @ref BT_LE_SCAN_TYPE_ACTIVE or @ref BT_LE_SCAN_TYPE_PASSIVE. + */ + + uint8_t type; + + /** Bit-field of scanning options. */ + + uint8_t options; + + /** Scan interval (N * 0.625 ms). + * + * @note When @kconfig{CONFIG_BT_SCAN_AND_INITIATE_IN_PARALLEL} is enabled + * and the application wants to scan and connect in parallel, the + * Bluetooth Controller may require the scan interval used for scanning + * and connection establishment to be equal to obtain the best + * performance. + */ + + uint16_t interval; + + /** Scan window (N * 0.625 ms) + * + * @note When @kconfig{CONFIG_BT_SCAN_AND_INITIATE_IN_PARALLEL} is enabled + * and the application wants to scan and connect in parallel, the + * Bluetooth Controller may require the scan window used for scanning and + * connection establishment to be equal to obtain the best performance. + */ + + uint16_t window; + + /* @brief Scan timeout (N * 10 ms) + * + * Application will be notified by the scan timeout callback. Set zero to + * disable timeout. + */ + + uint16_t timeout; + + /* @brief Scan interval LE Coded PHY (N * 0.625 MS) + * + * Set zero to use same as LE 1M PHY scan interval. + */ + + uint16_t interval_coded; + + /* @brief Scan window LE Coded PHY (N * 0.625 MS) + * + * Set zero to use same as LE 1M PHY scan window. + */ + + uint16_t window_coded; +}; + +/** LE advertisement and scan response packet information */ + +struct bt_le_scan_recv_info +{ + /* @brief Advertiser LE address and type. + * + * If advertiser is anonymous then this address will be + * @ref BT_ADDR_LE_ANY. + */ + + const bt_addr_le_t *addr; + + /** Advertising Set Identifier, valid range @ref BT_GAP_SID_MIN to @ref + * BT_GAP_SID_MAX. + */ + + uint8_t sid; + + /** @brief Strength of advertiser signal in dBm. + * + * Range: -127 to +20. May be set to @ref BT_GAP_RSSI_INVALID when the + * value is not available. + */ + + int8_t rssi; + + /** @brief Transmit power of the advertiser in dBm. + * + * Range: -127 to +20. May be set to @ref BT_GAP_TX_POWER_INVALID when the + * value is not available. + */ + + int8_t tx_power; + + /* @brief Advertising packet type. + * + * Uses the @ref bt_gap_adv_type value. + * + * May indicate that this is a scan response if the type is + * @ref BT_GAP_ADV_TYPE_SCAN_RSP. + */ + + uint8_t adv_type; + + /* @brief Advertising packet properties bitfield. + * + * Uses the @ref bt_gap_adv_prop values. May indicate that this is a scan + * response if the value contains the + * @ref BT_GAP_ADV_PROP_SCAN_RESPONSE bit. + * + */ + + uint16_t adv_props; + + /* @brief Periodic advertising interval (N * 1.25 ms). + * + * If 0 there is no periodic advertising. + */ + + uint16_t interval; + + /** Primary advertising channel PHY. */ + + uint8_t primary_phy; + + /** Secondary advertising channel PHY. */ + + uint8_t secondary_phy; + + /* @brief Target address of a directed advertisement. + * + * @c NULL if the report carries no target address. Whether the + * advertisement was directed is given by @ref BT_GAP_ADV_PROP_DIRECTED + * in + * @ref bt_le_scan_recv_info.adv_props. + * + * An LE Extended Advertising Report always carries the target address of a + * directed advertisement, whether or not the Controller resolved it. An LE + * Advertising Report has no target address field, so this is @c NULL for a + * directed advertisement the Controller resolved while scanning with + * the legacy scanning commands. + * + * The address type is @ref BT_ADDR_LE_UNRESOLVED if the Controller was + * unable to resolve it. + */ + + const bt_addr_le_t *direct_addr; +}; + +/** Listener context for (LE) scanning. + * + * @note The callbacks are invoked from a thread context, never from an + * ISR. Whether a callback is invoked from a context internal to the stack + * or synchronously from within the API call that triggers it, and from + * which context, is not part of the API and may change between releases. + * See @rstref{Callback execution contexts } + * for the hazards of blocking in a callback and their mitigations. + */ + +struct bt_le_scan_cb +{ + /* @brief Advertisement packet and scan response received callback. + * + * @param info Advertiser packet and scan response information. + * @param buf Buffer containing advertiser data. + */ + + void (*recv)(const struct bt_le_scan_recv_info *info, + struct net_buf_simple *buf); + + /** @brief The scanner has stopped scanning after scan timeout. */ + + void (*timeout)(void); + + struct list_node node; +}; + +/** + * @brief Initialize scan parameters + * + * @param _type Scan Type, @ref BT_LE_SCAN_TYPE_ACTIVE or @ref + * BT_LE_SCAN_TYPE_PASSIVE. + * @param _options Scan options + * @param _interval Scan Interval (N * 0.625 ms) + * @param _window Scan Window (N * 0.625 ms) + */ +#define BT_LE_SCAN_PARAM_INIT(_type, _options, _interval, _window) \ +{ \ + .type = (_type), \ + .options = (_options), \ + .interval = (_interval), \ + .window = (_window), \ + .timeout = 0, \ + .interval_coded = 0, \ + .window_coded = 0, \ +} + +/** + * @brief Helper to declare scan parameters inline + * + * @param _type Scan Type, @ref BT_LE_SCAN_TYPE_ACTIVE or @ref + * BT_LE_SCAN_TYPE_PASSIVE. + * @param _options Scan options + * @param _interval Scan Interval (N * 0.625 ms) + * @param _window Scan Window (N * 0.625 ms) + */ +#define BT_LE_SCAN_PARAM(_type, _options, _interval, _window) \ +((struct bt_le_scan_param[]){BT_LE_SCAN_PARAM_INIT(_type, _options, _interval, _window)}) + +/** + * @brief Helper macro to enable active scanning to discover new devices. + */ +#define BT_LE_SCAN_ACTIVE \ +BT_LE_SCAN_PARAM(BT_LE_SCAN_TYPE_ACTIVE, BT_LE_SCAN_OPT_FILTER_DUPLICATE, \ + BT_GAP_SCAN_FAST_INTERVAL, BT_GAP_SCAN_FAST_WINDOW) + +/** + * @brief Helper macro to enable active scanning to discover new devices with + * window == interval. + * + * Continuous scanning should be used to maximize the chances of receiving + * advertising packets. + */ +#define BT_LE_SCAN_ACTIVE_CONTINUOUS \ +BT_LE_SCAN_PARAM(BT_LE_SCAN_TYPE_ACTIVE, BT_LE_SCAN_OPT_FILTER_DUPLICATE, \ + BT_GAP_SCAN_FAST_INTERVAL_MIN, BT_GAP_SCAN_FAST_WINDOW) +BUILD_ASSERT(BT_GAP_SCAN_FAST_WINDOW == BT_GAP_SCAN_FAST_INTERVAL_MIN, + "Continuous scanning is requested by setting window and " + "interval equal."); + +/** + * @brief Helper macro to enable passive scanning to discover new devices. + * + * This macro should be used if information required for device + * identification (e.g., UUID) are known to be placed in Advertising Data. + */ +#define BT_LE_SCAN_PASSIVE \ +BT_LE_SCAN_PARAM(BT_LE_SCAN_TYPE_PASSIVE, BT_LE_SCAN_OPT_FILTER_DUPLICATE, \ + BT_GAP_SCAN_FAST_INTERVAL, BT_GAP_SCAN_FAST_WINDOW) + +/** + * @brief Helper macro to enable passive scanning to discover new devices + * with window==interval. + * + * This macro should be used if information required for device + * identification (e.g., UUID) are known to be placed in Advertising Data. + */ +#define BT_LE_SCAN_PASSIVE_CONTINUOUS \ +BT_LE_SCAN_PARAM(BT_LE_SCAN_TYPE_PASSIVE, BT_LE_SCAN_OPT_FILTER_DUPLICATE, \ + BT_GAP_SCAN_FAST_INTERVAL_MIN, BT_GAP_SCAN_FAST_WINDOW) +BUILD_ASSERT(BT_GAP_SCAN_FAST_WINDOW == BT_GAP_SCAN_FAST_INTERVAL_MIN, + "Continuous scanning is requested by setting window and " + "interval equal."); + +/** + * @brief Helper macro to enable active scanning to discover new devices. + * Include scanning on Coded PHY in addition to 1M PHY. + */ +#define BT_LE_SCAN_CODED_ACTIVE \ +BT_LE_SCAN_PARAM(BT_LE_SCAN_TYPE_ACTIVE, BT_LE_SCAN_OPT_CODED | BT_LE_SCAN_OPT_FILTER_DUPLICATE, \ + BT_GAP_SCAN_FAST_INTERVAL, BT_GAP_SCAN_FAST_WINDOW) + +/** + * @brief Helper macro to enable passive scanning to discover new devices. + * Include scanning on Coded PHY in addition to 1M PHY. + * + * This macro should be used if information required for device + * identification (e.g., UUID) are known to be placed in Advertising Data. + */ +#define BT_LE_SCAN_CODED_PASSIVE \ +BT_LE_SCAN_PARAM(BT_LE_SCAN_TYPE_PASSIVE, \ + BT_LE_SCAN_OPT_CODED | BT_LE_SCAN_OPT_FILTER_DUPLICATE, \ + BT_GAP_SCAN_FAST_INTERVAL, BT_GAP_SCAN_FAST_WINDOW) + +/** + * @brief Start (LE) scanning + * + * Start LE scanning with given parameters and provide results through the + * specified callback. + * + * @note The LE scanner by default does not use the Identity Address of the + * local device when @kconfig{CONFIG_BT_PRIVACY} is disabled. This is to + * prevent the active scanner from disclosing the identity address + * information when requesting additional information from advertisers. In + * order to enable directed advertiser reports then + * @kconfig{CONFIG_BT_SCAN_WITH_IDENTITY} must be enabled. This does not + * apply to directed advertisements whose target address the Controller was + * unable to resolve. Those are reported whenever + * @ref BT_LE_SCAN_OPT_EXT_FILTER_POLICY is used, as they cannot + * disclose the local identity address. + * + * @note Setting the `param.timeout` parameter is not supported when + * @kconfig{CONFIG_BT_PRIVACY} is enabled, when the param.type is @ref + * BT_LE_SCAN_TYPE_ACTIVE. Supplying a non-zero timeout will result in an + * -EINVAL error code. + * + * @note The scanner will automatically scan for extended advertising packets + * if their support is enabled through @kconfig{CONFIG_BT_EXT_ADV}. + * + * @param param Scan parameters. + * @param cb Callback to notify scan results. May be NULL if callback + * registration through @ref bt_le_scan_cb_register is + * preferred. + * + * @return Zero on success or error code otherwise, positive in case of + * protocol error or negative (POSIX) in case of stack internal error. + * @retval -EBUSY if the scanner is already being started in a different + * thread. + * @retval -EINVAL if @ref BT_LE_SCAN_OPT_EXT_FILTER_POLICY is set and + * @kconfig{CONFIG_BT_SCAN_EXT_FILTER_POLICY} is disabled. + * @retval -ENOTSUP if @ref BT_LE_SCAN_OPT_EXT_FILTER_POLICY is set and the + * Controller does not support the Extended Scanner Filter Policies. + */ + +int bt_le_scan_start(const struct bt_le_scan_param *param, + bt_le_scan_cb_t cb); + +/** + * @brief Stop (LE) scanning. + * + * Stops ongoing LE scanning. + * + * @return Zero on success or error code otherwise, positive in case of + * protocol error or negative (POSIX) in case of stack internal error. + * @retval -EBUSY The scanner is being started or stopped in a different + * thread. + */ + +int bt_le_scan_stop(void); + +/** + * @brief Register scanner packet callbacks. + * + * Adds the callback structure to the list of callback structures that + * monitors scanner activity. + * + * This callback will be called for all scanner activity. + * + * @param cb Callback struct. Must point to memory that remains valid. + * + * @retval 0 Success. + * @retval -EEXIST if @p cb was already registered. + */ + +int bt_le_scan_cb_register(struct bt_le_scan_cb *cb); + +/** + * @brief Unregister scanner packet callbacks. + * + * Remove the callback structure from the list of scanner callbacks. + * + * @param cb Callback struct. Must point to memory that remains valid. + */ + +void bt_le_scan_cb_unregister(struct bt_le_scan_cb *cb); + +/** + * @brief Add device (LE) to filter accept list. + * + * Add peer device LE address to the filter accept list. + * + * @note The filter accept list cannot be modified when an LE role is using + * the filter accept list, i.e advertiser or scanner using a filter accept + * list or automatic connecting to devices using filter accept list. + * + * @param addr Bluetooth LE identity address. + * + * @return Zero on success or error code otherwise, positive in case of + * protocol error or negative (POSIX) in case of stack internal error. + */ + +int bt_le_filter_accept_list_add(const bt_addr_le_t *addr); + +/** + * @brief Remove device (LE) from filter accept list. + * + * Remove peer device LE address from the filter accept list. + * + * @note The filter accept list cannot be modified when an LE role is using + * the filter accept list, i.e advertiser or scanner using a filter accept + * list or automatic connecting to devices using filter accept list. + * + * @param addr Bluetooth LE identity address. + * + * @return Zero on success or error code otherwise, positive in case of + * protocol error or negative (POSIX) in case of stack internal error. + */ + +int bt_le_filter_accept_list_remove(const bt_addr_le_t *addr); + +/** + * @brief Clear filter accept list. + * + * Clear all devices from the filter accept list. + * + * @note The filter accept list cannot be modified when an LE role is using + * the filter accept list, i.e advertiser or scanner using a filter accept + * list or automatic connecting to devices using filter accept list. + * + * @return Zero on success or error code otherwise, positive in case of + * protocol error or negative (POSIX) in case of stack internal error. + */ + +int bt_le_filter_accept_list_clear(void); + +/** + * @brief Set (LE) channel map. + * + * Used to inform the Controller of known channel classifications. The Host + * can specify which channels are bad or unknown by setting the corresponding + * bit in the channel map to respectively 0 or 1. + * + * @note The interval between two successive calls to this function must be + * at least one second. + * + * @param chan_map Channel map. 5 octets where each bit represents a channel. + * Only the lower 37 bits are valid. + * + * @return Zero on success or error code otherwise, positive in case of + * protocol error or negative (POSIX) in case of stack internal error. + */ + +int bt_le_set_chan_map(uint8_t chan_map[5]); + +/** + * @brief Set the Resolvable Private Address timeout in runtime + * + * The new RPA timeout value will be used for the next RPA rotation and all + * subsequent rotations until another override is scheduled with this API. + * + * Initially, @kconfig{CONFIG_BT_RPA_TIMEOUT} is used as the RPA timeout. + * + * @kconfig_dep{CONFIG_BT_RPA_TIMEOUT_DYNAMIC}. + * + * @param new_rpa_timeout Resolvable Private Address timeout in seconds. + * + * @retval 0 Success. + * @retval -EINVAL RPA timeout value is invalid. Valid range is 1s - 3600s. + */ + +int bt_le_set_rpa_timeout(uint16_t new_rpa_timeout); + +/** + * @brief Helper for parsing advertising (or EIR or OOB) data. + * + * A helper for parsing the basic AD Types used for Extended Inquiry Response + * (EIR), Advertising Data (AD), and OOB data blocks. The most common + * scenario is to call this helper on the advertising data received in the + * callback that was given to @ref bt_le_scan_start. + * + * @warning This helper function will consume @p ad when parsing. The user + * should make a copy if the original data is to be used afterwards. This can + * be done by using + * @ref net_buf_simple_save to store the state prior to the function + * call, and then using + * @ref net_buf_simple_restore to restore the state afterwards. + * + * @param ad Advertising data as given to the @ref bt_le_scan_cb_t + * callback. + * @param func Callback function which will be called for each element + * that's found in the data. The callback should return true + * to continue parsing, or false to stop parsing. + * @param user_data User data to be passed to the callback. + */ + +void bt_data_parse(struct net_buf_simple *ad, + bool (*func)(struct bt_data *data, void *user_data), + void *user_data); + +/** LE Secure Connections pairing Out of Band data. */ + +struct bt_le_oob_sc_data +{ + /** Random Number. */ + + uint8_t r[16]; + + /** Confirm Value. */ + + uint8_t c[16]; +}; + +/** LE Out of Band information. */ + +struct bt_le_oob +{ + /** LE address. If privacy is enabled this is a Resolvable Private Address. + */ + + bt_addr_le_t addr; + + /** LE Secure Connections pairing Out of Band data. */ + + struct bt_le_oob_sc_data le_sc_data; +}; + +/** + * @brief Get local LE Out of Band (OOB) information. + * + * This function allows to get local information that are useful for Out of + * Band pairing or connection creation. + * + * If privacy @kconfig{CONFIG_BT_PRIVACY} is enabled this will result in + * generating new Resolvable Private Address (RPA) that is valid for + * @kconfig{CONFIG_BT_RPA_TIMEOUT} seconds. This address will be used for + * advertising started by @ref bt_le_adv_start, active scanning and + * connection creation. + * + * @note If privacy is enabled the RPA cannot be refreshed in the following + * cases: + * - Creating a connection in progress, wait for the connected + * callback. In addition when extended advertising + * @kconfig{CONFIG_BT_EXT_ADV} is not enabled or not supported by the + * controller: + * - Advertiser is enabled using a Random Static Identity Address as a + * different local identity address. + * - The local identity address conflicts with the local identity + * address used by other roles. + * + * @note This function randomly generates cryptographic material used in the + * pairing process, and should be called again for each new pairing process. + * + * @param[in] id Local identity handle (typically @ref BT_ID_DEFAULT). + * Corresponds to the identity address this function will be + * called for. + * @param[out] oob LE OOB information + * + * @return Zero on success or error code otherwise, positive in case of + * protocol error or negative (POSIX) in case of stack internal error. + */ + +int bt_le_oob_get_local(uint8_t id, struct bt_le_oob *oob); + +/** + * @brief Get local LE Out of Band (OOB) information. + * + * This function allows to get local information that are useful for Out of + * Band pairing or connection creation. + * + * If privacy @kconfig{CONFIG_BT_PRIVACY} is enabled this will result in + * generating new Resolvable Private Address (RPA) that is valid for + * @kconfig{CONFIG_BT_RPA_TIMEOUT} seconds. This address will be used by the + * advertising set. + * + * @note When generating OOB information for multiple advertising set all + * OOB information needs to be generated at the same time. + * + * @note If privacy is enabled the RPA cannot be refreshed in the following + * cases: + * - Creating a connection in progress, wait for the connected + * callback. + * + * @note This function randomly generates cryptographic material used in the + * pairing process, and should be called again for each new pairing process. + * + * @param[in] adv The advertising set object + * @param[out] oob LE OOB information + * + * @return Zero on success or error code otherwise, positive in case + * of protocol error or negative (POSIX) in case of stack internal error. + */ + +int bt_le_ext_adv_oob_get_local(struct bt_le_ext_adv *adv, + struct bt_le_oob *oob); + +/** + * @brief Clear pairing information. + * + * @param id Local identity handle (typically @ref BT_ID_DEFAULT). + * Corresponds to the identity address this function will be + * called for. + * @param addr Remote address, NULL or BT_ADDR_LE_ANY to clear all remote + * devices. + * + * @return 0 on success or negative error value on failure. + */ + +int bt_unpair(uint8_t id, const bt_addr_le_t *addr); + +/** Information about a bond with a remote device. */ + +struct bt_bond_info +{ + /** Address of the remote device. */ + + bt_addr_le_t addr; +}; + +/** + * @brief Iterate through all existing bonds. + * + * @param id Local identity handle (typically @ref BT_ID_DEFAULT). + * Corresponds to the identity address used in iteration. + * @param func Function to call for each bond. + * @param user_data Data to pass to the callback function. + */ + +void bt_foreach_bond( + uint8_t id, void (*func)(const struct bt_bond_info *info, void *user_data), + void *user_data); + +/** A bonded peer's support for address resolution. */ + +enum bt_le_addr_res_support +{ + /** The peer has not been asked, or the answer could not be read. */ + + BT_LE_ADDR_RES_SUPPORT_UNKNOWN, + + /** The peer does not support address resolution. */ + + BT_LE_ADDR_RES_SUPPORT_NO, +/* The peer supports address resolution. + * + * Support is a static capability: it does not guarantee that the peer has + * address resolution enabled at any given moment. + */ + + BT_LE_ADDR_RES_SUPPORT_YES, +}; + +/** + * @brief Get a bonded peer's support for address resolution. + * + * A peer that does not support address resolution is unable to resolve the + * target address of a directed advertisement, i.e. it can only be reached by + * directed advertising that does not use @ref BT_LE_ADV_OPT_DIR_ADDR_RPA. + * + * The answer is the value of the peer's Central Address Resolution + * characteristic, which the host reads when the bond is created if + * @kconfig{CONFIG_BT_GATT_AUTO_READ_CENTRAL_ADDR_RES} is enabled, with a new + * attempt on later connections to the peer as long as the answer is unknown. + * It is unknown when that option is disabled, when there is no bond with the + * peer, or when the characteristic has not been read yet. The completion of + * the automatic read is signalled through + * @ref bt_conn_auth_info_cb.addr_res_support_read. + * + * @param id Local identity handle (typically @ref BT_ID_DEFAULT). + * @param peer Identity address of the bonded peer. + * + * @return The peer's support for address resolution. + */ + +enum bt_le_addr_res_support bt_le_bond_addr_res_support(uint8_t id, + const bt_addr_le_t *peer); + +/** + * @brief Configure vendor data path + * + * @details Request the Controller to configure the data transport path in a + * given direction between the Controller and the Host. + * + * @param dir Direction to be configured, + * BT_HCI_DATAPATH_DIR_HOST_TO_CTLR or + * BT_HCI_DATAPATH_DIR_CTLR_TO_HOST + * @param id Vendor specific logical transport channel ID, range + * [BT_HCI_DATAPATH_ID_VS..BT_HCI_DATAPATH_ID_VS_END] + * @param vs_config_len Length of additional vendor specific configuration + * data + * @param vs_config Pointer to additional vendor specific configuration + * data + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_configure_data_path(uint8_t dir, uint8_t id, uint8_t vs_config_len, + const uint8_t *vs_config); + +/** + * @brief Parameters for synchronizing with specific periodic advertising + * subevents. + * + * @details This struct contains the parameters used to synchronize with a + * subset of subevents in periodic advertising. It includes the periodic + * advertising properties, the number of subevents to sync to, and the list + * of subevents that the controller should synchronize with. + * + * @note Used in @ref bt_le_per_adv_sync_subevent function to control + * synchronization. + */ + +struct bt_le_per_adv_sync_subevent_params +{ + /** @brief Periodic Advertising Properties. + * + * Bit 6 is include TxPower, all others RFU. + * + */ + + uint16_t properties; + + /** Number of subevents to sync to */ + + uint8_t num_subevents; + + /** @brief The subevent(s) to synchronize with + * + * The array must have @ref num_subevents elements. + * + */ + + uint8_t *subevents; +}; + +/** @brief Synchronize with a subset of subevents + * + * Until this command is issued, the subevent(s) the controller is + * synchronized to is unspecified. + * + * @param per_adv_sync The periodic advertising sync object. + * @param params Subevent sync parameters. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_le_per_adv_sync_subevent(struct bt_le_per_adv_sync *per_adv_sync, + struct bt_le_per_adv_sync_subevent_params *params); + +/** + * @brief Parameters for sending a periodic advertising response. + * + * @details This struct contains the parameters used when sending a response + * to a periodic advertising request (see @ref + * bt_le_per_adv_set_response_data function). The response is sent in the + * specified subevent and response slot, and includes event and subevent + * counters to track the request the response corresponds to. + */ + +struct bt_le_per_adv_response_params +{ + /* @brief The periodic event counter of the request the response is sent + * to. + * + * @ref bt_le_per_adv_sync_recv_info + * + * @note The response can be sent up to one periodic interval after + * the request was received. + * + */ + + uint16_t request_event; + + /* @brief The subevent counter of the request the response is sent to. + * + * @ref bt_le_per_adv_sync_recv_info + * + */ + + uint8_t request_subevent; + + /** The subevent the response shall be sent in */ + + uint8_t response_subevent; + + /** The response slot the response shall be sent in */ + + uint8_t response_slot; +}; + +/** + * @brief Set the data for a response slot in a specific subevent of the + * PAwR. + * + * This function is called by the application to set the response data. The + * data for a response slot shall be transmitted only once. + * + * @param per_adv_sync The periodic advertising sync object. + * @param params Parameters. + * @param data The response data to send. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_le_per_adv_set_response_data(struct bt_le_per_adv_sync *per_adv_sync, + const struct bt_le_per_adv_response_params *params, + const struct net_buf_simple *data); + +/** + * @brief Check if a device identified by a Bluetooth LE address is bonded. + * + * @details Valid Bluetooth LE identity addresses are either public address + * or random static address. + * + * @param id Local identity handle (typically @ref BT_ID_DEFAULT). + * Corresponds to the identity address this function will be + * called for. + * @param addr Bluetooth LE device address. + * + * @return true if @p addr is bonded with local @p id + */ + +bool bt_le_bond_exists(uint8_t id, const bt_addr_le_t *addr); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_BLUETOOTH_H */ diff --git a/include/nuttx/net/bluetooth/buf.h b/include/nuttx/net/bluetooth/buf.h new file mode 100644 index 0000000000000..a7acb198c65c4 --- /dev/null +++ b/include/nuttx/net/bluetooth/buf.h @@ -0,0 +1,308 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/buf.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Bluetooth data buffer API + */ + +/* Copyright (c) 2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_BUF_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_BUF_H + +/** + * @brief Data buffers + * @defgroup bt_buf Data buffers + * @since 1.7 + * @version 1.0.0 + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Possible types of buffers passed around the Bluetooth stack in a form of + * bitmask. + */ + +enum bt_buf_type +{ + /** Unknown/invalid packet type, used for error checking */ + + BT_BUF_TYPE_NONE = 0, + + /** HCI command */ + + BT_BUF_CMD = BIT(0), + + /** HCI event */ + + BT_BUF_EVT = BIT(1), + + /** Outgoing ACL data */ + + BT_BUF_ACL_OUT = BIT(2), + + /** Incoming ACL data */ + + BT_BUF_ACL_IN = BIT(3), + + /** Outgoing ISO data */ + + BT_BUF_ISO_OUT = BIT(4), + + /** Incoming ISO data */ + + BT_BUF_ISO_IN = BIT(5), +}; + +/** Direction of HCI packets. Only used for mapping H:4 to BT_BUF_* values. */ + +enum bt_buf_dir +{ + /** Packet from the controller to the host */ + + BT_BUF_IN, + + /** Packet from the host to the controller */ + + BT_BUF_OUT, +}; + +/** Convert from bt_buf_type to H:4 type. + * + * @param type The bt_buf_type to convert + * @return The H:4 type + */ + +static inline uint8_t bt_buf_type_to_h4(enum bt_buf_type type) +{ + switch (type) + { + case BT_BUF_CMD: + return BT_HCI_H4_CMD; + case BT_BUF_ACL_IN: + case BT_BUF_ACL_OUT: + return BT_HCI_H4_ACL; + case BT_BUF_ISO_IN: + case BT_BUF_ISO_OUT: + return BT_HCI_H4_ISO; + case BT_BUF_EVT: + return BT_HCI_H4_EVT; + default: + DEBUGASSERT(false); + return 0; + } +} + +/** Convert from H:4 type to bt_buf_type. + * + * @param h4_type The H:4 type to convert + * @param dir The direction of the packet + * @return The bt_buf_type + */ + +static inline enum bt_buf_type bt_buf_type_from_h4(uint8_t h4_type, + enum bt_buf_dir dir) +{ + switch (h4_type) + { + case BT_HCI_H4_CMD: + return BT_BUF_CMD; + case BT_HCI_H4_ACL: + return dir == BT_BUF_OUT ? BT_BUF_ACL_OUT : BT_BUF_ACL_IN; + case BT_HCI_H4_EVT: + return BT_BUF_EVT; + case BT_HCI_H4_ISO: + return dir == BT_BUF_OUT ? BT_BUF_ISO_OUT : BT_BUF_ISO_IN; + default: + return BT_BUF_TYPE_NONE; + } +} + +/* Headroom reserved in buffers, primarily for HCI transport encoding + * purposes + */ +#define BT_BUF_RESERVE 1 + +/** Helper to include reserved HCI data in buffer calculations */ +#define BT_BUF_SIZE(size) (BT_BUF_RESERVE + (size)) + +/** Helper to calculate needed buffer size for HCI ACL packets */ +#define BT_BUF_ACL_SIZE(size) BT_BUF_SIZE(BT_HCI_ACL_HDR_SIZE + (size)) + +/** Helper to calculate needed buffer size for HCI Event packets. */ +#define BT_BUF_EVT_SIZE(size) BT_BUF_SIZE(BT_HCI_EVT_HDR_SIZE + (size)) + +/** Helper to calculate needed buffer size for HCI Command packets. */ +#define BT_BUF_CMD_SIZE(size) BT_BUF_SIZE(BT_HCI_CMD_HDR_SIZE + (size)) + +/** Helper to calculate needed buffer size for HCI ISO packets. */ +#define BT_BUF_ISO_SIZE(size) BT_BUF_SIZE(BT_HCI_ISO_HDR_SIZE + BT_HCI_ISO_SDU_TS_HDR_SIZE + (size)) + +/** Data size needed for HCI ACL RX buffers */ +#define BT_BUF_ACL_RX_SIZE BT_BUF_ACL_SIZE(CONFIG_BT_BUF_ACL_RX_SIZE) + +/** Data size needed for HCI Event RX buffers */ +#define BT_BUF_EVT_RX_SIZE BT_BUF_EVT_SIZE(CONFIG_BT_BUF_EVT_RX_SIZE) + +#if defined(CONFIG_BT_ISO) +#define BT_BUF_ISO_RX_SIZE BT_BUF_ISO_SIZE(CONFIG_BT_ISO_RX_MTU) +#define BT_BUF_ISO_RX_COUNT CONFIG_BT_ISO_RX_BUF_COUNT +#else +#define BT_BUF_ISO_RX_SIZE 0 +#define BT_BUF_ISO_RX_COUNT 0 +#endif /* CONFIG_BT_ISO */ + +/* see Core Spec v6.0 vol.4 part E 7.4.5 */ +#define BT_BUF_ACL_RX_COUNT_MAX 65535 + +#if defined(CONFIG_BT_CONN) && defined(CONFIG_BT_HCI_HOST) +/* The host needs more ACL buffers than maximum ACL links. This is because of + * the way we re-assemble ACL packets into L2CAP PDUs. + * + * We keep around the first buffer (that comes from the driver) to do + * re-assembly into, and if all links are re-assembling, there will be no + * buffer available for the HCI driver to allocate from. + */ +#define BT_BUF_ACL_RX_COUNT_EXTRA CONFIG_BT_BUF_ACL_RX_COUNT_EXTRA +#define BT_BUF_ACL_RX_COUNT (1 + BT_BUF_ACL_RX_COUNT_EXTRA) +#else +#define BT_BUF_ACL_RX_COUNT_EXTRA 0 +#define BT_BUF_ACL_RX_COUNT 0 +#endif /* CONFIG_BT_CONN && CONFIG_BT_HCI_HOST */ + +BUILD_ASSERT(BT_BUF_ACL_RX_COUNT <= BT_BUF_ACL_RX_COUNT_MAX, + "Maximum number of ACL RX buffer is 65535, reduce " + "CONFIG_BT_BUF_ACL_RX_COUNT_EXTRA"); + +/** Data size needed for HCI ACL, HCI ISO or Event RX buffers */ +#define BT_BUF_RX_SIZE (MAX(MAX(BT_BUF_ACL_RX_SIZE, BT_BUF_EVT_RX_SIZE), BT_BUF_ISO_RX_SIZE)) + +/* Controller can generate up to CONFIG_BT_BUF_ACL_TX_COUNT number of unique + * HCI Number of Completed Packets events. + */ + +BUILD_ASSERT(CONFIG_BT_BUF_EVT_RX_COUNT > CONFIG_BT_BUF_ACL_TX_COUNT, + "Increase Event RX buffer count to be greater than ACL TX " + "buffer count"); + +/** Buffer count needed for HCI ACL or HCI ISO plus Event RX buffers */ +#define BT_BUF_RX_COUNT (CONFIG_BT_BUF_EVT_RX_COUNT + MAX(BT_BUF_ACL_RX_COUNT, BT_BUF_ISO_RX_COUNT)) + +/** Data size needed for HCI Command buffers. */ +#define BT_BUF_CMD_TX_SIZE BT_BUF_CMD_SIZE(CONFIG_BT_BUF_CMD_TX_SIZE) + +/** Allocate a buffer for incoming data + * + * This will set the buffer type so it doesn't need to be explicitly encoded + * into the buffer. + * + * @param type Type of buffer. Only BT_BUF_EVT, BT_BUF_ACL_IN and + * BT_BUF_ISO_IN are allowed. + * @param timeout Non-negative waiting period to obtain a buffer or one of + * the special values 0 and ((clock_t)-1). + * @return A new buffer. + */ + +struct net_buf *bt_buf_get_rx(enum bt_buf_type type, clock_t timeout); + +/** A callback to notify about freed buffer in the incoming data pool. + * + * This callback is called when a buffer of a given type is freed and can be + * requested through the + * @ref bt_buf_get_rx function. However, this callback is called from the + * context of the buffer freeing operation and must not attempt to allocate a + * new buffer from the same pool. + * + * This callback must only be used for very short non-blocking operations + * (e.g. submitting a work item). When called from thread context, the + * scheduler is locked during execution and the callee must not perform any + * action that makes the current thread unready. When called from ISR + * context, the callback runs without scheduler lock. + * + * @isr_ok + * + * @param type_mask A bit mask of buffer types that have been freed. + */ + +typedef void (*bt_buf_rx_freed_cb_t)(enum bt_buf_type type_mask); + +/** Set the callback to notify about freed buffer in the incoming data pool. + * + * It's safe to call this inside the callback itself. + * + * @isr_ok + * + * @param cb Callback to notify about freed buffer in the incoming data pool. + * If NULL, the callback is disabled. + */ + +void bt_buf_rx_freed_cb_set(bt_buf_rx_freed_cb_t cb); + +/** Allocate a buffer for outgoing data + * + * This will set the buffer type so it doesn't need to be explicitly encoded + * into the buffer. + * + * @param type Type of buffer. BT_BUF_CMD or BT_BUF_ACL_OUT. + * @param timeout Non-negative waiting period to obtain a buffer or one of + * the special values 0 and ((clock_t)-1). + * @param data Initial data to append to buffer. This is optional and can + * be NULL. + * @param size Initial data size. + * @return A new buffer. + */ + +struct net_buf *bt_buf_get_tx( + enum bt_buf_type type, clock_t timeout, const void *data, size_t size); + +/** Allocate a buffer for an HCI Event + * + * This will set the buffer type so it doesn't need to be explicitly encoded + * into the buffer. + * + * @param evt HCI event code + * @param discardable Whether the driver considers the event discardable. + * @param timeout Non-negative waiting period to obtain a buffer or one + * of the special values 0 and ((clock_t)-1). + * @return A new buffer. + */ + +struct net_buf *bt_buf_get_evt(uint8_t evt, + bool discardable, clock_t timeout); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_BUF_H */ diff --git a/include/nuttx/net/bluetooth/byteorder.h b/include/nuttx/net/bluetooth/byteorder.h new file mode 100644 index 0000000000000..99c029e3a8d51 --- /dev/null +++ b/include/nuttx/net/bluetooth/byteorder.h @@ -0,0 +1,186 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/byteorder.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Bluetooth byteorder API + */ + +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_BYTEORDER_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_BYTEORDER_H + +/** + * @brief Byteorder + * @defgroup bt_byteorder Byteorder + * @since 3.4 + * @version 1.0.0 + * @ingroup bluetooth + * @{ + */ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** @brief Encode 16-bit value into array values in little-endian format. + * + * Helper macro to encode 16-bit values into comma separated values. + * + * @note @p _v is evaluated 2 times. + * + * @param _v 16-bit integer in host endianness. + * + * @return The comma separated values for the 16-bit value. + */ +#define BT_BYTES_LIST_LE16(_v) (((_v) >> 0) & 0xFFU), (((_v) >> 8) & 0xFFU) + +/** @brief Encode 24-bit value into array values in little-endian format. + * + * Helper macro to encode 24-bit values into comma separated values. + * + * @note @p _v is evaluated 3 times. + * + * @param _v 24-bit integer in host endianness. + * + * @return The comma separated values for the 24-bit value. + */ +#define BT_BYTES_LIST_LE24(_v) BT_BYTES_LIST_LE16(_v), (((_v) >> 16) & 0xFFU) + +/** @brief Encode 32-bit value into array values in little-endian format. + * + * Helper macro to encode 32-bit values into comma separated values. + * + * @note @p _v is evaluated 4 times. + * + * @param _v 32-bit integer in host endianness. + * + * @return The comma separated values for the 32-bit value. + */ +#define BT_BYTES_LIST_LE32(_v) BT_BYTES_LIST_LE24(_v), (((_v) >> 24) & 0xFFU) + +/** @brief Encode 40-bit value into array values in little-endian format. + * + * Helper macro to encode 40-bit values into comma separated values. + * + * @note @p _v is evaluated 5 times. + * + * @param _v 40-bit integer in host endianness. + * + * @return The comma separated values for the 40-bit value. + */ +#define BT_BYTES_LIST_LE40(_v) BT_BYTES_LIST_LE24(_v), BT_BYTES_LIST_LE16((_v) >> 24) + +/** @brief Encode 48-bit value into array values in little-endian format. + * + * Helper macro to encode 48-bit values into comma separated values. + * + * @note @p _v is evaluated 6 times. + * + * @param _v 48-bit integer in host endianness. + * + * @return The comma separated values for the 48-bit value. + */ +#define BT_BYTES_LIST_LE48(_v) BT_BYTES_LIST_LE32(_v), BT_BYTES_LIST_LE16((_v) >> 32) + +/** @brief Encode 64-bit value into array values in little-endian format. + * + * Helper macro to encode 64-bit values into comma separated values. + * + * @note @p _v is evaluated 8 times. + * + * @param _v 64-bit integer in host endianness. + * + * @return The comma separated values for the 64-bit value. + */ +#define BT_BYTES_LIST_LE64(_v) BT_BYTES_LIST_LE32(_v), BT_BYTES_LIST_LE32((_v) >> 32) + +/** @brief Encode 16-bit value into array values in big-endian format. + * + * Helper macro to encode 16-bit values into comma separated values. + * + * @note @p _v is evaluated 2 times. + * + * @param _v 16-bit integer in host endianness. + * + * @return The comma separated values for the 16-bit value. + */ +#define BT_BYTES_LIST_BE16(_v) (((_v) >> 8) & 0xFFU), (((_v) >> 0) & 0xFFU) + +/** @brief Encode 24-bit value into array values in big-endian format. + * + * Helper macro to encode 24-bit values into comma separated values. + * + * @note @p _v is evaluated 3 times. + * + * @param _v 24-bit integer in host endianness. + * + * @return The comma separated values for the 24-bit value. + */ +#define BT_BYTES_LIST_BE24(_v) (((_v) >> 16) & 0xFFU), BT_BYTES_LIST_BE16(_v) + +/** @brief Encode 32-bit value into array values in big-endian format. + * + * Helper macro to encode 32-bit values into comma separated values. + * + * @note @p _v is evaluated 4 times. + * + * @param _v 32-bit integer in host endianness. + * + * @return The comma separated values for the 32-bit value. + */ +#define BT_BYTES_LIST_BE32(_v) (((_v) >> 24) & 0xFFU), BT_BYTES_LIST_BE24(_v) + +/** @brief Encode 40-bit value into array values in big-endian format. + * + * Helper macro to encode 40-bit values into comma separated values. + * + * @note @p _v is evaluated 5 times. + * + * @param _v 40-bit integer in host endianness. + * + * @return The comma separated values for the 40-bit value. + */ +#define BT_BYTES_LIST_BE40(_v) BT_BYTES_LIST_BE16((_v) >> 24), BT_BYTES_LIST_BE24(_v) + +/** @brief Encode 48-bit value into array values in big-endian format. + * + * Helper macro to encode 48-bit values into comma separated values. + * + * @note @p _v is evaluated 6 times. + * + * @param _v 48-bit integer in host endianness. + * + * @return The comma separated values for the 48-bit value. + */ +#define BT_BYTES_LIST_BE48(_v) BT_BYTES_LIST_BE16((_v) >> 32), BT_BYTES_LIST_BE32(_v) + +/** @brief Encode 64-bit value into array values in big-endian format. + * + * Helper macro to encode 64-bit values into comma separated values. + * + * @note @p _v is evaluated 8 times. + * + * @param _v 64-bit integer in host endianness. + * + * @return The comma separated values for the 64-bit value. + */ +#define BT_BYTES_LIST_BE64(_v) BT_BYTES_LIST_BE32((_v) >> 32), BT_BYTES_LIST_BE32(_v) + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_BYTEORDER_H */ diff --git a/include/nuttx/net/bluetooth/classic/a2dp.h b/include/nuttx/net/bluetooth/classic/a2dp.h new file mode 100644 index 0000000000000..abaf4143cb3c2 --- /dev/null +++ b/include/nuttx/net/bluetooth/classic/a2dp.h @@ -0,0 +1,1187 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/classic/a2dp.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Advanced Audio Distribution Profile header. + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_A2DP_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_A2DP_H + +/** + * @file + * @brief Advanced Audio Distribution Profile (A2DP) + * @defgroup bt_a2dp Advanced Audio Distribution Profile (A2DP) + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** SBC IE length */ +#define BT_A2DP_SBC_IE_LENGTH (4U) +/** MPEG1,2 IE length */ +#define BT_A2DP_MPEG_1_2_IE_LENGTH (4U) +/** MPEG2,4 IE length */ +#define BT_A2DP_MPEG_2_4_IE_LENGTH (6U) + +/** @brief define the audio endpoint + * @param _role BT_AVDTP_SOURCE or BT_AVDTP_SINK. + * @param _codec value of enum bt_a2dp_codec_id. + * @param _capability the codec capability. + * @param _delay_report delay report capability + */ +#define BT_A2DP_EP_INIT(_role, _codec, _capability, _delay_report) \ +{ \ + .codec_type = _codec, \ + .sep = {.sep_info = {.media_type = BT_AVDTP_AUDIO, .tsep = _role}}, \ + .codec_cap = _capability, \ + .stream = NULL, \ + .delay_report = _delay_report, \ +} + +/** @brief define the audio sink endpoint + * @param _codec value of enum bt_a2dp_codec_id. + * @param _capability the codec capability. + * @param _delay_report delay report capability + */ +#define BT_A2DP_SINK_EP_INIT(_codec, _capability, _delay_report) \ +BT_A2DP_EP_INIT(BT_AVDTP_SINK, _codec, _capability, _delay_report) + +/** @brief define the audio source endpoint + * @param _codec value of enum bt_a2dp_codec_id. + * @param _capability the codec capability. + * @param _delay_report delay report capability + */ +#define BT_A2DP_SOURCE_EP_INIT(_codec, _capability, _delay_report) \ +BT_A2DP_EP_INIT(BT_AVDTP_SOURCE, _codec, _capability, _delay_report) + +/** @brief define the SBC sink endpoint that can be used as + * bt_a2dp_register_ep's parameter. + * + * SBC is mandatory as a2dp specification, BT_A2DP_SBC_SINK_EP_DEFAULT is + * more convenient for user to register SBC endpoint. + * + * @param _name unique structure name postfix. + * @param _freq sbc codec frequency. for example: + * A2DP_SBC_SAMP_FREQ_44100 | A2DP_SBC_SAMP_FREQ_48000 + * @param _ch_mode sbc codec channel mode. for example: + * A2DP_SBC_CH_MODE_MONO | A2DP_SBC_CH_MODE_STEREO + * @param _blk_len sbc codec block length. for example: + * A2DP_SBC_BLK_LEN_16 + * @param _subband sbc codec subband. for example: A2DP_SBC_SUBBAND_8 + * @param _alloc_mthd sbc codec allocate method. for example: + * A2DP_SBC_ALLOC_MTHD_LOUDNESS + * @param _min_bitpool sbc codec min bit pool. for example: 18 + * @param _max_bitpool sbc codec max bit pool. for example: 35 + * @param _delay_report delay report capability + */ +#define BT_A2DP_SBC_SINK_EP(_name, _freq, _ch_mode, _blk_len, _subband, _alloc_mthd, _min_bitpool, \ + _max_bitpool, _delay_report) \ +static struct bt_a2dp_codec_ie bt_a2dp_ep_cap_ie##_name = { \ + .len = BT_A2DP_SBC_IE_LENGTH, \ + .codec_ie = {_freq | _ch_mode, _blk_len | _subband | _alloc_mthd, _min_bitpool, \ + _max_bitpool}}; \ +static struct bt_a2dp_ep _name = \ + BT_A2DP_SINK_EP_INIT(BT_A2DP_SBC, (&bt_a2dp_ep_cap_ie##_name), _delay_report) + +/** @brief define the SBC source endpoint that can be used as + * bt_a2dp_register_ep's parameter. + * + * SBC is mandatory as a2dp specification, BT_A2DP_SBC_SOURCE_EP_DEFAULT is + * more convenient for user to register SBC endpoint. + * + * @param _name the endpoint variable name. + * @param _freq sbc codec frequency. for example: + * A2DP_SBC_SAMP_FREQ_44100 | A2DP_SBC_SAMP_FREQ_48000 + * @param _ch_mode sbc codec channel mode. for example: + * A2DP_SBC_CH_MODE_MONO | A2DP_SBC_CH_MODE_STEREO + * @param _blk_len sbc codec block length. for example: + * A2DP_SBC_BLK_LEN_16 + * @param _subband sbc codec subband. for example: A2DP_SBC_SUBBAND_8 + * @param _alloc_mthd sbc codec allocate method. for example: + * A2DP_SBC_ALLOC_MTHD_LOUDNESS + * @param _min_bitpool sbc codec min bit pool. for example: 18 + * @param _max_bitpool sbc codec max bit pool. for example: 35 + * @param _delay_report delay report capability + */ +#define BT_A2DP_SBC_SOURCE_EP(_name, _freq, _ch_mode, _blk_len, _subband, _alloc_mthd, \ + _min_bitpool, _max_bitpool, _delay_report) \ +static struct bt_a2dp_codec_ie bt_a2dp_ep_cap_ie##_name = { \ + .len = BT_A2DP_SBC_IE_LENGTH, \ + .codec_ie = {_freq | _ch_mode, _blk_len | _subband | _alloc_mthd, _min_bitpool, \ + _max_bitpool}}; \ +static struct bt_a2dp_ep _name = \ + BT_A2DP_SOURCE_EP_INIT(BT_A2DP_SBC, &bt_a2dp_ep_cap_ie##_name, _delay_report) + +/** @brief define the default SBC sink endpoint that can be used as + * bt_a2dp_register_ep's parameter. + * + * SBC is mandatory as a2dp specification, BT_A2DP_SBC_SINK_EP_DEFAULT is + * more convenient for user to register SBC endpoint. + * + * @param _name the endpoint variable name. + */ +#define BT_A2DP_SBC_SINK_EP_DEFAULT(_name) \ +static struct bt_a2dp_codec_ie bt_a2dp_ep_cap_ie##_name = { \ + .len = BT_A2DP_SBC_IE_LENGTH, \ + .codec_ie = {A2DP_SBC_SAMP_FREQ_44100 | A2DP_SBC_SAMP_FREQ_48000 | \ + A2DP_SBC_CH_MODE_MONO | A2DP_SBC_CH_MODE_STEREO | \ + A2DP_SBC_CH_MODE_JOINT, \ + A2DP_SBC_BLK_LEN_16 | A2DP_SBC_SUBBAND_8 | A2DP_SBC_ALLOC_MTHD_LOUDNESS, \ + 18U, 35U}}; \ +static struct bt_a2dp_ep _name = \ + BT_A2DP_SINK_EP_INIT(BT_A2DP_SBC, &bt_a2dp_ep_cap_ie##_name, true) + +/** @brief define the default SBC source endpoint that can be used as + * bt_a2dp_register_ep's parameter. + * + * SBC is mandatory as a2dp specification, BT_A2DP_SBC_SOURCE_EP_DEFAULT is + * more convenient for user to register SBC endpoint. + * + * @param _name the endpoint variable name. + */ +#define BT_A2DP_SBC_SOURCE_EP_DEFAULT(_name) \ +static struct bt_a2dp_codec_ie bt_a2dp_ep_cap_ie##_name = { \ + .len = BT_A2DP_SBC_IE_LENGTH, \ + .codec_ie = {A2DP_SBC_SAMP_FREQ_44100 | A2DP_SBC_SAMP_FREQ_48000 | \ + A2DP_SBC_CH_MODE_MONO | A2DP_SBC_CH_MODE_STEREO | \ + A2DP_SBC_CH_MODE_JOINT, \ + A2DP_SBC_BLK_LEN_16 | A2DP_SBC_SUBBAND_8 | A2DP_SBC_ALLOC_MTHD_LOUDNESS, \ + 18U, 35U}, \ +}; \ +static struct bt_a2dp_ep _name = \ + BT_A2DP_SOURCE_EP_INIT(BT_A2DP_SBC, &bt_a2dp_ep_cap_ie##_name, false) + +/** @brief define the SBC default configuration. + * + * @param _name unique structure name postfix. + * @param _freq_cfg sbc codec frequency. for example: + * A2DP_SBC_SAMP_FREQ_44100 + * @param _ch_mode_cfg sbc codec channel mode. for example: + * A2DP_SBC_CH_MODE_JOINT + * @param _blk_len_cfg sbc codec block length. for example: + * A2DP_SBC_BLK_LEN_16 + * @param _subband_cfg sbc codec subband. for example: + * A2DP_SBC_SUBBAND_8 + * @param _alloc_mthd_cfg sbc codec allocate method. for example: + * A2DP_SBC_ALLOC_MTHD_LOUDNESS + * @param _min_bitpool_cfg sbc codec min bit pool. for example: 18 + * @param _max_bitpool_cfg sbc codec max bit pool. for example: 35 + */ +#define BT_A2DP_SBC_EP_CFG(_name, _freq_cfg, _ch_mode_cfg, _blk_len_cfg, _subband_cfg, \ + _alloc_mthd_cfg, _min_bitpool_cfg, _max_bitpool_cfg) \ +static struct bt_a2dp_codec_ie bt_a2dp_codec_ie##_name = { \ + .len = BT_A2DP_SBC_IE_LENGTH, \ + .codec_ie = {_freq_cfg | _ch_mode_cfg, _blk_len_cfg | _subband_cfg | _alloc_mthd_cfg, \ + _min_bitpool_cfg, _max_bitpool_cfg}, \ +}; \ +struct bt_a2dp_codec_cfg _name = { \ + .codec_config = &bt_a2dp_codec_ie##_name, \ +} + +/** @brief define the SBC default configuration. + * + * @param _name unique structure name postfix. + * @param _freq_cfg the frequency to configure the remote same codec type + * endpoint. + */ +#define BT_A2DP_SBC_EP_CFG_DEFAULT(_name, _freq_cfg) \ +static struct bt_a2dp_codec_ie bt_a2dp_codec_ie##_name = { \ + .len = BT_A2DP_SBC_IE_LENGTH, \ + .codec_ie = {_freq_cfg | A2DP_SBC_CH_MODE_JOINT, \ + A2DP_SBC_BLK_LEN_16 | A2DP_SBC_SUBBAND_8 | A2DP_SBC_ALLOC_MTHD_LOUDNESS, \ + 18U, 35U}, \ +}; \ +struct bt_a2dp_codec_cfg _name = { \ + .codec_config = &bt_a2dp_codec_ie##_name, \ +} + +/** + * @brief A2DP error code + */ + +enum bt_a2dp_err_code +{ + /** Media Codec Type is not valid */ + + BT_A2DP_INVALID_CODEC_TYPE = 0xc1, + + /** Media Codec Type is not supported */ + + BT_A2DP_NOT_SUPPORTED_CODEC_TYPE = 0xc2, + + /** Sampling Frequency is not valid or multiple values have been selected */ + + BT_A2DP_INVALID_SAMPLING_FREQUENCY = 0xc3, + + /** Sampling Frequency is not supported */ + + BT_A2DP_NOT_SUPPORTED_SAMPLING_FREQUENCY = 0xc4, + + /** Channel Mode is not valid or multiple values have been selected */ + + BT_A2DP_INVALID_CHANNEL_MODE = 0xc5, + + /** Channel Mode is not supported */ + + BT_A2DP_NOT_SUPPORTED_CHANNEL_MODE = 0xc6, + + /** None or multiple values have been selected for Number of Subbands */ + + BT_A2DP_INVALID_SUBBANDS = 0xc7, + + /** Number of Subbands is not supported */ + + BT_A2DP_NOT_SUPPORTED_SUBBANDS = 0xc8, + + /** None or multiple values have been selected for Allocation Method */ + + BT_A2DP_INVALID_ALLOCATION_METHOD = 0xc9, + + /** Allocation Method is not supported */ + + BT_A2DP_NOT_SUPPORTED_ALLOCATION_METHOD = 0xca, + + /** Minimum Bitpool Value is not valid */ + + BT_A2DP_INVALID_MINIMUM_BITPOOL_VALUE = 0xcb, + + /** Minimum Bitpool Value is not supported */ + + BT_A2DP_NOT_SUPPORTED_MINIMUM_BITPOOL_VALUE = 0xcc, + + /** Maximum Bitpool Value is not valid */ + + BT_A2DP_INVALID_MAXIMUM_BITPOOL_VALUE = 0xcd, + + /** Maximum Bitpool Value is not supported */ + + BT_A2DP_NOT_SUPPORTED_MAXIMUM_BITPOOL_VALUE = 0xce, + + /** None or multiple values have been selected for Layer */ + + BT_A2DP_INVALID_LAYER = 0xcf, + + /** Layer is not supported */ + + BT_A2DP_NOT_SUPPORTED_LAYER = 0xd0, + + /** CRC is not supported */ + + BT_A2DP_NOT_SUPPORTED_CRC = 0xd1, + + /** MPF-2 is not supported */ + + BT_A2DP_NOT_SUPPORTED_MPF = 0xd2, + + /** VBR is not supported */ + + BT_A2DP_NOT_SUPPORTED_VBR = 0xd3, + + /** None or multiple values have been selected for Bit Rate */ + + BT_A2DP_INVALID_BIT_RATE = 0xd4, + + /** Bit Rate is not supported */ + + BT_A2DP_NOT_SUPPORTED_BIT_RATE = 0xd5, + /** Either 1) Object type is not valid or 2) None or multiple values have + * been selected for Object Type + */ + + BT_A2DP_INVALID_OBJECT_TYPE = 0xd6, + + /** Object Type is not supported */ + + BT_A2DP_NOT_SUPPORTED_OBJECT_TYPE = 0xd7, + /** Either 1) Channels is not valid or 2) None or multiple values have been + * selected for Channels + */ + + BT_A2DP_INVALID_CHANNELS = 0xd8, + + /** Channels is not supported */ + + BT_A2DP_NOT_SUPPORTED_CHANNELS = 0xd9, + + /** Version is not valid */ + + BT_A2DP_INVALID_VERSION = 0xda, + + /** Version is not supported */ + + BT_A2DP_NOT_SUPPORTED_VERSION = 0xdb, + + /** Maximum SUL is not acceptable for the Decoder in the SNK */ + + BT_A2DP_NOT_SUPPORTED_MAXIMUM_SUL = 0xdc, + + /** None or multiple values have been selected for Block Length */ + + BT_A2DP_INVALID_BLOCK_LENGTH = 0xdd, + + /** The requested CP Type is not supported */ + + BT_A2DP_INVALID_CP_TYPE = 0xe0, + /** The format of Content Protection Service Capability/Content Protection + * Scheme Dependent Data is not correct + */ + + BT_A2DP_INVALID_CP_FORMAT = 0xe1, + /** The codec parameter is invalid. Used if a more specific error code does + * not exist for the codec in use + */ + + BT_A2DP_INVALID_CODEC_PARAMETER = 0xe2, + /** The codec parameter is not supported. Used if a more specific error + * code does not exist for the codec in use + */ + + BT_A2DP_NOT_SUPPORTED_CODEC_PARAMETER = 0xe3, + + /** Combination of Object Type and DRC is invalid */ + + BT_A2DP_INVALID_DRC = 0xe4, + + /** DRC is not supported */ + + BT_A2DP_NOT_SUPPORTED_DRC = 0xe5, +}; + +/** @brief Codec Type */ + +enum bt_a2dp_codec_type +{ + /** Codec SBC */ + + BT_A2DP_SBC = 0x00, + + /** Codec MPEG-1 */ + + BT_A2DP_MPEG1 = 0x01, + + /** Codec MPEG-2 */ + + BT_A2DP_MPEG2 = 0x02, + + /** Codec MPEG-D */ + + BT_A2DP_MPEGD = 0x03, + + /** Codec ATRAC */ + + BT_A2DP_ATRAC = 0x04, + + /** Codec Non-A2DP */ + + BT_A2DP_VENDOR = 0xff +}; + +/** + * @struct bt_a2dp + * @brief A2DP structure + */ + +struct bt_a2dp; + +/* Internal to pass build */ + +struct bt_a2dp_stream; + +/** @brief codec information elements for the endpoint */ + +struct bt_a2dp_codec_ie +{ + /** Length of codec_cap */ + + uint8_t len; + + /** codec information element */ + + uint8_t codec_ie[CONFIG_BT_A2DP_CODEC_MAX_IE_LEN]; +}; + +/** @brief The endpoint configuration */ + +struct bt_a2dp_codec_cfg +{ + /** the delay reporting configured state */ + + bool delay_report; + + /** The media codec configuration content */ + + struct bt_a2dp_codec_ie *codec_config; +}; + +/** @brief Stream End Point */ + +struct bt_a2dp_ep +{ + /** Code Type @ref bt_a2dp_codec_type */ + + uint8_t codec_type; + + /** Whether the endpoint has delay reporting service */ + + bool delay_report; + + /** Capabilities */ + + struct bt_a2dp_codec_ie *codec_cap; + + /** AVDTP Stream End Point Identifier */ + + struct bt_avdtp_sep sep; + + /* Internally used stream object pointer */ + + struct bt_a2dp_stream *stream; +}; + +struct bt_a2dp_ep_info +{ + /** Code Type @ref bt_a2dp_codec_type */ + + uint8_t codec_type; + + /** Whether the endpoint has delay reporting service */ + + bool delay_report; + + /** Codec capabilities, if SBC, use function of a2dp_codec_sbc.h to parse + * it + */ + + struct bt_a2dp_codec_ie codec_cap; + + /** Stream End Point Information */ + + struct bt_avdtp_sep_info *sep_info; +}; + +/** @brief Helper enum to be used as return value of bt_a2dp_discover_ep_cb. + * The value informs the caller to perform further pending actions or stop + * them. + */ + +enum +{ + BT_A2DP_DISCOVER_EP_STOP = 0, + BT_A2DP_DISCOVER_EP_CONTINUE, +}; + +/** @typedef bt_a2dp_discover_ep_cb + * + * @brief Called when a stream endpoint is discovered. + * + * A function of this type is given by the user to the + * bt_a2dp_discover_param object. It'll be called on each valid stream + * endpoint discovery completion. When no more endpoint then NULL is passed + * to the user. Otherwise user can get valid endpoint information from + * parameter info, user can set parameter ep to get the endpoint after the + * callback is return. The returned function value allows the user to + * control retrieving follow-up endpoints if any. If the user doesn't want + * to read more endpoints since current found endpoints fulfill its + * requirements then should return BT_A2DP_DISCOVER_EP_STOP. Otherwise + * returned value means more subcall iterations are allowable. + * + * @param a2dp a2dp connection object identifying a2dp connection to queried + * remote. + * @param info Object pointing to the information of the callbacked + * endpoint. + * @param ep If the user want to use this found endpoint, user can set + * value to it to get the endpoint that can be used further in + * other A2DP APIs. It is NULL if info is NULL (no more endpoint + * is found). + * + * @return BT_A2DP_DISCOVER_EP_STOP in case of no more need to continue + * discovery for next endpoint. By returning BT_A2DP_DISCOVER_EP_STOP user + * allows this discovery continuation. + */ + +typedef uint8_t (*bt_a2dp_discover_ep_cb)( + struct bt_a2dp *a2dp, struct bt_a2dp_ep_info *info, struct bt_a2dp_ep + **ep); + +struct bt_a2dp_discover_param +{ + /** discover callback */ + + bt_a2dp_discover_ep_cb cb; + + /** The discovered endpoint info that is callbacked by cb */ + + struct bt_a2dp_ep_info info; + /** The max count of remote endpoints that can be got, it save endpoint + * info internally. + */ + + struct bt_avdtp_sep_info *seps_info; + /** The AVDTP version of the peer's A2DP sdp service. + * Stack uses it to determine using get_all_cap or get_cap cmd. When both + * versions are v1.3 or bigger version, get_all_cap is used, otherwise + * get_cap is used. It is the same value of the avdtp sepcificaiton's + * version value. For example: 0x0103 means version 1.3 If the value is 0 + * (unknown), stack process it as less than v1.3 + */ + + uint16_t avdtp_version; + + /** The max count of seps (stream endpoint) that can be got in this call + * route + */ + + uint8_t sep_count; +}; + +/** @brief The connecting callback */ + +struct bt_a2dp_cb +{ + /** @brief A a2dp connection has been established. + * + * This callback notifies the application of a a2dp connection. It means + * the AVDTP L2CAP connection. In case the err parameter is non-zero it + * means that the connection establishment failed. + * + * @param a2dp a2dp connection object. + * @param err error code. + */ + + void (*connected)(struct bt_a2dp *a2dp, int err); + /** @brief A a2dp connection has been disconnected. + * + * This callback notifies the application that a a2dp connection has been + * disconnected. + * + * @param a2dp a2dp connection object. + */ + + void (*disconnected)(struct bt_a2dp *a2dp); + /** + * @brief Endpoint config request callback + * + * The callback is called whenever an endpoint is requested to be + * configured. + * + * @param a2dp a2dp connection object. + * @param[in] ep Local Audio Endpoint being configured. + * @param[in] codec_cfg Codec configuration. + * @param[out] stream Pointer to stream that will be configured for + * the endpoint. + * @param[out] rsp_err_code give the error code if response error. + * bt_a2dp_err_code or bt_avdtp_err_code + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*config_req)(struct bt_a2dp *a2dp, + struct bt_a2dp_ep *ep, + struct bt_a2dp_codec_cfg *codec_cfg, + struct bt_a2dp_stream **stream, + uint8_t *rsp_err_code); + /** + * @brief Endpoint config request callback + * + * The callback is called whenever an endpoint is requested to be + * reconfigured. + * + * @param[in] stream Pointer to stream object. + * @param[in] codec_cfg Codec configuration. + * @param[out] rsp_err_code give the error code if response error. + * bt_a2dp_err_code or bt_avdtp_err_code + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*reconfig_req)(struct bt_a2dp_stream *stream, + struct bt_a2dp_codec_cfg *codec_cfg, + uint8_t *rsp_err_code); + /** @brief Callback function for bt_a2dp_stream_config() and + * bt_a2dp_stream_reconfig() + * + * Called when the codec configure operation is completed. + * + * @param[in] stream Pointer to stream object. + * @param[in] rsp_err_code the remote responded error code + * bt_a2dp_err_code or bt_avdtp_err_code + */ + + void (*config_rsp)(struct bt_a2dp_stream *stream, uint8_t rsp_err_code); + /** + * @brief Stream establishment request callback + * + * The callback is called whenever an stream is requested to be established + * (open cmd and create the stream l2cap channel). + * + * @param[in] stream Pointer to stream object. + * @param[out] rsp_err_code give the error code if response error. + * bt_a2dp_err_code or bt_avdtp_err_code + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*establish_req)(struct bt_a2dp_stream *stream, uint8_t *rsp_err_code); + /** @brief Callback function for bt_a2dp_stream_establish() + * + * Called when the establishment operation is completed. (open cmd and + * create the stream l2cap channel). + * + * @param[in] stream Pointer to stream object. + * @param[in] rsp_err_code the remote responded error code + * bt_a2dp_err_code or bt_avdtp_err_code + */ + + void (*establish_rsp)(struct bt_a2dp_stream *stream, uint8_t rsp_err_code); + /** + * @brief Stream release request callback + * + * The callback is called whenever an stream is requested to be released + * (release cmd and release the l2cap channel) + * + * @param[in] stream Pointer to stream object. + * @param[out] rsp_err_code give the error code if response error. + * bt_a2dp_err_code or bt_avdtp_err_code + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*release_req)(struct bt_a2dp_stream *stream, uint8_t *rsp_err_code); + /** @brief Callback function for bt_a2dp_stream_release() + * + * Called when the release operation is completed. (release cmd and + * release the l2cap channel) + * + * @param[in] stream Pointer to stream object. + * @param[in] rsp_err_code the remote responded error code + * bt_a2dp_err_code or bt_avdtp_err_code + */ + + void (*release_rsp)(struct bt_a2dp_stream *stream, uint8_t rsp_err_code); + /** + * @brief Stream start request callback + * + * The callback is called whenever an stream is requested to be started. + * + * @param[in] stream Pointer to stream object. + * @param[out] rsp_err_code give the error code if response error. + * bt_a2dp_err_code or bt_avdtp_err_code + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*start_req)(struct bt_a2dp_stream *stream, uint8_t *rsp_err_code); + /** @brief Callback function for bt_a2dp_stream_start() + * + * Called when the start operation is completed. + * + * @param[in] stream Pointer to stream object. + * @param[in] rsp_err_code the remote responded error code + * bt_a2dp_err_code or bt_avdtp_err_code + */ + + void (*start_rsp)(struct bt_a2dp_stream *stream, uint8_t rsp_err_code); + /** + * @brief Stream suspend request callback + * + * The callback is called whenever an stream is requested to be suspended. + * + * @param[in] stream Pointer to stream object. + * @param[out] rsp_err_code give the error code if response error. + * bt_a2dp_err_code or bt_avdtp_err_code + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*suspend_req)(struct bt_a2dp_stream *stream, uint8_t *rsp_err_code); + /** @brief Callback function for bt_a2dp_stream_suspend() + * + * Called when the suspend operation is completed. + * + * @param[in] stream Pointer to stream object. + * @param[in] rsp_err_code the remote responded error code + * bt_a2dp_err_code or bt_avdtp_err_code + */ + + void (*suspend_rsp)(struct bt_a2dp_stream *stream, uint8_t rsp_err_code); + /** + * @brief Stream abort request callback + * + * The callback is called whenever an stream is requested to be aborted. + * + * @param[in] stream Pointer to stream object. + * @param[out] rsp_err_code give the error code if response error. + * bt_a2dp_err_code or bt_avdtp_err_code + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*abort_req)(struct bt_a2dp_stream *stream, uint8_t *rsp_err_code); + /** @brief Callback function for bt_a2dp_stream_abort() + * + * Called when the abort operation is completed. + * + * @param[in] stream Pointer to stream object. + * @param[in] rsp_err_code the remote responded error code + * bt_a2dp_err_code or bt_avdtp_err_code + */ + + void (*abort_rsp)(struct bt_a2dp_stream *stream, uint8_t rsp_err_code); + /** + * @brief Stream get config callback + * + * The callback is called whenever an stream is requested to response + * configured configuration. + * + * @param[in] stream Pointer to stream object. + * @param[out] rsp_err_code give the error code if response error. + * bt_a2dp_err_code or bt_avdtp_err_code + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*get_config_req)(struct bt_a2dp_stream *stream, + uint8_t *rsp_err_code); + /** @brief Callback function for bt_a2dp_stream_get_config() + * + * Called when the get configuration operation is completed. + * + * @param[in] stream Pointer to stream object. + * @param[in] codec_cfg the codec configuration that is got + * @param[in] rsp_err_code the remote responded error code + * bt_a2dp_err_code or bt_avdtp_err_code + */ + + void (*get_config_rsp)(struct bt_a2dp_stream *stream, + struct bt_a2dp_codec_cfg *codec_cfg, + uint8_t rsp_err_code); +#ifdef CONFIG_BT_A2DP_SOURCE + /** + * @brief Stream delay report is received + * + * The callback is called whenever an stream's delay report is received. + * + * @param[in] stream Pointer to stream object. + * @param[in] value The delay report value in 1/10 milliseconds. + * @param[out] rsp_err_code give the error code if response error. + * bt_a2dp_err_code or bt_avdtp_err_code + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*delay_report_req)(struct bt_a2dp_stream *stream, uint16_t value, + uint8_t *rsp_err_code); +#endif +#ifdef CONFIG_BT_A2DP_SINK + /** @brief Callback function for bt_a2dp_stream_delay_report() + * + * Called when the delay report sending is completed. + * + * @param[in] stream Pointer to stream object. + * @param[in] rsp_err_code the remote responded error code + * bt_a2dp_err_code or bt_avdtp_err_code + */ + + void (*delay_report_rsp)(struct bt_a2dp_stream *stream, + uint8_t rsp_err_code); +#endif +}; + +/** @brief A2DP Connect. + * + * This function is to be called after the conn parameter is obtained by + * performing a GAP procedure. The API is to be used to establish A2DP + * connection between devices. This function only establish AVDTP L2CAP + * Signaling connection. After connection success, the callback that is + * registered by bt_a2dp_register_connect_callback is called. + * + * @param conn Pointer to bt_conn structure. + * + * @return pointer to struct bt_a2dp in case of success or NULL in case + * of error. + */ + +struct bt_a2dp *bt_a2dp_connect(struct bt_conn *conn); + +/** @brief disconnect l2cap a2dp + * + * This function close AVDTP L2CAP Signaling connection. It closes the AVDTP + * L2CAP Media connection too if it is established. + * + * @param a2dp The a2dp instance. + * + * @return 0 in case of success and error code in case of error. + */ + +int bt_a2dp_disconnect(struct bt_a2dp *a2dp); + +/** @brief Endpoint Registration. + * + * @param ep Pointer to bt_a2dp_ep structure. + * @param media_type Media type that the Endpoint is, #bt_avdtp_media_type. + * @param sep_type Stream endpoint type, #bt_avdtp_sep_type. + * + * @return 0 in case of success and error code in case of error. + */ + +int bt_a2dp_register_ep(struct bt_a2dp_ep *ep, + uint8_t media_type, uint8_t sep_type); + +/** @brief register callback. + * + * The cb is called when bt_a2dp_connect is called or it is operated by + * remote device. + * + * @param cb The callback function. + * + * @return 0 in case of success and error code in case of error. + */ + +int bt_a2dp_register_cb(struct bt_a2dp_cb *cb); + +/** @brief Obtain the ACL connection corresponding to A2DP. + * + * @param a2dp The A2DP instance. + * + * @return Connection object associated with the A2DP context. The caller + * gets a new reference to the connection object which must be released with + * bt_conn_unref() once done using the object. + */ + +struct bt_conn *bt_a2dp_get_conn(struct bt_a2dp *a2dp); + +/** @brief Discover remote endpoints. + * + * @param a2dp The a2dp instance. + * @param param the discover used param. + * + * @return 0 in case of success and error code in case of error. + */ + +int bt_a2dp_discover(struct bt_a2dp *a2dp, + struct bt_a2dp_discover_param *param); + +/** @brief A2DP Stream */ + +struct bt_a2dp_stream +{ + /** local endpoint */ + + struct bt_a2dp_ep *local_ep; + + /** remote endpoint */ + + struct bt_a2dp_ep *remote_ep; + + /** remote endpoint's Stream End Point ID */ + + uint8_t remote_ep_id; + + /** whether the delay report is configured on the stream */ + + bool delay_report; + + /** Audio stream operations */ + + struct bt_a2dp_stream_ops *ops; + + /** the a2dp connection */ + + struct bt_a2dp *a2dp; + + /** the stream current configuration */ + + struct bt_a2dp_codec_ie codec_config; +}; + +/** @brief The stream endpoint related operations */ + +struct bt_a2dp_stream_ops +{ + /* @brief Stream configured callback + * + * The callback is called whenever an Audio Stream has been configured or + * reconfigured. + * + * @param stream Stream object that has been configured. + */ + + void (*configured)(struct bt_a2dp_stream *stream); + /* @brief Stream establishment callback + * + * The callback is called whenever an Audio Stream has been established. + * + * @param stream Stream object that has been established. + */ + + void (*established)(struct bt_a2dp_stream *stream); + /* @brief Stream release callback + * + * The release procedure (bt_a2dp_stream_release, release_req, release_rsp + * and disconnect l2cap media channel), the abort procedure + * (bt_a2dp_stream_abort, abort_req, abort_rsp and disconnect l2cap media + * channel) and the error procedure (for example, the l2cap channel + * disconnected unexpectedly) will cause the stream to be released. The + * callback is called whenever an Audio Stream has been released. After + * released, the stream becomes invalid to control. + * + * @param stream Stream object that has been released. + */ + + void (*released)(struct bt_a2dp_stream *stream); + /* @brief Stream start callback + * + * The callback is called whenever an Audio Stream has been started. + * + * @param stream Stream object that has been started. + */ + + void (*started)(struct bt_a2dp_stream *stream); + /* @brief Stream suspend callback + * + * The callback is called whenever an Audio Stream has been suspended. + * + * @param stream Stream object that has been suspended. + */ + + void (*suspended)(struct bt_a2dp_stream *stream); +#if defined(CONFIG_BT_A2DP_SINK) || defined(__DOXYGEN__) + /** @brief the media streaming data, only for sink + * + * @param stream the stream object + * @param buf the data buf + * @param seq_num the sequence number + * @param ts the time stamp + */ + + void (*recv)(struct bt_a2dp_stream *stream, struct net_buf *buf, + uint16_t seq_num, uint32_t ts); +#endif /* CONFIG_BT_A2DP_SINK */ +#if defined(CONFIG_BT_A2DP_SOURCE) || defined(__DOXYGEN__) + /* @brief Stream audio HCI sent callback + * + * This callback will be called once the controller marks the SDU as + * completed. When the controller does so is implementation dependent. It + * could be after the SDU is enqueued for transmission, or after it is sent + * on air or flushed. + * + * This callback is only used if the ISO data path is HCI. + * + * @param stream Stream object. + */ + + void (*sent)(struct bt_a2dp_stream *stream); + /* @brief The delay report value is received + * + * This callback will be called once delay report is replied with accept + * (If `delay_report_req` is not set or `delay_report_req` reply success). + * + * @param stream Stream object. + * @param value The delay report value in 1/10 milliseconds. + */ + + void (*delay_report)(struct bt_a2dp_stream *stream, uint16_t value); +#endif /* CONFIG_BT_A2DP_SOURCE */ +}; + +/** + * @brief Register Audio callbacks for a stream. + * + * Register Audio callbacks for a stream. + * + * @param stream Stream object. + * @param ops Stream operations structure. + */ + +void bt_a2dp_stream_cb_register(struct bt_a2dp_stream *stream, + struct bt_a2dp_stream_ops *ops); + +/** @brief configure endpoint. + * + * bt_a2dp_discover can be used to find remote's endpoints. This function to + * configure the selected endpoint that is found by bt_a2dp_discover. This + * function sends AVDTP_SET_CONFIGURATION. + * + * @param a2dp The a2dp instance. + * @param stream Stream object. + * @param local_ep The configured endpoint that is registered. + * @param remote_ep The remote endpoint. + * @param config The config to configure the endpoint. + * + * @return 0 in case of success and error code in case of error. + */ + +int bt_a2dp_stream_config( + struct bt_a2dp *a2dp, struct bt_a2dp_stream *stream, + struct bt_a2dp_ep *local_ep, struct bt_a2dp_ep *remote_ep, + struct bt_a2dp_codec_cfg *config); + +/** @brief get config of the stream + * + * This function sends the AVDTP_GET_CONFIGURATION command. + * + * @param stream The stream object. + * + * @return 0 in case of success and error code in case of error. + */ + +int bt_a2dp_stream_get_config(struct bt_a2dp_stream *stream); + +/** @brief establish a2dp streamer. + * + * This function sends the AVDTP_OPEN command and create the l2cap channel. + * + * @param stream The stream object. + * + * @return 0 in case of success and error code in case of error. + */ + +int bt_a2dp_stream_establish(struct bt_a2dp_stream *stream); + +/** @brief release a2dp streamer. + * + * This function sends the AVDTP_CLOSE command and release the l2cap channel. + * After release, the stream becomes invalid. + * + * @param stream The stream object. + * + * @return 0 in case of success and error code in case of error. + */ + +int bt_a2dp_stream_release(struct bt_a2dp_stream *stream); + +/** @brief start a2dp streamer. + * + * This function sends the AVDTP_START command. + * + * @param stream The stream object. + * + * @return 0 in case of success and error code in case of error. + */ + +int bt_a2dp_stream_start(struct bt_a2dp_stream *stream); + +/** @brief suspend a2dp streamer. + * + * This function sends the AVDTP_SUSPEND command. + * + * @param stream The stream object. + * + * @return 0 in case of success and error code in case of error. + */ + +int bt_a2dp_stream_suspend(struct bt_a2dp_stream *stream); + +/** @brief re-configure a2dp streamer + * + * This function sends the AVDTP_RECONFIGURE command. + * + * @param stream The stream object. + * @param config The config to configure the stream. + * + * @return 0 in case of success and error code in case of error. + */ + +int bt_a2dp_stream_reconfig(struct bt_a2dp_stream *stream, + struct bt_a2dp_codec_cfg *config); + +/** @brief abort a2dp streamer. + * + * This function sends the AVDTP_ABORT command. After abort, the stream + * becomes invalid. + * + * @param stream The stream object. + * + * @return 0 in case of success and error code in case of error. + */ + +int bt_a2dp_stream_abort(struct bt_a2dp_stream *stream); + +/** @brief get the stream l2cap mtu + * + * @param stream The stream object. + * + * @return mtu value + */ + +uint32_t bt_a2dp_get_mtu(struct bt_a2dp_stream *stream); + +/** @brief send a2dp media data + * + * Only A2DP source side can call this function. + * + * @param stream The stream object. + * @param buf The data. + * @param seq_num The sequence number. + * @param ts The time stamp. + * + * @return 0 in case of success and error code in case of error. + */ + +int bt_a2dp_stream_send( + struct bt_a2dp_stream *stream, struct net_buf *buf, uint16_t seq_num, + uint32_t ts); + +/** + * @brief Allocate a net_buf for bt_a2dp_stream_send + * + * This function allocates a buffer from the specified pool, reserves + * sufficient headroom for protocol headers required by L2CAP over Bluetooth, + * fills the AVDTP header. + * + * @param pool The buffer pool to allocate from. + * @param timeout Non-negative waiting period to obtain a buffer or one of + * the special values 0 and ((clock_t)-1). + * + * @return A newly allocated net_buf. + */ + +struct net_buf *bt_a2dp_stream_create_pdu(struct net_buf_pool *pool, + clock_t timeout); + +/** @brief send delay report + * + * Only A2DP sink side can call this function. + * + * @param stream The stream object. + * @param delay Value in 1/10 milliseconds. + * + * @return 0 in case of success and error code in case of error. + */ + +int bt_a2dp_stream_delay_report(struct bt_a2dp_stream *stream, + uint16_t delay); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_A2DP_H */ diff --git a/include/nuttx/net/bluetooth/classic/a2dp_codec_sbc.h b/include/nuttx/net/bluetooth/classic/a2dp_codec_sbc.h new file mode 100644 index 0000000000000..0371b31486023 --- /dev/null +++ b/include/nuttx/net/bluetooth/classic/a2dp_codec_sbc.h @@ -0,0 +1,189 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/classic/a2dp_codec_sbc.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Advance Audio Distribution Profile - SBC Codec header. + */ + +/* SPDX-License-Identifier: Apache-2.0 + * Copyright (c) 2015-2016 Intel Corporation Copyright (c) 2021 NXP + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_A2DP_CODEC_SBC_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_A2DP_CODEC_SBC_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Sampling Frequency */ +#define A2DP_SBC_SAMP_FREQ_16000 BIT(7) +#define A2DP_SBC_SAMP_FREQ_32000 BIT(6) +#define A2DP_SBC_SAMP_FREQ_44100 BIT(5) +#define A2DP_SBC_SAMP_FREQ_48000 BIT(4) + +/* Channel Mode */ +#define A2DP_SBC_CH_MODE_MONO BIT(3) +#define A2DP_SBC_CH_MODE_DUAL BIT(2) +#define A2DP_SBC_CH_MODE_STEREO BIT(1) +#define A2DP_SBC_CH_MODE_JOINT BIT(0) + +/* Block Length */ +#define A2DP_SBC_BLK_LEN_4 BIT(7) +#define A2DP_SBC_BLK_LEN_8 BIT(6) +#define A2DP_SBC_BLK_LEN_12 BIT(5) +#define A2DP_SBC_BLK_LEN_16 BIT(4) + +/* Subbands */ +#define A2DP_SBC_SUBBAND_4 BIT(3) +#define A2DP_SBC_SUBBAND_8 BIT(2) + +/* Allocation Method */ +#define A2DP_SBC_ALLOC_MTHD_SNR BIT(1) +#define A2DP_SBC_ALLOC_MTHD_LOUDNESS BIT(0) + +#define BT_A2DP_SBC_SAMP_FREQ(cap) ((cap->config[0] >> 4) & 0x0f) +#define BT_A2DP_SBC_CHAN_MODE(cap) ((cap->config[0]) & 0x0f) +#define BT_A2DP_SBC_BLK_LEN(cap) ((cap->config[1] >> 4) & 0x0f) +#define BT_A2DP_SBC_SUB_BAND(cap) ((cap->config[1] >> 2) & 0x03) +#define BT_A2DP_SBC_ALLOC_MTHD(cap) ((cap->config[1]) & 0x03) +#define BT_A2DP_SBC_MIN_BITPOOL_VALUE 2 +#define BT_A2DP_SBC_MAX_BITPOOL_VALUE 250 + +/** @brief SBC Codec */ + +struct bt_a2dp_codec_sbc_params +{ + /** First two octets of configuration */ + + uint8_t config[2]; + + /** Minimum Bitpool Value */ + + uint8_t min_bitpool; + + /** Maximum Bitpool Value */ + + uint8_t max_bitpool; +} __packed; + +/** If the F bit is set to 0, this field indicates the number of frames + * contained in this packet. If the F bit is set to 1, this field indicates + * the number of remaining fragments, including the current fragment. + * Therefore, the last counter value shall be one. + */ +#define BT_A2DP_SBC_MEDIA_HDR_NUM_FRAMES_GET(hdr) FIELD_GET(GENMASK(3, 0), (hdr)) +/** Set to 1 for the last packet of a fragmented SBC frame, otherwise set to + * 0. + */ +#define BT_A2DP_SBC_MEDIA_HDR_L_GET(hdr) FIELD_GET(BIT(5), (hdr)) +/** Set to 1 for the starting packet of a fragmented SBC frame, otherwise set + * to 0. + */ +#define BT_A2DP_SBC_MEDIA_HDR_S_GET(hdr) FIELD_GET(BIT(6), (hdr)) +/** Set to 1 if the SBC frame is fragmented, otherwise set to 0. */ +#define BT_A2DP_SBC_MEDIA_HDR_F_GET(hdr) FIELD_GET(BIT(7), (hdr)) + +/** If the F bit is set to 0, this field indicates the number of frames + * contained in this packet. If the F bit is set to 1, this field indicates + * the number of remaining fragments, including the current fragment. + * Therefore, the last counter value shall be one. + */ +#define BT_A2DP_SBC_MEDIA_HDR_NUM_FRAMES_SET(hdr, val) \ +hdr = ((hdr) & ~GENMASK(3, 0)) | FIELD_PREP(GENMASK(3, 0), (val)) +/** Set to 1 for the last packet of a fragmented SBC frame, otherwise set to + * 0. + */ +#define BT_A2DP_SBC_MEDIA_HDR_L_SET(hdr, val) hdr = ((hdr) & ~BIT(5)) | FIELD_PREP(BIT(5), (val)) +/** Set to 1 for the starting packet of a fragmented SBC frame, otherwise set + * to 0. + */ +#define BT_A2DP_SBC_MEDIA_HDR_S_SET(hdr, val) hdr = ((hdr) & ~BIT(6)) | FIELD_PREP(BIT(6), (val)) +/** Set to 1 if the SBC frame is fragmented, otherwise set to 0. */ +#define BT_A2DP_SBC_MEDIA_HDR_F_SET(hdr, val) hdr = ((hdr) & ~BIT(7)) | FIELD_PREP(BIT(7), (val)) + +#define BT_A2DP_SBC_MEDIA_HDR_ENCODE(num_frames, l, s, f) \ +FIELD_PREP(GENMASK(3, 0), num_frames) | FIELD_PREP(BIT(5), l) | FIELD_PREP(BIT(6), s) | \ + FIELD_PREP(BIT(7), f) + +/** @brief get channel num of a2dp sbc config. + * + * @param sbc_codec The a2dp sbc parameter. + * + * @return the channel num. + */ + +uint8_t bt_a2dp_sbc_get_channel_num( + struct bt_a2dp_codec_sbc_params *sbc_codec); + +/** @brief get channel mode of a2dp sbc config. + * + * @param sbc_codec The a2dp sbc parameter. + * + * @return the channel mode. + */ + +enum sbc_ch_mode bt_a2dp_sbc_get_channel_mode( + struct bt_a2dp_codec_sbc_params *sbc_codec); + +/** @brief get sample rate of a2dp sbc config. + * + * @param sbc_codec The a2dp sbc parameter. + * + * @return the sample rate. + */ + +uint32_t bt_a2dp_sbc_get_sampling_frequency( + struct bt_a2dp_codec_sbc_params *sbc_codec); + +/** @brief get subband num of a2dp sbc config. + * + * @param sbc_codec The a2dp sbc parameter. + * + * @return the subband num. + */ + +uint8_t bt_a2dp_sbc_get_subband_num( + struct bt_a2dp_codec_sbc_params *sbc_codec); + +/** @brief get block length of a2dp sbc config. + * + * @param sbc_codec The a2dp sbc parameter. + * + * @return the block length. + */ + +uint8_t bt_a2dp_sbc_get_block_length( + struct bt_a2dp_codec_sbc_params *sbc_codec); + +/** @brief get allocation method of a2dp sbc config. + * + * @param sbc_codec The a2dp sbc parameter. + * + * @return the allocation method. + */ + +enum sbc_alloc_mthd bt_a2dp_sbc_get_allocation_method( + struct bt_a2dp_codec_sbc_params *sbc_codec); + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_A2DP_CODEC_SBC_H */ diff --git a/include/nuttx/net/bluetooth/classic/avdtp.h b/include/nuttx/net/bluetooth/classic/avdtp.h new file mode 100644 index 0000000000000..df0ab75d5c257 --- /dev/null +++ b/include/nuttx/net/bluetooth/classic/avdtp.h @@ -0,0 +1,312 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/classic/avdtp.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Audio/Video Distribution Transport Protocol header. + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_AVDTP_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_AVDTP_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define AVDTP_VERSION_1_3 0x0103 /**< AVDTP version 1.3 value */ + +#define AVDTP_VERSION AVDTP_VERSION_1_3 /**< AVDTP version used by NuttX */ + +/** + * @brief AVDTP error code + */ + +enum bt_avdtp_err_code +{ + /** The response is success, it is not from avdtp spec. */ + + BT_AVDTP_SUCCESS = 0x00, + + /** The request is time out without response, it is not from avdtp spec. */ + + BT_AVDTP_TIME_OUT = 0xff, + + /** The request packet header format error */ + + BT_AVDTP_BAD_HEADER_FORMAT = 0x01, + + /** The request packet length is not match the assumed length */ + + BT_AVDTP_BAD_LENGTH = 0x11, + + /** The requested command indicates an invalid ACP SEID (not addressable) */ + + BT_AVDTP_BAD_ACP_SEID = 0x12, + + /** The SEP is in use */ + + BT_AVDTP_SEP_IN_USE = 0x13, + + /** The SEP is not in use */ + + BT_AVDTP_SEP_NOT_IN_USE = 0x14, + + /** The value of Service Category in the request packet is not defined in + * AVDTP + */ + + BT_AVDTP_BAD_SERV_CATEGORY = 0x17, + + /** The requested command has an incorrect payload format */ + + BT_AVDTP_BAD_PAYLOAD_FORMAT = 0x18, + + /** The requested command is not supported by the device */ + + BT_AVDTP_NOT_SUPPORTED_COMMAND = 0x19, + /** The reconfigure command is an attempt to reconfigure a transport + * service codec_cap of the SEP. Reconfigure is only permitted for + * application service codec_cap + */ + + BT_AVDTP_INVALID_CAPABILITIES = 0x1a, + + /** The requested Recovery Type is not defined in AVDTP */ + + BT_AVDTP_BAD_RECOVERY_TYPE = 0x22, + + /** The format of Media Transport Capability is not correct */ + + BT_AVDTP_BAD_MEDIA_TRANSPORT_FORMAT = 0x23, + + /** The format of Recovery Service Capability is not correct */ + + BT_AVDTP_BAD_RECOVERY_FORMAT = 0x25, + + /** The format of Header Compression Service Capability is not correct */ + + BT_AVDTP_BAD_ROHC_FORMAT = 0x26, + + /** The format of Content Protection Service Capability is not correct */ + + BT_AVDTP_BAD_CP_FORMAT = 0x27, + + /** The format of Multiplexing Service Capability is not correct */ + + BT_AVDTP_BAD_MULTIPLEXING_FORMAT = 0x28, + + /** Configuration not supported */ + + BT_AVDTP_UNSUPPORTED_CONFIGURATION = 0x29, + /** Indicates that the ACP state machine is in an invalid state in order to + * process the signal. This also includes the situation when an INT + * receives a request for the same command that it is currently expecting a + * response + */ + + BT_AVDTP_BAD_STATE = 0x31, +}; + +/** @brief Stream End Point Type */ + +enum bt_avdtp_sep_type +{ + /** Source Role */ + + BT_AVDTP_SOURCE = 0, + + /** Sink Role */ + + BT_AVDTP_SINK = 1 +}; + +/** @brief Stream End Point Media Type */ + +enum bt_avdtp_media_type +{ + /** Audio Media Type */ + + BT_AVDTP_AUDIO = 0x00, + + /** Video Media Type */ + + BT_AVDTP_VIDEO = 0x01, + + /** Multimedia Media Type */ + + BT_AVDTP_MULTIMEDIA = 0x02 +}; + +/** @brief AVDTP stream endpoint information. + * Don't need to care endianness because it is not used for data parsing. + */ + +struct bt_avdtp_sep_info +{ + /** End Point usage status */ + + uint8_t inuse : 1; + + /** Stream End Point ID that is the identifier of the stream endpoint */ + + uint8_t id : 6; + + /** Reserved */ + + uint8_t reserved : 1; + + /** Stream End-point Type that indicates if the stream end-point is SNK or + * SRC + */ + + enum bt_avdtp_sep_type tsep; + /** Media-type of the End Point Only @ref BT_AVDTP_AUDIO is supported now. + */ + + enum bt_avdtp_media_type media_type; +}; + +/** @brief service category Type */ + +enum bt_avdtp_service_category +{ + /** Media Transport */ + + BT_AVDTP_SERVICE_MEDIA_TRANSPORT = 0x01, + + /** Reporting */ + + BT_AVDTP_SERVICE_REPORTING = 0x02, + + /** Recovery */ + + BT_AVDTP_SERVICE_MEDIA_RECOVERY = 0x03, + + /** Content Protection */ + + BT_AVDTP_SERVICE_CONTENT_PROTECTION = 0x04, + + /** Header Compression */ + + BT_AVDTP_SERVICE_HEADER_COMPRESSION = 0x05, + + /** Multiplexing */ + + BT_AVDTP_SERVICE_MULTIPLEXING = 0x06, + + /** Media Codec */ + + BT_AVDTP_SERVICE_MEDIA_CODEC = 0x07, + + /** Delay Reporting */ + + BT_AVDTP_SERVICE_DELAY_REPORTING = 0x08, +}; + +/** @brief service category Recovery Capabilities type */ + +enum bt_avdtp_recovery_type +{ + /** Forbidden */ + + BT_AVDTP_RECOVERY_TYPE_FORBIDDEN = 0x00, + + /** RFC2733 */ + + BT_ADVTP_RECOVERY_TYPE_RFC2733 = 0x01, +}; + +struct bt_avdtp_sep; + +/** @brief avdtp sep operations structure */ + +struct bt_avdtp_sep_ops +{ + /** @brief Stream End Point (SEP) l2cap connected callback + * + * If this callback is provided it will be called whenever the stream + * l2cap connection completes. + * + * @param sep The sep that has been connected + */ + + void (*connected)(struct bt_avdtp_sep *sep); + + /** @brief Stream End Point (SEP) l2cap disconnected callback + * + * If this callback is provided it will be called whenever the stream + * l2cap channel is disconnected, including when a connection gets + * rejected. + * + * @param sep The sep that has been disconnected + */ + + void (*disconnected)(struct bt_avdtp_sep *sep); + + /** @brief Stream End Point (SEP) received data + * + * If this callback is provided it will be called whenever the stream + * l2cap channel receives data. + * + * @param sep The sep that has received data. + * @param buf The data buf + */ + + void (*media_data_cb)(struct bt_avdtp_sep *sep, struct net_buf *buf); +}; + +/** @brief AVDTP Stream End Point */ + +struct bt_avdtp_sep +{ + /** Stream End Point information */ + + struct bt_avdtp_sep_info sep_info; + + /** Media Transport Channel */ + + struct bt_l2cap_br_chan chan; + + /* semaphore for lock/unlock */ + + sem_t sem_lock; + + /** avdtp session */ + + struct bt_avdtp *session; + + /** sep ops */ + + const struct bt_avdtp_sep_ops *ops; + + /** delay worker for disconnecting l2cap media channel */ + + struct k_work_delayable _delay_work; + + /** delay_work_state */ + + uint8_t _delay_work_state; + + /** SEP state */ + + uint8_t state; + + /* Internally used list node */ + + struct list_node _node; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_AVDTP_H */ diff --git a/include/nuttx/net/bluetooth/classic/avrcp.h b/include/nuttx/net/bluetooth/classic/avrcp.h new file mode 100644 index 0000000000000..3f9274ae0a6d6 --- /dev/null +++ b/include/nuttx/net/bluetooth/classic/avrcp.h @@ -0,0 +1,2935 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/classic/avrcp.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Audio Video Remote Control Profile header. + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * Copyright (C) 2024 Xiaomi Corporation Copyright 2025-2026 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_AVRCP_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_AVRCP_H + +/** + * @brief Audio Video Remote Control Profile (AVRCP) + * @defgroup bt_avrcp Audio Video Remote Control Profile (AVRCP) + * @ingroup bluetooth + * @{ + */ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define BT_AVRCP_COMPANY_ID_SIZE (3) +#define BT_AVRCP_COMPANY_ID_BLUETOOTH_SIG (0x001958) + +/** @brief AVRCP Capability ID */ + +typedef enum __packed +{ + BT_AVRCP_CAP_COMPANY_ID = 0x2, + BT_AVRCP_CAP_EVENTS_SUPPORTED = 0x3, +} bt_avrcp_cap_t; + +/** @brief AVRCP Notification Events */ + +typedef enum __packed +{ + BT_AVRCP_EVT_PLAYBACK_STATUS_CHANGED = 0x01, + BT_AVRCP_EVT_TRACK_CHANGED = 0x02, + BT_AVRCP_EVT_TRACK_REACHED_END = 0x03, + BT_AVRCP_EVT_TRACK_REACHED_START = 0x04, + BT_AVRCP_EVT_PLAYBACK_POS_CHANGED = 0x05, + BT_AVRCP_EVT_BATT_STATUS_CHANGED = 0x06, + BT_AVRCP_EVT_SYSTEM_STATUS_CHANGED = 0x07, + BT_AVRCP_EVT_PLAYER_APP_SETTING_CHANGED = 0x08, + BT_AVRCP_EVT_NOW_PLAYING_CONTENT_CHANGED = 0x09, + BT_AVRCP_EVT_AVAILABLE_PLAYERS_CHANGED = 0x0a, + BT_AVRCP_EVT_ADDRESSED_PLAYER_CHANGED = 0x0b, + BT_AVRCP_EVT_UIDS_CHANGED = 0x0c, + BT_AVRCP_EVT_VOLUME_CHANGED = 0x0d, +} bt_avrcp_evt_t; + +/** @brief AV/C command types */ + +typedef enum __packed +{ + BT_AVRCP_CTYPE_CONTROL = 0x0, + BT_AVRCP_CTYPE_STATUS = 0x1, + BT_AVRCP_CTYPE_SPECIFIC_INQUIRY = 0x2, + BT_AVRCP_CTYPE_NOTIFY = 0x3, + BT_AVRCP_CTYPE_GENERAL_INQUIRY = 0x4, +} bt_avrcp_ctype_t; + +/** @brief AV/C response codes */ + +typedef enum __packed +{ + BT_AVRCP_RSP_NOT_IMPLEMENTED = 0x8, + BT_AVRCP_RSP_ACCEPTED = 0x9, + BT_AVRCP_RSP_REJECTED = 0xa, + BT_AVRCP_RSP_IN_TRANSITION = 0xb, + BT_AVRCP_RSP_IMPLEMENTED = 0xc, /**< For SPECIFIC_INQUIRY and GENERAL_INQUIRY commands */ + BT_AVRCP_RSP_STABLE = 0xc, /**< For STATUS commands */ + BT_AVRCP_RSP_CHANGED = 0xd, + BT_AVRCP_RSP_INTERIM = 0xf, +} bt_avrcp_rsp_t; + +/** @brief AV/C subunit type, also used for unit type */ + +typedef enum __packed +{ + BT_AVRCP_SUBUNIT_TYPE_PANEL = 0x09, + BT_AVRCP_SUBUNIT_TYPE_UNIT = 0x1f, +} bt_avrcp_subunit_type_t; + +/** @brief AV/C operation ids used in AVRCP passthrough commands */ + +typedef enum __packed +{ + BT_AVRCP_OPID_SELECT = 0x00, + BT_AVRCP_OPID_UP = 0x01, + BT_AVRCP_OPID_DOWN = 0x02, + BT_AVRCP_OPID_LEFT = 0x03, + BT_AVRCP_OPID_RIGHT = 0x04, + BT_AVRCP_OPID_RIGHT_UP = 0x05, + BT_AVRCP_OPID_RIGHT_DOWN = 0x06, + BT_AVRCP_OPID_LEFT_UP = 0x07, + BT_AVRCP_OPID_LEFT_DOWN = 0x08, + BT_AVRCP_OPID_ROOT_MENU = 0x09, + BT_AVRCP_OPID_SETUP_MENU = 0x0a, + BT_AVRCP_OPID_CONTENTS_MENU = 0x0b, + BT_AVRCP_OPID_FAVORITE_MENU = 0x0c, + BT_AVRCP_OPID_EXIT = 0x0d, + + BT_AVRCP_OPID_0 = 0x20, + BT_AVRCP_OPID_1 = 0x21, + BT_AVRCP_OPID_2 = 0x22, + BT_AVRCP_OPID_3 = 0x23, + BT_AVRCP_OPID_4 = 0x24, + BT_AVRCP_OPID_5 = 0x25, + BT_AVRCP_OPID_6 = 0x26, + BT_AVRCP_OPID_7 = 0x27, + BT_AVRCP_OPID_8 = 0x28, + BT_AVRCP_OPID_9 = 0x29, + BT_AVRCP_OPID_DOT = 0x2a, + BT_AVRCP_OPID_ENTER = 0x2b, + BT_AVRCP_OPID_CLEAR = 0x2c, + + BT_AVRCP_OPID_CHANNEL_UP = 0x30, + BT_AVRCP_OPID_CHANNEL_DOWN = 0x31, + BT_AVRCP_OPID_PREVIOUS_CHANNEL = 0x32, + BT_AVRCP_OPID_SOUND_SELECT = 0x33, + BT_AVRCP_OPID_INPUT_SELECT = 0x34, + BT_AVRCP_OPID_DISPLAY_INFORMATION = 0x35, + BT_AVRCP_OPID_HELP = 0x36, + BT_AVRCP_OPID_PAGE_UP = 0x37, + BT_AVRCP_OPID_PAGE_DOWN = 0x38, + + BT_AVRCP_OPID_POWER = 0x40, + BT_AVRCP_OPID_VOLUME_UP = 0x41, + BT_AVRCP_OPID_VOLUME_DOWN = 0x42, + BT_AVRCP_OPID_MUTE = 0x43, + BT_AVRCP_OPID_PLAY = 0x44, + BT_AVRCP_OPID_STOP = 0x45, + BT_AVRCP_OPID_PAUSE = 0x46, + BT_AVRCP_OPID_RECORD = 0x47, + BT_AVRCP_OPID_REWIND = 0x48, + BT_AVRCP_OPID_FAST_FORWARD = 0x49, + BT_AVRCP_OPID_EJECT = 0x4a, + BT_AVRCP_OPID_FORWARD = 0x4b, + BT_AVRCP_OPID_BACKWARD = 0x4c, + + BT_AVRCP_OPID_ANGLE = 0x50, + BT_AVRCP_OPID_SUBPICTURE = 0x51, + + BT_AVRCP_OPID_F1 = 0x71, + BT_AVRCP_OPID_F2 = 0x72, + BT_AVRCP_OPID_F3 = 0x73, + BT_AVRCP_OPID_F4 = 0x74, + BT_AVRCP_OPID_F5 = 0x75, + BT_AVRCP_OPID_VENDOR_UNIQUE = 0x7e, +} bt_avrcp_opid_t; + +/** @brief AVRCP button state flag */ + +typedef enum __packed +{ + BT_AVRCP_BUTTON_PRESSED = 0, + BT_AVRCP_BUTTON_RELEASED = 1, +} bt_avrcp_button_state_t; + +/** + * @brief AVRCP status and error codes. + * + * These status codes are used in AVRCP responses to indicate the result of a + * command. + */ + +typedef enum __packed +{ + /** Invalid command. Valid for Commands: All */ + + BT_AVRCP_STATUS_INVALID_COMMAND = 0x00, + + /** Invalid parameter. Valid for Commands: All */ + + BT_AVRCP_STATUS_INVALID_PARAMETER = 0x01, + + /** Parameter content error. Valid for Commands: All */ + + BT_AVRCP_STATUS_PARAMETER_CONTENT_ERROR = 0x02, + + /** Internal error. Valid for Commands: All */ + + BT_AVRCP_STATUS_INTERNAL_ERROR = 0x03, + + /** Operation completed without error. Valid for Commands: All except where + * the response CType is AV/C REJECTED + */ + + BT_AVRCP_STATUS_OPERATION_COMPLETED = 0x04, + + /** The UIDs on the device have changed. Valid for Commands: All */ + + BT_AVRCP_STATUS_UID_CHANGED = 0x05, + + /** The Direction parameter is invalid. Valid for Commands: ChangePath */ + + BT_AVRCP_STATUS_INVALID_DIRECTION = 0x07, + + /** The UID provided does not refer to a folder item. Valid for Commands: + * ChangePath + */ + + BT_AVRCP_STATUS_NOT_A_DIRECTORY = 0x08, + + /** The UID provided does not refer to any currently valid item. + * Valid for Commands: ChangePath, PlayItem, AddToNowPlaying, + * GetItemAttributes + */ + + BT_AVRCP_STATUS_DOES_NOT_EXIST = 0x09, + + /** Invalid scope. + * Valid for Commands: GetFolderItems, PlayItem, AddToNowPlayer, + * GetItemAttributes, GetTotalNumberOfItems + */ + + BT_AVRCP_STATUS_INVALID_SCOPE = 0x0a, + + /** Range out of bounds. Valid for Commands: GetFolderItems */ + + BT_AVRCP_STATUS_RANGE_OUT_OF_BOUNDS = 0x0b, + + /** Folder item is not playable. Valid for Commands: Play Item, + * AddToNowPlaying + */ + + BT_AVRCP_STATUS_FOLDER_ITEM_IS_NOT_PLAYABLE = 0x0c, + + /** Media in use. Valid for Commands: PlayItem, AddToNowPlaying */ + + BT_AVRCP_STATUS_MEDIA_IN_USE = 0x0d, + + /** Now Playing List full. Valid for Commands: AddToNowPlaying */ + + BT_AVRCP_STATUS_NOW_PLAYING_LIST_FULL = 0x0e, + + /** Search not supported. Valid for Commands: Search */ + + BT_AVRCP_STATUS_SEARCH_NOT_SUPPORTED = 0x0f, + + /** Search in progress. Valid for Commands: Search */ + + BT_AVRCP_STATUS_SEARCH_IN_PROGRESS = 0x10, + + /** The specified Player Id does not refer to a valid player. Valid for + * Commands: SetAddressedPlayer, SetBrowsedPlayer + */ + + BT_AVRCP_STATUS_INVALID_PLAYER_ID = 0x11, + + /** Player not browsable. Valid for Commands: SetBrowsedPlayer */ + + BT_AVRCP_STATUS_PLAYER_NOT_BROWSABLE = 0x12, + + /** Player not addressed. Valid for Commands: Search, SetBrowsedPlayer */ + + BT_AVRCP_STATUS_PLAYER_NOT_ADDRESSED = 0x13, + + /** No valid search results. Valid for Commands: GetFolderItems */ + + BT_AVRCP_STATUS_NO_VALID_SEARCH_RESULTS = 0x14, + + /** No available players. Valid for Commands: ALL */ + + BT_AVRCP_STATUS_NO_AVAILABLE_PLAYERS = 0x15, + + /** Addressed player changed. Valid for Commands: All Register Notification + * commands + */ + + BT_AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED = 0x16, + + /** In transition response. + * The target is currently changing state (e.g., between play/pause). Note: + * This status is not used for browsing commands. + */ + + BT_AVRCP_STATUS_IN_TRANSITION = 0xfd, + + /** Not implemented response. + * The command/PDU is not supported by the target device. Note: This status + * is not used for browsing commands. + */ + + BT_AVRCP_STATUS_NOT_IMPLEMENTED = 0xfe, + + /** Successful response. + * The requested command or PDU was processed successfully by the target + * device. + * + * For control commands, it means the request was accepted. For status + * commands, it means the state is stable and reported successfully. + */ + + BT_AVRCP_STATUS_SUCCESS = BT_AVRCP_STATUS_OPERATION_COMPLETED, +} bt_avrcp_status_t; + +/** + * @struct bt_avrcp_ct + * @brief AVRCP CT structure + */ + +struct bt_avrcp_ct; +/** + * @struct bt_avrcp_tg + * @brief AVRCP TG structure + */ + +struct bt_avrcp_tg; + +struct bt_avrcp_unit_info_rsp +{ + bt_avrcp_subunit_type_t unit_type; + uint32_t company_id; +}; + +struct bt_avrcp_subunit_info_rsp +{ + bt_avrcp_subunit_type_t subunit_type; + uint8_t max_subunit_id; + const uint8_t *extended_subunit_type; /**< contains max_subunit_id items */ + const uint8_t *extended_subunit_id; /**< contains max_subunit_id items */ +}; + +#define BT_AVRCP_PASSTHROUGH_GET_STATE(payload) \ +((bt_avrcp_button_state_t)(FIELD_GET(BIT(7), ((payload)->opid_state)))) +#define BT_AVRCP_PASSTHROUGH_GET_OPID(payload) \ +((bt_avrcp_opid_t)(FIELD_GET(GENMASK(6, 0), ((payload)->opid_state)))) +#define BT_AVRCP_PASSTHROUGH_SET_STATE_OPID(payload, state, opid) \ +(payload)->opid_state = FIELD_PREP(BIT(7), state) | \ + FIELD_PREP(GENMASK(6, 0), opid) + +struct bt_avrcp_passthrough_opvu_data +{ + uint8_t company_id[BT_AVRCP_COMPANY_ID_SIZE]; + uint16_t opid_vu; +} __packed; + +struct bt_avrcp_passthrough_cmd +{ + uint8_t opid_state; /**< [7]: state_flag, [6:0]: opid */ + uint8_t data_len; + struct bt_avrcp_passthrough_opvu_data data[0]; /**< opvu data */ +} __packed; + +struct bt_avrcp_passthrough_rsp +{ + uint8_t opid_state; /**< [7]: state_flag, [6:0]: opid */ + uint8_t data_len; + struct bt_avrcp_passthrough_opvu_data data[0]; /**< opvu data */ +} __packed; + +struct bt_avrcp_get_caps_rsp +{ + uint8_t cap_id; /**< bt_avrcp_cap_t */ + uint8_t cap_cnt; /**< number of items contained in *cap */ + uint8_t cap[]; /**< 1 or 3 octets each depends on cap_id */ +} __packed; + +/** @brief AVRCP Character Set IDs */ + +typedef enum __packed +{ + BT_AVRCP_CHARSET_UTF8 = 0x006a, +} bt_avrcp_charset_t; + +/** @brief AVRCP Scope Values */ + +typedef enum __packed +{ + BT_AVRCP_SCOPE_MEDIA_PLAYER_LIST = 0x00, /**< Media Player List */ + BT_AVRCP_SCOPE_VFS = 0x01, /**< Virtual File System */ + BT_AVRCP_SCOPE_SEARCH = 0x02, /**< Search */ + BT_AVRCP_SCOPE_NOW_PLAYING = 0x03, /**< Now Playing */ +} bt_avrcp_scope_t; + +/** @brief AVRCP ChangePath direction */ + +typedef enum __packed +{ + BT_AVRCP_CHANGE_PATH_PARENT = 0x00, /**< Navigate to parent folder */ + BT_AVRCP_CHANGE_PATH_CHILD = 0x01, /**< Navigate to child folder */ +} bt_avrcp_change_path_t; + +/** @brief AVRCP folder types (for folder items) */ + +typedef enum __packed +{ + BT_AVRCP_FOLDER_TYPE_MIXED = 0x00, /**< Mixed folder type */ + BT_AVRCP_FOLDER_TYPE_TITLES = 0x01, /**< Titles folder */ + BT_AVRCP_FOLDER_TYPE_ALBUMS = 0x02, /**< Albums folder */ + BT_AVRCP_FOLDER_TYPE_ARTISTS = 0x03, /**< Artists folder */ + BT_AVRCP_FOLDER_TYPE_GENRES = 0x04, /**< Genres folder */ + BT_AVRCP_FOLDER_TYPE_PLAYLISTS = 0x05, /**< Playlists folder */ + BT_AVRCP_FOLDER_TYPE_YEARS = 0x06, /**< Years folder */ +} bt_avrcp_folder_type_t; + +/** @brief AVRCP media types (for media element items) */ + +typedef enum __packed +{ + BT_AVRCP_MEDIA_TYPE_AUDIO = 0x00, /**< Audio media */ + BT_AVRCP_MEDIA_TYPE_VIDEO = 0x01, /**< Video media */ +} bt_avrcp_media_type_t; + +/** @brief AVRCP Media Attribute IDs */ + +typedef enum __packed +{ + BT_AVRCP_MEDIA_ATTR_ID_TITLE = 0x01, /**< Title of media */ + BT_AVRCP_MEDIA_ATTR_ID_ARTIST = 0x02, /**< Artist name */ + BT_AVRCP_MEDIA_ATTR_ID_ALBUM = 0x03, /**< Album name */ + BT_AVRCP_MEDIA_ATTR_ID_TRACK_NUMBER = 0x04, /**< Track number */ + BT_AVRCP_MEDIA_ATTR_ID_TOTAL_TRACKS = 0x05, /**< Total number of tracks */ + BT_AVRCP_MEDIA_ATTR_ID_GENRE = 0x06, /**< Genre */ + BT_AVRCP_MEDIA_ATTR_ID_PLAYING_TIME = 0x07, /**< Playing time in milliseconds */ + BT_AVRCP_MEDIA_ATTR_ID_DEFAULT_COVER_ART = 0x08, /**< Default cover art */ +} bt_avrcp_media_attr_id_t; + +/** @brief GetFolderItems command request */ + +struct bt_avrcp_get_folder_items_cmd +{ + uint8_t scope; /**< bt_avrcp_scope_t */ + uint32_t start_item; /**< Start item index */ + uint32_t end_item; /**< End item index (inclusive) */ + uint8_t attr_count; /**< 0x00=all, 0x01..0xFE=count, 0xFF=none */ + uint32_t attr_ids[]; /**< Attribute IDs @ref bt_avrcp_media_attr_id_t */ +} __packed; + +/** @brief AVRCP item types (for browsing GetFolderItems, etc.) */ + +typedef enum __packed +{ + BT_AVRCP_ITEM_TYPE_MEDIA_PLAYER = 0x01, /**< Media player item */ + BT_AVRCP_ITEM_TYPE_FOLDER = 0x02, /**< Folder item */ + BT_AVRCP_ITEM_TYPE_MEDIA_ELEMENT = 0x03, /**< Media element item */ +} bt_avrcp_item_type_t; + +/** @brief Common item header for GetFolderItems response */ + +struct bt_avrcp_item_hdr +{ + uint8_t item_type; /**< @ref bt_avrcp_item_type_t */ + uint16_t item_len; /**< Length of the remaining fields of this item */ +} __packed; + +/** @brief Media Player item (item_type=0x01) */ + +struct bt_avrcp_media_player_item +{ + struct bt_avrcp_item_hdr hdr; + uint16_t player_id; /**< Player ID */ + uint8_t major_type; /**< Major Player Type */ + uint32_t player_subtype; /**< Player Subtype */ + uint8_t play_status; /**< Play status: @ref bt_avrcp_playback_status_t */ + uint8_t feature_bitmask[16]; /**< 128-bit Feature bitmask, octet0..15 */ + uint16_t charset_id; /**< Displayable name charset @ref bt_avrcp_charset_t */ + uint16_t name_len; /**< Displayable name length */ + uint8_t name[]; /**< Displayable name */ +} __packed; + +/** @brief Folder item (item_type=0x02) */ + +struct bt_avrcp_folder_item +{ + struct bt_avrcp_item_hdr hdr; + uint8_t uid[8]; /**< 64-bit Folder UID */ + uint8_t folder_type; /**< bt_avrcp_folder_type_t */ + uint8_t playable; /**< 0=non-playable, 1=playable */ + uint16_t charset_id; /**< Character set ID for name, see @ref bt_avrcp_charset_t */ + uint16_t name_len; /**< Length of the name in bytes */ + uint8_t name[]; /**< Folder name string data */ +} __packed; + +/** @brief AVRCP Media Attribute structure */ + +struct bt_avrcp_media_attr +{ + uint32_t attr_id; /**< Media attribute ID, see @ref bt_avrcp_media_attr_id_t */ + uint16_t charset_id; /**< Character set ID, see @ref bt_avrcp_charset_t */ + uint16_t attr_len; /**< Length of attribute value */ + uint8_t attr_val[]; /**< Attribute value data */ +} __packed; + +/** @brief Media Element Item Name structure */ + +struct media_element_item_name +{ + uint16_t charset_id; /**< Character set ID for name, see @ref bt_avrcp_charset_t */ + uint16_t name_len; /**< Length of the name in bytes */ + uint8_t name[]; /**< Name string data */ +} __packed; + +/** @brief Media Element Item Attributes structure */ + +struct media_element_item_attr +{ + uint8_t num_attrs; /**< Number of attributes */ + struct bt_avrcp_media_attr attrs[]; /**< attribute tuples (id/charset/len/value) */ +} __packed; + +/** @brief Media Element item (item_type = 0x03). + * + * The @p data field contains: + * - struct media_element_item_name (Character set + name_len + name[]). + * - Followed by struct media_element_item_attr. + */ + +struct bt_avrcp_media_element_item +{ + struct bt_avrcp_item_hdr hdr; + uint8_t uid[8]; /**< 64-bit element UID. */ + uint8_t media_type; /**< @ref bt_avrcp_media_type_t. */ + uint8_t data[]; /**< flexible array for name and attributes. */ +} __packed; + +/** @brief GetFolderItems response + * + * The items[] array contains a sequence of items, where each item starts + * with a bt_avrcp_item_hdr structure. Based on the item_type field in the + * header, the application should parse the item using the corresponding + * structure: + * + * - item_type = BT_AVRCP_ITEM_TYPE_MEDIA_PLAYER (0x01): + * Parse as @ref bt_avrcp_media_player_item + * + * - item_type = BT_AVRCP_ITEM_TYPE_FOLDER (0x02): + * Parse as @ref bt_avrcp_folder_item + * + * - item_type = BT_AVRCP_ITEM_TYPE_MEDIA_ELEMENT (0x03): + * Parse as @ref bt_avrcp_media_element_item + * + * @note All multi-octet fields are in big-endian format and need conversion + * using sys_be16_to_cpu(), sys_be32_to_cpu() etc. + */ + +struct bt_avrcp_get_folder_items_rsp +{ + uint8_t status; /**< bt_avrcp_status_t */ + uint16_t uid_counter; /**< UID counter */ + uint16_t num_items; /**< Number of items in this response */ + uint8_t items[]; /**< Sequence of items, each begins with bt_avrcp_item_hdr */ +} __packed; + +/** @brief ChangePath command request */ + +struct bt_avrcp_change_path_cmd +{ + uint16_t uid_counter; /**< UID counter */ + uint8_t direction; /**< change path direction @ref bt_avrcp_change_path_t */ + uint8_t folder_uid[8]; /**< 64-bit Folder UID */ +} __packed; + +/** @brief ChangePath response */ + +struct bt_avrcp_change_path_rsp +{ + uint8_t status; /**< @ref bt_avrcp_status_t */ + uint32_t num_items; /**< Number of items in the new folder */ +} __packed; + +/** @brief GetItemAttributes command request */ + +struct bt_avrcp_get_item_attrs_cmd +{ + uint8_t scope; /**< @ref bt_avrcp_scope_t */ + uint8_t uid[8]; /**< 64-bit UID of the item */ + uint16_t uid_counter; /**< UID counter */ + uint8_t num_attrs; /**< 0x00 = all attributes, else count */ + uint32_t attr_ids[]; /**< Attribute IDs @ref bt_avrcp_media_attr_id_t */ +} __packed; + +/** @brief GetItemAttributes response */ + +struct bt_avrcp_get_item_attrs_rsp +{ + uint8_t status; /**< @ref bt_avrcp_status_t */ + uint8_t num_attrs; /**< Number of attributes */ + struct bt_avrcp_media_attr attrs[]; /**< attribute tuples (id/charset/len/value) */ +} __packed; + +/** @brief PlayItem response */ + +struct bt_avrcp_play_item_rsp +{ + uint8_t status; /**< @ref bt_avrcp_status_t */ +} __packed; + +/** @brief GetTotalNumberOfItems command request */ + +struct bt_avrcp_get_total_number_of_items_cmd +{ + uint8_t scope; /**< @ref bt_avrcp_scope_t */ +} __packed; + +/** @brief GetTotalNumberOfItems response */ + +struct bt_avrcp_get_total_number_of_items_rsp +{ + uint8_t status; /**< @ref bt_avrcp_status_t */ + uint16_t uid_counter; /**< UID counter */ + uint32_t num_items; /**< Total number of items in given scope */ +} __packed; + +/** @brief Search command request */ + +struct bt_avrcp_search_cmd +{ + uint16_t charset_id; /**< Character set ID for str, @ref bt_avrcp_charset_t */ + uint16_t search_str_len; /**< Length of search string */ + uint8_t search_str[]; /**< Search string bytes */ +} __packed; + +/** @brief Search response */ + +struct bt_avrcp_search_rsp +{ + uint8_t status; /**< @ref bt_avrcp_status_t */ + uint16_t uid_counter; /**< UID counter after search */ + uint32_t num_items; /**< Number of matching items */ +} __packed; + +/** @brief get folder name (response) */ + +struct bt_avrcp_folder_name +{ + uint16_t folder_name_len; + uint8_t folder_name[]; +} __packed; + +/** @brief Set browsed player response structure */ + +struct bt_avrcp_set_browsed_player_rsp +{ + uint8_t status; /**< Status @ref bt_avrcp_status_t. */ + uint16_t uid_counter; /**< UID counter */ + uint32_t num_items; /**< Number of items in the folder */ + uint16_t charset_id; /**< Character set ID @ref bt_avrcp_charset_t */ + uint8_t folder_depth; /**< Folder depth */ + struct bt_avrcp_folder_name folder_names[0]; /**< Folder names data */ +} __packed; + +/** @brief AVRCP Playback Status */ + +typedef enum __packed +{ + BT_AVRCP_PLAYBACK_STATUS_STOPPED = 0x00, + BT_AVRCP_PLAYBACK_STATUS_PLAYING = 0x01, + BT_AVRCP_PLAYBACK_STATUS_PAUSED = 0x02, + BT_AVRCP_PLAYBACK_STATUS_FWD_SEEK = 0x03, + BT_AVRCP_PLAYBACK_STATUS_REV_SEEK = 0x04, + BT_AVRCP_PLAYBACK_STATUS_ERROR = 0xff, +} bt_avrcp_playback_status_t; + +/** @brief AVRCP System Status Code. */ + +typedef enum __packed +{ + BT_AVRCP_SYSTEM_STATUS_POWER_ON = 0x00, + BT_AVRCP_SYSTEM_STATUS_POWER_OFF = 0x01, + BT_AVRCP_SYSTEM_STATUS_UNPLUGGED = 0x02, +} bt_avrcp_system_status_t; + +/** @brief AVRCP Battery Status Code. */ + +typedef enum __packed +{ + BT_AVRCP_BATTERY_STATUS_NORMAL = 0x00, + BT_AVRCP_BATTERY_STATUS_WARNING = 0x01, + BT_AVRCP_BATTERY_STATUS_CRITICAL = 0x02, + BT_AVRCP_BATTERY_STATUS_EXTERNAL = 0x03, + BT_AVRCP_BATTERY_STATUS_FULL = 0x04, +} bt_avrcp_battery_status_t; + +/** AVRCP MAX absolute volume. */ +#define BT_AVRCP_MAX_ABSOLUTE_VOLUME 0x7F + +/** @brief GetElementAttributes command request structure */ + +struct bt_avrcp_get_element_attrs_cmd +{ + uint8_t identifier[8]; /**< Element identifier (0x0 for currently playing) */ + uint8_t num_attrs; /**< Number of attributes requested (0 = all) */ + uint32_t attr_ids[]; /**< Array of attribute IDs @ref bt_avrcp_media_attr_id_t */ +} __packed; + +/** @brief GetElementAttributes response structure */ + +struct bt_avrcp_get_element_attrs_rsp +{ + uint8_t num_attrs; /**< Number of attributes in response */ + struct bt_avrcp_media_attr attrs[]; /**< Array of media attributes */ +} __packed; + +/** @brief AVRCP Player Application Setting Attribute IDs */ + +typedef enum __packed +{ + BT_AVRCP_PLAYER_ATTR_EQUALIZER = 0x01, + BT_AVRCP_PLAYER_ATTR_REPEAT_MODE = 0x02, + BT_AVRCP_PLAYER_ATTR_SHUFFLE = 0x03, + BT_AVRCP_PLAYER_ATTR_SCAN = 0x04, +} bt_avrcp_player_attr_id_t; + +/** @brief AVRCP Player Application Setting Values for Equalizer */ + +typedef enum __packed +{ + BT_AVRCP_EQUALIZER_OFF = 0x01, + BT_AVRCP_EQUALIZER_ON = 0x02, +} bt_avrcp_equalizer_value_t; + +/** @brief AVRCP Player Application Setting Values for Repeat Mode */ + +typedef enum __packed +{ + BT_AVRCP_REPEAT_MODE_OFF = 0x01, + BT_AVRCP_REPEAT_MODE_SINGLE_TRACK = 0x02, + BT_AVRCP_REPEAT_MODE_ALL_TRACKS = 0x03, + BT_AVRCP_REPEAT_MODE_GROUP = 0x04, +} bt_avrcp_repeat_mode_value_t; + +/** @brief AVRCP Player Application Setting Values for Shuffle */ + +typedef enum __packed +{ + BT_AVRCP_SHUFFLE_OFF = 0x01, + BT_AVRCP_SHUFFLE_ALL_TRACKS = 0x02, + BT_AVRCP_SHUFFLE_GROUP = 0x03, +} bt_avrcp_shuffle_value_t; + +/** @brief AVRCP Player Application Setting Values for Scan */ + +typedef enum __packed +{ + BT_AVRCP_SCAN_OFF = 0x01, + BT_AVRCP_SCAN_ALL_TRACKS = 0x02, + BT_AVRCP_SCAN_GROUP = 0x03, +} bt_avrcp_scan_value_t; + +/** @brief ListPlayerApplicationSettingAttributes response */ + +struct bt_avrcp_list_player_app_setting_attrs_rsp +{ + uint8_t num_attrs; /**< Number of application setting attributes */ + uint8_t attr_ids[]; /**< Array of attribute IDs @ref bt_avrcp_player_attr_id_t */ +} __packed; + +/** @brief ListPlayerApplicationSettingValues command request */ + +struct bt_avrcp_list_player_app_setting_vals_cmd +{ + uint8_t attr_id; /**< Attribute ID to query values for */ +} __packed; + +/** @brief ListPlayerApplicationSettingValues response */ + +struct bt_avrcp_list_player_app_setting_vals_rsp +{ + uint8_t num_values; /**< Number of values for the attribute */ + uint8_t values[]; /**< Array of possible values */ +} __packed; + +/** @brief GetCurrentPlayerApplicationSettingValue command request */ + +struct bt_avrcp_get_curr_player_app_setting_val_cmd +{ + uint8_t num_attrs; /**< Number of attributes to query */ + uint8_t attr_ids[]; /**< Array of attribute IDs */ +} __packed; + +/** @brief AVRCP Attribute-Value Pair */ + +struct bt_avrcp_app_setting_attr_val +{ + uint8_t attr_id; /**< Attribute ID */ + uint8_t value_id; /**< Value ID */ +} __packed; + +/** @brief GetCurrentPlayerApplicationSettingValue response */ + +struct bt_avrcp_get_curr_player_app_setting_val_rsp +{ + uint8_t num_attrs; /**< Number of attributes returned */ + struct bt_avrcp_app_setting_attr_val attr_vals[]; /**< Array of attribute-value pairs */ +} __packed; + +/** @brief SetPlayerApplicationSettingValue command request */ + +struct bt_avrcp_set_player_app_setting_val_cmd +{ + uint8_t num_attrs; /**< Number of attributes to set */ + struct bt_avrcp_app_setting_attr_val attr_vals[]; /**< Array of attribute-value pairs */ +} __packed; + +/** @brief GetPlayerApplicationSettingAttributeText command request */ + +struct bt_avrcp_get_player_app_setting_attr_text_cmd +{ + uint8_t num_attrs; /**< Number of attributes to get text for */ + uint8_t attr_ids[]; /**< Array of attribute IDs */ +} __packed; + +/** @brief AVRCP Attribute Text Entry */ + +struct bt_avrcp_app_setting_attr_text +{ + uint8_t attr_id; /**< Attribute ID */ + uint16_t charset_id; /**< Charset ID */ + uint8_t text_len; /**< Length of text */ + uint8_t text[]; /**< Text string */ +} __packed; + +/** @brief GetPlayerApplicationSettingAttributeText response */ + +struct bt_avrcp_get_player_app_setting_attr_text_rsp +{ + uint8_t num_attrs; /**< Number of attributes returned */ + struct bt_avrcp_app_setting_attr_text attr_text[]; +} __packed; + +/** @brief GetPlayerApplicationSettingValueText command request */ + +struct bt_avrcp_get_player_app_setting_val_text_cmd +{ + uint8_t attr_id; /**< Attribute ID */ + uint8_t num_values; /**< Number of values to get text for */ + uint8_t value_ids[]; /**< Array of value IDs */ +} __packed; + +/** @brief AVRCP Attribute Text Entry */ + +struct bt_avrcp_app_setting_val_text +{ + uint8_t value_id; /**< Value ID */ + uint16_t charset_id; /**< Charset ID */ + uint8_t text_len; /**< Length of text */ + uint8_t text[]; /**< Text string */ +} __packed; + +/** @brief GetPlayerApplicationSettingValueText response */ + +struct bt_avrcp_get_player_app_setting_val_text_rsp +{ + uint8_t num_values; /**< Number of values returned */ + struct bt_avrcp_app_setting_val_text value_text[]; +} __packed; + +/** @brief InformDisplayableCharacterSet command request */ + +struct bt_avrcp_inform_displayable_char_set_cmd +{ + uint8_t num_charsets; /**< Number of character sets supported */ + uint16_t charset_ids[]; /**< Array of character set IDs */ +} __packed; + +/** @brief InformBatteryStatusOfCT command request */ + +struct bt_avrcp_inform_batt_status_of_ct_cmd +{ + uint8_t battery_status; /**< Battery status value @ref bt_avrcp_battery_status_t */ +} __packed; + +/** @brief GetPlayStatus response */ + +struct bt_avrcp_get_play_status_rsp +{ + uint32_t song_length; /**< Total length of the song in milliseconds */ + uint32_t song_position; /**< Current position in the song in milliseconds */ + uint8_t play_status; /**< Play status: @ref bt_avrcp_playback_status_t */ +} __packed; + +/** @brief RegisterNotification command request */ + +struct bt_avrcp_register_notification_cmd +{ + uint8_t event_id; /**< Event ID to register for */ + uint32_t interval; /**< Playback interval (used only for event_id = 0x05) */ +} __packed; + +/** @brief SetAbsoluteVolume command request */ + +struct bt_avrcp_set_absolute_volume_cmd +{ + uint8_t absolute_volume; /**< Volume level (0x00 to 0x7F) */ +} __packed; + +/** @brief SetAbsoluteVolume response */ + +struct bt_avrcp_set_absolute_volume_rsp +{ + uint8_t absolute_volume; /**< Volume level acknowledged */ +} __packed; + +/** @brief SetAddressedPlayer command request */ + +struct bt_avrcp_set_addressed_player_cmd +{ + uint16_t player_id; /**< Player ID to be addressed */ +} __packed; + +/** @brief PlayItem command request */ + +struct bt_avrcp_play_item_cmd +{ + uint8_t scope; /**< Scope: @ref bt_avrcp_scope_t */ + uint8_t uid[8]; /**< UID of the item */ + uint16_t uid_counter; /**< UID counter */ +} __packed; + +/** @brief AddToNowPlaying command request */ + +struct bt_avrcp_add_to_now_playing_cmd +{ + uint8_t scope; /**< Scope: @ref bt_avrcp_scope_t */ + uint8_t uid[8]; /**< UID of the item */ + uint16_t uid_counter; /**< UID counter */ +} __packed; + +struct bt_avrcp_event_data +{ + union + { + /* EVENT_PLAYBACK_STATUS_CHANGED */ + + uint8_t play_status; + + /* EVENT_TRACK_CHANGED */ + + uint8_t identifier[8]; + + /* EVENT_PLAYBACK_POS_CHANGED */ + + uint32_t playback_pos; + + /* EVENT_BATT_STATUS_CHANGED */ + + uint8_t battery_status; + + /* EVENT_SYSTEM_STATUS_CHANGED */ + + uint8_t system_status; + + /* EVENT_PLAYER_APPLICATION_SETTING_CHANGED */ + + struct __packed + { + uint8_t num_of_attr; + struct bt_avrcp_app_setting_attr_val *attr_vals; + } setting_changed; + + /* EVENT_ADDRESSED_PLAYER_CHANGED */ + + struct __packed + { + uint16_t player_id; + uint16_t uid_counter; + } addressed_player_changed; + + /* EVENT_UIDS_CHANGED */ + + uint16_t uid_counter; + + /* EVENT_VOLUME_CHANGED */ + + uint8_t absolute_volume; + }; +}; + +/** @brief Callback for AVRCP event notifications (CHANGED only). + * + * This callback is invoked by the AVRCP Target (TG) when a registered event + * occurs and a "changed" notification needs to be sent to the Controller + * (CT). + * + * For interim and rejected error cases, the callback will trigger the + * `notification` function registered by the Controller + * + * @param ct AVRCP CT connection object. + * + * @param event_id The AVRCP event identifier. @ref bt_avrcp_event_data This + * corresponds to one of the AVRCP event types such as + * EVENT_PLAYBACK_STATUS_CHANGED, EVENT_TRACK_CHANGED, etc. + * + * @param data Pointer to @ref bt_avrcp_event_data structure containing the + * event-specific data. The content of the union depends on the + * event_id. + * + * @note The callback implementation should not block or perform heavy + * operations. If needed, defer processing to another thread or task. + */ + +typedef void (*bt_avrcp_notify_changed_cb_t)( + struct bt_avrcp_ct *ct, uint8_t event_id, struct bt_avrcp_event_data + *data); + +struct bt_avrcp_ct_cb +{ + /** @brief An AVRCP CT connection has been established. + * + * This callback notifies the application of an avrcp connection, i.e., an + * AVCTP L2CAP connection. + * + * @param conn Connection object. + * @param ct AVRCP CT connection object. + */ + + void (*connected)(struct bt_conn *conn, struct bt_avrcp_ct *ct); + + /** @brief An AVRCP CT connection has been disconnected. + * + * This callback notifies the application that an avrcp connection has + * been disconnected. + * + * @param ct AVRCP CT connection object. + */ + + void (*disconnected)(struct bt_avrcp_ct *ct); + + /** @brief An AVRCP CT browsing connection has been established. + * + * This callback notifies the application of an avrcp browsing connection, + * i.e., an AVCTP browsing L2CAP connection. + * + * @param conn Connection object. + * @param ct AVRCP CT connection object. + */ + + void (*browsing_connected)(struct bt_conn *conn, struct bt_avrcp_ct *ct); + + /** @brief An AVRCP CT browsing connection has been disconnected. + * + * This callback notifies the application that an avrcp browsing + * connection has been disconnected. + * + * @param ct AVRCP CT connection object. + */ + + void (*browsing_disconnected)(struct bt_avrcp_ct *ct); + + /** @brief Callback function for bt_avrcp_get_caps(). + * + * Called when the get capabilities process is completed. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response. + * @param status The status code returned by the TG, indicating the result + * of the operation, @ref bt_avrcp_status_t. Typically + * corresponds to BT_AVRCP_STATUS_* values such as + * BT_AVRCP_STATUS_SUCCESS or + * BT_AVRCP_STATUS_INVALID_PARAMETER. + * @param buf The response buffer containing the + * BT_AVRCP_PDU_ID_GET_CAPS payload returned by the TG. The + * application can parse this payload according to the + * format, defined in @ref bt_avrcp_get_caps_rsp. If status + * is in the range BT_AVRCP_STATUS_INVALID_COMMAND to + * BT_AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED, and is not + * equal to BT_AVRCP_STATUS_OPERATION_COMPLETED, it + * indicates that the AVRCP response code is an AV/C + * REJECTED response, and buf is NULL. Note that all + * multi-octet fields are encoded in big-endian format. + */ + + void (*get_caps)(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t status, + struct net_buf *buf); + + /** @brief Callback function for bt_avrcp_get_unit_info(). + * + * Called when the get unit info process is completed. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response. + * @param rsp The response for UNIT INFO command. + */ + + void (*unit_info_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, + struct bt_avrcp_unit_info_rsp *rsp); + + /** @brief Callback function for bt_avrcp_get_subunit_info(). + * + * Called when the get subunit info process is completed. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response. + * @param rsp The response for SUBUNIT INFO command. + */ + + void (*subunit_info_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, + struct bt_avrcp_subunit_info_rsp *rsp); + + /** @brief Callback function for bt_avrcp_passthrough(). + * + * Called when a passthrough response is received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response. + * @param result The result of the operation. + * @param rsp The response for PASS THROUGH command. + */ + +void (*passthrough_rsp)(struct bt_avrcp_ct *ct, + uint8_t tid, + bt_avrcp_rsp_t result, + const struct bt_avrcp_passthrough_rsp *rsp); + + /** @brief Callback function for bt_avrcp_ct_set_browsed_player(). + * + * Called when the set browsed player process is completed. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response. + * @param buf The response buffer containing the set browsed player + * response data. If the operation is successful, the + * application can parse this payload according to the format + * defined in @ref bt_avrcp_set_browsed_player_rsp. If status + * indicates a reject error or operation not completed, buf + * only contains a single status byte. Note that the data is + * encoded in big-endian format. + */ + + void (*set_browsed_player)(struct bt_avrcp_ct *ct, uint8_t tid, + struct net_buf *buf); + + /** @brief Callback function for bt_avrcp_ct_get_folder_items(). + * + * Called when the Get Folder Items process is completed. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response. + * @param buf The response buffer containing the Get Folder Items response + * data. If the operation is successful, the application can + * parse this payload according to the format defined in @ref + * bt_avrcp_get_folder_items_rsp. If status indicates a reject + * error or operation not completed, buf only contains a single + * status byte. Note that the data is encoded in big-endian + * format. + */ + + void (*get_folder_items)(struct bt_avrcp_ct *ct, uint8_t tid, + struct net_buf *buf); + + /** @brief Callback function for bt_avrcp_ct_change_path(). + * + * Called when the Change Path process is completed. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response. + * @param status Operation status (see @ref bt_avrcp_status_t). + * @param num_items Number of items in the new folder (valid only if + * status is BT_AVRCP_STATUS_OPERATION_COMPLETED). + */ + + void (*change_path)(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t status, + uint32_t num_items); + + /** @brief Callback function for bt_avrcp_ct_get_item_attrs(). + * + * Called when the Get Item Attributes process is completed. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response. + * @param buf The response buffer containing the Get Item Attributes + * response data. If the operation is successful, the + * application can parse this payload according to the format + * defined in @ref bt_avrcp_get_item_attrs_rsp. If status + * indicates a reject error or operation not completed, buf + * only contains a single status byte. Note that the data is + * encoded in big-endian format. + */ + + void (*get_item_attrs)(struct bt_avrcp_ct *ct, uint8_t tid, + struct net_buf *buf); + + /** @brief Callback function for bt_avrcp_ct_get_total_number_of_items(). + * + * Called when the Get Total Number Of Items process is completed. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response. + * @param buf The response buffer containing the Get Total Number Of Items + * response data. If the operation is successful, the + * application can parse this payload according to the format + * defined @ref bt_avrcp_get_total_number_of_items_rsp. If + * status indicates a reject error or operation not completed, + * buf only contains a single status byte. Note that the data + * is encoded in big-endian format. + */ + + void (*get_total_number_of_items)(struct bt_avrcp_ct *ct, uint8_t tid, + struct net_buf *buf); + + /** @brief Callback function for bt_avrcp_ct_search(). + * + * Called when the Search process is completed. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response. + * @param buf The response buffer containing the Search response data. If + * the operation is successful, the application can parse this + * payload according to the format defined in @ref + * bt_avrcp_search_rsp. If status indicates a reject error or + * operation not completed, buf only contains a single status + * byte. Note that the data is encoded in big-endian format. + */ + + void (*search)(struct bt_avrcp_ct *ct, uint8_t tid, struct net_buf *buf); + + /** @brief Callback function for browsing channel general reject responses. + * + * Called when a general reject response is received on the browsing + * channel. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response. + * @param status The status code indicating the reason for rejection, see + * @ref bt_avrcp_status_t. + */ + + void (*browsing_general_reject)(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t status); + + /** @brief Callback function for Event Notification response (CT). + * + * Called when the AVRCP Target (TG) sends a response to a previously + * registered event (Register Notification). This callback reports the + * event type, the response phase (e.g., Interim), and the event-specific + * payload. + * + * @param ct AVRCP Controller (CT) connection context. + * @param tid Transaction label that correlates this notification with + * the original Register Notification request. + * @param status TG status/phase code (BT_AVRCP_STATUS_*). Typically + * BT_AVRCP_STATUS_SUCCESS for an interim notification. + * Error codes may be returned for invalid parameters or + * unsupported events. + * @param event_id The AVRCP event identifier. @ref bt_avrcp_event_data + * This corresponds to one of the AVRCP event types such as + * EVENT_PLAYBACK_STATUS_CHANGED, EVENT_TRACK_CHANGED, etc. + * + * @param data Pointer to @ref bt_avrcp_event_data structure containing the + * event-specific data. The content of the union depends on the + * event_id. + * + * @note This callback is only invoked for interim notifications and error + * statuses from the TG. For CHANGED event notifications, the event must + * first be registered using @ref bt_avrcp_notify_changed_cb_t. + */ + +void (*notification)(struct bt_avrcp_ct *ct, + uint8_t tid, + uint8_t status, + uint8_t event_id, + struct bt_avrcp_event_data *data); + + /** @brief Callback for PDU ID LIST_PLAYER_APP_SETTING_ATTRS. + * + * Called when the response for LIST_PLAYER_APP_SETTING_ATTRS is received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param status The status code returned by the TG, indicating the result + * of the operation, @ref bt_avrcp_status_t. Typically + * corresponds to BT_AVRCP_STATUS_* values such as + * BT_AVRCP_STATUS_SUCCESS or + * BT_AVRCP_STATUS_INVALID_PARAMETER. + * @param buf The response buffer containing the + * LIST_PLAYER_APP_SETTING_ATTRS payload returned by the TG. + * The application can parse this payload according to the + * format defined in + * @ref bt_avrcp_list_player_app_setting_attrs_rsp. If status is in the + * range BT_AVRCP_STATUS_INVALID_COMMAND to + * BT_AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED, and is not equal to + * BT_AVRCP_STATUS_OPERATION_COMPLETED, it indicates that the AVRCP + * response code is an AV/C REJECTED response, and buf is NULL. Note that + * all multi-octet fields are encoded in big-endian format. + */ + +void (*list_player_app_setting_attrs)(struct bt_avrcp_ct *ct, + uint8_t tid, + uint8_t status, + struct net_buf *buf); + + /** @brief Callback for PDU ID LIST_PLAYER_APP_SETTING_VALS. + * + * Called when the response for LIST_PLAYER_APP_SETTING_VALS is received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param status The status code returned by the TG, indicating the result + * of the operation, @ref bt_avrcp_status_t. Typically + * corresponds to BT_AVRCP_STATUS_* values such as + * BT_AVRCP_STATUS_SUCCESS or + * BT_AVRCP_STATUS_INVALID_PARAMETER. + * @param buf The response buffer containing the + * LIST_PLAYER_APP_SETTING_VALS payload returned by the TG. + * The application can parse this payload according to the + * format defined in + * @ref bt_avrcp_list_player_app_setting_vals_rsp. If status is in the + * range BT_AVRCP_STATUS_INVALID_COMMAND to + * BT_AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED, and is not equal to + * BT_AVRCP_STATUS_OPERATION_COMPLETED, it indicates that the AVRCP + * response code is an AV/C REJECTED response, and buf is NULL. Note that + * all multi-octet fields are encoded in big-endian format. + */ + +void (*list_player_app_setting_vals)(struct bt_avrcp_ct *ct, + uint8_t tid, + uint8_t status, + struct net_buf *buf); + + /** @brief Callback for PDU ID GET_CURR_PLAYER_APP_SETTING_VAL. + * + * Called when the response for GET_CURR_PLAYER_APP_SETTING_VAL is + * received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param status The status code returned by the TG, indicating the result + * of the operation, @ref bt_avrcp_status_t. Typically + * corresponds to BT_AVRCP_STATUS_* values such as + * BT_AVRCP_STATUS_SUCCESS or + * BT_AVRCP_STATUS_INVALID_PARAMETER. + * @param buf The response buffer containing the + * GET_CURR_PLAYER_APP_SETTING_VAL payload returned by the + * TG. The application can parse this payload according to + * the format defined in + * @ref bt_avrcp_get_curr_player_app_setting_val_rsp. If status is in the + * range BT_AVRCP_STATUS_INVALID_COMMAND to + * BT_AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED, and is not equal to + * BT_AVRCP_STATUS_OPERATION_COMPLETED, it indicates that the AVRCP + * response code is an AV/C REJECTED response, and buf is NULL. Note that + * all multi-octet fields are encoded in big-endian format. + */ + +void (*get_curr_player_app_setting_val)(struct bt_avrcp_ct *ct, + uint8_t tid, + uint8_t status, + struct net_buf *buf); + + /** @brief Callback for PDU ID SET_PLAYER_APP_SETTING_VAL. + * + * Called when the response for SET_PLAYER_APP_SETTING_VAL is received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param status The status code returned by the TG, indicating the result + * of the operation, @ref bt_avrcp_status_t. Typically + * corresponds to BT_AVRCP_STATUS_* values such as + * BT_AVRCP_STATUS_SUCCESS or + * BT_AVRCP_STATUS_INVALID_PARAMETER. + */ + + void (*set_player_app_setting_val)(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t status); + + /** @brief Callback for PDU ID GET_PLAYER_APP_SETTING_ATTR_TEXT. + * + * Called when the response for GET_PLAYER_APP_SETTING_ATTR_TEXT is + * received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param status The status code returned by the TG, indicating the result + * of the operation, @ref bt_avrcp_status_t. Typically + * corresponds to BT_AVRCP_STATUS_* values such as + * BT_AVRCP_STATUS_SUCCESS or + * BT_AVRCP_STATUS_INVALID_PARAMETER. + * @param buf The response buffer containing the + * GET_PLAYER_APP_SETTING_ATTR_TEXT payload returned by the + * TG, formatted as + * @ref bt_avrcp_get_player_app_setting_attr_text_rsp. + * If status is in the range BT_AVRCP_STATUS_INVALID_COMMAND to + * BT_AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED, and is not equal to + * BT_AVRCP_STATUS_OPERATION_COMPLETED, it indicates that the AVRCP + * response code is an AV/C REJECTED response, and buf is NULL. Note that + * all multi-octet fields are encoded in big-endian format. + */ + +void (*get_player_app_setting_attr_text)(struct bt_avrcp_ct *ct, + uint8_t tid, + uint8_t status, + struct net_buf *buf); + + /** @brief Callback for PDU ID GET_PLAYER_APP_SETTING_VAL_TEXT. + * + * Called when the response for GET_PLAYER_APP_SETTING_VAL_TEXT is + * received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param status The status code returned by the TG, indicating the result + * of the operation, @ref bt_avrcp_status_t. Typically + * corresponds to BT_AVRCP_STATUS_* values such as + * BT_AVRCP_STATUS_SUCCESS or + * BT_AVRCP_STATUS_INVALID_PARAMETER. + * @param buf The response buffer containing the + * GET_PLAYER_APP_SETTING_VAL_TEXT payload returned by the + * TG, formatted as + * @ref bt_avrcp_get_player_app_setting_val_text_rsp. + * If status is in the range BT_AVRCP_STATUS_INVALID_COMMAND to + * BT_AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED, and is not equal to + * BT_AVRCP_STATUS_OPERATION_COMPLETED, it indicates that the AVRCP + * response code is an AV/C REJECTED response, and buf is NULL. Note that + * all multi-octet fields are encoded in big-endian format. + */ + +void (*get_player_app_setting_val_text)(struct bt_avrcp_ct *ct, + uint8_t tid, + uint8_t status, + struct net_buf *buf); + + /** @brief Callback for PDU ID INFORM_DISPLAYABLE_CHAR_SET. + * + * Called when the response for INFORM_DISPLAYABLE_CHAR_SET is received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param status The status code returned by the TG, indicating the result + * of the operation, @ref bt_avrcp_status_t. Typically + * corresponds to BT_AVRCP_STATUS_* values such as + * BT_AVRCP_STATUS_SUCCESS or + * BT_AVRCP_STATUS_INVALID_PARAMETER. + */ + + void (*inform_displayable_char_set)(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t status); + + /** @brief Callback for PDU ID INFORM_BATT_STATUS_OF_CT. + * + * Called when the response for INFORM_BATT_STATUS_OF_CT is received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param status The status code returned by the TG, indicating the result + * of the operation, @ref bt_avrcp_status_t. Typically + * corresponds to BT_AVRCP_STATUS_* values such as + * BT_AVRCP_STATUS_SUCCESS or + * BT_AVRCP_STATUS_INVALID_PARAMETER. + */ + + void (*inform_batt_status_of_ct)(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t status); + + /** @brief Callback function for Set Absolute Volume response (CT). + * + * Called when the Set Absolute Volume response is received from the TG. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response. + * @param status The status code returned by the TG, indicating + * the result of the operation, @ref + * bt_avrcp_status_t. Typically corresponds to + * BT_AVRCP_STATUS_* values such as + * BT_AVRCP_STATUS_SUCCESS or + * BT_AVRCP_STATUS_INVALID_PARAMETER. + * @param absolute_volume The absolute volume value (0x00-0x7F). + */ + +void (*set_absolute_volume)(struct bt_avrcp_ct *ct, + uint8_t tid, + uint8_t status, + uint8_t absolute_volume); + + /** @brief Callback for PDU ID GET_ELEMENT_ATTRS. + * + * Called when the response for GET_ELEMENT_ATTRS is received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param status The status code returned by the TG, indicating the result + * of the operation, @ref bt_avrcp_status_t. Typically + * corresponds to BT_AVRCP_STATUS_* values such as + * BT_AVRCP_STATUS_SUCCESS or + * BT_AVRCP_STATUS_INVALID_PARAMETER. + * @param buf The response buffer containing the GET_ELEMENT_ATTRS + * payload returned by the TG, formatted as + * @ref bt_avrcp_get_element_attrs_rsp. + * If status is in the range BT_AVRCP_STATUS_INVALID_COMMAND to + * BT_AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED, and is not equal to + * BT_AVRCP_STATUS_OPERATION_COMPLETED, it indicates that the AVRCP + * response code is an AV/C REJECTED response, and buf is NULL. Note that + * all multi-octet fields are encoded in big-endian format. + */ + +void (*get_element_attrs)(struct bt_avrcp_ct *ct, + uint8_t tid, + uint8_t status, + struct net_buf *buf); + + /** @brief Callback for PDU ID GET_PLAY_STATUS. + * + * Called when the response for GET_PLAY_STATUS is received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param status The status code returned by the TG, indicating the result + * of the operation, @ref bt_avrcp_status_t. Typically + * corresponds to BT_AVRCP_STATUS_* values such as + * BT_AVRCP_STATUS_SUCCESS or + * BT_AVRCP_STATUS_INVALID_PARAMETER. + * @param buf The response buffer containing the GET_PLAY_STATUS + * payload returned by the TG, formatted as + * @ref bt_avrcp_get_play_status_rsp. + * If status is in the range BT_AVRCP_STATUS_INVALID_COMMAND to + * BT_AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED, and is not equal to + * BT_AVRCP_STATUS_OPERATION_COMPLETED, it indicates that the AVRCP + * response code is an AV/C REJECTED response, and buf is NULL. Note that + * all multi-octet fields are encoded in big-endian format. + */ + + void (*get_play_status)(struct bt_avrcp_ct *ct, + uint8_t tid, + uint8_t status, + struct net_buf *buf); + + /** @brief Callback for PDU ID SET_ADDRESSED_PLAYER. + * + * Called when the response for SET_ADDRESSED_PLAYER is received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param status The status code returned by the TG, indicating the result + * of the operation. @ref bt_avrcp_status_t. Typically + * corresponds to BT_AVRCP_STATUS_* values such as + * BT_AVRCP_STATUS_SUCCESS or + * BT_AVRCP_STATUS_INVALID_PARAMETER. + */ + + void (*set_addressed_player)(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t status); + + /** @brief Callback for PDU ID PLAY_ITEM. + * + * Called when the response for PLAY_ITEM is received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param status The status code returned by the TG, indicating the result + * of the operation. @ref bt_avrcp_status_t. Typically + * corresponds to BT_AVRCP_STATUS_* values such as + * BT_AVRCP_STATUS_SUCCESS or + * BT_AVRCP_STATUS_INVALID_PARAMETER. + */ + + void (*play_item)(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t status); + + /** @brief Callback for PDU ID ADD_TO_NOW_PLAYING. + * + * Called when the response for ADD_TO_NOW_PLAYING is received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param status The status code returned by the TG, indicating the result + * of the operation. @ref bt_avrcp_status_t. Typically + * corresponds to BT_AVRCP_STATUS_* values such as + * BT_AVRCP_STATUS_SUCCESS or + * BT_AVRCP_STATUS_INVALID_PARAMETER. + */ + + void (*add_to_now_playing)(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t status); +}; + +/** @brief Connect AVRCP. + * + * This function is to be called after the conn parameter is obtained by + * performing a GAP procedure. The API is to be used to establish AVRCP + * connection between devices. + * + * @param conn Pointer to bt_conn structure. + * + * @return 0 in case of success or error code in case of error. + * + */ + +int bt_avrcp_connect(struct bt_conn *conn); + +/** @brief Disconnect AVRCP. + * + * This function close AVCTP L2CAP connection. + * + * @param conn Pointer to bt_conn structure. + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_disconnect(struct bt_conn *conn); + +/** + * @brief Allocate a net_buf for AVRCP PDU transmission, reserving headroom + * for AVRCP, AVRCTP, L2CAP, and ACL headers. + * + * This function allocates a buffer from the specified pool and reserves + * sufficient headroom for protocol headers required by AVRCP over Bluetooth. + * + * @param pool The buffer pool to allocate from. + * + * @return A newly allocated net_buf with reserved headroom. + */ + +struct net_buf *bt_avrcp_create_pdu(struct net_buf_pool *pool); + +/** + * @brief Allocate a net_buf for AVRCP Vendor-Dependent PDU transmission, + * reserving headroom for the Vendor PDU header in addition to AVRCP, AVCTP, + * L2CAP, and ACL headers. + * + * This function allocates a buffer from the specified pool and reserves + * sufficient headroom for protocol headers required by AVRCP + * Vendor-Dependent PDUs over Bluetooth. + * + * @param pool The buffer pool to allocate from. + * + * @return A newly allocated net_buf with reserved headroom. + */ + +struct net_buf *bt_avrcp_create_vendor_pdu(struct net_buf_pool *pool); + +/** @brief Connect AVRCP browsing channel. + * + * This function is to be called after the AVRCP control channel is + * established. The API is to be used to establish AVRCP browsing connection + * between devices. + * + * @param conn Pointer to bt_conn structure. + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_browsing_connect(struct bt_conn *conn); + +/** @brief Disconnect AVRCP browsing channel. + * + * This function close AVCTP browsing channel L2CAP connection. + * + * @param conn Pointer to bt_conn structure. + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_browsing_disconnect(struct bt_conn *conn); + +/** @brief Register callback. + * + * Register AVRCP callbacks to monitor the state and interact with the + * remote device. + * + * @param cb The AVRCP CT callback function. + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_ct_register_cb(const struct bt_avrcp_ct_cb *cb); + +/** @brief Get AVRCP Capabilities. + * + * This function gets the capabilities supported by remote device. + * + * @param ct The AVRCP CT instance. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param cap_id Specific capability requested, see @ref bt_avrcp_cap_t. + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_ct_get_caps(struct bt_avrcp_ct *ct, + uint8_t tid, uint8_t cap_id); + +/** @brief Get AVRCP Unit Info. + * + * This function obtains information that pertains to the AV/C unit as a + * whole. + * + * @param ct The AVRCP CT instance. + * @param tid The transaction label of the response, valid from 0 to 15. + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_ct_get_unit_info(struct bt_avrcp_ct *ct, uint8_t tid); + +/** @brief Get AVRCP Subunit Info. + * + * This function obtains information about the subunit(s) of an AV/C unit. A + * device with AVRCP may support other subunits than the panel subunit if + * other profiles co-exist in the device. + * + * @param ct The AVRCP CT instance. + * @param tid The transaction label of the response, valid from 0 to 15. + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_ct_get_subunit_info(struct bt_avrcp_ct *ct, uint8_t tid); + +/** @brief Send AVRCP Pass Through command. + * + * This function send a pass through command to the remote device. + * Passsthhrough command is used to transfer user operation information from + * a CT to Panel subunit of TG. + * + * @param ct The AVRCP CT instance. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param opid The user operation id, see @ref bt_avrcp_opid_t. + * @param state The button state, see @ref bt_avrcp_button_state_t. + * @param payload The payload of the pass through command. Should not be + * NULL if len is not zero. + * @param len The length of the payload. + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_ct_passthrough( + struct bt_avrcp_ct *ct, uint8_t tid, uint8_t opid, uint8_t state, + const uint8_t *payload, uint8_t len); + +/** @brief Set browsed player. + * + * This function sets the browsed player on the remote device. + * + * @param ct The AVRCP CT instance. + * @param tid The transaction label of the response, valid from 0 to + * 15. + * @param player_id The player ID to be set as browsed player. + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_ct_set_browsed_player(struct bt_avrcp_ct *ct, + uint8_t tid, uint16_t player_id); + +/** @brief Register for AVRCP changed notifications with callback. + * + * This function registers for notifications from the target device. The + * notification response will be received through the provided callback + * function. + * + * @param ct The AVRCP CT instance. + * @param tid The transaction label of the response, valid from 0 to + * 15. + * @param event_id The event ID to register for, see @ref bt_avrcp_evt_t. + * @param interval The playback interval for position changed events. Other + * events will have this value set to 0 to ignore. + * @param cb The callback function to handle the changed notification + * response. + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_ct_register_notification( + struct bt_avrcp_ct *ct, uint8_t tid, uint8_t event_id, uint32_t interval, + bt_avrcp_notify_changed_cb_t cb); + +/** @brief Send AVRCP vendor dependent command for + * LIST_PLAYER_APP_SETTING_ATTRS. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response, valid from 0 to 15. + * + * @return 0 on success or error code. + */ + +int bt_avrcp_ct_list_player_app_setting_attrs(struct bt_avrcp_ct *ct, + uint8_t tid); + +/** @brief Send AVRCP vendor dependent command for + * LIST_PLAYER_APP_SETTING_VALS. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param attr_id Player application setting attribute ID for which the + * possible values should be listed (e.g., Equalizer, Repeat + * Mode, Shuffle, Scan). + * + * @return 0 on success or error code. + */ + +int bt_avrcp_ct_list_player_app_setting_vals( + struct bt_avrcp_ct *ct, uint8_t tid, uint8_t attr_id); + +/** @brief Send AVRCP vendor dependent command for + * GET_CURR_PLAYER_APP_SETTING_VAL. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param buf The command buffer containing the + * GET_CURR_PLAYER_APP_SETTING_VAL request payload, formatted as + * @ref bt_avrcp_get_curr_player_app_setting_val_cmd. + * Note that all multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ + +int bt_avrcp_ct_get_curr_player_app_setting_val( + struct bt_avrcp_ct *ct, uint8_t tid, struct net_buf *buf); + +/** @brief Send AVRCP vendor dependent command for + * SET_PLAYER_APP_SETTING_VAL. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param buf The command buffer containing the SET_PLAYER_APP_SETTING_VAL + * request payload, formatted as + * @ref bt_avrcp_set_player_app_setting_val_cmd. + * Note that all multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ + +int bt_avrcp_ct_set_player_app_setting_val( + struct bt_avrcp_ct *ct, uint8_t tid, struct net_buf *buf); + +/** @brief Send AVRCP vendor dependent command for + * GET_PLAYER_APP_SETTING_ATTR_TEXT. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param buf The command buffer containing the + * GET_PLAYER_APP_SETTING_ATTR_TEXT request payload, formatted as + * @ref bt_avrcp_get_player_app_setting_attr_text_cmd. + * Note that all multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ + +int bt_avrcp_ct_get_player_app_setting_attr_text( + struct bt_avrcp_ct *ct, uint8_t tid, struct net_buf *buf); + +/** @brief Send AVRCP vendor dependent command for + * GET_PLAYER_APP_SETTING_VAL_TEXT. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param buf The command buffer containing the + * GET_PLAYER_APP_SETTING_VAL_TEXT request payload, formatted as + * @ref bt_avrcp_get_player_app_setting_val_text_cmd. + * Note that all multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ + +int bt_avrcp_ct_get_player_app_setting_val_text( + struct bt_avrcp_ct *ct, uint8_t tid, struct net_buf *buf); + +/** @brief Send AVRCP vendor dependent command for + * INFORM_DISPLAYABLE_CHAR_SET. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param buf The command buffer containing the INFORM_DISPLAYABLE_CHAR_SET + * request payload, formatted as + * @ref bt_avrcp_inform_displayable_char_set_cmd. + * Note that all multi-octet fields are encoded in big-endian format + * + * @return 0 on success or error code. + */ + +int bt_avrcp_ct_inform_displayable_char_set( + struct bt_avrcp_ct *ct, uint8_t tid, struct net_buf *buf); + +/** @brief Send AVRCP vendor dependent command for INFORM_BATT_STATUS_OF_CT. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response, valid from 0 + * to 15. + * @param battery_status Battery status value @ref bt_avrcp_battery_status_t + * + * @return 0 on success or error code. + */ + +int bt_avrcp_ct_inform_batt_status_of_ct(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t battery_status); + +/** @brief Send Set Absolute Volume command (CT). + * + * This function sends the Set Absolute Volume command to the TG. + * + * @param ct The AVRCP CT instance. + * @param tid The transaction label of the command, valid from 0 + * to 15. + * @param absolute_volume The absolute volume value (0x00-0x7F). + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_ct_set_absolute_volume(struct bt_avrcp_ct *ct, + uint8_t tid, uint8_t absolute_volume); + +/** @brief Send AVRCP vendor dependent command for GET_ELEMENT_ATTRS. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param buf The command buffer containing the GET_ELEMENT_ATTRS request + * payload, formatted as + * @ref bt_avrcp_get_element_attrs_cmd. + * Note that all multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ + +int bt_avrcp_ct_get_element_attrs(struct bt_avrcp_ct *ct, + uint8_t tid, struct net_buf *buf); + +/** @brief Send AVRCP vendor dependent command for GET_PLAY_STATUS. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response, valid from 0 to 15. + * + * @return 0 on success or error code. + */ + +int bt_avrcp_ct_get_play_status(struct bt_avrcp_ct *ct, uint8_t tid); + +/** @brief Send AVRCP vendor dependent command for SET_ADDRESSED_PLAYER. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response, valid from 0 to + * 15. + * @param player_id The player ID to be set as addressed player. + * + * @return 0 on success or error code. + */ + +int bt_avrcp_ct_set_addressed_player(struct bt_avrcp_ct *ct, + uint8_t tid, uint16_t player_id); + +/** @brief Send AVRCP vendor dependent command for PLAY_ITEM. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param buf The command buffer containing the PLAY_ITEM request payload, + * formatted as + * @ref bt_avrcp_play_item_cmd. + * Note that all multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ + +int bt_avrcp_ct_play_item(struct bt_avrcp_ct *ct, + uint8_t tid, struct net_buf *buf); + +/** @brief Send AVRCP vendor dependent command for ADD_TO_NOW_PLAYING. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param buf The command buffer containing the ADD_TO_NOW_PLAYING request + * payload, formatted as + * @ref bt_avrcp_add_to_now_playing_cmd. + * Note that all multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ + +int bt_avrcp_ct_add_to_now_playing(struct bt_avrcp_ct *ct, + uint8_t tid, struct net_buf *buf); + +/** @brief Get Folder Items. + * + * This function sends AVRCP PDU GET_FOLDER_ITEMS to the remote device. + * + * @param ct The AVRCP CT instance. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param buf The command buffer containing the Get Folder Items command + * request payload, formatted as @ref + * bt_avrcp_get_folder_items_cmd. Note that all multi-octet + * fields are encoded in big-endian format. + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_ct_get_folder_items(struct bt_avrcp_ct *ct, + uint8_t tid, struct net_buf *buf); + +/** @brief Change Path. + * + * This function sends AVRCP PDU CHANGE_PATH to the remote device. + * + * @param ct The AVRCP CT instance. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param buf The command buffer containing the Change Path command request + * payload, formatted as @ref bt_avrcp_change_path_cmd. Note that + * all multi-octet fields are encoded in big-endian format. + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_ct_change_path(struct bt_avrcp_ct *ct, + uint8_t tid, struct net_buf *buf); + +/** @brief Get Item Attributes. + * + * This function sends AVRCP PDU GET_ITEM_ATTRIBUTES to the remote device. + * + * @param ct The AVRCP CT instance. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param buf The command buffer containing the Get Item Attributes command + * request payload, formatted as @ref + * bt_avrcp_get_item_attrs_cmd. Note that all multi-octet fields + * are encoded in big-endian format. + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_ct_get_item_attrs(struct bt_avrcp_ct *ct, + uint8_t tid, struct net_buf *buf); + +/** @brief Get Total Number Of Items. + * + * This function sends AVRCP PDU GET_TOTAL_NUMBER_OF_ITEMS to the remote + * device. + * + * @param ct The AVRCP CT instance. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param scope scope @ref bt_avrcp_scope_t. + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_ct_get_total_number_of_items(struct bt_avrcp_ct *ct, + uint8_t tid, uint8_t scope); + +/** @brief Search. + * + * This function sends AVRCP PDU SEARCH to the remote device. + * + * @param ct The AVRCP CT instance. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param buf The command buffer containing the Search command request + * payload, formatted as @ref bt_avrcp_search_cmd. Note that all + * multi-octet fields are encoded in big-endian format. + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_ct_search(struct bt_avrcp_ct *ct, + uint8_t tid, struct net_buf *buf); + +struct bt_avrcp_tg_cb +{ + /** @brief An AVRCP TG connection has been established. + * + * This callback notifies the application of an avrcp connection, i.e., an + * AVCTP L2CAP connection. + * + * @param conn Connection object. + * @param tg AVRCP TG connection object. + */ + + void (*connected)(struct bt_conn *conn, struct bt_avrcp_tg *tg); + + /** @brief An AVRCP TG connection has been disconnected. + * + * This callback notifies the application that an avrcp connection has + * been disconnected. + * + * @param tg AVRCP TG connection object. + */ + + void (*disconnected)(struct bt_avrcp_tg *tg); + + /** @brief Unit info request callback. + * + * This callback is called whenever an AVRCP unit info is requested. + * + * @param tid The transaction label of the request. + * @param tg AVRCP TG connection object. + */ + + void (*unit_info_req)(struct bt_avrcp_tg *tg, uint8_t tid); + + /** @brief Register notification request callback. + * + * This callback is called whenever an AVRCP register notification is + * requested. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param event_id The event ID that the CT wants to register for @ref + * bt_avrcp_evt_t. + * @param interval The playback interval for position changed event. other + * events will have this value set to 0 for ignoring. + */ + +void (*register_notification)(struct bt_avrcp_tg *tg, + uint8_t tid, + uint8_t event_id, + uint32_t interval); + + /** @brief Subunit Info Request callback. + * + * This callback is called whenever an AVRCP subunit info is requested. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + */ + + void (*subunit_info_req)(struct bt_avrcp_tg *tg, uint8_t tid); + + /** @brief Get capabilities request callback. + * + * This callback is called whenever an AVRCP get capabilities command is + * received. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param cap_id The capability ID requested. + */ + + void (*get_caps)(struct bt_avrcp_tg *tg, uint8_t tid, uint8_t cap_id); + + /** @brief An AVRCP TG browsing connection has been established. + * + * This callback notifies the application of an avrcp browsing connection, + * i.e., an AVCTP browsing L2CAP connection. + * + * @param conn Connection object. + * @param tg AVRCP TG connection object. + */ + + void (*browsing_connected)(struct bt_conn *conn, struct bt_avrcp_tg *tg); + + /** @brief An AVRCP TG browsing connection has been disconnected. + * + * This callback notifies the application that an avrcp browsing + * connection has been disconnected. + * + * @param tg AVRCP TG connection object. + */ + + void (*browsing_disconnected)(struct bt_avrcp_tg *tg); + + /** @brief Set browsed player request callback. + * + * This callback is called whenever an AVRCP set browsed player request is + * received. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param player_id The player ID to be set as browsed player. + */ + + void (*set_browsed_player)(struct bt_avrcp_tg *tg, uint8_t tid, + uint16_t player_id); + + /** @brief Get Folder Items request callback. + * + * This callback is called whenever an AVRCP GET_FOLDER_ITEMS request is + * received from the CT. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param buf The request buffer containing the Get Folder Items command + * parameters. The application can parse this payload according + * to the format defined in @ref bt_avrcp_get_folder_items_cmd. + * Note that all multi-octet fields are encoded in big-endian + * format. + */ + + void (*get_folder_items)(struct bt_avrcp_tg *tg, uint8_t tid, + struct net_buf *buf); + + /** @brief Change Path request callback. + * + * This callback is called whenever an AVRCP CHANGE_PATH request is + * received from the CT. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param buf The request buffer containing the Change Path command + * parameters. The application can parse this payload according + * to the format defined in @ref bt_avrcp_change_path_cmd. Note + * that all multi-octet fields are encoded in big-endian + * format. + */ + + void (*change_path)(struct bt_avrcp_tg *tg, uint8_t tid, + struct net_buf *buf); + + /** @brief Get Item Attributes request callback. + * + * This callback is called whenever an AVRCP GET_ITEM_ATTRIBUTES request + * is received from the CT. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param buf The request buffer containing the Get Item Attributes + * command parameters. The application can parse this payload + * according to the format defined in @ref + * bt_avrcp_get_item_attrs_cmd. Note that all multi-octet + * fields are encoded in big-endian format. + */ + + void (*get_item_attrs)(struct bt_avrcp_tg *tg, uint8_t tid, + struct net_buf *buf); + + /** @brief Get Total Number Of Items request callback. + * + * This callback is called whenever an AVRCP GET_TOTAL_NUMBER_OF_ITEMS + * request is received from the CT. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param scope The browsing scope for which to get the total number of + * items. See @ref bt_avrcp_scope_t for valid values. + */ + + void (*get_total_number_of_items)(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t scope); + + /** @brief Search request callback. + * + * This callback is called whenever an AVRCP SEARCH request is received + * from the CT. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param buf The request buffer containing the Search command parameters. + * The application can parse this payload according to the + * format defined in @ref bt_avrcp_search_cmd. Note that all + * multi-octet fields are encoded in big-endian format + * + */ + + void (*search)(struct bt_avrcp_tg *tg, uint8_t tid, struct net_buf *buf); + + /** @brief Pass Through command request callback. + * + * This callback is called whenever an AVRCP Pass Through command is + * request. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param buf The buffer containing the PASS THROUGH command payload. The + * application can parse this payload according to the format + * defined in @ref bt_avrcp_passthrough_cmd. Note that the data + * is encoded in big-endian format. + */ + + void (*passthrough_req)(struct bt_avrcp_tg *tg, uint8_t tid, + struct net_buf *buf); + + /** @brief Callback for PDU ID + * BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_ATTRS. + * + * Called when the TG receives a vendor dependent command + * LIST_PLAYER_APP_SETTING_ATTRS. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + */ + + void (*list_player_app_setting_attrs)(struct bt_avrcp_tg *tg, uint8_t tid); + + /** @brief Callback for PDU ID + * BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_VALS. + * + * Called when the TG receives a vendor dependent command for + * LIST_PLAYER_APP_SETTING_VALS. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + * @param attr_id Player application setting attribute ID for which the + * possible values should be listed (e.g., Equalizer, + * Repeat Mode, Shuffle, Scan). + */ + + void (*list_player_app_setting_vals)(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t attr_id); + + /** @brief Callback for PDU ID + * BT_AVRCP_PDU_ID_GET_CURR_PLAYER_APP_SETTING_VAL. + * + * Called when the TG receives a vendor dependent command + * GET_CURR_PLAYER_APP_SETTING_VAL. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + * @param buf The buffer containing the GET_CURR_PLAYER_APP_SETTING_VAL + * command payload, formatted as @ref + * bt_avrcp_get_curr_player_app_setting_val_cmd. The + * application should parse fields in big-endian order. + */ + + void (*get_curr_player_app_setting_val)(struct bt_avrcp_tg *tg, + uint8_t tid, + struct net_buf *buf); + + /** @brief Callback for PDU ID BT_AVRCP_PDU_ID_SET_PLAYER_APP_SETTING_VAL. + * + * Called when the TG receives a vendor dependent command for + * SET_PLAYER_APP_SETTING_VAL. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + * @param buf The buffer containing the SET_PLAYER_APP_SETTING_VAL command + * payload, formatted as @ref + * bt_avrcp_set_player_app_setting_val_cmd. The application + * should parse fields in big-endian order. + */ + + void (*set_player_app_setting_val)(struct bt_avrcp_tg *tg, uint8_t tid, + struct net_buf *buf); + + /** @brief Callback for PDU ID + * BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_ATTR_TEXT. + * + * Called when the TG receives a vendor dependent command + * GET_PLAYER_APP_SETTING_ATTR_TEXT. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + * @param buf The buffer containing the GET_PLAYER_APP_SETTING_ATTR_TEXT + * command payload, formatted as @ref + * bt_avrcp_get_player_app_setting_attr_text_cmd. The + * application should parse fields in big-endian order. + */ + +void (*get_player_app_setting_attr_text)(struct bt_avrcp_tg *tg, + uint8_t tid, + struct net_buf *buf); + + /** @brief Callback for PDU ID + * BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_VAL_TEXT. + * + * Called when the TG receives a vendor dependent command + * GET_PLAYER_APP_SETTING_VAL_TEXT. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + * @param buf The buffer containing the GET_PLAYER_APP_SETTING_VAL_TEXT + * command payload, formatted as @ref + * bt_avrcp_get_player_app_setting_val_text_cmd. The + * application should parse fields in big-endian order. + */ + + void (*get_player_app_setting_val_text)(struct bt_avrcp_tg *tg, + uint8_t tid, + struct net_buf *buf); + + /** @brief Callback for PDU ID BT_AVRCP_PDU_ID_INFORM_DISPLAYABLE_CHAR_SET. + * + * Called when the TG receives a vendor dependent command for + * INFORM_DISPLAYABLE_CHAR_SET. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + * @param buf The buffer containing the INFORM_DISPLAYABLE_CHAR_SET + * command payload, formatted as @ref + * bt_avrcp_inform_displayable_char_set_cmd. The application + * should parse fields in big-endian order. + */ + + void (*inform_displayable_char_set)(struct bt_avrcp_tg *tg, uint8_t tid, + struct net_buf *buf); + + /** @brief Callback for PDU ID BT_AVRCP_PDU_ID_INFORM_BATT_STATUS_OF_CT. + * + * Called when the TG receives a vendor dependent command for + * INFORM_BATT_STATUS_OF_CT. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + * @param battery_status Battery status value @ref + * bt_avrcp_battery_status_t + */ + + void (*inform_batt_status_of_ct)(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t battery_status); + + /** @brief Callback for PDU ID BT_AVRCP_PDU_ID_GET_ELEMENT_ATTRS. + * + * Called when the TG receives a vendor dependent command for + * GET_ELEMENT_ATTRS. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + * @param buf The buffer containing the GET_ELEMENT_ATTRS command payload, + * formatted as @ref bt_avrcp_get_element_attrs_cmd. The + * application should parse fields in big-endian order. + */ + + void (*get_element_attrs)(struct bt_avrcp_tg *tg, uint8_t tid, + struct net_buf *buf); + + /** @brief Callback function for Set Absolute Volume command (TG). + * + * Called when the Set Absolute Volume command is received from the CT. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + * @param absolute_volume The absolute volume value (0x00-0x7F). + */ + + void (*set_absolute_volume)(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t absolute_volume); + + /** @brief Callback for PDU ID BT_AVRCP_PDU_ID_GET_PLAY_STATUS. + * + * Called when the TG receives a vendor dependent command for + * GET_PLAY_STATUS. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + */ + + void (*get_play_status)(struct bt_avrcp_tg *tg, uint8_t tid); + + /** @brief Callback for PDU ID BT_AVRCP_PDU_ID_SET_ADDRESSED_PLAYER. + * + * Called when the TG receives a vendor dependent command for + * SET_ADDRESSED_PLAYER. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + * @param player_id The player ID to be set as addressed player. + */ + + void (*set_addressed_player)(struct bt_avrcp_tg *tg, uint8_t tid, + uint16_t player_id); + + /** @brief Callback for PDU ID BT_AVRCP_PDU_ID_PLAY_ITEM. + * + * Called when the TG receives a vendor dependent command for PLAY_ITEM. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + * @param buf The buffer containing the PLAY_ITEM command payload, + * formatted as @ref bt_avrcp_play_item_cmd. The application + * should parse fields in big-endian order. + */ + + void (*play_item)(struct bt_avrcp_tg *tg, + uint8_t tid, + struct net_buf *buf); + + /** @brief Callback for PDU ID BT_AVRCP_PDU_ID_ADD_TO_NOW_PLAYING. + * + * Called when the TG receives a vendor dependent command for + * ADD_TO_NOW_PLAYING. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + * @param buf The buffer containing the ADD_TO_NOW_PLAYING command + * payload, formatted as @ref bt_avrcp_add_to_now_playing_cmd. + * The application should parse fields in big-endian order. + */ + + void (*add_to_now_playing)(struct bt_avrcp_tg *tg, uint8_t tid, + struct net_buf *buf); +}; + +/** @brief Register callback. + * + * Register AVRCP callbacks to monitor the state and interact with the + * remote device. + * + * @param cb The AVRCP TG callback function. + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_tg_register_cb(const struct bt_avrcp_tg_cb *cb); + +/** @brief Send the unit info response. + * + * This function is called by the application to send the unit info + * response. + * + * @param tg The AVRCP TG instance. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param rsp The response for UNIT INFO command. + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_tg_send_unit_info_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + struct bt_avrcp_unit_info_rsp *rsp); + +/** @brief Send the subunit info response. + * + * This function is called by the application to send the subunit info + * response. + * + * @param tg The AVRCP TG instance. + * @param tid The transaction label of the response, valid from 0 to 15. + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_tg_send_subunit_info_rsp(struct bt_avrcp_tg *tg, uint8_t tid); + +/** @brief Send GET_CAPABILITIES response. + * + * This function is called by the application to send the GET_CAPABILITIES + * response. + * + * @param tg The AVRCP TG instance. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * @param buf The response buffer containing the GET_CAPS payload, + * formatted as @ref bt_avrcp_get_caps_rsp. Note that all + * multi-octet fields are encoded in big-endian format. + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_tg_get_caps(struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, + struct net_buf *buf); + +/** @brief Send notification response. + * + * This function sends a notification response from the AVRCP Target (TG) to + * the Controller (CT) for a previously registered event. The response can + * be: + * + * - **INTERIM**: Sent on the first call for the given @p event_id to + * indicate that the event is being monitored. + * - **CHANGED**: Sent on the next call for the same @p event_id when the + * event state has changed. + * + * @param tg The AVRCP TG instance. + * @param tid The transaction label of the response, valid from 0 to + * 15. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * @param event_id The AVRCP event ID for which the notification is sent, + * @ref bt_avrcp_evt_t. + * @param data Pointer to an bt_avrcp_event_data structure containing the + * event-specific data. The content of the union depends on the + * event_id. + * + * @return 0 in case of success or error code in case of error. + * + * @note + * - The first successful call for a given @p event_id sends an INTERIM + * response. + * - The next successful call for the same @p event_id sends a CHANGED + * response. + * - If @p status is BT_AVRCP_STATUS_NOT_IMPLEMENTED, a NOT_IMPLEMENTED + * response is sent. + * - If @p status is any other non-SUCCESS value, a REJECTED response is + * sent. + */ + +int bt_avrcp_tg_notification( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, uint8_t event_id, + struct bt_avrcp_event_data *data); + +/** @brief Send the set browsed player response. + * + * This function is called by the application to send the set browsed player + * response. + * + * @param tg The AVRCP TG instance. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param buf The response buffer containing the set browsed player response + * data. + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_tg_set_browsed_player(struct bt_avrcp_tg *tg, + uint8_t tid, struct net_buf *buf); + +/** @brief Send AVRCP Pass Through response. + * + * This function is called by the application to send the Pass Through + * response. + * + * @param tg The AVRCP TG instance. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param result The response code, see @ref bt_avrcp_rsp_t, can support + * 0x8(NOT_IMPLEMENTED), 0x9 (ACCEPTED), 0xA (REJECTED) + * @param buf The buffer containing the PASS THROUGH command payload. The + * application can construct this payload according to the + * format defined in @ref bt_avrcp_passthrough_rsp. Note that + * the data is encoded in big-endian format. + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_tg_send_passthrough_rsp( + struct bt_avrcp_tg *tg, uint8_t tid, bt_avrcp_rsp_t result, + struct net_buf *buf); + +/** @brief Send response for PDU ID + * BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_ATTRS. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * @param buf The response buffer containing the + * LIST_PLAYER_APP_SETTING_ATTRS payload, formatted as @ref + * bt_avrcp_list_player_app_setting_attrs_rsp. Note that all + * multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ + +int bt_avrcp_tg_list_player_app_setting_attrs( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, struct net_buf *buf); + +/** @brief Send response for PDU ID + * BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_VALS. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * @param buf The response buffer containing the + * LIST_PLAYER_APP_SETTING_VALS payload, formatted as @ref + * bt_avrcp_list_player_app_setting_vals_rsp. Note that all + * multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ + +int bt_avrcp_tg_list_player_app_setting_vals( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, struct net_buf *buf); + +/** @brief Send response for PDU ID + * BT_AVRCP_PDU_ID_GET_CURR_PLAYER_APP_SETTING_VAL. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * @param buf The response buffer containing the + * GET_CURR_PLAYER_APP_SETTING_VAL payload, formatted as @ref + * bt_avrcp_get_curr_player_app_setting_val_rsp. Note that all + * multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ + +int bt_avrcp_tg_get_curr_player_app_setting_val( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, struct net_buf *buf); + +/** @brief Send response for PDU ID + * BT_AVRCP_PDU_ID_SET_PLAYER_APP_SETTING_VAL. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * + * @return 0 on success or error code. + */ + +int bt_avrcp_tg_set_player_app_setting_val(struct bt_avrcp_tg *tg, + uint8_t tid, uint8_t status); + +/** @brief Send response for PDU ID + * BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_ATTR_TEXT. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * @param buf The response buffer containing the + * GET_PLAYER_APP_SETTING_ATTR_TEXT payload, formatted as @ref + * bt_avrcp_get_player_app_setting_attr_text_rsp. Note that + * all multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ + +int bt_avrcp_tg_get_player_app_setting_attr_text( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, struct net_buf *buf); + +/** @brief Send response for PDU ID + * BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_VAL_TEXT. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * @param buf The response buffer containing the + * GET_PLAYER_APP_SETTING_VAL_TEXT payload, formatted as @ref + * bt_avrcp_get_player_app_setting_val_text_rsp. Note that all + * multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ + +int bt_avrcp_tg_get_player_app_setting_val_text( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, struct net_buf *buf); + +/** @brief Send response for PDU ID + * BT_AVRCP_PDU_ID_INFORM_DISPLAYABLE_CHAR_SET. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * + * @return 0 on success or error code. + */ + +int bt_avrcp_tg_inform_displayable_char_set(struct bt_avrcp_tg *tg, + uint8_t tid, uint8_t status); + +/** @brief Send response for PDU ID BT_AVRCP_PDU_ID_INFORM_BATT_STATUS_OF_CT. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * + * @return 0 on success or error code. + */ + +int bt_avrcp_tg_inform_batt_status_of_ct(struct bt_avrcp_tg *tg, + uint8_t tid, uint8_t status); + +/** @brief Send Set Absolute Volume response (TG). + * + * This function sends the Set Absolute Volume response to the CT. + * + * @param tg The AVRCP TG instance. + * @param tid The transaction label of the response, valid from + * 0 to 15. + * @param status Status code of the operation @ref + * bt_avrcp_status_t. + * @param absolute_volume The absolute volume value (0x00-0x7F). + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_tg_absolute_volume( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, + uint8_t absolute_volume); + +/** @brief Send response for PDU ID BT_AVRCP_PDU_ID_GET_ELEMENT_ATTRS. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * @param buf The response buffer containing the GET_ELEMENT_ATTRS + * payload, formatted as @ref bt_avrcp_get_element_attrs_rsp. + * Note that all multi-octet fields are encoded in big-endian + * format. + * + * @return 0 on success or error code. + */ + +int bt_avrcp_tg_get_element_attrs( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, struct net_buf *buf); + +/** @brief Send response for PDU ID BT_AVRCP_PDU_ID_GET_PLAY_STATUS. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * @param buf The response buffer containing the GET_PLAY_STATUS payload, + * formatted as @ref bt_avrcp_get_play_status_rsp. Note that + * all multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ + +int bt_avrcp_tg_get_play_status( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, struct net_buf *buf); + +/** @brief Send response for PDU ID BT_AVRCP_PDU_ID_SET_ADDRESSED_PLAYER. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * + * @return 0 on success or error code. + */ + +int bt_avrcp_tg_set_addressed_player(struct bt_avrcp_tg *tg, + uint8_t tid, uint8_t status); + +/** @brief Send response for PDU ID BT_AVRCP_PDU_ID_PLAY_ITEM. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * + * @return 0 on success or error code. + */ + +int bt_avrcp_tg_play_item(struct bt_avrcp_tg *tg, + uint8_t tid, uint8_t status); + +/** @brief Send response for PDU ID BT_AVRCP_PDU_ID_ADD_TO_NOW_PLAYING. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * + * @return 0 on success or error code. + */ + +int bt_avrcp_tg_add_to_now_playing(struct bt_avrcp_tg *tg, + uint8_t tid, uint8_t status); + +/** @brief Send the Get Folder Items response. + * + * This function is called by the application to send the GET_FOLDER_ITEMS + * response. + * + * @param tg The AVRCP TG instance. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param buf The response buffer containing the Get Folder Items response + * parameters. If the operation is successful, the application + * should format this payload according to the format defined in + * @ref bt_avrcp_get_folder_items_rsp. If status indicates a + * reject error or operation not completed, buf only contains a single + * status byte. Note that all multi-octet fields are encoded in big-endian + * format. + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_tg_get_folder_items(struct bt_avrcp_tg *tg, + uint8_t tid, struct net_buf *buf); + +/** @brief Send the Change Path response. + * + * This function is called by the application to send the CHANGE_PATH + * response. + * + * @param tg The AVRCP TG instance. + * @param tid The transaction label of the response, valid from 0 to + * 15. + * @param status Operation status (see @ref bt_avrcp_status_t). + * @param num_items Number of items in the new folder (valid only if status + * is BT_AVRCP_STATUS_OPERATION_COMPLETED). + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_tg_change_path( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, uint32_t num_items); + +/** @brief Send the Get Item Attributes response. + * + * This function is called by the application to send the + * GET_ITEM_ATTRIBUTES response. + * + * @param tg The AVRCP TG instance. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param buf The response buffer containing the Get Item Attributes + * response parameters. If the operation is successful, the + * application should format this payload according to the format + * defined in + * @ref bt_avrcp_get_item_attrs_rsp. If status indicates a reject + * error or operation not completed, buf only contains a single status byte. + * Note that all multi-octet fields are encoded in big-endian format. + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_tg_get_item_attrs(struct bt_avrcp_tg *tg, + uint8_t tid, struct net_buf *buf); + +/** @brief Send the Get Total Number Of Items response. + * + * This function is called by the application to send the + * GET_TOTAL_NUMBER_OF_ITEMS response. + * + * @param tg The AVRCP TG instance. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param buf The response buffer containing the Get Total Number Of Items + * response parameters. If the operation is successful, the + * application should format this payload according to the format + * defined in + * @ref bt_avrcp_get_total_number_of_items_rsp. If status + * indicates a reject error or operation not completed, buf only contains a + * single status byte. Note that all multi-octet fields are encoded in + * big-endian format. + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_tg_get_total_number_of_items( + struct bt_avrcp_tg *tg, uint8_t tid, struct net_buf *buf); + +/** @brief Send the Search response. + * + * This function is called by the application to send the SEARCH response. + * + * @param tg The AVRCP TG instance. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param buf The response buffer containing the Search response parameters. + * If the operation is successful, the application should format + * this payload according to the format defined in @ref + * bt_avrcp_search_rsp. If status indicates a reject error or + * operation not completed, buf only contains a single status + * byte. Note that all multi-octet fields are encoded in + * big-endian format. + * + * @return 0 in case of success or error code in case of error. + */ + +int bt_avrcp_tg_search(struct bt_avrcp_tg *tg, + uint8_t tid, struct net_buf *buf); + +/** @brief Send General Reject response on the AVRCP Browsing channel (TG). + * + * This API sends a GENERAL_REJECT (PDU ID: BT_AVRCP_PDU_ID_GENERAL_REJECT) + * response for a specific Browsing command when the request PDU is not + * understood, has invalid parameters or cannot be processed generically. The + * browsing payload format is: PDU_ID (0xA0) + ParameterLength (0x0001) + + * status (1B). + * + * @param tg AVRCP Target instance. + * @param tid Transaction label of the request (0..15). + * @param status Status code (bt_avrcp_status_t), e.g. + * BT_AVRCP_STATUS_INVALID_COMMAND (0x00), + * BT_AVRCP_STATUS_INVALID_PARAMETER (0x01), etc. + * + * @return 0 on success, or a negative error code on failure. + */ + +int bt_avrcp_tg_browsing_general_reject(struct bt_avrcp_tg *tg, + uint8_t tid, uint8_t status); +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_AVRCP_H */ diff --git a/include/nuttx/net/bluetooth/classic/avrcp_cover_art.h b/include/nuttx/net/bluetooth/classic/avrcp_cover_art.h new file mode 100644 index 0000000000000..5a44ad71a80da --- /dev/null +++ b/include/nuttx/net/bluetooth/classic/avrcp_cover_art.h @@ -0,0 +1,527 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/classic/avrcp_cover_art.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Audio Video Remote Control Cover Art Profile header. + */ + +/* Copyright 2025-2026 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_AVRCP_COVER_ART_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_AVRCP_COVER_ART_H + +/** + * @brief Audio Video Remote Control Cover Art Profile (AVRCP-CA) + * @defgroup bt_avrcp_cover_art Audio Video Remote Control Cover Art Profile + * @ingroup bt_avrcp + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @struct bt_avrcp_ct + * @brief AVRCP CT structure + */ + +struct bt_avrcp_ct; +/** + * @struct bt_avrcp_tg + * @brief AVRCP TG structure + */ + +struct bt_avrcp_tg; + +/** + * @brief AVRCP Controller Cover Art structure + * + * This structure represents an AVRCP Controller Cover Art instance, which + * handles cover art retrieval from AVRCP Target devices. + */ + +struct bt_avrcp_cover_art_ct +{ + /** Basic Image Profile instance */ + + struct bt_bip bip; + + /** BIP client instance */ + + struct bt_bip_client client; + + /** Associated AVRCP controller */ + + struct bt_avrcp_ct *ct; + + /** Bluetooth connection */ + + struct bt_conn *conn; +}; + +/** + * @brief AVRCP Target Cover Art structure + * + * This structure represents an AVRCP Target Cover Art instance, which serves + * cover art to AVRCP Controller devices. + */ + +struct bt_avrcp_cover_art_tg +{ + /** Basic Image Profile instance */ + + struct bt_bip bip; + + /** BIP server instance */ + + struct bt_bip_server server; + + /** Associated AVRCP target */ + + struct bt_avrcp_tg *tg; + + /** Bluetooth connection */ + + struct bt_conn *conn; +}; + +/** + * @brief AVRCP Cover Art Controller callback structure + * + * This structure defines callback functions that will be called when various + * AVRCP Cover Art Controller events occur. + */ + +struct bt_avrcp_cover_art_ct_cb +{ + /** + * @brief L2CAP connected callback + * + * Called when the underlying transport (L2CAP) is connected + * + * @param ct AVRCP controller instance + * @param cover_art_ct AVRCP Cover Art controller instance + */ + + void (*l2cap_connected)(struct bt_avrcp_ct *ct, + struct bt_avrcp_cover_art_ct *cover_art_ct); + + /** + * @brief L2CAP disconnected callback + * + * Called when the underlying transport is disconnected + * + * @param ct AVRCP Cover Art controller instance + */ + + void (*l2cap_disconnected)(struct bt_avrcp_cover_art_ct *ct); + + /** + * @brief OBEX connect response callback + * + * Called when a connect response is received from the target + * + * @param ct AVRCP Cover Art controller instance + * @param rsp_code OBEX response code + * @param version OBEX version + * @param mopl Maximum OBEX packet length + * @param buf Response data buffer + */ + +void (*connect)(struct bt_avrcp_cover_art_ct *ct, + uint8_t rsp_code, + uint8_t version, + uint16_t mopl, + struct net_buf *buf); + + /** + * @brief OBEX disconnect response callback + * + * @param ct AVRCP Cover Art controller instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + */ + + void (*disconnect)(struct bt_avrcp_cover_art_ct *ct, uint8_t rsp_code, + struct net_buf *buf); + + /** + * @brief OBEX abort response callback + * + * @param ct AVRCP Cover Art controller instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + */ + + void (*abort)(struct bt_avrcp_cover_art_ct *ct, uint8_t rsp_code, + struct net_buf *buf); + + /** + * @brief Get image properties response callback + * + * @param ct AVRCP Cover Art controller instance + * @param rsp_code OBEX response code + * @param buf Response data buffer containing image properties + */ + +void (*get_image_properties)(struct bt_avrcp_cover_art_ct *ct, + uint8_t rsp_code, + struct net_buf *buf); + + /** + * @brief Get image response callback + * + * @param ct AVRCP Cover Art controller instance + * @param rsp_code OBEX response code + * @param buf Response data buffer containing image data + */ + + void (*get_image)(struct bt_avrcp_cover_art_ct *ct, uint8_t rsp_code, + struct net_buf *buf); + + /** + * @brief Get linked thumbnail response callback + * + * @param ct AVRCP Cover Art controller instance + * @param rsp_code OBEX response code + * @param buf Response data buffer containing thumbnail data + */ + +void (*get_linked_thumbnail)(struct bt_avrcp_cover_art_ct *ct, + uint8_t rsp_code, + struct net_buf *buf); +}; + +/** + * @brief AVRCP Cover Art Target callback structure + * + * This structure defines callback functions that will be called when various + * AVRCP Cover Art Target events occur. + */ + +struct bt_avrcp_cover_art_tg_cb +{ + /** + * @brief L2CAP connected callback + * + * Called when the underlying transport (L2CAP) is connected + * + * @param tg AVRCP target instance + * @param cover_art_tg AVRCP Cover Art target instance + */ + + void (*l2cap_connected)(struct bt_avrcp_tg *tg, + struct bt_avrcp_cover_art_tg *cover_art_tg); + + /** + * @brief L2CAP disconnected callback + * + * Called when the underlying transport is disconnected + * + * @param tg AVRCP Cover Art target instance + */ + + void (*l2cap_disconnected)(struct bt_avrcp_cover_art_tg *tg); + + /** + * @brief OBEX connect request callback + * + * @param tg AVRCP Cover Art target instance + * @param version OBEX version + * @param mopl Maximum OBEX packet length + * @param buf Request data buffer + */ + +void (*connect)(struct bt_avrcp_cover_art_tg *tg, + uint8_t version, + uint16_t mopl, + struct net_buf *buf); + + /** + * @brief OBEX disconnect request callback + * + * @param tg AVRCP Cover Art target instance + * @param buf Request data buffer + */ + + void (*disconnect)(struct bt_avrcp_cover_art_tg *tg, struct net_buf *buf); + + /** + * @brief OBEX abort request callback + * + * @param tg AVRCP Cover Art target instance + * @param buf Request data buffer + */ + + void (*abort)(struct bt_avrcp_cover_art_tg *tg, struct net_buf *buf); + + /** + * @brief Get image properties request callback + * + * @param tg AVRCP Cover Art target instance + * @param final True if this is the final packet in the request + * @param buf Request data buffer + */ + + void (*get_image_properties)(struct bt_avrcp_cover_art_tg *tg, bool final, + struct net_buf *buf); + + /** + * @brief Get image request callback + * + * @param tg AVRCP Cover Art target instance + * @param final True if this is the final packet in the request + * @param buf Request data buffer + */ + + void (*get_image)(struct bt_avrcp_cover_art_tg *tg, bool final, + struct net_buf *buf); + + /** + * @brief Get linked thumbnail request callback + * + * @param tg AVRCP Cover Art target instance + * @param final True if this is the final packet in the request + * @param buf Request data buffer + */ + + void (*get_linked_thumbnail)(struct bt_avrcp_cover_art_tg *tg, bool final, + struct net_buf *buf); +}; + +/** + * @brief Register AVRCP Cover Art Controller callbacks + * + * @param cb Pointer to callback structure + * @return 0 on success, negative error code on failure + */ + +int bt_avrcp_cover_art_ct_cb_register(struct bt_avrcp_cover_art_ct_cb *cb); + +/** + * @brief Register AVRCP Cover Art target callbacks + * + * @param cb Pointer to callback structure + * @return 0 on success, negative error code on failure + */ + +int bt_avrcp_cover_art_tg_cb_register(struct bt_avrcp_cover_art_tg_cb *cb); + +/** + * @brief Connect L2CAP channel for AVRCP Cover Art Controller + * + * @param ct AVRCP controller instance + * @param cover_art_ct Pointer to store the created Cover Art controller + * instance + * @param psm Protocol Service Multiplexer for the L2CAP connection + * @return 0 on success, negative error code on failure + */ + +int bt_avrcp_cover_art_ct_l2cap_connect(struct bt_avrcp_ct *ct, + struct bt_avrcp_cover_art_ct **cover_art_ct, + uint16_t psm); + +/** + * @brief Disconnect L2CAP channel for AVRCP Cover Art Controller + * + * @param ct AVRCP Cover Art controller instance + * @return 0 on success, negative error code on failure + */ + +int bt_avrcp_cover_art_ct_l2cap_disconnect(struct bt_avrcp_cover_art_ct *ct); + +/** + * @brief Disconnect L2CAP channel for AVRCP Cover Art Target + * + * @param tg AVRCP Cover Art target instance + * @return 0 on success, negative error code on failure + */ + +int bt_avrcp_cover_art_tg_l2cap_disconnect(struct bt_avrcp_cover_art_tg *tg); + +/** + * @brief Create PDU buffer for AVRCP Cover Art Controller + * + * @param ct AVRCP Cover Art controller instance + * @param pool Buffer pool to allocate from + * @return Allocated buffer or NULL on failure + */ + +struct net_buf *bt_avrcp_cover_art_ct_create_pdu( + struct bt_avrcp_cover_art_ct *ct, struct net_buf_pool *pool); + +/** + * @brief Create PDU buffer for AVRCP Cover Art Target + * + * @param tg AVRCP Cover Art target instance + * @param pool Buffer pool to allocate from + * @return Allocated buffer or NULL on failure + */ + +struct net_buf *bt_avrcp_cover_art_tg_create_pdu( + struct bt_avrcp_cover_art_tg *tg, struct net_buf_pool *pool); + +/** + * @brief Send OBEX connect request + * + * @param ct AVRCP Cover Art controller instance + * @return 0 on success, negative error code on failure + */ + +int bt_avrcp_cover_art_ct_connect(struct bt_avrcp_cover_art_ct *ct); + +/** + * @brief Send OBEX connect response + * + * @param tg AVRCP Cover Art target instance + * @param rsp_code OBEX response code + * @return 0 on success, negative error code on failure + */ + +int bt_avrcp_cover_art_tg_connect(struct bt_avrcp_cover_art_tg *tg, + uint8_t rsp_code); + +/** + * @brief Send OBEX disconnect request + * + * @param ct AVRCP Cover Art controller instance + * @return 0 on success, negative error code on failure + */ + +int bt_avrcp_cover_art_ct_disconnect(struct bt_avrcp_cover_art_ct *ct); + +/** + * @brief Send OBEX disconnect response + * + * @param tg AVRCP Cover Art target instance + * @param rsp_code OBEX response code + * @return 0 on success, negative error code on failure + */ + +int bt_avrcp_cover_art_tg_disconnect(struct bt_avrcp_cover_art_tg *tg, + uint8_t rsp_code); + +/** + * @brief Send OBEX abort request + * + * @param ct AVRCP Cover Art controller instance + * @param buf Request data buffer + * @return 0 on success, negative error code on failure + */ + +int bt_avrcp_cover_art_ct_abort(struct bt_avrcp_cover_art_ct *ct, + struct net_buf *buf); + +/** + * @brief Send OBEX abort response + * + * @param tg AVRCP Cover Art target instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + * @return 0 on success, negative error code on failure + */ + +int bt_avrcp_cover_art_tg_abort( + struct bt_avrcp_cover_art_tg *tg, uint8_t rsp_code, struct net_buf *buf); + +/** + * @brief Send get image properties request + * + * @param ct AVRCP Cover Art controller instance + * @param final True if this is the final packet in the request + * @param buf Request data buffer containing image handle + * @return 0 on success, negative error code on failure + */ + +int bt_avrcp_cover_art_ct_get_image_properties( + struct bt_avrcp_cover_art_ct *ct, bool final, struct net_buf *buf); + +/** + * @brief Send get image properties response + * + * @param tg AVRCP Cover Art target instance + * @param rsp_code OBEX response code + * @param buf Response data buffer containing image properties + * @return 0 on success, negative error code on failure + */ + +int bt_avrcp_cover_art_tg_get_image_properties( + struct bt_avrcp_cover_art_tg *tg, uint8_t rsp_code, struct net_buf *buf); + +/** + * @brief Send get image request + * + * @param ct AVRCP Cover Art controller instance + * @param final True if this is the final packet in the request + * @param buf Request data buffer containing image descriptor + * @return 0 on success, negative error code on failure + */ + +int bt_avrcp_cover_art_ct_get_image( + struct bt_avrcp_cover_art_ct *ct, bool final, struct net_buf *buf); + +/** + * @brief Send get image response + * + * @param tg AVRCP Cover Art target instance + * @param rsp_code OBEX response code + * @param buf Response data buffer containing image data + * @return 0 on success, negative error code on failure + */ + +int bt_avrcp_cover_art_tg_get_image( + struct bt_avrcp_cover_art_tg *tg, uint8_t rsp_code, struct net_buf *buf); + +/** + * @brief Send get linked thumbnail request + * + * @param ct AVRCP Cover Art controller instance + * @param final True if this is the final packet in the request + * @param buf Request data buffer containing image handle + * @return 0 on success, negative error code on failure + */ + +int bt_avrcp_cover_art_ct_get_linked_thumbnail( + struct bt_avrcp_cover_art_ct *ct, bool final, struct net_buf *buf); + +/** + * @brief Send get linked thumbnail response + * + * @param tg AVRCP Cover Art target instance + * @param rsp_code OBEX response code + * @param buf Response data buffer containing thumbnail data + * @return 0 on success, negative error code on failure + */ + +int bt_avrcp_cover_art_tg_get_linked_thumbnail( + struct bt_avrcp_cover_art_tg *tg, uint8_t rsp_code, struct net_buf *buf); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_AVRCP_COVER_ART_H */ diff --git a/include/nuttx/net/bluetooth/classic/bip.h b/include/nuttx/net/bluetooth/classic/bip.h new file mode 100644 index 0000000000000..26447b98e3307 --- /dev/null +++ b/include/nuttx/net/bluetooth/classic/bip.h @@ -0,0 +1,2500 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/classic/bip.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* bip.h - Bluetooth Basic Imaging Profile handling */ + +/* Copyright 2025-2026 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_BIP_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_BIP_H + +/** + * @brief Basic Imaging Profile (BIP) + * @defgroup bt_bip Basic Imaging Profile (BIP) + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** @brief OBEX Type header for Get Capabilities operation */ +#define BT_BIP_HDR_TYPE_GET_CAPS "x-bt/img-capabilities" +/** @brief OBEX Type header for Get Image List operation */ +#define BT_BIP_HDR_TYPE_GET_IMAGE_LIST "x-bt/img-listing" +/** @brief OBEX Type header for Get Image Properties operation */ +#define BT_BIP_HDR_TYPE_GET_IMAGE_PROPERTIES "x-bt/img-properties" +/** @brief OBEX Type header for Get Image operation */ +#define BT_BIP_HDR_TYPE_GET_IMAGE "x-bt/img-img" +/** @brief OBEX Type header for Get Linked Thumbnail operation */ +#define BT_BIP_HDR_TYPE_GET_LINKED_THUMBNAIL "x-bt/img-thm" +/** @brief OBEX Type header for Get Linked Attachment operation */ +#define BT_BIP_HDR_TYPE_GET_LINKED_ATTACHMENT "x-bt/img-attachment" +/** @brief OBEX Type header for Get Partial Image operation */ +#define BT_BIP_HDR_TYPE_GET_PARTIAL_IMAGE "x-bt/img-partial" +/** @brief OBEX Type header for Get Monitoring Image operation */ +#define BT_BIP_HDR_TYPE_GET_MONITORING_IMAGE "x-bt/img-monitoring" +/** @brief OBEX Type header for Get Status operation */ +#define BT_BIP_HDR_TYPE_GET_STATUS "x-bt/img-status" +/** @brief OBEX Type header for Put Image operation */ +#define BT_BIP_HDR_TYPE_PUT_IMAGE "x-bt/img-img" +/** @brief OBEX Type header for Put Linked Thumbnail operation */ +#define BT_BIP_HDR_TYPE_PUT_LINKED_THUMBNAIL "x-bt/img-thm" +/** @brief OBEX Type header for Put Linked Attachment operation */ +#define BT_BIP_HDR_TYPE_PUT_LINKED_ATTACHMENT "x-bt/img-attachment" +/** @brief OBEX Type header for Remote Display operation */ +#define BT_BIP_HDR_TYPE_REMOTE_DISPLAY "x-bt/img-display" +/** @brief OBEX Type header for Delete Image operation */ +#define BT_BIP_HDR_TYPE_DELETE_IMAGE "x-bt/img-img" +/** @brief OBEX Type header for Start Print operation */ +#define BT_BIP_HDR_TYPE_START_PRINT "x-bt/img-print" +/** @brief OBEX Type header for Start Archive operation */ +#define BT_BIP_HDR_TYPE_START_ARCHIVE "x-bt/img-archive" + +/** + * @brief Img-Descriptor header ID + * + * Byte sequence, length prefixed with a two-byte unsigned integer + */ +#define BT_BIP_HEADER_ID_IMG_DESC 0x71 + +/** + * @brief Img-Handle header ID + * + * Null terminated, UTF-16 encoded Unicode text length prefixed with a + * two-byte unsigned integer + */ +#define BT_BIP_HEADER_ID_IMG_HANDLE 0x30 + +/** + * @brief BIP Application Parameter Tag IDs + * + * These tag IDs are used in OBEX application parameter headers for various + * BIP operations. + */ + +enum bt_bip_appl_param_tag_id +{ + /** @brief Number of returned handles parameter */ + + BT_BIP_APPL_PARAM_TAG_ID_RETURNED_HANDLES = 0x01, + + /** @brief List start offset parameter */ + + BT_BIP_APPL_PARAM_TAG_ID_LIST_START_OFFSET = 0x02, + + /** @brief Latest captured images parameter */ + + BT_BIP_APPL_PARAM_TAG_ID_LATEST_CAPTURED_IMAGES = 0x03, + + /** @brief Partial file length parameter */ + + BT_BIP_APPL_PARAM_TAG_ID_PARTIAL_FILE_LEN = 0x04, + + /** @brief Partial file start offset parameter */ + + BT_BIP_APPL_PARAM_TAG_ID_PARTIAL_FILE_START_OFFSET = 0x05, + + /** @brief Total file size parameter */ + + BT_BIP_APPL_PARAM_TAG_ID_TOTAL_FILE_SIZE = 0x06, + + /** @brief End flag parameter */ + + BT_BIP_APPL_PARAM_TAG_ID_END_FLAG = 0x07, + + /** @brief Remote display parameter */ + + BT_BIP_APPL_PARAM_TAG_ID_REMOTE_DISPLAY = 0x08, + + /** @brief Service ID parameter */ + + BT_BIP_APPL_PARAM_TAG_ID_SERVICE_ID = 0x09, + + /** @brief Store flag parameter */ + + BT_BIP_APPL_PARAM_TAG_ID_STORE_FLAG = 0x0a, +}; + +/** @brief Forward declaration of BIP instance structure */ + +struct bt_bip; + +/** + * @brief BIP transport layer operations + * + * Callback structure for transport layer events + */ + +struct bt_bip_transport_ops +{ + /* @brief Transport connected callback + * + * Called when the underlying transport (RFCOMM/L2CAP) is connected + * + * @param conn Bluetooth connection + * @param bip BIP instance + */ + + void (*connected)(struct bt_conn *conn, struct bt_bip *bip); + + /* @brief Transport disconnected callback + * + * Called when the underlying transport is disconnected + * + * @param bip BIP instance + */ + + void (*disconnected)(struct bt_bip *bip); +}; + +/** + * @brief BIP RFCOMM server structure + * + * Structure for BIP server using RFCOMM transport + */ + +struct bt_bip_rfcomm_server +{ + /** @brief Underlying GOEP RFCOMM server */ + + struct bt_goep_transport_rfcomm_server server; + + /* @brief Accept connection callback + * + * Called when a new RFCOMM connection is accepted + * + * @param conn Bluetooth connection + * @param server RFCOMM server instance + * @param bip Pointer to store the created BIP instance + * @return 0 on success, negative error code on failure + */ + + int (*accept)(struct bt_conn *conn, struct bt_bip_rfcomm_server *server, + struct bt_bip **bip); +}; + +/** + * @brief Register BIP RFCOMM server + * + * @param server RFCOMM server to register + * @return 0 on success, negative error code on failure + */ + +int bt_bip_rfcomm_register(struct bt_bip_rfcomm_server *server); + +/** + * @brief Create transport connection over RFCOMM + * + * @param conn ACL conn object + * @param bip BIP instance + * @param channel RFCOMM channel number + * @return 0 on success, negative error code on failure + */ + +int bt_bip_rfcomm_connect(struct bt_conn *conn, + struct bt_bip *bip, uint8_t channel); + +/** + * @brief Disconnect BIP RFCOMM connection + * + * @param bip BIP instance + * @return 0 on success, negative error code on failure + */ + +int bt_bip_rfcomm_disconnect(struct bt_bip *bip); + +/** + * @brief BIP L2CAP server structure + * + * Structure for BIP server using L2CAP transport + */ + +struct bt_bip_l2cap_server +{ + /** @brief Underlying GOEP L2CAP server */ + + struct bt_goep_transport_l2cap_server server; + + /* @brief Accept connection callback + * + * Called when a new L2CAP connection is accepted + * + * @param conn Bluetooth connection + * @param server L2CAP server instance + * @param bip Pointer to store the created BIP instance + * @return 0 on success, negative error code on failure + */ + + int (*accept)(struct bt_conn *conn, struct bt_bip_l2cap_server *server, + struct bt_bip **bip); +}; + +/** + * @brief Register BIP L2CAP server + * + * @param server L2CAP server to register + * @return 0 on success, negative error code on failure + */ + +int bt_bip_l2cap_register(struct bt_bip_l2cap_server *server); + +/** + * @brief Create transport connection over L2CAP + * + * @param conn ACL conn object + * @param bip BIP instance + * @param psm L2CAP PSM (Protocol Service Multiplexer) + * @return 0 on success, negative error code on failure + */ + +int bt_bip_l2cap_connect(struct bt_conn *conn, + struct bt_bip *bip, uint16_t psm); + +/** + * @brief Disconnect BIP L2CAP connection + * + * @param bip BIP instance + * @return 0 on success, negative error code on failure + */ + +int bt_bip_l2cap_disconnect(struct bt_bip *bip); + +/** + * @brief BIP OBEX connection types + * + * Defines the different types of BIP connections as per BIP specification + */ + +enum __packed bt_bip_conn_type +{ + /** @brief Primary Image Push connection */ + + BT_BIP_PRIM_CONN_TYPE_IMAGE_PUSH = 0, + + /** @brief Primary Image Pull connection */ + + BT_BIP_PRIM_CONN_TYPE_IMAGE_PULL = 1, + + /** @brief Primary Advanced Image Printing connection */ + + BT_BIP_PRIM_CONN_TYPE_ADVANCED_IMAGE_PRINTING = 2, + + /** @brief Primary Auto Archive connection */ + + BT_BIP_PRIM_CONN_TYPE_AUTO_ARCHIVE = 3, + + /** @brief Primary Remote Camera connection */ + + BT_BIP_PRIM_CONN_TYPE_REMOTE_CAMERA = 4, + + /** @brief Primary Remote Display connection */ + + BT_BIP_PRIM_CONN_TYPE_REMOTE_DISPLAY = 5, + + /** @brief Secondary Referenced Objects connection */ + + BT_BIP_2ND_CONN_TYPE_REFERENCED_OBJECTS = 10, + + /** @brief Secondary Archived Objects connection */ + + BT_BIP_2ND_CONN_TYPE_ARCHIVED_OBJECTS = 11, +}; + +/** + * @brief BIP role types + * + * Defines the role of the device in BIP connection + */ + +enum __packed bt_bip_role +{ + /** @brief Unknown role */ + + BT_BIP_ROLE_UNKNOWN = 0, + + /** @brief Connection initiator */ + + BT_BIP_ROLE_INITIATOR = 1, + + /** @brief Connection responder */ + + BT_BIP_ROLE_RESPONDER = 2, +}; + +/** + * @brief BIP transport state + * + * Defines the state of the underlying transport connection + */ + +enum __packed bt_bip_transport_state +{ + /** @brief Transport is disconnected */ + + BT_BIP_TRANSPORT_STATE_DISCONNECTED = 0, + + /** @brief Transport is connecting */ + + BT_BIP_TRANSPORT_STATE_CONNECTING = 1, + + /** @brief Transport is connected */ + + BT_BIP_TRANSPORT_STATE_CONNECTED = 2, + + /** @brief Transport is disconnecting */ + + BT_BIP_TRANSPORT_STATE_DISCONNECTING = 3, +}; + +/** + * @brief BIP supported capabilities + * + * Defines the supported capabilities of the BIP device (responder). These + * capabilities indicate the primary BIP services that the device supports. + * Multiple capabilities can be combined using bitwise OR operations. + */ + +enum __packed bt_bip_supported_capabilities +{ + /** @brief Generic imaging */ + + BT_BIP_SUPP_CAP_GENERIC_IMAGE = 0, + + /** @brief Capturing */ + + BT_BIP_SUPP_CAP_CAPTURING = 1, + + /** @brief Printing */ + + BT_BIP_SUPP_CAP_PRINTING = 2, + + /** @brief Displaying */ + + BT_BIP_SUPP_CAP_DISPLAYING = 3, +}; + +/** + * @brief BIP supported features + * + * Defines the supported features of the BIP device (responder). These + * features indicate the primary BIP services that the device supports. + * Multiple features can be combined using bitwise OR operations. + */ + +enum __packed bt_bip_supported_features +{ + /** @brief Image Push feature - allows pushing images to the peer device */ + + BT_BIP_SUPP_FEAT_IMAGE_PUSH = 0, + + /** @brief Image Push Store feature - supports storing pushed images + * permanently + */ + + BT_BIP_SUPP_FEAT_IMAGE_PUSH_STORE = 1, + + /** @brief Image Push Print feature - supports printing pushed images + * directly + */ + + BT_BIP_SUPP_FEAT_IMAGE_PUSH_PRINT = 2, + + /** @brief Image Push Display feature - supports displaying pushed images */ + + BT_BIP_SUPP_FEAT_IMAGE_PUSH_DISPLAY = 3, + + /** @brief Image Pull feature - allows pulling/retrieving images from the + * peer + */ + + BT_BIP_SUPP_FEAT_IMAGE_PULL = 4, + + /** @brief Advanced Image Printing feature - supports advanced printing + * operations + */ + + BT_BIP_SUPP_FEAT_ADVANCED_IMAGE_PRINT = 5, + + /** @brief Auto Archive feature - supports automatic archiving of images */ + + BT_BIP_SUPP_FEAT_AUTO_ARCHIVE = 6, + + /** @brief Remote Camera feature - supports remote camera control and image + * capture + */ + + BT_BIP_SUPP_FEAT_REMOTE_CAMERA = 7, + + /** @brief Remote Display feature - supports remote display control */ + + BT_BIP_SUPP_FEAT_REMOTE_DISPLAY = 8, +}; + +/** + * @brief BIP supported functions + * + * Defines the supported functions of the BIP device (responder). These + * functions indicate the specific BIP operations that the device can + * perform. Multiple functions can be combined using bitwise OR operations. + * + * @note These values correspond to the BIP specification function + * definitions and are used to determine which operations can be performed on + * the peer device. + */ + +enum __packed bt_bip_supported_functions +{ + /** @brief Get Capabilities function - supports retrieving device + * capabilities + */ + + BT_BIP_SUPP_FUNC_GET_CAPS = 0, + + /** @brief Put Image function - supports receiving/storing images */ + + BT_BIP_SUPP_FUNC_PUT_IMAGE = 1, + + /** @brief Put Linked Attachment function - supports receiving image + * attachments + */ + + BT_BIP_SUPP_FUNC_PUT_LINKED_ATTACHMENT = 2, + + /** @brief Put Linked Thumbnail function - supports receiving image + * thumbnails + */ + + BT_BIP_SUPP_FUNC_PUT_LINKED_THUMBNAIL = 3, + + /** @brief Remote Display function - supports remote display operations */ + + BT_BIP_SUPP_FUNC_REMOTE_DISPLAY = 4, + + /** @brief Get Images List function - supports retrieving available image + * lists + */ + + BT_BIP_SUPP_FUNC_GET_IMAGE_LIST = 5, + + /** @brief Get Image Properties function - supports retrieving image + * metadata + */ + + BT_BIP_SUPP_FUNC_GET_IMAGE_PROPERTIES = 6, + + /** @brief Get Image function - supports retrieving/sending images */ + + BT_BIP_SUPP_FUNC_GET_IMAGE = 7, + + /** @brief Get Linked Thumbnail function - supports retrieving image + * thumbnails + */ + + BT_BIP_SUPP_FUNC_GET_LINKED_THUMBNAIL = 8, + + /** @brief Get Linked Attachment function - supports retrieving image + * attachments + */ + + BT_BIP_SUPP_FUNC_GET_LINKED_ATTACHMENT = 9, + + /** @brief Delete Image function - supports deleting images on the peer + * device + */ + + BT_BIP_SUPP_FUNC_DELETE_IMAGE = 10, + + /** @brief Start Print function - supports initiating print operations */ + + BT_BIP_SUPP_FUNC_START_PRINT = 11, + + /** @brief Get Partial Image function - supports retrieving partial image + * data + */ + + BT_BIP_SUPP_FUNC_GET_PARTIAL_IMAGE = 12, + + /** @brief Start Archive function - supports initiating archive operations + */ + + BT_BIP_SUPP_FUNC_START_ARCHIVE = 13, + /** @brief Get Monitoring Image function - supports retrieving monitoring + * images from remote camera + */ + + BT_BIP_SUPP_FUNC_GET_MONITORING_IMAGE = 14, + + /** @brief Get Status function - supports retrieving device/operation + * status + */ + + BT_BIP_SUPP_FUNC_GET_STATUS = 16, +}; + +/** + * @brief BIP instance structure + * + * Main structure representing a BIP session + */ + +struct bt_bip +{ + /** @brief Underlying GOEP transport instance */ + + struct bt_goep_transport goep_transport; + + /** @brief Underlying GOEP instance */ + + struct bt_goep goep; + + /** @brief Role in the connection */ + + enum bt_bip_role role; + + /** @brief Transport operation callbacks */ + + const struct bt_bip_transport_ops *ops; + + /** @internal Transport state (atomic) */ + + atomic_t _transport_state; + + /** @internal Responder supported capabilities */ + + uint8_t _supp_caps; + + /** @internal Responder supported features */ + + uint16_t _supp_feats; + + /** @internal Responder supported functions */ + + uint32_t _supp_funcs; + + /** @internal List of clients */ + + struct list_node _clients; + + /** @internal List of servers */ + + struct list_node _servers; + + /** @internal Node for linking in lists */ + + struct list_node _node; +}; + +/** + * @brief Set supported capabilities of BIP responder + * + * Set the supported capabilities bitmask of peer BIP responder to local BIP + * initiator. + * + * @param bip BIP initiator instance + * @param capabilities Bitmask of supported capabilities. It is discovered + * from BIP responder by SDP discovery. + * + * @return 0 on success, negative error code on failure + * + * @note This function should be called before establishing any BIP OBEX + * connections. + * @note The capabilities value is typically obtained through SDP discovery. + */ + +int bt_bip_set_supported_capabilities(struct bt_bip *bip, + uint8_t capabilities); + +/** + * @brief Set supported features of BIP responder + * + * Set the supported features bitmask of peer BIP responder to local BIP + * initiator. + * + * @param bip BIP initiator instance + * @param features Bitmask of supported features. It is discovered from BIP + * responder by SDP discovery. + * + * @return 0 on success, negative error code on failure + * + * @note This function should be called before establishing any BIP OBEX + * connections. + * @note The features value is typically obtained through SDP discovery. + */ + +int bt_bip_set_supported_features(struct bt_bip *bip, uint16_t features); + +/** + * @brief Set supported functions of BIP responder + * + * Set the supported functions bitmask of peer BIP responder to local BIP + * initiator. + * + * @param bip BIP initiator instance + * @param functions Bitmask of supported functions. It is discovered from BIP + * responder by SDP discovery. + * + * @return 0 on success, negative error code on failure + * + * @note This function should be called before establishing any BIP OBEX + * connections. + * @note The functions value is typically obtained through SDP discovery. + */ + +int bt_bip_set_supported_functions(struct bt_bip *bip, uint32_t functions); + +/** + * @brief BIP connection state + * + * Defines the state of a BIP OBEX connection + */ + +enum __packed bt_bip_state +{ + /** @brief Connection is disconnected */ + + BT_BIP_STATE_DISCONNECTED = 0, + + /** @brief Connection is being established */ + + BT_BIP_STATE_CONNECTING = 1, + + /** @brief Connection is established */ + + BT_BIP_STATE_CONNECTED = 2, + + /** @brief Connection is being terminated */ + + BT_BIP_STATE_DISCONNECTING = 3, +}; + +/** @brief Forward declaration of BIP server structure */ + +struct bt_bip_server; + +/** + * @brief BIP server callback structure + * + * Callback functions for handling BIP server operations + */ + +struct bt_bip_server_cb +{ + /* @brief OBEX Connect request received + * + * @param server BIP server instance + * @param version OBEX protocol version + * @param mopl Maximum OBEX packet length + * @param buf Request data buffer + */ + + void (*connect)(struct bt_bip_server *server, + uint8_t version, + uint16_t mopl, + struct net_buf *buf); + + /* @brief OBEX Disconnect request received + * + * @param server BIP server instance + * @param buf Request data buffer + */ + + void (*disconnect)(struct bt_bip_server *server, struct net_buf *buf); + + /* @brief OBEX Abort request received + * + * @param server BIP server instance + * @param buf Request data buffer + */ + + void (*abort)(struct bt_bip_server *server, struct net_buf *buf); + + /* @brief Get Capabilities request received + * + * @param server BIP server instance + * @param final True if this is the final packet + * @param buf Request data buffer + */ + + void (*get_caps)(struct bt_bip_server *server, bool final, + struct net_buf *buf); + + /* @brief Get Image List request received + * + * @param server BIP server instance + * @param final True if this is the final packet + * @param buf Request data buffer + */ + + void (*get_image_list)(struct bt_bip_server *server, bool final, + struct net_buf *buf); + + /* @brief Get Image Properties request received + * + * @param server BIP server instance + * @param final True if this is the final packet + * @param buf Request data buffer + */ + + void (*get_image_properties)(struct bt_bip_server *server, bool final, + struct net_buf *buf); + + /* @brief Get Image request received + * + * @param server BIP server instance + * @param final True if this is the final packet + * @param buf Request data buffer + */ + + void (*get_image)(struct bt_bip_server *server, bool final, + struct net_buf *buf); + + /* @brief Get Linked Thumbnail request received + * + * @param server BIP server instance + * @param final True if this is the final packet + * @param buf Request data buffer + */ + + void (*get_linked_thumbnail)(struct bt_bip_server *server, bool final, + struct net_buf *buf); + + /* @brief Get Linked Attachment request received + * + * @param server BIP server instance + * @param final True if this is the final packet + * @param buf Request data buffer + */ + + void (*get_linked_attachment)(struct bt_bip_server *server, bool final, + struct net_buf *buf); + + /* @brief Get Partial Image request received + * + * @param server BIP server instance + * @param final True if this is the final packet + * @param buf Request data buffer + */ + + void (*get_partial_image)(struct bt_bip_server *server, bool final, + struct net_buf *buf); + + /* @brief Get Monitoring Image request received + * + * @param server BIP server instance + * @param final True if this is the final packet + * @param buf Request data buffer + */ + + void (*get_monitoring_image)(struct bt_bip_server *server, bool final, + struct net_buf *buf); + + /* @brief Get Status request received + * + * @param server BIP server instance + * @param final True if this is the final packet + * @param buf Request data buffer + */ + + void (*get_status)(struct bt_bip_server *server, bool final, + struct net_buf *buf); + + /* @brief Put Image request received + * + * @param server BIP server instance + * @param final True if this is the final packet + * @param buf Request data buffer + */ + + void (*put_image)(struct bt_bip_server *server, bool final, + struct net_buf *buf); + + /* @brief Put Linked Thumbnail request received + * + * @param server BIP server instance + * @param final True if this is the final packet + * @param buf Request data buffer + */ + + void (*put_linked_thumbnail)(struct bt_bip_server *server, bool final, + struct net_buf *buf); + + /* @brief Put Linked Attachment request received + * + * @param server BIP server instance + * @param final True if this is the final packet + * @param buf Request data buffer + */ + + void (*put_linked_attachment)(struct bt_bip_server *server, bool final, + struct net_buf *buf); + + /* @brief Remote Display request received + * + * @param server BIP server instance + * @param final True if this is the final packet + * @param buf Request data buffer + */ + + void (*remote_display)(struct bt_bip_server *server, bool final, + struct net_buf *buf); + + /* @brief Delete Image request received + * + * @param server BIP server instance + * @param final True if this is the final packet + * @param buf Request data buffer + */ + + void (*delete_image)(struct bt_bip_server *server, bool final, + struct net_buf *buf); + + /* @brief Start Print request received + * + * @param server BIP server instance + * @param final True if this is the final packet + * @param buf Request data buffer + */ + + void (*start_print)(struct bt_bip_server *server, bool final, + struct net_buf *buf); + + /* @brief Start Archive request received + * + * @param server BIP server instance + * @param final True if this is the final packet + * @param buf Request data buffer + */ + + void (*start_archive)(struct bt_bip_server *server, bool final, + struct net_buf *buf); +}; + +/** @brief Forward declaration of BIP client structure */ + +struct bt_bip_client; + +/** + * @brief BIP client callback structure + * + * Callback functions for handling BIP client operation responses + */ + +struct bt_bip_client_cb +{ + /* @brief OBEX Connect response received + * + * @param client BIP client instance + * @param rsp_code OBEX response code + * @param version OBEX protocol version + * @param mopl Maximum OBEX packet length + * @param buf Response data buffer + */ + + void (*connect)(struct bt_bip_client *client, + uint8_t rsp_code, + uint8_t version, + uint16_t mopl, + struct net_buf *buf); + + /* @brief OBEX Disconnect response received + * + * @param client BIP client instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + */ + + void (*disconnect)(struct bt_bip_client *client, uint8_t rsp_code, + struct net_buf *buf); + + /* @brief OBEX Abort response received + * + * @param client BIP client instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + */ + + void (*abort)(struct bt_bip_client *client, uint8_t rsp_code, + struct net_buf *buf); + + /* @brief Get Capabilities response received + * + * @param client BIP client instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + */ + + void (*get_caps)(struct bt_bip_client *client, uint8_t rsp_code, + struct net_buf *buf); + + /* @brief Get Image List response received + * + * @param client BIP client instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + */ + + void (*get_image_list)(struct bt_bip_client *client, uint8_t rsp_code, + struct net_buf *buf); + + /* @brief Get Image Properties response received + * + * @param client BIP client instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + */ + + void (*get_image_properties)(struct bt_bip_client *client, + uint8_t rsp_code, + struct net_buf *buf); + + /* @brief Get Image response received + * + * @param client BIP client instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + */ + + void (*get_image)(struct bt_bip_client *client, uint8_t rsp_code, + struct net_buf *buf); + + /* @brief Get Linked Thumbnail response received + * + * @param client BIP client instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + */ + + void (*get_linked_thumbnail)(struct bt_bip_client *client, + uint8_t rsp_code, + struct net_buf *buf); + + /* @brief Get Linked Attachment response received + * + * @param client BIP client instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + */ + + void (*get_linked_attachment)(struct bt_bip_client *client, + uint8_t rsp_code, + struct net_buf *buf); + + /* @brief Get Partial Image response received + * + * @param client BIP client instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + */ + + void (*get_partial_image)(struct bt_bip_client *client, uint8_t rsp_code, + struct net_buf *buf); + + /* @brief Get Monitoring Image response received + * + * @param client BIP client instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + */ + + void (*get_monitoring_image)(struct bt_bip_client *client, + uint8_t rsp_code, + struct net_buf *buf); + + /* @brief Get Status response received + * + * @param client BIP client instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + */ + + void (*get_status)(struct bt_bip_client *client, uint8_t rsp_code, + struct net_buf *buf); + + /* @brief Put Image response received + * + * @param client BIP client instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + */ + + void (*put_image)(struct bt_bip_client *client, uint8_t rsp_code, + struct net_buf *buf); + + /* @brief Put Linked Thumbnail response received + * + * @param client BIP client instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + */ + + void (*put_linked_thumbnail)(struct bt_bip_client *client, + uint8_t rsp_code, + struct net_buf *buf); + + /* @brief Put Linked Attachment response received + * + * @param client BIP client instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + */ + + void (*put_linked_attachment)(struct bt_bip_client *client, + uint8_t rsp_code, + struct net_buf *buf); + + /* @brief Remote Display response received + * + * @param client BIP client instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + */ + + void (*remote_display)(struct bt_bip_client *client, uint8_t rsp_code, + struct net_buf *buf); + + /* @brief Delete Image response received + * + * @param client BIP client instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + */ + + void (*delete_image)(struct bt_bip_client *client, uint8_t rsp_code, + struct net_buf *buf); + + /* @brief Start Print response received + * + * @param client BIP client instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + */ + + void (*start_print)(struct bt_bip_client *client, uint8_t rsp_code, + struct net_buf *buf); + + /* @brief Start Archive response received + * + * @param client BIP client instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + */ + + void (*start_archive)(struct bt_bip_client *client, uint8_t rsp_code, + struct net_buf *buf); +}; + +/** + * @brief BIP server instance structure + * + * Structure representing a BIP server endpoint + */ + +struct bt_bip_server +{ + /** @internal Server callback functions */ + + const struct bt_bip_server_cb *_cb; + + /** @internal Parent BIP instance */ + + struct bt_bip *_bip; + + /** @internal Service UUID */ + + const struct bt_uuid_128 *_uuid; + + /** @internal Connection ID */ + + uint32_t _conn_id; + + /** @internal Connection type */ + + enum bt_bip_conn_type _type; + + /** @internal Underlying OBEX server */ + + struct bt_obex_server _server; + + /** @internal Current operation code */ + + uint8_t _opcode; + + /** @internal Current operation type string */ + + const char *_optype; + + /** @internal Server state (atomic) */ + + atomic_t _state; + + /** @internal Associated client (for secondary connections) */ + + union + { + struct bt_bip_client *_primary_client; + struct bt_bip_client *_secondary_client; + }; + + /** @internal Pending request callback */ + + void (*_req_cb)(struct bt_bip_server *server, bool final, + struct net_buf *buf); + + /** @internal Node for linking in lists */ + + struct list_node _node; +}; + +/** + * @brief BIP client instance structure + * + * Structure representing a BIP client endpoint + */ + +struct bt_bip_client +{ + /** @internal Client callback functions */ + + const struct bt_bip_client_cb *_cb; + + /** @internal Parent BIP instance */ + + struct bt_bip *_bip; + + /** @internal Target service UUID */ + + struct bt_uuid_128 _uuid; + + /** @internal Connection ID */ + + uint32_t _conn_id; + + /** @internal Connection type */ + + enum bt_bip_conn_type _type; + + /** @internal Underlying OBEX client */ + + struct bt_obex_client _client; + + /** @internal Client state (atomic) */ + + atomic_t _state; + + /** @internal Associated server (for secondary connections) */ + + union + { + struct bt_bip_server *_primary_server; + struct bt_bip_server *_secondary_server; + }; + + /** @internal Pending response callback */ + + void (*_rsp_cb)(struct bt_bip_client *client, uint8_t rsp_code, + struct net_buf *buf); + + /** @internal current request function type */ + + const char *_req_type; + + /** @internal Node for linking in lists */ + + struct list_node _node; +}; + +/** + * @brief Primary Image Push server callback initializer + * + * Helper macro to initialize callbacks for Primary Image Push server + */ +#define BT_BIP_PRIM_IMAGE_PUSH_CB(_connect, _disconnect, _get_caps, _put_image, \ + _put_linked_thumbnail, _put_linked_attachment) \ +{ \ + .connect = _connect, \ + .disconnect = _disconnect, \ + .get_caps = _get_caps, \ + .put_image = _put_image, \ + .put_linked_thumbnail = _put_linked_thumbnail, \ + .put_linked_attachment = _put_linked_attachment, \ +} + +/** + * @brief Primary Image Pull server callback initializer + * + * Helper macro to initialize callbacks for Primary Image Pull server + */ +#define BT_BIP_PRIM_IMAGE_PULL_CB(_connect, _disconnect, _get_caps, _get_image_list, \ + _get_image_properties, _get_image, _get_linked_thumbnail, \ + _get_linked_attachment, _delete_image) \ +{ \ + .connect = _connect, \ + .disconnect = _disconnect, \ + .get_caps = _get_caps, \ + .get_image_list = _get_image_list, \ + .get_image_properties = _get_image_properties, \ + .get_image = _get_image, \ + .get_linked_thumbnail = _get_linked_thumbnail, \ + .get_linked_attachment = _get_linked_attachment, \ + .delete_image = _delete_image, \ +} + +/** + * @brief Primary Image Print server callback initializer + * + * Helper macro to initialize callbacks for Primary Image Print server + */ +#define BT_BIP_PRIM_IMAGE_PRINT_CB(_connect, _disconnect, _get_caps, _get_status, _start_print) \ +{ \ + .connect = _connect, \ + .disconnect = _disconnect, \ + .get_caps = _get_caps, \ + .get_status = _get_status, \ + .start_print = _start_print, \ +} + +/** + * @brief Secondary Image Print server callback initializer + * + * Helper macro to initialize callbacks for Secondary Image Print server + */ +#define BT_BIP_2ND_IMAGE_PRINT_CB(_connect, _disconnect, _get_partial_image) \ +{ \ + .connect = _connect, \ + .disconnect = _disconnect, \ + .get_partial_image = _get_partial_image, \ +} + +/** + * @brief Primary Auto Archive server callback initializer + * + * Helper macro to initialize callbacks for Primary Auto Archive server + */ +#define BT_BIP_PRIM_AUTO_ARCHIVE_CB(_connect, _disconnect, _get_status, _start_archive) \ +{ \ + .connect = _connect, \ + .disconnect = _disconnect, \ + .get_status = _get_status, \ + .start_archive = _start_archive, \ +} + +/** + * @brief Secondary Auto Archive server callback initializer + * + * Helper macro to initialize callbacks for Secondary Auto Archive server + */ +#define BT_BIP_2ND_AUTO_ARCHIVE_CB(_connect, _disconnect, _get_caps, _get_image_list, \ + _get_image_properties, _get_image, _get_linked_thumbnail, \ + _get_linked_attachment, _delete_image) \ +{ \ + .connect = _connect, \ + .disconnect = _disconnect, \ + .get_caps = _get_caps, \ + .get_image_list = _get_image_list, \ + .get_image_properties = _get_image_properties, \ + .get_image = _get_image, \ + .get_linked_thumbnail = _get_linked_thumbnail, \ + .get_linked_attachment = _get_linked_attachment, \ + .delete_image = _delete_image, \ +} + +/** + * @brief Primary Remote Camera server callback initializer + * + * Helper macro to initialize callbacks for Primary Remote Camera server + */ +#define BT_BIP_PRIM_REMOTE_CAMERA_CB(_connect, _disconnect, _get_image_properties, _get_image, \ + _get_linked_thumbnail, _get_monitoring_image) \ +{ \ + .connect = _connect, \ + .disconnect = _disconnect, \ + .get_image_properties = _get_image_properties, \ + .get_image = _get_image, \ + .get_linked_thumbnail = _get_linked_thumbnail, \ + .get_monitoring_image = _get_monitoring_image, \ +} + +/** + * @brief Primary Remote Display server callback initializer + * + * Helper macro to initialize callbacks for Primary Remote Display server + */ +#define BT_BIP_PRIM_REMOTE_DISPLAY_CB(_connect, _disconnect, _get_caps, _get_image_list, \ + _put_image, _put_linked_thumbnail, _remote_display) \ +{ \ + .connect = _connect, \ + .disconnect = _disconnect, \ + .get_caps = _get_caps, \ + .get_image_list = _get_image_list, \ + .put_image = _put_image, \ + .put_linked_thumbnail = _put_linked_thumbnail, \ + .remote_display = _remote_display, \ +} + +/** @brief BIP Image Push service UUID */ +#define BT_BIP_UUID_IMAGE_PUSH \ +(const struct bt_uuid_128 *)BT_UUID_DECLARE_128( \ + BT_UUID_128_ENCODE(0xE33D9545, 0x8374, 0x4AD7, 0x9EC5, 0xC16BE31EDE8E)) + +/** @brief BIP Image Pull service UUID */ +#define BT_BIP_UUID_IMAGE_PULL \ +(const struct bt_uuid_128 *)BT_UUID_DECLARE_128( \ + BT_UUID_128_ENCODE(0x8EE9B3D0, 0x4608, 0x11D5, 0x841A, 0x0002A5325B4E)) + +/** @brief BIP Advanced Image Printing service UUID */ +#define BT_BIP_UUID_IMAGE_PRINT \ +(const struct bt_uuid_128 *)BT_UUID_DECLARE_128( \ + BT_UUID_128_ENCODE(0x92353350, 0x4608, 0x11D5, 0x841A, 0x0002A5325B4E)) + +/** @brief BIP Auto Archive service UUID */ +#define BT_BIP_UUID_AUTO_ARCHIVE \ +(const struct bt_uuid_128 *)BT_UUID_DECLARE_128( \ + BT_UUID_128_ENCODE(0x940126C0, 0x4608, 0x11D5, 0x841A, 0x0002A5325B4E)) + +/** @brief BIP Remote Camera service UUID */ +#define BT_BIP_UUID_REMOTE_CAMERA \ +(const struct bt_uuid_128 *)BT_UUID_DECLARE_128( \ + BT_UUID_128_ENCODE(0x947E7420, 0x4608, 0x11D5, 0x841A, 0x0002A5325B4E)) + +/** @brief BIP Remote Display service UUID */ +#define BT_BIP_UUID_REMOTE_DISPLAY \ +(const struct bt_uuid_128 *)BT_UUID_DECLARE_128( \ + BT_UUID_128_ENCODE(0x94C7CD20, 0x4608, 0x11D5, 0x841A, 0x0002A5325B4E)) + +/** @brief BIP Referenced Objects service UUID */ +#define BT_BIP_UUID_REFERENCED_OBJ \ +(const struct bt_uuid_128 *)BT_UUID_DECLARE_128( \ + BT_UUID_128_ENCODE(0x8E61F95D, 0x1A79, 0x11D4, 0x8EA4, 0x00805F9B9834)) + +/** @brief BIP Archived Objects service UUID */ +#define BT_BIP_UUID_ARCHIVED_OBJ \ +(const struct bt_uuid_128 *)BT_UUID_DECLARE_128( \ + BT_UUID_128_ENCODE(0x8E61F95E, 0x1A79, 0x11D4, 0x8EA4, 0x00805F9B9834)) + +/** + * @brief Register a primary BIP OBEX server + * + * Registers a primary BIP server for the specified connection type and + * service UUID. Primary servers handle the main BIP operations for their + * respective service types. + * + * @param bip BIP instance + * @param server BIP server structure to register + * @param type Primary connection type (IMAGE_PUSH, IMAGE_PULL, etc.) + * @param uuid Service UUID for the server + * @param cb Callback functions for handling server operations + * @return 0 on success, negative error code on failure + */ + +int bt_bip_primary_server_register( + struct bt_bip *bip, struct bt_bip_server *server, + enum bt_bip_conn_type type, const struct bt_uuid_128 *uuid, + struct bt_bip_server_cb *cb); + +/** + * @brief Register a secondary BIP OBEX server + * + * Registers a secondary BIP server that works in conjunction with a primary + * client. Secondary servers handle operations like referenced objects or + * archived objects. + * + * @param bip BIP instance + * @param server BIP server structure to register + * @param type Secondary connection type (REFERENCED_OBJECTS or + * ARCHIVED_OBJECTS) + * @param uuid Service UUID for the server + * @param cb Callback functions for handling server operations + * @param primary_client Associated primary client + * @return 0 on success, negative error code on failure + */ + +int bt_bip_secondary_server_register( + struct bt_bip *bip, struct bt_bip_server *server, + enum bt_bip_conn_type type, const struct bt_uuid_128 *uuid, + struct bt_bip_server_cb *cb, struct bt_bip_client *primary_client); + +/** + * @brief Register Primary Image Push OBEX server + * + * Convenience function to register a Primary Image Push server. + * + * @param bip BIP instance + * @param server BIP server structure + * @param uuid Service UUID (typically BT_BIP_UUID_IMAGE_PUSH) + * @param cb Server callback functions + * @return 0 on success, negative error code on failure + */ + +static inline int bt_bip_primary_image_push_server_register( + struct bt_bip *bip, struct bt_bip_server *server, + const struct bt_uuid_128 *uuid, struct bt_bip_server_cb *cb) +{ + return bt_bip_primary_server_register(bip, server, + BT_BIP_PRIM_CONN_TYPE_IMAGE_PUSH, + uuid, cb); +} + +/** + * @brief Register Primary Image Pull OBEX server + * + * Convenience function to register a Primary Image Pull server. + * + * @param bip BIP instance + * @param server BIP server structure + * @param uuid Service UUID (typically BT_BIP_UUID_IMAGE_PULL) + * @param cb Server callback functions + * @return 0 on success, negative error code on failure + */ + +static inline int bt_bip_primary_image_pull_server_register( + struct bt_bip *bip, struct bt_bip_server *server, + const struct bt_uuid_128 *uuid, struct bt_bip_server_cb *cb) +{ + return bt_bip_primary_server_register(bip, server, + BT_BIP_PRIM_CONN_TYPE_IMAGE_PULL, + uuid, cb); +} + +/** + * @brief Register Primary Advanced Image Printing OBEX server + * + * Convenience function to register a Primary Advanced Image Printing server. + * + * @param bip BIP instance + * @param server BIP server structure + * @param uuid Service UUID (typically BT_BIP_UUID_IMAGE_PRINT) + * @param cb Server callback functions + * @return 0 on success, negative error code on failure + */ + +static inline int bt_bip_primary_advanced_image_printing_server_register( + struct bt_bip *bip, struct bt_bip_server *server, + const struct bt_uuid_128 *uuid, struct bt_bip_server_cb *cb) +{ + return bt_bip_primary_server_register(bip, server, + BT_BIP_PRIM_CONN_TYPE_ADVANCED_IMAGE_PRINTING, + uuid, + cb); +} + +/** + * @brief Register Primary Auto Archive OBEX server + * + * Convenience function to register a Primary Auto Archive server. + * + * @param bip BIP instance + * @param server BIP server structure + * @param uuid Service UUID (typically BT_BIP_UUID_AUTO_ARCHIVE) + * @param cb Server callback functions + * @return 0 on success, negative error code on failure + */ + +static inline int bt_bip_primary_auto_archive_server_register( + struct bt_bip *bip, struct bt_bip_server *server, + const struct bt_uuid_128 *uuid, struct bt_bip_server_cb *cb) +{ + return bt_bip_primary_server_register(bip, server, + BT_BIP_PRIM_CONN_TYPE_AUTO_ARCHIVE, + uuid, cb); +} + +/** + * @brief Register Primary Remote Camera OBEX server + * + * Convenience function to register a Primary Remote Camera server. + * + * @param bip BIP instance + * @param server BIP server structure + * @param uuid Service UUID (typically BT_BIP_UUID_REMOTE_CAMERA) + * @param cb Server callback functions + * @return 0 on success, negative error code on failure + */ + +static inline int bt_bip_primary_remote_camera_server_register( + struct bt_bip *bip, struct bt_bip_server *server, + const struct bt_uuid_128 *uuid, struct bt_bip_server_cb *cb) +{ + return bt_bip_primary_server_register(bip, server, + BT_BIP_PRIM_CONN_TYPE_REMOTE_CAMERA, + uuid, cb); +} + +/** + * @brief Register Primary Remote Display OBEX server + * + * Convenience function to register a Primary Remote Display server. + * + * @param bip BIP instance + * @param server BIP server structure + * @param uuid Service UUID (typically BT_BIP_UUID_REMOTE_DISPLAY) + * @param cb Server callback functions + * @return 0 on success, negative error code on failure + */ + +static inline int bt_bip_primary_remote_display_server_register( + struct bt_bip *bip, struct bt_bip_server *server, + const struct bt_uuid_128 *uuid, struct bt_bip_server_cb *cb) +{ + return bt_bip_primary_server_register(bip, server, + BT_BIP_PRIM_CONN_TYPE_REMOTE_DISPLAY, + uuid, cb); +} + +/** + * @brief Register Secondary Advanced Image Printing OBEX server + * + * Convenience function to register a Secondary Advanced Image Printing + * server. This server handles referenced objects for the printing service. + * + * @param bip BIP instance + * @param server BIP server structure + * @param uuid Service UUID (typically BT_BIP_UUID_REFERENCED_OBJ) + * @param cb Server callback functions + * @param primary_client Associated primary client + * @return 0 on success, negative error code on failure + */ + +static inline int bt_bip_secondary_advanced_image_printing_server_register( + struct bt_bip *bip, struct bt_bip_server *server, + const struct bt_uuid_128 *uuid, struct bt_bip_server_cb *cb, + struct bt_bip_client *primary_client) +{ + return bt_bip_secondary_server_register(bip, server, + BT_BIP_2ND_CONN_TYPE_REFERENCED_OBJECTS, + uuid, cb, primary_client); +} + +/** + * @brief Register Secondary Auto Archive OBEX server + * + * Convenience function to register a Secondary Auto Archive server. This + * server handles archived objects for the auto archive service. + * + * @param bip BIP instance + * @param server BIP server structure + * @param uuid Service UUID (typically BT_BIP_UUID_ARCHIVED_OBJ) + * @param cb Server callback functions + * @param primary_client Associated primary client + * @return 0 on success, negative error code on failure + */ + +static inline int bt_bip_secondary_auto_archive_server_register( + struct bt_bip *bip, struct bt_bip_server *server, + const struct bt_uuid_128 *uuid, struct bt_bip_server_cb *cb, + struct bt_bip_client *primary_client) +{ + return bt_bip_secondary_server_register(bip, server, + BT_BIP_2ND_CONN_TYPE_ARCHIVED_OBJECTS, + uuid, cb, primary_client); +} + +/** + * @brief Unregister a BIP OBEX server + * + * Unregisters a previously registered BIP server. + * + * @note The specific server can only be unregistered if it is has been + * disconnected. + * + * @param server BIP server to unregister + * @return 0 on success, negative error code on failure + */ + +int bt_bip_server_unregister(struct bt_bip_server *server); + +/** + * @brief Connect a primary BIP OBEX client + * + * Initiates a primary BIP client connection for the specified service type + * BT_BIP_PRIM_CONN_TYPE_* of @ref bt_bip_conn_type. + * + * @param bip BIP instance + * @param client BIP client structure + * @param type Primary connection type + * @param cb Client callback functions + * @param buf Connection request data + * @return 0 on success, negative error code on failure + */ + +int bt_bip_primary_client_connect( + struct bt_bip *bip, struct bt_bip_client *client, + enum bt_bip_conn_type type, struct bt_bip_client_cb *cb, + struct net_buf *buf); + +/** + * @brief Connect a secondary BIP OBEX client + * + * Initiates a secondary BIP client connection with the specific type + * BT_BIP_2ND_CONN_TYPE_ of + * @ref bt_bip_conn_type to the secondary server of BIP initiator that works + * as primary OBEX client. + * + * @param bip BIP instance + * @param client BIP client structure + * @param type Secondary connection type + * @param cb Client callback functions + * @param buf Connection request data + * @param primary_server Associated primary server + * @return 0 on success, negative error code on failure + */ + +int bt_bip_secondary_client_connect( + struct bt_bip *bip, struct bt_bip_client *client, + enum bt_bip_conn_type type, struct bt_bip_client_cb *cb, + struct net_buf *buf, struct bt_bip_server *primary_server); + +/** + * @brief Connect Primary Image Push OBEX client + * + * Convenience function to connect a Primary Image Push client. + * + * @param bip BIP instance + * @param client BIP client structure + * @param cb Client callback functions + * @param buf Connection request data + * @return 0 on success, negative error code on failure + */ + +static inline int bt_bip_primary_image_push_client_connect( + struct bt_bip *bip, struct bt_bip_client *client, + struct bt_bip_client_cb *cb, struct net_buf *buf) +{ + return bt_bip_primary_client_connect(bip, client, + BT_BIP_PRIM_CONN_TYPE_IMAGE_PUSH, cb, + buf); +} + +/** + * @brief Connect Primary Image Pull OBEX client + * + * Convenience function to connect a Primary Image Pull client. + * + * @param bip BIP instance + * @param client BIP client structure + * @param cb Client callback functions + * @param buf Connection request data + * @return 0 on success, negative error code on failure + */ + +static inline int bt_bip_primary_image_pull_client_connect( + struct bt_bip *bip, struct bt_bip_client *client, + struct bt_bip_client_cb *cb, struct net_buf *buf) +{ + return bt_bip_primary_client_connect(bip, client, + BT_BIP_PRIM_CONN_TYPE_IMAGE_PULL, cb, + buf); +} + +/** + * @brief Connect Primary Advanced Image Printing OBEX client + * + * Convenience function to connect a Primary Advanced Image Printing client. + * + * @param bip BIP instance + * @param client BIP client structure + * @param cb Client callback functions + * @param buf Connection request data + * @return 0 on success, negative error code on failure + */ + +static inline int bt_bip_primary_advanced_image_printing_client_connect( + struct bt_bip *bip, struct bt_bip_client *client, + struct bt_bip_client_cb *cb, struct net_buf *buf) +{ + return bt_bip_primary_client_connect(bip, client, + BT_BIP_PRIM_CONN_TYPE_ADVANCED_IMAGE_PRINTING, + cb, buf); +} + +/** + * @brief Connect Primary Auto Archive OBEX client + * + * Convenience function to connect a Primary Auto Archive client. + * + * @param bip BIP instance + * @param client BIP client structure + * @param cb Client callback functions + * @param buf Connection request data + * @return 0 on success, negative error code on failure + */ + +static inline int bt_bip_primary_auto_archive_client_connect( + struct bt_bip *bip, struct bt_bip_client *client, + struct bt_bip_client_cb *cb, struct net_buf *buf) +{ + return bt_bip_primary_client_connect(bip, client, + BT_BIP_PRIM_CONN_TYPE_AUTO_ARCHIVE, + cb, buf); +} + +/** + * @brief Connect Primary Remote Camera OBEX client + * + * Convenience function to connect a Primary Remote Camera client. + * + * @param bip BIP instance + * @param client BIP client structure + * @param cb Client callback functions + * @param buf Connection request data + * @return 0 on success, negative error code on failure + */ + +static inline int bt_bip_primary_remote_camera_client_connect( + struct bt_bip *bip, struct bt_bip_client *client, + struct bt_bip_client_cb *cb, struct net_buf *buf) +{ + return bt_bip_primary_client_connect(bip, client, + BT_BIP_PRIM_CONN_TYPE_REMOTE_CAMERA, + cb, buf); +} + +/** + * @brief Connect Primary Remote Display OBEX client + * + * Convenience function to connect a Primary Remote Display client. + * + * @param bip BIP instance + * @param client BIP client structure + * @param cb Client callback functions + * @param buf Connection request data + * @return 0 on success, negative error code on failure + */ + +static inline int bt_bip_primary_remote_display_client_connect( + struct bt_bip *bip, struct bt_bip_client *client, + struct bt_bip_client_cb *cb, struct net_buf *buf) +{ + return bt_bip_primary_client_connect(bip, client, + BT_BIP_PRIM_CONN_TYPE_REMOTE_DISPLAY, + cb, buf); +} + +/** + * @brief Connect Secondary Advanced Image Printing OBEX client + * + * Convenience function to connect a Secondary Advanced Image Printing + * client. + * + * @param bip BIP instance + * @param client BIP client structure + * @param cb Client callback functions + * @param buf Connection request data + * @param primary_server Associated primary server + * @return 0 on success, negative error code on failure + */ + +static inline int bt_bip_secondary_advanced_image_printing_client_connect( + struct bt_bip *bip, struct bt_bip_client *client, + struct bt_bip_client_cb *cb, struct net_buf *buf, + struct bt_bip_server *primary_server) +{ + return bt_bip_secondary_client_connect(bip, client, + BT_BIP_2ND_CONN_TYPE_REFERENCED_OBJECTS, + cb, buf, primary_server); +} + +/** + * @brief Connect Secondary Auto Archive OBEX client + * + * Convenience function to connect a Secondary Auto Archive client. + * + * @param bip BIP instance + * @param client BIP client structure + * @param cb Client callback functions + * @param buf Connection request data + * @param primary_server Associated primary server + * @return 0 on success, negative error code on failure + */ + +static inline int +bt_bip_secondary_auto_archive_client_connect(struct bt_bip *bip, + struct bt_bip_client *client, + struct bt_bip_client_cb *cb, + struct net_buf *buf, + struct bt_bip_server *primary_server) +{ + return bt_bip_secondary_client_connect(bip, client, + BT_BIP_2ND_CONN_TYPE_ARCHIVED_OBJECTS, + cb, buf, primary_server); +} + +/** + * @brief Send OBEX Connect response + * + * Sends a response to an OBEX Connect request received by the server. + * + * @param server BIP server instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + * @return 0 on success, negative error code on failure + */ + +int bt_bip_connect_rsp(struct bt_bip_server *server, + uint8_t rsp_code, struct net_buf *buf); + +/** + * @brief Send OBEX Disconnect request + * + * Initiates an OBEX Disconnect from the client side. + * + * @param client BIP client instance + * @param buf Request data buffer + * @return 0 on success, negative error code on failure + */ + +int bt_bip_disconnect(struct bt_bip_client *client, struct net_buf *buf); + +/** + * @brief Send OBEX Disconnect response + * + * Sends a response to an OBEX Disconnect request received by the server. + * + * @param server BIP server instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + * @return 0 on success, negative error code on failure + */ + +int bt_bip_disconnect_rsp(struct bt_bip_server *server, + uint8_t rsp_code, struct net_buf *buf); + +/** + * @brief Send OBEX Abort request + * + * Initiates an OBEX Abort from the client side to cancel ongoing operation. + * + * @param client BIP client instance + * @param buf Request data buffer + * @return 0 on success, negative error code on failure + */ + +int bt_bip_abort(struct bt_bip_client *client, struct net_buf *buf); + +/** + * @brief Send OBEX Abort response + * + * Sends a response to an OBEX Abort request received by the server. + * + * @param server BIP server instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + * @return 0 on success, negative error code on failure + */ + +int bt_bip_abort_rsp(struct bt_bip_server *server, + uint8_t rsp_code, struct net_buf *buf); + +/** + * @brief Send Get Capabilities request + * + * Requests the imaging capabilities from the server. The following OBEX + * headers should be included for the first request, + * @ref BT_OBEX_HEADER_ID_CONN_ID and @ref BT_OBEX_HEADER_ID_TYPE. + * And the value of type header is @ref BT_BIP_HDR_TYPE_GET_CAPS. + * + * @param client BIP client instance + * @param final True if this is the final packet + * @param buf Request data buffer + * @return 0 on success, negative error code on failure + */ + +int bt_bip_get_capabilities(struct bt_bip_client *client, + bool final, struct net_buf *buf); + +/** + * @brief Send Get Capabilities response + * + * Responds to a Get Capabilities request with the server's capabilities. The + * following OBEX headers should be included for the first response, + * @ref BT_OBEX_HEADER_ID_BODY or @ref BT_OBEX_HEADER_ID_END_BODY. + * + * @param server BIP server instance + * @param rsp_code OBEX response code + * @param buf Response data buffer (capabilities document) + * @return 0 on success, negative error code on failure + */ + +int bt_bip_get_capabilities_rsp( + struct bt_bip_server *server, uint8_t rsp_code, struct net_buf *buf); + +/** + * @brief Send Get Image List request + * + * Requests a list of available images from the server. The following OBEX + * headers should be included for the first request, + * @ref BT_OBEX_HEADER_ID_CONN_ID, @ref BT_OBEX_HEADER_ID_TYPE, @ref + * BT_OBEX_HEADER_ID_APP_PARAM, and @ref BT_BIP_HEADER_ID_IMG_DESC. And the + * value of type header is @ref BT_BIP_HDR_TYPE_GET_IMAGE_LIST. The + * application parameter should include ID @ref + * BT_BIP_APPL_PARAM_TAG_ID_RETURNED_HANDLES, + * @ref BT_BIP_APPL_PARAM_TAG_ID_LIST_START_OFFSET, and + * @ref BT_BIP_APPL_PARAM_TAG_ID_LATEST_CAPTURED_IMAGES. + * + * @param client BIP client instance + * @param final True if this is the final packet + * @param buf Request data buffer + * @return 0 on success, negative error code on failure + */ + +int bt_bip_get_image_list(struct bt_bip_client *client, + bool final, struct net_buf *buf); + +/** + * @brief Send Get Image List response + * + * Responds to a Get Image List request with the available image list. The + * following OBEX headers should be included for the first response, + * @ref BT_OBEX_HEADER_ID_APP_PARAM and @ref BT_BIP_HEADER_ID_IMG_DESC. + * The application parameter should include ID @ref + * BT_BIP_APPL_PARAM_TAG_ID_RETURNED_HANDLES. + * + * @param server BIP server instance + * @param rsp_code OBEX response code + * @param buf Response data buffer (image list document) + * @return 0 on success, negative error code on failure + */ + +int bt_bip_get_image_list_rsp(struct bt_bip_server *server, uint8_t rsp_code, + struct net_buf *buf); + +/** + * @brief Send Get Image Properties request + * + * Requests properties of a specific image from the server. The following + * OBEX headers should be included for the first request, + * @ref BT_OBEX_HEADER_ID_CONN_ID, @ref BT_OBEX_HEADER_ID_TYPE, and + * @ref BT_BIP_HEADER_ID_IMG_HANDLE. + * And the value of type header is @ref BT_BIP_HDR_TYPE_GET_IMAGE_PROPERTIES. + * + * @param client BIP client instance + * @param final True if this is the final packet + * @param buf Request data buffer (should contain image handle) + * @return 0 on success, negative error code on failure + */ + +int bt_bip_get_image_properties(struct bt_bip_client *client, + bool final, struct net_buf *buf); + +/** + * @brief Send Get Image Properties response + * + * Responds to a Get Image Properties request with the image properties. The + * following OBEX headers should be included for the first response, + * @ref BT_OBEX_HEADER_ID_APP_PARAM, and @ref BT_OBEX_HEADER_ID_BODY, + * or @ref BT_OBEX_HEADER_ID_END_BODY. The application parameter should + * include ID @ref BT_BIP_APPL_PARAM_TAG_ID_RETURNED_HANDLES. + * + * @param server BIP server instance + * @param rsp_code OBEX response code + * @param buf Response data buffer (image properties document) + * @return 0 on success, negative error code on failure + */ + +int bt_bip_get_image_properties_rsp( + struct bt_bip_server *server, uint8_t rsp_code, struct net_buf *buf); + +/** + * @brief Send Get Image request + * + * Requests an image from the server. The following OBEX headers should be + * included for the first request, + * @ref BT_OBEX_HEADER_ID_CONN_ID, @ref BT_OBEX_HEADER_ID_TYPE, @ref + * BT_BIP_HEADER_ID_IMG_HANDLE and @ref BT_BIP_HEADER_ID_IMG_DESC. And the + * value of type header is @ref BT_BIP_HDR_TYPE_GET_IMAGE. + * + * @param client BIP client instance + * @param final True if this is the final packet + * @param buf Request data buffer (should contain image handle and + * descriptor) + * @return 0 on success, negative error code on failure + */ + +int bt_bip_get_image(struct bt_bip_client *client, + bool final, struct net_buf *buf); + +/** + * @brief Send Get Image response + * + * Responds to a Get Image request with the requested image data. The + * following OBEX headers should be included for the first response, + * @ref BT_OBEX_HEADER_ID_LEN, and @ref BT_OBEX_HEADER_ID_BODY, + * or @ref BT_OBEX_HEADER_ID_END_BODY. + * + * @param server BIP server instance + * @param rsp_code OBEX response code + * @param buf Response data buffer (image data) + * @return 0 on success, negative error code on failure + */ + +int bt_bip_get_image_rsp(struct bt_bip_server *server, + uint8_t rsp_code, struct net_buf *buf); + +/** + * @brief Send Get Linked Thumbnail request + * + * Requests a thumbnail linked to a specific image. The following OBEX + * headers should be included for the first request, + * @ref BT_OBEX_HEADER_ID_CONN_ID, @ref BT_OBEX_HEADER_ID_TYPE, and + * @ref BT_BIP_HEADER_ID_IMG_HANDLE. + * And the value of type header is @ref BT_BIP_HDR_TYPE_GET_LINKED_THUMBNAIL. + * + * @param client BIP client instance + * @param final True if this is the final packet + * @param buf Request data buffer (should contain image handle) + * @return 0 on success, negative error code on failure + */ + +int bt_bip_get_linked_thumbnail(struct bt_bip_client *client, + bool final, struct net_buf *buf); + +/** + * @brief Send Get Linked Thumbnail response + * + * Responds to a Get Linked Thumbnail request with the thumbnail data. The + * following OBEX headers should be included for the first response, + * @ref BT_OBEX_HEADER_ID_BODY, or @ref BT_OBEX_HEADER_ID_END_BODY. + * + * @param server BIP server instance + * @param rsp_code OBEX response code + * @param buf Response data buffer (thumbnail data) + * @return 0 on success, negative error code on failure + */ + +int bt_bip_get_linked_thumbnail_rsp( + struct bt_bip_server *server, uint8_t rsp_code, struct net_buf *buf); + +/** + * @brief Send Get Linked Attachment request + * + * Requests an attachment linked to a specific image. The following OBEX + * headers should be included for the first request, + * @ref BT_OBEX_HEADER_ID_CONN_ID, @ref BT_OBEX_HEADER_ID_TYPE, @ref + * BT_BIP_HEADER_ID_IMG_HANDLE, and @ref BT_OBEX_HEADER_ID_NAME. And the + * value of type header is @ref BT_BIP_HDR_TYPE_GET_LINKED_ATTACHMENT. + * + * @param client BIP client instance + * @param final True if this is the final packet + * @param buf Request data buffer (should contain image handle) + * @return 0 on success, negative error code on failure + */ + +int bt_bip_get_linked_attachment(struct bt_bip_client *client, + bool final, struct net_buf *buf); + +/** + * @brief Send Get Linked Attachment response + * + * Responds to a Get Linked Attachment request with the attachment data. The + * following OBEX headers should be included for the first response, + * @ref BT_OBEX_HEADER_ID_BODY, or @ref BT_OBEX_HEADER_ID_END_BODY. + * + * @param server BIP server instance + * @param rsp_code OBEX response code + * @param buf Response data buffer (attachment data) + * @return 0 on success, negative error code on failure + */ + +int bt_bip_get_linked_attachment_rsp( + struct bt_bip_server *server, uint8_t rsp_code, struct net_buf *buf); + +/** + * @brief Send Get Partial Image request + * + * Requests a partial image (portion of an image) from the server. The + * following OBEX headers should be included for the first request, + * @ref BT_OBEX_HEADER_ID_CONN_ID, @ref BT_OBEX_HEADER_ID_TYPE, @ref + * BT_OBEX_HEADER_ID_NAME, and @ref BT_OBEX_HEADER_ID_APP_PARAM. And the + * value of type header is @ref BT_BIP_HDR_TYPE_GET_PARTIAL_IMAGE. The + * application parameter should include ID @ref + * BT_BIP_APPL_PARAM_TAG_ID_PARTIAL_FILE_LEN, and @ref + * BT_BIP_APPL_PARAM_TAG_ID_PARTIAL_FILE_START_OFFSET. + * + * @param client BIP client instance + * @param final True if this is the final packet + * @param buf Request data buffer (should contain image handle and range + * info) + * @return 0 on success, negative error code on failure + */ + +int bt_bip_get_partial_image(struct bt_bip_client *client, + bool final, struct net_buf *buf); + +/** + * @brief Send Get Partial Image response + * + * Responds to a Get Partial Image request with the requested image portion. + * The following OBEX headers should be included for the first response, + * @ref BT_OBEX_HEADER_ID_BODY, or @ref BT_OBEX_HEADER_ID_END_BODY. + * + * @param server BIP server instance + * @param rsp_code OBEX response code + * @param buf Response data buffer (partial image data) + * @return 0 on success, negative error code on failure + */ + +int bt_bip_get_partial_image_rsp( + struct bt_bip_server *server, uint8_t rsp_code, struct net_buf *buf); + +/** + * @brief Send Get Monitoring Image request + * + * Requests a monitoring image from a remote camera. The following OBEX + * headers should be included for the first request, + * @ref BT_OBEX_HEADER_ID_CONN_ID, @ref BT_OBEX_HEADER_ID_TYPE, + * and @ref BT_OBEX_HEADER_ID_APP_PARAM. And the value of type header is @ref + * BT_BIP_HDR_TYPE_GET_MONITORING_IMAGE. The application parameter should + * include ID @ref BT_BIP_APPL_PARAM_TAG_ID_STORE_FLAG. + * + * @param client BIP client instance + * @param final True if this is the final packet + * @param buf Request data buffer + * @return 0 on success, negative error code on failure + */ + +int bt_bip_get_monitoring_image(struct bt_bip_client *client, + bool final, struct net_buf *buf); + +/** + * @brief Send Get Monitoring Image response + * + * Responds to a Get Monitoring Image request with the monitoring image. The + * following OBEX headers should be included for the first response, + * @ref BT_BIP_HEADER_ID_IMG_HANDLE and @ref BT_OBEX_HEADER_ID_BODY, or + * @ref BT_OBEX_HEADER_ID_END_BODY. + * + * @param server BIP server instance + * @param rsp_code OBEX response code + * @param buf Response data buffer (monitoring image data) + * @return 0 on success, negative error code on failure + */ + +int bt_bip_get_monitoring_image_rsp( + struct bt_bip_server *server, uint8_t rsp_code, struct net_buf *buf); + +/** + * @brief Send Get Status request + * + * Requests status information from the server. The following OBEX headers + * should be included for the first request, + * @ref BT_OBEX_HEADER_ID_CONN_ID, and @ref BT_OBEX_HEADER_ID_TYPE. + * And the value of type header is @ref BT_BIP_HDR_TYPE_GET_STATUS. + * + * @param client BIP client instance + * @param final True if this is the final packet + * @param buf Request data buffer + * @return 0 on success, negative error code on failure + */ + +int bt_bip_get_status(struct bt_bip_client *client, + bool final, struct net_buf *buf); + +/** + * @brief Send Get Status response + * + * Responds to a Get Status request with status information. + * + * @param server BIP server instance + * @param rsp_code OBEX response code + * @param buf Response data buffer (status document) + * @return 0 on success, negative error code on failure + */ + +int bt_bip_get_status_rsp(struct bt_bip_server *server, + uint8_t rsp_code, struct net_buf *buf); + +/** + * @brief Send Put Image request + * + * Sends an image to the server. The following OBEX headers should be + * included for the first request, + * @ref BT_OBEX_HEADER_ID_CONN_ID, @ref BT_OBEX_HEADER_ID_TYPE, @ref + * BT_OBEX_HEADER_ID_NAME, and @ref BT_BIP_HEADER_ID_IMG_DESC. And the value + * of type header is @ref BT_BIP_HDR_TYPE_PUT_IMAGE. + * + * @param client BIP client instance + * @param final True if this is the final packet + * @param buf Request data buffer (image data and metadata) + * @return 0 on success, negative error code on failure + */ + +int bt_bip_put_image(struct bt_bip_client *client, + bool final, struct net_buf *buf); + +/** + * @brief Send Put Image response + * + * Responds to a Put Image request. The following OBEX headers should be + * included for the first response, + * @ref BT_BIP_HEADER_ID_IMG_HANDLE. + * + * @param server BIP server instance + * @param rsp_code OBEX response code + * @param buf Response data buffer (may contain image handle) + * @return 0 on success, negative error code on failure + */ + +int bt_bip_put_image_rsp(struct bt_bip_server *server, + uint8_t rsp_code, struct net_buf *buf); + +/** + * @brief Send Put Linked Thumbnail request + * + * Sends a thumbnail linked to a specific image. The following OBEX headers + * should be included for the first request, + * @ref BT_OBEX_HEADER_ID_CONN_ID, @ref BT_OBEX_HEADER_ID_TYPE, and + * @ref BT_BIP_HEADER_ID_IMG_HANDLE. + * And the value of type header is @ref BT_BIP_HDR_TYPE_PUT_LINKED_THUMBNAIL. + * + * @param client BIP client instance + * @param final True if this is the final packet + * @param buf Request data buffer (thumbnail data and image handle) + * @return 0 on success, negative error code on failure + */ + +int bt_bip_put_linked_thumbnail(struct bt_bip_client *client, + bool final, struct net_buf *buf); + +/** + * @brief Send Put Linked Thumbnail response + * + * Responds to a Put Linked Thumbnail request. + * + * @param server BIP server instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + * @return 0 on success, negative error code on failure + */ + +int bt_bip_put_linked_thumbnail_rsp( + struct bt_bip_server *server, uint8_t rsp_code, struct net_buf *buf); + +/** + * @brief Send Put Linked Attachment request + * + * Sends an attachment linked to a specific image. The following OBEX headers + * should be included for the first request, + * @ref BT_OBEX_HEADER_ID_CONN_ID, @ref BT_OBEX_HEADER_ID_TYPE, @ref + * BT_BIP_HEADER_ID_IMG_HANDLE, and @ref BT_BIP_HEADER_ID_IMG_DESC. And the + * value of type header is @ref BT_BIP_HDR_TYPE_PUT_LINKED_ATTACHMENT. + * + * @param client BIP client instance + * @param final True if this is the final packet + * @param buf Request data buffer (attachment data and image handle) + * @return 0 on success, negative error code on failure + */ + +int bt_bip_put_linked_attachment(struct bt_bip_client *client, + bool final, struct net_buf *buf); + +/** + * @brief Send Put Linked Attachment response + * + * Responds to a Put Linked Attachment request. + * + * @param server BIP server instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + * @return 0 on success, negative error code on failure + */ + +int bt_bip_put_linked_attachment_rsp( + struct bt_bip_server *server, uint8_t rsp_code, struct net_buf *buf); + +/** + * @brief Send Remote Display request + * + * Sends a remote display command to the server. The following OBEX headers + * should be included for the first request, + * @ref BT_OBEX_HEADER_ID_CONN_ID, @ref BT_OBEX_HEADER_ID_TYPE, @ref + * BT_BIP_HEADER_ID_IMG_HANDLE, and @ref BT_OBEX_HEADER_ID_APP_PARAM. And the + * value of type header is @ref BT_BIP_HDR_TYPE_REMOTE_DISPLAY. The + * application parameter should include ID @ref + * BT_BIP_APPL_PARAM_TAG_ID_REMOTE_DISPLAY. + * + * @param client BIP client instance + * @param final True if this is the final packet + * @param buf Request data buffer (display command) + * @return 0 on success, negative error code on failure + */ + +int bt_bip_remote_display(struct bt_bip_client *client, + bool final, struct net_buf *buf); + +/** + * @brief Send Remote Display response + * + * Responds to a Remote Display request. The following OBEX headers should be + * included for the first response, + * @ref BT_BIP_HEADER_ID_IMG_HANDLE. + * + * @param server BIP server instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + * @return 0 on success, negative error code on failure + */ + +int bt_bip_remote_display_rsp(struct bt_bip_server *server, uint8_t rsp_code, + struct net_buf *buf); + +/** + * @brief Send Delete Image request + * + * Requests deletion of a specific image on the server. The following OBEX + * headers should be included for the first request, + * @ref BT_OBEX_HEADER_ID_CONN_ID, @ref BT_OBEX_HEADER_ID_TYPE, and + * @ref BT_BIP_HEADER_ID_IMG_HANDLE. + * And the value of type header is @ref BT_BIP_HDR_TYPE_DELETE_IMAGE. + * + * @param client BIP client instance + * @param final True if this is the final packet + * @param buf Request data buffer (should contain image handle) + * @return 0 on success, negative error code on failure + */ + +int bt_bip_delete_image(struct bt_bip_client *client, + bool final, struct net_buf *buf); + +/** + * @brief Send Delete Image response + * + * Responds to a Delete Image request. + * + * @param server BIP server instance + * @param rsp_code OBEX response code + * @param buf Response data buffer + * @return 0 on success, negative error code on failure + */ + +int bt_bip_delete_image_rsp(struct bt_bip_server *server, + uint8_t rsp_code, struct net_buf *buf); + +/** + * @brief Send Start Print request + * + * Initiates a print job on the server. The following OBEX headers should be + * included for the first request, + * @ref BT_OBEX_HEADER_ID_CONN_ID, @ref BT_OBEX_HEADER_ID_TYPE, and + * @ref BT_OBEX_HEADER_ID_APP_PARAM. + * And the value of type header is @ref BT_BIP_HDR_TYPE_START_PRINT. The + * application parameter should include ID @ref + * BT_BIP_APPL_PARAM_TAG_ID_STORE_FLAG. + * + * @param client BIP client instance + * @param final True if this is the final packet + * @param buf Request data buffer (print control object) + * @return 0 on success, negative error code on failure + */ + +int bt_bip_start_print(struct bt_bip_client *client, + bool final, struct net_buf *buf); + +/** + * @brief Send Start Print response + * + * Responds to a Start Print request. + * + * @param server BIP server instance + * @param rsp_code OBEX response code + * @param buf Response data buffer (may contain job ID) + * @return 0 on success, negative error code on failure + */ + +int bt_bip_start_print_rsp(struct bt_bip_server *server, + uint8_t rsp_code, struct net_buf *buf); + +/** + * @brief Send Start Archive request + * + * Initiates an archive operation on the server. The following OBEX headers + * should be included for the first request, + * @ref BT_OBEX_HEADER_ID_CONN_ID, @ref BT_OBEX_HEADER_ID_TYPE, and + * @ref BT_OBEX_HEADER_ID_APP_PARAM. + * And the value of type header is @ref BT_BIP_HDR_TYPE_START_ARCHIVE. The + * application parameter should include ID @ref + * BT_BIP_APPL_PARAM_TAG_ID_STORE_FLAG. + * + * @param client BIP client instance + * @param final True if this is the final packet + * @param buf Request data buffer (archive control object) + * @return 0 on success, negative error code on failure + */ + +int bt_bip_start_archive(struct bt_bip_client *client, + bool final, struct net_buf *buf); + +/** + * @brief Send Start Archive response + * + * Responds to a Start Archive request. + * + * @param server BIP server instance + * @param rsp_code OBEX response code + * @param buf Response data buffer (may contain job ID) + * @return 0 on success, negative error code on failure + */ + +int bt_bip_start_archive_rsp(struct bt_bip_server *server, + uint8_t rsp_code, struct net_buf *buf); + +/** + * @brief Add Image Descriptor header to buffer + * + * Adds an Image Descriptor header to the OBEX packet buffer. The descriptor + * contains format and encoding information for the image. + * + * @param buf Buffer to add header to + * @param len Length of descriptor data + * @param desc Pointer to descriptor data + * @return 0 on success, negative error code on failure + */ + +int bt_bip_add_header_image_desc(struct net_buf *buf, + uint16_t len, const uint8_t *desc); + +/** + * @brief Add Image Handle header to buffer + * + * Adds an Image Handle header to the OBEX packet buffer. The handle is a + * UTF-16 encoded string that uniquely identifies an image. + * + * @param buf Buffer to add header to + * @param len Length of handle data in bytes + * @param handle Pointer to UTF-16 encoded handle data + * @return 0 on success, negative error code on failure + */ + +int bt_bip_add_header_image_handle(struct net_buf *buf, + uint16_t len, const uint8_t *handle); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_BIP_H */ diff --git a/include/nuttx/net/bluetooth/classic/classic.h b/include/nuttx/net/bluetooth/classic/classic.h new file mode 100644 index 0000000000000..696c4106768f0 --- /dev/null +++ b/include/nuttx/net/bluetooth/classic/classic.h @@ -0,0 +1,734 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/classic/classic.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Bluetooth subsystem classic core APIs. + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_CLASSIC_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_CLASSIC_H + +/** + * @brief Bluetooth APIs + * @defgroup bluetooth Bluetooth APIs + * @ingroup connectivity + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Generic Access Profile (GAP) + * @defgroup bt_gap Generic Access Profile (GAP) + * @since 1.0 + * @version 1.1.1 + * @ingroup bluetooth + * @{ + */ + +/** + * @private + * @brief BR/EDR discovery private structure + */ + +struct bt_br_discovery_priv +{ + /** Clock offset */ + + uint16_t clock_offset; + + /** Page scan repetition mode */ + + uint8_t pscan_rep_mode; + + /** Resolving remote name */ + + uint8_t resolve_state; +}; + +/** Maximum size of Extended Inquiry Response */ +#define BT_BR_EIR_SIZE_MAX 240 + +/** @brief BR/EDR discovery result structure */ + +struct bt_br_discovery_result +{ + /** Private data */ + + struct bt_br_discovery_priv _priv; + + /** Remote device address */ + + bt_addr_t addr; + + /** RSSI from inquiry in dBm. Range: -127 to +20. */ + + int8_t rssi; + + /** Class of Device */ + + uint8_t cod[3]; + + /** Extended Inquiry Response */ + + uint8_t eir[BT_BR_EIR_SIZE_MAX]; +}; + +/** BR/EDR discovery parameters */ + +struct bt_br_discovery_param +{ + /** Maximum length of the discovery in units of 1.28 seconds. Valid range + * is 0x01 - 0x30. + */ + + uint8_t length; + + /** True if limited discovery procedure is to be used. */ + + bool limited; +}; + +/** + * @brief Start BR/EDR discovery + * + * Start BR/EDR discovery (inquiry) and provide results through the specified + * callback. The discovery results will be notified through callbacks + * registered by @ref bt_br_discovery_cb_register. If more inquiry results + * were received during session than fits in provided result storage, only + * ones with highest RSSI will be reported. + * + * @param param Discovery parameters. + * @param results Storage for discovery results. + * @param count Number of results in storage. Valid range: 1-255. + * + * @return Zero on success or error code otherwise, positive in case + * of protocol error or negative (POSIX) in case of stack internal error + */ + +int bt_br_discovery_start(const struct bt_br_discovery_param *param, + struct bt_br_discovery_result *results, + size_t count); + +/** + * @brief Stop BR/EDR discovery. + * + * Stops ongoing BR/EDR discovery. If discovery was stopped by this call + * results won't be reported + * + * @return Zero on success or error code otherwise, positive in case of + * protocol error or negative (POSIX) in case of stack internal error. + */ + +int bt_br_discovery_stop(void); + +struct bt_br_discovery_cb +{ + /* @brief An inquiry response received callback. + * + * @param result Storage used for discovery results + */ + + void (*recv)(const struct bt_br_discovery_result *result); + + /** @brief The inquiry has stopped after discovery timeout. + * + * @param results Storage used for discovery results + * @param count Number of valid discovery results. + */ + + void (*timeout)(const struct bt_br_discovery_result *results, + size_t count); + + struct list_node node; +}; + +/** + * @brief Register discovery packet callbacks. + * + * Adds the callback structure to the list of callback structures that + * monitors inquiry activity. + * + * This callback will be called for all inquiry activity, regardless of what + * API was used to start the discovery. + * + * @param cb Callback struct. Must point to memory that remains valid. + */ + +void bt_br_discovery_cb_register(struct bt_br_discovery_cb *cb); + +/** + * @brief Unregister discovery packet callbacks. + * + * Remove the callback structure from the list of discovery callbacks. + * + * @param cb Callback struct. Must point to memory that remains valid. + */ + +void bt_br_discovery_cb_unregister(struct bt_br_discovery_cb *cb); + +struct bt_br_oob +{ + /** BR/EDR address. */ + + bt_addr_t addr; +}; + +/** + * @brief Get BR/EDR local Out Of Band information + * + * This function allows to get local controller information that are useful + * for Out Of Band pairing or connection creation process. + * + * @param oob Out Of Band information + */ + +int bt_br_oob_get_local(struct bt_br_oob *oob); + +/** + * @brief Enable/disable set controller in discoverable state. + * + * Allows make local controller to listen on INQUIRY SCAN channel and + * responds to devices making general inquiry. To enable this state it's + * mandatory to first be in connectable state. + * + * If the device enters limited discoverable mode, the controller will leave + * from discoverable mode after the duration of + * @kconfig{CONFIG_BT_LIMITED_DISCOVERABLE_DURATION} seconds in the limited + * discoverable mode. + * + * @param enable Value allowing/disallowing controller to become + * discoverable. + * @param limited Value allowing/disallowing controller to enter limited + * discoverable mode. + * + * @return Negative if fail set to requested state or requested state has + * been already set. Zero if done successfully. + */ + +int bt_br_set_discoverable(bool enable, bool limited); + +/** + * @brief BR/EDR connection request response codes. + * + * Response codes for incoming BR/EDR connection requests, used by the + * connection request callback to accept or reject connections. + */ + +enum bt_br_conn_req_rsp +{ + /** Accept connection as central role */ + + BT_BR_CONN_REQ_ACCEPT_CENTRAL = BT_HCI_ROLE_CENTRAL, + + /** Accept connection as peripheral role */ + + BT_BR_CONN_REQ_ACCEPT_PERIPHERAL = BT_HCI_ROLE_PERIPHERAL, + + /** Reject connection due to insufficient resources */ + + BT_BR_CONN_REQ_REJECT_NO_RESOURCES = BT_HCI_ERR_INSUFFICIENT_RESOURCES, + + /** Reject connection due to insufficient security */ + + BT_BR_CONN_REQ_REJECT_SECURITY = BT_HCI_ERR_INSUFFICIENT_SECURITY, + + /** Reject connection due to unacceptable device address */ + + BT_BR_CONN_REQ_REJECT_ADDR = BT_HCI_ERR_BD_ADDR_UNACCEPTABLE, +}; + +/** + * @brief Callback type for incoming BR/EDR connection requests. + * + * This callback is invoked when a remote Bluetooth Classic device attempts + * to establish a connection to the local controller. The application must + * return a response code to accept or reject the connection, and specify the + * desired role if accepting. + * + * @param addr Remote device address attempting to connect. + * @param cod Class of Device of the remote device. + * + * @return @ref bt_br_conn_req_rsp Response code indicating whether to accept + * or reject the connection, and the desired role if accepting. + */ + +typedef enum bt_br_conn_req_rsp (*bt_br_conn_req_func_t)( + const bt_addr_t *addr, uint32_t cod); + +/** + * @brief Enable/disable set controller in connectable state. + * + * Allows make local controller to be connectable. It means the controller + * start listen to devices requests on PAGE SCAN channel. If disabled also + * resets discoverability if was set. + * + * @param enable Value allowing/disallowing controller to be connectable. + * @param func Callback function to handle incoming connection requests. If + * the @p enable is false, this parameter is ignored. If @p + * func is NULL, the conn_req will be accepted internally. The + * default role is peripheral. The role switch request can be + * performed if the + * @kconfig{CONFIG_BT_ACCEPT_CONN_AS_CENTRAL} is enabled. If @p func is + * provided, the conn_req will be passed to the callback function for the + * application to decide whether to accept or reject the connection, and the + * desired role for the connection. + * + * @return Negative if fail set to requested state or requested state has + * been already set. Zero if done successfully. + */ + +int bt_br_set_connectable(bool enable, bt_br_conn_req_func_t func); + +/** Minimum BR/EDR scan interval (0x0012, 11.25 ms, 0.625 ms units). */ +#define BT_BR_SCAN_INTERVAL_MIN 0x0012 +/** Maximum BR/EDR scan interval (0x1000, 2.560 s, 0.625 ms units). */ +#define BT_BR_SCAN_INTERVAL_MAX 0x1000 + +/** Minimum BR/EDR scan window (0x0011, 10.625 ms, 0.625 ms units). */ +#define BT_BR_SCAN_WINDOW_MIN 0x0011 +/** Maximum BR/EDR scan window (0x1000, 2.560 s, 0.625 ms units). */ +#define BT_BR_SCAN_WINDOW_MAX 0x1000 + +/** + * @name Defined BR/EDR Page Scan Intervals and Windows + * @{ + */ + +/** Default page scan interval (R0) (0x0800, 1.280 s, 0.625 ms units). */ +#define BT_BR_PAGE_SCAN_INTERVAL_R0 0x0800 +/** Default page scan window (R0) (0x0800, 1.280 s, 0.625 ms units). */ +#define BT_BR_PAGE_SCAN_WINDOW_R0 0x0800 + +/** Fast page scan interval (R1) (0x00a0, 100.0 ms, 0.625 ms units). */ +#define BT_BR_PAGE_SCAN_FAST_INTERVAL_R1 0x00a0 +/** Fast page scan window (R1) (0x0011, 10.625 ms, 0.625 ms units). */ +#define BT_BR_PAGE_SCAN_FAST_WINDOW_R1 0x0011 + +/** Medium page scan interval (R1) (0x0800, 1.280 s, 0.625 ms units). */ +#define BT_BR_PAGE_SCAN_MEDIUM_INTERVAL_R1 0x0800 +/** Medium page scan window (R1) (0x0011, 10.625 ms, 0.625 ms units). */ +#define BT_BR_PAGE_SCAN_MEDIUM_WINDOW_R1 0x0011 + +/** Slow page scan interval (R1) (0x0800, 1.280 s, 0.625 ms units). */ +#define BT_BR_PAGE_SCAN_SLOW_INTERVAL_R1 0x0800 +/** Slow page scan window (R1) (0x0011, 10.625 ms, 0.625 ms units). */ +#define BT_BR_PAGE_SCAN_SLOW_WINDOW_R1 0x0011 + +/** Fast page scan interval (R2) (0x1000, 2.560 s, 0.625 ms units). */ +#define BT_BR_PAGE_SCAN_FAST_INTERVAL_R2 0x1000 +/** Fast page scan window (R2) (0x0011, 10.625 ms, 0.625 ms units). */ +#define BT_BR_PAGE_SCAN_FAST_WINDOW_R2 0x0011 + +/** Slow page scan interval (R2) (0x1000, 2.560 s, 0.625 ms units). */ +#define BT_BR_PAGE_SCAN_SLOW_INTERVAL_R2 0x1000 +/** Slow page scan window (R2) (0x0011, 10.625 ms, 0.625 ms units). */ +#define BT_BR_PAGE_SCAN_SLOW_WINDOW_R2 0x0011 + +/** + * @} + */ + +/** Page scan type. */ + +enum bt_br_scan_type +{ + /** Standard scan (default) */ + + BT_BR_SCAN_TYPE_STANDARD = 0, + + /** Interlaced scan (1.2 devices only) */ + + BT_BR_SCAN_TYPE_INTERLACED = 1, +}; + +/** Page scan parameters. */ + +struct bt_br_page_scan_param +{ + /** Page scan interval in 0.625 ms units + * Range: 0x0012 to 0x1000. + * + * According to the Bluetooth Core Specification, only even values are + * used by the controller; if an odd value is provided, the controller may + * ignore the LSB or round the value. Applications should use even values + * to be compliant. + */ + + uint16_t interval; + + /** Page scan window in 0.625 ms units Range: 0x0011 to 0x1000. */ + + uint16_t window; + + /** Page scan type. */ + + enum bt_br_scan_type type; +}; + +/** + * @brief Initialize BR Scan parameters + * + * @param _interval Scan interval + * @param _window Scan window + * @param _type Scan type + */ + +#define BT_BR_SCAN_INIT(_interval, _window, _type) \ +{.interval = (_interval), .window = (_window), .type = (_type)} + +/** + * @brief Helper to declare BR/EDR page scan parameters inline + * + * @param _interval page scan interval, N * 0.625 milliseconds + * @param _window page scan window, N * 0.625 milliseconds + * @param _type @ref BT_BR_SCAN_TYPE_STANDARD or @ref + * BT_BR_SCAN_TYPE_INTERLACED + */ + +#define BT_BR_PAGE_SCAN_PARAM(_interval, _window, _type) \ +((const struct bt_br_page_scan_param[]){BT_BR_SCAN_INIT(_interval, _window, _type)}) + +/** + * @brief Default page scan parameters for R0 + * + * Page scan interval and window are set to 1.280 seconds (0x0800 in 0.625 ms + * units). The scan type is set to standard. + */ +#define BT_BR_PAGE_SCAN_PARAM_R0 \ +BT_BR_PAGE_SCAN_PARAM(BT_BR_PAGE_SCAN_INTERVAL_R0, BT_BR_PAGE_SCAN_WINDOW_R0, \ + BT_BR_SCAN_TYPE_STANDARD) + +/** + * @brief Fast page scan parameters for R1 + * + * Page scan interval is set to 100 ms (0x00A0 in 0.625 ms units), and the + * page scan window is set to 10.625ms (0x0011 in 0.625 ms units). The scan + * type is set to interlaced. + */ +#define BT_BR_PAGE_SCAN_PARAM_FAST_R1 \ +BT_BR_PAGE_SCAN_PARAM(BT_BR_PAGE_SCAN_FAST_INTERVAL_R1, BT_BR_PAGE_SCAN_FAST_WINDOW_R1, \ + BT_BR_SCAN_TYPE_INTERLACED) + +/** + * @brief Medium page scan parameters for R1 + * + * Page scan interval is set to 1.280 seconds (0x0800 in 0.625 ms units), and + * the page scan window is set to 10.625ms (0x0011 in 0.625 ms units). The + * scan type is set to interlaced. + */ +#define BT_BR_PAGE_SCAN_PARAM_MEDIUM_R1 \ +BT_BR_PAGE_SCAN_PARAM(BT_BR_PAGE_SCAN_MEDIUM_INTERVAL_R1, BT_BR_PAGE_SCAN_MEDIUM_WINDOW_R1, \ + BT_BR_SCAN_TYPE_INTERLACED) + +/** + * @brief Slow page scan parameters for R1 + * + * Page scan interval is set to 1.280 seconds (0x0800 in 0.625 ms units), and + * the page scan window is set to 10.625ms (0x0011 in 0.625 ms units). The + * scan type is set to standard. + */ +#define BT_BR_PAGE_SCAN_PARAM_SLOW_R1 \ +BT_BR_PAGE_SCAN_PARAM(BT_BR_PAGE_SCAN_SLOW_INTERVAL_R1, BT_BR_PAGE_SCAN_SLOW_WINDOW_R1, \ + BT_BR_SCAN_TYPE_STANDARD) + +/** + * @brief Fast page scan parameters for R2 + * + * Page scan interval is set to 2.560 seconds (0x1000 in 0.625 ms units), and + * the page scan window is set to 10.625ms (0x0011 in 0.625 ms units). The + * scan type is set to interlaced. + */ +#define BT_BR_PAGE_SCAN_PARAM_FAST_R2 \ +BT_BR_PAGE_SCAN_PARAM(BT_BR_PAGE_SCAN_FAST_INTERVAL_R2, BT_BR_PAGE_SCAN_FAST_WINDOW_R2, \ + BT_BR_SCAN_TYPE_INTERLACED) + +/** + * @brief Slow page scan parameters for R2 + * + * Page scan interval is set to 2.560 seconds (0x1000 in 0.625 ms units), and + * the page scan window is set to 10.625ms (0x0011 in 0.625 ms units). The + * scan type is set to standard. + */ +#define BT_BR_PAGE_SCAN_PARAM_SLOW_R2 \ +BT_BR_PAGE_SCAN_PARAM(BT_BR_PAGE_SCAN_SLOW_INTERVAL_R2, BT_BR_PAGE_SCAN_SLOW_WINDOW_R2, \ + BT_BR_SCAN_TYPE_STANDARD) + +/** + * @brief Update BR/EDR page scan parameters. + * + * This function updates the page scan parameters of the local BR/EDR + * controller. Page scan parameters determine how the controller listens for + * incoming connection requests from remote devices. + * + * The function validates the provided parameters, including the interval, + * window, and scan type, and sends the appropriate HCI commands to update + * the controller's page scan activity and scan type. + * + * The user can set custom page scan parameters using the helper macro + * BT_BR_PAGE_SCAN_PARAM() to define their own values. Alternatively, the + * user can use predefined standard parameters as defined in the Bluetooth + * specification: + * - @ref BT_BR_PAGE_SCAN_PARAM_R0 Default page scan parameters. + * - @ref BT_BR_PAGE_SCAN_PARAM_FAST_R1 Fast page scan parameters for R1. + * - @ref BT_BR_PAGE_SCAN_PARAM_MEDIUM_R1 Medium page scan parameters for R1. + * - @ref BT_BR_PAGE_SCAN_PARAM_SLOW_R1 Slow page scan parameters for R1. + * - @ref BT_BR_PAGE_SCAN_PARAM_FAST_R2 Fast page scan parameters for R2. + * - @ref BT_BR_PAGE_SCAN_PARAM_SLOW_R2 Slow page scan parameters for R2. + * + * These predefined parameters are designed to meet common use cases and + * ensure compliance with the Bluetooth specification. + * + * @param param Page scan parameters, including: - interval: Time between + * consecutive page scans (in 0.625 ms units). Must be in the + * range [0x0012, 0x1000]. - window: Duration of a single page + * scan (in 0.625 ms units). Must be in the range [0x0011, + * 0x1000]. - type: Page scan type (e.g., standard or + * interlaced). + * + * @return 0 on success. + * @return -EINVAL if the provided parameters are invalid. + * @return -EAGAIN if the device is not ready. + * @return -ENOBUFS if memory allocation for HCI commands fails. + * @return Other negative error codes for internal failures. + */ + +int bt_br_page_scan_update_param(const struct bt_br_page_scan_param *param); + +/** + * @brief BR/EDR inquiry scan parameters + * @note These parameters are used to configure the inquiry scan behavior of + * the local BR/EDR controller. + */ + +struct bt_br_inquiry_scan_param +{ + /** Inquiry scan interval in 0.625 ms units + * Range: 0x0012 to 0x1000. + * + * According to the Bluetooth Core Specification, only even values are + * used by the controller; if an odd value is provided, the controller may + * ignore the LSB or round the value. Applications should use even values + * to be compliant. + */ + + uint16_t interval; + + /** Inquiry scan window in 0.625 ms units Range: 0x0011 to 0x1000. */ + + uint16_t window; + + /** Inquiry scan type. */ + + enum bt_br_scan_type type; +}; + +/** + * @name Defined BR Inquiry Scan Intervals and Windows + * @{ + */ + +/** Default inquiry scan interval (0x1000, 2.560 s, 0.625 ms units). */ +#define BT_BR_INQUIRY_SCAN_INTERVAL_DEFAULT 0x1000 +/** Default inquiry scan window (0x0012, 11.25 ms, 0.625 ms units). */ +#define BT_BR_INQUIRY_SCAN_WINDOW_DEFAULT 0x0012 + +/** + * @} + */ + +/** + * @brief Helper to declare BR/EDR inquiry scan parameters inline + * + * @param _interval Inquiry scan interval, N * 0.625 milliseconds + * @param _window Inquiry scan window, N * 0.625 milliseconds + * @param _type @ref BT_BR_SCAN_TYPE_STANDARD or @ref + * BT_BR_SCAN_TYPE_INTERLACED + */ +#define BT_BR_INQUIRY_SCAN_PARAM(_interval, _window, _type) \ +((const struct bt_br_inquiry_scan_param[]){BT_BR_SCAN_INIT(_interval, _window, _type)}) + +/** + * @brief Default inquiry scan parameters + * + * Inquiry scan interval is set to 2.560 seconds (0x1000 in 0.625 ms units), + * and the inquiry scan window is set to 11.25 milliseconds (0x0012 in 0.625 + * ms units). The scan type is set to standard. + */ +#define BT_BR_INQUIRY_SCAN_PARAM_DEFAULT \ +BT_BR_INQUIRY_SCAN_PARAM(BT_BR_INQUIRY_SCAN_INTERVAL_DEFAULT, BT_BR_INQUIRY_SCAN_WINDOW_DEFAULT, \ + BT_BR_SCAN_TYPE_STANDARD) + +/** + * @brief Update BR/EDR inquiry scan parameters. + * + * This function updates the inquiry scan parameters of the local BR/EDR + * controller. Inquiry scan parameters determine how the controller handles + * inquiry requests. + * + * The function validates the provided parameters, including the interval, + * window, and scan type, and sends the appropriate HCI commands to update + * the controller's inquiry scan activity and scan type. + * + * The user can set custom inquiry scan parameters using the helper macro + * @ref BT_BR_INQUIRY_SCAN_PARAM to define their own values. + * Alternatively, the user can use predefined standard parameters as defined + * in the Bluetooth specification: + * - @ref BT_BR_INQUIRY_SCAN_PARAM_DEFAULT Default inquiry scan parameters. + * These predefined parameters are designed to meet common use cases and + * ensure compliance with the Bluetooth specification. + * + * @param param Inquiry scan parameters, including: - interval: Time between + * consecutive inquiry scans (in 0.625 ms units). Must be in the + * range [0x0012, 0x1000]. - window: Duration of a single + * inquiry scan (in 0.625 ms units). Must be in the range + * [0x0011, 0x1000]. - type: Inquiry scan type (e.g., standard + * or interlaced). + * + * @return 0 on success. + * @return -EINVAL if the provided parameters are invalid. + * @return -EAGAIN if the device is not ready. + * @return -ENOBUFS if memory allocation for HCI commands fails. + * @return Other negative error codes for internal failures. + */ + +int bt_br_inquiry_scan_update_param( + const struct bt_br_inquiry_scan_param *param); + +/** + * @brief Set the Class of Device of the local BR/EDR Controller. + * + * This function writes the Class of Device (COD) value to the BR/EDR + * controller. The COD is used by remote devices during the discovery process + * to identify the type of device and the services it provides. + * + * @note The limited discoverable mode bit (@ref + * BT_COD_MAJOR_SVC_CLASS_LIMITED_DISCOVER) cannot be set directly through + * this function. Use bt_br_set_discoverable() with the limited parameter to + * enable limited discoverable mode, which will automatically set the + * corresponding COD bit. + * + * @param cod Class of Device value to set. This should be a combination of + * Major Service Class bits (BT_COD_MAJOR_SVC_CLASS_*), Major + * Device Class bits (BT_COD_MAJOR_DEVICE_CLASS_*), and Minor + * Device Class bits (BT_COD_MINOR_DEVICE_CLASS_*). + * + * @retval 0 on success + * @retval -EAGAIN if the Bluetooth device is not ready + * @retval -EINVAL if the provided COD value attempts to set the limited + * discoverable bit directly, which is not permitted + * @retval Other negative error codes from underlying HCI command failures + * + * @see bt_br_set_discoverable() + * @see bt_br_get_class_of_device() + */ + +int bt_br_set_class_of_device(uint32_t cod); + +/** + * @brief Get the Class of Device of the local BR/EDR Controller. + * + * @param cod Pointer to where the current Class of Device value will be + * stored. + * + * @retval 0 on success + * @retval -EAGAIN if the Bluetooth device is not ready + * @retval -EINVAL if @p cod is NULL + * @retval Other negative error codes on failure + */ + +int bt_br_get_class_of_device(uint32_t *cod); + +/** @brief Check if a Bluetooth classic device address is bonded. + * + * @param addr Bluetooth classic device address. + * + * @return true if @p addr is bonded + */ + +bool bt_br_bond_exists(const bt_addr_t *addr); + +/** + * @brief Clear classic pairing information . + * + * @param addr Remote address, NULL or BT_ADDR_ANY to clear all remote + * devices. + * + * @return 0 on success or negative error value on failure. + */ + +int bt_br_unpair(const bt_addr_t *addr); + +/** Information about a bond with a remote device. */ + +struct bt_br_bond_info +{ + /** Address of the remote device. */ + + bt_addr_t addr; +}; + +/** + * @brief Iterate through all existing bonds of Classic. + * + * @param func Function to call for each bond. + * @param user_data Data to pass to the callback function. + */ + +void bt_br_foreach_bond( + void (*func)(const struct bt_br_bond_info *info, void *user_data), + void *user_data); + +/** @brief Write Extended Inquiry Response data. + * + * Write the extended inquiry response data to be sent during the extended + * inquiry response procedure. + * + * @param eir Array of bt_data elements to include in the EIR. + * @param eir_count Number of elements in the @p eir array. + * @param fec_required Whether FEC encoding is required (true/false). + * + * @return Zero for success, non-zero otherwise. + */ + +int bt_br_write_eir(const struct bt_data *eir, + size_t eir_count, bool fec_required); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_CLASSIC_H */ diff --git a/include/nuttx/net/bluetooth/classic/goep.h b/include/nuttx/net/bluetooth/classic/goep.h new file mode 100644 index 0000000000000..a271ae6141e8d --- /dev/null +++ b/include/nuttx/net/bluetooth/classic/goep.h @@ -0,0 +1,599 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/classic/goep.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* goep.h - Bluetooth Generic Object Exchange Profile handling */ + +/* Copyright 2024-2026 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_GOEP_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_GOEP_H + +/** + * @brief Generic Object Exchange Profile (GOEP) + * @defgroup bt_goep Generic Object Exchange Profile (GOEP) + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct bt_goep; + +/** @brief GOEP transport operations structure. + * + * The object has to stay valid and constant for the lifetime of the GOEP + * server and client. + */ + +struct bt_goep_transport_ops +{ + /** @brief GOEP transport connected callback + * + * If this callback is provided it will be called whenever the GOEP + * transport connection completes. + * + * @param conn The ACL connection. + * @param goep The GOEP object that has been connected. + */ + + void (*connected)(struct bt_conn *conn, struct bt_goep *goep); + + /** @brief GOEP transport disconnected callback + * + * If this callback is provided it will be called whenever the GOEP + * transport is disconnected, including when a connection gets rejected. + * + * @param goep The GOEP object that has been disconnected. + */ + + void (*disconnected)(struct bt_goep *goep); +}; + +/** @brief GOEP v1.1 structure. */ + +struct bt_goep_transport_v1 +{ + /** @brief RFCOMM DLC used for transport */ + + struct bt_rfcomm_dlc dlc; + + /** @brief Back-pointer to parent GOEP object + * + * This must be initialized to point to the parent @ref bt_goep structure + * before calling any GOEP transport connect/register APIs. The parent + * @ref bt_goep must have its @ref bt_goep::v1 field pointing to this + * structure and @ref bt_goep::v2 set to NULL for a valid GOEP v1.1 + * session. + */ + + struct bt_goep *goep; +}; + +/** @brief GOEP v2 structure. */ + +struct bt_goep_transport_v2 +{ + /** @brief L2CAP BR/EDR channel used for transport */ + + struct bt_l2cap_br_chan chan; + + /** @brief Back-pointer to parent GOEP object + * + * This must be initialized to point to the parent @ref bt_goep structure + * before calling any GOEP transport connect/register APIs. The parent + * @ref bt_goep must have its @ref bt_goep::v2 field pointing to this + * structure and @ref bt_goep::v1 set to NULL for a valid GOEP v2.0 + * session. + */ + + struct bt_goep *goep; +}; + +/** @brief GOEP transport unified structure. */ + +struct bt_goep_transport +{ + /** @brief GOEP v1.1 transport */ + + struct bt_goep_transport_v1 v1; + + /** @brief GOEP v2 transport */ + + struct bt_goep_transport_v2 v2; +}; + +/** @brief Life-span states of GOEP transport. + * + * Used only by internal APIs dealing with setting GOEP to proper transport + * state depending on operational context. + * + * GOEP transport enters the @ref BT_GOEP_TRANSPORT_CONNECTING state upon + * @ref bt_goep_transport_l2cap_connect, @ref + * bt_goep_transport_rfcomm_connect or upon returning from @ref + * bt_goep_transport_rfcomm_server::accept and + * bt_goep_transport_l2cap_server::accept. + * + * When GOEP transport leaves the @ref BT_GOEP_TRANSPORT_CONNECTING state + * and enters the @ref BT_GOEP_TRANSPORT_CONNECTED, @ref + * bt_goep_transport_ops::connected is called. + * + * When GOEP transport enters the @ref BT_GOEP_TRANSPORT_DISCONNECTED from + * other states, + * @ref bt_goep_transport_ops::disconnected is called. + */ + +enum __packed bt_goep_transport_state +{ + /** GOEP disconnected */ + + BT_GOEP_TRANSPORT_DISCONNECTED, + + /** GOEP in connecting state */ + + BT_GOEP_TRANSPORT_CONNECTING, + + /** GOEP ready for upper layer traffic on it */ + + BT_GOEP_TRANSPORT_CONNECTED, + + /** GOEP in disconnecting state */ + + BT_GOEP_TRANSPORT_DISCONNECTING, +}; + +/** @brief GOEP structure. + * + * @note Transport version selection and lifetime rules: + * - Exactly one of @ref bt_goep::v1 or @ref bt_goep::v2 shall be set + * (non-NULL) for an active session. + * - The pointed-to @ref bt_goep_transport_v1 / @ref + * bt_goep_transport_v2 instance must remain valid (address stable) for the + * entire lifetime of the transport connection, because the Bluetooth host + * stack uses CONTAINER_OF() on the embedded RFCOMM/L2CAP objects in + * asynchronous callbacks. + * - The version-specific object must have its @ref + * bt_goep_transport_v1::goep / + * @ref bt_goep_transport_v2::goep back-pointer initialized to the + * parent @ref bt_goep before calling any of the GOEP transport + * connect/register APIs. + */ + +struct bt_goep +{ + /** @brief To be used for transport v1. + * + * Must be NULL when using transport v2. + */ + + struct bt_goep_transport_v1 *v1; + + /** @brief To be used for transport v2. + * + * Must be NULL when using transport v1. + */ + + struct bt_goep_transport_v2 *v2; + + /** @internal connection handle */ + + struct bt_conn *_acl; + + /** @internal Saves the current transport state, @ref + * bt_goep_transport_state + */ + + atomic_t _state; + + /** @brief GOEP transport operations + * + * The upper layer should pass the operations to `transport_ops` when + * providing the GOEP structure. + */ + + const struct bt_goep_transport_ops *transport_ops; + + /** @brief OBEX object */ + + struct bt_obex obex; +}; + +/** @brief Initialize the GOEP and GOEP v1 (RFCOMM) transport structures. + * + * Sets up the mutual back-pointer between a @ref bt_goep instance and its + * transport instance + * @ref bt_goep_transport_v1, and clears the unused v2 pointer to avoid the + * goep version mismatch. + * + * Equivalent to: @code (_v1)->goep = (_goep); (_goep)->v1 = (_v1); + * (_goep)->v2 = NULL; @endcode + * + * @param _goep Pointer to the @ref bt_goep structure to initialize. + * @param _v1 Pointer to the @ref bt_goep_transport_v1 structure to + * associate. + */ +#define BT_GOEP_INIT_V1(_goep, _v1) \ +do \ + { \ + BUILD_ASSERT(SAME_TYPE(*(_goep), struct bt_goep) && \ + SAME_TYPE(*(_v1), struct bt_goep_transport_v1), \ + "pointer type mismatch in BT_GOEP_INIT_V1"); \ + (_v1)->goep = (_goep); \ + (_goep)->v1 = (_v1); \ + (_goep)->v2 = NULL; \ + } \ +while (0) + +/** @brief Initialize the GOEP and GOEP v2 (L2CAP) transport structures. + * + * Sets up the mutual back-pointer between a @ref bt_goep instance and its + * transport instance + * @ref bt_goep_transport_v2, and clears the unused v1 pointer to avoid the + * goep version mismatch. + * + * Equivalent to: @code (_v2)->goep = (_goep); (_goep)->v2 = (_v2); + * (_goep)->v1 = NULL; @endcode + * + * @param _goep Pointer to the @ref bt_goep structure to initialize. + * @param _v2 Pointer to the @ref bt_goep_transport_v2 structure to + * associate. + */ +#define BT_GOEP_INIT_V2(_goep, _v2) \ +do \ + { \ + BUILD_ASSERT(SAME_TYPE(*(_goep), struct bt_goep) && \ + SAME_TYPE(*(_v2), struct bt_goep_transport_v2), \ + "pointer type mismatch in BT_GOEP_INIT_V2"); \ + (_v2)->goep = (_goep); \ + (_goep)->v2 = (_v2); \ + (_goep)->v1 = NULL; \ + } \ +while (0) + +/** + * @defgroup bt_goep_transport_rfcomm GOEP transport RFCOMM + * @ingroup bt_goep + * @{ + */ + +/** @brief GOEP Server structure GOEP v1.1. */ + +struct bt_goep_transport_rfcomm_server +{ + /** @brief RFCOMM server for GOEP v1.1 + * + * The @ref bt_goep_transport_rfcomm_server::rfcomm is used to register a + * rfcomm server. + * + * The @ref bt_rfcomm_server::channel needs to be passed with a pre-set + * channel (not recommended however), Or give a value `0` to make the + * channel be auto-allocated when + * @ref bt_goep_transport_rfcomm_server_register is called. + * The @ref bt_rfcomm_server::accept should be not used by GOEP + * application, and instead the @ref + * bt_goep_transport_rfcomm_server::accept should be used. + */ + + struct bt_rfcomm_server rfcomm; + + /** @brief Server accept callback + * + * This callback is called whenever a new incoming GOEP connection + * requires authorization. + * + * Before returning the callback, @ref bt_goep::transport_ops should be + * initialized with valid address of type @ref bt_goep_transport_ops + * object. The value of the field `mtu` of @ref bt_obex::rx will be + * ignored. Instead, it will be assigned the value that be calculated + * based on @kconfig{CONFIG_BT_GOEP_RFCOMM_MTU}. + * + * @warning It is the responsibility of the caller to zero out the parent + * of the GOEP object. + * + * @note The accept callback must initialize the returned GOEP instance as + * follows: + * - Set @ref bt_goep::transport_ops to a valid operations table. + * - The field @ref bt_goep::v1 should be passed with the pointed-to + * @ref bt_goep_transport_v1 instance must remain valid (address + * stable) for the entire lifetime of the transport connection. And @ref + * bt_goep::v2 should be set to NULL. + * - Initialize the back-pointer in the version-specific object + * @ref bt_goep_transport_v1::goep to point at the parent @ref + * bt_goep. The version-specific object must remain valid for the lifetime + * of the transport. + * + * @param conn The connection that is requesting authorization. + * @param server Pointer to the server structure this callback relates to. + * @param goep Pointer to received the allocated GOEP object. + * + * @return 0 in case of success or negative value in case of error. + * @return -ENOMEM if no available space for new object. + * @return -EACCES if application did not authorize the connection. + * @return -EPERM if encryption key size is too short. + */ + +int (*accept)(struct bt_conn *conn, + struct bt_goep_transport_rfcomm_server *server, + struct bt_goep **goep); + + struct list_node node; +}; + +/** @brief Register GOEP RFCOMM server. + * + * Register GOEP server for a RFCOMM channel @ref bt_rfcomm_server::channel, + * each new connection is authorized using the @ref + * bt_goep_transport_rfcomm_server::accept callback which in case of success + * shall allocate the GOEP structure @ref bt_goep to be used by the new GOEP + * connection. + * + * @ref bt_rfcomm_server::channel may be pre-set to a given value (not + * recommended however). Or be left as 0, in which case the channel will be + * auto-allocated by RFCOMM. + * + * @param server Server structure. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_goep_transport_rfcomm_server_register( + struct bt_goep_transport_rfcomm_server *server); + +/** @brief Connect GOEP transport over RFCOMM + * + * Connect GOEP transport over RFCOMM, once the connection is completed, the + * callback + * @ref bt_goep_transport_ops::connected is called. If the connection is + * rejected, + * @ref bt_goep_transport_ops::disconnected callback is called instead. + * The GOEP object is passed (over an address of it) as second parameter, + * application should create transport dedicated GOEP object @ref bt_goep. + * Then pass to this API the location (address). Before calling the API, + * @ref bt_goep::transport_ops should be initialized with valid address of + * type @ref bt_goep_transport_ops object. The RFCOMM channel is passed as + * third parameter. It is the RFCOMM channel of RFCOMM server discovered + * from the peer device. + * + * The value of the field `mtu` of @ref bt_obex::rx will be ignored. + * Instead, it will be assigned the value that be calculated based on + * @kconfig{CONFIG_BT_GOEP_RFCOMM_MTU}. + * + * @warning It is the responsibility of the caller to zero out the parent of + * the GOEP object. + * + * @note The accept callback must initialize the returned GOEP instance as + * follows: + * - Set @ref bt_goep::transport_ops to a valid operations table. + * - The field @ref bt_goep::v1 should be passed with the pointed-to + * @ref bt_goep_transport_v1 instance must remain valid (address + * stable) for the entire lifetime of the transport connection. And + * @ref bt_goep::v2 should be set to NULL. + * - Initialize the back-pointer in the version-specific object + * @ref bt_goep_transport_v1::goep to point at the parent @ref + * bt_goep. The version-specific object must remain valid for the lifetime + * of the transport. + * + * @param conn Connection object. + * @param goep GOEP object. + * @param channel RFCOMM channel to connect to. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_goep_transport_rfcomm_connect(struct bt_conn *conn, + struct bt_goep *goep, uint8_t channel); + +/** @brief Disconnect GOEP transport from RFCOMM + * + * Disconnect GOEP RFCOMM transport. + * + * @param goep GOEP object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_goep_transport_rfcomm_disconnect(struct bt_goep *goep); + +/** @} */ + +/** + * @defgroup bt_goep_transport_l2cap GOEP transport L2CAP + * @ingroup bt_goep + * @{ + */ + +/** @brief GOEP Server structure for GOEP v2.0 and later. */ + +struct bt_goep_transport_l2cap_server +{ + /** @brief L2CAP PSM for GOEP v2.0 and later + * + * The @ref bt_goep_transport_l2cap_server::l2cap is used to register a + * l2cap server. + * + * The @ref bt_l2cap_server::psm needs to be passed with a pre-set psm + * (not recommended however), Or give a value `0` to make the psm be + * auto-allocated when + * @ref bt_goep_transport_l2cap_server_register is called. + * The @ref bt_l2cap_server::sec_level is used to require minimum security + * level for l2cap server. The @ref bt_l2cap_server::accept should be not + * used by GOEP application, and instead the @ref + * bt_goep_transport_l2cap_server::accept will be used. + */ + + struct bt_l2cap_server l2cap; + + /** @brief Server accept callback + * + * This callback is called whenever a new incoming GOEP connection + * requires authorization. + * + * Before returning the callback, @ref bt_goep::transport_ops should be + * initialized with valid address of type @ref bt_goep_transport_ops + * object. The value of the field `mtu` of @ref bt_obex::rx will be + * ignored. Instead, it will be assigned the value that be calculated + * based on @kconfig{CONFIG_BT_GOEP_L2CAP_MTU}. + * + * @warning It is the responsibility of the caller to zero out the parent + * of the GOEP object. + * + * @note The accept callback must initialize the returned GOEP instance as + * follows: + * - Set @ref bt_goep::transport_ops to a valid operations table. + * - The field @ref bt_goep::v2 should be passed with the pointed-to + * @ref bt_goep_transport_v2 instance must remain valid (address + * stable) for the entire lifetime of the transport connection. And @ref + * bt_goep::v1 should be set to NULL. + * - Initialize the back-pointer in the version-specific object + * @ref bt_goep_transport_v2::goep to point at the parent @ref + * bt_goep. The version-specific object must remain valid for the lifetime + * of the transport. + * + * @param conn The connection that is requesting authorization. + * @param server Pointer to the server structure this callback relates to. + * @param goep Pointer to received the allocated GOEP object. + * + * @return 0 in case of success or negative value in case of error. + * @return -ENOMEM if no available space for new object. + * @return -EACCES if application did not authorize the connection. + * @return -EPERM if encryption key size is too short. + */ + +int (*accept)(struct bt_conn *conn, + struct bt_goep_transport_l2cap_server *server, + struct bt_goep **goep); + + struct list_node node; +}; + +/** @brief Register GOEP L2CAP server. + * + * Register GOEP server for a L2CAP PSM @ref bt_l2cap_server::psm. each new + * connection is authorized using the @ref + * bt_goep_transport_l2cap_server::accept callback which in case of success + * shall allocate the GOEP structure @ref bt_goep to be used by the new GOEP + * connection. + * + * For L2CAP PSM, for fixed, SIG-assigned PSMs (in the range 0x0001-0x0eff) + * the PSM should not be used. For dynamic PSMs (in the range + * 0x1000-0xffff), + * @ref bt_l2cap_server::psm may be pre-set to a given value (not + * recommended however). And it shall have the least significant bit of the + * most significant octet equal to 0 and the least significant bit of all + * other octets equal to 1. Or be left as 0, in which case the channel will + * be auto-allocated by L2CAP. + * + * @param server Server structure. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_goep_transport_l2cap_server_register( + struct bt_goep_transport_l2cap_server *server); + +/** @brief Connect GOEP transport over L2CAP + * + * Connect GOEP transport by L2CAP, once the connection is completed, the + * callback + * @ref bt_goep_transport_ops::connected is called. If the connection is + * rejected, + * @ref bt_goep_transport_ops::disconnected callback is called instead. + * The GOEP object is passed (over an address of it) as second parameter, + * application should create transport dedicated GOEP object @ref bt_goep. + * Then pass to this API the location (address). Before calling the API, + * @ref bt_goep::transport_ops should be initialized with valid address of + * type @ref bt_goep_transport_ops object. The L2CAP PSM is passed as third + * parameter. It is the L2CAP PSM of OBEX SDP record discovered from the + * peer device. + * + * The value of the field `mtu` of @ref bt_obex::rx will be ignored. + * Instead, it will be assigned the value that be calculated based on + * @kconfig{CONFIG_BT_GOEP_L2CAP_MTU}. + * + * @warning It is the responsibility of the caller to zero out the parent of + * the GOEP object. + * + * @note The accept callback must initialize the returned GOEP instance as + * follows: + * - Set @ref bt_goep::transport_ops to a valid operations table. + * - The field @ref bt_goep::v2 should be passed with the pointed-to + * @ref bt_goep_transport_v2 instance must remain valid (address + * stable) for the entire lifetime of the transport connection. And + * @ref bt_goep::v1 should be set to NULL. + * - Initialize the back-pointer in the version-specific object + * @ref bt_goep_transport_v2::goep to point at the parent @ref + * bt_goep. The version-specific object must remain valid for the lifetime + * of the transport. + * + * @param conn Connection object. + * @param goep GOEP object. + * @param psm L2CAP PSM to connect to. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_goep_transport_l2cap_connect(struct bt_conn *conn, + struct bt_goep *goep, uint16_t psm); + +/** @brief Disconnect GOEP transport from L2CAP channel + * + * Disconnect GOEP L2CAP transport. + * + * @param goep GOEP object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_goep_transport_l2cap_disconnect(struct bt_goep *goep); + +/** @} */ + +/** @brief Allocate the buffer from given pool after reserving head room for + * GOEP + * + * For GOEP connection over RFCOMM, the reserved head room includes OBEX, + * RFCOMM, L2CAP and ACL headers. For GOEP connection over L2CAP, the + * reserved head room includes OBEX, L2CAP and ACL headers. + * + * @param goep GOEP object. + * @param pool Which pool to take the buffer from. + * + * @return New buffer. + */ + +struct net_buf *bt_goep_create_pdu(struct bt_goep *goep, + struct net_buf_pool *pool); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_GOEP_H */ diff --git a/include/nuttx/net/bluetooth/classic/hfp_ag.h b/include/nuttx/net/bluetooth/classic/hfp_ag.h new file mode 100644 index 0000000000000..d895bae84be09 --- /dev/null +++ b/include/nuttx/net/bluetooth/classic/hfp_ag.h @@ -0,0 +1,1010 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/classic/hfp_ag.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Handsfree Profile Audio Gateway handling. + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * Copyright 2023-2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_HFP_AG_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_HFP_AG_H + +/** + * @brief Hands Free Profile - Audio Gateway (HFP-AG) + * @defgroup bt_hfp_ag Hands Free Profile - Audio Gateway (HFP-AG) + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* HFP AG Indicators */ + +enum bt_hfp_ag_indicator +{ + BT_HFP_AG_SERVICE_IND = 0, /* Service availability indicator */ + BT_HFP_AG_CALL_IND = 1, /* call status indicator */ + BT_HFP_AG_CALL_SETUP_IND = 2, /* Call set up status indicator */ + BT_HFP_AG_CALL_HELD_IND = 3, /* Call hold status indicator */ + BT_HFP_AG_SIGNAL_IND = 4, /* Signal strength indicator */ + BT_HFP_AG_ROAM_IND = 5, /* Roaming status indicator */ + BT_HFP_AG_BATTERY_IND = 6, /* Battery change indicator */ + BT_HFP_AG_IND_MAX /* Indicator MAX value */ +}; + +/* HFP CODEC */ +#define BT_HFP_AG_CODEC_CVSD 0x01 +#define BT_HFP_AG_CODEC_MSBC 0x02 +#define BT_HFP_AG_CODEC_LC3_SWB 0x03 + +/** + * @struct bt_hfp_ag + * @brief HFP AG structure + */ + +struct bt_hfp_ag; + +/** + * @struct bt_hfp_ag_call + * @brief HFP AG call structure + */ + +struct bt_hfp_ag_call; + +/** @typedef bt_hfp_ag_query_subscriber_func_t + * @brief Query subscriber number callback function + * + * When AG wants to send subscriber number information, all information will + * be passed through the callback. And the subscriber number information + * will be sent out in this function. + * + * @param ag HFP AG object. + * @param number Subscriber number. + * @param type Type of subscriber number specifies the format of the + * phone number provided, and can be one of the following + * values: - values 128-143: The phone number format may be a + * national or international format, and may contain prefix + * and/or escape digits. No changes on the number + * presentation are required. - values 144-159: The phone + * number format is an international number, including the + * country code prefix. If the plus sign ("+") is not + * included as part of the number and shall be added by the + * AG as needed. - values 160-175: National number. No prefix + * nor escape digits included. + * @param service Service of subscriber number indicates which service this + * phone number relates to. Shall be either 4 (voice) or 5 + * (fax). + * + * @return 0 if should continue to the next subscriber number information. + * @return negative value to stop. + */ + +typedef int (*bt_hfp_ag_query_subscriber_func_t)( + struct bt_hfp_ag *ag, char *number, uint8_t type, uint8_t service); + +/* HF indicators */ + +enum hfp_ag_hf_indicators +{ + HFP_AG_ENHANCED_SAFETY_IND = 1, /* Enhanced Safety */ + HFP_AG_BATTERY_LEVEL_IND = 2, /* Remaining level of Battery */ +}; + +/* The status of the call */ + +enum __packed bt_hfp_ag_call_status +{ + BT_HFP_AG_CALL_STATUS_ACTIVE = 0, /* Call is active */ + BT_HFP_AG_CALL_STATUS_HELD = 1, /* Call is on hold */ + BT_HFP_AG_CALL_STATUS_DIALING = 2, /* Outgoing call is being dialed */ + BT_HFP_AG_CALL_STATUS_ALERTING = 3, /* Outgoing call is being alerted */ + BT_HFP_AG_CALL_STATUS_INCOMING = 4, /* Incoming call is came */ + BT_HFP_AG_CALL_STATUS_WAITING = 5, /* Incoming call is waiting */ + BT_HFP_AG_CALL_STATUS_INCOMING_HELD = 6 /* Call held by Response and Hold */ +}; + +/* The direction of the call */ + +enum __packed bt_hfp_ag_call_dir +{ + BT_HFP_AG_CALL_DIR_OUTGOING = 0, /* It is a outgoing call */ + BT_HFP_AG_CALL_DIR_INCOMING = 1, /* It is a incoming call */ +}; + +/** @brief The ongoing call + * + * @param number Phone number terminated with '\0' of the call. + * @param type Specify the format of the phone number. + * @param dir Call direction. + * @param status The status of the call. + */ + +struct bt_hfp_ag_ongoing_call +{ + char number[CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN + 1]; + uint8_t type; + enum bt_hfp_ag_call_dir dir; + enum bt_hfp_ag_call_status status; +}; + +/** @brief HFP profile AG application callback */ + +struct bt_hfp_ag_cb +{ + /** HF AG connected callback to application + * + * If this callback is provided it will be called whenever the AG + * connection completes. + * + * @param conn Connection object. + * @param ag HFP AG object. + */ + + void (*connected)(struct bt_conn *conn, struct bt_hfp_ag *ag); + /** HF disconnected callback to application + * + * If this callback is provided it will be called whenever the connection + * gets disconnected, including when a connection gets rejected or + * cancelled or any error in SLC establishment. + * + * @param ag HFP AG object. + */ + + void (*disconnected)(struct bt_hfp_ag *ag); + /** HF SCO/eSCO connected Callback + * + * If this callback is provided it will be called whenever the SCO/eSCO + * connection completes. + * + * @param ag HFP AG object. + * @param sco_conn SCO/eSCO Connection object. + */ + + void (*sco_connected)(struct bt_hfp_ag *ag, struct bt_conn *sco_conn); + /** HF SCO/eSCO disconnected Callback + * + * If this callback is provided it will be called whenever the SCO/eSCO + * connection gets disconnected. + * + * @param conn SCO/eSCO Connection object. + * @param reason BT_HCI_ERR_* reason for the disconnection. + */ + + void (*sco_disconnected)(struct bt_conn *sco_conn, uint8_t reason); + + /** Get indicator values callback + * + * If this callback is provided it will be called whenever the AG needs to + * provide current indicator values to the HF. This typically occurs when + * the HF sends `AT+CIND?` command to query the current status of AG + * indicators. + * + * The application should populate the indicator values through the + * provided pointers. All indicator values should be set according to the + * current status of the AG. + * + * @param ag HFP AG object. + * @param service Pointer to store service availability indicator value. + * 0 = service is not available, 1 = service is available. + * @param strength Pointer to store signal strength indicator value. + * Range: 0-5 (0 = no signal, 5 = maximum signal). + * @param roam Pointer to store roaming status indicator value. 0 = + * not roaming, 1 = roaming. + * @param battery Pointer to store battery level indicator value. Range: + * 0-5 (0 = battery exhausted, 5 = battery full). + * + * @note The AG is in SLC establishment phase. The AG callback + * `connected()` is not notified at this time. + * + * @note If the callback is not provided by the application or the + * returned error is no zero, the value of these all indicators will be + * set to 0 by default. And the specific can be set and notified by + * calling the dedicated function. Such as `service availability indicator + * value` can be set by calling the function + * `bt_hfp_ag_service_availability()`. The `signal strength value` can be + * set by calling `bt_hfp_ag_signal_strength()`, and so on. + * + * @return 0 in case of success or negative value in case of error. + */ + +int (*get_indicator_value)(struct bt_hfp_ag *ag, + uint8_t *service, + uint8_t *strength, + uint8_t *roam, + uint8_t *battery); + + /** Get ongoing call information Callback + * + * If this callback is provided it will be called whenever the AT command + * `AT+CIND?` is received from HF has been sent. After the callback + * notified, the ongoing calls should be set via function + * `bt_hfp_ag_ongoing_calls()` within the timeout + * @kconfig{CONFIG_BT_HFP_AG_GET_ONGOING_CALL_TIMEOUT}. + * + * @param ag HFP AG object. + * + * @note The AG is in SLC establishment phase. The AG callback + * `connected()` is not notified at this time. + * + * @return 0 in case of success. The response `+CIND` will be sent after + * the function `bt_hfp_ag_ongoing_calls()` called or after the time + * exceeds @kconfig{CONFIG_BT_HFP_AG_GET_ONGOING_CALL_TIMEOUT}. Or + * negative value in case of error. The response `+CIND` will be replied + * immediately. + */ + + int (*get_ongoing_call)(struct bt_hfp_ag *ag); + + /** HF memory dialing request Callback + * + * If this callback is provided it will be called whenever a new call is + * requested with memory dialing from HF. Get the phone number according + * to the given AG memory location. + * + * @param ag HFP AG object. + * @param location AG memory location + * @param number Dialing number + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*memory_dial)(struct bt_hfp_ag *ag, const char *location, + char **number); + + /** HF phone number calling request Callback + * + * If this callback is provided it will be called whenever a new call is + * requested with specific phone number from HF. When the callback is + * triggered, the application needs to start dialing the number with the + * passed phone number. If the callback is invalid, the phone number + * dialing from HF cannot be supported. + * + * @param ag HFP AG object. + * @param number Dialing number + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*number_call)(struct bt_hfp_ag *ag, const char *number); + + /** HF last number redial request Callback + * + * If this callback is provided it will be called whenever a last number + * redial request is received from HF via `AT+BLDN` command. When the + * callback is triggered, the application needs to provide the last dialed + * phone number. If the callback is invalid, the last number redial from + * HF cannot be supported. + * + * The application should: 1. Retrieve the last dialed phone number from + * its call history 2. Copy the phone number to the provided buffer + * + * @param ag HFP AG object. + * @param number Buffer to store the last dialed phone number. The buffer + * size is @kconfig{CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN} + + * 1, and should be null-terminated. + * + * @return 0 in case of success or negative value in case of error. + * If successful, the AG will proceed with the call setup procedure. If + * error, an ERROR response will be sent to HF. + */ + + int (*redial)(struct bt_hfp_ag *ag, + char number[CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN + 1]); + + /** HF outgoing Callback + * + * If this callback is provided it will be called whenever a new call is + * outgoing. + * + * @param ag HFP AG object. + * @param call HFP AG call object. + * @param number Dialing number + */ + + void (*outgoing)(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call, + const char *number); + + /** HF incoming Callback + * + * If this callback is provided it will be called whenever a new call is + * incoming. + * + * @param ag HFP AG object. + * @param call HFP AG call object. + * @param number Incoming number + */ + + void (*incoming)(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call, + const char *number); + + /** HF incoming call is held Callback + * + * If this callback is provided it will be called whenever the incoming + * call is held but not accepted. + * + * @param call HFP AG call object. + */ + + void (*incoming_held)(struct bt_hfp_ag_call *call); + + /** HF ringing Callback + * + * If this callback is provided it will be called whenever the call is in + * the ringing + * + * @param call HFP AG call object. + * @param in_band true - in-band ringing, false - No in-band ringing + */ + + void (*ringing)(struct bt_hfp_ag_call *call, bool in_band); + + /** HF call accept Callback + * + * If this callback is provided it will be called whenever the call is + * accepted. + * + * @param call HFP AG call object. + */ + + void (*accept)(struct bt_hfp_ag_call *call); + + /** HF call held Callback + * + * If this callback is provided it will be called whenever the call is + * held. + * + * @param call HFP AG call object. + */ + + void (*held)(struct bt_hfp_ag_call *call); + + /** HF call retrieve Callback + * + * If this callback is provided it will be called whenever the call is + * retrieved. + * + * @param call HFP AG call object. + */ + + void (*retrieve)(struct bt_hfp_ag_call *call); + + /** HF call reject Callback + * + * If this callback is provided it will be called whenever the call is + * rejected. + * + * @param call HFP AG call object. + */ + + void (*reject)(struct bt_hfp_ag_call *call); + + /** HF call terminate Callback + * + * If this callback is provided it will be called whenever the call is + * terminated. + * + * @param call HFP AG call object. + */ + + void (*terminate)(struct bt_hfp_ag_call *call); + + /** Supported codec Ids callback + * + * If this callback is provided it will be called whenever the supported + * codec ids are updated. + * + * @param ag HFP AG object. + * @param ids Bitmap of supported codec IDs. + */ + + void (*codec)(struct bt_hfp_ag *ag, uint32_t ids); + + /** Codec negotiate callback + * + * If this callback is provided it will be called whenever the codec + * negotiation succeeded or failed. + * + * @param ag HFP AG object. + * @param err Result of codec negotiation. + */ + + void (*codec_negotiate)(struct bt_hfp_ag *ag, int err); + + /** Audio connection request callback + * + * If this callback is provided it will be called whenever the audio + * connection request is triggered by HF. When AT+BCC AT command received, + * it means the procedure of establishment of audio connection is + * triggered by HF. If the callback is provided by application, AG needs + * to start the codec connection procedure by calling function + * `bt_hfp_ag_audio_connect` in application layer. Or, the codec + * connection procedure will be started with default codec id + * `BT_HFP_AG_CODEC_CVSD`. + * + * @param ag HFP AG object. + */ + + void (*audio_connect_req)(struct bt_hfp_ag *ag); + + /** HF VGM setting callback + * + * If this callback is provided it will be called whenever the VGM gain + * setting is informed from HF. + * + * @param ag HFP AG object. + * @param gain HF microphone gain value. + */ + + void (*vgm)(struct bt_hfp_ag *ag, uint8_t gain); + + /** HF VGS setting callback + * + * If this callback is provided it will be called whenever the VGS gain + * setting is informed from HF. + * + * @param ag HFP AG object. + * @param gain HF speaker gain value. + */ + + void (*vgs)(struct bt_hfp_ag *ag, uint8_t gain); + + /** HF ECNR turns off callback + * + * If this callback is provided it will be called whenever the ECNR + * turning off request is received from HF. If the callback is NULL or + * @kconfig{CONFIG_BT_HFP_AG_ECNR} is not enabled, the response result + * code of AT command will be an AT ERROR. + * + * @param ag HFP AG object. + */ + + void (*ecnr_turn_off)(struct bt_hfp_ag *ag); + + /** HF explicit call transfer callback + * + * If this callback is provided it will be called whenever the AT+CHLD=4 + * is sent from HF. When the callback is notified, the application should + * connect the two calls and disconnects the subscriber from both calls + * (Explicit Call Transfer). After the callback returned, the call objects + * will be invalid. If the callback is NULL, the response result code of + * AT command will be an AT ERROR. If @kconfig{CONFIG_BT_HFP_AG_3WAY_CALL} + * is not enabled, the callback will not be notified. + * + * @param ag HFP AG object. + */ + + void (*explicit_call_transfer)(struct bt_hfp_ag *ag); + + /** Voice recognition activation/deactivation callback + * + * If this callback is provided it will be called whenever the voice + * recognition activation is changed. If voice recognition is activated, + * the upper layer should call `bt_hfp_ag_audio_connect` with appropriate + * codec ID to setup audio connection. If the callback is not provided by + * upper layer, the function `bt_hfp_ag_audio_connect` will be called with + * default codec ID `BT_HFP_AG_CODEC_CVSD`. If + * @kconfig{CONFIG_BT_HFP_AG_VOICE_RECG} is not enabled, the callback will + * not be notified. + * + * @param ag HFP AG object. + * @param activate Voice recognition activation/deactivation. + */ + + void (*voice_recognition)(struct bt_hfp_ag *ag, bool activate); + + /** Ready to accept audio callback + * + * If this callback is provided it will be called whenever the HF is ready + * to accept audio. If the feature `Enhanced Voice Recognition Status` is + * supported by HF, the callback will be notified if the AT command + * `AT+BVRA=2` is received. The HF may send this value during an ongoing + * VR (Voice Recognition) session to terminate audio output from the AG + * (if there is any) and prepare the AG for new audio input. Or, the + * callback will be notified after the voice recognition is activated. If + * @kconfig{CONFIG_BT_HFP_AG_ENH_VOICE_RECG} is not enabled, the callback + * will not be notified. + * + * @param ag HFP AG object. + */ + + void (*ready_to_accept_audio)(struct bt_hfp_ag *ag); + + /** Request phone number callback + * + * If this callback is provided it will be called whenever the AT command + * `AT+BINP=1` is received. If the upper layer accepts the request, it + * shall obtain a phone number. If the upper layer rejects the request, it + * shall return a an error. If @kconfig{CONFIG_BT_HFP_AG_VOICE_TAG} is not + * enabled, the callback will not be notified. + * + * @param ag HFP AG object. + * @param number Phone number of voice tag. + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*request_phone_number)(struct bt_hfp_ag *ag, char **number); + + /** Transmit a DTMF Code callback + * + * If this callback is provided it will be called whenever the AT command + * `AT+VTS=` is received. During an ongoing call, the HF transmits + * the AT+VTS command to instruct the AG to transmit a specific DTMF code + * to its network connection. + * + * @param ag HFP AG object. + * @param code A specific DTMF code. + */ + + void (*transmit_dtmf_code)(struct bt_hfp_ag *ag, char code); + + /** Get subscriber number callback + * + * If this callback is provided it will be called whenever the AT command + * `AT+CNUM` is received. + * + * @param ag HFP AG object. + * @param func Query subscriber number callback. + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*subscriber_number)(struct bt_hfp_ag *ag, + bt_hfp_ag_query_subscriber_func_t func); + + /** HF indicator value callback + * + * If this callback is provided it will be called whenever the AT command + * `AT+BIEV` is received. If @kconfig{CONFIG_BT_HFP_AG_HF_INDICATORS} is + * not enabled, the callback will not be notified. + * + * @param ag HFP AG object. + * @param indicator HF indicator + * @param value The value of specific indicator + */ + +void (*hf_indicator_value)(struct bt_hfp_ag *ag, + enum hfp_ag_hf_indicators indicator, + uint32_t value); +}; + +/** @brief Register HFP AG profile + * + * Register Handsfree profile AG callbacks to monitor the state and get the + * required HFP details to display. + * + * @param cb callback structure. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_register(struct bt_hfp_ag_cb *cb); + +/** @brief Create the hfp ag session + * + * Create the hfp ag session + * + * @param conn ACL connection object. + * @param ag Created HFP AG object. + * @param channel Peer rfcomm channel to be connected. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_connect(struct bt_conn *conn, + struct bt_hfp_ag **ag, uint8_t channel); + +/** @brief Disconnect the hfp ag session + * + * Disconnect the hfp ag session + * + * @param ag HFP AG object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_disconnect(struct bt_hfp_ag *ag); + +/** @brief Notify HFP Unit of an incoming call + * + * Notify HFP Unit of an incoming call. + * + * @param ag HFP AG object. + * @param number Dialing number. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_remote_incoming(struct bt_hfp_ag *ag, const char *number); + +/** @brief Put the incoming call on hold + * + * Put the incoming call on hold. + * + * @param call HFP AG call object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_hold_incoming(struct bt_hfp_ag_call *call); + +/** @brief Reject the incoming call + * + * Reject the incoming call. + * + * @param call HFP AG call object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_reject(struct bt_hfp_ag_call *call); + +/** @brief Accept the incoming call + * + * Accept the incoming call. + * + * @param call HFP AG call object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_accept(struct bt_hfp_ag_call *call); + +/** @brief Terminate the active/hold call + * + * Terminate the active/hold call. + * + * @param call HFP AG call object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_terminate(struct bt_hfp_ag_call *call); + +/** @brief Retrieve the held call + * + * Retrieve the held call. + * + * @param call HFP AG call object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_retrieve(struct bt_hfp_ag_call *call); + +/** @brief Hold the active call + * + * Hold the active call. + * + * @param call HFP AG call object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_hold(struct bt_hfp_ag_call *call); + +/** @brief Dial a call + * + * Dial a call. + * + * @param ag HFP AG object. + * @param number Dialing number. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_outgoing(struct bt_hfp_ag *ag, const char *number); + +/** @brief Notify HFP Unit that the remote starts ringing + * + * Notify HFP Unit that the remote starts ringing. + * + * @param call HFP AG call object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_remote_ringing(struct bt_hfp_ag_call *call); + +/** @brief Notify HFP Unit that the remote rejects the call + * + * Notify HFP Unit that the remote rejects the call. + * + * @param call HFP AG call object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_remote_reject(struct bt_hfp_ag_call *call); + +/** @brief Notify HFP Unit that the remote accepts the call + * + * Notify HFP Unit that the remote accepts the call. + * + * @param call HFP AG call object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_remote_accept(struct bt_hfp_ag_call *call); + +/** @brief Notify HFP Unit that the remote terminates the active/hold call + * + * Notify HFP Unit that the remote terminates the active/hold call. + * + * @param call HFP AG call object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_remote_terminate(struct bt_hfp_ag_call *call); + +/** @brief explicit call transfer + * + * Connects the two calls and disconnects the subscriber from both calls + * (Explicit Call Transfer). If @kconfig{CONFIG_BT_HFP_AG_3WAY_CALL} is not + * enabled, the error `-ENOTSUP` will be returned if the function called. + * + * @param ag HFP AG object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_explicit_call_transfer(struct bt_hfp_ag *ag); + +/** @brief Set the HF microphone gain + * + * Set the HF microphone gain + * + * @param ag HFP AG object. + * @param vgm Microphone gain value. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_vgm(struct bt_hfp_ag *ag, uint8_t vgm); + +/** @brief Set the HF speaker gain + * + * Set the HF speaker gain + * + * @param ag HFP AG object. + * @param vgs Speaker gain value. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_vgs(struct bt_hfp_ag *ag, uint8_t vgs); + +/** @brief Set currently network operator + * + * Set currently network operator. + * + * @param ag HFP AG object. + * @param mode Current mode and provides no information with regard to the + * name of the operator. + * @param name A string in alphanumeric format representing the name of the + * network operator. This string shall not exceed 16 characters. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_set_operator(struct bt_hfp_ag *ag, uint8_t mode, char *name); + +/** @brief Create audio connection + * + * Create audio connection by HFP AG. There are two setups included, Codec + * connection and audio connection. The codec connection will be established + * firstly if the codec negotiation are supported by both side. If the + * passed codec id is not same as the last codec connection, the codec + * connection procedure will be triggered. After the codec connection is + * established, the audio connection will be started. The passed codec id + * could be one of BT_HFP_AG_CODEC_XXX. If the codec negotiation feature is + * supported by both side, the codec id could be one of the bitmaps of `ids` + * notified by callback `codec`. Or, the `id` should be + * BT_HFP_AG_CODEC_CVSD. + * + * @param ag HFP AG object. + * @param id Codec Id. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_audio_connect(struct bt_hfp_ag *ag, uint8_t id); + +/** @brief Set In-Band Ring Tone + * + * Set In-Band Ring Tone. + * + * @param ag HFP AG object. + * @param inband In-band or no in-band. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_inband_ringtone(struct bt_hfp_ag *ag, bool inband); + +/** @brief Enable/disable the voice recognition function + * + * Enables/disables the voice recognition function. If + * @kconfig{CONFIG_BT_HFP_AG_VOICE_RECG} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param ag HFP AG object. + * @param activate Activate/deactivate the voice recognition function. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_voice_recognition(struct bt_hfp_ag *ag, bool activate); + +/** @brief set voice recognition engine state + * + * It is used to set the voice recognition engine state. The unsolicited + * result code `+BVRA: 1,` will be sent. ``: Bitmask + * that reflects the current state of the voice recognition engine on the + * AG. Bit 0 - If it is 1, the AG is ready to accept audio input Bit 1 - If + * it is 1, the AG is sending audio to the HF Bit 2 - If it is 1, the AG is + * processing the audio input If @kconfig{CONFIG_BT_HFP_AG_ENH_VOICE_RECG} + * is not enabled, the error `-ENOTSUP` will be returned if the function + * called. + * + * @param ag HFP AG object. + * @param state The value of ``. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_vre_state(struct bt_hfp_ag *ag, uint8_t state); + +/** @brief set voice recognition engine state and textual representation + * + * It is used to set the voice recognition engine state with textual + * representation. unsolicited result code `+BVRA: 1,, + * ` will be sent. `` is same as parameter + * `state` of function `bt_hfp_ag_vre_state`. `: + * ,,, `. ``: Unique ID of + * the current text as a hexadecimal string (a maximum of 4 characters in + * length, but less than 4 characters in length is valid). ``: ID + * of the textType from the following list: 0 - Text recognized by the AG + * from the audio input provided by the HF 1 - Text of the audio output from + * the AG 2 - Text of the audio output from the AG that contains a question + * 3 - Text of the audio output from the AG that contains an error + * description ``: ID of the operation of the text 1 - + * NewText: Indicates that a new text started. Shall be used when the + * `` changes 2 - Replace: Replace any existing text with the same + * `` and same `` 3 - Append: Attach new text to existing + * text and keep the same `` and same `` ``: The + * `` parameter shall be a UTF-8 text string and shall always be + * contained within double quotes. If + * @kconfig{CONFIG_BT_HFP_AG_VOICE_RECG_TEXT} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param ag HFP AG object. + * @param state The value of ``. + * @param id Value of ``. + * @param type Value of ``. + * @param operation Value of ``. + * @param text Value of ``. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_vre_textual_representation( + struct bt_hfp_ag *ag, uint8_t state, const char *id, uint8_t type, + uint8_t operation, const char *text); + +/** @brief Set signal strength + * + * Set signal strength. + * + * @param ag HFP AG object. + * @param strength Signal strength. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_signal_strength(struct bt_hfp_ag *ag, uint8_t strength); + +/** @brief Set roaming status + * + * Set roaming status. + * + * @param ag HFP AG object. + * @param status Roaming status. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_roaming_status(struct bt_hfp_ag *ag, uint8_t status); + +/** @brief Set battery level + * + * Set battery level. + * + * @param ag HFP AG object. + * @param level battery level. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_battery_level(struct bt_hfp_ag *ag, uint8_t level); + +/** @brief Set service availability + * + * Set service availability. + * + * @param ag HFP AG object. + * @param available service availability + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_service_availability(struct bt_hfp_ag *ag, bool available); + +/** @brief Activate/deactivate HF indicator + * + * It allows HF to issue the +BIND unsolicited result code to + * activate/deactivate of the AG’s supported HF Indicators. The indicator + * of + * supported indicators can be activated/deactivated are defined in `enum + * hfp_ag_hf_indicators`. `BT_HFP_AG_HF_INDICATOR_ENH_SAFETY` is used to + * support `Enhanced Safety`. Only the configuration has been enabled, the + * `indicator` can be HFP_AG_ENHANCED_SAFETY_IND. + * `BT_HFP_AG_HF_INDICATOR_BATTERY` is used to support `Remaining level of + * Battery`. Only the configuration has been enabled, the `indicator` can be + * HFP_AG_BATTERY_LEVEL_IND. If @kconfig{CONFIG_BT_HFP_HF_HF_INDICATORS} is + * not enabled, the error `-ENOTSUP` will be returned if the function + * called. + * + * @param ag HFP AG object. + * @param indicator The indicator of the AG’s supported HF Indicators. + * @param enable enable/disable specific HF Indicator. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_hf_indicator( + struct bt_hfp_ag *ag, enum hfp_ag_hf_indicators indicator, bool enable); + +/** @brief Set the ongoing calls + * + * It is used to set the ongoing calls when AT command `AT+CIND?` is + * received. + * + * @param ag HFP AG object. + * @param calls Ongoing calls. + * @param count Ongoing call count. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_ag_ongoing_calls( + struct bt_hfp_ag *ag, struct bt_hfp_ag_ongoing_call *calls, size_t count); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_HFP_AG_H */ diff --git a/include/nuttx/net/bluetooth/classic/hfp_hf.h b/include/nuttx/net/bluetooth/classic/hfp_hf.h new file mode 100644 index 0000000000000..fae3373c46cef --- /dev/null +++ b/include/nuttx/net/bluetooth/classic/hfp_hf.h @@ -0,0 +1,1159 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/classic/hfp_hf.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Handsfree Profile handling. + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_HFP_HF_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_HFP_HF_H + +/** + * @brief Hands Free Profile (HFP) + * @defgroup bt_hfp Hands Free Profile (HFP) + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* HFP CODEC IDs */ +#define BT_HFP_HF_CODEC_CVSD 0x01 +#define BT_HFP_HF_CODEC_MSBC 0x02 +#define BT_HFP_HF_CODEC_LC3_SWB 0x03 + +/** + * @struct bt_hfp_hf + * @brief HFP HF structure + */ + +struct bt_hfp_hf; + +/** + * @struct bt_hfp_hf_call + * @brief HFP HF call structure + */ + +struct bt_hfp_hf_call; + +/** @brief The status of the call + * + * Enumeration defining the various states a call can be in during HFP + * communication between HF and AG. + */ + +enum __packed bt_hfp_hf_call_status +{ + /** Call is active and ongoing */ + + BT_HFP_HF_CALL_STATUS_ACTIVE = 0, + + /** Call is on hold */ + + BT_HFP_HF_CALL_STATUS_HELD = 1, + + /** Outgoing call is being dialed */ + + BT_HFP_HF_CALL_STATUS_DIALING = 2, + + /** Outgoing call is being alerted (ringing on remote end) */ + + BT_HFP_HF_CALL_STATUS_ALERTING = 3, + + /** Incoming call has arrived */ + + BT_HFP_HF_CALL_STATUS_INCOMING = 4, + + /** Incoming call is waiting (call waiting scenario) */ + + BT_HFP_HF_CALL_STATUS_WAITING = 5, + + /** Call held by Response and Hold feature */ + + BT_HFP_HF_CALL_STATUS_INCOMING_HELD = 6 +}; + +/** @brief The direction of the call + * + * Enumeration defining whether the call was initiated by the HF (outgoing) + * or by the remote party (incoming). + */ + +enum __packed bt_hfp_hf_call_dir +{ + /** It is an outgoing call initiated by HF */ + + BT_HFP_HF_CALL_DIR_OUTGOING = 0, + + /** It is an incoming call from remote party */ + + BT_HFP_HF_CALL_DIR_INCOMING = 1, +}; + +/** @brief The mode of the call + * + * Enumeration defining the type of call being established, whether voice, + * data, or fax transmission. + */ + +enum __packed bt_hfp_hf_call_mode +{ + /** Voice call */ + + BT_HFP_HF_CALL_MODE_VOICE = 0, + + /** Data call */ + + BT_HFP_HF_CALL_MODE_DATA = 1, + + /** Fax transmission */ + + BT_HFP_HF_CALL_MODE_FAX = 2, +}; + +/** @brief The information of current call + * + * Structure containing comprehensive information about a current call, + * including its index, direction, status, mode, multiparty status, phone + * number, and number type. + */ + +struct bt_hfp_hf_current_call +{ + /** Call index identifier */ + + uint8_t index; + + /** Call direction (incoming/outgoing) */ + + enum bt_hfp_hf_call_dir dir; + + /** Current status of the call */ + + enum bt_hfp_hf_call_status status; + + /** Call mode (voice/data/fax) */ + + enum bt_hfp_hf_call_mode mode; + + /** True if call is part of a multiparty conference */ + + bool multiparty; + + /** Phone number string, NULL if not available */ + + const char *number; + + /** Phone number type format identifier */ + + uint8_t type; +}; + +/** @brief HFP profile application callback */ + +struct bt_hfp_hf_cb +{ + /** HF connected callback to application + * + * If this callback is provided it will be called whenever the connection + * completes. + * + * @param conn Connection object. + * @param hf HFP HF object. + */ + + void (*connected)(struct bt_conn *conn, struct bt_hfp_hf *hf); + /** HF disconnected callback to application + * + * If this callback is provided it will be called whenever the connection + * gets disconnected, including when a connection gets rejected or + * cancelled or any error in SLC establishment. And the HFP HF object will + * be freed after the registered callback `disconnected` returned. + * + * @param hf HFP HF object. + */ + + void (*disconnected)(struct bt_hfp_hf *hf); + /** HF SCO/eSCO connected Callback + * + * If this callback is provided it will be called whenever the SCO/eSCO + * connection completes. + * + * @param hf HFP HF object. + * @param sco_conn SCO/eSCO Connection object. + */ + + void (*sco_connected)(struct bt_hfp_hf *hf, struct bt_conn *sco_conn); + /** HF SCO/eSCO disconnected Callback + * + * If this callback is provided it will be called whenever the SCO/eSCO + * connection gets disconnected. + * + * @param conn SCO/eSCO Connection object. + * @param reason BT_HCI_ERR_* reason for the disconnection. + */ + + void (*sco_disconnected)(struct bt_conn *sco_conn, uint8_t reason); + /** HF indicator Callback + * + * This callback provides service indicator value to the application + * + * @param hf HFP HF object. + * @param value service indicator value received from the AG. + */ + + void (*service)(struct bt_hfp_hf *hf, uint32_t value); + /** HF call outgoing Callback + * + * This callback provides the outgoing call status to the application. + * + * @param hf HFP HF object. + * @param call HFP HF call object. + */ + + void (*outgoing)(struct bt_hfp_hf *hf, struct bt_hfp_hf_call *call); + /** HF call outgoing call is ringing Callback + * + * This callback provides the outgoing call is ringing status to the + * application. + * + * @param call HFP HF call object. + */ + + void (*remote_ringing)(struct bt_hfp_hf_call *call); + /** HF call incoming Callback + * + * This callback provides the incoming call status to the application. + * + * @param hf HFP HF object. + * @param call HFP HF call object. + */ + + void (*incoming)(struct bt_hfp_hf *hf, struct bt_hfp_hf_call *call); + /** HF incoming call on hold Callback + * + * This callback provides the incoming call on hold status to the + * application. + * + * @param call HFP HF call object. + */ + + void (*incoming_held)(struct bt_hfp_hf_call *call); + /** HF call accept Callback + * + * This callback provides the incoming/outgoing call active status to the + * application. + * + * @param call HFP HF call object. + */ + + void (*accept)(struct bt_hfp_hf_call *call); + /** HF call reject Callback + * + * This callback provides the incoming/outgoing call reject status to the + * application. + * + * @param call HFP HF call object. + */ + + void (*reject)(struct bt_hfp_hf_call *call); + /** HF call terminate Callback + * + * This callback provides the incoming/outgoing call terminate status to + * the application. + * + * @param call HFP HF call object. + */ + + void (*terminate)(struct bt_hfp_hf_call *call); + /** HF call held Callback + * + * This callback provides call held to the application + * + * @param call HFP HF call object. + */ + + void (*held)(struct bt_hfp_hf_call *call); + /** HF call retrieve Callback + * + * This callback provides call retrieved to the application + * + * @param call HFP HF call object. + */ + + void (*retrieve)(struct bt_hfp_hf_call *call); + /** HF indicator Callback + * + * This callback provides signal indicator value to the application + * + * @param hf HFP HF object. + * @param value signal indicator value received from the AG. + */ + + void (*signal)(struct bt_hfp_hf *hf, uint32_t value); + /** HF indicator Callback + * + * This callback provides roaming indicator value to the application + * + * @param hf HFP HF object. + * @param value roaming indicator value received from the AG. + */ + + void (*roam)(struct bt_hfp_hf *hf, uint32_t value); + /** HF indicator Callback + * + * This callback battery service indicator value to the application + * + * @param hf HFP HF object. + * @param value battery indicator value received from the AG. + */ + + void (*battery)(struct bt_hfp_hf *hf, uint32_t value); + /** HF incoming call Ring indication callback to application + * + * If this callback is provided it will be called whenever there is an + * incoming call. + * + * @param call HFP HF call object. + */ + + void (*ring_indication)(struct bt_hfp_hf_call *call); + /** HF call dialing Callback + * + * This callback provides call dialing result to the application. + * + * @param hf HFP HF object. + * @param err Result of calling dialing. + */ + + void (*dialing)(struct bt_hfp_hf *hf, int err); + /** HF calling line identification notification callback to application + * + * If this callback is provided it will be called whenever there is a + * unsolicited result code +CLIP. If @kconfig{CONFIG_BT_HFP_HF_CLI} is not + * enabled, the unsolicited result code +CLIP will be ignored. And the + * callback will not be notified. + * + * @param call HFP HF call object. + * @param number Notified phone number. + * @param type Specify the format of the phone number. + */ + + void (*clip)(struct bt_hfp_hf_call *call, + const char *number, + uint8_t type); + /** HF microphone gain notification callback to application + * + * If this callback is provided it will be called whenever there is a + * unsolicited result code +VGM. If @kconfig{CONFIG_BT_HFP_HF_VOLUME} is + * not enabled, the unsolicited result code +VGM will be ignored. And the + * callback will not be notified. + * + * @param hf HFP HF object. + * @param gain Microphone gain. + */ + + void (*vgm)(struct bt_hfp_hf *hf, uint8_t gain); + /** HF speaker gain notification callback to application + * + * If this callback is provided it will be called whenever there is a + * unsolicited result code +VGS. If @kconfig{CONFIG_BT_HFP_HF_VOLUME} is + * not enabled, the unsolicited result code +VGS will be ignored. And the + * callback will not be notified. + * + * @param hf HFP HF object. + * @param gain Speaker gain. + */ + + void (*vgs)(struct bt_hfp_hf *hf, uint8_t gain); + /** HF in-band ring tone notification callback to application + * + * If this callback is provided it will be called whenever there is a + * unsolicited result code +BSIR issued by the AG to indicate to the HF + * that the in-band ring tone setting has been locally changed. + * + * @param hf HFP HF object. + * @param inband In-band ring tone status from the AG. + */ + + void (*inband_ring)(struct bt_hfp_hf *hf, bool inband); + /** HF network operator notification callback to application + * + * If this callback is provided it will be called whenever there is a + * response code +COPS issued by the AG to response the AT+COPS? command + * issued by the HF by calling function `bt_hfp_hf_get_operator`. + * + * @param hf HFP HF object. + * @param mode Current mode. + * @param format Format of the `operator` parameter string. It should be + * zero. + * @param operator A string in alphanumeric format representing the name + * of the network operator. + */ + + void (*operator)(struct bt_hfp_hf *hf, uint8_t mode, uint8_t format, + const char *operator); + /** Codec negotiate callback + * + * If this callback is provided it will be called whenever the unsolicited + * codec negotiation response received. There are two cases when the + * callback triggered, Case 1, the codec id can be accepted, the function + * `bt_hfp_hf_select_codec` should be called to accept the codec id. Case + * 2, the codec id can not be accepted, the function + * `bt_hfp_hf_set_codecs` should be called to trigger codec ID to be + * re-selected. If the callback is not provided by application, the + * function `bt_hfp_hf_select_codec` will be called to accept the codec + * id. Refers to BT_HFP_HF_CODEC_XXX for codec id value. If + * @kconfig{CONFIG_BT_HFP_HF_CODEC_NEG} is not enabled, the unsolicited + * result code +BCS will be ignored. And the callback will not be + * notified. + * + * @param hf HFP HF object. + * @param id Negotiated Codec ID. + */ + + void (*codec_negotiate)(struct bt_hfp_hf *hf, uint8_t id); + /** HF ECNR turns off callback + * + * If this callback is provided it will be called whenever the response of + * ECNR turning off is received from AG. If + * @kconfig{CONFIG_BT_HFP_HF_ECNR} is not enabled, the callback will not + * be notified. + * + * @param hf HFP HF object. + * @param err The result of request. + */ + + void (*ecnr_turn_off)(struct bt_hfp_hf *hf, int err); + /** HF call waiting notification callback to application + * + * If this callback is provided it will be called whenever there is a + * unsolicited result code +CCWA. This notification can be + * enabled/disabled by calling function `bt_hfp_hf_call_waiting_notify`. + * If @kconfig{CONFIG_BT_HFP_HF_3WAY_CALL} is not enabled, the unsolicited + * result code +CCWA will be ignored. And the callback will not be + * notified. + * + * @param call HFP HF call object. + * @param number Notified phone number. + * @param type Specify the format of the phone number. + */ + + void (*call_waiting)(struct bt_hfp_hf_call *call, const char *number, + uint8_t type); + /** Voice recognition activation/deactivation callback + * + * If this callback is provided it will be called whenever the unsolicited + * result code +BVRA is notified the HF when the voice recognition + * function in the AG is activated/deactivated autonomously from the AG. + * If @kconfig{CONFIG_BT_HFP_HF_VOICE_RECG} is not enabled, the + * unsolicited result code +BVRA will be ignored. And the callback will + * not be notified. + * + * @param hf HFP HF object. + * @param activate Voice recognition activation/deactivation. + */ + + void (*voice_recognition)(struct bt_hfp_hf *hf, bool activate); + /** Voice recognition engine state callback + * + * If this callback is provided it will be called whenever the unsolicited + * result code `+BVRA: 1,` is received from AG. ``: + * Bitmask that reflects the current state of the voice recognition engine + * on the AG. Bit 0 - If it is 1, the AG is ready to accept audio input + * Bit 1 - If it is 1, the AG is sending audio to the HF Bit 2 - If it is + * 1, the AG is processing the audio input If + * @kconfig{CONFIG_BT_HFP_HF_ENH_VOICE_RECG} is not enabled, the + * unsolicited result code +BVRA will be ignored. And the callback will + * not be notified. + * + * @param hf HFP HF object. + * @param state Value of ``. + */ + + void (*vre_state)(struct bt_hfp_hf *hf, uint8_t state); + /** Textual representation callback + * + * If this callback is provided it will be called whenever the unsolicited + * result code `+BVRA: 1,, ` is received + * from AG. `: ,,, + * `. ``: Unique ID of the current text as a hexadecimal + * string (a maximum of 4 characters in length, but less than 4 characters + * in length is valid). ``: ID of the textType from the + * following list: 0 - Text recognized by the AG from the audio input + * provided by the HF 1 - Text of the audio output from the AG 2 - Text of + * the audio output from the AG that contains a question 3 - Text of the + * audio output from the AG that contains an error description + * ``: ID of the operation of the text 1 - NewText: + * Indicates that a new text started. Shall be used when the `` + * changes 2 - Replace: Replace any existing text with the same `` + * and same `` 3 - Append: Attach new text to existing text and + * keep the same `` and same `` ``: The + * `` parameter shall be a UTF-8 text string and shall always be + * contained within double quotes. If + * @kconfig{CONFIG_BT_HFP_HF_VOICE_RECG_TEXT} is not enabled, the + * unsolicited result code +BVRA will be ignored. And the callback will + * not be notified. + * + * @param hf HFP HF object. + * @param id Value of ``. + * @param type Value of ``. + * @param operation Value of ``. + * @param text Value of ``. + */ + +void (*textual_representation)(struct bt_hfp_hf *hf, + char *id, + uint8_t type, + uint8_t operation, + const char *text); + /** Request phone number callback + * + * If this callback is provided it will be called whenever the result code + * `+BINP: ` is received from AG. If the request is failed, + * the `number` will be NULL. + * + * @param hf HFP HF object. + * @param number Value of ``. + */ + + void (*request_phone_number)(struct bt_hfp_hf *hf, const char *number); + + /** Query subscriber number callback + * + * If this callback is provided it will be called whenever the result code + * `+CUNM: [],, ,[ ,]` is received + * from AG. ``: This optional field is not supported, and shall be + * left blank. ``: Quoted string containing the phone number in + * the format specified by ``. `` field specifies the format + * of the phone number provided, and can be one of the following values: + * - values 128-143: The phone number format may be a national or + * international format, and may contain prefix and/or escape digits. No + * changes on the number presentation are required. + * - values 144-159: The phone number format is an international + * number, including the country code prefix. If the plus sign ("+") is + * not included as part of the number and shall be added by the AG as + * needed. + * - values 160-175: National number. No prefix nor escape digits + * included. ``: This optional field is not supported, and shall be + * left blank. ``: Indicates which service this phone number + * relates to. Shall be either 4 (voice) or 5 (fax). + * + * @param hf HFP HF object. + * @param number Value of `` without quotes. + * @param type Value of ``. + * @param service Value of ``. + */ + +void (*subscriber_number)(struct bt_hfp_hf *hf, + const char *number, + uint8_t type, + uint8_t service); + + /** Query list of current calls callback + * + * If this callback is provided it will be called whenever the result code + * `+CLCC: ,,,,[,,]` is + * received from AG. If the request is failed or no active calls, the + * callback will not be called. If the @ref bt_hfp_hf_current_call::number + * is NULL, the + * @ref bt_hfp_hf_current_call::type shall be ignored. + * + * @param hf HFP HF object. + * @param call Current call information. + */ + + void (*query_call)(struct bt_hfp_hf *hf, + struct bt_hfp_hf_current_call *call); +}; + +/** @brief Register HFP HF profile + * + * Register Handsfree profile callbacks to monitor the state and get the + * required HFP details to display. + * + * @param cb callback structure. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_register(struct bt_hfp_hf_cb *cb); + +/** @brief Initiate the service level connection establishment procedure + * + * Initiate the service level connection establishment procedure on the ACL + * connection specified by the parameter `conn` using the specific RFCOMM + * channel discovered by the function `bt_br_discovery_start`. + * + * The parameter `hf` is a output parameter. When the service level + * connection establishment procedure is initiated without any error, the + * HFP HF object is allocated and it will be returned via the parameter `hf` + * if the parameter `hf` is not a NULL pointer. + * + * When service level connection is established, the registered callback + * `connected` will be triggered to notify the application that the service + * level connection establishment procedure is done. And the HFP HF object + * is valid at this time. It means after the function is called without any + * error, all interfaces provided by HFP HF can only be called after the + * registered callback `connected` is triggered. + * + * @param conn ACL connection object. + * @param hf Created HFP HF object. + * @param channel Peer RFCOMM channel to be connected. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_connect(struct bt_conn *conn, + struct bt_hfp_hf **hf, uint8_t channel); + +/** @brief Release the service level connection + * + * Release the service level connection from the peer device. + * + * The function can only be called after the registered callback `connected` + * is triggered. + * + * If the function is called without any error, the HFP HF object is invalid + * at this time. All interfaces provided by HFP HF should not be called + * anymore. + * + * If the service level connection is released, the registered callback + * `disconnected` will be triggered to notify the application that the + * service level connection release procedure is done. And the HFP HF object + * will be freed after the registered callback `disconnected` returned. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_disconnect(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF enable/disable Calling Line Identification (CLI) + * Notification + * + * Enable/disable Calling Line Identification (CLI) Notification. The AT + * command `AT+CLIP` will be sent to the AG to enable/disable the CLI + * unsolicited result code +CLIP when calling the function. If + * @kconfig{CONFIG_BT_HFP_HF_CLI} is not enabled, the error `-ENOTSUP` will + * be returned if the function called. + * + * @param hf HFP HF object. + * @param enable Enable/disable CLI. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_cli(struct bt_hfp_hf *hf, bool enable); + +/** @brief Handsfree HF report Gain of Microphone (VGM) + * + * Report Gain of Microphone (VGM). The AT command `AT+VGM=` will be + * sent to the AG to report its current microphone gain level setting to the + * AG. `` is a decimal numeric constant, relating to a particular + * (implementation dependent) volume level controlled by the HF. This + * command does not change the microphone gain of the AG; it simply + * indicates the current value of the microphone gain in the HF. If + * @kconfig{CONFIG_BT_HFP_HF_VOLUME} is not enabled, the error `-ENOTSUP` + * will be returned if the function called. For "Volume Level + * Synchronization", the HF application could call the function to set VGM + * gain value in HF connection callback function. Then after the HF + * connection callback returned, VGM gain will be sent to HFP AG. + * + * @param hf HFP HF object. + * @param gain Gain of microphone. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_vgm(struct bt_hfp_hf *hf, uint8_t gain); + +/** @brief Handsfree HF report Gain of Speaker (VGS) + * + * Report Gain of Speaker (VGS). The AT command `AT+VGS=` will be sent + * to the AG to report its current speaker gain level setting to the AG. + * `` is a decimal numeric constant, relating to a particular + * (implementation dependent) volume level controlled by the HF. This + * command does not change the speaker gain of the AG; it simply indicates + * the current value of the speaker gain in the HF. If + * @kconfig{CONFIG_BT_HFP_HF_VOLUME} is not enabled, the error `-ENOTSUP` + * will be returned if the function called. For "Volume Level + * Synchronization", the HF application could call the function to set VGS + * gain value in HF connection callback function. Then after the HF + * connection callback returned, VGS gain will be sent to HFP AG. + * + * @param hf HFP HF object. + * @param gain Gain of speaker. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_vgs(struct bt_hfp_hf *hf, uint8_t gain); + +/** @brief Handsfree HF requests currently selected operator + * + * Send the AT+COPS? (Read) command to find the currently selected operator. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_get_operator(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF accept the incoming call + * + * Send the ATA command to accept the incoming call. OR, send the AT+BTRH=1 + * command to accept a held incoming call. + * + * @note It cannot be used when multiple calls are ongoing. + * + * @param call HFP HF call object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_accept(struct bt_hfp_hf_call *call); + +/** @brief Handsfree HF reject the incoming call + * + * Send the AT+CHUP command to reject the incoming call. OR, send the + * AT+BTRH=2 command to reject a held incoming call. + * + * @note It cannot be used when multiple calls are ongoing. + * + * @param call HFP HF call object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_reject(struct bt_hfp_hf_call *call); + +/** @brief Handsfree HF terminate the incoming call + * + * Send the AT+CHUP command to terminate the incoming call. + * + * @note It cannot be used when multiple calls are ongoing. + * + * @param call HFP HF call object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_terminate(struct bt_hfp_hf_call *call); + +/** @brief Handsfree HF put the incoming call on hold + * + * Send the AT+BTRH=0 command to put the incoming call on hold. If the + * incoming call has been held, the callback `on_hold` will be triggered. + * + * @note It cannot be used when multiple calls are ongoing. + * + * @param call HFP HF call object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_hold_incoming(struct bt_hfp_hf_call *call); + +/** @brief Handsfree HF query respond and hold status of AG + * + * Send the AT+BTRH? command to query respond and hold status of AG. The + * status respond and hold will be notified through callback `on_hold`. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_query_respond_hold_status(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF phone number call + * + * Initiate outgoing voice calls by providing the destination phone number + * to the AG. Send the ATDdd...dd; command to start phone number call. The + * result of the command will be notified through the callback `dialing`. + * + * @param hf HFP HF object. + * @param number Phone number. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_number_call(struct bt_hfp_hf *hf, const char *number); + +/** @brief Handsfree HF memory dialing call + * + * Initiate outgoing voice calls using the memory dialing feature of the AG. + * Send the ATD>nnn...; command to start memory dialing. The result of the + * command will be notified through the callback `dialing`. + * + * @param hf HFP HF object. + * @param location Memory location. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_memory_dial(struct bt_hfp_hf *hf, const char *location); + +/** @brief Handsfree HF redial last number + * + * Initiate outgoing voice calls by recalling the last number dialed by the + * AG. Send the AT+BLDN command to recall the last number. The result of the + * command will be notified through the callback `dialing`. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_redial(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF setup audio connection + * + * Setup audio connection by sending AT+BCC if the Codec Negotiation is + * supported by both side. Or, initialize the SCO audio connection directly. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_audio_connect(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF set selected codec id + * + * Set selected codec id by sending AT+BCS. The function is used to response + * the codec negotiation request notified by callback `codec_negotiate`. The + * parameter `codec_id` should be same as `id` of callback `codec_negotiate` + * if the id could be supported. Or, call `bt_hfp_hf_set_codecs` to notify + * the AG Codec IDs supported by HFP HF. If + * @kconfig{CONFIG_BT_HFP_HF_CODEC_NEG} is not enabled, the error `-ENOTSUP` + * will be returned if the function called. + * + * @param hf HFP HF object. + * @param codec_id Selected codec id. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_select_codec(struct bt_hfp_hf *hf, uint8_t codec_id); + +/** @brief Handsfree HF set supported codec ids + * + * Set supported codec ids by sending AT+BAC. This function is used to + * notify AG the supported Codec IDs of HF. If + * @kconfig{CONFIG_BT_HFP_HF_CODEC_NEG} is not enabled, the error `-ENOTSUP` + * will be returned if the function called. + * + * @param hf HFP HF object. + * @param codec_ids Supported codec IDs. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_set_codecs(struct bt_hfp_hf *hf, uint8_t codec_ids); + +/** @brief Handsfree HF turns off AG's EC and NR + * + * Turn off the AG's EC and NR by sending `AT+NREC=0`. The result of the + * command is notified through the callback `ecnr_turn_off`. If + * @kconfig{CONFIG_BT_HFP_HF_ECNR} is not enabled, the error `-ENOTSUP` will + * be returned if the function called. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_turn_off_ecnr(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF enable/disable call waiting notification + * + * Enable call waiting notification by sending `AT+CCWA=1`. Disable call + * waiting notification by sending `AT+CCWA=0`. If + * @kconfig{CONFIG_BT_HFP_HF_3WAY_CALL} is not enabled, the error `-ENOTSUP` + * will be returned if the function called. + * + * @param hf HFP HF object. + * @param enable Enable/disable. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_call_waiting_notify(struct bt_hfp_hf *hf, bool enable); + +/** @brief Handsfree HF release all held calls + * + * Release all held calls by sending `AT+CHLD=0`. If + * @kconfig{CONFIG_BT_HFP_HF_3WAY_CALL} is not enabled, the error `-ENOTSUP` + * will be returned if the function called. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_release_all_held(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF set User Determined User Busy (UDUB) for a waiting + * call + * + * Set User Determined User Busy (UDUB) for a waiting call by sending + * `AT+CHLD=0`. If @kconfig{CONFIG_BT_HFP_HF_3WAY_CALL} is not enabled, the + * error `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_set_udub(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF release all active calls and accept other call + * + * Release all active calls (if any exist) and accepts the other (held or + * waiting) call by sending `AT+CHLD=1`. If + * @kconfig{CONFIG_BT_HFP_HF_3WAY_CALL} is not enabled, the error `-ENOTSUP` + * will be returned if the function called. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_release_active_accept_other(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF hold all active calls and accept other call + * + * Hold all active calls (if any exist) and accepts the other (held or + * waiting) call by sending `AT+CHLD=2`. If + * @kconfig{CONFIG_BT_HFP_HF_3WAY_CALL} is not enabled, the error `-ENOTSUP` + * will be returned if the function called. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_hold_active_accept_other(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF add a held call to the conversation + * + * Add a held call to the conversation by sending `AT+CHLD=3`. If + * @kconfig{CONFIG_BT_HFP_HF_3WAY_CALL} is not enabled, the error `-ENOTSUP` + * will be returned if the function called. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_join_conversation(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF explicit call transfer + * + * Connects the two calls and disconnects the subscriber from both calls + * (Explicit Call Transfer) by sending `AT+CHLD=4`. If + * @kconfig{CONFIG_BT_HFP_HF_3WAY_CALL} is not enabled, the error `-ENOTSUP` + * will be returned if the function called. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_explicit_call_transfer(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF release call with specified index + * + * Release call with specified index by sending `AT+CHLD=1`. `` is + * index of specified call. If @kconfig{CONFIG_BT_HFP_HF_ECC} is not + * enabled, the error `-ENOTSUP` will be returned if the function called. + * + * @param call HFP HF call object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_release_specified_call(struct bt_hfp_hf_call *call); + +/** @brief Handsfree HF request private consultation mode with specified call + * + * Request private consultation mode with specified call (Place all calls on + * hold EXCEPT the call indicated by ``.) by sending `AT+CHLD=2`. + * `` is index of specified call. If @kconfig{CONFIG_BT_HFP_HF_ECC} is + * not enabled, the error `-ENOTSUP` will be returned if the function + * called. + * + * @param call HFP HF call object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_private_consultation_mode(struct bt_hfp_hf_call *call); + +/** @brief Handsfree HF enable/disable the voice recognition function + * + * Enables/disables the voice recognition function in the AG. If + * @kconfig{CONFIG_BT_HFP_HF_VOICE_RECG} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * @param activate Activate/deactivate the voice recognition function. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_voice_recognition(struct bt_hfp_hf *hf, bool activate); + +/** @brief Handsfree HF indicate that the HF is ready to accept audio + * + * This value indicates that the HF is ready to accept audio when the Audio + * Connection is first established. The HF shall only send this value if the + * eSCO link has been established. If + * @kconfig{CONFIG_BT_HFP_HF_ENH_VOICE_RECG} is not enabled, the error + * `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_ready_to_accept_audio(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF attach a phone number for a voice tag + * + * Send AT command "AT+BINP=1" to request phone number to the AG. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_request_phone_number(struct bt_hfp_hf *hf); + +/** @brief Handsfree HF Transmit A specific DTMF Code + * + * During an ongoing call, the HF transmits the AT+VTS command to instruct + * the AG to transmit a specific DTMF code to its network connection. The + * set of the code is "0-9,#,*,A-D". + * + * @param call HFP HF call object. + * @param code A specific DTMF code. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_transmit_dtmf_code(struct bt_hfp_hf_call *call, char code); + +/** @brief Handsfree HF Query Subscriber Number Information + * + * It allows HF to query the AG subscriber number by sending `AT+CNUM`. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_query_subscriber(struct bt_hfp_hf *hf); + +/* HFP HF Indicators */ + +enum hfp_hf_ag_indicators +{ + HF_SERVICE_IND = 0, /* AG service indicator */ + HF_CALL_IND, /* AG call indicator */ + HF_CALL_SETUP_IND, /* AG call setup indicator */ + HF_CALL_HELD_IND, /* AG call held indicator */ + HF_SIGNAL_IND, /* AG signal indicator */ + HF_ROAM_IND, /* AG roaming indicator */ + HF_BATTERY_IND /* AG battery indicator */ +}; + +/** @brief Handsfree HF set AG indicator activated/deactivated status + * + * It allows HF to issue the AT+BIA command if it needs to change the + * activated/deactivated status of indicators in the AG. The index of all + * indicators can be activated/deactivated are defined in `enum + * hfp_hf_ag_indicators`. The each bit of parameter `status` represents the + * indicator status corresponding to the index. Such as, value 0b111110 of + * `status` means the AG indicator `service` is required to be deactivated. + * Others are required to be activated. + * + * @param hf HFP HF object. + * @param status The activated/deactivated bitmap status of AG indicators. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_indicator_status(struct bt_hfp_hf *hf, uint8_t status); + +/** @brief Handsfree HF enable/disable enhanced safety + * + * It allows HF to transfer of HF indicator enhanced safety value. If + * @kconfig{CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY} is not enabled, the + * error `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * @param enable The enhanced safety is enabled/disabled. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_enhanced_safety(struct bt_hfp_hf *hf, bool enable); + +/** @brief Handsfree HF remaining battery level + * + * It allows HF to transfer of HF indicator remaining battery level value. + * If @kconfig{CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY} is not enabled, the + * error `-ENOTSUP` will be returned if the function called. + * + * @param hf HFP HF object. + * @param level The remaining battery level. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_battery(struct bt_hfp_hf *hf, uint8_t level); + +/** @brief Handsfree HF query list of current calls + * + * It allows HF to query list of current calls by sending `AT+CLCC` command. + * If @kconfig{CONFIG_BT_HFP_HF_ECS} is not enabled, the error `-ENOTSUP` + * will be returned if the function called. + * + * @param hf HFP HF object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_hfp_hf_query_list_of_current_calls(struct bt_hfp_hf *hf); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_HFP_HF_H */ diff --git a/include/nuttx/net/bluetooth/classic/hid.h b/include/nuttx/net/bluetooth/classic/hid.h new file mode 100644 index 0000000000000..8c7a24c00b985 --- /dev/null +++ b/include/nuttx/net/bluetooth/classic/hid.h @@ -0,0 +1,95 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/classic/hid.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Bluetooth HID protocol values shared by the Device and Host roles. + */ + +/* Copyright 2026 Xiaomi Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_HID_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_HID_H + +/** + * @brief Bluetooth HID + * @defgroup bt_hid Bluetooth HID + * @ingroup bluetooth + * @{ + */ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @name HID protocol mode values + * + * Defined in HID spec v1.1.2 Section 2.1.2, which makes Report Protocol Mode + * the default. The same values are carried by GET_PROTOCOL and SET_PROTOCOL + * in both directions, so they are shared by the Device and the Host role. @{ + */ + +/** Boot Protocol Mode (legacy). */ +#define BT_HID_PROTOCOL_BOOT_MODE 0x00U +/** Report Protocol Mode (default). */ +#define BT_HID_PROTOCOL_REPORT_MODE 0x01U +/** @} */ + +/** + * @name HID report type values + * + * The report type carried by GET_REPORT, SET_REPORT and DATA transfers. + * Defined in HID spec v1.1.2 Table 3.4. @{ + */ + +/** Reserved report type, used where no type applies. */ +#define BT_HID_REPORT_TYPE_OTHER 0x00U +/** Input report (device to host). */ +#define BT_HID_REPORT_TYPE_INPUT 0x01U +/** Output report (host to device). */ +#define BT_HID_REPORT_TYPE_OUTPUT 0x02U +/** Feature report. */ +#define BT_HID_REPORT_TYPE_FEATURE 0x03U +/** @} */ + +/** + * @name HID HANDSHAKE result codes + * + * Result of a request sent on the control channel. Defined in HID spec + * v1.1.2 Section 3.1.2.1. A Host reports them to the application, a Device + * answers with them, so they are shared by both roles. @{ + */ + +/** The request completed successfully. */ +#define BT_HID_HS_RSP_SUCCESS 0x00U +/** The peer is too busy to accept the request; the sender may retry. */ +#define BT_HID_HS_RSP_NOT_READY 0x01U +/** The request referenced an unsupported Report ID. */ +#define BT_HID_HS_RSP_ERR_INVALID_REPORT_ID 0x02U +/** The peer does not support the request. */ +#define BT_HID_HS_RSP_ERR_UNSUPPORTED_REQ 0x03U +/** The request carried an invalid parameter. */ +#define BT_HID_HS_RSP_ERR_INVALID_PARAM 0x04U +/** The peer could not identify the error condition. */ +#define BT_HID_HS_RSP_ERR_UNKNOWN 0x0eU +/** The peer encountered a fatal error and needs to be restarted. */ +#define BT_HID_HS_RSP_ERR_FATAL 0x0fU +/** @} */ + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_HID_H */ diff --git a/include/nuttx/net/bluetooth/classic/hid_device.h b/include/nuttx/net/bluetooth/classic/hid_device.h new file mode 100644 index 0000000000000..6e17ba6f7c4ae --- /dev/null +++ b/include/nuttx/net/bluetooth/classic/hid_device.h @@ -0,0 +1,341 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/classic/hid_device.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief HID Device Protocol handling. + */ + +/* Copyright 2025 Xiaomi Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_HID_DEVICE_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_HID_DEVICE_H + +/** + * @brief Bluetooth HID Device + * @defgroup bt_hid_device Bluetooth HID Device + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct bt_hid_device; + +/** @brief Callbacks supplied by the HID application + * + * Applications should register one instance of this callback table via + * `bt_hid_device_register()` to receive events and requests from the HID + * host. Only the callbacks the application needs must be provided; + * unimplemented callbacks should be set to NULL. + */ + +struct bt_hid_device_cb +{ + /** @brief connected callback to application + * + * If this callback is provided it will be called whenever the HID + * association completes. Both control and interrupt channels are + * established and the device is ready for HID traffic. + * + * @param hid HID device object. + */ + + void (*connected)(struct bt_hid_device *hid); + + /** @brief disconnected callback to application + * + * If this callback is provided it will be called whenever the HID + * association gets disconnected, including rejected or cancelled + * connections and errors during setup. The HID device object remains valid + * until this callback returns. + * + * @param hid HID device object. + */ + + void (*disconnected)(struct bt_hid_device *hid); + + /** @brief Set_Report request callback + * + * This callback provides report data sent by the HID host. This callback + * is mandatory (SET_REPORT must be supported per HID spec v1.1.2) and must + * be provided at registration. + * + * The buffer contains the raw Report Data Payload as received, including + * the Report ID byte if declared in the descriptor. Return 0 to accept the + * report. Return a negative errno to trigger a corresponding handshake + * error response. + * + * @param hid HID device object. + * @param type Report type (see BT_HID_REPORT_TYPE_*). + * @param buf Report payload buffer (ownership retained by stack). + * + * @return 0 on success or a negative errno on failure. + */ + + int (*set_report)(struct bt_hid_device *hid, uint8_t type, + struct net_buf *buf); + + /** @brief Get_Report request callback + * + * Called synchronously when the host sends a GET_REPORT request. This + * callback is mandatory (GET_REPORT must be supported per HID spec v1.1.2) + * and must be provided at registration. + * + * The application responds from within the callback by appending the + * report payload to @p rsp (including the Report ID byte if the report + * descriptor declares Report IDs) and returning 0. The stack then prepends + * the HIDP DATA header and sends the response. + * + * If no data is available yet, return -EAGAIN; the stack replies with a + * NOT_READY handshake and the host may retry (HID spec v1.1.2 Section + * 3.2.1.1). Any other negative errno triggers the matching handshake error + * response. + * + * @p req contains the remaining GET_REPORT parameters after the HIDP + * header: an optional Report ID (present per the report descriptor) + * followed, when @p size_present is true, by a 2-byte little-endian + * BufferSize (the maximum response length the host expects). When @p + * size_present is false no BufferSize is present. + * + * @param hid HID device object. + * @param type Report type (see BT_HID_REPORT_TYPE_*). + * @param size_present true if the request's Size bit is set, meaning a + * 2-byte BufferSize follows the optional Report ID in + * @p req. + * @param req Remaining request parameters (ownership retained by stack). + * @param rsp Response buffer to fill with the report payload (ownership + * retained by stack; sent by the stack on a 0 return). + * + * @return 0 if @p rsp was filled with the response, -EAGAIN to reply + * with NOT_READY, or another negative errno for a handshake error. + */ + +int (*get_report)(struct bt_hid_device *hid, + uint8_t type, + bool size_present, + struct net_buf *req, + struct net_buf *rsp); + + /** @brief Set_Protocol request callback + * + * Called when the host sends a SET_PROTOCOL request. The application + * should return 0 if the protocol mode is supported, or a negative errno + * if it is not. The driver updates the internal protocol mode flag and + * sends the appropriate HANDSHAKE response based on the return value. + * + * @param hid HID device object. + * @param protocol Requested protocol mode (BT_HID_PROTOCOL_BOOT_MODE or + * BT_HID_PROTOCOL_REPORT_MODE). + * + * @return 0 if the protocol mode is accepted, negative errno otherwise. + */ + + int (*set_protocol)(struct bt_hid_device *hid, uint8_t protocol); + + /** @brief Output report callback + * + * This callback provides output report data received on the interrupt + * channel from the HID host. The buffer contains the raw report payload + * including Report ID if declared in the descriptor. + * + * @param hid HID device object. + * @param buf Report payload buffer (ownership retained by stack). + */ + + void (*output_report)(struct bt_hid_device *hid, struct net_buf *buf); + + /** @brief Virtual Cable Unplug notification callback + * + * Invoked when the HID host sends a VIRTUAL_CABLE_UNPLUG HID_CONTROL + * message. Per HID spec v1.1.2 Section 3.1.2.2.3, the recipient shall + * destroy all bonding and Virtual Cable information stored for the peer. + * + * The stack tears down the INTR and CTRL channels on the application's + * behalf, but does not clear bonding: doing so requires dropping the link + * key (and conditionally the ACL), which may affect other profiles sharing + * the same ACL. The application is therefore responsible for clearing the + * bonding information when appropriate, for example: + * + * @code struct bt_conn *conn = bt_hid_device_get_conn(hid); + * + * if (conn != NULL) + * { + * bt_br_unpair(bt_conn_get_dst_br(conn)); bt_conn_unref(conn); } @endcode + * + * @param hid HID device object. + */ + + void (*vc_unplug)(struct bt_hid_device *hid); + + /** @brief Suspend/Exit-Suspend notification callback + * + * SUSPEND and EXIT_SUSPEND are unidirectional HID_CONTROL messages owned + * by the HID host. This callback simply relays each such message to the + * application as it is received; the device does not maintain any suspend + * state machine of its own. Per the HID profile, a suspended device that + * detects local user activity wakes the host by reconnecting, after which + * the host (optionally) issues EXIT_SUSPEND. Note EXIT_SUSPEND is not + * mandatory, so the application must not rely on it to leave the suspended + * state. + * + * Because the messages are relayed verbatim, the callback may fire on + * every SUSPEND/EXIT_SUSPEND the host sends, including duplicates. If the + * application needs idempotent or per-peer suspend behaviour across + * reconnect (e.g. HID DRE/BV-09-C), it tracks that itself, keyed on the + * peer identity it obtains from the ACL connection. + * + * @param hid HID device object. + * @param suspended true if entering suspend, false if exiting. + */ + + void (*suspend)(struct bt_hid_device *hid, bool suspended); +}; + +/** @brief Register HID device callbacks. + * + * The callback pointer must remain valid until bt_hid_device_unregister() is + * called. A second registration without an intervening unregister is + * rejected with -EALREADY; the callbacks are not replaced. + * + * @param cb Callbacks to register. Must not be NULL, and the mandatory + * @ref bt_hid_device_cb.get_report and + * @ref bt_hid_device_cb.set_report callbacks must be provided. + * + * @retval 0 on success. + * @retval -EINVAL if @p cb is NULL or a mandatory callback is missing. + * @retval -EALREADY if a callback set is already registered. + */ + +int bt_hid_device_register(const struct bt_hid_device_cb *cb); + +/** @brief Unregister HID device callbacks and disable HID services. + * + * This clears the callbacks and unregisters the L2CAP servers. The caller + * must disconnect any active HID session before calling this function; + * otherwise -EBUSY is returned and no action is taken. + * + * @retval 0 on success. + * @retval -EBUSY if an HID session is still active. + */ + +int bt_hid_device_unregister(void); + +/** @brief Initiate an outgoing HID association on the given connection. + * + * On success the bt_hid_device instance is stored in @p hid. The caller must + * not free this object. + * + * @param conn Bluetooth connection to use for the association. + * @param hid Pointer to store the HID device instance on success. + * + * @return 0 on success or a negative errno on failure. + */ + +int bt_hid_device_connect(struct bt_conn *conn, struct bt_hid_device **hid); + +/** @brief Disconnect a previously connected HID association. + * + * The disconnect is asynchronous. Callers should not free the @p hid + * instance until the disconnected callback is invoked. + * + * @param hid HID device instance to disconnect. + * + * @return 0 on success or a negative errno on failure. + */ + +int bt_hid_device_disconnect(struct bt_hid_device *hid); + +/** @brief Allocate/create a PDU buffer suitable for HID over L2CAP. + * + * If @p pool is NULL a default pool may be used by the implementation. + * + * @note Callers should use net_buf_add_*() to append report payload + * to the returned buffer. + * + * @param pool Buffer pool to allocate from, or NULL to use the default + * connection TX pool. + * + * @return A new net_buf instance or NULL on failure. + */ + +struct net_buf *bt_hid_device_create_pdu(struct net_buf_pool *pool); + +/** @brief Send a HID interrupt input report to the HID host. + * + * The buffer should contain the full report payload including the Report ID + * byte if the descriptor declares Report IDs (HID spec v1.1.2 Section + * 3.1.2.8). + * + * The HID device can transmit interrupt channel messages to the host at any + * time. + * + * @note The buffer must be allocated with bt_hid_device_create_pdu() + * to ensure sufficient headroom for the HID and L2CAP headers. + * + * @param hid HID device instance to send data on. + * @param buf Buffer containing the report payload (consumed on success). + * + * @return 0 on success or a negative errno on failure. + */ + +int bt_hid_device_input_report(struct bt_hid_device *hid, + struct net_buf *buf); + +/** @brief Trigger a virtual cable unplug procedure for the given HID + * association. + * + * This will request the remote host to treat the device as unplugged. + * + * @param hid HID device instance to unplug. + * + * @return 0 on success or a negative errno on failure. + */ + +int bt_hid_device_virtual_cable_unplug(struct bt_hid_device *hid); + +/** @brief Obtain the ACL connection associated with a HID device. + * + * Useful from callbacks that only receive the HID device object (for example + * @ref bt_hid_device_cb.vc_unplug) but need the peer identity, e.g. to clear + * bonding information. + * + * @param hid HID device object. + * + * @return Connection object associated with the HID device, or NULL if the + * device is not connected. The caller gets a new reference to the + * connection object which must be released with bt_conn_unref() once done + * using the object. + */ + +struct bt_conn *bt_hid_device_get_conn(struct bt_hid_device *hid); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_HID_DEVICE_H */ diff --git a/include/nuttx/net/bluetooth/classic/hid_host.h b/include/nuttx/net/bluetooth/classic/hid_host.h new file mode 100644 index 0000000000000..83da933c07c73 --- /dev/null +++ b/include/nuttx/net/bluetooth/classic/hid_host.h @@ -0,0 +1,404 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/classic/hid_host.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief HID Host Protocol handling. + */ + +/* Copyright 2026 Xiaomi Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_HID_HOST_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_HID_HOST_H + +/** + * @brief Bluetooth HID Host + * @defgroup bt_hid_host Bluetooth HID Host + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** @brief HID Host instance. + * + * Represents a single HID association with a remote HID Device. Instances + * are allocated from a fixed pool and owned by the stack; applications only + * hold the pointer handed out by bt_hid_host_connect() or the callbacks and + * must not allocate or free one. + */ + +struct bt_hid_host; + +/** @brief Callbacks supplied by the HID Host application. + * + * Register via bt_hid_host_register() to receive events from connected HID + * Devices. + * + * Only one control channel transaction may be outstanding at a time (HID + * spec v1.1.2 Section 3.2.1); further requests return -EBUSY until the + * response arrives. Each request has its own result callback, which reports + * the HANDSHAKE result code supplied by the device and is therefore only + * invoked when the device answers. A transaction that is never answered is + * treated as a lost connection per HID spec v1.1.2 Section 5.2.6: the HID + * association is torn down and the disconnected callback is invoked instead. + * Since at most one transaction is outstanding, an application that sees + * disconnected after issuing a request knows that request was lost. + */ + +struct bt_hid_host_cb +{ + /** @brief HID connection established. + * + * Called when both control and interrupt L2CAP channels are open and the + * HID association is ready for traffic. This is called for both outgoing + * (Host-initiated) and incoming (Device-initiated reconnection) + * connections. + * + * @param hid HID Host instance. + */ + + void (*connected)(struct bt_hid_host *hid); + + /** @brief HID connection terminated. + * + * Called when the HID association is fully torn down. + * + * @param hid HID Host instance. + */ + + void (*disconnected)(struct bt_hid_host *hid); + + /** @brief Input report received on the interrupt channel. + * + * Asynchronous input report data sent by the HID Device. + * + * The payload is delivered as received. It starts with the Report ID when + * the report descriptor declares Report IDs or the device is in Boot + * Protocol Mode, which only the application can tell. + * + * @param hid HID Host instance. + * @param buf Report payload (ownership retained by stack). + */ + + void (*input_report)(struct bt_hid_host *hid, struct net_buf *buf); + + /** @brief GET_REPORT result. + * + * Called for every GET_REPORT transaction, whether the device answered + * with a DATA message or rejected the request with a HANDSHAKE (HID spec + * v1.1.2 Section 3.2.1.1), so the outcome is handled in one place. + * + * The payload is delivered as received, starting with the Report ID when + * one is in use. + * + * @param hid HID Host instance. + * @param result_code BT_HID_HS_RSP_SUCCESS, or the BT_HID_HS_RSP_ERR_* + * code the device replied with. + * @param type Report type (INPUT/OUTPUT/FEATURE), undefined when + * @p result_code is not BT_HID_HS_RSP_SUCCESS. + * @param buf Report payload (ownership retained by stack), NULL + * when a DATA message is not received. + */ + +void (*get_report)(struct bt_hid_host *hid, + uint8_t result_code, + uint8_t type, + struct net_buf *buf); + + /** @brief SET_REPORT result. + * + * Called when the device answers a SET_REPORT request with a HANDSHAKE, + * which is the only reply a SET request gets (HID spec v1.1.2 Section + * 3.2.1.2). + * + * @param hid HID Host instance. + * @param result_code BT_HID_HS_RSP_SUCCESS, or the BT_HID_HS_RSP_ERR_* + * code the device replied with. + */ + + void (*set_report)(struct bt_hid_host *hid, uint8_t result_code); + + /** @brief GET_PROTOCOL result. + * + * Called for every GET_PROTOCOL transaction, whether the device answered + * with a DATA message carrying the protocol mode octet or rejected the + * request with a HANDSHAKE (HID spec v1.1.2 Section 3.2.1.5). + * + * @param hid HID Host instance. + * @param result_code BT_HID_HS_RSP_SUCCESS, or the BT_HID_HS_RSP_ERR_* + * code the device replied with. + * @param protocol Protocol mode of the device + * (BT_HID_PROTOCOL_BOOT_MODE or + * BT_HID_PROTOCOL_REPORT_MODE), undefined when @p + * result_code is not BT_HID_HS_RSP_SUCCESS. + */ + + void (*get_protocol)(struct bt_hid_host *hid, uint8_t result_code, + uint8_t protocol); + + /** @brief SET_PROTOCOL result. + * + * Called when the device answers a SET_PROTOCOL request with a HANDSHAKE, + * which is the only reply a SET request gets (HID spec v1.1.2 Section + * 3.2.1.6). The requested mode is in use only when @p result_code is + * BT_HID_HS_RSP_SUCCESS. + * + * @param hid HID Host instance. + * @param result_code BT_HID_HS_RSP_SUCCESS, or the BT_HID_HS_RSP_ERR_* + * code the device replied with. + */ + + void (*set_protocol)(struct bt_hid_host *hid, uint8_t result_code); + + /** @brief Virtual Cable Unplug received from Device. + * + * The remote HID Device has sent a VIRTUAL_CABLE_UNPLUG control message + * and the stack disconnects the HID association. HID spec v1.1.2 Section + * 3.1.2.2.3 also requires the bonding to be destroyed; this is left to the + * application because bt_br_unpair() would drop the whole ACL and with it + * any co-located profile. + * + * A control transaction that was still outstanding when the unplug arrived + * is discarded, so its result callback is never invoked: per Section + * 3.1.2.2.3 the recipient discards any pending control transfer. An + * application waiting for such a reply has to treat this callback as the + * outcome. + * + * @param hid HID Host instance. + */ + + void (*vc_unplug)(struct bt_hid_host *hid); +}; + +/** @brief Register HID Host callbacks. + * + * Registers the application callbacks and sets up L2CAP servers to accept + * incoming Device-initiated reconnections on PSM 0x0011/0x0013. + * + * @param cb Callback structure. Must remain valid for the lifetime of the + * HID Host subsystem. + * + * @return 0 on success, negative errno on failure. + */ + +int bt_hid_host_register(const struct bt_hid_host_cb *cb); + +/** @brief Unregister HID Host callbacks. + * + * Unregisters the L2CAP servers so incoming Device-initiated reconnections + * are no longer accepted. All HID associations must be disconnected first. + * + * @return 0 on success, negative errno on other failures. + * @retval -EBUSY if an HID association is still active. + * @retval -EALREADY if the HID Host callbacks are not registered. + */ + +int bt_hid_host_unregister(void); + +/** @brief Initiate an HID connection to a remote HID Device. + * + * Opens the L2CAP control channel and, once it is up, the interrupt channel. + * + * The HID service record is not read here. Applications that need the report + * descriptor or the device attributes discover them over SDP themselves, + * which also leaves them free to decide whether to do so on every + * connection. + * + * @param conn ACL connection to the remote device. + * @param hid Pointer to store the HID Host instance on success. + * + * @return 0 on success, -ENOTSUP if bt_hid_host_register() has not been + * called, negative errno on other failures. + */ + +int bt_hid_host_connect(struct bt_conn *conn, struct bt_hid_host **hid); + +/** @brief Get the ACL connection of an HID association. + * + * Needed by applications that have to act on the peer identity, for example + * to destroy the bonding after a Virtual Cable Unplug (HID spec v1.1.2 + * Section 3.1.2.2.3), which the stack leaves to the application. + * + * @param hid HID Host instance. + * + * @return New reference to the ACL connection which must be released with + * bt_conn_unref(), or NULL if the association is no longer established. + */ + +struct bt_conn *bt_hid_host_get_conn(struct bt_hid_host *hid); + +/** @brief Disconnect an HID association. + * + * Tears down the interrupt channel first, then the control channel per HID + * spec v1.1.2 Section 5.2.2. + * + * @param hid HID Host instance. + * + * @return 0 on success, negative errno on failure. + */ + +int bt_hid_host_disconnect(struct bt_hid_host *hid); + +/** @brief Send a GET_REPORT request to the HID Device. + * + * The response arrives asynchronously via the get_report callback, or the + * association is torn down if the device never answers. + * + * The ReportID field is required as soon as the report descriptor declares + * Report IDs, and in Boot Protocol Mode it is always required (HID spec + * v1.1.2 Section 3.1.2.3 and Section 3.3.1). Only the application knows the + * descriptor, so it decides whether the field is sent. + * + * @param hid HID Host instance. + * @param type Report type (BT_HID_REPORT_TYPE_INPUT/OUTPUT/FEATURE). + * @param report_id Report ID to request, or 0 to omit the field. Report ID + * 0 is reserved by the USB HID specification, so it + * cannot identify a report. + * @param buffer_size Maximum response payload size (0 = no limit). Must not + * exceed what the control channel can receive. + * + * @return 0 on success, negative errno on failure. + */ + +int bt_hid_host_get_report( + struct bt_hid_host *hid, uint8_t type, uint8_t report_id, + uint16_t buffer_size); + +/** @brief Send a SET_REPORT request to the HID Device. + * + * The response arrives via the set_report callback. The buffer must start + * with the Report ID when the report descriptor declares Report IDs or the + * device is in Boot Protocol Mode. + * + * @param hid HID Host instance. + * @param type Report type (BT_HID_REPORT_TYPE_INPUT/OUTPUT/FEATURE). + * @param buf Buffer containing report payload, allocated with + * bt_hid_host_create_pdu(). Retained by the caller on failure. + * + * @return 0 on success, negative errno on failure. + */ + +int bt_hid_host_set_report(struct bt_hid_host *hid, + uint8_t type, struct net_buf *buf); + +/** @brief Send a GET_PROTOCOL request to the HID Device. + * + * The response arrives via the get_protocol callback. + * + * @param hid HID Host instance. + * + * @return 0 on success, negative errno on failure. + */ + +int bt_hid_host_get_protocol(struct bt_hid_host *hid); + +/** @brief Send a SET_PROTOCOL request to the HID Device. + * + * The response arrives via the set_protocol callback. + * + * @param hid HID Host instance. + * @param protocol Target protocol (BT_HID_PROTOCOL_BOOT_MODE or + * BT_HID_PROTOCOL_REPORT_MODE). + * + * @return 0 on success, negative errno on failure. + */ + +int bt_hid_host_set_protocol(struct bt_hid_host *hid, uint8_t protocol); + +/** @brief Send an output report on the interrupt channel. + * + * This is an asynchronous transfer with no acknowledgment: HID spec v1.1.2 + * Section 3.1.2.9 leaves interrupt channel transfers unanswered. The buffer + * must start with the Report ID when the report descriptor declares Report + * IDs or the device is in Boot Protocol Mode. + * + * @param hid HID Host instance. + * @param buf Buffer containing output report, allocated with + * bt_hid_host_create_pdu() so the HIDP header fits (consumed on + * success). + * + * @return 0 on success, negative errno on failure. + */ + +int bt_hid_host_output_report(struct bt_hid_host *hid, struct net_buf *buf); + +/** @brief Send SUSPEND control to the HID Device. + * + * Informs the device that the host is entering a low-power state. + * + * @param hid HID Host instance. + * + * @return 0 on success, negative errno on failure. + */ + +int bt_hid_host_suspend(struct bt_hid_host *hid); + +/** @brief Send EXIT_SUSPEND control to the HID Device. + * + * Informs the device that the host is exiting low-power state. + * + * @param hid HID Host instance. + * + * @return 0 on success, negative errno on failure. + */ + +int bt_hid_host_exit_suspend(struct bt_hid_host *hid); + +/** @brief Send Virtual Cable Unplug to the HID Device. + * + * Sends a VCU control message and waits for the HID Device to acknowledge it + * by disconnecting the association, per HID spec v1.1.2 Section 3.1.2.2.3. + * If the peer does not disconnect, the host starts a fallback teardown after + * the transaction timeout. Destroying the bonding is left to the + * application, see the vc_unplug callback. + * + * @param hid HID Host instance. + * + * @return 0 on success, negative errno on failure. + */ + +int bt_hid_host_virtual_cable_unplug(struct bt_hid_host *hid); + +/** @brief Allocate a PDU buffer for HID Host transmissions. + * + * The returned buffer already reserves headroom for the HID header, so the + * profile can prepend it without copying. Callers only append the report + * payload with net_buf_add_*() and must not consume the headroom themselves. + * + * @param pool Buffer pool to allocate from, or NULL for default. + * + * @return net_buf on success, NULL on failure. + */ + +struct net_buf *bt_hid_host_create_pdu(struct net_buf_pool *pool); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_HID_HOST_H */ diff --git a/include/nuttx/net/bluetooth/classic/l2cap_br.h b/include/nuttx/net/bluetooth/classic/l2cap_br.h new file mode 100644 index 0000000000000..9c2ba1fe76bd7 --- /dev/null +++ b/include/nuttx/net/bluetooth/classic/l2cap_br.h @@ -0,0 +1,305 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/classic/l2cap_br.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Bluetooth L2CAP BR/EDR handling + */ + +/* Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_L2CAP_BR_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_L2CAP_BR_H + +/** + * @brief Bluetooth Logical Link Control and Adaptation Protocol (L2CAP) for + * BR/EDR + * @defgroup bt_l2cap_br Bluetooth Logical Link Control and Adaptation + * Protocol (L2CAP) for BR/EDR + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief ECHO request/response callback structure. + * + * This structure is used for tracking the ECHO request/response signaling + * packets of L2CAP BR. It is registered with the help of the + * bt_l2cap_br_echo_cb_register() API. It's permissible to register multiple + * instances of this @ref bt_l2cap_br_echo_cb type, in case different modules + * of an application are interested in tracking the ECHO request/response + * signaling packets. If a callback is not of interest for an instance, it + * may be set to NULL and will as a consequence not be used for that + * instance. + */ + +struct bt_l2cap_br_echo_cb +{ + /* @brief A ECHO request has been received. + * + * This callback notifies the application of a ECHO request has been + * received. The ECHO response should be performed by calling the + * bt_l2cap_br_echo_rsp() API. + * + * @param conn The ACL connection object. + * @param identifier The identifier of the ECHO request. + * @param buf Received ECHO data. + */ + + void (*req)(struct bt_conn *conn, uint8_t identifier, struct net_buf *buf); + + /* @brief A ECHO response has been received. + * + * This callback notifies the application of a ECHO response has been + * received. + * + * @param conn The ACL connection object. + * @param buf Received ECHO data. + */ + + void (*rsp)(struct bt_conn *conn, struct net_buf *buf); + + /** @internal Internally used field for list handling */ + + struct list_node _node; +}; + +/** + * @brief Register ECHO callbacks. + * + * Register callbacks to monitor the packets of L2CAP BR echo + * request/response. + * + * @param cb Callback struct. Must point to memory that remains valid. + * + * @retval 0 Success. + * @retval -EEXIST if @p cb was already registered. + */ + +int bt_l2cap_br_echo_cb_register(struct bt_l2cap_br_echo_cb *cb); + +/** + * @brief Unregister ECHO callbacks. + * + * Unregister callbacks that are used to monitor the packets of L2CAP BR echo + * request/response. + * + * @param cb Callback struct point to memory that remains valid. + * + * @retval 0 Success. + * @retval -EINVAL If @p cb is NULL. + * @retval -ENOENT if @p cb was not registered. + */ + +int bt_l2cap_br_echo_cb_unregister(struct bt_l2cap_br_echo_cb *cb); + +/** + * @brief Headroom needed for outgoing L2CAP ECHO REQ PDUs. + */ +#define BT_L2CAP_BR_ECHO_REQ_RESERVE BT_L2CAP_BUF_SIZE(4) + +/** + * @brief Headroom needed for outgoing L2CAP ECHO RSP PDUs. + */ +#define BT_L2CAP_BR_ECHO_RSP_RESERVE BT_L2CAP_BUF_SIZE(4) + +/** + * @brief Send ECHO data through ECHO request + * + * Send ECHO data through ECHO request. The application is required to have + * reserved + * @ref BT_L2CAP_BR_ECHO_REQ_RESERVE bytes in the buffer before sending. + * + * @param conn The ACL connection object. + * @param buf Sending ECHO data. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_l2cap_br_echo_req(struct bt_conn *conn, struct net_buf *buf); + +/** + * @brief Send ECHO data through ECHO response + * + * Send ECHO data through ECHO response. The application is required to have + * reserved + * @ref BT_L2CAP_BR_ECHO_RSP_RESERVE bytes in the buffer before sending. + * + * @param conn The ACL connection object. + * @param identifier The identifier of the ECHO request. + * @param buf Sending ECHO data. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_l2cap_br_echo_rsp(struct bt_conn *conn, + uint8_t identifier, struct net_buf *buf); + +/** + * @brief Connectionless data channel callback structure. + * + * This structure is used for tracking the connectionless data channel + * frames. It is registered with the help of the + * bt_l2cap_br_connless_register() API. It's permissible to register multiple + * instances of this @ref bt_l2cap_br_connless_cb type, in case different + * modules of an application are interested in tracking the connectionless + * data channel frames. + */ + +struct bt_l2cap_br_connless_cb +{ + /** @brief Register PSM. + * + * For BR, possible values: + * + * The PSM field is at least two octets in length. All PSM values shall + * have the least significant bit of the most significant octet equal to 0 + * and the least significant bit of all other octets equal to 1. + * + * 0 Any connectionless data channel frame will be notified. + * + * 0x0001-0x0eff Standard, Bluetooth SIG-assigned fixed values. + * + * > 0x1000 Dynamically allocated. May be pre-set by the application + * before server registration (not recommended however), or auto-allocated + * by the stack if the app gave 0 as the value. + */ + + uint16_t psm; + + /** Required minimum security level */ + + bt_security_t sec_level; + + /* @brief A connectionless channel data has been received. + * + * This callback notifies the application of a connectionless channel data + * has been received. + * + * @param conn The ACL connection object. + * @param psm Protocol/Service Multiplexer. + * @param buf Received connectionless channel data. + */ + + void (*recv)(struct bt_conn *conn, uint16_t psm, struct net_buf *buf); + + /** @internal Internally used field for list handling */ + + struct list_node _node; +}; + +/** + * @brief Register connectionless data channel callbacks. + * + * Register callbacks to monitor and receive connectionless data channel + * frames. This allows the application to receive data sent to a specific PSM + * without establishing a connection. + * + * @param cb Callback struct. Must point to memory that remains valid. The + * PSM field must be set to the desired Protocol/Service + * Multiplexer value, or 0 to receive all connectionless data + * frames. + * + * @retval 0 Success. + * @retval -EINVAL If @p cb is NULL, has invalid PSM value, has invalid + * `recv` callback, or has invalid security level. + * @retval -EEXIST If a callback with the same PSM was already registered. + */ + +int bt_l2cap_br_connless_register(struct bt_l2cap_br_connless_cb *cb); + +/** + * @brief Unregister connectionless data channel callbacks. + * + * Unregister callbacks that are used to monitor and receive connectionless + * data channel frames. + * + * @param cb Registered cb. + * + * @retval 0 Success. + * @retval -EINVAL If @p cb is NULL. + * @retval -ENOENT if @p cb was not registered. + */ + +int bt_l2cap_br_connless_unregister(struct bt_l2cap_br_connless_cb *cb); + +/** + * @brief L2CAP connectionless SDU header size. + */ +#define BT_L2CAP_CONNLESS_SDU_HDR_SIZE 2 + +/** + * @brief Headroom needed for outgoing L2CAP PDU format within a + * connectionless data channel. + */ +#define BT_L2CAP_CONNLESS_RESERVE BT_L2CAP_BUF_SIZE(BT_L2CAP_CONNLESS_SDU_HDR_SIZE) + +/** + * @brief Send data over connectionless data channel + * + * Send data over a connectionless data channel. This function allows sending + * data to a specific PSM without establishing the L2CAP channel connection. + * + * The application is required to have reserved @ref + * BT_L2CAP_CONNLESS_RESERVE bytes in the buffer before sending. + * + * @param conn Connection object. + * @param psm Protocol/Service Multiplexer identifying the destination + * service. + * @param buf Buffer containing the data to be sent. The application is + * required to have reserved enough headroom in the buffer for + * the L2CAP header. + * + * @retval 0 Success. + * @retval -EINVAL If @p conn or @p buf is NULL, @p psm is invalid, the + * reference counter of @p buf != 1, the head room of @p buf is less than + * @ref BT_L2CAP_CONNLESS_RESERVE, or the `buf->user_data_size` is less than + * the size of `struct closure`. + * @return -ENOTSUP If the connectionless reception feature is unsupported by + * remote. + * @return -ENOTCONN If the L2CAP channel with connectionless channel is not + * found. + * @return -EMSGSIZE If the data size exceeds the peer's connectionless + * channel MTU. + * @retval -ESHUTDOWN If the L2CAP channel with connectionless channel is + * NULL in low level. + */ + +int bt_l2cap_br_connless_send(struct bt_conn *conn, + uint16_t psm, struct net_buf *buf); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_L2CAP_BR_H */ diff --git a/include/nuttx/net/bluetooth/classic/map.h b/include/nuttx/net/bluetooth/classic/map.h new file mode 100644 index 0000000000000..4a506c9d245f5 --- /dev/null +++ b/include/nuttx/net/bluetooth/classic/map.h @@ -0,0 +1,3214 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/classic/map.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Message Access Profile header. + */ + +/* Copyright 2026 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_MAP_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_MAP_H + +/** + * @brief Message Access Profile (MAP) + * @defgroup bt_map Message Access Profile (MAP) + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** @brief MAP mandatory supported features bitmask + * + * Minimum set of features that must be supported by all MAP implementations. + * Includes notification registration, notification, browsing, uploading, and + * delete features. + */ +#define BT_MAP_MANDATORY_SUPPORTED_FEATURES (0x0000001FU) + +/** @brief MAP Message Access Server (MAS) UUID + * + * 128-bit UUID for the Message Access Server service. Used to identify MAS + * service in OBEX connections. + */ +#define BT_MAP_UUID_MAS \ +(const struct bt_uuid_128 *)BT_UUID_DECLARE_128( \ + BT_UUID_128_ENCODE(0xBB582B40, 0x420C, 0x11DB, 0xB0DE, 0x0800200C9A66)) + +/** @brief MAP Message Notification Server (MNS) UUID + * + * 128-bit UUID for the Message Notification Server service. Used to identify + * MNS service in OBEX connections. + */ +#define BT_MAP_UUID_MNS \ +(const struct bt_uuid_128 *)BT_UUID_DECLARE_128( \ + BT_UUID_128_ENCODE(0xBB582B41, 0x420C, 0x11DB, 0xB0DE, 0x0800200C9A66)) + +/** @brief Filler byte for padding + * + * Single byte used as padding in MAP operations where a body is required but + * no actual data needs to be transmitted. + */ +#define BT_MAP_FILLER_BYTE "0" + +/** @brief Type header for send event operation */ +#define BT_MAP_HDR_TYPE_SEND_EVENT "x-bt/MAP-event-report" +/** @brief Type header for set notification registration operation */ +#define BT_MAP_HDR_TYPE_SET_NTF_REG "x-bt/MAP-NotificationRegistration" +/** @brief Type header for get folder listing operation */ +#define BT_MAP_HDR_TYPE_GET_FOLDER_LISTING "x-obex/folder-listing" +/** @brief Type header for get message listing operation */ +#define BT_MAP_HDR_TYPE_GET_MSG_LISTING "x-bt/MAP-msg-listing" +/** @brief Type header for get message operation */ +#define BT_MAP_HDR_TYPE_GET_MSG "x-bt/message" +/** @brief Type header for set message status operation */ +#define BT_MAP_HDR_TYPE_SET_MSG_STATUS "x-bt/messageStatus" +/** @brief Type header for push message operation */ +#define BT_MAP_HDR_TYPE_PUSH_MSG "x-bt/message" +/** @brief Type header for update inbox operation */ +#define BT_MAP_HDR_TYPE_UPDATE_INBOX "x-bt/MAP-messageUpdate" +/** @brief Type header for get MAS instance info operation */ +#define BT_MAP_HDR_TYPE_GET_MAS_INST_INFO "x-bt/MASInstanceInformation" +/** @brief Type header for set owner status operation */ +#define BT_MAP_HDR_TYPE_SET_OWNER_STATUS "x-bt/ownerStatus" +/** @brief Type header for get owner status operation */ +#define BT_MAP_HDR_TYPE_GET_OWNER_STATUS "x-bt/ownerStatus" +/** @brief Type header for get conversation listing operation */ +#define BT_MAP_HDR_TYPE_GET_CONVO_LISTING "x-bt/MAP-convo-listing" +/** @brief Type header for set notification filter operation */ +#define BT_MAP_HDR_TYPE_SET_NTF_FILTER "x-bt/MAP-notification-filter" + +/** @brief MAP supported features + * + * Feature flags indicating optional MAP capabilities. These are exchanged + * during SDP discovery or MAP connection establishment. + */ + +enum __packed bt_map_supported_features +{ + /** Notification registration feature */ + + BT_MAP_NTF_REG_FEATURE = BIT(0), + + /** Notification feature */ + + BT_MAP_NTF_FEATURE = BIT(1), + + /** Browsing feature */ + + BT_MAP_BROWSING_FEATURE = BIT(2), + + /** Uploading feature */ + + BT_MAP_UPLOADING_FEATURE = BIT(3), + + /** Delete feature */ + + BT_MAP_DELETE_FEATURE = BIT(4), + + /** Instance information feature */ + + BT_MAP_INST_INFO_FEATURE = BIT(5), + + /** Extended event report version 1.1 */ + + BT_MAP_EXT_EVENT_REPORT_V11 = BIT(6), + + /** Extended event version 1.2 */ + + BT_MAP_EXT_EVENT_REPORT_V12 = BIT(7), + + /** Message format version 1.1 */ + + BT_MAP_MSG_FORMAT_V11 = BIT(8), + + /** Message listing format version 1.1 */ + + BT_MAP_MSG_LISTING_FORMAT_V11 = BIT(9), + + /** Persistent message handle */ + + BT_MAP_PERSISTENT_MSG_HANDLE = BIT(10), + + /** Database identifier */ + + BT_MAP_DB_ID = BIT(11), + + /** Folder version counter */ + + BT_MAP_FOLDER_VER_CNTR = BIT(12), + + /** Conversation version counter */ + + BT_MAP_CONVO_VER_CNTR = BIT(13), + + /** Participant presence change notification */ + + BT_MAP_PARTICIPANT_PRESENCE_CHANGE_NTF = BIT(14), + + /** Participant chat state change notification */ + + BT_MAP_PARTICIPANT_CHAT_STATE_CHANGE_NTF = BIT(15), + + /** PBAP contact cross reference */ + + BT_MAP_PBAP_CONTACT_XREF = BIT(16), + + /** Notification filtering */ + + BT_MAP_NTF_FILTERING = BIT(17), + + /** UTC offset timestamp format */ + + BT_MAP_UTC_OFFSET_TIMESTAMP_FORMAT = BIT(18), + + /** Supported features in connect request */ + + BT_MAP_SUPPORTED_FEATURES_CONN_REQ = BIT(19), + + /** Conversation listing */ + + BT_MAP_CONVO_LISTING = BIT(20), + + /** Owner status */ + + BT_MAP_OWNER_STATUS = BIT(21), + + /** Message forwarding */ + + BT_MAP_MSG_FORWARDING = BIT(22), +}; + +/** @brief MAP supported message types + * + * Bitmask indicating which message types are supported by a MAS instance. + */ + +enum __packed bt_map_supported_msg_type +{ + /** Email message type */ + + BT_MAP_MSG_TYPE_EMAIL = BIT(0), + + /** SMS GSM message type */ + + BT_MAP_MSG_TYPE_SMS_GSM = BIT(1), + + /** SMS CDMA message type */ + + BT_MAP_MSG_TYPE_SMS_CDMA = BIT(2), + + /** MMS message type */ + + BT_MAP_MSG_TYPE_MMS = BIT(3), + + /** Instant messaging type */ + + BT_MAP_MSG_TYPE_IM = BIT(4), +}; + +/** @brief MAP set folder operation flags + * + * Flags controlling folder navigation behavior in SetFolder operations. + */ + +enum __packed bt_map_set_folder_flags +{ + /** Navigate to root folder */ + + BT_MAP_SET_FOLDER_FLAGS_ROOT = BT_OBEX_SETPATH_FLAG_NO_CREATE, + + /** Navigate down to child folder */ + + BT_MAP_SET_FOLDER_FLAGS_DOWN = BT_OBEX_SETPATH_FLAG_NO_CREATE, + + /** Navigate up to parent folder */ + + BT_MAP_SET_FOLDER_FLAGS_UP = BT_OBEX_SETPATH_FLAG_BACKUP | + BT_OBEX_SETPATH_FLAG_NO_CREATE, +}; + +/** @brief MAP application parameter tag identifiers + * + * Tag IDs for application parameters used in MAP operations. These are used + * in OBEX Application Parameters header to pass operation-specific data. + */ + +enum __packed bt_map_appl_param_tag_id +{ + /** Maximum list count (2 bytes) */ + + BT_MAP_APPL_PARAM_TAG_ID_MAX_LIST_CNT = 0x01, + + /** List start offset (2 bytes) */ + + BT_MAP_APPL_PARAM_TAG_ID_LIST_START_OFFSET = 0x02, + + /** Filter message type (1 byte) @ref bt_map_filter_msg_type */ + + BT_MAP_APPL_PARAM_TAG_ID_FILTER_MSG_TYPE = 0x03, + + /** Filter period begin (variable length) */ + + BT_MAP_APPL_PARAM_TAG_ID_FILTER_PERIOD_BEGIN = 0x04, + + /** Filter period end (variable length) */ + + BT_MAP_APPL_PARAM_TAG_ID_FILTER_PERIOD_END = 0x05, + + /** Filter read status (1 byte) @ref bt_map_filter_read_status */ + + BT_MAP_APPL_PARAM_TAG_ID_FILTER_READ_STATUS = 0x06, + + /** Filter recipient (variable length) */ + + BT_MAP_APPL_PARAM_TAG_ID_FILTER_RECIPIENT = 0x07, + + /** Filter originator (variable length) */ + + BT_MAP_APPL_PARAM_TAG_ID_FILTER_ORIGINATOR = 0x08, + + /** Filter priority (1 byte) @ref bt_map_filter_priority */ + + BT_MAP_APPL_PARAM_TAG_ID_FILTER_PRIORITY = 0x09, + + /** Attachment (1 byte) @ref bt_map_attachment */ + + BT_MAP_APPL_PARAM_TAG_ID_ATTACHMENT = 0x0a, + + /** Transparent (1 byte) @ref bt_map_transparent */ + + BT_MAP_APPL_PARAM_TAG_ID_TRANSPARENT = 0x0b, + + /** Retry (1 byte) @ref bt_map_retry */ + + BT_MAP_APPL_PARAM_TAG_ID_RETRY = 0x0c, + + /** New message (1 byte) @ref bt_map_new_msg */ + + BT_MAP_APPL_PARAM_TAG_ID_NEW_MSG = 0x0d, + + /** Notification status (1 byte) @ref bt_map_ntf_status */ + + BT_MAP_APPL_PARAM_TAG_ID_NTF_STATUS = 0x0e, + + /** MAS instance ID (1 byte) */ + + BT_MAP_APPL_PARAM_TAG_ID_MAS_INST_ID = 0x0f, + + /** Parameter mask (4 bytes) @ref bt_map_param_mask */ + + BT_MAP_APPL_PARAM_TAG_ID_PARAM_MASK = 0x10, + + /** Folder listing size (2 bytes) */ + + BT_MAP_APPL_PARAM_TAG_ID_FOLDER_LISTING_SIZE = 0x11, + + /** Listing size (2 bytes) */ + + BT_MAP_APPL_PARAM_TAG_ID_LISTING_SIZE = 0x12, + + /** Subject length (variable, max 255 bytes) */ + + BT_MAP_APPL_PARAM_TAG_ID_SUBJECT_LEN = 0x13, + + /** Charset (1 byte) @ref bt_map_charset */ + + BT_MAP_APPL_PARAM_TAG_ID_CHARSET = 0x14, + + /** Fraction request (1 byte) @ref bt_map_fraction_request */ + + BT_MAP_APPL_PARAM_TAG_ID_FRACTION_REQUEST = 0x15, + + /** Fraction deliver (1 byte) @ref bt_map_fraction_deliver */ + + BT_MAP_APPL_PARAM_TAG_ID_FRACTION_DELIVER = 0x16, + + /** Status indicator (1 byte) @ref bt_map_status_ind */ + + BT_MAP_APPL_PARAM_TAG_ID_STATUS_IND = 0x17, + + /** Status value (1 byte) @ref bt_map_status_val */ + + BT_MAP_APPL_PARAM_TAG_ID_STATUS_VAL = 0x18, + + /** MSE time (variable length) */ + + BT_MAP_APPL_PARAM_TAG_ID_MSE_TIME = 0x19, + + /** Database identifier (variable, max 32 bytes) */ + + BT_MAP_APPL_PARAM_TAG_ID_DB_ID = 0x1a, + + /** Conversation listing version counter (variable, max 32 bytes) */ + + BT_MAP_APPL_PARAM_TAG_ID_CONVO_LISTING_VER_CNTR = 0x1b, + + /** Presence availability (1 byte) @ref bt_map_presence */ + + BT_MAP_APPL_PARAM_TAG_ID_PRESENCE_AVAIL = 0x1c, + + /** Presence text (variable length) */ + + BT_MAP_APPL_PARAM_TAG_ID_PRESENCE_TEXT = 0x1d, + + /** Last activity (variable length) */ + + BT_MAP_APPL_PARAM_TAG_ID_LAST_ACTIVITY = 0x1e, + + /** Filter last activity begin (variable length) */ + + BT_MAP_APPL_PARAM_TAG_ID_FILTER_LAST_ACTIVITY_BEGIN = 0x1f, + + /** Filter last activity end (variable length) */ + + BT_MAP_APPL_PARAM_TAG_ID_FILTER_LAST_ACTIVITY_END = 0x20, + + /** Chat state (1 byte) @ref bt_map_chat_state */ + + BT_MAP_APPL_PARAM_TAG_ID_CHAT_STATE = 0x21, + + /** Conversation ID (variable, max 32 bytes) */ + + BT_MAP_APPL_PARAM_TAG_ID_CONVO_ID = 0x22, + + /** Folder version counter (variable, max 32 bytes) */ + + BT_MAP_APPL_PARAM_TAG_ID_FOLDER_VER_CNTR = 0x23, + + /** Filter message handle (variable, max 16 bytes) */ + + BT_MAP_APPL_PARAM_TAG_ID_FILTER_MSG_HANDLE = 0x24, + + /** Notification filter mask (4 bytes) @ref bt_map_ntf_filter_mask */ + + BT_MAP_APPL_PARAM_TAG_ID_NTF_FILTER_MASK = 0x25, + + /** Conversation parameter mask (4 bytes) @ref bt_map_convo_param_mask */ + + BT_MAP_APPL_PARAM_TAG_ID_CONVO_PARAM_MASK = 0x26, + + /** Owner UCI (variable length) */ + + BT_MAP_APPL_PARAM_TAG_ID_OWNER_UCI = 0x27, + + /** Extended data (variable length) @ref bt_map_msg_ext_data */ + + BT_MAP_APPL_PARAM_TAG_ID_EXT_DATA = 0x28, + + /** MAP supported features (4 bytes) @ref bt_map_supported_features */ + + BT_MAP_APPL_PARAM_TAG_ID_MAP_SUPPORTED_FEATURES = 0x29, + + /** Message handle (variable, max 16 bytes) */ + + BT_MAP_APPL_PARAM_TAG_ID_MSG_HANDLE = 0x2a, + + /** Modify text (1 byte) @ref bt_map_modify_text */ + + BT_MAP_APPL_PARAM_TAG_ID_MODIFY_TEXT = 0x2b, +}; + +/** @brief MAP filter message type values + * + * Bitmask for filtering messages by type. + */ + +enum __packed bt_map_filter_msg_type +{ + /** No filtering */ + + BT_MAP_FILTER_MSG_TYPE_NO_FILTERING = 0x00, + + /** SMS GSM */ + + BT_MAP_FILTER_MSG_TYPE_SMS_GSM = BIT(0), + + /** SMS CDMA */ + + BT_MAP_FILTER_MSG_TYPE_SMS_CDMA = BIT(1), + + /** Email */ + + BT_MAP_FILTER_MSG_TYPE_EMAIL = BIT(2), + + /** MMS */ + + BT_MAP_FILTER_MSG_TYPE_MMS = BIT(3), + + /** Instant Message */ + + BT_MAP_FILTER_MSG_TYPE_IM = BIT(4), +}; + +/** @brief MAP filter read status values + * + * Filter for read/unread messages. + */ + +enum __packed bt_map_filter_read_status +{ + /** No filtering */ + + BT_MAP_FILTER_READ_STATUS_NO_FILTERING = 0x00, + + /** Unread messages only */ + + BT_MAP_FILTER_READ_STATUS_UNREAD = 0x01, + + /** Read messages only */ + + BT_MAP_FILTER_READ_STATUS_READ = 0x02, +}; + +/** @brief MAP filter priority values + * + * Filter for message priority. + */ + +enum __packed bt_map_filter_priority +{ + /** No filtering */ + + BT_MAP_FILTER_PRIORITY_NO_FILTERING = 0x00, + + /** High priority messages only */ + + BT_MAP_FILTER_PRIORITY_HIGH = 0x01, + + /** Non-high priority messages only */ + + BT_MAP_FILTER_PRIORITY_NON_HIGH = 0x02, +}; + +/** @brief MAP attachment values + * + * Indicates whether to include attachments. + */ + +enum __packed bt_map_attachment +{ + /** Off - no attachments */ + + BT_MAP_ATTACHMENT_OFF = 0x00, + + /** On - include attachments */ + + BT_MAP_ATTACHMENT_ON = 0x01, +}; + +/** @brief MAP transparent values + * + * Indicates whether to use transparent mode. + */ + +enum __packed bt_map_transparent +{ + /** Off - not transparent */ + + BT_MAP_TRANSPARENT_OFF = 0x00, + + /** On - transparent */ + + BT_MAP_TRANSPARENT_ON = 0x01, +}; + +/** @brief MAP retry values + * + * Indicates whether to retry sending. + */ + +enum __packed bt_map_retry +{ + /** Off - no retry */ + + BT_MAP_RETRY_OFF = 0x00, + + /** On - retry */ + + BT_MAP_RETRY_ON = 0x01, +}; + +/** @brief MAP new message values + * + * Indicates whether a message is new. + */ + +enum __packed bt_map_new_msg +{ + /** Not a new message */ + + BT_MAP_NEW_MSG_OFF = 0x00, + + /** New message */ + + BT_MAP_NEW_MSG_ON = 0x01, +}; + +/** @brief MAP notification status values + * + * Status for notification registration. + */ + +enum __packed bt_map_ntf_status +{ + /** Notifications off */ + + BT_MAP_NTF_STATUS_OFF = 0x00, + + /** Notifications on */ + + BT_MAP_NTF_STATUS_ON = 0x01, +}; + +/** @brief MAP parameter mask bits + * + * Bitmask for selecting which message attributes to include. + */ + +enum __packed bt_map_param_mask +{ + /** Subject */ + + BT_MAP_PARAM_MASK_SUBJECT = BIT(0), + + /** Datetime */ + + BT_MAP_PARAM_MASK_DATETIME = BIT(1), + + /** Sender name */ + + BT_MAP_PARAM_MASK_SENDER_NAME = BIT(2), + + /** Sender addressing */ + + BT_MAP_PARAM_MASK_SENDER_ADDRESSING = BIT(3), + + /** Recipient name */ + + BT_MAP_PARAM_MASK_RECIPIENT_NAME = BIT(4), + + /** Recipient addressing */ + + BT_MAP_PARAM_MASK_RECIPIENT_ADDRESSING = BIT(5), + + /** Type */ + + BT_MAP_PARAM_MASK_TYPE = BIT(6), + + /** Size */ + + BT_MAP_PARAM_MASK_SIZE = BIT(7), + + /** Reception status */ + + BT_MAP_PARAM_MASK_RECEPTION_STATUS = BIT(8), + + /** Text */ + + BT_MAP_PARAM_MASK_TEXT = BIT(9), + + /** Attachment size */ + + BT_MAP_PARAM_MASK_ATTACHMENT_SIZE = BIT(10), + + /** Priority */ + + BT_MAP_PARAM_MASK_PRIORITY = BIT(11), + + /** Read */ + + BT_MAP_PARAM_MASK_READ = BIT(12), + + /** Sent */ + + BT_MAP_PARAM_MASK_SENT = BIT(13), + + /** Protected */ + + BT_MAP_PARAM_MASK_PROTECTED = BIT(14), + + /** Replyto addressing */ + + BT_MAP_PARAM_MASK_REPLYTO_ADDRESSING = BIT(15), + + /** Delivery status */ + + BT_MAP_PARAM_MASK_DELIVERY_STATUS = BIT(16), + + /** Conversation ID */ + + BT_MAP_PARAM_MASK_CONVO_ID = BIT(17), + + /** Conversation name */ + + BT_MAP_PARAM_MASK_CONVO_NAME = BIT(18), + + /** Direction */ + + BT_MAP_PARAM_MASK_DIRECTION = BIT(19), + + /** Attachment MIME */ + + BT_MAP_PARAM_MASK_ATTACHMENT_MIME = BIT(20), +}; + +/** @brief MAP charset values + * + * Character set for message content. + */ + +enum __packed bt_map_charset +{ + /** Native charset */ + + BT_MAP_CHARSET_NATIVE = 0x00, + + /** UTF-8 charset */ + + BT_MAP_CHARSET_UTF8 = 0x01, +}; + +/** @brief MAP fraction request values + * + * Fraction of message to request. + */ + +enum __packed bt_map_fraction_request +{ + /** First fraction */ + + BT_MAP_FRACTION_REQUEST_FIRST = 0x00, + + /** Next fraction */ + + BT_MAP_FRACTION_REQUEST_NEXT = 0x01, +}; + +/** @brief MAP fraction deliver values + * + * Fraction of message delivered. + */ + +enum __packed bt_map_fraction_deliver +{ + /** More fractions to follow */ + + BT_MAP_FRACTION_DELIVER_MORE = 0x00, + + /** Last fraction */ + + BT_MAP_FRACTION_DELIVER_LAST = 0x01, +}; + +/** @brief MAP status indicator values + * + * Type of status to modify. + */ + +enum __packed bt_map_status_ind +{ + /** Read status */ + + BT_MAP_STATUS_IND_READ = 0x00, + + /** Deleted status */ + + BT_MAP_STATUS_IND_DELETED = 0x01, + + /** Set extended data */ + + BT_MAP_STATUS_IND_EXTENDED_DATA = 0x02, +}; + +/** @brief MAP status value values + * + * New status value. + */ + +enum __packed bt_map_status_val +{ + /** No/Off */ + + BT_MAP_STATUS_VAL_NO = 0x00, + + /** Yes/On */ + + BT_MAP_STATUS_VAL_YES = 0x01, +}; + +/** @brief MAP presence availability states + * + * Enumeration of possible presence states for the message owner. + */ + +enum __packed bt_map_presence +{ + /** Unknown presence state */ + + BT_MAP_PRESENCE_UNKNOWN = 0x00, + + /** Offline */ + + BT_MAP_PRESENCE_OFFLINE = 0x01, + + /** Online */ + + BT_MAP_PRESENCE_ONLINE = 0x02, + + /** Away */ + + BT_MAP_PRESENCE_AWAY = 0x03, + + /** Do not disturb */ + + BT_MAP_PRESENCE_DO_NOT_DISTURB = 0x04, + + /** Busy */ + + BT_MAP_PRESENCE_BUSY = 0x05, + + /** In a meeting */ + + BT_MAP_PRESENCE_IN_A_MEETING = 0x06, +}; + +/** @brief MAP chat states + * + * Enumeration of possible chat states for the message owner. + */ + +enum __packed bt_map_chat_state +{ + /** Unknown chat state */ + + BT_MAP_CHAT_STATE_UNKNOWN = 0x00, + + /** Inactive */ + + BT_MAP_CHAT_STATE_INACTIVE = 0x01, + + /** Active */ + + BT_MAP_CHAT_STATE_ACTIVE = 0x02, + + /** Composing */ + + BT_MAP_CHAT_STATE_COMPOSING = 0x03, + + /** Paused composing */ + + BT_MAP_CHAT_STATE_PAUSED_COMPOSING = 0x04, + + /** Gone */ + + BT_MAP_CHAT_STATE_GONE = 0x05, +}; + +/** @brief MAP notification filter mask bits + * + * Bitmask for filtering which event types to receive notifications for. + */ + +enum __packed bt_map_ntf_filter_mask +{ + /** NewMessage */ + + BT_MAP_NTF_FILTER_MASK_NEW_MSG = BIT(0), + + /** MessageDeleted */ + + BT_MAP_NTF_FILTER_MASK_MSG_DELETED = BIT(1), + + /** MessageShift */ + + BT_MAP_NTF_FILTER_MASK_MSG_SHIFT = BIT(2), + + /** SendingSuccess */ + + BT_MAP_NTF_FILTER_MASK_SENDING_SUCCESS = BIT(3), + + /** SendingFailure */ + + BT_MAP_NTF_FILTER_MASK_SENDING_FAILURE = BIT(4), + + /** DeliverySuccess */ + + BT_MAP_NTF_FILTER_MASK_DELIVERY_SUCCESS = BIT(5), + + /** DeliveryFailure */ + + BT_MAP_NTF_FILTER_MASK_DELIVERY_FAILURE = BIT(6), + + /** MemoryFull */ + + BT_MAP_NTF_FILTER_MASK_MEM_FULL = BIT(7), + + /** MemoryAvailable */ + + BT_MAP_NTF_FILTER_MASK_MEM_AVAIL = BIT(8), + + /** ReadStatusChanged */ + + BT_MAP_NTF_FILTER_MASK_READ_STATUS_CHANGED = BIT(9), + + /** ConversationChanged */ + + BT_MAP_NTF_FILTER_MASK_CONVO_CHANGED = BIT(10), + + /** ParticipantPresenceChanged */ + + BT_MAP_NTF_FILTER_MASK_PARTICIPANT_PRESENCE_CHANGED = BIT(11), + + /** ParticipantChatStateChanged */ + + BT_MAP_NTF_FILTER_MASK_PARTICIPANT_CHAT_STATE_CHANGED = BIT(12), + + /** MessageExtendedDataChanged */ + + BT_MAP_NTF_FILTER_MASK_MSG_EXT_DATA_CHANGED = BIT(13), + + /** MessageRemoved */ + + BT_MAP_NTF_FILTER_MASK_MSG_REMOVED = BIT(14), +}; + +/** @brief MAP conversation parameter mask bits + * + * Bitmask for selecting which conversation attributes to include. + */ + +enum __packed bt_map_convo_param_mask +{ + /** Conversation name */ + + BT_MAP_CONVO_PARAM_MASK_CONVO_NAME = BIT(0), + + /** Conversation last activity */ + + BT_MAP_CONVO_PARAM_MASK_CONVO_LAST_ACTIVITY = BIT(1), + + /** Read status */ + + BT_MAP_CONVO_PARAM_MASK_READ_STATUS = BIT(2), + + /** Version counter */ + + BT_MAP_CONVO_PARAM_MASK_VER_CNTR = BIT(3), + + /** Summary */ + + BT_MAP_CONVO_PARAM_MASK_SUMMARY = BIT(4), + + /** Participants */ + + BT_MAP_CONVO_PARAM_MASK_PARTICIPANTS = BIT(5), + + /** Participant UCI */ + + BT_MAP_CONVO_PARAM_MASK_PARTICIPANT_UCI = BIT(6), + + /** Participant display name */ + + BT_MAP_CONVO_PARAM_MASK_PARTICIPANT_DISP_NAME = BIT(7), + + /** Participant chat state */ + + BT_MAP_CONVO_PARAM_MASK_PARTICIPANT_CHAT_STATE = BIT(8), + + /** Participant last activity */ + + BT_MAP_CONVO_PARAM_MASK_PARTICIPANT_LAST_ACTIVITY = BIT(9), + + /** Participant X-BT-UID */ + + BT_MAP_CONVO_PARAM_MASK_PARTICIPANT_X_BT_UID = BIT(10), + + /** Participant name */ + + BT_MAP_CONVO_PARAM_MASK_PARTICIPANT_NAME = BIT(11), + + /** Participant presence availability */ + + BT_MAP_CONVO_PARAM_MASK_PARTICIPANT_PRESENCE_AVAIL = BIT(12), + + /** Participant presence text */ + + BT_MAP_CONVO_PARAM_MASK_PARTICIPANT_PRESENCE_TEXT = BIT(13), + + /** Participant priority */ + + BT_MAP_CONVO_PARAM_MASK_PARTICIPANT_PRIORITY = BIT(14), +}; + +/** @brief MAP message extended data types + * + * Types of extended metadata that can be associated with messages. + */ + +enum __packed bt_map_msg_ext_data +{ + /** Number of Facebook likes */ + + BT_MAP_MSG_EXT_DATA_FACEBOOK_LIKES = 0x00, + + /** Number of Twitter followers */ + + BT_MAP_MSG_EXT_DATA_TWITTER_FOLLOWERS = 0x01, + + /** Number of Twitter retweets */ + + BT_MAP_MSG_EXT_DATA_TWITTER_RETWEETS = 0x02, + + /** Number of Google +1s */ + + BT_MAP_MSG_EXT_DATA_GOOGLE_1S = 0x03, +}; + +/** @brief MAP modify text values + * + * Indicates how message text is modified. + */ + +enum __packed bt_map_modify_text +{ + /** Replace text */ + + BT_MAP_MODIFY_TEXT_REPLACE = 0x00, + + /** Prepend text */ + + BT_MAP_MODIFY_TEXT_PREPEND = 0x01, +}; + +/** + * @brief Messaging Client Equipment(MCE) OBEX Client + * @defgroup bt_map_mce_mas Messaging Client Equipment(MCE) OBEX Client + * @ingroup bt_map + * @{ + */ + +/** @brief Forward declaration of MAP Client MAS structure */ + +struct bt_map_mce_mas; + +/** @brief MAP Client MAS callbacks + * + * Callbacks for MAP Client MAS events. + */ + +struct bt_map_mce_mas_cb +{ + /** @brief RFCOMM transport connected callback + * + * Called when the underlying transport (RFCOMM) connection is established. + * OBEX connection has not yet been negotiated at this point. + * + * @param conn Bluetooth connection object. + * @param mce_mas MAS client instance. + */ + + void (*rfcomm_connected)(struct bt_conn *conn, + struct bt_map_mce_mas *mce_mas); + + /** @brief RFCOMM transport disconnected callback + * + * Called when the underlying transport (RFCOMM) connection is closed. All + * pending operations are cancelled. + * + * @param mce_mas MAS client instance. + */ + + void (*rfcomm_disconnected)(struct bt_map_mce_mas *mce_mas); + + /** @brief L2CAP transport connected callback + * + * Called when the underlying transport (L2CAP) connection is established. + * OBEX connection has not yet been negotiated at this point. + * + * @param conn Bluetooth connection object. + * @param mce_mas MAS client instance. + */ + + void (*l2cap_connected)(struct bt_conn *conn, + struct bt_map_mce_mas *mce_mas); + + /** @brief L2CAP transport disconnected callback + * + * Called when the underlying transport (L2CAP) connection is closed. All + * pending operations are cancelled. + * + * @param mce_mas MAS client instance. + */ + + void (*l2cap_disconnected)(struct bt_map_mce_mas *mce_mas); + + /** @brief Connect callback + * + * Called when OBEX CONNECT response is received. + * + * @param mce_mas MAS client instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_SUCCESS on success). + * @param version OBEX protocol version supported by server. + * @param mopl Maximum OBEX packet length supported by server. + * @param buf Buffer containing additional response headers + * (connection ID, etc.). + */ + +void (*connect)(struct bt_map_mce_mas *mce_mas, + uint8_t rsp_code, + uint8_t version, + uint16_t mopl, + struct net_buf *buf); + + /** @brief Disconnect callback + * + * Called when OBEX DISCONNECT response is received. + * + * @param mce_mas MAS client instance. + * @param rsp_code Response code @ref bt_obex_rsp_code. + * @param buf Buffer containing response headers. + */ + + void (*disconnect)(struct bt_map_mce_mas *mce_mas, uint8_t rsp_code, + struct net_buf *buf); + + /** @brief Abort callback + * + * Called when OBEX ABORT response is received. + * + * @param mce_mas MAS client instance. + * @param rsp_code Response code @ref bt_obex_rsp_code. + * @param buf Buffer containing response headers. + */ + + void (*abort)(struct bt_map_mce_mas *mce_mas, uint8_t rsp_code, + struct net_buf *buf); + + /** @brief Set notification registration callback + * + * Called when SetNotificationRegistration response is received. + * + * @param mce_mas MAS client instance. + * @param rsp_code Response code @ref bt_obex_rsp_code. + * @param buf Buffer containing response headers. + */ + + void (*set_ntf_reg)(struct bt_map_mce_mas *mce_mas, uint8_t rsp_code, + struct net_buf *buf); + + /** @brief Set folder callback + * + * Called when SetFolder response is received. + * + * @param mce_mas MAS client instance. + * @param rsp_code Response code @ref bt_obex_rsp_code. + * @param buf Buffer containing response headers. + */ + + void (*set_folder)(struct bt_map_mce_mas *mce_mas, uint8_t rsp_code, + struct net_buf *buf); + + /** @brief Get folder listing callback + * + * Called when GetFolderListing response is received. May be called + * multiple times for a single request if response is fragmented. + * + * @param mce_mas MAS client instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_CONTINUE for partial data). + * @param buf Buffer containing folder listing XML data and headers. + */ + + void (*get_folder_listing)(struct bt_map_mce_mas *mce_mas, + uint8_t rsp_code, + struct net_buf *buf); + + /** @brief Get message listing callback + * + * Called when GetMessageListing response is received. May be called + * multiple times for a single request if response is fragmented. + * + * @param mce_mas MAS client instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_CONTINUE for partial data). + * @param buf Buffer containing message listing XML data and headers. + */ + + void (*get_msg_listing)(struct bt_map_mce_mas *mce_mas, uint8_t rsp_code, + struct net_buf *buf); + + /** @brief Get message callback + * + * Called when GetMessage response is received. May be called multiple + * times for a single request if response is fragmented. + * + * @param mce_mas MAS client instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_CONTINUE for partial data). + * @param buf Buffer containing message bMessage data and headers. + */ + + void (*get_msg)(struct bt_map_mce_mas *mce_mas, uint8_t rsp_code, + struct net_buf *buf); + + /** @brief Set message status callback + * + * Called when SetMessageStatus response is received. + * + * @param mce_mas MAS client instance. + * @param rsp_code Response code @ref bt_obex_rsp_code. + * @param buf Buffer containing response headers. + */ + + void (*set_msg_status)(struct bt_map_mce_mas *mce_mas, uint8_t rsp_code, + struct net_buf *buf); + + /** @brief Push message callback + * + * Called when PushMessage response is received. May be called multiple + * times if request is fragmented (@ref BT_OBEX_RSP_CODE_CONTINUE). + * + * @param mce_mas MAS client instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_CONTINUE for partial upload). + * @param buf Buffer containing message handle (on success) and + * headers. + */ + + void (*push_msg)(struct bt_map_mce_mas *mce_mas, uint8_t rsp_code, + struct net_buf *buf); + + /** @brief Update inbox callback + * + * Called when UpdateInbox response is received. + * + * @param mce_mas MAS client instance. + * @param rsp_code Response code @ref bt_obex_rsp_code. + * @param buf Buffer containing response headers. + */ + + void (*update_inbox)(struct bt_map_mce_mas *mce_mas, uint8_t rsp_code, + struct net_buf *buf); + + /** @brief Get MAS instance info callback + * + * Called when GetMASInstanceInformation response is received. May be + * called multiple times for a single request if response is fragmented. + * + * @param mce_mas MAS client instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_CONTINUE for partial data). + * @param buf Buffer containing instance information XML data and + * headers. + */ + + void (*get_mas_inst_info)(struct bt_map_mce_mas *mce_mas, uint8_t rsp_code, + struct net_buf *buf); + + /** @brief Set owner status callback + * + * Called when SetOwnerStatus response is received. + * + * @param mce_mas MAS client instance. + * @param rsp_code Response code @ref bt_obex_rsp_code. + * @param buf Buffer containing response headers. + */ + + void (*set_owner_status)(struct bt_map_mce_mas *mce_mas, uint8_t rsp_code, + struct net_buf *buf); + + /** @brief Get owner status callback + * + * Called when GetOwnerStatus response is received. May be called multiple + * times for a single request if response is fragmented. + * + * @param mce_mas MAS client instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_CONTINUE for partial data). + * @param buf Buffer containing owner status data and headers. + */ + + void (*get_owner_status)(struct bt_map_mce_mas *mce_mas, uint8_t rsp_code, + struct net_buf *buf); + + /** @brief Get conversation listing callback + * + * Called when GetConversationListing response is received. May be called + * multiple times for a single request if response is fragmented. + * + * @param mce_mas MAS client instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_CONTINUE for partial data). + * @param buf Buffer containing conversation listing XML data and + * headers. + */ + + void (*get_convo_listing)(struct bt_map_mce_mas *mce_mas, uint8_t rsp_code, + struct net_buf *buf); + + /** @brief Set notification filter callback + * + * Called when SetNotificationFilter response is received. + * + * @param mce_mas MAS client instance. + * @param rsp_code Response code @ref bt_obex_rsp_code. + * @param buf Buffer containing response headers. + */ + + void (*set_ntf_filter)(struct bt_map_mce_mas *mce_mas, uint8_t rsp_code, + struct net_buf *buf); +}; + +/** @brief MAP Client MAS instance structure + * + * Represents a client connection to a Message Access Server. Used for + * browsing folders and manipulating messages on the server. + */ + +struct bt_map_mce_mas +{ + /** @brief Underlying GOEP transport instance */ + + struct bt_goep_transport goep_transport; + + /** @brief Underlying GOEP instance */ + + struct bt_goep goep; + + /** @internal Callbacks */ + + const struct bt_map_mce_mas_cb *_cb; + + /** @internal Transport state (atomic) */ + + atomic_t _transport_state; + + /** @internal Connection ID */ + + uint32_t _conn_id; + + /** @internal Underlying OBEX client */ + + struct bt_obex_client _client; + + /** @internal Client state (atomic) */ + + atomic_t _state; + + /** @internal Pending response callback */ + + void (*_rsp_cb)(struct bt_map_mce_mas *mce_mas, uint8_t rsp_code, + struct net_buf *buf); + + /** @internal Current request function type */ + + const char *_req_type; +}; + +/** @brief Connect MAP Client MAS over RFCOMM + * + * Initiates RFCOMM transport connection to a remote MAS. On success, the + * rfcomm_connected callback @ref bt_map_mce_mas_cb::rfcomm_connected will be + * called. + * + * @param conn Bluetooth connection to remote device. + * @param mce_mas MAS client instance. + * @param cb Pointer to callback structure (must remain valid for the + * lifetime of the connection). + * @param channel RFCOMM server channel (from SDP discovery). + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mas_rfcomm_connect( + struct bt_conn *conn, struct bt_map_mce_mas *mce_mas, + const struct bt_map_mce_mas_cb *cb, uint8_t channel); + +/** @brief Disconnect MAP Client MAS over RFCOMM + * + * Closes the RFCOMM transport connection. On success, the + * rfcomm_disconnected callback @ref bt_map_mce_mas_cb::rfcomm_disconnected + * will be called. + * + * @param mce_mas MAS client instance. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mas_rfcomm_disconnect(struct bt_map_mce_mas *mce_mas); + +/** @brief Connect MAP Client MAS over L2CAP + * + * Initiates L2CAP transport connection to a remote MAS. On success, the + * l2cap_connected callback @ref bt_map_mce_mas_cb::l2cap_connected will be + * called. + * + * @param conn Bluetooth connection to remote device. + * @param mce_mas MAS client instance. + * @param cb Pointer to callback structure (must remain valid for the + * lifetime of the connection). + * @param psm L2CAP PSM (from SDP discovery). + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mas_l2cap_connect( + struct bt_conn *conn, struct bt_map_mce_mas *mce_mas, + const struct bt_map_mce_mas_cb *cb, uint16_t psm); + +/** @brief Disconnect MAP Client MAS over L2CAP + * + * Closes the L2CAP transport connection. On success, the l2cap_disconnected + * callback @ref bt_map_mce_mas_cb::l2cap_disconnected will be called. + * + * @param mce_mas MAS client instance. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mas_l2cap_disconnect(struct bt_map_mce_mas *mce_mas); + +/** @brief Create PDU for MAP Client MAS + * + * Allocates a buffer for building MAP request packets. The buffer is sized + * appropriately for the negotiated MTU. + * + * @param mce_mas MAS client instance. + * @param pool Buffer pool to allocate from (NULL for default pool). + * + * @return Allocated buffer, or NULL on allocation failure. + */ + +struct net_buf *bt_map_mce_mas_create_pdu(struct bt_map_mce_mas *mce_mas, + struct net_buf_pool *pool); + +/** @brief Send OBEX connect request + * + * Initiates OBEX session establishment with the MAS. Must be called after + * transport connection is established. Once OBEX connect response received, + * the connect callback @ref bt_map_mce_mas_cb::connect will be called. + * + * @param mce_mas MAS client instance. + * @param buf Buffer containing connect headers (target UUID, supported + * features, etc.). If NULL, a default connect request is + * sent. If success returned, the function has taken the + * ownership of + * @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mas_connect(struct bt_map_mce_mas *mce_mas, + struct net_buf *buf); + +/** @brief Send OBEX disconnect request + * + * Initiates OBEX session termination. Once OBEX disconnect response + * received, the disconnect callback @ref bt_map_mce_mas_cb::disconnect will + * be called. + * + * @param mce_mas MAS client instance. + * @param buf Buffer containing disconnect headers (connection ID, etc.). + * If NULL, a default disconnect request is sent. If success + * returned, the function has taken the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mas_disconnect(struct bt_map_mce_mas *mce_mas, + struct net_buf *buf); + +/** @brief Send OBEX abort request + * + * Aborts the currently ongoing operation. Once OBEX abort response received, + * the abort callback @ref bt_map_mce_mas_cb::abort will be called. + * + * @param mce_mas MAS client instance. + * @param buf Buffer containing abort headers (connection ID, etc.). If + * NULL, a default abort request is sent. If success returned, + * the function has taken the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mas_abort(struct bt_map_mce_mas *mce_mas, + struct net_buf *buf); + +/** @brief Send set folder request + * + * Navigates the folder hierarchy on the MAS. Once set_folder response + * received, the set_folder callback @ref bt_map_mce_mas_cb::set_folder will + * be called. + * + * @param mce_mas MAS client instance. + * @param flags Navigation flags (see @ref bt_map_set_folder_flags). + * @param buf Buffer containing folder name (for DOWN) and headers. Must + * include connection ID. If success returned, the function + * has taken the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mas_set_folder(struct bt_map_mce_mas *mce_mas, + uint8_t flags, struct net_buf *buf); + +/** @brief Send set notification registration request + * + * Enables or disables event notifications from the MAS. Once set_ntf_reg + * response received, the set_ntf_reg callback @ref + * bt_map_mce_mas_cb::set_ntf_reg will be called. + * + * @param mce_mas MAS client instance. + * @param final True if this is the final packet, false if more data + * follows. + * @param buf Buffer containing notification status and headers. Must + * include connection ID, type header, and application + * parameters. If success returned, the function has taken the + * ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mas_set_ntf_reg(struct bt_map_mce_mas *mce_mas, + bool final, struct net_buf *buf); + +/** @brief Send get folder listing request + * + * Retrieves list of subfolders in the current folder. Once + * get_folder_listing response received, the get_folder_listing callback + * @ref + * bt_map_mce_mas_cb::get_folder_listing will be called (possibly multiple + * times). + * + * @param mce_mas MAS client instance. + * @param final True if this is the final packet, false if more data + * follows. + * @param buf Buffer containing filter parameters and headers. Must + * include connection ID and type header for initial request. + * If success returned, the function has taken the ownership + * of + * @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mas_get_folder_listing( + struct bt_map_mce_mas *mce_mas, bool final, struct net_buf *buf); + +/** @brief Send get message listing request + * + * Retrieves list of messages in the current folder. Once get_msg_listing + * response received, the get_msg_listing callback @ref + * bt_map_mce_mas_cb::get_msg_listing will be called (possibly multiple + * times). + * + * @param mce_mas MAS client instance. + * @param final True if this is the final packet, false if more data + * follows. + * @param buf Buffer containing folder name, filter parameters, and + * headers. Must include connection ID, type header, and name + * header for initial request. If success returned, the + * function has taken the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mas_get_msg_listing( + struct bt_map_mce_mas *mce_mas, bool final, struct net_buf *buf); + +/** @brief Send get message request + * + * Retrieves a specific message by handle. Once get_msg response received, + * the get_msg callback @ref bt_map_mce_mas_cb::get_msg will be called + * (possibly multiple times). + * + * @param mce_mas MAS client instance. + * @param final True if this is the final packet, false if more data + * follows. + * @param buf Buffer containing message handle, parameters, and headers. + * Must include connection ID, type header, and name header + * for initial request. If success returned, the function has + * taken the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mas_get_msg(struct bt_map_mce_mas *mce_mas, + bool final, struct net_buf *buf); + +/** @brief Send set message status request + * + * Modifies the status of a message (read/deleted/etc). Once set_msg_status + * response received, the set_msg_status callback @ref + * bt_map_mce_mas_cb::set_msg_status will be called. + * + * @param mce_mas MAS client instance. + * @param final True if this is the final packet, false if more data + * follows. + * @param buf Buffer containing message handle, status parameters, and + * headers. Must include connection ID, type header, name + * header, and application parameters. If success returned, + * the function has taken the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mas_set_msg_status(struct bt_map_mce_mas *mce_mas, bool final, + struct net_buf *buf); + +/** @brief Send push message request + * + * Uploads a new message to the server. Once push_msg response received, the + * push_msg callback @ref bt_map_mce_mas_cb::push_msg will be called + * (possibly multiple times). + * + * @param mce_mas MAS client instance. + * @param final True if this is the last packet, false if more data + * follows. + * @param buf Buffer containing message data (bMessage format) and + * headers. Must include connection ID, type header, name + * header, and application parameters for initial request. + * Must include end-of-body header when final is true. If + * success returned, the function has taken the ownership of + * @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mas_push_msg(struct bt_map_mce_mas *mce_mas, + bool final, struct net_buf *buf); + +/** @brief Send update inbox request + * + * Requests the server to check for new messages. Once update_inbox response + * received, the update_inbox callback @ref bt_map_mce_mas_cb::update_inbox + * will be called. + * + * @param mce_mas MAS client instance. + * @param final True if this is the final packet, false if more data + * follows. + * @param buf Buffer containing headers. Must include connection ID and + * type header. If success returned, the function has taken + * the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mas_update_inbox(struct bt_map_mce_mas *mce_mas, + bool final, struct net_buf *buf); + +/** @brief Send get MAS instance info request + * + * Retrieves information about the MAS instance. Once get_mas_inst_info + * response received, the get_mas_inst_info callback + * @ref + * bt_map_mce_mas_cb::get_mas_inst_info will be called (possibly multiple + * times). + * + * @param mce_mas MAS client instance. + * @param final True if this is the final packet, false if more data + * follows. + * @param buf Buffer containing instance ID and headers. Must include + * connection ID, type header, and application parameters for + * initial request. If success returned, the function has + * taken the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mas_get_mas_inst_info( + struct bt_map_mce_mas *mce_mas, bool final, struct net_buf *buf); + +/** @brief Send set owner status request + * + * Sets the owner's presence and chat state. Once set_owner_status response + * received, the set_owner_status callback + * @ref + * bt_map_mce_mas_cb::set_owner_status will be called. + * + * @param mce_mas MAS client instance. + * @param final True if this is the final packet, false if more data + * follows. + * @param buf Buffer containing owner status data and headers. Must + * include connection ID, type header, and application + * parameters. If success returned, the function has taken the + * ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mas_set_owner_status( + struct bt_map_mce_mas *mce_mas, bool final, struct net_buf *buf); + +/** @brief Send get owner status request + * + * Retrieves the owner's presence and chat state. Once get_owner_status + * response received, the get_owner_status callback + * @ref + * bt_map_mce_mas_cb::get_owner_status will be called (possibly multiple + * times). + * + * @param mce_mas MAS client instance. + * @param final True if this is the final packet, false if more data + * follows. + * @param buf Buffer containing headers. Must include connection ID and + * type header for initial request. If success returned, the + * function has taken the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mas_get_owner_status( + struct bt_map_mce_mas *mce_mas, bool final, struct net_buf *buf); + +/** @brief Send get conversation listing request + * + * Retrieves list of conversations. Once get_convo_listing response received, + * the get_convo_listing callback + * @ref + * bt_map_mce_mas_cb::get_convo_listing will be called (possibly multiple + * times). + * + * @param mce_mas MAS client instance. + * @param final True if this is the final packet, false if more data + * follows. + * @param buf Buffer containing filter parameters and headers. Must + * include connection ID and type header for initial request. + * If success returned, the function has taken the ownership + * of + * @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mas_get_convo_listing( + struct bt_map_mce_mas *mce_mas, bool final, struct net_buf *buf); + +/** @brief Send set notification filter request + * + * Configures which event types to receive notifications for. Once + * set_ntf_filter response received, the set_ntf_filter callback @ref + * bt_map_mce_mas_cb::set_ntf_filter will be called. + * + * @param mce_mas MAS client instance. + * @param final True if this is the final packet, false if more data + * follows. + * @param buf Buffer containing filter mask and headers. Must include + * connection ID, type header, and application parameters. If + * success returned, the function has taken the ownership of + * @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mas_set_ntf_filter(struct bt_map_mce_mas *mce_mas, bool final, + struct net_buf *buf); + +/** @} */ /* end of bt_map_mce_mas */ + +/** + * @brief Messaging Client Equipment(MCE) OBEX Server + * @defgroup bt_map_mce_mns Messaging Client Equipment(MCE) OBEX Server + * @ingroup bt_map + * @{ + */ + +/** @brief Forward declaration of MAP Client MNS structure */ + +struct bt_map_mce_mns; + +/** @brief MAP Client MNS RFCOMM server + * + * This structure represents an RFCOMM server for MAP Client MNS. + */ + +struct bt_map_mce_mns_rfcomm_server +{ + /** GOEP RFCOMM transport server - underlying transport layer */ + + struct bt_goep_transport_rfcomm_server server; + /** @brief Accept callback for incoming connections + * + * Called when a remote MSE attempts to connect to the MNS server. + * + * @param conn Bluetooth connection object. + * @param server RFCOMM server instance that received the connection. + * @param mce_mns Output parameter to store the allocated MNS instance. + * + * @return 0 on success, negative error code to reject the connection. + */ + +int (*accept)(struct bt_conn *conn, + struct bt_map_mce_mns_rfcomm_server *server, + struct bt_map_mce_mns **mce_mns); +}; + +/** @brief MAP Client MNS L2CAP server + * + * This structure represents an L2CAP server for MAP Client MNS. + */ + +struct bt_map_mce_mns_l2cap_server +{ + /** GOEP L2CAP transport server - underlying transport layer */ + + struct bt_goep_transport_l2cap_server server; + /** @brief Accept callback for incoming connections + * + * Called when a remote MSE attempts to connect to the MNS server. + * + * @param conn Bluetooth connection object. + * @param server L2CAP server instance that received the connection. + * @param mce_mns Output parameter to store the allocated MNS instance. + * + * @return 0 on success, negative error code to reject the connection. + */ + +int (*accept)(struct bt_conn *conn, + struct bt_map_mce_mns_l2cap_server *server, + struct bt_map_mce_mns **mce_mns); +}; + +/** @brief MAP Client MNS callbacks + * + * Callbacks for MAP Client MNS events. + */ + +struct bt_map_mce_mns_cb +{ + /** @brief RFCOMM transport connected callback + * + * Called when the underlying transport (RFCOMM) connection is established. + * OBEX connection has not yet been negotiated at this point. + * + * @param conn Bluetooth connection object. + * @param mce_mns MNS server instance. + */ + + void (*rfcomm_connected)(struct bt_conn *conn, + struct bt_map_mce_mns *mce_mns); + + /** @brief RFCOMM transport disconnected callback + * + * Called when the underlying transport (RFCOMM) connection is closed. + * + * @param mce_mns MNS server instance. + */ + + void (*rfcomm_disconnected)(struct bt_map_mce_mns *mce_mns); + + /** @brief L2CAP transport connected callback + * + * Called when the underlying transport (L2CAP) connection is established. + * OBEX connection has not yet been negotiated at this point. + * + * @param conn Bluetooth connection object. + * @param mce_mns MNS server instance. + */ + + void (*l2cap_connected)(struct bt_conn *conn, + struct bt_map_mce_mns *mce_mns); + + /** @brief L2CAP transport disconnected callback + * + * Called when the underlying transport (L2CAP) connection is closed. + * + * @param mce_mns MNS server instance. + */ + + void (*l2cap_disconnected)(struct bt_map_mce_mns *mce_mns); + + /** @brief Connect callback + * + * Called when OBEX CONNECT request is received from remote MSE. + * Application should respond using bt_map_mce_mns_connect(). + * + * @param mce_mns MNS server instance. + * @param version OBEX protocol version requested by client. + * @param mopl Maximum OBEX packet length requested by client. + * @param buf Buffer containing request headers (target UUID, etc.). + */ + +void (*connect)(struct bt_map_mce_mns *mce_mns, + uint8_t version, + uint16_t mopl, + struct net_buf *buf); + + /** @brief Disconnect callback + * + * Called when OBEX DISCONNECT request is received. Application should + * respond using bt_map_mce_mns_disconnect(). + * + * @param mce_mns MNS server instance. + * @param buf Buffer containing request headers. + */ + + void (*disconnect)(struct bt_map_mce_mns *mce_mns, struct net_buf *buf); + + /** @brief Abort callback + * + * Called when OBEX ABORT request is received. Application should respond + * using bt_map_mce_mns_abort(). + * + * @param mce_mns MNS server instance. + * @param buf Buffer containing request headers. + */ + + void (*abort)(struct bt_map_mce_mns *mce_mns, struct net_buf *buf); + + /** @brief Send event callback + * + * Called when SendEvent request is received from remote MSE. May be called + * multiple times if request is fragmented. Application should respond + * using bt_map_mce_mns_send_event(). + * + * @param mce_mns MNS server instance. + * @param final True if this is the final packet, false if more data + * follows. + * @param buf Buffer containing event report XML data and headers. + */ + + void (*send_event)(struct bt_map_mce_mns *mce_mns, bool final, + struct net_buf *buf); +}; + +/** @brief MAP Client MNS instance structure + * + * Represents a server that receives event notifications from a Message + * Notification Server. The MCE acts as a server for the MNS connection. + */ + +struct bt_map_mce_mns +{ + /** @brief Underlying GOEP transport instance */ + + struct bt_goep_transport goep_transport; + + /** @brief Underlying GOEP instance */ + + struct bt_goep goep; + + /** @internal Callbacks */ + + const struct bt_map_mce_mns_cb *_cb; + + /** @internal Transport state (atomic) */ + + atomic_t _transport_state; + + /** @internal Connection ID */ + + uint32_t _conn_id; + + /** @internal Underlying OBEX server */ + + struct bt_obex_server _server; + + /** @internal Server state (atomic) */ + + atomic_t _state; + + /** @internal Pending request callback */ + + void (*_req_cb)(struct bt_map_mce_mns *mce_mns, bool final, + struct net_buf *buf); + + /** @internal Operation type */ + + const char *_optype; + + /** @internal Operation code */ + + uint8_t _opcode; +}; + +/** @brief Register MAP Client MNS server + * + * Associates a callback table with the server object and registers it with + * the underlying OBEX server. + * + * @param mce_mns MNS server instance. + * @param cb Pointer to callback structure (must remain valid for the + * lifetime of the server). + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mns_register(struct bt_map_mce_mns *mce_mns, + const struct bt_map_mce_mns_cb *cb); + +/** @brief Register MAP Client MNS RFCOMM server + * + * Registers an RFCOMM server to accept incoming MNS connections. The server + * channel is allocated automatically or can be specified. + * + * @param server RFCOMM server structure with accept callback configured. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mns_rfcomm_register( + struct bt_map_mce_mns_rfcomm_server *server); + +/** @brief Disconnect MAP Client MNS over RFCOMM + * + * Closes the RFCOMM transport connection. On success, the + * rfcomm_disconnected callback @ref bt_map_mce_mns_cb::rfcomm_disconnected + * will be called. + * + * @param mce_mns MNS server instance. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mns_rfcomm_disconnect(struct bt_map_mce_mns *mce_mns); + +/** @brief Register MAP Client MNS L2CAP server + * + * Registers an L2CAP server to accept incoming MNS connections. The PSM is + * allocated automatically or can be specified. + * + * @param server L2CAP server structure with accept callback configured. + * + * @return 0 on success, negative error code on failure. + */ + +int +bt_map_mce_mns_l2cap_register(struct bt_map_mce_mns_l2cap_server *server); + +/** @brief Disconnect MAP Client MNS over L2CAP + * + * Closes the L2CAP transport connection. On success, the l2cap_disconnected + * callback @ref bt_map_mce_mns_cb::l2cap_disconnected will be called. + * + * @param mce_mns MNS server instance. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mns_l2cap_disconnect(struct bt_map_mce_mns *mce_mns); + +/** @brief Create PDU for MAP Client MNS + * + * Allocates a buffer for building MAP response packets. The buffer is sized + * appropriately for the negotiated MTU. + * + * @param mce_mns MNS server instance. + * @param pool Buffer pool to allocate from (NULL for default pool). + * + * @return Allocated buffer, or NULL on failure. + */ + +struct net_buf *bt_map_mce_mns_create_pdu(struct bt_map_mce_mns *mce_mns, + struct net_buf_pool *pool); + +/** @brief Send OBEX connect response + * + * Responds to an OBEX CONNECT request from the remote MSE. Called from the + * connect callback @ref bt_map_mce_mns_cb::connect. + * + * @param mce_mns MNS server instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_SUCCESS to accept). + * @param buf Buffer containing response headers (WHO, connection ID, + * etc.). If NULL, a default response is sent. If success + * returned, the function has taken the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mns_connect(struct bt_map_mce_mns *mce_mns, + uint8_t rsp_code, struct net_buf *buf); + +/** @brief Send OBEX disconnect response + * + * Responds to an OBEX DISCONNECT request from the remote MSE. Called from + * the disconnect callback @ref bt_map_mce_mns_cb::disconnect. + * + * @param mce_mns MNS server instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_SUCCESS to accept). + * @param buf Buffer containing response headers. If NULL, a default + * response is sent. If success returned, the function has + * taken the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mns_disconnect( + struct bt_map_mce_mns *mce_mns, uint8_t rsp_code, struct net_buf *buf); + +/** @brief Send OBEX abort response + * + * Responds to an OBEX ABORT request from the remote MSE. Called from the + * abort callback @ref bt_map_mce_mns_cb::abort. + * + * @param mce_mns MNS server instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_SUCCESS to accept). + * @param buf Buffer containing response headers. If NULL, a default + * response is sent. If success returned, the function has + * taken the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mns_abort(struct bt_map_mce_mns *mce_mns, + uint8_t rsp_code, struct net_buf *buf); + +/** @brief Send event response + * + * Responds to a SendEvent request from the remote MSE. Called from the + * send_event callback @ref bt_map_mce_mns_cb::send_event. + * + * @param mce_mns MNS server instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_CONTINUE for partial, + * @ref BT_OBEX_RSP_CODE_SUCCESS for complete). + * @param buf Buffer containing response headers. If NULL, a default response + * is sent. If success returned, the function has taken the + * ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mce_mns_send_event( + struct bt_map_mce_mns *mce_mns, uint8_t rsp_code, struct net_buf *buf); + +/** @} */ /* end of bt_map_mce_mns */ + +/** + * @brief Messaging Server Equipment(MSE) OBEX Server + * @defgroup bt_map_mse_mas Messaging Server Equipment(MSE) OBEX Server + * @ingroup bt_map + * @{ + */ + +/** @brief Forward declaration of MAP Server MAS structure */ + +struct bt_map_mse_mas; + +/** @brief MAP Server MAS RFCOMM server + * + * This structure represents an RFCOMM server for MAP Server MAS. + */ + +struct bt_map_mse_mas_rfcomm_server +{ + /** GOEP RFCOMM transport server - underlying transport layer */ + + struct bt_goep_transport_rfcomm_server server; + + /** Accept callback for incoming connections + * + * Called when a remote MCE attempts to connect to the MAS server. + * + * @param conn Bluetooth connection object. + * @param server RFCOMM server instance that received the connection. + * @param mse_mas Output parameter to store the allocated MAS instance. + * + * @return 0 on success, negative error code to reject the connection. + */ + +int (*accept)(struct bt_conn *conn, + struct bt_map_mse_mas_rfcomm_server *server, + struct bt_map_mse_mas **mse_mas); +}; + +/** @brief MAP Server MAS L2CAP server + * + * This structure represents an L2CAP server for MAP Server MAS. + */ + +struct bt_map_mse_mas_l2cap_server +{ + /** GOEP L2CAP transport server - underlying transport layer */ + + struct bt_goep_transport_l2cap_server server; + + /** Accept callback for incoming connections + * + * Called when a remote MCE attempts to connect to the MAS server. + * + * @param conn Bluetooth connection object. + * @param server L2CAP server instance that received the connection. + * @param mse_mas Output parameter to store the allocated MAS instance. + * + * @return 0 on success, negative error code to reject the connection. + */ + +int (*accept)(struct bt_conn *conn, + struct bt_map_mse_mas_l2cap_server *server, + struct bt_map_mse_mas **mse_mas); +}; + +/** @brief MAP Server MAS callbacks + * + * Callbacks for MAP Server MAS events. + */ + +struct bt_map_mse_mas_cb +{ + /** @brief RFCOMM transport connected callback + * + * Called when the underlying transport (RFCOMM) connection is established. + * OBEX connection has not yet been negotiated at this point. + * + * @param conn Bluetooth connection object. + * @param mse_mas MAS server instance. + */ + + void (*rfcomm_connected)(struct bt_conn *conn, + struct bt_map_mse_mas *mse_mas); + + /** @brief RFCOMM transport disconnected callback + * + * Called when the underlying transport (RFCOMM) connection is closed. + * + * @param mse_mas MAS server instance. + */ + + void (*rfcomm_disconnected)(struct bt_map_mse_mas *mse_mas); + + /** @brief L2CAP transport connected callback + * + * Called when the underlying transport (L2CAP) connection is established. + * OBEX connection has not yet been negotiated at this point. + * + * @param conn Bluetooth connection object. + * @param mse_mas MAS server instance. + */ + + void (*l2cap_connected)(struct bt_conn *conn, + struct bt_map_mse_mas *mse_mas); + + /** @brief L2CAP transport disconnected callback + * + * Called when the underlying transport (L2CAP) connection is closed. + * + * @param mse_mas MAS server instance. + */ + + void (*l2cap_disconnected)(struct bt_map_mse_mas *mse_mas); + + /** @brief Connect callback + * + * Called when OBEX CONNECT request is received from remote MCE. + * Application should respond using bt_map_mse_mas_connect(). + * + * @param mse_mas MAS server instance. + * @param version OBEX protocol version requested by client. + * @param mopl Maximum OBEX packet length requested by client. + * @param buf Buffer containing request headers (target UUID, supported + * features, etc.). + */ + +void (*connect)(struct bt_map_mse_mas *mse_mas, + uint8_t version, + uint16_t mopl, + struct net_buf *buf); + + /** @brief Disconnect callback + * + * Called when OBEX DISCONNECT request is received. Application should + * respond using bt_map_mse_mas_disconnect(). + * + * @param mse_mas MAS server instance. + * @param buf Buffer containing request headers. + */ + + void (*disconnect)(struct bt_map_mse_mas *mse_mas, struct net_buf *buf); + + /** @brief Abort callback + * + * Called when OBEX ABORT request is received. Application should respond + * using bt_map_mse_mas_abort(). + * + * @param mse_mas MAS server instance. + * @param buf Buffer containing request headers. + */ + + void (*abort)(struct bt_map_mse_mas *mse_mas, struct net_buf *buf); + + /** @brief Set notification registration callback + * + * Called when SetNotificationRegistration request is received. May be + * called multiple times if request is fragmented. Application should + * respond using bt_map_mse_mas_set_ntf_reg(). + * + * @param mse_mas MAS server instance. + * @param final True if this is the final packet, false if more data + * follows. + * @param buf Buffer containing notification status and headers. + */ + + void (*set_ntf_reg)(struct bt_map_mse_mas *mse_mas, bool final, + struct net_buf *buf); + + /** @brief Set folder callback + * + * Called when SetFolder request is received. Application should respond + * using bt_map_mse_mas_set_folder(). + * + * @param mse_mas MAS server instance. + * @param flags Navigation flags (see @ref bt_map_set_folder_flags). + * @param buf Buffer containing folder name (for DOWN) and headers. + */ + + void (*set_folder)(struct bt_map_mse_mas *mse_mas, uint8_t flags, + struct net_buf *buf); + + /** @brief Get folder listing callback + * + * Called when GetFolderListing request is received. May be called multiple + * times if client requests continuation. Application should respond using + * bt_map_mse_mas_get_folder_listing(). + * + * @param mse_mas MAS server instance. + * @param final True if this is the final packet, false if more data + * follows. + * @param buf Buffer containing filter parameters and headers. + */ + + void (*get_folder_listing)(struct bt_map_mse_mas *mse_mas, bool final, + struct net_buf *buf); + + /** @brief Get message listing callback + * + * Called when GetMessageListing request is received. May be called + * multiple times if client requests continuation. Application should + * respond using bt_map_mse_mas_get_msg_listing(). + * + * @param mse_mas MAS server instance. + * @param final True if this is the final packet, false if more data + * follows. + * @param buf Buffer containing folder name, filter parameters, and + * headers. + */ + + void (*get_msg_listing)(struct bt_map_mse_mas *mse_mas, bool final, + struct net_buf *buf); + + /** @brief Get message callback + * + * Called when GetMessage request is received. May be called multiple times + * if client requests continuation. Application should respond using + * bt_map_mse_mas_get_msg(). + * + * @param mse_mas MAS server instance. + * @param final True if this is the final packet, false if more data + * follows. + * @param buf Buffer containing message handle, parameters, and + * headers. + */ + + void (*get_msg)(struct bt_map_mse_mas *mse_mas, bool final, + struct net_buf *buf); + + /** @brief Set message status callback + * + * Called when SetMessageStatus request is received. May be called multiple + * times if request is fragmented. Application should respond using + * bt_map_mse_mas_set_msg_status(). + * + * @param mse_mas MAS server instance. + * @param final True if this is the final packet, false if more data + * follows. + * @param buf Buffer containing message handle, status parameters, and + * headers. + */ + + void (*set_msg_status)(struct bt_map_mse_mas *mse_mas, bool final, + struct net_buf *buf); + + /** @brief Push message callback + * + * Called when PushMessage request is received. May be called multiple + * times if request is fragmented. Application should respond using + * bt_map_mse_mas_push_msg(). + * + * @param mse_mas MAS server instance. + * @param final True if this is the final packet, false if more data + * follows. + * @param buf Buffer containing message data (bMessage format) and + * headers. + */ + + void (*push_msg)(struct bt_map_mse_mas *mse_mas, bool final, + struct net_buf *buf); + + /** @brief Update inbox callback + * + * Called when UpdateInbox request is received. May be called multiple + * times if request is fragmented. Application should respond using + * bt_map_mse_mas_update_inbox(). + * + * @param mse_mas MAS server instance. + * @param final True if this is the final packet, false if more data + * follows. + * @param buf Buffer containing headers. + */ + + void (*update_inbox)(struct bt_map_mse_mas *mse_mas, bool final, + struct net_buf *buf); + + /** @brief Get MAS instance info callback + * + * Called when GetMASInstanceInformation request is received. May be called + * multiple times if client requests continuation. Application should + * respond using bt_map_mse_mas_get_mas_inst_info(). + * + * @param mse_mas MAS server instance. + * @param final True if this is the final packet, false if more data + * follows. + * @param buf Buffer containing instance ID and headers. + */ + + void (*get_mas_inst_info)(struct bt_map_mse_mas *mse_mas, bool final, + struct net_buf *buf); + + /** @brief Set owner status callback + * + * Called when SetOwnerStatus request is received. May be called multiple + * times if request is fragmented. Application should respond using + * bt_map_mse_mas_set_owner_status(). + * + * @param mse_mas MAS server instance. + * @param final True if this is the final packet, false if more data + * follows. + * @param buf Buffer containing owner status data and headers. + */ + + void (*set_owner_status)(struct bt_map_mse_mas *mse_mas, bool final, + struct net_buf *buf); + + /** @brief Get owner status callback + * + * Called when GetOwnerStatus request is received. May be called multiple + * times if client requests continuation. Application should respond using + * bt_map_mse_mas_get_owner_status(). + * + * @param mse_mas MAS server instance. + * @param final True if this is the final packet, false if more data + * follows. + * @param buf Buffer containing headers. + */ + + void (*get_owner_status)(struct bt_map_mse_mas *mse_mas, bool final, + struct net_buf *buf); + + /** @brief Get conversation listing callback + * + * Called when GetConversationListing request is received. May be called + * multiple times if client requests continuation. Application should + * respond using bt_map_mse_mas_get_convo_listing(). + * + * @param mse_mas MAS server instance. + * @param final True if this is the final packet, false if more data + * follows. + * @param buf Buffer containing filter parameters and headers. + */ + + void (*get_convo_listing)(struct bt_map_mse_mas *mse_mas, bool final, + struct net_buf *buf); + + /** @brief Set notification filter callback + * + * Called when SetNotificationFilter request is received. May be called + * multiple times if request is fragmented. Application should respond + * using bt_map_mse_mas_set_ntf_filter(). + * + * @param mse_mas MAS server instance. + * @param final True if this is the final packet, false if more data + * follows. + * @param buf Buffer containing filter mask and headers. + */ + + void (*set_ntf_filter)(struct bt_map_mse_mas *mse_mas, bool final, + struct net_buf *buf); +}; + +/** @brief MAP Server MAS instance structure + * + * Represents a server that handles message access requests from clients. The + * MSE acts as a server for the MAS connection. + */ + +struct bt_map_mse_mas +{ + /** @brief Underlying GOEP transport instance */ + + struct bt_goep_transport goep_transport; + + /** @brief Underlying GOEP instance */ + + struct bt_goep goep; + + /** @internal Callbacks */ + + const struct bt_map_mse_mas_cb *_cb; + + /** @internal Transport state (atomic) */ + + atomic_t _transport_state; + + /** @internal Connection ID */ + + uint32_t _conn_id; + + /** @internal Underlying OBEX server */ + + struct bt_obex_server _server; + + /** @internal Server state (atomic) */ + + atomic_t _state; + + /** @internal Pending request callback */ + + void (*_req_cb)(struct bt_map_mse_mas *mse_mas, bool final, + struct net_buf *buf); + + /** @internal Operation type */ + + const char *_optype; + + /** @internal Operation code */ + + uint8_t _opcode; +}; + +/** @brief Register MAP Server MAS server + * + * Associates a callback table with the server object and registers it with + * the underlying OBEX server. + * + * @param mse_mas MAS server instance. + * @param cb Pointer to callback structure (must remain valid for the + * lifetime of the server). + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mas_register(struct bt_map_mse_mas *mse_mas, + const struct bt_map_mse_mas_cb *cb); + +/** @brief Register MAP Server MAS RFCOMM server + * + * Registers an RFCOMM server to accept incoming MAS connections. The server + * channel is allocated automatically or can be specified. + * + * @param server RFCOMM server structure with accept callback configured. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mas_rfcomm_register( + struct bt_map_mse_mas_rfcomm_server *server); + +/** @brief Disconnect MAP Server MAS over RFCOMM + * + * Closes the RFCOMM transport connection. On success, the + * rfcomm_disconnected callback @ref bt_map_mse_mas_cb::rfcomm_disconnected + * will be called. + * + * @param mse_mas MAS server instance. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mas_rfcomm_disconnect(struct bt_map_mse_mas *mse_mas); + +/** @brief Register MAP Server MAS L2CAP server + * + * Registers an L2CAP server to accept incoming MAS connections. The PSM is + * allocated automatically or can be specified. + * + * @param server L2CAP server structure with accept callback configured. + * + * @return 0 on success, negative error code on failure. + */ + +int +bt_map_mse_mas_l2cap_register(struct bt_map_mse_mas_l2cap_server *server); + +/** @brief Disconnect MAP Server MAS over L2CAP + * + * Closes the L2CAP transport connection. On success, the l2cap_disconnected + * callback @ref bt_map_mse_mas_cb::l2cap_disconnected will be called. + * + * @param mse_mas MAS server instance. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mas_l2cap_disconnect(struct bt_map_mse_mas *mse_mas); + +/** @brief Create PDU for MAP Server MAS + * + * Allocates a buffer for building MAP response packets. The buffer is sized + * appropriately for the negotiated MTU. + * + * @param mse_mas MAS server instance. + * @param pool Buffer pool to allocate from (NULL for default pool). + * + * @return Allocated buffer, or NULL on allocation failure. + */ + +struct net_buf *bt_map_mse_mas_create_pdu(struct bt_map_mse_mas *mse_mas, + struct net_buf_pool *pool); + +/** @brief Send OBEX connect response + * + * Responds to an OBEX CONNECT request from the remote MCE. Called from the + * connect callback @ref bt_map_mse_mas_cb::connect. + * + * @param mse_mas MAS server instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_SUCCESS to accept). + * @param buf Buffer containing response headers (WHO, connection ID, + * etc.). If NULL, a default response is sent. If success + * returned, the function has taken the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mas_connect(struct bt_map_mse_mas *mse_mas, + uint8_t rsp_code, struct net_buf *buf); + +/** @brief Send OBEX disconnect response + * + * Responds to an OBEX DISCONNECT request from the remote MCE. Called from + * the disconnect callback @ref bt_map_mse_mas_cb::disconnect. + * + * @param mse_mas MAS server instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_SUCCESS to accept). + * @param buf Buffer containing response headers. If success returned, + * the function has taken the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mas_disconnect( + struct bt_map_mse_mas *mse_mas, uint8_t rsp_code, struct net_buf *buf); + +/** @brief Send OBEX abort response + * + * Responds to an OBEX ABORT request from the remote MCE. Called from the + * abort callback @ref bt_map_mse_mas_cb::abort. + * + * @param mse_mas MAS server instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_SUCCESS to accept). + * @param buf Buffer containing response headers. If NULL, a default + * response is sent. If success returned, the function has + * taken the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mas_abort(struct bt_map_mse_mas *mse_mas, + uint8_t rsp_code, struct net_buf *buf); + +/** @brief Send set folder response + * + * Responds to a SetPath request from the remote MCE. Called from the + * set_folder callback @ref bt_map_mse_mas_cb::set_folder. + * + * @param mse_mas MAS server instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_SUCCESS on success). + * @param buf Buffer containing response headers. If NULL, a default + * response is sent. If success returned, the function has + * taken the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mas_set_folder( + struct bt_map_mse_mas *mse_mas, uint8_t rsp_code, struct net_buf *buf); + +/** @brief Send set notification registration response + * + * Responds to a SetNotificationRegistration request from the remote MCE. + * Called from the set_ntf_reg callback @ref bt_map_mse_mas_cb::set_ntf_reg. + * + * @param mse_mas MAS server instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_SUCCESS on success). + * @param buf Buffer containing response headers. If NULL, a default + * response is sent. If success returned, the function has + * taken the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mas_set_ntf_reg( + struct bt_map_mse_mas *mse_mas, uint8_t rsp_code, struct net_buf *buf); + +/** @brief Send get folder listing response + * + * Responds to a GetFolderListing request from the remote MCE. May be called + * multiple times to send fragmented response. Called from the + * get_folder_listing callback @ref bt_map_mse_mas_cb::get_folder_listing. + * + * @param mse_mas MAS server instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_CONTINUE for partial, + * @ref BT_OBEX_RSP_CODE_SUCCESS for complete). + * @param buf Buffer containing folder listing XML data and headers. If + * success returned, the function has taken the ownership of @p + * buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mas_get_folder_listing( + struct bt_map_mse_mas *mse_mas, uint8_t rsp_code, struct net_buf *buf); + +/** @brief Send get message listing response + * + * Responds to a GetMessageListing request from the remote MCE. May be called + * multiple times to send fragmented response. Called from the + * get_msg_listing callback @ref bt_map_mse_mas_cb::get_msg_listing. + * + * @param mse_mas MAS server instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_CONTINUE for partial, + * @ref BT_OBEX_RSP_CODE_SUCCESS for complete). + * @param buf Buffer containing message listing XML data and headers. If + * success returned, the function has taken the ownership of @p + * buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mas_get_msg_listing( + struct bt_map_mse_mas *mse_mas, uint8_t rsp_code, struct net_buf *buf); + +/** @brief Send get message response + * + * Responds to a GetMessage request from the remote MCE. May be called + * multiple times to send fragmented response. Called from the get_msg + * callback @ref bt_map_mse_mas_cb::get_msg. + * + * @param mse_mas MAS server instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_CONTINUE for partial, + * @ref BT_OBEX_RSP_CODE_SUCCESS for complete). + * @param buf Buffer containing message bMessage data and headers. If success + * returned, the function has taken the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mas_get_msg(struct bt_map_mse_mas *mse_mas, + uint8_t rsp_code, struct net_buf *buf); + +/** @brief Send set message status response + * + * Responds to a SetMessageStatus request from the remote MCE. Called from + * the set_msg_status callback @ref bt_map_mse_mas_cb::set_msg_status. + * + * @param mse_mas MAS server instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_SUCCESS on success). + * @param buf Buffer containing response headers. If NULL, a default + * response is sent. If success returned, the function has + * taken the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mas_set_msg_status( + struct bt_map_mse_mas *mse_mas, uint8_t rsp_code, struct net_buf *buf); + +/** @brief Send push message response + * + * Responds to a PushMessage request from the remote MCE. May be called + * multiple times if request is fragmented. Called from the push_msg callback + * @ref bt_map_mse_mas_cb::push_msg. + * + * @param mse_mas MAS server instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_CONTINUE for partial, + * @ref BT_OBEX_RSP_CODE_SUCCESS for complete). + * @param buf Buffer containing message handle (on success) and headers. If + * success returned, the function has taken the ownership of @p + * buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mas_push_msg(struct bt_map_mse_mas *mse_mas, uint8_t rsp_code, + struct net_buf *buf); + +/** @brief Send update inbox response + * + * Responds to an UpdateInbox request from the remote MCE. Called from the + * update_inbox callback @ref bt_map_mse_mas_cb::update_inbox. + * + * @param mse_mas MAS server instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_SUCCESS on success). + * @param buf Buffer containing response headers. If NULL, a default + * response is sent. If success returned, the function has + * taken the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mas_update_inbox( + struct bt_map_mse_mas *mse_mas, uint8_t rsp_code, struct net_buf *buf); + +/** @brief Send get MAS instance info response + * + * Responds to a GetMASInstanceInformation request from the remote MCE. May + * be called multiple times to send fragmented response. Called from the + * get_mas_inst_info callback @ref bt_map_mse_mas_cb::get_mas_inst_info. + * + * @param mse_mas MAS server instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_CONTINUE for partial, + * @ref BT_OBEX_RSP_CODE_SUCCESS for complete). + * @param buf Buffer containing instance information XML data and headers. If + * success returned, the function has taken the ownership of @p + * buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mas_get_mas_inst_info( + struct bt_map_mse_mas *mse_mas, uint8_t rsp_code, struct net_buf *buf); + +/** @brief Send set owner status response + * + * Responds to a SetOwnerStatus request from the remote MCE. Called from the + * set_owner_status callback @ref bt_map_mse_mas_cb::set_owner_status. + * + * @param mse_mas MAS server instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_SUCCESS on success). + * @param buf Buffer containing response headers. If NULL, a default + * response is sent. If success returned, the function has + * taken the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mas_set_owner_status( + struct bt_map_mse_mas *mse_mas, uint8_t rsp_code, struct net_buf *buf); + +/** @brief Send get owner status response + * + * Responds to a GetOwnerStatus request from the remote MCE. May be called + * multiple times to send fragmented response. Called from the + * get_owner_status callback @ref bt_map_mse_mas_cb::get_owner_status. + * + * @param mse_mas MAS server instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_CONTINUE for partial, + * @ref BT_OBEX_RSP_CODE_SUCCESS for complete). + * @param buf Buffer containing owner status data and headers. If success + * returned, the function has taken the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mas_get_owner_status( + struct bt_map_mse_mas *mse_mas, uint8_t rsp_code, struct net_buf *buf); + +/** @brief Send get conversation listing response + * + * Responds to a GetConversationListing request from the remote MCE. May be + * called multiple times to send fragmented response. Called from the + * get_convo_listing callback @ref bt_map_mse_mas_cb::get_convo_listing. + * + * @param mse_mas MAS server instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_CONTINUE for partial, + * @ref BT_OBEX_RSP_CODE_SUCCESS for complete). + * @param buf Buffer containing conversation listing XML data and headers. If + * success returned, the function has taken the ownership of @p + * buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mas_get_convo_listing( + struct bt_map_mse_mas *mse_mas, uint8_t rsp_code, struct net_buf *buf); + +/** @brief Send set notification filter response + * + * Responds to a SetNotificationFilter request from the remote MCE. Called + * from the set_ntf_filter callback @ref bt_map_mse_mas_cb::set_ntf_filter. + * + * @param mse_mas MAS server instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_SUCCESS on success). + * @param buf Buffer containing response headers. If NULL, a default + * response is sent. If success returned, the function has + * taken the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mas_set_ntf_filter( + struct bt_map_mse_mas *mse_mas, uint8_t rsp_code, struct net_buf *buf); + +/** @} */ /* end of bt_map_mse_mas */ + +/** + * @brief Messaging Server Equipment(MSE) OBEX Client + * @defgroup bt_map_mse_mns Messaging Server Equipment(MSE) OBEX Client + * @ingroup bt_map + * @{ + */ + +/** @brief Forward declaration of MAP Server MNS structure */ + +struct bt_map_mse_mns; + +/** @brief MAP Server MNS callbacks + * + * Callbacks for MAP Server MNS events. + */ + +struct bt_map_mse_mns_cb +{ + /** @brief RFCOMM transport connected callback + * + * Called when the underlying transport (RFCOMM) connection is established. + * OBEX connection has not yet been negotiated at this point. + * + * @param conn Bluetooth connection object. + * @param mse_mns MNS client instance. + */ + + void (*rfcomm_connected)(struct bt_conn *conn, + struct bt_map_mse_mns *mse_mns); + + /** @brief RFCOMM transport disconnected callback + * + * Called when the underlying transport (RFCOMM) connection is closed. All + * pending operations are cancelled. + * + * @param mse_mns MNS client instance. + */ + + void (*rfcomm_disconnected)(struct bt_map_mse_mns *mse_mns); + + /** @brief L2CAP transport connected callback + * + * Called when the underlying transport (L2CAP) connection is established. + * OBEX connection has not yet been negotiated at this point. + * + * @param conn Bluetooth connection object. + * @param mse_mns MNS client instance. + */ + + void (*l2cap_connected)(struct bt_conn *conn, + struct bt_map_mse_mns *mse_mns); + + /** @brief L2CAP transport disconnected callback + * + * Called when the underlying transport (L2CAP) connection is closed. All + * pending operations are cancelled. + * + * @param mse_mns MNS client instance. + */ + + void (*l2cap_disconnected)(struct bt_map_mse_mns *mse_mns); + + /** @brief Connect callback + * + * Called when OBEX CONNECT response is received. + * + * @param mse_mns MNS client instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_SUCCESS on success). + * @param version OBEX protocol version supported by server. + * @param mopl Maximum OBEX packet length supported by server. + * @param buf Buffer containing additional response headers + * (connection ID, etc.). + */ + +void (*connect)(struct bt_map_mse_mns *mse_mns, + uint8_t rsp_code, + uint8_t version, + uint16_t mopl, + struct net_buf *buf); + + /** @brief Disconnect callback + * + * Called when OBEX DISCONNECT response is received. + * + * @param mse_mns MNS client instance. + * @param rsp_code Response code @ref bt_obex_rsp_code. + * @param buf Buffer containing response headers. + */ + + void (*disconnect)(struct bt_map_mse_mns *mse_mns, uint8_t rsp_code, + struct net_buf *buf); + + /** @brief Abort callback + * + * Called when OBEX ABORT response is received. The aborted operation is + * cancelled. + * + * @param mse_mns MNS client instance. + * @param rsp_code Response code @ref bt_obex_rsp_code. + * @param buf Buffer containing response headers. + */ + + void (*abort)(struct bt_map_mse_mns *mse_mns, uint8_t rsp_code, + struct net_buf *buf); + + /** @brief Send event callback + * + * Called when SendEvent response is received. May be called multiple times + * if request is fragmented (@ref BT_OBEX_RSP_CODE_CONTINUE). + * + * @param mse_mns MNS client instance. + * @param rsp_code Response code @ref bt_obex_rsp_code (@ref + * BT_OBEX_RSP_CODE_CONTINUE for partial upload). + * @param buf Buffer containing response headers. + */ + + void (*send_event)(struct bt_map_mse_mns *mse_mns, uint8_t rsp_code, + struct net_buf *buf); +}; + +/** @brief MAP Server MNS instance structure + * + * Represents a client connection to a Message Notification Server. Used for + * sending event notifications to the MCE. + */ + +struct bt_map_mse_mns +{ + /** @brief Underlying GOEP transport instance */ + + struct bt_goep_transport goep_transport; + + /** @brief Underlying GOEP instance */ + + struct bt_goep goep; + + /** @internal Callbacks */ + + const struct bt_map_mse_mns_cb *_cb; + + /** @internal Transport state (atomic) */ + + atomic_t _transport_state; + + /** @internal Connection ID */ + + uint32_t _conn_id; + + /** @internal Underlying OBEX client */ + + struct bt_obex_client _client; + + /** @internal Client state (atomic) */ + + atomic_t _state; + + /** @internal Pending response callback */ + + void (*_rsp_cb)(struct bt_map_mse_mns *mse_mns, uint8_t rsp_code, + struct net_buf *buf); + + /** @internal Current request function type */ + + const char *_req_type; +}; + +/** @brief Connect MAP Server MNS over RFCOMM + * + * Initiates RFCOMM transport connection to a remote MNS. On success, the + * rfcomm_connected callback @ref bt_map_mse_mns_cb::rfcomm_connected will be + * called. + * + * @param conn Bluetooth connection to remote device. + * @param mse_mns MNS client instance. + * @param cb Pointer to callback structure (must remain valid for the + * lifetime of the connection). + * @param channel RFCOMM server channel (from SDP discovery). + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mns_rfcomm_connect( + struct bt_conn *conn, struct bt_map_mse_mns *mse_mns, + const struct bt_map_mse_mns_cb *cb, uint8_t channel); + +/** @brief Disconnect MAP Server MNS over RFCOMM + * + * Closes the RFCOMM transport connection. On success, the + * rfcomm_disconnected callback @ref bt_map_mse_mns_cb::rfcomm_disconnected + * will be called. + * + * @param mse_mns MNS client instance. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mns_rfcomm_disconnect(struct bt_map_mse_mns *mse_mns); + +/** @brief Connect MAP Server MNS over L2CAP + * + * Initiates L2CAP transport connection to a remote MNS. On success, the + * l2cap_connected callback @ref bt_map_mse_mns_cb::l2cap_connected will be + * called. + * + * @param conn Bluetooth connection to remote device. + * @param mse_mns MNS client instance. + * @param cb Pointer to callback structure (must remain valid for the + * lifetime of the connection). + * @param psm L2CAP PSM (from SDP discovery). + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mns_l2cap_connect( + struct bt_conn *conn, struct bt_map_mse_mns *mse_mns, + const struct bt_map_mse_mns_cb *cb, uint16_t psm); + +/** @brief Disconnect MAP Server MNS over L2CAP + * + * Closes the L2CAP transport connection. On success, the l2cap_disconnected + * callback @ref bt_map_mse_mns_cb::l2cap_disconnected will be called. + * + * @param mse_mns MNS client instance. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mns_l2cap_disconnect(struct bt_map_mse_mns *mse_mns); + +/** @brief Create PDU for MAP Server MNS + * + * Allocates a buffer for building MAP request packets. The buffer is sized + * appropriately for the negotiated MTU. + * + * @param mse_mns MNS client instance. + * @param pool Buffer pool to allocate from (NULL for default pool). + * + * @return Allocated buffer, or NULL on allocation failure. + */ + +struct net_buf *bt_map_mse_mns_create_pdu(struct bt_map_mse_mns *mse_mns, + struct net_buf_pool *pool); + +/** @brief Send OBEX connect request + * + * Initiates OBEX session establishment with the MNS. Must be called after + * transport connection is established. Once OBEX connect response received, + * the connect callback @ref bt_map_mse_mns_cb::connect will be called. + * + * @param mse_mns MNS client instance. + * @param buf Buffer containing connect headers (target UUID, etc.). If + * NULL, a default connect request is sent. If success + * returned, the function has taken the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mns_connect(struct bt_map_mse_mns *mse_mns, + struct net_buf *buf); + +/** @brief Send OBEX disconnect request + * + * Initiates OBEX session termination. Once OBEX disconnect response + * received, the disconnect callback @ref bt_map_mse_mns_cb::disconnect will + * be called. + * + * @param mse_mns MNS client instance. + * @param buf Buffer containing disconnect headers (connection ID, etc.). + * If NULL, a default disconnect request is sent. If success + * returned, the function has taken the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mns_disconnect(struct bt_map_mse_mns *mse_mns, + struct net_buf *buf); + +/** @brief Send OBEX abort request + * + * Aborts the currently ongoing operation. Once OBEX abort response received, + * the abort callback @ref bt_map_mse_mns_cb::abort will be called. + * + * @param mse_mns MNS client instance. + * @param buf Buffer containing abort headers (connection ID, etc.). If + * NULL, a default abort request is sent. If success returned, + * the function has taken the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mns_abort(struct bt_map_mse_mns *mse_mns, + struct net_buf *buf); + +/** @brief Send event notification request + * + * Sends an event notification to the remote MCE. May be called multiple + * times to send fragmented event data. Once send_event response received, + * the send_event callback @ref bt_map_mse_mns_cb::send_event will be called + * (possibly multiple times). + * + * @param mse_mns MNS client instance. + * @param final True if this is the last packet, false if more data + * follows. + * @param buf Buffer containing event report XML data and headers. Must + * include connection ID, type header, and application + * parameters for initial request. Must include end-of-body + * header when final is true. If success returned, the + * function has taken the ownership of @p buf. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_map_mse_mns_send_event(struct bt_map_mse_mns *mse_mns, + bool final, struct net_buf *buf); + +/** @} */ /* end of bt_map_mse_mns */ + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_MAP_H */ diff --git a/include/nuttx/net/bluetooth/classic/obex.h b/include/nuttx/net/bluetooth/classic/obex.h new file mode 100644 index 0000000000000..831e149f0ab7a --- /dev/null +++ b/include/nuttx/net/bluetooth/classic/obex.h @@ -0,0 +1,2292 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/classic/obex.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* obex.h - IrDA Object Exchange Protocol handling */ + +/* Copyright 2024-2026 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_OBEX_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_OBEX_H + +/** + * @brief IrDA Object Exchange Protocol (OBEX) + * @defgroup bt_obex IrDA Object Exchange Protocol (OBEX) + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define BT_OBEX_MIN_MTU 255 + +/** @brief OBEX Response Code. */ + +enum __packed bt_obex_rsp_code +{ + /** Continue */ + + BT_OBEX_RSP_CODE_CONTINUE = 0x90, + + /** OK */ + + BT_OBEX_RSP_CODE_OK = 0xa0, + + /** Success */ + + BT_OBEX_RSP_CODE_SUCCESS = 0xa0, + + /** Created */ + + BT_OBEX_RSP_CODE_CREATED = 0xa1, + + /** Accepted */ + + BT_OBEX_RSP_CODE_ACCEPTED = 0xa2, + + /** Non-Authoritative Information */ + + BT_OBEX_RSP_CODE_NON_AUTH_INFO = 0xa3, + + /** No Content */ + + BT_OBEX_RSP_CODE_NO_CONTENT = 0xa4, + + /** Reset Content */ + + BT_OBEX_RSP_CODE_RESET_CONTENT = 0xa5, + + /** Partial Content */ + + BT_OBEX_RSP_CODE_PARTIAL_CONTENT = 0xa6, + + /** Multiple Choices */ + + BT_OBEX_RSP_CODE_MULTI_CHOICES = 0xb0, + + /** Moved Permanently */ + + BT_OBEX_RSP_CODE_MOVED_PERM = 0xb1, + + /** Moved temporarily */ + + BT_OBEX_RSP_CODE_MOVED_TEMP = 0xb2, + + /** See Other */ + + BT_OBEX_RSP_CODE_SEE_OTHER = 0xb3, + + /** Not modified */ + + BT_OBEX_RSP_CODE_NOT_MODIFIED = 0xb4, + + /** Use Proxy */ + + BT_OBEX_RSP_CODE_USE_PROXY = 0xb5, + + /** Bad Request - server couldn’t understand request */ + + BT_OBEX_RSP_CODE_BAD_REQ = 0xc0, + + /** Unauthorized */ + + BT_OBEX_RSP_CODE_UNAUTH = 0xc1, + + /** Payment Required */ + + BT_OBEX_RSP_CODE_PAY_REQ = 0xc2, + + /** Forbidden - operation is understood but refused */ + + BT_OBEX_RSP_CODE_FORBIDDEN = 0xc3, + + /** Not Found */ + + BT_OBEX_RSP_CODE_NOT_FOUND = 0xc4, + + /** Method Not Allowed */ + + BT_OBEX_RSP_CODE_NOT_ALLOW = 0xc5, + + /** Not Acceptable */ + + BT_OBEX_RSP_CODE_NOT_ACCEPT = 0xc6, + + /** Proxy Authentication Required */ + + BT_OBEX_RSP_CODE_PROXY_AUTH_REQ = 0xc7, + + /** Request Time Out */ + + BT_OBEX_RSP_CODE_REQ_TIMEOUT = 0xc8, + + /** Conflict */ + + BT_OBEX_RSP_CODE_CONFLICT = 0xc9, + + /** Gone */ + + BT_OBEX_RSP_CODE_GONE = 0xca, + + /** Length Required */ + + BT_OBEX_RSP_CODE_LEN_REQ = 0xcb, + + /** Precondition Failed */ + + BT_OBEX_RSP_CODE_PRECON_FAIL = 0xcc, + + /** Requested Entity Too Large */ + + BT_OBEX_RSP_CODE_ENTITY_TOO_LARGE = 0xcd, + + /** Requested URL Too Large */ + + BT_OBEX_RSP_CODE_URL_TOO_LARGE = 0xce, + + /** Unsupported media type */ + + BT_OBEX_RSP_CODE_UNSUPP_MEDIA_TYPE = 0xcf, + + /** Internal serve Error */ + + BT_OBEX_RSP_CODE_INTER_ERROR = 0xd0, + + /** Not Implemented */ + + BT_OBEX_RSP_CODE_NOT_IMPL = 0xd1, + + /** Bad Gateway */ + + BT_OBEX_RSP_CODE_BAD_GATEWAY = 0xd2, + + /** Service Unavailable */ + + BT_OBEX_RSP_CODE_UNAVAIL = 0xd3, + + /** Gateway Timeout */ + + BT_OBEX_RSP_CODE_GATEWAY_TIMEOUT = 0xd4, + + /** HTTP Version not supported */ + + BT_OBEX_RSP_CODE_VER_UNSUPP = 0xd5, + + /** Database Full */ + + BT_OBEX_RSP_CODE_DB_FULL = 0xe0, + + /** Database Locked */ + + BT_OBEX_RSP_CODE_DB_LOCK = 0xe1, +}; + +/** Converts a OBEX response code to string. + * + * @param rsp_code Response code. + * + * @return The string representation of the response code. + * If @kconfig{CONFIG_BT_OEBX_RSP_CODE_TO_STR} is not enabled, this just + * returns the empty string. + */ +#if defined(CONFIG_BT_OEBX_RSP_CODE_TO_STR) +const char *bt_obex_rsp_code_to_str(enum bt_obex_rsp_code rsp_code); +#else +static inline const char *bt_obex_rsp_code_to_str( + enum bt_obex_rsp_code rsp_code) +{ +ARG_UNUSED(rsp_code); + +return ""; +} +#endif /* CONFIG_BT_OEBX_RSP_CODE_TO_STR */ + +/** @brief OBEX Header ID */ + +enum __packed bt_obex_header_id +{ + /** Number of objects (used by Connect) */ + + BT_OBEX_HEADER_ID_COUNT = 0xc0, + + /** Name of the object (often a file name) */ + + BT_OBEX_HEADER_ID_NAME = 0x01, + + /** Type of object - e.g. text, html, binary, manufacturer specific */ + + BT_OBEX_HEADER_ID_TYPE = 0x42, + + /** The length of the object in bytes */ + + BT_OBEX_HEADER_ID_LEN = 0xc3, + + /** Date/time stamp – ISO 8601 version - preferred */ + + BT_OBEX_HEADER_ID_TIME_ISO_8601 = 0x44, + + /** Date/time stamp – 4 byte version (for compatibility only) */ + + BT_OBEX_HEADER_ID_TIME = 0xc4, + + /** Text description of the object */ + + BT_OBEX_HEADER_ID_DES = 0x05, + + /** Name of service that operation is targeted to */ + + BT_OBEX_HEADER_ID_TARGET = 0x46, + + /** An HTTP 1.x header */ + + BT_OBEX_HEADER_ID_HTTP = 0x47, + + /** A chunk of the object body. */ + + BT_OBEX_HEADER_ID_BODY = 0x48, + + /** The final chunk of the object body. */ + + BT_OBEX_HEADER_ID_END_BODY = 0x49, + + /** Identifies the OBEX application, used to tell if talking to a peer. */ + + BT_OBEX_HEADER_ID_WHO = 0x4a, + + /** An identifier used for OBEX connection multiplexing. */ + + BT_OBEX_HEADER_ID_CONN_ID = 0xcb, + + /** Extended application request & response information. */ + + BT_OBEX_HEADER_ID_APP_PARAM = 0x4c, + + /** Authentication digest-challenge. */ + + BT_OBEX_HEADER_ID_AUTH_CHALLENGE = 0x4d, + + /** Authentication digest-response. */ + + BT_OBEX_HEADER_ID_AUTH_RSP = 0x4e, + + /** Indicates the creator of an object. */ + + BT_OBEX_HEADER_ID_CREATE_ID = 0xcf, + + /** Uniquely identifies the network client (OBEX server). */ + + BT_OBEX_HEADER_ID_WAN_UUID = 0x50, + + /** OBEX Object class of object. */ + + BT_OBEX_HEADER_ID_OBJECT_CLASS = 0x51, + + /** Parameters used in session commands/responses. */ + + BT_OBEX_HEADER_ID_SESSION_PARAM = 0x52, + + /** Sequence number used in each OBEX packet for reliability. */ + + BT_OBEX_HEADER_ID_SESSION_SEQ_NUM = 0x93, + + /** Specifies the action to be performed (used in ACTION operation). */ + + BT_OBEX_HEADER_ID_ACTION_ID = 0x94, + + /** The destination object name (used in certain ACTION operations). */ + + BT_OBEX_HEADER_ID_DEST_NAME = 0x15, + + /** 4-byte bit mask for setting permissions. */ + + BT_OBEX_HEADER_ID_PERM = 0xd6, + + /** 1-byte value to setup Single Response Mode (SRM). */ + + BT_OBEX_HEADER_ID_SRM = 0x97, + + /** 1-byte value for setting parameters used during Single Response Mode + * (SRM). + */ + + BT_OBEX_HEADER_ID_SRM_PARAM = 0x98, +}; + +/** The OBEX packet header length for PUT and GET request and response. */ +#define BT_OBEX_HDR_LEN 3 + +/** The max PDU data length of OBEX pachet. */ +#define BT_OBEX_PDU_LEN(mopl) ((mopl) - BT_OBEX_HDR_LEN) + +/** The header length of the OBEX header body/end body. */ +#define BT_OBEX_HDR_LEN_OF_HEADER_BODY 3 + +/** The max remaining length of the buffer if the adding header is body/end + * body. + * + * It is used to calculate the max sending length when adding the header + * body data. @code{.c} uint16_t len = BT_OBEX_PDU_LEN(mopl); + * + * len = MIN(len - buf->len, net_buf_tailroom(buf)); if (len > + * BT_OBEX_HDR_LEN_OF_HEADER_BODY) + * { + * len = BT_OBEX_DATA_LEN_OF_HEADER_BODY(len); Calling + * bt_obex_add_header_body() or bt_obex_add_header_end_body() ... } @endcode + * + * @param len The max remaining length. + */ +#define BT_OBEX_DATA_LEN_OF_HEADER_BODY(len) ((len) - BT_OBEX_HDR_LEN_OF_HEADER_BODY) + +#define BT_OBEX_SEND_BUF_RESERVE 7 + +struct bt_obex; +struct bt_obex_server; +struct bt_obex_client; + +/** @brief OBEX server operations structure. + * + * The object has to stay valid and constant for the lifetime of the OBEX + * server. + */ + +struct bt_obex_server_ops +{ + /** @brief OBEX connect request callback + * + * If this callback is provided it will be called whenever the OBEX + * connect request is received. + * + * @param server The OBEX server object. + * @param version OBEX version number. + * @param mopl Maximum OBEX packet length. + * @param buf Sequence of headers. + */ + +void (*connect)(struct bt_obex_server *server, + uint8_t version, + uint16_t mopl, + struct net_buf *buf); + + /** @brief OBEX disconnect request callback + * + * If this callback is provided it will be called whenever the OBEX + * disconnect request is received. + * + * @param server The OBEX server object. + * @param buf Sequence of headers. + */ + + void (*disconnect)(struct bt_obex_server *server, struct net_buf *buf); + + /** @brief OBEX put request callback + * + * If this callback is provided it will be called whenever the OBEX put + * request is received. + * + * @param server The OBEX server object. + * @param final If the final bit is set. + * @param buf Sequence of headers. + */ + + void (*put)(struct bt_obex_server *server, + bool final, + struct net_buf *buf); + + /** @brief OBEX get request callback + * + * If this callback is provided it will be called whenever the OBEX get + * request is received. + * + * @param server The OBEX server object. + * @param final If the final bit is set. + * @param buf Sequence of headers. + */ + + void (*get)(struct bt_obex_server *server, + bool final, + struct net_buf *buf); + + /** @brief OBEX abort request callback + * + * If this callback is provided it will be called whenever the OBEX abort + * request is received. + * + * @param server The OBEX server object. + * @param buf Optional headers. + */ + + void (*abort)(struct bt_obex_server *server, struct net_buf *buf); + + /** @brief OBEX SetPath request callback + * + * If this callback is provided it will be called whenever the OBEX + * SetPath request is received. + * + * @param server The OBEX server object. + * @param flags The flags @ref bt_obex_setpath_flags. + * @param buf Optional headers. + */ + + void (*setpath)(struct bt_obex_server *server, uint8_t flags, + struct net_buf *buf); + + /** @brief OBEX action request callback + * + * If this callback is provided it will be called whenever the OBEX action + * request is received. + * + * @param server The OBEX server object. + * @param final If the final bit is set. + * @param buf Sequence of headers (Including action identifier header + * if it exists). + */ + + void (*action)(struct bt_obex_server *server, bool final, + struct net_buf *buf); +}; + +/** @brief OBEX client operations structure. + * + * The object has to stay valid and constant for the lifetime of the OBEX + * client. + */ + +struct bt_obex_client_ops +{ + /** @brief OBEX connect response callback + * + * If this callback is provided it will be called whenever the OBEX + * connect response is received. + * + * @param client The OBEX client object. + * @param rsp_code Response code. + * @param version OBEX version number. + * @param mopl Maximum OBEX packet length. + * @param buf Sequence of headers. + */ + +void (*connect)(struct bt_obex_client *client, + uint8_t rsp_code, + uint8_t version, + uint16_t mopl, + struct net_buf *buf); + + /** @brief OBEX disconnect response callback + * + * If this callback is provided it will be called whenever the OBEX + * disconnect response is received. + * + * @param client The OBEX client object. + * @param rsp_code Response code. + * @param buf Sequence of headers. + */ + + void (*disconnect)(struct bt_obex_client *client, uint8_t rsp_code, + struct net_buf *buf); + + /** @brief OBEX put response callback + * + * If this callback is provided it will be called whenever the OBEX put + * response is received. + * + * @param client The OBEX client object. + * @param rsp_code Response code. + * @param buf Optional response headers. + */ + + void (*put)(struct bt_obex_client *client, uint8_t rsp_code, + struct net_buf *buf); + + /** @brief OBEX get response callback + * + * If this callback is provided it will be called whenever the OBEX get + * response is received. + * + * @param client The OBEX client object. + * @param rsp_code Response code. + * @param buf Optional response headers. + */ + + void (*get)(struct bt_obex_client *client, uint8_t rsp_code, + struct net_buf *buf); + + /** @brief OBEX abort response callback + * + * If this callback is provided it will be called whenever the OBEX abort + * response is received. + * + * @param client The OBEX client object. + * @param rsp_code Response code. + * @param buf Optional response headers. + */ + + void (*abort)(struct bt_obex_client *client, uint8_t rsp_code, + struct net_buf *buf); + + /** @brief OBEX SetPath response callback + * + * If this callback is provided it will be called whenever the OBEX + * SetPath response is received. + * + * @param client The OBEX client object. + * @param rsp_code Response code. + * @param buf Optional response headers. + */ + + void (*setpath)(struct bt_obex_client *client, uint8_t rsp_code, + struct net_buf *buf); + + /** @brief OBEX action response callback + * + * If this callback is provided it will be called whenever the OBEX action + * response is received. + * + * @param client The OBEX client object. + * @param rsp_code Response code. + * @param buf Optional response headers. + */ + + void (*action)(struct bt_obex_client *client, uint8_t rsp_code, + struct net_buf *buf); +}; + +/** @brief OBEX transport operations structure. + * + * The object has to stay valid and constant for the lifetime of the OBEX + * client/server. + */ + +struct bt_obex_transport_ops +{ + /** @brief allocate buf callback + * + * If this callback is provided it will be called to allocate a net buffer + * to store the outgoing data. + * + * @param obex The OBEX object. + * @param pool Which pool to take the buffer from. + * + * @return Allocated buffer. + */ + + struct net_buf * (*alloc_buf)(struct bt_obex *obex, + struct net_buf_pool *pool); + + /** @brief Send OBEX data via transport + * + * If this callback is provided it will be called to send data via OBEX + * transport. + * + * @param obex The OBEX object. + * @param buf OBEX packet. + * + * @return 0 in case of success or negative value in case of error. + * @return -EMSGSIZE if `buf` is larger than `obex`'s MOPL. + */ + + int (*send)(struct bt_obex *obex, struct net_buf *buf); + + /** @brief Disconnect from transport + * + * If this callback is provided it will be called to disconnect transport. + * + * @param obex The OBEX object. + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*disconnect)(struct bt_obex *obex); +}; + +/** @brief Life-span states of OBEX. + * + * Used only by internal APIs dealing with setting OBEX to proper state + * depending on operational context. + * + * OBEX enters the @ref BT_OBEX_CONNECTING state upon @ref bt_obex_connect, + * or upon returning from @ref bt_obex_server_ops::connect. + * + * When OBEX leaves the @ref BT_OBEX_CONNECTING state and enters the @ref + * BT_OBEX_CONNECTED, + * @ref bt_obex_connect_rsp or @ref bt_obex_client_ops::connect is called + * with the response code + * @ref BT_OBEX_RSP_CODE_SUCCESS. + * + * When OBEX enters the @ref BT_OBEX_DISCONNECTED from other states, + * @ref bt_obex_client_ops::disconnect or @ref bt_obex_connect_rsp is called + * with the response code + * @ref BT_OBEX_RSP_CODE_SUCCESS. Or OBEX transport enters the disconnected + * state from other OBEX transport states. + */ + +enum __packed bt_obex_state +{ + /** OBEX disconnected */ + + BT_OBEX_DISCONNECTED, + + /** OBEX in connecting state */ + + BT_OBEX_CONNECTING, + + /** OBEX ready for upper layer traffic on it */ + + BT_OBEX_CONNECTED, + + /** OBEX in disconnecting state */ + + BT_OBEX_DISCONNECTING, +}; + +union bt_obex_uuid +{ + struct bt_uuid uuid; + struct bt_uuid_16 u16; + struct bt_uuid_32 u32; + struct bt_uuid_128 u128; +}; + +/** @brief OBEX server structure. */ + +struct bt_obex_server +{ + /** @brief OBEX Object */ + + struct bt_obex *obex; + + /** @brief UUID of the service */ + + const struct bt_uuid_128 *uuid; + + /** @brief OBEX Server operations + * + * If it is a obex sever, the upper layer should pass the operations of + * server to `server_ops` when providing the OBEX server structure. + */ + + const struct bt_obex_server_ops *ops; + + /** @brief Receiving configurations */ + + struct + { + /** @brief The Maximum OBEX Packet Length (MOPL) */ + + uint16_t mopl; + } rx; + + /** @brief Transmission configurations */ + + struct + { + /** @brief The Maximum OBEX Packet Length (MOPL) */ + + uint16_t mopl; + } tx; + + /** @internal Saves the current state, @ref bt_obex_state */ + + atomic_t _state; + + /** @internal OBEX opcode */ + + atomic_t _opcode; + + /** @internal OBEX flags */ + + atomic_t _flags; + + /** @internal Target of service */ + + union bt_obex_uuid _target; + + /** @internal OBEX connection identifier */ + + uint32_t _conn_id; + + /** @internal sys snode */ + + struct list_node _node; +}; + +/** @brief OBEX client structure. */ + +struct bt_obex_client +{ + /** @brief OBEX Object */ + + struct bt_obex *obex; + + /** @brief OBEX Client operations + * + * If it is a obex client, the upper layer should pass the operations of + * client to `client_ops` when providing the OBEX client structure. + */ + + const struct bt_obex_client_ops *ops; + + /** @brief Receiving configurations */ + + struct + { + /** @brief The Maximum OBEX Packet Length (MOPL) */ + + uint16_t mopl; + } rx; + + /** @brief Transmission configurations */ + + struct + { + /** @brief The Maximum OBEX Packet Length (MOPL) */ + + uint16_t mopl; + } tx; + + /** @internal Saves the current state, @ref bt_obex_state */ + + atomic_t _state; + + /** @internal OBEX opcode */ + + atomic_t _opcode; + + /** @internal OBEX previous opcode */ + + atomic_t _pre_opcode; + + /** @internal OBEX flags */ + + atomic_t _flags; + + /** @internal target of the conn req */ + + union bt_obex_uuid _target; + + /** @internal OBEX connection identifier */ + + uint32_t _conn_id; + + /** @internal sys snode */ + + struct list_node _node; +}; + +/** @brief OBEX structure. */ + +struct bt_obex +{ + /** @brief Receiving configurations */ + + struct + { + /** @brief MTU of OBEX transport */ + + uint16_t mtu; + } rx; + + /** @brief Transmission configurations */ + + struct + { + /** @brief MTU of OBEX transport */ + + uint16_t mtu; + } tx; + + /** @internal OBEX transport operations */ + + const struct bt_obex_transport_ops *_transport_ops; + + /** @internal OBEX executing client */ + + void *_active_client; + + /** @internal OBEX last executed client */ + + void *_last_client; + + /** @internal OBEX executing client */ + + void *_active_server; + + /** @internal OBEX clients */ + + struct list_node _clients; + + /** @internal OBEX servers */ + + struct list_node _servers; +}; + +/** @brief OBEX server register + * + * Register a OBEX server with the specific UUID. + * + * The OBEX object of the server should be set before calling the function. + * If the UUID is NULL, any connection request will be accepted. Or, the + * space of the UUID should be retained. Before calling the API, @ref + * bt_obex_server::ops should be initialized with valid address of type @ref + * bt_obex_server_ops object. And the @ref bt_obex_server::obex should be + * initialized with the valid OBEX object of type @ref bt_obex. + * + * @param server The OBEX server object. + * @param uuid The UUID of the service. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_server_register(struct bt_obex_server *server, + const struct bt_uuid_128 *uuid); + +/** @brief OBEX server unregister + * + * Unregister a registered OBEX server. + * + * The server should be disconnected when calling the function. + * + * @param server The OBEX server object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_server_unregister(struct bt_obex_server *server); + +/** @brief OBEX connect request + * + * The connect operation initiates the connection and sets up the basic + * expectations of each side of the link. The connect request must fit in a + * single packet. The third parameter `buf` saves the packet data (sequence + * of headers) of the connect request is stored in third parameter `buf`. + * All headers are packed by calling function bt_obex_add_header_*. Such as + * header `Authenticate Challenge` is packed by calling + * @ref bt_obex_add_header_auth_challenge. + * The OBEX object of the client should be set before calling the function. + * Before calling the API, @ref bt_obex_client::ops should be initialized + * with valid address of type @ref bt_obex_client_ops object. And the @ref + * bt_obex_client::obex should be initialized with the valid OBEX object of + * type @ref bt_obex. + * + * @note Buffer ownership is transferred to the stack in case of success, in + * case of an error the caller retains the ownership of the buffer. + * + * @param client OBEX client object. + * @param mopl Maximum OBEX packet length. + * @param buf Sequence of headers to be sent out. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_connect(struct bt_obex_client *client, + uint16_t mopl, struct net_buf *buf); + +/** @brief OBEX connect response + * + * The connect response is used to acknowledged connect request packet. The + * connect response must fit in a single packet. + * + * The second parameter `rsp_code` is used to pass the response code @ref + * bt_obex_rsp_code. The typical values @ref BT_OBEX_RSP_CODE_SUCCESS for + * `success`. The 4th parameter `buf` saves the packet data (sequence of + * headers) of the connect response is stored in third parameter `buf`. All + * headers are packed by calling function bt_obex_add_header_*. Such as + * header `Authenticate Response` is packed by calling + * @ref bt_obex_add_header_auth_rsp. + * + * @note Buffer ownership is transferred to the stack in case of success, in + * case of an error the caller retains the ownership of the buffer. + * + * @param server OBEX server object. + * @param rsp_code Response code. + * @param mopl Maximum OBEX packet length. + * @param buf Sequence of headers to be sent out. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_connect_rsp( + struct bt_obex_server *server, uint8_t rsp_code, uint16_t mopl, + struct net_buf *buf); + +/** @brief OBEX disconnect request + * + * The disconnect operation signals the end of the OBEX connection from + * client side. The disconnect request must fit in a single packet. + * + * The second parameter `buf` saves the packet data (sequence of headers) of + * the disconnect request is stored in second parameter `buf`. All headers + * are packed by calling function bt_obex_add_header_*. Such as header + * `Connection Id` is packed by calling + * @ref bt_obex_add_header_conn_id. + * + * @note Buffer ownership is transferred to the stack in case of success, in + * case of an error the caller retains the ownership of the buffer. + * + * @param client OBEX client object. + * @param buf Sequence of headers to be sent out. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_disconnect(struct bt_obex_client *client, struct net_buf *buf); + +/** @brief OBEX disconnect response + * + * The disconnect response is used to acknowledged disconnect request + * packet. The disconnect response must fit in a single packet. + * + * The second parameter `rsp_code` is used to pass the response code @ref + * bt_obex_rsp_code. The typical values, @ref BT_OBEX_RSP_CODE_SUCCESS for + * `success`, @ref BT_OBEX_RSP_CODE_UNAVAIL for `service unavailable` if the + * header `Connection Id` is invalid. The third parameter `buf` saves the + * packet data (sequence of headers) of the connect response is stored in + * third parameter `buf`. All headers are packed by calling function + * bt_obex_add_header_*. + * + * @note Buffer ownership is transferred to the stack in case of success, in + * case of an error the caller retains the ownership of the buffer. + * + * @param server OBEX server object. + * @param rsp_code Response code. + * @param buf Sequence of headers to be sent out. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_disconnect_rsp(struct bt_obex_server *server, + uint8_t rsp_code, struct net_buf *buf); + +/** @brief OBEX put request + * + * The put operation sends one object from the client to the server. The put + * operation consists of one or more request packets, the last of which + * should have the second parameter `final` set. The third parameter `buf` + * saves the packet data (sequence of headers) of the put request is stored + * in third parameter `buf`. All headers are packed by calling function + * bt_obex_add_header_*. Such as header `Name` is packed by calling @ref + * bt_obex_add_header_name. + * + * @note A put operation with NO Body or End-of-Body headers whatsoever + * should be treated as a delete request. Similarly, a put operation with an + * empty End-of-Body header requests the recipient to create an empty + * object. This definition may not make sense or apply to every + * implementation (in other words devices are not required to support delete + * operations or empty objects). + * + * @note Buffer ownership is transferred to the stack in case of success, in + * case of an error the caller retains the ownership of the buffer. + * + * @param client OBEX client object. + * @param final The final bit of opcode. + * @param buf Sequence of headers to be sent out. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_put(struct bt_obex_client *client, + bool final, struct net_buf *buf); + +/** @brief OBEX put response + * + * The put response is used to acknowledged each put request packet. It is + * sent from the server to client. The second parameter `rsp_code` is used + * to pass the response code @ref bt_obex_rsp_code. The typical values, @ref + * BT_OBEX_RSP_CODE_CONTINUE for `continue`, @ref BT_OBEX_RSP_CODE_SUCCESS + * for `success`. The third parameter `buf` saves the packet data (sequence + * of headers) of the put response is stored in third parameter `buf`. All + * headers are packed by calling function bt_obex_add_header_*. Such as + * header `srm` is packed by calling @ref bt_obex_add_header_srm. Or, the + * `buf` could be NULL if there is not any data needs to be sent. + * + * @note Buffer ownership is transferred to the stack in case of success, in + * case of an error the caller retains the ownership of the buffer. + * + * @param server OBEX server object. + * @param rsp_code Response code. + * @param buf Sequence of headers to be sent out. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_put_rsp(struct bt_obex_server *server, + uint8_t rsp_code, struct net_buf *buf); + +/** @brief OBEX get request + * + * The get operation requests that the server return an object to the + * client. The get operation consists of one or more request packets, the + * last of which should have the second parameter `final` set, and the + * request phase of the get is complete. Once a get request is sent with the + * final bit, all subsequent get request packets must set the final bit + * until the operation is complete. The third parameter `buf` saves the + * packet data (sequence of headers) of the get request is stored in third + * parameter `buf`. All headers are packed by calling function + * bt_obex_add_header_*. Such as header `Name` is packed by calling @ref + * bt_obex_add_header_name. Or, the `buf` could be NULL if there is not any + * data needs to be sent. + * + * @note Buffer ownership is transferred to the stack in case of success, in + * case of an error the caller retains the ownership of the buffer. + * + * @param client OBEX client object. + * @param final The final bit of opcode. + * @param buf Sequence of headers to be sent out. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get(struct bt_obex_client *client, + bool final, struct net_buf *buf); + +/** @brief OBEX get response + * + * The get response is used to acknowledged get request packets. It is sent + * from the server to client. If the server has more than one object that + * fits the request, the behavior is system dependent. But the server device + * must not begin sending the object body chunks until the request phase is + * complete. + * + * The second parameter `rsp_code` is used to pass the response code @ref + * bt_obex_rsp_code. The typical values, @ref BT_OBEX_RSP_CODE_CONTINUE for + * `continue`, @ref BT_OBEX_RSP_CODE_SUCCESS for `success`. A successful + * response for an object that fits entirely in one response packet is + * @ref BT_OBEX_RSP_CODE_SUCCESS (Success, with Final bit set) in the + * response code, followed by the object body. If the response is large + * enough to require multiple GET responses, only the last response is @ref + * BT_OBEX_RSP_CODE_SUCCESS, and the others are all + * @ref BT_OBEX_RSP_CODE_CONTINUE (Continue). The object is returned as a + * sequence of headers just as with put operation. Any other response code + * @ref bt_obex_rsp_code indicates failure. The typical failure response + * codes, @ref BT_OBEX_RSP_CODE_BAD_REQ for `Bad request - server couldn’t + * understand request`, @ref BT_OBEX_RSP_CODE_FORBIDDEN for `Forbidden - + * operation is understood but refused`. The third parameter `buf` saves the + * packet data (sequence of headers) of the get response is stored in third + * parameter `buf`. All headers are packed by calling function + * bt_obex_add_header_*. Such as header `srm` is packed by calling @ref + * bt_obex_add_header_srm. Or, the `buf` could be NULL if there is not any + * data needs to be sent. + * + * @note Buffer ownership is transferred to the stack in case of success, in + * case of an error the caller retains the ownership of the buffer. + * + * @param server OBEX server object. + * @param rsp_code Response code. + * @param buf Sequence of headers to be sent out. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_rsp(struct bt_obex_server *server, + uint8_t rsp_code, struct net_buf *buf); + +/** @brief OBEX abort request + * + * The abort request is used when the client decides to terminate a + * multi-packet operation (such as put) before it would be normally end. The + * abort request always fits in one OBEX packet and have the Final bit set. + * The second parameter `buf` saves the packet data (sequence of headers) of + * the abort request is stored in third parameter `buf`. All headers are + * packed by calling function bt_obex_add_header_*. Such as header + * `description` is packed by calling + * @ref bt_obex_add_header_description. Or, the `buf` could be NULL if there + * is not any data needs to be sent. + * + * @note Buffer ownership is transferred to the stack in case of success, in + * case of an error the caller retains the ownership of the buffer. + * + * @param client OBEX client object. + * @param buf Sequence of headers to be sent out. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_abort(struct bt_obex_client *client, struct net_buf *buf); + +/** @brief OBEX abort response + * + * The abort response is used to acknowledged abort request packet. It is + * sent from the server to client. The abort response always fits in one + * OBEX packet and have the Final bit set. + * + * The second parameter `rsp_code` is used to pass the response code @ref + * bt_obex_rsp_code. The typical value @ref BT_OBEX_RSP_CODE_SUCCESS for + * `success`. The third parameter `buf` saves the packet data (sequence of + * headers) of the abort response is stored in third parameter `buf`. All + * headers are packed by calling function bt_obex_add_header_*. Or, the + * `buf` could be NULL if there is not any data needs to be sent. + * + * @note Buffer ownership is transferred to the stack in case of success, in + * case of an error the caller retains the ownership of the buffer. + * + * @param server OBEX server object. + * @param rsp_code Response code. + * @param buf Sequence of headers to be sent out. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_abort_rsp(struct bt_obex_server *server, + uint8_t rsp_code, struct net_buf *buf); + +/** @brief OBEX SetPath operation flags. */ + +enum __packed bt_obex_setpath_flags +{ + /** Backup a level before applying name (equivalent to ../ on many + * systems). + */ + + BT_OBEX_SETPATH_FLAG_BACKUP = BIT(0U), + + /** Don't create folder if it does not exist, return an error instead. */ + + BT_OBEX_SETPATH_FLAG_NO_CREATE = BIT(1U), +}; + +/** @brief OBEX setpath request + * + * The setpath request is used to set the "current folder" on the receiving + * side in order to enable transfers that need additional path information. + * The Path name is contained in a Name header. The setpath request always + * fits in one OBEX packet and have the Final bit set. + * + * The second parameter `flags` is the setpath operation flag bit maps. Bit + * 0 means `backup a level before applying name (equivalent to ../ on many + * systems)`. Bit 1 means `Don’t create folder if it does not exist, + * return + * an error instead.` Other bits must be set to zero by sender and ignored + * by receiver. The third parameter `buf` saves the packet data (sequence of + * headers) of the setpath request is stored in third parameter `buf`. All + * headers are packed by calling function bt_obex_add_header_*. Such as + * header `Name` is packed by calling @ref bt_obex_add_header_name. If the + * header `Name` is added, it means the client wants to go one level down + * relative to the current folder into the folder `name`. Or, the client + * wants to go back to the default folder when no bit of flags set. + * + * @note The Name header may be omitted when flags or constants indicate the + * entire operation being requested. For example, back up one level (bit 0 + * of `flags` is set), equivalent to "cd .." on some systems. + * + * @note Buffer ownership is transferred to the stack in case of success, in + * case of an error the caller retains the ownership of the buffer. + * + * @param client OBEX client object. + * @param flags Flags for setpath request @ref bt_obex_setpath_flags. + * @param buf Sequence of headers to be sent out. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_setpath(struct bt_obex_client *client, + uint8_t flags, struct net_buf *buf); + +/** @brief OBEX setpath response + * + * The setpath response is used to acknowledged setpath request packet. It + * is sent from the server to client. The setpath response always fits in + * one OBEX packet and have the Final bit set. + * + * The second parameter `rsp_code` is used to pass the response code @ref + * bt_obex_rsp_code. The typical value @ref BT_OBEX_RSP_CODE_SUCCESS for + * `success`. Any other response code + * @ref bt_obex_rsp_code indicates failure. The typical failure response + * codes, + * @ref BT_OBEX_RSP_CODE_BAD_REQ for `Bad request - server couldn’t + * understand request`, + * @ref BT_OBEX_RSP_CODE_FORBIDDEN for `Forbidden - operation is understood + * but refused`. The third parameter `buf` saves the packet data (sequence + * of headers) of the setpath response is stored in third parameter `buf`. + * All headers are packed by calling function bt_obex_add_header_*. Or, the + * `buf` could be NULL if there is not any data needs to be sent. + * + * @note Buffer ownership is transferred to the stack in case of success, in + * case of an error the caller retains the ownership of the buffer. + * + * @param server OBEX server object. + * @param rsp_code Response code. + * @param buf Sequence of headers to be sent out. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_setpath_rsp(struct bt_obex_server *server, + uint8_t rsp_code, struct net_buf *buf); + +/** @brief OBEX Actions. */ + +enum __packed bt_obex_action_id +{ + /** Copy Object Action */ + + BT_OBEX_ACTION_COPY = 0x00, + + /** Move/Rename Object Action */ + + BT_OBEX_ACTION_MOVE_RENAME = 0x01, + + /** Set Object Permissions Action */ + + BT_OBEX_ACTION_SET_PERM = 0x02, +}; + +/** @brief OBEX action request + * + * The action operation is defined to cover the needs of common actions. The + * `Action Id` header + * @ref BT_OBEX_HEADER_ID_ACTION_ID is used in the action operation and + * contains an action identifier which defines what action is to be + * performed. All actions are optional and depends on the implementation of + * server. + * + * There are three actions defined @ref bt_obex_action_id, + * + * @ref BT_OBEX_ACTION_COPY is used to copy an object from one location to + * another. The header `Name` @ref BT_OBEX_HEADER_ID_NAME specifies the + * source file name and the header `Dest Name` + * @ref BT_OBEX_HEADER_ID_DEST_NAME specifies the destination file name. + * These two headers are mandatory for this action. + * + * @ref BT_OBEX_ACTION_MOVE_RENAME is used to move an object from one + * location to another. It can also be used to rename an object. The header + * `Name` @ref BT_OBEX_HEADER_ID_NAME specifies the source file name and the + * header `Dest Name` @ref BT_OBEX_HEADER_ID_DEST_NAME specifies the + * destination file name. These two headers are mandatory for this action. + * + * @ref BT_OBEX_ACTION_SET_PERM is used to set the access permissions of an + * object or folder. The header `Name` @ref BT_OBEX_HEADER_ID_NAME specifies + * the source file name and the header `Permissions` @ref + * BT_OBEX_HEADER_ID_PERM specifies the new permissions for this object. + * These two headers are mandatory for this action. + * + * The action operation consists of one or more request packets, the last of + * which should have the second parameter `final` set. The third parameter + * `buf` saves the packet data (sequence of headers) of the action request + * is stored in third parameter `buf`. All headers are packed by calling + * function bt_obex_add_header_*. Such as header `Name` is packed by calling + * @ref bt_obex_add_header_name. + * + * @note Buffer ownership is transferred to the stack in case of success, in + * case of an error the caller retains the ownership of the buffer. + * + * @param client OBEX client object. + * @param final The final bit of opcode. + * @param buf Sequence of headers to be sent out. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_action(struct bt_obex_client *client, + bool final, struct net_buf *buf); + +/** @brief OBEX action response + * + * The action response is used to acknowledged action request packets. It is + * sent from the server to client. The second parameter `rsp_code` is used + * to pass the response code @ref bt_obex_rsp_code. The typical value @ref + * BT_OBEX_RSP_CODE_SUCCESS for `success`. + * + * There are three actions defined @ref bt_obex_action_id. For each action + * request, there are failure response codes corresponding to it. + * + * For action @ref BT_OBEX_ACTION_COPY, + * @ref BT_OBEX_RSP_CODE_NOT_FOUND - `Source object or destination folder + * does not exist`. + * @ref BT_OBEX_RSP_CODE_FORBIDDEN - `Cannot modify the permissions of the + * destination object/folder, permission denied`. + * @ref BT_OBEX_RSP_CODE_DB_FULL - `Cannot create object in destination + * folder, out of memory` + * @ref BT_OBEX_RSP_CODE_CONFLICT - `Cannot modify the permissions, sharing + * violation, object or Set Object Permissions Action busy`. + * @ref BT_OBEX_RSP_CODE_NOT_IMPL - `Set Object Permissions Action not + * supported`. + * @ref BT_OBEX_RSP_CODE_NOT_MODIFIED - `Cannot create folder/file, + * destination folder/file already exits`. + * + * For action @ref BT_OBEX_ACTION_MOVE_RENAME, + * @ref BT_OBEX_RSP_CODE_NOT_FOUND - `Source object or destination folder + * does not exist`. + * @ref BT_OBEX_RSP_CODE_FORBIDDEN - `Cannot modify the permissions of the + * destination object/folder, permission denied`. + * @ref BT_OBEX_RSP_CODE_DB_FULL - `Cannot create object in destination + * folder, out of memory` + * @ref BT_OBEX_RSP_CODE_CONFLICT - `Cannot modify the permissions, sharing + * violation, object or Set Object Permissions Action busy`. + * @ref BT_OBEX_RSP_CODE_NOT_IMPL - `Set Object Permissions Action not + * supported`. + * @ref BT_OBEX_RSP_CODE_NOT_MODIFIED - `Cannot create folder/file, + * destination folder/file already exits`. + * + * For action @ref BT_OBEX_ACTION_SET_PERM, + * @ref BT_OBEX_RSP_CODE_NOT_FOUND - `Source object or destination folder + * does not exist`. + * @ref BT_OBEX_RSP_CODE_FORBIDDEN - `Cannot modify the permissions of the + * destination object/folder, permission denied`. + * @ref BT_OBEX_RSP_CODE_NOT_IMPL - `Set Object Permissions Action not + * supported`. + * @ref BT_OBEX_RSP_CODE_CONFLICT - `Cannot modify the permissions, sharing + * violation, object or Set Object Permissions Action busy`. + * + * The third parameter `buf` saves the packet data (sequence of headers) of + * the action response is stored in third parameter `buf`. All headers are + * packed by calling function bt_obex_add_header_*. Or, the `buf` could be + * NULL if there is not any data needs to be sent. + * + * @note Buffer ownership is transferred to the stack in case of success, in + * case of an error the caller retains the ownership of the buffer. + * + * @param server OBEX server object. + * @param rsp_code Response code. + * @param buf Sequence of headers to be sent out. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_action_rsp(struct bt_obex_server *server, + uint8_t rsp_code, struct net_buf *buf); + +/** @brief Add Header: number of objects (used by Connect) + * + * @param buf Buffer needs to be sent. + * @param count Number of objects. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_add_header_count(struct net_buf *buf, uint32_t count); + +/** @brief Add Header: name of the object (often a file name) + * + * @param buf Buffer needs to be sent. + * @param len Length of name. + * @param name Name of the object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_add_header_name(struct net_buf *buf, + uint16_t len, const uint8_t *name); + +/** @brief Add Header: type of object - e.g. text, html, binary, manufacturer + * specific + * + * @param buf Buffer needs to be sent. + * @param len Length of type. + * @param type Type of the object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_add_header_type(struct net_buf *buf, + uint16_t len, const uint8_t *type); + +/** @brief Add Header: the length of the object in bytes + * + * @param buf Buffer needs to be sent. + * @param len The length of the object in bytes. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_add_header_len(struct net_buf *buf, uint32_t len); + +/** @brief Add Header: date/time stamp – ISO 8601 version - preferred + * + * @param buf Buffer needs to be sent. + * @param len Length of data/time stamp. + * @param t Data/time stamp. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_add_header_time_iso_8601(struct net_buf *buf, + uint16_t len, const uint8_t *t); + +/** @brief Add Header: date/time stamp – 4 byte version (for compatibility + * only) + * + * @param buf Buffer needs to be sent. + * @param t Data/time stamp. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_add_header_time(struct net_buf *buf, uint32_t t); + +/** @brief Add Header: text description of the object + * + * @param buf Buffer needs to be sent. + * @param len Length of description. + * @param dec Description of the object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_add_header_description(struct net_buf *buf, + uint16_t len, const uint8_t *dec); + +/** @brief Add Header: name of service that operation is targeted to + * + * @param buf Buffer needs to be sent. + * @param len Length of target name. + * @param target Target name. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_add_header_target(struct net_buf *buf, + uint16_t len, const uint8_t *target); + +/** @brief Add Header: an HTTP 1.x header + * + * @param buf Buffer needs to be sent. + * @param len Length of http 1.x header. + * @param http Http 1.x header. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_add_header_http(struct net_buf *buf, + uint16_t len, const uint8_t *http); + +/** @brief Add Header: a chunk of the object body + * + * @param buf Buffer needs to be sent. + * @param len Length of body. + * @param body Object Body. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_add_header_body(struct net_buf *buf, + uint16_t len, const uint8_t *body); + +/** @brief Add Header: the final chunk of the object body. + * + * @param buf Buffer needs to be sent. + * @param len Length of body. + * @param body Object Body. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_add_header_end_body(struct net_buf *buf, + uint16_t len, const uint8_t *body); + +/** @brief Add Header: a chunk (may be a final chunk) of the object body. + * + * The function is used to help to add body/end body for the upperlayer. + * When the tail room of the buffer is more than the passed body room, and + * the total length of buffer is not more than the mopl if the body has been + * added, the header end body will be added. Or, the header body will be + * added. + * + * @param buf Buffer needs to be sent. + * @param mopl The MOPL of the OBEX connection + * @param len Length of body. + * @param body Object Body. + * @param added_len The added length. + * + * @return 0 in case of success or negative value in case of error. + */ + +static inline int bt_obex_add_header_body_or_end_body( + struct net_buf *buf, uint16_t mopl, uint16_t len, const uint8_t *body, + uint16_t *added_len) +{ + uint16_t tx_len; + int err; + + /* OBEX Version 1.5, section 2.2.9 Body, End-of-Body + * The `body` could be a NULL, so the `len` of the name could 0. In some + * cases, the object body data is generated on the fly and the end cannot + * be anticipated, so it is legal to send a zero length End-of-Body header. + */ + + if ((buf == NULL) || ((len != 0) + && (body == NULL)) || (added_len == NULL) || + (mopl < BT_OBEX_MIN_MTU)) + { + return -EINVAL; + } + + tx_len = BT_OBEX_PDU_LEN(mopl); + if (tx_len <= buf->len) + { + return -ENOMEM; + } + + *added_len = 0; + + tx_len = MIN((tx_len - buf->len), net_buf_tailroom(buf)); + if (tx_len <= BT_OBEX_HDR_LEN_OF_HEADER_BODY) + { + return 0; + } + + tx_len = BT_OBEX_DATA_LEN_OF_HEADER_BODY(tx_len); + if (tx_len >= len) + { + *added_len = len; + err = bt_obex_add_header_end_body(buf, len, body); + } + else + { + *added_len = tx_len; + err = bt_obex_add_header_body(buf, tx_len, body); + } + + return err; +} + +/** @brief Add Header: identifies the OBEX application, used to tell if + * talking to a peer. + * + * @param buf Buffer needs to be sent. + * @param len Length of who. + * @param who Who. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_add_header_who(struct net_buf *buf, + uint16_t len, const uint8_t *who); + +/** @brief Add Header: an identifier used for OBEX connection multiplexing. + * + * @param buf Buffer needs to be sent. + * @param conn_id Connection Id. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_add_header_conn_id(struct net_buf *buf, uint32_t conn_id); + +/** + * @brief Bluetooth OBEX TLV triplet. + * + * Description of different data types that can be encoded into TLV triplet. + * Used to form arrays that are passed to the bt_obex_add_header_app_param(), + * bt_obex_add_header_auth_challenge(), and bt_obex_add_header_auth_rsp() + * function. + */ + +struct bt_obex_tlv +{ + uint8_t type; + uint8_t data_len; + const uint8_t *data; +}; + +/** @brief Add Header: extended application request & response information. + * + * @param buf Buffer needs to be sent. + * @param count Number of @ref bt_obex_tlv structures in @p data. + * @param data Array of @ref bt_obex_tlv structures. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_add_header_app_param(struct net_buf *buf, size_t count, + const struct bt_obex_tlv data[]); + +/* OBEX digest-challenge tag: Nonce String of bytes representing the nonce. + */ +#define BT_OBEX_CHALLENGE_TAG_NONCE 0x00 +/** OBEX digest-challenge nonce length (16 bytes) */ +#define BT_OBEX_CHALLENGE_TAG_NONCE_LEN 16 + +/* OBEX digest-challenge tag: Options Optional Challenge Information. + */ +#define BT_OBEX_CHALLENGE_TAG_OPTIONS 0x01 +/** OBEX digest-challenge options length (1 byte) */ +#define BT_OBEX_CHALLENGE_TAG_OPTIONS_LEN 1 + +/** Option BIT0: When set, the User Id must be sent in the authenticate + * response. + */ +#define BT_OBEX_CHALLENGE_TAG_OPTION_REQ_USER_ID BIT(0) +/** Option BIT1: Access mode: Read Only when set, otherwise Full access is + * permitted. + */ +#define BT_OBEX_CHALLENGE_TAG_OPTION_ACCESS_MODE BIT(1) + +/* OBEX digest-challenge tag: Realm A displayable string indicating which + * userid and/or password to use. The first byte of the string is the + * character set to use. The character set uses the same values as those + * defined in IrLMP for the nickname. + */ +#define BT_OBEX_CHALLENGE_TAG_REALM 0x02 + +/** @brief Add Header: authentication digest-challenge. + * + * @param buf Buffer needs to be sent. + * @param count Number of @ref bt_obex_tlv structures in @p data. + * @param data Array of @ref bt_obex_tlv structures. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_add_header_auth_challenge(struct net_buf *buf, size_t count, + const struct bt_obex_tlv data[]); + +/* OBEX digest-Response tag: Request-digest String of bytes representing the + * request digest. + */ +#define BT_OBEX_RESPONSE_TAG_REQ_DIGEST 0x00 +/** OBEX digest-response request-digest length (16 bytes) */ +#define BT_OBEX_RESPONSE_TAG_REQ_DIGEST_LEN 16 + +/* OBEX digest-Response tag: User Id User ID string of length n. Max size is + * 20 bytes. + */ +#define BT_OBEX_RESPONSE_TAG_USER_ID 0x01 + +/* OBEX digest-Response tag: Nonce The nonce sent in the digest challenge + * string. + */ +#define BT_OBEX_RESPONSE_TAG_NONCE 0x02 +/** OBEX digest-response nonce length (16 bytes) */ +#define BT_OBEX_RESPONSE_TAG_NONCE_LEN 16 + +/** @brief Add Header: authentication digest-response. + * + * @param buf Buffer needs to be sent. + * @param count Number of @ref bt_obex_tlv structures in @p data. + * @param data Array of @ref bt_obex_tlv structures. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_add_header_auth_rsp(struct net_buf *buf, size_t count, + const struct bt_obex_tlv data[]); + +/** @brief Add Header: indicates the creator of an object. + * + * @param buf Buffer needs to be sent. + * @param creator_id Creator Id. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_add_header_creator_id(struct net_buf *buf, uint32_t creator_id); + +/** @brief Add Header: uniquely identifies the network client (OBEX server). + * + * @param buf Buffer needs to be sent. + * @param len Length of UUID. + * @param uuid UUID. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_add_header_wan_uuid(struct net_buf *buf, + uint16_t len, const uint8_t *uuid); + +/** @brief Add Header: OBEX Object class of object. + * + * @param buf Buffer needs to be sent. + * @param len Length of object class. + * @param obj_class Class of object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_add_header_obj_class(struct net_buf *buf, + uint16_t len, const uint8_t *obj_class); + +/** @brief Add Header: parameters used in session commands/responses. + * + * @param buf Buffer needs to be sent. + * @param len Length of session parameter. + * @param session_param Session parameter. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_add_header_session_param(struct net_buf *buf, uint16_t len, + const uint8_t *session_param); + +/** @brief Add Header: sequence number used in each OBEX packet for + * reliability. + * + * @param buf Buffer needs to be sent. + * @param session_seq_number Session sequence parameter. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_add_header_session_seq_number(struct net_buf *buf, + uint32_t session_seq_number); + +/** @brief Add Header: specifies the action to be performed (used in ACTION + * operation). + * + * @param buf Buffer needs to be sent. + * @param action_id Action Id @ref bt_obex_action_id. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_add_header_action_id(struct net_buf *buf, uint8_t action_id); + +/** @brief Add Header: the destination object name (used in certain ACTION + * operations). + * + * @param buf Buffer needs to be sent. + * @param len Length of destination name. + * @param dest_name Destination name. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_add_header_dest_name(struct net_buf *buf, + uint16_t len, const uint8_t *dest_name); + +/** @brief Add Header: 4-byte bit mask for setting permissions. + * + * @param buf Buffer needs to be sent. + * @param perm Permissions. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_add_header_perm(struct net_buf *buf, uint32_t perm); + +/** @brief Add Header: 1-byte value to setup Single Response Mode (SRM). + * + * @param buf Buffer needs to be sent. + * @param srm SRM. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_add_header_srm(struct net_buf *buf, uint8_t srm); + +/** @brief Add Header: Single Response Mode (SRM) Parameter. + * + * @param buf Buffer needs to be sent. + * @param srm_param SRM parameter. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_add_header_srm_param(struct net_buf *buf, uint8_t srm_param); + +/** @brief OBEX Header structure */ + +struct bt_obex_hdr +{ + /** Header ID @ref bt_obex_header_id */ + + uint8_t id; + + /** The length of header value */ + + uint16_t len; + + /** Header value */ + + const uint8_t *data; +}; + +/** @brief Helper for parsing OBEX header. + * + * A helper for parsing the header structure for OBEX packets. The most + * common scenario is to call this helper on the in the callback of OBEX + * server and client. + * + * @warning This helper function will consume `buf` when parsing. The user + * should make a copy if the original data is to be used afterwards. + * + * @param buf OBEX packet as given to the callback of OBEX server and + * client. + * @param func Callback function which will be called for each element + * that's found in the data The callback should return true + * to continue parsing, or false to stop parsing. + * @param user_data User data to be passed to the callback. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_header_parse(struct net_buf *buf, + bool (*func)(struct bt_obex_hdr *hdr, + void *user_data), + void *user_data); + +/** @brief Get header value: number of objects (used by Connect) + * + * @param buf Buffer needs to be sent. + * @param count Number of objects. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_header_count(struct net_buf *buf, uint32_t *count); + +/** @brief Get header value: name of the object (often a file name) + * + * @param buf Buffer needs to be sent. + * @param len Length of name. + * @param name Name of the object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_header_name(struct net_buf *buf, + uint16_t *len, const uint8_t **name); + +/** @brief Get header value: type of object - e.g. text, html, binary, + * manufacturer specific + * + * @param buf Buffer needs to be sent. + * @param len Length of type. + * @param type Type of the object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_header_type(struct net_buf *buf, + uint16_t *len, const uint8_t **type); + +/** @brief Get header value: the length of the object in bytes + * + * @param buf Buffer needs to be sent. + * @param len The length of the object in bytes. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_header_len(struct net_buf *buf, uint32_t *len); + +/** @brief Get header value: date/time stamp – ISO 8601 version - preferred + * + * @param buf Buffer needs to be sent. + * @param len Length of data/time stamp. + * @param t Data/time stamp. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_header_time_iso_8601(struct net_buf *buf, + uint16_t *len, const uint8_t **t); + +/** @brief Get header value: date/time stamp – 4 byte version (for + * compatibility only) + * + * @param buf Buffer needs to be sent. + * @param t Data/time stamp. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_header_time(struct net_buf *buf, uint32_t *t); + +/** @brief Get header value: text description of the object + * + * @param buf Buffer needs to be sent. + * @param len Length of description. + * @param dec Description of the object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_header_description(struct net_buf *buf, + uint16_t *len, const uint8_t **dec); + +/** @brief Get header value: name of service that operation is targeted to + * + * @param buf Buffer needs to be sent. + * @param len Length of target name. + * @param target Target name. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_header_target(struct net_buf *buf, + uint16_t *len, const uint8_t **target); + +/** @brief Get header value: an HTTP 1.x header + * + * @param buf Buffer needs to be sent. + * @param len Length of http 1.x header. + * @param http Http 1.x header. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_header_http(struct net_buf *buf, + uint16_t *len, const uint8_t **http); + +/** @brief Get header value: a chunk of the object body. + * + * @param buf Buffer needs to be sent. + * @param len Length of body. + * @param body Object Body. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_header_body(struct net_buf *buf, + uint16_t *len, const uint8_t **body); + +/** @brief Get header value: the final chunk of the object body. + * + * @param buf Buffer needs to be sent. + * @param len Length of body. + * @param body Object Body. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_header_end_body(struct net_buf *buf, + uint16_t *len, const uint8_t **body); + +/** @brief Get header value: identifies the OBEX application, used to tell if + * talking to a peer. + * + * @param buf Buffer needs to be sent. + * @param len Length of who. + * @param who Who. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_header_who(struct net_buf *buf, + uint16_t *len, const uint8_t **who); + +/** @brief Get header value: an identifier used for OBEX connection + * multiplexing. + * + * @param buf Buffer needs to be sent. + * @param conn_id Connection Id. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_header_conn_id(struct net_buf *buf, uint32_t *conn_id); + +/** @brief Helper for parsing OBEX TLV triplet. + * + * A helper for parsing the TLV triplet structure for OBEX packets. The most + * common scenario is to call this helper on the in the callback of OBEX + * server and client. The @p data is encoded by using A Tag-Length-Value + * encoding scheme. Usually, it is the header value of the header + * `Application Request-Response Parameters`, `Authenticate Challenge`, and + * `Authenticate Response`. It means the @p data is the output of the + * @ref bt_obex_get_header_app_param, @ref + * bt_obex_get_header_auth_challenge, or + * @ref bt_obex_get_header_auth_rsp. + * + * @param len The length of the @p data. + * @param data The data as given to the callback of OBEX server and + * client. + * @param func Callback function which will be called for each TLV + * triplet that's found from the @p data. The callback + * should return true to continue parsing, or false to stop + * parsing. + * @param user_data User data to be passed to the callback. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_tlv_parse(uint16_t len, const uint8_t *data, + bool (*func)(struct bt_obex_tlv *tlv, + void *user_data), + void *user_data); + +/** @brief Get header value: extended application request & response + * information. + * + * The parameter can be parsed by calling @ref bt_obex_tlv_parse. + * + * @param buf Buffer needs to be sent. + * @param len Length of app_param. + * @param app_param Application parameter. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_header_app_param(struct net_buf *buf, + uint16_t *len, const uint8_t **app_param); + +/** @brief Get header value: authentication digest-challenge. + * + * The options can be parsed by calling @ref bt_obex_tlv_parse. + * + * @param buf Buffer needs to be sent. + * @param len Length of auth_challenge. + * @param auth Authentication challenge. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_header_auth_challenge(struct net_buf *buf, + uint16_t *len, const uint8_t **auth); + +/** @brief Get header value: authentication digest-response. + * + * The options can be parsed by calling @ref bt_obex_tlv_parse. + * + * @param buf Buffer needs to be sent. + * @param len Length of authentication response. + * @param auth Authentication response. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_header_auth_rsp(struct net_buf *buf, + uint16_t *len, const uint8_t **auth); + +/** @brief Get header value: indicates the creator of an object. + * + * @param buf Buffer needs to be sent. + * @param creator_id Creator Id. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_header_creator_id(struct net_buf *buf, uint32_t *creator_id); + +/** @brief Get header value: uniquely identifies the network client (OBEX + * server). + * + * @param buf Buffer needs to be sent. + * @param len Length of UUID. + * @param uuid UUID. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_header_wan_uuid(struct net_buf *buf, + uint16_t *len, const uint8_t **uuid); + +/** @brief Get header value: oBEX Object class of object. + * + * @param buf Buffer needs to be sent. + * @param len Length of object class. + * @param obj_class Class of object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_header_obj_class(struct net_buf *buf, + uint16_t *len, const uint8_t **obj_class); + +/** @brief Get header value: parameters used in session commands/responses. + * + * @param buf Buffer needs to be sent. + * @param len Length of session parameter. + * @param session_param Session parameter. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_header_session_param(struct net_buf *buf, uint16_t *len, + const uint8_t **session_param); + +/** @brief Get header value: sequence number used in each OBEX packet for + * reliability. + * + * @param buf Buffer needs to be sent. + * @param session_seq_number Session sequence parameter. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_header_session_seq_number(struct net_buf *buf, + uint32_t *session_seq_number); + +/** @brief Get header value: specifies the action to be performed (used in + * ACTION operation). + * + * @param buf Buffer needs to be sent. + * @param action_id Action Id @ref bt_obex_action_id. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_header_action_id(struct net_buf *buf, uint8_t *action_id); + +/** @brief Get header value: the destination object name (used in certain + * ACTION operations). + * + * @param buf Buffer needs to be sent. + * @param len Length of destination name. + * @param dest_name Destination name. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_header_dest_name(struct net_buf *buf, + uint16_t *len, const uint8_t **dest_name); + +/** @brief Get header value: 4-byte bit mask for setting permissions. + * + * @param buf Buffer needs to be sent. + * @param perm Permissions. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_header_perm(struct net_buf *buf, uint32_t *perm); + +/** @brief Get header value: 1-byte value to setup Single Response Mode + * (SRM). + * + * @param buf Buffer needs to be sent. + * @param srm SRM. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_header_srm(struct net_buf *buf, uint8_t *srm); + +/** @brief Get header value: Single Response Mode (SRM) Parameter. + * + * @param buf Buffer needs to be sent. + * @param srm_param SRM parameter. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_get_header_srm_param(struct net_buf *buf, uint8_t *srm_param); + +/** @brief Make the UUID with specific data and len + * + * @param uuid OBEX uuid. + * @param data UUID data. + * @param len UUID data length. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_obex_make_uuid(union bt_obex_uuid *uuid, + const uint8_t *data, uint16_t len); + +/** @brief Check if the string is valid + * + * @param id HEADER ID. + * @param len The length of string. + * @param str The address of string. + * + * @return true if the string is valid or false otherwise. + */ + +bool bt_obex_string_is_valid(uint8_t id, uint16_t len, const uint8_t *str); + +/** @brief Check whether the buf has the specified header + * + * @param buf Buffer needs to be sent. + * @param id The id of the header. + * + * @return true if the header is found or false otherwise. + */ + +bool bt_obex_has_header(struct net_buf *buf, uint8_t id); + +/** @brief Check whether the buf has the specified application parameter + * + * @param buf Buffer needs to be sent. + * @param id The tag id of the application parameter. + * + * @return true if the tag is found or false otherwise. + */ + +bool bt_obex_has_app_param(struct net_buf *buf, uint8_t id); + +/** @brief Generate nonce for OBEX authentication challenge. + * + * Generates a nonce value used in OBEX authentication challenges. The nonce + * is used to prevent replay attacks during authentication. + * + * @param pwd Password bytes used for nonce generation. + * @param pwd_len Length of @p pwd in bytes. + * @param nonce Output buffer to store the generated nonce (@ref + * BT_OBEX_CHALLENGE_TAG_NONCE_LEN bytes). + * + * @return 0 on success, negative error code on failure. + * + * @warning OBEX authentication uses MD5, which is cryptographically weak. + * It provides only limited security. + */ + +int bt_obex_generate_nonce(const uint8_t *pwd, size_t pwd_len, + uint8_t nonce[BT_OBEX_CHALLENGE_TAG_NONCE_LEN]); + +/** @brief Calculate request digest for OBEX authentication response. + * + * Computes the MD5 digest for an authentication response based on the + * password and received nonce. This digest is sent to prove knowledge of + * the password without transmitting the password itself. + * + * @param pwd Password bytes used for authentication. + * @param pwd_len Length of @p pwd in bytes. + * @param nonce Nonce value received in the authentication challenge (@ref + * BT_OBEX_CHALLENGE_TAG_NONCE_LEN bytes). + * @param digest Output buffer to store the calculated request digest (@ref + * BT_OBEX_RESPONSE_TAG_REQ_DIGEST_LEN bytes). + * + * @return 0 on success, negative error code on failure. + * + * @warning OBEX authentication uses MD5, which is cryptographically weak. + * It provides only limited security. + */ + +int bt_obex_calculate_request_digest(const uint8_t *pwd, + size_t pwd_len, + const uint8_t nonce[BT_OBEX_CHALLENGE_TAG_NONCE_LEN], + uint8_t digest[BT_OBEX_RESPONSE_TAG_REQ_DIGEST_LEN]); + +/** @brief Verify OBEX authentication response. + * + * Verifies that the received request digest matches the expected value + * computed from the password and nonce. Used by the authenticating party to + * validate the peer's authentication response. + * + * @param pwd Password bytes used for authentication. + * @param pwd_len Length of @p pwd in bytes. + * @param nonce Nonce value that was sent in the authentication challenge + * (@ref BT_OBEX_CHALLENGE_TAG_NONCE_LEN bytes). + * @param digest Request digest received from the peer (@ref + * BT_OBEX_RESPONSE_TAG_REQ_DIGEST_LEN bytes). + * + * @return 0 if authentication succeeds, negative error code on failure. + * + * @warning OBEX authentication uses MD5, which is cryptographically weak. + * It provides only limited security. + */ + +int bt_obex_verify_auth_response(const uint8_t *pwd, + size_t pwd_len, + const uint8_t nonce[BT_OBEX_CHALLENGE_TAG_NONCE_LEN], + const uint8_t digest[BT_OBEX_RESPONSE_TAG_REQ_DIGEST_LEN]); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_OBEX_H */ diff --git a/include/nuttx/net/bluetooth/classic/pbap.h b/include/nuttx/net/bluetooth/classic/pbap.h new file mode 100644 index 0000000000000..8f0d2ec287de4 --- /dev/null +++ b/include/nuttx/net/bluetooth/classic/pbap.h @@ -0,0 +1,1313 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/classic/pbap.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Phone Book Access Profile handling. + */ + +/* Copyright 2025-2026 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_PBAP_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_PBAP_H + +/** + * @brief Phone Book Access Profile (PBAP) + * @defgroup bt_pbap Phone Book Access Profile (PBAP) + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** @brief PBAP target UUID for service identification. + * + * 128-bit UUID (796135f0-f0c5-11d8-0966-0800200c9a66) used to identify the + * PBAP service during OBEX connection establishment. + */ +#define BT_PBAP_UUID \ +(const struct bt_uuid_128 *)BT_UUID_DECLARE_128( \ + BT_UUID_128_ENCODE(0x796135f0, 0xf0c5, 0x11d8, 0x0966, 0x0800200c9a66)) + +/** @brief OBEX type header for pull phone book operation. */ +#define BT_PBAP_PULL_PHONE_BOOK_TYPE "x-bt/phonebook" + +/** @brief OBEX type header for pull vCard listing operation. */ +#define BT_PBAP_PULL_VCARD_LISTING_TYPE "x-bt/vcard-listing" + +/** @brief OBEX type header for pull vCard entry operation. */ +#define BT_PBAP_PULL_VCARD_ENTRY_TYPE "x-bt/vcard" + +/** @brief Set phone book flags: Navigate up to parent directory. + * + * Combination of OBEX setpath flags used to navigate to the parent + * directory in the phone book hierarchy. + */ +#define BT_PBAP_SET_PHONE_BOOK_FLAGS_UP \ +(BT_OBEX_SETPATH_FLAG_BACKUP | BT_OBEX_SETPATH_FLAG_NO_CREATE) + +/** @brief Set phone book flags: Navigate down to child directory or to root. + * + * OBEX setpath flag used to navigate down to a child directory or to the + * root directory in the phone book hierarchy. + */ +#define BT_PBAP_SET_PHONE_BOOK_FLAGS_DOWN_OR_ROOT BT_OBEX_SETPATH_FLAG_NO_CREATE + +/** @brief PBAP response codes. + * + * Response codes used in PBAP operations, mapped from OBEX response codes. + * These indicate the status of PBAP requests and operations. + */ + +enum __packed bt_pbap_rsp_code +{ + /** Continue. */ + + BT_PBAP_RSP_CODE_CONTINUE = BT_OBEX_RSP_CODE_CONTINUE, + + /** OK. */ + + BT_PBAP_RSP_CODE_OK = BT_OBEX_RSP_CODE_OK, + + /** Success. */ + + BT_PBAP_RSP_CODE_SUCCESS = BT_OBEX_RSP_CODE_SUCCESS, + + /** Bad request - Server couldn't understand request. */ + + BT_PBAP_RSP_CODE_BAD_REQ = BT_OBEX_RSP_CODE_BAD_REQ, + + /** Unauthorized. */ + + BT_PBAP_RSP_CODE_UNAUTH = BT_OBEX_RSP_CODE_UNAUTH, + + /** Forbidden - Operation is understood but refused. */ + + BT_PBAP_RSP_CODE_FORBIDDEN = BT_OBEX_RSP_CODE_FORBIDDEN, + + /** Not found. */ + + BT_PBAP_RSP_CODE_NOT_FOUND = BT_OBEX_RSP_CODE_NOT_FOUND, + + /** Not acceptable. */ + + BT_PBAP_RSP_CODE_NOT_ACCEPT = BT_OBEX_RSP_CODE_NOT_ACCEPT, + + /** Precondition failed. */ + + BT_PBAP_RSP_CODE_PRECON_FAIL = BT_OBEX_RSP_CODE_PRECON_FAIL, + + /** Not implemented. */ + + BT_PBAP_RSP_CODE_NOT_IMPL = BT_OBEX_RSP_CODE_NOT_IMPL, + + /** Service unavailable. */ + + BT_PBAP_RSP_CODE_UNAVAIL = BT_OBEX_RSP_CODE_UNAVAIL, +}; + +/** @brief PBAP application parameter Order parameter values. */ + +enum bt_pbap_appl_param_order +{ + /** Indexed order. */ + + BT_PBAP_APPL_PARAM_ORDER_INDEXED = 0x00, + + /** Alphabetical order. */ + + BT_PBAP_APPL_PARAM_ORDER_ALPHABETICAL = 0x01, + + /** Phonetic order. */ + + BT_PBAP_APPL_PARAM_ORDER_PHONETIC = 0x02, +}; + +/** @brief PBAP application parameter Search Property parameter values. */ + +enum bt_pbap_appl_param_search_property +{ + /** Name. */ + + BT_PBAP_APPL_PARAM_SEARCH_PROPERTY_NAME = 0x00, + + /** Number. */ + + BT_PBAP_APPL_PARAM_SEARCH_PROPERTY_NUMBER = 0x01, + + /** Sound. */ + + BT_PBAP_APPL_PARAM_SEARCH_PROPERTY_SOUND = 0x02, +}; + +/** @brief PBAP application parameter Format parameter values. */ + +enum bt_pbap_appl_param_format +{ + /** vCard version 2.1. */ + + BT_PBAP_APPL_PARAM_FORMAT_2_1 = 0x00, + + /** vCard version 3.0. */ + + BT_PBAP_APPL_PARAM_FORMAT_3_0 = 0x01, +}; + +/** @brief PBAP application parameter vCard Selector Operator parameter + * values. + */ + +enum bt_pbap_appl_param_vcard_selector_operator +{ + /** OR operator. */ + + BT_PBAP_APPL_PARAM_VCARD_SELECTOR_OPERATOR_OR = 0x00, + + /** AND operator. */ + + BT_PBAP_APPL_PARAM_VCARD_SELECTOR_OPERATOR_AND = 0x01, +}; + +/** @brief PBAP application parameter reset neww missed calls parameter + * values. + */ + +enum bt_pbap_appl_param_reset_new_missed_calls +{ + /** Reset. */ + + BT_PBAP_APPL_PARAM_RESET_NEW_MISSED_CALLS = 0x01, +}; + +/** @brief PBAP property bitmask. */ + +enum bt_pbap_appl_param_property_mask +{ + /** vCard version. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_VERSION = BIT64(0), + + /** Formatted name. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_FN = BIT64(1), + + /** Structured presentation of name. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_N = BIT64(2), + + /** Associated image or photo. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_PHOTO = BIT64(3), + + /** Birthday. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_BDAY = BIT64(4), + + /** Delivery address. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_ADR = BIT64(5), + + /** Delivery address label. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_LABEL = BIT64(6), + + /** Telephone number. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_TEL = BIT64(7), + + /** Electronic mail address. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_EMAIL = BIT64(8), + + /** Email program name. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_MAILER = BIT64(9), + + /** Time zone. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_TZ = BIT64(10), + + /** Geographic position. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_GEO = BIT64(11), + + /** Job title. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_TITLE = BIT64(12), + + /** Role or occupation. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_ROLE = BIT64(13), + + /** Organization logo. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_LOGO = BIT64(14), + + /** vCard of person who acts as agent. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_AGENT = BIT64(15), + + /** Name of organization. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_ORG = BIT64(16), + + /** Comments. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_NOTE = BIT64(17), + + /** Revision. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_REV = BIT64(18), + + /** Pronunciation of name. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_SOUND = BIT64(19), + + /** Uniform resource locator. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_URL = BIT64(20), + + /** Unique ID. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_UID = BIT64(21), + + /** Public key. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_KEY = BIT64(22), + + /** Nickname. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_NICKNAME = BIT64(23), + + /** Categories. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_CATEGORIES = BIT64(24), + + /** Product ID. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_PRODID = BIT64(25), + + /** Class information. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_CLASS = BIT64(26), + + /** String used for sorting operations. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_SORT_STRING = BIT64(27), + + /** Time stamp of a missed call. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_X_IRMC_CALL_DATETIME = BIT64(28), + + /** Speed-dial shortcut. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_X_BT_SPEEDDIALKEY = BIT64(29), + + /** Bluetooth UCI (Unique Contact Identifier). */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_X_BT_UCI = BIT64(30), + + /** Bluetooth UID. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_X_BT_UID = BIT64(31), + + /** Proprietary filtering. */ + + BT_PBAP_APPL_PARAM_PROPERTY_SELECTOR_PROPRIETARY_FILTER = BIT64(39), +}; + +/** @brief PBAP application parameter tag IDs. */ + +enum __packed bt_pbap_appl_param_tag_id +{ + /** Order @ref bt_pbap_appl_param_order. */ + + BT_PBAP_APPL_PARAM_TAG_ID_ORDER = 0x01, + + /** Search value. */ + + BT_PBAP_APPL_PARAM_TAG_ID_SEARCH_VALUE = 0x02, + + /** Search property @ref bt_pbap_appl_param_search_property. */ + + BT_PBAP_APPL_PARAM_TAG_ID_SEARCH_PROPERTY = 0x03, + + /** Max list count. */ + + BT_PBAP_APPL_PARAM_TAG_ID_MAX_LIST_COUNT = 0x04, + + /** List start offset. */ + + BT_PBAP_APPL_PARAM_TAG_ID_LIST_START_OFFSET = 0x05, + + /** Property selector @ref bt_pbap_appl_param_property_mask. */ + + BT_PBAP_APPL_PARAM_TAG_ID_PROPERTY_SELECTOR = 0x06, + + /** Format @ref bt_pbap_appl_param_format. */ + + BT_PBAP_APPL_PARAM_TAG_ID_FORMAT = 0x07, + + /** Phonebook size. */ + + BT_PBAP_APPL_PARAM_TAG_ID_PHONEBOOK_SIZE = 0x08, + + /** New missed calls. */ + + BT_PBAP_APPL_PARAM_TAG_ID_NEW_MISSED_CALLS = 0x09, + + /** Primary folder version. */ + + BT_PBAP_APPL_PARAM_TAG_ID_PRIMARY_FOLDER_VERSION = 0x0a, + + /** Secondary folder version. */ + + BT_PBAP_APPL_PARAM_TAG_ID_SECONDARY_FOLDER_VERSION = 0x0b, + + /** vCard selector @ref bt_pbap_appl_param_property_mask . */ + + BT_PBAP_APPL_PARAM_TAG_ID_VCARD_SELECTOR = 0x0c, + + /** Database identifier. */ + + BT_PBAP_APPL_PARAM_TAG_ID_DATABASE_IDENTIFIER = 0x0d, + + /** vCard selector operator @ref bt_pbap_appl_param_property_mask. */ + + BT_PBAP_APPL_PARAM_TAG_ID_VCARD_SELECTOR_OPERATOR = 0x0e, + + /** Reset new missed calls @ref bt_pbap_appl_param_reset_new_missed_calls. + */ + + BT_PBAP_APPL_PARAM_TAG_ID_RESET_NEW_MISSED_CALLS = 0x0f, + + /** PBAP supported features @ref bt_pbap_supported_feature. */ + + BT_PBAP_APPL_PARAM_TAG_ID_SUPPORTED_FEATURES = 0x10, +}; + +/** @brief PBAP supported features bitmask. */ + +enum bt_pbap_supported_feature +{ + /** Download feature - Support for phone book download. */ + + BT_PBAP_SUPPORTED_FEATURE_DOWNLOAD = BIT(0), + + /** Browsing feature - Support for phone book browsing. */ + + BT_PBAP_SUPPORTED_FEATURE_BROWSING = BIT(1), + + /** Database identifier feature - Support for database change tracking. */ + + BT_PBAP_SUPPORTED_FEATURE_DATABASE_IDENTIFIER = BIT(2), + + /** Folder version counters feature - Support for folder version tracking. + */ + + BT_PBAP_SUPPORTED_FEATURE_FOLDER_VERSION_COUNTERS = BIT(3), + + /** vCard selector feature - Support for vCard property filtering. */ + + BT_PBAP_SUPPORTED_FEATURE_VCARD_SELECTOR = BIT(4), + + /** Enhanced missed calls feature - Support for enhanced missed call + * handling. + */ + + BT_PBAP_SUPPORTED_FEATURE_ENHANCED_MISSED_CALLS = BIT(5), + + /** UCI vCard property feature - Support for Unique Contact Identifier. */ + + BT_PBAP_SUPPORTED_FEATURE_UCI_VCARD_PROPERTY = BIT(6), + + /** UID vCard property feature - Support for vCard UID property. */ + + BT_PBAP_SUPPORTED_FEATURE_UID_VCARD_PROPERTY = BIT(7), + + /** Contact referencing feature - Support for contact reference handling. */ + + BT_PBAP_SUPPORTED_FEATURE_CONTACT_REFERENCING = BIT(8), + + /** Default contact image feature - Support for default contact images. */ + + BT_PBAP_SUPPORTED_FEATURE_DEFAULT_CONTACT_IMAGE = BIT(9), +}; + +/** @brief PBAP supported repositories bitmask. */ + +enum bt_pbap_supported_repositories +{ + /** Local phone book repository. */ + + BT_PBAP_SUPPORTED_REPOSITORIES_LOCAL_PHONE_BOOK = BIT(0), + + /** SIM card repository. */ + + BT_PBAP_SUPPORTED_REPOSITORIES_SIM = BIT(1), + + /** Speed dial repository. */ + + BT_PBAP_SUPPORTED_REPOSITORIES_SPEED_DIAL = BIT(2), + + /** Favorites repository. */ + + BT_PBAP_SUPPORTED_REPOSITORIES_FAVORITES = BIT(3), +}; + +/** @brief Forward declaration of PBAP PCE structure. */ + +struct bt_pbap_pce; + +/** @brief PBAP PCE (Phone Book Client Equipment) callback operations + * structure. + * + * This structure must remain valid and constant for the lifetime of the + * PBAP client. + */ + +struct bt_pbap_pce_cb +{ + /** @brief RFCOMM transport connected callback. + * + * Called when the underlying RFCOMM transport is connected. + * + * @param conn ACL connection. + * @param pbap_pce PBAP PCE object, @ref bt_pbap_pce. + */ + + void (*rfcomm_connected)(struct bt_conn *conn, + struct bt_pbap_pce *pbap_pce); + + /** @brief RFCOMM transport disconnected callback. + * + * Called when the underlying RFCOMM transport is disconnected. + * + * @param pbap_pce PBAP PCE object, @ref bt_pbap_pce. + */ + + void (*rfcomm_disconnected)(struct bt_pbap_pce *pbap_pce); + + /** @brief L2CAP transport connected callback. + * + * Called when the underlying L2CAP transport is connected. + * + * @param conn ACL connection. + * @param pbap_pce PBAP PCE object, @ref bt_pbap_pce. + */ + + void (*l2cap_connected)(struct bt_conn *conn, + struct bt_pbap_pce *pbap_pce); + + /** @brief L2CAP transport disconnected callback. + * + * Called when the underlying L2CAP transport is disconnected. + * + * @param pbap_pce PBAP PCE object, @ref bt_pbap_pce. + */ + + void (*l2cap_disconnected)(struct bt_pbap_pce *pbap_pce); + + /** @brief PBAP PCE connect response callback. + * + * Called when the PBAP connect response is received. + * + * @param pbap_pce PBAP PCE object, @ref bt_pbap_pce. + * @param rsp_code Response code, @ref bt_pbap_rsp_code. + * @param version OBEX version supported by PSE. + * @param mopl Maximum OBEX packet length of PSE. + * @param buf Optional response headers buffer. + */ + +void (*connect)(struct bt_pbap_pce *pbap_pce, + uint8_t rsp_code, + uint8_t version, + uint16_t mopl, + struct net_buf *buf); + + /** @brief PBAP PCE disconnect response callback. + * + * Called when the PBAP disconnect response is received. + * + * @param pbap_pce PBAP PCE object, @ref bt_pbap_pce. + * @param rsp_code Response code, @ref bt_pbap_rsp_code. + * @param buf Optional response headers buffer. + */ + + void (*disconnect)(struct bt_pbap_pce *pbap_pce, uint8_t rsp_code, + struct net_buf *buf); + + /** @brief PBAP PCE pull phone book response callback. + * + * Called when the PCE pull phone book response is received. + * + * @param pbap_pce PBAP PCE object, @ref bt_pbap_pce. + * @param rsp_code Response code, @ref bt_pbap_rsp_code. + * @param buf Optional response headers. + */ + + void (*pull_phone_book)(struct bt_pbap_pce *pbap_pce, uint8_t rsp_code, + struct net_buf *buf); + + /** @brief PBAP PCE pull vCard listing response callback. + * + * Called when the PCE pull vCard listing response is received. + * + * @param pbap_pce PBAP PCE object, @ref bt_pbap_pce. + * @param rsp_code Response code, @ref bt_pbap_rsp_code. + * @param buf Optional response headers. + */ + + void (*pull_vcard_listing)(struct bt_pbap_pce *pbap_pce, uint8_t rsp_code, + struct net_buf *buf); + + /** @brief PBAP PCE pull vCard entry response callback. + * + * Called when the PCE pull vCard entry response is received. + * + * @param pbap_pce PBAP PCE object, @ref bt_pbap_pce. + * @param rsp_code Response code, @ref bt_pbap_rsp_code. + * @param buf Optional response headers. + */ + + void (*pull_vcard_entry)(struct bt_pbap_pce *pbap_pce, uint8_t rsp_code, + struct net_buf *buf); + + /** @brief PBAP PCE set phone book response callback. + * + * Called when the PCE set phone book response is received. + * + * @param pbap_pce PBAP PCE object, @ref bt_pbap_pce. + * @param rsp_code Response code, @ref bt_pbap_rsp_code. + * @param buf Optional response headers buffer. + */ + + void (*set_phone_book)(struct bt_pbap_pce *pbap_pce, uint8_t rsp_code, + struct net_buf *buf); + + /** @brief PBAP PCE abort response callback. + * + * Called when the PCE abort response is received. + * + * @param pbap_pce PBAP PCE object, @ref bt_pbap_pce. + * @param rsp_code Response code, @ref bt_pbap_rsp_code. + * @param buf Optional response headers buffer. + */ + + void (*abort)(struct bt_pbap_pce *pbap_pce, uint8_t rsp_code, + struct net_buf *buf); +}; + +/** @brief PBAP PCE (Phone Book Client Equipment) structure. */ + +struct bt_pbap_pce +{ + /** @brief Callback operations structure. */ + + struct bt_pbap_pce_cb *cb; + + /** @internal Underlying GOEP transport instance */ + + struct bt_goep_transport _goep_transport; + + /** @internal GOEP (Generic Object Exchange Profile) instance. */ + + struct bt_goep _goep; + + /** @internal Transport state (atomic). */ + + atomic_t _transport_state; + + /** @internal Connection ID. + * + * Unique identifier for the OBEX connection session with the PSE. + */ + + uint32_t _conn_id; + + /** @internal OBEX client handle. + * + * Internal OBEX client structure for managing OBEX protocol operations. + */ + + struct bt_obex_client _client; + + /** @internal PBAP connection state. + * + * Atomic variable tracking the current state of the PBAP connection. + */ + + atomic_t _state; + + /** @internal Pending response callback. */ + + void (*_rsp_cb)(struct bt_pbap_pce *pbap_pce, uint8_t rsp_code, + struct net_buf *buf); + + /** @internal Current request function type. */ + + const char *_req_type; +}; + +/** @brief PCE Connect to PSE over RFCOMM transport. + * + * Establishes the underlying RFCOMM transport connection for PBAP PCE. Once + * connected, the rfcomm_connected callback will be called. After transport + * is connected, call @ref bt_pbap_pce_connect to establish the PBAP + * protocol connection. + * + * @param conn ACL connection. + * @param pbap_pce PBAP PCE instance to be connected, @ref bt_pbap_pce. + * @param cb Callback operations structure for handling PBAP events, + * @ref bt_pbap_pce_cb. + * @param channel RFCOMM server channel number to connect to. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_pbap_pce_rfcomm_connect( + struct bt_conn *conn, struct bt_pbap_pce *pbap_pce, + struct bt_pbap_pce_cb *cb, uint8_t channel); + +/** @brief Disconnect PBAP PCE RFCOMM transport. + * + * Closes the underlying RFCOMM transport connection. + * + * @param pbap_pce PBAP PCE instance to disconnect, @ref bt_pbap_pce. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_pbap_pce_rfcomm_disconnect(struct bt_pbap_pce *pbap_pce); + +/** @brief Connect PBAP PCE client over L2CAP transport. + * + * Establishes the underlying L2CAP transport connection for PBAP PCE. Once + * connected, the l2cap_connected callback will be called. After transport + * is connected, call @ref bt_pbap_pce_connect to establish the PBAP + * protocol connection. + * + * @param conn ACL connection. + * @param pbap_pce PBAP PCE instance to be connected, @ref bt_pbap_pce. + * @param cb Callback operations structure for handling PBAP events, + * @ref bt_pbap_pce_cb. + * @param psm L2CAP PSM (Protocol Service Multiplexer) to connect to. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_pbap_pce_l2cap_connect( + struct bt_conn *conn, struct bt_pbap_pce *pbap_pce, + struct bt_pbap_pce_cb *cb, uint16_t psm); + +/** @brief Disconnect PBAP PCE L2CAP transport. + * + * Closes the underlying L2CAP transport connection. + * + * @param pbap_pce PBAP PCE instance to disconnect, @ref bt_pbap_pce. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_pbap_pce_l2cap_disconnect(struct bt_pbap_pce *pbap_pce); + +/** @brief Allocate buffer from pool with reserved headroom for PBAP PCE. + * + * Allocates a network buffer from the given pool after reserving headroom + * for PBAP. For PBAP connection over RFCOMM, the reserved headroom includes + * OBEX, RFCOMM, L2CAP and ACL headers. For PBAP connection over L2CAP, the + * reserved headroom includes OBEX, L2CAP and ACL headers. This ensures + * proper packet formatting for the underlying transport. If pool is NULL, + * allocates from the default pool. + * + * @param pbap_pce PBAP PCE object that will use this buffer, @ref + * bt_pbap_pce. + * @param pool Network buffer pool from which to allocate the buffer, or + * NULL for default pool. + * + * @return Pointer to newly allocated buffer with reserved headroom, or NULL + * on failure. + */ + +struct net_buf *bt_pbap_pce_create_pdu(struct bt_pbap_pce *pbap_pce, + struct net_buf_pool *pool); + +/** @brief Establish PBAP protocol connection from PCE client to PSE server. + * + * Initiates a PBAP protocol connection over an already-established + * transport connection. The transport (RFCOMM or L2CAP) must be connected + * first using @ref bt_pbap_pce_rfcomm_connect or @ref + * bt_pbap_pce_l2cap_connect. + * + * Once the connection response is received, the connect callback in @ref + * bt_pbap_pce_cb will be called. If the connection is rejected, the + * callback will be called with an error response code. + * + * @param pbap_pce PBAP PCE object (transport must be connected first), @ref + * bt_pbap_pce. + * @param mopl Maximum OBEX packet length for this connection. + * @param buf Buffer containing connection request headers (should + * include Target UUID, and optionally authentication + * challenge, supported features, etc.). + * + * @return 0 on success, negative error code on failure. + */ + +int bt_pbap_pce_connect(struct bt_pbap_pce *pbap_pce, + uint16_t mopl, struct net_buf *buf); + +/** @brief Disconnect PBAP connection from PBAP client PCE. + * + * Disconnects the PBAP connection by sending an OBEX DISCONNECT request. + * The disconnect callback in @ref bt_pbap_pce_cb will be called when the + * response is received. + * + * @param pbap_pce PBAP PCE object, @ref bt_pbap_pce. + * @param buf Buffer containing optional disconnect headers. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_pbap_pce_disconnect(struct bt_pbap_pce *pbap_pce, + struct net_buf *buf); + +/** @brief Pull phone book from PBAP server PSE. + * + * Sends a pull phone book command to retrieve a complete phone book object + * from the PSE. + * + * @param pbap_pce PBAP PCE object, @ref bt_pbap_pce. + * @param buf Buffer to be sent. Should contain: - Name header with + * phone book path (e.g., "telecom/pb.vcf") - Type header + * with @ref BT_PBAP_PULL_PHONE_BOOK_TYPE - Optional + * application parameters (format, etc.) + * + * @return 0 on success, negative error code on failure. + */ + +int bt_pbap_pce_pull_phone_book(struct bt_pbap_pce *pbap_pce, + struct net_buf *buf); + +/** @brief Pull vCard listing from PBAP server PSE. + * + * Sends a request to retrieve a listing of vCard entries from a specific + * folder. + * + * @param pbap_pce PBAP PCE object, @ref bt_pbap_pce. + * @param buf Buffer containing: - Name header with folder path - Type + * header with @ref BT_PBAP_PULL_VCARD_LISTING_TYPE - + * Optional application parameters (search, etc.) + * + * @return 0 on success, negative error code on failure. + */ + +int bt_pbap_pce_pull_vcard_listing(struct bt_pbap_pce *pbap_pce, + struct net_buf *buf); + +/** @brief Pull specific vCard entry from PBAP server PSE. + * + * Sends a request to retrieve a specific vCard entry. + * + * @param pbap_pce PBAP PCE object, @ref bt_pbap_pce. + * @param buf Buffer containing: - Name header with vCard entry name + * (e.g., "0.vcf") - Type header with @ref + * BT_PBAP_PULL_VCARD_ENTRY_TYPE - Optional application + * parameters (property selector, format, etc.) + * + * @return 0 on success, negative error code on failure. + */ + +int bt_pbap_pce_pull_vcard_entry(struct bt_pbap_pce *pbap_pce, + struct net_buf *buf); + +/** @brief Set current phone book on PBAP server PSE. + * + * Sets the current phone book on the PSE. + * + * @param pbap_pce PBAP PCE object, @ref bt_pbap_pce. + * @param flags Navigation flags: - @ref BT_PBAP_SET_PHONE_BOOK_FLAGS_UP, + * Navigate to parent - @ref + * BT_PBAP_SET_PHONE_BOOK_FLAGS_DOWN_OR_ROOT, Navigate to + * child or root + * @param buf Buffer containing optional Name header with folder name. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_pbap_pce_set_phone_book(struct bt_pbap_pce *pbap_pce, + uint8_t flags, struct net_buf *buf); + +/** @brief Abort current operation on PBAP server PSE. + * + * Sends an abort request to cancel the current ongoing operation. + * + * @param pbap_pce PBAP PCE object, @ref bt_pbap_pce. + * @param buf Buffer containing optional abort headers. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_pbap_pce_abort(struct bt_pbap_pce *pbap_pce, struct net_buf *buf); + +/** @brief Forward declaration of PBAP PSE structure. */ + +struct bt_pbap_pse; + +/** @brief PBAP PSE (Phone Book Server Equipment) callback operations + * structure. + * + * Defines callbacks for handling PBAP client requests on the server side. + */ + +struct bt_pbap_pse_cb +{ + /** @brief RFCOMM transport connected callback. + * + * Called when the underlying RFCOMM transport is connected. + * + * @param conn ACL connection. + * @param pbap_pse PBAP PSE object, @ref bt_pbap_pse. + */ + + void (*rfcomm_connected)(struct bt_conn *conn, + struct bt_pbap_pse *pbap_pse); + + /** @brief RFCOMM transport disconnected callback. + * + * Called when the underlying RFCOMM transport is disconnected. + * + * @param pbap_pse PBAP PSE object, @ref bt_pbap_pse. + */ + + void (*rfcomm_disconnected)(struct bt_pbap_pse *pbap_pse); + + /** @brief L2CAP transport connected callback. + * + * Called when the underlying L2CAP transport is connected. + * + * @param conn ACL connection. + * @param pbap_pse PBAP PSE object, @ref bt_pbap_pse. + */ + + void (*l2cap_connected)(struct bt_conn *conn, + struct bt_pbap_pse *pbap_pse); + + /** @brief L2CAP transport disconnected callback. + * + * Called when the underlying L2CAP transport is disconnected. + * + * @param pbap_pse PBAP PSE object, @ref bt_pbap_pse. + */ + + void (*l2cap_disconnected)(struct bt_pbap_pse *pbap_pse); + + /** @brief PBAP PSE connect request callback. + * + * Called when a PBAP connect request is received from a PCE client. The + * PSE should validate the request and prepare a response. + * + * @param pbap_pse PBAP PSE object, @ref bt_pbap_pse. + * @param version PBAP version requested by PCE. + * @param mopl Maximum OBEX packet length requested by PCE. + * @param buf Request headers buffer (may contain target, + * authentication, etc.). + */ + +void (*connect)(struct bt_pbap_pse *pbap_pse, + uint8_t version, + uint16_t mopl, + struct net_buf *buf); + + /** @brief PBAP PSE disconnect request callback. + * + * Called when a PBAP disconnect request is received from a PCE client. + * + * @param pbap_pse PBAP PSE object, @ref bt_pbap_pse. + * @param buf Optional request headers buffer. + */ + + void (*disconnect)(struct bt_pbap_pse *pbap_pse, struct net_buf *buf); + + /** @brief PBAP PSE pull phone book request callback. + * + * Called when a pull phone book request is received from a PCE client. + * The PSE should retrieve the requested phone book and send it back. + * + * @param pbap_pse PBAP PSE object, @ref bt_pbap_pse. + * @param buf Request headers buffer (contains name, type, app + * parameters, etc.). + */ + + void (*pull_phone_book)(struct bt_pbap_pse *pbap_pse, struct net_buf *buf); + + /** @brief PBAP PSE pull vCard listing request callback. + * + * Called when a pull vCard listing request is received from a PCE client. + * The PSE should retrieve the requested vCard listing and send it back. + * + * @param pbap_pse PBAP PSE object, @ref bt_pbap_pse. + * @param buf Request headers buffer (contains name, type, app + * parameters, etc.). + */ + + void (*pull_vcard_listing)(struct bt_pbap_pse *pbap_pse, + struct net_buf *buf); + + /** @brief PBAP PSE pull vCard entry request callback. + * + * Called when a pull vCard entry request is received from a PCE client. + * The PSE should retrieve the requested vCard entry and send it back. + * + * @param pbap_pse PBAP PSE object, @ref bt_pbap_pse. + * @param buf Request headers buffer (contains name, type, app + * parameters, etc.). + */ + + void (*pull_vcard_entry)(struct bt_pbap_pse *pbap_pse, + struct net_buf *buf); + + /** @brief PBAP PSE set phone book request callback. + * + * Called when a set phone book request is received from a PCE client. The + * PSE should set the phone book to the requested one. + * + * @param pbap_pse PBAP PSE object, @ref bt_pbap_pse. + * @param flags Navigation flags (up/down/root). - @ref + * BT_PBAP_SET_PHONE_BOOK_FLAGS_UP, Navigate to parent - + * @ref BT_PBAP_SET_PHONE_BOOK_FLAGS_DOWN_OR_ROOT, Navigate + * to child or root + * @param buf Optional request headers buffer (may contain folder + * name). + */ + + void (*set_phone_book)(struct bt_pbap_pse *pbap_pse, uint8_t flags, + struct net_buf *buf); + + /** @brief PBAP PSE abort request callback. + * + * Called when an abort request is received from a PCE client. The PSE + * should cancel the current ongoing operation. + * + * @param pbap_pse PBAP PSE object, @ref bt_pbap_pse. + * @param buf Optional request headers buffer. + */ + + void (*abort)(struct bt_pbap_pse *pbap_pse, struct net_buf *buf); +}; + +/** @brief PBAP PSE RFCOMM server structure. + * + * Structure for managing PBAP server over RFCOMM transport. + */ + +struct bt_pbap_pse_rfcomm +{ + /** Underlying GOEP RFCOMM server. */ + + struct bt_goep_transport_rfcomm_server server; + + /** @brief Accept connection callback. + * + * Called when a new RFCOMM connection is accepted. The application should + * allocate and initialize a PBAP PSE instance. + * + * @param conn ACL connection. + * @param pbap_pse_rfcomm PBAP PSE RFCOMM server instance, @ref + * bt_pbap_pse_rfcomm. + * @param pbap_pse Pointer to store the created PBAP PSE instance, + * @ref bt_pbap_pse. + * + * @return 0 on success, negative error code on failure. + */ + +int (*accept)(struct bt_conn *conn, + struct bt_pbap_pse_rfcomm *pbap_pse_rfcomm, + struct bt_pbap_pse **pbap_pse); +}; + +/** @brief PBAP PSE L2CAP server structure. + * + * Structure for managing PBAP server over L2CAP transport. + */ + +struct bt_pbap_pse_l2cap +{ + /** Underlying GOEP L2CAP server. */ + + struct bt_goep_transport_l2cap_server server; + + /** @brief Accept connection callback. + * + * Called when a new L2CAP connection is accepted. The application should + * allocate and initialize a PBAP PSE instance. + * + * @param conn ACL connection. + * @param pbap_pse_l2cap PBAP PSE L2CAP server instance, @ref + * bt_pbap_pse_l2cap. + * @param pbap_pse Pointer to store the created PBAP PSE instance, + * @ref bt_pbap_pse. + * + * @return 0 on success, negative error code on failure. + */ + +int (*accept)(struct bt_conn *conn, + struct bt_pbap_pse_l2cap *pbap_pse_l2cap, + struct bt_pbap_pse **pbap_pse); +}; + +/** @brief PBAP PSE (Phone Book Server Equipment) structure. */ + +struct bt_pbap_pse +{ + /** @brief Callback operations structure. */ + + struct bt_pbap_pse_cb *cb; + + /** @internal Underlying GOEP transport instance */ + + struct bt_goep_transport _goep_transport; + + /** @internal GOEP (Generic Object Exchange Profile) instance. */ + + struct bt_goep _goep; + + /** @internal Transport state (atomic). */ + + atomic_t _transport_state; + + /** @internal Connection ID. + * + * Unique identifier for the OBEX connection session with the PCE. + */ + + uint32_t _conn_id; + + /** @internal OBEX server handle. + * + * Internal OBEX server structure for managing OBEX protocol operations. + */ + + struct bt_obex_server _server; + + /** @internal PBAP connection state. + * + * Atomic variable tracking the current state of the PBAP connection. + */ + + atomic_t _state; + + /** @internal Pending request callback. */ + + void (*_req_cb)(struct bt_pbap_pse *pse, struct net_buf *buf); + + /** @internal Current operation type string. */ + + const char *_optype; + + /** @internal Flags. */ + + atomic_t _flags; +}; + +/** @brief Allocate buffer from pool with reserved headroom for PBAP PSE. + * + * Allocates a network buffer from the given pool after reserving headroom + * for PBAP. For PBAP connection over RFCOMM, the reserved headroom includes + * OBEX, RFCOMM, L2CAP and ACL headers. For PBAP connection over L2CAP, the + * reserved headroom includes OBEX, L2CAP and ACL headers. This ensures + * proper packet formatting for the underlying transport. If pool is NULL, + * allocates from the default pool. + * + * @param pbap_pse PBAP PSE object that will use this buffer, @ref + * bt_pbap_pse. + * @param pool Network buffer pool from which to allocate the buffer, or + * NULL for default pool. + * + * @return Pointer to newly allocated buffer with reserved headroom, or NULL + * on failure. + */ + +struct net_buf *bt_pbap_pse_create_pdu(struct bt_pbap_pse *pbap_pse, + struct net_buf_pool *pool); + +/** @brief Register PBAP PSE RFCOMM server. + * + * Registers a PBAP server that listens for incoming RFCOMM connections. The + * server will be assigned an RFCOMM channel which should be advertised via + * SDP. + * + * @param server PBAP PSE RFCOMM server structure, @ref bt_pbap_pse_rfcomm. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_pbap_pse_rfcomm_register(struct bt_pbap_pse_rfcomm *server); + +/** @brief Register PBAP PSE L2CAP server. + * + * Registers a PBAP server that listens for incoming L2CAP connections. The + * server will be assigned an L2CAP PSM which should be advertised via SDP. + * + * @param server PBAP PSE L2CAP server structure, @ref bt_pbap_pse_l2cap. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_pbap_pse_l2cap_register(struct bt_pbap_pse_l2cap *server); + +/** @brief Register PBAP PSE instance. + * + * Registers a PBAP PSE instance with the PBAP subsystem. This initializes + * the PSE object and associates it with the provided callback structure for + * handling PBAP server operations. + * + * @param pbap_pse PBAP PSE object to register, @ref bt_pbap_pse. + * @param cb Callback operations structure for PSE events, @ref + * bt_pbap_pse_cb. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_pbap_pse_register(struct bt_pbap_pse *pbap_pse, + struct bt_pbap_pse_cb *cb); + +/** @brief Send connect response from PBAP PSE. + * + * Sends a response to a connect request from a PCE client. + * + * @param pbap_pse PBAP PSE object, @ref bt_pbap_pse. + * @param mopl Maximum OBEX packet length. + * @param rsp_code Response code, @ref bt_pbap_rsp_code. + * @param buf Buffer containing response headers (should include Who + * header, connection ID, etc.). + * + * @return 0 on success, negative error code on failure. + */ + +int bt_pbap_pse_connect_rsp( + struct bt_pbap_pse *pbap_pse, uint16_t mopl, uint8_t rsp_code, + struct net_buf *buf); + +/** @brief Send disconnect response from PBAP PSE. + * + * Sends a response to a disconnect request from a PCE client. + * + * @param pbap_pse PBAP PSE object, @ref bt_pbap_pse. + * @param rsp_code Response code, @ref bt_pbap_rsp_code. + * @param buf Buffer containing optional response headers. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_pbap_pse_disconnect_rsp( + struct bt_pbap_pse *pbap_pse, uint8_t rsp_code, struct net_buf *buf); + +/** @brief Send pull phone book response from PBAP PSE. + * + * Sends a response to a pull phone book request from a PCE client. For + * large phonebooks, multiple responses with @ref BT_PBAP_RSP_CODE_CONTINUE + * can be sent, followed by a final response with @ref + * BT_PBAP_RSP_CODE_SUCCESS. + * + * @param pbap_pse PBAP PSE object, @ref bt_pbap_pse. + * @param rsp_code Response code, @ref bt_pbap_rsp_code. + * @param buf Buffer containing response headers. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_pbap_pse_pull_phone_book_rsp( + struct bt_pbap_pse *pbap_pse, uint8_t rsp_code, struct net_buf *buf); + +/** @brief Send pull vCard listing response from PBAP PSE. + * + * Sends a response to a pull vCard listing request from a PCE client. For + * large listings, multiple responses with @ref BT_PBAP_RSP_CODE_CONTINUE + * can be sent, followed by a final response with @ref + * BT_PBAP_RSP_CODE_SUCCESS. + * + * @param pbap_pse PBAP PSE object, @ref bt_pbap_pse. + * @param rsp_code Response code, @ref bt_pbap_rsp_code. + * @param buf Buffer containing response headers. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_pbap_pse_pull_vcard_listing_rsp( + struct bt_pbap_pse *pbap_pse, uint8_t rsp_code, struct net_buf *buf); + +/** @brief Send pull vCard entry response from PBAP PSE. + * + * Sends a response to a pull vCard entry request from a PCE client. For + * large vCard entries, multiple responses with @ref + * BT_PBAP_RSP_CODE_CONTINUE can be sent, followed by a final response with + * @ref BT_PBAP_RSP_CODE_SUCCESS. + * + * @param pbap_pse PBAP PSE object, @ref bt_pbap_pse. + * @param rsp_code Response code, @ref bt_pbap_rsp_code. + * @param buf Buffer containing response headers. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_pbap_pse_pull_vcard_entry_rsp( + struct bt_pbap_pse *pbap_pse, uint8_t rsp_code, struct net_buf *buf); + +/** @brief Send set phone book response from PBAP PSE. + * + * Sends a response to a set phone book request from a PCE client. + * + * @param pbap_pse PBAP PSE object, @ref bt_pbap_pse. + * @param rsp_code Response code, @ref bt_pbap_rsp_code. + * @param buf Buffer containing optional response headers. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_pbap_pse_set_phone_book_rsp( + struct bt_pbap_pse *pbap_pse, uint8_t rsp_code, struct net_buf *buf); + +/** @brief Send abort response from PBAP PSE. + * + * Sends a response to an abort request from a PCE client. + * + * @param pbap_pse PBAP PSE object, @ref bt_pbap_pse. + * @param rsp_code Response code (typically @ref BT_PBAP_RSP_CODE_SUCCESS), + * @ref bt_pbap_rsp_code. + * @param buf Buffer containing optional response headers. + * + * @return 0 on success, negative error code on failure. + */ + +int bt_pbap_pse_abort_rsp(struct bt_pbap_pse *pbap_pse, + uint8_t rsp_code, struct net_buf *buf); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_PBAP_H */ diff --git a/include/nuttx/net/bluetooth/classic/rfcomm.h b/include/nuttx/net/bluetooth/classic/rfcomm.h new file mode 100644 index 0000000000000..1336e3aebd91a --- /dev/null +++ b/include/nuttx/net/bluetooth/classic/rfcomm.h @@ -0,0 +1,641 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/classic/rfcomm.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Bluetooth RFCOMM handling + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_RFCOMM_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_RFCOMM_H + +/** + * @brief RFCOMM + * @defgroup bt_rfcomm RFCOMM + * @since 1.6 + * @version 0.1.0 + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** RFCOMM Maximum Header Size. The length could be 2 bytes, it depends on + * information length. + */ +#define BT_RFCOMM_HDR_MAX_SIZE 4 +/** RFCOMM FCS Size */ +#define BT_RFCOMM_FCS_SIZE 1 +/** RFCOMM Credits Size */ +#define BT_RFCOMM_CREDITS_SIZE 1 + +/** @brief RFCOMM Overhead Size + * + * The overhead size of RFCOMM includes the maximum header size, FCS size, + * and credits size. + * + * For the field credits size, in the CFC supported case, the space of + * credits should be discounted from the maximum frame size. It is used to + * avoid the SDU length exceeding the maximum frame size if the credits field + * is included. + */ +#define BT_RFCOMM_OVERHEAD_SIZE \ +(BT_RFCOMM_HDR_MAX_SIZE + BT_RFCOMM_FCS_SIZE + BT_RFCOMM_CREDITS_SIZE) + +/** @brief Helper to calculate needed buffer size for RFCOMM PDUs. + * Useful for creating buffer pools. + * + * @param mtu Needed RFCOMM PDU MTU. + * + * @return Needed buffer size to match the requested RFCOMM PDU MTU. + */ +#define BT_RFCOMM_BUF_SIZE(mtu) BT_L2CAP_BUF_SIZE(BT_RFCOMM_OVERHEAD_SIZE + (mtu)) + +/* RFCOMM channels (1-30): pre-allocated for profiles to avoid conflicts */ + +enum +{ + BT_RFCOMM_CHAN_HFP_HF = 1, + BT_RFCOMM_CHAN_HFP_AG, + BT_RFCOMM_CHAN_HSP_AG, + BT_RFCOMM_CHAN_HSP_HS, + BT_RFCOMM_CHAN_SPP, + BT_RFCOMM_CHAN_DYNAMIC_START, +}; + +struct bt_rfcomm_dlc; + +/** @brief RFCOMM DLC operations structure. + * + * The object has to stay valid and constant for the lifetime of the DLC. + * + * @note The callbacks are invoked from a thread context, never from an + * ISR. Whether a callback is invoked from a context internal to the stack + * or synchronously from within the API call that triggers it, and from + * which context, is not part of the API and may change between releases. + * See @rstref{Callback execution contexts } + * for the hazards of blocking in a callback and their mitigations. + */ + +struct bt_rfcomm_dlc_ops +{ + /** DLC connected callback + * + * If this callback is provided it will be called whenever the connection + * completes. + * + * @param dlc The dlc that has been connected + */ + + void (*connected)(struct bt_rfcomm_dlc *dlc); + + /** DLC disconnected callback + * + * If this callback is provided it will be called whenever the dlc is + * disconnected, including when a connection gets rejected or cancelled + * (both incoming and outgoing) + * + * @param dlc The dlc that has been Disconnected + */ + + void (*disconnected)(struct bt_rfcomm_dlc *dlc); + + /** DLC recv callback + * + * Called whenever data is received on the DLC. + * + * If processing @p buf requires work that cannot complete immediately + * (e.g. passing data to another thread or a work queue), return @c + * -EINPROGRESS to defer the completion. The callback must not block in + * this case. Then the stack will hold back RX flow-control credits (CFC) + * or assert FC=1 (non-CFC) until @ref bt_rfcomm_dlc_recv_complete is + * called for every outstanding buffer. + * + * When @c -EINPROGRESS is returned, the application takes ownership of + * the @p buf reference and must eventually call @ref + * bt_rfcomm_dlc_recv_complete exactly once, passing back the same @p + * buf pointer. No @ref net_buf_ref or @ref net_buf_unref calls are + * needed around that hand-off. + * + * @warning @ref bt_rfcomm_dlc_recv_complete must be called from a thread + * context after this callback has returned. Calling it from inside + * the recv callback (before it returns) will underflow the in-progress + * counter and cause @ref bt_rfcomm_dlc_recv_complete to fail with @c + * -EINVAL. + * + * @param dlc The dlc receiving data. + * @param buf Buffer containing incoming data. + * + * @retval 0 Data was fully consumed synchronously; the stack retains + * ownership of @p buf. + * @retval -EINPROGRESS Data processing is deferred; the application now + * owns @p buf and must call @ref bt_rfcomm_dlc_recv_complete once + * processing is done. + * @return Other negative error code; the DLC will be actively + * disconnected by the stack. + */ + + int (*recv)(struct bt_rfcomm_dlc *dlc, struct net_buf *buf); + + /** DLC sent callback + * + * @param dlc The dlc which has sent data. + * @param err Sent result. + */ + + void (*sent)(struct bt_rfcomm_dlc *dlc, int err); +}; + +/** @brief Role of RFCOMM session and dlc. Used only by internal APIs + */ + +typedef enum bt_rfcomm_role +{ + BT_RFCOMM_ROLE_ACCEPTOR, + BT_RFCOMM_ROLE_INITIATOR +} +__packed bt_rfcomm_role_t; + +/** @brief RFCOMM DLC structure. */ + +struct bt_rfcomm_dlc +{ + /** @cond INTERNAL_HIDDEN */ + + /** Response Timeout eXpired (RTX) timer. + * + * Used to detect when a peer fails to respond to an RFCOMM command (e.g. + * SABM, DISC) within the allowed time window. + */ + + struct k_work_delayable rtx_work; + + /** Queue for outgoing data. + * + * Holds net_buf fragments waiting to be transmitted over this DLC. Frames + * are dequeued and sent by @p tx_work. + */ + + struct k_fifo tx_queue; + + /** TX credits semaphore. + * + * When Credit-Based Flow Control (CFC) is active this semaphore counts + * the number of frames the remote peer is willing to receive. When CFC is + * not negotiated it is used as a binary semaphore to serialize + * transmissions under aggregate (MSC) flow control. + */ + + sem_t tx_credits; + + /** Worker for RFCOMM TX. + * + * Submitted to the bt work queue whenever there are frames in @p tx_queue + * and credits are available, to drain the queue and push data to L2CAP. + */ + + struct k_work_delayable tx_work; + + /* Retry timeout expired timer */ + + struct k_work_delayable retry_timeout_work; + + /** Pointer to the RFCOMM session this DLC belongs to. */ + + struct bt_rfcomm_session *session; + + /** @endcond */ + + /** Pointer to the application callback operations for this DLC. */ + + struct bt_rfcomm_dlc_ops *ops; + + /** @cond INTERNAL_HIDDEN */ + + /** Internally used field for list handling. */ + + struct list_node _node; + + /** @endcond */ + + /** Minimum security level required before this DLC may be established. */ + + bt_security_t required_sec_level; + + /** @cond INTERNAL_HIDDEN */ + + /** Role of this DLC: initiator or acceptor. */ + + bt_rfcomm_role_t role; + + /** @endcond */ + + /** Maximum Transmission Unit for this DLC in bytes. + * + * Negotiated during the Parameter Negotiation (PN) procedure. Outgoing + * @ref bt_rfcomm_dlc_send buffers must not exceed this value. + */ + + uint16_t mtu; + + /** @cond INTERNAL_HIDDEN */ + + /** Data Link Connection Identifier assigned to this DLC. */ + + uint8_t dlci; + + /** Current connection state of this DLC. + * + * One of the BT_RFCOMM_STATE_* values defined in rfcomm_internal.h. + */ + + uint8_t state; + + /** Number of receive credits remaining for this DLC. + * + * Tracks how many additional UIH frames the local side may accept from + * the remote before flow control must be applied. Initialized from @p + * rx_credit_limit at connection setup and decremented as frames are + * received; refilled by sending credit grants to the remote. + * + * Only meaningful when CFC is enabled. + */ + + uint8_t rx_credit; + + /** @endcond */ + + /** Requested initial number of receive credits for this DLC. + * + * Sets how many UIH frames the local side is willing to accept from the + * remote after the DLC is established. The stack limits this value to + * MIN((BT_BUF_ACL_RX_COUNT - 1), 255) before use. If the field is 0, the + * stack uses the internal default MIN((BT_BUF_ACL_RX_COUNT - 1), 255). + * The result is written back to the field. Also the value of field will + * be limited to 7 when sending in Parameter Negotiation (PN) + * command/response. + * + * @note If the value exceeds MIN((BT_BUF_ACL_RX_COUNT - 1), 255), it will + * be limited to MIN((BT_BUF_ACL_RX_COUNT - 1), 255). + * @note This field can only be modified when the DLC is in an unconnected + * state. This means that from the time @ref bt_rfcomm_server::accept + * returns until + * @ref + * bt_rfcomm_dlc_ops::disconnected is called, it cannot be modified any + * more. + * + * Only meaningful when CFC is enabled. + */ + + uint8_t rx_credit_limit; + + /** @cond INTERNAL_HIDDEN */ + + /** Number of in-progress receive operations for this DLC. + * + * Counts how many @ref bt_rfcomm_dlc_ops::recv callbacks have returned @c + * -EINPROGRESS + * but have not yet been completed by calling @ref + * bt_rfcomm_dlc_recv_complete. + * + * For CFC-supported sessions, this counter is used to delay RX credit + * refill: RX credits are not refilled until the in-progress bufs are + * completed, ensuring the remote peer cannot send more frames than the + * application can handle concurrently. + * + * For non-CFC sessions, this counter gates the MSC flow-control commands: + * - MSC with FC=1 (pause) is sent only when the count is changing from 0 + * to 1 + * - MSC with FC=0 (resume) is sent only when the count is changing from 1 + * to 0 + */ + + atomic_t rx_credit_inprogress; + + /** DLC flags */ + + atomic_t flags; + + /** @endcond */ +}; + +struct bt_rfcomm_server +{ + /** Server Channel + * + * Possible values: 0 A dynamic value will be auto-allocated when + * bt_rfcomm_server_register() is called. + * + * 0x01 - 0x1e Dynamically allocated. May be pre-set by the application + * before server registration (not recommended however), or auto-allocated + * by the stack if the 0 is passed. + */ + + uint8_t channel; + + /** Server accept callback + * + * This callback is called whenever a new incoming connection requires + * authorization. + * + * @param conn The connection that is requesting authorization + * @param server Pointer to the server structure this callback relates to + * @param dlc Pointer to received the allocated dlc + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*accept)(struct bt_conn *conn, struct bt_rfcomm_server *server, + struct bt_rfcomm_dlc **dlc); + + /** @cond INTERNAL_HIDDEN */ + + struct list_node node; + + /** @endcond */ +}; + +/** @brief RFCOMM RPN baud rate values */ + +enum +{ + BT_RFCOMM_RPN_BAUD_RATE_2400 = 0x0, + BT_RFCOMM_RPN_BAUD_RATE_4800 = 0x1, + BT_RFCOMM_RPN_BAUD_RATE_7200 = 0x2, + BT_RFCOMM_RPN_BAUD_RATE_9600 = 0x3, + BT_RFCOMM_RPN_BAUD_RATE_19200 = 0x4, + BT_RFCOMM_RPN_BAUD_RATE_38400 = 0x5, + BT_RFCOMM_RPN_BAUD_RATE_57600 = 0x6, + BT_RFCOMM_RPN_BAUD_RATE_115200 = 0x7, + BT_RFCOMM_RPN_BAUD_RATE_230400 = 0x8 +}; + +/** @brief RFCOMM RPN data bit values */ + +enum +{ + BT_RFCOMM_RPN_DATA_BITS_5 = 0x0, + BT_RFCOMM_RPN_DATA_BITS_6 = 0x1, + BT_RFCOMM_RPN_DATA_BITS_7 = 0x2, + BT_RFCOMM_RPN_DATA_BITS_8 = 0x3 +}; + +/** @brief RFCOMM RPN stop bit values */ + +enum +{ + BT_RFCOMM_RPN_STOP_BITS_1 = 0, + BT_RFCOMM_RPN_STOP_BITS_1_5 = 1 +}; + +/** @brief RFCOMM RPN parity bit values */ + +enum +{ + BT_RFCOMM_RPN_PARITY_NONE = 0x0, + BT_RFCOMM_RPN_PARITY_ODD = 0x1, + BT_RFCOMM_RPN_PARITY_EVEN = 0x3, + BT_RFCOMM_RPN_PARITY_MARK = 0x5, + BT_RFCOMM_RPN_PARITY_SPACE = 0x7 +}; + +/** @brief Combine data bits, stop bits and parity into a single line + * settings byte + * + * @param data Data bits value (0-3) + * @param stop Stop bits value (0-1) + * @param parity Parity value (0-7) + * + * @return Combined line settings byte + */ +#define BT_RFCOMM_SET_LINE_SETTINGS(data, stop, parity) \ +((data & 0x3) | ((stop & 0x1) << 2) | ((parity & 0x7) << 3)) + +#define BT_RFCOMM_RPN_FLOW_NONE 0x00 +#define BT_RFCOMM_RPN_XON_CHAR 0x11 +#define BT_RFCOMM_RPN_XOFF_CHAR 0x13 + +/* Set 1 to all the param mask except reserved */ +#define BT_RFCOMM_RPN_PARAM_MASK_ALL 0x3f7f + +/** @brief RFCOMM Remote Port Negotiation (RPN) structure */ + +struct bt_rfcomm_rpn +{ + uint8_t dlci; + uint8_t baud_rate; + uint8_t line_settings; + uint8_t flow_control; + uint8_t xon_char; + uint8_t xoff_char; + uint16_t param_mask; +} __packed; + +/** @brief Register RFCOMM server + * + * Register RFCOMM server for a channel, each new connection is authorized + * using the accept() callback which in case of success shall allocate the + * dlc structure to be used by the new connection. + * + * @param server Server structure. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_rfcomm_server_register(struct bt_rfcomm_server *server); + +/** @brief Unregister RFCOMM server + * + * Unregister RFCOMM server for a channel. + * + * @param server Server structure. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_rfcomm_server_unregister(struct bt_rfcomm_server *server); + +/** @brief Connect RFCOMM channel + * + * Connect RFCOMM dlc by channel, once the connection is completed dlc + * connected() callback will be called. If the connection is rejected + * disconnected() callback is called instead. + * + * @param conn Connection object. + * @param dlc Dlc object. + * @param channel Server channel to connect to. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_rfcomm_dlc_connect(struct bt_conn *conn, + struct bt_rfcomm_dlc *dlc, uint8_t channel); + +/** @brief Send data to RFCOMM + * + * Send data from buffer to the dlc. Length should be less than or equal to + * mtu. + * + * @param dlc Dlc object. + * @param buf Data buffer. + * + * @return Bytes sent in case of success or negative value in case of error. + */ + +int bt_rfcomm_dlc_send(struct bt_rfcomm_dlc *dlc, struct net_buf *buf); + +/** @brief Disconnect RFCOMM dlc + * + * Disconnect RFCOMM dlc, if the connection is pending it will be canceled + * and as a result the dlc disconnected() callback is called. + * + * @param dlc Dlc object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_rfcomm_dlc_disconnect(struct bt_rfcomm_dlc *dlc); + +/** + * @brief Complete receiving RFCOMM channel data + * + * Must be called exactly once for each invocation of @ref + * bt_rfcomm_dlc_ops::recv that returned @c -EINPROGRESS. Pass back the same + * @p buf pointer that was received in that callback; no additional @ref + * net_buf_ref or @ref net_buf_unref calls are needed. + * + * @note This function must be called from a thread context only, never from + * an ISR. + * + * @note This function must be called only after @ref bt_rfcomm_dlc_ops::recv + * has returned. Calling it from within the recv callback (before it returns) + * underflows the in-progress counter and causes this function to return @c + * -EINVAL. + * + * When this function returns, the stack releases its reference to @p buf + * regardless of the return value, unless @p dlc or @p buf is @c NULL (in + * which case @c -EINVAL is returned immediately and @p buf is not + * touched). + * + * Flow-control behavior after a successful call: + * - CFC sessions: the stack may now send additional RX credits to the remote + * peer, allowing it to send more frames. + * - Non-CFC sessions: an MSC command with FC=0 (resume) is sent to the + * remote peer only when all in-progress buffers are completed. + * + * @param dlc Pointer to the RFCOMM DLC whose receive operation is being + * completed. + * @param buf The buffer that was passed to @ref bt_rfcomm_dlc_ops::recv when + * it returned @c -EINPROGRESS. + * + * @return 0 in case of success or negative value in case of error. + * @retval -EINVAL @p dlc or @p buf is @c NULL, the DLC's CFC state is + * unknown, or the in-progress counter would underflow (more completions than + * in-progress calls). + * @retval -ENOTCONN The DLC has no associated session or is not in the + * connected state. + */ + +int bt_rfcomm_dlc_recv_complete(struct bt_rfcomm_dlc *dlc, + struct net_buf *buf); + +/** @brief Allocate the buffer from pool after reserving head room for + * RFCOMM, L2CAP and ACL headers. + * + * @param pool Which pool to take the buffer from. + * + * @return New buffer. + */ + +struct net_buf *bt_rfcomm_create_pdu(struct net_buf_pool *pool); + +/** + * @brief Send Remote Port Negotiation command + * + * @param dlc Pointer to the RFCOMM DLC + * @param rpn Pointer to the RPN parameters to send + * + * @return 0 on success, negative error code on failure + */ + +int bt_rfcomm_send_rpn_cmd(struct bt_rfcomm_dlc *dlc, + struct bt_rfcomm_rpn *rpn); + +/** @brief Remote Line Status value: No error */ +#define BT_RFCOMM_RLS_NO_ERR (0x00U) + +/** @brief Remote Line Status value: error occurred + * + * @param err Error code to be set in the RLS value; must be one of the + * following values: + * @ref BT_RFCOMM_RLS_ERR_OVERRUN_ERROR, @ref + * BT_RFCOMM_RLS_ERR_PARITY_ERROR, or + * @ref BT_RFCOMM_RLS_ERR_FRAMING_ERROR. + * + * @return RLS value with error code set. + */ +#define BT_RFCOMM_RLS_ERR(err) (BIT(0) | (err)) + +/** @brief Overrun Error - Received character overwrote an unread character */ +#define BT_RFCOMM_RLS_ERR_OVERRUN_ERROR BIT(1) + +/** @brief Parity Error - Received character's parity was incorrect */ +#define BT_RFCOMM_RLS_ERR_PARITY_ERROR BIT(2) + +/** @brief Framing Error - a character did not terminate with a stop bit */ +#define BT_RFCOMM_RLS_ERR_FRAMING_ERROR BIT(3) + +/** + * @brief Send Remote Line Status Command + * + * Send remote line status with specific rls value @p line_status to the + * RFCOMM DLC. For @p line_status, the BIT(4-7) are reserved and must be set + * to 0. The BIT(0-3) indicate the Line Status. If the BIT(0) is set to 0, + * there is no error occurred. If the BIT(0) is set to 1, the error is + * indicated by BIT(1-3) with the following values: + * @ref BT_RFCOMM_RLS_ERR_OVERRUN_ERROR - Received character overwrote an + * unread character. + * @ref BT_RFCOMM_RLS_ERR_PARITY_ERROR - Received character's parity was + * incorrect. + * @ref BT_RFCOMM_RLS_ERR_FRAMING_ERROR - a character did not terminate with + * a stop bit. + * + * @p line_status can be created using @ref BT_RFCOMM_RLS_NO_ERR and @ref + * BT_RFCOMM_RLS_ERR macros. + * + * @param dlc Pointer to the RFCOMM DLC + * @param line_status Line Status value + * + * @return 0 on success, negative error code on failure + */ + +int bt_rfcomm_send_rls_cmd(struct bt_rfcomm_dlc *dlc, uint8_t line_status); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_RFCOMM_H */ diff --git a/include/nuttx/net/bluetooth/classic/sdp.h b/include/nuttx/net/bluetooth/classic/sdp.h new file mode 100644 index 0000000000000..b0881ee6cab99 --- /dev/null +++ b/include/nuttx/net/bluetooth/classic/sdp.h @@ -0,0 +1,1298 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/classic/sdp.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Service Discovery Protocol handling. + */ + +/* Copyright (c) 2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_SDP_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_SDP_H + +/** + * @file + * @brief Service Discovery Protocol (SDP) + * @defgroup bt_sdp Service Discovery Protocol (SDP) + * @since 1.6 + * @version 0.1.0 + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* All definitions are based on Bluetooth Assigned Numbers + * of the Bluetooth Specification + */ + +/** + * @name Service class identifiers of standard services and service groups + * @{ + */ +#define BT_SDP_SDP_SERVER_SVCLASS 0x1000 /**< Service Discovery Server */ +#define BT_SDP_BROWSE_GRP_DESC_SVCLASS 0x1001 /**< Browse Group Descriptor */ +#define BT_SDP_PUBLIC_BROWSE_GROUP 0x1002 /**< Public Browse Group */ +#define BT_SDP_SERIAL_PORT_SVCLASS 0x1101 /**< Serial Port */ +#define BT_SDP_LAN_ACCESS_SVCLASS 0x1102 /**< LAN Access Using PPP */ +#define BT_SDP_DIALUP_NET_SVCLASS 0x1103 /**< Dialup Networking */ +#define BT_SDP_IRMC_SYNC_SVCLASS 0x1104 /**< IrMC Sync */ +#define BT_SDP_OBEX_OBJPUSH_SVCLASS 0x1105 /**< OBEX Object Push */ +#define BT_SDP_OBEX_FILETRANS_SVCLASS 0x1106 /**< OBEX File Transfer */ +#define BT_SDP_IRMC_SYNC_CMD_SVCLASS 0x1107 /**< IrMC Sync Command */ +#define BT_SDP_HEADSET_SVCLASS 0x1108 /**< Headset */ +#define BT_SDP_CORDLESS_TELEPHONY_SVCLASS 0x1109 /**< Cordless Telephony */ +#define BT_SDP_AUDIO_SOURCE_SVCLASS 0x110a /**< Audio Source */ +#define BT_SDP_AUDIO_SINK_SVCLASS 0x110b /**< Audio Sink */ +#define BT_SDP_AV_REMOTE_TARGET_SVCLASS 0x110c /**< A/V Remote Control Target */ +#define BT_SDP_ADVANCED_AUDIO_SVCLASS 0x110d /**< Advanced Audio Distribution */ +#define BT_SDP_AV_REMOTE_SVCLASS 0x110e /**< A/V Remote Control */ +#define BT_SDP_AV_REMOTE_CONTROLLER_SVCLASS 0x110f /**< A/V Remote Control Controller */ +#define BT_SDP_INTERCOM_SVCLASS 0x1110 /**< Intercom */ +#define BT_SDP_FAX_SVCLASS 0x1111 /**< Fax */ +#define BT_SDP_HEADSET_AGW_SVCLASS 0x1112 /**< Headset AG */ +#define BT_SDP_WAP_SVCLASS 0x1113 /**< WAP */ +#define BT_SDP_WAP_CLIENT_SVCLASS 0x1114 /**< WAP Client */ +#define BT_SDP_PANU_SVCLASS 0x1115 /**< Personal Area Networking User */ +#define BT_SDP_NAP_SVCLASS 0x1116 /**< Network Access Point */ +#define BT_SDP_GN_SVCLASS 0x1117 /**< Group Network */ +#define BT_SDP_DIRECT_PRINTING_SVCLASS 0x1118 /**< Direct Printing */ +#define BT_SDP_REFERENCE_PRINTING_SVCLASS 0x1119 /**< Reference Printing */ +#define BT_SDP_IMAGING_SVCLASS 0x111a /**< Basic Imaging Profile */ +#define BT_SDP_IMAGING_RESPONDER_SVCLASS 0x111b /**< Imaging Responder */ +#define BT_SDP_IMAGING_ARCHIVE_SVCLASS 0x111c /**< Imaging Automatic Archive */ +#define BT_SDP_IMAGING_REFOBJS_SVCLASS 0x111d /**< Imaging Referenced Objects */ +#define BT_SDP_HANDSFREE_SVCLASS 0x111e /**< Handsfree */ +#define BT_SDP_HANDSFREE_AGW_SVCLASS 0x111f /**< Handsfree Audio Gateway */ +#define BT_SDP_DIRECT_PRT_REFOBJS_SVCLASS 0x1120 /**< Direct Printing Reference Objects Service */ +#define BT_SDP_REFLECTED_UI_SVCLASS 0x1121 /**< Reflected UI */ +#define BT_SDP_BASIC_PRINTING_SVCLASS 0x1122 /**< Basic Printing */ +#define BT_SDP_PRINTING_STATUS_SVCLASS 0x1123 /**< Printing Status */ +#define BT_SDP_HID_SVCLASS 0x1124 /**< Human Interface Device Service */ +#define BT_SDP_HCR_SVCLASS 0x1125 /**< Hardcopy Cable Replacement */ +#define BT_SDP_HCR_PRINT_SVCLASS 0x1126 /**< HCR Print */ +#define BT_SDP_HCR_SCAN_SVCLASS 0x1127 /**< HCR Scan */ +#define BT_SDP_CIP_SVCLASS 0x1128 /**< Common ISDN Access */ +#define BT_SDP_VIDEO_CONF_GW_SVCLASS 0x1129 /**< Video Conferencing Gateway */ +#define BT_SDP_UDI_MT_SVCLASS 0x112a /**< UDI MT */ +#define BT_SDP_UDI_TA_SVCLASS 0x112b /**< UDI TA */ +#define BT_SDP_AV_SVCLASS 0x112c /**< Audio/Video */ +#define BT_SDP_SAP_SVCLASS 0x112d /**< SIM Access */ +#define BT_SDP_PBAP_PCE_SVCLASS 0x112e /**< Phonebook Access Client */ +#define BT_SDP_PBAP_PSE_SVCLASS 0x112f /**< Phonebook Access Server */ +#define BT_SDP_PBAP_SVCLASS 0x1130 /**< Phonebook Access */ +#define BT_SDP_MAP_MSE_SVCLASS 0x1132 /**< Message Access Server */ +#define BT_SDP_MAP_MCE_SVCLASS 0x1133 /**< Message Notification Server */ +#define BT_SDP_MAP_SVCLASS 0x1134 /**< Message Access Profile */ +#define BT_SDP_GNSS_SVCLASS 0x1135 /**< GNSS */ +#define BT_SDP_GNSS_SERVER_SVCLASS 0x1136 /**< GNSS Server */ +#define BT_SDP_MPS_SC_SVCLASS 0x113a /**< MPS SC */ +#define BT_SDP_MPS_SVCLASS 0x113b /**< MPS */ +#define BT_SDP_PNP_INFO_SVCLASS 0x1200 /**< PnP Information */ +#define BT_SDP_GENERIC_NETWORKING_SVCLASS 0x1201 /**< Generic Networking */ +#define BT_SDP_GENERIC_FILETRANS_SVCLASS 0x1202 /**< Generic File Transfer */ +#define BT_SDP_GENERIC_AUDIO_SVCLASS 0x1203 /**< Generic Audio */ +#define BT_SDP_GENERIC_TELEPHONY_SVCLASS 0x1204 /**< Generic Telephony */ +#define BT_SDP_UPNP_SVCLASS 0x1205 /**< UPnP Service */ +#define BT_SDP_UPNP_IP_SVCLASS 0x1206 /**< UPnP IP Service */ +#define BT_SDP_UPNP_PAN_SVCLASS 0x1300 /**< UPnP IP PAN */ +#define BT_SDP_UPNP_LAP_SVCLASS 0x1301 /**< UPnP IP LAP */ +#define BT_SDP_UPNP_L2CAP_SVCLASS 0x1302 /**< UPnP IP L2CAP */ +#define BT_SDP_VIDEO_SOURCE_SVCLASS 0x1303 /**< Video Source */ +#define BT_SDP_VIDEO_SINK_SVCLASS 0x1304 /**< Video Sink */ +#define BT_SDP_VIDEO_DISTRIBUTION_SVCLASS 0x1305 /**< Video Distribution */ +#define BT_SDP_HDP_SVCLASS 0x1400 /**< HDP */ +#define BT_SDP_HDP_SOURCE_SVCLASS 0x1401 /**< HDP Source */ +#define BT_SDP_HDP_SINK_SVCLASS 0x1402 /**< HDP Sink */ +#define BT_SDP_GENERIC_ACCESS_SVCLASS 0x1800 /**< Generic Access Profile */ +#define BT_SDP_GENERIC_ATTRIB_SVCLASS 0x1801 /**< Generic Attribute Profile */ +#define BT_SDP_APPLE_AGENT_SVCLASS 0x2112 /**< Apple Agent */ + +/** + * @} + */ + +#define BT_SDP_SERVER_RECORD_HANDLE 0x0000 + +/** + * @name Attribute identifier codes + * + * Possible values for attribute-id are listed below. See SDP Spec, section + * "Service Attribute Definitions" for more details. + * + * @{ + */ +#define BT_SDP_ATTR_RECORD_HANDLE 0x0000 /**< Service Record Handle */ +#define BT_SDP_ATTR_SVCLASS_ID_LIST 0x0001 /**< Service Class ID List */ +#define BT_SDP_ATTR_RECORD_STATE 0x0002 /**< Service Record State */ +#define BT_SDP_ATTR_SERVICE_ID 0x0003 /**< Service ID */ +#define BT_SDP_ATTR_PROTO_DESC_LIST 0x0004 /**< Protocol Descriptor List */ +#define BT_SDP_ATTR_BROWSE_GRP_LIST 0x0005 /**< Browse Group List */ +#define BT_SDP_ATTR_LANG_BASE_ATTR_ID_LIST 0x0006 /**< Language Base Attribute ID List */ +#define BT_SDP_ATTR_SVCINFO_TTL 0x0007 /**< Service Info Time to Live */ +#define BT_SDP_ATTR_SERVICE_AVAILABILITY 0x0008 /**< Service Availability */ +#define BT_SDP_ATTR_PROFILE_DESC_LIST 0x0009 /**< Bluetooth Profile Descriptor List */ +#define BT_SDP_ATTR_DOC_URL 0x000a /**< Documentation URL */ +#define BT_SDP_ATTR_CLNT_EXEC_URL 0x000b /**< Client Executable URL */ +#define BT_SDP_ATTR_ICON_URL 0x000c /**< Icon URL */ +#define BT_SDP_ATTR_ADD_PROTO_DESC_LIST 0x000d /**< Additional Protocol Descriptor List */ + +#define BT_SDP_ATTR_GROUP_ID 0x0200 /**< Group ID */ +#define BT_SDP_ATTR_IP_SUBNET 0x0200 /**< IP Subnet */ +#define BT_SDP_ATTR_VERSION_NUM_LIST 0x0200 /**< Version Number List */ +#define BT_SDP_ATTR_SUPPORTED_FEATURES_LIST 0x0200 /**< Supported Features List */ +#define BT_SDP_ATTR_GOEP_L2CAP_PSM 0x0200 /**< GOEP L2CAP PSM */ +#define BT_SDP_ATTR_SVCDB_STATE 0x0201 /**< Service Database State */ + +#define BT_SDP_ATTR_MPSD_SCENARIOS 0x0200 /**< MPSD Scenarios */ +#define BT_SDP_ATTR_MPMD_SCENARIOS 0x0201 /**< MPMD Scenarios */ +#define BT_SDP_ATTR_MPS_DEPENDENCIES 0x0202 /**< Supported Profiles & Protocols */ + +#define BT_SDP_ATTR_SERVICE_VERSION 0x0300 /**< Service Version */ +#define BT_SDP_ATTR_EXTERNAL_NETWORK 0x0301 /**< External Network */ +#define BT_SDP_ATTR_SUPPORTED_DATA_STORES_LIST 0x0301 /**< Supported Data Stores List */ +#define BT_SDP_ATTR_DATA_EXCHANGE_SPEC 0x0301 /**< Data Exchange Specification */ +#define BT_SDP_ATTR_NETWORK 0x0301 /**< Network */ +#define BT_SDP_ATTR_FAX_CLASS1_SUPPORT 0x0302 /**< Fax Class 1 Support */ +#define BT_SDP_ATTR_REMOTE_AUDIO_VOLUME_CONTROL 0x0302 /**< Remote Audio Volume Control */ +#define BT_SDP_ATTR_MCAP_SUPPORTED_PROCEDURES 0x0302 /**< MCAP Supported Procedures */ +#define BT_SDP_ATTR_FAX_CLASS20_SUPPORT 0x0303 /**< Fax Class 2.0 Support */ +#define BT_SDP_ATTR_SUPPORTED_FORMATS_LIST 0x0303 /**< Supported Formats List */ +#define BT_SDP_ATTR_FAX_CLASS2_SUPPORT 0x0304 /**< Fax Class 2 Support (vendor-specific)*/ +#define BT_SDP_ATTR_AUDIO_FEEDBACK_SUPPORT 0x0305 /**< Audio Feedback Support */ +#define BT_SDP_ATTR_NETWORK_ADDRESS 0x0306 /**< Network Address */ +#define BT_SDP_ATTR_WAP_GATEWAY 0x0307 /**< WAP Gateway */ +#define BT_SDP_ATTR_HOMEPAGE_URL 0x0308 /**< Homepage URL */ +#define BT_SDP_ATTR_WAP_STACK_TYPE 0x0309 /**< WAP Stack Type */ +#define BT_SDP_ATTR_SECURITY_DESC 0x030a /**< Security Description */ +#define BT_SDP_ATTR_NET_ACCESS_TYPE 0x030b /**< Net Access Type */ +#define BT_SDP_ATTR_MAX_NET_ACCESSRATE 0x030c /**< Max Net Access Rate */ +#define BT_SDP_ATTR_IP4_SUBNET 0x030d /**< IPv4 Subnet */ +#define BT_SDP_ATTR_IP6_SUBNET 0x030e /**< IPv6 Subnet */ +#define BT_SDP_ATTR_SUPPORTED_CAPABILITIES 0x0310 /**< BIP Supported Capabilities */ +#define BT_SDP_ATTR_SUPPORTED_FEATURES 0x0311 /**< BIP Supported Features */ +#define BT_SDP_ATTR_SUPPORTED_FUNCTIONS 0x0312 /**< BIP Supported Functions */ +#define BT_SDP_ATTR_TOTAL_IMAGING_DATA_CAPACITY 0x0313 /**< BIP Total Imaging Data Capacity */ +#define BT_SDP_ATTR_SUPPORTED_REPOSITORIES 0x0314 /**< Supported Repositories */ +#define BT_SDP_ATTR_MAS_INSTANCE_ID 0x0315 /**< MAS Instance ID */ +#define BT_SDP_ATTR_SUPPORTED_MESSAGE_TYPES 0x0316 /**< Supported Message Types */ +#define BT_SDP_ATTR_PBAP_SUPPORTED_FEATURES 0x0317 /**< PBAP Supported Features */ +#define BT_SDP_ATTR_MAP_SUPPORTED_FEATURES 0x0317 /**< MAP Supported Features */ + +#define BT_SDP_ATTR_SPECIFICATION_ID 0x0200 /**< Specification ID */ +#define BT_SDP_ATTR_VENDOR_ID 0x0201 /**< Vendor ID */ +#define BT_SDP_ATTR_PRODUCT_ID 0x0202 /**< Product ID */ +#define BT_SDP_ATTR_VERSION 0x0203 /**< Version */ +#define BT_SDP_ATTR_PRIMARY_RECORD 0x0204 /**< Primary Record */ +#define BT_SDP_ATTR_VENDOR_ID_SOURCE 0x0205 /**< Vendor ID Source */ + +#define BT_SDP_ATTR_HID_DEVICE_RELEASE_NUMBER 0x0200 /**< HID Device Release Number */ +#define BT_SDP_ATTR_HID_PARSER_VERSION 0x0201 /**< HID Parser Version */ +#define BT_SDP_ATTR_HID_DEVICE_SUBCLASS 0x0202 /**< HID Device Subclass */ +#define BT_SDP_ATTR_HID_COUNTRY_CODE 0x0203 /**< HID Country Code */ +#define BT_SDP_ATTR_HID_VIRTUAL_CABLE 0x0204 /**< HID Virtual Cable */ +#define BT_SDP_ATTR_HID_RECONNECT_INITIATE 0x0205 /**< HID Reconnect Initiate */ +#define BT_SDP_ATTR_HID_DESCRIPTOR_LIST 0x0206 /**< HID Descriptor List */ +#define BT_SDP_ATTR_HID_LANG_ID_BASE_LIST 0x0207 /**< HID Language ID Base List */ +#define BT_SDP_ATTR_HID_SDP_DISABLE 0x0208 /**< HID SDP Disable */ +#define BT_SDP_ATTR_HID_BATTERY_POWER 0x0209 /**< HID Battery Power */ +#define BT_SDP_ATTR_HID_REMOTE_WAKEUP 0x020a /**< HID Remote Wakeup */ +#define BT_SDP_ATTR_HID_PROFILE_VERSION 0x020b /**< HID Profile Version */ +#define BT_SDP_ATTR_HID_SUPERVISION_TIMEOUT 0x020c /**< HID Supervision Timeout */ +#define BT_SDP_ATTR_HID_NORMALLY_CONNECTABLE 0x020d /**< HID Normally Connectable */ +#define BT_SDP_ATTR_HID_BOOT_DEVICE 0x020e /**< HID Boot Device */ +#define BT_SDP_ATTR_HID_SSR_HOST_MAX_LATENCY 0x020f /**< HID SSR Host Max Latency */ +#define BT_SDP_ATTR_HID_SSR_HOST_MIN_TIMEOUT 0x0210 /**< HID SSR Host Min Timeout */ + +/** + * @} + */ + +/* These identifiers are based on the SDP spec stating that + * "base attribute id of the primary (universal) language must be 0x0100" + * + * Other languages should have their own offset; e.g.: + * #define XXXLangBase yyyy + * #define AttrServiceName_XXX 0x0000+XXXLangBase + */ +#define BT_SDP_PRIMARY_LANG_BASE 0x0100 + +#define BT_SDP_ATTR_SVCNAME_PRIMARY (0x0000 + BT_SDP_PRIMARY_LANG_BASE) +#define BT_SDP_ATTR_SVCDESC_PRIMARY (0x0001 + BT_SDP_PRIMARY_LANG_BASE) +#define BT_SDP_ATTR_PROVNAME_PRIMARY (0x0002 + BT_SDP_PRIMARY_LANG_BASE) + +/** + * @name Protocol identifier codes + * @brief Protocol identifiers used in Bluetooth Service Discovery Protocol + * (SDP) + * @note Based on Bluetooth Assigned Numbers specification + * + * Possible values for protocol-id are listed below. See Assigned Numbers + * Spec, section "Protocol Identifiers" for more details. + * + * @{ + */ +#define BT_SDP_PROTO_SDP 0x0001 /**< Service Discovery Protocol */ +#define BT_SDP_PROTO_UDP 0x0002 /**< User Datagram Protocol */ +#define BT_SDP_PROTO_RFCOMM 0x0003 /**< Radio Frequency Communication */ +#define BT_SDP_PROTO_TCP 0x0004 /**< Transmission Control Protocol */ +#define BT_SDP_PROTO_TCS_BIN 0x0005 /**< Telephony Control Specification Binary */ +#define BT_SDP_PROTO_TCS_AT 0x0006 /**< Telephony Control Specification AT */ +#define BT_SDP_PROTO_ATT 0x0007 /**< Attribute Protocol */ +#define BT_SDP_PROTO_OBEX 0x0008 /**< Object Exchange Protocol */ +#define BT_SDP_PROTO_IP 0x0009 /**< Internet Protocol */ +#define BT_SDP_PROTO_FTP 0x000a /**< File Transfer Protocol */ +#define BT_SDP_PROTO_HTTP 0x000c /**< HyperText Transfer Protocol */ +#define BT_SDP_PROTO_WSP 0x000e /**< Wireless Session Protocol */ +#define BT_SDP_PROTO_BNEP 0x000f /**< Bluetooth Network Encapsulation Protocol */ +#define BT_SDP_PROTO_UPNP 0x0010 /**< Universal Plug and Play */ +#define BT_SDP_PROTO_HID 0x0011 /**< Human Interface Device Protocol */ +#define BT_SDP_PROTO_HARDCOPY_CTRL 0x0012 /**< Hardcopy Control Channel */ +#define BT_SDP_PROTO_HARDCOPY_DATA 0x0014 /**< Hardcopy Data Channel */ +#define BT_SDP_PROTO_HARDCOPY_NTF 0x0016 /**< Hardcopy Notification Channel */ +#define BT_SDP_PROTO_AVCTP 0x0017 /**< Audio/Video Control Transport Protocol */ +#define BT_SDP_PROTO_AVDTP 0x0019 /**< Audio/Video Distribution Transport Protocol */ +#define BT_SDP_PROTO_CMTP 0x001b /**< Common ISDN Access Protocol */ +#define BT_SDP_PROTO_MCAP_CTRL 0x001e /**< Multi-Channel Adaptation Protocol Control */ +#define BT_SDP_PROTO_MCAP_DATA 0x001f /**< Multi-Channel Adaptation Protocol Data */ +#define BT_SDP_PROTO_L2CAP 0x0100 /**< Logical Link Control and Adaptation Protocol */ + +/** + * @} + */ + +/** + * @name The Data representation in SDP PDUs (pps 339, 340 of BT SDP Spec) + * + * These are the exact data type+size descriptor values that go into the PDU + * buffer. + * + * The datatype (leading 5bits) + size descriptor (last 3 bits) is 8 bits. + * The size descriptor is critical to extract the right number of bytes for + * the data value from the PDU. + * + * For most basic types, the datatype+size descriptor is straightforward. + * However for constructed types and strings, the size of the data is in the + * next "n" bytes following the 8 bits (datatype+size) descriptor. Exactly + * what the "n" is specified in the 3 bits of the data size descriptor. + * + * TextString and URLString can be of size 2^{8, 16, 32} bytes DataSequence + * and DataSequenceAlternates can be of size 2^{8, 16, 32} The size are + * computed post-facto in the API and are not known apriori. + * + * For the type BT_SDP_UINT128, BT_SDP_INT128, and BT_SDP_UUID128, the + * byteorder of data should be little-endian. Such as, SPP UUID128: + * `00001101-0000-1000-8000-00805F9B34FB` will be represented as {0xFB, 0x34, + * 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x01, 0x11, + * 0x00, 0x00} For UUID 128, @ref BT_SDP_ARRAY_UUID_128 is used to declare + * the array. @{ + */ +#define BT_SDP_DATA_NIL 0x00 /**< Nil, the null type */ +#define BT_SDP_UINT8 0x08 /**< Unsigned 8-bit integer */ +#define BT_SDP_UINT16 0x09 /**< Unsigned 16-bit integer */ +#define BT_SDP_UINT32 0x0a /**< Unsigned 32-bit integer */ +#define BT_SDP_UINT64 0x0b /**< Unsigned 64-bit integer */ +#define BT_SDP_UINT128 0x0c /**< Unsigned 128-bit integer */ +#define BT_SDP_INT8 0x10 /**< Signed 8-bit integer */ +#define BT_SDP_INT16 0x11 /**< Signed 16-bit integer */ +#define BT_SDP_INT32 0x12 /**< Signed 32-bit integer */ +#define BT_SDP_INT64 0x13 /**< Signed 64-bit integer */ +#define BT_SDP_INT128 0x14 /**< Signed 128-bit integer */ +#define BT_SDP_UUID_UNSPEC 0x18 /**< UUID, unspecified size */ +#define BT_SDP_UUID16 0x19 /**< UUID, 16-bit */ +#define BT_SDP_UUID32 0x1a /**< UUID, 32-bit */ +#define BT_SDP_UUID128 0x1c /**< UUID, 128-bit */ +#define BT_SDP_TEXT_STR_UNSPEC 0x20 /**< Text string, unspecified size */ +#define BT_SDP_TEXT_STR8 0x25 /**< Text string, 8-bit length */ +#define BT_SDP_TEXT_STR16 0x26 /**< Text string, 16-bit length */ +#define BT_SDP_TEXT_STR32 0x27 /**< Text string, 32-bit length */ +#define BT_SDP_BOOL 0x28 /**< Boolean */ +#define BT_SDP_SEQ_UNSPEC 0x30 /**< Data element sequence, unspecified size */ +#define BT_SDP_SEQ8 0x35 /**< Data element sequence, 8-bit length */ +#define BT_SDP_SEQ16 0x36 /**< Data element sequence, 16-bit length */ +#define BT_SDP_SEQ32 0x37 /**< Data element sequence, 32-bit length */ +#define BT_SDP_ALT_UNSPEC 0x38 /**< Data element alternative, unspecified size */ +#define BT_SDP_ALT8 0x3d /**< Data element alternative, 8-bit length */ +#define BT_SDP_ALT16 0x3e /**< Data element alternative, 16-bit length */ +#define BT_SDP_ALT32 0x3f /**< Data element alternative, 32-bit length */ +#define BT_SDP_URL_STR_UNSPEC 0x40 /**< URL string, unspecified size */ +#define BT_SDP_URL_STR8 0x45 /**< URL string, 8-bit length */ +#define BT_SDP_URL_STR16 0x46 /**< URL string, 16-bit length */ +#define BT_SDP_URL_STR32 0x47 /**< URL string, 32-bit length */ + +/** + * @} + */ + +#define BT_SDP_TYPE_DESC_MASK 0xf8 +#define BT_SDP_SIZE_DESC_MASK 0x07 +#define BT_SDP_SIZE_INDEX_OFFSET 5 + +/** @brief SDP Generic Data Element Value. */ + +struct bt_sdp_data_elem +{ + uint8_t type; /**< Type of the data element */ + uint32_t data_size; /**< Size of the data element */ + uint32_t total_size; /**< Total size of the data element */ + const void *data; +}; + +/** @brief SDP Attribute Value. */ + +struct bt_sdp_attribute +{ + uint16_t id; /**< Attribute ID */ + struct bt_sdp_data_elem val; /**< Attribute data */ +}; + +/** @brief SDP Service Record Value. */ + +struct bt_sdp_record +{ + uint32_t handle; /**< Redundant, for quick ref */ + struct bt_sdp_attribute *attrs; /**< Base addr of attr array */ + size_t attr_count; /**< Number of attributes */ + uint8_t index; /**< Index of the record in LL */ + struct list_node node; +}; + +/* --------------------------------------------------- ------------------ + * | Service Hdl | Attr list ptr | Attr count | Next | -> | Service Hdl | ... + * --------------------------------------------------- ------------------ + */ + +/** + * @brief Declare an array of 8-bit elements in an attribute. + */ +#define BT_SDP_ARRAY_8(...) ((uint8_t[]){__VA_ARGS__}) + +/** + * @brief Declare an array of 16-bit elements in an attribute. + */ +#define BT_SDP_ARRAY_16(...) ((uint16_t[]){__VA_ARGS__}) + +/** + * @brief Declare an array of 32-bit elements in an attribute. + */ +#define BT_SDP_ARRAY_32(...) ((uint32_t[]){__VA_ARGS__}) + +/** + * @brief Declare a UUID 128 in little-endian format in an attribute. + * + * Helper macro to initialize a 128-bit UUID array value from the readable + * form of UUIDs. + * + * Example of how to declare the UUID `6E400001-B5A3-F393-E0A9-E50E24DCCA9E` + * + * @code BT_SDP_ARRAY_UUID_128(0x6E400001, 0xB5A3, 0xF393, 0xE0A9, + * 0xE50E24DCCA9E) @endcode + * + * Just replace the hyphen by the comma and add `0x` prefixes. + * + * @param w32 First part of the UUID (32 bits) + * @param w1 Second part of the UUID (16 bits) + * @param w2 Third part of the UUID (16 bits) + * @param w3 Fourth part of the UUID (16 bits) + * @param w48 Fifth part of the UUID (48 bits) + * + * @return The comma separated values for UUID 128. + */ +#define BT_SDP_ARRAY_UUID_128(w32, w1, w2, w3, w48) \ +BT_SDP_ARRAY_8(BT_UUID_128_ENCODE(w32, w1, w2, w3, w48)) + +/** + * @brief Declare a fixed-size data element header. + * + * @param _type Data element header containing type and size descriptors. + */ +#define BT_SDP_TYPE_SIZE(_type) \ +.type = _type, .data_size = BIT(_type & BT_SDP_SIZE_DESC_MASK), \ +.total_size = BIT(_type & BT_SDP_SIZE_DESC_MASK) + 1 + +/** + * @brief Declare a variable-size data element header. + * + * @param _type Data element header containing type and size descriptors. + * @param _size The actual size of the data. + */ +#define BT_SDP_TYPE_SIZE_VAR(_type, _size) \ +.type = _type, .data_size = _size, \ +.total_size = BIT((_type & BT_SDP_SIZE_DESC_MASK) - BT_SDP_SIZE_INDEX_OFFSET) + _size + 1 + +/** + * @brief Declare a list of data elements. + */ +#define BT_SDP_DATA_ELEM_LIST(...) ((struct bt_sdp_data_elem[]){__VA_ARGS__}) + +/** + * @brief SDP New Service Record Declaration Macro. + * + * Helper macro to declare a new service record. Default attributes: Record + * Handle, Record State, Language Base, Root Browse Group + * + */ +#define BT_SDP_NEW_SERVICE \ +{BT_SDP_ATTR_RECORD_HANDLE, {BT_SDP_TYPE_SIZE(BT_SDP_UINT32), BT_SDP_ARRAY_32(0)}}, \ + {BT_SDP_ATTR_RECORD_STATE, {BT_SDP_TYPE_SIZE(BT_SDP_UINT32), BT_SDP_ARRAY_32(0)}}, \ + {BT_SDP_ATTR_LANG_BASE_ATTR_ID_LIST, \ + { \ + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 9), \ + BT_SDP_DATA_ELEM_LIST( \ + {BT_SDP_TYPE_SIZE(BT_SDP_UINT16), BT_SDP_ARRAY_8('n', 'e')}, \ + {BT_SDP_TYPE_SIZE(BT_SDP_UINT16), BT_SDP_ARRAY_16(106)}, \ + {BT_SDP_TYPE_SIZE(BT_SDP_UINT16), \ + BT_SDP_ARRAY_16(BT_SDP_PRIMARY_LANG_BASE)}), \ + }}, \ +{ \ + BT_SDP_ATTR_BROWSE_GRP_LIST, \ + { \ + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), \ + BT_SDP_DATA_ELEM_LIST({BT_SDP_TYPE_SIZE(BT_SDP_UUID16), \ + BT_SDP_ARRAY_16(BT_SDP_PUBLIC_BROWSE_GROUP)}, ), \ + } \ +} + +/** + * @brief Generic SDP List Attribute Declaration Macro. + * + * Helper macro to declare a list attribute. + * + * @param _att_id List Attribute ID. + * @param _data_elem_seq Data element sequence for the list. + * @param _type_size SDP type and size descriptor. + */ +#define BT_SDP_LIST(_att_id, _type_size, _data_elem_seq) \ +{ \ + _att_id, \ + { \ + _type_size, _data_elem_seq \ + } \ +} + +/** + * @brief SDP Service ID Attribute Declaration Macro. + * + * Helper macro to declare a service ID attribute. + * + * @param _uuid Service ID 16bit UUID. + */ +#define BT_SDP_SERVICE_ID(_uuid) \ +{ \ + BT_SDP_ATTR_SERVICE_ID, \ + { \ + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), &((struct bt_uuid_16)_uuid) \ + } \ +} + +/** + * @brief SDP Name Attribute Declaration Macro. + * + * Helper macro to declare a service name attribute. + * + * @param _name Service name as a string (up to 256 chars). + */ +#define BT_SDP_SERVICE_NAME(_name) \ +{ \ + BT_SDP_ATTR_SVCNAME_PRIMARY, \ + { \ + BT_SDP_TYPE_SIZE_VAR(BT_SDP_TEXT_STR8, (sizeof(_name) - 1)), _name \ + } \ +} + +/** + * @brief SDP Supported Features Attribute Declaration Macro. + * + * Helper macro to declare supported features of a profile/protocol. + * + * @param _features Feature mask as 16bit unsigned integer. + */ +#define BT_SDP_SUPPORTED_FEATURES(_features) \ +{ \ + BT_SDP_ATTR_SUPPORTED_FEATURES, \ + { \ + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), BT_SDP_ARRAY_16(_features) \ + } \ +} + +/** + * @brief SDP Supported Capabilities Attribute Declaration Macro. + * + * Helper macro to declare supported capabilities of a profile/protocol. + * + * @param _capabilities Capability mask as 8bit unsigned integer. + */ +#define BT_SDP_SUPPORTED_CAPABILITIES(_capabilities) \ +{ \ + BT_SDP_ATTR_SUPPORTED_CAPABILITIES, \ + { \ + BT_SDP_TYPE_SIZE(BT_SDP_UINT8), BT_SDP_ARRAY_8(_capabilities) \ + } \ +} + +/** + * @brief SDP Supported Functions Attribute Declaration Macro. + * + * Helper macro to declare supported functions of a profile/protocol. + * + * @param _functions Function mask as 32bit unsigned integer. + */ +#define BT_SDP_SUPPORTED_FUNCTIONS(_functions) \ +{ \ + BT_SDP_ATTR_SUPPORTED_FUNCTIONS, \ + { \ + BT_SDP_TYPE_SIZE(BT_SDP_UINT32), BT_SDP_ARRAY_32(_functions) \ + } \ +} + +/** + * @brief SDP Service Declaration Macro. + * + * Helper macro to declare a service. + * + * @param _attrs List of attributes for the service record. + */ +#define BT_SDP_RECORD(_attrs) \ +{ \ + .attrs = _attrs, \ + .attr_count = ARRAY_SIZE((_attrs)), \ +} + +/* Server API */ + +/** @brief Register a Service Record. + * + * Register a Service Record. Applications can make use of macros such as + * BT_SDP_DECLARE_SERVICE, BT_SDP_LIST, BT_SDP_SERVICE_ID, + * BT_SDP_SERVICE_NAME, etc. A service declaration must start with + * BT_SDP_NEW_SERVICE. + * + * @param service Service record declared using BT_SDP_DECLARE_SERVICE. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_sdp_register_service(struct bt_sdp_record *service); + +/** @brief Unregister a Service Record. + * + * Remove a previously registered Service Record from the SDP database. The + * function checks for active SDP L2CAP channels and returns -EBUSY if any + * exist. A residual race window remains between the check and the actual + * removal; the caller is responsible for ensuring no new SDP connections + * are established during this call (e.g., by disconnecting BR/EDR links or + * disabling connectable mode beforehand). + * + * @param service Service record to unregister. + * + * @retval 0 Success. + * @retval -EINVAL @p service is NULL. + * @retval -EBUSY An SDP L2CAP channel is currently active. + * @retval -ENOENT @p service is not currently registered. + */ + +int bt_sdp_unregister_service(struct bt_sdp_record *service); + +/* Client API */ + +/** @brief Generic SDP Client Query Result data holder */ + +struct bt_sdp_client_result +{ + /** buffer containing unparsed SDP record result for given UUID */ + + struct net_buf *resp_buf; + + /** flag pointing that there are more result chunks for given UUID */ + + bool next_record_hint; +}; + +/** @brief Helper enum to be used as return value of bt_sdp_discover_func_t. + * The value informs the caller to perform further pending actions or stop + * them. + */ + +enum +{ + BT_SDP_DISCOVER_UUID_STOP = 0, + BT_SDP_DISCOVER_UUID_CONTINUE, +}; + +struct bt_sdp_discover_params; + +/** @typedef bt_sdp_discover_func_t + * + * @brief Callback type reporting to user that there is a resolved result + * on remote for given UUID and the result record buffer can be used by user + * for further inspection. + * + * A function of this type is given by the user to the + * bt_sdp_discover_params object. It'll be called on each valid record + * discovery completion for given UUID. When UUID resolution gives back no + * records then NULL is passed to the user. Otherwise user can get valid + * record(s) and then the internal hint 'next record' is set to false saying + * the UUID resolution is complete or the hint can be set by caller to true + * meaning that next record is available for given UUID. The returned + * function value allows the user to control retrieving follow-up resolved + * records if any. If the user doesn't want to read more resolved records + * for given UUID since current record data fulfills its requirements then + * should return BT_SDP_DISCOVER_UUID_STOP. Otherwise returned value means + * more subcall iterations are allowable. + * + * @param conn Connection object identifying connection to queried remote. + * @param result Object pointing to logical unparsed SDP record collected on + * base of response driven by given discover params. + * @param params Discover parameters. + * + * @return BT_SDP_DISCOVER_UUID_STOP in case of no more need to read next + * record data and continue discovery for given UUID. By returning + * @return BT_SDP_DISCOVER_UUID_CONTINUE user allows this discovery + * continuation. + */ + +typedef uint8_t (*bt_sdp_discover_func_t)( + struct bt_conn *conn, struct bt_sdp_client_result *result, + const struct bt_sdp_discover_params *params); + +/** SDP Discover types */ + +enum +{ + /** Discover Service Search. */ + + BT_SDP_DISCOVER_SERVICE_SEARCH, + + /** Discover Service Attribute. */ + + BT_SDP_DISCOVER_SERVICE_ATTR, + + /** Discover Service Search Attribute. */ + + BT_SDP_DISCOVER_SERVICE_SEARCH_ATTR, +}; + +/** Initializes SDP attribute ID range */ +#define BT_SDP_ATTR_ID_RANGE(beginning, ending) {(beginning), (ending)} + +/** + * @brief SDP attribute ID range + * + * If the beginning attribute ID is same with the ending attribute ID, the + * range represents a single attribute ID. + */ + +struct bt_sdp_attribute_id_range +{ + /** Beginning attribute ID of the range */ + + uint16_t beginning; + + /** Ending attribute ID of the range */ + + uint16_t ending; +}; + +/** @brief SDP attribute ID list for Service Attribute and Service Search + * Attribute transactions + */ + +struct bt_sdp_attribute_id_list +{ + /** Count of the SDP attribute ID range + * + * The encoded list has to fit in an SDP request PDU: a range is encoded in + * 5 bytes, a single attribute ID (equal beginning and ending) in 3 bytes, + * and the list including its 2-byte header is limited to 162 bytes. + * bt_sdp_discover() rejects longer lists. + */ + + size_t count; + + /** Attribute ID range array list */ + + struct bt_sdp_attribute_id_range *ranges; +}; + +/** @brief Main user structure used in SDP discovery of remote. */ + +struct bt_sdp_discover_params +{ + struct list_node _node; + union + { + /** UUID (service) to be discovered on remote SDP entity */ + + const struct bt_uuid *uuid; + + /** Service record handle */ + + uint32_t handle; + }; + + /** Discover callback to be called on resolved SDP record */ + + bt_sdp_discover_func_t func; + + /** Memory buffer enabled by user for SDP query results */ + + struct net_buf_pool *pool; + + /** Discover type */ + + uint8_t type; +/* Attribute ID list for Service Attribute and Service Search Attribute + * transactions + * + * If the `ids` is NULL or `ids->count` is 0, the default range `(0x0000, + * 0xffff)` will be used. + */ + + struct bt_sdp_attribute_id_list *ids; +}; + +/** @brief Allows user to start SDP discovery session. + * + * The function performs SDP service discovery on remote server driven by + * user delivered discovery parameters. Discovery session is made as soon as + * no SDP transaction is ongoing between peers and if any then this one is + * queued to be processed at discovery completion of previous one. On the + * service discovery completion the callback function will be called to get + * feedback to user about findings. + * + * If the UUID is UUID 128 for discovery type `Service Search` and `Service + * Search Attribute`, the UUID data should be represented as the + * little-endian byteorder sequence. + * + * Service Search: The SDP Client generates an SDP_SERVICE_SEARCH_REQ to + * locate service records that match the service search pattern + * (`params->uuid`) given as the first parameter of the PDU. Service + * Attribute: The SDP Client generates an SDP_SERVICE_ATTR_REQ to retrieve + * specified attribute values from a specific service record + * (`params->handle`). The AttributeIDList is specified by `params->ids`. + * Service Search Attribute: The SDP Client generates an + * SDP_SERVICE_SEARCH_ATTR_REQ to retrieve specified attribute values that + * match the service search pattern (`params->uuid`) given as the first + * parameter of the PDU. The AttributeIDList is specified by `params->ids`. + * + * @param conn Object identifying connection to remote. + * @param params SDP discovery parameters. + * + * @retval 0 The request @p params has been put to request pending queue. + * The SDP discovery result will be notified through the `params->func` ( + * @ref bt_sdp_discover_params.func ). If the parameter + * `result->resp_buf` ( + * @ref bt_sdp_client_result.resp_buf ) is NULL, the + * discovery is failed or no SDP record discovered. + * @retval -ENOTCONN The connection @p conn is invalid or not established. + * @retval -EINVAL The request @p params is invalid, or an attribute ID list + * that does not fit in a request PDU. + */ + +int bt_sdp_discover(struct bt_conn *conn, + struct bt_sdp_discover_params *params); + +/** @brief Release waiting SDP discovery request. + * + * Cancels a waiting SDP client request identified by the SDP discovery + * parameters object, i.e. one that was queued with bt_sdp_discover() but + * whose resolution has not started yet. On success the discovery callback + * will not be called for the request and the parameters object may be + * reused immediately. + * + * A request that is already being resolved cannot be canceled; in that case + * -EINPROGRESS is returned and the callback will still be called. + * + * @param conn Object identifying connection to remote. + * @param params SDP discovery parameters. + * + * @retval 0 The request was canceled. + * @retval -EINVAL @p conn or @p params is NULL. + * @retval -EINPROGRESS The request is already being resolved. + * @retval -ESRCH No such request is waiting. + */ + +int bt_sdp_discover_cancel(struct bt_conn *conn, + struct bt_sdp_discover_params *params); + +/* Helper types & functions for SDP client to get essential data from server + */ + +/** @brief Give to user parameter value related to given stacked protocol + * UUID. + * + * API extracts specific parameter associated with given protocol UUID + * available in Protocol Descriptor List attribute. + * + * @param buf Original buffered raw record data. + * @param proto Known protocol to be checked like RFCOMM or L2CAP. + * @param param On success populated by found parameter value. + * + * @return 0 on success when specific parameter associated with given + * protocol value is found, or negative if error occurred during processing. + */ + +int bt_sdp_get_proto_param(const struct net_buf *buf, + uint16_t proto, uint16_t *param); + +/** @brief Get additional parameter value related to given stacked protocol + * UUID. + * + * API extracts specific parameter associated with given protocol UUID + * available in Additional Protocol Descriptor List attribute. + * + * @param buf Original buffered raw record data. + * @param proto Known protocol to be checked like RFCOMM or L2CAP. + * @param index Zero-based index of the protocol descriptor + * (alternative protocol stack). Use 0 for the first + * protocol descriptor, 1 for the second, etc. + * @param[out] param On success populated by found parameter value. + * + * @return 0 on success when a specific parameter associated with a given + * protocol value is found, or negative if error occurred during processing. + */ + +int bt_sdp_get_addl_proto_param( + const struct net_buf *buf, uint16_t proto, uint8_t index, uint16_t *param); + +/** @brief Get profile version. + * + * Helper API extracting remote profile version number. To get it proper + * generic profile parameter needs to be selected usually listed in SDP + * Interoperability Requirements section for given profile specification. + * + * @param buf Original buffered raw record data. + * @param profile Profile family identifier the profile belongs. + * @param version On success populated by found version number. + * + * @return 0 on success, negative value if error occurred during processing. + */ + +int bt_sdp_get_profile_version(const struct net_buf *buf, + uint16_t profile, uint16_t *version); + +/** @brief Get SupportedFeatures attribute value + * + * Allows if exposed by remote retrieve SupportedFeature attribute. + * + * @param buf Buffer holding original raw record data from remote. + * @param features On success object to be populated with SupportedFeature + * mask. + * + * @return 0 on success if feature found and valid, negative in case any + * error + */ + +int bt_sdp_get_features(const struct net_buf *buf, uint16_t *features); + +/** @brief Get Vendor ID + * + * Helper API extracting remote Vendor ID. To get it proper generic profile + * parameter needs to be selected usually listed in SDP Interoperability + * Requirements section for given profile specification. + * + * @param buf Buffer holding original raw record data from remote. + * @param vendor_id On success populated by found Vendor ID. + * + * @return 0 on success if vendor_id found and valid, negative in case any + * error + */ + +int bt_sdp_get_vendor_id(const struct net_buf *buf, uint16_t *vendor_id); + +/** @brief Get Product ID + * + * Helper API extracting remote Product ID. To get it proper generic profile + * parameter needs to be selected usually listed in SDP Interoperability + * Requirements section for given profile specification. + * + * @param buf Buffer holding original raw record data from remote. + * @param product_id On success populated by found Product ID. mask. + * + * @return 0 on success if product_id found and valid, negative in case any + * error + */ + +int bt_sdp_get_product_id(const struct net_buf *buf, uint16_t *product_id); + +/** @brief Iterate through discovered SDP record + * + * Helper API iterating through the discovered SDP record. It parses the + * specified SDP RAW record data in the provided buffer and calls the + * specified callback function for each attribute found. The iteration + * continues until all attributes have been processed or the callback + * function returns false to stop the iteration. + * + * @param buf Buffer holding original discovered raw SDP record data + * from remote. + * @param func Callback function to be called for each attribute. The + * callback receives a pointer to the current attribute and + * user-provided data. Return the `true` to continue + * iteration, `false` to stop. + * @param user_data Optional user-provided context data passed to the + * callback function. + * + * @return 0 on success, negative value on error during the parsing or + * iteration. + */ + +int bt_sdp_record_parse(const struct net_buf *buf, +bool (*func)(const struct bt_sdp_attribute *attr, + void *user_data), + void *user_data); + +/** @brief check if the specific attribute identifier is found. + * + * API checks if the specific attribute identifier is found. + * + * @param buf Buffer containing the discovered SDP record data to be + * parsed. + * @param attr_id Specific attribute identifier. + * + * @return true if attribute identifier is found, false otherwise. + */ + +bool bt_sdp_has_attr(const struct net_buf *buf, uint16_t attr_id); + +/** @brief Get a specific attribute by ID from SDP record + * + * This function retrieves a specific attribute from the SDP record data by + * searching for the given attribute identifier. + * + * @param buf Buffer containing the discovered SDP record data to be + * parsed. + * @param attr_id The specific attribute identifier to search for. + * @param[out] attr Pointer to bt_sdp_attribute structure to store the found + * attribute. + * + * @return 0 on success when the attribute is found and populated, + * negative value if the attribute is not found or an error occurred. + */ + +int bt_sdp_get_attr(const struct net_buf *buf, + uint16_t attr_id, struct bt_sdp_attribute *attr); + +/** @brief SDP attribute value for unsigned integer types + * + * Structure to hold parsed unsigned integer values from SDP attributes. The + * size field indicates the actual size of the integer value. + */ + +struct bt_sdp_attr_value_uint +{ + /** Size of the unsigned integer value in bytes */ + + uint8_t size; + + /** Union containing the unsigned integer value based on size */ + + union + { + uint8_t u8; /**< 8-bit unsigned integer value */ + uint16_t u16; /**< 16-bit unsigned integer value */ + uint32_t u32; /**< 32-bit unsigned integer value */ + uint64_t u64; /**< 64-bit unsigned integer value */ + + /** 128-bit unsigned integer value as byte array */ + + uint8_t u128[BIT(BT_SDP_UINT128 & BT_SDP_SIZE_DESC_MASK)]; + }; +}; + +/** @brief SDP attribute value for signed integer types + * + * Structure to hold parsed signed integer values from SDP attributes. The + * size field indicates the actual size of the integer value. + */ + +struct bt_sdp_attr_value_int +{ + /** Size of the signed integer value in bytes */ + + uint8_t size; + + /** Union containing the signed integer value based on size */ + + union + { + int8_t s8; /**< 8-bit signed integer value */ + int16_t s16; /**< 16-bit signed integer value */ + int32_t s32; /**< 32-bit signed integer value */ + int64_t s64; /**< 64-bit signed integer value */ + + /** 128-bit signed integer value as byte array */ + + uint8_t s128[BIT(BT_SDP_INT128 & BT_SDP_SIZE_DESC_MASK)]; + }; +}; + +/** @brief SDP attribute value for text string types + * + * Structure to hold parsed text string values from SDP attributes. + */ + +struct bt_sdp_attr_value_text +{ + /** Length of the text string in bytes */ + + uint32_t len; + + /** Pointer to the text string data */ + + uint8_t *text; +}; + +/** @brief SDP attribute value for URL string types + * + * Structure to hold parsed URL string values from SDP attributes. + */ + +struct bt_sdp_attr_value_url +{ + /** Length of the URL string in bytes */ + + uint32_t len; + + /** Pointer to the URL string data */ + + uint8_t *url; +}; + +/** @brief SDP attribute value type enumeration + * + * Enumeration defining the different types of attribute values that can be + * parsed from SDP attributes. + */ + +enum bt_sdp_attr_value_type +{ + BT_SDP_ATTR_VALUE_TYPE_NONE = 0, /**< No value */ + BT_SDP_ATTR_VALUE_TYPE_UINT, /**< Unsigned integer */ + BT_SDP_ATTR_VALUE_TYPE_SINT, /**< Signed integer */ + BT_SDP_ATTR_VALUE_TYPE_BOOL, /**< Boolean */ + BT_SDP_ATTR_VALUE_TYPE_TEXT, /**< Text string */ + BT_SDP_ATTR_VALUE_TYPE_URL, /**< URL string */ +}; + +/** @brief SDP attribute value container structure + * + * Structure that holds a parsed attribute value of any supported type. The + * type field indicates which member of the union contains the actual value. + * This structure provides a unified way to handle different types of SDP + * attribute values after parsing. + */ + +struct bt_sdp_attr_value +{ + /** Type of the attribute value */ + + enum bt_sdp_attr_value_type type; + + /** Union containing the actual attribute value based on type */ + + union + { + /**< Boolean value (when type is BT_SDP_ATTR_VALUE_TYPE_BOOL) */ + + bool value; + + /**< Unsigned integer value (when type is BT_SDP_ATTR_VALUE_TYPE_UINT) */ + + struct bt_sdp_attr_value_uint uint; + + /**< Signed integer value (when type is BT_SDP_ATTR_VALUE_TYPE_SINT) */ + + struct bt_sdp_attr_value_int sint; + + /**< Text string value (when type is BT_SDP_ATTR_VALUE_TYPE_TEXT) */ + + struct bt_sdp_attr_value_text text; + + /**< URL string value (when type is BT_SDP_ATTR_VALUE_TYPE_URL) */ + + struct bt_sdp_attr_value_url url; + }; +}; + +/** @brief SDP attribute value pair structure + * + * Structure containing parsed attribute value information including the + * UUID identifier and the actual value with its type. This structure is + * used to associate a UUID with its corresponding attribute value, + * providing a complete representation of a parsed SDP attribute element. + * + * @note The UUID and value pointers will be invalid if the callback + * function returned. + * + * @note The UUID or value pointer may be NULL. If the UUID is NULL, it + * means the value does not belong to any UUID. If the value is NULL, it + * means UUID has not value. + */ + +struct bt_sdp_attr_value_pair +{ + /** UUID identifier for the attribute */ + + struct bt_uuid *uuid; + + /** Pointer to the parsed attribute value */ + + struct bt_sdp_attr_value *value; +}; + +/** @brief Parse SDP attribute values + * + * Parses the SDP attribute and calls the provided callback function for + * each parsed value found within the attribute. This function handles + * complex attribute structures that may contain multiple values. + * + * @param attr Pointer to the SDP attribute to be parsed. + * @param func Callback function to be called for each parsed value. + * The callback receives a bt_sdp_attr_value_pair structure + * containing the parsed value and user data. Return true + * to continue parsing, false to stop. + * @param user_data Optional user-provided context data passed to the + * callback. + * + * @return 0 on success, negative value if an error occurred during parsing. + */ + +int bt_sdp_attr_value_parse(const struct bt_sdp_attribute *attr, +bool (*func)(const struct bt_sdp_attr_value_pair *value, + void *user_data), + void *user_data); + +/** @brief Check if SDP attribute contains a specific UUID + * + * This function checks whether the specified SDP attribute contains the + * given UUID. This is useful for verifying if an attribute references a + * particular service or protocol UUID. + * + * @param attr Pointer to the SDP attribute to be checked. + * @param uuid Pointer to the UUID to search for within the attribute. + * + * @return true if the attribute contains the specified UUID, false + * otherwise. + */ + +bool bt_sdp_attr_has_uuid(const struct bt_sdp_attribute *attr, + const struct bt_uuid *uuid); + +/** @brief Read SDP attribute value for a specific UUID + * + * This function reads and extracts the attribute value associated with the + * specified UUID from the given SDP attribute. The parsed value is stored + * in the provided bt_sdp_attr_value structure. + * + * @param attr Pointer to the SDP attribute to read from. + * @param uuid Pointer to the UUID identifier for the value to be + * read. + * @param[out] value Pointer to bt_sdp_attr_value structure to store the + * extracted attribute value. + * + * @return 0 on success when the value is found and extracted, negative + * value if the UUID is not found or an error occurred. + */ + +int bt_sdp_attr_read( + const struct bt_sdp_attribute *attr, const struct bt_uuid *uuid, + struct bt_sdp_attr_value *value); + +/** @brief Parse Additional Protocol Descriptor List attribute + * + * This function parses the Additional Protocol Descriptor List attribute + * and calls the provided callback function for each protocol descriptor + * found within the attribute. The Additional Protocol Descriptor List + * contains alternative protocol stacks that can be used to access the + * service. + * + * @param attr Pointer to the SDP attribute containing Additional + * Protocol Descriptor List. + * @param func Callback function to be called for each protocol + * descriptor. The callback receives a pointer to the + * protocol descriptor attribute and user-provided data. + * Return true to continue parsing, false to stop. + * @param user_data Optional user-provided context data passed to the + * callback function. + * + * @return 0 on success, negative value if an error occurred during parsing. + */ + +int bt_sdp_attr_addl_proto_parse(const struct bt_sdp_attribute *attr, +bool (*func)(const struct bt_sdp_attribute *attr, + void *user_data), + void *user_data); + +/** @brief Get count of protocol descriptors in Additional Protocol + * Descriptor List + * + * This function returns the number of protocol descriptors contained in the + * Additional Protocol Descriptor List attribute. This is useful for + * determining how many alternative protocol stacks are available for the + * service. + * + * @param attr Pointer to the SDP attribute containing Additional Protocol + * Descriptor List. + * + * @return Number of protocol descriptors found on success, negative value + * if an error occurred during parsing. + */ + +ssize_t bt_sdp_attr_addl_proto_count(const struct bt_sdp_attribute *attr); + +/** @brief Read value from Additional Protocol Descriptor List for specific + * index and UUID + * + * This function searches the Additional Protocol Descriptor List attribute + * for a specific protocol UUID at the given protocol descriptor index and + * extracts its associated parameter value. The index parameter specifies + * which protocol descriptor (alternative protocol stack) to search within, + * and the UUID identifies the specific protocol within that descriptor. + * + * @param attr Pointer to the SDP attribute containing Additional + * Protocol Descriptor List. + * @param index Zero-based index of the protocol descriptor + * (alternative protocol stack). Use 0 for the first + * protocol descriptor, 1 for the second, etc. + * @param uuid Pointer to the UUID identifier of the protocol to + * search for within the specified protocol descriptor. + * @param[out] value Pointer to bt_sdp_attr_value structure to store the + * extracted parameter value. + * + * @return 0 on success when the protocol and its parameter are found and + * extracted, negative value if the protocol descriptor index is invalid, + * the UUID is not found within the specified descriptor, or an error + * occurred during parsing. + */ + +int bt_sdp_attr_addl_proto_read( + const struct bt_sdp_attribute *attr, uint16_t index, + const struct bt_uuid *uuid, struct bt_sdp_attr_value *value); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_CLASSIC_SDP_H */ diff --git a/include/nuttx/net/bluetooth/compat/device.h b/include/nuttx/net/bluetooth/compat/device.h new file mode 100644 index 0000000000000..3e3d2ac9dd6ce --- /dev/null +++ b/include/nuttx/net/bluetooth/compat/device.h @@ -0,0 +1,87 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/compat/device.h + * + * Author: chao an + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_DEVICE_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_DEVICE_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Minimal device model for BT HCI driver binding */ + +struct device_state +{ + bool initialized; + int init_res; +}; + +struct device +{ + const char *name; + const void *config; + const void *api; + void *data; + struct device_state *state; +}; + +static inline bool device_is_ready(const struct device *dev) +{ + if (dev == NULL) + { + return true; + } + + return dev->state && dev->state->initialized && dev->state->init_res == 0; +} + +#define DEVICE_DECLARE(name) extern const struct device name +#define DEVICE_NAME_GET(dev) ((dev)->name) + +/* NuttX-compatible HCI driver interface */ + +struct bt_hci_driver +{ + const struct device dev; + int (*open)(void); + int (*send)(const uint8_t *data, size_t len); + int (*close)(void); + void *priv; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_DEVICE_H */ diff --git a/include/nuttx/net/bluetooth/compat/drivers/bluetooth.h b/include/nuttx/net/bluetooth/compat/drivers/bluetooth.h new file mode 100644 index 0000000000000..93dd311123821 --- /dev/null +++ b/include/nuttx/net/bluetooth/compat/drivers/bluetooth.h @@ -0,0 +1,116 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/compat/drivers/bluetooth.h + * + * Author: chao an + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_DRIVERS_BLUETOOTH_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_DRIVERS_BLUETOOTH_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* BT HCI driver interface - NuttX native */ + +#ifndef _BT_HCI_EVT_HDR_DEFINED +#define _BT_HCI_EVT_HDR_DEFINED +struct bt_hci_evt_hdr +{ + uint8_t evt; + uint8_t len; +} +__attribute__((packed)); +#endif + +#ifndef _BT_HCI_ACL_HDR_DEFINED +#define _BT_HCI_ACL_HDR_DEFINED +struct bt_hci_acl_hdr +{ + uint16_t handle; + uint16_t len; +} +__attribute__((packed)); +#endif + +#ifndef _BT_HCI_ISO_HDR_DEFINED +#define _BT_HCI_ISO_HDR_DEFINED +struct bt_hci_iso_hdr +{ + uint16_t handle; + uint16_t len; +} +__attribute__((packed)); +#endif + +/* HCI driver vtable */ + +struct bt_hci_driver_api +{ + int (*open)(void); + int (*send)(const uint8_t *data, size_t len); + int (*close)(void); +}; + +/* HCI driver quirks (bitmask) */ +#define BT_HCI_QUIRK_NO_RESET BIT(0) +#define BT_HCI_QUIRK_NO_AUTO_DLE BIT(1) +#define BT_HCI_QUIRK_NO_FLOW_CONTROL BIT(2) + +/* Forward declarations */ + +struct device; +struct net_buf; +#include + +/* HCI driver data */ + +struct bt_hci_driver_data +{ + bt_addr_t public_addr; +}; + +/* HCI driver API functions */ + +typedef int (*bt_hci_recv_t)(const struct device *dev, struct net_buf *buf); + +int bt_hci_send(const struct device *dev, struct net_buf *buf); +int bt_hci_open(const struct device *dev, bt_hci_recv_t recv); +int bt_hci_close(const struct device *dev); +bool bt_hci_can_close(const struct device *dev); +void bt_hci_set_public_addr(const struct device *dev, const bt_addr_t *addr); +const bt_addr_t *bt_hci_get_public_addr(const struct device *dev); + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_DRIVERS_BLUETOOTH_H */ diff --git a/include/nuttx/net/bluetooth/compat/drivers/bluetooth/hci_lockstep.h b/include/nuttx/net/bluetooth/compat/drivers/bluetooth/hci_lockstep.h new file mode 100644 index 0000000000000..2b313810a2b4c --- /dev/null +++ b/include/nuttx/net/bluetooth/compat/drivers/bluetooth/hci_lockstep.h @@ -0,0 +1,70 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/compat/drivers/bluetooth/hci_lockstep.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Bluetooth HCI lockstep command helper. + */ + +/* Copyright (c) 2026 Silicon Laboratories Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_DRIVERS_BLUETOOTH_HCI_LOCKSTEP_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_DRIVERS_BLUETOOTH_HCI_LOCKSTEP_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef int (*bt_hci_lockstep_send_t)( + const struct device *dev, const uint8_t *pkt, size_t len); + +struct bt_hci_lockstep +{ + const struct device *dev; + bt_hci_lockstep_send_t send; + sem_t changed; + spinlock_t lock; + struct net_buf_simple *rsp; + uint16_t opcode; + uint8_t state; + uint8_t status; + clock_t timeout; +}; + +void bt_hci_lockstep_init( + struct bt_hci_lockstep *ls, const struct device *dev, + bt_hci_lockstep_send_t send); +void bt_hci_lockstep_reset(struct bt_hci_lockstep *ls); +bool bt_hci_lockstep_feed(struct bt_hci_lockstep *ls, + const uint8_t *pkt, size_t len); +int bt_hci_lockstep_cmd_send_sync( + struct bt_hci_lockstep *ls, uint16_t opcode, struct net_buf_simple *cmd, + struct net_buf_simple *rsp); + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_DRIVERS_BLUETOOTH_HCI_LOCKSTEP_H */ diff --git a/include/nuttx/net/bluetooth/compat/init.h b/include/nuttx/net/bluetooth/compat/init.h new file mode 100644 index 0000000000000..660b9c6b62955 --- /dev/null +++ b/include/nuttx/net/bluetooth/compat/init.h @@ -0,0 +1,91 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/compat/init.h + * + * Author: chao an + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_INIT_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_INIT_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include "sys/iterable_sections.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Init levels */ +#define PRE_KERNEL_1 0 +#define PRE_KERNEL_2 1 +#define POST_KERNEL 2 +#define APPLICATION 3 +#define SMP_BOOT 4 + +struct init_entry +{ + int (*init)(void); + int level; + int prio; + const char *name; +}; + +/* Default priorities used by NuttX Bluetooth code when NuttX config does not + * provide an equivalent symbol. + */ +#ifndef CONFIG_KERNEL_INIT_PRIORITY_DEFAULT +#define CONFIG_KERNEL_INIT_PRIORITY_DEFAULT 40 +#endif + +#ifndef CONFIG_APPLICATION_INIT_PRIORITY +#define CONFIG_APPLICATION_INIT_PRIORITY 90 +#endif + +/* SYS_INIT macro - creates an init entry collected by defines.c */ +#define SYS_INIT(init_fn, init_level, init_prio) \ +const struct init_entry _bt_init_##init_fn __attribute__((__used__)) = { \ + .init = init_fn, \ + .level = init_level, \ + .prio = init_prio, \ + .name = #init_fn, \ +} + +/* SYS_INIT_NAMED - same with explicit name */ +#define SYS_INIT_NAMED(name, init_fn, init_level, init_prio) \ +const struct init_entry _bt_init_##name __attribute__((__used__)) = { \ + .init = init_fn, \ + .level = init_level, \ + .prio = init_prio, \ + .name = #name, \ +} + +int bluetooth_initialize(void); + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_INIT_H */ diff --git a/include/nuttx/net/bluetooth/compat/kernel.h b/include/nuttx/net/bluetooth/compat/kernel.h new file mode 100644 index 0000000000000..f65efa10eca90 --- /dev/null +++ b/include/nuttx/net/bluetooth/compat/kernel.h @@ -0,0 +1,271 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/compat/kernel.h + * + * Author: chao an + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_KERNEL_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_KERNEL_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sys/util.h" +#include "toolchain.h" +#include "init.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* ===== FIFO / Queue ===== */ + +struct k_fifo +{ + struct list_node data_q; + struct list_node poll_events; + sem_t sem; + spinlock_t lock; /* Use raw spinlock for ISR safety */ + unsigned int waiters; + unsigned int cancel_waiters; +}; + +void k_fifo_init(struct k_fifo *fifo); +void k_fifo_cancel_wait(struct k_fifo *fifo); +void k_fifo_put(struct k_fifo *fifo, void *data); +void *k_fifo_get(struct k_fifo *fifo, clock_t timeout); +bool k_fifo_is_empty(struct k_fifo *fifo); +void *k_fifo_peek_head(struct k_fifo *fifo); +void z_k_queue_prepend_fifo(struct k_fifo *fifo, void *data); +bool z_k_queue_remove_fifo(struct k_fifo *fifo, void *data); + +#define k_queue k_fifo +#define k_queue_init(queue) k_fifo_init(queue) +#define k_queue_append(queue, data) k_fifo_put(queue, data) +#define k_queue_get(queue, timeout) k_fifo_get(queue, timeout) +#define k_queue_peek_head(queue) k_fifo_peek_head(queue) +#define k_queue_prepend(queue, data) z_k_queue_prepend_fifo(queue, data) +#define k_queue_remove(queue, data) z_k_queue_remove_fifo(queue, data) + +#define K_FIFO_DEFINE(name) \ +struct k_fifo name = { \ + .data_q = LIST_INITIAL_VALUE(name.data_q), \ + .poll_events = LIST_INITIAL_VALUE(name.poll_events), \ + .sem = SEM_INITIALIZER(0), \ +} + +/* ===== Work Queue ===== */ + +/* Work flags for k_work */ +#define K_WORK_QUEUED BIT(0) +#define K_WORK_DELAYED BIT(1) +#define K_WORK_RUNNING BIT(2) + +struct k_work +{ + struct work_s work; + void (*handler)(struct k_work *work); + struct k_work_q *queue; + uint32_t flags; +}; + +struct k_work_delayable +{ + struct k_work work; + struct k_work_q *queue; +}; + +struct k_work_q +{ + FAR struct kwork_wqueue_s *wqueue; + int qid; + pid_t thread_id; +}; + +struct k_work_queue_config +{ + const char *name; + bool no_yield; + bool essential; +}; + +/* Work queue synchronization */ + +struct k_work_sync +{ + sem_t sem; +}; + +typedef void (*k_work_handler_t)(struct k_work *work); + +#define Z_WORK_INITIALIZER(work_handler) {.handler = (k_work_handler_t)(work_handler)} + +#define Z_WORK_DELAYABLE_INITIALIZER(work_handler) {.work = Z_WORK_INITIALIZER(work_handler)} + +#define K_WORK_DEFINE(work, work_handler) struct k_work work = {.handler = (work_handler)} + +#define K_WORK_DELAYABLE_DEFINE(dwork, work_handler) \ +struct k_work_delayable dwork = {.work = {.handler = (work_handler)}} + +void k_work_init(struct k_work *work, k_work_handler_t handler); +void k_work_init_delayable(struct k_work_delayable *dwork, + k_work_handler_t handler); +int k_work_submit(struct k_work *work); +int k_work_submit_to_queue(struct k_work_q *queue, struct k_work *work); +int k_work_schedule(struct k_work_delayable *dwork, clock_t delay); +int k_work_schedule_for_queue( + struct k_work_q *queue, struct k_work_delayable *dwork, clock_t delay); +int k_work_cancel_delayable(struct k_work_delayable *dwork); +int k_work_cancel_delayable_sync(struct k_work_delayable *dwork, + struct k_work_sync *sync); +int k_work_reschedule(struct k_work_delayable *dwork, clock_t delay); +int k_work_reschedule_for_queue( + struct k_work_q *queue, struct k_work_delayable *dwork, clock_t delay); +bool k_work_is_pending(const struct k_work *work); +int k_work_busy_get(const struct k_work *work); +static inline bool k_work_delayable_is_pending( + const struct k_work_delayable *dwork) +{ + return k_work_is_pending(&dwork->work); +} +int k_work_flush(struct k_work *work, struct k_work_sync *sync); + +int k_work_cancel(struct k_work *work); +int k_work_cancel_sync(struct k_work *work, struct k_work_sync *sync); +struct k_work_delayable *k_work_delayable_from_work(struct k_work *work); +int k_work_delayable_busy_get(const struct k_work_delayable *dwork); +clock_t k_work_delayable_remaining_get(const struct k_work_delayable *dwork); +pid_t k_work_queue_thread_get(struct k_work_q *queue); +void k_work_queue_init(struct k_work_q *queue); +int k_work_queue_start( + struct k_work_q *queue, void *stack, size_t stack_size, int prio, + const struct k_work_queue_config *cfg); + +#define K_PRIO_COOP(prio) (prio) + +extern struct k_work_q k_sys_work_q; + +/* ===== Memory Slab ===== */ + +struct k_mem_slab +{ + char *buffer; + char *free_list; + size_t block_size; + uint32_t num_blocks; + uint32_t num_used; + bool initialized; + spinlock_t lock; +}; + +void k_mem_slab_init( + struct k_mem_slab *slab, void *buffer, size_t block_size, + uint32_t num_blocks); +int k_mem_slab_alloc(struct k_mem_slab *slab, void **mem, clock_t timeout); +void k_mem_slab_free(struct k_mem_slab *slab, void *mem); +uint32_t k_mem_slab_num_free_get(struct k_mem_slab *slab); + +#define K_MEM_SLAB_DEFINE(name, slab_block_size, slab_num_blocks, slab_align) \ +static char __aligned(slab_align) _k_mem_slab_buf_##name[(slab_block_size) * (slab_num_blocks)]; \ +struct k_mem_slab name = { \ + .buffer = _k_mem_slab_buf_##name, \ + .block_size = (slab_block_size), \ + .num_blocks = (slab_num_blocks), \ + .initialized = false, \ +} + +#define K_MEM_SLAB_DEFINE_TYPE(name, type, num_blocks, ...) \ +K_MEM_SLAB_DEFINE(name, sizeof(type), num_blocks, __alignof__(type)) + +#define K_MEM_SLAB_DEFINE_STATIC_TYPE(name, type, num_blocks, ...) \ +K_MEM_SLAB_DEFINE(name, sizeof(type), num_blocks, __alignof__(type)) + +#define K_MEM_SLAB_DEFINE_STATIC(name, slab_block_size, slab_num_blocks, slab_align) \ +static K_MEM_SLAB_DEFINE(name, slab_block_size, slab_num_blocks, slab_align) + +/* ===== Poll ===== */ +#define K_POLL_TYPE_FIFO_DATA_AVAILABLE 4 + +#define K_POLL_STATE_FIFO_DATA_AVAILABLE 0x004 +#define K_POLL_STATE_CANCELLED 0x020 +#define K_POLL_STATE_NOT_READY 0x080 + +#define K_POLL_MODE_NOTIFY_ONLY 0 + +struct k_poll_waiter; + +struct k_poll_event +{ + struct list_node node; + struct k_poll_waiter *poller; + int type; + uint32_t mode; + uint32_t state; + union + { + void *obj; + struct k_fifo *fifo; + }; + int tag; +}; + +#define K_POLL_EVENT_STATIC_INITIALIZER(_type, _mode, _obj, _tag) \ +{.type = (_type), \ + .mode = (_mode), \ + .state = K_POLL_STATE_NOT_READY, \ + .obj = (void *)(_obj), \ + .tag = (_tag)} + +int k_poll(struct k_poll_event *events, int num_events, clock_t timeout); + +/* ===== Miscellaneous ===== */ + +struct k_condvar +{ + pthread_cond_t cond; +}; + +#define Z_CONDVAR_INITIALIZER(obj) {.cond = PTHREAD_COND_INITIALIZER} +int k_condvar_init(struct k_condvar *condvar); +int k_condvar_wait(struct k_condvar *condvar, + rmutex_t *mutex, clock_t timeout); +int k_condvar_broadcast(struct k_condvar *condvar); + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_KERNEL_H */ diff --git a/include/nuttx/net/bluetooth/compat/logging/log.h b/include/nuttx/net/bluetooth/compat/logging/log.h new file mode 100644 index 0000000000000..2597a5e987a2d --- /dev/null +++ b/include/nuttx/net/bluetooth/compat/logging/log.h @@ -0,0 +1,122 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/compat/logging/log.h + * + * Author: chao an + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_LOGGING_LOG_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_LOGGING_LOG_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#ifndef CONFIG_BT_LOG_LEVEL +# define CONFIG_BT_LOG_LEVEL 3 +#endif + +enum +{ + ZEPHYR_LOG_ERR_PRIORITY = LOG_ERR, + ZEPHYR_LOG_WRN_PRIORITY = LOG_WARNING, + ZEPHYR_LOG_INF_PRIORITY = LOG_INFO, + ZEPHYR_LOG_DBG_PRIORITY = LOG_DEBUG, +}; + +/* NuttX syslog.h defines LOG_ERR (3), LOG_WARNING (4), LOG_INFO (6), + * LOG_DEBUG (7) as priority constants. These conflict with Zephyr's + * function-like LOG_ERR macro. Undefine LOG_ERR so Zephyr code works. + */ +#undef LOG_ERR + +/* Module registration stubs */ +#define LOG_MODULE_REGISTER(name, ...) +#define LOG_MODULE_DECLARE(name, ...) + +/* Log levels (map to NuttX syslog priorities) */ +#if CONFIG_BT_LOG_LEVEL >= 1 +# define LOG_ERR(fmt, ...) syslog(ZEPHYR_LOG_ERR_PRIORITY, \ + " %s: " fmt "\n", __func__, \ + ##__VA_ARGS__) +#else +# define LOG_ERR(fmt, ...) do { if (0) { syslog(ZEPHYR_LOG_ERR_PRIORITY, \ + " %s: " fmt "\n", __func__, \ + ##__VA_ARGS__); } } while (0) +#endif + +#if CONFIG_BT_LOG_LEVEL >= 2 +# define LOG_WRN(fmt, ...) syslog(ZEPHYR_LOG_WRN_PRIORITY, \ + " %s: " fmt "\n", __func__, \ + ##__VA_ARGS__) +#else +# define LOG_WRN(fmt, ...) do { if (0) { syslog(ZEPHYR_LOG_WRN_PRIORITY, \ + " %s: " fmt "\n", __func__, \ + ##__VA_ARGS__); } } while (0) +#endif + +#if CONFIG_BT_LOG_LEVEL >= 3 +# define LOG_INF(fmt, ...) syslog(ZEPHYR_LOG_INF_PRIORITY, \ + " %s: " fmt "\n", __func__, \ + ##__VA_ARGS__) +#else +# define LOG_INF(fmt, ...) do { if (0) { syslog(ZEPHYR_LOG_INF_PRIORITY, \ + " %s: " fmt "\n", __func__, \ + ##__VA_ARGS__); } } while (0) +#endif + +#if CONFIG_BT_LOG_LEVEL >= 4 +# define LOG_DBG(fmt, ...) syslog(ZEPHYR_LOG_DBG_PRIORITY, \ + " %s: " fmt "\n", __func__, \ + ##__VA_ARGS__) +#else +# define LOG_DBG(fmt, ...) do { if (0) { syslog(ZEPHYR_LOG_DBG_PRIORITY, \ + " %s: " fmt "\n", __func__, \ + ##__VA_ARGS__); } } while (0) +#endif + +/* Rate-limited logging compatibility. The current NuttX Bluetooth + * compatibility layer does not provide Zephyr's log-rate limiter state, so + * keep these as normal log calls rather than leaving them as unresolved + * function-like symbols. + */ +#define LOG_ERR_RATELIMIT(fmt, ...) LOG_ERR(fmt, ##__VA_ARGS__) +#define LOG_WRN_RATELIMIT(fmt, ...) LOG_WRN(fmt, ##__VA_ARGS__) +#define LOG_INF_RATELIMIT(fmt, ...) LOG_INF(fmt, ##__VA_ARGS__) +#define LOG_DBG_RATELIMIT(fmt, ...) LOG_DBG(fmt, ##__VA_ARGS__) + +#define LOG_ERR_RATELIMIT_RATE(rate, fmt, ...) LOG_ERR(fmt, ##__VA_ARGS__) +#define LOG_WRN_RATELIMIT_RATE(rate, fmt, ...) LOG_WRN(fmt, ##__VA_ARGS__) +#define LOG_INF_RATELIMIT_RATE(rate, fmt, ...) LOG_INF(fmt, ##__VA_ARGS__) +#define LOG_DBG_RATELIMIT_RATE(rate, fmt, ...) LOG_DBG(fmt, ##__VA_ARGS__) + +/* Hex dump */ +#define LOG_HEXDUMP_DBG(_data, _length, _str) +#define LOG_HEXDUMP_INF(_data, _length, _str) +#define LOG_HEXDUMP_WRN(_data, _length, _str) +#define LOG_HEXDUMP_ERR(_data, _length, _str) + +/* Printk fallback */ +#define LOG_PRINTK(fmt, ...) syslog(LOG_INFO, fmt, ##__VA_ARGS__) + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_LOGGING_LOG_H */ diff --git a/include/nuttx/net/bluetooth/compat/net_buf.h b/include/nuttx/net/bluetooth/compat/net_buf.h new file mode 100644 index 0000000000000..cb94620435729 --- /dev/null +++ b/include/nuttx/net/bluetooth/compat/net_buf.h @@ -0,0 +1,848 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/compat/net_buf.h + * + * Author: chao an + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_NET_BUF_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_NET_BUF_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Alignment for buffer definitions */ + +#if CONFIG_NET_BUF_ALIGNMENT == 0 +# define __net_buf_align __attribute__((aligned(sizeof(void *)))) +#else +# define __net_buf_align \ + __attribute__((aligned(CONFIG_NET_BUF_ALIGNMENT))) +#endif + +/* Flag indicating external data ownership */ + +#define NET_BUF_EXTERNAL_DATA (1 << 0) + +/**************************************************************************** + * Public Type Definitions + ****************************************************************************/ + +/** + * Simple network buffer representation. + * + * This is a simpler variant of the net_buf object (in fact net_buf uses + * net_buf_simple internally). It doesn't provide any kind of reference + * counting, user data, dynamic allocation, or in general the ability to + * pass through kernel objects such as FIFOs. + */ + +struct net_buf_simple +{ + uint8_t *data; /* Pointer to the start of data in the buffer */ + uint16_t len; /* Length of the data behind the data pointer */ + uint16_t size; /* Amount of data that __buf can store */ + uint8_t *__buf; /* Start of the data storage (do not access directly) */ +}; + +/** + * Parsing state of a buffer. + */ + +struct net_buf_simple_state +{ + uint16_t offset; /* Offset of data pointer from the storage start */ + uint16_t len; /* Length of data */ +}; + +/* Forward declarations */ + +struct net_buf_pool; +struct net_buf; + +/* Data allocator callback vtable */ + +struct net_buf_data_cb +{ + uint8_t *(*alloc)(struct net_buf *buf, size_t *size, + bool block); + uint8_t *(*ref)(struct net_buf *buf, uint8_t *data); + void (*unref)(struct net_buf *buf, uint8_t *data); +}; + +/* Data allocator configuration */ + +struct net_buf_data_alloc +{ + const struct net_buf_data_cb *cb; + void *alloc_data; + size_t max_alloc_size; +}; + +/* Fixed data pool helper: maps buffer index to its data region */ + +struct net_buf_pool_fixed +{ + uint8_t *data_pool; +}; + +/** + * Network buffer pool representation. + */ + +struct net_buf_pool +{ + struct list_node free_list; /* LIFO free list */ + spinlock_t lock; /* Prevents concurrent access */ + const uint16_t buf_count; /* Number of buffers in pool */ + uint16_t uninit_count; /* Number of uninitialized bufs */ + uint8_t user_data_size; /* User data size per buffer */ + void (*const destroy)(struct net_buf *buf); + const struct net_buf_data_alloc *alloc; /* Data allocation handlers */ + struct net_buf * const __bufs; /* Start of buffer storage array */ + sem_t sem; /* Semaphore for blocking alloc */ +}; + +/** + * Network buffer representation. + */ + +struct net_buf +{ + struct list_node node; /* Linked list node */ + struct net_buf *frags; /* Fragment chain */ + struct net_buf_pool *pool; /* Owning pool */ + uint8_t ref; /* Reference count */ + uint8_t flags; /* Buffer flags (e.g. NET_BUF_EXTERNAL_DATA) */ + uint8_t user_data_size; /* Size of user data on this buffer */ + + /* Union for ABI compatibility with net_buf_simple */ + + union + { + struct + { + uint8_t *data; + uint16_t len; + uint16_t size; + uint8_t *__buf; + }; + + struct net_buf_simple b; + }; + + uint8_t user_data[] __net_buf_align; /* Variable-size trailing metadata */ +}; + +/** + * LIFO stack for inter-thread buffer passing. + * + * Provides spinlock-protected LIFO (Last In First Out) semantics for + * passing net_buf instances between threads, equivalent to Zephyr's + * k_lifo used with net_buf. + */ + +struct net_buf_lifo +{ + struct list_node list; /* LIFO list */ + spinlock_t lock; /* Prevents concurrent put/get */ + sem_t sem; /* Counting semaphore for blocking get */ +}; + +/**************************************************************************** + * net_buf_simple stack variable macros + ****************************************************************************/ + +#define NET_BUF_SIMPLE_DEFINE(_name, _size) \ + uint8_t net_buf_data_##_name[_size]; \ + struct net_buf_simple _name = \ + { \ + .data = net_buf_data_##_name, \ + .len = 0, \ + .size = _size, \ + .__buf = net_buf_data_##_name, \ + } + +#define NET_BUF_SIMPLE_DEFINE_STATIC(_name, _size) \ + static uint8_t net_buf_data_##_name[_size]; \ + static struct net_buf_simple _name = \ + { \ + .data = net_buf_data_##_name, \ + .len = 0, \ + .size = _size, \ + .__buf = net_buf_data_##_name, \ + } + +#define NET_BUF_SIMPLE(_size) \ + ((struct net_buf_simple *)(&(struct \ + { \ + struct net_buf_simple buf; \ + uint8_t data[_size]; \ + }) \ + { \ + .buf.size = _size, \ + })) + +/**************************************************************************** + * net_buf_simple Pure Inline Functions + * + * (These only access struct members or call other pure inline functions.) + ****************************************************************************/ + +static inline void net_buf_simple_init(struct net_buf_simple *buf, + size_t reserve_head) +{ + if (!buf->__buf) + { + buf->__buf = (uint8_t *)buf + sizeof(*buf); + } + + buf->data = buf->__buf + reserve_head; + buf->len = 0U; +} + +static inline void net_buf_simple_reset(struct net_buf_simple *buf) +{ + buf->len = 0U; + buf->data = buf->__buf; +} + +static inline uint8_t *net_buf_simple_tail(const struct net_buf_simple *buf) +{ + return buf->data + buf->len; +} + +static inline size_t net_buf_simple_headroom( + const struct net_buf_simple *buf) +{ + return buf->data - buf->__buf; +} + +static inline size_t net_buf_simple_tailroom( + const struct net_buf_simple *buf) +{ + return buf->size - net_buf_simple_headroom(buf) - buf->len; +} + +static inline uint16_t net_buf_simple_max_len( + const struct net_buf_simple *buf) +{ + return buf->size - net_buf_simple_headroom(buf); +} + +static inline bool net_buf_simple_is_valid(const struct net_buf_simple *buf) +{ + size_t headroom; + + if (buf == NULL || buf->__buf == NULL || buf->data < buf->__buf) + { + return false; + } + + headroom = net_buf_simple_headroom(buf); + if (headroom > buf->size) + { + return false; + } + + return (size_t)buf->len <= (size_t)(buf->size - headroom); +} + +static inline void net_buf_simple_save(const struct net_buf_simple *buf, + struct net_buf_simple_state *state) +{ + state->offset = (uint16_t)net_buf_simple_headroom(buf); + state->len = buf->len; +} + +static inline void net_buf_simple_restore(struct net_buf_simple *buf, + struct net_buf_simple_state *state) +{ + buf->data = buf->__buf + state->offset; + buf->len = state->len; +} + +/**************************************************************************** + * net_buf_simple Function Declarations + * + * (These are declared BEFORE the net_buf inline wrappers that call them.) + ****************************************************************************/ + +void net_buf_simple_init_with_data(struct net_buf_simple *buf, + void *data, size_t size); + +void net_buf_simple_clone(const struct net_buf_simple *original, + struct net_buf_simple *clone); + +void *net_buf_simple_add(struct net_buf_simple *buf, size_t len); + +void *net_buf_simple_add_mem(struct net_buf_simple *buf, const void *mem, + size_t len); + +uint8_t *net_buf_simple_add_u8(struct net_buf_simple *buf, uint8_t val); + +void net_buf_simple_add_le16(struct net_buf_simple *buf, uint16_t val); +void net_buf_simple_add_be16(struct net_buf_simple *buf, uint16_t val); +void net_buf_simple_add_le32(struct net_buf_simple *buf, uint32_t val); +void net_buf_simple_add_be32(struct net_buf_simple *buf, uint32_t val); +void net_buf_simple_add_le64(struct net_buf_simple *buf, uint64_t val); +void net_buf_simple_add_be64(struct net_buf_simple *buf, uint64_t val); + +void *net_buf_simple_remove_mem(struct net_buf_simple *buf, size_t len); + +uint8_t net_buf_simple_remove_u8(struct net_buf_simple *buf); +uint16_t net_buf_simple_remove_le16(struct net_buf_simple *buf); +uint16_t net_buf_simple_remove_be16(struct net_buf_simple *buf); +uint32_t net_buf_simple_remove_le32(struct net_buf_simple *buf); +uint32_t net_buf_simple_remove_be32(struct net_buf_simple *buf); +uint64_t net_buf_simple_remove_le64(struct net_buf_simple *buf); +uint64_t net_buf_simple_remove_be64(struct net_buf_simple *buf); + +void *net_buf_simple_push(struct net_buf_simple *buf, size_t len); + +void *net_buf_simple_push_mem(struct net_buf_simple *buf, const void *mem, + size_t len); + +void net_buf_simple_push_u8(struct net_buf_simple *buf, uint8_t val); +void net_buf_simple_push_le16(struct net_buf_simple *buf, uint16_t val); +void net_buf_simple_push_be16(struct net_buf_simple *buf, uint16_t val); +void net_buf_simple_push_le32(struct net_buf_simple *buf, uint32_t val); +void net_buf_simple_push_be32(struct net_buf_simple *buf, uint32_t val); +void net_buf_simple_push_le64(struct net_buf_simple *buf, uint64_t val); +void net_buf_simple_push_be64(struct net_buf_simple *buf, uint64_t val); + +void *net_buf_simple_pull(struct net_buf_simple *buf, size_t len); + +void *net_buf_simple_pull_mem(struct net_buf_simple *buf, size_t len); + +uint8_t net_buf_simple_pull_u8(struct net_buf_simple *buf); +uint16_t net_buf_simple_pull_le16(struct net_buf_simple *buf); +uint16_t net_buf_simple_pull_be16(struct net_buf_simple *buf); +uint32_t net_buf_simple_pull_le32(struct net_buf_simple *buf); +uint32_t net_buf_simple_pull_be32(struct net_buf_simple *buf); +uint64_t net_buf_simple_pull_le64(struct net_buf_simple *buf); +uint64_t net_buf_simple_pull_be64(struct net_buf_simple *buf); + +void net_buf_simple_reserve(struct net_buf_simple *buf, size_t reserve); + +/**************************************************************************** + * net_buf Core Function Declarations + * + * (Declared BEFORE the inline wrappers below.) + ****************************************************************************/ + +extern const struct net_buf_data_cb g_net_buf_fixed_cb; + +struct net_buf *net_buf_alloc_fixed(struct net_buf_pool *pool, + int32_t timeout); + +struct net_buf *net_buf_alloc_len(struct net_buf_pool *pool, + size_t size, int32_t timeout); + +struct net_buf *net_buf_alloc_with_data(struct net_buf_pool *pool, + void *data, size_t size, + int32_t timeout); + +void net_buf_reset(struct net_buf *buf); +void net_buf_unref(struct net_buf *buf); +struct net_buf *net_buf_ref(struct net_buf *buf); +struct net_buf *net_buf_clone(struct net_buf *buf, int32_t timeout); +int net_buf_id(const struct net_buf *buf); +int net_buf_user_data_copy(struct net_buf *dst, + const struct net_buf *src); + +void net_buf_slist_put(struct list_node *list, struct net_buf *buf); +struct net_buf *net_buf_slist_get(struct list_node *list); + +/* LIFO queue operations (inter-thread buffer passing) */ + +void net_buf_lifo_init(struct net_buf_lifo *lifo); +void net_buf_lifo_put(struct net_buf_lifo *lifo, struct net_buf *buf); +struct net_buf *net_buf_lifo_get(struct net_buf_lifo *lifo, int32_t timeout); + +/* Fragments */ + +struct net_buf *net_buf_frag_last(struct net_buf *buf); +void net_buf_frag_insert(struct net_buf *parent, struct net_buf *frag); +struct net_buf *net_buf_frag_add(struct net_buf *head, + struct net_buf *frag); +struct net_buf *net_buf_frag_del(struct net_buf *parent, + struct net_buf *frag); + +/* Utilities */ + +size_t net_buf_linearize(void *dst, size_t dst_len, + const struct net_buf *src, + size_t offset, size_t len); + +typedef struct net_buf *(*net_buf_allocator_cb)(int32_t timeout, + void *user_data); + +size_t net_buf_append_bytes(struct net_buf *buf, size_t len, + const void *value, int32_t timeout, + net_buf_allocator_cb allocate_cb, + void *user_data); + +size_t net_buf_data_match(const struct net_buf *buf, size_t offset, + const void *data, size_t len); + +size_t net_buf_frags_len(struct net_buf *buf); + +/**************************************************************************** + * Pool Definition Macros + ****************************************************************************/ + +#define _NET_BUF_ARRAY_DEFINE(_name, _count, _ud_size) \ + struct _net_buf_##_name \ + { \ + uint8_t b[sizeof(struct net_buf)]; \ + uint8_t ud[_ud_size]; \ + } __net_buf_align; \ + static struct _net_buf_##_name _net_buf_##_name[_count] + +#define NET_BUF_POOL_INITIALIZER(_pool, _alloc, _bufs, _count, \ + _ud_size, _destroy) \ + { \ + .free_list = LIST_INITIAL_VALUE(_pool.free_list), \ + .lock = SP_UNLOCKED, \ + .buf_count = _count, \ + .uninit_count = _count, \ + .user_data_size = _ud_size, \ + .destroy = _destroy, \ + .alloc = _alloc, \ + .__bufs = (struct net_buf *)_bufs, \ + .sem = NXSEM_INITIALIZER(0, 0), \ + } + +#define NET_BUF_POOL_FIXED_DEFINE(_name, _count, _data_size, \ + _ud_size, _destroy) \ + _NET_BUF_ARRAY_DEFINE(_name, _count, _ud_size); \ + static uint8_t net_buf_data_##_name[_count][_data_size] \ + __net_buf_align; \ + static const struct net_buf_pool_fixed net_buf_fixed_##_name = \ + { \ + .data_pool = (uint8_t *)net_buf_data_##_name, \ + }; \ + static const struct net_buf_data_alloc \ + net_buf_fixed_alloc_##_name = \ + { \ + .cb = &g_net_buf_fixed_cb, \ + .alloc_data = (void *)&net_buf_fixed_##_name, \ + .max_alloc_size = _data_size, \ + }; \ + static struct net_buf_pool _name = \ + NET_BUF_POOL_INITIALIZER(_name, &net_buf_fixed_alloc_##_name, \ + _net_buf_##_name, _count, \ + _ud_size, _destroy) + +#define NET_BUF_POOL_DEFINE(_name, _count, _size, _ud_size, \ + _destroy) \ + NET_BUF_POOL_FIXED_DEFINE(_name, _count, _size, _ud_size, \ + _destroy) + +/**************************************************************************** + * net_buf Inline Functions + * + * (All declarations used by these inlines are now visible above.) + ****************************************************************************/ + +static inline struct net_buf *net_buf_alloc(struct net_buf_pool *pool, + int32_t timeout) +{ + return net_buf_alloc_fixed(pool, timeout); +} + +static inline void net_buf_destroy(struct net_buf *buf) +{ + struct net_buf_pool *pool = buf->pool; + + if (buf->__buf) + { + if (!(buf->flags & NET_BUF_EXTERNAL_DATA)) + { + pool->alloc->cb->unref(buf, buf->__buf); + } + + buf->__buf = NULL; + } + + list_add_head(&pool->free_list, &buf->node); + + /* Wake up a thread blocked in net_buf_alloc_len */ + + nxsem_post(&pool->sem); +} + +static inline void *net_buf_user_data(const struct net_buf *buf) +{ + return (void *)buf->user_data; +} + +static inline struct net_buf *net_buf_take(struct net_buf **orig) +{ + struct net_buf *buf = *orig; + + *orig = NULL; + return buf; +} + +static inline void net_buf_drop(struct net_buf **orig) +{ + struct net_buf *buf = net_buf_take(orig); + + if (buf != NULL) + { + net_buf_unref(buf); + } +} + +/* Convenience wrappers that delegate to net_buf_simple */ + +static inline void net_buf_reserve(struct net_buf *buf, size_t reserve) +{ + net_buf_simple_reserve(&buf->b, reserve); +} + +static inline void *net_buf_add(struct net_buf *buf, size_t len) +{ + return net_buf_simple_add(&buf->b, len); +} + +static inline void *net_buf_add_mem(struct net_buf *buf, const void *mem, + size_t len) +{ + return net_buf_simple_add_mem(&buf->b, mem, len); +} + +static inline uint8_t *net_buf_add_u8(struct net_buf *buf, uint8_t val) +{ + return net_buf_simple_add_u8(&buf->b, val); +} + +static inline void net_buf_add_le16(struct net_buf *buf, uint16_t val) +{ + net_buf_simple_add_le16(&buf->b, val); +} + +static inline void net_buf_add_le24(struct net_buf *buf, uint32_t val) +{ + uint8_t *data = net_buf_add(buf, 3); + + data[0] = (uint8_t)val; + data[1] = (uint8_t)(val >> 8); + data[2] = (uint8_t)(val >> 16); +} + +static inline void net_buf_add_be24(struct net_buf *buf, uint32_t val) +{ + uint8_t *data = net_buf_add(buf, 3); + + data[0] = (uint8_t)(val >> 16); + data[1] = (uint8_t)(val >> 8); + data[2] = (uint8_t)val; +} + +static inline void net_buf_simple_add_le24(struct net_buf_simple *buf, + uint32_t val) +{ + uint8_t *data = net_buf_simple_add(buf, 3); + + data[0] = (uint8_t)val; + data[1] = (uint8_t)(val >> 8); + data[2] = (uint8_t)(val >> 16); +} + +static inline uint32_t net_buf_simple_pull_le24(struct net_buf_simple *buf) +{ + uint8_t *data = net_buf_simple_pull(buf, 3); + + return (uint32_t)data[0] | ((uint32_t)data[1] << 8) | + ((uint32_t)data[2] << 16); +} + +static inline void net_buf_simple_push_be24(struct net_buf_simple *buf, + uint32_t val) +{ + uint8_t *data = net_buf_simple_push(buf, 3); + + data[0] = (uint8_t)(val >> 16); + data[1] = (uint8_t)(val >> 8); + data[2] = (uint8_t)val; +} + +static inline uint32_t net_buf_simple_pull_be24(struct net_buf_simple *buf) +{ + uint8_t *data = net_buf_simple_pull(buf, 3); + + return ((uint32_t)data[0] << 16) | ((uint32_t)data[1] << 8) | + (uint32_t)data[2]; +} + +static inline void net_buf_add_be16(struct net_buf *buf, uint16_t val) +{ + net_buf_simple_add_be16(&buf->b, val); +} + +static inline void net_buf_add_le32(struct net_buf *buf, uint32_t val) +{ + net_buf_simple_add_le32(&buf->b, val); +} + +static inline void net_buf_add_be32(struct net_buf *buf, uint32_t val) +{ + net_buf_simple_add_be32(&buf->b, val); +} + +static inline void net_buf_add_le64(struct net_buf *buf, uint64_t val) +{ + net_buf_simple_add_le64(&buf->b, val); +} + +static inline void net_buf_add_be64(struct net_buf *buf, uint64_t val) +{ + net_buf_simple_add_be64(&buf->b, val); +} + +static inline void *net_buf_push(struct net_buf *buf, size_t len) +{ + return net_buf_simple_push(&buf->b, len); +} + +static inline void *net_buf_push_mem(struct net_buf *buf, const void *mem, + size_t len) +{ + return net_buf_simple_push_mem(&buf->b, mem, len); +} + +static inline void net_buf_push_u8(struct net_buf *buf, uint8_t val) +{ + net_buf_simple_push_u8(&buf->b, val); +} + +static inline void net_buf_push_le16(struct net_buf *buf, uint16_t val) +{ + net_buf_simple_push_le16(&buf->b, val); +} + +static inline void net_buf_push_be16(struct net_buf *buf, uint16_t val) +{ + net_buf_simple_push_be16(&buf->b, val); +} + +static inline void net_buf_push_le32(struct net_buf *buf, uint32_t val) +{ + net_buf_simple_push_le32(&buf->b, val); +} + +static inline void net_buf_push_be32(struct net_buf *buf, uint32_t val) +{ + net_buf_simple_push_be32(&buf->b, val); +} + +static inline void net_buf_push_le64(struct net_buf *buf, uint64_t val) +{ + net_buf_simple_push_le64(&buf->b, val); +} + +static inline void net_buf_push_be64(struct net_buf *buf, uint64_t val) +{ + net_buf_simple_push_be64(&buf->b, val); +} + +static inline void *net_buf_pull(struct net_buf *buf, size_t len) +{ + return net_buf_simple_pull(&buf->b, len); +} + +static inline void *net_buf_pull_mem(struct net_buf *buf, size_t len) +{ + return net_buf_simple_pull_mem(&buf->b, len); +} + +static inline uint8_t net_buf_pull_u8(struct net_buf *buf) +{ + return net_buf_simple_pull_u8(&buf->b); +} + +static inline uint16_t net_buf_pull_le16(struct net_buf *buf) +{ + return net_buf_simple_pull_le16(&buf->b); +} + +static inline uint16_t net_buf_pull_be16(struct net_buf *buf) +{ + return net_buf_simple_pull_be16(&buf->b); +} + +static inline uint32_t net_buf_pull_be24(struct net_buf *buf) +{ + return net_buf_simple_pull_be24(&buf->b); +} + +static inline uint32_t net_buf_pull_le32(struct net_buf *buf) +{ + return net_buf_simple_pull_le32(&buf->b); +} + +static inline uint32_t net_buf_pull_be32(struct net_buf *buf) +{ + return net_buf_simple_pull_be32(&buf->b); +} + +static inline uint64_t net_buf_pull_le64(struct net_buf *buf) +{ + return net_buf_simple_pull_le64(&buf->b); +} + +static inline uint64_t net_buf_pull_be64(struct net_buf *buf) +{ + return net_buf_simple_pull_be64(&buf->b); +} + +static inline void *net_buf_remove_mem(struct net_buf *buf, size_t len) +{ + return net_buf_simple_remove_mem(&buf->b, len); +} + +static inline uint8_t net_buf_remove_u8(struct net_buf *buf) +{ + return net_buf_simple_remove_u8(&buf->b); +} + +static inline uint16_t net_buf_remove_le16(struct net_buf *buf) +{ + return net_buf_simple_remove_le16(&buf->b); +} + +static inline uint16_t net_buf_remove_be16(struct net_buf *buf) +{ + return net_buf_simple_remove_be16(&buf->b); +} + +static inline uint32_t net_buf_remove_le32(struct net_buf *buf) +{ + return net_buf_simple_remove_le32(&buf->b); +} + +static inline uint32_t net_buf_remove_be32(struct net_buf *buf) +{ + return net_buf_simple_remove_be32(&buf->b); +} + +static inline uint64_t net_buf_remove_le64(struct net_buf *buf) +{ + return net_buf_simple_remove_le64(&buf->b); +} + +static inline uint64_t net_buf_remove_be64(struct net_buf *buf) +{ + return net_buf_simple_remove_be64(&buf->b); +} + +static inline uint8_t *net_buf_tail(const struct net_buf *buf) +{ + return net_buf_simple_tail(&buf->b); +} + +static inline size_t net_buf_headroom(const struct net_buf *buf) +{ + return net_buf_simple_headroom(&buf->b); +} + +static inline size_t net_buf_tailroom(const struct net_buf *buf) +{ + return net_buf_simple_tailroom(&buf->b); +} + +/**************************************************************************** + * Name: net_buf_skip + * + * Description: + * Skip len bytes across the fragment chain. Fully consumed fragments + * are removed from the chain. + * + * Warning: this function will not check whether remaining data in the + * chain is at least len bytes. Callers must validate beforehand. + * + * Returned Value: + * Pointer to the fragment that contains the remaining data, or NULL if + * all data was consumed. + ****************************************************************************/ + +static inline struct net_buf *net_buf_skip(struct net_buf *buf, + size_t len) +{ + while (buf != NULL && len > 0) + { + net_buf_pull_u8(buf); + len--; + if (buf->len == 0) + { + buf = net_buf_frag_del(NULL, buf); + } + } + + return buf; +} + +/**************************************************************************** + * Name: net_buf_is_valid + * + * Description: + * Validate the internal consistency of a net_buf instance and its + * fragment chain. Useful for DEBUGASSERT-style sanity checks. + * + * Returned Value: + * true if the buffer and fragment chain are valid, false otherwise. + ****************************************************************************/ + +static inline bool net_buf_is_valid(const struct net_buf *buf) +{ + if (buf == NULL || buf->__buf == NULL || buf->pool == NULL) + { + return false; + } + + if (buf->ref == 0 || buf->ref == 0xff) + { + return false; + } + + if (!net_buf_simple_is_valid(&buf->b)) + { + return false; + } + + return (buf->frags == NULL) ? true : net_buf_is_valid(buf->frags); +} + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_NET_BUF_H */ diff --git a/include/nuttx/net/bluetooth/compat/psa/key_ids.h b/include/nuttx/net/bluetooth/compat/psa/key_ids.h new file mode 100644 index 0000000000000..26b4073888dfc --- /dev/null +++ b/include/nuttx/net/bluetooth/compat/psa/key_ids.h @@ -0,0 +1,38 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/compat/psa/key_ids.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2025 Nordic Semiconductor + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_PSA_KEY_IDS_H_ +#define ZEPHYR_PSA_KEY_IDS_H_ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +typedef uint32_t psa_key_id_t; + +#define ZEPHYR_PSA_OPENTHREAD_KEY_ID_RANGE_BEGIN (psa_key_id_t)0x20000 +#define ZEPHYR_PSA_OPENTHREAD_KEY_ID_RANGE_SIZE 0x10000 + +#define ZEPHYR_PSA_MATTER_KEY_ID_RANGE_BEGIN (psa_key_id_t)0x30000 +#define ZEPHYR_PSA_MATTER_KEY_ID_RANGE_SIZE 0x10000 + +#define ZEPHYR_PSA_BT_MESH_KEY_ID_RANGE_BEGIN (psa_key_id_t)0x20000000 +#define ZEPHYR_PSA_BT_MESH_KEY_ID_RANGE_SIZE 0xc000 + +#define ZEPHYR_PSA_WIFI_CREDENTIALS_KEY_ID_RANGE_BEGIN (psa_key_id_t)0x20010000 +#define ZEPHYR_PSA_WIFI_CREDENTIALS_KEY_ID_RANGE_SIZE 0x100 + +#define ZEPHYR_PSA_APPLICATION_KEY_ID_RANGE_BEGIN (psa_key_id_t)0x30000000 +#define ZEPHYR_PSA_APPLICATION_KEY_ID_RANGE_SIZE 0x100000 + +#endif /* ZEPHYR_PSA_KEY_IDS_H_ */ diff --git a/include/nuttx/net/bluetooth/compat/settings/settings.h b/include/nuttx/net/bluetooth/compat/settings/settings.h new file mode 100644 index 0000000000000..16e4acc0408e6 --- /dev/null +++ b/include/nuttx/net/bluetooth/compat/settings/settings.h @@ -0,0 +1,94 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/compat/settings/settings.h + * + * Author: chao an + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_SETTINGS_SETTINGS_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_SETTINGS_SETTINGS_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Minimal settings API for BT bonding key storage. + * Backed by filesystem or MTD flash. + */ + +#define SETTINGS_MAX_DIR_DEPTH 8 + +typedef int (*settings_read_cb)(void *cb_arg, void *data, size_t len); +typedef int (*settings_load_direct_cb)( + const char *key, size_t len, settings_read_cb read_cb, void *cb_arg, + void *param); + +int settings_subsys_init(void); +int settings_save_one(const char *name, const void *value, size_t val_len); +int settings_load_subtree(const char *subtree); +int settings_load_subtree_direct(const char *subtree, + settings_load_direct_cb cb, void *param); +int settings_delete(const char *name); +int settings_commit(void); + +/* Callback registration */ + +struct settings_handler +{ + const char *name; + int (*h_set)(const char *key, size_t len, settings_read_cb read_cb, + void *cb_arg); + int (*h_commit)(void); + int (*h_export)( + int (*cb)(const char *name, const void *value, size_t val_len)); + int (*h_rename)(const char *from, const char *to); +}; + +#define SETTINGS_STATIC_HANDLER_DEFINE(_hname, _tree, _hget, _hset, _hcommit, _hexport) \ +static Z_DECL_ALIGN(struct settings_handler) _hname __attribute__((__used__)) \ +__attribute__((__section__(".settings_handlers"))) = { \ + .name = (_tree), \ + .h_set = (_hset), \ + .h_commit = (_hcommit), \ + .h_export = (_hexport), \ +} + +int settings_register(struct settings_handler *handler); + +/* Internal helpers */ + +bool settings_name_steq(const char *name, + const char *key, const char **next); +int settings_name_next(const char *name, const char **next); + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_SETTINGS_SETTINGS_H */ diff --git a/include/nuttx/net/bluetooth/compat/sys/byteorder.h b/include/nuttx/net/bluetooth/compat/sys/byteorder.h new file mode 100644 index 0000000000000..d90e7d260b228 --- /dev/null +++ b/include/nuttx/net/bluetooth/compat/sys/byteorder.h @@ -0,0 +1,904 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/compat/sys/byteorder.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Byte order helpers. + */ + +/* Copyright (c) 2015-2016, Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_SYS_BYTEORDER_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_SYS_BYTEORDER_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_HAVE_BUILTIN_BSWAP16 +#define BSWAP_16(x) ((uint16_t)__builtin_bswap16(x)) +#else +#define BSWAP_16(x) ((uint16_t)((((x) >> 8) & 0xff) | (((x) & 0xff) << 8))) +#endif + +#ifdef CONFIG_HAVE_BUILTIN_BSWAP32 +#define BSWAP_24(x) ((uint32_t)__builtin_bswap32(x) >> 8) +#define BSWAP_32(x) ((uint32_t)__builtin_bswap32(x)) +#else +#define BSWAP_24(x) ((uint32_t)((((x) >> 16) & 0xff) | (((x)) & 0xff00) | (((x) & 0xff) << 16))) + +#define BSWAP_32(x) \ + ((uint32_t)((((x) >> 24) & 0xff) | (((x) >> 8) & 0xff00) | (((x) & 0xff00) << 8) | \ + (((x) & 0xff) << 24))) +#endif + +#ifdef CONFIG_HAVE_BUILTIN_BSWAP64 +#define BSWAP_40(x) ((uint64_t)__builtin_bswap64(x) >> 24) +#define BSWAP_48(x) ((uint64_t)__builtin_bswap64(x) >> 16) +#define BSWAP_64(x) ((uint64_t)__builtin_bswap64(x)) +#else +#define BSWAP_40(x) \ + ((uint64_t)((((x) >> 32) & 0xff) | (((x) >> 16) & 0xff00) | (((x)) & 0xff0000) | \ + (((x) & 0xff00) << 16) | (((x) & 0xff) << 32))) + +#define BSWAP_48(x) \ + ((uint64_t)((((x) >> 40) & 0xff) | (((x) >> 24) & 0xff00) | (((x) >> 8) & 0xff0000) | \ + (((x) & 0xff0000) << 8) | (((x) & 0xff00) << 24) | (((x) & 0xff) << 40))) + +#define BSWAP_64(x) \ + ((uint64_t)((((x) >> 56) & 0xff) | (((x) >> 40) & 0xff00) | (((x) >> 24) & 0xff0000) | \ + (((x) >> 8) & 0xff000000) | (((x) & 0xff000000) << 8) | (((x) & 0xff0000) << 24) | \ + (((x) & 0xff00) << 40) | (((x) & 0xff) << 56))) +#endif + +/** @def sys_le16_to_cpu + * @brief Convert 16-bit integer from little-endian to host endianness. + * + * @param val 16-bit integer in little-endian format. + * + * @return 16-bit integer in host endianness. + */ + +/** @def sys_cpu_to_le16 + * @brief Convert 16-bit integer from host endianness to little-endian. + * + * @param val 16-bit integer in host endianness. + * + * @return 16-bit integer in little-endian format. + */ + +/** @def sys_le24_to_cpu + * @brief Convert 24-bit integer from little-endian to host endianness. + * + * @param val 24-bit integer in little-endian format. + * + * @return 24-bit integer in host endianness. + */ + +/** @def sys_cpu_to_le24 + * @brief Convert 24-bit integer from host endianness to little-endian. + * + * @param val 24-bit integer in host endianness. + * + * @return 24-bit integer in little-endian format. + */ + +/** @def sys_le32_to_cpu + * @brief Convert 32-bit integer from little-endian to host endianness. + * + * @param val 32-bit integer in little-endian format. + * + * @return 32-bit integer in host endianness. + */ + +/** @def sys_cpu_to_le32 + * @brief Convert 32-bit integer from host endianness to little-endian. + * + * @param val 32-bit integer in host endianness. + * + * @return 32-bit integer in little-endian format. + */ + +/** @def sys_le48_to_cpu + * @brief Convert 48-bit integer from little-endian to host endianness. + * + * @param val 48-bit integer in little-endian format. + * + * @return 48-bit integer in host endianness. + */ + +/** @def sys_cpu_to_le48 + * @brief Convert 48-bit integer from host endianness to little-endian. + * + * @param val 48-bit integer in host endianness. + * + * @return 48-bit integer in little-endian format. + */ + +/** @def sys_be16_to_cpu + * @brief Convert 16-bit integer from big-endian to host endianness. + * + * @param val 16-bit integer in big-endian format. + * + * @return 16-bit integer in host endianness. + */ + +/** @def sys_cpu_to_be16 + * @brief Convert 16-bit integer from host endianness to big-endian. + * + * @param val 16-bit integer in host endianness. + * + * @return 16-bit integer in big-endian format. + */ + +/** @def sys_be24_to_cpu + * @brief Convert 24-bit integer from big-endian to host endianness. + * + * @param val 24-bit integer in big-endian format. + * + * @return 24-bit integer in host endianness. + */ + +/** @def sys_cpu_to_be24 + * @brief Convert 24-bit integer from host endianness to big-endian. + * + * @param val 24-bit integer in host endianness. + * + * @return 24-bit integer in big-endian format. + */ + +/** @def sys_be32_to_cpu + * @brief Convert 32-bit integer from big-endian to host endianness. + * + * @param val 32-bit integer in big-endian format. + * + * @return 32-bit integer in host endianness. + */ + +/** @def sys_cpu_to_be32 + * @brief Convert 32-bit integer from host endianness to big-endian. + * + * @param val 32-bit integer in host endianness. + * + * @return 32-bit integer in big-endian format. + */ + +/** @def sys_be48_to_cpu + * @brief Convert 48-bit integer from big-endian to host endianness. + * + * @param val 48-bit integer in big-endian format. + * + * @return 48-bit integer in host endianness. + */ + +/** @def sys_cpu_to_be48 + * @brief Convert 48-bit integer from host endianness to big-endian. + * + * @param val 48-bit integer in host endianness. + * + * @return 48-bit integer in big-endian format. + */ + +/** @def sys_uint16_to_array + * @brief Convert 16-bit unsigned integer to byte array. + * + * @details Byte order aware macro to treat an unsigned integer + * as an array, rather than an integer literal. For example, `0x0123` would + * be converted to `{0x01, 0x23}` for big endian machines, and `{0x23, + * 0x01}` for little endian machines. + * + * @param val 16-bit unsigned integer. + * + * @return 16-bit unsigned integer as byte array. + */ + +/** @def sys_uint32_to_array + * @brief Convert 32-bit unsigned integer to byte array. + * + * @details Byte order aware macro to treat an unsigned integer + * as an array, rather than an integer literal. For example, `0x01234567` + * would be converted to `{0x01, 0x23, 0x45, 0x67}` for big endian machines, + * and `{0x67, 0x45, 0x23, 0x01}` for little endian machines. + * + * @param val 32-bit unsigned integer. + * + * @return 32-bit unsigned integer as byte array. + */ + +/** @def sys_uint64_to_array + * @brief Convert 64-bit unsigned integer to byte array. + * + * @details Byte order aware macro to treat an unsigned integer + * as an array, rather than an integer literal. For example, + * `0x0123456789abcdef` would be converted to `{0x01, 0x23, 0x45, 0x67, + * 0x89, 0xab, 0xcd, 0xef}` for big endian machines, and `{0xef, 0xcd, 0xab, + * 0x89, 0x67, 0x45, 0x23, 0x01}` for little endian machines. + * + * @param val 64-bit unsigned integer. + * + * @return 64-bit unsigned integer as byte array. + */ + +#ifndef CONFIG_ENDIAN_BIG +#define sys_le16_to_cpu(val) (val) +#define sys_cpu_to_le16(val) (val) +#define sys_le24_to_cpu(val) (val) +#define sys_cpu_to_le24(val) (val) +#define sys_le32_to_cpu(val) (val) +#define sys_cpu_to_le32(val) (val) +#define sys_le40_to_cpu(val) (val) +#define sys_cpu_to_le40(val) (val) +#define sys_le48_to_cpu(val) (val) +#define sys_cpu_to_le48(val) (val) +#define sys_le64_to_cpu(val) (val) +#define sys_cpu_to_le64(val) (val) +#define sys_be16_to_cpu(val) BSWAP_16(val) +#define sys_cpu_to_be16(val) BSWAP_16(val) +#define sys_be24_to_cpu(val) BSWAP_24(val) +#define sys_cpu_to_be24(val) BSWAP_24(val) +#define sys_be32_to_cpu(val) BSWAP_32(val) +#define sys_cpu_to_be32(val) BSWAP_32(val) +#define sys_be40_to_cpu(val) BSWAP_40(val) +#define sys_cpu_to_be40(val) BSWAP_40(val) +#define sys_be48_to_cpu(val) BSWAP_48(val) +#define sys_cpu_to_be48(val) BSWAP_48(val) +#define sys_be64_to_cpu(val) BSWAP_64(val) +#define sys_cpu_to_be64(val) BSWAP_64(val) + +#define sys_uint16_to_array(val) {((val) & 0xff), (((val) >> 8) & 0xff)} + +#define sys_uint32_to_array(val) \ + {((val) & 0xff), (((val) >> 8) & 0xff), (((val) >> 16) & 0xff), (((val) >> 24) & 0xff)} + +#define sys_uint64_to_array(val) \ + {((val) & 0xff), (((val) >> 8) & 0xff), (((val) >> 16) & 0xff), (((val) >> 24) & 0xff), \ + (((val) >> 32) & 0xff), (((val) >> 40) & 0xff), (((val) >> 48) & 0xff), (((val) >> 56) & 0xff)} + +#else +#define sys_le16_to_cpu(val) BSWAP_16(val) +#define sys_cpu_to_le16(val) BSWAP_16(val) +#define sys_le24_to_cpu(val) BSWAP_24(val) +#define sys_cpu_to_le24(val) BSWAP_24(val) +#define sys_le32_to_cpu(val) BSWAP_32(val) +#define sys_cpu_to_le32(val) BSWAP_32(val) +#define sys_le40_to_cpu(val) BSWAP_40(val) +#define sys_cpu_to_le40(val) BSWAP_40(val) +#define sys_le48_to_cpu(val) BSWAP_48(val) +#define sys_cpu_to_le48(val) BSWAP_48(val) +#define sys_le64_to_cpu(val) BSWAP_64(val) +#define sys_cpu_to_le64(val) BSWAP_64(val) +#define sys_be16_to_cpu(val) (val) +#define sys_cpu_to_be16(val) (val) +#define sys_be24_to_cpu(val) (val) +#define sys_cpu_to_be24(val) (val) +#define sys_be32_to_cpu(val) (val) +#define sys_cpu_to_be32(val) (val) +#define sys_be40_to_cpu(val) (val) +#define sys_cpu_to_be40(val) (val) +#define sys_be48_to_cpu(val) (val) +#define sys_cpu_to_be48(val) (val) +#define sys_be64_to_cpu(val) (val) +#define sys_cpu_to_be64(val) (val) + +#define sys_uint16_to_array(val) {(((val) >> 8) & 0xff), ((val) & 0xff)} + +#define sys_uint32_to_array(val) \ + {(((val) >> 24) & 0xff), (((val) >> 16) & 0xff), (((val) >> 8) & 0xff), ((val) & 0xff)} + +#define sys_uint64_to_array(val) \ + {(((val) >> 56) & 0xff), (((val) >> 48) & 0xff), (((val) >> 40) & 0xff), (((val) >> 32) & 0xff), \ + (((val) >> 24) & 0xff), (((val) >> 16) & 0xff), (((val) >> 8) & 0xff), ((val) & 0xff)} + +#endif + +/** + * @brief Put a 16-bit integer as big-endian to arbitrary location. + * + * Put a 16-bit integer, originally in host endianness, to a potentially + * unaligned memory location in big-endian format. + * + * @param val 16-bit integer in host endianness. + * @param dst Destination memory address to store the result. + */ + +static inline void sys_put_be16(uint16_t val, uint8_t dst[2]) +{ + dst[0] = val >> 8; + dst[1] = val; +} + +/** + * @brief Put a 24-bit integer as big-endian to arbitrary location. + * + * Put a 24-bit integer, originally in host endianness, to a potentially + * unaligned memory location in big-endian format. + * + * @param val 24-bit integer in host endianness. + * @param dst Destination memory address to store the result. + */ + +static inline void sys_put_be24(uint32_t val, uint8_t dst[3]) +{ + dst[0] = val >> 16; + sys_put_be16(val, &dst[1]); +} + +/** + * @brief Put a 32-bit integer as big-endian to arbitrary location. + * + * Put a 32-bit integer, originally in host endianness, to a potentially + * unaligned memory location in big-endian format. + * + * @param val 32-bit integer in host endianness. + * @param dst Destination memory address to store the result. + */ + +static inline void sys_put_be32(uint32_t val, uint8_t dst[4]) +{ + sys_put_be16(val >> 16, dst); + sys_put_be16(val, &dst[2]); +} +/** + * @brief Put a 40-bit integer as big-endian to arbitrary location. + * + * Put a 40-bit integer, originally in host endianness, to a potentially + * unaligned memory location in big-endian format. + * + * @param val 40-bit integer in host endianness. + * @param dst Destination memory address to store the result. + */ + +static inline void sys_put_be40(uint64_t val, uint8_t dst[5]) +{ + dst[0] = val >> 32; + sys_put_be32(val, &dst[1]); +} + +/** + * @brief Put a 48-bit integer as big-endian to arbitrary location. + * + * Put a 48-bit integer, originally in host endianness, to a potentially + * unaligned memory location in big-endian format. + * + * @param val 48-bit integer in host endianness. + * @param dst Destination memory address to store the result. + */ + +static inline void sys_put_be48(uint64_t val, uint8_t dst[6]) +{ + sys_put_be16(val >> 32, dst); + sys_put_be32(val, &dst[2]); +} + +/** + * @brief Put a 64-bit integer as big-endian to arbitrary location. + * + * Put a 64-bit integer, originally in host endianness, to a potentially + * unaligned memory location in big-endian format. + * + * @param val 64-bit integer in host endianness. + * @param dst Destination memory address to store the result. + */ + +static inline void sys_put_be64(uint64_t val, uint8_t dst[8]) +{ + sys_put_be32(val >> 32, dst); + sys_put_be32(val, &dst[4]); +} + +/** + * @brief Put a 16-bit integer as little-endian to arbitrary location. + * + * Put a 16-bit integer, originally in host endianness, to a potentially + * unaligned memory location in little-endian format. + * + * @param val 16-bit integer in host endianness. + * @param dst Destination memory address to store the result. + */ + +static inline void sys_put_le16(uint16_t val, uint8_t dst[2]) +{ + dst[0] = val; + dst[1] = val >> 8; +} + +/** + * @brief Put a 24-bit integer as little-endian to arbitrary location. + * + * Put a 24-bit integer, originally in host endianness, to a potentially + * unaligned memory location in little-endian format. + * + * @param val 24-bit integer in host endianness. + * @param dst Destination memory address to store the result. + */ + +static inline void sys_put_le24(uint32_t val, uint8_t dst[3]) +{ + sys_put_le16(val, dst); + dst[2] = val >> 16; +} + +/** + * @brief Put a 32-bit integer as little-endian to arbitrary location. + * + * Put a 32-bit integer, originally in host endianness, to a potentially + * unaligned memory location in little-endian format. + * + * @param val 32-bit integer in host endianness. + * @param dst Destination memory address to store the result. + */ + +static inline void sys_put_le32(uint32_t val, uint8_t dst[4]) +{ + sys_put_le16(val, dst); + sys_put_le16(val >> 16, &dst[2]); +} + +/** + * @brief Put a 40-bit integer as little-endian to arbitrary location. + * + * Put a 40-bit integer, originally in host endianness, to a potentially + * unaligned memory location in little-endian format. + * + * @param val 40-bit integer in host endianness. + * @param dst Destination memory address to store the result. + */ + +static inline void sys_put_le40(uint64_t val, uint8_t dst[5]) +{ + sys_put_le32(val, dst); + dst[4] = val >> 32; +} + +/** + * @brief Put a 48-bit integer as little-endian to arbitrary location. + * + * Put a 48-bit integer, originally in host endianness, to a potentially + * unaligned memory location in little-endian format. + * + * @param val 48-bit integer in host endianness. + * @param dst Destination memory address to store the result. + */ + +static inline void sys_put_le48(uint64_t val, uint8_t dst[6]) +{ + sys_put_le32(val, dst); + sys_put_le16(val >> 32, &dst[4]); +} + +/** + * @brief Put a 64-bit integer as little-endian to arbitrary location. + * + * Put a 64-bit integer, originally in host endianness, to a potentially + * unaligned memory location in little-endian format. + * + * @param val 64-bit integer in host endianness. + * @param dst Destination memory address to store the result. + */ + +static inline void sys_put_le64(uint64_t val, uint8_t dst[8]) +{ + sys_put_le32(val, dst); + sys_put_le32(val >> 32, &dst[4]); +} + +/** + * @brief Get a 16-bit integer stored in big-endian format. + * + * Get a 16-bit integer, stored in big-endian format in a potentially + * unaligned memory location, and convert it to the host endianness. + * + * @param src Location of the big-endian 16-bit integer to get. + * + * @return 16-bit integer in host endianness. + */ + +static inline uint16_t sys_get_be16(const uint8_t src[2]) +{ + return ((uint16_t)src[0] << 8) | src[1]; +} + +/** + * @brief Get a 24-bit integer stored in big-endian format. + * + * Get a 24-bit integer, stored in big-endian format in a potentially + * unaligned memory location, and convert it to the host endianness. + * + * @param src Location of the big-endian 24-bit integer to get. + * + * @return 24-bit integer in host endianness. + */ + +static inline uint32_t sys_get_be24(const uint8_t src[3]) +{ + return ((uint32_t)src[0] << 16) | sys_get_be16(&src[1]); +} + +/** + * @brief Get a 32-bit integer stored in big-endian format. + * + * Get a 32-bit integer, stored in big-endian format in a potentially + * unaligned memory location, and convert it to the host endianness. + * + * @param src Location of the big-endian 32-bit integer to get. + * + * @return 32-bit integer in host endianness. + */ + +static inline uint32_t sys_get_be32(const uint8_t src[4]) +{ + return ((uint32_t)sys_get_be16(&src[0]) << 16) | sys_get_be16(&src[2]); +} + +/** + * @brief Get a 40-bit integer stored in big-endian format. + * + * Get a 40-bit integer, stored in big-endian format in a potentially + * unaligned memory location, and convert it to the host endianness. + * + * @param src Location of the big-endian 40-bit integer to get. + * + * @return 40-bit integer in host endianness. + */ + +static inline uint64_t sys_get_be40(const uint8_t src[5]) +{ + return ((uint64_t)sys_get_be32(&src[0]) << 8) | src[4]; +} + +/** + * @brief Get a 48-bit integer stored in big-endian format. + * + * Get a 48-bit integer, stored in big-endian format in a potentially + * unaligned memory location, and convert it to the host endianness. + * + * @param src Location of the big-endian 48-bit integer to get. + * + * @return 48-bit integer in host endianness. + */ + +static inline uint64_t sys_get_be48(const uint8_t src[6]) +{ + return ((uint64_t)sys_get_be32(&src[0]) << 16) | sys_get_be16(&src[4]); +} + +/** + * @brief Get a 64-bit integer stored in big-endian format. + * + * Get a 64-bit integer, stored in big-endian format in a potentially + * unaligned memory location, and convert it to the host endianness. + * + * @param src Location of the big-endian 64-bit integer to get. + * + * @return 64-bit integer in host endianness. + */ + +static inline uint64_t sys_get_be64(const uint8_t src[8]) +{ + return ((uint64_t)sys_get_be32(&src[0]) << 32) | sys_get_be32(&src[4]); +} + +/** + * @brief Get a 16-bit integer stored in little-endian format. + * + * Get a 16-bit integer, stored in little-endian format in a potentially + * unaligned memory location, and convert it to the host endianness. + * + * @param src Location of the little-endian 16-bit integer to get. + * + * @return 16-bit integer in host endianness. + */ + +static inline uint16_t sys_get_le16(const uint8_t src[2]) +{ + return ((uint16_t)src[1] << 8) | src[0]; +} + +/** + * @brief Get a 24-bit integer stored in little-endian format. + * + * Get a 24-bit integer, stored in little-endian format in a potentially + * unaligned memory location, and convert it to the host endianness. + * + * @param src Location of the little-endian 24-bit integer to get. + * + * @return 24-bit integer in host endianness. + */ + +static inline uint32_t sys_get_le24(const uint8_t src[3]) +{ + return ((uint32_t)src[2] << 16) | sys_get_le16(&src[0]); +} + +/** + * @brief Get a 32-bit integer stored in little-endian format. + * + * Get a 32-bit integer, stored in little-endian format in a potentially + * unaligned memory location, and convert it to the host endianness. + * + * @param src Location of the little-endian 32-bit integer to get. + * + * @return 32-bit integer in host endianness. + */ + +static inline uint32_t sys_get_le32(const uint8_t src[4]) +{ + return ((uint32_t)sys_get_le16(&src[2]) << 16) | sys_get_le16(&src[0]); +} + +/** + * @brief Get a 40-bit integer stored in little-endian format. + * + * Get a 40-bit integer, stored in little-endian format in a potentially + * unaligned memory location, and convert it to the host endianness. + * + * @param src Location of the little-endian 40-bit integer to get. + * + * @return 40-bit integer in host endianness. + */ + +static inline uint64_t sys_get_le40(const uint8_t src[5]) +{ + return ((uint64_t)sys_get_le32(&src[1]) << 8) | src[0]; +} + +/** + * @brief Get a 48-bit integer stored in little-endian format. + * + * Get a 48-bit integer, stored in little-endian format in a potentially + * unaligned memory location, and convert it to the host endianness. + * + * @param src Location of the little-endian 48-bit integer to get. + * + * @return 48-bit integer in host endianness. + */ + +static inline uint64_t sys_get_le48(const uint8_t src[6]) +{ + return ((uint64_t)sys_get_le32(&src[2]) << 16) | sys_get_le16(&src[0]); +} + +/** + * @brief Get a 64-bit integer stored in little-endian format. + * + * Get a 64-bit integer, stored in little-endian format in a potentially + * unaligned memory location, and convert it to the host endianness. + * + * @param src Location of the little-endian 64-bit integer to get. + * + * @return 64-bit integer in host endianness. + */ + +static inline uint64_t sys_get_le64(const uint8_t src[8]) +{ + return ((uint64_t)sys_get_le32(&src[4]) << 32) | sys_get_le32(&src[0]); +} + +/** + * @brief Swap one buffer content into another + * + * Copy the content of src buffer into dst buffer in reversed order, i.e.: + * src[n] will be put in dst[end-n] Where n is an index and 'end' the last + * index in both arrays. The 2 memory pointers must be pointing to different + * areas, and have a minimum size of given length. + * + * @param dst A valid pointer on a memory area where to copy the data in + * @param src A valid pointer on a memory area where to copy the data from + * @param length Size of both dst and src memory areas + */ + +static inline void sys_memcpy_swap(void *dst, const void *src, size_t length) +{ + uint8_t *pdst = (uint8_t *)dst; + const uint8_t *psrc = (const uint8_t *)src; + + DEBUGASSERT( + ((psrc < pdst + && (psrc + length) <= pdst) || (psrc > pdst + && (pdst + length) <= psrc))); + + psrc += length - 1; + + for (; length > 0; length--) + { + *pdst++ = *psrc--; + } +} + +/** + * @brief Swap buffer content + * + * In-place memory swap, where final content will be reversed. I.e.: buf[n] + * will be put in buf[end-n] Where n is an index and 'end' the last index of + * buf. + * + * @param buf A valid pointer on a memory area to swap + * @param length Size of buf memory area + */ + +static inline void sys_mem_swap(void *buf, size_t length) +{ + size_t i; + + for (i = 0; i < (length / 2); i++) + { + uint8_t tmp = ((uint8_t *)buf)[i]; + + ((uint8_t *)buf)[i] = ((uint8_t *)buf)[length - 1 - i]; + ((uint8_t *)buf)[length - 1 - i] = tmp; + } +} + +/** + * @brief Convert buffer from little-endian to host endianness. + * + * @param buf A valid pointer on a memory area to convert from + * little-endian to host endianness. + * @param length Size of buf memory area + */ + +static inline void sys_le_to_cpu(void *buf, size_t length) +{ + if (IS_ENABLED(CONFIG_ENDIAN_BIG)) + { + sys_mem_swap(buf, length); + } +} + +/** + * @brief Convert buffer from host endianness to little-endian. + * + * @param buf A valid pointer on a memory area to convert from host + * endianness to little-endian. + * @param length Size of buf memory area + */ + +static inline void sys_cpu_to_le(void *buf, size_t length) +{ + if (IS_ENABLED(CONFIG_ENDIAN_BIG)) + { + sys_mem_swap(buf, length); + } +} + +/** + * @brief Convert buffer from big-endian to host endianness. + * + * @param buf A valid pointer on a memory area to convert from big-endian + * to host endianness. + * @param length Size of buf memory area + */ + +static inline void sys_be_to_cpu(void *buf, size_t length) +{ + if (!IS_ENABLED(CONFIG_ENDIAN_BIG)) + { + sys_mem_swap(buf, length); + } +} + +/** + * @brief Convert buffer from host endianness to big-endian. + * + * @param buf A valid pointer on a memory area to convert from host + * endianness to big-endian. + * @param length Size of buf memory area + */ + +static inline void sys_cpu_to_be(void *buf, size_t length) +{ + if (!IS_ENABLED(CONFIG_ENDIAN_BIG)) + { + sys_mem_swap(buf, length); + } +} + +/** + * @brief Put a buffer as little-endian to arbitrary location. + * + * Put a buffer originally in host endianness, to a potentially unaligned + * memory location in little-endian format. + * + * @param dst A valid pointer on a memory area where to copy the data in + * @param src A valid pointer on a memory area where to copy the data from + * @param length Size of both dst and src memory areas + */ + +static inline void sys_put_le(void *dst, const void *src, size_t length) +{ + if (!IS_ENABLED(CONFIG_ENDIAN_BIG)) + { + (void)memcpy(dst, src, length); + } + else + { + sys_memcpy_swap(dst, src, length); + } +} + +/** + * @brief Put a buffer as big-endian to arbitrary location. + * + * Put a buffer originally in host endianness, to a potentially unaligned + * memory location in big-endian format. + * + * @param dst A valid pointer on a memory area where to copy the data in + * @param src A valid pointer on a memory area where to copy the data from + * @param length Size of both dst and src memory areas + */ + +static inline void sys_put_be(void *dst, const void *src, size_t length) +{ + if (!IS_ENABLED(CONFIG_ENDIAN_BIG)) + { + sys_memcpy_swap(dst, src, length); + } + else + { + (void)memcpy(dst, src, length); + } +} + +/** + * @brief Get a buffer stored in little-endian format. + * + * Get a buffer, stored in little-endian format in a potentially unaligned + * memory location, and convert it to the host endianness. + * + * @param dst A valid pointer on a memory area where to copy the data in + * @param src A valid pointer on a memory area where to copy the data from + * @param length Size of both dst and src memory areas + */ + +static inline void sys_get_le(void *dst, const void *src, size_t length) +{ + if (!IS_ENABLED(CONFIG_ENDIAN_BIG)) + { + (void)memcpy(dst, src, length); + } + else + { + sys_memcpy_swap(dst, src, length); + } +} + +/** + * @brief Get a buffer stored in big-endian format. + * + * Get a buffer, stored in big-endian format in a potentially unaligned + * memory location, and convert it to the host endianness. + * + * @param dst A valid pointer on a memory area where to copy the data in + * @param src A valid pointer on a memory area where to copy the data from + * @param length Size of both dst and src memory areas + */ + +static inline void sys_get_be(void *dst, const void *src, size_t length) +{ + if (!IS_ENABLED(CONFIG_ENDIAN_BIG)) + { + sys_memcpy_swap(dst, src, length); + } + else + { + (void)memcpy(dst, src, length); + } +} + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_SYS_BYTEORDER_H */ diff --git a/include/nuttx/net/bluetooth/compat/sys/check.h b/include/nuttx/net/bluetooth/compat/sys/check.h new file mode 100644 index 0000000000000..d4ff406a2a91e --- /dev/null +++ b/include/nuttx/net/bluetooth/compat/sys/check.h @@ -0,0 +1,44 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/compat/sys/check.h + * + * Author: chao an + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_SYS_CHECK_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_SYS_CHECK_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#if defined(CONFIG_ASSERT_ON_ERRORS) +#define CHECKIF(expr) \ + DEBUGASSERT(!(expr)); \ + if (0) +#elif defined(CONFIG_NO_RUNTIME_CHECKS) +#define CHECKIF(...) if (0) +#else +#define CHECKIF(expr) if (expr) +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_SYS_CHECK_H */ diff --git a/include/nuttx/net/bluetooth/compat/sys/crc.h b/include/nuttx/net/bluetooth/compat/sys/crc.h new file mode 100644 index 0000000000000..3618611455b1a --- /dev/null +++ b/include/nuttx/net/bluetooth/compat/sys/crc.h @@ -0,0 +1,142 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/compat/sys/crc.h + * + * Author: chao an + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_SYS_CRC_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_SYS_CRC_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#ifdef crc8 +#undef crc8 +#endif + +#ifdef crc16 +#undef crc16 +#endif + +#ifdef crc32 +#undef crc32 +#endif + +#define CRC8_POLY 0x07 +#define CRC8_REFLECT_POLY 0xe0 +#define CRC16_POLY 0x8005 +#define CRC16_REFLECT_POLY 0xa001 +#define CRC16_CCITT_POLY 0x1021 + +static inline uint8_t crc8( + const uint8_t *src, size_t len, uint8_t polynomial, uint8_t initial_value, + bool reversed) +{ + uint8_t crc = initial_value; + + while (len-- > 0) + { + crc ^= *src++; + + for (int i = 0; i < 8; i++) + { + if (reversed) + { + crc = (crc & 0x01) ? (crc >> 1) ^ polynomial : crc >> 1; + } + else + { + crc = (crc & 0x80) ? (crc << 1) ^ polynomial : crc << 1; + } + } + } + + return crc; +} + +static inline uint16_t crc16(uint16_t poly, + uint16_t seed, const uint8_t *src, size_t len) +{ + if (poly == CRC16_POLY) + { + return crc16h8005_part(src, len, seed); + } + + if (poly == CRC16_CCITT_POLY) + { + return crc16h1021_part(src, len, seed); + } + + uint16_t crc = seed; + + while (len-- > 0) + { + crc ^= (uint16_t)*src++ << 8; + + for (int i = 0; i < 8; i++) + { + crc = (crc & 0x8000) ? (crc << 1) ^ poly : crc << 1; + } + } + + return crc; +} + +static inline uint16_t crc16_reflect(uint16_t poly, + uint16_t seed, const uint8_t *src, + size_t len) +{ + if (poly == CRC16_REFLECT_POLY) + { + return crc16ibmpart(src, len, seed); + } + + uint16_t crc = seed; + + while (len-- > 0) + { + crc ^= *src++; + + for (int i = 0; i < 8; i++) + { + if ((crc & 0x0001) != 0) + { + crc = (crc >> 1) ^ poly; + } + else + { + crc >>= 1; + } + } + } + + return crc; +} + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_SYS_CRC_H */ diff --git a/include/nuttx/net/bluetooth/compat/sys/iterable_sections.h b/include/nuttx/net/bluetooth/compat/sys/iterable_sections.h new file mode 100644 index 0000000000000..af51d5eb06ba6 --- /dev/null +++ b/include/nuttx/net/bluetooth/compat/sys/iterable_sections.h @@ -0,0 +1,310 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/compat/sys/iterable_sections.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (C) 2020, Intel Corporation + * Copyright (C) 2023, Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef INCLUDE_ZEPHYR_SYS_ITERABLE_SECTIONS_H_ +#define INCLUDE_ZEPHYR_SYS_ITERABLE_SECTIONS_H_ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Iterable Sections APIs + * @defgroup iterable_section_apis Iterable Sections APIs + * @ingroup os_services + * @{ + */ + +/** + * @brief Defines a new element for an iterable section for a generic type. + * + * @details + * Convenience helper combining __in_section() and Z_DECL_ALIGN(). The + * section name will be '.[SECNAME].static.[SECTION_POSTFIX]' + * + * In the linker script, create output sections for these using + * ITERABLE_SECTION_ROM() or ITERABLE_SECTION_RAM(). + * + * @note In order to store the element in ROM, a const specifier has to + * be added to the declaration: const TYPE_SECTION_ITERABLE(...); + * + * @param[in] type data type of variable + * @param[in] varname name of variable to place in section + * @param[in] secname type name of iterable section. + * @param[in] section_postfix postfix to use in section name + */ +#define TYPE_SECTION_ITERABLE(type, varname, secname, section_postfix) \ +Z_DECL_ALIGN(type) varname __in_section(_##secname, static, _CONCAT(section_postfix, _)) __used + +/** + * @brief iterable section start symbol for a generic type + * + * will return '_[OUT_TYPE]_list_start'. + * + * @param[in] secname type name of iterable section. For 'struct foobar' this + * would be TYPE_SECTION_START(foobar) + * + */ +#define TYPE_SECTION_START(secname) _CONCAT(_##secname, _list_start) + +/** + * @brief iterable section end symbol for a generic type + * + * will return '__list_end'. + * + * @param[in] secname type name of iterable section. For 'struct foobar' this + * would be TYPE_SECTION_START(foobar) + */ +#define TYPE_SECTION_END(secname) _CONCAT(_##secname, _list_end) + +/** + * @brief iterable section extern for start symbol for a generic type + * + * Helper macro to give extern for start of iterable section. The macro + * typically will be called TYPE_SECTION_START_EXTERN(struct foobar, foobar). + * This allows the macro to hand different types as well as cases where the + * type and section name may differ. + * + * @param[in] type data type of section + * @param[in] secname name of output section + */ +#define TYPE_SECTION_START_EXTERN(type, secname) extern type TYPE_SECTION_START(secname)[] + +/** + * @brief iterable section extern for end symbol for a generic type + * + * Helper macro to give extern for end of iterable section. The macro + * typically will be called TYPE_SECTION_END_EXTERN(struct foobar, foobar). + * This allows the macro to hand different types as well as cases where the + * type and section name may differ. + * + * @param[in] type data type of section + * @param[in] secname name of output section + */ +#define TYPE_SECTION_END_EXTERN(type, secname) extern type TYPE_SECTION_END(secname)[] + +/** + * @brief Iterate over a specified iterable section for a generic type + * + * @details + * Iterator for structure instances gathered by TYPE_SECTION_ITERABLE(). The + * linker must provide a __list_start symbol and a + * __list_end symbol to mark the start and the end of the list of + * struct objects to iterate over. This is normally done using + * ITERABLE_SECTION_ROM() or ITERABLE_SECTION_RAM() in the linker script. + */ +#define TYPE_SECTION_FOREACH(type, secname, iterator) \ +extern type *_CONCAT(_##secname, _list)[]; \ +type **_iterator = _CONCAT(_##secname, _list); \ +for (type *iterator = (type *)*_iterator; *_iterator; iterator = (type *)*(++_iterator)) + +/** + * @brief Get element from section for a generic type. + * + * @note There is no protection against reading beyond the section. + * + * @param[in] type type of element + * @param[in] secname name of output section + * @param[in] i Index. + * @param[out] dst Pointer to location where pointer to element is + * written. + */ +#define TYPE_SECTION_GET(type, secname, i, dst) \ +do \ + { \ + TYPE_SECTION_START_EXTERN(type, secname); \ + *(dst) = &TYPE_SECTION_START(secname)[i]; \ + } \ +while (0) + +/** + * @brief Count elements in a section for a generic type. + * + * @param[in] type type of element + * @param[in] secname name of output section + * @param[out] dst Pointer to location where result is written. + */ +#define TYPE_SECTION_COUNT(type, secname, dst) \ +do \ + { \ + TYPE_SECTION_START_EXTERN(type, secname); \ + TYPE_SECTION_END_EXTERN(type, secname); \ + *(dst) = ((uintptr_t)TYPE_SECTION_END(secname) - (uintptr_t)TYPE_SECTION_START(secname)) / \ + sizeof(type); \ + } \ +while (0) + +/** + * @brief iterable section start symbol for a struct type + * + * @param[in] struct_type data type of section + */ +#define STRUCT_SECTION_START(struct_type) TYPE_SECTION_START(struct_type) + +/** + * @brief iterable section extern for start symbol for a struct + * + * Helper macro to give extern for start of iterable section. + * + * @param[in] struct_type data type of section + */ +#define STRUCT_SECTION_START_EXTERN(struct_type) \ +TYPE_SECTION_START_EXTERN(struct struct_type, struct_type) + +/** + * @brief iterable section end symbol for a struct type + * + * @param[in] struct_type data type of section + */ +#define STRUCT_SECTION_END(struct_type) TYPE_SECTION_END(struct_type) + +/** + * @brief iterable section extern for end symbol for a struct + * + * Helper macro to give extern for end of iterable section. + * + * @param[in] struct_type data type of section + */ +#define STRUCT_SECTION_END_EXTERN(struct_type) \ +TYPE_SECTION_END_EXTERN(struct struct_type, struct_type) + +/** + * @brief Defines a new element of alternate data type for an iterable + * section. + * + * @details + * Special variant of STRUCT_SECTION_ITERABLE(), for placing alternate data + * types within the iterable section of a specific data type. The data type + * sizes and semantics must be equivalent! + */ +#define STRUCT_SECTION_ITERABLE_ALTERNATE(secname, struct_type, varname) \ +TYPE_SECTION_ITERABLE(struct struct_type, varname, secname, varname) + +/** + * @brief Defines an array of elements of alternate data type for an iterable + * section. + * + * @see STRUCT_SECTION_ITERABLE_ALTERNATE + */ +#define STRUCT_SECTION_ITERABLE_ARRAY_ALTERNATE(secname, struct_type, varname, size) \ +TYPE_SECTION_ITERABLE(struct struct_type, varname[size], secname, varname) + +/** + * @brief Defines a new element for an iterable section. + * + * @details + * Convenience helper combining __in_section() and Z_DECL_ALIGN(). The + * section name is the struct type prepended with an underscore. The + * subsection is "static" and the subsubsection is the variable name. + * + * In the linker script, create output sections for these using + * ITERABLE_SECTION_ROM() or ITERABLE_SECTION_RAM(). + * + * @note In order to store the element in ROM, a const specifier has to + * be added to the declaration: const STRUCT_SECTION_ITERABLE(...); + */ +#define STRUCT_SECTION_ITERABLE(struct_type, varname) \ +STRUCT_SECTION_ITERABLE_ALTERNATE(struct_type, struct_type, varname) + +/** + * @brief Defines an array of elements for an iterable section. + * + * @see STRUCT_SECTION_ITERABLE + */ +#define STRUCT_SECTION_ITERABLE_ARRAY(struct_type, varname, size) \ +STRUCT_SECTION_ITERABLE_ARRAY_ALTERNATE(struct_type, struct_type, varname, size) + +/** + * @brief Defines a new element for an iterable section with a custom name. + * + * The name can be used to customize how iterable section entries are sorted. + * @see STRUCT_SECTION_ITERABLE() + */ +#define STRUCT_SECTION_ITERABLE_NAMED(struct_type, name, varname) \ +TYPE_SECTION_ITERABLE(struct struct_type, varname, struct_type, name) + +/** + * @brief Defines a new element for an iterable section with a custom name, + * placed in a custom section. + * + * The name can be used to customize how iterable section entries are sorted. + * @see STRUCT_SECTION_ITERABLE_NAMED() + */ +#define STRUCT_SECTION_ITERABLE_NAMED_ALTERNATE(struct_type, secname, name, varname) \ +TYPE_SECTION_ITERABLE(struct struct_type, varname, secname, name) + +/** + * @brief Iterate over a specified iterable section (alternate). + * + * @details + * Iterator for structure instances gathered by STRUCT_SECTION_ITERABLE(). + * The linker must provide a __list_start symbol and a + * __list_end symbol to mark the start and the end of the list of + * struct objects to iterate over. This is normally done using + * ITERABLE_SECTION_ROM() or ITERABLE_SECTION_RAM() in the linker script. + */ +#define STRUCT_SECTION_FOREACH_ALTERNATE(secname, struct_type, iterator) \ +TYPE_SECTION_FOREACH(struct struct_type, secname, iterator) + +/** + * @brief Iterate over a specified iterable section. + * + * @details + * Iterator for structure instances gathered by STRUCT_SECTION_ITERABLE(). + * The linker must provide a __list_start symbol and a + * __list_end symbol to mark the start and the end of the list + * of struct objects to iterate over. This is normally done using + * ITERABLE_SECTION_ROM() or ITERABLE_SECTION_RAM() in the linker script. + */ +#define STRUCT_SECTION_FOREACH(struct_type, iterator) \ +STRUCT_SECTION_FOREACH_ALTERNATE(struct_type, struct_type, iterator) + +/** + * @brief Get element from section. + * + * @note There is no protection against reading beyond the section. + * + * @param[in] struct_type Struct type. + * @param[in] i Index. + * @param[out] dst Pointer to location where pointer to element is + * written. + */ +#define STRUCT_SECTION_GET(struct_type, i, dst) \ +TYPE_SECTION_GET(struct struct_type, struct_type, i, dst) + +/** + * @brief Count elements in a section. + * + * @param[in] struct_type Struct type + * @param[out] dst Pointer to location where result is written. + */ +#define STRUCT_SECTION_COUNT(struct_type, dst) \ +TYPE_SECTION_COUNT(struct struct_type, struct_type, dst); + +/** + * @} + */ /* end of struct_section_apis */ + +#ifdef __cplusplus +} +#endif + +#endif /* INCLUDE_ZEPHYR_SYS_ITERABLE_SECTIONS_H_ */ diff --git a/include/nuttx/net/bluetooth/compat/sys/util.h b/include/nuttx/net/bluetooth/compat/sys/util.h new file mode 100644 index 0000000000000..4c00dc6da4b21 --- /dev/null +++ b/include/nuttx/net/bluetooth/compat/sys/util.h @@ -0,0 +1,483 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/compat/sys/util.h + * + * Author: chao an + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_SYS_UTIL_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_SYS_UTIL_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* IS_ENABLED trick */ +#define Z_IS_ENABLED1(config_macro) Z_IS_ENABLED2(_XXXX##config_macro) +#define _XXXX1 _YYYY, +#define Z_IS_ENABLED2(one_or_two_args) Z_IS_ENABLED3(one_or_two_args 1, 0) +#define Z_IS_ENABLED3(ignore, val, ...) val +#define IS_ENABLED(config_macro) Z_IS_ENABLED1(config_macro) + +/* Conditional code selection */ +#define COND_CODE_1(_flag, _if_1_code, _else_code) __COND_CODE(_flag, _if_1_code, _else_code) +#define __COND_CODE(_flag, _if_1_code, _else_code) \ +___COND_CODE(Z_IS_ENABLED1(_flag), _if_1_code, _else_code) +#define ___COND_CODE(_flag, _if_1_code, _else_code) \ +____COND_CODE(UTIL_CAT(_CC1_, _flag), _if_1_code, _else_code) +#define ____COND_CODE(selector, _if_1_code, _else_code) selector(_if_1_code, _else_code) +#define Z_DEBRACKET(...) __VA_ARGS__ +#define _CC1_1(_if_1_code, _else_code) Z_DEBRACKET _if_1_code +#define _CC1_0(_if_1_code, _else_code) Z_DEBRACKET _else_code + +/* IF_ENABLED - compile code only when config is enabled */ +#define IF_ENABLED(_cfg, _code) COND_CODE_1(_cfg, _code, ()) + +/* Arithmetic - using simple ternary for constant-foldability */ + +/* Force override toolchain.h statement-expression-compatible versions */ +#ifdef Z_MAX +#undef Z_MAX +#endif +#ifndef Z_MAX +#define Z_MAX(a, b) ((a) > (b) ? (a) : (b)) +#endif + +#ifdef Z_MIN +#undef Z_MIN +#endif +#ifndef Z_MIN +#define Z_MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + +#ifdef Z_CLAMP +#undef Z_CLAMP +#endif +#ifndef Z_CLAMP +#define Z_CLAMP(val, low, high) Z_MIN(Z_MAX(val, low), high) +#endif + +#ifndef CLAMP +#define CLAMP(val, low, high) Z_CLAMP(val, low, high) +#endif + +/* MIN/MAX convenience */ +#ifndef MAX +#define MAX(a, b) Z_MAX(a, b) +#endif + +#ifndef MIN +#define MIN(a, b) Z_MIN(a, b) +#endif + +/* Container of */ +#ifndef container_of +#define container_of(ptr, type, member) ((type *)((char *)(ptr) - offsetof(type, member))) +#endif + +#ifndef CONTAINER_OF +#define CONTAINER_OF(ptr, type, member) container_of(ptr, type, member) +#endif + +/* List helpers used by the Bluetooth compatibility layer. Keep these here + * instead of extending the generic NuttX list API. + */ + +#define list_for_every_container(list, entry, member) \ +list_for_every_entry(list, entry, __typeof__(*(entry)), member) + +#define list_for_every_container_safe(list, entry, temp, member) \ +list_for_every_entry_safe(list, entry, temp, __typeof__(*(entry)), member) + +#define list_peek_head_container(list, entry, member) \ +list_peek_head_type(list, __typeof__(*(entry)), member) + +#define list_peek_tail_container(list, entry, member) \ +list_peek_tail_type(list, __typeof__(*(entry)), member) + +#define list_next_container(list, entry, member) \ +list_next_type(list, &(entry)->member, __typeof__(*(entry)), member) + +static inline void bt_list_merge(struct list_node *list, + struct list_node *list_to_append) +{ + struct list_node *node; + + while ((node = list_remove_head(list_to_append)) != NULL) + { + list_add_tail(list, node); + } +} + +#define list_merge(list, list_to_append) bt_list_merge(list, list_to_append) + +static inline void bt_list_insert_after( + struct list_node *list, struct list_node *prev, struct list_node *node) +{ + if (prev == NULL) + { + list_add_head(list, node); + } + else + { + list_add_after(prev, node); + } +} + +#define list_insert_after(list, prev, node) bt_list_insert_after(list, prev, node) + +static inline bool bt_list_find( + struct list_node *list, const struct list_node *node, + struct list_node **prev) +{ + struct list_node *cur; + struct list_node *last = NULL; + + list_for_every(list, cur) + { + if (cur == node) + { + if (prev != NULL) + { + *prev = last; + } + + return true; + } + + last = cur; + } + + if (prev != NULL) + { + *prev = last; + } + + return false; +} + +#define list_find(list, node, prev) bt_list_find(list, node, prev) + +static inline bool bt_list_find_and_remove(struct list_node *list, + struct list_node *node) +{ + if (!bt_list_find(list, node, NULL)) + { + return false; + } + + list_delete(node); + return true; +} + +#define list_find_and_remove(list, node) bt_list_find_and_remove(list, node) + +static inline void bt_list_remove( + struct list_node *list, struct list_node *prev, struct list_node *node) +{ + (void)list; + (void)prev; + + list_delete(node); +} + +#define list_remove(list, prev, node) bt_list_remove(list, prev, node) + +/* Array size */ +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0])) +#endif + +#define ARRAY_INDEX(array, ptr) ((ptr) - (array)) + +#define POINTER_TO_UINT(x) ((uintptr_t)(x)) +#define UINT_TO_POINTER(x) ((void *)(uintptr_t)(x)) + +#define atomic_bits (sizeof(atomic_t) * 8) +#define atomic_bitmap_size(num_bits) (((num_bits) + atomic_bits - 1) / atomic_bits) + +#define atomic_define(name, num_bits) atomic_t name[atomic_bitmap_size(num_bits)] + +#define atomic_cmpxchg_value(obj, expected, desired) \ +({ \ + int32_t __expected = (expected); \ + atomic_cmpxchg(obj, &__expected, desired); \ +}) + +#define atomic_set_bit(target, bit) \ +((void)atomic_or(&(target)[(bit) / atomic_bits], (int32_t)(1ULL << ((bit) % atomic_bits)))) + +#define atomic_clear_bit(target, bit) \ +((void)atomic_and(&(target)[(bit) / atomic_bits], ~(int32_t)(1ULL << ((bit) % atomic_bits)))) + +#define atomic_test_bit(target, bit) \ +((atomic_read(&(target)[(bit) / atomic_bits]) & (int32_t)(1ULL << ((bit) % atomic_bits))) != 0) + +#define atomic_test_and_set_bit(target, bit) \ +((atomic_or(&(target)[(bit) / atomic_bits], (int32_t)(1ULL << ((bit) % atomic_bits))) & \ + (int32_t)(1ULL << ((bit) % atomic_bits))) != 0) + +#define atomic_test_and_clear_bit(target, bit) \ +((atomic_and(&(target)[(bit) / atomic_bits], ~(int32_t)(1ULL << ((bit) % atomic_bits))) & \ + (int32_t)(1ULL << ((bit) % atomic_bits))) != 0) + +#define atomic_set_bit_to(target, bit, val) \ +do \ + { \ + if (val) \ + { \ + atomic_set_bit(target, bit); \ + } \ + else \ + { \ + atomic_clear_bit(target, bit); \ + } \ + } \ +while (0) + +#define atomic_ptr_read(target) __atomic_load_n(target, __ATOMIC_RELAXED) + +#define atomic_ptr_xchg(target, value) __atomic_exchange_n(target, value, __ATOMIC_SEQ_CST) + +#define atomic_ptr_cmpxchg(target, expected, desired) \ +({ \ + __auto_type __expected = (expected); \ + __atomic_compare_exchange_n(target, &__expected, desired, false, __ATOMIC_SEQ_CST, \ + __ATOMIC_RELAXED); \ +}) + +/* Round up/down */ +#define ROUND_UP(x, align) (((x) + ((align) - 1)) & ~((align) - 1)) +#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) + +/* Inclusive range check */ +#ifndef IN_RANGE +#define IN_RANGE(val, min, max) (((val) >= (min)) && ((val) <= (max))) +#endif + +/* Part of array */ +#define PART_OF_ARRAY(array, ptr) \ +((uintptr_t)(ptr) >= (uintptr_t)(array) && \ + (uintptr_t)(ptr) < (uintptr_t)(&(array)[ARRAY_SIZE(array)])) + +#define IS_ARRAY_ELEMENT(array, ptr) PART_OF_ARRAY(array, ptr) + +/* Listify - expand macro F(idx, ...) LEN times. */ +#define LISTIFY(LEN, F, SEP, ...) UTIL_CAT(LISTIFY_, LEN)(F, SEP, __VA_ARGS__) +#define LISTIFY_0(F, SEP, ...) +#define LISTIFY_1(F, SEP, ...) F(0, __VA_ARGS__) +#define LISTIFY_2(F, SEP, ...) LISTIFY_1(F, SEP, __VA_ARGS__) Z_DEBRACKET SEP F(1, __VA_ARGS__) +#define LISTIFY_3(F, SEP, ...) LISTIFY_2(F, SEP, __VA_ARGS__) Z_DEBRACKET SEP F(2, __VA_ARGS__) +#define LISTIFY_4(F, SEP, ...) LISTIFY_3(F, SEP, __VA_ARGS__) Z_DEBRACKET SEP F(3, __VA_ARGS__) +#define LISTIFY_5(F, SEP, ...) LISTIFY_4(F, SEP, __VA_ARGS__) Z_DEBRACKET SEP F(4, __VA_ARGS__) +#define LISTIFY_6(F, SEP, ...) LISTIFY_5(F, SEP, __VA_ARGS__) Z_DEBRACKET SEP F(5, __VA_ARGS__) +#define LISTIFY_7(F, SEP, ...) LISTIFY_6(F, SEP, __VA_ARGS__) Z_DEBRACKET SEP F(6, __VA_ARGS__) +#define LISTIFY_8(F, SEP, ...) LISTIFY_7(F, SEP, __VA_ARGS__) Z_DEBRACKET SEP F(7, __VA_ARGS__) +#define LISTIFY_9(F, SEP, ...) LISTIFY_8(F, SEP, __VA_ARGS__) Z_DEBRACKET SEP F(8, __VA_ARGS__) +#define LISTIFY_10(F, SEP, ...) LISTIFY_9(F, SEP, __VA_ARGS__) Z_DEBRACKET SEP F(9, __VA_ARGS__) +#define LISTIFY_11(F, SEP, ...) LISTIFY_10(F, SEP, __VA_ARGS__) Z_DEBRACKET SEP F(10, __VA_ARGS__) +#define LISTIFY_12(F, SEP, ...) LISTIFY_11(F, SEP, __VA_ARGS__) Z_DEBRACKET SEP F(11, __VA_ARGS__) +#define LISTIFY_13(F, SEP, ...) LISTIFY_12(F, SEP, __VA_ARGS__) Z_DEBRACKET SEP F(12, __VA_ARGS__) +#define LISTIFY_14(F, SEP, ...) LISTIFY_13(F, SEP, __VA_ARGS__) Z_DEBRACKET SEP F(13, __VA_ARGS__) +#define LISTIFY_15(F, SEP, ...) LISTIFY_14(F, SEP, __VA_ARGS__) Z_DEBRACKET SEP F(14, __VA_ARGS__) +#define LISTIFY_16(F, SEP, ...) LISTIFY_15(F, SEP, __VA_ARGS__) Z_DEBRACKET SEP F(15, __VA_ARGS__) +#define LISTIFY_17(F, SEP, ...) LISTIFY_16(F, SEP, __VA_ARGS__) Z_DEBRACKET SEP F(16, __VA_ARGS__) +#define LISTIFY_18(F, SEP, ...) LISTIFY_17(F, SEP, __VA_ARGS__) Z_DEBRACKET SEP F(17, __VA_ARGS__) +#define LISTIFY_19(F, SEP, ...) LISTIFY_18(F, SEP, __VA_ARGS__) Z_DEBRACKET SEP F(18, __VA_ARGS__) +#define LISTIFY_20(F, SEP, ...) LISTIFY_19(F, SEP, __VA_ARGS__) Z_DEBRACKET SEP F(19, __VA_ARGS__) + +#define REVERSE_ARGS(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p) \ +p, o, n, m, l, k, j, i, h, g, f, e, d, c, b, a + +#define UTIL_CAT(a, b) UTIL_PRIMITIVE_CAT(a, b) +#define UTIL_PRIMITIVE_CAT(a, b) a##b + +#define UTIL_INC(v) UTIL_PRIMITIVE_CAT(UTIL_INC_, v) +#define UTIL_INC_0 1 +#define UTIL_INC_1 2 +#define UTIL_INC_2 3 +#define UTIL_INC_3 4 +#define UTIL_INC_4 5 +#define UTIL_INC_5 6 +#define UTIL_INC_6 7 +#define UTIL_INC_7 8 +#define UTIL_INC_8 9 + +/* Array iteration macros */ +#define ARRAY_FOR_EACH(array, idx) for (size_t idx = 0; (idx) < ARRAY_SIZE(array); (idx)++) + +#define ARRAY_FOR_EACH_PTR(array, ptr) \ +for (__typeof__(*(array)) *ptr = (array); ptr < &(array)[ARRAY_SIZE(array)]; ptr++) + +/* BIT macro */ +#ifndef BIT +#define BIT(n) (1UL << (n)) +#endif + +/* BIT_MASK */ +#define BIT_MASK(n) (BIT(n) - 1) + +#ifndef GENMASK +#define GENMASK(h, l) \ +(((~0UL) >> ((sizeof(unsigned long) * 8) - 1 - MAX(h, l))) & (~0UL << MIN(h, l))) +#endif + +#ifndef FIELD_GET +#define FIELD_GET(mask, value) (((value) & (mask)) >> __builtin_ctzl(mask)) +#endif + +#ifndef FIELD_PREP +#define FIELD_PREP(mask, value) (((value) << __builtin_ctzl(mask)) & (mask)) +#endif + +#ifndef WRITE_BIT +#define WRITE_BIT(var, bit, set) \ +do \ + { \ + if (set) \ + { \ + (var) |= BIT(bit); \ + } \ + else \ + { \ + (var) &= ~BIT(bit); \ + } \ + } \ +while (0) +#endif + +/** + * @brief Convert a hex string to binary data. + * + * @param hex Hex string to convert + * @param hexlen Length of hex string + * @param buf Output buffer + * @param buflen Size of output buffer + * @return Number of bytes written to buf, or 0 on error + */ + +static inline size_t hex2bin(const char *hex, + size_t hexlen, uint8_t *buf, size_t buflen) +{ + size_t i; + + if ((hexlen % 2) != 0 || (hexlen / 2) > buflen) + { + return 0; + } + + for (i = 0; i < hexlen / 2; i++) + { + char byte[3] = + { + hex[i * 2], hex[i * 2 + 1], '\0' + }; + unsigned int val; + + if (sscanf(byte, "%02x", &val) != 1) + { + return 0; + } + + buf[i] = (uint8_t)val; + } + + return hexlen / 2; +} + +static inline bool util_memeq(const void *a, const void *b, size_t len) +{ + return memcmp(a, b, len) == 0; +} + +static inline bool util_eq(const void *a, + size_t a_len, const void *b, size_t b_len) +{ + return a_len == b_len && memcmp(a, b, a_len) == 0; +} + +static inline void mem_xor_n(uint8_t *dst, + const uint8_t *src1, const uint8_t *src2, + size_t len) +{ + size_t i; + + for (i = 0; i < len; i++) + { + dst[i] = src1[i] ^ src2[i]; + } +} + +static inline void mem_xor_128(uint8_t dst[16], + const uint8_t src1[16], + const uint8_t src2[16]) +{ + mem_xor_n(dst, src1, src2, 16); +} + +static inline size_t sys_count_bits(const void *mem, size_t size) +{ + const uint8_t *bytes = mem; + size_t count = 0; + size_t i; + + for (i = 0; i < size; i++) + { + count += __builtin_popcount(bytes[i]); + } + + return count; +} + +/* u8_to_dec - convert uint8_t to decimal string */ + +static inline uint8_t u8_to_dec(char *buf, uint8_t buflen, uint8_t val) +{ + uint8_t pos = 0; + if (val >= 100 && pos < buflen) + buf[pos++] = '0' + val / 100; + if (val >= 10 && pos < buflen) + buf[pos++] = '0' + (val / 10) % 10; + if (pos < buflen) + buf[pos++] = '0' + val % 10; + if (pos < buflen) + buf[pos] = '\0'; + return pos; +} + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_SYS_UTIL_H */ diff --git a/include/nuttx/net/bluetooth/compat/toolchain.h b/include/nuttx/net/bluetooth/compat/toolchain.h new file mode 100644 index 0000000000000..ce099de012e91 --- /dev/null +++ b/include/nuttx/net/bluetooth/compat/toolchain.h @@ -0,0 +1,115 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/compat/toolchain.h + * + * Author: chao an + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_TOOLCHAIN_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_TOOLCHAIN_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#define Z_STRINGIFY(x) #x +#define STRINGIFY(s) Z_STRINGIFY(s) + +#define _DO_CONCAT(x, y) x##y +#define _CONCAT(x, y) _DO_CONCAT(x, y) + +#if defined(__cplusplus) && (__cplusplus >= 201103L) +#define BUILD_ASSERT(EXPR, MSG...) static_assert(EXPR, "" MSG) +#else +#define BUILD_ASSERT(EXPR, MSG...) _Static_assert((EXPR), "" MSG) +#endif + +#ifndef FLEXIBLE_ARRAY_DECLARE +#define FLEXIBLE_ARRAY_DECLARE(type, name, ...) type name[0] +#endif + +#define Z_DECL_ALIGN(type) __aligned(__alignof(type)) type + +#define CODE_UNREACHABLE compiler_assume(false) + +#define UNALIGNED_GET(g) \ + __extension__({ \ + struct __packed \ + { \ + __typeof__(*(g)) __v; \ + } *__g = (__typeof__(__g))(g); \ + __g->__v; \ + }) +#define ___in_section(a, b, c) +#define __in_section(a, b, c) ___in_section(a, b, c) + +#ifndef __fallthrough +#if __GNUC__ >= 7 +#define __fallthrough __attribute__((fallthrough)) +#else +#define __fallthrough +#endif +#endif + +#ifndef __packed +#define __packed end_packed_struct +#endif + +#ifndef __aligned +#define __aligned(x) aligned_data(x) +#endif + +#ifndef __printf_like +#define __printf_like(f, a) printf_like(f, a) +#endif + +#define __used used_code +#define __unused unused_code +#define __maybe_unused unused_code + +#ifndef __deprecated +#define __deprecated deprecated_function +#endif + +#ifndef __must_check +#define __must_check __attribute__((warn_unused_result)) +#endif + +#define ARG_UNUSED(x) (void)(x) +#define unlikely(x) (predict_false((bool)!!(x)) != 0L) +#define POPCOUNT(x) __builtin_popcount(x) + +#ifndef __weak +#define __weak weak_function +#endif + +#define __WARN(msg) __WARN1(GCC warning msg) +#define __WARN1(s) _Pragma(#s) + +#ifndef __DEPRECATED_MACRO +#define __DEPRECATED_MACRO __WARN("Macro is deprecated") +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_COMPAT_TOOLCHAIN_H */ diff --git a/include/nuttx/net/bluetooth/conn.h b/include/nuttx/net/bluetooth/conn.h new file mode 100644 index 0000000000000..221eeb612c98f --- /dev/null +++ b/include/nuttx/net/bluetooth/conn.h @@ -0,0 +1,4269 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/conn.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Bluetooth connection handling + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_CONN_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_CONN_H + +/** + * @brief Connection management + * @defgroup bt_conn Connection management + * @since 1.0 + * @version 1.0.1 + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @struct bt_conn + * @brief Opaque type representing a connection to a remote device + */ + +struct bt_conn; + +/** Connection parameters for LE connections */ + +struct bt_le_conn_param +{ + uint16_t interval_min; + uint16_t interval_max; + uint16_t latency; + uint16_t timeout; +}; + +/** @brief Initialize connection parameters + * + * @param int_min Minimum Connection Interval (N * 1.25 ms) + * @param int_max Maximum Connection Interval (N * 1.25 ms) + * @param lat Connection Latency + * @param to Supervision Timeout (N * 10 ms) + */ +#define BT_LE_CONN_PARAM_INIT(int_min, int_max, lat, to) \ +{ \ + .interval_min = (int_min), \ + .interval_max = (int_max), \ + .latency = (lat), \ + .timeout = (to), \ +} + +/** Helper to declare connection parameters inline + * + * @param int_min Minimum Connection Interval (N * 1.25 ms) + * @param int_max Maximum Connection Interval (N * 1.25 ms) + * @param lat Connection Latency + * @param to Supervision Timeout (N * 10 ms) + */ +#define BT_LE_CONN_PARAM(int_min, int_max, lat, to) \ +((struct bt_le_conn_param[]){BT_LE_CONN_PARAM_INIT(int_min, int_max, lat, to)}) + +/** Default LE connection parameters: + * Connection Interval: 30-50 ms Latency: 0 Timeout: 4 s + */ +#define BT_LE_CONN_PARAM_DEFAULT \ +BT_LE_CONN_PARAM(BT_GAP_INIT_CONN_INT_MIN, BT_GAP_INIT_CONN_INT_MAX, 0, \ + BT_GAP_MS_TO_CONN_TIMEOUT(4000)) + +/** Connection PHY information for LE connections */ + +struct bt_conn_le_phy_info +{ + uint8_t tx_phy; /** Connection transmit PHY */ + uint8_t rx_phy; /** Connection receive PHY */ +}; + +/** Connection PHY options */ + +enum +{ + /** Convenience value when no options are specified. */ + + BT_CONN_LE_PHY_OPT_NONE = 0, + + /** LE Coded using S=2 coding preferred when transmitting. */ + + BT_CONN_LE_PHY_OPT_CODED_S2 = BIT(0), + + /** LE Coded using S=8 coding preferred when transmitting. */ + + BT_CONN_LE_PHY_OPT_CODED_S8 = BIT(1), +}; + +/** Preferred PHY parameters for LE connections */ + +struct bt_conn_le_phy_param +{ + uint16_t options; /**< Connection PHY options. */ + uint8_t pref_tx_phy; /**< Bitmask of preferred transmit PHYs */ + uint8_t pref_rx_phy; /**< Bitmask of preferred receive PHYs */ +}; + +/** Initialize PHY parameters + * + * @param _pref_tx_phy Bitmask of preferred transmit PHYs. + * @param _pref_rx_phy Bitmask of preferred receive PHYs. + */ +#define BT_CONN_LE_PHY_PARAM_INIT(_pref_tx_phy, _pref_rx_phy) \ +{ \ + .options = BT_CONN_LE_PHY_OPT_NONE, \ + .pref_tx_phy = (_pref_tx_phy), \ + .pref_rx_phy = (_pref_rx_phy), \ +} + +/** Helper to declare PHY parameters inline + * + * @param _pref_tx_phy Bitmask of preferred transmit PHYs. + * @param _pref_rx_phy Bitmask of preferred receive PHYs. + */ +#define BT_CONN_LE_PHY_PARAM(_pref_tx_phy, _pref_rx_phy) \ +((struct bt_conn_le_phy_param[]){BT_CONN_LE_PHY_PARAM_INIT(_pref_tx_phy, _pref_rx_phy)}) + +/** Only LE 1M PHY */ +#define BT_CONN_LE_PHY_PARAM_1M BT_CONN_LE_PHY_PARAM(BT_GAP_LE_PHY_1M, BT_GAP_LE_PHY_1M) + +/** Only LE 2M PHY */ +#define BT_CONN_LE_PHY_PARAM_2M BT_CONN_LE_PHY_PARAM(BT_GAP_LE_PHY_2M, BT_GAP_LE_PHY_2M) + +/** Only LE Coded PHY. */ +#define BT_CONN_LE_PHY_PARAM_CODED BT_CONN_LE_PHY_PARAM(BT_GAP_LE_PHY_CODED, BT_GAP_LE_PHY_CODED) + +/** All LE PHYs. */ +#define BT_CONN_LE_PHY_PARAM_ALL \ +BT_CONN_LE_PHY_PARAM(BT_GAP_LE_PHY_1M | BT_GAP_LE_PHY_2M | BT_GAP_LE_PHY_CODED, \ + BT_GAP_LE_PHY_1M | BT_GAP_LE_PHY_2M | BT_GAP_LE_PHY_CODED) + +/** Connection data length information for LE connections */ + +struct bt_conn_le_data_len_info +{ + /** Maximum Link Layer transmission payload size in bytes. */ + + uint16_t tx_max_len; + + /** Maximum Link Layer transmission payload time in us. */ + + uint16_t tx_max_time; + + /** Maximum Link Layer reception payload size in bytes. */ + + uint16_t rx_max_len; + + /** Maximum Link Layer reception payload time in us. */ + + uint16_t rx_max_time; +}; + +/** Connection data length parameters for LE connections */ + +struct bt_conn_le_data_len_param +{ + /** Maximum Link Layer transmission payload size in bytes. */ + + uint16_t tx_max_len; + + /** Maximum Link Layer transmission payload time in us. */ + + uint16_t tx_max_time; +}; + +/** Initialize transmit data length parameters + * + * @param _tx_max_len Maximum Link Layer transmission payload size in + * bytes. + * @param _tx_max_time Maximum Link Layer transmission payload time in us. + */ +#define BT_CONN_LE_DATA_LEN_PARAM_INIT(_tx_max_len, _tx_max_time) \ +{ \ + .tx_max_len = (_tx_max_len), \ + .tx_max_time = (_tx_max_time), \ +} + +/** Helper to declare transmit data length parameters inline + * + * @param _tx_max_len Maximum Link Layer transmission payload size in + * bytes. + * @param _tx_max_time Maximum Link Layer transmission payload time in us. + */ +#define BT_CONN_LE_DATA_LEN_PARAM(_tx_max_len, _tx_max_time) \ +((struct bt_conn_le_data_len_param[]){BT_CONN_LE_DATA_LEN_PARAM_INIT( \ + _tx_max_len, _tx_max_time)}) + +/** Default LE data length parameters. */ +#define BT_LE_DATA_LEN_PARAM_DEFAULT \ + BT_CONN_LE_DATA_LEN_PARAM(BT_GAP_DATA_LEN_DEFAULT, \ + BT_GAP_DATA_TIME_DEFAULT) + +/** Maximum LE data length parameters. */ +#define BT_LE_DATA_LEN_PARAM_MAX \ + BT_CONN_LE_DATA_LEN_PARAM(BT_GAP_DATA_LEN_MAX, \ + BT_GAP_DATA_TIME_MAX) + +/** Connection subrating parameters for LE connections */ + +struct bt_conn_le_subrate_param +{ + /** Minimum subrate factor. */ + + uint16_t subrate_min; + + /** Maximum subrate factor. */ + + uint16_t subrate_max; + + /** Maximum Peripheral latency in units of subrated connection intervals. */ + + uint16_t max_latency; + /** Minimum number of underlying connection events to remain active + * after a packet containing a Link Layer PDU with a non-zero Length field + * is sent or received. + */ + + uint16_t continuation_number; + /** Connection Supervision timeout (N * 10 ms). + * If using @ref bt_conn_le_subrate_set_defaults, this is the maximum + * supervision timeout allowed in requests by a peripheral. + */ + + uint16_t supervision_timeout; +}; + +/** Subrating information for LE connections */ + +struct bt_conn_le_subrating_info +{ + /** Connection subrate factor. */ + + uint16_t factor; + /** Number of underlying connection events to remain active after + * a packet containing a Link Layer PDU with a non-zero Length field is + * sent or received. + */ + + uint16_t continuation_number; +}; + +/** Updated subrating connection parameters for LE connections */ + +struct bt_conn_le_subrate_changed +{ + /** HCI Status from LE Subrate Changed event. + * The remaining parameters will be unchanged if status is not + * BT_HCI_ERR_SUCCESS. + */ + + uint8_t status; + + /** Connection subrate factor. */ + + uint16_t factor; + /** Number of underlying connection events to remain active after + * a packet containing a Link Layer PDU with a non-zero Length field is + * sent or received. + */ + + uint16_t continuation_number; + + /** Peripheral latency in units of subrated connection intervals. */ + + uint16_t peripheral_latency; + + /** Connection Supervision timeout (N * 10 ms). */ + + uint16_t supervision_timeout; +}; + +/** @brief Maximum Connection Interval Groups possible + * + * The practical maximum is 41 groups based on HCI event size constraints: + * (HCI event max size - cmd_complete overhead - response fields) / + * sizeof(group) (255 - 4 - 3) / 6 = 41 groups max + */ +#define BT_CONN_LE_MAX_CONN_INTERVAL_GROUPS 41 + +/** @brief Minimum supported connection interval group + * + * Each group represents an arithmetic sequence of supported connection + * intervals: min, min + stride, min + 2 * stride, ..., min + n * stride + * (where min + n * stride <= max) + * + * Example: min = 10 (1250 us), max = 60 (7500 us), stride = 5 (625 us) + * -> Supported: 1250 us, 1875 us, 2500 us, ..., 6875 us, 7500 us + */ + +struct bt_conn_le_min_conn_interval_group +{ + /** @brief Lower bound of group interval range + * + * Unit: 125 microseconds + * + * Range: @ref BT_HCI_LE_SCI_INTERVAL_MIN_125US - @ref + * BT_HCI_LE_SCI_INTERVAL_MAX_125US + */ + + uint16_t min_125us; + /** @brief Upper bound of group interval range + * + * Unit: 125 microseconds + * + * Range: @ref BT_HCI_LE_SCI_INTERVAL_MIN_125US - @ref + * BT_HCI_LE_SCI_INTERVAL_MAX_125US + */ + + uint16_t max_125us; + /** @brief Increment between consecutive supported intervals + * + * Unit: 125 microseconds + * + * Range: @ref BT_HCI_LE_SCI_STRIDE_MIN_125US - @ref + * BT_HCI_LE_SCI_INTERVAL_MAX_125US + */ + + uint16_t stride_125us; +}; + +/** Minimum supported connection interval information */ + +struct bt_conn_le_min_conn_interval_info +{ + /** @brief Minimum supported connection interval + * + * Unit: microseconds + * + * Range: @ref BT_HCI_LE_MIN_SUPP_CONN_INT_MIN_US - @ref + * BT_HCI_LE_MIN_SUPP_CONN_INT_MAX_US + */ + + uint16_t min_supported_conn_interval_us; + /** @brief Number of interval groups. + * + * Range: 0 - @ref BT_CONN_LE_MAX_CONN_INTERVAL_GROUPS + * + * If zero, the controller only supports Rounded ConnInterval Values (RCV). + */ + + uint8_t num_groups; + /** @brief Array of supported connection interval groups. + * + * Multiple groups allow representing non-contiguous or differently-strided + * ranges. + */ + + struct bt_conn_le_min_conn_interval_group + groups[BT_CONN_LE_MAX_CONN_INTERVAL_GROUPS]; +}; + +/** Connection rate parameters for LE connections */ + +struct bt_conn_le_conn_rate_param +{ + /** @brief Minimum connection interval + * + * Unit: 125 microseconds + * + * Range: @ref BT_HCI_LE_SCI_INTERVAL_MIN_125US - @ref + * BT_HCI_LE_SCI_INTERVAL_MAX_125US + */ + + uint16_t interval_min_125us; + /** @brief Maximum connection interval + * + * Unit: 125 microseconds + * + * Range: @ref BT_HCI_LE_SCI_INTERVAL_MIN_125US - @ref + * BT_HCI_LE_SCI_INTERVAL_MAX_125US + */ + + uint16_t interval_max_125us; + /** @brief Minimum subrate factor + * + * Range: @ref BT_HCI_LE_SUBRATE_FACTOR_MIN - @ref + * BT_HCI_LE_SUBRATE_FACTOR_MAX + */ + + uint16_t subrate_min; + /** @brief Maximum subrate factor + * + * Range: @ref BT_HCI_LE_SUBRATE_FACTOR_MIN - @ref + * BT_HCI_LE_SUBRATE_FACTOR_MAX + */ + + uint16_t subrate_max; + /** @brief Maximum Peripheral latency + * + * Unit: subrated connection intervals @ref + * bt_conn_le_conn_rate_changed.subrate_factor + * + * Range: @ref BT_HCI_LE_PERIPHERAL_LATENCY_MIN - @ref + * BT_HCI_LE_PERIPHERAL_LATENCY_MAX + */ + + uint16_t max_latency; + /** @brief Minimum number of underlying connection events to remain active + * after a packet containing a Link Layer PDU with a non-zero Length field + * is sent or received. + * + * Range: @ref BT_HCI_LE_CONTINUATION_NUM_MIN - @ref + * BT_HCI_LE_CONTINUATION_NUM_MAX + */ + + uint16_t continuation_number; + /** @brief Connection Supervision timeout + * + * Unit: 10 milliseconds + * + * Range: @ref BT_HCI_LE_SUPERVISION_TIMEOUT_MIN - @ref + * BT_HCI_LE_SUPERVISION_TIMEOUT_MAX + */ + + uint16_t supervision_timeout_10ms; + /** @brief Minimum length of connection event + * + * Unit: 125 microseconds + * + * Range: @ref BT_HCI_LE_SCI_CE_LEN_MIN_125US - @ref + * BT_HCI_LE_SCI_CE_LEN_MAX_125US + */ + + uint16_t min_ce_len_125us; + /** @brief Maximum length of connection event + * + * Unit: 125 microseconds + * + * Range: @ref BT_HCI_LE_SCI_CE_LEN_MIN_125US - @ref + * BT_HCI_LE_SCI_CE_LEN_MAX_125US + */ + + uint16_t max_ce_len_125us; +}; + +/** Updated connection rate parameters */ + +struct bt_conn_le_conn_rate_changed +{ + /** Connection interval + * + * Unit: microseconds + * + * Range: @ref BT_HCI_LE_SCI_INTERVAL_MIN_US - @ref + * BT_HCI_LE_SCI_INTERVAL_MAX_US + */ + + uint32_t interval_us; + /** Connection subrate factor + * + * Range: @ref BT_HCI_LE_SUBRATE_FACTOR_MIN - @ref + * BT_HCI_LE_SUBRATE_FACTOR_MAX + */ + + uint16_t subrate_factor; + /** Peripheral latency + * + * Unit: subrated connection intervals @ref + * bt_conn_le_conn_rate_changed.subrate_factor + * + * Range: @ref BT_HCI_LE_PERIPHERAL_LATENCY_MIN - @ref + * BT_HCI_LE_PERIPHERAL_LATENCY_MAX + */ + + uint16_t peripheral_latency; + /** Number of underlying connection events to remain active after + * a packet containing a Link Layer PDU with a non-zero Length field is + * sent or received. + * + * Range: @ref BT_HCI_LE_CONTINUATION_NUM_MIN - @ref + * BT_HCI_LE_CONTINUATION_NUM_MAX + */ + + uint16_t continuation_number; + /** Connection Supervision timeout + * + * Unit: 10 milliseconds + * + * Range: @ref BT_HCI_LE_SUPERVISION_TIMEOUT_MIN - @ref + * BT_HCI_LE_SUPERVISION_TIMEOUT_MAX + */ + + uint16_t supervision_timeout_10ms; +}; + +/** Read all remote features complete callback params */ + +struct bt_conn_le_read_all_remote_feat_complete +{ + /** @brief HCI Status from LE Read All Remote Features Complete event. + * + * The remaining parameters will be unchanged if status is not @ref + * BT_HCI_ERR_SUCCESS. + */ + + uint8_t status; + + /** Number of pages supported by remote device. */ + + uint8_t max_remote_page; + + /** Number of pages fetched from remote device. */ + + uint8_t max_valid_page; + /** @brief Pointer to array of size 248, with feature bits of remote + * supported features. + * + * Page 0 being 8 bytes, with the following 10 pages of 24 bytes. Refer to + * BT_LE_FEAT_BIT_* for values. Refer to the BT_FEAT_LE_* macros for value + * comparison. See Bluetooth Core Specification, Vol 6, Part B, Section + * 4.6. + */ + + const uint8_t *features; +}; + +#define BT_CONN_LE_FRAME_SPACE_TYPES_MASK_ACL_IFS \ +(BT_HCI_LE_FRAME_SPACE_UPDATE_SPACING_TYPE_IFS_ACL_CP_MASK | \ + BT_HCI_LE_FRAME_SPACE_UPDATE_SPACING_TYPE_IFS_ACL_PC_MASK) + +#define BT_CONN_LE_FRAME_SPACE_TYPES_MASK_ACL \ +(BT_HCI_LE_FRAME_SPACE_UPDATE_SPACING_TYPE_IFS_ACL_CP_MASK | \ + BT_HCI_LE_FRAME_SPACE_UPDATE_SPACING_TYPE_IFS_ACL_PC_MASK | \ + BT_HCI_LE_FRAME_SPACE_UPDATE_SPACING_TYPE_MCES_MASK) + +#define BT_CONN_LE_FRAME_SPACE_TYPES_MASK_CIS \ +(BT_HCI_LE_FRAME_SPACE_UPDATE_SPACING_TYPE_IFS_CIS_MASK | \ + BT_HCI_LE_FRAME_SPACE_UPDATE_SPACING_TYPE_MSS_CIS_MASK) + +/** Maximum frame space in microseconds. + * As defined in Bluetooth Core Specification, Vol 4, Part E, Section + * 7.8.151. + */ +#define BT_CONN_LE_FRAME_SPACE_MAX (10000U) + +/** Frame space update initiator. */ + +enum bt_conn_le_frame_space_update_initiator +{ + /** Initiated by local host */ + + BT_CONN_LE_FRAME_SPACE_UPDATE_INITIATOR_LOCAL_HOST = + BT_HCI_LE_FRAME_SPACE_UPDATE_INITIATOR_LOCAL_HOST, + + /** Initiated by local controller */ + + BT_CONN_LE_FRAME_SPACE_UPDATE_INITIATOR_LOCAL_CONTROLLER = + BT_HCI_LE_FRAME_SPACE_UPDATE_INITIATOR_LOCAL_CONTROLLER, + + /** Initiated by peer */ + + BT_CONN_LE_FRAME_SPACE_UPDATE_INITIATOR_PEER = + BT_HCI_LE_FRAME_SPACE_UPDATE_INITIATOR_PEER +}; + +/** Frame space update params */ + +struct bt_conn_le_frame_space_update_param +{ + /** Phy mask of the PHYs to be updated. Refer to + * BT_HCI_LE_FRAME_SPACE_UPDATE_PHY_* for values. + */ + + uint8_t phys; + /** Spacing types mask of the spacing types to be updated. + * Refer to BT_HCI_LE_FRAME_SPACE_UPDATE_SPACING_TYPE_* and + * BT_CONN_LE_FRAME_SPACE_TYPES_MASK_* for values. + */ + + uint16_t spacing_types; + /** Minimum frame space in microseconds. + * Bluetooth Core Specification, Vol 4, Part E, Section 7.8.151 allows for + * a range of frame space from 0 to 10000 microseconds. The actual + * supported frame space values will be dependent on the controller's + * capabilities. + */ + + uint16_t frame_space_min; + /** Maximum frame space in microseconds. + * Bluetooth Core Specification, Vol 4, Part E, Section 7.8.151 allows for + * a range of frame space from 0 to 10000 microseconds. The actual + * supported frame space values will be dependent on the controller's + * capabilities. + */ + + uint16_t frame_space_max; +}; + +/** Frame space updated callback params */ + +struct bt_conn_le_frame_space_updated +{ + /** HCI Status from LE Frame Space Update Complete event. + * The remaining parameters will be invalid if status is not + * @ref BT_HCI_ERR_SUCCESS. + */ + + uint8_t status; + + /** Initiator of the frame space update. */ + + enum bt_conn_le_frame_space_update_initiator initiator; + + /** Updated frame space in microseconds. */ + + uint16_t frame_space; + /** Phy mask of the PHYs updated. Refer to + * BT_HCI_LE_FRAME_SPACE_UPDATE_PHY_* for values. + */ + + uint8_t phys; + /** Spacing types mask of the spacing types updated. + * Refer to BT_HCI_LE_FRAME_SPACE_UPDATE_SPACING_TYPE_* and + * BT_CONN_LE_FRAME_SPACE_TYPES_MASK_* for values. + */ + + uint16_t spacing_types; +}; + +/** Connection Type */ + +enum __packed bt_conn_type +{ + /** LE Connection Type */ + + BT_CONN_TYPE_LE = BIT(0), + + /** BR/EDR Connection Type */ + + BT_CONN_TYPE_BR = BIT(1), + + /** SCO Connection Type */ + + BT_CONN_TYPE_SCO = BIT(2), + + /** ISO Connection Type */ + + BT_CONN_TYPE_ISO = BIT(3), + + /** All Connection Type */ + + BT_CONN_TYPE_ALL = BT_CONN_TYPE_LE | + BT_CONN_TYPE_BR | + BT_CONN_TYPE_SCO | + BT_CONN_TYPE_ISO, +}; + +/** Supported AA-Only RTT precision. */ + +enum bt_conn_le_cs_capability_rtt_aa_only +{ + /** AA-Only RTT variant is not supported. */ + + BT_CONN_LE_CS_RTT_AA_ONLY_NOT_SUPP = 0, + + /** 10ns time-of-flight accuracy. */ + + BT_CONN_LE_CS_RTT_AA_ONLY_10NS, + + /** 150ns time-of-flight accuracy. */ + + BT_CONN_LE_CS_RTT_AA_ONLY_150NS, +}; + +/** Supported Sounding Sequence RTT precision. */ + +enum bt_conn_le_cs_capability_rtt_sounding +{ + /** Sounding Sequence RTT variant is not supported. */ + + BT_CONN_LE_CS_RTT_SOUNDING_NOT_SUPP = 0, + + /** 10ns time-of-flight accuracy. */ + + BT_CONN_LE_CS_RTT_SOUNDING_10NS, + + /** 150ns time-of-flight accuracy. */ + + BT_CONN_LE_CS_RTT_SOUNDING_150NS, +}; + +/** Supported Random Payload RTT precision. */ + +enum bt_conn_le_cs_capability_rtt_random_payload +{ + /** Random Payload RTT variant is not supported. */ + + BT_CONN_LE_CS_RTT_RANDOM_PAYLOAD_NOT_SUPP = 0, + + /** 10ns time-of-flight accuracy. */ + + BT_CONN_LE_CS_RTT_RANDOM_PAYLOAD_10NS, + + /** 150ns time-of-flight accuracy. */ + + BT_CONN_LE_CS_RTT_RANDOM_PAYLOAD_150NS, +}; + +/** Remote channel sounding capabilities for LE connections supporting CS */ + +struct bt_conn_le_cs_capabilities +{ + /** Number of CS configurations */ + + uint8_t num_config_supported; + /** Maximum number of consecutive CS procedures. + * + * When set to zero, indicates support for both fixed and indefinite + * numbers of CS procedures before termination. + */ + + uint16_t max_consecutive_procedures_supported; + + /** Number of antennas. */ + + uint8_t num_antennas_supported; + + /** Maximum number of antenna paths. */ + + uint8_t max_antenna_paths_supported; + + /** Initiator role. */ + + bool initiator_supported; + + /** Reflector role. */ + + bool reflector_supported; + + /** Mode-3 */ + + bool mode_3_supported; + + /** RTT AA-Only */ + + enum bt_conn_le_cs_capability_rtt_aa_only rtt_aa_only_precision; + + /** RTT Sounding */ + + enum bt_conn_le_cs_capability_rtt_sounding rtt_sounding_precision; + + /** RTT Random Payload */ + + enum bt_conn_le_cs_capability_rtt_random_payload + rtt_random_payload_precision; + /** Number of CS steps needed to achieve the + * accuracy requirements for RTT AA Only. + * + * Set to 0 if RTT AA Only isn't supported. + */ + + uint8_t rtt_aa_only_n; + /** Number of CS steps needed to achieve the + * accuracy requirements for RTT Sounding. + * + * Set to 0 if RTT Sounding isn't supported + */ + + uint8_t rtt_sounding_n; + /** Number of CS steps needed to achieve the + * accuracy requirements for RTT Random Payload. + * + * Set to 0 if RTT Random Payload isn't supported. + */ + + uint8_t rtt_random_payload_n; + /** Phase-based normalized attack detector metric when a CS_SYNC with + * sounding sequence is received. + */ + + bool phase_based_nadm_sounding_supported; + /** Phase-based normalized attack detector metric when a CS_SYNC with + * random sequence is received. + */ + + bool phase_based_nadm_random_supported; + + /** CS_SYNC LE 2M PHY. */ + + bool cs_sync_2m_phy_supported; + + /** CS_SYNC LE 2M 2BT PHY. */ + + bool cs_sync_2m_2bt_phy_supported; + + /** Subfeature: CS with no Frequency Actuation Error. */ + + bool cs_without_fae_supported; + + /** Subfeature: Channel Selection Algorithm #3c */ + + bool chsel_alg_3c_supported; + + /** Subfeature: Phase-based Ranging from RTT sounding sequence. */ + + bool pbr_from_rtt_sounding_seq_supported; + + /** Subfeature: IPT in the CS reflector */ + + bool cs_ipt_reflector; + /** Optional T_IP1 time durations during CS steps. + * + * - Bit 0: 10 us + * - Bit 1: 20 us + * - Bit 2: 30 us + * - Bit 3: 40 us + * - Bit 4: 50 us + * - Bit 5: 60 us + * - Bit 6: 80 us + */ + + uint16_t t_ip1_times_supported; + /** Optional T_IP2 time durations during CS steps. + * + * - Bit 0: 10 us + * - Bit 1: 20 us + * - Bit 2: 30 us + * - Bit 3: 40 us + * - Bit 4: 50 us + * - Bit 5: 60 us + * - Bit 6: 80 us + */ + + uint16_t t_ip2_times_supported; + /** Optional T_FCS time durations during CS steps. + * + * - Bit 0: 15 us + * - Bit 1: 20 us + * - Bit 2: 30 us + * - Bit 3: 40 us + * - Bit 4: 50 us + * - Bit 5: 60 us + * - Bit 6: 80 us + * - Bit 7: 100 us + * - Bit 8: 120 us + */ + + uint16_t t_fcs_times_supported; + /** Optional T_PM time durations during CS steps. + * + * - Bit 0: 10 us + * - Bit 1: 20 us + */ + + uint16_t t_pm_times_supported; + + /** Time in microseconds for the antenna switch period of the CS tones. */ + + uint8_t t_sw_time; + /** Supported SNR levels used in RTT packets. + * + * - Bit 0: 18dB + * - Bit 1: 21dB + * - Bit 2: 24dB + * - Bit 3: 27dB + * - Bit 4: 30dB + */ + + uint8_t tx_snr_capability; + /** Supported T_IP2_IPT time durations during CS steps. + * + * - Bit 0: 10 us + * - Bit 1: 20 us + * - Bit 2: 30 us + * - Bit 3: 40 us + * - Bit 4: 50 us + * - Bit 5: 60 us + * - Bit 6: 80 us + */ + + uint16_t t_ip2_ipt_times_supported; + /** Supported time in microseconds for the antenna switch period of the + * CS tones during IPT. + * + * 0x00, 0x01, 0x02, 0x04, or 0x0A - Time in microseconds for the antenna + * switch period of the CS tones + */ + + uint8_t t_sw_ipt_time_supported; +}; + +/** Remote FAE Table for LE connections supporting CS */ + +struct bt_conn_le_cs_fae_table +{ + int8_t *remote_fae_table; +}; + +/** @brief Extract main mode part from @ref bt_conn_le_cs_mode + * + * @private + * + * @param x @ref bt_conn_le_cs_mode value + * @retval 1 Matches @ref BT_HCI_OP_LE_CS_MAIN_MODE_1 (0x01) + * @retval 2 Matches @ref BT_HCI_OP_LE_CS_MAIN_MODE_2 (0x02) + * @retval 3 Matches @ref BT_HCI_OP_LE_CS_MAIN_MODE_3 (0x03) + * + * @note Returned values match the HCI main mode values. + */ +#define BT_CONN_LE_CS_MODE_MAIN_MODE_PART(x) ((x) & 0x3) + +/** @brief Extract sub-mode part from @ref bt_conn_le_cs_mode + * + * @private + * + * @param x @ref bt_conn_le_cs_mode value + * @retval 0 Internal encoding for @ref BT_HCI_OP_LE_CS_SUB_MODE_UNUSED + * (0xFF) + * @retval 1 Matches @ref BT_HCI_OP_LE_CS_SUB_MODE_1 (0x01) + * @retval 2 Matches @ref BT_HCI_OP_LE_CS_SUB_MODE_2 (0x02) + * @retval 3 Matches @ref BT_HCI_OP_LE_CS_SUB_MODE_3 (0x03) + * + * @note The value 0 encodes HCI 0xFF. This allows @ref bt_conn_le_cs_mode to + * fit in one byte. To obtain the HCI sub-mode value, use `(sub_mode == 0 ? + * 0xFF : sub_mode)`, where `sub_mode` is the value returned by this macro. + */ +#define BT_CONN_LE_CS_MODE_SUB_MODE_PART(x) (((x) >> 4) & 0x3) + +/** @brief Channel sounding mode (main and sub-mode) + * + * Represents the combination of Channel Sounding (CS) main mode and + * sub-mode. + * + * @note The underlying numeric values are an internal encoding and are + * not stable API. Do not assume a direct concatenation of HCI values when + * inspecting the raw enum value. + * + * @sa BT_CONN_LE_CS_MODE_MAIN_MODE_PART + * @sa BT_CONN_LE_CS_MODE_SUB_MODE_PART + */ + +enum bt_conn_le_cs_mode +{ + /** Main mode 1 (RTT), sub-mode: unused */ + + BT_CONN_LE_CS_MAIN_MODE_1_NO_SUB_MODE = BT_HCI_OP_LE_CS_MAIN_MODE_1, + + /** Main mode 2 (PBR), sub-mode: unused */ + + BT_CONN_LE_CS_MAIN_MODE_2_NO_SUB_MODE = BT_HCI_OP_LE_CS_MAIN_MODE_2, + + /** Main mode 3 (RTT and PBR), sub-mode: unused */ + + BT_CONN_LE_CS_MAIN_MODE_3_NO_SUB_MODE = BT_HCI_OP_LE_CS_MAIN_MODE_3, + + /** Main mode 2 (PBR), sub-mode 1 (RTT) */ + + BT_CONN_LE_CS_MAIN_MODE_2_SUB_MODE_1 = + BT_HCI_OP_LE_CS_MAIN_MODE_2 | (BT_HCI_OP_LE_CS_SUB_MODE_1 << 4), + + /** Main mode 2 (PBR), sub-mode 3 (RTT and PBR) */ + + BT_CONN_LE_CS_MAIN_MODE_2_SUB_MODE_3 = + BT_HCI_OP_LE_CS_MAIN_MODE_2 | (BT_HCI_OP_LE_CS_SUB_MODE_3 << 4), + + /** Main mode 3 (RTT and PBR), sub-mode 2 (PBR) */ + + BT_CONN_LE_CS_MAIN_MODE_3_SUB_MODE_2 = + BT_HCI_OP_LE_CS_MAIN_MODE_3 | (BT_HCI_OP_LE_CS_SUB_MODE_2 << 4), +}; + +/** Channel sounding role */ + +enum bt_conn_le_cs_role +{ + /** CS initiator role */ + + BT_CONN_LE_CS_ROLE_INITIATOR, + + /** CS reflector role */ + + BT_CONN_LE_CS_ROLE_REFLECTOR, +}; + +/** Channel sounding RTT type */ + +enum bt_conn_le_cs_rtt_type +{ + /** RTT AA only */ + + BT_CONN_LE_CS_RTT_TYPE_AA_ONLY = BT_HCI_OP_LE_CS_RTT_TYPE_AA_ONLY, + + /** RTT with 32-bit sounding sequence */ + + BT_CONN_LE_CS_RTT_TYPE_32_BIT_SOUNDING = + BT_HCI_OP_LE_CS_RTT_TYPE_32BIT_SOUND, + + /** RTT with 96-bit sounding sequence */ + + BT_CONN_LE_CS_RTT_TYPE_96_BIT_SOUNDING = + BT_HCI_OP_LE_CS_RTT_TYPE_96BIT_SOUND, + + /** RTT with 32-bit random sequence */ + + BT_CONN_LE_CS_RTT_TYPE_32_BIT_RANDOM = BT_HCI_OP_LE_CS_RTT_TYPE_32BIT_RAND, + + /** RTT with 64-bit random sequence */ + + BT_CONN_LE_CS_RTT_TYPE_64_BIT_RANDOM = BT_HCI_OP_LE_CS_RTT_TYPE_64BIT_RAND, + + /** RTT with 96-bit random sequence */ + + BT_CONN_LE_CS_RTT_TYPE_96_BIT_RANDOM = BT_HCI_OP_LE_CS_RTT_TYPE_96BIT_RAND, + + /** RTT with 128-bit random sequence */ + + BT_CONN_LE_CS_RTT_TYPE_128_BIT_RANDOM = + BT_HCI_OP_LE_CS_RTT_TYPE_128BIT_RAND, +}; + +/** Channel sounding PHY used for CS sync */ + +enum bt_conn_le_cs_sync_phy +{ + /** LE 1M PHY */ + + BT_CONN_LE_CS_SYNC_1M_PHY = BT_HCI_OP_LE_CS_CS_SYNC_1M, + + /** LE 2M PHY */ + + BT_CONN_LE_CS_SYNC_2M_PHY = BT_HCI_OP_LE_CS_CS_SYNC_2M, + + /** LE 2M 2BT PHY */ + + BT_CONN_LE_CS_SYNC_2M_2BT_PHY = BT_HCI_OP_LE_CS_CS_SYNC_2M_2BT, +}; + +/** Channel sounding channel selection type */ + +enum bt_conn_le_cs_chsel_type +{ + /** Use Channel Selection Algorithm #3b for non-mode-0 CS steps */ + + BT_CONN_LE_CS_CHSEL_TYPE_3B = BT_HCI_OP_LE_CS_TEST_CHSEL_TYPE_3B, + + /** Use Channel Selection Algorithm #3c for non-mode-0 CS steps */ + + BT_CONN_LE_CS_CHSEL_TYPE_3C = BT_HCI_OP_LE_CS_TEST_CHSEL_TYPE_3C, +}; + +/** Channel sounding channel sequence shape */ + +enum bt_conn_le_cs_ch3c_shape +{ + /** Use Hat shape for user-specified channel sequence */ + + BT_CONN_LE_CS_CH3C_SHAPE_HAT = BT_HCI_OP_LE_CS_TEST_CH3C_SHAPE_HAT, + + /** Use X shape for user-specified channel sequence */ + + BT_CONN_LE_CS_CH3C_SHAPE_X = BT_HCI_OP_LE_CS_TEST_CH3C_SHAPE_X, +}; + +/** Channel sounding configuration */ + +struct bt_conn_le_cs_config +{ + /** CS configuration ID */ + + uint8_t id; + + /** CS main and sub mode */ + + enum bt_conn_le_cs_mode mode; + + /** Minimum number of CS main mode steps to be executed before a submode + * step is executed + */ + + uint8_t min_main_mode_steps; + + /** Maximum number of CS main mode steps to be executed before a submode + * step is executed + */ + + uint8_t max_main_mode_steps; + /** Number of main mode steps taken from the end of the last CS subevent to + * be repeated at the beginning of the current CS subevent directly after + * the last mode-0 step of that event + */ + + uint8_t main_mode_repetition; + + /** Number of CS mode-0 steps to be included at the beginning of each CS + * subevent + */ + + uint8_t mode_0_steps; + + /** CS role */ + + enum bt_conn_le_cs_role role; + + /** RTT type */ + + enum bt_conn_le_cs_rtt_type rtt_type; + + /** CS Sync PHY */ + + enum bt_conn_le_cs_sync_phy cs_sync_phy; + /** The number of times the Channel_Map field will be cycled through for + * non-mode-0 steps within a CS procedure + */ + + uint8_t channel_map_repetition; + + /** Channel selection type */ + + enum bt_conn_le_cs_chsel_type channel_selection_type; + + /** User-specified channel sequence shape */ + + enum bt_conn_le_cs_ch3c_shape ch3c_shape; + + /** Number of channels skipped in each rising and falling sequence */ + + uint8_t ch3c_jump; + /** CS enhancements 1 + * Bit 0 - IPT is enabled in the CS reflector. All other bits are reserved + * and shall be set to 0. + */ + + uint8_t cs_enhancements_1; + + /** Interlude time in microseconds between the RTT packets */ + + uint8_t t_ip1_time_us; + + /** Interlude time in microseconds between the CS tones */ + + uint8_t t_ip2_time_us; + + /** Time in microseconds for frequency changes */ + + uint8_t t_fcs_time_us; + + /** Time in microseconds for the phase measurement period of the CS tones */ + + uint8_t t_pm_time_us; + /** Channel map used for CS procedure + * Channels n = 0, 1, 23, 24, 25, 77, and 78 are not allowed and shall be + * set to zero. Channel 79 is reserved for future use and shall be set to + * zero. At least 15 channels shall be enabled. + */ + + uint8_t channel_map[10]; +}; + +/** Procedure done status */ + +enum bt_conn_le_cs_procedure_done_status +{ + BT_CONN_LE_CS_PROCEDURE_COMPLETE = + BT_HCI_LE_CS_PROCEDURE_DONE_STATUS_COMPLETE, + BT_CONN_LE_CS_PROCEDURE_INCOMPLETE = + BT_HCI_LE_CS_PROCEDURE_DONE_STATUS_PARTIAL, + BT_CONN_LE_CS_PROCEDURE_ABORTED = + BT_HCI_LE_CS_PROCEDURE_DONE_STATUS_ABORTED, +}; + +/** Subevent done status */ + +enum bt_conn_le_cs_subevent_done_status +{ + BT_CONN_LE_CS_SUBEVENT_COMPLETE = + BT_HCI_LE_CS_SUBEVENT_DONE_STATUS_COMPLETE, + BT_CONN_LE_CS_SUBEVENT_ABORTED = BT_HCI_LE_CS_SUBEVENT_DONE_STATUS_ABORTED, +}; + +/** Procedure abort reason */ + +enum bt_conn_le_cs_procedure_abort_reason +{ + BT_CONN_LE_CS_PROCEDURE_NOT_ABORTED = + BT_HCI_LE_CS_PROCEDURE_ABORT_REASON_NO_ABORT, + BT_CONN_LE_CS_PROCEDURE_ABORT_REQUESTED = + BT_HCI_LE_CS_PROCEDURE_ABORT_REASON_LOCAL_HOST_OR_REMOTE_REQUEST, + BT_CONN_LE_CS_PROCEDURE_ABORT_TOO_FEW_CHANNELS = + BT_HCI_LE_CS_PROCEDURE_ABORT_REASON_TOO_FEW_CHANNELS, + BT_CONN_LE_CS_PROCEDURE_ABORT_CHMAP_INSTANT_PASSED = + BT_HCI_LE_CS_PROCEDURE_ABORT_REASON_CHMAP_INSTANT_PASSED, + BT_CONN_LE_CS_PROCEDURE_ABORT_UNSPECIFIED = + BT_HCI_LE_CS_PROCEDURE_ABORT_REASON_UNSPECIFIED, +}; + +/** Subevent abort reason */ + +enum bt_conn_le_cs_subevent_abort_reason +{ + BT_CONN_LE_CS_SUBEVENT_NOT_ABORTED = + BT_HCI_LE_CS_SUBEVENT_ABORT_REASON_NO_ABORT, + BT_CONN_LE_CS_SUBEVENT_ABORT_REQUESTED = + BT_HCI_LE_CS_SUBEVENT_ABORT_REASON_LOCAL_HOST_OR_REMOTE_REQUEST, + BT_CONN_LE_CS_SUBEVENT_ABORT_NO_CS_SYNC = + BT_HCI_LE_CS_SUBEVENT_ABORT_REASON_NO_CS_SYNC_RECEIVED, + BT_CONN_LE_CS_SUBEVENT_ABORT_SCHED_CONFLICT = + BT_HCI_LE_CS_SUBEVENT_ABORT_REASON_SCHED_CONFLICT, + BT_CONN_LE_CS_SUBEVENT_ABORT_UNSPECIFIED = + BT_HCI_LE_CS_SUBEVENT_ABORT_REASON_UNSPECIFIED, +}; + +/** Subevent data for LE connections supporting CS */ + +struct bt_conn_le_cs_subevent_result +{ + struct + { + /** CS configuration identifier. + * + * Range: 0 to 3 + * + * If these results were generated by a CS Test, this value will be + * set to 0 and has no meaning. + */ + + uint8_t config_id; + /** Starting ACL connection event counter. + * + * If these results were generated by a CS Test, this value will be + * set to 0 and has no meaning. + */ + + uint16_t start_acl_conn_event; + /** CS procedure count associated with these results. + * + * This is the CS procedure count since the completion of the Channel + * Sounding Security Start procedure. + */ + + uint16_t procedure_counter; + /** Frequency compensation value in units of 0.01 ppm. + * + * This is a 15-bit signed integer in the range [-100, 100] ppm. + * + * A value of @ref + * BT_HCI_LE_CS_SUBEVENT_RESULT_FREQ_COMPENSATION_NOT_AVAILABLE + * indicates that the role is not the initiator, or that the frequency + * compensation value is unavailable. + */ + + uint16_t frequency_compensation; + /** Reference power level in dBm. + * + * Range: -127 to 20 + * + * A value of @ref BT_HCI_LE_CS_REF_POWER_LEVEL_UNAVAILABLE indicates + * that the reference power level was not available during a subevent. + */ + + int8_t reference_power_level; + + /** Procedure status. */ + + enum bt_conn_le_cs_procedure_done_status procedure_done_status; + /** Subevent status + * + * For aborted subevents, this will be set to @ref + * BT_CONN_LE_CS_SUBEVENT_ABORTED and abort_step will contain the step + * number on which the subevent was aborted. Consider the following + * example: + * + * subevent_done_status = @ref BT_CONN_LE_CS_SUBEVENT_ABORTED + * num_steps_reported = 160 abort_step = 100 + * + * this would mean that steps from 0 to 99 are complete and steps from + * 100 to 159 are aborted. + */ + + enum bt_conn_le_cs_subevent_done_status subevent_done_status; + /** Abort reason. + * + * If the procedure status is + * @ref BT_CONN_LE_CS_PROCEDURE_ABORTED, this field will + * specify the reason for the abortion. + */ + + enum bt_conn_le_cs_procedure_abort_reason procedure_abort_reason; + /** Abort reason. + * + * If the subevent status is + * @ref BT_CONN_LE_CS_SUBEVENT_ABORTED, this field will + * specify the reason for the abortion. + */ + + enum bt_conn_le_cs_subevent_abort_reason subevent_abort_reason; + /** Number of antenna paths used during the phase measurement stage. + */ + + uint8_t num_antenna_paths; + /** Number of CS steps in the subevent. + */ + + uint8_t num_steps_reported; + /** Step number, on which the subevent was aborted + * if subevent_done_status is @ref BT_CONN_LE_CS_SUBEVENT_COMPLETE + * then abort_step will be unused and set to 255 + */ + + uint8_t abort_step; + } header; + /** Pointer to buffer containing step data. NULL if num_steps_reported is + * 0. + */ + + struct net_buf_simple *step_data_buf; +}; + +/** @brief Increment a connection's reference count. + * + * Increment the reference count of a connection object. + * + * @note Will return NULL if the reference count is zero. + * + * @param conn Connection object. + * + * @return Connection object with incremented reference count, or NULL if + * the reference count is zero. + */ + +struct bt_conn *bt_conn_ref(struct bt_conn *conn); + +/** @brief Decrement a connection's reference count. + * + * Decrement the reference count of a connection object. Unless the pointer + * variable is immediately going out of scope, it's recommended to use @ref + * bt_conn_drop instead, which will also set the pointer to NULL to prevent + * accidental reuse. + * + * @param conn Connection object. + */ + +void bt_conn_unref(struct bt_conn *conn); + +/** @brief Take ownership of a connection pointer, setting the original to + * NULL. + * + * This performs an atomic exchange on @p orig, setting it to NULL and + * returning the previous value. The reference count is not modified; the + * reference held by @p orig is transferred to the caller, who must + * eventually release it with bt_conn_unref(). + * + * @param orig Pointer to the connection pointer to transfer (must not be + * NULL). On return, @p *orig is set to NULL. + * + * @return The connection originally pointed to by @p orig, which may be + * NULL. + */ + +static inline struct bt_conn *__must_check +bt_conn_take(struct bt_conn **orig) +{ + return (struct bt_conn *)atomic_ptr_xchg((void **)orig, NULL); +} + +/** @brief Drop a connection reference and clear the pointer. + * + * This performs an atomic exchange on @p orig, setting it to NULL and + * unreferencing the previous value if it was not NULL. + * + * @param orig Pointer to the connection pointer to drop (must not be NULL). + * On return, @p *orig is set to NULL. + */ + +void bt_conn_drop(struct bt_conn **orig); + +/** @brief Iterate through all bt_conn objects. + * + * Iterates through all bt_conn objects that are alive in the Host allocator. + * + * To find established connections, combine this with @ref bt_conn_get_info. + * Check that @ref bt_conn_info.state is @ref BT_CONN_STATE_CONNECTED. + * + * Thread safety: This API is thread safe, but it does not guarantee a + * sequentially-consistent view for objects allocated during the current + * invocation of this API. E.g. If preempted while allocations A then B then + * C happen then results may include A and C but miss B. + * + * @param type Connection Type + * @param func Function to call for each connection. + * @param data Data to pass to the callback function. + */ + +void bt_conn_foreach( + enum bt_conn_type type, void (*func)(struct bt_conn *conn, void *data), + void *data); + +/** @brief Look up an existing connection by address. + * + * Look up an existing connection based on the remote address. + * + * The caller gets a new reference to the connection object which must be + * released with bt_conn_unref() once done using the object. + * + * @param id Local identity (in most cases BT_ID_DEFAULT). + * @param peer Remote address. + * + * @return Connection object or NULL if not found. + */ + +struct bt_conn *bt_conn_lookup_addr_le(uint8_t id, const bt_addr_le_t *peer); + +/** @brief Get destination (peer) address of a connection. + * + * @param conn Connection object. + * + * @return Destination address if @p conn is a valid @ref BT_CONN_TYPE_LE + * connection + */ + +const bt_addr_le_t *bt_conn_get_dst(const struct bt_conn *conn); + +/** @brief Get array index of a connection + * + * This function is used to map bt_conn to index of an array of connections. + * The array has CONFIG_BT_MAX_CONN elements. + * + * @param conn Connection object. + * + * @return Index of the connection object. + * The range of the returned value is 0..CONFIG_BT_MAX_CONN-1 + */ + +uint8_t bt_conn_index(const struct bt_conn *conn); + +/** LE Connection Info Structure */ + +struct bt_conn_le_info +{ + /** Source (Local) Identity Address */ + + const bt_addr_le_t *src; + /** Destination (Remote) Identity Address or remote Resolvable Private + * Address (RPA) before identity has been resolved. + */ + + const bt_addr_le_t *dst; + + /** Local device address used during connection setup. */ + + const bt_addr_le_t *local; + + /** Remote device address used during connection setup. */ + + const bt_addr_le_t *remote; + + /** Connection interval in microseconds */ + + uint32_t interval_us; +#if !defined(CONFIG_BT_SHORTER_CONNECTION_INTERVALS) || defined(__DOXYGEN__) + union + { + /** @brief Connection interval in units of 1.25 ms + * + * @deprecated Use @ref bt_conn_le_info.interval_us instead + */ + + __deprecated uint16_t interval; + + /** @cond INTERNAL_HIDDEN */ + + /** Workaround for setting deprecated @ref bt_conn_le_info.interval */ + + uint16_t _interval; + + /** @endcond */ + }; +#endif /* !CONFIG_BT_SHORTER_CONNECTION_INTERVALS */ + + uint16_t latency; /**< Connection peripheral latency */ + uint16_t timeout; /**< Connection supervision timeout */ + +#if defined(CONFIG_BT_USER_PHY_UPDATE) || defined(__DOXYGEN__) + /** Connection PHY info */ + + const struct bt_conn_le_phy_info *phy; +#endif /* defined(CONFIG_BT_USER_PHY_UPDATE) */ + +#if defined(CONFIG_BT_USER_DATA_LEN_UPDATE) || defined(__DOXYGEN__) + /** Connection maximum single fragment parameters */ + + const struct bt_conn_le_data_len_info *data_len; +#endif /* defined(CONFIG_BT_USER_DATA_LEN_UPDATE) */ + +#if defined(CONFIG_BT_SUBRATING) || defined(__DOXYGEN__) + /** Connection subrating parameters */ + + const struct bt_conn_le_subrating_info *subrate; +#endif /* defined(CONFIG_BT_SUBRATING) */ +}; + +/** @brief Convert connection interval to milliseconds + * + * Multiply by 1.25 to get milliseconds. + * + * Note that this may be inaccurate, as something like 7.5 ms cannot be + * accurately presented with integers. + */ +#define BT_CONN_INTERVAL_TO_MS(interval) ((interval) * 5U / 4U) + +/** @brief Convert connection interval to microseconds + * + * Multiply by 1250 to get microseconds. + */ +#define BT_CONN_INTERVAL_TO_US(interval) ((interval) * 1250U) + +/** @brief Convert shorter connection interval to microseconds + * + * Multiply by 125 to get microseconds. + */ +#define BT_CONN_SCI_INTERVAL_TO_US(_interval) ((_interval) * BT_HCI_LE_SCI_INTERVAL_UNIT_US) + +/** BR/EDR Connection Info Structure */ + +struct bt_conn_br_info +{ + const bt_addr_t *dst; /**< Destination (Remote) BR/EDR address */ +}; + +/** SCO Connection Info Structure */ + +struct bt_conn_sco_info +{ + uint8_t link_type; /**< SCO link type */ + uint8_t air_mode; /**< SCO air mode (codec type) */ +}; + +enum +{ + BT_CONN_ROLE_CENTRAL = 0, + BT_CONN_ROLE_PERIPHERAL = 1, +}; + +enum bt_conn_state +{ + /** Channel disconnected */ + + BT_CONN_STATE_DISCONNECTED, + + /** Channel in connecting state */ + + BT_CONN_STATE_CONNECTING, + + /** Channel connected and ready for upper layer traffic on it */ + + BT_CONN_STATE_CONNECTED, + + /** Channel in disconnecting state */ + + BT_CONN_STATE_DISCONNECTING, +}; + +/** Security level. */ + +typedef enum __packed +{ + /** Level 0: Only for BR/EDR special cases, like SDP */ + + BT_SECURITY_L0, + + /** Level 1: No encryption and no authentication. */ + + BT_SECURITY_L1, + + /** Level 2: Encryption and no authentication (no MITM). */ + + BT_SECURITY_L2, + + /** Level 3: Encryption and authentication (MITM). */ + + BT_SECURITY_L3, + + /** Level 4: Authenticated Secure Connections and 128-bit key. */ + + BT_SECURITY_L4, + /** Bit to force new pairing procedure, bit-wise OR with requested security + * level. + */ + + BT_SECURITY_FORCE_PAIR = BIT(7), +} bt_security_t; + +/** Security Info Flags. */ + +enum bt_security_flag +{ + /** Paired with Secure Connections. */ + + BT_SECURITY_FLAG_SC = BIT(0), + + /** Paired with Out of Band method. */ + + BT_SECURITY_FLAG_OOB = BIT(1), +}; + +/** Security Info Structure. */ + +struct bt_security_info +{ + /** Security Level. */ + + bt_security_t level; + + /** Encryption Key Size. */ + + uint8_t enc_key_size; + + /** Flags. */ + + enum bt_security_flag flags; +}; + +/** Connection Info Structure */ + +struct bt_conn_info +{ + /** Connection Type. */ + + enum bt_conn_type type; + + /** Connection Role. */ + + uint8_t role; + + /** Which local identity the connection was created with */ + + uint8_t id; + + /** Connection Type specific Info. */ + + union + { + /** LE Connection specific Info. */ + + struct bt_conn_le_info le; + + /** BR/EDR Connection specific Info. */ + + struct bt_conn_br_info br; + + /** SCO Connection specific Info. */ + + struct bt_conn_sco_info sco; + }; + + /** Connection state. */ + + enum bt_conn_state state; + + /** Security specific info. */ + + struct bt_security_info security; +}; + +/** LE Connection Remote Info Structure */ + +struct bt_conn_le_remote_info +{ + /** Remote LE feature set (bitmask). */ + + const uint8_t *features; +}; + +/** BR/EDR Connection Remote Info structure */ + +struct bt_conn_br_remote_info +{ + /** Remote feature set (pages of bitmasks). */ + + const uint8_t *features; + + /** Number of pages in the remote feature set. */ + + uint8_t num_pages; +}; + +/** @brief Connection Remote Info Structure + * + * @note The version, manufacturer and subversion fields will only contain + * valid data if @kconfig{CONFIG_BT_REMOTE_VERSION} is enabled. + */ + +struct bt_conn_remote_info +{ + /** Connection Type */ + + uint8_t type; + + /** Remote Link Layer version */ + + uint8_t version; + + /** Remote manufacturer identifier */ + + uint16_t manufacturer; + + /** Per-manufacturer unique revision */ + + uint16_t subversion; + + union + { + /** LE connection remote info */ + + struct bt_conn_le_remote_info le; + + /** BR/EDR connection remote info */ + + struct bt_conn_br_remote_info br; + }; +}; + +enum bt_conn_le_tx_power_phy +{ + /** Convenience macro for when no PHY is set. */ + + BT_CONN_LE_TX_POWER_PHY_NONE, + + /** LE 1M PHY */ + + BT_CONN_LE_TX_POWER_PHY_1M, + + /** LE 2M PHY */ + + BT_CONN_LE_TX_POWER_PHY_2M, + + /** LE Coded PHY using S=8 coding. */ + + BT_CONN_LE_TX_POWER_PHY_CODED_S8, + + /** LE Coded PHY using S=2 coding. */ + + BT_CONN_LE_TX_POWER_PHY_CODED_S2, +}; + +/** LE Transmit Power Level Structure */ + +struct bt_conn_le_tx_power +{ + /** Input: 1M, 2M, Coded S2 or Coded S8 */ + + uint8_t phy; + + /** @brief Output: current transmit power level in dBm. + * + * Range depends on which HCI command is used: + * - -30 to +20 when @p phy is 0 (HCI_Read_Transmit_Power_Level) + * - -127 to +20 when @p phy is non-zero + * (HCI_LE_Enhanced_Read_Transmit_Power_Level) + */ + + int8_t current_level; + + /** @brief Output: maximum transmit power level in dBm. + * + * Range depends on which HCI command is used: + * - -30 to +20 when @p phy is 0 (HCI_Read_Transmit_Power_Level) + * - -127 to +20 when @p phy is non-zero + * (HCI_LE_Enhanced_Read_Transmit_Power_Level) + */ + + int8_t max_level; +}; + +/** LE Transmit Power Reporting Structure */ + +struct bt_conn_le_tx_power_report +{ + /** Reason for Transmit power reporting, as documented in Core Spec. + * Version 5.4 Vol. 4, Part E, 7.7.65.33. + */ + + uint8_t reason; + + /** Phy of Transmit power reporting. */ + + enum bt_conn_le_tx_power_phy phy; + + /** Transmit power level + * - 0xXX - Transmit power level + * + Range: -127 to 20 + * + Units: dBm + * + * - 0x7E - Remote device is not managing power levels on this PHY. + * - 0x7F - Transmit power level is not available + */ + + int8_t tx_power_level; + + /** Bit 0: Transmit power level is at minimum level. Bit 1: Transmit power + * level is at maximum level. + */ + + uint8_t tx_power_level_flag; + + /** Change in transmit power level + * - 0xXX - Change in transmit power level (positive indicates increased + * power, negative indicates decreased power, zero indicates unchanged) + * Units: dB + * - 0x7F - Change is not available or is out of range. + */ + + int8_t delta; +}; + +/** @brief Path Loss zone that has been entered. + * + * The path loss zone that has been entered in the most recent LE Path Loss + * Monitoring Threshold Change event as documented in Core Spec. Version 5.4 + * Vol.4, Part E, 7.7.65.32. + * + * @note BT_CONN_LE_PATH_LOSS_ZONE_UNAVAILABLE has been added to notify when + * path loss becomes unavailable. + */ + +enum bt_conn_le_path_loss_zone +{ + /** Low path loss zone entered. */ + + BT_CONN_LE_PATH_LOSS_ZONE_ENTERED_LOW, + + /** Middle path loss zone entered. */ + + BT_CONN_LE_PATH_LOSS_ZONE_ENTERED_MIDDLE, + + /** High path loss zone entered. */ + + BT_CONN_LE_PATH_LOSS_ZONE_ENTERED_HIGH, + + /** Path loss has become unavailable. */ + + BT_CONN_LE_PATH_LOSS_ZONE_UNAVAILABLE, +}; + +BUILD_ASSERT(BT_CONN_LE_PATH_LOSS_ZONE_ENTERED_LOW == + BT_HCI_LE_ZONE_ENTERED_LOW); +BUILD_ASSERT(BT_CONN_LE_PATH_LOSS_ZONE_ENTERED_MIDDLE == + BT_HCI_LE_ZONE_ENTERED_MIDDLE); +BUILD_ASSERT(BT_CONN_LE_PATH_LOSS_ZONE_ENTERED_HIGH == + BT_HCI_LE_ZONE_ENTERED_HIGH); + +/** @brief LE Path Loss Monitoring Threshold Change Report Structure. */ + +struct bt_conn_le_path_loss_threshold_report +{ + /** Path Loss zone as documented in Core Spec. Version 5.4 Vol.4, Part E, + * 7.7.65.32. + */ + + enum bt_conn_le_path_loss_zone zone; + + /** Current path loss (dB). */ + + uint8_t path_loss; +}; + +/** @brief LE Path Loss Monitoring Parameters Structure as defined in Core + * Spec. Version 5.4 Vol.4, Part E, 7.8.119 LE Set Path Loss Reporting + * Parameters command. + */ + +struct bt_conn_le_path_loss_reporting_param +{ + /** High threshold for the path loss (dB). */ + + uint8_t high_threshold; + + /** Hysteresis value for the high threshold (dB). */ + + uint8_t high_hysteresis; + + /** Low threshold for the path loss (dB). */ + + uint8_t low_threshold; + + /** Hysteresis value for the low threshold (dB). */ + + uint8_t low_hysteresis; + /** Minimum time in number of connection events to be observed once the + * path loss crosses the threshold before an event is generated. + */ + + uint16_t min_time_spent; +}; + +/** @brief Passkey Keypress Notification type + * + * The numeric values are the same as in the Core specification for Pairing + * Keypress Notification PDU. + */ + +enum bt_conn_auth_keypress +{ + BT_CONN_AUTH_KEYPRESS_ENTRY_STARTED = 0x00, + BT_CONN_AUTH_KEYPRESS_DIGIT_ENTERED = 0x01, + BT_CONN_AUTH_KEYPRESS_DIGIT_ERASED = 0x02, + BT_CONN_AUTH_KEYPRESS_CLEARED = 0x03, + BT_CONN_AUTH_KEYPRESS_ENTRY_COMPLETED = 0x04, +}; + +/** @brief Get connection info + * + * @param conn Connection object. + * @param info Connection info object. + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_conn_get_info(const struct bt_conn *conn, struct bt_conn_info *info); + +/** @cond INTERNAL_HIDDEN */ + +struct bt_conn_tmp_str +{ + /* BT_ADDR_LE_STR_LEN covers both BR/EDR and LE string lengths */ + + char str[BT_ADDR_LE_STR_LEN]; +}; + +struct bt_conn_tmp_str bt_conn_dst_tmp_str(const struct bt_conn *conn); + +/** @endcond */ + +/** + * @brief Get a string pointer to a connection destination (peer) address. + * @def bt_conn_dst_str() + * + * @param _conn Pointer to the connection object. + * + * @return A string pointer which is only valid until the end of the full + * expression. In practice this means that this is primarily useful as an + * input parameter to printk/printf or logging calls. + */ +#define bt_conn_dst_str(_conn) bt_conn_dst_tmp_str(_conn).str + +/** @brief Function to determine the type of a connection + * + * @param conn The connection object + * @param type The types to check against. It is possible to supply multiple + * types, e.g. BT_CONN_TYPE_LE | BT_CONN_TYPE_SCO. + * + * @retval true @p conn is of type @p type + * @retval false @p conn is either NULL or not of type @p type + */ + +bool bt_conn_is_type(const struct bt_conn *conn, enum bt_conn_type type); + +/** @brief Get connection info for the remote device. + * + * @param conn @ref BT_CONN_TYPE_LE or @ref BT_CONN_TYPE_BR + * connection object. + * @param remote_info Connection remote info object. + * + * @note In order to retrieve the remote version (version, manufacturer + * and subversion) @kconfig + * { CONFIG_BT_REMOTE_VERSION} must be enabled + * + * @note The remote information is exchanged directly after the connection + * has been established. The application can be notified about when the + * remote information is available through the remote_info_available + * callback. + * + * @return Zero on success or (negative) error code on failure. + * @return -EBUSY The remote information is not yet available. + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_LE or @ref + * BT_CONN_TYPE_BR + * connection. + */ + +int bt_conn_get_remote_info(const struct bt_conn *conn, + struct bt_conn_remote_info *remote_info); + +/** @brief Get connection transmit power level. + * + * @param conn @ref BT_CONN_TYPE_LE connection object. + * @param tx_power_level Transmit power level descriptor. + * + * @return Zero on success or (negative) error code on failure. + * @return -ENOBUFS HCI command buffer is not available. + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_LE connection. + */ + +int bt_conn_le_get_tx_power_level(struct bt_conn *conn, + struct bt_conn_le_tx_power + *tx_power_level); + +/** @brief Get local enhanced connection transmit power level. + * + * @param conn @ref BT_CONN_TYPE_LE connection object. + * @param tx_power Transmit power level descriptor. + * + * @return Zero on success or (negative) error code on failure. + * @retval -ENOBUFS HCI command buffer is not available. + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_LE connection. + */ + +int bt_conn_le_enhanced_get_tx_power_level(struct bt_conn *conn, + struct bt_conn_le_tx_power *tx_power); + +/** @brief Get remote (peer) transmit power level. + * + * @param conn @ref BT_CONN_TYPE_LE connection object. + * @param phy PHY information. + * + * @return Zero on success or (negative) error code on failure. + * @retval -ENOBUFS HCI command buffer is not available. + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_LE connection. + */ + +int bt_conn_le_get_remote_tx_power_level(struct bt_conn *conn, + enum bt_conn_le_tx_power_phy phy); + +/** @brief Enable transmit power reporting. + * + * @param conn @ref BT_CONN_TYPE_LE connection object. + * @param local_enable Enable/disable reporting for local. + * @param remote_enable Enable/disable reporting for remote. + * + * @return Zero on success or (negative) error code on failure. + * @retval -ENOBUFS HCI command buffer is not available. + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_LE connection. + */ + +int bt_conn_le_set_tx_power_report_enable( + struct bt_conn *conn, bool local_enable, bool remote_enable); + +/** @brief Set Path Loss Monitoring Parameters. + * + * Change the configuration for path loss threshold change events for a + * given conn handle. + * + * @note To use this API @kconfig{CONFIG_BT_PATH_LOSS_MONITORING} must be + * set. + * + * @param conn @ref BT_CONN_TYPE_LE connection object. + * @param param Path Loss Monitoring parameters + * + * @return Zero on success or (negative) error code on failure. + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_LE connection. + */ + +int bt_conn_le_set_path_loss_mon_param(struct bt_conn *conn, + const struct bt_conn_le_path_loss_reporting_param *param); + +/** @brief Enable or Disable Path Loss Monitoring. + * + * Enable or disable Path Loss Monitoring, which will decide whether Path + * Loss Threshold events are sent from the controller to the host. + * + * @note To use this API @kconfig{CONFIG_BT_PATH_LOSS_MONITORING} must be + * set. + * + * @param conn @ref BT_CONN_TYPE_LE connection object. + * @param enable Enable/disable path loss reporting. + * + * @return Zero on success or (negative) error code on failure. + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_LE connection. + */ + +int bt_conn_le_set_path_loss_mon_enable(struct bt_conn *conn, bool enable); + +/** @brief Set Default Connection Subrating Parameters. + * + * Change the default subrating parameters for all future ACL connections + * where the local device is the central. This command does not affect any + * existing connection. Parameters set for specific connection will always + * have precedence. + * + * @note To use this API @kconfig{CONFIG_BT_SUBRATING} and + * @kconfig{CONFIG_BT_CENTRAL} must be set. + * + * @param params Subrating parameters. + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_conn_le_subrate_set_defaults( + const struct bt_conn_le_subrate_param *params); + +/** @brief Request New Subrating Parameters. + * + * Request a change to the subrating parameters of a connection. + * + * @note To use this API @kconfig{CONFIG_BT_SUBRATING} must be set. + * + * @param conn @ref BT_CONN_TYPE_LE connection object. + * @param params Subrating parameters. + * + * @return Zero on success or (negative) error code on failure. + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_LE connection. + */ + +int bt_conn_le_subrate_request(struct bt_conn *conn, + const struct bt_conn_le_subrate_param + *params); + +/** @brief Read Minimum Supported Connection Interval Groups. + * + * Read the minimum supported connection interval and supported interval + * groups from the local controller. This information describes what the + * local controller supports. + * + * @sa bt_conn_le_read_min_conn_interval if only + * @ref bt_conn_le_min_conn_interval_info.min_supported_conn_interval_us + * is needed. + * + * @kconfig_dep{CONFIG_BT_SHORTER_CONNECTION_INTERVALS} + * + * @param info Pointer to structure to receive the minimum connection + * interval information. + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_conn_le_read_min_conn_interval_groups( + struct bt_conn_le_min_conn_interval_info *info); + +/** @brief Read Minimum Supported Connection Interval. + * + * Read the minimum supported connection interval from the local controller. + * + * @sa bt_conn_le_read_min_conn_interval_groups if groups are needed. + * + * @kconfig_dep{CONFIG_BT_SHORTER_CONNECTION_INTERVALS} + * + * @param min_interval_us Pointer to variable to receive the minimum + * connection interval in microseconds. + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_conn_le_read_min_conn_interval(uint16_t *min_interval_us); + +/** @brief Set Default Connection Rate Parameters. + * + * Configure the range of Connection Rate values that this device will + * accept from a Peripheral initiating a Connection Rate Update procedure. + * + * The configured bounds: + * - Apply only to connections established after this call. + * - Are overridden on a given connection by any + * @ref bt_conn_le_conn_rate_request on that connection. + * + * @kconfig_dep{CONFIG_BT_SHORTER_CONNECTION_INTERVALS,CONFIG_BT_CENTRAL} + * + * @param params Connection rate parameters. + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_conn_le_conn_rate_set_defaults( + const struct bt_conn_le_conn_rate_param *params); + +/** @brief Request New Connection Rate Parameters. + * + * Request a change to the connection parameters of a connection. This + * includes Subrate parameters. This allows changing the connection interval + * to below the Baseline ConnInterval Values (BCV) and with finer + * granularity, if supported. + * + * Valid intervals of the local and peer controller should be known. See + * @ref bt_conn_le_read_min_conn_interval_groups + * + * @kconfig_dep{CONFIG_BT_SHORTER_CONNECTION_INTERVALS} + * + * @param conn @ref BT_CONN_TYPE_LE connection object. + * @param params Connection rate parameters. + * + * @return Zero on success or (negative) error code on failure. + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_LE connection. + */ + +int bt_conn_le_conn_rate_request(struct bt_conn *conn, + const struct bt_conn_le_conn_rate_param *params); + +/** @brief Read remote feature pages. + * + * Request remote feature pages, from 0 up to pages_requested or the number + * of pages supported by the peer. There is a maximum of 10 pages. This + * function will trigger the read_all_remote_feat_complete callback when the + * procedure is completed. + * + * @kconfig_dep{CONFIG_BT_LE_EXTENDED_FEAT_SET} + * + * @param conn @ref BT_CONN_TYPE_LE connection object. + * @param pages_requested Number of feature pages to be requested from peer. + * There is a maximum of 10 pages. + * + * @return Zero on success or (negative) error code on failure. + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_LE connection. + */ + +int bt_conn_le_read_all_remote_features(struct bt_conn *conn, + uint8_t pages_requested); + +/** @brief Update frame space. + * + * Request a change to the frame space parameters of a connection. This + * function will trigger the frame_space_updated callback when the procedure + * is completed. + * + * @kconfig_dep{CONFIG_BT_FRAME_SPACE_UPDATE}. + * + * @param conn @ref BT_CONN_TYPE_LE connection object. + * @param params Frame Space Update parameters. + * + * @return Zero on success or (negative) error code on failure. + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_LE connection. + */ + +int bt_conn_le_frame_space_update(struct bt_conn *conn, + const struct bt_conn_le_frame_space_update_param *params); + +/** @brief Update the connection parameters. + * + * If the local device is in the peripheral role then updating the + * connection parameters will be delayed. This delay can be configured by + * through the @kconfig{CONFIG_BT_CONN_PARAM_UPDATE_TIMEOUT} option. + * + * @param conn @ref BT_CONN_TYPE_LE connection object. + * @param param Updated connection parameters. + * + * @return Zero on success or (negative) error code on failure. + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_LE connection. + */ + +int bt_conn_le_param_update(struct bt_conn *conn, + const struct bt_le_conn_param *param); + +/** @brief Update the connection transmit data length parameters. + * + * @param conn @ref BT_CONN_TYPE_LE connection object. + * @param param Updated data length parameters. + * + * @return Zero on success or (negative) error code on failure. + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_LE connection. + */ + +int bt_conn_le_data_len_update(struct bt_conn *conn, + const struct bt_conn_le_data_len_param + *param); + +/** @brief Update the connection PHY parameters. + * + * Update the preferred transmit and receive PHYs of the connection. Use + * @ref BT_GAP_LE_PHY_NONE to indicate no preference. + * + * @param conn @ref BT_CONN_TYPE_LE connection object. + * @param param Updated connection parameters. + * + * @return Zero on success or (negative) error code on failure. + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_LE connection. + */ + +int bt_conn_le_phy_update(struct bt_conn *conn, + const struct bt_conn_le_phy_param *param); + +/** @brief Update the default PHY parameters to be used for all subsequent + * connections over the LE transport. + * + * Update the preferred transmit and receive PHYs of LE transport. Use @ref + * BT_GAP_LE_PHY_NONE to indicate no preference. For possible PHY values see + * @ref bt_gap_le_phy. + * + * @param pref_tx_phy Preferred transmitter phy parameters. + * @param pref_rx_phy Preferred receiver phy parameters. + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_conn_le_set_default_phy(uint8_t pref_tx_phy, uint8_t pref_rx_phy); + +/** @brief Disconnect from a remote device or cancel pending connection. + * + * Disconnect an active connection with the specified reason code or cancel + * pending outgoing connection. + * + * The disconnect reason for a normal disconnect should be: + * @ref BT_HCI_ERR_REMOTE_USER_TERM_CONN. + * + * The following disconnect reasons are accepted: + * - @ref BT_HCI_ERR_AUTH_FAIL + * - @ref BT_HCI_ERR_REMOTE_USER_TERM_CONN + * - @ref BT_HCI_ERR_REMOTE_LOW_RESOURCES + * - @ref BT_HCI_ERR_REMOTE_POWER_OFF + * - @ref BT_HCI_ERR_UNSUPP_REMOTE_FEATURE + * - @ref BT_HCI_ERR_PAIRING_NOT_SUPPORTED (BR/EDR connections only) + * - @ref BT_HCI_ERR_UNACCEPT_CONN_PARAM + * + * @param conn Connection to disconnect. + * @param reason Reason code for the disconnection. + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_conn_disconnect(struct bt_conn *conn, uint8_t reason); + +enum +{ + /** Convenience value when no options are specified. */ + + BT_CONN_LE_OPT_NONE = 0, + + /** @brief Enable LE Coded PHY. + * + * Enable scanning on the LE Coded PHY. + */ + + BT_CONN_LE_OPT_CODED = BIT(0), + + /** @brief Disable LE 1M PHY. + * + * Disable scanning on the LE 1M PHY. + * + * @note Requires @ref BT_CONN_LE_OPT_CODED. + */ + + BT_CONN_LE_OPT_NO_1M = BIT(1), +}; + +struct bt_conn_le_create_param +{ + /** Bit-field of create connection options. */ + + uint32_t options; + + /** Scan interval (N * 0.625 ms) + * + * @note When @kconfig{CONFIG_BT_SCAN_AND_INITIATE_IN_PARALLEL} is enabled + * and the application wants to scan and connect in parallel, the + * Bluetooth Controller may require the scan interval used for scanning + * and connection establishment to be equal to obtain the best + * performance. + */ + + uint16_t interval; + + /** Scan window (N * 0.625 ms) + * + * @note When @kconfig{CONFIG_BT_SCAN_AND_INITIATE_IN_PARALLEL} is enabled + * and the application wants to scan and connect in parallel, the + * Bluetooth Controller may require the scan window used for scanning and + * connection establishment to be equal to obtain the best performance. + */ + + uint16_t window; + + /** @brief Scan interval LE Coded PHY (N * 0.625 MS) + * + * Set zero to use same as LE 1M PHY scan interval + */ + + uint16_t interval_coded; + + /** @brief Scan window LE Coded PHY (N * 0.625 MS) + * + * Set zero to use same as LE 1M PHY scan window. + */ + + uint16_t window_coded; + + /** @brief Connection initiation timeout (N * 10 MS) + * + * Set zero to use the default @kconfig{CONFIG_BT_CREATE_CONN_TIMEOUT} + * timeout. + * + * @note Unused in @ref bt_conn_le_create_auto + */ + + uint16_t timeout; +}; + +/** @brief Initialize create connection parameters + * + * @param _options Create connection options. + * @param _interval Create connection scan interval (N * 0.625 ms). + * @param _window Create connection scan window (N * 0.625 ms). + */ +#define BT_CONN_LE_CREATE_PARAM_INIT(_options, _interval, _window) \ +{ \ + .options = (_options), \ + .interval = (_interval), \ + .window = (_window), \ + .interval_coded = 0, \ + .window_coded = 0, \ + .timeout = 0, \ +} + +/** Helper to declare create connection parameters inline + * + * @param _options Create connection options. + * @param _interval Create connection scan interval (N * 0.625 ms). + * @param _window Create connection scan window (N * 0.625 ms). + */ +#define BT_CONN_LE_CREATE_PARAM(_options, _interval, _window) \ +((struct bt_conn_le_create_param[]){BT_CONN_LE_CREATE_PARAM_INIT(_options, _interval, _window)}) + +/** Default LE create connection parameters. Scan continuously by setting + * scan interval equal to scan window. + */ +#define BT_CONN_LE_CREATE_CONN \ +BT_CONN_LE_CREATE_PARAM(BT_CONN_LE_OPT_NONE, BT_GAP_SCAN_FAST_INTERVAL, BT_GAP_SCAN_FAST_INTERVAL) + +/** Default LE create connection using filter accept list parameters. + * Scan window: 30 ms. Scan interval: 60 ms. + */ +#define BT_CONN_LE_CREATE_CONN_AUTO \ +BT_CONN_LE_CREATE_PARAM(BT_CONN_LE_OPT_NONE, BT_GAP_SCAN_FAST_INTERVAL, BT_GAP_SCAN_FAST_WINDOW) + +/** @brief Initiate an LE connection to a remote device. + * + * Allows initiate new LE link to remote peer using its address. + * + * The caller gets a new reference to the connection object which must be + * released with bt_conn_unref() once done using the object. If + * @kconfig{CONFIG_BT_CONN_CHECK_NULL_BEFORE_CREATE} is enabled, this + * function will return -EINVAL if dereferenced @p conn is not NULL. + * + * This uses the General Connection Establishment procedure. + * + * The application must disable explicit scanning before initiating a new LE + * connection if @kconfig{CONFIG_BT_SCAN_AND_INITIATE_IN_PARALLEL} is not + * enabled. + * + * @param[in] peer Remote address. + * @param[in] create_param Create connection parameters. + * @param[in] conn_param Initial connection parameters. + * @param[out] conn Valid connection object on success. + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_conn_le_create(const bt_addr_le_t *peer, + const struct bt_conn_le_create_param *create_param, + const struct bt_le_conn_param *conn_param, + struct bt_conn **conn); + +struct bt_conn_le_create_synced_param +{ + /** @brief Remote address + * + * The peer must be synchronized to the PAwR train. + * + */ + + const bt_addr_le_t *peer; + + /** The subevent where the connection will be initiated. */ + + uint8_t subevent; +}; + +/** @brief Create a connection to a synced device + * + * Initiate a connection to a synced device from a Periodic Advertising with + * Responses (PAwR) train. + * + * The caller gets a new reference to the connection object which must be + * released with bt_conn_unref() once done using the object. If + * @kconfig{CONFIG_BT_CONN_CHECK_NULL_BEFORE_CREATE} is enabled, this + * function will return -EINVAL if dereferenced @p conn is not NULL. + * + * This uses the Periodic Advertising Connection Procedure. + * + * @param[in] adv The adverting set the PAwR advertiser belongs + * to. + * @param[in] synced_param Create connection parameters. + * @param[in] conn_param Initial connection parameters. + * @param[out] conn Valid connection object on success. + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_conn_le_create_synced(const struct bt_le_ext_adv *adv, + const struct bt_conn_le_create_synced_param *synced_param, + const struct bt_le_conn_param *conn_param, + struct bt_conn **conn); + +/** @brief Automatically connect to remote devices in the filter accept list. + * + * This uses the Auto Connection Establishment procedure. The procedure will + * continue until a single connection is established or the procedure is + * stopped through @ref bt_conn_create_auto_stop. To establish connections + * to all devices in the filter accept list the procedure should be started + * again in the connected callback after a new connection has been + * established. + * + * @param create_param Create connection parameters + * @param conn_param Initial connection parameters. + * + * @return Zero on success or (negative) error code on failure. + * @return -ENOMEM No free connection object available. + */ + +int bt_conn_le_create_auto( + const struct bt_conn_le_create_param *create_param, + const struct bt_le_conn_param *conn_param); + +/** @brief Stop automatic connect creation. + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_conn_create_auto_stop(void); + +/** @brief Set security level for a connection. + * + * This function enable security (encryption) for a connection. If the + * device has bond information for the peer with sufficiently strong key + * encryption will be enabled. If the connection is already encrypted with + * sufficiently strong key this function does nothing. + * + * If the device has no bond information for the peer and is not already + * paired then the pairing procedure will be initiated. Note that @p sec has + * no effect on the security level selected for the pairing process. The + * selection is instead controlled by the values of the registered @ref + * bt_conn_auth_cb. If the device has bond information or is already paired + * and the keys are too weak then the pairing procedure will be initiated. + * + * This function may return an error if the required level of security + * defined using @p sec is not possible to achieve due to local or remote + * device limitation (e.g., input output capabilities), or if the maximum + * number of paired devices has been reached. + * + * This function may return an error if the pairing procedure has already + * been initiated by the local device or the peer device. + * + * @note When @kconfig{CONFIG_BT_SMP_SC_ONLY} is enabled then the security + * level will always be level 4. + * + * @note When @kconfig{CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY} is enabled then + * the security level will always be level 3. + * + * @note When @ref BT_SECURITY_FORCE_PAIR within @p sec is enabled then the + * pairing procedure will always be initiated. + * + * @param conn @ref BT_CONN_TYPE_LE or @ref BT_CONN_TYPE_BR connection + * object. + * @param sec Requested minimum security level. + * + * @return 0 on success or negative error + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_LE or @ref + * BT_CONN_TYPE_BR connection. + * @return -EALREADY A Security Request is already in flight (one sent by + * the peripheral has not yet been answered). It is left as-is and the + * security level it carries is not changed. + */ + +int bt_conn_set_security(struct bt_conn *conn, bt_security_t sec); + +/** @brief Get security level for a connection. + * + * @param conn @ref BT_CONN_TYPE_LE or @ref BT_CONN_TYPE_BR connection + * object. + * + * @return Connection security level if @kconfig{CONFIG_BT_SMP} or + * @kconfig{CONFIG_BT_CLASSIC} is enabled, else @ref BT_SECURITY_L1 + * @return @ref BT_SECURITY_L0 @p conn is not a valid @ref BT_CONN_TYPE_LE + * or @ref BT_CONN_TYPE_BR connection. + */ + +bt_security_t bt_conn_get_security(const struct bt_conn *conn); + +/** @brief Get encryption key size. + * + * This function gets encryption key size. If there is no security + * (encryption) enabled 0 will be returned. + * + * @param conn @ref BT_CONN_TYPE_LE or @ref BT_CONN_TYPE_BR connection + * object. + * + * @return Encryption key size. + * @return 0 @p conn is not a valid @ref BT_CONN_TYPE_LE or @ref + * BT_CONN_TYPE_BR connection. + */ + +uint8_t bt_conn_enc_key_size(const struct bt_conn *conn); + +enum bt_security_err +{ + /** Security procedure successful. */ + + BT_SECURITY_ERR_SUCCESS, + + /** Authentication failed. */ + + BT_SECURITY_ERR_AUTH_FAIL, + + /** PIN or encryption key is missing. */ + + BT_SECURITY_ERR_PIN_OR_KEY_MISSING, + + /** OOB data is not available. */ + + BT_SECURITY_ERR_OOB_NOT_AVAILABLE, + + /** The requested security level could not be reached. */ + + BT_SECURITY_ERR_AUTH_REQUIREMENT, + + /** Pairing is not supported */ + + BT_SECURITY_ERR_PAIR_NOT_SUPPORTED, + + /** Pairing is not allowed. */ + + BT_SECURITY_ERR_PAIR_NOT_ALLOWED, + + /** Invalid parameters. */ + + BT_SECURITY_ERR_INVALID_PARAM, + + /** Distributed Key Rejected */ + + BT_SECURITY_ERR_KEY_REJECTED, + + /** Pairing failed but the exact reason could not be specified. */ + + BT_SECURITY_ERR_UNSPECIFIED, +}; + +enum bt_conn_le_cs_procedure_enable_state +{ + BT_CONN_LE_CS_PROCEDURES_DISABLED = BT_HCI_OP_LE_CS_PROCEDURES_DISABLED, + BT_CONN_LE_CS_PROCEDURES_ENABLED = BT_HCI_OP_LE_CS_PROCEDURES_ENABLED, +}; + +/** CS Test Tone Antenna Config Selection. + * + * See Bluetooth Core Specification, Vol 6, Part A, Section 5.3 and + * Bluetooth Core Specification, Vol 6, Part H, Section 4.7 + * + * These enum values are indices in the following table, where N_AP is the + * maximum number of antenna paths (in the range [1, 4]). + * + * +--------+-------+------------+------------+--------+ + * | Config | Total | Initiator: | Reflector: | Config | + * | Index | Paths | # Antennas | # Antennas | | + * +--------+-------+------------+------------+--------+ + * | 0 | 1 | 1 | 1 | 1:1 | + * | 1 | 2 | 2 | 1 | N_AP:1 | + * | 2 | 3 | 3 | 1 | N_AP:1 | + * | 3 | 4 | 4 | 1 | N_AP:1 | + * | 4 | 2 | 1 | 2 | 1:N_AP | + * | 5 | 3 | 1 | 3 | 1:N_AP | + * | 6 | 4 | 1 | 4 | 1:N_AP | + * | 7 | 4 | 2 | 2 | 2:2 | + * +--------+-------+------------+------------+--------+ + * + * There are therefore four groups of possible antenna configurations: + * + * - 1:1 configuration, where both Initiator and Reflector support 1 antenna + * each + * - 1:N_AP configuration, where Initiator supports 1 antenna, Reflector + * supports N_AP antennas, and N_AP is a value in the range [2, 4] + * - N_AP:1 configuration, where Initiator supports N_AP antennas, Reflector + * supports 1 antenna, and N_AP is a value in the range [2, 4] + * - 2:2 configuration, where both Initiator and Reflector support 2 + * antennas and N_AP = 4 + */ + +enum bt_conn_le_cs_tone_antenna_config_selection +{ + /** Initiator (dev A): 1 antenna, Reflector (dev B): 1 antenna */ + + BT_LE_CS_TONE_ANTENNA_CONFIGURATION_A1_B1 = BT_HCI_OP_LE_CS_ACI_0, + + /** Initiator (dev A): 2 antennas, Reflector (dev B): 1 antenna */ + + BT_LE_CS_TONE_ANTENNA_CONFIGURATION_A2_B1 = BT_HCI_OP_LE_CS_ACI_1, + + /** Initiator (dev A): 3 antennas, Reflector (dev B): 1 antenna */ + + BT_LE_CS_TONE_ANTENNA_CONFIGURATION_A3_B1 = BT_HCI_OP_LE_CS_ACI_2, + + /** Initiator (dev A): 4 antennas, Reflector (dev B): 1 antenna */ + + BT_LE_CS_TONE_ANTENNA_CONFIGURATION_A4_B1 = BT_HCI_OP_LE_CS_ACI_3, + + /** Initiator (dev A): 1 antenna, Reflector (dev B): 2 antennas */ + + BT_LE_CS_TONE_ANTENNA_CONFIGURATION_A1_B2 = BT_HCI_OP_LE_CS_ACI_4, + + /** Initiator (dev A): 1 antenna, Reflector (dev B): 3 antennas */ + + BT_LE_CS_TONE_ANTENNA_CONFIGURATION_A1_B3 = BT_HCI_OP_LE_CS_ACI_5, + + /** Initiator (dev A): 1 antenna, Reflector (dev B): 4 antennas */ + + BT_LE_CS_TONE_ANTENNA_CONFIGURATION_A1_B4 = BT_HCI_OP_LE_CS_ACI_6, + + /** Initiator (dev A): 2 antennas, Reflector (dev B): 2 antennas */ + + BT_LE_CS_TONE_ANTENNA_CONFIGURATION_A2_B2 = BT_HCI_OP_LE_CS_ACI_7, +}; + +struct bt_conn_le_cs_procedure_enable_complete +{ + /* The ID associated with the desired configuration (0 to 3) */ + + uint8_t config_id; + + /* State of the CS procedure */ + + enum bt_conn_le_cs_procedure_enable_state state; + + /* Antenna configuration index */ + + enum bt_conn_le_cs_tone_antenna_config_selection + tone_antenna_config_selection; + + /* Transmit power level used for CS procedures (-127 to 20 dB; 0x7F if + * unavailable) + */ + + int8_t selected_tx_power; + + /* Duration of each CS subevent in microseconds (1250 us to 4 s) */ + + uint32_t subevent_len; + + /* Number of CS subevents anchored off the same ACL connection event (0x01 + * to 0x20) + */ + + uint8_t subevents_per_event; + + /* Time between consecutive CS subevents anchored off the same ACL + * connection event in units of 0.625 ms + */ + + uint16_t subevent_interval; + + /* Number of ACL connection events between consecutive CS event anchor + * points + */ + + uint16_t event_interval; + + /* Number of ACL connection events between consecutive CS procedure anchor + * points + */ + + uint16_t procedure_interval; + + /* Number of CS procedures to be scheduled (0 if procedures to continue + * until disabled) + */ + + uint16_t procedure_count; + + /* Maximum duration for each procedure in units of 0.625 ms (0x0001 to + * 0xFFFF) + */ + + uint16_t max_procedure_len; +}; + +/** @brief BR/EDR specific connection callbacks. */ + +struct bt_conn_br_cb +{ +#if defined(CONFIG_BT_POWER_MODE_CONTROL) || defined(__DOXYGEN__) + /** @brief A BR/EDR connection mode has changed. + * + * This callback notifies the application that the sniff mode has changed. + * + * @param conn Connection object. + * @param mode Active/Sniff mode. + * @param interval Sniff interval. + */ + + void (*mode_changed)(struct bt_conn *conn, + uint8_t mode, + uint16_t interval); +#endif /* CONFIG_BT_POWER_MODE_CONTROL */ + + /** @brief A BR/EDR connection role has changed. + * + * This callback notifies the application that the BR/EDR role switch + * procedure has completed. + * + * @param conn Connection object. + * @param status HCI status of role change event. + */ + + void (*role_changed)(struct bt_conn *conn, uint8_t status); + + /** @brief The packet type of the BR/EDR connection has changed. + * + * This callback notifies the application that the connection packet type + * change procedure has completed. + * + * @param conn Connection object. + * @param status HCI status of the event. + * @param packet_type New packet type bitmask. + */ + + void (*packet_type_changed)(struct bt_conn *conn, uint8_t status, + uint16_t packet_type); +}; + +/** @brief Synthetic error value for an L2CAP Connection Parameter Update + * Procedure (CPUP) rejection. + * + * Passed as the hci_err argument to @ref + * bt_conn_cb.le_param_update_rejected when the remote peer does not support + * the HCI Connection Parameter Request (CPR) procedure and the L2CAP CPUP + * fallback is rejected instead. This is a host-internal sentinel; it is not + * a real HCI error code. + */ +#define BT_CONN_PARAM_REJECT_ERR_L2CAP_CPUP 0xFF + +/** @brief Connection callback structure. + * + * This structure is used for tracking the state of a connection. It is + * registered with the help of the bt_conn_cb_register() API. It's + * permissible to register multiple instances of this @ref bt_conn_cb type, + * in case different modules of an application are interested in tracking + * the connection state. If a callback is not of interest for an instance, + * it may be set to NULL and will as a consequence not be used for that + * instance. + * + * @note The callbacks are invoked from a thread context, never from an + * ISR. Whether a callback is invoked from a context internal to the stack + * or synchronously from within the API call that triggers it, and from + * which context, is not part of the API and may change between releases. + * See @rstref{Callback execution contexts } + * for the hazards of blocking in a callback and their mitigations. + */ + +struct bt_conn_cb +{ + /** @brief A new connection has been established. + * + * This callback notifies the application of a new connection. In case the + * err parameter is non-zero it means that the connection establishment + * failed. + * + * @note If the connection was established from an advertising set then + * the advertising set cannot be restarted directly from this callback. + * Instead use the connected callback of the advertising set. + * + * @param conn New connection object. + * @param err HCI error. Zero for success, non-zero otherwise. + * + * @p err can mean either of the following: + * - @ref BT_HCI_ERR_UNKNOWN_CONN_ID Creating the connection started by + * @ref bt_conn_le_create was canceled either by the user through + * @ref bt_conn_disconnect or by the timeout in the host through + * @ref bt_conn_le_create_param timeout parameter, which defaults to + * @kconfig{CONFIG_BT_CREATE_CONN_TIMEOUT} seconds. + * - @p BT_HCI_ERR_ADV_TIMEOUT High duty cycle directed connectable + * advertiser started by @ref bt_le_adv_start failed to be connected + * within the timeout. + */ + + void (*connected)(struct bt_conn *conn, uint8_t err); + + /** @brief A connection has been disconnected. + * + * This callback notifies the application that a connection has been + * disconnected. + * + * When this callback is called the stack still has one reference to the + * connection object. If the application in this callback tries to start + * either a connectable advertiser or create a new connection this might + * fail because there are no free connection objects available. To avoid + * this issue, it's recommended to rely instead on @ref + * bt_conn_cb.recycled which notifies the application when a connection + * object has actually been freed. + * + * @param conn Connection object. + * @param reason BT_HCI_ERR_* reason for the disconnection. + */ + + void (*disconnected)(struct bt_conn *conn, uint8_t reason); + + /** @brief A connection object has been returned to the pool. + * + * This callback notifies the application that it might be able to allocate + * a connection object. No guarantee, first come, first serve. Only + * connections that are configurable by @kconfig{CONFIG_BT_MAX_CONN} + * trigger this callback, i.e. connections of type @ref BT_CONN_TYPE_LE or + * @ref BT_CONN_TYPE_BR. + * + * The maximum number of simultaneous connections is configured by + * @kconfig{CONFIG_BT_MAX_CONN}. + * + * This is the event to listen for to start a new connection or connectable + * advertiser, both when the intention is to start it after the system is + * completely finished with an earlier connection, and when the application + * wants to start a connection for any reason but failed and is waiting for + * the right time to retry. + */ + + void (*recycled)(void); + + /** @brief LE connection parameter update request. + * + * This callback notifies the application that a remote device is + * requesting to update the connection parameters. The application accepts + * the parameters by returning true, or rejects them by returning false. + * Before accepting, the application may also adjust the parameters to + * better suit its needs. + * + * It is recommended for an application to have just one of these + * callbacks for simplicity. However, if an application registers multiple + * it needs to manage the potentially different requirements for each + * callback. Each callback gets the parameters as returned by previous + * callbacks, i.e. they are not necessarily the same ones as the remote + * originally sent. + * + * If the application does not have this callback then the default is to + * accept the parameters. + * + * @param conn Connection object. + * @param param Proposed connection parameters. + * + * @return true to accept the parameters, or false to reject them. + */ + + bool (*le_param_req)(struct bt_conn *conn, struct bt_le_conn_param *param); + + /** @brief The parameters for an LE connection have been updated. + * + * This callback notifies the application that the connection parameters + * for an LE connection have been updated. + * + * @param conn Connection object. + * @param interval Connection interval. + * @param latency Connection latency. + * @param timeout Connection supervision timeout. + */ + +void (*le_param_updated)(struct bt_conn *conn, + uint16_t interval, + uint16_t latency, + uint16_t timeout); + +#if defined(CONFIG_BT_USER_CONN_PARAM_REJECTED) || defined(__DOXYGEN__) + /** @brief LE connection parameter update was rejected by the peer. + * + * This callback notifies the application that a connection parameter + * update request initiated by @ref bt_conn_le_param_update was rejected + * by the peer. + * + * @note Only called for explicit HCI rejections and L2CAP Connection + * Parameter Update Procedure (CPUP) rejections. Mutually exclusive with + * le_param_updated for the same event. If the remote does not support the + * Connection Parameter Request (CPR) procedure and the L2CAP fallback is + * rejected, @p hci_err will be BT_CONN_PARAM_REJECT_ERR_L2CAP_CPUP. + * + * @param conn Connection object. + * @param hci_err HCI error code (BT_HCI_ERR_*) for CPR rejection, or + * BT_CONN_PARAM_REJECT_ERR_L2CAP_CPUP for L2CAP fallback + * rejection. + */ + + void (*le_param_update_rejected)(struct bt_conn *conn, uint8_t hci_err); +#endif /* defined(CONFIG_BT_USER_CONN_PARAM_REJECTED) || defined(__DOXYGEN__) */ + +#if defined(CONFIG_BT_SMP) || defined(__DOXYGEN__) + /** @brief Remote Identity Address has been resolved. + * + * This callback notifies the application that a remote Identity Address + * has been resolved + * + * @param conn Connection object. + * @param rpa Resolvable Private Address. + * @param identity Identity Address. + */ + + void (*identity_resolved)(struct bt_conn *conn, const bt_addr_le_t *rpa, + const bt_addr_le_t *identity); +#endif /* CONFIG_BT_SMP */ +#if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_CLASSIC) + /** @brief The security level of a connection has changed. + * + * This callback notifies the application that the security of a + * connection has changed. + * + * The security level of the connection can either have been increased or + * remain unchanged. An increased security level means that the pairing + * procedure has been performed or the bond information from a previous + * connection has been applied. If the security level remains unchanged + * this means that the encryption key has been refreshed for the + * connection. + * + * @param conn Connection object. + * @param level New security level of the connection. + * @param err Security error. Zero for success, non-zero otherwise. + */ + void (*security_changed)(struct bt_conn *conn, bt_security_t level, + enum bt_security_err err); +#endif /* defined(CONFIG_BT_SMP) || defined(CONFIG_BT_CLASSIC) */ + +#if defined(CONFIG_BT_REMOTE_INFO) || defined(__DOXYGEN__) + /** @brief Remote information procedures has completed. + * + * This callback notifies the application that the remote information has + * been retrieved from the remote peer. + * + * @param conn Connection object. + * @param remote_info Connection information of remote device. + */ + + void (*remote_info_available)(struct bt_conn *conn, + struct bt_conn_remote_info *remote_info); +#endif /* defined(CONFIG_BT_REMOTE_INFO) */ + +#if defined(CONFIG_BT_CLASSIC) || defined(__DOXYGEN__) + /** @brief BR/EDR specific callbacks. */ + + struct bt_conn_br_cb br; +#endif /* CONFIG_BT_CLASSIC */ + +#if defined(CONFIG_BT_USER_PHY_UPDATE) || defined(__DOXYGEN__) + /** @brief The PHY of the connection has changed. + * + * This callback notifies the application that the PHY of the connection + * has changed. + * + * @param conn Connection object. + * @param param Connection LE PHY information. + */ + + void (*le_phy_updated)(struct bt_conn *conn, + struct bt_conn_le_phy_info *param); +#endif /* defined(CONFIG_BT_USER_PHY_UPDATE) */ + +#if defined(CONFIG_BT_USER_DATA_LEN_UPDATE) || defined(__DOXYGEN__) + /** @brief The data length parameters of the connection has changed. + * + * This callback notifies the application that the maximum Link Layer + * payload length or transmission time has changed. + * + * @param conn Connection object. + * @param info Connection data length information. + */ + + void (*le_data_len_updated)(struct bt_conn *conn, + struct bt_conn_le_data_len_info *info); +#endif /* defined(CONFIG_BT_USER_DATA_LEN_UPDATE) */ + +#if defined(CONFIG_BT_DF_CONNECTION_CTE_RX) || defined(__DOXYGEN__) + /** @brief Callback for IQ samples report collected when sampling + * CTE received by data channel PDU. + * + * @param conn The connection object. + * @param iq_report Report data for collected IQ samples. + */ + + void (*cte_report_cb)(struct bt_conn *conn, + const struct bt_df_conn_iq_samples_report + *iq_report); +#endif /* CONFIG_BT_DF_CONNECTION_CTE_RX */ + +#if defined(CONFIG_BT_TRANSMIT_POWER_CONTROL) || defined(__DOXYGEN__) + /** @brief LE Read Remote Transmit Power Level procedure has completed or + * LE Transmit Power Reporting event. + * + * This callback notifies the application that either the remote transmit + * power level has been read from the peer or transmit power level has + * changed for the local or remote controller when transmit power + * reporting is enabled for the respective side using @ref + * bt_conn_le_set_tx_power_report_enable. + * + * @param conn Connection object. + * @param report Transmit power report. + */ + + void (*tx_power_report)(struct bt_conn *conn, + const struct bt_conn_le_tx_power_report *report); +#endif /* CONFIG_BT_TRANSMIT_POWER_CONTROL */ + +#if defined(CONFIG_BT_PATH_LOSS_MONITORING) || defined(__DOXYGEN__) + /** @brief LE Path Loss Threshold event. + * + * This callback notifies the application that there has been a path loss + * threshold crossing or reporting the initial path loss threshold zone + * after using + * @ref bt_conn_le_set_path_loss_mon_enable. + * + * @param conn Connection object. + * @param report Path loss threshold report. + */ + +void (*path_loss_threshold_report)(struct bt_conn *conn, + const struct bt_conn_le_path_loss_threshold_report *report); +#endif /* CONFIG_BT_PATH_LOSS_MONITORING */ + +#if defined(CONFIG_BT_SUBRATING) || defined(__DOXYGEN__) + /** @brief LE Subrate Changed event. + * + * This callback notifies the application that the subrating parameters of + * the connection may have changed. The connection subrating parameters + * will be unchanged if status is not BT_HCI_ERR_SUCCESS. + * + * @param conn Connection object. + * @param params New subrating parameters. + */ + + void (*subrate_changed)(struct bt_conn *conn, + const struct bt_conn_le_subrate_changed *params); +#endif /* CONFIG_BT_SUBRATING */ + +#if defined(CONFIG_BT_SHORTER_CONNECTION_INTERVALS) || defined(__DOXYGEN__) + /** @brief LE Connection Rate Changed event. + * + * This callback notifies the application that the connection rate + * parameters (including both connection interval and subrating) of the + * connection may have changed. + * + * @param conn Connection object. + * @param status HCI Status from LE Connection Rate Change event. Possible + * Status codes: - Success (0x00) - Unknown Connection + * Identifier (0x02) - Command Disallowed (0x0C) - + * Unsupported Feature or Parameter Value (0x11) - Invalid + * HCI Command Parameters (0x12) - Unsupported Remote + * Feature (0x1A) - Unsupported LL Parameter Value (0x20) + * @param params New connection rate parameters. The connection rate + * parameters will be NULL if @p status is not @ref + * BT_HCI_ERR_SUCCESS. + */ + +void (*conn_rate_changed)(struct bt_conn *conn, + uint8_t status, + const struct bt_conn_le_conn_rate_changed *params); +#endif /* CONFIG_BT_SHORTER_CONNECTION_INTERVALS */ + +#if defined(CONFIG_BT_LE_EXTENDED_FEAT_SET) || defined(__DOXYGEN__) + /** @brief Read all remote features complete event. + * + * This callback notifies the application that a 'read all remote + * features' procedure of the connection is completed. The other params + * will not be populated if status is not @ref BT_HCI_ERR_SUCCESS. + * + * This callback can be triggered by calling @ref + * bt_conn_le_read_all_remote_features or by the procedure running + * autonomously in the controller. + * + * @param conn Connection object. + * @param params Remote features. + */ + +void (*read_all_remote_feat_complete)(struct bt_conn *conn, + const struct bt_conn_le_read_all_remote_feat_complete *params); +#endif /* CONFIG_BT_LE_EXTENDED_FEAT_SET */ + +#if defined(CONFIG_BT_FRAME_SPACE_UPDATE) || defined(__DOXYGEN__) + /** @brief Frame Space Update Complete event. + * + * This callback notifies the application that the frame space of the + * connection may have changed. The frame space update parameters will be + * invalid if status is not @ref BT_HCI_ERR_SUCCESS. + * + * This callback can be triggered by calling @ref + * bt_conn_le_frame_space_update, by the procedure running autonomously in + * the controller or by the peer. + * + * @param conn Connection object. + * @param params New frame space update parameters. + */ + +void (*frame_space_updated)(struct bt_conn *conn, + const struct bt_conn_le_frame_space_updated *params); +#endif /* CONFIG_BT_FRAME_SPACE_UPDATE */ + +#if defined(CONFIG_BT_CHANNEL_SOUNDING) || defined(__DOXYGEN__) + /** @brief LE CS Read Remote Supported Capabilities Complete event. + * + * This callback notifies the application that a Channel Sounding + * Capabilities Exchange procedure has completed. + * + * If status is BT_HCI_ERR_SUCCESS, the remote channel sounding + * capabilities have been received from the peer. + * + * @param conn Connection object. + * @param status HCI status of complete event. + * @param params Pointer to CS Capabilities on success or NULL otherwise. + */ + +void (*le_cs_read_remote_capabilities_complete)(struct bt_conn *conn, + uint8_t status, + struct bt_conn_le_cs_capabilities *params); + + /** @brief LE CS Read Remote FAE Table Complete event. + * + * This callback notifies the application that a Channel Sounding Mode-0 + * FAE Table Request procedure has completed. + * + * If status is BT_HCI_ERR_SUCCESS, the remote mode-0 FAE Table has been + * received from the peer. + * + * @param conn Connection object. + * @param status HCI status of complete event. + * @param params Pointer to FAE Table on success or NULL otherwise. + */ + +void (*le_cs_read_remote_fae_table_complete)(struct bt_conn *conn, + uint8_t status, + struct bt_conn_le_cs_fae_table *params); + + /** @brief LE CS Config created. + * + * This callback notifies the application that a Channel Sounding + * Configuration procedure has completed. + * + * If status is BT_HCI_ERR_SUCCESS, a new CS config is created. + * + * @param conn Connection object. + * @param status HCI status of complete event. + * @param config Pointer to CS configuration on success or NULL otherwise. + */ + + void (*le_cs_config_complete)(struct bt_conn *conn, uint8_t status, + struct bt_conn_le_cs_config *config); + + /** @brief LE CS Config removed. + * + * This callback notifies the application that a Channel Sounding + * Configuration procedure has completed and a CS config is removed + * + * @param conn Connection object. + * @param config_id ID of the CS configuration that was removed. + */ + + void (*le_cs_config_removed)(struct bt_conn *conn, uint8_t config_id); + + /** @brief Subevent Results from a CS procedure are available. + * + * This callback notifies the user that CS subevent results are available + * for the given connection object. + * + * @param conn Connection objects. + * @param result Subevent results + */ + +void (*le_cs_subevent_data_available)(struct bt_conn *conn, + struct bt_conn_le_cs_subevent_result *result); + + /** @brief LE CS Security Enabled. + * + * This callback notifies the application that a Channel Sounding Security + * Enable procedure has completed. + * + * If status is BT_HCI_ERR_SUCCESS, CS Security is enabled. + * + * @param conn Connection object. + * @param status HCI status of complete event. + */ + + void (*le_cs_security_enable_complete)(struct bt_conn *conn, + uint8_t status); + + /** @brief LE CS Procedure Enabled. + * + * This callback notifies the application that a Channel Sounding + * Procedure Enable procedure has completed. + * + * If status is BT_HCI_ERR_SUCCESS, CS procedure is enabled. + * + * @param conn Connection object. + * @param status HCI status. + * @param params Pointer to CS Procedure Enable parameters on success or + * NULL otherwise. + */ + +void (*le_cs_procedure_enable_complete)(struct bt_conn *conn, + uint8_t status, + struct bt_conn_le_cs_procedure_enable_complete *params); + +#endif + +#if defined(CONFIG_BT_CONN_DYNAMIC_CALLBACKS) + /** @internal Internally used field for list handling */ + + struct list_node _node; +#endif +}; + +/** @brief Register connection callbacks. + * + * Register callbacks to monitor the state of connections. + * + * @param cb Callback struct. Must point to memory that remains valid. + * + * @retval 0 Success. + * @retval -EEXIST if @p cb was already registered. + */ + +int bt_conn_cb_register(struct bt_conn_cb *cb); + +/** + * @brief Unregister connection callbacks. + * + * Unregister the state of connections callbacks. + * + * @param cb Callback struct point to memory that remains valid. + * + * @retval 0 Success + * @retval -EINVAL If @p cb is NULL + * @retval -ENOENT if @p cb was not registered + */ + +int bt_conn_cb_unregister(struct bt_conn_cb *cb); + +/** + * @brief Register a callback structure for connection events. + * + * @param _name Name of callback structure. + */ +#define BT_CONN_CB_DEFINE(_name) \ +static const STRUCT_SECTION_ITERABLE(bt_conn_cb, _CONCAT(bt_conn_cb_, _name)) + +/** Converts a security error to string. + * + * @return The string representation of the security error code. + * If @kconfig{CONFIG_BT_SECURITY_ERR_TO_STR} is not enabled, this just + * returns the empty string + */ +#if defined(CONFIG_BT_SECURITY_ERR_TO_STR) +const char *bt_security_err_to_str(enum bt_security_err err); +#else +static inline const char *bt_security_err_to_str(enum bt_security_err err) +{ +ARG_UNUSED(err); + +return ""; +} +#endif + +/** @brief Enable/disable bonding. + * + * Set/clear the Bonding flag in the Authentication Requirements of SMP + * Pairing Request/Response data. The initial value of this flag depends on + * BT_BONDABLE Kconfig setting. For the vast majority of applications + * calling this function shouldn't be needed. + * + * @param enable Value allowing/disallowing to be bondable. + */ + +void bt_set_bondable(bool enable); + +/** @brief Get bonding flag. + * + * Get current bonding flag. The initial value of this flag depends on + * @kconfig{CONFIG_BT_BONDABLE} Kconfig setting. The Bonding flag can be + * updated using bt_set_bondable(). + * + * @return Current bonding flag. + */ + +bool bt_get_bondable(void); + +/** @brief Set/clear the bonding flag for a given connection. + * + * Set/clear the Bonding flag in the Authentication Requirements of SMP + * Pairing Request/Response data for a given connection. + * + * The bonding flag for a given connection cannot be set/cleared if security + * procedures in the SMP module have already started. This function can be + * called only once per connection. + * + * If the bonding flag is not set/cleared for a given connection, the value + * will depend on global configuration which is set using bt_set_bondable. + * The default value of the global configuration is defined using + * CONFIG_BT_BONDABLE Kconfig option. + * + * @param conn @ref BT_CONN_TYPE_LE or @ref BT_CONN_TYPE_BR connection + * object. + * @param enable Value allowing/disallowing to be bondable. + * + * @retval 0 Success + * @retval -EALREADY Already in the requested state + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_LE or @ref + * BT_CONN_TYPE_BR connection + * @return -EINVAL @p conn is a valid @ref BT_CONN_TYPE_LE but could not get + * SMP context + */ + +int bt_conn_set_bondable(struct bt_conn *conn, bool enable); + +/** @brief Allow/disallow remote LE SC OOB data to be used for pairing. + * + * Set/clear the OOB data flag for LE SC SMP Pairing Request/Response data. + * + * @param enable Value allowing/disallowing remote LE SC OOB data. + */ + +void bt_le_oob_set_sc_flag(bool enable); + +/** @brief Allow/disallow remote legacy OOB data to be used for pairing. + * + * Set/clear the OOB data flag for legacy SMP Pairing Request/Response data. + * + * @param enable Value allowing/disallowing remote legacy OOB data. + */ + +void bt_le_oob_set_legacy_flag(bool enable); + +/** @brief Set OOB Temporary Key to be used for pairing + * + * This function allows to set OOB data for the LE legacy pairing procedure. + * The function should only be called in response to the oob_data_request() + * callback provided that the legacy method is user pairing. + * + * @param conn @ref BT_CONN_TYPE_LE connection object. + * @param tk Pointer to 16 byte long TK array. The TK value should be + * generated randomly for each new pairing process. + * + * @retval 0 Success + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_LE connection. + * @return -EINVAL @p tk is NULL. + */ + +int bt_le_oob_set_legacy_tk(struct bt_conn *conn, const uint8_t *tk); + +/** @brief Set OOB data during LE Secure Connections (SC) pairing procedure + * + * This function allows to set OOB data during the LE SC pairing procedure. + * The function should only be called in response to the oob_data_request() + * callback provided that LE SC method is used for pairing. + * + * The user should submit OOB data according to the information received in + * the callback. This may yield three different configurations: with only + * local OOB data present, with only remote OOB data present or with both + * local and remote OOB data present. + * + * @param conn @ref BT_CONN_TYPE_LE connection object. + * @param oobd_local Local OOB data or NULL if not present + * @param oobd_remote Remote OOB data or NULL if not present + * + * @return Zero on success or error code otherwise, positive in case of + * protocol error or negative (POSIX) in case of stack internal error. + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_LE connection. + */ + +int bt_le_oob_set_sc_data( + struct bt_conn *conn, const struct bt_le_oob_sc_data *oobd_local, + const struct bt_le_oob_sc_data *oobd_remote); + +/** @brief Get OOB data used for LE Secure Connections (SC) pairing procedure + * + * This function allows to get OOB data during the LE SC pairing procedure + * that were set by the bt_le_oob_set_sc_data() API. + * + * @note The OOB data will only be available as long as the connection + * object associated with it is valid. + * + * @param conn @ref BT_CONN_TYPE_LE connection object. + * @param oobd_local Local OOB data or NULL if not set + * @param oobd_remote Remote OOB data or NULL if not set + * + * @return Zero on success or error code otherwise, positive in case of + * protocol error or negative (POSIX) in case of stack internal error. + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_LE connection. + */ + +int bt_le_oob_get_sc_data( + struct bt_conn *conn, const struct bt_le_oob_sc_data **oobd_local, + const struct bt_le_oob_sc_data **oobd_remote); + +/** Info Structure for OOB pairing */ + +struct bt_conn_oob_info +{ + /** Type of OOB pairing method */ + + enum + { + /** LE legacy pairing */ + + BT_CONN_OOB_LE_LEGACY, + + /** LE SC pairing */ + + BT_CONN_OOB_LE_SC, + } type; + + union + { + /** LE Secure Connections OOB pairing parameters */ + + struct + { + /** OOB data configuration */ + + enum + { + /** Local OOB data requested */ + + BT_CONN_OOB_LOCAL_ONLY, + + /** Remote OOB data requested */ + + BT_CONN_OOB_REMOTE_ONLY, + + /** Both local and remote OOB data requested */ + + BT_CONN_OOB_BOTH_PEERS, + + /** No OOB data requested */ + + BT_CONN_OOB_NO_DATA, + } oob_config; + } lesc; + }; +}; + +/** @brief Pairing request and pairing response info structure. + * + * This structure is the same for both smp_pairing_req and smp_pairing_rsp + * and a subset of the packet data, except for the initial Code octet. It is + * documented in Core Spec. Vol. 3, Part H, 3.5.1 and 3.5.2. + */ + +struct bt_conn_pairing_feat +{ + /** IO Capability, Core Spec. Vol 3, Part H, 3.5.1, Table 3.4 */ + + uint8_t io_capability; + + /** OOB data flag, Core Spec. Vol 3, Part H, 3.5.1, Table 3.5 */ + + uint8_t oob_data_flag; + + /** AuthReq, Core Spec. Vol 3, Part H, 3.5.1, Fig. 3.3 */ + + uint8_t auth_req; + + /** Maximum Encryption Key Size, Core Spec. Vol 3, Part H, 3.5.1 */ + + uint8_t max_enc_key_size; + + /** Initiator Key Distribution/Generation, Core Spec. Vol 3, Part H, 3.6.1, + * Fig. 3.11 + */ + + uint8_t init_key_dist; + + /** Responder Key Distribution/Generation, Core Spec. Vol 3, Part H 3.6.1, + * Fig. 3.11 + */ + + uint8_t resp_key_dist; +}; + +/* Special passkey value that can be used to generate a random passkey when + * using the app_passkey callback from @ref bt_conn_auth_cb. + * + */ +#define BT_PASSKEY_RAND 0xffffffff + +/** Authenticated pairing callback structure + * + * @note The callbacks are invoked from a thread context, never from an + * ISR. Whether a callback is invoked from a context internal to the stack + * or synchronously from within the API call that triggers it, and from + * which context, is not part of the API and may change between releases. + * See @rstref{Callback execution contexts } + * for the hazards of blocking in a callback and their mitigations. + */ + +struct bt_conn_auth_cb +{ +#if defined(CONFIG_BT_SMP_APP_PAIRING_ACCEPT) || defined(__DOXYGEN__) + /** @brief Query to proceed incoming pairing or not. + * + * On any incoming pairing req/rsp this callback will be called for the + * application to decide whether to allow for the pairing to continue. + * + * The pairing info received from the peer is passed to assist making the + * decision. + * + * As this callback is synchronous the application should return a + * response value immediately. Otherwise it may affect the timing during + * pairing. Hence, this information should not be conveyed to the user to + * take action. + * + * The remaining callbacks are not affected by this, but do notice that + * other callbacks can be called during the pairing. Eg. if + * pairing_confirm is registered both will be called for Just-Works + * pairings. + * + * This callback may be unregistered in which case pairing continues as if + * the Kconfig flag was not set. + * + * For BR/EDR Secure Simple Pairing (SSP), this callback is called when + * receiving the BT_HCI_EVT_IO_CAPA_REQ hci event. The feat is NULL here. + * + * @param conn Connection where pairing is initiated. + * @param feat Pairing req/resp info. It is NULL in BR/EDR SSP. + */ + +enum bt_security_err (*pairing_accept)(struct bt_conn *conn, + const struct bt_conn_pairing_feat *const feat); +#endif /* CONFIG_BT_SMP_APP_PAIRING_ACCEPT */ + + /** @brief Display a passkey to the user. + * + * When called the application is expected to display the given passkey to + * the user, with the expectation that the passkey will then be entered on + * the peer device. The passkey will be in the range of 0 - 999999, and is + * expected to be padded with zeroes so that six digits are always shown. + * E.g. the value 37 should be shown as 000037. + * + * This callback may be set to NULL, which means that the local device + * lacks the ability do display a passkey. If set to non-NULL the cancel + * callback must also be provided, since this is the only way the + * application can find out that it should stop displaying the passkey. + * + * @param conn Connection where pairing is currently active. + * @param passkey Passkey to show to the user. + */ + + void (*passkey_display)(struct bt_conn *conn, unsigned int passkey); + +#if defined(CONFIG_BT_PASSKEY_KEYPRESS) || defined(__DOXYGEN__) + /** @brief Receive Passkey Keypress Notification during pairing + * + * This allows the remote device to use the local device to give users + * feedback on the progress of entering the passkey over there. This is + * useful when the remote device itself has no display suitable for + * showing the progress. + * + * The handler of this callback is expected to keep track of the number of + * digits entered and show a password-field-like feedback to the user. + * + * This callback is only relevant while the local side does Passkey + * Display. + * + * The event type is verified to be in range of the enum. No other + * sanitization has been done. The remote could send a large number of + * events of any type in any order. + * + * @param conn The related connection. + * @param type Type of event. Verified in range of the enum. + */ + + void (*passkey_display_keypress)(struct bt_conn *conn, + enum bt_conn_auth_keypress type); +#endif /* CONFIG_BT_PASSKEY_KEYPRESS */ + + /** @brief Request the user to enter a passkey. + * + * When called the user is expected to enter a passkey. The passkey must + * be in the range of 0 - 999999, and should be expected to be + * zero-padded, as that's how the peer device will typically be showing it + * (e.g. 37 would be shown as 000037). + * + * Once the user has entered the passkey its value should be given to the + * stack using the bt_conn_auth_passkey_entry() API. + * + * This callback may be set to NULL, which means that the local device + * lacks the ability to enter a passkey. If set to non-NULL the cancel + * callback must also be provided, since this is the only way the + * application can find out that it should stop requesting the user to + * enter a passkey. + * + * @param conn Connection where pairing is currently active. + */ + + void (*passkey_entry)(struct bt_conn *conn); + + /** @brief Request the user to confirm a passkey. + * + * When called the user is expected to confirm that the given passkey is + * also shown on the peer device.. The passkey will be in the range of 0 - + * 999999, and should be zero-padded to always be six digits (e.g. 37 + * would be shown as 000037). + * + * Once the user has confirmed the passkey to match, the + * bt_conn_auth_passkey_confirm() API should be called. If the user + * concluded that the passkey doesn't match the bt_conn_auth_cancel() API + * should be called. + * + * This callback may be set to NULL, which means that the local device + * lacks the ability to confirm a passkey. If set to non-NULL the cancel + * callback must also be provided, since this is the only way the + * application can find out that it should stop requesting the user to + * confirm a passkey. + * + * @param conn Connection where pairing is currently active. + * @param passkey Passkey to be confirmed. + */ + + void (*passkey_confirm)(struct bt_conn *conn, unsigned int passkey); + + /** @brief Request the user to provide Out of Band (OOB) data. + * + * When called the user is expected to provide OOB data. The required data + * are indicated by the information structure. + * + * For LE Secure Connections OOB pairing, the user should provide local + * OOB data, remote OOB data or both depending on their availability. + * Their value should be given to the stack using the + * bt_le_oob_set_sc_data() API. + * + * This callback must be set to non-NULL in order to support OOB pairing. + * + * @param conn Connection where pairing is currently active. + * @param info OOB pairing information. + */ + + void (*oob_data_request)(struct bt_conn *conn, + struct bt_conn_oob_info *info); + + /** @brief Cancel the ongoing user request. + * + * This callback will be called to notify the application that it should + * cancel any previous user request (passkey display, entry or + * confirmation). + * + * This may be set to NULL, but must always be provided whenever the + * passkey_display, passkey_entry passkey_confirm or pairing_confirm + * callback has been provided. + * + * @param conn Connection where pairing is currently active. + */ + + void (*cancel)(struct bt_conn *conn); + + /** @brief Request confirmation for an incoming pairing. + * + * This callback will be called to confirm an incoming pairing request + * where none of the other user callbacks is applicable. + * + * If the user decides to accept the pairing the + * bt_conn_auth_pairing_confirm() API should be called. If the user + * decides to reject the pairing the bt_conn_auth_cancel() API should be + * called. + * + * This callback may be set to NULL, which means that the local device + * lacks the ability to confirm a pairing request. If set to non-NULL the + * cancel callback must also be provided, since this is the only way the + * application can find out that it should stop requesting the user to + * confirm a pairing request. + * + * @param conn Connection where pairing is currently active. + */ + + void (*pairing_confirm)(struct bt_conn *conn); + +#if defined(CONFIG_BT_CLASSIC) || defined(__DOXYGEN__) + /** @brief Request the user to enter a passkey. + * + * This callback will be called for a BR/EDR (Bluetooth Classic) + * connection where pairing is being performed. Once called the user is + * expected to enter a PIN code with a length between 1 and 16 digits. If + * the @a highsec parameter is set to true the PIN code must be 16 digits + * long. + * + * Once entered, the PIN code should be given to the stack using the + * bt_conn_auth_pincode_entry() API. + * + * This callback may be set to NULL, however in that case pairing over + * BR/EDR will not be possible. If provided, the cancel callback must be + * provided as well. + * + * @param conn Connection where pairing is currently active. + * @param highsec true if 16 digit PIN is required. + */ + + void (*pincode_entry)(struct bt_conn *conn, bool highsec); +#endif /* CONFIG_BT_CLASSIC */ + +#if defined(CONFIG_BT_APP_PASSKEY) || defined(__DOXYGEN__) + /** @brief Allow the application to provide a passkey for pairing. + * + * If implemented, this callback allows the application to provide + * passkeys for pairing. The valid range of passkeys is 0 - 999999. The + * application shall return the passkey for pairing, or BT_PASSKEY_RAND to + * generate a random passkey. This callback is invoked only for the + * Passkey Entry method as defined in Core Specification Vol. 3, Part H. + * Which device in the pairing is showing the passkey depends on the IO + * capabilities of the device; see Table 2.8 of the Bluetooth Core + * Specification V6.0, Vol. 3, Part H for more details. For the purposes + * of this table, the device gains the "display" capability when this + * callback is non-NULL. This is irrespective of whether the callback + * returns a specified key or BT_PASSKEY_RAND. + * + * + * @note When using this callback, it is the responsibility of the + * application to use random and unique keys. + * + * @param conn Connection where pairing is currently active. + * @return Passkey for pairing, or BT_PASSKEY_RAND for the Host to + * generate a random passkey. + */ + + uint32_t (*app_passkey)(struct bt_conn *conn); +#endif /* CONFIG_BT_APP_PASSKEY */ +}; + +/** Authenticated pairing information callback structure + * + * @note The callbacks are invoked from a thread context, never from an + * ISR. Whether a callback is invoked from a context internal to the stack + * or synchronously from within the API call that triggers it, and from + * which context, is not part of the API and may change between releases. + * See @rstref{Callback execution contexts } + * for the hazards of blocking in a callback and their mitigations. + */ + +struct bt_conn_auth_info_cb +{ + /** @brief notify that pairing procedure was complete. + * + * This callback notifies the application that the pairing procedure has + * been completed. + * + * @param conn Connection object. + * @param bonded Bond information has been distributed during the pairing + * procedure. + */ + + void (*pairing_complete)(struct bt_conn *conn, bool bonded); + + /** @brief notify that pairing process has failed. + * + * @param conn Connection object. + * @param reason Pairing failed reason + */ + + void (*pairing_failed)(struct bt_conn *conn, enum bt_security_err reason); + + /** @brief Notify that bond has been deleted. + * + * This callback notifies the application that the bond information for + * the remote peer has been deleted + * + * @param id Which local identity had the bond. + * @param peer Remote address. + */ + + void (*bond_deleted)(uint8_t id, const bt_addr_le_t *peer); + + /** @brief The peer's support for address resolution has been read. + * + * This callback notifies the application that the automatic read of a + * bonded peer's Central Address Resolution characteristic, enabled with + * @kconfig{CONFIG_BT_GATT_AUTO_READ_CENTRAL_ADDR_RES}, has finished. The + * answer is also available from bt_le_bond_addr_res_support(), and + * remains unknown when the read failed. + * + * @param conn Connection the characteristic was read on. + * @param support The peer's support for address resolution. + */ + + void (*addr_res_support_read)(struct bt_conn *conn, + enum bt_le_addr_res_support support); + +#if defined(CONFIG_BT_CLASSIC) || defined(__DOXYGEN__) + /** @brief Notify that bond of classic has been deleted. + * + * This callback notifies the application that the bond information of + * classic for the remote peer has been deleted + * + * @param peer Remote address. + */ + + void (*br_bond_deleted)(const bt_addr_t *peer); +#endif /* CONFIG_BT_CLASSIC */ + + /** Internally used field for list handling */ + + struct list_node node; +}; + +/** @brief Register authentication callbacks. + * + * Register callbacks to handle authenticated pairing. Passing NULL + * unregisters a previous callbacks structure. + * + * @param cb Callback struct. + * + * @return Zero on success or negative error code otherwise + */ + +int bt_conn_auth_cb_register(const struct bt_conn_auth_cb *cb); + +/** @brief Overlay authentication callbacks used for a given connection. + * + * This function can be used only for Bluetooth LE connections. The + * @kconfig{CONFIG_BT_SMP} must be enabled for this function. + * + * The authentication callbacks for a given connection cannot be overlaid if + * security procedures in the SMP module have already started. This function + * can be called only once per connection. + * + * @param conn @ref BT_CONN_TYPE_LE or @ref BT_CONN_TYPE_BR connection + * object. + * @param cb Callback struct. + * + * @return Zero on success or negative error code otherwise + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_LE or @ref + * BT_CONN_TYPE_BR connection + */ + +int bt_conn_auth_cb_overlay(struct bt_conn *conn, + const struct bt_conn_auth_cb *cb); + +/** @brief Register authentication information callbacks. + * + * Register callbacks to get authenticated pairing information. Multiple + * registrations can be done. + * + * @param cb Callback struct. + * + * @return Zero on success or negative error code otherwise + */ + +int bt_conn_auth_info_cb_register(struct bt_conn_auth_info_cb *cb); + +/** @brief Unregister authentication information callbacks. + * + * Unregister callbacks to stop getting authenticated pairing information. + * + * @param cb Callback struct. + * + * @return Zero on success or negative error code otherwise + */ + +int bt_conn_auth_info_cb_unregister(struct bt_conn_auth_info_cb *cb); + +/** @brief Reply with entered passkey. + * + * This function should be called only after passkey_entry callback from + * bt_conn_auth_cb structure was called. + * + * @param conn @ref BT_CONN_TYPE_LE or @ref BT_CONN_TYPE_BR connection + * object. + * @param passkey Entered passkey. + * + * @return Zero on success or negative error code otherwise + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_LE or @ref + * BT_CONN_TYPE_BR connection + */ + +int bt_conn_auth_passkey_entry(struct bt_conn *conn, unsigned int passkey); + +/** @brief Send Passkey Keypress Notification during pairing + * + * This function may be called only after passkey_entry callback from + * bt_conn_auth_cb structure was called. + * + * Requires @kconfig{CONFIG_BT_PASSKEY_KEYPRESS}. + * + * @param conn @ref BT_CONN_TYPE_LE destination connection for the + * notification. + * @param type What keypress event type to send. @see bt_conn_auth_keypress. + * + * @retval 0 Success + * @retval -EINVAL Improper use of the API. + * @retval -ENOMEM Failed to allocate. + * @retval -ENOBUFS Failed to allocate. + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_LE connection + */ + +int bt_conn_auth_keypress_notify(struct bt_conn *conn, + enum bt_conn_auth_keypress type); + +/** @brief Cancel ongoing authenticated pairing. + * + * This function allows to cancel ongoing authenticated pairing. + * + * @param conn @ref BT_CONN_TYPE_LE or @ref BT_CONN_TYPE_BR connection + * object. + * + * @return Zero on success or negative error code otherwise + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_LE or @ref + * BT_CONN_TYPE_BR connection + */ + +int bt_conn_auth_cancel(struct bt_conn *conn); + +/** @brief Reply if passkey was confirmed to match by user. + * + * This function should be called only after passkey_confirm callback from + * bt_conn_auth_cb structure was called. + * + * @param conn @ref BT_CONN_TYPE_LE or @ref BT_CONN_TYPE_BR connection + * object. + * + * @return Zero on success or negative error code otherwise + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_LE or @ref + * BT_CONN_TYPE_BR connection + */ + +int bt_conn_auth_passkey_confirm(struct bt_conn *conn); + +/** @brief Reply if incoming pairing was confirmed by user. + * + * This function should be called only after pairing_confirm callback from + * bt_conn_auth_cb structure was called if user confirmed incoming pairing. + * + * @param conn @ref BT_CONN_TYPE_LE or @ref BT_CONN_TYPE_BR connection + * object. + * + * @return Zero on success or negative error code otherwise + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_LE or @ref + * BT_CONN_TYPE_BR connection + */ + +int bt_conn_auth_pairing_confirm(struct bt_conn *conn); + +/** @brief Reply with entered PIN code. + * + * This function should be called only after PIN code callback from + * bt_conn_auth_cb structure was called. It's for legacy 2.0 devices. + * + * @param conn @ref BT_CONN_TYPE_BR connection object. + * @param pin Entered PIN code. + * + * @return Zero on success or negative error code otherwise + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_BR connection + */ + +int bt_conn_auth_pincode_entry(struct bt_conn *conn, const char *pin); + +/** Connection parameters for BR/EDR connections */ + +struct bt_br_conn_param +{ + bool allow_role_switch; +}; + +/** @brief Initialize BR/EDR connection parameters + * + * @param role_switch True if role switch is allowed + */ +#define BT_BR_CONN_PARAM_INIT(role_switch) \ +{ \ + .allow_role_switch = (role_switch), \ +} + +/** Helper to declare BR/EDR connection parameters inline + * + * @param role_switch True if role switch is allowed + */ +#define BT_BR_CONN_PARAM(role_switch) \ +((struct bt_br_conn_param[]){BT_BR_CONN_PARAM_INIT(role_switch)}) + +/** Default BR/EDR connection parameters: Role switch allowed */ +#define BT_BR_CONN_PARAM_DEFAULT BT_BR_CONN_PARAM(true) + +/** @brief Initiate an BR/EDR connection to a remote device. + * + * Allows initiate new BR/EDR link to remote peer using its address. + * + * The caller gets a new reference to the connection object which must be + * released with bt_conn_unref() once done using the object. + * + * @param peer Remote address. + * @param param Initial connection parameters. + * + * @return Valid connection object on success or NULL otherwise. + */ + +struct bt_conn *bt_conn_create_br(const bt_addr_t *peer, + const struct bt_br_conn_param *param); + +/** @brief Look up an existing BR connection by address. + * + * Look up an existing BR connection based on the remote address. + * + * The caller gets a new reference to the connection object which must be + * released with bt_conn_unref() once done using the object. + * + * @param peer Remote address. + * + * @return Connection object or NULL if not found. + */ + +struct bt_conn *bt_conn_lookup_addr_br(const bt_addr_t *peer); + +/** @brief Get destination (peer) address of a connection. + * + * @param conn Connection object. + * + * @return Destination address if @p conn is a valid @ref BT_CONN_TYPE_BR + * connection + */ + +const bt_addr_t *bt_conn_get_dst_br(const struct bt_conn *conn); + +/** @brief Change the role of the conn. + * + * @param conn Connection object. + * @param role The role that want to switch as, the value is @ref + * BT_HCI_ROLE_CENTRAL and + * @ref BT_HCI_ROLE_PERIPHERAL from hci_types.h. + * + * @return Zero on success or (negative) error code on failure. + * @return -ENOBUFS HCI command buffer is not available. + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_BR connection + */ + +int bt_conn_br_switch_role(const struct bt_conn *conn, uint8_t role); + +/** @brief Enable/disable role switch of the connection by setting the + * connection's link policy. + * + * @param conn Connection object. + * @param enable Value enable/disable role switch of controller. + * + * @return Zero on success or (negative) error code on failure. + * @return -ENOBUFS HCI command buffer is not available. + * @return -EINVAL @p conn is not a valid @ref BT_CONN_TYPE_BR connection. + */ + +int bt_conn_br_set_role_switch_enable(const struct bt_conn *conn, + bool enable); + +/** @brief bluetooth conn check and enter sniff mode + * + * This function is used to identify which ACL link connection is to be + * placed in Sniff mode + * + * @kconfig_dep{CONFIG_BT_POWER_MODE_CONTROL} + * + * @param conn bt_conn conn + * @param min_interval Minimum sniff interval. + * @param max_interval Maxmum sniff interval. + * @param attempt Number of Baseband receive slots for sniff attempt. + * @param timeout Number of Baseband receive slots for sniff timeout. + */ + +int bt_conn_br_enter_sniff_mode(struct bt_conn *conn, + uint16_t min_interval, + uint16_t max_interval, + uint16_t attempt, + uint16_t timeout); + +/** @brief bluetooth conn check and exit sniff mode + * + * @kconfig_dep{CONFIG_BT_POWER_MODE_CONTROL} + * + * @param conn bt_conn conn + * + * @return Zero for success, non-zero otherwise. + */ + +int bt_conn_br_exit_sniff_mode(struct bt_conn *conn); + +/** @brief Set BR/EDR sniff subrating parameters. + * + * Configure sniff subrating parameters for a BR/EDR connection. Sniff + * subrating allows further power savings by reducing the number of sniff + * anchor points the device needs to listen on. + * + * @kconfig_dep{CONFIG_BT_POWER_MODE_CONTROL} + * + * @param conn Connection object. + * @param max_latency Maximum allowed sniff subrate latency (in + * baseband slots of 0.625 ms). Range: 0x0002 to + * 0xFFFE. + * @param min_remote_timeout Minimum sniff mode timeout for remote device + * (in baseband slots of 0.625 ms). Range: 0x0000 + * to 0xFFFE. + * @param min_local_timeout Minimum sniff mode timeout for local device (in + * baseband slots of 0.625 ms). Range: 0x0000 to + * 0xFFFE. + * + * @return Zero for success, non-zero otherwise. + */ + +int bt_conn_br_set_sniff_subrating( + struct bt_conn *conn, uint16_t max_latency, uint16_t min_remote_timeout, + uint16_t min_local_timeout); + +/** @brief Read BR/EDR supervision timeout. + * + * Read the current link supervision timeout value for a BR/EDR connection. + * + * @param conn Connection object. + * @param timeout Pointer to store the supervision timeout value. + * + * @return Zero for success, non-zero otherwise. + */ + +int bt_conn_br_get_supervision_timeout(struct bt_conn *conn, + uint16_t *timeout); + +/** @brief Set BR/EDR supervision timeout. + * + * Each physical link has a timer that is used for link supervision. This + * timer is used to detect physical link loss caused by devices moving out + * of range, or being blocked by interference, a device's power-down, or + * other similar failure cases. + * + * @param conn Connection object. + * @param timeout Link supervision timeout, Range: 0x0001 to 0xFFFF Time + * Range: 0.625 ms to 40.9 s. + * + * @return Zero for success, non-zero otherwise. + */ + +int bt_conn_br_set_supervision_timeout(struct bt_conn *conn, + uint16_t timeout); + +/** @brief Change BR/EDR connection packet type. + * + * Change which packet types can be used for an established BR/EDR ACL + * connection. This allows dynamically modifying the connection to support + * different types of user data for throughput optimization. + * + * @param conn Connection object. + * @param packet_type Raw HCI BR/EDR ACL Packet_Type field composed of + * BT_HCI_ACL_PKT_TYPE_* values. For basic rate packet + * type bits, a set bit allows use of that packet type. + * For BT_HCI_ACL_PKT_TYPE_NO_* bits, a set bit means + * the corresponding EDR packet type shall not be used, + * as defined by the Bluetooth specification. + * + * @retval 0 On success. + * @retval -errno On failure. + */ + +int bt_conn_br_change_packet_type(const struct bt_conn *conn, + uint16_t packet_type); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_CONN_H */ diff --git a/include/nuttx/net/bluetooth/controller.h b/include/nuttx/net/bluetooth/controller.h new file mode 100644 index 0000000000000..7947dc0076430 --- /dev/null +++ b/include/nuttx/net/bluetooth/controller.h @@ -0,0 +1,53 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/controller.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Bluetooth subsystem controller APIs. + */ + +/* Copyright (c) 2018 Codecoup + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_CONTROLLER_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_CONTROLLER_H + +/** + * @brief Bluetooth Controller + * @defgroup bt_ctrl Bluetooth Controller + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** @brief Set public address for controller + * + * Should be called before bt_enable(). + * + * @param addr Public address + */ + +void bt_ctlr_set_public_addr(const uint8_t *addr); + +#ifdef __cplusplus +} +#endif +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_CONTROLLER_H */ diff --git a/include/nuttx/net/bluetooth/crypto.h b/include/nuttx/net/bluetooth/crypto.h new file mode 100644 index 0000000000000..b7fbe49c73273 --- /dev/null +++ b/include/nuttx/net/bluetooth/crypto.h @@ -0,0 +1,151 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/crypto.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Bluetooth subsystem crypto APIs. + */ + +/* Copyright (c) 2017-2020 Nordic Semiconductor ASA + * Copyright (c) 2015-2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_CRYPTO_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_CRYPTO_H + +/** + * @brief Cryptography + * @defgroup bt_crypto Cryptography + * @since 1.8 + * @version 1.0.0 + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** @brief Generate random data. + * + * A random number generation helper which utilizes the Bluetooth + * controller's own RNG. + * + * @param buf Buffer to insert the random data + * @param len Length of random data to generate + * + * @return Zero on success or error code otherwise, positive in case + * of protocol error or negative (POSIX) in case of stack internal error + */ + +int bt_rand(void *buf, size_t len); + +/** @brief AES encrypt little-endian data. + * + * An AES encrypt helper is used to request the Bluetooth controller's own + * hardware to encrypt the plaintext using the key and returns the encrypted + * data. + * + * @param key 128 bit LS byte first key for the encryption of the + * plaintext + * @param plaintext 128 bit LS byte first plaintext data block to be + * encrypted + * @param enc_data 128 bit LS byte first encrypted data block + * + * @return Zero on success or error code otherwise. + */ + +int bt_encrypt_le(const uint8_t key[16], + const uint8_t plaintext[16], uint8_t enc_data[16]); + +/** @brief AES encrypt big-endian data. + * + * An AES encrypt helper is used to request the Bluetooth controller's own + * hardware to encrypt the plaintext using the key and returns the encrypted + * data. + * + * @param key 128 bit MS byte first key for the encryption of the + * plaintext + * @param plaintext 128 bit MS byte first plaintext data block to be + * encrypted + * @param enc_data 128 bit MS byte first encrypted data block + * + * @return Zero on success or error code otherwise. + */ + +int bt_encrypt_be(const uint8_t key[16], + const uint8_t plaintext[16], uint8_t enc_data[16]); + +/** @brief Decrypt big-endian data with AES-CCM. + * + * Decrypts and authorizes @c enc_data with AES-CCM, as described in + * https://tools.ietf.org/html/rfc3610. + * + * Assumes that the MIC follows directly after the encrypted data. + * + * @param key 128 bit MS byte first key + * @param nonce 13 byte MS byte first nonce + * @param enc_data Encrypted data + * @param len Length of the encrypted data + * @param aad Additional authenticated data + * @param aad_len Additional authenticated data length + * @param plaintext Plaintext buffer to place result in + * @param mic_size Size of the trailing MIC (in bytes) + * + * @retval 0 Successfully decrypted the data. + * @retval -EINVAL Invalid parameters. + * @retval -EBADMSG Authentication failed. + */ + +int bt_ccm_decrypt( + const uint8_t key[16], uint8_t nonce[13], const uint8_t *enc_data, + size_t len, const uint8_t *aad, size_t aad_len, uint8_t *plaintext, + size_t mic_size); + +/** @brief Encrypt big-endian data with AES-CCM. + * + * Encrypts and generates a MIC from @c plaintext with AES-CCM, as described + * in + * https://tools.ietf.org/html/rfc3610. + * + * Places the MIC directly after the encrypted data. + * + * @param key 128 bit MS byte first key + * @param nonce 13 byte MS byte first nonce + * @param plaintext Plaintext buffer to encrypt + * @param len Length of the encrypted data + * @param aad Additional authenticated data + * @param aad_len Additional authenticated data length + * @param enc_data Buffer to place encrypted data in + * @param mic_size Size of the trailing MIC (in bytes) + * + * @retval 0 Successfully encrypted the data. + * @retval -EINVAL Invalid parameters. + */ + +int bt_ccm_encrypt( + const uint8_t key[16], uint8_t nonce[13], const uint8_t *plaintext, + size_t len, const uint8_t *aad, size_t aad_len, uint8_t *enc_data, + size_t mic_size); + +#ifdef __cplusplus +} +#endif +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_CRYPTO_H */ diff --git a/include/nuttx/net/bluetooth/cs.h b/include/nuttx/net/bluetooth/cs.h new file mode 100644 index 0000000000000..7c2967e0e1ef9 --- /dev/null +++ b/include/nuttx/net/bluetooth/cs.h @@ -0,0 +1,1272 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/cs.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Bluetooth Channel Sounding handling + */ + +/* Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_CS_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_CS_H + +/** + * @brief LE Channel Sounding (CS) + * @defgroup bt_le_cs Channel Sounding (CS) + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Macro for getting a specific channel bit in CS channel map + * + * @param[in] chmap Channel map array + * @param[in] bit Bit number to be accessed + * + * @return Bit value, either 1 or 0 + */ +#define BT_LE_CS_CHANNEL_BIT_GET(chmap, bit) (((chmap)[(bit) / 8] >> ((bit) % 8)) & 1) + +/** + * @brief Macro for setting a specific channel bit value in CS channel map + * + * @param[in] chmap Channel map array + * @param[in] bit Bit number to be accessed + * @param[in] val Bit value to be set, either 1 or 0 + */ +#define BT_LE_CS_CHANNEL_BIT_SET_VAL(chmap, bit, val) \ +((chmap)[(bit) / 8] = ((chmap)[(bit) / 8] & ~BIT((bit) % 8)) | ((val) << ((bit) % 8))) + +enum bt_le_cs_sync_antenna_selection_opt +{ + /** Use antenna identifier 1 for CS_SYNC packets. */ + + BT_LE_CS_ANTENNA_SELECTION_OPT_ONE = BT_HCI_OP_LE_CS_ANTENNA_SEL_ONE, + + /** Use antenna identifier 2 for CS_SYNC packets. */ + + BT_LE_CS_ANTENNA_SELECTION_OPT_TWO = BT_HCI_OP_LE_CS_ANTENNA_SEL_TWO, + + /** Use antenna identifier 3 for CS_SYNC packets. */ + + BT_LE_CS_ANTENNA_SELECTION_OPT_THREE = BT_HCI_OP_LE_CS_ANTENNA_SEL_THREE, + + /** Use antenna identifier 4 for CS_SYNC packets. */ + + BT_LE_CS_ANTENNA_SELECTION_OPT_FOUR = BT_HCI_OP_LE_CS_ANTENNA_SEL_FOUR, + + /** Use antennas in repetitive order from 1 to 4 for CS_SYNC packets. */ + + BT_LE_CS_ANTENNA_SELECTION_OPT_REPETITIVE = + BT_HCI_OP_LE_CS_ANTENNA_SEL_REP, + + /** No recommendation for local controller antenna selection. */ + + BT_LE_CS_ANTENNA_SELECTION_OPT_NO_RECOMMENDATION = + BT_HCI_OP_LE_CS_ANTENNA_SEL_NONE, +}; + +/** Default CS settings in the local Controller */ + +struct bt_le_cs_set_default_settings_param +{ + /** Enable CS initiator role. */ + + bool enable_initiator_role; + + /** Enable CS reflector role. */ + + bool enable_reflector_role; + /** Antenna identifier to be used for CS_SYNC packets by the local + * controller. + */ + + enum bt_le_cs_sync_antenna_selection_opt cs_sync_antenna_selection; + /** Maximum output power (Effective Isotropic Radiated Power) in dBm, + * to be used for all CS transmissions. + * + * Value range is @ref BT_HCI_OP_LE_CS_MIN_MAX_TX_POWER to + * @ref BT_HCI_OP_LE_CS_MAX_MAX_TX_POWER. + */ + + int8_t max_tx_power; +}; + +/** CS Test CS_SYNC Antenna Identifier */ + +enum bt_le_cs_test_cs_sync_antenna_selection +{ + BT_LE_CS_TEST_CS_SYNC_ANTENNA_SELECTION_ONE = + BT_HCI_OP_LE_CS_ANTENNA_SEL_ONE, + BT_LE_CS_TEST_CS_SYNC_ANTENNA_SELECTION_TWO = + BT_HCI_OP_LE_CS_ANTENNA_SEL_TWO, + BT_LE_CS_TEST_CS_SYNC_ANTENNA_SELECTION_THREE = + BT_HCI_OP_LE_CS_ANTENNA_SEL_THREE, + BT_LE_CS_TEST_CS_SYNC_ANTENNA_SELECTION_FOUR = + BT_HCI_OP_LE_CS_ANTENNA_SEL_FOUR, +}; + +/** CS SNR control options */ + +enum bt_le_cs_snr_control +{ + BT_LE_CS_SNR_CONTROL_18dB = BT_HCI_OP_LE_CS_SNR_18, + BT_LE_CS_SNR_CONTROL_21dB = BT_HCI_OP_LE_CS_SNR_21, + BT_LE_CS_SNR_CONTROL_24dB = BT_HCI_OP_LE_CS_SNR_24, + BT_LE_CS_SNR_CONTROL_27dB = BT_HCI_OP_LE_CS_SNR_27, + BT_LE_CS_SNR_CONTROL_30dB = BT_HCI_OP_LE_CS_SNR_30, + BT_LE_CS_SNR_CONTROL_NOT_USED = BT_HCI_OP_LE_CS_SNR_NOT_USED, +}; + +/** CS Test Override 3 T_PM Tone Extension */ + +enum bt_le_cs_test_override_3_pm_tone_ext +{ + /** Initiator and reflector tones sent without tone extension */ + + BT_LE_CS_TEST_OVERRIDE_3_NO_TONE_EXT = BT_HCI_OP_LE_CS_TEST_TONE_EXT_NONE, + + /** Initiator tone sent with extension, reflector tone sent without tone + * extension + */ + + BT_LE_CS_TEST_OVERRIDE_3_INITIATOR_TONE_EXT_ONLY = + BT_HCI_OP_LE_CS_TEST_TONE_EXT_INIT, + + /** Initiator tone sent without extension, reflector tone sent with tone + * extension + */ + + BT_LE_CS_TEST_OVERRIDE_3_REFLECTOR_TONE_EXT_ONLY = + BT_HCI_OP_LE_CS_TEST_TONE_EXT_REFL, + + /** Initiator and reflector tones sent with tone extension */ + + BT_LE_CS_TEST_OVERRIDE_3_INITIATOR_AND_REFLECTOR_TONE_EXT = + BT_HCI_OP_LE_CS_TEST_TONE_EXT_BOTH, + /** Applicable for mode-2 and mode-3 only: + * + * Loop through: + * - @ref BT_LE_CS_TEST_OVERRIDE_3_NO_TONE_EXT + * - @ref BT_LE_CS_TEST_OVERRIDE_3_INITIATOR_TONE_EXT_ONLY + * - @ref BT_LE_CS_TEST_OVERRIDE_3_REFLECTOR_TONE_EXT_ONLY + * - @ref BT_LE_CS_TEST_OVERRIDE_3_INITIATOR_AND_REFLECTOR_TONE_EXT + */ + + BT_LE_CS_TEST_OVERRIDE_3_REPETITIVE_TONE_EXT = + BT_HCI_OP_LE_CS_TEST_TONE_EXT_REPEAT, +}; + +/** CS Test Override 4 Tone Antenna Permutation. + * + * These values represent indices in an antenna path permutation table. + * + * Which table is applicable (and which indices are valid) depends on the + * maximum number of antenna paths (N_AP). + * + * If N_AP = 2, the permutation table is: + * + * +-------------------+------------------------+ + * | Antenna Path | Antenna Path Positions | + * | Permutation Index | After Permutation | + * +-------------------+------------------------+ + * | 0 | A1 A2 | + * | 1 | A2 A1 | + * +-------------------+------------------------+ + * + * If N_AP = 3, the permutation table is: + * + * +-------------------+------------------------+ + * | Antenna Path | Antenna Path Positions | + * | Permutation Index | After Permutation | + * +-------------------+------------------------+ + * | 0 | A1 A2 A3 | + * | 1 | A2 A1 A3 | + * | 2 | A1 A3 A2 | + * | 3 | A3 A1 A2 | + * | 4 | A3 A2 A1 | + * | 5 | A2 A3 A1 | + * +-------------------+------------------------+ + * + * If N_AP = 4, the permutation table is: + * + * +-------------------+------------------------+ + * | Antenna Path | Antenna Path Positions | + * | Permutation Index | After Permutation | + * +-------------------+------------------------+ + * | 0 | A1 A2 A3 A4 | + * | 1 | A2 A1 A3 A4 | + * | 2 | A1 A3 A2 A4 | + * | 3 | A3 A1 A2 A4 | + * | 4 | A3 A2 A1 A4 | + * | 5 | A2 A3 A1 A4 | + * | 6 | A1 A2 A4 A3 | + * | 7 | A2 A1 A4 A3 | + * | 8 | A1 A4 A2 A3 | + * | 9 | A4 A1 A2 A3 | + * | 10 | A4 A2 A1 A3 | + * | 11 | A2 A4 A1 A3 | + * | 12 | A1 A4 A3 A2 | + * | 13 | A4 A1 A3 A2 | + * | 14 | A1 A3 A4 A2 | + * | 15 | A3 A1 A4 A2 | + * | 16 | A3 A4 A1 A2 | + * | 17 | A4 A3 A1 A2 | + * | 18 | A4 A2 A3 A1 | + * | 19 | A2 A4 A3 A1 | + * | 20 | A4 A3 A2 A1 | + * | 21 | A3 A4 A2 A1 | + * | 22 | A3 A2 A4 A1 | + * | 23 | A2 A3 A4 A1 | + * +-------------------+------------------------+ + */ + +enum bt_le_cs_test_override_4_tone_antenna_permutation +{ + BT_LE_CS_TEST_OVERRIDE_4_ANTENNA_PERMUTATION_INDEX_00 = + BT_HCI_OP_LE_CS_TEST_AP_INDEX_00, + BT_LE_CS_TEST_OVERRIDE_4_ANTENNA_PERMUTATION_INDEX_01 = + BT_HCI_OP_LE_CS_TEST_AP_INDEX_01, + BT_LE_CS_TEST_OVERRIDE_4_ANTENNA_PERMUTATION_INDEX_02 = + BT_HCI_OP_LE_CS_TEST_AP_INDEX_02, + BT_LE_CS_TEST_OVERRIDE_4_ANTENNA_PERMUTATION_INDEX_03 = + BT_HCI_OP_LE_CS_TEST_AP_INDEX_03, + BT_LE_CS_TEST_OVERRIDE_4_ANTENNA_PERMUTATION_INDEX_04 = + BT_HCI_OP_LE_CS_TEST_AP_INDEX_04, + BT_LE_CS_TEST_OVERRIDE_4_ANTENNA_PERMUTATION_INDEX_05 = + BT_HCI_OP_LE_CS_TEST_AP_INDEX_05, + BT_LE_CS_TEST_OVERRIDE_4_ANTENNA_PERMUTATION_INDEX_06 = + BT_HCI_OP_LE_CS_TEST_AP_INDEX_06, + BT_LE_CS_TEST_OVERRIDE_4_ANTENNA_PERMUTATION_INDEX_07 = + BT_HCI_OP_LE_CS_TEST_AP_INDEX_07, + BT_LE_CS_TEST_OVERRIDE_4_ANTENNA_PERMUTATION_INDEX_08 = + BT_HCI_OP_LE_CS_TEST_AP_INDEX_08, + BT_LE_CS_TEST_OVERRIDE_4_ANTENNA_PERMUTATION_INDEX_09 = + BT_HCI_OP_LE_CS_TEST_AP_INDEX_09, + BT_LE_CS_TEST_OVERRIDE_4_ANTENNA_PERMUTATION_INDEX_10 = + BT_HCI_OP_LE_CS_TEST_AP_INDEX_10, + BT_LE_CS_TEST_OVERRIDE_4_ANTENNA_PERMUTATION_INDEX_11 = + BT_HCI_OP_LE_CS_TEST_AP_INDEX_11, + BT_LE_CS_TEST_OVERRIDE_4_ANTENNA_PERMUTATION_INDEX_12 = + BT_HCI_OP_LE_CS_TEST_AP_INDEX_12, + BT_LE_CS_TEST_OVERRIDE_4_ANTENNA_PERMUTATION_INDEX_13 = + BT_HCI_OP_LE_CS_TEST_AP_INDEX_13, + BT_LE_CS_TEST_OVERRIDE_4_ANTENNA_PERMUTATION_INDEX_14 = + BT_HCI_OP_LE_CS_TEST_AP_INDEX_14, + BT_LE_CS_TEST_OVERRIDE_4_ANTENNA_PERMUTATION_INDEX_15 = + BT_HCI_OP_LE_CS_TEST_AP_INDEX_15, + BT_LE_CS_TEST_OVERRIDE_4_ANTENNA_PERMUTATION_INDEX_16 = + BT_HCI_OP_LE_CS_TEST_AP_INDEX_16, + BT_LE_CS_TEST_OVERRIDE_4_ANTENNA_PERMUTATION_INDEX_17 = + BT_HCI_OP_LE_CS_TEST_AP_INDEX_17, + BT_LE_CS_TEST_OVERRIDE_4_ANTENNA_PERMUTATION_INDEX_18 = + BT_HCI_OP_LE_CS_TEST_AP_INDEX_18, + BT_LE_CS_TEST_OVERRIDE_4_ANTENNA_PERMUTATION_INDEX_19 = + BT_HCI_OP_LE_CS_TEST_AP_INDEX_19, + BT_LE_CS_TEST_OVERRIDE_4_ANTENNA_PERMUTATION_INDEX_20 = + BT_HCI_OP_LE_CS_TEST_AP_INDEX_20, + BT_LE_CS_TEST_OVERRIDE_4_ANTENNA_PERMUTATION_INDEX_21 = + BT_HCI_OP_LE_CS_TEST_AP_INDEX_21, + BT_LE_CS_TEST_OVERRIDE_4_ANTENNA_PERMUTATION_INDEX_22 = + BT_HCI_OP_LE_CS_TEST_AP_INDEX_22, + BT_LE_CS_TEST_OVERRIDE_4_ANTENNA_PERMUTATION_INDEX_23 = + BT_HCI_OP_LE_CS_TEST_AP_INDEX_23, + /** Loop through all valid Antenna Permutation Indices starting from the + * lowest index. + */ + + BT_LE_CS_TEST_OVERRIDE_4_ANTENNA_PERMUTATION_INDEX_LOOP = + BT_HCI_OP_LE_CS_TEST_AP_INDEX_LOOP, +}; + +/** CS Test Override 7 Sounding Sequence Marker Value */ + +enum bt_le_cs_test_override_7_ss_marker_value +{ + BT_LE_CS_TEST_OVERRIDE_7_SS_MARKER_VAL_0011 = + BT_HCI_OP_LE_CS_TEST_SS_MARKER_VAL_0011, + BT_LE_CS_TEST_OVERRIDE_7_SS_MARKER_VAL_1100 = + BT_HCI_OP_LE_CS_TEST_SS_MARKER_VAL_1100, + + /** Loop through pattern '0011' and '1100' (in transmission order) */ + + BT_LE_CS_TEST_OVERRIDE_7_SS_MARKER_VAL_LOOP = + BT_HCI_OP_LE_CS_TEST_SS_MARKER_VAL_LOOP, +}; + +/** CS Test Override 8 CS_SYNC Payload Pattern */ + +enum bt_le_cs_test_override_8_cs_sync_payload_pattern +{ + /** PRBS9 payload sequence. */ + + BT_LE_CS_TEST_OVERRIDE_8_PAYLOAD_PATTERN_PRBS9 = + BT_HCI_OP_LE_CS_TEST_PAYLOAD_PRBS9, + + /** Repeated '11110000' payload sequence. */ + + BT_LE_CS_TEST_OVERRIDE_8_PAYLOAD_PATTERN_11110000 = + BT_HCI_OP_LE_CS_TEST_PAYLOAD_11110000, + + /** Repeated '10101010' payload sequence. */ + + BT_LE_CS_TEST_OVERRIDE_8_PAYLOAD_PATTERN_10101010 = + BT_HCI_OP_LE_CS_TEST_PAYLOAD_10101010, + + /** PRBS15 payload sequence. */ + + BT_LE_CS_TEST_OVERRIDE_8_PAYLOAD_PATTERN_PRBS15 = + BT_HCI_OP_LE_CS_TEST_PAYLOAD_PRBS15, + + /** Repeated '11111111' payload sequence. */ + + BT_LE_CS_TEST_OVERRIDE_8_PAYLOAD_PATTERN_11111111 = + BT_HCI_OP_LE_CS_TEST_PAYLOAD_11111111, + + /** Repeated '00000000' payload sequence. */ + + BT_LE_CS_TEST_OVERRIDE_8_PAYLOAD_PATTERN_00000000 = + BT_HCI_OP_LE_CS_TEST_PAYLOAD_00000000, + + /** Repeated '00001111' payload sequence. */ + + BT_LE_CS_TEST_OVERRIDE_8_PAYLOAD_PATTERN_00001111 = + BT_HCI_OP_LE_CS_TEST_PAYLOAD_00001111, + + /** Repeated '01010101' payload sequence. */ + + BT_LE_CS_TEST_OVERRIDE_8_PAYLOAD_PATTERN_01010101 = + BT_HCI_OP_LE_CS_TEST_PAYLOAD_01010101, + + /** Custom payload provided by the user. */ + + BT_LE_CS_TEST_OVERRIDE_8_PAYLOAD_PATTERN_USER = + BT_HCI_OP_LE_CS_TEST_PAYLOAD_USER, +}; + +/** CS Test parameters */ + +struct bt_le_cs_test_param +{ + /** CS main and sub mode to be used during the CS procedure. */ + + enum bt_conn_le_cs_mode mode; + /** Number of main mode steps taken from the end of the last CS subevent + * to be repeated at the beginning of the current CS subevent directly + * after the last mode-0 step of that event. + */ + + uint8_t main_mode_repetition; + + /** Number of CS mode-0 steps at the beginning of the test CS subevent. */ + + uint8_t mode_0_steps; + + /** CS Test role */ + + enum bt_conn_le_cs_role role; + + /** RTT variant */ + + enum bt_conn_le_cs_rtt_type rtt_type; + + /** CS_SYNC PHY */ + + enum bt_conn_le_cs_sync_phy cs_sync_phy; + + /** Antenna identifier to be used for CS_SYNC packets. */ + + enum bt_le_cs_test_cs_sync_antenna_selection cs_sync_antenna_selection; + /** CS subevent length in microseconds. + * + * Range: 1250us to 4s + */ + + uint32_t subevent_len; + /** Gap between the start of two consecutive CS subevents (N * 0.625 ms) + * + * A value of 0 means that there is only one CS subevent. + */ + + uint16_t subevent_interval; + /** Maximum allowed number of subevents in the procedure. + * + * A value of 0 means that this parameter is ignored. + */ + + uint8_t max_num_subevents; + /** Desired TX power level for the CS procedure, in dBm. + * + * Value range is @ref BT_HCI_OP_LE_CS_MIN_MAX_TX_POWER to + * @ref BT_HCI_OP_LE_CS_MAX_MAX_TX_POWER. + * + * Special values: + * - @ref BT_HCI_OP_LE_CS_TEST_MAXIMIZE_TX_POWER tells the controller + * it should use as high a transmit power as possible + * - @ref BT_HCI_OP_LE_CS_TEST_MINIMIZE_TX_POWER tells the controller + * it should use as low a transmit power as possible + */ + + uint8_t transmit_power_level; + /** Interlude time in microseconds between the RTT packets. + * + * Valid options are: + * - 10 us + * - 20 us + * - 30 us + * - 40 us + * - 50 us + * - 60 us + * - 80 us + * - 145 us + */ + + uint8_t t_ip1_time; + /** Interlude time in microseconds between the CS tones. + * + * Valid options are: + * - 10 us + * - 20 us + * - 30 us + * - 40 us + * - 50 us + * - 60 us + * - 80 us + * - 145 us + */ + + uint8_t t_ip2_time; + /** Time in microseconds for frequency changes. + * + * Valid options are: + * - 15 us + * - 20 us + * - 30 us + * - 40 us + * - 50 us + * - 60 us + * - 80 us + * - 100 us + * - 120 us + * - 150 us + */ + + uint8_t t_fcs_time; + /** Time in microseconds for the phase measurement period of the CS tones. + * + * Valid options are: + * - 10 us + * - 20 us + * - 40 us + */ + + uint8_t t_pm_time; + /** Time in microseconds for the antenna switch period of the CS tones. + * + * Valid options are: + * - 0 us + * - 1 us + * - 2 us + * - 4 us + * - 10 us + */ + + uint8_t t_sw_time; + /** Antenna Configuration Index used during antenna switching during the + * tone phases of CS steps. + */ + + enum bt_conn_le_cs_tone_antenna_config_selection + tone_antenna_config_selection; + + /** Initiator SNR control options */ + + enum bt_le_cs_snr_control initiator_snr_control; + + /** Reflector SNR control options */ + + enum bt_le_cs_snr_control reflector_snr_control; + + /** Determines octets 14 and 15 of the initial value of the DRBG nonce. */ + + uint16_t drbg_nonce; + + /** Override configuration. + * + * This parameter is used to override CS parameters from the DRBG. Each + * bit configures a different set of parameters. + * + * All overrides are optional, except for those configured by bit 0. + * + * These are: + * - Bit 0 set: Override using list of channels + * - Bit 0 not set: Override using channel map + * - Bit 2 set: Override main mode steps + * - Bit 3 set: Override T_PM_Tone_Ext + * - Bit 4 set: Override tone antenna permutation + * - Bit 5 set: Override CS_SYNC AA + * - Bit 6 set: Override SS marker positions + * - Bit 7 set: Override SS marker value + * - Bit 8 set: Override CS_SYNC payload pattern and user payload + * - Bit 10 set: Procedure is replaced with a stable phase test + */ + + uint16_t override_config; + + /** override config bit 0. */ + + struct + { + /** Number of times the channels indicated by the channel map or + * channel field are cycled through for non-mode-0 steps within a CS + * procedure. + */ + + uint8_t channel_map_repetition; + union + { + struct + { + uint8_t num_channels; + uint8_t *channels; + } set; + struct + { + uint8_t channel_map[10]; + enum bt_conn_le_cs_chsel_type channel_selection_type; + enum bt_conn_le_cs_ch3c_shape ch3c_shape; + uint8_t ch3c_jump; + } not_set; + }; + } override_config_0; + + /** Override config bit 2. These parameters are ignored if the bit is not + * set. + */ + + struct + { + uint8_t main_mode_steps; + } override_config_2; + + /** Override config bit 3. These parameters are ignored if the bit is not + * set. + */ + + struct + { + enum bt_le_cs_test_override_3_pm_tone_ext t_pm_tone_ext; + } override_config_3; + + /** Override config bit 4. These parameters are ignored if the bit is not + * set. + */ + + struct + { + enum bt_le_cs_test_override_4_tone_antenna_permutation + tone_antenna_permutation; + } override_config_4; + + /** Override config bit 5. These parameters are ignored if the bit is not + * set. + */ + + struct + { + /** Access Address used in CS_SYNC packets sent by the initiator. */ + + uint32_t cs_sync_aa_initiator; + + /** Access Address used in CS_SYNC packets sent by the reflector. */ + + uint32_t cs_sync_aa_reflector; + } override_config_5; + + /** Override config bit 6. These parameters are ignored if the bit is not + * set. + */ + + struct + { + /** Bit number where the first marker in the channel sounding sequence + * starts. + * + * Must be between 0 and 28 when using @ref + * BT_CONN_LE_CS_RTT_TYPE_32_BIT_SOUNDING. + */ + + uint8_t ss_marker1_position; + /** Bit number where the second marker in the channel sounding sequence + * starts. + * + * Must be between 67 and 92 when using @ref + * BT_CONN_LE_CS_RTT_TYPE_96_BIT_SOUNDING. + * + * A value of @ref + * BT_HCI_OP_LE_CS_TEST_SS_MARKER_2_POSITION_NOT_PRESENT indicates + * that this sounding sequence or marker is not present. + */ + + uint8_t ss_marker2_position; + } override_config_6; + + /** Override config bit 7. These parameters are ignored if the bit is not + * set. + */ + + struct + { + /** Value of the Sounding Sequence marker. */ + + enum bt_le_cs_test_override_7_ss_marker_value ss_marker_value; + } override_config_7; + + /** Override config bit 8. These parameters are ignored if the bit is not + * set. + */ + + struct + { + /** CS_SYNC payload pattern selection. */ + + enum bt_le_cs_test_override_8_cs_sync_payload_pattern + cs_sync_payload_pattern; + /** User payload for CS_SYNC packets. + * + * This parameter is only used when using + * @ref BT_LE_CS_TEST_OVERRIDE_8_PAYLOAD_PATTERN_USER + * + * The least significant bit corresponds to the most significant bit + * of the CS payload. When the sequence is less than 16 octets, the + * least significant octets shall be padded with zeros. + */ + + uint8_t cs_sync_user_payload[16]; + } override_config_8; +}; + +/** CS config creation context */ + +enum bt_le_cs_create_config_context +{ + /** Write CS configuration in local Controller only */ + + BT_LE_CS_CREATE_CONFIG_CONTEXT_LOCAL_ONLY, + /** Write CS configuration in both local and remote Controller using + * Channel Sounding Configuration procedure + */ + + BT_LE_CS_CREATE_CONFIG_CONTEXT_LOCAL_AND_REMOTE +}; + +/** CS Create Config params */ + +struct bt_le_cs_create_config_params +{ + /** CS configuration ID */ + + uint8_t id; + + /** Main and sub CS mode */ + + enum bt_conn_le_cs_mode mode; + + /** Minimum number of CS main mode steps to be executed before a submode + * step is executed + */ + + uint8_t min_main_mode_steps; + + /** Maximum number of CS main mode steps to be executed before a submode + * step is executed + */ + + uint8_t max_main_mode_steps; + /** Number of main mode steps taken from the end of the last CS subevent to + * be repeated at the beginning of the current CS subevent directly after + * the last mode-0 step of that event + */ + + uint8_t main_mode_repetition; + + /** Number of CS mode-0 steps to be included at the beginning of each CS + * subevent + */ + + uint8_t mode_0_steps; + + /** CS role */ + + enum bt_conn_le_cs_role role; + + /** RTT type */ + + enum bt_conn_le_cs_rtt_type rtt_type; + + /** CS Sync PHY */ + + enum bt_conn_le_cs_sync_phy cs_sync_phy; + /** Channel map used for CS procedure + * Channels n = 0, 1, 23, 24, 25, 77, and 78 are not allowed and shall be + * set to zero. Channel 79 is reserved for future use and shall be set to + * zero. At least 15 channels shall be enabled. + */ + + uint8_t channel_map[10]; + /** The number of times the Channel_Map field will be cycled through for + * non-mode-0 steps within a CS procedure + */ + + uint8_t channel_map_repetition; + + /** Channel selection type */ + + enum bt_conn_le_cs_chsel_type channel_selection_type; + + /** User-specified channel sequence shape */ + + enum bt_conn_le_cs_ch3c_shape ch3c_shape; + + /** Number of channels skipped in each rising and falling sequence */ + + uint8_t ch3c_jump; + + /** CS enhancements 1 */ + + uint8_t cs_enhancements_1; +}; + +/** Callbacks for CS Test */ + +struct bt_le_cs_test_cb +{ + /**@brief CS Test Subevent data. + * + * @param[in] data Subevent results. + */ + + void (*le_cs_test_subevent_data_available)( + struct bt_conn_le_cs_subevent_result *data); + + /**@brief CS Test End Complete. */ + + void (*le_cs_test_end_complete)(void); +}; + +/** Subevent result step */ + +struct bt_le_cs_subevent_step +{ + /** CS step mode. */ + + uint8_t mode; + + /** CS step channel index. */ + + uint8_t channel; + + /** Length of role- and mode-specific information being reported. */ + + uint8_t data_len; + + /** Pointer to role- and mode-specific information. */ + + const uint8_t *data; +}; + +/** Sign-extended IQ value extracted from step data. */ + +struct bt_le_cs_iq_sample +{ + int16_t i; + int16_t q; +}; + +/** @brief Extract in-phase and quadrature terms from HCI-formatted PCT. + * + * Convenience function for processing 24-bit phase correction terms found in + * CS step data. The 12-bit signed real and imaginary components are + * converted to host endianness and sign-extended. + * + * @param pct 24-bit little-endian phase correction term. + * + * @return struct bt_le_cs_iq_sample containing real and imaginary terms as + * int16_t + */ + +struct bt_le_cs_iq_sample bt_le_cs_parse_pct(const uint8_t pct[3]); + +/** @brief Set all valid channel map bits + * + * This command is used to enable all valid channels in a given CS channel + * map + * + * @param channel_map Channel map + */ + +void bt_le_cs_set_valid_chmap_bits(uint8_t channel_map[10]); + +/** @brief Read Remote Supported Capabilities + * + * This command is used to query the CS capabilities that are supported by + * the remote controller. + * + * @note To use this API @kconfig{CONFIG_BT_CHANNEL_SOUNDING} must be set. + * + * @param conn Connection Object. + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_le_cs_read_remote_supported_capabilities(struct bt_conn *conn); + +/** @brief Set Channel Sounding default settings. + * + * This command is used to set default Channel Sounding settings for this + * connection. + * + * @note To use this API @kconfig{CONFIG_BT_CHANNEL_SOUNDING} must be set. + * + * @param conn Connection Object. + * @param params Channel sounding default settings parameters. + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_le_cs_set_default_settings(struct bt_conn *conn, + const struct bt_le_cs_set_default_settings_param *params); + +/** @brief Read Remote FAE Table + * + * This command is used to read the per-channel mode-0 Frequency Actuation + * Error table of the remote Controller. + * + * If the remote controller supports a Frequency Actuation Error of zero + * relative to its mode-0 transmissions in the reflector role (see @ref + * bt_conn_le_cs_capabilities::cs_without_fae_supported), calling this + * function is not needed and the read remote FAE table complete event will + * be generated with an error code. + * + * @note To use this API @kconfig{CONFIG_BT_CHANNEL_SOUNDING} must be set. + * + * @param conn Connection Object. + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_le_cs_read_remote_fae_table(struct bt_conn *conn); + +/** @brief Register callbacks for the CS Test mode. + * + * Existing callbacks can be unregistered by providing NULL function + * pointers. + * + * @note To use this API @kconfig{CONFIG_BT_CHANNEL_SOUNDING_TEST} must be + * set. + * + * @param cs_test_cb Set of callbacks to be used with CS Test + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_le_cs_test_cb_register(struct bt_le_cs_test_cb cs_test_cb); + +/** @brief Start a CS test + * + * This command is used to start a CS test where the IUT is placed in the + * role of either the initiator or reflector. + * + * The first mode-0 channel in the list is used as the starting channel for + * the test. At the beginning of any test, the IUT in the reflector role + * shall listen on the first mode-0 channel until it receives the first + * transmission from the initiator. Similarly, with the IUT in the initiator + * role, the tester will start by listening on the first mode-0 channel and + * the IUT shall transmit on that channel for the first half of the first CS + * step. Thereafter, the parameters of this command describe the required + * transmit and receive behavior for the CS test. + * + * @note To use this API @kconfig{CONFIG_BT_CHANNEL_SOUNDING_TEST} must be + * set. + * + * @param params CS Test parameters + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_le_cs_start_test(const struct bt_le_cs_test_param *params); + +/** @brief Create CS configuration + * + * This command is used to create a new CS configuration or update an + * existing one with the config id specified. + * + * @note To use this API @kconfig{CONFIG_BT_CHANNEL_SOUNDING} must be set. + * + * @param conn Connection Object. + * @param params CS Create Config parameters + * @param context Controls whether the configuration is written to the local + * controller or both the local and the remote controller + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_le_cs_create_config( + struct bt_conn *conn, struct bt_le_cs_create_config_params *params, + enum bt_le_cs_create_config_context context); + +/** @brief Remove CS configuration + * + * This command is used to remove a CS configuration from the local + * controller identified by the config_id + * + * @note To use this API @kconfig{CONFIG_BT_CHANNEL_SOUNDING} must be set. + * + * @param conn Connection Object. + * @param config_id CS Config ID + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_le_cs_remove_config(struct bt_conn *conn, uint8_t config_id); + +/** @brief Stop ongoing CS Test + * + * This command is used to stop any CS test that is in progress. + * + * The controller is expected to finish reporting any subevent results before + * completing this termination. + * + * @note To use this API @kconfig{CONFIG_BT_CHANNEL_SOUNDING_TEST} must be + * set. + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_le_cs_stop_test(void); + +/** @brief Parse CS Subevent Step Data + * + * A helper for parsing HCI-formatted step data found in channel sounding + * subevent results. + * + * A typical use-case is filtering out data which does not meet certain + * packet quality or NADM requirements. + * + * @warning This function will consume the data when parsing. + * + * @param step_data_buf Pointer to a buffer containing the step data. + * @param func Callback function which will be called for each step + * data found. The callback should return true to + * continue parsing, or false to stop. + * @param user_data User data to be passed to the callback. + */ + +void bt_le_cs_step_data_parse(struct net_buf_simple *step_data_buf, +bool (*func)(struct bt_le_cs_subevent_step *step, + void *user_data), + void *user_data); + +/** @brief CS Security Enable + * + * This command is used to start or restart the Channel Sounding Security + * Start procedure in the local Controller for the ACL connection identified + * in the conn parameter. + * + * @note To use this API @kconfig{CONFIG_BT_CHANNEL_SOUNDING} must be set. + * + * @param conn Connection Object. + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_le_cs_security_enable(struct bt_conn *conn); + +struct bt_le_cs_procedure_enable_param +{ + uint8_t config_id; + enum bt_conn_le_cs_procedure_enable_state enable; +}; + +/** @brief CS Procedure Enable + * + * This command is used to enable or disable the scheduling of CS procedures + * by the local Controller, with the remote device identified in the conn + * parameter. + * + * @note To use this API @kconfig{CONFIG_BT_CHANNEL_SOUNDING} must be set. + * + * @param conn Connection Object. + * @param params Parameters for the CS Procedure Enable command. + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_le_cs_procedure_enable(struct bt_conn *conn, + const struct bt_le_cs_procedure_enable_param *params); + +enum bt_le_cs_procedure_phy +{ + BT_LE_CS_PROCEDURE_PHY_1M = BT_HCI_OP_LE_CS_PROCEDURE_PHY_1M, + BT_LE_CS_PROCEDURE_PHY_2M = BT_HCI_OP_LE_CS_PROCEDURE_PHY_2M, + BT_LE_CS_PROCEDURE_PHY_CODED_S8 = BT_HCI_OP_LE_CS_PROCEDURE_PHY_CODED_S8, + BT_LE_CS_PROCEDURE_PHY_CODED_S2 = BT_HCI_OP_LE_CS_PROCEDURE_PHY_CODED_S2, +}; + +#define BT_LE_CS_PROCEDURE_PREFERRED_PEER_ANTENNA_1 BIT(0) +#define BT_LE_CS_PROCEDURE_PREFERRED_PEER_ANTENNA_2 BIT(1) +#define BT_LE_CS_PROCEDURE_PREFERRED_PEER_ANTENNA_3 BIT(2) +#define BT_LE_CS_PROCEDURE_PREFERRED_PEER_ANTENNA_4 BIT(3) + +struct bt_le_cs_set_procedure_parameters_param +{ + /* The ID associated with the desired configuration (0 to 3) */ + + uint8_t config_id; + + /* Max. duration for each CS procedure, where T = N * 0.625 ms (0x0001 to + * 0xFFFF) + */ + + uint16_t max_procedure_len; + + /* Min. number of connection events between consecutive CS procedures + * (0x0001 to 0xFFFF) + */ + + uint16_t min_procedure_interval; + + /* Max. number of connection events between consecutive CS procedures + * (0x0001 to 0xFFFF) + */ + + uint16_t max_procedure_interval; + + /* Max. number of procedures to be scheduled (0x0000 for no limit; + * otherwise 0x0001 to 0xFFFF) + */ + + uint16_t max_procedure_count; + + /* Min. suggested duration for each CS subevent in microseconds (1250 us to + * 4 s) + */ + + uint32_t min_subevent_len; + + /* Max. suggested duration for each CS subevent in microseconds (1250 us to + * 4 s) + */ + + uint32_t max_subevent_len; + + /* Antenna configuration index */ + + enum bt_conn_le_cs_tone_antenna_config_selection + tone_antenna_config_selection; + + /* Phy */ + + enum bt_le_cs_procedure_phy phy; + + /* Transmit power delta, in signed dB, to indicate the recommended + * difference between the remote device's power level for the CS tones and + * RTT packets and the existing power level for the Phy indicated by the + * Phy parameter (0x80 for no recommendation) + */ + + int8_t tx_power_delta; + + /* Preferred peer antenna (Bitmask of + * BT_LE_CS_PROCEDURE_PREFERRED_PEER_ANTENNA_*) + */ + + uint8_t preferred_peer_antenna; + + /* Initiator SNR control adjustment */ + + enum bt_le_cs_snr_control snr_control_initiator; + + /* Reflector SNR control adjustment */ + + enum bt_le_cs_snr_control snr_control_reflector; +}; + +/** @brief CS Set Procedure Parameters + * + * This command is used to set the parameters for the scheduling of one or + * more CS procedures by the local controller. + * + * @note To use this API @kconfig{CONFIG_BT_CHANNEL_SOUNDING} must be set. + * + * @param conn Connection Object. + * @param params Parameters for the CS Set Procedure Parameters command. + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_le_cs_set_procedure_parameters(struct bt_conn *conn, + const struct bt_le_cs_set_procedure_parameters_param *params); + +/** @brief CS Set Channel Classification + * + * This command is used to update the channel classification based on its + * local information. + * + * The nth bitfield (in the range 0 to 78) contains the value for the CS + * channel index n. Channel Enabled = 1; Channel Disabled = 0. + * + * Channels n = 0, 1, 23, 24, 25, 77, and 78 shall be reserved for future use + * and shall be set to zero. At least 15 channels shall be enabled. + * + * The most significant bit (bit 79) is reserved for future use. + * + * @note To use this API, @kconfig{CONFIG_BT_CHANNEL_SOUNDING} must be set. + * + * @param channel_classification Bit fields + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_le_cs_set_channel_classification(uint8_t channel_classification[10]); + +/** @brief CS Read Local Supported Capabilities + * + * This command is used to read the CS capabilities that are supported by + * the local Controller. + * + * @note To use this API @kconfig{CONFIG_BT_CHANNEL_SOUNDING} must be set. + * + * @param ret Return values for the CS Procedure Enable command. + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_le_cs_read_local_supported_capabilities( + struct bt_conn_le_cs_capabilities *ret); + +/** @brief CS Read Local Supported Capabilities V2 + * + * This command is used to read the CS capabilities that are supported by + * the local Controller. + * + * @note To use this API @kconfig{CONFIG_BT_CHANNEL_SOUNDING} must be set. + * + * @param ret Return values for the CS Procedure Enable command. + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_le_cs_read_local_supported_capabilities_v2( + struct bt_conn_le_cs_capabilities *ret); + +/** @brief CS Write Cached Remote Supported Capabilities + * + * This command is used to write the cached copy of the CS capabilities that + * are supported by the remote Controller for the connection identified. + * + * @note To use this API @kconfig{CONFIG_BT_CHANNEL_SOUNDING} must be set. + * + * @param conn Connection Object. + * @param params Parameters for the CS Write Cached Remote Supported + * Capabilities command. + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_le_cs_write_cached_remote_supported_capabilities(struct bt_conn *conn, + const struct bt_conn_le_cs_capabilities *params); + +/** @brief CS Write Cached Remote Supported Capabilities V2 + * + * This command is used to write the cached copy of the CS capabilities that + * are supported by the remote Controller for the connection identified. + * + * @note To use this API @kconfig{CONFIG_BT_CHANNEL_SOUNDING} must be set. + * + * @param conn Connection Object. + * @param params Parameters for the CS Write Cached Remote Supported + * Capabilities command. + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_le_cs_write_cached_remote_supported_capabilities_v2( + struct bt_conn *conn, const struct bt_conn_le_cs_capabilities *params); + +/** @brief CS Write Cached Remote FAE Table + * + * This command is used to write a cached copy of the per-channel mode-0 + * Frequency Actuation Error table of the remote device in the local + * Controller. + * + * If the remote controller supports a Frequency Actuation Error of zero + * relative to its mode-0 transmissions in the reflector role (see @ref + * bt_conn_le_cs_capabilities::cs_without_fae_supported), calling this + * function is not needed. An error code will be returned. + * + * @note To use this API @kconfig{CONFIG_BT_CHANNEL_SOUNDING} must be set. + * + * @param conn Connection Object. + * @param remote_fae_table Per-channel mode-0 FAE table of the local + * Controller + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_le_cs_write_cached_remote_fae_table(struct bt_conn *conn, + int8_t remote_fae_table[72]); + +/** @brief Get antenna path used for the CS tone exchange + * when using multiple antenna paths for mode-2 or mode-3 CS procedure. + * + * The function implements antenna path permutation defined in Bluetooth + * Core Specification 6.0, Vol. 6, Part H, Section 4.7.5. + * + * @note To use this API @kconfig{CONFIG_BT_CHANNEL_SOUNDING} must be set. + * + * @param n_ap The number of antenna paths, range: + * [1, 4]. + * @param antenna_path_permutation_index Antenna Path Permutation Index. + * @param tone_index Index of the tone in the CS step, + * range [0, n_ap]. tone_index = n_ap + * corresponds to extension slot. + * + * @return Antenna path used to exchange CS tones, range: [0, 3]. + * @return -EINVAL if arguments are invalid. + */ + +int bt_le_cs_get_antenna_path( + uint8_t n_ap, uint8_t antenna_path_permutation_index, uint8_t tone_index); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_CS_H */ diff --git a/include/nuttx/net/bluetooth/data.h b/include/nuttx/net/bluetooth/data.h new file mode 100644 index 0000000000000..1c5a794f8fdd9 --- /dev/null +++ b/include/nuttx/net/bluetooth/data.h @@ -0,0 +1,135 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/data.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Bluetooth data types and helpers. + */ + +/* Copyright (c) 2017 Nordic Semiconductor ASA + * Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_DATA_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_DATA_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Get the size of a serialized @ref bt_data given its data length. + * + * Size of 'AD Structure'->'Length' field, equal to 1. Size of 'AD + * Structure'->'Data'->'AD Type' field, equal to 1. Size of 'AD + * Structure'->'Data'->'AD Data' field, equal to data_len. + * + * See Core Specification Version 5.4 Vol. 3 Part C, 11, Figure 11.1. + */ +#define BT_DATA_SERIALIZED_SIZE(data_len) ((data_len) + 2) + +/** + * @brief Bluetooth data. + * + * @details Description of different AD Types that can be encoded into + * advertising data. Used to form arrays that are passed to the @ref + * bt_le_adv_start function. The @ref BT_DATA define can be used as a helpter + * to declare the elements of an @ref bt_data array. + */ + +struct bt_data +{ + /** Type of scan response data or advertisement data. */ + + uint8_t type; + + /** Length of scan response data or advertisement data. */ + + uint8_t data_len; + + /** Pointer to Scan response or advertisement data. */ + + const uint8_t *data; +}; + +/** + * @brief Helper to declare elements of bt_data arrays + * + * This macro is mainly for creating an array of struct bt_data elements + * which is then passed to e.g. @ref bt_le_adv_start function. + * + * @param _type Type of advertising data field + * @param _data Pointer to the data field payload + * @param _data_len Number of octets behind the _data pointer + */ +#define BT_DATA(_type, _data, _data_len) \ +{ \ + .type = (_type), \ + .data_len = (_data_len), \ + .data = (const uint8_t *)(_data), \ +} + +/** + * @brief Helper to declare elements of bt_data arrays + * + * This macro is mainly for creating an array of struct bt_data elements + * which is then passed to e.g. @ref bt_le_adv_start function. + * + * @param _type Type of advertising data field + * @param _bytes Variable number of single-byte parameters + */ +#define BT_DATA_BYTES(_type, _bytes...) \ +BT_DATA(_type, ((uint8_t[]){_bytes}), sizeof((uint8_t[]){_bytes})) + +/** + * @brief Get the total size (in octets) of a given set of @ref bt_data + * structures. + * + * The total size includes the length (1 octet) and type (1 octet) fields for + * each element, plus their respective data lengths. + * + * @param[in] data Array of @ref bt_data structures. + * @param[in] data_count Number of @ref bt_data structures in @p data. + * + * @return Size of the concatenated data, built from the @ref bt_data + * structure set. + */ + +size_t bt_data_get_len(const struct bt_data data[], size_t data_count); + +/** + * @brief Serialize a @ref bt_data struct into an advertising structure (a + * flat array). + * + * The data are formatted according to the Bluetooth Core Specification v. + * 5.4, vol. 3, part C, 11. + * + * @param[in] input Single @ref bt_data structure to read from. + * @param[out] output Buffer large enough to store the advertising structure + * in @p input. The size of it must be at least the size + * of the `input->data_len + 2` (for the type and the + * length). + * + * @return Number of octets written in @p output. + */ + +size_t bt_data_serialize(const struct bt_data *input, uint8_t *output); + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_DATA_H */ diff --git a/include/nuttx/net/bluetooth/direction.h b/include/nuttx/net/bluetooth/direction.h new file mode 100644 index 0000000000000..7246bc8b9670c --- /dev/null +++ b/include/nuttx/net/bluetooth/direction.h @@ -0,0 +1,533 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/direction.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_DIRECTION_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_DIRECTION_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +/** Constant Tone Extension (CTE) types. */ + +enum bt_df_cte_type +{ + /** Convenience value for purposes where non of CTE types is allowed. */ + + BT_DF_CTE_TYPE_NONE = 0, + + /** Angle of Arrival mode. Antenna switching done on receiver site. */ + + BT_DF_CTE_TYPE_AOA = BIT(0), + /* @brief Angle of Departure mode with 1 us antenna switching slots. + * + * Antenna switching done on transmitter site. + */ + + BT_DF_CTE_TYPE_AOD_1US = BIT(1), + /* @brief Angle of Departure mode with 2 us antenna switching slots. + * + * Antenna switching done on transmitter site. + */ + + BT_DF_CTE_TYPE_AOD_2US = BIT(2), + + /** Convenience value that collects all possible CTE types in one entry. */ + + BT_DF_CTE_TYPE_ALL = (BT_DF_CTE_TYPE_AOA | + BT_DF_CTE_TYPE_AOD_1US | + BT_DF_CTE_TYPE_AOD_2US) +}; + +/** Allowed antenna switching slots: 1 us or 2 us */ + +enum bt_df_antenna_switching_slot +{ + BT_DF_ANTENNA_SWITCHING_SLOT_1US = 0x1, + BT_DF_ANTENNA_SWITCHING_SLOT_2US = 0x2 +}; + +/** Possible statuses of PDU that contained reported CTE. */ + +enum bt_df_packet_status +{ + /** Received PDU had CRC OK */ + + BT_DF_CTE_CRC_OK = 0x0, + /** Received PDU had incorrect CRC, but Radio peripheral + * was able to parse CTEInfo field of the PDU and process sampling of CTE. + */ + + BT_DF_CTE_CRC_ERR_CTE_BASED_TIME = 0x1, + /** Received PDU had incorrect CRC, but Radio peripheral was able to + * process sampling of CTE in some other way. + */ + + BT_DF_CTE_CRC_ERR_CTE_BASED_OTHER = 0x2, + + /** There were no sufficient resources to sample CTE. */ + + BT_DF_CTE_INSUFFICIENT_RESOURCES = 0xff +}; + +/** @brief Constant Tone Extension parameters for connectionless + * transmission. + * + * The structure holds information required to setup CTE transmission in + * periodic advertising. + */ + +struct bt_df_adv_cte_tx_param +{ + /** Length of CTE in 8us units. */ + + uint8_t cte_len; + /* @brief CTE type. + * + * Allowed values are defined by @ref bt_df_cte_type, except + * BT_DF_CTE_TYPE_NONE and BT_DF_CTE_TYPE_ALL. + */ + + uint8_t cte_type; + + /** Number of CTE to transmit in each periodic adv interval. */ + + uint8_t cte_count; + + /** Number of Antenna IDs in the switch pattern. */ + + uint8_t num_ant_ids; + + /** List of antenna IDs in the pattern. */ + + uint8_t *ant_ids; +}; + +/** + * @brief Constant Tone Extension parameters for connectionless reception. + * + * @note cte_type is a bit field that provides information about type of CTE + * an application expects (@ref bt_df_cte_type). In case cte_type bit + * BT_DF_CTE_TYPE_AOA is not set, members: slot_durations, num_ant_ids and + * ant_ids are not required and their values will be not verified for + * correctness. + */ + +struct bt_df_per_adv_sync_cte_rx_param +{ + /* @brief Bitfield with allowed CTE types. + * + * Allowed values are defined by @ref bt_df_cte_type, except + * BT_DF_CTE_TYPE_NONE. + */ + + uint8_t cte_types; + + /** Antenna switching slots (@ref bt_df_antenna_switching_slot). */ + + uint8_t slot_durations; + + /** Max number of CTEs to receive. Min is 1, max is 10, 0 means receive + * continuously. + */ + + uint8_t max_cte_count; + + /** Length of antenna switch pattern. */ + + uint8_t num_ant_ids; + + /** Antenna switch pattern. */ + + const uint8_t *ant_ids; +}; + +enum bt_df_iq_sample +{ + /** Reported IQ samples have 8 bits signed integer format. Size defined in + * BT Core 5.3 Vol 4, Part E sections 7.7.65.21 and 7.7.65.22. + */ + + BT_DF_IQ_SAMPLE_8_BITS_INT, + + /** Reported IQ samples have 16 bits signed integer format. Vendor specific + * extension. + */ + + BT_DF_IQ_SAMPLE_16_BITS_INT, +}; + +struct bt_df_per_adv_sync_iq_samples_report +{ + /** Channel index used to receive PDU with CTE that was sampled. */ + + uint8_t chan_idx; + + /** The RSSI of the PDU with CTE (excluding CTE). Range: -1270 to +200. + * Units: 0.1 dBm. + */ + + int16_t rssi; + + /** Id of antenna used to measure the RSSI. */ + + uint8_t rssi_ant_id; + + /** Type of CTE (@ref bt_df_cte_type). */ + + uint8_t cte_type; + + /** Duration of slots when received CTE type is AoA (@ref + * bt_df_antenna_switching_slot). + */ + + uint8_t slot_durations; + + /** Status of received PDU with CTE (@ref bt_df_packet_status). */ + + uint8_t packet_status; + + /** Value of the paEventCounter of the AUX_SYNC_IND PDU. */ + + uint16_t per_evt_counter; + + /** Number of IQ samples in report. */ + + uint8_t sample_count; + + /** Type of IQ samples provided in a report. */ + + enum bt_df_iq_sample sample_type; + + /** Pointer to IQ samples data. */ + + union + { + struct bt_hci_le_iq_sample const *sample; + struct bt_hci_le_iq_sample16 const *sample16; + }; +}; + +struct bt_df_conn_cte_rx_param +{ + /* @brief Bitfield with allowed CTE types. + * + * Allowed values are defined by @ref bt_df_cte_type, except + * BT_DF_CTE_TYPE_NONE. + */ + + uint8_t cte_types; + + /** Antenna switching slots (@ref bt_df_antenna_switching_slot). */ + + uint8_t slot_durations; + + /** Length of antenna switch pattern. */ + + uint8_t num_ant_ids; + + /** Antenna switch pattern. */ + + const uint8_t *ant_ids; +}; + +enum bt_df_conn_iq_report_err +{ + /** IQ samples report received successfully. */ + + BT_DF_IQ_REPORT_ERR_SUCCESS, + + /** Received PDU without CTE. No valid data in report. */ + + BT_DF_IQ_REPORT_ERR_NO_CTE, + + /** Peer rejected CTE request. No valid data in report. */ + + BT_DF_IQ_REPORT_ERR_PEER_REJECTED, +}; + +struct bt_df_conn_iq_samples_report +{ + /** Report receive failed reason. */ + + enum bt_df_conn_iq_report_err err; + + /** PHY that was used to receive PDU with CTE that was sampled. */ + + uint8_t rx_phy; + + /** Channel index used to receive PDU with CTE that was sampled. */ + + uint8_t chan_idx; + + /** The RSSI of the PDU with CTE (excluding CTE). Range: -1270 to +200. + * Units: 0.1 dBm. + */ + + int16_t rssi; + + /** Id of antenna used to measure the RSSI. */ + + uint8_t rssi_ant_id; + + /** Type of CTE (@ref bt_df_cte_type). */ + + uint8_t cte_type; + + /** Duration of slots when received CTE type is AoA (@ref + * bt_df_antenna_switching_slot). + */ + + uint8_t slot_durations; + + /** Status of received PDU with CTE (@ref bt_df_packet_status). */ + + uint8_t packet_status; + + /** Value of connection event counter when the CTE was received and + * sampled. + */ + + uint16_t conn_evt_counter; + + /** Type of IQ samples provided in a report. */ + + enum bt_df_iq_sample sample_type; + + /** Number of IQ samples in report. */ + + uint8_t sample_count; + + /** Pointer to IQ samples data. */ + + union + { + struct bt_hci_le_iq_sample const *sample; + struct bt_hci_le_iq_sample16 const *sample16; + }; +}; + +/** Constant Tone Extension parameters for CTE transmission in connected + * mode. + */ + +struct bt_df_conn_cte_tx_param +{ + /** Bitfield with allowed CTE types (@ref bt_df_cte_type. All enum members + * may be used except BT_DF_CTE_TYPE_NONE). + */ + + uint8_t cte_types; + + /** Number of antenna switch pattern. */ + + uint8_t num_ant_ids; + + /** Antenna switch pattern. */ + + const uint8_t *ant_ids; +}; + +struct bt_df_conn_cte_req_params +{ + /* @brief Requested interval for initiating the CTE Request procedure. + * + * Value 0x0 means, run the procedure once. Other values are intervals in + * number of connection events, to run the command periodically. + */ + + uint16_t interval; + + /** Requested length of the CTE in 8 us units. */ + + uint8_t cte_length; + /* @brief Requested type of the CTE. + * + * Allowed values are defined by @ref bt_df_cte_type, except + * BT_DF_CTE_TYPE_NONE and BT_DF_CTE_TYPE_ALL. + */ + + uint8_t cte_type; +}; + +/** + * @brief Set or update the Constant Tone Extension parameters for periodic + * advertising set. + * + * @param[in] adv Advertising set object. + * @param[in] params Constant Tone Extension parameters. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_df_set_adv_cte_tx_param(struct bt_le_ext_adv *adv, + const struct bt_df_adv_cte_tx_param *params); + +/** + * @brief Enable transmission of Constant Tone Extension for the given + * advertising set. + * + * Transmission of Constant Tone Extension may be enabled only after setting + * periodic advertising parameters (@ref bt_le_per_adv_set_param) and + * Constant Tone Extension parameters (@ref bt_df_set_adv_cte_tx_param). + * + * @param[in] adv Advertising set object. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_df_adv_cte_tx_enable(struct bt_le_ext_adv *adv); + +/** + * @brief Disable transmission of Constant Tone Extension for the given + * advertising set. + * + * @param[in] adv Advertising set object. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_df_adv_cte_tx_disable(struct bt_le_ext_adv *adv); + +/** + * @brief Enable receive and sampling of Constant Tone Extension for the + * given sync set. + * + * Receive and sampling of Constant Tone Extension may be enabled only after + * periodic advertising sync is established. + * + * @param sync Periodic advertising sync object. + * @param params CTE receive and sampling parameters. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_df_per_adv_sync_cte_rx_enable(struct bt_le_per_adv_sync *sync, + const struct bt_df_per_adv_sync_cte_rx_param *params); + +/** + * @brief Disable receive and sampling of Constant Tone Extension for the + * given sync set. + * + * @param sync Periodic advertising sync object. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_df_per_adv_sync_cte_rx_disable(struct bt_le_per_adv_sync *sync); + +/** + * @brief Enable receive and sampling of Constant Tone Extension for the + * connection object. + * + * @param conn Connection object. + * @param params CTE receive and sampling parameters. + * + * @return Zero in case of success, other value in case of failure. + */ + +int bt_df_conn_cte_rx_enable(struct bt_conn *conn, + const struct bt_df_conn_cte_rx_param *params); + +/** + * @brief Disable receive and sampling of Constant Tone Extension for the + * connection object. + * + * @param conn Connection object. + * + * @return Zero in case of success, other value in case of failure. + */ + +int bt_df_conn_cte_rx_disable(struct bt_conn *conn); + +/** + * @brief Set Constant Tone Extension transmission parameters for a + * connection. + * + * The function is available if @kconfig{CONFIG_BT_DF_CONNECTION_CTE_TX} is + * enabled. + * + * @note If neither BT_DF_CTE_TYPE_AOD_1US or BT_DF_CTE_TYPE_AOD_2US are set + * in the bitfield, then the bt_df_conn_cte_tx_param.num_ant_ids and + * bt_df_conn_cte_tx_param.ant_ids parameters will be ignored. + * + * @param conn Connection object. + * @param params CTE transmission parameters. + * + * @return Zero in case of success, other value in case of failure. + */ + +int bt_df_set_conn_cte_tx_param(struct bt_conn *conn, + const struct bt_df_conn_cte_tx_param + *params); + +/** + * @brief Enable Constant Tone Extension request procedure for a connection. + * + * The function is available if @kconfig{CONFIG_BT_DF_CONNECTION_CTE_REQ} is + * enabled. + * + * @param conn Connection object. + * @param params CTE receive and sampling parameters. + * + * @return Zero in case of success, other value in case of failure. + */ + +int bt_df_conn_cte_req_enable(struct bt_conn *conn, + const struct bt_df_conn_cte_req_params + *params); + +/** + * @brief Disable Constant Tone Extension request procedure for a connection. + * + * The function is available if @kconfig{CONFIG_BT_DF_CONNECTION_CTE_REQ} is + * enabled. + * + * @param conn Connection object. + * + * @return Zero in case of success, other value in case of failure. + */ + +int bt_df_conn_cte_req_disable(struct bt_conn *conn); + +/** + * @brief Enable Constant Tone Extension response procedure for a connection. + * + * The function is available if @kconfig{CONFIG_BT_DF_CONNECTION_CTE_RSP} is + * enabled. + * + * @param conn Connection object. + * + * @return Zero in case of success, other value in case of failure. + */ + +int bt_df_conn_cte_rsp_enable(struct bt_conn *conn); + +/** + * @brief Disable Constant Tone Extension response procedure for a + * connection. + * + * The function is available if @kconfig{CONFIG_BT_DF_CONNECTION_CTE_RSP} is + * enabled. + * + * @param conn Connection object. + * + * @return Zero in case of success, other value in case of failure. + */ + +int bt_df_conn_cte_rsp_disable(struct bt_conn *conn); + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_DIRECTION_H */ diff --git a/include/nuttx/net/bluetooth/ead.h b/include/nuttx/net/bluetooth/ead.h new file mode 100644 index 0000000000000..56860c54b711d --- /dev/null +++ b/include/nuttx/net/bluetooth/ead.h @@ -0,0 +1,143 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/ead.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_EAD_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_EAD_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Encrypted Advertising Data (EAD) + * @defgroup bt_ead Encrypted Advertising Data (EAD) + * @ingroup bluetooth + * @{ + */ + +/** Randomizer size in bytes */ +#define BT_EAD_RANDOMIZER_SIZE 5 +/** Key size in bytes */ +#define BT_EAD_KEY_SIZE 16 +/** Initialisation Vector size in bytes */ +#define BT_EAD_IV_SIZE 8 +/** MIC size in bytes */ +#define BT_EAD_MIC_SIZE 4 + +/** Get the size (in bytes) of the encrypted advertising data for a given + * payload size in bytes. + */ +#define BT_EAD_ENCRYPTED_PAYLOAD_SIZE(payload_size) \ +((payload_size) + BT_EAD_RANDOMIZER_SIZE + BT_EAD_MIC_SIZE) + +/** Get the size (in bytes) of the decrypted payload for a given payload size + * in bytes. + */ +#define BT_EAD_DECRYPTED_PAYLOAD_SIZE(encrypted_payload_size) \ +((encrypted_payload_size) - (BT_EAD_RANDOMIZER_SIZE + BT_EAD_MIC_SIZE)) + +/** + * @brief Encrypt and authenticate the given advertising data. + * + * The resulting data in @p encrypted_payload will look like that: + * - Randomizer is added in the @ref BT_EAD_RANDOMIZER_SIZE first bytes; + * - Encrypted payload is added ( @p payload_size bytes); + * - MIC is added in the last @ref BT_EAD_MIC_SIZE bytes. + * + * @attention The function must be called each time the RPA is updated or the + * data are modified. + * + * @note The term `advertising structure` is used to describe the advertising + * data with the advertising type and the length of those two. + * + * @param[in] session_key Key of @ref BT_EAD_KEY_SIZE bytes used for + * the encryption. + * @param[in] iv Initialisation Vector used to generate the + * nonce. It must be changed each time the + * Session Key changes. + * @param[in] payload Advertising Data to encrypt. Can be multiple + * advertising structures that are + * concatenated. + * @param[in] payload_size Size of the Advertising Data to encrypt. + * @param[out] encrypted_payload Encrypted Ad Data including the Randomizer + * and the MIC. Size must be at least @ref + * BT_EAD_RANDOMIZER_SIZE + @p payload_size + + * @ref BT_EAD_MIC_SIZE. Use @ref + * BT_EAD_ENCRYPTED_PAYLOAD_SIZE to get the + * right size. + * + * @retval 0 Data have been correctly encrypted and authenticated. + * @retval -EIO Error occurred during the encryption or the authentication. + * @retval -EINVAL One of the argument is a NULL pointer. + * @retval -ECANCELED Error occurred during the random number generation. + */ + +int bt_ead_encrypt(const uint8_t session_key[BT_EAD_KEY_SIZE], + const uint8_t iv[BT_EAD_IV_SIZE], + const uint8_t *payload, + size_t payload_size, + uint8_t *encrypted_payload); + +/** + * @brief Decrypt and authenticate the given encrypted advertising data. + * + * @note The term `advertising structure` is used to describe the advertising + * data with the advertising type and the length of those two. + * + * @param[in] session_key Key of 16 bytes used for the encryption. + * @param[in] iv Initialisation Vector used to generate + * the `nonce`. + * @param[in] encrypted_payload Encrypted Advertising Data received. + * This should only contain the advertising + * data from the received advertising + * structure, not the length nor the type. + * @param[in] encrypted_payload_size Size of the received advertising data in + * bytes. Should be equal to the length + * field of the received advertising + * structure, minus the size of the type (1 + * byte). + * @param[out] payload Decrypted advertising payload. Use @ref + * BT_EAD_DECRYPTED_PAYLOAD_SIZE to get the + * right size. + * + * @retval 0 Data have been correctly decrypted and authenticated. + * @retval -EIO Error occurred during the decryption or the authentication. + * @retval -EINVAL One of the argument is a NULL pointer or @p + * encrypted_payload_size is less than @ref BT_EAD_RANDOMIZER_SIZE + @ref + * BT_EAD_MIC_SIZE. + */ + +int bt_ead_decrypt(const uint8_t session_key[BT_EAD_KEY_SIZE], + const uint8_t iv[BT_EAD_IV_SIZE], + const uint8_t *encrypted_payload, + size_t encrypted_payload_size, + uint8_t *payload); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_EAD_H */ diff --git a/include/nuttx/net/bluetooth/gap.h b/include/nuttx/net/bluetooth/gap.h new file mode 100644 index 0000000000000..276cb84fbed23 --- /dev/null +++ b/include/nuttx/net/bluetooth/gap.h @@ -0,0 +1,671 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/gap.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Bluetooth Generic Access Profile defines and Assigned Numbers. + */ + +/* Copyright (c) 2019 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_GAP_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_GAP_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Bluetooth Generic Access Profile defines and Assigned Numbers. + * @defgroup bt_gap_defines Defines and Assigned Numbers + * @ingroup bt_gap + * @{ + */ + +/** + * @name Company Identifiers (see Bluetooth Assigned Numbers) + * @{ + */ +#define BT_COMP_ID_LF 0x05f1 /**< The Linux Foundation */ + +/** + * @} + */ + +/** + * @name Defined GAP timers. + * As per Bluetooth Core Specification 6.2, Vol 3, Part C, Appendix A. @{ + */ + +/** Recommended minimum scan interval for fast scanning. 30 ms. */ +#define BT_GAP_SCAN_FAST_INTERVAL_MIN 0x0030 +/** Recommended maximum scan interval for fast scanning. 60 ms. */ +#define BT_GAP_SCAN_FAST_INTERVAL 0x0060 +/** Recommended scan window for fast scanning. 30 ms. */ +#define BT_GAP_SCAN_FAST_WINDOW 0x0030 +/** Recommended scan interval for slow scanning (mode 1). 1.28 s. */ +#define BT_GAP_SCAN_SLOW_INTERVAL_1 0x0800 +/** Recommended scan window for slow scanning (mode 1). 11.25 ms. */ +#define BT_GAP_SCAN_SLOW_WINDOW_1 0x0012 +/** Recommended scan interval for slow scanning (mode 2). 2.56 s. */ +#define BT_GAP_SCAN_SLOW_INTERVAL_2 0x1000 +/** Recommended scan window for slow scanning (mode 2). 11.25 ms. */ +#define BT_GAP_SCAN_SLOW_WINDOW_2 0x0012 +/** Recommended minimum advertising interval for fast advertising (mode 1). + * 30 ms. + */ +#define BT_GAP_ADV_FAST_INT_MIN_1 0x0030 +/** Recommended maximum advertising interval for fast advertising (mode 1). + * 60 ms. + */ +#define BT_GAP_ADV_FAST_INT_MAX_1 0x0060 +/** Recommended minimum advertising interval for fast advertising (mode 2). + * 100 ms. + */ +#define BT_GAP_ADV_FAST_INT_MIN_2 0x00a0 +/** Recommended maximum advertising interval for fast advertising (mode 2). + * 150 ms. + */ +#define BT_GAP_ADV_FAST_INT_MAX_2 0x00f0 +/** Recommended minimum advertising interval for slow advertising. 1 s. */ +#define BT_GAP_ADV_SLOW_INT_MIN 0x0640 +/** Recommended maximum advertising interval for slow advertising. 1.2 s. */ +#define BT_GAP_ADV_SLOW_INT_MAX 0x0780 +/** Recommended minimum periodic advertising interval for fast advertising + * (mode 1). 30 ms. + */ +#define BT_GAP_PER_ADV_FAST_INT_MIN_1 0x0018 +/** Recommended maximum periodic advertising interval for fast advertising + * (mode 1). 60 ms. + */ +#define BT_GAP_PER_ADV_FAST_INT_MAX_1 0x0030 +/** Recommended minimum periodic advertising interval for fast advertising + * (mode 2). 100 ms. + */ +#define BT_GAP_PER_ADV_FAST_INT_MIN_2 0x0050 +/** Recommended maximum periodic advertising interval for fast advertising + * (mode 2). 150 ms. + */ +#define BT_GAP_PER_ADV_FAST_INT_MAX_2 0x0078 +/** Recommended minimum periodic advertising interval for slow advertising. 1 + * s. + */ +#define BT_GAP_PER_ADV_SLOW_INT_MIN 0x0320 +/** Recommended maximum periodic advertising interval for slow advertising. + * 1.2 s. + */ +#define BT_GAP_PER_ADV_SLOW_INT_MAX 0x03c0 +/** Recommended minimum initial connection interval. 30 ms. */ +#define BT_GAP_INIT_CONN_INT_MIN 0x0018 +/** Recommended maximum initial connection interval. 50 ms. */ +#define BT_GAP_INIT_CONN_INT_MAX 0x0028 +/** + * @} + */ + +/** LE PHY types */ + +enum bt_gap_le_phy +{ + /** Convenience macro for when no PHY is set. */ + + BT_GAP_LE_PHY_NONE = 0, + + /** LE 1M PHY */ + + BT_GAP_LE_PHY_1M = BIT(0), + + /** LE 2M PHY */ + + BT_GAP_LE_PHY_2M = BIT(1), + + /** LE Coded PHY, coding scheme not specified */ + + BT_GAP_LE_PHY_CODED = BIT(2), + /** LE Coded S=8 PHY. Only used for advertising reports when Kconfig + * BT_EXT_ADV_CODING_SELECTION is enabled. + */ + + BT_GAP_LE_PHY_CODED_S8 = BIT(3), + /** LE Coded S=2 PHY. Only used for advertising reports when Kconfig + * BT_EXT_ADV_CODING_SELECTION is enabled. + */ + + BT_GAP_LE_PHY_CODED_S2 = BIT(4), +}; + +/** Advertising PDU types */ + +enum bt_gap_adv_type +{ + /** Scannable and connectable advertising. */ + + BT_GAP_ADV_TYPE_ADV_IND = 0x00, + + /** Directed connectable advertising. */ + + BT_GAP_ADV_TYPE_ADV_DIRECT_IND = 0x01, + + /** Non-connectable and scannable advertising. */ + + BT_GAP_ADV_TYPE_ADV_SCAN_IND = 0x02, + + /** Non-connectable and non-scannable advertising. */ + + BT_GAP_ADV_TYPE_ADV_NONCONN_IND = 0x03, + + /** Additional advertising data requested by an active scanner. */ + + BT_GAP_ADV_TYPE_SCAN_RSP = 0x04, + + /** Extended advertising, see advertising properties. */ + + BT_GAP_ADV_TYPE_EXT_ADV = 0x05, +}; + +/** Advertising PDU properties */ + +enum bt_gap_adv_prop +{ + /** Connectable advertising. */ + + BT_GAP_ADV_PROP_CONNECTABLE = BIT(0), + + /** Scannable advertising. */ + + BT_GAP_ADV_PROP_SCANNABLE = BIT(1), + + /** Directed advertising. */ + + BT_GAP_ADV_PROP_DIRECTED = BIT(2), + + /** Additional advertising data requested by an active scanner. */ + + BT_GAP_ADV_PROP_SCAN_RESPONSE = BIT(3), + + /** Extended advertising. */ + + BT_GAP_ADV_PROP_EXT_ADV = BIT(4), +}; + +/** Maximum advertising data length. */ +#define BT_GAP_ADV_MAX_ADV_DATA_LEN 31 +/** Maximum extended advertising data length. + * + * @note The maximum advertising data length that can be sent by an extended + * advertiser is defined by the controller. + */ +#define BT_GAP_ADV_MAX_EXT_ADV_DATA_LEN 1650 + +/** Sentinel value indicating that the TX power level in dBm is not available + * or unknown. + */ +#define BT_GAP_TX_POWER_INVALID 0x7f +/** Sentinel value indicating that the RSSI in dBm is not available or + * unknown. + */ +#define BT_GAP_RSSI_INVALID 0x7f +/** Sentinel value indicating that the Advertising Set Identifier is invalid + * or unknown. + */ +#define BT_GAP_SID_INVALID 0xff +/** Sentinel value indicating that no timeout is set. */ +#define BT_GAP_NO_TIMEOUT 0x0000 + +/* The maximum allowed high duty cycle directed advertising timeout, 1.28 + * seconds in 10 ms unit. + */ +#define BT_GAP_ADV_HIGH_DUTY_CYCLE_MAX_TIMEOUT 128 + +/** Default data length */ +#define BT_GAP_DATA_LEN_DEFAULT 0x001b /* 27 bytes */ + +/** Maximum data length */ +#define BT_GAP_DATA_LEN_MAX 0x00fb /* 251 bytes */ + +/** Default data time */ +#define BT_GAP_DATA_TIME_DEFAULT 0x0148 /* 328 us */ + +/** Maximum data time */ +#define BT_GAP_DATA_TIME_MAX 0x4290 /* 17040 us */ + +/** Minimum advertising set number */ +#define BT_GAP_SID_MIN 0x00 +/** Maximum advertising set number */ +#define BT_GAP_SID_MAX 0x0f +/** Maximum number of consecutive periodic advertisement events that can be + * skipped after a successful receive. + */ +#define BT_GAP_PER_ADV_MAX_SKIP 0x01f3 +/** Minimum Periodic Advertising Timeout (N * 10 ms) */ +#define BT_GAP_PER_ADV_MIN_TIMEOUT 0x000a /* 100 ms */ + +/** Maximum Periodic Advertising Timeout (N * 10 ms) */ +#define BT_GAP_PER_ADV_MAX_TIMEOUT 0x4000 /* 163.84 s */ + +/** Minimum Periodic Advertising Interval (N * 1.25 ms) */ +#define BT_GAP_PER_ADV_MIN_INTERVAL 0x0006 /* 7.5 ms */ + +/** Maximum Periodic Advertising Interval (N * 1.25 ms) */ +#define BT_GAP_PER_ADV_MAX_INTERVAL 0xffff /* 81.91875 s */ + +/** + * @brief Convert advertising interval (N * 0.625 ms) to microseconds + * + * Value range of @p _interval is @ref BT_LE_ADV_INTERVAL_MIN to @ref + * BT_LE_ADV_INTERVAL_MAX + */ +#define BT_GAP_ADV_INTERVAL_TO_US(_interval) ((uint32_t)((_interval) * 625U)) + +/** + * @brief Convert advertising interval (N * 0.625 ms) to milliseconds + * + * Value range of @p _interval is @ref BT_LE_ADV_INTERVAL_MIN to @ref + * BT_LE_ADV_INTERVAL_MAX + * + * @note When intervals cannot be represented in milliseconds, this will + * round down. For example BT_GAP_ADV_INTERVAL_TO_MS(0x0021) will become 20 + * ms instead of 20.625 ms + */ +#define BT_GAP_ADV_INTERVAL_TO_MS(_interval) (BT_GAP_ADV_INTERVAL_TO_US(_interval) / USEC_PER_MSEC) + +/** + * @brief Convert isochronous interval (N * 1.25 ms) to microseconds + * + * Value range of @p _interval is @ref BT_HCI_ISO_INTERVAL_MIN to @ref + * BT_HCI_ISO_INTERVAL_MAX + */ +#define BT_GAP_ISO_INTERVAL_TO_US(_interval) ((uint32_t)((_interval) * 1250U)) + +/** + * @brief Convert isochronous interval (N * 1.25 ms) to milliseconds + * + * Value range of @p _interval is @ref BT_HCI_ISO_INTERVAL_MIN to @ref + * BT_HCI_ISO_INTERVAL_MAX + * + * @note When intervals cannot be represented in milliseconds, this will + * round down. For example BT_GAP_ISO_INTERVAL_TO_MS(0x0005) will become 6 ms + * instead of 6.25 ms + */ +#define BT_GAP_ISO_INTERVAL_TO_MS(_interval) (BT_GAP_ISO_INTERVAL_TO_US(_interval) / USEC_PER_MSEC) + +/** @brief Convert periodic advertising interval (N * 1.25 ms) to + * microseconds * + * + * Value range of @p _interval is @ref BT_HCI_LE_PER_ADV_INTERVAL_MIN to @ref + * BT_HCI_LE_PER_ADV_INTERVAL_MAX + */ +#define BT_GAP_PER_ADV_INTERVAL_TO_US(_interval) ((uint32_t)((_interval) * 1250U)) + +/** + * @brief Convert periodic advertising interval (N * 1.25 ms) to milliseconds + * + * @note When intervals cannot be represented in milliseconds, this will + * round down. For example BT_GAP_PER_ADV_INTERVAL_TO_MS(0x0009) will become + * 11 ms instead of 11.25 ms + */ +#define BT_GAP_PER_ADV_INTERVAL_TO_MS(_interval) \ +(BT_GAP_PER_ADV_INTERVAL_TO_US(_interval) / USEC_PER_MSEC) + +/** + * @brief Convert microseconds to advertising interval units (0.625 ms) + * + * Value range of @p _interval is 20000 to 1024000 + * + * @note If @p _interval is not a multiple of the unit, it will round down to + * nearest. For example BT_GAP_US_TO_ADV_INTERVAL(21000) will become 20625 + * microseconds + */ +#define BT_GAP_US_TO_ADV_INTERVAL(_interval) ((uint16_t)((_interval) / 625U)) + +/** + * @brief Convert milliseconds to advertising interval units (0.625 ms) + * + * Value range of @p _interval is 20 to 1024 + * + * @note If @p _interval is not a multiple of the unit, it will round down to + * nearest. For example BT_GAP_MS_TO_ADV_INTERVAL(21) will become 20.625 + * milliseconds + */ +#define BT_GAP_MS_TO_ADV_INTERVAL(_interval) \ +(BT_GAP_US_TO_ADV_INTERVAL((_interval) * USEC_PER_MSEC)) + +/** + * @brief Convert microseconds to periodic advertising interval units (1.25 + * ms) + * + * Value range of @p _interval is 7500 to 81918750 + * + * @note If @p _interval is not a multiple of the unit, it will round down to + * nearest. For example BT_GAP_US_TO_PER_ADV_INTERVAL(11000) will become + * 10000 microseconds + */ +#define BT_GAP_US_TO_PER_ADV_INTERVAL(_interval) ((uint16_t)((_interval) / 1250U)) + +/** + * @brief Convert milliseconds to periodic advertising interval units (1.25 + * ms) + * + * Value range of @p _interval is 7.5 to 81918.75 + * + * @note If @p _interval is not a multiple of the unit, it will round down to + * nearest. For example BT_GAP_MS_TO_PER_ADV_INTERVAL(11) will become 10 + * milliseconds + */ +#define BT_GAP_MS_TO_PER_ADV_INTERVAL(_interval) \ +(BT_GAP_US_TO_PER_ADV_INTERVAL((_interval) * USEC_PER_MSEC)) + +/** + * @brief Convert milliseconds to periodic advertising sync timeout units (10 + * ms) + * + * Value range of @p _timeout is 100 to 163840 + * + * @note If @p _timeout is not a multiple of the unit, it will round down to + * nearest. For example BT_GAP_MS_TO_PER_ADV_SYNC_TIMEOUT(4005) will become + * 4000 milliseconds + */ +#define BT_GAP_MS_TO_PER_ADV_SYNC_TIMEOUT(_timeout) ((uint16_t)((_timeout) / 10U)) + +/** + * @brief Convert microseconds to periodic advertising sync timeout units (10 + * ms) + * + * Value range of @p _timeout is 100000 to 163840000 + * + * @note If @p _timeout is not a multiple of the unit, it will round down to + * nearest. For example BT_GAP_US_TO_PER_ADV_SYNC_TIMEOUT(4005000) will + * become 4000000 microseconds + */ +#define BT_GAP_US_TO_PER_ADV_SYNC_TIMEOUT(_timeout) \ +(BT_GAP_MS_TO_PER_ADV_SYNC_TIMEOUT((_timeout) / USEC_PER_MSEC)) + +/** + * @brief Convert microseconds to scan interval units (0.625 ms) + * + * Value range of @p _interval is 2500 to 40959375 if + * @kconfig{CONFIG_BT_EXT_ADV} else 2500 to 10240000 + * + * @note If @p _interval is not a multiple of the unit, it will round down to + * nearest. For example BT_GAP_US_TO_SCAN_INTERVAL(21000) will become 20625 + * microseconds + */ +#define BT_GAP_US_TO_SCAN_INTERVAL(_interval) ((uint16_t)((_interval) / 625U)) + +/** + * @brief Convert milliseconds to scan interval units (0.625 ms) + * + * Value range of @p _interval is 2.5 to 40959.375 if + * @kconfig{CONFIG_BT_EXT_ADV} else 2500 to 10240 + * + * @note If @p _interval is not a multiple of the unit, it will round down to + * nearest. For example BT_GAP_MS_TO_SCAN_INTERVAL(21) will become 20.625 + * milliseconds + */ +#define BT_GAP_MS_TO_SCAN_INTERVAL(_interval) \ +(BT_GAP_US_TO_SCAN_INTERVAL((_interval) * USEC_PER_MSEC)) + +/** + * @brief Convert microseconds to scan window units (0.625 ms) + * + * Value range of @p _window is 2500 to 40959375 if + * @kconfig{CONFIG_BT_EXT_ADV} else 2500 to 10240000 + * + * @note If @p _window is not a multiple of the unit, it will round down to + * nearest. For example BT_GAP_US_TO_SCAN_WINDOW(21000) will become 20625 + * microseconds + */ +#define BT_GAP_US_TO_SCAN_WINDOW(_window) ((uint16_t)((_window) / 625U)) + +/** + * @brief Convert milliseconds to scan window units (0.625 ms) + * + * Value range of @p _window is 2.5 to 40959.375 if + * @kconfig{CONFIG_BT_EXT_ADV} else 2500 to 10240 + * + * @note If @p _window is not a multiple of the unit, it will round down to + * nearest. For example BT_GAP_MS_TO_SCAN_WINDOW(21) will become 20.625 + * milliseconds + */ +#define BT_GAP_MS_TO_SCAN_WINDOW(_window) (BT_GAP_US_TO_SCAN_WINDOW((_window) * USEC_PER_MSEC)) + +/** + * @brief Convert microseconds to connection interval units (1.25 ms) + * + * Value range of @p _interval is 7500 to 4000000 + * + * @note If @p _interval is not a multiple of the unit, it will round down to + * nearest. For example BT_GAP_US_TO_CONN_INTERVAL(21000) will become 20000 + * microseconds + */ +#define BT_GAP_US_TO_CONN_INTERVAL(_interval) ((uint16_t)((_interval) / 1250U)) + +/** + * @brief Convert milliseconds to connection interval units (1.25 ms) + * + * Value range of @p _interval is 7.5 to 4000 + * + * @note If @p _interval is not a multiple of the unit, it will round down to + * nearest. For example BT_GAP_MS_TO_CONN_INTERVAL(21) will become 20 + * milliseconds + */ +#define BT_GAP_MS_TO_CONN_INTERVAL(_interval) \ +(BT_GAP_US_TO_CONN_INTERVAL((_interval) * USEC_PER_MSEC)) + +/** + * @brief Convert milliseconds to connection supervision timeout units (10 + * ms) + * + * Value range of @p _timeout is 100 to 32000 + * + * @note If @p _timeout is not a multiple of the unit, it will round down to + * nearest. For example BT_GAP_MS_TO_CONN_TIMEOUT(4005) will become 4000 + * milliseconds + */ +#define BT_GAP_MS_TO_CONN_TIMEOUT(_timeout) ((uint16_t)((_timeout) / 10U)) + +/** + * @brief Convert microseconds to connection supervision timeout units (10 + * ms) + + * Value range of @p _timeout is 100000 to 32000000 + * + * @note If @p _timeout is not a multiple of the unit, it will round down to + * nearest. For example BT_GAP_MS_TO_CONN_TIMEOUT(4005000) will become + * 4000000 microseconds + */ +#define BT_GAP_US_TO_CONN_TIMEOUT(_timeout) (BT_GAP_MS_TO_CONN_TIMEOUT((_timeout) / USEC_PER_MSEC)) + +/** + * @brief Convert milliseconds to connection event length units (0.625) + * + * Value range of @p _event_len is 0 to 40959375 + * + * @note If @p _event_len is not a multiple of the unit, it will round down + * to nearest. For example BT_GAP_US_TO_CONN_EVENT_LEN(21000) will become + * 20625 milliseconds + */ +#define BT_GAP_US_TO_CONN_EVENT_LEN(_event_len) ((uint16_t)((_event_len) / 625U)) + +/** + * @brief Convert milliseconds to connection event length units (0.625) + * + * Value range of @p _event_len is 0 to 40959.375 + * + * @note If @p _event_len is not a multiple of the unit, it will round down + * to nearest. For example BT_GAP_MS_TO_CONN_EVENT_LEN(21) will become 20.625 + * milliseconds + */ +#define BT_GAP_MS_TO_CONN_EVENT_LEN(_event_len) \ +(BT_GAP_US_TO_CONN_EVENT_LEN((_event_len) * USEC_PER_MSEC)) + +/** Constant Tone Extension (CTE) types */ + +enum bt_gap_cte +{ + /** Angle of Arrival */ + + BT_GAP_CTE_AOA = 0x00, + + /** Angle of Departure with 1 us slots */ + + BT_GAP_CTE_AOD_1US = 0x01, + + /** Angle of Departure with 2 us slots */ + + BT_GAP_CTE_AOD_2US = 0x02, + + /** No extensions */ + + BT_GAP_CTE_NONE = 0xff, +}; + +/** @brief Peripheral sleep clock accuracy (SCA) in ppm (parts per million) */ + +enum bt_gap_sca +{ + BT_GAP_SCA_UNKNOWN = 0, /**< Unknown */ + BT_GAP_SCA_251_500 = 0, /**< 251 ppm to 500 ppm */ + BT_GAP_SCA_151_250 = 1, /**< 151 ppm to 250 ppm */ + BT_GAP_SCA_101_150 = 2, /**< 101 ppm to 150 ppm */ + BT_GAP_SCA_76_100 = 3, /**< 76 ppm to 100 ppm */ + BT_GAP_SCA_51_75 = 4, /**< 51 ppm to 75 ppm */ + BT_GAP_SCA_31_50 = 5, /**< 31 ppm to 50 ppm */ + BT_GAP_SCA_21_30 = 6, /**< 21 ppm to 30 ppm */ + BT_GAP_SCA_0_20 = 7, /**< 0 ppm to 20 ppm */ +}; + +/** + * @brief Encode 40 least significant bits of 64-bit LE Supported Features + * into array values in little-endian format. + * + * Helper macro to encode 40 least significant bits of 64-bit LE Supported + * Features value into advertising data. The number of bits that are encoded + * is a number of LE Supported Features defined by BT 5.3 Core specification. + * + * Example of how to encode the `0x000000dff00df00d` into advertising data. + * + * @code BT_DATA_BYTES(BT_DATA_LE_SUPPORTED_FEATURES, + * BT_LE_SUPP_FEAT_40_ENCODE(0x000000dff00df00d)) @endcode + * + * @param w64 LE Supported Features value (64-bits) + * + * @return The comma separated values for LE Supported Features value that + * may be used directly as an argument for @ref BT_DATA_BYTES. + */ +#define BT_LE_SUPP_FEAT_40_ENCODE(w64) BT_BYTES_LIST_LE40(w64) + +/** @brief Encode 4 least significant bytes of 64-bit LE Supported Features + * into 4 bytes long array of values in little-endian format. + * + * Helper macro to encode 64-bit LE Supported Features value into + * advertising data. The macro encodes 4 least significant bytes into + * advertising data. Other 4 bytes are not encoded. + * + * Example of how to encode the `0x000000dff00df00d` into advertising data. + * + * @code BT_DATA_BYTES(BT_DATA_LE_SUPPORTED_FEATURES, + * BT_LE_SUPP_FEAT_32_ENCODE(0x000000dff00df00d)) @endcode + * + * @param w64 LE Supported Features value (64-bits) + * + * @return The comma separated values for LE Supported Features value that + * may be used directly as an argument for @ref BT_DATA_BYTES. + */ +#define BT_LE_SUPP_FEAT_32_ENCODE(w64) BT_BYTES_LIST_LE32(w64) + +/** + * @brief Encode 3 least significant bytes of 64-bit LE Supported Features + * into 3 bytes long array of values in little-endian format. + * + * Helper macro to encode 64-bit LE Supported Features value into advertising + * data. The macro encodes 3 least significant bytes into advertising data. + * Other 5 bytes are not encoded. + * + * Example of how to encode the `0x000000dff00df00d` into advertising data. + * + * @code BT_DATA_BYTES(BT_DATA_LE_SUPPORTED_FEATURES, + * BT_LE_SUPP_FEAT_24_ENCODE(0x000000dff00df00d)) @endcode + * + * @param w64 LE Supported Features value (64-bits) + * + * @return The comma separated values for LE Supported Features value that + * may be used directly as an argument for @ref BT_DATA_BYTES. + */ +#define BT_LE_SUPP_FEAT_24_ENCODE(w64) BT_BYTES_LIST_LE24(w64) + +/** + * @brief Encode 2 least significant bytes of 64-bit LE Supported Features + * into 2 bytes long array of values in little-endian format. + * + * Helper macro to encode 64-bit LE Supported Features value into advertising + * data. The macro encodes 3 least significant bytes into advertising data. + * Other 6 bytes are not encoded. + * + * Example of how to encode the `0x000000dff00df00d` into advertising data. + * + * @code BT_DATA_BYTES(BT_DATA_LE_SUPPORTED_FEATURES, + * BT_LE_SUPP_FEAT_16_ENCODE(0x000000dff00df00d)) @endcode + * + * @param w64 LE Supported Features value (64-bits) + * + * @return The comma separated values for LE Supported Features value that + * may be used directly as an argument for @ref BT_DATA_BYTES. + */ +#define BT_LE_SUPP_FEAT_16_ENCODE(w64) BT_BYTES_LIST_LE16(w64) + +/** + * @brief Encode the least significant byte of 64-bit LE Supported Features + * into single byte long array. + * + * Helper macro to encode 64-bit LE Supported Features value into advertising + * data. The macro encodes the least significant byte into advertising data. + * Other 7 bytes are not encoded. + * + * Example of how to encode the `0x000000dff00df00d` into advertising data. + * + * @code BT_DATA_BYTES(BT_DATA_LE_SUPPORTED_FEATURES, + * BT_LE_SUPP_FEAT_8_ENCODE(0x000000dff00df00d)) @endcode + * + * @param w64 LE Supported Features value (64-bits) + * + * @return The value of least significant byte of LE Supported Features value + * that may be used directly as an argument for @ref BT_DATA_BYTES. + */ +#define BT_LE_SUPP_FEAT_8_ENCODE(w64) (((w64) >> 0) & 0xff) + +/** + * @brief Validate whether LE Supported Features value does not use bits that + * are reserved for future use. + * + * Helper macro to check if @p w64 has zeros as bits 40-63. The macro is + * compliant with BT 5.3 Core Specification where bits 0-40 has assigned + * values. In case of invalid value, build time error is reported. + */ +#define BT_LE_SUPP_FEAT_VALIDATE(w64) \ +BUILD_ASSERT(!((w64) & (~BIT64_MASK(40))), "RFU bit in LE Supported Features are not zeros.") + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_GAP_H */ diff --git a/include/nuttx/net/bluetooth/gatt.h b/include/nuttx/net/bluetooth/gatt.h new file mode 100644 index 0000000000000..8752791415088 --- /dev/null +++ b/include/nuttx/net/bluetooth/gatt.h @@ -0,0 +1,2746 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/gatt.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Generic Attribute Profile handling. + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_GATT_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_GATT_H + +/** + * @brief Generic Attribute Profile (GATT) + * @details The GATT layer manages the service database by providing APIs for + * service registration and attribute declaration. For more information, see + * @ref bt_gatt_client and @ref bt_gatt_server. + * @defgroup bt_gatt Generic Attribute Profile (GATT) + * @since 1.0 + * @version 1.0.1 + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** GATT attribute permission bit field values */ + +enum bt_gatt_perm +{ + /** No operations supported, e.g. for notify-only */ + + BT_GATT_PERM_NONE = 0, + + /** Attribute read permission. */ + + BT_GATT_PERM_READ = BIT(0), + + /** Attribute write permission. */ + + BT_GATT_PERM_WRITE = BIT(1), + + /** @brief Attribute read permission with encryption. + * + * If set, requires encryption for read access. + */ + + BT_GATT_PERM_READ_ENCRYPT = BIT(2), + + /** @brief Attribute write permission with encryption. + * + * If set, requires encryption for write access. + */ + + BT_GATT_PERM_WRITE_ENCRYPT = BIT(3), + + /** @brief Attribute read permission with authentication. + * + * If set, requires encryption using authenticated link-key for read + * access. + */ + + BT_GATT_PERM_READ_AUTHEN = BIT(4), + + /** @brief Attribute write permission with authentication. + * + * If set, requires encryption using authenticated link-key for write + * access. + */ + + BT_GATT_PERM_WRITE_AUTHEN = BIT(5), + + /** @brief Attribute prepare write permission. + * + * This permission is a Zephyr extension; it is not defined by the + * Bluetooth Core Specification. + * + * If set, prepare writes (used for long writes and reliable writes) + * invoke the attribute write callback with + * @ref BT_GATT_WRITE_FLAG_PREPARE before the value is queued. The + * callback must return ``0`` to accept the prepare write; any other + * return value is converted to an ATT error. If this permission is not + * set, prepare writes are accepted by default (subject to normal write + * permissions). + * + * @note Unlike the other values of this enum, this is not an access + * check: clearing it does not make the GATT layer reject prepare writes. + * Every value that gets queued is passed to the write callback when the + * client executes the write, so an attribute that cannot accept a + * prepared write has to reject it from its write callback: at prepare + * time if this permission is set, and otherwise at execution. + * + * Do not confuse this with @ref BT_GATT_CEP_RELIABLE_WRITE, which is a + * Characteristic Extended Property bit from the specification. + */ + + BT_GATT_PERM_PREPARE_WRITE = BIT(6), + + /** @brief Attribute read permission with LE Secure Connection encryption. + * + * If set, requires that LE Secure Connections is used for read access. + */ + + BT_GATT_PERM_READ_LESC = BIT(7), + + /** @brief Attribute write permission with LE Secure Connection encryption. + * + * If set, requires that LE Secure Connections is used for write access. + */ + + BT_GATT_PERM_WRITE_LESC = BIT(8), +}; + +/** + * @brief Construct error return value for attribute read and write + * callbacks. + * + * @param _att_err ATT error code + * + * @return Appropriate error code for the attribute callbacks. + */ +#define BT_GATT_ERR(_att_err) (-(_att_err)) + +/** GATT attribute write flags */ + +enum bt_gatt_attr_write_flag +{ + /** @brief Attribute prepare write flag + * + * If set, write callback should only check if the device is authorized but + * no data shall be written. + * + * Only delivered for attributes that set + * @ref BT_GATT_PERM_PREPARE_WRITE. See that permission for why its + * absence does not mean prepared writes are rejected. + */ + + BT_GATT_WRITE_FLAG_PREPARE = BIT(0), + + /** @brief Attribute write command flag + * + * If set, indicates that write operation is a command (Write without + * response) which doesn't generate any response. + */ + + BT_GATT_WRITE_FLAG_CMD = BIT(1), + + /** @brief Attribute write execute flag + * + * If set, indicates that write operation is a execute, which indicates the + * end of a long write, and will come after 1 or more + * @ref BT_GATT_WRITE_FLAG_PREPARE. + */ + + BT_GATT_WRITE_FLAG_EXECUTE = BIT(2), +}; + +/* Forward declaration of GATT Attribute structure */ + +struct bt_gatt_attr; + +/** @typedef bt_gatt_attr_read_func_t + * @brief Attribute read callback + * + * This is the type of the bt_gatt_attr.read() method. + * + * This function may safely assume the Attribute Permissions are satisfied + * for this read. Callers are responsible for this. + * + * Callers may set @p conn to emulate a GATT client read, or leave it NULL + * for local reads. + * + * @note GATT server relies on this method to handle read + * operations from remote GATT clients. But this method is not reserved for + * the GATT server. E.g. You can lookup attributes in the local ATT database + * and invoke this method. + * + * @note The GATT server propagates the return value from this + * method back to the remote client. + * + * @note When the stack invokes this method to serve a remote + * operation it does so from a thread context chosen by the stack, never + * from an ISR, and the implementation should not block. + * + * @param conn The connection that is requesting to read. NULL if local. + * @param attr The attribute that's being read + * @param buf Buffer to place the read result in + * @param len Length of data to read + * @param offset Offset to start reading from + * + * @return Number of bytes read, or in case of an error + * ``BT_GATT_ERR()`` with a specific ``BT_ATT_ERR_*`` error code. + */ + +typedef ssize_t (*bt_gatt_attr_read_func_t)( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset); + +/** @typedef bt_gatt_attr_write_func_t + * @brief Attribute Value write implementation + * + * This is the type of the bt_gatt_attr.write() method. + * + * This function may safely assume the Attribute Permissions are satisfied + * for this write. Callers are responsible for this. + * + * Callers may set @p conn to emulate a GATT client write, or leave it NULL + * for local writes. + * + * If @p flags contains @ref BT_GATT_WRITE_FLAG_PREPARE, then the method + * shall not perform a write, but instead only check if the write is + * authorized and return an error code if not. + * + * Attribute Value write implementations can and often do have side effects + * besides potentially storing the value. E.g. togging an LED. + * + * @note GATT server relies on this method to handle write + * operations from remote GATT clients. But this method is not reserved for + * the GATT server. E.g. You can lookup attributes in the local ATT database + * and invoke this method. + * + * @note The GATT server propagates the return value from this + * method back to the remote client. + * + * @note When the stack invokes this method to serve a remote + * operation it does so from a thread context chosen by the stack, never + * from an ISR, and the implementation should not block. + * + * @param conn The connection that is requesting to write + * @param attr The attribute that's being written + * @param buf Buffer with the data to write + * @param len Number of bytes in the buffer + * @param offset Offset to start writing from + * @param flags Flags of type @ref bt_gatt_attr_write_flag + * + * @return Number of bytes written, or in case of an error + * ``BT_GATT_ERR()`` with a specific ``BT_ATT_ERR_*`` error code. + */ + +typedef ssize_t (*bt_gatt_attr_write_func_t)( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags); + +/** @brief GATT Attribute + * + * This type primarily represents an ATT Attribute that may be an entry in + * the local ATT database. The objects of this type must be part of an array + * that forms a GATT service. + * + * While the formed GATT service is registered with the local GATT server, + * pointers to this type can typically be given to GATT server APIs, like + * bt_gatt_notify(). + */ + +struct bt_gatt_attr +{ + /** @brief Attribute Type + * + * The Attribute Type is a UUID which determines the interface that can be + * expected from the read() and write() methods and the possible + * permission configurations. + * + * E.g. Attribute of type @ref BT_UUID_GATT_CPF will act as a GATT + * Characteristic Presentation Format descriptor as specified in Core + * Specification 3.G.3.3.3.5. + * + * You can define a new Attribute Type for your application specific use + * by generating a new UUID for it. + */ + + const struct bt_uuid *uuid; + + /** @brief Attribute Value read method + * + * Readable Attributes must implement this method. + * + * Must be NULL if the attribute is not readable. + * + * The behavior of this method is determined by the Attribute Type. + * + * See @ref bt_gatt_attr_read_func_t. + */ + + bt_gatt_attr_read_func_t read; + + /** @brief Attribute Value write method + * + * Writeable Attributes must implement this method. + * + * Must be NULL if the attribute is not writable. + * + * The behavior of this method is determined by the Attribute Type. + * + * See @ref bt_gatt_attr_write_func_t. + */ + + bt_gatt_attr_write_func_t write; + + /** @brief Private data for read() and write() implementation + * + * The meaning of this field varies and is not specified here. + * + * @note Attributes may have the same Attribute Type but have + * different implementations, with incompatible user data. Attribute Type + * alone must not be used to infer the type of the user data. + * + * @sa bt_gatt_discover_func_t about this field. + */ + + void *user_data; + + /** @brief Attribute Handle + * + * The Attribute Handle is an index corresponding to a specific Attribute + * in the ATT database. + * + * @note Use bt_gatt_attr_get_handle() for attributes in the + * local ATT database. + */ + + uint16_t handle; + + /** @brief Attribute Permissions + * + * Bit field of @ref bt_gatt_perm. + * + * The permissions are security requirements that must be satisfied before + * calling read() or write(). + */ + + uint16_t perm : 15; + + /** @cond INTERNAL_HIDDEN + * Indicates if the attribute handle was assigned automatically. + * + * This flag is set to 1 if the attribute handle was assigned by the + * stack, and 0 if it was manually set by the application. + * + * @note Applications must not modify this field. + */ + + bool _auto_assigned_handle : 1; + + /** @endcond */ +}; + +/** @brief Static GATT Service structure + * + * Allows the user the declare static GATT Services with the aim of reducing + * the used RAM. The @ref BT_GATT_SERVICE_DEFINE macro can be used to + * statically define and register a service. + */ + +struct bt_gatt_service_static +{ + /** Service Attributes */ + + const struct bt_gatt_attr *attrs; + + /** Service Attribute count */ + + size_t attr_count; +}; + +/** @brief GATT Service structure + * + * This structure is used to define GATT services which can be registered + * and unregistered at runtime. See @ref bt_gatt_service_register for when + * services should be registered. + */ + +struct bt_gatt_service +{ + /** Service Attributes */ + + struct bt_gatt_attr *attrs; + + /** Service Attribute count */ + + size_t attr_count; + /** @cond INTERNAL_HIDDEN + * Field used for list handling. + */ + + struct list_node node; + + /** @endcond */ +}; + +/** @brief Service Attribute Value. + * + * This is the data described by the Attribute Type and indexed by the + * Attribute Handle in the database. + */ + +struct bt_gatt_service_val +{ + /** Service UUID. */ + + const struct bt_uuid *uuid; + + /** Handle of the last Attribute within the Service. */ + + uint16_t end_handle; +}; + +/** @brief Include Attribute Value. + * + * This structure represents an included service attribute in the GATT + * server. An included service is a service that is referenced within + * another service, allowing for the reuse of common service definitions. + */ + +struct bt_gatt_include +{ + /** Service UUID. */ + + const struct bt_uuid *uuid; + + /** Handle of the first attribute within the included service. */ + + uint16_t start_handle; + + /** Handle of the last attribute within the included service. */ + + uint16_t end_handle; +}; + +/** @brief GATT callback structure. */ + +struct bt_gatt_cb +{ + /** @brief The maximum ATT MTU on a connection has changed. + * + * This callback notifies the application that the maximum TX or RX ATT + * MTU has increased. + * + * @param conn Connection object. + * @param tx Updated TX ATT MTU. + * @param rx Updated RX ATT MTU. + */ + + void (*att_mtu_updated)(struct bt_conn *conn, uint16_t tx, uint16_t rx); + + /** @cond INTERNAL_HIDDEN + * Field used for list handling. + */ + + struct list_node node; + + /** @endcond */ +}; + +/** @brief GATT authorization callback structure. */ + +struct bt_gatt_authorization_cb +{ + /** @brief Authorize the GATT read operation. + * + * This callback allows the application to authorize the GATT read + * operation for the attribute that is being read. + * + * @param conn Connection object. + * @param attr The attribute that is being read. + * + * @retval true Authorize the operation and allow it to execute. + * @retval false Reject the operation and prevent it from executing. + */ + + bool (*read_authorize)(struct bt_conn *conn, + const struct bt_gatt_attr *attr); + + /** @brief Authorize the GATT write operation. + * + * This callback allows the application to authorize the GATT write + * operation for the attribute that is being written. + * + * @param conn Connection object. + * @param attr The attribute that is being written. + * + * @retval true Authorize the operation and allow it to execute. + * @retval false Reject the operation and prevent it from executing. + */ + + bool (*write_authorize)(struct bt_conn *conn, + const struct bt_gatt_attr *attr); +}; + +/** Characteristic Properties Bit field values */ + +/** + * @brief Characteristic broadcast property. + * + * If set, permits broadcasts of the Characteristic Value using Server + * Characteristic Configuration Descriptor. + */ +#define BT_GATT_CHRC_BROADCAST 0x01 +/** + * @brief Characteristic read property. + * + * If set, permits reads of the Characteristic Value. + */ +#define BT_GATT_CHRC_READ 0x02 +/** + * @brief Characteristic write without response property. + * + * If set, permits write of the Characteristic Value without response. + */ +#define BT_GATT_CHRC_WRITE_WITHOUT_RESP 0x04 +/** + * @brief Characteristic write with response property. + * + * If set, permits write of the Characteristic Value with response. + */ +#define BT_GATT_CHRC_WRITE 0x08 +/** + * @brief Characteristic notify property. + * + * If set, permits notifications of a Characteristic Value without + * acknowledgment. + */ +#define BT_GATT_CHRC_NOTIFY 0x10 +/** + * @brief Characteristic indicate property. + * + * If set, permits indications of a Characteristic Value with acknowledgment. + */ +#define BT_GATT_CHRC_INDICATE 0x20 +/** + * @brief Characteristic Authenticated Signed Writes property. + * + * @deprecated This API is deprecated. + * + * If set, permits signed writes to the Characteristic Value. + */ +#define BT_GATT_CHRC_AUTH 0x40 __DEPRECATED_MACRO +/** + * @brief Characteristic Extended Properties property. + * + * If set, additional characteristic properties are defined in the + * Characteristic Extended Properties Descriptor. + */ +#define BT_GATT_CHRC_EXT_PROP 0x80 + +/** @brief Attribute Value of a Characteristic Declaration. + * + * This is the data associated with the characteristic, and can be read from + * or written to by a GATT client depending on the characteristic + * properties. + */ + +struct bt_gatt_chrc +{ + /** Characteristic UUID. */ + + const struct bt_uuid *uuid; + + /** Characteristic Value handle. */ + + uint16_t value_handle; + + /** Characteristic properties, a bitmap of ``BT_GATT_CHRC_*`` macros. */ + + uint8_t properties; +}; + +/** Characteristic Extended Properties Bit field values */ + +/** + * @brief Characteristic Extended Property: Reliable Write + * + * If set in the Characteristic Extended Properties descriptor, the + * characteristic supports reliable writes as defined by the Bluetooth Core + * Specification. + * + * This is unrelated to @ref BT_GATT_PERM_PREPARE_WRITE, which is a + * Zephyr-specific attribute permission that controls whether prepare writes + * solicit an authorization callback. + */ +#define BT_GATT_CEP_RELIABLE_WRITE 0x0001 + +/** + * @brief Characteristic Extended Property: Writable Auxiliaries + * + * If set, the Characteristic User Description descriptor is writable. + */ +#define BT_GATT_CEP_WRITABLE_AUX 0x0002 + +/** @brief Characteristic Extended Properties Attribute Value. + * + * Used in the discovery of standard characteristic descriptor values. Shall + * exist if the @ref BT_GATT_CHRC_EXT_PROP bit is set in the characteristic + * properties. Can be used with the @ref BT_GATT_CEP macro to declare the + * CEP descriptor. + */ + +struct bt_gatt_cep +{ + /** Characteristic Extended properties, a bitmap of ``BT_GATT_CEP_*`` + * macros. + */ + + uint16_t properties; +}; + +/** Client Characteristic Configuration Values */ + +/** + * @brief Client Characteristic Configuration Notification. + * + * If set, changes to Characteristic Value shall be notified. + */ +#define BT_GATT_CCC_NOTIFY 0x0001 +/** + * @brief Client Characteristic Configuration Indication. + * + * If set, changes to Characteristic Value shall be indicated. + */ +#define BT_GATT_CCC_INDICATE 0x0002 + +/** @brief Client Characteristic Configuration Attribute Value + * + * Used in the discovery of standard characteristic descriptor values. + */ + +struct bt_gatt_ccc +{ + /** Client Characteristic Configuration flags, a bitmap of + * ``BT_GATT_CCC_*`` macros. + */ + + uint16_t flags; +}; + +/** Server Characteristic Configuration Values */ + +/** + * @brief Server Characteristic Configuration Broadcast + * + * If set, the characteristic value shall be broadcast in the advertising + * data when the server is advertising. + */ +#define BT_GATT_SCC_BROADCAST 0x0001 + +/** @brief Server Characteristic Configuration Attribute Value + * + * Used in the discovery of standard characteristic descriptor values. + */ + +struct bt_gatt_scc +{ + /** Server Characteristic Configuration flags, a bitmap of + * ``BT_GATT_SCC_*`` macros. + */ + + uint16_t flags; +}; + +/** @brief GATT Characteristic Presentation Format Attribute Value. + * + * Used in the discovery of standard characteristic descriptor values. Can + * be used with the @ref BT_GATT_CPF macro to declare the CPF descriptor. + */ + +struct bt_gatt_cpf +{ + /** @brief Format of the value of the characteristic. + * + * The format types can be found in section 2.4.1 of the Bluetooth SIG + * Assigned Numbers document. + */ + + uint8_t format; + /** @brief Exponent field for value formatting. + * + * Only used on integer format types. actual value = Characteristic Value + * x 10^Exponent + */ + + int8_t exponent; + /** @brief UUID of the unit of the characteristic. + * + * The units can be found in section 3.5 of the Bluetooth SIG Assigned + * Numbers document. + */ + + uint16_t unit; + /** @brief Name space of the description. + * + * Used to identify the organization that is responsible for defining the + * enumerations for the description field. See section 2.4.2 of the + * Bluetooth SIG Assigned Numbers document. + */ + + uint8_t name_space; + /** @brief Description of the characteristic as defined in a higher layer + * profile. + * + * An enumerated value defined by the organization identified by the + * name_space field. See section 2.4.2.1 of the Bluetooth SIG Assigned + * Numbers document. + */ + + uint16_t description; +}; + +/** + * @defgroup bt_gatt_server GATT Server APIs + * @ingroup bt_gatt + * @{ + */ + +/** Converts a GATT error to string. + * + * The GATT errors are created with @ref BT_GATT_ERR. + * + * The error codes are described in the Bluetooth Core specification, Vol 3, + * Part F, Section 3.4.1.1. + * + * The ATT and GATT documentation found in Vol 4, Part F and Part G describe + * when the different error codes are used. + * + * See also the defined BT_ATT_ERR_* macros. + * + * @return The string representation of the GATT error code. + * If @kconfig{CONFIG_BT_ATT_ERR_TO_STR} is not enabled, this just returns + * the empty string. + */ + +static inline const char *bt_gatt_err_to_str(int gatt_err) +{ + return bt_att_err_to_str((gatt_err) < 0 ? -(gatt_err) : (gatt_err)); +} + +/** @brief Register GATT callbacks. + * + * Register callbacks to monitor the state of GATT. The callback struct must + * remain valid for the remainder of the program. + * + * @param cb Callback struct. + */ + +void bt_gatt_cb_register(struct bt_gatt_cb *cb); + +/** @brief Unregister GATT callbacks. + * + * Unregister callbacks for monitoring the state of GATT. The callback + * struct should be one that was previously registered. + * + * @param cb Callback struct. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_gatt_cb_unregister(struct bt_gatt_cb *cb); + +/** @brief Register GATT authorization callbacks. + * + * Register callbacks to perform application-specific authorization of GATT + * operations on all registered GATT attributes. The callback structure must + * remain valid throughout the entire duration of the Bluetooth subsys + * activity. + * + * The @kconfig{CONFIG_BT_GATT_AUTHORIZATION_CUSTOM} Kconfig must be enabled + * to make this API functional. + * + * This API allows the user to register only one callback structure + * concurrently. Passing NULL unregisters the previous set of callbacks and + * makes it possible to register a new one. + * + * @param cb Callback struct. + * + * @return Zero on success or negative error code otherwise. + */ + +int bt_gatt_authorization_cb_register( + const struct bt_gatt_authorization_cb *cb); + +/** @brief Register GATT service. + * + * To register a GATT service, applications can make use of macros such as + * ``BT_GATT_PRIMARY_SERVICE``, ``BT_GATT_CHARACTERISTIC``, + * ``BT_GATT_DESCRIPTOR``, etc. + * + * When using @kconfig{CONFIG_BT_SETTINGS} then all services that should + * have bond configuration loaded, i.e. CCC values, must be registered + * before calling @ref settings_load. + * + * When using @kconfig{CONFIG_BT_GATT_CACHING} and + * @kconfig{CONFIG_BT_SETTINGS} then all services that should be included in + * the GATT Database Hash calculation should be added before calling @ref + * settings_load. All services registered after settings_load will trigger a + * new database hash calculation and a new hash stored. + * + * There are two situations where this function can be called: either before + * `bt_init()` has been called, or after `settings_load()` has been called. + * Registering a service in the middle is not supported and will return an + * error. + * + * @param svc Service containing the available attributes + * + * @return 0 in case of success or negative value in case of error. + * @return -EAGAIN if ``bt_init()`` has been called but ``settings_load()`` + * hasn't yet. + */ + +int bt_gatt_service_register(struct bt_gatt_service *svc); + +/** @brief Unregister GATT service. + * + * @param svc Service to be unregistered. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_gatt_service_unregister(struct bt_gatt_service *svc); + +/** @brief Check if GATT service is registered. + * + * @param svc Service to be checked. + * + * @return true if registered or false if not register. + */ + +bool bt_gatt_service_is_registered(const struct bt_gatt_service *svc); + +/** @brief to be used as return values for @ref bt_gatt_attr_func_t and @ref + * bt_gatt_read_func_t type callbacks. + */ + +enum bt_gatt_iter +{ + BT_GATT_ITER_STOP = 0, + BT_GATT_ITER_CONTINUE, +}; + +/** @typedef bt_gatt_attr_func_t + * @brief Attribute iterator callback. + * + * @param attr Attribute found. + * @param handle Attribute handle found. + * @param user_data Data given. + * + * @return ``BT_GATT_ITER_CONTINUE`` if should continue to the next + * attribute. + * @return ``BT_GATT_ITER_STOP`` to stop. + */ + +typedef uint8_t (*bt_gatt_attr_func_t)( + const struct bt_gatt_attr *attr, uint16_t handle, void *user_data); + +/** @brief Attribute iterator by type. + * + * Iterate attributes in the given range matching given UUID and/or data. + * + * @param start_handle Start attribute handle. + * @param end_handle End attribute handle. Often set to start_handle + + * attr_count or BT_ATT_LAST_ATTRIBUTE_HANDLE. + * @param uuid UUID to match, passing NULL skips UUID matching. + * @param attr_data Attribute data to match, passing NULL skips data + * matching. + * @param num_matches Number matches, passing 0 makes it unlimited. + * @param func Callback function. + * @param user_data Data to pass to the callback. + */ + +void bt_gatt_foreach_attr_type(uint16_t start_handle, + uint16_t end_handle, + const struct bt_uuid *uuid, + const void *attr_data, + uint16_t num_matches, + bt_gatt_attr_func_t func, + void *user_data); + +/** @brief Attribute iterator. + * + * Iterate attributes in the given range. + * + * @param start_handle Starting attribute handle. + * @param end_handle Ending attribute handle. + * @param func Callback function. + * @param user_data Data to pass to the callback. + */ + +static inline void bt_gatt_foreach_attr( + uint16_t start_handle, uint16_t end_handle, bt_gatt_attr_func_t func, + void *user_data) +{ + bt_gatt_foreach_attr_type(start_handle, end_handle, NULL, NULL, 0, func, + user_data); +} + +/** @brief Iterate to the next attribute + * + * Iterate to the next attribute following a given attribute. + * + * @param attr Current Attribute. + * + * @return The next attribute or NULL if it cannot be found. + */ + +struct bt_gatt_attr *bt_gatt_attr_next(const struct bt_gatt_attr *attr); + +/** @brief Find Attribute by UUID. + * + * Find the attribute with the matching UUID. To limit the search to a + * service set the attr to the service attributes and the ``attr_count`` to + * the service attribute count . + * + * @param attr Pointer to an attribute that serves as the starting + * point for the search of a match for the UUID. Passing + * NULL will search the entire range. + * @param attr_count The number of attributes from the starting point to + * search for a match for the UUID. Set to 0 to search + * until the end. + * @param uuid UUID to match. + */ + +struct bt_gatt_attr *bt_gatt_find_by_uuid( + const struct bt_gatt_attr *attr, uint16_t attr_count, + const struct bt_uuid *uuid); + +/** @brief Get Attribute handle. + * + * @param attr An attribute object currently registered in the local ATT + * server. + * + * @return Handle of the corresponding attribute or zero if the attribute + * could not be found. + */ + +uint16_t bt_gatt_attr_get_handle(const struct bt_gatt_attr *attr); + +/** @brief Get the handle of the characteristic value descriptor. + * + * @param attr A Characteristic Attribute. + * + * @note The ``user_data`` of the attribute must be of type @ref bt_gatt_chrc + * and the ``uuid`` shall be BT_UUID_GATT_CHRC. + * + * @return the handle of the corresponding Characteristic Value. The value + * will be zero (the invalid handle) if @p attr was not a characteristic + * attribute. + */ + +uint16_t bt_gatt_attr_value_handle(const struct bt_gatt_attr *attr); + +/** @brief Generic Read Attribute value helper. + * + * Read attribute value from local database storing the result into buffer. + * + * @param conn Connection object. + * @param attr Attribute to read. + * @param buf Buffer to store the value. + * @param buf_len Buffer length. + * @param offset Start offset. + * @param value Attribute value. + * @param value_len Length of the attribute value. + * + * @return number of bytes read in case of success or negative values in + * case of error. + */ + +ssize_t bt_gatt_attr_read( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t buf_len, uint16_t offset, const void *value, uint16_t value_len); + +/** @brief Read Service Attribute helper. + * + * Read service attribute value from local database storing the result into + * buffer after encoding it. + * @note Only use this with attributes which ``user_data`` is a ``bt_uuid``. + * + * @param conn Connection object. + * @param attr Attribute to read. + * @param buf Buffer to store the value read. + * @param len Buffer length. + * @param offset Start offset. + * + * @return number of bytes read in case of success or negative values in + * case of error. + */ + +ssize_t bt_gatt_attr_read_service( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset); + +/** + * @brief Statically define and register a service. + * + * Helper macro to statically define and register a service. + * + * @param _name Service name. + */ +#define BT_GATT_SERVICE_DEFINE(_name, ...) \ +const struct bt_gatt_attr attr_##_name[] = {__VA_ARGS__}; \ +const STRUCT_SECTION_ITERABLE(bt_gatt_service_static, \ + _name) = BT_GATT_SERVICE(attr_##_name) + +#define _BT_GATT_ATTRS_ARRAY_DEFINE(n, _instances, _attrs_def) \ +static struct bt_gatt_attr attrs_##n[] = _attrs_def(_instances[n]) + +#define _BT_GATT_SERVICE_ARRAY_ITEM(_n, _) BT_GATT_SERVICE(attrs_##_n) + +/** + * @brief Statically define service structure array. + * + * Helper macro to statically define service structure array. Each element + * of the array is linked to the service attribute array which is also + * defined in this scope using ``_attrs_def`` macro. + * + * @param _name Name of service structure array. + * @param _instances Array of instances to pass as user context to the + * attribute callbacks. + * @param _instance_num Number of elements in instance array. + * @param _attrs_def Macro provided by the user that defines attribute + * array for the service. This macro should accept + * single parameter which is the instance context. + */ +#define BT_GATT_SERVICE_INSTANCE_DEFINE(_name, _instances, _instance_num, _attrs_def) \ +BUILD_ASSERT(ARRAY_SIZE(_instances) == _instance_num, \ + "The number of array elements does not match its size"); \ +LISTIFY(_instance_num, _BT_GATT_ATTRS_ARRAY_DEFINE, (;), _instances, _attrs_def); \ +static struct bt_gatt_service _name[] = { \ + LISTIFY(_instance_num, _BT_GATT_SERVICE_ARRAY_ITEM, (, ))} + +/** + * @brief Service Structure Declaration Macro. + * + * Helper macro to declare a service structure. + * + * @param _attrs Service attributes. + */ +#define BT_GATT_SERVICE(_attrs) \ +{ \ + .attrs = _attrs, \ + .attr_count = ARRAY_SIZE(_attrs), \ +} + +/** + * @brief Primary Service Declaration Macro. + * + * Helper macro to declare a primary service attribute. + * + * @param _service Service attribute value. + */ +#define BT_GATT_PRIMARY_SERVICE(_service) \ +BT_GATT_ATTRIBUTE(BT_UUID_GATT_PRIMARY, BT_GATT_PERM_READ, bt_gatt_attr_read_service, NULL, \ + (void *)_service) + +/** + * @brief Secondary Service Declaration Macro. + * + * Helper macro to declare a secondary service attribute. + * + * @note A secondary service is only intended to be included from a primary + * service or another secondary service or other higher layer specification. + * + * @param _service Service attribute value. + */ +#define BT_GATT_SECONDARY_SERVICE(_service) \ +BT_GATT_ATTRIBUTE(BT_UUID_GATT_SECONDARY, BT_GATT_PERM_READ, bt_gatt_attr_read_service, NULL, \ + (void *)_service) + +/** @brief Read Include Attribute helper. + * + * Read include service attribute value from local database storing the + * result into buffer after encoding it. + * @note Only use this with attributes which user_data is a + * ``bt_gatt_include``. The function returns EINVAL if @p attr or @p + * attr->user_data is NULL. + * + * @param conn Connection object. + * @param attr Attribute to read. + * @param buf Buffer to store the value read. + * @param len Buffer length. + * @param offset Start offset. + * + * @return number of bytes read in case of success or negative values in + * case of error. + */ + +ssize_t bt_gatt_attr_read_included( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset); + +/** + * @brief Include Service Declaration Macro. + * + * Helper macro to declare database internal include service attribute. + * + * @param _service_incl the first service attribute of service to include + */ +#define BT_GATT_INCLUDE_SERVICE(_service_incl) \ +BT_GATT_ATTRIBUTE(BT_UUID_GATT_INCLUDE, BT_GATT_PERM_READ, bt_gatt_attr_read_included, NULL, \ + _service_incl) + +/** @brief Read Characteristic Attribute helper. + * + * Read characteristic attribute value from local database storing the + * result into buffer after encoding it. + * @note Only use this with attributes which ``user_data`` is a + * ``bt_gatt_chrc``. + * + * @param conn Connection object. + * @param attr Attribute to read. + * @param buf Buffer to store the value read. + * @param len Buffer length. + * @param offset Start offset. + * + * @return number of bytes read in case of success or negative values in + * case of error. + */ + +ssize_t bt_gatt_attr_read_chrc( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset); + +/** @brief Gatt Characteristic Initialization Macro. + * + * Helper macro used within the @ref BT_GATT_CHARACTERISTIC macro in the + * GATT attribute declaration to set the attribute user data. + * + * @param _uuid Characteristic attribute uuid. + * @param _handle Characcteristic attribute handle at init. + * @param _props Characteristic attribute properties, a bitmap of + * ``BT_GATT_CHRC_*`` macros. + */ +#define BT_GATT_CHRC_INIT(_uuid, _handle, _props) \ +{ \ + .uuid = _uuid, \ + .value_handle = _handle, \ + .properties = _props, \ +} + +/** + * @brief Characteristic and Value Declaration Macro. + * + * Helper macro to declare a characteristic attribute along with its + * attribute value. + * + * @param _uuid Characteristic attribute uuid. + * @param _props Characteristic attribute properties, a bitmap of + * ``BT_GATT_CHRC_*`` macros. + * @param _perm Characteristic Attribute access permissions, a bitmap + * of + * @ref bt_gatt_perm values. + * @param _read Characteristic Attribute read callback (@ref + * bt_gatt_attr_read_func_t). + * @param _write Characteristic Attribute write callback (@ref + * bt_gatt_attr_write_func_t). + * @param _user_data Characteristic Attribute user data. + */ +#define BT_GATT_CHARACTERISTIC(_uuid, _props, _perm, _read, _write, _user_data) \ +BT_GATT_ATTRIBUTE(BT_UUID_GATT_CHRC, BT_GATT_PERM_READ, bt_gatt_attr_read_chrc, NULL, \ + ((struct bt_gatt_chrc[]){ \ + BT_GATT_CHRC_INIT(_uuid, 0U, _props), \ + })), \ + BT_GATT_ATTRIBUTE(_uuid, _perm, _read, _write, _user_data) + +/** + * @brief BT_GATT_CCC_MAX is defined depending on whether + * @kconfig{CONFIG_BT_SETTINGS_CCC_LAZY_LOADING} or @kconfig{CONFIG_BT_CONN} + * is set. + * + * @kconfig{CONFIG_BT_SETTINGS_CCC_LAZY_LOADING} will set BT_GATT_CCC_MAX to + * @kconfig{CONFIG_BT_MAX_CONN} @kconfig{CONFIG_BT_CONN} will set + * BT_GATT_CCC_MAX to @kconfig{CONFIG_BT_MAX_PAIRED} + + * @kconfig{CONFIG_BT_MAX_CONN} If neither are set, BT_GATT_CCC_MAX is 0. + * + */ +#if defined(CONFIG_BT_SETTINGS_CCC_LAZY_LOADING) +#define BT_GATT_CCC_MAX (CONFIG_BT_MAX_CONN) +#elif defined(CONFIG_BT_CONN) +#define BT_GATT_CCC_MAX (CONFIG_BT_MAX_PAIRED + CONFIG_BT_MAX_CONN) +#else +#define BT_GATT_CCC_MAX 0 +#endif + +/** @brief GATT CCC configuration entry. + * + * bt_gatt_ccc_cfg is used within @ref bt_gatt_attr_read_ccc and @ref + * bt_gatt_attr_write_ccc to read and write the ccc configurations + * respectively. + * + */ + +struct bt_gatt_ccc_cfg +{ + /** Local identity, BT_ID_DEFAULT in most cases. */ + + uint8_t id; + + /** Remote peer address. */ + + bt_addr_le_t peer; + /** @brief Configuration value + * Value used to enable or disable notifications or indications for a + * specific characteristic. + */ + + uint16_t value; +}; + +/** @brief Internal representation of CCC value. + * + * @note Only use this as an argument for @ref BT_GATT_CCC_MANAGED + */ + +struct bt_gatt_ccc_managed_user_data +{ + /** Configuration for each connection */ + + struct bt_gatt_ccc_cfg cfg[BT_GATT_CCC_MAX]; + + /** Highest value of all connected peer's subscriptions, Reserved bits + * excluded + */ + + uint16_t value; + + /** @brief CCC attribute changed callback + * + * Called when the cached CCC value changes. While processing CCC writes, + * the value is the highest subscription value across all connected peers. + * On disconnect, the callback is invoked only when all subscriptions are + * disabled. + * + * @param attr The CCC attribute whose cached value changed. + * @param value New cached value. + */ + + void (*cfg_changed)(const struct bt_gatt_attr *attr, uint16_t value); + + /** @brief CCC attribute write validation callback + * + * @param conn The connection that is requesting to write + * @param attr The attribute that's being written + * @param value CCC value to write + * + * @return Number of bytes to write, or in case of an error + * BT_GATT_ERR() with a specific error code. + */ + + ssize_t (*cfg_write)(struct bt_conn *conn, + const struct bt_gatt_attr *attr, uint16_t value); + + /** @brief CCC attribute match handler + * + * Indicate if it is OK to send a notification or indication to the + * subscriber. + * + * @param conn The connection that is being checked + * @param attr The attribute that's being checked + * + * @return true if application has approved notification/indication, + * false if application does not approve. + */ + + bool (*cfg_match)(struct bt_conn *conn, const struct bt_gatt_attr *attr); +}; + +/** @brief Read Client Characteristic Configuration Attribute helper. + * + * Read CCC attribute value from local database storing the result into + * buffer after encoding it. + * + * @param conn Connection object. + * @param attr Attribute to read. + * @param buf Buffer to store the value read. + * @param len Buffer length. + * @param offset Start offset. + * + * @return number of bytes read in case of success or negative values in + * case of error. + */ + +/** @cond INTERNAL_HIDDEN + * @note Only use this with attributes which user_data is a + * bt_gatt_ccc_managed_user_data. + * @ref bt_gatt_ccc_managed_user_data being the internal + * representation of CCC value. + */ + +/** @endcond */ + +ssize_t bt_gatt_attr_read_ccc( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset); + +/** @brief Write Client Characteristic Configuration Attribute helper. + * + * Write value in the buffer into CCC attribute. + * + * @param conn Connection object. + * @param attr Attribute to read. + * @param buf Buffer to store the value read. + * @param len Buffer length. + * @param offset Start offset. + * @param flags Write flags, see @ref bt_gatt_attr_write_flag. + * + * @return number of bytes written in case of success or negative values in + * case of error. + */ + +/** @cond INTERNAL_HIDDEN + * @note Only use this with attributes which user_data is a + * bt_gatt_ccc_managed_user_data. + * @ref bt_gatt_ccc_managed_user_data being the internal + * representation of CCC value. + */ + +/** @endcond */ + +ssize_t bt_gatt_attr_write_ccc( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags); + +/** + * @brief Initialize Client Characteristic Configuration Declaration Macro. + * + * Helper macro to initialize a Managed CCC attribute value. + * + * @param _changed Configuration changed callback. + * @param _write Configuration write callback. + * @param _match Configuration match callback. + */ +#define BT_GATT_CCC_MANAGED_USER_DATA_INIT(_changed, _write, _match) \ +{ \ + .cfg = {}, \ + .cfg_changed = _changed, \ + .cfg_write = _write, \ + .cfg_match = _match, \ +} + +/** + * @brief Managed Client Characteristic Configuration Declaration Macro. + * + * Helper macro to declare a Managed CCC attribute. + * + * @param _ccc A new @ref bt_gatt_ccc_managed_user_data object with the + * same lifetime as the results of the call to + * BT_GATT_CCC_MANAGED. See the documentation of @ref + * bt_gatt_ccc_managed_user_data on how to initialize it. + * @param _perm CCC access permissions, a bitmap of @ref bt_gatt_perm + * values. + */ +#define BT_GATT_CCC_MANAGED(_ccc, _perm) \ +BT_GATT_ATTRIBUTE(BT_UUID_GATT_CCC, _perm, bt_gatt_attr_read_ccc, bt_gatt_attr_write_ccc, _ccc) + +/** + * @brief Client Characteristic Configuration Declaration Macro. + * + * Helper macro to declare a CCC attribute. + * + * @param _changed Configuration changed callback. + * @param _perm CCC access permissions, a bitmap of @ref bt_gatt_perm + * values. + */ +#define BT_GATT_CCC(_changed, _perm) \ +BT_GATT_CCC_MANAGED(((struct bt_gatt_ccc_managed_user_data[]){ \ + BT_GATT_CCC_MANAGED_USER_DATA_INIT(_changed, NULL, NULL)}), \ + _perm) + +/** + * @brief Client Characteristic Configuration Declaration Macro with write + * callback. + * + * Helper macro to declare a CCC attribute with a write callback. + * + * @param _changed Configuration changed callback. + * @param _write Configuration write callback. + * @param _perm CCC access permissions, a bitmap of @ref bt_gatt_perm + * values. + */ +#define BT_GATT_CCC_WITH_WRITE_CB(_changed, _write, _perm) \ +BT_GATT_CCC_MANAGED(((struct bt_gatt_ccc_managed_user_data[]){ \ + BT_GATT_CCC_MANAGED_USER_DATA_INIT(_changed, _write, NULL)}), \ + _perm) + +/** @brief Read Characteristic Extended Properties Attribute helper + * + * Read CEP attribute value from local database storing the result into + * buffer after encoding it. + * + * @note Only use this with attributes which user_data is a bt_gatt_cep. + * + * @param conn Connection object + * @param attr Attribute to read + * @param buf Buffer to store the value read + * @param len Buffer length + * @param offset Start offset + * + * @return number of bytes read in case of success or negative values in + * case of error. + */ + +ssize_t bt_gatt_attr_read_cep( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset); + +/** + * @brief Characteristic Extended Properties Declaration Macro. + * + * Helper macro to declare a CEP attribute. + * + * @param _value Pointer to a struct bt_gatt_cep. + */ +#define BT_GATT_CEP(_value) \ +BT_GATT_DESCRIPTOR(BT_UUID_GATT_CEP, BT_GATT_PERM_READ, bt_gatt_attr_read_cep, NULL, \ + (void *)_value) + +/** @brief Read Characteristic User Description Descriptor Attribute helper + * + * Read CUD attribute value from local database storing the result into + * buffer after encoding it. + * + * @note Only use this with attributes which user_data is a NULL-terminated + * C string. + * + * @param conn Connection object + * @param attr Attribute to read + * @param buf Buffer to store the value read + * @param len Buffer length + * @param offset Start offset + * + * @return number of bytes read in case of success or negative values in + * case of error. + */ + +ssize_t bt_gatt_attr_read_cud( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset); + +/** + * @brief Characteristic User Format Descriptor Declaration Macro. + * + * Helper macro to declare a CUD attribute. + * + * @param _value User description NULL-terminated C string. + * @param _perm Descriptor attribute access permissions, a bitmap of @ref + * bt_gatt_perm values. + */ +#define BT_GATT_CUD(_value, _perm) \ +BT_GATT_DESCRIPTOR(BT_UUID_GATT_CUD, _perm, bt_gatt_attr_read_cud, NULL, (void *)_value) + +/** @brief Read Characteristic Presentation format Descriptor Attribute + * helper + * + * Read CPF attribute value from local database storing the result into + * buffer after encoding it. + * + * @note Only use this with attributes which user_data is a @ref + * bt_gatt_cpf. + * + * @param conn Connection object + * @param attr Attribute to read + * @param buf Buffer to store the value read + * @param len Buffer length + * @param offset Start offset + * + * @return number of bytes read in case of success or negative values in + * case of error. + */ + +ssize_t bt_gatt_attr_read_cpf( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset); + +/** + * @brief Characteristic Presentation Format Descriptor Declaration Macro. + * + * Helper macro to declare a CPF attribute. + * + * @param _value Pointer to a struct bt_gatt_cpf. + */ +#define BT_GATT_CPF(_value) \ +BT_GATT_DESCRIPTOR(BT_UUID_GATT_CPF, BT_GATT_PERM_READ, bt_gatt_attr_read_cpf, NULL, \ + (void *)_value) + +/** + * @brief Descriptor Declaration Macro. + * + * Helper macro to declare a descriptor attribute. + * + * @param _uuid Descriptor attribute uuid. + * @param _perm Descriptor attribute access permissions, a bitmap of + * @ref bt_gatt_perm values. + * @param _read Descriptor attribute read callback (@ref + * bt_gatt_attr_read_func_t). + * @param _write Descriptor attribute write callback (@ref + * bt_gatt_attr_write_func_t). + * @param _user_data Descriptor attribute user data. + */ +#define BT_GATT_DESCRIPTOR(_uuid, _perm, _read, _write, _user_data) \ +BT_GATT_ATTRIBUTE(_uuid, _perm, _read, _write, _user_data) + +/** + * @brief Attribute Declaration Macro. + * + * Helper macro to declare an attribute. + * + * @param _uuid Attribute uuid. + * @param _perm Attribute access permissions, a bitmap of @ref + * bt_gatt_perm values. + * @param _read Attribute read callback (@ref + * bt_gatt_attr_read_func_t). + * @param _write Attribute write callback (@ref + * bt_gatt_attr_write_func_t). + * @param _user_data Attribute user data. + */ +#define BT_GATT_ATTRIBUTE(_uuid, _perm, _read, _write, _user_data) \ +{ \ + .uuid = _uuid, \ + .read = _read, \ + .write = _write, \ + .user_data = _user_data, \ + .handle = 0, \ + .perm = _perm, \ +} + +/** @brief Notification complete result callback. + * + * @param conn Connection object. + * @param user_data Data passed in by the user. + */ + +typedef void (*bt_gatt_complete_func_t)( + struct bt_conn *conn, void *user_data); + +/** @brief GATT notification parameters + * + * See also @ref bt_gatt_notify_cb and @ref bt_gatt_notify_multiple, using + * this parameter. + */ + +struct bt_gatt_notify_params +{ + /** @brief Notification Attribute UUID type + * + * Optional, use to search for an attribute with matching UUID when the + * attribute object pointer is not known. + */ + + const struct bt_uuid *uuid; + /** @brief Notification Attribute object + * + * Optional if uuid is provided, in this case it will be used as start + * range to search for the attribute with the given UUID. + */ + + const struct bt_gatt_attr *attr; + + /** Notification Value data */ + + const void *data; + + /** Notification Value length */ + + uint16_t len; + + /** Notification Value callback */ + + bt_gatt_complete_func_t func; + + /** Notification Value callback user data */ + + void *user_data; +#if defined(CONFIG_BT_EATT) || defined(__DOXYGEN__) + /** Att channel options. */ + + enum bt_att_chan_opt chan_opt; +#endif /* CONFIG_BT_EATT */ +}; + +/** @brief Notify attribute value change. + * + * This function works in the same way as @ref bt_gatt_notify. With the + * addition that after sending the notification the callback function will + * be called. + * + * The callback runs in a thread context, never in an ISR; see + * @rstref{Callback execution contexts }. When + * called from the System Workqueue context this API will not wait for + * resources for the callback but instead return an error. + * + * Alternatively it is possible to notify by UUID by setting it on the + * parameters, when using this method the attribute if provided is used as + * the start range when looking up for possible matches. + * + * @param conn Connection object. + * @param params Notification parameters. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_gatt_notify_cb(struct bt_conn *conn, + struct bt_gatt_notify_params *params); + +/** @brief Send multiple notifications in a single PDU. + * + * The GATT Server will send a single ATT_MULTIPLE_HANDLE_VALUE_NTF PDU + * containing all the notifications passed to this API. + * + * All `params` must have the same `func` and `user_data` (due to + * implementation limitation). But `func(user_data)` will be invoked for + * each parameter. + * + * As this API may block to wait for Bluetooth Host resources, it is not + * recommended to call it from a cooperative thread or a Bluetooth callback. + * + * The peer's GATT Client must write to this device's Client Supported + * Features attribute and set the bit for Multiple Handle Value + * Notifications before this API can be used. + * + * Only use this API to force the use of the ATT_MULTIPLE_HANDLE_VALUE_NTF + * PDU. For standard applications, `bt_gatt_notify_cb` is preferred, as it + * will use this PDU if supported and automatically fallback to + * ATT_HANDLE_VALUE_NTF when not supported by the peer. + * + * This API has an additional limitation: it only accepts valid attribute + * references and not UUIDs like `bt_gatt_notify` and `bt_gatt_notify_cb`. + * + * @param conn + * Target client. Notifying all connected clients by passing `NULL` is not + * yet supported, please use `bt_gatt_notify` instead. + * @param num_params + * Element count of `params` array. Has to be greater than 1. + * @param params + * Array of notification parameters. It is okay to free this after calling + * this function. + * + * @retval 0 + * Success. The PDU is queued for sending. + * @retval -EINVAL + * - One of the attribute handles is invalid. + * - Only one parameter was passed. This API expects 2 or more. + * - Not all `func` were equal or not all `user_data` were equal. + * - One of the characteristics is not notifiable. + * - An UUID was passed in one of the parameters. + * @retval -ERANGE + * - The notifications cannot all fit in a single + * ATT_MULTIPLE_HANDLE_VALUE_NTF. + * - They exceed the MTU of all open ATT bearers. + * @retval -EPERM + * The connection has a lower security level than required by one of the + * attributes. + * @retval -EOPNOTSUPP + * The peer hasn't yet communicated that it supports this PDU type. + */ + +int bt_gatt_notify_multiple(struct bt_conn *conn, uint16_t num_params, + struct bt_gatt_notify_params params[]); + +/** @brief Notify attribute value change. + * + * Send notification of attribute value change, if connection is NULL notify + * all peer that have notification enabled via CCC otherwise do a direct + * notification only the given connection. + * + * The attribute object on the parameters can be the so called + * Characteristic Declaration, which is usually declared with + * BT_GATT_CHARACTERISTIC followed by BT_GATT_CCC, or the Characteristic + * Value Declaration which is automatically created after the Characteristic + * Declaration when using BT_GATT_CHARACTERISTIC. + * + * The value is limited to the maximum notification value size reported by + * @ref bt_att_get_max_notify_size and has to be truncated by the caller if + * it is larger than that. + * + * @param conn Connection object. + * @param attr Characteristic or Characteristic Value attribute. + * @param data Pointer to Attribute data. + * @param len Attribute value length. + * + * @return 0 in case of success or negative value in case of error. + */ + +static inline int bt_gatt_notify( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *data, + uint16_t len) +{ + struct bt_gatt_notify_params params; + + memset(¶ms, 0, sizeof(params)); + + params.attr = attr; + params.data = data; + params.len = len; +#if defined(CONFIG_BT_EATT) + params.chan_opt = BT_ATT_CHAN_OPT_NONE; +#endif /* CONFIG_BT_EATT */ + + return bt_gatt_notify_cb(conn, ¶ms); +} + +/** @brief Notify attribute value change by UUID. + * + * Send notification of attribute value change, if connection is NULL notify + * all peer that have notification enabled via CCC otherwise do a direct + * notification only on the given connection. + * + * The attribute object is the starting point for the search of the UUID. + * + * @param conn Connection object. + * @param uuid The UUID. If the server contains multiple services with the + * same UUID, then the first occurrence, starting from the attr + * given, is used. + * @param attr Pointer to an attribute that serves as the starting point for + * the search of a match for the UUID. + * @param data Pointer to Attribute data. + * @param len Attribute value length. + * + * @return 0 in case of success or negative value in case of error. + */ + +static inline int bt_gatt_notify_uuid( + struct bt_conn *conn, const struct bt_uuid *uuid, + const struct bt_gatt_attr *attr, const void *data, uint16_t len) +{ + struct bt_gatt_notify_params params; + + memset(¶ms, 0, sizeof(params)); + + params.uuid = uuid; + params.attr = attr; + params.data = data; + params.len = len; +#if defined(CONFIG_BT_EATT) + params.chan_opt = BT_ATT_CHAN_OPT_NONE; +#endif /* CONFIG_BT_EATT */ + + return bt_gatt_notify_cb(conn, ¶ms); +} + +/* Forward declaration of the bt_gatt_indicate_params structure */ + +struct bt_gatt_indicate_params; + +/** @typedef bt_gatt_indicate_func_t + * @brief Indication complete result callback. + * + * @param conn Connection object. + * @param params Indication params object. + * @param err ATT error code + */ + +typedef void (*bt_gatt_indicate_func_t)( + struct bt_conn *conn, struct bt_gatt_indicate_params *params, uint8_t err); + +/** @typedef bt_gatt_indicate_params_destroy_t + * @brief Callback to destroy or clean up the GATT Indicate Value + * parameters. + * + * This callback function is invoked to clean up any resources associated + * with the `bt_gatt_indicate_params` structure once the GATT indication + * operation is completed. + * + * @param params Pointer to the GATT Indicate parameters structure to be + * cleaned up. + */ + +typedef void (*bt_gatt_indicate_params_destroy_t)( + struct bt_gatt_indicate_params *params); + +/** @brief GATT Indicate Value parameters + * + * See also @ref bt_gatt_indicate, using this parameter. + * + */ + +struct bt_gatt_indicate_params +{ + /** @brief Indicate Attribute UUID type + * + * Optional, use to search for an attribute with matching UUID when the + * attribute object pointer is not known. + */ + + const struct bt_uuid *uuid; + /** @brief Indicate Attribute object + * + * Optional if uuid is provided, in this case it will be used as start + * range to search for the attribute with the given UUID. + */ + + const struct bt_gatt_attr *attr; + + /** Indicate Value callback */ + + bt_gatt_indicate_func_t func; + + /** Indicate operation complete callback */ + + bt_gatt_indicate_params_destroy_t destroy; + + /** Indicate Value data */ + + const void *data; + + /** Indicate Value length */ + + uint16_t len; + + /** Private reference counter */ + + uint8_t _ref; +#if defined(CONFIG_BT_EATT) || defined(__DOXYGEN__) + /** Att channel options. */ + + enum bt_att_chan_opt chan_opt; +#endif /* CONFIG_BT_EATT */ +}; + +/** @brief Indicate attribute value change. + * + * Send an indication of attribute value change. if connection is NULL + * indicate all peer that have notification enabled via CCC otherwise do a + * direct indication only the given connection. + * + * The attribute object on the parameters can be the so called + * Characteristic Declaration, which is usually declared with + * BT_GATT_CHARACTERISTIC followed by BT_GATT_CCC, or the Characteristic + * Value Declaration which is automatically created after the Characteristic + * Declaration when using BT_GATT_CHARACTERISTIC. + * + * Alternatively it is possible to indicate by UUID by setting it on the + * parameters, when using this method the attribute if provided is used as + * the start range when looking up for possible matches. + * + * The value is limited to the maximum indication value size reported by + * @ref bt_att_get_max_indicate_size and has to be truncated by the caller + * if it is larger than that. + * + * @note This procedure is asynchronous therefore the parameters need to + * remains valid while it is active. The procedure is active until the + * destroy callback is run. + * + * @param conn Connection object. + * @param params Indicate parameters. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_gatt_indicate(struct bt_conn *conn, + struct bt_gatt_indicate_params *params); + +/** @brief Check if connection have subscribed to attribute + * + * Check if the connection has subscribed to an attribute value change. + * + * The attribute object can be the so called Characteristic Declaration, + * which is usually declared with BT_GATT_CHARACTERISTIC followed by + * BT_GATT_CCC, or the Characteristic Value Declaration which is + * automatically created after the Characteristic Declaration when using + * BT_GATT_CHARACTERISTIC, or the Client Characteristic Configuration + * Descriptor (CCCD) which is created by BT_GATT_CCC. + * + * @param conn Connection object. + * @param attr Attribute object. + * @param ccc_type The subscription type, @ref BT_GATT_CCC_NOTIFY and/or + * @ref BT_GATT_CCC_INDICATE. + * + * @return true if the attribute object has been subscribed. + */ + +bool bt_gatt_is_subscribed( + struct bt_conn *conn, const struct bt_gatt_attr *attr, uint16_t ccc_type); + +/** @brief Get ATT MTU for a connection + * + * Get negotiated ATT connection MTU, note that this does not equal the + * largest amount of attribute data that can be transferred within a single + * packet. + * + * @param conn Connection object. + * + * @return MTU in bytes + */ + +uint16_t bt_gatt_get_mtu(struct bt_conn *conn); + +/** @brief Get Unenhanced ATT (UATT) MTU for a connection + * + * Get UATT connection MTU. + * + * The ATT_MTU defines the largest size of an ATT PDU, encompassing the ATT + * opcode, additional fields, and any attribute value payload. Consequently, + * the maximum size of a value payload is less and varies based on the type + * of ATT PDU. For example, in an ATT_HANDLE_VALUE_NTF PDU, the Attribute + * Value field can contain up to ATT_MTU - 3 octets (size of opcode and + * handle). + * + * @param conn Connection object. + * + * @return 0 if @p conn does not have an UATT ATT_MTU (e.g: disconnected). + * @return Current UATT ATT_MTU. + */ + +uint16_t bt_gatt_get_uatt_mtu(struct bt_conn *conn); + +/** @} */ + +/** + * @defgroup bt_gatt_client GATT Client APIs + * @ingroup bt_gatt + * @{ + */ + +/** @brief GATT Exchange MTU parameters + * + * Used with @ref bt_gatt_exchange_mtu function to initiate an MTU exchange. + * The response is handled in the callback @p func, which is called upon + * completion in a thread context, never in an ISR; see @rstref{Callback + * execution contexts }. + * + * @p params must remain valid until the callback executes. + */ + +struct bt_gatt_exchange_params +{ + /** Callback for MTU exchange response */ + + void (*func)(struct bt_conn *conn, uint8_t err, + struct bt_gatt_exchange_params *params); +}; + +/** @brief Exchange MTU + * + * Once per connection, this client procedure can be used to set the MTU to + * the maximum possible size the buffers can hold. + * + * As the response comes in callback @p params->func, for example + * @ref bt_gatt_get_mtu can be invoked in the mtu_exchange-callback to read + * out the new negotiated ATT connection MTU. The callback runs in a thread + * context, never in an ISR; see @rstref{Callback execution contexts + * }. @p params must remain valid until start + * of callback. + * + * @param conn Connection object. + * @param params Exchange MTU parameters. + * + * @retval 0 Successfully queued request. Will call @p params->func on + * resolution. + * + * @retval -ENOMEM ATT request queue is full and blocking would cause + * deadlock. Allow a pending request to resolve before retrying, or call + * this function from a separate thread to get blocking behavior. Queue size + * is controlled by @kconfig{CONFIG_BT_ATT_TX_COUNT}. + * + * @retval -EALREADY The MTU exchange procedure has been already performed. + */ + +int bt_gatt_exchange_mtu(struct bt_conn *conn, + struct bt_gatt_exchange_params *params); + +struct bt_gatt_discover_params; + +/** @typedef bt_gatt_discover_func_t + * @brief Discover attribute callback function. + * + * @param conn Connection object. + * @param attr Attribute found, or NULL if not found. + * @param params Discovery parameters given. + * + * If discovery procedure has completed this callback will be called with + * attr set to NULL. This will not happen if procedure was stopped by + * returning BT_GATT_ITER_STOP. + * + * The attribute object as well as its UUID and value objects are temporary + * and must be copied to in order to cache its information. + * + * @note @ref bt_gatt_attr is given as an argument to @ref bt_gatt_discover + * function, but it's not a proper object of this type. @ref + * bt_gatt_attr.perm, and methods bt_gatt_attr.read() and + * bt_gatt_attr.write() are not available, and it's unsound to pass the + * pointer to GATT server APIs. Only the following fields of the attribute + * contains valid information: + * - uuid UUID representing the type of attribute. + * - handle Handle in the remote database. + * - user_data The value of the attribute, if the discovery type maps to an + * ATT operation that provides this information. NULL otherwise. See below. + * + * The effective type of @c attr->user_data is determined by @c params. Note + * that the fields @c params->type and @c params->uuid are left unchanged + * by the discovery procedure. + * + * @c params->type | @c params->uuid | Type of @c attr->user_data + * -------------------------------------|---------------------- + * @ref BT_GATT_DISCOVER_PRIMARY | any + * @ref BT_GATT_DISCOVER_SECONDARY | any + * @ref BT_GATT_DISCOVER_INCLUDE | any + * @ref BT_GATT_DISCOVER_CHARACTERISTIC | any + * @ref BT_GATT_DISCOVER_STD_CHAR_DESC | @ref BT_UUID_GATT_CEP + * @ref BT_GATT_DISCOVER_STD_CHAR_DESC | @ref BT_UUID_GATT_CCC + * @ref BT_GATT_DISCOVER_STD_CHAR_DESC | @ref BT_UUID_GATT_SCC + * @ref BT_GATT_DISCOVER_STD_CHAR_DESC | @ref BT_UUID_GATT_CPF + * @ref BT_GATT_DISCOVER_DESCRIPTOR | any + * @ref BT_GATT_DISCOVER_ATTRIBUTE | any + * + * -------------------------------------|------------------------- + * @ref BT_GATT_DISCOVER_PRIMARY | @ref bt_gatt_service_val + * @ref BT_GATT_DISCOVER_SECONDARY | @ref bt_gatt_service_val + * @ref BT_GATT_DISCOVER_INCLUDE | @ref bt_gatt_include + * @ref BT_GATT_DISCOVER_CHARACTERISTIC | @ref bt_gatt_chrc + * @ref BT_GATT_DISCOVER_STD_CHAR_DESC | @ref bt_gatt_cep + * @ref BT_GATT_DISCOVER_STD_CHAR_DESC | @ref bt_gatt_ccc + * @ref BT_GATT_DISCOVER_STD_CHAR_DESC | @ref bt_gatt_scc + * @ref BT_GATT_DISCOVER_STD_CHAR_DESC | @ref bt_gatt_cpf + * @ref BT_GATT_DISCOVER_DESCRIPTOR | NULL + * @ref BT_GATT_DISCOVER_ATTRIBUTE | NULL + * + * Also consider if using read-by-type instead of discovery is more + * convenient. See @ref bt_gatt_read with @ref + * bt_gatt_read_params.handle_count set to @c 0. + * + * @return BT_GATT_ITER_CONTINUE to continue discovery procedure. + * @return BT_GATT_ITER_STOP to stop discovery procedure. + */ + +typedef uint8_t (*bt_gatt_discover_func_t)( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params); + +/** GATT Discover types */ + +enum bt_gatt_discover_type +{ + /** Discover Primary Services. */ + + BT_GATT_DISCOVER_PRIMARY, + + /** Discover Secondary Services. */ + + BT_GATT_DISCOVER_SECONDARY, + + /** Discover Included Services. */ + + BT_GATT_DISCOVER_INCLUDE, + /** @brief Discover Characteristic Values. + * + * Discover Characteristic Value and its properties. + */ + + BT_GATT_DISCOVER_CHARACTERISTIC, + /** @brief Discover Descriptors. + * + * Discover Attributes which are not services or characteristics. + * + * @note The use of this type of discover is not recommended for + * discovering in ranges across multiple services/characteristics as it + * may incur in extra round trips. + */ + + BT_GATT_DISCOVER_DESCRIPTOR, + /** @brief Discover Attributes. + * + * Discover Attributes of any type. + * + * @note The use of this type of discover is not recommended for + * discovering in ranges across multiple services/characteristics as it + * may incur in more round trips. + */ + + BT_GATT_DISCOVER_ATTRIBUTE, + /** @brief Discover standard characteristic descriptor values. + * + * Discover standard characteristic descriptor values and their + * properties. Supported descriptors: + * - Characteristic Extended Properties + * - Client Characteristic Configuration + * - Server Characteristic Configuration + * - Characteristic Presentation Format + */ + + BT_GATT_DISCOVER_STD_CHAR_DESC, +}; + +/** Handle value to denote that the CCC will be automatically discovered */ +#define BT_GATT_AUTO_DISCOVER_CCC_HANDLE 0x0000U + +/** @brief GATT Discover Attributes parameters */ + +struct bt_gatt_discover_params +{ + /** Discover UUID type */ + + const struct bt_uuid *uuid; + + /** Discover attribute callback */ + + bt_gatt_discover_func_t func; + union + { + /** See @ref bt_gatt_include for more on included services. */ + + struct + { + /** Include service attribute declaration handle */ + + uint16_t attr_handle; + + /** Starting attribute handle for included service */ + + uint16_t start_handle; + + /** Ending attribute handle for included service */ + + uint16_t end_handle; + } _included; + + /** Starting attribute handle to begin discovery */ + + uint16_t start_handle; + }; + /** @brief Ending attribute handle to stop discovery at + * + * @note When discovery begins this can be set to + * @ref BT_ATT_LAST_ATTRIBUTE_HANDLE to discover all attributes + * in the service. + */ + + uint16_t end_handle; + + /** Discover type */ + + uint8_t type; +#if defined(CONFIG_BT_GATT_AUTO_DISCOVER_CCC) || defined(__DOXYGEN__) + /** Only for stack-internal use, used for automatic discovery. */ + + struct bt_gatt_subscribe_params *sub_params; +#endif /* defined(CONFIG_BT_GATT_AUTO_DISCOVER_CCC) || defined(__DOXYGEN__) */ +#if defined(CONFIG_BT_EATT) || defined(__DOXYGEN__) + /** Att channel options. */ + enum bt_att_chan_opt chan_opt; +#endif /* CONFIG_BT_EATT */ +}; + +/** @brief GATT Discover function + * + * This procedure is used by a client to discover attributes on a server. + * + * Primary Service Discovery: Procedure allows to discover primary services + * either by Discover All Primary Services or Discover Primary Services by + * Service UUID. Include Service Discovery: Procedure allows to discover all + * Include Services within specified range. Characteristic Discovery: + * Procedure allows to discover all characteristics within specified handle + * range as well as discover characteristics with specified UUID. + * Descriptors Discovery: Procedure allows to discover all characteristic + * descriptors within specified range. + * + * For each attribute found the callback is called which can then decide + * whether to continue discovering or stop. + * + * The Response comes in callback @p params->func. The callback runs in a + * thread context, never in an ISR; see @rstref{Callback execution contexts + * }. @p params must remain valid until start + * of callback where iter `attr` is `NULL` or callback will return + * `BT_GATT_ITER_STOP`. + * + * @param conn Connection object. + * @param params Discover parameters. + * + * @retval 0 Successfully queued request. Will call @p params->func on + * resolution. + * + * @retval -ENOMEM ATT request queue is full and blocking would cause + * deadlock. Allow a pending request to resolve before retrying, or call + * this function from a separate thread to get blocking behavior. Queue size + * is controlled by @kconfig{CONFIG_BT_ATT_TX_COUNT}. + */ + +int bt_gatt_discover(struct bt_conn *conn, + struct bt_gatt_discover_params *params); + +struct bt_gatt_read_params; + +/** @typedef bt_gatt_read_func_t + * @brief Read callback function + * + * When reading using by_uuid, `params->start_handle` is the attribute + * handle for this `data` item. + * + * If the received data length is invalid, the callback will called with the + * error @ref BT_ATT_ERR_INVALID_PDU. + * + * @param conn Connection object. + * @param err ATT error code. + * @param params Read parameters used. + * @param data Attribute value data. NULL means read has completed. + * @param length Attribute value length. + * + * @return BT_GATT_ITER_CONTINUE if should continue to the next attribute. + * @return BT_GATT_ITER_STOP to stop. + */ + +typedef uint8_t (*bt_gatt_read_func_t)( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, + const void *data, uint16_t length); + +/** @brief GATT Read parameters */ + +struct bt_gatt_read_params +{ + /** Read attribute callback. */ + + bt_gatt_read_func_t func; + /** If equals to 1 single.handle and single.offset are used. + * If greater than 1 multiple.handles are used. If equals to 0 by_uuid is + * used for Read Using Characteristic UUID. + */ + + size_t handle_count; + union + { + struct + { + /** Attribute handle. */ + + uint16_t handle; + + /** Attribute data offset. */ + + uint16_t offset; + } single; + struct + { + /** Attribute handles to read with Read Multiple Characteristic Values. + */ + + uint16_t *handles; + /** If true use Read Multiple Variable Length + * Characteristic Values procedure. The values of the set of + * attributes may be of variable or unknown length. If false use Read + * Multiple Characteristic Values procedure. The values of the set of + * attributes must be of a known fixed length, with the exception of + * the last value that can have a variable length. + */ + + bool variable; + } multiple; + struct + { + /** @brief Requested start attribute handle number. + * + * @details The starting handle is set to the starting point of the + * range over which this read should be performed. For example, this + * could be set to @ref BT_ATT_FIRST_ATTRIBUTE_HANDLE to set the + * starting point of the range at the beginning of the GATT database, + * or to the starting handle of a service after discovery. + * + * This value is automatically incremented by the stack after + * processing each matching handle-value pair returned by the server. + */ + + uint16_t start_handle; + /** @brief Requested end attribute handle number. + * + * @details The end handle is set to the ending point of the range over + * which this read should be performed. For example, this could be set + * to + * @ref BT_ATT_LAST_ATTRIBUTE_HANDLE to set the ending point of the + * range at the end of the GATT database, or to the end handle for + * service after discovery, where the end_handle is available in the + * @ref bt_gatt_service_val. + */ + + uint16_t end_handle; + + /** 2 or 16 octet UUID. */ + + const struct bt_uuid *uuid; + } by_uuid; + }; +#if defined(CONFIG_BT_EATT) || defined(__DOXYGEN__) + /** Att channel options. */ + + enum bt_att_chan_opt chan_opt; +#endif /* CONFIG_BT_EATT */ + /** Internal */ + uint16_t _att_mtu; +}; + +/** @brief Read Attribute Value by handle + * + * This procedure reads the attribute value and returns it to the callback. + * + * When reading attributes by UUID the callback can be called multiple times + * depending on how many instances of a given UUID exists with the + * start_handle being updated for each instance. + * + * To perform a GATT Long Read procedure, start with a Characteristic Value + * Read (by setting @c offset @c 0 and @c handle_count @c 1) and then return + * @ref BT_GATT_ITER_CONTINUE from the callback. This is equivalent to + * calling + * @ref bt_gatt_read again, but with the correct offset to continue the + * read. This may be repeated until the procedure is complete, which is + * signaled by the callback being called with @p data set to @c NULL. + * + * Note that returning @ref BT_GATT_ITER_CONTINUE is really starting a new + * ATT operation, so this can fail to allocate resources. However, all API + * errors are reported as if the server returned @ref BT_ATT_ERR_UNLIKELY. + * There is no way to distinguish between this condition and a @ref + * BT_ATT_ERR_UNLIKELY response from the server itself. + * + * Note that the effect of returning @ref BT_GATT_ITER_CONTINUE from the + * callback varies depending on the type of read operation. + * + * The Response comes in callback @p params->func. The callback runs in a + * thread context, never in an ISR; see @rstref{Callback execution contexts + * }. @p params must remain valid until start + * of callback. If the received data length is invalid, the callback @p + * params->func will called with the error @ref BT_ATT_ERR_INVALID_PDU. + * + * @param conn Connection object. + * @param params Read parameters. + * + * @retval 0 Successfully queued request. Will call @p params->func on + * resolution. + * + * @retval -ENOMEM ATT request queue is full and blocking would cause + * deadlock. Allow a pending request to resolve before retrying, or call + * this function from a separate thread to get blocking behavior. Queue size + * is controlled by @kconfig{CONFIG_BT_ATT_TX_COUNT}. + */ + +int bt_gatt_read(struct bt_conn *conn, struct bt_gatt_read_params *params); + +struct bt_gatt_write_params; + +/** @typedef bt_gatt_write_func_t + * @brief Write callback function + * + * @param conn Connection object. + * @param err ATT error code. + * @param params Write parameters used. + */ + +typedef void (*bt_gatt_write_func_t)( + struct bt_conn *conn, uint8_t err, struct bt_gatt_write_params *params); + +/** @brief GATT Write parameters */ + +struct bt_gatt_write_params +{ + /** Response callback */ + + bt_gatt_write_func_t func; + + /** Attribute handle */ + + uint16_t handle; + + /** Attribute data offset */ + + uint16_t offset; + + /** Data to be written */ + + const void *data; + + /** Length of the data */ + + uint16_t length; +#if defined(CONFIG_BT_EATT) || defined(__DOXYGEN__) + /** Att channel options. */ + + enum bt_att_chan_opt chan_opt; +#endif /* CONFIG_BT_EATT */ +}; + +/** @brief Write Attribute Value by handle + * + * The Response comes in callback @p params->func. The callback runs in a + * thread context, never in an ISR; see @rstref{Callback execution contexts + * }. @p params must remain valid until start + * of callback. + * + * @param conn Connection object. + * @param params Write parameters. + * + * @retval 0 Successfully queued request. Will call @p params->func on + * resolution. + * + * @retval -ENOMEM ATT request queue is full and blocking would cause + * deadlock. Allow a pending request to resolve before retrying, or call + * this function from a separate thread to get blocking behavior. Queue size + * is controlled by @kconfig{CONFIG_BT_ATT_TX_COUNT}. + */ + +int bt_gatt_write(struct bt_conn *conn, struct bt_gatt_write_params *params); + +/** @brief Write Attribute Value by handle without response with callback. + * + * This function works in the same way as @ref + * bt_gatt_write_without_response. With the addition that after sending the + * write the callback function will be called. + * + * The callback runs in a thread context, never in an ISR; see + * @rstref{Callback execution contexts }. When + * called from the System Workqueue context this API will not wait for + * resources for the callback but instead return an error. + * + * @param conn Connection object. + * @param handle Attribute handle. + * @param data Data to be written. + * @param length Data length. + * @param sign Whether to sign data. + * @param func Transmission complete callback. + * @param user_data User data to be passed back to callback. + * + * @retval 0 Successfully queued request. + * + * @retval -ENOMEM ATT request queue is full and blocking would cause + * deadlock. Allow a pending request to resolve before retrying, or call + * this function from a separate thread to get blocking behavior. Queue size + * is controlled by @kconfig{CONFIG_BT_ATT_TX_COUNT}. + */ + +int bt_gatt_write_without_response_cb( + struct bt_conn *conn, uint16_t handle, const void *data, uint16_t length, + bool sign, bt_gatt_complete_func_t func, void *user_data); + +/** @brief Write Attribute Value by handle without response + * + * This procedure write the attribute value without requiring an + * acknowledgment that the write was successfully performed + * + * @param conn Connection object. + * @param handle Attribute handle. + * @param data Data to be written. + * @param length Data length. + * @param sign Whether to sign data. + * + * @retval 0 Successfully queued request. + * + * @retval -ENOMEM ATT request queue is full and blocking would cause + * deadlock. Allow a pending request to resolve before retrying, or call + * this function from a separate thread to get blocking behavior. Queue size + * is controlled by @kconfig{CONFIG_BT_ATT_TX_COUNT}. + */ + +static inline int bt_gatt_write_without_response( + struct bt_conn *conn, uint16_t handle, const void *data, uint16_t length, + bool sign) +{ + return bt_gatt_write_without_response_cb(conn, handle, data, length, sign, + NULL, NULL); +} + +struct bt_gatt_subscribe_params; + +/** @typedef bt_gatt_notify_func_t + * @brief Notification callback function + * + * In the case of an empty notification, the @p data pointer will be + * non-NULL while the @p length will be 0, which is due to the special case + * where a @p data NULL pointer means unsubscribed. + * + * @param conn Connection object. May be NULL, indicating that the peer is + * being unpaired + * @param params Subscription parameters. + * @param data Attribute value data. If NULL then subscription was + * removed. + * @param length Attribute value length. + * + * @return BT_GATT_ITER_CONTINUE to continue receiving value notifications. + * BT_GATT_ITER_STOP to unsubscribe from value notifications. + */ + +typedef uint8_t (*bt_gatt_notify_func_t)( + struct bt_conn *conn, struct bt_gatt_subscribe_params *params, + const void *data, uint16_t length); + +/** @typedef bt_gatt_subscribe_func_t + * @brief Subscription callback function + * + * @param conn Connection object. + * @param err ATT error code. + * @param params Subscription parameters used. + */ + +typedef void (*bt_gatt_subscribe_func_t)( + struct bt_conn *conn, uint8_t err, struct bt_gatt_subscribe_params + *params); + +/** Subscription flags */ + +enum bt_gatt_sub_flag +{ + /** @brief Persistence flag + * + * If set, indicates that the subscription is not saved on the GATT server + * side. Therefore, upon disconnection, the subscription will be + * automatically removed from the client's subscriptions list and when the + * client reconnects, it will have to issue a new subscription. + */ + + BT_GATT_SUBSCRIBE_FLAG_VOLATILE, + + /** @brief No resubscribe flag + * + * By default when BT_GATT_SUBSCRIBE_FLAG_VOLATILE is unset, the + * subscription will be automatically renewed when the client reconnects, + * as a workaround for GATT servers that do not persist subscriptions. + * + * This flag will disable the automatic resubscription. It is useful if + * the application layer knows that the GATT server remembers + * subscriptions from previous connections and wants to avoid renewing the + * subscriptions. + */ + + BT_GATT_SUBSCRIBE_FLAG_NO_RESUB, + + /** @brief Write pending flag + * + * If set, indicates write operation is pending waiting remote end to + * respond. + * + * @note Internal use only. + */ + + BT_GATT_SUBSCRIBE_FLAG_WRITE_PENDING, + + /** @brief Sent flag + * + * If set, indicates that a subscription request (CCC write) has already + * been sent in the active connection. + * + * Used to avoid sending subscription requests multiple times when the + * @kconfig{CONFIG_BT_GATT_AUTO_RESUBSCRIBE} quirk is enabled. + * + * @note Internal use only. + */ + + BT_GATT_SUBSCRIBE_FLAG_SENT, + + BT_GATT_SUBSCRIBE_NUM_FLAGS +}; + +/** @brief GATT Subscribe parameters */ + +struct bt_gatt_subscribe_params +{ + /** Notification value callback */ + + bt_gatt_notify_func_t notify; + /** Subscribe CCC write request response callback + * If given, called with the subscription parameters given when + * subscribing + */ + + bt_gatt_subscribe_func_t subscribe; + + /** Subscribe value handle */ + + uint16_t value_handle; + + /** Subscribe CCC handle */ + + uint16_t ccc_handle; +#if defined(CONFIG_BT_GATT_AUTO_DISCOVER_CCC) || defined(__DOXYGEN__) + /** Subscribe End handle (for automatic discovery) */ + + uint16_t end_handle; + + /** Discover parameters used when ccc_handle = @ref + * BT_GATT_AUTO_DISCOVER_CCC_HANDLE + */ + + struct bt_gatt_discover_params *disc_params; +#endif /* defined(CONFIG_BT_GATT_AUTO_DISCOVER_CCC) || defined(__DOXYGEN__) */ + /** Subscribe value */ + uint16_t value; +#if defined(CONFIG_BT_SMP) || defined(__DOXYGEN__) + /** Minimum required security for received notification. Notifications + * and indications received over a connection with a lower security level + * are silently discarded. + */ + + bt_security_t min_security; +#endif /* CONFIG_BT_SMP */ + /** Subscription flags, see @ref bt_gatt_sub_flag */ + atomic_define(flags, BT_GATT_SUBSCRIBE_NUM_FLAGS); + + /** @cond INTERNAL_HIDDEN + * Field used for list handling. + */ + + struct list_node node; + + /** @endcond */ +#if defined(CONFIG_BT_EATT) || defined(__DOXYGEN__) + /** Att channel options. */ + + enum bt_att_chan_opt chan_opt; +#endif /* CONFIG_BT_EATT */ +}; + +/** @brief Subscribe Attribute Value Notification + * + * This procedure subscribe to value notification using the Client + * Characteristic Configuration handle. If notification received subscribe + * value callback is called to return notified value. One may then decide + * whether to unsubscribe directly from this callback. Notification callback + * with NULL data will not be called if subscription was removed by this + * method. + * + * The Response comes in callback @p params->subscribe, and notifications in + * @p params->notify. Both callbacks run in a thread context, never in an + * ISR; see @rstref{Callback execution contexts + * }. + * + * @note Notifications are asynchronous therefore the @p params must remain + * valid while subscribed and cannot be reused for additional subscriptions + * whilst active. + * + * @param conn Connection object. + * @param params Subscribe parameters. + * + * @retval 0 Successfully queued request. Will call @p params->write on + * resolution. + * + * @retval -ENOMEM ATT request queue is full and blocking would cause + * deadlock. Allow a pending request to resolve before retrying, or call + * this function from a separate thread to get blocking behavior. Queue size + * is controlled by @kconfig{CONFIG_BT_ATT_TX_COUNT}. + * + * @retval -EALREADY if there already exist a subscription using the @p + * params. + * + * @retval -EBUSY if @p params.ccc_handle is 0 and + * @kconfig{CONFIG_BT_GATT_AUTO_DISCOVER_CCC} is enabled and discovery for + * the @p params is already in progress. + */ + +int bt_gatt_subscribe(struct bt_conn *conn, + struct bt_gatt_subscribe_params *params); + +/** @brief Resubscribe Attribute Value Notification subscription + * + * Resubscribe to Attribute Value Notification when already subscribed from + * a previous connection. The GATT server will remember subscription from + * previous connections when bonded, so resubscribing can be done without + * performing a new subscribe procedure after a power cycle. + * + * @note Notifications are asynchronous therefore the parameters need to + * remain valid while subscribed. + * + * @param id Local identity (in most cases BT_ID_DEFAULT). + * @param peer Remote address. + * @param params Subscribe parameters. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_gatt_resubscribe(uint8_t id, const bt_addr_le_t *peer, + struct bt_gatt_subscribe_params *params); + +/** @brief Unsubscribe Attribute Value Notification + * + * This procedure unsubscribe to value notification using the Client + * Characteristic Configuration handle. Notification callback with NULL data + * will be called if subscription was removed by this call, until then the + * parameters cannot be reused. + * + * The Response comes in callback @p params->notify with NULL data, either + * from within this function or from a thread context chosen by the stack. + * + * @param conn Connection object. + * @param params Subscribe parameters. The parameters shall be a @ref + * bt_gatt_subscribe_params from a previous call to + * bt_gatt_subscribe(). + * + * @retval 0 Successfully queued request. Will call @p params->write on + * resolution. + * + * @retval -ENOMEM ATT request queue is full and blocking would cause + * deadlock. Allow a pending request to resolve before retrying, or call + * this function from a separate thread to get blocking behavior. Queue size + * is controlled by @kconfig{CONFIG_BT_ATT_TX_COUNT}. + */ + +int bt_gatt_unsubscribe(struct bt_conn *conn, + struct bt_gatt_subscribe_params *params); + +/** @brief Try to cancel the first pending request identified by @p params. + * + * This function does not release @p params for reuse. The usual callbacks + * for the request still apply. A successful cancel simulates a + * #BT_ATT_ERR_UNLIKELY response from the server. + * + * This function can cancel the following request functions: + * - #bt_gatt_exchange_mtu + * - #bt_gatt_discover + * - #bt_gatt_read + * - #bt_gatt_write + * - #bt_gatt_subscribe + * - #bt_gatt_unsubscribe + * + * @param conn The connection the request was issued on. + * @param params The address `params` used in the request function call. + */ + +void bt_gatt_cancel(struct bt_conn *conn, void *params); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_GATT_H */ diff --git a/include/nuttx/net/bluetooth/hci.h b/include/nuttx/net/bluetooth/hci.h new file mode 100644 index 0000000000000..518f513a8d532 --- /dev/null +++ b/include/nuttx/net/bluetooth/hci.h @@ -0,0 +1,257 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/hci.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* hci.h - Bluetooth Host Control Interface definitions */ + +/* Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_HCI_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_HCI_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Converts a HCI error to string. + * + * The error codes are described in the Bluetooth Core specification, Vol 1, + * Part F, Section 2. + * + * The HCI documentation found in Vol 4, Part E, describes when the different + * error codes are used. + * + * See also the defined BT_HCI_ERR_* macros. + * + * @return The string representation of the HCI error code. + * If @kconfig{CONFIG_BT_HCI_ERR_TO_STR} is not enabled, this just returns + * the empty string + */ +#if defined(CONFIG_BT_HCI_ERR_TO_STR) +const char *bt_hci_err_to_str(uint8_t hci_err); +#else +#include + +static inline const char *bt_hci_err_to_str(uint8_t hci_err) +{ +ARG_UNUSED(hci_err); + +return ""; +} +#endif + +/** Allocate an HCI command buffer. + * + * This function allocates a new buffer for an HCI command. Upon successful + * return the buffer is ready to have the command parameters encoded into it. + * Sufficient headroom gets automatically reserved in the buffer, allowing + * the actual command and H:4 headers to be encoded later, as part of calling + * bt_hci_cmd_send() or bt_hci_cmd_send_sync(). + * + * @param timeout Timeout for the allocation. + * + * @return Newly allocated buffer or NULL if allocation failed. + */ + +struct net_buf *bt_hci_cmd_alloc(clock_t timeout); + +/** Send a HCI command asynchronously. + * + * This function is used for sending a HCI command asynchronously. It can + * either be called for a buffer created using bt_hci_cmd_alloc(), or if the + * command has no parameters a NULL can be passed instead. The sending of the + * command will happen asynchronously, i.e. upon successful return from this + * function the caller only knows that it was queued successfully. + * + * If synchronous behavior, and retrieval of the Command Complete parameters + * is desired, the bt_hci_cmd_send_sync() API should be used instead. + * + * @param opcode Command OpCode. + * @param buf Command buffer or NULL (if no parameters). + * + * @return 0 on success (the command has been queued), negative errno value + * on failure. + * @retval -EHOSTDOWN The HCI transport is not open, i.e. Bluetooth is not + * enabled. + */ + +int bt_hci_cmd_send(uint16_t opcode, struct net_buf *buf); + +/** Send a HCI command synchronously. + * + * This function is used for sending a HCI command synchronously. It can + * either be called for a buffer created using bt_hci_cmd_alloc(), or if the + * command has no parameters a NULL can be passed instead. + * + * The function will block until a Command Status or a Command Complete event + * is returned. If either of these have a non-zero status the function will + * return a negative error code and the response reference will not be set. + * If the command completed successfully and a non-NULL rsp parameter was + * given, this parameter will be set to point to a buffer containing the + * response parameters. + * + * @param opcode Command OpCode. + * @param buf Command buffer or NULL (if no parameters). + * @param rsp Place to store a reference to the command response. May be + * NULL if the caller is not interested in the response + * parameters. If non-NULL is passed the caller is responsible + * for calling net_buf_unref() on the buffer when done parsing + * it. + * + * @return 0 on success (the command completed), negative errno value on + * failure. + * @retval -EHOSTDOWN The HCI transport is not open, i.e. Bluetooth is not + * enabled. + */ + +int bt_hci_cmd_send_sync(uint16_t opcode, + struct net_buf *buf, struct net_buf **rsp); + +/** @brief Get connection handle for a connection. + * + * @param conn Connection object. + * @param conn_handle Place to store the Connection handle. + * + * @return 0 on success or negative error value on failure. + */ + +int bt_hci_get_conn_handle(const struct bt_conn *conn, + uint16_t *conn_handle); + +/** @brief Get connection given a connection handle. + * + * The caller gets a new reference to the connection object which must be + * released with bt_conn_unref() once done using the object. + * + * @param handle The connection handle + * + * @returns The corresponding connection object on success. + * NULL if it does not exist. + */ + +struct bt_conn *bt_hci_conn_lookup_handle(uint16_t handle); + +/** @brief Get advertising handle for an advertising set. + * + * @param adv Advertising set. + * @param adv_handle Place to store the advertising handle. + * + * @return 0 on success or negative error value on failure. + */ + +int bt_hci_get_adv_handle(const struct bt_le_ext_adv *adv, + uint8_t *adv_handle); + +/** @brief Get advertising set given an advertising handle + * + * @param handle The advertising handle + * + * @returns The corresponding advertising set on success, + * NULL if it does not exist. + */ + +struct bt_le_ext_adv *bt_hci_adv_lookup_handle(uint8_t handle); + +/** @brief Get periodic advertising sync handle. + * + * @param sync Periodic advertising sync set. + * @param sync_handle Place to store the periodic advertising sync handle. + * + * @return 0 on success or negative error value on failure. + */ + +int bt_hci_get_adv_sync_handle(const struct bt_le_per_adv_sync *sync, + uint16_t *sync_handle); + +/** @brief Get periodic advertising sync given an periodic advertising sync + * handle. + * + * @param handle The periodic sync set handle + * + * @return The corresponding periodic advertising sync set object on success, + * NULL if it does not exist. + */ + +struct bt_le_per_adv_sync * +bt_hci_per_adv_sync_lookup_handle(uint16_t handle); + +/** @brief Obtain the version string given a core version number. + * + * The core version of a controller can be obtained by issuing the HCI Read + * Local Version Information command. + * + * See also the defines prefixed with BT_HCI_VERSION_. + * + * @param core_version The core version. + * + * @return Version string corresponding to the core version number. + */ + +const char *bt_hci_get_ver_str(uint8_t core_version); + +/** @typedef bt_hci_vnd_evt_cb_t + * @brief Callback type for vendor handling of HCI Vendor-Specific Events. + * + * A function of this type is registered with bt_hci_register_vnd_evt_cb() + * and will be called for any HCI Vendor-Specific Event. + * + * @param buf Buffer containing event parameters. + * + * @return true if the function handles the event or false to defer the + * handling of this event back to the stack. + */ + +typedef bool bt_hci_vnd_evt_cb_t(struct net_buf_simple *buf); + +/** Register user callback for HCI Vendor-Specific Events + * + * @param cb Callback to be called when the stack receives a HCI + * Vendor-Specific Event. + * + * @return 0 on success or negative error value on failure. + */ + +int bt_hci_register_vnd_evt_cb(bt_hci_vnd_evt_cb_t cb); + +/** @brief Get Random bytes from the LE Controller. + * + * Send the HCI_LE_Rand to the LE Controller as many times as required to + * fill the provided @p buffer. + * + * @note This function is provided as a helper to gather an arbitrary number + * of random bytes from an LE Controller using the HCI_LE_Rand command. + * + * @param buffer Buffer to fill with random bytes. + * @param len Length of the buffer in bytes. + * + * @return 0 on success or negative error value on failure. + */ + +int bt_hci_le_rand(void *buffer, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_HCI_H */ diff --git a/include/nuttx/net/bluetooth/hci_driver_nuttx.h b/include/nuttx/net/bluetooth/hci_driver_nuttx.h new file mode 100644 index 0000000000000..3e0d166dd02d3 --- /dev/null +++ b/include/nuttx/net/bluetooth/hci_driver_nuttx.h @@ -0,0 +1,93 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/hci_driver_nuttx.h + * + * Author: chao an + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef ZEPHYR_BT_HCI_DRIVER_NUTTX_H_ +#define ZEPHYR_BT_HCI_DRIVER_NUTTX_H_ + +/* NuttX HCI driver integration for NuttX Bluetooth Host Stack. + * + * This provides the bridge between the Zephyr BT host (which expects the + * Zephyr driver model) and NuttX's hardware drivers. + * + * To use with a specific HCI transport: 1. Register an HCI driver via + * bt_hci_driver_register() 2. Driver calls bt_recv() when it receives data + * from the controller 3. BT host calls the driver's send() when it has data + * for the controller + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct net_buf; +struct bt_driver_s; + +/* HCI driver operations */ + +struct bt_hci_driver_ops +{ + int (*open)(void); + int (*send)(struct net_buf *buf); + int (*close)(void); +}; + +/* Register an HCI driver. Returns 0 on success. */ + +int bt_hci_driver_register(const struct bt_hci_driver_ops *ops); + +/* Register a NuttX Bluetooth lower-half driver for the Zephyr BT host. */ + +int bt_netdev_register(struct bt_driver_s *driver); + +/* Called by the HCI driver when it receives data from the controller */ + +int bt_recv(struct net_buf *buf); + +/* Called by the HCI driver when it receives an ISO packet */ + +int bt_recv_iso(struct net_buf *buf); + +/* Get a buffer for HCI command/event/ACL data */ + +struct net_buf *bt_buf_get_cmd_complete(clock_t timeout); +struct net_buf *bt_buf_get_evt(uint8_t evt, + bool discardable, clock_t timeout); +struct net_buf *bt_buf_get_acl(clock_t timeout); +struct net_buf *bt_buf_get_iso(clock_t timeout); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_BT_HCI_DRIVER_NUTTX_H_ */ diff --git a/include/nuttx/net/bluetooth/hci_pkt.h b/include/nuttx/net/bluetooth/hci_pkt.h new file mode 100644 index 0000000000000..040bedd30464d --- /dev/null +++ b/include/nuttx/net/bluetooth/hci_pkt.h @@ -0,0 +1,273 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/hci_pkt.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Bluetooth HCI packet helpers. + */ + +/* Copyright (c) 2026 Silicon Laboratories Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_HCI_PKT_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_HCI_PKT_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Bluetooth HCI packet helpers + * @defgroup bt_hci_pkt Bluetooth HCI packet helpers + * @since 4.5 + * @version 0.1.0 + * @ingroup bluetooth + * + * Stateless helpers for framing HCI command packets and for parsing the HCI + * events that carry command responses, operating on @ref net_buf_simple + * buffers and on packet bytes. They depend on neither the Bluetooth Host nor + * the HCI driver interface and can be used in any build type: by the Host, + * by HCI drivers (for example to perform vendor-specific controller + * initialization over the driver's own transport, see hci_lockstep.h) and by + * controller-only applications. + * + * @note These are not general application APIs, even though the header lives + * in the application-visible include directory: the intended users are HCI + * drivers and Bluetooth stack internals. Applications that need to send HCI + * commands alongside a running Host use the higher-level + * bt_hci_cmd_alloc(), bt_hci_cmd_send() and bt_hci_cmd_send_sync() APIs of + * hci.h, which cooperate with the Host's command flow control. + * + * Packets are handled in the form used throughout Zephyr: the HCI packet + * indicator (@c BT_HCI_H4_CMD, @c BT_HCI_H4_EVT, ...) is the first byte, + * followed by the packet header and the parameters. Parameters are + * encoded and decoded by the caller with the packed @c bt_hci_cp_* and @c + * bt_hci_rp_* structures and the net_buf_simple API, exactly as in the + * Bluetooth Host. + * + * @{ + */ + +/** @brief Size of the prefix of an HCI command packet. + * + * The packet indicator followed by the HCI command header, which precede + * the command parameters. Equals the reservation the Bluetooth Host's + * command buffers use (see @ref BT_BUF_CMD_SIZE); kept separate so that + * this header has no dependency on the Host's buffer API. + */ +#define BT_HCI_PKT_CMD_HDR_SIZE (sizeof(uint8_t) + BT_HCI_CMD_HDR_SIZE) + +/** @brief Size of an HCI command packet with @p param_len bytes of + * parameters. + */ +#define BT_HCI_PKT_CMD_SIZE(param_len) (BT_HCI_PKT_CMD_HDR_SIZE + (param_len)) + +/** @cond INTERNAL_HIDDEN */ +#define Z_BT_HCI_PKT_CMD_DEFINE(_name, _max_param_len, _storage) \ +_storage uint8_t net_buf_data_##_name[BT_HCI_PKT_CMD_SIZE(_max_param_len)]; \ +_storage struct net_buf_simple _name = { \ + .data = net_buf_data_##_name + BT_HCI_PKT_CMD_HDR_SIZE, \ + .len = 0, \ + .size = BT_HCI_PKT_CMD_SIZE(_max_param_len), \ + .__buf = net_buf_data_##_name, \ +} +/** @endcond */ + +/** @brief Define a buffer for an HCI command packet. + * + * Defines and initializes a @ref net_buf_simple with storage for an HCI + * command packet carrying up to @p _max_param_len bytes of parameters. The + * buffer starts out empty, with headroom reserved for + * bt_hci_pkt_push_cmd_hdr(): parameters are added with the net_buf_simple + * API, after which the header is pushed in front of them. + * + * To reuse the buffer for another command, or to prepare a buffer over + * other storage, use bt_hci_pkt_reset_cmd(). + * + * @param _name Name of the net_buf_simple object. + * @param _max_param_len Maximum number of parameter bytes the buffer holds. + */ +#define BT_HCI_PKT_CMD_DEFINE(_name, _max_param_len) \ +Z_BT_HCI_PKT_CMD_DEFINE(_name, _max_param_len, ) + +/** @brief Define a static buffer for an HCI command packet. + * + * Same as BT_HCI_PKT_CMD_DEFINE(), with static storage duration. + * + * @param _name Name of the net_buf_simple object. + * @param _max_param_len Maximum number of parameter bytes the buffer holds. + */ +#define BT_HCI_PKT_CMD_DEFINE_STATIC(_name, _max_param_len) \ +Z_BT_HCI_PKT_CMD_DEFINE(_name, _max_param_len, static) + +/** @brief Reset a buffer for a new HCI command packet. + * + * Empties @p buf and reserves headroom for bt_hci_pkt_push_cmd_hdr(), so + * that command parameters can be added with the net_buf_simple API. Use it + * to reuse a buffer defined with BT_HCI_PKT_CMD_DEFINE(), or to prepare a + * net_buf_simple initialized over caller-provided storage (for example with + * net_buf_simple_init_with_data()). The storage must hold at least + * @ref BT_HCI_PKT_CMD_HDR_SIZE bytes. + * + * @param buf Buffer to reset. + */ + +void bt_hci_pkt_reset_cmd(struct net_buf_simple *buf); + +/** @brief Push the packet indicator and command header of an HCI command + * packet. + * + * Prepends @c BT_HCI_H4_CMD and an HCI command header with @p opcode to the + * contents of @p buf, which must hold the command parameters (if any). + * The parameter length in the header is derived from the length of @p + * buf, and the buffer then contains the complete command packet. + * + * @param buf Buffer holding the command parameters, with at least + * @ref BT_HCI_PKT_CMD_HDR_SIZE bytes of headroom, as set up + * by BT_HCI_PKT_CMD_DEFINE() or bt_hci_pkt_reset_cmd(). + * @param opcode HCI command opcode. + * + * @retval 0 Success; @p buf holds the complete command packet. + * @retval -EINVAL @p buf has insufficient headroom for the prefix. + * @retval -EMSGSIZE @p buf holds more parameter bytes than an HCI command + * can carry. + */ + +int bt_hci_pkt_push_cmd_hdr(struct net_buf_simple *buf, uint16_t opcode); + +/** @brief Decoded HCI command response. + * + * Filled by bt_hci_pkt_pull_cmd_complete(), bt_hci_pkt_pull_cmd_status() + * and bt_hci_pkt_parse_cmd_rsp() from an HCI_Command_Complete or + * HCI_Command_Status event. + */ + +struct bt_hci_pkt_cmd_rsp +{ + /** @brief Opcode of the command the event responds to. + * + * @c BT_OP_NOP when the event responds to no command and only updates the + * number of command packets the controller accepts. + */ + + uint16_t opcode; + /** @brief Num_HCI_Command_Packets. + * + * The number of further HCI command packets the controller accepts. + */ + + uint8_t ncmd; + /** @brief Status code (@c BT_HCI_ERR_*). + * + * For HCI_Command_Complete this is the first return parameter. It is + * meaningful only when @ref opcode is not @c BT_OP_NOP: that event + * responds to no command and carries no status, and the field is then + * @c BT_HCI_ERR_SUCCESS. Match @ref opcode before acting on it. When + * an HCI_Command_Complete for a command lacks the status its return + * parameters start with, reported as @c -ENODATA by the decoders, the + * field is @c BT_HCI_ERR_UNSPECIFIED. + */ + + uint8_t status; + /** @brief Return parameters of the command. + * + * Point into the decoded event and start with the status, as the @c + * bt_hci_rp_* structures do. HCI_Command_Status carries no return + * parameters: @ref rp_len is then 0 and @ref rp points just past the + * event parameters, so that a zero-length copy stays well defined. + */ + + const uint8_t *rp; + + /** @brief Length of @ref rp in bytes. */ + + size_t rp_len; +}; + +/** @brief Pull an HCI_Command_Complete event. + * + * Decodes the event parameters of an HCI_Command_Complete event from @p + * buf, which must be positioned at the start of the event parameters, i.e. + * after the HCI event header. On success the event parameters preceding the + * return parameters have been pulled, leaving @p buf positioned at the + * return parameters of the command, which start with the status (as the @c + * bt_hci_rp_* structures do). + * + * @param buf Buffer positioned at the event parameters. + * @param rsp Decoded response. + * + * @retval 0 Success; @p buf is positioned at the return parameters. + * @retval -ENODATA The event responds to a command but carries no return + * parameters, not even the status; @p rsp is filled in with @c + * BT_HCI_ERR_UNSPECIFIED as status and @p buf is positioned as on success. + * @retval -EINVAL The event is malformed; @p buf and @p rsp are unchanged. + */ + +int bt_hci_pkt_pull_cmd_complete(struct net_buf_simple *buf, + struct bt_hci_pkt_cmd_rsp *rsp); + +/** @brief Pull an HCI_Command_Status event. + * + * Decodes the event parameters of an HCI_Command_Status event from @p buf, + * which must be positioned at the start of the event parameters, i.e. after + * the HCI event header. On success the event parameters have been pulled. + * + * @param buf Buffer positioned at the event parameters. + * @param rsp Decoded response. + * + * @retval 0 Success; the event parameters have been pulled from @p buf. + * @retval -EINVAL The event is malformed; @p buf and @p rsp are unchanged. + */ + +int bt_hci_pkt_pull_cmd_status(struct net_buf_simple *buf, + struct bt_hci_pkt_cmd_rsp *rsp); + +/** @brief Parse an HCI command response from a complete HCI packet. + * + * Examines a complete HCI packet, starting with its packet indicator, and + * decodes it when it is an HCI_Command_Complete or HCI_Command_Status + * event, leaving the return parameters of the command in @ref + * bt_hci_pkt_cmd_rsp.rp. Bytes beyond the length given in the event header + * are ignored, and the packet is not modified, so any received packet can + * be examined in place. + * + * @param pkt Complete HCI packet. + * @param len Length of @p pkt in bytes. + * @param rsp Decoded response. + * + * @retval 0 The packet is a command response. + * @retval -ENODATA The packet is an HCI_Command_Complete for a command that + * carries no return parameters, not even the status; @p rsp is filled in + * with @c BT_HCI_ERR_UNSPECIFIED as status. + * @retval -ENOMSG The packet is not a command response; @p rsp is + * unchanged. + * @retval -EINVAL The packet is a malformed HCI event; @p rsp is unchanged. + */ + +int bt_hci_pkt_parse_cmd_rsp(const uint8_t *pkt, + size_t len, struct bt_hci_pkt_cmd_rsp *rsp); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_HCI_PKT_H */ diff --git a/include/nuttx/net/bluetooth/hci_raw.h b/include/nuttx/net/bluetooth/hci_raw.h new file mode 100644 index 0000000000000..8198bc8aa15e6 --- /dev/null +++ b/include/nuttx/net/bluetooth/hci_raw.h @@ -0,0 +1,76 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/hci_raw.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Bluetooth HCI RAW channel handling + */ + +/* Copyright (c) 2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_HCI_RAW_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_HCI_RAW_H + +/** + * @brief HCI RAW channel + * @defgroup hci_raw HCI RAW channel + * @since 2.3 + * @version 1.0.0 + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** @brief Send packet to the Bluetooth controller + * + * Send packet to the Bluetooth controller. The buffers should be allocated + * using bt_buf_get_tx(). + * + * @param buf HCI packet to be sent. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_send(struct net_buf *buf); + +/** @brief Enable Bluetooth RAW channel: + * + * Enable Bluetooth RAW HCI channel. + * + * @param rx_queue netbuf queue where HCI packets received from the + * Bluetooth controller are to be queued. The queue is + * defined in the caller while the available buffers pools + * are handled in the stack. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_enable_raw(struct k_fifo *rx_queue); + +#ifdef __cplusplus +} +#endif +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_HCI_RAW_H */ diff --git a/include/nuttx/net/bluetooth/hci_types.h b/include/nuttx/net/bluetooth/hci_types.h new file mode 100644 index 0000000000000..16d693c2fee7a --- /dev/null +++ b/include/nuttx/net/bluetooth/hci_types.h @@ -0,0 +1,5349 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/hci_types.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* hci.h - Bluetooth Host Control Interface types */ + +/* Copyright (c) 2015-2016 Intel Corporation + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_HCI_TYPES_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_HCI_TYPES_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Bluetooth spec v5.4 Vol 4, Part A Table 2.1: HCI packet indicators + * The following definitions are intended for use with the UART Transport + * Layer and may be reused with other transport layers if desired. + */ +#define BT_HCI_H4_NONE 0x00 /* None of the known packet types */ +#define BT_HCI_H4_CMD 0x01 /* HCI Command packet */ +#define BT_HCI_H4_ACL 0x02 /* HCI ACL Data packet */ +#define BT_HCI_H4_SCO 0x03 /* HCI Synchronous Data packet */ +#define BT_HCI_H4_EVT 0x04 /* HCI Event packet */ +#define BT_HCI_H4_ISO 0x05 /* HCI ISO Data packet */ + +#define BT_HCI_OWN_ADDR_PUBLIC 0x00 +#define BT_HCI_OWN_ADDR_RANDOM 0x01 +#define BT_HCI_OWN_ADDR_RPA_OR_PUBLIC 0x02 +#define BT_HCI_OWN_ADDR_RPA_OR_RANDOM 0x03 +#define BT_HCI_OWN_ADDR_RPA_MASK 0x02 + +#define BT_HCI_PEER_ADDR_RPA_UNRESOLVED 0xfe +#define BT_HCI_PEER_ADDR_ANONYMOUS 0xff + +#define BT_ENC_KEY_SIZE_MIN 0x07 +#define BT_ENC_KEY_SIZE_MAX 0x10 + +#define BT_HCI_ADV_HANDLE_INVALID 0xff +#define BT_HCI_SYNC_HANDLE_INVALID 0xffff +#define BT_HCI_PAWR_SUBEVENT_MAX 128 + +/* Bluetooth spec v5.4 Vol 4, Part E - 5.4.3 HCI Synchronous Data Packets */ + +struct bt_hci_sco_hdr +{ + uint16_t handle; /* 12 bit handle, 2 bit Packet Status Flag, 1 bit RFU */ + uint8_t len; +} __packed; +#define BT_HCI_SCO_HDR_SIZE 3 + +#ifndef _BT_HCI_EVT_HDR_DEFINED +#define _BT_HCI_EVT_HDR_DEFINED +/* Bluetooth spec v5.4 Vol 4, Part E - 5.4.4 HCI Event Packet */ + +struct bt_hci_evt_hdr +{ + uint8_t evt; + uint8_t len; + uint8_t data[]; +} __packed; +#endif +#define BT_HCI_EVT_HDR_SIZE 2 + +#define BT_ACL_START_NO_FLUSH 0x00 +#define BT_ACL_CONT 0x01 +#define BT_ACL_START 0x02 +#define BT_ACL_COMPLETE 0x03 + +#define BT_ACL_POINT_TO_POINT 0x00 +#define BT_ACL_BROADCAST 0x01 + +#define BT_ACL_HANDLE_MASK BIT_MASK(12) + +#define bt_acl_handle(h) ((h) & BT_ACL_HANDLE_MASK) +#define bt_acl_flags(h) ((h) >> 12) +#define bt_acl_flags_pb(f) ((f) & BIT_MASK(2)) +#define bt_acl_flags_bc(f) ((f) >> 2) +#define bt_acl_handle_pack(h, f) ((h) | ((f) << 12)) + +#ifndef _BT_HCI_ACL_HDR_DEFINED +#define _BT_HCI_ACL_HDR_DEFINED +/* Bluetooth spec v5.4 Vol 4, Part E - 5.4.2 ACL Data Packets */ + +struct bt_hci_acl_hdr +{ + uint16_t handle; + uint16_t len; +} __packed; +#endif +#define BT_HCI_ACL_HDR_SIZE 4 + +#define BT_ISO_START 0x00 +#define BT_ISO_CONT 0x01 +#define BT_ISO_SINGLE 0x02 +#define BT_ISO_END 0x03 + +#define bt_iso_handle(h) ((h) & 0x0fff) +#define bt_iso_flags(h) ((h) >> 12) +#define bt_iso_flags_pb(f) ((f) & 0x0003) +#define bt_iso_flags_ts(f) (((f) >> 2) & 0x0001) +#define bt_iso_pack_flags(pb, ts) (((pb) & 0x0003) | (((ts) & 0x0001) << 2)) +#define bt_iso_handle_pack(h, pb, ts) ((h) | (bt_iso_pack_flags(pb, ts) << 12)) +#define bt_iso_hdr_len(h) ((h) & BIT_MASK(14)) + +#define BT_ISO_DATA_VALID 0x00 +#define BT_ISO_DATA_INVALID 0x01 +#define BT_ISO_DATA_NOP 0x02 + +#define bt_iso_pkt_len(h) ((h) & BIT_MASK(12)) +#define bt_iso_pkt_flags(h) ((h) >> 14) +#define bt_iso_pkt_len_pack(h, f) (((h) & BIT_MASK(12)) | ((f) << 14)) + +struct bt_hci_iso_sdu_hdr +{ + uint16_t sn; + uint16_t slen; /* 12 bit len, 2 bit RFU, 2 bit packet status */ +} __packed; +#define BT_HCI_ISO_SDU_HDR_SIZE 4 + +struct bt_hci_iso_sdu_ts_hdr +{ + uint32_t ts; + struct bt_hci_iso_sdu_hdr sdu; +} __packed; +#define BT_HCI_ISO_SDU_TS_HDR_SIZE 8 + +#ifndef _BT_HCI_ISO_HDR_DEFINED +#define _BT_HCI_ISO_HDR_DEFINED +/* Bluetooth spec v5.4 Vol 4, Part E - 5.4.5 HCI ISO Data Packets */ + +struct bt_hci_iso_hdr +{ + uint16_t handle; /* 12 bit handle, 2 bit PB flags, 1 bit TS_Flag, 1 bit RFU */ + uint16_t len; /* 14 bits, 2 bits RFU */ +} __packed; +#endif +#define BT_HCI_ISO_HDR_SIZE 4 + +/* Bluetooth spec v5.4 Vol 4, Part E - 5.4.1 HCI Command Packet */ + +struct bt_hci_cmd_hdr +{ + uint16_t opcode; + uint8_t param_len; +} __packed; +#define BT_HCI_CMD_HDR_SIZE 3 + +/* Supported Commands */ +#define BT_CMD_TEST(cmd, octet, bit) (cmd[octet] & BIT(bit)) +#define BT_CMD_LE_STATES(cmd) BT_CMD_TEST(cmd, 28, 3) + +#define BT_FEAT_TEST(feat, page, octet, bit) ((feat[page][octet] & BIT(bit)) != 0) + +#define BT_FEAT_BREDR(feat) !BT_FEAT_TEST(feat, 0, 4, 5) +#define BT_FEAT_LE(feat) BT_FEAT_TEST(feat, 0, 4, 6) +#define BT_FEAT_EXT_FEATURES(feat) BT_FEAT_TEST(feat, 0, 7, 7) +#define BT_FEAT_HOST_SSP(feat) BT_FEAT_TEST(feat, 1, 0, 0) +#define BT_FEAT_SC(feat) BT_FEAT_TEST(feat, 2, 1, 0) + +#define BT_FEAT_LMP_SCO_CAPABLE(feat) BT_FEAT_TEST(feat, 0, 1, 3) +#define BT_FEAT_LMP_ESCO_CAPABLE(feat) BT_FEAT_TEST(feat, 0, 3, 7) +#define BT_FEAT_HV2_PKT(feat) BT_FEAT_TEST(feat, 0, 1, 4) +#define BT_FEAT_HV3_PKT(feat) BT_FEAT_TEST(feat, 0, 1, 5) +#define BT_FEAT_EV4_PKT(feat) BT_FEAT_TEST(feat, 0, 4, 0) +#define BT_FEAT_EV5_PKT(feat) BT_FEAT_TEST(feat, 0, 4, 1) +#define BT_FEAT_2EV3_PKT(feat) BT_FEAT_TEST(feat, 0, 5, 5) +#define BT_FEAT_3EV3_PKT(feat) BT_FEAT_TEST(feat, 0, 5, 6) +#define BT_FEAT_3SLOT_PKT(feat) BT_FEAT_TEST(feat, 0, 5, 7) + +/* LE features */ +#define BT_LE_FEAT_BIT_ENC 0 +#define BT_LE_FEAT_BIT_CONN_PARAM_REQ 1 +#define BT_LE_FEAT_BIT_EXT_REJ_IND 2 +#define BT_LE_FEAT_BIT_PER_INIT_FEAT_XCHG 3 +#define BT_LE_FEAT_BIT_PING 4 +#define BT_LE_FEAT_BIT_DLE 5 +#define BT_LE_FEAT_BIT_PRIVACY 6 +#define BT_LE_FEAT_BIT_EXT_SCAN 7 +#define BT_LE_FEAT_BIT_PHY_2M 8 +#define BT_LE_FEAT_BIT_SMI_TX 9 +#define BT_LE_FEAT_BIT_SMI_RX 10 +#define BT_LE_FEAT_BIT_PHY_CODED 11 +#define BT_LE_FEAT_BIT_EXT_ADV 12 +#define BT_LE_FEAT_BIT_PER_ADV 13 +#define BT_LE_FEAT_BIT_CHAN_SEL_ALGO_2 14 +#define BT_LE_FEAT_BIT_PWR_CLASS_1 15 +#define BT_LE_FEAT_BIT_MIN_USED_CHAN_PROC 16 +#define BT_LE_FEAT_BIT_CONN_CTE_REQ 17 +#define BT_LE_FEAT_BIT_CONN_CTE_RESP 18 +#define BT_LE_FEAT_BIT_CONNECTIONLESS_CTE_TX 19 +#define BT_LE_FEAT_BIT_CONNECTIONLESS_CTE_RX 20 +#define BT_LE_FEAT_BIT_ANT_SWITCH_TX_AOD 21 +#define BT_LE_FEAT_BIT_ANT_SWITCH_RX_AOA 22 +#define BT_LE_FEAT_BIT_RX_CTE 23 +#define BT_LE_FEAT_BIT_PAST_SEND 24 +#define BT_LE_FEAT_BIT_PAST_RECV 25 +#define BT_LE_FEAT_BIT_SCA_UPDATE 26 +#define BT_LE_FEAT_BIT_REMOTE_PUB_KEY_VALIDATE 27 +#define BT_LE_FEAT_BIT_CIS_CENTRAL 28 +#define BT_LE_FEAT_BIT_CIS_PERIPHERAL 29 +#define BT_LE_FEAT_BIT_ISO_BROADCASTER 30 +#define BT_LE_FEAT_BIT_SYNC_RECEIVER 31 +#define BT_LE_FEAT_BIT_ISO_CHANNELS 32 +#define BT_LE_FEAT_BIT_PWR_CTRL_REQ 33 +#define BT_LE_FEAT_BIT_PWR_CHG_IND 34 +#define BT_LE_FEAT_BIT_PATH_LOSS_MONITOR 35 +#define BT_LE_FEAT_BIT_PER_ADV_ADI_SUPP 36 +#define BT_LE_FEAT_BIT_CONN_SUBRATING 37 +#define BT_LE_FEAT_BIT_CONN_SUBRATING_HOST_SUPP 38 +#define BT_LE_FEAT_BIT_CHANNEL_CLASSIFICATION 39 +#define BT_LE_FEAT_BIT_ADV_CODING_SEL 40 +#define BT_LE_FEAT_BIT_ADV_CODING_SEL_HOST 41 +#define BT_LE_FEAT_BIT_DECISION_ADV_FILTER 42 +#define BT_LE_FEAT_BIT_PAWR_ADVERTISER 43 +#define BT_LE_FEAT_BIT_PAWR_SCANNER 44 +#define BT_LE_FEAT_BIT_UNSEG_FRAMED_MODE 45 +#define BT_LE_FEAT_BIT_CHANNEL_SOUNDING 46 +#define BT_LE_FEAT_BIT_CHANNEL_SOUNDING_HOST 47 +#define BT_LE_FEAT_BIT_CHANNEL_SOUNDING_TONE_QUAL_IND 48 +#define BT_LE_FEAT_BIT_EXTENDED_FEAT_SET 63 +#define BT_LE_FEAT_BIT_FRAME_SPACE_UPDATE 65 +#define BT_LE_FEAT_BIT_SHORTER_CONN_INTERVALS 72 +#define BT_LE_FEAT_BIT_SHORTER_CONN_INTERVALS_HOST_SUPP 73 + +#define BT_LE_FEAT_TEST(feat, n) (feat[(n) >> 3] & BIT((n) & 7)) + +#define BT_FEAT_LE_ENCR(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_ENC) +#define BT_FEAT_LE_CONN_PARAM_REQ_PROC(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_CONN_PARAM_REQ) +#define BT_FEAT_LE_PER_INIT_FEAT_XCHG(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_PER_INIT_FEAT_XCHG) +#define BT_FEAT_LE_DLE(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_DLE) +#define BT_FEAT_LE_PHY_2M(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_PHY_2M) +#define BT_FEAT_LE_PHY_CODED(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_PHY_CODED) +#define BT_FEAT_LE_PRIVACY(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_PRIVACY) +/** Test if the Extended Scanner Filter Policies feature is supported */ +#define BT_FEAT_LE_EXT_SCAN(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_EXT_SCAN) +#define BT_FEAT_LE_EXT_ADV(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_EXT_ADV) +#define BT_FEAT_LE_EXT_PER_ADV(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_PER_ADV) +#define BT_FEAT_LE_CONNECTION_CTE_REQ(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_CONN_CTE_REQ) +#define BT_FEAT_LE_CONNECTION_CTE_RESP(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_CONN_CTE_RESP) +#define BT_FEAT_LE_CONNECTIONLESS_CTE_TX(feat) \ +BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_CONNECTIONLESS_CTE_TX) +#define BT_FEAT_LE_CONNECTIONLESS_CTE_RX(feat) \ +BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_CONNECTIONLESS_CTE_RX) +#define BT_FEAT_LE_ANT_SWITCH_TX_AOD(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_ANT_SWITCH_TX_AOD) +#define BT_FEAT_LE_ANT_SWITCH_RX_AOA(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_ANT_SWITCH_RX_AOA) +#define BT_FEAT_LE_RX_CTE(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_RX_CTE) +#define BT_FEAT_LE_PAST_SEND(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_PAST_SEND) +#define BT_FEAT_LE_PAST_RECV(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_PAST_RECV) +#define BT_FEAT_LE_CIS_CENTRAL(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_CIS_CENTRAL) +#define BT_FEAT_LE_CIS_PERIPHERAL(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_CIS_PERIPHERAL) +#define BT_FEAT_LE_ISO_BROADCASTER(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_ISO_BROADCASTER) +#define BT_FEAT_LE_SYNC_RECEIVER(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_SYNC_RECEIVER) +#define BT_FEAT_LE_ISO_CHANNELS(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_ISO_CHANNELS) +#define BT_FEAT_LE_PWR_CTRL_REQ(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_PWR_CTRL_REQ) +#define BT_FEAT_LE_PWR_CHG_IND(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_PWR_CHG_IND) +#define BT_FEAT_LE_PATH_LOSS_MONITOR(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_PATH_LOSS_MONITOR) +#define BT_FEAT_LE_PER_ADV_ADI_SUPP(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_PER_ADV_ADI_SUPP) +#define BT_FEAT_LE_CONN_SUBRATING(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_CONN_SUBRATING) +#define BT_FEAT_LE_CONN_SUBRATING_HOST_SUPP(feat) \ +BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_CONN_SUBRATING_HOST_SUPP) +#define BT_FEAT_LE_CHANNEL_CLASSIFICATION(feat) \ +BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_CHANNEL_CLASSIFICATION) +#define BT_FEAT_LE_ADV_CODING_SEL(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_ADV_CODING_SEL) +#define BT_FEAT_LE_ADV_CODING_SEL_HOST(feat) \ +BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_ADV_CODING_SEL_HOST) +#define BT_FEAT_LE_PAWR_ADVERTISER(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_PAWR_ADVERTISER) +#define BT_FEAT_LE_PAWR_SCANNER(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_PAWR_SCANNER) +#define BT_FEAT_LE_CHANNEL_SOUNDING(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_CHANNEL_SOUNDING) +#define BT_FEAT_LE_CHANNEL_SOUNDING_HOST(feat) \ +BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_CHANNEL_SOUNDING_HOST) +#define BT_FEAT_LE_EXTENDED_FEAT_SET(feat) BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_EXTENDED_FEAT_SET) +#define BT_FEAT_LE_FRAME_SPACE_UPDATE_SET(feat) \ +BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_FRAME_SPACE_UPDATE) +#define BT_FEAT_LE_SHORTER_CONN_INTERVALS(feat) \ +BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_SHORTER_CONN_INTERVALS) +#define BT_FEAT_LE_SHORTER_CONN_INTERVALS_HOST_SUPP(feat) \ +BT_LE_FEAT_TEST(feat, BT_LE_FEAT_BIT_SHORTER_CONN_INTERVALS_HOST_SUPP) + +#define BT_FEAT_LE_CIS(feat) (BT_FEAT_LE_CIS_CENTRAL(feat) | BT_FEAT_LE_CIS_PERIPHERAL(feat)) +#define BT_FEAT_LE_BIS(feat) (BT_FEAT_LE_ISO_BROADCASTER(feat) | BT_FEAT_LE_SYNC_RECEIVER(feat)) +#define BT_FEAT_LE_ISO(feat) (BT_FEAT_LE_CIS(feat) | BT_FEAT_LE_BIS(feat)) + +/* LE States. See Core_v5.4, Vol 4, Part E, Section 7.8.27 */ +#define BT_LE_STATES_PER_CONN_ADV(states) (states & BIT64_MASK(38)) + +#if defined(CONFIG_BT_SCAN_AND_INITIATE_IN_PARALLEL) +/* Both passive and active scanner can be run in parallel with initiator. */ +#define BT_LE_STATES_SCAN_INIT(states) ((states) & BIT64_MASK(22) && (states) & BIT64_MASK(23)) + +#else +#define BT_LE_STATES_SCAN_INIT(states) 0 +#endif + +/* Bonding/authentication types */ +#define BT_HCI_NO_BONDING 0x00 +#define BT_HCI_NO_BONDING_MITM 0x01 +#define BT_HCI_DEDICATED_BONDING 0x02 +#define BT_HCI_DEDICATED_BONDING_MITM 0x03 +#define BT_HCI_GENERAL_BONDING 0x04 +#define BT_HCI_GENERAL_BONDING_MITM 0x05 + +/* MITM protection is enabled in SSP authentication requirements octet when + * LSB bit is set. + */ +#define BT_MITM 0x01 + +/* I/O capabilities */ +#define BT_IO_DISPLAY_ONLY 0x00 +#define BT_IO_DISPLAY_YESNO 0x01 +#define BT_IO_KEYBOARD_ONLY 0x02 +#define BT_IO_NO_INPUT_OUTPUT 0x03 + +/* SCO packet types */ +#define HCI_PKT_TYPE_HV1 0x0020 +#define HCI_PKT_TYPE_HV2 0x0040 +#define HCI_PKT_TYPE_HV3 0x0080 + +/* eSCO packet types */ +#define HCI_PKT_TYPE_SCO_HV1 0x0001 +#define HCI_PKT_TYPE_SCO_HV2 0x0002 +#define HCI_PKT_TYPE_SCO_HV3 0x0004 +#define HCI_PKT_TYPE_ESCO_EV3 0x0008 +#define HCI_PKT_TYPE_ESCO_EV4 0x0010 +#define HCI_PKT_TYPE_ESCO_EV5 0x0020 +#define HCI_PKT_TYPE_ESCO_2EV3 0x0040 +#define HCI_PKT_TYPE_ESCO_3EV3 0x0080 +#define HCI_PKT_TYPE_ESCO_2EV5 0x0100 +#define HCI_PKT_TYPE_ESCO_3EV5 0x0200 + +#define ESCO_PKT_MASK \ +(HCI_PKT_TYPE_SCO_HV1 | HCI_PKT_TYPE_SCO_HV2 | HCI_PKT_TYPE_SCO_HV3 | HCI_PKT_TYPE_ESCO_EV3 | \ + HCI_PKT_TYPE_ESCO_EV4 | HCI_PKT_TYPE_ESCO_EV5) +#define SCO_PKT_MASK (HCI_PKT_TYPE_SCO_HV1 | HCI_PKT_TYPE_SCO_HV2 | HCI_PKT_TYPE_SCO_HV3) +#define EDR_ESCO_PKT_MASK \ +(HCI_PKT_TYPE_ESCO_2EV3 | HCI_PKT_TYPE_ESCO_3EV3 | HCI_PKT_TYPE_ESCO_2EV5 | \ + HCI_PKT_TYPE_ESCO_3EV5) + +/* HCI BR/EDR link types */ +#define BT_HCI_SCO 0x00 +#define BT_HCI_ACL 0x01 +#define BT_HCI_ESCO 0x02 + +/* OpCode Group Fields */ +#define BT_OGF_LINK_CTRL 0x01 +#define BT_OGF_LINK_POLICY 0x02 +#define BT_OGF_BASEBAND 0x03 +#define BT_OGF_INFO 0x04 +#define BT_OGF_STATUS 0x05 +#define BT_OGF_LE 0x08 +#define BT_OGF_VS 0x3f + +/* Construct OpCode from OGF and OCF */ +#define BT_OP(ogf, ocf) ((ocf) | ((ogf) << 10)) + +/* Invalid opcode */ +#define BT_OP_NOP 0x0000 + +/* Obtain OGF from OpCode */ +#define BT_OGF(opcode) (((opcode) >> 10) & BIT_MASK(6)) +/* Obtain OCF from OpCode */ +#define BT_OCF(opcode) ((opcode) & BIT_MASK(10)) + +#define BT_HCI_OP_INQUIRY BT_OP(BT_OGF_LINK_CTRL, 0x0001) /* 0x0401 */ +struct bt_hci_op_inquiry +{ + uint8_t lap[3]; + uint8_t length; + uint8_t num_rsp; +} __packed; + +#define BT_HCI_OP_INQUIRY_CANCEL BT_OP(BT_OGF_LINK_CTRL, 0x0002) /* 0x0402 */ + +#define BT_HCI_OP_CONNECT BT_OP(BT_OGF_LINK_CTRL, 0x0005) /* 0x0405 */ +struct bt_hci_cp_connect +{ + bt_addr_t bdaddr; + uint16_t packet_type; + uint8_t pscan_rep_mode; + uint8_t reserved; + uint16_t clock_offset; + uint8_t allow_role_switch; +} __packed; + +#define BT_HCI_OP_DISCONNECT BT_OP(BT_OGF_LINK_CTRL, 0x0006) /* 0x0406 */ +struct bt_hci_cp_disconnect +{ + uint16_t handle; + uint8_t reason; +} __packed; + +#define BT_HCI_OP_CONNECT_CANCEL BT_OP(BT_OGF_LINK_CTRL, 0x0008) /* 0x0408 */ +struct bt_hci_cp_connect_cancel +{ + bt_addr_t bdaddr; +} __packed; +struct bt_hci_rp_connect_cancel +{ + uint8_t status; + bt_addr_t bdaddr; +} __packed; + +#define BT_HCI_OP_ACCEPT_CONN_REQ BT_OP(BT_OGF_LINK_CTRL, 0x0009) /* 0x0409 */ +struct bt_hci_cp_accept_conn_req +{ + bt_addr_t bdaddr; + uint8_t role; +} __packed; + +#define BT_HCI_VOICE_SETTING_AIR_CODING_FMT_CVSD 0x00 +#define BT_HCI_VOICE_SETTING_AIR_CODING_FMT_ULAW 0x01 +#define BT_HCI_VOICE_SETTING_AIR_CODING_FMT_ALAW 0x02 +#define BT_HCI_VOICE_SETTING_AIR_CODING_FMT_TRANSPARENT 0x03 + +#define BT_HCI_VOICE_SETTING_PCM_BIT_POS_DEFAULT 0x00 + +#define BT_HCI_VOICE_SETTING_SAMPLE_SIZE_8_BITS 0x00 +#define BT_HCI_VOICE_SETTING_SAMPLE_SIZE_16_BITS 0x01 + +#define BT_HCI_VOICE_SETTING_DATA_FMT_1_COMPLEMENT 0x00 +#define BT_HCI_VOICE_SETTING_DATA_FMT_2_COMPLEMENT 0x01 +#define BT_HCI_VOICE_SETTING_DATA_FMT_SIGNED 0x02 +#define BT_HCI_VOICE_SETTING_DATA_FMT_UNSIGNED 0x03 + +#define BT_HCI_VOICE_SETTING_CODING_FMT_LINEAR 0x00 +#define BT_HCI_VOICE_SETTING_CODING_FMT_ULAW 0x01 +#define BT_HCI_VOICE_SETTING_CODING_FMT_ALAW 0x02 + +#define BT_HCI_VOICE_SETTING_AIR_CODING_FMT_MASK GENMASK(1, 0) +#define BT_HCI_VOICE_SETTING_PCM_BIT_POS_MASK GENMASK(2, 4) +#define BT_HCI_VOICE_SETTING_SAMPLE_SIZE_MASK GENMASK(5, 5) +#define BT_HCI_VOICE_SETTING_DATA_FMT_MASK GENMASK(7, 6) +#define BT_HCI_VOICE_SETTING_CODING_FMT_MASK GENMASK(9, 8) + +#define BT_HCI_VOICE_SETTING_AIR_CODING_FMT_PREP(val) \ +FIELD_PREP(BT_HCI_VOICE_SETTING_AIR_CODING_FMT_MASK, val) +#define BT_HCI_VOICE_SETTING_PCM_BIT_POS_PREP(val) \ +FIELD_PREP(BT_HCI_VOICE_SETTING_PCM_BIT_POS_MASK, val) +#define BT_HCI_VOICE_SETTING_SAMPLE_SIZE_PREP(val) \ +FIELD_PREP(BT_HCI_VOICE_SETTING_SAMPLE_SIZE_MASK, val) +#define BT_HCI_VOICE_SETTING_DATA_FMT_PREP(val) FIELD_PREP(BT_HCI_VOICE_SETTING_DATA_FMT_MASK, val) +#define BT_HCI_VOICE_SETTING_CODING_FMT_PREP(val) \ +FIELD_PREP(BT_HCI_VOICE_SETTING_CODING_FMT_MASK, val) + +#define BT_HCI_VOICE_SETTING_AIR_CODING_FMT_GET(val) \ +FIELD_GET(BT_HCI_VOICE_SETTING_AIR_CODING_FMT_MASK, val) +#define BT_HCI_VOICE_SETTING_PCM_BIT_POS_GET(val) \ +FIELD_GET(BT_HCI_VOICE_SETTING_PCM_BIT_POS_MASK, val) +#define BT_HCI_VOICE_SETTING_SAMPLE_SIZE_GET(val) \ +FIELD_GET(BT_HCI_VOICE_SETTING_SAMPLE_SIZE_MASK, val) +#define BT_HCI_VOICE_SETTING_DATA_FMT_GET(val) FIELD_GET(BT_HCI_VOICE_SETTING_DATA_FMT_MASK, val) +#define BT_HCI_VOICE_SETTING_CODING_FMT_GET(val) \ +FIELD_GET(BT_HCI_VOICE_SETTING_CODING_FMT_MASK, val) + +#define BT_HCI_VOICE_SETTINGS(air_coding_fmt, pcm_bit_pos, sample_size, data_fmt, coding_fmt) \ +(BT_HCI_VOICE_SETTING_AIR_CODING_FMT_PREP(air_coding_fmt) | \ + BT_HCI_VOICE_SETTING_PCM_BIT_POS_PREP(pcm_bit_pos) | \ + BT_HCI_VOICE_SETTING_SAMPLE_SIZE_PREP(sample_size) | \ + BT_HCI_VOICE_SETTING_DATA_FMT_PREP(data_fmt) | \ + BT_HCI_VOICE_SETTING_CODING_FMT_PREP(coding_fmt)) + +#define BT_HCI_SCO_MAX_LATENCY_DEFAULT 0xffff /* do not care about SCO link latency */ + +#define BT_HCI_SCO_RETRANS_EFFORT_DEFAULT 0xff /* do not care about SCO retransmission effort */ + +#define BT_HCI_OP_SETUP_SYNC_CONN BT_OP(BT_OGF_LINK_CTRL, 0x0028) /* 0x0428 */ +struct bt_hci_cp_setup_sync_conn +{ + uint16_t handle; + uint32_t tx_bandwidth; + uint32_t rx_bandwidth; + uint16_t max_latency; + uint16_t content_format; + uint8_t retrans_effort; + uint16_t pkt_type; +} __packed; + +#define BT_HCI_OP_ACCEPT_SYNC_CONN_REQ BT_OP(BT_OGF_LINK_CTRL, 0x0029) /* 0x0429 */ +struct bt_hci_cp_accept_sync_conn_req +{ + bt_addr_t bdaddr; + uint32_t tx_bandwidth; + uint32_t rx_bandwidth; + uint16_t max_latency; + uint16_t content_format; + uint8_t retrans_effort; + uint16_t pkt_type; +} __packed; + +#define BT_HCI_OP_REJECT_CONN_REQ BT_OP(BT_OGF_LINK_CTRL, 0x000a) /* 0x040a */ +struct bt_hci_cp_reject_conn_req +{ + bt_addr_t bdaddr; + uint8_t reason; +} __packed; + +#define BT_HCI_OP_LINK_KEY_REPLY BT_OP(BT_OGF_LINK_CTRL, 0x000b) /* 0x040b */ +struct bt_hci_cp_link_key_reply +{ + bt_addr_t bdaddr; + uint8_t link_key[16]; +} __packed; + +#define BT_HCI_OP_LINK_KEY_NEG_REPLY BT_OP(BT_OGF_LINK_CTRL, 0x000c) /* 0x040c */ +struct bt_hci_cp_link_key_neg_reply +{ + bt_addr_t bdaddr; +} __packed; + +#define BT_HCI_OP_PIN_CODE_REPLY BT_OP(BT_OGF_LINK_CTRL, 0x000d) /* 0x040d */ +struct bt_hci_cp_pin_code_reply +{ + bt_addr_t bdaddr; + uint8_t pin_len; + uint8_t pin_code[16]; +} __packed; +struct bt_hci_rp_pin_code_reply +{ + uint8_t status; + bt_addr_t bdaddr; +} __packed; + +#define BT_HCI_OP_PIN_CODE_NEG_REPLY BT_OP(BT_OGF_LINK_CTRL, 0x000e) /* 0x040e */ +struct bt_hci_cp_pin_code_neg_reply +{ + bt_addr_t bdaddr; +} __packed; +struct bt_hci_rp_pin_code_neg_reply +{ + uint8_t status; + bt_addr_t bdaddr; +} __packed; + +/** HCI Change Connection Packet Type opcode */ +#define BT_HCI_OP_CHANGE_CONN_PACKET_TYPE BT_OP(BT_OGF_LINK_CTRL, 0x000f) /* 0x040f */ + +/** HCI Change Connection Packet Type command parameters */ + +struct bt_hci_cp_change_conn_packet_type +{ + /** Connection handle */ + + uint16_t handle; + + /** Packet type bitmask */ + + uint16_t packet_type; +} __packed; + +/* ACL packet type bits for HCI_Change_Connection_Packet_Type command. + * BR packet types (set bit = may be used): See Core Spec v6.0, Vol 4, Part + * E, Section 7.1.14 EDR packet types (set bit = shall NOT be used): Note: + * EDR bits use reverse logic compared to BR bits. + */ + +/** 2-DH1 shall not be used */ +#define BT_HCI_ACL_PKT_TYPE_NO_2DH1 BIT(1) +/** 3-DH1 shall not be used */ +#define BT_HCI_ACL_PKT_TYPE_NO_3DH1 BIT(2) +/** DM1 may be used */ +#define BT_HCI_ACL_PKT_TYPE_DM1 BIT(3) +/** DH1 may be used */ +#define BT_HCI_ACL_PKT_TYPE_DH1 BIT(4) +/** 2-DH3 shall not be used */ +#define BT_HCI_ACL_PKT_TYPE_NO_2DH3 BIT(8) +/** 3-DH3 shall not be used */ +#define BT_HCI_ACL_PKT_TYPE_NO_3DH3 BIT(9) +/** DM3 may be used */ +#define BT_HCI_ACL_PKT_TYPE_DM3 BIT(10) +/** DH3 may be used */ +#define BT_HCI_ACL_PKT_TYPE_DH3 BIT(11) +/** 2-DH5 shall not be used */ +#define BT_HCI_ACL_PKT_TYPE_NO_2DH5 BIT(12) +/** 3-DH5 shall not be used */ +#define BT_HCI_ACL_PKT_TYPE_NO_3DH5 BIT(13) +/** DM5 may be used */ +#define BT_HCI_ACL_PKT_TYPE_DM5 BIT(14) +/** DH5 may be used */ +#define BT_HCI_ACL_PKT_TYPE_DH5 BIT(15) + +/** Bitmask of all BR basic rate ACL packet types */ +#define BT_HCI_ACL_PKT_TYPE_BR_MASK \ +(BT_HCI_ACL_PKT_TYPE_DM1 | BT_HCI_ACL_PKT_TYPE_DH1 | BT_HCI_ACL_PKT_TYPE_DM3 | \ + BT_HCI_ACL_PKT_TYPE_DH3 | BT_HCI_ACL_PKT_TYPE_DM5 | BT_HCI_ACL_PKT_TYPE_DH5) + +#define BT_HCI_OP_AUTH_REQUESTED BT_OP(BT_OGF_LINK_CTRL, 0x0011) /* 0x0411 */ +struct bt_hci_cp_auth_requested +{ + uint16_t handle; +} __packed; + +#define BT_HCI_OP_SET_CONN_ENCRYPT BT_OP(BT_OGF_LINK_CTRL, 0x0013) /* 0x0413 */ +struct bt_hci_cp_set_conn_encrypt +{ + uint16_t handle; + uint8_t encrypt; +} __packed; + +#define BT_HCI_OP_REMOTE_NAME_REQUEST BT_OP(BT_OGF_LINK_CTRL, 0x0019) /* 0x0419 */ +struct bt_hci_cp_remote_name_request +{ + bt_addr_t bdaddr; + uint8_t pscan_rep_mode; + uint8_t reserved; + uint16_t clock_offset; +} __packed; + +#define BT_HCI_OP_REMOTE_NAME_CANCEL BT_OP(BT_OGF_LINK_CTRL, 0x001a) /* 0x041a */ +struct bt_hci_cp_remote_name_cancel +{ + bt_addr_t bdaddr; +} __packed; +struct bt_hci_rp_remote_name_cancel +{ + uint8_t status; + bt_addr_t bdaddr; +} __packed; + +#define BT_HCI_OP_READ_REMOTE_FEATURES BT_OP(BT_OGF_LINK_CTRL, 0x001b) /* 0x041b */ +struct bt_hci_cp_read_remote_features +{ + uint16_t handle; +} __packed; + +#define BT_HCI_OP_READ_REMOTE_EXT_FEATURES BT_OP(BT_OGF_LINK_CTRL, 0x001c) /* 0x041c */ +struct bt_hci_cp_read_remote_ext_features +{ + uint16_t handle; + uint8_t page; +} __packed; + +#define BT_HCI_OP_READ_REMOTE_VERSION_INFO BT_OP(BT_OGF_LINK_CTRL, 0x001d) /* 0x041d */ +struct bt_hci_cp_read_remote_version_info +{ + uint16_t handle; +} __packed; + +#define BT_HCI_OP_IO_CAPABILITY_REPLY BT_OP(BT_OGF_LINK_CTRL, 0x002b) /* 0x042b */ +struct bt_hci_cp_io_capability_reply +{ + bt_addr_t bdaddr; + uint8_t capability; + uint8_t oob_data; + uint8_t authentication; +} __packed; + +#define BT_HCI_OP_USER_CONFIRM_REPLY BT_OP(BT_OGF_LINK_CTRL, 0x002c) /* 0x042c */ +#define BT_HCI_OP_USER_CONFIRM_NEG_REPLY BT_OP(BT_OGF_LINK_CTRL, 0x002d) /* 0x042d */ +struct bt_hci_cp_user_confirm_reply +{ + bt_addr_t bdaddr; +} __packed; +struct bt_hci_rp_user_confirm_reply +{ + uint8_t status; + bt_addr_t bdaddr; +} __packed; + +#define BT_HCI_OP_USER_PASSKEY_REPLY BT_OP(BT_OGF_LINK_CTRL, 0x002e) /* 0x042e */ +struct bt_hci_cp_user_passkey_reply +{ + bt_addr_t bdaddr; + uint32_t passkey; +} __packed; + +#define BT_HCI_OP_USER_PASSKEY_NEG_REPLY BT_OP(BT_OGF_LINK_CTRL, 0x002f) /* 0x042f */ +struct bt_hci_cp_user_passkey_neg_reply +{ + bt_addr_t bdaddr; +} __packed; + +#define BT_HCI_OP_IO_CAPABILITY_NEG_REPLY BT_OP(BT_OGF_LINK_CTRL, 0x0034) /* 0x0434 */ +struct bt_hci_cp_io_capability_neg_reply +{ + bt_addr_t bdaddr; + uint8_t reason; +} __packed; + +#define BT_HCI_OP_SWITCH_ROLE BT_OP(BT_OGF_LINK_POLICY, 0x000b) +struct bt_hci_cp_switch_role +{ + bt_addr_t bdaddr; + uint8_t role; +} __packed; + +#define BT_HCI_LINK_POLICY_SETTINGS_ENABLE_ROLE_SWITCH BIT(0) +#define BT_HCI_LINK_POLICY_SETTINGS_ENABLE_HOLD_MODE BIT(1) +#define BT_HCI_LINK_POLICY_SETTINGS_ENABLE_SNIFF_SWITCH BIT(2) + +#define BT_HCI_OP_READ_LINK_POLICY_SETTINGS BT_OP(BT_OGF_LINK_POLICY, 0x000c) +struct bt_hci_cp_read_link_policy_settings +{ + uint16_t handle; +} __packed; +struct bt_hci_rp_read_link_policy_settings +{ + uint8_t status; + uint16_t handle; + uint16_t link_policy_settings; +} __packed; + +#define BT_HCI_OP_WRITE_LINK_POLICY_SETTINGS BT_OP(BT_OGF_LINK_POLICY, 0x000d) +struct bt_hci_cp_write_link_policy_settings +{ + uint16_t handle; + uint16_t link_policy_settings; +} __packed; + +#define BT_HCI_OP_READ_DEFAULT_LINK_POLICY_SETTINGS BT_OP(BT_OGF_LINK_POLICY, 0x000e) +struct bt_hci_rp_read_default_link_policy_settings +{ + uint8_t status; + uint16_t default_link_policy_settings; +} __packed; + +#define BT_HCI_OP_WRITE_DEFAULT_LINK_POLICY_SETTINGS BT_OP(BT_OGF_LINK_POLICY, 0x000f) +struct bt_hci_cp_write_default_link_policy_settings +{ + uint16_t default_link_policy_settings; +} __packed; + +#define BT_HCI_OP_SNIFF_MODE BT_OP(BT_OGF_LINK_POLICY, 0x0003) /* 0x0803 */ +struct bt_hci_cp_sniff_mode +{ + uint16_t handle; + uint16_t max_interval; + uint16_t min_interval; + uint16_t attempt; + uint16_t timeout; +} __packed; + +#define BT_HCI_OP_EXIT_SNIFF_MODE BT_OP(BT_OGF_LINK_POLICY, 0x0004) /* 0x0804 */ +struct bt_hci_cp_exit_sniff_mode +{ + uint16_t handle; +} __packed; + +/** HCI Sniff Subrating command opcode */ +#define BT_HCI_OP_SNIFF_SUBRATING BT_OP(BT_OGF_LINK_POLICY, 0x0011) /* 0x0811 */ + +/** HCI Sniff Subrating command parameters */ + +struct bt_hci_cp_sniff_subrating +{ + /** ACL connection handle */ + + uint16_t handle; + + /** Maximum latency */ + + uint16_t max_latency; + + /** Minimum remote timeout */ + + uint16_t min_remote_timeout; + + /** Minimum local timeout */ + + uint16_t min_local_timeout; +} __packed; + +/** HCI Sniff Subrating return parameters */ + +struct bt_hci_rp_sniff_subrating +{ + /** Status */ + + uint8_t status; + + /** ACL connection handle */ + + uint16_t handle; +} __packed; + +#define BT_HCI_OP_SET_EVENT_MASK BT_OP(BT_OGF_BASEBAND, 0x0001) /* 0x0c01 */ +struct bt_hci_cp_set_event_mask +{ + uint8_t events[8]; +} __packed; + +#define BT_HCI_OP_RESET BT_OP(BT_OGF_BASEBAND, 0x0003) /* 0x0c03 */ + +#define BT_HCI_OP_WRITE_LOCAL_NAME BT_OP(BT_OGF_BASEBAND, 0x0013) /* 0x0c13 */ +struct bt_hci_write_local_name +{ + uint8_t local_name[248]; +} __packed; + +#define BT_HCI_OP_READ_CONN_ACCEPT_TIMEOUT BT_OP(BT_OGF_BASEBAND, 0x0015) /* 0x0c15 */ +struct bt_hci_rp_read_conn_accept_timeout +{ + uint8_t status; + uint16_t conn_accept_timeout; +} __packed; + +#define BT_HCI_OP_WRITE_CONN_ACCEPT_TIMEOUT BT_OP(BT_OGF_BASEBAND, 0x0016) /* 0x0c16 */ +struct bt_hci_cp_write_conn_accept_timeout +{ + uint16_t conn_accept_timeout; +} __packed; + +struct bt_hci_rp_write_conn_accept_timeout +{ + uint8_t status; +} __packed; + +#define BT_HCI_OP_WRITE_PAGE_TIMEOUT BT_OP(BT_OGF_BASEBAND, 0x0018) /* 0x0c18 */ + +#define BT_HCI_OP_WRITE_SCAN_ENABLE BT_OP(BT_OGF_BASEBAND, 0x001a) /* 0x0c1a */ +#define BT_BREDR_SCAN_DISABLED 0x00 +#define BT_BREDR_SCAN_INQUIRY 0x01 +#define BT_BREDR_SCAN_PAGE 0x02 + +/** HCI opcode for Write Page Scan Activity. */ +#define BT_HCI_OP_WRITE_PAGE_SCAN_ACTIVITY BT_OP(BT_OGF_BASEBAND, 0x001c) /* 0x0c1c */ + +/** HCI command parameters for Write Page Scan Activity. */ + +struct bt_hci_cp_write_page_scan_activity +{ + /** Page scan interval in 0.625 ms units. */ + + uint16_t interval; + + /** Page scan window in 0.625 ms units. */ + + uint16_t window; +} __packed; + +/** HCI opcode for Write Inquiry Scan Activity. */ +#define BT_HCI_OP_WRITE_INQUIRY_SCAN_ACTIVITY BT_OP(BT_OGF_BASEBAND, 0x001e) /* 0x0c1e */ + +/** HCI command parameters for Write Inquiry Scan Activity. */ + +struct bt_hci_cp_write_inquiry_scan_activity +{ + /** Inquiry scan interval in 0.625 ms units. */ + + uint16_t interval; + + /** Inquiry scan window in 0.625 ms units. */ + + uint16_t window; +} __packed; + +#define BT_HCI_OP_READ_CLASS_OF_DEVICE BT_OP(BT_OGF_BASEBAND, 0x0023) /* 0x0c23 */ +struct bt_hci_rp_read_class_of_device +{ + uint8_t status; + uint8_t class_of_device[3]; +} __packed; + +#define BT_HCI_OP_WRITE_CLASS_OF_DEVICE BT_OP(BT_OGF_BASEBAND, 0x0024) /* 0x0c24 */ +struct bt_hci_cp_write_class_of_device +{ + uint8_t class_of_device[3]; +} __packed; + +#define BT_TX_POWER_LEVEL_CURRENT 0x00 +#define BT_TX_POWER_LEVEL_MAX 0x01 +#define BT_HCI_OP_READ_TX_POWER_LEVEL BT_OP(BT_OGF_BASEBAND, 0x002d) /* 0x0c2d */ +struct bt_hci_cp_read_tx_power_level +{ + uint16_t handle; + uint8_t type; +} __packed; + +struct bt_hci_rp_read_tx_power_level +{ + uint8_t status; + uint16_t handle; + int8_t tx_power_level; +} __packed; + +#define BT_HCI_LE_TX_POWER_PHY_1M 0x01 +#define BT_HCI_LE_TX_POWER_PHY_2M 0x02 +#define BT_HCI_LE_TX_POWER_PHY_CODED_S8 0x03 +#define BT_HCI_LE_TX_POWER_PHY_CODED_S2 0x04 +#define BT_HCI_OP_LE_ENH_READ_TX_POWER_LEVEL BT_OP(BT_OGF_LE, 0x0076) /* 0x2076 */ +struct bt_hci_cp_le_read_tx_power_level +{ + uint16_t handle; + uint8_t phy; +} __packed; + +struct bt_hci_rp_le_read_tx_power_level +{ + uint8_t status; + uint16_t handle; + uint8_t phy; + int8_t current_tx_power_level; + int8_t max_tx_power_level; +} __packed; + +#define BT_HCI_OP_LE_READ_REMOTE_TX_POWER_LEVEL BT_OP(BT_OGF_LE, 0x0077) /* 0x2077 */ + +#define BT_HCI_LE_TX_POWER_REPORT_DISABLE 0x00 +#define BT_HCI_LE_TX_POWER_REPORT_ENABLE 0x01 +#define BT_HCI_OP_LE_SET_TX_POWER_REPORT_ENABLE BT_OP(BT_OGF_LE, 0x007A) /* 0x207A */ +struct bt_hci_cp_le_set_tx_power_report_enable +{ + uint16_t handle; + uint8_t local_enable; + uint8_t remote_enable; +} __packed; + +#define BT_HCI_OP_LE_SET_PATH_LOSS_REPORTING_PARAMETERS BT_OP(BT_OGF_LE, 0x0078) /* 0x2078 */ +struct bt_hci_cp_le_set_path_loss_reporting_parameters +{ + uint16_t handle; + uint8_t high_threshold; + uint8_t high_hysteresis; + uint8_t low_threshold; + uint8_t low_hysteresis; + uint16_t min_time_spent; +} __packed; + +struct bt_hci_rp_le_set_path_loss_reporting_parameters +{ + uint16_t handle; + uint8_t status; +} __packed; + +#define BT_HCI_LE_PATH_LOSS_REPORTING_DISABLE 0x00 +#define BT_HCI_LE_PATH_LOSS_REPORTING_ENABLE 0x01 +#define BT_HCI_OP_LE_SET_PATH_LOSS_REPORTING_ENABLE BT_OP(BT_OGF_LE, 0x0079) /* 0x2079 */ + +struct bt_hci_cp_le_set_path_loss_reporting_enable +{ + uint16_t handle; + uint8_t enable; +} __packed; + +struct bt_hci_rp_le_set_path_loss_reporting_enable +{ + uint16_t handle; + uint8_t status; +} __packed; + +#define BT_HCI_OP_LE_SET_DEFAULT_SUBRATE BT_OP(BT_OGF_LE, 0x007D) /* 0x207D */ + +struct bt_hci_cp_le_set_default_subrate +{ + uint16_t subrate_min; + uint16_t subrate_max; + uint16_t max_latency; + uint16_t continuation_number; + uint16_t supervision_timeout; +} __packed; + +#define BT_HCI_OP_LE_SUBRATE_REQUEST BT_OP(BT_OGF_LE, 0x007E) /* 0x207E */ + +struct bt_hci_cp_le_subrate_request +{ + uint16_t handle; + uint16_t subrate_min; + uint16_t subrate_max; + uint16_t max_latency; + uint16_t continuation_number; + uint16_t supervision_timeout; +} __packed; + +#define BT_HCI_CTL_TO_HOST_FLOW_DISABLE 0x00 +#define BT_HCI_CTL_TO_HOST_FLOW_ENABLE 0x01 +#define BT_HCI_OP_SET_CTL_TO_HOST_FLOW BT_OP(BT_OGF_BASEBAND, 0x0031) /* 0x0c31 */ +struct bt_hci_cp_set_ctl_to_host_flow +{ + uint8_t flow_enable; +} __packed; + +#define BT_HCI_OP_HOST_BUFFER_SIZE BT_OP(BT_OGF_BASEBAND, 0x0033) /* 0x0c33 */ +struct bt_hci_cp_host_buffer_size +{ + uint16_t acl_mtu; + uint8_t sco_mtu; + uint16_t acl_pkts; + uint16_t sco_pkts; +} __packed; + +struct bt_hci_handle_count +{ + uint16_t handle; + uint16_t count; +} __packed; + +#define BT_HCI_OP_HOST_NUM_COMPLETED_PACKETS BT_OP(BT_OGF_BASEBAND, 0x0035) /* 0x0c35 */ +struct bt_hci_cp_host_num_completed_packets +{ + uint8_t num_handles; + struct bt_hci_handle_count h[0]; +} __packed; + +/** HCI opcode for Read Link Supervision Timeout. */ +#define BT_HCI_OP_READ_LINK_SUPERVISION_TIMEOUT BT_OP(BT_OGF_BASEBAND, 0x0036) /* 0x0c36 */ + +/** HCI command parameters for Read Link Supervision Timeout. */ + +struct bt_hci_cp_read_link_supervision_timeout +{ + /** Connection handle. */ + + uint16_t handle; +} __packed; + +/** HCI response parameters for Read Link Supervision Timeout. */ + +struct bt_hci_rp_read_link_supervision_timeout +{ + /** Status. */ + + uint8_t status; + + /** Connection handle. */ + + uint16_t handle; + + /** Link supervision timeout. */ + + uint16_t timeout; +} __packed; + +/** HCI opcode for Write Link Supervision Timeout. */ +#define BT_HCI_OP_WRITE_LINK_SUPERVISION_TIMEOUT BT_OP(BT_OGF_BASEBAND, 0x0037) /* 0x0c37 */ + +/** HCI command parameters for Write Link Supervision Timeout. */ + +struct bt_hci_cp_write_link_supervision_timeout +{ + /** Connection handle. */ + + uint16_t handle; + + /** Link supervision timeout. */ + + uint16_t timeout; +} __packed; + +#define BT_HCI_OP_WRITE_CURRENT_IAC_LAP BT_OP(BT_OGF_BASEBAND, 0x003a) /* 0x0c3a */ +struct bt_hci_iac_lap +{ + uint8_t iac[3]; +} __packed; + +struct bt_hci_cp_write_current_iac_lap +{ + uint8_t num_current_iac; + struct bt_hci_iac_lap lap[0]; +} __packed; + +/** HCI opcode for Write Inquiry Scan Type. */ +#define BT_HCI_OP_WRITE_INQUIRY_SCAN_TYPE BT_OP(BT_OGF_BASEBAND, 0x0043) /* 0x0c43 */ + +/** HCI command parameters for Write Inquiry Scan Type. */ + +struct bt_hci_cp_write_inquiry_scan_type +{ + /** Inquiry scan type. */ + + uint8_t type; +} __packed; + +#define BT_HCI_OP_WRITE_INQUIRY_MODE BT_OP(BT_OGF_BASEBAND, 0x0045) /* 0x0c45 */ +struct bt_hci_cp_write_inquiry_mode +{ + uint8_t mode; +} __packed; + +/** HCI opcode for Write Page Scan Type. */ +#define BT_HCI_OP_WRITE_PAGE_SCAN_TYPE BT_OP(BT_OGF_BASEBAND, 0x0047) /* 0x0c47 */ + +/** HCI command parameters for Write Page Scan Type. */ + +struct bt_hci_cp_write_page_scan_type +{ + /** Page scan type. */ + + uint8_t type; +} __packed; + +/** Maximum length of Extended Inquiry Response data. */ +#define BT_HCI_EIR_MAX_DATA_LEN 240 +/** HCI opcode for Write Extended Inquiry Response. */ +#define BT_HCI_OP_WRITE_EXT_INQUIRY_RESPONSE BT_OP(BT_OGF_BASEBAND, 0x0052) /* 0x0c52 */ + +/** HCI command parameters for Write Extended Inquiry Response. */ + +struct bt_hci_cp_write_ext_inquiry_response +{ + /** FEC encoding required. */ + + uint8_t fec_required; + + /** Extended inquiry response data. */ + + uint8_t eir[BT_HCI_EIR_MAX_DATA_LEN]; +} __packed; + +#define BT_HCI_OP_WRITE_SSP_MODE BT_OP(BT_OGF_BASEBAND, 0x0056) /* 0x0c56 */ +struct bt_hci_cp_write_ssp_mode +{ + uint8_t mode; +} __packed; + +#define BT_HCI_OP_SET_EVENT_MASK_PAGE_2 BT_OP(BT_OGF_BASEBAND, 0x0063) /* 0x0c63 */ +struct bt_hci_cp_set_event_mask_page_2 +{ + uint8_t events_page_2[8]; +} __packed; + +#define BT_HCI_OP_LE_WRITE_LE_HOST_SUPP BT_OP(BT_OGF_BASEBAND, 0x006d) /* 0x0c6d */ +struct bt_hci_cp_write_le_host_supp +{ + uint8_t le; + uint8_t simul; +} __packed; + +#define BT_HCI_OP_WRITE_SC_HOST_SUPP BT_OP(BT_OGF_BASEBAND, 0x007a) /* 0x0c7a */ +struct bt_hci_cp_write_sc_host_supp +{ + uint8_t sc_support; +} __packed; + +#define BT_HCI_OP_READ_AUTH_PAYLOAD_TIMEOUT BT_OP(BT_OGF_BASEBAND, 0x007b) /* 0x0c7b */ +struct bt_hci_cp_read_auth_payload_timeout +{ + uint16_t handle; +} __packed; + +struct bt_hci_rp_read_auth_payload_timeout +{ + uint8_t status; + uint16_t handle; + uint16_t auth_payload_timeout; +} __packed; + +#define BT_HCI_OP_WRITE_AUTH_PAYLOAD_TIMEOUT BT_OP(BT_OGF_BASEBAND, 0x007c) /* 0x0c7c */ +struct bt_hci_cp_write_auth_payload_timeout +{ + uint16_t handle; + uint16_t auth_payload_timeout; +} __packed; + +struct bt_hci_rp_write_auth_payload_timeout +{ + uint8_t status; + uint16_t handle; +} __packed; + +#define BT_HCI_OP_CONFIGURE_DATA_PATH BT_OP(BT_OGF_BASEBAND, 0x0083) /* 0x0c83 */ +struct bt_hci_cp_configure_data_path +{ + uint8_t data_path_dir; + uint8_t data_path_id; + uint8_t vs_config_len; + uint8_t vs_config[0]; +} __packed; + +struct bt_hci_rp_configure_data_path +{ + uint8_t status; +} __packed; + +/* HCI version from Assigned Numbers */ +#define BT_HCI_VERSION_1_0B 0 /**< 1.0b */ +#define BT_HCI_VERSION_1_1 1 /**< 1.1 */ +#define BT_HCI_VERSION_1_2 2 /**< 1.2 */ +#define BT_HCI_VERSION_2_0 3 /**< 2.0 */ +#define BT_HCI_VERSION_2_1 4 /**< 2.1 */ +#define BT_HCI_VERSION_3_0 5 /**< 3.0 */ +#define BT_HCI_VERSION_4_0 6 /**< 4.0 */ +#define BT_HCI_VERSION_4_1 7 /**< 4.1 */ +#define BT_HCI_VERSION_4_2 8 /**< 4.2 */ +#define BT_HCI_VERSION_5_0 9 /**< 5.0 */ +#define BT_HCI_VERSION_5_1 10 /**< 5.1 */ +#define BT_HCI_VERSION_5_2 11 /**< 5.2 */ +#define BT_HCI_VERSION_5_3 12 /**< 5.3 */ +#define BT_HCI_VERSION_5_4 13 /**< 5.4 */ +#define BT_HCI_VERSION_6_0 14 /**< 6.0 */ +#define BT_HCI_VERSION_6_1 15 /**< 6.1 */ +#define BT_HCI_VERSION_6_2 16 /**< 6.2 */ +#define BT_HCI_VERSION_6_3 17 /**< 6.3 */ + +#define BT_HCI_OP_READ_LOCAL_VERSION_INFO BT_OP(BT_OGF_INFO, 0x0001) /* 0x1001 */ +struct bt_hci_rp_read_local_version_info +{ + uint8_t status; + uint8_t hci_version; + uint16_t hci_revision; + uint8_t lmp_version; + uint16_t manufacturer; + uint16_t lmp_subversion; +} __packed; + +#define BT_HCI_OP_READ_SUPPORTED_COMMANDS BT_OP(BT_OGF_INFO, 0x0002) /* 0x1002 */ +struct bt_hci_rp_read_supported_commands +{ + uint8_t status; + uint8_t commands[64]; +} __packed; + +#define BT_HCI_OP_READ_LOCAL_EXT_FEATURES BT_OP(BT_OGF_INFO, 0x0004) /* 0x1004 */ +struct bt_hci_cp_read_local_ext_features +{ + uint8_t page; +}; +struct bt_hci_rp_read_local_ext_features +{ + uint8_t status; + uint8_t page; + uint8_t max_page; + uint8_t ext_features[8]; +} __packed; + +#define BT_HCI_OP_READ_LOCAL_FEATURES BT_OP(BT_OGF_INFO, 0x0003) /* 0x1003 */ +struct bt_hci_rp_read_local_features +{ + uint8_t status; + uint8_t features[8]; +} __packed; + +#define BT_HCI_OP_READ_BUFFER_SIZE BT_OP(BT_OGF_INFO, 0x0005) /* 0x1005 */ +struct bt_hci_rp_read_buffer_size +{ + uint8_t status; + uint16_t acl_max_len; + uint8_t sco_max_len; + uint16_t acl_max_num; + uint16_t sco_max_num; +} __packed; + +#define BT_HCI_OP_READ_BD_ADDR BT_OP(BT_OGF_INFO, 0x0009) /* 0x1009 */ +struct bt_hci_rp_read_bd_addr +{ + uint8_t status; + bt_addr_t bdaddr; +} __packed; + +/* logic transport type bits as returned when reading supported codecs */ +#define BT_HCI_CODEC_TRANSPORT_MASK_BREDR_ACL BIT(0) +#define BT_HCI_CODEC_TRANSPORT_MASK_BREDR_SCO BIT(1) +#define BT_HCI_CODEC_TRANSPORT_MASK_LE_CIS BIT(2) +#define BT_HCI_CODEC_TRANSPORT_MASK_LE_BIS BIT(3) + +/* logic transport types for reading codec capabilities and controller delays + */ +#define BT_HCI_LOGICAL_TRANSPORT_TYPE_BREDR_ACL 0x00 +#define BT_HCI_LOGICAL_TRANSPORT_TYPE_BREDR_SCO 0x01 +#define BT_HCI_LOGICAL_TRANSPORT_TYPE_LE_CIS 0x02 +#define BT_HCI_LOGICAL_TRANSPORT_TYPE_LE_BIS 0x03 + +/* audio datapath directions */ +#define BT_HCI_DATAPATH_DIR_HOST_TO_CTLR 0x00 +#define BT_HCI_DATAPATH_DIR_CTLR_TO_HOST 0x01 + +/* audio datapath IDs */ +#define BT_HCI_DATAPATH_ID_HCI 0x00 +#define BT_HCI_DATAPATH_ID_VS 0x01 +#define BT_HCI_DATAPATH_ID_VS_END 0xfe + +/* coding format assigned numbers, used for codec IDs */ +#define BT_HCI_CODING_FORMAT_ULAW_LOG 0x00 +#define BT_HCI_CODING_FORMAT_ALAW_LOG 0x01 +#define BT_HCI_CODING_FORMAT_CVSD 0x02 +#define BT_HCI_CODING_FORMAT_TRANSPARENT 0x03 +#define BT_HCI_CODING_FORMAT_LINEAR_PCM 0x04 +#define BT_HCI_CODING_FORMAT_MSBC 0x05 +#define BT_HCI_CODING_FORMAT_LC3 0x06 +#define BT_HCI_CODING_FORMAT_G729A 0x07 +#define BT_HCI_CODING_FORMAT_VS 0xFF + +#define BT_HCI_OP_READ_CODECS BT_OP(BT_OGF_INFO, 0x000b) /* 0x100b */ +struct bt_hci_std_codec_info +{ + uint8_t codec_id; +} __packed; +struct bt_hci_std_codecs +{ + uint8_t num_codecs; + struct bt_hci_std_codec_info codec_info[0]; +} __packed; +struct bt_hci_vs_codec_info +{ + uint16_t company_id; + uint16_t codec_id; +} __packed; +struct bt_hci_vs_codecs +{ + uint8_t num_codecs; + struct bt_hci_vs_codec_info codec_info[0]; +} __packed; +struct bt_hci_rp_read_codecs +{ + uint8_t status; + + /* other fields filled in dynamically */ + + uint8_t codecs[0]; +} __packed; + +#define BT_HCI_OP_READ_CODECS_V2 BT_OP(BT_OGF_INFO, 0x000d) /* 0x100d */ +struct bt_hci_std_codec_info_v2 +{ + uint8_t codec_id; + uint8_t transports; /* bitmap */ +} __packed; +struct bt_hci_std_codecs_v2 +{ + uint8_t num_codecs; + struct bt_hci_std_codec_info_v2 codec_info[0]; +} __packed; +struct bt_hci_vs_codec_info_v2 +{ + uint16_t company_id; + uint16_t codec_id; + uint8_t transports; /* bitmap */ +} __packed; +struct bt_hci_vs_codecs_v2 +{ + uint8_t num_codecs; + struct bt_hci_vs_codec_info_v2 codec_info[0]; +} __packed; +struct bt_hci_rp_read_codecs_v2 +{ + uint8_t status; + + /* other fields filled in dynamically */ + + uint8_t codecs[0]; +} __packed; + +struct bt_hci_cp_codec_id +{ + uint8_t coding_format; + uint16_t company_id; + uint16_t vs_codec_id; +} __packed; + +#define BT_HCI_OP_READ_CODEC_CAPABILITIES BT_OP(BT_OGF_INFO, 0x000e) /* 0x100e */ +struct bt_hci_cp_read_codec_capabilities +{ + struct bt_hci_cp_codec_id codec_id; + uint8_t transport; + uint8_t direction; +} __packed; +struct bt_hci_codec_capability_info +{ + uint8_t length; + uint8_t data[0]; +} __packed; +struct bt_hci_rp_read_codec_capabilities +{ + uint8_t status; + uint8_t num_capabilities; + + /* other fields filled in dynamically */ + + uint8_t capabilities[0]; +} __packed; + +#define BT_HCI_OP_READ_CTLR_DELAY BT_OP(BT_OGF_INFO, 0x000f) /* 0x100f */ +struct bt_hci_cp_read_ctlr_delay +{ + struct bt_hci_cp_codec_id codec_id; + uint8_t transport; + uint8_t direction; + uint8_t codec_config_len; + uint8_t codec_config[0]; +} __packed; +struct bt_hci_rp_read_ctlr_delay +{ + uint8_t status; + uint8_t min_ctlr_delay[3]; + uint8_t max_ctlr_delay[3]; +} __packed; + +#define BT_HCI_OP_READ_RSSI BT_OP(BT_OGF_STATUS, 0x0005) /* 0x1405 */ +struct bt_hci_cp_read_rssi +{ + uint16_t handle; +} __packed; +struct bt_hci_rp_read_rssi +{ + uint8_t status; + uint16_t handle; + int8_t rssi; +} __packed; + +#define BT_HCI_ENCRYPTION_KEY_SIZE_MIN 7 +#define BT_HCI_ENCRYPTION_KEY_SIZE_MAX 16 + +#define BT_HCI_OP_READ_ENCRYPTION_KEY_SIZE BT_OP(BT_OGF_STATUS, 0x0008) /* 0x1408 */ +struct bt_hci_cp_read_encryption_key_size +{ + uint16_t handle; +} __packed; +struct bt_hci_rp_read_encryption_key_size +{ + uint8_t status; + uint16_t handle; + uint8_t key_size; +} __packed; + +/* Bluetooth LE */ + +#define BT_HCI_OP_LE_SET_EVENT_MASK BT_OP(BT_OGF_LE, 0x0001) /* 0x2001 */ +struct bt_hci_cp_le_set_event_mask +{ + uint8_t events[8]; +} __packed; + +#define BT_HCI_OP_LE_READ_BUFFER_SIZE BT_OP(BT_OGF_LE, 0x0002) /* 0x2002 */ +struct bt_hci_rp_le_read_buffer_size +{ + uint8_t status; + uint16_t le_max_len; + uint8_t le_max_num; +} __packed; + +#define BT_HCI_OP_LE_READ_LOCAL_FEATURES BT_OP(BT_OGF_LE, 0x0003) /* 0x2003 */ +struct bt_hci_rp_le_read_local_features +{ + uint8_t status; + uint8_t features[8]; +} __packed; + +#define BT_HCI_OP_LE_SET_RANDOM_ADDRESS BT_OP(BT_OGF_LE, 0x0005) /* 0x2005 */ +struct bt_hci_cp_le_set_random_address +{ + bt_addr_t bdaddr; +} __packed; + +#define BT_HCI_ADV_IND 0x00 +#define BT_HCI_ADV_DIRECT_IND 0x01 +#define BT_HCI_ADV_SCAN_IND 0x02 +#define BT_HCI_ADV_NONCONN_IND 0x03 +#define BT_HCI_ADV_DIRECT_IND_LOW_DUTY 0x04 +#define BT_HCI_ADV_SCAN_RSP 0x04 + +#define BT_LE_ADV_INTERVAL_MIN 0x0020 +#define BT_LE_ADV_INTERVAL_MAX 0x4000 +/** Maximum LE Extended Advertising interval (0xFFFFFF, 0.625ms units). */ +#define BT_LE_EXT_ADV_INTERVAL_MAX 0xFFFFFFU +#define BT_LE_ADV_INTERVAL_DEFAULT 0x0800 + +#define BT_LE_ADV_CHAN_MAP_CHAN_37 0x01 +#define BT_LE_ADV_CHAN_MAP_CHAN_38 0x02 +#define BT_LE_ADV_CHAN_MAP_CHAN_39 0x04 +#define BT_LE_ADV_CHAN_MAP_ALL 0x07 + +#define BT_LE_ADV_FP_NO_FILTER 0x00 +#define BT_LE_ADV_FP_FILTER_SCAN_REQ 0x01 +#define BT_LE_ADV_FP_FILTER_CONN_IND 0x02 +#define BT_LE_ADV_FP_FILTER_BOTH 0x03 + +#define BT_HCI_OP_LE_SET_ADV_PARAM BT_OP(BT_OGF_LE, 0x0006) /* 0x2006 */ +struct bt_hci_cp_le_set_adv_param +{ + uint16_t min_interval; + uint16_t max_interval; + uint8_t type; + uint8_t own_addr_type; + bt_addr_le_t direct_addr; + uint8_t channel_map; + uint8_t filter_policy; +} __packed; + +#define BT_HCI_OP_LE_READ_ADV_CHAN_TX_POWER BT_OP(BT_OGF_LE, 0x0007) /* 0x2007 */ +struct bt_hci_rp_le_read_chan_tx_power +{ + uint8_t status; + int8_t tx_power_level; +} __packed; + +#define BT_HCI_OP_LE_SET_ADV_DATA BT_OP(BT_OGF_LE, 0x0008) /* 0x2008 */ +struct bt_hci_cp_le_set_adv_data +{ + uint8_t len; + uint8_t data[31]; +} __packed; + +#define BT_HCI_OP_LE_SET_SCAN_RSP_DATA BT_OP(BT_OGF_LE, 0x0009) /* 0x2009 */ +struct bt_hci_cp_le_set_scan_rsp_data +{ + uint8_t len; + uint8_t data[31]; +} __packed; + +#define BT_HCI_LE_ADV_DISABLE 0x00 +#define BT_HCI_LE_ADV_ENABLE 0x01 + +#define BT_HCI_OP_LE_SET_ADV_ENABLE BT_OP(BT_OGF_LE, 0x000a) /* 0x200a */ +struct bt_hci_cp_le_set_adv_enable +{ + uint8_t enable; +} __packed; + +/* Scan types */ +#define BT_HCI_OP_LE_SET_SCAN_PARAM BT_OP(BT_OGF_LE, 0x000b) /* 0x200b */ +#define BT_HCI_LE_SCAN_PASSIVE 0x00 +#define BT_HCI_LE_SCAN_ACTIVE 0x01 + +#define BT_HCI_LE_SCAN_FP_BASIC_NO_FILTER 0x00 +#define BT_HCI_LE_SCAN_FP_BASIC_FILTER 0x01 +#define BT_HCI_LE_SCAN_FP_EXT_NO_FILTER 0x02 +#define BT_HCI_LE_SCAN_FP_EXT_FILTER 0x03 + +struct bt_hci_cp_le_set_scan_param +{ + uint8_t scan_type; + uint16_t interval; + uint16_t window; + uint8_t addr_type; + uint8_t filter_policy; +} __packed; + +#define BT_HCI_OP_LE_SET_SCAN_ENABLE BT_OP(BT_OGF_LE, 0x000c) /* 0x200c */ + +#define BT_HCI_LE_SCAN_DISABLE 0x00 +#define BT_HCI_LE_SCAN_ENABLE 0x01 + +#define BT_HCI_LE_SCAN_FILTER_DUP_DISABLE 0x00 +#define BT_HCI_LE_SCAN_FILTER_DUP_ENABLE 0x01 + +struct bt_hci_cp_le_set_scan_enable +{ + uint8_t enable; + uint8_t filter_dup; +} __packed; + +#define BT_HCI_OP_LE_CREATE_CONN BT_OP(BT_OGF_LE, 0x000d) /* 0x200d */ + +#define BT_HCI_LE_CREATE_CONN_FP_NO_FILTER 0x00 +#define BT_HCI_LE_CREATE_CONN_FP_FILTER 0x01 + +struct bt_hci_cp_le_create_conn +{ + uint16_t scan_interval; + uint16_t scan_window; + uint8_t filter_policy; + bt_addr_le_t peer_addr; + uint8_t own_addr_type; + uint16_t conn_interval_min; + uint16_t conn_interval_max; + uint16_t conn_latency; + uint16_t supervision_timeout; + uint16_t min_ce_len; + uint16_t max_ce_len; +} __packed; + +#define BT_HCI_OP_LE_CREATE_CONN_CANCEL BT_OP(BT_OGF_LE, 0x000e) /* 0x200e */ + +#define BT_HCI_OP_LE_READ_FAL_SIZE BT_OP(BT_OGF_LE, 0x000f) /* 0x200f */ +struct bt_hci_rp_le_read_fal_size +{ + uint8_t status; + uint8_t fal_size; +} __packed; + +#define BT_HCI_OP_LE_CLEAR_FAL BT_OP(BT_OGF_LE, 0x0010) /* 0x2010 */ + +#define BT_HCI_OP_LE_ADD_DEV_TO_FAL BT_OP(BT_OGF_LE, 0x0011) /* 0x2011 */ +struct bt_hci_cp_le_add_dev_to_fal +{ + bt_addr_le_t addr; +} __packed; + +#define BT_HCI_OP_LE_REM_DEV_FROM_FAL BT_OP(BT_OGF_LE, 0x0012) /* 0x2012 */ +struct bt_hci_cp_le_rem_dev_from_fal +{ + bt_addr_le_t addr; +} __packed; + +#define BT_HCI_OP_LE_CONN_UPDATE BT_OP(BT_OGF_LE, 0x0013) /* 0x2013 */ +struct hci_cp_le_conn_update +{ + uint16_t handle; + uint16_t conn_interval_min; + uint16_t conn_interval_max; + uint16_t conn_latency; + uint16_t supervision_timeout; + uint16_t min_ce_len; + uint16_t max_ce_len; +} __packed; + +#define BT_HCI_OP_LE_SET_HOST_CHAN_CLASSIF BT_OP(BT_OGF_LE, 0x0014) /* 0x2014 */ +struct bt_hci_cp_le_set_host_chan_classif +{ + uint8_t ch_map[5]; +} __packed; + +#define BT_HCI_OP_LE_READ_CHAN_MAP BT_OP(BT_OGF_LE, 0x0015) /* 0x2015 */ +struct bt_hci_cp_le_read_chan_map +{ + uint16_t handle; +} __packed; +struct bt_hci_rp_le_read_chan_map +{ + uint8_t status; + uint16_t handle; + uint8_t ch_map[5]; +} __packed; + +#define BT_HCI_OP_LE_READ_REMOTE_FEATURES BT_OP(BT_OGF_LE, 0x0016) /* 0x2016 */ +struct bt_hci_cp_le_read_remote_features +{ + uint16_t handle; +} __packed; + +#define BT_HCI_OP_LE_ENCRYPT BT_OP(BT_OGF_LE, 0x0017) /* 0x2017 */ +struct bt_hci_cp_le_encrypt +{ + uint8_t key[16]; + uint8_t plaintext[16]; +} __packed; +struct bt_hci_rp_le_encrypt +{ + uint8_t status; + uint8_t enc_data[16]; +} __packed; + +#define BT_HCI_OP_LE_RAND BT_OP(BT_OGF_LE, 0x0018) /* 0x2018 */ +struct bt_hci_rp_le_rand +{ + uint8_t status; + uint8_t rand[8]; +} __packed; + +#define BT_HCI_OP_LE_START_ENCRYPTION BT_OP(BT_OGF_LE, 0x0019) /* 0x2019 */ +struct bt_hci_cp_le_start_encryption +{ + uint16_t handle; + + /** 64-bit random number for LTK identification. */ + + uint8_t rand[8]; + + /** 16-bit encrypted diversifier for LTK identification. */ + + uint8_t ediv[2]; + uint8_t ltk[16]; +} __packed; + +#define BT_HCI_OP_LE_LTK_REQ_REPLY BT_OP(BT_OGF_LE, 0x001a) /* 0x201a */ +struct bt_hci_cp_le_ltk_req_reply +{ + uint16_t handle; + uint8_t ltk[16]; +} __packed; +struct bt_hci_rp_le_ltk_req_reply +{ + uint8_t status; + uint16_t handle; +} __packed; + +#define BT_HCI_OP_LE_LTK_REQ_NEG_REPLY BT_OP(BT_OGF_LE, 0x001b) /* 0x201b */ +struct bt_hci_cp_le_ltk_req_neg_reply +{ + uint16_t handle; +} __packed; +struct bt_hci_rp_le_ltk_req_neg_reply +{ + uint8_t status; + uint16_t handle; +} __packed; + +#define BT_HCI_OP_LE_READ_SUPP_STATES BT_OP(BT_OGF_LE, 0x001c) /* 0x201c */ +struct bt_hci_rp_le_read_supp_states +{ + uint8_t status; + uint8_t le_states[8]; +} __packed; + +#define BT_HCI_OP_LE_RX_TEST BT_OP(BT_OGF_LE, 0x001d) /* 0x201d */ +struct bt_hci_cp_le_rx_test +{ + uint8_t rx_ch; +} __packed; + +#define BT_HCI_TEST_PKT_PAYLOAD_PRBS9 0x00 +#define BT_HCI_TEST_PKT_PAYLOAD_11110000 0x01 +#define BT_HCI_TEST_PKT_PAYLOAD_10101010 0x02 +#define BT_HCI_TEST_PKT_PAYLOAD_PRBS15 0x03 +#define BT_HCI_TEST_PKT_PAYLOAD_11111111 0x04 +#define BT_HCI_TEST_PKT_PAYLOAD_00000000 0x05 +#define BT_HCI_TEST_PKT_PAYLOAD_00001111 0x06 +#define BT_HCI_TEST_PKT_PAYLOAD_01010101 0x07 + +#define BT_HCI_OP_LE_TX_TEST BT_OP(BT_OGF_LE, 0x001e) /* 0x201e */ +struct bt_hci_cp_le_tx_test +{ + uint8_t tx_ch; + uint8_t test_data_len; + uint8_t pkt_payload; +} __packed; + +#define BT_HCI_OP_LE_TEST_END BT_OP(BT_OGF_LE, 0x001f) /* 0x201f */ +struct bt_hci_rp_le_test_end +{ + uint8_t status; + uint16_t rx_pkt_count; +} __packed; + +#define BT_HCI_OP_LE_CONN_PARAM_REQ_REPLY BT_OP(BT_OGF_LE, 0x0020) /* 0x2020 */ +struct bt_hci_cp_le_conn_param_req_reply +{ + uint16_t handle; + uint16_t interval_min; + uint16_t interval_max; + uint16_t latency; + uint16_t timeout; + uint16_t min_ce_len; + uint16_t max_ce_len; +} __packed; +struct bt_hci_rp_le_conn_param_req_reply +{ + uint8_t status; + uint16_t handle; +} __packed; + +#define BT_HCI_OP_LE_CONN_PARAM_REQ_NEG_REPLY BT_OP(BT_OGF_LE, 0x0021) /* 0x2021 */ +struct bt_hci_cp_le_conn_param_req_neg_reply +{ + uint16_t handle; + uint8_t reason; +} __packed; +struct bt_hci_rp_le_conn_param_req_neg_reply +{ + uint8_t status; + uint16_t handle; +} __packed; + +#define BT_HCI_OP_LE_SET_DATA_LEN BT_OP(BT_OGF_LE, 0x0022) /* 0x2022 */ +struct bt_hci_cp_le_set_data_len +{ + uint16_t handle; + uint16_t tx_octets; + uint16_t tx_time; +} __packed; +struct bt_hci_rp_le_set_data_len +{ + uint8_t status; + uint16_t handle; +} __packed; + +#define BT_HCI_OP_LE_READ_DEFAULT_DATA_LEN BT_OP(BT_OGF_LE, 0x0023) /* 0x2023 */ +struct bt_hci_rp_le_read_default_data_len +{ + uint8_t status; + uint16_t max_tx_octets; + uint16_t max_tx_time; +} __packed; + +#define BT_HCI_OP_LE_WRITE_DEFAULT_DATA_LEN BT_OP(BT_OGF_LE, 0x0024) /* 0x2024 */ +struct bt_hci_cp_le_write_default_data_len +{ + uint16_t max_tx_octets; + uint16_t max_tx_time; +} __packed; + +#define BT_HCI_OP_LE_P256_PUBLIC_KEY BT_OP(BT_OGF_LE, 0x0025) /* 0x2025 */ + +#define BT_HCI_OP_LE_GENERATE_DHKEY BT_OP(BT_OGF_LE, 0x0026) /* 0x2026 */ +struct bt_hci_cp_le_generate_dhkey +{ + uint8_t key[64]; +} __packed; + +#define BT_HCI_OP_LE_GENERATE_DHKEY_V2 BT_OP(BT_OGF_LE, 0x005e) /* 0x205e */ + +#define BT_HCI_LE_KEY_TYPE_GENERATED 0x00 +#define BT_HCI_LE_KEY_TYPE_DEBUG 0x01 + +struct bt_hci_cp_le_generate_dhkey_v2 +{ + uint8_t key[64]; + uint8_t key_type; +} __packed; + +#define BT_HCI_OP_LE_ADD_DEV_TO_RL BT_OP(BT_OGF_LE, 0x0027) /* 0x2027 */ +struct bt_hci_cp_le_add_dev_to_rl +{ + bt_addr_le_t peer_id_addr; + uint8_t peer_irk[16]; + uint8_t local_irk[16]; +} __packed; + +#define BT_HCI_OP_LE_REM_DEV_FROM_RL BT_OP(BT_OGF_LE, 0x0028) /* 0x2028 */ +struct bt_hci_cp_le_rem_dev_from_rl +{ + bt_addr_le_t peer_id_addr; +} __packed; + +#define BT_HCI_OP_LE_CLEAR_RL BT_OP(BT_OGF_LE, 0x0029) /* 0x2029 */ + +#define BT_HCI_OP_LE_READ_RL_SIZE BT_OP(BT_OGF_LE, 0x002a) /* 0x202a */ +struct bt_hci_rp_le_read_rl_size +{ + uint8_t status; + uint8_t rl_size; +} __packed; + +#define BT_HCI_OP_LE_READ_PEER_RPA BT_OP(BT_OGF_LE, 0x002b) /* 0x202b */ +struct bt_hci_cp_le_read_peer_rpa +{ + bt_addr_le_t peer_id_addr; +} __packed; +struct bt_hci_rp_le_read_peer_rpa +{ + uint8_t status; + bt_addr_t peer_rpa; +} __packed; + +#define BT_HCI_OP_LE_READ_LOCAL_RPA BT_OP(BT_OGF_LE, 0x002c) /* 0x202c */ +struct bt_hci_cp_le_read_local_rpa +{ + bt_addr_le_t peer_id_addr; +} __packed; +struct bt_hci_rp_le_read_local_rpa +{ + uint8_t status; + bt_addr_t local_rpa; +} __packed; + +#define BT_HCI_ADDR_RES_DISABLE 0x00 +#define BT_HCI_ADDR_RES_ENABLE 0x01 + +#define BT_HCI_OP_LE_SET_ADDR_RES_ENABLE BT_OP(BT_OGF_LE, 0x002d) /* 0x202d */ +struct bt_hci_cp_le_set_addr_res_enable +{ + uint8_t enable; +} __packed; + +#define BT_HCI_OP_LE_SET_RPA_TIMEOUT BT_OP(BT_OGF_LE, 0x002e) /* 0x202e */ +struct bt_hci_cp_le_set_rpa_timeout +{ + uint16_t rpa_timeout; +} __packed; + +/* All limits according to BT Core spec 5.4 [Vol 4, Part E, 7.8.46] */ +#define BT_HCI_LE_MAX_TX_OCTETS_MIN 0x001B +#define BT_HCI_LE_MAX_TX_OCTETS_MAX 0x00FB +#define BT_HCI_LE_MAX_RX_OCTETS_MIN 0x001B +#define BT_HCI_LE_MAX_RX_OCTETS_MAX 0x00FB + +#define BT_HCI_LE_MAX_TX_TIME_MIN 0x0148 +#define BT_HCI_LE_MAX_TX_TIME_MAX 0x4290 +#define BT_HCI_LE_MAX_RX_TIME_MIN 0x0148 +#define BT_HCI_LE_MAX_RX_TIME_MAX 0x4290 + +#define BT_HCI_OP_LE_READ_MAX_DATA_LEN BT_OP(BT_OGF_LE, 0x002f) /* 0x202f */ +struct bt_hci_rp_le_read_max_data_len +{ + uint8_t status; + uint16_t max_tx_octets; + uint16_t max_tx_time; + uint16_t max_rx_octets; + uint16_t max_rx_time; +} __packed; + +#define BT_HCI_LE_PHY_1M 0x01 +#define BT_HCI_LE_PHY_2M 0x02 +#define BT_HCI_LE_PHY_CODED 0x03 + +#define BT_HCI_OP_LE_READ_PHY BT_OP(BT_OGF_LE, 0x0030) /* 0x2030 */ +struct bt_hci_cp_le_read_phy +{ + uint16_t handle; +} __packed; +struct bt_hci_rp_le_read_phy +{ + uint8_t status; + uint16_t handle; + uint8_t tx_phy; + uint8_t rx_phy; +} __packed; + +#define BT_HCI_LE_PHY_TX_ANY BIT(0) +#define BT_HCI_LE_PHY_RX_ANY BIT(1) + +#define BT_HCI_LE_PHY_PREFER_1M BIT(0) +#define BT_HCI_LE_PHY_PREFER_2M BIT(1) +#define BT_HCI_LE_PHY_PREFER_CODED BIT(2) + +#define BT_HCI_OP_LE_SET_DEFAULT_PHY BT_OP(BT_OGF_LE, 0x0031) /* 0x2031 */ +struct bt_hci_cp_le_set_default_phy +{ + uint8_t all_phys; + uint8_t tx_phys; + uint8_t rx_phys; +} __packed; + +#define BT_HCI_LE_PHY_CODED_ANY 0x00 +#define BT_HCI_LE_PHY_CODED_S2 0x01 +#define BT_HCI_LE_PHY_CODED_S8 0x02 + +#define BT_HCI_OP_LE_SET_PHY BT_OP(BT_OGF_LE, 0x0032) /* 0x2032 */ +struct bt_hci_cp_le_set_phy +{ + uint16_t handle; + uint8_t all_phys; + uint8_t tx_phys; + uint8_t rx_phys; + uint16_t phy_opts; +} __packed; + +#define BT_HCI_LE_MOD_INDEX_STANDARD 0x00 +#define BT_HCI_LE_MOD_INDEX_STABLE 0x01 + +#define BT_HCI_LE_RX_PHY_1M 0x01 +#define BT_HCI_LE_RX_PHY_2M 0x02 +#define BT_HCI_LE_RX_PHY_CODED 0x03 + +#define BT_HCI_OP_LE_ENH_RX_TEST BT_OP(BT_OGF_LE, 0x0033) /* 0x2033 */ +struct bt_hci_cp_le_enh_rx_test +{ + uint8_t rx_ch; + uint8_t phy; + uint8_t mod_index; +} __packed; + +#define BT_HCI_LE_TX_PHY_1M 0x01 +#define BT_HCI_LE_TX_PHY_2M 0x02 +#define BT_HCI_LE_TX_PHY_CODED_S8 0x03 +#define BT_HCI_LE_TX_PHY_CODED_S2 0x04 + +#define BT_HCI_OP_LE_ENH_TX_TEST BT_OP(BT_OGF_LE, 0x0034) /* 0x2034 */ +struct bt_hci_cp_le_enh_tx_test +{ + uint8_t tx_ch; + uint8_t test_data_len; + uint8_t pkt_payload; + uint8_t phy; +} __packed; + +#define BT_HCI_OP_LE_SET_ADV_SET_RANDOM_ADDR BT_OP(BT_OGF_LE, 0x0035) /* 0x2035 */ +struct bt_hci_cp_le_set_adv_set_random_addr +{ + uint8_t handle; + bt_addr_t bdaddr; +} __packed; + +#define BT_HCI_LE_ADV_PROP_CONN BIT(0) +#define BT_HCI_LE_ADV_PROP_SCAN BIT(1) +#define BT_HCI_LE_ADV_PROP_DIRECT BIT(2) +#define BT_HCI_LE_ADV_PROP_HI_DC_CONN BIT(3) +#define BT_HCI_LE_ADV_PROP_LEGACY BIT(4) +#define BT_HCI_LE_ADV_PROP_ANON BIT(5) +#define BT_HCI_LE_ADV_PROP_TX_POWER BIT(6) + +#define BT_HCI_LE_PRIM_ADV_INTERVAL_MIN 0x000020 +#define BT_HCI_LE_PRIM_ADV_INTERVAL_MAX 0xFFFFFF + +#define BT_HCI_LE_ADV_SCAN_REQ_ENABLE 1 +#define BT_HCI_LE_ADV_SCAN_REQ_DISABLE 0 + +/** Minimum advertising TX power in dBm (Core Spec Vol 4, Part E, 7.8.53). */ +#define BT_HCI_LE_ADV_TX_POWER_MIN -127 +/** Maximum advertising TX power in dBm (Core Spec Vol 4, Part E, 7.8.53). */ +#define BT_HCI_LE_ADV_TX_POWER_MAX 20 +/** Advertising TX power: no preference, let the controller choose. */ +#define BT_HCI_LE_ADV_TX_POWER_NO_PREF 0x7F + +#define BT_HCI_LE_ADV_HANDLE_MAX 0xEF + +#define BT_HCI_LE_EXT_ADV_SID_INVALID 0xFF + +#define BT_HCI_OP_LE_SET_EXT_ADV_PARAM BT_OP(BT_OGF_LE, 0x0036) /* 0x2036 */ +struct bt_hci_cp_le_set_ext_adv_param +{ + uint8_t handle; + uint16_t props; + uint8_t prim_min_interval[3]; + uint8_t prim_max_interval[3]; + uint8_t prim_channel_map; + uint8_t own_addr_type; + bt_addr_le_t peer_addr; + uint8_t filter_policy; + int8_t tx_power; + uint8_t prim_adv_phy; + uint8_t sec_adv_max_skip; + uint8_t sec_adv_phy; + uint8_t sid; + uint8_t scan_req_notify_enable; +} __packed; +struct bt_hci_rp_le_set_ext_adv_param +{ + uint8_t status; + int8_t tx_power; +} __packed; + +#define BT_HCI_LE_ADV_PHY_OPTION_NO_REQUIRED 0x00 +#define BT_HCI_LE_ADV_PHY_OPTION_REQUIRE_S2 0x03 +#define BT_HCI_LE_ADV_PHY_OPTION_REQUIRE_S8 0x04 + +#define BT_HCI_OP_LE_SET_EXT_ADV_PARAM_V2 BT_OP(BT_OGF_LE, 0x007F) /* 0x207F */ +struct bt_hci_cp_le_set_ext_adv_param_v2 +{ + uint8_t handle; + uint16_t props; + uint8_t prim_min_interval[3]; + uint8_t prim_max_interval[3]; + uint8_t prim_channel_map; + uint8_t own_addr_type; + bt_addr_le_t peer_addr; + uint8_t filter_policy; + int8_t tx_power; + uint8_t prim_adv_phy; + uint8_t sec_adv_max_skip; + uint8_t sec_adv_phy; + uint8_t sid; + uint8_t scan_req_notify_enable; + uint8_t prim_adv_phy_opt; + uint8_t sec_adv_phy_opt; +} __packed; + +#define BT_HCI_LE_EXT_ADV_OP_INTERM_FRAG 0x00 +#define BT_HCI_LE_EXT_ADV_OP_FIRST_FRAG 0x01 +#define BT_HCI_LE_EXT_ADV_OP_LAST_FRAG 0x02 +#define BT_HCI_LE_EXT_ADV_OP_COMPLETE_DATA 0x03 +#define BT_HCI_LE_EXT_ADV_OP_UNCHANGED_DATA 0x04 + +#define BT_HCI_LE_EXT_ADV_FRAG_ENABLED 0x00 +#define BT_HCI_LE_EXT_ADV_FRAG_DISABLED 0x01 + +#define BT_HCI_LE_EXT_ADV_FRAG_MAX_LEN 251 + +#define BT_HCI_OP_LE_SET_EXT_ADV_DATA BT_OP(BT_OGF_LE, 0x0037) /* 0x2037 */ +struct bt_hci_cp_le_set_ext_adv_data +{ + uint8_t handle; + uint8_t op; + uint8_t frag_pref; + uint8_t len; + uint8_t data[0]; +} __packed; + +#define BT_HCI_OP_LE_SET_EXT_SCAN_RSP_DATA BT_OP(BT_OGF_LE, 0x0038) /* 0x2038 */ +struct bt_hci_cp_le_set_ext_scan_rsp_data +{ + uint8_t handle; + uint8_t op; + uint8_t frag_pref; + uint8_t len; + uint8_t data[0]; +} __packed; + +#define BT_HCI_OP_LE_SET_EXT_ADV_ENABLE BT_OP(BT_OGF_LE, 0x0039) /* 0x2039 */ +struct bt_hci_ext_adv_set +{ + uint8_t handle; + uint16_t duration; + uint8_t max_ext_adv_evts; +} __packed; + +struct bt_hci_cp_le_set_ext_adv_enable +{ + uint8_t enable; + uint8_t set_num; + struct bt_hci_ext_adv_set s[0]; +} __packed; + +#define BT_HCI_OP_LE_READ_MAX_ADV_DATA_LEN BT_OP(BT_OGF_LE, 0x003a) /* 0x203a */ +struct bt_hci_rp_le_read_max_adv_data_len +{ + uint8_t status; + uint16_t max_adv_data_len; +} __packed; + +#define BT_HCI_OP_LE_READ_NUM_ADV_SETS BT_OP(BT_OGF_LE, 0x003b) /* 0x203b */ +struct bt_hci_rp_le_read_num_adv_sets +{ + uint8_t status; + uint8_t num_sets; +} __packed; + +#define BT_HCI_OP_LE_REMOVE_ADV_SET BT_OP(BT_OGF_LE, 0x003c) /* 0x203c */ +struct bt_hci_cp_le_remove_adv_set +{ + uint8_t handle; +} __packed; + +#define BT_HCI_OP_CLEAR_ADV_SETS BT_OP(BT_OGF_LE, 0x003d) /* 0x203d */ + +#define BT_HCI_LE_PER_ADV_INTERVAL_MIN 0x0006 +#define BT_HCI_LE_PER_ADV_INTERVAL_MAX 0xFFFF + +#define BT_HCI_OP_LE_SET_PER_ADV_PARAM BT_OP(BT_OGF_LE, 0x003e) /* 0x203e */ +struct bt_hci_cp_le_set_per_adv_param +{ + uint8_t handle; + uint16_t min_interval; + uint16_t max_interval; + uint16_t props; +} __packed; + +#define BT_HCI_LE_PER_ADV_OP_INTERM_FRAG 0x00 +#define BT_HCI_LE_PER_ADV_OP_FIRST_FRAG 0x01 +#define BT_HCI_LE_PER_ADV_OP_LAST_FRAG 0x02 +#define BT_HCI_LE_PER_ADV_OP_COMPLETE_DATA 0x03 + +#define BT_HCI_LE_PER_ADV_FRAG_MAX_LEN 252 + +#define BT_HCI_OP_LE_SET_PER_ADV_DATA BT_OP(BT_OGF_LE, 0x003f) /* 0x203f */ +struct bt_hci_cp_le_set_per_adv_data +{ + uint8_t handle; + uint8_t op; + uint8_t len; + uint8_t data[0]; +} __packed; + +#define BT_HCI_LE_SET_PER_ADV_ENABLE_ENABLE BIT(0) +#define BT_HCI_LE_SET_PER_ADV_ENABLE_ADI BIT(1) + +#define BT_HCI_OP_LE_SET_PER_ADV_ENABLE BT_OP(BT_OGF_LE, 0x0040) /* 0x2040 */ +struct bt_hci_cp_le_set_per_adv_enable +{ + uint8_t enable; + uint8_t handle; +} __packed; + +#define BT_HCI_OP_LE_SET_EXT_SCAN_PARAM BT_OP(BT_OGF_LE, 0x0041) /* 0x2041 */ +struct bt_hci_ext_scan_phy +{ + uint8_t type; + uint16_t interval; + uint16_t window; +} __packed; + +#define BT_HCI_LE_EXT_SCAN_PHY_1M BIT(0) +#define BT_HCI_LE_EXT_SCAN_PHY_2M BIT(1) +#define BT_HCI_LE_EXT_SCAN_PHY_CODED BIT(2) + +struct bt_hci_cp_le_set_ext_scan_param +{ + uint8_t own_addr_type; + uint8_t filter_policy; + uint8_t phys; + struct bt_hci_ext_scan_phy p[0]; +} __packed; + +/* Extends BT_HCI_LE_SCAN_FILTER_DUP */ +#define BT_HCI_LE_EXT_SCAN_FILTER_DUP_ENABLE_RESET 0x02 + +#define BT_HCI_OP_LE_SET_EXT_SCAN_ENABLE BT_OP(BT_OGF_LE, 0x0042) /* 0x2042 */ +struct bt_hci_cp_le_set_ext_scan_enable +{ + uint8_t enable; + uint8_t filter_dup; + uint16_t duration; + uint16_t period; +} __packed; + +#define BT_HCI_OP_LE_EXT_CREATE_CONN BT_OP(BT_OGF_LE, 0x0043) /* 0x2043 */ +#define BT_HCI_OP_LE_EXT_CREATE_CONN_V2 BT_OP(BT_OGF_LE, 0x0085) /* 0x2085 */ +struct bt_hci_ext_conn_phy +{ + uint16_t scan_interval; + uint16_t scan_window; + uint16_t conn_interval_min; + uint16_t conn_interval_max; + uint16_t conn_latency; + uint16_t supervision_timeout; + uint16_t min_ce_len; + uint16_t max_ce_len; +} __packed; + +struct bt_hci_cp_le_ext_create_conn +{ + uint8_t filter_policy; + uint8_t own_addr_type; + bt_addr_le_t peer_addr; + uint8_t phys; + struct bt_hci_ext_conn_phy p[0]; +} __packed; + +struct bt_hci_cp_le_ext_create_conn_v2 +{ + uint8_t adv_handle; + uint8_t subevent; + uint8_t filter_policy; + uint8_t own_addr_type; + bt_addr_le_t peer_addr; + uint8_t phys; + struct bt_hci_ext_conn_phy p[0]; +} __packed; + +#define BT_HCI_OP_LE_SET_PER_ADV_SUBEVENT_DATA BT_OP(BT_OGF_LE, 0x0082) /* 0x2082 */ +struct bt_hci_cp_le_set_pawr_subevent_data_element +{ + uint8_t subevent; + uint8_t response_slot_start; + uint8_t response_slot_count; + uint8_t subevent_data_length; + uint8_t subevent_data[0]; +} __packed; + +struct bt_hci_cp_le_set_pawr_subevent_data +{ + uint8_t adv_handle; + uint8_t num_subevents; + struct bt_hci_cp_le_set_pawr_subevent_data_element subevents[0]; +} __packed; + +#define BT_HCI_OP_LE_SET_PER_ADV_RESPONSE_DATA BT_OP(BT_OGF_LE, 0x0083) /* 0x2083 */ +struct bt_hci_cp_le_set_pawr_response_data +{ + uint16_t sync_handle; + uint16_t request_event; + uint8_t request_subevent; + uint8_t response_subevent; + uint8_t response_slot; + uint8_t response_data_length; + uint8_t response_data[0]; +} __packed; + +#define BT_HCI_OP_LE_SET_PER_ADV_SYNC_SUBEVENT BT_OP(BT_OGF_LE, 0x0084) /* 0x2084 */ +struct bt_hci_cp_le_set_pawr_sync_subevent +{ + uint16_t sync_handle; + uint16_t periodic_adv_properties; + uint8_t num_subevents; + uint8_t subevents[0]; +} __packed; + +#define BT_HCI_OP_LE_SET_PER_ADV_PARAM_V2 BT_OP(BT_OGF_LE, 0x0086) /* 0x2086 */ +struct bt_hci_cp_le_set_per_adv_param_v2 +{ + uint8_t handle; + uint16_t min_interval; + uint16_t max_interval; + uint16_t props; + uint8_t num_subevents; + uint8_t subevent_interval; + uint8_t response_slot_delay; + uint8_t response_slot_spacing; + uint8_t num_response_slots; +} __packed; + +#define BT_HCI_LE_PER_ADV_CREATE_SYNC_FP_USE_LIST BIT(0) +#define BT_HCI_LE_PER_ADV_CREATE_SYNC_FP_REPORTS_DISABLED BIT(1) +#define BT_HCI_LE_PER_ADV_CREATE_SYNC_FP_FILTER_DUPLICATE BIT(2) + +#define BT_HCI_LE_PER_ADV_CREATE_SYNC_CTE_TYPE_NO_FILTERING 0 +#define BT_HCI_LE_PER_ADV_CREATE_SYNC_CTE_TYPE_NO_AOA BIT(0) +#define BT_HCI_LE_PER_ADV_CREATE_SYNC_CTE_TYPE_NO_AOD_1US BIT(1) +#define BT_HCI_LE_PER_ADV_CREATE_SYNC_CTE_TYPE_NO_AOD_2US BIT(2) +#define BT_HCI_LE_PER_ADV_CREATE_SYNC_CTE_TYPE_NO_CTE BIT(3) +#define BT_HCI_LE_PER_ADV_CREATE_SYNC_CTE_TYPE_ONLY_CTE BIT(4) +/* Constants to check correctness of CTE type */ +#define BT_HCI_LE_PER_ADV_CREATE_SYNC_CTE_TYPE_ALLOWED_BITS 5 +#define BT_HCI_LE_PER_ADV_CREATE_SYNC_CTE_TYPE_INVALID_VALUE \ +(~BIT_MASK(BT_HCI_LE_PER_ADV_CREATE_SYNC_CTE_TYPE_ALLOWED_BITS)) + +#define BT_HCI_OP_LE_PER_ADV_CREATE_SYNC BT_OP(BT_OGF_LE, 0x0044) /* 0x2044 */ +struct bt_hci_cp_le_per_adv_create_sync +{ + uint8_t options; + uint8_t sid; + bt_addr_le_t addr; + uint16_t skip; + uint16_t sync_timeout; + uint8_t cte_type; +} __packed; + +#define BT_HCI_OP_LE_PER_ADV_CREATE_SYNC_CANCEL BT_OP(BT_OGF_LE, 0x0045) /* 0x2045 */ + +#define BT_HCI_OP_LE_PER_ADV_TERMINATE_SYNC BT_OP(BT_OGF_LE, 0x0046) /* 0x2046 */ +struct bt_hci_cp_le_per_adv_terminate_sync +{ + uint16_t handle; +} __packed; + +#define BT_HCI_OP_LE_ADD_DEV_TO_PER_ADV_LIST BT_OP(BT_OGF_LE, 0x0047) /* 0x2047 */ +struct bt_hci_cp_le_add_dev_to_per_adv_list +{ + bt_addr_le_t addr; + uint8_t sid; +} __packed; + +#define BT_HCI_OP_LE_REM_DEV_FROM_PER_ADV_LIST BT_OP(BT_OGF_LE, 0x0048) /* 0x2048 */ +struct bt_hci_cp_le_rem_dev_from_per_adv_list +{ + bt_addr_le_t addr; + uint8_t sid; +} __packed; + +#define BT_HCI_OP_LE_CLEAR_PER_ADV_LIST BT_OP(BT_OGF_LE, 0x0049) /* 0x2049 */ + +#define BT_HCI_OP_LE_READ_PER_ADV_LIST_SIZE BT_OP(BT_OGF_LE, 0x004a) /* 0x204a */ +struct bt_hci_rp_le_read_per_adv_list_size +{ + uint8_t status; + uint8_t list_size; +} __packed; + +#define BT_HCI_OP_LE_READ_TX_POWER BT_OP(BT_OGF_LE, 0x004b) /* 0x204b */ +struct bt_hci_rp_le_read_tx_power +{ + uint8_t status; + int8_t min_tx_power; + int8_t max_tx_power; +} __packed; + +#define BT_HCI_OP_LE_READ_RF_PATH_COMP BT_OP(BT_OGF_LE, 0x004c) /* 0x204c */ +struct bt_hci_rp_le_read_rf_path_comp +{ + uint8_t status; + int16_t tx_path_comp; + int16_t rx_path_comp; +} __packed; + +#define BT_HCI_OP_LE_WRITE_RF_PATH_COMP BT_OP(BT_OGF_LE, 0x004d) /* 0x204d */ +struct bt_hci_cp_le_write_rf_path_comp +{ + int16_t tx_path_comp; + int16_t rx_path_comp; +} __packed; + +#define BT_HCI_LE_PRIVACY_MODE_NETWORK 0x00 +#define BT_HCI_LE_PRIVACY_MODE_DEVICE 0x01 + +#define BT_HCI_OP_LE_SET_PRIVACY_MODE BT_OP(BT_OGF_LE, 0x004e) /* 0x204e */ +struct bt_hci_cp_le_set_privacy_mode +{ + bt_addr_le_t id_addr; + uint8_t mode; +} __packed; + +#define BT_HCI_LE_TEST_CTE_DISABLED 0x00 +#define BT_HCI_LE_TEST_CTE_TYPE_ANY 0x00 +#define BT_HCI_LE_TEST_SLOT_DURATION_ANY 0x00 +#define BT_HCI_LE_TEST_SWITCH_PATTERN_LEN_ANY 0x00 + +#define BT_HCI_OP_LE_RX_TEST_V3 BT_OP(BT_OGF_LE, 0x004f) /* 0x204f */ +struct bt_hci_cp_le_rx_test_v3 +{ + uint8_t rx_ch; + uint8_t phy; + uint8_t mod_index; + uint8_t expected_cte_len; + uint8_t expected_cte_type; + uint8_t slot_durations; + uint8_t switch_pattern_len; + uint8_t ant_ids[0]; +} __packed; + +#define BT_HCI_OP_LE_TX_TEST_V3 BT_OP(BT_OGF_LE, 0x0050) /* 0x2050 */ + +struct bt_hci_cp_le_tx_test_v3 +{ + uint8_t tx_ch; + uint8_t test_data_len; + uint8_t pkt_payload; + uint8_t phy; + uint8_t cte_len; + uint8_t cte_type; + uint8_t switch_pattern_len; + uint8_t ant_ids[0]; +} __packed; + +/* Min and max Constant Tone Extension length in 8us units */ +#define BT_HCI_LE_CTE_LEN_MIN 0x2 +#define BT_HCI_LE_CTE_LEN_MAX 0x14 + +#define BT_HCI_LE_AOA_CTE 0x0 +#define BT_HCI_LE_AOD_CTE_1US 0x1 +#define BT_HCI_LE_AOD_CTE_2US 0x2 +#define BT_HCI_LE_NO_CTE 0xFF + +#define BT_HCI_LE_CTE_COUNT_MIN 0x1 +#define BT_HCI_LE_CTE_COUNT_MAX 0x10 + +#define BT_HCI_OP_LE_SET_CL_CTE_TX_PARAMS BT_OP(BT_OGF_LE, 0x0051) /* 0x2051 */ +struct bt_hci_cp_le_set_cl_cte_tx_params +{ + uint8_t handle; + uint8_t cte_len; + uint8_t cte_type; + uint8_t cte_count; + uint8_t switch_pattern_len; + uint8_t ant_ids[0]; +} __packed; + +#define BT_HCI_OP_LE_SET_CL_CTE_TX_ENABLE BT_OP(BT_OGF_LE, 0x0052) /* 0x2052 */ +struct bt_hci_cp_le_set_cl_cte_tx_enable +{ + uint8_t handle; + uint8_t cte_enable; +} __packed; + +#define BT_HCI_LE_ANTENNA_SWITCHING_SLOT_1US 0x1 +#define BT_HCI_LE_ANTENNA_SWITCHING_SLOT_2US 0x2 + +#define BT_HCI_LE_SAMPLE_CTE_ALL 0x0 +#define BT_HCI_LE_SAMPLE_CTE_COUNT_MIN 0x1 +#define BT_HCI_LE_SAMPLE_CTE_COUNT_MAX 0x10 + +#define BT_HCI_OP_LE_SET_CL_CTE_SAMPLING_ENABLE BT_OP(BT_OGF_LE, 0x0053) /* 0x2053 */ +struct bt_hci_cp_le_set_cl_cte_sampling_enable +{ + uint16_t sync_handle; + uint8_t sampling_enable; + uint8_t slot_durations; + uint8_t max_sampled_cte; + uint8_t switch_pattern_len; + uint8_t ant_ids[0]; +} __packed; + +struct bt_hci_rp_le_set_cl_cte_sampling_enable +{ + uint8_t status; + uint16_t sync_handle; +} __packed; + +#define BT_HCI_OP_LE_SET_CONN_CTE_RX_PARAMS BT_OP(BT_OGF_LE, 0x0054) /* 0x2054 */ +struct bt_hci_cp_le_set_conn_cte_rx_params +{ + uint16_t handle; + uint8_t sampling_enable; + uint8_t slot_durations; + uint8_t switch_pattern_len; + uint8_t ant_ids[0]; +} __packed; + +struct bt_hci_rp_le_set_conn_cte_rx_params +{ + uint8_t status; + uint16_t handle; +} __packed; + +#define BT_HCI_LE_AOA_CTE_RSP BIT(0) +#define BT_HCI_LE_AOD_CTE_RSP_1US BIT(1) +#define BT_HCI_LE_AOD_CTE_RSP_2US BIT(2) + +#define BT_HCI_LE_SWITCH_PATTERN_LEN_MIN 0x2 +#define BT_HCI_LE_SWITCH_PATTERN_LEN_MAX 0x4B + +#define BT_HCI_OP_LE_SET_CONN_CTE_TX_PARAMS BT_OP(BT_OGF_LE, 0x0055) /* 0x2055 */ +struct bt_hci_cp_le_set_conn_cte_tx_params +{ + uint16_t handle; + uint8_t cte_types; + uint8_t switch_pattern_len; + uint8_t ant_ids[0]; +} __packed; + +struct bt_hci_rp_le_set_conn_cte_tx_params +{ + uint8_t status; + uint16_t handle; +} __packed; + +/* Interval between consecutive CTE request procedure starts in number of + * connection events. + */ +#define BT_HCI_REQUEST_CTE_ONCE 0x0 +#define BT_HCI_REQUEST_CTE_INTERVAL_MIN 0x1 +#define BT_HCI_REQUEST_CTE_INTERVAL_MAX 0xFFFF + +#define BT_HCI_OP_LE_CONN_CTE_REQ_ENABLE BT_OP(BT_OGF_LE, 0x0056) /* 0x2056 */ +struct bt_hci_cp_le_conn_cte_req_enable +{ + uint16_t handle; + uint8_t enable; + uint16_t cte_request_interval; + uint8_t requested_cte_length; + uint8_t requested_cte_type; +} __packed; + +struct bt_hci_rp_le_conn_cte_req_enable +{ + uint8_t status; + uint16_t handle; +} __packed; + +#define BT_HCI_OP_LE_CONN_CTE_RSP_ENABLE BT_OP(BT_OGF_LE, 0x0057) /* 0x2057 */ +struct bt_hci_cp_le_conn_cte_rsp_enable +{ + uint16_t handle; + uint8_t enable; +} __packed; + +struct bt_hci_rp_le_conn_cte_rsp_enable +{ + uint8_t status; + uint16_t handle; +} __packed; + +#define BT_HCI_LE_1US_AOD_TX BIT(0) +#define BT_HCI_LE_1US_AOD_RX BIT(1) +#define BT_HCI_LE_1US_AOA_RX BIT(2) + +#define BT_HCI_LE_NUM_ANT_MIN 0x1 +#define BT_HCI_LE_NUM_ANT_MAX 0x4B + +#define BT_HCI_LE_MAX_SWITCH_PATTERN_LEN_MIN 0x2 +#define BT_HCI_LE_MAX_SWITCH_PATTERN_LEN_MAX 0x4B + +#define BT_HCI_LE_MAX_CTE_LEN_MIN 0x2 +#define BT_HCI_LE_MAX_CTE_LEN_MAX 0x14 + +#define BT_HCI_OP_LE_READ_ANT_INFO BT_OP(BT_OGF_LE, 0x0058) /* 0x2058 */ +struct bt_hci_rp_le_read_ant_info +{ + uint8_t status; + uint8_t switch_sample_rates; + uint8_t num_ant; + uint8_t max_switch_pattern_len; + uint8_t max_cte_len; +}; + +#define BT_HCI_LE_SET_PER_ADV_RECV_ENABLE_ENABLE BIT(0) +#define BT_HCI_LE_SET_PER_ADV_RECV_ENABLE_FILTER_DUPLICATE BIT(1) + +#define BT_HCI_OP_LE_SET_PER_ADV_RECV_ENABLE BT_OP(BT_OGF_LE, 0x0059) /* 0x2059 */ +struct bt_hci_cp_le_set_per_adv_recv_enable +{ + uint16_t handle; + uint8_t enable; +} __packed; + +#define BT_HCI_OP_LE_PER_ADV_SYNC_TRANSFER BT_OP(BT_OGF_LE, 0x005a) /* 0x205a */ +struct bt_hci_cp_le_per_adv_sync_transfer +{ + uint16_t conn_handle; + uint16_t service_data; + uint16_t sync_handle; +} __packed; + +struct bt_hci_rp_le_per_adv_sync_transfer +{ + uint8_t status; + uint16_t conn_handle; +} __packed; + +#define BT_HCI_OP_LE_PER_ADV_SET_INFO_TRANSFER BT_OP(BT_OGF_LE, 0x005b) /* 0x205b */ +struct bt_hci_cp_le_per_adv_set_info_transfer +{ + uint16_t conn_handle; + uint16_t service_data; + uint8_t adv_handle; +} __packed; + +struct bt_hci_rp_le_per_adv_set_info_transfer +{ + uint8_t status; + uint16_t conn_handle; +} __packed; + +#define BT_HCI_LE_PAST_MODE_NO_SYNC 0x00 +#define BT_HCI_LE_PAST_MODE_NO_REPORTS 0x01 +#define BT_HCI_LE_PAST_MODE_SYNC 0x02 +#define BT_HCI_LE_PAST_MODE_SYNC_FILTER_DUPLICATES 0x03 + +#define BT_HCI_LE_PAST_CTE_TYPE_NO_AOA BIT(0) +#define BT_HCI_LE_PAST_CTE_TYPE_NO_AOD_1US BIT(1) +#define BT_HCI_LE_PAST_CTE_TYPE_NO_AOD_2US BIT(2) +#define BT_HCI_LE_PAST_CTE_TYPE_NO_CTE BIT(3) +#define BT_HCI_LE_PAST_CTE_TYPE_ONLY_CTE BIT(4) + +#define BT_HCI_OP_LE_PAST_PARAM BT_OP(BT_OGF_LE, 0x005c) /* 0x205c */ +struct bt_hci_cp_le_past_param +{ + uint16_t conn_handle; + uint8_t mode; + uint16_t skip; + uint16_t timeout; + uint8_t cte_type; +} __packed; + +struct bt_hci_rp_le_past_param +{ + uint8_t status; + uint16_t conn_handle; +} __packed; + +#define BT_HCI_OP_LE_DEFAULT_PAST_PARAM BT_OP(BT_OGF_LE, 0x005d) /* 0x205d */ +struct bt_hci_cp_le_default_past_param +{ + uint8_t mode; + uint16_t skip; + uint16_t timeout; + uint8_t cte_type; +} __packed; + +struct bt_hci_rp_le_default_past_param +{ + uint8_t status; +} __packed; + +#define BT_HCI_OP_LE_READ_BUFFER_SIZE_V2 BT_OP(BT_OGF_LE, 0x0060) /* 0x2060 */ +struct bt_hci_rp_le_read_buffer_size_v2 +{ + uint8_t status; + uint16_t acl_max_len; + uint8_t acl_max_num; + uint16_t iso_max_len; + uint8_t iso_max_num; +} __packed; + +#define BT_HCI_OP_LE_READ_ISO_TX_SYNC BT_OP(BT_OGF_LE, 0x0061) /* 0x2061 */ +struct bt_hci_cp_le_read_iso_tx_sync +{ + uint16_t handle; +} __packed; + +struct bt_hci_rp_le_read_iso_tx_sync +{ + uint8_t status; + uint16_t handle; + uint16_t seq; + uint32_t timestamp; + uint8_t offset[3]; +} __packed; + +#define BT_HCI_ISO_CIG_ID_MAX 0xFE +#define BT_HCI_ISO_CIS_COUNT_MAX 0x1F +#define BT_HCI_ISO_SDU_INTERVAL_MIN 0x0000FF +#define BT_HCI_ISO_SDU_INTERVAL_MAX 0x0FFFFF +#define BT_HCI_ISO_WORST_CASE_SCA_VALID_MASK 0x07 +#define BT_HCI_ISO_PACKING_VALID_MASK 0x01 +#define BT_HCI_ISO_FRAMING_VALID_MASK 0x01 +#define BT_HCI_ISO_MAX_TRANSPORT_LATENCY_MIN 0x0005 +#define BT_HCI_ISO_MAX_TRANSPORT_LATENCY_MAX 0x0FA0 +#define BT_HCI_ISO_CIS_ID_VALID_MAX 0xEF +#define BT_HCI_ISO_MAX_SDU_VALID_MASK 0x0FFF +#define BT_HCI_ISO_PHY_VALID_MASK 0x07 +#define BT_HCI_ISO_INTERVAL_MIN 0x0004 +#define BT_HCI_ISO_INTERVAL_MAX 0x0C80 + +#define BT_HCI_OP_LE_SET_CIG_PARAMS BT_OP(BT_OGF_LE, 0x0062) /* 0x2062 */ +struct bt_hci_cis_params +{ + uint8_t cis_id; + uint16_t c_sdu; + uint16_t p_sdu; + uint8_t c_phy; + uint8_t p_phy; + uint8_t c_rtn; + uint8_t p_rtn; +} __packed; + +struct bt_hci_cp_le_set_cig_params +{ + uint8_t cig_id; + uint8_t c_interval[3]; + uint8_t p_interval[3]; + uint8_t sca; + uint8_t packing; + uint8_t framing; + uint16_t c_latency; + uint16_t p_latency; + uint8_t num_cis; + struct bt_hci_cis_params cis[0]; +} __packed; + +struct bt_hci_rp_le_set_cig_params +{ + uint8_t status; + uint8_t cig_id; + uint8_t num_handles; + uint16_t handle[0]; +} __packed; + +#define BT_HCI_OP_LE_SET_CIG_PARAMS_TEST BT_OP(BT_OGF_LE, 0x0063) /* 0x2063 */ +struct bt_hci_cis_params_test +{ + uint8_t cis_id; + uint8_t nse; + uint16_t c_sdu; + uint16_t p_sdu; + uint16_t c_pdu; + uint16_t p_pdu; + uint8_t c_phy; + uint8_t p_phy; + uint8_t c_bn; + uint8_t p_bn; +} __packed; + +struct bt_hci_cp_le_set_cig_params_test +{ + uint8_t cig_id; + uint8_t c_interval[3]; + uint8_t p_interval[3]; + uint8_t c_ft; + uint8_t p_ft; + uint16_t iso_interval; + uint8_t sca; + uint8_t packing; + uint8_t framing; + uint8_t num_cis; + struct bt_hci_cis_params_test cis[0]; +} __packed; + +struct bt_hci_rp_le_set_cig_params_test +{ + uint8_t status; + uint8_t cig_id; + uint8_t num_handles; + uint16_t handle[0]; +} __packed; + +#define BT_HCI_OP_LE_CREATE_CIS BT_OP(BT_OGF_LE, 0x0064) /* 0x2064 */ +struct bt_hci_cis +{ + uint16_t cis_handle; + uint16_t acl_handle; +} __packed; + +struct bt_hci_cp_le_create_cis +{ + uint8_t num_cis; + struct bt_hci_cis cis[0]; +} __packed; + +#define BT_HCI_OP_LE_REMOVE_CIG BT_OP(BT_OGF_LE, 0x0065) /* 0x2065 */ +struct bt_hci_cp_le_remove_cig +{ + uint8_t cig_id; +} __packed; + +struct bt_hci_rp_le_remove_cig +{ + uint8_t status; + uint8_t cig_id; +} __packed; + +#define BT_HCI_OP_LE_ACCEPT_CIS BT_OP(BT_OGF_LE, 0x0066) /* 0x2066 */ +struct bt_hci_cp_le_accept_cis +{ + uint16_t handle; +} __packed; + +#define BT_HCI_OP_LE_REJECT_CIS BT_OP(BT_OGF_LE, 0x0067) /* 0x2067 */ +struct bt_hci_cp_le_reject_cis +{ + uint16_t handle; + uint8_t reason; +} __packed; + +struct bt_hci_rp_le_reject_cis +{ + uint8_t status; + uint16_t handle; +} __packed; + +#define BT_HCI_OP_LE_CREATE_BIG BT_OP(BT_OGF_LE, 0x0068) /* 0x2068 */ +struct bt_hci_cp_le_create_big +{ + uint8_t big_handle; + uint8_t adv_handle; + uint8_t num_bis; + uint8_t sdu_interval[3]; + uint16_t max_sdu; + uint16_t max_latency; + uint8_t rtn; + uint8_t phy; + uint8_t packing; + uint8_t framing; + uint8_t encryption; + uint8_t bcode[16]; +} __packed; + +#define BT_HCI_OP_LE_CREATE_BIG_TEST BT_OP(BT_OGF_LE, 0x0069) /* 0x2069 */ +struct bt_hci_cp_le_create_big_test +{ + uint8_t big_handle; + uint8_t adv_handle; + uint8_t num_bis; + uint8_t sdu_interval[3]; + uint16_t iso_interval; + uint8_t nse; + uint16_t max_sdu; + uint16_t max_pdu; + uint8_t phy; + uint8_t packing; + uint8_t framing; + uint8_t bn; + uint8_t irc; + uint8_t pto; + uint8_t encryption; + uint8_t bcode[16]; +} __packed; + +#define BT_HCI_OP_LE_TERMINATE_BIG BT_OP(BT_OGF_LE, 0x006a) /* 0x206a */ +struct bt_hci_cp_le_terminate_big +{ + uint8_t big_handle; + uint8_t reason; +} __packed; + +#define BT_HCI_OP_LE_BIG_CREATE_SYNC BT_OP(BT_OGF_LE, 0x006b) /* 0x206b */ +struct bt_hci_cp_le_big_create_sync +{ + uint8_t big_handle; + uint16_t sync_handle; + uint8_t encryption; + uint8_t bcode[16]; + uint8_t mse; + uint16_t sync_timeout; + uint8_t num_bis; + uint8_t bis[0]; +} __packed; + +#define BT_HCI_OP_LE_BIG_TERMINATE_SYNC BT_OP(BT_OGF_LE, 0x006c) /* 0x206c */ +struct bt_hci_cp_le_big_terminate_sync +{ + uint8_t big_handle; +} __packed; + +struct bt_hci_rp_le_big_terminate_sync +{ + uint8_t status; + uint8_t big_handle; +} __packed; + +#define BT_HCI_OP_LE_REQ_PEER_SC BT_OP(BT_OGF_LE, 0x006d) /* 0x206d */ +struct bt_hci_cp_le_req_peer_sca +{ + uint16_t handle; +} __packed; + +#define BT_HCI_OP_LE_SETUP_ISO_PATH BT_OP(BT_OGF_LE, 0x006e) /* 0x206e */ +struct bt_hci_cp_le_setup_iso_path +{ + uint16_t handle; + uint8_t path_dir; + uint8_t path_id; + struct bt_hci_cp_codec_id codec_id; + uint8_t controller_delay[3]; + uint8_t codec_config_len; + uint8_t codec_config[0]; +} __packed; + +struct bt_hci_rp_le_setup_iso_path +{ + uint8_t status; + uint16_t handle; +} __packed; + +#define BT_HCI_OP_LE_REMOVE_ISO_PATH BT_OP(BT_OGF_LE, 0x006f) /* 0x206f */ +struct bt_hci_cp_le_remove_iso_path +{ + uint16_t handle; + uint8_t path_dir; +} __packed; + +struct bt_hci_rp_le_remove_iso_path +{ + uint8_t status; + uint16_t handle; +} __packed; + +#define BT_HCI_ISO_TEST_ZERO_SIZE_SDU 0 +#define BT_HCI_ISO_TEST_VARIABLE_SIZE_SDU 1 +#define BT_HCI_ISO_TEST_MAX_SIZE_SDU 2 + +#define BT_HCI_OP_LE_ISO_TRANSMIT_TEST BT_OP(BT_OGF_LE, 0x0070) /* 0x2070 */ +struct bt_hci_cp_le_iso_transmit_test +{ + uint16_t handle; + uint8_t payload_type; +} __packed; + +struct bt_hci_rp_le_iso_transmit_test +{ + uint8_t status; + uint16_t handle; +} __packed; + +#define BT_HCI_OP_LE_ISO_RECEIVE_TEST BT_OP(BT_OGF_LE, 0x0071) /* 0x2071 */ +struct bt_hci_cp_le_iso_receive_test +{ + uint16_t handle; + uint8_t payload_type; +} __packed; + +struct bt_hci_rp_le_iso_receive_test +{ + uint8_t status; + uint16_t handle; +} __packed; + +#define BT_HCI_OP_LE_ISO_READ_TEST_COUNTERS BT_OP(BT_OGF_LE, 0x0072) /* 0x2072 */ +struct bt_hci_cp_le_read_test_counters +{ + uint16_t handle; +} __packed; + +struct bt_hci_rp_le_read_test_counters +{ + uint8_t status; + uint16_t handle; + uint32_t received_cnt; + uint32_t missed_cnt; + uint32_t failed_cnt; +} __packed; + +#define BT_HCI_OP_LE_ISO_TEST_END BT_OP(BT_OGF_LE, 0x0073) /* 0x2073 */ +struct bt_hci_cp_le_iso_test_end +{ + uint16_t handle; +} __packed; + +struct bt_hci_rp_le_iso_test_end +{ + uint8_t status; + uint16_t handle; + uint32_t received_cnt; + uint32_t missed_cnt; + uint32_t failed_cnt; +} __packed; + +#define BT_HCI_OP_LE_SET_HOST_FEATURE BT_OP(BT_OGF_LE, 0x0074) /* 0x2074 */ +struct bt_hci_cp_le_set_host_feature +{ + uint8_t bit_number; + uint8_t bit_value; +} __packed; + +struct bt_hci_rp_le_set_host_feature +{ + uint8_t status; +} __packed; + +#define BT_HCI_OP_LE_READ_ISO_LINK_QUALITY BT_OP(BT_OGF_LE, 0x0075) /* 0x2075 */ +struct bt_hci_cp_le_read_iso_link_quality +{ + uint16_t handle; +} __packed; + +struct bt_hci_rp_le_read_iso_link_quality +{ + uint8_t status; + uint16_t handle; + uint32_t tx_unacked_packets; + uint32_t tx_flushed_packets; + uint32_t tx_last_subevent_packets; + uint32_t retransmitted_packets; + uint32_t crc_error_packets; + uint32_t rx_unreceived_packets; + uint32_t duplicate_packets; +} __packed; + +#define BT_HCI_OP_LE_TX_TEST_V4 BT_OP(BT_OGF_LE, 0x007B) /* 0x207B */ + +struct bt_hci_cp_le_tx_test_v4 +{ + uint8_t tx_ch; + uint8_t test_data_len; + uint8_t pkt_payload; + uint8_t phy; + uint8_t cte_len; + uint8_t cte_type; + uint8_t switch_pattern_len; + uint8_t ant_ids[0]; +} __packed; + +#define BT_HCI_OP_LE_READ_ALL_LOCAL_SUPPORTED_FEATURES BT_OP(BT_OGF_LE, 0x0087) /* 0x2087 */ +struct bt_hci_rp_le_read_all_local_supported_features +{ + uint8_t status; + uint8_t max_page; + uint8_t features[248]; +} __packed; + +#define BT_READ_ALL_LOCAL_FEATURES_SUPPORTED(supported_commands) \ +BT_CMD_TEST(supported_commands, 47, 2) + +#define BT_HCI_OP_LE_READ_ALL_REMOTE_FEATURES BT_OP(BT_OGF_LE, 0x0088) /* 0x2088 */ +struct bt_hci_cp_le_read_all_remote_features +{ + uint16_t handle; + uint8_t pages_requested; +} __packed; + +#define BT_HCI_TX_TEST_POWER_MIN -0x7F +#define BT_HCI_TX_TEST_POWER_MAX 0x14 + +#define BT_HCI_TX_TEST_POWER_MIN_SET 0x7E +#define BT_HCI_TX_TEST_POWER_MAX_SET 0x7F + +/* Helper structure for Tx power parameter in the HCI Tx Test v4 command. + * Previous parameter of this command is variable size so having separated + * structure for this parameter helps in command parameters unpacking. + */ + +struct bt_hci_cp_le_tx_test_v4_tx_power +{ + int8_t tx_power; +} __packed; + +#define BT_HCI_OP_LE_CS_READ_LOCAL_SUPPORTED_CAPABILITIES BT_OP(BT_OGF_LE, 0x0089) /* 0x2089 */ + +struct bt_hci_rp_le_read_local_supported_capabilities +{ + uint8_t status; + uint8_t num_config_supported; + uint16_t max_consecutive_procedures_supported; + uint8_t num_antennas_supported; + uint8_t max_antenna_paths_supported; + uint8_t roles_supported; + uint8_t modes_supported; + uint8_t rtt_capability; + uint8_t rtt_aa_only_n; + uint8_t rtt_sounding_n; + uint8_t rtt_random_payload_n; + uint16_t nadm_sounding_capability; + uint16_t nadm_random_capability; + uint8_t cs_sync_phys_supported; + uint16_t subfeatures_supported; + uint16_t t_ip1_times_supported; + uint16_t t_ip2_times_supported; + uint16_t t_fcs_times_supported; + uint16_t t_pm_times_supported; + uint8_t t_sw_time_supported; + uint8_t tx_snr_capability; +} __packed; + +/** HCI opcode for LE Read Local Supported Capabilities (v2). */ +#define BT_HCI_OP_LE_CS_READ_LOCAL_SUPPORTED_CAPABILITIES_V2 BT_OP(BT_OGF_LE, 0x00A5) /* 0x20A5 */ + +/* * Test if LE Read Local Supported Capabilities v2 command is supported + * (octet 49, bit 2). + */ +#define BT_LE_CS_READ_LOCAL_SUPPORTED_CAPABILITIES_V2_SUPPORTED(cmd) BT_CMD_TEST(cmd, 49, 2) + +/** HCI response parameters for LE Read Local Supported Capabilities command + * (v2). + */ + +struct bt_hci_rp_le_read_local_supported_capabilities_v2 +{ + /** Status. */ + + uint8_t status; + + /** Number of CS configurations supported. */ + + uint8_t num_config_supported; + + /** Maximum consecutive procedures supported. */ + + uint16_t max_consecutive_procedures_supported; + + /** Number of antennas supported. */ + + uint8_t num_antennas_supported; + + /** Maximum antenna paths supported. */ + + uint8_t max_antenna_paths_supported; + + /** Roles supported. */ + + uint8_t roles_supported; + + /** Modes supported. */ + + uint8_t modes_supported; + + /** RTT capability. */ + + uint8_t rtt_capability; + + /** RTT AA-only N. */ + + uint8_t rtt_aa_only_n; + + /** RTT sounding N. */ + + uint8_t rtt_sounding_n; + + /** RTT random payload N. */ + + uint8_t rtt_random_payload_n; + + /** NADM sounding capability. */ + + uint16_t nadm_sounding_capability; + + /** NADM random capability. */ + + uint16_t nadm_random_capability; + + /** CS sync PHYs supported. */ + + uint8_t cs_sync_phys_supported; + + /** Subfeatures supported. */ + + uint16_t subfeatures_supported; + + /** T_IP1 times supported. */ + + uint16_t t_ip1_times_supported; + + /** T_IP2 times supported. */ + + uint16_t t_ip2_times_supported; + + /** T_FCS times supported. */ + + uint16_t t_fcs_times_supported; + + /** T_PM times supported. */ + + uint16_t t_pm_times_supported; + + /** T_SW time supported. */ + + uint8_t t_sw_time_supported; + + /** TX SNR capability. */ + + uint8_t tx_snr_capability; + + /** T_IP2 IPT times supported. */ + + uint16_t t_ip2_ipt_times_supported; + + /** T_SW IPT time supported. */ + + uint8_t t_sw_ipt_time_supported; +} __packed; + +#define BT_HCI_OP_LE_CS_READ_REMOTE_SUPPORTED_CAPABILITIES BT_OP(BT_OGF_LE, 0x008A) /* 0x208A */ + +struct bt_hci_cp_le_read_remote_supported_capabilities +{ + uint16_t handle; +} __packed; + +#define BT_HCI_OP_LE_CS_WRITE_CACHED_REMOTE_SUPPORTED_CAPABILITIES \ +BT_OP(BT_OGF_LE, 0x008B) /* 0x208B */ + +struct bt_hci_cp_le_write_cached_remote_supported_capabilities +{ + uint16_t handle; + uint8_t num_config_supported; + uint16_t max_consecutive_procedures_supported; + uint8_t num_antennas_supported; + uint8_t max_antenna_paths_supported; + uint8_t roles_supported; + uint8_t modes_supported; + uint8_t rtt_capability; + uint8_t rtt_aa_only_n; + uint8_t rtt_sounding_n; + uint8_t rtt_random_payload_n; + uint16_t nadm_sounding_capability; + uint16_t nadm_random_capability; + uint8_t cs_sync_phys_supported; + uint16_t subfeatures_supported; + uint16_t t_ip1_times_supported; + uint16_t t_ip2_times_supported; + uint16_t t_fcs_times_supported; + uint16_t t_pm_times_supported; + uint8_t t_sw_time_supported; + uint8_t tx_snr_capability; +} __packed; + +/** HCI opcode for LE CS Write Cached Remote Supported Capabilities (v2). */ +#define BT_HCI_OP_LE_CS_WRITE_CACHED_REMOTE_SUPPORTED_CAPABILITIES_V2 \ +BT_OP(BT_OGF_LE, 0x00A6) /* 0x20A6 */ + +/** HCI command parameters for LE CS Write Cached Remote Supported + * Capabilities (v2). + */ + +struct bt_hci_cp_le_write_cached_remote_supported_capabilities_v2 +{ + /** Connection handle. */ + + uint16_t handle; + + /** Number of CS configurations supported. */ + + uint8_t num_config_supported; + + /** Maximum consecutive procedures supported. */ + + uint16_t max_consecutive_procedures_supported; + + /** Number of antennas supported. */ + + uint8_t num_antennas_supported; + + /** Maximum antenna paths supported. */ + + uint8_t max_antenna_paths_supported; + + /** Roles supported. */ + + uint8_t roles_supported; + + /** Modes supported. */ + + uint8_t modes_supported; + + /** RTT capability. */ + + uint8_t rtt_capability; + + /** RTT AA-only N. */ + + uint8_t rtt_aa_only_n; + + /** RTT sounding N. */ + + uint8_t rtt_sounding_n; + + /** RTT random payload N. */ + + uint8_t rtt_random_payload_n; + + /** NADM sounding capability. */ + + uint16_t nadm_sounding_capability; + + /** NADM random capability. */ + + uint16_t nadm_random_capability; + + /** CS sync PHYs supported. */ + + uint8_t cs_sync_phys_supported; + + /** Subfeatures supported. */ + + uint16_t subfeatures_supported; + + /** T_IP1 times supported. */ + + uint16_t t_ip1_times_supported; + + /** T_IP2 times supported. */ + + uint16_t t_ip2_times_supported; + + /** T_FCS times supported. */ + + uint16_t t_fcs_times_supported; + + /** T_PM times supported. */ + + uint16_t t_pm_times_supported; + + /** T_SW time supported. */ + + uint8_t t_sw_time_supported; + + /** TX SNR capability. */ + + uint8_t tx_snr_capability; + + /** T_IP2 IPT times supported. */ + + uint16_t t_ip2_ipt_times_supported; + + /** T_SW IPT time supported. */ + + uint8_t t_sw_ipt_time_supported; +} __packed; + +#define BT_HCI_OP_LE_CS_SECURITY_ENABLE BT_OP(BT_OGF_LE, 0x008C) /* 0x208C */ + +struct bt_hci_cp_le_security_enable +{ + uint16_t handle; +} __packed; + +#define BT_HCI_OP_LE_CS_SET_DEFAULT_SETTINGS BT_OP(BT_OGF_LE, 0x008D) /* 0x208D */ + +#define BT_HCI_OP_LE_CS_INITIATOR_ROLE_MASK BIT(0) +#define BT_HCI_OP_LE_CS_REFLECTOR_ROLE_MASK BIT(1) + +#define BT_HCI_OP_LE_CS_MIN_MAX_TX_POWER -127 +#define BT_HCI_OP_LE_CS_MAX_MAX_TX_POWER 20 + +#define BT_HCI_OP_LE_CS_ANTENNA_SEL_ONE 0x01 +#define BT_HCI_OP_LE_CS_ANTENNA_SEL_TWO 0x02 +#define BT_HCI_OP_LE_CS_ANTENNA_SEL_THREE 0x03 +#define BT_HCI_OP_LE_CS_ANTENNA_SEL_FOUR 0x04 +#define BT_HCI_OP_LE_CS_ANTENNA_SEL_REP 0xFE +#define BT_HCI_OP_LE_CS_ANTENNA_SEL_NONE 0xFF + +struct bt_hci_cp_le_cs_set_default_settings +{ + uint16_t handle; + uint8_t role_enable; + uint8_t cs_sync_antenna_selection; + int8_t max_tx_power; +} __packed; + +#define BT_HCI_OP_LE_CS_READ_REMOTE_FAE_TABLE BT_OP(BT_OGF_LE, 0x008E) /* 0x208E */ + +struct bt_hci_cp_le_read_remote_fae_table +{ + uint16_t handle; +} __packed; + +#define BT_HCI_OP_LE_CS_WRITE_CACHED_REMOTE_FAE_TABLE BT_OP(BT_OGF_LE, 0x008F) /* 0x208F */ + +struct bt_hci_cp_le_write_cached_remote_fae_table +{ + uint16_t handle; + int8_t remote_fae_table[72]; +} __packed; + +#define BT_HCI_OP_LE_CS_SET_CHANNEL_CLASSIFICATION BT_OP(BT_OGF_LE, 0x0092) /* 0x2092 */ + +#define BT_HCI_OP_LE_CS_SET_PROCEDURE_PARAMETERS BT_OP(BT_OGF_LE, 0x0093) /* 0x2093 */ + +#define BT_HCI_OP_LE_CS_PROCEDURE_PHY_1M 0x01 +#define BT_HCI_OP_LE_CS_PROCEDURE_PHY_2M 0x02 +#define BT_HCI_OP_LE_CS_PROCEDURE_PHY_CODED_S8 0x03 +#define BT_HCI_OP_LE_CS_PROCEDURE_PHY_CODED_S2 0x04 + +struct bt_hci_cp_le_set_procedure_parameters +{ + uint16_t handle; + uint8_t config_id; + uint16_t max_procedure_len; + uint16_t min_procedure_interval; + uint16_t max_procedure_interval; + uint16_t max_procedure_count; + uint8_t min_subevent_len[3]; + uint8_t max_subevent_len[3]; + uint8_t tone_antenna_config_selection; + uint8_t phy; + uint8_t tx_power_delta; + uint8_t preferred_peer_antenna; + uint8_t snr_control_initiator; + uint8_t snr_control_reflector; +} __packed; + +#define BT_HCI_OP_LE_CS_PROCEDURE_ENABLE BT_OP(BT_OGF_LE, 0x0094) /* 0x2094 */ + +#define BT_HCI_OP_LE_CS_PROCEDURES_DISABLED 0x00 +#define BT_HCI_OP_LE_CS_PROCEDURES_ENABLED 0x01 + +struct bt_hci_cp_le_procedure_enable +{ + uint16_t handle; + uint8_t config_id; + uint8_t enable; +} __packed; + +#define BT_HCI_OP_LE_CS_TEST BT_OP(BT_OGF_LE, 0x0095) /* 0x2095 */ + +#define BT_HCI_OP_LE_CS_MAIN_MODE_1 0x1 +#define BT_HCI_OP_LE_CS_MAIN_MODE_2 0x2 +#define BT_HCI_OP_LE_CS_MAIN_MODE_3 0x3 + +#define BT_HCI_OP_LE_CS_SUB_MODE_1 0x1 +#define BT_HCI_OP_LE_CS_SUB_MODE_2 0x2 +#define BT_HCI_OP_LE_CS_SUB_MODE_3 0x3 +#define BT_HCI_OP_LE_CS_SUB_MODE_UNUSED 0xFF + +#define BT_HCI_OP_LE_CS_INITIATOR_ROLE 0x0 +#define BT_HCI_OP_LE_CS_REFLECTOR_ROLE 0x1 + +#define BT_HCI_OP_LE_CS_RTT_TYPE_AA_ONLY 0x0 +#define BT_HCI_OP_LE_CS_RTT_TYPE_32BIT_SOUND 0x1 +#define BT_HCI_OP_LE_CS_RTT_TYPE_96BIT_SOUND 0x2 +#define BT_HCI_OP_LE_CS_RTT_TYPE_32BIT_RAND 0x3 +#define BT_HCI_OP_LE_CS_RTT_TYPE_64BIT_RAND 0x4 +#define BT_HCI_OP_LE_CS_RTT_TYPE_96BIT_RAND 0x5 +#define BT_HCI_OP_LE_CS_RTT_TYPE_128BIT_RAND 0x6 + +#define BT_HCI_OP_LE_CS_CS_SYNC_1M 0x1 +#define BT_HCI_OP_LE_CS_CS_SYNC_2M 0x2 +#define BT_HCI_OP_LE_CS_CS_SYNC_2M_2BT 0x3 + +#define BT_HCI_OP_LE_CS_TEST_MINIMIZE_TX_POWER 0x7E +#define BT_HCI_OP_LE_CS_TEST_MAXIMIZE_TX_POWER 0x7F + +#define BT_HCI_OP_LE_CS_ACI_0 0x0 +#define BT_HCI_OP_LE_CS_ACI_1 0x1 +#define BT_HCI_OP_LE_CS_ACI_2 0x2 +#define BT_HCI_OP_LE_CS_ACI_3 0x3 +#define BT_HCI_OP_LE_CS_ACI_4 0x4 +#define BT_HCI_OP_LE_CS_ACI_5 0x5 +#define BT_HCI_OP_LE_CS_ACI_6 0x6 +#define BT_HCI_OP_LE_CS_ACI_7 0x7 + +#define BT_HCI_OP_LE_CS_SNR_18 0x0 +#define BT_HCI_OP_LE_CS_SNR_21 0x1 +#define BT_HCI_OP_LE_CS_SNR_24 0x2 +#define BT_HCI_OP_LE_CS_SNR_27 0x3 +#define BT_HCI_OP_LE_CS_SNR_30 0x4 +#define BT_HCI_OP_LE_CS_SNR_NOT_USED 0xFF + +#define BT_HCI_OP_LE_CS_TEST_OVERRIDE_CONFIG_0_MASK BIT(0) +#define BT_HCI_OP_LE_CS_TEST_OVERRIDE_CONFIG_2_MASK BIT(2) +#define BT_HCI_OP_LE_CS_TEST_OVERRIDE_CONFIG_3_MASK BIT(3) +#define BT_HCI_OP_LE_CS_TEST_OVERRIDE_CONFIG_4_MASK BIT(4) +#define BT_HCI_OP_LE_CS_TEST_OVERRIDE_CONFIG_5_MASK BIT(5) +#define BT_HCI_OP_LE_CS_TEST_OVERRIDE_CONFIG_6_MASK BIT(6) +#define BT_HCI_OP_LE_CS_TEST_OVERRIDE_CONFIG_7_MASK BIT(7) +#define BT_HCI_OP_LE_CS_TEST_OVERRIDE_CONFIG_8_MASK BIT(8) +#define BT_HCI_OP_LE_CS_TEST_OVERRIDE_CONFIG_10_MASK BIT(10) + +#define BT_HCI_OP_LE_CS_TEST_CHSEL_TYPE_3B 0x0 +#define BT_HCI_OP_LE_CS_TEST_CHSEL_TYPE_3C 0x1 + +#define BT_HCI_OP_LE_CS_TEST_CH3C_SHAPE_HAT 0x0 +#define BT_HCI_OP_LE_CS_TEST_CH3C_SHAPE_X 0x1 + +#define BT_HCI_OP_LE_CS_TEST_TONE_EXT_NONE 0x0 +#define BT_HCI_OP_LE_CS_TEST_TONE_EXT_INIT 0x1 +#define BT_HCI_OP_LE_CS_TEST_TONE_EXT_REFL 0x2 +#define BT_HCI_OP_LE_CS_TEST_TONE_EXT_BOTH 0x3 +#define BT_HCI_OP_LE_CS_TEST_TONE_EXT_REPEAT 0x4 + +#define BT_HCI_OP_LE_CS_TEST_AP_INDEX_00 0x0 +#define BT_HCI_OP_LE_CS_TEST_AP_INDEX_01 0x1 +#define BT_HCI_OP_LE_CS_TEST_AP_INDEX_02 0x2 +#define BT_HCI_OP_LE_CS_TEST_AP_INDEX_03 0x3 +#define BT_HCI_OP_LE_CS_TEST_AP_INDEX_04 0x4 +#define BT_HCI_OP_LE_CS_TEST_AP_INDEX_05 0x5 +#define BT_HCI_OP_LE_CS_TEST_AP_INDEX_06 0x6 +#define BT_HCI_OP_LE_CS_TEST_AP_INDEX_07 0x7 +#define BT_HCI_OP_LE_CS_TEST_AP_INDEX_08 0x8 +#define BT_HCI_OP_LE_CS_TEST_AP_INDEX_09 0x9 +#define BT_HCI_OP_LE_CS_TEST_AP_INDEX_10 0xA +#define BT_HCI_OP_LE_CS_TEST_AP_INDEX_11 0xB +#define BT_HCI_OP_LE_CS_TEST_AP_INDEX_12 0xC +#define BT_HCI_OP_LE_CS_TEST_AP_INDEX_13 0xD +#define BT_HCI_OP_LE_CS_TEST_AP_INDEX_14 0xE +#define BT_HCI_OP_LE_CS_TEST_AP_INDEX_15 0xF +#define BT_HCI_OP_LE_CS_TEST_AP_INDEX_16 0x10 +#define BT_HCI_OP_LE_CS_TEST_AP_INDEX_17 0x11 +#define BT_HCI_OP_LE_CS_TEST_AP_INDEX_18 0x12 +#define BT_HCI_OP_LE_CS_TEST_AP_INDEX_19 0x13 +#define BT_HCI_OP_LE_CS_TEST_AP_INDEX_20 0x14 +#define BT_HCI_OP_LE_CS_TEST_AP_INDEX_21 0x15 +#define BT_HCI_OP_LE_CS_TEST_AP_INDEX_22 0x16 +#define BT_HCI_OP_LE_CS_TEST_AP_INDEX_23 0x17 +#define BT_HCI_OP_LE_CS_TEST_AP_INDEX_LOOP 0xFF + +#define BT_HCI_OP_LE_CS_TEST_SS_MARKER_2_POSITION_NOT_PRESENT 0xFF + +#define BT_HCI_OP_LE_CS_TEST_SS_MARKER_VAL_0011 0x0 +#define BT_HCI_OP_LE_CS_TEST_SS_MARKER_VAL_1100 0x1 +#define BT_HCI_OP_LE_CS_TEST_SS_MARKER_VAL_LOOP 0x2 + +#define BT_HCI_OP_LE_CS_TEST_PAYLOAD_PRBS9 0x00 +#define BT_HCI_OP_LE_CS_TEST_PAYLOAD_11110000 0x01 +#define BT_HCI_OP_LE_CS_TEST_PAYLOAD_10101010 0x02 +#define BT_HCI_OP_LE_CS_TEST_PAYLOAD_PRBS15 0x03 +#define BT_HCI_OP_LE_CS_TEST_PAYLOAD_11111111 0x04 +#define BT_HCI_OP_LE_CS_TEST_PAYLOAD_00000000 0x05 +#define BT_HCI_OP_LE_CS_TEST_PAYLOAD_00001111 0x06 +#define BT_HCI_OP_LE_CS_TEST_PAYLOAD_01010101 0x07 +#define BT_HCI_OP_LE_CS_TEST_PAYLOAD_USER 0x80 + +struct bt_hci_op_le_cs_test +{ + uint8_t main_mode_type; + uint8_t sub_mode_type; + uint8_t main_mode_repetition; + uint8_t mode_0_steps; + uint8_t role; + uint8_t rtt_type; + uint8_t cs_sync_phy; + uint8_t cs_sync_antenna_selection; + uint8_t subevent_len[3]; + uint16_t subevent_interval; + uint8_t max_num_subevents; + uint8_t transmit_power_level; + uint8_t t_ip1_time; + uint8_t t_ip2_time; + uint8_t t_fcs_time; + uint8_t t_pm_time; + uint8_t t_sw_time; + uint8_t tone_antenna_config_selection; + + /** CS enhancements 1. */ + + uint8_t cs_enhancements_1; + uint8_t snr_control_initiator; + uint8_t snr_control_reflector; + uint16_t drbg_nonce; + uint8_t channel_map_repetition; + uint16_t override_config; + uint8_t override_parameters_length; + uint8_t override_parameters_data[]; +} __packed; + +#define BT_HCI_OP_LE_CS_CREATE_CONFIG BT_OP(BT_OGF_LE, 0x0090) /* 0x2090 */ + +struct bt_hci_cp_le_cs_create_config +{ + uint16_t handle; + uint8_t config_id; + uint8_t create_context; + uint8_t main_mode_type; + uint8_t sub_mode_type; + uint8_t min_main_mode_steps; + uint8_t max_main_mode_steps; + uint8_t main_mode_repetition; + uint8_t mode_0_steps; + uint8_t role; + uint8_t rtt_type; + uint8_t cs_sync_phy; + uint8_t channel_map[10]; + uint8_t channel_map_repetition; + uint8_t channel_selection_type; + uint8_t ch3c_shape; + uint8_t ch3c_jump; + + /** CS enhancements 1. */ + + uint8_t cs_enhancements_1; +} __packed; + +#define BT_HCI_OP_LE_CS_REMOVE_CONFIG BT_OP(BT_OGF_LE, 0x0091) /* 0x2091 */ + +struct bt_hci_cp_le_cs_remove_config +{ + uint16_t handle; + uint8_t config_id; +} __packed; + +#define BT_HCI_OP_LE_CS_TEST_END BT_OP(BT_OGF_LE, 0x0096) /* 0x2096 */ + +#define BT_HCI_LE_FRAME_SPACE_UPDATE_PHY_1M_MASK BIT(0) +#define BT_HCI_LE_FRAME_SPACE_UPDATE_PHY_2M_MASK BIT(1) +#define BT_HCI_LE_FRAME_SPACE_UPDATE_PHY_CODED_MASK BIT(2) + +#define BT_HCI_LE_FRAME_SPACE_UPDATE_SPACING_TYPE_IFS_ACL_CP_MASK BIT(0) +#define BT_HCI_LE_FRAME_SPACE_UPDATE_SPACING_TYPE_IFS_ACL_PC_MASK BIT(1) +#define BT_HCI_LE_FRAME_SPACE_UPDATE_SPACING_TYPE_MCES_MASK BIT(2) +#define BT_HCI_LE_FRAME_SPACE_UPDATE_SPACING_TYPE_IFS_CIS_MASK BIT(3) +#define BT_HCI_LE_FRAME_SPACE_UPDATE_SPACING_TYPE_MSS_CIS_MASK BIT(4) + +#define BT_HCI_LE_FRAME_SPACE_UPDATE_INITIATOR_LOCAL_HOST (0) +#define BT_HCI_LE_FRAME_SPACE_UPDATE_INITIATOR_LOCAL_CONTROLLER (1) +#define BT_HCI_LE_FRAME_SPACE_UPDATE_INITIATOR_PEER (2) + +struct bt_hci_cp_le_frame_space_update +{ + uint16_t handle; + uint16_t frame_space_min; + uint16_t frame_space_max; + uint8_t phys; + uint16_t spacing_types; +} __packed; + +#define BT_HCI_OP_LE_FRAME_SPACE_UPDATE BT_OP(BT_OGF_LE, 0x009D) /* 0x209D */ + +/** All limits according to BT Core spec 6.2 [Vol 4, Part E, 7.8.154]. */ +#define BT_HCI_LE_SCI_INTERVAL_MIN_125US (0x0003U) +#define BT_HCI_LE_SCI_INTERVAL_MAX_125US (0x7D00U) +#define BT_HCI_LE_SCI_INTERVAL_MIN_US (375U) +#define BT_HCI_LE_SCI_INTERVAL_MAX_US (4000000U) +#define BT_HCI_LE_SCI_INTERVAL_UNIT_US (125U) + +#define BT_HCI_LE_SCI_STRIDE_MIN_125US (0x0001U) + +#define BT_HCI_LE_MIN_SUPP_CONN_INT_MIN_US (375U) +#define BT_HCI_LE_MIN_SUPP_CONN_INT_MAX_US (7500U) + +#define BT_HCI_LE_SCI_CE_LEN_MIN_125US (0x0001U) +#define BT_HCI_LE_SCI_CE_LEN_MAX_125US (0x3E7FU) + +struct bt_hci_le_read_min_supported_conn_interval_group +{ + uint16_t group_min; + uint16_t group_max; + uint16_t group_stride; +} __packed; + +struct bt_hci_op_le_read_min_supported_conn_interval +{ + uint8_t status; + uint8_t min_supported_conn_interval; + uint8_t num_groups; + struct bt_hci_le_read_min_supported_conn_interval_group groups[]; +} __packed; + +#define BT_HCI_OP_LE_READ_MIN_SUPPORTED_CONN_INTERVAL BT_OP(BT_OGF_LE, 0x00A3) /* 0x20A3 */ + +struct bt_hci_op_le_set_default_rate_parameters +{ + uint16_t conn_interval_min; + uint16_t conn_interval_max; + uint16_t subrate_min; + uint16_t subrate_max; + uint16_t max_latency; + uint16_t continuation_number; + uint16_t supervision_timeout; + uint16_t min_ce_len; + uint16_t max_ce_len; +} __packed; + +#define BT_HCI_OP_LE_SET_DEFAULT_RATE_PARAMETERS BT_OP(BT_OGF_LE, 0x00A2) /* 0x20A2 */ + +struct bt_hci_op_le_connection_rate_request +{ + uint16_t handle; + uint16_t conn_interval_min; + uint16_t conn_interval_max; + uint16_t subrate_min; + uint16_t subrate_max; + uint16_t max_latency; + uint16_t continuation_number; + uint16_t supervision_timeout; + uint16_t min_ce_len; + uint16_t max_ce_len; +} __packed; + +#define BT_HCI_OP_LE_CONNECTION_RATE_REQUEST BT_OP(BT_OGF_LE, 0x00A1) /* 0x20A1 */ + +/* Event definitions */ + +#define BT_HCI_EVT_UNKNOWN 0x00 +#define BT_HCI_EVT_VENDOR 0xff + +#define BT_HCI_EVT_INQUIRY_COMPLETE 0x01 +struct bt_hci_evt_inquiry_complete +{ + uint8_t status; +} __packed; + +#define BT_HCI_EVT_CONN_COMPLETE 0x03 +struct bt_hci_evt_conn_complete +{ + uint8_t status; + uint16_t handle; + bt_addr_t bdaddr; + uint8_t link_type; + uint8_t encr_enabled; +} __packed; + +#define BT_HCI_EVT_CONN_REQUEST 0x04 +struct bt_hci_evt_conn_request +{ + bt_addr_t bdaddr; + uint8_t dev_class[3]; + uint8_t link_type; +} __packed; + +#define BT_HCI_EVT_DISCONN_COMPLETE 0x05 +struct bt_hci_evt_disconn_complete +{ + uint8_t status; + uint16_t handle; + uint8_t reason; +} __packed; + +#define BT_HCI_EVT_AUTH_COMPLETE 0x06 +struct bt_hci_evt_auth_complete +{ + uint8_t status; + uint16_t handle; +} __packed; + +#define BT_HCI_EVT_REMOTE_NAME_REQ_COMPLETE 0x07 +struct bt_hci_evt_remote_name_req_complete +{ + uint8_t status; + bt_addr_t bdaddr; + uint8_t name[248]; +} __packed; + +/* Encryption Value */ +#define BT_HCI_ENCRYPTION_OFF 0x00 +#define BT_HCI_ENCRYPTION_ON_LE_AES_CCM 0x01 +#define BT_HCI_ENCRYPTION_ON_BR_E0 0x01 +#define BT_HCI_ENCRYPTION_ON_BR_AES_CCM 0x02 + +#define BT_HCI_EVT_ENCRYPT_CHANGE 0x08 +struct bt_hci_evt_encrypt_change +{ + uint8_t status; + uint16_t handle; + uint8_t encrypt; +} __packed; + +#define BT_HCI_EVT_REMOTE_FEATURES 0x0b +struct bt_hci_evt_remote_features +{ + uint8_t status; + uint16_t handle; + uint8_t features[8]; +} __packed; + +#define BT_HCI_EVT_REMOTE_VERSION_INFO 0x0c +struct bt_hci_evt_remote_version_info +{ + uint8_t status; + uint16_t handle; + uint8_t version; + uint16_t manufacturer; + uint16_t subversion; +} __packed; + +#define BT_HCI_EVT_CMD_COMPLETE 0x0e +struct bt_hci_evt_cmd_complete +{ + uint8_t ncmd; + uint16_t opcode; +} __packed; + +struct bt_hci_evt_cc_status +{ + uint8_t status; +} __packed; + +#define BT_HCI_EVT_CMD_STATUS 0x0f +struct bt_hci_evt_cmd_status +{ + uint8_t status; + uint8_t ncmd; + uint16_t opcode; +} __packed; + +#define BT_HCI_EVT_HARDWARE_ERROR 0x10 +struct bt_hci_evt_hardware_error +{ + uint8_t hardware_code; +} __packed; + +#define BT_HCI_EVT_ROLE_CHANGE 0x12 +struct bt_hci_evt_role_change +{ + uint8_t status; + bt_addr_t bdaddr; + uint8_t role; +} __packed; + +#define BT_HCI_EVT_NUM_COMPLETED_PACKETS 0x13 +struct bt_hci_evt_num_completed_packets +{ + uint8_t num_handles; + struct bt_hci_handle_count h[0]; +} __packed; + +/* Current mode */ +#define BT_ACTIVE_MODE 0x00 +#define BT_HOLD_MODE 0x01 +#define BT_SNIFF_MODE 0x02 + +#define BT_HCI_EVT_MODE_CHANGE 0x14 +struct bt_hci_evt_mode_change +{ + uint8_t status; + uint16_t handle; + uint8_t mode; + uint16_t interval; +} __packed; + +#define BT_HCI_EVT_PIN_CODE_REQ 0x16 +struct bt_hci_evt_pin_code_req +{ + bt_addr_t bdaddr; +} __packed; + +#define BT_HCI_EVT_LINK_KEY_REQ 0x17 +struct bt_hci_evt_link_key_req +{ + bt_addr_t bdaddr; +} __packed; + +/* Link Key types */ +#define BT_LK_COMBINATION 0x00 +#define BT_LK_LOCAL_UNIT 0x01 +#define BT_LK_REMOTE_UNIT 0x02 +#define BT_LK_DEBUG_COMBINATION 0x03 +#define BT_LK_UNAUTH_COMBINATION_P192 0x04 +#define BT_LK_AUTH_COMBINATION_P192 0x05 +#define BT_LK_CHANGED_COMBINATION 0x06 +#define BT_LK_UNAUTH_COMBINATION_P256 0x07 +#define BT_LK_AUTH_COMBINATION_P256 0x08 + +#define BT_HCI_EVT_LINK_KEY_NOTIFY 0x18 +struct bt_hci_evt_link_key_notify +{ + bt_addr_t bdaddr; + uint8_t link_key[16]; + uint8_t key_type; +} __packed; + +/* Overflow link types */ +#define BT_OVERFLOW_LINK_SYNCH 0x00 +#define BT_OVERFLOW_LINK_ACL 0x01 +#define BT_OVERFLOW_LINK_ISO 0x02 + +#define BT_HCI_EVT_DATA_BUF_OVERFLOW 0x1a +struct bt_hci_evt_data_buf_overflow +{ + uint8_t link_type; +} __packed; + +/** HCI Connection Packet Type Changed event. */ +#define BT_HCI_EVT_CONN_PKT_TYPE_CHANGED 0x1d +/** HCI Connection Packet Type Changed event parameters. */ + +struct bt_hci_evt_conn_pkt_type_changed +{ + /** HCI status. */ + + uint8_t status; + + /** Connection handle. */ + + uint16_t handle; + + /** Packet type bitmask. */ + + uint16_t packet_type; +} __packed; + +#define BT_HCI_EVT_INQUIRY_RESULT_WITH_RSSI 0x22 +struct bt_hci_evt_inquiry_result_with_rssi +{ + bt_addr_t addr; + uint8_t pscan_rep_mode; + uint8_t reserved; + uint8_t cod[3]; + uint16_t clock_offset; + int8_t rssi; +} __packed; + +#define BT_HCI_EVT_REMOTE_EXT_FEATURES 0x23 +struct bt_hci_evt_remote_ext_features +{ + uint8_t status; + uint16_t handle; + uint8_t page; + uint8_t max_page; + uint8_t features[8]; +} __packed; + +#define BT_HCI_EVT_LE_PER_ADV_SYNC_ESTABLISHED_V2 0x24 +struct bt_hci_evt_le_per_adv_sync_established_v2 +{ + uint8_t status; + uint16_t handle; + uint8_t sid; + bt_addr_le_t adv_addr; + uint8_t phy; + uint16_t interval; + uint8_t clock_accuracy; + uint8_t num_subevents; + uint8_t subevent_interval; + uint8_t response_slot_delay; + uint8_t response_slot_spacing; +} __packed; + +#define BT_HCI_EVT_LE_PER_ADVERTISING_REPORT_V2 0x25 +struct bt_hci_evt_le_per_advertising_report_v2 +{ + uint16_t handle; + int8_t tx_power; + int8_t rssi; + uint8_t cte_type; + uint16_t periodic_event_counter; + uint8_t subevent; + uint8_t data_status; + uint8_t length; + uint8_t data[0]; +} __packed; + +#define BT_HCI_EVT_LE_PAST_RECEIVED_V2 0x26 +struct bt_hci_evt_le_past_received_v2 +{ + uint8_t status; + uint16_t conn_handle; + uint16_t service_data; + uint16_t sync_handle; + uint8_t adv_sid; + bt_addr_le_t addr; + uint8_t phy; + uint16_t interval; + uint8_t clock_accuracy; + uint8_t num_subevents; + uint8_t subevent_interval; + uint8_t response_slot_delay; + uint8_t response_slot_spacing; +} __packed; + +#define BT_HCI_EVT_LE_PER_ADV_SUBEVENT_DATA_REQUEST 0x27 +struct bt_hci_evt_le_per_adv_subevent_data_request +{ + uint8_t adv_handle; + uint8_t subevent_start; + uint8_t subevent_data_count; +} __packed; + +#define BT_HCI_EVT_LE_PER_ADV_RESPONSE_REPORT 0x28 + +struct bt_hci_evt_le_per_adv_response +{ + int8_t tx_power; + int8_t rssi; + uint8_t cte_type; + uint8_t response_slot; + uint8_t data_status; + uint8_t data_length; + uint8_t data[0]; +} __packed; + +struct bt_hci_evt_le_per_adv_response_report +{ + uint8_t adv_handle; + uint8_t subevent; + uint8_t tx_status; + uint8_t num_responses; + struct bt_hci_evt_le_per_adv_response responses[0]; +} __packed; + +#define BT_HCI_EVT_LE_ENH_CONN_COMPLETE_V2 0x29 +struct bt_hci_evt_le_enh_conn_complete_v2 +{ + uint8_t status; + uint16_t handle; + uint8_t role; + bt_addr_le_t peer_addr; + bt_addr_t local_rpa; + bt_addr_t peer_rpa; + uint16_t interval; + uint16_t latency; + uint16_t supv_timeout; + uint8_t clock_accuracy; + uint8_t adv_handle; + uint16_t sync_handle; +} __packed; + +#define BT_HCI_EVT_SYNC_CONN_COMPLETE 0x2c +struct bt_hci_evt_sync_conn_complete +{ + uint8_t status; + uint16_t handle; + bt_addr_t bdaddr; + uint8_t link_type; + uint8_t tx_interval; + uint8_t retansmission_window; + uint16_t rx_pkt_length; + uint16_t tx_pkt_length; + uint8_t air_mode; +} __packed; + +#define BT_HCI_EVT_EXTENDED_INQUIRY_RESULT 0x2f +struct bt_hci_evt_extended_inquiry_result +{ + uint8_t num_reports; + bt_addr_t addr; + uint8_t pscan_rep_mode; + uint8_t reserved; + uint8_t cod[3]; + uint16_t clock_offset; + int8_t rssi; + uint8_t eir[240]; +} __packed; + +#define BT_HCI_EVT_ENCRYPT_KEY_REFRESH_COMPLETE 0x30 +struct bt_hci_evt_encrypt_key_refresh_complete +{ + uint8_t status; + uint16_t handle; +} __packed; + +#define BT_HCI_EVT_IO_CAPA_REQ 0x31 +struct bt_hci_evt_io_capa_req +{ + bt_addr_t bdaddr; +} __packed; + +#define BT_HCI_EVT_IO_CAPA_RESP 0x32 +struct bt_hci_evt_io_capa_resp +{ + bt_addr_t bdaddr; + uint8_t capability; + uint8_t oob_data; + uint8_t authentication; +} __packed; + +#define BT_HCI_EVT_USER_CONFIRM_REQ 0x33 +struct bt_hci_evt_user_confirm_req +{ + bt_addr_t bdaddr; + uint32_t passkey; +} __packed; + +#define BT_HCI_EVT_USER_PASSKEY_REQ 0x34 +struct bt_hci_evt_user_passkey_req +{ + bt_addr_t bdaddr; +} __packed; + +#define BT_HCI_EVT_SSP_COMPLETE 0x36 +struct bt_hci_evt_ssp_complete +{ + uint8_t status; + bt_addr_t bdaddr; +} __packed; + +#define BT_HCI_EVT_USER_PASSKEY_NOTIFY 0x3b +struct bt_hci_evt_user_passkey_notify +{ + bt_addr_t bdaddr; + uint32_t passkey; +} __packed; + +#define BT_HCI_EVT_LE_META_EVENT 0x3e +struct bt_hci_evt_le_meta_event +{ + uint8_t subevent; + uint8_t data[]; +} __packed; + +#define BT_HCI_EVT_AUTH_PAYLOAD_TIMEOUT_EXP 0x57 +struct bt_hci_evt_auth_payload_timeout_exp +{ + uint16_t handle; +} __packed; + +#define BT_HCI_ROLE_CENTRAL 0x00 +#define BT_HCI_ROLE_PERIPHERAL 0x01 + +#define BT_HCI_EVT_LE_CONN_COMPLETE 0x01 +struct bt_hci_evt_le_conn_complete +{ + uint8_t status; + uint16_t handle; + uint8_t role; + bt_addr_le_t peer_addr; + uint16_t interval; + uint16_t latency; + uint16_t supv_timeout; + uint8_t clock_accuracy; +} __packed; + +#define BT_HCI_LE_RSSI_NOT_AVAILABLE 0x7F + +#define BT_HCI_EVT_LE_ADVERTISING_REPORT 0x02 +struct bt_hci_evt_le_advertising_info +{ + uint8_t evt_type; + bt_addr_le_t addr; + uint8_t length; + uint8_t data[0]; +} __packed; +struct bt_hci_evt_le_advertising_report +{ + uint8_t num_reports; + struct bt_hci_evt_le_advertising_info adv_info[0]; +} __packed; + +/** All limits according to BT Core Spec v5.4 [Vol 4, Part E]. */ +#define BT_HCI_LE_INTERVAL_MIN 0x0006 +#define BT_HCI_LE_INTERVAL_MAX 0x0c80 +#define BT_HCI_LE_PERIPHERAL_LATENCY_MIN (0x0000U) +#define BT_HCI_LE_PERIPHERAL_LATENCY_MAX 0x01f3 +/** Minimum LE supervision timeout (10 ms) */ +#define BT_HCI_LE_SUPERVISION_TIMEOUT_MIN 0x000a +/** Maximum LE supervision timeout (3200 ms) */ +#define BT_HCI_LE_SUPERVISION_TIMEOUT_MAX 0x0c80 +/* Deprecated misspelled aliases: use BT_HCI_LE_SUPERVISION_TIMEOUT_{MIN,MAX} + * instead. + */ +#define BT_HCI_LE_SUPERVISON_TIMEOUT_MIN BT_HCI_LE_SUPERVISION_TIMEOUT_MIN __DEPRECATED_MACRO +#define BT_HCI_LE_SUPERVISON_TIMEOUT_MAX BT_HCI_LE_SUPERVISION_TIMEOUT_MAX __DEPRECATED_MACRO + +#define BT_HCI_LE_INTERVAL_UNIT_US (1250U) + +#define BT_HCI_EVT_LE_CONN_UPDATE_COMPLETE 0x03 +struct bt_hci_evt_le_conn_update_complete +{ + uint8_t status; + uint16_t handle; + uint16_t interval; + uint16_t latency; + uint16_t supv_timeout; +} __packed; + +#define BT_HCI_EVT_LE_REMOTE_FEAT_COMPLETE 0x04 +struct bt_hci_evt_le_remote_feat_complete +{ + uint8_t status; + uint16_t handle; + uint8_t features[8]; +} __packed; + +#define BT_HCI_EVT_LE_LTK_REQUEST 0x05 +struct bt_hci_evt_le_ltk_request +{ + uint16_t handle; + + /** 64-bit random number used to identify the LTK. */ + + uint8_t rand[8]; + + /** 16-bit encrypted diversifier used to identify the LTK. */ + + uint8_t ediv[2]; +} __packed; + +#define BT_HCI_EVT_LE_CONN_PARAM_REQ 0x06 +struct bt_hci_evt_le_conn_param_req +{ + uint16_t handle; + uint16_t interval_min; + uint16_t interval_max; + uint16_t latency; + uint16_t timeout; +} __packed; + +#define BT_HCI_EVT_LE_DATA_LEN_CHANGE 0x07 +struct bt_hci_evt_le_data_len_change +{ + uint16_t handle; + uint16_t max_tx_octets; + uint16_t max_tx_time; + uint16_t max_rx_octets; + uint16_t max_rx_time; +} __packed; + +#define BT_HCI_EVT_LE_P256_PUBLIC_KEY_COMPLETE 0x08 +struct bt_hci_evt_le_p256_public_key_complete +{ + uint8_t status; + uint8_t key[64]; +} __packed; + +#define BT_HCI_EVT_LE_GENERATE_DHKEY_COMPLETE 0x09 +struct bt_hci_evt_le_generate_dhkey_complete +{ + uint8_t status; + uint8_t dhkey[32]; +} __packed; + +#define BT_HCI_EVT_LE_ENH_CONN_COMPLETE 0x0a +struct bt_hci_evt_le_enh_conn_complete +{ + uint8_t status; + uint16_t handle; + uint8_t role; + bt_addr_le_t peer_addr; + bt_addr_t local_rpa; + bt_addr_t peer_rpa; + uint16_t interval; + uint16_t latency; + uint16_t supv_timeout; + uint8_t clock_accuracy; +} __packed; + +#define BT_HCI_EVT_LE_DIRECT_ADV_REPORT 0x0b +struct bt_hci_evt_le_direct_adv_info +{ + uint8_t evt_type; + bt_addr_le_t addr; + bt_addr_le_t dir_addr; + int8_t rssi; +} __packed; +struct bt_hci_evt_le_direct_adv_report +{ + uint8_t num_reports; + struct bt_hci_evt_le_direct_adv_info direct_adv_info[0]; +} __packed; + +#define BT_HCI_EVT_LE_PHY_UPDATE_COMPLETE 0x0c +struct bt_hci_evt_le_phy_update_complete +{ + uint8_t status; + uint16_t handle; + uint8_t tx_phy; + uint8_t rx_phy; +} __packed; + +#define BT_HCI_EVT_LE_EXT_ADVERTISING_REPORT 0x0d + +#define BT_HCI_LE_ADV_EVT_TYPE_CONN BIT(0) +#define BT_HCI_LE_ADV_EVT_TYPE_SCAN BIT(1) +#define BT_HCI_LE_ADV_EVT_TYPE_DIRECT BIT(2) +#define BT_HCI_LE_ADV_EVT_TYPE_SCAN_RSP BIT(3) +#define BT_HCI_LE_ADV_EVT_TYPE_LEGACY BIT(4) + +#define BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS(ev_type) (((ev_type) >> 5) & 0x03) +#define BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS_COMPLETE 0 +#define BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS_PARTIAL 1 +#define BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS_INCOMPLETE 2 +#define BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS_RX_FAILED 0xFF + +/* Advertising Coding Selection extended advertising report PHY values. + * Only used when Kconfig BT_EXT_ADV_CODING_SELECTION is enabled. + */ +#define BT_HCI_LE_ADV_EVT_PHY_1M 0x01 +#define BT_HCI_LE_ADV_EVT_PHY_2M 0x02 +#define BT_HCI_LE_ADV_EVT_PHY_CODED_S8 0x03 +#define BT_HCI_LE_ADV_EVT_PHY_CODED_S2 0x04 + +struct bt_hci_evt_le_ext_advertising_info +{ + uint16_t evt_type; + bt_addr_le_t addr; + uint8_t prim_phy; + uint8_t sec_phy; + uint8_t sid; + int8_t tx_power; + int8_t rssi; + uint16_t interval; + bt_addr_le_t direct_addr; + uint8_t length; + uint8_t data[0]; +} __packed; + +/** Maximum number of reports in an LE Extended Advertising Report. */ +#define BT_HCI_LE_EXT_ADV_REPORT_MAX_NUM_REPORTS 0x0a +struct bt_hci_evt_le_ext_advertising_report +{ + uint8_t num_reports; + struct bt_hci_evt_le_ext_advertising_info adv_info[0]; +} __packed; + +#define BT_HCI_EVT_LE_PER_ADV_SYNC_ESTABLISHED 0x0e +struct bt_hci_evt_le_per_adv_sync_established +{ + uint8_t status; + uint16_t handle; + uint8_t sid; + bt_addr_le_t adv_addr; + uint8_t phy; + uint16_t interval; + uint8_t clock_accuracy; +} __packed; + +#define BT_HCI_EVT_LE_PER_ADVERTISING_REPORT 0x0f +struct bt_hci_evt_le_per_advertising_report +{ + uint16_t handle; + int8_t tx_power; + int8_t rssi; + uint8_t cte_type; + uint8_t data_status; + uint8_t length; + uint8_t data[0]; +} __packed; + +#define BT_HCI_EVT_LE_PER_ADV_SYNC_LOST 0x10 +struct bt_hci_evt_le_per_adv_sync_lost +{ + uint16_t handle; +} __packed; + +#define BT_HCI_EVT_LE_SCAN_TIMEOUT 0x11 + +#define BT_HCI_EVT_LE_ADV_SET_TERMINATED 0x12 +struct bt_hci_evt_le_adv_set_terminated +{ + uint8_t status; + uint8_t adv_handle; + uint16_t conn_handle; + uint8_t num_completed_ext_adv_evts; +} __packed; + +#define BT_HCI_EVT_LE_SCAN_REQ_RECEIVED 0x13 +struct bt_hci_evt_le_scan_req_received +{ + uint8_t handle; + bt_addr_le_t addr; +} __packed; + +#define BT_HCI_LE_CHAN_SEL_ALGO_1 0x00 +#define BT_HCI_LE_CHAN_SEL_ALGO_2 0x01 + +#define BT_HCI_EVT_LE_CHAN_SEL_ALGO 0x14 +struct bt_hci_evt_le_chan_sel_algo +{ + uint16_t handle; + uint8_t chan_sel_algo; +} __packed; + +#define BT_HCI_LE_CTE_CRC_OK 0x0 +#define BT_HCI_LE_CTE_CRC_ERR_CTE_BASED_TIME 0x1 +#define BT_HCI_LE_CTE_CRC_ERR_CTE_BASED_OTHER 0x2 +#define BT_HCI_LE_CTE_INSUFFICIENT_RESOURCES 0xFF + +#define B_HCI_LE_CTE_REPORT_SAMPLE_COUNT_MIN 0x9 +#define B_HCI_LE_CTE_REPORT_SAMPLE_COUNT_MAX 0x52 + +#define BT_HCI_LE_CTE_REPORT_NO_VALID_SAMPLE 0x80 + +#define BT_HCI_EVT_LE_CONNECTIONLESS_IQ_REPORT 0x15 +struct bt_hci_le_iq_sample +{ + int8_t i; + int8_t q; +}; + +struct bt_hci_evt_le_connectionless_iq_report +{ + uint16_t sync_handle; + uint8_t chan_idx; + int16_t rssi; + uint8_t rssi_ant_id; + uint8_t cte_type; + uint8_t slot_durations; + uint8_t packet_status; + uint16_t per_evt_counter; + uint8_t sample_count; + struct bt_hci_le_iq_sample sample[0]; +} __packed; + +#define BT_HCI_EVT_LE_CONNECTION_IQ_REPORT 0x16 +struct bt_hci_evt_le_connection_iq_report +{ + uint16_t conn_handle; + uint8_t rx_phy; + uint8_t data_chan_idx; + int16_t rssi; + uint8_t rssi_ant_id; + uint8_t cte_type; + uint8_t slot_durations; + uint8_t packet_status; + uint16_t conn_evt_counter; + uint8_t sample_count; + struct bt_hci_le_iq_sample sample[0]; +} __packed; + +#define BT_HCI_CTE_REQ_STATUS_RSP_WITHOUT_CTE 0x0 + +#define BT_HCI_EVT_LE_CTE_REQUEST_FAILED 0x17 +struct bt_hci_evt_le_cte_req_failed +{ + /* According to BT 5.3 Core Spec the status field may have following + * values: + * - BT_HCI_CTE_REQ_STATUS_RSP_WITHOUT_CTE when received LL_CTE_RSP_PDU + * without CTE. + * - Other Controller error code for peer rejected request. + */ + + uint8_t status; + uint16_t conn_handle; +} __packed; + +#define BT_HCI_EVT_LE_PAST_RECEIVED 0x18 +struct bt_hci_evt_le_past_received +{ + uint8_t status; + uint16_t conn_handle; + uint16_t service_data; + uint16_t sync_handle; + uint8_t adv_sid; + bt_addr_le_t addr; + uint8_t phy; + uint16_t interval; + uint8_t clock_accuracy; +} __packed; + +/** Minimum sync delay for CIGs as defined by 7.7.65.25 LE CIS Established + * event + */ +#define BT_HCI_LE_CIG_SYNC_DELAY_MIN 0x0000F2U +/** Maximum sync delay for CIGs as defined by 7.7.65.25 LE CIS Established + * event + */ +#define BT_HCI_LE_CIG_SYNC_DELAY_MAX 0x7FFFFFU +/** Minimum sync delay for CISs as defined by 7.7.65.25 LE CIS Established + * event + */ +#define BT_HCI_LE_CIS_SYNC_DELAY_MIN 0x0000F2U +/** Maximum sync delay for CISs as defined by 7.7.65.25 LE CIS Established + * event + */ +#define BT_HCI_LE_CIS_SYNC_DELAY_MAX 0x7FFFFFU +/** Minimum transport latency central to peripheral as defined by 7.7.65.25 + * LE CIS Established event + */ +#define BT_HCI_LE_TRANSPORT_LATENCY_C_TO_P_MIN 0x0000F2U +/** Maximum transport latency central to peripheral as defined by 7.7.65.25 + * LE CIS Established event + */ +#define BT_HCI_LE_TRANSPORT_LATENCY_C_TO_P_MAX 0x7FFFFFU +/** Minimum transport latency peripheral to central as defined by 7.7.65.25 + * LE CIS Established event + */ +#define BT_HCI_LE_TRANSPORT_LATENCY_P_TO_C_MIN 0x0000F2U +/** Maximum transport latency peripheral to central as defined by 7.7.65.25 + * LE CIS Established event + */ +#define BT_HCI_LE_TRANSPORT_LATENCY_P_TO_C_MAX 0x7FFFFFU + +#define BT_HCI_EVT_LE_CIS_ESTABLISHED 0x19 +struct bt_hci_evt_le_cis_established +{ + uint8_t status; + uint16_t conn_handle; + uint8_t cig_sync_delay[3]; + uint8_t cis_sync_delay[3]; + uint8_t c_latency[3]; + uint8_t p_latency[3]; + uint8_t c_phy; + uint8_t p_phy; + uint8_t nse; + uint8_t c_bn; + uint8_t p_bn; + uint8_t c_ft; + uint8_t p_ft; + uint16_t c_max_pdu; + uint16_t p_max_pdu; + uint16_t interval; +} __packed; + +#define BT_HCI_EVT_LE_CIS_REQ 0x1a +struct bt_hci_evt_le_cis_req +{ + uint16_t acl_handle; + uint16_t cis_handle; + uint8_t cig_id; + uint8_t cis_id; +} __packed; + +#define BT_HCI_LE_BIG_HANDLE_MIN 0x00U +#define BT_HCI_LE_BIG_HANDLE_MAX 0xEFU +#define BT_HCI_LE_BIG_SYNC_DELAY_MIN 0x000030U +#define BT_HCI_LE_BIG_SYNC_DELAY_MAX 0x7FFFFFU +#define BT_HCI_LE_TRANSPORT_LATENCY_BIG_MIN 0x000030U +#define BT_HCI_LE_TRANSPORT_LATENCY_BIG_MAX 0x7FFFFFU + +#define BT_HCI_EVT_LE_BIG_COMPLETE 0x1b +struct bt_hci_evt_le_big_complete +{ + uint8_t status; + uint8_t big_handle; + uint8_t sync_delay[3]; + uint8_t latency[3]; + uint8_t phy; + uint8_t nse; + uint8_t bn; + uint8_t pto; + uint8_t irc; + uint16_t max_pdu; + uint16_t iso_interval; + uint8_t num_bis; + uint16_t handle[0]; +} __packed; + +#define BT_HCI_EVT_LE_BIG_TERMINATE 0x1c +struct bt_hci_evt_le_big_terminate +{ + uint8_t big_handle; + uint8_t reason; +} __packed; + +#define BT_HCI_EVT_LE_BIG_SYNC_ESTABLISHED 0x1d +struct bt_hci_evt_le_big_sync_established +{ + uint8_t status; + uint8_t big_handle; + uint8_t latency[3]; + uint8_t nse; + uint8_t bn; + uint8_t pto; + uint8_t irc; + uint16_t max_pdu; + uint16_t iso_interval; + uint8_t num_bis; + uint16_t handle[0]; +} __packed; + +#define BT_HCI_EVT_LE_BIG_SYNC_LOST 0x1e +struct bt_hci_evt_le_big_sync_lost +{ + uint8_t big_handle; + uint8_t reason; +} __packed; + +#define BT_HCI_EVT_LE_REQ_PEER_SCA_COMPLETE 0x1f +struct bt_hci_evt_le_req_peer_sca_complete +{ + uint8_t status; + uint16_t handle; + uint8_t sca; +} __packed; + +#define BT_HCI_LE_ZONE_ENTERED_LOW 0x0 +#define BT_HCI_LE_ZONE_ENTERED_MIDDLE 0x1 +#define BT_HCI_LE_ZONE_ENTERED_HIGH 0x2 +#define BT_HCI_LE_PATH_LOSS_UNAVAILABLE 0xFF + +#define BT_HCI_EVT_LE_PATH_LOSS_THRESHOLD 0x20 +struct bt_hci_evt_le_path_loss_threshold +{ + uint16_t handle; + uint8_t current_path_loss; + uint8_t zone_entered; +} __packed; + +/** Reason for Transmit power reporting. + */ + +/* Local Transmit power changed. */ +#define BT_HCI_LE_TX_POWER_REPORT_REASON_LOCAL_CHANGED 0x00 +/* Remote Transmit power changed. */ +#define BT_HCI_LE_TX_POWER_REPORT_REASON_REMOTE_CHANGED 0x01 +/* HCI_LE_Read_Remote_Transmit_Power_Level command completed. */ +#define BT_HCI_LE_TX_POWER_REPORT_REASON_READ_REMOTE_COMPLETED 0x02 + +#define BT_HCI_EVT_LE_TRANSMIT_POWER_REPORT 0x21 +struct bt_hci_evt_le_transmit_power_report +{ + uint8_t status; + uint16_t handle; + uint8_t reason; + uint8_t phy; + int8_t tx_power_level; + uint8_t tx_power_level_flag; + int8_t delta; +} __packed; + +#define BT_HCI_EVT_LE_BIGINFO_ADV_REPORT 0x22 +struct bt_hci_evt_le_biginfo_adv_report +{ + uint16_t sync_handle; + uint8_t num_bis; + uint8_t nse; + uint16_t iso_interval; + uint8_t bn; + uint8_t pto; + uint8_t irc; + uint16_t max_pdu; + uint8_t sdu_interval[3]; + uint16_t max_sdu; + uint8_t phy; + uint8_t framing; + uint8_t encryption; +} __packed; + +/** All limits according to BT Core Spec v5.4 [Vol 4, Part E]. */ +#define BT_HCI_LE_SUBRATE_FACTOR_MIN 0x0001 +#define BT_HCI_LE_SUBRATE_FACTOR_MAX 0x01f4 +#define BT_HCI_LE_CONTINUATION_NUM_MIN (0x0000U) +#define BT_HCI_LE_CONTINUATION_NUM_MAX 0x01f3 + +#define BT_HCI_EVT_LE_SUBRATE_CHANGE 0x23 +struct bt_hci_evt_le_subrate_change +{ + uint8_t status; + uint16_t handle; + uint16_t subrate_factor; + uint16_t peripheral_latency; + uint16_t continuation_number; + uint16_t supervision_timeout; +} __packed; + +#define BT_HCI_EVT_LE_CIS_ESTABLISHED_V2 0x2a +struct bt_hci_evt_le_cis_established_v2 +{ + uint8_t status; + uint16_t conn_handle; + uint8_t cig_sync_delay[3]; + uint8_t cis_sync_delay[3]; + uint8_t c_latency[3]; + uint8_t p_latency[3]; + uint8_t c_phy; + uint8_t p_phy; + uint8_t nse; + uint8_t c_bn; + uint8_t p_bn; + uint8_t c_ft; + uint8_t p_ft; + uint16_t c_max_pdu; + uint16_t p_max_pdu; + uint16_t interval; + uint8_t sub_interval[3]; + uint16_t c_max_sdu; + uint16_t p_max_sdu; + uint8_t c_sdu_interval[3]; + uint8_t p_sdu_interval[3]; + uint8_t framing; +} __packed; + +#define BT_HCI_EVT_LE_READ_ALL_REMOTE_FEAT_COMPLETE 0x2b + +#define BT_HCI_LE_FEATURE_PAGE_MAX 10 +#define BT_HCI_LE_BYTES_PER_FEATURE_PAGE 24 +#define BT_HCI_LE_BYTES_PAGE_0_FEATURE_PAGE 8 + +struct bt_hci_evt_le_read_all_remote_feat_complete +{ + uint8_t status; + uint16_t handle; + uint8_t max_remote_page; + uint8_t max_valid_page; + uint8_t features[248]; +} __packed; + +#define BT_HCI_LE_CS_INITIATOR_ROLE_MASK BIT(0) +#define BT_HCI_LE_CS_REFLECTOR_ROLE_MASK BIT(1) + +#define BT_HCI_LE_CS_MODES_SUPPORTED_MODE_3_MASK BIT(0) + +#define BT_HCI_LE_CS_RTT_AA_ONLY_N_10NS_MASK BIT(0) +#define BT_HCI_LE_CS_RTT_SOUNDING_N_10NS_MASK BIT(1) +#define BT_HCI_LE_CS_RTT_RANDOM_PAYLOAD_N_10NS_MASK BIT(2) + +#define BT_HCI_LE_CS_NADM_SOUNDING_CAPABILITY_PHASE_BASED_MASK BIT(0) +#define BT_HCI_LE_CS_NADM_RANDOM_CAPABILITY_PHASE_BASED_MASK BIT(0) + +#define BT_HCI_LE_CS_SYNC_PHYS_2M_MASK BIT(1) +#define BT_HCI_LE_CS_SYNC_PHYS_2M_2BT_MASK BIT(2) + +#define BT_HCI_LE_CS_SUBFEATURE_NO_TX_FAE_MASK BIT(1) +#define BT_HCI_LE_CS_SUBFEATURE_CHSEL_ALG_3C_MASK BIT(2) +#define BT_HCI_LE_CS_SUBFEATURE_PBR_FROM_RTT_SOUNDING_SEQ_MASK BIT(3) +/** Subfeature: CS with IPT in the reflector. */ +#define BT_HCI_LE_CS_SUBFEATURE_CS_IPT_REFLECTOR_MASK BIT(4) + +#define BT_HCI_LE_CS_T_IP1_TIME_10US_MASK BIT(0) +#define BT_HCI_LE_CS_T_IP1_TIME_20US_MASK BIT(1) +#define BT_HCI_LE_CS_T_IP1_TIME_30US_MASK BIT(2) +#define BT_HCI_LE_CS_T_IP1_TIME_40US_MASK BIT(3) +#define BT_HCI_LE_CS_T_IP1_TIME_50US_MASK BIT(4) +#define BT_HCI_LE_CS_T_IP1_TIME_60US_MASK BIT(5) +#define BT_HCI_LE_CS_T_IP1_TIME_80US_MASK BIT(6) + +#define BT_HCI_LE_CS_T_IP2_TIME_10US_MASK BIT(0) +#define BT_HCI_LE_CS_T_IP2_TIME_20US_MASK BIT(1) +#define BT_HCI_LE_CS_T_IP2_TIME_30US_MASK BIT(2) +#define BT_HCI_LE_CS_T_IP2_TIME_40US_MASK BIT(3) +#define BT_HCI_LE_CS_T_IP2_TIME_50US_MASK BIT(4) +#define BT_HCI_LE_CS_T_IP2_TIME_60US_MASK BIT(5) +#define BT_HCI_LE_CS_T_IP2_TIME_80US_MASK BIT(6) + +#define BT_HCI_LE_CS_T_FCS_TIME_15US_MASK BIT(0) +#define BT_HCI_LE_CS_T_FCS_TIME_20US_MASK BIT(1) +#define BT_HCI_LE_CS_T_FCS_TIME_30US_MASK BIT(2) +#define BT_HCI_LE_CS_T_FCS_TIME_40US_MASK BIT(3) +#define BT_HCI_LE_CS_T_FCS_TIME_50US_MASK BIT(4) +#define BT_HCI_LE_CS_T_FCS_TIME_60US_MASK BIT(5) +#define BT_HCI_LE_CS_T_FCS_TIME_80US_MASK BIT(6) +#define BT_HCI_LE_CS_T_FCS_TIME_100US_MASK BIT(7) +#define BT_HCI_LE_CS_T_FCS_TIME_1200US_MASK BIT(8) + +#define BT_HCI_LE_CS_T_PM_TIME_10US_MASK BIT(0) +#define BT_HCI_LE_CS_T_PM_TIME_20US_MASK BIT(1) + +#define BT_HCI_LE_CS_TX_SNR_CAPABILITY_18DB_MASK BIT(0) +#define BT_HCI_LE_CS_TX_SNR_CAPABILITY_21DB_MASK BIT(1) +#define BT_HCI_LE_CS_TX_SNR_CAPABILITY_24DB_MASK BIT(2) +#define BT_HCI_LE_CS_TX_SNR_CAPABILITY_27DB_MASK BIT(3) +#define BT_HCI_LE_CS_TX_SNR_CAPABILITY_30DB_MASK BIT(4) + +#define BT_HCI_EVT_LE_CS_READ_REMOTE_SUPPORTED_CAPABILITIES_COMPLETE 0x2C + +struct bt_hci_evt_le_cs_read_remote_supported_capabilities_complete +{ + uint8_t status; + uint16_t conn_handle; + uint8_t num_config_supported; + uint16_t max_consecutive_procedures_supported; + uint8_t num_antennas_supported; + uint8_t max_antenna_paths_supported; + uint8_t roles_supported; + uint8_t modes_supported; + uint8_t rtt_capability; + uint8_t rtt_aa_only_n; + uint8_t rtt_sounding_n; + uint8_t rtt_random_payload_n; + uint16_t nadm_sounding_capability; + uint16_t nadm_random_capability; + uint8_t cs_sync_phys_supported; + uint16_t subfeatures_supported; + uint16_t t_ip1_times_supported; + uint16_t t_ip2_times_supported; + uint16_t t_fcs_times_supported; + uint16_t t_pm_times_supported; + uint8_t t_sw_time_supported; + uint8_t tx_snr_capability; +} __packed; + +/** HCI LE CS Read Remote Supported Capabilities Complete event (v2). */ +#define BT_HCI_EVT_LE_CS_READ_REMOTE_SUPPORTED_CAPABILITIES_COMPLETE_V2 0x38 + +/** HCI event parameters for LE CS Read Remote Supported Capabilities + * Complete (v2). + */ + +struct bt_hci_evt_le_cs_read_remote_supported_capabilities_complete_v2 +{ + /** Status. */ + + uint8_t status; + + /** Connection handle. */ + + uint16_t conn_handle; + + /** Number of CS configurations supported. */ + + uint8_t num_config_supported; + + /** Maximum consecutive procedures supported. */ + + uint16_t max_consecutive_procedures_supported; + + /** Number of antennas supported. */ + + uint8_t num_antennas_supported; + + /** Maximum antenna paths supported. */ + + uint8_t max_antenna_paths_supported; + + /** Roles supported. */ + + uint8_t roles_supported; + + /** Modes supported. */ + + uint8_t modes_supported; + + /** RTT capability. */ + + uint8_t rtt_capability; + + /** RTT AA-only N. */ + + uint8_t rtt_aa_only_n; + + /** RTT sounding N. */ + + uint8_t rtt_sounding_n; + + /** RTT random payload N. */ + + uint8_t rtt_random_payload_n; + + /** NADM sounding capability. */ + + uint16_t nadm_sounding_capability; + + /** NADM random capability. */ + + uint16_t nadm_random_capability; + + /** CS sync PHYs supported. */ + + uint8_t cs_sync_phys_supported; + + /** Subfeatures supported. */ + + uint16_t subfeatures_supported; + + /** T_IP1 times supported. */ + + uint16_t t_ip1_times_supported; + + /** T_IP2 times supported. */ + + uint16_t t_ip2_times_supported; + + /** T_FCS times supported. */ + + uint16_t t_fcs_times_supported; + + /** T_PM times supported. */ + + uint16_t t_pm_times_supported; + + /** T_SW time supported. */ + + uint8_t t_sw_time_supported; + + /** TX SNR capability. */ + + uint8_t tx_snr_capability; + + /** T_IP2 IPT times supported. */ + + uint16_t t_ip2_ipt_times_supported; + + /** T_SW IPT time supported. */ + + uint8_t t_sw_ipt_time_supported; +} __packed; + +#define BT_HCI_EVT_LE_CS_READ_REMOTE_FAE_TABLE_COMPLETE 0x2D +struct bt_hci_evt_le_cs_read_remote_fae_table_complete +{ + uint8_t status; + uint16_t conn_handle; + int8_t remote_fae_table[72]; +} __packed; + +#define BT_HCI_LE_CS_CONFIG_ACTION_REMOVED 0x00 +#define BT_HCI_LE_CS_CONFIG_ACTION_CREATED 0x01 + +#define BT_HCI_EVT_LE_CS_SECURITY_ENABLE_COMPLETE 0x2E +struct bt_hci_evt_le_cs_security_enable_complete +{ + uint8_t status; + uint16_t handle; +} __packed; + +#define BT_HCI_EVT_LE_CS_CONFIG_COMPLETE 0x2F +struct bt_hci_evt_le_cs_config_complete +{ + uint8_t status; + uint16_t handle; + uint8_t config_id; + uint8_t action; + uint8_t main_mode_type; + uint8_t sub_mode_type; + uint8_t min_main_mode_steps; + uint8_t max_main_mode_steps; + uint8_t main_mode_repetition; + uint8_t mode_0_steps; + uint8_t role; + uint8_t rtt_type; + uint8_t cs_sync_phy; + uint8_t channel_map[10]; + uint8_t channel_map_repetition; + uint8_t channel_selection_type; + uint8_t ch3c_shape; + uint8_t ch3c_jump; + + /** CS enhancements 1. */ + + uint8_t cs_enhancements_1; + uint8_t t_ip1_time; + uint8_t t_ip2_time; + uint8_t t_fcs_time; + uint8_t t_pm_time; +} __packed; + +#define BT_HCI_LE_CS_TEST_CONN_HANDLE 0x0FFF + +#define BT_HCI_LE_CS_PROCEDURE_DONE_STATUS_COMPLETE 0x0 +#define BT_HCI_LE_CS_PROCEDURE_DONE_STATUS_PARTIAL 0x1 +#define BT_HCI_LE_CS_PROCEDURE_DONE_STATUS_ABORTED 0xF + +#define BT_HCI_LE_CS_SUBEVENT_DONE_STATUS_COMPLETE 0x0 +#define BT_HCI_LE_CS_SUBEVENT_DONE_STATUS_PARTIAL 0x1 +#define BT_HCI_LE_CS_SUBEVENT_DONE_STATUS_ABORTED 0xF + +#define BT_HCI_LE_CS_PROCEDURE_ABORT_REASON_NO_ABORT 0x0 +#define BT_HCI_LE_CS_PROCEDURE_ABORT_REASON_LOCAL_HOST_OR_REMOTE_REQUEST 0x1 +#define BT_HCI_LE_CS_PROCEDURE_ABORT_REASON_TOO_FEW_CHANNELS 0x2 +#define BT_HCI_LE_CS_PROCEDURE_ABORT_REASON_CHMAP_INSTANT_PASSED 0x3 +#define BT_HCI_LE_CS_PROCEDURE_ABORT_REASON_UNSPECIFIED 0xF + +#define BT_HCI_LE_CS_SUBEVENT_ABORT_REASON_NO_ABORT 0x0 +#define BT_HCI_LE_CS_SUBEVENT_ABORT_REASON_LOCAL_HOST_OR_REMOTE_REQUEST 0x1 +#define BT_HCI_LE_CS_SUBEVENT_ABORT_REASON_NO_CS_SYNC_RECEIVED 0x2 +#define BT_HCI_LE_CS_SUBEVENT_ABORT_REASON_SCHED_CONFLICT 0x3 +#define BT_HCI_LE_CS_SUBEVENT_ABORT_REASON_UNSPECIFIED 0xF + +#define BT_HCI_LE_CS_SUBEVENT_RESULT_N_AP_IGNORED 0x00 +#define BT_HCI_LE_CS_SUBEVENT_RESULT_N_AP_1 0x01 +#define BT_HCI_LE_CS_SUBEVENT_RESULT_N_AP_2 0x02 +#define BT_HCI_LE_CS_SUBEVENT_RESULT_N_AP_3 0x03 +#define BT_HCI_LE_CS_SUBEVENT_RESULT_N_AP_4 0x04 + +#define BT_HCI_LE_CS_SUBEVENT_RESULT_FREQ_COMPENSATION_NOT_AVAILABLE 0xC000 + +#define BT_HCI_LE_CS_SUBEVENT_RESULT_PCT_NOT_AVAILABLE 0xFFFFFFFF + +#define BT_HCI_LE_CS_REF_POWER_LEVEL_UNAVAILABLE 0x7F + +#define BT_HCI_LE_CS_PCT_I_MASK 0x000FFF +#define BT_HCI_LE_CS_PCT_Q_MASK 0xFFF000 + +#define BT_HCI_LE_CS_TONE_QUALITY_HIGH 0x0 +#define BT_HCI_LE_CS_TONE_QUALITY_MED 0x1 +#define BT_HCI_LE_CS_TONE_QUALITY_LOW 0x2 +#define BT_HCI_LE_CS_TONE_QUALITY_UNAVAILABLE 0x3 + +#define BT_HCI_LE_CS_NOT_TONE_EXT_SLOT 0x0 +#define BT_HCI_LE_CS_TONE_EXT_SLOT_EXT_NOT_EXPECTED 0x1 +#define BT_HCI_LE_CS_TONE_EXT_SLOT_EXT_EXPECTED 0x2 + +#define BT_HCI_LE_CS_TIME_DIFFERENCE_NOT_AVAILABLE ((int16_t)0x8000) + +#define BT_HCI_LE_CS_PACKET_NADM_ATTACK_EXT_UNLIKELY 0x00 +#define BT_HCI_LE_CS_PACKET_NADM_ATTACK_VERY_UNLIKELY 0x01 +#define BT_HCI_LE_CS_PACKET_NADM_ATTACK_UNLIKELY 0x02 +#define BT_HCI_LE_CS_PACKET_NADM_ATTACK_POSSIBLE 0x03 +#define BT_HCI_LE_CS_PACKET_NADM_ATTACK_LIKELY 0x04 +#define BT_HCI_LE_CS_PACKET_NADM_ATTACK_VERY_LIKELY 0x05 +#define BT_HCI_LE_CS_PACKET_NADM_ATTACK_EXT_LIKELY 0x06 +#define BT_HCI_LE_CS_PACKET_NADM_UNKNOWN 0xFF + +#define BT_HCI_LE_CS_PACKET_QUALITY_AA_CHECK_SUCCESSFUL 0x0 +#define BT_HCI_LE_CS_PACKET_QUALITY_AA_CHECK_BIT_ERRORS_FOUND 0x1 +#define BT_HCI_LE_CS_PACKET_QUALITY_AA_CHECK_AA_NOT_FOUND 0x2 + +#define BT_HCI_LE_CS_PACKET_RSSI_NOT_AVAILABLE 0x7F + +#define BT_HCI_EVT_LE_CS_SUBEVENT_RESULT 0x31 +/** Subevent result step data format: Mode 0 Initiator */ + +struct bt_hci_le_cs_step_data_mode_0_initiator +{ +#ifndef CONFIG_ENDIAN_BIG + uint8_t packet_quality_aa_check : 4; + uint8_t packet_quality_bit_errors : 4; +#else +uint8_t packet_quality_bit_errors : 4; +uint8_t packet_quality_aa_check : 4; +#endif /* CONFIG_ENDIAN_BIG */ + uint8_t packet_rssi; + uint8_t packet_antenna; + uint16_t measured_freq_offset; +} __packed; + +/** Subevent result step data format: Mode 0 Reflector */ + +struct bt_hci_le_cs_step_data_mode_0_reflector +{ +#ifndef CONFIG_ENDIAN_BIG + uint8_t packet_quality_aa_check : 4; + uint8_t packet_quality_bit_errors : 4; +#else +uint8_t packet_quality_bit_errors : 4; +uint8_t packet_quality_aa_check : 4; +#endif /* CONFIG_ENDIAN_BIG */ + uint8_t packet_rssi; + uint8_t packet_antenna; +} __packed; + +/** Subevent result step data format: Mode 1 */ + +struct bt_hci_le_cs_step_data_mode_1 +{ +#ifndef CONFIG_ENDIAN_BIG + uint8_t packet_quality_aa_check : 4; + uint8_t packet_quality_bit_errors : 4; +#else +uint8_t packet_quality_bit_errors : 4; +uint8_t packet_quality_aa_check : 4; +#endif /* CONFIG_ENDIAN_BIG */ + uint8_t packet_nadm; + uint8_t packet_rssi; + union + { + int16_t toa_tod_initiator; + int16_t tod_toa_reflector; + } __packed; + uint8_t packet_antenna; +} __packed; + +/** Subevent result step data format: Mode 1 with sounding sequence RTT + * support + */ + +struct bt_hci_le_cs_step_data_mode_1_ss_rtt +{ +#ifndef CONFIG_ENDIAN_BIG + uint8_t packet_quality_aa_check : 4; + uint8_t packet_quality_bit_errors : 4; +#else +uint8_t packet_quality_bit_errors : 4; +uint8_t packet_quality_aa_check : 4; +#endif /* CONFIG_ENDIAN_BIG */ + uint8_t packet_nadm; + uint8_t packet_rssi; + union + { + int16_t toa_tod_initiator; + int16_t tod_toa_reflector; + } __packed; + uint8_t packet_antenna; + uint8_t packet_pct1[4]; + uint8_t packet_pct2[4]; +} __packed; + +/** Format for per-antenna path step data in modes 2 and 3 */ + +struct bt_hci_le_cs_step_data_tone_info +{ + uint8_t phase_correction_term[3]; +#ifndef CONFIG_ENDIAN_BIG + uint8_t quality_indicator : 4; + uint8_t extension_indicator : 4; +#else +uint8_t extension_indicator : 4; +uint8_t quality_indicator : 4; +#endif /* CONFIG_ENDIAN_BIG */ +} __packed; + +/** Subevent result step data format: Mode 2 */ + +struct bt_hci_le_cs_step_data_mode_2 +{ + uint8_t antenna_permutation_index; + struct bt_hci_le_cs_step_data_tone_info tone_info[]; +} __packed; + +/** Subevent result step data format: Mode 3 */ + +struct bt_hci_le_cs_step_data_mode_3 +{ +#ifndef CONFIG_ENDIAN_BIG + uint8_t packet_quality_aa_check : 4; + uint8_t packet_quality_bit_errors : 4; +#else +uint8_t packet_quality_bit_errors : 4; +uint8_t packet_quality_aa_check : 4; +#endif /* CONFIG_ENDIAN_BIG */ + uint8_t packet_nadm; + uint8_t packet_rssi; + union + { + int16_t toa_tod_initiator; + int16_t tod_toa_reflector; + } __packed; + uint8_t packet_antenna; + uint8_t antenna_permutation_index; + struct bt_hci_le_cs_step_data_tone_info tone_info[]; +} __packed; + +/** Subevent result step data format: Mode 3 with sounding sequence RTT + * support + */ + +struct bt_hci_le_cs_step_data_mode_3_ss_rtt +{ +#ifndef CONFIG_ENDIAN_BIG + uint8_t packet_quality_aa_check : 4; + uint8_t packet_quality_bit_errors : 4; +#else +uint8_t packet_quality_bit_errors : 4; +uint8_t packet_quality_aa_check : 4; +#endif /* CONFIG_ENDIAN_BIG */ + uint8_t packet_nadm; + uint8_t packet_rssi; + union + { + int16_t toa_tod_initiator; + int16_t tod_toa_reflector; + } __packed; + uint8_t packet_antenna; + uint8_t packet_pct1[4]; + uint8_t packet_pct2[4]; + uint8_t antenna_permutation_index; + struct bt_hci_le_cs_step_data_tone_info tone_info[]; +} __packed; + +struct bt_hci_evt_le_cs_subevent_result_step +{ + uint8_t step_mode; + uint8_t step_channel; + uint8_t step_data_length; + uint8_t step_data[]; +} __packed; + +struct bt_hci_evt_le_cs_subevent_result +{ + uint16_t conn_handle; + uint8_t config_id; + uint16_t start_acl_conn_event_counter; + uint16_t procedure_counter; + uint16_t frequency_compensation; + uint8_t reference_power_level; + uint8_t procedure_done_status; + uint8_t subevent_done_status; +#ifndef CONFIG_ENDIAN_BIG + uint8_t procedure_abort_reason : 4; + uint8_t subevent_abort_reason : 4; +#else +uint8_t subevent_abort_reason : 4; +uint8_t procedure_abort_reason : 4; +#endif /* CONFIG_ENDIAN_BIG */ + uint8_t num_antenna_paths; + uint8_t num_steps_reported; + uint8_t steps[]; +} __packed; + +#define BT_HCI_EVT_LE_CS_SUBEVENT_RESULT_CONTINUE 0x32 + +struct bt_hci_evt_le_cs_subevent_result_continue +{ + uint16_t conn_handle; + uint8_t config_id; + uint8_t procedure_done_status; + uint8_t subevent_done_status; +#ifndef CONFIG_ENDIAN_BIG + uint8_t procedure_abort_reason : 4; + uint8_t subevent_abort_reason : 4; +#else +uint8_t subevent_abort_reason : 4; +uint8_t procedure_abort_reason : 4; +#endif /* CONFIG_ENDIAN_BIG */ + uint8_t num_antenna_paths; + uint8_t num_steps_reported; + uint8_t steps[]; +} __packed; + +#define BT_HCI_EVT_LE_CS_TEST_END_COMPLETE 0x33 +struct bt_hci_evt_le_cs_test_end_complete +{ + uint8_t status; +} __packed; + +#define BT_HCI_EVT_LE_CS_PROCEDURE_ENABLE_COMPLETE 0x30 +struct bt_hci_evt_le_cs_procedure_enable_complete +{ + uint8_t status; + uint16_t handle; + uint8_t config_id; + uint8_t state; + uint8_t tone_antenna_config_selection; + uint8_t selected_tx_power; + uint8_t subevent_len[3]; + uint8_t subevents_per_event; + uint16_t subevent_interval; + uint16_t event_interval; + uint16_t procedure_interval; + uint16_t procedure_count; + uint16_t max_procedure_len; +} __packed; + +#define BT_HCI_EVT_LE_FRAME_SPACE_UPDATE_COMPLETE 0x35 +struct bt_hci_evt_le_frame_space_update_complete +{ + uint8_t status; + uint16_t handle; + uint8_t initiator; + uint16_t frame_space; + uint8_t phys; + uint16_t spacing_types; +} __packed; + +#define BT_HCI_EVT_LE_CONN_RATE_CHANGE 0x37 +struct bt_hci_evt_le_conn_rate_change +{ + uint8_t status; + uint16_t handle; + uint16_t conn_interval; + uint16_t subrate_factor; + uint16_t peripheral_latency; + uint16_t continuation_number; + uint16_t supervision_timeout; +} __packed; + +/* Event mask bits */ + +#define BT_EVT_BIT(n) (1ULL << (n)) + +#define BT_EVT_MASK_INQUIRY_COMPLETE BT_EVT_BIT(0) +#define BT_EVT_MASK_CONN_COMPLETE BT_EVT_BIT(2) +#define BT_EVT_MASK_CONN_REQUEST BT_EVT_BIT(3) +#define BT_EVT_MASK_DISCONN_COMPLETE BT_EVT_BIT(4) +#define BT_EVT_MASK_AUTH_COMPLETE BT_EVT_BIT(5) +#define BT_EVT_MASK_REMOTE_NAME_REQ_COMPLETE BT_EVT_BIT(6) +#define BT_EVT_MASK_ENCRYPT_CHANGE BT_EVT_BIT(7) +#define BT_EVT_MASK_REMOTE_FEATURES BT_EVT_BIT(10) +#define BT_EVT_MASK_REMOTE_VERSION_INFO BT_EVT_BIT(11) +#define BT_EVT_MASK_HARDWARE_ERROR BT_EVT_BIT(15) +#define BT_EVT_MASK_ROLE_CHANGE BT_EVT_BIT(17) +#define BT_EVT_MASK_MODE_CHANGE BT_EVT_BIT(19) +#define BT_EVT_MASK_PIN_CODE_REQ BT_EVT_BIT(21) +#define BT_EVT_MASK_LINK_KEY_REQ BT_EVT_BIT(22) +#define BT_EVT_MASK_LINK_KEY_NOTIFY BT_EVT_BIT(23) +#define BT_EVT_MASK_DATA_BUFFER_OVERFLOW BT_EVT_BIT(25) +/** Event mask bit for Connection Packet Type Changed event. */ +#define BT_EVT_MASK_CONN_PKT_TYPE_CHANGED BT_EVT_BIT(28) +#define BT_EVT_MASK_INQUIRY_RESULT_WITH_RSSI BT_EVT_BIT(33) +#define BT_EVT_MASK_REMOTE_EXT_FEATURES BT_EVT_BIT(34) +#define BT_EVT_MASK_SYNC_CONN_COMPLETE BT_EVT_BIT(43) +#define BT_EVT_MASK_EXTENDED_INQUIRY_RESULT BT_EVT_BIT(46) +#define BT_EVT_MASK_ENCRYPT_KEY_REFRESH_COMPLETE BT_EVT_BIT(47) +#define BT_EVT_MASK_IO_CAPA_REQ BT_EVT_BIT(48) +#define BT_EVT_MASK_IO_CAPA_RESP BT_EVT_BIT(49) +#define BT_EVT_MASK_USER_CONFIRM_REQ BT_EVT_BIT(50) +#define BT_EVT_MASK_USER_PASSKEY_REQ BT_EVT_BIT(51) +#define BT_EVT_MASK_SSP_COMPLETE BT_EVT_BIT(53) +#define BT_EVT_MASK_USER_PASSKEY_NOTIFY BT_EVT_BIT(58) +#define BT_EVT_MASK_LE_META_EVENT BT_EVT_BIT(61) + +/* Page 2 */ +#define BT_EVT_MASK_NUM_COMPLETE_DATA_BLOCKS BT_EVT_BIT(8) +#define BT_EVT_MASK_TRIGG_CLOCK_CAPTURE BT_EVT_BIT(14) +#define BT_EVT_MASK_SYNCH_TRAIN_COMPLETE BT_EVT_BIT(15) +#define BT_EVT_MASK_SYNCH_TRAIN_RX BT_EVT_BIT(16) +#define BT_EVT_MASK_CL_PER_BC_RX BT_EVT_BIT(17) +#define BT_EVT_MASK_CL_PER_BC_TIMEOUT BT_EVT_BIT(18) +#define BT_EVT_MASK_TRUNC_PAGE_COMPLETE BT_EVT_BIT(19) +#define BT_EVT_MASK_PER_PAGE_RSP_TIMEOUT BT_EVT_BIT(20) +#define BT_EVT_MASK_CL_PER_BC_CH_MAP_CHANGE BT_EVT_BIT(21) +#define BT_EVT_MASK_INQUIRY_RSP_NOT BT_EVT_BIT(22) +#define BT_EVT_MASK_AUTH_PAYLOAD_TIMEOUT_EXP BT_EVT_BIT(23) +#define BT_EVT_MASK_SAM_STATUS_CHANGE BT_EVT_BIT(24) + +#define BT_EVT_MASK_LE_CONN_COMPLETE BT_EVT_BIT(0) +#define BT_EVT_MASK_LE_ADVERTISING_REPORT BT_EVT_BIT(1) +#define BT_EVT_MASK_LE_CONN_UPDATE_COMPLETE BT_EVT_BIT(2) +#define BT_EVT_MASK_LE_REMOTE_FEAT_COMPLETE BT_EVT_BIT(3) +#define BT_EVT_MASK_LE_LTK_REQUEST BT_EVT_BIT(4) +#define BT_EVT_MASK_LE_CONN_PARAM_REQ BT_EVT_BIT(5) +#define BT_EVT_MASK_LE_DATA_LEN_CHANGE BT_EVT_BIT(6) +#define BT_EVT_MASK_LE_P256_PUBLIC_KEY_COMPLETE BT_EVT_BIT(7) +#define BT_EVT_MASK_LE_GENERATE_DHKEY_COMPLETE BT_EVT_BIT(8) +#define BT_EVT_MASK_LE_ENH_CONN_COMPLETE BT_EVT_BIT(9) +#define BT_EVT_MASK_LE_DIRECT_ADV_REPORT BT_EVT_BIT(10) +#define BT_EVT_MASK_LE_PHY_UPDATE_COMPLETE BT_EVT_BIT(11) +#define BT_EVT_MASK_LE_EXT_ADVERTISING_REPORT BT_EVT_BIT(12) +#define BT_EVT_MASK_LE_PER_ADV_SYNC_ESTABLISHED BT_EVT_BIT(13) +#define BT_EVT_MASK_LE_PER_ADVERTISING_REPORT BT_EVT_BIT(14) +#define BT_EVT_MASK_LE_PER_ADV_SYNC_LOST BT_EVT_BIT(15) +#define BT_EVT_MASK_LE_SCAN_TIMEOUT BT_EVT_BIT(16) +#define BT_EVT_MASK_LE_ADV_SET_TERMINATED BT_EVT_BIT(17) +#define BT_EVT_MASK_LE_SCAN_REQ_RECEIVED BT_EVT_BIT(18) +#define BT_EVT_MASK_LE_CHAN_SEL_ALGO BT_EVT_BIT(19) +#define BT_EVT_MASK_LE_CONNECTIONLESS_IQ_REPORT BT_EVT_BIT(20) +#define BT_EVT_MASK_LE_CONNECTION_IQ_REPORT BT_EVT_BIT(21) +#define BT_EVT_MASK_LE_CTE_REQUEST_FAILED BT_EVT_BIT(22) +#define BT_EVT_MASK_LE_PAST_RECEIVED BT_EVT_BIT(23) +#define BT_EVT_MASK_LE_CIS_ESTABLISHED BT_EVT_BIT(24) +#define BT_EVT_MASK_LE_CIS_REQ BT_EVT_BIT(25) +#define BT_EVT_MASK_LE_BIG_COMPLETE BT_EVT_BIT(26) +#define BT_EVT_MASK_LE_BIG_TERMINATED BT_EVT_BIT(27) +#define BT_EVT_MASK_LE_BIG_SYNC_ESTABLISHED BT_EVT_BIT(28) +#define BT_EVT_MASK_LE_BIG_SYNC_LOST BT_EVT_BIT(29) +#define BT_EVT_MASK_LE_REQ_PEER_SCA_COMPLETE BT_EVT_BIT(30) +#define BT_EVT_MASK_LE_PATH_LOSS_THRESHOLD BT_EVT_BIT(31) +#define BT_EVT_MASK_LE_TRANSMIT_POWER_REPORTING BT_EVT_BIT(32) +#define BT_EVT_MASK_LE_BIGINFO_ADV_REPORT BT_EVT_BIT(33) +#define BT_EVT_MASK_LE_SUBRATE_CHANGE BT_EVT_BIT(34) + +#define BT_EVT_MASK_LE_PER_ADV_SYNC_ESTABLISHED_V2 BT_EVT_BIT(35) +#define BT_EVT_MASK_LE_PER_ADVERTISING_REPORT_V2 BT_EVT_BIT(36) +#define BT_EVT_MASK_LE_PAST_RECEIVED_V2 BT_EVT_BIT(37) +#define BT_EVT_MASK_LE_PER_ADV_SUBEVENT_DATA_REQ BT_EVT_BIT(38) +#define BT_EVT_MASK_LE_PER_ADV_RESPONSE_REPORT BT_EVT_BIT(39) +#define BT_EVT_MASK_LE_ENH_CONN_COMPLETE_V2 BT_EVT_BIT(40) +#define BT_EVT_MASK_LE_CIS_ESTABLISHED_V2 BT_EVT_BIT(41) + +#define BT_EVT_MASK_LE_READ_ALL_REMOTE_FEAT_COMPLETE BT_EVT_BIT(42) + +#define BT_EVT_MASK_LE_CS_READ_REMOTE_SUPPORTED_CAPABILITIES_COMPLETE BT_EVT_BIT(43) +#define BT_EVT_MASK_LE_CS_READ_REMOTE_FAE_TABLE_COMPLETE BT_EVT_BIT(44) +#define BT_EVT_MASK_LE_CS_SECURITY_ENABLE_COMPLETE BT_EVT_BIT(45) +#define BT_EVT_MASK_LE_CS_CONFIG_COMPLETE BT_EVT_BIT(46) +#define BT_EVT_MASK_LE_CS_PROCEDURE_ENABLE_COMPLETE BT_EVT_BIT(47) +#define BT_EVT_MASK_LE_CS_SUBEVENT_RESULT BT_EVT_BIT(48) +#define BT_EVT_MASK_LE_CS_SUBEVENT_RESULT_CONTINUE BT_EVT_BIT(49) +#define BT_EVT_MASK_LE_CS_TEST_END_COMPLETE BT_EVT_BIT(50) + +#define BT_EVT_MASK_LE_FRAME_SPACE_UPDATE_COMPLETE BT_EVT_BIT(52) +#define BT_EVT_MASK_LE_CONN_RATE_CHANGE BT_EVT_BIT(54) + +/** Event mask for LE CS Read Remote Supported Capabilities Complete (v2). */ +#define BT_EVT_MASK_LE_CS_READ_REMOTE_SUPPORTED_CAPABILITIES_COMPLETE_V2 BT_EVT_BIT(55) + +/** HCI Error Codes, BT Core Spec v5.4 [Vol 1, Part F]. */ +#define BT_HCI_ERR_SUCCESS 0x00 +#define BT_HCI_ERR_UNKNOWN_CMD 0x01 +#define BT_HCI_ERR_UNKNOWN_CONN_ID 0x02 +#define BT_HCI_ERR_HW_FAILURE 0x03 +#define BT_HCI_ERR_PAGE_TIMEOUT 0x04 +#define BT_HCI_ERR_AUTH_FAIL 0x05 +#define BT_HCI_ERR_PIN_OR_KEY_MISSING 0x06 +#define BT_HCI_ERR_MEM_CAPACITY_EXCEEDED 0x07 +#define BT_HCI_ERR_CONN_TIMEOUT 0x08 +#define BT_HCI_ERR_CONN_LIMIT_EXCEEDED 0x09 +#define BT_HCI_ERR_SYNC_CONN_LIMIT_EXCEEDED 0x0a +#define BT_HCI_ERR_CONN_ALREADY_EXISTS 0x0b +#define BT_HCI_ERR_CMD_DISALLOWED 0x0c +#define BT_HCI_ERR_INSUFFICIENT_RESOURCES 0x0d +#define BT_HCI_ERR_INSUFFICIENT_SECURITY 0x0e +#define BT_HCI_ERR_BD_ADDR_UNACCEPTABLE 0x0f +#define BT_HCI_ERR_CONN_ACCEPT_TIMEOUT 0x10 +#define BT_HCI_ERR_UNSUPP_FEATURE_PARAM_VAL 0x11 +#define BT_HCI_ERR_INVALID_PARAM 0x12 +#define BT_HCI_ERR_REMOTE_USER_TERM_CONN 0x13 +#define BT_HCI_ERR_REMOTE_LOW_RESOURCES 0x14 +#define BT_HCI_ERR_REMOTE_POWER_OFF 0x15 +#define BT_HCI_ERR_LOCALHOST_TERM_CONN 0x16 +#define BT_HCI_ERR_REPEATED_ATTEMPTS 0x17 +#define BT_HCI_ERR_PAIRING_NOT_ALLOWED 0x18 +#define BT_HCI_ERR_UNKNOWN_LMP_PDU 0x19 +#define BT_HCI_ERR_UNSUPP_REMOTE_FEATURE 0x1a +#define BT_HCI_ERR_SCO_OFFSET_REJECTED 0x1b +#define BT_HCI_ERR_SCO_INTERVAL_REJECTED 0x1c +#define BT_HCI_ERR_SCO_AIR_MODE_REJECTED 0x1d +#define BT_HCI_ERR_INVALID_LL_PARAM 0x1e +#define BT_HCI_ERR_UNSPECIFIED 0x1f +#define BT_HCI_ERR_UNSUPP_LL_PARAM_VAL 0x20 +#define BT_HCI_ERR_ROLE_CHANGE_NOT_ALLOWED 0x21 +#define BT_HCI_ERR_LL_RESP_TIMEOUT 0x22 +#define BT_HCI_ERR_LL_PROC_COLLISION 0x23 +#define BT_HCI_ERR_LMP_PDU_NOT_ALLOWED 0x24 +#define BT_HCI_ERR_ENC_MODE_NOT_ACCEPTABLE 0x25 +#define BT_HCI_ERR_LINK_KEY_CANNOT_BE_CHANGED 0x26 +#define BT_HCI_ERR_REQUESTED_QOS_NOT_SUPPORTED 0x27 +#define BT_HCI_ERR_INSTANT_PASSED 0x28 +/** Pairing with Unit Key Not Supported. This error is only valid for BR/EDR. + */ +#define BT_HCI_ERR_PAIRING_NOT_SUPPORTED 0x29 +#define BT_HCI_ERR_DIFF_TRANS_COLLISION 0x2a +#define BT_HCI_ERR_QOS_UNACCEPTABLE_PARAM 0x2c +#define BT_HCI_ERR_QOS_REJECTED 0x2d +#define BT_HCI_ERR_CHAN_ASSESS_NOT_SUPPORTED 0x2e +#define BT_HCI_ERR_INSUFF_SECURITY 0x2f +#define BT_HCI_ERR_PARAM_OUT_OF_MANDATORY_RANGE 0x30 +#define BT_HCI_ERR_ROLE_SWITCH_PENDING 0x32 +#define BT_HCI_ERR_RESERVED_SLOT_VIOLATION 0x34 +#define BT_HCI_ERR_ROLE_SWITCH_FAILED 0x35 +#define BT_HCI_ERR_EXT_INQ_RESP_TOO_LARGE 0x36 +#define BT_HCI_ERR_SIMPLE_PAIR_NOT_SUPP_BY_HOST 0x37 +#define BT_HCI_ERR_HOST_BUSY_PAIRING 0x38 +#define BT_HCI_ERR_CONN_REJECTED_DUE_TO_NO_CHAN 0x39 +#define BT_HCI_ERR_CONTROLLER_BUSY 0x3a +#define BT_HCI_ERR_UNACCEPT_CONN_PARAM 0x3b +#define BT_HCI_ERR_ADV_TIMEOUT 0x3c +#define BT_HCI_ERR_TERM_DUE_TO_MIC_FAIL 0x3d +#define BT_HCI_ERR_CONN_FAIL_TO_ESTAB 0x3e +#define BT_HCI_ERR_MAC_CONN_FAILED 0x3f +#define BT_HCI_ERR_CLOCK_ADJUST_REJECTED 0x40 +#define BT_HCI_ERR_SUBMAP_NOT_DEFINED 0x41 +#define BT_HCI_ERR_UNKNOWN_ADV_IDENTIFIER 0x42 +#define BT_HCI_ERR_LIMIT_REACHED 0x43 +#define BT_HCI_ERR_OP_CANCELLED_BY_HOST 0x44 +#define BT_HCI_ERR_PACKET_TOO_LONG 0x45 +#define BT_HCI_ERR_TOO_LATE 0x46 +#define BT_HCI_ERR_TOO_EARLY 0x47 + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_HCI_TYPES_H */ diff --git a/include/nuttx/net/bluetooth/hci_vs.h b/include/nuttx/net/bluetooth/hci_vs.h new file mode 100644 index 0000000000000..249bb274adab4 --- /dev/null +++ b/include/nuttx/net/bluetooth/hci_vs.h @@ -0,0 +1,536 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/hci_vs.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* hci_vs.h - Bluetooth Host Control Interface Vendor Specific definitions */ + +/* Copyright (c) 2017-2018 Nordic Semiconductor ASA + * Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_HCI_VS_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_HCI_VS_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define BT_VS_CMD_BIT_VERSION 0 +#define BT_VS_CMD_BIT_SUP_CMD 1 +#define BT_VS_CMD_BIT_SUP_FEAT 2 +#define BT_VS_CMD_BIT_SET_EVT_MASK 3 +#define BT_VS_CMD_BIT_RESET 4 +#define BT_VS_CMD_BIT_WRITE_BDADDR 5 +#define BT_VS_CMD_BIT_SET_TRACE_ENABLE 6 +#define BT_VS_CMD_BIT_READ_BUILD_INFO 7 +#define BT_VS_CMD_BIT_READ_STATIC_ADDRS 8 +#define BT_VS_CMD_BIT_READ_KEY_ROOTS 9 +#define BT_VS_CMD_BIT_READ_CHIP_TEMP 10 +#define BT_VS_CMD_BIT_READ_HOST_STACK_CMD 11 +#define BT_VS_CMD_BIT_SET_SCAN_REP_ENABLE 12 +#define BT_VS_CMD_BIT_WRITE_TX_POWER 13 +#define BT_VS_CMD_BIT_READ_TX_POWER 14 + +#define BT_VS_CMD_SUP_FEAT(cmd) BT_LE_FEAT_TEST(cmd, BT_VS_CMD_BIT_SUP_FEAT) +/** Check if HCI VS command BT_VS_CMD_BIT_WRITE_BDADDR is supported */ +#define BT_VS_CMD_WRITE_BD_ADDR(cmd) BT_LE_FEAT_TEST(cmd, BT_VS_CMD_BIT_WRITE_BDADDR) +#define BT_VS_CMD_READ_STATIC_ADDRS(cmd) BT_LE_FEAT_TEST(cmd, BT_VS_CMD_BIT_READ_STATIC_ADDRS) +#define BT_VS_CMD_READ_KEY_ROOTS(cmd) BT_LE_FEAT_TEST(cmd, BT_VS_CMD_BIT_READ_KEY_ROOTS) + +#define BT_HCI_VS_HW_PLAT_INTEL 0x0001 +#define BT_HCI_VS_HW_PLAT_NORDIC 0x0002 +#define BT_HCI_VS_HW_PLAT_NXP 0x0003 +#define BT_HCI_VS_HW_PLAT_ESPRESSIF 0x0004 + +#define BT_HCI_VS_HW_VAR_NORDIC_NRF51X 0x0001 +#define BT_HCI_VS_HW_VAR_NORDIC_NRF52X 0x0002 +#define BT_HCI_VS_HW_VAR_NORDIC_NRF53X 0x0003 +#define BT_HCI_VS_HW_VAR_NORDIC_NRF54HX 0x0004 +#define BT_HCI_VS_HW_VAR_NORDIC_NRF54LX 0x0005 + +#define BT_HCI_VS_HW_VAR_ESP32 0x0001 +#define BT_HCI_VS_HW_VAR_ESP32S3 0x0002 +#define BT_HCI_VS_HW_VAR_ESP32C2 0x0003 +#define BT_HCI_VS_HW_VAR_ESP32C3 0x0004 +#define BT_HCI_VS_HW_VAR_ESP32C6 0x0005 +#define BT_HCI_VS_HW_VAR_ESP32H2 0x0006 +#define BT_HCI_VS_HW_VAR_ESP32C5 0x0007 /**< ESP32-C5 hardware variant */ +#define BT_HCI_VS_HW_VAR_ESP32C61 0x0008 /**< ESP32-C61 hardware variant */ + +#define BT_HCI_VS_FW_VAR_STANDARD_CTLR 0x0000 +#define BT_HCI_VS_FW_VAR_VS_CTLR 0x0001 +#define BT_HCI_VS_FW_VAR_FW_LOADER 0x0002 +#define BT_HCI_VS_FW_VAR_RESCUE_IMG 0x0003 +#define BT_HCI_OP_VS_READ_VERSION_INFO BT_OP(BT_OGF_VS, 0x0001) +struct bt_hci_rp_vs_read_version_info +{ + uint8_t status; + uint16_t hw_platform; + uint16_t hw_variant; + uint8_t fw_variant; + uint8_t fw_version; + uint16_t fw_revision; + uint32_t fw_build; +} __packed; + +#define BT_HCI_OP_VS_READ_SUPPORTED_COMMANDS BT_OP(BT_OGF_VS, 0x0002) +struct bt_hci_rp_vs_read_supported_commands +{ + uint8_t status; + uint8_t commands[64]; +} __packed; + +#define BT_HCI_OP_VS_READ_SUPPORTED_FEATURES BT_OP(BT_OGF_VS, 0x0003) +struct bt_hci_rp_vs_read_supported_features +{ + uint8_t status; + uint8_t features[8]; +} __packed; + +#define BT_HCI_OP_VS_SET_EVENT_MASK BT_OP(BT_OGF_VS, 0x0004) +struct bt_hci_cp_vs_set_event_mask +{ + uint8_t event_mask[8]; +} __packed; + +#define BT_HCI_VS_RESET_SOFT 0x00 +#define BT_HCI_VS_RESET_HARD 0x01 +#define BT_HCI_OP_VS_RESET BT_OP(BT_OGF_VS, 0x0005) +struct bt_hci_cp_vs_reset +{ + uint8_t type; +} __packed; + +#define BT_HCI_OP_VS_WRITE_BD_ADDR BT_OP(BT_OGF_VS, 0x0006) +struct bt_hci_cp_vs_write_bd_addr +{ + bt_addr_t bdaddr; +} __packed; + +#define BT_HCI_VS_TRACE_DISABLED 0x00 +#define BT_HCI_VS_TRACE_ENABLED 0x01 + +#define BT_HCI_VS_TRACE_HCI_EVTS 0x00 +#define BT_HCI_VS_TRACE_VDC 0x01 +#define BT_HCI_OP_VS_SET_TRACE_ENABLE BT_OP(BT_OGF_VS, 0x0007) +struct bt_hci_cp_vs_set_trace_enable +{ + uint8_t enable; + uint8_t type; +} __packed; + +#define BT_HCI_OP_VS_READ_BUILD_INFO BT_OP(BT_OGF_VS, 0x0008) +struct bt_hci_rp_vs_read_build_info +{ + uint8_t status; + uint8_t info[0]; +} __packed; + +struct bt_hci_vs_static_addr +{ + bt_addr_t bdaddr; + uint8_t ir[16]; +} __packed; + +#define BT_HCI_OP_VS_READ_STATIC_ADDRS BT_OP(BT_OGF_VS, 0x0009) +struct bt_hci_rp_vs_read_static_addrs +{ + uint8_t status; + uint8_t num_addrs; + struct bt_hci_vs_static_addr a[0]; +} __packed; + +#define BT_HCI_OP_VS_READ_KEY_HIERARCHY_ROOTS BT_OP(BT_OGF_VS, 0x000a) +struct bt_hci_rp_vs_read_key_hierarchy_roots +{ + uint8_t status; + uint8_t ir[16]; + uint8_t er[16]; +} __packed; + +#define BT_HCI_OP_VS_READ_CHIP_TEMP BT_OP(BT_OGF_VS, 0x000b) +struct bt_hci_rp_vs_read_chip_temp +{ + uint8_t status; + int8_t temps; +} __packed; + +struct bt_hci_vs_cmd +{ + uint16_t vendor_id; + uint16_t opcode_base; +} __packed; + +#define BT_HCI_VS_VID_ANDROID 0x0001 +#define BT_HCI_VS_VID_MICROSOFT 0x0002 +#define BT_HCI_OP_VS_READ_HOST_STACK_CMDS BT_OP(BT_OGF_VS, 0x000c) +struct bt_hci_rp_vs_read_host_stack_cmds +{ + uint8_t status; + uint8_t num_cmds; + struct bt_hci_vs_cmd c[0]; +} __packed; + +#define BT_HCI_VS_SCAN_REQ_REPORTS_DISABLED 0x00 +#define BT_HCI_VS_SCAN_REQ_REPORTS_ENABLED 0x01 +#define BT_HCI_OP_VS_SET_SCAN_REQ_REPORTS BT_OP(BT_OGF_VS, 0x000d) +struct bt_hci_cp_vs_set_scan_req_reports +{ + uint8_t enable; +} __packed; + +#define BT_HCI_VS_LL_HANDLE_TYPE_ADV 0x00 +#define BT_HCI_VS_LL_HANDLE_TYPE_SCAN 0x01 +#define BT_HCI_VS_LL_HANDLE_TYPE_CONN 0x02 +#define BT_HCI_VS_LL_TX_POWER_LEVEL_NO_PREF 0x7F +#define BT_HCI_OP_VS_WRITE_TX_POWER_LEVEL BT_OP(BT_OGF_VS, 0x000e) +struct bt_hci_cp_vs_write_tx_power_level +{ + uint8_t handle_type; + uint16_t handle; + int8_t tx_power_level; +} __packed; + +struct bt_hci_rp_vs_write_tx_power_level +{ + uint8_t status; + uint8_t handle_type; + uint16_t handle; + int8_t selected_tx_power; +} __packed; + +#define BT_HCI_OP_VS_READ_TX_POWER_LEVEL BT_OP(BT_OGF_VS, 0x000f) +struct bt_hci_cp_vs_read_tx_power_level +{ + uint8_t handle_type; + uint16_t handle; +} __packed; + +struct bt_hci_rp_vs_read_tx_power_level +{ + uint8_t status; + uint8_t handle_type; + uint16_t handle; + int8_t tx_power_level; +} __packed; + +#define BT_HCI_OP_VS_READ_USB_TRANSPORT_MODE BT_OP(BT_OGF_VS, 0x0010) + +struct bt_hci_rp_vs_read_usb_transport_mode +{ + uint8_t status; + uint8_t num_supported_modes; + uint8_t supported_mode[0]; +} __packed; + +#define BT_HCI_VS_USB_H2_MODE 0x00 +#define BT_HCI_VS_USB_H4_MODE 0x01 + +#define BT_HCI_OP_VS_SET_USB_TRANSPORT_MODE BT_OP(BT_OGF_VS, 0x0011) + +struct bt_hci_cp_vs_set_usb_transport_mode +{ + uint8_t mode; +} __packed; + +#define BT_HCI_OP_VS_SET_MIN_NUM_USED_CHANS BT_OP(BT_OGF_VS, 0x0012) + +struct bt_hci_cp_vs_set_min_num_used_chans +{ + uint16_t handle; + uint8_t phys; + uint8_t min_used_chans; +} __packed; + +/* Events */ + +struct bt_hci_evt_vs +{ + uint8_t subevent; +} __packed; + +#define BT_HCI_EVT_VS_ERROR_DATA_TYPE_STACK_FRAME 0x01 +#define BT_HCI_EVT_VS_ERROR_DATA_TYPE_CTRL_ASSERT 0x02 +#define BT_HCI_EVT_VS_ERROR_DATA_TYPE_TRACE 0x03 +struct bt_hci_vs_fatal_error_cpu_data_cortex_m +{ + uint32_t a1; + uint32_t a2; + uint32_t a3; + uint32_t a4; + uint32_t ip; + uint32_t lr; + uint32_t pc; + uint32_t xpsr; +} __packed; + +#define BT_HCI_EVT_VS_ERROR_CPU_TYPE_CORTEX_M 0x01 +struct bt_hci_vs_fatal_error_stack_frame +{ + uint32_t reason; + uint8_t cpu_type; + uint8_t cpu_data[0]; +} __packed; + +struct bt_hci_evt_vs_fatal_error_trace_data +{ + uint64_t pc; + uint8_t err_info[0]; +} __packed; + +struct bt_hci_evt_vs_fatal_error +{ + uint8_t type; + uint8_t data[0]; +} __packed; + +#define BT_HCI_VS_TRACE_LMP_TX 0x01 +#define BT_HCI_VS_TRACE_LMP_RX 0x02 +#define BT_HCI_VS_TRACE_LLCP_TX 0x03 +#define BT_HCI_VS_TRACE_LLCP_RX 0x04 +#define BT_HCI_VS_TRACE_LE_CONN_IND 0x05 +#define BT_HCI_EVT_VS_TRACE_INFO 0x03 +struct bt_hci_evt_vs_trace_info +{ + uint8_t type; + uint8_t data[0]; +} __packed; + +#define BT_HCI_EVT_VS_SCAN_REQ_RX 0x04 +struct bt_hci_evt_vs_scan_req_rx +{ + bt_addr_le_t addr; + int8_t rssi; +} __packed; + +struct bt_hci_le_iq_sample16 +{ + int16_t i; + int16_t q; +} __packed; + +#define BT_HCI_EVT_VS_LE_CONNECTIONLESS_IQ_REPORT 0x5 +#define BT_HCI_VS_LE_CTE_REPORT_NO_VALID_SAMPLE 0x8000 +struct bt_hci_evt_vs_le_connectionless_iq_report +{ + uint16_t sync_handle; + uint8_t chan_idx; + int16_t rssi; + uint8_t rssi_ant_id; + uint8_t cte_type; + uint8_t slot_durations; + uint8_t packet_status; + uint16_t per_evt_counter; + uint8_t sample_count; + struct bt_hci_le_iq_sample16 sample[0]; +} __packed; + +#define BT_HCI_EVT_VS_LE_CONNECTION_IQ_REPORT 0x6 +struct bt_hci_evt_vs_le_connection_iq_report +{ + uint16_t conn_handle; + uint8_t rx_phy; + uint8_t data_chan_idx; + int16_t rssi; + uint8_t rssi_ant_id; + uint8_t cte_type; + uint8_t slot_durations; + uint8_t packet_status; + uint16_t conn_evt_counter; + uint8_t sample_count; + struct bt_hci_le_iq_sample16 sample[0]; +} __packed; + +/* Event mask bits */ + +#define BT_EVT_MASK_VS_FATAL_ERROR BT_EVT_BIT(1) +#define BT_EVT_MASK_VS_TRACE_INFO BT_EVT_BIT(2) +#define BT_EVT_MASK_VS_SCAN_REQ_RX BT_EVT_BIT(3) +#define BT_EVT_MASK_VS_LE_CONNECTIONLESS_IQ_REPORT BT_EVT_BIT(4) +#define BT_EVT_MASK_VS_LE_CONNECTION_IQ_REPORT BT_EVT_BIT(5) + +#define DEFAULT_VS_EVT_MASK \ +BT_EVT_MASK_VS_FATAL_ERROR | BT_EVT_MASK_VS_TRACE_INFO | BT_EVT_MASK_VS_SCAN_REQ_RX | \ + BT_EVT_MASK_VS_LE_CONNECTIONLESS_IQ_REPORT | BT_EVT_MASK_VS_LE_CONNECTION_IQ_REPORT + +/* Mesh HCI commands */ +#define BT_HCI_MESH_REVISION 0x01 + +#define BT_HCI_OP_VS_MESH BT_OP(BT_OGF_VS, 0x0042) +#define BT_HCI_MESH_EVT_PREFIX 0xF0 + +struct bt_hci_cp_mesh +{ + uint8_t opcode; +} __packed; + +#define BT_HCI_OC_MESH_GET_OPTS 0x00 +struct bt_hci_rp_mesh_get_opts +{ + uint8_t status; + uint8_t opcode; + uint8_t revision; + uint8_t ch_map; + int8_t min_tx_power; + int8_t max_tx_power; + uint8_t max_scan_filter; + uint8_t max_filter_pattern; + uint8_t max_adv_slot; + uint8_t max_tx_window; + uint8_t evt_prefix_len; + uint8_t evt_prefix; +} __packed; + +#define BT_HCI_MESH_PATTERN_LEN_MAX 0x0f + +#define BT_HCI_OC_MESH_SET_SCAN_FILTER 0x01 +struct bt_hci_mesh_pattern +{ + uint8_t pattern_len; + uint8_t pattern[0]; +} __packed; + +struct bt_hci_cp_mesh_set_scan_filter +{ + uint8_t scan_filter; + uint8_t filter_dup; + uint8_t num_patterns; + struct bt_hci_mesh_pattern patterns[0]; +} __packed; +struct bt_hci_rp_mesh_set_scan_filter +{ + uint8_t status; + uint8_t opcode; + uint8_t scan_filter; +} __packed; + +#define BT_HCI_OC_MESH_ADVERTISE 0x02 +struct bt_hci_cp_mesh_advertise +{ + uint8_t adv_slot; + uint8_t own_addr_type; + bt_addr_t random_addr; + uint8_t ch_map; + int8_t tx_power; + uint8_t min_tx_delay; + uint8_t max_tx_delay; + uint8_t retx_count; + uint8_t retx_interval; + uint8_t scan_delay; + uint16_t scan_duration; + uint8_t scan_filter; + uint8_t data_len; + uint8_t data[31]; +} __packed; +struct bt_hci_rp_mesh_advertise +{ + uint8_t status; + uint8_t opcode; + uint8_t adv_slot; +} __packed; + +#define BT_HCI_OC_MESH_ADVERTISE_TIMED 0x03 +struct bt_hci_cp_mesh_advertise_timed +{ + uint8_t adv_slot; + uint8_t own_addr_type; + bt_addr_t random_addr; + uint8_t ch_map; + int8_t tx_power; + uint8_t retx_count; + uint8_t retx_interval; + uint32_t instant; + uint16_t tx_delay; + uint16_t tx_window; + uint8_t data_len; + uint8_t data[31]; +} __packed; +struct bt_hci_rp_mesh_advertise_timed +{ + uint8_t status; + uint8_t opcode; + uint8_t adv_slot; +} __packed; + +#define BT_HCI_OC_MESH_ADVERTISE_CANCEL 0x04 +struct bt_hci_cp_mesh_advertise_cancel +{ + uint8_t adv_slot; +} __packed; +struct bt_hci_rp_mesh_advertise_cancel +{ + uint8_t status; + uint8_t opcode; + uint8_t adv_slot; +} __packed; + +#define BT_HCI_OC_MESH_SET_SCANNING 0x05 +struct bt_hci_cp_mesh_set_scanning +{ + uint8_t enable; + uint8_t ch_map; + uint8_t scan_filter; +} __packed; +struct bt_hci_rp_mesh_set_scanning +{ + uint8_t status; + uint8_t opcode; +} __packed; + +/* Events */ + +struct bt_hci_evt_mesh +{ + uint8_t prefix; + uint8_t subevent; +} __packed; + +#define BT_HCI_EVT_MESH_ADV_COMPLETE 0x00 +struct bt_hci_evt_mesh_adv_complete +{ + uint8_t adv_slot; +} __packed; + +#define BT_HCI_EVT_MESH_SCANNING_REPORT 0x01 +struct bt_hci_evt_mesh_scan_report +{ + bt_addr_le_t addr; + uint8_t chan; + int8_t rssi; + uint32_t instant; + uint8_t data_len; + uint8_t data[0]; +} __packed; +struct bt_hci_evt_mesh_scanning_report +{ + uint8_t num_reports; + struct bt_hci_evt_mesh_scan_report reports[0]; +} __packed; + +struct net_buf *hci_vs_err_stack_frame(unsigned int reason, + const struct arch_esf *esf); +struct net_buf *hci_vs_err_trace(const char *file, + uint32_t line, uint64_t pc); +struct net_buf *hci_vs_err_assert(const char *file, uint32_t line); + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_HCI_VS_H */ diff --git a/include/nuttx/net/bluetooth/iso.h b/include/nuttx/net/bluetooth/iso.h new file mode 100644 index 0000000000000..ab6f009eb851c --- /dev/null +++ b/include/nuttx/net/bluetooth/iso.h @@ -0,0 +1,1597 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/iso.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Bluetooth ISO handling + */ + +/* Copyright (c) 2020 Intel Corporation + * Copyright (c) 2021-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_ISO_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_ISO_H + +/** + * @brief Isochronous channels (ISO) + * @defgroup bt_iso Isochronous channels (ISO) + * + * @since 2.3 + * @version 0.8.0 + * + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Headroom needed for outgoing ISO SDUs + */ +#define BT_ISO_CHAN_SEND_RESERVE BT_BUF_ISO_SIZE(0) + +/** + * @brief Helper to calculate needed buffer size for ISO SDUs. + * Useful for creating buffer pools. + * + * @param mtu Required ISO SDU size + * + * @return Needed buffer size to match the requested ISO SDU MTU. + */ +#define BT_ISO_SDU_BUF_SIZE(mtu) BT_BUF_ISO_SIZE(mtu) + +/* Convert BIS index to bit + * + * The BIS indexes start from 0x01, so the lowest allowed bit is BIT(0) that + * represents index 0x01. To synchronize to e.g. BIS indexes 0x01 and 0x02, + * the bitfield value should be BIT(0) | BIT(1). As a general notation, to + * sync to BIS index N use BIT(N - 1). + */ +#define BT_ISO_BIS_INDEX_BIT(x) (BIT((x) - 1)) + +/** Value to set the ISO data path over HCi. */ +#define BT_ISO_DATA_PATH_HCI 0x00 + +/** Unknown SDU interval */ +#define BT_ISO_SDU_INTERVAL_UNKNOWN 0x000000U +/** The minimum value for vendor specific data path ID */ +#define BT_ISO_DATA_PATH_VS_ID_MIN 0x01 +/** The maximum value for vendor specific data path ID */ +#define BT_ISO_DATA_PATH_VS_ID_MAX 0xFE +/** Minimum controller delay in microseconds (0 us) */ +#define BT_ISO_CONTROLLER_DELAY_MIN 0x000000 +/** Maximum controller delay in microseconds (4,000,000 us) */ +#define BT_ISO_CONTROLLER_DELAY_MAX 0x3D0900 +/** Minimum interval value in microseconds (255 us) */ +#define BT_ISO_SDU_INTERVAL_MIN 0x0000FFU +/** Maximum interval value in microseconds (1,048,575 us) */ +#define BT_ISO_SDU_INTERVAL_MAX 0x0FFFFFU +/** Minimum ISO interval in units of 1.25 ms (5 ms) */ +#define BT_ISO_ISO_INTERVAL_MIN 0x0004U +/** Maximum ISO interval in units of 1.25 ms (4,000 ms) */ +#define BT_ISO_ISO_INTERVAL_MAX 0x0C80U +/** Minimum latency value in milliseconds (5 ms) */ +#define BT_ISO_LATENCY_MIN 0x0005 +/** Maximum latency value in milliseconds (4,000 ms) */ +#define BT_ISO_LATENCY_MAX 0x0FA0 +/** Packets will be sent sequentially between the channels in the group */ +#define BT_ISO_PACKING_SEQUENTIAL 0x00 +/** Packets will be sent interleaved between the channels in the group */ +#define BT_ISO_PACKING_INTERLEAVED 0x01 +/** Packets may be framed or unframed */ +#define BT_ISO_FRAMING_UNFRAMED 0x00 +/** Packets are always framed */ +#define BT_ISO_FRAMING_FRAMED 0x01 +/** Maximum number of isochronous channels in a single group (31) */ +#define BT_ISO_MAX_GROUP_ISO_COUNT 0x1F +/** Minimum SDU size (1 octet) */ +#define BT_ISO_MIN_SDU 0x0001 +/** Maximum SDU size (4095 octets) */ +#define BT_ISO_MAX_SDU 0x0FFF +/** Minimum PDU size (0 octet) */ +#define BT_ISO_CONNECTED_PDU_MIN 0x0000U +/** Minimum PDU size (1 octet) */ +#define BT_ISO_BROADCAST_PDU_MIN 0x0001U +/** Maximum PDU size (251 octets) */ +#define BT_ISO_PDU_MAX 0x00FBU +/** Minimum burst number (1) */ +#define BT_ISO_BN_MIN 0x01U +/** Maximum burst number (15) */ +#define BT_ISO_BN_MAX 0x0FU +/** Minimum flush timeout in multiples of ISO interval (1) */ +#define BT_ISO_FT_MIN 0x01U +/** Maximum flush timeout in multiples of ISO interval (255) */ +#define BT_ISO_FT_MAX 0xFFU +/** Minimum number of subevents (1) */ +#define BT_ISO_NSE_MIN 0x01U +/** Maximum number of subevents (31) */ +#define BT_ISO_NSE_MAX 0x1FU +/** Minimum BIG sync timeout value in units of 10 ms (100 ms) */ +#define BT_ISO_SYNC_TIMEOUT_MIN 0x000A +/** Maximum BIG sync timeout value in units of 10 ms (163,840 ms) */ +#define BT_ISO_SYNC_TIMEOUT_MAX 0x4000 +/** Controller controlled maximum subevent count value */ +#define BT_ISO_SYNC_MSE_ANY 0x00 +/** Minimum BIG sync maximum subevent count value (1) */ +#define BT_ISO_SYNC_MSE_MIN 0x01 +/** Maximum BIG sync maximum subevent count value (31) */ +#define BT_ISO_SYNC_MSE_MAX 0x1F +/** Minimum connected ISO retransmission value (0) */ +#define BT_ISO_CONNECTED_RTN_MIN 0x00 +/** Maximum connected ISO retransmission value (255) */ +#define BT_ISO_CONNECTED_RTN_MAX 0xFF +/** Minimum broadcast ISO retransmission value (0) */ +#define BT_ISO_BROADCAST_RTN_MIN 0x00 +/** Maximum broadcast ISO retransmission value (30) */ +#define BT_ISO_BROADCAST_RTN_MAX 0x1E +/** Broadcast code size (16 octets) */ +#define BT_ISO_BROADCAST_CODE_SIZE 0x10 +/** Lowest BIS index (1) */ +#define BT_ISO_BIS_INDEX_MIN 0x01 +/** Highest BIS index (31) */ +#define BT_ISO_BIS_INDEX_MAX 0x1F +/** Minimum Immediate Repetition Count (1) */ +#define BT_ISO_IRC_MIN 0x01U +/** Maximum Immediate Repetition Count (15) */ +#define BT_ISO_IRC_MAX 0x0FU +/** Minimum pre-transmission offset (0) */ +#define BT_ISO_PTO_MIN 0x00U +/** Maximum pre-transmission offset (15) */ +#define BT_ISO_PTO_MAX 0x0FU +/** No subinterval */ +#define BT_ISO_SUBINTERVAL_NONE 0x00000000U +/** Unknown subinterval */ +#define BT_ISO_SUBINTERVAL_UNKNOWN 0xFFFFFFFFU +/** Minimum subinterval in microseconds (400 us) */ +#define BT_ISO_SUBINTERVAL_MIN 0x00000190U +/** @brief Maximum subinterval in microseconds (3,999,999 us) + * + * This maximum depends on the ISO interval, as the subinterval shall be less + * than the ISO interval + */ +#define BT_ISO_SUBINTERVAL_MAX 0x00009C3FU + +/** + * @brief Check if ISO BIS bitfield is valid + * (BT_ISO_BIS_INDEX_BIT(1)|..|BT_ISO_BIS_INDEX_BIT(31)) + * + * @param _bis_bitfield BIS index bitfield (uint32) + */ +#define BT_ISO_VALID_BIS_BITFIELD(_bis_bitfield) \ +((_bis_bitfield) != 0U && (_bis_bitfield) <= BIT_MASK(BT_ISO_BIS_INDEX_MAX)) + +/** + * @brief Life-span states of ISO channel. Used only by internal APIs dealing + * with setting channel to proper state depending on operational context. + */ + +enum bt_iso_state +{ + /** Channel disconnected */ + + BT_ISO_STATE_DISCONNECTED, + + /** Channel in connecting state */ + + BT_ISO_STATE_CONNECTING, + + /** Channel ready for upper layer traffic on it */ + + BT_ISO_STATE_CONNECTED, + + /** Channel in disconnecting state */ + + BT_ISO_STATE_DISCONNECTING, +}; + +/** + * @brief ISO Channel Type. + */ + +enum bt_iso_chan_type +{ + BT_ISO_CHAN_TYPE_NONE, /**< No channel type */ + BT_ISO_CHAN_TYPE_CENTRAL, /**< Connected as central */ + BT_ISO_CHAN_TYPE_PERIPHERAL, /**< Connected as peripheral */ + BT_ISO_CHAN_TYPE_BROADCASTER, /**< Isochronous broadcaster */ + BT_ISO_CHAN_TYPE_SYNC_RECEIVER /**< Synchronized receiver */ +}; + +/** @brief ISO Channel structure. */ + +struct bt_iso_chan +{ + /** Channel connection reference */ + + struct bt_conn *iso; + + /** Channel operations reference */ + + struct bt_iso_chan_ops *ops; + + /** Channel QoS reference */ + + struct bt_iso_chan_qos *qos; + + /** Channel state */ + + enum bt_iso_state state; + + /** @internal Node used internally by the stack */ + + struct list_node node; +}; + +/** @brief ISO Channel IO QoS structure. */ + +struct bt_iso_chan_io_qos +{ + /* @brief Channel SDU. + * + * Value range is @ref BT_ISO_MIN_SDU to @ref BT_ISO_MAX_SDU. + */ + + uint16_t sdu; + /* @brief Channel PHY - See the @ref bt_gap_le_phy values. + * + * Setting @ref BT_GAP_LE_PHY_NONE is invalid. + */ + + uint8_t phy; + /* @brief Channel Retransmission Number. + * + * This value is ignored if any advanced ISO parameters are set. + */ + + uint8_t rtn; + +#if defined(CONFIG_BT_ISO_TEST_PARAMS) || defined(__DOXYGEN__) + /* @brief Maximum PDU size + * + * Maximum size, in octets, of the payload from link layer to link layer. + * + * Value range @ref BT_ISO_CONNECTED_PDU_MIN to @ref BT_ISO_PDU_MAX for + * connected ISO. + * + * Value range @ref BT_ISO_BROADCAST_PDU_MIN to @ref BT_ISO_PDU_MAX for + * broadcast ISO. + */ + + uint16_t max_pdu; + + /* @brief Burst number + * + * Value range @ref BT_ISO_BN_MIN to @ref BT_ISO_BN_MAX. + */ + + uint8_t burst_number; +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ +}; + +/** @brief ISO Channel QoS structure. */ + +struct bt_iso_chan_qos +{ + /* @brief Channel Receiving QoS. + * + * Setting NULL disables data path @ref BT_HCI_DATAPATH_DIR_CTLR_TO_HOST. + * + * Can only be set for a connected isochronous channel, or a broadcast + * isochronous receiver. + */ + + struct bt_iso_chan_io_qos *rx; + /* @brief Channel Transmission QoS + * + * Setting NULL disables data path @ref BT_HCI_DATAPATH_DIR_HOST_TO_CTLR. + * + * Can only be set for a connected isochronous channel, or a broadcast + * isochronous transmitter. + */ + + struct bt_iso_chan_io_qos *tx; + +#if defined(CONFIG_BT_ISO_TEST_PARAMS) || defined(__DOXYGEN__) + /* @brief Number of subevents + * + * Maximum number of subevents in each CIS or BIS event. + * + * Value range @ref BT_ISO_NSE_MIN to @ref BT_ISO_NSE_MAX. + */ + + uint8_t num_subevents; +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ +}; + +/** @brief ISO Channel Data Path structure. */ + +struct bt_iso_chan_path +{ + /* @brief Default path ID + * + * @ref BT_ISO_DATA_PATH_HCI to use ISO over HCI or between @ref + * BT_ISO_DATA_PATH_VS_ID_MIN and @ref BT_ISO_DATA_PATH_VS_ID_MAX for + * vendor specific data paths. + */ + + uint8_t pid; + /* @brief Coding Format + * + * See the BT_HCI_CODING_FORMAT_* values for valid values. + */ + + uint8_t format; + + /** Company ID */ + + uint16_t cid; + + /** Vendor-defined Codec ID */ + + uint16_t vid; + /* @brief Controller Delay in microseconds + * + * Value range from @ref BT_ISO_CONTROLLER_DELAY_MIN to @ref + * BT_ISO_CONTROLLER_DELAY_MAX. + */ + + uint32_t delay; + + /** Codec Configuration length */ + + uint8_t cc_len; + /* @brief Pointer to an array containing the Codec Configuration + * + * Shall not be NULL if bt_iso_chan_path.cc_len is non-zero. + */ + + const uint8_t *cc; +}; + +/** ISO packet status flag bits */ + +enum +{ + /** The ISO packet is valid. */ + + BT_ISO_FLAGS_VALID = BIT(0), + + /* @brief The ISO packet may possibly contain errors. + * + * May be caused by a failed CRC check or if missing a part of the SDU. + */ + + BT_ISO_FLAGS_ERROR = BIT(1), + + /** The ISO packet was lost. */ + + BT_ISO_FLAGS_LOST = BIT(2), + + /* @brief Timestamp is valid + * + * If not set, then the bt_iso_recv_info.ts value is not valid, and should + * not be used. + */ + + BT_ISO_FLAGS_TS = BIT(3) +}; + +/** @brief ISO Meta Data structure for received ISO packets. */ + +struct bt_iso_recv_info +{ + /* @brief ISO timestamp + * + * Only valid if @p flags has the @ref BT_ISO_FLAGS_TS bit set. + */ + + uint32_t ts; + + /** ISO packet sequence number of the first fragment in the SDU */ + + uint16_t seq_num; + + /** ISO packet flags bitfield (BT_ISO_FLAGS_*) */ + + uint8_t flags; +}; + +/** @brief ISO Meta Data structure for transmitted ISO packets. */ + +struct bt_iso_tx_info +{ + /** CIG reference point or BIG anchor point of a transmitted SDU, in + * microseconds. + */ + + uint32_t ts; + + /** Time offset, in microseconds */ + + uint32_t offset; + + /** Packet sequence number */ + + uint16_t seq_num; +}; + +/** + * @struct bt_iso_cig + * @brief Opaque type representing a Connected Isochronous Group (CIG). + */ + +struct bt_iso_cig; + +/** @brief Connected Isochronous Group (CIG) parameters */ + +struct bt_iso_cig_param +{ + /** @brief Array of pointers to CIS channels */ + + struct bt_iso_chan **cis_channels; + + /* @brief Number of channels in @p cis_channels + * + * Maximum number of channels in a single group is @ref + * BT_ISO_MAX_GROUP_ISO_COUNT + */ + + uint8_t num_cis; + + /* @brief Channel interval in us for SDUs sent from Central to Peripheral. + * + * Value range @ref BT_ISO_SDU_INTERVAL_MIN to @ref + * BT_ISO_SDU_INTERVAL_MAX. + */ + + uint32_t c_to_p_interval; + + /* @brief Channel interval in us for SDUs sent from Peripheral to Central. + * + * Value range @ref BT_ISO_SDU_INTERVAL_MIN to @ref + * BT_ISO_SDU_INTERVAL_MAX. + */ + + uint32_t p_to_c_interval; + + /* @brief Channel Latency in ms for SDUs sent from Central to Peripheral + * + * Value range @ref BT_ISO_LATENCY_MIN to @ref BT_ISO_LATENCY_MAX. + * + * This value is ignored if any advanced ISO parameters are set. + */ + + uint16_t c_to_p_latency; + + /* @brief Channel Latency in ms for SDUs sent from Peripheral to Central + * + * Value range @ref BT_ISO_LATENCY_MIN to @ref BT_ISO_LATENCY_MAX. + * + * This value is ignored if any advanced ISO parameters are set. + */ + + uint16_t p_to_c_latency; + + /* @brief Channel peripherals sleep clock accuracy Only for CIS + * + * Shall be worst case sleep clock accuracy of all the peripherals. For + * possible values, see @ref bt_gap_sca. If unknown for the peripherals, + * this should be set to @ref BT_GAP_SCA_UNKNOWN. + */ + + uint8_t sca; + + /* @brief Channel packing mode. + * + * @ref BT_ISO_PACKING_SEQUENTIAL or @ref BT_ISO_PACKING_INTERLEAVED + */ + + uint8_t packing; + + /* @brief Channel framing mode. + * + * @ref BT_ISO_FRAMING_UNFRAMED for unframed and @ref BT_ISO_FRAMING_FRAMED + * for framed. + */ + + uint8_t framing; + +#if defined(CONFIG_BT_ISO_TEST_PARAMS) || defined(__DOXYGEN__) + /* @brief Central to Peripheral flush timeout + * + * The flush timeout in multiples of ISO_Interval for each payload sent + * from the Central to Peripheral. + * + * Value range from @ref BT_ISO_FT_MIN to @ref BT_ISO_FT_MAX + */ + + uint8_t c_to_p_ft; + + /* @brief Peripheral to Central flush timeout + * + * The flush timeout in multiples of ISO_Interval for each payload sent + * from the Peripheral to Central. + * + * Value range from @ref BT_ISO_FT_MIN to @ref BT_ISO_FT_MAX. + */ + + uint8_t p_to_c_ft; + + /* @brief ISO interval + * + * Time between consecutive CIS anchor points. + * + * Value range from @ref BT_ISO_ISO_INTERVAL_MIN to @ref + * BT_ISO_ISO_INTERVAL_MAX. + */ + + uint16_t iso_interval; +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ +}; + +/** ISO connection parameters structure */ + +struct bt_iso_connect_param +{ + /** The ISO channel to connect */ + + struct bt_iso_chan *iso_chan; + + /** The ACL connection */ + + struct bt_conn *acl; +}; + +/** + * @struct bt_iso_big + * @brief Opaque type representing a Broadcast Isochronous Group (BIG). + */ + +struct bt_iso_big; + +/** @brief Broadcast Isochronous Group (BIG) creation parameters */ + +struct bt_iso_big_create_param +{ + /** Array of pointers to BIS channels */ + + struct bt_iso_chan **bis_channels; + + /* @brief Number of channels in @p bis_channels + * + * Maximum number of channels in a single group is @ref + * BT_ISO_MAX_GROUP_ISO_COUNT + */ + + uint8_t num_bis; + + /* @brief Channel interval in us. + * + * Value range @ref BT_ISO_SDU_INTERVAL_MIN to @ref + * BT_ISO_SDU_INTERVAL_MAX. + */ + + uint32_t interval; + + /* @brief Channel Latency in ms. + * + * Value range @ref BT_ISO_LATENCY_MIN to @ref BT_ISO_LATENCY_MAX. + * + * This value is ignored if any advanced ISO parameters are set. + */ + + uint16_t latency; + + /* @brief Channel packing mode. + * + * @ref BT_ISO_PACKING_SEQUENTIAL or @ref BT_ISO_PACKING_INTERLEAVED + */ + + uint8_t packing; + + /* @brief Channel framing mode. + * + * @ref BT_ISO_FRAMING_UNFRAMED for unframed and @ref BT_ISO_FRAMING_FRAMED + * for framed. + */ + + uint8_t framing; + + /** Whether or not to encrypt the streams. */ + + bool encryption; + + /* @brief Broadcast code + * + * The code used to derive the session key that is used to encrypt and + * decrypt BIS payloads. + * + * If the value is a string or the value is less than 16 octets, the + * remaining octets shall be 0. + * + * Example: The string "Broadcast Code" shall be [42 72 6F 61 64 63 61 73 + * 74 20 43 6F 64 65 00 00] + */ + + uint8_t bcode[BT_ISO_BROADCAST_CODE_SIZE]; + +#if defined(CONFIG_BT_ISO_TEST_PARAMS) || defined(__DOXYGEN__) + /* @brief Immediate Repetition Count + * + * The number of times the scheduled payloads are transmitted in a given + * event. + * + * Value range from @ref BT_ISO_IRC_MIN to @ref BT_ISO_IRC_MAX. + */ + + uint8_t irc; + + /* @brief Pre-transmission offset + * + * Offset used for pre-transmissions. + * + * Value range from @ref BT_ISO_PTO_MIN to @ref BT_ISO_PTO_MAX. + */ + + uint8_t pto; + + /* @brief ISO interval + * + * Time between consecutive BIS anchor points. + * + * Value range from @ref BT_ISO_ISO_INTERVAL_MIN to @ref + * BT_ISO_ISO_INTERVAL_MAX. + */ + + uint16_t iso_interval; +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ +}; + +/** @brief Broadcast Isochronous Group (BIG) Sync Parameters */ + +struct bt_iso_big_sync_param +{ + /** Array of pointers to BIS channels */ + + struct bt_iso_chan **bis_channels; + + /* @brief Number channels in @p bis_channels + * + * Maximum number of channels in a single group is @ref + * BT_ISO_MAX_GROUP_ISO_COUNT + */ + + uint8_t num_bis; + + /* @brief Bitfield of the BISes to sync to + * + * Use @ref BT_ISO_BIS_INDEX_BIT to convert BIS indexes to a bitfield. + * + * To synchronize to e.g. BIS indexes 0x01 and 0x02, this can be set to + * BT_ISO_BIS_INDEX_BIT(0x01) | BT_ISO_BIS_INDEX_BIT(0x02). + */ + + uint32_t bis_bitfield; + + /* @brief Maximum subevents + * + * The MSE (Maximum Subevents) parameter is the maximum number of subevents + * that a Controller should use to receive data payloads in each interval + * for a BIS. + * + * Value range is @ref BT_ISO_SYNC_MSE_MIN to @ref BT_ISO_SYNC_MSE_MAX, or + * @ref BT_ISO_SYNC_MSE_ANY to let the controller choose. + */ + + uint32_t mse; + + /* @brief Synchronization timeout for the BIG (N * 10 MS) + * + * Value range is @ref BT_ISO_SYNC_TIMEOUT_MIN to @ref + * BT_ISO_SYNC_TIMEOUT_MAX. + */ + + uint16_t sync_timeout; + + /** Whether or not the streams of the BIG are encrypted */ + + bool encryption; + + /* @brief Broadcast code + * + * The code used to derive the session key that is used to encrypt and + * decrypt BIS payloads. + * + * If the value is a string or a the value is less than 16 octets, the + * remaining octets shall be 0. + * + * Example: The string "Broadcast Code" shall be [42 72 6F 61 64 63 61 73 + * 74 20 43 6F 64 65 00 00] + */ + + uint8_t bcode[BT_ISO_BROADCAST_CODE_SIZE]; +}; + +/** @brief Broadcast Isochronous Group (BIG) information */ + +struct bt_iso_biginfo +{ + /** Address of the advertiser */ + + const bt_addr_le_t *addr; + + /** Advertiser SID */ + + uint8_t sid; + + /** Number of BISes in the BIG */ + + uint8_t num_bis; + + /** Maximum number of subevents in each isochronous event */ + + uint8_t sub_evt_count; + + /** Interval between two BIG anchor point (N * 1.25 ms) */ + + uint16_t iso_interval; + + /** The number of new payloads in each BIS event */ + + uint8_t burst_number; + + /** Offset used for pre-transmissions */ + + uint8_t offset; + + /** The number of times a payload is transmitted in a BIS event */ + + uint8_t rep_count; + + /** Maximum size, in octets, of the payload */ + + uint16_t max_pdu; + + /** The interval, in microseconds, of periodic SDUs. */ + + uint32_t sdu_interval; + + /** Maximum size of an SDU, in octets. */ + + uint16_t max_sdu; + + /** Channel PHY */ + + uint8_t phy; + + /** Channel framing mode */ + + uint8_t framing; + + /** Whether or not the BIG is encrypted */ + + bool encryption; +}; + +/** + * @brief ISO Channel operations structure. + * + * @note The callbacks are invoked from a thread context, never from an + * ISR. Whether a callback is invoked from a context internal to the stack + * or synchronously from within the API call that triggers it, and from + * which context, is not part of the API and may change between releases. + * See @rstref{Callback execution contexts } + * for the hazards of blocking in a callback and their mitigations. + */ + +struct bt_iso_chan_ops +{ + /* @brief Channel connected callback + * + * If this callback is provided it will be called whenever the connection + * completes. + * + * For a peripheral, the QoS values (see @ref bt_iso_chan_io_qos) are set + * when this is called. The peripheral does not have any information about + * the RTN though. + * + * @param chan The channel that has been connected + */ + + void (*connected)(struct bt_iso_chan *chan); + + /* @brief Channel disconnected callback + * + * If this callback is provided it will be called whenever the channel is + * disconnected, including when a connection gets rejected or when setting + * security fails. + * + * If the channel was established (i.e. @ref bt_iso_chan_ops.connected has + * been called for this channel), then the channel object is still valid + * and the memory of the channel shall not be memset to 0 or otherwise + * free'd. To avoid any issues it is recommended to use a @ref + * k_work_submit or similar to not overwrite any data while in the + * callback. + * + * For the above reason it is still possible to use bt_iso_chan_get_info() + * on the @p chan. + * + * If the @p chan is a unicast (CIS) channel, then this callback will + * always be called before @ref bt_conn_cb.disconnected when the associated + * ACL connection also disconnects. + * + * @param chan The channel that has been Disconnected + * @param reason BT_HCI_ERR_* reason for the disconnection. + */ + + void (*disconnected)(struct bt_iso_chan *chan, uint8_t reason); + + /* @brief Channel alloc_buf callback + * + * If this callback is provided the channel will use it to allocate buffers + * to store incoming data. + * + * @param chan The channel requesting a buffer. + * + * @return Allocated buffer. + */ + + struct net_buf *(*alloc_buf)(struct bt_iso_chan *chan); + + /* @brief Channel recv callback + * + * @param chan The channel receiving data. + * @param buf Buffer containing incoming data. + * @param info Pointer to the metadata for the buffer. The lifetime of the + * pointer is linked to the lifetime of the net_buf. Metadata + * such as sequence number and timestamp can be provided by the + * bluetooth controller. + */ + + void (*recv)(struct bt_iso_chan *chan, const struct bt_iso_recv_info *info, + struct net_buf *buf); + + /* @brief Channel sent callback + * + * This callback will be called once the controller marks the SDU as + * completed. When the controller does so is implementation dependent. It + * could be after the SDU is enqueued for transmission, or after it is sent + * on air or flushed. + * + * @param chan The channel which has sent data. + */ + + void (*sent)(struct bt_iso_chan *chan); + + /* @brief Channel send failed callback + * + * This callback will be called if the call to bt_iso_chan_send() was + * successful, but where the send operation later failed before the request + * was sent to the Bluetooth Controller. This can happen if the @p chan has + * disconnected. + * + * @param chan The channel for which the send operation failed. + * @param err The reason for the send failure. + */ + + void (*send_failed)(struct bt_iso_chan *chan, int err); +}; + +/** @brief ISO Accept Info Structure */ + +struct bt_iso_accept_info +{ + /** The ACL connection that is requesting authorization */ + + struct bt_conn *acl; + + /* @brief The ID of the connected isochronous group (CIG) on the central + * + * The ID is unique per ACL + */ + + uint8_t cig_id; + + /* @brief The ID of the connected isochronous stream (CIS) on the central + * + * This ID is unique within a CIG + */ + + uint8_t cis_id; +}; + +/** @brief ISO Server structure. */ + +struct bt_iso_server +{ + /* @brief Server accept callback + * + * This callback is called whenever a new incoming connection requires + * authorization. + * + * @param info The ISO accept information structure + * @param chan Pointer to receive the allocated channel + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*accept)(const struct bt_iso_accept_info *info, + struct bt_iso_chan **chan); +}; + +/** + * @brief Lookup a bt_iso_chan object by its @ref bt_iso_chan.iso reference + * + * This is useful to get the corresponding bt_iso_chan object when using e.g. + * bt_conn_foreach. + * + * @param iso A connection object with type @ref BT_CONN_TYPE_ISO + * @return The corresponding bt_iso_chan object or NULL. + */ + +struct bt_iso_chan *bt_iso_get_chan_by_conn(const struct bt_conn *iso); + +/** + * @brief Register ISO server. + * + * Register ISO server, each new connection is authorized using the accept() + * callback which in case of success shall allocate the channel structure to + * be used by the new connection. + * + * @param server Server structure. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_iso_server_register(struct bt_iso_server *server); + +/** + * @brief Unregister ISO server. + * + * Unregister previously registered ISO server. + * + * @param server Server structure. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_iso_server_unregister(struct bt_iso_server *server); + +/** + * @brief Creates a CIG as a central + * + * This can called at any time, even before connecting to a remote device. + * This must be called before any connected isochronous stream (CIS) channel + * can be connected. + * + * Once a CIG is created, the channels supplied in the @p param can be + * connected using bt_iso_chan_connect(). + * + * @param[in] param The parameters used to create and enable the CIG. + * @param[out] out_cig Connected Isochronous Group object on success. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_iso_cig_create(const struct bt_iso_cig_param *param, + struct bt_iso_cig **out_cig); + +/** + * @brief Reconfigure a CIG as a central + * + * This function can be used to update a CIG. It will update the group + * specific parameters, and, if supplied, change the QoS parameters of the + * individual CIS. If the cis_channels in @p param contains CIS that was not + * originally in the call to bt_iso_cig_create(), these will be added to the + * group. It is not possible to remove any CIS from the group after creation. + * + * This can be called at any time before connecting an ISO to a remote + * device. Once any CIS in the group has connected, the group cannot be + * changed. + * + * Once a CIG is created, the channels supplied in the @p param can be + * connected using bt_iso_chan_connect(). + * + * @param cig Connected Isochronous Group object. + * @param param The parameters used to reconfigure the CIG. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_iso_cig_reconfigure(struct bt_iso_cig *cig, + const struct bt_iso_cig_param *param); + +/** + * @brief Terminates a CIG as a central + * + * All the CIS in the CIG shall be disconnected first. + * + * @param cig Pointer to the CIG structure. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_iso_cig_terminate(struct bt_iso_cig *cig); + +/** + * @brief Connect ISO channels on ACL connections + * + * Connect ISO channels. The ISO channels must have been initialized in a CIG + * first by calling bt_iso_cig_create(). + * + * Once the connection is completed the channels' connected() callback will + * be called. If the connection is rejected disconnected() callback is called + * instead. + * + * This function will also setup the ISO data path based on the @p path + * parameter of the @ref bt_iso_chan_io_qos for each channel. + * + * @param param Pointer to a connect parameter array with the ISO and ACL + * pointers. + * @param count Number of connect parameters. + * + * @retval 0 Successfully started the connecting procedure. + * + * @retval -EINVAL Invalid parameters were supplied. + * + * @retval -EBUSY Some ISO channels are already being connected. + * It is not possible to have multiple outstanding connection requests. May + * also be returned if @kconfig{CONFIG_BT_SMP} is enabled and a pairing + * procedure is already in progress. + * + * @retval -ENOBUFS Not buffers available to send request to controller or if + * @kconfig{CONFIG_BT_SMP} is enabled and no more keys could be stored. + * + * @retval -ENOMEM If @kconfig{CONFIG_BT_SMP} is enabled and no more keys + * could be stored. + * + * @retval -EIO Controller rejected the request or if @kconfig{CONFIG_BT_SMP} + * is enabled and pairing has timed out. + * + * @retval -ENOTCONN If @kconfig{CONFIG_BT_SMP} is enabled the ACL is not + * connected. + */ + +int bt_iso_chan_connect(const struct bt_iso_connect_param *param, + size_t count); + +/** + * @brief Disconnect connected ISO channel + * + * Disconnect connected ISO channel. + * + * If the device is a central and the connection is pending it will be + * canceled and as a result the channel bt_iso_chan_ops.disconnected() + * callback is called. + * + * If the device is a peripheral and the connection is pending it will be + * rejected, as a peripheral shall wait for a CIS Established event (which + * may trigger a bt_iso_chan_ops.disconnected() callback in case of an + * error). + * + * Regarding to input parameter, to get details see reference description to + * bt_iso_chan_connect() API. + * + * @param chan Channel object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_iso_chan_disconnect(struct bt_iso_chan *chan); + +/** + * @brief Send data to ISO channel without timestamp + * + * Send data from buffer to the channel. If credits are not available, buf + * will be queued and sent as and when credits are received from peer. + * Regarding to first input parameter, to get details see reference + * description to bt_iso_chan_connect() API. + * + * @note Buffer ownership is transferred to the stack in case of success, in + * case of an error the caller retains the ownership of the buffer. + * + * @param chan Channel object. + * @param buf Buffer containing data to be sent. + * @param seq_num Packet Sequence number. This value shall be incremented + * for each call to this function and at least once per SDU + * interval for a specific channel. + * + * @return Number of octets sent in case of success or negative value in case + * of error. + */ + +int bt_iso_chan_send(struct bt_iso_chan *chan, + struct net_buf *buf, uint16_t seq_num); + +/** + * @brief Send data to ISO channel with timestamp + * + * Send data from buffer to the channel. If credits are not available, buf + * will be queued and sent as and when credits are received from peer. + * Regarding to first input parameter, to get details see reference + * description to bt_iso_chan_connect() API. + * + * @note Buffer ownership is transferred to the stack in case of success, in + * case of an error the caller retains the ownership of the buffer. + * + * @param chan Channel object. + * @param buf Buffer containing data to be sent. + * @param seq_num Packet Sequence number. This value shall be incremented + * for each call to this function and at least once per SDU + * interval for a specific channel. + * @param ts Timestamp of the SDU in microseconds (us). This value can + * be used to transmit multiple SDUs in the same SDU interval + * in a CIG or BIG. + * + * @return Number of octets sent in case of success or negative value in case + * of error. + */ + +int bt_iso_chan_send_ts( + struct bt_iso_chan *chan, struct net_buf *buf, uint16_t seq_num, + uint32_t ts); + +/** + * @brief Sets up the ISO data path for a ISO channel + * + * The channel must be associated with a BIS or CIS handle first which it is + * when the bt_iso_chan_ops.connected() callback is called. + * + * @param chan The channel to setup the ISO data path for + * @param dir The direction to setup, either @ref + * BT_HCI_DATAPATH_DIR_CTLR_TO_HOST or + * @ref BT_HCI_DATAPATH_DIR_HOST_TO_CTLR. For ISO broadcast + * channels this can only be + * @ref BT_HCI_DATAPATH_DIR_HOST_TO_CTLR, and for ISO sync + * receiver channels this can only be @ref BT_HCI_DATAPATH_DIR_CTLR_TO_HOST. + * @param path The data path + * + * @retval 0 Success + * @retval -EINVAL Invalid parameters + * @retval -ENOBUFS No HCI command buffer could be allocated + * @retval -EIO The controller rejected the request or response contains + * invalid data + * @retval -ENODEV @p chan is not associated with a CIS or BIS handle + * @retval -EACCES The controller rejected the request as disallowed + * @retval -ENOEXEC Unexpected error occurred + */ + +int bt_iso_setup_data_path(const struct bt_iso_chan *chan, uint8_t dir, + const struct bt_iso_chan_path *path); + +/** + * @brief Removes the ISO data path for a ISO channel + * + * Removes the ISO data path configured by bt_iso_setup_data_path() for the + * provided @p dir. + * + * The data paths of CIS for Peripherals are deleted by the controller, and + * thus it is not necessary (or possible) to remove data paths of CIS after + * they have disconnected for a Peripheral, as per Bluetooth Core + * specification 6.0, Vol 4, Part E, Section 7.7.5. The data paths for CIS + * for a Central remain valid, even after a disconnection, and thus a + * Central + * device should call bt_iso_remove_data_path() on disconnect if it no longer + * wants to use that + * CIS. + * All data paths created by a Central are removed when the CIG is removed + * with bt_iso_cig_terminate(). + * + * Any data paths associated with an ISO Sync Receiver BIG are removed by the + * controller when the BIG sync is lost or terminated, and thus it is not + * necessary (or possible) to remove data paths of ISO channels associated + * with a BIG for a Sync Receiver, as per Bluetooth Core specification 6.0, + * Vol 4, Part E, Section 7.7.65.30 + * + * All data paths associated with an ISO Broadcaster BIG are removed when the + * BIG is terminated by bt_iso_big_terminate(), and thus it is not necessary + * (or possible) to remove data paths of ISO channels associated with a BIG + * for a Broadcaster, as per Bluetooth Core specification 6.0, Vol 4, Part E, + * Section 7.8.105 + * + * @param chan The channel to setup the ISO data path for + * @param dir The direction to setup, either @ref + * BT_HCI_DATAPATH_DIR_CTLR_TO_HOST or + * @ref BT_HCI_DATAPATH_DIR_HOST_TO_CTLR. For ISO broadcast + * channels this can only be + * @ref BT_HCI_DATAPATH_DIR_HOST_TO_CTLR, and for ISO sync + * receiver channels this can only be @ref BT_HCI_DATAPATH_DIR_CTLR_TO_HOST. + + * @retval 0 Success + * @retval -EINVAL Invalid parameters + * @retval -ENOBUFS No HCI command buffer could be allocated + * @retval -EIO The controller rejected the request or response contains + * invalid data + * @retval -ENODEV @p chan is not associated with a CIS or BIS handle + * @retval -EACCES The controller rejected the request as disallowed + * @retval -ENOEXEC Unexpected error occurred + */ + +int bt_iso_remove_data_path(const struct bt_iso_chan *chan, uint8_t dir); + +/** @brief ISO Unicast TX Info Structure */ + +struct bt_iso_unicast_tx_info +{ + /** The transport latency in us */ + + uint32_t latency; + + /** The flush timeout (N * 1.25 ms) */ + + uint32_t flush_timeout; + + /** The maximum PDU size in octets */ + + uint16_t max_pdu; + + /** The transport PHY */ + + uint8_t phy; + + /** The burst number */ + + uint8_t bn; + + /** The maximum SDU size in octets + * + * May be set to @ref bt_iso_unicast_tx_info.max_pdu for peripherals if + * unknown + */ + + uint16_t max_sdu; + + /** The SDU interval in microseconds + * + * May be set to @ref BT_ISO_SDU_INTERVAL_UNKNOWN for if unknown. + */ + + uint32_t sdu_interval; +}; + +/** @brief ISO Unicast Info Structure */ + +struct bt_iso_unicast_info +{ + /** Connected Isochronous Group ID */ + + uint8_t cig_id; + + /** Connected Isochronous Stream ID */ + + uint8_t cis_id; + + /** The maximum time in us for all PDUs of all CIS in a CIG event */ + + uint32_t cig_sync_delay; + + /** The maximum time in us for all PDUs of this CIS in a CIG event */ + + uint32_t cis_sync_delay; + + /* @brief The subinterval in microseconds + * + * Will be @ref BT_ISO_SUBINTERVAL_NONE if there is no subinterval (NSE = + * 1). Will be @ref BT_ISO_SUBINTERVAL_UNKNOWN if unknown. + */ + + uint32_t subinterval; + + /** @brief TX information for the central to peripheral data path */ + + struct bt_iso_unicast_tx_info central; + + /** TX information for the peripheral to central data */ + + struct bt_iso_unicast_tx_info peripheral; +}; + +/** @brief ISO Broadcaster Info Structure */ + +struct bt_iso_broadcaster_info +{ + /** Broadcast Isochronous Group Handle */ + + uint8_t big_handle; + + /** Broadcast Isochronous Stream number */ + + uint8_t bis_number; + + /** The maximum time in us for all PDUs of all BIS in a BIG event */ + + uint32_t sync_delay; + + /** The transport latency in us */ + + uint32_t latency; + + /** Pre-transmission offset (N * 1.25 ms) */ + + uint32_t pto; + + /** The maximum PDU size in octets */ + + uint16_t max_pdu; + + /** The transport PHY */ + + uint8_t phy; + + /** The burst number */ + + uint8_t bn; + + /** Number of times a payload is transmitted in a BIS event */ + + uint8_t irc; +}; + +/** @brief ISO Synchronized Receiver Info Structure */ + +struct bt_iso_sync_receiver_info +{ + /** Broadcast Isochronous Group handle */ + + uint8_t big_handle; + + /** Broadcast Isochronous Stream number */ + + uint8_t bis_number; + + /** The transport latency in us */ + + uint32_t latency; + + /** Pre-transmission offset (N * 1.25 ms) */ + + uint32_t pto; + + /** The maximum PDU size in octets */ + + uint16_t max_pdu; + + /** The burst number */ + + uint8_t bn; + + /** Number of times a payload is transmitted in a BIS event */ + + uint8_t irc; +}; + +/** ISO channel Info Structure */ + +struct bt_iso_info +{ + /** Channel Type. */ + + enum bt_iso_chan_type type; + + /** The ISO interval (N * 1.25 ms) */ + + uint16_t iso_interval; + + /** The maximum number of subevents in each ISO event */ + + uint8_t max_subevent; + + /* @brief True if the channel is able to send data + * + * This is always true when @p type is @ref BT_ISO_CHAN_TYPE_BROADCASTER, + * and never true when @p type is @ref BT_ISO_CHAN_TYPE_SYNC_RECEIVER. + */ + + bool can_send; + + /* @brief True if the channel is able to recv data + * + * This is always true when @p type is @ref BT_ISO_CHAN_TYPE_SYNC_RECEIVER, + * and never true when @p type is @ref BT_ISO_CHAN_TYPE_BROADCASTER. + */ + + bool can_recv; + + /** Connection Type specific Info. */ + + union + { +#if defined(CONFIG_BT_ISO_UNICAST) || defined(__DOXYGEN__) + /** + * @brief Unicast specific Info. + * + * Only available when @kconfig{CONFIG_BT_ISO_UNICAST} is enabled. Use + * this when the @ref bt_iso_info.type is @ref BT_ISO_CHAN_TYPE_CENTRAL + * or + * @ref BT_ISO_CHAN_TYPE_PERIPHERAL. + */ + + struct bt_iso_unicast_info unicast; +#endif /* CONFIG_BT_ISO_UNICAST */ +#if defined(CONFIG_BT_ISO_BROADCASTER) || defined(__DOXYGEN__) + /** + * @brief Broadcaster specific Info. + * + * Only available when @kconfig{CONFIG_BT_ISO_BROADCASTER} is enabled. + * Use this when the @ref bt_iso_info.type is @ref + * BT_ISO_CHAN_TYPE_BROADCASTER. + */ + + struct bt_iso_broadcaster_info broadcaster; +#endif /* CONFIG_BT_ISO_BROADCASTER */ +#if defined(CONFIG_BT_ISO_SYNC_RECEIVER) || defined(__DOXYGEN__) + /** + * @brief Sync receiver specific Info. + * + * Only available when @kconfig{CONFIG_BT_ISO_SYNC_RECEIVER} is enabled. + * Use this when the @ref bt_iso_info.type is @ref + * BT_ISO_CHAN_TYPE_SYNC_RECEIVER. + */ + + struct bt_iso_sync_receiver_info sync_receiver; +#endif /* CONFIG_BT_ISO_SYNC_RECEIVER */ + }; +}; + +/** + * @brief Get ISO channel info + * + * @param chan Channel object. + * @param info Channel info object. + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_iso_chan_get_info(const struct bt_iso_chan *chan, + struct bt_iso_info *info); + +/** + * @brief Get ISO transmission timing info + * + * @details Reads timing information for transmitted ISO packet on an ISO + * channel. The HCI_LE_Read_ISO_TX_Sync HCI command is used to retrieve this + * information from the controller. + * + * @note An SDU must have already been successfully transmitted on the ISO + * channel for this function to return successfully. + * + * @param[in] chan Channel object. + * @param[out] info Transmit info object. + * + * @return Zero on success or (negative) error code on failure. + */ + +int bt_iso_chan_get_tx_sync(const struct bt_iso_chan *chan, + struct bt_iso_tx_info *info); + +/** + * @brief Struct to hold the Broadcast Isochronous Group callbacks + * + * These can be registered for usage with bt_iso_big_register_cb(). + */ + +struct bt_iso_big_cb +{ + /* @brief The BIG has started and all of the streams are ready for data + * + * @param big The started BIG + */ + + void (*started)(struct bt_iso_big *big); + + /* @brief The BIG has stopped and none of the streams are ready for data + * + * @param big The stopped BIG + * @param reason The reason why the BIG stopped (see the BT_HCI_ERR_* + * values) + */ + + void (*stopped)(struct bt_iso_big *big, uint8_t reason); + + /** @internal Internally used field for list handling */ + + struct list_node _node; +}; + +/** + * @brief Registers callbacks for Broadcast Sources + * + * @param cb Pointer to the callback structure. + * + * @retval 0 on success + * @retval -EINVAL if @p cb is NULL + * @retval -EEXIST if @p cb is already registered + */ + +int bt_iso_big_register_cb(struct bt_iso_big_cb *cb); + +/** + * @brief Creates a BIG as a broadcaster + * + * @param[in] padv Pointer to the periodic advertising object the + * BIGInfo shall be sent on. + * @param[in] param The parameters used to create and enable the BIG. The + * QOS parameters are determined by the QOS field of the + * first BIS in the BIS list of this parameter. + * @param[out] out_big Broadcast Isochronous Group object on success. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_iso_big_create( + struct bt_le_ext_adv *padv, struct bt_iso_big_create_param *param, + struct bt_iso_big **out_big); + +/** + * @brief Terminates a BIG as a broadcaster or receiver + * + * This function cannot be called while in @ref bt_iso_big_cb.started, @ref + * bt_iso_big_cb.stopped, + * @ref bt_iso_chan_ops.connected or @ref bt_iso_chan_ops.disconnected + * callbacks. + * + * @param big Pointer to the BIG structure. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_iso_big_terminate(struct bt_iso_big *big); + +/** + * @brief Creates a BIG as a receiver + * + * @param[in] sync Pointer to the periodic advertising sync object the + * BIGInfo was received on. + * @param[in] param The parameters used to create and enable the BIG sync. + * @param[out] out_big Broadcast Isochronous Group object on success. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_iso_big_sync( + struct bt_le_per_adv_sync *sync, struct bt_iso_big_sync_param *param, + struct bt_iso_big **out_big); + +/** + * @brief Returns a string representation of an ISO channel state + * + * @param state The state of the channel + * @return A string representation, or "unknown" if unknown state. + */ + +const char *bt_iso_chan_state_str(enum bt_iso_state state); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_ISO_H */ diff --git a/include/nuttx/net/bluetooth/l2cap.h b/include/nuttx/net/bluetooth/l2cap.h new file mode 100644 index 0000000000000..e9cb5df4142ed --- /dev/null +++ b/include/nuttx/net/bluetooth/l2cap.h @@ -0,0 +1,1311 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/l2cap.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Bluetooth L2CAP handling + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * Copyright (c) 2023 Nordic Semiconductor + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_L2CAP_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_L2CAP_H + +/** + * @brief L2CAP + * @defgroup bt_l2cap L2CAP + * @since 1.0 + * @version 1.0.0 + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** L2CAP PDU header size, used for buffer size calculations */ +#define BT_L2CAP_HDR_SIZE 4 + +/** Maximum Transmission Unit (MTU) for an outgoing L2CAP PDU. */ +#define BT_L2CAP_TX_MTU (CONFIG_BT_L2CAP_TX_MTU) + +/** Maximum Transmission Unit (MTU) for an incoming L2CAP PDU. */ +#define BT_L2CAP_RX_MTU (CONFIG_BT_BUF_ACL_RX_SIZE - BT_L2CAP_HDR_SIZE) + +/** @brief Helper to calculate needed buffer size for L2CAP PDUs. + * Useful for creating buffer pools. + * + * @param mtu Needed L2CAP PDU MTU. + * + * @return Needed buffer size to match the requested L2CAP PDU MTU. + */ +#define BT_L2CAP_BUF_SIZE(mtu) BT_BUF_ACL_SIZE(BT_L2CAP_HDR_SIZE + (mtu)) + +/** L2CAP SDU header size, used for buffer size calculations */ +#define BT_L2CAP_SDU_HDR_SIZE 2 + +/** @brief Maximum Transmission Unit for an unsegmented outgoing L2CAP SDU. + * + * The Maximum Transmission Unit for an outgoing L2CAP SDU when sent without + * segmentation, i.e. a single L2CAP SDU will fit inside a single L2CAP PDU. + * + * The MTU for outgoing L2CAP SDUs with segmentation is defined by the size + * of the application buffer pool. + */ +#define BT_L2CAP_SDU_TX_MTU (BT_L2CAP_TX_MTU - BT_L2CAP_SDU_HDR_SIZE) + +/** @brief Maximum Transmission Unit for an unsegmented incoming L2CAP SDU. + * + * The Maximum Transmission Unit for an incoming L2CAP SDU when sent without + * segmentation, i.e. a single L2CAP SDU will fit inside a single L2CAP PDU. + * + * The MTU for incoming L2CAP SDUs with segmentation is defined by the size + * of the application buffer pool. The application will have to define an + * alloc_buf callback for the channel in order to support receiving + * segmented L2CAP SDUs. + */ +#define BT_L2CAP_SDU_RX_MTU (BT_L2CAP_RX_MTU - BT_L2CAP_SDU_HDR_SIZE) + +/* @brief Helper to calculate needed buffer size for L2CAP SDUs. + * Useful for creating buffer pools. + * + * @param mtu Required BT_L2CAP_*_SDU. + * + * @return Needed buffer size to match the requested L2CAP SDU MTU. + */ +#define BT_L2CAP_SDU_BUF_SIZE(mtu) BT_L2CAP_BUF_SIZE(BT_L2CAP_SDU_HDR_SIZE + (mtu)) + +/** @brief L2CAP ECRED minimum MTU + * + * The minimum MTU for an L2CAP Enhanced Credit Based Connection. + * + * This requirement is inferred from text in Core 3.A.4.25 v6.0: + * + * L2CAP implementations shall support a minimum MTU size of 64 octets for + * these channels. + */ +#define BT_L2CAP_ECRED_MIN_MTU 64 + +/** @brief L2CAP ECRED minimum MPS + * + * The minimum MPS for an L2CAP Enhanced Credit Based Connection. + * + * This requirement is inferred from text in Core 3.A.4.25 v6.0: + * + * L2CAP implementations shall support a minimum MPS of 64 and may support + * an MPS up to 65533 octets for these channels. + */ +#define BT_L2CAP_ECRED_MIN_MPS 64 + +/** @brief L2CAP maximum MTU + * + * The maximum MTU for an L2CAP Based Connection. This is the same with or + * without ECRED. This requirement is taken from text in Core 3.A.4.22 and + * 3.A.4.26 v6.0. + */ +#define BT_L2CAP_MAX_MTU UINT16_MAX + +/** @brief L2CAP maximum MPS + * + * The maximum MPS for an L2CAP Based Connection. This is the same with or + * without ECRED. This requirement is taken from text in Core 3.A.4.22 and + * 3.A.4.26 v6.0. + */ +#define BT_L2CAP_MAX_MPS 65533 + +/** @brief The maximum number of channels in ECRED L2CAP signaling PDUs + * + * Currently, this is the maximum number of channels referred to in the + * following PDUs: + * - L2CAP_CREDIT_BASED_CONNECTION_REQ + * - L2CAP_CREDIT_BASED_RECONFIGURE_REQ + * + * @warning The commonality is inferred between the PDUs. The Bluetooth + * specification treats these as separate numbers and does now guarantee the + * same limit for potential future ECRED L2CAP signaling PDUs. + */ +#define BT_L2CAP_ECRED_CHAN_MAX_PER_REQ 5 + +/** + * @defgroup l2cap_cid L2CAP channel identifiers + * @ingroup bt_l2cap + * @{ + * + * @brief Standard L2CAP channel identifiers + */ + +/** @brief BR signaling channel */ +#define BT_L2CAP_CID_BR_SIG 0x0001 +/** @brief Attribute Protocol channel */ +#define BT_L2CAP_CID_ATT 0x0004 +/** @brief LE signaling channel */ +#define BT_L2CAP_CID_LE_SIG 0x0005 +/** @brief Security Manager Protocol channel */ +#define BT_L2CAP_CID_SMP 0x0006 +/** @brief BR Security Manager Protocol channel */ +#define BT_L2CAP_CID_BR_SMP 0x0007 + +/** @} */ + +struct bt_l2cap_chan; + +/** @typedef bt_l2cap_chan_destroy_t + * @brief Channel destroy callback + * + * @param chan Channel object. + */ + +typedef void (*bt_l2cap_chan_destroy_t)(struct bt_l2cap_chan *chan); + +/** @brief Life-span states of L2CAP CoC channel. + * + * Used only by internal APIs dealing with setting channel to proper state + * depending on operational context. + * + * A channel enters the @ref BT_L2CAP_CONNECTING state upon @ref + * bt_l2cap_chan_connect, @ref bt_l2cap_ecred_chan_connect or upon returning + * from @ref bt_l2cap_server.accept. + * + * When a channel leaves the @ref BT_L2CAP_CONNECTING state, @ref + * bt_l2cap_chan_ops.connected is called. + */ + +typedef enum bt_l2cap_chan_state +{ + /** Channel disconnected */ + + BT_L2CAP_DISCONNECTED, + + /** Channel in connecting state */ + + BT_L2CAP_CONNECTING, + + /** Channel in config state, BR/EDR specific */ + + BT_L2CAP_CONFIG, + + /** Channel ready for upper layer traffic on it */ + + BT_L2CAP_CONNECTED, + + /** Channel in disconnecting state */ + + BT_L2CAP_DISCONNECTING, +} +__packed bt_l2cap_chan_state_t; + +/** @brief Status of L2CAP channel. */ + +typedef enum bt_l2cap_chan_status +{ + /** Channel can send at least one PDU */ + + BT_L2CAP_STATUS_OUT, + + /** @brief Channel shutdown status + * + * Once this status is notified it means the channel will no longer be able + * to transmit or receive data. + */ + + BT_L2CAP_STATUS_SHUTDOWN, + + /** @brief Channel encryption pending status */ + + BT_L2CAP_STATUS_ENCRYPT_PENDING, + + /* Total number of status - must be at the end of the enum */ + + BT_L2CAP_NUM_STATUS, +} +__packed bt_l2cap_chan_status_t; + +/** @brief L2CAP Channel structure. */ + +struct bt_l2cap_chan +{ + /** Channel connection reference */ + + struct bt_conn *conn; + + /** Channel operations reference */ + + const struct bt_l2cap_chan_ops *ops; + struct list_node node; + bt_l2cap_chan_destroy_t destroy; + + atomic_define(status, BT_L2CAP_NUM_STATUS); +}; + +/** @brief LE L2CAP Endpoint structure. */ + +struct bt_l2cap_le_endpoint +{ + /** Endpoint Channel Identifier (CID) */ + + uint16_t cid; + + /** Endpoint Maximum Transmission Unit */ + + uint16_t mtu; + + /** Endpoint Maximum PDU payload Size */ + + uint16_t mps; + + /** Endpoint credits */ + + atomic_t credits; +}; + +/** @brief LE L2CAP Channel structure. */ + +struct bt_l2cap_le_chan +{ + /** Common L2CAP channel reference object */ + + struct bt_l2cap_chan chan; + /** @brief Channel Receiving Endpoint. + * + * If the application has set an alloc_buf channel callback for the + * channel to support receiving segmented L2CAP SDUs the application + * should initialize the MTU of the Receiving Endpoint. Otherwise the MTU + * of the receiving endpoint will be initialized to + * @ref BT_L2CAP_SDU_RX_MTU by the stack. + * + * This is the source of the MTU, MPS and credit values when sending + * L2CAP_LE_CREDIT_BASED_CONNECTION_REQ/RSP and L2CAP_CONFIGURATION_REQ. + */ + + struct bt_l2cap_le_endpoint rx; + + /** Pending RX MTU on ECFC reconfigure, used internally by stack */ + + uint16_t pending_rx_mtu; + + /** Channel Transmission Endpoint. + * + * This is an image of the remote's rx. + * + * The MTU and MPS is controlled by the remote by + * L2CAP_LE_CREDIT_BASED_CONNECTION_REQ/RSP or L2CAP_CONFIGURATION_REQ. + */ + + struct bt_l2cap_le_endpoint tx; + /** Channel Transmission queue + * + * Internal + * + * SDUs/PDUs given to @ref bt_l2cap_chan_send and @c bt_l2cap_send_pdu are + * stored here until they are sent to the Controller. + * + * The SDU header is prepended to SDUs before they are stored here. The + * head of this list (the next data to be sent) may be just the remaining + * part of an already partially transmitted SDU/PDU due to L2CAP + * segmentation and fragmentation. + * + * This is the outbox for a single channel. Channels may be serviced in any + * order. The transmission order does not follow the sequence of + * @ref bt_l2cap_chan_send calls across channels. + * + * There may be more data here than the channel currently has credits for. + * The transmission will wait until credits are available. + * + * Callbacks given to @ref bt_l2cap_chan_send are stored in the user_data + * of the buffer. These callbacks must be invoked when the Controller gives + * a Number of Buffers Complete Event for the last L2CAP PDU of the buffer + * or when the channel is disconnected. + */ + + struct k_fifo tx_queue; +#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) + /** Segment SDU packet from upper layer */ + + struct net_buf *_sdu; + uint16_t _sdu_len; +#if defined(CONFIG_BT_L2CAP_SEG_RECV) + uint16_t _sdu_len_done; +#endif /* CONFIG_BT_L2CAP_SEG_RECV */ + + struct k_work rx_work; + struct k_fifo rx_queue; + + bt_l2cap_chan_state_t state; + + /** Remote PSM to be connected */ + + uint16_t psm; + + /** Helps match request context during CoC */ + + uint8_t ident; + + /** Opcode of the pending request. Used to match responses with requests. */ + + uint8_t pending_req; + bt_security_t required_sec_level; + + /* Response Timeout eXpired (RTX) timer */ + + struct k_work_delayable rtx_work; + struct k_work_sync rtx_sync; +#endif + + /** @internal To be used with @ref bt_conn.upper_data_ready */ + + struct list_node _pdu_ready; + + /** @internal Holds the length of the current PDU/segment */ + + size_t _pdu_remaining; +}; + +/** + * @brief Helper macro getting container object of type bt_l2cap_le_chan + * address having the same container chan member address as object in + * question. + * + * @param _ch Address of object of bt_l2cap_chan type + * + * @return Address of in memory bt_l2cap_le_chan object type containing + * the address of in question object. + */ +#define BT_L2CAP_LE_CHAN(_ch) CONTAINER_OF(_ch, struct bt_l2cap_le_chan, chan) + +/** @brief Fixed L2CAP Channel structure. Should be defined using the + * @ref BT_L2CAP_FIXED_CHANNEL_DEFINE macro. + */ + +struct bt_l2cap_fixed_chan +{ + /** @brief Channel Identifier (CID) + * + * @note Shall be in the range 0x0001 to 0x003F (Core 3.A.2.1 v6.0). The + * CIDs in this range are reserved by the stack, so the application needs + * to make sure that the CID is not already in use. Table 2.1 in Core + * 3.A.2.1 and + * @ref l2cap_cid shows the CIDs used by the stack. + */ + + uint16_t cid; + + /** @brief Channel accept callback + * + * This callback needs to be provided by the application, and is invoked + * when a new connection has been established. If accepting the + * connection, the user is expected to allocate memory for a + * zero-initialized object of type @ref bt_l2cap_le_chan, and update the + * channel reference @p chan to point to the @ref bt_l2cap_le_chan.chan + * member of the allocated object. The channel should be initialized by + * assigning the callbacks to the + * @ref bt_l2cap_chan_ops field as follows: + * @code static int accept(struct bt_conn *conn, struct bt_l2cap_chan + * **chan) + * + * { + * // Allocation of fixed_chan and definition of the ops are assumed done + * prior. fixed_chan = (struct bt_l2cap_le_chan) + * { + * .chan.ops = &ops, }; + * + * *chan = &fixed_chan.chan; + * + * return 0; } @endcode The allocated context needs to be valid for the + * lifetime of the channel, i. e. freeing of the memory can be done in the + * @ref bt_l2cap_chan_ops.released callback. + * + * @warning Even though the reference passed back through @p chan is a + * @ref bt_l2cap_chan, it must be the @ref bt_l2cap_le_chan.chan + * member of a + * @ref bt_l2cap_le_chan object. The stack uses the containing + * object, so returning a bare @ref bt_l2cap_chan would result in + * out-of-bounds access. + * + * @param conn The connection that has been established. + * @param chan L2CAP channel reference. + * + * @return 0 to accept the channel connection, negative error code + * otherwise. + */ + + int (*accept)(struct bt_conn *conn, struct bt_l2cap_chan **chan); +}; + +/** + * @brief Register a fixed L2CAP channel. + * + * Define and register a fixed L2CAP channel of type @ref + * bt_l2cap_fixed_chan. + * + * @note Fixed L2CAP channels are according to the Core Specification + * (3.A.2.1 v6.0) reserved for the stack, but this allows the application to + * define their own, for instance if there are memory constraints and + * dynamic channels are not suitable. The application needs to make sure + * that the CID for the fixed channel is not already in use. + * + * @param _name Name of the fixed L2CAP channel. The channels are sorted + * lexicographically, and will be initialized in the same + * order. + */ +#define BT_L2CAP_FIXED_CHANNEL_DEFINE(_name) \ +const STRUCT_SECTION_ITERABLE(bt_l2cap_fixed_chan, _CONCAT(bt_l2cap_fixed_chan, _name)) + +/** L2CAP Endpoint Link Mode. Basic mode. */ +#define BT_L2CAP_BR_LINK_MODE_BASIC 0x00 +/** L2CAP Endpoint Link Mode. Retransmission mode. */ +#define BT_L2CAP_BR_LINK_MODE_RET 0x01 +/** L2CAP Endpoint Link Mode. Flow control mode. */ +#define BT_L2CAP_BR_LINK_MODE_FC 0x02 +/** L2CAP Endpoint Link Mode. Enhance retransmission mode. */ +#define BT_L2CAP_BR_LINK_MODE_ERET 0x03 +/** L2CAP Endpoint Link Mode. Streaming mode. */ +#define BT_L2CAP_BR_LINK_MODE_STREAM 0x04 + +/** Frame Check Sequence type. No FCS. */ +#define BT_L2CAP_BR_FCS_NO 0x00 +/** Frame Check Sequence type. 16-bit FCS. */ +#define BT_L2CAP_BR_FCS_16BIT 0x01 + +/** @brief BREDR L2CAP Endpoint structure. */ + +struct bt_l2cap_br_endpoint +{ + /** Endpoint Channel Identifier (CID) */ + + uint16_t cid; + + /** Endpoint Maximum Transmission Unit */ + + uint16_t mtu; +#if defined(CONFIG_BT_L2CAP_RET_FC) || defined(__DOXYGEN__) + /** Endpoint Link Mode. The value is defined as BT_L2CAP_BR_LINK_MODE_* */ + + uint8_t mode; + /** Whether Endpoint Link Mode is optional + * If the `optional` is true, the `mode` could be changed according to the + * extended feature and peer configuration from L2CAP configuration + * response and request. Otherwise, if the channel configuration process + * does not meet the set mode, the L2CAP channel will be disconnected. + */ + + bool optional; + /** Endpoint Maximum Transmit + * The field is used to set the max retransmission count. For `RET`, `FC`, + * and `ERET`, it should be not less 1. For `STREAM`, it should be 0. + */ + + uint8_t max_transmit; + /** Endpoint Retransmission Timeout + * The field is configured by @kconfig{CONFIG_BT_L2CAP_BR_RET_TIMEOUT} The + * field should be no more than the field `monitor_timeout`. + */ + + uint16_t ret_timeout; + /** Endpoint Monitor Timeout + * The field is configured by @kconfig{CONFIG_BT_L2CAP_BR_MONITOR_TIMEOUT} + */ + + uint16_t monitor_timeout; + + /** Endpoint Maximum PDU payload Size */ + + uint16_t mps; + /** Endpoint Maximum Window Size + * MAX supported window size is configured by + * @kconfig{CONFIG_BT_L2CAP_MAX_WINDOW_SIZE}. The field should be no more + * than @kconfig{CONFIG_BT_L2CAP_MAX_WINDOW_SIZE}. + */ + + uint16_t max_window; + /** Endpoint FCS Type + * The value is defined as BT_L2CAP_BR_FCS_* The default setting should be + * BT_L2CAP_BR_FCS_16BIT. For FC and RET, the FCS type should be + * BT_L2CAP_BR_FCS_16BIT. For ERET and STREAM, the FCS type is optional. If + * the field is not default value, the local will include FCS option in + * configuration request packet if both side support `FCS Option`. + */ + + uint8_t fcs; + /** Endpoint Extended Control. + * If this field is true, and both side support `Extended Window size + * feature`, the local will include `extended window size` option in + * configuration request packet. + */ + + bool extended_control; +#endif /* CONFIG_BT_L2CAP_RET_FC */ +}; + +/** I-Frame transmission window for none `BASIC` mode L2cap connected + * channel. + */ + +struct bt_l2cap_br_window +{ + struct list_node node; + + /** tx seq */ + + uint16_t tx_seq; + + /** data len */ + + uint16_t len; + + /** data address */ + + uint8_t *data; + + /** Transmit Counter */ + + uint8_t transmit_counter; + + /** SAR flag */ + + uint8_t sar; + + /** srej flag */ + + bool srej; + + /** retransmit flag */ + + bool retransmit; + + /* Save PDU state */ + + struct net_buf_simple_state sdu_state; + + /** @internal Holds the sending buffer. */ + + struct net_buf *sdu; +}; + +/** @brief BREDR L2CAP Channel structure. */ + +struct bt_l2cap_br_chan +{ + /** Common L2CAP channel reference object */ + + struct bt_l2cap_chan chan; + + /** Channel Receiving Endpoint */ + + struct bt_l2cap_br_endpoint rx; + + /** Channel Transmission Endpoint */ + + struct bt_l2cap_br_endpoint tx; + + /* For internal use only */ + + atomic_t flags[1]; + + bt_l2cap_chan_state_t state; + + /** Remote PSM to be connected */ + + uint16_t psm; + + /** Helps match request context during CoC */ + + uint8_t ident; + bt_security_t required_sec_level; + + /* Response Timeout eXpired (RTX) timer */ + + struct k_work_delayable rtx_work; + struct k_work_sync rtx_sync; + + /** @internal To be used with @ref bt_conn.upper_data_ready */ + + struct list_node _pdu_ready; + + /** @internal To be used with @ref bt_conn.upper_data_ready */ + + atomic_t _pdu_ready_lock; + + /** @internal List of net bufs not yet sent to lower layer */ + + struct list_node _pdu_tx_queue; + +#if defined(CONFIG_BT_L2CAP_RET_FC) || defined(__DOXYGEN__) + /** @internal Holds the remaining length of current sending buffer */ + + size_t _pdu_remaining; + + /** @internal Holds the sending buffer. */ + + struct net_buf *_pdu_buf; + + /** @internal TX windows for outstanding frame */ + + struct list_node _pdu_outstanding; + + /** @internal PDU restore state */ + + struct net_buf_simple_state _pdu_state; + + /** @internal Free TX windows */ + + struct k_fifo _free_tx_win; + + /** @internal TX windows */ + + struct bt_l2cap_br_window tx_win[CONFIG_BT_L2CAP_MAX_WINDOW_SIZE]; + + /** Segment SDU packet from upper layer */ + + struct net_buf *_sdu; + + /** @internal RX SDU */ + + uint16_t _sdu_len; +#if defined(CONFIG_BT_L2CAP_SEG_RECV) || defined(__DOXYGEN__) + uint16_t _sdu_len_done; +#endif /* CONFIG_BT_L2CAP_SEG_RECV */ + /** @internal variables and sequence numbers */ + /** @internal The sending peer uses the following variables and sequence + * numbers. + */ + /** @internal The send sequence number used to sequentially number each + * new I-frame transmitted. + */ + uint16_t tx_seq; + /** @internal The sequence number to be used in the next new I-frame + * transmitted. + */ + + uint16_t next_tx_seq; + /** @internal The sequence number of the next I-frame expected to be + * acknowledged by the receiving peer. + */ + + uint16_t expected_ack_seq; + /** @internal The receiving peer uses the following variables and sequence + * numbers. + */ + + /** @internal The sequence number sent in an acknowledgment frame to + * request transmission of I-frame with TxSeq = ReqSeq and acknowledge + * receipt of I-frames up to and including (ReqSeq-1). + */ + + uint16_t req_seq; + /** @internal The value of TxSeq expected in the next I-frame. + */ + + uint16_t expected_tx_seq; + /** @internal When segmented I-frames are buffered this is used to delay + * acknowledgment of received I-frame so that new I-frame transmissions do + * not cause buffer overflow. + */ + + uint16_t buffer_seq; + + /** @internal States of Enhanced Retransmission Mode */ + + /** @internal Holds the number of times an S-frame operation is retried + */ + + uint16_t retry_count; + + /** @internal save the ReqSeq of a SREJ frame */ + + uint16_t srej_save_req_seq; + + /** @internal Retransmission Timer */ + + struct k_work_delayable ret_work; + + /** @internal Monitor Timer */ + + struct k_work_delayable monitor_work; +#endif /* CONFIG_BT_L2CAP_RET_FC */ +}; + +/** @brief L2CAP Channel operations structure. + * + * The object has to stay valid and constant for the lifetime of the channel. + * + * @note The callbacks are invoked from a thread context, never from an + * ISR. Whether a callback is invoked from a context internal to the stack + * or synchronously from within the API call that triggers it, and from + * which context, is not part of the API and may change between releases. + * See @rstref{Callback execution contexts } + * for the hazards of blocking in a callback and their mitigations. + */ + +struct bt_l2cap_chan_ops +{ + /** @brief Channel connected callback + * + * If this callback is provided it will be called whenever the connection + * completes. + * + * @param chan The channel that has been connected + */ + + void (*connected)(struct bt_l2cap_chan *chan); + + /** @brief Channel disconnected callback + * + * If this callback is provided it will be called whenever the channel is + * disconnected, including when a connection gets rejected. + * + * @param chan The channel that has been Disconnected + */ + + void (*disconnected)(struct bt_l2cap_chan *chan); + + /** @brief Channel encrypt_change callback + * + * If this callback is provided it will be called whenever the security + * level changed (indirectly link encryption done) or authentication + * procedure fails. In both cases security initiator and responder got the + * final status (HCI status) passed by related to encryption and + * authentication events from local host's controller. + * + * @param chan The channel which has made encryption status changed. + * @param hci_status HCI status of performed security procedure caused by + * channel security requirements. The value is populated + * by HCI layer and set to 0 when success and to + * non-zero (reference to HCI Error Codes) when + * security/authentication failed. + */ + + void (*encrypt_change)(struct bt_l2cap_chan *chan, uint8_t hci_status); + + /** @brief Channel alloc_seg callback + * + * If this callback is provided the channel will use it to allocate + * buffers to store segments. This avoids wasting big SDU buffers with + * potentially much smaller PDUs. If this callback is supplied, it must + * return a valid buffer. + * + * @param chan The channel requesting a buffer. + * + * @return Allocated buffer. + */ + + struct net_buf *(*alloc_seg)(struct bt_l2cap_chan *chan); + + /** @brief Channel alloc_buf callback + * + * If this callback is provided the channel will use it to allocate + * buffers to store incoming data. Channels that requires segmentation + * must set this callback. If the application has not set a callback the + * L2CAP SDU MTU will be truncated to @ref BT_L2CAP_SDU_RX_MTU. + * + * @note The stack stores the number of received segments in the first + * two bytes of the buffer user data. The buffer returned by this callback + * must have a user data size of at least @c sizeof(uint16_t). + * + * @param chan The channel requesting a buffer. + * + * @return Allocated buffer. + */ + + struct net_buf *(*alloc_buf)(struct bt_l2cap_chan *chan); + + /** @brief Channel recv callback + * + * @param chan The channel receiving data. + * @param buf Buffer containing incoming data. + * + * @note This callback is mandatory, unless + * @kconfig{CONFIG_BT_L2CAP_SEG_RECV} is enabled and seg_recv is supplied. + * + * If the application returns @c -EINPROGRESS, the application takes + * ownership of the reference in @p buf. (I.e. This pointer value can + * simply be given to @ref bt_l2cap_chan_recv_complete without any + * calls + * @ref net_buf_ref or @ref net_buf_unref.) + * + * @return 0 in case of success or negative value in case of error. + * @return -EINPROGRESS in case where user has to confirm once the data + * has been processed by calling + * @ref bt_l2cap_chan_recv_complete passing back + * the buffer received with its original user_data which contains the + * number of segments/credits used by the packet. + */ + + int (*recv)(struct bt_l2cap_chan *chan, struct net_buf *buf); + + /** @brief Channel sent callback + * + * This callback will be called once the controller marks the SDU as + * completed. When the controller does so is implementation dependent. It + * could be after the SDU is enqueued for transmission, or after it is + * sent on air. + * + * @param chan The channel which has sent data. + */ + + void (*sent)(struct bt_l2cap_chan *chan); + + /** @brief Channel status callback + * + * If this callback is provided it will be called whenever the channel + * status changes. + * + * @param chan The channel which status changed + * @param status The channel status + */ + + void (*status)(struct bt_l2cap_chan *chan, atomic_t *status); + + /* @brief Channel released callback + * + * If this callback is set it is called when the stack has release all + * references to the channel object. + */ + + void (*released)(struct bt_l2cap_chan *chan); + + /** @brief Channel reconfigured callback + * + * If this callback is provided it will be called whenever peer or local + * device requested reconfiguration. Application may check updated MTU and + * MPS values by inspecting chan->le endpoints. + * + * @param chan The channel which was reconfigured + */ + + void (*reconfigured)(struct bt_l2cap_chan *chan); + +#if defined(CONFIG_BT_L2CAP_SEG_RECV) || defined(__DOXYGEN__) + /** @brief Handle L2CAP segments directly + * + * This is an alternative to @ref bt_l2cap_chan_ops.recv. They cannot be + * used together. + * + * This is called immediately for each received segment. + * + * Unlike with @ref bt_l2cap_chan_ops.recv, flow control is explicit. Each + * time this handler is invoked, the remote has permanently used up one + * credit. Use @ref bt_l2cap_chan_give_credits to give credits. + * + * The start of an SDU is marked by `seg_offset == 0`. The end of an SDU + * is marked by `seg_offset + seg->len == sdu_len`. + * + * The stack guarantees that: + * - The sender had the credit. + * - The SDU length does not exceed MTU. + * - The segment length does not exceed MPS. + * + * Additionally, the L2CAP protocol is such that: + * - Segments come in order. + * - SDUs cannot be interleaved or aborted halfway. + * + * @note With this alternative API, the application is responsible for + * setting the RX MTU and MPS. The MPS must not exceed @ref + * BT_L2CAP_RX_MTU. + * + * @param chan The receiving channel. + * @param sdu_len Byte length of the SDU this segment is part of. + * @param seg_offset The byte offset of this segment in the SDU. + * @param seg The segment payload. + */ + +void (*seg_recv)(struct bt_l2cap_chan *chan, + size_t sdu_len, + off_t seg_offset, + struct net_buf_simple *seg); +#endif /* CONFIG_BT_L2CAP_SEG_RECV */ +}; + +/** + * @brief Headroom needed for outgoing L2CAP PDUs. + */ +#define BT_L2CAP_CHAN_SEND_RESERVE (BT_L2CAP_BUF_SIZE(0)) + +/** + * @brief Headroom needed for outgoing L2CAP SDUs. + */ +#define BT_L2CAP_SDU_CHAN_SEND_RESERVE (BT_L2CAP_SDU_BUF_SIZE(0)) + +/** @brief L2CAP Server structure. */ + +struct bt_l2cap_server +{ + /** @brief Server PSM. + * + * For LE, possible values: 0 A dynamic value will be auto-allocated when + * bt_l2cap_server_register() is called. + * + * 0x0001-0x007f Standard, Bluetooth SIG-assigned fixed values. + * + * 0x0080-0x00ff Dynamically allocated. May be pre-set by the application + * before server registration (not recommended however), or auto-allocated + * by the stack if the app gave 0 as the value. + * + * For BR, possible values: + * + * The PSM field is at least two octets in length. All PSM values shall + * have the least significant bit of the most significant octet equal to 0 + * and the least significant bit of all other octets equal to 1. + * + * 0 A dynamic value will be auto-allocated when + * bt_l2cap_br_server_register() is called. + * + * 0x0001-0x0eff Standard, Bluetooth SIG-assigned fixed values. + * + * > 0x1000 Dynamically allocated. May be pre-set by the application + * before server registration (not recommended however), or auto-allocated + * by the stack if the app gave 0 as the value. + */ + + uint16_t psm; + + /** Required minimum security level */ + + bt_security_t sec_level; + + /** @brief Server accept callback + * + * This callback is called whenever a new incoming connection requires + * authorization. If accepting the connection, the callback is expected to + * allocate a zero-initialized channel object and update the channel + * reference @p chan to point to its common member: for a server + * registered with bt_l2cap_server_register() the object must be of type + * @ref bt_l2cap_le_chan and @p chan set to its @ref bt_l2cap_le_chan.chan + * member, and for a server registered with bt_l2cap_br_server_register() + * the object must be of type @ref bt_l2cap_br_chan and @p chan set to its + * @ref bt_l2cap_br_chan.chan member. + * + * @warning Even though the reference passed back through @p chan is a + * @ref bt_l2cap_chan, it must be a member of a @ref bt_l2cap_le_chan or + * @ref bt_l2cap_br_chan object as described above. The stack uses the + * containing object, so returning a bare @ref bt_l2cap_chan would result + * in out-of-bounds access. It is the responsibility of this callback to + * zero out the containing object. + * + * @param conn The connection that is requesting authorization + * @param server Pointer to the server structure this callback relates to + * @param chan Pointer to received the allocated channel + * + * @return 0 in case of success or negative value in case of error. + * @return -ENOMEM if no available space for new channel. + * @return -EACCES if application did not authorize the connection. + * @return -EPERM if encryption key size is too short. + */ + + int (*accept)(struct bt_conn *conn, struct bt_l2cap_server *server, + struct bt_l2cap_chan **chan); + + struct list_node node; +}; + +/** @brief Register L2CAP server. + * + * Register L2CAP server for a PSM, each new connection is authorized using + * the accept() callback which in case of success shall allocate the channel + * structure to be used by the new connection. + * + * For fixed, SIG-assigned PSMs (in the range 0x0001-0x007f) the PSM should + * be assigned to server->psm before calling this API. For dynamic PSMs (in + * the range 0x0080-0x00ff) server->psm may be pre-set to a given value + * (this is however not recommended) or be left as 0, in which case upon + * return a newly allocated value will have been assigned to it. For + * dynamically allocated values the expectation is that it's exposed through + * a GATT service, and that's how L2CAP clients discover how to connect to + * the server. + * + * @param server Server structure. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_l2cap_server_register(struct bt_l2cap_server *server); + +/** @brief Register L2CAP server on BR/EDR oriented connection. + * + * Register L2CAP server for a PSM, each new connection is authorized using + * the accept() callback which in case of success shall allocate the channel + * structure to be used by the new connection. + * + * For fixed, SIG-assigned PSMs (in the range 0x0001-0x0eff) the PSM should + * be assigned to server->psm before calling this API. For dynamic PSMs (in + * the range 0x1000-0xffff) server->psm may be pre-set to a given value + * (this is however not recommended) or be left as 0, in which case upon + * return a newly allocated value will have been assigned to it. For + * dynamically allocated values the expectation is that it's exposed through + * a SDP record, and that's how L2CAP clients discover how to connect to the + * server. + * + * @param server Server structure. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_l2cap_br_server_register(struct bt_l2cap_server *server); + +/** @brief Unregister L2CAP server on BR/EDR oriented connection. + * + * Unregister L2CAP server for a PSM. + * + * @param server Server structure. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_l2cap_br_server_unregister(struct bt_l2cap_server *server); + +/** @brief Connect Enhanced Credit Based L2CAP channels + * + * Connect up to 5 L2CAP channels by PSM, once the connection is completed + * each channel connected() callback will be called. If the connection is + * rejected disconnected() callback is called instead. + * + * @warning It is the responsibility of the caller to zero out the parents + * of the chan objects. + * + * @param conn Connection object. + * @param chans Array of channel objects. + * @param psm Channel PSM to connect to. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_l2cap_ecred_chan_connect(struct bt_conn *conn, + struct bt_l2cap_chan **chans, uint16_t psm); + +/** @brief Reconfigure Enhanced Credit Based L2CAP channels + * + * Reconfigure up to 5 L2CAP channels. Channels must be from the same + * bt_conn. Once reconfiguration is completed each channel reconfigured() + * callback will be called. MTU cannot be decreased on any of provided + * channels. + * + * @param chans Array of channel objects. Null-terminated. Elements after + * the first 5 are silently ignored. + * @param mtu Channel MTU to reconfigure to. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_l2cap_ecred_chan_reconfigure(struct bt_l2cap_chan **chans, + uint16_t mtu); + +/** @brief Reconfigure Enhanced Credit Based L2CAP channels + * + * Experimental API to reconfigure L2CAP ECRED channels with explicit MPS + * and MTU values. + * + * Pend a L2CAP ECRED reconfiguration for up to 5 channels. All provided + * channels must share the same @ref bt_conn. + * + * This API cannot decrease the MTU of any channel, and it cannot decrease + * the MPS of any channel when more than one channel is provided. + * + * There is no dedicated callback for this operation, but whenever a peer + * responds to a reconfiguration request, each affected channel's + * reconfigured() callback is invoked. + * + * This function may block. + * + * @warning Known issue: The implementation returns -EBUSY if there already + * is an ongoing reconfigure operation on the same connection. The caller + * may try again later. There is no event signaling when the existing + * operation finishes. + * + * @warning Known issue: The implementation returns -ENOMEM when unable to + * allocate. The caller may try again later. There is no event signaling the + * availability of buffers. + * + * @kconfig_dep{CONFIG_BT_L2CAP_RECONFIGURE_EXPLICIT} + * + * @param chans Array of channels to reconfigure. Must be non-empty + * and contain at most 5 (@ref + * BT_L2CAP_ECRED_CHAN_MAX_PER_REQ) elements. + * @param chan_count Number of channels in the array. + * @param mtu Desired MTU. Must be at least @ref + * BT_L2CAP_ECRED_MIN_MTU. + * @param mps Desired MPS. Must be in range @ref + * BT_L2CAP_ECRED_MIN_MPS to + * @ref BT_L2CAP_RX_MTU. + * + * @retval 0 Successfully pended operation. + * @retval -EINVAL Bad arguments. See above requirements. + * @retval -ENOTCONN Connection object is not in connected state. + * @retval -EBUSY Another outgoing reconfiguration is pending on the same + * connection. + * @retval -ENOMEM Host is out of buffers. + */ + +int bt_l2cap_ecred_chan_reconfigure_explicit( + struct bt_l2cap_chan **chans, size_t chan_count, uint16_t mtu, + uint16_t mps); + +/** @brief Connect L2CAP channel + * + * Connect L2CAP channel by PSM, once the connection is completed channel + * connected() callback will be called. If the connection is rejected + * disconnected() callback is called instead. Channel object passed (over an + * address of it) as second parameter shouldn't be instantiated in + * application as standalone. Instead of, application should create + * transport dedicated L2CAP objects, i.e. type of bt_l2cap_le_chan for LE + * and/or type of bt_l2cap_br_chan for BR/EDR. Then pass to this API the + * location (address) of bt_l2cap_chan type object which is a member of both + * transport dedicated objects. + * + * @warning It is the responsibility of the caller to zero out the parent of + * the chan object. + * + * @param conn Connection object. + * @param chan Channel object. + * @param psm Channel PSM to connect to. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_l2cap_chan_connect(struct bt_conn *conn, + struct bt_l2cap_chan *chan, uint16_t psm); + +/** @brief Disconnect L2CAP channel + * + * Disconnect L2CAP channel, if the connection is pending it will be + * canceled and as a result the channel disconnected() callback is called. + * Regarding to input parameter, to get details see reference description to + * bt_l2cap_chan_connect() API above. + * + * @param chan Channel object. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_l2cap_chan_disconnect(struct bt_l2cap_chan *chan); + +/** @brief Send data to L2CAP channel + * + * Send data from buffer to the channel. For dynamic channels; if credits + * are not available, buf will be queued and sent as and when credits are + * received from peer. + * + * Network buffer fragments (ie `buf->frags`) are not supported. + * + * When sending L2CAP data over an BR/EDR connection or a fixed L2CAP + * channel, the application is sending L2CAP PDUs. The application is + * required to have reserved @ref BT_L2CAP_CHAN_SEND_RESERVE bytes in the + * buffer before sending. The application should use the BT_L2CAP_BUF_SIZE() + * helper to correctly size the buffers for the for the outgoing buffer + * pool. + * + * When sending L2CAP data over a dynamic L2CAP channel, the application is + * sending L2CAP SDUs. The application shall reserve + * @ref BT_L2CAP_SDU_CHAN_SEND_RESERVE bytes in the buffer before sending. + * The application can use the BT_L2CAP_SDU_BUF_SIZE() helper to correctly + * size the buffer to account for the reserved headroom. + * + * When segmenting an L2CAP SDU into L2CAP PDUs the stack will first attempt + * to allocate buffers from the channel's `alloc_seg` callback and will + * fallback on the stack's global buffer pool (sized + * @kconfig{CONFIG_BT_L2CAP_TX_BUF_COUNT}). + * + * @warning The buffer's user_data _will_ be overwritten by this function. + * Do not store anything in it. As soon as a call to this function has been + * made, consider ownership of user_data transferred into the stack. + * + * @note Buffer ownership is transferred to the stack in case of success, in + * case of an error the caller retains the ownership of the buffer. + * + * @warning If the buffer's pool has a destroy callback defined, that + * callback may be invoked from the ISR context when the HCI driver releases + * the buffer. Thus, the destroy callback must not call any synchronization + * primitives that are unsafe in the ISR context, i. e. blocking calls or + * locking the scheduler. + * + * @param chan The channel to send the data to. See @ref + * bt_l2cap_chan_connect for more details. + * @param buf Buffer containing the data. + * + * @return 0 in case of success or negative value in case of error. + * @return -EINVAL if `buf` or `chan` is NULL. + * @return -EINVAL if `chan` is not either BR/EDR or LE based. + * @return -EINVAL if buffer doesn't have enough bytes reserved to fit + * header. + * @return -EINVAL if buffer's reference counter != 1 + * @return -EMSGSIZE if `buf` is larger than `chan`'s MTU. + * @return -ENOTCONN if underlying conn is disconnected. + * @return -ESHUTDOWN if L2CAP channel is disconnected. + * @return -other (from lower layers) if chan is BR/EDR. + */ + +int bt_l2cap_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf); + +/** @brief Give credits to the remote + * + * Only available for channels using @ref bt_l2cap_chan_ops.seg_recv. + * @kconfig{CONFIG_BT_L2CAP_SEG_RECV} must be enabled to make this function + * available. + * + * Each credit given allows the peer to send one segment. + * + * This function depends on a valid @p chan object. Make sure to + * default-initialize or memset @p chan when allocating or reusing it for + * new connections. + * + * Adding zero credits is not allowed. + * + * Credits can be given before entering the @ref BT_L2CAP_CONNECTING state. + * Doing so will adjust the 'initial credits' sent in the connection PDU. + * + * Must not be called while the channel is in @ref BT_L2CAP_CONNECTING + * state. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_l2cap_chan_give_credits(struct bt_l2cap_chan *chan, + uint16_t additional_credits); + +/** @brief Complete receiving L2CAP channel data + * + * Complete the reception of incoming data. This shall only be called if the + * channel recv callback has returned -EINPROGRESS to process some incoming + * data. The buffer shall contain the original user_data as that is used for + * storing the credits/segments used by the packet. + * + * @param chan Channel object. + * @param buf Buffer containing the data. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_l2cap_chan_recv_complete(struct bt_l2cap_chan *chan, + struct net_buf *buf); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_L2CAP_H */ diff --git a/include/nuttx/net/bluetooth/mesh.h b/include/nuttx/net/bluetooth/mesh.h new file mode 100644 index 0000000000000..81993e7a66d92 --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh.h @@ -0,0 +1,67 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Bluetooth Mesh Profile APIs. + */ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_H */ diff --git a/include/nuttx/net/bluetooth/mesh/access.h b/include/nuttx/net/bluetooth/mesh/access.h new file mode 100644 index 0000000000000..7cf1c17ba5641 --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/access.h @@ -0,0 +1,1142 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/access.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Access layer APIs. + */ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_ACCESS_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_ACCESS_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +/** + * @cond INTERNAL_HIDDEN + */ + +/* Internal macros used to initialize array members */ +#define BT_MESH_KEY_UNUSED_ELT_(IDX, _) BT_MESH_KEY_UNUSED +#define BT_MESH_ADDR_UNASSIGNED_ELT_(IDX, _) BT_MESH_ADDR_UNASSIGNED +#define BT_MESH_UUID_UNASSIGNED_ELT_(IDX, _) NULL +#define BT_MESH_MODEL_KEYS_UNUSED(_keys) {LISTIFY(_keys, BT_MESH_KEY_UNUSED_ELT_, (, ))} +#define BT_MESH_MODEL_GROUPS_UNASSIGNED(_grps) {LISTIFY(_grps, BT_MESH_ADDR_UNASSIGNED_ELT_, (, ))} +#if CONFIG_BT_MESH_LABEL_COUNT > 0 +#define BT_MESH_MODEL_UUIDS_UNASSIGNED() \ + .uuids = (const uint8_t *[]){ \ + LISTIFY(CONFIG_BT_MESH_LABEL_COUNT, BT_MESH_UUID_UNASSIGNED_ELT_, (, ))}, +#else +#define BT_MESH_MODEL_UUIDS_UNASSIGNED() +#endif + +/** @endcond */ + +#define BT_MESH_MODEL_RUNTIME_INIT(_user_data) \ + .rt = &(struct bt_mesh_model_rt_ctx){.user_data = (_user_data)}, + +/** + * @brief Access layer + * @defgroup bt_mesh_access Access layer + * @ingroup bt_mesh + * @{ + */ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @name Group addresses + * @{ + */ +#define BT_MESH_ADDR_UNASSIGNED 0x0000 /**< unassigned */ +#define BT_MESH_ADDR_ALL_NODES 0xffff /**< all-nodes */ +#define BT_MESH_ADDR_RELAYS 0xfffe /**< all-relays */ +#define BT_MESH_ADDR_FRIENDS 0xfffd /**< all-friends */ +#define BT_MESH_ADDR_PROXIES 0xfffc /**< all-proxies */ +#define BT_MESH_ADDR_DFW_NODES 0xfffb /**< all-directed-forwarding-nodes */ +#define BT_MESH_ADDR_IP_NODES 0xfffa /**< all-ipt-nodes */ +#define BT_MESH_ADDR_IP_BR_ROUTERS 0xfff9 /**< all-ipt-border-routers */ + +/** + * @} + */ + +/** + * @name Predefined key indexes + * @{ + */ +#define BT_MESH_KEY_UNUSED 0xffff /**< Key unused */ +#define BT_MESH_KEY_ANY 0xffff /**< Any key index */ +#define BT_MESH_KEY_DEV 0xfffe /**< Device key */ +#define BT_MESH_KEY_DEV_LOCAL BT_MESH_KEY_DEV /**< Local device key */ +#define BT_MESH_KEY_DEV_REMOTE 0xfffd /**< Remote device key */ +#define BT_MESH_KEY_DEV_ANY 0xfffc /**< Any device key */ + +/** + * @} + */ + +/* Check if a Bluetooth Mesh address is a unicast address. + */ +#define BT_MESH_ADDR_IS_UNICAST(addr) ((addr) && (addr) < 0x8000) +/* Check if a Bluetooth Mesh address is a group address. + */ +#define BT_MESH_ADDR_IS_GROUP(addr) ((addr) >= 0xc000 && (addr) < 0xff00) +/* Check if a Bluetooth Mesh address is a fixed group address. + */ +#define BT_MESH_ADDR_IS_FIXED_GROUP(addr) ((addr) >= 0xff00 && (addr) < 0xffff) +/* Check if a Bluetooth Mesh address is a virtual address. + */ +#define BT_MESH_ADDR_IS_VIRTUAL(addr) ((addr) >= 0x8000 && (addr) < 0xc000) +/* Check if a Bluetooth Mesh address is an RFU address. + */ +#define BT_MESH_ADDR_IS_RFU(addr) ((addr) >= 0xff00 && (addr) <= 0xfff8) + +/* Check if a Bluetooth Mesh key is a device key. + */ +#define BT_MESH_IS_DEV_KEY(key) (key == BT_MESH_KEY_DEV_LOCAL || key == BT_MESH_KEY_DEV_REMOTE) + +/** Maximum size of an access message segment (in octets). */ +#define BT_MESH_APP_SEG_SDU_MAX 12 + +/** Maximum payload size of an unsegmented access message (in octets). */ +#define BT_MESH_APP_UNSEG_SDU_MAX 15 + +/** Maximum number of segments supported for incoming messages. */ +#if defined(CONFIG_BT_MESH_RX_SEG_MAX) +#define BT_MESH_RX_SEG_MAX CONFIG_BT_MESH_RX_SEG_MAX +#else +#define BT_MESH_RX_SEG_MAX 0 +#endif + +/** Maximum number of segments supported for outgoing messages. */ +#if defined(CONFIG_BT_MESH_TX_SEG_MAX) +#define BT_MESH_TX_SEG_MAX CONFIG_BT_MESH_TX_SEG_MAX +#else +#define BT_MESH_TX_SEG_MAX 0 +#endif + +/** Maximum possible payload size of an outgoing access message (in octets). + */ +#define BT_MESH_TX_SDU_MAX \ +MAX((BT_MESH_TX_SEG_MAX * BT_MESH_APP_SEG_SDU_MAX), BT_MESH_APP_UNSEG_SDU_MAX) + +/** Maximum possible payload size of an incoming access message (in octets). + */ +#define BT_MESH_RX_SDU_MAX \ +MAX((BT_MESH_RX_SEG_MAX * BT_MESH_APP_SEG_SDU_MAX), BT_MESH_APP_UNSEG_SDU_MAX) + +/** Helper to define a mesh element within an array. + * + * In case the element has no SIG or Vendor models the helper macro + * BT_MESH_MODEL_NONE can be given instead. + * + * @param _loc Location Descriptor. + * @param _mods Array of models. + * @param _vnd_mods Array of vendor models. + */ +#define BT_MESH_ELEM(_loc, _mods, _vnd_mods) \ +{ \ + .rt = &(struct bt_mesh_elem_rt_ctx){0}, \ + .loc = (_loc), \ + .model_count = ARRAY_SIZE(_mods), \ + .vnd_model_count = ARRAY_SIZE(_vnd_mods), \ + .models = (_mods), \ + .vnd_models = (_vnd_mods), \ +} + +/** Abstraction that describes a Mesh Element */ + +struct bt_mesh_elem +{ + /** Mesh Element runtime information */ + + struct bt_mesh_elem_rt_ctx + { + /** Unicast Address. Set at runtime during provisioning. */ + + uint16_t addr; + } + *const rt; + + /** Location Descriptor (GATT Bluetooth Namespace Descriptors) */ + + const uint16_t loc; + + /** The number of SIG models in this element */ + + const uint8_t model_count; + + /** The number of vendor models in this element */ + + const uint8_t vnd_model_count; + + /** The list of SIG models in this element */ + + const struct bt_mesh_model *const models; + + /** The list of vendor models in this element */ + + const struct bt_mesh_model *const vnd_models; +}; + +/** Model opcode handler. */ + +struct bt_mesh_model_op +{ + /** OpCode encoded using the BT_MESH_MODEL_OP_* macros */ + + const uint32_t opcode; + + /** Message length. If the message has variable length then this value + * indicates minimum message length and should be positive. Handler + * function should verify precise length based on the contents of the + * message. If the message has fixed length then this value should be + * negative. Use BT_MESH_LEN_* macros when defining this value. + */ + + const ssize_t len; + + /** @brief Handler function for this opcode. + * + * @param model Model instance receiving the message. + * @param ctx Message context for the message. + * @param buf Message buffer containing the message payload, not + * including the opcode. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int (*const func)(const struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf); +}; + +/** Macro for encoding a 1-byte opcode (used by Bluetooth SIG defined + * models). + */ +#define BT_MESH_MODEL_OP_1(b0) (b0) +/** Macro for encoding a 2-byte opcode (used by Bluetooth SIG defined + * models). + */ +#define BT_MESH_MODEL_OP_2(b0, b1) (((b0) << 8) | (b1)) +/** Macro for encoding a 3-byte opcode (vendor-specific message). */ +#define BT_MESH_MODEL_OP_3(b0, cid) ((((b0) << 16) | 0xc00000) | (cid)) + +/** Macro for encoding exact message length for fixed-length messages. */ +#define BT_MESH_LEN_EXACT(len) (-len) +/** Macro for encoding minimum message length for variable-length messages. + */ +#define BT_MESH_LEN_MIN(len) (len) + +/** End of the opcode list. Must always be present. */ +#define BT_MESH_MODEL_OP_END {0, 0, NULL} + +#if !defined(__cplusplus) || defined(__DOXYGEN__) +/** + * @brief Helper to define an empty opcode list. + * + * This macro uses compound literal feature of C99 standard and thus is + * available only from C, not C++. + */ +#define BT_MESH_MODEL_NO_OPS ((struct bt_mesh_model_op[]){BT_MESH_MODEL_OP_END}) + +/** + * @brief Helper to define an empty model array + * + * This macro uses compound literal feature of C99 standard and thus is + * available only from C, not C++. + */ +#define BT_MESH_MODEL_NONE ((const struct bt_mesh_model[]){}) + +/** + * @brief Composition data SIG model entry with callback functions + * with specific number of keys & groups. + * + * This macro uses compound literal feature of C99 standard and thus is + * available only from C, not C++. + * + * @param _id Model ID. + * @param _op Array of model opcode handlers. + * @param _pub Model publish parameters. + * @param _user_data User data for the model. + * @param _keys Number of keys that can be bound to the model. Shall + * not exceed @kconfig{CONFIG_BT_MESH_MODEL_KEY_COUNT}. + * @param _grps Number of addresses that the model can be subscribed + * to. Shall not exceed + * @kconfig{CONFIG_BT_MESH_MODEL_GROUP_COUNT}. + * @param _cb Callback structure, or NULL to keep no callbacks. + */ +#define BT_MESH_MODEL_CNT_CB(_id, _op, _pub, _user_data, _keys, _grps, _cb) \ +{ \ + .id = (_id), \ + BT_MESH_MODEL_RUNTIME_INIT(_user_data).pub = _pub, \ + .keys = (uint16_t[])BT_MESH_MODEL_KEYS_UNUSED(_keys), \ + .keys_cnt = _keys, \ + .groups = (uint16_t[])BT_MESH_MODEL_GROUPS_UNASSIGNED(_grps), \ + .groups_cnt = _grps, \ + BT_MESH_MODEL_UUIDS_UNASSIGNED().op = _op, \ + .cb = _cb, \ +} + +/** + * @brief Composition data vendor model entry with callback functions + * with specific number of keys & groups. + * + * This macro uses compound literal feature of C99 standard and thus is + * available only from C, not C++. + * + * @param _company Company ID. + * @param _id Model ID. + * @param _op Array of model opcode handlers. + * @param _pub Model publish parameters. + * @param _user_data User data for the model. + * @param _keys Number of keys that can be bound to the model. Shall + * not exceed @kconfig{CONFIG_BT_MESH_MODEL_KEY_COUNT}. + * @param _grps Number of addresses that the model can be subscribed + * to. Shall not exceed + * @kconfig{CONFIG_BT_MESH_MODEL_GROUP_COUNT}. + * @param _cb Callback structure, or NULL to keep no callbacks. + */ +#define BT_MESH_MODEL_CNT_VND_CB(_company, _id, _op, _pub, _user_data, _keys, _grps, _cb) \ +{ \ + .vnd.company = (_company), \ + .vnd.id = (_id), \ + BT_MESH_MODEL_RUNTIME_INIT(_user_data).op = _op, \ + .pub = _pub, \ + .keys = (uint16_t[])BT_MESH_MODEL_KEYS_UNUSED(_keys), \ + .keys_cnt = _keys, \ + .groups = (uint16_t[])BT_MESH_MODEL_GROUPS_UNASSIGNED(_grps), \ + .groups_cnt = _grps, \ + BT_MESH_MODEL_UUIDS_UNASSIGNED().cb = _cb, \ +} + +/** + * @brief Composition data SIG model entry with callback functions. + * + * This macro uses compound literal feature of C99 standard and thus is + * available only from C, not C++. + * + * @param _id Model ID. + * @param _op Array of model opcode handlers. + * @param _pub Model publish parameters. + * @param _user_data User data for the model. + * @param _cb Callback structure, or NULL to keep no callbacks. + */ +#define BT_MESH_MODEL_CB(_id, _op, _pub, _user_data, _cb) \ +BT_MESH_MODEL_CNT_CB(_id, _op, _pub, _user_data, CONFIG_BT_MESH_MODEL_KEY_COUNT, \ + CONFIG_BT_MESH_MODEL_GROUP_COUNT, _cb) + +/* @brief Composition data SIG model entry with callback functions and + * metadata. + * + * This macro uses compound literal feature of C99 standard and thus is + * available only from C, not C++. + * + * @param _id Model ID. + * @param _op Array of model opcode handlers. + * @param _pub Model publish parameters. + * @param _user_data User data for the model. + * @param _cb Callback structure, or NULL to keep no callbacks. + * @param _metadata Metadata structure. Used if + * @kconfig{CONFIG_BT_MESH_LARGE_COMP_DATA_SRV} is enabled. + */ +#if defined(CONFIG_BT_MESH_LARGE_COMP_DATA_SRV) +#define BT_MESH_MODEL_METADATA_CB(_id, _op, _pub, _user_data, _cb, _metadata) \ +{ \ + .id = (_id), \ + BT_MESH_MODEL_RUNTIME_INIT(_user_data).pub = _pub, \ + .keys = (uint16_t[])BT_MESH_MODEL_KEYS_UNUSED(CONFIG_BT_MESH_MODEL_KEY_COUNT), \ + .keys_cnt = CONFIG_BT_MESH_MODEL_KEY_COUNT, \ + .groups = (uint16_t[])BT_MESH_MODEL_GROUPS_UNASSIGNED(CONFIG_BT_MESH_MODEL_GROUP_COUNT), \ + .groups_cnt = CONFIG_BT_MESH_MODEL_GROUP_COUNT, \ + BT_MESH_MODEL_UUIDS_UNASSIGNED().op = _op, \ + .cb = _cb, \ + .metadata = _metadata, \ +} +#else +#define BT_MESH_MODEL_METADATA_CB(_id, _op, _pub, _user_data, _cb, _metadata) \ +BT_MESH_MODEL_CB(_id, _op, _pub, _user_data, _cb) +#endif + +/* @brief Composition data vendor model entry with callback functions. + * + * This macro uses compound literal feature of C99 standard and thus is + * available only from C, not C++. + * + * @param _company Company ID. + * @param _id Model ID. + * @param _op Array of model opcode handlers. + * @param _pub Model publish parameters. + * @param _user_data User data for the model. + * @param _cb Callback structure, or NULL to keep no callbacks. + */ +#define BT_MESH_MODEL_VND_CB(_company, _id, _op, _pub, _user_data, _cb) \ +BT_MESH_MODEL_CNT_VND_CB(_company, _id, _op, _pub, _user_data, CONFIG_BT_MESH_MODEL_KEY_COUNT, \ + CONFIG_BT_MESH_MODEL_GROUP_COUNT, _cb) + +/* @brief Composition data vendor model entry with callback functions and + * metadata. + * + * This macro uses compound literal feature of C99 standard and thus is + * available only from C, not C++. + * + * @param _company Company ID. + * @param _id Model ID. + * @param _op Array of model opcode handlers. + * @param _pub Model publish parameters. + * @param _user_data User data for the model. + * @param _cb Callback structure, or NULL to keep no callbacks. + * @param _metadata Metadata structure. Used if + * @kconfig{CONFIG_BT_MESH_LARGE_COMP_DATA_SRV} is enabled. + */ +#if defined(CONFIG_BT_MESH_LARGE_COMP_DATA_SRV) +#define BT_MESH_MODEL_VND_METADATA_CB(_company, _id, _op, _pub, _user_data, _cb, _metadata) \ +{ \ + .vnd.company = (_company), \ + .vnd.id = (_id), \ + BT_MESH_MODEL_RUNTIME_INIT(_user_data).op = _op, \ + .pub = _pub, \ + .keys = (uint16_t[])BT_MESH_MODEL_KEYS_UNUSED(CONFIG_BT_MESH_MODEL_KEY_COUNT), \ + .keys_cnt = CONFIG_BT_MESH_MODEL_KEY_COUNT, \ + .groups = (uint16_t[])BT_MESH_MODEL_GROUPS_UNASSIGNED(CONFIG_BT_MESH_MODEL_GROUP_COUNT), \ + .groups_cnt = CONFIG_BT_MESH_MODEL_GROUP_COUNT, \ + BT_MESH_MODEL_UUIDS_UNASSIGNED().cb = _cb, \ + .metadata = _metadata, \ +} +#else +#define BT_MESH_MODEL_VND_METADATA_CB(_company, _id, _op, _pub, _user_data, _cb, _metadata) \ +BT_MESH_MODEL_VND_CB(_company, _id, _op, _pub, _user_data, _cb) +#endif +/** + * @brief Composition data SIG model entry. + * + * This macro uses compound literal feature of C99 standard and thus is + * available only from C, not C++. + * + * @param _id Model ID. + * @param _op Array of model opcode handlers. + * @param _pub Model publish parameters. + * @param _user_data User data for the model. + */ +#define BT_MESH_MODEL(_id, _op, _pub, _user_data) BT_MESH_MODEL_CB(_id, _op, _pub, _user_data, NULL) + +/** + * @brief Composition data vendor model entry. + * + * This macro uses compound literal feature of C99 standard and thus is + * available only from C, not C++. + * + * @param _company Company ID. + * @param _id Model ID. + * @param _op Array of model opcode handlers. + * @param _pub Model publish parameters. + * @param _user_data User data for the model. + */ +#define BT_MESH_MODEL_VND(_company, _id, _op, _pub, _user_data) \ +BT_MESH_MODEL_VND_CB(_company, _id, _op, _pub, _user_data, NULL) +#endif /* !defined(__cplusplus) || defined(__DOXYGEN__) */ + +/** + * @brief Encode transmission count & interval steps. + * + * @param count Number of retransmissions (first transmission is + * excluded). + * @param int_ms Interval steps in milliseconds. Must be greater than 0, + * less than or equal to 320, and a multiple of 10. + * + * @return Mesh transmit value that can be used e.g. for the default + * values of the configuration model data. + */ +#define BT_MESH_TRANSMIT(count, int_ms) ((uint8_t)((count) | (((int_ms / 10) - 1) << 3))) + +/** + * @brief Decode transmit count from a transmit value. + * + * @param transmit Encoded transmit count & interval value. + * + * @return Transmission count (actual transmissions is N + 1). + */ +#define BT_MESH_TRANSMIT_COUNT(transmit) (((transmit) & (uint8_t)BIT_MASK(3))) + +/** + * @brief Decode transmit interval from a transmit value. + * + * @param transmit Encoded transmit count & interval value. + * + * @return Transmission interval in milliseconds. + */ +#define BT_MESH_TRANSMIT_INT(transmit) ((((transmit) >> 3) + 1) * 10) + +/** + * @brief Encode Publish Retransmit count & interval steps. + * + * @param count Number of retransmissions (first transmission is excluded). + * @param int_ms Interval steps in milliseconds. Must be greater than 0 and + * a multiple of 50. + * + * @return Mesh transmit value that can be used e.g. for the default + * values of the configuration model data. + */ +#define BT_MESH_PUB_TRANSMIT(count, int_ms) BT_MESH_TRANSMIT(count, (int_ms) / 5) + +/** + * @brief Decode Publish Retransmit count from a given value. + * + * @param transmit Encoded Publish Retransmit count & interval value. + * + * @return Retransmission count (actual transmissions is N + 1). + */ +#define BT_MESH_PUB_TRANSMIT_COUNT(transmit) BT_MESH_TRANSMIT_COUNT(transmit) + +/** + * @brief Decode Publish Retransmit interval from a given value. + * + * @param transmit Encoded Publish Retransmit count & interval value. + * + * @return Transmission interval in milliseconds. + */ +#define BT_MESH_PUB_TRANSMIT_INT(transmit) ((((transmit) >> 3) + 1) * 50) + +/** + * @brief Get total number of messages within one publication interval + * including initial publication. + * + * @param pub Model publication context. + * + * @return total number of messages. + */ +#define BT_MESH_PUB_MSG_TOTAL(pub) (BT_MESH_PUB_TRANSMIT_COUNT((pub)->retransmit) + 1) + +/** + * @brief Get message number within one publication interval. + * + * Meant to be used inside @ref bt_mesh_model_pub.update. + * + * @param pub Model publication context. + * + * @return message number starting from 1. + */ +#define BT_MESH_PUB_MSG_NUM(pub) (BT_MESH_PUB_TRANSMIT_COUNT((pub)->retransmit) + 1 - (pub)->count) + +/** Model publication context. + * + * The context should primarily be created using the + * BT_MESH_MODEL_PUB_DEFINE macro. + */ + +struct bt_mesh_model_pub +{ + /** The model the context belongs to. Initialized by the stack. */ + + const struct bt_mesh_model *mod; + + uint16_t addr; /**< Publish Address. */ + const uint8_t *uuid; /**< Label UUID if Publish Address is Virtual Address. */ + uint16_t key : 12; /**< Publish AppKey Index. */ + uint16_t cred : 1; /**< Friendship Credentials Flag. */ + uint16_t send_rel : 1; /**< Force reliable sending (segment acks) */ + uint16_t fast_period : 1; /**< Use FastPeriodDivisor */ + uint16_t retr_update : 1; /**< Call update callback on every retransmission. */ + + uint8_t ttl; /**< Publish Time to Live. */ + uint8_t retransmit; /**< Retransmit Count & Interval Steps. */ + uint8_t period; /**< Publish Period. */ + uint8_t period_div : 4; /**< Divisor for the Period. */ + uint8_t count : 4; /**< Transmissions left. */ + + uint8_t delayable : 1; /**< Use random delay for publishing. */ + + uint32_t period_start; /**< Start of the current period. */ + + /** @brief Publication buffer, containing the publication message. + * + * This will get correctly created when the publication context has been + * defined using the BT_MESH_MODEL_PUB_DEFINE macro. + * + * BT_MESH_MODEL_PUB_DEFINE(name, update, size); + */ + + struct net_buf_simple *msg; + + /** @brief Callback for updating the publication buffer. + * + * When set to NULL, the model is assumed not to support periodic + * publishing. When set to non-NULL the callback will be called + * periodically and is expected to update + * @ref bt_mesh_model_pub.msg with a valid publication + * message. + * + * If the callback returns non-zero, the publication is skipped and will + * resume on the next periodic publishing interval. + * + * When @ref bt_mesh_model_pub.retr_update is set to 1, the callback will + * be called on every retransmission. + * + * @param mod The Model the Publication Context belongs to. + * + * @return Zero on success or (negative) error code otherwise. + */ + + int (*update)(const struct bt_mesh_model *mod); + + /** Publish Period Timer. Only for stack-internal use. */ + + struct k_work_delayable timer; +}; + +/* Define a model publication context. + * + * @param _name Variable name given to the context. + * @param _update Optional message update callback (may be NULL). + * @param _msg_len Length of the publication message. + */ +#define BT_MESH_MODEL_PUB_DEFINE(_name, _update, _msg_len) \ +NET_BUF_SIMPLE_DEFINE_STATIC(bt_mesh_pub_msg_##_name, _msg_len); \ +static struct bt_mesh_model_pub _name = { \ + .msg = &bt_mesh_pub_msg_##_name, \ + .update = _update, \ +} + +/** Models Metadata Entry struct + * + * The struct should primarily be created using the + * BT_MESH_MODELS_METADATA_ENTRY macro. + */ + +struct bt_mesh_models_metadata_entry +{ + /** Length of the metadata */ + + const uint16_t len; + + /** ID of the metadata */ + + const uint16_t id; + + /** Pointer to raw data */ + + const void *const data; +}; + +/* Initialize a Models Metadata entry structure in a list. + * + * @param _len Length of the metadata entry. + * @param _id ID of the Models Metadata entry. + * @param _data Pointer to a contiguous memory that contains the metadata. + */ +#define BT_MESH_MODELS_METADATA_ENTRY(_len, _id, _data) \ +{ \ + .len = (_len), \ + .id = _id, \ + .data = _data, \ +} + +/** Helper to define an empty Models metadata array */ +#define BT_MESH_MODELS_METADATA_NONE NULL + +/** End of the Models Metadata list. Must always be present. */ +#define BT_MESH_MODELS_METADATA_END {0, 0, NULL} + +/** Model callback functions. */ + +struct bt_mesh_model_cb +{ + /** @brief Set value handler of user data tied to the model. + * + * @sa settings_handler::h_set + * + * @param model Model to set the persistent data of. + * @param name Name/key of the settings item. + * @param len_rd The size of the data found in the backend. + * @param read_cb Function provided to read the data from the backend. + * @param cb_arg Arguments for the read function provided by the backend. + * + * @return 0 on success, error otherwise. + */ + +int (*const settings_set)(const struct bt_mesh_model *model, + const char *name, + size_t len_rd, + settings_read_cb read_cb, + void *cb_arg); + + /** @brief Callback called when the mesh is started. + * + * This handler gets called after the node has been provisioned, or after + * all mesh data has been loaded from persistent storage. + * + * When this callback fires, the mesh model may start its behavior, and + * all Access APIs are ready for use. + * + * @param model Model this callback belongs to. + * + * @return 0 on success, error otherwise. + */ + + int (*const start)(const struct bt_mesh_model *model); + + /** @brief Model init callback. + * + * Called on every model instance during mesh initialization. + * + * If any of the model init callbacks return an error, the Mesh subsystem + * initialization will be aborted, and the error will be returned to the + * caller of @ref bt_mesh_init. + * + * @param model Model to be initialized. + * + * @return 0 on success, error otherwise. + */ + + int (*const init)(const struct bt_mesh_model *model); + + /** @brief Model reset callback. + * + * Called when the mesh node is reset. All model data is deleted on reset, + * and the model should clear its state. + * + * @note If the model stores any persistent data, this needs to be + * erased manually. + * + * @param model Model this callback belongs to. + */ + + void (*const reset)(const struct bt_mesh_model *model); + + /** @brief Callback used to store pending model's user data. + * + * Triggered by @ref bt_mesh_model_data_store_schedule. + * + * To store the user data, call @ref bt_mesh_model_data_store. + * + * @param model Model this callback belongs to. + */ + + void (*const pending_store)(const struct bt_mesh_model *model); +}; + +/** Vendor model ID */ + +struct bt_mesh_mod_id_vnd +{ + /** Vendor's company ID */ + + uint16_t company; + + /** Model ID */ + + uint16_t id; +}; + +/** Abstraction that describes a Mesh Model instance */ + +struct bt_mesh_model +{ + union + { + /** SIG model ID */ + + const uint16_t id; + + /** Vendor model ID */ + + const struct bt_mesh_mod_id_vnd vnd; + }; + + /* Model runtime information */ + + struct bt_mesh_model_rt_ctx + { + uint8_t elem_idx; /* Belongs to Nth element */ + uint8_t mod_idx; /* Is the Nth model in the element */ + uint16_t flags; /* Model flags for internal bookkeeping */ + +#ifdef CONFIG_BT_MESH_MODEL_EXTENSIONS + /* Pointer to the next model in a model extension list. */ + + const struct bt_mesh_model *next; +#endif + /** Model-specific user data */ + + void *user_data; + } + *const rt; + + /** Model Publication */ + + struct bt_mesh_model_pub *const pub; + + /** AppKey List */ + + uint16_t *const keys; + const uint16_t keys_cnt; + + /** Subscription List (group or virtual addresses) */ + + uint16_t *const groups; + const uint16_t groups_cnt; + +#if (CONFIG_BT_MESH_LABEL_COUNT > 0) || defined(__DOXYGEN__) + /** List of Label UUIDs the model is subscribed to. */ + + const uint8_t **const uuids; +#endif + + /** Opcode handler list */ + + const struct bt_mesh_model_op *const op; + + /** Model callback structure. */ + + const struct bt_mesh_model_cb *const cb; + +#if defined(CONFIG_BT_MESH_LARGE_COMP_DATA_SRV) || defined(__DOXYGEN__) + /* Pointer to the array of model metadata entries. */ + + const struct bt_mesh_models_metadata_entry *const metadata; +#endif +}; + +/** Callback structure for monitoring model message sending */ + +struct bt_mesh_send_cb +{ + /** @brief Handler called at the start of the transmission. + * + * @param duration The duration of the full transmission. + * @param err Error occurring during sending. + * @param cb_data Callback data, as passed to the send API. + */ + + void (*start)(uint16_t duration, int err, void *cb_data); + /** @brief Handler called at the end of the transmission. + * + * @param err Error occurring during sending. + * @param cb_data Callback data, as passed to the send API. + */ + + void (*end)(int err, void *cb_data); +}; + +/** Special TTL value to request using configured default TTL */ +#define BT_MESH_TTL_DEFAULT 0xff + +/** Maximum allowed TTL value */ +#define BT_MESH_TTL_MAX 0x7f + +/** @brief Send an Access Layer message. + * + * @param model Mesh (client) Model that the message belongs to. + * @param ctx Message context, includes keys, TTL, etc. + * @param msg Access Layer payload (the actual message to be sent). + * @param cb Optional "message sent" callback. + * @param cb_data User data to be passed to the callback. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_model_send( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *msg, const struct bt_mesh_send_cb *cb, + void *cb_data); + +/** @brief Send a model publication message. + * + * Before calling this function, the user needs to ensure that the model + * publication message (@ref bt_mesh_model_pub.msg) contains a valid message + * to be sent. Note that this API is only to be used for non-period + * publishing. For periodic publishing the app only needs to make sure that + * @ref bt_mesh_model_pub.msg contains a valid message whenever the @ref + * bt_mesh_model_pub.update callback is called. + * + * @param model Mesh (client) Model that's publishing the message. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_model_publish(const struct bt_mesh_model *model); + +/** @brief Check if a message is being retransmitted. + * + * Meant to be used inside the @ref bt_mesh_model_pub.update callback. + * + * @param model Mesh Model that supports publication. + * + * @return true if this is a retransmission, false if this is a first + * publication. + */ + +static inline bool bt_mesh_model_pub_is_retransmission( + const struct bt_mesh_model *model) +{ + return model->pub->count != + BT_MESH_PUB_TRANSMIT_COUNT(model->pub->retransmit); +} + +/** @brief Get the element that a model belongs to. + * + * @param mod Mesh model. + * + * @return Pointer to the element that the given model belongs to. + */ + +const struct bt_mesh_elem *bt_mesh_model_elem( + const struct bt_mesh_model *mod); + +/** @brief Find a SIG model. + * + * @param elem Element to search for the model in. + * @param id Model ID of the model. + * + * @return A pointer to the Mesh model matching the given parameters, or + * NULL if no SIG model with the given ID exists in the given element. + */ + +const struct bt_mesh_model *bt_mesh_model_find( + const struct bt_mesh_elem *elem, uint16_t id); + +/** @brief Find a vendor model. + * + * @param elem Element to search for the model in. + * @param company Company ID of the model. + * @param id Model ID of the model. + * + * @return A pointer to the Mesh model matching the given parameters, or + * NULL if no vendor model with the given ID exists in the given element. + */ + +const struct bt_mesh_model *bt_mesh_model_find_vnd( + const struct bt_mesh_elem *elem, uint16_t company, uint16_t id); + +/** @brief Get whether the model is in the primary element of the device. + * + * @param mod Mesh model. + * + * @return true if the model is on the primary element, false otherwise. + */ + +static inline bool bt_mesh_model_in_primary(const struct bt_mesh_model *mod) +{ + return (mod->rt->elem_idx == 0); +} + +/** @brief Immediately store the model's user data in persistent storage. + * + * @param mod Mesh model. + * @param vnd This is a vendor model. + * @param name Name/key of the settings item. Only + * @ref SETTINGS_MAX_DIR_DEPTH bytes will be used at most. + * @param data Model data to store, or NULL to delete any model data. + * @param data_len Length of the model data. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_model_data_store( + const struct bt_mesh_model *mod, bool vnd, const char *name, + const void *data, size_t data_len); + +/** @brief Schedule the model's user data store in persistent storage. + * + * This function triggers the @ref bt_mesh_model_cb.pending_store callback + * for the corresponding model after delay defined by + * @kconfig{CONFIG_BT_MESH_STORE_TIMEOUT}. + * + * The delay is global for all models. Once scheduled, the callback can not + * be re-scheduled until previous schedule completes. + * + * @param mod Mesh model. + */ + +void bt_mesh_model_data_store_schedule(const struct bt_mesh_model *mod); + +/** @brief Let a model extend another. + * + * Mesh models may be extended to reuse their functionality, forming a more + * complex model. A Mesh model may extend any number of models, in any + * element. The extensions may also be nested, ie a model that extends + * another may itself be extended. + * + * A set of models that extend each other form a model extension list. + * + * All models in an extension list share one subscription list per element. + * The access layer will utilize the combined subscription list of all + * models in an extension list and element, giving the models extended + * subscription list capacity. + * + * If @kconfig{CONFIG_BT_MESH_COMP_PAGE_1} is enabled, it is not allowed to + * call this function before the @ref bt_mesh_model_cb.init callback is + * called for both models, except if it is called as part of the final + * callback. + * + * @param extending_mod Mesh model that is extending the base model. + * @param base_mod The model being extended. + * + * @retval 0 Successfully extended the base_mod model. + */ + +int bt_mesh_model_extend(const struct bt_mesh_model *extending_mod, + const struct bt_mesh_model *base_mod); + +/** @brief Let a model correspond to another. + * + * Mesh models may correspond to each other, which means that if one is + * present, other must be present too. A Mesh model may correspond to any + * number of models, in any element. All models connected together via + * correspondence form single Correspondence Group, which has it's unique + * Correspondence ID. Information about Correspondence is used to construct + * Composition Data Page 1. + * + * This function must be called on already initialized base_mod. Because + * this function is designed to be called in corresponding_mod initializer, + * this means that base_mod shall be initialized before corresponding_mod + * is. + * + * @param corresponding_mod Mesh model that is corresponding to the base + * model. + * @param base_mod The model being corresponded to. + * + * @retval 0 Successfully saved correspondence to the base_mod model. + * @retval -ENOMEM There is no more space to save this relation. + * @retval -ENOTSUP Composition Data Page 1 is not supported. + */ + +int bt_mesh_model_correspond(const struct bt_mesh_model *corresponding_mod, + const struct bt_mesh_model *base_mod); + +/** @brief Check if model is extended by another model. + * + * @param model The model to check. + * + * @retval true If model is extended by another model, otherwise false + */ + +bool bt_mesh_model_is_extended(const struct bt_mesh_model *model); + +/** @brief Indicate that the composition data will change on next boot up. + * + * Tell the config server that the composition data is expected to change on + * the next boot up, and the current composition data should be backed up. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_mesh_comp_change_prepare(void); + +/** @brief Indicate that the metadata will change on next boot up. + * + * Tell the config server that the models metadata is expected to change on + * the next boot up, and the current models metadata should be backed up. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_mesh_models_metadata_change_prepare(void); + +/** Node Composition */ + +struct bt_mesh_comp +{ + uint16_t cid; /**< Company ID */ + uint16_t pid; /**< Product ID */ + uint16_t vid; /**< Version ID */ + + size_t elem_count; /**< The number of elements in this device. */ + const struct bt_mesh_elem *elem; /**< List of elements. */ +}; + +/** Composition data page 2 record. */ + +struct bt_mesh_comp2_record +{ + /** Mesh profile ID. */ + + uint16_t id; + + /** Mesh Profile Version. */ + + struct + { + /** Major version. */ + + uint8_t x; + + /** Minor version. */ + + uint8_t y; + + /** Z version. */ + + uint8_t z; + } version; + + /** Element offset count. */ + + uint8_t elem_offset_cnt; + + /** Element offset list. */ + + const uint8_t *elem_offset; + + /** Length of additional data. */ + + uint16_t data_len; + + /** Additional data. */ + + const void *data; +}; + +/** Node Composition data page 2 */ + +struct bt_mesh_comp2 +{ + /** The number of Mesh Profile records on a device. */ + + size_t record_cnt; + + /** List of records. */ + + const struct bt_mesh_comp2_record *record; +}; + +/** @brief Register composition data page 2 of the device. + * + * Register Mesh Profiles information (Ref section 3.12 in Bluetooth SIG + * Assigned Numbers) for composition data page 2 of the device. + * + * @note There must be at least one record present in @c comp2 + * + * @param comp2 Pointer to composition data page 2. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_mesh_comp2_register(const struct bt_mesh_comp2 *comp2); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_ACCESS_H */ diff --git a/include/nuttx/net/bluetooth/mesh/blob.h b/include/nuttx/net/bluetooth/mesh/blob.h new file mode 100644 index 0000000000000..d9b1de275c6cf --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/blob.h @@ -0,0 +1,357 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/blob.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Header file for the Bluetooth Mesh BLOB Transfer model API. + * @ingroup bt_mesh_blob + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_BLOB_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_BLOB_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @defgroup bt_mesh_blob Bluetooth Mesh BLOB model API + * @ingroup bt_mesh + * @{ + */ + +#ifndef CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX +#define CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX 0 +#endif + +/** BLOB transfer mode. */ + +enum bt_mesh_blob_xfer_mode +{ + /** No valid transfer mode. */ + + BT_MESH_BLOB_XFER_MODE_NONE, + + /** Push mode (Push BLOB Transfer Mode). */ + + BT_MESH_BLOB_XFER_MODE_PUSH, + + /** Pull mode (Pull BLOB Transfer Mode). */ + + BT_MESH_BLOB_XFER_MODE_PULL, + + /** Both modes are valid. */ + + BT_MESH_BLOB_XFER_MODE_ALL, +}; + +/** Transfer phase. */ + +enum bt_mesh_blob_xfer_phase +{ + /** The BLOB Transfer Server is awaiting configuration. */ + + BT_MESH_BLOB_XFER_PHASE_INACTIVE, + + /** The BLOB Transfer Server is ready to receive a BLOB transfer. */ + + BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START, + + /** The BLOB Transfer Server is waiting for the next block of data. */ + + BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK, + + /** The BLOB Transfer Server is waiting for the next chunk of data. */ + + BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_CHUNK, + + /** The BLOB was transferred successfully. */ + + BT_MESH_BLOB_XFER_PHASE_COMPLETE, + + /** The BLOB transfer is paused. */ + + BT_MESH_BLOB_XFER_PHASE_SUSPENDED, +}; + +/** BLOB model status codes. */ + +enum bt_mesh_blob_status +{ + /** The message was processed successfully. */ + + BT_MESH_BLOB_SUCCESS, + /** The Block Number field value is not within the range of blocks being + * transferred. + */ + + BT_MESH_BLOB_ERR_INVALID_BLOCK_NUM, + /** The block size is smaller than the size indicated by the Min Block + * Size Log state or is larger than the size indicated by the Max Block + * Size Log state. + */ + + BT_MESH_BLOB_ERR_INVALID_BLOCK_SIZE, + /** The chunk size exceeds the size indicated by the Max Chunk Size + * state, or the number of chunks exceeds the number specified by the Max + * Total Chunks state. + */ + + BT_MESH_BLOB_ERR_INVALID_CHUNK_SIZE, + /** The operation cannot be performed while the server is in the current + * phase. + */ + + BT_MESH_BLOB_ERR_WRONG_PHASE, + + /** A parameter value in the message cannot be accepted. */ + + BT_MESH_BLOB_ERR_INVALID_PARAM, + + /** The message contains a BLOB ID value that is not expected. */ + + BT_MESH_BLOB_ERR_WRONG_BLOB_ID, + /** There is not enough space available in memory to receive the BLOB. + */ + + BT_MESH_BLOB_ERR_BLOB_TOO_LARGE, + /** The transfer mode is not supported by the BLOB Transfer Server model. + */ + + BT_MESH_BLOB_ERR_UNSUPPORTED_MODE, + + /** An internal error occurred on the node. */ + + BT_MESH_BLOB_ERR_INTERNAL, + /** The requested information cannot be provided while the server is in the + * current phase. + */ + + BT_MESH_BLOB_ERR_INFO_UNAVAILABLE, +}; + +/** BLOB transfer data block. */ + +struct bt_mesh_blob_block +{ + /** Block size in bytes */ + + size_t size; + + /** Offset in bytes from the start of the BLOB. */ + + off_t offset; + + /** Block number */ + + uint16_t number; + + /** Number of chunks in block. */ + + uint16_t chunk_count; + + /** Bitmap of missing chunks. */ + + uint8_t missing[DIV_ROUND_UP(CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX, 8)]; +}; + +/** BLOB data chunk. */ + +struct bt_mesh_blob_chunk +{ + /** Offset of the chunk data from the start of the block. */ + + off_t offset; + + /** Chunk data size. */ + + size_t size; + + /** Chunk data. */ + + uint8_t *data; +}; + +/** BLOB transfer. */ + +struct bt_mesh_blob_xfer +{ + /** BLOB ID. */ + + uint64_t id; + + /** Total BLOB size in bytes. */ + + size_t size; + + /** BLOB transfer mode. */ + + enum bt_mesh_blob_xfer_mode mode; + + /* Logarithmic representation of the block size. */ + + uint8_t block_size_log; + + /** Base chunk size. May be smaller for the last chunk. */ + + uint16_t chunk_size; +}; + +/** BLOB stream interaction mode. */ + +enum bt_mesh_blob_io_mode +{ + /** Read data from the stream. */ + + BT_MESH_BLOB_READ, + + /** Write data to the stream. */ + + BT_MESH_BLOB_WRITE, +}; + +/** BLOB stream. */ + +struct bt_mesh_blob_io +{ + /** @brief Open callback. + * + * Called when the reader is opened for reading. + * + * @param io BLOB stream. + * @param xfer BLOB transfer. + * @param mode Direction of the stream (read/write). + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int (*open)(const struct bt_mesh_blob_io *io, + const struct bt_mesh_blob_xfer *xfer, + enum bt_mesh_blob_io_mode mode); + + /** @brief Close callback. + * + * Called when the reader is closed. + * + * @param io BLOB stream. + * @param xfer BLOB transfer. + */ + + void (*close)(const struct bt_mesh_blob_io *io, + const struct bt_mesh_blob_xfer *xfer); + + /** @brief Block start callback. + * + * Called when a new block is opened for sending. Each block is only sent + * once, and are always sent in increasing order. The data chunks inside a + * single block may be requested out of order and multiple times. + * + * @param io BLOB stream. + * @param xfer BLOB transfer. + * @param block Block that was started. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int (*block_start)(const struct bt_mesh_blob_io *io, + const struct bt_mesh_blob_xfer *xfer, + const struct bt_mesh_blob_block *block); + + /** @brief Block end callback. + * + * Called when the current block has been transmitted in full. No data + * from this block will be requested again, and the application data + * associated with this block may be discarded. + * + * @param io BLOB stream. + * @param xfer BLOB transfer. + * @param block Block that finished sending. + */ + +void (*block_end)(const struct bt_mesh_blob_io *io, + const struct bt_mesh_blob_xfer *xfer, + const struct bt_mesh_blob_block *block); + + /** @brief Chunk data write callback. + * + * Used by the BLOB Transfer Server on incoming data. + * + * Each block is divided into chunks of data. This callback is called when + * a new chunk of data is received. Chunks may be received in any order + * within their block. + * + * If the callback returns successfully, this chunk will be marked as + * received, and will not be received again unless the block is restarted + * due to a transfer suspension. If the callback returns a non-zero value, + * the chunk remains unreceived, and the BLOB Transfer Client will attempt + * to resend it later. + * + * Note that the Client will only perform a limited number of attempts at + * delivering a chunk before dropping a Target node from the transfer. The + * number of retries performed by the Client is implementation specific. + * + * @param io BLOB stream. + * @param xfer BLOB transfer. + * @param block Block the chunk is part of. + * @param chunk Received chunk. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int (*wr)(const struct bt_mesh_blob_io *io, + const struct bt_mesh_blob_xfer *xfer, + const struct bt_mesh_blob_block *block, + const struct bt_mesh_blob_chunk *chunk); + + /** @brief Chunk data read callback. + * + * Used by the BLOB Transfer Client to fetch outgoing data. + * + * The Client calls the chunk data request callback to populate a chunk + * message going out to the Target nodes. The data request callback may be + * called out of order and multiple times for each offset, and cannot be + * used as an indication of progress. + * + * Returning a non-zero status code on the chunk data request callback + * results in termination of the transfer. + * + * @param io BLOB stream. + * @param xfer BLOB transfer. + * @param block Block the chunk is part of. + * @param chunk Chunk to get the data of. The buffer pointer to by the + * @c data member should be filled by the callback. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int (*rd)(const struct bt_mesh_blob_io *io, + const struct bt_mesh_blob_xfer *xfer, + const struct bt_mesh_blob_block *block, + const struct bt_mesh_blob_chunk *chunk); +}; + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_BLOB_H */ diff --git a/include/nuttx/net/bluetooth/mesh/blob_cli.h b/include/nuttx/net/bluetooth/mesh/blob_cli.h new file mode 100644 index 0000000000000..dec947bb04cee --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/blob_cli.h @@ -0,0 +1,578 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/blob_cli.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Header file for the Bluetooth Mesh BLOB Transfer Client model API. + * @ingroup bt_mesh_blob_cli + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_BLOB_CLI_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_BLOB_CLI_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @defgroup bt_mesh_blob_cli Bluetooth Mesh BLOB Transfer Client model API + * @ingroup bt_mesh + * @{ + */ + +struct bt_mesh_blob_cli; + +/* @brief BLOB Transfer Client model Composition Data entry. + * + * @param _cli Pointer to a @ref bt_mesh_blob_cli instance. + */ +#define BT_MESH_MODEL_BLOB_CLI(_cli) \ +BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_BLOB_CLI, _bt_mesh_blob_cli_op, NULL, _cli, \ + &_bt_mesh_blob_cli_cb) + +/** Target node's Pull mode (Pull BLOB Transfer Mode) context used while + * sending chunks to the Target node. + */ + +struct bt_mesh_blob_target_pull +{ + /** Timestamp when the Block Report Timeout Timer expires for this Target + * node. + */ + + int64_t block_report_timestamp; + + /** Missing chunks reported by this Target node. */ + + uint8_t missing[DIV_ROUND_UP(CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX, 8)]; +}; + +/** BLOB Transfer Client Target node. */ + +struct bt_mesh_blob_target +{ + /** Linked list node */ + + struct list_node n; + + /** Target node address. */ + + uint16_t addr; + + /** Target node's Pull mode context. Needs to be initialized when sending a + * BLOB in Pull mode. + */ + + struct bt_mesh_blob_target_pull *pull; + + /** BLOB transfer status, see @ref bt_mesh_blob_status. */ + + uint8_t status; + + uint8_t procedure_complete : 1; /* Procedure has been completed. */ + uint8_t acked : 1; /* Message has been acknowledged. Not used when sending. */ + uint8_t timedout : 1; /* Target node didn't respond after specified timeout. */ + uint8_t skip : 1; /* Skip Target node from broadcast. */ +}; + +/** BLOB transfer information. + * + * If @c phase is @ref BT_MESH_BLOB_XFER_PHASE_INACTIVE, the fields below @c + * phase are not initialized. If @c phase is @ref + * BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START, the fields below @c id are + * not initialized. + */ + +struct bt_mesh_blob_xfer_info +{ + /** BLOB transfer status. */ + + enum bt_mesh_blob_status status; + + /** BLOB transfer mode. */ + + enum bt_mesh_blob_xfer_mode mode; + + /** BLOB transfer phase. */ + + enum bt_mesh_blob_xfer_phase phase; + + /** BLOB ID. */ + + uint64_t id; + + /** BLOB size in octets. */ + + uint32_t size; + + /** Logarithmic representation of the block size. */ + + uint8_t block_size_log; + + /** MTU size in octets. */ + + uint16_t mtu_size; + + /** Bit field indicating blocks that were not received. */ + + const uint8_t *missing_blocks; +}; + +/** BLOB Transfer Client transfer inputs. */ + +struct bt_mesh_blob_cli_inputs +{ + /** Linked list of Target nodes. Each node should point to @ref + * bt_mesh_blob_target::n. + */ + + struct list_node targets; + + /** AppKey index to send with. */ + + uint16_t app_idx; + + /** Group address destination for the BLOB transfer, or @ref + * BT_MESH_ADDR_UNASSIGNED to send every message to each Target node + * individually. + */ + + uint16_t group; + + /** Time to live value of BLOB transfer messages. */ + + uint8_t ttl; + + /** Additional response time for the Target nodes, in 10-second increments. + * + * The extra time can be used to give the Target nodes more time to + * respond to messages from the Client. The actual timeout will be + * calculated according to the following formula: + * + * @verbatim timeout = 20 seconds + (10 seconds * timeout_base) + (100 ms + * * TTL) @endverbatim + * + * If a Target node fails to respond to a message from the Client within + * the configured transfer timeout, the Target node is dropped. + */ + + uint16_t timeout_base; +}; + +/** Transfer capabilities of a Target node. */ + +struct bt_mesh_blob_cli_caps +{ + /** Max BLOB size. */ + + size_t max_size; + + /** Logarithmic representation of the minimum block size. */ + + uint8_t min_block_size_log; + + /** Logarithmic representation of the maximum block size. */ + + uint8_t max_block_size_log; + + /** Max number of chunks per block. */ + + uint16_t max_chunks; + + /** Max chunk size. */ + + uint16_t max_chunk_size; + + /** Max MTU size. */ + + uint16_t mtu_size; + + /** Supported transfer modes. */ + + enum bt_mesh_blob_xfer_mode modes; +}; + +/** BLOB Transfer Client state. */ + +enum bt_mesh_blob_cli_state +{ + /** No transfer is active. */ + + BT_MESH_BLOB_CLI_STATE_NONE, + + /** Retrieving transfer capabilities. */ + + BT_MESH_BLOB_CLI_STATE_CAPS_GET, + + /** Sending transfer start. */ + + BT_MESH_BLOB_CLI_STATE_START, + + /** Sending block start. */ + + BT_MESH_BLOB_CLI_STATE_BLOCK_START, + + /** Sending block chunks. */ + + BT_MESH_BLOB_CLI_STATE_BLOCK_SEND, + + /** Checking block status. */ + + BT_MESH_BLOB_CLI_STATE_BLOCK_CHECK, + + /** Checking transfer status. */ + + BT_MESH_BLOB_CLI_STATE_XFER_CHECK, + + /** Cancelling transfer. */ + + BT_MESH_BLOB_CLI_STATE_CANCEL, + + /** Transfer is suspended. */ + + BT_MESH_BLOB_CLI_STATE_SUSPENDED, + + /** Checking transfer progress. */ + + BT_MESH_BLOB_CLI_STATE_XFER_PROGRESS_GET, +}; + +/** Event handler callbacks for the BLOB Transfer Client model. + * + * All handlers are optional. + */ + +struct bt_mesh_blob_cli_cb +{ + /** @brief Capabilities retrieval completion callback. + * + * Called when the capabilities retrieval procedure completes, indicating + * that a common set of acceptable transfer parameters have been + * established for the given list of Target nodes. All compatible Target + * nodes have status code @ref BT_MESH_BLOB_SUCCESS. + * + * @param cli BLOB Transfer Client instance. + * @param caps Safe transfer capabilities if the transfer capabilities + * of at least one Target node has satisfied the Client, or + * NULL otherwise. + */ + + void (*caps)(struct bt_mesh_blob_cli *cli, + const struct bt_mesh_blob_cli_caps *caps); + + /** @brief Target node loss callback. + * + * Called whenever a Target node has been lost due to some error in the + * transfer. Losing a Target node is not considered a fatal error for the + * Client until all Target nodes have been lost. + * + * @param cli BLOB Transfer Client instance. + * @param target Target node that was lost. + * @param reason Reason for the Target node loss. + */ + +void (*lost_target)(struct bt_mesh_blob_cli *cli, + struct bt_mesh_blob_target *target, + enum bt_mesh_blob_status reason); + + /** @brief Transfer is suspended. + * + * Called when the transfer is suspended due to response timeout from all + * Target nodes. + * + * @param cli BLOB Transfer Client instance. + */ + + void (*suspended)(struct bt_mesh_blob_cli *cli); + + /** @brief Transfer end callback. + * + * Called when the transfer ends. + * + * @param cli BLOB Transfer Client instance. + * @param xfer Completed transfer. + * @param success Status of the transfer. Is @c true if at least one + * Target node received the whole transfer. + */ + + void (*end)(struct bt_mesh_blob_cli *cli, + const struct bt_mesh_blob_xfer *xfer, bool success); + + /** @brief Transfer progress callback + * + * The content of @c info is invalidated upon exit from the callback. + * Therefore it needs to be copied if it is planned to be used later. + * + * @param cli BLOB Transfer Client instance. + * @param target Target node that responded to the request. + * @param info BLOB transfer information. + */ + +void (*xfer_progress)(struct bt_mesh_blob_cli *cli, + struct bt_mesh_blob_target *target, + const struct bt_mesh_blob_xfer_info *info); + + /** @brief End of Get Transfer Progress procedure. + * + * Called when all Target nodes have responded or the procedure timed-out. + * + * @param cli BLOB Transfer Client instance. + */ + + void (*xfer_progress_complete)(struct bt_mesh_blob_cli *cli); +}; + +/** @cond INTERNAL_HIDDEN */ + +struct blob_cli_broadcast_ctx +{ + /** Called for every Target node in unicast mode, or once in case of + * multicast mode. + */ + + void (*send)(struct bt_mesh_blob_cli *cli, uint16_t dst); + + /** Called after every @ref blob_cli_broadcast_ctx::send callback. */ + + void (*send_complete)(struct bt_mesh_blob_cli *cli, uint16_t dst); + /** If @ref blob_cli_broadcast_ctx::acked is true, called after all Target + * nodes have confirmed reception by @ref blob_cli_broadcast_rsp. + * Otherwise, called after transmission has been completed. + */ + + void (*next)(struct bt_mesh_blob_cli *cli); + /** If true, every transmission needs to be confirmed by @ref + * blob_cli_broadcast_rsp before + * @ref blob_cli_broadcast_ctx::next is called. + */ + + bool acked; + + /** If true, the message is always sent in a unicast way. */ + + bool force_unicast; + + /** If true, non-responsive Target nodes won't be dropped after transfer + * has timed out. + */ + + bool optional; + /** Set to true by the BLOB Transfer Client between blob_cli_broadcast and + * broadcast_complete calls. + */ + + bool is_inited; + /* Defines a time in ms by which the broadcast API postpones sending the + * message to a next target or completing the broadcast. + */ + + uint32_t post_send_delay_ms; +}; + +/** INTERNAL_HIDDEN @endcond */ + +/** BLOB Transfer Client model instance. */ + +struct bt_mesh_blob_cli +{ + /** Event handler callbacks */ + + const struct bt_mesh_blob_cli_cb *cb; + + /* Runtime state */ + + const struct bt_mesh_model *mod; + + struct + { + struct bt_mesh_blob_target *target; + struct blob_cli_broadcast_ctx ctx; + struct k_work_delayable retry; + + /* Represents Client Timeout timer in a timestamp. */ + + /* Used in Pull mode only. */ + + int64_t cli_timestamp; + struct k_work_delayable complete; + uint16_t pending; + uint8_t retries; + uint8_t sending : 1; + uint8_t cancelled : 1; + } tx; + + const struct bt_mesh_blob_io *io; + const struct bt_mesh_blob_cli_inputs *inputs; + const struct bt_mesh_blob_xfer *xfer; + uint32_t chunk_interval_ms; + uint16_t block_count; + uint16_t chunk_idx; + uint16_t mtu_size; + enum bt_mesh_blob_cli_state state; + struct bt_mesh_blob_block block; + struct bt_mesh_blob_cli_caps caps; +}; + +/** @brief Retrieve transfer capabilities for a list of Target nodes. + * + * Queries the availability and capabilities of all Target nodes, producing + * a cumulative set of transfer capabilities for the Target nodes, and + * returning it through the @ref bt_mesh_blob_cli_cb::caps callback. + * + * Retrieving the capabilities may take several seconds, depending on the + * number of Target nodes and mesh network performance. The end of the + * procedure is indicated through the @ref bt_mesh_blob_cli_cb::caps + * callback. + * + * This procedure is not required, but strongly recommended as a preparation + * for a transfer to maximize performance and the chances of success. + * + * @param cli BLOB Transfer Client instance. + * @param inputs Statically allocated BLOB Transfer Client transfer inputs. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_blob_cli_caps_get(struct bt_mesh_blob_cli *cli, + const struct bt_mesh_blob_cli_inputs *inputs); + +/** @brief Perform a BLOB transfer. + * + * Starts sending the transfer to the Target nodes. Only Target nodes with a + * @c status of @ref BT_MESH_BLOB_SUCCESS will be considered. + * + * The transfer will keep going either until all Target nodes have been + * dropped, or the full BLOB has been sent. + * + * The BLOB transfer may take several minutes, depending on the number of + * Target nodes, size of the BLOB and mesh network performance. The end of + * the transfer is indicated through the @ref bt_mesh_blob_cli_cb::end + * callback. + * + * A Client only supports one transfer at the time. + * + * @param cli BLOB Transfer Client instance. + * @param inputs Statically allocated BLOB Transfer Client transfer inputs. + * @param xfer Statically allocated transfer parameters. + * @param io BLOB stream to read the transfer from. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_blob_cli_send(struct bt_mesh_blob_cli *cli, + const struct bt_mesh_blob_cli_inputs *inputs, + const struct bt_mesh_blob_xfer *xfer, + const struct bt_mesh_blob_io *io); + +/** @brief Suspend the active transfer. + * + * @param cli BLOB Transfer Client instance. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_blob_cli_suspend(struct bt_mesh_blob_cli *cli); + +/** @brief Resume the suspended transfer. + * + * @param cli BLOB Transfer Client instance. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_blob_cli_resume(struct bt_mesh_blob_cli *cli); + +/** @brief Cancel an ongoing transfer. + * + * @param cli BLOB Transfer Client instance. + */ + +void bt_mesh_blob_cli_cancel(struct bt_mesh_blob_cli *cli); + +/** @brief Get the progress of BLOB transfer. + * + * This function can only be used if the BLOB Transfer Client is currently + * not performing a BLOB transfer. To get progress of the active BLOB + * transfer, use the + * @ref bt_mesh_blob_cli_xfer_progress_active_get function. + * + * @param cli BLOB Transfer Client instance. + * @param inputs Statically allocated BLOB Transfer Client transfer inputs. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_blob_cli_xfer_progress_get(struct bt_mesh_blob_cli *cli, + const struct bt_mesh_blob_cli_inputs *inputs); + +/** @brief Get the current progress of the active transfer in percent. + * + * @param cli BLOB Transfer Client instance. + * + * @return The current transfer progress, or 0 if no transfer is active. + */ + +uint8_t bt_mesh_blob_cli_xfer_progress_active_get( + struct bt_mesh_blob_cli *cli); + +/** @brief Get the current state of the BLOB Transfer Client. + * + * @param cli BLOB Transfer Client instance. + * + * @return true if the BLOB Transfer Client is currently participating in a + * transfer or retrieving the capabilities and false otherwise. + */ + +bool bt_mesh_blob_cli_is_busy(struct bt_mesh_blob_cli *cli); + +/** @brief Set chunk sending interval in ms + * + * This function is optional, and can be used to define how fast chunks are + * sent in the BLOB Client Model. Without an added delay, for example a + * Bluetooth Mesh DFU can cause network blockage by constantly sending the + * next chunks, especially if the chunks are sent to group addresses or + * multiple unicast addresses. + * + * @note: Big intervals may cause timeouts. Increasing the @c timeout_base + * accordingly can circumvent this. + * + * @param cli BLOB Transfer Client instance. + * @param interval_ms the delay before each chunk is sent out in ms. + */ + +void bt_mesh_blob_cli_set_chunk_interval_ms(struct bt_mesh_blob_cli *cli, + uint32_t interval_ms); + +/** @cond INTERNAL_HIDDEN */ + +extern const struct bt_mesh_model_op _bt_mesh_blob_cli_op[]; +extern const struct bt_mesh_model_cb _bt_mesh_blob_cli_cb; + +/** @endcond */ + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_BLOB_CLI_H */ diff --git a/include/nuttx/net/bluetooth/mesh/blob_io_flash.h b/include/nuttx/net/bluetooth/mesh/blob_io_flash.h new file mode 100644 index 0000000000000..29010ce3945ee --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/blob_io_flash.h @@ -0,0 +1,79 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/blob_io_flash.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Header file for the Bluetooth Mesh BLOB flash stream API. + * @ingroup bt_mesh_blob_io_flash + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_BLOB_IO_FLASH_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_BLOB_IO_FLASH_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @defgroup bt_mesh_blob_io_flash Bluetooth Mesh BLOB flash stream + * @ingroup bt_mesh + * @{ + */ + +/** BLOB flash stream. */ + +struct bt_mesh_blob_io_flash +{ + /** Flash stream area ID. */ + + uint8_t area_id; + + /** Active stream mode. */ + + enum bt_mesh_blob_io_mode mode; + + /** Offset into the stream area, in bytes. */ + + off_t offset; + + /* BLOB stream. */ + + struct bt_mesh_blob_io io; +}; + +/** @brief Initialize a flash stream. + * + * @param flash Flash stream. + * @param area_id Stream area identifier. + * @param offset Offset into the stream area, in bytes. + * + * @return 0 on success or (negative) error code otherwise. + */ + +int bt_mesh_blob_io_flash_init(struct bt_mesh_blob_io_flash *flash, + uint8_t area_id, + off_t offset); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_BLOB_IO_FLASH_H */ diff --git a/include/nuttx/net/bluetooth/mesh/blob_srv.h b/include/nuttx/net/bluetooth/mesh/blob_srv.h new file mode 100644 index 0000000000000..67cb21e611b3c --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/blob_srv.h @@ -0,0 +1,255 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/blob_srv.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Header file for the Bluetooth Mesh BLOB Transfer Server model API. + * @ingroup bt_mesh_blob_srv + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_BLOB_SRV_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_BLOB_SRV_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @defgroup bt_mesh_blob_srv Bluetooth Mesh BLOB Transfer Server model API + * @ingroup bt_mesh + * @{ + */ + +struct bt_mesh_blob_srv; + +/* @brief Max number of blocks in a single transfer. + */ +#if defined(CONFIG_BT_MESH_BLOB_SRV) +#define BT_MESH_BLOB_BLOCKS_MAX \ +(DIV_ROUND_UP(CONFIG_BT_MESH_BLOB_SIZE_MAX, CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MIN)) +#else +#define BT_MESH_BLOB_BLOCKS_MAX 1 +#endif + +/* @brief BLOB Transfer Server model composition data entry. + * + * @param _srv Pointer to a @ref bt_mesh_blob_srv instance. + */ +#define BT_MESH_MODEL_BLOB_SRV(_srv) \ +BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_BLOB_SRV, _bt_mesh_blob_srv_op, NULL, _srv, \ + &_bt_mesh_blob_srv_cb) + +/** @brief BLOB Transfer Server model event handlers. + * + * All callbacks are optional. + */ + +struct bt_mesh_blob_srv_cb +{ + /** @brief Transfer start callback. + * + * Called when the transfer has started with the prepared BLOB ID. + * + * @param srv BLOB Transfer Server instance. + * @param ctx Message context for the incoming start message. The entire + * transfer will be sent from the same source address. + * @param xfer Transfer parameters. + * + * @return 0 on success, or (negative) error code to reject the + * transfer. + */ + + int (*start)(struct bt_mesh_blob_srv *srv, struct bt_mesh_msg_ctx *ctx, + struct bt_mesh_blob_xfer *xfer); + + /** @brief Transfer end callback. + * + * Called when the transfer ends, either because it was cancelled, or + * because it finished successfully. A new transfer may be prepared. + * + * @note The transfer may end before it's started if the start + * parameters are invalid. + * + * @param srv BLOB Transfer Server instance. + * @param id BLOB ID of the cancelled transfer. + * @param success Whether the transfer was successful. + */ + + void (*end)(struct bt_mesh_blob_srv *srv, uint64_t id, bool success); + + /** @brief Transfer suspended callback. + * + * Called if the Server timed out while waiting for a transfer packet. A + * suspended transfer may resume later from the start of the current + * block. Any received chunks in the current block should be discarded, + * they will be received again if the transfer resumes. + * + * The transfer will call @c resumed again when resuming. + * + * @note The BLOB Transfer Server does not run a timer in the suspended + * state, and it's up to the application to determine whether the transfer + * should be permanently cancelled. Without interaction, the transfer will + * be suspended indefinitely, and the BLOB Transfer Server will not accept + * any new transfers. + * + * @param srv BLOB Transfer Server instance. + */ + + void (*suspended)(struct bt_mesh_blob_srv *srv); + + /** @brief Transfer resume callback. + * + * Called if the transfer is resumed after being suspended. + * + * @param srv BLOB Transfer Server instance. + */ + + void (*resume)(struct bt_mesh_blob_srv *srv); + + /** @brief Transfer recovery callback. + * + * Called when the Bluetooth Mesh subsystem is started if the device is + * rebooted in the middle of a transfer. + * + * Transfers will not be resumed after a reboot if this callback is not + * defined. + * + * @param srv BLOB Transfer Server instance. + * @param xfer Transfer to resume. + * @param io BLOB stream return parameter. Must be set to a valid BLOB + * stream by the callback. + * + * @return 0 on success, or (negative) error code to abandon the + * transfer. + */ + + int (*recover)(struct bt_mesh_blob_srv *srv, + struct bt_mesh_blob_xfer *xfer, + const struct bt_mesh_blob_io **io); +}; + +/** @brief BLOB Transfer Server model instance. */ + +struct bt_mesh_blob_srv +{ + /** Event handler callbacks. */ + + const struct bt_mesh_blob_srv_cb *cb; + + /* Runtime state: */ + + const struct bt_mesh_blob_io *io; + struct k_work_delayable rx_timeout; + struct bt_mesh_blob_block block; + const struct bt_mesh_model *mod; + enum bt_mesh_blob_xfer_phase phase; + + struct bt_mesh_blob_srv_state + { + struct bt_mesh_blob_xfer xfer; + uint16_t cli; + uint16_t app_idx; + uint16_t timeout_base; + uint16_t mtu_size; + uint8_t ttl; + + /* Bitfield of pending blocks. */ + + atomic_define(blocks, BT_MESH_BLOB_BLOCKS_MAX); + } state; + + /* Pull mode (Pull BLOB Transfer Mode) behavior. */ + + struct + { + uint16_t chunk_idx; + struct k_work_delayable report; + } pull; +}; + +/** @brief Prepare BLOB Transfer Server for an incoming transfer. + * + * Before a BLOB Transfer Server can receive a transfer, the transfer must + * be prepared through some application level mechanism. The BLOB Transfer + * Server will only accept incoming transfers with a matching BLOB ID. + * + * @param srv BLOB Transfer Server instance. + * @param id BLOB ID to accept. + * @param io BLOB stream to write the incoming BLOB to. + * @param ttl Time to live value to use in responses to the BLOB + * Transfer Client. + * @param timeout_base Extra time for the Client to respond in addition to + * the base 10 seconds, in 10-second increments. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_blob_srv_recv(struct bt_mesh_blob_srv *srv, + uint64_t id, + const struct bt_mesh_blob_io *io, + uint8_t ttl, + uint16_t timeout_base); + +/** @brief Cancel the current BLOB transfer. + * + * Tells the BLOB Transfer Client to drop this device from the list of + * Targets for the current transfer. Note that the client may continue + * sending the transfer to other Targets. + * + * @param srv BLOB Transfer Server instance. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_blob_srv_cancel(struct bt_mesh_blob_srv *srv); + +/** @brief Get the current state of the BLOB Transfer Server. + * + * @param srv BLOB Transfer Server instance. + * + * @return true if the BLOB Transfer Server is currently participating in a + * transfer, false otherwise. + */ + +bool bt_mesh_blob_srv_is_busy(const struct bt_mesh_blob_srv *srv); + +/** @brief Get the current progress of the active transfer in percent. + * + * @param srv BLOB Transfer Server instance. + * + * @return The current transfer progress, or 0 if no transfer is active. + */ + +uint8_t bt_mesh_blob_srv_progress(const struct bt_mesh_blob_srv *srv); + +/** @cond INTERNAL_HIDDEN */ + +extern const struct bt_mesh_model_op _bt_mesh_blob_srv_op[]; +extern const struct bt_mesh_model_cb _bt_mesh_blob_srv_cb; + +/** @endcond */ + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_BLOB_SRV_H */ diff --git a/include/nuttx/net/bluetooth/mesh/brg_cfg.h b/include/nuttx/net/bluetooth/mesh/brg_cfg.h new file mode 100644 index 0000000000000..28206526d4140 --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/brg_cfg.h @@ -0,0 +1,162 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/brg_cfg.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Header file for Bluetooth Mesh Bridge Configuration common + * definitions. + * @ingroup bt_mesh_brg_cfg + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_BRG_CFG_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_BRG_CFG_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @defgroup bt_mesh_brg_cfg Bridge Configuration common header + * @ingroup bt_mesh + * @{ + */ + +/** Subnet Bridge states */ + +enum bt_mesh_brg_cfg_state +{ + /** Subnet bridge functionality is disabled. */ + + BT_MESH_BRG_CFG_DISABLED, + + /** Subnet bridge state functionality is enabled. */ + + BT_MESH_BRG_CFG_ENABLED, +}; + +/* Bridging from Addr1 to Addr2. */ +#define BT_MESH_BRG_CFG_DIR_ONEWAY 1 +/* Bidirectional bridging between Addr1 and Addr2. */ +#define BT_MESH_BRG_CFG_DIR_TWOWAY 2 + +/** Bridging Table state entry corresponding to a entry in the Bridging + * Table. + */ + +struct bt_mesh_brg_cfg_table_entry +{ + /** Allowed directions for the bridged traffic (or bridged traffic not + * allowed) + */ + + uint8_t directions; + + /** NetKey Index of the first subnet */ + + uint16_t net_idx1; + + /** NetKey Index of the second subnet */ + + uint16_t net_idx2; + + /** Address of the node in the first subnet */ + + uint16_t addr1; + + /** Address of the node in the second subnet */ + + uint16_t addr2; +}; + +/** Bridging Table Status response */ + +struct bt_mesh_brg_cfg_table_status +{ + /** Status Code of the requesting message */ + + uint8_t status; + + /** Requested Bridging Table entry */ + + struct bt_mesh_brg_cfg_table_entry entry; +}; + +/** Used to filter set of pairs of NetKey Indexes from the Bridging Table */ + +struct bt_mesh_brg_cfg_filter_netkey +{ + uint16_t filter : 2; /* Filter applied to the set of pairs of NetKey Indexes */ + uint16_t prohibited : 2; /* Prohibited */ + uint16_t net_idx : 12; /* NetKey Index used for filtering or ignored */ +}; + +/** Bridged Subnets List response */ + +struct bt_mesh_brg_cfg_subnets_list +{ + /** Filter applied NetKey Indexes, and NetKey Index used for filtering. */ + + struct bt_mesh_brg_cfg_filter_netkey net_idx_filter; + + /** Start offset in units of bridges */ + + uint8_t start_idx; + + /** Pointer to allocated buffer for storing filtered of NetKey Indexes */ + + struct net_buf_simple *list; +}; + +/** Bridging Table List response */ + +struct bt_mesh_brg_cfg_table_list +{ + /** Status Code of the requesting message */ + + uint8_t status; + + /** NetKey Index of the first subnet */ + + uint16_t net_idx1; + + /** NetKey Index of the second subnet */ + + uint16_t net_idx2; + + /** Start offset in units of bridging table state entries */ + + uint16_t start_idx; + + /** Pointer to allocated buffer for storing list of bridged addresses and + * directions + */ + + struct net_buf_simple *list; +}; + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_BRG_CFG_H */ diff --git a/include/nuttx/net/bluetooth/mesh/brg_cfg_cli.h b/include/nuttx/net/bluetooth/mesh/brg_cfg_cli.h new file mode 100644 index 0000000000000..3716ac3ad8eab --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/brg_cfg_cli.h @@ -0,0 +1,374 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/brg_cfg_cli.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Header file for the Bluetooth Mesh Bridge Configuration Client + * model API. + * @ingroup bt_mesh_brg_cfg_cli + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_BRG_CFG_CLI_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_BRG_CFG_CLI_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @defgroup bt_mesh_brg_cfg_cli Bridge Configuration Client Model + * @ingroup bt_mesh + * @{ + * @brief API for the Bluetooth Mesh Bridge Configuration Client model + */ + +struct bt_mesh_brg_cfg_cli; + +/** + * @brief Bridge Configuration Client model Composition Data entry. + * + * @param _cli Pointer to a @ref bt_mesh_brg_cfg_cli instance. + */ +#define BT_MESH_MODEL_BRG_CFG_CLI(_cli) \ +BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_BRG_CFG_CLI, _bt_mesh_brg_cfg_cli_op, NULL, _cli, \ + &_bt_mesh_brg_cfg_cli_cb) + +/** Mesh Bridge Configuration Client Status messages callback */ + +struct bt_mesh_brg_cfg_cli_cb +{ + /** @brief Optional callback for Subnet Bridge Status message. + * + * Handles received Subnet Bridge Status messages from a Bridge + * Configuration Server. + + * @param cli Bridge Configuration Client context. + * @param addr Address of the sender. + * @param status Status received from the server. + */ + + void (*bridge_status)(struct bt_mesh_brg_cfg_cli *cli, uint16_t addr, + enum bt_mesh_brg_cfg_state status); + + /** @brief Optional callback for Bridging Table Size Status message. + * + * Handles received Bridging Table Size Status messages from a Bridge + * Configuration Server. + * + * @param cli Bridge Configuration Client context. + * @param addr Address of the sender. + * @param size Size received from the server. + */ + + void (*table_size_status)(struct bt_mesh_brg_cfg_cli *cli, uint16_t addr, + uint16_t size); + + /** @brief Optional callback for Bridging Table Status message. + * + * Handles received Bridging Table status messages from a Bridge + * Configuration Server. + * + * @param cli Bridge Configuration Client context. + * @param addr Address of the sender. + * @param rsp Response received from the Bridging Configuration + * Server. + */ + + void (*table_status)(struct bt_mesh_brg_cfg_cli *cli, uint16_t addr, + struct bt_mesh_brg_cfg_table_status *rsp); + + /** @brief Optional callback for Bridged Subnets List message. + * + * Handles received Bridged Subnets List messages from a Bridge + * Configuration Server. + * + * @param cli Bridge Configuration Client context. + * @param addr Address of the sender. + * @param rsp Response received from the Bridging Configuration + * Server. + */ + + void (*subnets_list)(struct bt_mesh_brg_cfg_cli *cli, uint16_t addr, + struct bt_mesh_brg_cfg_subnets_list *rsp); + + /** @brief Optional callback for Bridging Table List message. + * + * Handles received Bridging Table List messages from a Bridge + * Configuration Server. + * + * @param cli Bridge Configuration Client context. + * @param addr Address of the sender. + * @param rsp Response received from the Bridging Configuration + * Server. + */ + + void (*table_list)(struct bt_mesh_brg_cfg_cli *cli, uint16_t addr, + struct bt_mesh_brg_cfg_table_list *rsp); +}; + +/** Bridge Configuration Client Model Context */ + +struct bt_mesh_brg_cfg_cli +{ + /** Bridge Configuration model entry pointer */ + + const struct bt_mesh_model *model; + + /** Event handler callbacks */ + + const struct bt_mesh_brg_cfg_cli_cb *cb; + + /* Internal parameters for tracking message responses. */ + + struct bt_mesh_msg_ack_ctx ack_ctx; +}; + +/** @brief Sends a Subnet Bridge Get message to the given destination address + * + * This function sends a Subnet Bridge Get message to the given destination + * address to query the value of the Subnet Bridge state of a subnet. The + * Subnet Bridge state indicates whether the subnet bridged feature is + * enabled or not. The function expects a Subnet Bridge Status message as a + * response from the destination node. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt the message with. + * @param addr Target node address. + * @param status Status response parameter, returns one of + * @ref BT_MESH_BRG_CFG_DISABLED or + * @ref BT_MESH_BRG_CFG_ENABLED on success. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_brg_cfg_cli_get(uint16_t net_idx, + uint16_t addr, + enum bt_mesh_brg_cfg_state *status); + +/** @brief Sends a Subnet Bridge Set message to the given destination address + * with the given parameters + * + * This function sends a Subnet Bridge Set message to the given destination + * address with the given parameters to set the value of the Subnet Bridge + * state of a subnet. The Subnet Bridge state indicates whether the subnet + * bridge feature is enabled or not. The function expects a Subnet Bridge + * Status message as a response from the destination node. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt the message with. + * @param addr Target node address. + * @param val Value to set the Subnet Bridge state to. Must be one of + * @ref BT_MESH_BRG_CFG_DISABLED or + * @ref BT_MESH_BRG_CFG_ENABLED. + * @param status Status response parameter, returns one of + * @ref BT_MESH_BRG_CFG_DISABLED or + * @ref BT_MESH_BRG_CFG_ENABLED on success. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_brg_cfg_cli_set( + uint16_t net_idx, uint16_t addr, enum bt_mesh_brg_cfg_state val, + enum bt_mesh_brg_cfg_state *status); + +/** @brief Sends a Bridging Table Size Get message to the given destination + * address with the given parameters + * + * This function sends a Bridging Table Size Get message to the given + * destination address with the given parameters to get the size of the + * Bridging Table of the node. The Bridging Table size indicates the maximum + * number of entries that can be stored in the Bridging Table. The function + * expects a Bridging Table Size Status message as a response from the + * destination node. + * + * This method can be used asynchronously by setting @p size as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt the message with. + * @param addr Target node address. + * @param size Bridging Table size response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_brg_cfg_cli_table_size_get(uint16_t net_idx, + uint16_t addr, uint16_t *size); + +/** @brief Sends a Bridging Table Add message to the given destination + * address with the given parameters + * + * This function sends a Bridging Table Add message to the given destination + * address with the given parameters to add an entry to the Bridging Table. + * The Bridging Table contains the net keys and addresses that are + * authorized to be bridged by the node. The function expects a Bridging + * Table Status message as a response from the destination node. + * + * This method can be used asynchronously by setting @p rsp as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt the message with. + * @param addr Target node address. + * @param entry Pointer to bridging Table entry to add. + * @param rsp Status response parameter + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_brg_cfg_cli_table_add(uint16_t net_idx, uint16_t addr, + struct bt_mesh_brg_cfg_table_entry *entry, + struct bt_mesh_brg_cfg_table_status *rsp); + +/** @brief Sends a Bridging Table Remove message to the given destination + * address with the given parameters + * + * This function sends a Bridging Table Remove message to the given + * destination address with the given parameters to remove an entry from the + * Bridging Table. The Bridging Table contains the net keys and addresses + * that are authorized to be bridged by the node. The function expects a + * Bridging Table Status message as a response from the destination node. + * + * This method can be used asynchronously by setting @p rsp as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt the message with. + * @param addr Target node address. + * @param net_idx1 NetKey Index of the first subnet + * @param net_idx2 NetKey Index of the second subnet + * @param addr1 Address of the node in the first subnet + * @param addr2 Address of the node in the second subnet + * @param rsp Pointer to a struct storing the received response from + * the server, or NULL to not wait for a response. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_brg_cfg_cli_table_remove( + uint16_t net_idx, uint16_t addr, uint16_t net_idx1, uint16_t net_idx2, + uint16_t addr1, uint16_t addr2, struct bt_mesh_brg_cfg_table_status *rsp); + +/** @brief Sends a Bridged Subnets Get message to the given destination + * address with the given parameters + * + * This function sends a Bridged Subnets Get message to the given + * destination address with the given parameters to get the list of subnets + * that are bridged by the node. The function expects a Bridged Subnets List + * message as a response from the destination node. + * + * This method can be used asynchronously by setting @p rsp as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * When @c rsp is set, the user is responsible for providing a buffer for + * the filtered set of N pairs of NetKey Indexes in + * @ref bt_mesh_brg_cfg_subnets_list::list. If a buffer is not provided, the + * bridged subnets won't be copied. + + * @param net_idx Network index to encrypt the message with. + * @param addr Target node address. + * @param filter_net_idx Filter and NetKey Index used for filtering + * @param start_idx Start offset to read in units of Bridging Table + * state entries + * @param rsp Pointer to a struct storing the received response + * from the server, or NULL to not wait for a + * response. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_brg_cfg_cli_subnets_get(uint16_t net_idx, + uint16_t addr, + struct bt_mesh_brg_cfg_filter_netkey filter_net_idx, + uint8_t start_idx, + struct bt_mesh_brg_cfg_subnets_list *rsp); + +/** @brief Sends a Bridging Table Get message to the given destination + * address with the given parameters + * + * This function sends a Bridging Table Get message to the given destination + * address with the given parameters to get the contents of the Bridging + * Table. The Bridging Table contains the addresses that are authorized to + * be bridged by the node. The function expects a Bridging Table List + * message as a response from the destination node. + * + * This method can be used asynchronously by setting @p rsp as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * When @c rsp is set, the user is responsible for providing a buffer for + * the filtered set of N pairs of NetKey Indexes in + * @ref bt_mesh_brg_cfg_table_list::list. If a buffer is not provided, + * the bridged addresses won't be copied. If a buffer size is shorter than + * received list, only those many entries that fit in the buffer will be + * copied from the list, and rest will be discarded. + * + * @param net_idx Network index to encrypt the message with. + * @param addr Target node address. + * @param net_idx1 NetKey Index of the first subnet. + * @param net_idx2 NetKey Index of the second subnet. + * @param start_idx Start offset to read in units of Bridging Table state + * entries. + * @param rsp Pointer to a struct storing the received response from + * the server, or NULL to not wait for a response. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_brg_cfg_cli_table_get( + uint16_t net_idx, uint16_t addr, uint16_t net_idx1, uint16_t net_idx2, + uint16_t start_idx, struct bt_mesh_brg_cfg_table_list *rsp); + +/** @brief Get the current transmission timeout value. + * + * @return The configured transmission timeout in milliseconds. + */ + +int32_t bt_mesh_brg_cfg_cli_timeout_get(void); + +/** @brief Set the transmission timeout value. + * + * @param timeout The new transmission timeout. + */ + +void bt_mesh_brg_cfg_cli_timeout_set(int32_t timeout); + +/** @cond INTERNAL_HIDDEN */ + +extern const struct bt_mesh_model_op _bt_mesh_brg_cfg_cli_op[]; +extern const struct bt_mesh_model_cb _bt_mesh_brg_cfg_cli_cb; + +/** @endcond */ + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_BRG_CFG_CLI_H */ diff --git a/include/nuttx/net/bluetooth/mesh/brg_cfg_srv.h b/include/nuttx/net/bluetooth/mesh/brg_cfg_srv.h new file mode 100644 index 0000000000000..3106c665469ba --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/brg_cfg_srv.h @@ -0,0 +1,60 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/brg_cfg_srv.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** @file + * @brief Bluetooth Mesh Bridge Configuration Server Model APIs. + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_BRG_CFG_SRV_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_BRG_CFG_SRV_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @defgroup bt_mesh_brg_cfg_srv Bridge Configuration Server Model + * @ingroup bt_mesh + * @{ + * @brief API for the Bluetooth Mesh Bridge Configuration Server model + */ + +/** + * + * @brief Bridge Configuration Server model Composition Data entry. + */ +#define BT_MESH_MODEL_BRG_CFG_SRV \ +BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_BRG_CFG_SRV, _bt_mesh_brg_cfg_srv_op, NULL, NULL, \ + &_bt_mesh_brg_cfg_srv_cb) + +/** @cond INTERNAL_HIDDEN */ + +extern const struct bt_mesh_model_op _bt_mesh_brg_cfg_srv_op[]; +extern const struct bt_mesh_model_cb _bt_mesh_brg_cfg_srv_cb; + +/** @endcond */ + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_BRG_CFG_SRV_H */ diff --git a/include/nuttx/net/bluetooth/mesh/cdb.h b/include/nuttx/net/bluetooth/mesh/cdb.h new file mode 100644 index 0000000000000..7bf779bd88a53 --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/cdb.h @@ -0,0 +1,526 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/cdb.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2019 Tobias Svehagen + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Header file for the Bluetooth Mesh Configuration Database (CDB) + * API. + * @ingroup bt_mesh + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_CDB_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_CDB_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +#if defined(CONFIG_BT_MESH_CDB) +#define NODE_COUNT CONFIG_BT_MESH_CDB_NODE_COUNT +#define SUBNET_COUNT CONFIG_BT_MESH_CDB_SUBNET_COUNT +#define APP_KEY_COUNT CONFIG_BT_MESH_CDB_APP_KEY_COUNT +#else +#define NODE_COUNT 0 +#define SUBNET_COUNT 0 +#define APP_KEY_COUNT 0 +#endif + +enum +{ + BT_MESH_CDB_NODE_CONFIGURED, + + BT_MESH_CDB_NODE_FLAG_COUNT +}; + +/* doc string cdb node start */ + +struct bt_mesh_cdb_node +{ + /** Node UUID */ + + uint8_t uuid[16]; + + /** Primary element address */ + + uint16_t addr; + + /** Network key index used during provisioning */ + + uint16_t net_idx; + + /** Number of elements */ + + uint8_t num_elem; + + /** Device key */ + + struct bt_mesh_key dev_key; + + /** Node flags */ + + atomic_define(flags, BT_MESH_CDB_NODE_FLAG_COUNT); +}; + +/* doc string cdb node end */ + +/* doc string cdb subnet start */ + +struct bt_mesh_cdb_subnet +{ + /** Network key index */ + + uint16_t net_idx; + + /** Key Refresh phase */ + + uint8_t kr_phase; + + /** Old and new keys for Key Refresh */ + + struct + { + struct bt_mesh_key net_key; + } + keys[2]; +}; + +/* doc string cdb subnet end */ + +/* doc string cdb app key start */ + +struct bt_mesh_cdb_app_key +{ + /** Bound network key index */ + + uint16_t net_idx; + + /** Application key index */ + + uint16_t app_idx; + + /** Old and new keys for Key Refresh */ + + struct + { + struct bt_mesh_key app_key; + } + keys[2]; +}; + +/* doc string cdb app key end */ + +enum +{ + BT_MESH_CDB_VALID, + BT_MESH_CDB_SUBNET_PENDING, + BT_MESH_CDB_KEYS_PENDING, + BT_MESH_CDB_NODES_PENDING, + BT_MESH_CDB_IVU_IN_PROGRESS, + + BT_MESH_CDB_FLAG_COUNT, +}; + +/* doc string cdb start */ + +struct bt_mesh_cdb +{ + /** Network IV Index */ + + uint32_t iv_index; + + /** Lowest available unicast address for provisioning */ + + uint16_t lowest_avail_addr; + + /** Flags for CDB state (private) */ + + atomic_define(flags, BT_MESH_CDB_FLAG_COUNT); + + /** Nodes in the mesh network */ + + struct bt_mesh_cdb_node nodes[NODE_COUNT]; + + /** Subnets in the mesh network */ + + struct bt_mesh_cdb_subnet subnets[SUBNET_COUNT]; + + /** Application keys in the mesh network */ + + struct bt_mesh_cdb_app_key app_keys[APP_KEY_COUNT]; +}; + +/** The global CDB instance is accessible through this variable. */ + +extern struct bt_mesh_cdb bt_mesh_cdb; + +/* doc string cdb end */ + +/** @brief Create the Mesh Configuration Database. + * + * Create and initialize the Mesh Configuration Database. A primary subnet, + * ie one with NetIdx 0, will be added and the provided key will be used as + * NetKey for that subnet. + * + * @param key The NetKey to be used for the primary subnet. + * + * @return 0 on success or negative error code on failure. + */ + +int bt_mesh_cdb_create(const uint8_t key[16]); + +/** @brief Clear the Mesh Configuration Database. + * + * Remove all nodes, subnets and app-keys stored in the database and mark + * the database as invalid. The data will be cleared from persistent storage + * if CONFIG_BT_SETTINGS is enabled. + */ + +void bt_mesh_cdb_clear(void); + +/** @brief Set and store the IV Index and IV Update flag. + * + * The IV Index stored in the CDB will be the one used during provisioning + * of new nodes. This function is generally only used from inside the stack. + * + * This function will store the data to persistent storage if + * CONFIG_BT_SETTINGS is enabled. + * + * @param iv_index The new IV Index to use. + * @param iv_update True if there is an ongoing IV Update procedure. + */ + +void bt_mesh_cdb_iv_update(uint32_t iv_index, bool iv_update); + +/** @brief Allocate a node. + * + * Allocate a new node in the CDB. + * + * If @c addr is 0, @ref bt_mesh_cdb_free_addr_get will be used to allocate + * a free address. + * + * @param uuid UUID of the node. + * @param addr Address of the node's primary element. If 0, the lowest + * possible address available will be assigned to the node. + * @param num_elem Number of elements that the node has. + * @param net_idx NetIdx that the node was provisioned to. + * + * @return The new node or NULL if CDB has already allocated + * :kconfig:option:`CONFIG_BT_MESH_CDB_NODE_COUNT` nodes, or reached the end + * of the unicast address range, or if @c addr is non-zero and less than the + * lowest available address or collide with the allocated addresses. + */ + +struct bt_mesh_cdb_node *bt_mesh_cdb_node_alloc( + const uint8_t uuid[16], uint16_t addr, uint8_t num_elem, uint16_t net_idx); + +/** @brief Get the first available address for the given element count. + * + * @param num_elem Number of elements to accommodate. + * + * @return The first unicast address in an address range that allows a node + * with the given number of elements to fit. + */ + +uint16_t bt_mesh_cdb_free_addr_get(uint8_t num_elem); + +/** @brief Delete a node. + * + * Delete a node from the CDB. When deleting the node and the address of the + * last element of the deleted node is greater than the lowest available + * address, CDB will update the lowest available address. The lowest + * available address is reset and the deleted addresses can be reused only + * after IV Index update. + * + * @param node The node to be deleted. + * @param store If true, the node will be cleared from persistent storage. + */ + +void bt_mesh_cdb_node_del(struct bt_mesh_cdb_node *node, bool store); + +/** @brief Update a node. + * + * Assigns the node a new address and clears the previous persistent storage + * entry. + * + * @param node The node to be updated. + * @param addr New unicast address for the node. + * @param num_elem Updated number of elements in the node. + */ + +void bt_mesh_cdb_node_update(struct bt_mesh_cdb_node *node, + uint16_t addr, uint8_t num_elem); + +/** @brief Get a node by address. + * + * Try to find the node that has the provided address assigned to one of its + * elements. + * + * @param addr Address of the element to look for. + * + * @return The node that has an element with address addr or NULL if no such + * node exists. + */ + +struct bt_mesh_cdb_node *bt_mesh_cdb_node_get(uint16_t addr); + +/** @brief Store node to persistent storage. + * + * @param node Node to be stored. + */ + +void bt_mesh_cdb_node_store(const struct bt_mesh_cdb_node *node); + +/** @brief Import device key for selected node. + * + * Using security library with PSA implementation access to the key by + * pointer will not give a valid value since the key is hidden in the + * library. The application has to import the key. + * + * @param node Selected node. + * @param in key value. + * + * @return 0 on success or negative error code on failure. + */ + +int bt_mesh_cdb_node_key_import(struct bt_mesh_cdb_node *node, + const uint8_t in[16]); + +/** @brief Export device key from selected node. + * + * Using security library with PSA implementation access to the key by + * pointer will not give a valid value since the key is hidden in the + * library. The application has to export the key. + * + * @param node Selected node. + * @param out key value. + * + * @return 0 on success or negative error code on failure. + */ + +int bt_mesh_cdb_node_key_export(const struct bt_mesh_cdb_node *node, + uint8_t out[16]); + +enum +{ + BT_MESH_CDB_ITER_STOP = 0, + BT_MESH_CDB_ITER_CONTINUE, +}; + +/** @typedef bt_mesh_cdb_node_func_t + * @brief Node iterator callback. + * + * @param node Node found. + * @param user_data Data given. + * + * @return BT_MESH_CDB_ITER_CONTINUE to continue to iterate through the + * nodes or BT_MESH_CDB_ITER_STOP to stop. + */ + +typedef uint8_t (*bt_mesh_cdb_node_func_t)( + struct bt_mesh_cdb_node *node, void *user_data); + +/** @brief Node iterator. + * + * Iterate nodes in the Mesh Configuration Database. The callback function + * will only be called for valid, ie allocated, nodes. + * + * @param func Callback function. + * @param user_data Data to pass to the callback. + */ + +void bt_mesh_cdb_node_foreach(bt_mesh_cdb_node_func_t func, void *user_data); + +/** @brief Allocate a subnet. + * + * Allocate a new subnet in the CDB. + * + * @param net_idx NetIdx of the subnet. + * + * @return The new subnet or NULL if it cannot be allocated due to + * lack of resources or the subnet has been already allocated. + */ + +struct bt_mesh_cdb_subnet *bt_mesh_cdb_subnet_alloc(uint16_t net_idx); + +/** @brief Delete a subnet. + * + * Delete a subnet from the CDB. + * + * @param sub The subnet to be deleted. + * @param store If true, the subnet will be cleared from persistent storage. + */ + +void bt_mesh_cdb_subnet_del(struct bt_mesh_cdb_subnet *sub, bool store); + +/** @brief Get a subnet by NetIdx + * + * Try to find the subnet with the specified NetIdx. + * + * @param net_idx NetIdx of the subnet to look for. + * + * @return The subnet with the specified NetIdx or NULL if no such subnet + * exists. + */ + +struct bt_mesh_cdb_subnet *bt_mesh_cdb_subnet_get(uint16_t net_idx); + +/** @brief Store subnet to persistent storage. + * + * @param sub Subnet to be stored. + */ + +void bt_mesh_cdb_subnet_store(const struct bt_mesh_cdb_subnet *sub); + +/** @brief Get the flags for a subnet + * + * @param sub The subnet to get flags for. + * + * @return The flags for the subnet. + */ + +uint8_t bt_mesh_cdb_subnet_flags(const struct bt_mesh_cdb_subnet *sub); + +/** @brief Import network key for selected subnetwork. + * + * Using security library with PSA implementation access to the key by + * pointer will not give a valid value since the key is hidden in the + * library. The application has to import the key. + * + * @param sub Selected subnetwork. + * @param key_idx 0 or 1. If Key Refresh procedure is in progress then two + * keys are available. The old key has an index 0 and the new + * one has an index 1. Otherwise, the only key with index 0 + * exists. + * @param in key value. + * + * @return 0 on success or negative error code on failure. + */ + +int bt_mesh_cdb_subnet_key_import( + struct bt_mesh_cdb_subnet *sub, int key_idx, const uint8_t in[16]); + +/** @brief Export network key from selected subnetwork. + * + * Using security library with PSA implementation access to the key by + * pointer will not give a valid value since the key is hidden in the + * library. The application has to export the key. + * + * @param sub Selected subnetwork. + * @param key_idx 0 or 1. If Key Refresh procedure is in progress then two + * keys are available. The old key has an index 0 and the new + * one has an index 1. Otherwise, the only key with index 0 + * exists. + * @param out key value. + * + * @return 0 on success or negative error code on failure. + */ + +int bt_mesh_cdb_subnet_key_export( + const struct bt_mesh_cdb_subnet *sub, int key_idx, uint8_t out[16]); + +/** @brief Allocate an application key. + * + * Allocate a new application key in the CDB. + * + * @param net_idx NetIdx of NetKey that the application key is bound to. + * @param app_idx AppIdx of the application key. + * + * @return The new application key or NULL if it cannot be allocated due to + * lack of resources or the key has been already allocated. + */ + +struct bt_mesh_cdb_app_key *bt_mesh_cdb_app_key_alloc(uint16_t net_idx, + uint16_t app_idx); + +/** @brief Delete an application key. + * + * Delete an application key from the CDB. + * + * @param key The application key to be deleted. + * @param store If true, the key will be cleared from persistent storage. + */ + +void bt_mesh_cdb_app_key_del(struct bt_mesh_cdb_app_key *key, bool store); + +/** @brief Get an application key by AppIdx + * + * Try to find the application key with the specified AppIdx. + * + * @param app_idx AppIdx of the application key to look for. + * + * @return The application key with the specified AppIdx or NULL if no such + * key exists. + */ + +struct bt_mesh_cdb_app_key *bt_mesh_cdb_app_key_get(uint16_t app_idx); + +/** @brief Store application key to persistent storage. + * + * @param key Application key to be stored. + */ + +void bt_mesh_cdb_app_key_store(const struct bt_mesh_cdb_app_key *key); + +/** @brief Import application key. + * + * Using security library with PSA implementation access to the key by + * pointer will not give a valid value since the key is hidden in the + * library. The application has to import the key. + * + * @param key cdb application key structure. + * @param key_idx 0 or 1. If Key Refresh procedure is in progress then two + * keys are available. The old key has an index 0 and the new + * one has an index 1. Otherwise, the only key with index 0 + * exists. + * @param in key value. + * + * @return 0 on success or negative error code on failure. + */ + +int bt_mesh_cdb_app_key_import(struct bt_mesh_cdb_app_key *key, int key_idx, + const uint8_t in[16]); + +/** @brief Export application key. + * + * Using security library with PSA implementation access to the key by + * pointer will not give a valid value since the key is hidden in the + * library. The application has to export the key. + * + * @param key cdb application key structure. + * @param key_idx 0 or 1. If Key Refresh procedure is in progress then two + * keys are available. The old key has an index 0 and the new + * one has an index 1. Otherwise, the only key with index 0 + * exists. + * @param out key value. + * + * @return 0 on success or negative error code on failure. + */ + +int bt_mesh_cdb_app_key_export( + const struct bt_mesh_cdb_app_key *key, int key_idx, uint8_t out[16]); + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_CDB_H */ diff --git a/include/nuttx/net/bluetooth/mesh/cfg.h b/include/nuttx/net/bluetooth/mesh/cfg.h new file mode 100644 index 0000000000000..15779a809c78d --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/cfg.h @@ -0,0 +1,696 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/cfg.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Runtime configuration APIs. + */ + +/* Copyright (c) 2020 Nordic Semiconductor + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_CFG_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_CFG_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include + +/** + * @brief Runtime Configuration + * @defgroup bt_mesh_cfg Runtime Configuration + * @ingroup bt_mesh + * @{ + */ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Bluetooth Mesh feature states */ + +enum bt_mesh_feat_state +{ + /** Feature is supported, but disabled. */ + + BT_MESH_FEATURE_DISABLED, + + /** Feature is supported and enabled. */ + + BT_MESH_FEATURE_ENABLED, + + /** Feature is not supported, and cannot be enabled. */ + + BT_MESH_FEATURE_NOT_SUPPORTED, +}; + +/* Key Refresh Phase */ +#define BT_MESH_KR_NORMAL 0x00 +#define BT_MESH_KR_PHASE_1 0x01 +#define BT_MESH_KR_PHASE_2 0x02 +#define BT_MESH_KR_PHASE_3 0x03 + +/* Legacy feature defines */ +#define BT_MESH_RELAY_DISABLED BT_MESH_FEATURE_DISABLED +#define BT_MESH_RELAY_ENABLED BT_MESH_FEATURE_ENABLED +#define BT_MESH_RELAY_NOT_SUPPORTED BT_MESH_FEATURE_NOT_SUPPORTED + +#define BT_MESH_BEACON_DISABLED BT_MESH_FEATURE_DISABLED +#define BT_MESH_BEACON_ENABLED BT_MESH_FEATURE_ENABLED + +#define BT_MESH_PRIV_BEACON_DISABLED BT_MESH_FEATURE_DISABLED +#define BT_MESH_PRIV_BEACON_ENABLED BT_MESH_FEATURE_ENABLED + +#define BT_MESH_GATT_PROXY_DISABLED BT_MESH_FEATURE_DISABLED +#define BT_MESH_GATT_PROXY_ENABLED BT_MESH_FEATURE_ENABLED +#define BT_MESH_GATT_PROXY_NOT_SUPPORTED BT_MESH_FEATURE_NOT_SUPPORTED + +#define BT_MESH_PRIV_GATT_PROXY_DISABLED BT_MESH_FEATURE_DISABLED +#define BT_MESH_PRIV_GATT_PROXY_ENABLED BT_MESH_FEATURE_ENABLED +#define BT_MESH_PRIV_GATT_PROXY_NOT_SUPPORTED BT_MESH_FEATURE_NOT_SUPPORTED + +#define BT_MESH_FRIEND_DISABLED BT_MESH_FEATURE_DISABLED +#define BT_MESH_FRIEND_ENABLED BT_MESH_FEATURE_ENABLED +#define BT_MESH_FRIEND_NOT_SUPPORTED BT_MESH_FEATURE_NOT_SUPPORTED + +#define BT_MESH_NODE_IDENTITY_STOPPED BT_MESH_FEATURE_DISABLED +#define BT_MESH_NODE_IDENTITY_RUNNING BT_MESH_FEATURE_ENABLED +#define BT_MESH_NODE_IDENTITY_NOT_SUPPORTED BT_MESH_FEATURE_NOT_SUPPORTED + +/** @brief Enable or disable sending of the Secure Network Beacon. + * + * @param beacon New Secure Network Beacon state. + */ + +void bt_mesh_beacon_set(bool beacon); + +/** @brief Get the current Secure Network Beacon state. + * + * @returns Whether the Secure Network Beacon feature is enabled. + */ + +bool bt_mesh_beacon_enabled(void); + +/** @brief Enable or disable sending of the Mesh Private beacon. + * + * Support for the Private beacon state must be enabled with @c + * CONFIG_BT_MESH_PRIV_BEACONS. + * + * @param priv_beacon New Mesh Private beacon state. Must be one of + * @ref BT_MESH_FEATURE_ENABLED and + * @ref BT_MESH_FEATURE_DISABLED. + * + * @retval 0 Successfully changed the Mesh Private beacon feature state. + * @retval -ENOTSUP The Mesh Private beacon feature is not supported. + * @retval -EINVAL Invalid parameter. + * @retval -EALREADY Already in the given state. + */ + +int bt_mesh_priv_beacon_set(enum bt_mesh_feat_state priv_beacon); + +/** @brief Get the current Mesh Private beacon state. + * + * @returns The Mesh Private beacon feature state. + */ + +enum bt_mesh_feat_state bt_mesh_priv_beacon_get(void); + +/** @brief Set the current Mesh Private beacon update interval. + * + * The Mesh Private beacon's randomization value is updated regularly to + * maintain the node's privacy. The update interval controls how often the + * beacon is updated, in 10 second increments. + * + * @param interval Private beacon update interval in 10 second steps, or 0 to + * update on every beacon transmission. + */ + +void bt_mesh_priv_beacon_update_interval_set(uint8_t interval); + +/** @brief Get the current Mesh Private beacon update interval. + * + * The Mesh Private beacon's randomization value is updated regularly to + * maintain the node's privacy. The update interval controls how often the + * beacon is updated, in 10 second increments. + * + * @returns The Private beacon update interval in 10 second steps, or 0 if + * the beacon is updated every time it's transmitted. + */ + +uint8_t bt_mesh_priv_beacon_update_interval_get(void); + +/** @brief Set the default TTL value. + * + * The default TTL value is used when no explicit TTL value is set. Models + * will use the default TTL value when @ref bt_mesh_msg_ctx::send_ttl is + * @ref BT_MESH_TTL_DEFAULT. + * + * @param default_ttl The new default TTL value. Valid values are 0x00 and + * 0x02 to @ref BT_MESH_TTL_MAX. + * + * @retval 0 Successfully set the default TTL value. + * @retval -EINVAL Invalid TTL value. + */ + +int bt_mesh_default_ttl_set(uint8_t default_ttl); + +/** @brief Get the current default TTL value. + * + * @return The current default TTL value. + */ + +uint8_t bt_mesh_default_ttl_get(void); + +/** @brief Get the current Mesh On-Demand Private Proxy state. + * + * @retval 0 or positive value represents On-Demand Private Proxy feature + * state + * @retval -ENOTSUP The On-Demand Private Proxy feature is not supported. + */ + +int bt_mesh_od_priv_proxy_get(void); + +/** @brief Set state of Mesh On-Demand Private Proxy. + * + * Support for the On-Demand Private Proxy state must be enabled with @c + * BT_MESH_OD_PRIV_PROXY_SRV. + * + * @param on_demand_proxy New Mesh On-Demand Private Proxy state. Value of + * 0x00 means that advertising with Private Network + * Identity cannot be enabled on demand. Values in + * range 0x01 - 0xFF set interval of this advertising + * after valid Solicitation PDU is received or client + * disconnects. + * + * @retval 0 Successfully changed the Mesh On-Demand Private Proxy feature + * state. + * @retval -ENOTSUP The On-Demand Private Proxy feature is not supported. + * @retval -EINVAL Invalid parameter. + * @retval -EALREADY Already in the given state. + */ + +int bt_mesh_od_priv_proxy_set(uint8_t on_demand_proxy); + +/** @brief Set the Network Transmit parameters. + * + * The Network Transmit parameters determine the parameters local messages + * are transmitted with. + * + * @see BT_MESH_TRANSMIT + * + * @param xmit New Network Transmit parameters. Use @ref BT_MESH_TRANSMIT + * for encoding. + */ + +void bt_mesh_net_transmit_set(uint8_t xmit); + +/** @brief Get the current Network Transmit parameters. + * + * The @ref BT_MESH_TRANSMIT_COUNT and @ref BT_MESH_TRANSMIT_INT macros can + * be used to decode the Network Transmit parameters. + * + * @return The current Network Transmit parameters. + */ + +uint8_t bt_mesh_net_transmit_get(void); + +/** @brief Configure the Relay feature. + * + * Enable or disable the Relay feature, and configure the parameters to + * transmit relayed messages with. + * + * Support for the Relay feature must be enabled through the @c + * CONFIG_BT_MESH_RELAY configuration option. + * + * @see BT_MESH_TRANSMIT + * + * @param relay New Relay feature state. Must be one of + * @ref BT_MESH_FEATURE_ENABLED and + * @ref BT_MESH_FEATURE_DISABLED. + * @param xmit New Relay retransmit parameters. Use @ref BT_MESH_TRANSMIT + * for encoding. + * + * @retval 0 Successfully changed the Relay configuration. + * @retval -ENOTSUP The Relay feature is not supported. + * @retval -EINVAL Invalid parameter. + * @retval -EALREADY Already using the given parameters. + */ + +int bt_mesh_relay_set(enum bt_mesh_feat_state relay, uint8_t xmit); + +/** @brief Get the current Relay feature state. + * + * @returns The Relay feature state. + */ + +enum bt_mesh_feat_state bt_mesh_relay_get(void); + +/** @brief Get the current Relay Retransmit parameters. + * + * The @ref BT_MESH_TRANSMIT_COUNT and @ref BT_MESH_TRANSMIT_INT macros can + * be used to decode the Relay Retransmit parameters. + * + * @return The current Relay Retransmit parameters, or 0 if relay is not + * supported. + */ + +uint8_t bt_mesh_relay_retransmit_get(void); + +/** @brief Enable or disable the GATT Proxy feature. + * + * Support for the GATT Proxy feature must be enabled through the @c + * CONFIG_BT_MESH_GATT_PROXY configuration option. + * + * @note The GATT Proxy feature only controls a Proxy node's ability to + * relay messages to the mesh network. A node that supports GATT Proxy will + * still advertise Connectable Proxy beacons, even if the feature is + * disabled. The Proxy feature can only be fully disabled through compile + * time configuration. + * + * @param gatt_proxy New GATT Proxy state. Must be one of + * @ref BT_MESH_FEATURE_ENABLED and + * @ref BT_MESH_FEATURE_DISABLED. + * + * @retval 0 Successfully changed the GATT Proxy feature state. + * @retval -ENOTSUP The GATT Proxy feature is not supported. + * @retval -EINVAL Invalid parameter. + * @retval -EALREADY Already in the given state. + */ + +int bt_mesh_gatt_proxy_set(enum bt_mesh_feat_state gatt_proxy); + +/** @brief Get the current GATT Proxy state. + * + * @returns The GATT Proxy feature state. + */ + +enum bt_mesh_feat_state bt_mesh_gatt_proxy_get(void); + +/** @brief Enable or disable the Private GATT Proxy feature. + * + * Support for the Private GATT Proxy feature must be enabled through the + * @kconfig{CONFIG_BT_MESH_PRIV_BEACONS} and + * @kconfig{CONFIG_BT_MESH_GATT_PROXY} configuration options. + * + * @param priv_gatt_proxy New Private GATT Proxy state. Must be one of + * @ref BT_MESH_FEATURE_ENABLED and + * @ref BT_MESH_FEATURE_DISABLED. + * + * @retval 0 Successfully changed the Private GATT Proxy feature state. + * @retval -ENOTSUP The Private GATT Proxy feature is not supported. + * @retval -EINVAL Invalid parameter. + * @retval -EALREADY Already in the given state. + */ + +int bt_mesh_priv_gatt_proxy_set(enum bt_mesh_feat_state priv_gatt_proxy); + +/** @brief Get the current Private GATT Proxy state. + * + * @returns The Private GATT Proxy feature state. + */ + +enum bt_mesh_feat_state bt_mesh_priv_gatt_proxy_get(void); + +/** @brief Enable or disable the Friend feature. + * + * Any active friendships will be terminated immediately if the Friend + * feature is disabled. + * + * Support for the Friend feature must be enabled through the @c + * CONFIG_BT_MESH_FRIEND configuration option. + * + * @param friendship New Friend feature state. Must be one of + * @ref BT_MESH_FEATURE_ENABLED and + * @ref BT_MESH_FEATURE_DISABLED. + * + * @retval 0 Successfully changed the Friend feature state. + * @retval -ENOTSUP The Friend feature is not supported. + * @retval -EINVAL Invalid parameter. + * @retval -EALREADY Already in the given state. + */ + +int bt_mesh_friend_set(enum bt_mesh_feat_state friendship); + +/** @brief Get the current Friend state. + * + * @returns The Friend feature state. + */ + +enum bt_mesh_feat_state bt_mesh_friend_get(void); + +/** + * @brief Subnet Configuration + * @defgroup bt_mesh_cfg_subnet Subnet Configuration + * @{ + */ + +/** @brief Add a Subnet. + * + * Adds a subnet with the given network index and network key to the list of + * known Subnets. All messages sent on the given Subnet will be processed by + * this node, and the node may send and receive Network Beacons on the given + * Subnet. + * + * @param net_idx Network index. + * @param key Root network key of the Subnet. All other keys are derived + * from this. + * + * @retval STATUS_SUCCESS The Subnet was successfully added. + * @retval STATUS_INSUFF_RESOURCES No room for this Subnet. + * @retval STATUS_UNSPECIFIED The Subnet couldn't be created for some + * reason. + */ + +uint8_t bt_mesh_subnet_add(uint16_t net_idx, const uint8_t key[16]); + +/** @brief Update the given Subnet. + * + * Starts the Key Refresh procedure for this Subnet by adding a second set + * of encryption keys. The Subnet will continue sending with the old key + * (but receiving messages using both) until the Subnet enters Key Refresh + * phase 2. + * + * This allows a network configurator to replace old network and application + * keys for the entire network, effectively removing access for all nodes + * that aren't given the new keys. + * + * @param net_idx Network index. + * @param key New root network key of the Subnet. + * + * @retval STATUS_SUCCESS The Subnet was updated with a second key. + * @retval STATUS_INVALID_NETKEY The NetIdx is unknown. + * @retval STATUS_IDX_ALREADY_STORED The @c key value is the same as the + * current key. + * @retval STATUS_CANNOT_UPDATE The Subnet cannot be updated for some + * reason. + */ + +uint8_t bt_mesh_subnet_update(uint16_t net_idx, const uint8_t key[16]); + +/** @brief Delete a Subnet. + * + * Removes the Subnet with the given network index from the node. The node + * will stop sending Network Beacons with the given Subnet, and can no + * longer process messages on this Subnet. + * + * All Applications bound to this Subnet are also deleted. + * + * @param net_idx Network index. + * + * @retval STATUS_SUCCESS The Subnet was deleted. + * @retval STATUS_INVALID_NETKEY The NetIdx is unknown. + */ + +uint8_t bt_mesh_subnet_del(uint16_t net_idx); + +/** @brief Check whether a Subnet is known. + * + * @param net_idx Network index + * + * @return true if a Subnet with the given index exists, false otherwise. + */ + +bool bt_mesh_subnet_exists(uint16_t net_idx); + +/** @brief Set the Subnet's Key Refresh phase. + * + * The Key Refresh procedure is started by updating the Subnet keys through + * @ref bt_mesh_subnet_update. This puts the Subnet in Key Refresh Phase 1. + * Once all nodes have received the new Subnet key, Key Refresh Phase 2 can + * be activated through this function to start transmitting with the new + * network key. Finally, to revoke the old key, set the Key Refresh Phase to + * 3. This removes the old keys from the node, and returns the Subnet back + * to normal single-key operation with the new key set. + * + * @param net_idx Network index. + * @param phase Pointer to the new Key Refresh phase. Will return the + * actual Key Refresh phase after updating. + * + * @retval STATUS_SUCCESS The Key Refresh phase of the Subnet was + * successfully changed. + * @retval STATUS_INVALID_NETKEY The NetIdx is unknown. + * @retval STATUS_CANNOT_UPDATE The given phase change is invalid. + */ + +uint8_t bt_mesh_subnet_kr_phase_set(uint16_t net_idx, uint8_t *phase); + +/** @brief Get the Subnet's Key Refresh phase. + * + * @param net_idx Network index. + * @param phase Pointer to the Key Refresh variable to fill. + * + * @retval STATUS_SUCCESS Successfully populated the @c phase variable. + * @retval STATUS_INVALID_NETKEY The NetIdx is unknown. + */ + +uint8_t bt_mesh_subnet_kr_phase_get(uint16_t net_idx, uint8_t *phase); + +/** @brief Set the Node Identity state of the Subnet. + * + * The Node Identity state of a Subnet determines whether the Subnet + * advertises connectable Node Identity beacons for Proxy Clients to connect + * to. Once started, the Node Identity beacon runs for 60 seconds, or until + * it is stopped. + * + * This function serves the same purpose as @ref + * bt_mesh_proxy_identity_enable, but only acts on a single Subnet. + * + * GATT Proxy support must be enabled through + * @kconfig{CONFIG_BT_MESH_GATT_PROXY}. + * + * @param net_idx Network index. + * @param node_id New Node Identity state, must be either @ref + * BT_MESH_FEATURE_ENABLED or @ref BT_MESH_FEATURE_DISABLED. + * + * @retval STATUS_SUCCESS Successfully set the Node Identity state of the + * Subnet. + * @retval STATUS_INVALID_NETKEY The NetIdx is unknown. + * @retval STATUS_FEAT_NOT_SUPP The Node Identity feature is not supported. + * @retval STATUS_CANNOT_SET Couldn't set the Node Identity state. + */ + +uint8_t bt_mesh_subnet_node_id_set(uint16_t net_idx, + enum bt_mesh_feat_state node_id); + +/** @brief Get the Node Identity state of the Subnet. + * + * @param net_idx Network index. + * @param node_id Node Identity variable to fill. + * + * @retval STATUS_SUCCESS Successfully populated the @c node_id variable. + * @retval STATUS_INVALID_NETKEY The NetIdx is unknown. + */ + +uint8_t bt_mesh_subnet_node_id_get(uint16_t net_idx, + enum bt_mesh_feat_state *node_id); + +/** @brief Set the Private Node Identity state of the Subnet. + * + * The Private Node Identity state of a Subnet determines whether the Subnet + * advertises connectable Private Node Identity beacons for Proxy Clients to + * connect to. Once started, the Node Identity beacon runs for 60 seconds, + * or until it is stopped. + * + * Private Node Identity can only be enabled if regular Node Identity is not + * enabled for any subnet. + * + * GATT Proxy and Private Beacon support must be enabled through + * @kconfig{CONFIG_BT_MESH_GATT_PROXY} and + * @kconfig{CONFIG_BT_MESH_PRIV_BEACONS}. + * + * @param net_idx Network index. + * @param priv_node_id New Private Node Identity state, must be either @ref + * BT_MESH_FEATURE_ENABLED or @ref + * BT_MESH_FEATURE_DISABLED. + * + * @retval STATUS_SUCCESS Successfully set the Private Node Identity state + * of the Subnet. + * @retval STATUS_INVALID_NETKEY The NetIdx is unknown. + * @retval STATUS_FEAT_NOT_SUPP The Private Node Identity feature is not + * supported. + * @retval STATUS_TEMP_STATE_CHG_FAIL The Private Node Identity state cannot + * be enabled, because Node Identity state is already enabled. + * @retval STATUS_CANNOT_SET Couldn't set the Private Node Identity state. + */ + +uint8_t bt_mesh_subnet_priv_node_id_set(uint16_t net_idx, + enum bt_mesh_feat_state + priv_node_id); + +/** @brief Get the Private Node Identity state of the Subnet. + * + * @param net_idx Network index. + * @param priv_node_id Private Node Identity variable to fill. + * + * @retval STATUS_SUCCESS Successfully populated the @c priv_node_id + * variable. + * @retval STATUS_INVALID_NETKEY The NetIdx is unknown. + */ + +uint8_t bt_mesh_subnet_priv_node_id_get(uint16_t net_idx, + enum bt_mesh_feat_state *priv_node_id); + +/** @brief Get a list of all known Subnet indexes. + * + * Builds a list of all known Subnet indexes in the @c net_idxs array. If + * the @c net_idxs array is smaller than the list of known Subnets, this + * function fills all available entries and returns @c -ENOMEM. In this + * case, the next @c max entries of the list can be read out by calling + * @code bt_mesh_subnets_get(list, max, max); @endcode + * + * Note that any changes to the Subnet list between calls to this function + * could change the order and number of entries in the list. + * + * @param net_idxs Array to fill. + * @param max Max number of indexes to return. + * @param skip Number of indexes to skip. Enables batched processing of + * the list. + * + * @return The number of indexes added to the @c net_idxs array, or @c + * -ENOMEM + * if the number of known Subnets exceeds the @c max parameter. + */ + +ssize_t bt_mesh_subnets_get(uint16_t net_idxs[], size_t max, off_t skip); + +/** + * @} + */ + +/** + * @brief Application Configuration + * @defgroup bt_mesh_cfg_app Application Configuration + * @{ + */ + +/** @brief Add an Application key. + * + * Adds the Application with the given index to the list of known + * applications. Allows the node to send and receive model messages + * encrypted with this Application key. + * + * Every Application is bound to a specific Subnet. The node must know the + * Subnet the Application is bound to before it can add the Application. + * + * @param app_idx Application index. + * @param net_idx Network index the Application is bound to. + * @param key Application key value. + * + * @retval STATUS_SUCCESS The Application was successfully added. + * @retval STATUS_INVALID_NETKEY The NetIdx is unknown. + * @retval STATUS_INSUFF_RESOURCES There's no room for storing this + * Application. + * @retval STATUS_INVALID_BINDING This AppIdx is already bound to another + * Subnet. + * @retval STATUS_IDX_ALREADY_STORED This AppIdx is already stored with a + * different key value. + * @retval STATUS_CANNOT_SET Cannot set the Application key for some reason. + */ + +uint8_t bt_mesh_app_key_add(uint16_t app_idx, + uint16_t net_idx, const uint8_t key[16]); + +/** @brief Update an Application key. + * + * Update an Application with a second Application key, as part of the Key + * Refresh procedure of the bound Subnet. The node will continue + * transmitting with the old application key (but receiving on both) until + * the Subnet enters Key Refresh phase 2. Once the Subnet enters Key Refresh + * phase 3, the old application key will be deleted. + * + * @note The Application key can only be updated if the bound Subnet is in + * Key Refresh phase 1. + * + * @param app_idx Application index. + * @param net_idx Network index the Application is bound to, or + * @ref BT_MESH_KEY_ANY to skip the binding check. + * @param key New key value. + * + * @retval STATUS_SUCCESS The Application key was successfully updated. + * @retval STATUS_INVALID_NETKEY The NetIdx is unknown. + * @retval STATUS_INVALID_BINDING This AppIdx is not bound to the given + * NetIdx. + * @retval STATUS_CANNOT_UPDATE The Application key cannot be updated for + * some reason. + * @retval STATUS_IDX_ALREADY_STORED This AppIdx is already updated with a + * different key value. + */ + +uint8_t bt_mesh_app_key_update(uint16_t app_idx, + uint16_t net_idx, const uint8_t key[16]); + +/** @brief Delete an Application key. + * + * All models bound to this application will remove this binding. All models + * publishing with this application will stop publishing. + * + * @param app_idx Application index. + * @param net_idx Network index. + * + * @retval STATUS_SUCCESS The Application key was successfully deleted. + * @retval STATUS_INVALID_NETKEY The NetIdx is unknown. + * @retval STATUS_INVALID_BINDING This AppIdx is not bound to the given + * NetIdx. + */ + +uint8_t bt_mesh_app_key_del(uint16_t app_idx, uint16_t net_idx); + +/** @brief Check if an Application key is known. + * + * @param app_idx Application index. + * + * @return true if the Application is known, false otherwise. + */ + +bool bt_mesh_app_key_exists(uint16_t app_idx); + +/** @brief Get a list of all known Application key indexes. + * + * Builds a list of all Application indexes for the given network index in + * the @c app_idxs array. If the @c app_idxs array cannot fit all bound + * Applications, this function fills all available entries and returns @c + * -ENOMEM. In this case, the next @c max entries of the list can be read + * out by calling @code bt_mesh_app_keys_get(net_idx, list, max, max); + * @endcode + * + * Note that any changes to the Application key list between calls to this + * function could change the order and number of entries in the list. + * + * @param net_idx Network Index to get the Applications of, or @ref + * BT_MESH_KEY_ANY to get all Applications. + * @param app_idxs Array to fill. + * @param max Max number of indexes to return. + * @param skip Number of indexes to skip. Enables batched processing of + * the list. + * + * @return The number of indexes added to the @c app_idxs array, or @c + * -ENOMEM + * if the number of known Applications exceeds the @c max parameter. + */ + +ssize_t bt_mesh_app_keys_get(uint16_t net_idx, + uint16_t app_idxs[], size_t max, off_t skip); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_CFG_H */ diff --git a/include/nuttx/net/bluetooth/mesh/cfg_cli.h b/include/nuttx/net/bluetooth/mesh/cfg_cli.h new file mode 100644 index 0000000000000..6b026c8fe548f --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/cfg_cli.h @@ -0,0 +1,2149 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/cfg_cli.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Configuration Client Model APIs. + */ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_CFG_CLI_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_CFG_CLI_H + +/** + * @brief Configuration Client Model + * @defgroup bt_mesh_cfg_cli Configuration Client Model + * @ingroup bt_mesh + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct bt_mesh_cfg_cli; +struct bt_mesh_cfg_cli_hb_pub; +struct bt_mesh_cfg_cli_hb_sub; +struct bt_mesh_cfg_cli_mod_pub; + +/** Mesh Configuration Client Status messages callback */ + +struct bt_mesh_cfg_cli_cb +{ + /** @brief Optional callback for Composition data messages. + * + * Handles received Composition data messages from a server. + * + * @note For decoding @c buf, please refer to + * @ref bt_mesh_comp_p0_get and + * @ref bt_mesh_comp_p1_elem_pull. + * + * @param cli Client that received the status message. + * @param addr Address of the sender. + * @param page Composition data page. + * @param buf Composition data buffer. + */ + + void (*comp_data)(struct bt_mesh_cfg_cli *cli, uint16_t addr, uint8_t page, + struct net_buf_simple *buf); + + /** @brief Optional callback for Model Pub status messages. + * + * Handles received Model Pub status messages from a server. + * + * @param cli Client that received the status message. + * @param addr Address of the sender. + * @param status Status code for the message. + * @param elem_addr Address of the element. + * @param mod_id Model ID. + * @param cid Company ID. + * @param pub Publication configuration parameters. + */ + +void (*mod_pub_status)(struct bt_mesh_cfg_cli *cli, + uint16_t addr, + uint8_t status, + uint16_t elem_addr, + uint16_t mod_id, + uint16_t cid, + struct bt_mesh_cfg_cli_mod_pub *pub); + + /** @brief Optional callback for Model Sub Status messages. + * + * Handles received Model Sub Status messages from a server. + * + * @param cli Client that received the status message. + * @param addr Address of the sender. + * @param status Status Code for requesting message. + * @param elem_addr The unicast address of the element. + * @param sub_addr The sub address. + * @param mod_id The model ID within the element. + */ + +void (*mod_sub_status)(struct bt_mesh_cfg_cli *cli, + uint16_t addr, + uint8_t status, + uint16_t elem_addr, + uint16_t sub_addr, + uint32_t mod_id); + + /** @brief Optional callback for Model Sub list messages. + * + * Handles received Model Sub list messages from a server. + * + * @note The @c buf parameter should be decoded using + * @ref net_buf_simple_pull_le16 in iteration, as long + * as @c buf->len is greater than or equal to 2. + * + * @param cli Client that received the status message. + * @param addr Address of the sender. + * @param status Status code for the message. + * @param elem_addr Address of the element. + * @param mod_id Model ID. + * @param cid Company ID. + * @param buf Message buffer containing subscription addresses. + */ + +void (*mod_sub_list)(struct bt_mesh_cfg_cli *cli, + uint16_t addr, + uint8_t status, + uint16_t elem_addr, + uint16_t mod_id, + uint16_t cid, + struct net_buf_simple *buf); + + /** @brief Optional callback for Node Reset Status messages. + * + * Handles received Node Reset Status messages from a server. + * + * @param cli Client that received the status message. + * @param addr Address of the sender. + */ + + void (*node_reset_status)(struct bt_mesh_cfg_cli *cli, uint16_t addr); + + /** @brief Optional callback for Beacon Status messages. + * + * Handles received Beacon Status messages from a server. + * + * @param cli Client that received the status message. + * @param addr Address of the sender. + * @param status Status Code for requesting message. + */ + + void (*beacon_status)(struct bt_mesh_cfg_cli *cli, uint16_t addr, + uint8_t status); + + /** @brief Optional callback for Default TTL Status messages. + * + * Handles received Default TTL Status messages from a server. + * + * @param cli Client that received the status message. + * @param addr Address of the sender. + * @param status Status Code for requesting message. + */ + + void (*ttl_status)(struct bt_mesh_cfg_cli *cli, uint16_t addr, + uint8_t status); + + /** @brief Optional callback for Friend Status messages. + * + * Handles received Friend Status messages from a server. + * + * @param cli Client that received the status message. + * @param addr Address of the sender. + * @param status Status Code for requesting message. + */ + + void (*friend_status)(struct bt_mesh_cfg_cli *cli, uint16_t addr, + uint8_t status); + + /** @brief Optional callback for GATT Proxy Status messages. + * + * Handles received GATT Proxy Status messages from a server. + * + * @param cli Client that received the status message. + * @param addr Address of the sender. + * @param status Status Code for requesting message. + */ + + void (*gatt_proxy_status)(struct bt_mesh_cfg_cli *cli, uint16_t addr, + uint8_t status); + + /** @brief Optional callback for Network Transmit Status messages. + * + * Handles received Network Transmit Status messages from a server. + * + * @param cli Client that received the status message. + * @param addr Address of the sender. + * @param status Status Code for requesting message. + */ + + void (*network_transmit_status)(struct bt_mesh_cfg_cli *cli, + uint16_t addr, uint8_t status); + + /** @brief Optional callback for Relay Status messages. + * + * Handles received Relay Status messages from a server. + * + * @param cli Client that received the status message. + * @param addr Address of the sender. + * @param status Status Code for requesting message. + * @param transmit The relay retransmit count and interval steps. + */ + +void (*relay_status)(struct bt_mesh_cfg_cli *cli, + uint16_t addr, + uint8_t status, + uint8_t transmit); + + /** @brief Optional callback for NetKey Status messages. + * + * Handles received NetKey Status messages from a server. + * + * @param cli Client that received the status message. + * @param addr Address of the sender. + * @param status Status Code for requesting message. + * @param net_idx The index of the NetKey. + */ + +void (*net_key_status)(struct bt_mesh_cfg_cli *cli, + uint16_t addr, + uint8_t status, + uint16_t net_idx); + + /** @brief Optional callback for Netkey list messages. + * + * Handles received Netkey list messages from a server. + * + * @note The @c buf parameter should be decoded using the + * @ref bt_mesh_key_idx_unpack_list helper function. + * + * @param cli Client that received the status message. + * @param addr Address of the sender. + * @param buf Message buffer containing key indexes. + */ + + void (*net_key_list)(struct bt_mesh_cfg_cli *cli, uint16_t addr, + struct net_buf_simple *buf); + + /** @brief Optional callback for AppKey Status messages. + * + * Handles received AppKey Status messages from a server. + * + * @param cli Client that received the status message. + * @param addr Address of the sender. + * @param status Status Code for requesting message. + * @param net_idx The index of the NetKey. + * @param app_idx The index of the AppKey. + */ + +void (*app_key_status)(struct bt_mesh_cfg_cli *cli, + uint16_t addr, + uint8_t status, + uint16_t net_idx, + uint16_t app_idx); + + /** @brief Optional callback for Appkey list messages. + * + * Handles received Appkey list messages from a server. + * + * @note The @c buf parameter should be decoded using the + * @ref bt_mesh_key_idx_unpack_list helper function. + * + * @param cli Client that received the status message. + * @param addr Address of the sender. + * @param status Status code for the message. + * @param net_idx The index of the NetKey. + * @param buf Message buffer containing key indexes. + */ + +void (*app_key_list)(struct bt_mesh_cfg_cli *cli, + uint16_t addr, + uint8_t status, + uint16_t net_idx, + struct net_buf_simple *buf); + + /** @brief Optional callback for Model App Status messages. + * + * Handles received Model App Status messages from a server. + * + * @param cli Client that received the status message. + * @param addr Address of the sender. + * @param status Status Code for requesting message. + * @param elem_addr The unicast address of the element. + * @param app_idx The application key index. + * @param mod_id The model ID within the element. + */ + +void (*mod_app_status)(struct bt_mesh_cfg_cli *cli, + uint16_t addr, + uint8_t status, + uint16_t elem_addr, + uint16_t app_idx, + uint32_t mod_id); + + /** @brief Optional callback for Model App list messages. + * + * Handles received Model App list messages from a server. + * + * @note The @c buf parameter should be decoded using the + * @ref bt_mesh_key_idx_unpack_list helper function. + * + * @param cli Client that received the status message. + * @param addr Address of the sender. + * @param status Status code for the message. + * @param elem_addr Address of the element. + * @param mod_id Model ID. + * @param cid Company ID. + * @param buf Message buffer containing key indexes. + */ + +void (*mod_app_list)(struct bt_mesh_cfg_cli *cli, + uint16_t addr, + uint8_t status, + uint16_t elem_addr, + uint16_t mod_id, + uint16_t cid, + struct net_buf_simple *buf); + + /** @brief Optional callback for Node Identity Status messages. + * + * Handles received Node Identity Status messages from a server. + * + * @param cli Client that received the status message. + * @param addr Address of the sender. + * @param status Status Code for requesting message. + * @param net_idx The index of the NetKey. + * @param identity The node identity state. + */ + +void (*node_identity_status)(struct bt_mesh_cfg_cli *cli, + uint16_t addr, + uint8_t status, + uint16_t net_idx, + uint8_t identity); + + /** @brief Optional callback for LPN PollTimeout Status messages. + * + * Handles received LPN PollTimeout Status messages from a server. + * + * @param cli Client that received the status message. + * @param addr Address of the sender. + * @param elem_addr The unicast address of the LPN. + * @param timeout Current value of PollTimeout timer of the LPN. + */ + +void (*lpn_timeout_status)(struct bt_mesh_cfg_cli *cli, + uint16_t addr, + uint16_t elem_addr, + uint32_t timeout); + + /** @brief Optional callback for Key Refresh Phase status messages. + * + * Handles received Key Refresh Phase status messages from a server. + * + * @param cli Client that received the status message. + * @param addr Address of the sender. + * @param status Status code for the message. + * @param net_idx The index of the NetKey. + * @param phase Phase of the KRP. + */ + +void (*krp_status)(struct bt_mesh_cfg_cli *cli, + uint16_t addr, + uint8_t status, + uint16_t net_idx, + uint8_t phase); + + /** @brief Optional callback for Heartbeat pub status messages. + * + * Handles received Heartbeat pub status messages from a server. + * + * @param cli Client that received the status message. + * @param addr Address of the sender. + * @param status Status code for the message. + * @param pub HB publication configuration parameters. + */ + +void (*hb_pub_status)(struct bt_mesh_cfg_cli *cli, + uint16_t addr, + uint8_t status, + struct bt_mesh_cfg_cli_hb_pub *pub); + + /** @brief Optional callback for Heartbeat Sub status messages. + * + * Handles received Heartbeat Sub status messages from a server. + * + * @param cli Client that received the status message. + * @param addr Address of the sender. + * @param status Status code for the message. + * @param sub HB subscription configuration parameters. + */ + +void (*hb_sub_status)(struct bt_mesh_cfg_cli *cli, + uint16_t addr, + uint8_t status, + struct bt_mesh_cfg_cli_hb_sub *sub); +}; + +/** Mesh Configuration Client Model Context */ + +struct bt_mesh_cfg_cli +{ + /** Composition data model entry pointer. */ + + const struct bt_mesh_model *model; + + /** Optional callback for Mesh Configuration Client Status messages. */ + + const struct bt_mesh_cfg_cli_cb *cb; + + /* Internal parameters for tracking message responses. */ + + struct bt_mesh_msg_ack_ctx ack_ctx; +}; + +/** + * @brief Generic Configuration Client model composition data entry. + * + * @param cli_data Pointer to a @ref bt_mesh_cfg_cli instance. + */ +#define BT_MESH_MODEL_CFG_CLI(cli_data) \ +BT_MESH_MODEL_CNT_CB(BT_MESH_MODEL_ID_CFG_CLI, bt_mesh_cfg_cli_op, NULL, cli_data, 1, 0, \ + &bt_mesh_cfg_cli_cb) + +/* @brief Helper macro to encode model publication period in units of 100ms + * + * @param steps Number of 100ms steps. + * + * @return Encoded value that can be assigned to + * bt_mesh_cfg_cli_mod_pub.period + */ +#define BT_MESH_PUB_PERIOD_100MS(steps) ((steps) & BIT_MASK(6)) + +/** + * @brief Helper macro to encode model publication period in units of 1 + * second + * + * @param steps Number of 1 second steps. + * + * @return Encoded value that can be assigned to + * bt_mesh_cfg_cli_mod_pub.period + */ +#define BT_MESH_PUB_PERIOD_SEC(steps) (((steps) & BIT_MASK(6)) | (1 << 6)) + +/* @brief Helper macro to encode model publication period in units of 10 + * seconds + * + * @param steps Number of 10 second steps. + * + * @return Encoded value that can be assigned to + * bt_mesh_cfg_cli_mod_pub.period + */ +#define BT_MESH_PUB_PERIOD_10SEC(steps) (((steps) & BIT_MASK(6)) | (2 << 6)) + +/* @brief Helper macro to encode model publication period in units of 10 + * minutes + * + * @param steps Number of 10 minute steps. + * + * @return Encoded value that can be assigned to + * bt_mesh_cfg_cli_mod_pub.period + */ +#define BT_MESH_PUB_PERIOD_10MIN(steps) (((steps) & BIT_MASK(6)) | (3 << 6)) + +/** Model publication configuration parameters. */ + +struct bt_mesh_cfg_cli_mod_pub +{ + /** Publication destination address. */ + + uint16_t addr; + + /** Virtual address UUID, or NULL if this is not a virtual address. */ + + const uint8_t *uuid; + + /** Application index to publish with. */ + + uint16_t app_idx; + + /** Friendship credential flag. */ + + bool cred_flag; + + /** Time To Live to publish with. */ + + uint8_t ttl; + /** Encoded publish period. + * @see BT_MESH_PUB_PERIOD_100MS, BT_MESH_PUB_PERIOD_SEC, + * BT_MESH_PUB_PERIOD_10SEC, BT_MESH_PUB_PERIOD_10MIN + */ + + uint8_t period; + /** Encoded transmit parameters. + * @see BT_MESH_TRANSMIT + */ + + uint8_t transmit; +}; + +/** Heartbeat subscription configuration parameters. */ + +struct bt_mesh_cfg_cli_hb_sub +{ + /** Source address to receive Heartbeat messages from. */ + + uint16_t src; + + /** Destination address to receive Heartbeat messages on. */ + + uint16_t dst; + /** Logarithmic subscription period to keep listening for. The decoded + * subscription period is (1 << (period - 1)) seconds, or 0 seconds if + * period is 0. + */ + + uint8_t period; + /** Logarithmic Heartbeat subscription receive count. The decoded Heartbeat + * count is (1 << (count - 1)) if count is between 1 and 0xfe, 0 if count + * is 0 and 0xffff if count is 0xff. + * + * Ignored in Heartbeat subscription set. + */ + + uint8_t count; + /** Minimum hops in received messages, ie the shortest registered path from + * the publishing node to the subscribing node. A Heartbeat received from + * an immediate neighbor has hop count = 1. + * + * Ignored in Heartbeat subscription set. + */ + + uint8_t min; + /** Maximum hops in received messages, ie the longest registered path from + * the publishing node to the subscribing node. A Heartbeat received from + * an immediate neighbor has hop count = 1. + * + * Ignored in Heartbeat subscription set. + */ + + uint8_t max; +}; + +/** Heartbeat publication configuration parameters. */ + +struct bt_mesh_cfg_cli_hb_pub +{ + /** Heartbeat destination address. */ + + uint16_t dst; + /** Logarithmic Heartbeat count. Decoded as (1 << (count - 1)) if count is + * between 1 and 0x11, 0 if count is 0, or "indefinitely" if count is 0xff. + * + * When used in Heartbeat publication set, this parameter denotes the + * number of Heartbeat messages to send. + * + * When returned from Heartbeat publication get, this parameter denotes the + * number of Heartbeat messages remaining to be sent. + */ + + uint8_t count; + /** Logarithmic Heartbeat publication transmit interval in seconds. Decoded + * as (1 << (period - 1)) if period is between 1 and 0x11. If period is 0, + * Heartbeat publication is disabled. + */ + + uint8_t period; + + /** Publication message Time To Live value. */ + + uint8_t ttl; + /** Bitmap of features that trigger Heartbeat publications. Legal values + * are @ref BT_MESH_FEAT_RELAY, + * @ref BT_MESH_FEAT_PROXY, @ref BT_MESH_FEAT_FRIEND and + * @ref BT_MESH_FEAT_LOW_POWER + */ + + uint16_t feat; + + /** Network index to publish with. */ + + uint16_t net_idx; +}; + +/** @brief Reset the target node and remove it from the network. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param status Status response parameter + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_node_reset(uint16_t net_idx, + uint16_t addr, bool *status); + +/** @brief Get the target node's composition data. + * + * If the other device does not have the given composition data page, it + * will return the largest page number it supports that is less than the + * requested page index. The actual page the device responds with is + * returned in @c rsp. + * + * This method can be used asynchronously by setting @p rsp and @p comp as + * NULL. This way the method will not wait for response and will return + * immediately after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param page Composition data page, or 0xff to request the first + * available page. + * @param rsp Return parameter for the returned page number, or NULL. + * @param comp Composition data buffer to fill. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_comp_data_get( + uint16_t net_idx, uint16_t addr, uint8_t page, uint8_t *rsp, + struct net_buf_simple *comp); + +/** @brief Get the target node's network beacon state. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param status Status response parameter, returns one of + * @ref BT_MESH_BEACON_DISABLED or @ref + * BT_MESH_BEACON_ENABLED on success. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_beacon_get(uint16_t net_idx, + uint16_t addr, uint8_t *status); + +/** @brief Get the target node's network key refresh phase state. + * + * This method can be used asynchronously by setting @p status and @p phase + * as NULL. This way the method will not wait for response and will return + * immediately after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param key_net_idx Network key index. + * @param status Status response parameter. + * @param phase Pointer to the Key Refresh variable to fill. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_krp_get( + uint16_t net_idx, uint16_t addr, uint16_t key_net_idx, uint8_t *status, + uint8_t *phase); + +/** @brief Set the target node's network key refresh phase parameters. + * + * This method can be used asynchronously by setting @p status and @p phase + * as NULL. This way the method will not wait for response and will return + * immediately after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param key_net_idx Network key index. + * @param transition Transition parameter. + * @param status Status response parameter. + * @param phase Pointer to the new Key Refresh phase. Will return the + * actual Key Refresh phase after updating. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_krp_set( + uint16_t net_idx, uint16_t addr, uint16_t key_net_idx, uint8_t transition, + uint8_t *status, uint8_t *phase); + +/** @brief Set the target node's network beacon state. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param val New network beacon state, should be one of + * @ref BT_MESH_BEACON_DISABLED or @ref + * BT_MESH_BEACON_ENABLED. + * @param status Status response parameter. Returns one of + * @ref BT_MESH_BEACON_DISABLED or @ref + * BT_MESH_BEACON_ENABLED on success. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_beacon_set(uint16_t net_idx, + uint16_t addr, uint8_t val, uint8_t *status); + +/** @brief Get the target node's Time To Live value. + * + * This method can be used asynchronously by setting @p ttl as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param ttl TTL response buffer. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_ttl_get(uint16_t net_idx, uint16_t addr, uint8_t *ttl); + +/** @brief Set the target node's Time To Live value. + * + * This method can be used asynchronously by setting @p ttl as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param val New Time To Live value. + * @param ttl TTL response buffer. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_ttl_set(uint16_t net_idx, + uint16_t addr, uint8_t val, uint8_t *ttl); + +/** @brief Get the target node's Friend feature status. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param status Status response parameter. Returns one of + * @ref BT_MESH_FRIEND_DISABLED, @ref BT_MESH_FRIEND_ENABLED + * or + * @ref BT_MESH_FRIEND_NOT_SUPPORTED on success. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_friend_get(uint16_t net_idx, + uint16_t addr, uint8_t *status); + +/** @brief Set the target node's Friend feature state. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param val New Friend feature state. Should be one of + * @ref BT_MESH_FRIEND_DISABLED or + * @ref BT_MESH_FRIEND_ENABLED. + * @param status Status response parameter. Returns one of + * @ref BT_MESH_FRIEND_DISABLED, @ref BT_MESH_FRIEND_ENABLED + * or + * @ref BT_MESH_FRIEND_NOT_SUPPORTED on success. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_friend_set(uint16_t net_idx, + uint16_t addr, uint8_t val, uint8_t *status); + +/** @brief Get the target node's Proxy feature state. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param status Status response parameter. Returns one of + * @ref BT_MESH_GATT_PROXY_DISABLED, + * @ref BT_MESH_GATT_PROXY_ENABLED or + * @ref BT_MESH_GATT_PROXY_NOT_SUPPORTED on success. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_gatt_proxy_get(uint16_t net_idx, + uint16_t addr, uint8_t *status); + +/** @brief Set the target node's Proxy feature state. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param val New Proxy feature state. Must be one of + * @ref BT_MESH_GATT_PROXY_DISABLED or + * @ref BT_MESH_GATT_PROXY_ENABLED. + * @param status Status response parameter. Returns one of + * @ref BT_MESH_GATT_PROXY_DISABLED, + * @ref BT_MESH_GATT_PROXY_ENABLED or + * @ref BT_MESH_GATT_PROXY_NOT_SUPPORTED on success. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_gatt_proxy_set(uint16_t net_idx, + uint16_t addr, uint8_t val, + uint8_t *status); + +/** @brief Get the target node's network_transmit state. + * + * This method can be used asynchronously by setting @p transmit as NULL. + * This way the method will not wait for response and will return + * immediately after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param transmit Network transmit response parameter. Returns the encoded + * network transmission parameters on success. Decoded with + * @ref BT_MESH_TRANSMIT_COUNT and @ref + * BT_MESH_TRANSMIT_INT. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_net_transmit_get(uint16_t net_idx, + uint16_t addr, uint8_t *transmit); + +/** @brief Set the target node's network transmit parameters. + * + * This method can be used asynchronously by setting @p transmit as NULL. + * This way the method will not wait for response and will return + * immediately after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param val New encoded network transmit parameters. + * @see BT_MESH_TRANSMIT. + * @param transmit Network transmit response parameter. Returns the + * encoded network transmission parameters on success. + * Decoded with + * @ref BT_MESH_TRANSMIT_COUNT and @ref + * BT_MESH_TRANSMIT_INT. + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_net_transmit_set( + uint16_t net_idx, uint16_t addr, uint8_t val, uint8_t *transmit); + +/** @brief Get the target node's Relay feature state. + * + * This method can be used asynchronously by setting @p status and @p + * transmit as NULL. This way the method will not wait for response and will + * return immediately after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param status Status response parameter. Returns one of + * @ref BT_MESH_RELAY_DISABLED, @ref BT_MESH_RELAY_ENABLED + * or + * @ref BT_MESH_RELAY_NOT_SUPPORTED on success. + * @param transmit Transmit response parameter. Returns the encoded relay + * transmission parameters on success. Decoded with + * @ref BT_MESH_TRANSMIT_COUNT and @ref + * BT_MESH_TRANSMIT_INT. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_relay_get( + uint16_t net_idx, uint16_t addr, uint8_t *status, uint8_t *transmit); + +/** @brief Set the target node's Relay parameters. + * + * This method can be used asynchronously by setting @p status and @p + * transmit as NULL. This way the method will not wait for response and will + * return immediately after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param new_relay New relay state. Must be one of + * @ref BT_MESH_RELAY_DISABLED or + * @ref BT_MESH_RELAY_ENABLED. + * @param new_transmit New encoded relay transmit parameters. + * @see BT_MESH_TRANSMIT. + * @param status Status response parameter. Returns one of + * @ref BT_MESH_RELAY_DISABLED, @ref + * BT_MESH_RELAY_ENABLED or @ref BT_MESH_RELAY_NOT_SUPPORTED on success. + * @param transmit Transmit response parameter. Returns the encoded + * relay transmission parameters on success. Decoded + * with + * @ref BT_MESH_TRANSMIT_COUNT and + * @ref BT_MESH_TRANSMIT_INT. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_relay_set( + uint16_t net_idx, uint16_t addr, uint8_t new_relay, uint8_t new_transmit, + uint8_t *status, uint8_t *transmit); + +/** @brief Add a network key to the target node. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param key_net_idx Network key index. + * @param net_key Network key. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_net_key_add( + uint16_t net_idx, uint16_t addr, uint16_t key_net_idx, + const uint8_t net_key[16], uint8_t *status); + +/** @brief Get a list of the target node's network key indexes. + * + * This method can be used asynchronously by setting @p keys or @p key_cnt + * as NULL. This way the method will not wait for response and will return + * immediately after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param keys Net key index list response parameter. Will be filled with + * all the returned network key indexes it can fill. + * @param key_cnt Net key index list length. Should be set to the capacity + * of the @c keys list when calling. Will return the number + * of returned network key indexes upon success. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_net_key_get(uint16_t net_idx, + uint16_t addr, uint16_t *keys, + size_t *key_cnt); + +/** @brief Delete a network key from the target node. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param key_net_idx Network key index. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_net_key_del( + uint16_t net_idx, uint16_t addr, uint16_t key_net_idx, uint8_t *status); + +/** @brief Add an application key to the target node. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param key_net_idx Network key index the application key belongs to. + * @param key_app_idx Application key index. + * @param app_key Application key. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_app_key_add( + uint16_t net_idx, uint16_t addr, uint16_t key_net_idx, + uint16_t key_app_idx, const uint8_t app_key[16], uint8_t *status); + +/** @brief Get a list of the target node's application key indexes for a + * specific network key. + * + * This method can be used asynchronously by setting @p status and ( @p keys + * or @p key_cnt ) as NULL. This way the method will not wait for response + * and will return immediately after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param key_net_idx Network key index to request the app key indexes of. + * @param status Status response parameter. + * @param keys App key index list response parameter. Will be filled + * with all the returned application key indexes it can + * fill. + * @param key_cnt App key index list length. Should be set to the + * capacity of the @c keys list when calling. Will return + * the number of returned application key indexes upon + * success. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_app_key_get( + uint16_t net_idx, uint16_t addr, uint16_t key_net_idx, uint8_t *status, + uint16_t *keys, size_t *key_cnt); + +/** @brief Delete an application key from the target node. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param key_net_idx Network key index the application key belongs to. + * @param key_app_idx Application key index. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_app_key_del( + uint16_t net_idx, uint16_t addr, uint16_t key_net_idx, + uint16_t key_app_idx, uint8_t *status); + +/** @brief Bind an application to a SIG model on the target node. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param elem_addr Element address the model is in. + * @param mod_app_idx Application index to bind. + * @param mod_id Model ID. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_mod_app_bind( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_app_idx, + uint16_t mod_id, uint8_t *status); + +/** @brief Unbind an application from a SIG model on the target node. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param elem_addr Element address the model is in. + * @param mod_app_idx Application index to unbind. + * @param mod_id Model ID. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_mod_app_unbind( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_app_idx, + uint16_t mod_id, uint8_t *status); + +/** @brief Bind an application to a vendor model on the target node. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param elem_addr Element address the model is in. + * @param mod_app_idx Application index to bind. + * @param mod_id Model ID. + * @param cid Company ID of the model. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_mod_app_bind_vnd( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_app_idx, + uint16_t mod_id, uint16_t cid, uint8_t *status); + +/** @brief Unbind an application from a vendor model on the target node. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param elem_addr Element address the model is in. + * @param mod_app_idx Application index to unbind. + * @param mod_id Model ID. + * @param cid Company ID of the model. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_mod_app_unbind_vnd( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_app_idx, + uint16_t mod_id, uint16_t cid, uint8_t *status); + +/** @brief Get a list of all applications bound to a SIG model on the target + * node. + * + * This method can be used asynchronously by setting @p status and ( @p apps + * or @p app_cnt ) as NULL. This way the method will not wait for response + * and will return immediately after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param elem_addr Element address the model is in. + * @param mod_id Model ID. + * @param status Status response parameter. + * @param apps App index list response parameter. Will be filled with + * all the returned application key indexes it can fill. + * @param app_cnt App index list length. Should be set to the capacity of + * the + * @c apps list when calling. Will return the number of + * returned application key indexes upon success. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_mod_app_get( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_id, + uint8_t *status, uint16_t *apps, size_t *app_cnt); + +/** @brief Get a list of all applications bound to a vendor model on the + * target node. + * + * This method can be used asynchronously by setting @p status and ( @p apps + * or @p app_cnt ) as NULL. This way the method will not wait for response + * and will return immediately after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param elem_addr Element address the model is in. + * @param mod_id Model ID. + * @param cid Company ID of the model. + * @param status Status response parameter. + * @param apps App index list response parameter. Will be filled with + * all the returned application key indexes it can fill. + * @param app_cnt App index list length. Should be set to the capacity of + * the + * @c apps list when calling. Will return the number of + * returned application key indexes upon success. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_mod_app_get_vnd( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_id, + uint16_t cid, uint8_t *status, uint16_t *apps, size_t *app_cnt); + +/** @brief Get publish parameters for a SIG model on the target node. + * + * This method can be used asynchronously by setting @p status and @p pub as + * NULL. This way the method will not wait for response and will return + * immediately after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param elem_addr Element address the model is in. + * @param mod_id Model ID. + * @param pub Publication parameter return buffer. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_mod_pub_get( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_id, + struct bt_mesh_cfg_cli_mod_pub *pub, uint8_t *status); + +/** @brief Get publish parameters for a vendor model on the target node. + * + * This method can be used asynchronously by setting @p status and @p pub as + * NULL. This way the method will not wait for response and will return + * immediately after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param elem_addr Element address the model is in. + * @param mod_id Model ID. + * @param cid Company ID of the model. + * @param pub Publication parameter return buffer. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_mod_pub_get_vnd( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_id, + uint16_t cid, struct bt_mesh_cfg_cli_mod_pub *pub, uint8_t *status); + +/** @brief Set publish parameters for a SIG model on the target node. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @p pub shall not be NULL. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param elem_addr Element address the model is in. + * @param mod_id Model ID. + * @param pub Publication parameters. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_mod_pub_set( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_id, + struct bt_mesh_cfg_cli_mod_pub *pub, uint8_t *status); + +/** @brief Set publish parameters for a vendor model on the target node. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @p pub shall not be NULL. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param elem_addr Element address the model is in. + * @param mod_id Model ID. + * @param cid Company ID of the model. + * @param pub Publication parameters. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_mod_pub_set_vnd( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_id, + uint16_t cid, struct bt_mesh_cfg_cli_mod_pub *pub, uint8_t *status); + +/** @brief Add a group address to a SIG model's subscription list. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param elem_addr Element address the model is in. + * @param sub_addr Group address to add to the subscription list. + * @param mod_id Model ID. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_mod_sub_add( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t sub_addr, + uint16_t mod_id, uint8_t *status); + +/** @brief Add a group address to a vendor model's subscription list. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param elem_addr Element address the model is in. + * @param sub_addr Group address to add to the subscription list. + * @param mod_id Model ID. + * @param cid Company ID of the model. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_mod_sub_add_vnd( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t sub_addr, + uint16_t mod_id, uint16_t cid, uint8_t *status); + +/** @brief Delete a group address in a SIG model's subscription list. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param elem_addr Element address the model is in. + * @param sub_addr Group address to add to the subscription list. + * @param mod_id Model ID. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_mod_sub_del( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t sub_addr, + uint16_t mod_id, uint8_t *status); + +/** @brief Delete a group address in a vendor model's subscription list. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param elem_addr Element address the model is in. + * @param sub_addr Group address to add to the subscription list. + * @param mod_id Model ID. + * @param cid Company ID of the model. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_mod_sub_del_vnd( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t sub_addr, + uint16_t mod_id, uint16_t cid, uint8_t *status); + +/** @brief Overwrite all addresses in a SIG model's subscription list with a + * group address. + * + * Deletes all subscriptions in the model's subscription list, and adds a + * single group address instead. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param elem_addr Element address the model is in. + * @param sub_addr Group address to add to the subscription list. + * @param mod_id Model ID. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_mod_sub_overwrite( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t sub_addr, + uint16_t mod_id, uint8_t *status); + +/** @brief Overwrite all addresses in a vendor model's subscription list with + * a group address. + * + * Deletes all subscriptions in the model's subscription list, and adds a + * single group address instead. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param elem_addr Element address the model is in. + * @param sub_addr Group address to add to the subscription list. + * @param mod_id Model ID. + * @param cid Company ID of the model. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_mod_sub_overwrite_vnd( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t sub_addr, + uint16_t mod_id, uint16_t cid, uint8_t *status); + +/** @brief Add a virtual address to a SIG model's subscription list. + * + * This method can be used asynchronously by setting @p status and @p + * virt_addr as NULL. This way the method will not wait for response and + * will return immediately after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param elem_addr Element address the model is in. + * @param label Virtual address label to add to the subscription list. + * @param mod_id Model ID. + * @param virt_addr Virtual address response parameter. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_mod_sub_va_add( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, + const uint8_t label[16], uint16_t mod_id, uint16_t *virt_addr, + uint8_t *status); + +/** @brief Add a virtual address to a vendor model's subscription list. + * + * This method can be used asynchronously by setting @p status and @p + * virt_addr as NULL. This way the method will not wait for response and + * will return immediately after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param elem_addr Element address the model is in. + * @param label Virtual address label to add to the subscription list. + * @param mod_id Model ID. + * @param cid Company ID of the model. + * @param virt_addr Virtual address response parameter. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_mod_sub_va_add_vnd( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, + const uint8_t label[16], uint16_t mod_id, uint16_t cid, + uint16_t *virt_addr, uint8_t *status); + +/** @brief Delete a virtual address in a SIG model's subscription list. + * + * This method can be used asynchronously by setting @p status and @p + * virt_addr as NULL. This way the method will not wait for response and + * will return immediately after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param elem_addr Element address the model is in. + * @param label Virtual address parameter to add to the subscription + * list. + * @param mod_id Model ID. + * @param virt_addr Virtual address response parameter. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_mod_sub_va_del( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, + const uint8_t label[16], uint16_t mod_id, uint16_t *virt_addr, + uint8_t *status); + +/** @brief Delete a virtual address in a vendor model's subscription list. + * + * This method can be used asynchronously by setting @p status and @p + * virt_addr as NULL. This way the method will not wait for response and + * will return immediately after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param elem_addr Element address the model is in. + * @param label Virtual address label to add to the subscription list. + * @param mod_id Model ID. + * @param cid Company ID of the model. + * @param virt_addr Virtual address response parameter. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_mod_sub_va_del_vnd( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, + const uint8_t label[16], uint16_t mod_id, uint16_t cid, + uint16_t *virt_addr, uint8_t *status); + +/** @brief Overwrite all addresses in a SIG model's subscription list with a + * virtual address. + * + * Deletes all subscriptions in the model's subscription list, and adds a + * single group address instead. + * + * This method can be used asynchronously by setting @p status and @p + * virt_addr as NULL. This way the method will not wait for response and + * will return immediately after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param elem_addr Element address the model is in. + * @param label Virtual address label to add to the subscription list. + * @param mod_id Model ID. + * @param virt_addr Virtual address response parameter. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_mod_sub_va_overwrite( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, + const uint8_t label[16], uint16_t mod_id, uint16_t *virt_addr, + uint8_t *status); + +/** @brief Overwrite all addresses in a vendor model's subscription list with + * a virtual address. + * + * Deletes all subscriptions in the model's subscription list, and adds a + * single group address instead. + * + * This method can be used asynchronously by setting @p status and @p + * virt_addr as NULL. This way the method will not wait for response and + * will return immediately after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param elem_addr Element address the model is in. + * @param label Virtual address label to add to the subscription list. + * @param mod_id Model ID. + * @param cid Company ID of the model. + * @param virt_addr Virtual address response parameter. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_mod_sub_va_overwrite_vnd( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, + const uint8_t label[16], uint16_t mod_id, uint16_t cid, + uint16_t *virt_addr, uint8_t *status); + +/** @brief Get the subscription list of a SIG model on the target node. + * + * This method can be used asynchronously by setting @p status and ( @p subs + * or @p sub_cnt ) as NULL. This way the method will not wait for response + * and will return immediately after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param elem_addr Element address the model is in. + * @param mod_id Model ID. + * @param status Status response parameter. + * @param subs Subscription list response parameter. Will be filled + * with all the returned subscriptions it can fill. + * @param sub_cnt Subscription list element count. Should be set to the + * capacity of the @c subs list when calling. Will return + * the number of returned subscriptions upon success. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_mod_sub_get( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_id, + uint8_t *status, uint16_t *subs, size_t *sub_cnt); + +/** @brief Get the subscription list of a vendor model on the target node. + * + * This method can be used asynchronously by setting @p status and ( @p subs + * or @p sub_cnt ) as NULL. This way the method will not wait for response + * and will return immediately after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param elem_addr Element address the model is in. + * @param mod_id Model ID. + * @param cid Company ID of the model. + * @param status Status response parameter. + * @param subs Subscription list response parameter. Will be filled + * with all the returned subscriptions it can fill. + * @param sub_cnt Subscription list element count. Should be set to the + * capacity of the @c subs list when calling. Will return + * the number of returned subscriptions upon success. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_mod_sub_get_vnd( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_id, + uint16_t cid, uint8_t *status, uint16_t *subs, size_t *sub_cnt); + +/** @brief Set the target node's Heartbeat subscription parameters. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @p sub shall not be null. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param sub New Heartbeat subscription parameters. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_hb_sub_set(uint16_t net_idx, + uint16_t addr, + struct bt_mesh_cfg_cli_hb_sub *sub, + uint8_t *status); + +/** @brief Get the target node's Heartbeat subscription parameters. + * + * This method can be used asynchronously by setting @p status and @p sub as + * NULL. This way the method will not wait for response and will return + * immediately after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param sub Heartbeat subscription parameter return buffer. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_hb_sub_get(uint16_t net_idx, + uint16_t addr, + struct bt_mesh_cfg_cli_hb_sub *sub, + uint8_t *status); + +/** @brief Set the target node's Heartbeat publication parameters. + * + * @note The target node must already have received the specified network + * key. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @p pub shall not be NULL; + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param pub New Heartbeat publication parameters. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_hb_pub_set(uint16_t net_idx, + uint16_t addr, + const struct bt_mesh_cfg_cli_hb_pub *pub, + uint8_t *status); + +/** @brief Get the target node's Heartbeat publication parameters. + * + * This method can be used asynchronously by setting @p status and @p pub as + * NULL. This way the method will not wait for response and will return + * immediately after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param pub Heartbeat publication parameter return buffer. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_hb_pub_get(uint16_t net_idx, + uint16_t addr, + struct bt_mesh_cfg_cli_hb_pub *pub, + uint8_t *status); + +/** @brief Delete all group addresses in a SIG model's subscription list. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param elem_addr Element address the model is in. + * @param mod_id Model ID. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_mod_sub_del_all( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_id, + uint8_t *status); + +/** @brief Delete all group addresses in a vendor model's subscription list. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param elem_addr Element address the model is in. + * @param mod_id Model ID. + * @param cid Company ID of the model. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_mod_sub_del_all_vnd( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_id, + uint16_t cid, uint8_t *status); + +/** @brief Update a network key to the target node. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param key_net_idx Network key index. + * @param net_key Network key. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_net_key_update( + uint16_t net_idx, uint16_t addr, uint16_t key_net_idx, + const uint8_t net_key[16], uint8_t *status); + +/** @brief Update an application key to the target node. + * + * This method can be used asynchronously by setting @p status as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param key_net_idx Network key index the application key belongs to. + * @param key_app_idx Application key index. + * @param app_key Application key. + * @param status Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_app_key_update( + uint16_t net_idx, uint16_t addr, uint16_t key_net_idx, + uint16_t key_app_idx, const uint8_t app_key[16], uint8_t *status); + +/** @brief Set the Node Identity parameters. + * + * This method can be used asynchronously by setting @p status and @p + * identity as NULL. This way the method will not wait for response and will + * return immediately after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param new_identity New identity state. Must be one of + * @ref BT_MESH_NODE_IDENTITY_STOPPED or + * @ref BT_MESH_NODE_IDENTITY_RUNNING + * @param key_net_idx Network key index the application key belongs to. + * @param status Status response parameter. + * @param identity Identity response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_node_identity_set( + uint16_t net_idx, uint16_t addr, uint16_t key_net_idx, + uint8_t new_identity, uint8_t *status, uint8_t *identity); + +/** @brief Get the Node Identity parameters. + * + * This method can be used asynchronously by setting @p status and @p + * identity as NULL. This way the method will not wait for response and will + * return immediately after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param key_net_idx Network key index the application key belongs to. + * @param status Status response parameter. + * @param identity Identity response parameter. Must be one of + * @ref BT_MESH_NODE_IDENTITY_STOPPED or + * @ref BT_MESH_NODE_IDENTITY_RUNNING + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_node_identity_get( + uint16_t net_idx, uint16_t addr, uint16_t key_net_idx, uint8_t *status, + uint8_t *identity); + +/** @brief Get the Low Power Node Polltimeout parameters. + * + * This method can be used asynchronously by setting @p polltimeout as NULL. + * This way the method will not wait for response and will return + * immediately after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param unicast_addr LPN unicast address. + * @param polltimeout Poll timeout response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_cfg_cli_lpn_timeout_get( + uint16_t net_idx, uint16_t addr, uint16_t unicast_addr, + int32_t *polltimeout); + +/** @brief Get the current transmission timeout value. + * + * @return The configured transmission timeout in milliseconds. + */ + +int32_t bt_mesh_cfg_cli_timeout_get(void); + +/** @brief Set the transmission timeout value. + * + * @param timeout The new transmission timeout. + */ + +void bt_mesh_cfg_cli_timeout_set(int32_t timeout); + +/** Parsed Composition data page 0 representation. + * + * Should be pulled from the return buffer passed to + * @ref bt_mesh_cfg_cli_comp_data_get using + * @ref bt_mesh_comp_p0_get. + */ + +struct bt_mesh_comp_p0 +{ + /** Company ID */ + + uint16_t cid; + + /** Product ID */ + + uint16_t pid; + + /** Version ID */ + + uint16_t vid; + + /** Replay protection list size */ + + uint16_t crpl; + + /** Supported features, see @ref BT_MESH_FEAT_SUPPORTED. */ + + uint16_t feat; + + struct net_buf_simple *_buf; +}; + +/** Composition data page 0 element representation */ + +struct bt_mesh_comp_p0_elem +{ + /** Element location */ + + uint16_t loc; + + /** The number of SIG models in this element */ + + size_t nsig; + + /** The number of vendor models in this element */ + + size_t nvnd; + + uint8_t *_buf; +}; + +/** @brief Create a composition data page 0 representation from a buffer. + * + * The composition data page object will take ownership over the buffer, + * which should not be manipulated directly after this call. + * + * This function can be used in combination with @ref + * bt_mesh_cfg_cli_comp_data_get to read out composition data page 0 from + * other devices: + * + * @code NET_BUF_SIMPLE_DEFINE(buf, BT_MESH_RX_SDU_MAX); struct + * bt_mesh_comp_p0 comp; + * + * err = bt_mesh_cfg_cli_comp_data_get(net_idx, addr, 0, &page, &buf); if + * (!err) + * { + * bt_mesh_comp_p0_get(&comp, &buf); } @endcode + * + * @param buf Network buffer containing composition data. + * @param comp Composition data structure to fill. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_comp_p0_get(struct bt_mesh_comp_p0 *comp, + struct net_buf_simple *buf); + +/** @brief Pull a composition data page 0 element from a composition data + * page 0 instance. + * + * Each call to this function will pull out a new element from the + * composition data page, until all elements have been pulled. + * + * @param comp Composition data page + * @param elem Element to fill. + * + * @return A pointer to @c elem on success, or NULL if no more elements + * could be pulled. + */ + +struct bt_mesh_comp_p0_elem *bt_mesh_comp_p0_elem_pull( + const struct bt_mesh_comp_p0 *comp, struct bt_mesh_comp_p0_elem *elem); + +/** @brief Get a SIG model from the given composition data page 0 element. + * + * @param elem Element to read the model from. + * @param idx Index of the SIG model to read. + * + * @return The Model ID of the SIG model at the given index, or 0xffff if + * the index is out of bounds. + */ + +uint16_t bt_mesh_comp_p0_elem_mod(struct bt_mesh_comp_p0_elem *elem, + int idx); + +/** @brief Get a vendor model from the given composition data page 0 element. + * + * @param elem Element to read the model from. + * @param idx Index of the vendor model to read. + * + * @return The model ID of the vendor model at the given index, or + * {0xffff, 0xffff} if the index is out of bounds. + */ + +struct bt_mesh_mod_id_vnd bt_mesh_comp_p0_elem_mod_vnd( + struct bt_mesh_comp_p0_elem *elem, int idx); + +/** Composition data page 1 element representation */ + +struct bt_mesh_comp_p1_elem +{ + /** The number of SIG models in this element */ + + size_t nsig; + + /** The number of vendor models in this element */ + + size_t nvnd; + + /** Buffer containing SIG and Vendor Model Items */ + + struct net_buf_simple *_buf; +}; + +/** Composition data page 1 model item representation */ + +struct bt_mesh_comp_p1_model_item +{ + /** Corresponding_Group_ID field indicator */ + + bool cor_present; + + /** Determines the format of Extended Model Item */ + + bool format; + + /** Number of items in Extended Model Items */ + + uint8_t ext_item_cnt : 6; + /** Buffer containing Extended Model Items. + * If cor_present is set to 1 it starts with Corresponding_Group_ID + */ + + uint8_t cor_id; + struct net_buf_simple *_buf; +}; + +/** Extended Model Item in short representation */ + +struct bt_mesh_comp_p1_item_short +{ + /** Element address modifier */ + + uint8_t elem_offset : 3; + + /** Model Index */ + + uint8_t mod_item_idx : 5; +}; + +/** Extended Model Item in long representation */ + +struct bt_mesh_comp_p1_item_long +{ + /** Element address modifier */ + + uint8_t elem_offset; + + /** Model Index */ + + uint8_t mod_item_idx; +}; + +/** Extended Model Item */ + +struct bt_mesh_comp_p1_ext_item +{ + enum + { + SHORT, + LONG + } type; + + union + { + /** Item in short representation */ + + struct bt_mesh_comp_p1_item_short short_item; + + /** Item in long representation */ + + struct bt_mesh_comp_p1_item_long long_item; + }; +}; + +/** @brief Pull a Composition Data Page 1 Element from a composition data + * page 1 instance. + * + * Each call to this function will pull out a new element from the + * composition data page, until all elements have been pulled. + * + * @param buf Composition data page 1 buffer + * @param elem Element to fill. + * + * @return A pointer to @c elem on success, or NULL if no more elements + * could be pulled. + */ + +struct bt_mesh_comp_p1_elem *bt_mesh_comp_p1_elem_pull( + struct net_buf_simple *buf, struct bt_mesh_comp_p1_elem *elem); + +/** @brief Pull a Composition Data Page 1 Model Item from a Composition Data + * Page 1 Element + * + * Each call to this function will pull out a new item from the Composition + * Data Page 1 Element, until all items have been pulled. + * + * @param elem Composition data page 1 Element + * @param item Model Item to fill. + * + * @return A pointer to @c item on success, or NULL if no more elements + * could be pulled. + */ + +struct bt_mesh_comp_p1_model_item * +bt_mesh_comp_p1_item_pull(struct bt_mesh_comp_p1_elem *elem, + struct bt_mesh_comp_p1_model_item *item); + +/** @brief Pull Extended Model Item contained in Model Item + * + * Each call to this function will pull out a new element from the Extended + * Model Item, until all elements have been pulled. + * + * @param item Model Item to pull Extended Model Items from + * @param ext_item Extended Model Item to fill + * + * @return A pointer to @c ext_item on success, or NULL if item could not be + * pulled + */ + +struct bt_mesh_comp_p1_ext_item * +bt_mesh_comp_p1_pull_ext_item(struct bt_mesh_comp_p1_model_item *item, + struct bt_mesh_comp_p1_ext_item *ext_item); + +/** Composition data page 2 record parsing structure. */ + +struct bt_mesh_comp_p2_record +{ + /** Mesh profile ID. */ + + uint16_t id; + + /** Mesh Profile Version. */ + + struct + { + /** Major version. */ + + uint8_t x; + + /** Minor version. */ + + uint8_t y; + + /** Z version. */ + + uint8_t z; + } version; + + /** Element offset buffer. */ + + struct net_buf_simple *elem_buf; + + /** Additional data buffer. */ + + struct net_buf_simple *data_buf; +}; + +/** @brief Pull a Composition Data Page 2 Record from a composition data page + * 2 instance. + * + * Each call to this function will pull out a new element from the + * composition data page, until all elements have been pulled. + * + * @param buf Composition data page 2 buffer + * @param record Record to fill. + * + * @return A pointer to @c record on success, or NULL if no more elements + * could be pulled. + */ + +struct bt_mesh_comp_p2_record *bt_mesh_comp_p2_record_pull( + struct net_buf_simple *buf, struct bt_mesh_comp_p2_record *record); + +/** @brief Unpack a list of key index entries from a buffer. + * + * On success, @c dst_cnt is set to the amount of unpacked key index entries. + * + * @param buf Message buffer containing encoded AppKey or NetKey Indexes. + * @param dst_arr Destination array for the unpacked list. + * @param dst_cnt Size of the destination array. + * + * @return 0 on success. + * @return -EMSGSIZE if dst_arr size is to small to parse full message. + */ + +int bt_mesh_key_idx_unpack_list(struct net_buf_simple *buf, + uint16_t *dst_arr, size_t *dst_cnt); + +/** @cond INTERNAL_HIDDEN */ + +extern const struct bt_mesh_model_op bt_mesh_cfg_cli_op[]; +extern const struct bt_mesh_model_cb bt_mesh_cfg_cli_cb; + +/** @endcond */ + +#ifdef __cplusplus +} +#endif +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_CFG_CLI_H */ diff --git a/include/nuttx/net/bluetooth/mesh/cfg_srv.h b/include/nuttx/net/bluetooth/mesh/cfg_srv.h new file mode 100644 index 0000000000000..67847abb33a1d --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/cfg_srv.h @@ -0,0 +1,53 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/cfg_srv.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Configuration Server Model APIs. + */ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_CFG_SRV_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_CFG_SRV_H + +/** + * @brief Configuration Server Model + * @defgroup bt_mesh_cfg_srv Configuration Server Model + * @ingroup bt_mesh + * @{ + */ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Generic Configuration Server model composition data entry. + */ +#define BT_MESH_MODEL_CFG_SRV \ +BT_MESH_MODEL_CNT_CB(BT_MESH_MODEL_ID_CFG_SRV, bt_mesh_cfg_srv_op, NULL, NULL, 1, 0, \ + &bt_mesh_cfg_srv_cb) + +/** @cond INTERNAL_HIDDEN */ + +extern const struct bt_mesh_model_op bt_mesh_cfg_srv_op[]; +extern const struct bt_mesh_model_cb bt_mesh_cfg_srv_cb; + +/** @endcond */ + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_CFG_SRV_H */ diff --git a/include/nuttx/net/bluetooth/mesh/dfd.h b/include/nuttx/net/bluetooth/mesh/dfd.h new file mode 100644 index 0000000000000..ee5eccb247a94 --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/dfd.h @@ -0,0 +1,171 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/dfd.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Header file for the Bluetooth Mesh Firmware Distribution models + * API. + * @ingroup bt_mesh_dfd + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_DFD_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_DFD_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @defgroup bt_mesh_dfd Firmware Distribution models + * @ingroup bt_mesh + * @{ + */ + +/** Firmware distribution status. */ + +enum bt_mesh_dfd_status +{ + /** The message was processed successfully. */ + + BT_MESH_DFD_SUCCESS, + + /** Insufficient resources on the node. */ + + BT_MESH_DFD_ERR_INSUFFICIENT_RESOURCES, + + /** The operation cannot be performed while the Server is in the current + * phase. + */ + + BT_MESH_DFD_ERR_WRONG_PHASE, + + /** An internal error occurred on the node. */ + + BT_MESH_DFD_ERR_INTERNAL, + + /** The requested firmware image is not stored on the Distributor. */ + + BT_MESH_DFD_ERR_FW_NOT_FOUND, + + /** The AppKey identified by the AppKey Index is not known to the node. + */ + + BT_MESH_DFD_ERR_INVALID_APPKEY_INDEX, + + /** There are no Target nodes in the Distribution Receivers List state. */ + + BT_MESH_DFD_ERR_RECEIVERS_LIST_EMPTY, + + /** Another firmware image distribution is in progress. */ + + BT_MESH_DFD_ERR_BUSY_WITH_DISTRIBUTION, + + /** Another upload is in progress. */ + + BT_MESH_DFD_ERR_BUSY_WITH_UPLOAD, + + /** The URI scheme name indicated by the Update URI is not supported. */ + + BT_MESH_DFD_ERR_URI_NOT_SUPPORTED, + + /** The format of the Update URI is invalid. */ + + BT_MESH_DFD_ERR_URI_MALFORMED, + + /** The URI is currently unreachable. */ + + BT_MESH_DFD_ERR_URI_UNREACHABLE, + + /** The Check Firmware OOB procedure did not find any new firmware. */ + + BT_MESH_DFD_ERR_NEW_FW_NOT_AVAILABLE, + + /** The suspension of the Distribute Firmware procedure failed. */ + + BT_MESH_DFD_ERR_SUSPEND_FAILED, +}; + +/** Firmware distribution phases. */ + +enum bt_mesh_dfd_phase +{ + /** No firmware distribution is in progress. */ + + BT_MESH_DFD_PHASE_IDLE, + + /** Firmware distribution is in progress. */ + + BT_MESH_DFD_PHASE_TRANSFER_ACTIVE, + + /** The Transfer BLOB procedure has completed successfully. */ + + BT_MESH_DFD_PHASE_TRANSFER_SUCCESS, + + /** The Apply Firmware on Target Nodes procedure is being executed. */ + + BT_MESH_DFD_PHASE_APPLYING_UPDATE, + + /** The Distribute Firmware procedure has completed successfully. */ + + BT_MESH_DFD_PHASE_COMPLETED, + + /** The Distribute Firmware procedure has failed. */ + + BT_MESH_DFD_PHASE_FAILED, + + /** The Cancel Firmware Update procedure is being executed. */ + + BT_MESH_DFD_PHASE_CANCELING_UPDATE, + + /** The Transfer BLOB procedure is suspended. */ + + BT_MESH_DFD_PHASE_TRANSFER_SUSPENDED, +}; + +/** Firmware upload phases. */ + +enum bt_mesh_dfd_upload_phase +{ + /** No firmware upload is in progress. */ + + BT_MESH_DFD_UPLOAD_PHASE_IDLE, + + /** The Store Firmware procedure is being executed. */ + + BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_ACTIVE, + + /** The Store Firmware procedure or Store Firmware OOB procedure failed. + */ + + BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_ERROR, + + /** The Store Firmware procedure or the Store Firmware OOB procedure + * completed successfully. + */ + + BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_SUCCESS, +}; + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_DFD_H */ diff --git a/include/nuttx/net/bluetooth/mesh/dfd_srv.h b/include/nuttx/net/bluetooth/mesh/dfd_srv.h new file mode 100644 index 0000000000000..3bf8cbe4bdc26 --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/dfd_srv.h @@ -0,0 +1,351 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/dfd_srv.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @defgroup bt_mesh_dfd_srv Firmware Distribution Server model + * @ingroup bt_mesh_dfd + * @{ + * @brief API for the Firmware Distribution Server model + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_DFD_SRV_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_DFD_SRV_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifndef CONFIG_BT_MESH_DFD_SRV_TARGETS_MAX +#define CONFIG_BT_MESH_DFD_SRV_TARGETS_MAX 0 +#endif + +#ifndef CONFIG_BT_MESH_DFD_SRV_SLOT_MAX_SIZE +#define CONFIG_BT_MESH_DFD_SRV_SLOT_MAX_SIZE 0 +#endif + +#ifndef CONFIG_BT_MESH_DFD_SRV_SLOT_SPACE +#define CONFIG_BT_MESH_DFD_SRV_SLOT_SPACE 0 +#endif + +struct bt_mesh_dfd_srv; + +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD +/* @brief Initialization parameters for the @ref bt_mesh_dfd_srv with OOB + * upload support. + * + * @param[in] _cb Pointer to a @ref bt_mesh_dfd_srv_cb + * instance. + * @param[in] _oob_schemes Array of OOB schemes supported by the + * server, each scheme being a code point from + * the Bluetooth SIG Assigned Numbers + * document. + * @param[in] _oob_schemes_count Number of schemes in @c _oob_schemes. + */ +#define BT_MESH_DFD_SRV_OOB_INIT(_cb, _oob_schemes, _oob_schemes_count) \ +{ \ + .cb = _cb, \ + .dfu = BT_MESH_DFU_CLI_INIT(&_bt_mesh_dfd_srv_dfu_cb), \ + .upload = \ + { \ + .blob = {.cb = &_bt_mesh_dfd_srv_blob_cb}, \ + }, \ + .oob_schemes = \ + { \ + .schemes = _oob_schemes, \ + .count = _oob_schemes_count, \ + }, \ +} +#endif /* CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD */ + +/* @brief Initialization parameters for the @ref bt_mesh_dfd_srv. + * + * @param[in] _cb Pointer to a @ref bt_mesh_dfd_srv_cb instance. + */ +#define BT_MESH_DFD_SRV_INIT(_cb) \ +{ \ + .cb = _cb, \ + .dfu = BT_MESH_DFU_CLI_INIT(&_bt_mesh_dfd_srv_dfu_cb), \ + .upload = \ + { \ + .blob = {.cb = &_bt_mesh_dfd_srv_blob_cb}, \ + }, \ +} + +/* @brief Firmware Distribution Server model Composition Data entry. + * + * @param _srv Pointer to a @ref bt_mesh_dfd_srv instance. + */ +#define BT_MESH_MODEL_DFD_SRV(_srv) \ +BT_MESH_MODEL_DFU_CLI(&(_srv)->dfu), BT_MESH_MODEL_BLOB_SRV(&(_srv)->upload.blob), \ + BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_DFD_SRV, _bt_mesh_dfd_srv_op, NULL, _srv, \ + &_bt_mesh_dfd_srv_cb) + +/** Firmware Distribution Server callbacks: */ + +struct bt_mesh_dfd_srv_cb +{ + /** @brief Slot receive callback. + * + * Called at the start of an upload procedure. The callback must fill @c + * io with a pointer to a writable BLOB stream for the Firmware + * Distribution Server to write the firmware image to. + * + * @param srv Firmware Distribution Server model instance. + * @param slot DFU image slot being received. + * @param io BLOB stream response pointer. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int (*recv)(struct bt_mesh_dfd_srv *srv, + const struct bt_mesh_dfu_slot *slot, + const struct bt_mesh_blob_io **io); + +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD + /** @brief Firmware upload OOB start callback. + * + * Called at the start of an OOB firmware upload. The application must + * start a firmware check using an OOB mechanism, and then call + * @ref bt_mesh_dfd_srv_oob_check_complete. Depending on the return + * value of this function, the application must then start storing the + * firmware image using an OOB mechanism, and call + * @ref bt_mesh_dfd_srv_oob_store_complete. This callback is mandatory + * to support OOB uploads. + * + * @param srv Firmware Distribution Server model instance. + * @param slot Slot to be used for the upload. + * @param uri Pointer to buffer containing the URI used to check + * for new firmware. + * @param uri_len Length of the URI buffer. + * @param fwid Pointer to buffer containing the current firmware + * ID to be used when checking for availability of new + * firmware. + * @param fwid_len Length of the current firmware ID. Must be set to + * the length of the new firmware ID if it is + * available, or to 0 if new firmware is not + * available. + * + * @return BT_MESH_DFD_SUCCESS on success, or error code otherwise. + */ + +int (*start_oob_upload)(struct bt_mesh_dfd_srv *srv, + const struct bt_mesh_dfu_slot *slot, + const char *uri, + uint8_t uri_len, + const uint8_t *fwid, + uint16_t fwid_len); + + /** @brief Cancel store OOB callback + * + * Called when an OOB store is cancelled. The application must stop any + * ongoing OOB image transfer. This callback is mandatory to support OOB + * uploads. + * + * @param srv Firmware Distribution Server model instance. + * @param slot DFU image slot to cancel + */ + + void (*cancel_oob_upload)(struct bt_mesh_dfd_srv *srv, + const struct bt_mesh_dfu_slot *slot); + + /** @brief Get the progress of an ongoing OOB store + * + * Called by the Firmware Distribution Server model when it needs to get + * the current progress of an ongoing OOB store from the application. This + * callback is mandatory to support OOB uploads. + * + * @param srv Firmware Distribution Server model instance. + * @param slot DFU image slot to get progress for. + * + * @return The current progress of the ongoing OOB store, in percent. + */ + + uint8_t (*oob_progress_get)(struct bt_mesh_dfd_srv *srv, + const struct bt_mesh_dfu_slot *slot); +#endif /* CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD */ + + /** @brief Slot delete callback. + * + * Called when the Firmware Distribution Server is about to delete a DFU + * image slot. All allocated data associated with the firmware slot should + * be deleted. + * + * @param srv Firmware Update Server instance. + * @param slot DFU image slot being deleted. + */ + + void (*del)(struct bt_mesh_dfd_srv *srv, + const struct bt_mesh_dfu_slot *slot); + + /** @brief Slot send callback. + * + * Called at the start of a distribution procedure. The callback must fill + * @c io with a pointer to a readable BLOB stream for the Firmware + * Distribution Server to read the firmware image from. + * + * @param srv Firmware Distribution Server model instance. + * @param slot DFU image slot being sent. + * @param io BLOB stream response pointer. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int (*send)(struct bt_mesh_dfd_srv *srv, + const struct bt_mesh_dfu_slot *slot, + const struct bt_mesh_blob_io **io); + + /** @brief Phase change callback (Optional). + * + * Called whenever the phase of the Firmware Distribution Server changes. + * + * @param srv Firmware Distribution Server model instance. + * @param phase New Firmware Distribution phase. + */ + + void (*phase)(struct bt_mesh_dfd_srv *srv, enum bt_mesh_dfd_phase phase); +}; + +/** Firmware Distribution Server instance. */ + +struct bt_mesh_dfd_srv +{ + const struct bt_mesh_dfd_srv_cb *cb; + const struct bt_mesh_model *mod; + struct bt_mesh_dfu_cli dfu; + struct bt_mesh_dfu_target targets[CONFIG_BT_MESH_DFD_SRV_TARGETS_MAX]; + struct bt_mesh_blob_target_pull + pull_ctxs[CONFIG_BT_MESH_DFD_SRV_TARGETS_MAX]; + const struct bt_mesh_blob_io *io; + uint16_t target_cnt; + uint16_t slot_idx; + bool apply; + enum bt_mesh_dfd_phase phase; + struct bt_mesh_blob_cli_inputs inputs; + + struct + { + enum bt_mesh_dfd_upload_phase phase; + struct bt_mesh_dfu_slot *slot; + struct bt_mesh_blob_srv blob; +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD + bool is_oob; + bool is_pending_oob_check; + struct + { + uint8_t uri_len; + uint8_t uri[CONFIG_BT_MESH_DFU_URI_MAXLEN]; + uint16_t current_fwid_len; + uint8_t current_fwid[CONFIG_BT_MESH_DFU_FWID_MAXLEN]; + struct bt_mesh_msg_ctx ctx; + } oob; +#endif /* CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD */ + } upload; + +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD + struct + { + const uint8_t *schemes; + const uint8_t count; + } oob_schemes; +#endif /* CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD */ +}; + +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD +/** @brief Call when an OOB check has completed or failed + * + * This should be called by the application after an OOB check started by + * the @c start_oob_upload callback has completed or failed. The @p status + * param should be set to one of the following values: + * + * * @c BT_MESH_DFD_SUCCESS if the check was successful and a new firmware + * ID was found. * @c BT_MESH_DFD_ERR_URI_MALFORMED if the URI is not + * formatted correctly. * @c BT_MESH_DFD_ERR_URI_NOT_SUPPORTED if the URI + * scheme is not supported by the node. * @c + * BT_MESH_DFD_ERR_URI_UNREACHABLE if the URI can't be reached. * @c + * BT_MESH_DFD_ERR_NEW_FW_NOT_AVAILABLE if the check completes + * successfully but no new firmware is available. + * + * If this function returns 0, the application should then download the + * firmware to the slot. If an error code is returned, the application + * should abort the OOB upload. + * + * @param srv Firmware Distribution Server model instance. + * @param slot The slot used in the OOB upload. + * @param status The result of the firmware check. + * @param fwid If the check was successful and new firmware found, this + * should point to a buffer containing the new firmware ID + * to store. + * @param fwid_len The length of the firmware ID pointed to by @p fwid. + * + * @return 0 on success, (negative) error code otherwise. + */ + +int bt_mesh_dfd_srv_oob_check_complete(struct bt_mesh_dfd_srv *srv, + const struct bt_mesh_dfu_slot *slot, + int status, + uint8_t *fwid, + size_t fwid_len); + +/** @brief Call when an OOB store has completed or failed + * + * This should be called by the application after an OOB store started after + * a successful call to @c bt_mesh_dfd_srv_oob_check_complete has completed + * successfully or failed. + * + * @param srv Firmware Distribution Server model instance. + * @param slot The slot used when storing the firmware image. + * @param success @c true if the OOB store completed successfully, @c + * false otherwise. + * @param size The size of the stored firmware image, in bytes. + * @param metadata Pointer to the metadata received OOB, or @c NULL if + * no metadata was received. + * @param metadata_len Size of the metadata pointed to by @p metadata. + * + * @return 0 on success, (negative) error code otherwise. + */ + +int bt_mesh_dfd_srv_oob_store_complete(struct bt_mesh_dfd_srv *srv, + const struct bt_mesh_dfu_slot *slot, + bool success, + size_t size, + const uint8_t *metadata, + size_t metadata_len); +#endif /* CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD */ + +/** @cond INTERNAL_HIDDEN */ + +extern const struct bt_mesh_model_op _bt_mesh_dfd_srv_op[]; +extern const struct bt_mesh_model_cb _bt_mesh_dfd_srv_cb; +extern const struct bt_mesh_dfu_cli_cb _bt_mesh_dfd_srv_dfu_cb; +extern const struct bt_mesh_blob_srv_cb _bt_mesh_dfd_srv_blob_cb; + +/** @endcond */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_DFD_SRV_H */ + +/** @} */ diff --git a/include/nuttx/net/bluetooth/mesh/dfu.h b/include/nuttx/net/bluetooth/mesh/dfu.h new file mode 100644 index 0000000000000..d16666b0f7fe6 --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/dfu.h @@ -0,0 +1,237 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/dfu.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Header file for the Bluetooth Mesh Device Firmware Update (DFU) + * API. + * @ingroup bt_mesh_dfu + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_DFU_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_DFU_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @defgroup bt_mesh_dfu Bluetooth Mesh Device Firmware Update + * @ingroup bt_mesh + * @{ + */ + +#ifndef CONFIG_BT_MESH_DFU_FWID_MAXLEN +#define CONFIG_BT_MESH_DFU_FWID_MAXLEN 0 +#endif + +#ifndef CONFIG_BT_MESH_DFU_METADATA_MAXLEN +#define CONFIG_BT_MESH_DFU_METADATA_MAXLEN 0 +#endif + +#ifndef CONFIG_BT_MESH_DFU_URI_MAXLEN +#define CONFIG_BT_MESH_DFU_URI_MAXLEN 0 +#endif + +#ifndef CONFIG_BT_MESH_DFU_SLOT_CNT +#define CONFIG_BT_MESH_DFU_SLOT_CNT 0 +#endif + +/** DFU transfer phase. */ + +enum bt_mesh_dfu_phase +{ + /** Ready to start a Receive Firmware procedure. */ + + BT_MESH_DFU_PHASE_IDLE, + + /** The Transfer BLOB procedure failed. */ + + BT_MESH_DFU_PHASE_TRANSFER_ERR, + + /** The Receive Firmware procedure is being executed. */ + + BT_MESH_DFU_PHASE_TRANSFER_ACTIVE, + + /** The Verify Firmware procedure is being executed. */ + + BT_MESH_DFU_PHASE_VERIFY, + + /** The Verify Firmware procedure completed successfully. */ + + BT_MESH_DFU_PHASE_VERIFY_OK, + + /** The Verify Firmware procedure failed. */ + + BT_MESH_DFU_PHASE_VERIFY_FAIL, + + /** The Apply New Firmware procedure is being executed. */ + + BT_MESH_DFU_PHASE_APPLYING, + + /** Firmware transfer has been canceled. */ + + BT_MESH_DFU_PHASE_TRANSFER_CANCELED, + + /** Firmware applying succeeded. */ + + BT_MESH_DFU_PHASE_APPLY_SUCCESS, + + /** Firmware applying failed. */ + + BT_MESH_DFU_PHASE_APPLY_FAIL, + + /** Phase of a node was not yet retrieved. */ + + BT_MESH_DFU_PHASE_UNKNOWN, +}; + +/** DFU status. */ + +enum bt_mesh_dfu_status +{ + /** The message was processed successfully. */ + + BT_MESH_DFU_SUCCESS, + + /** Insufficient resources on the node */ + + BT_MESH_DFU_ERR_RESOURCES, + + /** The operation cannot be performed while the Server is in the current + * phase. + */ + + BT_MESH_DFU_ERR_WRONG_PHASE, + + /** An internal error occurred on the node. */ + + BT_MESH_DFU_ERR_INTERNAL, + + /** The message contains a firmware index value that is not expected. */ + + BT_MESH_DFU_ERR_FW_IDX, + + /** The metadata check failed. */ + + BT_MESH_DFU_ERR_METADATA, + + /** The Server cannot start a firmware update. */ + + BT_MESH_DFU_ERR_TEMPORARILY_UNAVAILABLE, + + /** Another BLOB transfer is in progress. */ + + BT_MESH_DFU_ERR_BLOB_XFER_BUSY, +}; + +/** Expected effect of a DFU transfer. */ + +enum bt_mesh_dfu_effect +{ + /** No changes to node Composition Data. */ + + BT_MESH_DFU_EFFECT_NONE, + + /** Node Composition Data changed and the node does not support remote + * provisioning. + */ + + BT_MESH_DFU_EFFECT_COMP_CHANGE_NO_RPR, + + /** Node Composition Data changed, and remote provisioning is supported. + * The node supports remote provisioning and Composition Data Page 0x80. + * Page 0x80 contains different Composition Data than Page 0x0. + */ + + BT_MESH_DFU_EFFECT_COMP_CHANGE, + + /** Node will be unprovisioned after the update. */ + + BT_MESH_DFU_EFFECT_UNPROV, +}; + +/** Action for DFU iteration callbacks. */ + +enum bt_mesh_dfu_iter +{ + /** Stop iterating. */ + + BT_MESH_DFU_ITER_STOP, + + /** Continue iterating. */ + + BT_MESH_DFU_ITER_CONTINUE, +}; + +/** DFU image instance. + * + * Each DFU image represents a single updatable firmware image. + */ + +struct bt_mesh_dfu_img +{ + /** Firmware ID. */ + + const void *fwid; + + /** Length of the firmware ID. */ + + size_t fwid_len; + + /** Update URI, or NULL. */ + + const char *uri; +}; + +/** DFU image slot for DFU distribution. */ + +struct bt_mesh_dfu_slot +{ + /** Size of the firmware in bytes. */ + + size_t size; + + /** Length of the firmware ID. */ + + size_t fwid_len; + + /** Length of the metadata. */ + + size_t metadata_len; + + /** Firmware ID. */ + + uint8_t fwid[CONFIG_BT_MESH_DFU_FWID_MAXLEN]; + + /** Metadata. */ + + uint8_t metadata[CONFIG_BT_MESH_DFU_METADATA_MAXLEN]; +}; + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_DFU_H */ diff --git a/include/nuttx/net/bluetooth/mesh/dfu_cli.h b/include/nuttx/net/bluetooth/mesh/dfu_cli.h new file mode 100644 index 0000000000000..c1b082c93c477 --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/dfu_cli.h @@ -0,0 +1,499 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/dfu_cli.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @defgroup bt_mesh_dfu_cli Firmware Uppdate Client model + * @ingroup bt_mesh_dfu + * @{ + * @brief API for the Bluetooth Mesh Firmware Update Client model + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_DFU_CLI_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_DFU_CLI_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct bt_mesh_dfu_cli; + +/** + * + * @brief Initialization parameters for the @ref bt_mesh_dfu_cli. + * + * @sa bt_mesh_dfu_cli_cb. + * + * @param _handlers Handler callback structure. + */ +#define BT_MESH_DFU_CLI_INIT(_handlers) \ +{ \ + .cb = _handlers, \ + .blob = {.cb = &_bt_mesh_dfu_cli_blob_handlers}, \ +} + +/** + * + * @brief Firmware Update Client model Composition Data entry. + * + * @param _cli Pointer to a @ref bt_mesh_dfu_cli instance. + */ +#define BT_MESH_MODEL_DFU_CLI(_cli) \ +BT_MESH_MODEL_BLOB_CLI(&(_cli)->blob), \ + BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_DFU_CLI, _bt_mesh_dfu_cli_op, NULL, _cli, \ + &_bt_mesh_dfu_cli_cb) + +/** DFU Target node. */ + +struct bt_mesh_dfu_target +{ + /** BLOB Target node */ + + struct bt_mesh_blob_target blob; + + /** Image index on the Target node */ + + uint8_t img_idx; + + /** Expected DFU effect, see @ref bt_mesh_dfu_effect. */ + + uint8_t effect; + + /** Current DFU status, see @ref bt_mesh_dfu_status. */ + + uint8_t status; + + /** Current DFU phase, see @ref bt_mesh_dfu_phase. */ + + uint8_t phase; +}; + +/** Metadata status response. */ + +struct bt_mesh_dfu_metadata_status +{ + /** Image index. */ + + uint8_t idx; + + /** Status code. */ + + enum bt_mesh_dfu_status status; + + /** Effect of transfer. */ + + enum bt_mesh_dfu_effect effect; +}; + +/** DFU Target node status parameters. */ + +struct bt_mesh_dfu_target_status +{ + /** Status of the previous operation. */ + + enum bt_mesh_dfu_status status; + + /** Phase of the current DFU transfer. */ + + enum bt_mesh_dfu_phase phase; + + /** The effect the update will have on the Target device's state. */ + + enum bt_mesh_dfu_effect effect; + + /** BLOB ID used in the transfer. */ + + uint64_t blob_id; + + /** Image index to transfer. */ + + uint8_t img_idx; + + /** TTL used in the transfer. */ + + uint8_t ttl; + + /** Additional response time for the Target nodes, in 10-second increments. + * + * The extra time can be used to give the Target nodes more time to + * respond to messages from the Client. The actual timeout will be + * calculated according to the following formula: + * + * @verbatim timeout = 20 seconds + (10 seconds * timeout_base) + (100 ms + * * TTL) @endverbatim + * + * If a Target node fails to respond to a message from the Client within + * the configured transfer timeout, the Target node is dropped. + */ + + uint16_t timeout_base; +}; + +/** @brief DFU image callback. + * + * The image callback is called for every DFU image on the Target node when + * calling @ref bt_mesh_dfu_cli_imgs_get. + * + * @param cli Firmware Update Client model instance. + * @param ctx Message context of the received message. + * @param idx Image index. + * @param total Total number of images on the Target node. + * @param img Image information for the given image index. + * @param cb_data Callback data. + * + * @retval BT_MESH_DFU_ITER_STOP Stop iterating through the image list and + * return from @ref bt_mesh_dfu_cli_imgs_get. + * @retval BT_MESH_DFU_ITER_CONTINUE Continue iterating through the image + * list if any images remain. + */ + +typedef enum bt_mesh_dfu_iter (*bt_mesh_dfu_img_cb_t)( + struct bt_mesh_dfu_cli *cli, struct bt_mesh_msg_ctx *ctx, uint8_t idx, + uint8_t total, const struct bt_mesh_dfu_img *img, void *cb_data); + +/** Firmware Update Client event callbacks. */ + +struct bt_mesh_dfu_cli_cb +{ + /** @brief BLOB transfer is suspended. + * + * Called when the BLOB transfer is suspended due to response timeout from + * all Target nodes. + * + * @param cli Firmware Update Client model instance. + */ + + void (*suspended)(struct bt_mesh_dfu_cli *cli); + + /** @brief DFU ended. + * + * Called when the DFU transfer ends, either because all Target nodes were + * lost or because the transfer was completed successfully. + * + * @param cli Firmware Update Client model instance. + * @param reason Reason for ending. + */ + + void (*ended)(struct bt_mesh_dfu_cli *cli, enum bt_mesh_dfu_status reason); + + /** @brief DFU transfer applied on all active Target nodes. + * + * Called at the end of the apply procedure started by @ref + * bt_mesh_dfu_cli_apply. + * + * @param cli Firmware Update Client model instance. + */ + + void (*applied)(struct bt_mesh_dfu_cli *cli); + + /** @brief DFU transfer confirmed on all active Target nodes. + * + * Called at the end of the apply procedure started by @ref + * bt_mesh_dfu_cli_confirm. + * + * @param cli Firmware Update Client model instance. + */ + + void (*confirmed)(struct bt_mesh_dfu_cli *cli); + + /** @brief DFU Target node was lost. + * + * A DFU Target node was dropped from the receivers list. The Target + * node's @c status is set to reflect the reason for the failure. + * + * @param cli Firmware Update Client model instance. + * @param target DFU Target node that was lost. + */ + + void (*lost_target)(struct bt_mesh_dfu_cli *cli, + struct bt_mesh_dfu_target *target); +}; + +/** Firmware Update Client model instance. + * + * Should be initialized with @ref BT_MESH_DFU_CLI_INIT. + */ + +struct bt_mesh_dfu_cli +{ + /** Callback structure. */ + + const struct bt_mesh_dfu_cli_cb *cb; + + /** Underlying BLOB Transfer Client. */ + + struct bt_mesh_blob_cli blob; + + /* runtime state */ + + uint32_t op; + const struct bt_mesh_model *mod; + + struct + { + const struct bt_mesh_dfu_slot *slot; + const struct bt_mesh_blob_io *io; + struct bt_mesh_blob_xfer blob; + uint8_t state; + uint8_t flags; + } xfer; + + struct + { + uint8_t ttl; + uint8_t type; + uint8_t img_cnt; + uint16_t addr; + sem_t sem; + void *params; + bt_mesh_dfu_img_cb_t img_cb; + } req; +}; + +/** BLOB parameters for Firmware Update Client transfer: */ + +struct bt_mesh_dfu_cli_xfer_blob_params +{ + /* Logarithmic representation of the block size. */ + + uint8_t block_size_log; + + /** Base chunk size. May be smaller for the last chunk. */ + + uint16_t chunk_size; +}; + +/** Firmware Update Client transfer parameters: */ + +struct bt_mesh_dfu_cli_xfer +{ + /** BLOB ID to use for this transfer, or 0 to set it randomly. */ + + uint64_t blob_id; + + /** DFU image slot to transfer. */ + + const struct bt_mesh_dfu_slot *slot; + + /** Transfer mode (Push (Push BLOB Transfer Mode) or Pull (Pull BLOB + * Transfer Mode)) + */ + + enum bt_mesh_blob_xfer_mode mode; + /** BLOB parameters to be used for the transfer, or NULL to retrieve Target + * nodes' capabilities before sending a firmware. + */ + + const struct bt_mesh_dfu_cli_xfer_blob_params *blob_params; +}; + +/** @brief Start distributing a DFU. + * + * Starts distribution of the firmware in the given slot to the list of DFU + * Target nodes in @c ctx. The transfer runs in the background, and its end + * is signalled through the @ref bt_mesh_dfu_cli_cb::ended callback. + * + * @note The BLOB Transfer Client transfer inputs @c targets list must point + * to a list of @ref bt_mesh_dfu_target nodes. + * + * @param cli Firmware Update Client model instance. + * @param inputs BLOB Transfer Client transfer inputs. + * @param io BLOB stream to read BLOB from. + * @param xfer Firmware Update Client transfer parameters. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_dfu_cli_send(struct bt_mesh_dfu_cli *cli, + const struct bt_mesh_blob_cli_inputs *inputs, + const struct bt_mesh_blob_io *io, + const struct bt_mesh_dfu_cli_xfer *xfer); + +/** @brief Suspend a DFU transfer. + * + * @param cli Firmware Update Client instance. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_dfu_cli_suspend(struct bt_mesh_dfu_cli *cli); + +/** @brief Resume the suspended transfer. + * + * @param cli Firmware Update Client instance. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_dfu_cli_resume(struct bt_mesh_dfu_cli *cli); + +/** @brief Cancel a DFU transfer. + * + * Will cancel the ongoing DFU transfer, or the transfer on a specific + * Target node if @c ctx is valid. + * + * @param cli Firmware Update Client model instance. + * @param ctx Message context, or NULL to cancel the ongoing DFU transfer. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_dfu_cli_cancel(struct bt_mesh_dfu_cli *cli, + struct bt_mesh_msg_ctx *ctx); + +/** @brief Apply the completed DFU transfer. + * + * A transfer can only be applied after it has ended successfully. The + * Firmware Update Client's @c applied callback is called at the end of the + * apply procedure. + * + * @param cli Firmware Update Client model instance. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_dfu_cli_apply(struct bt_mesh_dfu_cli *cli); + +/** @brief Confirm that the active transfer has been applied on the Target + * nodes. + * + * A transfer can only be confirmed after it has been applied. The Firmware + * Update Client's @c confirmed callback is called at the end of the confirm + * procedure. + * + * Target nodes that have reported the effect as @ref + * BT_MESH_DFU_EFFECT_UNPROV are expected to not respond to the query, and + * will fail if they do. + * + * @param cli Firmware Update Client model instance. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_dfu_cli_confirm(struct bt_mesh_dfu_cli *cli); + +/** @brief Get progress as a percentage of completion. + * + * @param cli Firmware Update Client model instance. + * + * @return The progress of the current transfer in percent, or 0 if no + * transfer is active. + */ + +uint8_t bt_mesh_dfu_cli_progress(struct bt_mesh_dfu_cli *cli); + +/** @brief Check whether a DFU transfer is in progress. + * + * @param cli Firmware Update Client model instance. + * + * @return true if the BLOB Transfer Client is currently participating in a + * transfer, false otherwise. + */ + +bool bt_mesh_dfu_cli_is_busy(struct bt_mesh_dfu_cli *cli); + +/** @brief Perform a DFU image list request. + * + * Requests the full list of DFU images on a Target node, and iterates + * through them, calling the @c cb for every image. + * + * The DFU image list request can be used to determine which image index the + * Target node holds its different firmwares in. + * + * Waits for a response until the procedure timeout expires. + * + * @param cli Firmware Update Client model instance. + * @param ctx Message context. + * @param cb Callback to call for each image index. + * @param cb_data Callback data to pass to @c cb. + * @param max_count Max number of images to return. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_dfu_cli_imgs_get( + struct bt_mesh_dfu_cli *cli, struct bt_mesh_msg_ctx *ctx, + bt_mesh_dfu_img_cb_t cb, void *cb_data, uint8_t max_count); + +/** @brief Perform a metadata check for the given DFU image slot. + * + * The metadata check procedure allows the Firmware Update Client to check + * if a Target node will accept a transfer of this DFU image slot, and what + * the effect would be. + * + * Waits for a response until the procedure timeout expires. + * + * @param cli Firmware Update Client model instance. + * @param ctx Message context. + * @param img_idx Target node's image index to check. + * @param slot DFU image slot to check for. + * @param rsp Metadata status response buffer. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_dfu_cli_metadata_check( + struct bt_mesh_dfu_cli *cli, struct bt_mesh_msg_ctx *ctx, uint8_t img_idx, + const struct bt_mesh_dfu_slot *slot, + struct bt_mesh_dfu_metadata_status *rsp); + +/** @brief Get the status of a Target node. + * + * @param cli Firmware Update Client model instance. + * @param ctx Message context. + * @param rsp Response data buffer. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_dfu_cli_status_get( + struct bt_mesh_dfu_cli *cli, struct bt_mesh_msg_ctx *ctx, + struct bt_mesh_dfu_target_status *rsp); + +/** @brief Get the current procedure timeout value. + * + * @return The configured procedure timeout. + */ + +int32_t bt_mesh_dfu_cli_timeout_get(void); + +/** @brief Set the procedure timeout value. + * + * @param timeout The new procedure timeout. + */ + +void bt_mesh_dfu_cli_timeout_set(int32_t timeout); + +/** @cond INTERNAL_HIDDEN */ + +extern const struct bt_mesh_blob_cli_cb _bt_mesh_dfu_cli_blob_handlers; +extern const struct bt_mesh_model_cb _bt_mesh_dfu_cli_cb; +extern const struct bt_mesh_model_op _bt_mesh_dfu_cli_op[]; + +/** @endcond */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_DFU_CLI_H */ + +/** @} */ diff --git a/include/nuttx/net/bluetooth/mesh/dfu_metadata.h b/include/nuttx/net/bluetooth/mesh/dfu_metadata.h new file mode 100644 index 0000000000000..56b5b71802bbb --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/dfu_metadata.h @@ -0,0 +1,171 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/dfu_metadata.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Header file for the Bluetooth Mesh Device Firmware Update (DFU) + * metadata API. + * @ingroup bt_mesh_dfu_metadata + */ + +/** + * @defgroup bt_mesh_dfu_metadata Bluetooth Mesh Device Firmware Update (DFU) + * metadata + * @ingroup bt_mesh_dfu + * @{ + * @brief Common types and functions for the Bluetooth Mesh DFU metadata. + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_DFU_METADATA_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_DFU_METADATA_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Firmware version. */ + +struct bt_mesh_dfu_metadata_fw_ver +{ + /** Firmware major version. */ + + uint8_t major; + + /** Firmware minor version. */ + + uint8_t minor; + + /** Firmware revision. */ + + uint16_t revision; + + /** Firmware build number. */ + + uint32_t build_num; +}; + +/** Firmware core type. */ + +enum bt_mesh_dfu_metadata_fw_core_type +{ + /** Application core. */ + + BT_MESH_DFU_FW_CORE_TYPE_APP = BIT(0), + + /** Network core. */ + + BT_MESH_DFU_FW_CORE_TYPE_NETWORK = BIT(1), + + /** Application-specific BLOB. */ + + BT_MESH_DFU_FW_CORE_TYPE_APP_SPECIFIC_BLOB = BIT(2), +}; + +/** Firmware metadata. */ + +struct bt_mesh_dfu_metadata +{ + /** New firmware version. */ + + struct bt_mesh_dfu_metadata_fw_ver fw_ver; + + /** New firmware size. */ + + uint32_t fw_size; + + /** New firmware core type. */ + + enum bt_mesh_dfu_metadata_fw_core_type fw_core_type; + + /** Hash of incoming Composition Data. */ + + uint32_t comp_hash; + + /** New number of node elements. */ + + uint16_t elems; + + /** Application-specific data for new firmware. This field is optional. */ + + uint8_t *user_data; + + /** Length of the application-specific field. */ + + uint32_t user_data_len; +}; + +/** @brief Decode a firmware metadata from a network buffer. + * + * @param buf Buffer containing a raw metadata to be decoded. + * @param metadata Pointer to a metadata structure to be filled. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_dfu_metadata_decode(struct net_buf_simple *buf, + struct bt_mesh_dfu_metadata *metadata); + +/** @brief Encode a firmware metadata into a network buffer. + * + * @param metadata Firmware metadata to be encoded. + * @param buf Buffer to store the encoded metadata. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_dfu_metadata_encode(const struct bt_mesh_dfu_metadata *metadata, + struct net_buf_simple *buf); + +/** @brief Compute hash of the Composition Data state. + * + * The format of the Composition Data is defined in MshPRTv1.1: 4.2.2.1. + * + * @param buf Pointer to buffer holding Composition Data. + * @param key 128-bit key to be used in the hash computation. + * @param hash Pointer to a memory location to which the hash will be + * stored. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_dfu_metadata_comp_hash_get(struct net_buf_simple *buf, + uint8_t *key, uint32_t *hash); + +/** @brief Compute hash of the Composition Data Page 0 of this device. + * + * @param key 128-bit key to be used in the hash computation. + * @param hash Pointer to a memory location to which the hash will be + * stored. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_dfu_metadata_comp_hash_local_get(uint8_t *key, uint32_t *hash); + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_DFU_METADATA_H */ + +/** @} */ diff --git a/include/nuttx/net/bluetooth/mesh/dfu_srv.h b/include/nuttx/net/bluetooth/mesh/dfu_srv.h new file mode 100644 index 0000000000000..faf10127492a6 --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/dfu_srv.h @@ -0,0 +1,310 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/dfu_srv.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @defgroup bt_mesh_dfu_srv Firmware Update Server model + * @ingroup bt_mesh_dfu + * @{ + * @brief API for the Bluetooth Mesh Firmware Update Server model + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_DFU_SRV_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_DFU_SRV_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct bt_mesh_dfu_srv; + +/* @brief Initialization parameters for @ref bt_mesh_dfu_srv. + * + * @param _handlers DFU handler function structure. + * @param _imgs List of @ref bt_mesh_dfu_img managed by this Server. + * @param _img_count Number of DFU images managed by this Server. + */ +#define BT_MESH_DFU_SRV_INIT(_handlers, _imgs, _img_count) \ +{ \ + .blob = {.cb = &_bt_mesh_dfu_srv_blob_cb}, \ + .cb = _handlers, \ + .imgs = _imgs, \ + .img_count = _img_count, \ +} + +/* @brief Firmware Update Server model entry. + * + * @param _srv Pointer to a @ref bt_mesh_dfu_srv instance. + */ +#define BT_MESH_MODEL_DFU_SRV(_srv) \ +BT_MESH_MODEL_BLOB_SRV(&(_srv)->blob), \ + BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_DFU_SRV, _bt_mesh_dfu_srv_op, NULL, _srv, \ + &_bt_mesh_dfu_srv_cb) + +/** @brief Firmware Update Server event callbacks. */ + +struct bt_mesh_dfu_srv_cb +{ + /** @brief Transfer check callback. + * + * The transfer check can be used to validate the incoming transfer before + * it starts. The contents of the metadata is implementation specific, and + * should contain all the information the application needs to determine + * whether this image should be accepted, and what the effect of the + * transfer would be. + * + * If applying the image will have an effect on the provisioning state of + * the mesh stack, this can be communicated through the @c effect return + * parameter. + * + * The metadata check can be performed both as part of starting a new + * transfer and as a separate procedure. + * + * This handler is optional. + * + * @param srv Firmware Update Server instance. + * @param img DFU image the metadata check is performed on. + * @param metadata Image metadata. + * @param effect Return parameter for the image effect on the + * provisioning state of the mesh stack. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int (*check)(struct bt_mesh_dfu_srv *srv, + const struct bt_mesh_dfu_img *img, + struct net_buf_simple *metadata, + enum bt_mesh_dfu_effect *effect); + + /** @brief Transfer start callback. + * + * Called when the Firmware Update Server is ready to start a new DFU + * transfer. The application must provide an initialized BLOB stream to be + * used during the DFU transfer. + * + * The following error codes are treated specially, and should be used to + * communicate these issues: + * - @c -ENOMEM: The device cannot fit this image. + * - @c -EBUSY: The application is temporarily unable to accept the + * transfer. + * - @c -EALREADY: The device has already received and verified this + * image, and there's no need to transfer it again. The Firmware Update + * model will skip the transfer phase, and mark the image as verified. + * + * This handler is mandatory. + * + * @param srv Firmware Update Server instance. + * @param img DFU image being updated. + * @param metadata Image metadata. + * @param io BLOB stream return parameter. Must be set to a + * valid BLOB stream by the callback. + * + * @return 0 on success, or (negative) error code otherwise. Return + * codes @c -ENOMEM, @c -EBUSY @c -EALREADY will be passed to the updater, + * other error codes are reported as internal errors. + */ + +int (*start)(struct bt_mesh_dfu_srv *srv, + const struct bt_mesh_dfu_img *img, + struct net_buf_simple *metadata, + const struct bt_mesh_blob_io **io); + + /** @brief Transfer end callback. + * + * This handler is optional. + * + * If the transfer is successful, the application should verify the + * firmware image, and call either @ref bt_mesh_dfu_srv_verified or + * @ref bt_mesh_dfu_srv_rejected depending on the outcome. + * + * If the transfer fails, the Firmware Update Server will be available for + * new transfers immediately after this function returns. + * + * @param srv Firmware Update Server instance. + * @param img DFU image that failed the update. + * @param success Whether the DFU transfer was successful. + */ + + void (*end)(struct bt_mesh_dfu_srv *srv, + const struct bt_mesh_dfu_img *img, bool success); + + /** @brief Transfer recovery callback. + * + * If the device reboots in the middle of a transfer, the Firmware Update + * Server calls this function when the Bluetooth Mesh subsystem is + * started. + * + * This callback is optional, but transfers will not be recovered after a + * reboot without it. + * + * @param srv Firmware Update Server instance. + * @param img DFU image being updated. + * @param io BLOB stream return parameter. Must be set to a valid BLOB + * stream by the callback. + * + * @return 0 on success, or (negative) error code to abandon the + * transfer. + */ + +int (*recover)(struct bt_mesh_dfu_srv *srv, + const struct bt_mesh_dfu_img *img, + const struct bt_mesh_blob_io **io); + + /** @brief Transfer apply callback. + * + * Called after a transfer has been validated, and the updater sends an + * apply message to the Target nodes. + * + * This handler is optional. + * + * @param srv Firmware Update Server instance. + * @param img DFU image that should be applied. + * + * @return 0 on success, or (negative) error code otherwise. + */ + + int (*apply)(struct bt_mesh_dfu_srv *srv, + const struct bt_mesh_dfu_img *img); +}; + +/** @brief Firmware Update Server instance. + * + * Should be initialized with @ref BT_MESH_DFU_SRV_INIT. + */ + +struct bt_mesh_dfu_srv +{ + /** Underlying BLOB Transfer Server. */ + + struct bt_mesh_blob_srv blob; + + /** Callback structure. */ + + const struct bt_mesh_dfu_srv_cb *cb; + + /** List of updatable images. */ + + const struct bt_mesh_dfu_img *imgs; + + /** Number of updatable images. */ + + size_t img_count; + + /** @cond INTERNAL_HIDDEN */ + + /* Runtime state */ + + const struct bt_mesh_model *mod; + struct + { + /* Effect of transfer, @see bt_mesh_dfu_effect. */ + + uint8_t effect; + + /* Current phase, @see bt_mesh_dfu_phase. */ + + uint8_t phase; + uint8_t ttl; + uint8_t idx; + uint16_t timeout_base; + uint16_t meta; + bool self_update; + } update; + + /** @endcond */ +}; + +/** @brief Accept the received DFU transfer. + * + * Should be called at the end of a successful DFU transfer. + * + * If the DFU transfer completes successfully, the application should verify + * the image validity (including any image authentication or integrity + * checks), and call this function if the image is ready to be applied. + * + * @param srv Firmware Update Server instance. + */ + +void bt_mesh_dfu_srv_verified(struct bt_mesh_dfu_srv *srv); + +/** @brief Reject the received DFU transfer. + * + * Should be called at the end of a successful DFU transfer. + * + * If the DFU transfer completes successfully, the application should verify + * the image validity (including any image authentication or integrity + * checks), and call this function if one of the checks fail. + * + * @param srv Firmware Update Server instance. + */ + +void bt_mesh_dfu_srv_rejected(struct bt_mesh_dfu_srv *srv); + +/** @brief Cancel the ongoing DFU transfer. + * + * @param srv Firmware Update Server instance. + */ + +void bt_mesh_dfu_srv_cancel(struct bt_mesh_dfu_srv *srv); + +/** @brief Confirm that the received DFU transfer was applied. + * + * Should be called as a result of the @ref bt_mesh_dfu_srv_cb.apply + * callback. + * + * @param srv Firmware Update Server instance. + */ + +void bt_mesh_dfu_srv_applied(struct bt_mesh_dfu_srv *srv); + +/** @brief Check if the Firmware Update Server is busy processing a transfer. + * + * @param srv Firmware Update Server instance. + * + * @return true if a DFU procedure is in progress, false otherwise. + */ + +bool bt_mesh_dfu_srv_is_busy(const struct bt_mesh_dfu_srv *srv); + +/** @brief Get the progress of the current DFU procedure, in percent. + * + * @param srv Firmware Update Server instance. + * + * @return The current transfer progress in percent. + */ + +uint8_t bt_mesh_dfu_srv_progress(const struct bt_mesh_dfu_srv *srv); + +/** @cond INTERNAL_HIDDEN */ + +extern const struct bt_mesh_model_op _bt_mesh_dfu_srv_op[]; +extern const struct bt_mesh_model_cb _bt_mesh_dfu_srv_cb; +extern const struct bt_mesh_blob_srv_cb _bt_mesh_dfu_srv_blob_cb; + +/** @endcond */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_DFU_SRV_H */ + +/** @} */ diff --git a/include/nuttx/net/bluetooth/mesh/health_cli.h b/include/nuttx/net/bluetooth/mesh/health_cli.h new file mode 100644 index 0000000000000..8e1a56a05f20b --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/health_cli.h @@ -0,0 +1,444 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/health_cli.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Health Client Model APIs. + */ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_HEALTH_CLI_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_HEALTH_CLI_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/** + * @brief Health Client Model + * @defgroup bt_mesh_health_cli Health Client Model + * @ingroup bt_mesh + * @{ + */ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Health Client Model Context */ + +struct bt_mesh_health_cli +{ + /** Composition data model entry pointer. */ + + const struct bt_mesh_model *model; + + /** Publication structure instance */ + + struct bt_mesh_model_pub pub; + + /** Publication buffer */ + + struct net_buf_simple pub_buf; + + /** Publication data */ + + uint8_t pub_data[BT_MESH_MODEL_BUF_LEN(BT_MESH_MODEL_OP_2(0x80, 0x32), 3)]; + + /** @brief Optional callback for Health Period Status messages. + * + * Handles received Health Period Status messages from a Health server. + * The @c divisor param represents the period divisor value. + * + * @param cli Health client that received the status message. + * @param addr Address of the sender. + * @param divisor Health Period Divisor value. + */ + + void (*period_status)(struct bt_mesh_health_cli *cli, uint16_t addr, + uint8_t divisor); + + /** @brief Optional callback for Health Attention Status messages. + * + * Handles received Health Attention Status messages from a Health server. + * The @c attention param represents the current attention value. + * + * @param cli Health client that received the status message. + * @param addr Address of the sender. + * @param attention Current attention value. + */ + + void (*attention_status)(struct bt_mesh_health_cli *cli, uint16_t addr, + uint8_t attention); + + /** @brief Optional callback for Health Fault Status messages. + * + * Handles received Health Fault Status messages from a Health server. The + * @c fault array represents all faults that are currently present in the + * server's element. + * + * @see bt_mesh_health_faults + * + * @param cli Health client that received the status message. + * @param addr Address of the sender. + * @param test_id Identifier of a most recently performed test. + * @param cid Company Identifier of the node. + * @param faults Array of faults. + * @param fault_count Number of faults in the fault array. + */ + +void (*fault_status)(struct bt_mesh_health_cli *cli, + uint16_t addr, + uint8_t test_id, + uint16_t cid, + uint8_t *faults, + size_t fault_count); + + /** @brief Optional callback for Health Current Status messages. + * + * Handles received Health Current Status messages from a Health server. + * The @c fault array represents all faults that are currently present in + * the server's element. + * + * @see bt_mesh_health_faults + * + * @param cli Health client that received the status message. + * @param addr Address of the sender. + * @param test_id Identifier of a most recently performed test. + * @param cid Company Identifier of the node. + * @param faults Array of faults. + * @param fault_count Number of faults in the fault array. + */ + +void (*current_status)(struct bt_mesh_health_cli *cli, + uint16_t addr, + uint8_t test_id, + uint16_t cid, + uint8_t *faults, + size_t fault_count); + + /** @brief Optional callback for updating the message to be sent as + * periodic publication. + * + * This callback is called before sending the periodic publication + * message. The callback can be used to update the message to be sent. + * + * If this callback is not implemented, periodic publication can still be + * enabled, but no messages will be sent. + * + * @param cli Health client that is sending the periodic publication + * message. + * @param pub_buf Publication message buffer to be updated. + * + * @return 0 if @p pub_buf is updated successfully, or (negative) error + * code on failure. The message won't be sent if an error is returned. + */ + + int (*update)(struct bt_mesh_health_cli *cli, + struct net_buf_simple *pub_buf); + + /* Internal parameters for tracking message responses. */ + + struct bt_mesh_msg_ack_ctx ack_ctx; +}; + +/** + * @brief Generic Health Client model composition data entry. + * + * @param cli_data Pointer to a @ref bt_mesh_health_cli instance. + */ +#define BT_MESH_MODEL_HEALTH_CLI(cli_data) \ +BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_HEALTH_CLI, bt_mesh_health_cli_op, &(cli_data)->pub, cli_data, \ + &bt_mesh_health_cli_cb) + +/** @brief Get the registered fault state for the given Company ID. + * + * This method can be used asynchronously by setting @p test_id and ( @p + * faults or @p fault_count ) as NULL This way the method will not wait for + * response and will return immediately after sending the command. + * + * To process the response arguments of an async method, register the @c + * fault_status callback in @c bt_mesh_health_cli struct. + * + * @see bt_mesh_health_faults + * + * @param cli Client model to send on. + * @param ctx Message context, or NULL to use the configured publish + * parameters. + * @param cid Company ID to get the registered faults of. + * @param test_id Test ID response buffer. + * @param faults Fault array response buffer. + * @param fault_count Fault count response buffer. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_health_cli_fault_get(struct bt_mesh_health_cli *cli, + struct bt_mesh_msg_ctx *ctx, + uint16_t cid, + uint8_t *test_id, + uint8_t *faults, + size_t *fault_count); + +/** @brief Clear the registered faults for the given Company ID. + * + * This method can be used asynchronously by setting @p test_id and ( @p + * faults or @p fault_count ) as NULL This way the method will not wait for + * response and will return immediately after sending the command. + * + * To process the response arguments of an async method, register the @c + * fault_status callback in @c bt_mesh_health_cli struct. + * + * @see bt_mesh_health_faults + * + * @param cli Client model to send on. + * @param ctx Message context, or NULL to use the configured publish + * parameters. + * @param cid Company ID to clear the registered faults for. + * @param test_id Test ID response buffer. + * @param faults Fault array response buffer. + * @param fault_count Fault count response buffer. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_health_cli_fault_clear(struct bt_mesh_health_cli *cli, + struct bt_mesh_msg_ctx *ctx, + uint16_t cid, + uint8_t *test_id, + uint8_t *faults, + size_t *fault_count); + +/** @brief Clear the registered faults for the given Company ID (unacked). + * + * @see bt_mesh_health_faults + * + * @param cli Client model to send on. + * @param ctx Message context, or NULL to use the configured publish + * parameters. + * @param cid Company ID to clear the registered faults for. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_health_cli_fault_clear_unack(struct bt_mesh_health_cli *cli, + struct bt_mesh_msg_ctx *ctx, + uint16_t cid); + +/** @brief Invoke a self-test procedure for the given Company ID. + * + * This method can be used asynchronously by setting @p faults or @p + * fault_count as NULL This way the method will not wait for response and + * will return immediately after sending the command. + * + * To process the response arguments of an async method, register the @c + * fault_status callback in @c bt_mesh_health_cli struct. + * + * @param cli Client model to send on. + * @param ctx Message context, or NULL to use the configured publish + * parameters. + * @param cid Company ID to invoke the test for. + * @param test_id Test ID response buffer. + * @param faults Fault array response buffer. + * @param fault_count Fault count response buffer. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_health_cli_fault_test(struct bt_mesh_health_cli *cli, + struct bt_mesh_msg_ctx *ctx, + uint16_t cid, + uint8_t test_id, + uint8_t *faults, + size_t *fault_count); + +/** @brief Invoke a self-test procedure for the given Company ID (unacked). + * + * @param cli Client model to send on. + * @param ctx Message context, or NULL to use the configured publish + * parameters. + * @param cid Company ID to invoke the test for. + * @param test_id Test ID response buffer. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_health_cli_fault_test_unack(struct bt_mesh_health_cli *cli, + struct bt_mesh_msg_ctx *ctx, + uint16_t cid, + uint8_t test_id); + +/** @brief Get the target node's Health fast period divisor. + * + * The health period divisor is used to increase the publish rate when a + * fault is registered. Normally, the Health server will publish with the + * period in the configured publish parameters. When a fault is registered, + * the publish period is divided by (1 << divisor). For example, if the + * target node's Health server is configured to publish with a period of 16 + * seconds, and the Health fast period divisor is 5, the Health server will + * publish with an interval of 500 ms when a fault is registered. + * + * This method can be used asynchronously by setting @p divisor as NULL. + * This way the method will not wait for response and will return + * immediately after sending the command. + * + * To process the response arguments of an async method, register the @c + * period_status callback in @c bt_mesh_health_cli struct. + * + * @param cli Client model to send on. + * @param ctx Message context, or NULL to use the configured publish + * parameters. + * @param divisor Health period divisor response buffer. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_health_cli_period_get(struct bt_mesh_health_cli *cli, + struct bt_mesh_msg_ctx *ctx, + uint8_t *divisor); + +/** @brief Set the target node's Health fast period divisor. + * + * The health period divisor is used to increase the publish rate when a + * fault is registered. Normally, the Health server will publish with the + * period in the configured publish parameters. When a fault is registered, + * the publish period is divided by (1 << divisor). For example, if the + * target node's Health server is configured to publish with a period of 16 + * seconds, and the Health fast period divisor is 5, the Health server will + * publish with an interval of 500 ms when a fault is registered. + * + * This method can be used asynchronously by setting @p updated_divisor as + * NULL. This way the method will not wait for response and will return + * immediately after sending the command. + * + * To process the response arguments of an async method, register the @c + * period_status callback in @c bt_mesh_health_cli struct. + * + * @param cli Client model to send on. + * @param ctx Message context, or NULL to use the configured + * publish parameters. + * @param divisor New Health period divisor. + * @param updated_divisor Health period divisor response buffer. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_health_cli_period_set(struct bt_mesh_health_cli *cli, + struct bt_mesh_msg_ctx *ctx, + uint8_t divisor, + uint8_t *updated_divisor); + +/** @brief Set the target node's Health fast period divisor (unacknowledged). + * + * This is an unacknowledged version of this API. + * + * @param cli Client model to send on. + * @param ctx Message context, or NULL to use the configured + * publish parameters. + * @param divisor New Health period divisor. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_health_cli_period_set_unack(struct bt_mesh_health_cli *cli, + struct bt_mesh_msg_ctx *ctx, + uint8_t divisor); + +/** @brief Get the current attention timer value. + * + * This method can be used asynchronously by setting @p attention as NULL. + * This way the method will not wait for response and will return + * immediately after sending the command. + * + * To process the response arguments of an async method, register the @c + * attention_status callback in @c bt_mesh_health_cli struct. + * + * @param cli Client model to send on. + * @param ctx Message context, or NULL to use the configured publish + * parameters. + * @param attention Attention timer response buffer, measured in seconds. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_health_cli_attention_get(struct bt_mesh_health_cli *cli, + struct bt_mesh_msg_ctx *ctx, + uint8_t *attention); + +/** @brief Set the attention timer. + * + * This method can be used asynchronously by setting @p updated_attention as + * NULL. This way the method will not wait for response and will return + * immediately after sending the command. + * + * To process the response arguments of an async method, register the @c + * attention_status callback in @c bt_mesh_health_cli struct. + * + * @param cli Client model to send on. + * @param ctx Message context, or NULL to use the configured + * publish parameters. + * @param attention New attention timer time, in seconds. + * @param updated_attention Attention timer response buffer, measured in + * seconds. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_health_cli_attention_set(struct bt_mesh_health_cli *cli, + struct bt_mesh_msg_ctx *ctx, + uint8_t attention, + uint8_t *updated_attention); + +/** @brief Set the attention timer (unacknowledged). + * + * @param cli Client model to send on. + * @param ctx Message context, or NULL to use the configured + * publish parameters. + * @param attention New attention timer time, in seconds. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_health_cli_attention_set_unack(struct bt_mesh_health_cli *cli, + struct bt_mesh_msg_ctx *ctx, + uint8_t attention); + +/** @brief Get the current transmission timeout value. + * + * @return The configured transmission timeout in milliseconds. + */ + +int32_t bt_mesh_health_cli_timeout_get(void); + +/** @brief Set the transmission timeout value. + * + * @param timeout The new transmission timeout. + */ + +void bt_mesh_health_cli_timeout_set(int32_t timeout); + +/** @cond INTERNAL_HIDDEN */ + +extern const struct bt_mesh_model_op bt_mesh_health_cli_op[]; +extern const struct bt_mesh_model_cb bt_mesh_health_cli_cb; + +/** @endcond */ + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_HEALTH_CLI_H */ diff --git a/include/nuttx/net/bluetooth/mesh/health_faults.h b/include/nuttx/net/bluetooth/mesh/health_faults.h new file mode 100644 index 0000000000000..52229250a9287 --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/health_faults.h @@ -0,0 +1,141 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/health_faults.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Health faults + */ + +/* Copyright (c) 2019 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_HEALTH_FAULTS_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_HEALTH_FAULTS_H + +/** + * @brief List of specification defined Health fault values. + * @defgroup bt_mesh_health_faults Health faults + * @ingroup bt_mesh + * @{ + */ + +/** No fault has occurred. */ +#define BT_MESH_HEALTH_FAULT_NO_FAULT 0x00 + +/** Battery Low Warning */ +#define BT_MESH_HEALTH_FAULT_BATTERY_LOW_WARNING 0x01 +/** Battery Low Error */ +#define BT_MESH_HEALTH_FAULT_BATTERY_LOW_ERROR 0x02 +/** Supply Voltage Too Low Warning */ +#define BT_MESH_HEALTH_FAULT_SUPPLY_VOLTAGE_TOO_LOW_WARNING 0x03 +/** Supply Voltage Too Low Error */ +#define BT_MESH_HEALTH_FAULT_SUPPLY_VOLTAGE_TOO_LOW_ERROR 0x04 +/** Supply Voltage Too High Warning */ +#define BT_MESH_HEALTH_FAULT_SUPPLY_VOLTAGE_TOO_HIGH_WARNING 0x05 +/** Supply Voltage Too High Error */ +#define BT_MESH_HEALTH_FAULT_SUPPLY_VOLTAGE_TOO_HIGH_ERROR 0x06 +/** Power Supply Interrupted Warning */ +#define BT_MESH_HEALTH_FAULT_POWER_SUPPLY_INTERRUPTED_WARNING 0x07 +/** Power Supply Interrupted Error */ +#define BT_MESH_HEALTH_FAULT_POWER_SUPPLY_INTERRUPTED_ERROR 0x08 +/** No Load Warning */ +#define BT_MESH_HEALTH_FAULT_NO_LOAD_WARNING 0x09 +/** No Load Error */ +#define BT_MESH_HEALTH_FAULT_NO_LOAD_ERROR 0x0A +/** Overload Warning */ +#define BT_MESH_HEALTH_FAULT_OVERLOAD_WARNING 0x0B +/** Overload Error */ +#define BT_MESH_HEALTH_FAULT_OVERLOAD_ERROR 0x0C +/** Overheat Warning */ +#define BT_MESH_HEALTH_FAULT_OVERHEAT_WARNING 0x0D +/** Overheat Error */ +#define BT_MESH_HEALTH_FAULT_OVERHEAT_ERROR 0x0E +/** Condensation Warning */ +#define BT_MESH_HEALTH_FAULT_CONDENSATION_WARNING 0x0F +/** Condensation Error */ +#define BT_MESH_HEALTH_FAULT_CONDENSATION_ERROR 0x10 +/** Vibration Warning */ +#define BT_MESH_HEALTH_FAULT_VIBRATION_WARNING 0x11 +/** Vibration Error */ +#define BT_MESH_HEALTH_FAULT_VIBRATION_ERROR 0x12 +/** Configuration Warning */ +#define BT_MESH_HEALTH_FAULT_CONFIGURATION_WARNING 0x13 +/** Configuration Error */ +#define BT_MESH_HEALTH_FAULT_CONFIGURATION_ERROR 0x14 +/** Element Not Calibrated Warning */ +#define BT_MESH_HEALTH_FAULT_ELEMENT_NOT_CALIBRATED_WARNING 0x15 +/** Element Not Calibrated Error */ +#define BT_MESH_HEALTH_FAULT_ELEMENT_NOT_CALIBRATED_ERROR 0x16 +/** Memory Warning */ +#define BT_MESH_HEALTH_FAULT_MEMORY_WARNING 0x17 +/** Memory Error */ +#define BT_MESH_HEALTH_FAULT_MEMORY_ERROR 0x18 +/** Self-Test Warning */ +#define BT_MESH_HEALTH_FAULT_SELF_TEST_WARNING 0x19 +/** Self-Test Error */ +#define BT_MESH_HEALTH_FAULT_SELF_TEST_ERROR 0x1A +/** Input Too Low Warning */ +#define BT_MESH_HEALTH_FAULT_INPUT_TOO_LOW_WARNING 0x1B +/** Input Too Low Error */ +#define BT_MESH_HEALTH_FAULT_INPUT_TOO_LOW_ERROR 0x1C +/** Input Too High Warning */ +#define BT_MESH_HEALTH_FAULT_INPUT_TOO_HIGH_WARNING 0x1D +/** Input Too High Error */ +#define BT_MESH_HEALTH_FAULT_INPUT_TOO_HIGH_ERROR 0x1E +/** Input No Change Warning */ +#define BT_MESH_HEALTH_FAULT_INPUT_NO_CHANGE_WARNING 0x1F +/** Input No Change Error */ +#define BT_MESH_HEALTH_FAULT_INPUT_NO_CHANGE_ERROR 0x20 +/** Actuator Blocked Warning */ +#define BT_MESH_HEALTH_FAULT_ACTUATOR_BLOCKED_WARNING 0x21 +/** Actuator Blocked Error */ +#define BT_MESH_HEALTH_FAULT_ACTUATOR_BLOCKED_ERROR 0x22 +/** Housing Opened Warning */ +#define BT_MESH_HEALTH_FAULT_HOUSING_OPENED_WARNING 0x23 +/** Housing Opened Error */ +#define BT_MESH_HEALTH_FAULT_HOUSING_OPENED_ERROR 0x24 +/** Tamper Warning */ +#define BT_MESH_HEALTH_FAULT_TAMPER_WARNING 0x25 +/** Tamper Error */ +#define BT_MESH_HEALTH_FAULT_TAMPER_ERROR 0x26 +/** Device Moved Warning */ +#define BT_MESH_HEALTH_FAULT_DEVICE_MOVED_WARNING 0x27 +/** Device Moved Error */ +#define BT_MESH_HEALTH_FAULT_DEVICE_MOVED_ERROR 0x28 +/** Device Dropped Warning */ +#define BT_MESH_HEALTH_FAULT_DEVICE_DROPPED_WARNING 0x29 +/** Device Dropped Error */ +#define BT_MESH_HEALTH_FAULT_DEVICE_DROPPED_ERROR 0x2A +/** Overflow Warning */ +#define BT_MESH_HEALTH_FAULT_OVERFLOW_WARNING 0x2B +/** Overflow Error */ +#define BT_MESH_HEALTH_FAULT_OVERFLOW_ERROR 0x2C +/** Empty Warning */ +#define BT_MESH_HEALTH_FAULT_EMPTY_WARNING 0x2D +/** Empty Error */ +#define BT_MESH_HEALTH_FAULT_EMPTY_ERROR 0x2E +/** Internal Bus Warning */ +#define BT_MESH_HEALTH_FAULT_INTERNAL_BUS_WARNING 0x2F +/** Internal Bus Error */ +#define BT_MESH_HEALTH_FAULT_INTERNAL_BUS_ERROR 0x30 +/** Mechanism Jammed Warning */ +#define BT_MESH_HEALTH_FAULT_MECHANISM_JAMMED_WARNING 0x31 +/** Mechanism Jammed Error */ +#define BT_MESH_HEALTH_FAULT_MECHANISM_JAMMED_ERROR 0x32 + +/** + * Start of the vendor specific fault values. + * + * All values below this are reserved for the Bluetooth Specification. + */ +#define BT_MESH_HEALTH_FAULT_VENDOR_SPECIFIC_START 0x80 + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_HEALTH_FAULTS_H */ diff --git a/include/nuttx/net/bluetooth/mesh/health_srv.h b/include/nuttx/net/bluetooth/mesh/health_srv.h new file mode 100644 index 0000000000000..42f640372780c --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/health_srv.h @@ -0,0 +1,255 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/health_srv.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Health Server Model APIs. + */ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_HEALTH_SRV_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_HEALTH_SRV_H + +/** + * @brief Health Server Model + * @defgroup bt_mesh_health_srv Health Server Model + * @ingroup bt_mesh + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Callback function for the Health Server model */ + +struct bt_mesh_health_srv_cb +{ + /** @brief Callback for fetching current faults. + * + * Fault values may either be defined by the specification, or by a + * vendor. Vendor specific faults should be interpreted in the context of + * the accompanying Company ID. Specification defined faults may be + * reported for any Company ID, and the same fault may be presented for + * multiple Company IDs. + * + * All faults shall be associated with at least one Company ID, + * representing the device vendor or some other vendor whose vendor + * specific fault values are used. + * + * If there are multiple Company IDs that have active faults, return only + * the faults associated with one of them at the time. To report faults + * for multiple Company IDs, interleave which Company ID is reported for + * each call. + * + * @param model Health Server model instance to get faults of. + * @param test_id Test ID response buffer. + * @param company_id Company ID response buffer. + * @param faults Array to fill with current faults. + * @param fault_count The number of faults the fault array can fit. Should + * be updated to reflect the number of faults copied + * into the array. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int (*fault_get_cur)(const struct bt_mesh_model *model, + uint8_t *test_id, + uint16_t *company_id, + uint8_t *faults, + uint8_t *fault_count); + + /** @brief Callback for fetching all registered faults. + * + * Registered faults are all past and current faults since the last call + * to @c fault_clear. Only faults associated with the given Company ID + * should be reported. + * + * Fault values may either be defined by the specification, or by a + * vendor. Vendor specific faults should be interpreted in the context of + * the accompanying Company ID. Specification defined faults may be + * reported for any Company ID, and the same fault may be presented for + * multiple Company IDs. + * + * @param model Health Server model instance to get faults of. + * @param company_id Company ID to get faults for. + * @param test_id Test ID response buffer. + * @param faults Array to fill with registered faults. + * @param fault_count The number of faults the fault array can fit. Should + * be updated to reflect the number of faults copied + * into the array. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int (*fault_get_reg)(const struct bt_mesh_model *model, + uint16_t company_id, + uint8_t *test_id, + uint8_t *faults, + uint8_t *fault_count); + + /** @brief Clear all registered faults associated with the given Company + * ID. + * + * @param model Health Server model instance to clear faults of. + * @param company_id Company ID to clear faults for. + * + * @return 0 on success, or (negative) error code otherwise. + */ + + int (*fault_clear)(const struct bt_mesh_model *model, uint16_t company_id); + + /** @brief Run a self-test. + * + * The Health server may support up to 256 self-tests for each Company ID. + * The behavior for all test IDs are vendor specific, and should be + * interpreted based on the accompanying Company ID. Test failures should + * result in changes to the fault array. + * + * @param model Health Server model instance to run test for. + * @param test_id Test ID to run. + * @param company_id Company ID to run test for. + * + * @return 0 if the test execution was started successfully, or + * (negative) error code otherwise. Note that the fault array will not be + * reported back to the client if the test execution didn't start. + */ + + int (*fault_test)(const struct bt_mesh_model *model, uint8_t test_id, + uint16_t company_id); + + /** @brief Start calling attention to the device. + * + * The attention state is used to map an element address to a physical + * device. When this callback is called, the device should start some + * physical procedure meant to call attention to itself, like blinking, + * buzzing, vibrating or moving. If there are multiple Health server + * instances on the device, the attention state should also help identify + * the specific element the server is in. + * + * The attention calling behavior should continue until the @c attn_off + * callback is called. + * + * @param model Health Server model to start the attention state of. + */ + + void (*attn_on)(const struct bt_mesh_model *model); + + /** @brief Stop the attention state. + * + * Any physical activity started to call attention to the device should be + * stopped. + * + * @param model + */ + + void (*attn_off)(const struct bt_mesh_model *model); +}; + +/* A helper to define a health publication context + * + * @param _name Name given to the publication context variable. + * @param _max_faults Maximum number of faults the element can have. + */ +#define BT_MESH_HEALTH_PUB_DEFINE(_name, _max_faults) \ +BT_MESH_MODEL_PUB_DEFINE(_name, NULL, (1 + 3 + (_max_faults))) + +/** Mesh Health Server Model Context */ + +struct bt_mesh_health_srv +{ + /** Composition data model entry pointer. */ + + const struct bt_mesh_model *model; + + /** Optional callback struct */ + + const struct bt_mesh_health_srv_cb *cb; + + /** Attention Timer state */ + + struct k_work_delayable attn_timer; +}; + +/* Define a new health server model. Note that this API needs to be repeated + * for each element that the application wants to have a health server model + * on. Each instance also needs a unique bt_mesh_health_srv and + * bt_mesh_model_pub context. + * + * @param srv Pointer to a unique struct bt_mesh_health_srv. + * @param pub Pointer to a unique struct bt_mesh_model_pub. + * @param ... Optional Health Server metadata if application is compiled + * with Large Composition Data Server support, otherwise this + * parameter is ignored. + * + * @return New mesh model instance. + */ +#define BT_MESH_MODEL_HEALTH_SRV(srv, pub, ...) \ +BT_MESH_MODEL_METADATA_CB(BT_MESH_MODEL_ID_HEALTH_SRV, bt_mesh_health_srv_op, pub, srv, \ + &bt_mesh_health_srv_cb, __VA_ARGS__) + +/* Health Test Information Metadata ID. + */ +#define BT_MESH_HEALTH_TEST_INFO_METADATA_ID 0x0000 + +#define BT_MESH_HEALTH_TEST_INFO_METADATA(tests) \ +{ \ + .len = ARRAY_SIZE(tests), \ + .id = BT_MESH_HEALTH_TEST_INFO_METADATA_ID, \ + .data = tests, \ +} + +/* Define a Health Test Info Metadata array. + * + * @param cid Company ID of the Health Test suite. + * @param tests A comma separated list of tests. + * + * @return A comma separated list of values that make Health Test Info + * Metadata + */ +#define BT_MESH_HEALTH_TEST_INFO(cid, tests...) \ +BT_BYTES_LIST_LE16(cid), sizeof((uint8_t[]){tests}), tests + +/** @brief Notify the stack that the fault array state of the given element + * has changed. + * + * This prompts the Health server on this element to publish the current + * fault array if periodic publishing is disabled. + * + * @param elem Element to update the fault state of. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_health_srv_fault_update(const struct bt_mesh_elem *elem); + +/** @cond INTERNAL_HIDDEN */ + +extern const struct bt_mesh_model_op bt_mesh_health_srv_op[]; +extern const struct bt_mesh_model_cb bt_mesh_health_srv_cb; + +/** @endcond */ + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_HEALTH_SRV_H */ diff --git a/include/nuttx/net/bluetooth/mesh/heartbeat.h b/include/nuttx/net/bluetooth/mesh/heartbeat.h new file mode 100644 index 0000000000000..dacb35ff6bf98 --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/heartbeat.h @@ -0,0 +1,182 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/heartbeat.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Heartbeat APIs. + */ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_HEARTBEAT_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_HEARTBEAT_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +/** + * @brief Heartbeat + * @defgroup bt_mesh_heartbeat Heartbeat + * @ingroup bt_mesh + * @{ + */ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Heartbeat Publication parameters */ + +struct bt_mesh_hb_pub +{ + /** Destination address. */ + + uint16_t dst; + + /** Remaining publish count. */ + + uint16_t count; + + /** Time To Live value. */ + + uint8_t ttl; + /** Bitmap of features that trigger a Heartbeat publication if they change. + * Legal values are @ref BT_MESH_FEAT_RELAY, + * @ref BT_MESH_FEAT_PROXY, @ref BT_MESH_FEAT_FRIEND and + * @ref BT_MESH_FEAT_LOW_POWER. + */ + + uint16_t feat; + + /** Network index used for publishing. */ + + uint16_t net_idx; + + /** Publication period in seconds. */ + + uint32_t period; +}; + +/** Heartbeat Subscription parameters. */ + +struct bt_mesh_hb_sub +{ + /** Subscription period in seconds. */ + + uint32_t period; + + /** Remaining subscription time in seconds. */ + + uint32_t remaining; + + /** Source address to receive Heartbeats from. */ + + uint16_t src; + + /** Destination address to received Heartbeats on. */ + + uint16_t dst; + + /** The number of received Heartbeat messages so far. */ + + uint16_t count; +/* Minimum hops in received messages, ie the shortest registered path from + * the publishing node to the subscribing node. A Heartbeat received from + * an immediate neighbor has hop count = 1. + */ + + uint8_t min_hops; +/* Maximum hops in received messages, ie the longest registered path from + * the publishing node to the subscribing node. A Heartbeat received from + * an immediate neighbor has hop count = 1. + */ + + uint8_t max_hops; +}; + +/** Heartbeat callback structure */ + +struct bt_mesh_hb_cb +{ + /** @brief Receive callback for heartbeats. + * + * Gets called on every received Heartbeat that matches the current + * Heartbeat subscription parameters. + * + * @param sub Current Heartbeat subscription parameters. + * @param hops The number of hops the Heartbeat was received with. + * @param feat The feature set of the publishing node. The value is a + * bitmap of @ref BT_MESH_FEAT_RELAY, + * @ref BT_MESH_FEAT_PROXY, + * @ref BT_MESH_FEAT_FRIEND and + * @ref BT_MESH_FEAT_LOW_POWER. + */ + + void (*recv)(const struct bt_mesh_hb_sub *sub, + uint8_t hops, + uint16_t feat); + + /** @brief Subscription end callback for heartbeats. + * + * Gets called when the subscription period ends, providing a summary of + * the received heartbeat messages. + * + * @param sub Current Heartbeat subscription parameters. + */ + + void (*sub_end)(const struct bt_mesh_hb_sub *sub); + + /** @brief Publication sent callback for heartbeats. + * + * Gets called when the heartbeat is successfully published. + * + * @param pub Current Heartbeat publication parameters. + */ + + void (*pub_sent)(const struct bt_mesh_hb_pub *pub); +}; + +/** + * @brief Register a callback structure for Heartbeat events. + * + * Registers a callback structure that will be called whenever Heartbeat + * events occur + * + * @param _name Name of callback structure. + */ +#define BT_MESH_HB_CB_DEFINE(_name) static const STRUCT_SECTION_ITERABLE(bt_mesh_hb_cb, _name) + +/** @brief Get the current Heartbeat publication parameters. + * + * @param get Heartbeat publication parameters return buffer. + */ + +void bt_mesh_hb_pub_get(struct bt_mesh_hb_pub *get); + +/** @brief Get the current Heartbeat subscription parameters. + * + * @param get Heartbeat subscription parameters return buffer. + */ + +void bt_mesh_hb_sub_get(struct bt_mesh_hb_sub *get); + +#ifdef __cplusplus +} +#endif +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_HEARTBEAT_H */ diff --git a/include/nuttx/net/bluetooth/mesh/keys.h b/include/nuttx/net/bluetooth/mesh/keys.h new file mode 100644 index 0000000000000..272bd74658718 --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/keys.h @@ -0,0 +1,45 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/keys.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Keys APIs. + */ + +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_KEYS_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_KEYS_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** The structure that keeps representation of key. */ + +struct bt_mesh_key +{ + /** PSA key representation is the PSA key identifier. */ + + psa_key_id_t key; +}; + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_KEYS_H */ diff --git a/include/nuttx/net/bluetooth/mesh/large_comp_data_cli.h b/include/nuttx/net/bluetooth/mesh/large_comp_data_cli.h new file mode 100644 index 0000000000000..a68a72aa7831d --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/large_comp_data_cli.h @@ -0,0 +1,200 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/large_comp_data_cli.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Header file for the Bluetooth Mesh Large Composition Data Client + * model API. + * @ingroup bt_mesh_large_comp_data_cli + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_LARGE_COMP_DATA_CLI_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_LARGE_COMP_DATA_CLI_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @defgroup bt_mesh_large_comp_data_cli Large Composition Data Client model + * @ingroup bt_mesh + * @{ + */ + +struct bt_mesh_large_comp_data_cli; + +/** Large Composition Data response. */ + +struct bt_mesh_large_comp_data_rsp +{ + /** Page number. */ + + uint8_t page; + + /** Offset within the page. */ + + uint16_t offset; + + /** Total size of the page. */ + + uint16_t total_size; + + /** Pointer to allocated buffer for storing received data. */ + + struct net_buf_simple *data; +}; + +/** Large Composition Data Status messages callbacks */ + +struct bt_mesh_large_comp_data_cli_cb +{ + /** @brief Optional callback for Large Composition Data Status message. + * + * Handles received Large Composition Data Status messages from a Large + * Composition Data Server. + * + * If the content of @c rsp is needed after exiting this callback, a user + * should deep copy it. + * + * @param cli Large Composition Data Client context. + * @param addr Address of the sender. + * @param rsp Response received from the server. + */ + +void (*large_comp_data_status)(struct bt_mesh_large_comp_data_cli *cli, + uint16_t addr, + struct bt_mesh_large_comp_data_rsp *rsp); + + /** @brief Optional callback for Models Metadata Status message. + * + * Handles received Models Metadata Status messages from a Large + * Composition Data Server. + * + * If the content of @c rsp is needed after exiting this callback, a user + * should deep copy it. + * + * @param cli Large Composition Data Client context. + * @param addr Address of the sender. + * @param rsp Response received from the server. + */ + +void (*models_metadata_status)(struct bt_mesh_large_comp_data_cli *cli, + uint16_t addr, + struct bt_mesh_large_comp_data_rsp *rsp); +}; + +/** Large Composition Data Client model context */ + +struct bt_mesh_large_comp_data_cli +{ + /** Model entry pointer. */ + + const struct bt_mesh_model *model; + + /** Internal parameters for tracking message responses. */ + + struct bt_mesh_msg_ack_ctx ack_ctx; + + /** Optional callback for Large Composition Data Status messages. */ + + const struct bt_mesh_large_comp_data_cli_cb *cb; +}; + +/** + * + * @brief Large Composition Data Client model Composition Data entry. + * + * @param cli_data Pointer to a @ref bt_mesh_large_comp_data_cli instance. + */ +#define BT_MESH_MODEL_LARGE_COMP_DATA_CLI(cli_data) \ +BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_LARGE_COMP_DATA_CLI, _bt_mesh_large_comp_data_cli_op, NULL, \ + cli_data, &_bt_mesh_large_comp_data_cli_cb) + +/** @brief Send Large Composition Data Get message. + * + * This API is used to read a portion of a Composition Data Page. + * + * This API can be used asynchronously by setting @p rsp as NULL. This way, + * the method will not wait for a response and will return immediately after + * sending the command. + * + * When @c rsp is set, the user is responsible for providing a buffer for the + * Composition Data in @ref bt_mesh_large_comp_data_rsp::data. If a buffer + * is not provided, the metadata won't be copied. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node element address. + * @param page Composition Data Page to read. + * @param offset Offset within the Composition Data Page. + * @param rsp Pointer to a struct storing the received response from + * the server, or NULL to not wait for a response. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_large_comp_data_get(uint16_t net_idx, + uint16_t addr, + uint8_t page, + size_t offset, + struct bt_mesh_large_comp_data_rsp *rsp); + +/** @brief Send Models Metadata Get message. + * + * This API is used to read a portion of a Models Metadata Page. + * + * This API can be used asynchronously by setting @p rsp as NULL. This way, + * the method will not wait for a response and will return immediately after + * sending the command. + * + * When @c rsp is set, a user is responsible for providing a buffer for + * metadata in @ref bt_mesh_large_comp_data_rsp::data. If a buffer is not + * provided, the metadata won't be copied. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node element address. + * @param page Models Metadata Page to read. + * @param offset Offset within the Models Metadata Page. + * @param rsp Pointer to a struct storing the received response from + * the server, or NULL to not wait for a response. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_models_metadata_get(uint16_t net_idx, + uint16_t addr, + uint8_t page, + size_t offset, + struct bt_mesh_large_comp_data_rsp *rsp); + +/** @cond INTERNAL_HIDDEN */ + +extern const struct bt_mesh_model_op _bt_mesh_large_comp_data_cli_op[]; +extern const struct bt_mesh_model_cb _bt_mesh_large_comp_data_cli_cb; + +/** @endcond */ + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_LARGE_COMP_DATA_CLI_H */ diff --git a/include/nuttx/net/bluetooth/mesh/large_comp_data_srv.h b/include/nuttx/net/bluetooth/mesh/large_comp_data_srv.h new file mode 100644 index 0000000000000..2378e408f15dc --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/large_comp_data_srv.h @@ -0,0 +1,63 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/large_comp_data_srv.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Header file for the Bluetooth Mesh Large Composition Data Server + * model API. + * @ingroup bt_mesh_large_comp_data_srv + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_LARGE_COMP_DATA_SRV_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_LARGE_COMP_DATA_SRV_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @defgroup bt_mesh_large_comp_data_srv Large Composition Data Server model + * @ingroup bt_mesh + * @{ + */ + +/** + * + * @brief Large Composition Data Server model composition data entry. + */ +#define BT_MESH_MODEL_LARGE_COMP_DATA_SRV \ +BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_LARGE_COMP_DATA_SRV, _bt_mesh_large_comp_data_srv_op, NULL, \ + NULL, &_bt_mesh_large_comp_data_srv_cb) + +/** @cond INTERNAL_HIDDEN */ + +extern const struct bt_mesh_model_op _bt_mesh_large_comp_data_srv_op[]; +extern const struct bt_mesh_model_cb _bt_mesh_large_comp_data_srv_cb; + +/** @endcond */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_LARGE_COMP_DATA_SRV_H */ + +/** + * @} + */ diff --git a/include/nuttx/net/bluetooth/mesh/main.h b/include/nuttx/net/bluetooth/mesh/main.h new file mode 100644 index 0000000000000..5781a31f8c9f4 --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/main.h @@ -0,0 +1,1034 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/main.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Bluetooth Mesh Protocol APIs. + */ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_MAIN_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_MAIN_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include + +/** + * @brief Provisioning + * @defgroup bt_mesh_prov Provisioning + * @ingroup bt_mesh + * @{ + */ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Available authentication algorithms. */ + +enum +{ + BT_MESH_PROV_AUTH_CMAC_AES128_AES_CCM, + BT_MESH_PROV_AUTH_HMAC_SHA256_AES_CCM, +}; + +/** OOB Type field values. */ + +enum +{ + BT_MESH_STATIC_OOB_AVAILABLE = BIT(0), /**< Static OOB information available */ + BT_MESH_OOB_AUTH_REQUIRED = BIT(1) /**< OOB authentication required */ +}; + +/** Available Provisioning output authentication actions. */ + +typedef enum +{ + BT_MESH_NO_OUTPUT = 0, + BT_MESH_BLINK = BIT(0), /**< Blink */ + BT_MESH_BEEP = BIT(1), /**< Beep */ + BT_MESH_VIBRATE = BIT(2), /**< Vibrate */ + BT_MESH_DISPLAY_NUMBER = BIT(3), /**< Output numeric */ + BT_MESH_DISPLAY_STRING = BIT(4), /**< Output alphanumeric */ +} bt_mesh_output_action_t; + +/** Available Provisioning input authentication actions. */ + +typedef enum +{ + BT_MESH_NO_INPUT = 0, + BT_MESH_PUSH = BIT(0), /**< Push */ + BT_MESH_TWIST = BIT(1), /**< Twist */ + BT_MESH_ENTER_NUMBER = BIT(2), /**< Input number */ + BT_MESH_ENTER_STRING = BIT(3), /**< Input alphanumeric */ +} bt_mesh_input_action_t; + +/** Available Provisioning bearers. */ + +typedef enum +{ + BT_MESH_PROV_ADV = BIT(0), /**< PB-ADV bearer */ + BT_MESH_PROV_GATT = BIT(1), /**< PB-GATT bearer */ + BT_MESH_PROV_REMOTE = BIT(2), /**< PB-Remote bearer */ +} bt_mesh_prov_bearer_t; + +/** Out of Band information location. */ + +typedef enum +{ + BT_MESH_PROV_OOB_OTHER = BIT(0), /**< Other */ + BT_MESH_PROV_OOB_URI = BIT(1), /**< Electronic / URI */ + BT_MESH_PROV_OOB_2D_CODE = BIT(2), /**< 2D machine-readable code */ + BT_MESH_PROV_OOB_BAR_CODE = BIT(3), /**< Bar Code */ + BT_MESH_PROV_OOB_NFC = BIT(4), /**< Near Field Communication (NFC) */ + BT_MESH_PROV_OOB_NUMBER = BIT(5), /**< Number */ + BT_MESH_PROV_OOB_STRING = BIT(6), /**< String */ + BT_MESH_PROV_OOB_CERTIFICATE = BIT(7), /**< Support for certificate-based provisioning */ + BT_MESH_PROV_OOB_RECORDS = BIT(8), /**< Support for provisioning records */ + + /* 9 - 10 are reserved */ + + BT_MESH_PROV_OOB_ON_BOX = BIT(11), /**< On box */ + BT_MESH_PROV_OOB_IN_BOX = BIT(12), /**< Inside box */ + BT_MESH_PROV_OOB_ON_PAPER = BIT(13), /**< On piece of paper */ + BT_MESH_PROV_OOB_IN_MANUAL = BIT(14), /**< Inside manual */ + BT_MESH_PROV_OOB_ON_DEV = BIT(15), /**< On device */ +} bt_mesh_prov_oob_info_t; + +/** Device Capabilities. */ + +struct bt_mesh_dev_capabilities +{ + /** Number of elements supported by the device */ + + uint8_t elem_count; + + /** Supported algorithms and other capabilities */ + + uint16_t algorithms; + + /** Supported public key types */ + + uint8_t pub_key_type; + + /** Supported OOB Types */ + + uint8_t oob_type; + + /** Supported Output OOB Actions */ + + bt_mesh_output_action_t output_actions; + + /** Supported Input OOB Actions */ + + bt_mesh_input_action_t input_actions; + + /** Maximum size of Output OOB supported */ + + uint8_t output_size; + + /** Maximum size in octets of Input OOB supported */ + + uint8_t input_size; +}; + +/** Provisioning properties & capabilities. */ + +struct bt_mesh_prov +{ + /** The UUID that's used when advertising as unprovisioned */ + + const uint8_t *uuid; + + /** Optional URI. This will be advertised separately from the + * unprovisioned beacon, however the unprovisioned beacon will contain a + * hash of it so the two can be associated by the provisioner. + */ + + const char *uri; + + /** Out of Band information field. */ + + bt_mesh_prov_oob_info_t oob_info; + + /** Pointer to Public Key in big-endian for OOB public key type support. + * + * Remember to enable @kconfig{CONFIG_BT_MESH_PROV_OOB_PUBLIC_KEY} when + * initializing this parameter. + * + * Must be used together with @ref bt_mesh_prov::private_key_be. + */ + + const uint8_t *public_key_be; + /** Pointer to Private Key in big-endian for OOB public key type support. + * + * Remember to enable @kconfig{CONFIG_BT_MESH_PROV_OOB_PUBLIC_KEY} when + * initializing this parameter. + * + * Must be used together with @ref bt_mesh_prov::public_key_be. + */ + + const uint8_t *private_key_be; + + /** Static OOB value */ + + const uint8_t *static_val; + + /** Static OOB value length */ + + uint8_t static_val_len; + + /** Maximum size of Output OOB supported */ + + uint8_t output_size; + + /** Supported Output OOB Actions */ + + uint16_t output_actions; + + /** Maximum size of Input OOB supported */ + + uint8_t input_size; + + /** Supported Input OOB Actions */ + + uint16_t input_actions; + + /** @brief Provisioning Capabilities. + * + * This callback notifies the application that the provisioning + * capabilities of the unprovisioned device has been received. + * + * The application can consequently call bt_mesh_auth_method_set_<*> to + * select suitable provisioning oob authentication method. + * + * When this callback returns, the provisioner will start authentication + * with the chosen method. + * + * @param cap capabilities supported by device. + */ + + void (*capabilities)(const struct bt_mesh_dev_capabilities *cap); + +#if defined CONFIG_BT_MESH_PROV_OOB_API_LEGACY + /** @brief Output of a number is requested. + * + * This callback notifies the application that it should output the given + * number using the given action. + * + * @param act Action for outputting the number. + * @param num Number to be outputted. + * + * @return Zero on success or negative error code otherwise + */ + + int (*output_number)(bt_mesh_output_action_t act, + uint32_t num) __deprecated; +#else + /** @brief Output of a numeric value is requested. + * + * This callback notifies the application that it should output the given + * numeric value using the given action. + * + * @param act Action for outputting the numeric value. + * @param numeric memory with numeric value in the little endian format to + * be outputted. + * @param size size of the numeric value in bytes. + * + * @return Zero on success or negative error code otherwise + */ + + int (*output_numeric)(bt_mesh_output_action_t act, uint8_t *numeric, + size_t size); +#endif + + /** @brief Output of a string is requested. + * + * This callback notifies the application that it should display the given + * string to the user. + * + * @param str String to be displayed. + * + * @return Zero on success or negative error code otherwise + */ + + int (*output_string)(const char *str); + + /** @brief Input is requested. + * + * This callback notifies the application that it should request input + * from the user using the given action. The requested input will either + * be a string or a number, and the application needs to consequently call + * the bt_mesh_input_string() or bt_mesh_input_numeric() functions once + * the data has been acquired from the user. + * + * @param act Action for inputting data. + * @param size Maximum size of the inputted data. + * + * @return Zero on success or negative error code otherwise + */ + + int (*input)(bt_mesh_input_action_t act, uint8_t size); + + /** @brief The other device finished their OOB input. + * + * This callback notifies the application that it should stop displaying + * its output OOB value, as the other party finished their OOB input. + */ + + void (*input_complete)(void); + + /** @brief Unprovisioned beacon has been received. + * + * This callback notifies the application that an unprovisioned beacon has + * been received. + * + * @param uuid UUID + * @param oob_info OOB Information + * @param uri_hash Pointer to URI Hash value. NULL if no hash was present + * in the beacon. + */ + + void (*unprovisioned_beacon)(uint8_t uuid[16], + bt_mesh_prov_oob_info_t oob_info, + uint32_t *uri_hash); + + /** @brief PB-GATT Unprovisioned Advertising has been received. + * + * This callback notifies the application that an PB-GATT unprovisioned + * Advertising has been received. + * + * @param uuid UUID + * @param oob_info OOB Information + */ + + void (*unprovisioned_beacon_gatt)(uint8_t uuid[16], + bt_mesh_prov_oob_info_t oob_info); + + /** @brief Provisioning link has been opened. + * + * This callback notifies the application that a provisioning link has + * been opened on the given provisioning bearer. + * + * @param bearer Provisioning bearer. + */ + + void (*link_open)(bt_mesh_prov_bearer_t bearer); + + /** @brief Provisioning link has been closed. + * + * This callback notifies the application that a provisioning link has + * been closed on the given provisioning bearer. + * + * @param bearer Provisioning bearer. + */ + + void (*link_close)(bt_mesh_prov_bearer_t bearer); + + /** @brief Provisioning is complete. + * + * This callback notifies the application that provisioning has been + * successfully completed, and that the local node has been assigned the + * specified NetKeyIndex and primary element address. + * + * @param net_idx NetKeyIndex given during provisioning. + * @param addr Primary element address. + */ + + void (*complete)(uint16_t net_idx, uint16_t addr); + + /** @brief Local node has been reprovisioned. + * + * This callback notifies the application that reprovisioning has been + * successfully completed. + * + * @param addr New primary element address. + */ + + void (*reprovisioned)(uint16_t addr); + + /** @brief A new node has been added to the provisioning database. + * + * This callback notifies the application that provisioning has been + * successfully completed, and that a node has been assigned the specified + * NetKeyIndex and primary element address. + * + * @param net_idx NetKeyIndex given during provisioning. + * @param uuid UUID of the added node + * @param addr Primary element address. + * @param num_elem Number of elements that this node has. + */ + + void (*node_added)(uint16_t net_idx, uint8_t uuid[16], uint16_t addr, + uint8_t num_elem); + + /** @brief Node has been reset. + * + * This callback notifies the application that the local node has been + * reset and needs to be provisioned again. The node will not + * automatically advertise as unprovisioned, rather the + * bt_mesh_prov_enable() API needs to be called to enable unprovisioned + * advertising on one or more provisioning bearers. + */ + + void (*reset)(void); +}; + +struct bt_mesh_rpr_cli; +struct bt_mesh_rpr_node; + +/** @brief Provide provisioning input OOB string. + * + * This is intended to be called after the @ref bt_mesh_prov::input callback + * has been called with BT_MESH_ENTER_STRING as the action. + * + * @param str String. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_mesh_input_string(const char *str); + +#if defined CONFIG_BT_MESH_PROV_OOB_API_LEGACY +/** @brief Provide provisioning input OOB number. + * + * This is intended to be called after the @ref bt_mesh_prov::input callback + * has been called with @ref BT_MESH_ENTER_NUMBER as the action. + * + * @param num Number. + * + * @return Zero on success or (negative) error code otherwise. + */ + +__deprecated int bt_mesh_input_number(uint32_t num); +#endif + +/** @brief Provide provisioning input OOB numeric value. + * + * This is intended to be called after the @ref bt_mesh_prov::input callback + * has been called with @ref BT_MESH_ENTER_NUMBER as the action. + * + * @param numeric Pointer to the numeric value in little endian. + * @param size Size of the numeric value in bytes (this is not OOB size). + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_mesh_input_numeric(uint8_t *numeric, size_t size); + +/** @brief Provide Device public key. + * + * @param public_key Device public key in big-endian. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_mesh_prov_remote_pub_key_set(const uint8_t public_key[64]); + +/** @brief Use Input OOB authentication. + * + * Provisioner only. + * + * Instruct the unprovisioned device to use the specified Input OOB + * authentication action. When using @ref BT_MESH_PUSH, @ref BT_MESH_TWIST + * or + * @ref BT_MESH_ENTER_NUMBER, the @ref bt_mesh_prov::output_numeric callback + * is called with a random number that has to be entered on the + * unprovisioned device. + * + * When using @ref BT_MESH_ENTER_STRING, the @ref + * bt_mesh_prov::output_string callback is called with a random string that + * has to be entered on the unprovisioned device. + * + * @param action Authentication action used by the unprovisioned device. + * @param size Authentication size. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_mesh_auth_method_set_input(bt_mesh_input_action_t action, + uint8_t size); + +/** @brief Use Output OOB authentication. + * + * Provisioner only. + * + * Instruct the unprovisioned device to use the specified Output OOB + * authentication action. The @ref bt_mesh_prov::input callback will be + * called. + * + * When using @ref BT_MESH_BLINK, @ref BT_MESH_BEEP, @ref BT_MESH_VIBRATE or + * @ref BT_MESH_DISPLAY_NUMBER, and the application has to call + * @ref bt_mesh_input_numeric with the random number indicated by + * the unprovisioned device. + * + * When using @ref BT_MESH_DISPLAY_STRING, the application has to call + * @ref bt_mesh_input_string with the random string displayed by the + * unprovisioned device. + * + * @param action Authentication action used by the unprovisioned device. + * @param size Authentication size. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_mesh_auth_method_set_output(bt_mesh_output_action_t action, + uint8_t size); + +/** @brief Use static OOB authentication. + * + * Provisioner only. + * + * Instruct the unprovisioned device to use static OOB authentication, and + * use the given static authentication value when provisioning. + * + * @param static_val Static OOB value. + * @param size Static OOB value size. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_mesh_auth_method_set_static(const uint8_t *static_val, uint8_t size); + +/** @brief Don't use OOB authentication. + * + * Provisioner only. + * + * Don't use any authentication when provisioning new devices. This is the + * default behavior. + * + * @warning Not using any authentication exposes the mesh network to + * impersonation attacks, where attackers can pretend to be the + * unprovisioned device to gain access to the network. Authentication is + * strongly encouraged. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_mesh_auth_method_set_none(void); + +/** @brief Enable specific provisioning bearers + * + * Enable one or more provisioning bearers. + * + * @param bearers Bit-wise or of provisioning bearers. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_mesh_prov_enable(bt_mesh_prov_bearer_t bearers); + +/** @brief Disable specific provisioning bearers + * + * Disable one or more provisioning bearers. + * + * @param bearers Bit-wise or of provisioning bearers. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_mesh_prov_disable(bt_mesh_prov_bearer_t bearers); + +/** @brief Provision the local Mesh Node. + * + * This API should normally not be used directly by the application. The + * only exception is for testing purposes where manual provisioning is + * desired without an actual external provisioner. + * + * @param net_key Network Key + * @param net_idx Network Key Index + * @param flags Provisioning Flags + * @param iv_index IV Index + * @param addr Primary element address + * @param dev_key Device Key + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_mesh_provision( + const uint8_t net_key[16], uint16_t net_idx, uint8_t flags, + uint32_t iv_index, uint16_t addr, const uint8_t dev_key[16]); + +/** @brief Provision a Mesh Node using PB-ADV + * + * @param uuid UUID + * @param net_idx Network Key Index + * @param addr Address to assign to remote device. If addr is + * 0, the lowest available address will be chosen. + * @param attention_duration The attention duration to be send to remote + * device + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_mesh_provision_adv( + const uint8_t uuid[16], uint16_t net_idx, uint16_t addr, + uint8_t attention_duration); + +/** @brief Provision a Mesh Node using PB-GATT + * + * @param uuid UUID + * @param net_idx Network Key Index + * @param addr Address to assign to remote device. If addr is + * 0, the lowest available address will be chosen. + * @param attention_duration The attention duration to be send to remote + * device + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_mesh_provision_gatt( + const uint8_t uuid[16], uint16_t net_idx, uint16_t addr, + uint8_t attention_duration); + +/** @brief Provision a Mesh Node using PB-Remote + * + * @param cli Remote Provisioning Client Model to provision with. + * @param srv Remote Provisioning Server that should be used to tunnel + * the provisioning. + * @param uuid UUID of the unprovisioned node + * @param net_idx Network Key Index to give to the unprovisioned node. + * @param addr Address to assign to remote device. If addr is 0, the + * lowest available address will be chosen. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_mesh_provision_remote( + struct bt_mesh_rpr_cli *cli, const struct bt_mesh_rpr_node *srv, + const uint8_t uuid[16], uint16_t net_idx, uint16_t addr); + +/** @brief Reprovision a Mesh Node using PB-Remote + * + * Reprovisioning can be used to change the device key, unicast address and + * composition data of another device. The reprovisioning procedure uses the + * same protocol as normal provisioning, with the same level of security. + * + * There are three tiers of reprovisioning: 1. Refreshing the device key 2. + * Refreshing the device key and node address. Composition data may change, + * including the number of elements. 3. Refreshing the device key and + * composition data, in case the composition data of the target node changed + * due to a firmware update or a similar procedure. + * + * The target node indicates that its composition data changed by + * instantiating its composition data page 128. If the number of elements + * have changed, it may be necessary to move the unicast address of the + * target node as well, to avoid overlapping addresses. + * + * @note Changing the unicast addresses of the target node requires changes + * to all nodes that publish directly to any of the target node's models. + * + * @param cli Remote Provisioning Client Model to provision on + * @param srv Remote Provisioning Server to reprovision + * @param addr Address to assign to remote device. If addr is 0, the + * lowest available address will be chosen. + * @param comp_change The target node has indicated that its composition + * data has changed. Note that the target node will + * reject the update if this isn't true. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_mesh_reprovision_remote( + struct bt_mesh_rpr_cli *cli, struct bt_mesh_rpr_node *srv, uint16_t addr, + bool comp_change); + +/** @brief Check if the local node has been provisioned. + * + * This API can be used to check if the local node has been provisioned or + * not. It can e.g. be helpful to determine if there was a stored network in + * flash, i.e. if the network was restored after calling settings_load(). + * + * @return True if the node is provisioned. False otherwise. + */ + +bool bt_mesh_is_provisioned(void); + +/** + * @} + */ + +/** + * @brief Bluetooth Mesh + * @defgroup bt_mesh Bluetooth Mesh + * @ingroup bluetooth + * @{ + */ + +/** Primary Network Key index */ +#define BT_MESH_NET_PRIMARY 0x000 + +/** Relay feature */ +#define BT_MESH_FEAT_RELAY BIT(0) +/** GATT Proxy feature */ +#define BT_MESH_FEAT_PROXY BIT(1) +/** Friend feature */ +#define BT_MESH_FEAT_FRIEND BIT(2) +/** Low Power Node feature */ +#define BT_MESH_FEAT_LOW_POWER BIT(3) +/** Supported heartbeat publication features */ +#define BT_MESH_FEAT_SUPPORTED \ +(BT_MESH_FEAT_RELAY | BT_MESH_FEAT_PROXY | BT_MESH_FEAT_FRIEND | BT_MESH_FEAT_LOW_POWER) + +/** @brief Initialize Mesh support + * + * After calling this API, the node will not automatically advertise as + * unprovisioned, rather the bt_mesh_prov_enable() API needs to be called to + * enable unprovisioned advertising on one or more provisioning bearers. + * + * @param prov Node provisioning information. + * @param comp Node Composition. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_mesh_init(const struct bt_mesh_prov *prov, + const struct bt_mesh_comp *comp); + +/** @brief Reset the state of the local Mesh node. + * + * Resets the state of the node, which means that it needs to be + * reprovisioned to become an active node in a Mesh network again. + * + * After calling this API, the node will not automatically advertise as + * unprovisioned, rather the bt_mesh_prov_enable() API needs to be called to + * enable unprovisioned advertising on one or more provisioning bearers. + * + */ + +void bt_mesh_reset(void); + +/** @brief Suspend the Mesh network temporarily. + * + * This API can be used for power saving purposes, but the user should be + * aware that leaving the local node suspended for a long period of time may + * cause it to become permanently disconnected from the Mesh network. If at + * all possible, the Friendship feature should be used instead, to make the + * node into a Low Power Node. + * + * @note Provisioning has not been designed to be suspended. If this API is + * called while provisioning is in progress, suspension is not possible and + * the function returns an error. + * + * @retval 0 Success. + * @retval -EBUSY Provisioning is active. + * @retval Other negative errno code on failure. + */ + +int bt_mesh_suspend(void); + +/** @brief Resume a suspended Mesh network. + * + * This API resumes the local node, after it has been suspended using the + * bt_mesh_suspend() API. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_resume(void); + +/** @brief Toggle the IV Update test mode + * + * This API is only available if the IV Update test mode has been enabled in + * Kconfig. It is needed for passing most of the IV Update qualification + * test cases. + * + * @param enable true to enable IV Update test mode, false to disable it. + */ + +void bt_mesh_iv_update_test(bool enable); + +/** @brief Toggle the IV Update state + * + * This API is only available if the IV Update test mode has been enabled in + * Kconfig. It is needed for passing most of the IV Update qualification + * test cases. + * + * @return true if IV Update In Progress state was entered, false otherwise. + */ + +bool bt_mesh_iv_update(void); + +/** @brief Toggle the Low Power feature of the local device + * + * Enables or disables the Low Power feature of the local device. This is + * exposed as a run-time feature, since the device might want to change this + * e.g. based on being plugged into a stable power source or running from a + * battery power source. + * + * @param enable true to enable LPN functionality, false to disable it. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_mesh_lpn_set(bool enable); + +/** @brief Send out a Friend Poll message. + * + * Send a Friend Poll message to the Friend of this node. If there is no + * established Friendship the function will return an error. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_mesh_lpn_poll(void); + +/** Low Power Node callback functions. */ + +struct bt_mesh_lpn_cb +{ + /** @brief Friendship established. + * + * This callback notifies the application that friendship has been + * successfully established. + * + * @param net_idx NetKeyIndex used during friendship establishment. + * @param friend_addr Friend address. + * @param queue_size Friend queue size. + * @param recv_window Low Power Node's listens duration for Friend + * response. + */ + + void (*established)(uint16_t net_idx, uint16_t friend_addr, + uint8_t queue_size, uint8_t recv_window); + + /** @brief Friendship terminated. + * + * This callback notifies the application that friendship has been + * terminated. + * + * @param net_idx NetKeyIndex used during friendship establishment. + * @param friend_addr Friend address. + */ + + void (*terminated)(uint16_t net_idx, uint16_t friend_addr); + + /** @brief Local Poll Request. + * + * This callback notifies the application that the local node has polled + * the friend node. + * + * This callback will be called before @ref bt_mesh_lpn_cb::established + * when attempting to establish a friendship. + * + * @param net_idx NetKeyIndex used during friendship establishment. + * @param friend_addr Friend address. + * @param retry Retry or first poll request for each transaction. + */ + + void (*polled)(uint16_t net_idx, uint16_t friend_addr, bool retry); +}; + +/** + * @brief Register a callback structure for Friendship events. + * + * @param _name Name of callback structure. + */ +#define BT_MESH_LPN_CB_DEFINE(_name) \ +static const STRUCT_SECTION_ITERABLE(bt_mesh_lpn_cb, \ + _CONCAT(bt_mesh_lpn_cb_, _name)) + +/** Friend Node callback functions. */ + +struct bt_mesh_friend_cb +{ + /** @brief Friendship established. + * + * This callback notifies the application that friendship has been + * successfully established. + * + * @param net_idx NetKeyIndex used during friendship establishment. + * @param lpn_addr Low Power Node address. + * @param recv_delay Receive Delay in units of 1 millisecond. + * @param polltimeout PollTimeout in units of 1 millisecond. + */ + + void (*established)(uint16_t net_idx, uint16_t lpn_addr, + uint8_t recv_delay, uint32_t polltimeout); + + /** @brief Friendship terminated. + * + * This callback notifies the application that friendship has been + * terminated. + * + * @param net_idx NetKeyIndex used during friendship establishment. + * @param lpn_addr Low Power Node address. + */ + + void (*terminated)(uint16_t net_idx, uint16_t lpn_addr); + + /** @brief Friend Poll Request. + * + * This callback notifies the application that the low power node has + * polled the friend node. + * + * This callback will be called before @ref bt_mesh_friend_cb::established + * when attempting to establish a friendship. + * + * @param net_idx NetKeyIndex used during friendship establishment. + * @param lpn_addr LPN address. + */ + + void (*polled)(uint16_t net_idx, uint16_t lpn_addr); +}; + +/** + * @brief Register a callback structure for Friendship events. + * + * Registers a callback structure that will be called whenever Friendship + * gets established or terminated. + * + * @param _name Name of callback structure. + */ +#define BT_MESH_FRIEND_CB_DEFINE(_name) \ +static const STRUCT_SECTION_ITERABLE(bt_mesh_friend_cb, \ + _CONCAT(bt_mesh_friend_cb_, _name)) + +#if defined(CONFIG_BT_TESTING) +struct bt_mesh_snb +{ + /** Flags */ + + uint8_t flags; + + /** Network ID */ + + uint64_t net_id; + + /** IV Index */ + + uint32_t iv_idx; + + /** Authentication Value */ + + uint64_t auth_val; +}; + +struct bt_mesh_prb +{ + /** Random */ + + uint8_t random[13]; + + /** Flags */ + + uint8_t flags; + + /** IV Index */ + + uint32_t iv_idx; + + /** Authentication tag */ + + uint64_t auth_tag; +}; + +/** Beacon callback functions. */ + +struct bt_mesh_beacon_cb +{ + /** @brief Secure Network Beacon received. + * + * This callback notifies the application that Secure Network Beacon was + * received. + * + * @param snb Structure describing received Secure Network Beacon + */ + + void (*snb_received)(const struct bt_mesh_snb *snb); + + /** @brief Private Beacon received. + * + * This callback notifies the application that Private Beacon was received + * and successfully decrypted. + * + * @param prb Structure describing received Private Beacon + */ + + void (*priv_received)(const struct bt_mesh_prb *prb); +}; + +/** + * @brief Register a callback structure for beacon events. + * + * Registers a callback structure that will be called whenever beacon + * advertisement is received. + * + * @param _name Name of callback structure. + */ +#define BT_MESH_BEACON_CB_DEFINE(_name) \ +static const STRUCT_SECTION_ITERABLE(bt_mesh_beacon_cb, \ + _CONCAT(bt_mesh_beacon_cb_, _name)) +#endif + +/** @brief Terminate Friendship. + * + * Terminated Friendship for given LPN. + * + * @param lpn_addr Low Power Node address. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_mesh_friend_terminate(uint16_t lpn_addr); + +/** @brief Store pending RPL entry(ies) in the persistent storage. + * + * This API allows the user to store pending RPL entry(ies) in the + * persistent storage without waiting for the timeout. + * + * @note When flash is used as the persistent storage, calling this API too + * frequently may wear it out. + * + * @param addr Address of the node which RPL entry needs to be stored or + * @ref BT_MESH_ADDR_ALL_NODES to store all pending RPL entries. + */ + +void bt_mesh_rpl_pending_store(uint16_t addr); + +/** @brief Iterate stored Label UUIDs. + * + * When @c addr is @ref BT_MESH_ADDR_UNASSIGNED, this function iterates over + * all available addresses starting with @c uuid. In this case, use + * @c retaddr to get virtual address representation of the returned + * Label UUID. When @c addr is a virtual address, this function + * returns next Label UUID corresponding to the @c addr. When + * @c uuid is NULL, this function returns the first available + * UUID. If @c uuid is previously returned uuid, this function + * returns following uuid. + * + * @param addr Virtual address to search for, or @ref + * BT_MESH_ADDR_UNASSIGNED. + * @param uuid Pointer to the previously returned Label UUID or NULL. + * @param retaddr Pointer to a memory where virtual address representation + * of the returning UUID is to be stored to. + * + * @return Pointer to Label UUID, or NULL if no more entries found. + */ + +const uint8_t *bt_mesh_va_uuid_get(uint16_t addr, + const uint8_t *uuid, uint16_t *retaddr); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_MAIN_H */ diff --git a/include/nuttx/net/bluetooth/mesh/msg.h b/include/nuttx/net/bluetooth/mesh/msg.h new file mode 100644 index 0000000000000..7139f631ab3ad --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/msg.h @@ -0,0 +1,335 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/msg.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Message APIs. + */ + +/* Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_MSG_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_MSG_H + +/** + * @brief Message + * @defgroup bt_mesh_msg Message + * @ingroup bt_mesh + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct bt_mesh_model; + +/** Length of a short Mesh MIC. */ +#define BT_MESH_MIC_SHORT 4 +/** Length of a long Mesh MIC. */ +#define BT_MESH_MIC_LONG 8 + +/** + * @brief Helper to determine the length of an opcode. + * + * @param _op Opcode. + */ +#define BT_MESH_MODEL_OP_LEN(_op) ((_op) <= 0xff ? 1 : (_op) <= 0xffff ? 2 : 3) + +/** + * @brief Helper for model message buffer length. + * + * Returns the length of a Mesh model message buffer, including the opcode + * length and a short MIC. + * + * @param _op Opcode of the message. + * @param _payload_len Length of the model payload. + */ +#define BT_MESH_MODEL_BUF_LEN(_op, _payload_len) \ +(BT_MESH_MODEL_OP_LEN(_op) + (_payload_len) + BT_MESH_MIC_SHORT) + +/** + * @brief Helper for model message buffer length. + * + * Returns the length of a Mesh model message buffer, including the opcode + * length and a long MIC. + * + * @param _op Opcode of the message. + * @param _payload_len Length of the model payload. + */ +#define BT_MESH_MODEL_BUF_LEN_LONG_MIC(_op, _payload_len) \ +(BT_MESH_MODEL_OP_LEN(_op) + (_payload_len) + BT_MESH_MIC_LONG) + +/** + * @brief Define a Mesh model message buffer using @ref + * NET_BUF_SIMPLE_DEFINE. + * + * @param _buf Buffer name. + * @param _op Opcode of the message. + * @param _payload_len Length of the model message payload. + */ +#define BT_MESH_MODEL_BUF_DEFINE(_buf, _op, _payload_len) \ +NET_BUF_SIMPLE_DEFINE(_buf, BT_MESH_MODEL_BUF_LEN(_op, (_payload_len))) + +/** Message sending context. */ + +struct bt_mesh_msg_ctx +{ + /** NetKey Index of the subnet to send the message on. */ + + uint16_t net_idx; + + /** AppKey Index to encrypt the message with. */ + + uint16_t app_idx; + + /** Remote address. */ + + uint16_t addr; + + /** Destination address of a received message. Not used for sending. */ + + uint16_t recv_dst; + + /** Label UUID if Remote address is Virtual address, or NULL otherwise. */ + + const uint8_t *uuid; + + /** RSSI of received packet. Not used for sending. */ + + int8_t recv_rssi; + + /** Received TTL value. Not used for sending. */ + + uint8_t recv_ttl; + + /** Force sending reliably by using segment acknowledgment */ + + bool send_rel; + + /** Send message with a random delay according to the Access layer + * transmitting rules. + */ + + bool rnd_delay; + + /** TTL, or BT_MESH_TTL_DEFAULT for default TTL. */ + + uint8_t send_ttl; +}; + +/** + * @brief Helper for bt_mesh_msg_ctx structure initialization. + * + * @note If @c dst is a Virtual Address, Label UUID shall be initialized + * separately. + * + * @param net_key_idx NetKey Index of the subnet to send the message on. Only + * used if @c app_key_idx points to devkey. + * @param app_key_idx AppKey Index to encrypt the message with. + * @param dst Remote addr. + * @param ttl Time To Live. + */ +#define BT_MESH_MSG_CTX_INIT(net_key_idx, app_key_idx, dst, ttl) \ +{ \ + .net_idx = (net_key_idx), \ + .app_idx = (app_key_idx), \ + .addr = (dst), \ + .send_ttl = (ttl), \ +} + +/** + * @brief Helper for bt_mesh_msg_ctx structure initialization secured with + * Application Key. + * + * @param app_key_idx AppKey Index to encrypt the message with. + * @param dst Remote addr. + */ +#define BT_MESH_MSG_CTX_INIT_APP(app_key_idx, dst) \ +BT_MESH_MSG_CTX_INIT(0, app_key_idx, dst, BT_MESH_TTL_DEFAULT) + +/** + * @brief Helper for bt_mesh_msg_ctx structure initialization secured with + * Device Key of a remote device. + * + * @param net_key_idx NetKey Index of the subnet to send the message on. + * @param dst Remote addr. + */ +#define BT_MESH_MSG_CTX_INIT_DEV(net_key_idx, dst) \ +BT_MESH_MSG_CTX_INIT(net_key_idx, BT_MESH_KEY_DEV_REMOTE, dst, BT_MESH_TTL_DEFAULT) + +/** + * @brief Helper for bt_mesh_msg_ctx structure initialization using Model + * Publication context. + * + * @param pub Pointer to a model publication context. + */ +#define BT_MESH_MSG_CTX_INIT_PUB(pub) \ +{ \ + .app_idx = (pub)->key, \ + .addr = (pub)->addr, \ + .send_ttl = (pub)->ttl, \ + .uuid = (pub)->uuid, \ +} + +/** @brief Initialize a model message. + * + * Clears the message buffer contents, and encodes the given opcode. The + * message buffer will be ready for filling in payload data. + * + * @param msg Message buffer. + * @param opcode Opcode to encode. + */ + +void bt_mesh_model_msg_init(struct net_buf_simple *msg, uint32_t opcode); + +/** + * Acknowledged message context for tracking the status of model messages + * pending a response. + */ + +struct bt_mesh_msg_ack_ctx +{ + sem_t sem; /**< Sync semaphore. */ + uint32_t op; /**< Opcode we're waiting for. */ + uint16_t dst; /**< Address of the node that should respond. */ + void *user_data; /**< User specific parameter. */ +}; + +/** @brief Initialize an acknowledged message context. + * + * Initializes semaphore used for synchronization between @ref + * bt_mesh_msg_ack_ctx_wait and + * @ref bt_mesh_msg_ack_ctx_rx calls. Call this function before using @ref + * bt_mesh_msg_ack_ctx. + * + * @param ack Acknowledged message context to initialize. + */ + +static inline void bt_mesh_msg_ack_ctx_init(struct bt_mesh_msg_ack_ctx *ack) +{ + nxsem_init(&ack->sem, 0, 0); +} + +/** @brief Reset the synchronization semaphore in an acknowledged message + * context. + * + * This function aborts call to @ref bt_mesh_msg_ack_ctx_wait. + * + * @param ack Acknowledged message context to be reset. + */ + +static inline void bt_mesh_msg_ack_ctx_reset(struct bt_mesh_msg_ack_ctx *ack) +{ + nxsem_reset(&ack->sem, 0); +} + +/** @brief Clear parameters of an acknowledged message context. + * + * This function clears the opcode, remote address and user data set by @ref + * bt_mesh_msg_ack_ctx_prepare. + * + * @param ack Acknowledged message context to be cleared. + */ + +void bt_mesh_msg_ack_ctx_clear(struct bt_mesh_msg_ack_ctx *ack); + +/** @brief Prepare an acknowledged message context for the incoming message + * to wait. + * + * This function sets the opcode, remote address of the incoming message and + * stores the user data. Use this function before calling @ref + * bt_mesh_msg_ack_ctx_wait. + * + * @param ack Acknowledged message context to prepare. + * @param op The message OpCode. + * @param dst Destination address of the message. + * @param user_data User data for the acknowledged message context. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_msg_ack_ctx_prepare( + struct bt_mesh_msg_ack_ctx *ack, uint32_t op, uint16_t dst, + void *user_data); + +/** @brief Check if the acknowledged message context is initialized with an + * opcode. + * + * @param ack Acknowledged message context. + * + * @return true if the acknowledged message context is initialized with an + * opcode, false otherwise. + */ + +static inline bool bt_mesh_msg_ack_ctx_busy(struct bt_mesh_msg_ack_ctx *ack) +{ + return (ack->op != 0); +} + +/** @brief Wait for a message acknowledge. + * + * This function blocks execution until @ref bt_mesh_msg_ack_ctx_rx is + * called or by timeout. + * + * @param ack Acknowledged message context of the message to wait for. + * @param timeout Wait timeout. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_msg_ack_ctx_wait(struct bt_mesh_msg_ack_ctx *ack, + clock_t timeout); + +/** @brief Mark a message as acknowledged. + * + * This function unblocks call to @ref bt_mesh_msg_ack_ctx_wait. + * + * @param ack Context of a message to be acknowledged. + */ + +static inline void bt_mesh_msg_ack_ctx_rx(struct bt_mesh_msg_ack_ctx *ack) +{ + nxsem_post(&ack->sem); +} + +/** @brief Check if an opcode and address of a message matches the expected + * one. + * + * @param ack Acknowledged message context to be checked. + * @param op OpCode of the incoming message. + * @param addr Source address of the incoming message. + * @param user_data If not NULL, returns a user data stored in the + * acknowledged message context by @ref + * bt_mesh_msg_ack_ctx_prepare. + * + * @return true if the incoming message matches the expected one, false + * otherwise. + */ + +bool bt_mesh_msg_ack_ctx_match( + const struct bt_mesh_msg_ack_ctx *ack, uint32_t op, uint16_t addr, + void **user_data); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_MSG_H */ diff --git a/include/nuttx/net/bluetooth/mesh/od_priv_proxy_cli.h b/include/nuttx/net/bluetooth/mesh/od_priv_proxy_cli.h new file mode 100644 index 0000000000000..22c477d990b93 --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/od_priv_proxy_cli.h @@ -0,0 +1,136 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/od_priv_proxy_cli.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Header file for the Bluetooth Mesh On-Demand Private GATT Proxy + * Client model API. + * @ingroup bt_mesh_od_priv_proxy_cli + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_OD_PRIV_PROXY_CLI_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_OD_PRIV_PROXY_CLI_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @defgroup bt_mesh_od_priv_proxy_cli Bluetooth Mesh On-Demand Private GATT + * Proxy Client + * @ingroup bt_mesh + * @{ + */ + +/** On-Demand Private Proxy Client Model Context */ + +struct bt_mesh_od_priv_proxy_cli +{ + /** Solicitation PDU RPL model entry pointer. */ + + const struct bt_mesh_model *model; + + /* Internal parameters for tracking message responses. */ + + struct bt_mesh_msg_ack_ctx ack_ctx; + + /** @brief Optional callback for On-Demand Private Proxy Status messages. + * + * Handles received On-Demand Private Proxy Status messages from a + * On-Demand Private Proxy server.The @c state param represents state of + * On-Demand Private Proxy server. + * + * @param cli On-Demand Private Proxy client that received the + * status message. + * @param addr Address of the sender. + * @param state State value. + */ + + void (*od_status)(struct bt_mesh_od_priv_proxy_cli *cli, uint16_t addr, + uint8_t state); +}; + +/** + * @brief On-Demand Private Proxy Client model composition data entry. + */ +#define BT_MESH_MODEL_OD_PRIV_PROXY_CLI(cli_data) \ +BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_ON_DEMAND_PROXY_CLI, _bt_mesh_od_priv_proxy_cli_op, NULL, \ + cli_data, &_bt_mesh_od_priv_proxy_cli_cb) + +/** @brief Get the target's On-Demand Private GATT Proxy state. + * + * This method can be used asynchronously by setting @p val_rsp as NULL. + * This way the method will not wait for response and will return + * immediately after sending the command. + * + * To process the response arguments of an async method, register the @c + * od_status callback in @c bt_mesh_od_priv_proxy_cli struct. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param val_rsp Response buffer for On-Demand Private GATT Proxy value. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_od_priv_proxy_cli_get(uint16_t net_idx, + uint16_t addr, uint8_t *val_rsp); + +/** @brief Set the target's On-Demand Private GATT Proxy state. + * + * This method can be used asynchronously by setting @p val_rsp as NULL. + * This way the method will not wait for response and will return + * immediately after sending the command. + * + * To process the response arguments of an async method, register the @c + * od_status callback in @c bt_mesh_od_priv_proxy_cli struct. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param val On-Demand Private GATT Proxy state to be set + * @param val_rsp Response buffer for On-Demand Private GATT Proxy value. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_od_priv_proxy_cli_set(uint16_t net_idx, + uint16_t addr, uint8_t val, + uint8_t *val_rsp); + +/** @brief Set the transmission timeout value. + * + * @param timeout The new transmission timeout in milliseconds. + */ + +void bt_mesh_od_priv_proxy_cli_timeout_set(int32_t timeout); + +/** @cond INTERNAL_HIDDEN */ + +extern const struct bt_mesh_model_op _bt_mesh_od_priv_proxy_cli_op[]; +extern const struct bt_mesh_model_cb _bt_mesh_od_priv_proxy_cli_cb; + +/** @endcond */ + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_OD_PRIV_PROXY_CLI_H */ diff --git a/include/nuttx/net/bluetooth/mesh/od_priv_proxy_srv.h b/include/nuttx/net/bluetooth/mesh/od_priv_proxy_srv.h new file mode 100644 index 0000000000000..c210ad6f8b2e2 --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/od_priv_proxy_srv.h @@ -0,0 +1,62 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/od_priv_proxy_srv.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Header file for the Bluetooth Mesh On-Demand Private GATT Proxy + * Server model API. + * @ingroup bt_mesh_od_priv_proxy_srv + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_OD_PRIV_PROXY_SRV_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_OD_PRIV_PROXY_SRV_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @defgroup bt_mesh_od_priv_proxy_srv Bluetooth Mesh On-Demand Private GATT + * Proxy Server + * @ingroup bt_mesh + * @{ + */ + +/** + * @brief On-Demand Private Proxy Server model composition data entry. + */ +#define BT_MESH_MODEL_OD_PRIV_PROXY_SRV \ +BT_MESH_MODEL_SOL_PDU_RPL_SRV, \ + BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_ON_DEMAND_PROXY_SRV, _bt_mesh_od_priv_proxy_srv_op, \ + NULL, NULL, &_bt_mesh_od_priv_proxy_srv_cb) + +/** @cond INTERNAL_HIDDEN */ + +extern const struct bt_mesh_model_op _bt_mesh_od_priv_proxy_srv_op[]; +extern const struct bt_mesh_model_cb _bt_mesh_od_priv_proxy_srv_cb; + +/** @endcond */ + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_OD_PRIV_PROXY_SRV_H */ diff --git a/include/nuttx/net/bluetooth/mesh/op_agg_cli.h b/include/nuttx/net/bluetooth/mesh/op_agg_cli.h new file mode 100644 index 0000000000000..2327fca551750 --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/op_agg_cli.h @@ -0,0 +1,122 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/op_agg_cli.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Header file for the Bluetooth Mesh Opcodes Aggregator Client model + * API. + * @ingroup bt_mesh_op_agg_cli + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_OP_AGG_CLI_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_OP_AGG_CLI_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @defgroup bt_mesh_op_agg_cli Opcodes Aggregator Client model + * @ingroup bt_mesh + * @{ + */ + +/** + * + * @brief Opcodes Aggregator Client model composition data entry. + */ +#define BT_MESH_MODEL_OP_AGG_CLI \ +BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_OP_AGG_CLI, _bt_mesh_op_agg_cli_op, NULL, NULL, \ + &_bt_mesh_op_agg_cli_cb) + +/** @brief Configure Opcodes Aggregator context. + * + * @param net_idx NetKey index to encrypt with. + * @param app_idx AppKey index to encrypt with. + * @param dst Target Opcodes Aggregator Server address. + * @param elem_addr Target node element address for the sequence + * message. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_op_agg_cli_seq_start(uint16_t net_idx, + uint16_t app_idx, + uint16_t dst, + uint16_t elem_addr); + +/** @brief Opcodes Aggregator message send. + * + * Uses previously configured context and sends aggregated message to target + * node. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_op_agg_cli_seq_send(void); + +/** @brief Abort Opcodes Aggregator context. + */ + +void bt_mesh_op_agg_cli_seq_abort(void); + +/** @brief Check if Opcodes Aggregator Sequence context is started. + * + * @return true if it is started, otherwise false. + */ + +bool bt_mesh_op_agg_cli_seq_is_started(void); + +/** @brief Get Opcodes Aggregator context tailroom. + * + * @return Remaining tailroom of Opcodes Aggregator SDU. + */ + +size_t bt_mesh_op_agg_cli_seq_tailroom(void); + +/** @brief Get the current transmission timeout value. + * + * @return The configured transmission timeout in milliseconds. + */ + +int32_t bt_mesh_op_agg_cli_timeout_get(void); + +/** @brief Set the transmission timeout value. + * + * @param timeout The new transmission timeout. + */ + +void bt_mesh_op_agg_cli_timeout_set(int32_t timeout); + +/** @cond INTERNAL_HIDDEN */ + +extern const struct bt_mesh_model_op _bt_mesh_op_agg_cli_op[]; +extern const struct bt_mesh_model_cb _bt_mesh_op_agg_cli_cb; + +/** @endcond */ + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_OP_AGG_CLI_H */ diff --git a/include/nuttx/net/bluetooth/mesh/op_agg_srv.h b/include/nuttx/net/bluetooth/mesh/op_agg_srv.h new file mode 100644 index 0000000000000..0769bca823695 --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/op_agg_srv.h @@ -0,0 +1,69 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/op_agg_srv.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Header file for the Bluetooth Mesh Opcodes Aggregator Server model + * API. + * @ingroup bt_mesh_op_agg_srv + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_OP_AGG_SRV_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_OP_AGG_SRV_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @defgroup bt_mesh_op_agg_srv Opcodes Aggregator Server model + * @ingroup bt_mesh + * @{ + */ + +/** + * + * @brief Opcodes Aggretator Server model composition data entry. + * + * @note The Opcodes Aggregator Server handles aggregated messages + * and dispatches them to the respective models and their message handlers. + * Current implementation assumes that responses are sent from the same + * execution context as the received message and doesn't allow to send a + * postponed response, e.g. from workqueue. + */ +#define BT_MESH_MODEL_OP_AGG_SRV \ +BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_OP_AGG_SRV, _bt_mesh_op_agg_srv_op, NULL, NULL, \ + &_bt_mesh_op_agg_srv_cb) + +/** @cond INTERNAL_HIDDEN */ + +extern const struct bt_mesh_model_op _bt_mesh_op_agg_srv_op[]; +extern const struct bt_mesh_model_cb _bt_mesh_op_agg_srv_cb; + +/** @endcond */ + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_OP_AGG_SRV_H */ diff --git a/include/nuttx/net/bluetooth/mesh/priv_beacon_cli.h b/include/nuttx/net/bluetooth/mesh/priv_beacon_cli.h new file mode 100644 index 0000000000000..0189efdd529b4 --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/priv_beacon_cli.h @@ -0,0 +1,251 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/priv_beacon_cli.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Header file for the Bluetooth Mesh Private Beacon Client model API. + * @ingroup bt_mesh_priv_beacon_cli + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_PRIV_BEACON_CLI_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_PRIV_BEACON_CLI_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @defgroup bt_mesh_priv_beacon_cli Bluetooth Mesh Private Beacon Client + * @ingroup bt_mesh + * @{ + */ + +/** + * + * @brief Private Beacon Client model composition data entry. + * + * @param cli_data Pointer to a @ref bt_mesh_priv_beacon_cli instance. + */ +#define BT_MESH_MODEL_PRIV_BEACON_CLI(cli_data) \ +BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_PRIV_BEACON_CLI, bt_mesh_priv_beacon_cli_op, NULL, cli_data, \ + &bt_mesh_priv_beacon_cli_cb) + +struct bt_mesh_priv_beacon_cli; + +/** Private Beacon */ + +struct bt_mesh_priv_beacon +{ + /** Private beacon is enabled */ + + uint8_t enabled; + /** Random refresh interval (in 10 second steps), or 0 to keep current + * value. + */ + + uint8_t rand_interval; +}; + +/** Private Node Identity */ + +struct bt_mesh_priv_node_id +{ + /** Index of the NetKey. */ + + uint16_t net_idx; + + /** Private Node Identity state */ + + uint8_t state; + + /** Response status code. */ + + uint8_t status; +}; + +/** Private Beacon Client Status messages callbacks */ + +struct bt_mesh_priv_beacon_cli_cb +{ + /** @brief Optional callback for Private Beacon Status message. + * + * Handles received Private Beacon Status messages from a Private Beacon + * server. + * + * @param cli Private Beacon client context. + * @param addr Address of the sender. + * @param priv_beacon Mesh Private Beacon state received from the server. + */ + +void (*priv_beacon_status)(struct bt_mesh_priv_beacon_cli *cli, + uint16_t addr, + struct bt_mesh_priv_beacon *priv_beacon); + + /** @brief Optional callback for Private GATT Proxy Status message. + * + * Handles received Private GATT Proxy Status messages from a Private + * Beacon server. + * + * @param cli Private Beacon client context. + * @param addr Address of the sender. + * @param gatt_proxy Private GATT Proxy state received from the server. + */ + +void (*priv_gatt_proxy_status)(struct bt_mesh_priv_beacon_cli *cli, + uint16_t addr, + uint8_t gatt_proxy); + + /** @brief Optional callback for Private Node Identity Status message. + * + * Handles received Private Node Identity Status messages from a Private + * Beacon server. + * + * @param cli Private Beacon client context. + * @param addr Address of the sender. + * @param priv_node_id Private Node Identity state received from the + * server. + */ + +void (*priv_node_id_status)(struct bt_mesh_priv_beacon_cli *cli, + uint16_t addr, + struct bt_mesh_priv_node_id *priv_node_id); +}; + +/** Mesh Private Beacon Client model */ + +struct bt_mesh_priv_beacon_cli +{ + const struct bt_mesh_model *model; + + /* Internal parameters for tracking message responses. */ + + struct bt_mesh_msg_ack_ctx ack_ctx; + + /** Optional callback for Private Beacon Client Status messages. */ + + const struct bt_mesh_priv_beacon_cli_cb *cb; +}; + +/** @brief Set the target's Private Beacon state. + * + * This method can be used asynchronously by setting @p rsp as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param val New Private Beacon value. + * @param rsp If set, returns response status on success. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_priv_beacon_cli_set( + uint16_t net_idx, uint16_t addr, struct bt_mesh_priv_beacon *val, + struct bt_mesh_priv_beacon *rsp); + +/** @brief Get the target's Private Beacon state. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param val Response buffer for Private Beacon value. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_priv_beacon_cli_get(uint16_t net_idx, + uint16_t addr, + struct bt_mesh_priv_beacon *val); + +/** @brief Set the target's Private GATT Proxy state. + * + * This method can be used asynchronously by setting @p rsp as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param val New Private GATT Proxy value. + * @param rsp If set, returns response status on success. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_priv_beacon_cli_gatt_proxy_set( + uint16_t net_idx, uint16_t addr, uint8_t val, uint8_t *rsp); + +/** @brief Get the target's Private GATT Proxy state. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param val Response buffer for Private GATT Proxy value. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_priv_beacon_cli_gatt_proxy_get(uint16_t net_idx, + uint16_t addr, uint8_t *val); + +/** @brief Set the target's Private Node Identity state. + * + * This method can be used asynchronously by setting @p rsp as NULL. This + * way the method will not wait for response and will return immediately + * after sending the command. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param val New Private Node Identity value. + * @param rsp If set, returns response status on success. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_priv_beacon_cli_node_id_set(uint16_t net_idx, uint16_t addr, + struct bt_mesh_priv_node_id *val, + struct bt_mesh_priv_node_id *rsp); + +/** @brief Get the target's Private Node Identity state. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param key_net_idx Network index to get the Private Node Identity state + * of. + * @param val Response buffer for Private Node Identity value. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_priv_beacon_cli_node_id_get( + uint16_t net_idx, uint16_t addr, uint16_t key_net_idx, + struct bt_mesh_priv_node_id *val); + +/** @cond INTERNAL_HIDDEN */ + +extern const struct bt_mesh_model_op bt_mesh_priv_beacon_cli_op[]; +extern const struct bt_mesh_model_cb bt_mesh_priv_beacon_cli_cb; + +/** @endcond */ + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_PRIV_BEACON_CLI_H */ diff --git a/include/nuttx/net/bluetooth/mesh/priv_beacon_srv.h b/include/nuttx/net/bluetooth/mesh/priv_beacon_srv.h new file mode 100644 index 0000000000000..594ae8a5c95b8 --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/priv_beacon_srv.h @@ -0,0 +1,60 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/priv_beacon_srv.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Header file for the Bluetooth Mesh Private Beacon Server model API. + * @ingroup bt_mesh_priv_beacon_srv + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_PRIV_BEACON_SRV_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_PRIV_BEACON_SRV_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @defgroup bt_mesh_priv_beacon_srv Bluetooth Mesh Private Beacon Server + * @ingroup bt_mesh + * @{ + */ + +/** + * + * @brief Private Beacon Server model composition data entry. + */ +#define BT_MESH_MODEL_PRIV_BEACON_SRV \ +BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_PRIV_BEACON_SRV, bt_mesh_priv_beacon_srv_op, NULL, NULL, \ + &bt_mesh_priv_beacon_srv_cb) + +/** @cond INTERNAL_HIDDEN */ + +extern const struct bt_mesh_model_op bt_mesh_priv_beacon_srv_op[]; +extern const struct bt_mesh_model_cb bt_mesh_priv_beacon_srv_cb; + +/** @endcond */ + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_PRIV_BEACON_SRV_H */ diff --git a/include/nuttx/net/bluetooth/mesh/proxy.h b/include/nuttx/net/bluetooth/mesh/proxy.h new file mode 100644 index 0000000000000..cf3a0faa6781e --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/proxy.h @@ -0,0 +1,141 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/proxy.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Proxy APIs. + */ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_PROXY_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_PROXY_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +/** + * @brief Proxy + * @defgroup bt_mesh_proxy Proxy + * @ingroup bt_mesh + * @{ + */ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Callbacks for the Proxy feature. + * + * Should be instantiated with @ref BT_MESH_PROXY_CB_DEFINE. + */ + +struct bt_mesh_proxy_cb +{ + /** @brief Started sending Node Identity beacons on the given subnet. + * + * @param net_idx Network index the Node Identity beacons are running on. + */ + + void (*identity_enabled)(uint16_t net_idx); + /** @brief Stopped sending Node Identity beacons on the given subnet. + * + * @param net_idx Network index the Node Identity beacons were running on. + */ + + void (*identity_disabled)(uint16_t net_idx); +}; + +/** + * @brief Register a callback structure for Proxy events. + * + * Registers a structure with callback functions that gets called on various + * Proxy events. + * + * @param _name Name of callback structure. + */ +#define BT_MESH_PROXY_CB_DEFINE(_name) \ +static const STRUCT_SECTION_ITERABLE(bt_mesh_proxy_cb, _CONCAT(bt_mesh_proxy_cb_, _name)) + +/** @brief Enable advertising with Node Identity. + * + * This API requires that GATT Proxy support has been enabled. Once called + * each subnet will start advertising using Node Identity for the next 60 + * seconds. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_proxy_identity_enable(void); + +/** @brief Enable advertising with Private Node Identity. + * + * This API requires that GATT Proxy support has been enabled. Once called + * each subnet will start advertising using Private Node Identity for the + * next 60 seconds. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_proxy_private_identity_enable(void); + +/** @brief Allow Proxy Client to auto connect to a network. + * + * This API allows a proxy client to auto-connect a given network. + * + * @param net_idx Network Key Index + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_proxy_connect(uint16_t net_idx); + +/** @brief Disallow Proxy Client to auto connect to a network. + * + * This API disallows a proxy client to connect a given network. + * + * @param net_idx Network Key Index + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_proxy_disconnect(uint16_t net_idx); + +/** @brief Schedule advertising of Solicitation PDUs. + * + * Once called, the device will schedule advertising Solicitation PDUs for + * the amount of time defined by @c adv_int * (@c + * CONFIG_BT_MESH_SOL_ADV_XMIT + 1), where @c adv_int is 20ms for + * Bluetooth v5.0 or higher, or 100ms otherwise. + * + * If the number of advertised Solicitation PDUs reached 0xFFFFFF, the + * advertisements will no longer be started until the node is reprovisioned. + * + * @param net_idx Network Key Index + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_proxy_solicit(uint16_t net_idx); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_PROXY_H */ diff --git a/include/nuttx/net/bluetooth/mesh/rpr.h b/include/nuttx/net/bluetooth/mesh/rpr.h new file mode 100644 index 0000000000000..86662f91ebfed --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/rpr.h @@ -0,0 +1,165 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/rpr.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Header file for Bluetooth Mesh Remote Provisioning common + * definitions. + * @ingroup bt_mesh_rpr + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_RPR_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_RPR_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @defgroup bt_mesh_rpr Remote Provisioning models + * @ingroup bt_mesh + * @{ + */ + +/** Unprovisioned device has URI hash value */ +#define BT_MESH_RPR_UNPROV_HASH BIT(0) +#define BT_MESH_RPR_UNPROV_ACTIVE BIT(1) /**< Internal */ +#define BT_MESH_RPR_UNPROV_FOUND BIT(2) /**< Internal */ +#define BT_MESH_RPR_UNPROV_REPORTED BIT(3) /**< Internal */ +#define BT_MESH_RPR_UNPROV_EXT BIT(4) /**< Internal */ +#define BT_MESH_RPR_UNPROV_HAS_LINK BIT(5) /**< Internal */ +#define BT_MESH_RPR_UNPROV_EXT_ADV_RXD BIT(6) /**< Internal */ + +/** Minimum extended scan duration in seconds */ +#define BT_MESH_RPR_EXT_SCAN_TIME_MIN 1 +/** Maximum extended scan duration in seconds */ +#define BT_MESH_RPR_EXT_SCAN_TIME_MAX 21 + +enum bt_mesh_rpr_status +{ + BT_MESH_RPR_SUCCESS, + BT_MESH_RPR_ERR_SCANNING_CANNOT_START, + BT_MESH_RPR_ERR_INVALID_STATE, + BT_MESH_RPR_ERR_LIMITED_RESOURCES, + BT_MESH_RPR_ERR_LINK_CANNOT_OPEN, + BT_MESH_RPR_ERR_LINK_OPEN_FAILED, + BT_MESH_RPR_ERR_LINK_CLOSED_BY_DEVICE, + BT_MESH_RPR_ERR_LINK_CLOSED_BY_SERVER, + BT_MESH_RPR_ERR_LINK_CLOSED_BY_CLIENT, + BT_MESH_RPR_ERR_LINK_CLOSED_AS_CANNOT_RECEIVE_PDU, + BT_MESH_RPR_ERR_LINK_CLOSED_AS_CANNOT_SEND_PDU, + BT_MESH_RPR_ERR_LINK_CLOSED_AS_CANNOT_DELIVER_PDU_REPORT, +}; + +enum bt_mesh_rpr_scan +{ + BT_MESH_RPR_SCAN_IDLE, + BT_MESH_RPR_SCAN_MULTI, + BT_MESH_RPR_SCAN_SINGLE, +}; + +enum bt_mesh_rpr_node_refresh +{ + /** Change the Device key. */ + + BT_MESH_RPR_NODE_REFRESH_DEVKEY, + + /** Change the Device key and address. Device composition may change. */ + + BT_MESH_RPR_NODE_REFRESH_ADDR, + + /** Change the Device key and composition. */ + + BT_MESH_RPR_NODE_REFRESH_COMPOSITION, +}; + +enum bt_mesh_rpr_link_state +{ + BT_MESH_RPR_LINK_IDLE, + BT_MESH_RPR_LINK_OPENING, + BT_MESH_RPR_LINK_ACTIVE, + BT_MESH_RPR_LINK_SENDING, + BT_MESH_RPR_LINK_CLOSING, +}; + +/** Remote provisioning actor, as seen across the mesh. */ + +struct bt_mesh_rpr_node +{ + /** Unicast address of the node. */ + + uint16_t addr; + + /** Network index used when communicating with the node. */ + + uint16_t net_idx; + + /** Time To Live value used when communicating with the node. */ + + uint8_t ttl; +}; + +/** Unprovisioned device. */ + +struct bt_mesh_rpr_unprov +{ + /** Unprovisioned device UUID. */ + + uint8_t uuid[16]; + + /** Flags, see @p BT_MESH_RPR_UNPROV_* flags. */ + + uint8_t flags; + + /** RSSI of unprovisioned device, as received by server. */ + + int8_t rssi; + + /** Out of band information. */ + + bt_mesh_prov_oob_info_t oob; + /** URI hash in unprovisioned beacon. + * + * Only valid if @c flags has @ref BT_MESH_RPR_UNPROV_HASH set. + */ + + uint32_t hash; +}; + +/** Remote Provisioning Link status */ + +struct bt_mesh_rpr_link +{ + /** Link status */ + + enum bt_mesh_rpr_status status; + + /** Link state */ + + enum bt_mesh_rpr_link_state state; +}; + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_RPR_H */ diff --git a/include/nuttx/net/bluetooth/mesh/rpr_cli.h b/include/nuttx/net/bluetooth/mesh/rpr_cli.h new file mode 100644 index 0000000000000..88dc1f2491587 --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/rpr_cli.h @@ -0,0 +1,305 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/rpr_cli.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Header file for the Bluetooth Mesh Remote Provisioning Client model + * API. + * @ingroup bt_mesh_rpr_cli + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_RPR_CLI_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_RPR_CLI_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @defgroup bt_mesh_rpr_cli Remote Provisioning Client model + * @ingroup bt_mesh + * @{ + */ + +/** Special value for the @c max_devs parameter of @ref + * bt_mesh_rpr_scan_start. + * + * Tells the Remote Provisioning Server not to put restrictions on the max + * number of devices reported to the Client. + */ +#define BT_MESH_RPR_SCAN_MAX_DEVS_ANY 0 + +struct bt_mesh_rpr_cli; + +/* @brief Remote Provisioning Client model composition data entry. + * + * @param _cli Pointer to a @ref bt_mesh_rpr_cli instance. + */ +#define BT_MESH_MODEL_RPR_CLI(_cli) \ +BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_REMOTE_PROV_CLI, _bt_mesh_rpr_cli_op, NULL, _cli, \ + &_bt_mesh_rpr_cli_cb) + +/** Scan status response */ + +struct bt_mesh_rpr_scan_status +{ + /** Current scan status */ + + enum bt_mesh_rpr_status status; + + /** Current scan state */ + + enum bt_mesh_rpr_scan scan; + + /** Max number of devices to report in current scan. */ + + uint8_t max_devs; + + /** Seconds remaining of the scan. */ + + uint8_t timeout; +}; + +/** Remote Provisioning Server scanning capabilities */ + +struct bt_mesh_rpr_caps +{ + /** Max number of scannable devices */ + + uint8_t max_devs; + + /** Supports active scan */ + + bool active_scan; +}; + +/** Remote Provisioning Client model instance. */ + +struct bt_mesh_rpr_cli +{ + /** @brief Scan report callback. + * + * @param cli Remote Provisioning Client. + * @param srv Remote Provisioning Server. + * @param unprov Unprovisioned device. + * @param adv_data Advertisement data for the unprovisioned device, or + * NULL if extended scanning hasn't been enabled. An empty + * buffer indicates that the extended scanning finished + * without collecting additional information. + */ + +void (*scan_report)(struct bt_mesh_rpr_cli *cli, + const struct bt_mesh_rpr_node *srv, + struct bt_mesh_rpr_unprov *unprov, + struct net_buf_simple *adv_data); + + /* Internal parameters */ + + struct bt_mesh_msg_ack_ctx scan_ack_ctx; + struct bt_mesh_msg_ack_ctx prov_ack_ctx; + + struct + { + struct k_work_delayable timeout; + struct bt_mesh_rpr_node srv; + uint8_t time; + uint8_t tx_pdu; + uint8_t rx_pdu; + enum bt_mesh_rpr_link_state state; + } link; + + const struct bt_mesh_model *mod; +}; + +/** @brief Get scanning capabilities of Remote Provisioning Server. + * + * @param cli Remote Provisioning Client. + * @param srv Remote Provisioning Server. + * @param caps Capabilities response buffer. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_rpr_scan_caps_get(struct bt_mesh_rpr_cli *cli, + const struct bt_mesh_rpr_node *srv, + struct bt_mesh_rpr_caps *caps); + +/** @brief Get current scanning state of Remote Provisioning Server. + * + * @param cli Remote Provisioning Client. + * @param srv Remote Provisioning Server. + * @param status Scan status response buffer. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_rpr_scan_get(struct bt_mesh_rpr_cli *cli, + const struct bt_mesh_rpr_node *srv, + struct bt_mesh_rpr_scan_status *status); + +/** @brief Start scanning for unprovisioned devices. + * + * Tells the Remote Provisioning Server to start scanning for unprovisioned + * devices. The Server will report back the results through the @ref + * bt_mesh_rpr_cli::scan_report callback. + * + * Use the @c uuid parameter to scan for a specific device, or leave it as + * NULL to report all unprovisioned devices. + * + * The Server will ignore duplicates, and report up to @c max_devs number of + * devices. Requesting a @c max_devs number that's higher than the + * Server's capability will result in an error. + * + * @param cli Remote Provisioning Client. + * @param srv Remote Provisioning Server. + * @param uuid Device UUID to scan for, or NULL to report all devices. + * @param timeout Scan timeout in seconds. Must be at least 1 second. + * @param max_devs Max number of devices to report, or 0 to report as many + * as possible. + * @param status Scan status response buffer. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_rpr_scan_start(struct bt_mesh_rpr_cli *cli, + const struct bt_mesh_rpr_node *srv, + const uint8_t uuid[16], + uint8_t timeout, + uint8_t max_devs, + struct bt_mesh_rpr_scan_status *status); + +/** @brief Start extended scanning for unprovisioned devices. + * + * Extended scanning supplements regular unprovisioned scanning, by allowing + * the Server to report additional data for a specific device. The Remote + * Provisioning Server will use active scanning to request a scan response + * from the unprovisioned device, if supported. If no UUID is provided, the + * Server will report a scan on its own OOB information and advertising + * data. + * + * Use the ad_types array to specify which AD types to include in the scan + * report. Some AD types invoke special behavior: + * - @ref BT_DATA_NAME_COMPLETE Will report both the complete and the + * shortened name. + * - @ref BT_DATA_URI If the unprovisioned beacon contains a URI hash, the + * Server will extend the scanning to include packets other than the scan + * response, to look for URIs matching the URI hash. Only matching URIs will + * be reported. + * + * The following AD types should not be used: + * - @ref BT_DATA_NAME_SHORTENED + * - @ref BT_DATA_UUID16_SOME + * - @ref BT_DATA_UUID32_SOME + * - @ref BT_DATA_UUID128_SOME + * + * Additionally, each AD type should only occur once. + * + * @param cli Remote Provisioning Client. + * @param srv Remote Provisioning Server. + * @param uuid Device UUID to start extended scanning for, or NULL to + * scan the remote server. + * @param timeout Scan timeout in seconds. Valid values from @ref + * BT_MESH_RPR_EXT_SCAN_TIME_MIN to @ref + * BT_MESH_RPR_EXT_SCAN_TIME_MAX. Ignored if UUID is NULL. + * @param ad_types List of AD types to include in the scan report. Must + * contain 1 to CONFIG_BT_MESH_RPR_AD_TYPES_MAX entries. + * @param ad_count Number of AD types in @c ad_types. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_rpr_scan_start_ext(struct bt_mesh_rpr_cli *cli, + const struct bt_mesh_rpr_node *srv, + const uint8_t uuid[16], + uint8_t timeout, + const uint8_t *ad_types, + size_t ad_count); + +/** @brief Stop any ongoing scanning on the Remote Provisioning Server. + * + * @param cli Remote Provisioning Client. + * @param srv Remote Provisioning Server. + * @param status Scan status response buffer. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_rpr_scan_stop(struct bt_mesh_rpr_cli *cli, + const struct bt_mesh_rpr_node *srv, + struct bt_mesh_rpr_scan_status *status); + +/** @brief Get the current link status of the Remote Provisioning Server. + * + * @param cli Remote Provisioning Client. + * @param srv Remote Provisioning Server. + * @param rsp Link status response buffer. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_rpr_link_get(struct bt_mesh_rpr_cli *cli, + const struct bt_mesh_rpr_node *srv, + struct bt_mesh_rpr_link *rsp); + +/** @brief Close any open link on the Remote Provisioning Server. + * + * @param cli Remote Provisioning Client. + * @param srv Remote Provisioning Server. + * @param rsp Link status response buffer. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_rpr_link_close(struct bt_mesh_rpr_cli *cli, + const struct bt_mesh_rpr_node *srv, + struct bt_mesh_rpr_link *rsp); + +/** @brief Get the current transmission timeout value. + * + * @return The configured transmission timeout in milliseconds. + */ + +int32_t bt_mesh_rpr_cli_timeout_get(void); + +/** @brief Set the transmission timeout value. + * + * The transmission timeout controls the amount of time the Remote + * Provisioning Client models will wait for a response from the Server. + * + * @param timeout The new transmission timeout. + */ + +void bt_mesh_rpr_cli_timeout_set(int32_t timeout); + +/** @cond INTERNAL_HIDDEN */ + +extern const struct bt_mesh_model_op _bt_mesh_rpr_cli_op[]; +extern const struct bt_mesh_model_cb _bt_mesh_rpr_cli_cb; + +/** @endcond */ + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_RPR_CLI_H */ diff --git a/include/nuttx/net/bluetooth/mesh/rpr_srv.h b/include/nuttx/net/bluetooth/mesh/rpr_srv.h new file mode 100644 index 0000000000000..986bd2c45694b --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/rpr_srv.h @@ -0,0 +1,63 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/rpr_srv.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Header file for the Bluetooth Mesh Remote Provisioning Server model + * API. + * @ingroup bt_mesh_rpr_srv + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_RPR_SRV_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_RPR_SRV_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @defgroup bt_mesh_rpr_srv Remote provisioning server + * @ingroup bt_mesh + * @{ + */ + +/** + * + * @brief Remote Provisioning Server model composition data entry. + */ +#define BT_MESH_MODEL_RPR_SRV \ +BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_REMOTE_PROV_SRV, _bt_mesh_rpr_srv_op, NULL, NULL, \ + &_bt_mesh_rpr_srv_cb) + +/** @cond INTERNAL_HIDDEN */ + +extern const struct bt_mesh_model_op _bt_mesh_rpr_srv_op[]; +extern const struct bt_mesh_model_cb _bt_mesh_rpr_srv_cb; + +/** @endcond */ + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_RPR_SRV_H */ diff --git a/include/nuttx/net/bluetooth/mesh/sar_cfg.h b/include/nuttx/net/bluetooth/mesh/sar_cfg.h new file mode 100644 index 0000000000000..8bc4177a06551 --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/sar_cfg.h @@ -0,0 +1,104 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/sar_cfg.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Header file for Bluetooth Mesh SAR Configuration common + * definitions. + * @ingroup bt_mesh_sar_cfg + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_SAR_CFG_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_SAR_CFG_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @defgroup bt_mesh_sar_cfg SAR Configuration common header + * @ingroup bt_mesh + * @{ + */ + +/** SAR Transmitter Configuration state structure */ + +struct bt_mesh_sar_tx +{ + /** SAR Segment Interval Step state */ + + uint8_t seg_int_step; + + /** SAR Unicast Retransmissions Count state */ + + uint8_t unicast_retrans_count; + + /** SAR Unicast Retransmissions Without Progress Count state */ + + uint8_t unicast_retrans_without_prog_count; + + /* SAR Unicast Retransmissions Interval Step state */ + + uint8_t unicast_retrans_int_step; + + /** SAR Unicast Retransmissions Interval Increment state */ + + uint8_t unicast_retrans_int_inc; + + /** SAR Multicast Retransmissions Count state */ + + uint8_t multicast_retrans_count; + + /** SAR Multicast Retransmissions Interval state */ + + uint8_t multicast_retrans_int; +}; + +/** SAR Receiver Configuration state structure */ + +struct bt_mesh_sar_rx +{ + /** SAR Segments Threshold state */ + + uint8_t seg_thresh; + + /** SAR Acknowledgment Delay Increment state */ + + uint8_t ack_delay_inc; + + /** SAR Discard Timeout state */ + + uint8_t discard_timeout; + + /** SAR Receiver Segment Interval Step state */ + + uint8_t rx_seg_int_step; + + /** SAR Acknowledgment Retransmissions Count state */ + + uint8_t ack_retrans_count; +}; + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_SAR_CFG_H */ diff --git a/include/nuttx/net/bluetooth/mesh/sar_cfg_cli.h b/include/nuttx/net/bluetooth/mesh/sar_cfg_cli.h new file mode 100644 index 0000000000000..6e6c0210a9784 --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/sar_cfg_cli.h @@ -0,0 +1,150 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/sar_cfg_cli.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** @file + * @brief Bluetooth Mesh SAR Configuration Client Model APIs. + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_SAR_CFG_CLI_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_SAR_CFG_CLI_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/** + * @brief Bluetooth Mesh + * @defgroup bt_mesh_sar_cfg_cli Bluetooth Mesh SAR Configuration Client + * Model + * @ingroup bt_mesh + * @{ + */ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Mesh SAR Configuration Client Model Context */ + +struct bt_mesh_sar_cfg_cli +{ + /** Access model pointer. */ + + const struct bt_mesh_model *model; + + /* Publication structure instance */ + + struct bt_mesh_model_pub pub; + + /* Synchronous message timeout in milliseconds. */ + + int32_t timeout; + + /* Internal parameters for tracking message responses. */ + + struct bt_mesh_msg_ack_ctx ack_ctx; +}; + +/** + * + * @brief SAR Configuration Client model composition data entry. + * + * @param[in] _cli Pointer to a @ref bt_mesh_sar_cfg_cli instance. + */ +#define BT_MESH_MODEL_SAR_CFG_CLI(_cli) \ +BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_SAR_CFG_CLI, _bt_mesh_sar_cfg_cli_op, _cli.pub, _cli, \ + &_bt_mesh_sar_cfg_cli_cb) + +/** @brief Get the SAR Transmitter state of the target node. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param rsp Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_sar_cfg_cli_transmitter_get(uint16_t net_idx, uint16_t addr, + struct bt_mesh_sar_tx *rsp); + +/** @brief Set the SAR Transmitter state of the target node. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param set New SAR Transmitter state to set on the target node. + * @param rsp Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_sar_cfg_cli_transmitter_set(uint16_t net_idx, uint16_t addr, + const struct bt_mesh_sar_tx *set, + struct bt_mesh_sar_tx *rsp); + +/** @brief Get the SAR Receiver state of the target node. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param rsp Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_sar_cfg_cli_receiver_get(uint16_t net_idx, + uint16_t addr, + struct bt_mesh_sar_rx *rsp); + +/** @brief Set the SAR Receiver state of the target node. + * + * @param net_idx Network index to encrypt with. + * @param addr Target node address. + * @param set New SAR Receiver state to set on the target node. + * @param rsp Status response parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_sar_cfg_cli_receiver_set(uint16_t net_idx, uint16_t addr, + const struct bt_mesh_sar_rx *set, + struct bt_mesh_sar_rx *rsp); + +/** @brief Get the current transmission timeout value. + * + * @return The configured transmission timeout in milliseconds. + */ + +int32_t bt_mesh_sar_cfg_cli_timeout_get(void); + +/** @brief Set the transmission timeout value. + * + * @param timeout The new transmission timeout. + */ + +void bt_mesh_sar_cfg_cli_timeout_set(int32_t timeout); + +/** @cond INTERNAL_HIDDEN */ + +extern const struct bt_mesh_model_op _bt_mesh_sar_cfg_cli_op[]; +extern const struct bt_mesh_model_cb _bt_mesh_sar_cfg_cli_cb; + +/** @endcond */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_SAR_CFG_CLI_H */ + +/** @} */ diff --git a/include/nuttx/net/bluetooth/mesh/sar_cfg_srv.h b/include/nuttx/net/bluetooth/mesh/sar_cfg_srv.h new file mode 100644 index 0000000000000..fa0ddca1b9599 --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/sar_cfg_srv.h @@ -0,0 +1,62 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/sar_cfg_srv.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** @file + * @brief Bluetooth Mesh SAR Configuration Server Model APIs. + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_SAR_CFG_SRV_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_SAR_CFG_SRV_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/** + * @brief Bluetooth Mesh + * @defgroup bt_mesh_sar_cfg_srv Bluetooth Mesh SAR Configuration Server + * Model + * @ingroup bt_mesh + * @{ + */ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * + * @brief Transport SAR Configuration Server model composition data entry. + */ +#define BT_MESH_MODEL_SAR_CFG_SRV \ +BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_SAR_CFG_SRV, bt_mesh_sar_cfg_srv_op, NULL, NULL, \ + &bt_mesh_sar_cfg_srv_cb) + +/** @cond INTERNAL_HIDDEN */ + +extern const struct bt_mesh_model_op bt_mesh_sar_cfg_srv_op[]; +extern const struct bt_mesh_model_cb bt_mesh_sar_cfg_srv_cb; + +/** @endcond */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_SAR_CFG_SRV_H */ + +/** + * @} + */ diff --git a/include/nuttx/net/bluetooth/mesh/sol_pdu_rpl_cli.h b/include/nuttx/net/bluetooth/mesh/sol_pdu_rpl_cli.h new file mode 100644 index 0000000000000..41b195289b028 --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/sol_pdu_rpl_cli.h @@ -0,0 +1,141 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/sol_pdu_rpl_cli.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Header file for the Bluetooth Mesh Solicitation PDU RPL Client + * model API. + * @ingroup bt_mesh_sol_pdu_rpl_cli + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_SOL_PDU_RPL_CLI_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_SOL_PDU_RPL_CLI_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @defgroup bt_mesh_sol_pdu_rpl_cli Bluetooth Mesh Solicitation PDU RPL + * Client + * @ingroup bt_mesh + * @{ + */ + +/** Solicitation PDU RPL Client Model Context */ + +struct bt_mesh_sol_pdu_rpl_cli +{ + /** Solicitation PDU RPL model entry pointer. */ + + const struct bt_mesh_model *model; + + /* Internal parameters for tracking message responses. */ + + struct bt_mesh_msg_ack_ctx ack_ctx; + + /** @brief Optional callback for Solicitation PDU RPL Status messages. + * + * Handles received Solicitation PDU RPL Status messages from a + * Solicitation PDU RPL server.The @c start param represents the start of + * range that server has cleared. The @c length param represents length + * of range cleared by server. + * + * @param cli Solicitation PDU RPL client that received the + * status message. + * @param addr Address of the sender. + * @param range_start Range start value. + * @param range_length Range length value. + */ + +void (*srpl_status)(struct bt_mesh_sol_pdu_rpl_cli *cli, + uint16_t addr, + uint16_t range_start, + uint8_t range_length); +}; + +/** + * @brief Solicitation PDU RPL Client model composition data entry. + */ +#define BT_MESH_MODEL_SOL_PDU_RPL_CLI(cli_data) \ +BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_SOL_PDU_RPL_CLI, _bt_mesh_sol_pdu_rpl_cli_op, NULL, cli_data, \ + &_bt_mesh_sol_pdu_rpl_cli_cb) + +/** @brief Remove entries from Solicitation PDU RPL of addresses in given + * range. + * + * This method can be used asynchronously by setting @p start_rsp or @p + * len_rsp as NULL. This way the method will not wait for response and will + * return immediately after sending the command. + * + * To process the response arguments of an async method, register the @c + * srpl_status callback in @c bt_mesh_sol_pdu_rpl_cli struct. + * + * @param ctx Message context for the message. + * @param range_start Start of Unicast address range. + * @param range_len Length of Unicast address range. Valid values are + * 0x00 and 0x02 to 0xff. + * @param start_rsp Range start response buffer. + * @param len_rsp Range length response buffer. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_sol_pdu_rpl_clear(struct bt_mesh_msg_ctx *ctx, + uint16_t range_start, + uint8_t range_len, + uint16_t *start_rsp, + uint8_t *len_rsp); + +/** @brief Remove entries from Solicitation PDU RPL of addresses in given + * range (unacked). + * + * @param ctx Message context for the message. + * @param range_start Start of Unicast address range. + * @param range_len Length of Unicast address range. Valid values are + * 0x00 and 0x02 to 0xff. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_sol_pdu_rpl_clear_unack(struct bt_mesh_msg_ctx *ctx, + uint16_t range_start, + uint8_t range_len); + +/** @brief Set the transmission timeout value. + * + * @param timeout The new transmission timeout in milliseconds. + */ + +void bt_mesh_sol_pdu_rpl_cli_timeout_set(int32_t timeout); + +/** @cond INTERNAL_HIDDEN */ + +extern const struct bt_mesh_model_op _bt_mesh_sol_pdu_rpl_cli_op[]; +extern const struct bt_mesh_model_cb _bt_mesh_sol_pdu_rpl_cli_cb; + +/** @endcond */ + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_SOL_PDU_RPL_CLI_H */ diff --git a/include/nuttx/net/bluetooth/mesh/sol_pdu_rpl_srv.h b/include/nuttx/net/bluetooth/mesh/sol_pdu_rpl_srv.h new file mode 100644 index 0000000000000..4a01ee2c092d4 --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/sol_pdu_rpl_srv.h @@ -0,0 +1,54 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/sol_pdu_rpl_srv.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Header file for the Bluetooth Mesh Solicitation PDU RPL + * Configuration Server model API. + * @ingroup bt_mesh_sol_pdu_rpl_srv + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_SOL_PDU_RPL_SRV_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_SOL_PDU_RPL_SRV_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @defgroup bt_mesh_sol_pdu_rpl_srv Bluetooth Mesh Solicitation PDU RPL + * Server + * @ingroup bt_mesh + * @{ + */ + +/** + * @brief Solicitation PDU RPL Server model composition data entry. + */ +#define BT_MESH_MODEL_SOL_PDU_RPL_SRV \ +BT_MESH_MODEL_CB(BT_MESH_MODEL_ID_SOL_PDU_RPL_SRV, _bt_mesh_sol_pdu_rpl_srv_op, NULL, NULL, \ + &_bt_mesh_sol_pdu_rpl_srv_cb) + +/** @cond INTERNAL_HIDDEN */ + +extern const struct bt_mesh_model_op _bt_mesh_sol_pdu_rpl_srv_op[]; +extern const struct bt_mesh_model_cb _bt_mesh_sol_pdu_rpl_srv_cb; + +/** @endcond */ + +/** @} */ + +#ifdef __cplusplus +} +#endif +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_SOL_PDU_RPL_SRV_H */ diff --git a/include/nuttx/net/bluetooth/mesh/statistic.h b/include/nuttx/net/bluetooth/mesh/statistic.h new file mode 100644 index 0000000000000..028f2ea5caf82 --- /dev/null +++ b/include/nuttx/net/bluetooth/mesh/statistic.h @@ -0,0 +1,189 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/mesh/statistic.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Bluetooth Mesh statistic APIs. + */ + +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_STATISTIC_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_STATISTIC_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/** + * @brief Statistic + * @defgroup bt_mesh_stat Statistic + * @ingroup bt_mesh + * @{ + */ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** The structure that keeps statistics of mesh frames handling. */ + +struct bt_mesh_statistic +{ + /** All received frames passed basic validation and decryption. */ + + /** Received frames over advertiser. */ + + uint32_t rx_adv; + + /** Received frames over loopback. */ + + uint32_t rx_loopback; + + /** Received frames over proxy. */ + + uint32_t rx_proxy; + + /** Received over unknown interface. */ + + uint32_t rx_uknown; + + /** Counter of frames that were initiated to relay over advertiser bearer. + */ + + uint32_t tx_adv_relay_planned; + + /** Counter of frames that succeeded relaying over advertiser bearer. */ + + uint32_t tx_adv_relay_succeeded; + + /** Counter of frames that were initiated to send over advertiser bearer + * locally. + */ + + uint32_t tx_local_planned; + + /** Counter of frames that succeeded to send over advertiser bearer + * locally. + */ + + uint32_t tx_local_succeeded; + + /** Counter of frames that were initiated to send over friend bearer. */ + + uint32_t tx_friend_planned; + + /** Counter of frames that succeeded to send over friend bearer. */ + + uint32_t tx_friend_succeeded; +}; + +/** @brief Get mesh frame handling statistic. + * + * @param st Bluetooth Mesh statistic. + */ + +void bt_mesh_stat_get(struct bt_mesh_statistic *st); + +/** @brief Reset mesh frame handling statistic. + */ + +void bt_mesh_stat_reset(void); + +/** Measured LPN friendship timing derived from timestamps. + * + * Values are computed by timestamping protocol events on the LPN side: + * - T1: Poll TX complete + * - T2: Scanner enabled (ReceiveDelay elapsed) + * - T3: Friend response received + * + * Measured ReceiveDelay = T2 - T1 (actual time before LPN starts + * listening). Measured ReceiveWindow = T3 - T2 (actual listening time until + * response). + */ + +struct bt_mesh_lpn_timing +{ + /** Measured ReceiveDelay for the last poll cycle in microseconds. Time + * from poll TX end to scanner enable. + */ + + uint32_t recv_delay_us; + + /** Minimum measured ReceiveDelay in microseconds. */ + + uint32_t recv_delay_min_us; + + /** Maximum measured ReceiveDelay in microseconds. */ + + uint32_t recv_delay_max_us; + /** Measured ReceiveWindow for the last poll cycle in microseconds. + * Time from scanner enable to scanner disable after Friend response RX. + * Retains the last successful measurement; not updated on timeout. + */ + + uint32_t recv_win_us; + + /** Minimum measured ReceiveWindow in microseconds. */ + + uint32_t recv_win_min_us; + + /** Maximum measured ReceiveWindow in microseconds. */ + + uint32_t recv_win_max_us; + /** Expected ReceiveWindow in microseconds. Time from scanner enable + * to scanner disable when no Friend response arrived (full window). Zero + * if no timeout has occurred yet. + */ + + uint32_t recv_win_expected_us; + + /** Total number of completed poll-response cycles. */ + + uint32_t cnt; + + /** Total number of failed poll-response cycles (no response within + * window). + */ + + uint32_t cnt_failed; +}; + +/** @brief Get measured LPN friendship poll timing. + * + * Returns timestamp-derived measurements of actual ReceiveDelay and + * ReceiveWindow observed on-air. Timestamps are captured using + * TICK2MSEC(clock_systime_ticks()) at poll TX completion, scanner enable, + * and response RX. + * + * @param timing Pointer to structure to populate. + * + * @retval 0 Success. + * @retval -ENOTSUP LPN or statistic feature not enabled. + * @retval -EINVAL NULL pointer. + */ + +int bt_mesh_stat_lpn_timing_get(struct bt_mesh_lpn_timing *timing); + +/** @brief Reset measured LPN friendship poll timing statistics. + */ + +void bt_mesh_stat_lpn_timing_reset(void); + +#ifdef __cplusplus +} +#endif +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_MESH_STATISTIC_H */ diff --git a/include/nuttx/net/bluetooth/sbc.h b/include/nuttx/net/bluetooth/sbc.h new file mode 100644 index 0000000000000..8d2a16e418d8f --- /dev/null +++ b/include/nuttx/net/bluetooth/sbc.h @@ -0,0 +1,209 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/sbc.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright 2024 - 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_SBC_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_SBC_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#if defined(CONFIG_LIBSBC) +#include "sbc_encoder.h" +#include "oi_codec_sbc.h" +#include "oi_status.h" +#endif + +/** @brief SBC channel mode */ + +enum __packed sbc_ch_mode +{ + /** Mono channel */ + + SBC_CH_MODE_MONO, + + /** Dual channel */ + + SBC_CH_MODE_DUAL_CHANNEL, + + /** Stereo channel */ + + SBC_CH_MODE_STEREO, + + /** Joint stereo channel */ + + SBC_CH_MODE_JOINT_STEREO, +}; + +/** @brief SBC allocation method */ + +enum __packed sbc_alloc_mthd +{ + /** Loudness allocation method */ + + SBC_ALLOC_MTHD_LOUDNESS, + + /** SNR allocation method */ + + SBC_ALLOC_MTHD_SNR, +}; + +/** @brief SBC encoder */ + +struct sbc_encoder +{ + /** @cond INTERNAL_HIDDEN */ + + /** Internally used field for encoder */ +#if defined(CONFIG_LIBSBC) + SBC_ENC_PARAMS sbc_encoder_params; +#endif + /** @endcond */ +}; + +/** @brief Encoder initialization parameters */ + +struct sbc_encoder_init_param +{ + /** Bit rate after encoded */ + + uint32_t bit_rate; + + /** Sample frequency */ + + uint32_t samp_freq; + + /** Block length */ + + uint8_t blk_len; + + /** Number of subbands */ + + uint8_t subband; + + /** Allocation method */ + + enum sbc_alloc_mthd alloc_mthd; + + /** Channel mode */ + + enum sbc_ch_mode ch_mode; + + /** Number of channels */ + + uint8_t ch_num; + + /** Minimum bitpool */ + + uint8_t min_bitpool; + + /** Maximum bitpool */ + + uint8_t max_bitpool; +}; + +/** @brief Setup encoder. + * + * @param encoder Handle of the encoder. + * @param param The parameters to initialize encoder @ref + * sbc_encoder_init_param. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int sbc_setup_encoder(struct sbc_encoder *encoder, + struct sbc_encoder_init_param *param); + +/** @brief Encode a frame. + * + * @param encoder Handle of the encoder. + * @param in_data Input PCM samples. + * @param out_data Encoded SBC frame. + * + * @return The encoded size of the frame in bytes. + */ + +uint32_t sbc_encode(struct sbc_encoder *encoder, + const void *in_data, void *out_data); + +/** @brief Return the number of PCM samples in a frame. + * + * @param encoder Handle of the encoder. + * + * @return Number of PCM samples or (negative) error code otherwise. + */ + +int sbc_frame_samples(struct sbc_encoder *encoder); + +/** @brief Return the number of PCM bytes in a frame. + * + * @param encoder Handle of the encoder. + * + * @return Number of PCM bytes or (negative) error code otherwise + */ + +int sbc_frame_bytes(struct sbc_encoder *encoder); + +/** @brief Return the encoded size of one frame. + * + * @param encoder Handle of the encoder. + * + * @return The encoded size of one frame in bytes or (negative) error code + * otherwise. + */ + +int sbc_frame_encoded_bytes(struct sbc_encoder *encoder); + +/** @brief SBC decoder */ + +struct sbc_decoder +{ + /** @cond INTERNAL_HIDDEN */ + + /** Internally used fields for decoder */ +#if defined(CONFIG_LIBSBC) + OI_CODEC_SBC_DECODER_CONTEXT context; + uint32_t context_data[CODEC_DATA_WORDS(2, SBC_CODEC_FAST_FILTER_BUFFERS)]; +#endif + /** @endcond */ +}; + +/** @brief Setup the SBC decoder. + * + * @param decoder Handle of the decoder. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int sbc_setup_decoder(struct sbc_decoder *decoder); + +/** @brief Decode a frame. + * + * @param decoder Handle of the decoder. + * @param in_data Input bitstream, it is increased after decode one frame + * @param in_size Input data size in bytes, it is decreased after decode + * one frame + * @param out_data Output PCM samples + * @param out_size Output data size in bytes + * + * @return Zero on success or (negative) error code otherwise. + */ + +int sbc_decode( + struct sbc_decoder *decoder, const void **in_data, uint32_t *in_size, + void *out_data, uint32_t *out_size); +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_SBC_H */ diff --git a/include/nuttx/net/bluetooth/services/ans.h b/include/nuttx/net/bluetooth/services/ans.h new file mode 100644 index 0000000000000..b0d6930945105 --- /dev/null +++ b/include/nuttx/net/bluetooth/services/ans.h @@ -0,0 +1,160 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/services/ans.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2025 Sean Kyer + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_ANS_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_ANS_H + +/** + * @brief Alert Notification Service (ANS) + * @defgroup bt_ans Alert Notification Service (ANS) + * + * @since 4.4 + * @version 0.1.0 + * + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Command not supported error code + */ +#define BT_ANS_ERR_CMD_NOT_SUP 0xa0 + +/** + * @brief ANS max text string size in octets + * + * This is the max string size in octets to be saved in a New Alert. Text + * longer than the max is truncated. + * + * section 3.165 of + * https://btprodspecificationrefs.blob.core.windows.net/gatt-s + * pecification-supplement/GATT_Specification_Supplement.pdf + * + */ +#define BT_ANS_MAX_TEXT_STR_SIZE 18 + +/** + * @brief ANS Category ID Enum + * + * Enumeration for whether the category is supported. + */ + +enum bt_ans_cat +{ + BT_ANS_CAT_SIMPLE_ALERT, /**< Simple alerts (general notifications). */ + BT_ANS_CAT_EMAIL, /**< Email messages. */ + BT_ANS_CAT_NEWS, /**< News updates. */ + BT_ANS_CAT_CALL, /**< Incoming call alerts. */ + BT_ANS_CAT_MISSED_CALL, /**< Missed call alerts. */ + BT_ANS_CAT_SMS_MMS, /**< SMS/MMS text messages. */ + BT_ANS_CAT_VOICE_MAIL, /**< Voicemail notifications. */ + BT_ANS_CAT_SCHEDULE, /**< Calendar or schedule alerts. */ + BT_ANS_CAT_HIGH_PRI_ALERT, /**< High-priority alerts. */ + BT_ANS_CAT_INSTANT_MESSAGE, /**< Instant messaging alerts. */ + + /** @cond INTERNAL_HIDDEN */ + + BT_ANS_CAT_NUM, /**< Marker for the number of categories. */ + + /** @endcond */ + + /* 10–15 reserved for future use */ +}; + +/** + * @brief Set the support for a given new alert category + * + * @param mask The bitmask of supported categories + * + * @return 0 on success + * @return negative error codes on failure + */ + +int bt_ans_set_new_alert_support_category(uint16_t mask); + +/** + * @brief Set the support for a given unread new alert category + * + * @param mask The bitmask of supported categories + * + * @return 0 on success + * @return negative error codes on failure + */ + +int bt_ans_set_unread_support_category(uint16_t mask); + +/** + * @brief Send a new alert to remote devices + * + * The new alert is transmitted to the remote devices if notifications are + * enabled. Each category will save the latest call to this function in case + * an immediate replay is requested via the ANS control point. + * + * @note This function waits on a Mutex with @ref ((clock_t)-1) to ensure + * atomic updates to notification structs. To avoid deadlocks, do not call + * this function in BT RX or System Workqueue threads. + * + * @param conn The connection object to send the alert to + * @param category The category the notification is for + * @param num_new Number of new alerts since last alert + * @param text Text brief of alert, null terminated + * + * @return 0 on success + * @return negative error codes on failure + */ + +int bt_ans_notify_new_alert( + struct bt_conn *conn, enum bt_ans_cat category, uint8_t num_new, + const char *text); + +/** + * @brief Set the total unread count for a given category + * + * The unread count is transmitted to the remote devices if notifications are + * enabled. Each category will save the latest call to this function in case + * an immediate replay is requested via the ANS control point. + * + * @note This function waits on a Mutex with @ref ((clock_t)-1) to ensure + * atomic updates to notification structs. To avoid deadlocks, do not call + * this function in BT RX or System Workqueue threads. + * + * @param conn The connection object to send the alert to + * @param category The category the unread count is for + * @param unread Total number of unread alerts + * + * @return 0 on success + * @return negative error codes on failure + */ + +int bt_ans_set_unread_count(struct bt_conn *conn, + enum bt_ans_cat category, uint8_t unread); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_ANS_H */ diff --git a/include/nuttx/net/bluetooth/services/bas.h b/include/nuttx/net/bluetooth/services/bas.h new file mode 100644 index 0000000000000..36ceaed806120 --- /dev/null +++ b/include/nuttx/net/bluetooth/services/bas.h @@ -0,0 +1,393 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/services/bas.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2024 Demant A/S + * Copyright (c) 2018 Nordic Semiconductor ASA Copyright (c) 2016 Intel + * Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_BAS_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_BAS_H + +/** + * @brief Battery Service (BAS) + * @defgroup bt_bas Battery Service (BAS) + * @ingroup bluetooth + * @{ + * + * [Experimental] Users should note that the APIs can change as a part of + * ongoing development. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Battery Critical Status Characteristic flags. + * + * Enumeration for the flags indicating the presence of various fields in the + * Battery Critical Status characteristic. + */ + +enum bt_bas_bcs_flags +{ + /** Battery Critical Status Bit 0: Critical Power State */ + + BT_BAS_BCS_BATTERY_CRITICAL_STATE = BIT(0), + + /** Battery Critical Status Bit 1: Immediate Service Required */ + + BT_BAS_BCS_IMMEDIATE_SERVICE_REQUIRED = BIT(1), +}; + +/** + * @brief Battery Level Status Characteristic flags. + * + * Enumeration for the flags indicating the presence of various fields in the + * Battery Level Status characteristic. + */ + +enum bt_bas_bls_flags +{ + /** Bit indicating that the Battery Level Status identifier is present. */ + + BT_BAS_BLS_FLAG_IDENTIFIER_PRESENT = BIT(0), + + /** Bit indicating that the Battery Level is present. */ + + BT_BAS_BLS_FLAG_BATTERY_LEVEL_PRESENT = BIT(1), + + /** Bit indicating that additional status information is present. */ + + BT_BAS_BLS_FLAG_ADDITIONAL_STATUS_PRESENT = BIT(2), +}; + +/** @brief Battery Present Status + * + * Enumeration for the presence of the battery. + */ + +enum bt_bas_bls_battery_present +{ + /** Battery is not present. */ + + BT_BAS_BLS_BATTERY_NOT_PRESENT = 0, + + /** Battery is present. */ + + BT_BAS_BLS_BATTERY_PRESENT = 1 +}; + +/** @brief Wired External Power Source Status + * + * Enumeration for the status of the wired external power source. + */ + +enum bt_bas_bls_wired_power_source +{ + /** Wired external power source is not connected. */ + + BT_BAS_BLS_WIRED_POWER_NOT_CONNECTED = 0, + + /** Wired external power source is connected. */ + + BT_BAS_BLS_WIRED_POWER_CONNECTED = 1, + + /** Wired external power source status is unknown. */ + + BT_BAS_BLS_WIRED_POWER_UNKNOWN = 2 +}; + +/** @brief Wireless External Power Source Status + * + * Enumeration for the status of the wireless external power source. + */ + +enum bt_bas_bls_wireless_power_source +{ + /** Wireless external power source is not connected. */ + + BT_BAS_BLS_WIRELESS_POWER_NOT_CONNECTED = 0, + + /** Wireless external power source is connected. */ + + BT_BAS_BLS_WIRELESS_POWER_CONNECTED = 1, + + /** Wireless external power source status is unknown. */ + + BT_BAS_BLS_WIRELESS_POWER_UNKNOWN = 2 +}; + +/** @brief Battery Charge State + * + * Enumeration for the charge state of the battery. + */ + +enum bt_bas_bls_battery_charge_state +{ + /** Battery charge state is unknown. */ + + BT_BAS_BLS_CHARGE_STATE_UNKNOWN = 0, + + /** Battery is currently charging. */ + + BT_BAS_BLS_CHARGE_STATE_CHARGING = 1, + + /** Battery is discharging actively. */ + + BT_BAS_BLS_CHARGE_STATE_DISCHARGING_ACTIVE = 2, + + /** Battery is discharging but inactive. */ + + BT_BAS_BLS_CHARGE_STATE_DISCHARGING_INACTIVE = 3 +}; + +/** @brief Battery Charge Level + * + * Enumeration for the level of charge in the battery. + */ + +enum bt_bas_bls_battery_charge_level +{ + /** Battery charge level is unknown. */ + + BT_BAS_BLS_CHARGE_LEVEL_UNKNOWN = 0, + + /** Battery charge level is good. */ + + BT_BAS_BLS_CHARGE_LEVEL_GOOD = 1, + + /** Battery charge level is low. */ + + BT_BAS_BLS_CHARGE_LEVEL_LOW = 2, + + /** Battery charge level is critical. */ + + BT_BAS_BLS_CHARGE_LEVEL_CRITICAL = 3 +}; + +/** @brief Battery Charge Type + * + * Enumeration for the type of charging applied to the battery. + */ + +enum bt_bas_bls_battery_charge_type +{ + /** Battery charge type is unknown or not charging. */ + + BT_BAS_BLS_CHARGE_TYPE_UNKNOWN = 0, + + /** Battery is charged using constant current. */ + + BT_BAS_BLS_CHARGE_TYPE_CONSTANT_CURRENT = 1, + + /** Battery is charged using constant voltage. */ + + BT_BAS_BLS_CHARGE_TYPE_CONSTANT_VOLTAGE = 2, + + /** Battery is charged using trickle charge. */ + + BT_BAS_BLS_CHARGE_TYPE_TRICKLE = 3, + + /** Battery is charged using float charge. */ + + BT_BAS_BLS_CHARGE_TYPE_FLOAT = 4 +}; + +/** @brief Charging Fault Reason + * + * Enumeration for the reasons of charging faults. + */ + +enum bt_bas_bls_charging_fault_reason +{ + /** No charging fault. */ + + BT_BAS_BLS_FAULT_REASON_NONE = 0, + + /** Charging fault due to battery issue. */ + + BT_BAS_BLS_FAULT_REASON_BATTERY = BIT(0), + + /** Charging fault due to external power source issue. */ + + BT_BAS_BLS_FAULT_REASON_EXTERNAL_POWER = BIT(1), + + /** Charging fault for other reasons. */ + + BT_BAS_BLS_FAULT_REASON_OTHER = BIT(2) +}; + +/** @brief Service Required Status + * + * Enumeration for whether the service is required. + */ + +enum bt_bas_bls_service_required +{ + /** Service is not required. */ + + BT_BAS_BLS_SERVICE_REQUIRED_FALSE = 0, + + /** Service is required. */ + + BT_BAS_BLS_SERVICE_REQUIRED_TRUE = 1, + + /** Service requirement is unknown. */ + + BT_BAS_BLS_SERVICE_REQUIRED_UNKNOWN = 2 +}; + +/** @brief Battery Fault Status + * + * Enumeration for the fault status of the battery. + */ + +enum bt_bas_bls_battery_fault +{ + /** No battery fault. */ + + BT_BAS_BLS_BATTERY_FAULT_NO = 0, + + /** Battery fault present. */ + + BT_BAS_BLS_BATTERY_FAULT_YES = 1 +}; + +/** @brief Read battery level value. + * + * Read the characteristic value of the battery level + * + * @return The battery level in percent. + */ + +uint8_t bt_bas_get_battery_level(void); + +/** @brief Update battery level value. + * + * Update the characteristic value of the battery level This will send a GATT + * notification to all current subscribers. + * + * @param level The battery level in percent. + * + * @return Zero in case of success and error code in case of error. + */ + +int bt_bas_set_battery_level(uint8_t level); + +/** + * @brief Set the battery present status. + * + * @param present The battery present status to set. + */ + +void bt_bas_bls_set_battery_present(enum bt_bas_bls_battery_present present); + +/** + * @brief Set the wired external power source status. + * + * @param source The wired external power source status to set. + */ + +void bt_bas_bls_set_wired_external_power_source( + enum bt_bas_bls_wired_power_source source); + +/** + * @brief Set the wireless external power source status. + * + * @param source The wireless external power source status to set. + */ + +void bt_bas_bls_set_wireless_external_power_source( + enum bt_bas_bls_wireless_power_source source); + +/** + * @brief Set the battery charge state. + * + * @param state The battery charge state to set. + */ + +void bt_bas_bls_set_battery_charge_state( + enum bt_bas_bls_battery_charge_state state); + +/** + * @brief Set the battery charge level. + * + * @param level The battery charge level to set. + */ + +void bt_bas_bls_set_battery_charge_level( + enum bt_bas_bls_battery_charge_level level); + +/** + * @brief Set the battery charge type. + * + * @param type The battery charge type to set. + */ + +void bt_bas_bls_set_battery_charge_type( + enum bt_bas_bls_battery_charge_type type); + +/** + * @brief Set the charging fault reason. + * + * @param reason The charging fault reason to set. + */ + +void bt_bas_bls_set_charging_fault_reason( + enum bt_bas_bls_charging_fault_reason reason); + +/** + * @brief Set the identifier of the battery. + * + * @kconfig_dep{CONFIG_BT_BAS_BLS_IDENTIFIER_PRESENT} + * + * @param identifier Identifier to set. + */ + +void bt_bas_bls_set_identifier(uint16_t identifier); + +/** + * @brief Set the service required status. + * + * @kconfig_dep{CONFIG_BT_BAS_BLS_ADDITIONAL_STATUS_PRESENT} + * + * @param value Service required status to set. + */ + +void bt_bas_bls_set_service_required(enum bt_bas_bls_service_required value); + +/** + * @brief Set the battery fault status. + * + * @kconfig_dep{CONFIG_BT_BAS_BLS_ADDITIONAL_STATUS_PRESENT} + * + * @param value Battery fault status to set. + */ + +void bt_bas_bls_set_battery_fault(enum bt_bas_bls_battery_fault value); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_BAS_H */ diff --git a/include/nuttx/net/bluetooth/services/cts.h b/include/nuttx/net/bluetooth/services/cts.h new file mode 100644 index 0000000000000..acebace1c265c --- /dev/null +++ b/include/nuttx/net/bluetooth/services/cts.h @@ -0,0 +1,302 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/services/cts.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2024 Croxel Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_CTS_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_CTS_H + +/** + * @brief Current Time Service (CTS) + * @defgroup bt_cts Current Time Service (CTS) + * @ingroup bluetooth + * @{ + * + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief ATT error code for ignored values during CTS write operations + */ +#define BT_CTS_ATT_ERR_VALUES_IGNORED 0x80U + +/** + * @brief Maximum value for the 1/256-second fractions field in CTS time + * format + */ +#define BT_CTS_FRACTION_256_MAX_VALUE 255U + +/** + * @brief Default timezone offset value indicating the timezone is unknown + * (15-minute increments) + */ +#define BT_CTS_TIMEZONE_DEFAULT_VALUE -128 + +/** + * @brief Timezone offset increment in minutes + */ +#define BT_CTS_TIMEZONE_INCREMENT_MIN 15U + +/** + * @brief DST offset increment in minutes + */ +#define BT_CTS_DST_INCREMENT_MIN 15U + +/** + * @brief Minimum valid timezone offset in 15-minute increments (-48 means + * UTC-12:00) + */ +#define BT_CTS_TIMEZONE_MIN -48 + +/** + * @brief Maximum valid timezone offset in 15-minute increments (56 means + * UTC+14:00) + */ +#define BT_CTS_TIMEZONE_MAX 56 + +/** + * @brief Daylight Saving Time (DST) offsets as defined in the specification + */ + +enum bt_cts_dst_offset +{ + /** Standard Time */ + + BT_CTS_DST_OFFSET_STANDARD_TIME = 0, + + /** Half an hour daylight saving time */ + + BT_CTS_DST_OFFSET_HALF_HOUR_DAYLIGHT_TIME = 2, + + /** One hour daylight saving time */ + + BT_CTS_DST_OFFSET_DAYLIGHT_TIME = 4, + + /** Two hour daylight saving time */ + + BT_CTS_DST_OFFSET_DOUBLE_DAYLIGHT_TIME = 8, + + /** Offset unknown */ + + BT_CTS_DST_OFFSET_UNKNOWN = 255, +}; + +/** + * @brief CTS time update reason bits as defined in the specification + */ + +enum bt_cts_update_reason +{ + /* Unknown reason of update no bit is set */ + + BT_CTS_UPDATE_REASON_UNKNOWN = 0, + + /* When time is changed manually e.g. through UI */ + + BT_CTS_UPDATE_REASON_MANUAL = BIT(0), + + /* If time is changed through external reference */ + + BT_CTS_UPDATE_REASON_EXTERNAL_REF = BIT(1), + + /* time changed due to timezone adjust */ + + BT_CTS_UPDATE_REASON_TIME_ZONE_CHANGE = BIT(2), + + /* time changed due to dst offset change */ + + BT_CTS_UPDATE_REASON_DAYLIGHT_SAVING = BIT(3), +}; + +/** + * @brief Current Time service data format, Please refer to + * specifications for more details + */ + +struct bt_cts_time_format +{ + uint16_t year; + uint8_t mon; + uint8_t mday; + uint8_t hours; + uint8_t min; + uint8_t sec; + uint8_t wday; + uint8_t fractions256; + uint8_t reason; +} __packed; + +/** + * @brief CTS Local Time Information + */ + +struct bt_cts_local_time +{ + /* @brief Represents the offset from UTC in number in 15-minute increments. + * + * Valid range from -48 (@ref BT_CTS_TIMEZONE_MIN) to +56 (@ref + * BT_CTS_TIMEZONE_MAX). A value of -128 (@ref + * BT_CTS_TIMEZONE_DEFAULT_VALUE) means that the timezone offset is not + * known. All other values are Reserved for Future Use. The offset defined + * in this characteristic is constant regardless of whether daylight + * savings is in effect. + */ + + int8_t timezone_offset; + /* @brief Represents the daylight saving time offset in 15-minute + * increments. + */ + + enum bt_cts_dst_offset dst_offset; +}; + +/** @brief Current Time Service callback structure */ + +struct bt_cts_cb +{ + /* @brief Current Time Service notifications changed + * + * @param enabled True if notifications are enabled, false if disabled + */ + + void (*notification_changed)(bool enabled); + + /* @brief The Current Time has been requested to update by a peer. + * + * It is the responsibility of the application to store the new time. + * + * @param[in] cts_time Requested time to write + * + * @return 0 if written successfully, negative error code on failure + */ + + int (*cts_time_write)(struct bt_cts_time_format *cts_time); + + /* @brief Callback to get current time. + * + * When current time Read request or notification is triggered, CTS uses + * this callback to retrieve current time information from application. + * Application must implement it and provide cts formatted current time + * information + * + * @param[out] cts_time Current time for the application to be filled + * + * @return 0 if filled successfully, negative error code on failure + */ + + int (*fill_current_cts_time)(struct bt_cts_time_format *cts_time); + + /* @brief The Local Time Information has been requested to update by a + * peer. + * + * It is the responsibility of the application to store the new Local time. + * + * @param[in] cts_local_time Requested local time to write + * + * @return 0 if written successfully, negative error code on failure + * + */ + + int (*cts_local_time_write)( + const struct bt_cts_local_time *cts_local_time); + + /* @brief Request to get current local time + * + * CTS uses this callback to retrieve local time information from + * application. Application must implement it and provide cts local time + * information + * + * @param[out] local_time Current local time struct to be filled + * + * @return 0 if filled successfully, negative error code on failure + */ + + int (*fill_current_cts_local_time)(struct bt_cts_local_time *local_time); +}; + +/** + * @brief This API should be called at application init. + * + * It is safe to call this API before or after bt_enable API + * + * @param cb pointer to required callback + * + * @return 0 on success, negative error code on failure + */ + +int bt_cts_init(const struct bt_cts_cb *cb); + +/** + * @brief Notify all connected clients with CTS enabled + * + * @param reason update reason to be sent to the clients + * + * @return 0 on success, negative error code on failure + */ + +int bt_cts_send_notification(enum bt_cts_update_reason reason); + +/** + * @brief Convert CTS formatted time to milliseconds since epoch + * + * @param[in] ct_time CTS formatted time + * @param[out] unix_ms pointer to store parsed millisecond since epoch + * + * @return 0 on success, negative error code on failure + */ + +int bt_cts_time_to_unix_ms(const struct bt_cts_time_format *ct_time, + int64_t *unix_ms); + +/** + * @brief Convert milliseconds since epoch to CTS formatted time + * + * @param[out] ct_time Pointer to store CTS formatted time + * @param[in] unix_ms milliseconds since epoch to be converted + * + * @return 0 on success, negative error code on failure + */ + +int bt_cts_time_from_unix_ms(struct bt_cts_time_format *ct_time, + int64_t unix_ms); + +/** + * @brief Convert CTS local time to milliseconds + * + * @param[in] local_time Pointer to local time information containing + * timezone and DST offset + * @param[out] relative_ms Pointer to variable containing the result value in + * milliseconds + * + * @return 0 on success, negative error code on failure + */ + +int bt_cts_local_time_to_ms(const struct bt_cts_local_time *local_time, + int32_t *relative_ms); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_CTS_H */ diff --git a/include/nuttx/net/bluetooth/services/dis.h b/include/nuttx/net/bluetooth/services/dis.h new file mode 100644 index 0000000000000..b5860327f51e3 --- /dev/null +++ b/include/nuttx/net/bluetooth/services/dis.h @@ -0,0 +1,30 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/services/dis.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief GATT Device Information Service + */ + +/* Copyright (c) 2018 Nordic Semiconductor ASA Copyright (c) 2016 Intel + * Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_DIS_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_DIS_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_DIS_H */ diff --git a/include/nuttx/net/bluetooth/services/ets.h b/include/nuttx/net/bluetooth/services/ets.h new file mode 100644 index 0000000000000..7f42c06f5a32e --- /dev/null +++ b/include/nuttx/net/bluetooth/services/ets.h @@ -0,0 +1,422 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/services/ets.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2025 Dipak Shetty + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_ETS_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_ETS_H + +/** + * @file + * @brief Elapsed Time Service (ETS) + * @defgroup bt_ets Elapsed Time Service (ETS) + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Elapsed Time data structure as defined in GATT Specification + * Supplement Section 3.82. + */ + +struct bt_ets_elapsed_time +{ + /** Flags field. @see @ref BT_ETS_FLAGS. */ + + uint8_t flags; + /** The actual time value in the resolution as defined by the flags. + * The Time Value field contains a counter of the number of time units as + * determined by the time resolution of the clock. The starting point of + * the timeline is 2000-01-01 00:00:00 when reporting a time of day or is + * implementation-dependent for a tick counter. + */ + + uint8_t time_value[6]; + + /** Time synchronization source type. @see @ref BT_ETS_TIME_SOURCE */ + + uint8_t time_sync_src; + + /** Combined Time Zone Daylight Saving Time offset from UTC in 15-minute + * units (signed). + */ + + int8_t tz_dst_offset; +} __packed; + +/** + * @brief Elapsed Time Service flags field bits (GATT Specification + * Supplement Section 3.82) + * @name Elapsed Time Service Flags + * @anchor BT_ETS_FLAGS + * + * @{ + */ + +/** Time Value reports a counter (tick counter). If 0, Time Value reports a + * time of day. + */ +#define BT_ETS_FLAG_TICK_COUNTER BIT(0) + +/** Time is UTC. If 0, time is local time. Has no meaning for tick counter. */ +#define BT_ETS_FLAG_UTC BIT(1) + +/** Time resolution mask (bits 2-3). */ +#define BT_ETS_FLAG_RESOLUTION_MASK (BIT(2) | BIT(3)) + +/** TZ/DST offset is used. If 0, offset not used. Has no meaning for tick + * counter. + */ +#define BT_ETS_FLAG_TZ_DST_USED BIT(4) + +/** Time stamp is from current timeline. */ +#define BT_ETS_FLAG_CURRENT_TIMELINE BIT(5) + +/** Reserved bits (6-7). */ +#define BT_ETS_FLAG_RESERVED_MASK (BIT(6) | BIT(7)) + +/** @} */ + +/** + * @brief Time resolution values for bits 2-3 of flags field. + */ + +enum bt_ets_time_resolution +{ + /** 1 second resolution. */ + + BT_ETS_RESOLUTION_1_SEC = 0, + + /** 100 milliseconds resolution. */ + + BT_ETS_RESOLUTION_100_MS = 1, + + /** 1 millisecond resolution. */ + + BT_ETS_RESOLUTION_1_MS = 2, + + /** 100 microseconds resolution. */ + + BT_ETS_RESOLUTION_100_US = 3, +}; + +/** + * @brief Time synchronization source values (GATT Specification Supplement + * Section 3.243) + * @name Time Source Values + * @anchor BT_ETS_TIME_SOURCE + * + * @{ + */ + +/** Unknown time source. */ +#define BT_ETS_TIME_SOURCE_UNKNOWN 0 +/** Network Time Protocol. */ +#define BT_ETS_TIME_SOURCE_NTP 1 +/** GPS/GNSS. */ +#define BT_ETS_TIME_SOURCE_GPS 2 +/** Radio Time Signal. */ +#define BT_ETS_TIME_SOURCE_RADIO 3 +/** Manually set. */ +#define BT_ETS_TIME_SOURCE_MANUAL 4 +/** Atomic Clock. */ +#define BT_ETS_TIME_SOURCE_ATOMIC 5 +/** Cellular Network. */ +#define BT_ETS_TIME_SOURCE_CELLULAR 6 +/** Not synchronized. */ +#define BT_ETS_TIME_SOURCE_NOT_SYNCHRONIZED 7 + +/** @} */ + +/** + * @brief Write result codes for write_elapsed_time callback. + * + * These codes indicate the result of a time write operation and map to + * specific ATT error responses as defined in the ETS specification. + */ + +enum bt_ets_write_result +{ + /** Write accepted successfully. */ + + BT_ETS_WRITE_SUCCESS = 0, + + /** Time source quality is too low. Rejects write with ATT error 0x80. */ + + BT_ETS_WRITE_TIME_SOURCE_TOO_LOW = 1, + + /** Time value is out of acceptable range. Rejects write with ATT error + * 0xFF. + */ + + BT_ETS_WRITE_OUT_OF_RANGE = 2, + + /** Incorrect time format (flags mismatch). Rejects write with ATT error + * 0x81. + */ + + BT_ETS_WRITE_INCORRECT_FORMAT = 3, +}; + +/** Combined supported flags mask based on configuration. */ +#define BT_ETS_SUPPORTED_FLAGS_MASK \ +((IS_ENABLED(CONFIG_BT_ETS_SUPPORT_TICK_COUNTER) ? BT_ETS_FLAG_TICK_COUNTER : 0) | \ + (IS_ENABLED(CONFIG_BT_ETS_SUPPORT_UTC) ? BT_ETS_FLAG_UTC : 0) | \ + (IS_ENABLED(CONFIG_BT_ETS_SUPPORT_TZ_DST) ? BT_ETS_FLAG_TZ_DST_USED : 0) | \ + (IS_ENABLED(CONFIG_BT_ETS_RESOLUTION_1_SEC) ? 0 \ + : IS_ENABLED(CONFIG_BT_ETS_RESOLUTION_100_MS) ? BIT(2) \ + : IS_ENABLED(CONFIG_BT_ETS_RESOLUTION_1_MS) ? BIT(3) \ + : IS_ENABLED(CONFIG_BT_ETS_RESOLUTION_100_US) ? (BIT(2) | BIT(3)) \ + : 0) | \ + BT_ETS_FLAG_CURRENT_TIMELINE) + +/** + * @brief Clock Status flags (Elapsed Time Service - Section 3.1.1.2) + * @name Clock Status Flags + * @anchor BT_ETS_CLOCK_STATUS + * + * @{ + */ + +/** Clock needs to be set. If 0, clock is set. */ +#define BT_ETS_CLOCK_STATUS_NEEDS_SET BIT(0) +/** Clock Status RFU mask (bits 1-7). */ +#define BT_ETS_CLOCK_STATUS_RFU_MASK 0xFE + +/** @} */ + +/** + * @name Clock Capabilities flags (Elapsed Time Service - Section 3.1.1.3) + * @anchor BT_ETS_CLOCK_CAPABILITIES + * + * @{ + */ + +/** Clock autonomously applies DST rules. */ +#define BT_ETS_CLOCK_CAP_DST_AUTO BIT(0) +/** Clock autonomously manages TZ changes. */ +#define BT_ETS_CLOCK_CAP_TZ_AUTO BIT(1) +/** Clock Capabilities RFU mask (bits 2-7). */ +#define BT_ETS_CLOCK_CAP_RFU_MASK 0xFC + +/** @} */ + +/** + * @brief Clock capabilities (Elapsed Time Service - Section 3.1.1.3) + */ +#define BT_ETS_CLOCK_CAPABILITIES_VALUE \ +((IS_ENABLED(CONFIG_BT_ETS_CLOCK_CAP_DST_AUTO) ? BT_ETS_CLOCK_CAP_DST_AUTO : 0) | \ + (IS_ENABLED(CONFIG_BT_ETS_CLOCK_CAP_TZ_AUTO) ? BT_ETS_CLOCK_CAP_TZ_AUTO : 0)) + +/** + * @brief Elapsed Time Service callback structure. + */ + +struct bt_ets_cb +{ + /** + * @brief Read elapsed time callback (MANDATORY). + * + * Called when a BLE client reads the Current Elapsed Time characteristic. + * Application must fill the elapsed time structure with current time + * information. + * + * @param time Elapsed time structure to fill. + * + * @retval 0 On success. + * @retval -errno Negative errno on failure. + */ + + int (*read_elapsed_time)(struct bt_ets_elapsed_time *time); + + /** + * @brief Read clock status (@ref BT_ETS_CLOCK_STATUS) callback + * (MANDATORY). + * + * Called when a BLE client reads the Current Elapsed Time characteristic. + * Application must return the current clock status flags. + * + * @param status Pointer to store clock status byte. + * + * @retval 0 On success. + * @retval -errno Negative errno on failure. + */ + + int (*read_clock_status)(uint8_t *status); + + /** + * @brief Write elapsed time callback (OPTIONAL). + * + * Called when a BLE client writes to the Current Elapsed Time + * characteristic. Application should validate time source quality, range, + * and other constraints. + * @kconfig_dep{CONFIG_BT_ETS_CURRENT_ELAPSED_TIME_WRITABLE} + * + * @note Clock Status and Clock Capabilities are excluded from writes per + * spec. + * + * @param time Elapsed time structure written by client. + * + * @return Result code indicating acceptance or specific rejection reason. + */ + + enum bt_ets_write_result (*write_elapsed_time)( + const struct bt_ets_elapsed_time *time); + + /** + * @brief Indication subscription changed callback (OPTIONAL). + * + * Called when a client enables or disables indications for the Current + * Elapsed Time characteristic. + * + * @param enabled True if indications enabled, false if disabled. + */ + + void (*indication_changed)(bool enabled); +}; + +/** + * @brief Initialize Elapsed Time Service. + * + * Must be called during initialization. This can be called before or after + * bt_enable, but before advertising. + * + * @param cb Callback structure with mandatory callbacks set. + * + * @retval 0 On success. + * @retval -EINVAL If @p cb is NULL or mandatory callbacks are NULL. + */ + +int bt_ets_init(const struct bt_ets_cb *cb); + +/** + * @brief Send indication to subscribed clients. + * + * Send indication to all subscribed clients with updated elapsed time. This + * should be called when the time changes other than by natural progression + * (e.g., manual adjustment, external time sync, DST change, timezone + * change). + * + * @param elapsed_time Elapsed time to indicate. + * @param clock_status Current clock status flags (@ref BT_ETS_CLOCK_STATUS). + * + * @retval 0 On success. + * @retval -ENOTCONN If no clients are connected or subscribed. + * @retval -errno Other negative errno on failure. + */ + +int bt_ets_indicate(const struct bt_ets_elapsed_time *elapsed_time, + uint8_t clock_status); + +/** + * @brief Decode ETS formatted time into milliseconds since Unix epoch. + * + * Converts ETS time format to milliseconds with Unix epoch (1970-01-01 + * 00:00:00 UTC). Handles epoch conversion (ETS uses 2000-01-01 vs Unix + * 1970-01-01), resolution scaling, and timezone conversion based on the + * flags field in @p et_time. + * + * The output @p unix_ms is always in UTC. Timezone handling based on flags: + * - @kconfig{CONFIG_BT_ETS_SUPPORT_UTC}: Time is already UTC, no conversion + * needed. If @ref BT_ETS_FLAG_TZ_DST_USED is set, the offset is + * informational only. + * - @kconfig{CONFIG_BT_ETS_SUPPORT_LOCAL_TIME} with + * @kconfig{CONFIG_BT_ETS_SUPPORT_TZ_DST}: If @ref BT_ETS_FLAG_TZ_DST_USED is + * set in @p et_time, converts local time to UTC by subtracting the offset: + * UTC = Local time - TZ/DST Offset Ɨ 15 minutes + * + * @kconfig_dep{CONFIG_BT_ETS_HELPER_API} + * @note This requires either @kconfig{CONFIG_BT_ETS_SUPPORT_UTC} or + * @kconfig{CONFIG_BT_ETS_SUPPORT_TZ_DST} to be enabled (proper UTC + * conversion needs either UTC mode or timezone offset information). + * + * @param et_time ETS time formatted structure to decode. + * @param unix_ms Pointer to store milliseconds since Unix epoch (1970-01-01 + * 00:00:00 UTC). + * + * @retval 0 On success. + * @retval -EINVAL If @p et_time or @p unix_ms is NULL. + * @retval -EOVERFLOW If the converted time value overflows during + * conversion. + */ + +int bt_ets_time_to_unix_ms(const struct bt_ets_elapsed_time *et_time, + int64_t *unix_ms); + +/** + * @brief Encode Unix milliseconds into ETS formatted time. + * + * Converts a Unix timestamp (1970-01-01 00:00:00 UTC) to ETS time format. + * Handles epoch conversion (Unix 1970-01-01 to ETS 2000-01-01), resolution + * scaling, and timezone conversion based on compile-time configuration. + * + * The @p unix_ms input is always in UTC. The @p tz_dst_offset parameter + * behavior: + * - @kconfig{CONFIG_BT_ETS_SUPPORT_UTC}: Time stays as UTC, no conversion. + * If @kconfig{CONFIG_BT_ETS_SUPPORT_TZ_DST} is enabled, offset is stored + * for client use. + * - @kconfig{CONFIG_BT_ETS_SUPPORT_LOCAL_TIME} with + * @kconfig{CONFIG_BT_ETS_SUPPORT_TZ_DST}: Offset is applied to convert UTC + * to local time and stored in output. Formula: Local time = UTC time + + * TZ/DST Offset Ɨ 15 minutes + * @ref BT_ETS_FLAG_TZ_DST_USED flag is set in output. + * + * The output @p et_time flags are set based on compile-time configuration: + * - Time mode: @kconfig{CONFIG_BT_ETS_SUPPORT_UTC} or + * @kconfig{CONFIG_BT_ETS_SUPPORT_LOCAL_TIME} + * - Resolution: CONFIG_BT_ETS_RESOLUTION_* (1_SEC, 100_MS, 1_MS, or 100_US) + * - @ref BT_ETS_FLAG_CURRENT_TIMELINE is always set + * + * @kconfig_dep{CONFIG_BT_ETS_HELPER_API} + * @note This requires either @kconfig{CONFIG_BT_ETS_SUPPORT_UTC} or + * @kconfig{CONFIG_BT_ETS_SUPPORT_TZ_DST} to be enabled. + * + * @param et_time Pointer to ETS time structure to fill. + * @param unix_ms Milliseconds since Unix epoch (1970-01-01 00:00:00 + * UTC) - always UTC. + * @param time_src Time synchronization source type. (see @ref + * BT_ETS_TIME_SOURCE) + * @param tz_dst_offset TZ/DST offset from UTC in 15-minute units (signed). + * Used for UTC-to-local conversion if in LOCAL_TIME + * mode. + * + * @retval 0 On success. + * @retval -EINVAL If @p et_time is NULL or time is before ETS epoch + * (2000-01-01) after conversion. + * @retval -EOVERFLOW If the time value exceeds 48-bit range after + * conversion. + */ + +int bt_ets_time_from_unix_ms( + struct bt_ets_elapsed_time *et_time, int64_t unix_ms, uint8_t time_src, + int8_t tz_dst_offset); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_ETS_H */ diff --git a/include/nuttx/net/bluetooth/services/hrs.h b/include/nuttx/net/bluetooth/services/hrs.h new file mode 100644 index 0000000000000..1d425fe4c8cb9 --- /dev/null +++ b/include/nuttx/net/bluetooth/services/hrs.h @@ -0,0 +1,124 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/services/hrs.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_HRS_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_HRS_H + +/** + * @brief Heart Rate Service (HRS) + * @defgroup bt_hrs Heart Rate Service (HRS) + * @ingroup bluetooth + * @{ + * + * [Experimental] Users should note that the APIs can change as a part of + * ongoing development. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * @brief Server shall restart the accumulation of energy expended from zero + */ +#define BT_HRS_CONTROL_POINT_RESET_ENERGY_EXPANDED_REQ 0x01 + +/** @brief Heart rate service callback structure */ + +struct bt_hrs_cb +{ + /** @brief Heart rate notifications changed + * + * @param enabled Flag that is true if notifications were enabled, false if + * they were disabled. + */ + + void (*ntf_changed)(bool enabled); + + /* @brief Heart rate control point write callback + * + * @note if Server supports the Energy Expended feature then application + * shall implement and support @ref + * BT_HRS_CONTROL_POINT_RESET_ENERGY_EXPANDED_REQ request code + * + * @param request control point request code + * + * @return 0 on successful handling of control point request + * @return -ENOTSUP if not supported. It can be used to pass handling to + * other listeners in case of multiple listeners + * @return other negative error codes will result in immediate error + * response + */ + + int (*ctrl_point_write)(uint8_t request); + + /** Internal member to form a list of callbacks */ + + struct list_node _node; +}; + +/** @brief Heart rate service callback register + * + * This function will register callbacks that will be called in certain + * events related to Heart rate service. + * + * @param cb Pointer to callbacks structure. Must point to memory that + * remains valid until unregistered. + * + * @return 0 on success + * @return -EINVAL in case @p cb is NULL + */ + +int bt_hrs_cb_register(struct bt_hrs_cb *cb); + +/** @brief Heart rate service callback unregister + * + * This function will unregister callback from Heart rate service. + * + * @param cb Pointer to callbacks structure + * + * @return 0 on success + * @return -EINVAL in case @p cb is NULL + * @return -ENOENT in case the @p cb was not found in registered callbacks + */ + +int bt_hrs_cb_unregister(struct bt_hrs_cb *cb); + +/** @brief Notify heart rate measurement. + * + * This will send a GATT notification to all current subscribers. + * + * @param heartrate The heartrate measurement in beats per minute. + * + * @return Zero in case of success and error code in case of error. + */ + +int bt_hrs_notify(uint16_t heartrate); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_HRS_H */ diff --git a/include/nuttx/net/bluetooth/services/ias.h b/include/nuttx/net/bluetooth/services/ias.h new file mode 100644 index 0000000000000..685627d04da90 --- /dev/null +++ b/include/nuttx/net/bluetooth/services/ias.h @@ -0,0 +1,148 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/services/ias.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Codecoup + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_IAS_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_IAS_H + +/** + * @brief Immediate Alert Service (IAS) + * @defgroup bt_ias Immediate Alert Service (IAS) + * @ingroup bluetooth + * @{ + * + * [Experimental] Users should note that the APIs can change as a part of + * ongoing development. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +enum bt_ias_alert_lvl +{ + /** No alerting should be done on device */ + + BT_IAS_ALERT_LVL_NO_ALERT, + + /** Device shall alert */ + + BT_IAS_ALERT_LVL_MILD_ALERT, + + /** Device should alert in strongest possible way */ + + BT_IAS_ALERT_LVL_HIGH_ALERT, +}; + +/** @brief Immediate Alert Service callback structure. */ + +struct bt_ias_cb +{ + /** + * @brief Callback function to stop alert. + * + * This callback is called when peer commands to disable alert. + */ + + void (*no_alert)(void); + + /** + * @brief Callback function for alert level value. + * + * This callback is called when peer commands to alert. + */ + + void (*mild_alert)(void); + + /** + * @brief Callback function for alert level value. + * + * This callback is called when peer commands to alert in the strongest + * possible way. + */ + + void (*high_alert)(void); +}; + +/** @brief Method for stopping alert locally + * + * @return Zero in case of success and error code in case of error. + */ + +int bt_ias_local_alert_stop(void); + +/** + * @brief Register a callback structure for immediate alert events. + * + * @param _name Name of callback structure. + */ +#define BT_IAS_CB_DEFINE(_name) \ +static const STRUCT_SECTION_ITERABLE(bt_ias_cb, _CONCAT(bt_ias_cb_, _name)) + +struct bt_ias_client_cb +{ + /** @brief Callback function for bt_ias_discover. + * + * This callback is called when discovery procedure is complete. + * + * @param conn Bluetooth connection object. + * @param err 0 on success, ATT error or negative errno otherwise + */ + + void (*discover)(struct bt_conn *conn, int err); +}; + +/** @brief Set alert level + * + * @param conn Bluetooth connection object + * @param level Level of alert to write + * + * @return Zero in case of success and error code in case of error. + */ + +int bt_ias_client_alert_write(struct bt_conn *conn, + enum bt_ias_alert_lvl level); + +/** @brief Discover Immediate Alert Service + * + * @param conn Bluetooth connection object + * + * @return Zero in case of success and error code in case of error. + */ + +int bt_ias_discover(struct bt_conn *conn); + +/** @brief Register Immediate Alert Client callbacks + * + * @param cb The callback structure + * + * @return Zero in case of success and error code in case of error. + */ + +int bt_ias_client_cb_register(const struct bt_ias_client_cb *cb); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_IAS_H */ diff --git a/include/nuttx/net/bluetooth/services/nus.h b/include/nuttx/net/bluetooth/services/nus.h new file mode 100644 index 0000000000000..6c480a8b3150a --- /dev/null +++ b/include/nuttx/net/bluetooth/services/nus.h @@ -0,0 +1,160 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/services/nus.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2024 Croxel, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_NUS_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_NUS_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** @brief UUIDs of Nordic UART GATT Service. + * Service: 6e400001-b5a3-f393-e0a9-e50e24dcca9e RX Char: + * 6e400002-b5a3-f393-e0a9-e50e24dcca9e TX Char: + * 6e400003-b5a3-f393-e0a9-e50e24dcca9e + */ +#define BT_UUID_NUS_SRV_VAL BT_UUID_128_ENCODE(0x6e400001, 0xb5a3, 0xf393, 0xe0a9, 0xe50e24dcca9e) +#define BT_UUID_NUS_RX_CHAR_VAL \ +BT_UUID_128_ENCODE(0x6e400002, 0xb5a3, 0xf393, 0xe0a9, 0xe50e24dcca9e) +#define BT_UUID_NUS_TX_CHAR_VAL \ +BT_UUID_128_ENCODE(0x6e400003, 0xb5a3, 0xf393, 0xe0a9, 0xe50e24dcca9e) + +/** @brief Macro to define instance of NUS Service. It allows users to + * define multiple NUS instances, analogous to Serial endpoints, and use + * each + * one for different purposes. A default NUS instance may be defined through + * Kconfig. + */ +#define BT_NUS_INST_DEFINE(_name) Z_INTERNAL_BT_NUS_INST_DEFINE(_name) + +/** @brief Callbacks for getting notified on NUS Service occurrences */ + +struct bt_nus_cb +{ + /** @brief Notifications subscription changed + * + * @param enabled Flag that is true if notifications were enabled, false if + * they were disabled. + * @param ctx User context provided in the callback structure. + */ + + void (*notif_enabled)(bool enabled, void *ctx); + + /** @brief Received Data + * + * @param conn Peer Connection object. + * @param data Pointer to buffer with data received. + * @param len Size in bytes of data received. + * @param ctx User context provided in the callback structure. + */ + + void (*received)(struct bt_conn *conn, const void *data, uint16_t len, + void *ctx); + + /** Internal member. Provided as a callback argument for user context */ + + void *ctx; + + /** Internal member to form a list of callbacks */ + + struct list_node _node; +}; + +/** @brief NUS server Instance callback register + * + * This function registers callbacks that will be called in certain events + * related to NUS. + * + * @param inst Pointer to instance of NUS service. NULL if using default + * instance. + * @param cb Pointer to callbacks structure. Must be valid throughout the + * lifetime of the application. + * @param ctx User context to be provided through the callback. + * + * @return 0 on success + * @return -EINVAL in case @p cb is NULL + */ + +int bt_nus_inst_cb_register(struct bt_nus_inst *inst, + struct bt_nus_cb *cb, void *ctx); + +/** @brief Send Data to NUS Instance + * + * @note This API sends the data to the specified peer. + * + * @param conn Connection object to send data to. NULL if notifying all + * peers. + * @param inst Pointer to instance of NUS service. NULL if using default + * instance. + * @param data Pointer to buffer with bytes to send. + * @param len Length in bytes of data to send. + * + * @return 0 on success, negative error code if failed. + * @return -EAGAIN when Bluetooth stack has not been enabled. + * @return -ENOTCONN when either no connection has been established or no + * peers have subscribed. + */ + +int bt_nus_inst_send( + struct bt_conn *conn, struct bt_nus_inst *inst, const void *data, + uint16_t len); + +/** @brief NUS server callback register + * + * @param cb Pointer to callbacks structure. Must be valid throughout the + * lifetime of the application. + * @param ctx User context to be provided through the callback. + * + * @return 0 on success, negative error code if failed. + * @return -EINVAL in case @p cb is NULL + */ + +static inline int bt_nus_cb_register(struct bt_nus_cb *cb, void *ctx) +{ + return bt_nus_inst_cb_register(NULL, cb, ctx); +} + +/** @brief Send Data over NUS + * + * @note This API sends the data to the specified peer. + * + * @param conn Connection object to send data to. NULL if notifying all + * peers. + * @param data Pointer to buffer with bytes to send. + * @param len Length in bytes of data to send. + * + * @return 0 on success, negative error code if failed. + * @return -EAGAIN when Bluetooth stack has not been enabled. + * @return -ENOTCONN when either no connection has been established or no + * peers have subscribed. + */ + +static inline int bt_nus_send(struct bt_conn *conn, + const void *data, uint16_t len) +{ + return bt_nus_inst_send(conn, NULL, data, len); +} + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_NUS_H */ diff --git a/include/nuttx/net/bluetooth/services/nus/inst.h b/include/nuttx/net/bluetooth/services/nus/inst.h new file mode 100644 index 0000000000000..33556d3a1996f --- /dev/null +++ b/include/nuttx/net/bluetooth/services/nus/inst.h @@ -0,0 +1,76 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/services/nus/inst.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2024 Croxel, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_NUS_INST_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_NUS_INST_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct bt_nus_inst +{ + /** Pointer to the NUS Service Instance */ + + const struct bt_gatt_service_static *svc; + + /** List of subscribers to invoke callbacks on asynchronous events */ + + struct list_node *cbs; +}; + +#define BT_UUID_NUS_SERVICE BT_UUID_DECLARE_128(BT_UUID_NUS_SRV_VAL) +#define BT_UUID_NUS_TX_CHAR BT_UUID_DECLARE_128(BT_UUID_NUS_TX_CHAR_VAL) +#define BT_UUID_NUS_RX_CHAR BT_UUID_DECLARE_128(BT_UUID_NUS_RX_CHAR_VAL) + +/** Required as the service may be instantiated outside of the module */ + +ssize_t nus_bt_chr_write(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, + uint16_t len, + uint16_t offset, + uint8_t flags); +void nus_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value); + +#define Z_INTERNAL_BT_NUS_INST_DEFINE(_name) \ + \ +BT_GATT_SERVICE_DEFINE( \ + _name##_svc, BT_GATT_PRIMARY_SERVICE(BT_UUID_NUS_SERVICE), \ + BT_GATT_CHARACTERISTIC(BT_UUID_NUS_TX_CHAR, BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_NONE, \ + NULL, NULL, NULL), \ + BT_GATT_CCC(nus_ccc_cfg_changed, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), \ + BT_GATT_CHARACTERISTIC(BT_UUID_NUS_RX_CHAR, \ + BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP, \ + BT_GATT_PERM_WRITE, NULL, nus_bt_chr_write, NULL), ); \ + \ +struct list_node _name##_cbs = LIST_INITIAL_VALUE(_name##_cbs); \ + \ +STRUCT_SECTION_ITERABLE(bt_nus_inst, _name) = { \ + .svc = &_name##_svc, \ + .cbs = &_name##_cbs, \ +} + +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_NUS_INST_H */ diff --git a/include/nuttx/net/bluetooth/services/ots.h b/include/nuttx/net/bluetooth/services/ots.h new file mode 100644 index 0000000000000..103b62bc72fb8 --- /dev/null +++ b/include/nuttx/net/bluetooth/services/ots.h @@ -0,0 +1,1304 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/services/ots.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020-2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_OTS_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_OTS_H + +/** + * @brief Object Transfer Service (OTS) + * @defgroup bt_ots Object Transfer Service (OTS) + * @since 2.4 + * @version 0.1.0 + * @ingroup bluetooth + * @{ + * + * [Experimental] Users should note that the APIs can change as a part of + * ongoing development. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** @brief Size of OTS object ID (in bytes). */ +#define BT_OTS_OBJ_ID_SIZE 6 + +/** @brief Minimum allowed value for object ID (except ID for directory + * listing) + */ +#define BT_OTS_OBJ_ID_MIN 0x000000000100ull + +/** @brief Maximum allowed value for object ID (except ID for directory + * listing) + */ +#define BT_OTS_OBJ_ID_MAX 0xffffffffffffull + +/** @brief ID of the Directory Listing Object */ +#define OTS_OBJ_ID_DIR_LIST 0x000000000000ull + +/** @brief Mask for OTS object IDs, preserving the 48 bits */ +#define BT_OTS_OBJ_ID_MASK BIT64_MASK(48) + +/** @brief Length of OTS object ID string (in bytes). */ +#define BT_OTS_OBJ_ID_STR_LEN 15 + +/** @brief Type of an OTS object. */ + +struct bt_ots_obj_type +{ + union + { + /* Used to indicate UUID type */ + + struct bt_uuid uuid; + + /* 16-bit UUID value */ + + struct bt_uuid_16 uuid_16; + + /* 128-bit UUID value */ + + struct bt_uuid_128 uuid_128; + }; +}; + +/** @brief Properties of an OTS object. */ + +enum +{ + /** Bit 0 Deletion of this object is permitted */ + + BT_OTS_OBJ_PROP_DELETE = 0, + + /** Bit 1 Execution of this object is permitted */ + + BT_OTS_OBJ_PROP_EXECUTE = 1, + + /** Bit 2 Reading this object is permitted */ + + BT_OTS_OBJ_PROP_READ = 2, + + /** Bit 3 Writing data to this object is permitted */ + + BT_OTS_OBJ_PROP_WRITE = 3, + + /** @brief Bit 4 Appending data to this object is permitted. + * + * Appending data increases its Allocated Size. + */ + + BT_OTS_OBJ_PROP_APPEND = 4, + + /** Bit 5 Truncation of this object is permitted */ + + BT_OTS_OBJ_PROP_TRUNCATE = 5, + + /** @brief Bit 6 Patching this object is permitted + * + * Patching this object overwrites some of the object's existing contents. + */ + + BT_OTS_OBJ_PROP_PATCH = 6, + + /** Bit 7 This object is a marked object */ + + BT_OTS_OBJ_PROP_MARKED = 7, +}; + +/** @brief Set @ref BT_OTS_OBJ_PROP_DELETE property. + * + * @param prop Object properties. + */ +#define BT_OTS_OBJ_SET_PROP_DELETE(prop) WRITE_BIT(prop, BT_OTS_OBJ_PROP_DELETE, 1) + +/** @brief Set @ref BT_OTS_OBJ_PROP_EXECUTE property. + * + * @param prop Object properties. + */ +#define BT_OTS_OBJ_SET_PROP_EXECUTE(prop) WRITE_BIT(prop, BT_OTS_OBJ_PROP_EXECUTE, 1) + +/** @brief Set @ref BT_OTS_OBJ_PROP_READ property. + * + * @param prop Object properties. + */ +#define BT_OTS_OBJ_SET_PROP_READ(prop) WRITE_BIT(prop, BT_OTS_OBJ_PROP_READ, 1) + +/** @brief Set @ref BT_OTS_OBJ_PROP_WRITE property. + * + * @param prop Object properties. + */ +#define BT_OTS_OBJ_SET_PROP_WRITE(prop) WRITE_BIT(prop, BT_OTS_OBJ_PROP_WRITE, 1) + +/** @brief Set @ref BT_OTS_OBJ_PROP_APPEND property. + * + * @param prop Object properties. + */ +#define BT_OTS_OBJ_SET_PROP_APPEND(prop) WRITE_BIT(prop, BT_OTS_OBJ_PROP_APPEND, 1) + +/** @brief Set @ref BT_OTS_OBJ_PROP_TRUNCATE property. + * + * @param prop Object properties. + */ +#define BT_OTS_OBJ_SET_PROP_TRUNCATE(prop) WRITE_BIT(prop, BT_OTS_OBJ_PROP_TRUNCATE, 1) + +/** @brief Set @ref BT_OTS_OBJ_PROP_PATCH property. + * + * @param prop Object properties. + */ +#define BT_OTS_OBJ_SET_PROP_PATCH(prop) WRITE_BIT(prop, BT_OTS_OBJ_PROP_PATCH, 1) + +/** @brief Set @ref BT_OTS_OBJ_PROP_MARKED property. + * + * @param prop Object properties. + */ +#define BT_OTS_OBJ_SET_PROP_MARKED(prop) WRITE_BIT(prop, BT_OTS_OBJ_PROP_MARKED, 1) + +/** @brief Get @ref BT_OTS_OBJ_PROP_DELETE property. + * + * @param prop Object properties. + */ +#define BT_OTS_OBJ_GET_PROP_DELETE(prop) ((prop) & BIT(BT_OTS_OBJ_PROP_DELETE)) + +/** @brief Get @ref BT_OTS_OBJ_PROP_EXECUTE property. + * + * @param prop Object properties. + */ +#define BT_OTS_OBJ_GET_PROP_EXECUTE(prop) ((prop) & BIT(BT_OTS_OBJ_PROP_EXECUTE)) + +/** @brief Get @ref BT_OTS_OBJ_PROP_READ property. + * + * @param prop Object properties. + */ +#define BT_OTS_OBJ_GET_PROP_READ(prop) ((prop) & BIT(BT_OTS_OBJ_PROP_READ)) + +/** @brief Get @ref BT_OTS_OBJ_PROP_WRITE property. + * + * @param prop Object properties. + */ +#define BT_OTS_OBJ_GET_PROP_WRITE(prop) ((prop) & BIT(BT_OTS_OBJ_PROP_WRITE)) + +/** @brief Get @ref BT_OTS_OBJ_PROP_APPEND property. + * + * @param prop Object properties. + */ +#define BT_OTS_OBJ_GET_PROP_APPEND(prop) ((prop) & BIT(BT_OTS_OBJ_PROP_APPEND)) + +/** @brief Get @ref BT_OTS_OBJ_PROP_TRUNCATE property. + * + * @param prop Object properties. + */ +#define BT_OTS_OBJ_GET_PROP_TRUNCATE(prop) ((prop) & BIT(BT_OTS_OBJ_PROP_TRUNCATE)) + +/** @brief Get @ref BT_OTS_OBJ_PROP_PATCH property. + * + * @param prop Object properties. + */ +#define BT_OTS_OBJ_GET_PROP_PATCH(prop) ((prop) & BIT(BT_OTS_OBJ_PROP_PATCH)) + +/** @brief Get @ref BT_OTS_OBJ_PROP_MARKED property. + * + * @param prop Object properties. + */ +#define BT_OTS_OBJ_GET_PROP_MARKED(prop) ((prop) & BIT(BT_OTS_OBJ_PROP_MARKED)) + +/** @brief Descriptor for OTS Object Size parameter. */ + +struct bt_ots_obj_size +{ + /** @brief Current Size */ + + uint32_t cur; + + /** @brief Allocated Size */ + + uint32_t alloc; +} __packed; + +/** @brief Object Action Control Point Feature bits. */ + +enum +{ + /** Bit 0 OACP Create Op Code Supported */ + + BT_OTS_OACP_FEAT_CREATE = 0, + + /** Bit 1 OACP Delete Op Code Supported */ + + BT_OTS_OACP_FEAT_DELETE = 1, + + /** Bit 2 OACP Calculate Checksum Op Code Supported */ + + BT_OTS_OACP_FEAT_CHECKSUM = 2, + + /** Bit 3 OACP Execute Op Code Supported */ + + BT_OTS_OACP_FEAT_EXECUTE = 3, + + /** Bit 4 OACP Read Op Code Supported */ + + BT_OTS_OACP_FEAT_READ = 4, + + /** Bit 5 OACP Write Op Code Supported */ + + BT_OTS_OACP_FEAT_WRITE = 5, + + /** Bit 6 Appending Additional Data to Objects Supported */ + + BT_OTS_OACP_FEAT_APPEND = 6, + + /** Bit 7 Truncation of Objects Supported */ + + BT_OTS_OACP_FEAT_TRUNCATE = 7, + + /** Bit 8 Patching of Objects Supported */ + + BT_OTS_OACP_FEAT_PATCH = 8, + + /** Bit 9 OACP Abort Op Code Supported */ + + BT_OTS_OACP_FEAT_ABORT = 9, +}; + +/* @enum bt_ots_oacp_write_op_mode + * @brief Mode Parameter for OACP Write Op Code. + */ + +enum bt_ots_oacp_write_op_mode +{ + BT_OTS_OACP_WRITE_OP_MODE_NONE = 0, + BT_OTS_OACP_WRITE_OP_MODE_TRUNCATE = BIT(1), +}; + +/** @brief Set @ref BT_OTS_OACP_FEAT_CREATE feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OACP_SET_FEAT_CREATE(feat) WRITE_BIT(feat, BT_OTS_OACP_FEAT_CREATE, 1) + +/** @brief Set @ref BT_OTS_OACP_FEAT_DELETE feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OACP_SET_FEAT_DELETE(feat) WRITE_BIT(feat, BT_OTS_OACP_FEAT_DELETE, 1) + +/** @brief Set @ref BT_OTS_OACP_FEAT_CHECKSUM feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OACP_SET_FEAT_CHECKSUM(feat) WRITE_BIT(feat, BT_OTS_OACP_FEAT_CHECKSUM, 1) + +/** @brief Set @ref BT_OTS_OACP_FEAT_EXECUTE feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OACP_SET_FEAT_EXECUTE(feat) WRITE_BIT(feat, BT_OTS_OACP_FEAT_EXECUTE, 1) + +/** @brief Set @ref BT_OTS_OACP_FEAT_READ feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OACP_SET_FEAT_READ(feat) WRITE_BIT(feat, BT_OTS_OACP_FEAT_READ, 1) + +/** @brief Set @ref BT_OTS_OACP_FEAT_WRITE feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OACP_SET_FEAT_WRITE(feat) WRITE_BIT(feat, BT_OTS_OACP_FEAT_WRITE, 1) + +/** @brief Set @ref BT_OTS_OACP_FEAT_APPEND feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OACP_SET_FEAT_APPEND(feat) WRITE_BIT(feat, BT_OTS_OACP_FEAT_APPEND, 1) + +/** @brief Set @ref BT_OTS_OACP_FEAT_TRUNCATE feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OACP_SET_FEAT_TRUNCATE(feat) WRITE_BIT(feat, BT_OTS_OACP_FEAT_TRUNCATE, 1) + +/** @brief Set @ref BT_OTS_OACP_FEAT_PATCH feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OACP_SET_FEAT_PATCH(feat) WRITE_BIT(feat, BT_OTS_OACP_FEAT_PATCH, 1) + +/** @brief Set @ref BT_OTS_OACP_FEAT_ABORT feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OACP_SET_FEAT_ABORT(feat) WRITE_BIT(feat, BT_OTS_OACP_FEAT_ABORT, 1) + +/** @brief Get @ref BT_OTS_OACP_FEAT_CREATE feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OACP_GET_FEAT_CREATE(feat) ((feat) & BIT(BT_OTS_OACP_FEAT_CREATE)) + +/** @brief Get @ref BT_OTS_OACP_FEAT_DELETE feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OACP_GET_FEAT_DELETE(feat) ((feat) & BIT(BT_OTS_OACP_FEAT_DELETE)) + +/** @brief Get @ref BT_OTS_OACP_FEAT_CHECKSUM feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OACP_GET_FEAT_CHECKSUM(feat) ((feat) & BIT(BT_OTS_OACP_FEAT_CHECKSUM)) + +/** @brief Get @ref BT_OTS_OACP_FEAT_EXECUTE feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OACP_GET_FEAT_EXECUTE(feat) ((feat) & BIT(BT_OTS_OACP_FEAT_EXECUTE)) + +/** @brief Get @ref BT_OTS_OACP_FEAT_READ feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OACP_GET_FEAT_READ(feat) ((feat) & BIT(BT_OTS_OACP_FEAT_READ)) + +/** @brief Get @ref BT_OTS_OACP_FEAT_WRITE feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OACP_GET_FEAT_WRITE(feat) ((feat) & BIT(BT_OTS_OACP_FEAT_WRITE)) + +/** @brief Get @ref BT_OTS_OACP_FEAT_APPEND feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OACP_GET_FEAT_APPEND(feat) ((feat) & BIT(BT_OTS_OACP_FEAT_APPEND)) + +/** @brief Get @ref BT_OTS_OACP_FEAT_TRUNCATE feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OACP_GET_FEAT_TRUNCATE(feat) ((feat) & BIT(BT_OTS_OACP_FEAT_TRUNCATE)) + +/** @brief Get @ref BT_OTS_OACP_FEAT_PATCH feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OACP_GET_FEAT_PATCH(feat) ((feat) & BIT(BT_OTS_OACP_FEAT_PATCH)) + +/** @brief Get @ref BT_OTS_OACP_FEAT_ABORT feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OACP_GET_FEAT_ABORT(feat) ((feat) & BIT(BT_OTS_OACP_FEAT_ABORT)) + +/** @brief Object List Control Point Feature bits. */ + +enum +{ + /** Bit 0 OLCP Go To Op Code Supported */ + + BT_OTS_OLCP_FEAT_GO_TO = 0, + + /** Bit 1 OLCP Order Op Code Supported */ + + BT_OTS_OLCP_FEAT_ORDER = 1, + + /** Bit 2 OLCP Request Number of Objects Op Code Supported */ + + BT_OTS_OLCP_FEAT_NUM_REQ = 2, + + /** Bit 3 OLCP Clear Marking Op Code Supported */ + + BT_OTS_OLCP_FEAT_CLEAR = 3, +}; + +/** @brief Set @ref BT_OTS_OLCP_FEAT_GO_TO feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OLCP_SET_FEAT_GO_TO(feat) WRITE_BIT(feat, BT_OTS_OLCP_FEAT_GO_TO, 1) + +/** @brief Set @ref BT_OTS_OLCP_FEAT_ORDER feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OLCP_SET_FEAT_ORDER(feat) WRITE_BIT(feat, BT_OTS_OLCP_FEAT_ORDER, 1) + +/** @brief Set @ref BT_OTS_OLCP_FEAT_NUM_REQ feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OLCP_SET_FEAT_NUM_REQ(feat) WRITE_BIT(feat, BT_OTS_OLCP_FEAT_NUM_REQ, 1) + +/** @brief Set @ref BT_OTS_OLCP_FEAT_CLEAR feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OLCP_SET_FEAT_CLEAR(feat) WRITE_BIT(feat, BT_OTS_OLCP_FEAT_CLEAR, 1) + +/** @brief Get @ref BT_OTS_OLCP_GET_FEAT_GO_TO feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OLCP_GET_FEAT_GO_TO(feat) ((feat) & BIT(BT_OTS_OLCP_FEAT_GO_TO)) + +/** @brief Get @ref BT_OTS_OLCP_GET_FEAT_ORDER feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OLCP_GET_FEAT_ORDER(feat) ((feat) & BIT(BT_OTS_OLCP_FEAT_ORDER)) + +/** @brief Get @ref BT_OTS_OLCP_GET_FEAT_NUM_REQ feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OLCP_GET_FEAT_NUM_REQ(feat) ((feat) & BIT(BT_OTS_OLCP_FEAT_NUM_REQ)) + +/** @brief Get @ref BT_OTS_OLCP_GET_FEAT_CLEAR feature. + * + * @param feat OTS features. + */ +#define BT_OTS_OLCP_GET_FEAT_CLEAR(feat) ((feat) & BIT(BT_OTS_OLCP_FEAT_CLEAR)) + +/**@brief Features of the OTS. */ + +struct bt_ots_feat +{ + /* OACP Features */ + + uint32_t oacp; + + /* OLCP Features */ + + uint32_t olcp; +} __packed; + +/** @brief Object metadata request bit field values */ + +enum +{ + /** @brief Request object name */ + + BT_OTS_METADATA_REQ_NAME = BIT(0), + + /** @brief Request object type */ + + BT_OTS_METADATA_REQ_TYPE = BIT(1), + + /** @brief Request object size */ + + BT_OTS_METADATA_REQ_SIZE = BIT(2), + + /** @brief Request object first created time */ + + BT_OTS_METADATA_REQ_CREATED = BIT(3), + + /** @brief Request object last modified time */ + + BT_OTS_METADATA_REQ_MODIFIED = BIT(4), + + /** @brief Request object ID */ + + BT_OTS_METADATA_REQ_ID = BIT(5), + + /** @brief Request object properties */ + + BT_OTS_METADATA_REQ_PROPS = BIT(6), + + /** @brief Request all object metadata */ + + BT_OTS_METADATA_REQ_ALL = 0x7f, +}; + +/** @brief Date and Time structure */ + +struct bt_ots_date_time +{ + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hours; + uint8_t minutes; + uint8_t seconds; +}; +#define BT_OTS_DATE_TIME_FIELD_SIZE 7 + +/** @brief Metadata of an OTS object + * + * Used by the server as a descriptor for OTS object initialization. Used by + * the client to present object metadata to the application. + */ + +struct bt_ots_obj_metadata +{ +#if defined(CONFIG_BT_OTS) || defined(__DOXYGEN__) + /** @brief Object Name */ + + char *name; +#endif /* CONFIG_BT_OTS */ + +#if defined(CONFIG_BT_OTS_CLIENT) || defined(__DOXYGEN__) + /* TODO: Unify client/server name */ + + /** @brief Object name (client) */ + + char name_c[CONFIG_BT_OTS_OBJ_MAX_NAME_LEN + 1]; +#endif /* CONFIG_BT_OTS_CLIENT */ + + /** @brief Object Type */ + + struct bt_ots_obj_type type; + + /** @brief Object Size */ + + struct bt_ots_obj_size size; + +#if defined(CONFIG_BT_OTS_CLIENT) || defined(__DOXYGEN__) + /** @brief Object first created time */ + + struct bt_ots_date_time first_created; + + /** @brief Object last modified time */ + + struct bt_ots_date_time modified; + + /** @brief Object ID */ + + uint64_t id; +#endif /* CONFIG_BT_OTS_CLIENT */ + + /** @brief Object Properties */ + + uint32_t props; +}; + +/** + * @struct bt_ots + * @brief Opaque OTS instance. + */ + +struct bt_ots; + +/** @brief Descriptor for OTS object addition */ + +struct bt_ots_obj_add_param +{ + /** @brief Object size to allocate */ + + uint32_t size; + + /** @brief Object type */ + + struct bt_ots_obj_type type; +}; + +/** @brief Descriptor for OTS created object. + * + * Descriptor for OTS object created by the application. This descriptor is + * returned by @ref bt_ots_cb.obj_created callback which contains further + * documentation on distinguishing between server and client object + * creation. + */ + +struct bt_ots_obj_created_desc +{ + /** @brief Object name + * + * The object name as a NULL terminated string. + * + * When the server creates a new object the name shall be > 0 and <= + * BT_OTS_OBJ_MAX_NAME_LEN When the client creates a new object the name + * shall be an empty string + */ + + char *name; + + /** @brief Object size + * + * @ref bt_ots_obj_size.alloc shall be >= @ref bt_ots_obj_add_param.size + * + * When the server creates a new object @ref bt_ots_obj_size.cur shall be + * <= @ref bt_ots_obj_add_param.size When the client creates a new object + * @ref bt_ots_obj_size.cur shall be 0 + */ + + struct bt_ots_obj_size size; + + /** @brief Object properties */ + + uint32_t props; +}; + +/** @brief OTS callback structure. */ + +struct bt_ots_cb +{ + /** @brief Object created callback + * + * This callback is called whenever a new object is created. Application + * can reject this request by returning an error when it does not have + * necessary resources to hold this new object. This callback is also + * triggered when the server creates a new object with bt_ots_obj_add() + * API. + * + * @param ots OTS instance. + * @param conn The connection that is requesting object creation + * or NULL if object is created by bt_ots_obj_add(). + * @param id Object ID. + * @param add_param Object creation requested parameters. + * @param created_desc Created object descriptor that shall be filled by + * the receiver of this callback. + * + * @return 0 in case of success or negative value in case of error. + * @return -ENOTSUP if object type is not supported + * @return -ENOMEM if no available space for new object. + * @return -EINVAL if an invalid parameter is provided + * @return other negative values are treated as a generic operation + * failure + */ + + int (*obj_created)(struct bt_ots *ots, struct bt_conn *conn, uint64_t id, + const struct bt_ots_obj_add_param *add_param, + struct bt_ots_obj_created_desc *created_desc); + + /** @brief Object deleted callback + * + * This callback is called whenever an object is deleted. It is also + * triggered when the server deletes an object with bt_ots_obj_delete() + * API. + * + * @param ots OTS instance. + * @param conn The connection that deleted the object or NULL if this + * request came from the server. + * @param id Object ID. + * + * @retval When an error is indicated by using a negative value, the + * object delete procedure is aborted and a corresponding failed status is + * returned to the client. + * @return 0 in case of success. + * @return -EBUSY if the object is locked. This is generally not expected + * to be returned by the application as the OTS layer tracks object + * accesses. An object locked status is returned to the client. + * @return Other negative values in case of error. A generic operation + * failed status is returned to the client. + */ + + int (*obj_deleted)(struct bt_ots *ots, struct bt_conn *conn, uint64_t id); + + /** @brief Object selected callback + * + * This callback is called on successful object selection. + * + * @param ots OTS instance. + * @param conn The connection that selected new object. + * @param id Object ID. + */ + + void (*obj_selected)(struct bt_ots *ots, + struct bt_conn *conn, + uint64_t id); + + /** @brief Object read callback + * + * This callback is called multiple times during the Object read + * operation. OTS module will keep requesting successive Object fragments + * from the application until the read operation is completed. The end of + * read operation is indicated by NULL data parameter. + * + * @param ots OTS instance. + * @param conn The connection that read object. + * @param id Object ID. + * @param data In: NULL once the read operations is completed. Out: Next + * chunk of data to be sent. + * @param len Remaining length requested by the client. + * @param offset Object data offset. + * + * @return Data length to be sent via data parameter. This value + * shall be smaller or equal to the len parameter. + * @return Negative value in case of an error. + */ + +ssize_t (*obj_read)(struct bt_ots *ots, + struct bt_conn *conn, + uint64_t id, + void **data, + size_t len, + off_t offset); + + /** @brief Object write callback + * + * This callback is called multiple times during the Object write + * operation. OTS module will keep providing successive Object fragments + * to the application until the write operation is completed. The offset + * and length of each write fragment is validated by the OTS module to be + * within the allocated size of the object. The remaining length indicates + * data length remaining to be written and will decrease each write + * iteration until it reaches 0 in the last write fragment. + * + * @param ots OTS instance. + * @param conn The connection that wrote object. + * @param id Object ID. + * @param data Next chunk of data to be written. + * @param len Length of the current chunk of data in the buffer. + * @param offset Object data offset. + * @param rem Remaining length in the write operation. + * + * @return Number of bytes written in case of success, if the number + * of bytes written does not match len, -EIO is returned to the L2CAP + * layer. + * @return A negative value in case of an error. + * @return -EINPROGRESS has a special meaning and is unsupported at + * the moment. It should not be returned. + */ + +ssize_t (*obj_write)(struct bt_ots *ots, + struct bt_conn *conn, + uint64_t id, + const void *data, + size_t len, + off_t offset, + size_t rem); + + /** @brief Object name written callback + * + * This callback is called when the object name is written. This is a + * notification to the application that the object name will be updated by + * the OTS service implementation. + * + * @param ots OTS instance. + * @param conn The connection that wrote object name. + * @param id Object ID. + * @param cur_name Current object name. + * @param new_name New object name. + */ + +void (*obj_name_written)(struct bt_ots *ots, + struct bt_conn *conn, + uint64_t id, + const char *cur_name, + const char *new_name); + + /** @brief Object Calculate checksum callback + * + * This callback is called when the OACP Calculate Checksum procedure is + * performed. Because object data is opaque to OTS, the application is the + * only one who knows where data is and should return pointer of actual + * object data. + * + * @param[in] ots OTS instance. + * @param[in] conn The connection that wrote object. + * @param[in] id Object ID. + * @param[in] offset The first octet of the object contents need to be + * calculated. + * @param[in] len The length number of octets object name. + * @param[out] data Pointer of actual object data. + * + * @return 0 to accept, or any negative value to reject. + */ + +int (*obj_cal_checksum)(struct bt_ots *ots, + struct bt_conn *conn, + uint64_t id, + off_t offset, + size_t len, + void **data); +}; + +/** @brief Descriptor for OTS initialization. */ + +struct bt_ots_init_param +{ + /* OTS features */ + + struct bt_ots_feat features; + + /* Callbacks */ + + struct bt_ots_cb *cb; +}; + +/** @brief Add an object to the OTS instance. + * + * This function adds an object to the OTS database. When the object is + * being added, a callback obj_created() is called to notify the user about + * a new object ID. + * + * @param ots OTS instance. + * @param param Object addition parameters. + * + * @return ID of created object in case of success. + * @return negative value in case of error. + */ + +int bt_ots_obj_add(struct bt_ots *ots, + const struct bt_ots_obj_add_param *param); + +/** @brief Delete an object from the OTS instance. + * + * This function deletes an object from the OTS database. When the object is + * deleted a callback obj_deleted() is called to notify the user about this + * event. At this point, it is possible to free allocated buffer for object + * data. + * + * @param ots OTS instance. + * @param id ID of the object to be deleted (uint48). + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_ots_obj_delete(struct bt_ots *ots, uint64_t id); + +/** @brief Get the service declaration attribute. + * + * This function is enabled for CONFIG_BT_OTS_SECONDARY_SVC configuration. + * The first service attribute can be included in any other GATT service. + * + * @param ots OTS instance. + * + * @return The first OTS attribute instance. + */ + +void *bt_ots_svc_decl_get(struct bt_ots *ots); + +/** @brief Initialize the OTS instance. + * + * @param ots OTS instance. + * @param ots_init OTS initialization descriptor. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_ots_init(struct bt_ots *ots, struct bt_ots_init_param *ots_init); + +/** @brief Get a free instance of OTS from the pool. + * + * @return OTS instance in case of success or NULL in case of error. + */ + +struct bt_ots *bt_ots_free_instance_get(void); + +#define BT_OTS_STOP 0 +#define BT_OTS_CONTINUE 1 + +/* TODO: Merge server and client instance as opaque type */ + +/** @brief OTS client instance */ + +struct bt_ots_client +{ + uint16_t start_handle; + uint16_t end_handle; + uint16_t feature_handle; + uint16_t obj_name_handle; + uint16_t obj_type_handle; + uint16_t obj_size_handle; + uint16_t obj_properties_handle; + uint16_t obj_created_handle; + uint16_t obj_modified_handle; + uint16_t obj_id_handle; + uint16_t oacp_handle; + uint16_t olcp_handle; + + struct bt_gatt_subscribe_params oacp_sub_params; + struct bt_gatt_discover_params oacp_sub_disc_params; + struct bt_gatt_subscribe_params olcp_sub_params; + struct bt_gatt_discover_params olcp_sub_disc_params; + + struct bt_gatt_write_params write_params; + struct bt_gatt_read_params read_proc; + struct bt_ots_client_cb *cb; + + struct bt_ots_feat features; + + struct bt_ots_obj_metadata cur_object; +}; + +/** OTS client callback structure */ + +struct bt_ots_client_cb +{ + /** @brief Callback function when a new object is selected. + * + * Called when the a new object is selected and the current object has + * changed. The `cur_object` in `ots_inst` will have been reset, and + * metadata should be read again with + * bt_ots_client_read_object_metadata(). + * + * @param ots_inst Pointer to the OTC instance. + * @param conn The connection to the peer device. + * @param err Error code (bt_ots_olcp_res_code). + */ + + void (*obj_selected)(struct bt_ots_client *ots_inst, struct bt_conn *conn, + int err); + + /** @brief Callback function for the data of the selected + * object. + * + * Called when the data of the selected object are read using + * bt_ots_client_read_object_data(). + * + * @param ots_inst Pointer to the OTC instance. + * @param conn The connection to the peer device. + * @param offset Offset of the received data. + * @param len Length of the received data. + * @param data_p Pointer to the received data. + * @param is_complete Indicate if the whole object has been received. + * + * @return int BT_OTS_STOP or BT_OTS_CONTINUE. BT_OTS_STOP can + * be used to stop reading. + */ + +int (*obj_data_read)(struct bt_ots_client *ots_inst, + struct bt_conn *conn, + uint32_t offset, + uint32_t len, + uint8_t *data_p, + bool is_complete); + + /** @brief Callback function for metadata of the selected object. + * + * Called when metadata of the selected object are read using + * bt_ots_client_read_object_metadata(). Not all of the metadata may have + * been initialized. + * + * @param ots_inst Pointer to the OTC instance. + * @param conn The connection to the peer device. + * @param err Error value. 0 on success, GATT error or ERRNO + * on fail. + * @param metadata_read Bitfield of the metadata that was successfully + * read. + */ + +void (*obj_metadata_read)(struct bt_ots_client *ots_inst, + struct bt_conn *conn, + int err, + uint8_t metadata_read); + + /** @brief Callback function for the data of the write object. + * + * Called when the data of the selected object is written using + * bt_ots_client_write_object_data(). + * + * @param ots_inst Pointer to the OTC instance. + * @param conn The connection to the peer device. + * @param len Length of the written data. + */ + + void (*obj_data_written)(struct bt_ots_client *ots_inst, + struct bt_conn *conn, size_t len); + + /** @brief Callback function when checksum indication is received. + * + * Called when the oacp_ind_handler received response of OP + * BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC. + * + * @param ots_inst Pointer to the OTC instance. + * @param conn The connection to the peer device. + * @param err Error code (bt_gatt_ots_oacp_res_code). + * @param checksum Checksum if error code is + * BT_GATT_OTS_OACP_RES_SUCCESS, otherwise 0. + */ + +void (*obj_checksum_calculated)(struct bt_ots_client *ots_inst, + struct bt_conn *conn, + int err, + uint32_t checksum); +}; + +/** @brief Register an Object Transfer Service Instance. + * + * Register an Object Transfer Service instance discovered on the peer. Call + * this function when an OTS instance is discovered (discovery is to be + * handled by the higher layer). + * + * @param[in] ots_inst Discovered OTS instance. + * + * @return int 0 if success, ERRNO on failure. + */ + +int bt_ots_client_register(struct bt_ots_client *ots_inst); + +/** @brief Unregister an Object Transfer Service Instance. + * + * Unregister an Object Transfer Service instance when disconnect from the + * peer. Call this function when an ACL using OTS instance is disconnected. + * + * @param[in] index Index of OTS instance. + * + * @return int 0 if success, ERRNO on failure. + */ + +int bt_ots_client_unregister(uint8_t index); + +/** @brief OTS Indicate Handler function. + * + * Set this function as callback for indicate handler when discovering OTS. + * + * @param conn Connection object. May be NULL, indicating that the + * peer is being unpaired. + * @param params Subscription parameters. + * @param data Attribute value data. If NULL then subscription was + * removed. + * @param length Attribute value length. + */ + +uint8_t bt_ots_client_indicate_handler(struct bt_conn *conn, + struct bt_gatt_subscribe_params *params, + const void *data, + uint16_t length); + +/** @brief Read the OTS feature characteristic. + * + * @param otc_inst Pointer to the OTC instance. + * @param conn Pointer to the connection object. + * + * @return int 0 if success, ERRNO on failure. + */ + +int bt_ots_client_read_feature(struct bt_ots_client *otc_inst, + struct bt_conn *conn); + +/** @brief Select an object by its Object ID. + * + * @param otc_inst Pointer to the OTC instance. + * @param conn Pointer to the connection object. + * @param obj_id Object's ID. + * + * @return int 0 if success, ERRNO on failure. + */ + +int bt_ots_client_select_id( + struct bt_ots_client *otc_inst, struct bt_conn *conn, uint64_t obj_id); + +/** @brief Select the first object. + * + * @param otc_inst Pointer to the OTC instance. + * @param conn Pointer to the connection object. + * + * @return int 0 if success, ERRNO on failure. + */ + +int bt_ots_client_select_first(struct bt_ots_client *otc_inst, + struct bt_conn *conn); + +/** @brief Select the last object. + * + * @param otc_inst Pointer to the OTC instance. + * @param conn Pointer to the connection object. + * + * @return int 0 if success, ERRNO on failure. + */ + +int bt_ots_client_select_last(struct bt_ots_client *otc_inst, + struct bt_conn *conn); + +/** @brief Select the next object. + * + * @param otc_inst Pointer to the OTC instance. + * @param conn Pointer to the connection object. + * + * @return int 0 if success, ERRNO on failure. + */ + +int bt_ots_client_select_next(struct bt_ots_client *otc_inst, + struct bt_conn *conn); + +/** @brief Select the previous object. + * + * @param otc_inst Pointer to the OTC instance. + * @param conn Pointer to the connection object. + * + * @return int 0 if success, ERRNO on failure. + */ + +int bt_ots_client_select_prev(struct bt_ots_client *otc_inst, + struct bt_conn *conn); + +/** @brief Read the metadata of the current object. + * + * The metadata are returned in the obj_metadata_read() callback. + * + * @param otc_inst Pointer to the OTC instance. + * @param conn Pointer to the connection object. + * @param metadata Bitfield (`BT_OTS_METADATA_REQ_*`) of the metadata to + * read. + * + * @return int 0 if success, ERRNO on failure. + */ + +int bt_ots_client_read_object_metadata( + struct bt_ots_client *otc_inst, struct bt_conn *conn, uint8_t metadata); + +/** @brief Read the data of the current selected object. + * + * This will trigger an OACP read operation for the current size of the + * object with a 0 offset and then expect receiving the content via the + * L2CAP CoC. + * + * The data of the object are returned in the obj_data_read() callback. + * + * @param otc_inst Pointer to the OTC instance. + * @param conn Pointer to the connection object. + * + * @return int 0 if success, ERRNO on failure. + */ + +int bt_ots_client_read_object_data(struct bt_ots_client *otc_inst, + struct bt_conn *conn); + +/** @brief Write the data of the current selected object. + * + * This will trigger an OACP write operation for the current object with a + * specified offset and then expect transferring the content via the L2CAP + * CoC. + * + * The length of the data written to object is returned in the + * obj_data_written() callback. + * + * @param otc_inst Pointer to the OTC instance. + * @param conn Pointer to the connection object. + * @param buf Pointer to the data buffer to be written. + * @param len Size of data. + * @param offset Offset to write, usually 0. + * @param mode Mode Parameter for OACP Write Op Code. See @ref + * bt_ots_oacp_write_op_mode. + * + * @return int 0 if success, ERRNO on failure. + */ + +int bt_ots_client_write_object_data( + struct bt_ots_client *otc_inst, struct bt_conn *conn, const void *buf, + size_t len, off_t offset, enum bt_ots_oacp_write_op_mode mode); + +/** @brief Get the checksum of the current selected object. + * + * This will trigger an OACP calculate checksum operation for the current + * object with a specified offset and length. + * + * The checksum goes to OACP IND and obj_checksum_calculated() callback. + * + * @param otc_inst Pointer to the OTC instance. + * @param conn Pointer to the connection object. + * @param offset Offset to calculate, usually 0. + * @param len Len of data to calculate checksum for. May be less + * than the current object's size, but shall not be + * larger. + * + * @return int 0 if success, ERRNO on failure. + */ + +int bt_ots_client_get_object_checksum( + struct bt_ots_client *otc_inst, struct bt_conn *conn, off_t offset, + size_t len); + +/** @brief Directory listing object metadata callback + * + * If a directory listing is decoded using bt_ots_client_decode_dirlisting(), + * this callback will be called for each object in the directory listing. + * + * @param meta The metadata of the decoded object + * + * @return int BT_OTS_STOP or BT_OTS_CONTINUE. BT_OTS_STOP can be used to + * stop the decoding. + */ + +typedef int (*bt_ots_client_dirlisting_cb)(struct bt_ots_obj_metadata *meta); + +/** @brief Decode Directory Listing object into object metadata. + * + * If the Directory Listing object contains multiple objects, then the + * callback will be called for each of them. + * + * @param data The data received for the directory listing object. + * @param length Length of the data. + * @param cb The callback that will be called for each object. + */ + +int bt_ots_client_decode_dirlisting(uint8_t *data, uint16_t length, + bt_ots_client_dirlisting_cb cb); + +/** @brief Converts binary OTS Object ID to string. + * + * @param obj_id Object ID. + * @param str Address of user buffer with enough room to store formatted + * string containing binary Object ID. + * @param len Length of data to be copied to user string buffer. Refer to + * BT_OTS_OBJ_ID_STR_LEN about recommended value. + * + * @return Number of successfully formatted bytes from binary ID. + */ + +static inline int bt_ots_obj_id_to_str(uint64_t obj_id, + char *str, size_t len) +{ + uint8_t id[6]; + + sys_put_le48(obj_id, id); + + return snprintf(str, len, "0x%02X%02X%02X%02X%02X%02X", + id[5], id[4], id[3], id[2], id[1], id[0]); +} + +/** @brief Displays one or more object metadata as text with LOG_INF. + * + * @param metadata Pointer to the first (or only) metadata in an array. + * @param count Number of metadata objects to display information of. + */ + +void bt_ots_metadata_display(struct bt_ots_obj_metadata *metadata, + uint16_t count); + +/** + * @brief Generate IEEE conform CRC32 checksum. + * + * To abstract IEEE implementation to service layer. + * + * @param data Pointer to data on which the CRC should be + * calculated. + * @param len Data length. + * + * @return CRC32 value. + * + */ + +static inline uint32_t bt_ots_client_calc_checksum(const uint8_t *data, + size_t len) +{ + return crc32_ieee(data, len); +} + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_SERVICES_OTS_H */ diff --git a/include/nuttx/net/bluetooth/testing.h b/include/nuttx/net/bluetooth/testing.h new file mode 100644 index 0000000000000..b106ece2029a9 --- /dev/null +++ b/include/nuttx/net/bluetooth/testing.h @@ -0,0 +1,57 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/testing.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2024 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +/** @brief Internal testing interfaces for Bluetooth + * @file @internal + * + * The interfaces in this file are internal and not stable. + */ + +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_TESTING_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_TESTING_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/** @brief Hook for `acl_in_pool.destroy` + * + * Weak-function interface. The user can simply define this function, and it + * will automatically become the event listener. + * + * @kconfig_dep{CONFIG_BT_TESTING} + */ + +void bt_testing_trace_event_acl_pool_destroy(struct net_buf *buf); + +/** @brief Trace when extended advertising report reassembly timed out + * + * Weak-function interface called when the host reassembly timer expires for + * a fragmented extended advertising report chain. + * + * @kconfig_dep{CONFIG_BT_TESTING} + */ + +void bt_testing_trace_ext_adv_reassembly_timeout(void); + +/** @brief Trace when a fragmented extended advertising report is delivered + * + * Weak-function interface called when a fragmented extended advertising + * report chain has been fully reassembled and delivered to the application. + * + * @kconfig_dep{CONFIG_BT_TESTING} + */ + +void bt_testing_trace_ext_adv_reassembly_complete(void); + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_TESTING_H */ diff --git a/include/nuttx/net/bluetooth/uuid.h b/include/nuttx/net/bluetooth/uuid.h new file mode 100644 index 0000000000000..5a0ee4e9294a2 --- /dev/null +++ b/include/nuttx/net/bluetooth/uuid.h @@ -0,0 +1,4880 @@ +/**************************************************************************** + * include/nuttx/net/bluetooth/uuid.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Bluetooth UUID handling + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __INCLUDE_NUTTX_NET_BLUETOOTH_UUID_H +#define __INCLUDE_NUTTX_NET_BLUETOOTH_UUID_H + +/** + * @brief UUIDs + * @defgroup bt_uuid UUIDs + * @since 1.0 + * @version 1.0.0 + * @ingroup bluetooth + * @{ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** @brief Bluetooth UUID types */ + +enum +{ + /** UUID type 16-bit. */ + + BT_UUID_TYPE_16, + + /** UUID type 32-bit. */ + + BT_UUID_TYPE_32, + + /** UUID type 128-bit. */ + + BT_UUID_TYPE_128, +}; + +/** Size in octets of a 16-bit UUID */ +#define BT_UUID_SIZE_16 2 + +/** Size in octets of a 32-bit UUID */ +#define BT_UUID_SIZE_32 4 + +/** Size in octets of a 128-bit UUID */ +#define BT_UUID_SIZE_128 16 + +/** @brief This is a 'tentative' type and should be used as a pointer only */ + +struct bt_uuid +{ + uint8_t type; +}; + +struct bt_uuid_16 +{ + /** UUID generic type. */ + + struct bt_uuid uuid; + + /** UUID value, 16-bit in host endianness. */ + + uint16_t val; +}; + +struct bt_uuid_32 +{ + /** UUID generic type. */ + + struct bt_uuid uuid; + + /** UUID value, 32-bit in host endianness. */ + + uint32_t val; +}; + +struct bt_uuid_128 +{ + /** UUID generic type. */ + + struct bt_uuid uuid; + + /** UUID value, 128-bit in little-endian format. */ + + uint8_t val[BT_UUID_SIZE_128]; +}; + +/** @brief Helper type that can store any UUID size. */ + +struct bt_uuid_any +{ + /** Union of all supported UUID sizes. */ + + union + { + /** Generic UUID view. */ + + struct bt_uuid uuid; + + /** 16-bit UUID view. */ + + struct bt_uuid_16 u16; + + /** 32-bit UUID view. */ + + struct bt_uuid_32 u32; + + /** 128-bit UUID view. */ + + struct bt_uuid_128 u128; + }; +}; + +/** @brief Initialize a 16-bit UUID. + * + * @param value 16-bit UUID value in host endianness. + */ +#define BT_UUID_INIT_16(value) \ +{ \ + .uuid = {BT_UUID_TYPE_16}, \ + .val = (value), \ +} + +/** @brief Initialize a 32-bit UUID. + * + * @param value 32-bit UUID value in host endianness. + */ +#define BT_UUID_INIT_32(value) \ +{ \ + .uuid = {BT_UUID_TYPE_32}, \ + .val = (value), \ +} + +/** @brief Initialize a 128-bit UUID. + * + * @param value 128-bit UUID array values in little-endian format. Can be + * combined with @ref BT_UUID_128_ENCODE to initialize a UUID + * from the readable form of UUIDs. + */ +#define BT_UUID_INIT_128(value...) \ +{ \ + .uuid = {BT_UUID_TYPE_128}, \ + .val = {value}, \ +} + +/** @brief Helper to declare a 16-bit UUID inline. + * + * @param value 16-bit UUID value in host endianness. + * + * @return Pointer to a generic UUID. + */ +#define BT_UUID_DECLARE_16(value) \ +((const struct bt_uuid *)((const struct bt_uuid_16[]){BT_UUID_INIT_16(value)})) + +/** @brief Helper to declare a 32-bit UUID inline. + * + * @param value 32-bit UUID value in host endianness. + * + * @return Pointer to a generic UUID. + */ +#define BT_UUID_DECLARE_32(value) \ +((const struct bt_uuid *)((const struct bt_uuid_32[]){BT_UUID_INIT_32(value)})) + +/** @brief Helper to declare a 128-bit UUID inline. + * + * @param value 128-bit UUID array values in little-endian format. Can be + * combined with @ref BT_UUID_128_ENCODE to declare a UUID from + * the readable form of UUIDs. + * + * @return Pointer to a generic UUID. + */ +#define BT_UUID_DECLARE_128(value...) \ +((const struct bt_uuid *)((const struct bt_uuid_128[]){BT_UUID_INIT_128(value)})) + +/** Helper macro to access the 16-bit UUID from a generic UUID. */ +#define BT_UUID_16(__u) CONTAINER_OF(__u, struct bt_uuid_16, uuid) + +/** Helper macro to access the 32-bit UUID from a generic UUID. */ +#define BT_UUID_32(__u) CONTAINER_OF(__u, struct bt_uuid_32, uuid) + +/** Helper macro to access the 128-bit UUID from a generic UUID. */ +#define BT_UUID_128(__u) CONTAINER_OF(__u, struct bt_uuid_128, uuid) + +/** @brief Encode 128 bit UUID into array values in little-endian format. + * + * Helper macro to initialize a 128-bit UUID array value from the readable + * form of UUIDs, or encode 128-bit UUID values into advertising data Can be + * combined with BT_UUID_DECLARE_128 to declare a 128-bit UUID. + * + * Example of how to declare the UUID `6E400001-B5A3-F393-E0A9-E50E24DCCA9E` + * + * @code BT_UUID_DECLARE_128( BT_UUID_128_ENCODE(0x6E400001, 0xB5A3, 0xF393, + * 0xE0A9, 0xE50E24DCCA9E)) @endcode + * + * Example of how to encode the UUID `6E400001-B5A3-F393-E0A9-E50E24DCCA9E` + * into advertising data. + * + * @code BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_128_ENCODE(0x6E400001, + * 0xB5A3, 0xF393, 0xE0A9, 0xE50E24DCCA9E)) @endcode + * + * Just replace the hyphen by the comma and add `0x` prefixes. + * + * @param w32 First part of the UUID (32 bits) + * @param w1 Second part of the UUID (16 bits) + * @param w2 Third part of the UUID (16 bits) + * @param w3 Fourth part of the UUID (16 bits) + * @param w48 Fifth part of the UUID (48 bits) + * + * @return The comma separated values for UUID 128 initializer that + * may be used directly as an argument for + * @ref BT_UUID_INIT_128 or @ref BT_UUID_DECLARE_128 + */ +#define BT_UUID_128_ENCODE(w32, w1, w2, w3, w48) \ +BT_BYTES_LIST_LE48(w48), BT_BYTES_LIST_LE16(w3), BT_BYTES_LIST_LE16(w2), BT_BYTES_LIST_LE16(w1), \ + BT_BYTES_LIST_LE32(w32) + +/** @brief Encode 16-bit UUID into array values in little-endian format. + * + * Helper macro to encode 16-bit UUID values into advertising data. + * + * Example of how to encode the UUID `0x180a` into advertising data. + * + * @code BT_DATA_BYTES(BT_DATA_UUID16_ALL, BT_UUID_16_ENCODE(0x180a)) + * @endcode + * + * @param w16 UUID value (16-bits) + * + * @return The comma separated values for UUID 16 value that + * may be used directly as an argument for @ref BT_DATA_BYTES. + */ +#define BT_UUID_16_ENCODE(w16) BT_BYTES_LIST_LE16(w16) + +/** @brief Encode 32-bit UUID into array values in little-endian format. + * + * Helper macro to encode 32-bit UUID values into advertising data. + * + * Example of how to encode the UUID `0x180a01af` into advertising data. + * + * @code BT_DATA_BYTES(BT_DATA_UUID32_ALL, BT_UUID_32_ENCODE(0x180a01af)) + * @endcode + * + * @param w32 UUID value (32-bits) + * + * @return The comma separated values for UUID 32 value that + * may be used directly as an argument for @ref BT_DATA_BYTES. + */ +#define BT_UUID_32_ENCODE(w32) BT_BYTES_LIST_LE32(w32) + +/** + * @brief Recommended length of user string buffer for Bluetooth UUID. + * + * @details The recommended length guarantee the output of UUID + * conversion will not lose valuable information about the UUID being + * processed. If the length of the UUID is known the string can be shorter. + */ +#define BT_UUID_STR_LEN 37 + +/** + * @brief Generic Access UUID value + */ +#define BT_UUID_GAP_VAL 0x1800 +/** + * @brief Generic Access + */ +#define BT_UUID_GAP BT_UUID_DECLARE_16(BT_UUID_GAP_VAL) +/** + * @brief Generic attribute UUID value + */ +#define BT_UUID_GATT_VAL 0x1801 +/** + * @brief Generic Attribute + */ +#define BT_UUID_GATT BT_UUID_DECLARE_16(BT_UUID_GATT_VAL) +/** + * @brief Immediate Alert Service UUID value + */ +#define BT_UUID_IAS_VAL 0x1802 +/** + * @brief Immediate Alert Service + */ +#define BT_UUID_IAS BT_UUID_DECLARE_16(BT_UUID_IAS_VAL) +/** + * @brief Link Loss Service UUID value + */ +#define BT_UUID_LLS_VAL 0x1803 +/** + * @brief Link Loss Service + */ +#define BT_UUID_LLS BT_UUID_DECLARE_16(BT_UUID_LLS_VAL) +/** + * @brief Tx Power Service UUID value + */ +#define BT_UUID_TPS_VAL 0x1804 +/** + * @brief Tx Power Service + */ +#define BT_UUID_TPS BT_UUID_DECLARE_16(BT_UUID_TPS_VAL) +/** + * @brief Current Time Service UUID value + */ +#define BT_UUID_CTS_VAL 0x1805 +/** + * @brief Current Time Service + */ +#define BT_UUID_CTS BT_UUID_DECLARE_16(BT_UUID_CTS_VAL) +/** + * @brief Reference Time Update Service UUID value + */ +#define BT_UUID_RTUS_VAL 0x1806 +/** + * @brief Reference Time Update Service + */ +#define BT_UUID_RTUS BT_UUID_DECLARE_16(BT_UUID_RTUS_VAL) +/** + * @brief Next DST Change Service UUID value + */ +#define BT_UUID_NDSTS_VAL 0x1807 +/** + * @brief Next DST Change Service + */ +#define BT_UUID_NDSTS BT_UUID_DECLARE_16(BT_UUID_NDSTS_VAL) +/** + * @brief Glucose Service UUID value + */ +#define BT_UUID_GS_VAL 0x1808 +/** + * @brief Glucose Service + */ +#define BT_UUID_GS BT_UUID_DECLARE_16(BT_UUID_GS_VAL) +/** + * @brief Health Thermometer Service UUID value + */ +#define BT_UUID_HTS_VAL 0x1809 +/** + * @brief Health Thermometer Service + */ +#define BT_UUID_HTS BT_UUID_DECLARE_16(BT_UUID_HTS_VAL) +/** + * @brief Device Information Service UUID value + */ +#define BT_UUID_DIS_VAL 0x180a +/** + * @brief Device Information Service + */ +#define BT_UUID_DIS BT_UUID_DECLARE_16(BT_UUID_DIS_VAL) +/** + * @brief Network Availability Service UUID value + */ +#define BT_UUID_NAS_VAL 0x180b +/** + * @brief Network Availability Service + */ +#define BT_UUID_NAS BT_UUID_DECLARE_16(BT_UUID_NAS_VAL) +/** + * @brief Watchdog Service UUID value + */ +#define BT_UUID_WDS_VAL 0x180c +/** + * @brief Watchdog Service + */ +#define BT_UUID_WDS BT_UUID_DECLARE_16(BT_UUID_WDS_VAL) +/** + * @brief Heart Rate Service UUID value + */ +#define BT_UUID_HRS_VAL 0x180d +/** + * @brief Heart Rate Service + */ +#define BT_UUID_HRS BT_UUID_DECLARE_16(BT_UUID_HRS_VAL) +/** + * @brief Phone Alert Service UUID value + */ +#define BT_UUID_PAS_VAL 0x180e +/** + * @brief Phone Alert Service + */ +#define BT_UUID_PAS BT_UUID_DECLARE_16(BT_UUID_PAS_VAL) +/** + * @brief Battery Service UUID value + */ +#define BT_UUID_BAS_VAL 0x180f +/** + * @brief Battery Service + */ +#define BT_UUID_BAS BT_UUID_DECLARE_16(BT_UUID_BAS_VAL) +/** + * @brief Blood Pressure Service UUID value + */ +#define BT_UUID_BPS_VAL 0x1810 +/** + * @brief Blood Pressure Service + */ +#define BT_UUID_BPS BT_UUID_DECLARE_16(BT_UUID_BPS_VAL) +/** + * @brief Alert Notification Service UUID value + */ +#define BT_UUID_ANS_VAL 0x1811 +/** + * @brief Alert Notification Service + */ +#define BT_UUID_ANS BT_UUID_DECLARE_16(BT_UUID_ANS_VAL) +/** + * @brief HID Service UUID value + */ +#define BT_UUID_HIDS_VAL 0x1812 +/** + * @brief HID Service + */ +#define BT_UUID_HIDS BT_UUID_DECLARE_16(BT_UUID_HIDS_VAL) +/** + * @brief Scan Parameters Service UUID value + */ +#define BT_UUID_SPS_VAL 0x1813 +/** + * @brief Scan Parameters Service + */ +#define BT_UUID_SPS BT_UUID_DECLARE_16(BT_UUID_SPS_VAL) +/** + * @brief Running Speed and Cadence Service UUID value + */ +#define BT_UUID_RSCS_VAL 0x1814 +/** + * @brief Running Speed and Cadence Service + */ +#define BT_UUID_RSCS BT_UUID_DECLARE_16(BT_UUID_RSCS_VAL) +/** + * @brief Automation IO Service UUID value + */ +#define BT_UUID_AIOS_VAL 0x1815 +/** + * @brief Automation IO Service + */ +#define BT_UUID_AIOS BT_UUID_DECLARE_16(BT_UUID_AIOS_VAL) +/** + * @brief Cycling Speed and Cadence Service UUID value + */ +#define BT_UUID_CSC_VAL 0x1816 +/** + * @brief Cycling Speed and Cadence Service + */ +#define BT_UUID_CSC BT_UUID_DECLARE_16(BT_UUID_CSC_VAL) +/** + * @brief Cycling Power Service UUID value + */ +#define BT_UUID_CPS_VAL 0x1818 +/** + * @brief Cycling Power Service + */ +#define BT_UUID_CPS BT_UUID_DECLARE_16(BT_UUID_CPS_VAL) +/** + * @brief Location and Navigation Service UUID value + */ +#define BT_UUID_LNS_VAL 0x1819 +/** + * @brief Location and Navigation Service + */ +#define BT_UUID_LNS BT_UUID_DECLARE_16(BT_UUID_LNS_VAL) +/** + * @brief Environmental Sensing Service UUID value + */ +#define BT_UUID_ESS_VAL 0x181a +/** + * @brief Environmental Sensing Service + */ +#define BT_UUID_ESS BT_UUID_DECLARE_16(BT_UUID_ESS_VAL) +/** + * @brief Body Composition Service UUID value + */ +#define BT_UUID_BCS_VAL 0x181b +/** + * @brief Body Composition Service + */ +#define BT_UUID_BCS BT_UUID_DECLARE_16(BT_UUID_BCS_VAL) +/** + * @brief User Data Service UUID value + */ +#define BT_UUID_UDS_VAL 0x181c +/** + * @brief User Data Service + */ +#define BT_UUID_UDS BT_UUID_DECLARE_16(BT_UUID_UDS_VAL) +/** + * @brief Weight Scale Service UUID value + */ +#define BT_UUID_WSS_VAL 0x181d +/** + * @brief Weight Scale Service + */ +#define BT_UUID_WSS BT_UUID_DECLARE_16(BT_UUID_WSS_VAL) +/** + * @brief Bond Management Service UUID value + */ +#define BT_UUID_BMS_VAL 0x181e +/** + * @brief Bond Management Service + */ +#define BT_UUID_BMS BT_UUID_DECLARE_16(BT_UUID_BMS_VAL) +/** + * @brief Continuous Glucose Monitoring Service UUID value + */ +#define BT_UUID_CGMS_VAL 0x181f +/** + * @brief Continuous Glucose Monitoring Service + */ +#define BT_UUID_CGMS BT_UUID_DECLARE_16(BT_UUID_CGMS_VAL) +/** + * @brief IP Support Service UUID value + */ +#define BT_UUID_IPSS_VAL 0x1820 +/** + * @brief IP Support Service + */ +#define BT_UUID_IPSS BT_UUID_DECLARE_16(BT_UUID_IPSS_VAL) +/** + * @brief Indoor Positioning Service UUID value + */ +#define BT_UUID_IPS_VAL 0x1821 +/** + * @brief Indoor Positioning Service + */ +#define BT_UUID_IPS BT_UUID_DECLARE_16(BT_UUID_IPS_VAL) +/** + * @brief Pulse Oximeter Service UUID value + */ +#define BT_UUID_POS_VAL 0x1822 +/** + * @brief Pulse Oximeter Service + */ +#define BT_UUID_POS BT_UUID_DECLARE_16(BT_UUID_POS_VAL) +/** + * @brief HTTP Proxy Service UUID value + */ +#define BT_UUID_HPS_VAL 0x1823 +/** + * @brief HTTP Proxy Service + */ +#define BT_UUID_HPS BT_UUID_DECLARE_16(BT_UUID_HPS_VAL) +/** + * @brief Transport Discovery Service UUID value + */ +#define BT_UUID_TDS_VAL 0x1824 +/** + * @brief Transport Discovery Service + */ +#define BT_UUID_TDS BT_UUID_DECLARE_16(BT_UUID_TDS_VAL) +/** + * @brief Object Transfer Service UUID value + */ +#define BT_UUID_OTS_VAL 0x1825 +/** + * @brief Object Transfer Service + */ +#define BT_UUID_OTS BT_UUID_DECLARE_16(BT_UUID_OTS_VAL) +/** + * @brief Fitness Machine Service UUID value + */ +#define BT_UUID_FMS_VAL 0x1826 +/** + * @brief Fitness Machine Service + */ +#define BT_UUID_FMS BT_UUID_DECLARE_16(BT_UUID_FMS_VAL) +/** + * @brief Mesh Provisioning Service UUID value + */ +#define BT_UUID_MESH_PROV_VAL 0x1827 +/** + * @brief Mesh Provisioning Service + */ +#define BT_UUID_MESH_PROV BT_UUID_DECLARE_16(BT_UUID_MESH_PROV_VAL) +/** + * @brief Mesh Proxy Service UUID value + */ +#define BT_UUID_MESH_PROXY_VAL 0x1828 +/** + * @brief Mesh Proxy Service + */ +#define BT_UUID_MESH_PROXY BT_UUID_DECLARE_16(BT_UUID_MESH_PROXY_VAL) +/** + * @brief Proxy Solicitation UUID value + */ +#define BT_UUID_MESH_PROXY_SOLICITATION_VAL 0x1859 +/** + * @brief Reconnection Configuration Service UUID value + */ +#define BT_UUID_RCSRV_VAL 0x1829 +/** + * @brief Reconnection Configuration Service + */ +#define BT_UUID_RCSRV BT_UUID_DECLARE_16(BT_UUID_RCSRV_VAL) +/** + * @brief Insulin Delivery Service UUID value + */ +#define BT_UUID_IDS_VAL 0x183a +/** + * @brief Insulin Delivery Service + */ +#define BT_UUID_IDS BT_UUID_DECLARE_16(BT_UUID_IDS_VAL) +/** + * @brief Binary Sensor Service UUID value + */ +#define BT_UUID_BSS_VAL 0x183b +/** + * @brief Binary Sensor Service + */ +#define BT_UUID_BSS BT_UUID_DECLARE_16(BT_UUID_BSS_VAL) +/** + * @brief Emergency Configuration Service UUID value + */ +#define BT_UUID_ECS_VAL 0x183c +/** + * @brief Emergency Configuration Service + */ +#define BT_UUID_ECS BT_UUID_DECLARE_16(BT_UUID_ECS_VAL) +/** + * @brief Authorization Control Service UUID value + */ +#define BT_UUID_ACLS_VAL 0x183d +/** + * @brief Authorization Control Service + */ +#define BT_UUID_ACLS BT_UUID_DECLARE_16(BT_UUID_ACLS_VAL) +/** + * @brief Physical Activity Monitor Service UUID value + */ +#define BT_UUID_PAMS_VAL 0x183e +/** + * @brief Physical Activity Monitor Service + */ +#define BT_UUID_PAMS BT_UUID_DECLARE_16(BT_UUID_PAMS_VAL) +/** + * @brief Elapsed Time Service UUID value + */ +#define BT_UUID_ETS_VAL 0x183f +/** + * @brief Elapsed Time Service + */ +#define BT_UUID_ETS BT_UUID_DECLARE_16(BT_UUID_ETS_VAL) +/** + * @brief Audio Input Control Service UUID value + */ +#define BT_UUID_AICS_VAL 0x1843 +/** + * @brief Audio Input Control Service + */ +#define BT_UUID_AICS BT_UUID_DECLARE_16(BT_UUID_AICS_VAL) +/** + * @brief Volume Control Service UUID value + */ +#define BT_UUID_VCS_VAL 0x1844 +/** + * @brief Volume Control Service + */ +#define BT_UUID_VCS BT_UUID_DECLARE_16(BT_UUID_VCS_VAL) +/** + * @brief Volume Offset Control Service UUID value + */ +#define BT_UUID_VOCS_VAL 0x1845 +/** + * @brief Volume Offset Control Service + */ +#define BT_UUID_VOCS BT_UUID_DECLARE_16(BT_UUID_VOCS_VAL) +/** + * @brief Coordinated Set Identification Service UUID value + */ +#define BT_UUID_CSIS_VAL 0x1846 +/** + * @brief Coordinated Set Identification Service + */ +#define BT_UUID_CSIS BT_UUID_DECLARE_16(BT_UUID_CSIS_VAL) +/** + * @brief Device Time Service UUID value + */ +#define BT_UUID_DTS_VAL 0x1847 +/** + * @brief Device Time Service + */ +#define BT_UUID_DTS BT_UUID_DECLARE_16(BT_UUID_DTS_VAL) +/** + * @brief Media Control Service UUID value + */ +#define BT_UUID_MCS_VAL 0x1848 +/** + * @brief Media Control Service + */ +#define BT_UUID_MCS BT_UUID_DECLARE_16(BT_UUID_MCS_VAL) +/** + * @brief Generic Media Control Service UUID value + */ +#define BT_UUID_GMCS_VAL 0x1849 +/** + * @brief Generic Media Control Service + */ +#define BT_UUID_GMCS BT_UUID_DECLARE_16(BT_UUID_GMCS_VAL) +/** + * @brief Constant Tone Extension Service UUID value + */ +#define BT_UUID_CTES_VAL 0x184a +/** + * @brief Constant Tone Extension Service + */ +#define BT_UUID_CTES BT_UUID_DECLARE_16(BT_UUID_CTES_VAL) +/** + * @brief Telephone Bearer Service UUID value + */ +#define BT_UUID_TBS_VAL 0x184b +/** + * @brief Telephone Bearer Service + */ +#define BT_UUID_TBS BT_UUID_DECLARE_16(BT_UUID_TBS_VAL) +/** + * @brief Generic Telephone Bearer Service UUID value + */ +#define BT_UUID_GTBS_VAL 0x184c +/** + * @brief Generic Telephone Bearer Service + */ +#define BT_UUID_GTBS BT_UUID_DECLARE_16(BT_UUID_GTBS_VAL) +/** + * @brief Microphone Control Service UUID value + */ +#define BT_UUID_MICS_VAL 0x184d +/** + * @brief Microphone Control Service + */ +#define BT_UUID_MICS BT_UUID_DECLARE_16(BT_UUID_MICS_VAL) +/** + * @brief Audio Stream Control Service UUID value + */ +#define BT_UUID_ASCS_VAL 0x184e +/** + * @brief Audio Stream Control Service + */ +#define BT_UUID_ASCS BT_UUID_DECLARE_16(BT_UUID_ASCS_VAL) +/** + * @brief Broadcast Audio Scan Service UUID value + */ +#define BT_UUID_BASS_VAL 0x184f +/** + * @brief Broadcast Audio Scan Service + */ +#define BT_UUID_BASS BT_UUID_DECLARE_16(BT_UUID_BASS_VAL) +/** + * @brief Published Audio Capabilities Service UUID value + */ +#define BT_UUID_PACS_VAL 0x1850 +/** + * @brief Published Audio Capabilities Service + */ +#define BT_UUID_PACS BT_UUID_DECLARE_16(BT_UUID_PACS_VAL) +/** + * @brief Basic Audio Announcement Service UUID value + */ +#define BT_UUID_BASIC_AUDIO_VAL 0x1851 +/** + * @brief Basic Audio Announcement Service + */ +#define BT_UUID_BASIC_AUDIO BT_UUID_DECLARE_16(BT_UUID_BASIC_AUDIO_VAL) +/** + * @brief Broadcast Audio Announcement Service UUID value + */ +#define BT_UUID_BROADCAST_AUDIO_VAL 0x1852 +/** + * @brief Broadcast Audio Announcement Service + */ +#define BT_UUID_BROADCAST_AUDIO BT_UUID_DECLARE_16(BT_UUID_BROADCAST_AUDIO_VAL) +/** + * @brief Common Audio Service UUID value + */ +#define BT_UUID_CAS_VAL 0x1853 +/** + * @brief Common Audio Service + */ +#define BT_UUID_CAS BT_UUID_DECLARE_16(BT_UUID_CAS_VAL) +/** + * @brief Hearing Access Service UUID value + */ +#define BT_UUID_HAS_VAL 0x1854 +/** + * @brief Hearing Access Service + */ +#define BT_UUID_HAS BT_UUID_DECLARE_16(BT_UUID_HAS_VAL) +/** + * @brief Telephony and Media Audio Service UUID value + */ +#define BT_UUID_TMAS_VAL 0x1855 +/** + * @brief Telephony and Media Audio Service + */ +#define BT_UUID_TMAS BT_UUID_DECLARE_16(BT_UUID_TMAS_VAL) +/** + * @brief Public Broadcast Announcement Service UUID value + */ +#define BT_UUID_PBA_VAL 0x1856 +/** + * @brief Public Broadcast Announcement Service + */ +#define BT_UUID_PBA BT_UUID_DECLARE_16(BT_UUID_PBA_VAL) +/** + * @brief GATT Primary Service UUID value + */ +#define BT_UUID_GATT_PRIMARY_VAL 0x2800 +/** + * @brief GATT Primary Service + */ +#define BT_UUID_GATT_PRIMARY BT_UUID_DECLARE_16(BT_UUID_GATT_PRIMARY_VAL) +/** + * @brief GATT Secondary Service UUID value + */ +#define BT_UUID_GATT_SECONDARY_VAL 0x2801 +/** + * @brief GATT Secondary Service + */ +#define BT_UUID_GATT_SECONDARY BT_UUID_DECLARE_16(BT_UUID_GATT_SECONDARY_VAL) +/** + * @brief GATT Include Service UUID value + */ +#define BT_UUID_GATT_INCLUDE_VAL 0x2802 +/** + * @brief GATT Include Service + */ +#define BT_UUID_GATT_INCLUDE BT_UUID_DECLARE_16(BT_UUID_GATT_INCLUDE_VAL) +/** + * @brief GATT Characteristic UUID value + */ +#define BT_UUID_GATT_CHRC_VAL 0x2803 +/** + * @brief GATT Characteristic + */ +#define BT_UUID_GATT_CHRC BT_UUID_DECLARE_16(BT_UUID_GATT_CHRC_VAL) +/** + * @brief GATT Characteristic Extended Properties UUID value + */ +#define BT_UUID_GATT_CEP_VAL 0x2900 +/** + * @brief GATT Characteristic Extended Properties + */ +#define BT_UUID_GATT_CEP BT_UUID_DECLARE_16(BT_UUID_GATT_CEP_VAL) +/** + * @brief GATT Characteristic User Description UUID value + */ +#define BT_UUID_GATT_CUD_VAL 0x2901 +/** + * @brief GATT Characteristic User Description + */ +#define BT_UUID_GATT_CUD BT_UUID_DECLARE_16(BT_UUID_GATT_CUD_VAL) +/** + * @brief GATT Client Characteristic Configuration UUID value + */ +#define BT_UUID_GATT_CCC_VAL 0x2902 +/** + * @brief GATT Client Characteristic Configuration + */ +#define BT_UUID_GATT_CCC BT_UUID_DECLARE_16(BT_UUID_GATT_CCC_VAL) +/** + * @brief GATT Server Characteristic Configuration UUID value + */ +#define BT_UUID_GATT_SCC_VAL 0x2903 +/** + * @brief GATT Server Characteristic Configuration + */ +#define BT_UUID_GATT_SCC BT_UUID_DECLARE_16(BT_UUID_GATT_SCC_VAL) +/** + * @brief GATT Characteristic Presentation Format UUID value + */ +#define BT_UUID_GATT_CPF_VAL 0x2904 +/** + * @brief GATT Characteristic Presentation Format + */ +#define BT_UUID_GATT_CPF BT_UUID_DECLARE_16(BT_UUID_GATT_CPF_VAL) +/** + * @brief GATT Characteristic Aggregated Format UUID value + */ +#define BT_UUID_GATT_CAF_VAL 0x2905 +/** + * @brief GATT Characteristic Aggregated Format + */ +#define BT_UUID_GATT_CAF BT_UUID_DECLARE_16(BT_UUID_GATT_CAF_VAL) +/** + * @brief Valid Range Descriptor UUID value + */ +#define BT_UUID_VALID_RANGE_VAL 0x2906 +/** + * @brief Valid Range Descriptor + */ +#define BT_UUID_VALID_RANGE BT_UUID_DECLARE_16(BT_UUID_VALID_RANGE_VAL) +/** + * @brief HID External Report Descriptor UUID value + */ +#define BT_UUID_HIDS_EXT_REPORT_VAL 0x2907 +/** + * @brief HID External Report Descriptor + */ +#define BT_UUID_HIDS_EXT_REPORT BT_UUID_DECLARE_16(BT_UUID_HIDS_EXT_REPORT_VAL) +/** + * @brief HID Report Reference Descriptor UUID value + */ +#define BT_UUID_HIDS_REPORT_REF_VAL 0x2908 +/** + * @brief HID Report Reference Descriptor + */ +#define BT_UUID_HIDS_REPORT_REF BT_UUID_DECLARE_16(BT_UUID_HIDS_REPORT_REF_VAL) +/** + * @brief Value Trigger Setting Descriptor UUID value + */ +#define BT_UUID_VAL_TRIGGER_SETTING_VAL 0x290a +/** + * @brief Value Trigger Setting Descriptor + */ +#define BT_UUID_VAL_TRIGGER_SETTING BT_UUID_DECLARE_16(BT_UUID_VAL_TRIGGER_SETTING_VAL) +/** + * @brief Environmental Sensing Configuration Descriptor UUID value + */ +#define BT_UUID_ES_CONFIGURATION_VAL 0x290b +/** + * @brief Environmental Sensing Configuration Descriptor + */ +#define BT_UUID_ES_CONFIGURATION BT_UUID_DECLARE_16(BT_UUID_ES_CONFIGURATION_VAL) +/** + * @brief Environmental Sensing Measurement Descriptor UUID value + */ +#define BT_UUID_ES_MEASUREMENT_VAL 0x290c +/** + * @brief Environmental Sensing Measurement Descriptor + */ +#define BT_UUID_ES_MEASUREMENT BT_UUID_DECLARE_16(BT_UUID_ES_MEASUREMENT_VAL) +/** + * @brief Environmental Sensing Trigger Setting Descriptor UUID value + */ +#define BT_UUID_ES_TRIGGER_SETTING_VAL 0x290d +/** + * @brief Environmental Sensing Trigger Setting Descriptor + */ +#define BT_UUID_ES_TRIGGER_SETTING BT_UUID_DECLARE_16(BT_UUID_ES_TRIGGER_SETTING_VAL) +/** + * @brief Time Trigger Setting Descriptor UUID value + */ +#define BT_UUID_TM_TRIGGER_SETTING_VAL 0x290e +/** + * @brief Time Trigger Setting Descriptor + */ +#define BT_UUID_TM_TRIGGER_SETTING BT_UUID_DECLARE_16(BT_UUID_TM_TRIGGER_SETTING_VAL) +/** + * @brief GAP Characteristic Device Name UUID value + */ +#define BT_UUID_GAP_DEVICE_NAME_VAL 0x2a00 +/** + * @brief GAP Characteristic Device Name + */ +#define BT_UUID_GAP_DEVICE_NAME BT_UUID_DECLARE_16(BT_UUID_GAP_DEVICE_NAME_VAL) +/** + * @brief GAP Characteristic Appearance UUID value + */ +#define BT_UUID_GAP_APPEARANCE_VAL 0x2a01 +/** + * @brief GAP Characteristic Appearance + */ +#define BT_UUID_GAP_APPEARANCE BT_UUID_DECLARE_16(BT_UUID_GAP_APPEARANCE_VAL) +/** + * @brief GAP Characteristic Peripheral Privacy Flag UUID value + */ +#define BT_UUID_GAP_PPF_VAL 0x2a02 +/** + * @brief GAP Characteristic Peripheral Privacy Flag + */ +#define BT_UUID_GAP_PPF BT_UUID_DECLARE_16(BT_UUID_GAP_PPF_VAL) +/** + * @brief GAP Characteristic Reconnection Address UUID value + */ +#define BT_UUID_GAP_RA_VAL 0x2a03 +/** + * @brief GAP Characteristic Reconnection Address + */ +#define BT_UUID_GAP_RA BT_UUID_DECLARE_16(BT_UUID_GAP_RA_VAL) +/** + * @brief GAP Characteristic Peripheral Preferred Connection Parameters UUID + * value + */ +#define BT_UUID_GAP_PPCP_VAL 0x2a04 +/** + * @brief GAP Characteristic Peripheral Preferred Connection Parameters + */ +#define BT_UUID_GAP_PPCP BT_UUID_DECLARE_16(BT_UUID_GAP_PPCP_VAL) +/** + * @brief GATT Characteristic Service Changed UUID value + */ +#define BT_UUID_GATT_SC_VAL 0x2a05 +/** + * @brief GATT Characteristic Service Changed + */ +#define BT_UUID_GATT_SC BT_UUID_DECLARE_16(BT_UUID_GATT_SC_VAL) +/** + * @brief GATT Characteristic Alert Level UUID value + */ +#define BT_UUID_ALERT_LEVEL_VAL 0x2a06 +/** + * @brief GATT Characteristic Alert Level + */ +#define BT_UUID_ALERT_LEVEL BT_UUID_DECLARE_16(BT_UUID_ALERT_LEVEL_VAL) +/** + * @brief TPS Characteristic Tx Power Level UUID value + */ +#define BT_UUID_TPS_TX_POWER_LEVEL_VAL 0x2a07 +/** + * @brief TPS Characteristic Tx Power Level + */ +#define BT_UUID_TPS_TX_POWER_LEVEL BT_UUID_DECLARE_16(BT_UUID_TPS_TX_POWER_LEVEL_VAL) +/** + * @brief GATT Characteristic Date Time UUID value + */ +#define BT_UUID_GATT_DT_VAL 0x2a08 +/** + * @brief GATT Characteristic Date Time + */ +#define BT_UUID_GATT_DT BT_UUID_DECLARE_16(BT_UUID_GATT_DT_VAL) +/** + * @brief GATT Characteristic Day of Week UUID value + */ +#define BT_UUID_GATT_DW_VAL 0x2a09 +/** + * @brief GATT Characteristic Day of Week + */ +#define BT_UUID_GATT_DW BT_UUID_DECLARE_16(BT_UUID_GATT_DW_VAL) +/** + * @brief GATT Characteristic Day Date Time UUID value + */ +#define BT_UUID_GATT_DDT_VAL 0x2a0a +/** + * @brief GATT Characteristic Day Date Time + */ +#define BT_UUID_GATT_DDT BT_UUID_DECLARE_16(BT_UUID_GATT_DDT_VAL) +/** + * @brief GATT Characteristic Exact Time 256 UUID value + */ +#define BT_UUID_GATT_ET256_VAL 0x2a0c +/** + * @brief GATT Characteristic Exact Time 256 + */ +#define BT_UUID_GATT_ET256 BT_UUID_DECLARE_16(BT_UUID_GATT_ET256_VAL) +/** + * @brief GATT Characteristic DST Offset UUID value + */ +#define BT_UUID_GATT_DST_VAL 0x2a0d +/** + * @brief GATT Characteristic DST Offset + */ +#define BT_UUID_GATT_DST BT_UUID_DECLARE_16(BT_UUID_GATT_DST_VAL) +/** + * @brief GATT Characteristic Time Zone UUID value + */ +#define BT_UUID_GATT_TZ_VAL 0x2a0e +/** + * @brief GATT Characteristic Time Zone + */ +#define BT_UUID_GATT_TZ BT_UUID_DECLARE_16(BT_UUID_GATT_TZ_VAL) +/** + * @brief GATT Characteristic Local Time Information UUID value + */ +#define BT_UUID_GATT_LTI_VAL 0x2a0f +/** + * @brief GATT Characteristic Local Time Information + */ +#define BT_UUID_GATT_LTI BT_UUID_DECLARE_16(BT_UUID_GATT_LTI_VAL) +/** + * @brief GATT Characteristic Time with DST UUID value + */ +#define BT_UUID_GATT_TDST_VAL 0x2a11 +/** + * @brief GATT Characteristic Time with DST + */ +#define BT_UUID_GATT_TDST BT_UUID_DECLARE_16(BT_UUID_GATT_TDST_VAL) +/** + * @brief GATT Characteristic Time Accuracy UUID value + */ +#define BT_UUID_GATT_TA_VAL 0x2a12 +/** + * @brief GATT Characteristic Time Accuracy + */ +#define BT_UUID_GATT_TA BT_UUID_DECLARE_16(BT_UUID_GATT_TA_VAL) +/** + * @brief GATT Characteristic Time Source UUID value + */ +#define BT_UUID_GATT_TS_VAL 0x2a13 +/** + * @brief GATT Characteristic Time Source + */ +#define BT_UUID_GATT_TS BT_UUID_DECLARE_16(BT_UUID_GATT_TS_VAL) +/** + * @brief GATT Characteristic Reference Time Information UUID value + */ +#define BT_UUID_GATT_RTI_VAL 0x2a14 +/** + * @brief GATT Characteristic Reference Time Information + */ +#define BT_UUID_GATT_RTI BT_UUID_DECLARE_16(BT_UUID_GATT_RTI_VAL) +/** + * @brief GATT Characteristic Time Update Control Point UUID value + */ +#define BT_UUID_GATT_TUCP_VAL 0x2a16 +/** + * @brief GATT Characteristic Time Update Control Point + */ +#define BT_UUID_GATT_TUCP BT_UUID_DECLARE_16(BT_UUID_GATT_TUCP_VAL) +/** + * @brief GATT Characteristic Time Update State UUID value + */ +#define BT_UUID_GATT_TUS_VAL 0x2a17 +/** + * @brief GATT Characteristic Time Update State + */ +#define BT_UUID_GATT_TUS BT_UUID_DECLARE_16(BT_UUID_GATT_TUS_VAL) +/** + * @brief GATT Characteristic Glucose Measurement UUID value + */ +#define BT_UUID_GATT_GM_VAL 0x2a18 +/** + * @brief GATT Characteristic Glucose Measurement + */ +#define BT_UUID_GATT_GM BT_UUID_DECLARE_16(BT_UUID_GATT_GM_VAL) +/** + * @brief BAS Characteristic Battery Level UUID value + */ +#define BT_UUID_BAS_BATTERY_LEVEL_VAL 0x2a19 +/** + * @brief BAS Characteristic Battery Level + */ +#define BT_UUID_BAS_BATTERY_LEVEL BT_UUID_DECLARE_16(BT_UUID_BAS_BATTERY_LEVEL_VAL) +/** + * @brief BAS Characteristic Battery Power State UUID value + */ +#define BT_UUID_BAS_BATTERY_POWER_STATE_VAL 0x2a1a +/** + * @brief BAS Characteristic Battery Power State + */ +#define BT_UUID_BAS_BATTERY_POWER_STATE BT_UUID_DECLARE_16(BT_UUID_BAS_BATTERY_POWER_STATE_VAL) +/** + * @brief BAS Characteristic Battery Level StateUUID value + */ +#define BT_UUID_BAS_BATTERY_LEVEL_STATE_VAL 0x2a1b +/** + * @brief BAS Characteristic Battery Level State + */ +#define BT_UUID_BAS_BATTERY_LEVEL_STATE BT_UUID_DECLARE_16(BT_UUID_BAS_BATTERY_LEVEL_STATE_VAL) +/** + * @brief HTS Characteristic Temperature Measurement UUID value + */ +#define BT_UUID_HTS_MEASUREMENT_VAL 0x2a1c +/** + * @brief HTS Characteristic Temperature Measurement Value + */ +#define BT_UUID_HTS_MEASUREMENT BT_UUID_DECLARE_16(BT_UUID_HTS_MEASUREMENT_VAL) +/** + * @brief HTS Characteristic Temperature Type UUID value + */ +#define BT_UUID_HTS_TEMP_TYP_VAL 0x2a1d +/** + * @brief HTS Characteristic Temperature Type + */ +#define BT_UUID_HTS_TEMP_TYP BT_UUID_DECLARE_16(BT_UUID_HTS_TEMP_TYP_VAL) +/** + * @brief HTS Characteristic Intermediate Temperature UUID value + */ +#define BT_UUID_HTS_TEMP_INT_VAL 0x2a1e +/** + * @brief HTS Characteristic Intermediate Temperature + */ +#define BT_UUID_HTS_TEMP_INT BT_UUID_DECLARE_16(BT_UUID_HTS_TEMP_INT_VAL) +/** + * @brief HTS Characteristic Temperature Celsius UUID value + */ +#define BT_UUID_HTS_TEMP_C_VAL 0x2a1f +/** + * @brief HTS Characteristic Temperature Celsius + */ +#define BT_UUID_HTS_TEMP_C BT_UUID_DECLARE_16(BT_UUID_HTS_TEMP_C_VAL) +/** + * @brief HTS Characteristic Temperature Fahrenheit UUID value + */ +#define BT_UUID_HTS_TEMP_F_VAL 0x2a20 +/** + * @brief HTS Characteristic Temperature Fahrenheit + */ +#define BT_UUID_HTS_TEMP_F BT_UUID_DECLARE_16(BT_UUID_HTS_TEMP_F_VAL) +/** + * @brief HTS Characteristic Measurement Interval UUID value + */ +#define BT_UUID_HTS_INTERVAL_VAL 0x2a21 +/** + * @brief HTS Characteristic Measurement Interval + */ +#define BT_UUID_HTS_INTERVAL BT_UUID_DECLARE_16(BT_UUID_HTS_INTERVAL_VAL) +/** + * @brief HID Characteristic Boot Keyboard Input Report UUID value + */ +#define BT_UUID_HIDS_BOOT_KB_IN_REPORT_VAL 0x2a22 +/** + * @brief HID Characteristic Boot Keyboard Input Report + */ +#define BT_UUID_HIDS_BOOT_KB_IN_REPORT BT_UUID_DECLARE_16(BT_UUID_HIDS_BOOT_KB_IN_REPORT_VAL) +/** + * @brief DIS Characteristic System ID UUID value + */ +#define BT_UUID_DIS_SYSTEM_ID_VAL 0x2a23 +/** + * @brief DIS Characteristic System ID + */ +#define BT_UUID_DIS_SYSTEM_ID BT_UUID_DECLARE_16(BT_UUID_DIS_SYSTEM_ID_VAL) +/** + * @brief DIS Characteristic Model Number String UUID value + */ +#define BT_UUID_DIS_MODEL_NUMBER_VAL 0x2a24 +/** + * @brief DIS Characteristic Model Number String + */ +#define BT_UUID_DIS_MODEL_NUMBER BT_UUID_DECLARE_16(BT_UUID_DIS_MODEL_NUMBER_VAL) +/** + * @brief DIS Characteristic Serial Number String UUID value + */ +#define BT_UUID_DIS_SERIAL_NUMBER_VAL 0x2a25 +/** + * @brief DIS Characteristic Serial Number String + */ +#define BT_UUID_DIS_SERIAL_NUMBER BT_UUID_DECLARE_16(BT_UUID_DIS_SERIAL_NUMBER_VAL) +/** + * @brief DIS Characteristic Firmware Revision String UUID value + */ +#define BT_UUID_DIS_FIRMWARE_REVISION_VAL 0x2a26 +/** + * @brief DIS Characteristic Firmware Revision String + */ +#define BT_UUID_DIS_FIRMWARE_REVISION BT_UUID_DECLARE_16(BT_UUID_DIS_FIRMWARE_REVISION_VAL) +/** + * @brief DIS Characteristic Hardware Revision String UUID value + */ +#define BT_UUID_DIS_HARDWARE_REVISION_VAL 0x2a27 +/** + * @brief DIS Characteristic Hardware Revision String + */ +#define BT_UUID_DIS_HARDWARE_REVISION BT_UUID_DECLARE_16(BT_UUID_DIS_HARDWARE_REVISION_VAL) +/** + * @brief DIS Characteristic Software Revision String UUID value + */ +#define BT_UUID_DIS_SOFTWARE_REVISION_VAL 0x2a28 +/** + * @brief DIS Characteristic Software Revision String + */ +#define BT_UUID_DIS_SOFTWARE_REVISION BT_UUID_DECLARE_16(BT_UUID_DIS_SOFTWARE_REVISION_VAL) +/** + * @brief DIS Characteristic Manufacturer Name String UUID Value + */ +#define BT_UUID_DIS_MANUFACTURER_NAME_VAL 0x2a29 +/** + * @brief DIS Characteristic Manufacturer Name String + */ +#define BT_UUID_DIS_MANUFACTURER_NAME BT_UUID_DECLARE_16(BT_UUID_DIS_MANUFACTURER_NAME_VAL) +/** + * @brief GATT Characteristic IEEE Regulatory Certification Data List UUID + * Value + */ +#define BT_UUID_GATT_IEEE_RCDL_VAL 0x2a2a +/** + * @brief GATT Characteristic IEEE Regulatory Certification Data List + */ +#define BT_UUID_GATT_IEEE_RCDL BT_UUID_DECLARE_16(BT_UUID_GATT_IEEE_RCDL_VAL) +/** + * @brief CTS Characteristic Current Time UUID value + */ +#define BT_UUID_CTS_CURRENT_TIME_VAL 0x2a2b +/** + * @brief CTS Characteristic Current Time + */ +#define BT_UUID_CTS_CURRENT_TIME BT_UUID_DECLARE_16(BT_UUID_CTS_CURRENT_TIME_VAL) +/** + * @brief Magnetic Declination Characteristic UUID value + */ +#define BT_UUID_MAGN_DECLINATION_VAL 0x2a2c +/** + * @brief Magnetic Declination Characteristic + */ +#define BT_UUID_MAGN_DECLINATION BT_UUID_DECLARE_16(BT_UUID_MAGN_DECLINATION_VAL) +/** + * @brief GATT Characteristic Legacy Latitude UUID Value + */ +#define BT_UUID_GATT_LLAT_VAL 0x2a2d +/** + * @brief GATT Characteristic Legacy Latitude + */ +#define BT_UUID_GATT_LLAT BT_UUID_DECLARE_16(BT_UUID_GATT_LLAT_VAL) +/** + * @brief GATT Characteristic Legacy Longitude UUID Value + */ +#define BT_UUID_GATT_LLON_VAL 0x2a2e +/** + * @brief GATT Characteristic Legacy Longitude + */ +#define BT_UUID_GATT_LLON BT_UUID_DECLARE_16(BT_UUID_GATT_LLON_VAL) +/** + * @brief GATT Characteristic Position 2D UUID Value + */ +#define BT_UUID_GATT_POS_2D_VAL 0x2a2f +/** + * @brief GATT Characteristic Position 2D + */ +#define BT_UUID_GATT_POS_2D BT_UUID_DECLARE_16(BT_UUID_GATT_POS_2D_VAL) +/** + * @brief GATT Characteristic Position 3D UUID Value + */ +#define BT_UUID_GATT_POS_3D_VAL 0x2a30 +/** + * @brief GATT Characteristic Position 3D + */ +#define BT_UUID_GATT_POS_3D BT_UUID_DECLARE_16(BT_UUID_GATT_POS_3D_VAL) +/** + * @brief GATT Characteristic Scan Refresh UUID Value + */ +#define BT_UUID_GATT_SR_VAL 0x2a31 +/** + * @brief GATT Characteristic Scan Refresh + */ +#define BT_UUID_GATT_SR BT_UUID_DECLARE_16(BT_UUID_GATT_SR_VAL) +/** + * @brief HID Boot Keyboard Output Report Characteristic UUID value + */ +#define BT_UUID_HIDS_BOOT_KB_OUT_REPORT_VAL 0x2a32 +/** + * @brief HID Boot Keyboard Output Report Characteristic + */ +#define BT_UUID_HIDS_BOOT_KB_OUT_REPORT BT_UUID_DECLARE_16(BT_UUID_HIDS_BOOT_KB_OUT_REPORT_VAL) +/** + * @brief HID Boot Mouse Input Report Characteristic UUID value + */ +#define BT_UUID_HIDS_BOOT_MOUSE_IN_REPORT_VAL 0x2a33 +/** + * @brief HID Boot Mouse Input Report Characteristic + */ +#define BT_UUID_HIDS_BOOT_MOUSE_IN_REPORT BT_UUID_DECLARE_16(BT_UUID_HIDS_BOOT_MOUSE_IN_REPORT_VAL) +/** + * @brief GATT Characteristic Glucose Measurement Context UUID Value + */ +#define BT_UUID_GATT_GMC_VAL 0x2a34 +/** + * @brief GATT Characteristic Glucose Measurement Context + */ +#define BT_UUID_GATT_GMC BT_UUID_DECLARE_16(BT_UUID_GATT_GMC_VAL) +/** + * @brief GATT Characteristic Blood Pressure Measurement UUID Value + */ +#define BT_UUID_GATT_BPM_VAL 0x2a35 +/** + * @brief GATT Characteristic Blood Pressure Measurement + */ +#define BT_UUID_GATT_BPM BT_UUID_DECLARE_16(BT_UUID_GATT_BPM_VAL) +/** + * @brief GATT Characteristic Intermediate Cuff Pressure UUID Value + */ +#define BT_UUID_GATT_ICP_VAL 0x2a36 +/** + * @brief GATT Characteristic Intermediate Cuff Pressure + */ +#define BT_UUID_GATT_ICP BT_UUID_DECLARE_16(BT_UUID_GATT_ICP_VAL) +/** + * @brief HRS Characteristic Measurement Interval UUID value + */ +#define BT_UUID_HRS_MEASUREMENT_VAL 0x2a37 +/** + * @brief HRS Characteristic Measurement Interval + */ +#define BT_UUID_HRS_MEASUREMENT BT_UUID_DECLARE_16(BT_UUID_HRS_MEASUREMENT_VAL) +/** + * @brief HRS Characteristic Body Sensor Location + */ +#define BT_UUID_HRS_BODY_SENSOR_VAL 0x2a38 +/** + * @brief HRS Characteristic Body Sensor Location + */ +#define BT_UUID_HRS_BODY_SENSOR BT_UUID_DECLARE_16(BT_UUID_HRS_BODY_SENSOR_VAL) +/** + * @brief HRS Characteristic Control Point UUID value + */ +#define BT_UUID_HRS_CONTROL_POINT_VAL 0x2a39 +/** + * @brief HRS Characteristic Control Point + */ +#define BT_UUID_HRS_CONTROL_POINT BT_UUID_DECLARE_16(BT_UUID_HRS_CONTROL_POINT_VAL) +/** + * @brief GATT Characteristic Removable UUID Value + */ +#define BT_UUID_GATT_REM_VAL 0x2a3a +/** + * @brief GATT Characteristic Removable + */ +#define BT_UUID_GATT_REM BT_UUID_DECLARE_16(BT_UUID_GATT_REM_VAL) +/** + * @brief GATT Characteristic Service Required UUID Value + */ +#define BT_UUID_GATT_SRVREQ_VAL 0x2a3b +/** + * @brief GATT Characteristic Service Required + */ +#define BT_UUID_GATT_SRVREQ BT_UUID_DECLARE_16(BT_UUID_GATT_SRVREQ_VAL) +/** + * @brief GATT Characteristic Scientific Temperature in Celsius UUID Value + */ +#define BT_UUID_GATT_SC_TEMP_C_VAL 0x2a3c +/** + * @brief GATT Characteristic Scientific Temperature in Celsius + */ +#define BT_UUID_GATT_SC_TEMP_C BT_UUID_DECLARE_16(BT_UUID_GATT_SC_TEMP_C_VAL) +/** + * @brief GATT Characteristic String UUID Value + */ +#define BT_UUID_GATT_STRING_VAL 0x2a3d +/** + * @brief GATT Characteristic String + */ +#define BT_UUID_GATT_STRING BT_UUID_DECLARE_16(BT_UUID_GATT_STRING_VAL) +/** + * @brief GATT Characteristic Network Availability UUID Value + */ +#define BT_UUID_GATT_NETA_VAL 0x2a3e +/** + * @brief GATT Characteristic Network Availability + */ +#define BT_UUID_GATT_NETA BT_UUID_DECLARE_16(BT_UUID_GATT_NETA_VAL) +/** + * @brief GATT Characteristic Alert Status UUID Value + */ +#define BT_UUID_GATT_ALRTS_VAL 0x2a3f +/** + * @brief GATT Characteristic Alert Status + */ +#define BT_UUID_GATT_ALRTS BT_UUID_DECLARE_16(BT_UUID_GATT_ALRTS_VAL) +/** + * @brief GATT Characteristic Ringer Control Point UUID Value + */ +#define BT_UUID_GATT_RCP_VAL 0x2a40 +/** + * @brief GATT Characteristic Ringer Control Point + */ +#define BT_UUID_GATT_RCP BT_UUID_DECLARE_16(BT_UUID_GATT_RCP_VAL) +/** + * @brief GATT Characteristic Ringer Setting UUID Value + */ +#define BT_UUID_GATT_RS_VAL 0x2a41 +/** + * @brief GATT Characteristic Ringer Setting + */ +#define BT_UUID_GATT_RS BT_UUID_DECLARE_16(BT_UUID_GATT_RS_VAL) +/** + * @brief GATT Characteristic Alert Category ID Bit Mask UUID Value + */ +#define BT_UUID_GATT_ALRTCID_MASK_VAL 0x2a42 +/** + * @brief GATT Characteristic Alert Category ID Bit Mask + */ +#define BT_UUID_GATT_ALRTCID_MASK BT_UUID_DECLARE_16(BT_UUID_GATT_ALRTCID_MASK_VAL) +/** + * @brief GATT Characteristic Alert Category ID UUID Value + */ +#define BT_UUID_GATT_ALRTCID_VAL 0x2a43 +/** + * @brief GATT Characteristic Alert Category ID + */ +#define BT_UUID_GATT_ALRTCID BT_UUID_DECLARE_16(BT_UUID_GATT_ALRTCID_VAL) +/** + * @brief GATT Characteristic Alert Notification Control Point Value + */ +#define BT_UUID_GATT_ALRTNCP_VAL 0x2a44 +/** + * @brief GATT Characteristic Alert Notification Control Point + */ +#define BT_UUID_GATT_ALRTNCP BT_UUID_DECLARE_16(BT_UUID_GATT_ALRTNCP_VAL) +/** + * @brief GATT Characteristic Unread Alert Status UUID Value + */ +#define BT_UUID_GATT_UALRTS_VAL 0x2a45 +/** + * @brief GATT Characteristic Unread Alert Status + */ +#define BT_UUID_GATT_UALRTS BT_UUID_DECLARE_16(BT_UUID_GATT_UALRTS_VAL) +/** + * @brief GATT Characteristic New Alert UUID Value + */ +#define BT_UUID_GATT_NALRT_VAL 0x2a46 +/** + * @brief GATT Characteristic New Alert + */ +#define BT_UUID_GATT_NALRT BT_UUID_DECLARE_16(BT_UUID_GATT_NALRT_VAL) +/** + * @brief GATT Characteristic Supported New Alert Category UUID Value + */ +#define BT_UUID_GATT_SNALRTC_VAL 0x2a47 +/** + * @brief GATT Characteristic Supported New Alert Category + */ +#define BT_UUID_GATT_SNALRTC BT_UUID_DECLARE_16(BT_UUID_GATT_SNALRTC_VAL) +/** + * @brief GATT Characteristic Supported Unread Alert Category UUID Value + */ +#define BT_UUID_GATT_SUALRTC_VAL 0x2a48 +/** + * @brief GATT Characteristic Supported Unread Alert Category + */ +#define BT_UUID_GATT_SUALRTC BT_UUID_DECLARE_16(BT_UUID_GATT_SUALRTC_VAL) +/** + * @brief GATT Characteristic Blood Pressure Feature UUID Value + */ +#define BT_UUID_GATT_BPF_VAL 0x2a49 +/** + * @brief GATT Characteristic Blood Pressure Feature + */ +#define BT_UUID_GATT_BPF BT_UUID_DECLARE_16(BT_UUID_GATT_BPF_VAL) +/** + * @brief HID Information Characteristic UUID value + */ +#define BT_UUID_HIDS_INFO_VAL 0x2a4a +/** + * @brief HID Information Characteristic + */ +#define BT_UUID_HIDS_INFO BT_UUID_DECLARE_16(BT_UUID_HIDS_INFO_VAL) +/** + * @brief HID Report Map Characteristic UUID value + */ +#define BT_UUID_HIDS_REPORT_MAP_VAL 0x2a4b +/** + * @brief HID Report Map Characteristic + */ +#define BT_UUID_HIDS_REPORT_MAP BT_UUID_DECLARE_16(BT_UUID_HIDS_REPORT_MAP_VAL) +/** + * @brief HID Control Point Characteristic UUID value + */ +#define BT_UUID_HIDS_CTRL_POINT_VAL 0x2a4c +/** + * @brief HID Control Point Characteristic + */ +#define BT_UUID_HIDS_CTRL_POINT BT_UUID_DECLARE_16(BT_UUID_HIDS_CTRL_POINT_VAL) +/** + * @brief HID Report Characteristic UUID value + */ +#define BT_UUID_HIDS_REPORT_VAL 0x2a4d +/** + * @brief HID Report Characteristic + */ +#define BT_UUID_HIDS_REPORT BT_UUID_DECLARE_16(BT_UUID_HIDS_REPORT_VAL) +/** + * @brief HID Protocol Mode Characteristic UUID value + */ +#define BT_UUID_HIDS_PROTOCOL_MODE_VAL 0x2a4e +/** + * @brief HID Protocol Mode Characteristic + */ +#define BT_UUID_HIDS_PROTOCOL_MODE BT_UUID_DECLARE_16(BT_UUID_HIDS_PROTOCOL_MODE_VAL) +/** + * @brief GATT Characteristic Scan Interval Windows UUID Value + */ +#define BT_UUID_GATT_SIW_VAL 0x2a4f +/** + * @brief GATT Characteristic Scan Interval Windows + */ +#define BT_UUID_GATT_SIW BT_UUID_DECLARE_16(BT_UUID_GATT_SIW_VAL) +/** + * @brief DIS Characteristic PnP ID UUID value + */ +#define BT_UUID_DIS_PNP_ID_VAL 0x2a50 +/** + * @brief DIS Characteristic PnP ID + */ +#define BT_UUID_DIS_PNP_ID BT_UUID_DECLARE_16(BT_UUID_DIS_PNP_ID_VAL) +/** + * @brief GATT Characteristic Glucose Feature UUID Value + */ +#define BT_UUID_GATT_GF_VAL 0x2a51 +/** + * @brief GATT Characteristic Glucose Feature + */ +#define BT_UUID_GATT_GF BT_UUID_DECLARE_16(BT_UUID_GATT_GF_VAL) +/** + * @brief Record Access Control Point Characteristic value + */ +#define BT_UUID_RECORD_ACCESS_CONTROL_POINT_VAL 0x2a52 +/** + * @brief Record Access Control Point + */ +#define BT_UUID_RECORD_ACCESS_CONTROL_POINT \ +BT_UUID_DECLARE_16(BT_UUID_RECORD_ACCESS_CONTROL_POINT_VAL) +/** + * @brief RSC Measurement Characteristic UUID value + */ +#define BT_UUID_RSC_MEASUREMENT_VAL 0x2a53 +/** + * @brief RSC Measurement Characteristic + */ +#define BT_UUID_RSC_MEASUREMENT BT_UUID_DECLARE_16(BT_UUID_RSC_MEASUREMENT_VAL) +/** + * @brief RSC Feature Characteristic UUID value + */ +#define BT_UUID_RSC_FEATURE_VAL 0x2a54 +/** + * @brief RSC Feature Characteristic + */ +#define BT_UUID_RSC_FEATURE BT_UUID_DECLARE_16(BT_UUID_RSC_FEATURE_VAL) +/** + * @brief SC Control Point Characteristic UUID value + */ +#define BT_UUID_SC_CONTROL_POINT_VAL 0x2a55 +/** + * @brief SC Control Point Characteristic + */ +#define BT_UUID_SC_CONTROL_POINT BT_UUID_DECLARE_16(BT_UUID_SC_CONTROL_POINT_VAL) +/** + * @brief GATT Characteristic Digital Input UUID Value + */ +#define BT_UUID_GATT_DI_VAL 0x2a56 +/** + * @brief GATT Characteristic Digital Input + */ +#define BT_UUID_GATT_DI BT_UUID_DECLARE_16(BT_UUID_GATT_DI_VAL) +/** + * @brief GATT Characteristic Digital Output UUID Value + */ +#define BT_UUID_GATT_DO_VAL 0x2a57 +/** + * @brief GATT Characteristic Digital Output + */ +#define BT_UUID_GATT_DO BT_UUID_DECLARE_16(BT_UUID_GATT_DO_VAL) +/** + * @brief GATT Characteristic Analog Input UUID Value + */ +#define BT_UUID_GATT_AI_VAL 0x2a58 +/** + * @brief GATT Characteristic Analog Input + */ +#define BT_UUID_GATT_AI BT_UUID_DECLARE_16(BT_UUID_GATT_AI_VAL) +/** + * @brief GATT Characteristic Analog Output UUID Value + */ +#define BT_UUID_GATT_AO_VAL 0x2a59 +/** + * @brief GATT Characteristic Analog Output + */ +#define BT_UUID_GATT_AO BT_UUID_DECLARE_16(BT_UUID_GATT_AO_VAL) +/** + * @brief GATT Characteristic Aggregate UUID Value + */ +#define BT_UUID_GATT_AGGR_VAL 0x2a5a +/** + * @brief GATT Characteristic Aggregate + */ +#define BT_UUID_GATT_AGGR BT_UUID_DECLARE_16(BT_UUID_GATT_AGGR_VAL) +/** + * @brief CSC Measurement Characteristic UUID value + */ +#define BT_UUID_CSC_MEASUREMENT_VAL 0x2a5b +/** + * @brief CSC Measurement Characteristic + */ +#define BT_UUID_CSC_MEASUREMENT BT_UUID_DECLARE_16(BT_UUID_CSC_MEASUREMENT_VAL) +/** + * @brief CSC Feature Characteristic UUID value + */ +#define BT_UUID_CSC_FEATURE_VAL 0x2a5c +/** + * @brief CSC Feature Characteristic + */ +#define BT_UUID_CSC_FEATURE BT_UUID_DECLARE_16(BT_UUID_CSC_FEATURE_VAL) +/** + * @brief Sensor Location Characteristic UUID value + */ +#define BT_UUID_SENSOR_LOCATION_VAL 0x2a5d +/** + * @brief Sensor Location Characteristic + */ +#define BT_UUID_SENSOR_LOCATION BT_UUID_DECLARE_16(BT_UUID_SENSOR_LOCATION_VAL) +/** + * @brief GATT Characteristic PLX Spot-Check Measurement UUID Value + */ +#define BT_UUID_GATT_PLX_SCM_VAL 0x2a5e +/** + * @brief GATT Characteristic PLX Spot-Check Measurement + */ +#define BT_UUID_GATT_PLX_SCM BT_UUID_DECLARE_16(BT_UUID_GATT_PLX_SCM_VAL) +/** + * @brief GATT Characteristic PLX Continuous Measurement UUID Value + */ +#define BT_UUID_GATT_PLX_CM_VAL 0x2a5f +/** + * @brief GATT Characteristic PLX Continuous Measurement + */ +#define BT_UUID_GATT_PLX_CM BT_UUID_DECLARE_16(BT_UUID_GATT_PLX_CM_VAL) +/** + * @brief GATT Characteristic PLX Features UUID Value + */ +#define BT_UUID_GATT_PLX_F_VAL 0x2a60 +/** + * @brief GATT Characteristic PLX Features + */ +#define BT_UUID_GATT_PLX_F BT_UUID_DECLARE_16(BT_UUID_GATT_PLX_F_VAL) +/** + * @brief GATT Characteristic Pulse Oximetry Pulastile Event UUID Value + */ +#define BT_UUID_GATT_POPE_VAL 0x2a61 +/** + * @brief GATT Characteristic Pulse Oximetry Pulsatile Event + */ +#define BT_UUID_GATT_POPE BT_UUID_DECLARE_16(BT_UUID_GATT_POPE_VAL) +/** + * @brief GATT Characteristic Pulse Oximetry Control Point UUID Value + */ +#define BT_UUID_GATT_POCP_VAL 0x2a62 +/** + * @brief GATT Characteristic Pulse Oximetry Control Point + */ +#define BT_UUID_GATT_POCP BT_UUID_DECLARE_16(BT_UUID_GATT_POCP_VAL) +/** + * @brief GATT Characteristic Cycling Power Measurement UUID Value + */ +#define BT_UUID_GATT_CPS_CPM_VAL 0x2a63 +/** + * @brief GATT Characteristic Cycling Power Measurement + */ +#define BT_UUID_GATT_CPS_CPM BT_UUID_DECLARE_16(BT_UUID_GATT_CPS_CPM_VAL) +/** + * @brief GATT Characteristic Cycling Power Vector UUID Value + */ +#define BT_UUID_GATT_CPS_CPV_VAL 0x2a64 +/** + * @brief GATT Characteristic Cycling Power Vector + */ +#define BT_UUID_GATT_CPS_CPV BT_UUID_DECLARE_16(BT_UUID_GATT_CPS_CPV_VAL) +/** + * @brief GATT Characteristic Cycling Power Feature UUID Value + */ +#define BT_UUID_GATT_CPS_CPF_VAL 0x2a65 +/** + * @brief GATT Characteristic Cycling Power Feature + */ +#define BT_UUID_GATT_CPS_CPF BT_UUID_DECLARE_16(BT_UUID_GATT_CPS_CPF_VAL) +/** + * @brief GATT Characteristic Cycling Power Control Point UUID Value + */ +#define BT_UUID_GATT_CPS_CPCP_VAL 0x2a66 +/** + * @brief GATT Characteristic Cycling Power Control Point + */ +#define BT_UUID_GATT_CPS_CPCP BT_UUID_DECLARE_16(BT_UUID_GATT_CPS_CPCP_VAL) +/** + * @brief GATT Characteristic Location and Speed UUID Value + */ +#define BT_UUID_GATT_LOC_SPD_VAL 0x2a67 +/** + * @brief GATT Characteristic Location and Speed + */ +#define BT_UUID_GATT_LOC_SPD BT_UUID_DECLARE_16(BT_UUID_GATT_LOC_SPD_VAL) +/** + * @brief GATT Characteristic Navigation UUID Value + */ +#define BT_UUID_GATT_NAV_VAL 0x2a68 +/** + * @brief GATT Characteristic Navigation + */ +#define BT_UUID_GATT_NAV BT_UUID_DECLARE_16(BT_UUID_GATT_NAV_VAL) +/** + * @brief GATT Characteristic Position Quality UUID Value + */ +#define BT_UUID_GATT_PQ_VAL 0x2a69 +/** + * @brief GATT Characteristic Position Quality + */ +#define BT_UUID_GATT_PQ BT_UUID_DECLARE_16(BT_UUID_GATT_PQ_VAL) +/** + * @brief GATT Characteristic LN Feature UUID Value + */ +#define BT_UUID_GATT_LNF_VAL 0x2a6a +/** + * @brief GATT Characteristic LN Feature + */ +#define BT_UUID_GATT_LNF BT_UUID_DECLARE_16(BT_UUID_GATT_LNF_VAL) +/** + * @brief GATT Characteristic LN Control Point UUID Value + */ +#define BT_UUID_GATT_LNCP_VAL 0x2a6b +/** + * @brief GATT Characteristic LN Control Point + */ +#define BT_UUID_GATT_LNCP BT_UUID_DECLARE_16(BT_UUID_GATT_LNCP_VAL) +/** + * @brief Elevation Characteristic UUID value + */ +#define BT_UUID_ELEVATION_VAL 0x2a6c +/** + * @brief Elevation Characteristic + */ +#define BT_UUID_ELEVATION BT_UUID_DECLARE_16(BT_UUID_ELEVATION_VAL) +/** + * @brief Pressure Characteristic UUID value + */ +#define BT_UUID_PRESSURE_VAL 0x2a6d +/** + * @brief Pressure Characteristic + */ +#define BT_UUID_PRESSURE BT_UUID_DECLARE_16(BT_UUID_PRESSURE_VAL) +/** + * @brief Temperature Characteristic UUID value + */ +#define BT_UUID_TEMPERATURE_VAL 0x2a6e +/** + * @brief Temperature Characteristic + */ +#define BT_UUID_TEMPERATURE BT_UUID_DECLARE_16(BT_UUID_TEMPERATURE_VAL) +/** + * @brief Humidity Characteristic UUID value + */ +#define BT_UUID_HUMIDITY_VAL 0x2a6f +/** + * @brief Humidity Characteristic + */ +#define BT_UUID_HUMIDITY BT_UUID_DECLARE_16(BT_UUID_HUMIDITY_VAL) +/** + * @brief True Wind Speed Characteristic UUID value + */ +#define BT_UUID_TRUE_WIND_SPEED_VAL 0x2a70 +/** + * @brief True Wind Speed Characteristic + */ +#define BT_UUID_TRUE_WIND_SPEED BT_UUID_DECLARE_16(BT_UUID_TRUE_WIND_SPEED_VAL) +/** + * @brief True Wind Direction Characteristic UUID value + */ +#define BT_UUID_TRUE_WIND_DIR_VAL 0x2a71 +/** + * @brief True Wind Direction Characteristic + */ +#define BT_UUID_TRUE_WIND_DIR BT_UUID_DECLARE_16(BT_UUID_TRUE_WIND_DIR_VAL) +/** + * @brief Apparent Wind Speed Characteristic UUID value + */ +#define BT_UUID_APPARENT_WIND_SPEED_VAL 0x2a72 +/** + * @brief Apparent Wind Speed Characteristic + */ +#define BT_UUID_APPARENT_WIND_SPEED BT_UUID_DECLARE_16(BT_UUID_APPARENT_WIND_SPEED_VAL) +/** + * @brief Apparent Wind Direction Characteristic UUID value + */ +#define BT_UUID_APPARENT_WIND_DIR_VAL 0x2a73 +/** + * @brief Apparent Wind Direction Characteristic + */ +#define BT_UUID_APPARENT_WIND_DIR BT_UUID_DECLARE_16(BT_UUID_APPARENT_WIND_DIR_VAL) +/** + * @brief Gust Factor Characteristic UUID value + */ +#define BT_UUID_GUST_FACTOR_VAL 0x2a74 +/** + * @brief Gust Factor Characteristic + */ +#define BT_UUID_GUST_FACTOR BT_UUID_DECLARE_16(BT_UUID_GUST_FACTOR_VAL) +/** + * @brief Pollen Concentration Characteristic UUID value + */ +#define BT_UUID_POLLEN_CONCENTRATION_VAL 0x2a75 +/** + * @brief Pollen Concentration Characteristic + */ +#define BT_UUID_POLLEN_CONCENTRATION BT_UUID_DECLARE_16(BT_UUID_POLLEN_CONCENTRATION_VAL) +/** + * @brief UV Index Characteristic UUID value + */ +#define BT_UUID_UV_INDEX_VAL 0x2a76 +/** + * @brief UV Index Characteristic + */ +#define BT_UUID_UV_INDEX BT_UUID_DECLARE_16(BT_UUID_UV_INDEX_VAL) +/** + * @brief Irradiance Characteristic UUID value + */ +#define BT_UUID_IRRADIANCE_VAL 0x2a77 +/** + * @brief Irradiance Characteristic + */ +#define BT_UUID_IRRADIANCE BT_UUID_DECLARE_16(BT_UUID_IRRADIANCE_VAL) +/** + * @brief Rainfall Characteristic UUID value + */ +#define BT_UUID_RAINFALL_VAL 0x2a78 +/** + * @brief Rainfall Characteristic + */ +#define BT_UUID_RAINFALL BT_UUID_DECLARE_16(BT_UUID_RAINFALL_VAL) +/** + * @brief Wind Chill Characteristic UUID value + */ +#define BT_UUID_WIND_CHILL_VAL 0x2a79 +/** + * @brief Wind Chill Characteristic + */ +#define BT_UUID_WIND_CHILL BT_UUID_DECLARE_16(BT_UUID_WIND_CHILL_VAL) +/** + * @brief Heat Index Characteristic UUID value + */ +#define BT_UUID_HEAT_INDEX_VAL 0x2a7a +/** + * @brief Heat Index Characteristic + */ +#define BT_UUID_HEAT_INDEX BT_UUID_DECLARE_16(BT_UUID_HEAT_INDEX_VAL) +/** + * @brief Dew Point Characteristic UUID value + */ +#define BT_UUID_DEW_POINT_VAL 0x2a7b +/** + * @brief Dew Point Characteristic + */ +#define BT_UUID_DEW_POINT BT_UUID_DECLARE_16(BT_UUID_DEW_POINT_VAL) +/** + * @brief GATT Characteristic Trend UUID Value + */ +#define BT_UUID_GATT_TREND_VAL 0x2a7c +/** + * @brief GATT Characteristic Trend + */ +#define BT_UUID_GATT_TREND BT_UUID_DECLARE_16(BT_UUID_GATT_TREND_VAL) +/** + * @brief Descriptor Value Changed Characteristic UUID value + */ +#define BT_UUID_DESC_VALUE_CHANGED_VAL 0x2a7d +/** + * @brief Descriptor Value Changed Characteristic + */ +#define BT_UUID_DESC_VALUE_CHANGED BT_UUID_DECLARE_16(BT_UUID_DESC_VALUE_CHANGED_VAL) +/** + * @brief GATT Characteristic Aerobic Heart Rate Low Limit UUID Value + */ +#define BT_UUID_GATT_AEHRLL_VAL 0x2a7e +/** + * @brief GATT Characteristic Aerobic Heart Rate Lower Limit + */ +#define BT_UUID_GATT_AEHRLL BT_UUID_DECLARE_16(BT_UUID_GATT_AEHRLL_VAL) +/** + * @brief GATT Characteristic Aerobic Threshold UUID Value + */ +#define BT_UUID_GATT_AETHR_VAL 0x2a7f +/** + * @brief GATT Characteristic Aerobic Threshold + */ +#define BT_UUID_GATT_AETHR BT_UUID_DECLARE_16(BT_UUID_GATT_AETHR_VAL) +/** + * @brief GATT Characteristic Age UUID Value + */ +#define BT_UUID_GATT_AGE_VAL 0x2a80 +/** + * @brief GATT Characteristic Age + */ +#define BT_UUID_GATT_AGE BT_UUID_DECLARE_16(BT_UUID_GATT_AGE_VAL) +/** + * @brief GATT Characteristic Anaerobic Heart Rate Lower Limit UUID Value + */ +#define BT_UUID_GATT_ANHRLL_VAL 0x2a81 +/** + * @brief GATT Characteristic Anaerobic Heart Rate Lower Limit + */ +#define BT_UUID_GATT_ANHRLL BT_UUID_DECLARE_16(BT_UUID_GATT_ANHRLL_VAL) +/** + * @brief GATT Characteristic Anaerobic Heart Rate Upper Limit UUID Value + */ +#define BT_UUID_GATT_ANHRUL_VAL 0x2a82 +/** + * @brief GATT Characteristic Anaerobic Heart Rate Upper Limit + */ +#define BT_UUID_GATT_ANHRUL BT_UUID_DECLARE_16(BT_UUID_GATT_ANHRUL_VAL) +/** + * @brief GATT Characteristic Anaerobic Threshold UUID Value + */ +#define BT_UUID_GATT_ANTHR_VAL 0x2a83 +/** + * @brief GATT Characteristic Anaerobic Threshold + */ +#define BT_UUID_GATT_ANTHR BT_UUID_DECLARE_16(BT_UUID_GATT_ANTHR_VAL) +/** + * @brief GATT Characteristic Aerobic Heart Rate Upper Limit UUID Value + */ +#define BT_UUID_GATT_AEHRUL_VAL 0x2a84 +/** + * @brief GATT Characteristic Aerobic Heart Rate Upper Limit + */ +#define BT_UUID_GATT_AEHRUL BT_UUID_DECLARE_16(BT_UUID_GATT_AEHRUL_VAL) +/** + * @brief GATT Characteristic Date of Birth UUID Value + */ +#define BT_UUID_GATT_DATE_BIRTH_VAL 0x2a85 +/** + * @brief GATT Characteristic Date of Birth + */ +#define BT_UUID_GATT_DATE_BIRTH BT_UUID_DECLARE_16(BT_UUID_GATT_DATE_BIRTH_VAL) +/** + * @brief GATT Characteristic Date of Threshold Assessment UUID Value + */ +#define BT_UUID_GATT_DATE_THRASS_VAL 0x2a86 +/** + * @brief GATT Characteristic Date of Threshold Assessment + */ +#define BT_UUID_GATT_DATE_THRASS BT_UUID_DECLARE_16(BT_UUID_GATT_DATE_THRASS_VAL) +/** + * @brief GATT Characteristic Email Address UUID Value + */ +#define BT_UUID_GATT_EMAIL_VAL 0x2a87 +/** + * @brief GATT Characteristic Email Address + */ +#define BT_UUID_GATT_EMAIL BT_UUID_DECLARE_16(BT_UUID_GATT_EMAIL_VAL) +/** + * @brief GATT Characteristic Fat Burn Heart Rate Lower Limit UUID Value + */ +#define BT_UUID_GATT_FBHRLL_VAL 0x2a88 +/** + * @brief GATT Characteristic Fat Burn Heart Rate Lower Limit + */ +#define BT_UUID_GATT_FBHRLL BT_UUID_DECLARE_16(BT_UUID_GATT_FBHRLL_VAL) +/** + * @brief GATT Characteristic Fat Burn Heart Rate Upper Limit UUID Value + */ +#define BT_UUID_GATT_FBHRUL_VAL 0x2a89 +/** + * @brief GATT Characteristic Fat Burn Heart Rate Upper Limit + */ +#define BT_UUID_GATT_FBHRUL BT_UUID_DECLARE_16(BT_UUID_GATT_FBHRUL_VAL) +/** + * @brief GATT Characteristic First Name UUID Value + */ +#define BT_UUID_GATT_FIRST_NAME_VAL 0x2a8a +/** + * @brief GATT Characteristic First Name + */ +#define BT_UUID_GATT_FIRST_NAME BT_UUID_DECLARE_16(BT_UUID_GATT_FIRST_NAME_VAL) +/** + * @brief GATT Characteristic Five Zone Heart Rate Limits UUID Value + */ +#define BT_UUID_GATT_5ZHRL_VAL 0x2a8b +/** + * @brief GATT Characteristic Five Zone Heart Rate Limits + */ +#define BT_UUID_GATT_5ZHRL BT_UUID_DECLARE_16(BT_UUID_GATT_5ZHRL_VAL) +/** + * @brief GATT Characteristic Gender UUID Value + */ +#define BT_UUID_GATT_GENDER_VAL 0x2a8c +/** + * @brief GATT Characteristic Gender + */ +#define BT_UUID_GATT_GENDER BT_UUID_DECLARE_16(BT_UUID_GATT_GENDER_VAL) +/** + * @brief GATT Characteristic Heart Rate Max UUID Value + */ +#define BT_UUID_GATT_HR_MAX_VAL 0x2a8d +/** + * @brief GATT Characteristic Heart Rate Max + */ +#define BT_UUID_GATT_HR_MAX BT_UUID_DECLARE_16(BT_UUID_GATT_HR_MAX_VAL) +/** + * @brief GATT Characteristic Height UUID Value + */ +#define BT_UUID_GATT_HEIGHT_VAL 0x2a8e +/** + * @brief GATT Characteristic Height + */ +#define BT_UUID_GATT_HEIGHT BT_UUID_DECLARE_16(BT_UUID_GATT_HEIGHT_VAL) +/** + * @brief GATT Characteristic Hip Circumference UUID Value + */ +#define BT_UUID_GATT_HC_VAL 0x2a8f +/** + * @brief GATT Characteristic Hip Circumference + */ +#define BT_UUID_GATT_HC BT_UUID_DECLARE_16(BT_UUID_GATT_HC_VAL) +/** + * @brief GATT Characteristic Last Name UUID Value + */ +#define BT_UUID_GATT_LAST_NAME_VAL 0x2a90 +/** + * @brief GATT Characteristic Last Name + */ +#define BT_UUID_GATT_LAST_NAME BT_UUID_DECLARE_16(BT_UUID_GATT_LAST_NAME_VAL) +/** + * @brief GATT Characteristic Maximum Recommended Heart Rate> UUID Value + */ +#define BT_UUID_GATT_MRHR_VAL 0x2a91 +/** + * @brief GATT Characteristic Maximum Recommended Heart Rate + */ +#define BT_UUID_GATT_MRHR BT_UUID_DECLARE_16(BT_UUID_GATT_MRHR_VAL) +/** + * @brief GATT Characteristic Resting Heart Rate UUID Value + */ +#define BT_UUID_GATT_RHR_VAL 0x2a92 +/** + * @brief GATT Characteristic Resting Heart Rate + */ +#define BT_UUID_GATT_RHR BT_UUID_DECLARE_16(BT_UUID_GATT_RHR_VAL) +/** + * @brief GATT Characteristic Sport Type for Aerobic and Anaerobic + * Thresholds UUID Value + */ +#define BT_UUID_GATT_AEANTHR_VAL 0x2a93 +/** + * @brief GATT Characteristic Sport Type for Aerobic and Anaerobic Threshold + */ +#define BT_UUID_GATT_AEANTHR BT_UUID_DECLARE_16(BT_UUID_GATT_AEANTHR_VAL) +/** + * @brief GATT Characteristic Three Zone Heart Rate Limits UUID Value + */ +#define BT_UUID_GATT_3ZHRL_VAL 0x2a94 +/** + * @brief GATT Characteristic Three Zone Heart Rate Limits + */ +#define BT_UUID_GATT_3ZHRL BT_UUID_DECLARE_16(BT_UUID_GATT_3ZHRL_VAL) +/** + * @brief GATT Characteristic Two Zone Heart Rate Limits UUID Value + */ +#define BT_UUID_GATT_2ZHRL_VAL 0x2a95 +/** + * @brief GATT Characteristic Two Zone Heart Rate Limits + */ +#define BT_UUID_GATT_2ZHRL BT_UUID_DECLARE_16(BT_UUID_GATT_2ZHRL_VAL) +/** + * @brief GATT Characteristic VO2 Max UUID Value + */ +#define BT_UUID_GATT_VO2_MAX_VAL 0x2a96 +/** + * @brief GATT Characteristic VO2 Max + */ +#define BT_UUID_GATT_VO2_MAX BT_UUID_DECLARE_16(BT_UUID_GATT_VO2_MAX_VAL) +/** + * @brief GATT Characteristic Waist Circumference UUID Value + */ +#define BT_UUID_GATT_WC_VAL 0x2a97 +/** + * @brief GATT Characteristic Waist Circumference + */ +#define BT_UUID_GATT_WC BT_UUID_DECLARE_16(BT_UUID_GATT_WC_VAL) +/** + * @brief GATT Characteristic Weight UUID Value + */ +#define BT_UUID_GATT_WEIGHT_VAL 0x2a98 +/** + * @brief GATT Characteristic Weight + */ +#define BT_UUID_GATT_WEIGHT BT_UUID_DECLARE_16(BT_UUID_GATT_WEIGHT_VAL) +/** + * @brief GATT Characteristic Database Change Increment UUID Value + */ +#define BT_UUID_GATT_DBCHINC_VAL 0x2a99 +/** + * @brief GATT Characteristic Database Change Increment + */ +#define BT_UUID_GATT_DBCHINC BT_UUID_DECLARE_16(BT_UUID_GATT_DBCHINC_VAL) +/** + * @brief GATT Characteristic User Index UUID Value + */ +#define BT_UUID_GATT_USRIDX_VAL 0x2a9a +/** + * @brief GATT Characteristic User Index + */ +#define BT_UUID_GATT_USRIDX BT_UUID_DECLARE_16(BT_UUID_GATT_USRIDX_VAL) +/** + * @brief GATT Characteristic Body Composition Feature UUID Value + */ +#define BT_UUID_GATT_BCF_VAL 0x2a9b +/** + * @brief GATT Characteristic Body Composition Feature + */ +#define BT_UUID_GATT_BCF BT_UUID_DECLARE_16(BT_UUID_GATT_BCF_VAL) +/** + * @brief GATT Characteristic Body Composition Measurement UUID Value + */ +#define BT_UUID_GATT_BCM_VAL 0x2a9c +/** + * @brief GATT Characteristic Body Composition Measurement + */ +#define BT_UUID_GATT_BCM BT_UUID_DECLARE_16(BT_UUID_GATT_BCM_VAL) +/** + * @brief GATT Characteristic Weight Measurement UUID Value + */ +#define BT_UUID_GATT_WM_VAL 0x2a9d +/** + * @brief GATT Characteristic Weight Measurement + */ +#define BT_UUID_GATT_WM BT_UUID_DECLARE_16(BT_UUID_GATT_WM_VAL) +/** + * @brief GATT Characteristic Weight Scale Feature UUID Value + */ +#define BT_UUID_GATT_WSF_VAL 0x2a9e +/** + * @brief GATT Characteristic Weight Scale Feature + */ +#define BT_UUID_GATT_WSF BT_UUID_DECLARE_16(BT_UUID_GATT_WSF_VAL) +/** + * @brief GATT Characteristic User Control Point UUID Value + */ +#define BT_UUID_GATT_USRCP_VAL 0x2a9f +/** + * @brief GATT Characteristic User Control Point + */ +#define BT_UUID_GATT_USRCP BT_UUID_DECLARE_16(BT_UUID_GATT_USRCP_VAL) +/** + * @brief Magnetic Flux Density - 2D Characteristic UUID value + */ +#define BT_UUID_MAGN_FLUX_DENSITY_2D_VAL 0x2aa0 +/** + * @brief Magnetic Flux Density - 2D Characteristic + */ +#define BT_UUID_MAGN_FLUX_DENSITY_2D BT_UUID_DECLARE_16(BT_UUID_MAGN_FLUX_DENSITY_2D_VAL) +/** + * @brief Magnetic Flux Density - 3D Characteristic UUID value + */ +#define BT_UUID_MAGN_FLUX_DENSITY_3D_VAL 0x2aa1 +/** + * @brief Magnetic Flux Density - 3D Characteristic + */ +#define BT_UUID_MAGN_FLUX_DENSITY_3D BT_UUID_DECLARE_16(BT_UUID_MAGN_FLUX_DENSITY_3D_VAL) +/** + * @brief GATT Characteristic Language UUID Value + */ +#define BT_UUID_GATT_LANG_VAL 0x2aa2 +/** + * @brief GATT Characteristic Language + */ +#define BT_UUID_GATT_LANG BT_UUID_DECLARE_16(BT_UUID_GATT_LANG_VAL) +/** + * @brief Barometric Pressure Trend Characteristic UUID value + */ +#define BT_UUID_BAR_PRESSURE_TREND_VAL 0x2aa3 +/** + * @brief Barometric Pressure Trend Characteristic + */ +#define BT_UUID_BAR_PRESSURE_TREND BT_UUID_DECLARE_16(BT_UUID_BAR_PRESSURE_TREND_VAL) +/** + * @brief Bond Management Control Point UUID value + */ +#define BT_UUID_BMS_CONTROL_POINT_VAL 0x2aa4 +/** + * @brief Bond Management Control Point + */ +#define BT_UUID_BMS_CONTROL_POINT BT_UUID_DECLARE_16(BT_UUID_BMS_CONTROL_POINT_VAL) +/** + * @brief Bond Management Feature UUID value + */ +#define BT_UUID_BMS_FEATURE_VAL 0x2aa5 +/** + * @brief Bond Management Feature + */ +#define BT_UUID_BMS_FEATURE BT_UUID_DECLARE_16(BT_UUID_BMS_FEATURE_VAL) +/** + * @brief Central Address Resolution Characteristic UUID value + */ +#define BT_UUID_CENTRAL_ADDR_RES_VAL 0x2aa6 +/** + * @brief Central Address Resolution Characteristic + */ +#define BT_UUID_CENTRAL_ADDR_RES BT_UUID_DECLARE_16(BT_UUID_CENTRAL_ADDR_RES_VAL) +/** + * @brief CGM Measurement Characteristic value + */ +#define BT_UUID_CGM_MEASUREMENT_VAL 0x2aa7 +/** + * @brief CGM Measurement Characteristic + */ +#define BT_UUID_CGM_MEASUREMENT BT_UUID_DECLARE_16(BT_UUID_CGM_MEASUREMENT_VAL) +/** + * @brief CGM Feature Characteristic value + */ +#define BT_UUID_CGM_FEATURE_VAL 0x2aa8 +/** + * @brief CGM Feature Characteristic + */ +#define BT_UUID_CGM_FEATURE BT_UUID_DECLARE_16(BT_UUID_CGM_FEATURE_VAL) +/** + * @brief CGM Status Characteristic value + */ +#define BT_UUID_CGM_STATUS_VAL 0x2aa9 +/** + * @brief CGM Status Characteristic + */ +#define BT_UUID_CGM_STATUS BT_UUID_DECLARE_16(BT_UUID_CGM_STATUS_VAL) +/** + * @brief CGM Session Start Time Characteristic value + */ +#define BT_UUID_CGM_SESSION_START_TIME_VAL 0x2aaa +/** + * @brief CGM Session Start Time + */ +#define BT_UUID_CGM_SESSION_START_TIME BT_UUID_DECLARE_16(BT_UUID_CGM_SESSION_START_TIME_VAL) +/** + * @brief CGM Session Run Time Characteristic value + */ +#define BT_UUID_CGM_SESSION_RUN_TIME_VAL 0x2aab +/** + * @brief CGM Session Run Time + */ +#define BT_UUID_CGM_SESSION_RUN_TIME BT_UUID_DECLARE_16(BT_UUID_CGM_SESSION_RUN_TIME_VAL) +/** + * @brief CGM Specific Ops Control Point Characteristic value + */ +#define BT_UUID_CGM_SPECIFIC_OPS_CONTROL_POINT_VAL 0x2aac +/** + * @brief CGM Specific Ops Control Point + */ +#define BT_UUID_CGM_SPECIFIC_OPS_CONTROL_POINT \ +BT_UUID_DECLARE_16(BT_UUID_CGM_SPECIFIC_OPS_CONTROL_POINT_VAL) +/** + * @brief GATT Characteristic Indoor Positioning Configuration UUID Value + */ +#define BT_UUID_GATT_IPC_VAL 0x2aad +/** + * @brief GATT Characteristic Indoor Positioning Configuration + */ +#define BT_UUID_GATT_IPC BT_UUID_DECLARE_16(BT_UUID_GATT_IPC_VAL) +/** + * @brief GATT Characteristic Latitude UUID Value + */ +#define BT_UUID_GATT_LAT_VAL 0x2aae +/** + * @brief GATT Characteristic Latitude + */ +#define BT_UUID_GATT_LAT BT_UUID_DECLARE_16(BT_UUID_GATT_LAT_VAL) +/** + * @brief GATT Characteristic Longitude UUID Value + */ +#define BT_UUID_GATT_LON_VAL 0x2aaf +/** + * @brief GATT Characteristic Longitude + */ +#define BT_UUID_GATT_LON BT_UUID_DECLARE_16(BT_UUID_GATT_LON_VAL) +/** + * @brief GATT Characteristic Local North Coordinate UUID Value + */ +#define BT_UUID_GATT_LNCOORD_VAL 0x2ab0 +/** + * @brief GATT Characteristic Local North Coordinate + */ +#define BT_UUID_GATT_LNCOORD BT_UUID_DECLARE_16(BT_UUID_GATT_LNCOORD_VAL) +/** + * @brief GATT Characteristic Local East Coordinate UUID Value + */ +#define BT_UUID_GATT_LECOORD_VAL 0x2ab1 +/** + * @brief GATT Characteristic Local East Coordinate + */ +#define BT_UUID_GATT_LECOORD BT_UUID_DECLARE_16(BT_UUID_GATT_LECOORD_VAL) +/** + * @brief GATT Characteristic Floor Number UUID Value + */ +#define BT_UUID_GATT_FN_VAL 0x2ab2 +/** + * @brief GATT Characteristic Floor Number + */ +#define BT_UUID_GATT_FN BT_UUID_DECLARE_16(BT_UUID_GATT_FN_VAL) +/** + * @brief GATT Characteristic Altitude UUID Value + */ +#define BT_UUID_GATT_ALT_VAL 0x2ab3 +/** + * @brief GATT Characteristic Altitude + */ +#define BT_UUID_GATT_ALT BT_UUID_DECLARE_16(BT_UUID_GATT_ALT_VAL) +/** + * @brief GATT Characteristic Uncertainty UUID Value + */ +#define BT_UUID_GATT_UNCERTAINTY_VAL 0x2ab4 +/** + * @brief GATT Characteristic Uncertainty + */ +#define BT_UUID_GATT_UNCERTAINTY BT_UUID_DECLARE_16(BT_UUID_GATT_UNCERTAINTY_VAL) +/** + * @brief GATT Characteristic Location Name UUID Value + */ +#define BT_UUID_GATT_LOC_NAME_VAL 0x2ab5 +/** + * @brief GATT Characteristic Location Name + */ +#define BT_UUID_GATT_LOC_NAME BT_UUID_DECLARE_16(BT_UUID_GATT_LOC_NAME_VAL) +/** + * @brief URI UUID value + */ +#define BT_UUID_URI_VAL 0x2ab6 +/** + * @brief URI + */ +#define BT_UUID_URI BT_UUID_DECLARE_16(BT_UUID_URI_VAL) +/** + * @brief HTTP Headers UUID value + */ +#define BT_UUID_HTTP_HEADERS_VAL 0x2ab7 +/** + * @brief HTTP Headers + */ +#define BT_UUID_HTTP_HEADERS BT_UUID_DECLARE_16(BT_UUID_HTTP_HEADERS_VAL) +/** + * @brief HTTP Status Code UUID value + */ +#define BT_UUID_HTTP_STATUS_CODE_VAL 0x2ab8 +/** + * @brief HTTP Status Code + */ +#define BT_UUID_HTTP_STATUS_CODE BT_UUID_DECLARE_16(BT_UUID_HTTP_STATUS_CODE_VAL) +/** + * @brief HTTP Entity Body UUID value + */ +#define BT_UUID_HTTP_ENTITY_BODY_VAL 0x2ab9 +/** + * @brief HTTP Entity Body + */ +#define BT_UUID_HTTP_ENTITY_BODY BT_UUID_DECLARE_16(BT_UUID_HTTP_ENTITY_BODY_VAL) +/** + * @brief HTTP Control Point UUID value + */ +#define BT_UUID_HTTP_CONTROL_POINT_VAL 0x2aba +/** + * @brief HTTP Control Point + */ +#define BT_UUID_HTTP_CONTROL_POINT BT_UUID_DECLARE_16(BT_UUID_HTTP_CONTROL_POINT_VAL) +/** + * @brief HTTPS Security UUID value + */ +#define BT_UUID_HTTPS_SECURITY_VAL 0x2abb +/** + * @brief HTTPS Security + */ +#define BT_UUID_HTTPS_SECURITY BT_UUID_DECLARE_16(BT_UUID_HTTPS_SECURITY_VAL) +/** + * @brief GATT Characteristic TDS Control Point UUID Value + */ +#define BT_UUID_GATT_TDS_CP_VAL 0x2abc +/** + * @brief GATT Characteristic TDS Control Point + */ +#define BT_UUID_GATT_TDS_CP BT_UUID_DECLARE_16(BT_UUID_GATT_TDS_CP_VAL) +/** + * @brief OTS Feature Characteristic UUID value + */ +#define BT_UUID_OTS_FEATURE_VAL 0x2abd +/** + * @brief OTS Feature Characteristic + */ +#define BT_UUID_OTS_FEATURE BT_UUID_DECLARE_16(BT_UUID_OTS_FEATURE_VAL) +/** + * @brief OTS Object Name Characteristic UUID value + */ +#define BT_UUID_OTS_NAME_VAL 0x2abe +/** + * @brief OTS Object Name Characteristic + */ +#define BT_UUID_OTS_NAME BT_UUID_DECLARE_16(BT_UUID_OTS_NAME_VAL) +/** + * @brief OTS Object Type Characteristic UUID value + */ +#define BT_UUID_OTS_TYPE_VAL 0x2abf +/** + * @brief OTS Object Type Characteristic + */ +#define BT_UUID_OTS_TYPE BT_UUID_DECLARE_16(BT_UUID_OTS_TYPE_VAL) +/** + * @brief OTS Object Size Characteristic UUID value + */ +#define BT_UUID_OTS_SIZE_VAL 0x2ac0 +/** + * @brief OTS Object Size Characteristic + */ +#define BT_UUID_OTS_SIZE BT_UUID_DECLARE_16(BT_UUID_OTS_SIZE_VAL) +/** + * @brief OTS Object First-Created Characteristic UUID value + */ +#define BT_UUID_OTS_FIRST_CREATED_VAL 0x2ac1 +/** + * @brief OTS Object First-Created Characteristic + */ +#define BT_UUID_OTS_FIRST_CREATED BT_UUID_DECLARE_16(BT_UUID_OTS_FIRST_CREATED_VAL) +/** + * @brief OTS Object Last-Modified Characteristic UUI value + */ +#define BT_UUID_OTS_LAST_MODIFIED_VAL 0x2ac2 +/** + * @brief OTS Object Last-Modified Characteristic + */ +#define BT_UUID_OTS_LAST_MODIFIED BT_UUID_DECLARE_16(BT_UUID_OTS_LAST_MODIFIED_VAL) +/** + * @brief OTS Object ID Characteristic UUID value + */ +#define BT_UUID_OTS_ID_VAL 0x2ac3 +/** + * @brief OTS Object ID Characteristic + */ +#define BT_UUID_OTS_ID BT_UUID_DECLARE_16(BT_UUID_OTS_ID_VAL) +/** + * @brief OTS Object Properties Characteristic UUID value + */ +#define BT_UUID_OTS_PROPERTIES_VAL 0x2ac4 +/** + * @brief OTS Object Properties Characteristic + */ +#define BT_UUID_OTS_PROPERTIES BT_UUID_DECLARE_16(BT_UUID_OTS_PROPERTIES_VAL) +/** + * @brief OTS Object Action Control Point Characteristic UUID value + */ +#define BT_UUID_OTS_ACTION_CP_VAL 0x2ac5 +/** + * @brief OTS Object Action Control Point Characteristic + */ +#define BT_UUID_OTS_ACTION_CP BT_UUID_DECLARE_16(BT_UUID_OTS_ACTION_CP_VAL) +/** + * @brief OTS Object List Control Point Characteristic UUID value + */ +#define BT_UUID_OTS_LIST_CP_VAL 0x2ac6 +/** + * @brief OTS Object List Control Point Characteristic + */ +#define BT_UUID_OTS_LIST_CP BT_UUID_DECLARE_16(BT_UUID_OTS_LIST_CP_VAL) +/** + * @brief OTS Object List Filter Characteristic UUID value + */ +#define BT_UUID_OTS_LIST_FILTER_VAL 0x2ac7 +/** + * @brief OTS Object List Filter Characteristic + */ +#define BT_UUID_OTS_LIST_FILTER BT_UUID_DECLARE_16(BT_UUID_OTS_LIST_FILTER_VAL) +/** + * @brief OTS Object Changed Characteristic UUID value + */ +#define BT_UUID_OTS_CHANGED_VAL 0x2ac8 +/** + * @brief OTS Object Changed Characteristic + */ +#define BT_UUID_OTS_CHANGED BT_UUID_DECLARE_16(BT_UUID_OTS_CHANGED_VAL) +/** + * @brief GATT Characteristic Resolvable Private Address Only UUID Value + */ +#define BT_UUID_GATT_RPAO_VAL 0x2ac9 +/** + * @brief GATT Characteristic Resolvable Private Address Only + */ +#define BT_UUID_GATT_RPAO BT_UUID_DECLARE_16(BT_UUID_GATT_RPAO_VAL) +/** + * @brief OTS Unspecified Object Type UUID value + */ +#define BT_UUID_OTS_TYPE_UNSPECIFIED_VAL 0x2aca +/** + * @brief OTS Unspecified Object Type + */ +#define BT_UUID_OTS_TYPE_UNSPECIFIED BT_UUID_DECLARE_16(BT_UUID_OTS_TYPE_UNSPECIFIED_VAL) +/** + * @brief OTS Directory Listing UUID value + */ +#define BT_UUID_OTS_DIRECTORY_LISTING_VAL 0x2acb +/** + * @brief OTS Directory Listing + */ +#define BT_UUID_OTS_DIRECTORY_LISTING BT_UUID_DECLARE_16(BT_UUID_OTS_DIRECTORY_LISTING_VAL) +/** + * @brief GATT Characteristic Fitness Machine Feature UUID Value + */ +#define BT_UUID_GATT_FMF_VAL 0x2acc +/** + * @brief GATT Characteristic Fitness Machine Feature + */ +#define BT_UUID_GATT_FMF BT_UUID_DECLARE_16(BT_UUID_GATT_FMF_VAL) +/** + * @brief GATT Characteristic Treadmill Data UUID Value + */ +#define BT_UUID_GATT_TD_VAL 0x2acd +/** + * @brief GATT Characteristic Treadmill Data + */ +#define BT_UUID_GATT_TD BT_UUID_DECLARE_16(BT_UUID_GATT_TD_VAL) +/** + * @brief GATT Characteristic Cross Trainer Data UUID Value + */ +#define BT_UUID_GATT_CTD_VAL 0x2ace +/** + * @brief GATT Characteristic Cross Trainer Data + */ +#define BT_UUID_GATT_CTD BT_UUID_DECLARE_16(BT_UUID_GATT_CTD_VAL) +/** + * @brief GATT Characteristic Step Climber Data UUID Value + */ +#define BT_UUID_GATT_STPCD_VAL 0x2acf +/** + * @brief GATT Characteristic Step Climber Data + */ +#define BT_UUID_GATT_STPCD BT_UUID_DECLARE_16(BT_UUID_GATT_STPCD_VAL) +/** + * @brief GATT Characteristic Stair Climber Data UUID Value + */ +#define BT_UUID_GATT_STRCD_VAL 0x2ad0 +/** + * @brief GATT Characteristic Stair Climber Data + */ +#define BT_UUID_GATT_STRCD BT_UUID_DECLARE_16(BT_UUID_GATT_STRCD_VAL) +/** + * @brief GATT Characteristic Rower Data UUID Value + */ +#define BT_UUID_GATT_RD_VAL 0x2ad1 +/** + * @brief GATT Characteristic Rower Data + */ +#define BT_UUID_GATT_RD BT_UUID_DECLARE_16(BT_UUID_GATT_RD_VAL) +/** + * @brief GATT Characteristic Indoor Bike Data UUID Value + */ +#define BT_UUID_GATT_IBD_VAL 0x2ad2 +/** + * @brief GATT Characteristic Indoor Bike Data + */ +#define BT_UUID_GATT_IBD BT_UUID_DECLARE_16(BT_UUID_GATT_IBD_VAL) +/** + * @brief GATT Characteristic Training Status UUID Value + */ +#define BT_UUID_GATT_TRSTAT_VAL 0x2ad3 +/** + * @brief GATT Characteristic Training Status + */ +#define BT_UUID_GATT_TRSTAT BT_UUID_DECLARE_16(BT_UUID_GATT_TRSTAT_VAL) +/** + * @brief GATT Characteristic Supported Speed Range UUID Value + */ +#define BT_UUID_GATT_SSR_VAL 0x2ad4 +/** + * @brief GATT Characteristic Supported Speed Range + */ +#define BT_UUID_GATT_SSR BT_UUID_DECLARE_16(BT_UUID_GATT_SSR_VAL) +/** + * @brief GATT Characteristic Supported Inclination Range UUID Value + */ +#define BT_UUID_GATT_SIR_VAL 0x2ad5 +/** + * @brief GATT Characteristic Supported Inclination Range + */ +#define BT_UUID_GATT_SIR BT_UUID_DECLARE_16(BT_UUID_GATT_SIR_VAL) +/** + * @brief GATT Characteristic Supported Resistance Level Range UUID Value + */ +#define BT_UUID_GATT_SRLR_VAL 0x2ad6 +/** + * @brief GATT Characteristic Supported Resistance Level Range + */ +#define BT_UUID_GATT_SRLR BT_UUID_DECLARE_16(BT_UUID_GATT_SRLR_VAL) +/** + * @brief GATT Characteristic Supported Heart Rate Range UUID Value + */ +#define BT_UUID_GATT_SHRR_VAL 0x2ad7 +/** + * @brief GATT Characteristic Supported Heart Rate Range + */ +#define BT_UUID_GATT_SHRR BT_UUID_DECLARE_16(BT_UUID_GATT_SHRR_VAL) +/** + * @brief GATT Characteristic Supported Power Range UUID Value + */ +#define BT_UUID_GATT_SPR_VAL 0x2ad8 +/** + * @brief GATT Characteristic Supported Power Range + */ +#define BT_UUID_GATT_SPR BT_UUID_DECLARE_16(BT_UUID_GATT_SPR_VAL) +/** + * @brief GATT Characteristic Fitness Machine Control Point UUID Value + */ +#define BT_UUID_GATT_FMCP_VAL 0x2ad9 +/** + * @brief GATT Characteristic Fitness Machine Control Point + */ +#define BT_UUID_GATT_FMCP BT_UUID_DECLARE_16(BT_UUID_GATT_FMCP_VAL) +/** + * @brief GATT Characteristic Fitness Machine Status UUID Value + */ +#define BT_UUID_GATT_FMS_VAL 0x2ada +/** + * @brief GATT Characteristic Fitness Machine Status + */ +#define BT_UUID_GATT_FMS BT_UUID_DECLARE_16(BT_UUID_GATT_FMS_VAL) +/** + * @brief Mesh Provisioning Data In UUID value + */ +#define BT_UUID_MESH_PROV_DATA_IN_VAL 0x2adb +/** + * @brief Mesh Provisioning Data In + */ +#define BT_UUID_MESH_PROV_DATA_IN BT_UUID_DECLARE_16(BT_UUID_MESH_PROV_DATA_IN_VAL) +/** + * @brief Mesh Provisioning Data Out UUID value + */ +#define BT_UUID_MESH_PROV_DATA_OUT_VAL 0x2adc +/** + * @brief Mesh Provisioning Data Out + */ +#define BT_UUID_MESH_PROV_DATA_OUT BT_UUID_DECLARE_16(BT_UUID_MESH_PROV_DATA_OUT_VAL) +/** + * @brief Mesh Proxy Data In UUID value + */ +#define BT_UUID_MESH_PROXY_DATA_IN_VAL 0x2add +/** + * @brief Mesh Proxy Data In + */ +#define BT_UUID_MESH_PROXY_DATA_IN BT_UUID_DECLARE_16(BT_UUID_MESH_PROXY_DATA_IN_VAL) +/** + * @brief Mesh Proxy Data Out UUID value + */ +#define BT_UUID_MESH_PROXY_DATA_OUT_VAL 0x2ade +/** + * @brief Mesh Proxy Data Out + */ +#define BT_UUID_MESH_PROXY_DATA_OUT BT_UUID_DECLARE_16(BT_UUID_MESH_PROXY_DATA_OUT_VAL) +/** + * @brief GATT Characteristic New Number Needed UUID Value + */ +#define BT_UUID_GATT_NNN_VAL 0x2adf +/** + * @brief GATT Characteristic New Number Needed + */ +#define BT_UUID_GATT_NNN BT_UUID_DECLARE_16(BT_UUID_GATT_NNN_VAL) +/** + * @brief GATT Characteristic Average Current UUID Value + */ +#define BT_UUID_GATT_AC_VAL 0x2ae0 +/** + * @brief GATT Characteristic Average Current + */ +#define BT_UUID_GATT_AC BT_UUID_DECLARE_16(BT_UUID_GATT_AC_VAL) +/** + * @brief GATT Characteristic Average Voltage UUID Value + */ +#define BT_UUID_GATT_AV_VAL 0x2ae1 +/** + * @brief GATT Characteristic Average Voltage + */ +#define BT_UUID_GATT_AV BT_UUID_DECLARE_16(BT_UUID_GATT_AV_VAL) +/** + * @brief GATT Characteristic Boolean UUID Value + */ +#define BT_UUID_GATT_BOOLEAN_VAL 0x2ae2 +/** + * @brief GATT Characteristic Boolean + */ +#define BT_UUID_GATT_BOOLEAN BT_UUID_DECLARE_16(BT_UUID_GATT_BOOLEAN_VAL) +/** + * @brief GATT Characteristic Chromatic Distance From Planckian UUID Value + */ +#define BT_UUID_GATT_CRDFP_VAL 0x2ae3 +/** + * @brief GATT Characteristic Chromatic Distance From Planckian + */ +#define BT_UUID_GATT_CRDFP BT_UUID_DECLARE_16(BT_UUID_GATT_CRDFP_VAL) +/** + * @brief GATT Characteristic Chromaticity Coordinates UUID Value + */ +#define BT_UUID_GATT_CRCOORDS_VAL 0x2ae4 +/** + * @brief GATT Characteristic Chromaticity Coordinates + */ +#define BT_UUID_GATT_CRCOORDS BT_UUID_DECLARE_16(BT_UUID_GATT_CRCOORDS_VAL) +/** + * @brief GATT Characteristic Chromaticity In CCT And Duv Values UUID Value + */ +#define BT_UUID_GATT_CRCCT_VAL 0x2ae5 +/** + * @brief GATT Characteristic Chromaticity In CCT And Duv Values + */ +#define BT_UUID_GATT_CRCCT BT_UUID_DECLARE_16(BT_UUID_GATT_CRCCT_VAL) +/** + * @brief GATT Characteristic Chromaticity Tolerance UUID Value + */ +#define BT_UUID_GATT_CRT_VAL 0x2ae6 +/** + * @brief GATT Characteristic Chromaticity Tolerance + */ +#define BT_UUID_GATT_CRT BT_UUID_DECLARE_16(BT_UUID_GATT_CRT_VAL) +/** + * @brief GATT Characteristic CIE 13.3-1995 Color Rendering Index UUID Value + */ +#define BT_UUID_GATT_CIEIDX_VAL 0x2ae7 +/** + * @brief GATT Characteristic CIE 13.3-1995 Color Rendering Index + */ +#define BT_UUID_GATT_CIEIDX BT_UUID_DECLARE_16(BT_UUID_GATT_CIEIDX_VAL) +/** + * @brief GATT Characteristic Coefficient UUID Value + */ +#define BT_UUID_GATT_COEFFICIENT_VAL 0x2ae8 +/** + * @brief GATT Characteristic Coefficient + */ +#define BT_UUID_GATT_COEFFICIENT BT_UUID_DECLARE_16(BT_UUID_GATT_COEFFICIENT_VAL) +/** + * @brief GATT Characteristic Correlated Color Temperature UUID Value + */ +#define BT_UUID_GATT_CCTEMP_VAL 0x2ae9 +/** + * @brief GATT Characteristic Correlated Color Temperature + */ +#define BT_UUID_GATT_CCTEMP BT_UUID_DECLARE_16(BT_UUID_GATT_CCTEMP_VAL) +/** + * @brief GATT Characteristic Count 16 UUID Value + */ +#define BT_UUID_GATT_COUNT16_VAL 0x2aea +/** + * @brief GATT Characteristic Count 16 + */ +#define BT_UUID_GATT_COUNT16 BT_UUID_DECLARE_16(BT_UUID_GATT_COUNT16_VAL) +/** + * @brief GATT Characteristic Count 24 UUID Value + */ +#define BT_UUID_GATT_COUNT24_VAL 0x2aeb +/** + * @brief GATT Characteristic Count 24 + */ +#define BT_UUID_GATT_COUNT24 BT_UUID_DECLARE_16(BT_UUID_GATT_COUNT24_VAL) +/** + * @brief GATT Characteristic Country Code UUID Value + */ +#define BT_UUID_GATT_CNTRCODE_VAL 0x2aec +/** + * @brief GATT Characteristic Country Code + */ +#define BT_UUID_GATT_CNTRCODE BT_UUID_DECLARE_16(BT_UUID_GATT_CNTRCODE_VAL) +/** + * @brief GATT Characteristic Date UTC UUID Value + */ +#define BT_UUID_GATT_DATEUTC_VAL 0x2aed +/** + * @brief GATT Characteristic Date UTC + */ +#define BT_UUID_GATT_DATEUTC BT_UUID_DECLARE_16(BT_UUID_GATT_DATEUTC_VAL) +/** + * @brief GATT Characteristic Electric Current UUID Value + */ +#define BT_UUID_GATT_EC_VAL 0x2aee +/** + * @brief GATT Characteristic Electric Current + */ +#define BT_UUID_GATT_EC BT_UUID_DECLARE_16(BT_UUID_GATT_EC_VAL) +/** + * @brief GATT Characteristic Electric Current Range UUID Value + */ +#define BT_UUID_GATT_ECR_VAL 0x2aef +/** + * @brief GATT Characteristic Electric Current Range + */ +#define BT_UUID_GATT_ECR BT_UUID_DECLARE_16(BT_UUID_GATT_ECR_VAL) +/** + * @brief GATT Characteristic Electric Current Specification UUID Value + */ +#define BT_UUID_GATT_ECSPEC_VAL 0x2af0 +/** + * @brief GATT Characteristic Electric Current Specification + */ +#define BT_UUID_GATT_ECSPEC BT_UUID_DECLARE_16(BT_UUID_GATT_ECSPEC_VAL) +/** + * @brief GATT Characteristic Electric Current Statistics UUID Value + */ +#define BT_UUID_GATT_ECSTAT_VAL 0x2af1 +/** + * @brief GATT Characteristic Electric Current Statistics + */ +#define BT_UUID_GATT_ECSTAT BT_UUID_DECLARE_16(BT_UUID_GATT_ECSTAT_VAL) +/** + * @brief GATT Characteristic Energy UUID Value + */ +#define BT_UUID_GATT_ENERGY_VAL 0x2af2 +/** + * @brief GATT Characteristic Energy + */ +#define BT_UUID_GATT_ENERGY BT_UUID_DECLARE_16(BT_UUID_GATT_ENERGY_VAL) +/** + * @brief GATT Characteristic Energy In A Period Of Day UUID Value + */ +#define BT_UUID_GATT_EPOD_VAL 0x2af3 +/** + * @brief GATT Characteristic Energy In A Period Of Day + */ +#define BT_UUID_GATT_EPOD BT_UUID_DECLARE_16(BT_UUID_GATT_EPOD_VAL) +/** + * @brief GATT Characteristic Event Statistics UUID Value + */ +#define BT_UUID_GATT_EVTSTAT_VAL 0x2af4 +/** + * @brief GATT Characteristic Event Statistics + */ +#define BT_UUID_GATT_EVTSTAT BT_UUID_DECLARE_16(BT_UUID_GATT_EVTSTAT_VAL) +/** + * @brief GATT Characteristic Fixed String 16 UUID Value + */ +#define BT_UUID_GATT_FSTR16_VAL 0x2af5 +/** + * @brief GATT Characteristic Fixed String 16 + */ +#define BT_UUID_GATT_FSTR16 BT_UUID_DECLARE_16(BT_UUID_GATT_FSTR16_VAL) +/** + * @brief GATT Characteristic Fixed String 24 UUID Value + */ +#define BT_UUID_GATT_FSTR24_VAL 0x2af6 +/** + * @brief GATT Characteristic Fixed String 24 + */ +#define BT_UUID_GATT_FSTR24 BT_UUID_DECLARE_16(BT_UUID_GATT_FSTR24_VAL) +/** + * @brief GATT Characteristic Fixed String 36 UUID Value + */ +#define BT_UUID_GATT_FSTR36_VAL 0x2af7 +/** + * @brief GATT Characteristic Fixed String 36 + */ +#define BT_UUID_GATT_FSTR36 BT_UUID_DECLARE_16(BT_UUID_GATT_FSTR36_VAL) +/** + * @brief GATT Characteristic Fixed String 8 UUID Value + */ +#define BT_UUID_GATT_FSTR8_VAL 0x2af8 +/** + * @brief GATT Characteristic Fixed String 8 + */ +#define BT_UUID_GATT_FSTR8 BT_UUID_DECLARE_16(BT_UUID_GATT_FSTR8_VAL) +/** + * @brief GATT Characteristic Generic Level UUID Value + */ +#define BT_UUID_GATT_GENLVL_VAL 0x2af9 +/** + * @brief GATT Characteristic Generic Level + */ +#define BT_UUID_GATT_GENLVL BT_UUID_DECLARE_16(BT_UUID_GATT_GENLVL_VAL) +/** + * @brief GATT Characteristic Global Trade Item Number UUID Value + */ +#define BT_UUID_GATT_GTIN_VAL 0x2afa +/** + * @brief GATT Characteristic Global Trade Item Number + */ +#define BT_UUID_GATT_GTIN BT_UUID_DECLARE_16(BT_UUID_GATT_GTIN_VAL) +/** + * @brief GATT Characteristic Illuminance UUID Value + */ +#define BT_UUID_GATT_ILLUM_VAL 0x2afb +/** + * @brief GATT Characteristic Illuminance + */ +#define BT_UUID_GATT_ILLUM BT_UUID_DECLARE_16(BT_UUID_GATT_ILLUM_VAL) +/** + * @brief GATT Characteristic Luminous Efficacy UUID Value + */ +#define BT_UUID_GATT_LUMEFF_VAL 0x2afc +/** + * @brief GATT Characteristic Luminous Efficacy + */ +#define BT_UUID_GATT_LUMEFF BT_UUID_DECLARE_16(BT_UUID_GATT_LUMEFF_VAL) +/** + * @brief GATT Characteristic Luminous Energy UUID Value + */ +#define BT_UUID_GATT_LUMNRG_VAL 0x2afd +/** + * @brief GATT Characteristic Luminous Energy + */ +#define BT_UUID_GATT_LUMNRG BT_UUID_DECLARE_16(BT_UUID_GATT_LUMNRG_VAL) +/** + * @brief GATT Characteristic Luminous Exposure UUID Value + */ +#define BT_UUID_GATT_LUMEXP_VAL 0x2afe +/** + * @brief GATT Characteristic Luminous Exposure + */ +#define BT_UUID_GATT_LUMEXP BT_UUID_DECLARE_16(BT_UUID_GATT_LUMEXP_VAL) +/** + * @brief GATT Characteristic Luminous Flux UUID Value + */ +#define BT_UUID_GATT_LUMFLX_VAL 0x2aff +/** + * @brief GATT Characteristic Luminous Flux + */ +#define BT_UUID_GATT_LUMFLX BT_UUID_DECLARE_16(BT_UUID_GATT_LUMFLX_VAL) +/** + * @brief GATT Characteristic Luminous Flux Range UUID Value + */ +#define BT_UUID_GATT_LUMFLXR_VAL 0x2b00 +/** + * @brief GATT Characteristic Luminous Flux Range + */ +#define BT_UUID_GATT_LUMFLXR BT_UUID_DECLARE_16(BT_UUID_GATT_LUMFLXR_VAL) +/** + * @brief GATT Characteristic Luminous Intensity UUID Value + */ +#define BT_UUID_GATT_LUMINT_VAL 0x2b01 +/** + * @brief GATT Characteristic Luminous Intensity + */ +#define BT_UUID_GATT_LUMINT BT_UUID_DECLARE_16(BT_UUID_GATT_LUMINT_VAL) +/** + * @brief GATT Characteristic Mass Flow UUID Value + */ +#define BT_UUID_GATT_MASSFLOW_VAL 0x2b02 +/** + * @brief GATT Characteristic Mass Flow + */ +#define BT_UUID_GATT_MASSFLOW BT_UUID_DECLARE_16(BT_UUID_GATT_MASSFLOW_VAL) +/** + * @brief GATT Characteristic Perceived Lightness UUID Value + */ +#define BT_UUID_GATT_PERLGHT_VAL 0x2b03 +/** + * @brief GATT Characteristic Perceived Lightness + */ +#define BT_UUID_GATT_PERLGHT BT_UUID_DECLARE_16(BT_UUID_GATT_PERLGHT_VAL) +/** + * @brief GATT Characteristic Percentage 8 UUID Value + */ +#define BT_UUID_GATT_PER8_VAL 0x2b04 +/** + * @brief GATT Characteristic Percentage 8 + */ +#define BT_UUID_GATT_PER8 BT_UUID_DECLARE_16(BT_UUID_GATT_PER8_VAL) +/** + * @brief GATT Characteristic Power UUID Value + */ +#define BT_UUID_GATT_PWR_VAL 0x2b05 +/** + * @brief GATT Characteristic Power + */ +#define BT_UUID_GATT_PWR BT_UUID_DECLARE_16(BT_UUID_GATT_PWR_VAL) +/** + * @brief GATT Characteristic Power Specification UUID Value + */ +#define BT_UUID_GATT_PWRSPEC_VAL 0x2b06 +/** + * @brief GATT Characteristic Power Specification + */ +#define BT_UUID_GATT_PWRSPEC BT_UUID_DECLARE_16(BT_UUID_GATT_PWRSPEC_VAL) +/** + * @brief GATT Characteristic Relative Runtime In A Current Range UUID Value + */ +#define BT_UUID_GATT_RRICR_VAL 0x2b07 +/** + * @brief GATT Characteristic Relative Runtime In A Current Range + */ +#define BT_UUID_GATT_RRICR BT_UUID_DECLARE_16(BT_UUID_GATT_RRICR_VAL) +/** + * @brief GATT Characteristic Relative Runtime In A Generic Level Range UUID + * Value + */ +#define BT_UUID_GATT_RRIGLR_VAL 0x2b08 +/** + * @brief GATT Characteristic Relative Runtime In A Generic Level Range + */ +#define BT_UUID_GATT_RRIGLR BT_UUID_DECLARE_16(BT_UUID_GATT_RRIGLR_VAL) +/** + * @brief GATT Characteristic Relative Value In A Voltage Range UUID Value + */ +#define BT_UUID_GATT_RVIVR_VAL 0x2b09 +/** + * @brief GATT Characteristic Relative Value In A Voltage Range + */ +#define BT_UUID_GATT_RVIVR BT_UUID_DECLARE_16(BT_UUID_GATT_RVIVR_VAL) +/** + * @brief GATT Characteristic Relative Value In A Illuminance Range UUID + * Value + */ +#define BT_UUID_GATT_RVIIR_VAL 0x2b0a +/** + * @brief GATT Characteristic Relative Value In A Illuminance Range + */ +#define BT_UUID_GATT_RVIIR BT_UUID_DECLARE_16(BT_UUID_GATT_RVIIR_VAL) +/** + * @brief GATT Characteristic Relative Value In A Period Of Day UUID Value + */ +#define BT_UUID_GATT_RVIPOD_VAL 0x2b0b +/** + * @brief GATT Characteristic Relative Value In A Period Of Day + */ +#define BT_UUID_GATT_RVIPOD BT_UUID_DECLARE_16(BT_UUID_GATT_RVIPOD_VAL) +/** + * @brief GATT Characteristic Relative Value In A Temperature Range UUID + * Value + */ +#define BT_UUID_GATT_RVITR_VAL 0x2b0c +/** + * @brief GATT Characteristic Relative Value In A Temperature Range + */ +#define BT_UUID_GATT_RVITR BT_UUID_DECLARE_16(BT_UUID_GATT_RVITR_VAL) +/** + * @brief GATT Characteristic Temperature 8 UUID Value + */ +#define BT_UUID_GATT_TEMP8_VAL 0x2b0d +/** + * @brief GATT Characteristic Temperature 8 + */ +#define BT_UUID_GATT_TEMP8 BT_UUID_DECLARE_16(BT_UUID_GATT_TEMP8_VAL) +/** + * @brief GATT Characteristic Temperature 8 In A Period Of Day UUID Value + */ +#define BT_UUID_GATT_TEMP8_IPOD_VAL 0x2b0e +/** + * @brief GATT Characteristic Temperature 8 In A Period Of Day + */ +#define BT_UUID_GATT_TEMP8_IPOD BT_UUID_DECLARE_16(BT_UUID_GATT_TEMP8_IPOD_VAL) +/** + * @brief GATT Characteristic Temperature 8 Statistics UUID Value + */ +#define BT_UUID_GATT_TEMP8_STAT_VAL 0x2b0f +/** + * @brief GATT Characteristic Temperature 8 Statistics + */ +#define BT_UUID_GATT_TEMP8_STAT BT_UUID_DECLARE_16(BT_UUID_GATT_TEMP8_STAT_VAL) +/** + * @brief GATT Characteristic Temperature Range UUID Value + */ +#define BT_UUID_GATT_TEMP_RNG_VAL 0x2b10 +/** + * @brief GATT Characteristic Temperature Range + */ +#define BT_UUID_GATT_TEMP_RNG BT_UUID_DECLARE_16(BT_UUID_GATT_TEMP_RNG_VAL) +/** + * @brief GATT Characteristic Temperature Statistics UUID Value + */ +#define BT_UUID_GATT_TEMP_STAT_VAL 0x2b11 +/** + * @brief GATT Characteristic Temperature Statistics + */ +#define BT_UUID_GATT_TEMP_STAT BT_UUID_DECLARE_16(BT_UUID_GATT_TEMP_STAT_VAL) +/** + * @brief GATT Characteristic Time Decihour 8 UUID Value + */ +#define BT_UUID_GATT_TIM_DC8_VAL 0x2b12 +/** + * @brief GATT Characteristic Time Decihour 8 + */ +#define BT_UUID_GATT_TIM_DC8 BT_UUID_DECLARE_16(BT_UUID_GATT_TIM_DC8_VAL) +/** + * @brief GATT Characteristic Time Exponential 8 UUID Value + */ +#define BT_UUID_GATT_TIM_EXP8_VAL 0x2b13 +/** + * @brief GATT Characteristic Time Exponential 8 + */ +#define BT_UUID_GATT_TIM_EXP8 BT_UUID_DECLARE_16(BT_UUID_GATT_TIM_EXP8_VAL) +/** + * @brief GATT Characteristic Time Hour 24 UUID Value + */ +#define BT_UUID_GATT_TIM_H24_VAL 0x2b14 +/** + * @brief GATT Characteristic Time Hour 24 + */ +#define BT_UUID_GATT_TIM_H24 BT_UUID_DECLARE_16(BT_UUID_GATT_TIM_H24_VAL) +/** + * @brief GATT Characteristic Time Millisecond 24 UUID Value + */ +#define BT_UUID_GATT_TIM_MS24_VAL 0x2b15 +/** + * @brief GATT Characteristic Time Millisecond 24 + */ +#define BT_UUID_GATT_TIM_MS24 BT_UUID_DECLARE_16(BT_UUID_GATT_TIM_MS24_VAL) +/** + * @brief GATT Characteristic Time Second 16 UUID Value + */ +#define BT_UUID_GATT_TIM_S16_VAL 0x2b16 +/** + * @brief GATT Characteristic Time Second 16 + */ +#define BT_UUID_GATT_TIM_S16 BT_UUID_DECLARE_16(BT_UUID_GATT_TIM_S16_VAL) +/** + * @brief GATT Characteristic Time Second 8 UUID Value + */ +#define BT_UUID_GATT_TIM_S8_VAL 0x2b17 +/** + * @brief GATT Characteristic Time Second 8 + */ +#define BT_UUID_GATT_TIM_S8 BT_UUID_DECLARE_16(BT_UUID_GATT_TIM_S8_VAL) +/** + * @brief GATT Characteristic Voltage UUID Value + */ +#define BT_UUID_GATT_V_VAL 0x2b18 +/** + * @brief GATT Characteristic Voltage + */ +#define BT_UUID_GATT_V BT_UUID_DECLARE_16(BT_UUID_GATT_V_VAL) +/** + * @brief GATT Characteristic Voltage Specification UUID Value + */ +#define BT_UUID_GATT_V_SPEC_VAL 0x2b19 +/** + * @brief GATT Characteristic Voltage Specification + */ +#define BT_UUID_GATT_V_SPEC BT_UUID_DECLARE_16(BT_UUID_GATT_V_SPEC_VAL) +/** + * @brief GATT Characteristic Voltage Statistics UUID Value + */ +#define BT_UUID_GATT_V_STAT_VAL 0x2b1a +/** + * @brief GATT Characteristic Voltage Statistics + */ +#define BT_UUID_GATT_V_STAT BT_UUID_DECLARE_16(BT_UUID_GATT_V_STAT_VAL) +/** + * @brief GATT Characteristic Volume Flow UUID Value + */ +#define BT_UUID_GATT_VOLF_VAL 0x2b1b +/** + * @brief GATT Characteristic Volume Flow + */ +#define BT_UUID_GATT_VOLF BT_UUID_DECLARE_16(BT_UUID_GATT_VOLF_VAL) +/** + * @brief GATT Characteristic Chromaticity Coordinate (not Coordinates) UUID + * Value + */ +#define BT_UUID_GATT_CRCOORD_VAL 0x2b1c +/** + * @brief GATT Characteristic Chromaticity Coordinate (not Coordinates) + */ +#define BT_UUID_GATT_CRCOORD BT_UUID_DECLARE_16(BT_UUID_GATT_CRCOORD_VAL) +/** + * @brief GATT Characteristic RC Feature UUID Value + */ +#define BT_UUID_GATT_RCF_VAL 0x2b1d +/** + * @brief GATT Characteristic RC Feature + */ +#define BT_UUID_GATT_RCF BT_UUID_DECLARE_16(BT_UUID_GATT_RCF_VAL) +/** + * @brief GATT Characteristic RC Settings UUID Value + */ +#define BT_UUID_GATT_RCSET_VAL 0x2b1e +/** + * @brief GATT Characteristic RC Settings + */ +#define BT_UUID_GATT_RCSET BT_UUID_DECLARE_16(BT_UUID_GATT_RCSET_VAL) +/** + * @brief GATT Characteristic Reconnection Configuration Control Point UUID + * Value + */ +#define BT_UUID_GATT_RCCP_VAL 0x2b1f +/** + * @brief GATT Characteristic Reconnection Configuration Control Point + */ +#define BT_UUID_GATT_RCCP BT_UUID_DECLARE_16(BT_UUID_GATT_RCCP_VAL) +/** + * @brief GATT Characteristic IDD Status Changed UUID Value + */ +#define BT_UUID_GATT_IDD_SC_VAL 0x2b20 +/** + * @brief GATT Characteristic IDD Status Changed + */ +#define BT_UUID_GATT_IDD_SC BT_UUID_DECLARE_16(BT_UUID_GATT_IDD_SC_VAL) +/** + * @brief GATT Characteristic IDD Status UUID Value + */ +#define BT_UUID_GATT_IDD_S_VAL 0x2b21 +/** + * @brief GATT Characteristic IDD Status + */ +#define BT_UUID_GATT_IDD_S BT_UUID_DECLARE_16(BT_UUID_GATT_IDD_S_VAL) +/** + * @brief GATT Characteristic IDD Annunciation Status UUID Value + */ +#define BT_UUID_GATT_IDD_AS_VAL 0x2b22 +/** + * @brief GATT Characteristic IDD Annunciation Status + */ +#define BT_UUID_GATT_IDD_AS BT_UUID_DECLARE_16(BT_UUID_GATT_IDD_AS_VAL) +/** + * @brief GATT Characteristic IDD Features UUID Value + */ +#define BT_UUID_GATT_IDD_F_VAL 0x2b23 +/** + * @brief GATT Characteristic IDD Features + */ +#define BT_UUID_GATT_IDD_F BT_UUID_DECLARE_16(BT_UUID_GATT_IDD_F_VAL) +/** + * @brief GATT Characteristic IDD Status Reader Control Point UUID Value + */ +#define BT_UUID_GATT_IDD_SRCP_VAL 0x2b24 +/** + * @brief GATT Characteristic IDD Status Reader Control Point + */ +#define BT_UUID_GATT_IDD_SRCP BT_UUID_DECLARE_16(BT_UUID_GATT_IDD_SRCP_VAL) +/** + * @brief GATT Characteristic IDD Command Control Point UUID Value + */ +#define BT_UUID_GATT_IDD_CCP_VAL 0x2b25 +/** + * @brief GATT Characteristic IDD Command Control Point + */ +#define BT_UUID_GATT_IDD_CCP BT_UUID_DECLARE_16(BT_UUID_GATT_IDD_CCP_VAL) +/** + * @brief GATT Characteristic IDD Command Data UUID Value + */ +#define BT_UUID_GATT_IDD_CD_VAL 0x2b26 +/** + * @brief GATT Characteristic IDD Command Data + */ +#define BT_UUID_GATT_IDD_CD BT_UUID_DECLARE_16(BT_UUID_GATT_IDD_CD_VAL) +/** + * @brief GATT Characteristic IDD Record Access Control Point UUID Value + */ +#define BT_UUID_GATT_IDD_RACP_VAL 0x2b27 +/** + * @brief GATT Characteristic IDD Record Access Control Point + */ +#define BT_UUID_GATT_IDD_RACP BT_UUID_DECLARE_16(BT_UUID_GATT_IDD_RACP_VAL) +/** + * @brief GATT Characteristic IDD History Data UUID Value + */ +#define BT_UUID_GATT_IDD_HD_VAL 0x2b28 +/** + * @brief GATT Characteristic IDD History Data + */ +#define BT_UUID_GATT_IDD_HD BT_UUID_DECLARE_16(BT_UUID_GATT_IDD_HD_VAL) +/** + * @brief GATT Characteristic Client Supported Features UUID value + */ +#define BT_UUID_GATT_CLIENT_FEATURES_VAL 0x2b29 +/** + * @brief GATT Characteristic Client Supported Features + */ +#define BT_UUID_GATT_CLIENT_FEATURES BT_UUID_DECLARE_16(BT_UUID_GATT_CLIENT_FEATURES_VAL) +/** + * @brief GATT Characteristic Database Hash UUID value + */ +#define BT_UUID_GATT_DB_HASH_VAL 0x2b2a +/** + * @brief GATT Characteristic Database Hash + */ +#define BT_UUID_GATT_DB_HASH BT_UUID_DECLARE_16(BT_UUID_GATT_DB_HASH_VAL) +/** + * @brief GATT Characteristic BSS Control Point UUID Value + */ +#define BT_UUID_GATT_BSS_CP_VAL 0x2b2b +/** + * @brief GATT Characteristic BSS Control Point + */ +#define BT_UUID_GATT_BSS_CP BT_UUID_DECLARE_16(BT_UUID_GATT_BSS_CP_VAL) +/** + * @brief GATT Characteristic BSS Response UUID Value + */ +#define BT_UUID_GATT_BSS_R_VAL 0x2b2c +/** + * @brief GATT Characteristic BSS Response + */ +#define BT_UUID_GATT_BSS_R BT_UUID_DECLARE_16(BT_UUID_GATT_BSS_R_VAL) +/** + * @brief GATT Characteristic Emergency ID UUID Value + */ +#define BT_UUID_GATT_EMG_ID_VAL 0x2b2d +/** + * @brief GATT Characteristic Emergency ID + */ +#define BT_UUID_GATT_EMG_ID BT_UUID_DECLARE_16(BT_UUID_GATT_EMG_ID_VAL) +/** + * @brief GATT Characteristic Emergency Text UUID Value + */ +#define BT_UUID_GATT_EMG_TXT_VAL 0x2b2e +/** + * @brief GATT Characteristic Emergency Text + */ +#define BT_UUID_GATT_EMG_TXT BT_UUID_DECLARE_16(BT_UUID_GATT_EMG_TXT_VAL) +/** + * @brief GATT Characteristic ACS Status UUID Value + */ +#define BT_UUID_GATT_ACS_S_VAL 0x2b2f +/** + * @brief GATT Characteristic ACS Status + */ +#define BT_UUID_GATT_ACS_S BT_UUID_DECLARE_16(BT_UUID_GATT_ACS_S_VAL) +/** + * @brief GATT Characteristic ACS Data In UUID Value + */ +#define BT_UUID_GATT_ACS_DI_VAL 0x2b30 +/** + * @brief GATT Characteristic ACS Data In + */ +#define BT_UUID_GATT_ACS_DI BT_UUID_DECLARE_16(BT_UUID_GATT_ACS_DI_VAL) +/** + * @brief GATT Characteristic ACS Data Out Notify UUID Value + */ +#define BT_UUID_GATT_ACS_DON_VAL 0x2b31 +/** + * @brief GATT Characteristic ACS Data Out Notify + */ +#define BT_UUID_GATT_ACS_DON BT_UUID_DECLARE_16(BT_UUID_GATT_ACS_DON_VAL) +/** + * @brief GATT Characteristic ACS Data Out Indicate UUID Value + */ +#define BT_UUID_GATT_ACS_DOI_VAL 0x2b32 +/** + * @brief GATT Characteristic ACS Data Out Indicate + */ +#define BT_UUID_GATT_ACS_DOI BT_UUID_DECLARE_16(BT_UUID_GATT_ACS_DOI_VAL) +/** + * @brief GATT Characteristic ACS Control Point UUID Value + */ +#define BT_UUID_GATT_ACS_CP_VAL 0x2b33 +/** + * @brief GATT Characteristic ACS Control Point + */ +#define BT_UUID_GATT_ACS_CP BT_UUID_DECLARE_16(BT_UUID_GATT_ACS_CP_VAL) +/** + * @brief GATT Characteristic Enhanced Blood Pressure Measurement UUID Value + */ +#define BT_UUID_GATT_EBPM_VAL 0x2b34 +/** + * @brief GATT Characteristic Enhanced Blood Pressure Measurement + */ +#define BT_UUID_GATT_EBPM BT_UUID_DECLARE_16(BT_UUID_GATT_EBPM_VAL) +/** + * @brief GATT Characteristic Enhanced Intermediate Cuff Pressure UUID Value + */ +#define BT_UUID_GATT_EICP_VAL 0x2b35 +/** + * @brief GATT Characteristic Enhanced Intermediate Cuff Pressure + */ +#define BT_UUID_GATT_EICP BT_UUID_DECLARE_16(BT_UUID_GATT_EICP_VAL) +/** + * @brief GATT Characteristic Blood Pressure Record UUID Value + */ +#define BT_UUID_GATT_BPR_VAL 0x2b36 +/** + * @brief GATT Characteristic Blood Pressure Record + */ +#define BT_UUID_GATT_BPR BT_UUID_DECLARE_16(BT_UUID_GATT_BPR_VAL) +/** + * @brief GATT Characteristic Registered User UUID Value + */ +#define BT_UUID_GATT_RU_VAL 0x2b37 +/** + * @brief GATT Characteristic Registered User + */ +#define BT_UUID_GATT_RU BT_UUID_DECLARE_16(BT_UUID_GATT_RU_VAL) +/** + * @brief GATT Characteristic BR-EDR Handover Data UUID Value + */ +#define BT_UUID_GATT_BR_EDR_HD_VAL 0x2b38 +/** + * @brief GATT Characteristic BR-EDR Handover Data + */ +#define BT_UUID_GATT_BR_EDR_HD BT_UUID_DECLARE_16(BT_UUID_GATT_BR_EDR_HD_VAL) +/** + * @brief GATT Characteristic Bluetooth SIG Data UUID Value + */ +#define BT_UUID_GATT_BT_SIG_D_VAL 0x2b39 +/** + * @brief GATT Characteristic Bluetooth SIG Data + */ +#define BT_UUID_GATT_BT_SIG_D BT_UUID_DECLARE_16(BT_UUID_GATT_BT_SIG_D_VAL) +/** + * @brief GATT Characteristic Server Supported Features UUID value + */ +#define BT_UUID_GATT_SERVER_FEATURES_VAL 0x2b3a +/** + * @brief GATT Characteristic Server Supported Features + */ +#define BT_UUID_GATT_SERVER_FEATURES BT_UUID_DECLARE_16(BT_UUID_GATT_SERVER_FEATURES_VAL) +/** + * @brief GATT Characteristic Physical Activity Monitor Features UUID Value + */ +#define BT_UUID_GATT_PHY_AMF_VAL 0x2b3b +/** + * @brief GATT Characteristic Physical Activity Monitor Features + */ +#define BT_UUID_GATT_PHY_AMF BT_UUID_DECLARE_16(BT_UUID_GATT_PHY_AMF_VAL) +/** + * @brief GATT Characteristic General Activity Instantaneous Data UUID Value + */ +#define BT_UUID_GATT_GEN_AID_VAL 0x2b3c +/** + * @brief GATT Characteristic General Activity Instantaneous Data + */ +#define BT_UUID_GATT_GEN_AID BT_UUID_DECLARE_16(BT_UUID_GATT_GEN_AID_VAL) +/** + * @brief GATT Characteristic General Activity Summary Data UUID Value + */ +#define BT_UUID_GATT_GEN_ASD_VAL 0x2b3d +/** + * @brief GATT Characteristic General Activity Summary Data + */ +#define BT_UUID_GATT_GEN_ASD BT_UUID_DECLARE_16(BT_UUID_GATT_GEN_ASD_VAL) +/** + * @brief GATT Characteristic CardioRespiratory Activity Instantaneous Data + * UUID Value + */ +#define BT_UUID_GATT_CR_AID_VAL 0x2b3e +/** + * @brief GATT Characteristic CardioRespiratory Activity Instantaneous Data + */ +#define BT_UUID_GATT_CR_AID BT_UUID_DECLARE_16(BT_UUID_GATT_CR_AID_VAL) +/** + * @brief GATT Characteristic CardioRespiratory Activity Summary Data UUID + * Value + */ +#define BT_UUID_GATT_CR_ASD_VAL 0x2b3f +/** + * @brief GATT Characteristic CardioRespiratory Activity Summary Data + */ +#define BT_UUID_GATT_CR_ASD BT_UUID_DECLARE_16(BT_UUID_GATT_CR_ASD_VAL) +/** + * @brief GATT Characteristic Step Counter Activity Summary Data UUID Value + */ +#define BT_UUID_GATT_SC_ASD_VAL 0x2b40 +/** + * @brief GATT Characteristic Step Counter Activity Summary Data + */ +#define BT_UUID_GATT_SC_ASD BT_UUID_DECLARE_16(BT_UUID_GATT_SC_ASD_VAL) +/** + * @brief GATT Characteristic Sleep Activity Instantaneous Data UUID Value + */ +#define BT_UUID_GATT_SLP_AID_VAL 0x2b41 +/** + * @brief GATT Characteristic Sleep Activity Instantaneous Data + */ +#define BT_UUID_GATT_SLP_AID BT_UUID_DECLARE_16(BT_UUID_GATT_SLP_AID_VAL) +/** + * @brief GATT Characteristic Sleep Activity Summary Data UUID Value + */ +#define BT_UUID_GATT_SLP_ASD_VAL 0x2b42 +/** + * @brief GATT Characteristic Sleep Activity Summary Data + */ +#define BT_UUID_GATT_SLP_ASD BT_UUID_DECLARE_16(BT_UUID_GATT_SLP_ASD_VAL) +/** + * @brief GATT Characteristic Physical Activity Monitor Control Point UUID + * Value + */ +#define BT_UUID_GATT_PHY_AMCP_VAL 0x2b43 +/** + * @brief GATT Characteristic Physical Activity Monitor Control Point + */ +#define BT_UUID_GATT_PHY_AMCP BT_UUID_DECLARE_16(BT_UUID_GATT_PHY_AMCP_VAL) +/** + * @brief GATT Characteristic Activity Current Session UUID Value + */ +#define BT_UUID_GATT_ACS_VAL 0x2b44 +/** + * @brief GATT Characteristic Activity Current Session + */ +#define BT_UUID_GATT_ACS BT_UUID_DECLARE_16(BT_UUID_GATT_ACS_VAL) +/** + * @brief GATT Characteristic Physical Activity Session Descriptor UUID + * Value + */ +#define BT_UUID_GATT_PHY_ASDESC_VAL 0x2b45 +/** + * @brief GATT Characteristic Physical Activity Session Descriptor + */ +#define BT_UUID_GATT_PHY_ASDESC BT_UUID_DECLARE_16(BT_UUID_GATT_PHY_ASDESC_VAL) +/** + * @brief GATT Characteristic Preferred Units UUID Value + */ +#define BT_UUID_GATT_PREF_U_VAL 0x2b46 +/** + * @brief GATT Characteristic Preferred Units + */ +#define BT_UUID_GATT_PREF_U BT_UUID_DECLARE_16(BT_UUID_GATT_PREF_U_VAL) +/** + * @brief GATT Characteristic High Resolution Height UUID Value + */ +#define BT_UUID_GATT_HRES_H_VAL 0x2b47 +/** + * @brief GATT Characteristic High Resolution Height + */ +#define BT_UUID_GATT_HRES_H BT_UUID_DECLARE_16(BT_UUID_GATT_HRES_H_VAL) +/** + * @brief GATT Characteristic Middle Name UUID Value + */ +#define BT_UUID_GATT_MID_NAME_VAL 0x2b48 +/** + * @brief GATT Characteristic Middle Name + */ +#define BT_UUID_GATT_MID_NAME BT_UUID_DECLARE_16(BT_UUID_GATT_MID_NAME_VAL) +/** + * @brief GATT Characteristic Stride Length UUID Value + */ +#define BT_UUID_GATT_STRDLEN_VAL 0x2b49 +/** + * @brief GATT Characteristic Stride Length + */ +#define BT_UUID_GATT_STRDLEN BT_UUID_DECLARE_16(BT_UUID_GATT_STRDLEN_VAL) +/** + * @brief GATT Characteristic Handedness UUID Value + */ +#define BT_UUID_GATT_HANDEDNESS_VAL 0x2b4a +/** + * @brief GATT Characteristic Handedness + */ +#define BT_UUID_GATT_HANDEDNESS BT_UUID_DECLARE_16(BT_UUID_GATT_HANDEDNESS_VAL) +/** + * @brief GATT Characteristic Device Wearing Position UUID Value + */ +#define BT_UUID_GATT_DEVICE_WP_VAL 0x2b4b +/** + * @brief GATT Characteristic Device Wearing Position + */ +#define BT_UUID_GATT_DEVICE_WP BT_UUID_DECLARE_16(BT_UUID_GATT_DEVICE_WP_VAL) +/** + * @brief GATT Characteristic Four Zone Heart Rate Limit UUID Value + */ +#define BT_UUID_GATT_4ZHRL_VAL 0x2b4c +/** + * @brief GATT Characteristic Four Zone Heart Rate Limit + */ +#define BT_UUID_GATT_4ZHRL BT_UUID_DECLARE_16(BT_UUID_GATT_4ZHRL_VAL) +/** + * @brief GATT Characteristic High Intensity Exercise Threshold UUID Value + */ +#define BT_UUID_GATT_HIET_VAL 0x2b4d +/** + * @brief GATT Characteristic High Intensity Exercise Threshold + */ +#define BT_UUID_GATT_HIET BT_UUID_DECLARE_16(BT_UUID_GATT_HIET_VAL) +/** + * @brief GATT Characteristic Activity Goal UUID Value + */ +#define BT_UUID_GATT_AG_VAL 0x2b4e +/** + * @brief GATT Characteristic Activity Goal + */ +#define BT_UUID_GATT_AG BT_UUID_DECLARE_16(BT_UUID_GATT_AG_VAL) +/** + * @brief GATT Characteristic Sedentary Interval Notification UUID Value + */ +#define BT_UUID_GATT_SIN_VAL 0x2b4f +/** + * @brief GATT Characteristic Sedentary Interval Notification + */ +#define BT_UUID_GATT_SIN BT_UUID_DECLARE_16(BT_UUID_GATT_SIN_VAL) +/** + * @brief GATT Characteristic Caloric Intake UUID Value + */ +#define BT_UUID_GATT_CI_VAL 0x2b50 +/** + * @brief GATT Characteristic Caloric Intake + */ +#define BT_UUID_GATT_CI BT_UUID_DECLARE_16(BT_UUID_GATT_CI_VAL) +/** + * @brief GATT Characteristic TMAP Role UUID Value + */ +#define BT_UUID_GATT_TMAPR_VAL 0x2b51 +/** + * @brief GATT Characteristic TMAP Role + */ +#define BT_UUID_GATT_TMAPR BT_UUID_DECLARE_16(BT_UUID_GATT_TMAPR_VAL) +/** + * @brief Audio Input Control Service State value + */ +#define BT_UUID_AICS_STATE_VAL 0x2b77 +/** + * @brief Audio Input Control Service State + */ +#define BT_UUID_AICS_STATE BT_UUID_DECLARE_16(BT_UUID_AICS_STATE_VAL) +/** + * @brief Audio Input Control Service Gain Settings Properties value + */ +#define BT_UUID_AICS_GAIN_SETTINGS_VAL 0x2b78 +/** + * @brief Audio Input Control Service Gain Settings Properties + */ +#define BT_UUID_AICS_GAIN_SETTINGS BT_UUID_DECLARE_16(BT_UUID_AICS_GAIN_SETTINGS_VAL) +/** + * @brief Audio Input Control Service Input Type value + */ +#define BT_UUID_AICS_INPUT_TYPE_VAL 0x2b79 +/** + * @brief Audio Input Control Service Input Type + */ +#define BT_UUID_AICS_INPUT_TYPE BT_UUID_DECLARE_16(BT_UUID_AICS_INPUT_TYPE_VAL) +/** + * @brief Audio Input Control Service Input Status value + */ +#define BT_UUID_AICS_INPUT_STATUS_VAL 0x2b7a +/** + * @brief Audio Input Control Service Input Status + */ +#define BT_UUID_AICS_INPUT_STATUS BT_UUID_DECLARE_16(BT_UUID_AICS_INPUT_STATUS_VAL) +/** + * @brief Audio Input Control Service Control Point value + */ +#define BT_UUID_AICS_CONTROL_VAL 0x2b7b +/** + * @brief Audio Input Control Service Control Point + */ +#define BT_UUID_AICS_CONTROL BT_UUID_DECLARE_16(BT_UUID_AICS_CONTROL_VAL) +/** + * @brief Audio Input Control Service Input Description value + */ +#define BT_UUID_AICS_DESCRIPTION_VAL 0x2b7c +/** + * @brief Audio Input Control Service Input Description + */ +#define BT_UUID_AICS_DESCRIPTION BT_UUID_DECLARE_16(BT_UUID_AICS_DESCRIPTION_VAL) +/** + * @brief Volume Control Setting value + */ +#define BT_UUID_VCS_STATE_VAL 0x2b7d +/** + * @brief Volume Control Setting + */ +#define BT_UUID_VCS_STATE BT_UUID_DECLARE_16(BT_UUID_VCS_STATE_VAL) +/** + * @brief Volume Control Control point value + */ +#define BT_UUID_VCS_CONTROL_VAL 0x2b7e +/** + * @brief Volume Control Control point + */ +#define BT_UUID_VCS_CONTROL BT_UUID_DECLARE_16(BT_UUID_VCS_CONTROL_VAL) +/** + * @brief Volume Control Flags value + */ +#define BT_UUID_VCS_FLAGS_VAL 0x2b7f +/** + * @brief Volume Control Flags + */ +#define BT_UUID_VCS_FLAGS BT_UUID_DECLARE_16(BT_UUID_VCS_FLAGS_VAL) +/** + * @brief Volume Offset State value + */ +#define BT_UUID_VOCS_STATE_VAL 0x2b80 +/** + * @brief Volume Offset State + */ +#define BT_UUID_VOCS_STATE BT_UUID_DECLARE_16(BT_UUID_VOCS_STATE_VAL) +/** + * @brief Audio Location value + */ +#define BT_UUID_VOCS_LOCATION_VAL 0x2b81 +/** + * @brief Audio Location + */ +#define BT_UUID_VOCS_LOCATION BT_UUID_DECLARE_16(BT_UUID_VOCS_LOCATION_VAL) +/** + * @brief Volume Offset Control Point value + */ +#define BT_UUID_VOCS_CONTROL_VAL 0x2b82 +/** + * @brief Volume Offset Control Point + */ +#define BT_UUID_VOCS_CONTROL BT_UUID_DECLARE_16(BT_UUID_VOCS_CONTROL_VAL) +/** + * @brief Volume Offset Audio Output Description value + */ +#define BT_UUID_VOCS_DESCRIPTION_VAL 0x2b83 +/** + * @brief Volume Offset Audio Output Description + */ +#define BT_UUID_VOCS_DESCRIPTION BT_UUID_DECLARE_16(BT_UUID_VOCS_DESCRIPTION_VAL) +/** + * @brief Set Identity Resolving Key value + */ +#define BT_UUID_CSIS_SIRK_VAL 0x2b84 +/** + * @brief Set Identity Resolving Key + */ +#define BT_UUID_CSIS_SIRK BT_UUID_DECLARE_16(BT_UUID_CSIS_SIRK_VAL) +/** + * @brief Set size value + */ +#define BT_UUID_CSIS_SET_SIZE_VAL 0x2b85 +/** + * @brief Set size + */ +#define BT_UUID_CSIS_SET_SIZE BT_UUID_DECLARE_16(BT_UUID_CSIS_SET_SIZE_VAL) +/** + * @brief Set lock value + */ +#define BT_UUID_CSIS_SET_LOCK_VAL 0x2b86 +/** + * @brief Set lock + */ +#define BT_UUID_CSIS_SET_LOCK BT_UUID_DECLARE_16(BT_UUID_CSIS_SET_LOCK_VAL) +/** + * @brief Rank value + */ +#define BT_UUID_CSIS_RANK_VAL 0x2b87 +/** + * @brief Rank + */ +#define BT_UUID_CSIS_RANK BT_UUID_DECLARE_16(BT_UUID_CSIS_RANK_VAL) +/** + * @brief GATT Characteristic Encrypted Data Key Material UUID Value + */ +#define BT_UUID_GATT_EDKM_VAL 0x2b88 +/** + * @brief GATT Characteristic Encrypted Data Key Material + */ +#define BT_UUID_GATT_EDKM BT_UUID_DECLARE_16(BT_UUID_GATT_EDKM_VAL) +/** + * @brief GATT Characteristic Apparent Energy 32 UUID Value + */ +#define BT_UUID_GATT_AE32_VAL 0x2b89 +/** + * @brief GATT Characteristic Apparent Energy 32 + */ +#define BT_UUID_GATT_AE32 BT_UUID_DECLARE_16(BT_UUID_GATT_AE32_VAL) +/** + * @brief GATT Characteristic Apparent Power UUID Value + */ +#define BT_UUID_GATT_AP_VAL 0x2b8a +/** + * @brief GATT Characteristic Apparent Power + */ +#define BT_UUID_GATT_AP BT_UUID_DECLARE_16(BT_UUID_GATT_AP_VAL) +/** + * @brief GATT Characteristic CO2 Concentration UUID Value + */ +#define BT_UUID_GATT_CO2CONC_VAL 0x2b8c +/** + * @brief GATT Characteristic CO2 Concentration + */ +#define BT_UUID_GATT_CO2CONC BT_UUID_DECLARE_16(BT_UUID_GATT_CO2CONC_VAL) +/** + * @brief GATT Characteristic Cosine of the Angle UUID Value + */ +#define BT_UUID_GATT_COS_VAL 0x2b8d +/** + * @brief GATT Characteristic Cosine of the Angle + */ +#define BT_UUID_GATT_COS BT_UUID_DECLARE_16(BT_UUID_GATT_COS_VAL) +/** + * @brief GATT Characteristic Device Time Feature UUID Value + */ +#define BT_UUID_GATT_DEVTF_VAL 0x2b8e +/** + * @brief GATT Characteristic Device Time Feature + */ +#define BT_UUID_GATT_DEVTF BT_UUID_DECLARE_16(BT_UUID_GATT_DEVTF_VAL) +/** + * @brief GATT Characteristic Device Time Parameters UUID Value + */ +#define BT_UUID_GATT_DEVTP_VAL 0x2b8f +/** + * @brief GATT Characteristic Device Time Parameters + */ +#define BT_UUID_GATT_DEVTP BT_UUID_DECLARE_16(BT_UUID_GATT_DEVTP_VAL) +/** + * @brief GATT Characteristic Device Time UUID Value + */ +#define BT_UUID_GATT_DEVT_VAL 0x2b90 +/** + * @brief GATT Characteristic Device Time + */ +#define BT_UUID_GATT_DEVT BT_UUID_DECLARE_16(BT_UUID_GATT_DEVT_VAL) +/** + * @brief GATT Characteristic Device Time Control Point UUID Value + */ +#define BT_UUID_GATT_DEVTCP_VAL 0x2b91 +/** + * @brief GATT Characteristic Device Time Control Point + */ +#define BT_UUID_GATT_DEVTCP BT_UUID_DECLARE_16(BT_UUID_GATT_DEVTCP_VAL) +/** + * @brief GATT Characteristic Time Change Log Data UUID Value + */ +#define BT_UUID_GATT_TCLD_VAL 0x2b92 +/** + * @brief GATT Characteristic Time Change Log Data + */ +#define BT_UUID_GATT_TCLD BT_UUID_DECLARE_16(BT_UUID_GATT_TCLD_VAL) +/** + * @brief Media player name value + */ +#define BT_UUID_MCS_PLAYER_NAME_VAL 0x2b93 +/** + * @brief Media player name + */ +#define BT_UUID_MCS_PLAYER_NAME BT_UUID_DECLARE_16(BT_UUID_MCS_PLAYER_NAME_VAL) +/** + * @brief Media Icon Object ID value + */ +#define BT_UUID_MCS_ICON_OBJ_ID_VAL 0x2b94 +/** + * @brief Media Icon Object ID + */ +#define BT_UUID_MCS_ICON_OBJ_ID BT_UUID_DECLARE_16(BT_UUID_MCS_ICON_OBJ_ID_VAL) +/** + * @brief Media Icon URL value + */ +#define BT_UUID_MCS_ICON_URL_VAL 0x2b95 +/** + * @brief Media Icon URL + */ +#define BT_UUID_MCS_ICON_URL BT_UUID_DECLARE_16(BT_UUID_MCS_ICON_URL_VAL) +/** + * @brief Track Changed value + */ +#define BT_UUID_MCS_TRACK_CHANGED_VAL 0x2b96 +/** + * @brief Track Changed + */ +#define BT_UUID_MCS_TRACK_CHANGED BT_UUID_DECLARE_16(BT_UUID_MCS_TRACK_CHANGED_VAL) +/** + * @brief Track Title value + */ +#define BT_UUID_MCS_TRACK_TITLE_VAL 0x2b97 +/** + * @brief Track Title + */ +#define BT_UUID_MCS_TRACK_TITLE BT_UUID_DECLARE_16(BT_UUID_MCS_TRACK_TITLE_VAL) +/** + * @brief Track Duration value + */ +#define BT_UUID_MCS_TRACK_DURATION_VAL 0x2b98 +/** + * @brief Track Duration + */ +#define BT_UUID_MCS_TRACK_DURATION BT_UUID_DECLARE_16(BT_UUID_MCS_TRACK_DURATION_VAL) +/** + * @brief Track Position value + */ +#define BT_UUID_MCS_TRACK_POSITION_VAL 0x2b99 +/** + * @brief Track Position + */ +#define BT_UUID_MCS_TRACK_POSITION BT_UUID_DECLARE_16(BT_UUID_MCS_TRACK_POSITION_VAL) +/** + * @brief Playback Speed value + */ +#define BT_UUID_MCS_PLAYBACK_SPEED_VAL 0x2b9a +/** + * @brief Playback Speed + */ +#define BT_UUID_MCS_PLAYBACK_SPEED BT_UUID_DECLARE_16(BT_UUID_MCS_PLAYBACK_SPEED_VAL) +/** + * @brief Seeking Speed value + */ +#define BT_UUID_MCS_SEEKING_SPEED_VAL 0x2b9b +/** + * @brief Seeking Speed + */ +#define BT_UUID_MCS_SEEKING_SPEED BT_UUID_DECLARE_16(BT_UUID_MCS_SEEKING_SPEED_VAL) +/** + * @brief Track Segments Object ID value + */ +#define BT_UUID_MCS_TRACK_SEGMENTS_OBJ_ID_VAL 0x2b9c +/** + * @brief Track Segments Object ID + */ +#define BT_UUID_MCS_TRACK_SEGMENTS_OBJ_ID BT_UUID_DECLARE_16(BT_UUID_MCS_TRACK_SEGMENTS_OBJ_ID_VAL) +/** + * @brief Current Track Object ID value + */ +#define BT_UUID_MCS_CURRENT_TRACK_OBJ_ID_VAL 0x2b9d +/** + * @brief Current Track Object ID + */ +#define BT_UUID_MCS_CURRENT_TRACK_OBJ_ID BT_UUID_DECLARE_16(BT_UUID_MCS_CURRENT_TRACK_OBJ_ID_VAL) +/** + * @brief Next Track Object ID value + */ +#define BT_UUID_MCS_NEXT_TRACK_OBJ_ID_VAL 0x2b9e +/** + * @brief Next Track Object ID + */ +#define BT_UUID_MCS_NEXT_TRACK_OBJ_ID BT_UUID_DECLARE_16(BT_UUID_MCS_NEXT_TRACK_OBJ_ID_VAL) +/** + * @brief Parent Group Object ID value + */ +#define BT_UUID_MCS_PARENT_GROUP_OBJ_ID_VAL 0x2b9f +/** + * @brief Parent Group Object ID + */ +#define BT_UUID_MCS_PARENT_GROUP_OBJ_ID BT_UUID_DECLARE_16(BT_UUID_MCS_PARENT_GROUP_OBJ_ID_VAL) +/** + * @brief Group Object ID value + */ +#define BT_UUID_MCS_CURRENT_GROUP_OBJ_ID_VAL 0x2ba0 +/** + * @brief Group Object ID + */ +#define BT_UUID_MCS_CURRENT_GROUP_OBJ_ID BT_UUID_DECLARE_16(BT_UUID_MCS_CURRENT_GROUP_OBJ_ID_VAL) +/** + * @brief Playing Order value + */ +#define BT_UUID_MCS_PLAYING_ORDER_VAL 0x2ba1 +/** + * @brief Playing Order + */ +#define BT_UUID_MCS_PLAYING_ORDER BT_UUID_DECLARE_16(BT_UUID_MCS_PLAYING_ORDER_VAL) +/** + * @brief Playing Orders supported value + */ +#define BT_UUID_MCS_PLAYING_ORDERS_VAL 0x2ba2 +/** + * @brief Playing Orders supported + */ +#define BT_UUID_MCS_PLAYING_ORDERS BT_UUID_DECLARE_16(BT_UUID_MCS_PLAYING_ORDERS_VAL) +/** + * @brief Media State value + */ +#define BT_UUID_MCS_MEDIA_STATE_VAL 0x2ba3 +/** + * @brief Media State + */ +#define BT_UUID_MCS_MEDIA_STATE BT_UUID_DECLARE_16(BT_UUID_MCS_MEDIA_STATE_VAL) +/** + * @brief Media Control Point value + */ +#define BT_UUID_MCS_MEDIA_CONTROL_POINT_VAL 0x2ba4 +/** + * @brief Media Control Point + */ +#define BT_UUID_MCS_MEDIA_CONTROL_POINT BT_UUID_DECLARE_16(BT_UUID_MCS_MEDIA_CONTROL_POINT_VAL) +/** + * @brief Media control opcodes supported value + */ +#define BT_UUID_MCS_MEDIA_CONTROL_OPCODES_VAL 0x2ba5 +/** + * @brief Media control opcodes supported + */ +#define BT_UUID_MCS_MEDIA_CONTROL_OPCODES BT_UUID_DECLARE_16(BT_UUID_MCS_MEDIA_CONTROL_OPCODES_VAL) +/** + * @brief Search result object ID value + */ +#define BT_UUID_MCS_SEARCH_RESULTS_OBJ_ID_VAL 0x2ba6 +/** + * @brief Search result object ID + */ +#define BT_UUID_MCS_SEARCH_RESULTS_OBJ_ID BT_UUID_DECLARE_16(BT_UUID_MCS_SEARCH_RESULTS_OBJ_ID_VAL) +/** + * @brief Search control point value + */ +#define BT_UUID_MCS_SEARCH_CONTROL_POINT_VAL 0x2ba7 +/** + * @brief Search control point + */ +#define BT_UUID_MCS_SEARCH_CONTROL_POINT BT_UUID_DECLARE_16(BT_UUID_MCS_SEARCH_CONTROL_POINT_VAL) +/** + * @brief GATT Characteristic Energy 32 UUID Value + */ +#define BT_UUID_GATT_E32_VAL 0x2ba8 +/** + * @brief GATT Characteristic Energy 32 + */ +#define BT_UUID_GATT_E32 BT_UUID_DECLARE_16(BT_UUID_GATT_E32_VAL) + +/** + * @brief Media Player Icon Object Type value + */ +#define BT_UUID_OTS_TYPE_MPL_ICON_VAL 0x2ba9 +/** + * @brief Media Player Icon Object Type + */ +#define BT_UUID_OTS_TYPE_MPL_ICON BT_UUID_DECLARE_16(BT_UUID_OTS_TYPE_MPL_ICON_VAL) +/** + * @brief Track Segments Object Type value + */ +#define BT_UUID_OTS_TYPE_TRACK_SEGMENT_VAL 0x2baa +/** + * @brief Track Segments Object Type + */ +#define BT_UUID_OTS_TYPE_TRACK_SEGMENT BT_UUID_DECLARE_16(BT_UUID_OTS_TYPE_TRACK_SEGMENT_VAL) +/** + * @brief Track Object Type value + */ +#define BT_UUID_OTS_TYPE_TRACK_VAL 0x2bab +/** + * @brief Track Object Type + */ +#define BT_UUID_OTS_TYPE_TRACK BT_UUID_DECLARE_16(BT_UUID_OTS_TYPE_TRACK_VAL) +/** + * @brief Group Object Type value + */ +#define BT_UUID_OTS_TYPE_GROUP_VAL 0x2bac +/** + * @brief Group Object Type + */ +#define BT_UUID_OTS_TYPE_GROUP BT_UUID_DECLARE_16(BT_UUID_OTS_TYPE_GROUP_VAL) +/** + * @brief GATT Characteristic Constant Tone Extension Enable UUID Value + */ +#define BT_UUID_GATT_CTEE_VAL 0x2bad +/** + * @brief GATT Characteristic Constant Tone Extension Enable + */ +#define BT_UUID_GATT_CTEE BT_UUID_DECLARE_16(BT_UUID_GATT_CTEE_VAL) +/** + * @brief GATT Characteristic Advertising Constant Tone Extension Minimum + * Length UUID Value + */ +#define BT_UUID_GATT_ACTEML_VAL 0x2bae +/** + * @brief GATT Characteristic Advertising Constant Tone Extension Minimum + * Length + */ +#define BT_UUID_GATT_ACTEML BT_UUID_DECLARE_16(BT_UUID_GATT_ACTEML_VAL) +/** + * @brief GATT Characteristic Advertising Constant Tone Extension Minimum + * Transmit Count UUID Value + */ +#define BT_UUID_GATT_ACTEMTC_VAL 0x2baf +/** + * @brief GATT Characteristic Advertising Constant Tone Extension Minimum + * Transmit Count + */ +#define BT_UUID_GATT_ACTEMTC BT_UUID_DECLARE_16(BT_UUID_GATT_ACTEMTC_VAL) +/** + * @brief GATT Characteristic Advertising Constant Tone Extension Transmit + * Duration UUID Value + */ +#define BT_UUID_GATT_ACTETD_VAL 0x2bb0 +/** + * @brief GATT Characteristic Advertising Constant Tone Extension Transmit + * Duration + */ +#define BT_UUID_GATT_ACTETD BT_UUID_DECLARE_16(BT_UUID_GATT_ACTETD_VAL) +/** + * @brief GATT Characteristic Advertising Constant Tone Extension Interval + * UUID Value + */ +#define BT_UUID_GATT_ACTEI_VAL 0x2bb1 +/** + * @brief GATT Characteristic Advertising Constant Tone Extension Interval + */ +#define BT_UUID_GATT_ACTEI BT_UUID_DECLARE_16(BT_UUID_GATT_ACTEI_VAL) +/** + * @brief GATT Characteristic Advertising Constant Tone Extension PHY UUID + * Value + */ +#define BT_UUID_GATT_ACTEP_VAL 0x2bb2 +/** + * @brief GATT Characteristic Advertising Constant Tone Extension PHY + */ +#define BT_UUID_GATT_ACTEP BT_UUID_DECLARE_16(BT_UUID_GATT_ACTEP_VAL) +/** + * @brief Bearer Provider Name value + */ +#define BT_UUID_TBS_PROVIDER_NAME_VAL 0x2bb3 +/** + * @brief Bearer Provider Name + */ +#define BT_UUID_TBS_PROVIDER_NAME BT_UUID_DECLARE_16(BT_UUID_TBS_PROVIDER_NAME_VAL) +/** + * @brief Bearer UCI value + */ +#define BT_UUID_TBS_UCI_VAL 0x2bb4 +/** + * @brief Bearer UCI + */ +#define BT_UUID_TBS_UCI BT_UUID_DECLARE_16(BT_UUID_TBS_UCI_VAL) +/** + * @brief Bearer Technology value + */ +#define BT_UUID_TBS_TECHNOLOGY_VAL 0x2bb5 +/** + * @brief Bearer Technology + */ +#define BT_UUID_TBS_TECHNOLOGY BT_UUID_DECLARE_16(BT_UUID_TBS_TECHNOLOGY_VAL) +/** + * @brief Bearer URI Prefixes Supported List value + */ +#define BT_UUID_TBS_URI_LIST_VAL 0x2bb6 +/** + * @brief Bearer URI Prefixes Supported List + */ +#define BT_UUID_TBS_URI_LIST BT_UUID_DECLARE_16(BT_UUID_TBS_URI_LIST_VAL) +/** + * @brief Bearer Signal Strength value + */ +#define BT_UUID_TBS_SIGNAL_STRENGTH_VAL 0x2bb7 +/** + * @brief Bearer Signal Strength + */ +#define BT_UUID_TBS_SIGNAL_STRENGTH BT_UUID_DECLARE_16(BT_UUID_TBS_SIGNAL_STRENGTH_VAL) +/** + * @brief Bearer Signal Strength Reporting Interval value + */ +#define BT_UUID_TBS_SIGNAL_INTERVAL_VAL 0x2bb8 +/** + * @brief Bearer Signal Strength Reporting Interval + */ +#define BT_UUID_TBS_SIGNAL_INTERVAL BT_UUID_DECLARE_16(BT_UUID_TBS_SIGNAL_INTERVAL_VAL) +/** + * @brief Bearer List Current Calls value + */ +#define BT_UUID_TBS_LIST_CURRENT_CALLS_VAL 0x2bb9 +/** + * @brief Bearer List Current Calls + */ +#define BT_UUID_TBS_LIST_CURRENT_CALLS BT_UUID_DECLARE_16(BT_UUID_TBS_LIST_CURRENT_CALLS_VAL) +/** + * @brief Content Control ID value + */ +#define BT_UUID_CCID_VAL 0x2bba +/** + * @brief Content Control ID + */ +#define BT_UUID_CCID BT_UUID_DECLARE_16(BT_UUID_CCID_VAL) +/** + * @brief Status flags value + */ +#define BT_UUID_TBS_STATUS_FLAGS_VAL 0x2bbb +/** + * @brief Status flags + */ +#define BT_UUID_TBS_STATUS_FLAGS BT_UUID_DECLARE_16(BT_UUID_TBS_STATUS_FLAGS_VAL) +/** + * @brief Incoming Call Target Caller ID value + */ +#define BT_UUID_TBS_INCOMING_URI_VAL 0x2bbc +/** + * @brief Incoming Call Target Caller ID + */ +#define BT_UUID_TBS_INCOMING_URI BT_UUID_DECLARE_16(BT_UUID_TBS_INCOMING_URI_VAL) +/** + * @brief Call State value + */ +#define BT_UUID_TBS_CALL_STATE_VAL 0x2bbd +/** + * @brief Call State + */ +#define BT_UUID_TBS_CALL_STATE BT_UUID_DECLARE_16(BT_UUID_TBS_CALL_STATE_VAL) +/** + * @brief Call Control Point value + */ +#define BT_UUID_TBS_CALL_CONTROL_POINT_VAL 0x2bbe +/** + * @brief Call Control Point + */ +#define BT_UUID_TBS_CALL_CONTROL_POINT BT_UUID_DECLARE_16(BT_UUID_TBS_CALL_CONTROL_POINT_VAL) +/** + * @brief Optional Opcodes value + */ +#define BT_UUID_TBS_OPTIONAL_OPCODES_VAL 0x2bbf +/** + * @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) +/** + * @brief Incoming Call value + */ +#define BT_UUID_TBS_INCOMING_CALL_VAL 0x2bc1 +/** + * @brief Incoming Call + */ +#define BT_UUID_TBS_INCOMING_CALL BT_UUID_DECLARE_16(BT_UUID_TBS_INCOMING_CALL_VAL) +/** + * @brief Incoming Call Friendly name value + */ +#define BT_UUID_TBS_FRIENDLY_NAME_VAL 0x2bc2 +/** + * @brief Incoming Call Friendly name + */ +#define BT_UUID_TBS_FRIENDLY_NAME BT_UUID_DECLARE_16(BT_UUID_TBS_FRIENDLY_NAME_VAL) +/** + * @brief Microphone Control Service Mute value + */ +#define BT_UUID_MICS_MUTE_VAL 0x2bc3 +/** + * @brief Microphone Control Service Mute + */ +#define BT_UUID_MICS_MUTE BT_UUID_DECLARE_16(BT_UUID_MICS_MUTE_VAL) +/** + * @brief Audio Stream Endpoint Sink Characteristic value + */ +#define BT_UUID_ASCS_ASE_SNK_VAL 0x2bc4 +/** + * @brief Audio Stream Endpoint Sink Characteristic + */ +#define BT_UUID_ASCS_ASE_SNK BT_UUID_DECLARE_16(BT_UUID_ASCS_ASE_SNK_VAL) +/** + * @brief Audio Stream Endpoint Source Characteristic value + */ +#define BT_UUID_ASCS_ASE_SRC_VAL 0x2bc5 +/** + * @brief Audio Stream Endpoint Source Characteristic + */ +#define BT_UUID_ASCS_ASE_SRC BT_UUID_DECLARE_16(BT_UUID_ASCS_ASE_SRC_VAL) +/** + * @brief Audio Stream Endpoint Control Point Characteristic value + */ +#define BT_UUID_ASCS_ASE_CP_VAL 0x2bc6 +/** + * @brief Audio Stream Endpoint Control Point Characteristic + */ +#define BT_UUID_ASCS_ASE_CP BT_UUID_DECLARE_16(BT_UUID_ASCS_ASE_CP_VAL) +/** + * @brief Broadcast Audio Scan Service Scan State value + */ +#define BT_UUID_BASS_CONTROL_POINT_VAL 0x2bc7 +/** + * @brief Broadcast Audio Scan Service Scan State + */ +#define BT_UUID_BASS_CONTROL_POINT BT_UUID_DECLARE_16(BT_UUID_BASS_CONTROL_POINT_VAL) +/** + * @brief Broadcast Audio Scan Service Receive State value + */ +#define BT_UUID_BASS_RECV_STATE_VAL 0x2bc8 +/** + * @brief Broadcast Audio Scan Service Receive State + */ +#define BT_UUID_BASS_RECV_STATE BT_UUID_DECLARE_16(BT_UUID_BASS_RECV_STATE_VAL) +/** + * @brief Sink PAC Characteristic value + */ +#define BT_UUID_PACS_SNK_VAL 0x2bc9 +/** + * @brief Sink PAC Characteristic + */ +#define BT_UUID_PACS_SNK BT_UUID_DECLARE_16(BT_UUID_PACS_SNK_VAL) +/** + * @brief Sink PAC Locations Characteristic value + */ +#define BT_UUID_PACS_SNK_LOC_VAL 0x2bca +/** + * @brief Sink PAC Locations Characteristic + */ +#define BT_UUID_PACS_SNK_LOC BT_UUID_DECLARE_16(BT_UUID_PACS_SNK_LOC_VAL) +/** + * @brief Source PAC Characteristic value + */ +#define BT_UUID_PACS_SRC_VAL 0x2bcb +/** + * @brief Source PAC Characteristic + */ +#define BT_UUID_PACS_SRC BT_UUID_DECLARE_16(BT_UUID_PACS_SRC_VAL) +/** + * @brief Source PAC Locations Characteristic value + */ +#define BT_UUID_PACS_SRC_LOC_VAL 0x2bcc +/** + * @brief Source PAC Locations Characteristic + */ +#define BT_UUID_PACS_SRC_LOC BT_UUID_DECLARE_16(BT_UUID_PACS_SRC_LOC_VAL) +/** + * @brief Available Audio Contexts Characteristic value + */ +#define BT_UUID_PACS_AVAILABLE_CONTEXT_VAL 0x2bcd +/** + * @brief Available Audio Contexts Characteristic + */ +#define BT_UUID_PACS_AVAILABLE_CONTEXT BT_UUID_DECLARE_16(BT_UUID_PACS_AVAILABLE_CONTEXT_VAL) +/** + * @brief Supported Audio Context Characteristic value + */ +#define BT_UUID_PACS_SUPPORTED_CONTEXT_VAL 0x2bce +/** + * @brief Supported Audio Context Characteristic + */ +#define BT_UUID_PACS_SUPPORTED_CONTEXT BT_UUID_DECLARE_16(BT_UUID_PACS_SUPPORTED_CONTEXT_VAL) +/** + * @brief GATT Characteristic Ammonia Concentration UUID Value + */ +#define BT_UUID_GATT_NH4CONC_VAL 0x2bcf +/** + * @brief GATT Characteristic Ammonia Concentration + */ +#define BT_UUID_GATT_NH4CONC BT_UUID_DECLARE_16(BT_UUID_GATT_NH4CONC_VAL) +/** + * @brief GATT Characteristic Carbon Monoxide Concentration UUID Value + */ +#define BT_UUID_GATT_COCONC_VAL 0x2bd0 +/** + * @brief GATT Characteristic Carbon Monoxide Concentration + */ +#define BT_UUID_GATT_COCONC BT_UUID_DECLARE_16(BT_UUID_GATT_COCONC_VAL) +/** + * @brief GATT Characteristic Methane Concentration UUID Value + */ +#define BT_UUID_GATT_CH4CONC_VAL 0x2bd1 +/** + * @brief GATT Characteristic Methane Concentration + */ +#define BT_UUID_GATT_CH4CONC BT_UUID_DECLARE_16(BT_UUID_GATT_CH4CONC_VAL) +/** + * @brief GATT Characteristic Nitrogen Dioxide Concentration UUID Value + */ +#define BT_UUID_GATT_NO2CONC_VAL 0x2bd2 +/** + * @brief GATT Characteristic Nitrogen Dioxide Concentration + */ +#define BT_UUID_GATT_NO2CONC BT_UUID_DECLARE_16(BT_UUID_GATT_NO2CONC_VAL) +/** + * @brief GATT Characteristic Non-Methane Volatile Organic Compounds + * Concentration UUID Value + */ +#define BT_UUID_GATT_NONCH4CONC_VAL 0x2bd3 +/** + * @brief GATT Characteristic Non-Methane Volatile Organic Compounds + * Concentration + */ +#define BT_UUID_GATT_NONCH4CONC BT_UUID_DECLARE_16(BT_UUID_GATT_NONCH4CONC_VAL) +/** + * @brief GATT Characteristic Ozone Concentration UUID Value + */ +#define BT_UUID_GATT_O3CONC_VAL 0x2bd4 +/** + * @brief GATT Characteristic Ozone Concentration + */ +#define BT_UUID_GATT_O3CONC BT_UUID_DECLARE_16(BT_UUID_GATT_O3CONC_VAL) +/** + * @brief GATT Characteristic Particulate Matter - PM1 Concentration UUID + * Value + */ +#define BT_UUID_GATT_PM1CONC_VAL 0x2bd5 +/** + * @brief GATT Characteristic Particulate Matter - PM1 Concentration + */ +#define BT_UUID_GATT_PM1CONC BT_UUID_DECLARE_16(BT_UUID_GATT_PM1CONC_VAL) +/** + * @brief GATT Characteristic Particulate Matter - PM2.5 Concentration UUID + * Value + */ +#define BT_UUID_GATT_PM25CONC_VAL 0x2bd6 +/** + * @brief GATT Characteristic Particulate Matter - PM2.5 Concentration + */ +#define BT_UUID_GATT_PM25CONC BT_UUID_DECLARE_16(BT_UUID_GATT_PM25CONC_VAL) +/** + * @brief GATT Characteristic Particulate Matter - PM10 Concentration UUID + * Value + */ +#define BT_UUID_GATT_PM10CONC_VAL 0x2bd7 +/** + * @brief GATT Characteristic Particulate Matter - PM10 Concentration + */ +#define BT_UUID_GATT_PM10CONC BT_UUID_DECLARE_16(BT_UUID_GATT_PM10CONC_VAL) +/** + * @brief GATT Characteristic Sulfur Dioxide Concentration UUID Value + */ +#define BT_UUID_GATT_SO2CONC_VAL 0x2bd8 +/** + * @brief GATT Characteristic Sulfur Dioxide Concentration + */ +#define BT_UUID_GATT_SO2CONC BT_UUID_DECLARE_16(BT_UUID_GATT_SO2CONC_VAL) +/** + * @brief GATT Characteristic Sulfur Hexafluoride Concentration UUID Value + */ +#define BT_UUID_GATT_SF6CONC_VAL 0x2bd9 +/** + * @brief GATT Characteristic Sulfur Hexafluoride Concentration + */ +#define BT_UUID_GATT_SF6CONC BT_UUID_DECLARE_16(BT_UUID_GATT_SF6CONC_VAL) +/** + * @brief Hearing Aid Features Characteristic value + */ +#define BT_UUID_HAS_HEARING_AID_FEATURES_VAL 0x2bda +/** + * @brief Hearing Aid Features Characteristic + */ +#define BT_UUID_HAS_HEARING_AID_FEATURES BT_UUID_DECLARE_16(BT_UUID_HAS_HEARING_AID_FEATURES_VAL) +/** + * @brief Hearing Aid Preset Control Point Characteristic value + */ +#define BT_UUID_HAS_PRESET_CONTROL_POINT_VAL 0x2bdb +/** + * @brief Hearing Aid Preset Control Point Characteristic + */ +#define BT_UUID_HAS_PRESET_CONTROL_POINT BT_UUID_DECLARE_16(BT_UUID_HAS_PRESET_CONTROL_POINT_VAL) +/** + * @brief Active Preset Index Characteristic value + */ +#define BT_UUID_HAS_ACTIVE_PRESET_INDEX_VAL 0x2bdc +/** + * @brief Active Preset Index Characteristic + */ +#define BT_UUID_HAS_ACTIVE_PRESET_INDEX BT_UUID_DECLARE_16(BT_UUID_HAS_ACTIVE_PRESET_INDEX_VAL) +/** + * @brief GATT Characteristic Fixed String 64 UUID Value + */ +#define BT_UUID_GATT_FSTR64_VAL 0x2bde +/** + * @brief GATT Characteristic Fixed String 64 + */ +#define BT_UUID_GATT_FSTR64 BT_UUID_DECLARE_16(BT_UUID_GATT_FSTR64_VAL) +/** + * @brief GATT Characteristic High Temperature UUID Value + */ +#define BT_UUID_GATT_HITEMP_VAL 0x2bdf +/** + * @brief GATT Characteristic High Temperature + */ +#define BT_UUID_GATT_HITEMP BT_UUID_DECLARE_16(BT_UUID_GATT_HITEMP_VAL) +/** + * @brief GATT Characteristic High Voltage UUID Value + */ +#define BT_UUID_GATT_HV_VAL 0x2be0 +/** + * @brief GATT Characteristic High Voltage + */ +#define BT_UUID_GATT_HV BT_UUID_DECLARE_16(BT_UUID_GATT_HV_VAL) +/** + * @brief GATT Characteristic Light Distribution UUID Value + */ +#define BT_UUID_GATT_LD_VAL 0x2be1 +/** + * @brief GATT Characteristic Light Distribution + */ +#define BT_UUID_GATT_LD BT_UUID_DECLARE_16(BT_UUID_GATT_LD_VAL) +/** + * @brief GATT Characteristic Light Output UUID Value + */ +#define BT_UUID_GATT_LO_VAL 0x2be2 +/** + * @brief GATT Characteristic Light Output + */ +#define BT_UUID_GATT_LO BT_UUID_DECLARE_16(BT_UUID_GATT_LO_VAL) +/** + * @brief GATT Characteristic Light Source Type UUID Value + */ +#define BT_UUID_GATT_LST_VAL 0x2be3 +/** + * @brief GATT Characteristic Light Source Type + */ +#define BT_UUID_GATT_LST BT_UUID_DECLARE_16(BT_UUID_GATT_LST_VAL) +/** + * @brief GATT Characteristic Noise UUID Value + */ +#define BT_UUID_GATT_NOISE_VAL 0x2be4 +/** + * @brief GATT Characteristic Noise + */ +#define BT_UUID_GATT_NOISE BT_UUID_DECLARE_16(BT_UUID_GATT_NOISE_VAL) +/** + * @brief GATT Characteristic Relative Runtime in a Correlated Color + * Temperature Range UUID Value + */ +#define BT_UUID_GATT_RRCCTP_VAL 0x2be5 +/** + * @brief GATT Characteristic Relative Runtime in a Correlated Color + * Temperature Range + */ +#define BT_UUID_GATT_RRCCTR BT_UUID_DECLARE_16(BT_UUID_GATT_RRCCTR_VAL) +/** + * @brief GATT Characteristic Time Second 32 UUID Value + */ +#define BT_UUID_GATT_TIM_S32_VAL 0x2be6 +/** + * @brief GATT Characteristic Time Second 32 + */ +#define BT_UUID_GATT_TIM_S32 BT_UUID_DECLARE_16(BT_UUID_GATT_TIM_S32_VAL) +/** + * @brief GATT Characteristic VOC Concentration UUID Value + */ +#define BT_UUID_GATT_VOCCONC_VAL 0x2be7 +/** + * @brief GATT Characteristic VOC Concentration + */ +#define BT_UUID_GATT_VOCCONC BT_UUID_DECLARE_16(BT_UUID_GATT_VOCCONC_VAL) +/** + * @brief GATT Characteristic Voltage Frequency UUID Value + */ +#define BT_UUID_GATT_VF_VAL 0x2be8 +/** + * @brief GATT Characteristic Voltage Frequency + */ +#define BT_UUID_GATT_VF BT_UUID_DECLARE_16(BT_UUID_GATT_VF_VAL) +/** + * @brief BAS Characteristic Battery Critical Status UUID Value + */ +#define BT_UUID_BAS_BATTERY_CRIT_STATUS_VAL 0x2be9 +/** + * @brief BAS Characteristic Battery Critical Status + */ +#define BT_UUID_BAS_BATTERY_CRIT_STATUS BT_UUID_DECLARE_16(BT_UUID_BAS_BATTERY_CRIT_STATUS_VAL) +/** + * @brief BAS Characteristic Battery Health Status UUID Value + */ +#define BT_UUID_BAS_BATTERY_HEALTH_STATUS_VAL 0x2bea +/** + * @brief BAS Characteristic Battery Health Status + */ +#define BT_UUID_BAS_BATTERY_HEALTH_STATUS BT_UUID_DECLARE_16(BT_UUID_BAS_BATTERY_HEALTH_STATUS_VAL) +/** + * @brief BAS Characteristic Battery Health Information UUID Value + */ +#define BT_UUID_BAS_BATTERY_HEALTH_INF_VAL 0x2beb +/** + * @brief BAS Characteristic Battery Health Information + */ +#define BT_UUID_BAS_BATTERY_HEALTH_INF BT_UUID_DECLARE_16(BT_UUID_BAS_BATTERY_HEALTH_INF_VAL) +/** + * @brief BAS Characteristic Battery Information UUID Value + */ +#define BT_UUID_BAS_BATTERY_INF_VAL 0x2bec +/** + * @brief BAS Characteristic Battery Information + */ +#define BT_UUID_BAS_BATTERY_INF BT_UUID_DECLARE_16(BT_UUID_BAS_BATTERY_INF_VAL) +/** + * @brief BAS Characteristic Battery Level Status UUID Value + */ +#define BT_UUID_BAS_BATTERY_LEVEL_STATUS_VAL 0x2bed +/** + * @brief BAS Characteristic Battery Level Status + */ +#define BT_UUID_BAS_BATTERY_LEVEL_STATUS BT_UUID_DECLARE_16(BT_UUID_BAS_BATTERY_LEVEL_STATUS_VAL) +/** + * @brief BAS Characteristic Battery Time Status UUID Value + */ +#define BT_UUID_BAS_BATTERY_TIME_STATUS_VAL 0x2bee +/** + * @brief BAS Characteristic Battery Time Status + */ +#define BT_UUID_BAS_BATTERY_TIME_STATUS BT_UUID_DECLARE_16(BT_UUID_BAS_BATTERY_TIME_STATUS_VAL) +/** + * @brief GATT Characteristic Estimated Service Date UUID Value + */ +#define BT_UUID_GATT_ESD_VAL 0x2bef +/** + * @brief GATT Characteristic Estimated Service Date + */ +#define BT_UUID_GATT_ESD BT_UUID_DECLARE_16(BT_UUID_GATT_ESD_VAL) +/** + * @brief BAS Characteristic Battery Energy Status UUID Value + */ +#define BT_UUID_BAS_BATTERY_ENERGY_STATUS_VAL 0x2bf0 +/** + * @brief BAS Characteristic Battery Energy Status + */ +#define BT_UUID_BAS_BATTERY_ENERGY_STATUS BT_UUID_DECLARE_16(BT_UUID_BAS_BATTERY_ENERGY_STATUS_VAL) +/** + * @brief ETS Characteristic Current Elapsed Time UUID Value + */ +#define BT_UUID_ETS_CURRENT_ELAPSED_TIME_VAL 0x2bf2 +/** + * @brief ETS Characteristic Current Elapsed Time + */ +#define BT_UUID_ETS_CURRENT_ELAPSED_TIME BT_UUID_DECLARE_16(BT_UUID_ETS_CURRENT_ELAPSED_TIME_VAL) +/** + * @brief GATT Characteristic LE GATT Security Levels UUID Value + */ +#define BT_UUID_GATT_SL_VAL 0x2bf5 +/** + * @brief GATT Characteristic LE GATT Security Levels + */ +#define BT_UUID_GATT_SL BT_UUID_DECLARE_16(BT_UUID_GATT_SL_VAL) + +/** + * @brief GATT Characteristic UDI for Medical Devices UUID Value + */ +#define BT_UUID_UDI_FOR_MEDICAL_DEVICES_VAL 0x2bff +/** + * @brief GATT Characteristic UDI for Medical Devices + */ +#define BT_UUID_UDI_FOR_MEDICAL_DEVICES BT_UUID_DECLARE_16(BT_UUID_UDI_FOR_MEDICAL_DEVICES_VAL) + +/** + * @brief Gaming Audio Service UUID value + */ +#define BT_UUID_GMAS_VAL 0x1858 +/** + * @brief Gaming Audio Service + */ +#define BT_UUID_GMAS BT_UUID_DECLARE_16(BT_UUID_GMAS_VAL) + +/** + * @brief Gaming Audio Profile Role UUID value + */ +#define BT_UUID_GMAP_ROLE_VAL 0x2C00 +/** + * @brief Gaming Audio Profile Role + */ +#define BT_UUID_GMAP_ROLE BT_UUID_DECLARE_16(BT_UUID_GMAP_ROLE_VAL) + +/** + * @brief Gaming Audio Profile Unicast Game Gateway Features UUID value + */ +#define BT_UUID_GMAP_UGG_FEAT_VAL 0x2C01 +/** + * @brief Gaming Audio Profile Unicast Game Gateway Features + */ +#define BT_UUID_GMAP_UGG_FEAT BT_UUID_DECLARE_16(BT_UUID_GMAP_UGG_FEAT_VAL) + +/** + * @brief Gaming Audio Profile Unicast Game Terminal Features UUID value + */ +#define BT_UUID_GMAP_UGT_FEAT_VAL 0x2C02 +/** + * @brief Gaming Audio Profile Unicast Game Terminal Features + */ +#define BT_UUID_GMAP_UGT_FEAT BT_UUID_DECLARE_16(BT_UUID_GMAP_UGT_FEAT_VAL) + +/** + * @brief Gaming Audio Profile Broadcast Game Sender Features UUID value + */ +#define BT_UUID_GMAP_BGS_FEAT_VAL 0x2C03 +/** + * @brief Gaming Audio Profile Broadcast Game Sender Features + */ +#define BT_UUID_GMAP_BGS_FEAT BT_UUID_DECLARE_16(BT_UUID_GMAP_BGS_FEAT_VAL) + +/** + * @brief Gaming Audio Profile Broadcast Game Receiver Features UUID value + */ +#define BT_UUID_GMAP_BGR_FEAT_VAL 0x2C04 +/** + * @brief Gaming Audio Profile Broadcast Game Receiver Features + */ +#define BT_UUID_GMAP_BGR_FEAT BT_UUID_DECLARE_16(BT_UUID_GMAP_BGR_FEAT_VAL) + +/** + * @brief HID SCI (Shorter Connection Intervals) Mode UUID value + */ +#define BT_UUID_HIDS_SCI_MODE_VAL 0x2C39 +/** + * @brief HID SCI (Shorter Connection Intervals) Mode + */ +#define BT_UUID_HIDS_SCI_MODE BT_UUID_DECLARE_16(BT_UUID_HIDS_SCI_MODE_VAL) + +/** + * @brief HID SCI (Shorter Connection Intervals) Information UUID value + */ +#define BT_UUID_HIDS_SCI_INFO_VAL 0x2C3A +/** + * @brief HID SCI (Shorter Connection Intervals) Information + */ +#define BT_UUID_HIDS_SCI_INFO BT_UUID_DECLARE_16(BT_UUID_HIDS_SCI_INFO_VAL) + +/* Protocol UUIDs */ +#define BT_UUID_SDP_VAL 0x0001 +#define BT_UUID_SDP BT_UUID_DECLARE_16(BT_UUID_SDP_VAL) +#define BT_UUID_UDP_VAL 0x0002 +#define BT_UUID_UDP BT_UUID_DECLARE_16(BT_UUID_UDP_VAL) +#define BT_UUID_RFCOMM_VAL 0x0003 +#define BT_UUID_RFCOMM BT_UUID_DECLARE_16(BT_UUID_RFCOMM_VAL) +#define BT_UUID_TCP_VAL 0x0004 +#define BT_UUID_TCP BT_UUID_DECLARE_16(BT_UUID_TCP_VAL) +#define BT_UUID_TCS_BIN_VAL 0x0005 +#define BT_UUID_TCS_BIN BT_UUID_DECLARE_16(BT_UUID_TCS_BIN_VAL) +#define BT_UUID_TCS_AT_VAL 0x0006 +#define BT_UUID_TCS_AT BT_UUID_DECLARE_16(BT_UUID_TCS_AT_VAL) +#define BT_UUID_ATT_VAL 0x0007 +#define BT_UUID_ATT BT_UUID_DECLARE_16(BT_UUID_ATT_VAL) +#define BT_UUID_OBEX_VAL 0x0008 +#define BT_UUID_OBEX BT_UUID_DECLARE_16(BT_UUID_OBEX_VAL) +#define BT_UUID_IP_VAL 0x0009 +#define BT_UUID_IP BT_UUID_DECLARE_16(BT_UUID_IP_VAL) +#define BT_UUID_FTP_VAL 0x000a +#define BT_UUID_FTP BT_UUID_DECLARE_16(BT_UUID_FTP_VAL) +#define BT_UUID_HTTP_VAL 0x000c +#define BT_UUID_HTTP BT_UUID_DECLARE_16(BT_UUID_HTTP_VAL) +#define BT_UUID_WSP_VAL 0x000e +#define BT_UUID_WSP BT_UUID_DECLARE_16(BT_UUID_WSP_VAL) +#define BT_UUID_BNEP_VAL 0x000f +#define BT_UUID_BNEP BT_UUID_DECLARE_16(BT_UUID_BNEP_VAL) +#define BT_UUID_UPNP_VAL 0x0010 +#define BT_UUID_UPNP BT_UUID_DECLARE_16(BT_UUID_UPNP_VAL) +#define BT_UUID_HIDP_VAL 0x0011 +#define BT_UUID_HIDP BT_UUID_DECLARE_16(BT_UUID_HIDP_VAL) +#define BT_UUID_HCRP_CTRL_VAL 0x0012 +#define BT_UUID_HCRP_CTRL BT_UUID_DECLARE_16(BT_UUID_HCRP_CTRL_VAL) +#define BT_UUID_HCRP_DATA_VAL 0x0014 +#define BT_UUID_HCRP_DATA BT_UUID_DECLARE_16(BT_UUID_HCRP_DATA_VAL) +#define BT_UUID_HCRP_NOTE_VAL 0x0016 +#define BT_UUID_HCRP_NOTE BT_UUID_DECLARE_16(BT_UUID_HCRP_NOTE_VAL) +#define BT_UUID_AVCTP_VAL 0x0017 +#define BT_UUID_AVCTP BT_UUID_DECLARE_16(BT_UUID_AVCTP_VAL) +#define BT_UUID_AVCTP_BROWSING_VAL 0x0018 +#define BT_UUID_AVCTP_BROWSING BT_UUID_DECLARE_16(BT_UUID_AVCTP_BROWSING_VAL) +#define BT_UUID_AVDTP_VAL 0x0019 +#define BT_UUID_AVDTP BT_UUID_DECLARE_16(BT_UUID_AVDTP_VAL) +#define BT_UUID_CMTP_VAL 0x001b +#define BT_UUID_CMTP BT_UUID_DECLARE_16(BT_UUID_CMTP_VAL) +#define BT_UUID_UDI_VAL 0x001d +#define BT_UUID_UDI BT_UUID_DECLARE_16(BT_UUID_UDI_VAL) +#define BT_UUID_MCAP_CTRL_VAL 0x001e +#define BT_UUID_MCAP_CTRL BT_UUID_DECLARE_16(BT_UUID_MCAP_CTRL_VAL) +#define BT_UUID_MCAP_DATA_VAL 0x001f +#define BT_UUID_MCAP_DATA BT_UUID_DECLARE_16(BT_UUID_MCAP_DATA_VAL) +#define BT_UUID_L2CAP_VAL 0x0100 +#define BT_UUID_L2CAP BT_UUID_DECLARE_16(BT_UUID_L2CAP_VAL) + +/** @brief Compare Bluetooth UUIDs. + * + * Compares 2 Bluetooth UUIDs, if the types are different both UUIDs are + * first converted to 128 bits format before comparing. + * + * @param u1 First Bluetooth UUID to compare + * @param u2 Second Bluetooth UUID to compare + * + * @return negative value if @a u1 < @a u2, 0 if @a u1 == @a u2, else + * positive + */ + +int bt_uuid_cmp(const struct bt_uuid *u1, const struct bt_uuid *u2); + +/** @brief Create a bt_uuid from a little-endian data buffer. + * + * Create a bt_uuid from a little-endian data buffer. The data_len parameter + * is used to determine whether the UUID is in 16, 32 or 128 bit format + * (length 2, 4 or 16). Note: 32 bit format is not allowed over the air. + * + * @param uuid Pointer to the bt_uuid variable + * @param data pointer to UUID stored in little-endian data buffer + * @param data_len length of the UUID in the data buffer + * + * @return true if the data was valid and the UUID was successfully created. + */ + +bool bt_uuid_create(struct bt_uuid *uuid, + const uint8_t *data, uint8_t data_len); + +/** @brief Convert Bluetooth UUID to string. + * + * Converts Bluetooth UUID to string. UUID can be in any format, 16-bit, + * 32-bit or 128-bit. + * + * @param uuid Bluetooth UUID + * @param str pointer where to put converted string + * @param len length of str + */ + +void bt_uuid_to_str(const struct bt_uuid *uuid, char *str, size_t len); + +/** @brief Convert a UUID string to a Bluetooth UUID. + * + * Parses a UUID string and stores the result in a @ref bt_uuid_any. The + * UUID type (16/32/128-bit) is determined by the input string length. + * + * Accepted formats: + * - 4 hex digits (e.g. "180D") -> BT_UUID_TYPE_16 + * - 8 hex digits (e.g. "0000180D") -> BT_UUID_TYPE_32 + * - 36-char canonical form (e.g. "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx") + * -> BT_UUID_TYPE_128 + * + * A full 128-bit UUID string always produces BT_UUID_TYPE_128, even if it + * matches the Bluetooth Base UUID pattern. Use the short forms (4 or 8 hex + * chars) to obtain 16-bit or 32-bit types. + * + * Notes: + * - No "0x" prefix. + * - No leading/trailing whitespace. + * - Only hexadecimal characters (case-insensitive). + * - 128-bit UUIDs must include hyphens in the standard positions. + * - 16/32-bit UUIDs must not include separators or hyphens. + * + * @param str Pointer to the UUID string. + * @param uuid Pointer to a @ref bt_uuid_any to receive the result. + * + * @return 0 on success, or a negative error code on failure. + */ + +int bt_uuid_from_str(const char *str, struct bt_uuid_any *uuid); + +/** @brief Compress a 128-bit UUID to 16-bit or 32-bit if it matches the + * Bluetooth Base UUID. + * + * If @p src is already 16-bit or 32-bit it is copied as-is. A 128-bit UUID + * whose bytes [4..15] match the Bluetooth Base UUID is compressed to 16-bit + * (when bytes [0..1] of the RFC 9562 form are zero) or 32-bit. Non-matching + * 128-bit UUIDs are not compressed and cause the function to return + * -ENOTSUP without modifying @p dst. + * + * @param[in] src Source UUID to compress. + * @param[out] dst Destination to receive the (possibly shorter) UUID. + * + * @retval 0 UUID was compressed (or copied for 16/32-bit input). + * @retval -ENOTSUP 128-bit UUID does not match the Bluetooth Base UUID; + * @p dst is left unmodified. + */ + +int bt_uuid_compress(const struct bt_uuid *src, struct bt_uuid_any *dst); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __INCLUDE_NUTTX_NET_BLUETOOTH_UUID_H */ diff --git a/libs/zblue/CMakeLists.txt b/libs/zblue/CMakeLists.txt new file mode 100644 index 0000000000000..654864bd2fc01 --- /dev/null +++ b/libs/zblue/CMakeLists.txt @@ -0,0 +1,40 @@ +# ############################################################################## +# libs/zblue/CMakeLists.txt +# +# Author: chao an +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +if(CONFIG_BT) + nuttx_add_kernel_library(zblue) + + if(CONFIG_CRYPTO_MBEDTLS) + nuttx_add_dependencies(TARGET zblue DEPENDS mbedtls) + endif() + + add_subdirectory(compat) + add_subdirectory(common) + add_subdirectory(crypto) + add_subdirectory(lib) + add_subdirectory(host) + add_subdirectory(audio) + add_subdirectory(mesh) + add_subdirectory(services) +endif() diff --git a/libs/zblue/Kconfig b/libs/zblue/Kconfig new file mode 100644 index 0000000000000..33ca848a670b9 --- /dev/null +++ b/libs/zblue/Kconfig @@ -0,0 +1,440 @@ +############################################################################ +# libs/zblue/Kconfig +# +# Author: chao an +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +# +# NuttX Bluetooth Host Stack +# + +menuconfig ZBLUE_ENABLED + bool "ZBlue Bluetooth Host Stack" + depends on BUILD_FLAT + default n + ---help--- + Enable the NuttX Bluetooth host stack ported from Zephyr RTOS. + This provides a full BLE host implementation including HCI, L2CAP, + ATT/GATT, SMP, and Mesh support. + + The current Bluetooth host stack requires flat builds. Protected and + kernel builds are not supported yet. + +if ZBLUE_ENABLED + +menuconfig BT + bool "Bluetooth Support" + default y + ---help--- + Enable Bluetooth support in the ZBlue host stack. + +if BT + +config BT_HOST + bool "BLE Host Stack" + default y + ---help--- + Enable the Bluetooth Low Energy (BLE) host stack. + Includes HCI core, L2CAP, ATT/GATT, SMP, advertising and scanning. + +config BT_CONN + bool "BLE Connection Support" + default y + depends on BT_HOST + ---help--- + Enable BLE connection management. Required for GATT client/server + and most BLE profiles. + +config BT_BROADCASTER + bool "Broadcaster Role" + default y + depends on BT_HOST + ---help--- + Enable the Broadcaster role (advertising only, no connections). + +config BT_OBSERVER + bool "Observer Role" + default y + depends on BT_HOST + ---help--- + Enable the Observer role (scanning only, no connections). + +config BT_PERIPHERAL + bool "Peripheral Role" + default y + depends on BT_HOST && BT_CONN + select BT_BROADCASTER + ---help--- + Enable the Peripheral role (advertising + accept connections). + +config BT_CENTRAL + bool "Central Role" + default y + depends on BT_HOST && BT_CONN + select BT_OBSERVER + ---help--- + Enable the Central role (scan + initiate connections). + +config BT_SERVICES + bool "GATT Services/Profiles" + default n + depends on BT_HOST && BT_CONN + ---help--- + Enable standard GATT profiles and services. + +choice BT_LOG_LEVEL_CHOICE + prompt "Bluetooth log level" + default BT_LOG_LEVEL_INF + ---help--- + Sets the global Bluetooth host log level. The selected level enables + messages at that severity and above. + +config BT_LOG_LEVEL_OFF + bool "Off" + +config BT_LOG_LEVEL_ERR + bool "Error" + +config BT_LOG_LEVEL_WRN + bool "Warning" + +config BT_LOG_LEVEL_INF + bool "Info" + +config BT_LOG_LEVEL_DBG + bool "Debug" + +endchoice + +config BT_DEBUG + bool "Bluetooth Debug Logging" + default n + ---help--- + Enable detailed Bluetooth debug logging. + +source "libs/zblue/Kconfig.adv" + +config BT_MAX_CONN + int "Maximum number of simultaneous connections" + depends on BT_CONN + range 1 250 + default 3 + default 2 if BT_MESH_GATT_CLIENT + default 1 + help + Maximum number of simultaneous Bluetooth connections supported. + +config BT_L2CAP_RX_MTU + int + default 251 + +config BT_LOG_LEVEL + int + default 4 if BT_DEBUG + default 4 if BT_LOG_LEVEL_DBG + default 3 if BT_LOG_LEVEL_INF + default 2 if BT_LOG_LEVEL_WRN + default 1 if BT_LOG_LEVEL_ERR + default 0 + +config BT_HCI_CORE_LOG_LEVEL + int + default BT_LOG_LEVEL + +config BT_CONN_TX + bool + default BT_CONN || BT_ISO_TX + help + Hidden configuration that is true if ACL or broadcast ISO is enabled. + +config BT_LE_LOCAL_MINIMUM_REQUIRED_FEATURE_PAGE + int + default 1 if BT_FRAME_SPACE_UPDATE || BT_SHORTER_CONNECTION_INTERVALS + default 0 + depends on BT_LE_EXTENDED_FEAT_SET + help + Minimum required feature page that must be supported. + +config BT_LE_MAX_LOCAL_SUPPORTED_FEATURE_PAGE + int "Maximum supported feature page" + default BT_LE_LOCAL_MINIMUM_REQUIRED_FEATURE_PAGE + range BT_LE_LOCAL_MINIMUM_REQUIRED_FEATURE_PAGE 10 + depends on BT_LE_EXTENDED_FEAT_SET + help + Maximum feature page that can be stored for local supported features. + +config BT_LE_EXTENDED_FEAT_SET + bool "LL Extended Feature Set" + help + Enable support for the LL Extended Feature Set feature. + +if BT_CONN + +config BT_CONN_DYNAMIC_CALLBACKS + bool "Dynamic connection callbacks" + default y + help + Enable support for dynamic connection callbacks. This links in + bt_conn_cb_register(). Disabling this config is safe. It will make + uses of this and associated APIs fail to link. Note: Static registration + through BT_CONN_CB_DEFINE() is always available and does not depend on + this config. + +config BT_HCI_ACL_FLOW_CONTROL + bool "Controller to Host ACL flow control support" + default y + default y if BT_HCI_RAW + help + Enable support for throttling ACL buffers from the controller to the host. + +config BT_REMOTE_VERSION + bool "Allow fetching of remote version" + default y if BT_HCI_RAW + help + Enable this to get access to the remote version in the Controller and + in the Host through bt_conn_get_remote_info(). + +config BT_PHY_UPDATE + bool "PHY Update" + default y + help + Enable support for Bluetooth 5.0 PHY Update Procedure. + +config BT_DATA_LEN_UPDATE + bool "Data Length Update" + default y if ((BT_BUF_ACL_TX_SIZE > 27) || (BT_BUF_ACL_RX_SIZE > 27)) + help + Enable support for Bluetooth v4.2 LE Data Length Update procedure. + +config BT_PER_ADV_SYNC_TRANSFER_RECEIVER + bool "Periodic Advertising Sync Transfer receiver" + depends on BT_PER_ADV_SYNC && BT_CONN + +config BT_PER_ADV_SYNC_TRANSFER_SENDER + bool "Periodic Advertising Sync Transfer sender" + depends on (BT_PER_ADV_SYNC || BT_PER_ADV) && BT_CONN + +config BT_SCA_UPDATE + bool "Sleep Clock Accuracy Update" + default y if BT_ISO_PERIPHERAL || BT_ISO_CENTRAL + help + Enable support for Bluetooth 5.1 Sleep Clock Accuracy Update Procedure. + +config BT_TRANSMIT_POWER_CONTROL + bool "LE Power Control" + help + Enable support for LE Power Control Request feature. + +config BT_PATH_LOSS_MONITORING + bool "LE Path Loss Monitoring" + help + Enable support for LE Path Loss Monitoring feature. + +config BT_SUBRATING + bool "LE Connection Subrating" + help + Enable support for LE Connection Subrating feature. + +config BT_FRAME_SPACE_UPDATE + bool "Frame Space Update" + depends on BT_CONN && BT_LE_EXTENDED_FEAT_SET + help + Enable support for Bluetooth 6.0 Frame Space Update feature. + +config BT_CHANNEL_SOUNDING + bool "Channel Sounding [EXPERIMENTAL]" + help + Enable support for Bluetooth 6.0 Channel Sounding feature. + +config BT_CHANNEL_SOUNDING_TEST + bool "Channel Sounding Test [EXPERIMENTAL]" + depends on BT_CHANNEL_SOUNDING + help + Enable support for Channel Sounding test mode. + +config BT_CHANNEL_SOUNDING_REASSEMBLY_BUFFER_SIZE + int "Subevent result reassembly buffer size" + depends on BT_CHANNEL_SOUNDING + range 239 5600 + default 5600 + help + Size of the reassembly buffer for Channel Sounding subevent results. + +config BT_CHANNEL_SOUNDING_REASSEMBLY_BUFFER_CNT + int "Subevent result reassembly buffer count" + depends on BT_CHANNEL_SOUNDING + range 1 BT_MAX_CONN + default 1 + help + Controls the number of reassembly buffers for Channel Sounding procedures. + +config BT_SHORTER_CONNECTION_INTERVALS + bool "Shorter Connection Intervals" + depends on BT_LE_EXTENDED_FEAT_SET + depends on BT_SUBRATING + help + Enable support for the Bluetooth 6.2 Shorter Connection Intervals feature. + +endif # BT_CONN + +config BT_HCI_RAW + bool + default n + +config BT_ISO + bool + default n + +config BT_ISO_TX + bool + +config BT_ISO_RX + bool + +config BT_ISO_UNICAST + bool + depends on BT_CONN + select BT_ISO + select BT_ISO_TX + select BT_ISO_RX + help + This option enables support for Bluetooth Unicast Isochronous channels. + +config BT_ISO_CENTRAL + bool "Bluetooth Isochronous Channel Unicast Central Support" + depends on BT_CENTRAL + select BT_ISO_UNICAST + help + This option enables support for Bluetooth Broadcast Isochronous channels + for the central role. + +config BT_ISO_PERIPHERAL + bool "Bluetooth Isochronous Channel Unicast Peripheral Support" + depends on BT_PERIPHERAL + select BT_ISO_UNICAST + help + This option enables support for Bluetooth Unicast Isochronous channels + for the peripheral role. + +config BT_ISO_BROADCAST + bool + select BT_ISO + select BT_EXT_ADV + +config BT_ISO_BROADCASTER + bool "Bluetooth Isochronous Broadcaster Support" + select BT_ISO_BROADCAST + select BT_ISO_TX + select BT_BROADCASTER + select BT_PER_ADV + help + This option enables support for the Bluetooth Isochronous Broadcaster. + +config BT_ISO_SYNC_RECEIVER + bool "Bluetooth Isochronous Synchronized Receiver Support" + depends on BT_OBSERVER + select BT_ISO_BROADCAST + select BT_ISO_RX + select BT_PER_ADV_SYNC + help + This option enables support for the Bluetooth Isochronous Synchronized Receiver. + +if BT_ISO + +config BT_ISO_MAX_CHAN + int "Maximum number of simultaneous ISO channels" + depends on BT_ISO + default BT_MAX_CONN if BT_CONN + default 1 + range 1 64 + help + Maximum number of simultaneous Bluetooth isochronous channels supported. + +config BT_ISO_TX_BUF_COUNT + int "Number of Isochronous TX buffers" + default 1 + range 1 255 + help + Number of buffers available for outgoing Isochronous channel SDUs. + +config BT_ISO_TX_MTU + int "Maximum supported MTU for Isochronous TX buffers" + range 1 4095 + default 247 + help + Maximum MTU for Isochronous channels TX buffers. + +config BT_ISO_RX_BUF_COUNT + int "Number of Isochronous RX buffers" + default 1 + range 1 255 + help + Number of buffers available for incoming Isochronous channel SDUs. + +config BT_ISO_RX_MTU + int "Maximum supported MTU for Isochronous RX buffers" + default 251 + range 23 4095 + help + Maximum MTU for Isochronous channels RX buffers. + +config BT_ISO_TEST_PARAMS + bool "ISO test parameters support" + help + Enabling advanced ISO parameters will allow the use of ISO test + parameters for creating a CIG or a BIG. + +if BT_ISO_UNICAST + +config BT_ISO_MAX_CIG + int "Maximum number of Connected Isochronous Groups (CIGs) to support" + default 1 + help + Maximum number of CIGs that are supported by the host. + +endif # BT_ISO_UNICAST + +if BT_ISO_BROADCAST + +config BT_ISO_MAX_BIG + int "Maximum number of Broadcast Isochronous Groups (BIGs) to support" + default 1 + range 1 BT_EXT_ADV_MAX_ADV_SET + help + Maximum number of BIGs that are supported by the host. + +endif # BT_ISO_BROADCAST +endif # BT_ISO + +config BT_HCI_SET_PUBLIC_ADDR + bool + default n + +source "libs/zblue/compat/Kconfig" +source "libs/zblue/common/Kconfig" +source "libs/zblue/crypto/Kconfig" +source "libs/zblue/lib/Kconfig" +source "libs/zblue/host/Kconfig" +source "libs/zblue/audio/Kconfig" + +endif # BT + +endif # ZBLUE_ENABLED diff --git a/libs/zblue/Kconfig.adv b/libs/zblue/Kconfig.adv new file mode 100644 index 0000000000000..222a95fd99278 --- /dev/null +++ b/libs/zblue/Kconfig.adv @@ -0,0 +1,103 @@ +############################################################################ +# libs/zblue/Kconfig.adv +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth LE Advertising and Scanning configuration options + +# Copyright (c) 2016 Intel Corporation +# Copyright (c) 2021 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config BT_EXT_ADV + bool "Extended Advertising and Scanning support" + default y if BT_MESH + help + Select this to enable Extended Advertising API support. + This enables support for advertising with multiple advertising sets, + extended advertising data, and advertising on LE Coded PHY. + It enables support for receiving extended advertising data as a + scanner, including support for advertising data over the LE coded PHY. + It enables establishing connections over LE Coded PHY. + +if BT_EXT_ADV + +config BT_EXT_ADV_LEGACY_SUPPORT + bool "Support starting advertising through legacy commands" + help + Select this to enable the use of the Legacy Advertising HCI commands. + This option should be used where the capabilities of the controller + is not known. + If this option is not enabled the controller must support the extended + advertising feature. + +config BT_EXT_ADV_MAX_ADV_SET + int "Maximum number of simultaneous advertising sets" + range 1 64 + default 1 + help + Maximum number of simultaneous Bluetooth advertising sets + supported. + +config BT_PER_ADV + bool "Periodic Advertising and Scanning support" + help + Select this to enable Periodic Advertising API support. This allows + the device to send advertising data periodically at deterministic + intervals. Scanners can synchronize to the periodic advertisements + to periodically get the data. + +config BT_PER_ADV_RSP + bool "Periodic Advertising with Responses support" + depends on BT_PER_ADV + help + Select this to enable Periodic Advertising with Responses + API support. + +config BT_PER_ADV_SYNC + bool "Periodic advertising sync support" + depends on BT_OBSERVER + help + Select this to enable Periodic Advertising Sync API support. + Syncing with a periodic advertiser allows the device to periodically + and deterministic receive data from that device in a connectionless + manner. + +config BT_PER_ADV_SYNC_RSP + bool "Periodic Advertising with Responses sync support" + depends on BT_OBSERVER + help + Select this to enable Periodic Advertising with Responses Sync + API support. + +config BT_EXT_ADV_CODING_SELECTION + bool "Advertising Coding Selection support" + help + Select this to enable Advertising Coding Selection API support. + This allows the Host to indicate their strict requirement + concerning coding scheme when using Extended Advertising. + +config BT_EXT_ADV_REASSEMBLY_TIMEOUT + int "Extended advertising reassembly timeout" + depends on BT_OBSERVER + default 1000 + range 100 10000 + help + Timeout in milliseconds for reassembling extended advertising reports. + If reassembly is in progress, and no more data is received within this + timeout, the chain will be discarded. + +if BT_PER_ADV_SYNC + +config BT_PER_ADV_SYNC_MAX + int "Maximum number of simultaneous periodic advertising syncs" + range 1 64 + default 1 + help + Maximum number of simultaneous periodic advertising syncs supported. + +endif # BT_PER_ADV_SYNC +endif # BT_EXT_ADV diff --git a/libs/zblue/Make.defs b/libs/zblue/Make.defs new file mode 100644 index 0000000000000..f9ec7eac5d3a1 --- /dev/null +++ b/libs/zblue/Make.defs @@ -0,0 +1,37 @@ +############################################################################ +# libs/zblue/Make.defs +# +# Author: chao an +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +# NuttX Bluetooth Host Stack + +ifeq ($(CONFIG_BT),y) + +include $(TOPDIR)/libs/zblue/compat/Make.defs +include $(TOPDIR)/libs/zblue/common/Make.defs +include $(TOPDIR)/libs/zblue/crypto/Make.defs +include $(TOPDIR)/libs/zblue/lib/Make.defs +include $(TOPDIR)/libs/zblue/host/Make.defs +include $(TOPDIR)/libs/zblue/audio/Make.defs +include $(TOPDIR)/libs/zblue/mesh/Make.defs +include $(TOPDIR)/libs/zblue/services/Make.defs +endif diff --git a/libs/zblue/Makefile b/libs/zblue/Makefile new file mode 100644 index 0000000000000..d16ff22569434 --- /dev/null +++ b/libs/zblue/Makefile @@ -0,0 +1,72 @@ +############################################################################ +# libs/zblue/Makefile +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +include $(TOPDIR)/Make.defs + +include $(TOPDIR)/libs/zblue/Make.defs + +CSRCS = $(patsubst $(TOPDIR)/libs/zblue/%,%,$(BT_CSRCS)) +VPATH := $(patsubst $(TOPDIR)/libs/zblue/%,%,$(VPATH)) +DEPPATH := $(subst $(TOPDIR)/libs/zblue/,,$(DEPPATH)) + +AOBJS = $(ASRCS:.S=$(OBJEXT)) +COBJS = $(CSRCS:.c=$(OBJEXT)) + +SRCS = $(ASRCS) $(CSRCS) +OBJS = $(AOBJS) $(COBJS) + +BIN = libzblue$(LIBEXT) + +all: $(BIN) +.PHONY: depend clean distclean + +$(AOBJS): %$(OBJEXT): %.S + $(call ASSEMBLE, $<, $@) + +$(COBJS): %$(OBJEXT): %.c + $(call COMPILE, $<, $@) + +$(BIN): $(OBJS) + $(call ARCHIVE_REBUILD, $@, $(OBJS)) + +makedepfile: $(CSRCS:.c=.ddc) $(ASRCS:.S=.dds) + $(call CATFILE, Make.dep, $^) + $(call DELFILE, $^) + +.depend: Makefile $(SRCS) $(TOPDIR)$(DELIM).config +ifeq ($(CONFIG_BT),y) + $(Q) $(MAKE) makedepfile +endif + $(Q) touch $@ + +depend: .depend + +clean: + $(call DELFILE, $(BIN)) + $(call CLEAN) + +distclean: clean + $(call DELFILE, $(CSRCS:.c=.ddc) $(ASRCS:.S=.dds)) + $(call DELFILE, Make.dep) + $(call DELFILE, .depend) + +-include Make.dep diff --git a/libs/zblue/audio/CMakeLists.txt b/libs/zblue/audio/CMakeLists.txt new file mode 100644 index 0000000000000..0cd0858cecca7 --- /dev/null +++ b/libs/zblue/audio/CMakeLists.txt @@ -0,0 +1,166 @@ +# ############################################################################## +# libs/zblue/audio/CMakeLists.txt +# +# Author: chao an +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +if(CONFIG_BT_AUDIO) + set(BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/audio.c) + + if(CONFIG_BT_VOCS OR CONFIG_BT_VOCS_CLIENT) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/vocs.c) + endif() + if(CONFIG_BT_VOCS_CLIENT) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/vocs_client.c) + endif() + + if(CONFIG_BT_AICS OR CONFIG_BT_AICS_CLIENT) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/aics.c) + endif() + if(CONFIG_BT_AICS_CLIENT) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/aics_client.c) + endif() + + if(CONFIG_BT_VCP_VOL_REND) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/vcp_vol_rend.c) + endif() + if(CONFIG_BT_VCP_VOL_CTLR) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/vcp_vol_ctlr.c) + endif() + + if(CONFIG_BT_MICP_MIC_DEV) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/micp_mic_dev.c) + endif() + if(CONFIG_BT_MICP_MIC_CTLR) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/micp_mic_ctlr.c) + endif() + + if(CONFIG_BT_CONN) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/ccid.c) + endif() + + if(CONFIG_BT_CSIP_SET_MEMBER) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/csip_set_member.c) + endif() + if(CONFIG_BT_CSIP_SET_COORDINATOR) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/csip_set_coordinator.c) + endif() + if(CONFIG_BT_CSIP_SET_MEMBER OR CONFIG_BT_CSIP_SET_COORDINATOR) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/csip_crypto.c) + endif() + + if(CONFIG_BT_TBS) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/tbs.c) + endif() + if(CONFIG_BT_TBS_CLIENT) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/tbs_client.c) + endif() + if(CONFIG_BT_MCC) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/mcc.c) + endif() + if(CONFIG_BT_MCS) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/mcs.c) + endif() + if(CONFIG_BT_MPL) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/mpl.c) + endif() + if(CONFIG_MCTL) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/media_proxy.c) + endif() + + if(CONFIG_BT_ASCS) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/ascs.c) + endif() + if(CONFIG_BT_PACS) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/pacs.c) + endif() + if(CONFIG_BT_BAP_STREAM) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/bap_stream.c + ${CMAKE_CURRENT_LIST_DIR}/codec.c ${CMAKE_CURRENT_LIST_DIR}/bap_iso.c) + endif() + if(CONFIG_BT_BAP_BASE) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/bap_base.c) + endif() + if(CONFIG_BT_BAP_UNICAST_SERVER) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/bap_unicast_server.c) + endif() + if(CONFIG_BT_BAP_UNICAST_CLIENT) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/bap_unicast_client.c) + endif() + if(CONFIG_BT_BAP_BROADCAST_SOURCE) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/bap_broadcast_source.c) + endif() + if(CONFIG_BT_BAP_BROADCAST_SINK) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/bap_broadcast_sink.c) + endif() + if(CONFIG_BT_BAP_SCAN_DELEGATOR) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/bap_scan_delegator.c) + endif() + if(CONFIG_BT_BAP_BROADCAST_ASSISTANT) + list(APPEND BT_AUDIO_SRCS + ${CMAKE_CURRENT_LIST_DIR}/bap_broadcast_assistant.c) + endif() + if(CONFIG_BT_CCP_CALL_CONTROL_CLIENT) + list(APPEND BT_AUDIO_SRCS + ${CMAKE_CURRENT_LIST_DIR}/ccp_call_control_client.c) + endif() + if(CONFIG_BT_CCP_CALL_CONTROL_SERVER) + list(APPEND BT_AUDIO_SRCS + ${CMAKE_CURRENT_LIST_DIR}/ccp_call_control_server.c) + endif() + if(CONFIG_BT_HAS) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/has.c) + endif() + if(CONFIG_BT_HAS_CLIENT) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/has_client.c) + endif() + if(CONFIG_BT_CAP) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/cap_stream.c) + endif() + if(CONFIG_BT_CAP_ACCEPTOR) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/cap_acceptor.c) + endif() + if(CONFIG_BT_CAP_INITIATOR) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/cap_initiator.c) + endif() + if(CONFIG_BT_CAP_COMMANDER) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/cap_commander.c) + endif() + if(CONFIG_BT_CAP_HANDOVER) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/cap_handover.c) + endif() + if(CONFIG_BT_CAP_INITIATOR_UNICAST OR CONFIG_BT_CAP_COMMANDER) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/cap_common.c) + endif() + if(CONFIG_BT_TMAP) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/tmap.c) + endif() + if(CONFIG_BT_GMAP) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/gmap_client.c + ${CMAKE_CURRENT_LIST_DIR}/gmap_server.c) + endif() + if(CONFIG_BT_PBP) + list(APPEND BT_AUDIO_SRCS ${CMAKE_CURRENT_LIST_DIR}/pbp.c) + endif() + + target_sources(zblue PRIVATE ${BT_AUDIO_SRCS}) + target_include_directories(zblue PRIVATE ${CMAKE_CURRENT_LIST_DIR}) +endif() diff --git a/libs/zblue/audio/Kconfig b/libs/zblue/audio/Kconfig new file mode 100644 index 0000000000000..ca7d52c6e0698 --- /dev/null +++ b/libs/zblue/audio/Kconfig @@ -0,0 +1,75 @@ +############################################################################ +# libs/zblue/audio/Kconfig +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth Audio configuration options + +# +# Copyright (c) 2020 Intel Corporation +# Copyright (c) 2022-2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +menuconfig BT_AUDIO + bool "Bluetooth Audio support" + help + This option enables Bluetooth Audio support. The specific + features that are available may depend on other features + that have been enabled in the stack, such as Periodic + Advertisement for Broadcast and L2CAP Dynamic Channel + for Unicast. + +if BT_AUDIO + +config BT_AUDIO_RX + def_bool BT_ASCS_ASE_SNK || BT_BAP_UNICAST_CLIENT_ASE_SRC || BT_BAP_BROADCAST_SINK + help + Indicates that the stack is configured with support for receiving + audio (for example as a unicast Audio Stream sink or a Broadcast + Sink). This internal option is derived automatically and is not meant + to be set directly. + +config BT_AUDIO_TX + def_bool BT_ASCS_ASE_SRC || BT_BAP_UNICAST_CLIENT_ASE_SNK || BT_BAP_BROADCAST_SOURCE + help + Indicates that the stack is configured with support for transmitting + audio (for example as a unicast Audio Stream source or a Broadcast + Source). This internal option is derived automatically and is not + meant to be set directly. + +config BT_AUDIO_NOTIFY_RETRY_DELAY + int "Delay for notification sending retried attempt in 1.25 ms units" + range 6 3200 + default 20 + help + This option sets the time in 1.25 ms units before the stack will + retry to send notification that failed due to lack of TX buffers + available. + +source "libs/zblue/audio/Kconfig.bap" +source "libs/zblue/audio/Kconfig.ccp" +source "libs/zblue/audio/Kconfig.vocs" +source "libs/zblue/audio/Kconfig.aics" +source "libs/zblue/audio/Kconfig.vcp" +source "libs/zblue/audio/Kconfig.micp" +source "libs/zblue/audio/Kconfig.csip" +source "libs/zblue/audio/Kconfig.tbs" +source "libs/zblue/audio/Kconfig.mcs" +source "libs/zblue/audio/Kconfig.has" +source "libs/zblue/audio/Kconfig.mpl" +source "libs/zblue/audio/Kconfig.mctl" +source "libs/zblue/audio/Kconfig.cap" +source "libs/zblue/audio/Kconfig.tmap" +source "libs/zblue/audio/Kconfig.gmap" +source "libs/zblue/audio/Kconfig.pbp" + +module = BT_AUDIO +module-str = "Bluetooth Audio" +# source "subsys/logging/Kconfig.template.log_config" + +endif # BT_AUDIO diff --git a/libs/zblue/audio/Kconfig.aics b/libs/zblue/audio/Kconfig.aics new file mode 100644 index 0000000000000..61f035fafad47 --- /dev/null +++ b/libs/zblue/audio/Kconfig.aics @@ -0,0 +1,61 @@ +############################################################################ +# libs/zblue/audio/Kconfig.aics +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth Audio - Audio Input Control Service options +# +# Copyright (c) 2020 Bose Corporation +# Copyright (c) 2020 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +##################### Audio Input Control Service ##################### + +config BT_AICS_MAX_INSTANCE_COUNT + int "Audio Input Control Service max instance count" + depends on BT_SMP + default 0 + range 0 15 + help + This option sets the maximum number of instances of Audio Input + Control Services. + +config BT_AICS + bool # hidden + default y if BT_AICS_MAX_INSTANCE_COUNT > 0 + depends on BT_GATT_DYNAMIC_DB + help + This hidden option enables support for Audio Input Control Service. + +if BT_AICS + +config BT_AICS_MAX_INPUT_DESCRIPTION_SIZE + int "Audio Input Control Service max input description size" + default 32 + range 0 512 + help + This option sets the maximum input description size in octets. + +endif # BT_AICS + +##################### Audio Input Control Service Client ##################### + +config BT_AICS_CLIENT_MAX_INSTANCE_COUNT + int "Audio Input Control Service client max instance count" + depends on BT_SMP + default 0 + range 0 15 + help + This option sets the maximum number of instances of Audio Input + Control Services. + +config BT_AICS_CLIENT + bool # hidden + default y if BT_AICS_CLIENT_MAX_INSTANCE_COUNT > 0 + help + This hidden option enables support for Audio Input Control Service. diff --git a/libs/zblue/audio/Kconfig.ascs b/libs/zblue/audio/Kconfig.ascs new file mode 100644 index 0000000000000..5ef0c0a00a1f0 --- /dev/null +++ b/libs/zblue/audio/Kconfig.ascs @@ -0,0 +1,76 @@ +############################################################################ +# libs/zblue/audio/Kconfig.ascs +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth Audio - Audio Stream Control configuration options +# +# Copyright (c) 2020 Intel Corporation +# Copyright (c) 2022 Nordic Semiconductor ASA + +# SPDX-License-Identifier: Apache-2.0 +# + +config BT_ASCS + bool "Audio Stream Control Service Support" + depends on BT_SMP + depends on BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE >= 19 # Minimum requirement for LC3 per BAP + help + This option enables support for Audio Stream Control Service. + +if BT_ASCS +config BT_ASCS_MAX_ASE_SNK_COUNT + int "Maximum number of Audio Stream Endpoint Sink Characteristics" + default 2 + range 0 255 + help + An ASE Sink characteristic represents the state of an ASE, which is + coupled to a single direction of a unicast Audio Stream. + +config BT_ASCS_MAX_ASE_SRC_COUNT + int "Maximum number of Audio Stream Endpoint Source Characteristics" + default 2 + range 0 255 + help + An ASE Source characteristic represents the state of an ASE, which is + coupled to a single direction of a unicast Audio Stream. + +config BT_ASCS_ASE_SNK + def_bool BT_ASCS_MAX_ASE_SNK_COUNT > 0 + +config BT_ASCS_ASE_SRC + def_bool BT_ASCS_MAX_ASE_SRC_COUNT > 0 + +config BT_ASCS_MAX_ACTIVE_ASES + int "Number of simultaneously supported ASE sessions" + default BT_ISO_MAX_CHAN + range 1 65535 + help + The number of simultaneously supported active ASEs, in particular + meaning the number of ASEs that are allowed to be in a non-idle state at + a single time. + +config BT_ASCS_ISO_DISCONNECT_DELAY + int "Milliseconds of delay before ASCS disconnects ISO after stream stop" + range 0 5000 + default 500 + help + The number of milliseconds ASCS will wait before disconnecting the ISO + of a stopped stream. The delay is added as it is the Unicast Client's + responsibility, but this is a failsafe to ensure that ISO channel is + being properly disconnected. + +config BT_ASCS_ASE_BUF_TIMEOUT + int "Milliseconds of timeout when handle concurrent access to the long read ASE buffer" + range 0 1000 + default 50 + help + The number of milliseconds that the ASCS implementation will maximum wait before rejecting + an ASE read or dropping a notification if the ASE state is being accessed by another + thread. + + +endif # BT_ASCS diff --git a/libs/zblue/audio/Kconfig.bap b/libs/zblue/audio/Kconfig.bap new file mode 100644 index 0000000000000..409d121202e42 --- /dev/null +++ b/libs/zblue/audio/Kconfig.bap @@ -0,0 +1,303 @@ +############################################################################ +# libs/zblue/audio/Kconfig.bap +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth Audio - Basic Audio Profile configuration options +# +# Copyright (c) 2020 Intel Corporation +# Copyright (c) 2022-2025 Nordic Semiconductor ASA + +# SPDX-License-Identifier: Apache-2.0 +# + +config BT_BAP_UNICAST + # FIXME: BT_ASCS should not enable this, but it is added as a temporary solution while ASCS + # and BAP are being decoupled + def_bool BT_BAP_UNICAST_SERVER || BT_BAP_UNICAST_CLIENT || BT_ASCS + +config BT_BAP_UNICAST_SERVER + bool "Bluetooth Unicast Audio Server Support" + depends on BT_GATT_DYNAMIC_DB + depends on BT_PERIPHERAL + depends on BT_ISO_PERIPHERAL + depends on BT_ASCS + depends on BT_BONDABLE + depends on BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE >= 19 # Minimum requirement for LC3 per BAP + select BT_PAC_SNK if BT_ASCS_ASE_SNK + select BT_PAC_SRC if BT_ASCS_ASE_SRC + help + This option enables support for Bluetooth Unicast Audio Server + using Isochronous channels. + +config BT_BAP_UNICAST_CLIENT + bool "Bluetooth Unicast Audio Client Support" + depends on BT_GATT_CLIENT + depends on BT_GATT_AUTO_DISCOVER_CCC + depends on BT_GATT_AUTO_UPDATE_MTU + depends on BT_CENTRAL + depends on BT_ISO_CENTRAL + depends on BT_BONDABLE + depends on BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE >= 19 # Minimum requirement for LC3 per BAP + help + This option enables support for Bluetooth Unicast Audio Client + using Isochronous channels. + +config BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE + int "Codec Specific Configuration Data Size" + default 19 + range 0 255 + help + Number of octets to support for Codec Specific Configuration data. + The default value 19 matches the required fields for the LC3 codec. + +config BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE + int "Codec Specific Configuration Metadata Size" + default 4 + range 0 255 + help + Number of octets to support for Codec Specific Configuration metadata. + +config BT_BAP_BASS_MAX_SUBGROUPS + int "Maximum number of subgroups supported for the BASS receive states" + default 1 + range 1 24 + help + This option sets the maximum number of subgroups supported. + Due to limitations in advertising data, the maximum size of all subgroups are 249. + The minimum size of a subgroup is 10 octets. + So effectively there can be a maximum of 24 subgroups in a BASE. + +config BT_AUDIO_CODEC_CAP_MAX_DATA_SIZE + int "Codec Capabilities Data Size" + default 19 + range 0 255 + help + Number of octets to support for Codec Specific Capabilities data. + The default value 19 matches the required fields for the LC3 codec. + +config BT_AUDIO_CODEC_CAP_MAX_METADATA_SIZE + int "Codec Capabilities Metadata Size" + default 4 + range 0 255 + help + Number of octets to support for Codec Specific Capabilities metadata. + +if BT_BAP_UNICAST_CLIENT +config BT_BAP_UNICAST_CLIENT_GROUP_COUNT + int "Basic Audio Unicast Group count" + default BT_ISO_MAX_CIG + range 1 BT_ISO_MAX_CIG + help + This option sets the number of connected audio groups to support as + the unicast client. + +config BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT + int "Basic Audio Profile Unicast Group Connected Isochronous Stream (CIS) count" + depends on BT_BAP_UNICAST_CLIENT_GROUP_COUNT > 0 + default 1 + range 1 BT_ISO_MAX_CHAN if BT_ISO_MAX_CHAN < 31 + range 1 31 + help + This option sets the maximum number of CIS per unicast group to support. + Since BAP streams are unidirectional, two BAP streams may use a single CIS, the number of + BAP audio streams per group may be up to twice of this value. + +config BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT + int "Basic Audio Profile ASE Sink count" + default 2 + range 0 255 + help + This option enables caching a number of Audio Stream Endpoint Sink + instances for Basic Audio Profile on a per connection basis. + +config BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT + int "Basic Audio Profile ASE Source count" + default 2 + range 0 255 + help + This option enables caching a number of Audio Stream Endpoint Source + instances for Basic Audio Profile on a per connection basis. + +config BT_BAP_UNICAST_CLIENT_ASE_SNK + def_bool BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 + +config BT_BAP_UNICAST_CLIENT_ASE_SRC + def_bool BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 + +endif # BT_BAP_UNICAST_CLIENT + +config BT_BAP_BROADCAST_SOURCE + bool "Bluetooth Broadcast Source Audio Support" + depends on BT_ISO_BROADCASTER + depends on BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE >= 19 # Minimum requirement for LC3 per BAP + help + This option enables support for Bluetooth Broadcast Source Audio using + Isochronous channels. +if BT_BAP_BROADCAST_SOURCE + +config BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT + int "Basic Audio Broadcast Source subgroup count" + default 1 + range 1 BT_ISO_MAX_CHAN if BT_ISO_MAX_CHAN < 31 + range 1 31 + help + This option sets the maximum number of subgroups per broadcast source + to support. + +config BT_BAP_BROADCAST_SRC_COUNT + int "Basic Audio Broadcaster source count" + default 1 + range 1 BT_ISO_MAX_BIG + help + This option sets the number of broadcast sources to support. + One broadcast source can send multiple streams + (up to BT_BAP_BROADCAST_SRC_STREAM_COUNT per broadcast source). + + +config BT_BAP_BROADCAST_SRC_STREAM_COUNT + int "Basic Audio Broadcast Source Stream count" + default 1 + range 1 BT_ISO_MAX_CHAN if BT_ISO_MAX_CHAN < 31 + range 1 31 + help + This option sets the maximum number of streams per broadcast source + to support. + +endif # BT_BAP_BROADCAST_SOURCE + +config BT_BAP_BROADCAST_SINK + bool "Bluetooth Broadcast Sink Audio Support" + depends on BT_ISO_SYNC_RECEIVER + depends on BT_PAC_SNK + depends on BT_PERIPHERAL + depends on BT_BAP_SCAN_DELEGATOR + depends on BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE >= 19 # Minimum requirement for LC3 per BAP + help + This option enables support for Bluetooth Broadcast Sink Audio using + Isochronous channels. + +if BT_BAP_BROADCAST_SINK + +config BT_BAP_BROADCAST_SNK_SUBGROUP_COUNT + int "Basic Audio Profile Broadcast Sink subgroup count" + default BT_BAP_BASS_MAX_SUBGROUPS + range 1 BT_BAP_BASS_MAX_SUBGROUPS + help + This option sets the maximum number of subgroups per broadcast sink + to support. + +config BT_BAP_BROADCAST_SNK_COUNT + int "Basic Audio Broadcaster Sink count" + default 1 + range 0 BT_ISO_MAX_BIG + help + This option sets the number of broadcast sinks to support. + One broadcast sink can receive multiple streams + (up to BT_BAP_BROADCAST_SNK_STREAM_COUNT per broadcast sink). + +config BT_BAP_BROADCAST_SNK_STREAM_COUNT + int "Basic Audio Broadcast Sink Stream count" + depends on BT_BAP_BROADCAST_SNK_COUNT > 0 + default 1 + range 1 BT_ISO_MAX_CHAN if BT_ISO_MAX_CHAN < 31 + range 1 31 + help + This option sets the maximum number of streams per broadcast sink + to support. + +endif # BT_BAP_BROADCAST_SINK + +config BT_BAP_SCAN_DELEGATOR + bool "Basic Audio Profile Scan Delegator role support" + depends on BT_EXT_ADV + depends on BT_PER_ADV_SYNC + depends on BT_ISO_SYNC_RECEIVER + depends on BT_OBSERVER + depends on BT_GATT_DYNAMIC_DB + depends on BT_BONDABLE + depends on BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE >= 19 # Minimum requirement for LC3 per BAP + help + This option enables support for the Scan Delegator role and the + Broadcast Audio Scan Service (BASS). + +if BT_BAP_SCAN_DELEGATOR + +config BT_BAP_SCAN_DELEGATOR_RECV_STATE_COUNT + int "Scan Delegator Receive State Count" + default 1 + range 1 3 + help + Sets the number of receive state characteristics present on the + server. Each characteristic may hold information to sync to a + periodic advertise or a broadcast isochronous stream. + +config BT_BAP_SCAN_DELEGATOR_BUF_TIMEOUT + int "Milliseconds of timeout when handle concurrent access to the long read ASE buffer" + range 0 1000 + default 50 + help + The maximum number of milliseconds that the scan delegator implementation will wait + before rejecting a read or dropping a notification if the scan delegator state is + being accessed by another thread. + +endif # BT_BAP_SCAN_DELEGATOR + +config BT_BAP_BROADCAST_ASSISTANT + bool "Basic Audio Profile Broadcast Assistant role support" + depends on BT_EXT_ADV + depends on BT_PER_ADV_SYNC + depends on BT_ISO_SYNC_RECEIVER + depends on BT_GATT_CLIENT + depends on BT_GATT_AUTO_DISCOVER_CCC + depends on BT_GATT_AUTO_UPDATE_MTU + depends on BT_OBSERVER + depends on BT_BONDABLE + depends on BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE >= 19 # Minimum requirement for LC3 per BAP + help + This option enables support for the Broadcast Assistant role. + +if BT_BAP_BROADCAST_ASSISTANT + +config BT_BAP_BROADCAST_ASSISTANT_RECV_STATE_COUNT + int "Broadcast Assistant Max Receive State Count" + default 1 + range 1 255 + help + Sets the number of maximum receive stat characteristics that will be + discovered and ready to use. Each characteristic may hold information + to sync to a periodic advertise or a broadcast isochronous stream. + +endif # BT_BAP_BROADCAST_ASSISTANT + +config BT_BAP_DEBUG_STREAM_DATA + bool "Bluetooth Audio Stream data debug" + depends on BT_BAP_STREAM_LOG_LEVEL_DBG + help + Use this option to enable Bluetooth Audio Stream data debug logs for + the Bluetooth Audio functionality. This will enable debug logs for all + audio data received and sent. + +config BT_BAP_STREAM + # Virtual/hidden option + bool + default y if BT_ASCS || BT_BAP_UNICAST_CLIENT || \ + BT_BAP_BROADCAST_SOURCE || BT_BAP_BROADCAST_SINK + +config BT_BAP_DEBUG_STREAM_SEQ_NUM + bool "Bluetooth Audio Stream sequence number debug" + depends on BT_BAP_STREAM_LOG_LEVEL >= BT_BAP_STREAM_LOG_LEVEL_WRN + default y + help + Use this option to enable Bluetooth Audio Stream sequence number debugging logs for + the Bluetooth Audio functionality. This will provide a warning if the application + provides unexpected sequence numbers. + +config BT_BAP_BASE + def_bool BT_BAP_BROADCAST_SINK || BT_BAP_BROADCAST_ASSISTANT || BT_BAP_SCAN_DELEGATOR + +source "libs/zblue/audio/Kconfig.pacs" +source "libs/zblue/audio/Kconfig.ascs" diff --git a/libs/zblue/audio/Kconfig.cap b/libs/zblue/audio/Kconfig.cap new file mode 100644 index 0000000000000..ef636f8addce1 --- /dev/null +++ b/libs/zblue/audio/Kconfig.cap @@ -0,0 +1,71 @@ +############################################################################ +# libs/zblue/audio/Kconfig.cap +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth Audio - Common Audio Profile (CAP) options +# +# Copyright (c) 2022 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +config BT_CAP + def_bool BT_CAP_ACCEPTOR || BT_CAP_INITIATOR + +config BT_CAP_SUBPROC_SUPPORT + def_bool BT_CAP_INITIATOR_UNICAST || (BT_BAP_BROADCAST_ASSISTANT && BT_CAP_COMMANDER) + +config BT_CAP_ACCEPTOR + bool "Common Audio Profile Acceptor Role Support" + depends on BT_BAP_UNICAST_SERVER || (BT_BAP_BROADCAST_SINK && BT_BAP_SCAN_DELEGATOR) + depends on BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE >= 4 + help + Enabling this will enable the CAP Acceptor role. This instantiates the + common audio service (CAS). + +config BT_CAP_ACCEPTOR_SET_MEMBER + bool "Common Audio Profile Acceptor Role Set Member support" + depends on BT_CAP_ACCEPTOR + depends on BT_CSIP_SET_MEMBER + depends on BT_CSIP_SET_MEMBER_SIZE_SUPPORT + depends on BT_CSIP_SET_MEMBER_RANK_SUPPORT + depends on BT_GATT_DYNAMIC_DB + help + Enabling this will allow a CAP acceptor to be a set member. + Enabling this will require a manual register of the CAS service. + Enabling will take one of the allocated CSIS instances + (BT_CSIP_SET_MEMBER_MAX_INSTANCE_COUNT). + +config BT_CAP_INITIATOR_UNICAST + def_bool BT_CAP_INITIATOR && BT_BAP_UNICAST_CLIENT + +config BT_CAP_INITIATOR + bool "Common Audio Profile Initiator Role Support" + depends on (BT_BAP_UNICAST_CLIENT && BT_CSIP_SET_COORDINATOR) || BT_BAP_BROADCAST_SOURCE + depends on BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE >= 4 + help + Enabling this will enable the CAP Initiator role. + + +config BT_CAP_COMMANDER + bool "Common Audio Profile Initiator Role Support" + depends on (BT_BAP_BROADCAST_ASSISTANT && BT_BAP_SCAN_DELEGATOR && BT_CSIP_SET_COORDINATOR) || \ + (BT_BAP_SCAN_DELEGATOR && BT_CSIP_SET_COORDINATOR) || \ + (BT_VCP_VOL_CTLR && BT_CSIP_SET_COORDINATOR) || \ + (BT_MICP_MIC_CTLR && BT_CSIP_SET_COORDINATOR) || \ + BT_TBS_CLIENT || \ + BT_MCC + help + Enabling this will enable the CAP Initiator role. + +config BT_CAP_HANDOVER + bool "Common Audio Profile Handover Procedures" + depends on BT_CAP_COMMANDER && BT_CAP_INITIATOR && BT_BAP_BROADCAST_ASSISTANT && \ + BT_BAP_BROADCAST_SOURCE && BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 + help + Enable support for the CAP Handover procedures, allowing a device to switch between + broadcast and unicast for a usecase. diff --git a/libs/zblue/audio/Kconfig.ccp b/libs/zblue/audio/Kconfig.ccp new file mode 100644 index 0000000000000..aa739e9eb7c9e --- /dev/null +++ b/libs/zblue/audio/Kconfig.ccp @@ -0,0 +1,86 @@ +############################################################################ +# libs/zblue/audio/Kconfig.ccp +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth Audio - Call Control Profile (CCP) configuration options +# +# Copyright (c) 2024 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +if BT_AUDIO + +config BT_CCP_CALL_CONTROL_CLIENT + bool "Call Control Profile Client Support" + depends on BT_EXT_ADV + depends on BT_TBS_CLIENT + depends on BT_BONDABLE + help + This option enables support for the Call Control Profile Client which uses the Telephone + Bearer Service (TBS) client to control calls on a remote device. + +if BT_CCP_CALL_CONTROL_CLIENT + +config BT_CCP_CALL_CONTROL_CLIENT_BEARER_COUNT + int "Telephone bearer count" + default 1 + range 1 255 if BT_TBS_CLIENT_TBS + range 1 1 + help + The number of supported telephone bearers on the CCP Call Control Client + +config BT_CCP_CALL_CONTROL_CLIENT_CB_USER_DATA + bool "Call Control Profile Client support for user_data in callbacks" + help + This option enables support for user_data in Call Control Profile Client callbacks. + +module = BT_CCP_CALL_CONTROL_CLIENT +module-str = "Call Control Profile Client" +# source "subsys/logging/Kconfig.template.log_config" + +endif # BT_CCP_CALL_CONTROL_CLIENT + +config BT_CCP_CALL_CONTROL_SERVER + bool "Call Control Profile Call Control Server Support" + depends on BT_EXT_ADV + depends on BT_TBS + depends on BT_BONDABLE + help + This option enables support for the Call Control Profile Call Control Server which uses + the Telephone Bearer Service (TBS) to hold and control calls on a device. + +if BT_CCP_CALL_CONTROL_SERVER + +config BT_CCP_CALL_CONTROL_SERVER_BEARER_COUNT + int "Telephone bearer count" + default 1 + range 1 255 + help + The number of supported telephone bearers on the CCP Call Control Server + +config BT_CCP_CALL_CONTROL_SERVER_PROVIDER_NAME_MAX_LENGTH + int "The maximum length of the bearer provider name excluding null terminator" + default BT_TBS_MAX_PROVIDER_NAME_LENGTH + range 1 BT_TBS_MAX_PROVIDER_NAME_LENGTH + help + Sets the maximum length of the bearer provider name. + +config BT_CCP_CALL_CONTROL_SERVER_URI_SCHEMES_MAX_LENGTH + int "The maximum length of the bearer URI schemes support list excluding null terminator" + default BT_TBS_MAX_SCHEME_LIST_LENGTH + range 1 BT_TBS_MAX_SCHEME_LIST_LENGTH + help + Sets the maximum length of the bearer URI schemes support list. + +module = BT_CCP_CALL_CONTROL_SERVER +module-str = "Call Control Profile Call Control Server" +# source "subsys/logging/Kconfig.template.log_config" + +endif # BT_CCP_CALL_CONTROL_SERVER + +endif # BT_AUDIO diff --git a/libs/zblue/audio/Kconfig.csip b/libs/zblue/audio/Kconfig.csip new file mode 100644 index 0000000000000..3da4d2b8f0156 --- /dev/null +++ b/libs/zblue/audio/Kconfig.csip @@ -0,0 +1,126 @@ +############################################################################ +# libs/zblue/audio/Kconfig.csip +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth Audio - Broadcast Assistant configuration options +# +# Copyright (c) 2020 Bose Corporation +# Copyright (c) 2021 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +#################### Coordinated Set Identification Service #################### + +config BT_CSIP_SET_MEMBER + bool "Coordinated Set Identification Profile Set Member support" + depends on BT_GATT_DYNAMIC_DB + depends on BT_SMP + imply BT_EXT_ADV if BT_PRIVACY + help + This option enables support for Coordinated Set Identification + Profile Set Member role and the Coordinated Set Identification + Service. + +if BT_CSIP_SET_MEMBER + +config BT_CSIP_SET_MEMBER_TEST_SAMPLE_DATA + bool "Coordinated Set Identification Service Test Sample Data" + help + Enable the use of the sample data defined by the CSIS spec SIRK. + This will use the sample SIRK, prand and LTK. + + WARNING: This option enables anyone to track and decrypt the SIRK + (if encrypted) using public sample data. + Should not be used for production builds. + +config BT_CSIP_SET_MEMBER_ENC_SIRK_SUPPORT + bool "Support for encrypted SIRK" + default y + help + Enables support encrypting the SIRK. + +config BT_CSIP_SET_MEMBER_MAX_INSTANCE_COUNT + int "Coordinated Set Identification Profile max service instance count" + default 1 + range 1 255 + help + This option sets the maximum number of instances of Coordinated Set + Identification Services. If the service is declared as primary service + then only a single instance is possible. + + Enabling BT_CAP_ACCEPTOR_SET_MEMBER will take one of the allocated + instances. + +config BT_CSIP_SET_MEMBER_SIRK_NOTIFIABLE + bool "SIRK notifiable Support" + help + This option enables support for clients to be notified on SIRK changes. + +config BT_CSIP_SET_MEMBER_SIZE_SUPPORT + bool "Support for Coordinated Set Size" + help + Enables support for Coordinated Set Sizes. + +config BT_CSIP_SET_MEMBER_SIZE_NOTIFIABLE + bool "Set Size notifiable support" + depends on BT_CSIP_SET_MEMBER_SIZE_SUPPORT + help + This option enables support for clients to be notified on Set Size changes. + +config BT_CSIP_SET_MEMBER_LOCK_SUPPORT + bool "Support for Set Member Lock" + select BT_CSIP_SET_MEMBER_RANK_SUPPORT + help + This option enables support for Set Member Locks. + +config BT_CSIP_SET_MEMBER_RANK_SUPPORT + bool "Support for Set Member Rank" + help + This option enables support for Set Member Ranks. + +endif # BT_CSIP_SET_MEMBER + +#################### Coordinated Set Identification Client #################### + +config BT_CSIP_SET_COORDINATOR + bool "Coordinated Set Identification Profile Set Coordinator Support" + depends on BT_GATT_CLIENT + depends on BT_GATT_AUTO_DISCOVER_CCC + depends on BT_SMP + help + This option enables support for Coordinated Set Identification + Profile Set Coordinator. + +if BT_CSIP_SET_COORDINATOR + +config BT_CSIP_SET_COORDINATOR_TEST_SAMPLE_DATA + bool "Coordinated Set Identification Service Test Sample Data" + help + Enable the use of the sample data defined by the CSIS spec. + This will use the sample SIRK, prand and LTK. + + WARNING: This option enables the client to use the sample data + to locate members and decrypt the SIRK, and thus won't work + with set members that are not using the sample data. + Should not be used for production builds. + +config BT_CSIP_SET_COORDINATOR_MAX_CSIS_INSTANCES + int "Coordinated Set Identification Service Count" + default 1 + range 1 3 + help + Sets the number of service instances, which corresponds to the number + of different sets the peer device may be in. + +config BT_CSIP_SET_COORDINATOR_ENC_SIRK_SUPPORT + bool "Support for encrypted SIRK" + default y + help + Enables support encrypting the SIRK. + +endif # BT_CSIP_SET_COORDINATOR diff --git a/libs/zblue/audio/Kconfig.gmap b/libs/zblue/audio/Kconfig.gmap new file mode 100644 index 0000000000000..ed92ba8db86c8 --- /dev/null +++ b/libs/zblue/audio/Kconfig.gmap @@ -0,0 +1,37 @@ +############################################################################ +# libs/zblue/audio/Kconfig.gmap +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth Audio - Gaming Audio Profile (GMAP) options +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +config BT_GMAP_UGG_SUPPORTED + def_bool BT_CAP_INITIATOR && BT_BAP_UNICAST_CLIENT && BT_VCP_VOL_CTLR + +config BT_GMAP_UGT_SUPPORTED + def_bool BT_CAP_ACCEPTOR && BT_BAP_UNICAST_SERVER + +config BT_GMAP_BGS_SUPPORTED + def_bool BT_CAP_INITIATOR && BT_BAP_BROADCAST_SOURCE && BT_BAP_BROADCAST_ASSISTANT + +config BT_GMAP_BGR_SUPPORTED + def_bool BT_CAP_ACCEPTOR && BT_BAP_BROADCAST_SINK && BT_VCP_VOL_REND + +config BT_GMAP + bool "Gaming Audio Profile" + depends on BT_CAP_ACCEPTOR || BT_CAP_INITIATOR + help + Enabling this will enable GMAP. + +parent-module = BT +module = BT_GMAP +module-str = "Bluetooth Gaming Audio Profile" +# source "subsys/logging/Kconfig.template.log_config_inherit" diff --git a/libs/zblue/audio/Kconfig.has b/libs/zblue/audio/Kconfig.has new file mode 100644 index 0000000000000..5c387ddabef6b --- /dev/null +++ b/libs/zblue/audio/Kconfig.has @@ -0,0 +1,72 @@ +############################################################################ +# libs/zblue/audio/Kconfig.has +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth Audio - Hearing Access Service options +# +# Copyright (c) 2022 Codecoup +# Copyright (c) 2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 + +menuconfig BT_HAS + bool "Hearing Access Service support" + depends on BT_GATT_DYNAMIC_DB + depends on BT_BAP_UNICAST_SERVER + help + This option enables support for Hearing Access Service. + +if BT_HAS + +config BT_HAS_FEATURES_NOTIFIABLE + bool "Hearing Aid Features Notifiable Support" + help + This option enables support for clients to subscribe for notifications + on the Hearing Aid Features characteristic. + +config BT_HAS_PRESET_COUNT + int "Preset record list size" + default 2 + range 0 255 + help + This option sets the number of Hearing Access Service Presets + that can be registered. Setting this value to 0 disables Presets support. + +config BT_HAS_PRESET_SUPPORT + def_bool BT_HAS_PRESET_COUNT > 0 + +if BT_HAS_PRESET_SUPPORT + +config BT_HAS_PRESET_NAME_DYNAMIC + bool "Allow to set preset name on runtime" + help + Enabling this option allows for runtime configuration of preset name. + +config BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE + bool "Preset Control Point Notifiable support" + depends on BT_EATT + help + This option enables support for clients to subscribe for notifications + on the Hearing Aid Preset Control Point characteristic. + +config BT_HAS_ACTIVE_PRESET_INDEX + def_bool BT_HAS_PRESET_SUPPORT + help + This option enables the Hearing Aid Active Preset Index characteristic. + +endif # BT_HAS_PRESET_SUPPORT + +endif # BT_HAS + +config BT_HAS_CLIENT + bool "Hearing Access Service Client support" + depends on BT_GATT_CLIENT + depends on BT_GATT_AUTO_DISCOVER_CCC + depends on BT_GATT_AUTO_UPDATE_MTU + depends on BT_SMP + help + This option enables support for Hearing Access Service Client. diff --git a/libs/zblue/audio/Kconfig.mcs b/libs/zblue/audio/Kconfig.mcs new file mode 100644 index 0000000000000..5feb1a126fcb6 --- /dev/null +++ b/libs/zblue/audio/Kconfig.mcs @@ -0,0 +1,226 @@ +############################################################################ +# libs/zblue/audio/Kconfig.mcs +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth Audio - Media control configuration options + +# +# Copyright (c) 2020-2026 Nordic Semiconductor +# +# SPDX-License-Identifier: Apache-2.0 +# + +#### Media Control Service ################################ + +config BT_MCS + bool "Media Control Service Support" + depends on MCTL_LOCAL_PLAYER_REMOTE_CONTROL + depends on BT_GATT_DYNAMIC_DB + depends on BT_SMP + help + This option enables support for the Media Control Service. + +#### Media Control Client ################################ + +config BT_MCC + bool "Media Control Client Support" + depends on BT_GATT_CLIENT + depends on BT_GATT_AUTO_DISCOVER_CCC + depends on BT_SMP + help + This option enables support for the Media Control Client. + +if BT_MCC + +config BT_MCC_MEDIA_PLAYER_NAME_MAX + int "Max length of media player name" + default 20 + range 1 255 + help + Sets the maximum number of bytes (including the null termination) of + the name of the media player. If the name is longer, the media + control client will truncate the name when reading it. + +config BT_MCC_ICON_URL_MAX + int "Max length of media player icon URL" + default 40 + range 1 255 + help + Sets the maximum number of bytes (including the null termination) of + the media player icon URL. If the URL is longer, the media control + client will truncate the name when reading it. + +config BT_MCC_TRACK_TITLE_MAX + int "Max length of the title of a track" + default 40 + range 1 255 + help + Sets the maximum number of bytes (including the null termination) of + the title of any track in the media player. If the title is longer, + the media control client will truncate the title when reading it. + +config BT_MCC_SEGMENT_NAME_MAX + int "Max length of the name of a track segment" + default 25 + range 1 255 + help + Sets the maximum number of bytes (including the null termination) + of the name of any track segment in the media player. If the name is + longer, the media control client will truncate the name when reading + it. + +config BT_MCC_OTS + bool "Media Control Client support for Object Transfer Service" + depends on BT_OTS_CLIENT + help + Use this option to configure support in the Media Control Client for + an included Object Transfer Service in the Media Control Service. + +if BT_MCC_OTS + +config BT_MCC_OTC_OBJ_BUF_SIZE + int "The size of the buffer used for OTC object in MCC" + default 512 + range 1 65536 + help + Sets the size (in octets) of the buffer used for receiving the content + of objects. + Should be set large enough to fit any expected object. + +config BT_MCC_TOTAL_OBJ_CONTENT_MEM + int "Total memory size to use for storing the content of objects" + default 1 + range 0 65536 + help + Sets the total memory size (in octets) to use for storing the content + of objects. + This is used for the total memory pool buffer size from which memory + is allocated when reading object content. + +config BT_MCC_TRACK_SEGS_MAX_CNT + int "Maximum number of tracks segments in a track segment object" + default 25 + range 0 500 + help + Sets the maximum number of tracks segments in a track segment object. + If the received object is bigger, the remaining data in the object + will be ignored. + +config BT_MCC_GROUP_RECORDS_MAX + int "Maximum number of records in a group object" + default 25 + range 0 500 + help + Sets the maximum number of records in a group object. If the received + group object has more records than this, the remaining records in the + object will be ignored. + +endif # BT_MCC_OTS + +config BT_MCC_MINIMAL + bool "Minimal Media Control Client without optional procedures" + help + This option disables all optional procedures in the Media Control Client. + +config BT_MCC_READ_MEDIA_PLAYER_ICON_URL + bool "Support reading Media Player Icon URL" + default !BT_MCC_MINIMAL + help + This option enables support for Read Media Information procedure + optionally read the Media Player Icon URL. + +config BT_MCC_READ_TRACK_TITLE + bool "Support reading Track Title" + default !BT_MCC_MINIMAL + help + This option enables support for reading Track Title. + +config BT_MCC_READ_TRACK_TITLE_ENABLE_SUBSCRIPTION + bool "Support to enable or disable the subscription of Track Title" + default !BT_MCC_MINIMAL + help + This option enables support for the subscription of Track Title. + +config BT_MCC_READ_TRACK_DURATION + bool "Support reading Track Duration" + default !BT_MCC_MINIMAL + help + This option enables support for reading Track Duration. + +config BT_MCC_READ_TRACK_POSITION + bool "Support reading Track Position" + default !BT_MCC_MINIMAL + help + This option enables support for reading Track Position. + +config BT_MCC_SET_TRACK_POSITION + bool "Support setting Track Position" + default !BT_MCC_MINIMAL + help + This option enables support for setting Track Position. + +config BT_MCC_READ_PLAYBACK_SPEED + bool "Support reading Playback Speed" + default !BT_MCC_MINIMAL + help + This option enables support for reading Playback Speed. + +config BT_MCC_SET_PLAYBACK_SPEED + bool "Support setting Playback Speed" + default !BT_MCC_MINIMAL + help + This option enables support for setting Playback Speed. + +config BT_MCC_READ_SEEKING_SPEED + bool "Support reading Seeking Speed" + default !BT_MCC_MINIMAL + help + This option enables support for reading Seeking Speed. + +config BT_MCC_READ_PLAYING_ORDER + bool "Support reading Playing Order" + default !BT_MCC_MINIMAL + help + This option enables support for reading Playing Order. + +config BT_MCC_SET_PLAYING_ORDER + bool "Support setting Playing Order" + default !BT_MCC_MINIMAL + help + This option enables support for setting Playing Order. + +config BT_MCC_READ_PLAYING_ORDER_SUPPORTED + bool "Support reading Playing Order Supported" + default !BT_MCC_MINIMAL + help + This option enables support for reading Playing Order Supported. + +config BT_MCC_READ_MEDIA_STATE + bool "Support reading Media State" + default !BT_MCC_MINIMAL + help + This option enables support for reading Media State. + +config BT_MCC_SET_MEDIA_CONTROL_POINT + bool "Support setting Media Control Point" + default !BT_MCC_MINIMAL + help + This option enables support for setting Media Control Point. + +config BT_MCC_READ_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED + bool "Support reading Media Control Point Opcodes Supported" + default !BT_MCC_MINIMAL + help + This option enables support for reading Media Control Point Opcodes Supported. + +config BT_MCC_READ_CONTENT_CONTROL_ID + bool "Support reading Content Control ID" + default !BT_MCC_MINIMAL + help + This option enables support for reading Content Control ID. + +endif # BT_MCC diff --git a/libs/zblue/audio/Kconfig.mctl b/libs/zblue/audio/Kconfig.mctl new file mode 100644 index 0000000000000..d9bd869e4e046 --- /dev/null +++ b/libs/zblue/audio/Kconfig.mctl @@ -0,0 +1,67 @@ +############################################################################ +# libs/zblue/audio/Kconfig.mctl +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth Audio - Media control configuration options + +# +# Copyright (c) 2022 Nordic Semiconductor +# +# SPDX-License-Identifier: Apache-2.0 +# + +# TODO: Decide, and add, top-level namespace prefix (currently none) to symbols. +# Dependent upon where the module will be placed. + +config MCTL + bool "Support for media player control" + help + Enables support for control of local and remote media players + To enable support for control of a local media player, support for + local media player must be enabled + +if MCTL + +config MCTL_LOCAL_PLAYER_CONTROL + bool "Support for control of local media player" + help + This option enables support for control of a local media player + +config MCTL_LOCAL_PLAYER_LOCAL_CONTROL + bool "Support for local control of local media player" + default y + depends on MCTL_LOCAL_PLAYER_CONTROL + help + This option enables support for local application control of local + media players + +config MCTL_LOCAL_PLAYER_REMOTE_CONTROL + bool "Support for remote control of local media players" + depends on MCTL_LOCAL_PLAYER_CONTROL + help + This option enables support for remote control of local media + players. + +config MCTL_REMOTE_PLAYER_CONTROL + bool "Support for control of remote media player" + default y + # TODO: Remove dependency on BT_MCC once MCC has been reworked + depends on BT_MCC + help + This options enables support for control of a remote media player. + +config MCTL_REMOTE_PLAYER_CONTROL_OBJECTS + bool "Support for accessing objects on remote player" + depends on MCTL_REMOTE_PLAYER_CONTROL + default y + # TODO: Remove dependency on BT_MCC_OTS once MCC has been reworked + depends on BT_MCC_OTS + help + This options enables support for accessing objects (tracks, groups, + search results, ...) on a remote media player + +endif # MCTL diff --git a/libs/zblue/audio/Kconfig.micp b/libs/zblue/audio/Kconfig.micp new file mode 100644 index 0000000000000..237393f852087 --- /dev/null +++ b/libs/zblue/audio/Kconfig.micp @@ -0,0 +1,75 @@ +############################################################################ +# libs/zblue/audio/Kconfig.micp +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth Audio - Microphone Control Service options +# +# Copyright (c) 2020 Bose Corporation +# Copyright (c) 2020-2022 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +########### Microphone Control Profile Microphone Device ########### + +config BT_MICP_MIC_DEV + bool "Microphone Control Profile Microphone Device Support" + depends on BT_GATT_DYNAMIC_DB + depends on BT_BONDABLE + help + This option enables support for Microphone Control Profile + Microphone Device. + +if BT_MICP_MIC_DEV + +config BT_MICP_MIC_DEV_AICS_INSTANCE_COUNT + int "Audio Input Control Service instance count for \ + Microphone Control Service Microphone Device" + default 0 + range 0 BT_AICS_MAX_INSTANCE_COUNT + help + This option sets the number of instances of Audio Input Control + Services for Microphone Control Profile Microphone Device. + +config BT_MICP_MIC_DEV_AICS + bool # Hidden + default y if BT_MICP_MIC_DEV_AICS_INSTANCE_COUNT > 0 + help + This hidden option makes it possible to easily check if AICS is + enabled for Microphone Control Profile Microphone Device. + +endif # BT_MICP_MIC_DEV + +########### Microphone Control Profile Microphone Controller ########### + +config BT_MICP_MIC_CTLR + bool "Microphone Control Profile Microphone Controller Support" + depends on BT_GATT_CLIENT + depends on BT_GATT_AUTO_DISCOVER_CCC + depends on BT_BONDABLE + help + This option enables support for the Microphone Control Profile + Microphone Controller role + +if BT_MICP_MIC_CTLR + +config BT_MICP_MIC_CTLR_MAX_AICS_INST + int "Maximum number of Audio Input Control Service instances to setup" + default 0 + range 0 BT_AICS_CLIENT_MAX_INSTANCE_COUNT + help + Sets the maximum number of Audio Input Control Service (AICS) + instances to setup and use. + +config BT_MICP_MIC_CTLR_AICS + bool # Hidden + default y if BT_MICP_MIC_CTLR_MAX_AICS_INST > 0 + help + This hidden option makes it possible to easily check if AICS is + enabled for MICP client. + +endif # BT_MICP_MIC_CTLR diff --git a/libs/zblue/audio/Kconfig.mpl b/libs/zblue/audio/Kconfig.mpl new file mode 100644 index 0000000000000..150497fda5d05 --- /dev/null +++ b/libs/zblue/audio/Kconfig.mpl @@ -0,0 +1,116 @@ +############################################################################ +# libs/zblue/audio/Kconfig.mpl +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth Audio - Media player configuration options + +# +# Copyright (c) 2022 Nordic Semiconductor +# +# SPDX-License-Identifier: Apache-2.0 +# + +config BT_MPL + bool "Support for media player" + help + Enables support for media player + Note that the provided media player is a sample that only provides a + mock-up with no actual media being played. + For real media playback, the sample must be extended, hooked up to a + real media player or replaced with a real media player. + +if BT_MPL + +config BT_MPL_MEDIA_PLAYER_NAME + string "Media Player Name" + default "Player0" + help + Use this option to set the name of the media player. + +config BT_MPL_MEDIA_PLAYER_NAME_MAX + int "Max length of media player name" + default 20 + range 1 255 + help + Sets the maximum number of bytes (including the null termination) of + the name of the media player. + +config BT_MPL_ICON_URL + string "Media player Icon URL" + default "http://server.some.where/path/icon.png" + help + Use this option to set the URL of the Media Player Icon. + +config BT_MPL_ICON_URL_MAX + int "Max length of media player icon URL" + default 40 + range 1 255 + help + Sets the maximum number of bytes (including the null termination) of + the media player icon URL. + +config BT_MPL_TRACK_TITLE_MAX + int "Max length of the title of a track" + default 40 + range 1 255 + help + Sets the maximum number of bytes (including the null termination) of + the title of any track in the media player. + +config BT_MPL_SEGMENT_NAME_MAX + int "Max length of the name of a track segment" + default 25 + range 1 255 + help + Sets the maximum number of bytes (including the null termination) + of the name of any track segment in the media player. + +config BT_MPL_GROUP_TITLE_MAX + int "Max length of the title of a group of tracks" + default BT_MPL_TRACK_TITLE_MAX + range 1 255 + help + Sets the maximum number of bytes (including the null termination) of + the title of any group in the media player. + +config BT_MPL_OBJECTS + bool "Support for media player objects" + depends on BT_OTS + # TODO: Temporarily depends also on BT_MCS, to avoid issues with the + # bt_mcs_get_ots() call + depends on BT_MCS + help + Enables support for objects in the media player + Objects are used to give/get more information about e.g. media tracks. + Requires the Object Transfer Service + +if BT_MPL_OBJECTS + +config BT_MPL_MAX_OBJ_SIZE + int "Total memory size to use for storing the content of objects" + default 127 + range 0 65536 + help + Sets the total memory size (in octets) to use for storing the content of objects. + This is used for the total memory pool buffer size from which memory + is allocated when sending object content. + +config BT_MPL_ICON_BITMAP_SIZE + int "Media player Icon bitmap object size" + default 127 + help + This option sets the maximum size (in octets) of the icon object. + +config BT_MPL_TRACK_MAX_SIZE + int "Maximum size for a track object" + default 127 + help + This option sets the maximum size (in octets) of a track object. + +endif # BT_MPL_OBJECTS + +endif # BT_MPL diff --git a/libs/zblue/audio/Kconfig.pacs b/libs/zblue/audio/Kconfig.pacs new file mode 100644 index 0000000000000..9fa17559905aa --- /dev/null +++ b/libs/zblue/audio/Kconfig.pacs @@ -0,0 +1,100 @@ +############################################################################ +# libs/zblue/audio/Kconfig.pacs +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth Audio - Published Audio Capabilities configuration options +# +# Copyright (c) 2022 Nordic Semiconductor ASA + +# SPDX-License-Identifier: Apache-2.0 +# + +menu "Published Audio Capabilities (PAC) options" + +config BT_PAC_SNK + bool "Sink PAC Characteristic Support" + depends on BT_GATT_DYNAMIC_DB + depends on BT_SMP + help + This option enables support for Sink Published Audio Capabilities. + +if BT_PAC_SNK +config BT_PAC_SNK_NOTIFIABLE + bool "Sink PAC Notifiable Support" + help + This option enables support for clients to be notified on the Sink + PAC Characteristic changes. + +config BT_PAC_SNK_LOC + bool "Sink PAC Location Support" + default y + help + This option enables support for Sink PAC Location Characteristic. + +config BT_PAC_SNK_LOC_WRITEABLE + bool "Sink PAC Location Writable Support" + depends on BT_PAC_SNK_LOC + help + This option enables support for clients to write to the Sink PAC + Location Characteristic. + +config BT_PAC_SNK_LOC_NOTIFIABLE + bool "Sink Audio Location Notifiable Support" + depends on BT_PAC_SNK_LOC + help + This option enables support for clients to be notified on the Sink + Audio Location Characteristic changes. + +endif # BT_PACS_SNK + +config BT_PAC_SRC + bool "Source PAC Characteristic Support" + depends on BT_GATT_DYNAMIC_DB + help + This option enables support for Source Published Audio Capabilities. + +if BT_PAC_SRC + +config BT_PAC_SRC_NOTIFIABLE + bool "Source PAC Notifiable Support" + help + This option enables support for clients to be notified on the Source + PAC Characteristic changes. + +config BT_PAC_SRC_LOC + bool "Source PAC Location Support" + default y + help + This option enables support for Source PAC Location Characteristic. + +config BT_PAC_SRC_LOC_WRITEABLE + bool "Source PAC Location Writable Support" + depends on BT_PAC_SRC_LOC + help + This option enables support for clients to write to the Source PAC + Location Characteristic. + +config BT_PAC_SRC_LOC_NOTIFIABLE + bool "Source Audio Location Notifiable Support" + depends on BT_PAC_SRC_LOC + help + This option enables support for clients to be notified on the Source + Audio Location Characteristic changes. + +endif # BT_PAC_SRC + +config BT_PACS + def_bool BT_PAC_SNK || BT_PAC_SRC + +config BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE + bool "Supported Audio Context Notifiable Support" + depends on BT_PACS + help + This option enables support for clients to be notified on the + Supported Audio Contexts Characteristic changes. + +endmenu diff --git a/libs/zblue/audio/Kconfig.pbp b/libs/zblue/audio/Kconfig.pbp new file mode 100644 index 0000000000000..f9ed59a61c9d2 --- /dev/null +++ b/libs/zblue/audio/Kconfig.pbp @@ -0,0 +1,19 @@ +############################################################################ +# libs/zblue/audio/Kconfig.pbp +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth Audio - Public Broadcast Profile (PBP) options +# +# Copyright 2023 NXP +# +# SPDX-License-Identifier: Apache-2.0 +# + +config BT_PBP + bool "Public Broadcast Profile" + help + Enabling this will enable PBP. diff --git a/libs/zblue/audio/Kconfig.tbs b/libs/zblue/audio/Kconfig.tbs new file mode 100644 index 0000000000000..42e72e54d0968 --- /dev/null +++ b/libs/zblue/audio/Kconfig.tbs @@ -0,0 +1,275 @@ +############################################################################ +# libs/zblue/audio/Kconfig.tbs +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth Audio - Call control configuration options +# +# Copyright (c) 2020 Bose Corporation +# Copyright (c) 2024-2026 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +if BT_AUDIO + +##################### Telephone Bearer Service ##################### + +config BT_TBS + bool "Telephone Bearer Service Support" + depends on BT_GATT_DYNAMIC_DB + depends on BT_SMP + help + This option enables support for Telephone Bearer Service. By default this only + initializes the GTBS service. If specific TBS services are wanted, they need to be + enabled by setting BT_TBS_BEARER_COUNT to a non-zero value. + +if BT_TBS + +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 0 + range 0 255 + help + Sets the number of TBS instances that are instantiated + +config BT_TBS_MAX_SCHEME_LIST_LENGTH + int "The maximum length of the URI scheme list" + default 30 + range 1 512 + help + Sets the maximum length of the URI scheme list. + +config BT_TBS_LOCK_TIMEOUT + int "Milliseconds of timeout when handling concurrent operations" + range 0 1000 + default 10 + help + The number of milliseconds that the TBS implementation will maximum wait before rejecting + an write request if another is already in progress in another thread. + +endif # BT_TBS + + +##################### Call Control Client ##################### + +config BT_TBS_CLIENT_GTBS + bool "Generic Telephone Bearer Service client support" + depends on BT_GATT_CLIENT + depends on BT_GATT_AUTO_DISCOVER_CCC + depends on BT_SMP + help + This option enables support for the GTBS-oriented Call Control client. + +config BT_TBS_CLIENT_TBS + bool "Telephone Bearer Service client support" + depends on BT_GATT_CLIENT + depends on BT_GATT_AUTO_DISCOVER_CCC + depends on BT_SMP + help + This option enables support for the TBS-oriented Call Control client. + +config BT_TBS_CLIENT + def_bool BT_TBS_CLIENT_GTBS || BT_TBS_CLIENT_TBS + +if BT_TBS_CLIENT + +config BT_TBS_CLIENT_MAX_CALLS + int "Maximum Number Of Calls Supported" + default 1 + help + Sets the maximum number of calls the client supports per TBS instance. + +config BT_TBS_CLIENT_MAX_TBS_INSTANCES + int "Maximum number of TBS instances to setup" + depends on BT_TBS_CLIENT_TBS + default 1 + range 1 3 + help + Sets the maximum number of Telephone Bearer Service (TBS) + instances to setup and use. + +config BT_TBS_CLIENT_MINIMAL + bool "Minimal TBS Client without optional procedures" + default n + help + This option disables all optional procedures in the TBS Client. + +config BT_TBS_CLIENT_BEARER_PROVIDER_NAME + bool "Support reading Bearer Provider Name" + default !BT_TBS_CLIENT_MINIMAL + help + This option enables support for reading Bearer Provider Name. + +config BT_TBS_CLIENT_BEARER_UCI + bool "Support reading Bearer UCI" + default !BT_TBS_CLIENT_MINIMAL + help + This option enables support for reading Bearer UCI. + +config BT_TBS_CLIENT_BEARER_TECHNOLOGY + bool "Support reading Bearer Technology" + default !BT_TBS_CLIENT_MINIMAL + help + This option enables support for reading Bearer Technology. + +config BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST + bool "Support reading Bearer URI Schemes Supported List" + default !BT_TBS_CLIENT_MINIMAL + help + This option enables support for reading Bearer URI Schemes Supported + List. + +config BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH + bool "Support reading Bearer Signal Strength" + default !BT_TBS_CLIENT_MINIMAL + help + This option enables support for reading Bearer Signal Strength. + +config BT_TBS_CLIENT_READ_BEARER_SIGNAL_INTERVAL + bool "Support reading Bearer Signal Strength Reporting Interval" + default !BT_TBS_CLIENT_MINIMAL + help + This option enables support for reading Bearer Signal Strength + Reporting Interval. + +config BT_TBS_CLIENT_SET_BEARER_SIGNAL_INTERVAL + bool "Support setting Bearer Signal Strength Reporting Interval" + default !BT_TBS_CLIENT_MINIMAL + help + This option enables support for setting Bearer Signal Strength + Reporting Interval. + +config BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS + bool "Support reading Bearer List Current Calls" + default !BT_TBS_CLIENT_MINIMAL + help + This option enables support for reading Bearer List Current Calls. + +config BT_TBS_CLIENT_CCID + bool "Support reading Content Control ID" + default !BT_TBS_CLIENT_MINIMAL + help + This option enables support for reading Content Control ID. + +config BT_TBS_CLIENT_INCOMING_URI + bool "Support reading Incoming Call Target Bearer URI" + default !BT_TBS_CLIENT_MINIMAL + help + This option enables support for reading Incoming Call Target Bearer + URI. + +config BT_TBS_CLIENT_STATUS_FLAGS + bool "Support reading Status Flags" + default !BT_TBS_CLIENT_MINIMAL + help + This option enables support for reading Status Flags. + +config BT_TBS_CLIENT_CP_PROCEDURES + def_bool y + depends on (BT_TBS_CLIENT_ACCEPT_CALL || \ + BT_TBS_CLIENT_TERMINATE_CALL || \ + BT_TBS_CLIENT_HOLD_CALL || \ + BT_TBS_CLIENT_RETRIEVE_CALL || \ + BT_TBS_CLIENT_ORIGINATE_CALL || \ + BT_TBS_CLIENT_JOIN_CALLS) + help + This hidden option indicates that there are at least one control + point procedure available. + When this option is disabled it indicates that the control point can + be optimized away. + +config BT_TBS_CLIENT_ACCEPT_CALL + bool "Support Accept Call" + default !BT_TBS_CLIENT_MINIMAL + help + This option enables support for answering an incoming call. + +config BT_TBS_CLIENT_TERMINATE_CALL + bool "Support Terminate Call" + default !BT_TBS_CLIENT_MINIMAL + help + This option enables support for terminating a call. + +config BT_TBS_CLIENT_HOLD_CALL + bool "Support Hold Call" + default !BT_TBS_CLIENT_MINIMAL + help + This option enables support for putting a call on hold. + +config BT_TBS_CLIENT_RETRIEVE_CALL + bool "Support Retrieve Call" + default !BT_TBS_CLIENT_MINIMAL + help + This option enables support for retrieving a call on hold. + +config BT_TBS_CLIENT_ORIGINATE_CALL + bool "Support Originate Call" + default !BT_TBS_CLIENT_MINIMAL + help + This option enables support for originating a call. + +config BT_TBS_CLIENT_JOIN_CALLS + bool "Support Join Calls" + depends on BT_TBS_CLIENT_MAX_CALLS > 1 + default !BT_TBS_CLIENT_MINIMAL + help + This option enables support for joining calls. + +config BT_TBS_CLIENT_OPTIONAL_OPCODES + bool "Support reading Optional Opcodes" + default !BT_TBS_CLIENT_MINIMAL + help + This option enables support for reading Optional Opcodes. + +config BT_TBS_CLIENT_INCOMING_CALL + bool "Support reading Incoming Call" + default !BT_TBS_CLIENT_MINIMAL + help + This option enables support for reading Incoming Call. + +config BT_TBS_CLIENT_CALL_FRIENDLY_NAME + bool "Support reading Call Friendly Name" + default !BT_TBS_CLIENT_MINIMAL + help + This option enables support for reading Call Friendly Name. + +endif # BT_TBS_CLIENT + +if BT_TBS || BT_TBS_CLIENT + +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 in octets (without NULL terminator). + 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 1 512 + help + Sets the maximum length of the bearer provider name in octets (without NULL terminator). + +config BT_TBS_MAX_FRIENDLY_NAME_LENGTH + int "The maximum length of the friendly name supported" + default 30 + range 1 512 + help + Sets the maximum length of the friendly name in octets (without NULL terminator). + +endif # BT_TBS || BT_TBS_CLIENT + +endif # BT_AUDIO diff --git a/libs/zblue/audio/Kconfig.tmap b/libs/zblue/audio/Kconfig.tmap new file mode 100644 index 0000000000000..315d51f2cfb15 --- /dev/null +++ b/libs/zblue/audio/Kconfig.tmap @@ -0,0 +1,45 @@ +############################################################################ +# libs/zblue/audio/Kconfig.tmap +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth Audio - Telephony and Media Audio Profile (TMAP) options +# +# Copyright 2023 NXP +# +# SPDX-License-Identifier: Apache-2.0 +# +config BT_TMAP_CG_SUPPORTED + def_bool (BT_CAP_INITIATOR && BT_CAP_COMMANDER && BT_BAP_UNICAST_CLIENT && \ + BT_BAP_UNICAST_CLIENT_ASE_SRC > 0 && BT_BAP_UNICAST_CLIENT_ASE_SNK > 0 && \ + BT_VCP_VOL_CTLR && BT_TBS) + +config BT_TMAP_CT_SUPPORTED + def_bool BT_CAP_ACCEPTOR && (BT_ASCS_ASE_SNK > 0 || BT_ASCS_ASE_SRC > 0) + +config BT_TMAP_UMS_SUPPORTED + def_bool (BT_CAP_INITIATOR && BT_CAP_COMMANDER && BT_BAP_UNICAST_CLIENT && \ + BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 && BT_VCP_VOL_CTLR && BT_MCS) + +config BT_TMAP_UMR_SUPPORTED + def_bool BT_CAP_ACCEPTOR && BT_ASCS_ASE_SNK > 0 && BT_VCP_VOL_REND + +config BT_TMAP_BMS_SUPPORTED + def_bool BT_CAP_INITIATOR && BT_BAP_BROADCAST_SOURCE + +config BT_TMAP_BMR_SUPPORTED + def_bool BT_CAP_ACCEPTOR && BT_BAP_BROADCAST_SINK && BT_VCP_VOL_REND + +config BT_TMAP + bool "Telephony and Media Audio Profile" + depends on BT_CAP_ACCEPTOR || BT_CAP_INITIATOR + help + Enabling this will enable TMAP. + +parent-module = BT +module = BT_TMAP +module-str = "Telephony and Media Audio Profile" +# source "subsys/logging/Kconfig.template.log_config_inherit" diff --git a/libs/zblue/audio/Kconfig.vcp b/libs/zblue/audio/Kconfig.vcp new file mode 100644 index 0000000000000..44548fd8d2424 --- /dev/null +++ b/libs/zblue/audio/Kconfig.vcp @@ -0,0 +1,109 @@ +############################################################################ +# libs/zblue/audio/Kconfig.vcp +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth Audio - Volume Control Profile configuration options +# +# Copyright (c) 2020 Bose Corporation +# Copyright (c) 2020-2022 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +################### Volume Control Profile Volume Renderer ################### + +config BT_VCP_VOL_REND + bool "Volume Control Profile Volume Renderer Support" + depends on BT_GATT_DYNAMIC_DB + depends on BT_SMP + help + This option enables support for Volume Control Profile Volume Renderer + role and the Volume Control Service. + +if BT_VCP_VOL_REND +config BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT + int "Volume Offset Control Service instance count" + default 0 + range 0 BT_VOCS_MAX_INSTANCE_COUNT + help + This option sets the number of instances of Volume Offset Control + Services. + +config BT_VCP_VOL_REND_VOCS + bool # Hidden + default y if BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT > 0 + help + This hidden option makes it possible to easily check if VOCS is + enabled for VCS. + +config BT_VCP_VOL_REND_AICS_INSTANCE_COUNT + int "Audio Input Control Service instance count for VCS" + default 0 + range 0 BT_AICS_MAX_INSTANCE_COUNT + help + This option sets the number of instances of Audio Input Control + Services for VCS. + +config BT_VCP_VOL_REND_AICS + bool # Hidden + default y if BT_VCP_VOL_REND_AICS_INSTANCE_COUNT > 0 + help + This hidden option makes it possible to easily check if AICS is + enabled for VCS. + +config BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE + bool "Volume Flags notifiable support" + help + This option enables support for clients to subscribe for notifications + on the Volume Flags characteristic. + +endif # BT_VCP_VOL_REND + +################### Volume Control Profile Volume Controller ################### + +config BT_VCP_VOL_CTLR + bool "Volume Control Profile Volume Controller Support" + depends on BT_GATT_CLIENT + depends on BT_GATT_AUTO_DISCOVER_CCC + depends on BT_SMP + help + This option enables support for Volume Control Profile Volume + Controller. + +if BT_VCP_VOL_CTLR + +config BT_VCP_VOL_CTLR_MAX_VOCS_INST + int "Maximum number of VOCS instances to setup" + default 0 + range 0 BT_VOCS_CLIENT_MAX_INSTANCE_COUNT + help + Sets the maximum number of Volume Offset Control Service (VOCS) + instances to setup and use. + +config BT_VCP_VOL_CTLR_VOCS + bool # Hidden + default y if BT_VCP_VOL_CTLR_MAX_VOCS_INST > 0 + help + This hidden option makes it possible to easily check if VOCS is + enabled for VCS client. + +config BT_VCP_VOL_CTLR_MAX_AICS_INST + int "Maximum number of AICS instances to setup" + default 0 + range 0 3 + help + Sets the maximum number of Audio Input Control Service (AICS) + instances to setup and use. + +config BT_VCP_VOL_CTLR_AICS + bool # Hidden + default y if BT_VCP_VOL_CTLR_MAX_AICS_INST > 0 + help + This hidden option makes it possible to easily check if AICS is + enabled for VCS client. + +endif # BT_VCP_VOL_CTLR diff --git a/libs/zblue/audio/Kconfig.vocs b/libs/zblue/audio/Kconfig.vocs new file mode 100644 index 0000000000000..a570b83a6e510 --- /dev/null +++ b/libs/zblue/audio/Kconfig.vocs @@ -0,0 +1,60 @@ +############################################################################ +# libs/zblue/audio/Kconfig.vocs +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth Audio - Volume Offset Control Service options +# +# Copyright (c) 2020 Bose Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# + +##################### Volume Offset Control Service ##################### + +config BT_VOCS_MAX_INSTANCE_COUNT + int "Volume Offset Control Service max instance count" + depends on BT_SMP + default 0 + range 0 15 + help + This option sets the maximum number of instances of Volume Offset + Control Services. + +config BT_VOCS + bool # hidden + default y if BT_VOCS_MAX_INSTANCE_COUNT > 0 + help + This hidden option enables support for Volume Control Service. + +if BT_VOCS + +config BT_VOCS_MAX_OUTPUT_DESCRIPTION_SIZE + int "Volume Offset Control Service max output description size" + default 32 + range 0 512 + help + This option sets the maximum output description size in octets. + +endif # BT_VOCS + +##################### Volume Offset Control Service Client ##################### + +config BT_VOCS_CLIENT_MAX_INSTANCE_COUNT + int "Volume Offset Control Service client max instance count" + depends on BT_SMP + default 0 + range 0 15 + help + This option sets the maximum number of instances of Volume Offset + Control Service clients. + +config BT_VOCS_CLIENT + bool # hidden + default y if BT_VOCS_CLIENT_MAX_INSTANCE_COUNT > 0 + depends on BT_GATT_AUTO_DISCOVER_CCC + help + This hidden option enables support for Volume Offset Control Service. diff --git a/libs/zblue/audio/Make.defs b/libs/zblue/audio/Make.defs new file mode 100644 index 0000000000000..e7c9ce0d88c48 --- /dev/null +++ b/libs/zblue/audio/Make.defs @@ -0,0 +1,167 @@ +############################################################################ +# libs/zblue/audio/Make.defs +# +# Author: chao an +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +ifeq ($(CONFIG_BT_AUDIO),y) + +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/audio.c + +ifneq ($(CONFIG_BT_VOCS)$(CONFIG_BT_VOCS_CLIENT),) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/vocs.c +endif +ifeq ($(CONFIG_BT_VOCS_CLIENT),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/vocs_client.c +endif + +ifneq ($(CONFIG_BT_AICS)$(CONFIG_BT_AICS_CLIENT),) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/aics.c +endif +ifeq ($(CONFIG_BT_AICS_CLIENT),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/aics_client.c +endif + +ifeq ($(CONFIG_BT_VCP_VOL_REND),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/vcp_vol_rend.c +endif +ifeq ($(CONFIG_BT_VCP_VOL_CTLR),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/vcp_vol_ctlr.c +endif + +ifeq ($(CONFIG_BT_MICP_MIC_DEV),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/micp_mic_dev.c +endif +ifeq ($(CONFIG_BT_MICP_MIC_CTLR),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/micp_mic_ctlr.c +endif + +ifeq ($(CONFIG_BT_CONN),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/ccid.c +endif + +ifeq ($(CONFIG_BT_CSIP_SET_MEMBER),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/csip_set_member.c +endif +ifeq ($(CONFIG_BT_CSIP_SET_COORDINATOR),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/csip_set_coordinator.c +endif +ifneq ($(CONFIG_BT_CSIP_SET_MEMBER)$(CONFIG_BT_CSIP_SET_COORDINATOR),) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/csip_crypto.c +endif + +ifeq ($(CONFIG_BT_TBS),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/tbs.c +endif +ifeq ($(CONFIG_BT_TBS_CLIENT),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/tbs_client.c +endif +ifeq ($(CONFIG_BT_MCC),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/mcc.c +endif +ifeq ($(CONFIG_BT_MCS),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/mcs.c +endif +ifeq ($(CONFIG_BT_MPL),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/mpl.c +endif +ifeq ($(CONFIG_MCTL),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/media_proxy.c +endif + +ifeq ($(CONFIG_BT_ASCS),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/ascs.c +endif +ifeq ($(CONFIG_BT_PACS),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/pacs.c +endif +ifeq ($(CONFIG_BT_BAP_STREAM),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/bap_stream.c +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/codec.c +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/bap_iso.c +endif +ifeq ($(CONFIG_BT_BAP_BASE),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/bap_base.c +endif +ifeq ($(CONFIG_BT_BAP_UNICAST_SERVER),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/bap_unicast_server.c +endif +ifeq ($(CONFIG_BT_BAP_UNICAST_CLIENT),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/bap_unicast_client.c +endif +ifeq ($(CONFIG_BT_BAP_BROADCAST_SOURCE),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/bap_broadcast_source.c +endif +ifeq ($(CONFIG_BT_BAP_BROADCAST_SINK),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/bap_broadcast_sink.c +endif +ifeq ($(CONFIG_BT_BAP_SCAN_DELEGATOR),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/bap_scan_delegator.c +endif +ifeq ($(CONFIG_BT_BAP_BROADCAST_ASSISTANT),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/bap_broadcast_assistant.c +endif +ifeq ($(CONFIG_BT_CCP_CALL_CONTROL_CLIENT),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/ccp_call_control_client.c +endif +ifeq ($(CONFIG_BT_CCP_CALL_CONTROL_SERVER),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/ccp_call_control_server.c +endif +ifeq ($(CONFIG_BT_HAS),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/has.c +endif +ifeq ($(CONFIG_BT_HAS_CLIENT),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/has_client.c +endif +ifeq ($(CONFIG_BT_CAP),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/cap_stream.c +endif +ifeq ($(CONFIG_BT_CAP_ACCEPTOR),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/cap_acceptor.c +endif +ifeq ($(CONFIG_BT_CAP_INITIATOR),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/cap_initiator.c +endif +ifeq ($(CONFIG_BT_CAP_COMMANDER),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/cap_commander.c +endif +ifeq ($(CONFIG_BT_CAP_HANDOVER),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/cap_handover.c +endif +ifneq ($(CONFIG_BT_CAP_INITIATOR_UNICAST)$(CONFIG_BT_CAP_COMMANDER),) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/cap_common.c +endif +ifeq ($(CONFIG_BT_TMAP),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/tmap.c +endif +ifeq ($(CONFIG_BT_GMAP),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/gmap_client.c +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/gmap_server.c +endif +ifeq ($(CONFIG_BT_PBP),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/audio/pbp.c +endif + +VPATH += $(TOPDIR)/libs/zblue/audio +DEPPATH += --dep-path $(TOPDIR)/libs/zblue/audio +CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)/libs/zblue/audio + +endif diff --git a/libs/zblue/audio/aics.c b/libs/zblue/audio/aics.c new file mode 100644 index 0000000000000..1c1f6894dd251 --- /dev/null +++ b/libs/zblue/audio/aics.c @@ -0,0 +1,1164 @@ +/**************************************************************************** + * libs/zblue/audio/aics.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Bose Corporation + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/bt_str.h" +#include "aics_internal.h" +#include "audio_internal.h" + +#define LOG_LEVEL CONFIG_BT_AICS_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_aics); + +#define VALID_AICS_OPCODE(opcode) \ + ((opcode) >= BT_AICS_OPCODE_SET_GAIN && (opcode) <= BT_AICS_OPCODE_SET_AUTO) + +#define AICS_CP_LEN 0x02U +#define AICS_CP_SET_GAIN_LEN 0x03U + +static ssize_t write_description( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags); + +static ssize_t write_aics_control( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags); + +#if defined(CONFIG_BT_AICS) +static void aics_state_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value); +static ssize_t read_aics_state( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset); +static ssize_t read_aics_gain_settings( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset); +static ssize_t read_type( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset); +static void aics_input_status_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value); +static ssize_t read_input_status( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset); +static void aics_description_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value); +static ssize_t read_description( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset); + +#define BT_AICS_SERVICE_DEFINITION(_aics) \ + {BT_GATT_SECONDARY_SERVICE(BT_UUID_AICS), \ + BT_AUDIO_CHRC(BT_UUID_AICS_STATE, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, read_aics_state, NULL, &_aics), \ + BT_AUDIO_CCC(aics_state_cfg_changed), \ + BT_AUDIO_CHRC(BT_UUID_AICS_GAIN_SETTINGS, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT, \ + read_aics_gain_settings, NULL, &_aics), \ + BT_AUDIO_CHRC(BT_UUID_AICS_INPUT_TYPE, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT, read_type, \ + NULL, &_aics), \ + BT_AUDIO_CHRC(BT_UUID_AICS_INPUT_STATUS, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, read_input_status, NULL, &_aics), \ + BT_AUDIO_CCC(aics_input_status_cfg_changed), \ + BT_AUDIO_CHRC(BT_UUID_AICS_CONTROL, BT_GATT_CHRC_WRITE, BT_GATT_PERM_WRITE_ENCRYPT, NULL, \ + write_aics_control, &_aics), \ + BT_AUDIO_CHRC(BT_UUID_AICS_DESCRIPTION, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, read_description, write_description, &_aics), \ + BT_AUDIO_CCC(aics_description_cfg_changed)} + +static struct bt_aics aics_insts[CONFIG_BT_AICS_MAX_INSTANCE_COUNT]; +static uint32_t instance_cnt; +BT_GATT_SERVICE_INSTANCE_DEFINE(aics_service_list, + aics_insts, + CONFIG_BT_AICS_MAX_INSTANCE_COUNT, + + BT_AICS_SERVICE_DEFINITION); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void aics_state_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); +} + +static ssize_t read_aics_state( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct bt_aics *inst = BT_AUDIO_CHRC_USER_DATA(attr); + + LOG_DBG("gain %d, mute %u, gain_mode %s (0x%02X), counter %u", + inst->srv.state.gain, + inst->srv.state.mute, bt_aics_mode_to_str( + inst->srv.state.gain_mode), inst->srv.state.gain_mode, + inst->srv.state.change_counter); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &inst->srv.state, + sizeof(inst->srv.state)); +} + +static ssize_t read_aics_gain_settings( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct bt_aics *inst = BT_AUDIO_CHRC_USER_DATA(attr); + + LOG_DBG("units %u, min %d, max %d", inst->srv.gain_settings.units, + inst->srv.gain_settings.minimum, inst->srv.gain_settings.maximum); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + &inst->srv.gain_settings, + sizeof(inst->srv.gain_settings)); +} + +static ssize_t read_type( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct bt_aics *inst = BT_AUDIO_CHRC_USER_DATA(attr); + + LOG_DBG("%u", inst->srv.type); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &inst->srv.type, + sizeof(inst->srv.type)); +} + +static void aics_input_status_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); +} + +static ssize_t read_input_status( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct bt_aics *inst = BT_AUDIO_CHRC_USER_DATA(attr); + + LOG_DBG("%u", inst->srv.status); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &inst->srv.status, + sizeof(inst->srv.status)); +} + +static const char *aics_notify_str(enum bt_aics_notify notify) +{ + switch (notify) + { + case AICS_NOTIFY_STATE: + return "state"; + case AICS_NOTIFY_DESCRIPTION: + return "description"; + case AICS_NOTIFY_STATUS: + return "status"; + default: + return "unknown"; + } +} + +static void notify_work_reschedule(struct bt_aics *inst, + enum bt_aics_notify notify, clock_t delay) +{ + int err; + + atomic_set_bit(inst->srv.notify, notify); + + err = k_work_reschedule(&inst->srv.notify_work, delay); + if (err < 0) + { + LOG_ERR("Failed to reschedule %s notification err %d", + aics_notify_str(notify), err); + } +} + +static void notify(struct bt_aics *inst, + enum bt_aics_notify notify, const struct bt_uuid *uuid, + const void *data, uint16_t len) +{ + int err; + + err = bt_gatt_notify_uuid(NULL, uuid, inst->srv.service_p->attrs, data, + len); + if (err == -ENOMEM) + { + notify_work_reschedule(inst, notify, + USEC2TICK(BT_AUDIO_NOTIFY_RETRY_DELAY_US)); + } + else if (err < 0 && err != -ENOTCONN) + { + LOG_ERR("Notify %s err %d", aics_notify_str(notify), err); + } + else + { + /* Notification sent successfully */ + } +} + +static void notify_work_handler(struct k_work *work) +{ + struct k_work_delayable *d_work = k_work_delayable_from_work(work); + struct bt_aics *inst = CONTAINER_OF(d_work, struct bt_aics, + srv.notify_work); + + if (atomic_test_and_clear_bit(inst->srv.notify, AICS_NOTIFY_STATE)) + { + notify(inst, AICS_NOTIFY_STATE, BT_UUID_AICS_STATE, &inst->srv.state, + sizeof(inst->srv.state)); + } + + if (atomic_test_and_clear_bit(inst->srv.notify, AICS_NOTIFY_DESCRIPTION)) + { + notify(inst, AICS_NOTIFY_DESCRIPTION, BT_UUID_AICS_DESCRIPTION, + &inst->srv.description, + strlen(inst->srv.description)); + } + + if (atomic_test_and_clear_bit(inst->srv.notify, AICS_NOTIFY_STATUS)) + { + notify(inst, AICS_NOTIFY_STATUS, BT_UUID_AICS_INPUT_STATUS, + &inst->srv.status, + sizeof(inst->srv.status)); + } +} + +static void value_changed(struct bt_aics *inst, enum bt_aics_notify notify) +{ + notify_work_reschedule(inst, notify, 0); +} +#else +#define value_changed(...) +#endif /* CONFIG_BT_AICS */ + +static uint8_t valid_control_point_write(uint16_t len, + uint16_t offset, + const struct bt_aics_gain_control *cp, + uint8_t change_counter) +{ + if (offset != 0U) + { + LOG_DBG("Invalid offset: %u", offset); + return BT_ATT_ERR_INVALID_OFFSET; + } + + if (len == 0U || cp == NULL) + { + LOG_DBG("Invalid length (%u) or NULL data (%p)", len, cp); + return BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + + /* Check opcode before length */ + + if (!VALID_AICS_OPCODE(cp->cp.opcode)) + { + LOG_DBG("Invalid opcode %u", cp->cp.opcode); + return BT_AICS_ERR_OP_NOT_SUPPORTED; + } + + if ((len < AICS_CP_LEN) || + (len == AICS_CP_SET_GAIN_LEN + && cp->cp.opcode != BT_AICS_OPCODE_SET_GAIN) || + (len > AICS_CP_SET_GAIN_LEN)) + { + LOG_DBG("Invalid length: %u", len); + return BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + + LOG_DBG("Opcode %u, counter %u", cp->cp.opcode, cp->cp.counter); + if (cp->cp.counter != change_counter) + { + LOG_DBG("Invalid counter: %u != %u", cp->cp.counter, change_counter); + return BT_AICS_ERR_INVALID_COUNTER; + } + + return BT_ATT_ERR_SUCCESS; +} + +static uint8_t handle_set_gain_op( + struct bt_aics *inst, const struct bt_aics_gain_control *cp, + bool *state_change) +{ + LOG_DBG("Set gain %d", cp->gain_setting); + if (cp->gain_setting < inst->srv.gain_settings.minimum || + cp->gain_setting > inst->srv.gain_settings.maximum) + { + return BT_AICS_ERR_OUT_OF_RANGE; + } + + if (inst->srv.state.gain != cp->gain_setting) + { + inst->srv.state.gain = cp->gain_setting; + *state_change = true; + } + + return BT_ATT_ERR_SUCCESS; +} + +static uint8_t handle_unmute_op(struct bt_aics *inst, bool *state_change) +{ + LOG_DBG("Unmute"); + if (inst->srv.state.mute == BT_AICS_STATE_MUTE_DISABLED) + { + return BT_AICS_ERR_MUTE_DISABLED; + } + + if (inst->srv.state.mute != BT_AICS_STATE_UNMUTED) + { + inst->srv.state.mute = BT_AICS_STATE_UNMUTED; + *state_change = true; + } + + return BT_ATT_ERR_SUCCESS; +} + +static uint8_t handle_mute_op(struct bt_aics *inst, bool *state_change) +{ + LOG_DBG("Mute"); + + if (inst->srv.state.mute == BT_AICS_STATE_MUTE_DISABLED) + { + return BT_AICS_ERR_MUTE_DISABLED; + } + + if (inst->srv.state.mute != BT_AICS_STATE_MUTED) + { + inst->srv.state.mute = BT_AICS_STATE_MUTED; + *state_change = true; + } + + return BT_ATT_ERR_SUCCESS; +} + +static uint8_t handle_set_manual_mode_op(struct bt_aics *inst, + bool *state_change) +{ + LOG_DBG("Set manual mode"); + + if (BT_AICS_INPUT_MODE_IMMUTABLE(inst->srv.state.gain_mode)) + { + return BT_AICS_ERR_GAIN_MODE_NOT_ALLOWED; + } + + if (inst->srv.state.gain_mode != BT_AICS_MODE_MANUAL) + { + inst->srv.state.gain_mode = BT_AICS_MODE_MANUAL; + *state_change = true; + } + + return BT_ATT_ERR_SUCCESS; +} + +static uint8_t handle_set_automatic_mode_op(struct bt_aics *inst, + bool *state_change) +{ + LOG_DBG("Set automatic mode"); + + if (BT_AICS_INPUT_MODE_IMMUTABLE(inst->srv.state.gain_mode)) + { + return BT_AICS_ERR_GAIN_MODE_NOT_ALLOWED; + } + + if (inst->srv.state.gain_mode != BT_AICS_MODE_AUTO) + { + inst->srv.state.gain_mode = BT_AICS_MODE_AUTO; + *state_change = true; + } + + return BT_ATT_ERR_SUCCESS; +} + +static ssize_t write_aics_control( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + struct bt_aics *inst = BT_AUDIO_CHRC_USER_DATA(attr); + const struct bt_aics_gain_control *cp = buf; + bool state_change = false; + int ret; + + ARG_UNUSED(conn); + ARG_UNUSED(flags); + + ret = valid_control_point_write(len, offset, cp, + inst->srv.state.change_counter); + if (ret != BT_ATT_ERR_SUCCESS) + { + return BT_GATT_ERR(ret); + } + + switch (cp->cp.opcode) + { + case BT_AICS_OPCODE_SET_GAIN: + ret = handle_set_gain_op(inst, cp, &state_change); + break; + case BT_AICS_OPCODE_UNMUTE: + ret = handle_unmute_op(inst, &state_change); + break; + case BT_AICS_OPCODE_MUTE: + ret = handle_mute_op(inst, &state_change); + break; + case BT_AICS_OPCODE_SET_MANUAL: + ret = handle_set_manual_mode_op(inst, &state_change); + break; + case BT_AICS_OPCODE_SET_AUTO: + ret = handle_set_automatic_mode_op(inst, &state_change); + break; + default: + ret = BT_AICS_ERR_OP_NOT_SUPPORTED; + } + + if (ret != BT_ATT_ERR_SUCCESS) + { + return BT_GATT_ERR(ret); + } + + if (state_change) + { + inst->srv.state.change_counter++; /* May overflow which is OK */ + + LOG_DBG("New state: gain %d, mute %u, gain_mode %s (0x%02X), counter " + "%u", + inst->srv.state.gain, inst->srv.state.mute, + bt_aics_mode_to_str(inst->srv.state.gain_mode), + inst->srv.state.gain_mode, + inst->srv.state.change_counter); + + value_changed(inst, AICS_NOTIFY_STATE); + + if (inst->srv.cb && inst->srv.cb->state) + { + inst->srv.cb->state(inst, 0, inst->srv.state.gain, + inst->srv.state.mute, + inst->srv.state.gain_mode); + } + else + { + LOG_DBG("Callback not registered for instance %p", inst); + } + } + + return len; +} + +#if defined(CONFIG_BT_AICS) +static void aics_description_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); +} +#endif /* CONFIG_BT_AICS */ + +static ssize_t write_description( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + struct bt_aics *inst = BT_AUDIO_CHRC_USER_DATA(attr); + + ARG_UNUSED(conn); + ARG_UNUSED(flags); + + if (offset != 0U) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (len >= sizeof(inst->srv.description)) + { + LOG_DBG("Output desc was clipped from length %u to %zu", len, + sizeof(inst->srv.description) - 1); + + /* We just clip the string value if it's too long */ + + len = (uint16_t)sizeof(inst->srv.description) - 1; + } + + if (memcmp(buf, inst->srv.description, len)) + { + memcpy(inst->srv.description, buf, len); + inst->srv.description[len] = '\0'; + + value_changed(inst, AICS_NOTIFY_DESCRIPTION); + + if (inst->srv.cb && inst->srv.cb->description) + { + inst->srv.cb->description(inst, 0, inst->srv.description); + } + else + { + LOG_DBG("Callback not registered for instance %p", inst); + } + } + + LOG_DBG("%s", inst->srv.description); + + return len; +} + +static int aics_write(struct bt_aics *inst, + ssize_t (*write)(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, + uint16_t offset, + uint8_t flags), + const void *buf, uint16_t len) +{ + struct bt_audio_attr_user_data user_data = + { + .user_data = inst, + }; + + struct bt_gatt_attr attr = + { + .user_data = &user_data, + }; + + int err; + + err = write(NULL, &attr, buf, len, 0, 0); + if (err < 0) + { + return err; + } + + return 0; +} + +#if defined(CONFIG_BT_AICS) +static ssize_t read_description( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct bt_aics *inst = BT_AUDIO_CHRC_USER_DATA(attr); + + LOG_DBG("%s", inst->srv.description); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + &inst->srv.description, + strlen(inst->srv.description)); +} + +/* Public Api */ + +void *bt_aics_svc_decl_get(struct bt_aics *aics) +{ + if (!aics) + { + LOG_DBG("NULL instance"); + return NULL; + } + + return aics->srv.service_p->attrs; +} + +static void prepare_aics_instances(void) +{ + for (int i = 0; i < ARRAY_SIZE(aics_insts); i++) + { + aics_insts[i].srv.service_p = &aics_service_list[i]; + } +} + +int bt_aics_register(struct bt_aics *aics, + struct bt_aics_register_param *param) +{ + int err; + static bool instance_prepared; + + if (!aics) + { + LOG_DBG("NULL aics pointer"); + return -ENOTCONN; + } + + if (!param) + { + LOG_DBG("NULL param"); + return -EINVAL; + } + + if (!instance_prepared) + { + prepare_aics_instances(); + instance_prepared = true; + } + + if (aics->srv.initialized) + { + return -EALREADY; + } + + if (param->mute > BT_AICS_STATE_MUTE_DISABLED) + { + LOG_DBG("Invalid AICS mute value: %u", param->mute); + return -EINVAL; + } + + if (param->gain_mode > BT_AICS_MODE_AUTO) + { + LOG_DBG("Invalid AICS mode value: %u", param->gain_mode); + return -EINVAL; + } + + if (param->type > BT_AICS_INPUT_TYPE_AMBIENT) + { + LOG_DBG("Invalid AICS input type value: %u", param->type); + return -EINVAL; + } + + if (param->units == 0) + { + LOG_DBG("AICS units value shall not be 0"); + return -EINVAL; + } + + if (!(param->min_gain <= param->max_gain)) + { + LOG_DBG("AICS min gain (%d) shall be lower than or equal to max gain " + "(%d)", + param->min_gain, + param->max_gain); + return -EINVAL; + } + + if (param->gain < param->min_gain || param->gain > param->max_gain) + { + LOG_DBG("AICS gain (%d) shall be not lower than min gain (%d) " + "or higher than max gain (%d)", + param->gain, param->min_gain, param->max_gain); + return -EINVAL; + } + + aics->srv.state.gain = param->gain; + aics->srv.state.mute = param->mute; + aics->srv.state.gain_mode = param->gain_mode; + aics->srv.gain_settings.units = param->units; + aics->srv.gain_settings.minimum = param->min_gain; + aics->srv.gain_settings.maximum = param->max_gain; + aics->srv.type = param->type; + aics->srv.status = + param->status ? BT_AICS_STATUS_ACTIVE : BT_AICS_STATUS_INACTIVE; + aics->srv.cb = param->cb; + + atomic_set(aics->srv.notify, 0); + k_work_init_delayable(&aics->srv.notify_work, notify_work_handler); + + if (param->description) + { + (void)utf8_lcpy(aics->srv.description, param->description, + sizeof(aics->srv.description)); + if (IS_ENABLED(CONFIG_BT_AICS_LOG_LEVEL_DBG) && + strcmp(aics->srv.description, param->description)) + { + LOG_DBG("Input desc clipped to %s", aics->srv.description); + } + } + + /* Iterate over the attributes in AICS (starting from i = 1 to skip the + * service declaration) to find the BT_UUID_AICS_DESCRIPTION and update the + * characteristic value (at [i]), update that with the write permission and + * callback, and also update the characteristic declaration (always found + * at [i - 1]) with the BT_GATT_CHRC_WRITE_WITHOUT_RESP property. + */ + + if (param->desc_writable) + { + for (int i = 1; i < aics->srv.service_p->attr_count; i++) + { + struct bt_gatt_attr *attr; + + attr = &aics->srv.service_p->attrs[i]; + + if (!bt_uuid_cmp(attr->uuid, BT_UUID_AICS_DESCRIPTION)) + { + /* Update attr and chrc to be writable */ + + struct bt_gatt_chrc *chrc; + + chrc = aics->srv.service_p->attrs[i - 1].user_data; + attr->perm |= BT_GATT_PERM_WRITE_ENCRYPT; + chrc->properties |= BT_GATT_CHRC_WRITE_WITHOUT_RESP; + + break; + } + } + } + + err = bt_gatt_service_register(aics->srv.service_p); + if (err != 0) + { + LOG_DBG("Could not register AICS service"); + return err; + } + + aics->srv.initialized = true; + + return 0; +} + +struct bt_aics *bt_aics_free_instance_get(void) +{ + if (instance_cnt >= CONFIG_BT_AICS_MAX_INSTANCE_COUNT) + { + return NULL; + } + + return (struct bt_aics *)&aics_insts[instance_cnt++]; +} + +/* Public Api */ + +int bt_aics_deactivate(struct bt_aics *inst) +{ + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_AICS_CLIENT) && inst->client_instance) + { + return -ENOTSUP; + } + + if (inst->srv.status == BT_AICS_STATUS_ACTIVE) + { + inst->srv.status = BT_AICS_STATUS_INACTIVE; + LOG_DBG("Instance %p: Status was set to inactive", inst); + + value_changed(inst, AICS_NOTIFY_STATUS); + + if (inst->srv.cb && inst->srv.cb->status) + { + inst->srv.cb->status(inst, 0, inst->srv.status); + } + else + { + LOG_DBG("Callback not registered for instance %p", inst); + } + } + + return 0; +} + +int bt_aics_activate(struct bt_aics *inst) +{ + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_AICS_CLIENT) && inst->client_instance) + { + return -ENOTSUP; + } + + if (inst->srv.status == BT_AICS_STATUS_INACTIVE) + { + inst->srv.status = BT_AICS_STATUS_ACTIVE; + LOG_DBG("Instance %p: Status was set to active", inst); + + value_changed(inst, AICS_NOTIFY_STATUS); + + if (inst->srv.cb && inst->srv.cb->status) + { + inst->srv.cb->status(inst, 0, inst->srv.status); + } + else + { + LOG_DBG("Callback not registered for instance %p", inst); + } + } + + return 0; +} + +#endif /* CONFIG_BT_AICS */ +int bt_aics_gain_set_manual_only(struct bt_aics *inst) +{ + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + inst->srv.state.gain_mode = BT_AICS_MODE_MANUAL_ONLY; + + value_changed(inst, AICS_NOTIFY_STATE); + + return 0; +} + +int bt_aics_gain_set_auto_only(struct bt_aics *inst) +{ + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + inst->srv.state.gain_mode = BT_AICS_MODE_AUTO_ONLY; + + value_changed(inst, AICS_NOTIFY_STATE); + + return 0; +} + +int bt_aics_state_get(struct bt_aics *inst) +{ + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_AICS_CLIENT) && inst->client_instance) + { + return bt_aics_client_state_get(inst); + } + else if (IS_ENABLED(CONFIG_BT_AICS) && !inst->client_instance) + { + if (inst->srv.cb && inst->srv.cb->state) + { + inst->srv.cb->state(inst, 0, inst->srv.state.gain, + inst->srv.state.mute, + inst->srv.state.gain_mode); + } + else + { + LOG_DBG("Callback not registered for instance %p", inst); + } + + return 0; + } + + return -ENOTSUP; +} + +int bt_aics_gain_setting_get(struct bt_aics *inst) +{ + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_AICS_CLIENT) && inst->client_instance) + { + return bt_aics_client_gain_setting_get(inst); + } + else if (IS_ENABLED(CONFIG_BT_AICS) && !inst->client_instance) + { + if (inst->srv.cb && inst->srv.cb->gain_setting) + { + inst->srv.cb->gain_setting(inst, 0, inst->srv.gain_settings.units, + inst->srv.gain_settings.minimum, + inst->srv.gain_settings.maximum); + } + else + { + LOG_DBG("Callback not registered for instance %p", inst); + } + + return 0; + } + + return -ENOTSUP; +} + +int bt_aics_type_get(struct bt_aics *inst) +{ + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_AICS_CLIENT) && inst->client_instance) + { + return bt_aics_client_type_get(inst); + } + else if (IS_ENABLED(CONFIG_BT_AICS) && !inst->client_instance) + { + if (inst->srv.cb && inst->srv.cb->type) + { + inst->srv.cb->type(inst, 0, inst->srv.type); + } + else + { + LOG_DBG("Callback not registered for instance %p", inst); + } + + return 0; + } + + return -ENOTSUP; +} + +int bt_aics_status_get(struct bt_aics *inst) +{ + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_AICS_CLIENT) && inst->client_instance) + { + return bt_aics_client_status_get(inst); + } + else if (IS_ENABLED(CONFIG_BT_AICS) && !inst->client_instance) + { + if (inst->srv.cb && inst->srv.cb->status) + { + inst->srv.cb->status(inst, 0, inst->srv.status); + } + else + { + LOG_DBG("Callback not registered for instance %p", inst); + } + + return 0; + } + + return -ENOTSUP; +} + +int bt_aics_disable_mute(struct bt_aics *inst) +{ + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + inst->srv.state.mute = BT_AICS_STATE_MUTE_DISABLED; + + value_changed(inst, AICS_NOTIFY_STATE); + + return 0; +} + +int bt_aics_unmute(struct bt_aics *inst) +{ + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_AICS_CLIENT) && inst->client_instance) + { + return bt_aics_client_unmute(inst); + } + else if (IS_ENABLED(CONFIG_BT_AICS) && !inst->client_instance) + { + struct bt_aics_control cp; + + cp.opcode = BT_AICS_OPCODE_UNMUTE; + cp.counter = inst->srv.state.change_counter; + + return aics_write(inst, write_aics_control, &cp, sizeof(cp)); + } + + return -ENOTSUP; +} + +int bt_aics_mute(struct bt_aics *inst) +{ + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_AICS_CLIENT) && inst->client_instance) + { + return bt_aics_client_mute(inst); + } + else if (IS_ENABLED(CONFIG_BT_AICS) && !inst->client_instance) + { + struct bt_aics_control cp; + + cp.opcode = BT_AICS_OPCODE_MUTE; + cp.counter = inst->srv.state.change_counter; + + return aics_write(inst, write_aics_control, &cp, sizeof(cp)); + } + + return -ENOTSUP; +} + +int bt_aics_manual_gain_set(struct bt_aics *inst) +{ + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_AICS_CLIENT) && inst->client_instance) + { + return bt_aics_client_manual_gain_set(inst); + } + else if (IS_ENABLED(CONFIG_BT_AICS) && !inst->client_instance) + { + struct bt_aics_control cp; + + cp.opcode = BT_AICS_OPCODE_SET_MANUAL; + cp.counter = inst->srv.state.change_counter; + + return aics_write(inst, write_aics_control, &cp, sizeof(cp)); + } + + return -ENOTSUP; +} + +int bt_aics_automatic_gain_set(struct bt_aics *inst) +{ + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_AICS_CLIENT) && inst->client_instance) + { + return bt_aics_client_automatic_gain_set(inst); + } + else if (IS_ENABLED(CONFIG_BT_AICS) && !inst->client_instance) + { + struct bt_aics_control cp; + + cp.opcode = BT_AICS_OPCODE_SET_AUTO; + cp.counter = inst->srv.state.change_counter; + + return aics_write(inst, write_aics_control, &cp, sizeof(cp)); + } + + return -ENOTSUP; +} + +int bt_aics_gain_set(struct bt_aics *inst, int8_t gain) +{ + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_AICS_CLIENT) && inst->client_instance) + { + return bt_aics_client_gain_set(inst, gain); + } + else if (IS_ENABLED(CONFIG_BT_AICS) && !inst->client_instance) + { + struct bt_aics_gain_control cp; + + cp.cp.opcode = BT_AICS_OPCODE_SET_GAIN; + cp.cp.counter = inst->srv.state.change_counter; + cp.gain_setting = gain; + + return aics_write(inst, write_aics_control, &cp, sizeof(cp)); + } + + return -ENOTSUP; +} + +int bt_aics_description_get(struct bt_aics *inst) +{ + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_AICS_CLIENT) && inst->client_instance) + { + return bt_aics_client_description_get(inst); + } + else if (IS_ENABLED(CONFIG_BT_AICS) && !inst->client_instance) + { + if (inst->srv.cb && inst->srv.cb->description) + { + inst->srv.cb->description(inst, 0, inst->srv.description); + } + else + { + LOG_DBG("Callback not registered for instance %p", inst); + } + + return 0; + } + + return -ENOTSUP; +} + +int bt_aics_description_set(struct bt_aics *inst, const char *description) +{ + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (!description) + { + LOG_DBG("NULL description"); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_AICS_CLIENT) && inst->client_instance) + { + return bt_aics_client_description_set(inst, description); + } + else if (IS_ENABLED(CONFIG_BT_AICS) && !inst->client_instance) + { + return aics_write(inst, write_description, description, + strlen(description)); + } + + return -ENOTSUP; +} diff --git a/libs/zblue/audio/aics_client.c b/libs/zblue/audio/aics_client.c new file mode 100644 index 0000000000000..967a8a100315e --- /dev/null +++ b/libs/zblue/audio/aics_client.c @@ -0,0 +1,1400 @@ +/**************************************************************************** + * libs/zblue/audio/aics_client.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Bluetooth AICS client */ + +/* Copyright (c) 2020 Bose Corporation + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "aics_internal.h" +#include "common/bt_str.h" + +LOG_MODULE_REGISTER(bt_aics_client, CONFIG_BT_AICS_CLIENT_LOG_LEVEL); + +static struct bt_aics + aics_insts[CONFIG_BT_MAX_CONN * CONFIG_BT_AICS_CLIENT_MAX_INSTANCE_COUNT]; + +static int aics_client_common_control(uint8_t opcode, struct bt_aics *inst); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static struct bt_aics *lookup_aics_by_handle(struct bt_conn *conn, + uint16_t handle) +{ + for (int i = 0; i < ARRAY_SIZE(aics_insts); i++) + { + if (aics_insts[i].cli.conn == conn && + atomic_test_bit(aics_insts[i].cli.flags, + BT_AICS_CLIENT_FLAG_ACTIVE) && + aics_insts[i].cli.start_handle <= handle && + aics_insts[i].cli.end_handle >= handle) + { + return &aics_insts[i]; + } + } + + LOG_DBG("Could not find AICS instance with handle 0x%04x", handle); + + return NULL; +} + +uint8_t aics_client_notify_handler( + struct bt_conn *conn, struct bt_gatt_subscribe_params *params, + const void *data, uint16_t length) +{ + uint16_t handle = params->value_handle; + struct bt_aics *inst; + const struct bt_aics_state *state; + const uint8_t *status; + + if (conn == NULL) + { + return BT_GATT_ITER_CONTINUE; + } + + inst = lookup_aics_by_handle(conn, handle); + + if (!inst) + { + LOG_DBG("Instance not found"); + return BT_GATT_ITER_STOP; + } + + if (!data || !length) + { + return BT_GATT_ITER_CONTINUE; + } + + if (handle == inst->cli.state_handle) + { + if (length == sizeof(*state)) + { + state = (const struct bt_aics_state *)data; + LOG_DBG("Inst %p: Gain %d, mute %u, gain_mode %s (0x%02X), " + "counter " + "%u", + inst, state->gain, + state->mute, bt_aics_mode_to_str( + state->gain_mode), state->gain_mode, + state->change_counter); + + inst->cli.change_counter = state->change_counter; + + if (inst->cli.cb && inst->cli.cb->state) + { + inst->cli.cb->state(inst, 0, state->gain, state->mute, + state->gain_mode); + } + } + } + else if (handle == inst->cli.status_handle) + { + if (length == sizeof(*status)) + { + status = (const uint8_t *)data; + LOG_DBG("Inst %p: Status %u", inst, *status); + if (inst->cli.cb && inst->cli.cb->status) + { + inst->cli.cb->status(inst, 0, *status); + } + } + } + else if (handle == inst->cli.desc_handle) + { + char desc[MIN(BT_L2CAP_RX_MTU, BT_ATT_MAX_ATTRIBUTE_LEN) + 1]; + + /* Truncate if too large */ + + if (length > sizeof(desc) - 1) + { + LOG_DBG("Description truncated from %u to %zu octets", length, + sizeof(desc) - 1); + } + + length = MIN(sizeof(desc) - 1, length); + + memcpy(desc, data, length); + desc[length] = '\0'; + LOG_DBG("Inst %p: Input description: %s", inst, desc); + if (inst->cli.cb && inst->cli.cb->description) + { + inst->cli.cb->description(inst, 0, desc); + } + } + else + { + LOG_DBG("Receive notification on unexpected handle 0x%04X", handle); + } + + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t aics_client_read_state_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + int cb_err = err; + struct bt_aics *inst = lookup_aics_by_handle(conn, params->single.handle); + const struct bt_aics_state *state = (const struct bt_aics_state *)data; + + memset(params, 0, sizeof(*params)); + + if (!inst) + { + LOG_DBG("Instance not found"); + return BT_GATT_ITER_STOP; + } + + LOG_DBG("Inst %p: err: 0x%02X", inst, err); + atomic_clear_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_BUSY); + + if (cb_err) + { + LOG_DBG("State read failed: %d", err); + if (inst->cli.cb && inst->cli.cb->state) + { + inst->cli.cb->state(inst, cb_err, 0, 0, 0); + } + + return BT_GATT_ITER_STOP; + } + + if (data) + { + if (length == sizeof(*state)) + { + LOG_DBG("Gain %d, mute %u, gain_mode %s (0x%02X), counter %u", + state->gain, state->mute, + bt_aics_mode_to_str(state->gain_mode), + state->gain_mode, + state->change_counter); + + inst->cli.change_counter = state->change_counter; + } + else + { + LOG_DBG("Invalid length %u (expected %zu)", + length, + sizeof(*state)); + cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + } + else + { + LOG_DBG("Invalid state"); + cb_err = BT_ATT_ERR_UNLIKELY; + } + + if (inst->cli.cb && inst->cli.cb->state) + { + inst->cli.cb->state(inst, cb_err, state->gain, state->mute, + state->gain_mode); + } + + return BT_GATT_ITER_STOP; +} + +static uint8_t aics_client_read_gain_settings_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, + const void *data, uint16_t length) +{ + int cb_err = err; + struct bt_aics *inst = lookup_aics_by_handle(conn, params->single.handle); + const struct bt_aics_gain_settings *gain_settings = + (const struct bt_aics_gain_settings *)data; + + memset(params, 0, sizeof(*params)); + + if (!inst) + { + LOG_DBG("Instance not found"); + return BT_GATT_ITER_STOP; + } + + LOG_DBG("Inst %p: err: 0x%02X", inst, err); + atomic_clear_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_BUSY); + + if (cb_err) + { + LOG_DBG("Gain settings read failed: %d", err); + if (inst->cli.cb && inst->cli.cb->gain_setting) + { + inst->cli.cb->gain_setting(inst, cb_err, 0, 0, 0); + } + + return BT_GATT_ITER_STOP; + } + + if (data) + { + if (length == sizeof(*gain_settings)) + { + LOG_DBG("Units %u, Max %d, Min %d", gain_settings->units, + gain_settings->maximum, + gain_settings->minimum); + } + else + { + LOG_DBG("Invalid length %u (expected %zu)", length, + sizeof(*gain_settings)); + cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + } + else + { + LOG_DBG("Invalid gain settings"); + cb_err = BT_ATT_ERR_UNLIKELY; + } + + if (inst->cli.cb && inst->cli.cb->gain_setting) + { + inst->cli.cb->gain_setting(inst, cb_err, gain_settings->units, + gain_settings->minimum, + gain_settings->maximum); + } + + return BT_GATT_ITER_STOP; +} + +static uint8_t aics_client_read_type_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + int cb_err = err; + const uint8_t *type = (const uint8_t *)data; + struct bt_aics *inst = lookup_aics_by_handle(conn, params->single.handle); + + memset(params, 0, sizeof(*params)); + + if (!inst) + { + LOG_DBG("Instance not found"); + return BT_GATT_ITER_STOP; + } + + LOG_DBG("Inst %p: err: 0x%02X", inst, err); + atomic_clear_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_BUSY); + + if (cb_err) + { + LOG_DBG("Type read failed: %d", err); + if (inst->cli.cb && inst->cli.cb->type) + { + inst->cli.cb->type(inst, cb_err, 0); + } + + return BT_GATT_ITER_STOP; + } + + if (data) + { + if (length == sizeof(*type)) + { + LOG_DBG("Type %u", *type); + } + else + { + LOG_DBG("Invalid length %u (expected %zu)", length, sizeof(*type)); + cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + } + else + { + LOG_DBG("Invalid type"); + cb_err = BT_ATT_ERR_UNLIKELY; + } + + if (inst->cli.cb && inst->cli.cb->type) + { + inst->cli.cb->type(inst, cb_err, *type); + } + + return BT_GATT_ITER_STOP; +} + +static uint8_t aics_client_read_status_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + int cb_err = err; + const uint8_t *status = (const uint8_t *)data; + struct bt_aics *inst = lookup_aics_by_handle(conn, params->single.handle); + + memset(params, 0, sizeof(*params)); + + if (!inst) + { + LOG_DBG("Instance not found"); + return BT_GATT_ITER_STOP; + } + + LOG_DBG("Inst %p: err: 0x%02X", inst, err); + atomic_clear_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_BUSY); + + if (cb_err) + { + LOG_DBG("Status read failed: %d", err); + if (inst->cli.cb && inst->cli.cb->status) + { + inst->cli.cb->status(inst, cb_err, 0); + } + + return BT_GATT_ITER_STOP; + } + + if (data) + { + if (length == sizeof(*status)) + { + LOG_DBG("Status %u", *status); + } + else + { + LOG_DBG("Invalid length %u (expected %zu)", length, + sizeof(*status)); + cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + } + else + { + LOG_DBG("Invalid status"); + cb_err = BT_ATT_ERR_UNLIKELY; + } + + if (inst->cli.cb && inst->cli.cb->status) + { + inst->cli.cb->status(inst, cb_err, *status); + } + + return BT_GATT_ITER_STOP; +} + +static void aics_cp_notify_app(struct bt_aics *inst, uint8_t err) +{ + if (!inst->cli.cb) + { + return; + } + + switch (inst->cli.cp_val.cp.opcode) + { + case BT_AICS_OPCODE_SET_GAIN: + if (inst->cli.cb->set_gain) + { + inst->cli.cb->set_gain(inst, err); + } + break; + case BT_AICS_OPCODE_UNMUTE: + if (inst->cli.cb->unmute) + { + inst->cli.cb->unmute(inst, err); + } + break; + case BT_AICS_OPCODE_MUTE: + if (inst->cli.cb->mute) + { + inst->cli.cb->mute(inst, err); + } + break; + case BT_AICS_OPCODE_SET_MANUAL: + if (inst->cli.cb->set_manual_mode) + { + inst->cli.cb->set_manual_mode(inst, err); + } + break; + case BT_AICS_OPCODE_SET_AUTO: + if (inst->cli.cb->set_auto_mode) + { + inst->cli.cb->set_auto_mode(inst, err); + } + break; + default: + LOG_DBG("Unknown opcode 0x%02x", inst->cli.cp_val.cp.opcode); + break; + } +} + +static uint8_t internal_read_state_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + int cb_err = err; + struct bt_aics *inst = lookup_aics_by_handle(conn, params->single.handle); + const struct bt_aics_state *state = (const struct bt_aics_state *)data; + + memset(params, 0, sizeof(*params)); + + if (!inst) + { + LOG_ERR("Instance not found"); + return BT_GATT_ITER_STOP; + } + + if (err != 0) + { + LOG_WRN("State read failed: %d", err); + } + else if (data) + { + if (length == sizeof(*state)) + { + int write_err; + + LOG_DBG("Gain %d, mute %u, gain_mode %s (0x%02X), counter %u", + state->gain, state->mute, + bt_aics_mode_to_str(state->gain_mode), + state->gain_mode, + state->change_counter); + inst->cli.change_counter = state->change_counter; + + /* clear busy flag to reuse function */ + + atomic_clear_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_BUSY); + + if (inst->cli.cp_val.cp.opcode == BT_AICS_OPCODE_SET_GAIN) + { + write_err = bt_aics_client_gain_set(inst, + inst->cli.cp_val.gain_setting); + } + else + { + write_err = + aics_client_common_control(inst->cli.cp_val.cp.opcode, + inst); + } + + if (write_err) + { + cb_err = BT_ATT_ERR_UNLIKELY; + } + } + else + { + LOG_DBG("Invalid length %u (expected %zu)", + length, + sizeof(*state)); + cb_err = BT_ATT_ERR_UNLIKELY; + } + } + else + { + /* Since we return BT_GATT_ITER_STOP this should never happen */ + + DEBUGASSERT(false); + } + + if (cb_err) + { + atomic_clear_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_BUSY); + aics_cp_notify_app(inst, cb_err); + } + + return BT_GATT_ITER_STOP; +} + +static void aics_client_write_aics_cp_cb(struct bt_conn *conn, uint8_t err, + struct bt_gatt_write_params *params) +{ + int cb_err = err; + struct bt_aics *inst = lookup_aics_by_handle(conn, params->handle); + + memset(params, 0, sizeof(*params)); + + if (!inst) + { + LOG_DBG("Instance not found"); + return; + } + + LOG_DBG("Inst %p: err: %d", inst, cb_err); + + /* If the change counter is out of data when a write was attempted from + * the application, we automatically initiate a read to get the newest + * state and try again. Once the change counter has been read, we restart + * the applications write request. If it fails the second time, we return + * an error to the application. + */ + + if (cb_err == BT_AICS_ERR_INVALID_COUNTER && + atomic_test_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_CP_RETRIED)) + { + cb_err = BT_ATT_ERR_UNLIKELY; + } + else if (cb_err == BT_AICS_ERR_INVALID_COUNTER && inst->cli.state_handle) + { + inst->cli.read_params.func = internal_read_state_cb; + inst->cli.read_params.handle_count = 1U; + inst->cli.read_params.single.handle = inst->cli.state_handle; + inst->cli.read_params.single.offset = 0U; + + atomic_set_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_CP_RETRIED); + + cb_err = bt_gatt_read(conn, &inst->cli.read_params); + if (cb_err != 0) + { + LOG_WRN("Could not read state: %d", cb_err); + } + else + { + /* Wait for read callback */ + + return; + } + } + else + { + /* Write failed, fallthrough and notify application */ + } + + atomic_clear_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_CP_RETRIED); + atomic_clear_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_BUSY); + + aics_cp_notify_app(inst, cb_err); +} + +static int aics_client_common_control(uint8_t opcode, struct bt_aics *inst) +{ + int err; + + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (!inst->client_instance) + { + LOG_DBG("Not a client instance instance"); + return -EINVAL; + } + + if (inst->cli.conn == NULL) + { + LOG_DBG("NULL conn"); + return -EINVAL; + } + + if (!inst->cli.control_handle) + { + LOG_DBG("Handle not set for opcode %u", opcode); + return -EINVAL; + } + else if (atomic_test_and_set_bit(inst->cli.flags, + BT_AICS_CLIENT_FLAG_BUSY)) + { + return -EBUSY; + } + + inst->cli.cp_val.cp.opcode = opcode; + inst->cli.cp_val.cp.counter = inst->cli.change_counter; + + inst->cli.write_params.offset = 0; + inst->cli.write_params.data = &inst->cli.cp_val.cp; + inst->cli.write_params.length = sizeof(inst->cli.cp_val.cp); + inst->cli.write_params.handle = inst->cli.control_handle; + inst->cli.write_params.func = aics_client_write_aics_cp_cb; + + err = bt_gatt_write(inst->cli.conn, &inst->cli.write_params); + if (err != 0) + { + atomic_clear_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_BUSY); + } + + return err; +} + +static uint8_t aics_client_read_desc_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + int cb_err = err; + char desc[MIN(BT_L2CAP_RX_MTU, BT_ATT_MAX_ATTRIBUTE_LEN) + 1]; + struct bt_aics *inst = lookup_aics_by_handle(conn, params->single.handle); + + memset(params, 0, sizeof(*params)); + + if (!inst) + { + LOG_DBG("Instance not found"); + return BT_GATT_ITER_STOP; + } + + atomic_clear_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_BUSY); + + if (cb_err) + { + LOG_DBG("Description read failed: %d", err); + if (inst->cli.cb && inst->cli.cb->description) + { + inst->cli.cb->description(inst, cb_err, NULL); + } + + return BT_GATT_ITER_STOP; + } + + if (data) + { + LOG_HEXDUMP_DBG(data, length, "Input description read"); + + /* Truncate if too large */ + + if (length > sizeof(desc) - 1) + { + LOG_DBG("Description truncated from %u to %zu octets", length, + sizeof(desc) - 1); + } + + length = MIN(sizeof(desc) - 1, length); + + /* TODO: Handle long reads */ + + memcpy(desc, data, length); + } + + desc[length] = '\0'; + LOG_DBG("Input description: %s", desc); + + if (inst->cli.cb && inst->cli.cb->description) + { + inst->cli.cb->description(inst, cb_err, desc); + } + + return BT_GATT_ITER_STOP; +} + +static bool valid_inst_discovered(struct bt_aics *inst) +{ + return inst->cli.state_handle && inst->cli.gain_handle && + inst->cli.type_handle && + inst->cli.status_handle && inst->cli.control_handle && + inst->cli.desc_handle; +} + +static int store_attr_handle_and_subscribe( + struct bt_aics_client *client_inst, struct bt_conn *conn, + const struct bt_gatt_chrc *chrc) +{ + struct bt_gatt_subscribe_params *sub_params = NULL; + + if (bt_uuid_cmp(chrc->uuid, BT_UUID_AICS_STATE) == 0U) + { + LOG_DBG("Audio Input state"); + client_inst->state_handle = chrc->value_handle; + sub_params = &client_inst->state_sub_params; + } + else if (bt_uuid_cmp(chrc->uuid, BT_UUID_AICS_GAIN_SETTINGS) == 0U) + { + LOG_DBG("Gain settings"); + client_inst->gain_handle = chrc->value_handle; + } + else if (bt_uuid_cmp(chrc->uuid, BT_UUID_AICS_INPUT_TYPE) == 0U) + { + LOG_DBG("Input type"); + client_inst->type_handle = chrc->value_handle; + } + else if (bt_uuid_cmp(chrc->uuid, BT_UUID_AICS_INPUT_STATUS) == 0U) + { + LOG_DBG("Input status"); + client_inst->status_handle = chrc->value_handle; + sub_params = &client_inst->status_sub_params; + } + else if (bt_uuid_cmp(chrc->uuid, BT_UUID_AICS_CONTROL) == 0U) + { + LOG_DBG("Control point"); + client_inst->control_handle = chrc->value_handle; + } + else if (bt_uuid_cmp(chrc->uuid, BT_UUID_AICS_DESCRIPTION) == 0U) + { + LOG_DBG("Description"); + client_inst->desc_handle = chrc->value_handle; + if ((chrc->properties & BT_GATT_CHRC_NOTIFY) != 0U) + { + sub_params = &client_inst->desc_sub_params; + } + + if ((chrc->properties & BT_GATT_CHRC_WRITE_WITHOUT_RESP) != 0U) + { + atomic_set_bit(client_inst->flags, + BT_AICS_CLIENT_FLAG_DESC_WRITABLE); + } + } + else + { + DEBUGASSERT(false); + } + + if (sub_params != NULL) + { + int err; + + sub_params->value = BT_GATT_CCC_NOTIFY; + sub_params->value_handle = chrc->value_handle; + /* TODO: Don't assume that CCC is at value handle + 1; + * do proper discovery; + */ + + sub_params->ccc_handle = chrc->value_handle + 1; + sub_params->notify = aics_client_notify_handler; + atomic_set_bit(sub_params->flags, BT_GATT_SUBSCRIBE_FLAG_VOLATILE); + + err = bt_gatt_subscribe(conn, sub_params); + if (err != 0 && err != -EALREADY) + { + LOG_ERR("Failed to subscribe: %d", err); + + return err; + } + } + + return 0; +} + +static uint8_t aics_discover_func( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_aics_client *client_inst = CONTAINER_OF(params, + struct bt_aics_client, discover_params); + struct bt_aics *inst = CONTAINER_OF(client_inst, struct bt_aics, cli); + const struct bt_gatt_chrc *chrc; + int err; + + if (!attr) + { + LOG_DBG("Discovery complete for AICS %p", inst); + + memset(params, 0, sizeof(*params)); + + atomic_clear_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_BUSY); + + if (inst->cli.cb && inst->cli.cb->discover) + { + err = valid_inst_discovered(inst) ? 0 : -ENOENT; + + inst->cli.cb->discover(inst, err); + } + + return BT_GATT_ITER_STOP; + } + + LOG_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle); + + DEBUGASSERT(params->type == BT_GATT_DISCOVER_CHARACTERISTIC); + + chrc = (const struct bt_gatt_chrc *)attr->user_data; + if (inst->cli.start_handle == 0U) + { + /* if start handle is unset */ + + inst->cli.start_handle = chrc->value_handle; + } + + inst->cli.end_handle = chrc->value_handle; + + err = store_attr_handle_and_subscribe(client_inst, conn, chrc); + if (err != 0) + { + if (client_inst->cb && client_inst->cb->discover) + { + client_inst->cb->discover(inst, err); + } + + return BT_GATT_ITER_STOP; + } + + return BT_GATT_ITER_CONTINUE; +} + +static void aics_client_reset(struct bt_aics *inst) +{ + inst->cli.change_counter = 0; + inst->cli.gain_mode = 0; + inst->cli.start_handle = 0; + inst->cli.end_handle = 0; + inst->cli.state_handle = 0; + inst->cli.gain_handle = 0; + inst->cli.type_handle = 0; + inst->cli.status_handle = 0; + inst->cli.control_handle = 0; + inst->cli.desc_handle = 0; + + atomic_clear_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_DESC_WRITABLE); + atomic_clear_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_CP_RETRIED); + + bt_conn_drop(&inst->cli.conn); +} + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + ARG_UNUSED(reason); + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + for (size_t i = 0U; i < ARRAY_SIZE(aics_insts); i++) + { + if (aics_insts[i].cli.conn == conn) + { + aics_client_reset(&aics_insts[i]); + } + } +} + +BT_CONN_CB_DEFINE(conn_callbacks) = +{ + .disconnected = disconnected, +}; + +int bt_aics_discover(struct bt_conn *conn, struct bt_aics *inst, + const struct bt_aics_discover_param *param) +{ + struct bt_conn *ref; + int err = 0; + + if (!inst || !conn || !param) + { + LOG_DBG("%s cannot be NULL", + inst == NULL ? "inst" : conn == NULL ? "conn" : "param"); + return -EINVAL; + } + + if (param->end_handle <= param->start_handle) + { + LOG_DBG("start_handle (%u) shall be less than end_handle (%u)", + param->start_handle, + param->end_handle); + return -EINVAL; + } + + if (!atomic_test_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_ACTIVE)) + { + LOG_DBG("Inactive instance"); + return -EINVAL; + } + + if (atomic_test_and_set_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_BUSY)) + { + LOG_DBG("Instance is busy"); + return -EBUSY; + } + + ref = bt_conn_ref(conn); + if (ref == NULL) + { + err = -ENOTCONN; + goto cleanup; + } + + aics_client_reset(inst); + + (void)memset(&inst->cli.discover_params, 0, + sizeof(inst->cli.discover_params)); + + inst->cli.discover_params.start_handle = param->start_handle; + inst->cli.discover_params.end_handle = param->end_handle; + inst->cli.discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; + inst->cli.discover_params.func = aics_discover_func; + + inst->cli.conn = ref; + + err = bt_gatt_discover(conn, &inst->cli.discover_params); + if (err != 0) + { + LOG_DBG("Discover failed (err %d)", err); + bt_conn_unref(ref); + inst->cli.conn = NULL; + goto cleanup; + } + + return 0; + +cleanup: + atomic_clear_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_BUSY); + return err; +} + +struct bt_aics *bt_aics_client_free_instance_get(void) +{ + for (int i = 0; i < ARRAY_SIZE(aics_insts); i++) + { + if (!atomic_test_and_set_bit(aics_insts[i].cli.flags, + BT_AICS_CLIENT_FLAG_ACTIVE)) + { + aics_insts[i].client_instance = true; + return &aics_insts[i]; + } + } + + return NULL; +} + +int bt_aics_client_free_instance(struct bt_aics *aics) +{ + ARRAY_FOR_EACH_PTR(aics_insts, inst) + { + if (aics == inst) + { + if (!atomic_test_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_ACTIVE)) + { + return -EALREADY; + } + + /* Test and set the BT_AICS_CLIENT_FLAG_BUSY flag here to reduce, + * but not eliminate, chance of any operations happening while we + * are free'ing this instance. + */ + + if (atomic_test_and_set_bit(inst->cli.flags, + BT_AICS_CLIENT_FLAG_BUSY)) + { + return -EBUSY; + } + + aics_client_reset(inst); + + atomic_clear_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_BUSY); + atomic_clear_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_ACTIVE); + + return 0; + } + } + + return -EINVAL; +} + +int bt_aics_client_conn_get(const struct bt_aics *aics, + struct bt_conn **conn) +{ + if (aics == NULL) + { + LOG_DBG("NULL aics pointer"); + return -EINVAL; + } + + if (!aics->client_instance) + { + LOG_DBG("aics pointer shall be client instance"); + return -EINVAL; + } + + if (aics->cli.conn == NULL) + { + LOG_DBG("aics pointer not associated with a connection. " + "Do discovery first"); + return -ENOTCONN; + } + + *conn = aics->cli.conn; + return 0; +} + +int bt_aics_client_state_get(struct bt_aics *inst) +{ + int err; + + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (!inst->client_instance) + { + LOG_DBG("Not a client instance instance"); + return -EINVAL; + } + + if (inst->cli.conn == NULL) + { + LOG_DBG("NULL conn"); + return -EINVAL; + } + + if (!inst->cli.state_handle) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + else if (atomic_test_and_set_bit(inst->cli.flags, + BT_AICS_CLIENT_FLAG_BUSY)) + { + return -EBUSY; + } + + inst->cli.read_params.func = aics_client_read_state_cb; + inst->cli.read_params.handle_count = 1U; + inst->cli.read_params.single.handle = inst->cli.state_handle; + + err = bt_gatt_read(inst->cli.conn, &inst->cli.read_params); + if (err != 0) + { + atomic_clear_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_BUSY); + } + + return err; +} + +int bt_aics_client_gain_setting_get(struct bt_aics *inst) +{ + int err; + + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (!inst->client_instance) + { + LOG_DBG("Not a client instance instance"); + return -EINVAL; + } + + if (inst->cli.conn == NULL) + { + LOG_DBG("NULL conn"); + return -EINVAL; + } + + if (!inst->cli.gain_handle) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + else if (atomic_test_and_set_bit(inst->cli.flags, + BT_AICS_CLIENT_FLAG_BUSY)) + { + return -EBUSY; + } + + inst->cli.read_params.func = aics_client_read_gain_settings_cb; + inst->cli.read_params.handle_count = 1U; + inst->cli.read_params.single.handle = inst->cli.gain_handle; + + err = bt_gatt_read(inst->cli.conn, &inst->cli.read_params); + if (err != 0) + { + atomic_clear_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_BUSY); + } + + return err; +} + +int bt_aics_client_type_get(struct bt_aics *inst) +{ + int err; + + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (!inst->client_instance) + { + LOG_DBG("Not a client instance instance"); + return -EINVAL; + } + + if (inst->cli.conn == NULL) + { + LOG_DBG("NULL conn"); + return -EINVAL; + } + + if (!inst->cli.type_handle) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + else if (atomic_test_and_set_bit(inst->cli.flags, + BT_AICS_CLIENT_FLAG_BUSY)) + { + return -EBUSY; + } + + inst->cli.read_params.func = aics_client_read_type_cb; + inst->cli.read_params.handle_count = 1U; + inst->cli.read_params.single.handle = inst->cli.type_handle; + + err = bt_gatt_read(inst->cli.conn, &inst->cli.read_params); + if (err != 0) + { + atomic_clear_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_BUSY); + } + + return err; +} + +int bt_aics_client_status_get(struct bt_aics *inst) +{ + int err; + + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (!inst->client_instance) + { + LOG_DBG("Not a client instance instance"); + return -EINVAL; + } + + if (inst->cli.conn == NULL) + { + LOG_DBG("NULL conn"); + return -EINVAL; + } + + if (!inst->cli.status_handle) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + else if (atomic_test_and_set_bit(inst->cli.flags, + BT_AICS_CLIENT_FLAG_BUSY)) + { + return -EBUSY; + } + + inst->cli.read_params.func = aics_client_read_status_cb; + inst->cli.read_params.handle_count = 1U; + inst->cli.read_params.single.handle = inst->cli.status_handle; + + err = bt_gatt_read(inst->cli.conn, &inst->cli.read_params); + if (err != 0) + { + atomic_clear_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_BUSY); + } + + return err; +} + +int bt_aics_client_unmute(struct bt_aics *inst) +{ + return aics_client_common_control(BT_AICS_OPCODE_UNMUTE, inst); +} + +int bt_aics_client_mute(struct bt_aics *inst) +{ + return aics_client_common_control(BT_AICS_OPCODE_MUTE, inst); +} + +int bt_aics_client_manual_gain_set(struct bt_aics *inst) +{ + return aics_client_common_control(BT_AICS_OPCODE_SET_MANUAL, inst); +} + +int bt_aics_client_automatic_gain_set(struct bt_aics *inst) +{ + return aics_client_common_control(BT_AICS_OPCODE_SET_AUTO, inst); +} + +int bt_aics_client_gain_set(struct bt_aics *inst, int8_t gain) +{ + int err; + + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (!inst->client_instance) + { + LOG_DBG("Not a client instance instance"); + return -EINVAL; + } + + if (inst->cli.conn == NULL) + { + LOG_DBG("NULL conn"); + return -EINVAL; + } + + if (!inst->cli.control_handle) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + else if (atomic_test_and_set_bit(inst->cli.flags, + BT_AICS_CLIENT_FLAG_BUSY)) + { + return -EBUSY; + } + + inst->cli.cp_val.cp.opcode = BT_AICS_OPCODE_SET_GAIN; + inst->cli.cp_val.cp.counter = inst->cli.change_counter; + inst->cli.cp_val.gain_setting = gain; + + inst->cli.write_params.data = &inst->cli.cp_val; + inst->cli.write_params.length = sizeof(inst->cli.cp_val); + inst->cli.write_params.handle = inst->cli.control_handle; + inst->cli.write_params.func = aics_client_write_aics_cp_cb; + + err = bt_gatt_write(inst->cli.conn, &inst->cli.write_params); + if (err != 0) + { + atomic_clear_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_BUSY); + } + + return err; +} + +int bt_aics_client_description_get(struct bt_aics *inst) +{ + int err; + + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (!inst->client_instance) + { + LOG_DBG("Not a client instance instance"); + return -EINVAL; + } + + if (inst->cli.conn == NULL) + { + LOG_DBG("NULL conn"); + return -EINVAL; + } + + if (!inst->cli.desc_handle) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + else if (atomic_test_and_set_bit(inst->cli.flags, + BT_AICS_CLIENT_FLAG_BUSY)) + { + return -EBUSY; + } + + inst->cli.read_params.func = aics_client_read_desc_cb; + inst->cli.read_params.handle_count = 1U; + inst->cli.read_params.single.handle = inst->cli.desc_handle; + + err = bt_gatt_read(inst->cli.conn, &inst->cli.read_params); + if (err != 0) + { + atomic_clear_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_BUSY); + } + + return err; +} + +int bt_aics_client_description_set(struct bt_aics *inst, + const char *description) +{ + int err; + + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (!inst->client_instance) + { + LOG_DBG("Not a client instance instance"); + return -EINVAL; + } + + if (inst->cli.conn == NULL) + { + LOG_DBG("NULL conn"); + return -EINVAL; + } + + if (!inst->cli.desc_handle) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + else if (!atomic_test_bit(inst->cli.flags, + BT_AICS_CLIENT_FLAG_DESC_WRITABLE)) + { + LOG_DBG("Description is not writable on peer service instance"); + return -EPERM; + } + else if (atomic_test_and_set_bit(inst->cli.flags, + BT_AICS_CLIENT_FLAG_BUSY)) + { + return -EBUSY; + } + + err = bt_gatt_write_without_response(inst->cli.conn, inst->cli.desc_handle, + description, + strlen(description), false); + if (err != 0) + { + atomic_clear_bit(inst->cli.flags, BT_AICS_CLIENT_FLAG_BUSY); + } + + return err; +} + +void bt_aics_client_cb_register(struct bt_aics *inst, struct bt_aics_cb *cb) +{ + if (!inst) + { + LOG_DBG("inst cannot be NULL"); + return; + } + + inst->cli.cb = cb; +} diff --git a/libs/zblue/audio/aics_internal.h b/libs/zblue/audio/aics_internal.h new file mode 100644 index 0000000000000..4eb8fe5d55d53 --- /dev/null +++ b/libs/zblue/audio/aics_internal.h @@ -0,0 +1,174 @@ +/**************************************************************************** + * libs/zblue/audio/aics_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Internal APIs for Bluetooth AICS. + */ + +/* Copyright (c) 2020 Bose Corporation + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_AICS_INTERNAL_ +#define ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_AICS_INTERNAL_ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_BT_AICS) +#define BT_AICS_MAX_DESC_SIZE CONFIG_BT_AICS_MAX_INPUT_DESCRIPTION_SIZE +#else +#define BT_AICS_MAX_DESC_SIZE 1U +#endif /* CONFIG_BT_AICS */ + +/* AICS opcodes */ +#define BT_AICS_OPCODE_SET_GAIN 0x01U +#define BT_AICS_OPCODE_UNMUTE 0x02U +#define BT_AICS_OPCODE_MUTE 0x03U +#define BT_AICS_OPCODE_SET_MANUAL 0x04U +#define BT_AICS_OPCODE_SET_AUTO 0x05U + +/* AICS status */ +#define BT_AICS_STATUS_INACTIVE 0x00U +#define BT_AICS_STATUS_ACTIVE 0x01U + +#define BT_AICS_INPUT_MODE_IMMUTABLE(gain_mode) \ + ((gain_mode) == BT_AICS_MODE_MANUAL_ONLY || (gain_mode) == BT_AICS_MODE_AUTO_ONLY) + +struct bt_aics_control +{ + uint8_t opcode; + uint8_t counter; +} __packed; + +struct bt_aics_gain_control +{ + struct bt_aics_control cp; + int8_t gain_setting; +} __packed; + +enum bt_aics_client_flag +{ + BT_AICS_CLIENT_FLAG_BUSY, + BT_AICS_CLIENT_FLAG_CP_RETRIED, + BT_AICS_CLIENT_FLAG_DESC_WRITABLE, + BT_AICS_CLIENT_FLAG_ACTIVE, + + BT_AICS_CLIENT_FLAG_NUM_FLAGS, /* keep as last */ +}; + +struct bt_aics_client +{ + uint8_t change_counter; + uint8_t gain_mode; + + uint16_t start_handle; + uint16_t end_handle; + uint16_t state_handle; + uint16_t gain_handle; + uint16_t type_handle; + uint16_t status_handle; + uint16_t control_handle; + uint16_t desc_handle; + struct bt_gatt_subscribe_params state_sub_params; + struct bt_gatt_subscribe_params status_sub_params; + struct bt_gatt_subscribe_params desc_sub_params; + + struct bt_aics_gain_control cp_val; + struct bt_gatt_write_params write_params; + struct bt_gatt_read_params read_params; + struct bt_gatt_discover_params discover_params; + struct bt_aics_cb *cb; + struct bt_conn *conn; + + atomic_define(flags, BT_AICS_CLIENT_FLAG_NUM_FLAGS); +}; + +struct bt_aics_state +{ + int8_t gain; + uint8_t mute; + uint8_t gain_mode; + uint8_t change_counter; +} __packed; + +struct bt_aics_gain_settings +{ + uint8_t units; + int8_t minimum; + int8_t maximum; +} __packed; + +enum bt_aics_notify +{ + AICS_NOTIFY_STATE, + AICS_NOTIFY_DESCRIPTION, + AICS_NOTIFY_STATUS, + AICS_NOTIFY_NUM, +}; + +struct bt_aics_server +{ + struct bt_aics_state state; + struct bt_aics_gain_settings gain_settings; + bool initialized; + uint8_t type; + uint8_t status; + struct bt_aics *inst; + char description[BT_AICS_MAX_DESC_SIZE]; + struct bt_aics_cb *cb; + + struct bt_gatt_service *service_p; + + atomic_define(notify, AICS_NOTIFY_NUM); + struct k_work_delayable notify_work; +}; + +/* Struct used as a common type for the api */ + +struct bt_aics +{ + bool client_instance; + union + { + struct bt_aics_server srv; + struct bt_aics_client cli; + }; +}; + +uint8_t aics_client_notify_handler( + struct bt_conn *conn, struct bt_gatt_subscribe_params *params, + const void *data, uint16_t length); +int bt_aics_client_register(struct bt_aics *inst); +int bt_aics_client_unregister(struct bt_aics *inst); +int bt_aics_client_state_get(struct bt_aics *inst); +int bt_aics_client_gain_setting_get(struct bt_aics *inst); +int bt_aics_client_type_get(struct bt_aics *inst); +int bt_aics_client_status_get(struct bt_aics *inst); +int bt_aics_client_unmute(struct bt_aics *inst); +int bt_aics_client_mute(struct bt_aics *inst); +int bt_aics_client_manual_gain_set(struct bt_aics *inst); +int bt_aics_client_automatic_gain_set(struct bt_aics *inst); +int bt_aics_client_gain_set(struct bt_aics *inst, int8_t gain); +int bt_aics_client_description_get(struct bt_aics *inst); +int bt_aics_client_description_set(struct bt_aics *inst, + const char *description); + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_AICS_INTERNAL_ */ diff --git a/libs/zblue/audio/ascs.c b/libs/zblue/audio/ascs.c new file mode 100644 index 0000000000000..b65d06ae0c290 --- /dev/null +++ b/libs/zblue/audio/ascs.c @@ -0,0 +1,4351 @@ +/**************************************************************************** + * libs/zblue/audio/ascs.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* @file + * @brief Bluetooth ASCS + */ + +/* Copyright (c) 2020 Intel Corporation + * Copyright (c) 2022-2026 Nordic Semiconductor ASA + * Copyright (c) 2024 Demant A/S + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(bt_ascs, CONFIG_BT_ASCS_LOG_LEVEL); + +#include "common/assert.h" +#include "common/bt_str.h" + +#include "../host/att_internal.h" + +#include "ascs_internal.h" +#include "audio_internal.h" +#include "bap_endpoint.h" +#include "bap_iso.h" +#include "bap_stream.h" +#include "bap_unicast_server.h" +#include "cap_internal.h" +#include "pacs_internal.h" + +#define ASE_BUF_SEM_TIMEOUT MSEC2TICK(CONFIG_BT_ASCS_ASE_BUF_TIMEOUT) + +#define MAX_ASES_SESSIONS \ + CONFIG_BT_MAX_CONN *(CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT + CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT) + +#define NTF_HEADER_SIZE (3) /* opcode (1) + handle (2) */ + +BUILD_ASSERT(CONFIG_BT_ASCS_MAX_ACTIVE_ASES <= + MAX(MAX_ASES_SESSIONS, CONFIG_BT_ISO_MAX_CHAN), + "Max active ASEs are set to more than actual number of ASEs " + "or ISOs"); + +#define ASE_ID(_ase) ase->ep.id +#define ASE_DIR(_id) \ + (_id > CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT ? BT_AUDIO_DIR_SOURCE : BT_AUDIO_DIR_SINK) +#define ASE_UUID(_id) \ + (_id > CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT ? BT_UUID_ASCS_ASE_SRC : BT_UUID_ASCS_ASE_SNK) +#define ASE_COUNT (CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT + CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT) +#define BT_BAP_ASCS_RSP_NULL ((struct bt_bap_ascs_rsp[]){BT_BAP_ASCS_RSP(0, 0)}) + +#define CP_RSP_BUF_SIZE \ + (sizeof(struct bt_ascs_cp_rsp) + (ASE_COUNT * sizeof(struct bt_ascs_cp_ase_rsp))) + +/* Ensure that the cp_rsp_buf can fit in any notification + * (sizeof buffer - header for notification) + */ + +BUILD_ASSERT(BT_ATT_BUF_SIZE - NTF_HEADER_SIZE >= CP_RSP_BUF_SIZE, + "BT_ATT_BUF_SIZE not large enough to hold responses for all " + "ASEs"); + +struct bt_ascs_ase +{ + struct bt_conn *conn; + struct bt_bap_ep ep; + const struct bt_gatt_attr *attr; + struct k_work_delayable disconnect_work; + enum bt_bap_ep_state state_pending; + bool unexpected_iso_link_loss; + bool has_pending_state; + bool force_releasing; +}; + +struct ascs_client +{ + struct bt_conn *conn; + struct k_work_delayable notify_work; + + uint8_t _cp_rsp_data[CP_RSP_BUF_SIZE]; + struct net_buf_simple cp_rsp_buf; + bool rsp_building; + bool rsp_pending; +}; + +struct bt_ascs +{ + /* Whether the service has been registered or not */ + + bool registered; + + struct bt_ascs_ase ase_pool[CONFIG_BT_ASCS_MAX_ACTIVE_ASES]; + + struct ascs_client clients[CONFIG_BT_MAX_PAIRED]; +} ascs; + +/* Minimum state size when in the codec configured state */ +#define MIN_CONFIG_STATE_SIZE (1 + 1 + 1 + 1 + 1 + 2 + 3 + 3 + 3 + 3 + 5 + 1) +/* Minimum state size when in the QoS configured state */ +#define MIN_QOS_STATE_SIZE (1 + 1 + 1 + 1 + 3 + 1 + 1 + 2 + 1 + 2 + 3 + 1 + 1 + 1) + +/* Calculate the size requirement of the ASE BUF, based on the maximum + * possible size of the Codec Configured state or the QoS Configured state, + * as either of them can be the largest state + */ +#define ASE_BUF_SIZE \ + MIN(BT_ATT_MAX_ATTRIBUTE_LEN, \ + MAX(MIN_CONFIG_STATE_SIZE + CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE, \ + MIN_QOS_STATE_SIZE + CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE)) + +/* Verify that the prepare count is large enough to cover the maximum value + * we support a client writing + */ + +BUILD_ASSERT((BT_ATT_BUF_SIZE - NTF_HEADER_SIZE) >= ASE_BUF_SIZE || + DIV_ROUND_UP(ASE_BUF_SIZE, + (BT_ATT_BUF_SIZE - NTF_HEADER_SIZE)) <= + CONFIG_BT_ATT_PREPARE_COUNT, + "CONFIG_BT_ATT_PREPARE_COUNT not large enough to cover the " + "maximum supported ASCS value"); + +/* It is mandatory to support long writes in ASCS unconditionally, and thus + * CONFIG_BT_ATT_PREPARE_COUNT must be at least 1 to support the feature + */ + +BUILD_ASSERT(CONFIG_BT_ATT_PREPARE_COUNT > 0, + "CONFIG_BT_ATT_PREPARE_COUNT shall be at least 1"); + +static const struct bt_ascs_cb *ascs_cb; + +static sem_t ase_buf_sem = NXSEM_INITIALIZER(1, 0); +NET_BUF_SIMPLE_DEFINE_STATIC(ase_buf, ASE_BUF_SIZE); + +static int control_point_notify(struct ascs_client *client); +static int ascs_ep_get_status(struct bt_bap_ep *ep, + struct net_buf_simple *buf); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void ascs_app_rsp_warn_valid(const struct bt_bap_ascs_rsp *rsp) +{ + /* Validate application error code */ + + switch (rsp->code) + { + case BT_BAP_ASCS_RSP_CODE_SUCCESS: + case BT_BAP_ASCS_RSP_CODE_CAP_UNSUPPORTED: + case BT_BAP_ASCS_RSP_CODE_NO_MEM: + case BT_BAP_ASCS_RSP_CODE_UNSPECIFIED: + case BT_BAP_ASCS_RSP_CODE_CONF_UNSUPPORTED: + case BT_BAP_ASCS_RSP_CODE_CONF_REJECTED: + case BT_BAP_ASCS_RSP_CODE_METADATA_UNSUPPORTED: + case BT_BAP_ASCS_RSP_CODE_METADATA_REJECTED: + break; + default: + LOG_WRN("Invalid application error code: %u", rsp->code); + return; + } + + /* Validate application error code and reason combinations */ + + switch (rsp->code) + { + case BT_BAP_ASCS_RSP_CODE_SUCCESS: + case BT_BAP_ASCS_RSP_CODE_CAP_UNSUPPORTED: + case BT_BAP_ASCS_RSP_CODE_NO_MEM: + case BT_BAP_ASCS_RSP_CODE_UNSPECIFIED: + if (rsp->reason != BT_BAP_ASCS_REASON_NONE) + { + LOG_WRN("Invalid reason %u for code %u", rsp->reason, rsp->code); + } + break; + case BT_BAP_ASCS_RSP_CODE_CONF_UNSUPPORTED: + case BT_BAP_ASCS_RSP_CODE_CONF_REJECTED: + if (!IN_RANGE(rsp->reason, BT_BAP_ASCS_REASON_NONE, + BT_BAP_ASCS_REASON_CIS)) + { + LOG_WRN("Invalid reason %u for code %u", rsp->reason, rsp->code); + } + break; + case BT_BAP_ASCS_RSP_CODE_METADATA_UNSUPPORTED: + case BT_BAP_ASCS_RSP_CODE_METADATA_REJECTED: + if (!BT_AUDIO_METADATA_TYPE_IS_KNOWN(rsp->metadata_type)) + { + LOG_WRN("Invalid metadata type %u for code %u", + rsp->metadata_type, + rsp->code); + } + break; + default: + break; + } +} + +static bool is_valid_ase_id(uint8_t ase_id) +{ + return IN_RANGE(ase_id, 1, ASE_COUNT); +} + +static void ase_free(struct bt_ascs_ase *ase) +{ + DEBUGASSERT(ase && ase->conn); + + LOG_DBG("conn %p ase %p id 0x%02x", (void *)ase->conn, ase, ASE_ID(ase)); + + if (ase->ep.iso != NULL) + { + bt_bap_iso_unbind_ep(ase->ep.iso, &ase->ep); + } + + bt_conn_drop(&ase->conn); + ase->state_pending = BT_BAP_EP_STATE_IDLE; + ase->has_pending_state = false; + + (void)k_work_cancel_delayable(&ase->disconnect_work); +} + +static int ase_state_notify(struct bt_ascs_ase *ase) +{ + struct bt_conn *conn = ase->conn; + struct bt_conn_info conn_info; + int max_ntf_size; + uint16_t ntf_size; + int err; + + DEBUGASSERT(conn != NULL); + + err = bt_conn_get_info(conn, &conn_info); + DEBUGASSERT(err == 0); + + if (conn_info.state != BT_CONN_STATE_CONNECTED || + !bt_gatt_is_subscribed(conn, ase->attr, BT_GATT_CCC_NOTIFY)) + { + return 0; + } + + err = nxsem_tickwait(&ase_buf_sem, ASE_BUF_SEM_TIMEOUT); + if (err != 0) + { + LOG_WRN("Failed to take ase_buf_sem: %d", err); + + return err; + } + + ascs_ep_get_status(&ase->ep, &ase_buf); + + max_ntf_size = bt_att_get_max_notify_size(conn, BT_ATT_CHAN_OPT_NONE); + if (max_ntf_size < 0) + { + DEBUGASSERT(max_ntf_size != -EINVAL); + nxsem_post(&ase_buf_sem); + return max_ntf_size; + } + + ntf_size = MIN(max_ntf_size, ase_buf.len); + if (ntf_size < ase_buf.len) + { + LOG_DBG("Sending truncated notification (%u / %u)", ntf_size, + ase_buf.len); + } + + err = bt_gatt_notify(conn, ase->attr, ase_buf.data, ntf_size); + + nxsem_post(&ase_buf_sem); + + return err; +} + +static void ascs_disconnect_stream_work_handler(struct k_work *work) +{ + struct k_work_delayable *d_work = k_work_delayable_from_work(work); + struct bt_ascs_ase *ase = CONTAINER_OF(d_work, struct bt_ascs_ase, + disconnect_work); + struct bt_bap_ep *ep = &ase->ep; + struct bt_bap_stream *stream = ep->stream; + struct bt_bap_stream *pair_stream; + + DEBUGASSERT(ep != NULL && ep->iso && stream != NULL); + + if (ep->dir == BT_AUDIO_DIR_SINK) + { + pair_stream = ep->iso->tx.stream; + } + else + { + pair_stream = ep->iso->rx.stream; + } + + LOG_DBG("ase %p ep %p stream %p pair_stream %p", ase, ep, stream, + pair_stream); + + if (pair_stream != NULL) + { + struct bt_ascs_ase *pair_ase; + + DEBUGASSERT(pair_stream->ep != NULL); + + if (pair_stream->ep->state == BT_BAP_EP_STATE_STREAMING) + { + /* Should not disconnect ISO if the stream is paired + * with another one in the streaming state + */ + + return; + } + + pair_ase = CONTAINER_OF(pair_stream->ep, struct bt_ascs_ase, ep); + + /* Cancel pair ASE disconnect work if pending */ + + (void)k_work_cancel_delayable(&pair_ase->disconnect_work); + } + + if (stream != NULL && ep->iso != NULL && + (ep->iso->chan.state == BT_ISO_STATE_CONNECTED || + ep->iso->chan.state == BT_ISO_STATE_CONNECTING)) + { + const int err = bt_bap_stream_disconnect(stream); + + if (err != 0) + { + LOG_ERR("Failed to disconnect CIS %p: %d", stream, err); + } + } +} + +static int ascs_disconnect_stream(struct bt_bap_stream *stream) +{ + struct bt_ascs_ase *ase = CONTAINER_OF(stream->ep, struct bt_ascs_ase, ep); + + LOG_DBG("%p", stream); + + return k_work_reschedule(&ase->disconnect_work, + MSEC2TICK(CONFIG_BT_ASCS_ISO_DISCONNECT_DELAY)); +} + +static void ase_enter_state_idle(struct bt_ascs_ase *ase) +{ + struct bt_bap_stream *stream = ase->ep.stream; + struct bt_bap_stream_ops *ops; + + DEBUGASSERT(stream != NULL); + + ase->ep.receiver_ready = false; + stream->iso = NULL; + + bt_bap_stream_detach(stream); + + ops = stream->ops; + if (ops != NULL && ops->released != NULL) + { + ops->released(stream); + } + + ase_free(ase); +} + +static void ase_enter_state_codec_configured(struct bt_ascs_ase *ase) +{ + struct bt_bap_stream *stream = ase->ep.stream; + struct bt_bap_stream_ops *ops; + + DEBUGASSERT(stream != NULL); + + ase->ep.receiver_ready = false; + stream->iso = NULL; + + ops = stream->ops; + if (ops != NULL && ops->codec_configured != NULL) + { + ops->codec_configured(stream, &ase->ep.qos_pref); + } +} + +static void ase_enter_state_qos_configured(struct bt_ascs_ase *ase) +{ + struct bt_bap_stream *stream = ase->ep.stream; + struct bt_bap_stream_ops *ops; + + DEBUGASSERT(stream != NULL); + + ase->ep.receiver_ready = false; + + ops = stream->ops; + if (ops != NULL && ops->qos_configured != NULL) + { + ops->qos_configured(stream); + } +} + +static void ase_enter_state_enabling(struct bt_ascs_ase *ase) +{ + struct bt_bap_stream *stream = ase->ep.stream; + struct bt_bap_stream_ops *ops; + + DEBUGASSERT(stream != NULL); + + ops = stream->ops; + if (ops != NULL && ops->enabled != NULL) + { + ops->enabled(stream); + } + + /* SINK ASEs can autonomously go into the streaming state if the CIS is + * connected + */ + + if (ase->ep.dir == BT_AUDIO_DIR_SINK && ase->ep.receiver_ready && + ase->ep.iso != NULL && + ase->ep.iso->chan.state == BT_ISO_STATE_CONNECTED) + { + ascs_ep_set_state(&ase->ep, BT_BAP_EP_STATE_STREAMING); + } +} + +static void ase_enter_state_streaming(struct bt_ascs_ase *ase) +{ + struct bt_bap_stream *stream = ase->ep.stream; + struct bt_bap_stream_ops *ops; + + DEBUGASSERT(stream != NULL); + + /* Setup the ISO data path when the stream is started. We could do it + * earlier when the CIS is connected, but then we would just receive audio + * data that we would then just discard + */ + + bt_bap_setup_iso_data_path(stream); + + ops = stream->ops; + if (ops != NULL && ops->started != NULL) + { + ops->started(stream); + } +} + +static void ase_metadata_updated(struct bt_ascs_ase *ase) +{ + struct bt_bap_stream *stream = ase->ep.stream; + struct bt_bap_stream_ops *ops; + + DEBUGASSERT(stream != NULL); + + ops = stream->ops; + if (ops != NULL && ops->metadata_updated != NULL) + { + ops->metadata_updated(stream); + } +} + +static void ase_exit_state_streaming(struct bt_ascs_ase *ase) +{ + const enum bt_bap_ep_state next_state = ase->ep.state; + struct bt_bap_stream *stream = ase->ep.stream; + struct bt_bap_stream_ops *ops; + uint8_t reason = ase->ep.reason; + + DEBUGASSERT(stream != NULL); + + if (reason == BT_HCI_ERR_SUCCESS) + { + /* Default to BT_HCI_ERR_UNSPECIFIED if no other reason is set */ + + reason = BT_HCI_ERR_UNSPECIFIED; + } + + if (ase->ep.iso != NULL && + ase->ep.iso->chan.state == BT_ISO_STATE_CONNECTED) + { + /* Remove the ISO data path as we no longer want to process any ISO + * data for this stream, but only if the CIS is still connected. If the + * CIS disconnected, then the data path is automatically removed by the + * controller + */ + + bt_bap_remove_iso_data_path(stream); + } + + ops = stream->ops; + + /* On link-loss we go from streaming state to QOS configured state, + * and it makes sense to do the disabled callback before entering the QOS + * configured state + */ + + if (next_state == BT_BAP_EP_STATE_QOS_CONFIGURED) + { + if (ops != NULL && ops->disabled != NULL) + { + ops->disabled(stream); + } + else + { + LOG_WRN("No callback for disabled set"); + } + } + + if (ops != NULL && ops->stopped != NULL) + { + ops->stopped(stream, reason); + } + else + { + LOG_WRN("No callback for stopped set"); + } +} + +static void ase_exit_state_enabling(struct bt_ascs_ase *ase) +{ + const enum bt_bap_ep_state next_state = ase->ep.state; + struct bt_bap_stream *stream = ase->ep.stream; + struct bt_bap_stream_ops *ops; + + ops = stream->ops; + + /* When the EP direction is BT_AUDIO_DIR_SOURCE the state machine goes from + * enabled to disabled where the disabled callback will be called, for + * BT_AUDIO_DIR_SINK we go from enabled to qos_configured, and logically we + * have to do the disabled callback first + */ + + if (next_state == BT_BAP_EP_STATE_QOS_CONFIGURED && + ase->ep.dir == BT_AUDIO_DIR_SINK) + { + if (ops != NULL && ops->disabled != NULL) + { + ops->disabled(stream); + } + else + { + LOG_WRN("No callback for disabled set"); + } + } +} + +static void ase_enter_state_disabling(struct bt_ascs_ase *ase) +{ + struct bt_bap_stream *stream = ase->ep.stream; + struct bt_bap_stream_ops *ops; + + DEBUGASSERT(stream != NULL); + + ase->ep.receiver_ready = false; + + ops = stream->ops; + if (ops != NULL && ops->disabled != NULL) + { + ops->disabled(stream); + } +} + +static void ase_enter_state_releasing(struct bt_ascs_ase *ase) +{ + struct bt_bap_stream *stream = ase->ep.stream; + + DEBUGASSERT(stream != NULL); + + ase->ep.receiver_ready = false; + + /* Either the client or the server may disconnect the CISes when entering + * the releasing state. + */ + + if (bt_bap_stream_can_disconnect(stream)) + { + int err; + + err = ascs_disconnect_stream(stream); + if (err < 0) + { + LOG_ERR("Failed to disconnect stream %p: %d", stream, err); + } + } + else + { + ascs_ep_set_state(&ase->ep, BT_BAP_EP_STATE_IDLE); + } +} + +static struct ascs_client *client_from_conn(const struct bt_conn *conn) +{ + ARRAY_FOR_EACH_PTR(ascs.clients, client) + { + if (client->conn == conn) + { + return client; + } + } + + return NULL; +} + +/** + * @brief Perform actions **after** a state change + * + * @param ase The ASE to do the post-state transition for + */ + +static void execute_post_state_transition(struct bt_ascs_ase *ase, + enum bt_bap_ep_state old_state) +{ + const enum bt_bap_ep_state new_state = ase->ep.state; + + LOG_DBG("ase %p ep %p id 0x%02x %s -> %s", ase, &ase->ep, ASE_ID(ase), + bt_bap_ep_state_str(old_state), bt_bap_ep_state_str(new_state)); + + if (old_state == new_state) + { + switch (new_state) + { + case BT_BAP_EP_STATE_ENABLING: + case BT_BAP_EP_STATE_STREAMING: + ase_metadata_updated(ase); + return; + default: + break; + } + } + + /* Actions needed for exiting the old state */ + + switch (old_state) + { + case BT_BAP_EP_STATE_STREAMING: + ase_exit_state_streaming(ase); + break; + case BT_BAP_EP_STATE_ENABLING: + ase_exit_state_enabling(ase); + break; + default: + break; + } + + /* Actions needed for entering the new state */ + + switch (new_state) + { + case BT_BAP_EP_STATE_IDLE: + ase_enter_state_idle(ase); + break; + case BT_BAP_EP_STATE_CODEC_CONFIGURED: + ase_enter_state_codec_configured(ase); + break; + case BT_BAP_EP_STATE_QOS_CONFIGURED: + ase_enter_state_qos_configured(ase); + break; + case BT_BAP_EP_STATE_ENABLING: + ase_enter_state_enabling(ase); + break; + case BT_BAP_EP_STATE_STREAMING: + ase_enter_state_streaming(ase); + break; + case BT_BAP_EP_STATE_DISABLING: + ase_enter_state_disabling(ase); + break; + case BT_BAP_EP_STATE_RELEASING: + ase_enter_state_releasing(ase); + break; + default: + LOG_ERR("Invalid state %d", new_state); + } +} + +static int execute_state_transition(struct bt_ascs_ase *ase) +{ + const enum bt_bap_ep_state new_state = ase->state_pending; + const enum bt_bap_ep_state old_state = ase->ep.state; + int err; + + DEBUGASSERT(ase->has_pending_state); + + ase->ep.state = new_state; + + /* Notify ASE state if not force releasing */ + + if (ase->conn != NULL && !ase->force_releasing) + { + err = ase_state_notify(ase); + if (err == -ENOMEM) + { + /* Revert back to old state */ + + ase->ep.state = old_state; + return err; + } + + if (err < 0) + { + LOG_ERR("Failed to notify ASE %p state (err %d)", ase, err); + return err; + } + } + + ase->has_pending_state = false; + + execute_post_state_transition(ase, old_state); + + return 0; +} + +static void reschedule_notify_work(struct ascs_client *client) +{ + struct bt_conn_info info; + uint32_t retry_delay_us; + __maybe_unused int err; + + err = bt_conn_get_info(client->conn, &info); + DEBUGASSERT(err == 0); + + retry_delay_us = info.le.interval_us; + + /* Reschedule the state transition */ + + err = k_work_reschedule(&client->notify_work, USEC2TICK(retry_delay_us)); + DEBUGASSERT(err >= 0); + + LOG_DBG("Out of buffers for notification. Will retry in %dus", + retry_delay_us); +} + +static void notify_work_handler(struct k_work *work) +{ + struct ascs_client *client = + CONTAINER_OF(k_work_delayable_from_work(work), struct ascs_client, + notify_work); + + if (client->rsp_building) + { + /* Response is building, so do not attempt to send any incomplete + * notifications + */ + + reschedule_notify_work(client); + return; + } + + if (client->rsp_pending) + { + const int err = control_point_notify(client); + + if (err == -ENOMEM) + { + reschedule_notify_work(client); + return; + } + + net_buf_simple_reset(&client->cp_rsp_buf); + client->rsp_pending = false; + + /* Any other error will be non-recoverable (BT disabled, conn + * disconnected, client unsubscribed, ASCS unregistered, etc. so don't + * attempt to reschedule or to send any other notifications for this + * client. + */ + + if (err != 0) + { + return; + } + } + + ARRAY_FOR_EACH_PTR(ascs.ase_pool, ase) + { + if (ase->conn == client->conn && ase->has_pending_state) + { + const int err = execute_state_transition(ase); + + if (err == -ENOMEM) + { + reschedule_notify_work(client); + return; + } + + /* Any other error will be non-recoverable (BT disabled, conn + * disconnected, client unsubscribed, ASCS unregistered, etc. so + * don't attempt to reschedule or to send any other notifications + * for this client. + */ + + if (err != 0) + { + return; + } + } + } +} + +int ascs_ep_set_state(struct bt_bap_ep *ep, enum bt_bap_ep_state state) +{ + struct bt_ascs_ase *ase = CONTAINER_OF(ep, struct bt_ascs_ase, ep); + struct ascs_client *client = client_from_conn(ase->conn); + int err; + + if (client == NULL) + { + LOG_ERR("Could not set state for NULL client"); + return -EINVAL; + } + + if (!bt_bap_stream_valid_state_transition(ep, state)) + { + return -EBADMSG; + } + + /* Take semaphore here in case that `ase_state_notify` is constructing the + * new state to avoid modifying the `state_pending` value. + */ + + err = nxsem_tickwait(&ase_buf_sem, ASE_BUF_SEM_TIMEOUT); + if (err != 0) + { + LOG_WRN("Failed to take ase_buf_sem: %d", err); + + return err; + } + + ase->state_pending = state; + ase->has_pending_state = true; + + nxsem_post(&ase_buf_sem); + + err = k_work_schedule(&client->notify_work, 0); + if (err < 0) + { + LOG_ERR("Failed to schedule state transition work err %d", err); + return err; + } + + return 0; +} + +static void ascs_ep_get_status_config(struct bt_bap_ep *ep, + struct net_buf_simple *buf) +{ + struct bt_ascs_ase_status_config *cfg; + struct bt_bap_qos_cfg_pref *pref = &ep->qos_pref; + + cfg = net_buf_simple_add(buf, sizeof(*cfg)); + cfg->framing = + pref->unframed_supported ? + BT_ASCS_QOS_FRAMING_UNFRAMED : BT_ASCS_QOS_FRAMING_FRAMED; + cfg->phy = pref->phy; + cfg->rtn = pref->rtn; + cfg->latency = sys_cpu_to_le16(pref->latency); + sys_put_le24(pref->pd_min, cfg->pd_min); + sys_put_le24(pref->pd_max, cfg->pd_max); + sys_put_le24(pref->pref_pd_min, cfg->prefer_pd_min); + sys_put_le24(pref->pref_pd_max, cfg->prefer_pd_max); + cfg->codec.id = ep->codec_cfg.id; + cfg->codec.cid = sys_cpu_to_le16(ep->codec_cfg.cid); + cfg->codec.vid = sys_cpu_to_le16(ep->codec_cfg.vid); + + LOG_DBG("dir %s unframed_supported 0x%02x phy 0x%02x rtn %u " + "latency %u pd_min %u pd_max %u pref_pd_min %u pref_pd_max %u " + "codec id 0x%02x", + bt_audio_dir_to_str(ep->dir), pref->unframed_supported, pref->phy, + pref->rtn, + pref->latency, pref->pd_min, pref->pd_max, pref->pref_pd_min, + pref->pref_pd_max, + ep->stream->codec_cfg->id); + + cfg->cc_len = ep->codec_cfg.data_len; + net_buf_simple_add_mem(buf, ep->codec_cfg.data, ep->codec_cfg.data_len); +} + +static void ascs_ep_get_status_qos(struct bt_bap_ep *ep, + struct net_buf_simple *buf) +{ + struct bt_ascs_ase_status_qos *qos; + + qos = net_buf_simple_add(buf, sizeof(*qos)); + qos->cig_id = ep->cig_id; + qos->cis_id = ep->cis_id; + sys_put_le24(ep->stream->qos->interval, qos->interval); + qos->framing = ep->stream->qos->framing; + qos->phy = ep->stream->qos->phy; + qos->sdu = sys_cpu_to_le16(ep->stream->qos->sdu); + qos->rtn = ep->stream->qos->rtn; + qos->latency = sys_cpu_to_le16(ep->stream->qos->latency); + sys_put_le24(ep->stream->qos->pd, qos->pd); + + LOG_DBG("dir %s codec id 0x%02x interval %u framing 0x%02x phy 0x%02x " + "rtn %u latency %u pd %u", + bt_audio_dir_to_str(ep->dir), ep->stream->codec_cfg->id, + ep->stream->qos->interval, + ep->stream->qos->framing, ep->stream->qos->phy, + ep->stream->qos->rtn, + ep->stream->qos->latency, ep->stream->qos->pd); +} + +static void ascs_ep_get_status_enable(struct bt_bap_ep *ep, + struct net_buf_simple *buf) +{ + struct bt_ascs_ase_status_enable *enable; + + enable = net_buf_simple_add(buf, sizeof(*enable)); + enable->cig_id = ep->cig_id; + enable->cis_id = ep->cis_id; + +#if CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE > 0 + enable->metadata_len = ep->codec_cfg.meta_len; + net_buf_simple_add_mem(buf, ep->codec_cfg.meta, ep->codec_cfg.meta_len); +#else + enable->metadata_len = 0U; +#endif /* CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE > 0 */ + + LOG_DBG("dir %s cig 0x%02x cis 0x%02x", bt_audio_dir_to_str(ep->dir), + ep->cig_id, ep->cis_id); +} + +static ssize_t ascs_ase_read_status_idle( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct bt_ascs_ase_status status = + { + .id = POINTER_TO_UINT(BT_AUDIO_CHRC_USER_DATA(attr)), + .state = BT_BAP_EP_STATE_IDLE, + }; + + LOG_DBG("conn %p id 0x%02x", (void *)conn, status.id); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &status, + sizeof(status)); +} + +static int ascs_ep_get_status(struct bt_bap_ep *ep, + struct net_buf_simple *buf) +{ + if (!ep || !buf) + { + return -EINVAL; + } + + LOG_DBG("ep %p id 0x%02x state %s", ep, ep->id, + bt_bap_ep_state_str(ep->state)); + + /* Reset if buffer before using */ + + net_buf_simple_reset(buf); + + (void)net_buf_simple_add_u8(buf, ep->id); + (void)net_buf_simple_add_u8(buf, ep->state); + + switch (ep->state) + { + case BT_BAP_EP_STATE_IDLE: + + /* Fallthrough */ + + case BT_BAP_EP_STATE_RELEASING: + break; + case BT_BAP_EP_STATE_CODEC_CONFIGURED: + ascs_ep_get_status_config(ep, buf); + break; + case BT_BAP_EP_STATE_QOS_CONFIGURED: + ascs_ep_get_status_qos(ep, buf); + break; + case BT_BAP_EP_STATE_ENABLING: + + /* Fallthrough */ + + case BT_BAP_EP_STATE_STREAMING: + + /* Fallthrough */ + + case BT_BAP_EP_STATE_DISABLING: + ascs_ep_get_status_enable(ep, buf); + break; + default: + LOG_ERR("Invalid Endpoint state"); + break; + } + + return 0; +} + +static int ascs_iso_accept(const struct bt_iso_accept_info *info, + struct bt_iso_chan **iso_chan) +{ + LOG_DBG("conn %p", (void *)info->acl); + + for (size_t i = 0U; i < ARRAY_SIZE(ascs.ase_pool); i++) + { + struct bt_ascs_ase *ase = &ascs.ase_pool[i]; + enum bt_bap_ep_state state; + struct bt_iso_chan *chan; + + if (ase->conn != info->acl || ase->ep.cig_id != info->cig_id || + ase->ep.cis_id != info->cis_id) + { + continue; + } + + state = ase->ep.state; + if (state != BT_BAP_EP_STATE_ENABLING && + state != BT_BAP_EP_STATE_QOS_CONFIGURED) + { + LOG_WRN("ase %p cannot accept ISO connection", ase); + break; + } + + DEBUGASSERT(ase->ep.iso != NULL); + + chan = &ase->ep.iso->chan; + if (chan->iso != NULL) + { + LOG_WRN("ase %p chan %p already connected", ase, chan); + return -EALREADY; + } + + *iso_chan = chan; + + LOG_DBG("iso_chan %p", *iso_chan); + + return 0; + } + + return -EACCES; +} + +#if defined(CONFIG_BT_AUDIO_RX) +static void ascs_iso_recv( + struct bt_iso_chan *chan, const struct bt_iso_recv_info *info, + struct net_buf *buf) +{ + struct bt_bap_iso *iso = CONTAINER_OF(chan, struct bt_bap_iso, chan); + const struct bt_bap_stream_ops *ops; + struct bt_bap_stream *stream; + struct bt_bap_ep *ep; + + ep = iso->rx.ep; + if (ep == NULL) + { + /* In the case that the CIS has been setup as bidirectional, and + * only one of the directions have an ASE configured yet, we should + * only care about valid ISO packets when doing this check. The reason + * is that some controllers send HCI ISO data packets to the host, even + * if no SDU was sent on the remote side. This basically means that + * empty PDUs are sent to the host as HCI ISO data packets, which we + * should just ignore + */ + + if ((info->flags & BT_ISO_FLAGS_VALID) != 0) + { + LOG_DBG("Valid ISO packet of len %zu received for iso %p not " + "bound " + "with ep", + net_buf_frags_len(buf), chan); + } + + return; + } + + if (ep->state != BT_BAP_EP_STATE_STREAMING) + { + if (IS_ENABLED(CONFIG_BT_BAP_DEBUG_STREAM_DATA)) + { + LOG_DBG("ep %p is not in the streaming state: %s", ep, + bt_bap_ep_state_str(ep->state)); + } + + return; + } + + stream = ep->stream; + if (stream == NULL) + { + LOG_ERR("No stream for ep %p", ep); + return; + } + + ops = stream->ops; + + if (IS_ENABLED(CONFIG_BT_BAP_DEBUG_STREAM_DATA)) + { + LOG_DBG("stream %p ep %p len %zu", stream, stream->ep, + net_buf_frags_len(buf)); + } + + if (ops != NULL && ops->recv != NULL) + { + ops->recv(stream, info, buf); + } + else + { + LOG_WRN("No callback for recv set"); + } +} +#endif /* CONFIG_BT_AUDIO_RX */ + +#if defined(CONFIG_BT_AUDIO_TX) +static void ascs_iso_sent(struct bt_iso_chan *chan) +{ + struct bt_bap_iso *iso = CONTAINER_OF(chan, struct bt_bap_iso, chan); + const struct bt_bap_stream_ops *ops; + struct bt_bap_stream *stream; + struct bt_bap_ep *ep; + + ep = iso->tx.ep; + if (ep == NULL) + { + LOG_ERR("iso %p not bound with ep", chan); + return; + } + + stream = ep->stream; + if (stream == NULL) + { + LOG_ERR("No stream for ep %p", ep); + return; + } + + ops = stream->ops; + + if (IS_ENABLED(CONFIG_BT_BAP_DEBUG_STREAM_DATA)) + { + LOG_DBG("stream %p ep %p", stream, stream->ep); + } + + if (ops != NULL && ops->sent != NULL) + { + ops->sent(stream); + } +} +#endif /* CONFIG_BT_AUDIO_TX */ + +static void ascs_update_sdu_size(struct bt_bap_ep *ep) +{ + struct bt_iso_chan_io_qos *io_qos; + struct bt_bap_qos_cfg *qos_cfg = &ep->qos; + + if (ep->dir == BT_AUDIO_DIR_SINK) + { + io_qos = ep->iso->chan.qos->rx; + } + else if (ep->dir == BT_AUDIO_DIR_SOURCE) + { + io_qos = ep->iso->chan.qos->tx; + } + else + { + return; + } + + io_qos->sdu = qos_cfg->sdu; + io_qos->rtn = qos_cfg->rtn; +} + +static void ascs_ep_iso_connected(struct bt_bap_ep *ep) +{ + struct bt_ascs_ase *ase = CONTAINER_OF(ep, struct bt_ascs_ase, ep); + const struct bt_bap_stream_ops *stream_ops; + struct bt_bap_stream *stream; + + if (ep->state != BT_BAP_EP_STATE_ENABLING) + { + LOG_DBG("ep %p not in enabling state: %s", ep, + bt_bap_ep_state_str(ep->state)); + return; + } + + stream = ep->stream; + if (stream == NULL) + { + LOG_ERR("No stream for ep %p", ep); + return; + } + + /* Reset reason */ + + ep->reason = BT_HCI_ERR_SUCCESS; + ase->unexpected_iso_link_loss = false; + + /* Some values are not provided by the HCI events when the CIS is + * established for the peripheral, so we update them here based on the + * parameters provided by the BAP Unicast Client + */ + + ascs_update_sdu_size(ep); + + LOG_DBG("stream %p ep %p dir " + "%s", stream, ep, bt_audio_dir_to_str(ep->dir)); + +#if defined(CONFIG_BT_BAP_DEBUG_STREAM_SEQ_NUM) + /* reset sequence number */ + + stream->_prev_seq_num = 0U; +#endif /* CONFIG_BT_BAP_DEBUG_STREAM_SEQ_NUM */ + + stream_ops = stream->ops; + if (stream_ops != NULL && stream_ops->connected != NULL) + { + stream_ops->connected(stream); + } + + if (ep->dir == BT_AUDIO_DIR_SINK && ep->receiver_ready) + { + /* Source ASEs shall be ISO connected first, and then receive + * the receiver start ready command to enter the streaming state + */ + + ascs_ep_set_state(ep, BT_BAP_EP_STATE_STREAMING); + } +} + +static void ascs_iso_connected(struct bt_iso_chan *chan) +{ + struct bt_bap_iso *iso = CONTAINER_OF(chan, struct bt_bap_iso, chan); + + if (iso->rx.ep == NULL && iso->tx.ep == NULL) + { + LOG_ERR("iso %p not bound with ep", chan); + return; + } + + if (iso->rx.ep != NULL) + { + ascs_ep_iso_connected(iso->rx.ep); + } + + if (iso->tx.ep != NULL) + { + ascs_ep_iso_connected(iso->tx.ep); + } +} + +static void ascs_ep_iso_disconnected(struct bt_bap_ep *ep, uint8_t reason) +{ + struct bt_ascs_ase *ase = CONTAINER_OF(ep, struct bt_ascs_ase, ep); + const struct bt_bap_stream_ops *stream_ops; + struct bt_bap_stream *stream; + + stream = ep->stream; + if (stream == NULL) + { + LOG_ERR("No stream for ep %p", ep); + return; + } + + LOG_DBG("stream %p ep %p state %s reason 0x%02x", stream, stream->ep, + bt_bap_ep_state_str(ep->state), reason); + + stream_ops = stream->ops; + if (stream_ops != NULL && stream_ops->disconnected != NULL) + { + stream_ops->disconnected(stream, reason); + } + + /* Cancel ASE disconnect work if pending */ + + (void)k_work_cancel_delayable(&ase->disconnect_work); + ep->reason = reason; + + if (ep->state == BT_BAP_EP_STATE_RELEASING) + { + ascs_ep_set_state(ep, BT_BAP_EP_STATE_IDLE); + } + else if (ep->state == BT_BAP_EP_STATE_STREAMING || + ep->state == BT_BAP_EP_STATE_DISABLING) + { + /* ASCS_v1.0 3.2 ASE state machine transitions + * + * If the server detects link loss of a CIS for an ASE in the Streaming + * state or the Disabling state, the server shall immediately + * transition that ASE to the QoS Configured state. + */ + + ase->unexpected_iso_link_loss = true; + + ascs_ep_set_state(ep, BT_BAP_EP_STATE_QOS_CONFIGURED); + } + else + { + /* no op */ + } +} + +static void ascs_iso_disconnected(struct bt_iso_chan *chan, uint8_t reason) +{ + struct bt_bap_iso *iso = CONTAINER_OF(chan, struct bt_bap_iso, chan); + + if (iso->rx.ep == NULL && iso->tx.ep == NULL) + { + return; + } + + if (iso->rx.ep != NULL) + { + ascs_ep_iso_disconnected(iso->rx.ep, reason); + } + + if (iso->tx.ep != NULL) + { + ascs_ep_iso_disconnected(iso->tx.ep, reason); + } +} + +static struct bt_iso_chan_ops ascs_iso_ops = +{ +#if defined(CONFIG_BT_AUDIO_RX) + .recv = ascs_iso_recv, +#endif /* CONFIG_BT_AUDIO_RX */ +#if defined(CONFIG_BT_AUDIO_TX) + .sent = ascs_iso_sent, +#endif /* CONFIG_BT_AUDIO_TX */ + .connected = ascs_iso_connected, + .disconnected = ascs_iso_disconnected, +}; + +static void ascs_ase_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + LOG_DBG("attr %p value 0x%04x", attr, value); +} + +static struct net_buf_simple *ascs_cp_rsp_get(const struct bt_conn *conn) +{ + struct ascs_client *client = client_from_conn(conn); + + if (client != NULL) + { + return &client->cp_rsp_buf; + } + + return NULL; +} + +static void ascs_cp_rsp_init(struct net_buf_simple *buf, uint8_t op) +{ + struct bt_ascs_cp_rsp *rsp; + + net_buf_simple_reset(buf); + + rsp = net_buf_simple_add(buf, sizeof(*rsp)); + rsp->op = op; + rsp->num_ase = 0U; +} + +/* Add response to an opcode/ASE ID */ + +static void ascs_cp_rsp_add( + const struct bt_conn *conn, uint8_t ase_id, uint8_t code, uint8_t reason) +{ + struct net_buf_simple *buf = ascs_cp_rsp_get(conn); + + if (buf == NULL) + { + LOG_WRN("Failed to get cp_rsp_buf for conn %p with addr %s", conn, + bt_conn_dst_str(conn)); + return; + } + + struct bt_ascs_cp_rsp *rsp = (void *)buf->__buf; + struct bt_ascs_cp_ase_rsp *ase_rsp; + + LOG_DBG("id 0x%02x code %s (0x%02x) reason %s (0x%02x)", ase_id, + bt_ascs_rsp_str(code), code, + bt_ascs_reason_str(reason), reason); + + if (rsp->num_ase == BT_ASCS_UNSUPP_OR_LENGTH_ERR_NUM_ASE) + { + return; + } + + switch (code) + { + /* If the Response_Code value is 0x01 or 0x02, Number_of_ASEs shall be + * set to 0xFF. + */ + + case BT_BAP_ASCS_RSP_CODE_NOT_SUPPORTED: + case BT_BAP_ASCS_RSP_CODE_INVALID_LENGTH: + rsp->num_ase = BT_ASCS_UNSUPP_OR_LENGTH_ERR_NUM_ASE; + break; + default: + rsp->num_ase++; + break; + } + + ase_rsp = net_buf_simple_add(buf, sizeof(*ase_rsp)); + ase_rsp->ase_id = ase_id; + ase_rsp->code = code; + ase_rsp->reason = reason; +} + +static void ascs_cp_rsp_success(struct bt_ascs_ase *ase) +{ + ascs_cp_rsp_add(ase->conn, ASE_ID(ase), BT_BAP_ASCS_RSP_CODE_SUCCESS, + BT_BAP_ASCS_REASON_NONE); +} + +static int ase_release(struct bt_ascs_ase *ase, + uint8_t reason, struct bt_bap_ascs_rsp *rsp) +{ + enum bt_bap_ep_state state = ase->ep.state; + int err; + + if (state == BT_BAP_EP_STATE_IDLE || state == BT_BAP_EP_STATE_RELEASING) + { + LOG_WRN("Invalid operation in state: %s", bt_bap_ep_state_str(state)); + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE, + BT_BAP_ASCS_REASON_NONE); + return -EBADMSG; + } + + if (ase->has_pending_state) + { + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + LOG_DBG("Rejecting due to ASE %p having a pending state change", ase); + return -EBUSY; + } + + if (ascs_cb == NULL || ascs_cb->release == NULL) + { + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + return -ENOTSUP; + } + + err = ascs_cb->release(ase->ep.stream, rsp); + if (err != 0) + { + if (rsp->code == BT_BAP_ASCS_RSP_CODE_SUCCESS) + { + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + } + + LOG_ERR("Release failed: err %d, code %u, reason %u", err, rsp->code, + rsp->reason); + return err; + } + + /* Set reason in case this exits the streaming state */ + + ase->ep.reason = reason; + + ascs_ep_set_state(&ase->ep, BT_BAP_EP_STATE_RELEASING); + + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_SUCCESS, + BT_BAP_ASCS_REASON_NONE); + return 0; +} + +int bt_ascs_release_ase(struct bt_bap_ep *ep) +{ + if (!bt_ascs_has_ep(ep)) + { + LOG_DBG("ep %p not in ASCS", ep); + return -EINVAL; + } + + struct bt_ascs_ase *ase = CONTAINER_OF(ep, struct bt_ascs_ase, ep); + const enum bt_bap_ep_state state = ep->state; + + if (state == BT_BAP_EP_STATE_IDLE) + { + ase_free(ase); + return 0; + } + + return ase_release(ase, BT_HCI_ERR_LOCALHOST_TERM_CONN, + BT_BAP_ASCS_RSP_NULL); +} + +static int ase_disable(struct bt_ascs_ase *ase, + uint8_t reason, struct bt_bap_ascs_rsp *rsp) +{ + struct bt_bap_stream *stream; + struct bt_bap_ep *ep; + int err; + + LOG_DBG("ase %p", ase); + + ep = &ase->ep; + + switch (ep->state) + { + /* Valid only if ASE_State field = 0x03 (Enabling) */ + + case BT_BAP_EP_STATE_ENABLING: + + /* or 0x04 (Streaming) */ + + case BT_BAP_EP_STATE_STREAMING: + break; + default: + LOG_WRN("Invalid operation in state: %s", + bt_bap_ep_state_str(ep->state)); + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE, + BT_BAP_ASCS_REASON_NONE); + return -EBADMSG; + } + + if (ase->has_pending_state) + { + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + LOG_DBG("Rejecting due to ASE %p having a pending state change", ase); + return -EBUSY; + } + + stream = ep->stream; + + if (ascs_cb == NULL || ascs_cb->disable == NULL) + { + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + return -ENOTSUP; + } + + err = ascs_cb->disable(stream, rsp); + if (err != 0) + { + if (rsp->code == BT_BAP_ASCS_RSP_CODE_SUCCESS) + { + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + } + + LOG_ERR("Disable failed: err %d, code %u, reason %u", err, rsp->code, + rsp->reason); + return err; + } + + /* Set reason in case this exits the streaming state */ + + ep->reason = reason; + + /* The ASE state machine goes into different states from this operation + * based on whether it is a source or a sink ASE. + */ + + if (ep->dir == BT_AUDIO_DIR_SOURCE) + { + ascs_ep_set_state(ep, BT_BAP_EP_STATE_DISABLING); + } + else + { + ascs_ep_set_state(ep, BT_BAP_EP_STATE_QOS_CONFIGURED); + } + + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_SUCCESS, + BT_BAP_ASCS_REASON_NONE); + return 0; +} + +int bt_ascs_disable_ase(struct bt_bap_ep *ep) +{ + if (!bt_ascs_has_ep(ep)) + { + LOG_DBG("ep %p not in ASCS", ep); + return -EINVAL; + } + + struct bt_ascs_ase *ase = CONTAINER_OF(ep, struct bt_ascs_ase, ep); + + return ase_disable(ase, BT_HCI_ERR_LOCALHOST_TERM_CONN, + BT_BAP_ASCS_RSP_NULL); +} + +static void force_ase_release(struct bt_ascs_ase *ase, uint8_t reason) +{ + struct ascs_client *client; + struct k_work_sync sync; + + (void)k_work_cancel_delayable_sync(&ase->disconnect_work, &sync); + + if (ase->conn == NULL) + { + client = NULL; + } + else + { + client = client_from_conn(ase->conn); + + DEBUGASSERT(client != NULL); + (void)k_work_cancel_delayable_sync(&client->notify_work, &sync); + } + + /* Execute and pending state transitions to call the appropriate callbacks + */ + + if (ase->has_pending_state) + { + const enum bt_bap_ep_state old_state = ase->ep.state; + + ase->ep.state = ase->state_pending; + execute_post_state_transition(ase, old_state); + } + + /* If the ASE is not idle we do a local release */ + + if (ase->ep.state != BT_BAP_EP_STATE_IDLE) + { + const enum bt_bap_ep_state old_state = ase->ep.state; + + ase->ep.reason = reason; + ase->ep.state = BT_BAP_EP_STATE_RELEASING; + execute_post_state_transition(ase, old_state); + + /* Entering the releasing state may attempt to disconnect the CIS */ + + if (k_work_delayable_is_pending(&ase->disconnect_work)) + { + /* Force any disconnect work to complete */ + + (void)k_work_cancel_delayable_sync(&ase->disconnect_work, &sync); + ascs_disconnect_stream_work_handler(&ase->disconnect_work.work); + } + + /* Entering releasing state may trigger a work item to handle the + * BT_BAP_EP_STATE_RELEASING -> BT_BAP_EP_STATE_IDLE state, which we + * will just cancel as we force that transition below + */ + + if (client != NULL) + { + (void)k_work_cancel_delayable_sync(&client->notify_work, &sync); + } + } + + /* Finalize the releasing operation if not already completed */ + + if (ase->ep.state != BT_BAP_EP_STATE_IDLE) + { + const enum bt_bap_ep_state old_state = ase->ep.state; + + ase->ep.state = BT_BAP_EP_STATE_IDLE; + execute_post_state_transition(ase, old_state); + + /* At this point, `ase` object have been free'd */ + } + + DEBUGASSERT(!k_work_delayable_is_pending(&ase->disconnect_work)); + DEBUGASSERT(ase->ep.state == BT_BAP_EP_STATE_IDLE); + DEBUGASSERT(!ase->has_pending_state); +} + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + struct ascs_client *client; + struct k_work_sync sync; + + if (bt_conn_get_security(conn) < BT_SECURITY_L2 || + !bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + client = client_from_conn(conn); + DEBUGASSERT(client != NULL); + + (void)k_work_cancel_delayable_sync(&client->notify_work, &sync); + + for (size_t i = 0U; i < ARRAY_SIZE(ascs.ase_pool); i++) + { + struct bt_ascs_ase *ase = &ascs.ase_pool[i]; + + if (ase->conn != conn) + { + continue; + } + + /* Release the ASE, finalizing any pending state transitions */ + + force_ase_release(ase, reason); + } + + net_buf_simple_reset(&client->cp_rsp_buf); + client->rsp_pending = false; + bt_conn_drop(&client->conn); +} + +static void ascs_security_changed_cb( + struct bt_conn *conn, bt_security_t level, + enum bt_security_err security_err) +{ + struct ascs_client *client; + + if (security_err != 0 || level < BT_SECURITY_L2 || + !bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + client = client_from_conn(conn); + if (client == NULL) + { + __maybe_unused bool added = false; + + ARRAY_FOR_EACH_PTR(ascs.clients, new_client) + { + if (new_client->conn == NULL) + { + new_client->conn = bt_conn_ref(conn); + added = true; + break; + } + } + + DEBUGASSERT(added); + } + + /* else Already there - Can happen if security changes multiple times */ +} + +BT_CONN_CB_DEFINE(conn_cb) = +{ + .disconnected = disconnected, + .security_changed = ascs_security_changed_cb, +}; + +struct bap_iso_find_params +{ + struct bt_conn *acl; + uint8_t cig_id; + uint8_t cis_id; +}; + +static bool bap_iso_find_func(struct bt_bap_iso *iso, void *user_data) +{ + struct bap_iso_find_params *params = user_data; + const struct bt_bap_ep *ep; + + if (iso->rx.ep != NULL) + { + ep = iso->rx.ep; + } + else if (iso->tx.ep != NULL) + { + ep = iso->tx.ep; + } + else + { + return false; + } + + return ep->stream->conn == params->acl && ep->cig_id == params->cig_id && + ep->cis_id == params->cis_id; +} + +static struct bt_bap_iso *bap_iso_get_or_new(struct bt_conn *conn, + uint8_t cig_id, uint8_t cis_id) +{ + struct bt_bap_iso *iso; + struct bap_iso_find_params params = + { + .acl = bt_conn_ref(conn), + .cig_id = cig_id, + .cis_id = cis_id, + }; + + iso = bt_bap_iso_find(bap_iso_find_func, ¶ms); + bt_conn_unref(conn); + + if (iso) + { + return iso; + } + + iso = bt_bap_iso_new(); + if (!iso) + { + return NULL; + } + + bt_bap_iso_init(iso, &ascs_iso_ops); + + return iso; +} + +static uint8_t ase_attr_cb(const struct bt_gatt_attr *attr, + uint16_t handle, void *user_data) +{ + struct bt_ascs_ase *ase = user_data; + + ARG_UNUSED(handle); + + if (ASE_ID(ase) == POINTER_TO_UINT(BT_AUDIO_CHRC_USER_DATA(attr))) + { + ase->attr = attr; + + return BT_GATT_ITER_STOP; + } + + return BT_GATT_ITER_CONTINUE; +} + +void ascs_ep_init(struct bt_bap_ep *ep, uint8_t id) +{ + LOG_DBG("ep %p id 0x%02x", ep, id); + + (void)memset(ep, 0, sizeof(*ep)); + ep->id = id; + ep->dir = ASE_DIR(id); + ep->reason = BT_HCI_ERR_SUCCESS; +} + +static void ase_init(struct bt_ascs_ase *ase, + struct bt_conn *conn, uint8_t id) +{ + memset(ase, 0, sizeof(*ase)); + + ascs_ep_init(&ase->ep, id); + + ase->conn = bt_conn_ref(conn); + + /* Lookup ASE characteristic */ + + bt_gatt_foreach_attr_type(BT_ATT_FIRST_ATTRIBUTE_HANDLE, + BT_ATT_LAST_ATTRIBUTE_HANDLE, + ASE_UUID(id), NULL, 0U, ase_attr_cb, ase); + + DEBUGASSERT(ase->attr); + + k_work_init_delayable(&ase->disconnect_work, + ascs_disconnect_stream_work_handler); +} + +static struct bt_ascs_ase *ase_new(struct bt_conn *conn, uint8_t id) +{ + struct bt_ascs_ase *ase = NULL; + + DEBUGASSERT(id > 0U && id <= ASE_COUNT); + + for (size_t i = 0U; i < ARRAY_SIZE(ascs.ase_pool); i++) + { + if (ascs.ase_pool[i].conn == NULL) + { + ase = &ascs.ase_pool[i]; + break; + } + } + + if (ase == NULL) + { + return NULL; + } + + ase_init(ase, conn, id); + + LOG_DBG("conn %p new ase %p id 0x%02x", (void *)conn, ase, id); + + return ase; +} + +static struct bt_ascs_ase *ase_find(struct bt_conn *conn, uint8_t id) +{ + for (size_t i = 0U; i < ARRAY_SIZE(ascs.ase_pool); i++) + { + struct bt_ascs_ase *ase = &ascs.ase_pool[i]; + + if (ase->conn == conn && ASE_ID(ase) == id) + { + return ase; + } + } + + return NULL; +} + +bool bt_ascs_has_ep(const struct bt_bap_ep *ep) +{ + return PART_OF_ARRAY(ascs.ase_pool, ep); +} + +static ssize_t ascs_ase_read( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + uint8_t ase_id = POINTER_TO_UINT(BT_AUDIO_CHRC_USER_DATA(attr)); + struct bt_ascs_ase *ase = NULL; + ssize_t ret_val; + int err; + + LOG_DBG("conn %p attr %p buf %p len %u offset %u", (void *)conn, attr, buf, + len, offset); + + /* The callback can be used locally to read the ASE_ID in which case conn + * won't be set. + */ + + if (conn != NULL) + { + ase = ase_find(conn, ase_id); + } + + /* If NULL, we haven't assigned an ASE, this also means that we are + * currently in IDLE + */ + + if (ase == NULL) + { + return ascs_ase_read_status_idle(conn, attr, buf, len, offset); + } + + err = nxsem_tickwait(&ase_buf_sem, ASE_BUF_SEM_TIMEOUT); + if (err != 0) + { + LOG_DBG("Failed to take ase_buf_sem: %d", err); + + return BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES); + } + + ascs_ep_get_status(&ase->ep, &ase_buf); + + ret_val = bt_gatt_attr_read(conn, attr, buf, len, offset, ase_buf.data, + ase_buf.len); + + nxsem_post(&ase_buf_sem); + + return ret_val; +} + +static void ascs_cp_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + LOG_DBG("attr %p value 0x%04x", attr, value); +} + +static int ascs_ep_set_codec( + struct bt_bap_ep *ep, uint8_t target_latency, uint8_t target_phy, + uint8_t id, uint16_t cid, uint16_t vid, const uint8_t *cc, uint8_t len, + struct bt_bap_ascs_rsp *rsp) +{ + const struct bt_audio_codec_cap *codec_cap; + struct bt_audio_codec_cfg *codec_cfg; + const struct bt_pac_codec codec_id = + { + .id = id, + .cid = cid, + .vid = vid, + }; + + if (ep == NULL) + { + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_CONF_INVALID, + BT_BAP_ASCS_REASON_CODEC_DATA); + return -EINVAL; + } + + codec_cfg = &ep->codec_cfg; + + LOG_DBG("ep %p dir %s codec id 0x%02x cid 0x%04x vid 0x%04x len %u", ep, + bt_audio_dir_to_str(ep->dir), id, cid, vid, len); + + codec_cap = bt_pacs_get_codec_cap(ep->dir, &codec_id); + if (codec_cap == NULL) + { + LOG_DBG("Codec with id 0x%02x cid 0x%04x and vid 0x%04x for dir %s is " + "not " + "supported by our capabilities", + codec_id.id, codec_id.cid, codec_id.vid, bt_audio_dir_to_str( + ep->dir)); + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_CONF_INVALID, + BT_BAP_ASCS_REASON_CODEC); + return -ENOENT; + } + + codec_cfg->target_latency = target_latency; + codec_cfg->target_phy = target_phy; + codec_cfg->id = id; + codec_cfg->cid = cid; + codec_cfg->vid = vid; + codec_cfg->data_len = len; + (void)memcpy(codec_cfg->data, cc, len); + codec_cfg->path_id = codec_cap->path_id; + + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_SUCCESS, + BT_BAP_ASCS_REASON_NONE); + + return 0; +} + +static int ase_config(struct bt_ascs_ase *ase, + const struct bt_ascs_config *cfg) +{ + struct bt_bap_stream *stream = NULL; + struct bt_audio_codec_cfg codec_cfg; + struct bt_bap_ascs_rsp rsp = + BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_SUCCESS, + BT_BAP_ASCS_REASON_NONE); + int err; + + LOG_DBG("ase %p latency 0x%02x phy 0x%02x codec 0x%02x " + "cid 0x%04x vid 0x%04x codec config len 0x%02x", + ase, cfg->latency, cfg->phy, cfg->codec.id, + sys_le16_to_cpu(cfg->codec.cid), + sys_le16_to_cpu(cfg->codec.vid), cfg->cc_len); + + if (!IN_RANGE(cfg->latency, BT_ASCS_CONFIG_LATENCY_LOW, + BT_ASCS_CONFIG_LATENCY_HIGH)) + { + LOG_WRN("Invalid latency: 0x%02x", cfg->latency); + ascs_cp_rsp_add(ase->conn, ASE_ID(ase), + BT_BAP_ASCS_RSP_CODE_CONF_INVALID, + BT_BAP_ASCS_REASON_LATENCY); + return -EINVAL; + } + + if (!IN_RANGE(cfg->phy, BT_ASCS_CONFIG_PHY_LE_1M, + BT_ASCS_CONFIG_PHY_LE_CODED)) + { + LOG_WRN("Invalid PHY: 0x%02x", cfg->phy); + ascs_cp_rsp_add(ase->conn, ASE_ID(ase), + BT_BAP_ASCS_RSP_CODE_CONF_INVALID, + BT_BAP_ASCS_REASON_PHY); + return -EINVAL; + } + + if (cfg->cc_len > CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE) + { + LOG_DBG("Can not store %u codec configuration data", cfg->cc_len); + + return -ENOMEM; + } + + switch (ase->ep.state) + { + /* Valid only if ASE_State field = 0x00 (Idle) */ + + case BT_BAP_EP_STATE_IDLE: + + /* or 0x01 (Codec Configured) */ + + case BT_BAP_EP_STATE_CODEC_CONFIGURED: + + /* or 0x02 (QoS Configured) */ + + case BT_BAP_EP_STATE_QOS_CONFIGURED: + break; + default: + LOG_WRN("Invalid operation in state: %s", + bt_bap_ep_state_str(ase->ep.state)); + ascs_cp_rsp_add(ase->conn, ASE_ID(ase), + BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE, + BT_BAP_ASCS_REASON_NONE); + return -EINVAL; + } + + if (ase->has_pending_state) + { + ascs_cp_rsp_add(ase->conn, ASE_ID(ase), + BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + LOG_DBG("Rejecting due to ASE %p having a pending state change", ase); + return -EBUSY; + } + + /* Store current codec configuration to be able to restore it + * in case of error. + */ + + (void)memcpy(&codec_cfg, &ase->ep.codec_cfg, sizeof(codec_cfg)); + + err = ascs_ep_set_codec(&ase->ep, cfg->latency, cfg->phy, cfg->codec.id, + sys_le16_to_cpu(cfg->codec.cid), + sys_le16_to_cpu(cfg->codec.vid), + (const uint8_t *)cfg->cc, cfg->cc_len, &rsp); + if (err != 0) + { + ascs_app_rsp_warn_valid(&rsp); + (void)memcpy(&ase->ep.codec_cfg, &codec_cfg, sizeof(codec_cfg)); + ascs_cp_rsp_add(ase->conn, ASE_ID(ase), rsp.code, rsp.reason); + return err; + } + + if (ascs_cb == NULL) + { + err = -ENOTSUP; + rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + } + else if (ase->ep.stream != NULL) + { + stream = ase->ep.stream; + if (ascs_cb->reconfig != NULL) + { + err = ascs_cb->reconfig(ase->ep.stream, ase->ep.dir, + &ase->ep.codec_cfg, + &ase->ep.qos_pref, &rsp); + } + else + { + err = -ENOTSUP; + rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + } + + if (err == 0) + { + if (!bt_bap_valid_qos_pref(&ase->ep.qos_pref)) + { + LOG_DBG("Invalid QoS preferences"); + + /* If the upper layers provide an invalid QoS preferences we + * reject the request from the client, as it would not be able + * to parse the result as valid anyways + */ + + err = -EINVAL; + } + + ascs_app_rsp_warn_valid(&rsp); + } + } + else + { + stream = NULL; + if (ascs_cb->config != NULL) + { + err = ascs_cb->config(ase->conn, &ase->ep, ase->ep.dir, + &ase->ep.codec_cfg, &stream, + &ase->ep.qos_pref, &rsp); + } + else + { + err = -ENOTSUP; + rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + } + + if (err == 0 && stream == NULL) + { + LOG_DBG("Upper layer did not provide a stream object"); + err = -ENOMEM; + } + + if (err == 0 && stream != NULL) + { + if (!bt_bap_valid_qos_pref(&ase->ep.qos_pref)) + { + LOG_DBG("Invalid QoS preferences"); + + /* If the upper layers provide an invalid QoS preferences we + * reject the request from the client, as it would not be able + * to parse the result as valid anyways + */ + + err = -EINVAL; + } + + bt_bap_stream_init(stream); + } + } + + if (err != 0) + { + ascs_app_rsp_warn_valid(&rsp); + + if (rsp.code == BT_BAP_ASCS_RSP_CODE_SUCCESS) + { + rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + } + + LOG_DBG("Config failed: err %d, stream %p, code %u, reason %u", err, + stream, rsp.code, + rsp.reason); + + (void)memcpy(&ase->ep.codec_cfg, &codec_cfg, sizeof(codec_cfg)); + ascs_cp_rsp_add(ase->conn, ASE_ID(ase), rsp.code, rsp.reason); + + return err; + } + + ascs_cp_rsp_success(ase); + + bt_bap_stream_attach(ase->conn, stream, &ase->ep); + stream->codec_cfg = &ase->ep.codec_cfg; + + ascs_ep_set_state(&ase->ep, BT_BAP_EP_STATE_CODEC_CONFIGURED); + + return 0; +} + +static struct bt_bap_ep *ep_lookup_stream(struct bt_conn *conn, + struct bt_bap_stream *stream) +{ + for (size_t i = 0U; i < ARRAY_SIZE(ascs.ase_pool); i++) + { + struct bt_ascs_ase *ase = &ascs.ase_pool[i]; + + if (ase->conn == conn && ase->ep.stream == stream) + { + return &ase->ep; + } + } + + return NULL; +} + +int bt_ascs_config_ase(struct bt_conn *conn, struct bt_bap_stream *stream, + const struct bt_audio_codec_cfg *codec_cfg, + const struct bt_bap_qos_cfg_pref *qos_pref) +{ + const struct bt_audio_codec_cap *codec_cap; + struct bt_ascs_ase *ase = NULL; + struct bt_pac_codec codec_id; + struct bt_bap_ep *ep; + int err; + + if (conn == NULL + || stream == NULL + || codec_cfg == NULL + || qos_pref == NULL) + { + LOG_DBG("NULL value(s) supplied)"); + return -EINVAL; + } + + ep = ep_lookup_stream(conn, stream); + if (ep != NULL) + { + LOG_DBG("Stream already configured for conn %p", (void *)stream->conn); + return -EALREADY; + } + + /* Get a free ASE or NULL if all ASE instances are already in use */ + + for (int i = 1; i <= ASE_COUNT; i++) + { + if (ase_find(conn, i) == NULL) + { + ase = ase_new(conn, i); + break; + } + } + + if (ase == NULL) + { + LOG_WRN("No free ASE found."); + return -ENOTSUP; + } + + ep = &ase->ep; + + if (ep == NULL) + { + return -EINVAL; + } + + codec_id.id = codec_cfg->id; + codec_id.cid = codec_cfg->cid; + codec_id.vid = codec_cfg->vid; + + codec_cap = bt_pacs_get_codec_cap(ep->dir, &codec_id); + if (codec_cap == NULL) + { + LOG_DBG("Codec with id 0x%02x cid 0x%04x and vid 0x%04x for dir %s is " + "not " + "supported by our capabilities", + codec_id.id, codec_id.cid, codec_id.vid, bt_audio_dir_to_str( + ep->dir)); + return -ENOENT; + } + + (void)memcpy(&ep->codec_cfg, codec_cfg, sizeof(ep->codec_cfg)); + + ep->qos_pref = *qos_pref; + + bt_bap_stream_attach(conn, stream, ep); + + err = ascs_ep_set_state(ep, BT_BAP_EP_STATE_CODEC_CONFIGURED); + if (err != 0) + { + bt_bap_stream_detach(stream); + ase_free(ase); + return err; + } + + return 0; +} + +int bt_ascs_reconfig_ase(struct bt_bap_ep *ep, + const struct bt_audio_codec_cfg *codec_cfg) +{ + /* rsp is not used for other purposes than to provide a pointer to the + * callback + */ + + struct bt_bap_ascs_rsp rsp = + BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_SUCCESS, + BT_BAP_ASCS_REASON_NONE); + enum bt_bap_ep_state state; + int err; + + if (!bt_ascs_has_ep(ep)) + { + LOG_DBG("ep %p not in ASCS", ep); + return -EINVAL; + } + + state = ep->state; + switch (state) + { + /* Valid only if ASE_State field = 0x01 (Codec Configured) */ + + case BT_BAP_EP_STATE_CODEC_CONFIGURED: + + /* or 0x02 (QoS Configured) */ + + case BT_BAP_EP_STATE_QOS_CONFIGURED: + break; + default: + LOG_DBG("Invalid state: %s", bt_bap_ep_state_str(state)); + return -EBADMSG; + } + + if (ascs_cb != NULL && ascs_cb->reconfig != NULL) + { + err = ascs_cb->reconfig(ep->stream, ep->dir, codec_cfg, &ep->qos_pref, + &rsp); + } + else + { + err = -EOPNOTSUPP; + } + + if (err != 0) + { + return err; + } + + (void)memcpy(&ep->codec_cfg, codec_cfg, sizeof(*codec_cfg)); + + return ascs_ep_set_state(ep, BT_BAP_EP_STATE_CODEC_CONFIGURED); +} + +int bt_ascs_start_ase(struct bt_bap_ep *ep) +{ + /* rsp is not used for other purposes than to provide a pointer to the + * callback + */ + + struct bt_bap_ascs_rsp rsp = + BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_SUCCESS, + BT_BAP_ASCS_REASON_NONE); + int err; + + if (!bt_ascs_has_ep(ep)) + { + LOG_DBG("ep %p not in ASCS", ep); + + return -EINVAL; + } + + if (ep->dir != BT_AUDIO_DIR_SINK) + { + LOG_DBG("Invalid operation for ep %p with dir %u", ep, ep->dir); + + return -EINVAL; + } + + if (ep->state != BT_BAP_EP_STATE_ENABLING) + { + LOG_DBG("Invalid state: %s", bt_bap_ep_state_str(ep->state)); + + return -EBADMSG; + } + + if (ascs_cb != NULL && ascs_cb->start != NULL) + { + err = ascs_cb->start(ep->stream, &rsp); + } + else + { + err = -EOPNOTSUPP; + } + + if (err != 0) + { + return err; + } + + /* If ISO is connected to go streaming state, + * else wait for ISO to be connected + */ + + if (ep->iso->chan.state == BT_ISO_STATE_CONNECTED) + { + return ascs_ep_set_state(ep, BT_BAP_EP_STATE_STREAMING); + } + + ep->receiver_ready = true; + + return 0; +} + +int bt_ascs_metadata_ase(struct bt_bap_ep *ep, + const uint8_t meta[], size_t meta_len) +{ + /* rsp is not used for other purposes than to provide a pointer to the + * callback + */ + + struct bt_bap_ascs_rsp rsp = + BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_SUCCESS, + BT_BAP_ASCS_REASON_NONE); + enum bt_bap_ep_state state; + int err; + + if (!bt_ascs_has_ep(ep)) + { + LOG_DBG("ep %p not in ASCS", ep); + return -EINVAL; + } + + state = ep->state; + switch (state) + { + /* Valid for an ASE only if ASE_State field = 0x03 (Enabling) */ + + case BT_BAP_EP_STATE_ENABLING: + + /* or 0x04 (Streaming) */ + + case BT_BAP_EP_STATE_STREAMING: + break; + default: + LOG_DBG("Invalid state: %s", bt_bap_ep_state_str(state)); + return -EBADMSG; + } + + if (meta_len > sizeof(ep->codec_cfg.meta)) + { + return -ENOMEM; + } + + if (ascs_cb != NULL && ascs_cb->metadata != NULL) + { + err = ascs_cb->metadata(ep->stream, meta, meta_len, &rsp); + } + else + { + err = -EOPNOTSUPP; + } + + if (err != 0) + { + return err; + } + + (void)memcpy(ep->codec_cfg.meta, meta, meta_len); + + /* Set the state to the same state to trigger the notifications */ + + return ascs_ep_set_state(ep, ep->state); +} + +static uint16_t get_max_ase_rsp_for_conn(struct bt_conn *conn) +{ + const int max_ntf_size = bt_att_get_max_notify_size(conn, + BT_ATT_CHAN_OPT_NONE); + const size_t rsp_hdr_size = sizeof(struct bt_ascs_cp_rsp); + + if (max_ntf_size > 0 && (size_t)max_ntf_size > rsp_hdr_size) + { + return (max_ntf_size - + rsp_hdr_size) / sizeof(struct bt_ascs_cp_ase_rsp); + } + + return 0U; +} + +static bool is_valid_num_ases(struct bt_conn *conn, uint8_t num_ases) +{ + const uint16_t max_ase_rsp = get_max_ase_rsp_for_conn(conn); + + if (num_ases < 1U) + { + LOG_WRN("Number_of_ASEs parameter value is less than 1"); + return false; + } + else if (num_ases > ASE_COUNT) + { + /* If the request is for more ASEs than we have, we just reject the + * request + */ + + LOG_DBG("Number_of_ASEs parameter value (%u) is greater than %d", + num_ases, ASE_COUNT); + return false; + } + else if (num_ases > max_ase_rsp) + { + /* If the request is for more ASEs than we can respond to, we reject + * the request + */ + + LOG_DBG("Number_of_ASEs parameter value (%u) is greater than what we " + "can respond " + "to (%u) based on the MTU", + num_ases, max_ase_rsp); + return false; + } + + return true; +} + +static bool is_valid_config_len(struct bt_conn *conn, + struct net_buf_simple *buf) +{ + const struct bt_ascs_config_op *op; + struct net_buf_simple_state state; + + net_buf_simple_save(buf, &state); + + if (buf->len < sizeof(*op)) + { + LOG_WRN("Invalid length %u < %zu", buf->len, sizeof(*op)); + return false; + } + + op = net_buf_simple_pull_mem(buf, sizeof(*op)); + if (!is_valid_num_ases(conn, op->num_ases)) + { + return false; + } + + for (uint8_t i = 0U; i < op->num_ases; i++) + { + const struct bt_ascs_config *config; + + if (buf->len < sizeof(*config)) + { + LOG_WRN("Malformed params array"); + return false; + } + + config = net_buf_simple_pull_mem(buf, sizeof(*config)); + if (buf->len < config->cc_len) + { + LOG_WRN("Malformed codec specific config"); + return false; + } + + (void)net_buf_simple_pull_mem(buf, config->cc_len); + } + + if (buf->len > 0) + { + LOG_WRN("Unexpected data"); + return false; + } + + net_buf_simple_restore(buf, &state); + + return true; +} + +static ssize_t ascs_config(struct bt_conn *conn, struct net_buf_simple *buf) +{ + const struct bt_ascs_config_op *req; + const struct bt_ascs_config *cfg; + + if (!is_valid_config_len(conn, buf)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + req = net_buf_simple_pull_mem(buf, sizeof(*req)); + + LOG_DBG("num_ases %u", req->num_ases); + + for (uint8_t i = 0U; i < req->num_ases; i++) + { + struct bt_ascs_ase *ase; + int err; + + cfg = net_buf_simple_pull_mem(buf, sizeof(*cfg)); + (void)net_buf_simple_pull(buf, cfg->cc_len); + + LOG_DBG("ase_id 0x%02x cc_len %u", cfg->ase_id, cfg->cc_len); + + if (cfg->ase_id == 0U || cfg->ase_id > ASE_COUNT) + { + LOG_WRN("Invalid ASE ID: %u", cfg->ase_id); + ascs_cp_rsp_add(conn, cfg->ase_id, + BT_BAP_ASCS_RSP_CODE_INVALID_ASE, + BT_BAP_ASCS_REASON_NONE); + continue; + } + + ase = ase_find(conn, cfg->ase_id); + if (ase != NULL) + { + ase_config(ase, cfg); + continue; + } + + ase = ase_new(conn, cfg->ase_id); + if (!ase) + { + ascs_cp_rsp_add(conn, cfg->ase_id, BT_BAP_ASCS_RSP_CODE_NO_MEM, + BT_BAP_ASCS_REASON_NONE); + LOG_WRN("No free ASE found for config ASE ID 0x%02x", cfg->ase_id); + continue; + } + + err = ase_config(ase, cfg); + if (err != 0) + { + ase_free(ase); + } + } + + return buf->size; +} + +int bt_ascs_foreach_ep(struct bt_conn *conn, + bt_bap_ep_func_t func, void *user_data) +{ + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + return -EINVAL; + } + + if (func == NULL) + { + LOG_DBG("func is NULL"); + return -EINVAL; + } + + for (size_t i = 0U; i < ARRAY_SIZE(ascs.ase_pool); i++) + { + struct bt_ascs_ase *ase = &ascs.ase_pool[i]; + + if (ase->conn == conn) + { + if (!func(&ase->ep, user_data)) + { + return -ECANCELED; + } + } + } + + return 0; +} + +static void ase_qos(struct bt_ascs_ase *ase, uint8_t cig_id, uint8_t cis_id, + struct bt_bap_qos_cfg *qos, struct bt_bap_ascs_rsp *rsp) +{ + struct bt_bap_ep *ep = &ase->ep; + struct bt_bap_stream *stream; + + LOG_DBG("ase %p cig 0x%02x cis 0x%02x interval %u framing 0x%02x phy " + "0x%02x sdu %u rtn %u " + "latency %u pd %u", + ase, cig_id, cis_id, qos->interval, qos->framing, qos->phy, + qos->sdu, qos->rtn, + qos->latency, qos->pd); + + switch (ep->state) + { + /* Valid only if ASE_State field = 0x01 (Codec Configured) */ + + case BT_BAP_EP_STATE_CODEC_CONFIGURED: + + /* or 0x02 (QoS Configured) */ + + case BT_BAP_EP_STATE_QOS_CONFIGURED: + break; + default: + LOG_WRN("Invalid operation in state: %s", + bt_bap_ep_state_str(ep->state)); + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE, + BT_BAP_ASCS_REASON_NONE); + return; + } + + if (ase->has_pending_state) + { + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + LOG_DBG("Rejecting due to ASE %p having a pending state change", ase); + return; + } + + stream = ep->stream; + if (stream == NULL) + { + LOG_ERR("NULL stream"); + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + return; + } + + if (stream->ep == NULL) + { + LOG_ERR("NULL stream->ep"); + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + return; + } + + rsp->reason = bt_audio_verify_qos(qos); + if (rsp->reason != BT_BAP_ASCS_REASON_NONE) + { + rsp->code = BT_BAP_ASCS_RSP_CODE_CONF_INVALID; + return; + } + + rsp->reason = bt_bap_stream_verify_qos(stream, qos); + if (rsp->reason != BT_BAP_ASCS_REASON_NONE) + { + rsp->code = BT_BAP_ASCS_RSP_CODE_CONF_INVALID; + return; + } + + if (ascs_cb != NULL && ascs_cb->qos != NULL) + { + const int err = ascs_cb->qos(stream, qos, rsp); + + if (err != 0) + { + if (rsp->code == BT_BAP_ASCS_RSP_CODE_SUCCESS) + { + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + } + + LOG_DBG("Application returned error: err %d status %u reason %u", + err, rsp->code, + rsp->reason); + return; + } + } + + /* QoS->QoS transition. Unbind ISO if CIG/CIS changed. */ + + if (ep->iso != NULL && (ep->cig_id != cig_id || ep->cis_id != cis_id)) + { + bt_bap_iso_unbind_ep(ep->iso, ep); + stream->iso = NULL; + } + + if (ep->iso == NULL) + { + struct bt_bap_iso *iso; + + iso = bap_iso_get_or_new(ase->conn, cig_id, cis_id); + if (iso == NULL) + { + LOG_ERR("Could not allocate bap_iso"); + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_NO_MEM, + BT_BAP_ASCS_REASON_NONE); + return; + } + + if (bt_bap_iso_get_ep(false, iso, ep->dir) != NULL) + { + LOG_ERR("iso %p already in use in dir %s", &iso->chan, + bt_audio_dir_to_str(ep->dir)); + bt_bap_iso_unref(iso); + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_CONF_INVALID, + BT_BAP_ASCS_REASON_CIS); + return; + } + + bt_bap_iso_bind_ep(iso, ep); + stream->iso = &iso->chan; + bt_bap_iso_unref(iso); + } + + /* Store the QoS once accepted */ + + ep->qos = *qos; + stream->qos = &ep->qos; + + ep->cig_id = cig_id; + ep->cis_id = cis_id; + + ascs_ep_set_state(ep, BT_BAP_EP_STATE_QOS_CONFIGURED); + + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_SUCCESS, + BT_BAP_ASCS_REASON_NONE); +} + +static bool is_valid_qos_len(struct bt_conn *conn, + struct net_buf_simple *buf) +{ + const struct bt_ascs_qos_op *op; + struct net_buf_simple_state state; + size_t params_size; + + net_buf_simple_save(buf, &state); + + if (buf->len < sizeof(*op)) + { + LOG_WRN("Invalid length %u < %zu", buf->len, sizeof(*op)); + return false; + } + + op = net_buf_simple_pull_mem(buf, sizeof(*op)); + if (!is_valid_num_ases(conn, op->num_ases)) + { + return false; + } + + params_size = sizeof(struct bt_ascs_qos) * op->num_ases; + if (buf->len < params_size) + { + LOG_WRN("Malformed params array"); + return false; + } + + (void)net_buf_simple_pull_mem(buf, params_size); + + if (buf->len > 0) + { + LOG_WRN("Unexpected data"); + return false; + } + + net_buf_simple_restore(buf, &state); + + return true; +} + +static ssize_t ascs_qos(struct bt_conn *conn, struct net_buf_simple *buf) +{ + const struct bt_ascs_qos_op *req; + + if (!is_valid_qos_len(conn, buf)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + req = net_buf_simple_pull_mem(buf, sizeof(*req)); + + LOG_DBG("num_ases %u", req->num_ases); + + for (uint8_t i = 0U; i < req->num_ases; i++) + { + struct bt_bap_ascs_rsp rsp = + BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + struct bt_bap_qos_cfg cqos; + const struct bt_ascs_qos *qos; + struct bt_ascs_ase *ase; + + qos = net_buf_simple_pull_mem(buf, sizeof(*qos)); + + LOG_DBG("ase_id 0x%02x", qos->ase_id); + + if (!is_valid_ase_id(qos->ase_id)) + { + ascs_cp_rsp_add(conn, qos->ase_id, + BT_BAP_ASCS_RSP_CODE_INVALID_ASE, + BT_BAP_ASCS_REASON_NONE); + LOG_WRN("Unknown ase_id 0x%02x", qos->ase_id); + continue; + } + + ase = ase_find(conn, qos->ase_id); + if (!ase) + { + LOG_DBG("Invalid operation for idle ASE"); + ascs_cp_rsp_add(conn, qos->ase_id, + BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE, + BT_BAP_ASCS_REASON_NONE); + continue; + } + + cqos.interval = sys_get_le24(qos->interval); + cqos.framing = qos->framing; + cqos.phy = qos->phy; + cqos.sdu = sys_le16_to_cpu(qos->sdu); + cqos.rtn = qos->rtn; + cqos.latency = sys_le16_to_cpu(qos->latency); + cqos.pd = sys_get_le24(qos->pd); + + ase_qos(ase, qos->cig, qos->cis, &cqos, &rsp); + ascs_cp_rsp_add(conn, qos->ase_id, rsp.code, rsp.reason); + } + + return buf->size; +} + +struct ascs_parse_result +{ + int err; + struct bt_conn *conn; + struct bt_bap_ascs_rsp *rsp; + const struct bt_bap_ep *ep; +}; + +static bool is_context_available(struct bt_conn *conn, + enum bt_audio_dir dir, uint16_t context) +{ + return (context & bt_pacs_get_available_contexts_for_conn(conn, + dir)) == context; +} + +static bool ascs_parse_metadata(struct bt_data *data, void *user_data) +{ + struct ascs_parse_result *result = user_data; + const uint8_t data_len = data->data_len; + const uint8_t data_type = data->type; + const uint8_t *data_value = data->data; + + LOG_DBG("type 0x%02x len %u", data_type, data_len); + + if (!BT_AUDIO_METADATA_TYPE_IS_KNOWN(data_type)) + { + LOG_WRN("Unknown metadata type 0x%02x", data_type); + return true; + } + + switch (data_type) + { + /* TODO: Consider rejecting BT_AUDIO_METADATA_TYPE_PREF_CONTEXT type */ + + case BT_AUDIO_METADATA_TYPE_PREF_CONTEXT: + case BT_AUDIO_METADATA_TYPE_STREAM_CONTEXT: + { + uint16_t context; + + if (data_len != sizeof(context)) + { + *result->rsp = + BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_METADATA_INVALID, + data_type); + result->err = -EBADMSG; + return false; + } + + context = sys_get_le16(data_value); + if (context == BT_AUDIO_CONTEXT_TYPE_NONE) + { + *result->rsp = + BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_METADATA_INVALID, + data_type); + result->err = -EINVAL; + return false; + } + + /* Reject unsupported CAP Acceptor stream contexts. */ + + if (IS_ENABLED(CONFIG_BT_CAP_ACCEPTOR) && + data_type == BT_AUDIO_METADATA_TYPE_STREAM_CONTEXT && + !is_context_available(result->conn, result->ep->dir, context)) + { + LOG_WRN("Context 0x%04x is unavailable", context); + *result->rsp = + BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_METADATA_REJECTED, + data_type); + result->err = -EACCES; + return false; + } + + break; + } + + case BT_AUDIO_METADATA_TYPE_LANG: + if (data_len != BT_AUDIO_LANG_SIZE) + { + *result->rsp = + BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_METADATA_INVALID, + data_type); + result->err = -EBADMSG; + return false; + } + + break; + case BT_AUDIO_METADATA_TYPE_CCID_LIST: + { + /* Verify that the CCID is a known CCID on the writing device */ + + if (IS_ENABLED(CONFIG_BT_CAP_ACCEPTOR) && + !bt_cap_acceptor_ccids_exist(result->ep->stream->conn, + data_value, data_len)) + { + LOG_WRN("Metadata contain unknown CCID(s)"); + } + + break; + } + + case BT_AUDIO_METADATA_TYPE_PARENTAL_RATING: + if (data_len != 1U) + { + *result->rsp = + BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_METADATA_INVALID, + data_type); + result->err = -EBADMSG; + return false; + } + + break; + case BT_AUDIO_METADATA_TYPE_AUDIO_STATE: + { + uint8_t state; + + if (data_len != sizeof(state)) + { + *result->rsp = + BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_METADATA_INVALID, + data_type); + result->err = -EBADMSG; + return false; + } + + break; + } + + /* TODO: Consider rejecting BROADCAST_IMMEDIATE metadata. */ + + case BT_AUDIO_METADATA_TYPE_BROADCAST_IMMEDIATE: + if (data_len != 0U) + { + *result->rsp = + BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_METADATA_INVALID, + data_type); + result->err = -EBADMSG; + return false; + } + + break; + default: + break; + } + + return true; +} + +static int ascs_verify_metadata( + struct bt_bap_ep *ep, const struct bt_ascs_metadata *meta, + struct bt_bap_ascs_rsp *rsp) +{ + struct bt_ascs_ase *ase = CONTAINER_OF(ep, struct bt_ascs_ase, ep); + struct ascs_parse_result result = + { + .conn = ase->conn, + .rsp = rsp, + .err = 0, + .ep = ep, + }; + + int err; + + if (meta->len > CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE) + { + LOG_WRN("Not enough space for Codec Config Metadata: %u > %d", + meta->len, + CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE); + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_NO_MEM, + BT_BAP_ASCS_REASON_NONE); + + return -ENOMEM; + } + + /* Parse LTV entries */ + + err = bt_audio_data_parse(meta->data, meta->len, ascs_parse_metadata, + &result); + if (err != 0 && err != -ECANCELED) + { + /* ECANCELED is called if the callback stops the parsing prematurely, + * in which case result.err will be set + */ + + LOG_DBG("Failed to parse metadata: %d", err); + *rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_METADATA_INVALID, + BT_BAP_ASCS_REASON_NONE); + + return err; + } + + return result.err; +} + +static void ase_metadata(struct bt_ascs_ase *ase, + struct bt_ascs_metadata *meta) +{ + struct bt_bap_stream *stream; + struct bt_bap_ep *ep; + struct bt_bap_ascs_rsp rsp = + BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_SUCCESS, + BT_BAP_ASCS_REASON_NONE); + enum bt_bap_ep_state state; + int err; + + LOG_DBG("ase %p meta->len %u", ase, meta->len); + + ep = &ase->ep; + state = ep->state; + + switch (state) + { + /* Valid for an ASE only if ASE_State field = 0x03 (Enabling) */ + + case BT_BAP_EP_STATE_ENABLING: + + /* or 0x04 (Streaming) */ + + case BT_BAP_EP_STATE_STREAMING: + break; + default: + LOG_WRN("Invalid operation in state: %s", + bt_bap_ep_state_str(state)); + ascs_cp_rsp_add(ase->conn, ASE_ID(ase), + BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE, + BT_BAP_ASCS_REASON_NONE); + return; + } + + if (ase->has_pending_state) + { + ascs_cp_rsp_add(ase->conn, ASE_ID(ase), + BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + LOG_DBG("Rejecting due to ASE %p having a pending state change", ase); + return; + } + + stream = ep->stream; + + err = ascs_verify_metadata(ep, meta, &rsp); + if (err != 0) + { + LOG_DBG("Invalid metadata from client: %d", err); + + /* rsp will be set by ascs_verify_metadata */ + } + else if (ascs_cb != NULL && ascs_cb->metadata != NULL) + { + err = ascs_cb->metadata(stream, meta->data, meta->len, &rsp); + } + else + { + err = -ENOTSUP; + rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + } + + if (err != 0) + { + ascs_app_rsp_warn_valid(&rsp); + + if (rsp.code == BT_BAP_ASCS_RSP_CODE_SUCCESS) + { + rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + } + + LOG_ERR("Metadata failed: err %d, code %u, reason %u", err, rsp.code, + rsp.reason); + ascs_cp_rsp_add(ase->conn, ASE_ID(ase), rsp.code, rsp.reason); + return; + } + + ep->codec_cfg.meta_len = meta->len; + (void)memcpy(ep->codec_cfg.meta, meta->data, meta->len); + + /* Set the state to the same state to trigger the notifications */ + + ascs_ep_set_state(ep, ep->state); + ascs_cp_rsp_success(ase); +} + +static int ase_enable(struct bt_ascs_ase *ase, struct bt_ascs_metadata *meta) +{ + struct bt_bap_stream *stream; + struct bt_bap_ep *ep; + struct bt_bap_ascs_rsp rsp = + BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_SUCCESS, + BT_BAP_ASCS_REASON_NONE); + int err; + + LOG_DBG("ase %p meta->len %u", ase, meta->len); + + ep = &ase->ep; + + /* Valid for an ASE only if ASE_State field = 0x02 (QoS Configured) */ + + if (ep->state != BT_BAP_EP_STATE_QOS_CONFIGURED) + { + err = -EBADMSG; + LOG_WRN("Invalid operation in state: %s", + bt_bap_ep_state_str(ep->state)); + ascs_cp_rsp_add(ase->conn, ASE_ID(ase), + BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE, + BT_BAP_ASCS_REASON_NONE); + return err; + } + + if (ase->has_pending_state) + { + ascs_cp_rsp_add(ase->conn, ASE_ID(ase), + BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + LOG_DBG("Rejecting due to ASE %p having a pending state change", ase); + return -EBUSY; + } + + stream = ep->stream; + + err = ascs_verify_metadata(ep, meta, &rsp); + if (err != 0) + { + LOG_DBG("Invalid metadata from client: %d", err); + + /* rsp will be set by ascs_verify_metadata */ + } + else if (ascs_cb != NULL && ascs_cb->enable != NULL) + { + err = ascs_cb->enable(stream, meta->data, meta->len, &rsp); + } + else + { + err = -ENOTSUP; + rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + } + + if (err != 0) + { + ascs_app_rsp_warn_valid(&rsp); + + if (rsp.code == BT_BAP_ASCS_RSP_CODE_SUCCESS) + { + rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + } + + LOG_ERR("Enable rejected: err %d, code %u, reason %u", err, rsp.code, + rsp.reason); + ascs_cp_rsp_add(ase->conn, ASE_ID(ase), rsp.code, rsp.reason); + + return -EFAULT; + } + + ep->codec_cfg.meta_len = meta->len; + (void)memcpy(ep->codec_cfg.meta, meta->data, meta->len); + + ascs_ep_set_state(ep, BT_BAP_EP_STATE_ENABLING); + + ascs_cp_rsp_success(ase); + + return 0; +} + +static bool is_valid_enable_len(struct bt_conn *conn, + struct net_buf_simple *buf) +{ + const struct bt_ascs_enable_op *op; + struct net_buf_simple_state state; + + net_buf_simple_save(buf, &state); + + if (buf->len < sizeof(*op)) + { + LOG_WRN("Invalid length %u < %zu", buf->len, sizeof(*op)); + return false; + } + + op = net_buf_simple_pull_mem(buf, sizeof(*op)); + if (!is_valid_num_ases(conn, op->num_ases)) + { + return false; + } + + for (uint8_t i = 0U; i < op->num_ases; i++) + { + const struct bt_ascs_metadata *metadata; + + if (buf->len < sizeof(*metadata)) + { + LOG_WRN("Malformed params array"); + return false; + } + + metadata = net_buf_simple_pull_mem(buf, sizeof(*metadata)); + if (buf->len < metadata->len) + { + LOG_WRN("Malformed metadata"); + return false; + } + + (void)net_buf_simple_pull_mem(buf, metadata->len); + } + + if (buf->len > 0) + { + LOG_WRN("Unexpected data"); + return false; + } + + net_buf_simple_restore(buf, &state); + + return true; +} + +static ssize_t ascs_enable(struct bt_conn *conn, struct net_buf_simple *buf) +{ + const struct bt_ascs_enable_op *req; + struct bt_ascs_metadata *meta; + + if (!is_valid_enable_len(conn, buf)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + req = net_buf_simple_pull_mem(buf, sizeof(*req)); + + LOG_DBG("num_ases %u", req->num_ases); + + for (uint8_t i = 0U; i < req->num_ases; i++) + { + struct bt_ascs_ase *ase; + + meta = net_buf_simple_pull_mem(buf, sizeof(*meta)); + (void)net_buf_simple_pull(buf, meta->len); + + if (!is_valid_ase_id(meta->ase_id)) + { + ascs_cp_rsp_add(conn, meta->ase_id, + BT_BAP_ASCS_RSP_CODE_INVALID_ASE, + BT_BAP_ASCS_REASON_NONE); + LOG_WRN("Unknown ase_id 0x%02x", meta->ase_id); + continue; + } + + ase = ase_find(conn, meta->ase_id); + if (!ase) + { + LOG_DBG("Invalid operation for idle ase_id 0x%02x", meta->ase_id); + ascs_cp_rsp_add(conn, meta->ase_id, + BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE, + BT_BAP_ASCS_REASON_NONE); + continue; + } + + ase_enable(ase, meta); + } + + return buf->size; +} + +static void ase_start(struct bt_ascs_ase *ase) +{ + struct bt_bap_ep *ep; + struct bt_bap_ascs_rsp rsp = + BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_SUCCESS, + BT_BAP_ASCS_REASON_NONE); + int err; + + LOG_DBG("ase %p", ase); + + ep = &ase->ep; + + /* Valid for an ASE only if ASE_State field = 0x02 (QoS Configured) */ + + if (ep->state != BT_BAP_EP_STATE_ENABLING) + { + LOG_WRN("Invalid operation in state: %s", + bt_bap_ep_state_str(ep->state)); + ascs_cp_rsp_add(ase->conn, ASE_ID(ase), + BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE, + BT_BAP_ASCS_REASON_NONE); + return; + } + + if (ase->has_pending_state) + { + ascs_cp_rsp_add(ase->conn, ASE_ID(ase), + BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + LOG_DBG("Rejecting due to ASE %p having a pending state change", ase); + return; + } + + if (ep->iso->chan.state != BT_ISO_STATE_CONNECTED) + { + /* An ASE may not go into the streaming state unless the CIS + * is connected + */ + + LOG_WRN("Start failed: CIS not connected: %u", ep->iso->chan.state); + ascs_cp_rsp_add(ase->conn, ASE_ID(ase), + BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE, + BT_BAP_ASCS_REASON_NONE); + return; + } + + if (ascs_cb != NULL && ascs_cb->start != NULL) + { + err = ascs_cb->start(ep->stream, &rsp); + } + else + { + err = -ENOTSUP; + rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + } + + if (err != 0) + { + ascs_app_rsp_warn_valid(&rsp); + + if (rsp.code == BT_BAP_ASCS_RSP_CODE_SUCCESS) + { + rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + } + + LOG_ERR("Start failed: err %d, code %u, reason %u", err, rsp.code, + rsp.reason); + ascs_cp_rsp_add(ase->conn, ASE_ID(ase), rsp.code, rsp.reason); + + return; + } + + ep->receiver_ready = true; + + ascs_ep_set_state(ep, BT_BAP_EP_STATE_STREAMING); + + ascs_cp_rsp_success(ase); +} + +static bool is_valid_start_len(struct bt_conn *conn, + struct net_buf_simple *buf) +{ + const struct bt_ascs_start_op *op; + struct net_buf_simple_state state; + + net_buf_simple_save(buf, &state); + + if (buf->len < sizeof(*op)) + { + LOG_WRN("Invalid length %u < %zu", buf->len, sizeof(*op)); + return false; + } + + op = net_buf_simple_pull_mem(buf, sizeof(*op)); + if (!is_valid_num_ases(conn, op->num_ases)) + { + return false; + } + + if (buf->len != op->num_ases) + { + LOG_WRN("Number_of_ASEs mismatch"); + return false; + } + + net_buf_simple_restore(buf, &state); + + return true; +} + +static ssize_t ascs_start(struct bt_conn *conn, struct net_buf_simple *buf) +{ + const struct bt_ascs_start_op *req; + + if (!is_valid_start_len(conn, buf)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + req = net_buf_simple_pull_mem(buf, sizeof(*req)); + + LOG_DBG("num_ases %u", req->num_ases); + + for (uint8_t i = 0U; i < req->num_ases; i++) + { + struct bt_ascs_ase *ase; + uint8_t id; + + id = net_buf_simple_pull_u8(buf); + + LOG_DBG("ase_id 0x%02x", id); + + if (!is_valid_ase_id(id)) + { + ascs_cp_rsp_add(conn, id, BT_BAP_ASCS_RSP_CODE_INVALID_ASE, + BT_BAP_ASCS_REASON_NONE); + LOG_WRN("Unknown ase_id 0x%02x", id); + continue; + } + + /* If the ASE_ID written by the client represents a Sink ASE, the + * server shall not accept the Receiver Start Ready operation for that + * ASE. The server shall send a notification of the ASE Control Point + * characteristic to the client, and the server shall set the + * Response_Code value for that ASE to 0x05 (Invalid ASE direction). + */ + + if (ASE_DIR(id) == BT_AUDIO_DIR_SINK) + { + LOG_WRN("Start failed: invalid operation for Sink"); + ascs_cp_rsp_add(conn, id, BT_BAP_ASCS_RSP_CODE_INVALID_DIR, + BT_BAP_ASCS_REASON_NONE); + continue; + } + + ase = ase_find(conn, id); + if (!ase) + { + LOG_DBG("Invalid operation for idle ASE"); + ascs_cp_rsp_add(conn, id, BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE, + BT_BAP_ASCS_REASON_NONE); + continue; + } + + ase_start(ase); + } + + return buf->size; +} + +static bool is_valid_disable_len(struct bt_conn *conn, + struct net_buf_simple *buf) +{ + const struct bt_ascs_disable_op *op; + struct net_buf_simple_state state; + + net_buf_simple_save(buf, &state); + + if (buf->len < sizeof(*op)) + { + LOG_WRN("Invalid length %u < %zu", buf->len, sizeof(*op)); + return false; + } + + op = net_buf_simple_pull_mem(buf, sizeof(*op)); + if (!is_valid_num_ases(conn, op->num_ases)) + { + return false; + } + + if (buf->len != op->num_ases) + { + LOG_WRN("Number_of_ASEs mismatch"); + return false; + } + + net_buf_simple_restore(buf, &state); + + return true; +} + +static ssize_t ascs_disable(struct bt_conn *conn, struct net_buf_simple *buf) +{ + const struct bt_ascs_disable_op *req; + + if (!is_valid_disable_len(conn, buf)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + req = net_buf_simple_pull_mem(buf, sizeof(*req)); + + LOG_DBG("num_ases %u", req->num_ases); + + for (uint8_t i = 0U; i < req->num_ases; i++) + { + struct bt_bap_ascs_rsp rsp = + BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + struct bt_ascs_ase *ase; + uint8_t id; + + id = net_buf_simple_pull_u8(buf); + + LOG_DBG("ase_id 0x%02x", id); + + if (!is_valid_ase_id(id)) + { + ascs_cp_rsp_add(conn, id, BT_BAP_ASCS_RSP_CODE_INVALID_ASE, + BT_BAP_ASCS_REASON_NONE); + LOG_WRN("Unknown ase_id 0x%02x", id); + continue; + } + + ase = ase_find(conn, id); + if (!ase) + { + LOG_DBG("Invalid operation for idle ASE"); + ascs_cp_rsp_add(conn, id, BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE, + BT_BAP_ASCS_REASON_NONE); + continue; + } + + ase_disable(ase, BT_HCI_ERR_REMOTE_USER_TERM_CONN, &rsp); + ascs_cp_rsp_add(conn, id, rsp.code, rsp.reason); + } + + return buf->size; +} + +static void ase_stop(struct bt_ascs_ase *ase) +{ + struct bt_bap_stream *stream; + struct bt_bap_ep *ep; + struct bt_bap_ascs_rsp rsp = + BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_SUCCESS, + BT_BAP_ASCS_REASON_NONE); + int err; + + LOG_DBG("ase %p", ase); + + ep = &ase->ep; + + if (ep->state != BT_BAP_EP_STATE_DISABLING) + { + LOG_WRN("Invalid operation in state: %s", + bt_bap_ep_state_str(ep->state)); + ascs_cp_rsp_add(ase->conn, ASE_ID(ase), + BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE, + BT_BAP_ASCS_REASON_NONE); + return; + } + + if (ase->has_pending_state) + { + ascs_cp_rsp_add(ase->conn, ASE_ID(ase), + BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + LOG_DBG("Rejecting due to ASE %p having a pending state change", ase); + return; + } + + stream = ep->stream; + if (ascs_cb != NULL && ascs_cb->stop != NULL) + { + err = ascs_cb->stop(stream, &rsp); + } + else + { + err = -ENOTSUP; + rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + } + + if (err != 0) + { + ascs_app_rsp_warn_valid(&rsp); + + if (rsp.code == BT_BAP_ASCS_RSP_CODE_SUCCESS) + { + rsp = BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + } + + LOG_ERR("Stop failed: err %d, code %u, reason %u", err, rsp.code, + rsp.reason); + ascs_cp_rsp_add(ase->conn, ASE_ID(ase), rsp.code, rsp.reason); + return; + } + + /* If the Receiver Stop Ready operation has completed successfully the + * Unicast Client or the Unicast Server may terminate a CIS established for + * that ASE by following the Connected Isochronous Stream Terminate + * procedure defined in Volume 3, Part C, Section 9.3.15. + */ + + if (bt_bap_stream_can_disconnect(stream)) + { + err = ascs_disconnect_stream(stream); + if (err < 0) + { + LOG_ERR("Failed to disconnect stream %p: %d", stream, err); + } + } + + ascs_ep_set_state(ep, BT_BAP_EP_STATE_QOS_CONFIGURED); + + ascs_cp_rsp_success(ase); +} + +static bool is_valid_stop_len(struct bt_conn *conn, + struct net_buf_simple *buf) +{ + const struct bt_ascs_stop_op *op; + struct net_buf_simple_state state; + + net_buf_simple_save(buf, &state); + + if (buf->len < sizeof(*op)) + { + LOG_WRN("Invalid length %u < %zu", buf->len, sizeof(*op)); + return false; + } + + op = net_buf_simple_pull_mem(buf, sizeof(*op)); + if (!is_valid_num_ases(conn, op->num_ases)) + { + return false; + } + + if (buf->len != op->num_ases) + { + LOG_WRN("Number_of_ASEs mismatch"); + return false; + } + + net_buf_simple_restore(buf, &state); + + return true; +} + +static ssize_t ascs_stop(struct bt_conn *conn, struct net_buf_simple *buf) +{ + const struct bt_ascs_start_op *req; + + if (!is_valid_stop_len(conn, buf)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + req = net_buf_simple_pull_mem(buf, sizeof(*req)); + + LOG_DBG("num_ases %u", req->num_ases); + + for (uint8_t i = 0U; i < req->num_ases; i++) + { + struct bt_ascs_ase *ase; + uint8_t id; + + id = net_buf_simple_pull_u8(buf); + + LOG_DBG("ase_id 0x%02x", id); + + if (!is_valid_ase_id(id)) + { + ascs_cp_rsp_add(conn, id, BT_BAP_ASCS_RSP_CODE_INVALID_ASE, + BT_BAP_ASCS_REASON_NONE); + LOG_WRN("Unknown ase_id 0x%02x", id); + continue; + } + + /* If the ASE_ID written by the client represents a Sink ASE, the + * server shall not accept the Receiver Stop Ready operation for that + * ASE. The server shall send a notification of the ASE Control Point + * characteristic to the client, and the server shall set the + * Response_Code value for that ASE to 0x05 (Invalid ASE direction). + */ + + if (ASE_DIR(id) == BT_AUDIO_DIR_SINK) + { + LOG_WRN("Stop failed: invalid operation for Sink"); + ascs_cp_rsp_add(conn, id, BT_BAP_ASCS_RSP_CODE_INVALID_DIR, + BT_BAP_ASCS_REASON_NONE); + continue; + } + + ase = ase_find(conn, id); + if (!ase) + { + LOG_DBG("Invalid operation for idle ASE"); + ascs_cp_rsp_add(conn, id, BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE, + BT_BAP_ASCS_REASON_NONE); + continue; + } + + ase_stop(ase); + } + + return buf->size; +} + +static bool is_valid_metadata_len(struct bt_conn *conn, + struct net_buf_simple *buf) +{ + const struct bt_ascs_metadata_op *op; + struct net_buf_simple_state state; + + net_buf_simple_save(buf, &state); + + if (buf->len < sizeof(*op)) + { + LOG_WRN("Invalid length %u < %zu", buf->len, sizeof(*op)); + return false; + } + + op = net_buf_simple_pull_mem(buf, sizeof(*op)); + if (!is_valid_num_ases(conn, op->num_ases)) + { + return false; + } + + for (uint8_t i = 0U; i < op->num_ases; i++) + { + const struct bt_ascs_metadata *metadata; + + if (buf->len < sizeof(*metadata)) + { + LOG_WRN("Malformed params array"); + return false; + } + + metadata = net_buf_simple_pull_mem(buf, sizeof(*metadata)); + if (buf->len < metadata->len) + { + LOG_WRN("Malformed metadata"); + return false; + } + + (void)net_buf_simple_pull_mem(buf, metadata->len); + } + + if (buf->len > 0) + { + LOG_WRN("Unexpected data"); + return false; + } + + net_buf_simple_restore(buf, &state); + + return true; +} + +static ssize_t ascs_metadata(struct bt_conn *conn, + struct net_buf_simple *buf) +{ + const struct bt_ascs_metadata_op *req; + struct bt_ascs_metadata *meta; + + if (!is_valid_metadata_len(conn, buf)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + req = net_buf_simple_pull_mem(buf, sizeof(*req)); + + LOG_DBG("num_ases %u", req->num_ases); + + for (uint8_t i = 0U; i < req->num_ases; i++) + { + struct bt_ascs_ase *ase; + + meta = net_buf_simple_pull_mem(buf, sizeof(*meta)); + (void)net_buf_simple_pull(buf, meta->len); + + if (meta->len > CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE) + { + LOG_DBG("Cannot store %u octets of metadata", meta->len); + + ascs_cp_rsp_add(conn, meta->ase_id, BT_BAP_ASCS_RSP_CODE_NO_MEM, + BT_BAP_ASCS_REASON_NONE); + continue; + } + + if (!is_valid_ase_id(meta->ase_id)) + { + ascs_cp_rsp_add(conn, meta->ase_id, + BT_BAP_ASCS_RSP_CODE_INVALID_ASE, + BT_BAP_ASCS_REASON_NONE); + LOG_WRN("Unknown ase_id 0x%02x", meta->ase_id); + continue; + } + + ase = ase_find(conn, meta->ase_id); + if (!ase) + { + LOG_DBG("Invalid operation for idle ase_id 0x%02x", meta->ase_id); + ascs_cp_rsp_add(conn, meta->ase_id, + BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE, + BT_BAP_ASCS_REASON_NONE); + continue; + } + + ase_metadata(ase, meta); + } + + return buf->size; +} + +static bool is_valid_release_len(struct bt_conn *conn, + struct net_buf_simple *buf) +{ + const struct bt_ascs_release_op *op; + struct net_buf_simple_state state; + + net_buf_simple_save(buf, &state); + + if (buf->len < sizeof(*op)) + { + LOG_WRN("Invalid length %u < %zu", buf->len, sizeof(*op)); + return false; + } + + op = net_buf_simple_pull_mem(buf, sizeof(*op)); + if (!is_valid_num_ases(conn, op->num_ases)) + { + return false; + } + + if (buf->len != op->num_ases) + { + LOG_WRN("Number_of_ASEs mismatch"); + return false; + } + + net_buf_simple_restore(buf, &state); + + return true; +} + +static ssize_t ascs_release(struct bt_conn *conn, struct net_buf_simple *buf) +{ + const struct bt_ascs_release_op *req; + + if (!is_valid_release_len(conn, buf)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + req = net_buf_simple_pull_mem(buf, sizeof(*req)); + + LOG_DBG("num_ases %u", req->num_ases); + + for (uint8_t i = 0U; i < req->num_ases; i++) + { + struct bt_bap_ascs_rsp rsp = + BT_BAP_ASCS_RSP(BT_BAP_ASCS_RSP_CODE_UNSPECIFIED, + BT_BAP_ASCS_REASON_NONE); + struct bt_ascs_ase *ase; + uint8_t id; + + id = net_buf_simple_pull_u8(buf); + + LOG_DBG("ase_id 0x%02x", id); + + if (!is_valid_ase_id(id)) + { + ascs_cp_rsp_add(conn, id, BT_BAP_ASCS_RSP_CODE_INVALID_ASE, + BT_BAP_ASCS_REASON_NONE); + LOG_WRN("Unknown ase_id 0x%02x", id); + continue; + } + + ase = ase_find(conn, id); + if (!ase) + { + LOG_DBG("Invalid operation for idle ASE"); + ascs_cp_rsp_add(conn, id, BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE, + BT_BAP_ASCS_REASON_NONE); + continue; + } + + ase_release(ase, BT_HCI_ERR_REMOTE_USER_TERM_CONN, &rsp); + ascs_cp_rsp_add(conn, id, rsp.code, rsp.reason); + } + + return buf->size; +} + +static void control_point_respond(struct ascs_client *client) +{ + __maybe_unused int err; + + client->rsp_pending = true; + client->rsp_building = false; + + err = k_work_reschedule(&client->notify_work, 0); + DEBUGASSERT(err >= 0); +} + +static ssize_t ascs_cp_write( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *data, + uint16_t len, uint16_t offset, uint8_t flags) +{ + struct ascs_client *client = client_from_conn(conn); + const struct bt_ascs_ase_cp *req; + struct net_buf_simple buf; + ssize_t ret; + + if (client == NULL) + { + LOG_ERR("Failed to get client for conn %p with addr %s", conn, + bt_conn_dst_str(conn)); + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + if (flags & BT_GATT_WRITE_FLAG_PREPARE) + { + /* Return 0 to allow long writes */ + + return 0; + } + + if (offset != 0 && (flags & BT_GATT_WRITE_FLAG_EXECUTE) == 0) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (len < sizeof(*req)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + net_buf_simple_init_with_data(&buf, (void *)data, len); + + req = net_buf_simple_pull_mem(&buf, sizeof(*req)); + + LOG_DBG("conn %p attr %p buf %p len %u op %s (0x%02x)", (void *)conn, attr, + data, len, + bt_ascs_op_str(req->op), req->op); + + if (client->rsp_pending) + { + LOG_WRN("Already processing control point operation from conn %p", + conn); + return BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES); + } + + client->rsp_building = true; + ascs_cp_rsp_init(&client->cp_rsp_buf, req->op); + + switch (req->op) + { + case BT_ASCS_CONFIG_OP: + ret = ascs_config(conn, &buf); + break; + case BT_ASCS_QOS_OP: + ret = ascs_qos(conn, &buf); + break; + case BT_ASCS_ENABLE_OP: + ret = ascs_enable(conn, &buf); + break; + case BT_ASCS_START_OP: + ret = ascs_start(conn, &buf); + break; + case BT_ASCS_DISABLE_OP: + ret = ascs_disable(conn, &buf); + break; + case BT_ASCS_STOP_OP: + ret = ascs_stop(conn, &buf); + break; + case BT_ASCS_METADATA_OP: + ret = ascs_metadata(conn, &buf); + break; + case BT_ASCS_RELEASE_OP: + ret = ascs_release(conn, &buf); + break; + default: + ascs_cp_rsp_add(conn, BT_ASCS_ASE_ID_NONE, + BT_BAP_ASCS_RSP_CODE_NOT_SUPPORTED, + BT_BAP_ASCS_REASON_NONE); + LOG_DBG("Unknown opcode"); + goto respond; + } + + if (ret == BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN)) + { + ascs_cp_rsp_add(conn, BT_ASCS_ASE_ID_NONE, + BT_BAP_ASCS_RSP_CODE_INVALID_LENGTH, + BT_BAP_ASCS_REASON_NONE); + } + +respond: + control_point_respond(client); + + return len; +} + +#define BT_ASCS_ASE_DEFINE(_uuid, _id) \ + BT_AUDIO_CHRC(_uuid, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_READ_ENCRYPT, \ + ascs_ase_read, NULL, UINT_TO_POINTER(_id)), \ + BT_AUDIO_CCC(ascs_ase_cfg_changed) +#define BT_ASCS_ASE_SNK_DEFINE(_n, ...) BT_ASCS_ASE_DEFINE(BT_UUID_ASCS_ASE_SNK, (_n) + 1) +#define BT_ASCS_ASE_SRC_DEFINE(_n, ...) \ + BT_ASCS_ASE_DEFINE(BT_UUID_ASCS_ASE_SRC, (_n) + 1 + CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT) + +#define BT_ASCS_CHR_ASE_CONTROL_POINT \ + BT_AUDIO_CHRC(BT_UUID_ASCS_ASE_CP, \ + BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_WRITE_ENCRYPT | BT_GATT_PERM_PREPARE_WRITE, NULL, ascs_cp_write, \ + NULL), \ + BT_AUDIO_CCC(ascs_cp_cfg_changed) + +#if CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT > 0 +#define BT_ASCS_ASE_SINKS LISTIFY(CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT, BT_ASCS_ASE_SNK_DEFINE, (, )), +#else +#define BT_ASCS_ASE_SINKS +#endif /* CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT > 0 */ + +#if CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT > 0 +#define BT_ASCS_ASE_SOURCES LISTIFY(CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT, BT_ASCS_ASE_SRC_DEFINE, (, )), +#else +#define BT_ASCS_ASE_SOURCES +#endif /* CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT > 0 */ + +#define BT_ASCS_SERVICE_DEFINITION() \ + {BT_GATT_PRIMARY_SERVICE(BT_UUID_ASCS), \ + BT_AUDIO_CHRC(BT_UUID_ASCS_ASE_CP, \ + BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_WRITE_ENCRYPT | BT_GATT_PERM_PREPARE_WRITE, NULL, ascs_cp_write, \ + NULL), \ + BT_AUDIO_CCC(ascs_cp_cfg_changed), BT_ASCS_ASE_SINKS BT_ASCS_ASE_SOURCES} + +#define ASCS_ASE_CHAR_ATTR_COUNT 3 /* declaration + value + cccd */ + +static struct bt_gatt_attr ascs_attrs[] = BT_ASCS_SERVICE_DEFINITION(); +static struct bt_gatt_service ascs_svc = + (struct bt_gatt_service)BT_GATT_SERVICE(ascs_attrs); + +static void configure_ase_char(uint8_t snk_cnt, uint8_t src_cnt) +{ + uint8_t snk_ases_to_rem = CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT - snk_cnt; + uint8_t src_ases_to_rem = CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT - src_cnt; + size_t attrs_to_rem; + + /* Remove the Source ASEs. The ones to remove will always be at the very + * tail of the attributes, so we just decrease the count with the amount of + * sources we want to remove. + */ + + attrs_to_rem = src_ases_to_rem * ASCS_ASE_CHAR_ATTR_COUNT; + ascs_svc.attr_count -= attrs_to_rem; + + /* Remove the Sink ASEs. */ + + attrs_to_rem = snk_ases_to_rem * ASCS_ASE_CHAR_ATTR_COUNT; + if (CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT == 0 || + CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT == src_ases_to_rem) + { + /* If there are no Source ASEs present, then we can just decrease the + * attribute count + */ + + ascs_svc.attr_count -= attrs_to_rem; + } + else + { + /* As Source ASEs are present, we need to iterate backwards (as this + * will likely be the shortest distance). Find the first Sink to save, + * and move all Sources backwards to it. + */ + + size_t src_start_idx = + ascs_svc.attr_count - (src_cnt * ASCS_ASE_CHAR_ATTR_COUNT); + size_t new_src_start_idx = + src_start_idx - (snk_ases_to_rem * ASCS_ASE_CHAR_ATTR_COUNT); + + for (size_t i = 0U; i < src_cnt * ASCS_ASE_CHAR_ATTR_COUNT; i++) + { + ascs_svc.attrs[new_src_start_idx + i] = + ascs_svc.attrs[src_start_idx + i]; + } + + ascs_svc.attr_count -= attrs_to_rem; + } +} + +static struct bt_iso_server iso_server = +{ + .accept = ascs_iso_accept, +}; + +static const struct bt_gatt_attr ascs_attrs_backup[] = + BT_ASCS_SERVICE_DEFINITION(); +BUILD_ASSERT(sizeof(ascs_attrs_backup) == sizeof(ascs_attrs)); + +int bt_ascs_register(const struct bt_ascs_register_param *param) +{ + int err = 0; + + if (param == NULL) + { + LOG_DBG("param is NULL"); + + return -EINVAL; + } + + if (ascs.registered) + { + LOG_DBG("ASCS already registered"); + + return -EALREADY; + } + + if (param->snk_cnt > CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT || + param->src_cnt > CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT) + { + LOG_DBG("Provided ASE count above maximum"); + + return -EINVAL; + } + + /* At least one ASE has been registered */ + + if (param->snk_cnt == 0U && param->src_cnt == 0U) + { + LOG_DBG("Can't register ASCS with zero ASEs"); + + return -EINVAL; + } + + err = bt_iso_server_register(&iso_server); + if (err != 0) + { + DEBUGASSERT(err == -EADDRINUSE || err == -ENOTSUP); + + LOG_DBG("Failed to register ISO server %d", err); + + return err; + } + + configure_ase_char(param->snk_cnt, param->src_cnt); + + ARRAY_FOR_EACH_PTR(ascs.clients, client) + { + net_buf_simple_init_with_data(&client->cp_rsp_buf, client->_cp_rsp_data, + sizeof(client->_cp_rsp_data)); + net_buf_simple_reset(&client->cp_rsp_buf); + client->rsp_pending = false; + + k_work_init_delayable(&client->notify_work, notify_work_handler); + } + + err = bt_gatt_service_register(&ascs_svc); + if (err != 0) + { + __maybe_unused int unregister_err; + + DEBUGASSERT(err == -EINVAL); + + LOG_DBG("Failed to register ASCS in gatt DB"); + + unregister_err = bt_iso_server_unregister(&iso_server); + DEBUGASSERT(unregister_err == 0); + + (void)memcpy(ascs_attrs, ascs_attrs_backup, sizeof(ascs_attrs_backup)); + + return err; + } + + ascs_cb = param->cb; + ascs.registered = true; + + return 0; +} + +static int control_point_notify(struct ascs_client *client) +{ + return bt_gatt_notify_uuid(client->conn, BT_UUID_ASCS_ASE_CP, + ascs_svc.attrs, + client->cp_rsp_buf.data, + client->cp_rsp_buf.len); +} + +int bt_ascs_unregister(void) +{ + int err; + + if (!ascs.registered) + { + LOG_DBG("No ascs instance registered"); + return -EALREADY; + } + + /* Unregister the GATT service first to prevent any GATT operations while + * cleaning up + */ + + err = bt_gatt_service_unregister(&ascs_svc); + if (err != 0) + { + DEBUGASSERT(err == -ENOENT); + + LOG_DBG("Failed to unregister ASCS: %d", err); + + return err; + } + + ARRAY_FOR_EACH_PTR(ascs.clients, client) + { + struct k_work_sync sync; + + (void)k_work_cancel_delayable_sync(&client->notify_work, &sync); + + DEBUGASSERT(!k_work_delayable_is_pending(&client->notify_work)); + } + + ARRAY_FOR_EACH_PTR(ascs.ase_pool, ase) + { + force_ase_release(ase, BT_HCI_ERR_LOCALHOST_TERM_CONN); + } + + err = bt_iso_server_unregister(&iso_server); + DEBUGASSERT(err == 0); + + (void)memcpy(ascs_attrs, ascs_attrs_backup, sizeof(ascs_attrs_backup)); + ascs_svc.attr_count = ARRAY_SIZE(ascs_attrs); + ascs_cb = NULL; + ascs.registered = false; + + return 0; +} + +struct bt_conn *bt_ascs_ep_get_conn(const struct bt_bap_ep *ep) +{ + struct bt_ascs_ase *ase = CONTAINER_OF(ep, struct bt_ascs_ase, ep); + + DEBUGASSERT(bt_ascs_has_ep(ep)); + + if (ase->conn == NULL) + { + return NULL; + } + + return bt_conn_ref(ase->conn); +} diff --git a/libs/zblue/audio/ascs_internal.h b/libs/zblue/audio/ascs_internal.h new file mode 100644 index 0000000000000..294ed7d19658d --- /dev/null +++ b/libs/zblue/audio/ascs_internal.h @@ -0,0 +1,483 @@ +/**************************************************************************** + * libs/zblue/audio/ascs_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* @file + * @brief Internal APIs for ASCS handling + + * Copyright (c) 2020 Intel Corporation Copyright (c) 2022-2023 Nordic + * Semiconductor ASA Copyright (c) 2024 Demant A/S + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef BT_ASCS_INTERNAL_H +#define BT_ASCS_INTERNAL_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include + +#define BT_ASCS_ASE_ID_NONE 0x00U + +/* The number of ASEs in the notification when the opcode is unsupported or + * the length of the control point write request is incorrect + */ +#define BT_ASCS_UNSUPP_OR_LENGTH_ERR_NUM_ASE 0xFFU + +/* Transport QoS Packing */ +#define BT_ASCS_QOS_PACKING_SEQ 0x00U +#define BT_ASCS_QOS_PACKING_INT 0x01U + +/* Transport QoS Framing */ +#define BT_ASCS_QOS_FRAMING_UNFRAMED 0x00U +#define BT_ASCS_QOS_FRAMING_FRAMED 0x01U + +/* Format of the ASE characteristic, defined in Table 4.2 */ + +struct bt_ascs_ase_status +{ + uint8_t id; + uint8_t state; + uint8_t params[]; +} __packed; + +struct bt_ascs_codec_config +{ + uint8_t len; + uint8_t type; + uint8_t data[]; +} __packed; + +struct bt_ascs_codec +{ + uint8_t id; + uint16_t cid; + uint16_t vid; +} __packed; + +#define BT_ASCS_PD_NO_PREF 0x00000000U + +/* ASE_State = 0x01 (Codec Configured), defined in Table 4.3. */ + +struct bt_ascs_ase_status_config +{ + uint8_t framing; + uint8_t phy; + uint8_t rtn; + uint16_t latency; + uint8_t pd_min[3]; + uint8_t pd_max[3]; + uint8_t prefer_pd_min[3]; + uint8_t prefer_pd_max[3]; + struct bt_ascs_codec codec; + uint8_t cc_len; + + /* LTV-formatted Codec-Specific Configuration */ + + struct bt_ascs_codec_config cc[]; +} __packed; + +/* ASE_State = 0x02 (QoS Configured), defined in Table 4.4. */ + +struct bt_ascs_ase_status_qos +{ + uint8_t cig_id; + uint8_t cis_id; + uint8_t interval[3]; + uint8_t framing; + uint8_t phy; + uint16_t sdu; + uint8_t rtn; + uint16_t latency; + uint8_t pd[3]; +} __packed; + +/* ASE_Status = 0x03 (Enabling) defined in Table 4.5. */ + +struct bt_ascs_ase_status_enable +{ + uint8_t cig_id; + uint8_t cis_id; + uint8_t metadata_len; + uint8_t metadata[]; +} __packed; + +/* ASE_Status = 0x04 (Streaming) defined in Table 4.5. */ + +struct bt_ascs_ase_status_stream +{ + uint8_t cig_id; + uint8_t cis_id; + uint8_t metadata_len; + uint8_t metadata[]; +} __packed; + +/* ASE_Status = 0x05 (Disabling) as defined in Table 4.5. */ + +struct bt_ascs_ase_status_disable +{ + uint8_t cig_id; + uint8_t cis_id; + uint8_t metadata_len; + uint8_t metadata[]; +} __packed; + +/* ASE Control Point Protocol */ + +struct bt_ascs_ase_cp +{ + /* Request/Notification opcode */ + + uint8_t op; + uint8_t pdu[]; +} __packed; + +/* Opcodes */ +#define BT_ASCS_CONFIG_OP 0x01U + +#define BT_ASCS_CONFIG_LATENCY_LOW 0x01U +#define BT_ASCS_CONFIG_LATENCY_MEDIUM 0x02U +#define BT_ASCS_CONFIG_LATENCY_HIGH 0x03U + +#define BT_ASCS_CONFIG_PHY_LE_1M 0x01U +#define BT_ASCS_CONFIG_PHY_LE_2M 0x02U +#define BT_ASCS_CONFIG_PHY_LE_CODED 0x03U + +struct bt_ascs_config +{ + /* ASE ID */ + + uint8_t ase_id; + + /* Target latency */ + + uint8_t latency; + + /* Target PHY */ + + uint8_t phy; + + /* Codec ID */ + + struct bt_ascs_codec codec; + + /* Codec Specific Config Length */ + + uint8_t cc_len; + + /* LTV-formatted Codec-Specific Configuration */ + + struct bt_ascs_codec_config cc[]; +} __packed; + +struct bt_ascs_config_op +{ + /* Number of ASEs */ + + uint8_t num_ases; + + /* Config Parameters */ + + struct bt_ascs_config cfg[]; +} __packed; + +#define BT_ASCS_QOS_OP 0x02U +struct bt_ascs_qos +{ + /* ASE ID */ + + uint8_t ase_id; + + /* CIG ID */ + + uint8_t cig; + + /* CIG ID */ + + uint8_t cis; + + /* Frame interval */ + + uint8_t interval[3]; + + /* Frame framing */ + + uint8_t framing; + + /* PHY */ + + uint8_t phy; + + /* Maximum SDU Size */ + + uint16_t sdu; + + /* Retransmission Effort */ + + uint8_t rtn; + + /* Transport Latency */ + + uint16_t latency; + + /* Presentation Delay */ + + uint8_t pd[3]; +} __packed; + +struct bt_ascs_qos_op +{ + /* Number of ASEs */ + + uint8_t num_ases; + + /* QoS Parameters */ + + struct bt_ascs_qos qos[]; +} __packed; + +#define BT_ASCS_ENABLE_OP 0x03U +struct bt_ascs_metadata +{ + /* ASE ID */ + + uint8_t ase_id; + + /* Metadata length */ + + uint8_t len; + + /* LTV-formatted Metadata */ + + uint8_t data[]; +} __packed; + +struct bt_ascs_enable_op +{ + /* Number of ASEs */ + + uint8_t num_ases; + + /* Metadata */ + + struct bt_ascs_metadata metadata[]; +} __packed; + +#define BT_ASCS_START_OP 0x04U +struct bt_ascs_start_op +{ + /* Number of ASEs */ + + uint8_t num_ases; + + /* ASE IDs */ + + uint8_t ase_ids[]; +} __packed; + +#define BT_ASCS_DISABLE_OP 0x05U +struct bt_ascs_disable_op +{ + /* Number of ASEs */ + + uint8_t num_ases; + + /* ASE IDs */ + + uint8_t ase_ids[]; +} __packed; + +#define BT_ASCS_STOP_OP 0x06U +struct bt_ascs_stop_op +{ + /* Number of ASEs */ + + uint8_t num_ases; + + /* ASE IDs */ + + uint8_t ase_ids[]; +} __packed; + +#define BT_ASCS_METADATA_OP 0x07U +struct bt_ascs_metadata_op +{ + /* Number of ASEs */ + + uint8_t num_ases; + + /* Metadata */ + + struct bt_ascs_metadata metadata[]; +} __packed; + +#define BT_ASCS_RELEASE_OP 0x08U +struct bt_ascs_release_op +{ + /* Number of ASEs */ + + uint8_t num_ases; + + /* ASE IDs */ + + uint8_t ase_ids[]; +} __packed; + +struct bt_ascs_cp_ase_rsp +{ + /* ASE ID */ + + uint8_t ase_id; + + /* Response code */ + + uint8_t code; + + /* Response reason */ + + uint8_t reason; +} __packed; + +struct bt_ascs_cp_rsp +{ + /* Opcode */ + + uint8_t op; + + /* Number of ASEs */ + + uint8_t num_ase; + + /* ASE response */ + + struct bt_ascs_cp_ase_rsp ase_rsp[]; +} __packed; + +static inline const char *bt_ascs_op_str(uint8_t op) +{ + switch (op) + { + case BT_ASCS_CONFIG_OP: + return "Config Codec"; + case BT_ASCS_QOS_OP: + return "Config QoS"; + case BT_ASCS_ENABLE_OP: + return "Enable"; + case BT_ASCS_START_OP: + return "Receiver Start Ready"; + case BT_ASCS_DISABLE_OP: + return "Disable"; + case BT_ASCS_STOP_OP: + return "Receiver Stop Ready"; + case BT_ASCS_METADATA_OP: + return "Update Metadata"; + case BT_ASCS_RELEASE_OP: + return "Release"; + default: + return "Unknown"; + } +} + +static inline const char *bt_ascs_rsp_str(uint8_t code) +{ + switch (code) + { + case BT_BAP_ASCS_RSP_CODE_SUCCESS: + return "Success"; + case BT_BAP_ASCS_RSP_CODE_NOT_SUPPORTED: + return "Unsupported Opcode"; + case BT_BAP_ASCS_RSP_CODE_INVALID_LENGTH: + return "Invalid Length"; + case BT_BAP_ASCS_RSP_CODE_INVALID_ASE: + return "Invalid ASE_ID"; + case BT_BAP_ASCS_RSP_CODE_INVALID_ASE_STATE: + return "Invalid ASE State"; + case BT_BAP_ASCS_RSP_CODE_INVALID_DIR: + return "Invalid ASE Direction"; + case BT_BAP_ASCS_RSP_CODE_CAP_UNSUPPORTED: + return "Unsupported Capabilities"; + case BT_BAP_ASCS_RSP_CODE_CONF_UNSUPPORTED: + return "Unsupported Configuration Value"; + case BT_BAP_ASCS_RSP_CODE_CONF_REJECTED: + return "Rejected Configuration Value"; + case BT_BAP_ASCS_RSP_CODE_CONF_INVALID: + return "Invalid Configuration Value"; + case BT_BAP_ASCS_RSP_CODE_METADATA_UNSUPPORTED: + return "Unsupported Metadata"; + case BT_BAP_ASCS_RSP_CODE_METADATA_REJECTED: + return "Rejected Metadata"; + case BT_BAP_ASCS_RSP_CODE_METADATA_INVALID: + return "Invalid Metadata"; + case BT_BAP_ASCS_RSP_CODE_NO_MEM: + return "Insufficient Resources"; + case BT_BAP_ASCS_RSP_CODE_UNSPECIFIED: + return "Unspecified Error"; + default: + return "Unknown"; + } +} + +static inline const char *bt_ascs_reason_str(uint8_t reason) +{ + switch (reason) + { + case BT_BAP_ASCS_REASON_NONE: + return "None"; + case BT_BAP_ASCS_REASON_CODEC: + return "Codec ID"; + case BT_BAP_ASCS_REASON_CODEC_DATA: + return "Codec Specific Configuration"; + case BT_BAP_ASCS_REASON_INTERVAL: + return "SDU Interval"; + case BT_BAP_ASCS_REASON_FRAMING: + return "Framing"; + case BT_BAP_ASCS_REASON_PHY: + return "PHY"; + case BT_BAP_ASCS_REASON_SDU: + return "Maximum SDU Size"; + case BT_BAP_ASCS_REASON_RTN: + return "Retransmission Number"; + case BT_BAP_ASCS_REASON_LATENCY: + return "Maximum Transport Delay"; + case BT_BAP_ASCS_REASON_PD: + return "Presentation Delay"; + case BT_BAP_ASCS_REASON_CIS: + return "Invalid ASE CIS Mapping"; + default: + return "Unknown"; + } +} + +int ascs_ep_set_state(struct bt_bap_ep *ep, enum bt_bap_ep_state state); +bool bt_ascs_has_ep(const struct bt_bap_ep *ep); + +int bt_ascs_config_ase(struct bt_conn *conn, struct bt_bap_stream *stream, + const struct bt_audio_codec_cfg *codec_cfg, + const struct bt_bap_qos_cfg_pref *qos_pref); +int bt_ascs_reconfig_ase(struct bt_bap_ep *ep, + const struct bt_audio_codec_cfg *codec_cfg); +int bt_ascs_disable_ase(struct bt_bap_ep *ep); +int bt_ascs_release_ase(struct bt_bap_ep *ep); +int bt_ascs_start_ase(struct bt_bap_ep *ep); +int bt_ascs_metadata_ase(struct bt_bap_ep *ep, + const uint8_t meta[], size_t meta_len); + +int bt_ascs_foreach_ep(struct bt_conn *conn, + bt_bap_ep_func_t func, void *user_data); + +struct bt_conn *bt_ascs_ep_get_conn(const struct bt_bap_ep *ep); + +#endif /* BT_ASCS_INTERNAL_H */ diff --git a/libs/zblue/audio/audio.c b/libs/zblue/audio/audio.c new file mode 100644 index 0000000000000..26d9e0c46f265 --- /dev/null +++ b/libs/zblue/audio/audio.c @@ -0,0 +1,333 @@ +/**************************************************************************** + * libs/zblue/audio/audio.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Common functions for LE Audio services */ + +/* Copyright (c) 2022 Codecoup + * Copyright (c) 2025-2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "audio_internal.h" + +LOG_MODULE_REGISTER(bt_audio, CONFIG_BT_AUDIO_LOG_LEVEL); + +int bt_audio_data_parse(const uint8_t ltv[], size_t size, bool( + *func)(struct bt_data *data, void *user_data), + void *user_data) +{ + size_t i = 0U; + + if (ltv == NULL) + { + LOG_DBG("ltv is NULL"); + + return -EINVAL; + } + + if (func == NULL) + { + LOG_DBG("func is NULL"); + + return -EINVAL; + } + + while (i < size) + { + const uint8_t len = ltv[i]; + struct bt_data data; + + if (i + len > size || len < sizeof(data.type)) + { + LOG_DBG("Invalid len %u at i = %zu", len, i); + + return -EINVAL; + } + + i++; /* Increment as we have parsed the len field */ + + data.type = ltv[i]; + i++; + data.data_len = len - sizeof(data.type); + + if (data.data_len > 0U) + { + data.data = <v[i]; + } + else + { + data.data = NULL; + } + + if (!func(&data, user_data)) + { + return -ECANCELED; + } + + i += data.data_len; + } + + return 0; +} + +struct search_type_param +{ + bool found; + uint8_t type; + uint8_t data_len; + const uint8_t **data; +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static bool parse_cb(struct bt_data *data, void *user_data) +{ + struct search_type_param *param = (struct search_type_param *)user_data; + + if (param->type == data->type) + { + param->found = true; + param->data_len = data->data_len; + *param->data = data->data; + + return false; + } + + return true; +} + +int bt_audio_data_get_val(const uint8_t ltv_data[], + size_t size, uint8_t type, const uint8_t **data) +{ + struct search_type_param param = + { + .found = false, + .type = type, + .data_len = 0U, + .data = data, + }; + + int err; + + if (ltv_data == NULL) + { + LOG_DBG("ltv_data is NULL"); + return -EINVAL; + } + + if (data == NULL) + { + LOG_DBG("data is NULL"); + return -EINVAL; + } + + *data = NULL; + + /* If the size is 0 we can terminate early */ + + if (size == 0U) + { + return -ENODATA; + } + + err = bt_audio_data_parse(ltv_data, size, parse_cb, ¶m); + if (err != 0 && err != -ECANCELED) + { + LOG_DBG("Could not parse the data: %d", err); + return err; + } + + if (!param.found) + { + return -ENODATA; + } + + return param.data_len; +} + +uint8_t bt_audio_get_chan_count(enum bt_audio_location chan_allocation) +{ + if (chan_allocation == BT_AUDIO_LOCATION_MONO_AUDIO) + { + return 1; + } + + return sys_count_bits(&chan_allocation, sizeof(chan_allocation)); +} + +static bool valid_ltv_cb(struct bt_data *data, void *user_data) +{ + ARG_UNUSED(data); + ARG_UNUSED(user_data); + + /* just return true to continue parsing as bt_data_parse will validate for + * us + */ + + return true; +} + +bool bt_audio_valid_ltv(const uint8_t *data, uint8_t data_len) +{ + return bt_audio_data_parse(data, data_len, valid_ltv_cb, NULL) == 0; +} + +#if defined(CONFIG_BT_CONN) + +uint8_t bt_audio_security_check(const struct bt_conn *conn) +{ + struct bt_conn_info info; + int err; + + err = bt_conn_get_info(conn, &info); + if (err < 0) + { + LOG_DBG("Could not get conn info for %p: %d", conn, err); + + return BT_ATT_ERR_UNLIKELY; + } + + if (info.state != BT_CONN_STATE_CONNECTED) + { + LOG_DBG("conn %p invalid state: %d", conn, info.state); + + return BT_ATT_ERR_UNLIKELY; + } + + if (info.type != BT_CONN_TYPE_LE) + { + LOG_DBG("conn %p invalid type: %d", conn, info.type); + + return BT_ATT_ERR_UNLIKELY; + } + + /* Require an encryption key with at least 128 bits of entropy, derived + * from SC or OOB method. + */ + + if ((info.security.flags & (BT_SECURITY_FLAG_OOB | + BT_SECURITY_FLAG_SC)) == 0) + { + /* If the client has insufficient security to read/write the requested + * attribute then an ATT_ERROR_RSP PDU shall be sent with the Error + * Code parameter set to Insufficient Authentication (0x05). + */ + + LOG_DBG("conn %p invalid security flags: %d", conn, + info.security.flags); + + return BT_ATT_ERR_AUTHENTICATION; + } + + if (info.security.enc_key_size < BT_ENC_KEY_SIZE_MAX) + { + LOG_DBG("conn %p invalid encryption key size: %u", conn, + info.security.enc_key_size); + + return BT_ATT_ERR_ENCRYPTION_KEY_SIZE; + } + + return BT_ATT_ERR_SUCCESS; +} + +ssize_t bt_audio_read_chrc( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + const struct bt_audio_attr_user_data *user_data = attr->user_data; + + if (user_data->read == NULL) + { + return BT_GATT_ERR(BT_ATT_ERR_READ_NOT_PERMITTED); + } + + if (conn != NULL) + { + uint8_t err; + + err = bt_audio_security_check(conn); + if (err != 0) + { + return BT_GATT_ERR(err); + } + } + + return user_data->read(conn, attr, buf, len, offset); +} + +ssize_t bt_audio_write_chrc( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + const struct bt_audio_attr_user_data *user_data = attr->user_data; + + if (user_data->write == NULL) + { + return BT_GATT_ERR(BT_ATT_ERR_WRITE_NOT_PERMITTED); + } + + if (conn != NULL) + { + uint8_t err; + + err = bt_audio_security_check(conn); + if (err != 0) + { + return BT_GATT_ERR(err); + } + } + + return user_data->write(conn, attr, buf, len, offset, flags); +} + +ssize_t bt_audio_ccc_cfg_write( + struct bt_conn *conn, const struct bt_gatt_attr *attr, uint16_t value) +{ + ARG_UNUSED(attr); + + if (conn != NULL) + { + uint8_t err; + + err = bt_audio_security_check(conn); + if (err != 0) + { + return BT_GATT_ERR(err); + } + } + + return sizeof(value); +} +#endif /* CONFIG_BT_CONN */ diff --git a/libs/zblue/audio/audio_internal.h b/libs/zblue/audio/audio_internal.h new file mode 100644 index 0000000000000..67d30c1e77ebe --- /dev/null +++ b/libs/zblue/audio/audio_internal.h @@ -0,0 +1,121 @@ +/**************************************************************************** + * libs/zblue/audio/audio_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* @file + * @brief Internal APIs for LE Audio + * + * Copyright (c) 2022 Codecoup Copyright (c) 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#define BT_AUDIO_NOTIFY_RETRY_DELAY_US ((CONFIG_BT_AUDIO_NOTIFY_RETRY_DELAY) * 1250U) + +/** @brief LE Audio Attribute User Data. */ + +struct bt_audio_attr_user_data +{ + /** Attribute read callback */ + + ssize_t (*read)(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, + uint16_t len, + uint16_t offset); + + /** Attribute write callback */ + + ssize_t (*write)(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, + uint16_t len, + uint16_t offset, + uint8_t flags); + + /** Attribute user data */ + + void *user_data; +}; + +/** LE Audio Read Characteristic value helper. */ + +ssize_t bt_audio_read_chrc(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, + uint16_t len, + uint16_t offset); + +/** LE Audio Write Characteristic value helper. */ + +ssize_t bt_audio_write_chrc(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, + uint16_t len, + uint16_t offset, + uint8_t flags); + +#define BT_AUDIO_ATTR_USER_DATA_INIT(_read, _write, _user_data) \ + { \ + .read = _read, \ + .write = _write, \ + .user_data = _user_data, \ + } + +/** Helper to define LE Audio characteristic. */ +#define BT_AUDIO_CHRC(_uuid, _props, _perm, _read, _write, _user_data) \ + BT_GATT_CHARACTERISTIC(_uuid, _props, _perm, bt_audio_read_chrc, bt_audio_write_chrc, \ + ((struct bt_audio_attr_user_data[]){ \ + BT_AUDIO_ATTR_USER_DATA_INIT(_read, _write, _user_data), \ + })) + +#define BT_AUDIO_CHRC_USER_DATA(_attr) \ + (((struct bt_audio_attr_user_data *)(_attr)->user_data)->user_data) + +/** LE Audio Write CCCD value helper. */ + +ssize_t bt_audio_ccc_cfg_write(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + uint16_t value); + +/** Helper to define LE Audio CCC descriptor. */ +#define BT_AUDIO_CCC(_changed) \ + BT_GATT_CCC_MANAGED( \ + ((struct bt_gatt_ccc_managed_user_data[]){ \ + BT_GATT_CCC_MANAGED_USER_DATA_INIT(_changed, bt_audio_ccc_cfg_write, NULL)}), \ + (BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT)) + +bool bt_audio_valid_ltv(const uint8_t *data, uint8_t data_len); + +/** Checks the security of an ACL connection + * + * LE Audio requires a 128-bit key from LE Secure connections or where OOB + * data was used + * + * @param conn The ACL connection pointer + * + * @retval BT_ATT_ERR_SUCCESS Success + * @retval BT_ATT_ERR_UNLIKELY Invalid @p conn (NULL, not connected or not + * ACL) + * @retval BT_ATT_ERR_AUTHENTICATION Missing LE Secure Connection or OOB data + * @retval BT_ATT_ERR_ENCRYPTION_KEY_SIZE Key size is not 128-bit + */ + +uint8_t bt_audio_security_check(const struct bt_conn *conn); diff --git a/libs/zblue/audio/bap_base.c b/libs/zblue/audio/bap_base.c new file mode 100644 index 0000000000000..cd2adb2d1233e --- /dev/null +++ b/libs/zblue/audio/bap_base.c @@ -0,0 +1,769 @@ +/**************************************************************************** + * libs/zblue/audio/bap_base.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* bap_base.c - BAP BASE handling */ + +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(bt_bap_base, CONFIG_BT_BAP_BASE_LOG_LEVEL); + +/* The BASE and the following defines are defined by BAP v1.0.1, section + * 3.7.2.2 Basic Audio Announcements + */ +#define BASE_CODEC_ID_SIZE (1U /* id */ + 2U /* cid */ + 2U /* vid */) +#define BASE_PD_SIZE 3U +#define BASE_SUBGROUP_COUNT_SIZE 1U +#define BASE_NUM_BIS_SIZE 1U +#define BASE_CC_LEN_SIZE 1U +#define BASE_META_LEN_SIZE 1U +#define BASE_BIS_INDEX_SIZE 1U +#define BASE_BIS_CC_LEN_SIZE 1U +#define BASE_SUBGROUP_MAX_SIZE (BT_BASE_MAX_SIZE - BASE_PD_SIZE - BASE_SUBGROUP_COUNT_SIZE) +#define BASE_SUBGROUP_MIN_SIZE \ + (BASE_NUM_BIS_SIZE + BASE_CODEC_ID_SIZE + BASE_CC_LEN_SIZE + BASE_META_LEN_SIZE + \ + BASE_BIS_INDEX_SIZE + BASE_BIS_CC_LEN_SIZE) +#define BASE_MIN_SIZE \ + (BT_UUID_SIZE_16 + BASE_PD_SIZE + BASE_SUBGROUP_COUNT_SIZE + BASE_SUBGROUP_MIN_SIZE) +#define BASE_SUBGROUP_MAX_COUNT \ + ((BT_BASE_MAX_SIZE - BASE_PD_SIZE - BASE_SUBGROUP_COUNT_SIZE) / BASE_SUBGROUP_MIN_SIZE) + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static uint32_t base_pull_pd(struct net_buf_simple *net_buf) +{ + return net_buf_simple_pull_le24(net_buf); +} + +static uint8_t base_pull_bis_count(struct net_buf_simple *net_buf) +{ + return net_buf_simple_pull_u8(net_buf); +} + +static void base_pull_codec_id(struct net_buf_simple *net_buf, + struct bt_bap_base_codec_id *codec_id) +{ + struct bt_bap_base_codec_id codec; + + codec.id = net_buf_simple_pull_u8(net_buf); /* coding format */ + codec.cid = net_buf_simple_pull_le16(net_buf); /* company id */ + codec.vid = net_buf_simple_pull_le16(net_buf); /* VS codec id */ + + if (codec_id != NULL) + { + *codec_id = codec; + } +} + +static uint8_t base_pull_ltv(struct net_buf_simple *net_buf, uint8_t **data) +{ + const uint8_t len = net_buf_simple_pull_u8(net_buf); + + if (data == NULL) + { + net_buf_simple_pull_mem(net_buf, len); + } + else + { + *data = net_buf_simple_pull_mem(net_buf, len); + } + + return len; +} + +static bool check_pull_ltv(struct net_buf_simple *net_buf) +{ + uint8_t ltv_len; + + if (net_buf->len < sizeof(ltv_len)) + { + return false; + } + + ltv_len = net_buf_simple_pull_u8(net_buf); + if (net_buf->len < ltv_len) + { + return false; + } + + net_buf_simple_pull_mem(net_buf, ltv_len); + + return true; +} + +static struct bt_bap_base_subgroup *base_pull_subgroup( + struct net_buf_simple *net_buf, uint8_t index) +{ + struct bt_bap_base_subgroup *subgroup = + (struct bt_bap_base_subgroup *)net_buf->data; + uint8_t bis_count; + + if (net_buf->len < sizeof(bis_count)) + { + LOG_DBG("Invalid BASE length when pulling bis_count: %u", + net_buf->size); + + return NULL; + } + + bis_count = base_pull_bis_count(net_buf); + if (!IN_RANGE(bis_count, 1U, BT_ISO_MAX_GROUP_ISO_COUNT)) + { + LOG_DBG("Subgroup[%u]: Invalid BIS count: %u", index, bis_count); + + return NULL; + } + + if (net_buf->len < BASE_CODEC_ID_SIZE) + { + LOG_DBG("Invalid BASE length when pulling codec: %u", net_buf->size); + + return NULL; + } + + base_pull_codec_id(net_buf, NULL); + + /* Pull CC */ + + if (!check_pull_ltv(net_buf)) + { + LOG_DBG("Invalid BASE length when pulling CC LTV: %u", net_buf->size); + + return NULL; + } + + /* Pull meta */ + + if (!check_pull_ltv(net_buf)) + { + LOG_DBG("Invalid BASE length when pulling meta LTV: %u", + net_buf->size); + + return NULL; + } + + for (uint8_t i = 0U; i < bis_count; i++) + { + uint8_t bis_index; + + if (net_buf->len < sizeof(bis_index)) + { + LOG_DBG("Invalid BASE length when pulling bis_index: %u", + net_buf->size); + + return NULL; + } + + bis_index = net_buf_simple_pull_u8(net_buf); + if (!IN_RANGE(bis_index, 1U, BT_ISO_BIS_INDEX_MAX)) + { + LOG_DBG("Subgroup[%u]: Invalid BIS index: %u", index, bis_index); + + return NULL; + } + + /* Pull BIS CC data */ + + if (!check_pull_ltv(net_buf)) + { + LOG_DBG("Invalid BASE length when pulling BIS CC LTV: %u", + net_buf->size); + + return NULL; + } + } + + return subgroup; +} + +const struct bt_bap_base *bt_bap_base_get_base_from_ad( + const struct bt_data *ad) +{ + struct bt_uuid_16 broadcast_uuid; + const struct bt_bap_base *base; + struct net_buf_simple net_buf; + uint8_t subgroup_count; + void *uuid; + + if (ad == NULL) + { + LOG_DBG("data is NULL"); + + return NULL; + } + + if (ad->type != BT_DATA_SVC_DATA16) + { + LOG_DBG("Invalid type: %u", ad->type); + + return NULL; + } + + if (ad->data_len < BASE_MIN_SIZE) + { + LOG_DBG("Invalid len: %u", ad->data_len); + + return NULL; + } + + net_buf_simple_init_with_data(&net_buf, (void *)ad->data, ad->data_len); + + uuid = net_buf_simple_pull_mem(&net_buf, BT_UUID_SIZE_16); + if (!bt_uuid_create(&broadcast_uuid.uuid, uuid, BT_UUID_SIZE_16)) + { + LOG_ERR("bt_uuid_create failed"); + + return NULL; + } + + if (bt_uuid_cmp(&broadcast_uuid.uuid, BT_UUID_BASIC_AUDIO) != 0) + { + LOG_DBG("Invalid UUID"); + + return NULL; + } + + /* Store the start of the BASE */ + + base = (const struct bt_bap_base *)net_buf.data; + + /* Pull all data to verify that the result BASE is valid */ + + base_pull_pd(&net_buf); + subgroup_count = net_buf_simple_pull_u8(&net_buf); + if (!IN_RANGE(subgroup_count, 1U, BASE_SUBGROUP_MAX_COUNT)) + { + LOG_DBG("Invalid subgroup count: %u", subgroup_count); + + return NULL; + } + + for (uint8_t i = 0U; i < subgroup_count; i++) + { + const struct bt_bap_base_subgroup *subgroup = + base_pull_subgroup(&net_buf, + i); + + if (subgroup == NULL) + { + LOG_DBG("Subgroup[%u] is invalid", i); + + return NULL; + } + } + + return base; +} + +int bt_bap_base_get_size(const struct bt_bap_base *base) +{ + struct net_buf_simple net_buf; + uint8_t subgroup_count; + size_t size = 0U; + + if (base == NULL) + { + LOG_DBG("base is NULL"); + + return -EINVAL; + } + + net_buf_simple_init_with_data(&net_buf, (void *)base, BT_BASE_MAX_SIZE); + base_pull_pd(&net_buf); + size += BASE_PD_SIZE; + subgroup_count = net_buf_simple_pull_u8(&net_buf); + size += BASE_SUBGROUP_COUNT_SIZE; + + /* Parse subgroup data */ + + for (uint8_t i = 0U; i < subgroup_count; i++) + { + uint8_t bis_count; + uint8_t len; + + bis_count = base_pull_bis_count(&net_buf); + size += BASE_NUM_BIS_SIZE; + + base_pull_codec_id(&net_buf, NULL); + size += BASE_CODEC_ID_SIZE; + + /* Codec config */ + + len = base_pull_ltv(&net_buf, NULL); + size += len + BASE_CC_LEN_SIZE; + + /* meta */ + + len = base_pull_ltv(&net_buf, NULL); + size += len + BASE_META_LEN_SIZE; + + /* Parse BIS data */ + + for (uint8_t j = 0U; j < bis_count; j++) + { + /* BIS index */ + + net_buf_simple_pull_u8(&net_buf); + size += BASE_BIS_INDEX_SIZE; + + /* Codec config */ + + len = base_pull_ltv(&net_buf, NULL); + size += len + BASE_BIS_CC_LEN_SIZE; + } + } + + return (int)size; +} + +int bt_bap_base_get_pres_delay(const struct bt_bap_base *base) +{ + struct net_buf_simple net_buf; + uint32_t pd; + + if (base == NULL) + { + LOG_DBG("base is NULL"); + + return -EINVAL; + } + + net_buf_simple_init_with_data(&net_buf, (void *)base, sizeof(pd)); + pd = base_pull_pd(&net_buf); + + return (int)pd; /* PD is 24-bit so it fits in an int */ +} + +int bt_bap_base_get_subgroup_count(const struct bt_bap_base *base) +{ + struct net_buf_simple net_buf; + uint8_t subgroup_count; + + if (base == NULL) + { + LOG_DBG("base is NULL"); + + return -EINVAL; + } + + net_buf_simple_init_with_data(&net_buf, (void *)base, BT_BASE_MAX_SIZE); + base_pull_pd(&net_buf); + subgroup_count = net_buf_simple_pull_u8(&net_buf); + + /* subgroup_count is 8-bit so it fits in an int */ + + return (int)subgroup_count; +} + +int bt_bap_base_foreach_subgroup(const struct bt_bap_base *base, bool( + *func)(const struct bt_bap_base_subgroup *data, void *user_data), + void *user_data) +{ + struct net_buf_simple net_buf; + uint8_t subgroup_count; + + if (base == NULL) + { + LOG_DBG("base is NULL"); + + return -EINVAL; + } + + if (func == NULL) + { + LOG_DBG("func is NULL"); + + return -EINVAL; + } + + net_buf_simple_init_with_data(&net_buf, (void *)base, BT_BASE_MAX_SIZE); + base_pull_pd(&net_buf); + subgroup_count = net_buf_simple_pull_u8(&net_buf); + + for (uint8_t i = 0U; i < subgroup_count; i++) + { + const struct bt_bap_base_subgroup *subgroup = + base_pull_subgroup(&net_buf, + i); + + if (subgroup == NULL) + { + LOG_DBG("Subgroup[%u] is invalid", i); + + return -EINVAL; + } + + if (!func(subgroup, user_data)) + { + LOG_DBG("user stopped parsing"); + + return -ECANCELED; + } + } + + return 0; +} + +int bt_bap_base_get_subgroup_codec_id( + const struct bt_bap_base_subgroup *subgroup, + struct bt_bap_base_codec_id *codec_id) +{ + struct net_buf_simple net_buf; + + if (subgroup == NULL) + { + LOG_DBG("subgroup is NULL"); + + return -EINVAL; + } + + if (codec_id == NULL) + { + LOG_DBG("codec_id is NULL"); + + return -EINVAL; + } + + net_buf_simple_init_with_data(&net_buf, (void *)subgroup, + BASE_SUBGROUP_MAX_SIZE); + base_pull_bis_count(&net_buf); + base_pull_codec_id(&net_buf, codec_id); + + return 0; +} + +int bt_bap_base_get_subgroup_codec_data( + const struct bt_bap_base_subgroup *subgroup, uint8_t **data) +{ + struct net_buf_simple net_buf; + + if (subgroup == NULL) + { + LOG_DBG("subgroup is NULL"); + + return -EINVAL; + } + + if (data == NULL) + { + LOG_DBG("data is NULL"); + + return -EINVAL; + } + + net_buf_simple_init_with_data(&net_buf, (void *)subgroup, + BASE_SUBGROUP_MAX_SIZE); + base_pull_bis_count(&net_buf); + base_pull_codec_id(&net_buf, NULL); + + /* Codec config */ + + return base_pull_ltv(&net_buf, data); +} + +int bt_bap_base_get_subgroup_codec_meta( + const struct bt_bap_base_subgroup *subgroup, uint8_t **meta) +{ + struct net_buf_simple net_buf; + + if (subgroup == NULL) + { + LOG_DBG("subgroup is NULL"); + + return -EINVAL; + } + + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + + return -EINVAL; + } + + net_buf_simple_init_with_data(&net_buf, (void *)subgroup, + BASE_SUBGROUP_MAX_SIZE); + base_pull_bis_count(&net_buf); + base_pull_codec_id(&net_buf, NULL); + + /* Codec config */ + + base_pull_ltv(&net_buf, NULL); + + /* meta */ + + return base_pull_ltv(&net_buf, meta); +} + +int bt_bap_base_subgroup_codec_to_codec_cfg( + const struct bt_bap_base_subgroup *subgroup, + struct bt_audio_codec_cfg *codec_cfg) +{ + struct bt_bap_base_codec_id codec_id; + struct net_buf_simple net_buf; + uint8_t *ltv_data; + uint8_t ltv_len; + + if (subgroup == NULL) + { + LOG_DBG("subgroup is NULL"); + + return -EINVAL; + } + + if (codec_cfg == NULL) + { + LOG_DBG("codec_cfg is NULL"); + + return -EINVAL; + } + + net_buf_simple_init_with_data(&net_buf, (void *)subgroup, + BASE_SUBGROUP_MAX_SIZE); + base_pull_bis_count(&net_buf); + base_pull_codec_id(&net_buf, &codec_id); + + codec_cfg->id = codec_id.id; + codec_cfg->cid = codec_id.cid; + codec_cfg->vid = codec_id.vid; + + /* Codec config */ + + ltv_len = base_pull_ltv(&net_buf, <v_data); + + if (ltv_len > ARRAY_SIZE(codec_cfg->data)) + { + LOG_DBG("Cannot fit %u octets of codec data (max %zu)", ltv_len, + ARRAY_SIZE(codec_cfg->data)); + + return -ENOMEM; + } + + codec_cfg->data_len = ltv_len; + memcpy(codec_cfg->data, ltv_data, ltv_len); + + /* Meta */ + + ltv_len = base_pull_ltv(&net_buf, <v_data); + + if (ltv_len > ARRAY_SIZE(codec_cfg->meta)) + { + LOG_DBG("Cannot fit %u octets of codec meta (max %zu)", ltv_len, + ARRAY_SIZE(codec_cfg->meta)); + + return -ENOMEM; + } + + codec_cfg->meta_len = ltv_len; + memcpy(codec_cfg->meta, ltv_data, ltv_len); + + return 0; +} + +int bt_bap_base_get_subgroup_bis_count( + const struct bt_bap_base_subgroup *subgroup) +{ + struct net_buf_simple net_buf; + + if (subgroup == NULL) + { + LOG_DBG("subgroup is NULL"); + + return -EINVAL; + } + + net_buf_simple_init_with_data(&net_buf, (void *)subgroup, + BASE_SUBGROUP_MAX_SIZE); + + return base_pull_bis_count(&net_buf); +} + +int bt_bap_base_subgroup_foreach_bis( + const struct bt_bap_base_subgroup *subgroup, + bool (*func)(const struct bt_bap_base_subgroup_bis *subgroup, + void *user_data), + void *user_data) +{ + struct net_buf_simple net_buf; + uint8_t bis_count; + + if (subgroup == NULL) + { + LOG_DBG("subgroup is NULL"); + + return -EINVAL; + } + + if (func == NULL) + { + LOG_DBG("func is NULL"); + + return -EINVAL; + } + + net_buf_simple_init_with_data(&net_buf, (void *)subgroup, + BASE_SUBGROUP_MAX_SIZE); + + bis_count = base_pull_bis_count(&net_buf); + base_pull_codec_id(&net_buf, NULL); + + /* Codec config */ + + base_pull_ltv(&net_buf, NULL); + + /* meta */ + + base_pull_ltv(&net_buf, NULL); + + for (uint8_t i = 0U; i < bis_count; i++) + { + struct bt_bap_base_subgroup_bis bis; + + bis.index = net_buf_simple_pull_u8(&net_buf); /* index */ + + /* Codec config */ + + bis.data_len = base_pull_ltv(&net_buf, &bis.data); + + if (!func(&bis, user_data)) + { + LOG_DBG("user stopped parsing"); + + return -ECANCELED; + } + } + + return 0; +} + +int bt_bap_base_subgroup_bis_codec_to_codec_cfg( + const struct bt_bap_base_subgroup_bis *bis, + struct bt_audio_codec_cfg *codec_cfg) +{ + if (bis == NULL) + { + LOG_DBG("bis is NULL"); + + return -EINVAL; + } + + if (codec_cfg == NULL) + { + LOG_DBG("codec_cfg is NULL"); + + return -EINVAL; + } + + if (bis->data_len > ARRAY_SIZE(codec_cfg->data)) + { + LOG_DBG("Cannot fit %u octets of codec data (max %zu)", bis->data_len, + ARRAY_SIZE(codec_cfg->data)); + + return -ENOMEM; + } + + codec_cfg->data_len = bis->data_len; + memcpy(codec_cfg->data, bis->data, bis->data_len); + + return 0; +} + +static bool base_subgroup_bis_cb(const struct bt_bap_base_subgroup_bis *bis, + void *user_data) +{ + uint32_t *base_bis_index_bitfield = user_data; + + *base_bis_index_bitfield |= BT_ISO_BIS_INDEX_BIT(bis->index); + + return true; +} + +static bool base_subgroup_cb(const struct bt_bap_base_subgroup *subgroup, + void *user_data) +{ + const int err = bt_bap_base_subgroup_foreach_bis(subgroup, + base_subgroup_bis_cb, user_data); + + if (err != 0) + { + LOG_DBG("Failed to parse all BIS: %d", err); + return false; + } + + return true; +} + +int bt_bap_base_subgroup_get_bis_indexes( + const struct bt_bap_base_subgroup *subgroup, uint32_t *bis_indexes) +{ + if (subgroup == NULL) + { + LOG_DBG("subgroup is NULL"); + + return -EINVAL; + } + + if (bis_indexes == NULL) + { + LOG_DBG("bis_indexes is NULL"); + + return -EINVAL; + } + + *bis_indexes = 0U; + + return bt_bap_base_subgroup_foreach_bis(subgroup, base_subgroup_bis_cb, + bis_indexes); +} + +int bt_bap_base_get_bis_indexes(const struct bt_bap_base *base, + uint32_t *bis_indexes) +{ + if (base == NULL) + { + LOG_DBG("base is NULL"); + + return -EINVAL; + } + + if (bis_indexes == NULL) + { + LOG_DBG("bis_indexes is NULL"); + + return -EINVAL; + } + + *bis_indexes = 0U; + + return bt_bap_base_foreach_subgroup(base, base_subgroup_cb, bis_indexes); +} diff --git a/libs/zblue/audio/bap_broadcast_assistant.c b/libs/zblue/audio/bap_broadcast_assistant.c new file mode 100644 index 0000000000000..565ae92665074 --- /dev/null +++ b/libs/zblue/audio/bap_broadcast_assistant.c @@ -0,0 +1,2109 @@ +/**************************************************************************** + * libs/zblue/audio/bap_broadcast_assistant.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Bluetooth BAP Broadcast Assistant */ + +/* Copyright (c) 2019 Bose Corporation + * Copyright (c) 2022-2026 Nordic Semiconductor ASA + * Copyright (c) 2024 Demant A/S + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +LOG_MODULE_REGISTER(bt_bap_broadcast_assistant, + CONFIG_BT_BAP_BROADCAST_ASSISTANT_LOG_LEVEL); + +#include "common/bt_str.h" + +#include "audio_internal.h" +#include "bap_internal.h" + +#define MINIMUM_RECV_STATE_LEN 15U + +struct bap_broadcast_assistant_recv_state_info +{ + uint8_t src_id; + + /** Cached PAST available */ + + bool past_avail; + uint8_t adv_sid; + uint32_t broadcast_id; + bt_addr_le_t addr; +}; + +enum bap_broadcast_assistant_flag +{ + BAP_BA_FLAG_BUSY, + BAP_BA_FLAG_DISCOVER_IN_PROGRESS, + BAP_BA_FLAG_SCANNING, + + BAP_BA_FLAG_NUM_FLAGS, /* keep as last */ +}; + +struct bap_broadcast_assistant_instance +{ + struct bt_conn *conn; + uint8_t pa_sync; + uint8_t recv_state_cnt; + + uint16_t cp_handle; + uint16_t + recv_state_handles[CONFIG_BT_BAP_BROADCAST_ASSISTANT_RECV_STATE_COUNT]; + + struct bt_gatt_subscribe_params + recv_state_sub_params + [CONFIG_BT_BAP_BROADCAST_ASSISTANT_RECV_STATE_COUNT]; + struct bt_gatt_discover_params + recv_state_disc_params + [CONFIG_BT_BAP_BROADCAST_ASSISTANT_RECV_STATE_COUNT]; + + /* We ever only allow a single outstanding operation per instance, so we + * can reuse the memory for the GATT params + */ + + union + { + struct bt_gatt_read_params read_params; + struct bt_gatt_write_params write_params; + struct bt_gatt_discover_params disc_params; + }; + + struct k_work_delayable bap_read_work; + uint16_t long_read_handle; + /* The inst->net_buf needs to use the maximum ATT attribute size as a + * single receive state may use the full size + */ + + uint8_t att_buf[BT_ATT_MAX_ATTRIBUTE_LEN]; + struct net_buf_simple net_buf; + + struct bap_broadcast_assistant_recv_state_info + recv_states[CONFIG_BT_BAP_BROADCAST_ASSISTANT_RECV_STATE_COUNT]; + + atomic_define(flags, BAP_BA_FLAG_NUM_FLAGS); +}; + +static struct list_node broadcast_assistant_cbs = + LIST_INITIAL_VALUE(broadcast_assistant_cbs); + +static struct bap_broadcast_assistant_instance + broadcast_assistants[CONFIG_BT_MAX_CONN]; + +static const struct bt_uuid *bass_uuid = BT_UUID_BASS; + +static int read_recv_state(struct bap_broadcast_assistant_instance *inst, + uint8_t idx); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int16_t lookup_index_by_handle( + struct bap_broadcast_assistant_instance *inst, uint16_t handle) +{ + for (size_t i = 0U; i < ARRAY_SIZE(inst->recv_state_handles); i++) + { + if (inst->recv_state_handles[i] == handle) + { + return i; + } + } + + LOG_ERR("Unknown handle 0x%04x", handle); + + return -1; +} + +static struct bap_broadcast_assistant_instance *inst_by_conn( + struct bt_conn *conn) +{ + struct bap_broadcast_assistant_instance *inst; + + if (conn == NULL) + { + LOG_DBG("NULL conn"); + return NULL; + } + + inst = &broadcast_assistants[bt_conn_index(conn)]; + + if (inst->conn == conn) + { + return inst; + } + + return NULL; +} + +static void bap_broadcast_assistant_discover_complete( + struct bt_conn *conn, int err, uint8_t recv_state_count) +{ + struct bap_broadcast_assistant_instance *inst = inst_by_conn(conn); + struct bt_bap_broadcast_assistant_cb *listener; + struct bt_bap_broadcast_assistant_cb *next; + + if (inst != NULL) + { + net_buf_simple_reset(&inst->net_buf); + atomic_clear_bit(inst->flags, BAP_BA_FLAG_BUSY); + atomic_clear_bit(inst->flags, BAP_BA_FLAG_DISCOVER_IN_PROGRESS); + } + + list_for_every_container_safe(&broadcast_assistant_cbs, listener, next, + _node) + { + if (listener->discover) + { + listener->discover(conn, err, recv_state_count); + } + } +} + +static void +bap_broadcast_assistant_recv_state_changed(struct bt_conn *conn, + int err, + const struct bt_bap_scan_delegator_recv_state *state) +{ + struct bt_bap_broadcast_assistant_cb *listener; + struct bt_bap_broadcast_assistant_cb *next; + + list_for_every_container_safe(&broadcast_assistant_cbs, listener, next, + _node) + { + if (listener->recv_state) + { + listener->recv_state(conn, err, state); + } + } +} + +static void bap_broadcast_assistant_recv_state_removed(struct bt_conn *conn, + uint8_t src_id) +{ + struct bt_bap_broadcast_assistant_cb *listener; + struct bt_bap_broadcast_assistant_cb *next; + + list_for_every_container_safe(&broadcast_assistant_cbs, listener, next, + _node) + { + if (listener->recv_state_removed) + { + listener->recv_state_removed(conn, src_id); + } + } +} + +static void bap_broadcast_assistant_scan_results( + const struct bt_le_scan_recv_info *info, uint32_t broadcast_id) +{ + struct bt_bap_broadcast_assistant_cb *listener; + struct bt_bap_broadcast_assistant_cb *next; + + list_for_every_container_safe(&broadcast_assistant_cbs, listener, next, + _node) + { + if (listener->scan) + { + listener->scan(info, broadcast_id); + } + } +} + +static bool past_available(const struct bt_conn *conn, + const bt_addr_le_t *adv_addr, uint8_t sid) +{ + if (IS_ENABLED(CONFIG_BT_PER_ADV_SYNC_TRANSFER_SENDER)) + { + struct bt_le_local_features local_features; + struct bt_conn_remote_info remote_info; + int err; + + err = bt_le_get_local_features(&local_features); + if (err != 0) + { + LOG_DBG("Failed to get local features: %d", err); + return false; + } + + err = bt_conn_get_remote_info(conn, &remote_info); + if (err != 0) + { + LOG_DBG("Failed to get remote info: %d", err); + return false; + } + + LOG_DBG("%p remote %s PAST, local %s PAST", (void *)conn, + BT_FEAT_LE_PAST_RECV(remote_info.le.features) ? + "supports" : "does not support", + BT_FEAT_LE_PAST_SEND(local_features.features) ? + "supports" : "does not support"); + + return BT_FEAT_LE_PAST_RECV(remote_info.le.features) && + BT_FEAT_LE_PAST_SEND(local_features.features) && + bt_le_per_adv_sync_lookup_addr(adv_addr, sid) != NULL; + } + else + { + return false; + } +} + +static int parse_recv_state(const void *data, + uint16_t length, + struct bt_bap_scan_delegator_recv_state *recv_state) +{ + struct net_buf_simple buf; + bt_addr_t *addr; + + DEBUGASSERT(recv_state); + + if (data == NULL || length == 0) + { + LOG_DBG("NULL data"); + return -EINVAL; + } + + if (length < MINIMUM_RECV_STATE_LEN) + { + LOG_DBG("Invalid receive state length %u, expected at least " + "%u", length, + MINIMUM_RECV_STATE_LEN); + return -EINVAL; + } + + net_buf_simple_init_with_data(&buf, (void *)data, length); + + (void)memset(recv_state, 0, sizeof(*recv_state)); + + recv_state->src_id = net_buf_simple_pull_u8(&buf); + recv_state->addr.type = net_buf_simple_pull_u8(&buf); + addr = net_buf_simple_pull_mem(&buf, sizeof(*addr)); + bt_addr_copy(&recv_state->addr.a, addr); + recv_state->adv_sid = net_buf_simple_pull_u8(&buf); + recv_state->broadcast_id = net_buf_simple_pull_le24(&buf); + recv_state->pa_sync_state = net_buf_simple_pull_u8(&buf); + recv_state->encrypt_state = net_buf_simple_pull_u8(&buf); + if (recv_state->encrypt_state == BT_BAP_BIG_ENC_STATE_BAD_CODE) + { + uint8_t *broadcast_code; + const size_t minimum_size = + sizeof(recv_state->bad_code) + + sizeof(recv_state->num_subgroups); + + if (buf.len < minimum_size) + { + LOG_DBG("Invalid receive state length %u, expected at least %zu", + buf.len, minimum_size); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + broadcast_code = net_buf_simple_pull_mem(&buf, + BT_ISO_BROADCAST_CODE_SIZE); + (void)memcpy(recv_state->bad_code, broadcast_code, + sizeof(recv_state->bad_code)); + } + + recv_state->num_subgroups = net_buf_simple_pull_u8(&buf); + if (recv_state->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) + { + LOG_DBG("Cannot parse %u subgroups (max " + "%d)", recv_state->num_subgroups, + CONFIG_BT_BAP_BASS_MAX_SUBGROUPS); + + return -ENOMEM; + } + + for (int i = 0; i < recv_state->num_subgroups; i++) + { + struct bt_bap_bass_subgroup *subgroup = &recv_state->subgroups[i]; + uint8_t *metadata; + + if (buf.len < sizeof(subgroup->bis_sync)) + { + LOG_DBG("Invalid receive state length %u, expected at least %zu", + buf.len, + buf.len + sizeof(subgroup->bis_sync)); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + subgroup->bis_sync = net_buf_simple_pull_le32(&buf); + + if (buf.len < sizeof(subgroup->metadata_len)) + { + LOG_DBG("Invalid receive state length %u, expected at least %zu", + buf.len, + buf.len + sizeof(subgroup->metadata_len)); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + subgroup->metadata_len = net_buf_simple_pull_u8(&buf); + + if (buf.len < subgroup->metadata_len) + { + LOG_DBG("Invalid receive state length %u, expected at least %u", + buf.len, + buf.len + subgroup->metadata_len); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + if (subgroup->metadata_len > sizeof(subgroup->metadata)) + { + LOG_DBG("Metadata too long: %u/%zu", subgroup->metadata_len, + sizeof(subgroup->metadata)); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + metadata = net_buf_simple_pull_mem(&buf, subgroup->metadata_len); + (void)memcpy(subgroup->metadata, metadata, subgroup->metadata_len); + } + + if (buf.len != 0) + { + LOG_DBG("Invalid receive state length %u, but only %u was parsed", + length, length - buf.len); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + return 0; +} + +static void +bap_long_read_reset(struct bap_broadcast_assistant_instance *inst) +{ + inst->long_read_handle = 0U; + net_buf_simple_reset(&inst->net_buf); + atomic_clear_bit(inst->flags, BAP_BA_FLAG_BUSY); +} + +static uint8_t parse_and_send_recv_state( + struct bt_conn *conn, uint16_t handle, const void *data, uint16_t length, + struct bt_bap_scan_delegator_recv_state *recv_state) +{ + int err; + int16_t index; + struct bap_broadcast_assistant_instance *inst = inst_by_conn(conn); + + if (inst == NULL) + { + return BT_GATT_ITER_STOP; + } + + err = parse_recv_state(data, length, recv_state); + if (err != 0) + { + LOG_WRN("Invalid receive state received"); + + return BT_GATT_ITER_STOP; + } + + index = lookup_index_by_handle(inst, handle); + if (index < 0) + { + LOG_DBG("Invalid index"); + + return BT_GATT_ITER_STOP; + } + + inst->recv_states[index].src_id = recv_state->src_id; + inst->recv_states[index].past_avail = + past_available(conn, &recv_state->addr, recv_state->adv_sid); + + bap_broadcast_assistant_recv_state_changed(conn, 0, recv_state); + + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t broadcast_assistant_bap_ntf_read_func( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *read, + const void *data, uint16_t length) +{ + struct bt_bap_scan_delegator_recv_state recv_state; + uint16_t handle = read->single.handle; + uint16_t data_length; + struct bap_broadcast_assistant_instance *inst = inst_by_conn(conn); + + if (inst == NULL) + { + return BT_GATT_ITER_STOP; + } + + LOG_DBG("conn %p err 0x%02x len %u", (void *)conn, err, length); + + if (err != 0) + { + LOG_DBG("Failed to read: %u", err); + memset(read, 0, sizeof(*read)); + bap_long_read_reset(inst); + + return BT_GATT_ITER_STOP; + } + + LOG_DBG("handle 0x%04x", handle); + + if (data != NULL) + { + if (net_buf_simple_tailroom(&inst->net_buf) < length) + { + LOG_DBG("Buffer full, invalid server response of size %u", + length + inst->net_buf.len); + memset(read, 0, sizeof(*read)); + bap_long_read_reset(inst); + + return BT_GATT_ITER_STOP; + } + + /* store data */ + + net_buf_simple_add_mem(&inst->net_buf, data, length); + + return BT_GATT_ITER_CONTINUE; + } + + /* we reset the buffer so that it is ready for new data */ + + memset(read, 0, sizeof(*read)); + data_length = inst->net_buf.len; + bap_long_read_reset(inst); + + /* do the parse and callback to send notify to application */ + + parse_and_send_recv_state(conn, handle, inst->net_buf.data, data_length, + &recv_state); + + return BT_GATT_ITER_STOP; +} + +static void long_bap_read(struct bt_conn *conn, uint16_t handle) +{ + int err; + struct bap_broadcast_assistant_instance *inst = inst_by_conn(conn); + + if (inst == NULL) + { + return; + } + + if (atomic_test_and_set_bit(inst->flags, BAP_BA_FLAG_BUSY)) + { + LOG_DBG("conn %p", (void *)conn); + + /* If the client is busy reading reschedule the long read */ + + struct bt_conn_info conn_info; + + err = bt_conn_get_info(conn, &conn_info); + if (err != 0) + { + LOG_DBG("Failed to get conn info, use default interval"); + + conn_info.le.interval_us = + BT_CONN_INTERVAL_TO_US(BT_GAP_INIT_CONN_INT_MIN); + } + + /* Wait a connection interval to retry */ + + err = k_work_reschedule(&inst->bap_read_work, + USEC2TICK(conn_info.le.interval_us)); + if (err < 0) + { + LOG_DBG("Failed to reschedule read work: %d", err); + bap_long_read_reset(inst); + } + + return; + } + + inst->read_params.func = broadcast_assistant_bap_ntf_read_func; + inst->read_params.handle_count = 1U; + inst->read_params.single.handle = handle; + inst->read_params.single.offset = inst->net_buf.len; + + err = bt_gatt_read(conn, &inst->read_params); + if (err != 0) + { + /* TODO: If read failed due to buffers, retry */ + + LOG_DBG("Failed to read: %d", err); + bap_long_read_reset(inst); + } +} + +static void delayed_bap_read_handler(struct k_work *work) +{ + struct bap_broadcast_assistant_instance *inst = CONTAINER_OF( + (struct k_work_delayable *)work, + struct bap_broadcast_assistant_instance, bap_read_work); + + long_bap_read(inst->conn, inst->long_read_handle); +} + +/** @brief Handles notifications and indications from the server */ + +static uint8_t notify_handler( + struct bt_conn *conn, struct bt_gatt_subscribe_params *params, + const void *data, uint16_t length) +{ + uint16_t handle = params->value_handle; + struct bt_bap_scan_delegator_recv_state recv_state; + int16_t index; + struct bap_broadcast_assistant_instance *inst; + + if (conn == NULL) + { + /* Indicates that the CCC has been removed - no-op */ + + return BT_GATT_ITER_CONTINUE; + } + + inst = inst_by_conn(conn); + + if (inst == NULL) + { + return BT_GATT_ITER_STOP; + } + + if (atomic_test_bit(inst->flags, BAP_BA_FLAG_DISCOVER_IN_PROGRESS)) + { + /* If we are discovering then we ignore notifications as the handles + * may change + */ + + return BT_GATT_ITER_CONTINUE; + } + + if (data == NULL) + { + LOG_DBG("[UNSUBSCRIBED] %u", handle); + params->value_handle = 0U; + + return BT_GATT_ITER_STOP; + } + + LOG_HEXDUMP_DBG(data, length, "Receive state notification:"); + + index = lookup_index_by_handle(inst, handle); + if (index < 0) + { + LOG_DBG("Invalid index"); + + return BT_GATT_ITER_STOP; + } + + if (length != 0) + { + const int max_ntf_size = bt_att_get_max_notify_size(conn, + BT_ATT_CHAN_OPT_NONE); + + if (max_ntf_size < 0) + { + DEBUGASSERT(max_ntf_size != -EINVAL); + return BT_GATT_ITER_STOP; + } + + /* Cancel any pending long reads containing now obsolete information */ + + (void)k_work_cancel_delayable(&inst->bap_read_work); + + if (max_ntf_size >= 0 && length == max_ntf_size) + { + /* TODO: if we are busy we should not overwrite the + * long_read_handle, we'll have to keep track of the handle and + * parameters separately for each characteristic, similar to the + * bt_bap_unicast_client_ep struct for the unicast client + */ + + inst->long_read_handle = handle; + + if (!atomic_test_bit(inst->flags, BAP_BA_FLAG_BUSY)) + { + net_buf_simple_add_mem(&inst->net_buf, data, length); + } + + long_bap_read(conn, handle); + } + else + { + return parse_and_send_recv_state(conn, handle, data, length, + &recv_state); + } + } + else + { + inst->recv_states[index].past_avail = false; + bap_broadcast_assistant_recv_state_removed(conn, + inst->recv_states + [index].src_id); + } + + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t read_recv_state_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct bap_broadcast_assistant_instance *inst = inst_by_conn(conn); + bool active_recv_state = data != NULL && length != 0; + struct bt_bap_scan_delegator_recv_state recv_state; + uint16_t handle = params->single.handle; + int cb_err = err; + + if (inst == NULL) + { + return BT_GATT_ITER_STOP; + } + + (void)memset(params, 0, sizeof(*params)); + + LOG_DBG("%s receive state", active_recv_state ? "Active" : "Inactive"); + + if (cb_err == 0 && active_recv_state) + { + int16_t index; + + index = lookup_index_by_handle(inst, handle); + if (index < 0) + { + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_HANDLE); + } + else + { + cb_err = parse_recv_state(data, length, &recv_state); + + if (cb_err != 0) + { + LOG_DBG("Invalid receive state"); + } + else + { + struct bap_broadcast_assistant_recv_state_info *stored_state = + &inst->recv_states[index]; + + stored_state->src_id = recv_state.src_id; + stored_state->adv_sid = recv_state.adv_sid; + stored_state->broadcast_id = recv_state.broadcast_id; + bt_addr_le_copy(&stored_state->addr, &recv_state.addr); + inst->recv_states[index].past_avail = + past_available(conn, &recv_state.addr, + recv_state.adv_sid); + } + } + } + + if (cb_err != 0) + { + LOG_DBG("err %d", cb_err); + atomic_clear_bit(inst->flags, BAP_BA_FLAG_BUSY); + bap_broadcast_assistant_recv_state_changed(conn, cb_err, NULL); + } + else + { + atomic_clear_bit(inst->flags, BAP_BA_FLAG_BUSY); + bap_broadcast_assistant_recv_state_changed(conn, cb_err, + active_recv_state ? + &recv_state : NULL); + } + + return BT_GATT_ITER_STOP; +} + +static void discover_init(struct bap_broadcast_assistant_instance *inst) +{ + k_work_init_delayable(&inst->bap_read_work, delayed_bap_read_handler); + net_buf_simple_init_with_data(&inst->net_buf, inst->att_buf, + sizeof(inst->att_buf)); + net_buf_simple_reset(&inst->net_buf); + atomic_set_bit(inst->flags, BAP_BA_FLAG_DISCOVER_IN_PROGRESS); +} + +/** + * @brief This will discover all characteristics on the server, retrieving + * the + * handles of the writeable characteristics and subscribing to all notify and + * indicate characteristics. + */ + +static uint8_t char_discover_func( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_gatt_subscribe_params *sub_params = NULL; + int err; + struct bap_broadcast_assistant_instance *inst = inst_by_conn(conn); + + if (inst == NULL) + { + return BT_GATT_ITER_STOP; + } + + if (attr == NULL) + { + LOG_DBG("Found %u BASS receive states", inst->recv_state_cnt); + (void)memset(params, 0, sizeof(*params)); + + bap_broadcast_assistant_discover_complete(conn, 0, + inst->recv_state_cnt); + + return BT_GATT_ITER_STOP; + } + + LOG_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle); + + if (params->type == BT_GATT_DISCOVER_CHARACTERISTIC) + { + struct bt_gatt_chrc *chrc = (struct bt_gatt_chrc *)attr->user_data; + + if (bt_uuid_cmp(chrc->uuid, BT_UUID_BASS_CONTROL_POINT) == 0) + { + LOG_DBG("Control Point"); + inst->cp_handle = attr->handle + 1; + } + else if (bt_uuid_cmp(chrc->uuid, BT_UUID_BASS_RECV_STATE) == 0) + { + if (inst->recv_state_cnt < + CONFIG_BT_BAP_BROADCAST_ASSISTANT_RECV_STATE_COUNT) + { + const uint8_t idx = inst->recv_state_cnt; + + LOG_DBG("Receive State %u", inst->recv_state_cnt); + inst->recv_state_handles[idx] = attr->handle + 1; + sub_params = &inst->recv_state_sub_params[idx]; + sub_params->disc_params = &inst->recv_state_disc_params[idx]; + inst->recv_state_cnt++; + } + } + else + { + LOG_DBG("Invalid UUID %s", bt_uuid_str(chrc->uuid)); + bap_broadcast_assistant_discover_complete(conn, -EBADMSG, 0); + + return BT_GATT_ITER_STOP; + } + + if (sub_params != NULL) + { + sub_params->end_handle = params->end_handle; + sub_params->ccc_handle = BT_GATT_AUTO_DISCOVER_CCC_HANDLE; + sub_params->value = BT_GATT_CCC_NOTIFY; + sub_params->value_handle = attr->handle + 1; + sub_params->notify = notify_handler; + atomic_set_bit(sub_params->flags, BT_GATT_SUBSCRIBE_FLAG_VOLATILE); + + err = bt_gatt_subscribe(conn, sub_params); + if (err != 0) + { + LOG_DBG("Could not subscribe to handle 0x%04x: %d", + sub_params->value_handle, err); + + bap_broadcast_assistant_discover_complete(conn, err, 0); + + return BT_GATT_ITER_STOP; + } + } + } + + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t service_discover_func( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + int err; + struct bt_gatt_service_val *prim_service; + struct bap_broadcast_assistant_instance *inst = inst_by_conn(conn); + + if (inst == NULL) + { + return BT_GATT_ITER_STOP; + } + + if (attr == NULL) + { + LOG_DBG("Could not discover BASS"); + (void)memset(params, 0, sizeof(*params)); + + err = BT_GATT_ERR(BT_ATT_ERR_NOT_SUPPORTED); + + bap_broadcast_assistant_discover_complete(conn, err, 0); + + return BT_GATT_ITER_STOP; + } + + LOG_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle); + + if (params->type == BT_GATT_DISCOVER_PRIMARY) + { + prim_service = (struct bt_gatt_service_val *)attr->user_data; + + inst->disc_params.uuid = NULL; + inst->disc_params.start_handle = attr->handle + 1U; + inst->disc_params.end_handle = prim_service->end_handle; + inst->disc_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; + inst->disc_params.func = char_discover_func; + + err = bt_gatt_discover(conn, &inst->disc_params); + if (err != 0) + { + LOG_DBG("Discover failed (err %d)", err); + bap_broadcast_assistant_discover_complete(conn, err, 0); + } + } + + return BT_GATT_ITER_STOP; +} + +static void bap_broadcast_assistant_write_cp_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_write_params *params) +{ + struct bt_bap_broadcast_assistant_cb *listener; + struct bt_bap_broadcast_assistant_cb *next; + struct bap_broadcast_assistant_instance *inst = inst_by_conn(conn); + + ARG_UNUSED(params); + + if (inst == NULL) + { + return; + } + + uint8_t opcode = net_buf_simple_pull_u8(&inst->net_buf); + + /* we reset the buffer, so that we are ready for new notifications and + * writes + */ + + net_buf_simple_reset(&inst->net_buf); + + atomic_clear_bit(inst->flags, BAP_BA_FLAG_BUSY); + + list_for_every_container_safe(&broadcast_assistant_cbs, listener, next, + _node) + { + switch (opcode) + { + case BT_BAP_BASS_OP_SCAN_STOP: + if (listener->scan_stop != NULL) + { + listener->scan_stop(conn, err); + } + break; + case BT_BAP_BASS_OP_SCAN_START: + if (listener->scan_start != NULL) + { + listener->scan_start(conn, err); + } + break; + case BT_BAP_BASS_OP_ADD_SRC: + if (listener->add_src != NULL) + { + listener->add_src(conn, err); + } + break; + case BT_BAP_BASS_OP_MOD_SRC: + if (listener->mod_src != NULL) + { + listener->mod_src(conn, err); + } + break; + case BT_BAP_BASS_OP_BROADCAST_CODE: + if (listener->broadcast_code != NULL) + { + listener->broadcast_code(conn, err); + } + break; + case BT_BAP_BASS_OP_REM_SRC: + if (listener->rem_src != NULL) + { + listener->rem_src(conn, err); + } + break; + default: + LOG_DBG("Unknown opcode 0x%02x", opcode); + break; + } + } +} + +static int bt_bap_broadcast_assistant_common_cp(struct bt_conn *conn, + const struct net_buf_simple *buf) +{ + int err; + struct bap_broadcast_assistant_instance *inst; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + inst = inst_by_conn(conn); + + if (inst == NULL) + { + return -EINVAL; + } + + if (inst->cp_handle == 0U) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + + inst->write_params.offset = 0; + inst->write_params.data = buf->data; + inst->write_params.length = buf->len; + inst->write_params.handle = inst->cp_handle; + inst->write_params.func = bap_broadcast_assistant_write_cp_cb; + + err = bt_gatt_write(conn, &inst->write_params); + if (err != 0) + { + atomic_clear_bit(inst->flags, BAP_BA_FLAG_BUSY); + + /* Report expected possible errors */ + + if (err == -ENOTCONN || err == -ENOMEM) + { + return err; + } + + return -ENOEXEC; + } + + return 0; +} + +static bool broadcast_source_found(struct bt_data *data, void *user_data) +{ + const struct bt_le_scan_recv_info *info = user_data; + struct bt_uuid_16 adv_uuid; + uint32_t broadcast_id; + + if (data->type != BT_DATA_SVC_DATA16) + { + return true; + } + + if (data->data_len < BT_UUID_SIZE_16 + BT_AUDIO_BROADCAST_ID_SIZE) + { + return true; + } + + if (!bt_uuid_create(&adv_uuid.uuid, data->data, BT_UUID_SIZE_16)) + { + return true; + } + + if (bt_uuid_cmp(&adv_uuid.uuid, BT_UUID_BROADCAST_AUDIO) != 0) + { + return true; + } + + broadcast_id = sys_get_le24(data->data + BT_UUID_SIZE_16); + + LOG_DBG("Found BIS advertiser with address %s SID 0x%02X and broadcast_id " + "0x%06X", + bt_addr_le_str(info->addr), info->sid, broadcast_id); + + bap_broadcast_assistant_scan_results(info, broadcast_id); + + return false; +} + +void scan_recv(const struct bt_le_scan_recv_info *info, + struct net_buf_simple *ad) +{ + /* We are only interested in non-connectable periodic advertisers */ + + if ((info->adv_props & BT_GAP_ADV_PROP_CONNECTABLE) != 0 || + info->interval == 0) + { + return; + } + + bt_data_parse(ad, broadcast_source_found, (void *)info); +} + +static struct bt_le_scan_cb scan_cb = +{ + .recv = scan_recv +}; + +/* BAP 6.5.4 states that the Broadcast Assistant shall not initiate the Add + * Source operation if the operation would result in duplicate values for the + * combined Source_Address_Type, Source_Adv_SID, and Broadcast_ID fields of + * any Broadcast Receive State characteristic exposed by the Scan Delegator. + */ + +static bool broadcast_src_is_duplicate( + struct bap_broadcast_assistant_instance *inst, uint32_t broadcast_id, + uint8_t adv_sid, uint8_t addr_type) +{ + for (size_t i = 0U; i < ARRAY_SIZE(inst->recv_states); i++) + { + const struct bap_broadcast_assistant_recv_state_info *state = + &inst->recv_states[i]; + + if (state != NULL && state->broadcast_id == broadcast_id && + state->adv_sid == adv_sid && + state->addr.type == addr_type) + { + LOG_DBG("recv_state already exists at src_id=0x%02X", + state->src_id); + + return true; + } + } + + return false; +} + +/* Public Api */ + +static int broadcast_assistant_reset( + struct bap_broadcast_assistant_instance *inst) +{ + inst->pa_sync = 0U; + inst->recv_state_cnt = 0U; + inst->cp_handle = 0U; + inst->long_read_handle = 0U; + (void)k_work_cancel_delayable(&inst->bap_read_work); + + for (int i = + 0U; i < CONFIG_BT_BAP_BROADCAST_ASSISTANT_RECV_STATE_COUNT; i++) + { + memset(&inst->recv_states[i], 0, sizeof(inst->recv_states[i])); + inst->recv_states[i].broadcast_id = BT_BAP_INVALID_BROADCAST_ID; + inst->recv_states[i].adv_sid = BT_HCI_LE_EXT_ADV_SID_INVALID; + inst->recv_states[i].past_avail = false; + inst->recv_state_handles[i] = 0U; + } + + if (inst->conn != NULL) + { + struct bt_conn *conn = inst->conn; + struct bt_conn_info info; + int err; + + err = bt_conn_get_info(conn, &info); + if (err != 0) + { + return err; + } + + if (info.state == BT_CONN_STATE_CONNECTED) + { + for (size_t i = + 0U; i < ARRAY_SIZE(inst->recv_state_sub_params); i++) + { + /* It's okay if this fail with -EINVAL as that means that they + * are not currently subscribed + */ + + err = bt_gatt_unsubscribe(conn, + &inst->recv_state_sub_params[i]); + if (err != 0 && err != -EINVAL) + { + LOG_DBG("Failed to unsubscribe to state: %d", err); + + return err; + } + } + } + + bt_conn_drop(&inst->conn); + } + + /* The subscribe parameters must remain instact so they can get cleaned up + * by GATT + */ + + memset(&inst->disc_params, 0, sizeof(inst->disc_params)); + memset(&inst->recv_state_disc_params, 0, + sizeof(inst->recv_state_disc_params)); + memset(&inst->read_params, 0, sizeof(inst->read_params)); + memset(&inst->write_params, 0, sizeof(inst->write_params)); + + return 0; +} + +static void disconnected_cb(struct bt_conn *conn, uint8_t reason) +{ + struct bap_broadcast_assistant_instance *inst; + + ARG_UNUSED(reason); + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + inst = inst_by_conn(conn); + if (inst) + { + (void)broadcast_assistant_reset(inst); + } +} + +BT_CONN_CB_DEFINE(conn_callbacks) = +{ + .disconnected = disconnected_cb, +}; + +int bt_bap_broadcast_assistant_discover(struct bt_conn *conn) +{ + struct bap_broadcast_assistant_instance *inst; + struct bt_conn *ref; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + if (bt_audio_security_check(conn) != BT_ATT_ERR_SUCCESS) + { + LOG_DBG("Invalid conn %p for discovery", conn); + + return -EINVAL; + } + + inst = &broadcast_assistants[bt_conn_index(conn)]; + + /* Do not allow new discoveries while we are reading or writing */ + + if (atomic_test_and_set_bit(inst->flags, BAP_BA_FLAG_BUSY)) + { + LOG_DBG("Instance is busy"); + return -EBUSY; + } + + ref = bt_conn_ref(conn); + if (ref == NULL) + { + err = -ENOTCONN; + goto cleanup; + } + + err = broadcast_assistant_reset(inst); + if (err != 0) + { + LOG_DBG("Failed to reset broadcast assistant: %d", err); + bt_conn_unref(ref); + err = -ENOEXEC; + goto cleanup; + } + + /* Discover BASS on peer, setup handles and notify */ + + discover_init(inst); + + inst->disc_params.func = service_discover_func; + inst->disc_params.uuid = bass_uuid; + inst->disc_params.type = BT_GATT_DISCOVER_PRIMARY; + inst->disc_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; + inst->disc_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + + inst->conn = ref; + + err = bt_gatt_discover(conn, &inst->disc_params); + if (err != 0) + { + /* Report expected possible errors */ + + if (err != -ENOTCONN && err != -ENOMEM) + { + LOG_DBG("Unexpected err %d from bt_gatt_discover", err); + err = -ENOEXEC; + } + + bt_conn_unref(ref); + inst->conn = NULL; + goto cleanup; + } + + return 0; + +cleanup: + atomic_clear_bit(inst->flags, BAP_BA_FLAG_DISCOVER_IN_PROGRESS); + atomic_clear_bit(inst->flags, BAP_BA_FLAG_BUSY); + return err; +} + +/* TODO: naming is different from e.g. bt_vcp_vol_ctrl_cb_register */ + +int bt_bap_broadcast_assistant_register_cb( + struct bt_bap_broadcast_assistant_cb *cb) +{ + struct bt_bap_broadcast_assistant_cb *tmp; + + if (cb == NULL) + { + return -EINVAL; + } + + list_for_every_container(&broadcast_assistant_cbs, tmp, _node) + { + if (tmp == cb) + { + LOG_DBG("Already registered"); + return -EALREADY; + } + } + + list_add_tail(&broadcast_assistant_cbs, &cb->_node); + + return 0; +} + +int bt_bap_broadcast_assistant_unregister_cb( + struct bt_bap_broadcast_assistant_cb *cb) +{ + if (cb == NULL) + { + return -EINVAL; + } + + if (!list_find_and_remove(&broadcast_assistant_cbs, &cb->_node)) + { + return -EALREADY; + } + + return 0; +} + +int bt_bap_broadcast_assistant_scan_start(struct bt_conn *conn, + bool start_scan) +{ + struct bt_bap_bass_cp_scan_start *cp; + int err; + struct bap_broadcast_assistant_instance *inst; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + inst = inst_by_conn(conn); + if (inst == NULL) + { + return -EINVAL; + } + + if (inst->cp_handle == 0U) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(inst->flags, BAP_BA_FLAG_BUSY)) + { + /* Do not allow writes while we are discovering as the handles may + * change + */ + + LOG_DBG("instance busy"); + + return -EBUSY; + } + + /* TODO: Remove the start_scan parameter and support from the assistant */ + + if (start_scan) + { + static bool cb_registered; + + if (atomic_test_and_set_bit(inst->flags, BAP_BA_FLAG_SCANNING)) + { + LOG_DBG("Already scanning"); + + atomic_clear_bit(inst->flags, BAP_BA_FLAG_BUSY); + + return -EALREADY; + } + + if (!cb_registered) + { + bt_le_scan_cb_register(&scan_cb); + cb_registered = true; + } + + err = bt_le_scan_start(BT_LE_SCAN_PASSIVE, NULL); + if (err != 0) + { + LOG_DBG("Could not start scan (%d)", err); + + atomic_clear_bit(inst->flags, BAP_BA_FLAG_BUSY); + atomic_clear_bit(inst->flags, BAP_BA_FLAG_SCANNING); + + /* Report expected possible errors */ + + if (err == -EAGAIN) + { + return err; + } + + LOG_DBG("Unexpected err %d from bt_le_scan_start", err); + + return -ENOEXEC; + } + } + + /* Reset buffer before using */ + + net_buf_simple_reset(&inst->net_buf); + cp = net_buf_simple_add(&inst->net_buf, sizeof(*cp)); + + cp->opcode = BT_BAP_BASS_OP_SCAN_START; + + err = bt_bap_broadcast_assistant_common_cp(conn, &inst->net_buf); + if (err != 0 && start_scan) + { + /* bt_bap_broadcast_assistant_common_cp clears the busy flag on error */ + + err = bt_le_scan_stop(); + if (err != 0) + { + LOG_DBG("Could not stop scan (%d)", err); + + return -ENOEXEC; + } + + atomic_clear_bit(inst->flags, BAP_BA_FLAG_SCANNING); + } + + return err; +} + +int bt_bap_broadcast_assistant_scan_stop(struct bt_conn *conn) +{ + struct bt_bap_bass_cp_scan_stop *cp; + int err; + struct bap_broadcast_assistant_instance *inst; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + inst = inst_by_conn(conn); + if (inst == NULL) + { + return -EINVAL; + } + + if (inst->cp_handle == 0U) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(inst->flags, BAP_BA_FLAG_BUSY)) + { + LOG_DBG("instance busy"); + + return -EBUSY; + } + + if (atomic_test_bit(inst->flags, BAP_BA_FLAG_SCANNING)) + { + err = bt_le_scan_stop(); + if (err != 0) + { + LOG_DBG("Could not stop scan (%d)", err); + + atomic_clear_bit(inst->flags, BAP_BA_FLAG_BUSY); + + return err; + } + + atomic_clear_bit(inst->flags, BAP_BA_FLAG_SCANNING); + } + + /* Reset buffer before using */ + + net_buf_simple_reset(&inst->net_buf); + cp = net_buf_simple_add(&inst->net_buf, sizeof(*cp)); + + cp->opcode = BT_BAP_BASS_OP_SCAN_STOP; + + return bt_bap_broadcast_assistant_common_cp(conn, &inst->net_buf); +} + +static bool bis_syncs_unique_or_no_pref(uint32_t requested_bis_syncs, + uint32_t aggregated_bis_syncs) +{ + if (requested_bis_syncs == 0U || aggregated_bis_syncs == 0U) + { + return true; + } + + if (requested_bis_syncs == BT_BAP_BIS_SYNC_NO_PREF && + aggregated_bis_syncs == BT_BAP_BIS_SYNC_NO_PREF) + { + return true; + } + + return (requested_bis_syncs & aggregated_bis_syncs) != 0U; +} + +static bool valid_bis_sync_request(uint32_t requested_bis_syncs, + uint32_t aggregated_bis_syncs) +{ + /* Verify that the request BIS sync indexes are unique or no preference */ + + if (!bis_syncs_unique_or_no_pref(requested_bis_syncs, + aggregated_bis_syncs)) + { + LOG_DBG("Duplicate BIS index 0x%08x (aggregated %x)", + requested_bis_syncs, + aggregated_bis_syncs); + return false; + } + + if (requested_bis_syncs != BT_BAP_BIS_SYNC_NO_PREF && + aggregated_bis_syncs == BT_BAP_BIS_SYNC_NO_PREF) + { + LOG_DBG("Invalid BIS index 0x%08X mixing BT_BAP_BIS_SYNC_NO_PREF and " + "specific BIS", + requested_bis_syncs); + return false; + } + + if (!valid_bis_syncs(requested_bis_syncs)) + { + LOG_DBG("Invalid BIS sync: 0x%08X", requested_bis_syncs); + return false; + } + + return true; +} + +static bool valid_subgroup_params( + uint8_t pa_sync, const struct bt_bap_bass_subgroup subgroups[], + uint8_t num_subgroups) +{ + uint32_t aggregated_bis_syncs = 0U; + + for (uint8_t i = 0U; i < num_subgroups; i++) + { + /* BIS sync values of 0 and BT_BAP_BIS_SYNC_NO_PREF are allowed at any + * time, but any other values are only allowed if PA sync state is also + * set + */ + + if (pa_sync == 0U && + (subgroups[i].bis_sync != 0U && + subgroups[i].bis_sync != BT_BAP_BIS_SYNC_NO_PREF)) + { + LOG_DBG("[%u]: Only syncing to BIS is not allowed", i); + + return false; + } + + /* Verify that the request BIS sync indexes are unique or no preference + */ + + if (!valid_bis_sync_request(subgroups[i].bis_sync, + aggregated_bis_syncs)) + { + LOG_DBG("Invalid BIS Sync request[%d]", i); + + return false; + } + + /* Keep track of BIS sync values to ensure that we do not have + * duplicates + */ + + aggregated_bis_syncs |= subgroups[i].bis_sync; + +#if defined(CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE) + if (subgroups[i].metadata_len > + CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE) + { + LOG_DBG("[%u]: Invalid metadata_len: %u", i, + subgroups[i].metadata_len); + + return false; + } + } +#endif /* CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE */ + + return true; +} + +static bool valid_add_src_param( + const struct bt_bap_broadcast_assistant_add_src_param *param) +{ + if (param == NULL) + { + LOG_DBG("param is NULL"); + return false; + } + + if (param->addr.type > BT_ADDR_LE_RANDOM) + { + LOG_DBG("Invalid address type %u", param->addr.type); + return false; + } + + if (param->adv_sid > BT_GAP_SID_MAX) + { + LOG_DBG("Invalid adv_sid %u", param->adv_sid); + return false; + } + + if (!(param->pa_interval != BT_BAP_PA_INTERVAL_UNKNOWN) && + !IN_RANGE(param->pa_interval, BT_GAP_PER_ADV_MIN_INTERVAL, + BT_GAP_PER_ADV_MAX_INTERVAL)) + { + LOG_DBG("Invalid pa_interval 0x%04X", param->pa_interval); + return false; + } + + if (param->broadcast_id > BT_AUDIO_BROADCAST_ID_MAX) + { + LOG_DBG("Invalid broadcast_id 0x%08X", param->broadcast_id); + return false; + } + + if (param->num_subgroups != 0 && param->subgroups == NULL) + { + LOG_DBG("Subgroups are NULL when num_subgroups = %u", + param->num_subgroups); + return false; + } + + if (param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) + { + LOG_DBG("Too many subgroups %u/%u", param->num_subgroups, + CONFIG_BT_BAP_BASS_MAX_SUBGROUPS); + + return false; + } + + if (param->subgroups != NULL) + { + if (!valid_subgroup_params(param->pa_sync, param->subgroups, + param->num_subgroups)) + { + return false; + } + } + + return true; +} + +int bt_bap_broadcast_assistant_add_src(struct bt_conn *conn, + const struct bt_bap_broadcast_assistant_add_src_param *param) +{ + struct bt_bap_bass_cp_add_src *cp; + struct bap_broadcast_assistant_instance *inst; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + inst = inst_by_conn(conn); + + if (inst == NULL) + { + return -EINVAL; + } + + /* Check if this operation would result in a duplicate before proceeding */ + + if (broadcast_src_is_duplicate(inst, param->broadcast_id, param->adv_sid, + param->addr.type)) + { + LOG_DBG("Broadcast source already exists for broadcast_id 0x%06X, sid " + "0x%02X and " + "type 0x%02X", + param->broadcast_id, param->adv_sid, param->addr.type); + + return -EINVAL; + } + + if (!valid_add_src_param(param)) + { + return -EINVAL; + } + + if (inst->cp_handle == 0U) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(inst->flags, BAP_BA_FLAG_BUSY)) + { + LOG_DBG("instance busy"); + + return -EBUSY; + } + + /* Reset buffer before using */ + + net_buf_simple_reset(&inst->net_buf); + cp = net_buf_simple_add(&inst->net_buf, sizeof(*cp)); + + cp->opcode = BT_BAP_BASS_OP_ADD_SRC; + cp->adv_sid = param->adv_sid; + bt_addr_le_copy(&cp->addr, ¶m->addr); + + sys_put_le24(param->broadcast_id, cp->broadcast_id); + + if (param->pa_sync) + { + if (past_available(conn, ¶m->addr, param->adv_sid)) + { + cp->pa_sync = BT_BAP_BASS_PA_REQ_SYNC_PAST; + } + else + { + cp->pa_sync = BT_BAP_BASS_PA_REQ_SYNC; + } + } + else + { + cp->pa_sync = BT_BAP_BASS_PA_REQ_NO_SYNC; + } + + cp->pa_interval = sys_cpu_to_le16(param->pa_interval); + + cp->num_subgroups = param->num_subgroups; + for (int i = 0; i < param->num_subgroups; i++) + { + struct bt_bap_bass_cp_subgroup *subgroup; + const size_t subgroup_size = + sizeof(subgroup->bis_sync) + sizeof(subgroup->metadata_len) + + param->subgroups[i].metadata_len; + + if (inst->net_buf.len + subgroup_size > inst->net_buf.size) + { + LOG_DBG("MTU is too small to send %zu octets", + inst->net_buf.len + subgroup_size); + + /* TODO: Validate parameters before setting the busy flag to reduce + * cleanup + */ + + atomic_clear_bit(inst->flags, BAP_BA_FLAG_BUSY); + + return -EINVAL; + } + + subgroup = net_buf_simple_add(&inst->net_buf, subgroup_size); + + subgroup->bis_sync = param->subgroups[i].bis_sync; + +#if defined(CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE) + if (param->subgroups[i].metadata_len != 0) + { + (void)memcpy(subgroup->metadata, param->subgroups[i].metadata, + param->subgroups[i].metadata_len); + subgroup->metadata_len = param->subgroups[i].metadata_len; + } + else + { + subgroup->metadata_len = 0U; + } +#else + subgroup->metadata_len = 0U; +#endif /* CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE */ + } + + return bt_bap_broadcast_assistant_common_cp(conn, &inst->net_buf); +} + +static bool valid_add_mod_param( + const struct bt_bap_broadcast_assistant_mod_src_param *param) +{ + if (param == NULL) + { + LOG_DBG("param is NULL"); + return false; + } + + if (!(param->pa_interval != BT_BAP_PA_INTERVAL_UNKNOWN) && + !IN_RANGE(param->pa_interval, BT_GAP_PER_ADV_MIN_INTERVAL, + BT_GAP_PER_ADV_MAX_INTERVAL)) + { + LOG_DBG("Invalid pa_interval 0x%04X", param->pa_interval); + return false; + } + + if (param->num_subgroups != 0 && param->subgroups == NULL) + { + LOG_DBG("Subgroups are NULL when num_subgroups = %u", + param->num_subgroups); + return false; + } + + if (param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) + { + LOG_DBG("Too many subgroups %u/%u", param->num_subgroups, + CONFIG_BT_BAP_BASS_MAX_SUBGROUPS); + + return false; + } + + if (param->subgroups != NULL) + { + if (!valid_subgroup_params(param->pa_sync, param->subgroups, + param->num_subgroups)) + { + return false; + } + } + + return true; +} + +int bt_bap_broadcast_assistant_mod_src(struct bt_conn *conn, + const struct bt_bap_broadcast_assistant_mod_src_param *param) +{ + struct bt_bap_bass_cp_mod_src *cp; + bool known_recv_state; + bool past_avail; + struct bap_broadcast_assistant_instance *inst; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + if (!valid_add_mod_param(param)) + { + return -EINVAL; + } + + inst = inst_by_conn(conn); + if (inst == NULL) + { + return -EINVAL; + } + + if (inst->cp_handle == 0U) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(inst->flags, BAP_BA_FLAG_BUSY)) + { + LOG_DBG("instance busy"); + + return -EBUSY; + } + + /* Reset buffer before using */ + + net_buf_simple_reset(&inst->net_buf); + cp = net_buf_simple_add(&inst->net_buf, sizeof(*cp)); + + cp->opcode = BT_BAP_BASS_OP_MOD_SRC; + cp->src_id = param->src_id; + + /* Since we do not have the address and SID in this function, we + * rely on cached PAST support information + */ + + known_recv_state = false; + past_avail = false; + for (size_t i = 0U; i < ARRAY_SIZE(inst->recv_states); i++) + { + if (inst->recv_states[i].src_id == param->src_id) + { + known_recv_state = true; + past_avail = inst->recv_states[i].past_avail; + break; + } + } + + if (!known_recv_state) + { + LOG_WRN("Attempting to modify unknown receive state: %u", + param->src_id); + } + + if (param->pa_sync) + { + if (known_recv_state && past_avail) + { + cp->pa_sync = BT_BAP_BASS_PA_REQ_SYNC_PAST; + } + else + { + cp->pa_sync = BT_BAP_BASS_PA_REQ_SYNC; + } + } + else + { + cp->pa_sync = BT_BAP_BASS_PA_REQ_NO_SYNC; + } + + cp->pa_interval = sys_cpu_to_le16(param->pa_interval); + + cp->num_subgroups = param->num_subgroups; + for (int i = 0; i < param->num_subgroups; i++) + { + struct bt_bap_bass_cp_subgroup *subgroup; + const size_t subgroup_size = + sizeof(subgroup->bis_sync) + sizeof(subgroup->metadata_len) + + param->subgroups[i].metadata_len; + + if (inst->net_buf.len + subgroup_size > inst->net_buf.size) + { + LOG_DBG("MTU is too small to send %zu octets", + inst->net_buf.len + subgroup_size); + + /* TODO: Validate parameters before setting the busy flag to reduce + * cleanup + */ + + atomic_clear_bit(inst->flags, BAP_BA_FLAG_BUSY); + + return -EINVAL; + } + + subgroup = net_buf_simple_add(&inst->net_buf, subgroup_size); + + subgroup->bis_sync = param->subgroups[i].bis_sync; + +#if defined(CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE) + if (param->subgroups[i].metadata_len != 0) + { + (void)memcpy(subgroup->metadata, param->subgroups[i].metadata, + param->subgroups[i].metadata_len); + subgroup->metadata_len = param->subgroups[i].metadata_len; + } + else + { + subgroup->metadata_len = 0U; + } +#else + subgroup->metadata_len = 0U; +#endif /* CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE */ + } + + return bt_bap_broadcast_assistant_common_cp(conn, &inst->net_buf); +} + +int bt_bap_broadcast_assistant_set_broadcast_code(struct bt_conn *conn, + uint8_t src_id, + const uint8_t broadcast_code[BT_ISO_BROADCAST_CODE_SIZE]) +{ + struct bt_bap_bass_cp_broadcase_code *cp; + struct bap_broadcast_assistant_instance *inst; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + inst = inst_by_conn(conn); + if (inst == NULL) + { + return -EINVAL; + } + + if (inst->cp_handle == 0U) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(inst->flags, BAP_BA_FLAG_BUSY)) + { + LOG_DBG("instance busy"); + + return -EBUSY; + } + + /* Reset buffer before using */ + + net_buf_simple_reset(&inst->net_buf); + cp = net_buf_simple_add(&inst->net_buf, sizeof(*cp)); + + cp->opcode = BT_BAP_BASS_OP_BROADCAST_CODE; + cp->src_id = src_id; + + (void)memcpy(cp->broadcast_code, broadcast_code, + BT_ISO_BROADCAST_CODE_SIZE); + + LOG_HEXDUMP_DBG(cp->broadcast_code, BT_ISO_BROADCAST_CODE_SIZE, + "broadcast code:"); + + return bt_bap_broadcast_assistant_common_cp(conn, &inst->net_buf); +} + +int bt_bap_broadcast_assistant_rem_src(struct bt_conn *conn, uint8_t src_id) +{ + struct bt_bap_bass_cp_rem_src *cp; + struct bap_broadcast_assistant_instance *inst; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + inst = inst_by_conn(conn); + if (inst == NULL) + { + return -EINVAL; + } + + if (inst->cp_handle == 0U) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(inst->flags, BAP_BA_FLAG_BUSY)) + { + LOG_DBG("instance busy"); + + return -EBUSY; + } + + /* Reset buffer before using */ + + net_buf_simple_reset(&inst->net_buf); + cp = net_buf_simple_add(&inst->net_buf, sizeof(*cp)); + + cp->opcode = BT_BAP_BASS_OP_REM_SRC; + cp->src_id = src_id; + + return bt_bap_broadcast_assistant_common_cp(conn, &inst->net_buf); +} + +static int read_recv_state(struct bap_broadcast_assistant_instance *inst, + uint8_t idx) +{ + int err; + + inst->read_params.func = read_recv_state_cb; + inst->read_params.handle_count = 1U; + inst->read_params.single.handle = inst->recv_state_handles[idx]; + + err = bt_gatt_read(inst->conn, &inst->read_params); + if (err != 0) + { + (void)memset(&inst->read_params, 0, sizeof(inst->read_params)); + } + + return err; +} + +int bt_bap_broadcast_assistant_read_recv_state(struct bt_conn *conn, + uint8_t idx) +{ + int err; + struct bap_broadcast_assistant_instance *inst; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + inst = inst_by_conn(conn); + if (inst == NULL) + { + return -EINVAL; + } + + if (idx >= ARRAY_SIZE(inst->recv_state_handles)) + { + LOG_DBG("Invalid idx: %u", idx); + + return -EINVAL; + } + + if (inst->recv_state_handles[idx] == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(inst->flags, BAP_BA_FLAG_BUSY)) + { + LOG_DBG("instance busy"); + + return -EBUSY; + } + + err = read_recv_state(inst, idx); + if (err != 0) + { + (void)memset(&inst->read_params, 0, sizeof(inst->read_params)); + atomic_clear_bit(inst->flags, BAP_BA_FLAG_BUSY); + } + + return err; +} diff --git a/libs/zblue/audio/bap_broadcast_sink.c b/libs/zblue/audio/bap_broadcast_sink.c new file mode 100644 index 0000000000000..77681bc751b4a --- /dev/null +++ b/libs/zblue/audio/bap_broadcast_sink.c @@ -0,0 +1,1858 @@ +/**************************************************************************** + * libs/zblue/audio/bap_broadcast_sink.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Bluetooth Audio Broadcast Sink */ + +/* Copyright (c) 2021-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../host/conn_internal.h" +#include "../host/iso_internal.h" + +#include "audio_internal.h" +#include "bap_endpoint.h" +#include "bap_iso.h" +#include "pacs_internal.h" + +LOG_MODULE_REGISTER(bt_bap_broadcast_sink, + CONFIG_BT_BAP_BROADCAST_SINK_LOG_LEVEL); + +#include "common/bt_str.h" + +#define PA_SYNC_INTERVAL_TO_TIMEOUT_RATIO 20 /* Set the timeout relative to interval */ +#define BROADCAST_SYNC_MIN_INDEX (BIT(1U)) + +static struct bt_bap_ep broadcast_sink_eps[CONFIG_BT_BAP_BROADCAST_SNK_COUNT] + [CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT]; +static struct bt_bap_broadcast_sink + broadcast_sinks[CONFIG_BT_BAP_BROADCAST_SNK_COUNT]; + +struct codec_cap_lookup_id_data +{ + uint8_t id; + const struct bt_audio_codec_cap *codec_cap; +}; + +static struct list_node sink_cbs = LIST_INITIAL_VALUE(sink_cbs); + +/* The mod_src_param is and shall only be used by the BT RX thread. It is + * statically defined due to the size of it, and that it's configurable in + * size, and can cause stack overflow issues otherwise + */ + +static struct bt_bap_scan_delegator_mod_src_param mod_src_param; + +static void broadcast_sink_cleanup(struct bt_bap_broadcast_sink *sink); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static bool find_recv_state_by_src_id_cb( + const struct bt_bap_scan_delegator_recv_state *recv_state, void *user_data) +{ + const struct bt_bap_broadcast_sink *sink = user_data; + + if (atomic_test_bit(sink->flags, + BT_BAP_BROADCAST_SINK_FLAG_SRC_ID_VALID) && + sink->bass_src_id == recv_state->src_id) + { + return true; + } + + return false; +} + +static bool +find_recv_state_by_sink_fields_cb( + const struct bt_bap_scan_delegator_recv_state *recv_state, + void *user_data) +{ + const struct bt_bap_broadcast_sink *sink = user_data; + struct bt_le_per_adv_sync_info sync_info; + int err; + + err = bt_le_per_adv_sync_get_info(sink->pa_sync, &sync_info); + if (err != 0) + { + LOG_DBG("Failed to get sync info: %d", err); + + return false; + } + + /* BASS 3.1.1.4 + * + * If the server attempts to synchronize with the PA, the server may + * determine the Advertiser_Address to be used in the Periodic Advertising + * Synchronization Establishment procedure by: + * + * - Comparing the Adv_SID written by the client to the Adv_SID subfield of + * the ADI field of ADV_EXT_IND PDUs transmitted by the Broadcast Source. + * - Comparing the Broadcast_ID written by the client to the Broadcast_ID + * in the AdvData field of AUX_ADV_IND PDUs transmitted by the Broadcast + * Source. + * + * Since the address and type may be omitted when synchronizing to the PA, + * we cannot use neither the address or type when looking up a receive + * state based on a PA sync, as the address and type from the PA may not + * match. The only values that we can be sure about are the SID and + * Broadcast ID. + */ + + return recv_state->adv_sid == sync_info.sid && + recv_state->broadcast_id == sink->broadcast_id; +}; + +static void update_recv_state_big_synced( + const struct bt_bap_broadcast_sink *sink) +{ + const struct bt_bap_scan_delegator_recv_state *recv_state; + int err; + + recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_src_id_cb, + (void *)sink); + if (recv_state == NULL) + { + LOG_WRN("Failed to find receive state for sink %p", sink); + + return; + } + + (void)memset(&mod_src_param, 0, sizeof(mod_src_param)); + + mod_src_param.num_subgroups = sink->subgroup_count; + for (uint8_t i = 0U; i < sink->subgroup_count; i++) + { + struct bt_bap_bass_subgroup *subgroup_param = + &mod_src_param.subgroups[i]; + const struct bt_bap_broadcast_sink_subgroup *sink_subgroup = + &sink->subgroups[i]; + + /* Set the bis_sync value to the indexes available per subgroup */ + + subgroup_param->bis_sync = + sink_subgroup->bis_indexes & sink->indexes_bitfield; + } + + if (recv_state->encrypt_state == BT_BAP_BIG_ENC_STATE_BCODE_REQ || + recv_state->encrypt_state == BT_BAP_BIG_ENC_STATE_BAD_CODE) + { + mod_src_param.encrypt_state = BT_BAP_BIG_ENC_STATE_DEC; + } + else + { + mod_src_param.encrypt_state = recv_state->encrypt_state; + } + + /* Copy existing unchanged data */ + + mod_src_param.src_id = recv_state->src_id; + mod_src_param.broadcast_id = recv_state->broadcast_id; + for (uint8_t i = 0U; i < recv_state->num_subgroups; i++) + { + struct bt_bap_bass_subgroup *subgroup_param = + &mod_src_param.subgroups[i]; + + subgroup_param->metadata_len = recv_state->subgroups[i].metadata_len; + (void)memcpy(subgroup_param->metadata, + recv_state->subgroups[i].metadata, + subgroup_param->metadata_len); + } + + err = bt_bap_scan_delegator_mod_src(&mod_src_param); + if (err != 0) + { + LOG_WRN("Failed to modify Receive State for sink %p: %d", sink, err); + } +} + +static void update_recv_state_big_cleared( + const struct bt_bap_broadcast_sink *sink, uint8_t reason) +{ + const struct bt_bap_scan_delegator_recv_state *recv_state; + int err; + + recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_src_id_cb, + (void *)sink); + if (recv_state == NULL) + { + /* This is likely due to the receive state being removed while we are + * BIG synced + */ + + LOG_DBG("Could not find receive state for sink %p", sink); + + return; + } + + (void)memset(&mod_src_param, 0, sizeof(mod_src_param)); + + if ((recv_state->encrypt_state == BT_BAP_BIG_ENC_STATE_BCODE_REQ || + recv_state->encrypt_state == BT_BAP_BIG_ENC_STATE_DEC) && + reason == BT_HCI_ERR_TERM_DUE_TO_MIC_FAIL) + { + /* Sync failed due to bad broadcast code */ + + mod_src_param.encrypt_state = BT_BAP_BIG_ENC_STATE_BAD_CODE; + } + else + { + mod_src_param.encrypt_state = BT_BAP_BIG_ENC_STATE_NO_ENC; + } + + /* If we are currently not synced (bis_sync == 0U) then we set the BIS sync + * to BT_BAP_BIS_SYNC_FAILED to indicate a sync failure, unless the reason + * is BT_HCI_ERR_TERM_DUE_TO_MIC_FAIL (indicating a bad broadcast code). We + * treat this as a + * special case due to current open errata + * https://bluetooth.atlassian.net/browse/ES-28435 + * and https://bluetooth.atlassian.net/browse/ES-28482 where the expected + * behavior is not + * properly defined but the qualification tests expect the BIS sync value + * to be set to 0 in case of a bad broadcast code. + * + * If we are already synced and this is called, then that indicates a sync + * lost, in which case we set the BIS sync to 0. + */ + + for (uint8_t i = 0U; i < recv_state->num_subgroups; i++) + { + if (recv_state->subgroups[i].bis_sync == 0U) + { + if (reason == BT_HCI_ERR_TERM_DUE_TO_MIC_FAIL) + { + mod_src_param.subgroups[i].bis_sync = 0U; + } + else + { + mod_src_param.subgroups[i].bis_sync = BT_BAP_BIS_SYNC_FAILED; + } + } + else + { + mod_src_param.subgroups[i].bis_sync = 0U; + } + } + + /* Copy existing unchanged data */ + + mod_src_param.num_subgroups = recv_state->num_subgroups; + mod_src_param.src_id = recv_state->src_id; + mod_src_param.broadcast_id = recv_state->broadcast_id; + for (uint8_t i = 0U; i < recv_state->num_subgroups; i++) + { + struct bt_bap_bass_subgroup *subgroup_param = + &mod_src_param.subgroups[i]; + + subgroup_param->metadata_len = recv_state->subgroups[i].metadata_len; + (void)memcpy(subgroup_param->metadata, + recv_state->subgroups[i].metadata, + subgroup_param->metadata_len); + } + + err = bt_bap_scan_delegator_mod_src(&mod_src_param); + if (err != 0) + { + LOG_WRN("Failed to modify Receive State for sink %p: %d", sink, err); + } +} + +static void broadcast_sink_clear_big(struct bt_bap_broadcast_sink *sink, + uint8_t reason) +{ + sink->big = NULL; + + update_recv_state_big_cleared(sink, reason); +} + +static struct bt_bap_broadcast_sink *broadcast_sink_lookup_iso_chan( + const struct bt_iso_chan *chan) +{ + for (size_t i = 0U; i < ARRAY_SIZE(broadcast_sinks); i++) + { + for (uint8_t j = 0U; j < broadcast_sinks[i].stream_count; j++) + { + if (broadcast_sinks[i].bis[j].chan == chan) + { + return &broadcast_sinks[i]; + } + } + } + + return NULL; +} + +static void broadcast_sink_set_ep_state(struct bt_bap_ep *ep, + enum bt_bap_ep_state state) +{ + uint8_t old_state; + + old_state = ep->state; + + LOG_DBG("ep %p id 0x%02x %s -> %s", ep, ep->id, + bt_bap_ep_state_str(old_state), + bt_bap_ep_state_str(state)); + + switch (old_state) + { + case BT_BAP_EP_STATE_IDLE: + if (state != BT_BAP_EP_STATE_QOS_CONFIGURED) + { + LOG_DBG("Invalid broadcast sync endpoint state transition"); + return; + } + break; + case BT_BAP_EP_STATE_QOS_CONFIGURED: + if (state != BT_BAP_EP_STATE_IDLE && state != + BT_BAP_EP_STATE_STREAMING) + { + LOG_DBG("Invalid broadcast sync endpoint state transition"); + return; + } + break; + case BT_BAP_EP_STATE_STREAMING: + if (state != BT_BAP_EP_STATE_IDLE) + { + LOG_DBG("Invalid broadcast sync endpoint state transition"); + return; + } + break; + default: + LOG_ERR("Invalid broadcast sync endpoint state: %s", + bt_bap_ep_state_str(old_state)); + return; + } + + ep->state = state; + + if (state == BT_BAP_EP_STATE_IDLE) + { + struct bt_bap_stream *stream = ep->stream; + + if (stream != NULL) + { + bt_bap_iso_unbind_ep(ep->iso, ep); + stream->iso = NULL; + stream->ep = NULL; + stream->codec_cfg = NULL; + stream->group = NULL; + ep->stream = NULL; + } + } +} + +static void broadcast_sink_iso_recv( + struct bt_iso_chan *chan, const struct bt_iso_recv_info *info, + struct net_buf *buf) +{ + struct bt_bap_iso *iso = CONTAINER_OF(chan, struct bt_bap_iso, chan); + const struct bt_bap_stream_ops *ops; + struct bt_bap_stream *stream; + struct bt_bap_ep *ep = iso->rx.ep; + size_t buf_len; + + if (ep == NULL) + { + LOG_ERR("iso %p not bound with ep", chan); + return; + } + + stream = ep->stream; + if (stream == NULL) + { + LOG_ERR("No stream for ep %p", ep); + return; + } + + ops = stream->ops; + + buf_len = net_buf_frags_len(buf); + if (IS_ENABLED(CONFIG_BT_BAP_DEBUG_STREAM_DATA)) + { + LOG_DBG("stream %p ep %p len %zu", stream, stream->ep, buf_len); + } + + if (buf_len > stream->qos->sdu) + { + LOG_WRN("Received %u octets but stream %p was only configured for %u", + buf_len, stream, + stream->qos->sdu); + } + + if (ops != NULL && ops->recv != NULL) + { + ops->recv(stream, info, buf); + } + else + { + LOG_WRN("No callback for recv set"); + } +} + +static bool broadcast_sink_is_in_state(struct bt_bap_broadcast_sink *sink, + enum bt_bap_ep_state state) +{ + struct bt_bap_stream *stream; + + if (sink == NULL) + { + LOG_DBG("sink is NULL"); + + return state == BT_BAP_EP_STATE_IDLE; + } + + if (list_is_empty(&sink->streams)) + { + LOG_DBG("Sink does not have any streams"); + + return state == BT_BAP_EP_STATE_IDLE; + } + + list_for_every_container(&sink->streams, stream, _node) + { + if (stream->ep != NULL && stream->ep->state != state) + { + return false; + } + } + + return true; +} + +static void broadcast_sink_iso_connected(struct bt_iso_chan *chan) +{ + struct bt_bap_iso *iso = CONTAINER_OF(chan, struct bt_bap_iso, chan); + const struct bt_bap_stream_ops *ops; + struct bt_bap_broadcast_sink *sink; + struct bt_bap_stream *stream; + struct bt_bap_ep *ep = iso->rx.ep; + + if (ep == NULL) + { + LOG_ERR("iso %p not bound with ep", chan); + return; + } + + stream = ep->stream; + if (stream == NULL) + { + LOG_ERR("No stream for ep %p", ep); + return; + } + + LOG_DBG("stream %p", stream); + + ops = stream->ops; + if (ops != NULL && ops->connected != NULL) + { + ops->connected(stream); + } + + sink = broadcast_sink_lookup_iso_chan(chan); + if (sink == NULL) + { + LOG_ERR("Could not lookup sink by iso %p", chan); + return; + } + + broadcast_sink_set_ep_state(ep, BT_BAP_EP_STATE_STREAMING); + + /* Setup the ISO data path */ + + bt_bap_setup_iso_data_path(stream); + + if (ops != NULL && ops->started != NULL) + { + ops->started(stream); + } + + if (broadcast_sink_is_in_state(sink, BT_BAP_EP_STATE_STREAMING)) + { + update_recv_state_big_synced(sink); + } +} + +static void broadcast_sink_iso_disconnected(struct bt_iso_chan *chan, + uint8_t reason) +{ + struct bt_bap_iso *iso = CONTAINER_OF(chan, struct bt_bap_iso, chan); + const struct bt_bap_stream_ops *ops; + struct bt_bap_stream *stream; + struct bt_bap_ep *ep = iso->rx.ep; + struct bt_bap_broadcast_sink *sink; + + if (ep == NULL) + { + LOG_ERR("iso %p not bound with ep", chan); + return; + } + + stream = ep->stream; + if (stream == NULL) + { + LOG_ERR("No stream for ep %p", ep); + return; + } + + LOG_DBG("stream %p ep %p reason 0x%02x", stream, ep, reason); + + ops = stream->ops; + if (ops != NULL && ops->disconnected != NULL) + { + ops->disconnected(stream, reason); + } + + broadcast_sink_set_ep_state(ep, BT_BAP_EP_STATE_IDLE); + + sink = broadcast_sink_lookup_iso_chan(chan); + if (sink == NULL) + { + LOG_ERR("Could not lookup sink by iso %p", chan); + } + else + { + if (!list_find_and_remove(&sink->streams, &stream->_node)) + { + LOG_DBG("Could not find and remove stream %p from sink %p", stream, + sink); + } + } + + if (ops != NULL && ops->stopped != NULL) + { + ops->stopped(stream, reason); + } +} + +static struct bt_iso_chan_ops broadcast_sink_iso_ops = +{ + .recv = broadcast_sink_iso_recv, + .connected = broadcast_sink_iso_connected, + .disconnected = broadcast_sink_iso_disconnected, +}; + +static struct bt_bap_broadcast_sink *broadcast_sink_free_get(void) +{ + /* Find free entry */ + + for (int i = 0; i < ARRAY_SIZE(broadcast_sinks); i++) + { + if (!atomic_test_bit(broadcast_sinks[i].flags, + BT_BAP_BROADCAST_SINK_FLAG_INITIALIZED)) + { + broadcast_sinks[i].index = i; + broadcast_sinks[i].broadcast_id = BT_BAP_INVALID_BROADCAST_ID; + + return &broadcast_sinks[i]; + } + } + + return NULL; +} + +static struct bt_bap_broadcast_sink *broadcast_sink_get_by_pa( + struct bt_le_per_adv_sync *sync) +{ + for (int i = 0; i < ARRAY_SIZE(broadcast_sinks); i++) + { + if (broadcast_sinks[i].pa_sync == sync) + { + return &broadcast_sinks[i]; + } + } + + return NULL; +} + +static struct bt_bap_broadcast_sink *broadcast_sink_get_by_big( + const struct bt_iso_big *big) +{ + for (size_t i = 0U; i < ARRAY_SIZE(broadcast_sinks); i++) + { + if (broadcast_sinks[i].big == big) + { + return &broadcast_sinks[i]; + } + } + + return NULL; +} + +static void broadcast_sink_add_src(struct bt_bap_broadcast_sink *sink) +{ + struct bt_bap_scan_delegator_add_src_param add_src_param; + struct bt_le_per_adv_sync_info sync_info; + int err; + + err = bt_le_per_adv_sync_get_info(sink->pa_sync, &sync_info); + DEBUGASSERT(err == 0); + + bt_addr_le_copy(&add_src_param.addr, &sync_info.addr); + add_src_param.sid = sync_info.sid; + + /* When a broadcast sink is created we always assume the PA sync provided + * is synced + */ + + add_src_param.pa_state = BT_BAP_PA_STATE_SYNCED; + add_src_param.broadcast_id = sink->broadcast_id; + + /* Will be updated when we receive the BASE */ + + add_src_param.encrypt_state = BT_BAP_BIG_ENC_STATE_NO_ENC; + add_src_param.num_subgroups = 0U; + + err = bt_bap_scan_delegator_add_src(&add_src_param); + if (err < 0) + { + LOG_WRN("Failed to add sync as Receive State for sink %p: %d", sink, + err); + } + else + { + sink->bass_src_id = (uint8_t)err; + atomic_set_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_SRC_ID_VALID); + } +} + +static bool base_subgroup_meta_cb( + const struct bt_bap_base_subgroup *subgroup, void *user_data) +{ + struct bt_bap_bass_subgroup *subgroup_param; + uint8_t *meta; + int ret; + + ARG_UNUSED(user_data); + + if (mod_src_param.num_subgroups == ARRAY_SIZE(mod_src_param.subgroups)) + { + return false; + } + + subgroup_param = &mod_src_param.subgroups[mod_src_param.num_subgroups]; + + ret = bt_bap_base_get_subgroup_codec_meta(subgroup, &meta); + if (ret < 0) + { + return false; + } + + if (ret <= sizeof(subgroup_param->metadata)) + { + subgroup_param->metadata_len = (uint8_t)ret; + (void)memcpy(subgroup_param->metadata, meta, + subgroup_param->metadata_len); + } + else + { + /* If we cannot store the metadata, we just omit it. + * BASS section 3.2.1.10 Metadata_Length field states "the server may + * write the length of any Metadata parameters for each subgroup to the + * Metadata_Length field" + */ + + subgroup_param->metadata_len = 0U; + } + + mod_src_param.num_subgroups++; + + return true; +} + +static int update_recv_state_base_copy_meta(const struct bt_bap_base *base) +{ + int err; + + err = bt_bap_base_foreach_subgroup(base, base_subgroup_meta_cb, NULL); + if (err != 0) + { + LOG_DBG("Failed to parse subgroups: %d", err); + return err; + } + + return 0; +} + +static void update_recv_state_base(const struct bt_bap_broadcast_sink *sink, + const struct bt_bap_base *base) +{ + const struct bt_bap_scan_delegator_recv_state *recv_state; + int err; + + recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_src_id_cb, + (void *)sink); + if (recv_state == NULL) + { + LOG_WRN("Failed to find receive state for sink %p", sink); + + return; + } + + (void)memset(&mod_src_param, 0, sizeof(mod_src_param)); + + /* Will set the mod_src_param.num_subgroups and subgroup-related parameters + */ + + err = update_recv_state_base_copy_meta(base); + if (err != 0) + { + LOG_WRN_RATELIMIT("Failed to parse all subgroups from BASE for sink " + "%p: %d (%u != %d)", + sink, + err, + mod_src_param.num_subgroups, + bt_bap_base_get_subgroup_count( + base)); + return; + } + + /* Copy existing unchanged data */ + + mod_src_param.src_id = recv_state->src_id; + mod_src_param.encrypt_state = recv_state->encrypt_state; + mod_src_param.broadcast_id = recv_state->broadcast_id; + + for (uint8_t i = 0U; i < sink->subgroup_count; i++) + { + struct bt_bap_bass_subgroup *subgroup_param = + &mod_src_param.subgroups[i]; + + /* Leave the bis_sync unchanged */ + + subgroup_param->bis_sync = recv_state->subgroups[i].bis_sync; + } + + err = bt_bap_scan_delegator_mod_src(&mod_src_param); + if (err != 0) + { + LOG_WRN("Failed to modify Receive State for sink %p: %d", sink, err); + } +} + +static bool base_subgroup_bis_count_cb( + const struct bt_bap_base_subgroup *subgroup, void *user_data) +{ + uint8_t *bis_cnt = user_data; + int ret; + + ret = bt_bap_base_get_subgroup_bis_count(subgroup); + if (ret < 0) + { + return false; + } + + *bis_cnt += (uint8_t)ret; + + return true; +} + +static int base_get_bis_count(const struct bt_bap_base *base) +{ + uint8_t bis_cnt = 0U; + int err; + + err = bt_bap_base_foreach_subgroup(base, base_subgroup_bis_count_cb, + &bis_cnt); + if (err != 0) + { + LOG_DBG("Failed to parse subgroups: %d", err); + return err; + } + + return bis_cnt; +} + +static bool base_decode_subgroup_bis_cb( + const struct bt_bap_base_subgroup_bis *bis, void *user_data) +{ + uint32_t *base_bis_index_bitfield = user_data; + + *base_bis_index_bitfield |= BT_ISO_BIS_INDEX_BIT(bis->index); + + return true; +} + +static bool base_decode_subgroup_cb( + const struct bt_bap_base_subgroup *subgroup, void *user_data) +{ + struct bt_bap_broadcast_sink *sink = + (struct bt_bap_broadcast_sink *)user_data; + const struct bt_audio_codec_cap *codec_cap; + struct bt_audio_codec_cfg codec_cfg; + struct bt_pac_codec codec_id; + int ret; + + if (sink->subgroup_count == ARRAY_SIZE(sink->subgroups)) + { + return false; + } + + uint32_t *subgroup_bis_indexes = + &sink->subgroups[sink->subgroup_count].bis_indexes; + + *subgroup_bis_indexes = 0U; + + ret = bt_bap_base_subgroup_codec_to_codec_cfg(subgroup, &codec_cfg); + if (ret < 0) + { + LOG_DBG("Could not store codec_cfg: %d", ret); + return false; + } + + /* Lookup and assign path_id based on capabilities */ + + codec_id.id = codec_cfg.id; + codec_id.cid = codec_cfg.cid; + codec_id.vid = codec_cfg.vid; + + codec_cap = bt_pacs_get_codec_cap(BT_AUDIO_DIR_SINK, &codec_id); + if (codec_cap == NULL) + { + LOG_DBG("Codec with id 0x%02x cid 0x%04x and vid 0x%04x is not " + "supported by our " + "capabilities", + codec_id.id, codec_id.cid, codec_id.vid); + } + else + { + ret = bt_bap_base_subgroup_foreach_bis(subgroup, + base_decode_subgroup_bis_cb, + subgroup_bis_indexes); + + if (ret != 0) + { + LOG_DBG("Could not parse BISes: %d", ret); + return false; + } + + sink->valid_indexes_bitfield |= *subgroup_bis_indexes; + } + + sink->subgroup_count++; + + return true; +} + +static bool pa_decode_base(struct bt_data *data, void *user_data) +{ + struct bt_bap_broadcast_sink *sink = + (struct bt_bap_broadcast_sink *)user_data; + const struct bt_bap_base *base = bt_bap_base_get_base_from_ad(data); + struct bt_bap_broadcast_sink_cb *listener; + int base_size; + + /* Base is NULL if the data does not contain a valid BASE */ + + if (base == NULL) + { + return true; + } + + /* We provide the BASE without the service data UUID */ + + base_size = bt_bap_base_get_size(base); + if (base_size != sink->base_size || memcmp(base, sink->base, + base_size) != 0) + { + /* New BASE, parse */ + + if (atomic_test_bit(sink->flags, + BT_BAP_BROADCAST_SINK_FLAG_BIGINFO_RECEIVED)) + { + int ret; + + ret = base_get_bis_count(base); + + if (ret < 0) + { + LOG_DBG_RATELIMIT("Invalid BASE: %d", ret); + return false; + } + else if (ret != sink->biginfo.num_bis) + { + LOG_DBG_RATELIMIT("BASE contains different amount of BIS (%u) " + "than reported " + "by BIGInfo (%u)", + ret, sink->biginfo.num_bis); + return false; + } + } + + /* Store newest BASE info until we are BIG synced */ + + if (sink->big == NULL) + { + int err; + + sink->subgroup_count = 0U; + sink->valid_indexes_bitfield = 0U; + + err = bt_bap_base_foreach_subgroup(base, base_decode_subgroup_cb, + sink); + if (err != 0) + { + LOG_WRN_RATELIMIT("Failed to parse all subgroups for sink %p: " + "%d (%u != %d)", + sink, + err, sink->subgroup_count, + bt_bap_base_get_subgroup_count(base)); + return false; + } + + LOG_DBG("Updating BASE for sink %p with %d subgroups\n", sink, + sink->subgroup_count); + + memcpy(sink->base, base, base_size); + sink->base_size = base_size; + } + + /* Metadata may change after syncing, so parse that regardless of + * `sink->big` + */ + + if (atomic_test_bit(sink->flags, + BT_BAP_BROADCAST_SINK_FLAG_SRC_ID_VALID)) + { + update_recv_state_base(sink, base); + } + } + + list_for_every_container(&sink_cbs, listener, _node) + { + if (listener->base_recv != NULL) + { + listener->base_recv(sink, base, (size_t)base_size); + } + } + + return false; +} + +static void pa_recv(struct bt_le_per_adv_sync *sync, + const struct bt_le_per_adv_sync_recv_info *info, + struct net_buf_simple *buf) +{ + struct bt_bap_broadcast_sink *sink = broadcast_sink_get_by_pa(sync); + + ARG_UNUSED(info); + + if (sink == NULL) + { + /* Not a PA sync that we control */ + + return; + } + + if (list_is_empty(&sink_cbs)) + { + /* Terminate early if we do not have any broadcast sink listeners */ + + return; + } + + bt_data_parse(buf, pa_decode_base, (void *)sink); +} + +static void pa_synced_cb(struct bt_le_per_adv_sync *sync, + struct bt_le_per_adv_sync_synced_info *info) +{ + struct bt_bap_broadcast_sink *sink = broadcast_sink_get_by_pa(sync); + int err; + + ARG_UNUSED(info); + + if (sink != NULL) + { + err = bt_bap_scan_delegator_set_pa_state(sink->bass_src_id, + BT_BAP_PA_STATE_SYNCED); + if (err != 0) + { + LOG_WRN("Failed to set PA state: %d", err); + } + } +} + +static void pa_term_cb(struct bt_le_per_adv_sync *sync, + const struct bt_le_per_adv_sync_term_info *info) +{ + struct bt_bap_broadcast_sink *sink = broadcast_sink_get_by_pa(sync); + int err; + + ARG_UNUSED(info); + + if (sink != NULL) + { + err = bt_bap_scan_delegator_set_pa_state(sink->bass_src_id, + BT_BAP_PA_STATE_NOT_SYNCED); + if (err != 0) + { + LOG_WRN("Failed to set PA state: %d", err); + } + + sink->pa_sync = NULL; + sink->base_size = 0U; + } +} + +static void update_recv_state_encryption( + const struct bt_bap_broadcast_sink *sink) +{ + const struct bt_bap_scan_delegator_recv_state *recv_state; + int err; + + DEBUGASSERT(sink->big == NULL); + + recv_state = bt_bap_scan_delegator_find_state(find_recv_state_by_src_id_cb, + (void *)sink); + if (recv_state == NULL) + { + LOG_WRN("Failed to find receive state for sink %p", sink); + + return; + } + + (void)memset(&mod_src_param, 0, sizeof(mod_src_param)); + + /* Only change the encrypt state, and leave the rest as is */ + + if (atomic_test_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_BIG_ENCRYPTED)) + { + mod_src_param.encrypt_state = BT_BAP_BIG_ENC_STATE_BCODE_REQ; + } + else + { + mod_src_param.encrypt_state = BT_BAP_BIG_ENC_STATE_NO_ENC; + } + + if (mod_src_param.encrypt_state == recv_state->encrypt_state) + { + /* No change, abort */ + + return; + } + + /* Copy existing data */ + + /* TODO: Maybe we need more refined functions to set only specific fields? + */ + + mod_src_param.src_id = recv_state->src_id; + mod_src_param.broadcast_id = recv_state->broadcast_id; + mod_src_param.num_subgroups = recv_state->num_subgroups; + (void)memcpy(mod_src_param.subgroups, recv_state->subgroups, + sizeof(recv_state->subgroups)); + + err = bt_bap_scan_delegator_mod_src(&mod_src_param); + if (err != 0) + { + LOG_WRN("Failed to modify Receive State for sink %p: %d", sink, err); + } +} + +static void biginfo_recv(struct bt_le_per_adv_sync *sync, + const struct bt_iso_biginfo *biginfo) +{ + struct bt_bap_broadcast_sink_cb *listener; + struct bt_bap_broadcast_sink *sink; + + sink = broadcast_sink_get_by_pa(sync); + if (sink == NULL) + { + /* Not ours */ + + return; + } + + if (sink->big != NULL) + { + /* Already synced - ignore */ + + return; + } + + atomic_set_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_BIGINFO_RECEIVED); + if (biginfo->encryption != atomic_test_bit(sink->flags, + BT_BAP_BROADCAST_SINK_FLAG_BIG_ENCRYPTED)) + { + atomic_set_bit_to(sink->flags, + BT_BAP_BROADCAST_SINK_FLAG_BIG_ENCRYPTED, + biginfo->encryption); + + if (atomic_test_bit(sink->flags, + BT_BAP_BROADCAST_SINK_FLAG_SRC_ID_VALID)) + { + update_recv_state_encryption(sink); + } + } + + (void)memcpy(&sink->biginfo, biginfo, sizeof(sink->biginfo)); + + list_for_every_container(&sink_cbs, listener, _node) + { + if (listener->syncable != NULL) + { + listener->syncable(sink, biginfo); + } + } +} + +static uint16_t interval_to_sync_timeout(uint16_t interval) +{ + uint32_t interval_us; + uint32_t timeout; + + /* Add retries and convert to unit in 10's of ms */ + + interval_us = BT_GAP_PER_ADV_INTERVAL_TO_US(interval); + timeout = + BT_GAP_US_TO_PER_ADV_SYNC_TIMEOUT( + interval_us) * PA_SYNC_INTERVAL_TO_TIMEOUT_RATIO; + + /* Enforce restraints */ + + timeout = CLAMP(timeout, BT_GAP_PER_ADV_MIN_TIMEOUT, + BT_GAP_PER_ADV_MAX_TIMEOUT); + + return (uint16_t)timeout; +} + +static void big_started_cb(struct bt_iso_big *big) +{ + struct bt_bap_broadcast_sink *sink = broadcast_sink_get_by_big(big); + struct bt_bap_broadcast_sink_cb *listener; + + if (sink == NULL) + { + /* Not one of ours */ + + return; + } + + list_for_every_container(&sink_cbs, listener, _node) + { + if (listener->started != NULL) + { + listener->started(sink); + } + } +} + +static void big_stopped_cb(struct bt_iso_big *big, uint8_t reason) +{ + struct bt_bap_broadcast_sink *sink = broadcast_sink_get_by_big(big); + struct bt_bap_broadcast_sink_cb *listener; + + if (sink == NULL) + { + /* Not one of ours */ + + return; + } + + broadcast_sink_clear_big(sink, reason); + + list_for_every_container(&sink_cbs, listener, _node) + { + if (listener->stopped != NULL) + { + listener->stopped(sink, reason); + } + } +} + +int bt_bap_broadcast_sink_register_cb(struct bt_bap_broadcast_sink_cb *cb) +{ + static bool iso_big_cb_registered; + + if (cb == NULL) + { + LOG_DBG("cb is NULL"); + + return -EINVAL; + } + + if (list_find(&sink_cbs, &cb->_node, NULL)) + { + LOG_DBG("cb %p is already registered", cb); + + return -EEXIST; + } + + if (!iso_big_cb_registered) + { + static struct bt_iso_big_cb big_cb = + { + .started = big_started_cb, + .stopped = big_stopped_cb, + }; + + const int err = bt_iso_big_register_cb(&big_cb); + + if (err != 0) + { + DEBUGASSERT(false); + } + + iso_big_cb_registered = true; + } + + list_add_tail(&sink_cbs, &cb->_node); + + return 0; +} + +bool bt_bap_broadcast_sink_has_ep(const struct bt_bap_ep *ep) +{ + for (int i = 0; i < ARRAY_SIZE(broadcast_sink_eps); i++) + { + if (IS_ARRAY_ELEMENT(broadcast_sink_eps[i], ep)) + { + return true; + } + } + + return false; +} + +static void broadcast_sink_ep_init(struct bt_bap_ep *ep) +{ + LOG_DBG("ep %p", ep); + + (void)memset(ep, 0, sizeof(*ep)); + ep->dir = BT_AUDIO_DIR_SINK; + ep->iso = NULL; +} + +static struct bt_bap_ep *broadcast_sink_new_ep(uint8_t index) +{ + for (size_t i = 0U; i < ARRAY_SIZE(broadcast_sink_eps[index]); i++) + { + struct bt_bap_ep *ep = &broadcast_sink_eps[index][i]; + + /* If ep->stream is NULL the endpoint is unallocated */ + + if (ep->stream == NULL) + { + broadcast_sink_ep_init(ep); + return ep; + } + } + + return NULL; +} + +static int bt_bap_broadcast_sink_setup_stream( + struct bt_bap_broadcast_sink *sink, struct bt_bap_stream *stream, + const struct bt_audio_codec_cfg *codec_cfg, uint8_t id) +{ + struct bt_bap_iso *iso; + struct bt_bap_ep *ep; + + if (stream->group != NULL) + { + LOG_DBG("Stream %p already in group %p", stream, stream->group); + return -EALREADY; + } + + ep = broadcast_sink_new_ep(sink->index); + if (ep == NULL) + { + LOG_DBG("Could not allocate new broadcast endpoint"); + return -ENOMEM; + } + + iso = bt_bap_iso_new(); + if (iso == NULL) + { + LOG_DBG("Could not allocate iso"); + return -ENOMEM; + } + + bt_bap_iso_init(iso, &broadcast_sink_iso_ops); + bt_bap_iso_bind_ep(iso, ep); + stream->iso = &iso->chan; + ep->id = id; + + (void)memset(&ep->qos, 0, sizeof(ep->qos)); + ep->qos.pd = + bt_bap_base_get_pres_delay((const struct bt_bap_base *)sink->base); + ep->qos.framing = sink->biginfo.framing; + ep->qos.phy = sink->biginfo.phy; + ep->qos.rtn = 0U; /* unknown for broadcast sinks */ + ep->qos.sdu = sink->biginfo.max_sdu; + ep->qos.interval = sink->biginfo.sdu_interval; +#if defined(CONFIG_BT_ISO_TEST_PARAMS) + ep->qos.max_pdu = sink->biginfo.max_pdu; + ep->qos.burst_number = sink->biginfo.burst_number; + ep->qos.num_subevents = sink->biginfo.sub_evt_count; +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ + bt_bap_qos_cfg_to_iso_qos(iso->chan.qos->rx, &ep->qos); + (void)memcpy(&ep->codec_cfg, codec_cfg, sizeof(*codec_cfg)); + + bt_bap_iso_unref(iso); + + bt_bap_stream_attach(NULL, stream, ep); + stream->qos = &ep->qos; + stream->group = sink; + + return 0; +} + +static void +broadcast_sink_cleanup_streams(struct bt_bap_broadcast_sink *sink) +{ + struct bt_bap_stream *stream; + struct bt_bap_stream *next; + + list_for_every_container_safe(&sink->streams, stream, next, _node) + { + if (stream->ep != NULL) + { + bt_bap_iso_unbind_ep(stream->ep->iso, stream->ep); + stream->iso = NULL; + stream->ep->stream = NULL; + stream->ep = NULL; + } + + stream->qos = NULL; + stream->codec_cfg = NULL; + stream->group = NULL; + + list_remove(&sink->streams, NULL, &stream->_node); + } + + sink->stream_count = 0U; + sink->indexes_bitfield = 0U; +} + +static void broadcast_sink_cleanup(struct bt_bap_broadcast_sink *sink) +{ + if (sink->stream_count > 0U) + { + broadcast_sink_cleanup_streams(sink); + } + + (void)memset(sink, 0, sizeof(*sink)); /* also clears flags */ +} + +int bt_bap_broadcast_sink_create( + struct bt_le_per_adv_sync *pa_sync, uint32_t broadcast_id, + struct bt_bap_broadcast_sink **out_sink) +{ + const struct bt_bap_scan_delegator_recv_state *recv_state; + struct bt_bap_broadcast_sink *sink; + + if (pa_sync == NULL) + { + LOG_DBG("pa_sync is NULL"); + + return -EINVAL; + } + + if (broadcast_id > BT_AUDIO_BROADCAST_ID_MAX) + { + LOG_DBG("Invalid broadcast_id: 0x%X", broadcast_id); + + return -EINVAL; + } + + if (out_sink == NULL) + { + LOG_DBG("sink was NULL"); + + return -EINVAL; + } + + sink = broadcast_sink_free_get(); + if (sink == NULL) + { + LOG_DBG("No more free broadcast sinks"); + + return -ENOMEM; + } + + sink->broadcast_id = broadcast_id; + sink->pa_sync = pa_sync; + + recv_state = + bt_bap_scan_delegator_find_state(find_recv_state_by_sink_fields_cb, + (void *)sink); + if (recv_state == NULL) + { + broadcast_sink_add_src(sink); + } + else + { + /* The PA sync is known by the Scan Delegator */ + + if (recv_state->broadcast_id != broadcast_id) + { + LOG_DBG("Broadcast ID mismatch: 0x%X != 0x%X", + recv_state->broadcast_id, broadcast_id); + + broadcast_sink_cleanup(sink); + return -EINVAL; + } + + sink->bass_src_id = recv_state->src_id; + if (recv_state->pa_sync_state != BT_BAP_PA_STATE_SYNCED) + { + int err; + + /* When a broadcast sink is created we always assume the PA sync + * provided is synced + */ + + err = bt_bap_scan_delegator_set_pa_state(sink->bass_src_id, + BT_BAP_PA_STATE_SYNCED); + if (err != 0) + { + LOG_DBG("Failed to set PA state: %d", err); + + broadcast_sink_cleanup(sink); + + return err; + } + } + + atomic_set_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_SRC_ID_VALID); + } + + atomic_set_bit(sink->flags, BT_BAP_BROADCAST_SINK_FLAG_INITIALIZED); + + *out_sink = sink; + return 0; +} + +struct sync_base_info_data +{ + struct bt_audio_codec_cfg + codec_cfgs[CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT]; + struct bt_audio_codec_cfg *subgroup_codec_cfg; + uint32_t sync_indexes_bitfield; + uint8_t subgroup_count; + uint8_t stream_count; +}; + +static bool merge_bis_and_subgroup_data_cb(struct bt_data *data, + void *user_data) +{ + struct bt_audio_codec_cfg *codec_cfg = user_data; + int err; + + err = bt_audio_codec_cfg_set_val(codec_cfg, data->type, data->data, + data->data_len); + if (err < 0) + { + LOG_DBG("Failed to set type %u with len %u in codec_cfg: %d", + data->type, + data->data_len, + err); + + return false; + } + + return true; +} + +static bool sync_base_subgroup_bis_index_cb( + const struct bt_bap_base_subgroup_bis *bis, void *user_data) +{ + struct sync_base_info_data *data = user_data; + struct bt_audio_codec_cfg *codec_cfg; + + /* Only process selected BISes */ + + if ((data->sync_indexes_bitfield & BT_ISO_BIS_INDEX_BIT(bis->index)) == 0) + { + return true; + } + + codec_cfg = &data->codec_cfgs[data->stream_count]; + + memcpy(codec_cfg, data->subgroup_codec_cfg, + sizeof(struct bt_audio_codec_cfg)); + + if (bis->data_len > 0) + { + /* Merge subgroup codec configuration with the BIS configuration + * As per the BAP spec, if a value exist at level 2 (subgroup) and 3 + * (BIS), then it is the value at level 3 that shall be used + */ + + if (codec_cfg->id == BT_HCI_CODING_FORMAT_LC3) + { + int err; + + memcpy(codec_cfg, data->subgroup_codec_cfg, + sizeof(struct bt_audio_codec_cfg)); + + err = bt_audio_data_parse(bis->data, bis->data_len, + merge_bis_and_subgroup_data_cb, + codec_cfg); + if (err != 0) + { + LOG_DBG("Could not merge BIS and subgroup config in " + "codec_cfg: " + "%d", + err); + + return false; + } + } + else + { + /* If it is not LC3, then we don't know how to merge the subgroup + * and BIS codecs, so we just append them + */ + + if (codec_cfg->data_len + bis->data_len > sizeof(codec_cfg->data)) + { + LOG_DBG("Could not store BIS and subgroup config in codec_cfg " + "(%u " + "> %u)", + codec_cfg->data_len + bis->data_len, sizeof( + codec_cfg->data)); + return false; + } + + memcpy(&codec_cfg->data[codec_cfg->data_len], bis->data, + bis->data_len); + codec_cfg->data_len += bis->data_len; + } + } + + data->stream_count++; + + return true; +} + +static bool sync_base_subgroup_cb( + const struct bt_bap_base_subgroup *subgroup, void *user_data) +{ + struct sync_base_info_data *data = user_data; + const struct bt_audio_codec_cap *codec_cap; + struct bt_audio_codec_cfg codec_cfg; + struct bt_pac_codec codec_id; + int ret; + + if (data->subgroup_count == CONFIG_BT_BAP_BROADCAST_SNK_SUBGROUP_COUNT) + { + /* We've parsed as many subgroups as we support */ + + LOG_DBG("Could only store %u subgroups", data->subgroup_count); + return false; + } + + ret = bt_bap_base_subgroup_codec_to_codec_cfg(subgroup, &codec_cfg); + if (ret < 0) + { + LOG_DBG("Could not store codec_cfg: %d", ret); + return false; + } + + /* Lookup and assign path_id based on capabilities */ + + codec_id.id = codec_cfg.id; + codec_id.cid = codec_cfg.cid; + codec_id.vid = codec_cfg.vid; + + codec_cap = bt_pacs_get_codec_cap(BT_AUDIO_DIR_SINK, &codec_id); + if (codec_cap == NULL) + { + LOG_DBG("Codec with id 0x%02x cid 0x%04x and vid 0x%04x is not " + "supported by our " + "capabilities", + codec_id.id, codec_id.cid, codec_id.vid); + } + else + { + codec_cfg.path_id = codec_cap->path_id; + codec_cfg.ctlr_transcode = codec_cap->ctlr_transcode; + + data->subgroup_codec_cfg = &codec_cfg; + + ret = bt_bap_base_subgroup_foreach_bis(subgroup, + sync_base_subgroup_bis_index_cb, + data); + if (ret < 0) + { + LOG_DBG("Could not parse BISes: %d", ret); + return false; + } + + data->subgroup_count++; + } + + return true; +} + +int bt_bap_broadcast_sink_sync( + struct bt_bap_broadcast_sink *sink, uint32_t indexes_bitfield, + struct bt_bap_stream *streams[], + const uint8_t broadcast_code[BT_ISO_BROADCAST_CODE_SIZE]) +{ + static struct sync_base_info_data data; + struct bt_iso_big_sync_param param; + struct bt_iso_chan *bis_channels[CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT]; + uint8_t bis_count; + uint8_t stream_count; + int err; + int ret; + + if (sink == NULL) + { + LOG_DBG("sink is NULL"); + return -EINVAL; + } + + if (indexes_bitfield == 0U + || indexes_bitfield > BIT_MASK(BT_ISO_BIS_INDEX_MAX)) + { + LOG_DBG("Invalid indexes_bitfield: 0x%08X", indexes_bitfield); + return -EINVAL; + } + + if (streams == NULL) + { + LOG_DBG("streams is NULL"); + return -EINVAL; + } + + if (sink->pa_sync == NULL) + { + LOG_DBG("Sink is not PA synced"); + return -EINVAL; + } + + if (!atomic_test_bit(sink->flags, + BT_BAP_BROADCAST_SINK_FLAG_BIGINFO_RECEIVED)) + { + /* TODO: We could store the request to sync and start the sync + * once the BIGInfo has been received, and then do the sync then. This + * would be similar how LE Create Connection works. + */ + + LOG_DBG("BIGInfo not received, cannot sync yet"); + return -EAGAIN; + } + + if (atomic_test_bit(sink->flags, + BT_BAP_BROADCAST_SINK_FLAG_BIG_ENCRYPTED) && + broadcast_code == NULL) + { + LOG_DBG("Broadcast code required"); + + return -EINVAL; + } + + /* Validate that number of bits set is within supported range */ + + bis_count = sys_count_bits(&indexes_bitfield, sizeof(indexes_bitfield)); + if (bis_count > CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT) + { + LOG_DBG("Cannot sync to more than %d streams (%u was requested)", + CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT, bis_count); + return -EINVAL; + } + + /* Validate that the bits set are present in BASE */ + + if ((indexes_bitfield & sink->valid_indexes_bitfield) != indexes_bitfield) + { + LOG_DBG("Request BIS indexes (0x%08X) contains bits not present in " + "BASE (0x%08X)", + indexes_bitfield, sink->valid_indexes_bitfield); + return -EINVAL; + } + + memset(&data, 0, sizeof(data)); + + data.sync_indexes_bitfield = indexes_bitfield; + + ret = bt_bap_base_foreach_subgroup((const struct bt_bap_base *)sink->base, + sync_base_subgroup_cb, + &data); + if (ret != 0) + { + LOG_DBG("Failed to parse all subgroups: %d", ret); + return ret; + } + + stream_count = data.stream_count; + + for (size_t i = 0U; i < stream_count; i++) + { + if (streams[i] == NULL) + { + LOG_DBG("streams[%zu] is NULL", i); + return -EINVAL; + } + } + + sink->stream_count = 0U; + for (size_t i = 0U; i < stream_count; i++) + { + struct bt_bap_stream *stream; + const struct bt_audio_codec_cfg *codec_cfg; + + stream = streams[i]; + codec_cfg = &data.codec_cfgs[i]; + + err = bt_bap_broadcast_sink_setup_stream(sink, stream, codec_cfg, i); + if (err != 0) + { + LOG_DBG("Failed to setup streams[%zu]: %d", i, err); + broadcast_sink_cleanup_streams(sink); + return err; + } + + sink->bis[i].chan = bt_bap_stream_iso_chan_get(stream); + list_add_tail(&sink->streams, &stream->_node); + sink->stream_count++; + + bis_channels[i] = sink->bis[i].chan; + } + + param.bis_channels = bis_channels; + param.num_bis = sink->stream_count; + param.bis_bitfield = indexes_bitfield; + param.mse = 0; /* Let controller decide */ + param.sync_timeout = interval_to_sync_timeout(sink->biginfo.iso_interval); + param.encryption = atomic_test_bit(sink->flags, + BT_BAP_BROADCAST_SINK_FLAG_BIG_ENCRYPTED); + if (param.encryption) + { + memcpy(param.bcode, broadcast_code, sizeof(param.bcode)); + } + else + { + memset(param.bcode, 0, sizeof(param.bcode)); + } + + err = bt_iso_big_sync(sink->pa_sync, ¶m, &sink->big); + if (err != 0) + { + broadcast_sink_cleanup_streams(sink); + return err; + } + + sink->indexes_bitfield = indexes_bitfield; + for (size_t i = 0U; i < stream_count; i++) + { + struct bt_bap_ep *ep = streams[i]->ep; + + broadcast_sink_set_ep_state(ep, BT_BAP_EP_STATE_QOS_CONFIGURED); + } + + return 0; +} + +int bt_bap_broadcast_sink_stop(struct bt_bap_broadcast_sink *sink) +{ + int err; + + if (sink == NULL) + { + LOG_DBG("sink is NULL"); + return -EINVAL; + } + + if (list_is_empty(&sink->streams)) + { + LOG_DBG("Source does not have any streams (already stopped)"); + return -EALREADY; + } + + if (broadcast_sink_is_in_state(sink, BT_BAP_EP_STATE_IDLE)) + { + LOG_DBG("Broadcast sink %p in idle state", sink); + return -EBADMSG; + } + + err = bt_iso_big_terminate(sink->big); + if (err != 0) + { + LOG_DBG("Failed to terminate BIG (err %d)", err); + return err; + } + + return 0; +} + +int bt_bap_broadcast_sink_delete(struct bt_bap_broadcast_sink *sink) +{ + if (sink == NULL) + { + LOG_DBG("sink is NULL"); + return -EINVAL; + } + + if (!broadcast_sink_is_in_state(sink, BT_BAP_EP_STATE_IDLE)) + { + LOG_DBG("Broadcast sink %p not in idle state", sink); + return -EBADMSG; + } + + /* Reset the broadcast sink */ + + broadcast_sink_cleanup(sink); + + return 0; +} + +static int broadcast_sink_init(void) +{ + static struct bt_le_per_adv_sync_cb cb = + { + .synced = pa_synced_cb, + .recv = pa_recv, + .biginfo = biginfo_recv, + .term = pa_term_cb, + }; + + bt_le_per_adv_sync_cb_register(&cb); + + return 0; +} + +SYS_INIT(broadcast_sink_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/libs/zblue/audio/bap_broadcast_source.c b/libs/zblue/audio/bap_broadcast_source.c new file mode 100644 index 0000000000000..2732394fcd14c --- /dev/null +++ b/libs/zblue/audio/bap_broadcast_source.c @@ -0,0 +1,1697 @@ +/**************************************************************************** + * libs/zblue/audio/bap_broadcast_source.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Bluetooth Audio Broadcast Source */ + +/* Copyright (c) 2021-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(bt_bap_broadcast_source, + CONFIG_BT_BAP_BROADCAST_SOURCE_LOG_LEVEL); + +#include "audio_internal.h" +#include "bap_endpoint.h" +#include "bap_iso.h" +#include "bap_stream.h" + +struct bt_bap_broadcast_subgroup +{ + /* The streams used to create the broadcast source */ + + struct list_node streams; + + /* The codec of the subgroup */ + + struct bt_audio_codec_cfg codec_cfg; + + /* List node */ + + struct list_node _node; +}; + +static struct bt_bap_ep + broadcast_source_eps[CONFIG_BT_BAP_BROADCAST_SRC_COUNT] + [BROADCAST_STREAM_CNT]; +static struct bt_bap_broadcast_subgroup + broadcast_source_subgroups[CONFIG_BT_BAP_BROADCAST_SRC_COUNT] + [CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT]; +static struct bt_bap_broadcast_source + broadcast_sources[CONFIG_BT_BAP_BROADCAST_SRC_COUNT]; +static struct list_node bap_broadcast_source_cbs = + LIST_INITIAL_VALUE(bap_broadcast_source_cbs); + +/* 2 octets UUID 3 octets presentation delay 1 octet number of subgroups + * + * Each subgroup then has 1 octet of number of BIS 5 octets of Codec_ID 1 + * octet codec specific configuration len 0-n octets of codec specific + * configuration 1 octet metadata len 0-n octets of metadata + * + * For each BIS in the subgroup there is 1 octet for the BIS index 1 octet + * codec specific configuration len 0-n octets of codec specific + * configuration + * + * For a minimal BASE with 1 subgroup and 1 BIS without and other data the + * total comes to 16 + */ +#define MINIMUM_BASE_SIZE 16U + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void broadcast_source_set_ep_state(struct bt_bap_ep *ep, + uint8_t state) +{ + uint8_t old_state; + + old_state = ep->state; + + LOG_DBG("ep %p id 0x%02x %s -> %s", ep, ep->id, + bt_bap_ep_state_str(old_state), + bt_bap_ep_state_str(state)); + + switch (old_state) + { + case BT_BAP_EP_STATE_IDLE: + if (state != BT_BAP_EP_STATE_QOS_CONFIGURED) + { + LOG_DBG("Invalid broadcast sync endpoint state transition"); + return; + } + break; + case BT_BAP_EP_STATE_QOS_CONFIGURED: + if (state != BT_BAP_EP_STATE_IDLE && + state != BT_BAP_EP_STATE_ENABLING) + { + LOG_DBG("Invalid broadcast sync endpoint state transition"); + return; + } + break; + case BT_BAP_EP_STATE_ENABLING: + if (state != BT_BAP_EP_STATE_STREAMING && + state != BT_BAP_EP_STATE_QOS_CONFIGURED) + { + LOG_DBG("Invalid broadcast sync endpoint state transition"); + return; + } + break; + case BT_BAP_EP_STATE_STREAMING: + if (state != BT_BAP_EP_STATE_QOS_CONFIGURED) + { + LOG_DBG("Invalid broadcast sync endpoint state transition"); + return; + } + break; + default: + LOG_ERR("Invalid broadcast sync endpoint state: %s", + bt_bap_ep_state_str(old_state)); + return; + } + + ep->state = state; +} + +static void broadcast_source_set_state( + struct bt_bap_broadcast_source *source, uint8_t state) +{ + struct bt_bap_broadcast_subgroup *subgroup; + + list_for_every_container(&source->subgroups, subgroup, _node) + { + struct bt_bap_stream *stream; + + list_for_every_container(&subgroup->streams, stream, _node) + { + broadcast_source_set_ep_state(stream->ep, state); + } + } +} + +static void broadcast_source_iso_sent(struct bt_iso_chan *chan) +{ + struct bt_bap_iso *iso = CONTAINER_OF(chan, struct bt_bap_iso, chan); + const struct bt_bap_stream_ops *ops; + struct bt_bap_stream *stream; + struct bt_bap_ep *ep = iso->tx.ep; + + if (ep == NULL) + { + LOG_ERR("iso %p not bound with ep", chan); + return; + } + + stream = ep->stream; + if (stream == NULL) + { + LOG_ERR("No stream for ep %p", ep); + return; + } + + ops = stream->ops; + + if (IS_ENABLED(CONFIG_BT_BAP_DEBUG_STREAM_DATA)) + { + LOG_DBG("stream %p ep %p", stream, stream->ep); + } + + if (ops != NULL && ops->sent != NULL) + { + ops->sent(stream); + } +} + +static void broadcast_source_iso_connected(struct bt_iso_chan *chan) +{ + struct bt_bap_iso *iso = CONTAINER_OF(chan, struct bt_bap_iso, chan); + const struct bt_bap_stream_ops *ops; + struct bt_bap_stream *stream; + struct bt_bap_ep *ep = iso->tx.ep; + + if (ep == NULL) + { + LOG_ERR("iso %p not bound with ep", chan); + return; + } + + stream = ep->stream; + if (stream == NULL) + { + LOG_ERR("No stream for ep %p", ep); + return; + } + + LOG_DBG("stream %p ep %p", stream, ep); + +#if defined(CONFIG_BT_BAP_DEBUG_STREAM_SEQ_NUM) + /* reset sequence number */ + + stream->_prev_seq_num = 0U; +#endif /* CONFIG_BT_BAP_DEBUG_STREAM_SEQ_NUM */ + + /* Setup the ISO data path */ + + bt_bap_setup_iso_data_path(stream); + + ops = stream->ops; + if (ops != NULL && ops->connected != NULL) + { + ops->connected(stream); + } + + broadcast_source_set_ep_state(ep, BT_BAP_EP_STATE_STREAMING); + + if (ops != NULL && ops->started != NULL) + { + ops->started(stream); + } +} + +static void broadcast_source_iso_disconnected(struct bt_iso_chan *chan, + uint8_t reason) +{ + struct bt_bap_iso *iso = CONTAINER_OF(chan, struct bt_bap_iso, chan); + const struct bt_bap_stream_ops *ops; + struct bt_bap_stream *stream; + struct bt_bap_ep *ep = iso->tx.ep; + + if (ep == NULL) + { + LOG_ERR("iso %p not bound with ep", chan); + return; + } + + stream = ep->stream; + if (stream == NULL) + { + LOG_ERR("No stream for ep %p", ep); + return; + } + + LOG_DBG("stream %p ep %p reason 0x%02x", stream, stream->ep, reason); + + ops = stream->ops; + if (ops != NULL && ops->disconnected != NULL) + { + ops->disconnected(stream, reason); + } + + broadcast_source_set_ep_state(ep, BT_BAP_EP_STATE_QOS_CONFIGURED); + + if (ops != NULL && ops->stopped != NULL) + { + ops->stopped(stream, reason); + } +} + +static struct bt_iso_chan_ops broadcast_source_iso_ops = +{ + .sent = broadcast_source_iso_sent, + .connected = broadcast_source_iso_connected, + .disconnected = broadcast_source_iso_disconnected, +}; + +bool bt_bap_broadcast_source_has_ep(const struct bt_bap_ep *ep) +{ + for (int i = 0; i < ARRAY_SIZE(broadcast_source_eps); i++) + { + if (IS_ARRAY_ELEMENT(broadcast_source_eps[i], ep)) + { + return true; + } + } + + return false; +} + +static void broadcast_source_ep_init(struct bt_bap_ep *ep) +{ + LOG_DBG("ep %p", ep); + + (void)memset(ep, 0, sizeof(*ep)); + ep->dir = BT_AUDIO_DIR_SOURCE; + ep->iso = NULL; +} + +static struct bt_bap_ep *broadcast_source_new_ep(uint8_t index) +{ + for (size_t i = 0U; i < ARRAY_SIZE(broadcast_source_eps[index]); i++) + { + struct bt_bap_ep *ep = &broadcast_source_eps[index][i]; + + /* If ep->stream is NULL the endpoint is unallocated */ + + if (ep->stream == NULL) + { + broadcast_source_ep_init(ep); + return ep; + } + } + + return NULL; +} + +static struct bt_bap_broadcast_subgroup *broadcast_source_new_subgroup( + uint8_t index) +{ + for (size_t i = 0U; i < ARRAY_SIZE(broadcast_source_subgroups[index]); i++) + { + struct bt_bap_broadcast_subgroup *subgroup = + &broadcast_source_subgroups[index][i]; + + if (list_is_empty(&subgroup->streams)) + { + return subgroup; + } + } + + return NULL; +} + +static bool merge_bis_and_subgroup_data_cb(struct bt_data *data, + void *user_data) +{ + struct bt_audio_codec_cfg *codec_cfg = user_data; + int err; + + err = bt_audio_codec_cfg_set_val(codec_cfg, data->type, data->data, + data->data_len); + if (err < 0) + { + LOG_DBG("Failed to set type %u with len %u in codec_cfg: %d", + data->type, + data->data_len, + err); + + return false; + } + + return true; +} + +static void update_codec_cfg_data( + struct bt_audio_codec_cfg *codec_cfg, const uint8_t data[], + size_t data_len) +{ + if (data_len > 0) + { + int err; + + /* Merge subgroup codec configuration with the BIS configuration + * As per the BAP spec, if a value exist at level 2 (subgroup) and 3 + * (BIS), then it is the value at level 3 that shall be used + */ + + if (codec_cfg->id == BT_HCI_CODING_FORMAT_LC3) + { + err = bt_audio_data_parse(data, data_len, + merge_bis_and_subgroup_data_cb, + codec_cfg); + } + else + { + /* If it is not LC3, then we don't know how to merge the subgroup + * and BIS codecs, so we just append them + */ + + if (codec_cfg->data_len + data_len > sizeof(codec_cfg->data)) + { + LOG_DBG("Could not store BIS and subgroup config in codec_cfg " + "(%u " + "> %u)", + codec_cfg->data_len + data_len, sizeof( + codec_cfg->data)); + err = -ENOMEM; + } + else + { + err = 0; + (void)memcpy(&codec_cfg->data[codec_cfg->data_len], data, + data_len); + codec_cfg->data_len += data_len; + } + } + + DEBUGASSERT(err == 0); + } +} + +static int +broadcast_source_setup_stream(uint8_t index, + struct bt_bap_stream *stream, + const struct bt_audio_codec_cfg *subgroup_codec_cfg, + const struct bt_bap_broadcast_source_stream_param *stream_param, + const struct bt_bap_qos_cfg *qos, + struct bt_bap_broadcast_source *source, + uint8_t id) +{ + struct bt_bap_iso *iso; + struct bt_bap_ep *ep; + + ep = broadcast_source_new_ep(index); + if (ep == NULL) + { + LOG_DBG("Could not allocate new broadcast endpoint"); + return -ENOMEM; + } + + iso = bt_bap_iso_new(); + if (iso == NULL) + { + LOG_DBG("Could not allocate iso"); + return -ENOMEM; + } + + bt_bap_iso_init(iso, &broadcast_source_iso_ops); + bt_bap_iso_bind_ep(iso, ep); + stream->iso = &iso->chan; + ep->id = id; + + (void)memcpy(&ep->qos, qos, sizeof(*qos)); + (void)memcpy(&ep->codec_cfg, subgroup_codec_cfg, + sizeof(*subgroup_codec_cfg)); + + /* If there are any BIS specific codec configuration data, update the data + * to contain both the subgroup and BIS specific data + */ + + update_codec_cfg_data(&ep->codec_cfg, stream_param->data, + stream_param->data_len); + +#if defined(CONFIG_BT_ISO_TEST_PARAMS) + iso->chan.qos->num_subevents = qos->num_subevents; +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ + + bt_bap_iso_unref(iso); + + bt_bap_stream_attach(NULL, stream, ep); + stream->qos = &ep->qos; + stream->group = source; + + return 0; +} + +static bool encode_base_subgroup(struct bt_bap_broadcast_subgroup *subgroup, + struct bt_audio_broadcast_stream_data *stream_data, + uint8_t *streams_encoded, + struct net_buf_simple *buf) +{ + struct bt_bap_stream *stream; + const struct bt_audio_codec_cfg *codec_cfg; + uint8_t stream_count; + uint8_t len; + + stream_count = 0U; + list_for_every_container(&subgroup->streams, stream, _node) + { + stream_count++; + } + + codec_cfg = &subgroup->codec_cfg; + + net_buf_simple_add_u8(buf, stream_count); + net_buf_simple_add_u8(buf, codec_cfg->id); + net_buf_simple_add_le16(buf, codec_cfg->cid); + net_buf_simple_add_le16(buf, codec_cfg->vid); + + net_buf_simple_add_u8(buf, codec_cfg->data_len); + if ((buf->size - buf->len) < codec_cfg->data_len) + { + LOG_DBG("No room for config data: %zu", codec_cfg->data_len); + + return false; + } + + net_buf_simple_add_mem(buf, codec_cfg->data, codec_cfg->data_len); + + if ((buf->size - buf->len) < sizeof(len)) + { + LOG_DBG("No room for metadata length"); + + return false; + } + + net_buf_simple_add_u8(buf, codec_cfg->meta_len); + +#if CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE > 0 + if ((buf->size - buf->len) < codec_cfg->meta_len) + { + LOG_DBG("No room for metadata data: %zu", codec_cfg->meta_len); + + return false; + } + + net_buf_simple_add_mem(buf, codec_cfg->meta, codec_cfg->meta_len); +#endif /* CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE > 0 */ + + /* Create BIS index bitfield */ + + for (uint8_t i = 0U; i < stream_count; i++) + { + /* Set the bis_index to *streams_encoded plus 1 as the indexes start + * from 1 + */ + + const uint8_t bis_index = *streams_encoded + 1; + + if ((buf->size - buf->len) < (sizeof(bis_index) + sizeof(uint8_t))) + { + LOG_DBG("No room for BIS[%d] index", i); + + return false; + } + + net_buf_simple_add_u8(buf, bis_index); + + if ((buf->size - buf->len) < sizeof(len)) + { + LOG_DBG("No room for bis codec config length"); + + return false; + } + + net_buf_simple_add_u8(buf, stream_data[i].data_len); + if ((buf->size - buf->len) < stream_data[i].data_len) + { + LOG_DBG("No room for BIS[%u] data: %zu", i, + stream_data[i].data_len); + + return false; + } + + net_buf_simple_add_mem(buf, stream_data[i].data, + stream_data[i].data_len); + + (*streams_encoded)++; + } + + return true; +} + +static bool encode_base(struct bt_bap_broadcast_source *source, + struct net_buf_simple *buf) +{ + struct bt_bap_broadcast_subgroup *subgroup; + uint8_t streams_encoded; + uint8_t subgroup_count; + uint32_t pd = 0U; + + /* 13 is the size of the fixed size values following this check */ + + if ((buf->size - buf->len) < MINIMUM_BASE_SIZE) + { + return false; + } + + subgroup_count = 0U; + list_for_every_container(&source->subgroups, subgroup, _node) + { +/* All broadcast stream share the same PD, so just take the PD from the + * first stream in the first subgroup + */ + + if (subgroup_count == 0U) + { + const struct bt_bap_stream *stream = + list_peek_head_container(&subgroup->streams, stream, _node); + + DEBUGASSERT(stream != NULL && stream->ep != NULL); + pd = stream->ep->qos.pd; + } + + subgroup_count++; + } + + /* The minimum number of subgroups in a broadcast source is 1 */ + + if (subgroup_count == 0U) + { + LOG_DBG("Invalid number of subgroups for broadcast source"); + return false; + } + + net_buf_simple_add_le16(buf, BT_UUID_BASIC_AUDIO_VAL); + + net_buf_simple_add_le24(buf, pd); + net_buf_simple_add_u8(buf, subgroup_count); + + /* Since the `stream_data` is only stored in the broadcast source, + * we need to provide that information when encoding each subgroup + */ + + streams_encoded = 0U; + list_for_every_container(&source->subgroups, subgroup, _node) + { + if (!encode_base_subgroup(subgroup, &source->stream_data[ + streams_encoded], + &streams_encoded, + buf)) + { + return false; + } + } + + return true; +} + +static void broadcast_source_cleanup(struct bt_bap_broadcast_source *source) +{ + struct bt_bap_broadcast_subgroup *subgroup; + struct bt_bap_broadcast_subgroup *next_subgroup; + + list_for_every_container_safe(&source->subgroups, subgroup, next_subgroup, + _node) + { + struct bt_bap_stream *stream; + struct bt_bap_stream *next_stream; + + list_for_every_container_safe(&subgroup->streams, stream, next_stream, + _node) + { + bt_bap_iso_unbind_ep(stream->ep->iso, stream->ep); + stream->iso = NULL; + stream->ep->stream = NULL; + stream->ep = NULL; + stream->codec_cfg = NULL; + stream->qos = NULL; + stream->group = NULL; + + list_remove(&subgroup->streams, NULL, &stream->_node); + } + + list_remove(&source->subgroups, NULL, &subgroup->_node); + } + + (void)memset(source, 0, sizeof(*source)); +} + +static bool +can_merge_codec_cfg_data(const struct bt_audio_codec_cfg *subgroup_codec_cfg, + const struct bt_bap_broadcast_source_stream_param *stream_param) +{ + if (stream_param->data_len == 0) + { + return true; + } + + /* Fake merge subgroup codec configuration with the BIS configuration to + * check the merged size. As per the BAP spec, if a value exist at level 2 + * (subgroup) and 3 (BIS), then it is the value at level 3 that shall be + * used + */ + + if (subgroup_codec_cfg->id == BT_HCI_CODING_FORMAT_LC3) + { + struct bt_audio_codec_cfg merged_codec_cfg; + int err; + + (void)memcpy(merged_codec_cfg.data, subgroup_codec_cfg->data, + subgroup_codec_cfg->data_len); + merged_codec_cfg.data_len = subgroup_codec_cfg->data_len; + + err = bt_audio_data_parse(stream_param->data, stream_param->data_len, + merge_bis_and_subgroup_data_cb, + &merged_codec_cfg); + if (err != 0) + { + LOG_DBG("Could not merge BIS and subgroup config in codec_cfg: %d", + err); + + return false; + } + } + else + { + /* If it is not LC3, then we don't know how to merge the subgroup and + * BIS codecs, so we just append them + */ + + if (subgroup_codec_cfg->data_len + stream_param->data_len > + sizeof(subgroup_codec_cfg->data)) + { + LOG_DBG("Could not merge BIS and subgroup config (%u > %u)", + subgroup_codec_cfg->data_len + stream_param->data_len, + sizeof(subgroup_codec_cfg->data)); + + return false; + } + } + + return true; +} + +static bool valid_broadcast_source_subgroup_param( + const struct bt_bap_broadcast_source_subgroup_param *subgroup_param, + const struct bt_bap_broadcast_source *source) +{ + if (subgroup_param->params == NULL) + { + LOG_DBG("subgroup_param->params is NULL"); + return false; + } + + if (!IN_RANGE(subgroup_param->params_count, 1U, + CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT)) + { + LOG_DBG("subgroup_param->count (%zu) is invalid", + subgroup_param->params_count); + return false; + } + + if (!bt_audio_valid_codec_cfg(subgroup_param->codec_cfg)) + { + LOG_DBG("subgroup_param->codec_cfg is invalid"); + return false; + } + + for (size_t i = 0U; i < subgroup_param->params_count; i++) + { + const struct bt_bap_broadcast_source_stream_param *stream_param; + + stream_param = &subgroup_param->params[i]; + + if (stream_param->stream == NULL) + { + LOG_DBG("subgroup_param->stream_params[%zu]->stream is NULL", i); + return false; + } + + if (stream_param->stream->group != NULL && + stream_param->stream->group != source) + { + LOG_DBG("subgroup_param->stream_params[%zu]->stream is already " + "part of " + "group %p", + i, stream_param->stream->group); + return false; + } + + if (stream_param->data == NULL && stream_param->data_len != 0) + { + LOG_DBG("subgroup_param->stream_params[%zu]->data is NULL with " + "len " + "%zu", + i, + stream_param->data_len); + return false; + } + + if (stream_param->data_len > CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE) + { + LOG_DBG("subgroup_param->stream_params[%zu]->data_len too large: " + "%zu > %d", + i, + stream_param->data_len, + CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE); + return false; + } + + if (stream_param->data != NULL && + subgroup_param->codec_cfg->id == BT_HCI_CODING_FORMAT_LC3 && + !bt_audio_valid_ltv(stream_param->data, stream_param->data_len)) + { + LOG_DBG("subgroup_param->stream_params[%zu]->data not valid LTV", + i); + return false; + } + + if (!can_merge_codec_cfg_data(subgroup_param->codec_cfg, stream_param)) + { + LOG_DBG("Cannot merge subgroup and BIS subgroup codec_cfg data"); + return false; + } + } + + return true; +} + +static bool valid_broadcast_source_param( + const struct bt_bap_broadcast_source_param *param, + const struct bt_bap_broadcast_source *source) +{ + const struct bt_bap_qos_cfg *qos; + + if (param == NULL) + { + LOG_DBG("param is NULL"); + return false; + } + + if (!IN_RANGE(param->params_count, 1U, + CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT)) + { + LOG_DBG("param->params_count %zu is invalid", param->params_count); + return false; + } + + if (param->packing != BT_ISO_PACKING_SEQUENTIAL && + param->packing != BT_ISO_PACKING_INTERLEAVED) + { + LOG_DBG("param->packing %u is invalid", param->packing); + return false; + } + + qos = param->qos; + if (qos == NULL) + { + LOG_DBG("param->qos is NULL"); + return false; + } + + if (bt_audio_verify_qos(qos) != BT_BAP_ASCS_REASON_NONE) + { + LOG_DBG("param->qos is invalid"); + return false; + } + + if (param->qos->rtn > BT_ISO_BROADCAST_RTN_MAX) + { + LOG_DBG("param->qos->rtn %u invalid", param->qos->rtn); + return false; + } + + if (param->params == NULL) + { + LOG_DBG("param->params is NULL"); + return false; + } + + if (param->params_count == 0) + { + LOG_DBG("param->params_count is 0"); + return false; + } + + for (size_t i = 0U; i < param->params_count; i++) + { + const struct bt_bap_broadcast_source_subgroup_param *subgroup_param; + + subgroup_param = ¶m->params[i]; + + if (!valid_broadcast_source_subgroup_param(subgroup_param, source)) + { + LOG_DBG("subgroup_params[%zu] is invalid", i); + return false; + } + } + + return true; +} + +/** Gets the "highest" state of all BIS in the broadcast source */ + +static enum bt_bap_ep_state broadcast_source_get_state( + struct bt_bap_broadcast_source *source) +{ + enum bt_bap_ep_state state = BT_BAP_EP_STATE_IDLE; + struct bt_bap_broadcast_subgroup *subgroup; + + if (source == NULL) + { + LOG_DBG("source is NULL"); + return state; + } + + if (list_is_empty(&source->subgroups)) + { + LOG_DBG("Source does not have any streams"); + return state; + } + + list_for_every_container(&source->subgroups, subgroup, _node) + { + struct bt_bap_stream *stream; + + list_for_every_container(&subgroup->streams, stream, _node) + { + if (stream->ep != NULL) + { + state = MAX(state, stream->ep->state); + } + } + } + + return state; +} + +int bt_bap_broadcast_source_create( + struct bt_bap_broadcast_source_param *param, + struct bt_bap_broadcast_source **out_source) +{ + struct bt_bap_broadcast_source *source; + const struct bt_bap_qos_cfg *qos; + size_t stream_count; + uint8_t index; + int err; + + if (out_source == NULL) + { + LOG_DBG("out_source is NULL"); + return -EINVAL; + } + + /* Set out_source to NULL until the source has actually been created */ + + *out_source = NULL; + + if (!valid_broadcast_source_param(param, NULL)) + { + LOG_DBG("Invalid parameters"); + return -EINVAL; + } + + source = NULL; + for (index = 0U; index < ARRAY_SIZE(broadcast_sources); index++) + { + if (list_is_empty(&broadcast_sources[index].subgroups)) + { + /* Find free entry */ + + source = &broadcast_sources[index]; + break; + } + } + + if (source == NULL) + { + LOG_DBG("Could not allocate any more broadcast sources"); + return -ENOMEM; + } + + stream_count = 0U; + qos = param->qos; + /* Go through all subgroups and streams and setup each setup with an + * endpoint + */ + + for (size_t i = 0U; i < param->params_count; i++) + { + const struct bt_bap_broadcast_source_subgroup_param *subgroup_param; + struct bt_bap_broadcast_subgroup *subgroup; + + subgroup_param = ¶m->params[i]; + + subgroup = broadcast_source_new_subgroup(index); + if (subgroup == NULL) + { + LOG_DBG("Could not allocate new broadcast subgroup"); + broadcast_source_cleanup(source); + return -ENOMEM; + } + + (void)memcpy(&subgroup->codec_cfg, subgroup_param->codec_cfg, + sizeof(subgroup->codec_cfg)); + list_add_tail(&source->subgroups, &subgroup->_node); + + /* Check that we are not above the maximum BIS count */ + + if (subgroup_param->params_count + stream_count > BROADCAST_STREAM_CNT) + { + LOG_DBG("Cannot create broadcaster with %zu streams", + stream_count); + broadcast_source_cleanup(source); + + return -ENOMEM; + } + + for (size_t j = 0U; j < subgroup_param->params_count; j++) + { + const struct bt_bap_broadcast_source_stream_param *stream_param = + &subgroup_param->params[j]; + const struct bt_audio_codec_cfg *subgroup_codec_cfg = + subgroup_param->codec_cfg; + struct bt_bap_stream *stream = stream_param->stream; + + err = broadcast_source_setup_stream(index, stream, + subgroup_codec_cfg, + stream_param, qos, + source, stream_count); + if (err != 0) + { + LOG_DBG("Failed to setup streams[%zu]: %d", i, err); + broadcast_source_cleanup(source); + return err; + } + + if (stream_param->data != NULL) + { + /* Store the BIS specific codec configuration data in + * the broadcast source. It is stored in the broadcast source, + * instead of the stream object, as this is only relevant for + * the broadcast source, and not used for unicast or broadcast + * sink. + */ + + (void)memcpy(source->stream_data[stream_count].data, + stream_param->data, + stream_param->data_len * + sizeof(*stream_param->data)); + source->stream_data[stream_count].data_len = + stream_param->data_len; + } + else + { + source->stream_data[stream_count].data_len = 0U; + } + + list_add_tail(&subgroup->streams, &stream->_node); + stream_count++; + } + } + + /* Finalize state changes and store information */ + + broadcast_source_set_state(source, BT_BAP_EP_STATE_QOS_CONFIGURED); + source->packing = param->packing; +#if defined(CONFIG_BT_ISO_TEST_PARAMS) + source->irc = param->irc; + source->pto = param->pto; + source->iso_interval = param->iso_interval; +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ + + source->encryption = param->encryption; + if (source->encryption) + { + (void)memcpy(source->broadcast_code, param->broadcast_code, + sizeof(source->broadcast_code)); + } + + *out_source = source; + + return 0; +} + +static bool valid_broadcast_source_reconfig_param( + const struct bt_bap_broadcast_source_param *param, + struct bt_bap_broadcast_source *source) +{ + struct bt_bap_broadcast_subgroup *subgroup; + size_t subgroup_cnt = 0U; + size_t bis_count = 0U; + + /* Verify that the parameter counts do not exceed existing number of + * subgroups and streams + */ + + list_for_every_container(&source->subgroups, subgroup, _node) + { + const struct bt_bap_broadcast_source_subgroup_param *subgroup_param = + ¶m->params[subgroup_cnt]; + const size_t subgroup_stream_param_cnt = subgroup_param->params_count; + struct bt_bap_stream *stream; + size_t subgroup_stream_cnt = 0U; + + list_for_every_container(&subgroup->streams, stream, _node) + { + subgroup_stream_cnt++; + } + + /* Verify that the param stream is in the subgroup */ + + for (size_t i = 0U; i < subgroup_param->params_count; i++) + { + struct bt_bap_stream *subgroup_stream; + struct bt_bap_stream *param_stream; + bool stream_in_subgroup = false; + + param_stream = subgroup_param->params[i].stream; + + list_for_every_container(&subgroup->streams, subgroup_stream, _node) + { + if (subgroup_stream == param_stream) + { + stream_in_subgroup = true; + break; + } + } + + if (!stream_in_subgroup) + { + LOG_DBG("Invalid param->params[%zu]->param[%zu].stream " + "not in subgroup", + subgroup_cnt, i); + return false; + } + + bis_count++; + if (bis_count > BROADCAST_STREAM_CNT) + { + LOG_DBG("Stream count %d exceeded", bis_count); + return false; + } + } + + if (subgroup_stream_cnt < subgroup_stream_param_cnt) + { + LOG_DBG("Invalid param->params[%zu]->params_count: %zu " + "(only %zu streams in subgroup)", + subgroup_cnt, + subgroup_stream_param_cnt, + subgroup_stream_cnt); + return false; + } + + subgroup_cnt++; + } + + if (subgroup_cnt < param->params_count) + { + LOG_DBG("Invalid param->params_count: %zu (only %zu subgroups in " + "source)", + param->params_count, subgroup_cnt); + return false; + } + + return true; +} + +static void broadcast_source_reconfig_update_subgroup( + struct bt_bap_broadcast_source *source, + struct bt_bap_broadcast_subgroup *subgroup, + const struct bt_bap_broadcast_source_subgroup_param *subgroup_param) +{ + struct bt_bap_stream *stream; + + (void)memcpy(&subgroup->codec_cfg, subgroup_param->codec_cfg, + sizeof(subgroup->codec_cfg)); + + /* Store data from subgroup_param */ + + for (size_t i = 0U; i < subgroup_param->params_count; i++) + { + const struct bt_bap_broadcast_source_stream_param *stream_param = + &subgroup_param->params[i]; + struct bt_audio_broadcast_stream_data *stream_data; + + stream = stream_param->stream; + + /* Store the BIS specific codec configuration data in the broadcast + * source. It is stored in the broadcast* source, instead of the stream + * object, as this is only relevant for the broadcast source, and not + * used for unicast or broadcast sink. + */ + + stream_data = &source->stream_data[stream->ep->id]; + + if (stream_param->data != NULL) + { + (void)memcpy(stream_data->data, stream_param->data, + stream_param->data_len); + stream_data->data_len = stream_param->data_len; + } + else + { + stream_data->data_len = 0U; + } + } + + /* Apply the codec_cfg to all streams in the subgroup, and not just the + * ones in the params + */ + + list_for_every_container(&subgroup->streams, stream, _node) + { + const struct bt_audio_broadcast_stream_data *stream_data = + &source->stream_data[stream->ep->id]; + struct bt_audio_codec_cfg *codec_cfg = &stream->ep->codec_cfg; + + (void)memcpy(codec_cfg, subgroup_param->codec_cfg, + sizeof(struct bt_audio_codec_cfg)); + + update_codec_cfg_data(codec_cfg, stream_data->data, + stream_data->data_len); + } +} + +int bt_bap_broadcast_source_reconfig(struct bt_bap_broadcast_source *source, + struct bt_bap_broadcast_source_param *param) +{ + struct bt_bap_broadcast_subgroup *subgroup = NULL; + enum bt_bap_ep_state broadcast_state; + const struct bt_bap_qos_cfg *qos; + + if (source == NULL) + { + LOG_DBG("source is NULL"); + return -EINVAL; + } + + if (!valid_broadcast_source_param(param, source)) + { + LOG_DBG("Invalid parameters"); + return -EINVAL; + } + + broadcast_state = broadcast_source_get_state(source); + if (broadcast_state != BT_BAP_EP_STATE_QOS_CONFIGURED) + { + LOG_DBG("Broadcast source invalid state: %u", broadcast_state); + return -EBADMSG; + } + + if (!valid_broadcast_source_reconfig_param(param, source)) + { + return -EINVAL; + } + + qos = param->qos; + + /* We update up to the first param->params_count subgroups */ + + for (size_t i = 0U; i < param->params_count; i++) + { + const struct bt_bap_broadcast_source_subgroup_param *subgroup_param; + + if (i == 0) + { + subgroup = list_peek_head_container(&source->subgroups, subgroup, + _node); + } + else + { + /* Number of subgroups in `source` are verified in + * valid_broadcast_source_reconfig_param so this should never + * happen + */ + + DEBUGASSERT(subgroup != NULL); + subgroup = list_next_container(&source->subgroups, + subgroup, + _node); + } + + subgroup_param = ¶m->params[i]; + + broadcast_source_reconfig_update_subgroup(source, subgroup, + subgroup_param); + } + + /* Finally we apply the new qos and to all streams */ + + list_for_every_container(&source->subgroups, subgroup, _node) + { + struct bt_bap_stream *stream; + + list_for_every_container(&subgroup->streams, stream, _node) + { + struct bt_bap_ep *ep = stream->ep; + + (void)memcpy(&ep->qos, qos, sizeof(*qos)); + } + } + + return 0; +} + +int bt_bap_broadcast_source_update_metadata( + struct bt_bap_broadcast_source *source, const uint8_t meta[], + size_t meta_len) +{ + struct bt_bap_broadcast_subgroup *subgroup; + enum bt_bap_ep_state broadcast_state; + + if (source == NULL) + { + LOG_DBG("source is NULL"); + + return -EINVAL; + } + + if ((meta == NULL && meta_len != 0) || (meta != NULL && meta_len == 0)) + { + LOG_DBG("Invalid metadata combination: %p %zu", meta, meta_len); + + return -EINVAL; + } + + if (meta_len > CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE) + { + LOG_DBG("Invalid meta_len: %zu (max %d)", meta_len, + CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE); + + return -EINVAL; + } + + broadcast_state = broadcast_source_get_state(source); + if (broadcast_state != BT_BAP_EP_STATE_STREAMING) + { + LOG_DBG("Broadcast source invalid state: %u", broadcast_state); + + return -EBADMSG; + } + + /* TODO: We should probably find a way to update the metadata + * for each subgroup individually + */ + + list_for_every_container(&source->subgroups, subgroup, _node) + { + (void)memset(subgroup->codec_cfg.meta, 0, + sizeof(subgroup->codec_cfg.meta)); + + if (meta != NULL) + { + (void)memcpy(subgroup->codec_cfg.meta, meta, meta_len); + } + + subgroup->codec_cfg.meta_len = meta_len; /* may be 0 */ + } + + return 0; +} + +int bt_bap_broadcast_source_start(struct bt_bap_broadcast_source *source, + struct bt_le_ext_adv *adv) +{ + struct bt_iso_chan *bis[BROADCAST_STREAM_CNT]; + struct bt_iso_big_create_param param = + { + 0 + }; + + struct bt_bap_broadcast_subgroup *subgroup; + enum bt_bap_ep_state broadcast_state; + struct bt_bap_stream *stream; + struct bt_bap_qos_cfg *qos; + size_t bis_count; + int err; + + if (source == NULL) + { + LOG_DBG("source is NULL"); + return -EINVAL; + } + + if (adv == NULL) + { + LOG_DBG("adv is NULL"); + return -EINVAL; + } + + broadcast_state = broadcast_source_get_state(source); + if (broadcast_source_get_state(source) != BT_BAP_EP_STATE_QOS_CONFIGURED) + { + LOG_DBG("Broadcast source invalid state: %u", broadcast_state); + return -EBADMSG; + } + + bis_count = 0U; + qos = NULL; + list_for_every_container(&source->subgroups, subgroup, _node) + { + list_for_every_container(&subgroup->streams, stream, _node) + { + struct bt_bap_ep *ep = stream->ep; + struct bt_iso_chan_io_qos *iso_qos = ep->iso->chan.qos->tx; + + bis[bis_count] = bt_bap_stream_iso_chan_get(stream); + bis_count++; + + bt_bap_qos_cfg_to_iso_qos(iso_qos, &ep->qos); + + /* All BIS will share the same QOS values */ + + if (qos == NULL) + { + qos = &ep->qos; + } + } + } + + if (bis_count == 0U) + { + /* TODO: This is just an extra check. Ideally this will never happen, + * but the above is not (yet) thread safe so we need to handle this + * case. + */ + + LOG_DBG("Can not start source with no streams"); + return -ENOEXEC; + } + + /* Create BIG */ + + param.num_bis = bis_count; + param.bis_channels = bis; + param.framing = qos->framing; + param.packing = source->packing; + param.interval = qos->interval; + param.latency = qos->latency; + param.encryption = source->encryption; + if (param.encryption) + { + (void)memcpy(param.bcode, source->broadcast_code, sizeof(param.bcode)); + } +#if defined(CONFIG_BT_ISO_TEST_PARAMS) + + param.irc = source->irc; + param.pto = source->pto; + param.iso_interval = source->iso_interval; +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ + + /* Set the enabling state early in case that the BIS is connected before we + * can manage to set it afterwards + */ + + broadcast_source_set_state(source, BT_BAP_EP_STATE_ENABLING); + + err = bt_iso_big_create(adv, ¶m, &source->big); + if (err != 0) + { + LOG_DBG("Failed to create BIG: %d", err); + broadcast_source_set_state(source, BT_BAP_EP_STATE_QOS_CONFIGURED); + + return err; + } + + return 0; +} + +int bt_bap_broadcast_source_stop(struct bt_bap_broadcast_source *source) +{ + enum bt_bap_ep_state broadcast_state; + int err; + + if (source == NULL) + { + LOG_DBG("source is NULL"); + return -EINVAL; + } + + broadcast_state = broadcast_source_get_state(source); + if (broadcast_state != BT_BAP_EP_STATE_STREAMING && + broadcast_state != BT_BAP_EP_STATE_ENABLING) + { + LOG_DBG("Broadcast source invalid state: %u", broadcast_state); + return -EBADMSG; + } + + if (source->big == NULL) + { + LOG_DBG("Source is not started"); + return -EALREADY; + } + + err = bt_iso_big_terminate(source->big); + if (err != 0) + { + LOG_DBG("Failed to terminate BIG (err %d)", err); + return err; + } + + return 0; +} + +int bt_bap_broadcast_source_delete(struct bt_bap_broadcast_source *source) +{ + enum bt_bap_ep_state broadcast_state; + + if (source == NULL) + { + LOG_DBG("source is NULL"); + return -EINVAL; + } + + broadcast_state = broadcast_source_get_state(source); + if (broadcast_state != BT_BAP_EP_STATE_QOS_CONFIGURED) + { + LOG_DBG("Broadcast source invalid state: %u", broadcast_state); + return -EBADMSG; + } + + broadcast_source_set_state(source, BT_BAP_EP_STATE_IDLE); + + /* Reset the broadcast source */ + + broadcast_source_cleanup(source); + + return 0; +} + +int bt_bap_broadcast_source_get_base(struct bt_bap_broadcast_source *source, + struct net_buf_simple *base_buf) +{ + enum bt_bap_ep_state broadcast_state; + + if (source == NULL) + { + LOG_DBG("source is NULL"); + return -EINVAL; + } + + if (base_buf == NULL) + { + LOG_DBG("base_buf is NULL"); + return -EINVAL; + } + + broadcast_state = broadcast_source_get_state(source); + if (broadcast_state == BT_BAP_EP_STATE_IDLE) + { + LOG_DBG("Broadcast source invalid state: %u", broadcast_state); + return -EBADMSG; + } + + if (!encode_base(source, base_buf)) + { + LOG_DBG("base_buf %p with size %u not large enough", base_buf, + base_buf->size); + + return -EMSGSIZE; + } + + return 0; +} + +static struct bt_bap_broadcast_source *get_broadcast_source_by_big( + const struct bt_iso_big *big) +{ + for (size_t i = 0U; i < ARRAY_SIZE(broadcast_sources); i++) + { + if (broadcast_sources[i].big == big) + { + return &broadcast_sources[i]; + } + } + + return NULL; +} + +static void big_started_cb(struct bt_iso_big *big) +{ + struct bt_bap_broadcast_source *source = get_broadcast_source_by_big(big); + struct bt_bap_broadcast_source_cb *listener; + + if (source == NULL) + { + /* Not one of ours */ + + return; + } + + list_for_every_container(&bap_broadcast_source_cbs, listener, _node) + { + if (listener->started != NULL) + { + listener->started(source); + } + } +} + +static void big_stopped_cb(struct bt_iso_big *big, uint8_t reason) +{ + struct bt_bap_broadcast_source *source = get_broadcast_source_by_big(big); + struct bt_bap_broadcast_source_cb *listener; + + if (source == NULL) + { + /* Not one of ours */ + + return; + } + + source->big = NULL; + + list_for_every_container(&bap_broadcast_source_cbs, listener, _node) + { + if (listener->stopped != NULL) + { + listener->stopped(source, reason); + } + } +} + +int +bt_bap_broadcast_source_register_cb(struct bt_bap_broadcast_source_cb *cb) +{ + static bool iso_big_cb_registered; + + if (cb == NULL) + { + LOG_DBG("cb is NULL"); + + return -EINVAL; + } + + if (list_find(&bap_broadcast_source_cbs, &cb->_node, NULL)) + { + LOG_DBG("cb %p is already registered", cb); + + return -EEXIST; + } + + if (!iso_big_cb_registered) + { + static struct bt_iso_big_cb big_cb = + { + .started = big_started_cb, + .stopped = big_stopped_cb, + }; + + const int err = bt_iso_big_register_cb(&big_cb); + + if (err != 0) + { + DEBUGASSERT(false); + } + + iso_big_cb_registered = true; + } + + list_add_tail(&bap_broadcast_source_cbs, &cb->_node); + + return 0; +} + +int bt_bap_broadcast_source_unregister_cb( + struct bt_bap_broadcast_source_cb *cb) +{ + if (cb == NULL) + { + LOG_DBG("cb is NULL"); + + return -EINVAL; + } + + if (!list_find_and_remove(&bap_broadcast_source_cbs, &cb->_node)) + { + LOG_DBG("cb %p is not registered", cb); + + return -ENOENT; + } + + return 0; +} + +int bt_bap_broadcast_source_foreach_stream( + struct bt_bap_broadcast_source *source, + bt_bap_broadcast_source_foreach_stream_func_t func, void *user_data) +{ + struct bt_bap_broadcast_subgroup *subgroup; + struct bt_bap_broadcast_subgroup *next_subgroup; + + if (source == NULL) + { + LOG_DBG("source is NULL"); + return -EINVAL; + } + + if (func == NULL) + { + LOG_DBG("func is NULL"); + return -EINVAL; + } + + list_for_every_container_safe(&source->subgroups, subgroup, next_subgroup, + _node) + { + struct bt_bap_stream *stream; + struct bt_bap_stream *next_stream; + + list_for_every_container_safe(&subgroup->streams, stream, next_stream, + _node) + { + if (!func(stream, user_data)) + { + return -ECANCELED; + } + } + } + + return 0; +} diff --git a/libs/zblue/audio/bap_endpoint.h b/libs/zblue/audio/bap_endpoint.h new file mode 100644 index 0000000000000..c3e65d25fa87d --- /dev/null +++ b/libs/zblue/audio/bap_endpoint.h @@ -0,0 +1,256 @@ +/**************************************************************************** + * libs/zblue/audio/bap_endpoint.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* @file + * @brief Internal APIs for Audio Endpoint handling + + * Copyright (c) 2020 Intel Corporation Copyright (c) 2022-2023 Nordic + * Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bap_stream.h" + +#if defined(CONFIG_BT_BAP_UNICAST_CLIENT) +#define UNICAST_GROUP_CNT CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_COUNT +#define UNICAST_GROUP_STREAM_CNT CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT +#else /* !CONFIG_BT_BAP_UNICAST_CLIENT */ +#define UNICAST_GROUP_CNT 0U +#define UNICAST_GROUP_STREAM_CNT 0U +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT */ +#if defined(CONFIG_BT_BAP_BROADCAST_SOURCE) +#define BROADCAST_STREAM_CNT CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT +#else /* !CONFIG_BT_BAP_BROADCAST_SOURCE */ +#define BROADCAST_STREAM_CNT 0U +#endif /* CONFIG_BT_BAP_BROADCAST_SOURCE */ + +/* Temp struct declarations to handle circular dependencies */ + +struct bt_bap_unicast_group; +struct bt_bap_broadcast_source; + +struct bt_bap_ep +{ + uint8_t dir; + uint8_t cig_id; + uint8_t cis_id; + uint8_t id; /* ASE ID or BIS ID (BIS index - 1) */ + enum bt_bap_ep_state state; + struct bt_bap_stream *stream; + struct bt_audio_codec_cfg codec_cfg; + struct bt_bap_qos_cfg qos; + struct bt_bap_qos_cfg_pref qos_pref; + struct bt_bap_iso *iso; + + /* unicast stopped reason */ + + uint8_t reason; + + /* Used by the unicast server and client */ + + bool receiver_ready; +}; + +struct bt_bap_unicast_group_cig_param +{ + uint32_t c_to_p_interval; + uint32_t p_to_c_interval; + uint16_t c_to_p_latency; + uint16_t p_to_c_latency; + uint8_t framing; + uint8_t packing; +#if defined(CONFIG_BT_ISO_TEST_PARAMS) + uint8_t c_to_p_ft; + uint8_t p_to_c_ft; + uint16_t iso_interval; +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ +}; + +struct bt_bap_unicast_group +{ + /* Group-wide QoS used to create the CIG */ + + struct bt_bap_unicast_group_cig_param cig_param; + + /* Unicast group fields */ + + uint8_t index; + bool allocated; + /* Used to determine whether any stream in this group has been started + * which will prevent reconfiguring it + */ + + bool has_been_connected; + struct bt_iso_cig *cig; + + /* The ISO API for CIG creation requires an array of pointers to ISO + * channels + */ + + struct bt_iso_chan *cis[UNICAST_GROUP_STREAM_CNT]; + struct list_node streams; + + /* Configured sink presentation delay */ + + uint32_t sink_pd; + + /* Configured source presentation delay */ + + uint32_t source_pd; +}; + +struct bt_audio_broadcast_stream_data +{ + /** Codec Specific Data len */ + + size_t data_len; + + /** Codec Specific Data */ + + uint8_t data[CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE]; +}; + +struct bt_bap_broadcast_source +{ + uint8_t stream_count; + uint8_t packing; + bool encryption; + + struct bt_iso_big *big; +#if defined(CONFIG_BT_ISO_TEST_PARAMS) + /* Stored advanced parameters */ + + uint8_t irc; + uint8_t pto; + uint16_t iso_interval; +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ + + /* The codec specific configured data for each stream in the subgroup */ + + struct bt_audio_broadcast_stream_data stream_data[BROADCAST_STREAM_CNT]; + uint8_t broadcast_code[BT_ISO_BROADCAST_CODE_SIZE]; + + /* The subgroups containing the streams used to create the broadcast source + */ + + struct list_node subgroups; +}; + +enum bt_bap_broadcast_sink_flag +{ + /** Sink has been initialized */ + + BT_BAP_BROADCAST_SINK_FLAG_INITIALIZED, + + /** BIGInfo has been received */ + + BT_BAP_BROADCAST_SINK_FLAG_BIGINFO_RECEIVED, + + /** Periodic Advertising is syncing */ + + BT_BAP_BROADCAST_SINK_FLAG_SYNCING, + + /** Broadcast sink instance is scanning */ + + BT_BAP_BROADCAST_SINK_FLAG_SCANNING, + + /** The BIG is encrypted */ + + BT_BAP_BROADCAST_SINK_FLAG_BIG_ENCRYPTED, + + /** The Scan Delegator Source ID is valid */ + + BT_BAP_BROADCAST_SINK_FLAG_SRC_ID_VALID, + + /** Total number of flags */ + + BT_BAP_BROADCAST_SINK_FLAG_NUM_FLAGS, +}; + +struct bt_bap_broadcast_sink_subgroup +{ + uint32_t bis_indexes; +}; + +struct bt_bap_broadcast_sink_bis +{ + uint8_t index; + struct bt_iso_chan *chan; +}; + +#if defined(CONFIG_BT_BAP_BROADCAST_SINK) +struct bt_bap_broadcast_sink +{ + uint8_t index; /* index of broadcast_snks array */ + uint8_t stream_count; + uint8_t bass_src_id; + uint8_t subgroup_count; + uint32_t broadcast_id; /* 24 bit */ + uint32_t indexes_bitfield; + uint32_t valid_indexes_bitfield; /* based on codec support */ + struct bt_iso_biginfo biginfo; + struct bt_le_per_adv_sync *pa_sync; + struct bt_iso_big *big; + uint8_t base_size; + uint8_t base[BT_BASE_MAX_SIZE]; + struct bt_bap_broadcast_sink_bis + bis[CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT]; + struct bt_bap_broadcast_sink_subgroup + subgroups[CONFIG_BT_BAP_BROADCAST_SNK_SUBGROUP_COUNT]; + const struct bt_bap_scan_delegator_recv_state *recv_state; + + /* The streams used to create the broadcast sink */ + + struct list_node streams; + + /** Flags */ + + atomic_define(flags, BT_BAP_BROADCAST_SINK_FLAG_NUM_FLAGS); +}; +#endif /* CONFIG_BT_BAP_BROADCAST_SINK */ + +static inline const char *bt_bap_ep_state_str(enum bt_bap_ep_state state) +{ + switch (state) + { + case BT_BAP_EP_STATE_IDLE: + return "idle"; + case BT_BAP_EP_STATE_CODEC_CONFIGURED: + return "codec-configured"; + case BT_BAP_EP_STATE_QOS_CONFIGURED: + return "qos-configured"; + case BT_BAP_EP_STATE_ENABLING: + return "enabling"; + case BT_BAP_EP_STATE_STREAMING: + return "streaming"; + case BT_BAP_EP_STATE_DISABLING: + return "disabling"; + case BT_BAP_EP_STATE_RELEASING: + return "releasing"; + default: + return "unknown"; + } +} diff --git a/libs/zblue/audio/bap_internal.h b/libs/zblue/audio/bap_internal.h new file mode 100644 index 0000000000000..8c1656f1bb67c --- /dev/null +++ b/libs/zblue/audio/bap_internal.h @@ -0,0 +1,175 @@ +/**************************************************************************** + * libs/zblue/audio/bap_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Internal header for Bluetooth BAP. + */ + +/* Copyright (c) 2019 Bose Corporation + * Copyright (c) 2021-2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define BT_BAP_BASS_SCAN_STATE_NOT_SCANNING 0x00U +#define BT_BAP_BASS_SCAN_STATE_SCANNING 0x01U + +#define BT_BAP_BASS_OP_SCAN_STOP 0x00U +#define BT_BAP_BASS_OP_SCAN_START 0x01U +#define BT_BAP_BASS_OP_ADD_SRC 0x02U +#define BT_BAP_BASS_OP_MOD_SRC 0x03U +#define BT_BAP_BASS_OP_BROADCAST_CODE 0x04U +#define BT_BAP_BASS_OP_REM_SRC 0x05U + +#define BT_BAP_BASS_SCAN_STATE_IDLE 0x00U +#define BT_BAP_BASS_SCAN_STATE_SCANNING 0x01U +#define BT_BAP_BASS_SCAN_STATE_FAILED 0x02U +#define BT_BAP_BASS_SCAN_STATE_SYNCED 0x03U + +#define BT_BAP_BASS_PA_REQ_NO_SYNC 0x00U +#define BT_BAP_BASS_PA_REQ_SYNC_PAST 0x01U +#define BT_BAP_BASS_PA_REQ_SYNC 0x02U + +#define BT_BAP_BASS_VALID_OPCODE(opcode) ((opcode) <= BT_BAP_BASS_OP_REM_SRC) + +struct bt_bap_bass_cp_scan_stop +{ + uint8_t opcode; +} __packed; + +struct bt_bap_bass_cp_scan_start +{ + uint8_t opcode; +} __packed; + +struct bt_bap_bass_cp_subgroup +{ + uint32_t bis_sync; + uint8_t metadata_len; + uint8_t metadata[]; +} __packed; + +struct bt_bap_bass_cp_add_src +{ + uint8_t opcode; + bt_addr_le_t addr; + uint8_t adv_sid; + uint8_t broadcast_id[BT_AUDIO_BROADCAST_ID_SIZE]; + uint8_t pa_sync; + uint16_t pa_interval; + uint8_t num_subgroups; + struct bt_bap_bass_cp_subgroup subgroups[]; +} __packed; + +struct bt_bap_bass_cp_mod_src +{ + uint8_t opcode; + uint8_t src_id; + uint8_t pa_sync; + uint16_t pa_interval; + uint8_t num_subgroups; + struct bt_bap_bass_cp_subgroup subgroups[]; +} __packed; + +struct bt_bap_bass_cp_broadcase_code +{ + uint8_t opcode; + uint8_t src_id; + uint8_t broadcast_code[BT_ISO_BROADCAST_CODE_SIZE]; +} __packed; + +struct bt_bap_bass_cp_rem_src +{ + uint8_t opcode; + uint8_t src_id; +} __packed; + +union bt_bap_bass_cp +{ + uint8_t opcode; + struct bt_bap_bass_cp_scan_stop scan_stop; + struct bt_bap_bass_cp_scan_start scan_start; + struct bt_bap_bass_cp_add_src add_src; + struct bt_bap_bass_cp_mod_src mod_src; + struct bt_bap_bass_cp_broadcase_code broadcast_code; + struct bt_bap_bass_cp_rem_src rem_src; +}; + +static inline const char *bt_bap_pa_state_str(uint8_t state) +{ + switch (state) + { + case BT_BAP_PA_STATE_NOT_SYNCED: + return "Not synchronized to PA"; + case BT_BAP_PA_STATE_INFO_REQ: + return "SyncInfo Request"; + case BT_BAP_PA_STATE_SYNCED: + return "Synchronized to PA"; + case BT_BAP_PA_STATE_FAILED: + return "Failed to synchronize to PA"; + case BT_BAP_PA_STATE_NO_PAST: + return "No PAST"; + default: + return "unknown state"; + } +} + +static inline const char *bt_bap_big_enc_state_str(uint8_t state) +{ + switch (state) + { + case BT_BAP_BIG_ENC_STATE_NO_ENC: + return "Not encrypted"; + case BT_BAP_BIG_ENC_STATE_BCODE_REQ: + return "Broadcast_Code required"; + case BT_BAP_BIG_ENC_STATE_DEC: + return "Decrypting"; + case BT_BAP_BIG_ENC_STATE_BAD_CODE: + return "Bad_Code (incorrect encryption key)"; + default: + return "unknown state"; + } +} + +static inline bool valid_bis_syncs(uint32_t bis_sync) +{ + if (bis_sync == BT_BAP_BIS_SYNC_NO_PREF) + { + return true; + } + + if (bis_sync > BIT_MASK(BT_ISO_MAX_GROUP_ISO_COUNT)) + { + return false; + } + + return true; +} + +bool bt_bap_broadcast_sink_has_ep(const struct bt_bap_ep *ep); +bool bt_bap_broadcast_source_has_ep(const struct bt_bap_ep *ep); +bool bt_bap_unicast_client_has_ep(const struct bt_bap_ep *ep); +bool bt_bap_unicast_server_has_ep(const struct bt_bap_ep *ep); +struct bt_conn * +bt_bap_unicast_client_ep_get_conn(const struct bt_bap_ep *ep); +struct bt_conn * +bt_bap_unicast_server_ep_get_conn(const struct bt_bap_ep *ep); diff --git a/libs/zblue/audio/bap_iso.c b/libs/zblue/audio/bap_iso.c new file mode 100644 index 0000000000000..79e9703c8ffce --- /dev/null +++ b/libs/zblue/audio/bap_iso.c @@ -0,0 +1,455 @@ +/**************************************************************************** + * libs/zblue/audio/bap_iso.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* bap_iso.c - BAP ISO handling */ + +/* Copyright (c) 2022 Codecoup + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bap_iso.h" +#include "audio_internal.h" +#include "bap_endpoint.h" +#include "bap_internal.h" + +LOG_MODULE_REGISTER(bt_bap_iso, CONFIG_BT_BAP_ISO_LOG_LEVEL); + +/* TODO: Optimize the ISO_POOL_SIZE */ +#define ISO_POOL_SIZE CONFIG_BT_ISO_MAX_CHAN + +static struct bt_bap_iso iso_pool[ISO_POOL_SIZE]; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +struct bt_bap_iso *bt_bap_iso_new(void) +{ + struct bt_bap_iso *iso = NULL; + + for (size_t i = 0U; i < ARRAY_SIZE(iso_pool); i++) + { + if (atomic_cmpxchg_value(&iso_pool[i].ref, 0, 1)) + { + iso = &iso_pool[i]; + break; + } + } + + if (!iso) + { + return NULL; + } + + (void)memset(iso, 0, offsetof(struct bt_bap_iso, ref)); + + return iso; +} + +struct bt_bap_iso *bt_bap_iso_ref(struct bt_bap_iso *iso) +{ + int32_t old; + + DEBUGASSERT(iso != NULL); + + /* Reference counter must be checked to avoid incrementing ref from + * zero, then we should return NULL instead. Loop on clear-and-set in case + * someone has modified the reference count since the read, and start over + * again when that happens. + */ + + do + { + old = atomic_read(&iso->ref); + + if (!old) + { + return NULL; + } + } + while (!atomic_cmpxchg_value(&iso->ref, old, old + 1)); + + return iso; +} + +void bt_bap_iso_unref(struct bt_bap_iso *iso) +{ + int32_t old; + + DEBUGASSERT(iso != NULL); + + old = atomic_sub(&iso->ref, 1); + + DEBUGASSERT(old > 0); +} + +void bt_bap_iso_foreach(bt_bap_iso_func_t func, void *user_data) +{ + for (size_t i = 0U; i < ARRAY_SIZE(iso_pool); i++) + { + struct bt_bap_iso *iso = bt_bap_iso_ref(&iso_pool[i]); + bool iter; + + if (!iso) + { + continue; + } + + iter = func(iso, user_data); + bt_bap_iso_unref(iso); + + if (!iter) + { + return; + } + } +} + +struct bt_bap_iso_find_param +{ + struct bt_bap_iso *iso; + bt_bap_iso_func_t func; + void *user_data; +}; + +static bool bt_bap_iso_find_cb(struct bt_bap_iso *iso, void *user_data) +{ + struct bt_bap_iso_find_param *param = user_data; + bool found; + + found = param->func(iso, param->user_data); + if (found) + { + param->iso = bt_bap_iso_ref(iso); + } + + return !found; +} + +struct bt_bap_iso *bt_bap_iso_find(bt_bap_iso_func_t func, void *user_data) +{ + struct bt_bap_iso_find_param param = + { + .iso = NULL, + .func = func, + .user_data = user_data, + }; + + bt_bap_iso_foreach(bt_bap_iso_find_cb, ¶m); + + return param.iso; +} + +void bt_bap_iso_init(struct bt_bap_iso *iso, struct bt_iso_chan_ops *ops) +{ + iso->chan.ops = ops; + iso->chan.qos = &iso->qos; + + /* Setup the QoS for both Tx and Rx + * This is due to the limitation in the ISO API where pointers like the + * `qos->tx` shall be initialized before the CIS is created + */ + + iso->chan.qos->rx = &iso->rx.qos; + iso->chan.qos->tx = &iso->tx.qos; +} + +static struct bt_bap_iso_dir *bap_iso_get_iso_dir( + bool unicast_client, struct bt_bap_iso *iso, enum bt_audio_dir dir) +{ + /* TODO FIX FOR CLIENT */ + + if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && unicast_client) + { + /* For the unicast client, the direction and tx/rx is reversed */ + + if (dir == BT_AUDIO_DIR_SOURCE) + { + return &iso->rx; + } + else + { + return &iso->tx; + } + } + + if (dir == BT_AUDIO_DIR_SINK) + { + return &iso->rx; + } + else + { + return &iso->tx; + } +} + +void bt_bap_setup_iso_data_path(struct bt_bap_stream *stream) +{ + const struct bt_audio_codec_cfg *codec_cfg = stream->codec_cfg; + struct bt_bap_ep *ep = stream->ep; + struct bt_bap_iso *bap_iso = ep->iso; + const bool is_unicast_client = + IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && + bt_bap_unicast_client_has_ep(ep); + struct bt_bap_iso_dir *iso_dir = bap_iso_get_iso_dir(is_unicast_client, + bap_iso, ep->dir); + struct bt_iso_chan_path path = + { + 0 + }; + + uint8_t dir; + int err; + + if (iso_dir == &bap_iso->rx) + { + dir = BT_HCI_DATAPATH_DIR_CTLR_TO_HOST; + } + else + { + dir = BT_HCI_DATAPATH_DIR_HOST_TO_CTLR; + } + + path.pid = codec_cfg->path_id; + + /* Configure the data path to either use the controller for transcoding, or + * set the path to be transparent to indicate that the transcoding happens + * somewhere else + */ + + if (codec_cfg->ctlr_transcode) + { + path.format = codec_cfg->id; + path.cid = codec_cfg->cid; + path.vid = codec_cfg->vid; + path.cc_len = codec_cfg->data_len; + path.cc = codec_cfg->data; + } + else + { + path.format = BT_HCI_CODING_FORMAT_TRANSPARENT; + } + + err = bt_iso_setup_data_path(&bap_iso->chan, dir, &path); + if (err != 0) + { + LOG_ERR("Failed to set ISO data path for ep %p and codec_cfg %p: %d", + ep, + codec_cfg, err); + } +} + +void bt_bap_remove_iso_data_path(struct bt_bap_stream *stream) +{ + struct bt_bap_ep *ep = stream->ep; + struct bt_bap_iso *bap_iso = ep->iso; + const bool is_unicast_client = + IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && + bt_bap_unicast_client_has_ep(ep); + struct bt_bap_iso_dir *iso_dir = bap_iso_get_iso_dir(is_unicast_client, + bap_iso, ep->dir); + uint8_t dir; + int err; + + if (iso_dir == &bap_iso->rx) + { + dir = BT_HCI_DATAPATH_DIR_CTLR_TO_HOST; + } + else + { + dir = BT_HCI_DATAPATH_DIR_HOST_TO_CTLR; + } + + err = bt_iso_remove_data_path(&bap_iso->chan, dir); + if (err != 0) + { + LOG_ERR("Failed to remove ISO data path for ep %p: %d", ep, err); + } +} + +static bool is_unicast_client_ep(struct bt_bap_ep *ep) +{ + return IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && + bt_bap_unicast_client_has_ep(ep); +} + +void bt_bap_iso_bind_ep(struct bt_bap_iso *iso, struct bt_bap_ep *ep) +{ + const struct bt_bap_ep *paired_ep; + struct bt_bap_iso_dir *iso_dir; + + DEBUGASSERT(ep != NULL); + DEBUGASSERT(iso != NULL); + DEBUGASSERT(ep->iso == NULL); + DEBUGASSERT(ep->dir == BT_AUDIO_DIR_SINK || ep->dir == + BT_AUDIO_DIR_SOURCE); + + LOG_DBG("iso %p ep %p dir %s", iso, ep, bt_audio_dir_to_str(ep->dir)); + + iso_dir = bap_iso_get_iso_dir(is_unicast_client_ep(ep), iso, ep->dir); + DEBUGASSERT(iso_dir->ep == NULL); + iso_dir->ep = ep; + + ep->iso = bt_bap_iso_ref(iso); + + paired_ep = bt_bap_iso_get_paired_ep(ep); + if (paired_ep != NULL && paired_ep->stream != NULL && + paired_ep->stream->conn != NULL && + ep->stream != NULL && ep->stream->conn != NULL) + { + DEBUGASSERT(paired_ep->stream->conn == ep->stream->conn); + } +} + +void bt_bap_iso_unbind_ep(struct bt_bap_iso *iso, struct bt_bap_ep *ep) +{ + struct bt_bap_iso_dir *iso_dir; + + DEBUGASSERT(ep != NULL); + DEBUGASSERT(iso != NULL); + DEBUGASSERT(ep->iso == iso); + DEBUGASSERT(ep->dir == BT_AUDIO_DIR_SINK || ep->dir == + BT_AUDIO_DIR_SOURCE); + + LOG_DBG("iso %p ep %p dir %s", iso, ep, bt_audio_dir_to_str(ep->dir)); + + iso_dir = bap_iso_get_iso_dir(is_unicast_client_ep(ep), iso, ep->dir); + DEBUGASSERT(iso_dir->ep == ep); + iso_dir->ep = NULL; + + bt_bap_iso_unref(ep->iso); + ep->iso = NULL; +} + +struct bt_bap_ep *bt_bap_iso_get_ep( + bool unicast_client, struct bt_bap_iso *iso, enum bt_audio_dir dir) +{ + struct bt_bap_iso_dir *iso_dir; + + DEBUGASSERT(dir == BT_AUDIO_DIR_SINK || dir == BT_AUDIO_DIR_SOURCE); + + LOG_DBG("iso %p dir %s", iso, bt_audio_dir_to_str(dir)); + + iso_dir = bap_iso_get_iso_dir(unicast_client, iso, dir); + + return iso_dir->ep; +} + +struct bt_bap_ep *bt_bap_iso_get_paired_ep(const struct bt_bap_ep *ep) +{ + if (ep->iso->rx.ep == ep) + { + return ep->iso->tx.ep; + } + else + { + return ep->iso->rx.ep; + } +} + +#if defined(CONFIG_BT_BAP_UNICAST_CLIENT) +void bt_bap_iso_bind_stream( + struct bt_bap_iso *bap_iso, struct bt_bap_stream *stream, + enum bt_audio_dir dir) +{ + struct bt_bap_iso_dir *bap_iso_ep; + + DEBUGASSERT(stream != NULL); + DEBUGASSERT(bap_iso != NULL); + DEBUGASSERT(stream->iso == NULL); + + LOG_DBG("bap_iso %p stream %p dir %s", bap_iso, stream, + bt_audio_dir_to_str(dir)); + + /* For the unicast client, the direction and tx/rx is reversed */ + + if (dir == BT_AUDIO_DIR_SOURCE) + { + bap_iso_ep = &bap_iso->rx; + } + else + { + bap_iso_ep = &bap_iso->tx; + } + + DEBUGASSERT(bap_iso_ep->stream == NULL); + bap_iso_ep->stream = stream; + + stream->iso = &bt_bap_iso_ref(bap_iso)->chan; +} + +void bt_bap_iso_unbind_stream(struct bt_bap_stream *stream, + enum bt_audio_dir dir) +{ + struct bt_bap_iso_dir *bap_iso_ep; + struct bt_bap_iso *bap_iso; + + DEBUGASSERT(stream != NULL); + DEBUGASSERT(stream->iso != NULL); + + bap_iso = CONTAINER_OF(stream->iso, struct bt_bap_iso, chan); + + LOG_DBG("bap_iso %p stream %p dir %s", bap_iso, stream, + bt_audio_dir_to_str(dir)); + + /* For the unicast client, the direction and tx/rx is reversed */ + + if (dir == BT_AUDIO_DIR_SOURCE) + { + bap_iso_ep = &bap_iso->rx; + } + else + { + bap_iso_ep = &bap_iso->tx; + } + + DEBUGASSERT(bap_iso_ep->stream == stream); + bap_iso_ep->stream = NULL; + + bt_bap_iso_unref(bap_iso); + stream->iso = NULL; +} + +struct bt_bap_stream *bt_bap_iso_get_stream(struct bt_bap_iso *iso, + enum bt_audio_dir dir) +{ + DEBUGASSERT(dir == BT_AUDIO_DIR_SINK || dir == BT_AUDIO_DIR_SOURCE); + + LOG_DBG("iso %p dir %s", iso, bt_audio_dir_to_str(dir)); + + /* For the unicast client, the direction and tx/rx is reversed */ + + if (dir == BT_AUDIO_DIR_SOURCE) + { + return iso->rx.stream; + } + else + { + return iso->tx.stream; + } +} +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT */ diff --git a/libs/zblue/audio/bap_iso.h b/libs/zblue/audio/bap_iso.h new file mode 100644 index 0000000000000..f2af9dc670901 --- /dev/null +++ b/libs/zblue/audio/bap_iso.h @@ -0,0 +1,78 @@ +/**************************************************************************** + * libs/zblue/audio/bap_iso.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* @file + * @brief Internal APIs for BAP ISO handling + * + * Copyright (c) 2022 Codecoup Copyright (c) 2023-2024 Nordic Semiconductor + * ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include + +struct bt_bap_iso_dir +{ + struct bt_bap_stream *stream; + struct bt_bap_ep *ep; + struct bt_iso_chan_io_qos qos; + uint8_t cc[CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE]; +}; + +struct bt_bap_iso +{ + struct bt_iso_chan chan; + struct bt_iso_chan_qos qos; + + struct bt_bap_iso_dir rx; + struct bt_bap_iso_dir tx; + + /* Must be at the end so that everything else in the structure can be + * memset to zero without affecting the ref. + */ + + atomic_t ref; +}; + +typedef bool (*bt_bap_iso_func_t)(struct bt_bap_iso *iso, void *user_data); + +struct bt_bap_iso *bt_bap_iso_new(void); +struct bt_bap_iso *bt_bap_iso_ref(struct bt_bap_iso *iso); +void bt_bap_iso_unref(struct bt_bap_iso *iso); +void bt_bap_iso_foreach(bt_bap_iso_func_t func, void *user_data); +struct bt_bap_iso *bt_bap_iso_find(bt_bap_iso_func_t func, void *user_data); +void bt_bap_iso_init(struct bt_bap_iso *iso, struct bt_iso_chan_ops *ops); +void bt_bap_iso_bind_ep(struct bt_bap_iso *iso, struct bt_bap_ep *ep); +void bt_bap_setup_iso_data_path(struct bt_bap_stream *stream); +void bt_bap_remove_iso_data_path(struct bt_bap_stream *stream); +void bt_bap_iso_unbind_ep(struct bt_bap_iso *iso, struct bt_bap_ep *ep); +struct bt_bap_ep *bt_bap_iso_get_ep( + bool unicast_client, struct bt_bap_iso *iso, enum bt_audio_dir dir); + +struct bt_bap_ep *bt_bap_iso_get_paired_ep(const struct bt_bap_ep *ep); + +/* Unicast client-only functions */ + +void bt_bap_iso_bind_stream( + struct bt_bap_iso *bap_iso, struct bt_bap_stream *stream, + enum bt_audio_dir dir); +void bt_bap_iso_unbind_stream(struct bt_bap_stream *stream, + enum bt_audio_dir dir); +struct bt_bap_stream *bt_bap_iso_get_stream(struct bt_bap_iso *iso, + enum bt_audio_dir dir); diff --git a/libs/zblue/audio/bap_scan_delegator.c b/libs/zblue/audio/bap_scan_delegator.c new file mode 100644 index 0000000000000..388c0eedbbd17 --- /dev/null +++ b/libs/zblue/audio/bap_scan_delegator.c @@ -0,0 +1,2735 @@ +/**************************************************************************** + * libs/zblue/audio/bap_scan_delegator.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Bluetooth Scan Delegator */ + +/* Copyright (c) 2019 Bose Corporation + * Copyright (c) 2021-2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(bt_bap_scan_delegator, + CONFIG_BT_BAP_SCAN_DELEGATOR_LOG_LEVEL); + +#if !defined(CONFIG_ARCH_POSIX) && defined(CONFIG_BT_BAP_SCAN_DELEGATOR_LOG_LEVEL_DBG) && \ + defined(CONFIG_LOG) && !defined(CONFIG_LOG_MODE_DEFERRED) +#warning Logging when non-deferred log mode is selected is not fully supported. \ + Use the CONFIG_LOG_MODE_DEFERRED Kconfig option when this feature is enabled. +#endif + +#include "common/bt_str.h" + +#include "audio_internal.h" +#include "bap_internal.h" +#include "common/bt_settings_commit.h" + +#define PAST_TIMEOUT SEC2TICK(10) + +#define SCAN_DELEGATOR_BUF_SEM_TIMEOUT MSEC2TICK(CONFIG_BT_BAP_SCAN_DELEGATOR_BUF_TIMEOUT) +NET_BUF_SIMPLE_DEFINE_STATIC(read_buf, BT_ATT_MAX_ATTRIBUTE_LEN); + +struct bass_recv_state_flags +{ + bool updated : 1; +}; + +struct bass_client +{ + bt_addr_le_t addr; + uint8_t id; + + struct bass_recv_state_flags flags; +}; + +struct bass_recv_state_internal +{ + const struct bt_gatt_attr *attr; + + /* `active` determines whether the `state` field and related values related + * to that are valid, but not the mutex, clients or notify_work + */ + + bool active; + uint8_t index; + + /* Determines whether the remote has requested a PA sync request and app + * has accepted + */ + + bool pa_sync_requested; + struct bt_bap_scan_delegator_recv_state state; + uint8_t broadcast_code[BT_ISO_BROADCAST_CODE_SIZE]; + + /** Requested BIS sync bitfield for each subgroup */ + + uint32_t requested_bis_sync[CONFIG_BT_BAP_BASS_MAX_SUBGROUPS]; + + /* Mutex (reentrant Locking) ensure multiple threads to safely access + * receive state data + */ + + rmutex_t mutex; + struct bass_client clients[CONFIG_BT_MAX_PAIRED]; + + struct k_work_delayable notify_work; +}; + +struct bt_bap_scan_delegator_inst +{ + uint8_t next_src_id; + struct bass_recv_state_internal + recv_states[CONFIG_BT_BAP_SCAN_DELEGATOR_RECV_STATE_COUNT]; +}; + +enum scan_delegator_flag +{ + SCAN_DELEGATOR_FLAG_REGISTERED, + SCAN_DELEGATOR_FLAG_UNREGISTERING, + + SCAN_DELEGATOR_FLAG_NUM, +}; + +static atomic_define(scan_delegator_flags, SCAN_DELEGATOR_FLAG_NUM); + +static struct bt_bap_scan_delegator_inst scan_delegator; +static struct bt_bap_scan_delegator_cb *scan_delegator_cbs; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void set_receive_state_changed( + struct bass_recv_state_internal *internal_state) +{ + bool schedule_work = false; + + ARRAY_FOR_EACH_PTR(internal_state->clients, client) + { + /* Check for `BT_ADDR_LE_NONE` to determine if the client has been + * initialized + */ + + if (!bt_addr_le_eq(&client->addr, BT_ADDR_LE_NONE)) + { + struct bt_conn *conn; + + client->flags.updated = true; + + if (schedule_work) + { + continue; + } + + /* Schedule work for connected subscribed devices only. */ + + conn = bt_conn_lookup_addr_le(client->id, &client->addr); + if (conn != NULL) + { + struct bt_conn_info conn_info; + __maybe_unused int err; + + err = bt_conn_get_info(conn, &conn_info); + DEBUGASSERT(err == 0); + + if (conn_info.state == BT_CONN_STATE_CONNECTED && + bt_gatt_is_subscribed(conn, internal_state->attr, + BT_GATT_CCC_NOTIFY)) + { + schedule_work = true; + } + + bt_conn_unref(conn); + } + } + } + + if (schedule_work) + { + __maybe_unused int err; + + /* We may schedule the same work multiple times, but that is OK as + * scheduling the same work multiple times is a no-op + */ + + err = k_work_schedule(&internal_state->notify_work, 0); + DEBUGASSERT(err >= 0); + } +} + +/** + * @brief Returns whether a value's bits is a subset of another value's bits + * + * @param a The subset to check + * @param b The bits to check against + * + * @return True if @p a is a bitwise subset of @p b + */ + +static bool bits_subset_of(uint32_t a, uint32_t b) +{ + return (((a) & (~(b))) == 0); +} + +static bool bis_syncs_unique_or_no_pref(uint32_t requested_bis_syncs, + uint32_t aggregated_bis_syncs) +{ + if (requested_bis_syncs == 0U || aggregated_bis_syncs == 0U) + { + return true; + } + + if (requested_bis_syncs == BT_BAP_BIS_SYNC_NO_PREF && + aggregated_bis_syncs == BT_BAP_BIS_SYNC_NO_PREF) + { + return true; + } + + return (requested_bis_syncs & aggregated_bis_syncs) == 0U; +} + +static bool valid_bis_sync_request(uint32_t requested_bis_syncs, + uint32_t aggregated_bis_syncs) +{ + /* Verify that the request BIS sync indexes are unique or no preference */ + + if (!bis_syncs_unique_or_no_pref(requested_bis_syncs, + aggregated_bis_syncs)) + { + LOG_DBG("Duplicate BIS index 0x%08x (aggregated 0x%08X)", + requested_bis_syncs, + aggregated_bis_syncs); + return false; + } + + if (requested_bis_syncs != BT_BAP_BIS_SYNC_NO_PREF && + aggregated_bis_syncs == BT_BAP_BIS_SYNC_NO_PREF) + { + LOG_DBG("Invalid BIS index 0x%08X mixing BT_BAP_BIS_SYNC_NO_PREF and " + "specific BIS", + requested_bis_syncs); + return false; + } + + if (!valid_bis_syncs(requested_bis_syncs)) + { + LOG_DBG("Invalid BIS sync: 0x%08X", requested_bis_syncs); + return false; + } + + return true; +} + +static void bt_debug_dump_recv_state( + struct bass_recv_state_internal *internal_state) +{ + __maybe_unused int err; + + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + if (internal_state->active) + { + const struct bt_bap_scan_delegator_recv_state *state = + &internal_state->state; + const bool is_bad_code = + state->encrypt_state == BT_BAP_BIG_ENC_STATE_BAD_CODE; + + LOG_DBG("Receive State[%d]: src ID %u, addr %s, adv_sid %u, " + "broadcast_id 0x%06X, " + "pa_sync_state %u, encrypt state %u%s%s, num_subgroups %u", + internal_state->index, state->src_id, + bt_addr_le_str(&state->addr), state->adv_sid, + state->broadcast_id, state->pa_sync_state, + state->encrypt_state, + is_bad_code ? ", bad code" : "", + is_bad_code ? bt_hex(state->bad_code, + sizeof(state->bad_code)) : "", + state->num_subgroups); + + for (uint8_t i = 0U; i < state->num_subgroups; i++) + { + const struct bt_bap_bass_subgroup *subgroup = &state->subgroups[i]; + + LOG_DBG("\tSubgroup[%u]: BIS sync %u (" + "requested %u), metadata_len %zu, " + "metadata: %s", + i, subgroup->bis_sync, + internal_state->requested_bis_sync[i], + subgroup->metadata_len, bt_hex(subgroup->metadata, + subgroup->metadata_len)); + } + } + else + { + LOG_DBG("Inactive receive state"); + } + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); +} + +static void net_buf_put_recv_state( + const struct bass_recv_state_internal *internal_state) +{ + const struct bt_bap_scan_delegator_recv_state *state = + &internal_state->state; + + net_buf_simple_reset(&read_buf); + + DEBUGASSERT(internal_state); + + if (!internal_state->active) + { + /* Notify empty */ + + return; + } + + (void)net_buf_simple_add_u8(&read_buf, state->src_id); + (void)net_buf_simple_add_u8(&read_buf, state->addr.type); + (void)net_buf_simple_add_mem(&read_buf, &state->addr.a, + sizeof(state->addr.a)); + (void)net_buf_simple_add_u8(&read_buf, state->adv_sid); + (void)net_buf_simple_add_le24(&read_buf, state->broadcast_id); + (void)net_buf_simple_add_u8(&read_buf, state->pa_sync_state); + (void)net_buf_simple_add_u8(&read_buf, state->encrypt_state); + if (state->encrypt_state == BT_BAP_BIG_ENC_STATE_BAD_CODE) + { + (void)net_buf_simple_add_mem(&read_buf, &state->bad_code, + sizeof(state->bad_code)); + } + + (void)net_buf_simple_add_u8(&read_buf, state->num_subgroups); + for (int i = 0; i < state->num_subgroups; i++) + { + const struct bt_bap_bass_subgroup *subgroup = &state->subgroups[i]; + + (void)net_buf_simple_add_le32(&read_buf, subgroup->bis_sync); + (void)net_buf_simple_add_u8(&read_buf, subgroup->metadata_len); + (void)net_buf_simple_add_mem(&read_buf, subgroup->metadata, + subgroup->metadata_len); + } +} + +static void receive_state_updated(struct bt_conn *conn, + struct bass_recv_state_internal *internal_state) +{ + if (IS_ENABLED(CONFIG_BT_BAP_SCAN_DELEGATOR_LOG_LEVEL_DBG)) + { + bt_debug_dump_recv_state(internal_state); + } + + if (scan_delegator_cbs != NULL + && scan_delegator_cbs->recv_state_updated != NULL) + { + scan_delegator_cbs->recv_state_updated(conn, &internal_state->state); + } +} + +static void notify_work_handler(struct k_work *work) +{ + struct bass_recv_state_internal *internal_state = CONTAINER_OF( + k_work_delayable_from_work(work), struct bass_recv_state_internal, + notify_work); + __maybe_unused int err; + + if (atomic_test_bit(scan_delegator_flags, + SCAN_DELEGATOR_FLAG_UNREGISTERING) || + !atomic_test_bit(scan_delegator_flags, SCAN_DELEGATOR_FLAG_REGISTERED)) + { + LOG_DBG("Skipping work handler due to service not active"); + return; + } + + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + net_buf_put_recv_state(internal_state); + + ARRAY_FOR_EACH_PTR(internal_state->clients, client) + { + struct bt_conn_info conn_info; + struct bt_conn *conn; + + if (!client->flags.updated) + { + continue; + } + + DEBUGASSERT(!bt_addr_le_eq(&client->addr, BT_ADDR_LE_NONE)); + + conn = bt_conn_lookup_addr_le(client->id, &client->addr); + if (conn == NULL) + { + /* Not connected */ + + continue; + } + + err = bt_conn_get_info(conn, &conn_info); + DEBUGASSERT(err == 0); + + if (conn_info.state == BT_CONN_STATE_CONNECTED && + bt_gatt_is_subscribed(conn, internal_state->attr, + BT_GATT_CCC_NOTIFY)) + { + const int max_ntf_size = bt_att_get_max_notify_size(conn, + BT_ATT_CHAN_OPT_NONE); + uint16_t ntf_size; + + if (max_ntf_size < 0) + { + DEBUGASSERT(max_ntf_size != -EINVAL); + LOG_DBG("Failed to get max notification size: %d", + max_ntf_size); + bt_conn_unref(conn); + continue; + } + + ntf_size = MIN(max_ntf_size, read_buf.len); + + if (ntf_size < read_buf.len) + { + LOG_DBG("Sending truncated notification (%u/%u)", ntf_size, + read_buf.len); + } + + LOG_DBG("Sending bytes %u for %p", ntf_size, (void *)conn); + err = bt_gatt_notify_uuid(conn, BT_UUID_BASS_RECV_STATE, + internal_state->attr, + read_buf.data, ntf_size); + if (err == 0) + { + client->flags.updated = false; + } + else + { + LOG_DBG("Could not notify receive state: %d", err); + err = k_work_reschedule(&internal_state->notify_work, + USEC2TICK(conn_info.le.interval_us)); + DEBUGASSERT(err >= 0); + } + } + + bt_conn_unref(conn); + } + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); +} + +static void bis_sync_request_updated(struct bt_conn *conn, + const struct bass_recv_state_internal *internal_state) +{ + if (scan_delegator_cbs != NULL && scan_delegator_cbs->bis_sync_req != NULL) + { + scan_delegator_cbs->bis_sync_req(conn, &internal_state->state, + internal_state->requested_bis_sync); + } + else + { + LOG_WRN("bis_sync_req callback is missing"); + } +} + +static struct bass_client *get_bass_client( + struct bass_recv_state_internal *internal_state, uint8_t id, + const bt_addr_le_t *addr) +{ + ARRAY_FOR_EACH_PTR(internal_state->clients, client) + { + if (client->id == id && bt_addr_le_eq(&client->addr, addr)) + { + return client; + } + } + + return NULL; +} + +static struct bass_client *get_bass_client_by_conn( + struct bass_recv_state_internal *internal_state, struct bt_conn *conn) +{ + struct bt_conn_info conn_info; + __maybe_unused int err; + + err = bt_conn_get_info(conn, &conn_info); + DEBUGASSERT(err == 0); + + return get_bass_client(internal_state, conn_info.id, conn_info.le.dst); +} + +static void add_addr_to_client_list( + struct bass_recv_state_internal *internal_state, uint8_t id, + const bt_addr_le_t *addr) +{ + __maybe_unused bool added = false; + __maybe_unused int err; + + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + DEBUGASSERT(get_bass_client(internal_state, id, addr) == NULL); + + ARRAY_FOR_EACH_PTR(internal_state->clients, client) + { + if (bt_addr_le_eq(&client->addr, BT_ADDR_LE_NONE)) + { + bt_addr_le_copy(&client->addr, addr); + client->id = id; + added = true; + + LOG_DBG("Added addr %s to state %p", bt_addr_le_str(addr), + internal_state); + + break; + } + } + + DEBUGASSERT(added); + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); +} + +static void add_conn_to_client_list(const struct bt_conn *conn) +{ + struct bt_conn_info conn_info; + __maybe_unused int err; + + err = bt_conn_get_info(conn, &conn_info); + DEBUGASSERT(err == 0); + + ARRAY_FOR_EACH_PTR(scan_delegator.recv_states, internal_state) + { + add_addr_to_client_list(internal_state, + conn_info.id, + conn_info.le.dst); + } +} + +static void clear_bass_client(struct bass_client *client) +{ + bt_addr_le_copy(&client->addr, BT_ADDR_LE_NONE); + client->id = 0x00u; + client->flags = (struct bass_recv_state_flags) + { + 0 + }; +} + +static void rem_conn_from_client_list(const struct bt_conn *conn) +{ + struct bt_conn_info conn_info; + __maybe_unused int err; + + err = bt_conn_get_info(conn, &conn_info); + DEBUGASSERT(err == 0); + + ARRAY_FOR_EACH_PTR(scan_delegator.recv_states, internal_state) + { + struct bass_client *client; + + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + /* Client may be NULL if a device disconnects before security changed */ + + client = get_bass_client(internal_state, + conn_info.id, + conn_info.le.dst); + if (client != NULL) + { + clear_bass_client(client); + } + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + } +} + +#if defined(CONFIG_BT_SETTINGS) + +static void settings_foreach_bond_cb(const struct bt_bond_info *info, + void *data) +{ + uint8_t id = (uint8_t)POINTER_TO_UINT(data); + + ARRAY_FOR_EACH_PTR(scan_delegator.recv_states, internal_state) + { + const struct bass_client *client; + __maybe_unused int err; + + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + /* Client may exist if settings are loaded after connection. */ + + client = get_bass_client(internal_state, id, &info->addr); + if (client == NULL) + { + add_addr_to_client_list(internal_state, id, &info->addr); + } + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + } +} + +static int scan_delegator_settings_commit(void) +{ + size_t count; + + /* We cannot call BT APIs before it has been enabled */ + + if (!bt_is_ready()) + { + return 0; + } + + bt_id_get(NULL, &count); + DEBUGASSERT(count > 0U && count < UINT8_MAX); + + for (uint8_t id = 0U; id < count; id++) + { + bt_foreach_bond(id, settings_foreach_bond_cb, UINT_TO_POINTER(id)); + } + + LOG_DBG("Restored Scan Delegator client list from bonded devices"); + + /* TODO: Store and restore flags from settings */ + + return 0; +} + +/* Register settings handler with commit priority, BT_SETTINGS_CPRIO_2, + * to ensure settings_commit() runs after BT keys settings are loaded. + * Priority is reduced to ensure existing bonds are loaded first. + */ + +SETTINGS_STATIC_HANDLER_DEFINE_WITH_CPRIO(bt_bap_scan_delegator, + "bt/bap_scan_delegator", + NULL, + + NULL, + scan_delegator_settings_commit, + NULL, + BT_SETTINGS_CPRIO_2); +#endif /* CONFIG_BT_SETTINGS */ + +static void security_changed_cb(struct bt_conn *conn, bt_security_t level, + enum bt_security_err security_err) +{ + struct bt_conn_info conn_info; + __maybe_unused int err; + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + /* If there doesn't exist a bond, then this function is a no-op: We either + * add the bonded address or trigger notification work for bonded devices + */ + + if (!atomic_test_bit(scan_delegator_flags, SCAN_DELEGATOR_FLAG_REGISTERED)) + { + /* Not yet registered, ignore callback */ + + return; + } + + err = bt_conn_get_info(conn, &conn_info); + DEBUGASSERT(err == 0); + + const bool bonded = bt_le_bond_exists(conn_info.id, conn_info.le.dst); + + LOG_DBG("%s security changed err %d level %d (%sbonded)", + bt_addr_le_str(conn_info.le.dst), + security_err, level, bonded ? "" : "not "); + + if (security_err != BT_SECURITY_ERR_SUCCESS || level < BT_SECURITY_L2 || + !bonded) + { + return; + } + + ARRAY_FOR_EACH_PTR(scan_delegator.recv_states, internal_state) + { + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + /* Notify bonded clients after reconnect if the BRS is non-empty. */ + + if (internal_state->active) + { + struct bass_client *client; + + client = get_bass_client_by_conn(internal_state, conn); + if (client != NULL) + { + client->flags.updated = true; + err = k_work_schedule(&internal_state->notify_work, 0); + DEBUGASSERT(err >= 0); + } + } + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + } +} + +static void disconnected_cb(struct bt_conn *conn, uint8_t reason) +{ + struct bt_conn_info conn_info; + __maybe_unused int err; + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + if (!atomic_test_bit(scan_delegator_flags, SCAN_DELEGATOR_FLAG_REGISTERED)) + { + /* Not yet registered, ignore callback */ + + return; + } + + err = bt_conn_get_info(conn, &conn_info); + DEBUGASSERT(err == 0); + + const bool bonded = bt_le_bond_exists(conn_info.id, conn_info.le.dst); + + LOG_DBG("Disconnected: %s (reason %u)", bt_addr_le_str(conn_info.le.dst), + reason); + + if (!bonded) + { + rem_conn_from_client_list(conn); + } +} + +BT_CONN_CB_DEFINE(conn_callbacks) = +{ + .security_changed = security_changed_cb, + .disconnected = disconnected_cb, +}; + +static void pairing_complete_cb(struct bt_conn *conn, bool bonded) +{ + struct bass_recv_state_internal *internal_state = + &scan_delegator.recv_states[0]; + __maybe_unused int err; + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + LOG_DBG("%s paired (%sbonded)", bt_conn_dst_str(conn), + bonded ? "" : "not "); + + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + /* We can just search the first state, as the address is always added and + * removed from all states + */ + + if (get_bass_client_by_conn(internal_state, conn) == NULL) + { + /* This will add the address to all internal states */ + + add_conn_to_client_list(conn); + } + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); +} + +static void bond_deleted_cb(uint8_t id, const bt_addr_le_t *addr) +{ + ARRAY_FOR_EACH_PTR(scan_delegator.recv_states, internal_state) + { + struct bass_client *client; + __maybe_unused int err; + + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + client = get_bass_client(internal_state, id, addr); + DEBUGASSERT(client != NULL); + + clear_bass_client(client); + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + } +} + +static uint8_t next_src_id(void) +{ + uint8_t next_src_id; + bool unique = false; + + while (!unique) + { + next_src_id = scan_delegator.next_src_id++; + unique = true; + for (size_t i = 0U; i < ARRAY_SIZE(scan_delegator.recv_states); i++) + { + if (scan_delegator.recv_states[i].active && + scan_delegator.recv_states[i].state.src_id == next_src_id) + { + unique = false; + break; + } + } + } + + return next_src_id; +} + +static struct bass_recv_state_internal *bass_lookup_src_id(uint8_t src_id) +{ + for (size_t i = 0U; i < ARRAY_SIZE(scan_delegator.recv_states); i++) + { + if (scan_delegator.recv_states[i].active && + scan_delegator.recv_states[i].state.src_id == src_id) + { + return &scan_delegator.recv_states[i]; + } + } + + return NULL; +} + +/* BAP 6.5.4 states that the combined Source_Address_Type, Source_Adv_SID, + * and Broadcast_ID fields are what makes a receive state unique. + */ + +static struct bass_recv_state_internal *bass_lookup_state( + uint8_t addr_type, uint8_t adv_sid, uint32_t broadcast_id) +{ + struct bass_recv_state_internal *res = NULL; + + ARRAY_FOR_EACH_PTR(scan_delegator.recv_states, recv_state_internal) + { + __maybe_unused int err; + + if (!recv_state_internal->active) + { + continue; + } + + err = nxrmutex_ticklock(&recv_state_internal->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + if (recv_state_internal->state.addr.type == addr_type && + recv_state_internal->state.adv_sid == adv_sid && + recv_state_internal->state.broadcast_id == broadcast_id) + { + res = recv_state_internal; + } + + err = nxrmutex_unlock(&recv_state_internal->mutex); + DEBUGASSERT(err == 0); + + if (res != NULL) + { + break; + } + } + + return res; +} + +static struct bass_recv_state_internal *get_free_recv_state(void) +{ + for (size_t i = 0U; i < ARRAY_SIZE(scan_delegator.recv_states); i++) + { + struct bass_recv_state_internal *free_internal_state = + &scan_delegator.recv_states[i]; + + if (!free_internal_state->active) + { + return free_internal_state; + } + } + + return NULL; +} + +static void free_recv_state(struct bass_recv_state_internal *state) +{ + state->active = false; + state->pa_sync_requested = false; + (void)memset(&state->state, 0, sizeof(state->state)); + (void)memset(state->broadcast_code, 0, sizeof(state->broadcast_code)); + (void)memset(state->requested_bis_sync, 0, + sizeof(state->requested_bis_sync)); +} + +static bool supports_past(struct bt_conn *conn, uint8_t pa_sync_val) +{ + if (IS_ENABLED(CONFIG_BT_PER_ADV_SYNC_TRANSFER_RECEIVER)) + { + struct bt_le_local_features local_features; + struct bt_conn_remote_info remote_info; + int err; + + err = bt_le_get_local_features(&local_features); + if (err != 0) + { + LOG_DBG("Failed to get local features: %d", err); + return false; + } + + err = bt_conn_get_remote_info(conn, &remote_info); + if (err != 0) + { + LOG_DBG("Failed to get remote info: %d", err); + return false; + } + + LOG_DBG("%p remote %s PAST, local %s PAST (req %u)", (void *)conn, + BT_FEAT_LE_PAST_SEND(remote_info.le.features) ? + "supports" : "does not support", + BT_FEAT_LE_PAST_RECV(local_features.features) ? + "supports" : "does not support", + pa_sync_val); + + return pa_sync_val == BT_BAP_BASS_PA_REQ_SYNC_PAST && + BT_FEAT_LE_PAST_SEND(remote_info.le.features) && + BT_FEAT_LE_PAST_RECV(local_features.features); + } + else + { + return false; + } +} + +static int pa_sync_request(struct bt_conn *conn, + const struct bt_bap_scan_delegator_recv_state *state, + uint8_t pa_sync_val, + uint16_t pa_interval) +{ + int err = -EACCES; + + if (scan_delegator_cbs != NULL && scan_delegator_cbs->pa_sync_req != NULL) + { + const bool past_supported = supports_past(conn, pa_sync_val); + + err = scan_delegator_cbs->pa_sync_req(conn, state, past_supported, + pa_interval); + } + else + { + LOG_WRN("pa_sync_req callback is missing, rejecting PA sync request"); + } + + return err; +} + +static int pa_sync_term_request(struct bt_conn *conn, + const struct bt_bap_scan_delegator_recv_state *state) +{ + int err = -EACCES; + + if (scan_delegator_cbs != NULL && scan_delegator_cbs->pa_sync_req != NULL) + { + err = scan_delegator_cbs->pa_sync_term_req(conn, state); + } + else + { + LOG_WRN("pa_sync_term_req callback is missing, rejecting PA sync term " + "request"); + } + + return err; +} + +static int scan_delegator_add_src(struct bt_conn *conn, + struct net_buf_simple *buf) +{ + struct bass_recv_state_internal *internal_state = NULL; + struct bt_bap_scan_delegator_recv_state *state; + bt_addr_le_t *addr; + uint8_t pa_sync; + uint16_t pa_interval; + uint32_t aggregated_bis_syncs = 0U; + uint32_t broadcast_id; + bool bis_sync_requested; + uint16_t total_len; + struct bt_bap_bass_cp_add_src *add_src; + uint8_t adv_sid; + int err; + + /* subtract 1 as the opcode has already been pulled */ + + if (buf->len < sizeof(struct bt_bap_bass_cp_add_src) - 1) + { + LOG_DBG("Invalid length %u", buf->size); + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); + } + + add_src = (void *)(buf->data - 1); + total_len = sizeof(struct bt_bap_bass_cp_add_src) - 1; + for (int i = 0; i < add_src->num_subgroups; i++) + { + struct bt_bap_bass_cp_subgroup *subgroup; + uint16_t index = total_len; + + total_len += sizeof(struct bt_bap_bass_cp_subgroup); + if (total_len > buf->len) + { + LOG_DBG("Invalid length %u", buf->len); + + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); + } + + subgroup = (void *)&buf->data[index]; + total_len += subgroup->metadata_len; + if (total_len > buf->len) + { + LOG_DBG("Invalid length %u", buf->len); + + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); + } + } + + if (total_len != buf->len) + { + LOG_DBG("Invalid length %u", buf->len); + + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); + } + + addr = net_buf_simple_pull_mem(buf, sizeof(*addr)); + if (addr->type > BT_ADDR_LE_RANDOM) + { + LOG_DBG("Invalid address type %u", addr->type); + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + + adv_sid = net_buf_simple_pull_u8(buf); + if (adv_sid > BT_GAP_SID_MAX) + { + LOG_DBG("Invalid adv SID %u", adv_sid); + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + + broadcast_id = net_buf_simple_pull_le24(buf); + + internal_state = bass_lookup_state(addr->type, adv_sid, broadcast_id); + if (internal_state != NULL) + { + LOG_DBG("Adding addr type= " + "0x%02X adv_sid=0x%02X and broadcast_id=0x%06X would " + "result in duplication", + addr->type, adv_sid, broadcast_id); + + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); + } + + internal_state = get_free_recv_state(); + if (internal_state == NULL) + { + LOG_DBG("Could not get free receive state"); + + return BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES); + } + + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + state = &internal_state->state; + + state->src_id = next_src_id(); + bt_addr_le_copy(&state->addr, addr); + state->adv_sid = adv_sid; + state->broadcast_id = broadcast_id; + + pa_sync = net_buf_simple_pull_u8(buf); + if (pa_sync > BT_BAP_BASS_PA_REQ_SYNC) + { + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + LOG_DBG("Invalid PA sync value %u", pa_sync); + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + + pa_interval = net_buf_simple_pull_le16(buf); + + state->num_subgroups = net_buf_simple_pull_u8(buf); + if (state->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) + { + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + LOG_WRN("Too many subgroups %u/%u", state->num_subgroups, + CONFIG_BT_BAP_BASS_MAX_SUBGROUPS); + return BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES); + } + + bis_sync_requested = false; + for (int i = 0; i < state->num_subgroups; i++) + { + struct bt_bap_bass_subgroup *subgroup = &state->subgroups[i]; + uint8_t *metadata; + + internal_state->requested_bis_sync[i] = net_buf_simple_pull_le32(buf); + + if (internal_state->requested_bis_sync[i] && + pa_sync == BT_BAP_BASS_PA_REQ_NO_SYNC) + { + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + LOG_DBG("Cannot sync to BIS without PA"); + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + + if (internal_state->requested_bis_sync[i] != 0U) + { + bis_sync_requested = true; + } + + if (!valid_bis_sync_request(internal_state->requested_bis_sync[i], + aggregated_bis_syncs)) + { + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + LOG_DBG("Invalid BIS Sync request[%d]", i); + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + + aggregated_bis_syncs |= internal_state->requested_bis_sync[i]; + + subgroup->metadata_len = net_buf_simple_pull_u8(buf); + + if (subgroup->metadata_len > + CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE) + { + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + LOG_WRN("Metadata too long %u/%u", subgroup->metadata_len, + CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE); + return BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES); + } + + metadata = net_buf_simple_pull_mem(buf, subgroup->metadata_len); + (void)memcpy(subgroup->metadata, metadata, subgroup->metadata_len); + } + + if (scan_delegator_cbs != NULL && scan_delegator_cbs->add_source != NULL) + { + /* Unlock mutex to avoid potential deadlock on app callback */ + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + err = scan_delegator_cbs->add_source(conn, state); + if (err != 0) + { + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + (void)memset(state, 0, sizeof(*state)); + internal_state->active = false; + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + LOG_DBG("add_source callback rejected: 0x%02x", err); + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); + } + + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + } + + /* The active flag shall be set before any application callbacks, so that + * any calls for the receive state can be processed + */ + + internal_state->active = true; + + if (pa_sync != BT_BAP_BASS_PA_REQ_NO_SYNC) + { + /* Unlock mutex to avoid potential deadlock on app callback */ + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + err = pa_sync_request(conn, state, pa_sync, pa_interval); + if (err != 0) + { + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + free_recv_state(internal_state); + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + LOG_DBG("PA sync %u from %p was rejected with reason %d", pa_sync, + (void *)conn, err); + + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); + } + + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + internal_state->pa_sync_requested = true; + } + + set_receive_state_changed(internal_state); + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + LOG_DBG("New source added"); + + /* app callback */ + + receive_state_updated(conn, internal_state); + + if (bis_sync_requested) + { + bis_sync_request_updated(conn, internal_state); + } + + return 0; +} + +static int scan_delegator_mod_src(struct bt_conn *conn, + struct net_buf_simple *buf) +{ + uint32_t requested_bis_sync[CONFIG_BT_BAP_BASS_MAX_SUBGROUPS] = + { + }; + + struct bt_bap_scan_delegator_recv_state backup_state; + bool backup_pa_sync_requested; + struct bass_recv_state_internal *internal_state; + struct bt_bap_scan_delegator_recv_state *state; + uint8_t src_id; + bool state_changed = false; + uint16_t pa_interval; + uint8_t num_subgroups; + struct bt_bap_bass_subgroup subgroups[CONFIG_BT_BAP_BASS_MAX_SUBGROUPS] = + { + 0 + }; + + uint8_t pa_sync; + uint32_t aggregated_bis_syncs = 0U; + bool bis_sync_change_requested; + uint16_t total_len; + struct bt_bap_bass_cp_mod_src *mod_src; + int err; + + /* subtract 1 as the opcode has already been pulled */ + + if (buf->len < sizeof(struct bt_bap_bass_cp_mod_src) - 1) + { + LOG_DBG("Invalid length %u", buf->len); + + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); + } + + mod_src = (void *)(buf->data - 1); + total_len = sizeof(struct bt_bap_bass_cp_mod_src) - 1; + for (int i = 0; i < mod_src->num_subgroups; i++) + { + struct bt_bap_bass_cp_subgroup *subgroup; + uint16_t index = total_len; + + total_len += sizeof(struct bt_bap_bass_cp_subgroup); + if (total_len > buf->len) + { + LOG_DBG("Invalid length %u", buf->len); + + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); + } + + subgroup = (void *)&buf->data[index]; + total_len += subgroup->metadata_len; + if (total_len > buf->len) + { + LOG_DBG("Invalid length %u", buf->len); + + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); + } + } + + if (total_len != buf->len) + { + LOG_DBG("Invalid length %u", buf->len); + + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); + } + + src_id = net_buf_simple_pull_u8(buf); + internal_state = bass_lookup_src_id(src_id); + + LOG_DBG("src_id %u", src_id); + + if (internal_state == NULL) + { + LOG_DBG("Could not find state by src id %u", src_id); + + return BT_GATT_ERR(BT_BAP_BASS_ERR_INVALID_SRC_ID); + } + + pa_sync = net_buf_simple_pull_u8(buf); + if (pa_sync > BT_BAP_BASS_PA_REQ_SYNC) + { + LOG_DBG("Invalid PA sync value %u", pa_sync); + + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + + pa_interval = net_buf_simple_pull_le16(buf); + + num_subgroups = net_buf_simple_pull_u8(buf); + if (num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) + { + LOG_WRN("Too many subgroups %u/%u", num_subgroups, + CONFIG_BT_BAP_BASS_MAX_SUBGROUPS); + return BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES); + } + + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + bis_sync_change_requested = false; + for (int i = 0; i < num_subgroups; i++) + { + struct bt_bap_bass_subgroup *subgroup = &subgroups[i]; + uint8_t *metadata; + + requested_bis_sync[i] = net_buf_simple_pull_le32(buf); + + if (requested_bis_sync[i] != 0U && + pa_sync == BT_BAP_BASS_PA_REQ_NO_SYNC) + { + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + LOG_DBG("Cannot sync to BIS without PA"); + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + + if (!valid_bis_sync_request(requested_bis_sync[i], + aggregated_bis_syncs)) + { + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + LOG_DBG("Invalid BIS Sync request[%d]", i); + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + + aggregated_bis_syncs |= requested_bis_sync[i]; + + /* If the BIS sync request is different than what was previously was + * requested, or different than what we are current synced to, we set + * bis_sync_change_requested to let the application know that the state + * may need a change + */ + + if (requested_bis_sync[i] == BT_BAP_BIS_SYNC_NO_PREF || + internal_state->requested_bis_sync[i] != requested_bis_sync[i] || + internal_state->state.subgroups[i].bis_sync != + requested_bis_sync[i]) + { + bis_sync_change_requested = true; + } + + subgroup->metadata_len = net_buf_simple_pull_u8(buf); + + if (subgroup->metadata_len > + CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE) + { + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + LOG_WRN("Metadata too long %u/%u", subgroup->metadata_len, + CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE); + return BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES); + } + + metadata = net_buf_simple_pull_mem(buf, subgroup->metadata_len); + + (void)memcpy(subgroup->metadata, metadata, subgroup->metadata_len); + } + + /* All input has been validated; update receive state and check for changes + */ + + state = &internal_state->state; + + /* Store backup in case upper layers rejects */ + + (void)memcpy(&backup_state, state, sizeof(backup_state)); + backup_pa_sync_requested = internal_state->pa_sync_requested; + + /* BAP_v1.0.1, 3.1.1.4: If the server has synchronized to a BIS and the + * server has detected that the BIS is encrypted, and if the server does + * not have an encryption key to decrypt the BIS, the server shall write a + * value of 0x01 (Broadcast_Code required) to the BIG_Encryption field. If + * the client requests to sync to a BIS again after a previous sync attempt + * failed due to a bad broadcast code, we shall therefore request the + * broadcast code again instead of leaving the receive state at Bad_Code. + */ + + if (aggregated_bis_syncs != 0U && + state->encrypt_state == BT_BAP_BIG_ENC_STATE_BAD_CODE) + { + state->encrypt_state = BT_BAP_BIG_ENC_STATE_BCODE_REQ; + state_changed = true; + } + + if (state->num_subgroups != num_subgroups) + { + state->num_subgroups = num_subgroups; + state_changed = true; + } + + for (int i = 0; i < num_subgroups; i++) + { + const bool metadata_len_changed = + subgroups[i].metadata_len != state->subgroups[i].metadata_len; + + if (metadata_len_changed) + { + state->subgroups[i].metadata_len = subgroups[i].metadata_len; + state_changed = true; + } + + if (metadata_len_changed || memcmp(subgroups[i].metadata, + state->subgroups[i].metadata, + sizeof(subgroups[i].metadata)) != 0) + { + if (state->subgroups[i].metadata_len == 0U) + { + memset(state->subgroups[i].metadata, 0, + state->subgroups[i].metadata_len); + } + else + { + (void)memcpy(state->subgroups[i].metadata, + subgroups[i].metadata, + state->subgroups[i].metadata_len); + } + + state_changed = true; + } + } + + if (scan_delegator_cbs != NULL && scan_delegator_cbs->modify_source != + NULL) + { + /* Unlock mutex to avoid potential deadlock on app callback */ + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + err = scan_delegator_cbs->modify_source(conn, state); + if (err != 0) + { + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + /* Restore backup */ + + (void)memcpy(state, &backup_state, sizeof(backup_state)); + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + LOG_DBG("Modify Source rejected with reason 0x%02x", err); + + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); + } + + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + } + + /* Only send the sync request to upper layers if it is requested, and + * we have not already sent the request to the application and if we are + * not already synced + */ + + if (pa_sync != BT_BAP_BASS_PA_REQ_NO_SYNC && + !internal_state->pa_sync_requested && + state->pa_sync_state != BT_BAP_PA_STATE_SYNCED) + { + const uint8_t pa_sync_state = state->pa_sync_state; + + /* Unlock mutex to avoid potential deadlock on app callback */ + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + err = pa_sync_request(conn, state, pa_sync, pa_interval); + if (err != 0) + { + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + /* Restore backup */ + + (void)memcpy(state, &backup_state, sizeof(backup_state)); + internal_state->pa_sync_requested = backup_pa_sync_requested; + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + LOG_DBG("PA sync %u from %p was rejected with reason %d", pa_sync, + (void *)conn, err); + + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); + } + else if (pa_sync_state != state->pa_sync_state) + { + /* Temporary work around if the state is changed when + * pa_sync_request is + * called. See + * https://github.com/zephyrproject-rtos/zephyr/issues/79308 for + * more information about this issue. + */ + + state_changed = true; + } + + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + internal_state->pa_sync_requested = true; + } + else if (pa_sync == BT_BAP_BASS_PA_REQ_NO_SYNC && + (internal_state->pa_sync_requested || + state->pa_sync_state == BT_BAP_PA_STATE_INFO_REQ || + state->pa_sync_state == BT_BAP_PA_STATE_SYNCED)) + { + /* Only send sync term request if we have received a sync request, or + * if the state reflects a pending PAST or active sync + */ + + /* Unlock mutex to avoid potential deadlock on app callback */ + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + /* Terminate PA sync */ + + err = pa_sync_term_request(conn, &internal_state->state); + if (err != 0) + { + LOG_DBG("PA sync term from %p was rejected with reason %d", + (void *)conn, err); + + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + /* Restore backup */ + + (void)memcpy(state, &backup_state, sizeof(backup_state)); + internal_state->pa_sync_requested = backup_pa_sync_requested; + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); + } + + state_changed = true; + + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + internal_state->pa_sync_requested = false; + } + + /* Store requested_bis_sync after everything has been validated */ + + (void)memcpy(internal_state->requested_bis_sync, requested_bis_sync, + sizeof(requested_bis_sync)); + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + /* Notify if changed */ + + if (state_changed) + { + LOG_DBG("Source modified"); + + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + set_receive_state_changed(internal_state); + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + } + + if (state_changed) + { + /* app callback */ + + receive_state_updated(conn, internal_state); + } + + if (bis_sync_change_requested) + { + bis_sync_request_updated(conn, internal_state); + } + + return 0; +} + +static int scan_delegator_broadcast_code(struct bt_conn *conn, + struct net_buf_simple *buf) +{ + struct bass_recv_state_internal *internal_state; + uint8_t src_id; + const uint8_t *broadcast_code; + + /* subtract 1 as the opcode has already been pulled */ + + if (buf->len != sizeof(struct bt_bap_bass_cp_broadcase_code) - 1) + { + LOG_DBG("Invalid length %u", buf->size); + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); + } + + src_id = net_buf_simple_pull_u8(buf); + internal_state = bass_lookup_src_id(src_id); + + if (internal_state == NULL) + { + LOG_DBG("Could not find state by src id %u", src_id); + return BT_GATT_ERR(BT_BAP_BASS_ERR_INVALID_SRC_ID); + } + + broadcast_code = net_buf_simple_pull_mem(buf, + sizeof(internal_state->broadcast_code)); + + (void)memcpy(internal_state->broadcast_code, broadcast_code, + sizeof(internal_state->broadcast_code)); + + LOG_DBG("Index %u: broadcast code added: %s", internal_state->index, + bt_hex(internal_state->broadcast_code, + sizeof(internal_state->broadcast_code))); + + if (scan_delegator_cbs != NULL && + scan_delegator_cbs->broadcast_code != NULL) + { + scan_delegator_cbs->broadcast_code(conn, &internal_state->state, + broadcast_code); + } + + return 0; +} + +static int scan_delegator_rem_src(struct bt_conn *conn, + struct net_buf_simple *buf) +{ + struct bass_recv_state_internal *internal_state; + struct bt_bap_scan_delegator_recv_state *state; + uint8_t src_id; + int err; + + /* subtract 1 as the opcode has already been pulled */ + + if (buf->len != sizeof(struct bt_bap_bass_cp_rem_src) - 1) + { + LOG_DBG("Invalid length %u", buf->size); + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); + } + + src_id = net_buf_simple_pull_u8(buf); + internal_state = bass_lookup_src_id(src_id); + + if (internal_state == NULL) + { + LOG_DBG("Could not find state by src id %u", src_id); + return BT_GATT_ERR(BT_BAP_BASS_ERR_INVALID_SRC_ID); + } + + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + state = &internal_state->state; + + if (internal_state->pa_sync_requested || + state->pa_sync_state == BT_BAP_PA_STATE_INFO_REQ || + state->pa_sync_state == BT_BAP_PA_STATE_SYNCED) + { + LOG_DBG("Cannot remove source ID 0x%02x while PA is syncing or synced", + state->src_id); + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + LOG_DBG("Cannot remove source ID 0x%02x while PA is synced", src_id); + /* We shouldn't return a success here, but the Test Spec requires it at + * the moment, + * Errata to fix this: https://bluetooth.atlassian.net/browse/ES-28445 + */ + + return BT_GATT_ERR(BT_ATT_ERR_SUCCESS); + } + + for (uint8_t i = 0U; i < state->num_subgroups; i++) + { + if (internal_state->requested_bis_sync[i] != 0U && + internal_state->state.subgroups[i].bis_sync != 0U) + { + LOG_DBG("Cannot remove source ID 0x%02x while BIS for " + "subgroup[%u] " + "is " + "synced or syncing (0x%08X - 0x%08X)", + state->src_id, i, internal_state->requested_bis_sync[i], + internal_state->state.subgroups[i].bis_sync); + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + LOG_DBG("Cannot remove source ID 0x%02x while BIS is synced", + src_id); + /* We shouldn't return a success here, but the Test Spec requires + * it at the moment, Errata to fix this: + * https://bluetooth.atlassian.net/browse/ES-28445 + */ + + return BT_GATT_ERR(BT_ATT_ERR_SUCCESS); + } + } + + /* If conn == NULL then it's a local operation and we do not need to ask + * the application + */ + + if (conn != NULL && scan_delegator_cbs != NULL && + scan_delegator_cbs->remove_source != NULL) + { + /* Unlock mutex to avoid potential deadlock on app callback */ + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + err = scan_delegator_cbs->remove_source(conn, src_id); + if (err != 0) + { + LOG_DBG("Remove Source rejected with reason 0x%02x", err); + + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); + } + + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + } + + free_recv_state(internal_state); + + set_receive_state_changed(internal_state); + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + LOG_DBG("Removed source with ID 0x%02x", src_id); + + /* app callback */ + + receive_state_updated(conn, internal_state); + + return BT_GATT_ERR(BT_ATT_ERR_SUCCESS); +} + +static ssize_t write_control_point( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *data, + uint16_t len, uint16_t offset, uint8_t flags) +{ + struct net_buf_simple buf; + uint8_t opcode; + int err; + + ARG_UNUSED(attr); + ARG_UNUSED(flags); + + if (offset != 0) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + else if (len == 0) + { + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); + } + + net_buf_simple_init_with_data(&buf, (void *)data, len); + + opcode = net_buf_simple_pull_u8(&buf); + + if (!BT_BAP_BASS_VALID_OPCODE(opcode)) + { + return BT_GATT_ERR(BT_BAP_BASS_ERR_OPCODE_NOT_SUPPORTED); + } + + LOG_HEXDUMP_DBG(data, len, "Data"); + + switch (opcode) + { + case BT_BAP_BASS_OP_SCAN_STOP: + LOG_DBG("Assistant stopping scanning"); + + if (buf.len != 0) + { + LOG_DBG("Invalid length %u", buf.size); + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); + } + + if (scan_delegator_cbs != NULL && + scan_delegator_cbs->scanning_state != NULL) + { + scan_delegator_cbs->scanning_state(conn, false); + } + + break; + case BT_BAP_BASS_OP_SCAN_START: + LOG_DBG("Assistant starting scanning"); + + if (buf.len != 0) + { + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); + } + + if (scan_delegator_cbs != NULL && + scan_delegator_cbs->scanning_state != NULL) + { + scan_delegator_cbs->scanning_state(conn, true); + } + + break; + case BT_BAP_BASS_OP_ADD_SRC: + LOG_DBG("Assistant adding source"); + + err = scan_delegator_add_src(conn, &buf); + if (err != 0) + { + LOG_DBG("Could not add source %d", err); + return err; + } + + break; + case BT_BAP_BASS_OP_MOD_SRC: + LOG_DBG("Assistant modifying source"); + + err = scan_delegator_mod_src(conn, &buf); + if (err != 0) + { + LOG_DBG("Could not modify source %d", err); + return err; + } + + break; + case BT_BAP_BASS_OP_BROADCAST_CODE: + LOG_DBG("Assistant setting broadcast code"); + + err = scan_delegator_broadcast_code(conn, &buf); + if (err != 0) + { + LOG_DBG("Could not set broadcast code"); + return err; + } + + break; + case BT_BAP_BASS_OP_REM_SRC: + LOG_DBG("Assistant removing source"); + + err = scan_delegator_rem_src(conn, &buf); + if (err != 0) + { + LOG_DBG("Could not remove source %d", err); + return err; + } + + break; + default: + break; + } + + return len; +} + +static void recv_state_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); +} + +static ssize_t read_recv_state( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + uint8_t idx = POINTER_TO_UINT(BT_AUDIO_CHRC_USER_DATA(attr)); + struct bass_recv_state_internal *internal_state = + &scan_delegator.recv_states[idx]; + __maybe_unused int err; + ssize_t ret_val; + + if (IS_ENABLED(CONFIG_BT_BAP_SCAN_DELEGATOR_LOG_LEVEL_DBG)) + { + bt_debug_dump_recv_state(internal_state); + } + + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + if (internal_state->active) + { + net_buf_put_recv_state(internal_state); + + ret_val = + bt_gatt_attr_read(conn, attr, buf, len, offset, read_buf.data, + read_buf.len); + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + } + else + { + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + ret_val = bt_gatt_attr_read(conn, attr, buf, len, offset, NULL, 0); + } + + return ret_val; +} + +#define RECEIVE_STATE_CHARACTERISTIC(idx) \ + BT_AUDIO_CHRC(BT_UUID_BASS_RECV_STATE, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, read_recv_state, NULL, UINT_TO_POINTER(idx)), \ + BT_AUDIO_CCC(recv_state_cfg_changed) + +static struct bt_gatt_attr attr_bass_svc[] = +{ + BT_GATT_PRIMARY_SERVICE(BT_UUID_BASS), + BT_AUDIO_CHRC(BT_UUID_BASS_CONTROL_POINT, + BT_GATT_CHRC_WRITE_WITHOUT_RESP | BT_GATT_CHRC_WRITE, + BT_GATT_PERM_WRITE_ENCRYPT, NULL, write_control_point, + NULL), + RECEIVE_STATE_CHARACTERISTIC(0), +#if CONFIG_BT_BAP_SCAN_DELEGATOR_RECV_STATE_COUNT > 1 + RECEIVE_STATE_CHARACTERISTIC(1), +#if CONFIG_BT_BAP_SCAN_DELEGATOR_RECV_STATE_COUNT > 2 + RECEIVE_STATE_CHARACTERISTIC(2) +#endif /* CONFIG_BT_BAP_SCAN_DELEGATOR_RECV_STATE_COUNT > 2 */ +#endif /* CONFIG_BT_BAP_SCAN_DELEGATOR_RECV_STATE_COUNT > 1 */ +}; + +static struct bt_gatt_service bass_svc = BT_GATT_SERVICE(attr_bass_svc); + +static int bass_register(void) +{ + int err; + + err = bt_gatt_service_register(&bass_svc); + if (err != 0) + { + LOG_DBG("Failed to register BASS service (err %d)", err); + return err; + } + + LOG_DBG("BASS service registered"); + + return 0; +} + +static void bass_unregister(void) +{ + __maybe_unused int err; + + err = bt_gatt_service_unregister(&bass_svc); + DEBUGASSERT(err == 0); + + LOG_DBG("BASS service unregistered"); +} + +/* Public Api */ + +int bt_bap_scan_delegator_register(struct bt_bap_scan_delegator_cb *cb) +{ + int err; + + if (atomic_test_and_set_bit(scan_delegator_flags, + SCAN_DELEGATOR_FLAG_REGISTERED)) + { + LOG_DBG("Scan delegator already registered"); + return -EALREADY; + } + + err = bass_register(); + if (err != 0) + { + atomic_clear_bit(scan_delegator_flags, SCAN_DELEGATOR_FLAG_REGISTERED); + return err; + } + + scan_delegator_cbs = cb; + + return 0; +} + +int bt_bap_scan_delegator_unregister(void) +{ + int err; + + if (atomic_test_and_set_bit(scan_delegator_flags, + SCAN_DELEGATOR_FLAG_UNREGISTERING)) + { + LOG_DBG("Scan delegator already unregistering"); + return -EALREADY; + } + + if (!atomic_test_bit(scan_delegator_flags, SCAN_DELEGATOR_FLAG_REGISTERED)) + { + LOG_DBG("Scan delegator not yet registered"); + atomic_clear_bit(scan_delegator_flags, + SCAN_DELEGATOR_FLAG_UNREGISTERING); + + return -EAGAIN; + } + + /* Cancel all pending work before unregistering the service */ + + ARRAY_FOR_EACH_PTR(scan_delegator.recv_states, internal_state) + { + struct k_work_sync sync; + + /* Cancel k_work before taking the mutex to ensure that the work can be + * finished + */ + + (void)k_work_cancel_delayable_sync(&internal_state->notify_work, + &sync); + } + + bass_unregister(); + + scan_delegator_cbs = NULL; + + ARRAY_FOR_EACH_PTR(scan_delegator.recv_states, internal_state) + { + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + ARRAY_FOR_EACH_PTR(internal_state->clients, client) + { + /* Keep address and ID; clear only transient flags. */ + + client->flags = (struct bass_recv_state_flags) + { + 0 + }; + } + + free_recv_state(internal_state); + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + } + + atomic_clear_bit(scan_delegator_flags, SCAN_DELEGATOR_FLAG_REGISTERED); + atomic_clear_bit(scan_delegator_flags, SCAN_DELEGATOR_FLAG_UNREGISTERING); + + return 0; +} + +int bt_bap_scan_delegator_set_pa_state(uint8_t src_id, + enum bt_bap_pa_state pa_state) +{ + struct bass_recv_state_internal *internal_state = + bass_lookup_src_id(src_id); + struct bt_bap_scan_delegator_recv_state *recv_state; + bool state_changed = false; + __maybe_unused int err; + + if (internal_state == NULL) + { + LOG_DBG("Could not find recv_state by src_id %u", src_id); + return -EINVAL; + } + + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + recv_state = &internal_state->state; + /* We consider the PA sync request handled when this function is called. + * This will allow for following requests to sync, if not already synced, + * to trigger the callback again. + */ + + internal_state->pa_sync_requested = false; + + if (recv_state->pa_sync_state != pa_state) + { + recv_state->pa_sync_state = pa_state; + set_receive_state_changed(internal_state); + state_changed = true; + } + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + if (state_changed) + { + /* app callback */ + + receive_state_updated(NULL, internal_state); + } + + return 0; +} + +int bt_bap_scan_delegator_set_bis_sync_state(uint8_t src_id, + uint32_t bis_synced[CONFIG_BT_BAP_BASS_MAX_SUBGROUPS]) +{ + struct bass_recv_state_internal *internal_state = + bass_lookup_src_id(src_id); + uint32_t big_sync_bitfield; + __maybe_unused int err; + bool notify = false; + + if (internal_state == NULL) + { + LOG_DBG("Could not find recv_state by src_id %u", src_id); + return -EINVAL; + } + + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + /* Verify state for all subgroups before assigning any data */ + + big_sync_bitfield = 0U; + for (uint8_t i = 0U; i < internal_state->state.num_subgroups; i++) + { + if (i >= CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) + { + break; + } + + big_sync_bitfield |= bis_synced[i]; + + if (bis_synced[i] == BT_BAP_BIS_SYNC_NO_PREF || + !bits_subset_of(bis_synced[i], + internal_state->requested_bis_sync[i])) + { + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + LOG_DBG("Subgroup[%u] invalid bis_sync value 0x%08X for 0x%08X", i, + bis_synced[i], + internal_state->requested_bis_sync[i]); + return -EINVAL; + } + } + + if (internal_state->state.pa_sync_state != BT_BAP_PA_STATE_SYNCED && + big_sync_bitfield != 0U) + { + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + LOG_DBG("PA for src_id %u isn't synced, cannot be BIG synced", src_id); + return -EINVAL; + } + + for (uint8_t i = 0U; i < internal_state->state.num_subgroups; i++) + { + struct bt_bap_bass_subgroup *subgroup = + &internal_state->state.subgroups[i]; + + if (i >= CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) + { + break; + } + + if (bis_synced[i] != subgroup->bis_sync) + { + notify = true; + subgroup->bis_sync = bis_synced[i]; + } + } + + if (internal_state->state.encrypt_state == BT_BAP_BIG_ENC_STATE_BAD_CODE) + { + (void)memset(internal_state->state.bad_code, 0xff, + sizeof(internal_state->state.bad_code)); + } + + if (notify) + { + set_receive_state_changed(internal_state); + } + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + LOG_DBG("Source ID 0x%02x synced", src_id); + + if (notify) + { + /* app callback */ + + receive_state_updated(NULL, internal_state); + } + + return 0; +} + +static bool +valid_bt_bap_scan_delegator_add_src_param( + const struct bt_bap_scan_delegator_add_src_param *param) +{ + uint32_t aggregated_bis_syncs = 0U; + + if (param->broadcast_id > BT_AUDIO_BROADCAST_ID_MAX) + { + LOG_DBG("Invalid broadcast_id: %u", param->broadcast_id); + + return false; + } + + if (param->addr.type > BT_ADDR_LE_RANDOM) + { + LOG_DBG("param->addr.type %u is invalid", param->addr.type); + return false; + } + + if (param->sid > BT_GAP_SID_MAX) + { + LOG_DBG("param->sid %d is invalid", param->sid); + return false; + } + + if (param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) + { + LOG_WRN("Too many subgroups %u/%u", param->num_subgroups, + CONFIG_BT_BAP_BASS_MAX_SUBGROUPS); + + return false; + } + + if (!IN_RANGE(param->pa_state, BT_BAP_PA_STATE_NOT_SYNCED, + BT_BAP_PA_STATE_NO_PAST)) + { + LOG_DBG("Invalid PA state: %d", param->pa_state); + + return false; + } + + for (uint8_t i = 0U; i < param->num_subgroups; i++) + { + const struct bt_bap_bass_subgroup *subgroup = ¶m->subgroups[i]; + + if (!bis_syncs_unique_or_no_pref(subgroup->bis_sync, + aggregated_bis_syncs)) + { + LOG_DBG("Invalid BIS sync: %u", subgroup->bis_sync); + + return false; + } + + aggregated_bis_syncs |= subgroup->bis_sync; + + if (subgroup->metadata_len > + CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE) + { + LOG_DBG("subgroup[%u]: Invalid metadata_len: %u", i, + subgroup->metadata_len); + + return false; + } + } + + return true; +} + +int bt_bap_scan_delegator_add_src( + const struct bt_bap_scan_delegator_add_src_param *param) +{ + struct bass_recv_state_internal *internal_state = NULL; + struct bt_bap_scan_delegator_recv_state *state; + __maybe_unused int err; + + if (atomic_test_bit(scan_delegator_flags, + SCAN_DELEGATOR_FLAG_UNREGISTERING) || + !atomic_test_bit(scan_delegator_flags, SCAN_DELEGATOR_FLAG_REGISTERED)) + { + LOG_DBG("Scan delegator not available"); + return -EAGAIN; + } + + if (!valid_bt_bap_scan_delegator_add_src_param(param)) + { + return -EINVAL; + } + + internal_state = bass_lookup_state(param->addr.type, param->sid, + param->broadcast_id); + if (internal_state != NULL) + { + LOG_DBG("Adding addr.type= " + "0x%02X adv_sid=0x%02X and broadcast_id=0x%06X would " + "result in duplication", + param->addr.type, param->sid, param->broadcast_id); + + return -EALREADY; + } + + internal_state = get_free_recv_state(); + if (internal_state == NULL) + { + LOG_DBG("Could not get free receive state"); + + return -ENOMEM; + } + + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + state = &internal_state->state; + + state->src_id = next_src_id(); + bt_addr_le_copy(&state->addr, ¶m->addr); + state->adv_sid = param->sid; + state->broadcast_id = param->broadcast_id; + state->pa_sync_state = param->pa_state; + state->encrypt_state = param->encrypt_state; + state->num_subgroups = param->num_subgroups; + + if (state->num_subgroups > 0U) + { + (void)memcpy(state->subgroups, param->subgroups, + sizeof(state->subgroups)); + } + else + { + (void)memset(state->subgroups, 0, sizeof(state->subgroups)); + } + + internal_state->active = true; + + /* Set all requested_bis_sync to BT_BAP_BIS_SYNC_NO_PREF, as no + * Broadcast Assistant has set any requests yet + */ + + for (size_t i = + 0U; i < ARRAY_SIZE(internal_state->requested_bis_sync); i++) + { + internal_state->requested_bis_sync[i] = BT_BAP_BIS_SYNC_NO_PREF; + } + + set_receive_state_changed(internal_state); + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + LOG_DBG("New source added"); + + /* app callback */ + + receive_state_updated(NULL, internal_state); + + return state->src_id; +} + +static bool +valid_bt_bap_scan_delegator_mod_src_param( + const struct bt_bap_scan_delegator_mod_src_param *param) +{ + uint32_t aggregated_bis_syncs = 0U; + + if (param->broadcast_id > BT_AUDIO_BROADCAST_ID_MAX) + { + LOG_DBG("Invalid broadcast_id: %u", param->broadcast_id); + + return false; + } + + if (param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) + { + LOG_WRN("Too many subgroups %u/%u", param->num_subgroups, + CONFIG_BT_BAP_BASS_MAX_SUBGROUPS); + + return false; + } + + for (uint8_t i = 0U; i < param->num_subgroups; i++) + { + const struct bt_bap_bass_subgroup *subgroup = ¶m->subgroups[i]; + + if (subgroup->bis_sync != BT_BAP_BIS_SYNC_FAILED && + !bis_syncs_unique_or_no_pref(subgroup->bis_sync, + aggregated_bis_syncs)) + { + LOG_DBG("Invalid BIS sync: %u", subgroup->bis_sync); + + return false; + } + + aggregated_bis_syncs |= subgroup->bis_sync; + + if (subgroup->metadata_len > + CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE) + { + LOG_DBG("subgroup[%u]: Invalid metadata_len: %u", i, + subgroup->metadata_len); + + return false; + } + } + + return true; +} + +int bt_bap_scan_delegator_mod_src( + const struct bt_bap_scan_delegator_mod_src_param *param) +{ + struct bass_recv_state_internal *internal_state = NULL; + struct bt_bap_scan_delegator_recv_state *state; + bool state_changed = false; + __maybe_unused int err; + + if (!valid_bt_bap_scan_delegator_mod_src_param(param)) + { + return -EINVAL; + } + + internal_state = bass_lookup_src_id(param->src_id); + if (internal_state == NULL) + { + LOG_DBG("Could not find receive state with src_id %u", param->src_id); + + return -ENOENT; + } + + err = nxrmutex_ticklock(&internal_state->mutex, + SCAN_DELEGATOR_BUF_SEM_TIMEOUT); + DEBUGASSERT(err == 0); + + state = &internal_state->state; + + if (state->broadcast_id != param->broadcast_id) + { + state->broadcast_id = param->broadcast_id; + state_changed = true; + } + + if (state->num_subgroups != param->num_subgroups) + { + state->num_subgroups = param->num_subgroups; + state_changed = true; + } + + if (state->encrypt_state != param->encrypt_state) + { + state->encrypt_state = param->encrypt_state; + + if (state->encrypt_state == BT_BAP_BIG_ENC_STATE_BAD_CODE) + { + (void)memset(state->bad_code, 0xff, + sizeof(internal_state->state.bad_code)); + } + + state_changed = true; + } + + /* Verify that the BIS sync values is acceptable for the receive state */ + + for (uint8_t i = 0U; i < state->num_subgroups; i++) + { + const uint32_t bis_sync = param->subgroups[i].bis_sync; + const uint32_t bis_sync_requested = + internal_state->requested_bis_sync[i]; + + if (bis_sync != BT_BAP_BIS_SYNC_FAILED && !bits_subset_of(bis_sync, + bis_sync_requested)) + { + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + LOG_DBG("Subgroup[%d] invalid bis_sync value 0x%08X for 0x%08X", i, + bis_sync, + bis_sync_requested); + return -EINVAL; + } + } + + for (uint8_t i = 0U; i < state->num_subgroups; i++) + { + const struct bt_bap_bass_subgroup *param_subgroup = + ¶m->subgroups[i]; + struct bt_bap_bass_subgroup *subgroup = &state->subgroups[i]; + + if (subgroup->bis_sync != param_subgroup->bis_sync) + { + subgroup->bis_sync = param_subgroup->bis_sync; + state_changed = true; + } + + if (subgroup->metadata_len != param_subgroup->metadata_len) + { + subgroup->metadata_len = param_subgroup->metadata_len; + state_changed = true; + } + + if (subgroup->metadata_len != param_subgroup->metadata_len || + memcmp(subgroup->metadata, param_subgroup->metadata, + param_subgroup->metadata_len) != 0) + { + (void)memcpy(subgroup->metadata, param_subgroup->metadata, + param_subgroup->metadata_len); + subgroup->metadata_len = param_subgroup->metadata_len; + state_changed = true; + } + } + + if (state_changed) + { + set_receive_state_changed(internal_state); + } + + err = nxrmutex_unlock(&internal_state->mutex); + DEBUGASSERT(err == 0); + + if (state_changed) + { + LOG_DBG("Source modified"); + + /* app callback */ + + receive_state_updated(NULL, internal_state); + } + + return 0; +} + +int bt_bap_scan_delegator_rem_src(uint8_t src_id) +{ + struct net_buf_simple buf; + + net_buf_simple_init_with_data(&buf, (void *)&src_id, sizeof(src_id)); + + if (scan_delegator_rem_src(NULL, &buf) == 0) + { + return 0; + } + else + { + return -EINVAL; + } +} + +void bt_bap_scan_delegator_foreach_state( + bt_bap_scan_delegator_state_func_t func, void *user_data) +{ + for (size_t i = 0U; i < ARRAY_SIZE(scan_delegator.recv_states); i++) + { + if (scan_delegator.recv_states[i].active) + { + bool stop; + + stop = func(&scan_delegator.recv_states[i].state, user_data); + if (stop) + { + return; + } + } + } +} + +struct scan_delegator_state_find_state_param +{ + const struct bt_bap_scan_delegator_recv_state *recv_state; + bt_bap_scan_delegator_state_func_t func; + void *user_data; +}; + +static bool +scan_delegator_state_find_state_cb( + const struct bt_bap_scan_delegator_recv_state *recv_state, + void *user_data) +{ + struct scan_delegator_state_find_state_param *param = user_data; + bool found; + + found = param->func(recv_state, param->user_data); + if (found) + { + param->recv_state = recv_state; + + return true; + } + + return false; +} + +const struct bt_bap_scan_delegator_recv_state * +bt_bap_scan_delegator_find_state(bt_bap_scan_delegator_state_func_t func, + void *user_data) +{ + struct scan_delegator_state_find_state_param param = + { + .recv_state = NULL, + .func = func, + .user_data = user_data, + }; + + bt_bap_scan_delegator_foreach_state(scan_delegator_state_find_state_cb, + ¶m); + + return param.recv_state; +} + +const struct bt_bap_scan_delegator_recv_state * +bt_bap_scan_delegator_lookup_src_id( + uint8_t src_id) +{ + const struct bass_recv_state_internal *internal_state = + bass_lookup_src_id(src_id); + + if (internal_state != NULL) + { + return &internal_state->state; + } + + return NULL; +} + +static int scan_delegator_init(void) +{ + static struct bt_conn_auth_info_cb auth_callbacks = + { + .pairing_complete = pairing_complete_cb, + .bond_deleted = bond_deleted_cb, + }; + + __maybe_unused int err; + + ARRAY_FOR_EACH(scan_delegator.recv_states, idx) + { + struct bass_recv_state_internal *internal_state = + &scan_delegator.recv_states[idx]; + + /* Store the pointer to the first characteristic in each receive state */ + + internal_state->index = idx; + + /* Every 3rd attribute is a new receive state, starting from attrs[3] */ + + internal_state->attr = &bass_svc.attrs[(idx + 1U) * 3U]; + + err = nxrmutex_init(&internal_state->mutex); + DEBUGASSERT(err == 0); + + k_work_init_delayable(&internal_state->notify_work, notify_work_handler); + + ARRAY_FOR_EACH_PTR(internal_state->clients, client) + { + bt_addr_le_copy(&client->addr, BT_ADDR_LE_NONE); + } + + err = nxrmutex_init(&internal_state->mutex); + DEBUGASSERT(err == 0); + } + + err = bt_conn_auth_info_cb_register(&auth_callbacks); + DEBUGASSERT(err == 0); + + return 0; +} + +SYS_INIT(scan_delegator_init, APPLICATION, 0); diff --git a/libs/zblue/audio/bap_stream.c b/libs/zblue/audio/bap_stream.c new file mode 100644 index 0000000000000..e72e8879c29d9 --- /dev/null +++ b/libs/zblue/audio/bap_stream.c @@ -0,0 +1,1888 @@ +/**************************************************************************** + * libs/zblue/audio/bap_stream.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Bluetooth Audio Stream */ + +/* Copyright (c) 2020 Intel Corporation + * Copyright (c) 2021-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../host/iso_internal.h" + +#include "ascs_internal.h" +#include "audio_internal.h" +#include "bap_internal.h" +#include "bap_iso.h" +#include "bap_endpoint.h" +#include "bap_unicast_client_internal.h" +#include "bap_unicast_server.h" + +LOG_MODULE_REGISTER(bt_bap_stream, CONFIG_BT_BAP_STREAM_LOG_LEVEL); + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +#if defined(CONFIG_BT_BAP_UNICAST_CLIENT) || defined(CONFIG_BT_BAP_BROADCAST_SOURCE) || \ + defined(CONFIG_BT_BAP_BROADCAST_SINK) +void bt_bap_qos_cfg_to_iso_qos(struct bt_iso_chan_io_qos *io, + const struct bt_bap_qos_cfg *qos_cfg) +{ + io->sdu = qos_cfg->sdu; + io->phy = qos_cfg->phy; + io->rtn = qos_cfg->rtn; +#if defined(CONFIG_BT_ISO_TEST_PARAMS) + io->burst_number = qos_cfg->burst_number; + io->max_pdu = qos_cfg->max_pdu; +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ +} +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT || \ + + * CONFIG_BT_BAP_BROADCAST_SOURCE || \ CONFIG_BT_BAP_BROADCAST_SINK \ + */ + +void bt_bap_stream_init(struct bt_bap_stream *stream) +{ + struct bt_bap_stream_ops *ops; + void *user_data; + + /* Save the stream->ops and stream->user_data owned by API user */ + + ops = stream->ops; + user_data = stream->user_data; + + memset(stream, 0, sizeof(*stream)); + + /* Restore */ + + stream->ops = ops; + stream->user_data = user_data; +} + +void bt_bap_stream_attach(struct bt_conn *conn, + struct bt_bap_stream *stream, struct bt_bap_ep *ep) +{ + LOG_DBG("conn %p stream %p ep %p", (void *)conn, stream, ep); + + if (conn != NULL) + { + DEBUGASSERT(stream->conn == NULL || stream->conn == conn); + if (stream->conn == NULL) + { + stream->conn = bt_conn_ref(conn); + } + } + + stream->ep = ep; + stream->codec_cfg = &ep->codec_cfg; + ep->stream = stream; +} + +struct bt_iso_chan *bt_bap_stream_iso_chan_get(struct bt_bap_stream *stream) +{ + if (stream != NULL && stream->ep != NULL && stream->ep->iso != NULL) + { + return &stream->ep->iso->chan; + } + + return NULL; +} + +void bt_bap_stream_cb_register(struct bt_bap_stream *stream, + struct bt_bap_stream_ops *ops) +{ + stream->ops = ops; +} + +int bt_bap_ep_get_info(const struct bt_bap_ep *ep, + struct bt_bap_ep_info *info) +{ + enum bt_audio_dir dir; + + if (ep == NULL) + { + LOG_DBG("ep is NULL"); + + return -EINVAL; + } + + if (info == NULL) + { + LOG_DBG("info is NULL"); + + return -EINVAL; + } + + dir = ep->dir; + + info->id = ep->id; + info->state = ep->state; + info->dir = dir; + info->qos_pref = &ep->qos_pref; + + if (ep->iso == NULL) + { + info->paired_ep = NULL; + info->iso_chan = NULL; + } + else + { + info->paired_ep = bt_bap_iso_get_paired_ep(ep); + info->iso_chan = &ep->iso->chan; + } + + info->can_send = false; + info->can_recv = false; + if (IS_ENABLED(CONFIG_BT_AUDIO_TX) && ep->stream != NULL) + { + if (IS_ENABLED(CONFIG_BT_BAP_BROADCAST_SOURCE) && + bt_bap_broadcast_source_has_ep(ep)) + { + info->can_send = true; + } + else if (IS_ENABLED(CONFIG_BT_BAP_BROADCAST_SINK) && + bt_bap_broadcast_sink_has_ep(ep)) + { + info->can_recv = true; + } + else if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && + bt_bap_unicast_client_has_ep(ep)) + { + /* dir is not initialized before the connection is set */ + + if (ep->stream->conn != NULL) + { + info->can_send = dir == BT_AUDIO_DIR_SINK; + info->can_recv = dir == BT_AUDIO_DIR_SOURCE; + } + } + else if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_SERVER) && + bt_bap_unicast_server_has_ep(ep)) + { + /* dir is not initialized before the connection is set */ + + if (ep->stream->conn != NULL) + { + info->can_send = dir == BT_AUDIO_DIR_SOURCE; + info->can_recv = dir == BT_AUDIO_DIR_SINK; + } + } + else + { + LOG_DBG("Invalid endpoint"); + + return -EINVAL; + } + } + + return 0; +} + +bool bt_bap_qos_cfg_eq(const struct bt_bap_qos_cfg *a, + const struct bt_bap_qos_cfg *b) +{ + if (a == b) + { + return true; + } + + if (a == NULL || b == NULL) + { + return false; + } + + return a->pd == b->pd && a->framing == b->framing && a->phy == b->phy && + a->rtn == b->rtn && + a->sdu == b->sdu && +#if defined(CONFIG_BT_BAP_BROADCAST_SOURCE) || defined(CONFIG_BT_BAP_UNICAST) + a->latency == b->latency && +#endif /* CONFIG_BT_BAP_BROADCAST_SOURCE || CONFIG_BT_BAP_UNICAST */ +#if defined(CONFIG_BT_ISO_TEST_PARAMS) + a->max_pdu == b->max_pdu && a->burst_number == b->burst_number && + a->num_subevents == b->num_subevents && +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ + a->interval == b->interval; +} + +struct bt_conn *bt_bap_ep_get_conn(const struct bt_bap_ep *ep) +{ + struct bt_conn *conn; + + if (ep == NULL) + { + LOG_DBG("ep is NULL"); + + return NULL; + } + + if ((IS_ENABLED(CONFIG_BT_BAP_BROADCAST_SOURCE) && + bt_bap_broadcast_source_has_ep(ep)) || + (IS_ENABLED(CONFIG_BT_BAP_BROADCAST_SINK) && + bt_bap_broadcast_sink_has_ep(ep))) + { + conn = NULL; + } + else if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && + bt_bap_unicast_client_has_ep(ep)) + { + conn = bt_bap_unicast_client_ep_get_conn(ep); + } + else if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_SERVER) && + bt_bap_unicast_server_has_ep(ep)) + { + conn = bt_bap_unicast_server_ep_get_conn(ep); + } + else + { + LOG_DBG("Invalid endpoint %p", ep); + conn = NULL; + } + + return conn; +} + +enum bt_bap_ascs_reason bt_audio_verify_qos(const struct bt_bap_qos_cfg *qos) +{ + if (qos->interval < BT_ISO_SDU_INTERVAL_MIN || + qos->interval > BT_ISO_SDU_INTERVAL_MAX) + { + LOG_DBG("Interval not within allowed range: %u (%u-%u)", qos->interval, + BT_ISO_SDU_INTERVAL_MIN, BT_ISO_SDU_INTERVAL_MAX); + return BT_BAP_ASCS_REASON_INTERVAL; + } + + if (qos->framing > BT_BAP_QOS_CFG_FRAMING_FRAMED) + { + LOG_DBG("Invalid Framing 0x%02x", qos->framing); + return BT_BAP_ASCS_REASON_FRAMING; + } + + if (qos->phy != BT_BAP_QOS_CFG_1M && qos->phy != BT_BAP_QOS_CFG_2M && + qos->phy != BT_BAP_QOS_CFG_CODED) + { + LOG_DBG("Invalid PHY 0x%02x", qos->phy); + return BT_BAP_ASCS_REASON_PHY; + } + + if (qos->sdu > BT_ISO_MAX_SDU) + { + LOG_DBG("Invalid SDU %u", qos->sdu); + return BT_BAP_ASCS_REASON_SDU; + } + +#if defined(CONFIG_BT_BAP_BROADCAST_SOURCE) || defined(CONFIG_BT_BAP_UNICAST) + if (qos->latency < BT_ISO_LATENCY_MIN || qos->latency > BT_ISO_LATENCY_MAX) + { + LOG_DBG("Invalid Latency %u", qos->latency); + return BT_BAP_ASCS_REASON_LATENCY; + } +#endif /* CONFIG_BT_BAP_BROADCAST_SOURCE || CONFIG_BT_BAP_UNICAST */ + + if (qos->pd > BT_AUDIO_PD_MAX) + { + LOG_DBG("Invalid presentation delay %u", qos->pd); + return BT_BAP_ASCS_REASON_PD; + } + + return BT_BAP_ASCS_REASON_NONE; +} + +bool bt_audio_valid_codec_cfg(const struct bt_audio_codec_cfg *codec_cfg) +{ + if (codec_cfg == NULL) + { + LOG_DBG("codec is NULL"); + return false; + } + + if (codec_cfg->id == BT_HCI_CODING_FORMAT_LC3) + { + if (codec_cfg->cid != 0U) + { + LOG_DBG("codec_cfg->cid (%u) is invalid", codec_cfg->cid); + return false; + } + + if (codec_cfg->vid != 0U) + { + LOG_DBG("codec_cfg->vid (%u) is invalid", codec_cfg->vid); + return false; + } + } + + /* Verify that codec configuration length is 0 when using + * BT_HCI_CODING_FORMAT_TRANSPARENT as per the core spec, 5.4, Vol 4, Part + * E, 7.8.109 + */ + + if (codec_cfg->id == BT_HCI_CODING_FORMAT_TRANSPARENT && + codec_cfg->data_len != 0) + { + LOG_DBG("Invalid data_len %zu for codec_id %u", codec_cfg->data_len, + codec_cfg->id); + return false; + } + + if (codec_cfg->data_len > CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE) + { + LOG_DBG("codec_cfg->data_len (%zu) is invalid", codec_cfg->data_len); + return false; + } + + if (codec_cfg->id == BT_HCI_CODING_FORMAT_LC3 && + !bt_audio_valid_ltv(codec_cfg->data, codec_cfg->data_len)) + { + LOG_DBG("codec_cfg->data not valid LTV"); + return false; + } + +#if CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE > 0 + if (codec_cfg->meta_len > CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE) + { + LOG_DBG("codec_cfg->meta_len (%zu) is invalid", codec_cfg->meta_len); + return false; + } + + if (codec_cfg->id == BT_HCI_CODING_FORMAT_LC3 && + !bt_audio_valid_ltv(codec_cfg->data, codec_cfg->data_len)) + { + LOG_DBG("codec_cfg->meta not valid LTV"); + return false; + } +#endif /* CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE > 0 */ + + return true; +} + +bool bt_bap_valid_qos_pref(const struct bt_bap_qos_cfg_pref *qos_pref) +{ + const uint8_t phy_mask = + BT_GAP_LE_PHY_1M | BT_GAP_LE_PHY_2M | BT_GAP_LE_PHY_CODED; + + if ((qos_pref->phy & (~phy_mask)) != 0U) + { + LOG_DBG("Invalid phy: %u", qos_pref->phy); + + return false; + } + + if (!IN_RANGE(qos_pref->latency, BT_ISO_LATENCY_MIN, BT_ISO_LATENCY_MAX)) + { + LOG_DBG("Invalid latency: %u", qos_pref->latency); + + return false; + } + + if (qos_pref->pd_min > BT_AUDIO_PD_MAX) + { + LOG_DBG("Invalid pd_min: %u", qos_pref->pd_min); + + return false; + } + + if (qos_pref->pd_max > BT_AUDIO_PD_MAX) + { + LOG_DBG("Invalid pd_min: %u", qos_pref->pd_min); + + return false; + } + + if (qos_pref->pd_max < qos_pref->pd_min) + { + LOG_DBG("Invalid combination of pd_min %u and pd_max: %u", + qos_pref->pd_min, + qos_pref->pd_max); + + return false; + } + + /* If pref_pd_min != BT_AUDIO_PD_PREF_NONE then pd_min <= pref_pd_min <= + * pd_max + */ + + if (qos_pref->pref_pd_min != BT_AUDIO_PD_PREF_NONE && + !IN_RANGE(qos_pref->pref_pd_min, qos_pref->pd_min, qos_pref->pd_max)) + { + LOG_DBG("Invalid combination of pref_pd_min %u, pd_min %u and pd_max: " + "%u", + qos_pref->pref_pd_min, qos_pref->pd_min, qos_pref->pd_max); + + return false; + } + + if (qos_pref->pref_pd_max != BT_AUDIO_PD_PREF_NONE) + { + /* If pref_pd_min == BT_AUDIO_PD_PREF_NONE then pd_min <= pref_pd_max + * <= pd_max + * + * If pref_pd_min != BT_AUDIO_PD_PREF_NONE then pd_min <= pref_pd_min + * <= pref_pd_max <= pd_max + */ + + if (qos_pref->pref_pd_min == BT_AUDIO_PD_PREF_NONE) + { + if (!IN_RANGE(qos_pref->pref_pd_max, qos_pref->pd_min, + qos_pref->pd_max)) + { + LOG_DBG("Invalid combination of pref_pd_max %u, pd_min %u and " + "pd_max: %u", + qos_pref->pref_pd_max, qos_pref->pd_min, + qos_pref->pd_max); + + return false; + } + } + else + { + if (!IN_RANGE(qos_pref->pref_pd_max, qos_pref->pref_pd_min, + qos_pref->pd_max)) + { + LOG_DBG("Invalid combination of pref_pd_max %u, pref_pd_min " + "%u " + "and " + "pd_max: %u", + qos_pref->pref_pd_max, qos_pref->pd_min, + qos_pref->pd_max); + + return false; + } + } + } + + return true; +} + +#if defined(CONFIG_BT_AUDIO_TX) +static bool bt_bap_stream_can_send(const struct bt_bap_stream *stream) +{ + struct bt_bap_ep_info info; + int err; + + if (stream == NULL || stream->ep == NULL) + { + return false; + } + + err = bt_bap_ep_get_info(stream->ep, &info); + if (err != 0) + { + return false; + } + + return info.can_send; +} + +static int bap_stream_send( + struct bt_bap_stream *stream, struct net_buf *buf, uint16_t seq_num, + uint32_t ts, bool has_ts) +{ + struct bt_iso_chan *iso_chan; + struct bt_bap_ep *ep; + int ret; + + if (stream == NULL) + { + LOG_DBG("stream is NULL"); + return -EINVAL; + } + + if (stream->ep == NULL) + { + LOG_DBG("stream->ep %p is NULL", stream); + return -EINVAL; + } + + if (!bt_bap_stream_can_send(stream)) + { + LOG_DBG("Stream is not configured for TX"); + + return -EINVAL; + } + + ep = stream->ep; + + if (ep->state != BT_BAP_EP_STATE_STREAMING) + { + LOG_DBG("Channel %p not ready for streaming (state: %s)", stream, + bt_bap_ep_state_str(ep->state)); + return -EBADMSG; + } + + iso_chan = bt_bap_stream_iso_chan_get(stream); + + if (has_ts) + { + ret = bt_iso_chan_send_ts(iso_chan, buf, seq_num, ts); + } + else + { + ret = bt_iso_chan_send(iso_chan, buf, seq_num); + } + + if (ret < 0) + { + return ret; + } + +#if defined(CONFIG_BT_BAP_DEBUG_STREAM_SEQ_NUM) + if (stream->_prev_seq_num != 0U && seq_num != 0U && + (stream->_prev_seq_num + 1U) != seq_num) + { + LOG_WRN("Unexpected seq_num diff between %u and %u for %p", + stream->_prev_seq_num, seq_num, + stream); + } + + stream->_prev_seq_num = seq_num; +#endif /* CONFIG_BT_BAP_DEBUG_STREAM_SEQ_NUM */ + + return ret; +} + +int bt_bap_stream_send(struct bt_bap_stream *stream, + struct net_buf *buf, uint16_t seq_num) +{ + return bap_stream_send(stream, buf, seq_num, 0, false); +} + +int bt_bap_stream_send_ts( + struct bt_bap_stream *stream, struct net_buf *buf, uint16_t seq_num, + uint32_t ts) +{ + return bap_stream_send(stream, buf, seq_num, ts, true); +} + +int bt_bap_stream_get_tx_sync(struct bt_bap_stream *stream, + struct bt_iso_tx_info *info) +{ + struct bt_iso_chan *iso_chan; + + if (stream == NULL) + { + LOG_DBG("stream is null"); + + return -EINVAL; + } + + if (info == NULL) + { + LOG_DBG("info is null"); + + return -EINVAL; + } + + if (!bt_bap_stream_can_send(stream)) + { + LOG_DBG("Stream is not configured for TX"); + + return -EINVAL; + } + + iso_chan = bt_bap_stream_iso_chan_get(stream); + if (iso_chan == NULL) + { + LOG_DBG("Could not get iso channel from stream %p", stream); + return -EINVAL; + } + + return bt_iso_chan_get_tx_sync(iso_chan, info); +} +#endif /* CONFIG_BT_AUDIO_TX */ + +#if defined(CONFIG_BT_BAP_UNICAST) +static uint8_t conn_get_role(const struct bt_conn *conn) +{ + struct bt_conn_info info; + int err; + + err = bt_conn_get_info(conn, &info); + DEBUGASSERT(err == 0); + + return info.role; +} + +static bool bap_stream_valid_client_snk_ase_op(enum bt_bap_ep_state ep_state, + uint8_t ase_op) +{ + bool valid_op; + + switch (ep_state) + { + case BT_BAP_EP_STATE_IDLE: + switch (ase_op) + { + case BT_ASCS_CONFIG_OP: + valid_op = true; + break; + default: + valid_op = false; + break; + } + break; + case BT_BAP_EP_STATE_CODEC_CONFIGURED: + switch (ase_op) + { + case BT_ASCS_CONFIG_OP: + case BT_ASCS_QOS_OP: + case BT_ASCS_RELEASE_OP: + valid_op = true; + break; + default: + valid_op = false; + break; + } + break; + case BT_BAP_EP_STATE_QOS_CONFIGURED: + switch (ase_op) + { + case BT_ASCS_CONFIG_OP: + case BT_ASCS_QOS_OP: + case BT_ASCS_ENABLE_OP: + case BT_ASCS_RELEASE_OP: + valid_op = true; + break; + default: + valid_op = false; + break; + } + break; + case BT_BAP_EP_STATE_ENABLING: + switch (ase_op) + { + case BT_ASCS_DISABLE_OP: + case BT_ASCS_METADATA_OP: + case BT_ASCS_RELEASE_OP: + valid_op = true; + break; + default: + valid_op = false; + break; + } + break; + case BT_BAP_EP_STATE_STREAMING: + switch (ase_op) + { + case BT_ASCS_DISABLE_OP: + case BT_ASCS_METADATA_OP: + case BT_ASCS_RELEASE_OP: + valid_op = true; + break; + default: + valid_op = false; + break; + } + break; + default: + valid_op = false; + break; + } + + return valid_op; +} + +static bool bap_stream_valid_client_src_ase_op(enum bt_bap_ep_state ep_state, + uint8_t ase_op) +{ + bool valid_op; + + switch (ep_state) + { + case BT_BAP_EP_STATE_IDLE: + switch (ase_op) + { + case BT_ASCS_CONFIG_OP: + valid_op = true; + break; + default: + valid_op = false; + break; + } + break; + case BT_BAP_EP_STATE_CODEC_CONFIGURED: + switch (ase_op) + { + case BT_ASCS_CONFIG_OP: + case BT_ASCS_QOS_OP: + case BT_ASCS_RELEASE_OP: + valid_op = true; + break; + default: + valid_op = false; + break; + } + break; + case BT_BAP_EP_STATE_QOS_CONFIGURED: + switch (ase_op) + { + case BT_ASCS_CONFIG_OP: + case BT_ASCS_QOS_OP: + case BT_ASCS_ENABLE_OP: + case BT_ASCS_RELEASE_OP: + valid_op = true; + break; + default: + valid_op = false; + break; + } + break; + case BT_BAP_EP_STATE_ENABLING: + switch (ase_op) + { + case BT_ASCS_START_OP: + case BT_ASCS_DISABLE_OP: + case BT_ASCS_METADATA_OP: + case BT_ASCS_RELEASE_OP: + valid_op = true; + break; + default: + valid_op = false; + break; + } + break; + case BT_BAP_EP_STATE_STREAMING: + switch (ase_op) + { + case BT_ASCS_DISABLE_OP: + case BT_ASCS_METADATA_OP: + case BT_ASCS_RELEASE_OP: + valid_op = true; + break; + default: + valid_op = false; + break; + } + break; + case BT_BAP_EP_STATE_DISABLING: + switch (ase_op) + { + case BT_ASCS_STOP_OP: + case BT_ASCS_RELEASE_OP: + valid_op = true; + break; + default: + valid_op = false; + break; + } + break; + default: + valid_op = false; + break; + } + + return valid_op; +} + +static bool bap_stream_valid_client_ase_op( + enum bt_audio_dir ep_dir, enum bt_bap_ep_state ep_state, uint8_t ase_op) +{ + bool valid_op; + + if (ep_dir == BT_AUDIO_DIR_SINK + && IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK)) + { + valid_op = bap_stream_valid_client_snk_ase_op(ep_state, ase_op); + } + else if (ep_dir == BT_AUDIO_DIR_SOURCE && + IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC)) + { + valid_op = bap_stream_valid_client_src_ase_op(ep_state, ase_op); + } + else + { + valid_op = false; + } + + return valid_op; +} + +static bool bap_stream_valid_server_snk_ase_op(enum bt_bap_ep_state ep_state, + uint8_t ase_op) +{ + bool valid_op; + + switch (ep_state) + { + case BT_BAP_EP_STATE_IDLE: + switch (ase_op) + { + case BT_ASCS_CONFIG_OP: + valid_op = true; + break; + default: + valid_op = false; + break; + } + break; + case BT_BAP_EP_STATE_CODEC_CONFIGURED: + switch (ase_op) + { + case BT_ASCS_CONFIG_OP: + case BT_ASCS_RELEASE_OP: + valid_op = true; + break; + default: + valid_op = false; + break; + } + break; + case BT_BAP_EP_STATE_QOS_CONFIGURED: + switch (ase_op) + { + case BT_ASCS_CONFIG_OP: + case BT_ASCS_RELEASE_OP: + valid_op = true; + break; + default: + valid_op = false; + break; + } + break; + case BT_BAP_EP_STATE_ENABLING: + switch (ase_op) + { + case BT_ASCS_START_OP: + case BT_ASCS_DISABLE_OP: + case BT_ASCS_METADATA_OP: + case BT_ASCS_RELEASE_OP: + valid_op = true; + break; + default: + valid_op = false; + break; + } + break; + case BT_BAP_EP_STATE_STREAMING: + switch (ase_op) + { + case BT_ASCS_DISABLE_OP: + case BT_ASCS_METADATA_OP: + case BT_ASCS_RELEASE_OP: + valid_op = true; + break; + default: + valid_op = false; + break; + } + break; + default: + valid_op = false; + break; + } + + return valid_op; +} + +static bool bap_stream_valid_server_src_ase_op(enum bt_bap_ep_state ep_state, + uint8_t ase_op) +{ + bool valid_op; + + switch (ep_state) + { + case BT_BAP_EP_STATE_IDLE: + switch (ase_op) + { + case BT_ASCS_CONFIG_OP: + valid_op = true; + break; + default: + valid_op = false; + break; + } + break; + case BT_BAP_EP_STATE_CODEC_CONFIGURED: + switch (ase_op) + { + case BT_ASCS_CONFIG_OP: + case BT_ASCS_RELEASE_OP: + valid_op = true; + break; + default: + valid_op = false; + break; + } + break; + case BT_BAP_EP_STATE_QOS_CONFIGURED: + switch (ase_op) + { + case BT_ASCS_CONFIG_OP: + case BT_ASCS_RELEASE_OP: + valid_op = true; + break; + default: + valid_op = false; + break; + } + break; + case BT_BAP_EP_STATE_ENABLING: + switch (ase_op) + { + case BT_ASCS_DISABLE_OP: + case BT_ASCS_METADATA_OP: + case BT_ASCS_RELEASE_OP: + valid_op = true; + break; + default: + valid_op = false; + break; + } + break; + case BT_BAP_EP_STATE_STREAMING: + switch (ase_op) + { + case BT_ASCS_DISABLE_OP: + case BT_ASCS_METADATA_OP: + case BT_ASCS_RELEASE_OP: + valid_op = true; + break; + default: + valid_op = false; + break; + } + break; + case BT_BAP_EP_STATE_DISABLING: + switch (ase_op) + { + case BT_ASCS_RELEASE_OP: + valid_op = true; + break; + default: + valid_op = false; + break; + } + break; + default: + valid_op = false; + break; + } + + return valid_op; +} + +static bool bap_stream_valid_server_ase_op( + enum bt_audio_dir ep_dir, enum bt_bap_ep_state ep_state, uint8_t ase_op) +{ + bool valid_op; + + if (ep_dir == BT_AUDIO_DIR_SINK && IS_ENABLED(CONFIG_BT_ASCS_ASE_SNK)) + { + valid_op = bap_stream_valid_server_snk_ase_op(ep_state, ase_op); + } + else if (ep_dir == BT_AUDIO_DIR_SOURCE && + IS_ENABLED(CONFIG_BT_ASCS_ASE_SRC)) + { + valid_op = bap_stream_valid_server_src_ase_op(ep_state, ase_op); + } + else + { + valid_op = false; + } + + return valid_op; +} + +static bool bap_stream_valid_ase_op( + const struct bt_conn *conn, const struct bt_bap_ep *ep, uint8_t ase_op) +{ + DEBUGASSERT(ep != NULL); + DEBUGASSERT(ep->dir == BT_AUDIO_DIR_SINK + || ep->dir == BT_AUDIO_DIR_SOURCE); + + const enum bt_bap_ep_state ep_state = ep->state; + const enum bt_audio_dir ep_dir = ep->dir; + bool valid_op; + uint8_t role; + + role = conn_get_role(conn); + if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && + role == BT_CONN_ROLE_CENTRAL) + { + valid_op = bap_stream_valid_client_ase_op(ep_dir, ep_state, ase_op); + } + else if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_SERVER) && + role == BT_CONN_ROLE_PERIPHERAL) + { + valid_op = bap_stream_valid_server_ase_op(ep_dir, ep_state, ase_op); + } + else + { + valid_op = false; + } + + LOG_DBG("ASE operation (dir %s) %s (%u) in state %s is%s valid", + bt_audio_dir_to_str(ep_dir), + bt_ascs_op_str(ase_op), + ase_op, + bt_bap_ep_state_str(ep_state), + valid_op ? "" : " not"); + + return valid_op; +} + +static bool valid_snk_state_transition(enum bt_bap_ep_state old_state, + enum bt_bap_ep_state new_state) +{ + bool valid_transition; + + switch (old_state) + { + case BT_BAP_EP_STATE_IDLE: + switch (new_state) + { + case BT_BAP_EP_STATE_CODEC_CONFIGURED: + valid_transition = true; + break; + default: + valid_transition = false; + break; + } + break; + case BT_BAP_EP_STATE_CODEC_CONFIGURED: + switch (new_state) + { + case BT_BAP_EP_STATE_CODEC_CONFIGURED: + case BT_BAP_EP_STATE_QOS_CONFIGURED: + case BT_BAP_EP_STATE_RELEASING: + valid_transition = true; + break; + default: + valid_transition = false; + break; + } + break; + case BT_BAP_EP_STATE_QOS_CONFIGURED: + switch (new_state) + { + case BT_BAP_EP_STATE_CODEC_CONFIGURED: + case BT_BAP_EP_STATE_QOS_CONFIGURED: + case BT_BAP_EP_STATE_ENABLING: + case BT_BAP_EP_STATE_RELEASING: + valid_transition = true; + break; + default: + valid_transition = false; + break; + } + break; + case BT_BAP_EP_STATE_ENABLING: + switch (new_state) + { + case BT_BAP_EP_STATE_QOS_CONFIGURED: + case BT_BAP_EP_STATE_ENABLING: + case BT_BAP_EP_STATE_STREAMING: + case BT_BAP_EP_STATE_RELEASING: + valid_transition = true; + break; + default: + valid_transition = false; + break; + } + break; + case BT_BAP_EP_STATE_STREAMING: + switch (new_state) + { + case BT_BAP_EP_STATE_QOS_CONFIGURED: + case BT_BAP_EP_STATE_STREAMING: + case BT_BAP_EP_STATE_RELEASING: + valid_transition = true; + break; + default: + valid_transition = false; + break; + } + break; + case BT_BAP_EP_STATE_RELEASING: + switch (new_state) + { + case BT_BAP_EP_STATE_IDLE: + case BT_BAP_EP_STATE_CODEC_CONFIGURED: + valid_transition = true; + break; + default: + valid_transition = false; + break; + } + break; + default: + valid_transition = false; + break; + } + + return valid_transition; +} + +static bool valid_src_state_transition(enum bt_bap_ep_state old_state, + enum bt_bap_ep_state new_state) +{ + bool valid_transition; + + switch (old_state) + { + case BT_BAP_EP_STATE_IDLE: + switch (new_state) + { + case BT_BAP_EP_STATE_IDLE: + case BT_BAP_EP_STATE_CODEC_CONFIGURED: + valid_transition = true; + break; + default: + valid_transition = false; + break; + } + break; + case BT_BAP_EP_STATE_CODEC_CONFIGURED: + switch (new_state) + { + case BT_BAP_EP_STATE_CODEC_CONFIGURED: + case BT_BAP_EP_STATE_QOS_CONFIGURED: + case BT_BAP_EP_STATE_RELEASING: + valid_transition = true; + break; + default: + valid_transition = false; + break; + } + break; + case BT_BAP_EP_STATE_QOS_CONFIGURED: + switch (new_state) + { + case BT_BAP_EP_STATE_CODEC_CONFIGURED: + case BT_BAP_EP_STATE_QOS_CONFIGURED: + case BT_BAP_EP_STATE_ENABLING: + case BT_BAP_EP_STATE_RELEASING: + valid_transition = true; + break; + default: + valid_transition = false; + break; + } + break; + case BT_BAP_EP_STATE_ENABLING: + switch (new_state) + { + case BT_BAP_EP_STATE_ENABLING: + case BT_BAP_EP_STATE_STREAMING: + case BT_BAP_EP_STATE_DISABLING: + case BT_BAP_EP_STATE_RELEASING: + valid_transition = true; + break; + default: + valid_transition = false; + break; + } + break; + case BT_BAP_EP_STATE_STREAMING: + switch (new_state) + { + case BT_BAP_EP_STATE_QOS_CONFIGURED: /* Allowed during CIS disconnect */ + case BT_BAP_EP_STATE_STREAMING: + case BT_BAP_EP_STATE_DISABLING: + case BT_BAP_EP_STATE_RELEASING: + valid_transition = true; + break; + default: + valid_transition = false; + break; + } + break; + case BT_BAP_EP_STATE_DISABLING: + switch (new_state) + { + case BT_BAP_EP_STATE_QOS_CONFIGURED: + case BT_BAP_EP_STATE_RELEASING: + valid_transition = true; + break; + default: + valid_transition = false; + break; + } + break; + case BT_BAP_EP_STATE_RELEASING: + switch (new_state) + { + case BT_BAP_EP_STATE_IDLE: + case BT_BAP_EP_STATE_CODEC_CONFIGURED: + valid_transition = true; + break; + default: + valid_transition = false; + break; + } + break; + default: + valid_transition = false; + break; + } + + return valid_transition; +} + +bool bt_bap_stream_valid_state_transition(const struct bt_bap_ep *ep, + enum bt_bap_ep_state state) +{ + /* The tables below reflect the state machine from ASCS + * The state machines in the ASCS specification does not show the unique + * cases of ACL disconnection (any state -> idle or codec configured) or + * ISO disconnection, but they are supported here + * + * ASCS_v1.0 3.2 ASE state machine transitions: + * + * If the server detects link loss of a CIS for an ASE in the Streaming + * state or the Disabling state, the server shall immediately transition + * that ASE to the QoS Configured state. + */ + + DEBUGASSERT(ep != NULL); + DEBUGASSERT(ep->dir == BT_AUDIO_DIR_SINK || ep->dir == + BT_AUDIO_DIR_SOURCE); + + const enum bt_bap_ep_state ep_state = ep->state; + const enum bt_audio_dir ep_dir = ep->dir; + bool valid_transition; + + if (ep_dir == BT_AUDIO_DIR_SINK && + (IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK) || + IS_ENABLED(CONFIG_BT_ASCS_ASE_SNK))) + { + valid_transition = valid_snk_state_transition(ep_state, state); + } + else if (ep_dir == BT_AUDIO_DIR_SOURCE && + (IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC) || + IS_ENABLED(CONFIG_BT_ASCS_ASE_SRC))) + { + valid_transition = valid_src_state_transition(ep_state, state); + } + else + { + valid_transition = false; + } + + LOG_DBG("state transition (dir %s): %s -> %s is%s valid", + bt_audio_dir_to_str(ep_dir), + bt_bap_ep_state_str(ep_state), bt_bap_ep_state_str(state), + valid_transition ? "" : " not"); + + return valid_transition; +} + +/** Checks if the stream can terminate the CIS + * + * If the CIS is used for another stream, or if the CIS is not in the + * connected state it will return false. + */ + +bool bt_bap_stream_can_disconnect(const struct bt_bap_stream *stream) +{ + const struct bt_bap_ep *stream_ep; + enum bt_iso_state iso_state; + + if (stream == NULL) + { + return false; + } + + stream_ep = stream->ep; + + if (stream_ep == NULL || stream_ep->iso == NULL) + { + return false; + } + + iso_state = stream_ep->iso->chan.state; + + if (iso_state == BT_ISO_STATE_CONNECTED || + iso_state == BT_ISO_STATE_CONNECTING) + { + const struct bt_bap_ep *pair_ep; + + pair_ep = bt_bap_iso_get_paired_ep(stream_ep); + + /* If there are no paired endpoint, or the paired endpoint is in the + * QoS Configured or Codec Configured state, we can disconnect the CIS + */ + + if (pair_ep == NULL || + pair_ep->state == BT_BAP_EP_STATE_QOS_CONFIGURED || + pair_ep->state == BT_BAP_EP_STATE_CODEC_CONFIGURED) + { + return true; + } + } + + return false; +} + +enum bt_bap_ascs_reason bt_bap_stream_verify_qos( + const struct bt_bap_stream *stream, const struct bt_bap_qos_cfg *qos) +{ + const struct bt_bap_qos_cfg_pref *qos_pref = &stream->ep->qos_pref; + + if (qos_pref->latency < qos->latency) + { + /* Latency is a preferred value. Print debug info but do not fail. */ + + LOG_DBG("Latency %u higher than preferred max %u", qos->latency, + qos_pref->latency); + } + + if (!IN_RANGE(qos->pd, qos_pref->pd_min, qos_pref->pd_max)) + { + LOG_DBG("Presentation Delay not within range: min %u max %u pd %u", + qos_pref->pd_min, + qos_pref->pd_max, qos->pd); + return BT_BAP_ASCS_REASON_PD; + } + + return BT_BAP_ASCS_REASON_NONE; +} + +void bt_bap_stream_detach(struct bt_bap_stream *stream) +{ + LOG_DBG("stream %p conn %p ep %p", stream, (void *)stream->conn, + (void *)stream->ep); + + bt_conn_drop(&stream->conn); + stream->codec_cfg = NULL; + + if (stream->ep != NULL) + { + stream->ep->stream = NULL; + stream->ep = NULL; + } +} + +int bt_bap_stream_disconnect(struct bt_bap_stream *stream) +{ + struct bt_iso_chan *iso_chan; + + LOG_DBG("stream %p", stream); + + if (stream == NULL) + { + return -EINVAL; + } + + iso_chan = bt_bap_stream_iso_chan_get(stream); + if (iso_chan == NULL || iso_chan->iso == NULL) + { + LOG_DBG("Not connected"); + + return -ENOTCONN; + } + + return bt_iso_chan_disconnect(iso_chan); +} + +void bt_bap_stream_reset(struct bt_bap_stream *stream) +{ + LOG_DBG("stream %p", stream); + + if (stream == NULL) + { + return; + } + + if (stream->ep != NULL && stream->ep->iso != NULL) + { + bt_bap_iso_unbind_ep(stream->ep->iso, stream->ep); + } + + bt_bap_stream_detach(stream); +} + +#if defined(CONFIG_BT_BAP_UNICAST_CLIENT) + +int bt_bap_stream_config( + struct bt_conn *conn, struct bt_bap_stream *stream, struct bt_bap_ep *ep, + const struct bt_audio_codec_cfg *codec_cfg) +{ + int err; + + LOG_DBG("conn %p stream %p, ep %p codec_cfg %p codec id 0x%02x " + "codec cid 0x%04x codec vid 0x%04x", + (void *)conn, stream, ep, codec_cfg, codec_cfg ? codec_cfg->id : 0, + codec_cfg ? codec_cfg->cid : 0, codec_cfg ? codec_cfg->vid : 0); + + if (conn == NULL || stream == NULL || codec_cfg == NULL || ep == NULL) + { + LOG_DBG("NULL value(s) supplied)"); + return -EINVAL; + } + + if (stream->conn != NULL) + { + LOG_DBG("Stream already configured for conn %p", (void *)stream->conn); + return -EALREADY; + } + + if (!bap_stream_valid_ase_op(conn, ep, BT_ASCS_CONFIG_OP)) + { + return -EBADMSG; + } + + if (ep->stream != NULL) + { + LOG_DBG("Endpoint %p already configured for stream %p", ep, stream); + + return -EINVAL; + } + + DEBUGASSERT(ep->iso == NULL); + + bt_bap_stream_attach(conn, stream, ep); + + /* If a stream has been added to a group at this point, then it has a + * reference to a CIS. and we can bind the ep to the CIS + */ + + if (stream->iso != NULL) + { + struct bt_bap_iso *bap_iso = CONTAINER_OF(stream->iso, + struct bt_bap_iso, + chan); + + /* Not yet bound with the bap_iso */ + + bt_bap_iso_bind_ep(bap_iso, ep); + } + + err = bt_bap_unicast_client_config(stream, codec_cfg); + if (err != 0) + { + LOG_DBG("Failed to configure stream: %d", err); + + bt_bap_stream_reset(stream); + return err; + } + + return 0; +} + +int bt_bap_stream_qos(struct bt_conn *conn, + struct bt_bap_unicast_group *group) +{ + struct bt_bap_stream *stream; + int err; + + LOG_DBG("conn %p group %p", (void *)conn, group); + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + return -EINVAL; + } + + if (group == NULL) + { + LOG_DBG("group is NULL"); + return -EINVAL; + } + + if (list_is_empty(&group->streams)) + { + LOG_DBG("group stream list is empty"); + return -ENOEXEC; + } + + list_for_every_container(&group->streams, stream, _node) + { + const struct bt_bap_ep *ep = stream->ep; + + if (conn != stream->conn) + { + continue; + } + + if (ep != NULL && !bap_stream_valid_ase_op(conn, ep, BT_ASCS_QOS_OP)) + { + return -EBADMSG; + } + } + + err = bt_bap_unicast_client_qos(conn, group); + if (err != 0) + { + LOG_DBG("Failed to configure stream: %d", err); + return err; + } + + return 0; +} + +int bt_bap_stream_enable(struct bt_bap_stream *stream, + const uint8_t meta[], size_t meta_len) +{ + int err; + + LOG_DBG("stream %p", stream); + + if (stream == NULL || stream->ep == NULL || stream->conn == NULL) + { + LOG_DBG("Invalid stream"); + return -EINVAL; + } + + if (!bap_stream_valid_ase_op(stream->conn, stream->ep, BT_ASCS_ENABLE_OP)) + { + return -EBADMSG; + } + + err = bt_bap_unicast_client_enable(stream, meta, meta_len); + if (err != 0) + { + LOG_DBG("Failed to enable stream: %d", err); + return err; + } + + return 0; +} + +int bt_bap_stream_stop(struct bt_bap_stream *stream) +{ + int err; + + if (stream == NULL || stream->ep == NULL || stream->conn == NULL) + { + LOG_DBG("Invalid stream"); + return -EINVAL; + } + + if (!bap_stream_valid_ase_op(stream->conn, stream->ep, BT_ASCS_STOP_OP)) + { + return -EBADMSG; + } + + err = bt_bap_unicast_client_stop(stream); + if (err != 0) + { + LOG_DBG("Stopping stream failed: %d", err); + return err; + } + + return 0; +} +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT */ + +int bt_bap_stream_reconfig(struct bt_bap_stream *stream, + const struct bt_audio_codec_cfg *codec_cfg) +{ + struct bt_conn *conn; + uint8_t role; + int err; + + LOG_DBG("stream %p codec_cfg %p", stream, codec_cfg); + + if (stream == NULL || stream->ep == NULL || stream->conn == NULL) + { + LOG_DBG("Invalid stream"); + return -EINVAL; + } + + if (codec_cfg == NULL) + { + LOG_DBG("codec_cfg is NULL"); + return -EINVAL; + } + + conn = stream->conn; + if (!bap_stream_valid_ase_op(conn, stream->ep, BT_ASCS_CONFIG_OP)) + { + return -EBADMSG; + } + + role = conn_get_role(conn); + if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && + role == BT_CONN_ROLE_CENTRAL) + { + err = bt_bap_unicast_client_config(stream, codec_cfg); + } + else if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_SERVER) && + role == BT_CONN_ROLE_PERIPHERAL) + { + err = bt_bap_unicast_server_reconfig(stream, codec_cfg); + } + else + { + err = -EOPNOTSUPP; + } + + if (err != 0) + { + LOG_DBG("reconfiguring stream failed: %d", err); + } + + return err; +} + +#if defined(CONFIG_BT_BAP_UNICAST_CLIENT) +int bt_bap_stream_connect(struct bt_bap_stream *stream) +{ + enum bt_bap_ep_state state; + + LOG_DBG("stream %p ep %p", stream, stream == NULL ? NULL : stream->ep); + + if (stream == NULL || stream->ep == NULL || stream->conn == NULL) + { + LOG_DBG("Invalid stream"); + return -EINVAL; + } + + /* Valid only after the CIS ID has been assigned in QoS configured state + * and while we are not streaming + */ + + state = stream->ep->state; + switch (state) + { + case BT_BAP_EP_STATE_QOS_CONFIGURED: + case BT_BAP_EP_STATE_ENABLING: + break; + default: + LOG_ERR("Invalid state: %s", bt_bap_ep_state_str(state)); + return -EBADMSG; + } + + /* Only a unicast client can connect a stream */ + + if (conn_get_role(stream->conn) == BT_CONN_ROLE_CENTRAL) + { + return bt_bap_unicast_client_connect(stream); + } + else + { + return -EOPNOTSUPP; + } +} +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT */ + +int bt_bap_stream_start(struct bt_bap_stream *stream) +{ + struct bt_conn *conn; + uint8_t role; + int err; + + LOG_DBG("stream %p ep %p", stream, stream == NULL ? NULL : stream->ep); + + if (stream == NULL || stream->ep == NULL || stream->conn == NULL) + { + LOG_DBG("Invalid stream"); + return -EINVAL; + } + + conn = stream->conn; + if (!bap_stream_valid_ase_op(conn, stream->ep, BT_ASCS_START_OP)) + { + return -EBADMSG; + } + + role = conn_get_role(conn); + if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && + role == BT_CONN_ROLE_CENTRAL) + { + err = bt_bap_unicast_client_start(stream); + } + else if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_SERVER) && + role == BT_CONN_ROLE_PERIPHERAL) + { + err = bt_bap_unicast_server_start(stream); + } + else + { + err = -EOPNOTSUPP; + } + + if (err != 0) + { + LOG_DBG("Starting stream failed: %d", err); + return err; + } + + return 0; +} + +int bt_bap_stream_metadata(struct bt_bap_stream *stream, + const uint8_t meta[], size_t meta_len) +{ + struct bt_conn *conn; + uint8_t role; + int err; + + LOG_DBG("stream %p meta_len %zu", stream, meta_len); + + if (stream == NULL || stream->ep == NULL || stream->conn == NULL) + { + LOG_DBG("Invalid stream"); + return -EINVAL; + } + + if ((meta == NULL && meta_len != 0U) || (meta != NULL && meta_len == 0U)) + { + LOG_DBG("Invalid meta (%p) or len (%zu)", meta, meta_len); + return -EINVAL; + } + + conn = stream->conn; + if (!bap_stream_valid_ase_op(conn, stream->ep, BT_ASCS_METADATA_OP)) + { + return -EBADMSG; + } + + role = conn_get_role(conn); + if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && + role == BT_CONN_ROLE_CENTRAL) + { + err = bt_bap_unicast_client_metadata(stream, meta, meta_len); + } + else if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_SERVER) && + role == BT_CONN_ROLE_PERIPHERAL) + { + err = bt_bap_unicast_server_metadata(stream, meta, meta_len); + } + else + { + err = -EOPNOTSUPP; + } + + if (err != 0) + { + LOG_DBG("Updating metadata failed: %d", err); + return err; + } + + return 0; +} + +int bt_bap_stream_disable(struct bt_bap_stream *stream) +{ + struct bt_conn *conn; + uint8_t role; + int err; + + LOG_DBG("stream %p", stream); + + if (stream == NULL || stream->ep == NULL || stream->conn == NULL) + { + LOG_DBG("Invalid stream"); + return -EINVAL; + } + + conn = stream->conn; + if (!bap_stream_valid_ase_op(conn, stream->ep, BT_ASCS_DISABLE_OP)) + { + return -EBADMSG; + } + + role = conn_get_role(conn); + if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && + role == BT_CONN_ROLE_CENTRAL) + { + err = bt_bap_unicast_client_disable(stream); + } + else if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_SERVER) && + role == BT_CONN_ROLE_PERIPHERAL) + { + err = bt_bap_unicast_server_disable(stream); + } + else + { + err = -EOPNOTSUPP; + } + + if (err != 0) + { + LOG_DBG("Disabling stream failed: %d", err); + return err; + } + + return 0; +} + +int bt_bap_stream_release(struct bt_bap_stream *stream) +{ + const struct bt_bap_ep *ep; + struct bt_conn *conn; + uint8_t role; + int err; + + LOG_DBG("stream %p", stream); + + if (stream == NULL || stream->ep == NULL || stream->conn == NULL) + { + LOG_DBG("Invalid stream"); + return -EINVAL; + } + + conn = stream->conn; + ep = stream->ep; + + /* If ep->state == BT_BAP_EP_STATE_IDLE, then the stream and/or ASE will be + * reset even if it is not a valid ASCS transition + */ + + if (ep->state != BT_BAP_EP_STATE_IDLE && !bap_stream_valid_ase_op(conn, ep, + BT_ASCS_RELEASE_OP)) + { + return -EBADMSG; + } + + role = conn_get_role(conn); + if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && + role == BT_CONN_ROLE_CENTRAL) + { + err = bt_bap_unicast_client_release(stream); + } + else if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_SERVER) && + role == BT_CONN_ROLE_PERIPHERAL) + { + err = bt_bap_unicast_server_release(stream); + } + else + { + err = -EOPNOTSUPP; + } + + if (err != 0) + { + LOG_DBG("Releasing stream failed: %d", err); + return err; + } + + return 0; +} +#endif /* CONFIG_BT_BAP_UNICAST */ diff --git a/libs/zblue/audio/bap_stream.h b/libs/zblue/audio/bap_stream.h new file mode 100644 index 0000000000000..40d1f58f351f9 --- /dev/null +++ b/libs/zblue/audio/bap_stream.h @@ -0,0 +1,58 @@ +/**************************************************************************** + * libs/zblue/audio/bap_stream.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* @file + * @brief Internal APIs for Audio Stream handling + + * Copyright (c) 2020 Intel Corporation Copyright (c) 2021-2025 Nordic + * Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include + +void bt_bap_stream_init(struct bt_bap_stream *stream); + +/* Disconnect ISO channel */ + +int bt_bap_stream_disconnect(struct bt_bap_stream *stream); + +void bt_bap_stream_reset(struct bt_bap_stream *stream); + +void bt_bap_stream_attach(struct bt_conn *conn, + struct bt_bap_stream *stream, struct bt_bap_ep + *ep); + +void bt_bap_qos_cfg_to_iso_qos(struct bt_iso_chan_io_qos *io, + const struct bt_bap_qos_cfg *qos_cfg); + +void bt_bap_stream_detach(struct bt_bap_stream *stream); + +enum bt_bap_ascs_reason +bt_audio_verify_qos(const struct bt_bap_qos_cfg *qos); +bool bt_audio_valid_codec_cfg(const struct bt_audio_codec_cfg *codec_cfg); +bool bt_bap_valid_qos_pref(const struct bt_bap_qos_cfg_pref *qos_pref); +bool bt_bap_stream_can_disconnect(const struct bt_bap_stream *stream); + +enum bt_bap_ascs_reason bt_bap_stream_verify_qos( + const struct bt_bap_stream *stream, const struct bt_bap_qos_cfg *qos); + +struct bt_iso_chan *bt_bap_stream_iso_chan_get(struct bt_bap_stream *stream); +bool bt_bap_stream_valid_state_transition(const struct bt_bap_ep *ep, + enum bt_bap_ep_state state); diff --git a/libs/zblue/audio/bap_unicast_client.c b/libs/zblue/audio/bap_unicast_client.c new file mode 100644 index 0000000000000..277d31e87d1f5 --- /dev/null +++ b/libs/zblue/audio/bap_unicast_client.c @@ -0,0 +1,5876 @@ +/**************************************************************************** + * libs/zblue/audio/bap_unicast_client.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* @file + * @brief Bluetooth Unicast Client + */ + +/* Copyright (c) 2020 Intel Corporation + * Copyright (c) 2022-2026 Nordic Semiconductor ASA Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../host/conn_internal.h" +#include "../host/hci_core.h" +#include "../host/iso_internal.h" + +#include "ascs_internal.h" +#include "audio_internal.h" +#include "bap_endpoint.h" +#include "bap_iso.h" +#include "bap_unicast_client_internal.h" +#include "pacs_internal.h" + +BUILD_ASSERT(CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 || + CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0, + "CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT or " + "CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT shall be non-zero"); + +BUILD_ASSERT(CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT == 0 || + CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 1, + "CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT shall be either " + "0 or > 1"); + +BUILD_ASSERT(CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT == 0 || + CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 1, + "CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT shall be either " + "0 or > 1"); + +LOG_MODULE_REGISTER(bt_bap_unicast_client, + CONFIG_BT_BAP_UNICAST_CLIENT_LOG_LEVEL); + +#define PAC_DIR_UNUSED(dir) ((dir) != BT_AUDIO_DIR_SINK && (dir) != BT_AUDIO_DIR_SOURCE) +#define BAP_HANDLE_UNUSED 0x0000u +struct bt_bap_unicast_client_ep +{ + uint16_t handle; + uint16_t cp_handle; + struct bt_gatt_subscribe_params subscribe; + struct bt_gatt_discover_params discover; + struct bt_bap_ep ep; + struct k_work_delayable ase_read_work; + + /* Bool to help handle different order of CP and ASE notification when + * releasing + */ + + bool release_requested; + bool cp_ntf_pending; +}; + +static const struct bt_uuid *snk_uuid = BT_UUID_PACS_SNK; +static const struct bt_uuid *src_uuid = BT_UUID_PACS_SRC; +static const struct bt_uuid *pacs_supp_context_uuid = + BT_UUID_PACS_SUPPORTED_CONTEXT; +static const struct bt_uuid *pacs_snk_loc_uuid = BT_UUID_PACS_SNK_LOC; +static const struct bt_uuid *pacs_src_loc_uuid = BT_UUID_PACS_SRC_LOC; +static const struct bt_uuid *pacs_avail_ctx_uuid = + BT_UUID_PACS_AVAILABLE_CONTEXT; +static const struct bt_uuid *ase_snk_uuid = BT_UUID_ASCS_ASE_SNK; +static const struct bt_uuid *ase_src_uuid = BT_UUID_ASCS_ASE_SRC; +static const struct bt_uuid *cp_uuid = BT_UUID_ASCS_ASE_CP; + +static struct bt_bap_unicast_group unicast_groups[UNICAST_GROUP_CNT]; + +enum unicast_client_flag +{ + UNICAST_CLIENT_FLAG_BUSY, + UNICAST_CLIENT_FLAG_DISCOVERY_IN_PROGRESS, + + UNICAST_CLIENT_FLAG_NUM_FLAGS, /* keep as last */ +}; + +static struct unicast_client +{ +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 + struct bt_bap_unicast_client_ep + snks[CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT]; +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 */ +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 + struct bt_bap_unicast_client_ep + srcs[CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT]; +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 */ + + struct bt_gatt_subscribe_params cp_subscribe; + struct bt_gatt_subscribe_params snk_loc_subscribe; + struct bt_gatt_subscribe_params src_loc_subscribe; + struct bt_gatt_subscribe_params avail_ctx_subscribe; + struct bt_gatt_subscribe_params supp_ctx_subscribe; + + /* TODO: We should be able to reduce the number of discover params for + * CCCD discovery, but requires additional work if it is to support an + * arbitrary number of EATT bearers, as control point and location CCCD + * discovery needs to be done in serial to avoid using the same discover + * parameters twice + */ + + struct bt_gatt_discover_params loc_cc_disc; + struct bt_gatt_discover_params avail_ctx_cc_disc; + struct bt_gatt_discover_params supp_ctx_cc_disc; + + /* Discovery parameters */ + + enum bt_audio_dir dir; + union + { + struct bt_gatt_read_params read_params; + struct bt_gatt_discover_params disc_params; + struct bt_gatt_write_params write_params; + }; + + /* The att_buf needs to use the maximum ATT attribute size as a single + * PAC record may use the full size + */ + + uint8_t att_buf[BT_ATT_MAX_ATTRIBUTE_LEN]; + struct net_buf_simple net_buf; + + atomic_define(flags, UNICAST_CLIENT_FLAG_NUM_FLAGS); +} + +uni_cli_insts[CONFIG_BT_MAX_CONN]; + +static struct list_node unicast_client_cbs = + LIST_INITIAL_VALUE(unicast_client_cbs); + +/* TODO: Move the functions to avoid these prototypes */ + +static int unicast_client_ep_set_metadata(struct bt_bap_ep *ep, + void *data, uint8_t len); + +static int unicast_client_ep_set_codec_cfg( + struct bt_bap_ep *ep, uint8_t id, uint16_t cid, uint16_t vid, void *data, + uint8_t len); +static int unicast_client_ep_start(struct bt_bap_ep *ep, + struct net_buf_simple *buf); + +static int unicast_client_ase_discover(struct bt_conn *conn, + uint16_t start_handle); + +static void unicast_client_reset(struct bt_bap_ep *ep, uint8_t reason); + +static void delayed_ase_read_handler(struct k_work *work); +static void unicast_client_ep_set_status( + const struct bt_conn *conn, struct bt_bap_ep *ep, + struct net_buf_simple *buf, bool is_notification); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int unicast_client_send_start(struct bt_bap_ep *ep) +{ + if (ep->receiver_ready != true || ep->dir != BT_AUDIO_DIR_SOURCE) + { + LOG_DBG("Invalid ep %p %u %s", ep, ep->receiver_ready, + bt_audio_dir_to_str(ep->dir)); + + return -EINVAL; + } + + struct bt_ascs_start_op *req; + struct net_buf_simple *buf; + int err; + + buf = bt_bap_unicast_client_ep_create_pdu(ep->stream->conn, + BT_ASCS_START_OP); + if (buf == NULL) + { + LOG_DBG("Could not create PDU"); + return -EBUSY; + } + + req = net_buf_simple_add(buf, sizeof(*req)); + req->num_ases = 1U; + + err = unicast_client_ep_start(ep, buf); + if (err != 0) + { + LOG_DBG("unicast_client_ep_start failed: %d", err); + + return err; + } + + err = bt_bap_unicast_client_ep_send(ep->stream->conn, ep, buf); + if (err != 0) + { + LOG_DBG("bt_bap_unicast_client_ep_send failed: %d", err); + + return err; + } + + return 0; +} + +static bool unicast_client_ep_idle_state(struct bt_bap_ep *ep); + +static struct bt_bap_stream *audio_stream_by_ep_id( + const struct bt_conn *conn, uint8_t id) +{ +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 || CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 + const uint8_t conn_index = bt_conn_index(conn); +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 || \ + * CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 \ + */ + +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 + for (size_t i = 0U; i < ARRAY_SIZE(uni_cli_insts[conn_index].snks); i++) + { + const struct bt_bap_unicast_client_ep *client_ep = + &uni_cli_insts[conn_index].snks[i]; + + if (client_ep->ep.id == id) + { + return client_ep->ep.stream; + } + } +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 */ + +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 + for (size_t i = 0U; i < ARRAY_SIZE(uni_cli_insts[conn_index].srcs); i++) + { + const struct bt_bap_unicast_client_ep *client_ep = + &uni_cli_insts[conn_index].srcs[i]; + + if (client_ep->ep.id == id) + { + return client_ep->ep.stream; + } + } +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 */ + + return NULL; +} + +static void reset_att_buf(struct unicast_client *client) +{ + net_buf_simple_init_with_data(&client->net_buf, &client->att_buf, + sizeof(client->att_buf)); + net_buf_simple_reset(&client->net_buf); +} + +#if defined(CONFIG_BT_AUDIO_RX) +static void unicast_client_ep_iso_recv(struct bt_iso_chan *chan, + const struct bt_iso_recv_info *info, + struct net_buf *buf) +{ + struct bt_bap_iso *iso = CONTAINER_OF(chan, struct bt_bap_iso, chan); + const struct bt_bap_stream_ops *ops; + struct bt_bap_stream *stream; + struct bt_bap_ep *ep = iso->rx.ep; + + if (ep == NULL) + { + /* In the case that the CIS has been setup as bidirectional, and + * only one of the directions have an ASE configured yet, we should + * only care about valid ISO packets when doing this check. The reason + * is that some controllers send HCI ISO data packets to the host, even + * if no SDU was sent on the remote side. This basically means that + * empty PDUs are sent to the host as HCI ISO data packets, which we + * should just ignore + */ + + if ((info->flags & BT_ISO_FLAGS_VALID) != 0) + { + LOG_DBG("Valid ISO packet of len %zu received for iso %p not " + "bound " + "with ep", + net_buf_frags_len(buf), chan); + } + + return; + } + + if (ep->state != BT_BAP_EP_STATE_STREAMING) + { + if (IS_ENABLED(CONFIG_BT_BAP_DEBUG_STREAM_DATA)) + { + LOG_DBG("ep %p is not in the streaming state: %s", ep, + bt_bap_ep_state_str(ep->state)); + } + + return; + } + + stream = ep->stream; + if (stream == NULL) + { + LOG_ERR("No stream for ep %p", ep); + return; + } + + ops = stream->ops; + + if (IS_ENABLED(CONFIG_BT_BAP_DEBUG_STREAM_DATA)) + { + LOG_DBG("stream %p ep %p len %zu", stream, ep, net_buf_frags_len(buf)); + } + + if (ops != NULL && ops->recv != NULL) + { + ops->recv(stream, info, buf); + } + else + { + LOG_WRN("No callback for recv set"); + } +} +#endif /* CONFIG_BT_AUDIO_RX */ + +#if defined(CONFIG_BT_AUDIO_TX) +static void unicast_client_ep_iso_sent(struct bt_iso_chan *chan) +{ + struct bt_bap_iso *iso = CONTAINER_OF(chan, struct bt_bap_iso, chan); + struct bt_bap_stream *stream; + struct bt_bap_ep *ep = iso->tx.ep; + + if (ep == NULL) + { + LOG_ERR("iso %p not bound with ep", chan); + return; + } + + stream = ep->stream; + if (stream == NULL) + { + LOG_ERR("No stream for ep %p", ep); + return; + } + + if (IS_ENABLED(CONFIG_BT_BAP_DEBUG_STREAM_DATA)) + { + LOG_DBG("stream %p ep %p", stream, ep); + } + + if (stream->ops != NULL && stream->ops->sent != NULL) + { + stream->ops->sent(stream); + } +} +#endif /* CONFIG_BT_AUDIO_TX */ + +static void unicast_client_ep_iso_connected(struct bt_bap_ep *ep) +{ + const struct bt_bap_stream_ops *stream_ops; + struct bt_bap_stream *stream = ep->stream; + + if (stream == NULL) + { + LOG_ERR("No stream for ep %p", ep); + return; + } + + if (stream->group == NULL) + { + LOG_ERR("No group for stream %p for ep %p", stream, ep); + return; + } + + ((struct bt_bap_unicast_group *)stream->group)->has_been_connected = true; + + if (ep->state != BT_BAP_EP_STATE_QOS_CONFIGURED && + ep->state != BT_BAP_EP_STATE_ENABLING) + { + LOG_DBG("endpoint in invalid state: %s", + bt_bap_ep_state_str(ep->state)); + return; + } + + LOG_DBG("stream %p ep %p dir %s receiver_ready %u", stream, ep, + bt_audio_dir_to_str(ep->dir), + ep->receiver_ready); + +#if defined(CONFIG_BT_BAP_DEBUG_STREAM_SEQ_NUM) + /* reset sequence number */ + + stream->_prev_seq_num = 0U; +#endif /* CONFIG_BT_BAP_DEBUG_STREAM_SEQ_NUM */ + + stream_ops = stream->ops; + if (stream_ops != NULL && stream_ops->connected != NULL) + { + stream_ops->connected(stream); + } +} + +static void unicast_client_iso_connected(struct bt_iso_chan *chan) +{ + struct bt_bap_iso *iso = CONTAINER_OF(chan, struct bt_bap_iso, chan); + + if (iso->rx.ep == NULL && iso->tx.ep == NULL) + { + LOG_ERR("iso %p not bound with ep", chan); + return; + } + + if (iso->rx.ep != NULL) + { + unicast_client_ep_iso_connected(iso->rx.ep); + } + + if (iso->tx.ep != NULL) + { + unicast_client_ep_iso_connected(iso->tx.ep); + } +} + +static void unicast_client_ep_iso_disconnected(struct bt_bap_ep *ep, + uint8_t reason) +{ + const struct bt_bap_stream_ops *stream_ops; + struct bt_bap_stream *stream; + + stream = ep->stream; + if (stream == NULL) + { + LOG_ERR("Stream not associated with an ep"); + return; + } + + LOG_DBG("stream %p ep %p reason 0x%02x", stream, ep, reason); + ep->reason = reason; + + stream_ops = stream->ops; + if (stream_ops != NULL && stream_ops->disconnected != NULL) + { + stream_ops->disconnected(stream, reason); + } + + /* If we were in the idle state when we started the ISO disconnection + * then we need to call unicast_client_ep_idle_state again when the ISO has + * finalized the disconnection + */ + + if (ep->state == BT_BAP_EP_STATE_IDLE) + { + (void)unicast_client_ep_idle_state(ep); + } +} + +static void unicast_client_iso_disconnected(struct bt_iso_chan *chan, + uint8_t reason) +{ + struct bt_bap_iso *iso = CONTAINER_OF(chan, struct bt_bap_iso, chan); + + if (iso->rx.ep == NULL && iso->tx.ep == NULL) + { + LOG_ERR("iso %p not bound with ep", chan); + return; + } + + if (iso->rx.ep != NULL) + { + unicast_client_ep_iso_disconnected(iso->rx.ep, reason); + } + + if (iso->tx.ep != NULL) + { + unicast_client_ep_iso_disconnected(iso->tx.ep, reason); + } +} + +static struct bt_iso_chan_ops unicast_client_iso_ops = +{ +#if defined(CONFIG_BT_AUDIO_RX) + .recv = unicast_client_ep_iso_recv, +#endif /* CONFIG_BT_AUDIO_RX */ +#if defined(CONFIG_BT_AUDIO_TX) + .sent = unicast_client_ep_iso_sent, +#endif /* CONFIG_BT_AUDIO_TX */ + .connected = unicast_client_iso_connected, + .disconnected = unicast_client_iso_disconnected, +}; + +bool bt_bap_unicast_client_has_ep(const struct bt_bap_ep *ep) +{ + for (size_t i = 0U; i < ARRAY_SIZE(uni_cli_insts); i++) + { +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 + if (PART_OF_ARRAY(uni_cli_insts[i].snks, ep)) + { + return true; + } +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 */ + +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 + if (PART_OF_ARRAY(uni_cli_insts[i].srcs, ep)) + { + return true; + } +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 */ + } + + return false; +} + +struct bt_conn *bt_bap_unicast_client_ep_get_conn(const struct bt_bap_ep *ep) +{ + for (size_t i = 0U; i < ARRAY_SIZE(uni_cli_insts); i++) + { +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 + if (PART_OF_ARRAY(uni_cli_insts[i].snks, ep)) + { + struct bt_bap_unicast_client_ep *client_ep = + CONTAINER_OF(ep, struct bt_bap_unicast_client_ep, ep); + + if (client_ep->handle == BAP_HANDLE_UNUSED) + { + return NULL; + } + + return bt_conn_lookup_index((uint8_t)i); + } +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 */ + +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 + if (PART_OF_ARRAY(uni_cli_insts[i].srcs, ep)) + { + struct bt_bap_unicast_client_ep *client_ep = + CONTAINER_OF(ep, struct bt_bap_unicast_client_ep, ep); + + if (client_ep->handle == BAP_HANDLE_UNUSED) + { + return NULL; + } + + return bt_conn_lookup_index((uint8_t)i); + } +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 */ + } + + return NULL; +} + +static void unicast_client_ep_init(struct bt_bap_ep *ep, + uint16_t handle, uint8_t dir) +{ + struct bt_bap_unicast_client_ep *client_ep; + + LOG_DBG("ep %p dir %s handle " + "0x%04x", ep, bt_audio_dir_to_str(dir), handle); + + client_ep = CONTAINER_OF(ep, struct bt_bap_unicast_client_ep, ep); + + (void)memset(ep, 0, sizeof(*ep)); + client_ep->handle = handle; + ep->id = 0U; + ep->dir = dir; + ep->reason = BT_HCI_ERR_SUCCESS; + k_work_init_delayable(&client_ep->ase_read_work, delayed_ase_read_handler); +} + +static struct bt_bap_ep *unicast_client_ep_find(struct bt_conn *conn, + uint16_t handle) +{ + uint8_t index; + + index = bt_conn_index(conn); + +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 + for (size_t i = 0U; i < ARRAY_SIZE(uni_cli_insts[index].snks); i++) + { + struct bt_bap_unicast_client_ep *client_ep = + &uni_cli_insts[index].snks[i]; + + if ((handle && client_ep->handle == handle) || + (!handle && client_ep->handle)) + { + return &client_ep->ep; + } + } +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 */ + +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 + for (size_t i = 0U; i < ARRAY_SIZE(uni_cli_insts[index].srcs); i++) + { + struct bt_bap_unicast_client_ep *client_ep = + &uni_cli_insts[index].srcs[i]; + + if ((handle && client_ep->handle == handle) || + (!handle && client_ep->handle)) + { + return &client_ep->ep; + } + } +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 */ + + return NULL; +} + +struct bt_bap_iso *bt_bap_unicast_client_new_audio_iso(void) +{ + struct bt_bap_iso *bap_iso; + + bap_iso = bt_bap_iso_new(); + if (bap_iso == NULL) + { + return NULL; + } + + bt_bap_iso_init(bap_iso, &unicast_client_iso_ops); + + LOG_DBG("New bap_iso %p", bap_iso); + + return bap_iso; +} + +static struct bt_bap_ep *unicast_client_ep_new( + struct bt_conn *conn, enum bt_audio_dir dir, uint16_t handle) +{ + size_t i; + size_t size; + uint8_t index; + struct bt_bap_unicast_client_ep *cache; + + index = bt_conn_index(conn); + + switch (dir) + { +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 + case BT_AUDIO_DIR_SINK: + cache = uni_cli_insts[index].snks; + size = ARRAY_SIZE(uni_cli_insts[index].snks); + break; +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 */ +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 + case BT_AUDIO_DIR_SOURCE: + cache = uni_cli_insts[index].srcs; + size = ARRAY_SIZE(uni_cli_insts[index].srcs); + break; +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 */ + default: + return NULL; + } + + for (i = 0U; i < size; i++) + { + struct bt_bap_unicast_client_ep *client_ep = &cache[i]; + + if (!client_ep->handle) + { + unicast_client_ep_init(&client_ep->ep, handle, dir); + return &client_ep->ep; + } + } + + return NULL; +} + +static struct bt_bap_ep *unicast_client_ep_get( + struct bt_conn *conn, enum bt_audio_dir dir, uint16_t handle) +{ + struct bt_bap_ep *ep; + + ep = unicast_client_ep_find(conn, handle); + if (ep || !handle) + { + return ep; + } + + return unicast_client_ep_new(conn, dir, handle); +} + +static void unicast_client_ep_set_local_idle_state(struct bt_bap_ep *ep) +{ + struct bt_conn *conn = ep->stream != NULL ? ep->stream->conn : NULL; + struct bt_ascs_ase_status status = + { + .id = ep->id, + .state = BT_BAP_EP_STATE_IDLE, + }; + + struct net_buf_simple buf; + + net_buf_simple_init_with_data(&buf, &status, sizeof(status)); + + unicast_client_ep_set_status(conn, ep, &buf, false); +} + +static void unicast_client_notify_location( + struct bt_conn *conn, enum bt_audio_dir dir, enum bt_audio_location loc) +{ + struct bt_bap_unicast_client_cb *listener; + struct bt_bap_unicast_client_cb *next; + + list_for_every_container_safe(&unicast_client_cbs, listener, next, _node) + { + if (listener->location != NULL) + { + listener->location(conn, dir, loc); + } + } +} + +static void unicast_client_notify_available_contexts(struct bt_conn *conn, + enum bt_audio_context snk_ctx, + enum bt_audio_context src_ctx) +{ + struct bt_bap_unicast_client_cb *listener; + struct bt_bap_unicast_client_cb *next; + + list_for_every_container_safe(&unicast_client_cbs, listener, next, _node) + { + if (listener->available_contexts != NULL) + { + listener->available_contexts(conn, snk_ctx, src_ctx); + } + } +} + +static void unicast_client_notify_pac_record(struct bt_conn *conn, + const struct bt_audio_codec_cap *codec_cap) +{ + const struct unicast_client *client = &uni_cli_insts[bt_conn_index(conn)]; + struct bt_bap_unicast_client_cb *listener; + struct bt_bap_unicast_client_cb *next; + const enum bt_audio_dir dir = client->dir; + + /* TBD: Since the PAC records are optionally notifiable we may want to + * supply the index and total count of records in the callback, so that it + * easier for the upper layers to determine when a new set of PAC records + * is being reported. + */ + + list_for_every_container_safe(&unicast_client_cbs, listener, next, _node) + { + if (listener->pac_record != NULL) + { + listener->pac_record(conn, dir, codec_cap); + } + } +} + +static void unicast_client_notify_endpoint(struct bt_conn *conn, + struct bt_bap_ep *ep) +{ + const struct unicast_client *client = &uni_cli_insts[bt_conn_index(conn)]; + struct bt_bap_unicast_client_cb *listener; + struct bt_bap_unicast_client_cb *next; + const enum bt_audio_dir dir = client->dir; + + list_for_every_container_safe(&unicast_client_cbs, listener, next, _node) + { + if (listener->endpoint != NULL) + { + listener->endpoint(conn, dir, ep); + } + } +} + +static void unicast_client_discover_complete(struct bt_conn *conn, int err) +{ + struct unicast_client *client = &uni_cli_insts[bt_conn_index(conn)]; + struct bt_bap_unicast_client_cb *listener; + struct bt_bap_unicast_client_cb *next; + const enum bt_audio_dir dir = client->dir; + + /* Discover complete - Reset discovery values */ + + client->dir = 0U; + reset_att_buf(client); + atomic_clear_bit(client->flags, UNICAST_CLIENT_FLAG_DISCOVERY_IN_PROGRESS); + atomic_clear_bit(client->flags, UNICAST_CLIENT_FLAG_BUSY); + + list_for_every_container_safe(&unicast_client_cbs, listener, next, _node) + { + if (listener->discover != NULL) + { + listener->discover(conn, err, dir); + } + } +} + +static void unicast_client_notify_ep_config(struct bt_bap_stream *stream, + enum bt_bap_ascs_rsp_code rsp_code, + enum bt_bap_ascs_reason reason) +{ + struct bt_bap_unicast_client_cb *listener; + struct bt_bap_unicast_client_cb *next; + + list_for_every_container_safe(&unicast_client_cbs, listener, next, _node) + { + if (listener->config != NULL) + { + listener->config(stream, rsp_code, reason); + } + } +} + +static void unicast_client_notify_ep_qos(struct bt_bap_stream *stream, + enum bt_bap_ascs_rsp_code rsp_code, + enum bt_bap_ascs_reason reason) +{ + struct bt_bap_unicast_client_cb *listener; + struct bt_bap_unicast_client_cb *next; + + list_for_every_container_safe(&unicast_client_cbs, listener, next, _node) + { + if (listener->qos != NULL) + { + listener->qos(stream, rsp_code, reason); + } + } +} + +static void unicast_client_notify_ep_enable(struct bt_bap_stream *stream, + enum bt_bap_ascs_rsp_code rsp_code, + enum bt_bap_ascs_reason reason) +{ + struct bt_bap_unicast_client_cb *listener; + struct bt_bap_unicast_client_cb *next; + + list_for_every_container_safe(&unicast_client_cbs, listener, next, _node) + { + if (listener->enable != NULL) + { + listener->enable(stream, rsp_code, reason); + } + } +} + +static void unicast_client_notify_ep_start(struct bt_bap_stream *stream, + enum bt_bap_ascs_rsp_code + rsp_code, + enum bt_bap_ascs_reason reason) +{ + struct bt_bap_unicast_client_cb *listener; + struct bt_bap_unicast_client_cb *next; + + list_for_every_container_safe(&unicast_client_cbs, listener, next, _node) + { + if (listener->start != NULL) + { + listener->start(stream, rsp_code, reason); + } + } +} + +static void unicast_client_notify_ep_stop(struct bt_bap_stream *stream, + enum bt_bap_ascs_rsp_code rsp_code, + enum bt_bap_ascs_reason reason) +{ + struct bt_bap_unicast_client_cb *listener; + struct bt_bap_unicast_client_cb *next; + + list_for_every_container_safe(&unicast_client_cbs, listener, next, _node) + { + if (listener->stop != NULL) + { + listener->stop(stream, rsp_code, reason); + } + } +} + +static void unicast_client_notify_ep_disable(struct bt_bap_stream *stream, + enum bt_bap_ascs_rsp_code rsp_code, + enum bt_bap_ascs_reason reason) +{ + struct bt_bap_unicast_client_cb *listener; + struct bt_bap_unicast_client_cb *next; + + list_for_every_container_safe(&unicast_client_cbs, listener, next, _node) + { + if (listener->disable != NULL) + { + listener->disable(stream, rsp_code, reason); + } + } +} + +static void unicast_client_notify_ep_metadata(struct bt_bap_stream *stream, + enum bt_bap_ascs_rsp_code rsp_code, + enum bt_bap_ascs_reason reason) +{ + struct bt_bap_unicast_client_cb *listener; + struct bt_bap_unicast_client_cb *next; + + list_for_every_container_safe(&unicast_client_cbs, listener, next, _node) + { + if (listener->metadata != NULL) + { + listener->metadata(stream, rsp_code, reason); + } + } +} + +static void unicast_client_notify_ep_released(struct bt_bap_stream *stream, + enum bt_bap_ascs_rsp_code rsp_code, + enum bt_bap_ascs_reason reason) +{ + struct bt_bap_unicast_client_cb *listener; + struct bt_bap_unicast_client_cb *next; + + list_for_every_container_safe(&unicast_client_cbs, listener, next, _node) + { + if (listener->release != NULL) + { + listener->release(stream, rsp_code, reason); + } + } +} + +static bool unicast_client_ep_idle_state(struct bt_bap_ep *ep) +{ + struct bt_bap_unicast_client_ep *client_ep = + CONTAINER_OF(ep, struct bt_bap_unicast_client_ep, ep); + struct bt_bap_stream *stream = ep->stream; + + ep->receiver_ready = false; + + if (stream == NULL) + { + return false; + } + + /* If CIS is connected, disconnect and wait for CIS disconnection */ + + if (bt_bap_stream_can_disconnect(stream)) + { + int err; + + LOG_DBG("Disconnecting stream"); + err = bt_bap_stream_disconnect(stream); + + if (err != 0) + { + LOG_ERR("Failed to disconnect stream: %d", err); + } + + return false; + } + else if (ep->iso != NULL && + ep->iso->chan.state == BT_ISO_STATE_DISCONNECTING) + { + /* Wait for disconnection */ + + return false; + } + + bt_bap_stream_reset(stream); + + /* Notify upper layer */ + + if (client_ep->release_requested) + { + client_ep->release_requested = false; + + if (client_ep->cp_ntf_pending) + { + /* In case that we get the idle state notification before the CP + * notification we trigger the CP callback now, as after this we + * won't be able to find the stream by the ASE ID + */ + + client_ep->cp_ntf_pending = false; + + unicast_client_notify_ep_released(stream, + BT_BAP_ASCS_RSP_CODE_SUCCESS, + BT_BAP_ASCS_REASON_NONE); + } + } + + return true; +} + +static void unicast_client_ep_qos_update(struct bt_bap_ep *ep, + const struct bt_ascs_ase_status_qos *qos) +{ + struct bt_iso_chan_io_qos *iso_io_qos; + + LOG_DBG("ep %p dir %s bap_iso %p", ep, bt_audio_dir_to_str(ep->dir), + ep->iso); + + if (ep->dir == BT_AUDIO_DIR_SOURCE) + { + /* If the endpoint is a source, then we need to + * reset our RX parameters + */ + + iso_io_qos = &ep->iso->rx.qos; + } + else if (ep->dir == BT_AUDIO_DIR_SINK) + { + /* If the endpoint is a sink, then we need to + * reset our TX parameters + */ + + iso_io_qos = &ep->iso->tx.qos; + } + else + { + DEBUGASSERT(false); + return; + } + + iso_io_qos->phy = qos->phy; + iso_io_qos->sdu = sys_le16_to_cpu(qos->sdu); + iso_io_qos->rtn = qos->rtn; +} + +/** + * @brief Clear data related to long reads + * + * Once we have pulled all the data from the buffer, we can clear the busy + * flag and reset the buffer. This function should be called after all data + * from the buffer has been properly applied, but before the application + * callbacks are called, so that an application can perform a new operation + * in the callback + */ + +static void clear_and_reset_if_long_read(const struct bt_conn *conn, + const struct net_buf_simple *buf) +{ + struct unicast_client *client; + + if (conn == NULL) + { + /* Local operation, no buffers used */ + + return; + } + + client = &uni_cli_insts[bt_conn_index(conn)]; + + if (buf == &client->net_buf) + { + reset_att_buf(client); + + /* Clear busy flag if not during discovery. Discovery will need to keep + * the busy flag for the remaining part of the discovery procedure + */ + + if (!atomic_test_bit(client->flags, + UNICAST_CLIENT_FLAG_DISCOVERY_IN_PROGRESS)) + { + DEBUGASSERT(atomic_test_bit(client->flags, + UNICAST_CLIENT_FLAG_BUSY)); + atomic_clear_bit(client->flags, UNICAST_CLIENT_FLAG_BUSY); + } + } + +/* else buf may just be a stack allocated net_buf_simple which isn't + * used for long reads + */ +} + +static bool unicast_client_ep_config_state(struct bt_bap_ep *ep, + struct net_buf_simple *buf) +{ + struct bt_bap_unicast_client_ep *client_ep = + CONTAINER_OF(ep, struct bt_bap_unicast_client_ep, ep); + struct bt_ascs_ase_status_config *cfg; + struct bt_bap_qos_cfg_pref *pref; + struct bt_bap_stream *stream; + void *cc; + + ep->receiver_ready = false; + + if (client_ep->release_requested) + { + LOG_DBG("Released was requested, change local state to idle"); + ep->reason = BT_HCI_ERR_LOCALHOST_TERM_CONN; + unicast_client_ep_set_local_idle_state(ep); + return false; + } + + if (buf->len < sizeof(*cfg)) + { + LOG_ERR("Config status too short"); + return false; + } + + stream = ep->stream; + if (stream == NULL) + { + LOG_WRN("No stream active for endpoint"); + return false; + } + + DEBUGASSERT(stream->codec_cfg == &ep->codec_cfg); + + cfg = net_buf_simple_pull_mem(buf, sizeof(*cfg)); + + if (buf->len < cfg->cc_len) + { + LOG_ERR("Malformed ASE Config status: buf->len %u < %u cc_len", + buf->len, + cfg->cc_len); + return false; + } + + cc = net_buf_simple_pull_mem(buf, cfg->cc_len); + + pref = &stream->ep->qos_pref; + + /* Convert to interval representation so they can be matched by QoS */ + + pref->unframed_supported = cfg->framing == BT_ASCS_QOS_FRAMING_UNFRAMED; + pref->phy = cfg->phy; + pref->rtn = cfg->rtn; + pref->latency = sys_le16_to_cpu(cfg->latency); + pref->pd_min = sys_get_le24(cfg->pd_min); + pref->pd_max = sys_get_le24(cfg->pd_max); + pref->pref_pd_min = sys_get_le24(cfg->prefer_pd_min); + pref->pref_pd_max = sys_get_le24(cfg->prefer_pd_max); + + LOG_DBG("dir %s unframed_supported 0x%02x phy 0x%02x rtn %u " + "latency %u pd_min %u pd_max %u pref_pd_min %u pref_pd_max %u " + "codec 0x%02x ", + bt_audio_dir_to_str(ep->dir), pref->unframed_supported, pref->phy, + pref->rtn, + pref->latency, pref->pd_min, pref->pd_max, pref->pref_pd_min, + pref->pref_pd_max, + cfg->codec.id); + + if (!bt_bap_valid_qos_pref(pref)) + { + LOG_DBG("Invalid QoS preferences"); + memset(pref, 0, sizeof(*pref)); + + /* If the sever provide an invalid QoS preferences we treat it as an + * error and do nothing + */ + + return false; + } + + unicast_client_ep_set_codec_cfg(ep, cfg->codec.id, + sys_le16_to_cpu(cfg->codec.cid), + sys_le16_to_cpu(cfg->codec.vid), cc, + cfg->cc_len); + + return true; +} + +static bool unicast_client_ep_qos_state(struct bt_bap_ep *ep, + struct net_buf_simple *buf) +{ + const enum bt_audio_dir dir = ep->dir; + struct bt_ascs_ase_status_qos *qos; + struct bt_bap_unicast_group *group; + struct bt_bap_stream *stream; + + ep->receiver_ready = false; + + if (buf->len < sizeof(*qos)) + { + LOG_DBG("QoS status too short"); + return false; + } + + stream = ep->stream; + if (stream == NULL) + { + LOG_DBG("No stream active for endpoint"); + return false; + } + + if (stream->group == NULL) + { + LOG_DBG("Stream %p is not configured to a group", stream); + return false; + } + + qos = net_buf_simple_pull_mem(buf, sizeof(*qos)); + + /* Update existing QoS configuration */ + + unicast_client_ep_qos_update(ep, qos); + + ep->cig_id = qos->cig_id; + ep->cis_id = qos->cis_id; + ep->qos.interval = sys_get_le24(qos->interval); + ep->qos.framing = qos->framing; + ep->qos.phy = qos->phy; + ep->qos.sdu = sys_le16_to_cpu(qos->sdu); + ep->qos.rtn = qos->rtn; + ep->qos.latency = sys_le16_to_cpu(qos->latency); + ep->qos.pd = sys_get_le24(qos->pd); + + LOG_DBG("dir %s cig 0x%02x cis 0x%02x codec 0x%02x interval %u " + "framing 0x%02x phy 0x%02x rtn %u latency %u pd %u", + bt_audio_dir_to_str(dir), ep->cig_id, ep->cis_id, + stream->codec_cfg->id, ep->qos.interval, + ep->qos.framing, ep->qos.phy, ep->qos.rtn, ep->qos.latency, + ep->qos.pd); + + DEBUGASSERT(stream->group != NULL); + group = (struct bt_bap_unicast_group *)stream->group; + if (dir == BT_AUDIO_DIR_SINK) + { + if (group->sink_pd == BT_BAP_PD_UNSET) + { + LOG_WRN("Group %p sink_pd is unset group", group); + } + else + { + if (group->sink_pd != ep->qos.pd) + { + LOG_WRN("Sink stream %p PD %u does not match the sink PD %u " + "of " + "the " + "group %p", + stream, ep->qos.pd, group->sink_pd, group); + } + } + } + else if (dir == BT_AUDIO_DIR_SOURCE) + { + if (group->source_pd == BT_BAP_PD_UNSET) + { + LOG_WRN("Group %p source_pd is unset group", group); + } + else + { + if (group->source_pd != ep->qos.pd) + { + LOG_WRN("Source stream %p PD %u does not match the source PD " + "%u of the group %p", + stream, ep->qos.pd, group->source_pd, group); + } + } + } + else + { + DEBUGASSERT(false); + } + + /* Disconnect ISO if connected */ + + if (bt_bap_stream_can_disconnect(stream)) + { + const int err = bt_bap_stream_disconnect(stream); + + if (err != 0) + { + LOG_ERR("Failed to disconnect stream: %d", err); + } + } + + stream->qos = &ep->qos; + + return true; +} + +static bool unicast_client_ep_enabling_state(struct bt_bap_ep *ep, + struct net_buf_simple *buf) +{ + struct bt_ascs_ase_status_enable *enable; + struct bt_bap_stream *stream; + void *metadata; + + if (buf->len < sizeof(*enable)) + { + LOG_ERR("Enabling status too short"); + return false; + } + + stream = ep->stream; + if (stream == NULL) + { + LOG_ERR("No stream active for endpoint"); + return false; + } + + enable = net_buf_simple_pull_mem(buf, sizeof(*enable)); + + if (buf->len < enable->metadata_len) + { + LOG_ERR("Malformed PDU: remaining len %u expected %u", buf->len, + enable->metadata_len); + return false; + } + + metadata = net_buf_simple_pull_mem(buf, enable->metadata_len); + + LOG_DBG("dir %s cig 0x%02x cis 0x%02x", bt_audio_dir_to_str(ep->dir), + ep->cig_id, ep->cis_id); + + unicast_client_ep_set_metadata(ep, metadata, enable->metadata_len); + + return true; +} + +static bool unicast_client_ep_streaming_state( + struct bt_bap_ep *ep, struct net_buf_simple *buf, bool state_changed) +{ + struct bt_ascs_ase_status_stream *stream_status; + struct bt_bap_stream *stream; + void *metadata; + + if (buf->len < sizeof(struct bt_ascs_ase_status_stream)) + { + LOG_ERR("Streaming status too short"); + return false; + } + + stream = ep->stream; + if (stream == NULL) + { + LOG_ERR("No stream active for endpoint"); + return false; + } + + stream_status = net_buf_simple_pull_mem(buf, sizeof(*stream_status)); + + if (buf->len < stream_status->metadata_len) + { + LOG_ERR("Malformed PDU: remaining len %u expected %u", buf->len, + stream_status->metadata_len); + return false; + } + + metadata = net_buf_simple_pull_mem(buf, stream_status->metadata_len); + + LOG_DBG("dir %s cig 0x%02x cis 0x%02x", bt_audio_dir_to_str(ep->dir), + ep->cig_id, ep->cis_id); + + unicast_client_ep_set_metadata(ep, metadata, stream_status->metadata_len); + + /* Notify upper layer + * + * If the state did not change then only the metadata was changed + */ + + if (state_changed) + { + /* Setup the ISO data path when the stream is started. We could do it + * earlier when the CIS is connected, but then we would just receive + * audio data that we would then just discard + */ + + bt_bap_setup_iso_data_path(stream); + } + + return true; +} + +static bool unicast_client_ep_disabling_state(struct bt_bap_ep *ep, + struct net_buf_simple *buf) +{ + struct bt_bap_stream *stream; + + ep->receiver_ready = false; + + if (buf->len < sizeof(struct bt_ascs_ase_status_disable)) + { + LOG_ERR("Disabling status too short"); + return false; + } + + stream = ep->stream; + if (stream == NULL) + { + LOG_ERR("No stream active for endpoint"); + return false; + } + + LOG_DBG("dir %s cig 0x%02x cis 0x%02x", bt_audio_dir_to_str(ep->dir), + ep->cig_id, ep->cis_id); + + return true; +} + +static bool unicast_client_ep_releasing_state(struct bt_bap_ep *ep, + struct net_buf_simple *buf) +{ + struct bt_bap_stream *stream; + + ARG_UNUSED(buf); + + ep->receiver_ready = false; + + stream = ep->stream; + if (stream == NULL) + { + LOG_ERR("No stream active for endpoint"); + return false; + } + + LOG_DBG("dir %s", bt_audio_dir_to_str(ep->dir)); + + if (bt_bap_stream_can_disconnect(stream)) + { + /* The Unicast Client shall terminate any CIS established for + * that ASE by following the Connected Isochronous Stream Terminate + * procedure defined in Volume 3, Part C, Section 9.3.15 in when the + * Unicast Client has determined that the ASE is in the Releasing + * state. + */ + + const int err = bt_bap_stream_disconnect(stream); + + if (err != 0) + { + LOG_ERR("Failed to disconnect stream: %d", err); + } + } + + return true; +} + +static void unicast_client_ep_notify_app( + struct bt_bap_stream *stream, bool state_changed, + enum bt_bap_ep_state new_state, enum bt_bap_ep_state old_state, + enum bt_audio_dir dir, uint8_t reason) +{ + const struct bt_bap_stream_ops *ops; + + /* Nothing more to do if there are no stream and/or no callbacks */ + + if (stream == NULL) + { + return; + } + + ops = stream->ops; + if (ops == NULL) + { + return; + } + + switch (new_state) + { + case BT_BAP_EP_STATE_IDLE: + if (ops->released != NULL) + { + ops->released(stream); + } + else + { + LOG_WRN("No callback for released set"); + } + break; + case BT_BAP_EP_STATE_CODEC_CONFIGURED: + + /* Notify upper layer */ + + if (ops->codec_configured != NULL) + { + ops->codec_configured(stream, &stream->ep->qos_pref); + } + else + { + LOG_WRN("No callback for codec_configured set"); + } + break; + case BT_BAP_EP_STATE_QOS_CONFIGURED: + if (dir == BT_AUDIO_DIR_SINK) + { + /* If the old state was enabling or streaming, then the sink + * ASE has been disabled. Since the sink ASE does not have a + * disabling state, we can check if by comparing the old_state + */ + + const bool disabled = + old_state == BT_BAP_EP_STATE_ENABLING || + old_state == BT_BAP_EP_STATE_STREAMING; + + if (disabled) + { + if (ops->disabled != NULL) + { + ops->disabled(stream); + } + + if (ops->stopped != NULL) + { + ops->stopped(stream, reason); + } + } + } + else if (dir == BT_AUDIO_DIR_SOURCE) + { + if (old_state == BT_BAP_EP_STATE_DISABLING && + ops->stopped != NULL) + { + /* We left the disabling state, let the upper layers know + * that + * the stream is stopped + */ + + ops->stopped(stream, reason); + } + } + else + { + DEBUGASSERT(false); + } + + if (ops->qos_configured != NULL) + { + ops->qos_configured(stream); + } + else + { + LOG_WRN("No callback for qos_configured set"); + } + break; + case BT_BAP_EP_STATE_ENABLING: + + /* If the state did not change then only the metadata was changed */ + + if (state_changed) + { + if (ops->enabled != NULL) + { + ops->enabled(stream); + } + else + { + LOG_WRN("No callback for enabled set"); + } + } + else + { + if (ops->metadata_updated != NULL) + { + ops->metadata_updated(stream); + } + else + { + LOG_WRN("No callback for metadata_updated set"); + } + } + break; + case BT_BAP_EP_STATE_STREAMING: + + /* If the state did not change then only the metadata was changed */ + + if (state_changed) + { + if (ops->started != NULL) + { + ops->started(stream); + } + else + { + LOG_WRN("No callback for started set"); + } + } + else + { + if (ops->metadata_updated != NULL) + { + ops->metadata_updated(stream); + } + else + { + LOG_WRN("No callback for metadata_updated set"); + } + } + break; + case BT_BAP_EP_STATE_DISABLING: + if (ops->disabled != NULL) + { + ops->disabled(stream); + } + else + { + LOG_WRN("No callback for disabled set"); + } + break; + case BT_BAP_EP_STATE_RELEASING: + /* Call the `disable` `stopped` callback if we leave the + * BT_BAP_EP_STATE_STREAMING state for any reason + */ + + if (state_changed && old_state == BT_BAP_EP_STATE_STREAMING) + { + if (ops->disabled != NULL) + { + ops->disabled(stream); + } + + if (ops->stopped != NULL) + { + ops->stopped(stream, reason); + } + } + break; + default: + LOG_WRN("Unexpected new_state: %d", new_state); + break; + } +} + +static void unicast_client_ep_set_status( + const struct bt_conn *conn, struct bt_bap_ep *ep, + struct net_buf_simple *buf, bool is_notification) +{ + struct bt_bap_unicast_client_ep *client_ep; + struct bt_ascs_ase_status *status; + enum bt_bap_ep_state new_state; + enum bt_bap_ep_state old_state; + struct bt_bap_stream *stream; + bool trigger_callback; + bool state_changed; + uint8_t reason; + + DEBUGASSERT(ep != NULL); + DEBUGASSERT(buf != NULL); + + client_ep = CONTAINER_OF(ep, struct bt_bap_unicast_client_ep, ep); + + status = net_buf_simple_pull_mem(buf, sizeof(*status)); + new_state = status->state; + + /* Only check state changes for notifications. In the case of local changes + * or when we are reading the state during discovery, we do not want to + * compare states as that would cause a invalid state change from e.g. IDLE + * to IDLE + */ + + if (is_notification && !bt_bap_stream_valid_state_transition(ep, + new_state)) + { + /* TODO: We should notify the application when this happens + * https://github.com/zephyrproject-rtos/zephyr/issues/94353 + */ + + clear_and_reset_if_long_read(conn, buf); + return; + } + + /* ep and stream may be detached as part of bt_bap_stream_reset during the + * state machine below, so to be able to provide the stream pointer from + * the ep pointer, we need to store that before processing the state + * machine so that we can provide the stream pointer to the callbacks + */ + + stream = ep->stream; + reason = ep->reason; + old_state = ep->state; + ep->id = status->id; + ep->state = new_state; + state_changed = old_state != new_state; + + if (state_changed) + { + if (old_state == BT_BAP_EP_STATE_STREAMING) + { + /* We left the streaming state, let the upper layers know that the + * stream is stopped + */ + + if (reason == BT_HCI_ERR_SUCCESS) + { + /* Default to BT_HCI_ERR_UNSPECIFIED if no other reason is set + */ + + reason = BT_HCI_ERR_UNSPECIFIED; + } + else + { + /* Reset reason */ + + ep->reason = BT_HCI_ERR_SUCCESS; + } + + if (stream != NULL) + { + if (ep->iso != NULL) + { + /* Remove the ISO data path as we no longer want to process + * any ISO data for this stream. + */ + + bt_bap_remove_iso_data_path(stream); + } + + /* Special case: If we are going from streaming to idle, that + * indicates a disconnect. Call `stopped` while `stream` still + * have a reference to the ACL + */ + + if (ep->state == BT_BAP_EP_STATE_IDLE) + { + if (stream->ops != NULL && stream->ops->stopped != NULL) + { + stream->ops->stopped(stream, reason); + } + else + { + LOG_WRN("No callback for stopped set"); + } + } + } + } + else if (ep->dir == BT_AUDIO_DIR_SOURCE && + old_state == BT_BAP_EP_STATE_DISABLING) + { + /* We left the disabling state */ + + if (reason == BT_HCI_ERR_SUCCESS) + { + /* Default to BT_HCI_ERR_UNSPECIFIED if no other reason is set + */ + + reason = BT_HCI_ERR_UNSPECIFIED; + } + else + { + /* Reset reason */ + + ep->reason = BT_HCI_ERR_SUCCESS; + } + } + } + + LOG_DBG("ep %p handle 0x%04x id 0x%02x dir %s state %s -> %s", ep, + client_ep->handle, ep->id, + bt_audio_dir_to_str(ep->dir), bt_bap_ep_state_str(old_state), + bt_bap_ep_state_str(ep->state)); + + trigger_callback = false; + switch (new_state) + { + case BT_BAP_EP_STATE_IDLE: + trigger_callback = unicast_client_ep_idle_state(ep); + break; + case BT_BAP_EP_STATE_CODEC_CONFIGURED: + trigger_callback = unicast_client_ep_config_state(ep, buf); + break; + case BT_BAP_EP_STATE_QOS_CONFIGURED: + trigger_callback = unicast_client_ep_qos_state(ep, buf); + break; + case BT_BAP_EP_STATE_ENABLING: + trigger_callback = unicast_client_ep_enabling_state(ep, buf); + break; + case BT_BAP_EP_STATE_STREAMING: + trigger_callback = unicast_client_ep_streaming_state(ep, buf, + state_changed); + break; + case BT_BAP_EP_STATE_DISABLING: + trigger_callback = unicast_client_ep_disabling_state(ep, buf); + break; + case BT_BAP_EP_STATE_RELEASING: + trigger_callback = unicast_client_ep_releasing_state(ep, buf); + break; + default: + + /* We verify the state change above, so this should never happen */ + + DEBUGASSERT(false); + } + + clear_and_reset_if_long_read(conn, buf); + + if (trigger_callback && stream != NULL) + { + unicast_client_ep_notify_app(stream, state_changed, ep->state, + old_state, + ep->dir, reason); + } +} + +static bool valid_ltv_cb(struct bt_data *data, void *user_data) +{ + ARG_UNUSED(data); + ARG_UNUSED(user_data); + + /* just return true to continue parsing as bt_data_parse will validate for + * us + */ + + return true; +} + +static int unicast_client_ep_set_codec_cfg( + struct bt_bap_ep *ep, uint8_t id, uint16_t cid, uint16_t vid, void *data, + uint8_t len) +{ + if (ep == NULL) + { + return -EINVAL; + } + + LOG_DBG("ep %p codec id 0x%02x cid 0x%04x vid 0x%04x len %u", ep, id, cid, + vid, len); + + if (len > sizeof(ep->codec_cfg.data)) + { + LOG_DBG("Cannot store %u octets of codec data", len); + + return -ENOMEM; + } + + ep->codec_cfg.data_len = len; + (void)memcpy(ep->codec_cfg.data, data, len); + + ep->codec_cfg.id = id; + ep->codec_cfg.cid = cid; + ep->codec_cfg.vid = vid; + + return 0; +} + +static int unicast_client_set_codec_cap( + uint8_t id, uint16_t cid, uint16_t vid, void *data, uint8_t data_len, + void *meta, uint8_t meta_len, struct bt_audio_codec_cap *codec_cap) +{ + struct net_buf_simple buf; + + if (!codec_cap) + { + return -EINVAL; + } + + LOG_DBG("codec id 0x%02x cid 0x%04x vid 0x%04x data_len %u meta_len " + "%u", id, + cid, vid, data_len, + meta_len); + + /* Reset current data */ + + (void)memset(codec_cap, 0, sizeof(*codec_cap)); + + codec_cap->id = id; + codec_cap->cid = cid; + codec_cap->vid = vid; + + if (data_len > 0U) + { + if (data_len > sizeof(codec_cap->data)) + { + return -ENOMEM; + } + + net_buf_simple_init_with_data(&buf, data, data_len); + + /* If codec is LC3, then it shall be LTV encoded - We verify this + * before storing the data For any non-LC3 codecs, we cannot verify + * anything + */ + + if (id == BT_HCI_CODING_FORMAT_LC3) + { + bt_data_parse(&buf, valid_ltv_cb, NULL); + + /* Check if all entries could be parsed */ + + if (buf.len) + { + LOG_ERR("Unable to parse Codec capabilities: len %u", buf.len); + return -EINVAL; + } + } + + memcpy(codec_cap->data, data, data_len); + codec_cap->data_len = data_len; + } + + if (meta_len > 0U) + { + if (meta_len > sizeof(codec_cap->meta)) + { + return -ENOMEM; + } + + net_buf_simple_init_with_data(&buf, meta, meta_len); + + bt_data_parse(&buf, valid_ltv_cb, NULL); + + /* Check if all entries could be parsed */ + + if (buf.len) + { + LOG_ERR("Unable to parse Codec metadata: len %u", buf.len); + return -EINVAL; + } + + memcpy(codec_cap->meta, meta, meta_len); + codec_cap->meta_len = meta_len; + } + + return 0; +} + +static int unicast_client_ep_set_metadata(struct bt_bap_ep *ep, + void *data, uint8_t len) +{ + if (ep == NULL) + { + return -EINVAL; + } + + LOG_DBG("ep %p len %u", ep, len); + + if (CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE > 0) + { + if (len > sizeof(ep->codec_cfg.meta)) + { + LOG_DBG("Cannot store %u octets of metadata", len); + + return -ENOMEM; + } + + ep->codec_cfg.meta_len = len; + (void)memcpy(ep->codec_cfg.meta, data, len); + } + + return 0; +} + +static uint8_t unicast_client_cp_notify(struct bt_conn *conn, + struct bt_gatt_subscribe_params *params, + const void *data, + uint16_t length) +{ + struct bt_ascs_cp_rsp *rsp; + struct net_buf_simple buf; + + LOG_DBG("conn %p len %u", conn, length); + + if (!data) + { + LOG_DBG("Unsubscribed"); + params->value_handle = BAP_HANDLE_UNUSED; + return BT_GATT_ITER_STOP; + } + + net_buf_simple_init_with_data(&buf, (void *)data, length); + + if (buf.len < sizeof(*rsp)) + { + LOG_ERR("Control Point Notification too small"); + return BT_GATT_ITER_STOP; + } + + rsp = net_buf_simple_pull_mem(&buf, sizeof(*rsp)); + + if (rsp->num_ase == BT_ASCS_UNSUPP_OR_LENGTH_ERR_NUM_ASE) + { + /* This is a special case where num_ase == + * BT_ASCS_UNSUPP_OR_LENGTH_ERR_NUM_ASE but really there is only one + * ASE response + */ + + rsp->num_ase = 1U; + } + + for (uint8_t i = 0U; i < rsp->num_ase; i++) + { + struct bt_bap_unicast_client_ep *client_ep = NULL; + struct bt_ascs_cp_ase_rsp *ase_rsp; + struct bt_bap_stream *stream; + + if (buf.len < sizeof(*ase_rsp)) + { + LOG_ERR("Control Point Notification too small: %u", buf.len); + return BT_GATT_ITER_STOP; + } + + ase_rsp = net_buf_simple_pull_mem(&buf, sizeof(*ase_rsp)); + + LOG_DBG("op %s (0x%02x) id 0x%02x code %s (0x%02x) " + "reason %s (0x%02x)", + bt_ascs_op_str(rsp->op), rsp->op, ase_rsp->ase_id, + bt_ascs_rsp_str(ase_rsp->code), + ase_rsp->code, bt_ascs_reason_str( + ase_rsp->reason), + ase_rsp->reason); + stream = audio_stream_by_ep_id(conn, ase_rsp->ase_id); + if (stream == NULL) + { + LOG_DBG("Could not find stream by id %u", ase_rsp->ase_id); + + continue; + } + else + { + client_ep = CONTAINER_OF(stream->ep, + struct bt_bap_unicast_client_ep, + ep); + client_ep->cp_ntf_pending = false; + } + + switch (rsp->op) + { + case BT_ASCS_CONFIG_OP: + unicast_client_notify_ep_config(stream, ase_rsp->code, + ase_rsp->reason); + break; + case BT_ASCS_QOS_OP: + unicast_client_notify_ep_qos(stream, ase_rsp->code, + ase_rsp->reason); + break; + case BT_ASCS_ENABLE_OP: + unicast_client_notify_ep_enable(stream, ase_rsp->code, + ase_rsp->reason); + break; + case BT_ASCS_START_OP: + unicast_client_notify_ep_start(stream, ase_rsp->code, + ase_rsp->reason); + break; + case BT_ASCS_DISABLE_OP: + unicast_client_notify_ep_disable(stream, ase_rsp->code, + ase_rsp->reason); + break; + case BT_ASCS_STOP_OP: + unicast_client_notify_ep_stop(stream, ase_rsp->code, + ase_rsp->reason); + break; + case BT_ASCS_METADATA_OP: + unicast_client_notify_ep_metadata(stream, ase_rsp->code, + ase_rsp->reason); + break; + case BT_ASCS_RELEASE_OP: + /* client_ep->release_requested is set to false if handled by the + * endpoint notification handler + */ + + if (client_ep != NULL && client_ep->release_requested) + { + /* If request was reject, do not expect endpoint + * notifications + */ + + if (ase_rsp->code != BT_BAP_ASCS_RSP_CODE_SUCCESS) + { + client_ep->cp_ntf_pending = false; + client_ep->release_requested = false; + } + + unicast_client_notify_ep_released(stream, ase_rsp->code, + ase_rsp->reason); + } + break; + default: + break; + } + } + + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t unicast_client_ase_ntf_read_func( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *read, + const void *data, uint16_t length) +{ + uint16_t handle = read->single.handle; + struct unicast_client *client; + struct net_buf_simple *buf; + struct bt_bap_ep *ep; + + LOG_DBG("conn %p err 0x%02x len %u", conn, err, length); + + if (err != 0) + { + LOG_DBG("Failed to read ASE: %u", err); + + return BT_GATT_ITER_STOP; + } + + LOG_DBG("handle 0x%04x", handle); + + client = &uni_cli_insts[bt_conn_index(conn)]; + buf = &client->net_buf; + + if (data != NULL) + { + if (net_buf_simple_tailroom(buf) < length) + { + LOG_DBG("Buffer full, invalid server response of size %u", + length + client->net_buf.len); + clear_and_reset_if_long_read(conn, buf); + + return BT_GATT_ITER_STOP; + } + + /* store data */ + + net_buf_simple_add_mem(buf, data, length); + + return BT_GATT_ITER_CONTINUE; + } + + memset(read, 0, sizeof(*read)); + + if (buf->len < sizeof(struct bt_ascs_ase_status)) + { + LOG_DBG("Read response too small (%u)", buf->len); + clear_and_reset_if_long_read(conn, buf); + + return BT_GATT_ITER_STOP; + } + + ep = unicast_client_ep_get(conn, client->dir, handle); + if (!ep) + { + LOG_DBG("Unknown %s ep for handle 0x%04X", + bt_audio_dir_to_str(client->dir), handle); + clear_and_reset_if_long_read(conn, buf); + } + else + { + /* Set reason in case this exits the streaming state, unless already + * set + */ + + if (ep->reason == BT_HCI_ERR_SUCCESS) + { + ep->reason = BT_HCI_ERR_REMOTE_USER_TERM_CONN; + } + + /* Also treat long reads from notifications as notifications */ + + unicast_client_ep_set_status(conn, ep, &client->net_buf, true); + } + + return BT_GATT_ITER_STOP; +} + +static void long_ase_read(struct bt_bap_unicast_client_ep *client_ep) +{ + /* Perform long read if notification is maximum size */ + + struct bt_conn *conn = client_ep->ep.stream->conn; + struct unicast_client *client = &uni_cli_insts[bt_conn_index(conn)]; + struct net_buf_simple *long_read_buf = &client->net_buf; + int err; + + LOG_DBG("conn %p ep %p 0x%04X", conn, &client_ep->ep, client_ep->handle); + + if (atomic_test_and_set_bit(client->flags, UNICAST_CLIENT_FLAG_BUSY)) + { + /* If the client is busy reading or writing something else, reschedule + * the long read. + */ + + struct bt_conn_info conn_info; + + err = bt_conn_get_info(conn, &conn_info); + if (err != 0) + { + LOG_DBG("Failed to get conn info, use default interval"); + + conn_info.le.interval_us = + BT_CONN_INTERVAL_TO_US(BT_GAP_INIT_CONN_INT_MIN); + } + + /* Wait a connection interval to retry */ + + err = k_work_reschedule(&client_ep->ase_read_work, + USEC2TICK(conn_info.le.interval_us)); + if (err < 0) + { + LOG_DBG("Failed to reschedule ASE long read work: %d", err); + } + + return; + } + + client->read_params.func = unicast_client_ase_ntf_read_func; + client->read_params.handle_count = 1U; + client->read_params.single.handle = client_ep->handle; + client->read_params.single.offset = long_read_buf->len; + + err = bt_gatt_read(conn, &client->read_params); + if (err != 0) + { + LOG_DBG("Failed to read ASE: %d", err); + atomic_clear_bit(client->flags, UNICAST_CLIENT_FLAG_BUSY); + } +} + +static void delayed_ase_read_handler(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_bap_unicast_client_ep *client_ep = + CONTAINER_OF(dwork, struct bt_bap_unicast_client_ep, + ase_read_work); + + LOG_DBG("ep %p 0x%04X", &client_ep->ep, client_ep->handle); + + /* Try reading again */ + + long_ase_read(client_ep); +} + +static uint8_t unicast_client_ep_notify(struct bt_conn *conn, + struct bt_gatt_subscribe_params *params, + const void *data, + uint16_t length) +{ + struct net_buf_simple buf; + struct bt_bap_unicast_client_ep *client_ep; + int max_ntf_size; + struct bt_bap_ep *ep; + + client_ep = CONTAINER_OF(params, struct bt_bap_unicast_client_ep, + subscribe); + ep = &client_ep->ep; + + LOG_DBG("conn %p ep %p len %u", conn, ep, length); + + /* Cancel any pending long reads for the endpoint */ + + (void)k_work_cancel_delayable(&client_ep->ase_read_work); + + if (!data) + { + LOG_DBG("Unsubscribed"); + params->value_handle = BAP_HANDLE_UNUSED; + return BT_GATT_ITER_STOP; + } + + max_ntf_size = bt_att_get_max_notify_size(conn, BT_ATT_CHAN_OPT_NONE); + if (max_ntf_size < 0) + { + DEBUGASSERT(max_ntf_size != -EINVAL); + return BT_GATT_ITER_STOP; + } + + if (length == max_ntf_size) + { + struct unicast_client *client = &uni_cli_insts[bt_conn_index(conn)]; + + if (!atomic_test_bit(client->flags, UNICAST_CLIENT_FLAG_BUSY)) + { + struct net_buf_simple *long_read_buf = &client->net_buf; + + /* store data */ + + net_buf_simple_add_mem(long_read_buf, data, length); + } + + long_ase_read(client_ep); + + return BT_GATT_ITER_CONTINUE; + } + + net_buf_simple_init_with_data(&buf, (void *)data, length); + + if (buf.len < sizeof(struct bt_ascs_ase_status)) + { + LOG_ERR("Notification too small"); + return BT_GATT_ITER_STOP; + } + + /* Set reason in case this exits the streaming state, unless already set */ + + if (ep->reason == BT_HCI_ERR_SUCCESS) + { + ep->reason = BT_HCI_ERR_REMOTE_USER_TERM_CONN; + } + + unicast_client_ep_set_status(conn, ep, &buf, true); + + return BT_GATT_ITER_CONTINUE; +} + +static int unicast_client_ep_subscribe(struct bt_conn *conn, + struct bt_bap_ep *ep) +{ + struct bt_bap_unicast_client_ep *client_ep; + int err; + + client_ep = CONTAINER_OF(ep, struct bt_bap_unicast_client_ep, ep); + + LOG_DBG("ep %p handle 0x%02x", ep, client_ep->handle); + + if (client_ep->subscribe.value_handle) + { + return 0; + } + + client_ep->subscribe.value_handle = client_ep->handle; + client_ep->subscribe.ccc_handle = BT_GATT_AUTO_DISCOVER_CCC_HANDLE; + client_ep->subscribe.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + client_ep->subscribe.disc_params = &client_ep->discover; + client_ep->subscribe.notify = unicast_client_ep_notify; + client_ep->subscribe.value = BT_GATT_CCC_NOTIFY; + atomic_set_bit(client_ep->subscribe.flags, + BT_GATT_SUBSCRIBE_FLAG_VOLATILE); + + err = bt_gatt_subscribe(conn, &client_ep->subscribe); + if (err != 0) + { + (void)memset(&client_ep->subscribe, 0, sizeof(client_ep->subscribe)); + return err; + } + + return 0; +} + +static void unicast_client_cp_sub_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_subscribe_params *sub_params) +{ + ARG_UNUSED(sub_params); + + LOG_DBG("conn %p err %u", conn, err); + + unicast_client_discover_complete(conn, err); +} + +static void unicast_client_ep_set_cp(struct bt_conn *conn, uint16_t handle) +{ + struct unicast_client *client = &uni_cli_insts[bt_conn_index(conn)]; + + LOG_DBG("conn %p 0x%04x", conn, handle); + +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 + for (size_t i = 0U; i < ARRAY_SIZE(client->snks); i++) + { + struct bt_bap_unicast_client_ep *client_ep = &client->snks[i]; + + if (client_ep->handle) + { + client_ep->cp_handle = handle; + } + } +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 */ + +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 + for (size_t i = 0U; i < ARRAY_SIZE(client->srcs); i++) + { + struct bt_bap_unicast_client_ep *client_ep = &client->srcs[i]; + + if (client_ep->handle) + { + client_ep->cp_handle = handle; + } + } +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 */ + + if (!client->cp_subscribe.value_handle) + { + int err; + + client->cp_subscribe.value_handle = handle; + client->cp_subscribe.ccc_handle = BT_GATT_AUTO_DISCOVER_CCC_HANDLE; + client->cp_subscribe.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + client->cp_subscribe.disc_params = &client->disc_params; + client->cp_subscribe.notify = unicast_client_cp_notify; + client->cp_subscribe.value = BT_GATT_CCC_NOTIFY; + client->cp_subscribe.subscribe = unicast_client_cp_sub_cb; + atomic_set_bit(client->cp_subscribe.flags, + BT_GATT_SUBSCRIBE_FLAG_VOLATILE); + + err = bt_gatt_subscribe(conn, &client->cp_subscribe); + if (err != 0) + { + (void)memset(&client->cp_subscribe, 0, + sizeof(client->cp_subscribe)); + LOG_DBG("Failed to subscribe: %d", err); + + unicast_client_discover_complete(conn, err); + + return; + } + } + else + { + /* already subscribed */ + + unicast_client_discover_complete(conn, 0); + } +} + +struct net_buf_simple *bt_bap_unicast_client_ep_create_pdu( + struct bt_conn *conn, uint8_t op) +{ + struct unicast_client *client = &uni_cli_insts[bt_conn_index(conn)]; + struct bt_ascs_ase_cp *hdr; + + if (atomic_test_bit(client->flags, UNICAST_CLIENT_FLAG_BUSY)) + { + return NULL; + } + + hdr = net_buf_simple_add(&client->net_buf, sizeof(*hdr)); + hdr->op = op; + + return &client->net_buf; +} + +static int unicast_client_ep_config( + struct bt_bap_ep *ep, struct net_buf_simple *buf, + const struct bt_audio_codec_cfg *codec_cfg) +{ + struct bt_ascs_config *req; + + LOG_DBG("ep %p buf %p codec %p", ep, buf, codec_cfg); + + if (!ep) + { + return -EINVAL; + } + + LOG_DBG("id 0x%02x dir %s codec 0x%02x", ep->id, + bt_audio_dir_to_str(ep->dir), codec_cfg->id); + + req = net_buf_simple_add(buf, sizeof(*req)); + req->ase_id = ep->id; + req->latency = codec_cfg->target_latency; + req->phy = codec_cfg->target_phy; + req->codec.id = codec_cfg->id; + req->codec.cid = sys_cpu_to_le16(codec_cfg->cid); + req->codec.vid = sys_cpu_to_le16(codec_cfg->vid); + + req->cc_len = codec_cfg->data_len; + net_buf_simple_add_mem(buf, codec_cfg->data, codec_cfg->data_len); + + return 0; +} + +static int unicast_client_add_qos( + struct bt_bap_ep *ep, struct net_buf_simple *buf, + struct bt_bap_qos_cfg *qos) +{ + struct bt_ascs_qos *req; + struct bt_conn_iso *conn_iso; + + LOG_DBG("ep %p buf %p (%u / %u) qos %p", + ep, + buf, + buf->len, + buf->size, + qos); + + if (ep == NULL || ep->iso == NULL || ep->iso->chan.iso == NULL) + { + LOG_DBG("Invalid endpoint %p (%p (%p))", ep, + ep == NULL ? NULL : ep->iso, + (ep == NULL || ep->iso == NULL) ? NULL : ep->iso->chan.iso); + return -EINVAL; + } + + conn_iso = &ep->iso->chan.iso->iso; + + LOG_DBG("id 0x%02x cig 0x%02x cis 0x%02x interval %u framing 0x%02x " + "phy 0x%02x sdu %u rtn %u latency %u pd %u", + ep->id, conn_iso->info.unicast.cig_id, + conn_iso->info.unicast.cis_id, qos->interval, + qos->framing, qos->phy, qos->sdu, qos->rtn, qos->latency, qos->pd); + + if (buf->len + sizeof(*req) > buf->size) + { + return -ENOMEM; + } + + req = net_buf_simple_add(buf, sizeof(*req)); + req->ase_id = ep->id; + req->cig = conn_iso->info.unicast.cig_id; + req->cis = conn_iso->info.unicast.cis_id; + sys_put_le24(qos->interval, req->interval); + req->framing = qos->framing; + req->phy = qos->phy; + req->sdu = sys_cpu_to_le16(qos->sdu); + req->rtn = qos->rtn; + req->latency = sys_cpu_to_le16(qos->latency); + sys_put_le24(qos->pd, req->pd); + + return 0; +} + +static int unicast_client_ep_enable( + struct bt_bap_ep *ep, struct net_buf_simple *buf, const uint8_t meta[], + size_t meta_len) +{ + struct bt_ascs_metadata *req; + + LOG_DBG("ep %p buf %p meta_len %zu", ep, buf, meta_len); + + if (!ep) + { + return -EINVAL; + } + + LOG_DBG("id 0x%02x", ep->id); + + req = net_buf_simple_add(buf, sizeof(*req)); + req->ase_id = ep->id; + + req->len = meta_len; + net_buf_simple_add_mem(buf, meta, meta_len); + + return 0; +} + +static int unicast_client_ep_metadata( + struct bt_bap_ep *ep, struct net_buf_simple *buf, const uint8_t meta[], + size_t meta_len) +{ + struct bt_ascs_metadata *req; + + LOG_DBG("ep %p buf %p meta_len %zu", ep, buf, meta_len); + + if (!ep) + { + return -EINVAL; + } + + LOG_DBG("id 0x%02x", ep->id); + + req = net_buf_simple_add(buf, sizeof(*req)); + req->ase_id = ep->id; + + req->len = meta_len; + net_buf_simple_add_mem(buf, meta, meta_len); + + return 0; +} + +static int unicast_client_ep_start(struct bt_bap_ep *ep, + struct net_buf_simple *buf) +{ + LOG_DBG("ep %p buf %p", ep, buf); + + if (!ep) + { + return -EINVAL; + } + + LOG_DBG("id 0x%02x", ep->id); + + net_buf_simple_add_u8(buf, ep->id); + + return 0; +} + +static int unicast_client_ep_disable(struct bt_bap_ep *ep, + struct net_buf_simple *buf) +{ + LOG_DBG("ep %p buf %p", ep, buf); + + if (!ep) + { + return -EINVAL; + } + + LOG_DBG("id 0x%02x", ep->id); + + net_buf_simple_add_u8(buf, ep->id); + + return 0; +} + +static int unicast_client_ep_stop(struct bt_bap_ep *ep, + struct net_buf_simple *buf) +{ + LOG_DBG("ep %p buf %p", ep, buf); + + if (!ep) + { + return -EINVAL; + } + + LOG_DBG("id 0x%02x", ep->id); + + net_buf_simple_add_u8(buf, ep->id); + + return 0; +} + +static int unicast_client_ep_release(struct bt_bap_ep *ep, + struct net_buf_simple *buf) +{ + LOG_DBG("ep %p buf %p", ep, buf); + + if (!ep) + { + return -EINVAL; + } + + LOG_DBG("id 0x%02x", ep->id); + + net_buf_simple_add_u8(buf, ep->id); + + return 0; +} + +static void gatt_write_cb(struct bt_conn *conn, + uint8_t err, struct bt_gatt_write_params *params) +{ + struct unicast_client *client = &uni_cli_insts[bt_conn_index(conn)]; + + LOG_DBG("conn %p err %u", conn, err); + + memset(params, 0, sizeof(*params)); + reset_att_buf(client); + atomic_clear_bit(client->flags, UNICAST_CLIENT_FLAG_BUSY); + + /* TBD: Should we do anything in case of error here? */ +} + +int bt_bap_unicast_client_ep_send(struct bt_conn *conn, struct bt_bap_ep *ep, + struct net_buf_simple *buf) +{ + struct unicast_client *client = &uni_cli_insts[bt_conn_index(conn)]; + struct bt_bap_unicast_client_ep *client_ep = + CONTAINER_OF(ep, struct bt_bap_unicast_client_ep, ep); + int max_write_command_size; + int err; + + LOG_DBG("conn %p ep %p buf %p len %u", conn, ep, buf, buf->len); + + /* Write commands have the same size as notifications */ + + max_write_command_size = bt_att_get_max_notify_size(conn, + BT_ATT_CHAN_OPT_NONE); + if (max_write_command_size < 0) + { + DEBUGASSERT(max_write_command_size != -EINVAL); + LOG_DBG("Failed to get max write size: %d", max_write_command_size); + return max_write_command_size; /* contains errno value */ + } + + if (buf->len > max_write_command_size) + { + if (atomic_test_and_set_bit(client->flags, UNICAST_CLIENT_FLAG_BUSY)) + { + LOG_DBG("Client connection is busy"); + return -EBUSY; + } + + client->write_params.func = gatt_write_cb; + client->write_params.handle = client_ep->cp_handle; + client->write_params.offset = 0U; + client->write_params.data = buf->data; + client->write_params.length = buf->len; +#if defined(CONFIG_BT_EATT) + client->write_params.chan_opt = BT_ATT_CHAN_OPT_NONE; +#endif /* CONFIG_BT_EATT */ + + err = bt_gatt_write(conn, &client->write_params); + if (err != 0) + { + LOG_DBG("bt_gatt_write failed: %d", err); + atomic_clear_bit(client->flags, UNICAST_CLIENT_FLAG_BUSY); + } + } + else + { + err = bt_gatt_write_without_response(conn, client_ep->cp_handle, + buf->data, buf->len, false); + if (err != 0) + { + LOG_DBG("bt_gatt_write_without_response failed: %d", err); + } + + /* No callback for writing without response, so reset the buffer here */ + + reset_att_buf(client); + } + + if (err == 0) + { + client_ep->cp_ntf_pending = true; + } + + return err; +} + +static void unicast_client_reset(struct bt_bap_ep *ep, uint8_t reason) +{ + struct bt_bap_unicast_client_ep *client_ep = + CONTAINER_OF(ep, struct bt_bap_unicast_client_ep, ep); + + LOG_DBG("ep %p", ep); + ep->reason = reason; + + /* Pretend we received an idle state notification from the server to + * trigger all cleanup + */ + + unicast_client_ep_set_local_idle_state(ep); + + DEBUGASSERT(ep->iso == NULL); + + (void)k_work_cancel_delayable(&client_ep->ase_read_work); + (void)memset(ep, 0, sizeof(*ep)); + + client_ep->cp_handle = BAP_HANDLE_UNUSED; + client_ep->handle = BAP_HANDLE_UNUSED; + (void)memset(&client_ep->discover, 0, sizeof(client_ep->discover)); + client_ep->release_requested = false; + client_ep->cp_ntf_pending = false; + + /* Need to keep the subscribe params intact for the callback */ +} + +static void unicast_client_ep_reset(struct bt_conn *conn, uint8_t reason) +{ + struct unicast_client *client; + uint8_t index; + + LOG_DBG("conn %p", conn); + + index = bt_conn_index(conn); + +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 + for (size_t i = 0U; i < ARRAY_SIZE(uni_cli_insts[index].snks); i++) + { + struct bt_bap_ep *ep = &uni_cli_insts[index].snks[i].ep; + + unicast_client_reset(ep, reason); + } +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 */ + +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 + for (size_t i = 0U; i < ARRAY_SIZE(uni_cli_insts[index].srcs); i++) + { + struct bt_bap_ep *ep = &uni_cli_insts[index].srcs[i].ep; + + unicast_client_reset(ep, reason); + } +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 */ + + client = &uni_cli_insts[index]; + client->dir = 0U; + reset_att_buf(client); + atomic_set(client->flags, 0); +} + +static void bt_bap_qos_cfg_to_cig_param(struct bt_iso_cig_param *cig_param, + const struct bt_bap_unicast_group *group) +{ + cig_param->framing = group->cig_param.framing; + cig_param->c_to_p_interval = group->cig_param.c_to_p_interval; + cig_param->p_to_c_interval = group->cig_param.p_to_c_interval; + cig_param->c_to_p_latency = group->cig_param.c_to_p_latency; + cig_param->p_to_c_latency = group->cig_param.p_to_c_latency; + cig_param->packing = group->cig_param.packing; + cig_param->sca = BT_GAP_SCA_UNKNOWN; + + IF_ENABLED(CONFIG_BT_ISO_TEST_PARAMS, ( + { + cig_param->c_to_p_ft = group->cig_param.c_to_p_ft; + cig_param->p_to_c_ft = group->cig_param.p_to_c_ft; + cig_param->iso_interval = group->cig_param.iso_interval; + } + + )); + + /* In the case that we only setup a single direction, we still need + * (as per section 7.8.97 LE Set CIG Parameters command) to set the + * interval and latency on both sides. The value shall be ignored, but the + * value may not always be ignored correctly by all controllers, so we + * always set it here. If it is at this point unset, then we set the + * opposing direction to the same value. + */ + + if (cig_param->c_to_p_interval == 0U) + { + cig_param->c_to_p_interval = cig_param->p_to_c_interval; + } + else if (cig_param->p_to_c_interval == 0U) + { + cig_param->p_to_c_interval = cig_param->c_to_p_interval; + } + + if (cig_param->c_to_p_latency == 0U) + { + cig_param->c_to_p_latency = cig_param->p_to_c_latency; + } + else if (cig_param->p_to_c_latency == 0U) + { + cig_param->p_to_c_latency = cig_param->c_to_p_latency; + } +} + +static void unicast_client_iso_param_to_qos_cfg( + const struct bt_bap_stream *stream, struct bt_bap_qos_cfg *qos) +{ + const struct bt_bap_iso *bap_iso = CONTAINER_OF(stream->iso, + struct bt_bap_iso, chan); + const struct bt_bap_unicast_group *unicast_group = stream->group; + const struct bt_iso_chan_io_qos *iso_qos; + enum bt_audio_dir dir = stream->ep->dir; + + if (dir == BT_AUDIO_DIR_SINK) + { + qos->pd = unicast_group->sink_pd; + qos->latency = unicast_group->cig_param.c_to_p_latency; + qos->interval = unicast_group->cig_param.c_to_p_interval; + iso_qos = &bap_iso->tx.qos; + } + else + { + qos->pd = unicast_group->source_pd; + qos->latency = unicast_group->cig_param.p_to_c_latency; + qos->interval = unicast_group->cig_param.p_to_c_interval; + iso_qos = &bap_iso->rx.qos; + } + + qos->framing = unicast_group->cig_param.framing; + qos->phy = iso_qos->phy; + qos->rtn = iso_qos->rtn; + qos->sdu = iso_qos->sdu; +#if defined(CONFIG_BT_ISO_TEST_PARAMS) + qos->max_pdu = iso_qos->max_pdu; + qos->burst_number = iso_qos->burst_number; + qos->num_subevents = bap_iso->qos.num_subevents; +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ +} + +static uint8_t unicast_group_get_cis_count( + const struct bt_bap_unicast_group *unicast_group) +{ + uint8_t cis_count = 0U; + + for (size_t i = 0U; i < ARRAY_SIZE(unicast_group->cis); i++) + { + if (unicast_group->cis[i] == NULL) + { + /* A NULL CIS acts as a NULL terminator */ + + break; + } + + cis_count++; + } + + return cis_count; +} + +static int bt_audio_cig_create(struct bt_bap_unicast_group *group) +{ + struct bt_iso_cig_param param = + { + 0 + }; + + int err; + + LOG_DBG("group %p", group); + + param.num_cis = unicast_group_get_cis_count(group); + param.cis_channels = group->cis; + bt_bap_qos_cfg_to_cig_param(¶m, group); + + err = bt_iso_cig_create(¶m, &group->cig); + if (err != 0) + { + LOG_ERR("bt_iso_cig_create failed: %d", err); + return err; + } + + return 0; +} + +static int bt_audio_cig_reconfigure(struct bt_bap_unicast_group *group) +{ + struct bt_iso_cig_param param; + uint8_t cis_count; + int err; + + LOG_DBG("group %p ", group); + + cis_count = 0U; + for (size_t i = 0U; i < ARRAY_SIZE(group->cis); i++) + { + if (group->cis[i] == NULL) + { + /* A NULL CIS acts as a NULL terminator */ + + break; + } + + cis_count++; + } + + param.num_cis = cis_count; + param.cis_channels = group->cis; + bt_bap_qos_cfg_to_cig_param(¶m, group); + + err = bt_iso_cig_reconfigure(group->cig, ¶m); + if (err != 0) + { + LOG_ERR("bt_iso_cig_create failed: %d", err); + return err; + } + + return 0; +} + +static int unicast_client_cig_terminate(struct bt_bap_unicast_group *group) +{ + LOG_DBG("group %p", group); + + return bt_iso_cig_terminate(group->cig); +} + +static int unicast_group_add_iso(struct bt_bap_unicast_group *group, + struct bt_bap_iso *iso) +{ + struct bt_iso_chan **chan_slot = NULL; + + DEBUGASSERT(group != NULL); + DEBUGASSERT(iso != NULL); + + /* Append iso channel to the group->cis array */ + + for (size_t i = 0U; i < ARRAY_SIZE(group->cis); i++) + { + /* Return if already there */ + + if (group->cis[i] == &iso->chan) + { + return 0; + } + + if (chan_slot == NULL && group->cis[i] == NULL) + { + chan_slot = &group->cis[i]; + } + } + + if (chan_slot == NULL) + { + return -ENOMEM; + } + + *chan_slot = &iso->chan; + + return 0; +} + +static void unicast_group_del_iso(struct bt_bap_unicast_group *group, + struct bt_bap_iso *iso) +{ + struct bt_bap_stream *stream; + + DEBUGASSERT(group != NULL); + DEBUGASSERT(iso != NULL); + + list_for_every_container(&group->streams, stream, _node) + { + if (stream->ep->iso == iso) + { + /* still in use by some other stream */ + + return; + } + } + + for (size_t i = 0U; i < ARRAY_SIZE(group->cis); i++) + { + if (group->cis[i] == &iso->chan) + { + group->cis[i] = NULL; + return; + } + } +} + +int bt_bap_unicast_client_qos_from_group(const struct bt_bap_stream *stream, + struct bt_bap_qos_cfg *qos) +{ + if (stream == NULL) + { + LOG_DBG("stream is NULL"); + return -EINVAL; + } + + if (stream->group == NULL) + { + LOG_DBG("stream not in a group"); + return -EINVAL; + } + + if (stream->iso == NULL) + { + LOG_DBG("stream not bound with an ISO chan"); + return -EINVAL; + } + + if (qos == NULL) + { + LOG_DBG("qos is NULL"); + return -EINVAL; + } + + /* stream->ep does not need to be set. We should be able to get dir based + * on bap_iso + */ + + const struct bt_bap_iso *bap_iso = CONTAINER_OF(stream->iso, + struct bt_bap_iso, chan); + const enum bt_audio_dir dir = + bap_iso->tx.stream == + stream ? BT_AUDIO_DIR_SINK : BT_AUDIO_DIR_SOURCE; + const struct bt_bap_unicast_group *unicast_group = stream->group; + const struct bt_iso_chan_io_qos *iso_qos; + + /* memset the struct since it contains padding so that it can be used with + * memcmp + */ + + (void)memset(qos, 0, sizeof(*qos)); + if (dir == BT_AUDIO_DIR_SINK) + { + qos->pd = unicast_group->sink_pd; + qos->latency = unicast_group->cig_param.c_to_p_latency; + qos->interval = unicast_group->cig_param.c_to_p_interval; + iso_qos = &bap_iso->tx.qos; + } + else + { + qos->pd = unicast_group->source_pd; + qos->latency = unicast_group->cig_param.p_to_c_latency; + qos->interval = unicast_group->cig_param.p_to_c_interval; + iso_qos = &bap_iso->rx.qos; + } + + qos->framing = unicast_group->cig_param.framing; + qos->phy = iso_qos->phy; + qos->rtn = iso_qos->rtn; + qos->sdu = iso_qos->sdu; +#if defined(CONFIG_BT_ISO_TEST_PARAMS) + qos->max_pdu = iso_qos->max_pdu; + qos->burst_number = iso_qos->burst_number; + qos->num_subevents = bap_iso->qos.num_subevents; +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ + + return 0; +} + +static void unicast_client_qos_cfg_to_iso_qos(struct bt_bap_iso *iso, + const struct bt_bap_qos_cfg *qos, + enum bt_audio_dir dir) +{ + struct bt_iso_chan_io_qos *io_qos; + struct bt_iso_chan_io_qos *other_io_qos; + + if (dir == BT_AUDIO_DIR_SINK) + { + /* If the endpoint is a sink, then we need to + * configure our TX parameters + */ + + io_qos = iso->chan.qos->tx; + if (bt_bap_iso_get_ep(true, iso, BT_AUDIO_DIR_SOURCE) == NULL) + { + other_io_qos = iso->chan.qos->rx; + } + else + { + other_io_qos = NULL; + } + } + else + { + /* If the endpoint is a source, then we need to + * configure our RX parameters + */ + + io_qos = iso->chan.qos->rx; + if (bt_bap_iso_get_ep(true, iso, BT_AUDIO_DIR_SINK) == NULL) + { + other_io_qos = iso->chan.qos->tx; + } + else + { + other_io_qos = NULL; + } + } + + bt_bap_qos_cfg_to_iso_qos(io_qos, qos); +#if defined(CONFIG_BT_ISO_TEST_PARAMS) + iso->chan.qos->num_subevents = qos->num_subevents; +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ + + if (other_io_qos != NULL) + { + /* If the opposing ASE of the CIS is not yet configured, we + * still need to set the PHY value when creating the CIG. + */ + + other_io_qos->phy = io_qos->phy; + } +} + +static void unicast_group_set_iso_stream_param( + struct bt_bap_unicast_group *group, struct bt_bap_iso *iso, + const struct bt_bap_qos_cfg *qos, enum bt_audio_dir dir) +{ + /* Store the stream Codec QoS in the bap_iso */ + + unicast_client_qos_cfg_to_iso_qos(iso, qos, dir); + + /* Store the group Codec QoS in the group - this assumes that the + * parameters have been verified first + */ + + group->cig_param.framing = qos->framing; + if (dir == BT_AUDIO_DIR_SOURCE) + { + group->cig_param.p_to_c_interval = qos->interval; + group->cig_param.p_to_c_latency = qos->latency; + group->source_pd = qos->pd; + } + else + { + group->cig_param.c_to_p_interval = qos->interval; + group->cig_param.c_to_p_latency = qos->latency; + group->sink_pd = qos->pd; + } +} + +static void unicast_group_add_stream(struct bt_bap_unicast_group *group, + struct bt_bap_unicast_group_stream_param *param, + struct bt_bap_iso *iso, + enum bt_audio_dir dir) +{ + struct bt_bap_stream *stream = param->stream; + const struct bt_bap_qos_cfg *qos = param->qos; + + LOG_DBG("group %p stream %p qos %p iso %p dir %u", group, stream, qos, iso, + dir); + + DEBUGASSERT(stream->ep == NULL || (stream->ep != NULL && + stream->ep->iso == NULL)); + + stream->group = group; + + /* iso initialized already */ + + bt_bap_iso_bind_stream(iso, stream, dir); + if (stream->ep != NULL) + { + bt_bap_iso_bind_ep(iso, stream->ep); + } + + unicast_group_set_iso_stream_param(group, iso, qos, dir); + + list_add_tail(&group->streams, &stream->_node); +} + +static int unicast_group_add_stream_pair(struct bt_bap_unicast_group *group, + struct bt_bap_unicast_group_stream_pair_param *param) +{ + struct bt_bap_iso *iso; + int err; + + DEBUGASSERT(group != NULL); + DEBUGASSERT(param != NULL); + DEBUGASSERT(param->rx_param != NULL || param->tx_param != NULL); + + iso = bt_bap_unicast_client_new_audio_iso(); + if (iso == NULL) + { + return -ENOMEM; + } + + err = unicast_group_add_iso(group, iso); + if (err < 0) + { + bt_bap_iso_unref(iso); + return err; + } + + if (param->rx_param != NULL) + { + unicast_group_add_stream(group, param->rx_param, iso, + BT_AUDIO_DIR_SOURCE); + } + + if (param->tx_param != NULL) + { + unicast_group_add_stream(group, param->tx_param, iso, + BT_AUDIO_DIR_SINK); + } + + bt_bap_iso_unref(iso); + + return 0; +} + +static void unicast_group_del_stream(struct bt_bap_unicast_group *group, + struct bt_bap_stream *stream, + enum bt_audio_dir dir) +{ + DEBUGASSERT(group != NULL); + DEBUGASSERT(stream != NULL); + + if (list_find_and_remove(&group->streams, &stream->_node)) + { + struct bt_bap_ep *ep = stream->ep; + + if (stream->iso != NULL) + { + bt_bap_iso_unbind_stream(stream, dir); + } + + if (ep != NULL && ep->iso != NULL) + { + unicast_group_del_iso(group, ep->iso); + + bt_bap_iso_unbind_ep(ep->iso, ep); + } + + stream->group = NULL; + } +} + +static void unicast_group_del_stream_pair(struct bt_bap_unicast_group *group, + struct bt_bap_unicast_group_stream_pair_param *param) +{ + DEBUGASSERT(group != NULL); + DEBUGASSERT(param != NULL); + DEBUGASSERT(param->rx_param != NULL || param->tx_param != NULL); + + if (param->rx_param != NULL) + { + DEBUGASSERT(param->rx_param->stream); + unicast_group_del_stream(group, param->rx_param->stream, + BT_AUDIO_DIR_SOURCE); + } + + if (param->tx_param != NULL) + { + DEBUGASSERT(param->tx_param->stream); + unicast_group_del_stream(group, param->tx_param->stream, + BT_AUDIO_DIR_SINK); + } +} + +static struct bt_bap_unicast_group *unicast_group_alloc(void) +{ + struct bt_bap_unicast_group *group = NULL; + + for (size_t i = 0U; i < ARRAY_SIZE(unicast_groups); i++) + { + if (!unicast_groups[i].allocated) + { + group = &unicast_groups[i]; + + (void)memset(group, 0, sizeof(*group)); + + group->allocated = true; + group->index = i; + group->sink_pd = BT_BAP_PD_UNSET; + group->source_pd = BT_BAP_PD_UNSET; + + break; + } + } + + return group; +} + +static void unicast_group_free(struct bt_bap_unicast_group *group) +{ + struct bt_bap_stream *stream; + struct bt_bap_stream *next; + + DEBUGASSERT(group != NULL); + + list_for_every_container_safe(&group->streams, stream, next, _node) + { + struct bt_bap_iso *bap_iso = CONTAINER_OF(stream->iso, struct bt_bap_iso, + chan); + struct bt_bap_ep *ep = stream->ep; + + stream->group = NULL; + if (bap_iso != NULL) + { + if (bap_iso->rx.stream == stream) + { + bt_bap_iso_unbind_stream(stream, BT_AUDIO_DIR_SOURCE); + } + else if (bap_iso->tx.stream == stream) + { + bt_bap_iso_unbind_stream(stream, BT_AUDIO_DIR_SINK); + } + else + { + LOG_ERR("stream %p has invalid bap_iso %p", stream, bap_iso); + } + } + + if (ep != NULL && ep->iso != NULL) + { + bt_bap_iso_unbind_ep(ep->iso, ep); + } + + list_remove(&group->streams, NULL, &stream->_node); + } + + (void)memset(&group->cig_param, 0, sizeof(group->cig_param)); + group->sink_pd = BT_BAP_PD_UNSET; + group->source_pd = BT_BAP_PD_UNSET; + group->allocated = false; +} + +static int stream_param_check( + const struct bt_bap_unicast_group_stream_param *param) +{ + if (param->stream == NULL) + { + LOG_DBG("param->stream is NULL"); + return -EINVAL; + } + + if (param->qos == NULL) + { + LOG_DBG("param->qos is NULL"); + return -EINVAL; + } + + if (param->stream != NULL && param->stream->group != NULL) + { + LOG_DBG("stream %p already part of group %p", param->stream, + param->stream->group); + return -EALREADY; + } + + if (bt_audio_verify_qos(param->qos) != BT_BAP_ASCS_REASON_NONE) + { + LOG_DBG("Invalid QoS"); + return -EINVAL; + } + + return 0; +} + +static int stream_pair_param_check( + const struct bt_bap_unicast_group_stream_pair_param *param) +{ + int err; + + if (param->rx_param == NULL && param->tx_param == NULL) + { + LOG_DBG("Invalid stream parameters"); + return -EINVAL; + } + + if (param->rx_param != NULL) + { + err = stream_param_check(param->rx_param); + if (err < 0) + { + return err; + } + } + + if (param->tx_param != NULL) + { + err = stream_param_check(param->tx_param); + if (err < 0) + { + return err; + } + } + + return 0; +} + +/** Validates that the stream parameter does not contain invalid values */ + +static bool valid_unicast_group_stream_param( + const struct bt_bap_unicast_group *unicast_group, + const struct bt_bap_unicast_group_stream_param *param, + struct bt_bap_unicast_group_cig_param *cig_param, uint32_t *pd, + enum bt_audio_dir dir) +{ + const struct bt_bap_qos_cfg *qos; + + if (param->stream == NULL) + { + LOG_DBG("param->stream is NULL"); + return false; + } + + if (param->qos == NULL) + { + LOG_DBG("param->qos is NULL"); + return false; + } + + /* If unicast_group is non-NULL then we are doing a reconfigure */ + + if (unicast_group != NULL) + { + if (param->stream->group != unicast_group) + { + LOG_DBG("stream %p not part of group %p (%p)", param->stream, + unicast_group, + param->stream->group); + return false; + } + } + else + { + if (param->stream->group != NULL) + { + LOG_DBG("stream %p already part of group %p", param->stream, + param->stream->group); + + return false; + } + } + + if (bt_audio_verify_qos(param->qos) != BT_BAP_ASCS_REASON_NONE) + { + LOG_DBG("Invalid QoS"); + return false; + } + + qos = param->qos; + + /* If unset we set the interval else we verify that all streams use the + * same interval and latency in the same direction, as that is required + * when creating a CIG + */ + + if (dir == BT_AUDIO_DIR_SINK) + { + if (cig_param->c_to_p_interval == 0) + { + cig_param->c_to_p_interval = qos->interval; + } + else if (cig_param->c_to_p_interval != qos->interval) + { + LOG_DBG("Group %p c_to_p_interval does not match stream %p: %u != " + "%u", + unicast_group, + param->stream, cig_param->c_to_p_interval, qos->interval); + return false; + } + + if (cig_param->c_to_p_latency == 0) + { + cig_param->c_to_p_latency = qos->latency; + } + else if (cig_param->c_to_p_latency != qos->latency) + { + LOG_DBG("Group %p c_to_p_latency does not match stream %p: %u != " + "%u", + unicast_group, + param->stream, cig_param->c_to_p_latency, qos->latency); + return false; + } + + if (*pd == BT_BAP_PD_UNSET) + { + *pd = qos->pd; + } + else if (*pd != qos->pd) + { + LOG_DBG("Group %p sink PD does not match stream %p: %u != %u", + unicast_group, + param->stream, *pd, qos->pd); + return false; + } + } + else + { + if (cig_param->p_to_c_interval == 0) + { + cig_param->p_to_c_interval = qos->interval; + } + else if (cig_param->p_to_c_interval != qos->interval) + { + LOG_DBG("Group %p p_to_c_interval does not match stream %p: %u != " + "%u", + unicast_group, + param->stream, cig_param->p_to_c_interval, qos->interval); + return false; + } + + if (cig_param->p_to_c_latency == 0) + { + cig_param->p_to_c_latency = qos->latency; + } + else if (cig_param->p_to_c_latency != qos->latency) + { + LOG_DBG("Group %p p_to_c_latency does not match stream %p: %u != " + "%u", + unicast_group, + param->stream, cig_param->p_to_c_latency, qos->latency); + return false; + } + + if (*pd == BT_BAP_PD_UNSET) + { + *pd = qos->pd; + } + else if (*pd != qos->pd) + { + LOG_DBG("Group %p source PD does not match stream %p: %u != %u", + unicast_group, + param->stream, *pd, qos->pd); + return false; + } + } + + if (cig_param->framing == 0) + { + if (qos->framing == BT_BAP_QOS_CFG_FRAMING_UNFRAMED) + { + cig_param->framing = BT_ISO_FRAMING_UNFRAMED; + } + else if (qos->framing == BT_BAP_QOS_CFG_FRAMING_FRAMED) + { + cig_param->framing = BT_ISO_FRAMING_FRAMED; + } + } + else if ((qos->framing == BT_BAP_QOS_CFG_FRAMING_UNFRAMED && + cig_param->framing != BT_ISO_FRAMING_UNFRAMED) || + (qos->framing == BT_BAP_QOS_CFG_FRAMING_FRAMED && + cig_param->framing != BT_ISO_FRAMING_FRAMED)) + { + LOG_DBG("Group %p framing does not match stream %p: %u != %u", + unicast_group, param->stream, + cig_param->framing, qos->framing); + return false; + } + + return true; +} + +static bool +valid_group_stream_pair_param( + const struct bt_bap_unicast_group *unicast_group, + const struct bt_bap_unicast_group_stream_pair_param *pair_param, + struct bt_bap_unicast_group_cig_param *cig_param, + uint32_t *sink_pd, + uint32_t *source_pd) +{ + if (pair_param == NULL) + { + LOG_DBG("pair_param is NULL"); + return false; + } + + if (pair_param->rx_param == NULL && pair_param->tx_param == NULL) + { + LOG_DBG("pair_param->rx_param and pair_param->tx_param are NULL"); + return false; + } + + if (pair_param->rx_param != NULL) + { + if (!valid_unicast_group_stream_param(unicast_group, + pair_param->rx_param, cig_param, + source_pd, BT_AUDIO_DIR_SOURCE)) + { + return false; + } + } + + if (pair_param->tx_param != NULL) + { + if (!valid_unicast_group_stream_param(unicast_group, + pair_param->tx_param, cig_param, + sink_pd, + BT_AUDIO_DIR_SINK)) + { + return false; + } + } + + return true; +} + +static bool valid_unicast_group_param( + struct bt_bap_unicast_group *unicast_group, + const struct bt_bap_unicast_group_param *param) +{ + struct bt_bap_unicast_group_cig_param cig_param = + { + 0 + }; + + uint32_t source_pd = BT_BAP_PD_UNSET; + uint32_t sink_pd = BT_BAP_PD_UNSET; + + if (param == NULL) + { + LOG_DBG("streams is NULL"); + return false; + } + + if (param->params_count > UNICAST_GROUP_STREAM_CNT) + { + LOG_DBG("Too many streams provided: %u/%u", param->params_count, + UNICAST_GROUP_STREAM_CNT); + return false; + } + + if (unicast_group != NULL) + { + const size_t group_cis_cnt = + unicast_group_get_cis_count(unicast_group); + + if (param->params_count != group_cis_cnt) + { + LOG_DBG("Mismatch between group CIS count (%zu) and params_count " + "(%zu)", + group_cis_cnt, + param->params_count); + + return false; + } + } + + for (size_t i = 0U; i < param->params_count; i++) + { + if (!valid_group_stream_pair_param(unicast_group, ¶m->params[i], + &cig_param, &sink_pd, + &source_pd)) + { + return false; + } + } + + return true; +} + +int bt_bap_unicast_group_create(struct bt_bap_unicast_group_param *param, + struct bt_bap_unicast_group **out_unicast_group) +{ + struct bt_bap_unicast_group *unicast_group; + int err; + + if (out_unicast_group == NULL) + { + LOG_DBG("out_unicast_group is NULL"); + return -EINVAL; + } + + /* Set out_unicast_group to NULL until the source has actually been created + */ + + *out_unicast_group = NULL; + + unicast_group = unicast_group_alloc(); + if (unicast_group == NULL) + { + LOG_DBG("Could not allocate any more unicast groups"); + return -ENOMEM; + } + + if (!valid_unicast_group_param(NULL, param)) + { + unicast_group_free(unicast_group); + + return -EINVAL; + } + + for (size_t i = 0U; i < param->params_count; i++) + { + struct bt_bap_unicast_group_stream_pair_param *stream_param; + + stream_param = ¶m->params[i]; + + unicast_group->cig_param.packing = param->packing; + IF_ENABLED(CONFIG_BT_ISO_TEST_PARAMS, ( + { + unicast_group->cig_param.c_to_p_ft = param->c_to_p_ft; + unicast_group->cig_param.p_to_c_ft = param->p_to_c_ft; + unicast_group->cig_param.iso_interval = param->iso_interval; + } + + )); + + err = unicast_group_add_stream_pair(unicast_group, stream_param); + if (err < 0) + { + LOG_DBG("unicast_group_add_stream failed: %d", err); + unicast_group_free(unicast_group); + + return err; + } + } + + err = bt_audio_cig_create(unicast_group); + if (err != 0) + { + LOG_DBG("bt_audio_cig_create failed: %d", err); + unicast_group_free(unicast_group); + + return err; + } + + *out_unicast_group = unicast_group; + + return 0; +} + +int bt_bap_unicast_group_reconfig(struct bt_bap_unicast_group *unicast_group, + const struct bt_bap_unicast_group_param *param) +{ + struct bt_iso_chan_io_qos rx_io_qos_backup[UNICAST_GROUP_STREAM_CNT]; + struct bt_iso_chan_io_qos tx_io_qos_backup[UNICAST_GROUP_STREAM_CNT]; + struct bt_bap_unicast_group_cig_param cig_param_backup; + struct bt_bap_stream *tmp_stream; + size_t idx; + int err; + + IF_ENABLED(CONFIG_BT_ISO_TEST_PARAMS, + (uint8_t num_subevents_backup[UNICAST_GROUP_STREAM_CNT])); + + if (unicast_group == NULL) + { + LOG_DBG("unicast_group is NULL"); + return -EINVAL; + } + + if (unicast_group->has_been_connected) + { + LOG_DBG("Cannot modify a unicast_group where a CIS has been " + "connected"); + return -EINVAL; + } + + if (!valid_unicast_group_param(unicast_group, param)) + { + return -EINVAL; + } + + /* Make backups of the values in case that the reconfigure request is + * rejected by e.g. the controller + */ + + idx = 0U; + list_for_every_container(&unicast_group->streams, tmp_stream, _node) + { + const struct bt_bap_iso *bap_iso = CONTAINER_OF(tmp_stream->iso, + struct bt_bap_iso, chan); + const struct bt_iso_chan_qos *qos = bap_iso->chan.qos; + + memcpy(&rx_io_qos_backup[idx], qos->rx, sizeof(rx_io_qos_backup[idx])); + memcpy(&tx_io_qos_backup[idx], qos->tx, sizeof(tx_io_qos_backup[idx])); + IF_ENABLED(CONFIG_BT_ISO_TEST_PARAMS, + (num_subevents_backup[idx] = qos->num_subevents)); + idx++; + } + + memcpy(&cig_param_backup, &unicast_group->cig_param, + sizeof(cig_param_backup)); + + /* Update the stream and group parameters */ + + for (size_t i = 0U; i < param->params_count; i++) + { + struct bt_bap_unicast_group_stream_pair_param *stream_param = + ¶m->params[i]; + struct bt_bap_unicast_group_stream_param *rx_param = + stream_param->rx_param; + struct bt_bap_unicast_group_stream_param *tx_param = + stream_param->tx_param; + + unicast_group->cig_param.packing = param->packing; + IF_ENABLED(CONFIG_BT_ISO_TEST_PARAMS, ( + { + unicast_group->cig_param.c_to_p_ft = param->c_to_p_ft; + unicast_group->cig_param.p_to_c_ft = param->p_to_c_ft; + unicast_group->cig_param.iso_interval = param->iso_interval; + } + + )); + + if (rx_param != NULL) + { + struct bt_bap_iso *bap_iso = CONTAINER_OF(rx_param->stream->iso, + struct bt_bap_iso, chan); + + unicast_group_set_iso_stream_param(unicast_group, bap_iso, + rx_param->qos, + BT_AUDIO_DIR_SOURCE); + } + + if (tx_param != NULL) + { + struct bt_bap_iso *bap_iso = CONTAINER_OF(tx_param->stream->iso, + struct bt_bap_iso, chan); + + unicast_group_set_iso_stream_param(unicast_group, bap_iso, + tx_param->qos, + BT_AUDIO_DIR_SINK); + } + } + + /* Reconfigure the CIG based on the above new values */ + + err = bt_audio_cig_reconfigure(unicast_group); + if (err != 0) + { + LOG_DBG("bt_audio_cig_reconfigure failed: %d", err); + + /* Revert any changes above */ + + memcpy(&unicast_group->cig_param, &cig_param_backup, + sizeof(cig_param_backup)); + idx = 0U; + list_for_every_container(&unicast_group->streams, tmp_stream, _node) + { + struct bt_bap_iso *bap_iso = CONTAINER_OF(tmp_stream->iso, + struct bt_bap_iso, chan); + struct bt_iso_chan_qos *qos = bap_iso->chan.qos; + + memcpy(qos->rx, &rx_io_qos_backup[idx], + sizeof(rx_io_qos_backup[idx])); + memcpy(qos->tx, &tx_io_qos_backup[idx], + sizeof(tx_io_qos_backup[idx])); + IF_ENABLED(CONFIG_BT_ISO_TEST_PARAMS, + (qos->num_subevents = num_subevents_backup[idx])); + idx++; + } + + return err; + } + + return 0; +} + +int bt_bap_unicast_group_add_streams( + struct bt_bap_unicast_group *unicast_group, + struct bt_bap_unicast_group_stream_pair_param params[], size_t num_param) +{ + struct bt_bap_unicast_group_cig_param cig_param = + { + 0 + }; + + uint32_t source_pd = BT_BAP_PD_UNSET; + uint32_t sink_pd = BT_BAP_PD_UNSET; + struct bt_bap_stream *tmp_stream; + size_t total_stream_cnt; + struct bt_iso_cig *cig; + size_t num_added; + int err; + + if (unicast_group == NULL) + { + LOG_DBG("unicast_group is NULL"); + return -EINVAL; + } + + if (unicast_group->has_been_connected) + { + LOG_DBG("Cannot modify a unicast_group where a CIS has been " + "connected"); + return -EINVAL; + } + + if (params == NULL) + { + LOG_DBG("params is NULL"); + return -EINVAL; + } + + if (num_param == 0) + { + LOG_DBG("num_param is 0"); + return -EINVAL; + } + + total_stream_cnt = num_param; + list_for_every_container(&unicast_group->streams, tmp_stream, _node) + { + total_stream_cnt++; + } + + if (total_stream_cnt > UNICAST_GROUP_STREAM_CNT) + { + LOG_DBG("Too many streams provided: %u/%u", total_stream_cnt, + UNICAST_GROUP_STREAM_CNT); + return -EINVAL; + } + + for (size_t i = 0U; i < num_param; i++) + { + if (!valid_group_stream_pair_param(unicast_group, ¶ms[i], + &cig_param, + &sink_pd, + &source_pd)) + { + return -EINVAL; + } + } + + /* We can just check the CIG state to see if any streams have started as + * that would start the ISO connection procedure + */ + + cig = unicast_group->cig; + if (cig != NULL && cig->state != BT_ISO_CIG_STATE_CONFIGURED) + { + LOG_DBG("At least one unicast group stream is started"); + return -EBADMSG; + } + + for (num_added = 0U; num_added < num_param; num_added++) + { + struct bt_bap_unicast_group_stream_pair_param *stream_param; + + stream_param = ¶ms[num_added]; + + err = stream_pair_param_check(stream_param); + if (err < 0) + { + return err; + } + + err = unicast_group_add_stream_pair(unicast_group, stream_param); + if (err < 0) + { + LOG_DBG("unicast_group_add_stream failed: %d", err); + goto fail; + } + } + + err = bt_audio_cig_reconfigure(unicast_group); + if (err != 0) + { + LOG_DBG("bt_audio_cig_reconfigure failed: %d", err); + goto fail; + } + + return 0; + +fail: + + /* Restore group by removing the newly added streams */ + + while (num_added > 0U) + { + num_added--; + unicast_group_del_stream_pair(unicast_group, ¶ms[num_added]); + } + + return err; +} + +int bt_bap_unicast_group_delete(struct bt_bap_unicast_group *unicast_group) +{ + struct bt_bap_stream *stream; + + if (unicast_group == NULL) + { + LOG_DBG("unicast_group is NULL"); + return -EINVAL; + } + + list_for_every_container(&unicast_group->streams, stream, _node) + { +/* If a stream has an endpoint, it is not ready to be removed + * from a group, as it is not in an idle state + */ + + if (stream->ep != NULL) + { + LOG_DBG("stream %p is not released", stream); + return -EINVAL; + } + } + + if (unicast_group->cig != NULL) + { + const int err = unicast_client_cig_terminate(unicast_group); + + if (err != 0) + { + LOG_DBG("unicast_client_cig_terminate failed with err %d", err); + + return err; + } + } + + unicast_group_free(unicast_group); + + return 0; +} + +int bt_bap_unicast_group_foreach_stream( + struct bt_bap_unicast_group *unicast_group, + bt_bap_unicast_group_foreach_stream_func_t func, void *user_data) +{ + struct bt_bap_stream *stream; + struct bt_bap_stream *next; + + if (unicast_group == NULL) + { + LOG_DBG("unicast_group is NULL"); + return -EINVAL; + } + + if (func == NULL) + { + LOG_DBG("func is NULL"); + return -EINVAL; + } + + list_for_every_container_safe(&unicast_group->streams, stream, next, _node) + { + if (!func(stream, user_data)) + { + return -ECANCELED; + } + } + + return 0; +} + +int bt_bap_unicast_group_get_info( + const struct bt_bap_unicast_group *unicast_group, + struct bt_bap_unicast_group_info *info) +{ + if (unicast_group == NULL) + { + LOG_DBG("unicast_group is NULL"); + return -EINVAL; + } + + if (info == NULL) + { + LOG_DBG("info is NULL"); + return -EINVAL; + } + + info->sink_pd = unicast_group->sink_pd; + info->source_pd = unicast_group->source_pd; + info->c_to_p_interval = unicast_group->cig_param.c_to_p_interval; + info->p_to_c_interval = unicast_group->cig_param.p_to_c_interval; + info->c_to_p_latency = unicast_group->cig_param.c_to_p_latency; + info->p_to_c_latency = unicast_group->cig_param.p_to_c_latency; + + /* The framing is stored as the ISO value, so it is converted back to the + * BAP value + */ + + if (unicast_group->cig_param.framing == BT_ISO_FRAMING_FRAMED) + { + info->framing = BT_BAP_QOS_CFG_FRAMING_FRAMED; + } + else + { + info->framing = BT_BAP_QOS_CFG_FRAMING_UNFRAMED; + } + + info->packing = unicast_group->cig_param.packing; + info->has_been_connected = unicast_group->has_been_connected; + IF_ENABLED(CONFIG_BT_ISO_TEST_PARAMS, ( + { + info->c_to_p_ft = unicast_group->cig_param.c_to_p_ft; + info->p_to_c_ft = unicast_group->cig_param.p_to_c_ft; + info->iso_interval = unicast_group->cig_param.iso_interval; + } + + )); + + return 0; +} + +int bt_bap_unicast_client_config(struct bt_bap_stream *stream, + const struct bt_audio_codec_cfg *codec_cfg) +{ + struct bt_audio_codec_cfg *ep_codec_cfg; + struct bt_bap_ep *ep = stream->ep; + struct bt_ascs_config_op *op; + struct net_buf_simple *buf; + int err; + + LOG_DBG("stream %p", stream); + + if (stream == NULL) + { + LOG_DBG("Stream is NULL"); + + return -EINVAL; + } + + if (stream->conn == NULL) + { + LOG_DBG("Stream %p does not have a connection", stream); + + return -ENOTCONN; + } + + if (stream->ep == NULL || &stream->ep->codec_cfg != stream->codec_cfg) + { + LOG_DBG("Invalid stream %p and stream->ep %p combination", stream, + stream->ep); + + return -EINVAL; + } + + buf = bt_bap_unicast_client_ep_create_pdu(stream->conn, BT_ASCS_CONFIG_OP); + if (buf == NULL) + { + LOG_DBG("Could not create PDU"); + return -EBUSY; + } + + op = net_buf_simple_add(buf, sizeof(*op)); + op->num_ases = 0x01u; + + err = unicast_client_ep_config(ep, buf, codec_cfg); + if (err != 0) + { + return err; + } + + err = bt_bap_unicast_client_ep_send(stream->conn, ep, buf); + if (err != 0) + { + return err; + } + + /* Some values like path_id, ctlr_transcode, target_latency and target_phy + * are not updated via the notification, so store them here immediately + */ + + ep_codec_cfg = &stream->ep->codec_cfg; + ep_codec_cfg->path_id = codec_cfg->path_id; + ep_codec_cfg->ctlr_transcode = codec_cfg->ctlr_transcode; + ep_codec_cfg->target_latency = codec_cfg->target_latency; + ep_codec_cfg->target_phy = codec_cfg->target_phy; + + return 0; +} + +int bt_bap_unicast_client_qos(struct bt_conn *conn, + struct bt_bap_unicast_group *group) +{ + struct bt_bap_stream *stream; + struct bt_ascs_config_op *op; + struct net_buf_simple *buf; + struct bt_bap_ep *ep; + bool conn_stream_found; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -ENOTCONN; + } + + if (group == NULL) + { + LOG_DBG("group is NULL"); + + return -EINVAL; + } + + /* Used to determine if a stream for the supplied connection pointer + * was actually found + */ + + conn_stream_found = false; + list_for_every_container(&group->streams, stream, _node) + { + if (stream->conn == conn) + { + conn_stream_found = true; + continue; + } + + if (stream->ep == NULL) + { + /* Only consider configured streams */ + + continue; + } + + ep = stream->ep; + DEBUGASSERT(ep->state < BT_BAP_EP_STATE_QOS_CONFIGURED || + ep->dir == BT_AUDIO_DIR_SINK || + ep->dir == BT_AUDIO_DIR_SOURCE); + } + + if (!conn_stream_found) + { + LOG_DBG("No streams in the group %p for conn %p", group, conn); + return -EINVAL; + } + + list_for_every_container(&group->streams, stream, _node) + { + const struct bt_bap_ep *paired_ep; + struct bt_bap_qos_cfg qos; + + DEBUGASSERT(stream->group == group); + DEBUGASSERT(stream->iso != NULL); + + if (stream->conn != conn) + { + /* Channel not part of this ACL, skip */ + + continue; + } + + ep = stream->ep; + if (ep == NULL) + { + LOG_DBG("stream->ep is NULL"); + return -EINVAL; + } + + if (ep->iso == NULL) + { + LOG_DBG("stream->ep->iso is NULL"); + return -EINVAL; + } + + if (ep->iso->chan.iso == NULL) + { + LOG_DBG("stream->ep->iso->chan.iso is NULL"); + return -EINVAL; + } + + paired_ep = bt_bap_iso_get_paired_ep(ep); + if (paired_ep != NULL && paired_ep->stream != NULL && + paired_ep->stream->conn != NULL && + paired_ep->stream->conn != stream->conn) + { + LOG_DBG("Misconfigured group %p: Stream %p has endpoint %p for conn " + "%p " + "paired with endpoint %p for conn %p", + group, stream, ep, stream->conn, paired_ep, + paired_ep->stream->conn); + + return -EINVAL; + } + + unicast_client_iso_param_to_qos_cfg(stream, &qos); + if (bt_bap_stream_verify_qos(stream, &qos) != BT_BAP_ASCS_REASON_NONE) + { + return -EINVAL; + } + + if (stream->iso == NULL) + { + /* This can only happen if the stream was somehow added + * to a group without the bap_iso being bound to it + */ + + LOG_ERR("Could not find bap_iso for stream %p", stream); + return -EINVAL; + } + } + + /* Generate the control point write */ + + buf = bt_bap_unicast_client_ep_create_pdu(conn, BT_ASCS_QOS_OP); + if (buf == NULL) + { + LOG_DBG("Could not create PDU"); + return -EBUSY; + } + + op = net_buf_simple_add(buf, sizeof(*op)); + + (void)memset(op, 0, sizeof(*op)); + ep = NULL; /* Needed to find the control point handle */ + list_for_every_container(&group->streams, stream, _node) + { + struct bt_bap_qos_cfg qos; + + if (stream->conn != conn) + { + /* Channel not part of this ACL, skip */ + + continue; + } + + op->num_ases++; + + unicast_client_iso_param_to_qos_cfg(stream, &qos); + err = unicast_client_add_qos(stream->ep, buf, &qos); + if (err != 0) + { + LOG_DBG("[%zu] Failed to add QoS parameters for stream %p: %d", + op->num_ases, stream, err); + + return err; + } + + if (ep == NULL) + { + ep = stream->ep; + } + } + + err = bt_bap_unicast_client_ep_send(conn, ep, buf); + if (err != 0) + { + LOG_DBG("Could not send config QoS: %d", err); + + return err; + } + + return 0; +} + +int bt_bap_unicast_client_enable( + struct bt_bap_stream *stream, const uint8_t meta[], size_t meta_len) +{ + struct bt_bap_ep *ep = stream->ep; + struct net_buf_simple *buf; + struct bt_ascs_enable_op *req; + int err; + + LOG_DBG("stream %p", stream); + + if (stream->conn == NULL) + { + LOG_DBG("Stream %p does not have a connection", stream); + + return -ENOTCONN; + } + + buf = bt_bap_unicast_client_ep_create_pdu(stream->conn, BT_ASCS_ENABLE_OP); + if (buf == NULL) + { + LOG_DBG("Could not create PDU"); + return -EBUSY; + } + + req = net_buf_simple_add(buf, sizeof(*req)); + req->num_ases = 0x01u; + + err = unicast_client_ep_enable(ep, buf, meta, meta_len); + if (err != 0) + { + return err; + } + + return bt_bap_unicast_client_ep_send(stream->conn, ep, buf); +} + +int bt_bap_unicast_client_metadata( + struct bt_bap_stream *stream, const uint8_t meta[], size_t meta_len) +{ + struct bt_bap_ep *ep = stream->ep; + struct net_buf_simple *buf; + struct bt_ascs_enable_op *req; + int err; + + LOG_DBG("stream %p", stream); + + if (stream->conn == NULL) + { + LOG_DBG("Stream %p does not have a connection", stream); + + return -ENOTCONN; + } + + buf = bt_bap_unicast_client_ep_create_pdu(stream->conn, + BT_ASCS_METADATA_OP); + if (buf == NULL) + { + LOG_DBG("Could not create PDU"); + return -EBUSY; + } + + req = net_buf_simple_add(buf, sizeof(*req)); + req->num_ases = 0x01u; + + err = unicast_client_ep_metadata(ep, buf, meta, meta_len); + if (err != 0) + { + return err; + } + + return bt_bap_unicast_client_ep_send(stream->conn, ep, buf); +} + +int bt_bap_unicast_client_connect(struct bt_bap_stream *stream) +{ + struct bt_iso_connect_param param; + struct bt_iso_chan *iso_chan; + enum bt_iso_state iso_state; + + LOG_DBG("stream %p", stream); + + if (stream == NULL) + { + LOG_DBG("stream is NULL"); + + return -EINVAL; + } + + iso_chan = bt_bap_stream_iso_chan_get(stream); + if (iso_chan == NULL) + { + LOG_DBG("iso_chan is NULL"); + + return -EINVAL; + } + + LOG_DBG("stream %p iso %p", stream, iso_chan); + + param.acl = stream->conn; + param.iso_chan = iso_chan; + + iso_state = iso_chan->state; + if (iso_state == BT_ISO_STATE_DISCONNECTED) + { + const int err = bt_iso_chan_connect(¶m, 1); + + if (err == 0 || err == -EBUSY) + { + /* Expected / known return values */ + + return err; + } + + /* unknown return value */ + + LOG_DBG("Unexpected err %d from bt_iso_chan_connect", err); + + return -ENOEXEC; + } + else if (iso_state == BT_ISO_STATE_CONNECTING || + iso_state == BT_ISO_STATE_CONNECTED) + { + LOG_DBG("iso_chan %p already connecting or connected (%u)", iso_chan, + iso_state); + + return -EALREADY; + } + + LOG_DBG("iso_chan %p in invalid state state (%u), cannot " + "connect", iso_chan, + iso_state); + + return -EBADMSG; +} + +int bt_bap_unicast_client_start(struct bt_bap_stream *stream) +{ + struct bt_bap_ep *ep = stream->ep; + int err; + + LOG_DBG("stream %p", stream); + + if (stream->conn == NULL) + { + LOG_DBG("Stream %p does not have a connection", stream); + + return -ENOTCONN; + } + + /* As per the ASCS spec, only source streams can be started by the client */ + + if (ep->dir == BT_AUDIO_DIR_SINK) + { + LOG_DBG("Stream %p is not a source stream", stream); + + return -EINVAL; + } + + ep->receiver_ready = true; + + err = unicast_client_send_start(ep); + if (err != 0) + { + LOG_DBG("Failed to send start: %d", err); + + /* TBD: Should we release the stream then? */ + + ep->receiver_ready = false; + + return err; + } + + return 0; +} + +int bt_bap_unicast_client_disable(struct bt_bap_stream *stream) +{ + struct bt_bap_ep *ep = stream->ep; + struct net_buf_simple *buf; + struct bt_ascs_disable_op *req; + int err; + + LOG_DBG("stream %p", stream); + + if (stream->conn == NULL) + { + LOG_DBG("Stream %p does not have a connection", stream); + + return -ENOTCONN; + } + + buf = bt_bap_unicast_client_ep_create_pdu(stream->conn, + BT_ASCS_DISABLE_OP); + if (buf == NULL) + { + LOG_DBG("Could not create PDU"); + return -EBUSY; + } + + req = net_buf_simple_add(buf, sizeof(*req)); + req->num_ases = 0x01u; + + err = unicast_client_ep_disable(ep, buf); + if (err != 0) + { + return err; + } + + return bt_bap_unicast_client_ep_send(stream->conn, ep, buf); +} + +int bt_bap_unicast_client_stop(struct bt_bap_stream *stream) +{ + struct bt_bap_ep *ep = stream->ep; + struct net_buf_simple *buf; + struct bt_ascs_start_op *req; + int err; + + LOG_DBG("stream %p", stream); + + if (stream->conn == NULL) + { + LOG_DBG("Stream %p does not have a connection", stream); + + return -ENOTCONN; + } + + buf = bt_bap_unicast_client_ep_create_pdu(stream->conn, BT_ASCS_STOP_OP); + if (buf == NULL) + { + LOG_DBG("Could not create PDU"); + return -EBUSY; + } + + req = net_buf_simple_add(buf, sizeof(*req)); + req->num_ases = 0x00u; + + /* When initiated by the client, valid only if Direction field + * parameter value = 0x02 (Server is Audio Source) + */ + + if (ep->dir == BT_AUDIO_DIR_SOURCE) + { + err = unicast_client_ep_stop(ep, buf); + if (err != 0) + { + return err; + } + + req->num_ases++; + + err = bt_bap_unicast_client_ep_send(stream->conn, ep, buf); + if (err != 0) + { + /* Return expected error directly */ + + if (err == -ENOTCONN || err == -ENOMEM) + { + return err; + } + + LOG_DBG("bt_bap_unicast_client_ep_send failed with unexpected " + "error %d", + err); + + return -ENOEXEC; + } + } + + return 0; +} + +int bt_bap_unicast_client_release(struct bt_bap_stream *stream) +{ + struct bt_bap_unicast_client_ep *client_ep; + struct bt_bap_ep *ep = stream->ep; + struct net_buf_simple *buf; + struct bt_ascs_disable_op *req; + int err; + int len; + + LOG_DBG("stream %p", stream); + + if (ep->state == BT_BAP_EP_STATE_IDLE) + { + bt_bap_stream_reset(stream); + return 0; + } + + if (stream->conn == NULL) + { + LOG_DBG("Stream %p does not have a connection", stream); + + return -ENOTCONN; + } + + buf = bt_bap_unicast_client_ep_create_pdu(stream->conn, + BT_ASCS_RELEASE_OP); + if (buf == NULL) + { + LOG_DBG("Could not create PDU"); + return -EBUSY; + } + + req = net_buf_simple_add(buf, sizeof(*req)); + req->num_ases = 0x01u; + len = buf->len; + + err = unicast_client_ep_release(ep, buf); + if (err != 0) + { + return err; + } + + /* Check if anything needs to be send */ + + if (len == buf->len) + { + return 0; + } + + err = bt_bap_unicast_client_ep_send(stream->conn, ep, buf); + if (err != 0) + { + return err; + } + + client_ep = CONTAINER_OF(ep, struct bt_bap_unicast_client_ep, ep); + client_ep->release_requested = true; + + return 0; +} + +static uint8_t unicast_client_cp_discover_func(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *discover) +{ + struct bt_gatt_chrc *chrc; + uint16_t value_handle; + + if (!attr) + { + LOG_ERR("Unable to find ASE Control Point"); + + unicast_client_discover_complete(conn, BT_ATT_ERR_ATTRIBUTE_NOT_FOUND); + return BT_GATT_ITER_STOP; + } + + chrc = attr->user_data; + value_handle = chrc->value_handle; + memset(discover, 0, sizeof(*discover)); + + LOG_DBG("conn %p attr %p handle 0x%04x", conn, attr, value_handle); + + unicast_client_ep_set_cp(conn, value_handle); + + return BT_GATT_ITER_STOP; +} + +static int unicast_client_ase_cp_discover(struct bt_conn *conn) +{ + struct unicast_client *client = &uni_cli_insts[bt_conn_index(conn)]; + + LOG_DBG("conn %p", conn); + + client->disc_params.uuid = cp_uuid; + client->disc_params.func = unicast_client_cp_discover_func; + client->disc_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; + client->disc_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + client->disc_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; + + return bt_gatt_discover(conn, &client->disc_params); +} + +static uint8_t unicast_client_ase_read_func( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *read, + const void *data, uint16_t length) +{ + uint16_t handle = read->single.handle; + struct unicast_client *client; + struct net_buf_simple *buf; + struct bt_bap_ep *ep; + int cb_err; + + LOG_DBG("conn %p err 0x%02x len %u", conn, err, length); + + if (err != 0) + { + cb_err = err; + goto fail; + } + + LOG_DBG("handle 0x%04x", handle); + + client = &uni_cli_insts[bt_conn_index(conn)]; + buf = &client->net_buf; + + if (data != NULL) + { + if (net_buf_simple_tailroom(buf) < length) + { + LOG_DBG("Buffer full, invalid server response of size %u", + length + client->net_buf.len); + + cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + + goto fail; + } + + /* store data */ + + net_buf_simple_add_mem(buf, data, length); + + return BT_GATT_ITER_CONTINUE; + } + + memset(read, 0, sizeof(*read)); + + if (buf->len < sizeof(struct bt_ascs_ase_status)) + { + LOG_DBG("Read response too small (%u)", buf->len); + + cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + + goto fail; + } + + ep = unicast_client_ep_get(conn, client->dir, handle); + if (!ep) + { + /* The BAP spec declares that the unicast client shall subscribe to all + * ASEs. In case that we cannot support this due to memory + * restrictions, we should consider the discovery procedure as failing. + */ + + LOG_WRN("No space left to parse ASE"); + cb_err = -ENOMEM; + + goto fail; + } + + unicast_client_ep_set_status(conn, ep, buf, false); + cb_err = unicast_client_ep_subscribe(conn, ep); + if (cb_err != 0) + { + LOG_DBG("Failed to subscribe to ep %p: %d", ep, cb_err); + goto fail; + } + + reset_att_buf(client); + + unicast_client_notify_endpoint(conn, ep); + + cb_err = unicast_client_ase_discover(conn, handle); + if (cb_err != 0) + { + LOG_DBG("Failed to read ASE: %d", cb_err); + goto fail; + } + + return BT_GATT_ITER_STOP; + +fail: + unicast_client_discover_complete(conn, cb_err); + return BT_GATT_ITER_STOP; +} + +static bool any_ases_found(const struct unicast_client *client) +{ + /* We always allocate ases from 0 to X, so to verify if any sink or source + * ASEs have been found we can just check the first index + */ +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 + if (client->dir == BT_AUDIO_DIR_SINK && + client->snks[0].handle == BAP_HANDLE_UNUSED) + { + LOG_DBG("No sink ASEs found"); + return false; + } +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT > 0 */ +#if CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 + + if (client->dir == BT_AUDIO_DIR_SOURCE && + client->srcs[0].handle == BAP_HANDLE_UNUSED) + { + LOG_DBG("No source ASEs found"); + return false; + } +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT > 0 */ + + return true; +} + +static uint8_t unicast_client_ase_discover_cb( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *discover) +{ + struct unicast_client *client; + struct bt_gatt_chrc *chrc; + uint16_t value_handle; + int err; + + client = &uni_cli_insts[bt_conn_index(conn)]; + + if (attr == NULL) + { + if (!any_ases_found(client)) + { + unicast_client_discover_complete(conn, + BT_ATT_ERR_ATTRIBUTE_NOT_FOUND); + } + else + { + err = unicast_client_ase_cp_discover(conn); + if (err != 0) + { + LOG_ERR("Unable to discover ASE Control Point"); + + unicast_client_discover_complete(conn, err); + } + } + + return BT_GATT_ITER_STOP; + } + + chrc = attr->user_data; + value_handle = chrc->value_handle; + memset(discover, 0, sizeof(*discover)); + + LOG_DBG("conn %p attr %p handle 0x%04x dir %s", conn, attr, value_handle, + bt_audio_dir_to_str(client->dir)); + + client->read_params.func = unicast_client_ase_read_func; + client->read_params.handle_count = 1U; + client->read_params.single.handle = value_handle; + client->read_params.single.offset = 0U; + + err = bt_gatt_read(conn, &client->read_params); + if (err != 0) + { + LOG_DBG("Failed to read ASEs: %d", err); + + unicast_client_discover_complete(conn, err); + } + + return BT_GATT_ITER_STOP; +} + +static int unicast_client_ase_discover(struct bt_conn *conn, + uint16_t start_handle) +{ + struct unicast_client *client = &uni_cli_insts[bt_conn_index(conn)]; + + LOG_DBG("conn %p ", conn); + + if (client->dir == BT_AUDIO_DIR_SINK) + { + client->disc_params.uuid = ase_snk_uuid; + } + else if (client->dir == BT_AUDIO_DIR_SOURCE) + { + client->disc_params.uuid = ase_src_uuid; + } + else + { + return -EINVAL; + } + + client->disc_params.func = unicast_client_ase_discover_cb; + client->disc_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; + client->disc_params.start_handle = start_handle; + client->disc_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + + return bt_gatt_discover(conn, &client->disc_params); +} + +static uint8_t unicast_client_pacs_avail_ctx_read_func( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *read, + const void *data, uint16_t length) +{ + struct bt_pacs_context context; + struct net_buf_simple buf; + int cb_err; + + memset(read, 0, sizeof(*read)); + + LOG_DBG("conn %p err 0x%02x len %u", conn, err, length); + + if (err != 0 || data == NULL || length != sizeof(context)) + { + LOG_DBG("Could not read available context: %d, %p, %u", err, data, + length); + + if (err == BT_ATT_ERR_SUCCESS) + { + err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + + unicast_client_discover_complete(conn, err); + + return BT_GATT_ITER_STOP; + } + + net_buf_simple_init_with_data(&buf, (void *)data, length); + context.snk = net_buf_simple_pull_le16(&buf); + context.src = net_buf_simple_pull_le16(&buf); + + LOG_DBG("sink context %u, source context %u", context.snk, context.src); + + unicast_client_notify_available_contexts(conn, context.snk, context.src); + + /* Read ASE instances */ + + cb_err = unicast_client_ase_discover(conn, BT_ATT_FIRST_ATTRIBUTE_HANDLE); + if (cb_err != 0) + { + LOG_ERR("Unable to read ASE: %d", cb_err); + + unicast_client_discover_complete(conn, cb_err); + } + + return BT_GATT_ITER_STOP; +} + +static uint8_t unicast_client_pacs_avail_ctx_notify_cb(struct bt_conn *conn, + struct bt_gatt_subscribe_params *params, + const void *data, + uint16_t length) +{ + struct bt_pacs_context context; + struct net_buf_simple buf; + + LOG_DBG("conn %p len %u", conn, length); + + if (!data) + { + LOG_DBG("Unsubscribed"); + params->value_handle = BAP_HANDLE_UNUSED; + return BT_GATT_ITER_STOP; + } + + if (length != sizeof(context)) + { + LOG_ERR("Available context notification incorrect size: %u", length); + return BT_GATT_ITER_STOP; + } + + net_buf_simple_init_with_data(&buf, (void *)data, length); + context.snk = net_buf_simple_pull_le16(&buf); + context.src = net_buf_simple_pull_le16(&buf); + + LOG_DBG("sink context %u, source context %u", context.snk, context.src); + + unicast_client_notify_available_contexts(conn, context.snk, context.src); + + return BT_GATT_ITER_CONTINUE; +} + +static int unicast_client_pacs_avail_ctx_read(struct bt_conn *conn, + uint16_t handle) +{ + struct unicast_client *client = &uni_cli_insts[bt_conn_index(conn)]; + + LOG_DBG("conn %p", conn); + + client->read_params.func = unicast_client_pacs_avail_ctx_read_func; + client->read_params.handle_count = 1U; + client->read_params.single.handle = handle; + client->read_params.single.offset = 0U; + + return bt_gatt_read(conn, &client->read_params); +} + +static uint8_t unicast_client_pacs_avail_ctx_discover_cb( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *discover) +{ + uint8_t index = bt_conn_index(conn); + const struct bt_gatt_chrc *chrc; + uint8_t chrc_properties; + uint16_t value_handle; + int err; + + if (!attr) + { + /* If available_ctx is not found, we terminate the discovery as + * the characteristic is mandatory + */ + + LOG_ERR("Unable to find available PAC context"); + + unicast_client_discover_complete(conn, BT_ATT_ERR_ATTRIBUTE_NOT_FOUND); + + return BT_GATT_ITER_STOP; + } + + chrc = attr->user_data; + chrc_properties = chrc->properties; + value_handle = chrc->value_handle; + memset(discover, 0, sizeof(*discover)); + + LOG_DBG("conn %p attr %p handle 0x%04x", conn, attr, value_handle); + + if (chrc_properties & BT_GATT_CHRC_NOTIFY) + { + struct bt_gatt_subscribe_params *sub_params; + + sub_params = &uni_cli_insts[index].avail_ctx_subscribe; + + if (sub_params->value_handle == 0U) + { + LOG_DBG("Subscribing to handle %u", value_handle); + sub_params->value_handle = value_handle; + sub_params->ccc_handle = BT_GATT_AUTO_DISCOVER_CCC_HANDLE; + sub_params->end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + sub_params->disc_params = &uni_cli_insts[index].avail_ctx_cc_disc; + sub_params->notify = unicast_client_pacs_avail_ctx_notify_cb; + sub_params->value = BT_GATT_CCC_NOTIFY; + atomic_set_bit(sub_params->flags, BT_GATT_SUBSCRIBE_FLAG_VOLATILE); + + err = bt_gatt_subscribe(conn, sub_params); + if (err != 0) + { + (void)memset(sub_params, 0, sizeof(*sub_params)); + LOG_DBG("Failed to subscribe to avail_ctx: %d", err); + + unicast_client_discover_complete(conn, err); + + return BT_GATT_ITER_STOP; + } + } + + /* else already subscribed */ + } + else + { + LOG_DBG("Invalid chrc->properties: %u", chrc_properties); + /* If the characteristic is not subscribable we terminate the + * discovery as BT_GATT_CHRC_NOTIFY is mandatory + */ + + unicast_client_discover_complete(conn, BT_ATT_ERR_NOT_SUPPORTED); + + return BT_GATT_ITER_STOP; + } + + err = unicast_client_pacs_avail_ctx_read(conn, value_handle); + if (err != 0) + { + LOG_DBG("Failed to read PACS avail_ctx: %d", err); + + unicast_client_discover_complete(conn, err); + } + + return BT_GATT_ITER_STOP; +} + +static int unicast_client_pacs_avail_ctx_discover(struct bt_conn *conn) +{ + struct unicast_client *client = &uni_cli_insts[bt_conn_index(conn)]; + + LOG_DBG("conn %p", conn); + + client->disc_params.uuid = pacs_avail_ctx_uuid; + client->disc_params.func = unicast_client_pacs_avail_ctx_discover_cb; + client->disc_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; + client->disc_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + client->disc_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; + + return bt_gatt_discover(conn, &client->disc_params); +} + +static uint8_t unicast_client_pacs_location_read_func( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *read, + const void *data, uint16_t length) +{ + struct unicast_client *client; + struct net_buf_simple buf; + uint32_t location; + int cb_err; + + memset(read, 0, sizeof(*read)); + + client = &uni_cli_insts[bt_conn_index(conn)]; + + LOG_DBG("conn %p err 0x%02x len %u", conn, err, length); + + if (err != 0 || data == NULL || length != sizeof(location)) + { + LOG_DBG("Unable to read PACS location for dir %s: %u, %p, %u", + bt_audio_dir_to_str(client->dir), err, data, length); + + if (err == BT_ATT_ERR_SUCCESS) + { + err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + + unicast_client_discover_complete(conn, err); + + return BT_GATT_ITER_STOP; + } + + net_buf_simple_init_with_data(&buf, (void *)data, length); + location = net_buf_simple_pull_le32(&buf); + + LOG_DBG("dir %s loc %X", bt_audio_dir_to_str(client->dir), location); + + unicast_client_notify_location(conn, client->dir, + (enum bt_audio_location)location); + + /* Read available contexts */ + + cb_err = unicast_client_pacs_avail_ctx_discover(conn); + if (cb_err != 0) + { + LOG_ERR("Unable to read available contexts: %d", cb_err); + + unicast_client_discover_complete(conn, cb_err); + } + + return BT_GATT_ITER_STOP; +} + +static uint8_t unicast_client_pacs_location_notify_cb(struct bt_conn *conn, + struct bt_gatt_subscribe_params *params, + const void *data, + uint16_t length) +{ + struct net_buf_simple buf; + enum bt_audio_dir dir; + uint32_t location; + + LOG_DBG("conn %p len %u", conn, length); + + if (!data) + { + LOG_DBG("Unsubscribed"); + params->value_handle = BAP_HANDLE_UNUSED; + return BT_GATT_ITER_STOP; + } + + if (length != sizeof(location)) + { + LOG_ERR("Location notification incorrect size: %u", length); + return BT_GATT_ITER_STOP; + } + + if (params == &uni_cli_insts[bt_conn_index(conn)].snk_loc_subscribe) + { + dir = BT_AUDIO_DIR_SINK; + } + else if (params == &uni_cli_insts[bt_conn_index(conn)].src_loc_subscribe) + { + dir = BT_AUDIO_DIR_SOURCE; + } + else + { + LOG_ERR("Invalid notification"); + + return BT_GATT_ITER_CONTINUE; + } + + net_buf_simple_init_with_data(&buf, (void *)data, length); + location = net_buf_simple_pull_le32(&buf); + + LOG_DBG("dir %s loc %X", bt_audio_dir_to_str(dir), location); + + unicast_client_notify_location(conn, + dir, + (enum bt_audio_location)location); + + return BT_GATT_ITER_CONTINUE; +} + +static int unicast_client_pacs_location_read(struct bt_conn *conn, + uint16_t handle) +{ + struct unicast_client *client = &uni_cli_insts[bt_conn_index(conn)]; + + LOG_DBG("conn %p", conn); + + client->read_params.func = unicast_client_pacs_location_read_func; + client->read_params.handle_count = 1U; + client->read_params.single.handle = handle; + client->read_params.single.offset = 0U; + + return bt_gatt_read(conn, &client->read_params); +} + +static uint8_t unicast_client_pacs_location_discover_cb(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *discover) +{ + uint8_t index = bt_conn_index(conn); + const struct bt_gatt_chrc *chrc; + uint16_t value_handle; + int err; + + if (!attr) + { + /* If location is not found, we just continue reading the + * available contexts, as location is optional. + */ + + err = unicast_client_pacs_avail_ctx_discover(conn); + if (err != 0) + { + LOG_ERR("Unable to read available contexts: %d", err); + + unicast_client_discover_complete(conn, err); + } + + return BT_GATT_ITER_STOP; + } + + chrc = attr->user_data; + value_handle = chrc->value_handle; + memset(discover, 0, sizeof(*discover)); + + LOG_DBG("conn %p attr %p handle 0x%04x", conn, attr, value_handle); + + if (chrc->properties & BT_GATT_CHRC_NOTIFY) + { + const struct unicast_client *client = &uni_cli_insts[index]; + struct bt_gatt_subscribe_params *sub_params; + + if (client->dir == BT_AUDIO_DIR_SINK) + { + sub_params = &uni_cli_insts[index].snk_loc_subscribe; + } + else + { + sub_params = &uni_cli_insts[index].src_loc_subscribe; + } + + if (sub_params->value_handle == 0U) + { + sub_params->value_handle = value_handle; + sub_params->ccc_handle = BT_GATT_AUTO_DISCOVER_CCC_HANDLE; + sub_params->end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + sub_params->disc_params = &uni_cli_insts[index].loc_cc_disc; + sub_params->notify = unicast_client_pacs_location_notify_cb; + sub_params->value = BT_GATT_CCC_NOTIFY; + atomic_set_bit(sub_params->flags, BT_GATT_SUBSCRIBE_FLAG_VOLATILE); + + err = bt_gatt_subscribe(conn, sub_params); + if (err != 0) + { + (void)memset(sub_params, 0, sizeof(*sub_params)); + LOG_ERR("Failed to subscribe to location: %d", err); + } + } + } + + err = unicast_client_pacs_location_read(conn, value_handle); + if (err != 0) + { + LOG_DBG("Failed to read PACS location: %d", err); + + unicast_client_discover_complete(conn, err); + } + + return BT_GATT_ITER_STOP; +} + +static int unicast_client_pacs_location_discover(struct bt_conn *conn) +{ + struct unicast_client *client = &uni_cli_insts[bt_conn_index(conn)]; + + LOG_DBG("conn %p dir %s", conn, bt_audio_dir_to_str(client->dir)); + + if (client->dir == BT_AUDIO_DIR_SINK) + { + client->disc_params.uuid = pacs_snk_loc_uuid; + } + else if (client->dir == BT_AUDIO_DIR_SOURCE) + { + client->disc_params.uuid = pacs_src_loc_uuid; + } + else + { + return -EINVAL; + } + + client->disc_params.func = unicast_client_pacs_location_discover_cb; + client->disc_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; + client->disc_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + client->disc_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; + + return bt_gatt_discover(conn, &client->disc_params); +} + +static void unicast_client_notify_supp_contexts( + struct bt_conn *conn, enum bt_audio_context snk_ctx, + enum bt_audio_context src_ctx) +{ + struct bt_bap_unicast_client_cb *listener; + struct bt_bap_unicast_client_cb *next; + + list_for_every_container_safe(&unicast_client_cbs, listener, next, _node) + { + if (listener->supported_contexts != NULL) + { + listener->supported_contexts(conn, snk_ctx, src_ctx); + } + } +} + +static uint8_t unicast_client_pacs_supp_ctx_notify_cb(struct bt_conn *conn, + struct bt_gatt_subscribe_params *params, + const void *data, + uint16_t length) +{ + struct bt_pacs_context context; + struct net_buf_simple buf; + + LOG_DBG("conn %p len %u", conn, length); + + if (data == NULL) + { + LOG_DBG("Unsubscribed"); + params->value_handle = BAP_HANDLE_UNUSED; + return BT_GATT_ITER_STOP; + } + + if (length != sizeof(context)) + { + LOG_ERR("Supported context notification incorrect size: %u", length); + return BT_GATT_ITER_STOP; + } + + net_buf_simple_init_with_data(&buf, (void *)data, length); + context.snk = net_buf_simple_pull_le16(&buf); + context.src = net_buf_simple_pull_le16(&buf); + + LOG_DBG("sink context %u, source context %u", context.snk, context.src); + + unicast_client_notify_supp_contexts(conn, context.snk, context.src); + + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t unicast_client_pacs_supp_context_read_func( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *read, + const void *data, uint16_t length) +{ + struct bt_pacs_context context; + struct net_buf_simple buf; + int cb_err; + + memset(read, 0, sizeof(*read)); + + LOG_DBG("conn %p err 0x%02x len %u", conn, err, length); + + if (err != 0 || data == NULL || length != sizeof(context)) + { + LOG_DBG("Could not read supported context: %d, %p, %u", err, data, + length); + + if (err == BT_ATT_ERR_SUCCESS) + { + err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + + unicast_client_discover_complete(conn, err); + + return BT_GATT_ITER_STOP; + } + + net_buf_simple_init_with_data(&buf, (void *)data, length); + context.snk = net_buf_simple_pull_le16(&buf); + context.src = net_buf_simple_pull_le16(&buf); + + LOG_DBG("sink context %u, source context %u", context.snk, context.src); + + unicast_client_notify_supp_contexts(conn, context.snk, context.src); + + /* Read ASE instances */ + + cb_err = unicast_client_pacs_location_discover(conn); + if (cb_err != 0) + { + LOG_ERR("Unable to read PACS location: %d", cb_err); + + unicast_client_discover_complete(conn, cb_err); + } + + return BT_GATT_ITER_STOP; +} + +static int unicast_client_pacs_supp_ctx_read(struct bt_conn *conn, + uint16_t handle) +{ + struct unicast_client *client = &uni_cli_insts[bt_conn_index(conn)]; + + LOG_DBG("conn %p", conn); + + client->read_params.func = unicast_client_pacs_supp_context_read_func; + client->read_params.handle_count = 1U; + client->read_params.single.handle = handle; + client->read_params.single.offset = 0U; + + return bt_gatt_read(conn, &client->read_params); +} + +static uint8_t +unicast_client_pacs_supp_context_discover_cb(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *discover) +{ + const struct bt_gatt_chrc *chrc; + uint8_t chrc_properties; + uint16_t value_handle; + int err; + + if (attr == NULL) + { + LOG_ERR("Unable to find supported PAC context"); + + unicast_client_discover_complete(conn, BT_ATT_ERR_ATTRIBUTE_NOT_FOUND); + + return BT_GATT_ITER_STOP; + } + + chrc = attr->user_data; + chrc_properties = chrc->properties; + value_handle = chrc->value_handle; + memset(discover, 0, sizeof(*discover)); + + LOG_DBG("conn %p attr %p handle 0x%04x", conn, attr, value_handle); + + /* Supported contexts are optionally notifiable */ + + if ((chrc_properties & BT_GATT_CHRC_NOTIFY) != 0U) + { + const uint8_t conn_index = bt_conn_index(conn); + struct bt_gatt_subscribe_params *sub_params; + + sub_params = &uni_cli_insts[conn_index].supp_ctx_subscribe; + + if (sub_params->value_handle == 0U) + { + LOG_DBG("Subscribing to handle %u", value_handle); + sub_params->value_handle = value_handle; + sub_params->ccc_handle = BT_GATT_AUTO_DISCOVER_CCC_HANDLE; + sub_params->end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + sub_params->disc_params = + &uni_cli_insts[conn_index].supp_ctx_cc_disc; + sub_params->notify = unicast_client_pacs_supp_ctx_notify_cb; + sub_params->value = BT_GATT_CCC_NOTIFY; + atomic_set_bit(sub_params->flags, BT_GATT_SUBSCRIBE_FLAG_VOLATILE); + + err = bt_gatt_subscribe(conn, sub_params); + if (err != 0) + { + (void)memset(sub_params, 0, sizeof(*sub_params)); + LOG_DBG("Failed to subscribe to supp_ctx: %d", err); + + unicast_client_discover_complete(conn, err); + + return BT_GATT_ITER_STOP; + } + } + + /* else already subscribed */ + } + + err = unicast_client_pacs_supp_ctx_read(conn, value_handle); + if (err != 0) + { + LOG_DBG("Failed to read supported contexts: %d", err); + + unicast_client_discover_complete(conn, err); + } + + return BT_GATT_ITER_STOP; +} + +static int unicast_client_pacs_supp_context_discover(struct bt_conn *conn) +{ + struct unicast_client *client = &uni_cli_insts[bt_conn_index(conn)]; + + LOG_DBG("conn %p", conn); + + client->disc_params.uuid = pacs_supp_context_uuid; + client->disc_params.func = unicast_client_pacs_supp_context_discover_cb; + client->disc_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; + client->disc_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; + client->disc_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + + return bt_gatt_discover(conn, &client->disc_params); +} + +static uint8_t unicast_client_read_func(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *read, + const void *data, + uint16_t length) +{ + uint16_t handle = read->single.handle; + struct unicast_client *client; + struct bt_pacs_read_rsp *rsp; + struct net_buf_simple *buf; + int cb_err = err; + uint8_t i; + + LOG_DBG("conn %p err 0x%02x len %u", conn, err, length); + + if (cb_err != BT_ATT_ERR_SUCCESS) + { + goto fail; + } + + LOG_DBG("handle 0x%04x", handle); + + client = &uni_cli_insts[bt_conn_index(conn)]; + buf = &client->net_buf; + + if (data != NULL) + { + if (net_buf_simple_tailroom(buf) < length) + { + LOG_DBG("Buffer full, invalid server response of size %u", + length + client->net_buf.len); + + cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + + goto fail; + } + + /* store data */ + + net_buf_simple_add_mem(buf, data, length); + + return BT_GATT_ITER_CONTINUE; + } + + memset(read, 0, sizeof(*read)); + + if (buf->len < sizeof(*rsp)) + { + LOG_DBG("Read response too small (%u)", buf->len); + + cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + + goto fail; + } + + rsp = net_buf_simple_pull_mem(buf, sizeof(*rsp)); + + /* If no PAC was found don't bother discovering ASE and ASE CP */ + + if (!rsp->num_pac) + { + goto fail; + } + + for (i = 0U; i < rsp->num_pac; i++) + { + struct bt_audio_codec_cap codec_cap; + struct bt_pac_codec *pac_codec; + struct bt_pac_ltv_data *meta; + struct bt_pac_ltv_data *cc; + void *cc_ltv; + void *meta_ltv; + + LOG_DBG("pac #%u/%u", i + 1, rsp->num_pac); + + if (buf->len < sizeof(*pac_codec)) + { + LOG_ERR("Malformed PAC: remaining len %u expected %zu", buf->len, + sizeof(*pac_codec)); + break; + } + + pac_codec = net_buf_simple_pull_mem(buf, sizeof(*pac_codec)); + + if (buf->len < sizeof(*cc)) + { + LOG_ERR("Malformed PAC: remaining len %u expected %zu", buf->len, + sizeof(*cc)); + break; + } + + cc = net_buf_simple_pull_mem(buf, sizeof(*cc)); + if (buf->len < cc->len) + { + LOG_ERR("Malformed PAC: remaining len %u expected %zu", buf->len, + cc->len); + break; + } + + cc_ltv = net_buf_simple_pull_mem(buf, cc->len); + + if (buf->len < sizeof(*meta)) + { + LOG_ERR("Malformed PAC: remaining len %u expected %zu", buf->len, + sizeof(*meta)); + break; + } + + meta = net_buf_simple_pull_mem(buf, sizeof(*meta)); + if (buf->len < meta->len) + { + LOG_ERR("Malformed PAC: remaining len %u expected %u", buf->len, + meta->len); + break; + } + + meta_ltv = net_buf_simple_pull_mem(buf, meta->len); + + if (unicast_client_set_codec_cap(pac_codec->id, + sys_le16_to_cpu(pac_codec->cid), + sys_le16_to_cpu(pac_codec->vid), + cc_ltv, cc->len, meta_ltv, + meta->len, &codec_cap)) + { + LOG_ERR("Unable to parse Codec"); + break; + } + + LOG_DBG("codec 0x%02x capabilities len %u meta len %u ", codec_cap.id, + codec_cap.data_len, + codec_cap.meta_len); + + unicast_client_notify_pac_record(conn, &codec_cap); + } + + reset_att_buf(client); + + if (i != rsp->num_pac) + { + LOG_DBG("Failed to process all PAC records (%u/%u)", i, rsp->num_pac); + cb_err = BT_ATT_ERR_INVALID_PDU; + goto fail; + } + + /* Read PACS contexts */ + + cb_err = unicast_client_pacs_supp_context_discover(conn); + if (cb_err != 0) + { + LOG_ERR("Unable to read PACS context: %d", cb_err); + goto fail; + } + + return BT_GATT_ITER_STOP; + +fail: + unicast_client_discover_complete(conn, cb_err); + return BT_GATT_ITER_STOP; +} + +static uint8_t unicast_client_pac_discover_cb( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *discover) +{ + struct unicast_client *client = &uni_cli_insts[bt_conn_index(conn)]; + struct bt_gatt_chrc *chrc; + uint16_t value_handle; + int err; + + if (attr == NULL) + { + LOG_DBG("Unable to find %s PAC", bt_audio_dir_to_str(client->dir)); + + unicast_client_discover_complete(conn, BT_ATT_ERR_ATTRIBUTE_NOT_FOUND); + + return BT_GATT_ITER_STOP; + } + + chrc = attr->user_data; + value_handle = chrc->value_handle; + memset(discover, 0, sizeof(*discover)); + + LOG_DBG("conn %p attr %p handle 0x%04x dir %s", conn, attr, value_handle, + bt_audio_dir_to_str(client->dir)); + + /* TODO: Subscribe to PAC */ + + /* Reset to use for long read */ + + reset_att_buf(client); + + client->read_params.func = unicast_client_read_func; + client->read_params.handle_count = 1U; + client->read_params.single.handle = value_handle; + client->read_params.single.offset = 0U; + + err = bt_gatt_read(conn, &client->read_params); + if (err != 0) + { + LOG_DBG("Failed to read PAC records: %d", err); + + unicast_client_discover_complete(conn, err); + } + + return BT_GATT_ITER_STOP; +} + +static void unicast_client_disconnected(struct bt_conn *conn, uint8_t reason) +{ + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + LOG_DBG("conn %p reason 0x%02x", conn, reason); + + unicast_client_ep_reset(conn, reason); +} + +BT_CONN_CB_DEFINE(conn_cbs) = +{ + .disconnected = unicast_client_disconnected, +}; + +int bt_bap_unicast_client_discover(struct bt_conn *conn, + enum bt_audio_dir dir) +{ + struct unicast_client *client; + struct bt_conn_info info; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + return -EINVAL; + } + + err = bt_conn_get_info(conn, &info); + DEBUGASSERT(err == 0); + + if (info.role != BT_CONN_ROLE_CENTRAL) + { + LOG_DBG("Invalid conn role: %u, shall be central", info.role); + + return -EINVAL; + } + + if (bt_audio_security_check(conn) != BT_ATT_ERR_SUCCESS) + { + LOG_DBG("Invalid conn %p for discovery", conn); + + return -EINVAL; + } + + if (dir != BT_AUDIO_DIR_SINK && dir != BT_AUDIO_DIR_SOURCE) + { + LOG_DBG("Invalid dir %d", dir); + return -EINVAL; + } + + client = &uni_cli_insts[bt_conn_index(conn)]; + if (atomic_test_and_set_bit(client->flags, UNICAST_CLIENT_FLAG_BUSY)) + { + LOG_DBG("Client connection is busy"); + return -EBUSY; + } + + atomic_set_bit(client->flags, UNICAST_CLIENT_FLAG_DISCOVERY_IN_PROGRESS); + + if (dir == BT_AUDIO_DIR_SINK) + { + client->disc_params.uuid = snk_uuid; + } + else + { + client->disc_params.uuid = src_uuid; + } + + client->disc_params.func = unicast_client_pac_discover_cb; + client->disc_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; + client->disc_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; + client->disc_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + + err = bt_gatt_discover(conn, &client->disc_params); + if (err != 0) + { + atomic_clear_bit(client->flags, + UNICAST_CLIENT_FLAG_DISCOVERY_IN_PROGRESS); + atomic_clear_bit(client->flags, UNICAST_CLIENT_FLAG_BUSY); + + /* Report expected possible errors */ + + if (err == -ENOTCONN || err == -ENOMEM) + { + return err; + } + + LOG_DBG("Unexpected err %d from bt_gatt_discover", err); + + return -ENOEXEC; + } + + client->dir = dir; + + return 0; +} + +int bt_bap_unicast_client_register_cb(struct bt_bap_unicast_client_cb *cb) +{ + if (cb == NULL) + { + LOG_DBG("cb is NULL"); + return -EINVAL; + } + + if (list_find(&unicast_client_cbs, &cb->_node, NULL)) + { + return -EEXIST; + } + + list_add_tail(&unicast_client_cbs, &cb->_node); + + return 0; +} + +int bt_bap_unicast_client_unregister_cb(struct bt_bap_unicast_client_cb *cb) +{ + if (cb == NULL) + { + LOG_DBG("cb was NULL"); + return -EINVAL; + } + + if (!list_find_and_remove(&unicast_client_cbs, &cb->_node)) + { + LOG_DBG("cb was not registered"); + return -EALREADY; + } + + return 0; +} diff --git a/libs/zblue/audio/bap_unicast_client_internal.h b/libs/zblue/audio/bap_unicast_client_internal.h new file mode 100644 index 0000000000000..67c3de0411753 --- /dev/null +++ b/libs/zblue/audio/bap_unicast_client_internal.h @@ -0,0 +1,56 @@ +/**************************************************************************** + * libs/zblue/audio/bap_unicast_client_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* @file + * @brief Internal APIs for BAP + + * Copyright (c) 2021-2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include + +int bt_bap_unicast_client_config(struct bt_bap_stream *stream, + const struct bt_audio_codec_cfg *codec_cfg); + +int bt_bap_unicast_client_qos(struct bt_conn *conn, + struct bt_bap_unicast_group *group); + +int bt_bap_unicast_client_enable( + struct bt_bap_stream *stream, const uint8_t meta[], size_t meta_len); + +int bt_bap_unicast_client_metadata( + struct bt_bap_stream *stream, const uint8_t meta[], size_t meta_len); + +int bt_bap_unicast_client_disable(struct bt_bap_stream *stream); + +int bt_bap_unicast_client_start(struct bt_bap_stream *stream); +int bt_bap_unicast_client_connect(struct bt_bap_stream *stream); + +int bt_bap_unicast_client_stop(struct bt_bap_stream *stream); + +int bt_bap_unicast_client_release(struct bt_bap_stream *stream); + +struct net_buf_simple *bt_bap_unicast_client_ep_create_pdu( + struct bt_conn *conn, uint8_t op); + +int bt_bap_unicast_client_ep_send(struct bt_conn *conn, struct bt_bap_ep *ep, + struct net_buf_simple *buf); + +struct bt_bap_iso *bt_bap_unicast_client_new_audio_iso(void); diff --git a/libs/zblue/audio/bap_unicast_server.c b/libs/zblue/audio/bap_unicast_server.c new file mode 100644 index 0000000000000..4ae5ff292a1ce --- /dev/null +++ b/libs/zblue/audio/bap_unicast_server.c @@ -0,0 +1,318 @@ +/**************************************************************************** + * libs/zblue/audio/bap_unicast_server.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Bluetooth Audio Unicast Server */ + +/* Copyright (c) 2021-2023 Nordic Semiconductor ASA + * Copyright (c) 2024 Demant A/S + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "ascs_internal.h" +#include "bap_iso.h" +#include "bap_endpoint.h" +#include "pacs_internal.h" + +LOG_MODULE_REGISTER(bt_bap_unicast_server, + CONFIG_BT_BAP_UNICAST_SERVER_LOG_LEVEL); + +static const struct bt_bap_unicast_server_cb *unicast_server_cb; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int ascs_config_cb( + struct bt_conn *conn, const struct bt_bap_ep *ep, enum bt_audio_dir dir, + const struct bt_audio_codec_cfg *codec_cfg, struct bt_bap_stream **stream, + struct bt_bap_qos_cfg_pref *const pref, struct bt_bap_ascs_rsp *rsp) +{ + if (unicast_server_cb != NULL && unicast_server_cb->config != NULL) + { + return unicast_server_cb->config(conn, + ep, + dir, + codec_cfg, + stream, + pref, + + rsp); + } + else + { + return -EOPNOTSUPP; + } +} + +static int ascs_reconfig_cb( + struct bt_bap_stream *stream, enum bt_audio_dir dir, + const struct bt_audio_codec_cfg *codec_cfg, + struct bt_bap_qos_cfg_pref *const pref, struct bt_bap_ascs_rsp *rsp) +{ + if (unicast_server_cb != NULL && unicast_server_cb->reconfig != NULL) + { + return unicast_server_cb->reconfig(stream, dir, codec_cfg, pref, rsp); + } + else + { + return -EOPNOTSUPP; + } +} + +static int ascs_qos_cb( + struct bt_bap_stream *stream, const struct bt_bap_qos_cfg *qos, + struct bt_bap_ascs_rsp *rsp) +{ + if (unicast_server_cb != NULL && unicast_server_cb->qos != NULL) + { + return unicast_server_cb->qos(stream, qos, rsp); + } + else + { + return -EOPNOTSUPP; + } +} + +static int ascs_enable_cb( + struct bt_bap_stream *stream, const uint8_t meta[], size_t meta_len, + struct bt_bap_ascs_rsp *rsp) +{ + if (unicast_server_cb != NULL && unicast_server_cb->enable != NULL) + { + return unicast_server_cb->enable(stream, meta, meta_len, rsp); + } + else + { + return -EOPNOTSUPP; + } +} + +static int ascs_start_cb(struct bt_bap_stream *stream, + struct bt_bap_ascs_rsp *rsp) +{ + if (unicast_server_cb != NULL && unicast_server_cb->start != NULL) + { + return unicast_server_cb->start(stream, rsp); + } + else + { + return -EOPNOTSUPP; + } +} + +static int ascs_metadata_cb( + struct bt_bap_stream *stream, const uint8_t meta[], size_t meta_len, + struct bt_bap_ascs_rsp *rsp) +{ + if (unicast_server_cb != NULL && unicast_server_cb->metadata != NULL) + { + return unicast_server_cb->metadata(stream, meta, meta_len, rsp); + } + else + { + return -EOPNOTSUPP; + } +} + +static int ascs_disable_cb(struct bt_bap_stream *stream, + struct bt_bap_ascs_rsp *rsp) +{ + if (unicast_server_cb != NULL && unicast_server_cb->disable != NULL) + { + return unicast_server_cb->disable(stream, rsp); + } + else + { + return -EOPNOTSUPP; + } +} + +static int ascs_stop_cb(struct bt_bap_stream *stream, + struct bt_bap_ascs_rsp *rsp) +{ + if (unicast_server_cb != NULL && unicast_server_cb->stop != NULL) + { + return unicast_server_cb->stop(stream, rsp); + } + else + { + return -EOPNOTSUPP; + } +} + +static int ascs_release_cb(struct bt_bap_stream *stream, + struct bt_bap_ascs_rsp *rsp) +{ + if (unicast_server_cb != NULL && unicast_server_cb->release != NULL) + { + return unicast_server_cb->release(stream, rsp); + } + else + { + return -EOPNOTSUPP; + } +} + +int bt_bap_unicast_server_register( + const struct bt_bap_unicast_server_register_param *param) +{ + static struct bt_ascs_cb ascs_cb = + { + .config = ascs_config_cb, + .reconfig = ascs_reconfig_cb, + .qos = ascs_qos_cb, + .enable = ascs_enable_cb, + .start = ascs_start_cb, + .metadata = ascs_metadata_cb, + .disable = ascs_disable_cb, + .stop = ascs_stop_cb, + .release = ascs_release_cb, + }; + + if (param == NULL) + { + LOG_DBG("param is NULL"); + return -EINVAL; + } + + struct bt_ascs_register_param ascs_param = + { + .snk_cnt = param->snk_cnt, + .src_cnt = param->src_cnt, + .cb = &ascs_cb, + }; + + return bt_ascs_register(&ascs_param); +} + +int bt_bap_unicast_server_unregister(void) +{ + if (unicast_server_cb != NULL) + { + LOG_DBG("Callbacks are still registered"); + return -EAGAIN; + } + + return bt_ascs_unregister(); +} + +int bt_bap_unicast_server_register_cb( + const struct bt_bap_unicast_server_cb *cb) +{ + if (cb == NULL) + { + LOG_DBG("cb is NULL"); + return -EINVAL; + } + + if (unicast_server_cb != NULL) + { + LOG_DBG("callback structure already registered"); + return -EALREADY; + } + + unicast_server_cb = cb; + + return 0; +} + +int bt_bap_unicast_server_unregister_cb( + const struct bt_bap_unicast_server_cb *cb) +{ + if (unicast_server_cb == NULL) + { + LOG_DBG("no callback is registered"); + return -EALREADY; + } + + if (cb == NULL) + { + LOG_DBG("cb is NULL"); + return -EINVAL; + } + + if (unicast_server_cb != cb) + { + LOG_DBG("callback structure not registered"); + return -EINVAL; + } + + unicast_server_cb = NULL; + + return 0; +} + +int bt_bap_unicast_server_reconfig(struct bt_bap_stream *stream, + const struct + bt_audio_codec_cfg *codec_cfg) +{ + return bt_ascs_reconfig_ase(stream->ep, codec_cfg); +} + +int bt_bap_unicast_server_start(struct bt_bap_stream *stream) +{ + return bt_ascs_start_ase(stream->ep); +} + +int bt_bap_unicast_server_metadata( + struct bt_bap_stream *stream, const uint8_t meta[], size_t meta_len) +{ + return bt_ascs_metadata_ase(stream->ep, meta, meta_len); +} + +int bt_bap_unicast_server_disable(struct bt_bap_stream *stream) +{ + return bt_ascs_disable_ase(stream->ep); +} + +int bt_bap_unicast_server_release(struct bt_bap_stream *stream) +{ + return bt_ascs_release_ase(stream->ep); +} + +int bt_bap_unicast_server_config_ase( + struct bt_conn *conn, struct bt_bap_stream *stream, + const struct bt_audio_codec_cfg *codec_cfg, + const struct bt_bap_qos_cfg_pref *qos_pref) +{ + return bt_ascs_config_ase(conn, stream, codec_cfg, qos_pref); +} + +int bt_bap_unicast_server_foreach_ep(struct bt_conn *conn, + bt_bap_ep_func_t func, void *user_data) +{ + return bt_ascs_foreach_ep(conn, func, user_data); +} + +bool bt_bap_unicast_server_has_ep(const struct bt_bap_ep *ep) +{ + return bt_ascs_has_ep(ep); +} + +struct bt_conn *bt_bap_unicast_server_ep_get_conn(const struct bt_bap_ep *ep) +{ + return bt_ascs_ep_get_conn(ep); +} diff --git a/libs/zblue/audio/bap_unicast_server.h b/libs/zblue/audio/bap_unicast_server.h new file mode 100644 index 0000000000000..1c810f347faf4 --- /dev/null +++ b/libs/zblue/audio/bap_unicast_server.h @@ -0,0 +1,32 @@ +/**************************************************************************** + * libs/zblue/audio/bap_unicast_server.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Bluetooth Audio Unicast Server */ + +/* Copyright (c) 2021-2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include + +int bt_bap_unicast_server_reconfig(struct bt_bap_stream *stream, + const struct bt_audio_codec_cfg *codec_cfg); +int bt_bap_unicast_server_start(struct bt_bap_stream *stream); +int bt_bap_unicast_server_metadata(struct bt_bap_stream *stream, + const uint8_t meta[], + size_t meta_len); +int bt_bap_unicast_server_disable(struct bt_bap_stream *stream); +int bt_bap_unicast_server_release(struct bt_bap_stream *stream); diff --git a/libs/zblue/audio/cap_acceptor.c b/libs/zblue/audio/cap_acceptor.c new file mode 100644 index 0000000000000..b6ccba5d8c17e --- /dev/null +++ b/libs/zblue/audio/cap_acceptor.c @@ -0,0 +1,163 @@ +/**************************************************************************** + * libs/zblue/audio/cap_acceptor.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "cap_internal.h" + +LOG_MODULE_REGISTER(bt_cap_acceptor, CONFIG_BT_CAP_ACCEPTOR_LOG_LEVEL); + +#if defined(CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER) + +static struct bt_gatt_attr svc_attrs[] = +{ + BT_GATT_PRIMARY_SERVICE(BT_UUID_CAS), BT_GATT_INCLUDE_SERVICE(NULL) /* To be overwritten */ +}; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bt_cap_acceptor_register( + const struct bt_csip_set_member_register_param *param, + struct bt_csip_set_member_svc_inst **svc_inst) +{ + static struct bt_gatt_service cas; + int err; + + if (param == NULL) + { + LOG_DBG("param is NULL"); + + return -EINVAL; + } + + if (param->set_size == 0U) + { + LOG_DBG("param->set_size shall be non-zero"); + return -EINVAL; + } + + if (param->rank == 0U) + { + LOG_DBG("param->rank shall be non-zero"); + return -EINVAL; + } + + if (svc_inst == NULL) + { + LOG_DBG("svc_inst is NULL"); + + return -EINVAL; + } + + err = bt_csip_set_member_register(param, svc_inst); + if (err != 0) + { + LOG_DBG("Failed to register CSIP"); + return err; + } + + cas = (struct bt_gatt_service)BT_GATT_SERVICE(svc_attrs); + + /* Overwrite the include definition with the CSIP */ + + cas.attrs[1].user_data = bt_csip_set_member_svc_decl_get(*svc_inst); + + err = bt_gatt_service_register(&cas); + if (err != 0) + { + const int csip_err = bt_csip_set_member_unregister(*svc_inst); + + if (csip_err) + { + LOG_ERR("Failed to unregister CSIS: %d", csip_err); + } + + cas.attrs[1].user_data = NULL; + LOG_DBG("Failed to register CAS"); + + return err; + } + + return 0; +} + +#else /* CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER */ + +BT_GATT_SERVICE_DEFINE(cas_svc, BT_GATT_PRIMARY_SERVICE(BT_UUID_CAS) + +/* TODO: Add support for included CSIP */ + +); + +#endif /* CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER */ + +bool bt_cap_acceptor_ccid_exist(const struct bt_conn *conn, uint8_t ccid) +{ + if (IS_ENABLED(CONFIG_BT_TBS_CLIENT_CCID) && + bt_tbs_client_get_by_ccid(conn, + ccid) != NULL) + { + return true; + } + + /* TODO: check mcs */ + + return false; +} + +bool bt_cap_acceptor_ccids_exist(const struct bt_conn *conn, + const uint8_t ccids[], + uint8_t ccid_cnt) +{ + for (uint8_t i = 0U; i < ccid_cnt; i++) + { + const uint8_t ccid = ccids[i]; + + if (!bt_cap_acceptor_ccid_exist(conn, ccid)) + { + LOG_DBG("CCID %u is unknown", ccid); + + /* TBD: + * Should we reject the Metadata? + * + * Should unknown CCIDs trigger a discovery procedure for TBS or + * MCS? + * + * Or should we just accept as is, and then let the application + * decide? + */ + + return false; + } + } + + /* This will also return true if the ccid_cnt is 0 which is intended */ + + return true; +} diff --git a/libs/zblue/audio/cap_commander.c b/libs/zblue/audio/cap_commander.c new file mode 100644 index 0000000000000..4d027aafda113 --- /dev/null +++ b/libs/zblue/audio/cap_commander.c @@ -0,0 +1,3392 @@ +/**************************************************************************** + * libs/zblue/audio/cap_commander.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "audio_internal.h" +#include "bap_endpoint.h" +#include "bap_internal.h" +#include "cap_internal.h" +#include "csip_internal.h" + +LOG_MODULE_REGISTER(bt_cap_commander, CONFIG_BT_CAP_COMMANDER_LOG_LEVEL); + +#include "common/bt_str.h" + +static const struct bt_cap_commander_cb *cap_cb; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bt_cap_commander_register_cb(const struct bt_cap_commander_cb *cb) +{ + if (cb == NULL) + { + LOG_DBG("cb is NULL"); + + return -EINVAL; + } + + if (cap_cb != NULL) + { + LOG_DBG("callbacks already registered"); + + return -EALREADY; + } + + cap_cb = cb; + + return 0; +} + +int bt_cap_commander_unregister_cb(const struct bt_cap_commander_cb *cb) +{ + if (cb == NULL) + { + LOG_DBG("cb is NULL"); + return -EINVAL; + } + + if (cap_cb != cb) + { + LOG_DBG("cb is not registered"); + return -EINVAL; + } + + cap_cb = NULL; + + return 0; +} + +static void +cap_commander_discover_complete(struct bt_conn *conn, + int err, + const struct bt_csip_set_coordinator_set_member *member, + const struct bt_csip_set_coordinator_csis_inst *csis_inst) +{ + if (cap_cb && cap_cb->discovery_complete) + { + cap_cb->discovery_complete(conn, err, member, csis_inst); + } +} + +int bt_cap_commander_discover(struct bt_conn *conn) +{ + if (conn == NULL) + { + LOG_DBG("NULL conn"); + return -EINVAL; + } + + return bt_cap_common_discover(conn, cap_commander_discover_complete); +} + +#if defined(CONFIG_BT_BAP_BROADCAST_ASSISTANT) + +static struct bt_cap_commander_proc_param * +broadcast_reception_get_next_start_proc_param( + struct bt_cap_common_proc *active_proc) +{ + struct bt_cap_commander_proc_param *proc_param = NULL; + + for (size_t i = 0U; i < active_proc->proc_cnt; i++) + { + const struct cap_broadcast_reception_start *start_param = + &active_proc->proc_param.commander[ + i].broadcast_reception_start; + + if (start_param->write_pending) + { + /* If we have a write pending on any device, we will always wait + * before starting the next write request to minimize the amount of + * ATT buffers required + * + * If we cannot get an ATT buffer immediately for the next TX, the + * procedure aborts. + */ + + return NULL; + } + + if (proc_param == NULL && !start_param->started_recvd && + !start_param->write_pending && + !start_param->waiting_for_notification) + { + proc_param = &active_proc->proc_param.commander[i]; + } + } + + return proc_param; +} + +static struct bt_cap_commander_proc_param * +broadcast_reception_get_next_stop_proc_param( + struct bt_cap_common_proc *active_proc) +{ + struct bt_cap_commander_proc_param *proc_param = NULL; + + for (size_t i = 0U; i < active_proc->proc_cnt; i++) + { + const struct cap_broadcast_reception_stop *stop_param = + &active_proc->proc_param.commander[i].broadcast_reception_stop; + + const enum bt_cap_common_subproc_type subproc_type = + active_proc->subproc_type; + + if (stop_param->write_pending) + { + /* If we have a write pending on any device, we will always wait + * before starting the next write request to minimize the amount of + * ATT buffers required + * + * If we cannot get an ATT buffer immediately for the next TX, the + * procedure aborts. + */ + + return NULL; + } + + /* If none are ready for the next write request, we just return NULL */ + + if (subproc_type == BT_CAP_COMMON_SUBPROC_TYPE_STOP_SRC) + { + if (proc_param == NULL && !stop_param->stopped_recvd && + !stop_param->waiting_for_notification) + { + proc_param = &active_proc->proc_param.commander[i]; + } + } + else + { + /* BT_CAP_COMMON_SUBPROC_TYPE_REM_SRC */ + + DEBUGASSERT(subproc_type == BT_CAP_COMMON_SUBPROC_TYPE_REM_SRC); + if (proc_param == NULL && !stop_param->removed_recvd && + !stop_param->waiting_for_notification) + { + proc_param = &active_proc->proc_param.commander[i]; + } + } + } + + return proc_param; +} + +/** + * @brief Gets the next stream for the active procedure. + * + * It will find the first proc_param that isn't completed nor waiting for a + * write response or a notification. It will also consider if any other + * procedure is in progress, and will only allow one active write request at + * any one time to keep the ATT buffer requirements low + * + * Returns NULL if there are no proc_param available + */ + +static struct bt_cap_commander_proc_param * +broadcast_reception_get_next_proc_param( + struct bt_cap_common_proc *active_proc) +{ + const enum bt_cap_common_proc_type proc_type = active_proc->proc_type; + struct bt_cap_commander_proc_param *proc_param = NULL; + + if (proc_type == BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_START) + { + proc_param = + broadcast_reception_get_next_start_proc_param(active_proc); + } + else if (proc_type == BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_STOP) + { + proc_param = broadcast_reception_get_next_stop_proc_param(active_proc); + } + else + { + DEBUGASSERT(false); + } + + return proc_param; +} + +static size_t broadcast_reception_get_start_proc_done_cnt( + struct bt_cap_common_proc *active_proc) +{ + size_t proc_done_cnt = 0U; + + /* To support state changes by the server, we cannot rely simply on the + * number of BAP procedures we have initiated. For the start CAP procedures + * we use the states to determine how far we are. + */ + + for (size_t i = 0U; i < active_proc->proc_cnt; i++) + { + struct bt_cap_commander_proc_param *proc_param = + &active_proc->proc_param.commander[i]; + struct cap_broadcast_reception_start *start_param = + &proc_param->broadcast_reception_start; + + if (start_param->started_recvd && + !start_param->waiting_for_notification && + !start_param->write_pending) + { + proc_done_cnt++; + } + else if (active_proc->failed_conn != NULL && + proc_param->conn == active_proc->failed_conn) + { + /* Mark failed connection as done to trigger the + * bt_cap_common_proc_all_handled check for + * bt_cap_common_proc_is_aborted + */ + + proc_done_cnt++; + } + else + { + /* no op */ + } + } + + return proc_done_cnt; +} + +static size_t broadcast_reception_get_stop_proc_done_cnt( + struct bt_cap_common_proc *active_proc) +{ + const enum bt_cap_common_subproc_type subproc_type = + active_proc->subproc_type; + size_t proc_done_cnt = 0U; + + /* To support state changes by the server, we cannot rely simply on the + * number of BAP procedures we have initiated. For the stop CAP procedures + * we use the states to determine how far we are. + */ + + for (size_t i = 0U; i < active_proc->proc_cnt; i++) + { + struct bt_cap_commander_proc_param *proc_param = + &active_proc->proc_param.commander[i]; + struct cap_broadcast_reception_stop *stop_param = + &proc_param->broadcast_reception_stop; + + if (active_proc->failed_conn != NULL && + proc_param->conn == active_proc->failed_conn) + { + /* Mark failed connection as done to trigger the + * bt_cap_common_proc_all_handled check for + * bt_cap_common_proc_is_aborted + */ + + proc_done_cnt++; + } + else if (stop_param->waiting_for_notification || + stop_param->write_pending) + { + continue; + } + else if (subproc_type == BT_CAP_COMMON_SUBPROC_TYPE_STOP_SRC) + { + if (stop_param->stopped_recvd) + { + proc_done_cnt++; + } + } + else + { + /* BT_CAP_COMMON_SUBPROC_TYPE_REM_SRC */ + + DEBUGASSERT(subproc_type == BT_CAP_COMMON_SUBPROC_TYPE_REM_SRC); + if (stop_param->removed_recvd) + { + proc_done_cnt++; + } + } + } + + return proc_done_cnt; +} + +static void broadcast_reception_update_proc_done_cnt( + struct bt_cap_common_proc *active_proc) +{ + const enum bt_cap_common_subproc_type subproc_type = + active_proc->subproc_type; + const enum bt_cap_common_proc_type proc_type = active_proc->proc_type; + size_t proc_done_cnt = 0U; + + /* To support state changes by the server, we cannot rely simply on the + * number of BAP procedures we have initiated. For the start and stop CAP + * procedures we use the states to determine how far we are. + */ + + if (proc_type == BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_START) + { + proc_done_cnt = + broadcast_reception_get_start_proc_done_cnt(active_proc); + } + else if (proc_type == BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_STOP) + { + proc_done_cnt = + broadcast_reception_get_stop_proc_done_cnt(active_proc); + } + else + { + DEBUGASSERT(false); + } + + DEBUGASSERT(proc_done_cnt >= active_proc->proc_done_cnt && + proc_done_cnt <= active_proc->proc_cnt); + + LOG_DBG("proc %d subproc %d: %zu/%zu (was %zu/%zu)", proc_type, + subproc_type, + proc_done_cnt, + active_proc->proc_cnt, active_proc->proc_done_cnt, + active_proc->proc_cnt); + + active_proc->proc_done_cnt = proc_done_cnt; +} + +static struct bt_cap_commander_proc_param * +get_proc_param_by_conn(struct bt_cap_common_proc *active_proc, + const struct bt_conn *conn) +{ + for (size_t i = 0U; i < active_proc->proc_cnt; i++) + { + if (active_proc->proc_param.commander[i].conn == conn) + { + return &active_proc->proc_param.commander[i]; + } + } + + return NULL; +} + +static int broadcast_reception_start_add_src( + struct bt_cap_common_proc *active_proc, + struct bt_cap_commander_proc_param *proc_param) +{ + struct cap_broadcast_reception_start *start_param = + &proc_param->broadcast_reception_start; + struct bt_bap_broadcast_assistant_add_src_param add_src_param = + { + 0 + }; + + struct bt_conn *conn; + int err; + + DEBUGASSERT(!start_param->write_pending); + DEBUGASSERT(!start_param->started_recvd); + + conn = proc_param->conn; + + bt_addr_le_copy(&add_src_param.addr, &start_param->addr); + add_src_param.adv_sid = start_param->adv_sid; + add_src_param.broadcast_id = start_param->broadcast_id; + add_src_param.pa_sync = true; + add_src_param.pa_interval = start_param->pa_interval; + add_src_param.num_subgroups = start_param->num_subgroups; + add_src_param.subgroups = start_param->subgroups; + + active_proc->proc_initiated_cnt++; + start_param->write_pending = true; + start_param->waiting_for_notification = true; + + LOG_DBG("Adding source for conn %p", conn); + err = bt_bap_broadcast_assistant_add_src(conn, &add_src_param); + if (err != 0) + { + active_proc->proc_initiated_cnt--; + start_param->write_pending = false; + start_param->waiting_for_notification = false; + + LOG_DBG("Failed to add_src for conn %p: %d", conn, err); + } + + return err; +} + +static int broadcast_reception_stop_mod_src( + struct bt_cap_common_proc *active_proc, + struct bt_cap_commander_proc_param *proc_param) +{ + struct cap_broadcast_reception_stop *stop_param = + &proc_param->broadcast_reception_stop; + struct bt_bap_broadcast_assistant_mod_src_param mod_src_param = + { + 0 + }; + + struct bt_conn *conn; + int err; + + DEBUGASSERT(!stop_param->write_pending); + DEBUGASSERT(!stop_param->stopped_recvd); + + conn = proc_param->conn; + + mod_src_param.src_id = stop_param->src_id; + mod_src_param.pa_sync = false; + mod_src_param.pa_interval = BT_BAP_PA_INTERVAL_UNKNOWN; + mod_src_param.num_subgroups = stop_param->num_subgroups; + + mod_src_param.subgroups = stop_param->subgroups; + + active_proc->proc_initiated_cnt++; + stop_param->write_pending = true; + stop_param->waiting_for_notification = true; + + LOG_DBG("Modifying source with id 0x%02X for conn " + "%p", mod_src_param.src_id, + conn); + err = bt_bap_broadcast_assistant_mod_src(conn, &mod_src_param); + if (err != 0) + { + active_proc->proc_initiated_cnt--; + stop_param->write_pending = false; + stop_param->waiting_for_notification = false; + + LOG_DBG("Failed to mod_src for conn %p: %d", conn, err); + } + + return err; +} + +static int broadcast_reception_stop_rem_src( + struct bt_cap_common_proc *active_proc, + struct bt_cap_commander_proc_param *proc_param) +{ + struct cap_broadcast_reception_stop *stop_param = + &proc_param->broadcast_reception_stop; + struct bt_conn *conn; + int err; + + DEBUGASSERT(!stop_param->write_pending); + DEBUGASSERT(!stop_param->removed_recvd); + + conn = proc_param->conn; + + active_proc->proc_initiated_cnt++; + stop_param->write_pending = true; + stop_param->waiting_for_notification = true; + + LOG_DBG("Removing source with id 0x%02X for conn %p", stop_param->src_id, + conn); + err = bt_bap_broadcast_assistant_rem_src(conn, stop_param->src_id); + if (err != 0) + { + active_proc->proc_initiated_cnt--; + stop_param->write_pending = false; + stop_param->waiting_for_notification = false; + + LOG_DBG("Failed to rem_src for conn %p: %d", conn, err); + } + + return err; +} + +static void cap_commander_ba_add_src_cb(struct bt_conn *conn, int err) +{ + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + struct cap_broadcast_reception_start *start_param; + struct bt_cap_commander_proc_param *proc_param; + + if (!bt_cap_common_conn_in_active_proc(conn)) + { + /* State change happened outside of a procedure; ignore */ + + bt_cap_common_unlock_proc(); + + return; + } + + if (!bt_cap_common_proc_is_type( + BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_START)) + { + /* Unexpected callback - Ignore */ + + bt_cap_common_unlock_proc(); + return; + } + + proc_param = get_proc_param_by_conn(active_proc, conn); + DEBUGASSERT(proc_param != NULL); + start_param = &proc_param->broadcast_reception_start; + start_param->write_pending = false; + + if (err != 0) + { + LOG_DBG("Failed to add source on %p: %d", conn, err); + + bt_cap_common_abort_proc(conn, err); + } + else + { + LOG_DBG("Conn %p broadcast add source accepted", (void *)conn); + } + + broadcast_reception_update_proc_done_cnt(active_proc); + + if (bt_cap_common_proc_is_aborted()) + { + if (bt_cap_common_proc_all_handled()) + { + cap_commander_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return; + } + + if (!bt_cap_common_proc_is_done()) + { + proc_param = broadcast_reception_get_next_proc_param(active_proc); + + /* If proc_param is NULL we have to wait for a receive state + * notification before executing next write request + */ + + if (proc_param != NULL) + { + err = broadcast_reception_start_add_src(active_proc, proc_param); + if (err != 0) + { + LOG_DBG("broadcast_reception_start_add_src failed: %d", err); + + bt_cap_common_abort_proc(proc_param->conn, err); + cap_commander_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + } + else + { + bt_cap_common_unlock_proc(); + } + } + else + { + cap_commander_proc_complete(active_proc); + } +} + +static bool valid_broadcast_reception_start_param( + const struct bt_cap_commander_broadcast_reception_start_param *param) +{ + uint32_t total_bis_sync = 0U; + + if (param == NULL) + { + LOG_DBG("param is NULL"); + return false; + } + + if (param->count == 0) + { + LOG_DBG("Invalid param->count: %zu", param->count); + return false; + } + + if (param->count > CONFIG_BT_MAX_CONN) + { + LOG_DBG("param->count (%zu) is larger than CONFIG_BT_MAX_CONN (%d)", + param->count, + CONFIG_BT_MAX_CONN); + return false; + } + + if (param->param == NULL) + { + LOG_DBG("param->param is NULL"); + return false; + } + + for (size_t i = 0U; i < param->count; i++) + { + const struct + bt_cap_commander_broadcast_reception_start_member_param *start_param = + ¶m->param[i]; + const union bt_cap_set_member *member = ¶m->param[i].member; + const struct bt_conn *member_conn = + bt_cap_common_get_member_conn(param->type, + member); + + if (member == NULL) + { + LOG_DBG("param->param[%zu].member is NULL", i); + return false; + } + + if (member_conn == NULL) + { + LOG_DBG("Invalid param->param[%zu].member", i); + return false; + } + + if (start_param->addr.type > BT_ADDR_LE_RANDOM) + { + LOG_DBG("Invalid address type %u", start_param->addr.type); + return false; + } + + if (start_param->adv_sid > BT_GAP_SID_MAX) + { + LOG_DBG("param->param[%zu]->adv_sid is larger than %d", i, + BT_GAP_SID_MAX); + return false; + } + + if (!IN_RANGE(start_param->pa_interval, BT_GAP_PER_ADV_MIN_INTERVAL, + BT_GAP_PER_ADV_MAX_INTERVAL)) + { + LOG_DBG("param->param[%zu]->pa_interval is out of range", i); + return false; + } + + if (start_param->broadcast_id > BT_AUDIO_BROADCAST_ID_MAX) + { + LOG_DBG("param->param[%zu]->broadcast_id is larger than %u", i, + BT_AUDIO_BROADCAST_ID_MAX); + return false; + } + + if (start_param->num_subgroups == 0) + { + LOG_DBG("param->param[%zu]->num_subgroups is 0", i); + return false; + } + + if (start_param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) + { + LOG_DBG("Too many subgroups %u/%u", start_param->num_subgroups, + CONFIG_BT_BAP_BASS_MAX_SUBGROUPS); + + return false; + } + + total_bis_sync = 0U; + for (size_t j = 0U; j < start_param->num_subgroups; j++) + { + const struct bt_bap_bass_subgroup *param_subgroups = + &start_param->subgroups[j]; + + if (!valid_bis_syncs(param_subgroups->bis_sync)) + { + LOG_DBG("param->param[%zu].subgroup[%zu].bis_sync is invalid " + "%u", + i, j, + param_subgroups->bis_sync); + + return false; + } + + if ((total_bis_sync & param_subgroups->bis_sync) != 0) + { + LOG_DBG("param->param[%zu].subgroup[%zu].bis_sync 0x%08X has " + "duplicate bits (0x%08X) ", + i, j, param_subgroups->bis_sync, total_bis_sync); + + return false; + } + + total_bis_sync |= param_subgroups->bis_sync; + + if (param_subgroups->metadata_len > + CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE) + { + LOG_DBG("param->param[%zu].subgroup[%zu].metadata_len too " + "long " + "%u/%u", + i, j, param_subgroups->metadata_len, + CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE); + + return false; + } +#if defined(CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE) + + if (param_subgroups->metadata_len > 0 && + !bt_audio_valid_ltv(param_subgroups->metadata, + param_subgroups->metadata_len)) + { + LOG_DBG("param->param[%zu].subgroup[%zu].metadata not valid " + "LTV", + i, j); + } +#endif + } + + for (size_t j = 0U; j < i; j++) + { + const union bt_cap_set_member *other = ¶m->param[j].member; + + if (other == member) + { + LOG_DBG("param->members[%zu] (%p) is duplicated by " + "param->members[%zu] (%p)", + j, other, i, member); + return false; + } + } + } + + return true; +} + +int cap_commander_broadcast_reception_start( + struct bt_cap_common_proc *active_proc, + const struct bt_cap_commander_broadcast_reception_start_param *param) +{ + struct bt_cap_commander_proc_param *proc_param; + int err; + + cap_commander_register_broadcast_assistant_callbacks(); + + bt_cap_common_set_proc(BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_START, + param->count); + + for (size_t i = 0U; i < param->count; i++) + { + const struct + bt_cap_commander_broadcast_reception_start_member_param *member_param = + ¶m->param[i]; + struct bt_cap_commander_proc_param *stored_param; + struct bt_conn *member_conn = + bt_cap_common_get_member_conn(param->type, + &member_param->member); + + /* Perform extra check in case that connection state has changed */ + + if (member_conn == NULL) + { + LOG_DBG("Invalid param->members[%zu]", i); + + return -EINVAL; + } + + /* Store the necessary parameters as we cannot assume that the supplied + * parameters are kept valid TODO: consider putting this into a + * function + */ + + stored_param = &active_proc->proc_param.commander[i]; + stored_param->conn = member_conn; + + bt_addr_le_copy(&stored_param->broadcast_reception_start.addr, + &member_param->addr); + stored_param->broadcast_reception_start.write_pending = false; + stored_param->broadcast_reception_start.waiting_for_notification = + false; + stored_param->broadcast_reception_start.started_recvd = false; + stored_param->broadcast_reception_start.adv_sid = + member_param->adv_sid; + stored_param->broadcast_reception_start.broadcast_id = + member_param->broadcast_id; + stored_param->broadcast_reception_start.pa_interval = + member_param->pa_interval; + stored_param->broadcast_reception_start.num_subgroups = + member_param->num_subgroups; + memcpy(stored_param->broadcast_reception_start.subgroups, + member_param->subgroups, + sizeof(struct bt_bap_bass_subgroup) * + member_param->num_subgroups); + } + + proc_param = &active_proc->proc_param.commander[0]; + + /* TODO: what to do if we are adding a source that has already been added? + */ + + err = broadcast_reception_start_add_src(active_proc, proc_param); + if (err != 0) + { + return -ENOEXEC; + } + + return 0; +} + +int bt_cap_commander_broadcast_reception_start( + const struct bt_cap_commander_broadcast_reception_start_param *param) +{ + struct bt_cap_common_proc *active_proc; + int err; + + if (!valid_broadcast_reception_start_param(param)) + { + return -EINVAL; + } + + active_proc = bt_cap_common_get_active_proc(); + + if (bt_cap_common_test_and_set_proc_active()) + { + bt_cap_common_unlock_proc(); + + LOG_DBG("A CAP procedure is already in progress"); + + return -EBUSY; + } + + err = cap_commander_broadcast_reception_start(active_proc, param); + if (err != 0) + { + bt_cap_common_clear_proc(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return err; +} + +static bool +cap_commander_recv_state_in_active_proc( + struct bt_cap_common_proc *active_proc, + const struct bt_conn *conn, + uint8_t src_id, + const struct bt_bap_scan_delegator_recv_state *state) +{ + const struct bt_cap_commander_proc_param *proc_param; + + if (active_proc->proc_type == + BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_START) + { + proc_param = get_proc_param_by_conn(active_proc, conn); + DEBUGASSERT(proc_param != NULL); + + const struct cap_broadcast_reception_start *start_param = + &proc_param->broadcast_reception_start; + + /* Check for the address type, sid and broadcast_id (the unique + * identifiers of a receive state as per BASS) + */ + + return !start_param->started_recvd && state != NULL && + state->adv_sid == start_param->adv_sid && + state->addr.type == start_param->addr.type && + state->broadcast_id == start_param->broadcast_id; + } + else if (active_proc->proc_type == + BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_STOP) + { + proc_param = get_proc_param_by_conn(active_proc, conn); + DEBUGASSERT(proc_param != NULL); + + const struct cap_broadcast_reception_stop *stop_param = + &proc_param->broadcast_reception_stop; + + /* When stopping we can just check the `src_id` */ + + if (src_id == stop_param->src_id) + { + if (state == NULL && !stop_param->removed_recvd) + { + return true; + } + else if (state != NULL && !stop_param->stopped_recvd) + { + return true; + } + else + { + return false; + } + } + else + { + return false; + } + } + else + { + return false; + } +} + +/** + * @brief Common handler for updated and remove receive states. + * + * @param conn The connection that sent the notification + * @param src_id The source ID of the receive state. Useful when state == + * NULL + * @param state The receive state. May be NULL + */ + +static void cap_commander_handle_recv_state( + struct bt_conn *conn, uint8_t src_id, + const struct bt_bap_scan_delegator_recv_state *state) +{ + enum bt_cap_common_subproc_type subproc_type; + struct bt_cap_commander_proc_param *proc_param; + struct bt_cap_common_proc *active_proc; + bool big_synced = false; + bool big_failed = false; + int err; + + active_proc = bt_cap_common_get_active_proc(); + subproc_type = active_proc->subproc_type; + + if (!bt_cap_common_conn_in_active_proc(conn)) + { + bt_cap_common_unlock_proc(); + return; + } + + if (!cap_commander_recv_state_in_active_proc(active_proc, conn, src_id, + state)) + { + bt_cap_common_unlock_proc(); + return; + } + + if (state == NULL) + { + LOG_DBG("BASS recv state: conn %p removed", (void *)conn); + } + else + { + LOG_DBG("BASS recv state: conn %p, src_id %u pa_sync_state %u", + (void *)conn, state->src_id, + state->pa_sync_state); + + for (uint8_t i = 0U; i < state->num_subgroups; i++) + { + const struct bt_bap_bass_subgroup *subgroup = &state->subgroups[i]; + + LOG_DBG("Subgroup[%u].bis_sync: 0x%08X", i, subgroup->bis_sync); + if (subgroup->bis_sync == BT_BAP_BIS_SYNC_FAILED) + { + big_failed = true; + } + else if (subgroup->bis_sync != 0U) + { + big_synced = true; + } + else + { + /* no-op */ + } + } + } + + if (active_proc->proc_type == + BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_START) + { + const bool pa_sync_failed = + state != NULL + && (state->pa_sync_state == BT_BAP_PA_STATE_FAILED || + state->pa_sync_state == + BT_BAP_PA_STATE_NO_PAST); + struct cap_broadcast_reception_start *start_param; + + proc_param = get_proc_param_by_conn(active_proc, conn); + DEBUGASSERT(proc_param != NULL); + + start_param = &proc_param->broadcast_reception_start; + + if (big_failed || pa_sync_failed) + { + LOG_DBG("%s sync failed for %p", big_failed ? "BIG" : "PA", conn); + + bt_cap_common_abort_proc(conn, -EFAULT); + } + else if (state != NULL + && state->pa_sync_state == BT_BAP_PA_STATE_SYNCED && + big_synced) + { + start_param->started_recvd = true; + start_param->waiting_for_notification = false; + } + else + { + /* it is just an extra notification that we ignore */ + + bt_cap_common_unlock_proc(); + + return; + } + } + else if (active_proc->proc_type == + BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_STOP) + { + struct cap_broadcast_reception_stop *stop_param; + + proc_param = get_proc_param_by_conn(active_proc, conn); + DEBUGASSERT(proc_param != NULL); + + stop_param = &proc_param->broadcast_reception_stop; + + if (state == NULL && !stop_param->removed_recvd) + { + stop_param->removed_recvd = true; + stop_param->waiting_for_notification = false; + } + else if (state != NULL && state->src_id == stop_param->src_id && + state->pa_sync_state != BT_BAP_PA_STATE_SYNCED && + !big_synced && + !stop_param->stopped_recvd) + { + stop_param->stopped_recvd = true; + stop_param->waiting_for_notification = false; + } + else + { + /* it is just an extra notification that we ignore */ + + bt_cap_common_unlock_proc(); + + return; + } + } + else + { + /* No active procedure that cares about these updates */ + + bt_cap_common_unlock_proc(); + + return; + } + + broadcast_reception_update_proc_done_cnt(active_proc); + + if (bt_cap_common_proc_is_aborted()) + { + if (bt_cap_common_proc_all_handled()) + { + cap_commander_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return; + } + + if (!bt_cap_common_proc_is_done()) + { + if (active_proc->proc_type == + BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_START) + { + proc_param = broadcast_reception_get_next_proc_param(active_proc); + + /* If proc_param is NULL we have to wait for a write + * response before starting a new write request for the next scan + * delegator + */ + + if (proc_param != NULL) + { + err = broadcast_reception_start_add_src(active_proc, + proc_param); + if (err != 0) + { + LOG_DBG("broadcast_reception_start_add_src failed: %d", + err); + + bt_cap_common_abort_proc(proc_param->conn, err); + cap_commander_proc_complete(active_proc); + + return; + } + } + + bt_cap_common_unlock_proc(); + } + else if (active_proc->proc_type == + BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_STOP) + { + proc_param = broadcast_reception_get_next_proc_param(active_proc); + + /* If proc_param is NULL we have to wait for a write + * response before starting a new write request for the next scan + * delegator + */ + + if (proc_param != NULL) + { + if (subproc_type == BT_CAP_COMMON_SUBPROC_TYPE_STOP_SRC) + { + err = broadcast_reception_stop_mod_src(active_proc, + proc_param); + } + else + { + /* BT_CAP_COMMON_SUBPROC_TYPE_REM_SRC */ + + DEBUGASSERT(subproc_type == + BT_CAP_COMMON_SUBPROC_TYPE_REM_SRC); + err = broadcast_reception_stop_rem_src(active_proc, + proc_param); + } + + if (err != 0) + { + LOG_DBG("%s failed: %d", + subproc_type == BT_CAP_COMMON_SUBPROC_TYPE_STOP_SRC + ? "broadcast_reception_stop_mod_src" + : "broadcast_reception_stop_rem_src", + err); + + bt_cap_common_abort_proc(proc_param->conn, err); + cap_commander_proc_complete(active_proc); + + return; + } + } + + bt_cap_common_unlock_proc(); + } + else + { + /* No active procedure that cares about these updates */ + + bt_cap_common_unlock_proc(); + + return; + } + } + else if (active_proc->proc_type == + BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_STOP && + subproc_type == BT_CAP_COMMON_SUBPROC_TYPE_STOP_SRC) + { + bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_REM_SRC); + + proc_param = broadcast_reception_get_next_proc_param(active_proc); + DEBUGASSERT(proc_param != NULL); + + err = broadcast_reception_stop_rem_src(active_proc, proc_param); + if (err != 0) + { + LOG_DBG("broadcast_reception_stop_rem_src failed: %d", err); + + bt_cap_common_abort_proc(proc_param->conn, err); + cap_commander_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + } + else + { + cap_commander_proc_complete(active_proc); + } +} + +static void cap_commander_ba_recv_state_cb(struct bt_conn *conn, + int err, + const struct bt_bap_scan_delegator_recv_state *state) +{ + if (err != 0) + { + /* Reads can trigger errors, but we are only interested in + * notifications, so we can just discard any possible errors as we did + * not trigger them + */ + + return; + } + + if (state == NULL) + { + /* Ignore, we use cap_commander_ba_recv_state_removed_cb for removed + * receive states + */ + + return; + } + + cap_commander_handle_recv_state(conn, state->src_id, state); +} + +static void cap_commander_ba_recv_state_removed_cb(struct bt_conn *conn, + uint8_t src_id) +{ + /* Reuse the recv_state_cb to handle removed receive states */ + + cap_commander_handle_recv_state(conn, src_id, NULL); +} + +static void cap_commander_ba_rem_src_cb(struct bt_conn *conn, int err) +{ + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + struct cap_broadcast_reception_stop *stop_param; + struct bt_cap_commander_proc_param *proc_param; + + if (!bt_cap_common_conn_in_active_proc(conn)) + { + /* State change happened outside of a procedure; ignore */ + + bt_cap_common_unlock_proc(); + + return; + } + + if (!bt_cap_common_proc_is_type( + BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_STOP)) + { + /* Unexpected callback - Ignore */ + + bt_cap_common_unlock_proc(); + return; + } + + proc_param = get_proc_param_by_conn(active_proc, conn); + DEBUGASSERT(proc_param != NULL); + stop_param = &proc_param->broadcast_reception_stop; + DEBUGASSERT(stop_param->write_pending); + stop_param->write_pending = false; + + if (err != 0) + { + LOG_DBG("Failed removing source: %d", err); + + bt_cap_common_abort_proc(conn, err); + } + else + { + LOG_DBG("Conn %p broadcast remove source accepted", (void *)conn); + } + + broadcast_reception_update_proc_done_cnt(active_proc); + + if (bt_cap_common_proc_is_aborted()) + { + if (bt_cap_common_proc_all_handled()) + { + cap_commander_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return; + } + + if (!bt_cap_common_proc_is_done()) + { + proc_param = broadcast_reception_get_next_proc_param(active_proc); + + /* If proc_param is NULL we have to wait for a receive state + * notification before executing next write request + */ + + if (proc_param != NULL) + { + err = broadcast_reception_stop_rem_src(active_proc, proc_param); + + if (err != 0) + { + LOG_DBG("broadcast_reception_stop_rem_src failed: %d", err); + + bt_cap_common_abort_proc(proc_param->conn, err); + cap_commander_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + } + else + { + bt_cap_common_unlock_proc(); + } + } + else + { + cap_commander_proc_complete(active_proc); + } +} + +static void cap_commander_ba_mod_src_cb(struct bt_conn *conn, int err) +{ + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + struct bt_cap_commander_proc_param *proc_param; + struct cap_broadcast_reception_stop *stop_param; + + if (!bt_cap_common_conn_in_active_proc(conn)) + { + /* State change happened outside of a procedure; ignore */ + + bt_cap_common_unlock_proc(); + + return; + } + + if (!bt_cap_common_proc_is_type( + BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_STOP)) + { + /* Unexpected callback - Ignore */ + + bt_cap_common_unlock_proc(); + return; + } + + proc_param = get_proc_param_by_conn(active_proc, conn); + DEBUGASSERT(proc_param != NULL); + stop_param = &proc_param->broadcast_reception_stop; + + DEBUGASSERT(stop_param->write_pending); + stop_param->write_pending = false; + + if (err != 0) + { + LOG_DBG("Failed to modify source on %p: %d", conn, err); + + bt_cap_common_abort_proc(conn, err); + } + else + { + LOG_DBG("Conn %p broadcast source modify accepted", (void *)conn); + } + + broadcast_reception_update_proc_done_cnt(active_proc); + + if (bt_cap_common_proc_is_aborted()) + { + if (bt_cap_common_proc_all_handled()) + { + cap_commander_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return; + } + + if (!bt_cap_common_proc_is_done()) + { + proc_param = broadcast_reception_get_next_proc_param(active_proc); + + /* If proc_param is NULL we have to wait for a write + * response before starting a new write request for the next scan + * delegator + */ + + if (proc_param != NULL) + { + err = broadcast_reception_stop_mod_src(active_proc, proc_param); + if (err != 0) + { + LOG_DBG("broadcast_reception_stop_mod_src failed: %d", err); + + bt_cap_common_abort_proc(proc_param->conn, err); + cap_commander_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + } + else + { + bt_cap_common_unlock_proc(); + } + } + else + { + /* Once we are doing modifying the sources to stop the broadcast, we + * start removing them + */ + + bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_REM_SRC); + + /* The servers may actually remove the sources themselves, so verify + * that we haven't received empty (removed) receive states yet after + * setting the new subproc + */ + + if (!bt_cap_common_proc_is_done()) + { + proc_param = broadcast_reception_get_next_proc_param(active_proc); + DEBUGASSERT(proc_param != NULL); + + /* If proc_param is NULL we have to wait for a write + * response before starting a new write request for the next scan + * delegator + */ + + err = broadcast_reception_stop_rem_src(active_proc, proc_param); + if (err != 0) + { + LOG_DBG("broadcast_reception_stop_rem_src failed: %d", err); + + bt_cap_common_abort_proc(proc_param->conn, err); + cap_commander_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + } + else + { + cap_commander_proc_complete(active_proc); + } + } +} + +bool bt_cap_commander_valid_broadcast_reception_stop_param( + const struct bt_cap_commander_broadcast_reception_stop_param *param) +{ + if (param == NULL) + { + LOG_DBG("param is NULL"); + return false; + } + + if (param->count == 0) + { + LOG_DBG("Invalid param->count: %zu", param->count); + return false; + } + + if (param->count > CONFIG_BT_MAX_CONN) + { + LOG_DBG("param->count (%zu) is larger than CONFIG_BT_MAX_CONN (%d)", + param->count, + CONFIG_BT_MAX_CONN); + return false; + } + + if (param->param == NULL) + { + LOG_DBG("param->param is NULL"); + return false; + } + + for (size_t i = 0U; i < param->count; i++) + { + const struct + bt_cap_commander_broadcast_reception_stop_member_param *stop_param = + ¶m->param[i]; + const union bt_cap_set_member *member = ¶m->param[i].member; + const struct bt_conn *member_conn = + bt_cap_common_get_member_conn(param->type, + member); + + if (member == NULL) + { + LOG_DBG("param->param[%zu].member is NULL", i); + return false; + } + + if (member_conn == NULL) + { + LOG_DBG("Invalid param->param[%zu].member", i); + return false; + } + + if (stop_param->num_subgroups == 0) + { + LOG_DBG("param->param[%zu]->num_subgroups is 0", i); + return false; + } + + if (stop_param->num_subgroups > CONFIG_BT_BAP_BASS_MAX_SUBGROUPS) + { + LOG_DBG("Too many subgroups %u/%u", stop_param->num_subgroups, + CONFIG_BT_BAP_BASS_MAX_SUBGROUPS); + return false; + } + + for (size_t j = 0U; j < i; j++) + { + const union bt_cap_set_member *other = ¶m->param[j].member; + uint8_t other_src_id = param->param[j].src_id; + + if (other == member && stop_param->src_id == other_src_id) + { + LOG_DBG("param->members[%zu], src_id %d (%p) is duplicated " + "by " + "param->members[%zu], src_id %d (%p)", + j, other_src_id, other, i, stop_param->src_id, member); + return false; + } + } + } + + return true; +} + +int cap_commander_broadcast_reception_stop( + struct bt_cap_common_proc *active_proc, + const struct bt_cap_commander_broadcast_reception_stop_param *param) +{ + struct bt_cap_commander_proc_param *proc_param; + int err; + + cap_commander_register_broadcast_assistant_callbacks(); + + bt_cap_common_set_proc(BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_STOP, + param->count); + bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_STOP_SRC); + + for (size_t i = 0U; i < param->count; i++) + { + const struct + bt_cap_commander_broadcast_reception_stop_member_param *member_param = + ¶m->param[i]; + struct bt_cap_commander_proc_param *stored_param; + struct bt_conn *member_conn = + bt_cap_common_get_member_conn(param->type, + &member_param->member); + + /* Perform extra check in case that connection state has changed */ + + if (member_conn == NULL) + { + LOG_DBG("Invalid param->member[%zu]", i); + + return -EINVAL; + } + + /* Store the necessary parameters as we cannot assume that the supplied + * parameters are kept valid + */ + + stored_param = &active_proc->proc_param.commander[i]; + stored_param->conn = member_conn; + stored_param->broadcast_reception_stop.write_pending = false; + stored_param->broadcast_reception_stop.waiting_for_notification = + false; + stored_param->broadcast_reception_stop.stopped_recvd = false; + stored_param->broadcast_reception_stop.removed_recvd = false; + stored_param->broadcast_reception_stop.src_id = member_param->src_id; + stored_param->broadcast_reception_stop.num_subgroups = + member_param->num_subgroups; + for (size_t j = 0U; j < CONFIG_BT_BAP_BASS_MAX_SUBGROUPS; j++) + { + stored_param->broadcast_reception_stop.subgroups[j].bis_sync = 0; + stored_param->broadcast_reception_stop.subgroups[j].metadata_len = + 0; + } + } + + proc_param = &active_proc->proc_param.commander[0]; + + err = broadcast_reception_stop_mod_src(active_proc, proc_param); + if (err != 0) + { + return -ENOEXEC; + } + + return 0; +} + +int bt_cap_commander_broadcast_reception_stop( + const struct bt_cap_commander_broadcast_reception_stop_param *param) +{ + struct bt_cap_common_proc *active_proc; + int err; + + if (!bt_cap_commander_valid_broadcast_reception_stop_param(param)) + { + return -EINVAL; + } + + active_proc = bt_cap_common_get_active_proc(); + + if (bt_cap_common_test_and_set_proc_active()) + { + bt_cap_common_unlock_proc(); + + LOG_DBG("A CAP procedure is already in progress"); + + return -EBUSY; + } + + err = cap_commander_broadcast_reception_stop(active_proc, param); + if (err != 0) + { + bt_cap_common_clear_proc(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return err; +} + +static void cap_commander_ba_set_broadcast_code_cb(struct bt_conn *conn, + int err) +{ + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + + LOG_DBG("conn %p", (void *)conn); + + if (!bt_cap_common_conn_in_active_proc(conn)) + { + /* State change happened outside of a procedure; ignore */ + + bt_cap_common_unlock_proc(); + + return; + } + + active_proc->proc_done_cnt++; + if (err != 0) + { + LOG_DBG("Failed to distribute broadcast code: %d", err); + + bt_cap_common_abort_proc(conn, err); + } + else + { + LOG_DBG("Conn %p broadcast code set (%zu/%zu done)", (void *)conn, + active_proc->proc_done_cnt, + active_proc->proc_cnt); + } + + if (bt_cap_common_proc_is_aborted()) + { + if (bt_cap_common_proc_all_handled()) + { + cap_commander_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return; + } + + if (!bt_cap_common_proc_is_done()) + { + struct bt_cap_commander_proc_param *proc_param; + + proc_param = + &active_proc->proc_param.commander[active_proc->proc_done_cnt]; + conn = proc_param->conn; + + active_proc->proc_initiated_cnt++; + err = bt_bap_broadcast_assistant_set_broadcast_code( + conn, proc_param->distribute_broadcast_code.src_id, + proc_param->distribute_broadcast_code.broadcast_code); + if (err != 0) + { + LOG_DBG("Failed to perform set broadcast code for conn %p: %d", + (void *)proc_param->conn, + err); + + bt_cap_common_abort_proc(proc_param->conn, err); + cap_commander_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + } + else + { + cap_commander_proc_complete(active_proc); + } +} + +static bool valid_distribute_broadcast_code_param( + const struct bt_cap_commander_distribute_broadcast_code_param *param) +{ + if (param == NULL) + { + LOG_DBG("param is NULL"); + return false; + } + + if (param->count == 0) + { + LOG_DBG("Invalid param->count: %zu", param->count); + return false; + } + + if (param->count > CONFIG_BT_MAX_CONN) + { + LOG_DBG("param->count (%zu) is larger than CONFIG_BT_MAX_CONN (%d)", + param->count, + CONFIG_BT_MAX_CONN); + return false; + } + + if (param->param == NULL) + { + LOG_DBG("param->param is NULL"); + return false; + } + + for (size_t i = 0U; i < param->count; i++) + { + const union bt_cap_set_member *member = ¶m->param[i].member; + const struct bt_conn *member_conn = + bt_cap_common_get_member_conn(param->type, + member); + + if (member == NULL) + { + LOG_DBG("param->param[%zu].member is NULL", i); + return false; + } + + if (member_conn == NULL) + { + LOG_DBG("Invalid param->param[%zu].member", i); + return false; + } + + for (size_t j = 0U; j < i; j++) + { + const union bt_cap_set_member *other = ¶m->param[j].member; + const struct bt_conn *other_conn = + bt_cap_common_get_member_conn(param->type, + other); + + if (other_conn == member_conn) + { + LOG_DBG("param->param[%zu].member.member (%p) is " + "duplicated by " + "param->member[%zu].member.member (%p)", + j, (void *)other_conn, i, (void *)member_conn); + return false; + } + } + } + + return true; +} + +int bt_cap_commander_distribute_broadcast_code( + const struct bt_cap_commander_distribute_broadcast_code_param *param) +{ + struct bt_cap_commander_proc_param *proc_param; + struct bt_cap_common_proc *active_proc; + struct bt_conn *conn; + int err; + + if (!valid_distribute_broadcast_code_param(param)) + { + return -EINVAL; + } + + active_proc = bt_cap_common_get_active_proc(); + + if (bt_cap_common_test_and_set_proc_active()) + { + bt_cap_common_unlock_proc(); + + LOG_DBG("A CAP procedure is already in progress"); + + return -EBUSY; + } + + cap_commander_register_broadcast_assistant_callbacks(); + + bt_cap_common_set_proc(BT_CAP_COMMON_PROC_TYPE_DISTRIBUTE_BROADCAST_CODE, + param->count); + + for (size_t i = 0U; i < param->count; i++) + { + const struct + bt_cap_commander_distribute_broadcast_code_member_param *member_param = + ¶m->param[i]; + struct bt_cap_commander_proc_param *stored_param; + struct bt_conn *member_conn = + bt_cap_common_get_member_conn(param->type, + &member_param->member); + + /* Perform extra check in case that connection state has changed */ + + if (member_conn == NULL) + { + LOG_DBG("Invalid param->member[%zu]", i); + + bt_cap_common_clear_proc(active_proc); + + return -EINVAL; + } + + /* Store the necessary parameters as we cannot assume that the + * supplied parameters are kept valid + */ + + stored_param = &active_proc->proc_param.commander[i]; + stored_param->conn = member_conn; + stored_param->distribute_broadcast_code.src_id = member_param->src_id; + memcpy(stored_param->distribute_broadcast_code.broadcast_code, + param->broadcast_code, + BT_ISO_BROADCAST_CODE_SIZE); + } + + active_proc->proc_initiated_cnt++; + + proc_param = &active_proc->proc_param.commander[0]; + + conn = proc_param->conn; + + err = bt_bap_broadcast_assistant_set_broadcast_code( + conn, proc_param->distribute_broadcast_code.src_id, + proc_param->distribute_broadcast_code.broadcast_code); + if (err != 0) + { + bt_cap_common_clear_proc(active_proc); + + LOG_DBG("Failed to start distribute broadcast code for conn %p: %d", + (void *)conn, err); + + return -ENOEXEC; + } + + bt_cap_common_unlock_proc(); + + return 0; +} + +void cap_commander_register_broadcast_assistant_callbacks(void) +{ + static bool broadcast_assistant_cb_registered; + + if (!broadcast_assistant_cb_registered) + { + static struct bt_bap_broadcast_assistant_cb broadcast_assistant_cb = + { + .add_src = cap_commander_ba_add_src_cb, + .mod_src = cap_commander_ba_mod_src_cb, + .rem_src = cap_commander_ba_rem_src_cb, + .recv_state = cap_commander_ba_recv_state_cb, + .recv_state_removed = cap_commander_ba_recv_state_removed_cb, + .broadcast_code = cap_commander_ba_set_broadcast_code_cb, + }; + + int err; + + err = bt_bap_broadcast_assistant_register_cb(&broadcast_assistant_cb); + DEBUGASSERT(err == 0); + + broadcast_assistant_cb_registered = true; + } +} +#endif /* CONFIG_BT_BAP_BROADCAST_ASSISTANT */ + +/** + * @brief Completes a CAP commander procedure. + * + * This will also unlock the provided @p active_proc + */ + +void cap_commander_proc_complete(struct bt_cap_common_proc *active_proc) +{ + enum bt_cap_common_proc_type proc_type; + struct bt_conn *failed_conn; + int err; + + failed_conn = active_proc->failed_conn; + err = active_proc->err; + proc_type = active_proc->proc_type; + + if (IS_ENABLED(CONFIG_BT_CAP_HANDOVER) && + bt_cap_common_active_proc_is_handover()) + { + bt_cap_handover_commander_proc_complete(active_proc); + return; + } + + bt_cap_common_clear_proc(active_proc); + + if (cap_cb == NULL) + { + return; + } + + switch (proc_type) + { +#if defined(CONFIG_BT_VCP_VOL_CTLR) + case BT_CAP_COMMON_PROC_TYPE_VOLUME_CHANGE: + if (cap_cb->volume_changed != NULL) + { + cap_cb->volume_changed(failed_conn, err); + } + break; + case BT_CAP_COMMON_PROC_TYPE_VOLUME_MUTE_CHANGE: + if (cap_cb->volume_mute_changed != NULL) + { + cap_cb->volume_mute_changed(failed_conn, err); + } + break; +#if defined(CONFIG_BT_VCP_VOL_CTLR_VOCS) + case BT_CAP_COMMON_PROC_TYPE_VOLUME_OFFSET_CHANGE: + if (cap_cb->volume_offset_changed != NULL) + { + cap_cb->volume_offset_changed(failed_conn, err); + } + break; +#endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS */ +#endif /* CONFIG_BT_VCP_VOL_CTLR */ +#if defined(CONFIG_BT_MICP_MIC_CTLR) + case BT_CAP_COMMON_PROC_TYPE_MICROPHONE_MUTE_CHANGE: + if (cap_cb->microphone_mute_changed != NULL) + { + cap_cb->microphone_mute_changed(failed_conn, err); + } + break; +#if defined(CONFIG_BT_MICP_MIC_CTLR_AICS) + case BT_CAP_COMMON_PROC_TYPE_MICROPHONE_GAIN_CHANGE: + if (cap_cb->microphone_gain_changed != NULL) + { + cap_cb->microphone_gain_changed(failed_conn, err); + } + break; +#endif /* CONFIG_BT_MICP_MIC_CTLR_AICS */ +#endif /* CONFIG_BT_MICP_MIC_CTLR */ +#if defined(CONFIG_BT_BAP_BROADCAST_ASSISTANT) + case BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_START: + if (cap_cb->broadcast_reception_start != NULL) + { + cap_cb->broadcast_reception_start(failed_conn, err); + } + break; + case BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_STOP: + if (cap_cb->broadcast_reception_stop != NULL) + { + cap_cb->broadcast_reception_stop(failed_conn, err); + } + break; + case BT_CAP_COMMON_PROC_TYPE_DISTRIBUTE_BROADCAST_CODE: + if (cap_cb->distribute_broadcast_code != NULL) + { + cap_cb->distribute_broadcast_code(failed_conn, err); + } + break; +#endif /* CONFIG_BT_BAP_BROADCAST_ASSISTANT */ + case BT_CAP_COMMON_PROC_TYPE_NONE: + default: + DEBUGASSERT(false); + } +} + +int bt_cap_commander_cancel(void) +{ + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + + if (!bt_cap_common_proc_is_active() && !bt_cap_common_proc_is_aborted()) + { + bt_cap_common_unlock_proc(); + + LOG_DBG("No CAP procedure is in progress"); + + return -EALREADY; + } + + if ((IS_ENABLED(CONFIG_BT_CAP_HANDOVER) && + bt_cap_common_active_proc_is_handover()) || + !bt_cap_common_active_proc_is_commander()) + { + bt_cap_common_unlock_proc(); + + LOG_DBG("No CAP commander procedure is in progress"); + + return -EOPNOTSUPP; + } + + bt_cap_common_abort_proc(NULL, -ECANCELED); + cap_commander_proc_complete(active_proc); + + return 0; +} + +#if defined(CONFIG_BT_VCP_VOL_CTLR) +static struct bt_vcp_vol_ctlr_cb vol_ctlr_cb; +static bool vcp_cb_registered; + +static int cap_commander_register_vcp_cb(void) +{ + int err; + + err = bt_vcp_vol_ctlr_cb_register(&vol_ctlr_cb); + if (err != 0) + { + LOG_DBG("Failed to register VCP callbacks: %d", err); + + return -ENOEXEC; + } + + vcp_cb_registered = true; + + return 0; +} + +static bool valid_change_volume_param( + const struct bt_cap_commander_change_volume_param *param) +{ + if (param == NULL) + { + LOG_DBG("param is NULL"); + return false; + } + + if (param->count == 0) + { + LOG_DBG("Invalid param->count: %u", param->count); + return false; + } + + if (param->members == NULL) + { + LOG_DBG("param->members is NULL"); + return false; + } + + if (param->count > CONFIG_BT_MAX_CONN) + { + LOG_DBG("param->count (%zu) is larger than CONFIG_BT_MAX_CONN (%d)", + param->count, + CONFIG_BT_MAX_CONN); + return false; + } + + for (size_t i = 0U; i < param->count; i++) + { + const union bt_cap_set_member *member = ¶m->members[i]; + const struct bt_conn *member_conn = + bt_cap_common_get_member_conn(param->type, + member); + + if (member == NULL) + { + LOG_DBG("param->members[%zu] is NULL", i); + return false; + } + + if (member_conn == NULL) + { + LOG_DBG("Invalid param->members[%zu]", i); + return false; + } + + if (bt_vcp_vol_ctlr_get_by_conn(member_conn) == NULL) + { + LOG_DBG("Volume control not available for param->members[%zu]", i); + return false; + } + + for (size_t j = 0U; j < i; j++) + { + const union bt_cap_set_member *other = ¶m->members[j]; + + if (other == member) + { + LOG_DBG("param->members[%zu] (%p) is duplicated by " + "param->members[%zu] (%p)", + j, other, i, member); + return false; + } + } + } + + return true; +} + +static void cap_commander_vcp_vol_set_cb(struct bt_vcp_vol_ctlr *vol_ctlr, + int err) +{ + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + struct bt_conn *conn; + int vcp_err; + + LOG_DBG("vol_ctlr %p", (void *)vol_ctlr); + + vcp_err = bt_vcp_vol_ctlr_conn_get(vol_ctlr, &conn); + if (vcp_err != 0) + { + bt_cap_common_unlock_proc(); + LOG_ERR("Failed to get conn by vol_ctrl: %d", vcp_err); + return; + } + + LOG_DBG("conn %p", (void *)conn); + if (!bt_cap_common_conn_in_active_proc(conn)) + { + /* State change happened outside of a procedure; ignore */ + + bt_cap_common_unlock_proc(); + + return; + } + + active_proc->proc_done_cnt++; + if (err != 0) + { + LOG_DBG("Failed to set volume: %d", err); + + bt_cap_common_abort_proc(conn, err); + } + else + { + LOG_DBG("Conn %p volume updated (%zu/%zu streams done)", (void *)conn, + active_proc->proc_done_cnt, active_proc->proc_cnt); + } + + if (bt_cap_common_proc_is_aborted()) + { + if (bt_cap_common_proc_all_handled()) + { + cap_commander_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return; + } + + if (!bt_cap_common_proc_is_done()) + { + const struct bt_cap_commander_proc_param *proc_param; + + proc_param = + &active_proc->proc_param.commander[active_proc->proc_done_cnt]; + conn = proc_param->conn; + active_proc->proc_initiated_cnt++; + err = bt_vcp_vol_ctlr_set_vol(bt_vcp_vol_ctlr_get_by_conn(conn), + proc_param->change_volume.volume); + if (err != 0) + { + LOG_DBG("Failed to set volume for conn %p: %d", + (void *)proc_param->conn, err); + + bt_cap_common_abort_proc(proc_param->conn, err); + cap_commander_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + } + else + { + cap_commander_proc_complete(active_proc); + } +} + +int bt_cap_commander_change_volume( + const struct bt_cap_commander_change_volume_param *param) +{ + const struct bt_cap_commander_proc_param *proc_param; + struct bt_cap_common_proc *active_proc; + struct bt_conn *conn; + int err; + + if (!valid_change_volume_param(param)) + { + return -EINVAL; + } + + active_proc = bt_cap_common_get_active_proc(); + + if (bt_cap_common_test_and_set_proc_active()) + { + bt_cap_common_unlock_proc(); + + LOG_DBG("A CAP procedure is already in progress"); + + return -EBUSY; + } + + vol_ctlr_cb.vol_set = cap_commander_vcp_vol_set_cb; + if (!vcp_cb_registered) + { + err = cap_commander_register_vcp_cb(); + DEBUGASSERT(err == 0); + } + + bt_cap_common_set_proc(BT_CAP_COMMON_PROC_TYPE_VOLUME_CHANGE, + param->count); + + for (size_t i = 0U; i < param->count; i++) + { + struct bt_conn + *member_conn = bt_cap_common_get_member_conn(param->type, + ¶m->members[i]); + + /* Perform extra check in case that connection state has changed */ + + if (member_conn == NULL) + { + LOG_DBG("Invalid param->members[%zu]", i); + + bt_cap_common_clear_proc(active_proc); + + return -EINVAL; + } + + /* Store the necessary parameters as we cannot assume that the supplied + * parameters are kept valid + */ + + active_proc->proc_param.commander[i].conn = member_conn; + active_proc->proc_param.commander[i].change_volume.volume = + param->volume; + } + + proc_param = &active_proc->proc_param.commander[0]; + conn = proc_param->conn; + active_proc->proc_initiated_cnt++; + err = bt_vcp_vol_ctlr_set_vol(bt_vcp_vol_ctlr_get_by_conn(conn), + proc_param->change_volume.volume); + if (err != 0) + { + LOG_DBG("Failed to set volume for conn %p: %d", (void *)conn, err); + + bt_cap_common_clear_proc(active_proc); + + return -ENOEXEC; + } + + bt_cap_common_unlock_proc(); + + return 0; +} + +static bool valid_change_volume_mute_state_param( + const struct bt_cap_commander_change_volume_mute_state_param *param) +{ + if (param == NULL) + { + LOG_DBG("param is NULL"); + return false; + } + + if (param->count == 0) + { + LOG_DBG("Invalid param->count: %u", param->count); + return false; + } + + if (param->members == NULL) + { + LOG_DBG("param->members is NULL"); + return false; + } + + if (param->count > CONFIG_BT_MAX_CONN) + { + LOG_DBG("param->count (%zu) is larger than CONFIG_BT_MAX_CONN (%d)", + param->count, + CONFIG_BT_MAX_CONN); + return false; + } + + for (size_t i = 0U; i < param->count; i++) + { + const union bt_cap_set_member *member = ¶m->members[i]; + const struct bt_conn *member_conn = + bt_cap_common_get_member_conn(param->type, + member); + + if (member == NULL) + { + LOG_DBG("param->members[%zu] is NULL", i); + return false; + } + + if (member_conn == NULL) + { + LOG_DBG("Invalid param->members[%zu]", i); + return false; + } + + if (bt_vcp_vol_ctlr_get_by_conn(member_conn) == NULL) + { + LOG_DBG("Volume control not available for param->members[%zu]", i); + return false; + } + + for (size_t j = 0U; j < i; j++) + { + const union bt_cap_set_member *other = ¶m->members[j]; + + if (other == member) + { + LOG_DBG("param->members[%zu] (%p) is duplicated by " + "param->members[%zu] (%p)", + j, other, i, member); + return false; + } + } + } + + return true; +} + +static void cap_commander_vcp_vol_mute_cb(struct bt_vcp_vol_ctlr *vol_ctlr, + int err) +{ + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + struct bt_conn *conn; + int vcp_err; + + LOG_DBG("vol_ctlr %p", (void *)vol_ctlr); + + vcp_err = bt_vcp_vol_ctlr_conn_get(vol_ctlr, &conn); + if (vcp_err != 0) + { + bt_cap_common_unlock_proc(); + + LOG_ERR("Failed to get conn by vol_ctrl: %d", vcp_err); + return; + } + + LOG_DBG("conn %p", (void *)conn); + if (!bt_cap_common_conn_in_active_proc(conn)) + { + /* State change happened outside of a procedure; ignore */ + + bt_cap_common_unlock_proc(); + + return; + } + + active_proc->proc_done_cnt++; + if (err != 0) + { + LOG_DBG("Failed to set volume: %d", err); + + bt_cap_common_abort_proc(conn, err); + } + else + { + LOG_DBG("Conn %p volume updated (%zu/%zu streams done)", (void *)conn, + active_proc->proc_done_cnt, active_proc->proc_cnt); + } + + if (bt_cap_common_proc_is_aborted()) + { + if (bt_cap_common_proc_all_handled()) + { + cap_commander_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return; + } + + if (!bt_cap_common_proc_is_done()) + { + const struct bt_cap_commander_proc_param *proc_param; + + proc_param = + &active_proc->proc_param.commander[active_proc->proc_done_cnt]; + conn = proc_param->conn; + active_proc->proc_initiated_cnt++; + if (proc_param->change_vol_mute.mute) + { + err = bt_vcp_vol_ctlr_mute(bt_vcp_vol_ctlr_get_by_conn(conn)); + } + else + { + err = bt_vcp_vol_ctlr_unmute(bt_vcp_vol_ctlr_get_by_conn(conn)); + } + + if (err != 0) + { + LOG_DBG("Failed to set volume for conn %p: %d", + (void *)proc_param->conn, err); + + bt_cap_common_abort_proc(proc_param->conn, err); + cap_commander_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + } + else + { + cap_commander_proc_complete(active_proc); + } +} + +int bt_cap_commander_change_volume_mute_state( + const struct bt_cap_commander_change_volume_mute_state_param *param) +{ + const struct bt_cap_commander_proc_param *proc_param; + struct bt_cap_common_proc *active_proc; + struct bt_conn *conn; + int err; + + if (!valid_change_volume_mute_state_param(param)) + { + return -EINVAL; + } + + active_proc = bt_cap_common_get_active_proc(); + + if (bt_cap_common_test_and_set_proc_active()) + { + bt_cap_common_unlock_proc(); + + LOG_DBG("A CAP procedure is already in progress"); + + return -EBUSY; + } + + vol_ctlr_cb.mute = cap_commander_vcp_vol_mute_cb; + vol_ctlr_cb.unmute = cap_commander_vcp_vol_mute_cb; + if (!vcp_cb_registered) + { + err = cap_commander_register_vcp_cb(); + DEBUGASSERT(err == 0); + } + + bt_cap_common_set_proc(BT_CAP_COMMON_PROC_TYPE_VOLUME_MUTE_CHANGE, + param->count); + + for (size_t i = 0U; i < param->count; i++) + { + struct bt_conn *member_conn = + bt_cap_common_get_member_conn(param->type, + ¶m->members[i]); + + /* Perform extra check in case that connection state has changed */ + + if (member_conn == NULL) + { + LOG_DBG("Invalid param->members[%zu]", i); + + bt_cap_common_clear_proc(active_proc); + + return -EINVAL; + } + + /* Store the necessary parameters as we cannot assume that the supplied + * parameters are kept valid + */ + + active_proc->proc_param.commander[i].conn = member_conn; + active_proc->proc_param.commander[i].change_vol_mute.mute = + param->mute; + } + + proc_param = &active_proc->proc_param.commander[0]; + conn = proc_param->conn; + active_proc->proc_initiated_cnt++; + + if (proc_param->change_vol_mute.mute) + { + err = bt_vcp_vol_ctlr_mute(bt_vcp_vol_ctlr_get_by_conn(conn)); + } + else + { + err = bt_vcp_vol_ctlr_unmute(bt_vcp_vol_ctlr_get_by_conn(conn)); + } + + if (err != 0) + { + LOG_DBG("Failed to set volume mute state for conn %p: " + "%d", (void *)conn, + err); + + bt_cap_common_clear_proc(active_proc); + + return -ENOEXEC; + } + + bt_cap_common_unlock_proc(); + + return 0; +} + +#if defined(CONFIG_BT_VCP_VOL_CTLR_VOCS) +static bool +valid_change_offset_param( + const struct bt_cap_commander_change_volume_offset_param *param) +{ + if (param == NULL) + { + LOG_DBG("param is NULL"); + return false; + } + + if (param->count == 0) + { + LOG_DBG("Invalid param->count: %u", param->count); + return false; + } + + if (param->param == NULL) + { + LOG_DBG("param->param is NULL"); + return false; + } + + if (param->count > CONFIG_BT_MAX_CONN) + { + LOG_DBG("param->count (%zu) is larger than CONFIG_BT_MAX_CONN (%d)", + param->count, + CONFIG_BT_MAX_CONN); + return false; + } + + for (size_t i = 0U; i < param->count; i++) + { + const struct bt_cap_commander_change_volume_offset_member_param + *member_param = + ¶m->param[i]; + const union bt_cap_set_member *member = &member_param->member; + const struct bt_conn *member_conn = + bt_cap_common_get_member_conn(param->type, + member); + struct bt_vcp_vol_ctlr *vol_ctlr; + struct bt_vcp_included included; + int err; + + if (member == NULL) + { + LOG_DBG("param->param[%zu].member is NULL", i); + return false; + } + + if (member_conn == NULL) + { + LOG_DBG("Invalid param->param[%zu].member", i); + return false; + } + + vol_ctlr = bt_vcp_vol_ctlr_get_by_conn(member_conn); + if (vol_ctlr == NULL) + { + LOG_DBG("Volume control not available for " + "param->param[%zu].member", + i); + return false; + } + + err = bt_vcp_vol_ctlr_included_get(vol_ctlr, &included); + if (err != 0 || included.vocs_cnt == 0) + { + LOG_DBG("Volume offset control not available for " + "param->param[%zu].member", + i); + return -ENOEXEC; + } + + if (!IN_RANGE(member_param->offset, BT_VOCS_MIN_OFFSET, + BT_VOCS_MAX_OFFSET)) + { + LOG_DBG("Invalid offset %d for param->param[%zu].offset", + member_param->offset, i); + return false; + } + + for (size_t j = 0U; j < i; j++) + { + const union bt_cap_set_member *other = ¶m->param[j].member; + + if (other == member) + { + LOG_DBG("param->param[%zu].member (%p) is duplicated by " + "param->param[%zu].member (%p)", + j, other, i, member); + return false; + } + } + } + + return true; +} + +static void cap_commander_vcp_set_offset_cb(struct bt_vocs *inst, int err) +{ + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + struct bt_conn *conn; + int vocs_err; + + LOG_DBG("bt_vocs %p", (void *)inst); + + vocs_err = bt_vocs_client_conn_get(inst, &conn); + if (vocs_err != 0) + { + bt_cap_common_unlock_proc(); + + LOG_ERR("Failed to get conn by inst: %d", vocs_err); + return; + } + + LOG_DBG("conn %p", (void *)conn); + if (!bt_cap_common_conn_in_active_proc(conn)) + { + /* State change happened outside of a procedure; ignore */ + + bt_cap_common_unlock_proc(); + + return; + } + + active_proc->proc_done_cnt++; + if (err != 0) + { + LOG_DBG("Failed to set offset: %d", err); + + bt_cap_common_abort_proc(conn, err); + } + else + { + LOG_DBG("Conn %p offset updated (%zu/%zu streams done)", (void *)conn, + active_proc->proc_done_cnt, active_proc->proc_cnt); + } + + if (bt_cap_common_proc_is_aborted()) + { + if (bt_cap_common_proc_all_handled()) + { + cap_commander_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return; + } + + if (!bt_cap_common_proc_is_done()) + { + const struct bt_cap_commander_proc_param *proc_param; + + proc_param = + &active_proc->proc_param.commander[active_proc->proc_done_cnt]; + conn = proc_param->conn; + active_proc->proc_initiated_cnt++; + + err = bt_vocs_state_set(proc_param->change_offset.vocs, + proc_param->change_offset.offset); + if (err != 0) + { + LOG_DBG("Failed to set offset for conn %p: %d", + (void *)proc_param->conn, err); + + bt_cap_common_abort_proc(proc_param->conn, err); + cap_commander_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + } + else + { + cap_commander_proc_complete(active_proc); + } +} + +int bt_cap_commander_change_volume_offset( + const struct bt_cap_commander_change_volume_offset_param *param) +{ + const struct bt_cap_commander_proc_param *proc_param; + struct bt_cap_common_proc *active_proc; + struct bt_vcp_vol_ctlr *vol_ctlr; + struct bt_conn *conn; + int err; + + if (!valid_change_offset_param(param)) + { + return -EINVAL; + } + + active_proc = bt_cap_common_get_active_proc(); + + if (bt_cap_common_test_and_set_proc_active()) + { + bt_cap_common_unlock_proc(); + + LOG_DBG("A CAP procedure is already in progress"); + + return -EBUSY; + } + + vol_ctlr_cb.vocs_cb.set_offset = cap_commander_vcp_set_offset_cb; + if (!vcp_cb_registered) + { + err = cap_commander_register_vcp_cb(); + DEBUGASSERT(err == 0); + } + + bt_cap_common_set_proc(BT_CAP_COMMON_PROC_TYPE_VOLUME_OFFSET_CHANGE, + param->count); + + for (size_t i = 0U; i < param->count; i++) + { + const struct + bt_cap_commander_change_volume_offset_member_param *member_param = + ¶m->param[i]; + struct bt_conn *member_conn = + bt_cap_common_get_member_conn(param->type, + &member_param->member); + struct bt_vcp_included included; + + /* Perform extra check in case that connection state has changed */ + + if (member_conn == NULL) + { + LOG_DBG("Invalid param->members[%zu]", i); + + bt_cap_common_clear_proc(active_proc); + + return -EINVAL; + } + + vol_ctlr = bt_vcp_vol_ctlr_get_by_conn(member_conn); + if (vol_ctlr == NULL) + { + LOG_DBG("Invalid param->members[%zu] vol_ctlr", i); + + bt_cap_common_clear_proc(active_proc); + + return -EINVAL; + } + + err = bt_vcp_vol_ctlr_included_get(vol_ctlr, &included); + if (err != 0 || included.vocs_cnt == 0) + { + LOG_DBG("Invalid param->members[%zu] vocs", i); + + bt_cap_common_clear_proc(active_proc); + + return -EINVAL; + } + + /* Store the necessary parameters as we cannot assume that the supplied + * parameters are kept valid + */ + + active_proc->proc_param.commander[i].conn = member_conn; + active_proc->proc_param.commander[i].change_offset.offset = + member_param->offset; + /* TODO: For now we just use the first VOCS instance + * - How should we handle multiple? + */ + + active_proc->proc_param.commander[i].change_offset.vocs = + included.vocs[0]; + } + + proc_param = &active_proc->proc_param.commander[0]; + conn = proc_param->conn; + active_proc->proc_initiated_cnt++; + + err = bt_vocs_state_set(proc_param->change_offset.vocs, + proc_param->change_offset.offset); + if (err != 0) + { + LOG_DBG("Failed to set volume for conn %p: %d", (void *)conn, err); + + bt_cap_common_clear_proc(active_proc); + + return -ENOEXEC; + } + + bt_cap_common_unlock_proc(); + + return 0; +} +#endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS */ +#endif /* CONFIG_BT_VCP_VOL_CTLR */ + +#if defined(CONFIG_BT_MICP_MIC_CTLR) +static struct bt_micp_mic_ctlr_cb mic_ctlr_cb; +static bool micp_callbacks_registered; + +static int cap_commander_register_micp_callbacks(void) +{ + int err; + + err = bt_micp_mic_ctlr_cb_register(&mic_ctlr_cb); + if (err != 0) + { + LOG_DBG("Failed to register MICP callbacks: %d", err); + + return -ENOEXEC; + } + + micp_callbacks_registered = true; + + return 0; +} + +static bool valid_change_microphone_mute_state_param( + const struct bt_cap_commander_change_microphone_mute_state_param *param) +{ + if (param == NULL) + { + LOG_DBG("param is NULL"); + return false; + } + + if (param->count == 0) + { + LOG_DBG("Invalid param->count: %u", param->count); + return false; + } + + if (param->members == NULL) + { + LOG_DBG("param->members is NULL"); + return false; + } + + if (param->count > CONFIG_BT_MAX_CONN) + { + LOG_DBG("param->count (%zu) is larger than CONFIG_BT_MAX_CONN (%d)", + param->count, + CONFIG_BT_MAX_CONN); + return false; + } + + for (size_t i = 0U; i < param->count; i++) + { + const union bt_cap_set_member *member = ¶m->members[i]; + const struct bt_conn *member_conn = + bt_cap_common_get_member_conn(param->type, + member); + + if (member == NULL) + { + LOG_DBG("param->members[%zu] is NULL", i); + return false; + } + + if (member_conn == NULL) + { + LOG_DBG("Invalid param->members[%zu]", i); + return false; + } + + if (bt_micp_mic_ctlr_get_by_conn(member_conn) == NULL) + { + LOG_DBG("Microphone control not available for param->members[%zu]", + i); + return false; + } + + for (size_t j = 0U; j < i; j++) + { + const union bt_cap_set_member *other = ¶m->members[j]; + + if (other == member) + { + LOG_DBG("param->members[%zu] (%p) is duplicated by " + "param->members[%zu] (%p)", + j, other, i, member); + return false; + } + } + } + + return true; +} + +static void cap_commander_micp_mic_mute_cb(struct bt_micp_mic_ctlr *mic_ctlr, + int err) +{ + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + struct bt_conn *conn; + int micp_err; + + LOG_DBG("mic_ctlr %p", (void *)mic_ctlr); + + micp_err = bt_micp_mic_ctlr_conn_get(mic_ctlr, &conn); + if (micp_err != 0) + { + bt_cap_common_unlock_proc(); + LOG_ERR("Failed to get conn by mic_ctlr: %d", micp_err); + return; + } + + LOG_DBG("conn %p", (void *)conn); + if (!bt_cap_common_conn_in_active_proc(conn)) + { + /* State change happened outside of a procedure; ignore */ + + bt_cap_common_unlock_proc(); + + return; + } + + active_proc->proc_done_cnt++; + if (err != 0) + { + LOG_DBG("Failed to change microphone mute: %d", err); + + bt_cap_common_abort_proc(conn, err); + } + else + { + LOG_DBG("Conn %p mute updated (%zu/%zu streams done)", (void *)conn, + active_proc->proc_done_cnt, active_proc->proc_cnt); + } + + if (bt_cap_common_proc_is_aborted()) + { + if (bt_cap_common_proc_all_handled()) + { + cap_commander_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return; + } + + if (!bt_cap_common_proc_is_done()) + { + const struct bt_cap_commander_proc_param *proc_param; + + proc_param = + &active_proc->proc_param.commander[active_proc->proc_done_cnt]; + conn = proc_param->conn; + active_proc->proc_initiated_cnt++; + if (proc_param->change_mic_mute.mute) + { + err = bt_micp_mic_ctlr_mute(bt_micp_mic_ctlr_get_by_conn(conn)); + } + else + { + err = bt_micp_mic_ctlr_unmute(bt_micp_mic_ctlr_get_by_conn(conn)); + } + + if (err != 0) + { + LOG_DBG("Failed to change mute for conn %p: %d", + (void *)proc_param->conn, err); + + bt_cap_common_abort_proc(proc_param->conn, err); + cap_commander_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + } + else + { + cap_commander_proc_complete(active_proc); + } +} + +int bt_cap_commander_change_microphone_mute_state( + const struct bt_cap_commander_change_microphone_mute_state_param *param) +{ + const struct bt_cap_commander_proc_param *proc_param; + struct bt_cap_common_proc *active_proc; + struct bt_conn *conn; + int err; + + if (!valid_change_microphone_mute_state_param(param)) + { + return -EINVAL; + } + + active_proc = bt_cap_common_get_active_proc(); + + if (bt_cap_common_test_and_set_proc_active()) + { + bt_cap_common_unlock_proc(); + + LOG_DBG("A CAP procedure is already in progress"); + + return -EBUSY; + } + + bt_cap_common_set_proc(BT_CAP_COMMON_PROC_TYPE_MICROPHONE_MUTE_CHANGE, + param->count); + + mic_ctlr_cb.mute_written = cap_commander_micp_mic_mute_cb; + mic_ctlr_cb.unmute_written = cap_commander_micp_mic_mute_cb; + if (!micp_callbacks_registered) + { + err = cap_commander_register_micp_callbacks(); + DEBUGASSERT(err == 0); + } + + for (size_t i = 0U; i < param->count; i++) + { + struct bt_conn *member_conn = + bt_cap_common_get_member_conn(param->type, + ¶m->members[i]); + + /* Perform extra check in case that connection state has changed */ + + if (member_conn == NULL) + { + LOG_DBG("Invalid param->members[%zu]", i); + + bt_cap_common_clear_proc(active_proc); + + return -EINVAL; + } + + /* Store the necessary parameters as we cannot assume that the supplied + * parameters are kept valid + */ + + active_proc->proc_param.commander[i].conn = member_conn; + active_proc->proc_param.commander[i].change_mic_mute.mute = + param->mute; + } + + proc_param = &active_proc->proc_param.commander[0]; + conn = proc_param->conn; + active_proc->proc_initiated_cnt++; + + if (proc_param->change_mic_mute.mute) + { + err = bt_micp_mic_ctlr_mute(bt_micp_mic_ctlr_get_by_conn(conn)); + } + else + { + err = bt_micp_mic_ctlr_unmute(bt_micp_mic_ctlr_get_by_conn(conn)); + } + + if (err != 0) + { + LOG_DBG("Failed to set microphone mute state for conn %p: %d", + (void *)conn, err); + + bt_cap_common_clear_proc(active_proc); + + return -ENOEXEC; + } + + bt_cap_common_unlock_proc(); + + return 0; +} + +#if defined(CONFIG_BT_MICP_MIC_CTLR_AICS) +static bool valid_change_microphone_gain_param( + const struct bt_cap_commander_change_microphone_gain_setting_param *param) +{ + if (param == NULL) + { + LOG_DBG("param is NULL"); + return false; + } + + if (param->count == 0) + { + LOG_DBG("Invalid param->count: %u", param->count); + return false; + } + + if (param->param == NULL) + { + LOG_DBG("param->param is NULL"); + return false; + } + + if (param->count > CONFIG_BT_MAX_CONN) + { + LOG_DBG("param->count (%zu) is larger than CONFIG_BT_MAX_CONN (%d)", + param->count, + CONFIG_BT_MAX_CONN); + return false; + } + + for (size_t i = 0U; i < param->count; i++) + { + const union bt_cap_set_member *member = ¶m->param[i].member; + const struct bt_conn *member_conn = + bt_cap_common_get_member_conn(param->type, + member); + struct bt_micp_mic_ctlr *mic_ctlr; + struct bt_micp_included included; + int err; + + if (member == NULL) + { + LOG_DBG("param->param[%zu].member is NULL", i); + return false; + } + + if (member_conn == NULL) + { + LOG_DBG("Invalid param->param[%zu].member", i); + return false; + } + + mic_ctlr = bt_micp_mic_ctlr_get_by_conn(member_conn); + if (mic_ctlr == NULL) + { + LOG_DBG("Microphone control not available for " + "param->param[%zu].member", + i); + return false; + } + + err = bt_micp_mic_ctlr_included_get(mic_ctlr, &included); + if (err != 0 || included.aics_cnt == 0) + { + LOG_DBG("Microphone audio input control not available for " + "param->param[%zu].member", + i); + return -ENOEXEC; + } + + for (size_t j = 0U; j < i; j++) + { + const union bt_cap_set_member *other = ¶m->param[j].member; + + if (other == member) + { + LOG_DBG("param->param[%zu].member (%p) is duplicated by " + "param->param[%zu].member (%p)", + j, other, i, member); + return false; + } + } + } + + return true; +} + +static void cap_commander_micp_gain_set_cb(struct bt_aics *inst, int err) +{ + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + struct bt_conn *conn; + int micp_err; + + LOG_DBG("bt_aics %p", (void *)inst); + + micp_err = bt_aics_client_conn_get(inst, &conn); + if (micp_err != 0) + { + bt_cap_common_unlock_proc(); + LOG_ERR("Failed to get conn by aics: %d", micp_err); + return; + } + + LOG_DBG("conn %p", (void *)conn); + if (!bt_cap_common_conn_in_active_proc(conn)) + { + /* State change happened outside of a procedure; ignore */ + + bt_cap_common_unlock_proc(); + + return; + } + + active_proc->proc_done_cnt++; + if (err != 0) + { + LOG_DBG("Failed to set gain: %d", err); + + bt_cap_common_abort_proc(conn, err); + } + else + { + LOG_DBG("Conn %p gain updated (%zu/%zu streams done)", (void *)conn, + active_proc->proc_done_cnt, active_proc->proc_cnt); + } + + if (bt_cap_common_proc_is_aborted()) + { + if (bt_cap_common_proc_all_handled()) + { + cap_commander_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return; + } + + if (!bt_cap_common_proc_is_done()) + { + const struct bt_cap_commander_proc_param *proc_param; + + proc_param = + &active_proc->proc_param.commander[active_proc->proc_done_cnt]; + conn = proc_param->conn; + active_proc->proc_initiated_cnt++; + err = bt_aics_gain_set(proc_param->change_gain.aics, + proc_param->change_gain.gain); + if (err != 0) + { + LOG_DBG("Failed to set gain for conn %p: %d", + (void *)proc_param->conn, err); + + bt_cap_common_abort_proc(proc_param->conn, err); + cap_commander_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + } + else + { + cap_commander_proc_complete(active_proc); + } +} + +int bt_cap_commander_change_microphone_gain_setting( + const struct bt_cap_commander_change_microphone_gain_setting_param *param) +{ + const struct bt_cap_commander_proc_param *proc_param; + struct bt_cap_common_proc *active_proc; + struct bt_conn *conn; + int err; + + if (!valid_change_microphone_gain_param(param)) + { + return -EINVAL; + } + + active_proc = bt_cap_common_get_active_proc(); + + if (bt_cap_common_test_and_set_proc_active()) + { + bt_cap_common_unlock_proc(); + + LOG_DBG("A CAP procedure is already in progress"); + + return -EBUSY; + } + + bt_cap_common_set_proc(BT_CAP_COMMON_PROC_TYPE_MICROPHONE_GAIN_CHANGE, + param->count); + + mic_ctlr_cb.aics_cb.set_gain = cap_commander_micp_gain_set_cb; + if (!micp_callbacks_registered) + { + err = cap_commander_register_micp_callbacks(); + DEBUGASSERT(err == 0); + } + + for (size_t i = 0U; i < param->count; i++) + { + const union bt_cap_set_member *member = ¶m->param[i].member; + struct bt_conn *member_conn = + bt_cap_common_get_member_conn(param->type, + member); + struct bt_micp_mic_ctlr *mic_ctlr; + struct bt_micp_included included; + + /* Perform extra check in case that connection state has changed */ + + if (member_conn == NULL) + { + LOG_DBG("Invalid param->param[%zu].member", i); + + bt_cap_common_clear_proc(active_proc); + + return -EINVAL; + } + + mic_ctlr = bt_micp_mic_ctlr_get_by_conn(member_conn); + if (mic_ctlr == NULL) + { + LOG_DBG("Invalid param->param[%zu].member mic_ctlr", i); + + bt_cap_common_clear_proc(active_proc); + + return -EINVAL; + } + + err = bt_micp_mic_ctlr_included_get(mic_ctlr, &included); + if (err != 0 || included.aics_cnt == 0) + { + LOG_DBG("Invalid param->param[%zu].member aics", i); + + bt_cap_common_clear_proc(active_proc); + + return -EINVAL; + } + + /* Store the necessary parameters as we cannot assume that the supplied + * parameters are kept valid + */ + + active_proc->proc_param.commander[i].conn = member_conn; + active_proc->proc_param.commander[i].change_gain.gain = + param->param[i].gain; + /* TODO: For now we just use the first AICS instance + * - How should we handle multiple? + */ + + active_proc->proc_param.commander[i].change_gain.aics = + included.aics[0]; + } + + proc_param = &active_proc->proc_param.commander[0]; + conn = proc_param->conn; + active_proc->proc_initiated_cnt++; + + err = bt_aics_gain_set(proc_param->change_gain.aics, + proc_param->change_gain.gain); + if (err != 0) + { + LOG_DBG("Failed to set gain for conn %p: %d", (void *)conn, err); + + bt_cap_common_clear_proc(active_proc); + + return -ENOEXEC; + } + + bt_cap_common_unlock_proc(); + + return 0; +} +#endif /* CONFIG_BT_MICP_MIC_CTLR_AICS */ +#endif /* CONFIG_BT_MICP_MIC_CTLR */ diff --git a/libs/zblue/audio/cap_common.c b/libs/zblue/audio/cap_common.c new file mode 100644 index 0000000000000..fdf622de90c9f --- /dev/null +++ b/libs/zblue/audio/cap_common.c @@ -0,0 +1,639 @@ +/**************************************************************************** + * libs/zblue/audio/cap_common.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2023-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cap_internal.h" +#include "csip_internal.h" + +LOG_MODULE_REGISTER(bt_cap_common, CONFIG_BT_CAP_COMMON_LOG_LEVEL); + +#include "common/bt_str.h" + +static struct bt_cap_common_client bt_cap_common_clients[CONFIG_BT_MAX_CONN]; +static const struct bt_uuid *cas_uuid = BT_UUID_CAS; +static struct bt_cap_common_proc active_proc; + +static rmutex_t active_proc_mutex = NXRMUTEX_INITIALIZER; +#define CAP_ACTIVE_PROC_MUTEX_TIMEOUT MSEC2TICK(10000U) + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +struct bt_cap_common_proc *bt_cap_common_get_active_proc(void) +{ + __maybe_unused int err; + + err = nxrmutex_ticklock(&active_proc_mutex, CAP_ACTIVE_PROC_MUTEX_TIMEOUT); + DEBUGASSERT(err == 0); + + LOG_DBG("Took active_proc_mutex (count: %u)", active_proc_mutex.count); + + return &active_proc; +} + +void bt_cap_common_unlock_proc(void) +{ + __maybe_unused int err; + + err = nxrmutex_unlock(&active_proc_mutex); + DEBUGASSERT(err == 0); + + LOG_DBG("Released active_proc_mutex"); +} + +uint32_t bt_cap_common_get_active_proc_mutex_count( + const struct bt_cap_common_proc *proc) +{ + DEBUGASSERT(proc == &active_proc); + DEBUGASSERT(active_proc_mutex.count > 0U); + + return active_proc_mutex.count; +} + +void bt_cap_common_clear_proc(struct bt_cap_common_proc *proc) +{ + (void)memset(proc, 0, sizeof(*proc)); + + bt_cap_common_unlock_proc(); +} + +void bt_cap_common_set_proc(enum bt_cap_common_proc_type proc_type, + size_t proc_cnt) +{ + LOG_DBG("Setting proc to %d for %zu procedures", proc_type, proc_cnt); + + active_proc.proc_cnt = proc_cnt; + active_proc.proc_type = proc_type; + active_proc.proc_done_cnt = 0U; + active_proc.proc_initiated_cnt = 0U; +} + +#if defined(CONFIG_BT_CAP_SUBPROC_SUPPORT) +void bt_cap_common_set_subproc(enum bt_cap_common_subproc_type subproc_type) +{ + LOG_DBG("Setting subproc to %d", subproc_type); + + active_proc.proc_done_cnt = 0U; + active_proc.proc_initiated_cnt = 0U; + active_proc.subproc_type = subproc_type; + active_proc.subproc_initiated = false; +} + +bool bt_cap_common_proc_is_type(enum bt_cap_common_proc_type proc_type) +{ + return active_proc.proc_type == proc_type; +} + +bool bt_cap_common_subproc_is_type( + enum bt_cap_common_subproc_type subproc_type) +{ + return active_proc.subproc_type == subproc_type; +} +#endif /* CONFIG_BT_CAP_SUBPROC_SUPPORT */ + +#if defined(CONFIG_BT_CAP_HANDOVER) +void bt_cap_common_set_handover_active(void) +{ + atomic_set_bit(active_proc.proc_state_flags, + BT_CAP_COMMON_PROC_STATE_HANDOVER); +} + +bool bt_cap_common_active_proc_is_handover(void) +{ + return atomic_test_bit(active_proc.proc_state_flags, + BT_CAP_COMMON_PROC_STATE_HANDOVER); +} +#endif /* CONFIG_BT_CAP_HANDOVER */ + +struct bt_conn *bt_cap_common_get_member_conn(enum bt_cap_set_type type, + const union bt_cap_set_member *member) +{ + if (member == NULL) + { + return NULL; + } + + if (type == BT_CAP_SET_TYPE_CSIP) + { + struct bt_cap_common_client *client; + + /* We have verified that `client` won't be NULL in + * `valid_change_volume_param`. + */ + + client = bt_cap_common_get_client_by_csis(member->csip); + if (client == NULL) + { + return NULL; + } + + return client->conn; + } + + return member->member; +} + +bool bt_cap_common_test_and_set_proc_active(void) +{ + return atomic_test_and_set_bit(active_proc.proc_state_flags, + BT_CAP_COMMON_PROC_STATE_ACTIVE); +} + +bool bt_cap_common_proc_is_active(void) +{ + return atomic_test_bit(active_proc.proc_state_flags, + BT_CAP_COMMON_PROC_STATE_ACTIVE); +} + +bool bt_cap_common_proc_is_aborted(void) +{ + return atomic_test_bit(active_proc.proc_state_flags, + BT_CAP_COMMON_PROC_STATE_ABORTED); +} + +bool bt_cap_common_proc_all_handled(void) +{ + return active_proc.proc_done_cnt == active_proc.proc_initiated_cnt; +} + +bool bt_cap_common_proc_is_done(void) +{ + return active_proc.proc_done_cnt == active_proc.proc_cnt; +} + +void bt_cap_common_abort_proc(struct bt_conn *conn, int err) +{ + if (bt_cap_common_proc_is_aborted()) + { + /* no-op */ + + return; + } + +#if defined(CONFIG_BT_CAP_INITIATOR_UNICAST) + LOG_DBG("Aborting proc %d with subproc %d for %p: %d (%zu/%zu done, %zu " + "initiated)", + active_proc.proc_type, active_proc.subproc_type, (void *)conn, err, + active_proc.proc_done_cnt, active_proc.proc_cnt, + active_proc.proc_initiated_cnt); +#else /* !CONFIG_BT_CAP_INITIATOR_UNICAST */ + LOG_DBG("Aborting proc %d for %p: %d (%zu/%zu done, %zu initiated)", + active_proc.proc_type, + (void *)conn, err, active_proc.proc_done_cnt, active_proc.proc_cnt, + active_proc.proc_initiated_cnt); +#endif /* CONFIG_BT_CAP_INITIATOR_UNICAST */ + + active_proc.err = err; + active_proc.failed_conn = conn; + atomic_set_bit(active_proc.proc_state_flags, + BT_CAP_COMMON_PROC_STATE_ABORTED); +} + +#if defined(CONFIG_BT_CAP_INITIATOR_UNICAST) +bool bt_cap_common_active_proc_is_initiator(void) +{ + switch (active_proc.proc_type) + { + case BT_CAP_COMMON_PROC_TYPE_START: + case BT_CAP_COMMON_PROC_TYPE_UPDATE: + case BT_CAP_COMMON_PROC_TYPE_STOP: + return true; + default: + return false; + } +} +#endif /* CONFIG_BT_CAP_INITIATOR_UNICAST */ + +#if defined(CONFIG_BT_CAP_COMMANDER) +bool bt_cap_common_active_proc_is_commander(void) +{ + switch (active_proc.proc_type) + { + case BT_CAP_COMMON_PROC_TYPE_VOLUME_CHANGE: + case BT_CAP_COMMON_PROC_TYPE_VOLUME_OFFSET_CHANGE: + case BT_CAP_COMMON_PROC_TYPE_VOLUME_MUTE_CHANGE: + case BT_CAP_COMMON_PROC_TYPE_MICROPHONE_GAIN_CHANGE: + case BT_CAP_COMMON_PROC_TYPE_MICROPHONE_MUTE_CHANGE: + case BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_START: + case BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_STOP: + case BT_CAP_COMMON_PROC_TYPE_DISTRIBUTE_BROADCAST_CODE: + return true; + default: + return false; + } +} +#endif /* CONFIG_BT_CAP_INITIATOR_UNICAST */ + +bool bt_cap_common_conn_in_active_proc(const struct bt_conn *conn) +{ + if (!bt_cap_common_proc_is_active()) + { + return false; + } + + for (size_t i = 0U; i < active_proc.proc_initiated_cnt; i++) + { +#if defined(CONFIG_BT_CAP_INITIATOR_UNICAST) + if (bt_cap_common_active_proc_is_initiator()) + { + if (active_proc.proc_param.initiator[i].stream->bap_stream.conn == + conn) + { + return true; + } + } +#endif /* CONFIG_BT_CAP_INITIATOR_UNICAST */ +#if defined(CONFIG_BT_CAP_COMMANDER) + + if (bt_cap_common_active_proc_is_commander()) + { + if (active_proc.proc_param.commander[i].conn == conn) + { + return true; + } + } +#endif /* CONFIG_BT_CAP_COMMANDER */ + } + + return false; +} + +bool bt_cap_common_stream_in_active_proc( + const struct bt_cap_stream *cap_stream) +{ + if (!bt_cap_common_proc_is_active()) + { + return false; + } + +#if defined(CONFIG_BT_CAP_INITIATOR_UNICAST) + if (bt_cap_common_active_proc_is_initiator()) + { + for (size_t i = 0U; i < active_proc.proc_cnt; i++) + { + if (active_proc.proc_param.initiator[i].stream == cap_stream) + { + return true; + } + } + } +#endif /* CONFIG_BT_CAP_INITIATOR_UNICAST */ + + return false; +} + +static void bt_cap_common_disconnected(struct bt_conn *conn, uint8_t reason) +{ + struct bt_cap_common_proc *active_proc_ptr = + bt_cap_common_get_active_proc(); + struct bt_cap_common_client *client; + + ARG_UNUSED(reason); + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + client = bt_cap_common_get_client_by_acl(conn); + + LOG_DBG("conn %p disconnected", conn); + + bt_conn_drop(&client->conn); + (void)memset(client, 0, sizeof(*client)); + + if (bt_cap_common_conn_in_active_proc(conn)) + { + bt_cap_common_abort_proc(conn, -ECONNRESET); + +#if defined(CONFIG_BT_CAP_INITIATOR_UNICAST) + if (bt_cap_common_active_proc_is_initiator()) + { + /* For Initiator procedures, we may have multiple procedures per + * connection, so loop through and count the ones that we consider + * "done" + */ + + for (size_t i = 0U; i < active_proc_ptr->proc_initiated_cnt; i++) + { + struct bt_cap_initiator_proc_param *proc_param = + &active_proc_ptr->proc_param.initiator[i]; + + if (proc_param->in_progress && + proc_param->stream->bap_stream.conn == conn) + { + proc_param->in_progress = false; + active_proc_ptr->proc_done_cnt++; + + /* bap_stream.conn will be cleared by BAP */ + } + } + + if (bt_cap_common_proc_all_handled()) + { + cap_initiator_unicast_audio_proc_complete(active_proc_ptr); + return; + } + } +#endif /* CONFIG_BT_CAP_INITIATOR_UNICAST */ +#if defined(CONFIG_BT_CAP_COMMANDER) + + if (bt_cap_common_active_proc_is_commander()) + { + /* For Commander procedures there is a 1:1 between number of + * procedures and connections, so we always just increment by 1 + */ + + for (size_t i = 0U; i < active_proc_ptr->proc_initiated_cnt; i++) + { + if (active_proc.proc_param.commander[i].conn == conn) + { + active_proc.proc_param.commander[i].conn = NULL; + active_proc_ptr->proc_done_cnt++; + } + } + + if (bt_cap_common_proc_all_handled()) + { + cap_commander_proc_complete(active_proc_ptr); + return; + } + } +#endif /* CONFIG_BT_CAP_INITIATOR_UNICAST */ + } + + bt_cap_common_unlock_proc(); +} + +BT_CONN_CB_DEFINE(conn_callbacks) = +{ + .disconnected = bt_cap_common_disconnected, +}; + +struct bt_cap_common_client *bt_cap_common_get_client_by_acl( + const struct bt_conn *acl) +{ + if (acl == NULL) + { + return NULL; + } + + return &bt_cap_common_clients[bt_conn_index(acl)]; +} + +struct bt_cap_common_client * +bt_cap_common_get_client_by_csis( + const struct bt_csip_set_coordinator_csis_inst *csis_inst) +{ + if (csis_inst == NULL) + { + return NULL; + } + + for (size_t i = 0U; i < ARRAY_SIZE(bt_cap_common_clients); i++) + { + struct bt_cap_common_client *client = &bt_cap_common_clients[i]; + + if (client->csis_inst == csis_inst) + { + return client; + } + } + + return NULL; +} + +static void cap_common_discover_complete(struct bt_conn *conn, + int err, + const struct bt_csip_set_coordinator_set_member *member, + const struct bt_csip_set_coordinator_csis_inst *csis_inst) +{ + struct bt_cap_common_client *client; + + client = bt_cap_common_get_client_by_acl(conn); + if (client != NULL && client->discover_cb_func != NULL) + { + const bt_cap_common_discover_func_t cb_func = client->discover_cb_func; + + client->discover_cb_func = NULL; + cb_func(conn, err, member, csis_inst); + } +} + +static void csis_client_discover_cb(struct bt_conn *conn, + const struct bt_csip_set_coordinator_set_member *member, + int err, + size_t set_count) +{ + struct bt_cap_common_client *client; + + if (err != 0) + { + LOG_DBG("CSIS client discover failed: %d", err); + + cap_common_discover_complete(conn, err, NULL, NULL); + + return; + } + + client = bt_cap_common_get_client_by_acl(conn); + client->csis_inst = bt_csip_set_coordinator_csis_inst_by_handle(conn, + client->csis_start_handle); + + if (member == NULL || set_count == 0 || client->csis_inst == NULL) + { + LOG_ERR("Unable to find CSIS for CAS"); + + cap_common_discover_complete(conn, -ENODATA, NULL, NULL); + } + else + { + LOG_DBG("Found CAS with CSIS"); + cap_common_discover_complete(conn, 0, member, client->csis_inst); + } +} + +static uint8_t bt_cap_common_discover_included_cb(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + if (attr == NULL) + { + LOG_DBG("CAS CSIS include not found"); + + cap_common_discover_complete(conn, 0, NULL, NULL); + } + else + { + const struct bt_gatt_include *included_service = attr->user_data; + struct bt_cap_common_client *client = + CONTAINER_OF(params, struct bt_cap_common_client, param); + + /* If the remote CAS includes CSIS, we first check if we + * have already discovered it, and if so we can just retrieve it and + * forward it to the application. If not, then we start CSIS discovery + */ + + client->csis_start_handle = included_service->start_handle; + client->csis_inst = + bt_csip_set_coordinator_csis_inst_by_handle(conn, + client->csis_start_handle); + if (client->csis_inst == NULL) + { + static struct bt_csip_set_coordinator_cb csis_client_cb = + { + .discover = csis_client_discover_cb, + }; + + static bool csis_cbs_registered; + int err; + + LOG_DBG("CAS CSIS not known, discovering"); + + if (!csis_cbs_registered) + { + err = bt_csip_set_coordinator_register_cb(&csis_client_cb); + DEBUGASSERT(err == 0); + + csis_cbs_registered = true; + } + + err = bt_csip_set_coordinator_discover(conn); + if (err != 0) + { + LOG_DBG("Discover failed (err %d)", err); + cap_common_discover_complete(conn, err, NULL, NULL); + } + } + else + { + const struct bt_csip_set_coordinator_set_member *member = + bt_csip_set_coordinator_set_member_by_conn(conn); + + LOG_DBG("Found CAS with CSIS"); + + cap_common_discover_complete(conn, 0, member, client->csis_inst); + } + } + + return BT_GATT_ITER_STOP; +} + +static uint8_t bt_cap_common_discover_cas_cb( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + if (attr == NULL) + { + cap_common_discover_complete(conn, -ENODATA, NULL, NULL); + } + else + { + const struct bt_gatt_service_val *prim_service = attr->user_data; + struct bt_cap_common_client *client = + CONTAINER_OF(params, struct bt_cap_common_client, param); + int err; + + client->conn = bt_conn_ref(conn); + + if (attr->handle == prim_service->end_handle) + { + LOG_DBG("Found CAS without CSIS"); + cap_common_discover_complete(conn, 0, NULL, NULL); + + return BT_GATT_ITER_STOP; + } + + LOG_DBG("Found CAS, discovering included CSIS"); + + params->uuid = NULL; + params->start_handle = attr->handle + 1; + params->end_handle = prim_service->end_handle; + params->type = BT_GATT_DISCOVER_INCLUDE; + params->func = bt_cap_common_discover_included_cb; + + err = bt_gatt_discover(conn, params); + if (err != 0) + { + LOG_DBG("Discover failed (err %d)", err); + + cap_common_discover_complete(conn, err, NULL, NULL); + } + } + + return BT_GATT_ITER_STOP; +} + +int bt_cap_common_discover(struct bt_conn *conn, + bt_cap_common_discover_func_t func) +{ + struct bt_gatt_discover_params *param; + struct bt_cap_common_client *client; + int err; + + client = bt_cap_common_get_client_by_acl(conn); + if (client->discover_cb_func != NULL) + { + return -EBUSY; + } + + param = &client->param; + param->func = bt_cap_common_discover_cas_cb; + param->uuid = cas_uuid; + param->type = BT_GATT_DISCOVER_PRIMARY; + param->start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; + param->end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + + client->discover_cb_func = func; + + err = bt_gatt_discover(conn, param); + if (err != 0) + { + client->discover_cb_func = NULL; + + /* Report expected possible errors */ + + if (err == -ENOTCONN || err == -ENOMEM) + { + return err; + } + + LOG_DBG("Unexpected err %d from bt_gatt_discover", err); + return -ENOEXEC; + } + + return 0; +} diff --git a/libs/zblue/audio/cap_handover.c b/libs/zblue/audio/cap_handover.c new file mode 100644 index 0000000000000..00705bb3e8df6 --- /dev/null +++ b/libs/zblue/audio/cap_handover.c @@ -0,0 +1,1439 @@ +/**************************************************************************** + * libs/zblue/audio/cap_handover.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bap_endpoint.h" +#include "cap_internal.h" +#include "common/bt_str.h" +#include "csip_internal.h" + +LOG_MODULE_REGISTER(bt_cap_handover, CONFIG_BT_CAP_HANDOVER_LOG_LEVEL); + +static const struct bt_cap_handover_cb *cap_cb; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +bool bt_cap_handover_is_handover_broadcast_source( + const struct bt_cap_broadcast_source *cap_broadcast_source) +{ + const struct bt_cap_common_proc *active_proc = + bt_cap_common_get_active_proc(); + const struct bt_cap_handover_proc_param *proc_param = + &active_proc->proc_param.handover; + bool ret; + + if (!bt_cap_common_active_proc_is_handover()) + { + ret = false; + } + else if (proc_param->is_unicast_to_broadcast) + { + ret = cap_broadcast_source == + proc_param->unicast_to_broadcast.broadcast_source; + } + else + { + ret = cap_broadcast_source == + proc_param->broadcast_to_unicast.broadcast_source; + } + + bt_cap_common_unlock_proc(); + + return ret; +} + +struct cap_unicast_group_stream_lookup +{ + struct bt_cap_stream + *active_sink_streams[CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT]; + struct bt_cap_stream + *streams[CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT]; + size_t active_sink_streams_cnt; + size_t cnt; +}; + +static bool unicast_group_foreach_stream_cb(struct bt_cap_stream *cap_stream, + void *user_data) +{ + struct cap_unicast_group_stream_lookup *data = user_data; + const struct bt_bap_stream *bap_stream = &cap_stream->bap_stream; + + DEBUGASSERT(data->cnt < ARRAY_SIZE(data->streams)); + DEBUGASSERT(data->active_sink_streams_cnt < ARRAY_SIZE( + data->active_sink_streams)); + + if (bap_stream->ep != NULL) + { + struct bt_bap_ep_info ep_info; + int err; + + err = bt_bap_ep_get_info(bap_stream->ep, &ep_info); + DEBUGASSERT(err == 0); + + /* Only consider sink streams for handover to broadcast */ + + if (ep_info.state == BT_BAP_EP_STATE_STREAMING && + ep_info.dir == BT_AUDIO_DIR_SINK) + { + data->active_sink_streams[data->active_sink_streams_cnt] = + cap_stream; + data->active_sink_streams_cnt++; + } + } + + data->streams[data->cnt] = cap_stream; + data->cnt++; + + return true; +} + +void bt_cap_handover_complete(struct bt_cap_common_proc *active_proc) +{ + struct bt_cap_handover_proc_param *proc_param = + &active_proc->proc_param.handover; + const bool is_unicast_to_broadcast = proc_param->is_unicast_to_broadcast; + struct bt_conn *failed_conn = active_proc->failed_conn; + struct bt_cap_broadcast_source *broadcast_source; + struct bt_cap_unicast_group *unicast_group; + const int err = active_proc->err; + + if (is_unicast_to_broadcast) + { + broadcast_source = proc_param->unicast_to_broadcast.broadcast_source; + unicast_group = proc_param->unicast_to_broadcast.unicast_group; + } + else + { + broadcast_source = proc_param->broadcast_to_unicast.broadcast_source; + unicast_group = proc_param->broadcast_to_unicast.unicast_group; + } + + bt_cap_common_clear_proc(active_proc); + + if (cap_cb != NULL) + { + if (is_unicast_to_broadcast) + { + if (cap_cb->unicast_to_broadcast_complete != NULL) + { + cap_cb->unicast_to_broadcast_complete(err, failed_conn, + unicast_group, + broadcast_source); + } + } + else + { + if (cap_cb->broadcast_to_unicast_complete != NULL) + { + cap_cb->broadcast_to_unicast_complete(err, failed_conn, + broadcast_source, + unicast_group); + } + } + } +} + +void bt_cap_handover_unicast_proc_complete( + struct bt_cap_common_proc *active_proc) +{ + const enum bt_cap_common_proc_type proc_type = active_proc->proc_type; + + if (proc_type == BT_CAP_COMMON_PROC_TYPE_STOP) + { + if (active_proc->err != 0) + { + bt_cap_handover_complete(active_proc); + } + else + { + /* continue */ + + const int err = + bt_cap_handover_unicast_to_broadcast_setup_broadcast( + active_proc); + + if (err != 0) + { + active_proc->err = err; + active_proc->failed_conn = NULL; + + bt_cap_handover_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + } + } + else if (proc_type == BT_CAP_COMMON_PROC_TYPE_START) + { + bt_cap_handover_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + + DEBUGASSERT(false); + } +} + +static int cap_handover_broadcast_audio_stopped( + struct bt_cap_common_proc *active_proc) +{ + struct bt_cap_handover_proc_param *proc_param = + &active_proc->proc_param.handover; + int err; + + /* This will be the case when we do broadcast to unicast handover and + * it successfully stops the broadcast + */ + + LOG_DBG("Deleting broadcast_source %p", + proc_param->broadcast_to_unicast.broadcast_source); + + err = + bt_cap_initiator_broadcast_audio_delete( + proc_param->broadcast_to_unicast.broadcast_source); + if (err != 0) + { + LOG_DBG("Failed to delete broadcast source: %d", err); + + return err; + } + + proc_param->broadcast_to_unicast.broadcast_source = NULL; + + err = + bt_cap_unicast_group_create( + proc_param->broadcast_to_unicast.unicast_group_param, + &proc_param->broadcast_to_unicast.unicast_group); + if (err != 0) + { + LOG_DBG("Failed to create unicast group: %d", err); + + return err; + } + + err = cap_initiator_unicast_audio_start(active_proc, + proc_param->broadcast_to_unicast.unicast_start_param); + if (err != 0) + { + LOG_DBG("Failed to start unicast audio: %d", err); + + return err; + } + + return 0; +} + +void bt_cap_handover_commander_proc_complete( + struct bt_cap_common_proc *active_proc) +{ + const enum bt_cap_common_proc_type proc_type = active_proc->proc_type; + + if (proc_type == BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_START) + { + bt_cap_handover_complete(active_proc); + } + else if (proc_type == BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_STOP) + { + /* Trigger next step (stopping broadcast source) */ + + const int err = + bt_cap_handover_broadcast_reception_stopped(active_proc); + + if (err != 0) + { + active_proc->err = err; + active_proc->failed_conn = NULL; + + bt_cap_handover_complete(active_proc); + + return; + } + + bt_cap_common_unlock_proc(); + } + else + { + DEBUGASSERT(false); + } +} + +void bt_cap_handover_broadcast_source_stopped(uint8_t reason) +{ + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + struct bt_cap_handover_proc_param *proc_param = + &active_proc->proc_param.handover; + + if (proc_param->is_unicast_to_broadcast) + { + LOG_DBG("Unexpected broadcast source %p stop with reason 0x%02x", + proc_param->unicast_to_broadcast.broadcast_source, reason); + + __maybe_unused const int err = bt_cap_initiator_broadcast_audio_delete( + proc_param->unicast_to_broadcast.broadcast_source); + + DEBUGASSERT(err == 0); + proc_param->unicast_to_broadcast.broadcast_source = NULL; + + active_proc->err = reason; + bt_cap_handover_complete(active_proc); + } + else + { + LOG_DBG("broadcast_source %p stopped with reason 0x%02x", + proc_param->broadcast_to_unicast.broadcast_source, reason); + + if (reason == BT_HCI_ERR_LOCALHOST_TERM_CONN) + { + /* Successfully stopped the broadcast source */ + + const int err = cap_handover_broadcast_audio_stopped(active_proc); + + if (err != 0) + { + active_proc->err = err; + active_proc->failed_conn = NULL; + + bt_cap_handover_complete(active_proc); + + return; + } + + bt_cap_common_unlock_proc(); + } + else + { + proc_param->broadcast_to_unicast.broadcast_source = NULL; + active_proc->err = reason; + bt_cap_handover_complete(active_proc); + } + } +} + +void bt_cap_handover_unicast_to_broadcast_reception_start(void) +{ + struct bt_cap_commander_broadcast_reception_start_param param = + { + 0 + }; + + struct bt_cap_initiator_broadcast_create_param *create_param; + struct bt_cap_handover_proc_param *proc_param; + struct bt_cap_common_proc *active_proc; + struct bt_le_ext_adv_info adv_info; + int err; + + active_proc = bt_cap_common_get_active_proc(); + proc_param = &active_proc->proc_param.handover; + create_param = proc_param->unicast_to_broadcast.broadcast_create_param; + param.type = proc_param->unicast_to_broadcast.type; + param.param = + proc_param->unicast_to_broadcast.reception_start_member_params; + + err = bt_le_ext_adv_get_info(proc_param->unicast_to_broadcast.ext_adv, + &adv_info); + DEBUGASSERT(err != -EINVAL); + if (err != 0) + { + /* May happen if the advertising set was deleted while in this + * procedure + */ + + LOG_DBG("Failed to get adv info: %d", err); + active_proc->err = err; + active_proc->failed_conn = NULL; + + bt_cap_handover_complete(active_proc); + + return; + } + + if (adv_info.ext_adv_state != BT_LE_EXT_ADV_STATE_ENABLED) + { + struct bt_le_ext_adv_info info; + + /* Start advertising to get the actual adv addr */ + + err = bt_le_ext_adv_start(proc_param->unicast_to_broadcast.ext_adv, + BT_LE_EXT_ADV_START_DEFAULT); + if (err != 0) + { + LOG_DBG("Failed to start advertising set (err %d)", err); + + active_proc->err = err; + active_proc->failed_conn = NULL; + + bt_cap_handover_complete(active_proc); + + return; + } + + err = bt_le_ext_adv_get_info(proc_param->unicast_to_broadcast.ext_adv, + &info); + DEBUGASSERT(err == 0); + + LOG_DBG("Started advertising with addr %s", bt_addr_le_str(info.addr)); + + /* Since bt_le_ext_adv_get_info returns the pointer to actual + * advertising address we do not need to call it again to get the + * address + */ + } + + /* else if we are already in the paused or active state and do not need to + * anything + */ + + ARRAY_FOR_EACH( + proc_param->unicast_to_broadcast.reception_start_member_params, i) + { + struct bt_cap_commander_broadcast_reception_start_member_param + *member_param = + &proc_param->unicast_to_broadcast.reception_start_member_params[ + i]; + const struct bt_conn *member_conn = bt_cap_common_get_member_conn( + proc_param->unicast_to_broadcast.type, &member_param->member); + +/* The member_conns are populated from index and upwards, thus once we + * reach a NULL pointer in the array, there are no more acceptors to + * send the reception start request to. + */ + + if (member_conn == NULL) + { + break; + } + + member_param->addr = bt_addr_le_any; + member_param->adv_sid = adv_info.sid; + member_param->pa_interval = proc_param->unicast_to_broadcast.pa_interval; + member_param->broadcast_id = + proc_param->unicast_to_broadcast.broadcast_id; + bt_addr_le_copy(&member_param->addr, adv_info.addr); + + member_param->num_subgroups = create_param->subgroup_count; + for (size_t j = 0U; j < member_param->num_subgroups; j++) + { + const struct bt_cap_initiator_broadcast_subgroup_param + *subgroup_param = + &create_param->subgroup_params[j]; + struct bt_bap_bass_subgroup *subgroup = &member_param->subgroups[j]; + + subgroup->metadata_len = subgroup_param->codec_cfg->meta_len; + (void)memcpy(subgroup->metadata, subgroup_param->codec_cfg->meta, + subgroup->metadata_len); + + /* The bis_sync value has been set up earlier in + * bt_cap_handover_unicast_to_broadcast while we still had the ACL + * references + */ + + DEBUGASSERT(subgroup->bis_sync != 0U); + } + + param.count++; + } + + err = cap_commander_broadcast_reception_start(active_proc, ¶m); + if (err != 0) + { + LOG_DBG("Failed to start reception start: %d", err); + + bt_cap_handover_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } +} + +int bt_cap_handover_unicast_to_broadcast_setup_broadcast( + struct bt_cap_common_proc *active_proc) +{ + struct bt_cap_initiator_broadcast_create_param *broadcast_create_param; + struct bt_cap_broadcast_source **broadcast_source; + struct bt_cap_handover_proc_param *proc_param; + struct bt_le_ext_adv *ext_adv; + int err; + + proc_param = &active_proc->proc_param.handover; + broadcast_create_param = + proc_param->unicast_to_broadcast.broadcast_create_param; + + broadcast_source = &proc_param->unicast_to_broadcast.broadcast_source; + + err = + bt_cap_unicast_group_delete( + proc_param->unicast_to_broadcast.unicast_group); + DEBUGASSERT(err == 0); + proc_param->unicast_to_broadcast.unicast_group = NULL; + + err = bt_cap_initiator_broadcast_audio_create(broadcast_create_param, + broadcast_source); + if (err != 0) + { + LOG_DBG("Failed to create broadcast source: %d", err); + + return err; + } + + if (cap_cb->unicast_to_broadcast_created != NULL) + { + /* Let application know the broadcast source was created so they can + * set up the periodic advertising data with the BASE + */ + + cap_cb->unicast_to_broadcast_created(*broadcast_source); + } + else + { + LOG_WRN("unicast_to_broadcast_created not set; BASE won't be " + "available " + "to peers " + "and procedure may not finish"); + } + + ext_adv = active_proc->proc_param.handover.unicast_to_broadcast.ext_adv; + err = bt_cap_initiator_broadcast_audio_start(*broadcast_source, ext_adv); + if (err != 0) + { + __maybe_unused int del_err; + + LOG_DBG("Failed to start broadcast source: %d", err); + + del_err = bt_cap_initiator_broadcast_audio_delete(*broadcast_source); + DEBUGASSERT(del_err == 0); + + return err; + } + + return 0; +} + +static bool valid_unicast_to_broadcast_stream_metadata_param( + const struct bt_cap_initiator_broadcast_subgroup_param *subgroup_param, + const struct bt_cap_stream *stream, + const struct cap_unicast_group_stream_lookup *lookup_data) +{ + const uint8_t *broadcast_ccid_list; + const uint8_t *unicast_ccid_list; + int broadcast_ret; + int unicast_ret; + + ARG_UNUSED(lookup_data); + + /* Compare existing unicast metadata with the subgroup param meteadata. It + * is mandatory that the CCID list and the context type remain the same + */ + + if (stream->bap_stream.codec_cfg == subgroup_param->codec_cfg) + { + return true; + } + + /* Verify CCID lists */ + + unicast_ret = + bt_audio_codec_cfg_meta_get_ccid_list(stream->bap_stream.codec_cfg, + &unicast_ccid_list); + + /* CCID list is not mandatory, so it is OK if it is missing, as long + * as it is missing for both unicast and broadcast + */ + + if (unicast_ret < 0 && unicast_ret != -ENODATA) + { + LOG_DBG("Could not get unicast CCID list: %d", unicast_ret); + return false; + } + + broadcast_ret = + bt_audio_codec_cfg_meta_get_ccid_list(subgroup_param->codec_cfg, + &broadcast_ccid_list); + + if (unicast_ret != broadcast_ret) + { + LOG_DBG("Could not get broadcast CCID list: %d != %d", unicast_ret, + broadcast_ret); + return false; + } + + /* we only need to compare if the list exists and is non-empty */ + + if (unicast_ret > 0 && !util_memeq(unicast_ccid_list, broadcast_ccid_list, + unicast_ret)) + { + LOG_DBG("Unicast and broadcast CCID lists are not the same"); + return false; + } + + /* Verify streaming contexts (mandatory to be in the metadata ) */ + + unicast_ret = + bt_audio_codec_cfg_meta_get_stream_context( + stream->bap_stream.codec_cfg); + if (unicast_ret <= 0) + { + /* mandatory to have a streaming context */ + + LOG_DBG("Could not get unicast stream context: %d", unicast_ret); + return false; + } + + broadcast_ret = + bt_audio_codec_cfg_meta_get_stream_context( + subgroup_param->codec_cfg); + if (broadcast_ret <= 0) + { + LOG_DBG("Could not get broadcast stream context: %d", broadcast_ret); + return false; + } + + if (unicast_ret != broadcast_ret) + { + LOG_DBG("Unicast and broadcast stream context differ: 0x%04X != " + "0x%04X", + unicast_ret, + broadcast_ret); + return false; + } + + return true; +} + +static bool +valid_unicast_to_broadcast_metadata( + const struct bt_cap_handover_unicast_to_broadcast_param *param, + const struct + cap_unicast_group_stream_lookup *lookup_data) +{ + size_t unique_metadata_cnt = 0U; + + /* Count unique metadata from the unicast streams to verify the correct + * number of subgroups exist. Each unique set of metadata needs to be in + * its own group + */ + + for (size_t i = 0U; i < lookup_data->active_sink_streams_cnt; i++) + { + const struct bt_bap_stream *bap_stream_i = + &lookup_data->active_sink_streams[i]->bap_stream; + const struct bt_audio_codec_cfg *codec_cfg_i = bap_stream_i->codec_cfg; + bool unique_metadata = true; + + for (size_t j = 0U; j < i; j++) + { + const struct bt_bap_stream *bap_stream_j = + &lookup_data->active_sink_streams[j]->bap_stream; + const struct bt_audio_codec_cfg *codec_cfg_j = + bap_stream_j->codec_cfg; + + if (util_eq(codec_cfg_i->meta, codec_cfg_i->meta_len, + codec_cfg_j->meta, + codec_cfg_j->meta_len)) + { + unique_metadata = false; + break; + } + } + + if (unique_metadata) + { + unique_metadata_cnt++; + } + + if (unique_metadata_cnt > CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT) + { + LOG_DBG("Cannot create broadcast source with %zu subgroups (max " + "%d)", + unique_metadata_cnt, + CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT); + + return false; + } + + if (unique_metadata_cnt > + param->broadcast_create_param->subgroup_count) + { + LOG_DBG("Mismatch between unique metadata from unicast (%zu) and " + "number of " + "subgroups (%zu)", + unique_metadata_cnt, + param->broadcast_create_param->subgroup_count); + + return false; + } + } + + return true; +} + +static bool valid_unicast_to_broadcast_create_param( + const struct bt_cap_handover_unicast_to_broadcast_param *param, + const struct cap_unicast_group_stream_lookup *lookup_data) +{ + size_t total_broadcast_streams = 0U; + + for (size_t i = 0U; i < param->broadcast_create_param->subgroup_count; i++) + { + const struct + bt_cap_initiator_broadcast_subgroup_param *subgroup_param = + ¶m->broadcast_create_param->subgroup_params[i]; + + for (size_t j = 0U; j < subgroup_param->stream_count; j++) + { + const struct bt_cap_stream *stream = + subgroup_param->stream_params[j].stream; + bool stream_is_handed_over = false; + + if (stream == NULL) + { + LOG_DBG("subgroup_param[%zu].stream_params[%zu].stream is " + "NULL", + i, j); + return false; + } + + if (stream->bap_stream.group != + param->unicast_group->bap_unicast_group) + { + LOG_DBG("Stream %p is not part of the unicast group " + "%p", stream, + param->unicast_group->bap_unicast_group); + return false; + } + + for (size_t k = 0U; k < lookup_data->active_sink_streams_cnt; k++) + { + if (stream == lookup_data->active_sink_streams[k]) + { + stream_is_handed_over = true; + break; + } + } + + if (!stream_is_handed_over) + { + LOG_DBG("Stream %p was not in unicast group %p", stream, + param->unicast_group); + return false; + } + + if ( + !valid_unicast_to_broadcast_stream_metadata_param(subgroup_param, + stream, + lookup_data)) + { + return false; + } + + total_broadcast_streams++; + } + } + + if (total_broadcast_streams != lookup_data->active_sink_streams_cnt) + { + LOG_DBG("Not all unicast sink streams are being handed over to " + "broadcast (%zu != " + "%zu)", + total_broadcast_streams, lookup_data->active_sink_streams_cnt); + return false; + } + + return true; +} + +static bool +valid_unicast_to_broadcast_param( + const struct bt_cap_handover_unicast_to_broadcast_param *param, + struct + cap_unicast_group_stream_lookup *lookup_data) +{ + struct bt_le_ext_adv_info adv_info; + int err; + + if (param == NULL) + { + LOG_DBG("param is NULL"); + return false; + } + + if (param->unicast_group == NULL) + { + LOG_DBG("param->unicast_group is NULL"); + return false; + } + + if ( + !bt_cap_initiator_broadcast_audio_start_valid_param( + param->broadcast_create_param)) + { + LOG_DBG("param->broadcast_create_param is invalid"); + return false; + } + + if (param->ext_adv == NULL) + { + LOG_DBG("param->ext_adv is NULL"); + return false; + } + + err = bt_le_ext_adv_get_info(param->ext_adv, &adv_info); + DEBUGASSERT(err == 0); + + if (adv_info.per_adv_state == BT_LE_PER_ADV_STATE_NONE) + { + LOG_DBG("Advertising set %p not configured for periodic advertising", + param->ext_adv); + return false; + } + + if (param->broadcast_id > BT_AUDIO_BROADCAST_ID_MAX) + { + LOG_DBG("param->broadcast_id is invalid: 0x%08X", param->broadcast_id); + return false; + } + + if (!IN_RANGE(param->pa_interval, BT_GAP_PER_ADV_MIN_INTERVAL, + BT_GAP_PER_ADV_MAX_INTERVAL)) + { + LOG_DBG("param->pa_interval is invalid: %u", param->pa_interval); + return false; + } + + err = bt_cap_unicast_group_foreach_stream(param->unicast_group, + unicast_group_foreach_stream_cb, + lookup_data); + DEBUGASSERT(err == 0); + if (lookup_data->active_sink_streams_cnt == 0U) + { + LOG_DBG("param->unicast_group does not contain any active streams"); + + return false; + } + + if (!valid_unicast_to_broadcast_create_param(param, lookup_data)) + { + return false; + } + + if (!valid_unicast_to_broadcast_metadata(param, lookup_data)) + { + return false; + } + + return true; +} + +int bt_cap_handover_unicast_to_broadcast( + const struct bt_cap_handover_unicast_to_broadcast_param *param) +{ + struct cap_unicast_group_stream_lookup lookup_data = + { + 0 + }; + + struct bt_cap_unicast_audio_stop_param stop_param = + { + 0 + }; + + struct bt_cap_handover_proc_param *proc_param; + struct bt_cap_common_proc *active_proc; + uint8_t bis_index; + int err; + + if (!valid_unicast_to_broadcast_param(param, &lookup_data)) + { + return -EINVAL; + } + + /* TBD: How do we prevent the unicast group from being modified or deleted + * while we are doing this procedure? TBD: How do we check for BASS? + */ + + if (lookup_data.cnt == 0U) + { + LOG_DBG("param->unicast_group does not contain any streams"); + + return -EINVAL; + } + + active_proc = bt_cap_common_get_active_proc(); + + if (bt_cap_common_test_and_set_proc_active()) + { + bt_cap_common_unlock_proc(); + + LOG_DBG("A CAP procedure is already in progress"); + + return -EBUSY; + } + + proc_param = &active_proc->proc_param.handover; + + /* Populate an array of unique connection pointers to determine which + * acceptors to add the broadcast source to + */ + + for (size_t i = 0U; i < lookup_data.active_sink_streams_cnt; i++) + { + const struct bt_cap_stream *stream = + lookup_data.active_sink_streams[i]; + bool conn_added; + + /* Add stream's conn to conns if not already there */ + + conn_added = false; + ARRAY_FOR_EACH( + proc_param->unicast_to_broadcast.reception_start_member_params, + j) + { + struct bt_cap_commander_broadcast_reception_start_member_param + *member_param; + + member_param = &proc_param->unicast_to_broadcast. + reception_start_member_params[j]; + const struct bt_conn *member_conn = + bt_cap_common_get_member_conn(param->type, + &member_param->member); + + if (member_conn == stream->bap_stream.conn) + { + conn_added = true; + break; + } + + if (member_conn == NULL) + { + if (param->type == BT_CAP_SET_TYPE_CSIP) + { + struct bt_cap_common_client *client = + bt_cap_common_get_client_by_acl( + stream->bap_stream.conn); + + member_param->member.csip = + bt_csip_set_coordinator_csis_inst_by_handle( + stream->bap_stream.conn, client->csis_start_handle); + } + else + { + member_param->member.member = stream->bap_stream.conn; + conn_added = true; + break; + } + } + } + + if (!conn_added) + { + LOG_DBG("Stream %p connection could not be added. " + "Some streams contain an invalid conn pointer", + stream); + + bt_cap_common_clear_proc(active_proc); + + return -EINVAL; + } + } + + /* We need to set up the expected BIS index fields for the reception start + * now, as once the unicast group has been stopped, the reference to the + * ACL connection from the stream will be lost + */ + + bis_index = 1U; /* BIS indexes start from 1 */ + for (size_t i = 0U; i < param->broadcast_create_param->subgroup_count; i++) + { + const struct + bt_cap_initiator_broadcast_subgroup_param *subgroup_param = + ¶m->broadcast_create_param->subgroup_params[i]; + + for (size_t j = 0U; j < subgroup_param->stream_count; j++) + { + const struct bt_cap_stream *stream = + subgroup_param->stream_params[j].stream; + + ARRAY_FOR_EACH_PTR( + proc_param->unicast_to_broadcast.reception_start_member_params, + member_param) + { + const struct bt_conn *member_conn = + bt_cap_common_get_member_conn(param->type, + &member_param->member); + + /* Once we reach a NULL connection pointer, we've handled all + * acceptors from the unicast group + */ + + if (member_conn == NULL) + { + break; + } + + if (stream->bap_stream.conn == member_conn) + { + member_param->subgroups[i].bis_sync |= + BT_ISO_BIS_INDEX_BIT(bis_index); + } + } + + bis_index++; + } + } + + /* Store the broadcast parameters for later */ + + proc_param->unicast_to_broadcast.broadcast_create_param = + param->broadcast_create_param; + proc_param->unicast_to_broadcast.unicast_group = param->unicast_group; + proc_param->unicast_to_broadcast.ext_adv = param->ext_adv; + proc_param->unicast_to_broadcast.type = param->type; + proc_param->unicast_to_broadcast.pa_interval = param->pa_interval; + proc_param->unicast_to_broadcast.broadcast_id = param->broadcast_id; + + stop_param.type = param->type; + stop_param.release = true; + stop_param.streams = lookup_data.streams; + stop_param.count = lookup_data.cnt; + + bt_cap_common_set_handover_active(); + proc_param->is_unicast_to_broadcast = true; + + DEBUGASSERT(bt_cap_initiator_valid_unicast_audio_stop_param(&stop_param)); + + err = cap_initiator_unicast_audio_stop(active_proc, &stop_param); + if (err != 0) + { + LOG_DBG("Failed to stop unicast audio: %d", err); + + bt_cap_common_clear_proc(active_proc); + + return -ENOEXEC; + } + + bt_cap_common_unlock_proc(); + + return 0; +} + +struct cap_broadcast_source_stream_lookup +{ + struct bt_cap_stream *streams[CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT]; + size_t cnt; +}; + +static bool broadcast_source_foreach_stream_cb( + struct bt_cap_stream *cap_stream, void *user_data) +{ + struct cap_broadcast_source_stream_lookup *data = user_data; + const struct bt_bap_stream *bap_stream = &cap_stream->bap_stream; + + DEBUGASSERT(data->cnt < ARRAY_SIZE(data->streams)); + + if (!bt_cap_initiator_stream_is_in_state(bap_stream, + BT_BAP_EP_STATE_STREAMING)) + { + LOG_DBG("Stream %p is in invalid state %s", cap_stream, + bt_bap_ep_state_str(bt_cap_initiator_stream_get_state( + bap_stream))); + return false; + } + + data->streams[data->cnt] = cap_stream; + data->cnt++; + + return true; +} + +static bool valid_valid_broadcast_to_unicast_unicast_start_param( + const struct bt_cap_handover_broadcast_to_unicast_param *param, + const struct cap_broadcast_source_stream_lookup *lookup_data) +{ + for (size_t i = 0U; i < param->unicast_start_param->count; i++) + { + const struct bt_cap_unicast_audio_start_stream_param *stream_param = + ¶m->unicast_start_param->stream_params[i]; + const struct bt_cap_stream *cap_stream = stream_param->stream; + const struct bt_bap_stream *bap_stream = &cap_stream->bap_stream; + bool stream_is_handed_over = false; + + if (!bt_cap_initiator_stream_is_in_state(bap_stream, + BT_BAP_EP_STATE_STREAMING)) + { + LOG_DBG("Stream %p is in invalid state %s", cap_stream, + bt_bap_ep_state_str( + bt_cap_initiator_stream_get_state(bap_stream))); + return false; + } + + if (bap_stream->group != param->broadcast_source->bap_broadcast) + { + LOG_DBG("Stream %p is not in the broadcast source", cap_stream); + return false; + } + + for (size_t j = 0U; j < lookup_data->cnt; j++) + { + if (cap_stream == lookup_data->streams[j]) + { + stream_is_handed_over = true; + break; + } + } + + if (!stream_is_handed_over) + { + LOG_DBG("Stream %p was not in broadcast_source %p", cap_stream, + param->broadcast_source); + return false; + } + + /* If we are doing reception stop, verify that all unicast streams + * being started are also stopping a broadcast reception + */ + + if (param->reception_stop_param != NULL) + { + bool reception_stopped = false; + + for (size_t j = 0U; j < param->reception_stop_param->count; j++) + { + const struct bt_conn *member_conn = + bt_cap_common_get_member_conn( + param->reception_stop_param->type, + &stream_param->member); + const struct bt_conn *other_conn = + bt_cap_common_get_member_conn( + param->reception_stop_param->type, + ¶m->reception_stop_param->param[j].member); + + if (member_conn == other_conn) + { + reception_stopped = true; + break; + } + } + + if (!reception_stopped) + { + return false; + } + } + } + + return true; +} + +static bool +valid_broadcast_to_unicast_param( + const struct bt_cap_handover_broadcast_to_unicast_param *param) +{ + struct cap_broadcast_source_stream_lookup lookup_data = + { + 0 + }; + + int err; + + if (param == NULL) + { + LOG_DBG("param is NULL"); + return false; + } + + if (param->broadcast_source == NULL) + { + LOG_DBG("param->broadcast_source is NULL"); + return false; + } + + if (!bt_cap_initiator_valid_unicast_group_param( + param->unicast_group_param)) + { + LOG_DBG("param->unicast_group_param is invalid"); + return false; + } + + if (!bt_cap_initiator_valid_unicast_audio_start_param( + param->unicast_start_param)) + { + LOG_DBG("param->unicast_start_param is invalid"); + return false; + } + + if (param->reception_stop_param == NULL) + { + if (param->broadcast_id > BT_AUDIO_BROADCAST_ID_MAX) + { + LOG_DBG("param->broadcast_id is invalid: 0x%08X", + param->broadcast_id); + return false; + } + + if (param->adv_sid > BT_GAP_SID_MAX) + { + LOG_DBG("param->adv_sid is larger than %d", BT_GAP_SID_MAX); + return false; + } + + if (param->adv_type != BT_ADDR_LE_PUBLIC && + param->adv_type != BT_ADDR_LE_RANDOM) + { + LOG_DBG("Invalid address type %u", param->adv_type); + return false; + } + } + else + { + if (!bt_cap_commander_valid_broadcast_reception_stop_param( + param->reception_stop_param)) + { + LOG_DBG("param->reception_stop_param is invalid"); + return false; + } + + if (param->unicast_start_param->type != + param->reception_stop_param->type) + { + LOG_DBG("Mismatching types for param->unicast_start_param (%d) " + "and " + "param->reception_stop_param (%d)", + param->unicast_start_param->type, + param->reception_stop_param->type); + return false; + } + } + + err = bt_cap_initiator_broadcast_foreach_stream( + param->broadcast_source, broadcast_source_foreach_stream_cb, + (void *)&lookup_data); + if (err == -ECANCELED) + { + LOG_DBG("param->broadcast_source contain non-active streams"); + + return false; + } + + DEBUGASSERT(err == 0); + + if (lookup_data.cnt == 0U) + { + LOG_DBG("param->broadcast_source does not contain any streams"); + + return false; + } + + if (lookup_data.cnt != param->unicast_start_param->count) + { + LOG_DBG("Invalid unicast_start_param->count %u, expected %u", + param->unicast_start_param->count, lookup_data.cnt); + + return false; + } + + if (!valid_valid_broadcast_to_unicast_unicast_start_param(param, + &lookup_data)) + { + LOG_DBG("param->unicast_start_param is invalid"); + + return false; + } + + return true; +} + +int bt_cap_handover_broadcast_reception_stopped( + struct bt_cap_common_proc *active_proc) +{ + struct bt_cap_handover_proc_param *proc_param; + int err; + + proc_param = &active_proc->proc_param.handover; + + err = + bt_cap_initiator_broadcast_audio_stop( + proc_param->broadcast_to_unicast.broadcast_source); + if (err != 0) + { + LOG_DBG("Failed to stop broadcast source: %d", err); + active_proc->err = err; + } + + /* Else wait for broadcast stopped */ + + return err; +} + +int bt_cap_handover_broadcast_to_unicast( + const struct bt_cap_handover_broadcast_to_unicast_param *param) +{ + struct bt_cap_handover_proc_param *proc_param; + struct bt_cap_common_proc *active_proc; + int err; + +/* This will perform the following operations 1) Check parameters 2) + * Broadcast Reception Stop (optional) 3) Broadcast Source Stop 4) + * Broadcast Source Delete 5) Unicast Group create 6) Unicast Audio Start + */ + + if (!valid_broadcast_to_unicast_param(param)) + { + return -EINVAL; + } + + active_proc = bt_cap_common_get_active_proc(); + + if (bt_cap_common_test_and_set_proc_active()) + { + bt_cap_common_unlock_proc(); + + LOG_DBG("A CAP procedure is already in progress"); + + return -EBUSY; + } + + proc_param = &active_proc->proc_param.handover; + proc_param->broadcast_to_unicast.unicast_group_param = + param->unicast_group_param; + proc_param->broadcast_to_unicast.unicast_start_param = + param->unicast_start_param; + proc_param->broadcast_to_unicast.broadcast_source = + param->broadcast_source; + proc_param->broadcast_to_unicast.broadcast_id = param->broadcast_id; + proc_param->broadcast_to_unicast.adv_sid = param->adv_sid; + proc_param->broadcast_to_unicast.adv_type = param->adv_type; + + bt_cap_common_set_handover_active(); + proc_param->is_unicast_to_broadcast = false; + + if (param->reception_stop_param == NULL) + { + /* Register the broadcast assistant callbacks to receive the BASS + * receive states + */ + + cap_commander_register_broadcast_assistant_callbacks(); + + err = bt_cap_handover_broadcast_reception_stopped(active_proc); + } + else + { + err = cap_commander_broadcast_reception_stop(active_proc, + param->reception_stop_param); + } + + if (err != 0) + { + bt_cap_common_clear_proc(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return err; +} + +int bt_cap_handover_register_cb(const struct bt_cap_handover_cb *cb) +{ + if (cb == NULL) + { + LOG_DBG("cb is NULL"); + return -EINVAL; + } + + if (cap_cb != NULL) + { + LOG_DBG("callbacks already registered"); + return -EALREADY; + } + + cap_cb = cb; + + return 0; +} + +int bt_cap_handover_unregister_cb(const struct bt_cap_handover_cb *cb) +{ + if (cb == NULL) + { + LOG_DBG("cb is NULL"); + return -EINVAL; + } + + if (cap_cb != cb) + { + LOG_DBG("cb is not registered"); + return -EINVAL; + } + + cap_cb = NULL; + + return 0; +} + +int bt_cap_handover_cancel(void) +{ + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + + if (!bt_cap_common_proc_is_active() && !bt_cap_common_proc_is_aborted()) + { + bt_cap_common_unlock_proc(); + + LOG_DBG("No CAP procedure is in progress"); + + return -EALREADY; + } + + if (!bt_cap_common_active_proc_is_handover()) + { + bt_cap_common_unlock_proc(); + + LOG_DBG("No CAP Handover procedure is in progress"); + + return -EOPNOTSUPP; + } + + bt_cap_common_abort_proc(NULL, -ECANCELED); + bt_cap_handover_complete(active_proc); + + return 0; +} diff --git a/libs/zblue/audio/cap_initiator.c b/libs/zblue/audio/cap_initiator.c new file mode 100644 index 0000000000000..51ddc6484d715 --- /dev/null +++ b/libs/zblue/audio/cap_initiator.c @@ -0,0 +1,3894 @@ +/**************************************************************************** + * libs/zblue/audio/cap_initiator.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022-2025 Nordic Semiconductor ASA + * Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "bap_endpoint.h" +#include "cap_internal.h" +#include "csip_internal.h" + +LOG_MODULE_REGISTER(bt_cap_initiator, CONFIG_BT_CAP_INITIATOR_LOG_LEVEL); + +#include "common/bt_str.h" + +static const struct bt_cap_initiator_cb *cap_cb; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bt_cap_initiator_register_cb(const struct bt_cap_initiator_cb *cb) +{ + if (cb == NULL) + { + LOG_DBG("cb is NULL"); + return -EINVAL; + } + + if (cap_cb != NULL) + { + LOG_DBG("callbacks already registered"); + return -EALREADY; + } + + cap_cb = cb; + + return 0; +} + +int bt_cap_initiator_unregister_cb(const struct bt_cap_initiator_cb *cb) +{ + if (cb == NULL) + { + LOG_DBG("cb is NULL"); + return -EINVAL; + } + + if (cap_cb != cb) + { + LOG_DBG("cb is not registered"); + return -EINVAL; + } + + cap_cb = NULL; + + return 0; +} + +struct valid_metadata_param +{ + bool stream_context_found; + bool valid; +}; + +static bool data_func_cb(struct bt_data *data, void *user_data) +{ + struct valid_metadata_param *metadata_param = + (struct valid_metadata_param *)user_data; + + LOG_DBG("type %u len %u data %s", data->type, data->data_len, + bt_hex(data->data, data->data_len)); + + if (data->type == BT_AUDIO_METADATA_TYPE_STREAM_CONTEXT) + { + if (data->data_len != 2) + { + /* Stream context size */ + + return false; + } + + metadata_param->stream_context_found = true; + } + else if (IS_ENABLED(CONFIG_BT_CONN) && + data->type == BT_AUDIO_METADATA_TYPE_CCID_LIST) + { + /* If the application supplies a CCID list, we verify that the CCIDs + * exist on our device This is guarded by CONFIG_BT_CONN as without + * that we do not have a GATT DB to check. + */ + + for (uint8_t i = 0U; i < data->data_len; i++) + { + const uint8_t ccid = data->data[i]; + + if (bt_ccid_find_attr(ccid) == NULL) + { + LOG_DBG("Unknown characteristic for CCID 0x%02X", ccid); + metadata_param->valid = false; + + return false; + } + } + } + + return true; +} + +static bool cap_initiator_valid_metadata(const uint8_t meta[], + size_t meta_len) +{ + struct valid_metadata_param metadata_param = + { + .stream_context_found = false, + .valid = true, + }; + + int err; + + LOG_DBG("meta %p len %zu", (void *)meta, meta_len); + + err = bt_audio_data_parse(meta, meta_len, data_func_cb, &metadata_param); + if (err != 0 && err != -ECANCELED) + { + return false; + } + + if (!metadata_param.stream_context_found) + { + LOG_DBG("No streaming context supplied"); + } + + return metadata_param.stream_context_found && metadata_param.valid; +} + +#if defined(CONFIG_BT_BAP_BROADCAST_SOURCE) +static struct bt_cap_broadcast_source + broadcast_sources[CONFIG_BT_BAP_BROADCAST_SRC_COUNT]; + +bool bt_cap_initiator_broadcast_audio_start_valid_param( + const struct bt_cap_initiator_broadcast_create_param *param) +{ + if (param == NULL) + { + LOG_DBG("param is NULL"); + return false; + } + + if (param->subgroup_params == NULL) + { + LOG_DBG("subgroup_params is NULL"); + return false; + } + + if (param->subgroup_count == 0U) + { + LOG_DBG("subgroup_count is 0"); + return false; + } + + for (size_t i = 0U; i < param->subgroup_count; i++) + { + const struct bt_cap_initiator_broadcast_subgroup_param *subgroup_param; + const struct bt_audio_codec_cfg *codec_cfg; + bool valid_metadata; + + subgroup_param = ¶m->subgroup_params[i]; + codec_cfg = subgroup_param->codec_cfg; + + /* Streaming Audio Context shall be present in CAP */ + + if (codec_cfg == NULL) + { + LOG_DBG("subgroup[%zu]->codec_cfg is NULL", i); + return false; + } + + valid_metadata = cap_initiator_valid_metadata(codec_cfg->meta, + codec_cfg->meta_len); + + if (!valid_metadata) + { + LOG_DBG("Invalid metadata supplied for subgroup[%zu]", i); + return false; + } + } + + return true; +} + +static void cap_initiator_broadcast_to_bap_broadcast_param( + const struct bt_cap_initiator_broadcast_create_param *cap_param, + struct bt_bap_broadcast_source_param *bap_param, + struct bt_bap_broadcast_source_subgroup_param + bap_subgroup_params[CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT], + struct bt_bap_broadcast_source_stream_param + bap_stream_params[CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT]) +{ + size_t stream_cnt = 0U; + + bap_param->params_count = cap_param->subgroup_count; + bap_param->params = bap_subgroup_params; + bap_param->qos = cap_param->qos; + bap_param->packing = cap_param->packing; + bap_param->encryption = cap_param->encryption; + if (bap_param->encryption) + { + memcpy(bap_param->broadcast_code, cap_param->broadcast_code, + BT_ISO_BROADCAST_CODE_SIZE); + } + else + { + memset(bap_param->broadcast_code, 0, BT_ISO_BROADCAST_CODE_SIZE); + } +#if defined(CONFIG_BT_ISO_TEST_PARAMS) + + bap_param->irc = cap_param->irc; + bap_param->pto = cap_param->pto; + bap_param->iso_interval = cap_param->iso_interval; +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ + + for (size_t i = 0U; i < bap_param->params_count; i++) + { + const struct + bt_cap_initiator_broadcast_subgroup_param *cap_subgroup_param = + &cap_param->subgroup_params[i]; + struct bt_bap_broadcast_source_subgroup_param *bap_subgroup_param = + &bap_param->params[i]; + + bap_subgroup_param->codec_cfg = cap_subgroup_param->codec_cfg; + bap_subgroup_param->params_count = cap_subgroup_param->stream_count; + bap_subgroup_param->params = &bap_stream_params[stream_cnt]; + + for (size_t j = 0U; j < bap_subgroup_param->params_count; j++, + stream_cnt++) + { + const struct + bt_cap_initiator_broadcast_stream_param *cap_stream_param = + &cap_subgroup_param->stream_params[j]; + struct bt_bap_broadcast_source_stream_param *bap_stream_param = + &bap_subgroup_param->params[j]; + + bap_stream_param->stream = &cap_stream_param->stream->bap_stream; + bap_stream_param->data_len = cap_stream_param->data_len; + /* We do not need to copy the data, as that is the same type of + * struct, so we can just point to the CAP parameter data + */ + + bap_stream_param->data = cap_stream_param->data; + } + } +} + +int bt_cap_initiator_broadcast_audio_create( + const struct bt_cap_initiator_broadcast_create_param *param, + struct bt_cap_broadcast_source **broadcast_source) +{ + struct bt_bap_broadcast_source_subgroup_param + bap_subgroup_params[CONFIG_BT_BAP_BROADCAST_SRC_SUBGROUP_COUNT]; + struct bt_bap_broadcast_source_stream_param + bap_stream_params[CONFIG_BT_BAP_BROADCAST_SRC_STREAM_COUNT]; + struct bt_bap_broadcast_source_param bap_create_param = + { + 0 + }; + + int err; + + if (broadcast_source == NULL) + { + LOG_DBG("source is NULL"); + return -EINVAL; + } + + if (!bt_cap_initiator_broadcast_audio_start_valid_param(param)) + { + return -EINVAL; + } + + *broadcast_source = NULL; + for (size_t i = 0U; i < ARRAY_SIZE(broadcast_sources); i++) + { + if (broadcast_sources[i].bap_broadcast == NULL) + { + *broadcast_source = &broadcast_sources[i]; + break; + } + } + + if (*broadcast_source == NULL) + { + LOG_DBG("Failed to allocate a new broadcast source"); + return -ENOMEM; + } + + cap_initiator_broadcast_to_bap_broadcast_param(param, &bap_create_param, + bap_subgroup_params, + bap_stream_params); + + err = bt_bap_broadcast_source_create(&bap_create_param, + &(*broadcast_source)->bap_broadcast); + if (err != 0) + { + /* Return known errors */ + + if (err == -EINVAL || err == -ENOMEM) + { + return err; + } + + LOG_DBG("Unexpected error from bt_bap_broadcast_source_create: %d", + err); + + return -ENOEXEC; + } + + return 0; +} + +static struct bt_cap_broadcast_source * +get_cap_broadcast_source_by_bap_broadcast_source( + const struct bt_bap_broadcast_source *bap_broadcast_source) +{ + for (size_t i = 0U; i < ARRAY_SIZE(broadcast_sources); i++) + { + if (broadcast_sources[i].bap_broadcast == bap_broadcast_source) + { + return &broadcast_sources[i]; + } + } + + return NULL; +} + +static void broadcast_source_started_cb( + struct bt_bap_broadcast_source *bap_broadcast_source) +{ + struct bt_cap_broadcast_source *source = + get_cap_broadcast_source_by_bap_broadcast_source( + bap_broadcast_source); + + if (source == NULL) + { + /* Not one of ours */ + + return; + } + + if (IS_ENABLED(CONFIG_BT_CAP_HANDOVER) && + bt_cap_handover_is_handover_broadcast_source(source)) + { + bt_cap_handover_unicast_to_broadcast_reception_start(); + return; + } + + if (cap_cb != NULL && cap_cb->broadcast_started != NULL) + { + cap_cb->broadcast_started(source); + } +} + +static void broadcast_source_stopped_cb( + struct bt_bap_broadcast_source *bap_broadcast_source, uint8_t reason) +{ + struct bt_cap_broadcast_source *source = + get_cap_broadcast_source_by_bap_broadcast_source( + bap_broadcast_source); + + if (source == NULL) + { + /* Not one of ours */ + + return; + } + + if (IS_ENABLED(CONFIG_BT_CAP_HANDOVER) && + bt_cap_handover_is_handover_broadcast_source(source)) + { + bt_cap_handover_broadcast_source_stopped(reason); + return; + } + + if (cap_cb && cap_cb->broadcast_stopped) + { + cap_cb->broadcast_stopped(source, reason); + } +} + +int bt_cap_initiator_broadcast_audio_start( + struct bt_cap_broadcast_source *broadcast_source, + struct bt_le_ext_adv *adv) +{ + static bool broadcast_source_cbs_registered; + + if (adv == NULL) + { + LOG_DBG("adv is NULL"); + return -EINVAL; + } + + if (broadcast_source == NULL) + { + LOG_DBG("broadcast_source is NULL"); + return -EINVAL; + } + + if (!broadcast_source_cbs_registered) + { + static struct bt_bap_broadcast_source_cb broadcast_source_cb = + { + .started = broadcast_source_started_cb, + .stopped = broadcast_source_stopped_cb, + }; + + const int err = + bt_bap_broadcast_source_register_cb(&broadcast_source_cb); + + if (err != 0) + { + DEBUGASSERT(false); + } + + broadcast_source_cbs_registered = true; + } + + return bt_bap_broadcast_source_start(broadcast_source->bap_broadcast, adv); +} + +int bt_cap_initiator_broadcast_audio_update( + struct bt_cap_broadcast_source *broadcast_source, const uint8_t meta[], + size_t meta_len) +{ + if (broadcast_source == NULL) + { + LOG_DBG("broadcast_source is NULL"); + return -EINVAL; + } + + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + if (!cap_initiator_valid_metadata(meta, meta_len)) + { + LOG_DBG("Invalid metadata"); + return -EINVAL; + } + + return + bt_bap_broadcast_source_update_metadata(broadcast_source->bap_broadcast, + meta, meta_len); +} + +int bt_cap_initiator_broadcast_audio_stop( + struct bt_cap_broadcast_source *broadcast_source) +{ + if (broadcast_source == NULL) + { + LOG_DBG("broadcast_source is NULL"); + return -EINVAL; + } + + return bt_bap_broadcast_source_stop(broadcast_source->bap_broadcast); +} + +int bt_cap_initiator_broadcast_audio_delete( + struct bt_cap_broadcast_source *broadcast_source) +{ + int err; + + if (broadcast_source == NULL) + { + LOG_DBG("broadcast_source is NULL"); + return -EINVAL; + } + + err = bt_bap_broadcast_source_delete(broadcast_source->bap_broadcast); + if (err == 0) + { + broadcast_source->bap_broadcast = NULL; + } + + return err; +} + +int bt_cap_initiator_broadcast_get_base( + struct bt_cap_broadcast_source *broadcast_source, + struct net_buf_simple *base_buf) +{ + if (broadcast_source == NULL) + { + LOG_DBG("broadcast_source is NULL"); + return -EINVAL; + } + + return bt_bap_broadcast_source_get_base(broadcast_source->bap_broadcast, + base_buf); +} + +struct cap_broadcast_source_foreach_stream_data +{ + bt_cap_initiator_broadcast_foreach_stream_func_t func; + void *user_data; +}; + +static bool cap_broadcast_source_foreach_stream_cb( + struct bt_bap_stream *bap_stream, void *user_data) +{ + struct cap_broadcast_source_foreach_stream_data *data = user_data; + + /* Since we are iterating on a CAP broadcast source, we can assume that all + * streams are CAP streams + */ + + return data->func(CONTAINER_OF(bap_stream, struct bt_cap_stream, + bap_stream), + data->user_data); +} + +int bt_cap_initiator_broadcast_foreach_stream( + struct bt_cap_broadcast_source *broadcast_source, + bt_cap_initiator_broadcast_foreach_stream_func_t func, void *user_data) +{ + struct cap_broadcast_source_foreach_stream_data data = + { + .func = func, + .user_data = user_data, + }; + + if (broadcast_source == NULL) + { + LOG_DBG("source is NULL"); + return -EINVAL; + } + + if (func == NULL) + { + LOG_DBG("func is NULL"); + return -EINVAL; + } + + return + bt_bap_broadcast_source_foreach_stream(broadcast_source->bap_broadcast, + cap_broadcast_source_foreach_stream_cb, + &data); +} + +#endif /* CONFIG_BT_BAP_BROADCAST_SOURCE */ + +#if defined(CONFIG_BT_BAP_UNICAST_CLIENT) +static struct bt_cap_unicast_group + unicast_groups[CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_COUNT]; + +enum bt_bap_ep_state bt_cap_initiator_stream_get_state( + const struct bt_bap_stream *bap_stream) +{ + struct bt_bap_ep_info ep_info; + int err; + + if (bap_stream->ep == NULL) + { + return BT_BAP_EP_STATE_IDLE; + } + + err = bt_bap_ep_get_info(bap_stream->ep, &ep_info); + if (err != 0) + { + LOG_DBG("Failed to get endpoint info %p: %d", bap_stream, err); + + return BT_BAP_EP_STATE_IDLE; + } + + return ep_info.state; +} + +bool bt_cap_initiator_stream_is_in_state( + const struct bt_bap_stream *bap_stream, enum bt_bap_ep_state state) +{ + return bt_cap_initiator_stream_get_state(bap_stream) == state; +} + +static bool stream_is_dir(const struct bt_bap_stream *bap_stream, + enum bt_audio_dir dir) +{ + struct bt_bap_ep_info ep_info; + int err; + + if (bap_stream->conn == NULL) + { + return false; + } + + err = bt_bap_ep_get_info(bap_stream->ep, &ep_info); + if (err != 0) + { + LOG_DBG("Failed to get endpoint info %p: %d", bap_stream, err); + + return false; + } + + return ep_info.dir == dir; +} + +static enum bt_iso_state bap_stream_get_iso_state( + const struct bt_bap_stream *bap_stream) +{ + struct bt_bap_ep_info ep_info; + int err; + + if (bap_stream->ep == NULL) + { + return BT_ISO_STATE_DISCONNECTED; + } + + err = bt_bap_ep_get_info(bap_stream->ep, &ep_info); + if (err != 0) + { + LOG_DBG("Failed to get endpoint info %p: %d", bap_stream, err); + + return BT_ISO_STATE_DISCONNECTED; + } + + if (ep_info.iso_chan == NULL) + { + return BT_ISO_STATE_DISCONNECTED; + } + + return ep_info.iso_chan->state; +} + +static bool iso_is_in_state(const struct bt_cap_stream *cap_stream, + enum bt_iso_state state) +{ + const struct bt_bap_stream *bap_stream = &cap_stream->bap_stream; + + return bap_stream_get_iso_state(bap_stream) == state; +} + +static void set_cap_stream_in_progress( + struct bt_cap_common_proc *active_proc, struct bt_cap_stream *cap_stream, + bool value) +{ + for (size_t i = 0U; i < active_proc->proc_cnt; i++) + { + if (cap_stream == active_proc->proc_param.initiator[i].stream) + { + active_proc->proc_param.initiator[i].in_progress = value; + return; + } + } + + DEBUGASSERT(false); +} + +/** + * @brief Gets the next stream for the active procedure. + * + * Returns NULL if all streams are in the right state for the current step + */ + +static struct bt_cap_initiator_proc_param * +get_proc_param_by_cap_stream(struct bt_cap_common_proc *active_proc, + const struct bt_cap_stream *cap_stream) +{ + for (size_t i = 0U; i < active_proc->proc_cnt; i++) + { + if (active_proc->proc_param.initiator[i].stream == cap_stream) + { + return &active_proc->proc_param.initiator[i]; + } + } + + return NULL; +} + +/** + * @brief Compare two @ref bt_audio_codec_cfg and return whether they are + * equal, excluding metadata + * + * Since metadata is set at a different point in time than the actual codec + * configuration, it cannot be checked at the same time. + * + * @param a The first codec config to compare with + * @param b The second codec config to compare with + * + * @retval true @p a and @p b points to the same memory or all fields are + * identical. + * @retval false Either @p a or @p b is NULL or any of the fields are not + * identical. + */ + +static bool codec_cfg_and_data_eq(const struct bt_audio_codec_cfg *a, + const struct bt_audio_codec_cfg *b) +{ + if (a == NULL || b == NULL) + { + return false; + } + + if (a == b) + { + return true; + } + + /* Values like path_id, ctlr_transcode, target_latency and target_phy are + * local only, but the only way to update them in BAP is to perform a new + * codec configuration, so we need to consider them here too. + */ + + return a->path_id == b->path_id + && a->ctlr_transcode == b->ctlr_transcode && + a->target_latency == b->target_latency && + a->target_phy == b->target_phy && + a->id == b->id && a->cid == b->cid && a->vid == b->vid && + util_eq(a->data, a->data_len, b->data, b->data_len); +} + +static void update_proc_done_cnt(struct bt_cap_common_proc *active_proc) +{ + const enum bt_cap_common_subproc_type subproc_type = + active_proc->subproc_type; + const enum bt_cap_common_proc_type proc_type = active_proc->proc_type; + size_t proc_done_cnt = 0U; + + if (proc_type == BT_CAP_COMMON_PROC_TYPE_START) + { + /* To support state changes by the server, we cannot rely simply on the + * number of BAP procedures we have initiated. For the start and stop + * CAP procedures we use the states to determine how far we are. + */ + + for (size_t i = 0U; i < active_proc->proc_cnt; i++) + { + struct bt_cap_initiator_proc_param *proc_param; + struct bt_cap_stream *cap_stream; + struct bt_bap_stream *bap_stream; + enum bt_bap_ep_state state; + + proc_param = &active_proc->proc_param.initiator[i]; + + if (proc_param->in_progress) + { + continue; + } + + cap_stream = proc_param->stream; + bap_stream = &cap_stream->bap_stream; + + state = bt_cap_initiator_stream_get_state(bap_stream); + + switch (subproc_type) + { + case BT_CAP_COMMON_SUBPROC_TYPE_CODEC_CONFIG: + if (state > BT_BAP_EP_STATE_IDLE && + codec_cfg_and_data_eq(bap_stream->codec_cfg, + proc_param->start.codec_cfg)) + { + proc_done_cnt++; + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_QOS_CONFIG: + if (state > BT_BAP_EP_STATE_CODEC_CONFIGURED) + { + struct bt_bap_qos_cfg qos; + __maybe_unused int err; + + err = bt_bap_unicast_client_qos_from_group(bap_stream, + &qos); + /* May only happen if the stream is modified by another + * thread, in which case everything else may be broken... + */ + + DEBUGASSERT(err == 0); + if (bt_bap_qos_cfg_eq(bap_stream->qos, &qos)) + { + proc_done_cnt++; + } + } + else if (state < BT_BAP_EP_STATE_CODEC_CONFIGURED) + { + /* Unexpected state - Abort */ + + LOG_DBG("Unexpected state change"); + + bt_cap_common_abort_proc(bap_stream->conn, -EBADMSG); + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_ENABLE: + if (state > BT_BAP_EP_STATE_QOS_CONFIGURED && + util_eq(bap_stream->codec_cfg->meta, + bap_stream->codec_cfg->meta_len, + proc_param->start.codec_cfg->meta, + proc_param->start.codec_cfg->meta_len)) + { + proc_done_cnt++; + } + else if (state < BT_BAP_EP_STATE_QOS_CONFIGURED) + { + /* Unexpected state - Abort */ + + LOG_DBG("Unexpected state change"); + + bt_cap_common_abort_proc(bap_stream->conn, -EBADMSG); + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_CONNECT: + if (state < BT_BAP_EP_STATE_ENABLING) + { + /* Unexpected state - Abort */ + + LOG_DBG("Unexpected state change"); + + bt_cap_common_abort_proc(bap_stream->conn, -EBADMSG); + } + else if (proc_param->start.connected) + { + proc_done_cnt++; + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_START: + if (state > BT_BAP_EP_STATE_ENABLING) + { + proc_done_cnt++; + } + else if (state < BT_BAP_EP_STATE_ENABLING || + !iso_is_in_state(cap_stream, + BT_ISO_STATE_CONNECTED)) + { + /* Unexpected state - Abort */ + + LOG_DBG("Unexpected state change"); + + bt_cap_common_abort_proc(bap_stream->conn, -EBADMSG); + } + break; + default: + DEBUGASSERT(false); + } + } + } + else if (proc_type == BT_CAP_COMMON_PROC_TYPE_STOP) + { + /* To support state changes by the server, we cannot rely simply on the + * number of BAP procedures we have initiated. For the start and stop + * CAP procedures we use the states to determine how far we are. + */ + + for (size_t i = 0U; i < active_proc->proc_cnt; i++) + { + struct bt_cap_initiator_proc_param *proc_param; + struct bt_cap_stream *cap_stream; + struct bt_bap_stream *bap_stream; + enum bt_bap_ep_state state; + + proc_param = &active_proc->proc_param.initiator[i]; + cap_stream = proc_param->stream; + bap_stream = &cap_stream->bap_stream; + + state = bt_cap_initiator_stream_get_state(bap_stream); + + switch (subproc_type) + { + case BT_CAP_COMMON_SUBPROC_TYPE_DISABLE: + if (state == BT_BAP_EP_STATE_QOS_CONFIGURED || + state == BT_BAP_EP_STATE_DISABLING) + { + proc_done_cnt++; + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_STOP: + if (state == BT_BAP_EP_STATE_QOS_CONFIGURED) + { + proc_done_cnt++; + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_RELEASE: + if (proc_param->stop.completed) + { + proc_done_cnt++; + } + break; + default: + DEBUGASSERT(false); + } + } + } + else if (proc_type == BT_CAP_COMMON_PROC_TYPE_UPDATE) + { + /* For metadata we cannot check the states for all streams, as it does + * not trigger a state change + */ + + struct bt_cap_initiator_proc_param *proc_param; + struct bt_cap_stream *cap_stream; + struct bt_bap_stream *bap_stream; + enum bt_bap_ep_state state; + + proc_param = + &active_proc->proc_param.initiator[active_proc->proc_done_cnt]; + cap_stream = proc_param->stream; + bap_stream = &cap_stream->bap_stream; + + state = bt_cap_initiator_stream_get_state(bap_stream); + + switch (subproc_type) + { + case BT_CAP_COMMON_SUBPROC_TYPE_META_UPDATE: + if (state == BT_BAP_EP_STATE_ENABLING || + state == BT_BAP_EP_STATE_STREAMING) + { + if (util_eq(bap_stream->codec_cfg->meta, + bap_stream->codec_cfg->meta_len, + proc_param->meta_update.meta, + proc_param->meta_update.meta_len)) + { + proc_done_cnt = active_proc->proc_done_cnt + 1U; + } + } + else + { + /* Unexpected state - Abort */ + + LOG_DBG("Unexpected state change"); + + bt_cap_common_abort_proc(bap_stream->conn, -EBADMSG); + } + break; + default: + DEBUGASSERT(false); + } + } + + active_proc->proc_done_cnt = proc_done_cnt; + + LOG_DBG("proc %d subproc %d: %zu/%zu", proc_type, subproc_type, + active_proc->proc_done_cnt, + active_proc->proc_cnt); +} + +/** + * @brief Gets the next stream for the active procedure. + * + * Returns NULL if all streams are in the right state for the current step + */ + +static struct bt_cap_initiator_proc_param * +get_next_proc_param(struct bt_cap_common_proc *active_proc) +{ + const enum bt_cap_common_subproc_type subproc_type = + active_proc->subproc_type; + + for (size_t i = 0U; i < active_proc->proc_cnt; i++) + { + struct bt_cap_initiator_proc_param *proc_param; + struct bt_cap_stream *cap_stream; + struct bt_bap_stream *bap_stream; + enum bt_bap_ep_state state; + + proc_param = &active_proc->proc_param.initiator[i]; + + if (proc_param->in_progress) + { + continue; + } + + cap_stream = proc_param->stream; + bap_stream = &cap_stream->bap_stream; + state = bt_cap_initiator_stream_get_state(bap_stream); + + switch (subproc_type) + { + case BT_CAP_COMMON_SUBPROC_TYPE_CODEC_CONFIG: + DEBUGASSERT(active_proc->proc_type == + BT_CAP_COMMON_PROC_TYPE_START); + + if (state == BT_BAP_EP_STATE_IDLE) + { + return proc_param; + } + + if (state == BT_BAP_EP_STATE_CODEC_CONFIGURED && + bap_stream->codec_cfg != NULL && + !codec_cfg_and_data_eq(bap_stream->codec_cfg, + proc_param->start.codec_cfg)) + { + return proc_param; + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_QOS_CONFIG: + DEBUGASSERT(active_proc->proc_type == + BT_CAP_COMMON_PROC_TYPE_START); + + if (state == BT_BAP_EP_STATE_CODEC_CONFIGURED) + { + return proc_param; + } + + if (state == BT_BAP_EP_STATE_QOS_CONFIGURED && + bap_stream->qos != NULL) + { + struct bt_bap_qos_cfg qos; + __maybe_unused int err; + + err = bt_bap_unicast_client_qos_from_group(bap_stream, &qos); + /* May only happen if the stream is modified by another + * thread, + * in which case everything else may be broken... + */ + + DEBUGASSERT(err == 0); + if (!bt_bap_qos_cfg_eq(bap_stream->qos, &qos)) + { + return proc_param; + } + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_ENABLE: + DEBUGASSERT(active_proc->proc_type == + BT_CAP_COMMON_PROC_TYPE_START); + + if (state == BT_BAP_EP_STATE_QOS_CONFIGURED) + { + return proc_param; + } + + if (state == BT_BAP_EP_STATE_ENABLING && + bap_stream->codec_cfg != NULL && + !util_eq(bap_stream->codec_cfg->meta, + bap_stream->codec_cfg->meta_len, + proc_param->start.codec_cfg->meta, + proc_param->start.codec_cfg->meta_len)) + { + return proc_param; + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_CONNECT: + DEBUGASSERT(active_proc->proc_type == + BT_CAP_COMMON_PROC_TYPE_START); + + if (state == BT_BAP_EP_STATE_ENABLING && + !proc_param->start.connected) + { + return proc_param; + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_START: + DEBUGASSERT(active_proc->proc_type == + BT_CAP_COMMON_PROC_TYPE_START); + + if (state == BT_BAP_EP_STATE_ENABLING) + { + /* TODO: Add check for connected */ + + return proc_param; + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_META_UPDATE: + DEBUGASSERT(active_proc->proc_type == + BT_CAP_COMMON_PROC_TYPE_UPDATE); + + if ((state == BT_BAP_EP_STATE_ENABLING || + state == BT_BAP_EP_STATE_STREAMING) && + !util_eq(bap_stream->codec_cfg->meta, + bap_stream->codec_cfg->meta_len, + proc_param->meta_update.meta, + proc_param->meta_update.meta_len)) + { + return proc_param; + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_DISABLE: + DEBUGASSERT(active_proc->proc_type == + BT_CAP_COMMON_PROC_TYPE_STOP); + + if (state == BT_BAP_EP_STATE_ENABLING || + state == BT_BAP_EP_STATE_STREAMING) + { + return proc_param; + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_STOP: + DEBUGASSERT(active_proc->proc_type == + BT_CAP_COMMON_PROC_TYPE_STOP); + + if (state == BT_BAP_EP_STATE_DISABLING) + { + return proc_param; + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_RELEASE: + DEBUGASSERT(active_proc->proc_type == + BT_CAP_COMMON_PROC_TYPE_STOP); + + if (proc_param->stop.release && !proc_param->stop.completed) + { + return proc_param; + } + break; + default: + break; + } + } + + return NULL; +} + +static void +bt_cap_initiator_discover_complete(struct bt_conn *conn, + int err, + const struct bt_csip_set_coordinator_set_member *member, + const struct bt_csip_set_coordinator_csis_inst *csis_inst) +{ + if (cap_cb && cap_cb->unicast_discovery_complete) + { + cap_cb->unicast_discovery_complete(conn, err, member, csis_inst); + } +} + +int bt_cap_initiator_unicast_discover(struct bt_conn *conn) +{ + if (conn == NULL) + { + LOG_DBG("NULL conn"); + return -EINVAL; + } + + return bt_cap_common_discover(conn, bt_cap_initiator_discover_complete); +} + +/** Validates that the stream parameter does not contain invalid values */ + +static bool valid_unicast_group_stream_param( + const struct bt_cap_unicast_group_stream_param *param) +{ + if (param->stream == NULL) + { + LOG_DBG("param->stream is NULL"); + return false; + } + + /* Remaining values will be validated by BAP */ + + return true; +} + +static bool +valid_group_stream_pair_param( + const struct bt_cap_unicast_group_stream_pair_param *pair_param) +{ + if (pair_param->rx_param != NULL && + !valid_unicast_group_stream_param(pair_param->rx_param)) + { + return false; + } + + if (pair_param->tx_param != NULL && + !valid_unicast_group_stream_param(pair_param->tx_param)) + { + return false; + } + + return true; +} + +bool bt_cap_initiator_valid_unicast_group_param( + const struct bt_cap_unicast_group_param *param) +{ + if (param == NULL) + { + LOG_DBG("param is NULL"); + return false; + } + + if (param->params_count > CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT) + { + LOG_DBG("Too many streams provided: %u/%u", param->params_count, + UNICAST_GROUP_STREAM_CNT); + return false; + } + + for (size_t i = 0U; i < param->params_count; i++) + { + if (!valid_group_stream_pair_param(¶m->params[i])) + { + return false; + } + } + + return true; +} + +struct cap_to_bap_unicast_params +{ + struct bt_bap_unicast_group_stream_param + stream_params[CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT * + 2U]; + struct bt_bap_unicast_group_stream_pair_param + pair_params[CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT]; + struct bt_bap_unicast_group_param group_param; +}; + +static void cap_to_bap_unicast_group_pair_param( + const struct bt_cap_unicast_group_stream_pair_param pair_params[], + struct cap_to_bap_unicast_params *bap_params) +{ + size_t stream_param_idx = 0U; + + for (size_t i = 0U; i < bap_params->group_param.params_count; i++) + { + const struct bt_cap_unicast_group_stream_pair_param *pair_param = + &pair_params[i]; + struct bt_bap_unicast_group_stream_pair_param *bap_pair_param = + &bap_params->pair_params[i]; + struct bt_bap_unicast_group_stream_param *bap_stream_param; + + if (pair_param->rx_param != NULL) + { + bap_stream_param = &bap_params->stream_params[stream_param_idx]; + stream_param_idx++; + + bap_stream_param->stream = + &pair_param->rx_param->stream->bap_stream; + bap_stream_param->qos = pair_param->rx_param->qos_cfg; + + bap_pair_param->rx_param = bap_stream_param; + } + else + { + bap_pair_param->rx_param = NULL; + } + + if (pair_param->tx_param != NULL) + { + bap_stream_param = &bap_params->stream_params[stream_param_idx]; + stream_param_idx++; + + bap_stream_param->stream = + &pair_param->tx_param->stream->bap_stream; + bap_stream_param->qos = pair_param->tx_param->qos_cfg; + + bap_pair_param->tx_param = bap_stream_param; + } + else + { + bap_pair_param->tx_param = NULL; + } + } +} + +static void cap_to_bap_unicast_group_param( + const struct bt_cap_unicast_group_param *param, + struct cap_to_bap_unicast_params *bap_params) +{ + bap_params->group_param.params_count = param->params_count; + bap_params->group_param.packing = param->packing; +#if defined(CONFIG_BT_ISO_TEST_PARAMS) + bap_params->group_param.c_to_p_ft = param->c_to_p_ft; + bap_params->group_param.p_to_c_ft = param->p_to_c_ft; + bap_params->group_param.iso_interval = param->iso_interval; +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ + bap_params->group_param.params = bap_params->pair_params; + + cap_to_bap_unicast_group_pair_param(param->params, bap_params); +} + +int bt_cap_unicast_group_create( + const struct bt_cap_unicast_group_param *param, + struct bt_cap_unicast_group **out_unicast_group) +{ + struct bt_cap_unicast_group *unicast_group = NULL; + struct cap_to_bap_unicast_params bap_param = + { + 0 + }; + + int err; + + static rmutex_t list_mutex = NXRMUTEX_INITIALIZER; + + if (out_unicast_group == NULL) + { + LOG_DBG("out_unicast_group is NULL"); + return -EINVAL; + } + + if (!bt_cap_initiator_valid_unicast_group_param(param)) + { + return -EINVAL; + } + + *out_unicast_group = NULL; + + (void)nxrmutex_lock(&list_mutex); + for (size_t i = 0U; i < ARRAY_SIZE(unicast_groups); i++) + { + if (unicast_groups[i].bap_unicast_group == NULL) + { + unicast_group = &unicast_groups[i]; + break; + } + } + + if (unicast_group == NULL) + { + LOG_DBG("Could not allocate more unicast groups"); + (void)nxrmutex_unlock(&list_mutex); + return -ENOMEM; + } + + /* Populate the BAP group params based on the CAP params (mostly just a + * copy) + */ + + cap_to_bap_unicast_group_param(param, &bap_param); + + err = bt_bap_unicast_group_create(&bap_param.group_param, + &unicast_group->bap_unicast_group); + if (err != 0) + { + LOG_DBG("Failed to create unicast group: %d", err); + } + else + { + *out_unicast_group = unicast_group; + } + + (void)nxrmutex_unlock(&list_mutex); + + return err; +} + +int bt_cap_unicast_group_reconfig(struct bt_cap_unicast_group *unicast_group, + const struct bt_cap_unicast_group_param *param) +{ + struct cap_to_bap_unicast_params bap_param = + { + 0 + }; + + if (unicast_group == NULL) + { + LOG_DBG("unicast_group is NULL"); + return -EINVAL; + } + + if (!bt_cap_initiator_valid_unicast_group_param(param)) + { + return -EINVAL; + } + + /* Populate the BAP group params based on the CAP params (mostly just a + * copy) + */ + + cap_to_bap_unicast_group_param(param, &bap_param); + + return bt_bap_unicast_group_reconfig(unicast_group->bap_unicast_group, + &bap_param.group_param); +} + +int bt_cap_unicast_group_add_streams( + struct bt_cap_unicast_group *unicast_group, + const struct bt_cap_unicast_group_stream_pair_param params[], + size_t num_param) +{ + struct cap_to_bap_unicast_params bap_param = + { + .group_param.params_count = num_param + }; + + if (unicast_group == NULL) + { + LOG_DBG("unicast_group is NULL"); + return -EINVAL; + } + + if (params == NULL) + { + LOG_DBG("params is NULL"); + return -EINVAL; + } + + if (num_param == 0U) + { + LOG_DBG("num_param is 0"); + return -EINVAL; + } + + for (size_t i = 0U; i < num_param; i++) + { + if (!valid_group_stream_pair_param(¶ms[i])) + { + return -EINVAL; + } + } + + cap_to_bap_unicast_group_pair_param(params, &bap_param); + + return bt_bap_unicast_group_add_streams(unicast_group->bap_unicast_group, + bap_param.pair_params, + num_param); +} + +int bt_cap_unicast_group_delete(struct bt_cap_unicast_group *unicast_group) +{ + int err; + + if (unicast_group == NULL) + { + LOG_DBG("unicast_group is NULL"); + return -EINVAL; + } + + err = bt_bap_unicast_group_delete(unicast_group->bap_unicast_group); + if (err == 0) + { + unicast_group->bap_unicast_group = NULL; + } + + return err; +} + +struct cap_unicast_group_foreach_stream_data +{ + bt_cap_unicast_group_foreach_stream_func_t func; + void *user_data; +}; + +static bool bap_unicast_group_foreach_stream_cb( + struct bt_bap_stream *bap_stream, void *user_data) +{ + struct cap_unicast_group_foreach_stream_data *data = user_data; + + /* Since we are iterating on a CAP unicast group, we can assume that all + * streams are CAP streams + */ + + return data->func(CONTAINER_OF(bap_stream, struct bt_cap_stream, + bap_stream), + data->user_data); +} + +int bt_cap_unicast_group_foreach_stream( + struct bt_cap_unicast_group *unicast_group, + bt_cap_unicast_group_foreach_stream_func_t func, void *user_data) +{ + struct cap_unicast_group_foreach_stream_data data = + { + .func = func, + .user_data = user_data, + }; + + if (unicast_group == NULL) + { + LOG_DBG("unicast_group is NULL"); + return -EINVAL; + } + + if (func == NULL) + { + LOG_DBG("func is NULL"); + return -EINVAL; + } + + return + bt_bap_unicast_group_foreach_stream(unicast_group->bap_unicast_group, + bap_unicast_group_foreach_stream_cb, + &data); +} + +int bt_cap_unicast_group_get_info( + const struct bt_cap_unicast_group *unicast_group, + struct bt_cap_unicast_group_info *info) +{ + if (unicast_group == NULL) + { + LOG_DBG("unicast_group is NULL"); + return -EINVAL; + } + + if (info == NULL) + { + LOG_DBG("info is NULL"); + return -EINVAL; + } + + info->unicast_group = unicast_group->bap_unicast_group; + + return 0; +} + +bool bt_cap_initiator_valid_unicast_audio_start_param( + const struct bt_cap_unicast_audio_start_param *param) +{ + /* Group can either be a unicast group or a broadcast source (in the case + * of handover) + */ + + void *group = NULL; + + if (param == NULL) + { + LOG_DBG("param is NULL"); + return false; + } + + if (param->count == 0) + { + LOG_DBG("Invalid param->count: %u", param->count); + return false; + } + + if (param->stream_params == NULL) + { + LOG_DBG("param->stream_params is NULL"); + return false; + } + + if (param->count > CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT) + { + LOG_DBG("param->count (%zu) is larger than " + "CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT (%d)", + param->count, CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT); + return false; + } + + for (size_t i = 0U; i < param->count; i++) + { + const struct bt_cap_unicast_audio_start_stream_param *stream_param = + ¶m->stream_params[i]; + const union bt_cap_set_member *member = &stream_param->member; + const struct bt_cap_stream *cap_stream = stream_param->stream; + const struct bt_audio_codec_cfg *codec_cfg = stream_param->codec_cfg; + const struct bt_bap_ep *ep = stream_param->ep; + const struct bt_bap_stream *bap_stream; + const struct bt_conn *member_conn = + bt_cap_common_get_member_conn(param->type, + member); + struct bt_conn *ep_conn; + + if (member == NULL) + { + LOG_DBG("param->members[%zu] is NULL", i); + return false; + } + + if (member_conn == NULL) + { + LOG_DBG("Invalid param->members[%zu]", i); + return false; + } + + if (stream_param->codec_cfg == NULL) + { + LOG_DBG("param->stream_params[%zu].codec_cfg is NULL", i); + return false; + } + + if (!cap_initiator_valid_metadata(codec_cfg->meta, + codec_cfg->meta_len)) + { + LOG_DBG("param->stream_params[%zu].codec_cfg is invalid", i); + return false; + } + + if (ep == NULL) + { + LOG_DBG("param->stream_params[%zu].ep is NULL", i); + return false; + } + + ep_conn = bt_bap_ep_get_conn(ep); + if (ep_conn == NULL) + { + LOG_DBG("param->stream_params[%zu].ep is invalid", i); + return false; + } + + if (ep_conn != member_conn) + { + LOG_DBG("param->stream_params[%zu].ep conn %p does not match " + "param->members[%zu] %p", + i, ep_conn, i, member_conn); + bt_conn_unref(ep_conn); + + return false; + } + + bt_conn_unref(ep_conn); + + if (cap_stream == NULL) + { + LOG_DBG("param->streams[%zu] is NULL", i); + return false; + } + + bap_stream = &cap_stream->bap_stream; + + if (bap_stream->group == NULL) + { + LOG_DBG("param->streams[%zu] (%p) is not in a unicast group", i, + bap_stream); + return false; + } + + /* Use the group of the first stream for comparison */ + + if (group == NULL) + { + group = bap_stream->group; + } + else + { + if (bap_stream->group != group) + { + LOG_DBG("param->streams[%zu] (%p) is not in this group %p", i, + group, bap_stream); + return false; + } + } + + for (size_t j = 0U; j < i; j++) + { + if (param->stream_params[j].stream == cap_stream) + { + LOG_DBG("param->stream_params[%zu] (%p) is " + "duplicated by " + "param->stream_params[%zu] (%p)", + j, param->stream_params[j].stream, i, cap_stream); + return false; + } + } + } + + return true; +} + +static bool +cap_initiator_unicast_subproc_complete_and_continue( + const struct bt_cap_common_proc *active_proc) +{ + LOG_DBG("subproc %d for proc %d completed (%sinitiated)", + active_proc->subproc_type, + active_proc->proc_type, + !active_proc->subproc_initiated ? "not " : ""); + + if (!active_proc->subproc_initiated) + { + /* The subprocedure was skipped as all streams were already in the + * requested state, so we do not notify the application + */ + + return true; + } + + if (IS_ENABLED(CONFIG_BT_CAP_HANDOVER) && + bt_cap_common_active_proc_is_handover()) + { + /* The procedure was not started by the application, so we do not + * notify the application about the subprocedures + */ + + return true; + } + + if (cap_cb == NULL) + { + return true; + } + + switch (active_proc->subproc_type) + { + case BT_CAP_COMMON_SUBPROC_TYPE_CODEC_CONFIG: + if (cap_cb->unicast_start_codec_configured != NULL) + { + cap_cb->unicast_start_codec_configured(); + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_QOS_CONFIG: + if (cap_cb->unicast_start_qos_configured != NULL) + { + cap_cb->unicast_start_qos_configured(); + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_ENABLE: + if (cap_cb->unicast_start_enabled != NULL) + { + cap_cb->unicast_start_enabled(); + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_CONNECT: + if (cap_cb->unicast_start_connected != NULL) + { + cap_cb->unicast_start_connected(); + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_START: + if (cap_cb->unicast_start_started != NULL) + { + cap_cb->unicast_start_started(); + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_DISABLE: + if (cap_cb->unicast_stop_disabled != NULL) + { + cap_cb->unicast_stop_disabled(); + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_STOP: + if (cap_cb->unicast_stop_stopped != NULL) + { + cap_cb->unicast_stop_stopped(); + } + break; + case BT_CAP_COMMON_SUBPROC_TYPE_RELEASE: + if (cap_cb->unicast_stop_released != NULL) + { + cap_cb->unicast_stop_released(); + } + break; + default: + DEBUGASSERT(false); + break; + } + + if (bt_cap_common_proc_is_aborted()) + { + DEBUGASSERT(bt_cap_common_proc_all_handled()); + + return false; + } + + return true; +} + +void cap_initiator_unicast_audio_proc_complete( + struct bt_cap_common_proc *active_proc) +{ + enum bt_cap_common_proc_type proc_type; + struct bt_conn *failed_conn; + int err; + + failed_conn = active_proc->failed_conn; + err = active_proc->err; + proc_type = active_proc->proc_type; + + if (IS_ENABLED(CONFIG_BT_CAP_HANDOVER) && + bt_cap_common_active_proc_is_handover()) + { + /* Clear initiator parameters. Normally this is done just before the + * application callbacks with bt_cap_common_clear_proc, but since that + * is not happening here, we clear them manually. They need to be + * cleared as the call to the CAP APIs does not clear old data, and we + * need to reset everything before calling + * cap_initiator_unicast_audio_start + */ + + memset(active_proc->proc_param.initiator, 0, + sizeof(active_proc->proc_param.initiator)); + + bt_cap_handover_unicast_proc_complete(active_proc); + + return; + } + + bt_cap_common_clear_proc(active_proc); + + if (cap_cb == NULL) + { + return; + } + + switch (proc_type) + { + case BT_CAP_COMMON_PROC_TYPE_START: + if (cap_cb->unicast_start_complete != NULL) + { + cap_cb->unicast_start_complete(err, failed_conn); + } + break; + case BT_CAP_COMMON_PROC_TYPE_UPDATE: + if (cap_cb->unicast_update_complete != NULL) + { + cap_cb->unicast_update_complete(err, failed_conn); + } + break; + case BT_CAP_COMMON_PROC_TYPE_STOP: + if (cap_cb->unicast_stop_complete != NULL) + { + cap_cb->unicast_stop_complete(err, failed_conn); + } + break; + case BT_CAP_COMMON_PROC_TYPE_NONE: + default: + DEBUGASSERT(false); + } +} + +void bt_cap_initiator_cp_cb( + struct bt_cap_stream *cap_stream, enum bt_bap_ascs_rsp_code rsp_code, + enum bt_bap_ascs_reason reason) +{ + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + + if (!bt_cap_common_stream_in_active_proc(cap_stream)) + { + /* State change happened outside of a procedure; ignore */ + + bt_cap_common_unlock_proc(); + + return; + } + + LOG_DBG("cap_stream %p", cap_stream); + + set_cap_stream_in_progress(active_proc, cap_stream, false); + + if (rsp_code != BT_BAP_ASCS_RSP_CODE_SUCCESS) + { + /* In the case that the control point write is rejected, we will not + * get a ASE state change notification. This is considered an error + * that shall abort the current procedure. In the case of success we + * use the notification to determine the proc_done_cnt. + */ + + active_proc->proc_done_cnt++; + + LOG_DBG("Control point operation on stream %p failed with %d and " + "reason %d", + cap_stream, + rsp_code, reason); + + /* Control point operation failed or was rejected by the peer - Abort */ + + bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EACCES); + + if (bt_cap_common_proc_is_aborted()) + { + if (bt_cap_common_proc_all_handled()) + { + cap_initiator_unicast_audio_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return; + } + } + + bt_cap_common_unlock_proc(); +} + +static int +cap_initiator_unicast_audio_configure(struct bt_cap_common_proc *active_proc, + const struct bt_cap_unicast_audio_start_param *param) +{ + struct bt_cap_initiator_proc_param *proc_param; + const struct bt_audio_codec_cfg *codec_cfg; + struct bt_bap_stream *bap_stream; + struct bt_bap_ep *ep; + struct bt_conn *conn; + int err; + /** TODO: If this is a CSIP set, then the order of the procedures may + * not match the order in the parameters, and the CSIP ordered access + * procedure should be used. + */ + + for (size_t i = 0U; i < param->count; i++) + { + struct bt_cap_unicast_audio_start_stream_param *stream_param = + ¶m->stream_params[i]; + union bt_cap_set_member *member = &stream_param->member; + struct bt_cap_stream *cap_stream = stream_param->stream; + + conn = bt_cap_common_get_member_conn(param->type, member); + + /* Ensure that ops are registered before any procedures are started */ + + bt_cap_stream_ops_register_bap(cap_stream); + + /* Store the necessary parameters as we cannot assume that the supplied + * parameters are kept valid + */ + + active_proc->proc_param.initiator[i].stream = cap_stream; + active_proc->proc_param.initiator[i].start.ep = stream_param->ep; + active_proc->proc_param.initiator[i].start.conn = conn; + active_proc->proc_param.initiator[i].start.codec_cfg = + stream_param->codec_cfg; + } + + /* Store the information about the active procedure so that we can + * continue the procedure after each step + */ + + bt_cap_common_set_proc(BT_CAP_COMMON_PROC_TYPE_START, param->count); + bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_CODEC_CONFIG); + + proc_param = get_next_proc_param(active_proc); + if (proc_param == NULL) + { + /* If proc_param is NULL then this step is a no-op and we can skip to + * the next step + */ + + bt_cap_initiator_codec_configured( + active_proc->proc_param.initiator[0].stream); + + return 0; + } + + bap_stream = &proc_param->stream->bap_stream; + codec_cfg = proc_param->start.codec_cfg; + conn = proc_param->start.conn; + ep = proc_param->start.ep; + active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; + + active_proc->subproc_initiated = true; + if (bap_stream->conn == NULL) + { + /* Since BAP operations may require a write long or a read long on the + * notification, we cannot assume that we can do multiple streams at + * once, thus do it one at a time. TODO: We should always be able to do + * one per ACL, so there is room for optimization. This applies to all + * BAP calls in this file. + */ + + err = bt_bap_stream_config(conn, bap_stream, ep, codec_cfg); + } + else + { + err = bt_bap_stream_reconfig(bap_stream, codec_cfg); + } + + if (err != 0) + { + LOG_DBG("Failed to config stream %p: %d", proc_param->stream, err); + } + + return err; +} + +int cap_initiator_unicast_audio_start(struct bt_cap_common_proc *active_proc, + const struct bt_cap_unicast_audio_start_param *param) +{ + bool all_streaming = true; + + for (size_t i = 0U; i < param->count; i++) + { + const struct bt_bap_stream *bap_stream = + ¶m->stream_params[i].stream->bap_stream; + + if (!bt_cap_initiator_stream_is_in_state(bap_stream, + BT_BAP_EP_STATE_STREAMING)) + { + all_streaming = false; + } + } + + if (all_streaming) + { + LOG_DBG("All streams are already in the streaming state"); + + return -EALREADY; + } + + return cap_initiator_unicast_audio_configure(active_proc, param); +} + +int bt_cap_initiator_unicast_audio_start( + const struct bt_cap_unicast_audio_start_param *param) +{ + struct bt_cap_common_proc *active_proc; + int err; + + if (!bt_cap_initiator_valid_unicast_audio_start_param(param)) + { + return -EINVAL; + } + + active_proc = bt_cap_common_get_active_proc(); + + if (bt_cap_common_test_and_set_proc_active()) + { + bt_cap_common_unlock_proc(); + + LOG_DBG("A CAP procedure is already in progress"); + + return -EBUSY; + } + + err = cap_initiator_unicast_audio_start(active_proc, param); + if (err != 0) + { + bt_cap_common_clear_proc(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return err; +} + +void bt_cap_initiator_codec_configured(struct bt_cap_stream *cap_stream) +{ + struct bt_conn *conns[MIN(CONFIG_BT_MAX_CONN, + CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT)]; + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + struct bt_cap_initiator_proc_param *proc_param; + struct bt_bap_unicast_group *unicast_group; + + if (!bt_cap_common_stream_in_active_proc(cap_stream)) + { + /* State change happened outside of a procedure; ignore */ + + bt_cap_common_unlock_proc(); + + return; + } + + LOG_DBG("cap_stream %p", cap_stream); + + if (bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_RELEASE)) + { + /* When releasing a stream, it may go into the codec configured state + * if the unicast server caches the configuration - We treat it as a + * release + */ + + bt_cap_initiator_released(cap_stream); + + bt_cap_common_unlock_proc(); + return; + } + else if (!bt_cap_common_subproc_is_type( + BT_CAP_COMMON_SUBPROC_TYPE_CODEC_CONFIG)) + { + /* Unexpected callback - Abort */ + + LOG_DBG("Unexpected state change"); + + bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EBADMSG); + } + else + { + update_proc_done_cnt(active_proc); + + LOG_DBG("Stream %p configured (%zu/%zu streams done)", cap_stream, + active_proc->proc_done_cnt, + active_proc->proc_cnt); + } + + if (bt_cap_common_proc_is_aborted()) + { + if (bt_cap_common_proc_all_handled()) + { + cap_initiator_unicast_audio_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return; + } + + if (!bt_cap_common_proc_is_done()) + { + const struct bt_audio_codec_cfg *codec_cfg; + struct bt_cap_stream *next_cap_stream; + struct bt_bap_stream *next_bap_stream; + struct bt_conn *conn; + struct bt_bap_ep *ep; + int err; + + proc_param = get_next_proc_param(active_proc); + DEBUGASSERT(proc_param != NULL); + next_cap_stream = proc_param->stream; + conn = proc_param->start.conn; + ep = proc_param->start.ep; + codec_cfg = proc_param->start.codec_cfg; + next_bap_stream = &next_cap_stream->bap_stream; + active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; + active_proc->subproc_initiated = true; + + if (next_bap_stream->conn == NULL) + { + err = bt_bap_stream_config(conn, next_bap_stream, ep, codec_cfg); + } + else + { + err = bt_bap_stream_reconfig(next_bap_stream, codec_cfg); + } + + if (err != 0) + { + LOG_DBG("Failed to config stream %p: %d", next_cap_stream, err); + + bt_cap_common_abort_proc(conn, err); + cap_initiator_unicast_audio_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return; + } + + /* The QoS Configure procedure works on a set of connections and a + * unicast group, so we generate a list of unique connection pointers for + * the procedure + */ + + (void)memset(conns, 0, sizeof(conns)); + for (size_t i = 0U; i < active_proc->proc_cnt; i++) + { + struct bt_conn *stream_conn = + active_proc->proc_param.initiator[i].stream->bap_stream.conn; + struct bt_conn **free_conn = NULL; + bool already_added = false; + + for (size_t j = 0U; j < ARRAY_SIZE(conns); j++) + { + if (stream_conn == conns[j]) + { + already_added = true; + break; + } + else if (conns[j] == NULL && free_conn == NULL) + { + free_conn = &conns[j]; + } + } + + if (already_added) + { + continue; + } + + if (free_conn != NULL) + { + *free_conn = stream_conn; + } + else + { + DEBUGASSERT(false); + bt_cap_common_unlock_proc(); + return; + } + } + + if (!cap_initiator_unicast_subproc_complete_and_continue(active_proc)) + { + cap_initiator_unicast_audio_proc_complete(active_proc); + + return; + } + + /* All streams in the procedure share the same unicast group, so we just + * use the reference from the first stream + */ + + bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_QOS_CONFIG); + proc_param = get_next_proc_param(active_proc); + if (proc_param == NULL) + { + bt_cap_common_unlock_proc(); + /* If proc_param is NULL then this step is a no-op and we can skip to + * the next step + */ + + bt_cap_initiator_qos_configured( + active_proc->proc_param.initiator[0].stream); + + return; + } + + unicast_group = (struct bt_bap_unicast_group *) + proc_param->stream->bap_stream.group; + + for (size_t i = 0U; i < ARRAY_SIZE(conns); i++) + { + int err; + + /* When conns[i] is NULL, we have QoS Configured all unique connections + */ + + if (conns[i] == NULL) + { + break; + } + + for (size_t j = 0U; j < active_proc->proc_cnt; j++) + { + proc_param = &active_proc->proc_param.initiator[j]; + if (proc_param->stream->bap_stream.conn == conns[i]) + { + active_proc->proc_initiated_cnt++; + proc_param->in_progress = false; + break; + } + } + + proc_param->in_progress = true; + active_proc->subproc_initiated = true; + + err = bt_bap_stream_qos(conns[i], unicast_group); + if (err != 0) + { + LOG_DBG("Failed to set stream QoS for conn %p and group %p: %d", + (void *)conns[i], + unicast_group, err); + + /* End or mark procedure as aborted. + * If we have sent any requests over air, we will abort once all + * sent requests has completed + */ + + bt_cap_common_abort_proc(conns[i], err); + if (i == 0U) + { + cap_initiator_unicast_audio_proc_complete(active_proc); + + return; + } + + break; + } + } + + bt_cap_common_unlock_proc(); +} + +void bt_cap_initiator_qos_configured(struct bt_cap_stream *cap_stream) +{ + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + + if (!bt_cap_common_stream_in_active_proc(cap_stream)) + { + /* State change happened outside of a procedure; ignore */ + + bt_cap_common_unlock_proc(); + + return; + } + + LOG_DBG("cap_stream %p", cap_stream); + + if (!(bt_cap_common_proc_is_type(BT_CAP_COMMON_PROC_TYPE_START) && + bt_cap_common_subproc_is_type( + BT_CAP_COMMON_SUBPROC_TYPE_QOS_CONFIG)) && + !(bt_cap_common_proc_is_type(BT_CAP_COMMON_PROC_TYPE_STOP))) + { + /* Unexpected callback - Abort */ + + LOG_DBG("Unexpected state change"); + + bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EBADMSG); + } + else + { + update_proc_done_cnt(active_proc); + + LOG_DBG("Stream %p QoS configured (%zu/%zu streams done)", cap_stream, + active_proc->proc_done_cnt, active_proc->proc_cnt); + } + + if (bt_cap_common_proc_is_aborted()) + { + if (bt_cap_common_proc_all_handled()) + { + cap_initiator_unicast_audio_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return; + } + + if (bt_cap_common_proc_is_type(BT_CAP_COMMON_PROC_TYPE_START)) + { + struct bt_cap_initiator_proc_param *proc_param; + const struct bt_audio_codec_cfg *codec_cfg; + struct bt_cap_stream *next_cap_stream; + struct bt_bap_stream *bap_stream; + int err; + + if (!bt_cap_common_proc_is_done()) + { + /* Not yet finished, wait for all */ + + bt_cap_common_unlock_proc(); + + return; + } + + if (!cap_initiator_unicast_subproc_complete_and_continue(active_proc)) + { + cap_initiator_unicast_audio_proc_complete(active_proc); + + return; + } + + bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_ENABLE); + proc_param = get_next_proc_param(active_proc); + if (proc_param == NULL) + { + bt_cap_common_unlock_proc(); + + /* If proc_param is NULL then this step is a no-op and we can skip + * to the next step + */ + + bt_cap_initiator_enabled( + active_proc->proc_param.initiator[0].stream); + + return; + } + + next_cap_stream = proc_param->stream; + bap_stream = &next_cap_stream->bap_stream; + active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; + active_proc->subproc_initiated = true; + + codec_cfg = proc_param->start.codec_cfg; + + err = bt_bap_stream_enable(bap_stream, codec_cfg->meta, + codec_cfg->meta_len); + if (err != 0) + { + LOG_DBG("Failed to enable stream %p: %d", next_cap_stream, err); + + bt_cap_common_abort_proc(bap_stream->conn, err); + cap_initiator_unicast_audio_proc_complete(active_proc); + + return; + } + } + else if (bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_RELEASE)) + { + struct bt_cap_initiator_proc_param *proc_param; + struct bt_cap_stream *next_cap_stream; + struct bt_bap_stream *next_bap_stream; + int err; + + proc_param = get_next_proc_param(active_proc); + DEBUGASSERT(proc_param != NULL); + + next_cap_stream = proc_param->stream; + next_bap_stream = &next_cap_stream->bap_stream; + active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; + active_proc->subproc_initiated = true; + + err = bt_bap_stream_release(next_bap_stream); + if (err != 0) + { + LOG_DBG("Failed to release stream %p: %d", next_cap_stream, err); + + bt_cap_common_abort_proc(next_bap_stream->conn, err); + cap_initiator_unicast_audio_proc_complete(active_proc); + + return; + } + } + + bt_cap_common_unlock_proc(); +} + +void bt_cap_initiator_enabled(struct bt_cap_stream *cap_stream) +{ + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + struct bt_cap_initiator_proc_param *proc_param; + struct bt_bap_stream *bap_stream; + int err; + + if (!bt_cap_common_stream_in_active_proc(cap_stream)) + { + /* State change happened outside of a procedure; ignore */ + + bt_cap_common_unlock_proc(); + + return; + } + + LOG_DBG("cap_stream %p", cap_stream); + + if (!bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_ENABLE)) + { + /* Unexpected callback - Abort */ + + LOG_DBG("Unexpected state change"); + + bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EBADMSG); + } + else + { + update_proc_done_cnt(active_proc); + + LOG_DBG("Stream %p enabled (%zu/%zu streams done)", cap_stream, + active_proc->proc_done_cnt, + active_proc->proc_cnt); + } + + if (bt_cap_common_proc_is_aborted()) + { + if (bt_cap_common_proc_all_handled()) + { + cap_initiator_unicast_audio_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return; + } + + if (!bt_cap_common_proc_is_done()) + { + const struct bt_audio_codec_cfg *codec_cfg; + struct bt_cap_stream *next_cap_stream; + struct bt_bap_stream *next_bap_stream; + + proc_param = get_next_proc_param(active_proc); + DEBUGASSERT(proc_param != NULL); + next_cap_stream = proc_param->stream; + next_bap_stream = &next_cap_stream->bap_stream; + + active_proc->proc_initiated_cnt++; + + proc_param->in_progress = true; + codec_cfg = proc_param->start.codec_cfg; + active_proc->subproc_initiated = true; + + err = bt_bap_stream_enable(next_bap_stream, codec_cfg->meta, + codec_cfg->meta_len); + if (err != 0) + { + LOG_DBG("Failed to enable stream %p: %d", next_cap_stream, err); + + bt_cap_common_abort_proc(next_bap_stream->conn, err); + cap_initiator_unicast_audio_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return; + } + + if (!cap_initiator_unicast_subproc_complete_and_continue(active_proc)) + { + cap_initiator_unicast_audio_proc_complete(active_proc); + + return; + } + + bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_CONNECT); + proc_param = get_next_proc_param(active_proc); + if (proc_param == NULL) + { + bt_cap_common_unlock_proc(); + + /* If proc_param is NULL then this step is a no-op and we can skip to + * the next step + */ + + bt_cap_initiator_connected(active_proc->proc_param.initiator[ + 0].stream); + + return; + } + + bap_stream = &proc_param->stream->bap_stream; + proc_param->in_progress = true; + active_proc->subproc_initiated = true; + + err = bt_bap_stream_connect(bap_stream); + if (err == -EALREADY) + { + /* If the stream is already connected we can just call the callback + * directly NOTE: It's important that we do not do any additional + * functionality after calling this + */ + + proc_param->in_progress = false; + bt_cap_initiator_connected(proc_param->stream); + + bt_cap_common_unlock_proc(); + } + else if (err != 0) + { + LOG_DBG("Failed to connect stream %p: %d", proc_param->stream, err); + + /* End and mark procedure as aborted. + * If we have sent any requests over air, we will abort once all sent + * requests has completed + */ + + bt_cap_common_abort_proc(bap_stream->conn, err); + cap_initiator_unicast_audio_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } +} + +static bool all_streams_streaming( + const struct bt_cap_common_proc *active_proc) +{ + for (size_t i = 0U; i < active_proc->proc_cnt; i++) + { + if (!bt_cap_initiator_stream_is_in_state( + &active_proc->proc_param.initiator[i].stream->bap_stream, + BT_BAP_EP_STATE_STREAMING)) + { + return false; + } + } + + return true; +} + +void bt_cap_initiator_connected(struct bt_cap_stream *cap_stream) +{ + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + struct bt_cap_initiator_proc_param *proc_param; + struct bt_bap_stream *bap_stream; + int err; + + if (!bt_cap_common_stream_in_active_proc(cap_stream)) + { + /* State change happened outside of a procedure; ignore */ + + bt_cap_common_unlock_proc(); + + return; + } + + LOG_DBG("cap_stream %p", cap_stream); + + set_cap_stream_in_progress(active_proc, cap_stream, false); + + if (!bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_CONNECT)) + { + /* Unexpected callback - Abort */ + + LOG_DBG("Unexpected state change"); + + bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EBADMSG); + } + else + { + proc_param = get_proc_param_by_cap_stream(active_proc, cap_stream); + DEBUGASSERT(proc_param != NULL); + + /* Sets connected before update_proc_done_cnt as that is the only way + * to can track the CIS state change + */ + + proc_param->start.connected = true; + update_proc_done_cnt(active_proc); + + LOG_DBG("Stream %p connected (%zu/%zu streams done)", cap_stream, + active_proc->proc_done_cnt, + active_proc->proc_cnt); + } + + if (bt_cap_common_proc_is_aborted()) + { + if (bt_cap_common_proc_all_handled()) + { + cap_initiator_unicast_audio_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return; + } + + if (!bt_cap_common_proc_is_done()) + { + struct bt_cap_stream *next_cap_stream; + struct bt_bap_stream *next_bap_stream; + + proc_param = get_next_proc_param(active_proc); + if (proc_param != NULL) + { + next_cap_stream = proc_param->stream; + next_bap_stream = &next_cap_stream->bap_stream; + + active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; + active_proc->subproc_initiated = true; + + err = bt_bap_stream_connect(next_bap_stream); + if (err == 0 || err == -EALREADY) + { + if (err == -EALREADY) + { + proc_param->in_progress = false; + } + + /* Pending connected - wait for connected callback */ + } + else if (err != 0) + { + LOG_DBG("Failed to connect stream %p: %d", next_cap_stream, + err); + + bt_cap_common_abort_proc(next_bap_stream->conn, err); + cap_initiator_unicast_audio_proc_complete(active_proc); + + return; + } + } + + /* else pending connection - wait for connected callback */ + + bt_cap_common_unlock_proc(); + + return; + } + + if (!cap_initiator_unicast_subproc_complete_and_continue(active_proc)) + { + cap_initiator_unicast_audio_proc_complete(active_proc); + + return; + } + + /* All streams connected - Start sending the receiver start ready for all + * source ASEs. For sink ASEs it is the responsibility of the unicast + * server to do the receiver start ready operation. If there are no source + * ASEs then we just wait. + */ + + bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_START); + proc_param = get_next_proc_param(active_proc); + if (proc_param == NULL) + { + /* Check if all streams are in the streaming state, and if so, then + * complete, otherwise fail. The CIS connect event may come after the + * ASE state if we are doing Sink streams only If + * CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK is not enabled, we always treat + * this as an error + */ + + if (IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK) && + all_streams_streaming(active_proc)) + { + /* In the case of sink streams only, we treat this as we would + * treat source streams, except that we will go directly to the + * started step here + */ + + active_proc->subproc_initiated = true; + bt_cap_common_unlock_proc(); + bt_cap_initiator_started( + active_proc->proc_param.initiator[0].stream); + } + else + { + LOG_WRN("proc is not done, but could not get next proc_param"); + + bt_cap_common_abort_proc(NULL, -ESRCH); + cap_initiator_unicast_audio_proc_complete(active_proc); + } + + return; + } + + bap_stream = &proc_param->stream->bap_stream; + if (stream_is_dir(bap_stream, BT_AUDIO_DIR_SOURCE)) + { + proc_param->in_progress = true; + active_proc->subproc_initiated = true; + + err = bt_bap_stream_start(bap_stream); + if (err != 0) + { + LOG_DBG("Failed to start stream %p: %d", proc_param->stream, err); + + bt_cap_common_abort_proc(bap_stream->conn, err); + cap_initiator_unicast_audio_proc_complete(active_proc); + + return; + } + } + else + { + /* May happen if remaining streams are sink only. Mark + * subproc_initiated to treat this similar to sources and then just + * wait for notification from server + */ + + active_proc->subproc_initiated = true; + } + + bt_cap_common_unlock_proc(); +} + +void bt_cap_initiator_started(struct bt_cap_stream *cap_stream) +{ + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + + LOG_DBG("cap_stream %p", cap_stream); + + if (!bt_cap_common_stream_in_active_proc(cap_stream)) + { + /* State change happened outside of a procedure; ignore */ + + bt_cap_common_unlock_proc(); + + return; + } + + /* Streams may go into the streaming state while we are connecting or + * starting them + */ + + if (bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_CONNECT)) + { + /* If we are still connecting the streams, we terminate early as to not + * perform any start operations until all streams are connected + */ + + bt_cap_common_unlock_proc(); + + return; + } + else if (!bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_START)) + { + /* Unexpected callback - Abort */ + + LOG_DBG("Unexpected state change"); + + bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EBADMSG); + } + else + { + update_proc_done_cnt(active_proc); + + LOG_DBG("Stream %p started (%zu/%zu streams done)", cap_stream, + active_proc->proc_done_cnt, + active_proc->proc_cnt); + } + + if (bt_cap_common_proc_is_aborted()) + { + if (bt_cap_common_proc_all_handled()) + { + cap_initiator_unicast_audio_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return; + } + + if (!bt_cap_common_proc_is_done()) + { + struct bt_cap_initiator_proc_param *proc_param; + struct bt_cap_stream *next_cap_stream; + struct bt_bap_stream *next_bap_stream; + + proc_param = get_next_proc_param(active_proc); + if (proc_param == NULL) + { + /* If proc_param is NULL then this step is a no-op. + * May happen if we have sink streams only, mark subproc_initiated + * to treat this similar to sources and then just wait for + * notification from server + */ + + active_proc->subproc_initiated = true; + + bt_cap_common_unlock_proc(); + + return; + } + + next_cap_stream = proc_param->stream; + next_bap_stream = &next_cap_stream->bap_stream; + + if (stream_is_dir(next_bap_stream, BT_AUDIO_DIR_SOURCE)) + { + int err; + + proc_param->in_progress = true; + active_proc->subproc_initiated = true; + + err = bt_bap_stream_start(next_bap_stream); + if (err != 0) + { + LOG_DBG("Failed to start stream %p: %d", next_cap_stream, err); + + /* End and mark procedure as aborted. + * If we have sent any requests over air, we will abort once + * all sent requests has completed + */ + + bt_cap_common_abort_proc(next_bap_stream->conn, err); + cap_initiator_unicast_audio_proc_complete(active_proc); + + return; + } + } + + bt_cap_common_unlock_proc(); + + /* Return to await for response from server */ + + return; + } + + (void)cap_initiator_unicast_subproc_complete_and_continue(active_proc); + + cap_initiator_unicast_audio_proc_complete(active_proc); +} + +static bool can_update_metadata(const struct bt_bap_stream *bap_stream) +{ + return bt_cap_initiator_stream_is_in_state(bap_stream, + BT_BAP_EP_STATE_ENABLING) || + bt_cap_initiator_stream_is_in_state(bap_stream, + BT_BAP_EP_STATE_STREAMING); +} + +static bool valid_unicast_audio_update_param( + const struct bt_cap_unicast_audio_update_param *param) +{ + struct bt_bap_unicast_group *unicast_group = NULL; + + if (param == NULL) + { + LOG_DBG("param is NULL"); + return false; + } + + if (param->count == 0) + { + LOG_DBG("Invalid param->count: %u", param->count); + return false; + } + + if (param->stream_params == NULL) + { + LOG_DBG("param->stream_params is NULL"); + return false; + } + + if (param->count > CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT) + { + LOG_DBG("param->count (%zu) is larger than " + "CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT (%d)", + param->count, CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT); + return false; + } + + for (size_t i = 0U; i < param->count; i++) + { + const struct bt_cap_unicast_audio_update_stream_param *stream_param = + ¶m->stream_params[i]; + const struct bt_cap_stream *cap_stream = stream_param->stream; + const struct bt_bap_stream *bap_stream; + struct bt_conn *conn; + + if (cap_stream == NULL) + { + LOG_DBG("param->stream_params[%zu] is NULL", i); + return false; + } + + bap_stream = &cap_stream->bap_stream; + conn = bap_stream->conn; + if (conn == NULL) + { + LOG_DBG("param->stream_params[%zu].stream->bap_stream.conn is " + "NULL", + i); + + return false; + } + + if (bap_stream->group == NULL) + { + LOG_DBG("param->stream_params[%zu] is not in a unicast group", i); + return false; + } + + /* Use the group of the first stream for comparison */ + + if (unicast_group == NULL) + { + unicast_group = bap_stream->group; + } + else + { + if (bap_stream->group != unicast_group) + { + LOG_DBG("param->stream_params[%zu] is not in this group %p", i, + unicast_group); + return false; + } + } + + if (!can_update_metadata(bap_stream)) + { + LOG_DBG("param->stream_params[%zu].stream is not in right state " + "to " + "be " + "updated", + i); + + return false; + } + + if (!cap_initiator_valid_metadata(stream_param->meta, + stream_param->meta_len)) + { + LOG_DBG("param->stream_params[%zu] invalid metadata", i); + + return false; + } + + for (size_t j = 0U; j < i; j++) + { + if (param->stream_params[j].stream == cap_stream) + { + LOG_DBG("param->stream_params[%zu] (%p) is " + "duplicated by " + "param->stream_params[%zu] (%p)", + j, param->stream_params[j].stream, i, cap_stream); + return false; + } + } + } + + return true; +} + +int bt_cap_initiator_unicast_audio_update( + const struct bt_cap_unicast_audio_update_param *param) +{ + struct bt_cap_initiator_proc_param *proc_param; + struct bt_cap_common_proc *active_proc; + struct bt_bap_stream *bap_stream; + bool metadata_is_set = true; + const uint8_t *meta; + size_t meta_len; + int err; + + if (!valid_unicast_audio_update_param(param)) + { + return -EINVAL; + } + + for (size_t i = 0U; i < param->count; i++) + { + const struct bt_cap_unicast_audio_update_stream_param *stream_param = + ¶m->stream_params[i]; + const struct bt_cap_stream *cap_stream = stream_param->stream; + + if (!util_eq(cap_stream->bap_stream.codec_cfg->meta, + cap_stream->bap_stream.codec_cfg->meta_len, + stream_param->meta, + stream_param->meta_len)) + { + metadata_is_set = false; + break; + } + + LOG_DBG("param->stream_params[%zu].meta is already set for stream %p", + i, + cap_stream); + } + + if (metadata_is_set) + { + return -EALREADY; + } + + active_proc = bt_cap_common_get_active_proc(); + + if (bt_cap_common_test_and_set_proc_active()) + { + bt_cap_common_unlock_proc(); + + LOG_DBG("A CAP procedure is already in progress"); + + return -EBUSY; + } + + for (size_t i = 0U; i < param->count; i++) + { + const struct bt_cap_unicast_audio_update_stream_param *stream_param = + ¶m->stream_params[i]; + struct bt_cap_stream *cap_stream = stream_param->stream; + + /* Ensure that ops are registered before any procedures are started */ + + bt_cap_stream_ops_register_bap(cap_stream); + + active_proc->proc_param.initiator[i].stream = cap_stream; + active_proc->proc_param.initiator[i].meta_update.meta_len = + stream_param->meta_len; + memcpy(&active_proc->proc_param.initiator[i].meta_update.meta, + stream_param->meta, + stream_param->meta_len); + } + + bt_cap_common_set_proc(BT_CAP_COMMON_PROC_TYPE_UPDATE, param->count); + bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_META_UPDATE); + + proc_param = get_next_proc_param(active_proc); + DEBUGASSERT(proc_param != NULL); + + bap_stream = &proc_param->stream->bap_stream; + meta_len = proc_param->meta_update.meta_len; + meta = proc_param->meta_update.meta; + active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; + + err = bt_bap_stream_metadata(bap_stream, meta, meta_len); + if (err != 0) + { + LOG_DBG("Failed to update metadata for stream %p: %d", + proc_param->stream, err); + + bt_cap_common_clear_proc(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return err; +} + +int bt_cap_initiator_unicast_audio_cancel(void) +{ + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + + if (!bt_cap_common_proc_is_active() && !bt_cap_common_proc_is_aborted()) + { + bt_cap_common_unlock_proc(); + + LOG_DBG("No CAP procedure is in progress"); + + return -EALREADY; + } + + if ((IS_ENABLED(CONFIG_BT_CAP_HANDOVER) && + bt_cap_common_active_proc_is_handover()) || + !bt_cap_common_active_proc_is_initiator()) + { + bt_cap_common_unlock_proc(); + + LOG_DBG("No CAP initiator procedure is in progress"); + + return -EOPNOTSUPP; + } + + bt_cap_common_abort_proc(NULL, -ECANCELED); + + if (bt_cap_common_get_active_proc_mutex_count(active_proc) == 1U) + { + /* Since this may be called in a CAP callback, check if we hold the + * only mutex lock, and if we do, then we complete it, else + * cap_initiator_unicast_subproc_complete_and_continue is responsible + * for handling that + */ + + cap_initiator_unicast_audio_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return 0; +} + +void bt_cap_initiator_metadata_updated(struct bt_cap_stream *cap_stream) +{ + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + + if (!bt_cap_common_stream_in_active_proc(cap_stream)) + { + /* State change happened outside of a procedure; ignore */ + + bt_cap_common_unlock_proc(); + + return; + } + + if (!bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_META_UPDATE)) + { + /* Unexpected callback - Abort */ + + LOG_DBG("Unexpected state change"); + + bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EBADMSG); + } + else + { + update_proc_done_cnt(active_proc); + + LOG_DBG("Stream %p QoS metadata updated (%zu/%zu streams done)", + cap_stream, + active_proc->proc_done_cnt, active_proc->proc_cnt); + } + + if (bt_cap_common_proc_is_aborted()) + { + if (bt_cap_common_proc_all_handled()) + { + cap_initiator_unicast_audio_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return; + } + + if (!bt_cap_common_proc_is_done()) + { + const size_t proc_done_cnt = active_proc->proc_done_cnt; + struct bt_cap_initiator_proc_param *proc_param; + struct bt_cap_stream *next_cap_stream; + struct bt_bap_stream *bap_stream; + const uint8_t *meta; + size_t meta_len; + int err; + + proc_param = &active_proc->proc_param.initiator[proc_done_cnt]; + meta_len = proc_param->meta_update.meta_len; + meta = proc_param->meta_update.meta; + next_cap_stream = proc_param->stream; + bap_stream = &next_cap_stream->bap_stream; + active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; + + err = bt_bap_stream_metadata(bap_stream, meta, meta_len); + if (err != 0) + { + LOG_DBG("Failed to update metadata for stream %p: %d", + next_cap_stream, err); + + bt_cap_common_abort_proc(bap_stream->conn, err); + cap_initiator_unicast_audio_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + } + else + { + cap_initiator_unicast_audio_proc_complete(active_proc); + } +} + +static bool can_release_stream(const struct bt_bap_stream *bap_stream) +{ + enum bt_bap_ep_state state; + + if (bap_stream->conn == NULL) + { + return false; + } + + state = bt_cap_initiator_stream_get_state(bap_stream); + + /* We cannot release idle endpoints. */ + + return state != BT_BAP_EP_STATE_IDLE; +} + +static bool can_disable_stream(const struct bt_bap_stream *bap_stream) +{ + enum bt_bap_ep_state state; + + if (bap_stream->conn == NULL) + { + return false; + } + + state = bt_cap_initiator_stream_get_state(bap_stream); + + return state == BT_BAP_EP_STATE_STREAMING || + state == BT_BAP_EP_STATE_ENABLING; +} + +static bool can_stop_stream(const struct bt_bap_stream *bap_stream) +{ + enum bt_iso_state iso_state; + + if (bap_stream->conn == NULL) + { + return false; + } + + if (stream_is_dir(bap_stream, BT_AUDIO_DIR_SINK)) + { + return false; + } + + iso_state = bap_stream_get_iso_state(bap_stream); + if (iso_state != BT_ISO_STATE_CONNECTED && + iso_state != BT_ISO_STATE_CONNECTING) + { + return false; + } + + return bt_cap_initiator_stream_is_in_state(bap_stream, + BT_BAP_EP_STATE_DISABLING); +} + +bool bt_cap_initiator_valid_unicast_audio_stop_param( + const struct bt_cap_unicast_audio_stop_param *param) +{ + struct bt_bap_unicast_group *unicast_group = NULL; + + if (param == NULL) + { + LOG_DBG("param is NULL"); + return false; + } + + if (param->count == 0) + { + LOG_DBG("Invalid param->count: %u", param->count); + return false; + } + + if (param->streams == NULL) + { + LOG_DBG("param->streams is NULL"); + return false; + } + + if (param->count > CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT) + { + LOG_DBG("param->count (%zu) is larger than " + "CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT (%d)", + param->count, CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT); + return false; + } + + for (size_t i = 0U; i < param->count; i++) + { + const struct bt_cap_stream *cap_stream = param->streams[i]; + const struct bt_bap_stream *bap_stream; + struct bt_conn *conn; + + if (cap_stream == NULL) + { + LOG_DBG("param->streams[%zu] is NULL", i); + return false; + } + + bap_stream = &cap_stream->bap_stream; + conn = bap_stream->conn; + if (conn == NULL) + { + LOG_DBG("param->streams[%zu]->bap_stream.conn is NULL", i); + + return false; + } + + if (param->type == BT_CAP_SET_TYPE_CSIP) + { + struct bt_cap_common_client *client = + bt_cap_common_get_client_by_acl(conn); + + if (client->csis_inst == NULL) + { + LOG_DBG("param->streams[%zu]->bap_stream.conn not part of a " + "coordinated set", + i); + + return false; + } + } + + if (bap_stream->group == NULL) + { + LOG_DBG("param->streams[%zu] is not in a unicast group", i); + return false; + } + + /* Use the group of the first stream for comparison */ + + if (unicast_group == NULL) + { + unicast_group = bap_stream->group; + } + else + { + if (bap_stream->group != unicast_group) + { + LOG_DBG("param->streams[%zu] is not in this group %p", i, + unicast_group); + return false; + } + } + + for (size_t j = 0U; j < i; j++) + { + if (param->streams[j] == cap_stream) + { + LOG_DBG("param->stream_params[%zu] (%p) is " + "duplicated by " + "param->stream_params[%zu] (%p)", + j, param->streams[j], i, cap_stream); + return false; + } + } + } + + return true; +} + +int cap_initiator_unicast_audio_stop(struct bt_cap_common_proc *active_proc, + const struct bt_cap_unicast_audio_stop_param *param) +{ + bool can_release = false; + bool can_disable = false; + bool can_stop = false; + int err; + + for (size_t i = 0U; i < param->count; i++) + { + struct bt_cap_stream *cap_stream = param->streams[i]; + struct bt_bap_stream *bap_stream = &cap_stream->bap_stream; + + /* Ensure that ops are registered before any procedures are started */ + + bt_cap_stream_ops_register_bap(cap_stream); + + active_proc->proc_param.initiator[i].stream = cap_stream; + active_proc->proc_param.initiator[i].stop.release = param->release; + active_proc->proc_param.initiator[i].stop.completed = false; + + if (!can_disable && can_disable_stream(bap_stream)) + { + can_disable = true; + } + + if (!can_stop && can_stop_stream(bap_stream)) + { + can_stop = true; + } + + if (!can_release && param->release && can_release_stream(bap_stream)) + { + can_release = true; + } + } + + if (!can_disable && !can_stop && !can_release) + { + LOG_DBG("Cannot %s any streams", + !can_disable ? "disable" : !can_stop ? "stop" : "release"); + + return -EALREADY; + } + + bt_cap_common_set_proc(BT_CAP_COMMON_PROC_TYPE_STOP, param->count); + /** TODO: If this is a CSIP set, then the order of the procedures may + * not match the order in the parameters, and the CSIP ordered access + * procedure should be used. + */ + + if (can_disable) + { + struct bt_cap_initiator_proc_param *proc_param; + struct bt_bap_stream *bap_stream; + + bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_DISABLE); + + proc_param = get_next_proc_param(active_proc); + DEBUGASSERT(proc_param != NULL); + bap_stream = &proc_param->stream->bap_stream; + active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; + active_proc->subproc_initiated = true; + + err = bt_bap_stream_disable(bap_stream); + if (err != 0) + { + LOG_DBG("Failed to disable bap_stream %p: %d", proc_param->stream, + err); + } + } + else if (can_stop) + { + struct bt_cap_initiator_proc_param *proc_param; + struct bt_bap_stream *bap_stream; + + bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_STOP); + + proc_param = get_next_proc_param(active_proc); + DEBUGASSERT(proc_param != NULL); + bap_stream = &proc_param->stream->bap_stream; + active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; + active_proc->subproc_initiated = true; + + err = bt_bap_stream_stop(bap_stream); + if (err != 0) + { + LOG_DBG("Failed to stop bap_stream %p: %d", proc_param->stream, + err); + } + } + else + { + struct bt_cap_initiator_proc_param *proc_param; + struct bt_bap_stream *bap_stream; + + bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_RELEASE); + + proc_param = get_next_proc_param(active_proc); + DEBUGASSERT(proc_param != NULL); + bap_stream = &proc_param->stream->bap_stream; + active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; + active_proc->subproc_initiated = true; + + err = bt_bap_stream_release(bap_stream); + if (err != 0) + { + LOG_DBG("Failed to release bap_stream %p: %d", proc_param->stream, + err); + } + } + + return err; +} + +int bt_cap_initiator_unicast_audio_stop( + const struct bt_cap_unicast_audio_stop_param *param) +{ + struct bt_cap_common_proc *active_proc; + int err; + + if (!bt_cap_initiator_valid_unicast_audio_stop_param(param)) + { + return -EINVAL; + } + + active_proc = bt_cap_common_get_active_proc(); + + if (bt_cap_common_test_and_set_proc_active()) + { + bt_cap_common_unlock_proc(); + + LOG_DBG("A CAP procedure is already in progress"); + + return -EBUSY; + } + + err = cap_initiator_unicast_audio_stop(active_proc, param); + if (err != 0) + { + bt_cap_common_clear_proc(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return err; +} + +void bt_cap_initiator_disabled(struct bt_cap_stream *cap_stream) +{ + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + + if (!bt_cap_common_stream_in_active_proc(cap_stream)) + { + /* State change happened outside of a procedure; ignore */ + + bt_cap_common_unlock_proc(); + + return; + } + + if (!bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_DISABLE)) + { + /* Unexpected callback - Abort */ + + LOG_DBG("Unexpected state change"); + + bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EBADMSG); + } + else + { + update_proc_done_cnt(active_proc); + + LOG_DBG("Stream %p disabled (%zu/%zu streams done)", cap_stream, + active_proc->proc_done_cnt, + active_proc->proc_cnt); + } + + if (bt_cap_common_proc_is_aborted()) + { + if (bt_cap_common_proc_all_handled()) + { + cap_initiator_unicast_audio_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return; + } + + if (!bt_cap_common_proc_is_done()) + { + struct bt_cap_initiator_proc_param *proc_param; + struct bt_cap_stream *next_cap_stream; + struct bt_bap_stream *next_bap_stream; + int err; + + proc_param = get_next_proc_param(active_proc); + DEBUGASSERT(proc_param != NULL); + next_cap_stream = proc_param->stream; + next_bap_stream = &next_cap_stream->bap_stream; + active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; + active_proc->subproc_initiated = true; + + err = bt_bap_stream_disable(next_bap_stream); + if (err != 0) + { + LOG_DBG("Failed to disable stream %p: %d", next_cap_stream, err); + + bt_cap_common_abort_proc(next_bap_stream->conn, err); + cap_initiator_unicast_audio_proc_complete(active_proc); + } + } + else + { + struct bt_cap_initiator_proc_param *proc_param; + struct bt_cap_stream *next_cap_stream; + struct bt_bap_stream *next_bap_stream; + int err; + + if (!cap_initiator_unicast_subproc_complete_and_continue(active_proc)) + { + cap_initiator_unicast_audio_proc_complete(active_proc); + + return; + } + + bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_STOP); + + proc_param = get_next_proc_param(active_proc); + if (proc_param == NULL) + { + /* If proc_param is NULL then this step is a no-op. + * May happen if we have sink streams only, mark subproc_initiated + * to treat this similar to sources. BAP will call ops->stopped for + * the sink stream after this + */ + + active_proc->subproc_initiated = true; + + bt_cap_common_unlock_proc(); + + return; + } + + next_cap_stream = proc_param->stream; + next_bap_stream = &next_cap_stream->bap_stream; + active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; + active_proc->subproc_initiated = true; + + err = bt_bap_stream_stop(next_bap_stream); + if (err != 0) + { + LOG_DBG("Failed to stop stream %p: %d", next_cap_stream, err); + + bt_cap_common_abort_proc(next_bap_stream->conn, err); + cap_initiator_unicast_audio_proc_complete(active_proc); + + return; + } + else + { + /* else wait for server notification */ + } + } + + bt_cap_common_unlock_proc(); +} + +void bt_cap_initiator_stopped(struct bt_cap_stream *cap_stream) +{ + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + + if (!bt_cap_common_stream_in_active_proc(cap_stream)) + { + /* State change happened outside of a procedure; ignore */ + + bt_cap_common_unlock_proc(); + + return; + } + + if (!bt_cap_common_proc_is_type(BT_CAP_COMMON_PROC_TYPE_STOP)) + { + /* Unexpected callback - Abort */ + + LOG_DBG("Unexpected state change"); + + bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EBADMSG); + } + else + { + if (bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_STOP)) + { + update_proc_done_cnt(active_proc); + + LOG_DBG("Stream %p stopped (%zu/%zu streams done)", cap_stream, + active_proc->proc_done_cnt, active_proc->proc_cnt); + } + else + { + /* We are still doing disable - Wait for those to be done, as + * stopped may also be called when we are disabling sink ASEs + */ + + bt_cap_common_unlock_proc(); + + return; + } + } + + if (bt_cap_common_proc_is_aborted()) + { + if (bt_cap_common_proc_all_handled()) + { + cap_initiator_unicast_audio_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return; + } + + if (!bt_cap_common_proc_is_done()) + { + struct bt_cap_initiator_proc_param *proc_param; + struct bt_cap_stream *next_cap_stream; + struct bt_bap_stream *next_bap_stream; + int err; + + proc_param = get_next_proc_param(active_proc); + if (proc_param == NULL) + { + /* If proc_param is NULL then this step is a no-op. + * May happen if we have sink streams only, mark subproc_initiated + * to treat this similar to sources. BAP will call + * ops->qos_configured for the sink stream after this + */ + + active_proc->subproc_initiated = true; + + bt_cap_common_unlock_proc(); + + return; + } + + next_cap_stream = proc_param->stream; + next_bap_stream = &next_cap_stream->bap_stream; + + active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; + active_proc->subproc_initiated = true; + + err = bt_bap_stream_stop(next_bap_stream); + if (err != 0) + { + LOG_DBG("Failed to stop stream %p: %d", next_cap_stream, err); + + bt_cap_common_abort_proc(next_bap_stream->conn, err); + cap_initiator_unicast_audio_proc_complete(active_proc); + + return; + } + + /* else wait for server notification */ + } + else + { + /* We are done stopping streams now - We mark the next subproc. If + * get_next_proc_param returns a NULL value it means that we are + * complete done. If it returns a non-NULL value, it means that we need + * to start releasing streams. However, since the QoS Configured state + * is better suited to trigger this, we simply wait until + * bt_cap_initiator_qos_configured is called. + */ + + struct bt_cap_initiator_proc_param *proc_param; + + if (!cap_initiator_unicast_subproc_complete_and_continue(active_proc)) + { + cap_initiator_unicast_audio_proc_complete(active_proc); + + return; + } + + bt_cap_common_set_subproc(BT_CAP_COMMON_SUBPROC_TYPE_RELEASE); + + proc_param = get_next_proc_param(active_proc); + if (proc_param == NULL) + { + /* If proc_param is NULL then this step is a no-op and we can + * finish the procedure + */ + + cap_initiator_unicast_audio_proc_complete(active_proc); + + return; + } + + /* wait for bt_cap_initiator_qos_configured */ + } + + bt_cap_common_unlock_proc(); +} + +void bt_cap_initiator_released(struct bt_cap_stream *cap_stream) +{ + struct bt_cap_common_proc *active_proc = bt_cap_common_get_active_proc(); + + if (!bt_cap_common_stream_in_active_proc(cap_stream)) + { + /* State change happened outside of a procedure; ignore */ + + bt_cap_common_unlock_proc(); + + return; + } + + if (!bt_cap_common_subproc_is_type(BT_CAP_COMMON_SUBPROC_TYPE_RELEASE)) + { + /* Unexpected callback - Abort */ + + LOG_DBG("Unexpected state change"); + + bt_cap_common_abort_proc(cap_stream->bap_stream.conn, -EBADMSG); + } + else + { + /* Mark released as completed */ + + for (size_t i = 0U; i < active_proc->proc_cnt; i++) + { + if (active_proc->proc_param.initiator[i].stream == cap_stream) + { + active_proc->proc_param.initiator[i].stop.completed = true; + break; + } + } + + update_proc_done_cnt(active_proc); + + LOG_DBG("Stream %p released (%zu/%zu streams done)", cap_stream, + active_proc->proc_done_cnt, + active_proc->proc_cnt); + } + + if (bt_cap_common_proc_is_aborted()) + { + if (bt_cap_common_proc_all_handled()) + { + cap_initiator_unicast_audio_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + + return; + } + + if (!bt_cap_common_proc_is_done()) + { + struct bt_cap_initiator_proc_param *proc_param; + struct bt_cap_stream *next_cap_stream; + struct bt_bap_stream *next_bap_stream; + int err; + + proc_param = get_next_proc_param(active_proc); + DEBUGASSERT(proc_param != NULL); + next_cap_stream = proc_param->stream; + next_bap_stream = &next_cap_stream->bap_stream; + active_proc->proc_initiated_cnt++; + proc_param->in_progress = true; + active_proc->subproc_initiated = true; + + err = bt_bap_stream_release(next_bap_stream); + if (err != 0) + { + LOG_DBG("Failed to release stream %p: %d", next_cap_stream, err); + + bt_cap_common_abort_proc(next_bap_stream->conn, err); + cap_initiator_unicast_audio_proc_complete(active_proc); + } + else + { + bt_cap_common_unlock_proc(); + } + } + else + { + (void)cap_initiator_unicast_subproc_complete_and_continue(active_proc); + cap_initiator_unicast_audio_proc_complete(active_proc); + } +} + +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT */ diff --git a/libs/zblue/audio/cap_internal.h b/libs/zblue/audio/cap_internal.h new file mode 100644 index 0000000000000..7e29ef240ead3 --- /dev/null +++ b/libs/zblue/audio/cap_internal.h @@ -0,0 +1,474 @@ +/**************************************************************************** + * libs/zblue/audio/cap_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Bluetooth Audio Common Audio Profile internal header */ + +/* Copyright (c) 2022-2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +bool bt_cap_acceptor_ccid_exist(const struct bt_conn *conn, uint8_t ccid); +bool bt_cap_acceptor_ccids_exist( + const struct bt_conn *conn, const uint8_t ccids[], uint8_t ccid_cnt); + +void bt_cap_initiator_codec_configured(struct bt_cap_stream *cap_stream); +void bt_cap_initiator_qos_configured(struct bt_cap_stream *cap_stream); +void bt_cap_initiator_enabled(struct bt_cap_stream *cap_stream); +void bt_cap_initiator_started(struct bt_cap_stream *cap_stream); +void bt_cap_initiator_connected(struct bt_cap_stream *cap_stream); +void bt_cap_initiator_metadata_updated(struct bt_cap_stream *cap_stream); +void bt_cap_initiator_disabled(struct bt_cap_stream *cap_stream); +void bt_cap_initiator_stopped(struct bt_cap_stream *cap_stream); +void bt_cap_initiator_released(struct bt_cap_stream *cap_stream); +void bt_cap_stream_ops_register_bap(struct bt_cap_stream *cap_stream); +void bt_cap_initiator_cp_cb( + struct bt_cap_stream *cap_stream, enum bt_bap_ascs_rsp_code rsp_code, + enum bt_bap_ascs_reason reason); + +enum bt_cap_common_proc_state +{ + BT_CAP_COMMON_PROC_STATE_ACTIVE, + BT_CAP_COMMON_PROC_STATE_ABORTED, + /* While the handover is technically a procedure, and arguably should use + * `bt_cap_common_set_proc`, we set the it as a flag instead so that we can + * rely on the API functions to perform their procedures + */ + + BT_CAP_COMMON_PROC_STATE_HANDOVER, + + BT_CAP_COMMON_PROC_STATE_FLAG_NUM, +}; + +enum bt_cap_common_proc_type +{ + BT_CAP_COMMON_PROC_TYPE_NONE, + BT_CAP_COMMON_PROC_TYPE_START, + BT_CAP_COMMON_PROC_TYPE_UPDATE, + BT_CAP_COMMON_PROC_TYPE_STOP, + BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_START, + BT_CAP_COMMON_PROC_TYPE_BROADCAST_RECEPTION_STOP, + BT_CAP_COMMON_PROC_TYPE_DISTRIBUTE_BROADCAST_CODE, + BT_CAP_COMMON_PROC_TYPE_VOLUME_CHANGE, + BT_CAP_COMMON_PROC_TYPE_VOLUME_OFFSET_CHANGE, + BT_CAP_COMMON_PROC_TYPE_VOLUME_MUTE_CHANGE, + BT_CAP_COMMON_PROC_TYPE_MICROPHONE_GAIN_CHANGE, + BT_CAP_COMMON_PROC_TYPE_MICROPHONE_MUTE_CHANGE, +}; + +enum bt_cap_common_subproc_type +{ + BT_CAP_COMMON_SUBPROC_TYPE_NONE, + BT_CAP_COMMON_SUBPROC_TYPE_CODEC_CONFIG, + BT_CAP_COMMON_SUBPROC_TYPE_QOS_CONFIG, + BT_CAP_COMMON_SUBPROC_TYPE_ENABLE, + BT_CAP_COMMON_SUBPROC_TYPE_CONNECT, + BT_CAP_COMMON_SUBPROC_TYPE_START, + BT_CAP_COMMON_SUBPROC_TYPE_META_UPDATE, + BT_CAP_COMMON_SUBPROC_TYPE_DISABLE, + BT_CAP_COMMON_SUBPROC_TYPE_STOP, + BT_CAP_COMMON_SUBPROC_TYPE_RELEASE, + BT_CAP_COMMON_SUBPROC_TYPE_STOP_SRC, + BT_CAP_COMMON_SUBPROC_TYPE_REM_SRC, +}; + +struct bt_cap_unicast_group +{ + struct bt_bap_unicast_group *bap_unicast_group; +}; + +struct bt_cap_broadcast_source +{ + struct bt_bap_broadcast_source *bap_broadcast; +}; + +struct bt_cap_initiator_proc_param +{ + struct bt_cap_stream *stream; + union + { + struct + { + struct bt_conn *conn; + struct bt_bap_ep *ep; + const struct bt_audio_codec_cfg *codec_cfg; + bool connected; + } start; + struct + { + /** Codec Specific Capabilities Metadata count */ + + size_t meta_len; + + /** Codec Specific Capabilities Metadata */ + + uint8_t meta[CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE]; + } meta_update; + struct + { + bool release; + /** Since releasing may end up with the ASE state going from + * BT_BAP_EP_STATE_CODEC_CONFIGURED -> BT_BAP_EP_STATE_RELEASING -> + * BT_BAP_EP_STATE_CODEC_CONFIGURED we cannot rely 100% on the ASE + * state to determine the state of the procedure + */ + + bool completed; + } stop; + }; + bool in_progress; +}; + +#if defined(CONFIG_BT_BAP_BROADCAST_ASSISTANT) +struct cap_broadcast_reception_start +{ + bt_addr_le_t addr; + bool write_pending : 1; + bool waiting_for_notification : 1; + bool started_recvd : 1; + uint8_t adv_sid; + uint32_t broadcast_id; + uint16_t pa_interval; + uint8_t num_subgroups; + struct bt_bap_bass_subgroup subgroups[CONFIG_BT_BAP_BASS_MAX_SUBGROUPS]; +}; + +struct cap_broadcast_reception_stop +{ + bool write_pending : 1; + bool waiting_for_notification : 1; + bool stopped_recvd : 1; + bool removed_recvd : 1; + uint8_t src_id; + uint8_t num_subgroups; + struct bt_bap_bass_subgroup subgroups[CONFIG_BT_BAP_BASS_MAX_SUBGROUPS]; +}; + +/* Note that although the broadcast_code will be the same for all + * we nevertheless store a separate copy for each sink, for consistency in + * the struct bt_cap_commander_proc_param There is no memory savings by not + * having broadcast_code part of the union: struct + * cap_broadcast_reception_start uses minimum 20 bytes and struct + * cap_distribute_broadcast_code uses 17 bytes + */ + +struct cap_distribute_broadcast_code +{ + uint8_t src_id; + uint8_t broadcast_code[BT_ISO_BROADCAST_CODE_SIZE]; +}; +#endif /* CONFIG_BT_BAP_BROADCAST_ASSISTANT */ + +struct bt_cap_commander_proc_param +{ + struct bt_conn *conn; + union + { +#if defined(CONFIG_BT_VCP_VOL_CTLR) + struct + { + uint8_t volume; + } change_volume; + struct + { + bool mute; + } change_vol_mute; +#endif /* CONFIG_BT_VCP_VOL_CTLR */ +#if defined(CONFIG_BT_VCP_VOL_CTLR_VOCS) + struct + { + int16_t offset; + struct bt_vocs *vocs; + } change_offset; +#endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS */ +#if defined(CONFIG_BT_BAP_BROADCAST_ASSISTANT) + struct cap_broadcast_reception_start broadcast_reception_start; + struct cap_broadcast_reception_stop broadcast_reception_stop; + struct cap_distribute_broadcast_code distribute_broadcast_code; +#endif /* CONFIG_BT_BAP_BROADCAST_ASSISTANT */ +#if defined(CONFIG_BT_MICP_MIC_CTLR) + struct + { + bool mute; + } change_mic_mute; +#if defined(CONFIG_BT_MICP_MIC_CTLR_AICS) + struct + { + int8_t gain; + struct bt_aics *aics; + } change_gain; +#endif /* CONFIG_BT_MICP_MIC_CTLR_AICS */ +#endif /* CONFIG_BT_MICP_MIC_CTLR */ + }; +}; + +#if defined(CONFIG_BT_CAP_HANDOVER) +struct bt_cap_handover_proc_param +{ + union + { + struct + { + /* Struct containing the converted unicast group configuration */ + + struct bt_cap_initiator_broadcast_create_param *broadcast_create_param; + + /* The resulting broadcast source */ + + struct bt_cap_broadcast_source *broadcast_source; + + /* The advertising set to start the source on */ + + struct bt_le_ext_adv *ext_adv; + + /* The source unicast group with the streams. */ + + struct bt_cap_unicast_group *unicast_group; + + /* Set type */ + + enum bt_cap_set_type type; + + /* The PA interval of the ext_adv */ + + uint16_t pa_interval; + + /* The broadcast ID the broadcast source will use */ + + uint32_t broadcast_id; + + struct bt_cap_commander_broadcast_reception_start_member_param + reception_start_member_params[CONFIG_BT_MAX_CONN]; + } unicast_to_broadcast; + struct + { + /* The existing broadcast source */ + + struct bt_cap_broadcast_source *broadcast_source; + + /* The resulting unicast group */ + + struct bt_cap_unicast_group *unicast_group; + + /* Broadcast ID of broadcast_source */ + + uint32_t broadcast_id; + + /* Advertising SID of broadcast_source */ + + uint8_t adv_sid; + + /* Advertising type of broadcast_source */ + + uint8_t adv_type; + + /* Unicast group create param from caller */ + + struct bt_cap_unicast_group_param *unicast_group_param; + + /* Unicast start param from caller */ + + struct bt_cap_unicast_audio_start_param *unicast_start_param; + } broadcast_to_unicast; + }; + + /* Flag to determine which of the two structs above to use */ + + bool is_unicast_to_broadcast; +}; +#endif /* CONFIG_BT_CAP_HANDOVER */ + +typedef void (*bt_cap_common_discover_func_t)( + struct bt_conn *conn, int err, + const struct bt_csip_set_coordinator_set_member *member, + const struct bt_csip_set_coordinator_csis_inst *csis_inst); + +struct bt_cap_common_proc_param +{ + union + { +#if defined(CONFIG_BT_CAP_INITIATOR_UNICAST) + struct bt_cap_initiator_proc_param + initiator[CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT]; +#endif /* CONFIG_BT_CAP_INITIATOR_UNICAST */ +#if defined(CONFIG_BT_CAP_COMMANDER) + struct bt_cap_commander_proc_param commander[CONFIG_BT_MAX_CONN]; +#endif /* CONFIG_BT_CAP_COMMANDER */ + }; +#if defined(CONFIG_BT_CAP_HANDOVER) + /* The handover parameters cannot be part of the union as they need to + * exist while we are performing CAP initiator and CAP Commander procedures + */ + + struct bt_cap_handover_proc_param handover; +#endif /* CONFIG_BT_CAP_HANDOVER */ +}; + +struct bt_cap_common_proc +{ + atomic_define(proc_state_flags, BT_CAP_COMMON_PROC_STATE_FLAG_NUM); + + /* Total number of items (streams or connections) in the procedure */ + + size_t proc_cnt; + + /* Number of items where a subprocedure have been started */ + + size_t proc_initiated_cnt; + + /* Number of items done with the procedure, including failures */ + + size_t proc_done_cnt; + enum bt_cap_common_proc_type proc_type; + int err; + struct bt_conn *failed_conn; + struct bt_cap_common_proc_param proc_param; +#if defined(CONFIG_BT_CAP_SUBPROC_SUPPORT) + enum bt_cap_common_subproc_type subproc_type; + + /* Whether any request has been sent to a peer device for the current + * subprocedure + */ + + bool subproc_initiated; +#endif /* CONFIG_BT_CAP_SUBPROC_SUPPORT */ +}; + +struct bt_cap_common_client +{ + struct bt_conn *conn; + struct bt_gatt_discover_params param; + bt_cap_common_discover_func_t discover_cb_func; + uint16_t csis_start_handle; + const struct bt_csip_set_coordinator_csis_inst *csis_inst; +}; + +/* Returns the current active proc with the mutex taken. + * + * The caller is responsible for unlocking the mutex. + */ + +struct bt_cap_common_proc *bt_cap_common_get_active_proc(void); +void bt_cap_common_unlock_proc(void); +/** + * @brief Clear and unlock @p proc + * + * @param proc The procedure to unlock and clear + */ + +void bt_cap_common_clear_proc(struct bt_cap_common_proc *proc); +uint32_t bt_cap_common_get_active_proc_mutex_count( + const struct bt_cap_common_proc *proc); +void bt_cap_common_set_proc(enum bt_cap_common_proc_type proc_type, + size_t proc_cnt); +void bt_cap_common_set_subproc(enum bt_cap_common_subproc_type subproc_type); +void bt_cap_common_set_handover_active(void); + +bool bt_cap_common_active_proc_is_handover(void); +bool bt_cap_common_active_proc_is_initiator(void); +bool bt_cap_common_active_proc_is_commander(void); +bool bt_cap_common_proc_is_type(enum bt_cap_common_proc_type proc_type); +bool bt_cap_common_subproc_is_type( + enum bt_cap_common_subproc_type subproc_type); +struct bt_conn *bt_cap_common_get_member_conn( + enum bt_cap_set_type type, const union bt_cap_set_member *member); +bool bt_cap_common_test_and_set_proc_active(void); +bool bt_cap_common_proc_is_active(void); +bool bt_cap_common_proc_is_aborted(void); +bool bt_cap_common_proc_all_handled(void); +bool bt_cap_common_proc_is_done(void); +void bt_cap_common_abort_proc(struct bt_conn *conn, int err); +bool bt_cap_common_conn_in_active_proc(const struct bt_conn *conn); +bool bt_cap_common_stream_in_active_proc( + const struct bt_cap_stream *cap_stream); +struct bt_cap_common_client *bt_cap_common_get_client_by_acl( + const struct bt_conn *acl); +struct bt_cap_common_client * +bt_cap_common_get_client_by_csis( + const struct bt_csip_set_coordinator_csis_inst *csis_inst); +int bt_cap_common_discover(struct bt_conn *conn, + bt_cap_common_discover_func_t func); + +bool bt_cap_initiator_broadcast_audio_start_valid_param( + const struct bt_cap_initiator_broadcast_create_param *param); +bool bt_cap_initiator_valid_unicast_audio_start_param( + const struct bt_cap_unicast_audio_start_param *param); +bool bt_cap_initiator_valid_unicast_group_param( + const struct bt_cap_unicast_group_param *param); +bool bt_cap_initiator_stream_is_in_state( + const struct bt_bap_stream *bap_stream, enum bt_bap_ep_state state); +bool bt_cap_initiator_valid_unicast_audio_stop_param( + const struct bt_cap_unicast_audio_stop_param *param); +int cap_initiator_unicast_audio_start( + struct bt_cap_common_proc *active_proc, + const struct bt_cap_unicast_audio_start_param *param); +int cap_initiator_unicast_audio_stop( + struct bt_cap_common_proc *active_proc, + const struct bt_cap_unicast_audio_stop_param *param); +enum bt_bap_ep_state bt_cap_initiator_stream_get_state( + const struct bt_bap_stream *bap_stream); +void cap_initiator_unicast_audio_proc_complete( + struct bt_cap_common_proc *active_proc); + +int cap_commander_broadcast_reception_start( + struct bt_cap_common_proc *active_proc, + const struct bt_cap_commander_broadcast_reception_start_param *param); +int cap_commander_broadcast_reception_stop( + struct bt_cap_common_proc *active_proc, + const struct bt_cap_commander_broadcast_reception_stop_param *param); +bool bt_cap_commander_valid_broadcast_reception_stop_param( + const struct bt_cap_commander_broadcast_reception_stop_param *param); +void cap_commander_register_broadcast_assistant_callbacks(void); +void cap_commander_proc_complete(struct bt_cap_common_proc *active_proc); + +/** Completes the handover procedure. This also unlocks the + * active_proc_mutex. + */ + +void bt_cap_handover_complete(struct bt_cap_common_proc *active_proc); +int bt_cap_handover_unicast_to_broadcast_setup_broadcast( + struct bt_cap_common_proc *active_proc); +void bt_cap_handover_unicast_to_broadcast_reception_start(void); + +/** Completes the unicast procedure. This also unlocks the active_proc_mutex. + */ + +void bt_cap_handover_unicast_proc_complete( + struct bt_cap_common_proc *active_proc); +void bt_cap_handover_commander_proc_complete( + struct bt_cap_common_proc *active_proc); +void bt_cap_handover_broadcast_source_stopped(uint8_t reason); +void bt_cap_handover_receive_state_updated( + const struct bt_conn *conn, + const struct bt_bap_scan_delegator_recv_state *state); +bool bt_cap_handover_is_handover_broadcast_source( + const struct bt_cap_broadcast_source *cap_broadcast_source); +int bt_cap_handover_broadcast_reception_stopped( + struct bt_cap_common_proc *active_proc); diff --git a/libs/zblue/audio/cap_stream.c b/libs/zblue/audio/cap_stream.c new file mode 100644 index 0000000000000..4267726f7e6c9 --- /dev/null +++ b/libs/zblue/audio/cap_stream.c @@ -0,0 +1,426 @@ +/**************************************************************************** + * libs/zblue/audio/cap_stream.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022-2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cap_internal.h" + +LOG_MODULE_REGISTER(bt_cap_stream, CONFIG_BT_CAP_STREAM_LOG_LEVEL); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static bool stream_is_central(struct bt_bap_stream *bap_stream) +{ + if (IS_ENABLED(CONFIG_BT_CONN)) + { + struct bt_conn_info info; + int err; + + if (bap_stream->conn == NULL) + { + return false; + } + + err = bt_conn_get_info(bap_stream->conn, &info); + if (err == 0 && info.role == BT_CONN_ROLE_CENTRAL) + { + return true; + } + } + + return false; +} + +#if defined(CONFIG_BT_BAP_UNICAST) +static void cap_stream_codec_configured_cb(struct bt_bap_stream *bap_stream, + const struct bt_bap_qos_cfg_pref *pref) +{ + struct bt_cap_stream *cap_stream = CONTAINER_OF(bap_stream, + struct bt_cap_stream, bap_stream); + struct bt_bap_stream_ops *ops = cap_stream->ops; + + LOG_DBG("%p", cap_stream); + + if (ops != NULL && ops->codec_configured != NULL) + { + ops->codec_configured(bap_stream, pref); + } + + if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR) && + IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && + stream_is_central(bap_stream)) + { + bt_cap_initiator_codec_configured(cap_stream); + } +} + +static void cap_stream_qos_configured_cb(struct bt_bap_stream *bap_stream) +{ + struct bt_cap_stream *cap_stream = CONTAINER_OF(bap_stream, + struct bt_cap_stream, bap_stream); + struct bt_bap_stream_ops *ops = cap_stream->ops; + + LOG_DBG("%p", cap_stream); + + if (ops != NULL && ops->qos_configured != NULL) + { + ops->qos_configured(bap_stream); + } + + if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR) && + IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && + stream_is_central(bap_stream)) + { + bt_cap_initiator_qos_configured(cap_stream); + } +} + +static void cap_stream_enabled_cb(struct bt_bap_stream *bap_stream) +{ + struct bt_cap_stream *cap_stream = CONTAINER_OF(bap_stream, + struct bt_cap_stream, bap_stream); + struct bt_bap_stream_ops *ops = cap_stream->ops; + + LOG_DBG("%p", cap_stream); + + if (ops != NULL && ops->enabled != NULL) + { + ops->enabled(bap_stream); + } + + if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR) && + IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && + stream_is_central(bap_stream)) + { + bt_cap_initiator_enabled(cap_stream); + } +} + +static void cap_stream_metadata_updated_cb(struct bt_bap_stream *bap_stream) +{ + struct bt_cap_stream *cap_stream = CONTAINER_OF(bap_stream, + struct bt_cap_stream, bap_stream); + struct bt_bap_stream_ops *ops = cap_stream->ops; + + LOG_DBG("%p", cap_stream); + + if (ops != NULL && ops->metadata_updated != NULL) + { + ops->metadata_updated(bap_stream); + } + + if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR) && + IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && + stream_is_central(bap_stream)) + { + bt_cap_initiator_metadata_updated(cap_stream); + } +} + +static void cap_stream_disabled_cb(struct bt_bap_stream *bap_stream) +{ + struct bt_cap_stream *cap_stream = CONTAINER_OF(bap_stream, + struct bt_cap_stream, bap_stream); + struct bt_bap_stream_ops *ops = cap_stream->ops; + + LOG_DBG("%p", cap_stream); + + if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR) && + IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && + stream_is_central(bap_stream)) + { + bt_cap_initiator_disabled(cap_stream); + } + + if (ops != NULL && ops->disabled != NULL) + { + ops->disabled(bap_stream); + } +} + +static void cap_stream_released_cb(struct bt_bap_stream *bap_stream) +{ + struct bt_cap_stream *cap_stream = CONTAINER_OF(bap_stream, + struct bt_cap_stream, bap_stream); + struct bt_bap_stream_ops *ops = cap_stream->ops; + + LOG_DBG("%p", cap_stream); + + if (ops != NULL && ops->released != NULL) + { + ops->released(bap_stream); + } + + /* Here we cannot use stream_is_central as bap_stream->conn is NULL, so + * fall back to a more generic, less accurate check + */ + + if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR) && + IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT)) + { + bt_cap_initiator_released(cap_stream); + } +} + +#endif /* CONFIG_BT_BAP_UNICAST */ + +static void cap_stream_started_cb(struct bt_bap_stream *bap_stream) +{ + struct bt_cap_stream *cap_stream = CONTAINER_OF(bap_stream, + struct bt_cap_stream, bap_stream); + struct bt_bap_stream_ops *ops = cap_stream->ops; + + LOG_DBG("%p", cap_stream); + + if (ops != NULL && ops->started != NULL) + { + ops->started(bap_stream); + } + + if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR) && + IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && + stream_is_central(bap_stream)) + { + bt_cap_initiator_started(cap_stream); + } +} + +static void cap_stream_stopped_cb(struct bt_bap_stream *bap_stream, + uint8_t reason) +{ + struct bt_cap_stream *cap_stream = CONTAINER_OF(bap_stream, + struct bt_cap_stream, bap_stream); + struct bt_bap_stream_ops *ops = cap_stream->ops; + + LOG_DBG("%p", cap_stream); + + if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR) && + IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && + stream_is_central(bap_stream)) + { + bt_cap_initiator_stopped(cap_stream); + } + + if (ops != NULL && ops->stopped != NULL) + { + ops->stopped(bap_stream, reason); + } +} + +#if defined(CONFIG_BT_AUDIO_RX) +static void cap_stream_recv_cb(struct bt_bap_stream *bap_stream, + const struct bt_iso_recv_info *info, + struct net_buf *buf) +{ + struct bt_cap_stream *cap_stream = CONTAINER_OF(bap_stream, + struct bt_cap_stream, bap_stream); + struct bt_bap_stream_ops *ops = cap_stream->ops; + + if (ops != NULL && ops->recv != NULL) + { + ops->recv(bap_stream, info, buf); + } +} +#endif /* CONFIG_BT_AUDIO_RX */ + +#if defined(CONFIG_BT_AUDIO_TX) +static void cap_stream_sent_cb(struct bt_bap_stream *bap_stream) +{ + struct bt_cap_stream *cap_stream = CONTAINER_OF(bap_stream, + struct bt_cap_stream, bap_stream); + struct bt_bap_stream_ops *ops = cap_stream->ops; + + if (ops != NULL && ops->sent != NULL) + { + ops->sent(bap_stream); + } +} +#endif /* CONFIG_BT_AUDIO_TX */ + +static void cap_stream_connected_cb(struct bt_bap_stream *bap_stream) +{ + struct bt_cap_stream *cap_stream = CONTAINER_OF(bap_stream, + struct bt_cap_stream, bap_stream); + struct bt_bap_stream_ops *ops = cap_stream->ops; + + if (ops != NULL && ops->connected != NULL) + { + ops->connected(bap_stream); + } + + if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR) && + IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && + stream_is_central(bap_stream)) + { + bt_cap_initiator_connected(cap_stream); + } +} + +static void cap_stream_disconnected_cb(struct bt_bap_stream *bap_stream, + uint8_t reason) +{ + struct bt_cap_stream *cap_stream = CONTAINER_OF(bap_stream, + struct bt_cap_stream, bap_stream); + struct bt_bap_stream_ops *ops = cap_stream->ops; + + if (ops != NULL && ops->disconnected != NULL) + { + ops->disconnected(bap_stream, reason); + } +} + +static struct bt_bap_stream_ops bap_stream_ops = +{ +#if defined(CONFIG_BT_BAP_UNICAST) + .codec_configured = cap_stream_codec_configured_cb, + .qos_configured = cap_stream_qos_configured_cb, + .enabled = cap_stream_enabled_cb, + .metadata_updated = cap_stream_metadata_updated_cb, + .disabled = cap_stream_disabled_cb, + .released = cap_stream_released_cb, +#endif /* CONFIG_BT_BAP_UNICAST */ + .started = cap_stream_started_cb, + .stopped = cap_stream_stopped_cb, +#if defined(CONFIG_BT_AUDIO_RX) + .recv = cap_stream_recv_cb, +#endif /* CONFIG_BT_AUDIO_RX */ +#if defined(CONFIG_BT_AUDIO_TX) + .sent = cap_stream_sent_cb, +#endif /* CONFIG_BT_AUDIO_TX */ + .connected = cap_stream_connected_cb, + .disconnected = cap_stream_disconnected_cb, +}; + +static void unicast_client_cp_cb(struct bt_bap_stream *bap_stream, + enum bt_bap_ascs_rsp_code rsp_code, + enum bt_bap_ascs_reason reason) +{ + if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR) && + IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT) && + stream_is_central(bap_stream)) + { + struct bt_cap_stream *cap_stream = CONTAINER_OF(bap_stream, + struct bt_cap_stream, bap_stream); + + bt_cap_initiator_cp_cb(cap_stream, rsp_code, reason); + } +} + +void bt_cap_stream_ops_register_bap(struct bt_cap_stream *cap_stream) +{ + bt_bap_stream_cb_register(&cap_stream->bap_stream, &bap_stream_ops); + + if (IS_ENABLED(CONFIG_BT_CAP_INITIATOR) && + IS_ENABLED(CONFIG_BT_BAP_UNICAST_CLIENT)) + { + /* The CAP initiator can use the same callback for all of these as the + * result is the same: Abort current procedure + */ + + static struct bt_bap_unicast_client_cb unicast_client_cb = + { + .config = unicast_client_cp_cb, + .qos = unicast_client_cp_cb, + .enable = unicast_client_cp_cb, + .start = unicast_client_cp_cb, + .stop = unicast_client_cp_cb, + .disable = unicast_client_cp_cb, + .metadata = unicast_client_cp_cb, + .release = unicast_client_cp_cb, + }; + + int err; + + err = bt_bap_unicast_client_register_cb(&unicast_client_cb); + DEBUGASSERT(err == 0 || err == -EEXIST); + } +} + +void bt_cap_stream_ops_register(struct bt_cap_stream *stream, + struct bt_bap_stream_ops *ops) +{ + stream->ops = ops; + + /* CAP basically just forwards the BAP callbacks after doing what it (CAP) + * needs to do, so we can just always register the BAP callbacks here + * + * It is, however, only the CAP Initiator Unicast that depend on the + * callbacks being set in order to work, so for the CAP Initiator Unicast + * we need an additional register to ensure correctness. + */ + + bt_cap_stream_ops_register_bap(stream); +} + +#if defined(CONFIG_BT_AUDIO_TX) +int bt_cap_stream_send(struct bt_cap_stream *stream, + struct net_buf *buf, uint16_t seq_num) +{ + if (stream == NULL) + { + LOG_DBG("stream is NULL"); + + return -EINVAL; + } + + return bt_bap_stream_send(&stream->bap_stream, buf, seq_num); +} + +int bt_cap_stream_send_ts( + struct bt_cap_stream *stream, struct net_buf *buf, uint16_t seq_num, + uint32_t ts) +{ + if (stream == NULL) + { + LOG_DBG("stream is NULL"); + + return -EINVAL; + } + + return bt_bap_stream_send_ts(&stream->bap_stream, buf, seq_num, ts); +} + +int bt_cap_stream_get_tx_sync(struct bt_cap_stream *stream, + struct bt_iso_tx_info *info) +{ + if (stream == NULL) + { + LOG_DBG("stream is NULL"); + + return -EINVAL; + } + + return bt_bap_stream_get_tx_sync(&stream->bap_stream, info); +} +#endif /* CONFIG_BT_AUDIO_TX */ diff --git a/libs/zblue/audio/ccid.c b/libs/zblue/audio/ccid.c new file mode 100644 index 0000000000000..60840248991f2 --- /dev/null +++ b/libs/zblue/audio/ccid.c @@ -0,0 +1,106 @@ +/**************************************************************************** + * libs/zblue/audio/ccid.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Bluetooth Audio Content Control */ + +/* Copyright (c) 2020 Bose Corporation + * Copyright (c) 2021-2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +struct ccid_search_param +{ + const struct bt_gatt_attr *attr; + uint8_t ccid; +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static uint8_t ccid_attr_cb(const struct bt_gatt_attr *attr, + uint16_t handle, void *user_data) +{ + struct ccid_search_param *search_param = user_data; + + ARG_UNUSED(handle); + + if (attr->read != NULL) + { + uint8_t ccid = 0U; + ssize_t res; + + res = attr->read(NULL, attr, &ccid, sizeof(ccid), 0); + + if (res == sizeof(ccid) && search_param->ccid == ccid) + { + search_param->attr = attr; + + return BT_GATT_ITER_STOP; + } + } + + return BT_GATT_ITER_CONTINUE; +} + +const struct bt_gatt_attr *bt_ccid_find_attr(uint8_t ccid) +{ + struct ccid_search_param search_param = + { + .attr = NULL, + .ccid = ccid, + }; + + bt_gatt_foreach_attr_type(BT_ATT_FIRST_ATTRIBUTE_HANDLE, + BT_ATT_LAST_ATTRIBUTE_HANDLE, + BT_UUID_CCID, NULL, 0U, ccid_attr_cb, + &search_param); + + return search_param.attr; +} + +int bt_ccid_alloc_value(void) +{ + static uint8_t next_ccid_value; + const uint8_t tmp = next_ccid_value; + + /* Verify that the CCID is unused and increment until we reach an unused + * value or until we reach our starting point This is not a perfect check + * as there may be services that are not registered that have allocated a + * CCID. Implementing a perfect solution would require a alloc and free API + * where we need to keep track of all allocated CCIDs, which requires + * additional memory for something that's very unlikely to be an issue. + */ + + while (bt_ccid_find_attr(next_ccid_value) != NULL) + { + next_ccid_value++; + if (tmp == next_ccid_value) + { + return -ENOMEM; + } + } + + return next_ccid_value++; +} diff --git a/libs/zblue/audio/ccp_call_control_client.c b/libs/zblue/audio/ccp_call_control_client.c new file mode 100644 index 0000000000000..6d1d3fd66e26a --- /dev/null +++ b/libs/zblue/audio/ccp_call_control_client.c @@ -0,0 +1,736 @@ +/**************************************************************************** + * libs/zblue/audio/ccp_call_control_client.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Bluetooth CCP - Call Control Profile Call Control Client + * + * Copyright (c) 2024-2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(bt_ccp_call_control_client, + CONFIG_BT_CCP_CALL_CONTROL_CLIENT_LOG_LEVEL); + +static struct list_node ccp_call_control_client_cbs = + LIST_INITIAL_VALUE(ccp_call_control_client_cbs); + +/* A service instance can either be a GTBS or a TBS instance */ + +struct bt_ccp_call_control_client_bearer +{ + uint8_t tbs_index; + bool discovered; +}; + +enum ccp_call_control_client_flag +{ + CCP_CALL_CONTROL_CLIENT_FLAG_BUSY, + + CCP_CALL_CONTROL_CLIENT_FLAG_NUM_FLAGS, /* keep as last */ +}; + +struct bt_ccp_call_control_client +{ + struct bt_ccp_call_control_client_bearer + bearers[CONFIG_BT_CCP_CALL_CONTROL_CLIENT_BEARER_COUNT]; + struct bt_conn *conn; + + atomic_define(flags, CCP_CALL_CONTROL_CLIENT_FLAG_NUM_FLAGS); +}; + +static struct bt_ccp_call_control_client clients[CONFIG_BT_MAX_CONN]; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static struct bt_ccp_call_control_client_bearer * +get_bearer_by_tbs_index(struct bt_ccp_call_control_client *client, + uint8_t index) +{ + for (size_t i = 0U; i < ARRAY_SIZE(client->bearers); i++) + { + struct bt_ccp_call_control_client_bearer *bearer = &client->bearers[i]; + + if (bearer->discovered && bearer->tbs_index == index) + { + return bearer; + } + } + + return NULL; +} + +static struct bt_ccp_call_control_client * +get_client_by_bearer(const struct bt_ccp_call_control_client_bearer *bearer) +{ + for (size_t i = 0U; i < ARRAY_SIZE(clients); i++) + { + if (IS_ARRAY_ELEMENT(clients[i].bearers, bearer)) + { + return &clients[i]; + } + } + + return NULL; +} + +static struct bt_ccp_call_control_client *get_client_by_conn( + const struct bt_conn *conn) +{ + DEBUGASSERT(bt_conn_is_type(conn, BT_CONN_TYPE_LE)); + + return &clients[bt_conn_index(conn)]; +} + +static void populate_bearers(struct bt_ccp_call_control_client *client, + struct bt_ccp_call_control_client_bearers *bearers) +{ + size_t i = 0U; + +#if defined(CONFIG_BT_TBS_CLIENT_GTBS) + if (client->bearers[i].discovered) + { + bearers->gtbs_bearer = &client->bearers[i]; + i++; + } +#endif /* CONFIG_BT_TBS_CLIENT_GTBS */ + +#if defined(CONFIG_BT_TBS_CLIENT_TBS) + for (; i < ARRAY_SIZE(client->bearers); i++) + { + if (!client->bearers[i].discovered) + { + break; + } + + bearers->tbs_bearers[bearers->tbs_count] = &client->bearers[i]; + bearers->tbs_count++; + } +#endif /* CONFIG_BT_TBS_CLIENT_TBS */ +} + +static void tbs_client_discover_cb( + struct bt_conn *conn, int err, uint8_t tbs_count, bool gtbs_found) +{ + struct bt_ccp_call_control_client *client = get_client_by_conn(conn); + struct bt_ccp_call_control_client_bearers bearers = + { + 0 + }; + + struct bt_ccp_call_control_client_cb *listener; + struct bt_ccp_call_control_client_cb *next; + + LOG_DBG("conn %p err %d tbs_count %u gtbs_found %d", (void *)conn, err, + tbs_count, gtbs_found); + + memset(client->bearers, 0, sizeof((client->bearers))); + + if (IS_ENABLED(CONFIG_BT_TBS_CLIENT_GTBS) && gtbs_found) + { + client->bearers[0].discovered = true; + client->bearers[0].tbs_index = BT_TBS_GTBS_INDEX; + } + + if (IS_ENABLED(CONFIG_BT_TBS_CLIENT_TBS)) + { + for (uint8_t i = 0U; i < tbs_count; i++) + { + const uint8_t idx = i + (gtbs_found ? 1 : 0); + + if (idx >= ARRAY_SIZE(client->bearers)) + { + LOG_WRN("Discovered more TBS instances (%u) than the CCP Call " + "Control Client supports %zu", + tbs_count, ARRAY_SIZE(client->bearers)); + break; + } + + client->bearers[idx].discovered = true; + client->bearers[idx].tbs_index = i; + } + } + + populate_bearers(client, &bearers); + + atomic_clear_bit(client->flags, CCP_CALL_CONTROL_CLIENT_FLAG_BUSY); + + list_for_every_container_safe(&ccp_call_control_client_cbs, listener, next, + _node) + { + if (listener->discover != NULL) + { + void *user_data = + COND_CODE_1(CONFIG_BT_CCP_CALL_CONTROL_CLIENT_CB_USER_DATA, + (listener->user_data), (NULL)); + + listener->discover(client, err, &bearers, user_data); + } + } +} + +int bt_ccp_call_control_client_discover(struct bt_conn *conn, + struct bt_ccp_call_control_client **out_client) +{ + struct bt_ccp_call_control_client *client; + struct bt_conn *ref; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + if (out_client == NULL) + { + LOG_DBG("client is NULL"); + + return -EINVAL; + } + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + LOG_DBG("Invalid connection type for %p", conn); + return -EINVAL; + } + + client = get_client_by_conn(conn); + if (atomic_test_and_set_bit(client->flags, + CCP_CALL_CONTROL_CLIENT_FLAG_BUSY)) + { + return -EBUSY; + } + + ref = bt_conn_ref(conn); + if (ref == NULL) + { + err = -ENOTCONN; + goto cleanup; + } + + client->conn = ref; + + err = bt_tbs_client_discover(conn); + if (err != 0) + { + LOG_DBG("Failed to discover TBS for %p: %d", (void *)conn, err); + + /* Return known errors */ + + if (err != -EBUSY && err != -ENOTCONN) + { + err = -ENOEXEC; + } + + bt_conn_unref(ref); + client->conn = NULL; + goto cleanup; + } + + *out_client = client; + + return 0; + +cleanup: + atomic_clear_bit(client->flags, CCP_CALL_CONTROL_CLIENT_FLAG_BUSY); + return err; +} + +int bt_ccp_call_control_client_register_cb( + struct bt_ccp_call_control_client_cb *cb) +{ + if (cb == NULL) + { + LOG_DBG("cb is NULL"); + + return -EINVAL; + } + + if (list_find(&ccp_call_control_client_cbs, &cb->_node, NULL)) + { + return -EEXIST; + } + + list_add_tail(&ccp_call_control_client_cbs, &cb->_node); + + return 0; +} + +int bt_ccp_call_control_client_unregister_cb( + struct bt_ccp_call_control_client_cb *cb) +{ + if (cb == NULL) + { + LOG_DBG("cb is NULL"); + return -EINVAL; + } + + if (!list_find_and_remove(&ccp_call_control_client_cbs, &cb->_node)) + { + return -EALREADY; + } + + return 0; +} + +int bt_ccp_call_control_client_get_bearers( + struct bt_ccp_call_control_client *client, + struct bt_ccp_call_control_client_bearers *bearers) +{ + if (client == NULL) + { + LOG_DBG("client is NULL"); + return -EINVAL; + } + + if (bearers == NULL) + { + LOG_DBG("bearers is NULL"); + return -EINVAL; + } + + memset(bearers, 0, sizeof(*bearers)); + populate_bearers(client, bearers); + + return 0; +} + +/** + * @brief Validates a bearer and provides a client with ownership of the busy + * flag + * + * @param[in] bearer The bearer to validate + * @param[out] client A client identified by the @p bearer with the busy flag + * set if return value is 0. + * + * @return 0 if the bearer is valid and the @p client has been populated, + * else an error. + */ + +__maybe_unused static int +validate_bearer_and_get_client( + const struct bt_ccp_call_control_client_bearer *bearer, + struct bt_ccp_call_control_client **client) +{ + if (bearer == NULL) + { + LOG_DBG("bearer is NULL"); + + return -EINVAL; + } + + *client = get_client_by_bearer(bearer); + if (*client == NULL) + { + LOG_DBG("Could not identify client from bearer %p", bearer); + + return -EEXIST; + } + + if (!bearer->discovered) + { + LOG_DBG("bearer %p is not discovered", bearer); + + return -EFAULT; + } + + if (atomic_test_and_set_bit((*client)->flags, + CCP_CALL_CONTROL_CLIENT_FLAG_BUSY)) + { + LOG_DBG("Client %p identified by bearer %p is busy", *client, bearer); + + return -EBUSY; + } + + return 0; +} + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) +static void tbs_client_read_bearer_provider_name_cb( + struct bt_conn *conn, int err, uint8_t inst_index, const char *name) +{ + struct bt_ccp_call_control_client *client = get_client_by_conn(conn); + struct bt_ccp_call_control_client_cb *listener; + struct bt_ccp_call_control_client_cb *next; + struct bt_ccp_call_control_client_bearer *bearer; + + atomic_clear_bit(client->flags, CCP_CALL_CONTROL_CLIENT_FLAG_BUSY); + + bearer = get_bearer_by_tbs_index(client, inst_index); + if (bearer == NULL) + { + LOG_DBG("Could not lookup bearer for client %p and index 0x%02X", + client, + inst_index); + + return; + } + + list_for_every_container_safe(&ccp_call_control_client_cbs, listener, next, + _node) + { + if (listener->bearer_provider_name != NULL) + { + void *user_data = + COND_CODE_1(CONFIG_BT_CCP_CALL_CONTROL_CLIENT_CB_USER_DATA, + (listener->user_data), (NULL)); + + listener->bearer_provider_name(bearer, err, name, user_data); + } + } +} + +int bt_ccp_call_control_client_read_bearer_provider_name( + struct bt_ccp_call_control_client_bearer *bearer) +{ + struct bt_ccp_call_control_client *client; + int err; + + err = validate_bearer_and_get_client(bearer, &client); + if (err != 0) + { + return err; + } + + err = bt_tbs_client_read_bearer_provider_name(client->conn, + bearer->tbs_index); + if (err != 0) + { + atomic_clear_bit(client->flags, CCP_CALL_CONTROL_CLIENT_FLAG_BUSY); + + /* Return expected return values directly */ + + if (err == -ENOTCONN || err == -EBUSY) + { + LOG_DBG("bt_tbs_client_read_bearer_provider_name returned %d", + err); + + return err; + } + + LOG_DBG("Unexpected error from " + "bt_tbs_client_read_bearer_provider_name: %d", + err); + + return -ENOEXEC; + } + + return 0; +} +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) +static void tbs_client_read_bearer_uci_cb( + struct bt_conn *conn, int err, uint8_t inst_index, const char *uci) +{ + struct bt_ccp_call_control_client *client = get_client_by_conn(conn); + struct bt_ccp_call_control_client_cb *listener; + struct bt_ccp_call_control_client_cb *next; + struct bt_ccp_call_control_client_bearer *bearer; + + atomic_clear_bit(client->flags, CCP_CALL_CONTROL_CLIENT_FLAG_BUSY); + + bearer = get_bearer_by_tbs_index(client, inst_index); + if (bearer == NULL) + { + LOG_DBG("Could not lookup bearer for client %p and index 0x%02X", + client, + inst_index); + + return; + } + + list_for_every_container_safe(&ccp_call_control_client_cbs, listener, next, + _node) + { + if (listener->bearer_uci != NULL) + { + void *user_data = + COND_CODE_1(CONFIG_BT_CCP_CALL_CONTROL_CLIENT_CB_USER_DATA, + (listener->user_data), (NULL)); + + listener->bearer_uci(bearer, err, uci, user_data); + } + } +} + +int bt_ccp_call_control_client_read_bearer_uci( + struct bt_ccp_call_control_client_bearer *bearer) +{ + struct bt_ccp_call_control_client *client; + int err; + + err = validate_bearer_and_get_client(bearer, &client); + if (err != 0) + { + return err; + } + + err = bt_tbs_client_read_bearer_uci(client->conn, bearer->tbs_index); + if (err != 0) + { + atomic_clear_bit(client->flags, CCP_CALL_CONTROL_CLIENT_FLAG_BUSY); + + /* Return expected return values directly */ + + if (err == -ENOTCONN || err == -EBUSY) + { + LOG_DBG("bt_tbs_client_read_bearer_uci returned %d", err); + + return err; + } + + LOG_DBG("Unexpected error from bt_tbs_client_read_bearer_uci: " + "%d", err); + + return -ENOEXEC; + } + + return 0; +} +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_UCI */ + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) +static void tbs_client_read_bearer_tech_cb( + struct bt_conn *conn, int err, uint8_t inst_index, + enum bt_bearer_tech tech) +{ + struct bt_ccp_call_control_client *client = get_client_by_conn(conn); + struct bt_ccp_call_control_client_cb *listener; + struct bt_ccp_call_control_client_cb *next; + struct bt_ccp_call_control_client_bearer *bearer; + + atomic_clear_bit(client->flags, CCP_CALL_CONTROL_CLIENT_FLAG_BUSY); + + bearer = get_bearer_by_tbs_index(client, inst_index); + if (bearer == NULL) + { + LOG_DBG("Could not lookup bearer for client %p and index 0x%02X", + client, + inst_index); + + return; + } + + list_for_every_container_safe(&ccp_call_control_client_cbs, listener, next, + _node) + { + if (listener->bearer_tech != NULL) + { + void *user_data = + COND_CODE_1(CONFIG_BT_CCP_CALL_CONTROL_CLIENT_CB_USER_DATA, + (listener->user_data), (NULL)); + + listener->bearer_tech(bearer, err, tech, user_data); + } + } +} + +int bt_ccp_call_control_client_read_bearer_tech( + struct bt_ccp_call_control_client_bearer *bearer) +{ + struct bt_ccp_call_control_client *client; + int err; + + err = validate_bearer_and_get_client(bearer, &client); + if (err != 0) + { + return err; + } + + err = bt_tbs_client_read_technology(client->conn, bearer->tbs_index); + if (err != 0) + { + atomic_clear_bit(client->flags, CCP_CALL_CONTROL_CLIENT_FLAG_BUSY); + + /* Return expected return values directly */ + + if (err == -ENOTCONN || err == -EBUSY) + { + LOG_DBG("bt_tbs_client_read_technology returned %d", err); + + return err; + } + + LOG_DBG("Unexpected error from bt_tbs_client_read_technology: " + "%d", err); + + return -ENOEXEC; + } + + return 0; +} +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY */ + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST) +static void tbs_client_read_bearer_uri_schemes_cb( + struct bt_conn *conn, int err, uint8_t inst_index, const char *uri_schemes) +{ + struct bt_ccp_call_control_client *client = get_client_by_conn(conn); + struct bt_ccp_call_control_client_cb *listener; + struct bt_ccp_call_control_client_cb *next; + struct bt_ccp_call_control_client_bearer *bearer; + + atomic_clear_bit(client->flags, CCP_CALL_CONTROL_CLIENT_FLAG_BUSY); + + bearer = get_bearer_by_tbs_index(client, inst_index); + if (bearer == NULL) + { + LOG_DBG("Could not lookup bearer for client %p and index 0x%02X", + client, + inst_index); + + return; + } + + list_for_every_container_safe(&ccp_call_control_client_cbs, listener, next, + _node) + { + if (listener->bearer_uri_schemes != NULL) + { + void *user_data = + COND_CODE_1(CONFIG_BT_CCP_CALL_CONTROL_CLIENT_CB_USER_DATA, + (listener->user_data), (NULL)); + + listener->bearer_uri_schemes(bearer, err, uri_schemes, user_data); + } + } +} + +int bt_ccp_call_control_client_read_bearer_uri_schemes( + struct bt_ccp_call_control_client_bearer *bearer) +{ + struct bt_ccp_call_control_client *client; + int err; + + err = validate_bearer_and_get_client(bearer, &client); + if (err != 0) + { + return err; + } + + err = bt_tbs_client_read_uri_list(client->conn, bearer->tbs_index); + if (err != 0) + { + atomic_clear_bit(client->flags, CCP_CALL_CONTROL_CLIENT_FLAG_BUSY); + + /* Return expected return values directly */ + + if (err == -ENOTCONN || err == -EBUSY) + { + LOG_DBG("bt_tbs_client_read_uri_list returned %d", err); + + return err; + } + + LOG_DBG("Unexpected error from bt_tbs_client_read_uri_list: %d", err); + + return -ENOEXEC; + } + + return 0; +} +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST */ + +static void connected_cb(struct bt_conn *conn, uint8_t err) +{ + static bool cbs_registered; + + ARG_UNUSED(conn); + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + /* We register the callbacks in the connected callback. That way we ensure + * that they are registered before any procedures are completed or we + * receive any notifications, while registering them as late as possible + */ + + if (err == BT_HCI_ERR_SUCCESS && !cbs_registered) + { + static struct bt_tbs_client_cb tbs_client_cbs = + { + .discover = tbs_client_discover_cb, +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) + .bearer_provider_name = + tbs_client_read_bearer_provider_name_cb, +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) + .bearer_uci = tbs_client_read_bearer_uci_cb, +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_UCI */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) + .technology = tbs_client_read_bearer_tech_cb, +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST) + .uri_list = tbs_client_read_bearer_uri_schemes_cb, +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST */ + }; + + __maybe_unused int cb_err; + + cb_err = bt_tbs_client_register_cb(&tbs_client_cbs); + DEBUGASSERT(cb_err == 0); + + cbs_registered = true; + } +} + +static void disconnected_cb(struct bt_conn *conn, uint8_t reason) +{ + struct bt_ccp_call_control_client *client; + + ARG_UNUSED(reason); + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + client = get_client_by_conn(conn); + + /* client->conn may be NULL */ + + if (client->conn == conn) + { + bt_conn_drop(&client->conn); + + memset(client->bearers, 0, sizeof(client->bearers)); + } +} + +BT_CONN_CB_DEFINE(conn_callbacks) = +{ + .connected = connected_cb, + .disconnected = disconnected_cb, +}; diff --git a/libs/zblue/audio/ccp_call_control_server.c b/libs/zblue/audio/ccp_call_control_server.c new file mode 100644 index 0000000000000..7c893f494edb9 --- /dev/null +++ b/libs/zblue/audio/ccp_call_control_server.c @@ -0,0 +1,697 @@ +/**************************************************************************** + * libs/zblue/audio/ccp_call_control_server.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Bluetooth CCP - Call Control Profile Call Control Server + * + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common/bt_str.h" + +LOG_MODULE_REGISTER(bt_ccp_call_control_server, + CONFIG_BT_CCP_CALL_CONTROL_SERVER_LOG_LEVEL); + +#define MUTEX_TIMEOUT MSEC2TICK(1000U) + +/* A service instance can either be a GTBS or a TBS instance */ + +struct bt_ccp_call_control_server_bearer +{ + char + provider_name + [CONFIG_BT_CCP_CALL_CONTROL_SERVER_PROVIDER_NAME_MAX_LENGTH + 1]; + char uci[BT_TBS_MAX_UCI_SIZE]; + enum bt_bearer_tech bearer_tech; + char uri_schemes[CONFIG_BT_CCP_CALL_CONTROL_SERVER_URI_SCHEMES_MAX_LENGTH + + 1]; + uint8_t tbs_index; + bool registered; + rmutex_t mutex; +}; + +static struct bt_ccp_call_control_server_bearer + bearers[CONFIG_BT_CCP_CALL_CONTROL_SERVER_BEARER_COUNT]; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/** + * @brief Returns a free bearer + * + * If the return value is not NULL, the caller is responsible for unlocking + * the mutex + * + * @return A free bearer, or NULL + */ + +static struct bt_ccp_call_control_server_bearer *get_free_bearer(void) +{ + for (size_t i = 0U; i < ARRAY_SIZE(bearers); i++) + { + int err; + + err = nxrmutex_trylock(&bearers[i].mutex); + if (err != 0) + { + continue; + } + + if (!bearers[i].registered) + { + return &bearers[i]; + } + + err = nxrmutex_unlock(&bearers[i].mutex); + DEBUGASSERT(err == 0); + } + + return NULL; +} + +static bool valid_uri_schemes(const char *uri_schemes, + size_t *uri_schemes_len) +{ + const char *c = uri_schemes; + size_t len = 0U; + bool new_uri; + + if (uri_schemes == NULL) + { + LOG_DBG("uri_schemes is NULL"); + + return false; + } + + if (*uri_schemes == '\0') + { + LOG_DBG("uri_schemes cannot be empty"); + + return false; + } + + /* Below we check for valid schemes. Since the allowed schemes are defined + * outside of Bluetooth (controlled by Internet Assigned Numbers Authority + * (IANA), we cannot do a reliable check on all existing ones, as new ones + * may be added at any time. We can, however, check the format. Generally + * URI schemes are lower case, but there are a few exceptions. + */ + + /* The scheme must start with a letter */ + + if (!isalpha(*c)) + { + return false; + } + + c++; + len++; + + /* The rest of the scheme shall be ASCII characters, numbers or `+`, `.` or + * `-` Since the list contains multiple schemes, the separator (`,`) is + * also allowed + */ + + new_uri = true; + while (*c != '\0') + { + if (new_uri) + { + /* The scheme must start with a letter */ + + if (!isalpha(*c)) + { + return false; + } + + new_uri = false; + } + else + { + const bool valid_char = isalnum(*c) || *c == '+' || *c == '.' || + *c == '-'; + + /* If character is not valid, we check if is the separator (`,`) + * and if there are additional characters after that, else reject + * it + */ + + if (!valid_char) + { + if (*c == ',' && *(c + 1) != '\0') + { + new_uri = true; + } + else + { + LOG_DBG("URI scheme contains invalid characters (%c)", *c); + return false; + } + } + } + + c++; + len++; + + if (len > BT_ATT_MAX_ATTRIBUTE_LEN) + { + LOG_DBG("Invalid URI schemes length (> BT_ATT_MAX_ATTRIBUTE_LEN " + "(%d))", + BT_ATT_MAX_ATTRIBUTE_LEN); + return false; + } + } + + *uri_schemes_len = len; + + return true; +} + +int bt_ccp_call_control_server_register_bearer( + const struct bt_tbs_register_param *param, + struct bt_ccp_call_control_server_bearer **bearer) +{ + struct bt_ccp_call_control_server_bearer *free_bearer; + size_t uri_schemes_len; + __maybe_unused int err; + int ret; + + if (bearer == NULL) + { + LOG_DBG("bearer is NULL"); + + return -EINVAL; + } + + if (param == NULL) + { + LOG_DBG("param is NULL"); + + return -EINVAL; + } + + if (!valid_uri_schemes(param->uri_schemes_supported, &uri_schemes_len)) + { + return -EINVAL; + } + + if (uri_schemes_len > + CONFIG_BT_CCP_CALL_CONTROL_SERVER_URI_SCHEMES_MAX_LENGTH) + { + LOG_DBG("uri_schemes buffer not large enough (is %zu > %d)", + uri_schemes_len, + CONFIG_BT_CCP_CALL_CONTROL_SERVER_URI_SCHEMES_MAX_LENGTH); + + return -ENOMEM; + } + + free_bearer = get_free_bearer(); + if (free_bearer == NULL) + { + return -ENOMEM; + } + + ret = bt_tbs_register_bearer(param); + if (ret < 0) + { + LOG_DBG("Failed to register TBS bearer: %d", ret); + + /* Return known errors */ + + if (!(ret == -EINVAL || ret == -EALREADY || ret == -EAGAIN || + ret == -ENOMEM)) + { + ret = -ENOEXEC; + } + } + else + { + free_bearer->registered = true; + free_bearer->tbs_index = (uint8_t)ret; + (void)utf8_lcpy(free_bearer->provider_name, param->provider_name, + sizeof(free_bearer->provider_name)); + (void)utf8_lcpy(free_bearer->uci, + param->uci, + sizeof(free_bearer->uci)); + free_bearer->bearer_tech = param->technology; + + (void)memcpy(free_bearer->uri_schemes, param->uri_schemes_supported, + uri_schemes_len); + free_bearer->uri_schemes[uri_schemes_len] = '\0'; + + *bearer = free_bearer; + + ret = 0; + } + + err = nxrmutex_unlock(&free_bearer->mutex); + DEBUGASSERT(err == 0); + + return ret; +} + +int bt_ccp_call_control_server_unregister_bearer( + struct bt_ccp_call_control_server_bearer *bearer) +{ + __maybe_unused int err; + int ret; + + if (bearer == NULL) + { + LOG_DBG("bearer is NULL"); + + return -EINVAL; + } + + err = nxrmutex_ticklock(&bearer->mutex, MUTEX_TIMEOUT); + DEBUGASSERT(err == 0); + + if (!bearer->registered) + { + LOG_DBG("Bearer %p already unregistered", bearer); + + ret = -EALREADY; + } + else + { + ret = bt_tbs_unregister_bearer(bearer->tbs_index); + if (ret == 0) + { + bearer->registered = false; + } + else + { + /* Return known errors */ + + if (!(ret == -EINVAL || ret == -EALREADY)) + { + ret = -ENOEXEC; + } + } + } + + err = nxrmutex_unlock(&bearer->mutex); + DEBUGASSERT(err == 0); + + return ret; +} + +int bt_ccp_call_control_server_set_bearer_provider_name( + struct bt_ccp_call_control_server_bearer *bearer, const char *name) +{ + __maybe_unused int err; + size_t len; + int ret; + + if (bearer == NULL) + { + LOG_DBG("bearer is NULL"); + + return -EINVAL; + } + + if (name == NULL) + { + LOG_DBG("name is NULL"); + + return -EINVAL; + } + + len = strlen(name); + if (len > CONFIG_BT_CCP_CALL_CONTROL_SERVER_PROVIDER_NAME_MAX_LENGTH || + len == 0U) + { + LOG_DBG("Invalid name length: %zu", len); + + return -EINVAL; + } + + err = nxrmutex_ticklock(&bearer->mutex, MUTEX_TIMEOUT); + DEBUGASSERT(err == 0); + + if (!bearer->registered) + { + LOG_DBG("Bearer %p not registered", bearer); + + ret = -EFAULT; + } + else + { + if (strcmp(bearer->provider_name, name) != 0) + { + ret = bt_tbs_set_bearer_provider_name(bearer->tbs_index, name); + if (ret == 0) + { + (void)utf8_lcpy(bearer->provider_name, name, + sizeof(bearer->provider_name)); + } + else + { + /* Return known errors */ + + if (!(ret == -EINVAL || ret == -EBUSY)) + { + ret = -ENOEXEC; + } + } + } + else + { + ret = 0; + } + } + + err = nxrmutex_unlock(&bearer->mutex); + DEBUGASSERT(err == 0); + + return ret; +} + +int bt_ccp_call_control_server_get_bearer_provider_name( + struct bt_ccp_call_control_server_bearer *bearer, char *name, + size_t name_size) +{ + size_t provider_name_len; + __maybe_unused int err; + int ret; + + if (bearer == NULL) + { + LOG_DBG("bearer is NULL"); + + return -EINVAL; + } + + if (name == NULL) + { + LOG_DBG("name is NULL"); + + return -EINVAL; + } + + err = nxrmutex_ticklock(&bearer->mutex, MUTEX_TIMEOUT); + DEBUGASSERT(err == 0); + + if (!bearer->registered) + { + LOG_DBG("Bearer %p not registered", bearer); + + ret = -EFAULT; + } + else + { + provider_name_len = strlen(bearer->provider_name); + if (name_size <= provider_name_len) + { + LOG_DBG("name buffer not large enough (is <= %zu)", + provider_name_len); + + ret = -ENOMEM; + } + else + { + (void)memcpy(name, bearer->provider_name, provider_name_len); + name[provider_name_len] = '\0'; + + ret = 0; + } + } + + err = nxrmutex_unlock(&bearer->mutex); + DEBUGASSERT(err == 0); + + return ret; +} + +int bt_ccp_call_control_server_get_bearer_uci( + struct bt_ccp_call_control_server_bearer *bearer, + char uci[BT_TBS_MAX_UCI_SIZE]) +{ + __maybe_unused int err; + int ret; + size_t uci_len; + + if (bearer == NULL) + { + LOG_DBG("bearer is NULL"); + + return -EINVAL; + } + + if (uci == NULL) + { + LOG_DBG("uci is NULL"); + + return -EINVAL; + } + + err = nxrmutex_ticklock(&bearer->mutex, MUTEX_TIMEOUT); + DEBUGASSERT(err == 0); + + if (!bearer->registered) + { + LOG_DBG("Bearer %p not registered", bearer); + + ret = -EFAULT; + } + else + { + uci_len = strlen(bearer->uci); + DEBUGASSERT(uci_len < BT_TBS_MAX_UCI_SIZE); + (void)memcpy(uci, bearer->uci, uci_len); + uci[uci_len] = '\0'; + + ret = 0; + } + + err = nxrmutex_unlock(&bearer->mutex); + DEBUGASSERT(err == 0); + + return ret; +} + +int bt_ccp_call_control_server_set_bearer_tech( + struct bt_ccp_call_control_server_bearer *bearer, enum bt_bearer_tech tech) +{ + int err; + + if (bearer == NULL) + { + LOG_DBG("bearer is NULL"); + + return -EINVAL; + } + + if (!bearer->registered) + { + LOG_DBG("Bearer %p not registered", bearer); + + return -EFAULT; + } + + if (!IN_RANGE(tech, BT_BEARER_TECH_3G, BT_BEARER_TECH_WCDMA)) + { + LOG_DBG("Invalid tech param: %d", tech); + + return -EINVAL; + } + + if (bearer->bearer_tech == tech) + { + return 0; + } + + err = bt_tbs_set_bearer_technology(bearer->tbs_index, tech); + if (err == 0) + { + bearer->bearer_tech = tech; + } + + return err; +} + +int bt_ccp_call_control_server_get_bearer_tech( + const struct bt_ccp_call_control_server_bearer *bearer, + enum bt_bearer_tech *tech) +{ + if (bearer == NULL) + { + LOG_DBG("bearer is NULL"); + + return -EINVAL; + } + + if (tech == NULL) + { + LOG_DBG("tech is NULL"); + + return -EINVAL; + } + + if (!bearer->registered) + { + LOG_DBG("Bearer %p not registered", bearer); + + return -EFAULT; + } + + *tech = bearer->bearer_tech; + + return 0; +} + +int bt_ccp_call_control_server_set_bearer_uri_schemes( + struct bt_ccp_call_control_server_bearer *bearer, const char *uri_schemes) +{ + size_t uri_schemes_len; + __maybe_unused int err; + int ret; + + if (bearer == NULL) + { + LOG_DBG("bearer is NULL"); + + return -EINVAL; + } + + if (!valid_uri_schemes(uri_schemes, &uri_schemes_len)) + { + return -EINVAL; + } + + if (uri_schemes_len > + CONFIG_BT_CCP_CALL_CONTROL_SERVER_URI_SCHEMES_MAX_LENGTH) + { + LOG_DBG("uri_schemes buffer not large enough (is %zu > %d)", + uri_schemes_len, + CONFIG_BT_CCP_CALL_CONTROL_SERVER_URI_SCHEMES_MAX_LENGTH); + + return -ENOMEM; + } + + err = nxrmutex_ticklock(&bearer->mutex, MUTEX_TIMEOUT); + DEBUGASSERT(err == 0); + + if (!bearer->registered) + { + LOG_DBG("Bearer %p not registered", bearer); + + ret = -EFAULT; + } + else if (strcmp(bearer->uri_schemes, uri_schemes) == 0) + { + LOG_DBG("Value unchanged"); + ret = 0; + } + else + { + ret = bt_tbs_set_uri_scheme_list(bearer->tbs_index, uri_schemes); + if (ret == 0) + { + (void)memcpy(bearer->uri_schemes, uri_schemes, uri_schemes_len); + bearer->uri_schemes[uri_schemes_len] = '\0'; + } + } + + err = nxrmutex_unlock(&bearer->mutex); + DEBUGASSERT(err == 0); + + return ret; +} + +int bt_ccp_call_control_server_get_bearer_uri_schemes( + struct bt_ccp_call_control_server_bearer *bearer, char *uri_schemes, + size_t uri_schemes_size) +{ + size_t uri_schemes_len; + __maybe_unused int err; + int ret; + + if (bearer == NULL) + { + LOG_DBG("bearer is NULL"); + + return -EINVAL; + } + + if (uri_schemes == NULL) + { + LOG_DBG("uri_schemes is NULL"); + + return -EINVAL; + } + + err = nxrmutex_ticklock(&bearer->mutex, MUTEX_TIMEOUT); + DEBUGASSERT(err == 0); + + if (!bearer->registered) + { + LOG_DBG("Bearer %p not registered", bearer); + + ret = -EFAULT; + } + else + { + uri_schemes_len = strlen(bearer->uri_schemes); + if (uri_schemes_size <= uri_schemes_len) + { + LOG_DBG("name buffer not large enough (is <= %zu)", + uri_schemes_len); + + ret = -ENOMEM; + } + else + { + (void)memcpy(uri_schemes, bearer->uri_schemes, uri_schemes_len); + uri_schemes[uri_schemes_len] = '\0'; + ret = 0; + } + } + + err = nxrmutex_unlock(&bearer->mutex); + DEBUGASSERT(err == 0); + + return ret; +} + +static int ccp_server_init(void) +{ + ARRAY_FOR_EACH_PTR(bearers, bearer) + { + __maybe_unused int err; + + err = nxrmutex_init(&bearer->mutex); + DEBUGASSERT(err == 0); + } + + return 0; +} + +SYS_INIT(ccp_server_init, APPLICATION, 0); diff --git a/libs/zblue/audio/codec.c b/libs/zblue/audio/codec.c new file mode 100644 index 0000000000000..8169e93654144 --- /dev/null +++ b/libs/zblue/audio/codec.c @@ -0,0 +1,2796 @@ +/**************************************************************************** + * libs/zblue/audio/codec.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Bose Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** @file + * @brief Bluetooth LE-Audio codec LTV parsing + * + * Helper functions to parse codec config data as specified in the Bluetooth + * assigned numbers for Generic Audio. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common/bt_str.h" + +LOG_MODULE_REGISTER(bt_audio_codec, CONFIG_BT_AUDIO_CODEC_LOG_LEVEL); + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bt_audio_codec_cfg_freq_to_freq_hz(enum bt_audio_codec_cfg_freq freq) +{ + switch (freq) + { + case BT_AUDIO_CODEC_CFG_FREQ_8KHZ: + return 8000; + case BT_AUDIO_CODEC_CFG_FREQ_11KHZ: + return 11025; + case BT_AUDIO_CODEC_CFG_FREQ_16KHZ: + return 16000; + case BT_AUDIO_CODEC_CFG_FREQ_22KHZ: + return 22050; + case BT_AUDIO_CODEC_CFG_FREQ_24KHZ: + return 24000; + case BT_AUDIO_CODEC_CFG_FREQ_32KHZ: + return 32000; + case BT_AUDIO_CODEC_CFG_FREQ_44KHZ: + return 44100; + case BT_AUDIO_CODEC_CFG_FREQ_48KHZ: + return 48000; + case BT_AUDIO_CODEC_CFG_FREQ_88KHZ: + return 88200; + case BT_AUDIO_CODEC_CFG_FREQ_96KHZ: + return 96000; + case BT_AUDIO_CODEC_CFG_FREQ_176KHZ: + return 176400; + case BT_AUDIO_CODEC_CFG_FREQ_192KHZ: + return 192000; + case BT_AUDIO_CODEC_CFG_FREQ_384KHZ: + return 384000; + default: + return -EINVAL; + } +} + +int bt_audio_codec_cfg_freq_hz_to_freq(uint32_t freq_hz) +{ + switch (freq_hz) + { + case 8000U: + return BT_AUDIO_CODEC_CFG_FREQ_8KHZ; + case 11025U: + return BT_AUDIO_CODEC_CFG_FREQ_11KHZ; + case 16000U: + return BT_AUDIO_CODEC_CFG_FREQ_16KHZ; + case 22050U: + return BT_AUDIO_CODEC_CFG_FREQ_22KHZ; + case 24000U: + return BT_AUDIO_CODEC_CFG_FREQ_24KHZ; + case 32000U: + return BT_AUDIO_CODEC_CFG_FREQ_32KHZ; + case 44100U: + return BT_AUDIO_CODEC_CFG_FREQ_44KHZ; + case 48000U: + return BT_AUDIO_CODEC_CFG_FREQ_48KHZ; + case 88200U: + return BT_AUDIO_CODEC_CFG_FREQ_88KHZ; + case 96000U: + return BT_AUDIO_CODEC_CFG_FREQ_96KHZ; + case 176400U: + return BT_AUDIO_CODEC_CFG_FREQ_176KHZ; + case 192000U: + return BT_AUDIO_CODEC_CFG_FREQ_192KHZ; + case 384000U: + return BT_AUDIO_CODEC_CFG_FREQ_384KHZ; + default: + return -EINVAL; + } +} + +int bt_audio_codec_cfg_frame_dur_to_frame_dur_us( + enum bt_audio_codec_cfg_frame_dur frame_dur) +{ + switch (frame_dur) + { + case BT_AUDIO_CODEC_CFG_DURATION_7_5: + return 7500; + case BT_AUDIO_CODEC_CFG_DURATION_10: + return 10000; + default: + return -EINVAL; + } +} + +int bt_audio_codec_cfg_frame_dur_us_to_frame_dur(uint32_t frame_dur_us) +{ + switch (frame_dur_us) + { + case 7500U: + return BT_AUDIO_CODEC_CFG_DURATION_7_5; + case 10000U: + return BT_AUDIO_CODEC_CFG_DURATION_10; + default: + return -EINVAL; + } +} + +#if CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE > 0 || \ + CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE > 0 || \ + CONFIG_BT_AUDIO_CODEC_CAP_MAX_DATA_SIZE > 0 || \ + CONFIG_BT_AUDIO_CODEC_CAP_MAX_METADATA_SIZE > 0 + +static int ltv_set_val( + struct net_buf_simple *buf, uint8_t type, const uint8_t *data, + size_t data_len) +{ + size_t new_buf_len; + uint16_t i = 0U; + + while (i < buf->len) + { + uint8_t *len_ptr = &buf->data[i]; + const uint8_t len = buf->data[i]; + uint8_t data_type; + uint8_t value_len; + + i += sizeof(len); + + if (i + len > buf->len || len < sizeof(data_type)) + { + LOG_DBG("Invalid len %u at i = %u", len, i); + + return -EINVAL; + } + + data_type = buf->data[i]; + i += sizeof(data_type); + + value_len = len - sizeof(data_type); + + if (data_type == type) + { + uint8_t *value = &buf->data[i]; + + if (data_len == value_len) + { + memcpy(value, data, data_len); + } + else + { + const int16_t diff = data_len - value_len; + uint8_t *old_next_data_start; + uint8_t *new_next_data_start; + uint8_t data_len_to_move; + + /* Check if this is the last value in the buffer */ + + if (value + value_len == buf->data + buf->len) + { + data_len_to_move = 0U; + } + else + { + old_next_data_start = value + value_len; + new_next_data_start = value + data_len; + data_len_to_move = + buf->len - (old_next_data_start - buf->data); + } + + if (diff < 0) + { + /* In this case we need to move memory around after the + * copy to fit the new shorter data + */ + + memcpy(value, data, data_len); + if (data_len_to_move > 0U) + { + memmove(new_next_data_start, old_next_data_start, + data_len_to_move); + } + } + else + { + /* In this case we need to move memory around before + * the copy to fit the new longer data + */ + + if ((buf->len + diff) > buf->size) + { + LOG_DBG("Cannot fit data_len %zu in buf with len " + "%u and size %u", + data_len, buf->len, buf->size); + return -ENOMEM; + } + + if (data_len_to_move > 0U) + { + memmove(new_next_data_start, old_next_data_start, + data_len_to_move); + } + + memcpy(value, data, data_len); + } + + buf->len += diff; + *len_ptr += diff; + } + + return buf->len; + } + + i += value_len; + } + + /* If we reach here, we did not find the data in the buffer, so we simply + * add it + */ + + new_buf_len = buf->len + 1 /* len */ + sizeof(type) + data_len; + if (new_buf_len <= buf->size) + { + net_buf_simple_add_u8(buf, data_len + sizeof(type)); /* len */ + net_buf_simple_add_u8(buf, type); /* type */ + if (data_len > 0U) + { + net_buf_simple_add_mem(buf, data, data_len); /* value */ + } + } + else + { + LOG_DBG("Cannot fit data_len %zu in codec_cfg with len %u and size %u", + data_len, buf->len, + buf->size); + return -ENOMEM; + } + + return buf->len; +} + +static int ltv_unset_val(struct net_buf_simple *buf, uint8_t type) +{ + uint16_t i = 0U; + + while (i < buf->len) + { + uint8_t *ltv_start = &buf->data[i]; + const uint8_t len = buf->data[i]; + uint8_t data_type; + uint8_t value_len; + + i += sizeof(len); + + if (i + len > buf->len || len < sizeof(data_type)) + { + LOG_DBG("Invalid len %u at i = %u", len, i); + + return -EINVAL; + } + + data_type = buf->data[i]; + i += sizeof(data_type); + + value_len = len - sizeof(data_type); + + if (data_type == type) + { + const uint8_t ltv_size = + value_len + sizeof(data_type) + sizeof(len); + uint8_t *value = &buf->data[i]; + + /* Check if this is not the last value in the buffer */ + + if (value + value_len != buf->data + buf->len) + { + uint8_t *next_data_start; + uint8_t data_len_to_move; + + next_data_start = value + value_len; + data_len_to_move = buf->len - (next_data_start - buf->data); + memmove(ltv_start, next_data_start, data_len_to_move); + + LOG_ERR("buf->data %p, ltv_start %p, value_len %u " + "next_data_start " + "%p data_len_to_move %u", + buf->data, ltv_start, value_len, next_data_start, + data_len_to_move); + } + + /* else just reduce the length of the buffer */ + + buf->len -= ltv_size; + + return buf->len; + } + + i += value_len; + } + + return buf->len; +} +#endif /* CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE > 0 || \ + + * CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE > 0 || \ + * CONFIG_BT_AUDIO_CODEC_CAP_MAX_DATA_SIZE > 0 || \ + * CONFIG_BT_AUDIO_CODEC_CAP_MAX_METADATA_SIZE > 0 \ + */ + +#if CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE > 0 + +static void init_net_buf_simple_from_codec_cfg(struct net_buf_simple *buf, + struct bt_audio_codec_cfg *codec_cfg) +{ + buf->__buf = codec_cfg->data; + buf->data = codec_cfg->data; + buf->size = sizeof(codec_cfg->data); + buf->len = codec_cfg->data_len; +} + +int bt_audio_codec_cfg_get_val(const struct bt_audio_codec_cfg *codec_cfg, + enum bt_audio_codec_cfg_type type, + const uint8_t **data) +{ + if (codec_cfg == NULL) + { + LOG_DBG("codec is NULL"); + return -EINVAL; + } + + if (data == NULL) + { + LOG_DBG("data is NULL"); + return -EINVAL; + } + + return bt_audio_data_get_val(codec_cfg->data, codec_cfg->data_len, + (uint8_t)type, data); +} + +int bt_audio_codec_cfg_set_val(struct bt_audio_codec_cfg *codec_cfg, + enum bt_audio_codec_cfg_type type, + const uint8_t *data, + size_t data_len) +{ + struct net_buf_simple buf; + int ret; + + if (codec_cfg == NULL) + { + LOG_DBG("codec_cfg is NULL"); + return -EINVAL; + } + + if (data == NULL) + { + LOG_DBG("data is NULL"); + return -EINVAL; + } + + if (data_len == 0U || data_len > UINT8_MAX) + { + LOG_DBG("Invalid data_len %zu", data_len); + return -EINVAL; + } + + init_net_buf_simple_from_codec_cfg(&buf, codec_cfg); + + ret = ltv_set_val(&buf, type, data, data_len); + if (ret >= 0) + { + codec_cfg->data_len = ret; + } + + return ret; +} + +int bt_audio_codec_cfg_unset_val(struct bt_audio_codec_cfg *codec_cfg, + enum bt_audio_codec_cfg_type type) +{ + struct net_buf_simple buf; + int ret; + + if (codec_cfg == NULL) + { + LOG_DBG("codec_cfg is NULL"); + return -EINVAL; + } + + init_net_buf_simple_from_codec_cfg(&buf, codec_cfg); + + ret = ltv_unset_val(&buf, type); + if (ret >= 0) + { + codec_cfg->data_len = ret; + } + + return ret; +} + +int bt_audio_codec_cfg_get_freq(const struct bt_audio_codec_cfg *codec_cfg) +{ + enum bt_audio_codec_cfg_freq freq; + const uint8_t *data; + uint8_t data_len; + + if (codec_cfg == NULL) + { + LOG_DBG("codec is NULL"); + return -EINVAL; + } + + data_len = bt_audio_codec_cfg_get_val(codec_cfg, BT_AUDIO_CODEC_CFG_FREQ, + &data); + if (data == NULL) + { + return -ENODATA; + } + + if (data_len != sizeof(uint8_t)) + { + return -EBADMSG; + } + + freq = data[0]; + if (bt_audio_codec_cfg_freq_to_freq_hz(freq) < 0) + { + LOG_DBG("Invalid freq value: 0x%02X", freq); + return -EBADMSG; + } + + return freq; +} + +int bt_audio_codec_cfg_set_freq(struct bt_audio_codec_cfg *codec_cfg, + enum bt_audio_codec_cfg_freq freq) +{ + uint8_t freq_u8; + + if (bt_audio_codec_cfg_freq_to_freq_hz(freq) < 0) + { + LOG_DBG("Invalid freq value: %d", freq); + return -EINVAL; + } + + freq_u8 = (uint8_t)freq; + + return bt_audio_codec_cfg_set_val(codec_cfg, BT_AUDIO_CODEC_CFG_FREQ, + &freq_u8, sizeof(freq_u8)); +} + +int bt_audio_codec_cfg_get_frame_dur( + const struct bt_audio_codec_cfg *codec_cfg) +{ + enum bt_audio_codec_cfg_frame_dur frame_dur; + const uint8_t *data; + uint8_t data_len; + + if (codec_cfg == NULL) + { + LOG_DBG("codec is NULL"); + return -EINVAL; + } + + data_len = bt_audio_codec_cfg_get_val(codec_cfg, + BT_AUDIO_CODEC_CFG_DURATION, + &data); + if (data == NULL) + { + return -ENODATA; + } + + if (data_len != sizeof(uint8_t)) + { + return -EBADMSG; + } + + frame_dur = data[0]; + if (bt_audio_codec_cfg_frame_dur_to_frame_dur_us(frame_dur) < 0) + { + LOG_DBG("Invalid frame_dur value: 0x%02X", frame_dur); + return -EBADMSG; + } + + return frame_dur; +} + +int bt_audio_codec_cfg_set_frame_dur(struct bt_audio_codec_cfg *codec_cfg, + enum bt_audio_codec_cfg_frame_dur frame_dur) +{ + uint8_t frame_dur_u8; + + if (bt_audio_codec_cfg_frame_dur_to_frame_dur_us(frame_dur) < 0) + { + LOG_DBG("Invalid freq value: %d", frame_dur); + return -EINVAL; + } + + frame_dur_u8 = (uint8_t)frame_dur; + + return bt_audio_codec_cfg_set_val(codec_cfg, BT_AUDIO_CODEC_CFG_DURATION, + &frame_dur_u8, + sizeof(frame_dur_u8)); +} + +int bt_audio_codec_cfg_get_chan_allocation( + const struct bt_audio_codec_cfg *codec_cfg, + enum bt_audio_location *chan_allocation, bool fallback_to_default) +{ + const uint8_t *data; + uint8_t data_len; + + *chan_allocation = 0; + + if (codec_cfg == NULL) + { + LOG_DBG("codec is NULL"); + return -EINVAL; + } + + if (chan_allocation == NULL) + { + return -EINVAL; + } + + data_len = bt_audio_codec_cfg_get_val(codec_cfg, + BT_AUDIO_CODEC_CFG_CHAN_ALLOC, + &data); + if (data == NULL) + { + if (fallback_to_default && codec_cfg->id == BT_HCI_CODING_FORMAT_LC3) + { + *chan_allocation = BT_AUDIO_LOCATION_MONO_AUDIO; + + return 0; + } + + return -ENODATA; + } + + if (data_len != sizeof(uint32_t)) + { + return -EBADMSG; + } + + *chan_allocation = sys_get_le32(data); + + return 0; +} + +int bt_audio_codec_cfg_set_chan_allocation( + struct bt_audio_codec_cfg *codec_cfg, + enum bt_audio_location chan_allocation) +{ + uint32_t chan_allocation_u32; + + if ((chan_allocation & BT_AUDIO_LOCATION_ANY) != chan_allocation) + { + LOG_DBG("Invalid chan_allocation value: 0x%08X", chan_allocation); + return -EINVAL; + } + + chan_allocation_u32 = sys_cpu_to_le32((uint32_t)chan_allocation); + + return bt_audio_codec_cfg_set_val(codec_cfg, BT_AUDIO_CODEC_CFG_CHAN_ALLOC, + (const uint8_t *)&chan_allocation_u32, + sizeof(chan_allocation_u32)); +} + +int bt_audio_codec_cfg_get_octets_per_frame( + const struct bt_audio_codec_cfg *codec_cfg) +{ + const uint8_t *data; + uint8_t data_len; + + if (codec_cfg == NULL) + { + LOG_DBG("codec is NULL"); + return -EINVAL; + } + + data_len = bt_audio_codec_cfg_get_val(codec_cfg, + BT_AUDIO_CODEC_CFG_FRAME_LEN, &data); + if (data == NULL) + { + return -ENODATA; + } + + if (data_len != sizeof(uint16_t)) + { + return -EBADMSG; + } + + return sys_get_le16(data); +} + +int bt_audio_codec_cfg_set_octets_per_frame( + struct bt_audio_codec_cfg *codec_cfg, uint16_t octets_per_frame) +{ + uint16_t octets_per_frame_le16; + + octets_per_frame_le16 = sys_cpu_to_le16(octets_per_frame); + + return bt_audio_codec_cfg_set_val(codec_cfg, BT_AUDIO_CODEC_CFG_FRAME_LEN, + (uint8_t *)&octets_per_frame_le16, + sizeof(octets_per_frame_le16)); +} + +int bt_audio_codec_cfg_get_frame_blocks_per_sdu( + const struct bt_audio_codec_cfg *codec_cfg, bool fallback_to_default) +{ + const uint8_t *data; + uint8_t data_len; + + if (codec_cfg == NULL) + { + LOG_DBG("codec is NULL"); + return -EINVAL; + } + + data_len = bt_audio_codec_cfg_get_val(codec_cfg, + BT_AUDIO_CODEC_CFG_FRAME_BLKS_PER_SDU, + &data); + if (data == NULL) + { + if (fallback_to_default && codec_cfg->id == BT_HCI_CODING_FORMAT_LC3) + { + return 1; + } + + return -ENODATA; + } + + if (data_len != sizeof(uint8_t)) + { + return -EBADMSG; + } + + return data[0]; +} + +int bt_audio_codec_cfg_set_frame_blocks_per_sdu( + struct bt_audio_codec_cfg *codec_cfg, uint8_t frame_blocks) +{ + return bt_audio_codec_cfg_set_val(codec_cfg, + BT_AUDIO_CODEC_CFG_FRAME_BLKS_PER_SDU, + &frame_blocks, + sizeof(frame_blocks)); +} +#endif /* CONFIG_BT_AUDIO_CODEC_CFG_MAX_DATA_SIZE > 0 */ + +#if CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE > 0 || \ + CONFIG_BT_AUDIO_CODEC_CAP_MAX_METADATA_SIZE > 0 + +static void init_net_buf_simple_from_meta( + struct net_buf_simple *buf, uint8_t meta[], size_t meta_len, + size_t meta_size) +{ + buf->__buf = meta; + buf->data = meta; + buf->size = meta_size; + buf->len = meta_len; +} + +static int codec_meta_get_val(const uint8_t meta[], + size_t meta_len, + enum bt_audio_metadata_type type, + const uint8_t **data) +{ + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + if (data == NULL) + { + LOG_DBG("data is NULL"); + return -EINVAL; + } + + return bt_audio_data_get_val(meta, meta_len, (uint8_t)type, data); +} + +static int codec_meta_set_val( + uint8_t meta[], size_t meta_len, size_t meta_size, + enum bt_audio_metadata_type type, const uint8_t *data, size_t data_len) +{ + struct net_buf_simple buf; + + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + if (data == NULL && data_len != 0U) + { + LOG_DBG("data is NULL"); + return -EINVAL; + } + + if (data_len > UINT8_MAX) + { + LOG_DBG("Invalid data_len %zu", data_len); + return -EINVAL; + } + + init_net_buf_simple_from_meta(&buf, meta, meta_len, meta_size); + + return ltv_set_val(&buf, (uint8_t)type, data, data_len); +} + +static int codec_meta_unset_val( + uint8_t meta[], size_t meta_len, size_t meta_size, + enum bt_audio_metadata_type type) +{ + struct net_buf_simple buf; + + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + init_net_buf_simple_from_meta(&buf, meta, meta_len, meta_size); + + return ltv_unset_val(&buf, type); +} + +static int codec_meta_get_pref_context(const uint8_t meta[], size_t meta_len) +{ + const uint8_t *data; + int ret; + + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + ret = codec_meta_get_val(meta, meta_len, + BT_AUDIO_METADATA_TYPE_PREF_CONTEXT, + &data); + if (data == NULL) + { + return -ENODATA; + } + + if (ret != sizeof(uint16_t)) + { + return -EBADMSG; + } + + return sys_get_le16(data); +} + +static int codec_meta_set_pref_context( + uint8_t meta[], size_t meta_len, size_t meta_size, + enum bt_audio_context ctx) +{ + uint16_t ctx_le16; + + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + if ((ctx & BT_AUDIO_CONTEXT_TYPE_ANY) != ctx) + { + LOG_DBG("Invalid ctx value: %d", ctx); + return -EINVAL; + } + + ctx_le16 = sys_cpu_to_le16((uint16_t)ctx); + + return codec_meta_set_val(meta, meta_len, meta_size, + BT_AUDIO_METADATA_TYPE_PREF_CONTEXT, + (const uint8_t *)&ctx_le16, sizeof(ctx_le16)); +} + +static int codec_meta_get_stream_context(const uint8_t meta[], + size_t meta_len) +{ + const uint8_t *data; + int ret; + + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + ret = codec_meta_get_val(meta, meta_len, + BT_AUDIO_METADATA_TYPE_STREAM_CONTEXT, + &data); + if (data == NULL) + { + return -ENODATA; + } + + if (ret != sizeof(uint16_t)) + { + return -EBADMSG; + } + + return sys_get_le16(data); +} + +static int codec_meta_set_stream_context( + uint8_t meta[], size_t meta_len, size_t meta_size, + enum bt_audio_context ctx) +{ + uint16_t ctx_le16; + + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + if ((ctx & BT_AUDIO_CONTEXT_TYPE_ANY) != ctx) + { + LOG_DBG("Invalid ctx value: %d", ctx); + return -EINVAL; + } + + ctx_le16 = sys_cpu_to_le16((uint16_t)ctx); + + return codec_meta_set_val(meta, meta_len, meta_size, + BT_AUDIO_METADATA_TYPE_STREAM_CONTEXT, + (const uint8_t *)&ctx_le16, sizeof(ctx_le16)); +} + +static int codec_meta_get_program_info(const uint8_t meta[], size_t meta_len, + const uint8_t **program_info) +{ + const uint8_t *data; + int ret; + + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + if (program_info == NULL) + { + LOG_DBG("program_info is NULL"); + return -EINVAL; + } + + ret = codec_meta_get_val(meta, meta_len, + BT_AUDIO_METADATA_TYPE_PROGRAM_INFO, + &data); + if (data == NULL) + { + return -ENODATA; + } + + *program_info = data; + + return ret; +} + +static int codec_meta_set_program_info( + uint8_t meta[], size_t meta_len, size_t meta_size, + const uint8_t *program_info, size_t program_info_len) +{ + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + if (program_info == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + return codec_meta_set_val(meta, meta_len, meta_size, + BT_AUDIO_METADATA_TYPE_PROGRAM_INFO, + program_info, program_info_len); +} + +static int codec_meta_get_lang(const uint8_t meta[], + size_t meta_len, const uint8_t **lang) +{ + const uint8_t *data; + int ret; + + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + if (lang == NULL) + { + LOG_DBG("lang is NULL"); + return -EINVAL; + } + + ret = codec_meta_get_val(meta, meta_len, BT_AUDIO_METADATA_TYPE_LANG, + &data); + if (data == NULL) + { + return -ENODATA; + } + + if (ret != BT_AUDIO_LANG_SIZE) + { + return -EBADMSG; + } + + *lang = data; + + return 0; +} + +static int codec_meta_set_lang( + uint8_t meta[], size_t meta_len, size_t meta_size, + const uint8_t lang[BT_AUDIO_LANG_SIZE]) +{ + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + if (lang == NULL) + { + LOG_DBG("lang is NULL"); + return -EINVAL; + } + + return codec_meta_set_val(meta, meta_len, meta_size, + BT_AUDIO_METADATA_TYPE_LANG, lang, + BT_AUDIO_LANG_SIZE); +} + +static int codec_meta_get_ccid_list(const uint8_t meta[], size_t meta_len, + const uint8_t **ccid_list) +{ + const uint8_t *data; + int ret; + + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + if (ccid_list == NULL) + { + LOG_DBG("ccid_list is NULL"); + return -EINVAL; + } + + ret = codec_meta_get_val(meta, meta_len, BT_AUDIO_METADATA_TYPE_CCID_LIST, + &data); + if (data == NULL) + { + return -ENODATA; + } + + *ccid_list = data; + + return ret; +} + +static int codec_meta_set_ccid_list( + uint8_t meta[], size_t meta_len, size_t meta_size, + const uint8_t *ccid_list, size_t ccid_list_len) +{ + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + if (ccid_list == NULL) + { + LOG_DBG("ccid_list is NULL"); + return -EINVAL; + } + + return codec_meta_set_val(meta, meta_len, meta_size, + BT_AUDIO_METADATA_TYPE_CCID_LIST, ccid_list, + ccid_list_len); +} + +static int codec_meta_get_parental_rating(const uint8_t meta[], + size_t meta_len) +{ + const uint8_t *data; + int ret; + + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + ret = codec_meta_get_val(meta, meta_len, + BT_AUDIO_METADATA_TYPE_PARENTAL_RATING, + &data); + if (data == NULL) + { + return -ENODATA; + } + + if (ret != sizeof(uint8_t)) + { + return -EBADMSG; + } + + return data[0]; +} + +static int codec_meta_set_parental_rating( + uint8_t meta[], size_t meta_len, size_t meta_size, + enum bt_audio_parental_rating parental_rating) +{ + uint8_t parental_rating_u8; + + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + if (parental_rating > BT_AUDIO_PARENTAL_RATING_AGE_18_OR_ABOVE) + { + LOG_DBG("Invalid parental_rating value: %d", parental_rating); + return -EINVAL; + } + + parental_rating_u8 = (uint8_t)parental_rating; + + return codec_meta_set_val(meta, meta_len, meta_size, + BT_AUDIO_METADATA_TYPE_PARENTAL_RATING, + &parental_rating_u8, sizeof(parental_rating_u8)); +} + +static int codec_meta_get_program_info_uri( + const uint8_t meta[], size_t meta_len, const uint8_t **program_info_uri) +{ + const uint8_t *data; + int ret; + + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + if (program_info_uri == NULL) + { + LOG_DBG("program_info_uri is NULL"); + return -EINVAL; + } + + ret = codec_meta_get_val(meta, meta_len, + BT_AUDIO_METADATA_TYPE_PROGRAM_INFO_URI, + &data); + if (data == NULL) + { + return -ENODATA; + } + + *program_info_uri = data; + + return ret; +} + +static int codec_meta_set_program_info_uri( + uint8_t meta[], size_t meta_len, size_t meta_size, + const uint8_t *program_info_uri, size_t program_info_uri_len) +{ + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + if (program_info_uri == NULL) + { + LOG_DBG("program_info_uri is NULL"); + return -EINVAL; + } + + return codec_meta_set_val(meta, meta_len, meta_size, + BT_AUDIO_METADATA_TYPE_PROGRAM_INFO_URI, + program_info_uri, program_info_uri_len); +} + +static int codec_meta_get_audio_active_state(const uint8_t meta[], + size_t meta_len) +{ + const uint8_t *data; + int ret; + + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + ret = codec_meta_get_val(meta, meta_len, + BT_AUDIO_METADATA_TYPE_AUDIO_STATE, + &data); + if (data == NULL) + { + return -ENODATA; + } + + if (ret != sizeof(uint8_t)) + { + return -EBADMSG; + } + + return data[0]; +} + +static int codec_meta_set_audio_active_state( + uint8_t meta[], size_t meta_len, size_t meta_size, + enum bt_audio_active_state state) +{ + uint8_t state_u8; + + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + if (state != BT_AUDIO_ACTIVE_STATE_DISABLED && + state != BT_AUDIO_ACTIVE_STATE_ENABLED) + { + LOG_DBG("Invalid state value: %d", state); + return -EINVAL; + } + + state_u8 = (uint8_t)state; + + return codec_meta_set_val(meta, meta_len, meta_size, + BT_AUDIO_METADATA_TYPE_AUDIO_STATE, + &state_u8, sizeof(state_u8)); +} + +static int codec_meta_get_bcast_audio_immediate_rend_flag( + const uint8_t meta[], size_t meta_len) +{ + const uint8_t *data; + + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + return codec_meta_get_val(meta, meta_len, + BT_AUDIO_METADATA_TYPE_BROADCAST_IMMEDIATE, + &data); +} + +static int codec_meta_set_bcast_audio_immediate_rend_flag( + uint8_t meta[], size_t meta_len, size_t meta_size) +{ + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + return codec_meta_set_val(meta, meta_len, meta_size, + BT_AUDIO_METADATA_TYPE_BROADCAST_IMMEDIATE, + NULL, 0); +} + +static int codec_meta_get_assisted_listening_stream(const uint8_t meta[], + size_t meta_len) +{ + const uint8_t *data; + int ret; + + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + ret = codec_meta_get_val(meta, meta_len, + BT_AUDIO_METADATA_TYPE_ASSISTED_LISTENING_STREAM, + &data); + if (data == NULL) + { + return -ENODATA; + } + + if (ret != sizeof(uint8_t)) + { + return -EBADMSG; + } + + return data[0]; +} + +static int codec_meta_set_assisted_listening_stream( + uint8_t meta[], size_t meta_len, size_t meta_size, + enum bt_audio_assisted_listening_stream val) +{ + uint8_t val_u8; + + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + if (val != BT_AUDIO_ASSISTED_LISTENING_STREAM_UNSPECIFIED) + { + LOG_DBG("Invalid value: %d", val); + return -EINVAL; + } + + val_u8 = (uint8_t)val; + + return codec_meta_set_val(meta, meta_len, meta_size, + BT_AUDIO_METADATA_TYPE_ASSISTED_LISTENING_STREAM, + &val_u8, + sizeof(val_u8)); +} + +static int codec_meta_get_broadcast_name( + const uint8_t meta[], size_t meta_len, const uint8_t **broadcast_name) +{ + char + broadcast_name_str[BT_AUDIO_BROADCAST_NAME_LEN_MAX + sizeof((char)'\0')]; + const uint8_t *data; + int char_cnt; + int ret; + + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + if (broadcast_name == NULL) + { + LOG_DBG("broadcast_name is NULL"); + return -EINVAL; + } + + ret = codec_meta_get_val(meta, meta_len, + BT_AUDIO_METADATA_TYPE_BROADCAST_NAME, + &data); + if (data == NULL) + { + return -ENODATA; + } + + if (!IN_RANGE(ret, BT_AUDIO_BROADCAST_NAME_LEN_MIN, + BT_AUDIO_BROADCAST_NAME_LEN_MAX)) + { + LOG_DBG("Invalid broadcast name len %d", ret); + return -EBADMSG; + } + + /* Since the input is not a NULL-terminated string, we need to copy it to + * add a NULL terminator before we can use utf8_count_chars to verify the + * number of characters + */ + + (void)memcpy(broadcast_name_str, data, ret); + broadcast_name_str[ret] = '\0'; + char_cnt = utf8_count_chars(broadcast_name_str); + if (!IN_RANGE(char_cnt, BT_AUDIO_BROADCAST_NAME_CHAR_MIN, + BT_AUDIO_BROADCAST_NAME_CHAR_MAX)) + { + LOG_DBG("Invalid broadcast name %s", broadcast_name_str); + return -EBADMSG; + } + + *broadcast_name = data; + + return ret; +} + +static int codec_meta_set_broadcast_name( + uint8_t meta[], size_t meta_len, size_t meta_size, + const uint8_t *broadcast_name, size_t broadcast_name_len) +{ + char + broadcast_name_str[BT_AUDIO_BROADCAST_NAME_LEN_MAX + sizeof((char)'\0')]; + int char_cnt; + + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + if (broadcast_name == NULL) + { + LOG_DBG("broadcast_name is NULL"); + return -EINVAL; + } + + if (!IN_RANGE(broadcast_name_len, BT_AUDIO_BROADCAST_NAME_LEN_MIN, + BT_AUDIO_BROADCAST_NAME_LEN_MAX)) + { + LOG_DBG("Invalid broadcast name len %zu", broadcast_name_len); + return -EINVAL; + } + + /* Since the input is not a NULL-terminated string, we need to copy it to + * add a NULL terminator before we can use utf8_count_chars to verify the + * number of characters + */ + + (void)memcpy(broadcast_name_str, broadcast_name, broadcast_name_len); + broadcast_name_str[broadcast_name_len] = '\0'; + char_cnt = utf8_count_chars(broadcast_name_str); + if (!IN_RANGE(char_cnt, BT_AUDIO_BROADCAST_NAME_CHAR_MIN, + BT_AUDIO_BROADCAST_NAME_CHAR_MAX)) + { + LOG_DBG("Invalid broadcast name %s", broadcast_name_str); + return -EINVAL; + } + + return codec_meta_set_val(meta, meta_len, meta_size, + BT_AUDIO_METADATA_TYPE_BROADCAST_NAME, + broadcast_name, broadcast_name_len); +} + +static int codec_meta_get_extended(const uint8_t meta[], size_t meta_len, + const uint8_t **extended_meta) +{ + const uint8_t *data; + int ret; + + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + if (extended_meta == NULL) + { + LOG_DBG("extended_meta is NULL"); + return -EINVAL; + } + + ret = codec_meta_get_val(meta, meta_len, BT_AUDIO_METADATA_TYPE_EXTENDED, + &data); + if (data == NULL) + { + return -ENODATA; + } + + *extended_meta = data; + + return ret; +} + +static int codec_meta_set_extended( + uint8_t meta[], size_t meta_len, size_t meta_size, const uint8_t *extended, + size_t extended_len) +{ + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + if (extended == NULL) + { + LOG_DBG("extended is NULL"); + return -EINVAL; + } + + return codec_meta_set_val(meta, meta_len, meta_size, + BT_AUDIO_METADATA_TYPE_EXTENDED, extended, + extended_len); +} + +static int codec_meta_get_vendor(const uint8_t meta[], + size_t meta_len, + const uint8_t **vendor_meta) +{ + const uint8_t *data; + int ret; + + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + if (vendor_meta == NULL) + { + LOG_DBG("vendor_meta is NULL"); + return -EINVAL; + } + + ret = codec_meta_get_val(meta, meta_len, BT_AUDIO_METADATA_TYPE_VENDOR, + &data); + if (data == NULL) + { + return -ENODATA; + } + + *vendor_meta = data; + + return ret; +} + +static int codec_meta_set_vendor( + uint8_t meta[], size_t meta_len, size_t meta_size, const uint8_t *vendor, + size_t vendor_len) +{ + if (meta == NULL) + { + LOG_DBG("meta is NULL"); + return -EINVAL; + } + + if (vendor == NULL) + { + LOG_DBG("vendor is NULL"); + return -EINVAL; + } + + return codec_meta_set_val(meta, meta_len, meta_size, + BT_AUDIO_METADATA_TYPE_VENDOR, vendor, + vendor_len); +} + +#if CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE > 0 +int bt_audio_codec_cfg_meta_get_val( + const struct bt_audio_codec_cfg *codec_cfg, uint8_t type, + const uint8_t **data) +{ + if (codec_cfg == NULL) + { + LOG_DBG("codec_cfg is NULL"); + return -EINVAL; + } + + return codec_meta_get_val(codec_cfg->meta, + codec_cfg->meta_len, + type, + data); +} + +int bt_audio_codec_cfg_meta_set_val(struct bt_audio_codec_cfg *codec_cfg, + enum bt_audio_metadata_type type, + const uint8_t *data, + size_t data_len) +{ + int ret; + + if (codec_cfg == NULL) + { + LOG_DBG("codec_cfg is NULL"); + return -EINVAL; + } + + ret = codec_meta_set_val(codec_cfg->meta, codec_cfg->meta_len, + ARRAY_SIZE(codec_cfg->meta), type, + data, data_len); + if (ret >= 0) + { + codec_cfg->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cfg_meta_unset_val(struct bt_audio_codec_cfg *codec_cfg, + enum bt_audio_metadata_type type) +{ + int ret; + + if (codec_cfg == NULL) + { + LOG_DBG("codec_cfg is NULL"); + return -EINVAL; + } + + ret = codec_meta_unset_val(codec_cfg->meta, codec_cfg->meta_len, + ARRAY_SIZE(codec_cfg->meta), + type); + if (ret >= 0) + { + codec_cfg->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cfg_meta_get_pref_context( + const struct bt_audio_codec_cfg *codec_cfg, bool fallback_to_default) +{ + int ret; + + if (codec_cfg == NULL) + { + LOG_DBG("codec_cfg is NULL"); + return -EINVAL; + } + + ret = codec_meta_get_pref_context(codec_cfg->meta, codec_cfg->meta_len); + + if (ret == -ENODATA && fallback_to_default && + codec_cfg->id == BT_HCI_CODING_FORMAT_LC3) + { + return BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED; + } + + return ret; +} + +int bt_audio_codec_cfg_meta_set_pref_context( + struct bt_audio_codec_cfg *codec_cfg, enum bt_audio_context ctx) +{ + int ret; + + ret = codec_meta_set_pref_context(codec_cfg->meta, codec_cfg->meta_len, + ARRAY_SIZE(codec_cfg->meta), ctx); + if (ret >= 0) + { + codec_cfg->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cfg_meta_get_stream_context( + const struct bt_audio_codec_cfg *codec_cfg) +{ + if (codec_cfg == NULL) + { + LOG_DBG("codec_cfg is NULL"); + return -EINVAL; + } + + return codec_meta_get_stream_context(codec_cfg->meta, codec_cfg->meta_len); +} + +int bt_audio_codec_cfg_meta_set_stream_context( + struct bt_audio_codec_cfg *codec_cfg, enum bt_audio_context ctx) +{ + int ret; + + ret = codec_meta_set_stream_context(codec_cfg->meta, codec_cfg->meta_len, + ARRAY_SIZE(codec_cfg->meta), ctx); + if (ret >= 0) + { + codec_cfg->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cfg_meta_get_program_info( + const struct bt_audio_codec_cfg *codec_cfg, const uint8_t **program_info) +{ + if (codec_cfg == NULL) + { + LOG_DBG("codec_cfg is NULL"); + return -EINVAL; + } + + return codec_meta_get_program_info(codec_cfg->meta, codec_cfg->meta_len, + program_info); +} + +int bt_audio_codec_cfg_meta_set_program_info( + struct bt_audio_codec_cfg *codec_cfg, const uint8_t *program_info, + size_t program_info_len) +{ + int ret; + + ret = codec_meta_set_program_info(codec_cfg->meta, codec_cfg->meta_len, + ARRAY_SIZE(codec_cfg->meta), + program_info, + program_info_len); + if (ret >= 0) + { + codec_cfg->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cfg_meta_get_lang( + const struct bt_audio_codec_cfg *codec_cfg, const uint8_t **lang) +{ + if (codec_cfg == NULL) + { + LOG_DBG("codec_cfg is NULL"); + return -EINVAL; + } + + return codec_meta_get_lang(codec_cfg->meta, codec_cfg->meta_len, lang); +} + +int bt_audio_codec_cfg_meta_set_lang(struct bt_audio_codec_cfg *codec_cfg, + const uint8_t lang[BT_AUDIO_LANG_SIZE]) +{ + int ret; + + ret = codec_meta_set_lang(codec_cfg->meta, codec_cfg->meta_len, + ARRAY_SIZE(codec_cfg->meta), + lang); + if (ret >= 0) + { + codec_cfg->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cfg_meta_get_ccid_list( + const struct bt_audio_codec_cfg *codec_cfg, const uint8_t **ccid_list) +{ + if (codec_cfg == NULL) + { + LOG_DBG("codec_cfg is NULL"); + return -EINVAL; + } + + return codec_meta_get_ccid_list(codec_cfg->meta, codec_cfg->meta_len, + ccid_list); +} + +int bt_audio_codec_cfg_meta_set_ccid_list( + struct bt_audio_codec_cfg *codec_cfg, const uint8_t *ccid_list, + size_t ccid_list_len) +{ + int ret; + + ret = codec_meta_set_ccid_list(codec_cfg->meta, codec_cfg->meta_len, + ARRAY_SIZE(codec_cfg->meta), + ccid_list, ccid_list_len); + if (ret >= 0) + { + codec_cfg->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cfg_meta_get_parental_rating( + const struct bt_audio_codec_cfg *codec_cfg) +{ + if (codec_cfg == NULL) + { + LOG_DBG("codec_cfg is NULL"); + return -EINVAL; + } + + return codec_meta_get_parental_rating(codec_cfg->meta, + codec_cfg->meta_len); +} + +int bt_audio_codec_cfg_meta_set_parental_rating( + struct bt_audio_codec_cfg *codec_cfg, + enum bt_audio_parental_rating parental_rating) +{ + int ret; + + ret = codec_meta_set_parental_rating(codec_cfg->meta, codec_cfg->meta_len, + ARRAY_SIZE(codec_cfg->meta), + parental_rating); + if (ret >= 0) + { + codec_cfg->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cfg_meta_get_program_info_uri( + const struct bt_audio_codec_cfg *codec_cfg, + const uint8_t **program_info_uri) +{ + if (codec_cfg == NULL) + { + LOG_DBG("codec_cfg is NULL"); + return -EINVAL; + } + + return codec_meta_get_program_info_uri(codec_cfg->meta, + codec_cfg->meta_len, + + program_info_uri); +} + +int bt_audio_codec_cfg_meta_set_program_info_uri( + struct bt_audio_codec_cfg *codec_cfg, const uint8_t *program_info_uri, + size_t program_info_uri_len) +{ + int ret; + + ret = codec_meta_set_program_info_uri(codec_cfg->meta, codec_cfg->meta_len, + ARRAY_SIZE(codec_cfg->meta), + program_info_uri, + program_info_uri_len); + if (ret >= 0) + { + codec_cfg->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cfg_meta_get_audio_active_state( + const struct bt_audio_codec_cfg *codec_cfg) +{ + if (codec_cfg == NULL) + { + LOG_DBG("codec_cfg is NULL"); + return -EINVAL; + } + + return codec_meta_get_audio_active_state(codec_cfg->meta, + codec_cfg->meta_len); +} + +int bt_audio_codec_cfg_meta_set_audio_active_state( + struct bt_audio_codec_cfg *codec_cfg, enum bt_audio_active_state state) +{ + int ret; + + ret = codec_meta_set_audio_active_state(codec_cfg->meta, + codec_cfg->meta_len, + ARRAY_SIZE(codec_cfg->meta), + state); + if (ret >= 0) + { + codec_cfg->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cfg_meta_get_bcast_audio_immediate_rend_flag( + const struct bt_audio_codec_cfg *codec_cfg) +{ + if (codec_cfg == NULL) + { + LOG_DBG("codec_cfg is NULL"); + return -EINVAL; + } + + return codec_meta_get_bcast_audio_immediate_rend_flag(codec_cfg->meta, + codec_cfg->meta_len); +} + +int bt_audio_codec_cfg_meta_set_bcast_audio_immediate_rend_flag( + struct bt_audio_codec_cfg *codec_cfg) +{ + int ret; + + ret = codec_meta_set_bcast_audio_immediate_rend_flag(codec_cfg->meta, + codec_cfg->meta_len, + ARRAY_SIZE( + codec_cfg->meta)); + if (ret >= 0) + { + codec_cfg->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cfg_meta_get_assisted_listening_stream( + const struct bt_audio_codec_cfg *codec_cfg) +{ + if (codec_cfg == NULL) + { + LOG_DBG("codec_cfg is NULL"); + return -EINVAL; + } + + return codec_meta_get_assisted_listening_stream(codec_cfg->meta, + codec_cfg->meta_len); +} + +int bt_audio_codec_cfg_meta_set_assisted_listening_stream( + struct bt_audio_codec_cfg *codec_cfg, + enum bt_audio_assisted_listening_stream val) +{ + int ret; + + ret = codec_meta_set_assisted_listening_stream(codec_cfg->meta, + codec_cfg->meta_len, + ARRAY_SIZE(codec_cfg->meta), + val); + if (ret >= 0) + { + codec_cfg->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cfg_meta_get_extended( + const struct bt_audio_codec_cfg *codec_cfg, const uint8_t **extended_meta) +{ + if (codec_cfg == NULL) + { + LOG_DBG("codec_cfg is NULL"); + return -EINVAL; + } + + return codec_meta_get_extended(codec_cfg->meta, codec_cfg->meta_len, + extended_meta); +} + +int bt_audio_codec_cfg_meta_set_extended( + struct bt_audio_codec_cfg *codec_cfg, const uint8_t *extended_meta, + size_t extended_meta_len) +{ + int ret; + + ret = codec_meta_set_extended(codec_cfg->meta, codec_cfg->meta_len, + ARRAY_SIZE(codec_cfg->meta), + extended_meta, extended_meta_len); + if (ret >= 0) + { + codec_cfg->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cfg_meta_get_vendor( + const struct bt_audio_codec_cfg *codec_cfg, const uint8_t **vendor_meta) +{ + if (codec_cfg == NULL) + { + LOG_DBG("codec_cfg is NULL"); + return -EINVAL; + } + + return codec_meta_get_vendor(codec_cfg->meta, codec_cfg->meta_len, + vendor_meta); +} + +int bt_audio_codec_cfg_meta_set_vendor(struct bt_audio_codec_cfg *codec_cfg, + const uint8_t *vendor_meta, + size_t vendor_meta_len) +{ + int ret; + + ret = codec_meta_set_vendor(codec_cfg->meta, codec_cfg->meta_len, + ARRAY_SIZE(codec_cfg->meta), + vendor_meta, vendor_meta_len); + if (ret >= 0) + { + codec_cfg->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cfg_meta_get_broadcast_name( + const struct bt_audio_codec_cfg *codec_cfg, const uint8_t **broadcast_name) +{ + if (codec_cfg == NULL) + { + LOG_DBG("codec_cfg is NULL"); + return -EINVAL; + } + + return codec_meta_get_broadcast_name(codec_cfg->meta, codec_cfg->meta_len, + broadcast_name); +} + +int bt_audio_codec_cfg_meta_set_broadcast_name( + struct bt_audio_codec_cfg *codec_cfg, const uint8_t *broadcast_name, + size_t broadcast_name_len) +{ + int ret; + + ret = codec_meta_set_broadcast_name(codec_cfg->meta, codec_cfg->meta_len, + ARRAY_SIZE(codec_cfg->meta), + broadcast_name, + broadcast_name_len); + if (ret >= 0) + { + codec_cfg->meta_len = ret; + } + + return ret; +} +#endif /* CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE > 0 */ + +#if CONFIG_BT_AUDIO_CODEC_CAP_MAX_METADATA_SIZE > 0 +int bt_audio_codec_cap_meta_get_val( + const struct bt_audio_codec_cap *codec_cap, uint8_t type, + const uint8_t **data) +{ + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + return codec_meta_get_val(codec_cap->meta, + codec_cap->meta_len, + type, + data); +} + +int bt_audio_codec_cap_meta_set_val(struct bt_audio_codec_cap *codec_cap, + enum bt_audio_metadata_type type, + const uint8_t *data, + size_t data_len) +{ + int ret; + + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + ret = codec_meta_set_val(codec_cap->meta, codec_cap->meta_len, + ARRAY_SIZE(codec_cap->meta), type, + data, data_len); + if (ret >= 0) + { + codec_cap->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cap_meta_unset_val(struct bt_audio_codec_cap *codec_cap, + enum bt_audio_metadata_type type) +{ + int ret; + + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + ret = codec_meta_unset_val(codec_cap->meta, codec_cap->meta_len, + ARRAY_SIZE(codec_cap->meta), + type); + if (ret >= 0) + { + codec_cap->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cap_meta_get_pref_context( + const struct bt_audio_codec_cap *codec_cap) +{ + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + return codec_meta_get_pref_context(codec_cap->meta, codec_cap->meta_len); +} + +int bt_audio_codec_cap_meta_set_pref_context( + struct bt_audio_codec_cap *codec_cap, enum bt_audio_context ctx) +{ + int ret; + + ret = codec_meta_set_pref_context(codec_cap->meta, codec_cap->meta_len, + ARRAY_SIZE(codec_cap->meta), ctx); + if (ret >= 0) + { + codec_cap->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cap_meta_get_stream_context( + const struct bt_audio_codec_cap *codec_cap) +{ + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + return codec_meta_get_stream_context(codec_cap->meta, codec_cap->meta_len); +} + +int bt_audio_codec_cap_meta_set_stream_context( + struct bt_audio_codec_cap *codec_cap, enum bt_audio_context ctx) +{ + int ret; + + ret = codec_meta_set_stream_context(codec_cap->meta, codec_cap->meta_len, + ARRAY_SIZE(codec_cap->meta), ctx); + if (ret >= 0) + { + codec_cap->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cap_meta_get_program_info( + const struct bt_audio_codec_cap *codec_cap, const uint8_t **program_info) +{ + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + return codec_meta_get_program_info(codec_cap->meta, codec_cap->meta_len, + program_info); +} + +int bt_audio_codec_cap_meta_set_program_info( + struct bt_audio_codec_cap *codec_cap, const uint8_t *program_info, + size_t program_info_len) +{ + int ret; + + ret = codec_meta_set_program_info(codec_cap->meta, codec_cap->meta_len, + ARRAY_SIZE(codec_cap->meta), + program_info, + program_info_len); + if (ret >= 0) + { + codec_cap->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cap_meta_get_lang( + const struct bt_audio_codec_cap *codec_cap, const uint8_t **lang) +{ + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + return codec_meta_get_lang(codec_cap->meta, codec_cap->meta_len, lang); +} + +int bt_audio_codec_cap_meta_set_lang(struct bt_audio_codec_cap *codec_cap, + const uint8_t lang[BT_AUDIO_LANG_SIZE]) +{ + int ret; + + ret = codec_meta_set_lang(codec_cap->meta, codec_cap->meta_len, + ARRAY_SIZE(codec_cap->meta), + lang); + if (ret >= 0) + { + codec_cap->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cap_meta_get_ccid_list( + const struct bt_audio_codec_cap *codec_cap, const uint8_t **ccid_list) +{ + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + return codec_meta_get_ccid_list(codec_cap->meta, codec_cap->meta_len, + ccid_list); +} + +int bt_audio_codec_cap_meta_set_ccid_list( + struct bt_audio_codec_cap *codec_cap, const uint8_t *ccid_list, + size_t ccid_list_len) +{ + int ret; + + ret = codec_meta_set_ccid_list(codec_cap->meta, codec_cap->meta_len, + ARRAY_SIZE(codec_cap->meta), + ccid_list, ccid_list_len); + if (ret >= 0) + { + codec_cap->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cap_meta_get_parental_rating( + const struct bt_audio_codec_cap *codec_cap) +{ + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + return codec_meta_get_parental_rating(codec_cap->meta, + codec_cap->meta_len); +} + +int bt_audio_codec_cap_meta_set_parental_rating( + struct bt_audio_codec_cap *codec_cap, + enum bt_audio_parental_rating parental_rating) +{ + int ret; + + ret = codec_meta_set_parental_rating(codec_cap->meta, codec_cap->meta_len, + ARRAY_SIZE(codec_cap->meta), + parental_rating); + if (ret >= 0) + { + codec_cap->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cap_meta_get_program_info_uri( + const struct bt_audio_codec_cap *codec_cap, + const uint8_t **program_info_uri) +{ + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + return codec_meta_get_program_info_uri(codec_cap->meta, + codec_cap->meta_len, + + program_info_uri); +} + +int bt_audio_codec_cap_meta_set_program_info_uri( + struct bt_audio_codec_cap *codec_cap, const uint8_t *program_info_uri, + size_t program_info_uri_len) +{ + int ret; + + ret = codec_meta_set_program_info_uri(codec_cap->meta, codec_cap->meta_len, + ARRAY_SIZE(codec_cap->meta), + program_info_uri, + program_info_uri_len); + if (ret >= 0) + { + codec_cap->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cap_meta_get_audio_active_state( + const struct bt_audio_codec_cap *codec_cap) +{ + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + return codec_meta_get_audio_active_state(codec_cap->meta, + codec_cap->meta_len); +} + +int bt_audio_codec_cap_meta_set_audio_active_state( + struct bt_audio_codec_cap *codec_cap, enum bt_audio_active_state state) +{ + int ret; + + ret = codec_meta_set_audio_active_state(codec_cap->meta, + codec_cap->meta_len, + ARRAY_SIZE(codec_cap->meta), + state); + if (ret >= 0) + { + codec_cap->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cap_meta_get_bcast_audio_immediate_rend_flag( + const struct bt_audio_codec_cap *codec_cap) +{ + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + return codec_meta_get_bcast_audio_immediate_rend_flag(codec_cap->meta, + codec_cap->meta_len); +} + +int bt_audio_codec_cap_meta_set_bcast_audio_immediate_rend_flag( + struct bt_audio_codec_cap *codec_cap) +{ + int ret; + + ret = codec_meta_set_bcast_audio_immediate_rend_flag(codec_cap->meta, + codec_cap->meta_len, + ARRAY_SIZE( + codec_cap->meta)); + if (ret >= 0) + { + codec_cap->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cap_meta_get_assisted_listening_stream( + const struct bt_audio_codec_cap *codec_cap) +{ + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + return codec_meta_get_assisted_listening_stream(codec_cap->meta, + codec_cap->meta_len); +} + +int bt_audio_codec_cap_meta_set_assisted_listening_stream( + struct bt_audio_codec_cap *codec_cap, + enum bt_audio_assisted_listening_stream val) +{ + int ret; + + ret = codec_meta_set_assisted_listening_stream(codec_cap->meta, + codec_cap->meta_len, + ARRAY_SIZE(codec_cap->meta), + val); + if (ret >= 0) + { + codec_cap->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cap_meta_get_extended( + const struct bt_audio_codec_cap *codec_cap, const uint8_t **extended_meta) +{ + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + return codec_meta_get_extended(codec_cap->meta, codec_cap->meta_len, + extended_meta); +} + +int bt_audio_codec_cap_meta_set_extended( + struct bt_audio_codec_cap *codec_cap, const uint8_t *extended_meta, + size_t extended_meta_len) +{ + int ret; + + ret = codec_meta_set_extended(codec_cap->meta, codec_cap->meta_len, + ARRAY_SIZE(codec_cap->meta), + extended_meta, extended_meta_len); + if (ret >= 0) + { + codec_cap->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cap_meta_get_vendor( + const struct bt_audio_codec_cap *codec_cap, const uint8_t **vendor_meta) +{ + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + return codec_meta_get_vendor(codec_cap->meta, codec_cap->meta_len, + vendor_meta); +} + +int bt_audio_codec_cap_meta_set_vendor(struct bt_audio_codec_cap *codec_cap, + const uint8_t *vendor_meta, + size_t vendor_meta_len) +{ + int ret; + + ret = codec_meta_set_vendor(codec_cap->meta, codec_cap->meta_len, + ARRAY_SIZE(codec_cap->meta), + vendor_meta, vendor_meta_len); + if (ret >= 0) + { + codec_cap->meta_len = ret; + } + + return ret; +} + +int bt_audio_codec_cap_meta_get_broadcast_name( + const struct bt_audio_codec_cap *codec_cap, const uint8_t **broadcast_name) +{ + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + return codec_meta_get_broadcast_name(codec_cap->meta, codec_cap->meta_len, + broadcast_name); +} + +int bt_audio_codec_cap_meta_set_broadcast_name( + struct bt_audio_codec_cap *codec_cap, const uint8_t *broadcast_name, + size_t broadcast_name_len) +{ + int ret; + + ret = codec_meta_set_broadcast_name(codec_cap->meta, codec_cap->meta_len, + ARRAY_SIZE(codec_cap->meta), + broadcast_name, + broadcast_name_len); + if (ret >= 0) + { + codec_cap->meta_len = ret; + } + + return ret; +} +#endif /* CONFIG_BT_AUDIO_CODEC_CAP_MAX_METADATA_SIZE > 0 */ +#endif /* CONFIG_BT_AUDIO_CODEC_CFG_MAX_METADATA_SIZE > 0 || \ + + * CONFIG_BT_AUDIO_CODEC_CAP_MAX_METADATA_SIZE > 0 \ + */ + +#if CONFIG_BT_AUDIO_CODEC_CAP_MAX_DATA_SIZE > 0 + +static void init_net_buf_simple_from_codec_cap(struct net_buf_simple *buf, + struct bt_audio_codec_cap *codec_cap) +{ + buf->__buf = codec_cap->data; + buf->data = codec_cap->data; + buf->size = sizeof(codec_cap->data); + buf->len = codec_cap->data_len; +} + +int bt_audio_codec_cap_get_val(const struct bt_audio_codec_cap *codec_cap, + enum bt_audio_codec_cap_type type, + const uint8_t **data) +{ + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + if (data == NULL) + { + LOG_DBG("data is NULL"); + return -EINVAL; + } + + return bt_audio_data_get_val(codec_cap->data, codec_cap->data_len, + (uint8_t)type, data); +} + +int bt_audio_codec_cap_set_val(struct bt_audio_codec_cap *codec_cap, + enum bt_audio_codec_cap_type type, + const uint8_t *data, + size_t data_len) +{ + struct net_buf_simple buf; + int ret; + + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + if (data == NULL) + { + LOG_DBG("data is NULL"); + return -EINVAL; + } + + if (data_len == 0U || data_len > UINT8_MAX) + { + LOG_DBG("Invalid data_len %zu", data_len); + return -EINVAL; + } + + init_net_buf_simple_from_codec_cap(&buf, codec_cap); + + ret = ltv_set_val(&buf, type, data, data_len); + if (ret >= 0) + { + codec_cap->data_len = ret; + } + + return ret; +} + +int bt_audio_codec_cap_unset_val(struct bt_audio_codec_cap *codec_cap, + enum bt_audio_codec_cap_type type) +{ + struct net_buf_simple buf; + int ret; + + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + init_net_buf_simple_from_codec_cap(&buf, codec_cap); + + ret = ltv_unset_val(&buf, type); + if (ret >= 0) + { + codec_cap->data_len = ret; + } + + return ret; +} + +int bt_audio_codec_cap_get_freq(const struct bt_audio_codec_cap *codec_cap) +{ + const uint8_t *data; + uint8_t data_len; + + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + data_len = bt_audio_codec_cap_get_val(codec_cap, + BT_AUDIO_CODEC_CAP_TYPE_FREQ, &data); + if (data == NULL) + { + return -ENODATA; + } + + if (data_len != sizeof(uint16_t)) + { + return -EBADMSG; + } + + return sys_get_le16(data); +} + +int bt_audio_codec_cap_set_freq(struct bt_audio_codec_cap *codec_cap, + enum bt_audio_codec_cap_freq freq) +{ + uint16_t freq_le16; + + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + if ((freq & BT_AUDIO_CODEC_CAP_FREQ_ANY) != freq) + { + LOG_DBG("Invalid freq value: %d", freq); + return -EINVAL; + } + + freq_le16 = sys_cpu_to_le16((uint16_t)freq); + + return bt_audio_codec_cap_set_val(codec_cap, BT_AUDIO_CODEC_CAP_TYPE_FREQ, + (uint8_t *)&freq_le16, + sizeof(freq_le16)); +} + +int bt_audio_codec_cap_get_frame_dur( + const struct bt_audio_codec_cap *codec_cap) +{ + const uint8_t *data; + uint8_t data_len; + + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + data_len = bt_audio_codec_cap_get_val(codec_cap, + BT_AUDIO_CODEC_CAP_TYPE_DURATION, + &data); + if (data == NULL) + { + return -ENODATA; + } + + if (data_len != sizeof(uint8_t)) + { + return -EBADMSG; + } + + return data[0]; +} + +int bt_audio_codec_cap_set_frame_dur(struct bt_audio_codec_cap *codec_cap, + enum bt_audio_codec_cap_frame_dur frame_dur) +{ + uint8_t frame_dur_u8; + + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + if ((frame_dur & BT_AUDIO_CODEC_CAP_DURATION_ANY) == 0) + { + LOG_DBG("Invalid frame_dur value: %d", frame_dur); + return -EINVAL; + } + + if ((frame_dur & BT_AUDIO_CODEC_CAP_DURATION_PREFER_7_5) != 0) + { + if ((frame_dur & BT_AUDIO_CODEC_CAP_DURATION_PREFER_10) != 0) + { + LOG_DBG("Cannot prefer both 7.5 and 10ms: %d", frame_dur); + return -EINVAL; + } + + if ((frame_dur & BT_AUDIO_CODEC_CAP_DURATION_7_5) == 0) + { + LOG_DBG("Cannot prefer 7.5ms when not supported: %d", frame_dur); + return -EINVAL; + } + } + + if ((frame_dur & BT_AUDIO_CODEC_CAP_DURATION_PREFER_10) != 0 && + (frame_dur & BT_AUDIO_CODEC_CAP_DURATION_10) == 0) + { + LOG_DBG("Cannot prefer 10ms when not supported: %d", frame_dur); + return -EINVAL; + } + + frame_dur_u8 = (uint8_t)frame_dur; + + return bt_audio_codec_cap_set_val(codec_cap, + BT_AUDIO_CODEC_CAP_TYPE_DURATION, + &frame_dur_u8, + sizeof(frame_dur_u8)); +} + +int bt_audio_codec_cap_get_supported_audio_chan_counts( + const struct bt_audio_codec_cap *codec_cap, bool fallback_to_default) +{ + const uint8_t *data; + uint8_t data_len; + + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + data_len = bt_audio_codec_cap_get_val(codec_cap, + BT_AUDIO_CODEC_CAP_TYPE_CHAN_COUNT, + &data); + if (data == NULL) + { + if (fallback_to_default && codec_cap->id == BT_HCI_CODING_FORMAT_LC3) + { + return 1; + } + + return -ENODATA; + } + + if (data_len != sizeof(uint8_t)) + { + return -EBADMSG; + } + + return data[0]; +} + +int bt_audio_codec_cap_set_supported_audio_chan_counts( + struct bt_audio_codec_cap *codec_cap, + enum bt_audio_codec_cap_chan_count chan_count) +{ + uint8_t chan_count_u8; + + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + if ((chan_count & BT_AUDIO_CODEC_CAP_CHAN_COUNT_ANY) != chan_count) + { + LOG_DBG("Invalid chan_count value: %d", chan_count); + return -EINVAL; + } + + chan_count_u8 = (uint8_t)chan_count; + + return bt_audio_codec_cap_set_val(codec_cap, + BT_AUDIO_CODEC_CAP_TYPE_CHAN_COUNT, + &chan_count_u8, + sizeof(chan_count_u8)); +} + +int bt_audio_codec_cap_get_octets_per_frame( + const struct bt_audio_codec_cap *codec_cap, + struct bt_audio_codec_octets_per_codec_frame *codec_frame) +{ + const uint8_t *data; + uint8_t data_len; + + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + if (codec_frame == NULL) + { + LOG_DBG("codec_frame is NULL"); + return -EINVAL; + } + + data_len = bt_audio_codec_cap_get_val(codec_cap, + BT_AUDIO_CODEC_CAP_TYPE_FRAME_LEN, + &data); + if (data == NULL) + { + return -ENODATA; + } + + if (data_len != sizeof(uint32_t)) + { + return -EBADMSG; + } + + codec_frame->min = sys_get_le16(data); + codec_frame->max = sys_get_le16(data + sizeof(codec_frame->min)); + + return 0; +} + +int bt_audio_codec_cap_set_octets_per_frame( + struct bt_audio_codec_cap *codec_cap, + const struct bt_audio_codec_octets_per_codec_frame *codec_frame) +{ + uint8_t codec_frame_le32[4]; + + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + if (codec_frame == NULL) + { + LOG_DBG("codec_frame is NULL"); + return -EINVAL; + } + + if (codec_frame->min > codec_frame->max) + { + LOG_DBG("Invalid codec_frame values: %u/%u", codec_frame->min, + codec_frame->max); + return -EINVAL; + } + + sys_put_le16(codec_frame->min, codec_frame_le32); + sys_put_le16(codec_frame->max, codec_frame_le32 + 2); + + return bt_audio_codec_cap_set_val(codec_cap, + BT_AUDIO_CODEC_CAP_TYPE_FRAME_LEN, + codec_frame_le32, + sizeof(codec_frame_le32)); +} + +int bt_audio_codec_cap_get_max_codec_frames_per_sdu( + const struct bt_audio_codec_cap *codec_cap, bool fallback_to_default) +{ + const uint8_t *data; + uint8_t data_len; + + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + data_len = bt_audio_codec_cap_get_val(codec_cap, + BT_AUDIO_CODEC_CAP_TYPE_FRAME_COUNT, + &data); + if (data == NULL) + { + if (fallback_to_default && codec_cap->id == BT_HCI_CODING_FORMAT_LC3) + { + return 1; + } + + return -ENODATA; + } + + if (data_len != sizeof(uint8_t)) + { + return -EBADMSG; + } + + return data[0]; +} + +int bt_audio_codec_cap_set_max_codec_frames_per_sdu( + struct bt_audio_codec_cap *codec_cap, uint8_t codec_frames_per_sdu) +{ + if (codec_cap == NULL) + { + LOG_DBG("codec_cap is NULL"); + return -EINVAL; + } + + return bt_audio_codec_cap_set_val(codec_cap, + BT_AUDIO_CODEC_CAP_TYPE_FRAME_COUNT, + &codec_frames_per_sdu, + sizeof(codec_frames_per_sdu)); +} + +#endif /* CONFIG_BT_AUDIO_CODEC_CAP_MAX_DATA_SIZE > 0 */ diff --git a/libs/zblue/audio/csip_crypto.c b/libs/zblue/audio/csip_crypto.c new file mode 100644 index 0000000000000..5868ad334e4c0 --- /dev/null +++ b/libs/zblue/audio/csip_crypto.c @@ -0,0 +1,246 @@ +/**************************************************************************** + * libs/zblue/audio/csip_crypto.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Bose Corporation + * Copyright (c) 2021-2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + * + * The static functions in this file operate on Big Endian (BE) as the + * underlying encryption library is BE as well. Furthermore, the sample data + * in the CSIS spec is also provided as BE, and logging values as BE will + * make it easier to compare. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "crypto/bt_crypto.h" + +#include "common/bt_str.h" + +#include "csip_crypto.h" + +LOG_MODULE_REGISTER(bt_csip_crypto, + CONFIG_BT_CSIP_SET_MEMBER_CRYPTO_LOG_LEVEL); + +#define BT_CSIP_CRYPTO_PADDING_SIZE 13U +#define BT_CSIP_PADDED_RAND_SIZE (BT_CSIP_CRYPTO_PADDING_SIZE + BT_CSIP_CRYPTO_PRAND_SIZE) + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +#define BT_CSIP_R_MASK BIT_MASK(24) /* r is 24 bit / 3 octet */ + +int bt_csip_sih(const uint8_t sirk[BT_CSIP_SIRK_SIZE], + uint8_t r[BT_CSIP_CRYPTO_PRAND_SIZE], + uint8_t out[BT_CSIP_CRYPTO_HASH_SIZE]) +{ + uint8_t res[BT_CSIP_PADDED_RAND_SIZE]; /* need to store 128 bit */ + int err; + + if ((r[BT_CSIP_CRYPTO_PRAND_SIZE - 1] & BIT(7U)) || + ((r[BT_CSIP_CRYPTO_PRAND_SIZE - 1] & BIT(6U)) == 0)) + { + LOG_DBG("Invalid r %s", bt_hex(r, BT_CSIP_CRYPTO_PRAND_SIZE)); + } + + LOG_DBG("SIRK %s", bt_hex(sirk, BT_CSIP_SIRK_SIZE)); + LOG_DBG("r %s", bt_hex(r, BT_CSIP_CRYPTO_PRAND_SIZE)); + + /* r' = padding || r */ + + (void)memset(res + BT_CSIP_CRYPTO_PRAND_SIZE, 0, + BT_CSIP_CRYPTO_PADDING_SIZE); + memcpy(res, r, BT_CSIP_CRYPTO_PRAND_SIZE); + + LOG_DBG("r' %s", bt_hex(res, sizeof(res))); + + err = bt_encrypt_le(sirk, res, res); + + if (err != 0) + { + return err; + } + + /* The output of the function sih is: + * sih(k, r) = e(k, r') mod 2^24 The output of the security function e is + * then truncated to 24 bits by taking the least significant 24 bits of + * the output of e as the result of sih. + */ + + LOG_DBG("res %s", bt_hex(res, sizeof(res))); + + /* Result is the lowest 3 bytes */ + + memcpy(out, res, BT_CSIP_CRYPTO_HASH_SIZE); + + LOG_DBG("sih %s", bt_hex(out, BT_CSIP_CRYPTO_HASH_SIZE)); + + return 0; +} + +/** + * @brief k1 derivation function + * + * The key derivation function k1 is used to derive a key. The derived key is + * used to encrypt and decrypt the value of the Set Identity Resolving Key + * characteristic. + * + * @param n n is 0 or more bytes. + * @param n_size Number of bytes in @p n. + * @param salt A 16-byte salt. + * @param p p is 0 or more bytes. + * @param p_size Number of bytes in @p p. + * @param out A 16-byte output buffer. + * @return int 0 on success, any other value indicates a failure. + */ + +static int k1(const uint8_t *n, + size_t n_size, const uint8_t salt[BT_CSIP_CRYPTO_SALT_SIZE], + const uint8_t *p, size_t p_size, uint8_t out[16]) +{ + /* TODO: This is basically a duplicate of bt_mesh_k1 - Perhaps they can + * be merged + */ + + uint8_t t[16]; + int err; + + /* T = AES_CMAC_SALT(N) + * + * k1(N, SALT, P) = AES-CMAC_T(P) + */ + + LOG_DBG("BE: n %s", bt_hex(n, n_size)); + LOG_DBG("BE: salt %s", bt_hex(salt, BT_CSIP_CRYPTO_SALT_SIZE)); + LOG_DBG("BE: p %s", bt_hex(p, p_size)); + + err = bt_crypto_aes_cmac(salt, n, n_size, t); + + LOG_DBG("BE: t %s", bt_hex(t, sizeof(t))); + + if (err != 0) + { + return err; + } + + err = bt_crypto_aes_cmac(t, p, p_size, out); + + LOG_DBG("BE: out %s", bt_hex(out, 16)); + + return err; +} + +/** + * @brief s1 SALT generation function + * + * @param m A non-zero length octet array or ASCII encoded string + * @param m_size Size of @p m. + * @param out 16-byte output buffer. + * @return int 0 on success, any other value indicates a failure. + */ + +static int s1(const uint8_t *m, + size_t m_size, uint8_t out[BT_CSIP_CRYPTO_SALT_SIZE]) +{ + uint8_t zero[16]; + int err; + + /* s1(M) = AES-CMAC_zero(M) */ + + LOG_DBG("BE: m %s", bt_hex(m, m_size)); + + memset(zero, 0, sizeof(zero)); + + err = bt_crypto_aes_cmac(zero, m, m_size, out); + + LOG_DBG("BE: out %s", bt_hex(out, 16)); + + return err; +} + +int bt_csip_sef(const uint8_t k[BT_CSIP_CRYPTO_KEY_SIZE], + const uint8_t sirk[BT_CSIP_SIRK_SIZE], + uint8_t out_sirk[BT_CSIP_SIRK_SIZE]) +{ + const uint8_t m[] = + { + 'S', 'I', 'R', 'K', 'e', 'n', 'c' + }; + + const uint8_t p[] = + { + 'c', 's', 'i', 's' + }; + + uint8_t s1_out[BT_CSIP_CRYPTO_SALT_SIZE]; + uint8_t k1_out[BT_CSIP_CRYPTO_KEY_SIZE]; + uint8_t k1_tmp[BT_CSIP_CRYPTO_KEY_SIZE]; + int err; + + /* sef(K, SIRK) = k1(K, s1("SIRKenc"), "csis") ^ SIRK */ + + LOG_DBG("SIRK %s", bt_hex(sirk, BT_CSIP_SIRK_SIZE)); + + /* Swap because aes_cmac is big endian and k is little endian */ + + sys_memcpy_swap(k1_tmp, k, sizeof(k1_tmp)); + LOG_DBG("BE: k %s", bt_hex(k1_tmp, sizeof(k1_tmp))); + + err = s1(m, sizeof(m), s1_out); + if (err != 0) + { + return err; + } + + LOG_DBG("BE: s1 result %s", bt_hex(s1_out, sizeof(s1_out))); + + err = k1(k1_tmp, sizeof(k1_tmp), s1_out, p, sizeof(p), k1_out); + if (err != 0) + { + return err; + } + + LOG_DBG("BE: k1 result %s", bt_hex(k1_out, sizeof(k1_out))); + + /* Get result back to little endian. */ + + sys_mem_swap(k1_out, sizeof(k1_out)); + + mem_xor_128(out_sirk, k1_out, sirk); + LOG_DBG("out %s", bt_hex(out_sirk, BT_CSIP_SIRK_SIZE)); + + return 0; +} + +int bt_csip_sdf(const uint8_t k[BT_CSIP_CRYPTO_KEY_SIZE], + const uint8_t enc_sirk[BT_CSIP_SIRK_SIZE], + uint8_t out_sirk[BT_CSIP_SIRK_SIZE]) +{ + /* SIRK encryption is currently symmetric, which means that we can + * simply apply the sef function to decrypt it. + */ + + /* sdf(K, EncSIRK) = k1(K, s1("SIRKenc"), "csis") ^ EncSIRK */ + + LOG_DBG("Running SDF as SEF"); + return bt_csip_sef(k, enc_sirk, out_sirk); +} diff --git a/libs/zblue/audio/csip_crypto.h b/libs/zblue/audio/csip_crypto.h new file mode 100644 index 0000000000000..fa98d9e4c0050 --- /dev/null +++ b/libs/zblue/audio/csip_crypto.h @@ -0,0 +1,95 @@ +/**************************************************************************** + * libs/zblue/audio/csip_crypto.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2019 Bose Corporation + * Copyright (c) 2021-2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include + +#define BT_CSIP_CRYPTO_KEY_SIZE 16U +#define BT_CSIP_CRYPTO_SALT_SIZE 16U +#define BT_CSIP_CRYPTO_PRAND_SIZE 3U +#define BT_CSIP_CRYPTO_HASH_SIZE 3U + +/** + * @brief Private Set Unique identifier hash function sih. + * + * The RSI hash function sih is used to generate a hash value that is used in + * RSIs - Used by the Coordinated Set Identification service and profile. + * + * @param sirk 16-byte SIRK in little-endian byte order. + * @param r 3-byte random value in little-endian byte order. + * @param out 3-byte output buffer in little-endian byte order. + * + * @return int 0 on success, any other value indicates a failure. + */ + +int bt_csip_sih(const uint8_t sirk[BT_CSIP_SIRK_SIZE], + uint8_t r[BT_CSIP_CRYPTO_PRAND_SIZE], + uint8_t out[BT_CSIP_CRYPTO_HASH_SIZE]); + +/** + * @brief SIRK encryption function sef + * + * The SIRK encryption function sef is used by the server to encrypt the SIRK + * with a key K. The value of K depends on the transport on which the pairing + * between the client and the server was performed. + * + * If the pairing was performed on Basic Rate/Enhanced Data Rate (BR/EDR), K + * is equal to the Link Key shared by the server and the client. K = Link + * Key. + * + * If the pairing was performed on Bluetooth Low Energy (LE), K is equal to + * the LTK shared by the server and client. That is, K = LTK + * + * @param k 16-byte key in little-endian byte order. + * @param sirk 16-byte unencrypted SIRK key in little-endian byte order. + * @param out_sirk 16-byte encrypted SIRK key in little-endian byte order. + * + * @return int 0 on success, any other value indicates a failure. + */ + +int bt_csip_sef(const uint8_t k[BT_CSIP_CRYPTO_KEY_SIZE], + const uint8_t sirk[BT_CSIP_SIRK_SIZE], + uint8_t out_sirk[BT_CSIP_SIRK_SIZE]); + +/** + * @brief SIRK decryption function sdf + * + * The SIRK decryption function sdf is used by the client to decrypt the SIRK + * with a key K. The value of K depends on the transport on which the pairing + * between the client and the server was performed. + * + * If the pairing was performed on Basic Rate/Enhanced Data Rate (BR/EDR), K + * is equal to the Link Key shared by the server and the client. K = Link + * Key. + * + * If the pairing was performed on Bluetooth Low Energy (LE), K is equal to + * the LTK shared by the server and client. That is, K = LTK + * + * @param k 16-byte key in little-endian byte order. + * @param sirk 16-byte encrypted SIRK in little-endian byte order. + * @param out_sirk 16-byte decrypted SIRK in little-endian byte order. + * + * @return int 0 on success, any other value indicates a failure. + */ + +int bt_csip_sdf(const uint8_t k[BT_CSIP_CRYPTO_KEY_SIZE], + const uint8_t enc_sirk[BT_CSIP_SIRK_SIZE], + uint8_t out_sirk[BT_CSIP_SIRK_SIZE]); diff --git a/libs/zblue/audio/csip_internal.h b/libs/zblue/audio/csip_internal.h new file mode 100644 index 0000000000000..5668875c4b372 --- /dev/null +++ b/libs/zblue/audio/csip_internal.h @@ -0,0 +1,71 @@ +/**************************************************************************** + * libs/zblue/audio/csip_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Internal APIs for Bluetooth CSIP + * + * Copyright (c) 2021-2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#define BT_CSIP_SIRK_TYPE_ENCRYPTED 0x00U +#define BT_CSIP_SIRK_TYPE_PLAIN 0x01U + +#define BT_CSIP_RELEASE_VALUE 0x01U +#define BT_CSIP_LOCK_VALUE 0x02U + +struct bt_csip_sirk +{ + uint8_t type; + + /* SIRK stored in little endian byte order. */ + + uint8_t value[BT_CSIP_SIRK_SIZE]; +} __packed; + +struct bt_csip_set_coordinator_svc_inst * +bt_csip_set_coordinator_lookup_instance_by_index(const struct bt_conn *conn, + uint8_t idx); + +struct bt_csip_set_coordinator_svc_inst +{ + uint8_t set_lock; + + uint16_t start_handle; + uint16_t end_handle; + uint16_t sirk_handle; + uint16_t set_size_handle; + uint16_t set_lock_handle; + uint16_t rank_handle; + + uint8_t idx; + struct bt_gatt_subscribe_params sirk_sub_params; + struct bt_gatt_discover_params sirk_sub_disc_params; + struct bt_gatt_subscribe_params size_sub_params; + struct bt_gatt_discover_params size_sub_disc_params; + struct bt_gatt_subscribe_params lock_sub_params; + struct bt_gatt_discover_params lock_sub_disc_params; + + struct bt_conn *conn; + struct bt_csip_set_coordinator_set_info *set_info; +}; + +struct bt_csip_set_coordinator_csis_inst * +bt_csip_set_coordinator_csis_inst_by_handle(struct bt_conn *conn, + uint16_t start_handle); diff --git a/libs/zblue/audio/csip_set_coordinator.c b/libs/zblue/audio/csip_set_coordinator.c new file mode 100644 index 0000000000000..1b019533015d7 --- /dev/null +++ b/libs/zblue/audio/csip_set_coordinator.c @@ -0,0 +1,2166 @@ +/**************************************************************************** + * libs/zblue/audio/csip_set_coordinator.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Bluetooth Coordinated Set Identification Client + * + * Copyright (c) 2020 Bose Corporation Copyright (c) 2021-2024 Nordic + * Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + * + * csip_set_coordinator should be used in the following way 1) Find and + * connect to a set device 2) Do discovery 3) read values (always SIRK, size, + * lock and rank if possible) 4) Discover other set members if applicable 5) + * Connect and bond with each set member 6) Do discovery of each member 7) + * Read rank for each set member 8) Lock all members based on rank if + * possible 9) Do whatever is needed during lock 10) Unlock all members + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/bt_str.h" +#include "csip_crypto.h" +#include "csip_internal.h" +#include "host/conn_internal.h" +#include "host/keys.h" + +LOG_MODULE_REGISTER(bt_csip_set_coordinator, + CONFIG_BT_CSIP_SET_COORDINATOR_LOG_LEVEL); + +static struct active_members +{ + struct bt_csip_set_coordinator_set_member *members[CONFIG_BT_MAX_CONN]; + struct bt_csip_set_coordinator_set_info info; + uint8_t members_count; + uint8_t members_handled; + uint8_t members_restored; + bool in_progress; + + bt_csip_set_coordinator_ordered_access_t oap_cb; +} active; + +enum set_coordinator_flag +{ + SET_COORDINATOR_FLAG_BUSY, + + SET_COORDINATOR_FLAG_NUM_FLAGS, /* keep as last */ +}; + +struct bt_csip_set_coordinator_inst +{ + uint8_t inst_count; + uint8_t gatt_write_buf[1]; + + struct bt_csip_set_coordinator_svc_inst + svc_insts[CONFIG_BT_CSIP_SET_COORDINATOR_MAX_CSIS_INSTANCES]; + struct bt_csip_set_coordinator_set_member set_member; + struct bt_conn *conn; + struct bt_csip_set_coordinator_svc_inst *cur_inst; + struct bt_gatt_discover_params discover_params; + struct bt_gatt_read_params read_params; + struct bt_gatt_write_params write_params; + + atomic_define(flags, SET_COORDINATOR_FLAG_NUM_FLAGS); +}; + +static struct bt_uuid_16 uuid = BT_UUID_INIT_16(0); + +static struct list_node csip_set_coordinator_cbs = + LIST_INITIAL_VALUE(csip_set_coordinator_cbs); +static struct bt_csip_set_coordinator_inst client_insts[CONFIG_BT_MAX_CONN]; + +static int read_sirk(struct bt_csip_set_coordinator_svc_inst *svc_inst); +static int csip_set_coordinator_read_set_size( + struct bt_conn *conn, uint8_t inst_idx, bt_gatt_read_func_t cb); +static int csip_set_coordinator_read_set_lock( + struct bt_csip_set_coordinator_svc_inst *svc_inst); + +static uint8_t csip_set_coordinator_discover_insts_read_sirk_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, + const void *data, uint16_t length); +static void discover_insts_resume( + struct bt_conn *conn, uint16_t sirk_handle, uint16_t size_handle, + uint16_t rank_handle); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void active_members_reset(void) +{ + for (size_t i = 0U; i < active.members_count; i++) + { + const struct bt_csip_set_coordinator_set_member *member = + active.members[i]; + struct bt_csip_set_coordinator_inst *client = + CONTAINER_OF(member, struct bt_csip_set_coordinator_inst, + set_member); + + atomic_clear_bit(client->flags, SET_COORDINATOR_FLAG_BUSY); + } + + (void)memset(&active, 0, sizeof(active)); +} + +static struct bt_csip_set_coordinator_svc_inst *lookup_instance_by_handle( + struct bt_conn *conn, uint16_t handle) +{ + uint8_t conn_index; + struct bt_csip_set_coordinator_inst *client; + + DEBUGASSERT(conn); + DEBUGASSERT(handle > 0U); + + conn_index = bt_conn_index(conn); + client = &client_insts[conn_index]; + + for (int i = 0; i < ARRAY_SIZE(client->svc_insts); i++) + { + if (client->svc_insts[i].start_handle <= handle && + client->svc_insts[i].end_handle >= handle) + { + return &client->svc_insts[i]; + } + } + + return NULL; +} + +struct bt_csip_set_coordinator_svc_inst * +bt_csip_set_coordinator_lookup_instance_by_index(const struct bt_conn *conn, + uint8_t idx) +{ + uint8_t conn_index; + struct bt_csip_set_coordinator_inst *client; + + DEBUGASSERT(conn); + DEBUGASSERT(idx < CONFIG_BT_CSIP_SET_COORDINATOR_MAX_CSIS_INSTANCES); + + conn_index = bt_conn_index(conn); + client = &client_insts[conn_index]; + return &client->svc_insts[idx]; +} + +static struct bt_csip_set_coordinator_svc_inst * +lookup_instance_by_set_info( + const struct bt_csip_set_coordinator_set_member *member, + const struct + bt_csip_set_coordinator_set_info *set_info) +{ + struct bt_csip_set_coordinator_inst *inst = + CONTAINER_OF(member, + struct bt_csip_set_coordinator_inst, + set_member); + + for (int i = 0; i < ARRAY_SIZE(member->insts); i++) + { + const struct bt_csip_set_coordinator_set_info *member_set_info; + + member_set_info = &member->insts[i].info; + if (member_set_info->set_size == set_info->set_size && + memcmp(member_set_info->sirk, set_info->sirk, + sizeof(set_info->sirk)) == 0) + { + return bt_csip_set_coordinator_lookup_instance_by_index(inst->conn, + i); + } + } + + return NULL; +} + +static struct bt_csip_set_coordinator_svc_inst * +get_next_active_instance(void) +{ + struct bt_csip_set_coordinator_set_member *member; + struct bt_csip_set_coordinator_svc_inst *svc_inst; + + member = active.members[active.members_handled]; + + svc_inst = lookup_instance_by_set_info(member, &active.info); + if (svc_inst == NULL) + { + LOG_DBG("Failed to lookup instance by set_info"); + } + + return svc_inst; +} + +static int member_rank_compare_asc(const void *m1, const void *m2) +{ + const struct bt_csip_set_coordinator_set_member *member_1 = + *(const struct bt_csip_set_coordinator_set_member **)m1; + const struct bt_csip_set_coordinator_set_member *member_2 = + *(const struct bt_csip_set_coordinator_set_member **)m2; + struct bt_csip_set_coordinator_svc_inst *svc_inst_1; + struct bt_csip_set_coordinator_svc_inst *svc_inst_2; + + svc_inst_1 = lookup_instance_by_set_info(member_1, &active.info); + svc_inst_2 = lookup_instance_by_set_info(member_2, &active.info); + + if (svc_inst_1 == NULL) + { + LOG_ERR("svc_inst_1 was NULL for member %p", member_1); + return 0; + } + + if (svc_inst_2 == NULL) + { + LOG_ERR("svc_inst_2 was NULL for member %p", member_2); + return 0; + } + + return svc_inst_1->set_info->rank - svc_inst_2->set_info->rank; +} + +static int member_rank_compare_desc(const void *m1, const void *m2) +{ + /* If we call the "compare ascending" function with the members + * reversed, it will work as a descending comparison + */ + + return member_rank_compare_asc(m2, m1); +} + +static void active_members_store_ordered( + const struct bt_csip_set_coordinator_set_member *members[], size_t count, + const struct bt_csip_set_coordinator_set_info *info, bool ascending) +{ + (void)memcpy(active.members, members, count * sizeof(members[0U])); + active.members_count = count; + memcpy(&active.info, info, sizeof(active.info)); + + if (count > 1U && CONFIG_BT_MAX_CONN > 1) + { + qsort(active.members, count, sizeof(members[0U]), + ascending ? member_rank_compare_asc : member_rank_compare_desc); + +#if defined(CONFIG_ASSERT) + for (size_t i = 1U; i < count; i++) + { + const struct bt_csip_set_coordinator_svc_inst *svc_inst_1 = + lookup_instance_by_set_info(active.members[i - 1U], info); + const struct bt_csip_set_coordinator_svc_inst *svc_inst_2 = + lookup_instance_by_set_info(active.members[i], info); + const uint8_t rank_1 = svc_inst_1->set_info->rank; + const uint8_t rank_2 = svc_inst_2->set_info->rank; + + if (ascending) + { + DEBUGASSERT(rank_1 <= rank_2); + } + else + { + DEBUGASSERT(rank_1 >= rank_2); + } + } +#endif /* CONFIG_ASSERT */ + } +} + +static int sirk_decrypt(struct bt_conn *conn, + const uint8_t *enc_sirk, uint8_t *out_sirk) +{ + int err; + const uint8_t *k; + + if (IS_ENABLED(CONFIG_BT_CSIP_SET_COORDINATOR_TEST_SAMPLE_DATA)) + { + /* test_k is from the sample data from A.2 in the CSIS spec */ + + static const uint8_t test_k[] = + { + /* Sample data is in big-endian, we need it in little-endian. */ + + REVERSE_ARGS(0x67u, 0x6eu, 0x1bu, 0x9bu, 0xd4u, 0x48u, 0x69u, + 0x6fu, 0x06u, 0x1eu, + 0xc6u, 0x22u, 0x3cu, 0xe5u, 0xceu, 0xd9u) + }; + + LOG_DBG("Decrypting with sample data K"); + + k = test_k; + } + else + { + k = conn->le.keys->ltk.val; + } + + err = bt_csip_sdf(k, enc_sirk, out_sirk); + + return err; +} + +static void lock_changed(struct bt_csip_set_coordinator_csis_inst *inst, + bool locked) +{ + struct bt_csip_set_coordinator_cb *listener; + + active_members_reset(); + + list_for_every_container(&csip_set_coordinator_cbs, listener, _node) + { + if (listener->lock_changed) + { + listener->lock_changed(inst, locked); + } + } +} + +static void sirk_changed(struct bt_csip_set_coordinator_csis_inst *inst) +{ + struct bt_csip_set_coordinator_cb *listener; + + list_for_every_container(&csip_set_coordinator_cbs, listener, _node) + { + if (listener->sirk_changed) + { + listener->sirk_changed(inst); + } + } +} + +static void size_changed(struct bt_conn *conn, + struct bt_csip_set_coordinator_csis_inst *inst) +{ + struct bt_csip_set_coordinator_cb *listener; + + list_for_every_container(&csip_set_coordinator_cbs, listener, _node) + { + if (listener->size_changed != NULL) + { + listener->size_changed(conn, inst); + } + } +} + +static void release_set_complete(int err) +{ + struct bt_csip_set_coordinator_cb *listener; + + active_members_reset(); + + list_for_every_container(&csip_set_coordinator_cbs, listener, _node) + { + if (listener->release_set) + { + listener->release_set(err); + } + } +} + +static void lock_set_complete(int err) +{ + struct bt_csip_set_coordinator_cb *listener; + + active_members_reset(); + + list_for_every_container(&csip_set_coordinator_cbs, listener, _node) + { + if (listener->lock_set) + { + listener->lock_set(err); + } + } +} + +static void ordered_access_complete( + const struct bt_csip_set_coordinator_set_info *set_info, int err, + bool locked, struct bt_csip_set_coordinator_set_member *member) +{ + struct bt_csip_set_coordinator_cb *listener; + + active_members_reset(); + + list_for_every_container(&csip_set_coordinator_cbs, listener, _node) + { + if (listener->ordered_access) + { + listener->ordered_access(set_info, err, locked, member); + } + } +} + +static void discover_complete(struct bt_csip_set_coordinator_inst *client, + int err) +{ + struct bt_csip_set_coordinator_cb *listener; + + client->cur_inst = NULL; + atomic_clear_bit(client->flags, SET_COORDINATOR_FLAG_BUSY); + + list_for_every_container(&csip_set_coordinator_cbs, listener, _node) + { + if (listener->discover) + { + if (err == 0) + { + listener->discover(client->conn, &client->set_member, err, + client->inst_count); + } + else + { + listener->discover(client->conn, NULL, err, 0U); + } + } + } +} + +static uint8_t sirk_notify_func( + struct bt_conn *conn, struct bt_gatt_subscribe_params *params, + const void *data, uint16_t length) +{ + uint16_t handle = params->value_handle; + struct bt_csip_set_coordinator_svc_inst *svc_inst; + + if (data == NULL) + { + LOG_DBG("[UNSUBSCRIBED] %u", params->value_handle); + params->value_handle = 0U; + + return BT_GATT_ITER_STOP; + } + + if (conn == NULL) + { + return BT_GATT_ITER_CONTINUE; + } + + svc_inst = lookup_instance_by_handle(conn, handle); + + if (svc_inst != NULL) + { + LOG_DBG("Instance %u", svc_inst->idx); + if (length == sizeof(struct bt_csip_sirk)) + { + struct bt_csip_sirk *sirk = (struct bt_csip_sirk *)data; + struct bt_csip_set_coordinator_inst *client; + struct bt_csip_set_coordinator_csis_inst *inst; + uint8_t *dst_sirk; + + client = &client_insts[bt_conn_index(conn)]; + inst = &client->set_member.insts[svc_inst->idx]; + dst_sirk = inst->info.sirk; + + LOG_DBG("SIRK %sencrypted", + sirk->type == BT_CSIP_SIRK_TYPE_PLAIN ? "not " : ""); + + /* Assuming not connected to other set devices */ + + if (sirk->type == BT_CSIP_SIRK_TYPE_ENCRYPTED) + { + if ( + IS_ENABLED(CONFIG_BT_CSIP_SET_COORDINATOR_ENC_SIRK_SUPPORT)) + { + int err; + + LOG_HEXDUMP_DBG(sirk->value, sizeof(*sirk), + "Encrypted SIRK"); + err = sirk_decrypt(conn, sirk->value, dst_sirk); + if (err != 0) + { + LOG_ERR("Could not decrypt " + "SIRK %d", + err); + } + } + else + { + LOG_DBG("Encrypted SIRK not supported"); + return BT_GATT_ITER_CONTINUE; + } + } + else + { + (void)memcpy(dst_sirk, sirk->value, sizeof(sirk->value)); + } + + LOG_HEXDUMP_DBG(dst_sirk, BT_CSIP_SIRK_SIZE, "SIRK"); + + sirk_changed(inst); + } + else + { + LOG_DBG("Invalid length %u", length); + } + } + else + { + LOG_DBG("Notification/Indication on unknown service inst"); + } + + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t size_notify_func( + struct bt_conn *conn, struct bt_gatt_subscribe_params *params, + const void *data, uint16_t length) +{ + uint8_t set_size; + uint16_t handle = params->value_handle; + struct bt_csip_set_coordinator_svc_inst *svc_inst; + + if (data == NULL) + { + LOG_DBG("[UNSUBSCRIBED] %u", params->value_handle); + params->value_handle = 0U; + + return BT_GATT_ITER_STOP; + } + + if (conn == NULL) + { + return BT_GATT_ITER_CONTINUE; + } + + svc_inst = lookup_instance_by_handle(conn, handle); + + if (svc_inst != NULL) + { + if (length == sizeof(set_size)) + { + struct bt_csip_set_coordinator_set_info *set_info; + struct bt_csip_set_coordinator_csis_inst *inst; + struct bt_csip_set_coordinator_inst *client; + + client = &client_insts[bt_conn_index(conn)]; + inst = &client->set_member.insts[svc_inst->idx]; + set_info = &inst->info; + + (void)memcpy(&set_size, data, length); + LOG_DBG("Set size updated from %u to %u", set_info->set_size, + set_size); + + set_info->set_size = set_size; + size_changed(conn, inst); + } + else + { + LOG_DBG("Invalid length %u", length); + } + } + else + { + LOG_DBG("Notification/Indication on unknown service inst"); + } + + LOG_HEXDUMP_DBG(data, length, "Value"); + + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t lock_notify_func( + struct bt_conn *conn, struct bt_gatt_subscribe_params *params, + const void *data, uint16_t length) +{ + uint8_t value; + uint16_t handle = params->value_handle; + struct bt_csip_set_coordinator_svc_inst *svc_inst; + + if (data == NULL) + { + LOG_DBG("[UNSUBSCRIBED] %u", params->value_handle); + params->value_handle = 0U; + + return BT_GATT_ITER_STOP; + } + + if (conn == NULL) + { + return BT_GATT_ITER_CONTINUE; + } + + svc_inst = lookup_instance_by_handle(conn, handle); + + if (svc_inst != NULL) + { + if (length == sizeof(svc_inst->set_lock)) + { + struct bt_csip_set_coordinator_inst *client; + struct bt_csip_set_coordinator_csis_inst *inst; + bool locked; + + (void)memcpy(&value, data, length); + if (value != BT_CSIP_RELEASE_VALUE && value != BT_CSIP_LOCK_VALUE) + { + LOG_DBG("Invalid value %u", value); + return BT_GATT_ITER_STOP; + } + + (void)memcpy(&svc_inst->set_lock, data, length); + + locked = svc_inst->set_lock == BT_CSIP_LOCK_VALUE; + LOG_DBG("Instance %u lock was %s", svc_inst->idx, + locked ? "locked" : "released"); + + client = &client_insts[bt_conn_index(conn)]; + inst = &client->set_member.insts[svc_inst->idx]; + + lock_changed(inst, locked); + } + else + { + LOG_DBG("Invalid length %u", length); + } + } + else + { + LOG_DBG("Notification/Indication on unknown service inst"); + } + + LOG_HEXDUMP_DBG(data, length, "Value"); + + return BT_GATT_ITER_CONTINUE; +} + +static int csip_set_coordinator_write_set_lock( + struct bt_csip_set_coordinator_svc_inst *inst, bool lock, + bt_gatt_write_func_t cb) +{ + struct bt_csip_set_coordinator_inst *client = + &client_insts[bt_conn_index(inst->conn)]; + + if (inst->set_lock_handle == 0) + { + LOG_DBG("Handle not set"); + client->cur_inst = NULL; + return -EINVAL; + } + + /* Write to call control point */ + + client->gatt_write_buf[0] = + lock ? BT_CSIP_LOCK_VALUE : BT_CSIP_RELEASE_VALUE; + client->write_params.data = client->gatt_write_buf; + client->write_params.length = sizeof(lock); + client->write_params.func = cb; + client->write_params.handle = inst->set_lock_handle; + + return bt_gatt_write(inst->conn, &client->write_params); +} + +static int read_sirk(struct bt_csip_set_coordinator_svc_inst *svc_inst) +{ + struct bt_csip_set_coordinator_inst *client = + &client_insts[bt_conn_index(svc_inst->conn)]; + + if (client->cur_inst != NULL) + { + if (client->cur_inst != svc_inst) + { + return -EBUSY; + } + } + else + { + client->cur_inst = svc_inst; + } + + if (svc_inst->sirk_handle == 0) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + + client->read_params.func = + csip_set_coordinator_discover_insts_read_sirk_cb; + client->read_params.handle_count = 1U; + client->read_params.single.handle = svc_inst->sirk_handle; + client->read_params.single.offset = 0U; + + return bt_gatt_read(svc_inst->conn, &client->read_params); +} + +static int csip_set_coordinator_read_set_size( + struct bt_conn *conn, uint8_t inst_idx, bt_gatt_read_func_t cb) +{ + struct bt_csip_set_coordinator_inst *client = + &client_insts[bt_conn_index(conn)]; + + if (inst_idx >= CONFIG_BT_CSIP_SET_COORDINATOR_MAX_CSIS_INSTANCES) + { + return -EINVAL; + } + else if (client->cur_inst != NULL) + { + if (client->cur_inst != + bt_csip_set_coordinator_lookup_instance_by_index(conn, + inst_idx)) + { + return -EBUSY; + } + } + else + { + client->cur_inst = + bt_csip_set_coordinator_lookup_instance_by_index(conn, + inst_idx); + if (client->cur_inst == NULL) + { + LOG_DBG("Inst not found"); + return -EINVAL; + } + } + + if (client->cur_inst->set_size_handle == 0) + { + LOG_DBG("Handle not set"); + client->cur_inst = NULL; + return -EINVAL; + } + + client->read_params.func = cb; + client->read_params.handle_count = 1U; + client->read_params.single.handle = client->cur_inst->set_size_handle; + client->read_params.single.offset = 0U; + + return bt_gatt_read(conn, &client->read_params); +} + +static int csip_set_coordinator_read_rank( + struct bt_conn *conn, uint8_t inst_idx, bt_gatt_read_func_t cb) +{ + struct bt_csip_set_coordinator_inst *client = + &client_insts[bt_conn_index(conn)]; + + if (inst_idx >= CONFIG_BT_CSIP_SET_COORDINATOR_MAX_CSIS_INSTANCES) + { + return -EINVAL; + } + else if (client->cur_inst != NULL) + { + if (client->cur_inst != + bt_csip_set_coordinator_lookup_instance_by_index(conn, + inst_idx)) + { + return -EBUSY; + } + } + else + { + client->cur_inst = + bt_csip_set_coordinator_lookup_instance_by_index(conn, + inst_idx); + if (client->cur_inst == NULL) + { + LOG_DBG("Inst not found"); + return -EINVAL; + } + } + + if (client->cur_inst->rank_handle == 0) + { + LOG_DBG("Handle not set"); + client->cur_inst = NULL; + return -EINVAL; + } + + client->read_params.func = cb; + client->read_params.handle_count = 1U; + client->read_params.single.handle = client->cur_inst->rank_handle; + client->read_params.single.offset = 0U; + + return bt_gatt_read(conn, &client->read_params); +} + +static int csip_set_coordinator_discover_sets( + struct bt_csip_set_coordinator_inst *client) +{ + struct bt_csip_set_coordinator_set_member *member = &client->set_member; + + /* Start reading values and call CB when done */ + + return read_sirk(( + struct bt_csip_set_coordinator_svc_inst + *)member->insts[0].svc_inst); +} + +static uint8_t discover_func( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_gatt_chrc *chrc; + struct bt_csip_set_coordinator_inst *client = + &client_insts[bt_conn_index(conn)]; + struct bt_gatt_subscribe_params *sub_params = NULL; + void *notify_handler = NULL; + + if (attr == NULL) + { + LOG_DBG("Setup complete for %u / %u", client->cur_inst->idx + 1, + client->inst_count); + (void)memset(params, 0, sizeof(*params)); + + if (CONFIG_BT_CSIP_SET_COORDINATOR_MAX_CSIS_INSTANCES > 1 && + (client->cur_inst->idx + 1) < client->inst_count) + { + int err; + + client->cur_inst = &client->svc_insts[client->cur_inst->idx + 1]; + client->discover_params.uuid = NULL; + client->discover_params.start_handle = + client->cur_inst->start_handle; + client->discover_params.end_handle = client->cur_inst->end_handle; + client->discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; + client->discover_params.func = discover_func; + + err = bt_gatt_discover(conn, &client->discover_params); + if (err != 0) + { + LOG_DBG("Discover failed (err %d)", err); + discover_complete(client, err); + } + } + else + { + int err; + + client->cur_inst = NULL; + err = csip_set_coordinator_discover_sets(client); + if (err != 0) + { + LOG_DBG("Discover sets failed (err %d)", err); + discover_complete(client, err); + } + } + + return BT_GATT_ITER_STOP; + } + + LOG_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle); + + if (params->type == BT_GATT_DISCOVER_CHARACTERISTIC && + client->inst_count != 0U) + { + chrc = (struct bt_gatt_chrc *)attr->user_data; + if (bt_uuid_cmp(chrc->uuid, BT_UUID_CSIS_SIRK) == 0) + { + LOG_DBG("SIRK"); + client->cur_inst->sirk_handle = chrc->value_handle; + sub_params = &client->cur_inst->sirk_sub_params; + sub_params->disc_params = &client->cur_inst->sirk_sub_disc_params; + notify_handler = sirk_notify_func; + } + else if (bt_uuid_cmp(chrc->uuid, BT_UUID_CSIS_SET_SIZE) == 0) + { + LOG_DBG("Set size"); + client->cur_inst->set_size_handle = chrc->value_handle; + sub_params = &client->cur_inst->size_sub_params; + sub_params->disc_params = &client->cur_inst->size_sub_disc_params; + notify_handler = size_notify_func; + } + else if (bt_uuid_cmp(chrc->uuid, BT_UUID_CSIS_SET_LOCK) == 0) + { + struct bt_csip_set_coordinator_set_info *set_info; + + LOG_DBG("Set lock"); + client->cur_inst->set_lock_handle = chrc->value_handle; + sub_params = &client->cur_inst->lock_sub_params; + sub_params->disc_params = &client->cur_inst->lock_sub_disc_params; + notify_handler = lock_notify_func; + + set_info = &client->set_member.insts[client->cur_inst->idx].info; + set_info->lockable = true; + } + else if (bt_uuid_cmp(chrc->uuid, BT_UUID_CSIS_RANK) == 0) + { + LOG_DBG("Set rank"); + client->cur_inst->rank_handle = chrc->value_handle; + } + + if (sub_params != NULL && notify_handler != NULL) + { + sub_params->value = 0U; + if ((chrc->properties & BT_GATT_CHRC_NOTIFY) != 0) + { + sub_params->value = BT_GATT_CCC_NOTIFY; + } + else if ((chrc->properties & BT_GATT_CHRC_INDICATE) != 0) + { + sub_params->value = BT_GATT_CCC_INDICATE; + } + + if (sub_params->value != 0U) + { + int err; + + sub_params->ccc_handle = BT_GATT_AUTO_DISCOVER_CCC_HANDLE; + sub_params->end_handle = client->cur_inst->end_handle; + sub_params->value_handle = chrc->value_handle; + sub_params->notify = notify_handler; + atomic_set_bit(sub_params->flags, + BT_GATT_SUBSCRIBE_FLAG_VOLATILE); + + err = bt_gatt_subscribe(conn, sub_params); + if (err != 0 && err != -EALREADY) + { + LOG_DBG("Failed to subscribe (err %d)", err); + discover_complete(client, err); + + return BT_GATT_ITER_STOP; + } + } + } + } + + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t primary_discover_func( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_gatt_service_val *prim_service; + struct bt_csip_set_coordinator_inst *client = + &client_insts[bt_conn_index(conn)]; + + if (attr == NULL || + client->inst_count == CONFIG_BT_CSIP_SET_COORDINATOR_MAX_CSIS_INSTANCES) + { + LOG_DBG("Discover complete, found %u instances", client->inst_count); + (void)memset(params, 0, sizeof(*params)); + + if (client->inst_count != 0U) + { + int err; + + client->cur_inst = &client->svc_insts[0]; + client->discover_params.uuid = NULL; + client->discover_params.start_handle = + client->cur_inst->start_handle; + client->discover_params.end_handle = client->cur_inst->end_handle; + client->discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; + client->discover_params.func = discover_func; + + err = bt_gatt_discover(conn, &client->discover_params); + if (err != 0) + { + LOG_DBG("Discover failed (err %d)", err); + discover_complete(client, err); + } + } + else + { + discover_complete(client, 0); + } + + return BT_GATT_ITER_STOP; + } + + LOG_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle); + + if (params->type == BT_GATT_DISCOVER_PRIMARY) + { + prim_service = (struct bt_gatt_service_val *)attr->user_data; + client->discover_params.start_handle = attr->handle + 1; + + client->cur_inst = &client->svc_insts[client->inst_count]; + client->cur_inst->idx = client->inst_count; + client->cur_inst->start_handle = attr->handle; + client->cur_inst->end_handle = prim_service->end_handle; + client->cur_inst->conn = bt_conn_ref(conn); + client->cur_inst->set_info = + &client->set_member.insts[client->cur_inst->idx].info; + client->inst_count++; + } + + return BT_GATT_ITER_CONTINUE; +} + +bool bt_csip_set_coordinator_is_set_member( + const uint8_t sirk[BT_CSIP_SIRK_SIZE], struct bt_data *data) +{ + if (data->type == BT_DATA_CSIS_RSI && data->data_len == BT_CSIP_RSI_SIZE) + { + uint8_t err; + uint8_t hash[BT_CSIP_CRYPTO_HASH_SIZE]; + uint8_t prand[BT_CSIP_CRYPTO_PRAND_SIZE]; + uint8_t calculated_hash[BT_CSIP_CRYPTO_HASH_SIZE]; + + memcpy(hash, data->data, BT_CSIP_CRYPTO_HASH_SIZE); + memcpy(prand, data->data + BT_CSIP_CRYPTO_HASH_SIZE, + BT_CSIP_CRYPTO_PRAND_SIZE); + + LOG_DBG("hash: %s", bt_hex(hash, BT_CSIP_CRYPTO_HASH_SIZE)); + LOG_DBG("prand %s", bt_hex(prand, BT_CSIP_CRYPTO_PRAND_SIZE)); + err = bt_csip_sih(sirk, prand, calculated_hash); + if (err != 0) + { + return false; + } + + LOG_DBG("calculated_hash: %s", bt_hex(calculated_hash, + BT_CSIP_CRYPTO_HASH_SIZE)); + LOG_DBG("hash: %s", bt_hex(hash, BT_CSIP_CRYPTO_HASH_SIZE)); + + return memcmp(calculated_hash, hash, BT_CSIP_CRYPTO_HASH_SIZE) == 0; + } + + return false; +} + +static uint8_t csip_set_coordinator_discover_insts_read_rank_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, + const void *data, uint16_t length) +{ + struct bt_csip_set_coordinator_inst *client = + &client_insts[bt_conn_index(conn)]; + + ARG_UNUSED(params); + + DEBUGASSERT(client->cur_inst != NULL); + + if (err != 0) + { + LOG_DBG("err: 0x%02X", err); + + discover_complete(client, err); + } + else if (data != NULL) + { + struct bt_csip_set_coordinator_set_info *set_info; + + LOG_HEXDUMP_DBG(data, length, "Data read"); + + set_info = &client->set_member.insts[client->cur_inst->idx].info; + + if (length == sizeof(set_info->rank)) + { + (void)memcpy(&set_info->rank, data, length); + LOG_DBG("%u", set_info->rank); + } + else + { + LOG_DBG("Invalid length, continuing to next member"); + } + + discover_insts_resume(conn, 0, 0, 0); + } + + return BT_GATT_ITER_STOP; +} + +static uint8_t +csip_set_coordinator_discover_insts_read_set_size_cb(struct bt_conn *conn, + uint8_t err, + struct + bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct bt_csip_set_coordinator_inst *client = + &client_insts[bt_conn_index(conn)]; + + ARG_UNUSED(params); + + DEBUGASSERT(client->cur_inst != NULL); + + if (err != 0) + { + LOG_DBG("err: 0x%02X", err); + + discover_complete(client, err); + } + else if (data != NULL) + { + struct bt_csip_set_coordinator_set_info *set_info; + + LOG_HEXDUMP_DBG(data, length, "Data read"); + + set_info = &client->set_member.insts[client->cur_inst->idx].info; + + if (length == sizeof(set_info->set_size)) + { + (void)memcpy(&set_info->set_size, data, length); + LOG_DBG("%u", set_info->set_size); + } + else + { + LOG_DBG("Invalid length"); + } + + discover_insts_resume(conn, 0, 0, client->cur_inst->rank_handle); + } + + return BT_GATT_ITER_STOP; +} + +static int parse_sirk( + struct bt_csip_set_coordinator_inst *client, const void *data, + uint16_t length) +{ + uint8_t *sirk; + + sirk = client->set_member.insts[client->cur_inst->idx].info.sirk; + + if (length == sizeof(struct bt_csip_sirk)) + { + struct bt_csip_sirk *recvd_sirk = (struct bt_csip_sirk *)data; + + LOG_DBG("SIRK %sencrypted", + recvd_sirk->type == BT_CSIP_SIRK_TYPE_PLAIN ? "not " : ""); + + /* Assuming not connected to other set devices */ + + if (recvd_sirk->type == BT_CSIP_SIRK_TYPE_ENCRYPTED) + { + if (IS_ENABLED(CONFIG_BT_CSIP_SET_COORDINATOR_ENC_SIRK_SUPPORT)) + { + int err; + + LOG_HEXDUMP_DBG(recvd_sirk->value, sizeof(recvd_sirk->value), + "Encrypted SIRK"); + err = sirk_decrypt(client->conn, recvd_sirk->value, sirk); + if (err != 0) + { + LOG_ERR("Could not decrypt " + "SIRK %d", + err); + return err; + } + } + else + { + LOG_WRN("Encrypted SIRK not supported"); + sirk = NULL; + return BT_ATT_ERR_INSUFFICIENT_ENCRYPTION; + } + } + else + { + (void)memcpy(sirk, recvd_sirk->value, sizeof(recvd_sirk->value)); + } + + if (sirk != NULL) + { + LOG_HEXDUMP_DBG(sirk, BT_CSIP_SIRK_SIZE, "SIRK"); + } + } + else + { + LOG_DBG("Invalid length"); + return BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + + return 0; +} + +static uint8_t csip_set_coordinator_discover_insts_read_sirk_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, + const void *data, uint16_t length) +{ + struct bt_csip_set_coordinator_inst *client = + &client_insts[bt_conn_index(conn)]; + int cb_err = err; + + ARG_UNUSED(params); + + DEBUGASSERT(client->cur_inst != NULL); + + if (err != 0) + { + LOG_DBG("err: 0x%02X", err); + + discover_complete(client, err); + } + else if (data != NULL) + { + LOG_HEXDUMP_DBG(data, length, "Data read"); + + cb_err = parse_sirk(client, data, length); + + if (cb_err != 0) + { + LOG_DBG("Could not parse SIRK: %d", cb_err); + } + else + { + discover_insts_resume(conn, 0, client->cur_inst->set_size_handle, + client->cur_inst->rank_handle); + } + } + + return BT_GATT_ITER_STOP; +} + +/** + * @brief Reads the (next) characteristics for the set discovery procedure + * + * It skips all handles that are 0. + * + * @param conn Connection to a CSIP set member device. + * @param sirk_handle 0, or the handle for the SIRK characteristic. + * @param size_handle 0, or the handle for the size characteristic. + * @param rank_handle 0, or the handle for the rank characteristic. + */ + +static void discover_insts_resume( + struct bt_conn *conn, uint16_t sirk_handle, uint16_t size_handle, + uint16_t rank_handle) +{ + int cb_err = 0; + struct bt_csip_set_coordinator_inst *client = + &client_insts[bt_conn_index(conn)]; + + ARG_UNUSED(sirk_handle); + + if (size_handle != 0U) + { + cb_err = csip_set_coordinator_read_set_size( + conn, + client->cur_inst->idx, + csip_set_coordinator_discover_insts_read_set_size_cb); + if (cb_err != 0) + { + LOG_DBG("Could not read set size: %d", cb_err); + } + } + else if (rank_handle != 0) + { + cb_err = csip_set_coordinator_read_rank(conn, client->cur_inst->idx, + csip_set_coordinator_discover_insts_read_rank_cb); + if (cb_err != 0) + { + LOG_DBG("Could not read set rank: %d", cb_err); + } + } + else + { + uint8_t next_idx = client->cur_inst->idx + 1; + + client->cur_inst = NULL; + if (next_idx < client->inst_count) + { + client->cur_inst = + bt_csip_set_coordinator_lookup_instance_by_index(conn, + next_idx); + + /* Read next */ + + cb_err = read_sirk(client->cur_inst); + } + else + { + discover_complete(client, 0); + + return; + } + } + + if (cb_err != 0) + { + discover_complete(client, cb_err); + } +} + +static void csip_set_coordinator_write_restore_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_write_params *params) +{ + struct bt_csip_set_coordinator_inst *client = + &client_insts[bt_conn_index(conn)]; + + ARG_UNUSED(params); + + if (err != 0) + { + LOG_WRN("Could not restore (%d)", err); + release_set_complete(err); + + return; + } + + active.members_restored++; + LOG_DBG("Restored %u/%u members", active.members_restored, + active.members_handled); + + if (active.members_restored < active.members_handled && + CONFIG_BT_MAX_CONN > 1) + { + struct bt_csip_set_coordinator_set_member *member; + int csip_err; + + member = + active.members + [active.members_handled - active.members_restored - 1]; + client->cur_inst = lookup_instance_by_set_info(member, &active.info); + if (client->cur_inst == NULL) + { + release_set_complete(-ENOENT); + + return; + } + + csip_err = csip_set_coordinator_write_set_lock(client->cur_inst, false, + csip_set_coordinator_write_restore_cb); + if (csip_err != 0) + { + LOG_DBG("Failed to release next member[%u]: %d", + active.members_handled, csip_err); + + release_set_complete(csip_err); + } + } + else + { + release_set_complete(0); + } +} + +static void csip_set_coordinator_write_lock_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_write_params *params) +{ + struct bt_csip_set_coordinator_inst *client = + &client_insts[bt_conn_index(conn)]; + + ARG_UNUSED(params); + + if (err != 0) + { + LOG_DBG("Could not lock (0x%X)", err); + if (active.members_handled > 0U && CONFIG_BT_MAX_CONN > 1) + { + struct bt_csip_set_coordinator_set_member *member; + int csip_err; + + active.members_restored = 0U; + + member = active.members[active.members_handled - 1]; + client->cur_inst = lookup_instance_by_set_info(member, + &active.info); + if (client->cur_inst == NULL) + { + LOG_DBG("Failed to lookup instance by set_info"); + + lock_set_complete(-ENOENT); + return; + } + + csip_err = csip_set_coordinator_write_set_lock(client->cur_inst, + false, + csip_set_coordinator_write_restore_cb); + if (csip_err != 0) + { + LOG_WRN("Could not release lock of previous locked member: %d", + csip_err); + active_members_reset(); + return; + } + } + else + { + lock_set_complete(err); + } + + return; + } + + active.members_handled++; + LOG_DBG("Locked %u/%u members", active.members_handled, + active.members_count); + + if (active.members_handled < active.members_count) + { + struct bt_csip_set_coordinator_svc_inst *prev_inst = client->cur_inst; + int csip_err; + + client->cur_inst = get_next_active_instance(); + if (client->cur_inst == NULL) + { + lock_set_complete(-ENOENT); + + return; + } + + csip_err = csip_set_coordinator_write_set_lock(client->cur_inst, true, + csip_set_coordinator_write_lock_cb); + if (csip_err != 0) + { + LOG_DBG("Failed to lock next member[%u]: %d", + active.members_handled, + csip_err); + + active.members_restored = 0U; + + csip_err = csip_set_coordinator_write_set_lock(prev_inst, false, + csip_set_coordinator_write_restore_cb); + if (csip_err != 0) + { + LOG_WRN("Could not release lock of previous locked member: %d", + csip_err); + active_members_reset(); + return; + } + } + } + else + { + lock_set_complete(0); + } +} + +static void csip_set_coordinator_write_release_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_write_params *params) +{ + struct bt_csip_set_coordinator_inst *client = + &client_insts[bt_conn_index(conn)]; + + ARG_UNUSED(params); + + if (err != 0) + { + LOG_DBG("Could not release lock (%d)", err); + release_set_complete(err); + + return; + } + + active.members_handled++; + LOG_DBG("Released %u/%u members", active.members_handled, + active.members_count); + + if (active.members_handled < active.members_count) + { + int csip_err; + + client->cur_inst = get_next_active_instance(); + if (client->cur_inst == NULL) + { + release_set_complete(-ENOENT); + + return; + } + + csip_err = csip_set_coordinator_write_set_lock(client->cur_inst, false, + csip_set_coordinator_write_release_cb); + if (csip_err != 0) + { + LOG_DBG("Failed to release next member[%u]: %d", + active.members_handled, csip_err); + + release_set_complete(csip_err); + } + } + else + { + release_set_complete(0); + } +} + +static void csip_set_coordinator_lock_state_read_cb(int err, bool locked) +{ + const struct bt_csip_set_coordinator_set_info *info = &active.info; + struct bt_csip_set_coordinator_set_member *cur_member = NULL; + + if (err != 0 || locked) + { + cur_member = active.members[active.members_handled]; + } + else if (active.oap_cb == NULL || !active.oap_cb(info, active.members, + active.members_count)) + { + err = -ECANCELED; + } + + ordered_access_complete(info, err, locked, cur_member); +} + +static uint8_t csip_set_coordinator_read_lock_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, + const void *data, uint16_t length) +{ + struct bt_csip_set_coordinator_inst *client = + &client_insts[bt_conn_index(conn)]; + uint8_t value = 0U; + + ARG_UNUSED(params); + + if (err != 0) + { + LOG_DBG("Could not read lock value (0x%X)", err); + + csip_set_coordinator_lock_state_read_cb(err, false); + + return BT_GATT_ITER_STOP; + } + + active.members_handled++; + LOG_DBG("Read lock state on %u/%u members", active.members_handled, + active.members_count); + + if (data == NULL || length != sizeof(client->cur_inst->set_lock)) + { + LOG_DBG("Invalid data %p or length %u", data, length); + + csip_set_coordinator_lock_state_read_cb(err, false); + + return BT_GATT_ITER_STOP; + } + + value = ((uint8_t *)data)[0]; + if (value != BT_CSIP_RELEASE_VALUE && value != BT_CSIP_LOCK_VALUE) + { + LOG_DBG("Invalid value %u read", value); + err = BT_ATT_ERR_UNLIKELY; + + csip_set_coordinator_lock_state_read_cb(err, false); + + return BT_GATT_ITER_STOP; + } + + client->cur_inst->set_lock = value; + + if (value != BT_CSIP_RELEASE_VALUE) + { + LOG_DBG("Set member not unlocked"); + + csip_set_coordinator_lock_state_read_cb(0, true); + + return BT_GATT_ITER_STOP; + } + + if (active.members_handled < active.members_count) + { + int csip_err; + + client->cur_inst = get_next_active_instance(); + if (client->cur_inst == NULL) + { + csip_set_coordinator_lock_state_read_cb(-ENOENT, false); + + return BT_GATT_ITER_STOP; + } + + csip_err = csip_set_coordinator_read_set_lock(client->cur_inst); + if (csip_err != 0) + { + LOG_DBG("Failed to read next member[%u]: %d", + active.members_handled, + csip_err); + + csip_set_coordinator_lock_state_read_cb(err, false); + } + } + else + { + csip_set_coordinator_lock_state_read_cb(0, false); + } + + return BT_GATT_ITER_STOP; +} + +static int csip_set_coordinator_read_set_lock( + struct bt_csip_set_coordinator_svc_inst *inst) +{ + struct bt_csip_set_coordinator_inst *client = + &client_insts[bt_conn_index(inst->conn)]; + int err; + + if (inst->set_lock_handle == 0) + { + LOG_DBG("Handle not set"); + client->cur_inst = NULL; + return -EINVAL; + } + + client->read_params.func = csip_set_coordinator_read_lock_cb; + client->read_params.handle_count = 1U; + client->read_params.single.handle = inst->set_lock_handle; + client->read_params.single.offset = 0; + + client->cur_inst = inst; + + err = bt_gatt_read(inst->conn, &client->read_params); + if (err != 0) + { + client->cur_inst = NULL; + } + + return err; +} + +static void csip_set_coordinator_reset( + struct bt_csip_set_coordinator_inst *inst) +{ + inst->inst_count = 0U; + memset(&inst->set_member, 0, sizeof(inst->set_member)); + + for (size_t i = 0U; i < ARRAY_SIZE(inst->svc_insts); i++) + { + struct bt_csip_set_coordinator_svc_inst + *svc_inst = &inst->svc_insts[i]; + + svc_inst->idx = 0U; + svc_inst->set_lock = 0U; + svc_inst->start_handle = 0U; + svc_inst->end_handle = 0U; + svc_inst->sirk_handle = 0U; + svc_inst->set_size_handle = 0U; + svc_inst->set_lock_handle = 0U; + svc_inst->rank_handle = 0U; + + bt_conn_drop(&svc_inst->conn); + + if (svc_inst->set_info != NULL) + { + memset(svc_inst->set_info, 0, sizeof(*svc_inst->set_info)); + svc_inst->set_info = NULL; + } + } + + bt_conn_drop(&inst->conn); +} + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + struct bt_csip_set_coordinator_inst *inst; + + ARG_UNUSED(reason); + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + inst = &client_insts[bt_conn_index(conn)]; + if (inst->conn == conn) + { + csip_set_coordinator_reset(inst); + } +} + +BT_CONN_CB_DEFINE(conn_callbacks) = +{ + .disconnected = disconnected, +}; + +struct bt_csip_set_coordinator_csis_inst * +bt_csip_set_coordinator_csis_inst_by_handle(struct bt_conn *conn, + uint16_t start_handle) +{ + const struct bt_csip_set_coordinator_svc_inst *svc_inst; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return NULL; + } + + if (start_handle == 0) + { + LOG_DBG("start_handle is 0"); + + return NULL; + } + + svc_inst = lookup_instance_by_handle(conn, start_handle); + + if (svc_inst != NULL) + { + struct bt_csip_set_coordinator_inst *client; + + client = &client_insts[bt_conn_index(conn)]; + + return &client->set_member.insts[svc_inst->idx]; + } + + return NULL; +} + +struct bt_csip_set_coordinator_set_member * +bt_csip_set_coordinator_set_member_by_conn(const struct bt_conn *conn) +{ + struct bt_csip_set_coordinator_inst *client; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return NULL; + } + + client = &client_insts[bt_conn_index(conn)]; + if (client->conn == conn) + { + return &client->set_member; + } + + return NULL; +} + +/* Public Functions */ + +int +bt_csip_set_coordinator_register_cb(struct bt_csip_set_coordinator_cb *cb) +{ + if (cb == NULL) + { + LOG_DBG("cb is NULL"); + + return -EINVAL; + } + + list_add_tail(&csip_set_coordinator_cbs, &cb->_node); + + return 0; +} + +int bt_csip_set_coordinator_discover(struct bt_conn *conn) +{ + struct bt_csip_set_coordinator_inst *client; + struct bt_conn *ref; + int err; + + if (conn == NULL) + { + LOG_DBG("NULL conn"); + return -EINVAL; + } + + client = &client_insts[bt_conn_index(conn)]; + if (atomic_test_and_set_bit(client->flags, SET_COORDINATOR_FLAG_BUSY)) + { + return -EBUSY; + } + + ref = bt_conn_ref(conn); + if (ref == NULL) + { + err = -ENOTCONN; + goto cleanup; + } + + csip_set_coordinator_reset(client); + + /* Discover CSIS on peer, setup handles and notify */ + + (void)memset(&client->discover_params, 0, sizeof(client->discover_params)); + (void)memcpy(&uuid, BT_UUID_CSIS, sizeof(uuid)); + client->discover_params.func = primary_discover_func; + client->discover_params.uuid = &uuid.uuid; + client->discover_params.type = BT_GATT_DISCOVER_PRIMARY; + client->discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; + client->discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + + err = bt_gatt_discover(conn, &client->discover_params); + if (err != 0) + { + bt_conn_unref(ref); + goto cleanup; + } + + for (size_t i = 0U; i < ARRAY_SIZE(client->set_member.insts); i++) + { + client->set_member.insts[i].svc_inst = (void *)&client->svc_insts[i]; + } + + client->conn = ref; + + return 0; + +cleanup: + atomic_clear_bit(client->flags, SET_COORDINATOR_FLAG_BUSY); + return err; +} + +static int verify_members( + const struct bt_csip_set_coordinator_set_member **members, uint8_t count, + const struct bt_csip_set_coordinator_set_info *set_info) +{ + bool zero_rank; + uint8_t ranks[CONFIG_BT_MAX_CONN]; + + if (count > CONFIG_BT_MAX_CONN) + { + LOG_DBG("count (%u) larger than maximum support servers (%d)", count, + CONFIG_BT_MAX_CONN); + return -EINVAL; + } + + zero_rank = false; + for (int i = 0; i < count; i++) + { + const struct bt_csip_set_coordinator_set_member *member = members[i]; + struct bt_csip_set_coordinator_inst *client_inst = + CONTAINER_OF(member, + struct bt_csip_set_coordinator_inst, + set_member); + struct bt_csip_set_coordinator_svc_inst *svc_inst; + struct bt_conn *conn; + + if (member == NULL) + { + LOG_DBG("Invalid member[%d] was NULL", i); + return -EINVAL; + } + + conn = client_inst->conn; + + if (conn == NULL) + { + LOG_DBG("Member[%d] conn was NULL", i); + return -EINVAL; + } + + if (conn->state != BT_CONN_CONNECTED) + { + LOG_DBG("Member[%d] was not connected", i); + return -ENOTCONN; + } + + svc_inst = lookup_instance_by_set_info(member, set_info); + if (svc_inst == NULL) + { + LOG_DBG("Member[%d] could not find matching instance for the " + "set_info", + i); + return -EINVAL; + } + + ranks[i] = svc_inst->set_info->rank; + if (ranks[i] == 0U && !zero_rank) + { + zero_rank = true; + } + else if (ranks[i] != 0 && zero_rank) + { + /* all members in a set shall either use rank, or not use rank */ + + LOG_DBG("Found mix of 0 and non-0 ranks"); + return -EINVAL; + } + } + + if (CONFIG_BT_MAX_CONN > 1 && !zero_rank && count > 1U) + { + /* Search for duplicate ranks */ + + for (uint8_t i = 0U; i < count - 1; i++) + { + for (uint8_t j = i + 1; j < count; j++) + { + if (ranks[j] == ranks[i]) + { + /* duplicate rank */ + + LOG_DBG("Duplicate rank (%u) for members[%zu] " + "and members[%zu]", + ranks[i], i, j); + return -EINVAL; + } + } + } + } + + return 0; +} + +static bool check_and_set_members_busy( + const struct bt_csip_set_coordinator_set_member *members[], size_t count) +{ + size_t num_free; + + for (num_free = 0U; num_free < count; num_free++) + { + const struct bt_csip_set_coordinator_set_member *member = + members[num_free]; + struct bt_csip_set_coordinator_inst *client = + CONTAINER_OF(member, + struct bt_csip_set_coordinator_inst, + set_member); + + if (atomic_test_and_set_bit(client->flags, SET_COORDINATOR_FLAG_BUSY)) + { + LOG_DBG("Member[%zu] (%p) is busy", num_free, member); + break; + } + } + + /* If any is busy, revert any busy states we've set */ + + if (num_free != count) + { + for (size_t i = 0U; i < num_free; i++) + { + const struct bt_csip_set_coordinator_set_member *member = + members[i]; + struct bt_csip_set_coordinator_inst *client = + CONTAINER_OF(member, + struct bt_csip_set_coordinator_inst, + set_member); + + atomic_clear_bit(client->flags, SET_COORDINATOR_FLAG_BUSY); + } + } + + return num_free == count; +} + +static int +csip_set_coordinator_get_lock_state( + const struct bt_csip_set_coordinator_set_member **members, + uint8_t count, + const struct + bt_csip_set_coordinator_set_info *set_info) +{ + int err; + + if (active.in_progress) + { + LOG_DBG("Procedure in progress"); + return -EBUSY; + } + + err = verify_members(members, count, set_info); + if (err != 0) + { + LOG_DBG("Could not verify members: %d", err); + return err; + } + + if (!check_and_set_members_busy(members, count)) + { + LOG_DBG("One or more members are busy"); + return -EBUSY; + } + + active_members_store_ordered(members, count, set_info, true); + + for (uint8_t i = 0U; i < count; i++) + { + struct bt_csip_set_coordinator_svc_inst *svc_inst; + + svc_inst = lookup_instance_by_set_info(active.members[i], + &active.info); + if (svc_inst == NULL) + { + LOG_DBG("Failed to lookup instance by set_info"); + + active_members_reset(); + return -ENOENT; + } + + if (svc_inst->set_info->lockable) + { + err = csip_set_coordinator_read_set_lock(svc_inst); + if (err == 0) + { + active.in_progress = true; + } + + break; + } + + active.members_handled++; + } + + if (!active.in_progress && err == 0) + { + /* We are not reading any lock states (because they are not on the + * remote devices), so we can just initiate the ordered access + * procedure (oap) callback directly here. + */ + + if (active.oap_cb == NULL || + !active.oap_cb(&active.info, active.members, active.members_count)) + { + err = -ECANCELED; + } + + ordered_access_complete(&active.info, err, false, NULL); + } + + return err; +} + +int bt_csip_set_coordinator_ordered_access( + const struct bt_csip_set_coordinator_set_member *members[], uint8_t count, + const struct bt_csip_set_coordinator_set_info *set_info, + bt_csip_set_coordinator_ordered_access_t cb) +{ + int err; + + /* wait for the get_lock_state to finish and then call the callback */ + + active.oap_cb = cb; + + err = csip_set_coordinator_get_lock_state(members, count, set_info); + if (err != 0) + { + active.oap_cb = NULL; + + return err; + } + + return 0; +} + +/* As per CSIP, locking and releasing sets can only be done by bonded + * devices, so it does not makes sense to have these functions available if + * we do not support bonding + */ +#if defined(CONFIG_BT_BONDABLE) +static bool all_members_bonded( + const struct bt_csip_set_coordinator_set_member *members[], size_t count) +{ + for (size_t i = 0U; i < count; i++) + { + const struct bt_csip_set_coordinator_set_member *member = members[i]; + const struct bt_csip_set_coordinator_inst *client = + CONTAINER_OF(member, + struct bt_csip_set_coordinator_inst, + set_member); + struct bt_conn_info info; + int err; + + err = bt_conn_get_info(client->conn, &info); + if (err != 0 || !bt_le_bond_exists(info.id, info.le.dst)) + { + LOG_DBG("Member[%zu] is not bonded", i); + + return false; + } + } + + return true; +} + +int bt_csip_set_coordinator_lock( + const struct bt_csip_set_coordinator_set_member **members, uint8_t count, + const struct bt_csip_set_coordinator_set_info *set_info) +{ + struct bt_csip_set_coordinator_svc_inst *svc_inst; + int err; + + if (active.in_progress) + { + LOG_DBG("Procedure in progress"); + return -EBUSY; + } + + err = verify_members(members, count, set_info); + if (err != 0) + { + LOG_DBG("Could not verify members: %d", err); + return err; + } + + if (!all_members_bonded(members, count)) + { + return -EINVAL; + } + + if (!check_and_set_members_busy(members, count)) + { + LOG_DBG("One or more members are busy"); + return -EBUSY; + } + + active_members_store_ordered(members, count, set_info, true); + + svc_inst = lookup_instance_by_set_info(active.members[0], &active.info); + if (svc_inst == NULL) + { + LOG_DBG("Failed to lookup instance by set_info"); + + active_members_reset(); + return -ENOENT; + } + + err = csip_set_coordinator_write_set_lock(svc_inst, true, + csip_set_coordinator_write_lock_cb); + if (err == 0) + { + active.in_progress = true; + } + + return err; +} + +int bt_csip_set_coordinator_release( + const struct bt_csip_set_coordinator_set_member **members, uint8_t count, + const struct bt_csip_set_coordinator_set_info *set_info) +{ + struct bt_csip_set_coordinator_svc_inst *svc_inst; + int err; + + if (active.in_progress) + { + LOG_DBG("Procedure in progress"); + return -EBUSY; + } + + err = verify_members(members, count, set_info); + if (err != 0) + { + LOG_DBG("Could not verify members: %d", err); + return err; + } + + if (!all_members_bonded(members, count)) + { + return -EINVAL; + } + + if (!check_and_set_members_busy(members, count)) + { + LOG_DBG("One or more members are busy"); + return -EBUSY; + } + + active_members_store_ordered(members, count, set_info, false); + + svc_inst = lookup_instance_by_set_info(active.members[0], &active.info); + if (svc_inst == NULL) + { + LOG_DBG("Failed to lookup instance by set_info"); + + active_members_reset(); + return -ENOENT; + } + + err = csip_set_coordinator_write_set_lock(svc_inst, false, + csip_set_coordinator_write_release_cb); + if (err == 0) + { + active.in_progress = true; + } + + return err; +} +#endif /* CONFIG_BT_BONDABLE */ diff --git a/libs/zblue/audio/csip_set_member.c b/libs/zblue/audio/csip_set_member.c new file mode 100644 index 0000000000000..5369cacd401bf --- /dev/null +++ b/libs/zblue/audio/csip_set_member.c @@ -0,0 +1,1709 @@ +/**************************************************************************** + * libs/zblue/audio/csip_set_member.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Bluetooth CSIP - Coordinated Set Identification Profile */ + +/* Copyright (c) 2019 Bose Corporation + * Copyright (c) 2020-2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../host/conn_internal.h" +#include "../host/keys.h" +#include "../host/settings.h" + +#include "common/bt_settings_commit.h" +#include "common/bt_str.h" +#include "audio_internal.h" +#include "csip_internal.h" +#include "csip_crypto.h" + +#define CSIP_SET_LOCK_TIMER_VALUE SEC2TICK(60) + +#define CSIS_CHAR_ATTR_COUNT 3 /* declaration + value + cccd */ +#define CSIS_RANK_CHAR_ATTR_COUNT 2 /* declaration + value */ + +#define CSIS_MAX_ATTR \ + (3U + IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_SIRK_NOTIFIABLE) + \ + (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_SIZE_SUPPORT) ? 2U : 0U) + \ + IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_SIZE_NOTIFIABLE) + \ + (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT) ? 3U : 0U) + \ + (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_RANK_SUPPORT) ? 2U : 0U)) + +LOG_MODULE_REGISTER(bt_csip_set_member, CONFIG_BT_CSIP_SET_MEMBER_LOG_LEVEL); + +enum csip_flag +{ + FLAG_ACTIVE, + FLAG_NOTIFY_LOCK, + FLAG_NOTIFY_SIRK, + FLAG_NOTIFY_SIZE, + FLAG_NUM, +}; + +struct csip_client +{ + bt_addr_le_t addr; + + /* Pending notification flags */ + + atomic_define(flags, FLAG_NUM); +}; + +struct bt_csip_set_member_svc_inst +{ + struct bt_csip_sirk sirk; +#if defined(CONFIG_BT_CSIP_SET_MEMBER_SIZE_SUPPORT) + uint8_t set_size; +#endif /* CONFIG_BT_CSIP_SET_MEMBER_SIZE_SUPPORT */ +#if defined(CONFIG_BT_CSIP_SET_MEMBER_RANK_SUPPORT) + uint8_t rank; +#endif /* CONFIG_BT_CSIP_SET_MEMBER_RANK_SUPPORT */ +#if defined(CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT) + uint8_t set_lock; + bool lockable; + struct k_work_delayable set_lock_timer; + bt_addr_le_t lock_client_addr; +#endif /* CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT */ + struct bt_csip_set_member_cb *cb; + struct csip_client clients[CONFIG_BT_MAX_PAIRED]; + + struct bt_gatt_service gatt_svc; + struct bt_gatt_attr attrs[CSIS_MAX_ATTR]; + + /* Must be last: exclude from memset during unregister */ + + rmutex_t mutex; +}; + +static struct bt_csip_set_member_svc_inst + svc_insts[CONFIG_BT_CSIP_SET_MEMBER_MAX_INSTANCE_COUNT]; + +static void deferred_nfy_work_handler(struct k_work *work); +static void add_bonded_addr_to_client_list(const struct bt_bond_info *info, + void *data); + +#if defined(CONFIG_BT_SETTINGS) + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int csip_settings_commit(void) +{ + bt_foreach_bond(BT_ID_DEFAULT, add_bonded_addr_to_client_list, NULL); + + LOG_DBG("Restored CSIP client list from bonded devices"); + + return 0; +} + +/* Register CSIP settings handler with commit priority, BT_SETTINGS_CPRIO_2, + * to ensure csip_settings_commit() runs after BT keys settings are loaded. + * Priority is reduced to ensure existing bonds are loaded first. + */ + +SETTINGS_STATIC_HANDLER_DEFINE_WITH_CPRIO(bt_csip_set_member, + "bt/csip", + NULL, + NULL, + + csip_settings_commit, + NULL, + BT_SETTINGS_CPRIO_2); +#endif /* CONFIG_BT_SETTINGS */ + +static K_WORK_DELAYABLE_DEFINE(deferred_nfy_work, deferred_nfy_work_handler); + +#if defined(CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT) +static bool is_last_client_to_write( + const struct bt_csip_set_member_svc_inst *svc_inst, + const struct bt_conn *conn) +{ + if (conn != NULL) + { + return bt_addr_le_eq(bt_conn_get_dst(conn), + &svc_inst->lock_client_addr); + } + else + { + return bt_addr_le_eq(BT_ADDR_LE_NONE, &svc_inst->lock_client_addr); + } +} +#endif /* CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT */ + +static void notify_work_reschedule(clock_t delay) +{ + int err; + + /* If it is already scheduled, don't reschedule */ + + if (k_work_delayable_remaining_get(&deferred_nfy_work) > 0) + { + return; + } + + err = k_work_reschedule(&deferred_nfy_work, delay); + if (err < 0) + { + LOG_ERR("Failed to reschedule notification work err %d", err); + } +} + +static void notify_clients(struct bt_csip_set_member_svc_inst *svc_inst, + struct bt_conn *excluded_client, + enum csip_flag flag) +{ + bool submit_work = false; + + /* Mark all bonded devices (except the excluded one) as pending + * notifications + */ + + for (size_t i = 0U; i < ARRAY_SIZE(svc_inst->clients); i++) + { + struct csip_client *client; + + client = &svc_inst->clients[i]; + + if (atomic_test_bit(client->flags, FLAG_ACTIVE)) + { + if (excluded_client != NULL && + bt_addr_le_eq(bt_conn_get_dst(excluded_client), &client->addr)) + { + continue; + } + + atomic_set_bit(client->flags, flag); + submit_work = true; + } + } + + /* Reschedule work for notifying */ + + if (submit_work) + { + notify_work_reschedule(0); + } +} + +static int sirk_encrypt( + struct bt_conn *conn, const struct bt_csip_sirk *sirk, + struct bt_csip_sirk *enc_sirk) +{ + int err; + const uint8_t *k; + + if (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_TEST_SAMPLE_DATA)) + { + /* test_k is from the sample data from A.2 in the CSIS spec */ + + static const uint8_t test_k[] = + { + /* Sample data is in big-endian, we need it in little-endian. */ + + REVERSE_ARGS(0x67u, + 0x6eu, + 0x1bu, + 0x9bu, + 0xd4u, + 0x48u, + 0x69u, + 0x6fu, + 0x06u, + 0x1eu, + 0xc6u, 0x22u, 0x3cu, 0xe5u, 0xceu, 0xd9u) + }; + + LOG_DBG("Encrypting test SIRK"); + k = test_k; + } + else + { + if (conn == NULL) + { + return -EINVAL; + } + + k = conn->le.keys->ltk.val; + } + + err = bt_csip_sef(k, sirk->value, enc_sirk->value); + + if (err != 0) + { + return err; + } + + enc_sirk->type = BT_CSIP_SIRK_TYPE_ENCRYPTED; + + return 0; +} + +static int generate_prand(uint8_t dest[BT_CSIP_CRYPTO_PRAND_SIZE]) +{ + bool valid = false; + + do + { + int res; + uint32_t prand; + + *dest = 0; + res = bt_rand(dest, BT_CSIP_CRYPTO_PRAND_SIZE); + if (res != 0) + { + return res; + } + + /* Validate Prand: Must contain both a 1 and a 0 */ + + prand = sys_get_le24(dest); + if (prand != 0U && prand != 0x3fffffu) + { + valid = true; + } + } + while (!valid); + + dest[BT_CSIP_CRYPTO_PRAND_SIZE - 1] &= 0x3fu; + dest[BT_CSIP_CRYPTO_PRAND_SIZE - 1] |= BIT(6U); + + return 0; +} + +int bt_csip_set_member_generate_rsi( + const struct bt_csip_set_member_svc_inst *svc_inst, + uint8_t rsi[BT_CSIP_RSI_SIZE]) +{ + int res = 0; + uint8_t prand[BT_CSIP_CRYPTO_PRAND_SIZE]; + uint8_t hash[BT_CSIP_CRYPTO_HASH_SIZE]; + + if (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_TEST_SAMPLE_DATA)) + { + /* prand is from the sample data from A.2 in the CSIS spec */ + + sys_put_le24(0x69f563, prand); + } + else + { + res = generate_prand(prand); + + if (res != 0) + { + LOG_WRN("Could not generate new prand"); + return res; + } + } + + res = bt_csip_sih(svc_inst->sirk.value, prand, hash); + if (res != 0) + { + LOG_WRN("Could not generate new RSI"); + return res; + } + + (void)memcpy(rsi, hash, BT_CSIP_CRYPTO_HASH_SIZE); + (void)memcpy(rsi + BT_CSIP_CRYPTO_HASH_SIZE, prand, + BT_CSIP_CRYPTO_PRAND_SIZE); + + return res; +} + +static ssize_t read_sirk( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct bt_csip_sirk enc_sirk; + struct bt_csip_sirk *sirk; + struct bt_csip_set_member_svc_inst *svc_inst = + BT_AUDIO_CHRC_USER_DATA(attr); + + if (svc_inst->cb != NULL && svc_inst->cb->sirk_read_req != NULL) + { + ssize_t gatt_err = BT_GATT_ERR(BT_ATT_ERR_SUCCESS); + uint8_t cb_rsp; + + /* Ask higher layer for what SIRK to return, if any */ + + cb_rsp = svc_inst->cb->sirk_read_req(conn, &svc_insts[0]); + + if (cb_rsp == BT_CSIP_READ_SIRK_REQ_RSP_ACCEPT) + { + sirk = &svc_inst->sirk; + } + else if (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_ENC_SIRK_SUPPORT) && + cb_rsp == BT_CSIP_READ_SIRK_REQ_RSP_ACCEPT_ENC) + { + int err; + + err = sirk_encrypt(conn, &svc_inst->sirk, &enc_sirk); + if (err != 0) + { + LOG_ERR("Could not encrypt SIRK: %d", err); + gatt_err = BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + else + { + sirk = &enc_sirk; + LOG_HEXDUMP_DBG(enc_sirk.value, sizeof(enc_sirk.value), + "Encrypted SIRK"); + } + } + else if (cb_rsp == BT_CSIP_READ_SIRK_REQ_RSP_REJECT) + { + gatt_err = BT_GATT_ERR(BT_ATT_ERR_AUTHORIZATION); + } + else if (cb_rsp == BT_CSIP_READ_SIRK_REQ_RSP_OOB_ONLY) + { + gatt_err = BT_GATT_ERR(BT_CSIP_ERROR_SIRK_OOB_ONLY); + } + else + { + LOG_ERR("Invalid callback response: %u", cb_rsp); + gatt_err = BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + if (gatt_err != BT_GATT_ERR(BT_ATT_ERR_SUCCESS)) + { + return gatt_err; + } + } + else + { + sirk = &svc_inst->sirk; + } + + LOG_DBG("SIRK %sencrypted", + sirk->type == BT_CSIP_SIRK_TYPE_PLAIN ? "not " : ""); + LOG_HEXDUMP_DBG(svc_inst->sirk.value, + sizeof(svc_inst->sirk.value), + "SIRK"); + return bt_gatt_attr_read(conn, + attr, + buf, + len, + offset, + sirk, + sizeof(*sirk)); +} + +static void sirk_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); +} + +static ssize_t read_set_size( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ +#if defined(CONFIG_BT_CSIP_SET_MEMBER_SIZE_SUPPORT) + struct bt_csip_set_member_svc_inst *svc_inst = + BT_AUDIO_CHRC_USER_DATA(attr); + + LOG_DBG("%u", svc_inst->set_size); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &svc_inst->set_size, + sizeof(svc_inst->set_size)); +#else + ARG_UNUSED(conn); + ARG_UNUSED(attr); + ARG_UNUSED(buf); + ARG_UNUSED(len); + ARG_UNUSED(offset); + + DEBUGASSERT(false); + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); +#endif /* CONFIG_BT_CSIP_SET_MEMBER_SIZE_SUPPORT */ +} + +static void set_size_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); +} + +static ssize_t read_set_lock( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ +#if defined(CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT) + struct bt_csip_set_member_svc_inst *svc_inst = + BT_AUDIO_CHRC_USER_DATA(attr); + + LOG_DBG("%u", svc_inst->set_lock); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &svc_inst->set_lock, + sizeof(svc_inst->set_lock)); +#else + ARG_UNUSED(conn); + ARG_UNUSED(attr); + ARG_UNUSED(buf); + ARG_UNUSED(len); + ARG_UNUSED(offset); + + DEBUGASSERT(false); + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); +#endif /* CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT */ +} + +#if defined(CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT) +/** + * @brief Set the lock value of a CSIP instance. + * + * @param conn The connection locking the instance. Will be NULL if the + * server locally sets the lock. + * @param svc_inst The CSIP instance to change the lock value of + * @param val The lock value (BT_CSIP_LOCK_VALUE or + * BT_CSIP_RELEASE_VALUE) + * + * @return BT_CSIP_ERROR_* on failure or 0 if success + */ + +static uint8_t set_lock( + struct bt_conn *conn, struct bt_csip_set_member_svc_inst *svc_inst, + uint8_t val) +{ + bool notify; + + if (val != BT_CSIP_RELEASE_VALUE && val != BT_CSIP_LOCK_VALUE) + { + return BT_CSIP_ERROR_LOCK_INVAL_VALUE; + } + + if (svc_inst->set_lock == BT_CSIP_LOCK_VALUE) + { + if (val == BT_CSIP_LOCK_VALUE) + { + if (is_last_client_to_write(svc_inst, conn)) + { + return BT_CSIP_ERROR_LOCK_ALREADY_GRANTED; + } + else + { + return BT_CSIP_ERROR_LOCK_DENIED; + } + } + else if (!is_last_client_to_write(svc_inst, conn)) + { + return BT_CSIP_ERROR_LOCK_RELEASE_DENIED; + } + } + + notify = svc_inst->set_lock != val; + + svc_inst->set_lock = val; + if (svc_inst->set_lock == BT_CSIP_LOCK_VALUE) + { + if (conn != NULL) + { + bt_addr_le_copy(&svc_inst->lock_client_addr, + bt_conn_get_dst(conn)); + } + + (void)k_work_reschedule(&svc_inst->set_lock_timer, + CSIP_SET_LOCK_TIMER_VALUE); + } + else + { + bt_addr_le_copy(&svc_inst->lock_client_addr, BT_ADDR_LE_NONE); + (void)k_work_cancel_delayable(&svc_inst->set_lock_timer); + } + + LOG_DBG("%u", svc_inst->set_lock); + + if (notify) + { + /* The Spec states that all clients, except for the + * client writing the value, shall be notified (if subscribed) + */ + + notify_clients(svc_inst, conn, FLAG_NOTIFY_LOCK); + + if (svc_inst->cb != NULL && svc_inst->cb->lock_changed != NULL) + { + bool locked = svc_inst->set_lock == BT_CSIP_LOCK_VALUE; + + svc_inst->cb->lock_changed(conn, svc_inst, locked); + } + } + + return 0; +} +#endif /* CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT */ + +static ssize_t write_set_lock( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + ARG_UNUSED(flags); + +#if defined(CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT) + ssize_t res; + uint8_t val; + struct bt_csip_set_member_svc_inst *svc_inst = + BT_AUDIO_CHRC_USER_DATA(attr); + + if (offset != 0) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + else if (len != sizeof(val)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + (void)memcpy(&val, buf, len); + + res = set_lock(conn, svc_inst, val); + if (res != BT_ATT_ERR_SUCCESS) + { + return BT_GATT_ERR(res); + } + + return len; +#else + ARG_UNUSED(conn); + ARG_UNUSED(attr); + ARG_UNUSED(buf); + ARG_UNUSED(len); + ARG_UNUSED(offset); + + DEBUGASSERT(false); + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); +#endif /* CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT */ +} + +static void set_lock_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); +} + +static ssize_t read_rank( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ +#if defined(CONFIG_BT_CSIP_SET_MEMBER_RANK_SUPPORT) + struct bt_csip_set_member_svc_inst *svc_inst = + BT_AUDIO_CHRC_USER_DATA(attr); + + LOG_DBG("%u", svc_inst->rank); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &svc_inst->rank, + sizeof(svc_inst->rank)); + +#else + ARG_UNUSED(conn); + ARG_UNUSED(attr); + ARG_UNUSED(buf); + ARG_UNUSED(len); + ARG_UNUSED(offset); + + DEBUGASSERT(false); + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); +#endif /* CONFIG_BT_CSIP_SET_MEMBER_RANK_SUPPORT */ +} + +#if defined(CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT) +static void set_lock_timer_handler(struct k_work *work) +{ + struct k_work_delayable *delayable; + struct bt_csip_set_member_svc_inst *svc_inst; + + delayable = k_work_delayable_from_work(work); + svc_inst = CONTAINER_OF(delayable, struct bt_csip_set_member_svc_inst, + set_lock_timer); + + LOG_DBG("Lock timeout, releasing"); + svc_inst->set_lock = BT_CSIP_RELEASE_VALUE; + notify_clients(svc_inst, NULL, FLAG_NOTIFY_LOCK); + + if (svc_inst->cb != NULL && svc_inst->cb->lock_changed != NULL) + { + bool locked = svc_inst->set_lock == BT_CSIP_LOCK_VALUE; + + svc_inst->cb->lock_changed(NULL, svc_inst, locked); + } +} +#endif /* CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT */ + +static void csip_security_changed(struct bt_conn *conn, bt_security_t level, + enum bt_security_err err) +{ + const bt_addr_le_t *peer_addr; + + ARG_UNUSED(level); + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE) || err != 0 || + conn->encrypt == 0) + { + return; + } + + peer_addr = bt_conn_get_dst(conn); + + if (!bt_le_bond_exists(conn->id, &conn->le.dst)) + { + return; + } + + for (size_t i = 0U; i < ARRAY_SIZE(svc_insts); i++) + { + struct bt_csip_set_member_svc_inst *svc_inst = &svc_insts[i]; + struct csip_client *client; + bool found = false; + + /* Check if client is already in the active list */ + + for (size_t j = 0U; j < ARRAY_SIZE(svc_inst->clients); j++) + { + client = &svc_inst->clients[j]; + + if (atomic_test_bit(client->flags, FLAG_ACTIVE) && + bt_addr_le_eq(peer_addr, &client->addr)) + { + found = true; + break; + } + } + + /* If not found, add the bonded address to the client list */ + + if (!found) + { + const struct bt_bond_info bond_info = + { + .addr = *peer_addr + }; + + add_bonded_addr_to_client_list(&bond_info, NULL); + return; + } + + /* Check if client has pending notifications */ + + if (atomic_test_bit(client->flags, FLAG_NOTIFY_LOCK) || + atomic_test_bit(client->flags, FLAG_NOTIFY_SIRK) || + atomic_test_bit(client->flags, FLAG_NOTIFY_SIZE)) + { + notify_work_reschedule(0); + break; + } + } +} + +static void handle_csip_disconnect( + struct bt_csip_set_member_svc_inst *svc_inst, struct bt_conn *conn) +{ + LOG_DBG("Non-bonded device"); +#if defined(CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT) + if (is_last_client_to_write(svc_inst, conn)) + { + bt_addr_le_copy(&svc_inst->lock_client_addr, BT_ADDR_LE_NONE); + svc_inst->set_lock = BT_CSIP_RELEASE_VALUE; + notify_clients(svc_inst, NULL, FLAG_NOTIFY_LOCK); + + if (svc_inst->cb != NULL && svc_inst->cb->lock_changed != NULL) + { + bool locked = svc_inst->set_lock == BT_CSIP_LOCK_VALUE; + + svc_inst->cb->lock_changed(conn, svc_inst, locked); + } + } +#endif /* CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT */ + + /* Check if the disconnected device once was bonded and stored + * here as a bonded device + */ + + for (size_t i = 0U; i < ARRAY_SIZE(svc_inst->clients); i++) + { + struct csip_client *client; + + client = &svc_inst->clients[i]; + + if (bt_addr_le_eq(bt_conn_get_dst(conn), &client->addr)) + { + (void)memset(client, 0, sizeof(*client)); + break; + } + } +} + +static void csip_disconnected(struct bt_conn *conn, uint8_t reason) +{ + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + LOG_DBG("Disconnected: %s (reason %u)", bt_conn_dst_str(conn), reason); + + if (!bt_le_bond_exists(conn->id, &conn->le.dst)) + { + for (size_t i = 0U; i < ARRAY_SIZE(svc_insts); i++) + { + handle_csip_disconnect(&svc_insts[i], conn); + } + } +} + +static void handle_csip_auth_complete( + struct bt_csip_set_member_svc_inst *svc_inst, struct bt_conn *conn) +{ + /* Check if already in list, and do nothing if it is */ + + for (size_t i = 0U; i < ARRAY_SIZE(svc_inst->clients); i++) + { + struct csip_client *client; + + client = &svc_inst->clients[i]; + + if (atomic_test_bit(client->flags, FLAG_ACTIVE) && + bt_addr_le_eq(bt_conn_get_dst(conn), &client->addr)) + { + return; + } + } + + /* Else add the device */ + + for (size_t i = 0U; i < ARRAY_SIZE(svc_inst->clients); i++) + { + struct csip_client *client; + + client = &svc_inst->clients[i]; + + if (!atomic_test_bit(client->flags, FLAG_ACTIVE)) + { + atomic_set_bit(client->flags, FLAG_ACTIVE); + memcpy(&client->addr, bt_conn_get_dst(conn), sizeof(bt_addr_le_t)); + + /* Send out all pending notifications */ + + notify_work_reschedule(0); + return; + } + } + + LOG_WRN("Could not add device to pending notification list"); +} + +static void auth_pairing_complete(struct bt_conn *conn, bool bonded) +{ + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + +/* If a pairing is complete for a bonded device, then we 1) Store the + * connection pointer to later validate SIRK encryption 2) Check if the + * device is already in the `clients`, and if it is not, then we 3) Check + * if there's room for another device in the `clients` array. If there are + * no more room for a new device, then 4) Either we ignore this new device + * (bad luck), or we overwrite the oldest entry, following the behavior of + * the key storage. + */ + + LOG_DBG("%s paired (%sbonded)", bt_conn_dst_str(conn), + bonded ? "" : "not "); + + if (!bonded) + { + return; + } + + for (size_t i = 0U; i < ARRAY_SIZE(svc_insts); i++) + { + handle_csip_auth_complete(&svc_insts[i], conn); + } +} + +static void csip_bond_deleted(uint8_t id, const bt_addr_le_t *peer) +{ + ARG_UNUSED(id); + + for (size_t i = 0U; i < ARRAY_SIZE(svc_insts); i++) + { + struct bt_csip_set_member_svc_inst *svc_inst = &svc_insts[i]; + + for (size_t j = 0U; j < ARRAY_SIZE(svc_inst->clients); j++) + { + /* Check if match, and if active, if so, reset */ + + if (atomic_test_bit(svc_inst->clients[i].flags, FLAG_ACTIVE) && + bt_addr_le_eq(peer, &svc_inst->clients[i].addr)) + { + atomic_set(svc_inst->clients[i].flags, 0); + (void)memset(&svc_inst->clients[i].addr, 0, + sizeof(bt_addr_le_t)); + break; + } + } + } +} + +BT_CONN_CB_DEFINE(conn_callbacks) = +{ + .disconnected = csip_disconnected, + .security_changed = csip_security_changed, +}; + +static struct bt_conn_auth_info_cb auth_callbacks = +{ + .pairing_complete = auth_pairing_complete, + .bond_deleted = + csip_bond_deleted +}; + +/* Public Api */ + +void *bt_csip_set_member_svc_decl_get( + const struct bt_csip_set_member_svc_inst *svc_inst) +{ + if (svc_inst == NULL || svc_inst->gatt_svc.attrs == NULL) + { + return NULL; + } + + return svc_inst->gatt_svc.attrs; +} + +static bool valid_register_param( + const struct bt_csip_set_member_register_param *param) +{ + if (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT) && + param->lockable && + param->rank == 0U) + { + LOG_DBG("Rank cannot be 0 if service is lockable"); + return false; + } + + if (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_SIZE_SUPPORT) && + IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_RANK_SUPPORT) && + param->rank > 0U && + param->set_size > 0U && param->rank > param->set_size) + { + LOG_DBG("Invalid rank: %u (" + "shall be less than or equal to set_size: %u)", + param->rank, + param->set_size); + return false; + } + +#if CONFIG_BT_CSIP_SET_MEMBER_MAX_INSTANCE_COUNT > 1 + if (param->parent == NULL) + { + LOG_DBG("Parent service not provided"); + return false; + } +#endif /* CONFIG_BT_CSIP_SET_MEMBER_MAX_INSTANCE_COUNT > 1 */ + + return true; +} + +static void notify( + struct bt_csip_set_member_svc_inst *svc_inst, struct bt_conn *conn, + const struct bt_uuid *uuid, const void *data, uint16_t len) +{ + int err; + const struct bt_gatt_attr *attr; + + attr = bt_gatt_find_by_uuid(svc_inst->gatt_svc.attrs, + svc_inst->gatt_svc.attr_count, uuid); + + if (attr == NULL) + { + LOG_WRN("Attribute for UUID %p not found", uuid); + return; + } + + if (!bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_NOTIFY)) + { + LOG_DBG("Connection %p not subscribed to UUID %p", conn, uuid); + return; + } + + err = bt_gatt_notify(conn, attr, data, len); + if (err != 0) + { + if (err == -ENOTCONN) + { + LOG_DBG("Notification error: ENOTCONN (%d)", err); + } + else + { + LOG_ERR("Notification error: %d", err); + } + } +} + +static void notify_cb(struct bt_conn *conn, void *data) +{ + struct bt_conn_info info; + int err = 0; + + ARG_UNUSED(data); + + err = bt_conn_get_info(conn, &info); + if (err != 0) + { + return; + } + + if (info.state != BT_CONN_STATE_CONNECTED) + { + /* Not connected */ + + LOG_DBG("Not connected: %u", info.state); + return; + } + + for (size_t i = 0U; i < ARRAY_SIZE(svc_insts); i++) + { + struct bt_csip_set_member_svc_inst *svc_inst = &svc_insts[i]; + struct csip_client *client; + bool client_found = false; + + err = nxrmutex_trylock(&svc_inst->mutex); + if (err != 0) + { + LOG_DBG("Mutex lock failed (%d) for svc_inst[%zu], rescheduling", + err, i); + notify_work_reschedule(USEC2TICK(BT_AUDIO_NOTIFY_RETRY_DELAY_US)); + continue; + } + + if (svc_inst->gatt_svc.attrs == NULL) + { + goto unlock_and_return; + } + + /* find the client object for the connection */ + + for (size_t j = 0U; j < ARRAY_SIZE(svc_inst->clients); j++) + { + client = &svc_inst->clients[j]; + + if (atomic_test_bit(client->flags, FLAG_ACTIVE) && + bt_addr_le_eq(bt_conn_get_dst(conn), &client->addr)) + { + client_found = true; + break; + } + } + + if (client_found == false) + { + goto unlock_and_return; + } + +#if defined(CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT) + if (atomic_test_and_clear_bit(client->flags, FLAG_NOTIFY_LOCK)) + { + notify(svc_inst, conn, BT_UUID_CSIS_SET_LOCK, &svc_inst->set_lock, + sizeof(svc_inst->set_lock)); + } +#endif /* CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT */ + + if (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_SIRK_NOTIFIABLE) && + atomic_test_and_clear_bit(client->flags, FLAG_NOTIFY_SIRK)) + { + notify(svc_inst, conn, BT_UUID_CSIS_SIRK, &svc_inst->sirk, + sizeof(svc_inst->sirk)); + } +#if defined(CONFIG_BT_CSIP_SET_MEMBER_SIZE_SUPPORT) + + if (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_SIZE_NOTIFIABLE) && + atomic_test_and_clear_bit(client->flags, FLAG_NOTIFY_SIZE)) + { + notify(svc_inst, conn, BT_UUID_CSIS_SET_SIZE, &svc_inst->set_size, + sizeof(svc_inst->set_size)); + } +#endif /* CONFIG_BT_CSIP_SET_MEMBER_SIZE_SUPPORT */ + +unlock_and_return: + err = nxrmutex_unlock(&svc_inst->mutex); + DEBUGASSERT(err == 0); + } +} + +static void deferred_nfy_work_handler(struct k_work *work) +{ + ARG_UNUSED(work); + + bt_conn_foreach(BT_CONN_TYPE_LE, notify_cb, NULL); +} + +static void add_bonded_addr_to_client_list(const struct bt_bond_info *info, + void *data) +{ + ARG_UNUSED(data); + + for (size_t i = 0U; i < ARRAY_SIZE(svc_insts); i++) + { + struct bt_csip_set_member_svc_inst *svc_inst = &svc_insts[i]; + + for (size_t j = 0U; j < ARRAY_SIZE(svc_inst->clients); j++) + { + /* Check if device is registered, it not, add it */ + + if (!atomic_test_and_set_bit(svc_inst->clients[j].flags, + FLAG_ACTIVE)) + { + memcpy(&svc_inst->clients[j].addr, &info->addr, + sizeof(bt_addr_le_t)); + LOG_DBG("Added %s to bonded list\n", + bt_addr_le_str(&svc_inst->clients[j].addr)); + break; + } + } + } +} + +/* Set sirk */ + +static const struct bt_gatt_chrc set_sirk_user_data = BT_GATT_CHRC_INIT( + BT_UUID_CSIS_SIRK, 0U, + BT_GATT_CHRC_READ | + (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_SIRK_NOTIFIABLE) ? + BT_GATT_CHRC_NOTIFY : 0U)); + +#define CSIS_SET_SIRK_AUDIO_USER_DATA(_n, ...) \ + (struct bt_audio_attr_user_data) BT_AUDIO_ATTR_USER_DATA_INIT(read_sirk, NULL, &svc_insts[(_n)]) + +static const struct bt_audio_attr_user_data set_sirk_audio_user_data[] = +{ + LISTIFY(CONFIG_BT_CSIP_SET_MEMBER_MAX_INSTANCE_COUNT, + CSIS_SET_SIRK_AUDIO_USER_DATA, + (, + + )), +}; + +#define CSIS_SET_SIRK_AUDIO_CCC_USER_DATA(_n, ...) \ + (struct bt_gatt_ccc_managed_user_data) \ + BT_GATT_CCC_MANAGED_USER_DATA_INIT(sirk_cfg_changed, bt_audio_ccc_cfg_write, NULL) + +/* Unlike the other user_data used for CSIP this + * one needs to be in RAM as it contains the `value` written by clients + */ + +static struct bt_gatt_ccc_managed_user_data set_sirk_audio_ccc_user_data[] = +{ + LISTIFY(CONFIG_BT_CSIP_SET_MEMBER_MAX_INSTANCE_COUNT, + CSIS_SET_SIRK_AUDIO_CCC_USER_DATA, + + (, + )), +}; + +/* Set size */ + +static const struct bt_gatt_chrc set_size_user_data = BT_GATT_CHRC_INIT( + BT_UUID_CSIS_SET_SIZE, 0U, + BT_GATT_CHRC_READ | + (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_SIZE_NOTIFIABLE) ? + BT_GATT_CHRC_NOTIFY : 0U)); + +#define CSIS_SET_SIZE_AUDIO_USER_DATA(_n, ...) \ + (struct bt_audio_attr_user_data) \ + BT_AUDIO_ATTR_USER_DATA_INIT(read_set_size, NULL, &svc_insts[(_n)]) + +static const struct bt_audio_attr_user_data set_size_audio_user_data[] = +{ + LISTIFY(CONFIG_BT_CSIP_SET_MEMBER_MAX_INSTANCE_COUNT, + CSIS_SET_SIZE_AUDIO_USER_DATA, + (, + + )), +}; + +#define CSIS_SET_SIZE_AUDIO_CCC_USER_DATA(_n, ...) \ + (struct bt_gatt_ccc_managed_user_data) \ + BT_GATT_CCC_MANAGED_USER_DATA_INIT(set_size_cfg_changed, bt_audio_ccc_cfg_write, NULL) + +/* Unlike the other user_data used for CSIP this + * one needs to be in RAM as it contains the `value` written by clients + */ + +static struct bt_gatt_ccc_managed_user_data set_size_audio_ccc_user_data[] = +{ + LISTIFY(CONFIG_BT_CSIP_SET_MEMBER_MAX_INSTANCE_COUNT, + CSIS_SET_SIZE_AUDIO_CCC_USER_DATA, + + (, + )), +}; + +/* Set lock */ + +static const struct bt_gatt_chrc set_lock_user_data = BT_GATT_CHRC_INIT( + BT_UUID_CSIS_SET_LOCK, + 0U, + BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | BT_GATT_CHRC_NOTIFY); + +#define CSIS_SET_LOCK_AUDIO_USER_DATA(_n, ...) \ + (struct bt_audio_attr_user_data) \ + BT_AUDIO_ATTR_USER_DATA_INIT(read_set_lock, write_set_lock, &svc_insts[(_n)]) + +static const struct bt_audio_attr_user_data set_lock_audio_user_data[] = +{ + LISTIFY(CONFIG_BT_CSIP_SET_MEMBER_MAX_INSTANCE_COUNT, + CSIS_SET_LOCK_AUDIO_USER_DATA, + (, + + )), +}; + +#define CSIS_SET_LOCK_AUDIO_CCC_USER_DATA(_n, ...) \ + (struct bt_gatt_ccc_managed_user_data) \ + BT_GATT_CCC_MANAGED_USER_DATA_INIT(set_lock_cfg_changed, bt_audio_ccc_cfg_write, NULL) + +/* Unlike the other user_data used for CSIP this + * one needs to be in RAM as it contains the `value` written by clients + */ + +static struct bt_gatt_ccc_managed_user_data set_lock_audio_ccc_user_data[] = +{ + LISTIFY(CONFIG_BT_CSIP_SET_MEMBER_MAX_INSTANCE_COUNT, + CSIS_SET_LOCK_AUDIO_CCC_USER_DATA, + + (, + )), +}; + +/* Rank */ + +static const struct bt_gatt_chrc rank_user_data = + BT_GATT_CHRC_INIT(BT_UUID_CSIS_RANK, 0U, BT_GATT_CHRC_READ); + +#define CSIS_RANK_AUDIO_USER_DATA(_n, ...) \ + (struct bt_audio_attr_user_data) BT_AUDIO_ATTR_USER_DATA_INIT(read_rank, NULL, &svc_insts[(_n)]) + +static const struct bt_audio_attr_user_data rank_audio_user_data[] = +{ + LISTIFY(CONFIG_BT_CSIP_SET_MEMBER_MAX_INSTANCE_COUNT, + CSIS_RANK_AUDIO_USER_DATA, + (, + + )), +}; + +static const struct bt_uuid *svc_uuid = BT_UUID_GATT_PRIMARY; +static const struct bt_uuid *ccc_uuid = BT_UUID_GATT_CCC; +static const struct bt_uuid *chrc_uuid = BT_UUID_GATT_CHRC; +static const struct bt_uuid *csis_uuid = BT_UUID_CSIS; +static const struct bt_uuid *sirk_uuid = BT_UUID_CSIS_SIRK; +static const struct bt_uuid *size_uuid = BT_UUID_CSIS_SET_SIZE; +static const struct bt_uuid *lock_uuid = BT_UUID_CSIS_SET_LOCK; +static const struct bt_uuid *rank_uuid = BT_UUID_CSIS_RANK; +static void instantiate_service(struct bt_csip_set_member_svc_inst *inst, + const struct bt_csip_set_member_register_param *param) +{ + const ptrdiff_t idx = ARRAY_INDEX(svc_insts, inst); + size_t attr_cnt = 0U; + + inst->gatt_svc.attrs = inst->attrs; + + /* Primary service */ + + inst->attrs[attr_cnt].uuid = svc_uuid; + inst->attrs[attr_cnt].perm = BT_GATT_PERM_READ; + inst->attrs[attr_cnt].read = bt_gatt_attr_read_service; + inst->attrs[attr_cnt].user_data = (void *)csis_uuid; + attr_cnt++; + + /* Set SIRK declaration */ + + inst->attrs[attr_cnt].uuid = chrc_uuid; + inst->attrs[attr_cnt].perm = BT_GATT_PERM_READ; + inst->attrs[attr_cnt].read = bt_gatt_attr_read_chrc; + inst->attrs[attr_cnt].user_data = (void *)&set_sirk_user_data; + attr_cnt++; + + /* Set SIRK value */ + + inst->attrs[attr_cnt].uuid = sirk_uuid; + inst->attrs[attr_cnt].perm = BT_GATT_PERM_READ_ENCRYPT; + inst->attrs[attr_cnt].read = bt_audio_read_chrc; + inst->attrs[attr_cnt].user_data = (void *)&set_sirk_audio_user_data[idx]; + attr_cnt++; + + if (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_SIRK_NOTIFIABLE)) + { + /* Set SIRK CCCD */ + + inst->attrs[attr_cnt].uuid = ccc_uuid; + inst->attrs[attr_cnt].perm = + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT; + inst->attrs[attr_cnt].read = bt_gatt_attr_read_ccc; + inst->attrs[attr_cnt].write = bt_gatt_attr_write_ccc; + inst->attrs[attr_cnt].user_data = + (void *)&set_sirk_audio_ccc_user_data[idx]; + attr_cnt++; + } + + if (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_SIZE_SUPPORT) && + param->set_size > 0U) + { + /* Set Size declaration */ + + inst->attrs[attr_cnt].uuid = chrc_uuid; + inst->attrs[attr_cnt].perm = BT_GATT_PERM_READ; + inst->attrs[attr_cnt].read = bt_gatt_attr_read_chrc; + inst->attrs[attr_cnt].user_data = (void *)&set_size_user_data; + attr_cnt++; + + /* Set Size value */ + + inst->attrs[attr_cnt].uuid = size_uuid; + inst->attrs[attr_cnt].perm = BT_GATT_PERM_READ_ENCRYPT; + inst->attrs[attr_cnt].read = bt_audio_read_chrc; + inst->attrs[attr_cnt].user_data = + (void *)&set_size_audio_user_data[idx]; + attr_cnt++; + + if (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_SIZE_NOTIFIABLE)) + { + /* Set Size CCCD */ + + inst->attrs[attr_cnt].uuid = ccc_uuid; + inst->attrs[attr_cnt].perm = + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT; + inst->attrs[attr_cnt].read = bt_gatt_attr_read_ccc; + inst->attrs[attr_cnt].write = bt_gatt_attr_write_ccc; + inst->attrs[attr_cnt].user_data = + (void *)&set_size_audio_ccc_user_data[idx]; + attr_cnt++; + } + } + + if (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT) && param->lockable) + { + /* Set lock declaration */ + + inst->attrs[attr_cnt].uuid = chrc_uuid; + inst->attrs[attr_cnt].perm = BT_GATT_PERM_READ; + inst->attrs[attr_cnt].read = bt_gatt_attr_read_chrc; + inst->attrs[attr_cnt].user_data = (void *)&set_lock_user_data; + attr_cnt++; + + /* Set lock value */ + + inst->attrs[attr_cnt].uuid = lock_uuid; + inst->attrs[attr_cnt].perm = + BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT; + inst->attrs[attr_cnt].read = bt_audio_read_chrc; + inst->attrs[attr_cnt].write = bt_audio_write_chrc; + inst->attrs[attr_cnt].user_data = + (void *)&set_lock_audio_user_data[idx]; + attr_cnt++; + + /* Set lock CCCD */ + + inst->attrs[attr_cnt].uuid = ccc_uuid; + inst->attrs[attr_cnt].perm = + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT; + inst->attrs[attr_cnt].read = bt_gatt_attr_read_ccc; + inst->attrs[attr_cnt].write = bt_gatt_attr_write_ccc; + inst->attrs[attr_cnt].user_data = + (void *)&set_lock_audio_ccc_user_data[idx]; + attr_cnt++; + } + + if (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_RANK_SUPPORT) && param->rank > 0U) + { + /* Rank declaration */ + + inst->attrs[attr_cnt].uuid = chrc_uuid; + inst->attrs[attr_cnt].perm = BT_GATT_PERM_READ; + inst->attrs[attr_cnt].read = bt_gatt_attr_read_chrc; + inst->attrs[attr_cnt].user_data = (void *)&rank_user_data; + attr_cnt++; + + /* Rank value */ + + inst->attrs[attr_cnt].uuid = rank_uuid; + inst->attrs[attr_cnt].perm = BT_GATT_PERM_READ_ENCRYPT; + inst->attrs[attr_cnt].read = bt_audio_read_chrc; + inst->attrs[attr_cnt].user_data = (void *)&rank_audio_user_data[idx]; + attr_cnt++; + } + + inst->gatt_svc.attr_count = attr_cnt; +} + +int bt_csip_set_member_register( + const struct bt_csip_set_member_register_param *param, + struct bt_csip_set_member_svc_inst **svc_inst) +{ + static bool first_register; + struct bt_csip_set_member_svc_inst *inst; + int err; + + if (param == NULL) + { + LOG_DBG("NULL param"); + return -EINVAL; + } + + if (!valid_register_param(param)) + { + LOG_DBG("Invalid parameters"); + return -EINVAL; + } + + if (!first_register) + { + for (size_t i = 0U; i < ARRAY_SIZE(svc_insts); i++) + { + nxrmutex_init(&svc_insts[i].mutex); + } + + err = bt_conn_auth_info_cb_register(&auth_callbacks); + DEBUGASSERT(err == 0); + + first_register = true; + } + + inst = NULL; + ARRAY_FOR_EACH_PTR(svc_insts, si) + { + err = nxrmutex_trylock(&si->mutex); + if (err != 0) + { + /* Try the next */ + + continue; + } + + if (si->gatt_svc.attrs == NULL) + { + inst = si; + break; + } + else + { + err = nxrmutex_unlock(&si->mutex); + DEBUGASSERT(err == 0); + } + } + + if (inst == NULL) + { + LOG_DBG("Too many set member registrations"); + return -ENOMEM; + } + + instantiate_service(inst, param); + + err = bt_gatt_service_register(&inst->gatt_svc); + if (err != 0) + { + int mutex_err; + + LOG_DBG("CSIS service register failed: %d", err); + mutex_err = nxrmutex_unlock(&inst->mutex); + DEBUGASSERT(mutex_err == 0); + return err; + } +#if defined(CONFIG_BT_CSIP_SET_MEMBER_RANK_SUPPORT) + + inst->rank = param->rank; +#endif /* CONFIG_BT_CSIP_SET_MEMBER_RANK_SUPPORT */ +#if defined(CONFIG_BT_CSIP_SET_MEMBER_SIZE_SUPPORT) + inst->set_size = param->set_size; +#endif /* CONFIG_BT_CSIP_SET_MEMBER_SIZE_SUPPORT */ + inst->sirk.type = BT_CSIP_SIRK_TYPE_PLAIN; + inst->cb = param->cb; +#if defined(CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT) + k_work_init_delayable(&inst->set_lock_timer, set_lock_timer_handler); + inst->set_lock = BT_CSIP_RELEASE_VALUE; + inst->lockable = param->lockable; + bt_addr_le_copy(&inst->lock_client_addr, BT_ADDR_LE_NONE); +#endif /* CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT */ + if (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_TEST_SAMPLE_DATA)) + { + uint8_t test_sirk[] = + { + 0xcdu, 0xccu, 0x72u, 0xddu, 0x86u, 0x8cu, 0xcdu, 0xceu, + 0x22u, 0xfdu, 0xa1u, 0x21u, 0x09u, 0x7du, 0x7du, 0x45u, + }; + + (void)memcpy(inst->sirk.value, test_sirk, sizeof(test_sirk)); + LOG_DBG("CSIP SIRK was overwritten by sample data SIRK"); + } + else + { + (void)memcpy(inst->sirk.value, param->sirk, sizeof(inst->sirk.value)); + } + + *svc_inst = inst; + + err = nxrmutex_unlock(&inst->mutex); + DEBUGASSERT(err == 0); + + return 0; +} + +int bt_csip_set_member_unregister( + struct bt_csip_set_member_svc_inst *svc_inst) +{ + int err; + + if (svc_inst == NULL) + { + LOG_DBG("NULL svc_inst"); + return -EINVAL; + } + + err = nxrmutex_trylock(&svc_inst->mutex); + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + return -EBUSY; + } + + err = bt_gatt_service_unregister(&svc_inst->gatt_svc); + if (err != 0) + { + int mutex_err; + + LOG_DBG("CSIS service unregister failed: %d", err); + mutex_err = nxrmutex_unlock(&svc_inst->mutex); + DEBUGASSERT(mutex_err == 0); + + return err; + } + +#if defined(CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT) + (void)k_work_cancel_delayable(&svc_inst->set_lock_timer); +#endif /* CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT */ + memset(svc_inst, 0, offsetof(struct bt_csip_set_member_svc_inst, mutex)); + + err = nxrmutex_unlock(&svc_inst->mutex); + DEBUGASSERT(err == 0); + + return 0; +} + +int bt_csip_set_member_sirk(struct bt_csip_set_member_svc_inst *svc_inst, + const uint8_t sirk[BT_CSIP_SIRK_SIZE]) +{ + if (svc_inst == NULL) + { + LOG_DBG("NULL svc_inst"); + return -EINVAL; + } + + if (sirk == NULL) + { + LOG_DBG("NULL SIRK"); + return -EINVAL; + } + + if (memcmp(sirk, svc_inst->sirk.value, BT_CSIP_SIRK_SIZE) != 0) + { + memcpy(svc_inst->sirk.value, sirk, BT_CSIP_SIRK_SIZE); + + if (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_SIRK_NOTIFIABLE)) + { + notify_clients(svc_inst, NULL, FLAG_NOTIFY_SIRK); + } + } + + return 0; +} + +#if defined(CONFIG_BT_CSIP_SET_MEMBER_SIZE_SUPPORT) || \ + defined(CONFIG_BT_CSIP_SET_MEMBER_RANK_SUPPORT) +int bt_csip_set_member_set_size_and_rank( + struct bt_csip_set_member_svc_inst *svc_inst, uint8_t size, uint8_t rank) +{ + if (svc_inst == NULL) + { + LOG_DBG("svc_inst is NULL"); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_SIZE_SUPPORT) && size < 1U) + { + LOG_DBG("Invalid set size %u", size); + return -EINVAL; + } + +#if defined(CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT) + if (!svc_inst->lockable && rank != 0U) + { + LOG_DBG("Invalid rank %u for non-lockable service", rank); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_SIZE_SUPPORT) && + svc_inst->lockable && + !IN_RANGE(rank, 1U, size)) + { + LOG_DBG("Invalid rank: %u for size %u", rank, size); + return -EINVAL; + } +#elif defined(CONFIG_BT_CSIP_SET_MEMBER_RANK_SUPPORT) + if (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_SIZE_SUPPORT) && + !IN_RANGE(rank, 1U, size)) + { + LOG_DBG("Invalid rank: %u for size %u", rank, size); + return -EINVAL; + } +#endif /* CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT */ + +#if defined(CONFIG_BT_CSIP_SET_MEMBER_SIZE_SUPPORT) + if (svc_inst->set_size == size) + { + LOG_DBG("Set size %u is already set", size); + return -EALREADY; + } + + svc_inst->set_size = size; +#endif /* CONFIG_BT_CSIP_SET_MEMBER_SIZE_SUPPORT */ + +#if defined(CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT) + svc_inst->rank = svc_inst->lockable ? rank : 0U; +#elif defined(CONFIG_BT_CSIP_SET_MEMBER_RANK_SUPPORT) + svc_inst->rank = rank; +#endif /* CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT */ + if (IS_ENABLED(CONFIG_BT_CSIP_SET_MEMBER_SIZE_NOTIFIABLE)) + { + notify_clients(svc_inst, NULL, FLAG_NOTIFY_SIZE); + } + + return 0; +} +#endif /* CONFIG_BT_CSIP_SET_MEMBER_SIZE_SUPPORT || CONFIG_BT_CSIP_SET_MEMBER_RANK_SUPPORT */ + +int bt_csip_set_member_get_info( + const struct bt_csip_set_member_svc_inst *svc_inst, + struct bt_csip_set_member_set_info *info) +{ + if (svc_inst == NULL) + { + LOG_DBG("svc_inst is NULL"); + return -EINVAL; + } + + if (info == NULL) + { + LOG_DBG("info is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT) + info->lockable = svc_inst->lockable; + info->locked = svc_inst->set_lock == BT_CSIP_LOCK_VALUE; + bt_addr_le_copy(&info->lock_client_addr, &svc_inst->lock_client_addr); +#else + info->lockable = false; + info->locked = false; + bt_addr_le_copy(&info->lock_client_addr, BT_ADDR_LE_NONE); +#endif /* CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT */ +#if defined(CONFIG_BT_CSIP_SET_MEMBER_RANK_SUPPORT) + info->rank = svc_inst->rank; +#else + info->rank = 0U; +#endif /* CONFIG_BT_CSIP_SET_MEMBER_RANK_SUPPORT */ +#if defined(CONFIG_BT_CSIP_SET_MEMBER_SIZE_SUPPORT) + info->set_size = svc_inst->set_size; +#else + info->set_size = 0U; +#endif /* CONFIG_BT_CSIP_SET_MEMBER_SIZE_SUPPORT */ + memcpy(info->sirk, svc_inst->sirk.value, BT_CSIP_SIRK_SIZE); + return 0; +} + +#if defined(CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT) +int bt_csip_set_member_lock(struct bt_csip_set_member_svc_inst *svc_inst, + bool lock, bool force) +{ + uint8_t lock_val; + int err = 0; + + if (lock) + { + lock_val = BT_CSIP_LOCK_VALUE; + } + else + { + lock_val = BT_CSIP_RELEASE_VALUE; + } + + if (!lock && force) + { + svc_inst->set_lock = BT_CSIP_RELEASE_VALUE; + notify_clients(svc_inst, NULL, FLAG_NOTIFY_LOCK); + + if (svc_inst->cb != NULL && svc_inst->cb->lock_changed != NULL) + { + svc_inst->cb->lock_changed(NULL, &svc_insts[0], false); + } + } + else + { + err = set_lock(NULL, svc_inst, lock_val); + } + + if (err < 0) + { + return BT_GATT_ERR(err); + } + else + { + return 0; + } +} +#endif /* CONFIG_BT_CSIP_SET_MEMBER_LOCK_SUPPORT */ diff --git a/libs/zblue/audio/gmap_client.c b/libs/zblue/audio/gmap_client.c new file mode 100644 index 0000000000000..82ce5fa9da551 --- /dev/null +++ b/libs/zblue/audio/gmap_client.c @@ -0,0 +1,864 @@ +/**************************************************************************** + * libs/zblue/audio/gmap_client.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "audio_internal.h" + +LOG_MODULE_REGISTER(bt_gmap_client, CONFIG_BT_GMAP_LOG_LEVEL); + +static const struct bt_uuid *gmas_uuid = BT_UUID_GMAS; +static const struct bt_uuid *gmap_role_uuid = BT_UUID_GMAP_ROLE; +static const struct bt_uuid *gmap_ugg_feat_uuid = BT_UUID_GMAP_UGG_FEAT; +static const struct bt_uuid *gmap_ugt_feat_uuid = BT_UUID_GMAP_UGT_FEAT; +static const struct bt_uuid *gmap_bgs_feat_uuid = BT_UUID_GMAP_BGS_FEAT; +static const struct bt_uuid *gmap_bgr_feat_uuid = BT_UUID_GMAP_BGR_FEAT; + +static const struct bt_gmap_cb *gmap_cb; + +enum gmap_client_flag +{ + GMAP_CLIENT_FLAG_BUSY, + + GMAP_CLIENT_FLAG_NUM_FLAGS, /* keep as last */ +}; + +static struct bt_gmap_client +{ + /** Profile connection reference */ + + struct bt_conn *conn; + + /* Remote role and features */ + + enum bt_gmap_role role; + struct bt_gmap_feat feat; + + uint16_t svc_start_handle; + uint16_t svc_end_handle; + + /* GATT procedure parameters */ + + union + { + struct bt_gatt_read_params read; + struct bt_gatt_discover_params discover; + } params; + + atomic_define(flags, GMAP_CLIENT_FLAG_NUM_FLAGS); +} + +gmap_insts[CONFIG_BT_MAX_CONN]; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void gmap_reset(struct bt_gmap_client *gmap_cli) +{ + if (gmap_cli->conn != NULL) + { + bt_conn_unref(gmap_cli->conn); + gmap_cli->conn = NULL; + } + + gmap_cli->role = 0; + (void)memset(&gmap_cli->feat, 0, sizeof(gmap_cli->feat)); + gmap_cli->svc_start_handle = 0U; + gmap_cli->svc_end_handle = 0U; + (void)memset(&gmap_cli->params, 0, sizeof(gmap_cli->params)); +} + +static struct bt_gmap_client *client_by_conn(struct bt_conn *conn) +{ + struct bt_gmap_client *gmap_cli = &gmap_insts[bt_conn_index(conn)]; + + if (gmap_cli->conn == conn) + { + return gmap_cli; + } + + return NULL; +} + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + struct bt_gmap_client *gmap_cli; + + ARG_UNUSED(reason); + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + gmap_cli = client_by_conn(conn); + if (gmap_cli != NULL) + { + bt_conn_drop(&gmap_cli->conn); + } +} + +BT_CONN_CB_DEFINE(conn_callbacks) = +{ + .disconnected = disconnected, +}; + +static void discover_complete(struct bt_gmap_client *gmap_cli) +{ + LOG_DBG("conn %p", (void *)gmap_cli->conn); + + atomic_clear_bit(gmap_cli->flags, GMAP_CLIENT_FLAG_BUSY); + + if (gmap_cb != NULL && gmap_cb->discover != NULL) + { + gmap_cb->discover(gmap_cli->conn, 0, gmap_cli->role, gmap_cli->feat); + } +} + +static void discover_failed(struct bt_gmap_client *gmap_cli, int err) +{ + struct bt_conn *conn = gmap_cli->conn; + + gmap_reset(gmap_cli); + + LOG_DBG("conn %p err %d", (void *)conn, err); + + atomic_clear_bit(gmap_cli->flags, GMAP_CLIENT_FLAG_BUSY); + + if (gmap_cb != NULL && gmap_cb->discover != NULL) + { + gmap_cb->discover(conn, err, 0, (struct bt_gmap_feat) + { + 0 + } + + ); + } +} + +static uint8_t bgr_feat_read_cb(struct bt_conn *conn, + uint8_t att_err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t len) +{ + struct bt_gmap_client *gmap_cli = client_by_conn(conn); + struct net_buf_simple buf; + int err = att_err; + + DEBUGASSERT(gmap_cli); + + LOG_DBG("conn %p att_err 0x%02x params %p data %p len %u", (void *)conn, + att_err, params, data, + len); + + if (data == NULL + || att_err != BT_ATT_ERR_SUCCESS + || len != sizeof(uint8_t)) + { + if (att_err == BT_ATT_ERR_SUCCESS) + { + att_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + + discover_failed(gmap_cli, err); + + return BT_GATT_ITER_STOP; + } + + net_buf_simple_init_with_data(&buf, (void *)data, len); + + gmap_cli->feat.bgr_feat = net_buf_simple_pull_u8(&buf); + LOG_DBG("bgr_feat 0x%02x", gmap_cli->feat.bgr_feat); + + discover_complete(gmap_cli); + + return BT_GATT_ITER_STOP; +} + +static int gmap_read_bgr_feat(struct bt_gmap_client *gmap_cli, + uint16_t handle) +{ + LOG_DBG("conn %p handle 0x%04x", (void *)gmap_cli->conn, handle); + + memset(&gmap_cli->params.read, 0, sizeof(gmap_cli->params.read)); + + gmap_cli->params.read.func = bgr_feat_read_cb; + gmap_cli->params.read.handle_count = 1u; + gmap_cli->params.read.single.handle = handle; + gmap_cli->params.read.single.offset = 0u; + + return bt_gatt_read(gmap_cli->conn, &gmap_cli->params.read); +} + +static uint8_t bgr_feat_discover_func( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_gmap_client *gmap_cli = client_by_conn(conn); + const struct bt_gatt_chrc *chrc; + int err; + + DEBUGASSERT(gmap_cli != NULL); + + LOG_DBG("conn %p attr %p params %p", (void *)conn, attr, params); + + if (attr == NULL) + { + discover_failed(gmap_cli, -ENOENT); + + return BT_GATT_ITER_STOP; + } + + chrc = attr->user_data; + + /* Read features */ + + err = gmap_read_bgr_feat(gmap_cli, chrc->value_handle); + if (err != 0) + { + discover_failed(gmap_cli, err); + } + + return BT_GATT_ITER_STOP; +} + +static int gmap_discover_bgr_feat(struct bt_gmap_client *gmap_cli) +{ + LOG_DBG("conn %p", (void *)gmap_cli->conn); + + memset(&gmap_cli->params.discover, 0, sizeof(gmap_cli->params.discover)); + + gmap_cli->params.discover.func = bgr_feat_discover_func; + gmap_cli->params.discover.uuid = gmap_bgr_feat_uuid; + gmap_cli->params.discover.type = BT_GATT_DISCOVER_CHARACTERISTIC; + gmap_cli->params.discover.start_handle = gmap_cli->svc_start_handle; + gmap_cli->params.discover.end_handle = gmap_cli->svc_end_handle; + + return bt_gatt_discover(gmap_cli->conn, &gmap_cli->params.discover); +} + +static uint8_t bgs_feat_read_cb(struct bt_conn *conn, + uint8_t att_err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t len) +{ + struct bt_gmap_client *gmap_cli = client_by_conn(conn); + struct net_buf_simple buf; + int err = att_err; + + DEBUGASSERT(gmap_cli); + + LOG_DBG("conn %p att_err 0x%02x params %p data %p len %u", (void *)conn, + att_err, params, data, + len); + + if (data == NULL + || att_err != BT_ATT_ERR_SUCCESS + || len != sizeof(uint8_t)) + { + if (att_err == BT_ATT_ERR_SUCCESS) + { + att_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + + discover_failed(gmap_cli, err); + + return BT_GATT_ITER_STOP; + } + + net_buf_simple_init_with_data(&buf, (void *)data, len); + + gmap_cli->feat.bgs_feat = net_buf_simple_pull_u8(&buf); + LOG_DBG("bgs_feat 0x%02x", gmap_cli->feat.bgs_feat); + + if ((gmap_cli->role & BT_GMAP_ROLE_BGR) != 0) + { + err = gmap_discover_bgr_feat(gmap_cli); + } + else + { + discover_complete(gmap_cli); + + return BT_GATT_ITER_STOP; + } + + if (err != 0) + { + discover_failed(gmap_cli, err); + } + + return BT_GATT_ITER_STOP; +} + +static int gmap_read_bgs_feat(struct bt_gmap_client *gmap_cli, + uint16_t handle) +{ + LOG_DBG("conn %p handle 0x%04x", (void *)gmap_cli->conn, handle); + + memset(&gmap_cli->params.read, 0, sizeof(gmap_cli->params.read)); + + gmap_cli->params.read.func = bgs_feat_read_cb; + gmap_cli->params.read.handle_count = 1u; + gmap_cli->params.read.single.handle = handle; + gmap_cli->params.read.single.offset = 0u; + + return bt_gatt_read(gmap_cli->conn, &gmap_cli->params.read); +} + +static uint8_t bgs_feat_discover_func( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_gmap_client *gmap_cli = client_by_conn(conn); + const struct bt_gatt_chrc *chrc; + int err; + + DEBUGASSERT(gmap_cli != NULL); + + LOG_DBG("conn %p attr %p params %p", (void *)conn, attr, params); + + if (attr == NULL) + { + discover_failed(gmap_cli, -ENOENT); + + return BT_GATT_ITER_STOP; + } + + chrc = attr->user_data; + + /* Read features */ + + err = gmap_read_bgs_feat(gmap_cli, chrc->value_handle); + if (err != 0) + { + discover_failed(gmap_cli, err); + } + + return BT_GATT_ITER_STOP; +} + +static int gmap_discover_bgs_feat(struct bt_gmap_client *gmap_cli) +{ + LOG_DBG("conn %p", (void *)gmap_cli->conn); + + memset(&gmap_cli->params.discover, 0, sizeof(gmap_cli->params.discover)); + + gmap_cli->params.discover.func = bgs_feat_discover_func; + gmap_cli->params.discover.uuid = gmap_bgs_feat_uuid; + gmap_cli->params.discover.type = BT_GATT_DISCOVER_CHARACTERISTIC; + gmap_cli->params.discover.start_handle = gmap_cli->svc_start_handle; + gmap_cli->params.discover.end_handle = gmap_cli->svc_end_handle; + + return bt_gatt_discover(gmap_cli->conn, &gmap_cli->params.discover); +} + +static uint8_t ugt_feat_read_cb(struct bt_conn *conn, + uint8_t att_err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t len) +{ + struct bt_gmap_client *gmap_cli = client_by_conn(conn); + struct net_buf_simple buf; + int err = att_err; + + DEBUGASSERT(gmap_cli); + + LOG_DBG("conn %p att_err 0x%02x params %p data %p len %u", (void *)conn, + att_err, params, data, + len); + + if (data == NULL + || att_err != BT_ATT_ERR_SUCCESS + || len != sizeof(uint8_t)) + { + if (att_err == BT_ATT_ERR_SUCCESS) + { + att_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + + discover_failed(gmap_cli, err); + + return BT_GATT_ITER_STOP; + } + + net_buf_simple_init_with_data(&buf, (void *)data, len); + + gmap_cli->feat.ugt_feat = net_buf_simple_pull_u8(&buf); + LOG_DBG("ugt_feat 0x%02x", gmap_cli->feat.ugt_feat); + + if ((gmap_cli->role & BT_GMAP_ROLE_BGS) != 0) + { + err = gmap_discover_bgs_feat(gmap_cli); + } + else if ((gmap_cli->role & BT_GMAP_ROLE_BGR) != 0) + { + err = gmap_discover_bgr_feat(gmap_cli); + } + else + { + discover_complete(gmap_cli); + + return BT_GATT_ITER_STOP; + } + + if (err != 0) + { + discover_failed(gmap_cli, err); + } + + return BT_GATT_ITER_STOP; +} + +static int gmap_read_ugt_feat(struct bt_gmap_client *gmap_cli, + uint16_t handle) +{ + LOG_DBG("conn %p handle 0x%04x", (void *)gmap_cli->conn, handle); + + memset(&gmap_cli->params.read, 0, sizeof(gmap_cli->params.read)); + + gmap_cli->params.read.func = ugt_feat_read_cb; + gmap_cli->params.read.handle_count = 1u; + gmap_cli->params.read.single.handle = handle; + gmap_cli->params.read.single.offset = 0u; + + return bt_gatt_read(gmap_cli->conn, &gmap_cli->params.read); +} + +static uint8_t ugt_feat_discover_func( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_gmap_client *gmap_cli = client_by_conn(conn); + const struct bt_gatt_chrc *chrc; + int err; + + DEBUGASSERT(gmap_cli != NULL); + + LOG_DBG("conn %p attr %p params %p", (void *)conn, attr, params); + + if (attr == NULL) + { + discover_failed(gmap_cli, -ENOENT); + + return BT_GATT_ITER_STOP; + } + + chrc = attr->user_data; + + /* Read features */ + + err = gmap_read_ugt_feat(gmap_cli, chrc->value_handle); + if (err != 0) + { + discover_failed(gmap_cli, err); + } + + return BT_GATT_ITER_STOP; +} + +static int gmap_discover_ugt_feat(struct bt_gmap_client *gmap_cli) +{ + LOG_DBG("conn %p", (void *)gmap_cli->conn); + + memset(&gmap_cli->params.discover, 0, sizeof(gmap_cli->params.discover)); + + gmap_cli->params.discover.func = ugt_feat_discover_func; + gmap_cli->params.discover.uuid = gmap_ugt_feat_uuid; + gmap_cli->params.discover.type = BT_GATT_DISCOVER_CHARACTERISTIC; + gmap_cli->params.discover.start_handle = gmap_cli->svc_start_handle; + gmap_cli->params.discover.end_handle = gmap_cli->svc_end_handle; + + return bt_gatt_discover(gmap_cli->conn, &gmap_cli->params.discover); +} + +static uint8_t ugg_feat_read_cb(struct bt_conn *conn, + uint8_t att_err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t len) +{ + struct bt_gmap_client *gmap_cli = client_by_conn(conn); + struct net_buf_simple buf; + int err = att_err; + + DEBUGASSERT(gmap_cli); + + LOG_DBG("conn %p att_err 0x%02x params %p data %p len %u", (void *)conn, + att_err, params, data, + len); + + if (data == NULL + || att_err != BT_ATT_ERR_SUCCESS + || len != sizeof(uint8_t)) + { + if (att_err == BT_ATT_ERR_SUCCESS) + { + att_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + + discover_failed(gmap_cli, err); + + return BT_GATT_ITER_STOP; + } + + net_buf_simple_init_with_data(&buf, (void *)data, len); + + gmap_cli->feat.ugg_feat = net_buf_simple_pull_u8(&buf); + LOG_DBG("ugg_feat 0x%02x", gmap_cli->feat.ugg_feat); + + if ((gmap_cli->role & BT_GMAP_ROLE_UGT) != 0) + { + err = gmap_discover_ugt_feat(gmap_cli); + } + else if ((gmap_cli->role & BT_GMAP_ROLE_BGS) != 0) + { + err = gmap_discover_bgs_feat(gmap_cli); + } + else if ((gmap_cli->role & BT_GMAP_ROLE_BGR) != 0) + { + err = gmap_discover_bgr_feat(gmap_cli); + } + else + { + discover_complete(gmap_cli); + + return BT_GATT_ITER_STOP; + } + + if (err != 0) + { + discover_failed(gmap_cli, err); + } + + return BT_GATT_ITER_STOP; +} + +static int gmap_read_ugg_feat(struct bt_gmap_client *gmap_cli, + uint16_t handle) +{ + LOG_DBG("conn %p handle 0x%04x", (void *)gmap_cli->conn, handle); + + memset(&gmap_cli->params.read, 0, sizeof(gmap_cli->params.read)); + + gmap_cli->params.read.func = ugg_feat_read_cb; + gmap_cli->params.read.handle_count = 1u; + gmap_cli->params.read.single.handle = handle; + gmap_cli->params.read.single.offset = 0u; + + return bt_gatt_read(gmap_cli->conn, &gmap_cli->params.read); +} + +static uint8_t ugg_feat_discover_func( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_gmap_client *gmap_cli = client_by_conn(conn); + const struct bt_gatt_chrc *chrc; + int err; + + DEBUGASSERT(gmap_cli != NULL); + + LOG_DBG("conn %p attr %p params %p", (void *)conn, attr, params); + + if (attr == NULL) + { + discover_failed(gmap_cli, -ENOENT); + + return BT_GATT_ITER_STOP; + } + + chrc = attr->user_data; + + /* Read features */ + + err = gmap_read_ugg_feat(gmap_cli, chrc->value_handle); + if (err != 0) + { + discover_failed(gmap_cli, err); + } + + return BT_GATT_ITER_STOP; +} + +static int gmap_discover_ugg_feat(struct bt_gmap_client *gmap_cli) +{ + LOG_DBG("conn %p", (void *)gmap_cli->conn); + + memset(&gmap_cli->params.discover, 0, sizeof(gmap_cli->params.discover)); + + gmap_cli->params.discover.func = ugg_feat_discover_func; + gmap_cli->params.discover.uuid = gmap_ugg_feat_uuid; + gmap_cli->params.discover.type = BT_GATT_DISCOVER_CHARACTERISTIC; + gmap_cli->params.discover.start_handle = gmap_cli->svc_start_handle; + gmap_cli->params.discover.end_handle = gmap_cli->svc_end_handle; + + return bt_gatt_discover(gmap_cli->conn, &gmap_cli->params.discover); +} + +static uint8_t role_read_cb(struct bt_conn *conn, + uint8_t att_err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t len) +{ + struct bt_gmap_client *gmap_cli = client_by_conn(conn); + struct net_buf_simple buf; + int err = att_err; + + DEBUGASSERT(gmap_cli); + + LOG_DBG("conn %p att_err 0x%02x params %p data %p len %u", (void *)conn, + att_err, params, data, + len); + + if (data == NULL + || att_err != BT_ATT_ERR_SUCCESS + || len != sizeof(uint8_t)) + { + if (att_err == BT_ATT_ERR_SUCCESS) + { + att_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + + discover_failed(gmap_cli, err); + + return BT_GATT_ITER_STOP; + } + + net_buf_simple_init_with_data(&buf, (void *)data, len); + + gmap_cli->role = net_buf_simple_pull_u8(&buf); + LOG_DBG("role 0x%02x", gmap_cli->role); + + if ((gmap_cli->role & BT_GMAP_ROLE_UGG) != 0) + { + err = gmap_discover_ugg_feat(gmap_cli); + } + else if ((gmap_cli->role & BT_GMAP_ROLE_UGT) != 0) + { + err = gmap_discover_ugt_feat(gmap_cli); + } + else if ((gmap_cli->role & BT_GMAP_ROLE_BGS) != 0) + { + err = gmap_discover_bgs_feat(gmap_cli); + } + else if ((gmap_cli->role & BT_GMAP_ROLE_BGR) != 0) + { + err = gmap_discover_bgr_feat(gmap_cli); + } + else + { + LOG_DBG("Remote device does not support any known roles"); + err = -ECANCELED; + } + + if (err != 0) + { + discover_failed(gmap_cli, err); + } + + return BT_GATT_ITER_STOP; +} + +static int gmap_read_role(struct bt_gmap_client *gmap_cli, uint16_t handle) +{ + LOG_DBG("conn %p handle 0x%04x", (void *)gmap_cli->conn, handle); + + memset(&gmap_cli->params.read, 0, sizeof(gmap_cli->params.read)); + + gmap_cli->params.read.func = role_read_cb; + gmap_cli->params.read.handle_count = 1u; + gmap_cli->params.read.single.handle = handle; + gmap_cli->params.read.single.offset = 0u; + + return bt_gatt_read(gmap_cli->conn, &gmap_cli->params.read); +} + +static uint8_t role_discover_func( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_gmap_client *gmap_cli = client_by_conn(conn); + const struct bt_gatt_chrc *chrc; + int err; + + DEBUGASSERT(gmap_cli != NULL); + + LOG_DBG("conn %p attr %p params %p", (void *)conn, attr, params); + + if (attr == NULL) + { + discover_failed(gmap_cli, -ENOENT); + + return BT_GATT_ITER_STOP; + } + + chrc = attr->user_data; + + /* Read features */ + + err = gmap_read_role(gmap_cli, chrc->value_handle); + if (err != 0) + { + discover_failed(gmap_cli, err); + } + + return BT_GATT_ITER_STOP; +} + +static int gmap_discover_role(struct bt_gmap_client *gmap_cli) +{ + LOG_DBG("conn %p", (void *)gmap_cli->conn); + + memset(&gmap_cli->params.discover, 0, sizeof(gmap_cli->params.discover)); + + gmap_cli->params.discover.func = role_discover_func; + gmap_cli->params.discover.uuid = gmap_role_uuid; + gmap_cli->params.discover.type = BT_GATT_DISCOVER_CHARACTERISTIC; + gmap_cli->params.discover.start_handle = gmap_cli->svc_start_handle; + gmap_cli->params.discover.end_handle = gmap_cli->svc_end_handle; + + return bt_gatt_discover(gmap_cli->conn, &gmap_cli->params.discover); +} + +static uint8_t gmas_discover_func( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_gmap_client *gmap_cli = client_by_conn(conn); + const struct bt_gatt_service_val *svc; + int err; + + DEBUGASSERT(gmap_cli != NULL); + + LOG_DBG("conn %p attr %p params %p", (void *)conn, attr, params); + + if (attr == NULL) + { + discover_failed(gmap_cli, -ENOENT); + + return BT_GATT_ITER_STOP; + } + + svc = (struct bt_gatt_service_val *)attr->user_data; + gmap_cli->svc_start_handle = attr->handle; + gmap_cli->svc_end_handle = svc->end_handle; + + err = gmap_discover_role(gmap_cli); + if (err != 0) + { + discover_failed(gmap_cli, err); + } + + return BT_GATT_ITER_STOP; +} + +int bt_gmap_discover(struct bt_conn *conn) +{ + struct bt_gmap_client *gmap_cli; + struct bt_conn *ref; + int err; + + if (conn == NULL) + { + LOG_DBG("NULL conn"); + + return -EINVAL; + } + + gmap_cli = &gmap_insts[bt_conn_index(conn)]; + + if (atomic_test_and_set_bit(gmap_cli->flags, GMAP_CLIENT_FLAG_BUSY)) + { + LOG_DBG("Busy"); + + return -EBUSY; + } + + ref = bt_conn_ref(conn); + if (ref == NULL) + { + err = -ENOTCONN; + goto cleanup; + } + + gmap_reset(gmap_cli); + + gmap_cli->params.discover.func = gmas_discover_func; + gmap_cli->params.discover.uuid = gmas_uuid; + gmap_cli->params.discover.type = BT_GATT_DISCOVER_PRIMARY; + gmap_cli->params.discover.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; + gmap_cli->params.discover.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + + gmap_cli->conn = ref; + + err = bt_gatt_discover(conn, &gmap_cli->params.discover); + if (err != 0) + { + LOG_DBG("Failed to initiate discovery: %d", err); + bt_conn_unref(ref); + gmap_cli->conn = NULL; + err = -ENOEXEC; + goto cleanup; + } + + return 0; + +cleanup: + atomic_clear_bit(gmap_cli->flags, GMAP_CLIENT_FLAG_BUSY); + return err; +} + +int bt_gmap_cb_register(const struct bt_gmap_cb *cb) +{ + if (cb == NULL) + { + LOG_DBG("cb is NULL"); + + return -EINVAL; + } + + if (gmap_cb != NULL) + { + LOG_DBG("GMAP callbacks already registered"); + + return -EALREADY; + } + + gmap_cb = cb; + + return 0; +} diff --git a/libs/zblue/audio/gmap_server.c b/libs/zblue/audio/gmap_server.c new file mode 100644 index 0000000000000..4f9aeb6b71945 --- /dev/null +++ b/libs/zblue/audio/gmap_server.c @@ -0,0 +1,550 @@ +/**************************************************************************** + * libs/zblue/audio/gmap_server.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "audio_internal.h" + +LOG_MODULE_REGISTER(bt_gmap_server, CONFIG_BT_GMAP_LOG_LEVEL); + +#define BT_GMAP_ROLE_MASK \ + (BT_GMAP_ROLE_UGG | BT_GMAP_ROLE_UGT | BT_GMAP_ROLE_BGS | BT_GMAP_ROLE_BGR) + +static uint8_t gmap_role; +static struct bt_gmap_feat gmap_features; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static ssize_t read_gmap_role( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + LOG_DBG("role 0x%02X", gmap_role); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &gmap_role, + sizeof(gmap_role)); +} + +#if defined(CONFIG_BT_GMAP_UGG_SUPPORTED) +static ssize_t read_gmap_ugg_feat( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + const uint8_t feat = (uint8_t)gmap_features.ugg_feat; + + LOG_DBG("feat 0x%02X", feat); + + return bt_gatt_attr_read(conn, + attr, + buf, + len, + offset, + &feat, + sizeof(feat)); +} + +static const struct bt_gatt_attr ugg_feat_chrc[] = +{ + BT_AUDIO_CHRC(BT_UUID_GMAP_UGG_FEAT, + BT_GATT_CHRC_READ, + BT_GATT_PERM_READ_ENCRYPT, + + read_gmap_ugg_feat, NULL, NULL), +}; +#endif /* CONFIG_BT_GMAP_UGG_SUPPORTED */ + +#if defined(CONFIG_BT_GMAP_UGT_SUPPORTED) +static ssize_t read_gmap_ugt_feat( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + const uint8_t feat = (uint8_t)gmap_features.ugt_feat; + + LOG_DBG("feat 0x%02X", feat); + + return bt_gatt_attr_read(conn, + attr, + buf, + len, + offset, + &feat, + sizeof(feat)); +} + +static const struct bt_gatt_attr ugt_feat_chrc[] = +{ + BT_AUDIO_CHRC(BT_UUID_GMAP_UGT_FEAT, + BT_GATT_CHRC_READ, + BT_GATT_PERM_READ_ENCRYPT, + + read_gmap_ugt_feat, NULL, NULL), +}; + +#endif /* CONFIG_BT_GMAP_UGT_SUPPORTED */ + +#if defined(CONFIG_BT_GMAP_BGS_SUPPORTED) +static ssize_t read_gmap_bgs_feat( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + const uint8_t feat = (uint8_t)gmap_features.bgs_feat; + + LOG_DBG("feat 0x%02X", feat); + + return bt_gatt_attr_read(conn, + attr, + buf, + len, + offset, + &feat, + sizeof(feat)); +} + +static const struct bt_gatt_attr bgs_feat_chrc[] = +{ + BT_AUDIO_CHRC(BT_UUID_GMAP_BGS_FEAT, + BT_GATT_CHRC_READ, + BT_GATT_PERM_READ_ENCRYPT, + + read_gmap_bgs_feat, NULL, NULL), +}; +#endif /* CONFIG_BT_GMAP_BGS_SUPPORTED */ + +#if defined(CONFIG_BT_GMAP_BGR_SUPPORTED) +static ssize_t read_gmap_bgr_feat( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + const uint8_t feat = (uint8_t)gmap_features.bgr_feat; + + LOG_DBG("feat 0x%02X", feat); + + return bt_gatt_attr_read(conn, + attr, + buf, + len, + offset, + &feat, + sizeof(feat)); +} + +static const struct bt_gatt_attr bgr_feat_chrc[] = +{ + BT_AUDIO_CHRC(BT_UUID_GMAP_BGR_FEAT, + BT_GATT_CHRC_READ, + BT_GATT_PERM_READ_ENCRYPT, + + read_gmap_bgr_feat, NULL, NULL), +}; +#endif /* CONFIG_BT_GMAP_BGR_SUPPORTED */ + +/* There are 4 optional characteristics - Use a dummy definition to allocate + * memory and then add the characteristics at will when registering or + * modifying the role(s) + */ +#define GMAS_DUMMY_CHRC BT_AUDIO_CHRC(0, 0, 0, NULL, NULL, NULL) + +/* Gaming Audio Service attributes */ + +static struct bt_gatt_attr svc_attrs[] = +{ + BT_GATT_PRIMARY_SERVICE(BT_UUID_GMAS), + BT_AUDIO_CHRC(BT_UUID_GMAP_ROLE, + BT_GATT_CHRC_READ, + BT_GATT_PERM_READ_ENCRYPT, + + read_gmap_role, NULL, NULL), +#if defined(CONFIG_BT_GMAP_UGG_SUPPORTED) + GMAS_DUMMY_CHRC, +#endif /* CONFIG_BT_GMAP_UGG_SUPPORTED */ +#if defined(CONFIG_BT_GMAP_UGT_SUPPORTED) + GMAS_DUMMY_CHRC, +#endif /* CONFIG_BT_GMAP_UGT_SUPPORTED */ +#if defined(CONFIG_BT_GMAP_BGS_SUPPORTED) + GMAS_DUMMY_CHRC, +#endif /* CONFIG_BT_GMAP_BGS_SUPPORTED */ +#if defined(CONFIG_BT_GMAP_BGR_SUPPORTED) + GMAS_DUMMY_CHRC, +#endif /* CONFIG_BT_GMAP_BGR_SUPPORTED */ +}; + +static struct bt_gatt_service gmas; + +static bool valid_gmap_role(enum bt_gmap_role role) +{ + if (role == 0 || (role & BT_GMAP_ROLE_MASK) != role) + { + LOG_DBG("Invalid role %d", role); + } + + if ((role & BT_GMAP_ROLE_UGG) != 0 && + !IS_ENABLED(CONFIG_BT_GMAP_UGG_SUPPORTED)) + { + LOG_DBG("Device does not support the UGG role"); + + return false; + } + + if ((role & BT_GMAP_ROLE_UGT) != 0 && + !IS_ENABLED(CONFIG_BT_GMAP_UGT_SUPPORTED)) + { + LOG_DBG("Device does not support the UGT role"); + + return false; + } + + if ((role & BT_GMAP_ROLE_BGS) != 0 && + !IS_ENABLED(CONFIG_BT_GMAP_BGS_SUPPORTED)) + { + LOG_DBG("Device does not support the BGS role"); + + return false; + } + + if ((role & BT_GMAP_ROLE_BGR) != 0 && + !IS_ENABLED(CONFIG_BT_GMAP_BGR_SUPPORTED)) + { + LOG_DBG("Device does not support the BGR role"); + + return false; + } + + return true; +} + +static bool valid_gmap_features(enum bt_gmap_role role, + struct bt_gmap_feat features) +{ + /* Guard with BT_GMAP_UGG_SUPPORTED as the Kconfigs may not be available + * without it + */ +#if defined(CONFIG_BT_GMAP_UGG_SUPPORTED) + if ((role & BT_GMAP_ROLE_UGG) != 0) + { + enum bt_gmap_ugg_feat ugg_feat = features.ugg_feat; + + if ((ugg_feat & BT_GMAP_UGG_FEAT_MULTIPLEX) != 0 && + CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT == 0) + { + LOG_DBG("Cannot support BT_GMAP_UGG_FEAT_MULTIPLEX with " + "CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT == 0"); + + return false; + } + + if ((ugg_feat & BT_GMAP_UGG_FEAT_96KBPS_SOURCE) != 0 && + CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT == 0) + { + LOG_DBG("Cannot support BT_GMAP_UGG_FEAT_96KBPS_SOURCE with " + "CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SRC_COUNT == 0"); + + return false; + } + + if ((ugg_feat & BT_GMAP_UGG_FEAT_MULTISINK) != 0 && + (CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT < 2 || + CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT < 2)) + { + LOG_DBG("Cannot support BT_GMAP_UGG_FEAT_MULTISINK with " + "CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT (%d) or " + "CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT (%d) < 2", + CONFIG_BT_BAP_UNICAST_CLIENT_ASE_SNK_COUNT, + CONFIG_BT_BAP_UNICAST_CLIENT_GROUP_STREAM_COUNT); + + return false; + } + } +#endif /* CONFIG_BT_BAP_UNICAST_CLIENT */ + +#if defined(CONFIG_BT_GMAP_UGT_SUPPORTED) + if ((role & BT_GMAP_ROLE_UGT) != 0) + { + enum bt_gmap_ugt_feat ugt_feat = features.ugt_feat; + enum bt_gmap_bgr_feat bgr_feat = features.bgr_feat; + + if ((ugt_feat & BT_GMAP_UGT_FEAT_SOURCE) == 0 && + (ugt_feat & BT_GMAP_UGT_FEAT_SINK) == 0) + { + LOG_DBG("Device shall support either BT_GMAP_UGT_FEAT_SOURCE or " + "BT_GMAP_UGT_FEAT_SINK"); + + return false; + } + + if ((ugt_feat & BT_GMAP_UGT_FEAT_SOURCE) == 0 && + ((ugt_feat & BT_GMAP_UGT_FEAT_80KBPS_SOURCE) != 0 || + (ugt_feat & BT_GMAP_UGT_FEAT_MULTISOURCE) != 0)) + { + LOG_DBG("Device shall support BT_GMAP_UGT_FEAT_SOURCE if " + "BT_GMAP_UGT_FEAT_80KBPS_SOURCE or " + "BT_GMAP_UGT_FEAT_MULTISOURCE is " + "supported"); + + return false; + } + + if ((ugt_feat & BT_GMAP_UGT_FEAT_SOURCE) != 0 && + CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT == 0) + { + LOG_DBG("Cannot support BT_GMAP_UGT_FEAT_SOURCE with " + "CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT == 0"); + + return false; + } + + if ((ugt_feat & BT_GMAP_UGT_FEAT_MULTISOURCE) != 0 && + (CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT < 2 || + CONFIG_BT_ASCS_MAX_ACTIVE_ASES < 2)) + { + LOG_DBG("Cannot support BT_GMAP_UGT_FEAT_MULTISOURCE with " + "CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT (%d) or " + "CONFIG_BT_ASCS_MAX_ACTIVE_ASES (%d) < 2", + CONFIG_BT_ASCS_MAX_ASE_SRC_COUNT, + CONFIG_BT_ASCS_MAX_ACTIVE_ASES); + + return false; + } + + if ((ugt_feat & BT_GMAP_UGT_FEAT_SINK) != 0 && + CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT == 0) + { + LOG_DBG("Cannot support BT_GMAP_UGT_FEAT_SINK with " + "CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT == 0"); + + return false; + } + + if ((ugt_feat & BT_GMAP_UGT_FEAT_SINK) == 0 && + ((ugt_feat & BT_GMAP_UGT_FEAT_64KBPS_SINK) != 0 || + (ugt_feat & BT_GMAP_UGT_FEAT_MULTISINK) != 0 || + (ugt_feat & BT_GMAP_UGT_FEAT_MULTIPLEX) != 0)) + { + LOG_DBG("Device shall support BT_GMAP_UGT_FEAT_SINK if " + "BT_GMAP_UGT_FEAT_64KBPS_SINK, BT_GMAP_UGT_FEAT_MULTISINK " + "or " + "BT_GMAP_UGT_FEAT_MULTIPLEX is supported"); + + return false; + } + + if ((ugt_feat & BT_GMAP_UGT_FEAT_MULTISINK) != 0 && + (CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT < 2 || + CONFIG_BT_ASCS_MAX_ACTIVE_ASES < 2)) + { + LOG_DBG("Cannot support BT_GMAP_UGT_FEAT_MULTISINK with " + "CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT (%d) or " + "CONFIG_BT_ASCS_MAX_ACTIVE_ASES (%d) < 2", + CONFIG_BT_ASCS_MAX_ASE_SNK_COUNT, + CONFIG_BT_ASCS_MAX_ACTIVE_ASES); + + return false; + } + + /* If the device supports both the UGT and BGT roles, then it needs + * have the same support for multiplexing for both roles + */ + + if ((role & BT_GMAP_ROLE_BGR) != 0 && + !IS_ENABLED(CONFIG_BT_GMAP_BGR_SUPPORTED) && + (ugt_feat & BT_GMAP_UGT_FEAT_MULTIPLEX) != (bgr_feat & + BT_GMAP_BGR_FEAT_MULTIPLEX)) + { + LOG_DBG("Device shall support BT_GMAP_UGT_FEAT_MULTIPLEX if " + "BT_GMAP_BGR_FEAT_MULTIPLEX is supported, and vice versa"); + } + } +#endif /* CONFIG_BT_GMAP_UGT_SUPPORTED */ + +#if defined(CONFIG_BT_GMAP_BGR_SUPPORTED) + if ((role & BT_GMAP_ROLE_BGR) != 0) + { + enum bt_gmap_bgr_feat bgr_feat = features.bgr_feat; + + if ((bgr_feat & BT_GMAP_BGR_FEAT_MULTISINK) != 0 && + CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT < 2) + { + LOG_DBG("Cannot support BT_GMAP_BGR_FEAT_MULTISINK with " + "CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT (%d) < 2", + CONFIG_BT_BAP_BROADCAST_SNK_STREAM_COUNT); + + return false; + } + } +#endif /* CONFIG_BT_GMAP_BGR_SUPPORTED */ + + /* If the roles are not supported, then the feature characteristics are not + * instantiated and the feature values do not need to be checked, as they + * will never be read (thus ignore by the stack) + */ + + return true; +} + +static void update_service(enum bt_gmap_role role) +{ + gmas.attrs = svc_attrs; + gmas.attr_count = 3U; /* service + 2 attributes for BT_UUID_GMAP_ROLE */ + + /* Add characteristics based on the role selected and what is supported */ +#if defined(CONFIG_BT_GMAP_UGG_SUPPORTED) + if (role & BT_GMAP_ROLE_UGG) + { + memcpy(&gmas.attrs[gmas.attr_count], ugg_feat_chrc, + sizeof(ugg_feat_chrc)); + gmas.attr_count += ARRAY_SIZE(ugg_feat_chrc); + } +#endif /* CONFIG_BT_GMAP_UGG_SUPPORTED */ + +#if defined(CONFIG_BT_GMAP_UGT_SUPPORTED) + if (role & BT_GMAP_ROLE_UGT) + { + memcpy(&gmas.attrs[gmas.attr_count], ugt_feat_chrc, + sizeof(ugt_feat_chrc)); + gmas.attr_count += ARRAY_SIZE(ugt_feat_chrc); + } +#endif /* CONFIG_BT_GMAP_UGT_SUPPORTED */ + +#if defined(CONFIG_BT_GMAP_BGS_SUPPORTED) + if (role & BT_GMAP_ROLE_BGS) + { + memcpy(&gmas.attrs[gmas.attr_count], bgs_feat_chrc, + sizeof(bgs_feat_chrc)); + gmas.attr_count += ARRAY_SIZE(bgs_feat_chrc); + } +#endif /* CONFIG_BT_GMAP_BGS_SUPPORTED */ + +#if defined(CONFIG_BT_GMAP_BGR_SUPPORTED) + if (role & BT_GMAP_ROLE_BGR) + { + memcpy(&gmas.attrs[gmas.attr_count], bgr_feat_chrc, + sizeof(bgr_feat_chrc)); + gmas.attr_count += ARRAY_SIZE(bgr_feat_chrc); + } +#endif /* CONFIG_BT_GMAP_BGR_SUPPORTED */ +} + +int bt_gmap_register(enum bt_gmap_role role, struct bt_gmap_feat features) +{ + int err; + + if (!valid_gmap_role(role)) + { + LOG_DBG("Invalid role: %d", role); + + return -EINVAL; + } + + if (!valid_gmap_features(role, features)) + { + LOG_DBG("Invalid features"); + + return -EINVAL; + } + + update_service(role); + + err = bt_gatt_service_register(&gmas); + if (err != 0) + { + LOG_DBG("Could not register the GMAS service"); + + return -ENOEXEC; + } + + gmap_role = role; + gmap_features = features; + + return 0; +} + +int bt_gmap_set_role(enum bt_gmap_role role, struct bt_gmap_feat features) +{ + int err; + + if (gmap_role == 0U) + { + /* not registered if this is 0 */ + + LOG_DBG("GMAP not registered"); + + return -ENOEXEC; + } + + if (!valid_gmap_role(role)) + { + LOG_DBG("Invalid role: %d", role); + + return -EINVAL; + } + + if (!valid_gmap_features(role, features)) + { + LOG_DBG("Invalid features"); + + return -EINVAL; + } + + if (gmap_role == role) + { + LOG_DBG("No role change"); + + if (memcmp(&gmap_features, &features, sizeof(gmap_features)) == 0) + { + LOG_DBG("No feature change"); + + return -EALREADY; + } + + gmap_features = features; + + return 0; + } + + /* Re-register the service to trigger a db_changed() if the roles changed */ + + err = bt_gatt_service_unregister(&gmas); + if (err != 0) + { + LOG_DBG("Failed to unregister service: %d", err); + + return -ENOENT; + } + + err = bt_gmap_register(role, gmap_features); + if (err != 0) + { + LOG_DBG("Failed to update GMAS: %d", err); + + return -ECANCELED; + } + + return 0; +} diff --git a/libs/zblue/audio/has.c b/libs/zblue/audio/has.c new file mode 100644 index 0000000000000..62adbd562d843 --- /dev/null +++ b/libs/zblue/audio/has.c @@ -0,0 +1,2283 @@ +/**************************************************************************** + * libs/zblue/audio/has.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Codecoup + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../host/settings.h" + +#include "audio_internal.h" +#include "common/bt_str.h" +#include "has_internal.h" + +LOG_MODULE_REGISTER(bt_has, CONFIG_BT_HAS_LOG_LEVEL); + +/* The service allows operations with paired devices only. + * The number of clients is set to maximum number of simultaneous connections + * to paired devices. + */ +#define MAX_INSTS MIN(CONFIG_BT_MAX_CONN, CONFIG_BT_MAX_PAIRED) + +#define BITS_CHANGED(_new_value, _old_value) ((_new_value) ^ (_old_value)) +#define FEATURE_DEVICE_TYPE_UNCHANGED(_new_value) \ + !BITS_CHANGED(_new_value, (has.features & BT_HAS_FEAT_HEARING_AID_TYPE_MASK)) +#define FEATURE_SYNC_SUPPORT_UNCHANGED(_new_value) \ + !BITS_CHANGED(_new_value, ((has.features & BT_HAS_FEAT_PRESET_SYNC_SUPP) != 0 ? 1 : 0)) +#define FEATURE_IND_PRESETS_UNCHANGED(_new_value) \ + !BITS_CHANGED(_new_value, ((has.features & BT_HAS_FEAT_INDEPENDENT_PRESETS) != 0 ? 1 : 0)) +#define BONDED_CLIENT_INIT_FLAGS \ + (BIT(FLAG_ACTIVE_INDEX_CHANGED) | BIT(FLAG_NOTIFY_PRESET_LIST) | BIT(FLAG_FEATURES_CHANGED)) + +static struct bt_has has; + +#if defined(CONFIG_BT_HAS_ACTIVE_PRESET_INDEX) + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void active_preset_index_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + LOG_DBG("attr %p value 0x%04x", attr, value); +} +#endif /* CONFIG_BT_HAS_ACTIVE_PRESET_INDEX */ + +#if defined(CONFIG_BT_HAS_PRESET_SUPPORT) +struct has_client; + +static int read_preset_response(struct has_client *client); +static int preset_list_changed(struct has_client *client); +static int +preset_list_changed_generic_update_tail(struct has_client *client); +static int +preset_list_changed_record_deleted_last(struct has_client *client); +static ssize_t write_control_point( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *data, + uint16_t len, uint16_t offset, uint8_t flags); + +static void preset_cp_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + LOG_DBG("attr %p value 0x%04x", attr, value); +} + +static ssize_t read_active_preset_index( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + uint8_t active_index; + + LOG_DBG("conn %p attr %p offset %d", (void *)conn, attr, offset); + + if (offset > sizeof(active_index)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + active_index = bt_has_preset_active_get(); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &active_index, + sizeof(active_index)); +} +#endif /* CONFIG_BT_HAS_PRESET_SUPPORT */ + +#if defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE) +static void features_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + LOG_DBG("attr %p value 0x%04x", attr, value); +} +#endif /* CONFIG_BT_HAS_FEATURES_NOTIFIABLE */ + +static ssize_t read_features( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + LOG_DBG("conn %p attr %p offset %d", (void *)conn, attr, offset); + + if (offset > sizeof(has.features)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &has.features, + sizeof(has.features)); +} + +#if defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE) +#define BT_HAS_CHR_FEATURES \ + BT_AUDIO_CHRC(BT_UUID_HAS_HEARING_AID_FEATURES, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, read_features, NULL, NULL), \ + BT_AUDIO_CCC(features_cfg_changed), +#else +#define BT_HAS_CHR_FEATURES \ + BT_AUDIO_CHRC(BT_UUID_HAS_HEARING_AID_FEATURES, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT, \ + read_features, NULL, NULL), +#endif /* CONFIG_BT_HAS_FEATURES_NOTIFIABLE */ + +#if defined(CONFIG_BT_HAS_PRESET_SUPPORT) +#if defined(CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE) +#define BT_HAS_CHR_PRESET_CONTROL_POINT \ + BT_AUDIO_CHRC(BT_UUID_HAS_PRESET_CONTROL_POINT, \ + BT_GATT_CHRC_WRITE | BT_GATT_CHRC_INDICATE | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_WRITE_ENCRYPT, NULL, write_control_point, NULL), \ + BT_AUDIO_CCC(preset_cp_cfg_changed), +#else +#define BT_HAS_CHR_PRESET_CONTROL_POINT \ + BT_AUDIO_CHRC(BT_UUID_HAS_PRESET_CONTROL_POINT, BT_GATT_CHRC_WRITE | BT_GATT_CHRC_INDICATE, \ + BT_GATT_PERM_WRITE_ENCRYPT, NULL, write_control_point, NULL), \ + BT_AUDIO_CCC(preset_cp_cfg_changed), +#endif /* CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE */ +#else +#define BT_HAS_CHR_PRESET_CONTROL_POINT +#endif /* CONFIG_BT_HAS_PRESET_SUPPORT */ + +#if defined(CONFIG_BT_HAS_ACTIVE_PRESET_INDEX) +#define BT_HAS_CHR_ACTIVE_PRESET_INDEX \ + BT_AUDIO_CHRC(BT_UUID_HAS_ACTIVE_PRESET_INDEX, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, read_active_preset_index, NULL, NULL), \ + BT_AUDIO_CCC(active_preset_index_cfg_changed) +#else +#define BT_HAS_CHR_ACTIVE_PRESET_INDEX +#endif /* CONFIG_BT_HAS_ACTIVE_PRESET_INDEX */ + +/* Hearing Access Service GATT Attributes */ + +static struct bt_gatt_attr has_attrs[] = +{ + BT_GATT_PRIMARY_SERVICE(BT_UUID_HAS), + BT_HAS_CHR_FEATURES BT_HAS_CHR_PRESET_CONTROL_POINT + BT_HAS_CHR_ACTIVE_PRESET_INDEX +}; + +static struct bt_gatt_service has_svc; +static struct bt_gatt_attr *hearing_aid_features_attr; +static struct bt_gatt_attr *preset_control_point_attr; +static struct bt_gatt_attr *active_preset_index_attr; + +#if defined(CONFIG_BT_HAS_PRESET_SUPPORT) || defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE) +static void notify_work_handler(struct k_work *work); + +enum flag_internal +{ + FLAG_ACTIVE_INDEX_CHANGED, + FLAG_PENDING_READ_PRESET_RESPONSE, + FLAG_NOTIFY_PRESET_LIST, + FLAG_NOTIFY_PRESET_LIST_GENERIC_UPDATE_TAIL, + FLAG_NOTIFY_PRESET_LIST_RECORD_DELETED_LAST, + FLAG_FEATURES_CHANGED, + FLAG_NUM, +}; + +/* Stored client context */ + +static struct client_context +{ + bt_addr_le_t addr; + + /* Pending notification flags */ + + atomic_define(flags, FLAG_NUM); + + /* Last notified preset index */ + + uint8_t last_preset_index_known; +} + +contexts[CONFIG_BT_MAX_PAIRED]; + +/* Connected client instance */ + +static struct has_client +{ + struct bt_conn *conn; +#if defined(CONFIG_BT_HAS_PRESET_SUPPORT) + union + { + struct bt_gatt_indicate_params ind; +#if defined(CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE) + struct bt_gatt_notify_params ntf; +#endif /* CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE */ + } params; + + uint8_t preset_changed_index_next; + struct bt_has_cp_read_presets_req read_presets_req; +#endif /* CONFIG_BT_HAS_PRESET_SUPPORT */ + struct k_work_delayable notify_work; + struct client_context *context; +} + +has_client_list[MAX_INSTS]; + +static struct client_context *context_find(const bt_addr_le_t *addr) +{ + DEBUGASSERT(addr != NULL); + + for (size_t i = 0U; i < ARRAY_SIZE(contexts); i++) + { + if (bt_addr_le_eq(&contexts[i].addr, addr)) + { + return &contexts[i]; + } + } + + return NULL; +} + +static struct client_context *context_alloc(const bt_addr_le_t *addr) +{ + struct client_context *context; + + DEBUGASSERT(addr != NULL); + + /* Free contexts has BT_ADDR_LE_ANY as the address */ + + context = context_find(BT_ADDR_LE_ANY); + if (context == NULL) + { + return NULL; + } + + memset(context, 0, sizeof(*context)); + + bt_addr_le_copy(&context->addr, addr); + + return context; +} + +static void context_free(struct client_context *context) +{ + bt_addr_le_copy(&context->addr, BT_ADDR_LE_ANY); +} + +static void client_free(struct has_client *client) +{ + struct bt_conn_info info = + { + 0 + }; + + int err; + +#if defined(CONFIG_BT_HAS_PRESET_SUPPORT) || defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE) + (void)k_work_cancel_delayable(&client->notify_work); +#endif /* CONFIG_BT_HAS_PRESET_SUPPORT || CONFIG_BT_HAS_FEATURES_NOTIFIABLE */ + + err = bt_conn_get_info(client->conn, &info); + DEBUGASSERT(err == 0); + + if (client->context != NULL && !bt_le_bond_exists(info.id, info.le.dst)) + { + /* Free stored context of non-bonded client */ + + context_free(client->context); + client->context = NULL; + } + + bt_conn_drop(&client->conn); +} + +static struct has_client *client_alloc(struct bt_conn *conn) +{ + struct bt_conn_info info = + { + 0 + }; + + struct has_client *client = NULL; + int err; + + for (size_t i = 0U; i < ARRAY_SIZE(has_client_list); i++) + { + if (conn == has_client_list[i].conn) + { + return &has_client_list[i]; + } + + /* first free slot */ + + if (!client && has_client_list[i].conn == NULL) + { + client = &has_client_list[i]; + } + } + + DEBUGASSERT(client); + + memset(client, 0, sizeof(*client)); + + client->conn = bt_conn_ref(conn); + +#if defined(CONFIG_BT_HAS_PRESET_SUPPORT) || defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE) + k_work_init_delayable(&client->notify_work, notify_work_handler); +#endif /* CONFIG_BT_HAS_PRESET_SUPPORT || CONFIG_BT_HAS_FEATURES_NOTIFIABLE */ + + err = bt_conn_get_info(conn, &info); + if (err != 0) + { + LOG_DBG("Could not get conn info: %d", err); + + return NULL; + } + + client->context = context_find(info.le.dst); + if (client->context == NULL) + { + client->context = context_alloc(info.le.dst); + if (client->context == NULL) + { + LOG_ERR("Failed to allocate client_context for %s", + bt_addr_le_str(info.le.dst)); + + client_free(client); + + return NULL; + } + + LOG_DBG("New client_context for %s", bt_addr_le_str(info.le.dst)); + } + + return client; +} + +static struct has_client *client_find_by_conn(struct bt_conn *conn) +{ + for (size_t i = 0U; i < ARRAY_SIZE(has_client_list); i++) + { + if (conn == has_client_list[i].conn) + { + return &has_client_list[i]; + } + } + + return NULL; +} + +static void notify_work_reschedule(struct has_client *client, clock_t delay) +{ + int err; + + DEBUGASSERT(client->conn); + + if (k_work_delayable_remaining_get(&client->notify_work) > 0) + { + return; + } + + err = k_work_reschedule(&client->notify_work, delay); + if (err < 0) + { + LOG_ERR("Failed to reschedule notification work err %d", err); + } +} + +static void security_changed(struct bt_conn *conn, + bt_security_t level, enum bt_security_err err) +{ + struct has_client *client; + struct bt_conn_info info; + int ret; + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + LOG_DBG("conn %p level %d err %d", (void *)conn, level, err); + + if (err != BT_SECURITY_ERR_SUCCESS) + { + return; + } + + client = client_alloc(conn); + if (unlikely(!client)) + { + LOG_ERR("Failed to allocate client"); + return; + } + + if (!has.registered) + { + return; + } + + ret = bt_conn_get_info(client->conn, &info); + if (ret < 0) + { + LOG_ERR("bt_conn_get_info err %d", ret); + return; + } + + if (!bt_le_bond_exists(info.id, info.le.dst)) + { + return; + } + + if (atomic_read(client->context->flags) != 0) + { + notify_work_reschedule(client, 0); + } +} + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + struct has_client *client; + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + LOG_DBG("conn %p reason %d", (void *)conn, reason); + + client = client_find_by_conn(conn); + if (client) + { + client_free(client); + } +} + +static void identity_resolved(struct bt_conn *conn, const bt_addr_le_t *rpa, + const bt_addr_le_t *identity) +{ + struct has_client *client; + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + LOG_DBG("conn %p %s -> %s", (void *)conn, bt_addr_le_str(rpa), + bt_addr_le_str(identity)); + + client = client_find_by_conn(conn); + if (client == NULL) + { + return; + } + + bt_addr_le_copy(&client->context->addr, identity); +} + +BT_CONN_CB_DEFINE(conn_cb) = +{ + .disconnected = disconnected, + .security_changed = security_changed, + .identity_resolved = identity_resolved, +}; + +static void notify_work_handler(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct has_client *client = CONTAINER_OF(dwork, struct has_client, + notify_work); + int err; + + if (IS_ENABLED(CONFIG_BT_HAS_FEATURES_NOTIFIABLE) && + atomic_test_and_clear_bit(client->context->flags, + FLAG_FEATURES_CHANGED) && + bt_gatt_is_subscribed(client->conn, hearing_aid_features_attr, + BT_GATT_CCC_NOTIFY)) + { + err = bt_gatt_notify(client->conn, hearing_aid_features_attr, + &has.features, + sizeof(has.features)); + if (err == -ENOMEM) + { + atomic_set_bit(client->context->flags, FLAG_FEATURES_CHANGED); + notify_work_reschedule(client, + USEC2TICK(BT_AUDIO_NOTIFY_RETRY_DELAY_US)); + } + else if (err < 0) + { + LOG_ERR("Notify features err %d", err); + } + } + +#if defined(CONFIG_BT_HAS_PRESET_SUPPORT) + if (atomic_test_and_clear_bit(client->context->flags, + FLAG_PENDING_READ_PRESET_RESPONSE)) + { + err = read_preset_response(client); + if (err == -ENOMEM) + { + atomic_set_bit(client->context->flags, + FLAG_PENDING_READ_PRESET_RESPONSE); + notify_work_reschedule(client, + USEC2TICK(BT_AUDIO_NOTIFY_RETRY_DELAY_US)); + } + else if (err < 0) + { + LOG_ERR("Notify read preset response err %d", err); + } + } + else if (atomic_test_and_clear_bit(client->context->flags, + FLAG_NOTIFY_PRESET_LIST)) + { + err = preset_list_changed(client); + if (err == -ENOMEM) + { + atomic_set_bit(client->context->flags, FLAG_NOTIFY_PRESET_LIST); + notify_work_reschedule(client, + USEC2TICK(BT_AUDIO_NOTIFY_RETRY_DELAY_US)); + } + else if (err < 0) + { + LOG_ERR("Notify preset list changed err %d", err); + } + } + else if (atomic_test_and_clear_bit(client->context->flags, + FLAG_NOTIFY_PRESET_LIST_GENERIC_UPDATE_TAIL)) + { + err = preset_list_changed_generic_update_tail(client); + if (err == -ENOMEM) + { + atomic_set_bit(client->context->flags, + FLAG_NOTIFY_PRESET_LIST_GENERIC_UPDATE_TAIL); + notify_work_reschedule(client, + USEC2TICK(BT_AUDIO_NOTIFY_RETRY_DELAY_US)); + } + else if (err < 0) + { + LOG_ERR("Notify preset list changed generic update tail err %d", + err); + } + } + else if (atomic_test_and_clear_bit(client->context->flags, + FLAG_NOTIFY_PRESET_LIST_RECORD_DELETED_LAST)) + { + err = preset_list_changed_record_deleted_last(client); + if (err == -ENOMEM) + { + atomic_set_bit(client->context->flags, + FLAG_NOTIFY_PRESET_LIST_RECORD_DELETED_LAST); + notify_work_reschedule(client, + USEC2TICK(BT_AUDIO_NOTIFY_RETRY_DELAY_US)); + } + else if (err < 0) + { + LOG_ERR("Notify preset list changed recoed deleted last err %d", + err); + } + } + +#endif /* CONFIG_BT_HAS_PRESET_SUPPORT */ + + if (IS_ENABLED(CONFIG_BT_HAS_PRESET_SUPPORT) && + atomic_test_and_clear_bit(client->context->flags, + FLAG_ACTIVE_INDEX_CHANGED) && + bt_gatt_is_subscribed(client->conn, active_preset_index_attr, + BT_GATT_CCC_NOTIFY)) + { + uint8_t active_index; + + active_index = bt_has_preset_active_get(); + + err = bt_gatt_notify(client->conn, active_preset_index_attr, + &active_index, + sizeof(active_index)); + if (err == -ENOMEM) + { + atomic_set_bit(client->context->flags, FLAG_ACTIVE_INDEX_CHANGED); + notify_work_reschedule(client, + USEC2TICK(BT_AUDIO_NOTIFY_RETRY_DELAY_US)); + } + else if (err < 0) + { + LOG_ERR("Notify active index err %d", err); + } + } +} + +static void notify(struct has_client *client, enum flag_internal flag) +{ + if (client != NULL) + { + atomic_set_bit(client->context->flags, flag); + notify_work_reschedule(client, 0); + return; + } + + /* Mark notification to be sent to all clients */ + + for (size_t i = 0U; i < ARRAY_SIZE(contexts); i++) + { + atomic_set_bit(contexts[i].flags, flag); + } + + for (size_t i = 0U; i < ARRAY_SIZE(has_client_list); i++) + { + client = &has_client_list[i]; + + if (client->conn == NULL) + { + continue; + } + + notify_work_reschedule(client, 0); + } +} + +static void bond_deleted_cb(uint8_t id, const bt_addr_le_t *addr) +{ + struct client_context *context; + + ARG_UNUSED(id); + + context = context_find(addr); + if (context != NULL) + { + context_free(context); + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + int err; + + err = bt_settings_delete("has", 0, addr); + if (err != 0) + { + LOG_WRN("Failed to delete settings: %d", err); + } + } +} + +static struct bt_conn_auth_info_cb auth_info_cb = +{ + .bond_deleted = bond_deleted_cb, +}; +#endif /* CONFIG_BT_HAS_PRESET_SUPPORT || CONFIG_BT_HAS_FEATURES_NOTIFIABLE */ + +#if defined(CONFIG_BT_HAS_PRESET_SUPPORT) +static struct has_preset *active_preset; + +/* HAS internal preset representation */ + +static struct has_preset +{ + uint8_t index; + enum bt_has_properties properties; +#if defined(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC) + char name[BT_HAS_PRESET_NAME_MAX + 1]; /* +1 byte for NULL-terminator */ +#else + const char *name; +#endif /* CONFIG_BT_HAS_PRESET_NAME_DYNAMIC */ + const struct bt_has_preset_ops *ops; + struct list_node node; +} + +preset_pool[CONFIG_BT_HAS_PRESET_COUNT]; + +static struct list_node preset_list = LIST_INITIAL_VALUE(preset_list); +static struct list_node preset_free_list = + LIST_INITIAL_VALUE(preset_free_list); + +typedef bool (*preset_func_t)(const struct has_preset *preset, + void *user_data); + +static int preset_foreach( + uint8_t start_index, uint8_t end_index, preset_func_t func, + void *user_data) +{ + struct has_preset *preset; + struct has_preset *tmp; + + if (func == NULL) + { + LOG_DBG("func is NULL"); + return -EINVAL; + } + + list_for_every_container_safe(&preset_list, preset, tmp, node) + { + if (preset->index < start_index) + { + continue; + } + + if (preset->index > end_index) + { + break; + } + + if (!func(preset, user_data)) + { + return -ECANCELED; + } + } + + return 0; +} + +static bool preset_found(const struct has_preset *preset, void *user_data) +{ + const struct has_preset **found = user_data; + + *found = preset; + + return false; +} + +static void preset_insert(struct has_preset *preset) +{ + struct has_preset *tmp; + struct has_preset *prev = NULL; + + list_for_every_container(&preset_list, tmp, node) + { + if (tmp->index > preset->index) + { + if (prev) + { + list_insert_after(&preset_list, &prev->node, &preset->node); + } + else + { + list_add_head(&preset_list, &preset->node); + } + + return; + } + + prev = tmp; + } + + list_add_tail(&preset_list, &preset->node); +} + +static struct has_preset *preset_alloc( + uint8_t index, enum bt_has_properties properties, const char *name, + const struct bt_has_preset_ops *ops) +{ + struct has_preset *preset; + struct list_node *node; + + node = list_remove_head(&preset_free_list); + if (node == NULL) + { + return NULL; + } + + preset = CONTAINER_OF(node, struct has_preset, node); + preset->index = index; + preset->properties = properties; +#if defined(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC) + utf8_lcpy(preset->name, name, ARRAY_SIZE(preset->name)); +#else + preset->name = name; +#endif /* CONFIG_BT_HAS_PRESET_NAME_DYNAMIC */ + preset->ops = ops; + + preset_insert(preset); + + return preset; +} + +static void preset_free(struct has_preset *preset) +{ + bool removed; + + removed = list_find_and_remove(&preset_list, &preset->node); + if (removed) + { + list_add_tail(&preset_free_list, &preset->node); + } +} + +static struct has_preset *preset_get_head(void) +{ + struct has_preset *next; + + return list_peek_head_container(&preset_list, next, node); +} + +static struct has_preset *preset_get_tail(void) +{ + struct has_preset *prev; + + return list_peek_tail_container(&preset_list, prev, node); +} + +static struct has_preset *preset_get_prev(const struct has_preset *preset) +{ + struct has_preset *prev; + + list_for_every_container(&preset_list, prev, node) + { + if (list_next_container(&preset_list, prev, node) == preset) + { + return prev; + } + } + + prev = preset_get_tail(); + if (prev == preset) + { + return NULL; + } + + return prev; +} + +static struct has_preset *preset_lookup_index(uint8_t index) +{ + struct has_preset *preset; + + list_for_every_container(&preset_list, preset, node) + { + if (preset->index == index) + { + return preset; + } + } + + return NULL; +} + +static struct has_preset *preset_get_next(struct has_preset *preset) +{ + struct has_preset *next; + + next = list_next_container(&preset_list, preset, node); + if (next == NULL) + { + next = preset_get_head(); + if (next == preset) + { + return NULL; + } + } + + return next; +} + +static uint8_t preset_get_prev_index(const struct has_preset *preset) +{ + const struct has_preset *prev; + + prev = preset_get_prev(preset); + if (prev == NULL || prev->index >= preset->index) + { + return BT_HAS_PRESET_INDEX_NONE; + } + + return prev->index; +} + +static void control_point_ntf_complete(struct bt_conn *conn, void *user_data) +{ + struct has_client *client = client_find_by_conn(conn); + + ARG_UNUSED(user_data); + + LOG_DBG("conn %p", (void *)conn); + + /* Resubmit if needed */ + + if (client != NULL && atomic_read(client->context->flags) != 0) + { + notify_work_reschedule(client, 0); + } +} + +static void control_point_ind_complete( + struct bt_conn *conn, struct bt_gatt_indicate_params *params, uint8_t err) +{ + ARG_UNUSED(params); + + if (err != 0) + { + /* TODO: Handle error somehow */ + + LOG_ERR("conn %p err 0x%02x", (void *)conn, err); + } + + control_point_ntf_complete(conn, NULL); +} + +static int control_point_send(struct has_client *client, + struct net_buf_simple *buf) +{ + const int max_ntf_size = bt_att_get_max_notify_size(client->conn, + BT_ATT_CHAN_OPT_NONE); + + if (max_ntf_size < 0) + { + DEBUGASSERT(max_ntf_size != -EINVAL); + LOG_DBG("Failed to get max notification size: %d", max_ntf_size); + return max_ntf_size; /* contains errno value */ + } + + if (max_ntf_size < buf->len) + { + LOG_WRN("Sending truncated control point PDU %u < %u", max_ntf_size, + buf->len); + buf->len = max_ntf_size; + } + +#if defined(CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE) + if (bt_eatt_count(client->conn) > 0 && + bt_gatt_is_subscribed(client->conn, preset_control_point_attr, + BT_GATT_CCC_NOTIFY)) + { + memset(&client->params.ntf, 0, sizeof(client->params.ntf)); + client->params.ntf.attr = preset_control_point_attr; + client->params.ntf.func = control_point_ntf_complete; + client->params.ntf.data = buf->data; + client->params.ntf.len = buf->len; + + return bt_gatt_notify_cb(client->conn, &client->params.ntf); + } +#endif /* CONFIG_BT_HAS_PRESET_CONTROL_POINT_NOTIFIABLE */ + + if (bt_gatt_is_subscribed(client->conn, preset_control_point_attr, + BT_GATT_CCC_INDICATE)) + { + memset(&client->params.ind, 0, sizeof(client->params.ind)); + client->params.ind.attr = preset_control_point_attr; + client->params.ind.func = control_point_ind_complete; + client->params.ind.destroy = NULL; + client->params.ind.data = buf->data; + + /* indications have same size as notifications */ + + client->params.ind.len = buf->len; + + return bt_gatt_indicate(client->conn, &client->params.ind); + } + + return -ECANCELED; +} + +static int control_point_send_all(struct net_buf_simple *buf) +{ + int result = 0; + + for (size_t i = 0U; i < ARRAY_SIZE(contexts); i++) + { + struct client_context *context = &contexts[i]; + struct has_client *client = NULL; + int err; + + for (size_t j = 0U; j < ARRAY_SIZE(has_client_list); j++) + { + if (has_client_list[j].context == context) + { + client = &has_client_list[j]; + break; + } + } + + if (client == NULL || client->conn == NULL) + { + /* Mark preset changed operation as pending */ + + atomic_set_bit(context->flags, FLAG_NOTIFY_PRESET_LIST); + continue; + } + + if (!bt_gatt_is_subscribed(client->conn, preset_control_point_attr, + BT_GATT_CCC_NOTIFY | BT_GATT_CCC_INDICATE)) + { + continue; + } + + err = control_point_send(client, buf); + if (err != 0) + { + result = err; + + /* continue anyway */ + } + } + + return result; +} + +static int bt_has_cp_read_preset_rsp( + struct has_client *client, const struct has_preset *preset, bool is_last) +{ + struct bt_has_cp_hdr *hdr; + struct bt_has_cp_read_preset_rsp *rsp; + + NET_BUF_SIMPLE_DEFINE(buf, + sizeof(*hdr) + sizeof(*rsp) + BT_HAS_PRESET_NAME_MAX); + + LOG_DBG("conn %p index 0x%02x prop 0x%02x %s is_last 0x%02x", + (void *)client->conn, preset->index, + preset->properties, preset->name, is_last); + + hdr = net_buf_simple_add(&buf, sizeof(*hdr)); + hdr->opcode = BT_HAS_OP_READ_PRESET_RSP; + rsp = net_buf_simple_add(&buf, sizeof(*rsp)); + rsp->is_last = is_last ? 0x01 : 0x00; + rsp->index = preset->index; + rsp->properties = preset->properties; + net_buf_simple_add_mem(&buf, preset->name, strlen(preset->name)); + + return control_point_send(client, &buf); +} + +static void preset_changed_prepare(struct net_buf_simple *buf, + uint8_t change_id, uint8_t is_last) +{ + struct bt_has_cp_hdr *hdr; + struct bt_has_cp_preset_changed *preset_changed; + + hdr = net_buf_simple_add(buf, sizeof(*hdr)); + hdr->opcode = BT_HAS_OP_PRESET_CHANGED; + preset_changed = net_buf_simple_add(buf, sizeof(*preset_changed)); + preset_changed->change_id = change_id; + preset_changed->is_last = is_last; +} + +static int bt_has_cp_generic_update( + struct has_client *client, uint8_t prev_index, uint8_t index, + uint8_t properties, const char *name, uint8_t is_last) +{ + struct bt_has_cp_generic_update *generic_update; + + NET_BUF_SIMPLE_DEFINE(buf, + sizeof(struct bt_has_cp_hdr) + + sizeof(struct bt_has_cp_preset_changed) + + sizeof(struct bt_has_cp_generic_update) + + BT_HAS_PRESET_NAME_MAX); + + LOG_DBG("client %p prev_index 0x%02x index 0x%02x prop 0x%02x %s is_last " + "%d", + client, prev_index, + index, properties, name, is_last); + + preset_changed_prepare(&buf, BT_HAS_CHANGE_ID_GENERIC_UPDATE, is_last); + + generic_update = net_buf_simple_add(&buf, sizeof(*generic_update)); + generic_update->prev_index = prev_index; + generic_update->index = index; + generic_update->properties = properties; + net_buf_simple_add_mem(&buf, name, strlen(name)); + + if (client) + { + return control_point_send(client, &buf); + } + else + { + return control_point_send_all(&buf); + } +} + +#if defined(CONFIG_BT_SETTINGS) +struct client_context_store +{ + /* Last notified preset index */ + + uint8_t last_preset_index_known; +} __packed; + +static int settings_set_cb(const char *name, + size_t len_rd, settings_read_cb read_cb, + void *cb_arg) +{ + struct client_context_store store; + struct client_context *context; + bt_addr_le_t addr; + ssize_t len; + int err; + + if (!name) + { + LOG_ERR("Insufficient number of arguments"); + return -EINVAL; + } + + err = bt_settings_decode_key(name, &addr); + if (err != 0) + { + LOG_ERR("Unable to decode address %s", name); + return -EINVAL; + } + + context = context_find(&addr); + if (context == NULL) + { + /* Find and initialize a free entry */ + + context = context_alloc(&addr); + if (context == NULL) + { + LOG_ERR("Failed to allocate client_context for %s", + bt_addr_le_str(&addr)); + return -ENOMEM; + } + } + + if (len_rd) + { + len = read_cb(cb_arg, &store, sizeof(store)); + if (len < 0) + { + LOG_ERR("Failed to decode value (err %zd)", len); + return len; + } + + context->last_preset_index_known = store.last_preset_index_known; + } + else + { + context->last_preset_index_known = 0x00u; + } + + /* Notify all the characteristics values after reboot */ + + atomic_set(context->flags, BONDED_CLIENT_INIT_FLAGS); + + return 0; +} + +static BT_SETTINGS_DEFINE(has, "has", settings_set_cb, NULL); + +static void store_client_context(struct client_context *context) +{ + struct client_context_store store = + { + .last_preset_index_known = context->last_preset_index_known, + }; + + int err; + + LOG_DBG("%s last_preset_index_known " + "0x%02x", bt_addr_le_str(&context->addr), + store.last_preset_index_known); + + err = bt_settings_store("has", 0, &context->addr, &store, sizeof(store)); + if (err != 0) + { + LOG_ERR("Failed to store err %d", err); + } +} +#else +#define store_client_context(...) +#endif /* CONFIG_BT_SETTINGS */ + +static void update_last_preset_index_known(struct has_client *client, + uint8_t index) +{ + if (client != NULL && client->context != NULL && + client->context->last_preset_index_known != index) + { + client->context->last_preset_index_known = index; + store_client_context(client->context); + return; + } + + for (size_t i = 0U; i < ARRAY_SIZE(has_client_list); i++) + { + client = &has_client_list[i]; + + /* For each connected client */ + + if (client->conn != NULL && client->context != NULL && + client->context->last_preset_index_known != index) + { + client->context->last_preset_index_known = index; + store_client_context(client->context); + } + } +} + +static int read_preset_response(struct has_client *client) +{ + const struct has_preset *preset = NULL; + bool is_last = true; + int err; + + DEBUGASSERT(client != NULL); + + err = preset_foreach(client->read_presets_req.start_index, + BT_HAS_PRESET_INDEX_LAST, preset_found, + &preset); + DEBUGASSERT(err != -EINVAL); + + if (unlikely(preset == NULL)) + { + return bt_has_cp_read_preset_rsp(client, NULL, BT_HAS_IS_LAST); + } + + if (client->read_presets_req.num_presets > 1) + { + const struct has_preset *next = NULL; + + err = preset_foreach(preset->index + 1, BT_HAS_PRESET_INDEX_LAST, + preset_found, &next); + DEBUGASSERT(err != -EINVAL); + + is_last = next == NULL; + } + + err = bt_has_cp_read_preset_rsp(client, preset, is_last); + if (err != 0) + { + return err; + } + + if (preset->index > client->context->last_preset_index_known) + { + update_last_preset_index_known(client, preset->index); + } + + if (!is_last) + { + client->read_presets_req.start_index = preset->index + 1; + client->read_presets_req.num_presets--; + + atomic_set_bit(client->context->flags, + FLAG_PENDING_READ_PRESET_RESPONSE); + notify_work_reschedule(client, + USEC2TICK(BT_AUDIO_NOTIFY_RETRY_DELAY_US)); + } + + return 0; +} + +static int bt_has_cp_preset_record_deleted(struct has_client *client, + uint8_t index) +{ + NET_BUF_SIMPLE_DEFINE(buf, sizeof(struct bt_has_cp_hdr) + + sizeof(struct bt_has_cp_preset_changed) + sizeof(uint8_t)); + + LOG_DBG("client %p index 0x%02x", client, index); + + preset_changed_prepare(&buf, BT_HAS_CHANGE_ID_PRESET_DELETED, + BT_HAS_IS_LAST); + net_buf_simple_add_u8(&buf, index); + + if (client != NULL) + { + return control_point_send(client, &buf); + } + else + { + return control_point_send_all(&buf); + } +} + +/* Generic Update the last (already deleted) preset */ + +static int preset_list_changed_generic_update_tail(struct has_client *client) +{ + const struct has_preset *prev; + struct has_preset last = + { + /* The index value of the last preset the client knew about. */ + + .index = client->context->last_preset_index_known, + + /* As the properties of deleted preset is not available anymore, we + * set this value to 0x00 meaning the preset is unavailable and + * non-writable which is actually true + */ + + .properties = BT_HAS_PROP_NONE, + + /* As the name of deleted preset are not available anymore, we set + * this value to the value what is compliant with specification. As + * per HAS_v1.0 the Name is 1-40 octet value. + */ + + .name = "N/A", + }; + + int err; + + prev = preset_get_tail(); + + err = bt_has_cp_generic_update(client, + prev ? prev->index : + BT_HAS_PRESET_INDEX_NONE, + last.index, + last.properties, last.name, false); + if (err != 0) + { + return err; + } + + return 0; +} + +static int preset_list_changed_record_deleted_last(struct has_client *client) +{ + const struct has_preset *last; + int err; + + err = bt_has_cp_preset_record_deleted(client, + client->context->last_preset_index_known); + if (err != 0) + { + return err; + } + + last = preset_get_tail(); + + update_last_preset_index_known(client, + last ? last->index : BT_HAS_PRESET_INDEX_NONE); + + return 0; +} + +static int preset_list_changed(struct has_client *client) +{ + const struct has_preset *preset = NULL; + const struct has_preset *next = NULL; + bool is_last = true; + int err; + + if (list_is_empty(&preset_list)) + { + /* The preset list is empty. We need to indicate deletion of all + * presets + */ + + atomic_set_bit(client->context->flags, + FLAG_NOTIFY_PRESET_LIST_GENERIC_UPDATE_TAIL); + atomic_set_bit(client->context->flags, + FLAG_NOTIFY_PRESET_LIST_RECORD_DELETED_LAST); + notify_work_reschedule(client, + USEC2TICK(BT_AUDIO_NOTIFY_RETRY_DELAY_US)); + + return 0; + } + + err = preset_foreach(client->preset_changed_index_next, + BT_HAS_PRESET_INDEX_LAST, preset_found, + &preset); + DEBUGASSERT(err != -EINVAL); + + if (preset == NULL) + { + return 0; + } + + err = preset_foreach(preset->index + 1, BT_HAS_PRESET_INDEX_LAST, + preset_found, &next); + DEBUGASSERT(err != -EINVAL); + + /* It is last Preset Changed notification if there are no presets left to + * notify and the currently notified preset have the highest index known to + * the client. + */ + + is_last = next == NULL && + preset->index >= client->context->last_preset_index_known; + + err = bt_has_cp_generic_update(client, preset_get_prev_index(preset), + preset->index, + preset->properties, preset->name, is_last); + if (err != 0) + { + return err; + } + + if (is_last) + { + client->preset_changed_index_next = 0U; + + /* It's the last preset notified, so update the highest index known to + * the client + */ + + update_last_preset_index_known(client, preset->index); + + return 0; + } + + if (next == NULL) + { + /* If we end up here, the last preset known to the client has been + * removed. As we do not hold the information about the deleted + * presets, we need to use Generic Update procedure to: 1. Notify the + * presets that have been removed in range (PrevIndex = + * current_preset_last, Index=previous_preset_last) 2. Notify deletion + * of preset Index=previous_preset_last. + */ + + atomic_set_bit(client->context->flags, + FLAG_NOTIFY_PRESET_LIST_GENERIC_UPDATE_TAIL); + atomic_set_bit(client->context->flags, + FLAG_NOTIFY_PRESET_LIST_RECORD_DELETED_LAST); + } + else + { + client->preset_changed_index_next = preset->index + 1; + + atomic_set_bit(client->context->flags, FLAG_NOTIFY_PRESET_LIST); + } + + notify_work_reschedule(client, USEC2TICK(BT_AUDIO_NOTIFY_RETRY_DELAY_US)); + + return 0; +} + +static uint8_t handle_read_preset_req(struct bt_conn *conn, + struct net_buf_simple *buf) +{ + const struct bt_has_cp_read_presets_req *req; + const struct has_preset *preset = NULL; + struct has_client *client; + __maybe_unused int err; + + if (buf->len < sizeof(*req)) + { + return BT_HAS_ERR_INVALID_PARAM_LEN; + } + + /* As per HAS_d1.0r00 Client Characteristic Configuration Descriptor + * Improperly Configured shall be returned if client writes Read Presets + * Request but is not registered for indications. + */ + + if (!bt_gatt_is_subscribed(conn, preset_control_point_attr, + BT_GATT_CCC_INDICATE)) + { + return BT_ATT_ERR_CCC_IMPROPER_CONF; + } + + client = client_find_by_conn(conn); + if (client == NULL) + { + return BT_ATT_ERR_UNLIKELY; + } + + req = net_buf_simple_pull_mem(buf, sizeof(*req)); + + LOG_DBG("start_index %d num_presets %d", req->start_index, + req->num_presets); + + /* Abort if there is no preset in requested index range */ + + err = preset_foreach(req->start_index, BT_HAS_PRESET_INDEX_LAST, + preset_found, &preset); + DEBUGASSERT(err != -EINVAL); + + if (preset == NULL) + { + return BT_ATT_ERR_OUT_OF_RANGE; + } + + /* Reject if already in progress */ + + if (atomic_test_bit(client->context->flags, + FLAG_PENDING_READ_PRESET_RESPONSE)) + { + return BT_HAS_ERR_OPERATION_NOT_POSSIBLE; + } + + /* Store the request */ + + client->read_presets_req.start_index = req->start_index; + client->read_presets_req.num_presets = req->num_presets; + + notify(client, FLAG_PENDING_READ_PRESET_RESPONSE); + + return 0; +} + +static int set_preset_name(uint8_t index, const char *name, size_t len) +{ + struct has_preset *preset = NULL; + __maybe_unused int err; + + LOG_DBG("index %d name_len %zu", index, len); + + if (len < BT_HAS_PRESET_NAME_MIN || len > BT_HAS_PRESET_NAME_MAX) + { + return -EINVAL; + } + + /* Abort if there is no preset in requested index range */ + + err = preset_foreach(index, BT_HAS_PRESET_INDEX_LAST, preset_found, + &preset); + DEBUGASSERT(err != -EINVAL); + + if (preset == NULL) + { + return -ENOENT; + } + + if (!(preset->properties & BT_HAS_PROP_WRITABLE)) + { + return -EPERM; + } + + IF_ENABLED(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC, + (DEBUGASSERT(len < ARRAY_SIZE(preset->name)); + + (void)memcpy(preset->name, name, len); + + /* NULL-terminate string */ + + preset->name[len] = '\0'; + + /* Properly truncate a NULL-terminated UTF-8 string */ + + utf8_trunc(preset->name); + )); + + if (preset->ops->name_changed) + { + preset->ops->name_changed(index, preset->name); + } + + return bt_has_cp_generic_update(NULL, preset_get_prev_index(preset), + preset->index, + preset->properties, preset->name, + BT_HAS_IS_LAST); +} + +static uint8_t handle_write_preset_name(struct bt_conn *conn, + struct net_buf_simple *buf) +{ + const struct bt_has_cp_write_preset_name *req; + struct has_client *client; + int err; + + if (buf->len < sizeof(*req)) + { + return BT_HAS_ERR_INVALID_PARAM_LEN; + } + + /* As per HAS_v1.0 Client Characteristic Configuration Descriptor + * Improperly Configured shall be returned if client writes Write Preset + * Name opcode but is not registered for indications. + */ + + if (!bt_gatt_is_subscribed(conn, preset_control_point_attr, + BT_GATT_CCC_INDICATE)) + { + return BT_ATT_ERR_CCC_IMPROPER_CONF; + } + + client = client_find_by_conn(conn); + if (!client) + { + return BT_ATT_ERR_UNLIKELY; + } + + req = net_buf_simple_pull_mem(buf, sizeof(*req)); + + err = set_preset_name(req->index, (const char *)req->name, buf->len); + if (err == -EINVAL) + { + return BT_HAS_ERR_INVALID_PARAM_LEN; + } + else if (err == -ENOENT) + { + return BT_ATT_ERR_OUT_OF_RANGE; + } + else if (err == -EPERM) + { + return BT_HAS_ERR_WRITE_NAME_NOT_ALLOWED; + } + else if (err != 0) + { + return BT_ATT_ERR_UNLIKELY; + } + + return BT_ATT_ERR_SUCCESS; +} + +static void preset_set_active(struct has_preset *preset) +{ + if (active_preset != preset) + { + active_preset = preset; + + notify(NULL, FLAG_ACTIVE_INDEX_CHANGED); + } +} + +static uint8_t preset_select(struct has_preset *preset, bool sync) +{ + const int err = preset->ops->select(preset->index, sync); + + if (err == -EINPROGRESS) + { + /* User has to confirm once the requested preset becomes active by + * calling bt_has_preset_active_set. + */ + + return 0; + } + + if (err == -EBUSY) + { + return BT_HAS_ERR_OPERATION_NOT_POSSIBLE; + } + + if (err != 0) + { + return BT_ATT_ERR_UNLIKELY; + } + + preset_set_active(preset); + + return 0; +} + +static bool is_preset_available(const struct has_preset *preset) +{ + return (preset->properties & BT_HAS_PROP_AVAILABLE) != 0; +} + +static uint8_t handle_set_active_preset(struct net_buf_simple *buf, + bool sync) +{ + const struct bt_has_cp_set_active_preset *pdu; + struct has_preset *preset; + + if (buf->len < sizeof(*pdu)) + { + return BT_HAS_ERR_INVALID_PARAM_LEN; + } + + pdu = net_buf_simple_pull_mem(buf, sizeof(*pdu)); + + preset = preset_lookup_index(pdu->index); + if (preset == NULL) + { + return BT_ATT_ERR_OUT_OF_RANGE; + } + + if (!is_preset_available(preset)) + { + return BT_HAS_ERR_OPERATION_NOT_POSSIBLE; + } + + return preset_select(preset, sync); +} + +static uint8_t handle_set_next_preset(bool sync) +{ + struct has_preset *next; + struct has_preset *tmp; + + if (active_preset == NULL) + { + next = preset_get_head(); + } + else + { + next = preset_get_next(active_preset); + } + + tmp = next; + do + { + if (next == NULL) + { + break; + } + + if (is_preset_available(next)) + { + return preset_select(next, sync); + } + + next = preset_get_next(next); + } + while (tmp != next); + + return BT_HAS_ERR_OPERATION_NOT_POSSIBLE; +} + +static uint8_t handle_set_prev_preset(bool sync) +{ + struct has_preset *prev; + struct has_preset *tmp; + + if (active_preset == NULL) + { + prev = preset_get_tail(); + } + else + { + prev = preset_get_prev(active_preset); + } + + tmp = prev; + do + { + if (prev == NULL) + { + break; + } + + if (is_preset_available(prev)) + { + return preset_select(prev, sync); + } + + prev = preset_get_prev(prev); + } + while (tmp != prev); + + return BT_HAS_ERR_OPERATION_NOT_POSSIBLE; +} + +static uint8_t handle_control_point_op(struct bt_conn *conn, + struct net_buf_simple *buf) +{ + const struct bt_has_cp_hdr *hdr; + + hdr = net_buf_simple_pull_mem(buf, sizeof(*hdr)); + + LOG_DBG("conn %p opcode %s (0x%02x)", (void *)conn, + bt_has_op_str(hdr->opcode), hdr->opcode); + + switch (hdr->opcode) + { + case BT_HAS_OP_READ_PRESET_REQ: + return handle_read_preset_req(conn, buf); + case BT_HAS_OP_WRITE_PRESET_NAME: + if (IS_ENABLED(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC)) + { + return handle_write_preset_name(conn, buf); + } + else + { + return BT_HAS_ERR_WRITE_NAME_NOT_ALLOWED; + } + break; + case BT_HAS_OP_SET_ACTIVE_PRESET: + return handle_set_active_preset(buf, false); + case BT_HAS_OP_SET_NEXT_PRESET: + return handle_set_next_preset(false); + case BT_HAS_OP_SET_PREV_PRESET: + return handle_set_prev_preset(false); + case BT_HAS_OP_SET_ACTIVE_PRESET_SYNC: + if ((has.features & BT_HAS_FEAT_PRESET_SYNC_SUPP) != 0) + { + return handle_set_active_preset(buf, true); + } + else + { + return BT_HAS_ERR_PRESET_SYNC_NOT_SUPP; + } + + case BT_HAS_OP_SET_NEXT_PRESET_SYNC: + if ((has.features & BT_HAS_FEAT_PRESET_SYNC_SUPP) != 0) + { + return handle_set_next_preset(true); + } + else + { + return BT_HAS_ERR_PRESET_SYNC_NOT_SUPP; + } + + case BT_HAS_OP_SET_PREV_PRESET_SYNC: + if ((has.features & BT_HAS_FEAT_PRESET_SYNC_SUPP) != 0) + { + return handle_set_prev_preset(true); + } + else + { + return BT_HAS_ERR_PRESET_SYNC_NOT_SUPP; + } + }; + + return BT_HAS_ERR_INVALID_OPCODE; +} + +static ssize_t write_control_point( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *data, + uint16_t len, uint16_t offset, uint8_t flags) +{ + struct net_buf_simple buf; + uint8_t err; + + LOG_DBG("conn %p attr %p data %p len %d offset %d flags 0x%02x", + (void *)conn, attr, data, len, + offset, flags); + + if (offset > 0) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (len == 0) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + net_buf_simple_init_with_data(&buf, (void *)data, len); + + err = handle_control_point_op(conn, &buf); + if (err != 0) + { + LOG_WRN("handle_control_point_op err 0x%02x", err); + return BT_GATT_ERR(err); + } + + return len; +} + +int bt_has_preset_register(const struct bt_has_preset_register_param *param) +{ + struct has_preset *preset; + size_t name_len; + + if (param == NULL) + { + LOG_ERR("param is NULL"); + return -EINVAL; + } + + if (param->index == BT_HAS_PRESET_INDEX_NONE) + { + LOG_ERR("param->index is invalid"); + return -EINVAL; + } + + if (param->name == NULL) + { + LOG_ERR("param->name is NULL"); + return -EINVAL; + } + + name_len = strlen(param->name); + + if (name_len < BT_HAS_PRESET_NAME_MIN) + { + LOG_ERR("param->name is too short (%zu < %u)", name_len, + BT_HAS_PRESET_NAME_MIN); + return -EINVAL; + } + + if (name_len > BT_HAS_PRESET_NAME_MAX) + { + LOG_WRN("param->name is too long (%zu > %u)", name_len, + BT_HAS_PRESET_NAME_MAX); + } + + if (param->ops == NULL) + { + LOG_ERR("param->ops is NULL"); + return -EINVAL; + } + + if (param->ops->select == NULL) + { + LOG_ERR("param->ops->select is NULL"); + return -EINVAL; + } + + preset = preset_lookup_index(param->index); + if (preset != NULL) + { + return -EALREADY; + } + + if (!IS_ENABLED(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC) && + (param->properties & BT_HAS_PROP_WRITABLE) > 0) + { + LOG_ERR("Writable presets are not supported"); + return -ENOTSUP; + } + + preset = preset_alloc(param->index, param->properties, param->name, + param->ops); + if (preset == NULL) + { + return -ENOMEM; + } + + if (preset == preset_get_tail()) + { + update_last_preset_index_known(NULL, preset->index); + } + + return bt_has_cp_generic_update(NULL, preset_get_prev_index(preset), + preset->index, + preset->properties, preset->name, + BT_HAS_IS_LAST); +} + +int bt_has_preset_unregister(uint8_t index) +{ + struct has_preset *preset; + int err; + + if (index == BT_HAS_PRESET_INDEX_NONE) + { + LOG_ERR("index is invalid"); + return -EINVAL; + } + + preset = preset_lookup_index(index); + if (preset == NULL) + { + return -ENOENT; + } + + if (preset == active_preset) + { + return -EADDRINUSE; + } + + err = bt_has_cp_preset_record_deleted(NULL, preset->index); + if (err != 0) + { + return err; + } + + if (preset == preset_get_tail()) + { + update_last_preset_index_known(NULL, preset_get_prev_index(preset)); + } + + preset_free(preset); + + return 0; +} + +static int set_preset_availability(uint8_t index, bool available) +{ + NET_BUF_SIMPLE_DEFINE(buf, sizeof(struct bt_has_cp_hdr) + + sizeof(struct bt_has_cp_preset_changed) + sizeof(uint8_t)); + struct has_preset *preset; + uint8_t change_id; + + if (index == BT_HAS_PRESET_INDEX_NONE) + { + LOG_ERR("index is invalid"); + return -EINVAL; + } + + preset = preset_lookup_index(index); + if (preset == NULL) + { + return -ENOENT; + } + + if (is_preset_available(preset) == available) + { + /* availability not changed */ + + return 0; + } + + preset->properties ^= BT_HAS_PROP_AVAILABLE; + + if (is_preset_available(preset)) + { + change_id = BT_HAS_CHANGE_ID_PRESET_AVAILABLE; + } + else + { + change_id = BT_HAS_CHANGE_ID_PRESET_UNAVAILABLE; + } + + preset_changed_prepare(&buf, change_id, BT_HAS_IS_LAST); + net_buf_simple_add_u8(&buf, preset->index); + + return control_point_send_all(&buf); +} + +int bt_has_preset_available(uint8_t index) +{ + return set_preset_availability(index, true); +} + +int bt_has_preset_unavailable(uint8_t index) +{ + return set_preset_availability(index, false); +} + +struct bt_has_preset_foreach_data +{ + bt_has_preset_func_t func; + void *user_data; +}; + +static bool bt_has_preset_foreach_func(const struct has_preset *preset, + void *user_data) +{ + const struct bt_has_preset_foreach_data *data = user_data; + + return data->func(preset->index, preset->properties, preset->name, + data->user_data); +} + +int bt_has_preset_foreach(uint8_t index, + bt_has_preset_func_t func, void *user_data) +{ + uint8_t start_index; + uint8_t end_index; + struct bt_has_preset_foreach_data data = + { + .func = func, + .user_data = user_data, + }; + + if (func == NULL) + { + LOG_DBG("func is NULL"); + return -EINVAL; + } + + if (index == BT_HAS_PRESET_INDEX_NONE) + { + start_index = BT_HAS_PRESET_INDEX_FIRST; + end_index = BT_HAS_PRESET_INDEX_LAST; + } + else + { + start_index = end_index = index; + } + + return preset_foreach(start_index, end_index, bt_has_preset_foreach_func, + &data); +} + +int bt_has_preset_active_set(uint8_t index) +{ + struct has_preset *preset; + + if (index == BT_HAS_PRESET_INDEX_NONE) + { + preset_set_active(NULL); + return 0; + } + + preset = preset_lookup_index(index); + if (preset == NULL) + { + return -ENOENT; + } + + if (!is_preset_available(preset)) + { + return -EINVAL; + } + + preset_set_active(preset); + + return 0; +} + +uint8_t bt_has_preset_active_get(void) +{ + if (active_preset == NULL) + { + return BT_HAS_PRESET_INDEX_NONE; + } + + return active_preset->index; +} + +int bt_has_preset_name_change(uint8_t index, const char *name) +{ + if (name == NULL) + { + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC)) + { + return set_preset_name(index, name, strlen(name)); + } + else + { + return -EOPNOTSUPP; + } +} +#endif /* CONFIG_BT_HAS_PRESET_SUPPORT */ + +static int +has_features_register(const struct bt_has_features_param *features) +{ + /* Initialize the supported features characteristic value */ + + has.features = features->type; + + if (IS_ENABLED(CONFIG_BT_HAS_PRESET_SUPPORT)) + { + has.features |= BT_HAS_FEAT_DYNAMIC_PRESETS; + + if (features->preset_sync_support) + { + if (features->type != BT_HAS_HEARING_AID_TYPE_BINAURAL) + { + LOG_DBG("Preset sync support only available " + "for binaural hearing aid type"); + return -EINVAL; + } + + has.features |= BT_HAS_FEAT_PRESET_SYNC_SUPP; + } + + if (features->independent_presets) + { + if (features->type != BT_HAS_HEARING_AID_TYPE_BINAURAL) + { + LOG_DBG("Independent presets only available " + "for binaural hearing aid type"); + return -EINVAL; + } + + has.features |= BT_HAS_FEAT_INDEPENDENT_PRESETS; + } + } + + if (IS_ENABLED(CONFIG_BT_HAS_PRESET_NAME_DYNAMIC)) + { + has.features |= BT_HAS_FEAT_WRITABLE_PRESETS_SUPP; + } + + return 0; +} + +#if defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE) +int bt_has_features_set(const struct bt_has_features_param *features) +{ + int err; + + if (!has.registered) + { + return -ENOTSUP; + } + + /* Check whether any features will change, otherwise we don't want to + * notify clients + */ + + if (FEATURE_DEVICE_TYPE_UNCHANGED(features->type) && + FEATURE_SYNC_SUPPORT_UNCHANGED(features->preset_sync_support) && + FEATURE_IND_PRESETS_UNCHANGED(features->independent_presets)) + { + return 0; + } + + err = has_features_register(features); + if (err != 0) + { + LOG_DBG("Failed to register features"); + return err; + } + + notify(NULL, FLAG_FEATURES_CHANGED); + + return 0; +} +#endif /* CONFIG_BT_HAS_FEATURES_NOTIFIABLE */ + +int bt_has_register(const struct bt_has_features_param *features) +{ + int err; + + LOG_DBG("features %p", features); + + if (!features) + { + LOG_DBG("NULL params pointer"); + return -EINVAL; + } + + if (has.registered) + { + return -EALREADY; + } + + err = has_features_register(features); + if (err != 0) + { + LOG_DBG("HAS service failed to register features: %d", err); + return err; + } + + has_svc = (struct bt_gatt_service)BT_GATT_SERVICE(has_attrs); + err = bt_gatt_service_register(&has_svc); + if (err != 0) + { + LOG_DBG("HAS service register failed: %d", err); + return err; + } + + if (IS_ENABLED(CONFIG_BT_HAS_FEATURES_NOTIFIABLE)) + { + hearing_aid_features_attr = bt_gatt_find_by_uuid(has_svc.attrs, + has_svc.attr_count, + BT_UUID_HAS_HEARING_AID_FEATURES); + DEBUGASSERT(hearing_aid_features_attr != NULL); + } + + if (IS_ENABLED(CONFIG_BT_HAS_PRESET_SUPPORT)) + { + preset_control_point_attr = bt_gatt_find_by_uuid(has_svc.attrs, + has_svc.attr_count, + BT_UUID_HAS_PRESET_CONTROL_POINT); + DEBUGASSERT(preset_control_point_attr != NULL); + + active_preset_index_attr = bt_gatt_find_by_uuid(has_svc.attrs, + has_svc.attr_count, + BT_UUID_HAS_ACTIVE_PRESET_INDEX); + DEBUGASSERT(active_preset_index_attr != NULL); + } + +#if defined(CONFIG_BT_HAS_PRESET_SUPPORT) + for (size_t i = 0U; i < ARRAY_SIZE(preset_pool); i++) + { + struct has_preset *preset = &preset_pool[i]; + + list_add_tail(&preset_free_list, &preset->node); + } +#endif /* CONFIG_BT_HAS_PRESET_SUPPORT */ + +#if defined(CONFIG_BT_HAS_PRESET_SUPPORT) || defined(CONFIG_BT_HAS_FEATURES_NOTIFIABLE) + err = bt_conn_auth_info_cb_register(&auth_info_cb); + DEBUGASSERT(err == 0); +#endif /* CONFIG_BT_HAS_PRESET_SUPPORT || CONFIG_BT_HAS_FEATURES_NOTIFIABLE */ + + has.registered = true; + + return 0; +} diff --git a/libs/zblue/audio/has_client.c b/libs/zblue/audio/has_client.c new file mode 100644 index 0000000000000..a3a4bcfb8487b --- /dev/null +++ b/libs/zblue/audio/has_client.c @@ -0,0 +1,1230 @@ +/**************************************************************************** + * libs/zblue/audio/has_client.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Codecoup + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/bt_str.h" +#include "has_internal.h" + +LOG_MODULE_REGISTER(bt_has_client, CONFIG_BT_HAS_CLIENT_LOG_LEVEL); + +#define HAS_INST(_has) CONTAINER_OF(_has, struct bt_has_client, has) +#define HANDLE_IS_VALID(handle) ((handle) != 0x0000) +static struct bt_has_client clients[CONFIG_BT_MAX_CONN]; +static const struct bt_has_client_cb *client_cb; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static struct bt_has_client *inst_by_conn(struct bt_conn *conn) +{ + struct bt_has_client *inst = &clients[bt_conn_index(conn)]; + + if (inst->conn == conn) + { + return inst; + } + + return NULL; +} + +static void inst_cleanup(struct bt_has_client *inst) +{ + bt_conn_unref(inst->conn); + + (void)memset(inst, 0, sizeof(*inst)); +} + +static enum bt_has_capabilities get_capabilities( + const struct bt_has_client *inst) +{ + enum bt_has_capabilities caps = 0; + + /* The Control Point support is optional, as the server might have no + * presets support + */ + + if (HANDLE_IS_VALID(inst->control_point_subscription.value_handle)) + { + caps |= BT_HAS_PRESET_SUPPORT; + } + + return caps; +} + +static void handle_read_preset_rsp(struct bt_has_client *inst, + struct net_buf_simple *buf) +{ + const struct bt_has_cp_read_preset_rsp *pdu; + struct bt_has_preset_record record; + char name[BT_HAS_PRESET_NAME_MAX + 1]; /* + 1 byte for null-terminator */ + size_t name_len; + + LOG_DBG("conn %p buf %p", (void *)inst->conn, buf); + + if (buf->len < sizeof(*pdu)) + { + LOG_ERR("malformed PDU"); + return; + } + + pdu = net_buf_simple_pull_mem(buf, sizeof(*pdu)); + + if (pdu->is_last > BT_HAS_IS_LAST) + { + LOG_WRN("unexpected is_last value 0x%02x", pdu->is_last); + } + + record.index = pdu->index; + record.properties = pdu->properties; + record.name = name; + + name_len = buf->len + 1; /* + 1 byte for NULL terminator */ + if (name_len > ARRAY_SIZE(name)) + { + LOG_WRN("name is too long (%zu > %u)", buf->len, + BT_HAS_PRESET_NAME_MAX); + + name_len = ARRAY_SIZE(name); + } + + utf8_lcpy(name, (const char *)pdu->name, name_len); + + client_cb->preset_read_rsp(&inst->has, 0, &record, !!pdu->is_last); +} + +static void handle_generic_update( + struct bt_has_client *inst, struct net_buf_simple *buf, bool is_last) +{ + const struct bt_has_cp_generic_update *pdu; + struct bt_has_preset_record record; + char name[BT_HAS_PRESET_NAME_MAX + 1]; /* + 1 byte for null-terminator */ + size_t name_len; + + if (buf->len < sizeof(*pdu)) + { + LOG_ERR("malformed PDU"); + return; + } + + pdu = net_buf_simple_pull_mem(buf, sizeof(*pdu)); + + record.index = pdu->index; + record.properties = pdu->properties; + record.name = name; + + name_len = buf->len + 1; /* + 1 byte for NULL terminator */ + if (name_len > ARRAY_SIZE(name)) + { + LOG_WRN("name is too long (%zu > %u)", buf->len, + BT_HAS_PRESET_NAME_MAX); + + name_len = ARRAY_SIZE(name); + } + + utf8_lcpy(name, (const char *)pdu->name, name_len); + + client_cb->preset_update(&inst->has, pdu->prev_index, &record, is_last); +} + +static void handle_preset_deleted( + struct bt_has_client *inst, struct net_buf_simple *buf, bool is_last) +{ + if (buf->len < sizeof(uint8_t)) + { + LOG_ERR("malformed PDU"); + return; + } + + client_cb->preset_deleted(&inst->has, + net_buf_simple_pull_u8(buf), + is_last); +} + +static void handle_preset_availability( + struct bt_has_client *inst, struct net_buf_simple *buf, bool available, + bool is_last) +{ + if (buf->len < sizeof(uint8_t)) + { + LOG_ERR("malformed PDU"); + return; + } + + client_cb->preset_availability(&inst->has, net_buf_simple_pull_u8(buf), + available, is_last); +} + +static void handle_preset_changed(struct bt_has_client *inst, + struct net_buf_simple *buf) +{ + const struct bt_has_cp_preset_changed *pdu; + + LOG_DBG("conn %p buf %p", (void *)inst->conn, buf); + + if (buf->len < sizeof(*pdu)) + { + LOG_ERR("malformed PDU"); + return; + } + + pdu = net_buf_simple_pull_mem(buf, sizeof(*pdu)); + + if (pdu->is_last > BT_HAS_IS_LAST) + { + LOG_WRN("unexpected is_last 0x%02x", pdu->is_last); + } + + switch (pdu->change_id) + { + case BT_HAS_CHANGE_ID_GENERIC_UPDATE: + if (client_cb->preset_update) + { + handle_generic_update(inst, buf, !!pdu->is_last); + } + break; + case BT_HAS_CHANGE_ID_PRESET_DELETED: + if (client_cb->preset_deleted) + { + handle_preset_deleted(inst, buf, !!pdu->is_last); + } + break; + case BT_HAS_CHANGE_ID_PRESET_AVAILABLE: + if (client_cb->preset_availability) + { + handle_preset_availability(inst, buf, !!pdu->is_last, true); + } + + return; + case BT_HAS_CHANGE_ID_PRESET_UNAVAILABLE: + if (client_cb->preset_availability) + { + handle_preset_availability(inst, buf, !!pdu->is_last, false); + } + + return; + default: + LOG_WRN("unknown change_id 0x%02x", pdu->change_id); + } +} + +static uint8_t control_point_notify_cb(struct bt_conn *conn, + struct bt_gatt_subscribe_params *params, + const void *data, + uint16_t len) +{ + struct bt_has_client *inst = + CONTAINER_OF(params, + struct bt_has_client, + control_point_subscription); + const struct bt_has_cp_hdr *hdr; + struct net_buf_simple buf; + + LOG_DBG("conn %p params %p data %p len %u", (void *)conn, params, data, + len); + + if (!conn) + { + /* Unpaired, continue receiving notifications */ + + return BT_GATT_ITER_CONTINUE; + } + + if (!data) + { + /* Unsubscribed */ + + params->value_handle = 0u; + + return BT_GATT_ITER_STOP; + } + + if (len < sizeof(*hdr)) + { + /* Ignore malformed notification */ + + return BT_GATT_ITER_CONTINUE; + } + + net_buf_simple_init_with_data(&buf, (void *)data, len); + + hdr = net_buf_simple_pull_mem(&buf, sizeof(*hdr)); + + switch (hdr->opcode) + { + case BT_HAS_OP_READ_PRESET_RSP: + handle_read_preset_rsp(inst, &buf); + break; + case BT_HAS_OP_PRESET_CHANGED: + handle_preset_changed(inst, &buf); + break; + }; + + return BT_GATT_ITER_CONTINUE; +} + +static void discover_complete(struct bt_has_client *inst) +{ + LOG_DBG("conn %p", (void *)inst->conn); + + atomic_clear_bit(inst->flags, HAS_CLIENT_DISCOVER_IN_PROGRESS); + + client_cb->discover(inst->conn, 0, &inst->has, + inst->has.features & BT_HAS_FEAT_HEARING_AID_TYPE_MASK, + get_capabilities(inst)); + + /* If Active Preset Index supported, notify it's value */ + + if (client_cb->preset_switch && + HANDLE_IS_VALID(inst->active_index_subscription.value_handle)) + { + client_cb->preset_switch(&inst->has, 0, inst->has.active_index); + } +} + +static void discover_failed(struct bt_conn *conn, int err) +{ + LOG_DBG("conn %p", (void *)conn); + + client_cb->discover(conn, err, NULL, 0, 0); +} + +static int cp_write(struct bt_has_client *inst, struct net_buf_simple *buf, + bt_gatt_write_func_t func) +{ + const uint16_t value_handle = + inst->control_point_subscription.value_handle; + + if (!HANDLE_IS_VALID(value_handle)) + { + return -ENOTSUP; + } + + inst->params.write.func = func; + inst->params.write.handle = value_handle; + inst->params.write.offset = 0U; + inst->params.write.data = buf->data; + inst->params.write.length = buf->len; + + return bt_gatt_write(inst->conn, &inst->params.write); +} + +static void read_presets_req_cb(struct bt_conn *conn, uint8_t err, + struct bt_gatt_write_params *params) +{ + struct bt_has_client *inst = CONTAINER_OF(params, struct bt_has_client, + params.write); + + LOG_DBG("conn %p err 0x%02x param %p", (void *)conn, err, params); + + atomic_clear_bit(inst->flags, HAS_CLIENT_CP_OPERATION_IN_PROGRESS); + + if (err != 0) + { + client_cb->preset_read_rsp(&inst->has, err, NULL, true); + } +} + +static int read_presets_req(struct bt_has_client *inst, + uint8_t start_index, uint8_t num_presets) +{ + struct bt_has_cp_hdr *hdr; + struct bt_has_cp_read_presets_req *req; + + NET_BUF_SIMPLE_DEFINE(buf, sizeof(*hdr) + sizeof(*req)); + + LOG_DBG("conn %p start_index 0x%02x num_presets %d", (void *)inst->conn, + start_index, + num_presets); + + hdr = net_buf_simple_add(&buf, sizeof(*hdr)); + hdr->opcode = BT_HAS_OP_READ_PRESET_REQ; + req = net_buf_simple_add(&buf, sizeof(*req)); + req->start_index = start_index; + req->num_presets = num_presets; + + return cp_write(inst, &buf, read_presets_req_cb); +} + +static void set_active_preset_cb(struct bt_conn *conn, uint8_t err, + struct bt_gatt_write_params *params) +{ + struct bt_has_client *inst = CONTAINER_OF(params, struct bt_has_client, + params.write); + + LOG_DBG("conn %p err 0x%02x param %p", (void *)conn, err, params); + + atomic_clear_bit(inst->flags, HAS_CLIENT_CP_OPERATION_IN_PROGRESS); + + if (err != 0) + { + client_cb->preset_switch(&inst->has, err, inst->has.active_index); + } +} + +static int preset_set(struct bt_has_client *inst, + uint8_t opcode, uint8_t index) +{ + struct bt_has_cp_hdr *hdr; + struct bt_has_cp_set_active_preset *req; + + NET_BUF_SIMPLE_DEFINE(buf, sizeof(*hdr) + sizeof(*req)); + + LOG_DBG("conn %p opcode 0x%02x index 0x%02x", (void *)inst->conn, opcode, + index); + + hdr = net_buf_simple_add(&buf, sizeof(*hdr)); + hdr->opcode = opcode; + req = net_buf_simple_add(&buf, sizeof(*req)); + req->index = index; + + return cp_write(inst, &buf, set_active_preset_cb); +} + +static int preset_set_next_or_prev(struct bt_has_client *inst, + uint8_t opcode) +{ + struct bt_has_cp_hdr *hdr; + + NET_BUF_SIMPLE_DEFINE(buf, sizeof(*hdr)); + + LOG_DBG("conn %p opcode 0x%02x", (void *)inst->conn, opcode); + + hdr = net_buf_simple_add(&buf, sizeof(*hdr)); + hdr->opcode = opcode; + + return cp_write(inst, &buf, set_active_preset_cb); +} + +static uint8_t active_index_update(struct bt_has_client *inst, + const void *data, uint16_t len) +{ + struct net_buf_simple buf; + const uint8_t prev = inst->has.active_index; + + net_buf_simple_init_with_data(&buf, (void *)data, len); + + inst->has.active_index = net_buf_simple_pull_u8(&buf); + + LOG_DBG("conn %p index " + "0x%02x", (void *)inst->conn, inst->has.active_index); + + return prev; +} + +static uint8_t active_preset_notify_cb(struct bt_conn *conn, + struct bt_gatt_subscribe_params *params, + const void *data, + uint16_t len) +{ + struct bt_has_client *inst = + CONTAINER_OF(params, + struct bt_has_client, + active_index_subscription); + uint8_t prev; + + LOG_DBG("conn %p params %p data %p len %u", (void *)conn, params, data, + len); + + if (!conn) + { + /* Unpaired, stop receiving notifications from device */ + + return BT_GATT_ITER_STOP; + } + + if (!data) + { + /* Unsubscribed */ + + params->value_handle = 0u; + + return BT_GATT_ITER_STOP; + } + + if (len == 0) + { + /* Ignore empty notification */ + + return BT_GATT_ITER_CONTINUE; + } + + prev = active_index_update(inst, data, len); + + if (atomic_test_bit(inst->flags, HAS_CLIENT_DISCOVER_IN_PROGRESS)) + { + /* Got notification during discovery process, postpone the active_index + * callback until discovery is complete. + */ + + return BT_GATT_ITER_CONTINUE; + } + + if (client_cb + && client_cb->preset_switch + && inst->has.active_index != prev) + { + client_cb->preset_switch(&inst->has, 0, inst->has.active_index); + } + + return BT_GATT_ITER_CONTINUE; +} + +static void active_index_subscribe_cb(struct bt_conn *conn, uint8_t att_err, + struct + bt_gatt_subscribe_params *params) +{ + struct bt_has_client *inst = + CONTAINER_OF(params, + struct bt_has_client, + active_index_subscription); + + LOG_DBG("conn %p att_err 0x%02x params %p", (void *)inst->conn, att_err, + params); + + if (att_err != BT_ATT_ERR_SUCCESS) + { + /* Cleanup instance so that it can be reused */ + + inst_cleanup(inst); + + discover_failed(conn, att_err); + } + else + { + discover_complete(inst); + } +} + +static int active_index_subscribe(struct bt_has_client *inst, + uint16_t value_handle) +{ + int err; + + LOG_DBG("conn %p handle 0x%04x", (void *)inst->conn, value_handle); + + inst->active_index_subscription.notify = active_preset_notify_cb; + inst->active_index_subscription.subscribe = active_index_subscribe_cb; + inst->active_index_subscription.value_handle = value_handle; + inst->active_index_subscription.ccc_handle = + BT_GATT_AUTO_DISCOVER_CCC_HANDLE; + inst->active_index_subscription.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + inst->active_index_subscription.disc_params = &inst->params.discover; + inst->active_index_subscription.value = BT_GATT_CCC_NOTIFY; + atomic_set_bit(inst->active_index_subscription.flags, + BT_GATT_SUBSCRIBE_FLAG_VOLATILE); + + err = bt_gatt_subscribe(inst->conn, &inst->active_index_subscription); + if (err != 0 && err != -EALREADY) + { + return err; + } + + return 0; +} + +static uint8_t active_index_read_cb(struct bt_conn *conn, + uint8_t att_err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t len) +{ + struct bt_has_client *inst = CONTAINER_OF(params, struct bt_has_client, + params.read); + int err = att_err; + + LOG_DBG("conn %p att_err 0x%02x params %p data %p len %u", (void *)conn, + att_err, params, data, + len); + + if (att_err != BT_ATT_ERR_SUCCESS || len == 0) + { + goto fail; + } + + active_index_update(inst, data, len); + + err = active_index_subscribe(inst, params->by_uuid.start_handle); + if (err != 0) + { + LOG_ERR("Subscribe failed (err %d)", err); + goto fail; + } + + return BT_GATT_ITER_STOP; + +fail: + + /* Cleanup instance so that it can be reused */ + + inst_cleanup(inst); + + discover_failed(conn, err); + + return BT_GATT_ITER_STOP; +} + +static int active_index_read(struct bt_has_client *inst) +{ + LOG_DBG("conn %p", (void *)inst->conn); + + (void)memset(&inst->params.read, 0, sizeof(inst->params.read)); + + (void)memcpy(&inst->params.uuid, BT_UUID_HAS_ACTIVE_PRESET_INDEX, + sizeof(inst->params.uuid)); + inst->params.read.func = active_index_read_cb; + inst->params.read.handle_count = 0u; + inst->params.read.by_uuid.uuid = &inst->params.uuid.uuid; + inst->params.read.by_uuid.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; + inst->params.read.by_uuid.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + + return bt_gatt_read(inst->conn, &inst->params.read); +} + +static void control_point_subscribe_cb(struct bt_conn *conn, + uint8_t att_err, + struct bt_gatt_subscribe_params *params) +{ + struct bt_has_client *inst = + CONTAINER_OF(params, + struct bt_has_client, + control_point_subscription); + int err = att_err; + + LOG_DBG("conn %p att_err 0x%02x", (void *)inst->conn, att_err); + + if (att_err != BT_ATT_ERR_SUCCESS) + { + goto fail; + } + + err = active_index_read(inst); + if (err != 0) + { + LOG_ERR("Active Preset Index read failed (err %d)", err); + goto fail; + } + + return; + +fail: + + /* Cleanup instance so that it can be reused */ + + inst_cleanup(inst); + + discover_failed(conn, err); +} + +static int control_point_subscribe( + struct bt_has_client *inst, uint16_t value_handle, uint8_t properties) +{ + int err; + + LOG_DBG("conn %p handle 0x%04x", (void *)inst->conn, value_handle); + + inst->control_point_subscription.notify = control_point_notify_cb; + inst->control_point_subscription.subscribe = control_point_subscribe_cb; + inst->control_point_subscription.value_handle = value_handle; + inst->control_point_subscription.ccc_handle = + BT_GATT_AUTO_DISCOVER_CCC_HANDLE; + inst->control_point_subscription.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + inst->control_point_subscription.disc_params = &inst->params.discover; + atomic_set_bit(inst->control_point_subscription.flags, + BT_GATT_SUBSCRIBE_FLAG_VOLATILE); + + if (IS_ENABLED(CONFIG_BT_EATT) && properties & BT_GATT_CHRC_NOTIFY) + { + inst->control_point_subscription.value = + BT_GATT_CCC_INDICATE | BT_GATT_CCC_NOTIFY; + } + else + { + inst->control_point_subscription.value = BT_GATT_CCC_INDICATE; + } + + err = bt_gatt_subscribe(inst->conn, &inst->control_point_subscription); + if (err != 0 && err != -EALREADY) + { + return err; + } + + return 0; +} + +static uint8_t control_point_discover_cb( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_has_client *inst = CONTAINER_OF(params, struct bt_has_client, + params.discover); + const struct bt_gatt_chrc *chrc; + int err; + + LOG_DBG("conn %p attr %p params %p", (void *)conn, attr, params); + + if (!attr) + { + LOG_INF("Control Point not found"); + discover_complete(inst); + return BT_GATT_ITER_STOP; + } + + chrc = attr->user_data; + + err = control_point_subscribe(inst, chrc->value_handle, chrc->properties); + if (err != 0) + { + LOG_ERR("Subscribe failed (err %d)", err); + + /* Cleanup instance so that it can be reused */ + + inst_cleanup(inst); + + discover_failed(conn, err); + } + + return BT_GATT_ITER_STOP; +} + +static int control_point_discover(struct bt_has_client *inst) +{ + LOG_DBG("conn %p", (void *)inst->conn); + + (void)memset(&inst->params.discover, 0, sizeof(inst->params.discover)); + + (void)memcpy(&inst->params.uuid, BT_UUID_HAS_PRESET_CONTROL_POINT, + sizeof(inst->params.uuid)); + inst->params.discover.uuid = &inst->params.uuid.uuid; + inst->params.discover.func = control_point_discover_cb; + inst->params.discover.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; + inst->params.discover.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + inst->params.discover.type = BT_GATT_DISCOVER_CHARACTERISTIC; + + return bt_gatt_discover(inst->conn, &inst->params.discover); +} + +static void features_update(struct bt_has_client *inst, + const void *data, uint16_t len) +{ + struct net_buf_simple buf; + + net_buf_simple_init_with_data(&buf, (void *)data, len); + + inst->has.features = net_buf_simple_pull_u8(&buf); + + LOG_DBG("conn %p features 0x%02x", (void *)inst->conn, inst->has.features); +} + +static uint8_t features_read_cb(struct bt_conn *conn, + uint8_t att_err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t len) +{ + struct bt_has_client *inst = CONTAINER_OF(params, struct bt_has_client, + params.read); + int err = att_err; + + LOG_DBG("conn %p att_err 0x%02x params %p data %p len %u", (void *)conn, + att_err, params, data, + len); + + if (att_err != BT_ATT_ERR_SUCCESS || len == 0) + { + goto fail; + } + + features_update(inst, data, len); + + if (!client_cb->preset_switch) + { + /* Complete the discovery if client is not interested in active preset + * changes + */ + + discover_complete(inst); + return BT_GATT_ITER_STOP; + } + + err = control_point_discover(inst); + if (err != 0) + { + LOG_ERR("Control Point discover failed (err %d)", err); + goto fail; + } + + return BT_GATT_ITER_STOP; + +fail: + + /* Cleanup instance so that it can be reused */ + + inst_cleanup(inst); + + discover_failed(conn, err); + + return BT_GATT_ITER_STOP; +} + +static int features_read(struct bt_has_client *inst, uint16_t value_handle) +{ + LOG_DBG("conn %p handle 0x%04x", (void *)inst->conn, value_handle); + + (void)memset(&inst->params.read, 0, sizeof(inst->params.read)); + + inst->params.read.func = features_read_cb; + inst->params.read.handle_count = 1u; + inst->params.read.single.handle = value_handle; + inst->params.read.single.offset = 0u; + + return bt_gatt_read(inst->conn, &inst->params.read); +} + +static void features_subscribe_cb(struct bt_conn *conn, uint8_t att_err, + struct bt_gatt_subscribe_params *params) +{ + struct bt_has_client *inst = CONTAINER_OF(params, struct bt_has_client, + features_subscription); + int err = att_err; + + LOG_DBG("conn %p att_err 0x%02x params %p", (void *)conn, att_err, params); + + if (att_err != BT_ATT_ERR_SUCCESS) + { + goto fail; + } + + err = features_read(inst, inst->features_subscription.value_handle); + if (err != 0) + { + LOG_ERR("Read failed (err %d)", err); + goto fail; + } + + return; + +fail: + + /* Cleanup instance so that it can be reused */ + + inst_cleanup(inst); + + discover_failed(conn, err); +} + +static uint8_t features_notify_cb( + struct bt_conn *conn, struct bt_gatt_subscribe_params *params, + const void *data, uint16_t len) +{ + struct bt_has_client *inst = CONTAINER_OF(params, struct bt_has_client, + features_subscription); + + LOG_DBG("conn %p params %p data %p len %u", (void *)conn, params, data, + len); + + if (!conn) + { + /* Unpaired, stop receiving notifications from device */ + + return BT_GATT_ITER_STOP; + } + + if (!data) + { + /* Unsubscribed */ + + params->value_handle = 0u; + + return BT_GATT_ITER_STOP; + } + + if (len == 0) + { + /* Ignore empty notification */ + + return BT_GATT_ITER_CONTINUE; + } + + features_update(inst, data, len); + + return BT_GATT_ITER_CONTINUE; +} + +static int features_subscribe(struct bt_has_client *inst, + uint16_t value_handle) +{ + int err; + + LOG_DBG("conn %p handle 0x%04x", (void *)inst->conn, value_handle); + + inst->features_subscription.notify = features_notify_cb; + inst->features_subscription.subscribe = features_subscribe_cb; + inst->features_subscription.value_handle = value_handle; + inst->features_subscription.ccc_handle = BT_GATT_AUTO_DISCOVER_CCC_HANDLE; + inst->features_subscription.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + inst->features_subscription.disc_params = &inst->params.discover; + inst->features_subscription.value = BT_GATT_CCC_NOTIFY; + atomic_set_bit(inst->features_subscription.flags, + BT_GATT_SUBSCRIBE_FLAG_VOLATILE); + + err = bt_gatt_subscribe(inst->conn, &inst->features_subscription); + if (err != 0 && err != -EALREADY) + { + return err; + } + + return 0; +} + +static uint8_t features_discover_cb( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_has_client *inst = CONTAINER_OF(params, struct bt_has_client, + params.discover); + const struct bt_gatt_chrc *chrc; + int err; + + LOG_DBG("conn %p attr %p params %p", (void *)conn, attr, params); + + if (!attr) + { + err = -ENOENT; + goto fail; + } + + chrc = attr->user_data; + + /* Subscribe first if notifications are supported, otherwise read the + * features + */ + + if (chrc->properties & BT_GATT_CHRC_NOTIFY) + { + err = features_subscribe(inst, chrc->value_handle); + if (err != 0) + { + LOG_ERR("Subscribe failed (err %d)", err); + goto fail; + } + } + else + { + err = features_read(inst, chrc->value_handle); + if (err != 0) + { + LOG_ERR("Read failed (err %d)", err); + goto fail; + } + } + + return BT_GATT_ITER_STOP; + +fail: + + /* Cleanup instance so that it can be reused */ + + inst_cleanup(inst); + + discover_failed(conn, err); + + return BT_GATT_ITER_STOP; +} + +static int features_discover(struct bt_has_client *inst) +{ + LOG_DBG("conn %p", (void *)inst->conn); + + (void)memset(&inst->params.discover, 0, sizeof(inst->params.discover)); + + (void)memcpy(&inst->params.uuid, BT_UUID_HAS_HEARING_AID_FEATURES, + sizeof(inst->params.uuid)); + inst->params.discover.uuid = &inst->params.uuid.uuid; + inst->params.discover.func = features_discover_cb; + inst->params.discover.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; + inst->params.discover.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + inst->params.discover.type = BT_GATT_DISCOVER_CHARACTERISTIC; + + return bt_gatt_discover(inst->conn, &inst->params.discover); +} + +int bt_has_client_cb_register(const struct bt_has_client_cb *cb) +{ + if (!cb) + { + return -EINVAL; + } + + if (client_cb) + { + return -EALREADY; + } + + client_cb = cb; + + return 0; +} + +/* Hearing Access Service discovery + * + * This will initiate a discover procedure. The procedure will do the + * following sequence: 1) HAS related characteristic discovery 2) CCC + * subscription 3) Hearing Aid Features and Active Preset Index + * characteristic read 5) When everything above have been completed, the + * callback is called + */ + +int bt_has_client_discover(struct bt_conn *conn) +{ + struct bt_has_client *inst; + struct bt_conn *ref; + int err; + + LOG_DBG("conn %p", (void *)conn); + + if (!conn || !client_cb || !client_cb->discover) + { + return -EINVAL; + } + + inst = &clients[bt_conn_index(conn)]; + + if (atomic_test_bit(inst->flags, HAS_CLIENT_CP_OPERATION_IN_PROGRESS) || + atomic_test_and_set_bit(inst->flags, HAS_CLIENT_DISCOVER_IN_PROGRESS)) + { + return -EBUSY; + } + + if (inst->conn) + { + return -EALREADY; + } + + ref = bt_conn_ref(conn); + if (ref == NULL) + { + err = -ENOTCONN; + goto cleanup; + } + + inst->conn = ref; + + err = features_discover(inst); + if (err != 0) + { + bt_conn_unref(inst->conn); + inst->conn = NULL; + goto cleanup; + } + + return 0; + +cleanup: + atomic_clear_bit(inst->flags, HAS_CLIENT_DISCOVER_IN_PROGRESS); + return err; +} + +int bt_has_client_conn_get(const struct bt_has *has, struct bt_conn **conn) +{ + struct bt_has_client *inst = HAS_INST(has); + + *conn = bt_conn_ref(inst->conn); + + return 0; +} + +int bt_has_client_presets_read(struct bt_has *has, + uint8_t start_index, uint8_t count) +{ + struct bt_has_client *inst = HAS_INST(has); + int err; + + LOG_DBG("conn %p start_index 0x%02x count %d", (void *)inst->conn, + start_index, count); + + if (!inst->conn) + { + return -ENOTCONN; + } + + if (atomic_test_bit(inst->flags, HAS_CLIENT_DISCOVER_IN_PROGRESS) || + atomic_test_and_set_bit(inst->flags, + HAS_CLIENT_CP_OPERATION_IN_PROGRESS)) + { + return -EBUSY; + } + + if (start_index == BT_HAS_PRESET_INDEX_NONE) + { + return -EINVAL; + } + + if (count == 0u) + { + return -EINVAL; + } + + err = read_presets_req(inst, start_index, count); + if (err != 0) + { + atomic_clear_bit(inst->flags, HAS_CLIENT_CP_OPERATION_IN_PROGRESS); + } + + return err; +} + +int bt_has_client_preset_set(struct bt_has *has, uint8_t index, bool sync) +{ + struct bt_has_client *inst = HAS_INST(has); + uint8_t opcode; + + LOG_DBG("conn %p index 0x%02x", (void *)inst->conn, index); + + if (!inst->conn) + { + return -ENOTCONN; + } + + if (index == BT_HAS_PRESET_INDEX_NONE) + { + return -EINVAL; + } + + if (sync && (inst->has.features & BT_HAS_FEAT_PRESET_SYNC_SUPP) == 0) + { + return -EOPNOTSUPP; + } + + if (atomic_test_bit(inst->flags, HAS_CLIENT_DISCOVER_IN_PROGRESS) || + atomic_test_and_set_bit(inst->flags, + HAS_CLIENT_CP_OPERATION_IN_PROGRESS)) + { + return -EBUSY; + } + + opcode = + sync ? BT_HAS_OP_SET_ACTIVE_PRESET_SYNC : + BT_HAS_OP_SET_ACTIVE_PRESET; + + return preset_set(inst, opcode, index); +} + +int bt_has_client_preset_next(struct bt_has *has, bool sync) +{ + struct bt_has_client *inst = HAS_INST(has); + uint8_t opcode; + + LOG_DBG("conn %p sync %d", (void *)inst->conn, sync); + + if (!inst->conn) + { + return -ENOTCONN; + } + + if (sync && (inst->has.features & BT_HAS_FEAT_PRESET_SYNC_SUPP) == 0) + { + return -EOPNOTSUPP; + } + + if (atomic_test_bit(inst->flags, HAS_CLIENT_DISCOVER_IN_PROGRESS) || + atomic_test_and_set_bit(inst->flags, + HAS_CLIENT_CP_OPERATION_IN_PROGRESS)) + { + return -EBUSY; + } + + opcode = sync ? BT_HAS_OP_SET_NEXT_PRESET_SYNC : BT_HAS_OP_SET_NEXT_PRESET; + + return preset_set_next_or_prev(inst, opcode); +} + +int bt_has_client_preset_prev(struct bt_has *has, bool sync) +{ + struct bt_has_client *inst = HAS_INST(has); + uint8_t opcode; + + LOG_DBG("conn %p sync %d", (void *)inst->conn, sync); + + if (!inst->conn) + { + return -ENOTCONN; + } + + if (sync && (inst->has.features & BT_HAS_FEAT_PRESET_SYNC_SUPP) == 0) + { + return -EOPNOTSUPP; + } + + if (atomic_test_bit(inst->flags, HAS_CLIENT_DISCOVER_IN_PROGRESS) || + atomic_test_and_set_bit(inst->flags, + HAS_CLIENT_CP_OPERATION_IN_PROGRESS)) + { + return -EBUSY; + } + + opcode = sync ? BT_HAS_OP_SET_PREV_PRESET_SYNC : BT_HAS_OP_SET_PREV_PRESET; + + return preset_set_next_or_prev(inst, opcode); +} + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + struct bt_has_client *inst; + + ARG_UNUSED(reason); + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + inst = inst_by_conn(conn); + if (!inst) + { + return; + } + + if (atomic_test_bit(inst->flags, HAS_CLIENT_DISCOVER_IN_PROGRESS)) + { + discover_failed(conn, -ECONNABORTED); + } + + inst_cleanup(inst); +} + +BT_CONN_CB_DEFINE(conn_cb) = +{ + .disconnected = disconnected, +}; diff --git a/libs/zblue/audio/has_internal.h b/libs/zblue/audio/has_internal.h new file mode 100644 index 0000000000000..796988eb0acf7 --- /dev/null +++ b/libs/zblue/audio/has_internal.h @@ -0,0 +1,215 @@ +/**************************************************************************** + * libs/zblue/audio/has_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Internal APIs for Bluetooth Hearing Access Profile. + */ + +/* Copyright (c) 2022 Codecoup + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include + +/* Control Point opcodes */ +#define BT_HAS_OP_READ_PRESET_REQ 0x01U +#define BT_HAS_OP_READ_PRESET_RSP 0x02U +#define BT_HAS_OP_PRESET_CHANGED 0x03U +#define BT_HAS_OP_WRITE_PRESET_NAME 0x04U +#define BT_HAS_OP_SET_ACTIVE_PRESET 0x05U +#define BT_HAS_OP_SET_NEXT_PRESET 0x06U +#define BT_HAS_OP_SET_PREV_PRESET 0x07U +#define BT_HAS_OP_SET_ACTIVE_PRESET_SYNC 0x08U +#define BT_HAS_OP_SET_NEXT_PRESET_SYNC 0x09U +#define BT_HAS_OP_SET_PREV_PRESET_SYNC 0x0AU + +/* Application error codes */ +#define BT_HAS_ERR_INVALID_OPCODE 0x80U +#define BT_HAS_ERR_WRITE_NAME_NOT_ALLOWED 0x81U +#define BT_HAS_ERR_PRESET_SYNC_NOT_SUPP 0x82U +#define BT_HAS_ERR_OPERATION_NOT_POSSIBLE 0x83U +#define BT_HAS_ERR_INVALID_PARAM_LEN 0x84U + +/* Hearing Aid Feature bits */ +#define BT_HAS_FEAT_HEARING_AID_TYPE_LSB BIT(0U) +#define BT_HAS_FEAT_HEARING_AID_TYPE_MSB BIT(1U) +#define BT_HAS_FEAT_PRESET_SYNC_SUPP BIT(2U) +#define BT_HAS_FEAT_INDEPENDENT_PRESETS BIT(3U) +#define BT_HAS_FEAT_DYNAMIC_PRESETS BIT(4U) +#define BT_HAS_FEAT_WRITABLE_PRESETS_SUPP BIT(5U) + +#define BT_HAS_FEAT_HEARING_AID_TYPE_MASK \ + (BT_HAS_FEAT_HEARING_AID_TYPE_LSB | BT_HAS_FEAT_HEARING_AID_TYPE_MSB) + +/* Preset Changed Change ID values */ +#define BT_HAS_CHANGE_ID_GENERIC_UPDATE 0x00U +#define BT_HAS_CHANGE_ID_PRESET_DELETED 0x01U +#define BT_HAS_CHANGE_ID_PRESET_AVAILABLE 0x02U +#define BT_HAS_CHANGE_ID_PRESET_UNAVAILABLE 0x03U + +#define BT_HAS_IS_LAST 0x01U + +struct bt_has +{ + /** Hearing Aid Features value */ + + uint8_t features; + + /** Active preset index value */ + + uint8_t active_index; + + /* Whether the service has been registered or not */ + + bool registered; +}; + +struct bt_has_cp_hdr +{ + uint8_t opcode; + uint8_t data[]; +} __packed; + +struct bt_has_cp_read_presets_req +{ + uint8_t start_index; + uint8_t num_presets; +} __packed; + +struct bt_has_cp_read_preset_rsp +{ + uint8_t is_last; + uint8_t index; + uint8_t properties; + uint8_t name[]; +} __packed; + +struct bt_has_cp_preset_changed +{ + uint8_t change_id; + uint8_t is_last; + uint8_t additional_params[]; +} __packed; + +struct bt_has_cp_generic_update +{ + uint8_t prev_index; + uint8_t index; + uint8_t properties; + uint8_t name[]; +} __packed; + +struct bt_has_cp_write_preset_name +{ + uint8_t index; + uint8_t name[]; +} __packed; + +struct bt_has_cp_set_active_preset +{ + uint8_t index; +} __packed; + +static inline const char *bt_has_op_str(uint8_t op) +{ + switch (op) + { + case BT_HAS_OP_READ_PRESET_REQ: + return "Read preset request"; + case BT_HAS_OP_READ_PRESET_RSP: + return "Read preset response"; + case BT_HAS_OP_PRESET_CHANGED: + return "Preset changed"; + case BT_HAS_OP_WRITE_PRESET_NAME: + return "Write preset name"; + case BT_HAS_OP_SET_ACTIVE_PRESET: + return "Set active preset"; + case BT_HAS_OP_SET_NEXT_PRESET: + return "Set next preset"; + case BT_HAS_OP_SET_PREV_PRESET: + return "Set previous preset"; + case BT_HAS_OP_SET_ACTIVE_PRESET_SYNC: + return "Set active preset (sync)"; + case BT_HAS_OP_SET_NEXT_PRESET_SYNC: + return "Set next preset (sync)"; + case BT_HAS_OP_SET_PREV_PRESET_SYNC: + return "Set previous preset (sync)"; + default: + return "Unknown"; + } +} + +static inline const char *bt_has_change_id_str(uint8_t change_id) +{ + switch (change_id) + { + case BT_HAS_CHANGE_ID_GENERIC_UPDATE: + return "Generic update"; + case BT_HAS_CHANGE_ID_PRESET_DELETED: + return "Preset deleted"; + case BT_HAS_CHANGE_ID_PRESET_AVAILABLE: + return "Preset available"; + case BT_HAS_CHANGE_ID_PRESET_UNAVAILABLE: + return "Preset unavailable"; + default: + return "Unknown changeId"; + } +} + +enum has_client_flags +{ + HAS_CLIENT_DISCOVER_IN_PROGRESS, + HAS_CLIENT_CP_OPERATION_IN_PROGRESS, + + HAS_CLIENT_NUM_FLAGS, /* keep as last */ +}; + +struct bt_has_client +{ + /** Common profile reference object */ + + struct bt_has has; + + /** Profile connection reference */ + + struct bt_conn *conn; + + /** Internal flags */ + + atomic_define(flags, HAS_CLIENT_NUM_FLAGS); + + /* GATT procedure parameters */ + + union + { + struct + { + struct bt_uuid_16 uuid; + union + { + struct bt_gatt_read_params read; + struct bt_gatt_discover_params discover; + }; + }; + struct bt_gatt_write_params write; + } params; + + struct bt_gatt_subscribe_params features_subscription; + struct bt_gatt_subscribe_params control_point_subscription; + struct bt_gatt_subscribe_params active_index_subscription; +}; diff --git a/libs/zblue/audio/mcc.c b/libs/zblue/audio/mcc.c new file mode 100644 index 0000000000000..2dbb0395b24c6 --- /dev/null +++ b/libs/zblue/audio/mcc.c @@ -0,0 +1,4849 @@ +/**************************************************************************** + * libs/zblue/audio/mcc.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Bluetooth Media Control Client/Protocol implementation + */ + +/* Copyright (c) 2019-2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../services/ots/ots_client_internal.h" +#include "common/bt_str.h" +#include "mcc_internal.h" +#include "mcs_internal.h" + +/* TODO: Temporarily copied here from media_proxy_internal.h - clean up */ + +/* Debug output of 48 bit Object ID value */ + +/* (Zephyr does not yet support debug output of more than 32 bit values.) */ + +/* Takes a text and a 64-bit integer as input */ +#define LOG_DBG_OBJ_ID(text, id64) \ + do \ + { \ + if (IS_ENABLED(CONFIG_BT_MCS_LOG_LEVEL)) \ + { \ + char t[BT_OTS_OBJ_ID_STR_LEN]; \ + (void)bt_ots_obj_id_to_str(id64, t, sizeof(t)); \ + LOG_DBG(text "0x%s", t); \ + } \ + } \ + while (0) + +LOG_MODULE_REGISTER(bt_mcc, CONFIG_BT_MCC_LOG_LEVEL); + +static struct mcs_instance_t mcs_instance; +static struct bt_uuid_16 uuid = BT_UUID_INIT_16(0); + +static struct bt_mcc_cb *mcc_cb; +static bool subscribe_all; + +#ifdef CONFIG_BT_MCC_OTS +NET_BUF_SIMPLE_DEFINE_STATIC(otc_obj_buf, CONFIG_BT_MCC_OTC_OBJ_BUF_SIZE); +#endif /* CONFIG_BT_MCC_OTS */ + +#ifdef CONFIG_BT_MCC_OTS +void on_obj_selected(struct bt_ots_client *otc_inst, + struct bt_conn *conn, int err); + +void on_object_metadata( + struct bt_ots_client *otc_inst, struct bt_conn *conn, int err, + uint8_t metadata_read); + +int on_icon_content( + struct bt_ots_client *otc_inst, struct bt_conn *conn, uint32_t offset, + uint32_t len, uint8_t *data_p, bool is_complete); + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +#endif /* CONFIG_BT_MCC_OTS */ + +struct mcs_instance_t *lookup_inst_by_conn(struct bt_conn *conn) +{ + if (conn == NULL) + { + return NULL; + } + + /* TODO: Expand when supporting more instances */ + + return &mcs_instance; +} + +static void mcc_player_name_cb(struct bt_conn *conn, + uint8_t err, + const void *data, + uint16_t length) +{ + int cb_err = err; + char name[CONFIG_BT_MCC_MEDIA_PLAYER_NAME_MAX]; + + LOG_DBG("err: 0x%02x, length: %d, data: %p", err, length, data); + + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if (!data) + { + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else + { + LOG_HEXDUMP_DBG(data, length, "Player name read"); + + if (length >= sizeof(name)) + { + length = sizeof(name) - 1; + } + + (void)memcpy(&name, data, length); + name[length] = '\0'; + LOG_DBG("Player name: %s", name); + } + + if (mcc_cb && mcc_cb->read_player_name) + { + mcc_cb->read_player_name(conn, cb_err, name); + } +} + +static uint8_t mcc_read_player_name_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + read_params); + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + mcc_player_name_cb(conn, err, data, length); + + return BT_GATT_ITER_STOP; +} + +#ifdef CONFIG_BT_MCC_OTS +static uint8_t mcc_read_icon_obj_id_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + read_params); + int cb_err = err; + uint8_t *pid = (uint8_t *)data; + uint64_t id = 0U; + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + LOG_DBG("err: 0x%02x, length: %d, data: %p", err, length, data); + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if ((!pid) || (length != BT_OTS_OBJ_ID_SIZE)) + { + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else + { + LOG_HEXDUMP_DBG(pid, length, "Icon Object ID"); + id = sys_get_le48(pid); + LOG_DBG_OBJ_ID("Icon Object ID: ", id); + + if (!BT_MCS_VALID_OBJ_ID(id)) + { + cb_err = BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + } + + if (mcc_cb && mcc_cb->read_icon_obj_id) + { + mcc_cb->read_icon_obj_id(conn, cb_err, id); + } + + return BT_GATT_ITER_STOP; +} +#endif /* CONFIG_BT_MCC_OTS */ + +#if defined(CONFIG_BT_MCC_READ_MEDIA_PLAYER_ICON_URL) +static uint8_t mcc_read_icon_url_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + read_params); + int cb_err = err; + char url[CONFIG_BT_MCC_ICON_URL_MAX]; + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + LOG_DBG("err: 0x%02x, length: %d, data: %p", err, length, data); + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if (!data) + { + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else if (length >= sizeof(url)) + { + cb_err = BT_ATT_ERR_INSUFFICIENT_RESOURCES; + } + else + { + LOG_HEXDUMP_DBG(data, length, "Icon URL"); + (void)memcpy(&url, data, length); + url[length] = '\0'; + LOG_DBG("Icon URL: %s", url); + } + + if (mcc_cb && mcc_cb->read_icon_url) + { + mcc_cb->read_icon_url(conn, cb_err, url); + } + + return BT_GATT_ITER_STOP; +} +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_PLAYER_ICON_URL) */ + +#if defined(CONFIG_BT_MCC_READ_TRACK_TITLE) +static void mcc_track_title_cb(struct bt_conn *conn, + uint8_t err, + const void *data, + uint16_t length) +{ + int cb_err = err; + char title[CONFIG_BT_MCC_TRACK_TITLE_MAX]; + + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if (!data) + { + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else + { + LOG_HEXDUMP_DBG(data, length, "Track title"); + if (length >= sizeof(title)) + { + /* If the description is too long; clip it. */ + + length = sizeof(title) - 1; + } + + (void)memcpy(&title, data, length); + title[length] = '\0'; + LOG_DBG("Track title: %s", title); + } + + if (mcc_cb && mcc_cb->read_track_title) + { + mcc_cb->read_track_title(conn, cb_err, title); + } +} + +static uint8_t mcc_read_track_title_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + read_params); + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + mcc_track_title_cb(conn, err, data, length); + + return BT_GATT_ITER_STOP; +} +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_TITLE)*/ + +#if defined(CONFIG_BT_MCC_READ_TRACK_DURATION) +static void mcc_track_duration_cb( + struct bt_conn *conn, uint8_t err, const void *data, uint16_t length) +{ + int cb_err = err; + int32_t dur = 0; + + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if ((!data) || (length != sizeof(dur))) + { + LOG_DBG("length: %d, data: %p", length, data); + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else + { + dur = sys_get_le32((uint8_t *)data); + LOG_DBG("Track duration: %d", dur); + LOG_HEXDUMP_DBG(data, sizeof(int32_t), "Track duration"); + } + + if (mcc_cb && mcc_cb->read_track_duration) + { + mcc_cb->read_track_duration(conn, cb_err, dur); + } +} + +static uint8_t mcc_read_track_duration_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + read_params); + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + mcc_track_duration_cb(conn, err, data, length); + + return BT_GATT_ITER_STOP; +} +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_DURATION) */ + +#if defined(CONFIG_BT_MCC_READ_TRACK_POSITION) +static void mcc_track_position_cb( + struct bt_conn *conn, uint8_t err, const void *data, uint16_t length) +{ + int cb_err = err; + int32_t pos = 0; + + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if ((!data) || (length != sizeof(pos))) + { + LOG_DBG("length: %d, data: %p", length, data); + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else + { + pos = sys_get_le32((uint8_t *)data); + LOG_DBG("Track position: %d", pos); + LOG_HEXDUMP_DBG(data, sizeof(pos), "Track position"); + } + + if (mcc_cb && mcc_cb->read_track_position) + { + mcc_cb->read_track_position(conn, cb_err, pos); + } +} + +static uint8_t mcc_read_track_position_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + read_params); + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + mcc_track_position_cb(conn, err, data, length); + + return BT_GATT_ITER_STOP; +} +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_POSITION) */ + +#if defined(CONFIG_BT_MCC_SET_TRACK_POSITION) +static void mcs_write_track_position_cb(struct bt_conn *conn, uint8_t err, + struct bt_gatt_write_params *params) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + write_params); + int cb_err = err; + int32_t pos = 0; + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if (!params->data || params->length != sizeof(pos)) + { + LOG_DBG("length: %d, data: %p", params->length, params->data); + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else + { + pos = sys_get_le32((uint8_t *)params->data); + LOG_DBG("Track position: %d", pos); + LOG_HEXDUMP_DBG(params->data, sizeof(pos), + "Track position in callback"); + } + + if (mcc_cb && mcc_cb->set_track_position) + { + mcc_cb->set_track_position(conn, cb_err, pos); + } +} +#endif /* defined(CONFIG_BT_MCC_SET_TRACK_POSITION) */ + +#if defined(CONFIG_BT_MCC_READ_PLAYBACK_SPEED) +static void mcc_playback_speed_cb( + struct bt_conn *conn, uint8_t err, const void *data, uint16_t length) +{ + int cb_err = err; + int8_t speed = 0; + + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if ((!data) || (length != sizeof(speed))) + { + LOG_DBG("length: %d, data: %p", length, data); + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else + { + speed = *(int8_t *)data; + LOG_DBG("Playback speed: %d", speed); + LOG_HEXDUMP_DBG(data, sizeof(int8_t), "Playback speed"); + } + + if (mcc_cb && mcc_cb->read_playback_speed) + { + mcc_cb->read_playback_speed(conn, cb_err, speed); + } +} + +static uint8_t mcc_read_playback_speed_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + read_params); + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + mcc_playback_speed_cb(conn, err, data, length); + + return BT_GATT_ITER_STOP; +} +#endif /* defined (CONFIG_BT_MCC_READ_PLAYBACK_SPEED) */ + +#if defined(CONFIG_BT_MCC_SET_PLAYBACK_SPEED) +static void mcs_write_playback_speed_cb(struct bt_conn *conn, uint8_t err, + struct bt_gatt_write_params *params) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + write_params); + int cb_err = err; + int8_t speed = 0; + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if (!params->data || params->length != sizeof(speed)) + { + LOG_DBG("length: %d, data: %p", params->length, params->data); + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else + { + speed = *(int8_t *)params->data; + LOG_DBG("Playback_speed: %d", speed); + } + + if (mcc_cb && mcc_cb->set_playback_speed) + { + mcc_cb->set_playback_speed(conn, cb_err, speed); + } +} +#endif /* defined (CONFIG_BT_MCC_SET_PLAYBACK_SPEED) */ + +#if defined(CONFIG_BT_MCC_READ_SEEKING_SPEED) +static void mcc_seeking_speed_cb( + struct bt_conn *conn, uint8_t err, const void *data, uint16_t length) +{ + int cb_err = err; + int8_t speed = 0; + + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if ((!data) || (length != sizeof(speed))) + { + LOG_DBG("length: %d, data: %p", length, data); + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else + { + speed = *(int8_t *)data; + LOG_DBG("Seeking speed: %d", speed); + LOG_HEXDUMP_DBG(data, sizeof(int8_t), "Seeking speed"); + } + + if (mcc_cb && mcc_cb->read_seeking_speed) + { + mcc_cb->read_seeking_speed(conn, cb_err, speed); + } +} + +static uint8_t mcc_read_seeking_speed_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + read_params); + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + mcc_seeking_speed_cb(conn, err, data, length); + + return BT_GATT_ITER_STOP; +} +#endif /* defined (CONFIG_BT_MCC_READ_SEEKING_SPEED) */ + +#ifdef CONFIG_BT_MCC_OTS +static uint8_t mcc_read_segments_obj_id_cb(struct bt_conn *conn, + uint8_t err, + struct + bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + read_params); + int cb_err = err; + uint8_t *pid = (uint8_t *)data; + uint64_t id = 0U; + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if ((!pid) || (length != BT_OTS_OBJ_ID_SIZE)) + { + LOG_DBG("length: %d, data: %p", length, data); + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else + { + LOG_HEXDUMP_DBG(pid, length, "Segments Object ID"); + id = sys_get_le48(pid); + LOG_DBG_OBJ_ID("Segments Object ID: ", id); + + if (!BT_MCS_VALID_OBJ_ID(id)) + { + cb_err = BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + } + + if (mcc_cb && mcc_cb->read_segments_obj_id) + { + mcc_cb->read_segments_obj_id(conn, cb_err, id); + } + + return BT_GATT_ITER_STOP; +} + +static void mcc_current_track_obj_id_cb( + struct bt_conn *conn, uint8_t err, const void *data, uint16_t length) +{ + int cb_err = err; + uint8_t *pid = (uint8_t *)data; + uint64_t id = 0U; + + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if ((!pid) || (length != BT_OTS_OBJ_ID_SIZE)) + { + LOG_DBG("length: %d, data: %p", length, data); + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else + { + LOG_HEXDUMP_DBG(pid, length, "Current Track Object ID"); + id = sys_get_le48(pid); + LOG_DBG_OBJ_ID("Current Track Object ID: ", id); + + if (!BT_MCS_VALID_OBJ_ID(id)) + { + cb_err = BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + } + + if (mcc_cb && mcc_cb->read_current_track_obj_id) + { + mcc_cb->read_current_track_obj_id(conn, cb_err, id); + } +} + +static uint8_t mcc_read_current_track_obj_id_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, + const void *data, uint16_t length) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + read_params); + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + mcc_current_track_obj_id_cb(conn, err, data, length); + + return BT_GATT_ITER_STOP; +} + +static void mcs_write_current_track_obj_id_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_write_params *params) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + write_params); + int cb_err = err; + uint64_t obj_id = 0U; + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if (!params->data || params->length != BT_OTS_OBJ_ID_SIZE) + { + LOG_DBG("length: %d, data: %p", params->length, params->data); + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else + { + obj_id = sys_get_le48((const uint8_t *)params->data); + LOG_DBG_OBJ_ID("Object ID: ", obj_id); + + if (!BT_MCS_VALID_OBJ_ID(obj_id)) + { + cb_err = BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + } + + if (mcc_cb && mcc_cb->set_current_track_obj_id) + { + mcc_cb->set_current_track_obj_id(conn, cb_err, obj_id); + } +} + +static void mcc_next_track_obj_id_cb( + struct bt_conn *conn, uint8_t err, const void *data, uint16_t length) +{ + int cb_err = err; + uint8_t *pid = (uint8_t *)data; + uint64_t id = 0U; + + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if (length == 0U) + { + LOG_DBG("Characteristic is empty"); + } + else if (!pid || (length != BT_OTS_OBJ_ID_SIZE)) + { + LOG_DBG("length: %d, data: %p", length, data); + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else + { + LOG_HEXDUMP_DBG(pid, length, "Next Track Object ID"); + id = sys_get_le48(pid); + LOG_DBG_OBJ_ID("Next Track Object ID: ", id); + + if (!BT_MCS_VALID_OBJ_ID(id)) + { + cb_err = BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + } + + if (mcc_cb && mcc_cb->read_next_track_obj_id) + { + mcc_cb->read_next_track_obj_id(conn, cb_err, id); + } +} + +static uint8_t mcc_read_next_track_obj_id_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, + const void *data, uint16_t length) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + read_params); + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + mcc_next_track_obj_id_cb(conn, err, data, length); + + return BT_GATT_ITER_STOP; +} + +static void mcs_write_next_track_obj_id_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_write_params *params) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + write_params); + int cb_err = err; + uint64_t obj_id = 0U; + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if (!params->data || params->length != BT_OTS_OBJ_ID_SIZE) + { + LOG_DBG("length: %d, data: %p", params->length, params->data); + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else + { + obj_id = sys_get_le48((const uint8_t *)params->data); + LOG_DBG_OBJ_ID("Object ID: ", obj_id); + + if (!BT_MCS_VALID_OBJ_ID(obj_id)) + { + cb_err = BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + } + + if (mcc_cb && mcc_cb->set_next_track_obj_id) + { + mcc_cb->set_next_track_obj_id(conn, cb_err, obj_id); + } +} + +static void mcc_parent_group_obj_id_cb( + struct bt_conn *conn, uint8_t err, const void *data, uint16_t length) +{ + int cb_err = err; + uint8_t *pid = (uint8_t *)data; + uint64_t id = 0U; + + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if (!pid || (length != BT_OTS_OBJ_ID_SIZE)) + { + LOG_DBG("length: %d, data: %p", length, data); + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else + { + LOG_HEXDUMP_DBG(pid, length, "Parent Group Object ID"); + id = sys_get_le48(pid); + LOG_DBG_OBJ_ID("Parent Group Object ID: ", id); + + if (!BT_MCS_VALID_OBJ_ID(id)) + { + cb_err = BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + } + + if (mcc_cb && mcc_cb->read_parent_group_obj_id) + { + mcc_cb->read_parent_group_obj_id(conn, cb_err, id); + } +} + +static uint8_t mcc_read_parent_group_obj_id_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, + const void *data, uint16_t length) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + read_params); + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + mcc_parent_group_obj_id_cb(conn, err, data, length); + + return BT_GATT_ITER_STOP; +} + +static void mcc_current_group_obj_id_cb( + struct bt_conn *conn, uint8_t err, const void *data, uint16_t length) +{ + int cb_err = err; + uint8_t *pid = (uint8_t *)data; + uint64_t id = 0U; + + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if (!pid || (length != BT_OTS_OBJ_ID_SIZE)) + { + LOG_DBG("length: %d, data: %p", length, data); + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else + { + LOG_HEXDUMP_DBG(pid, length, "Current Group Object ID"); + id = sys_get_le48(pid); + LOG_DBG_OBJ_ID("Current Group Object ID: ", id); + + if (!BT_MCS_VALID_OBJ_ID(id)) + { + cb_err = BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + } + + if (mcc_cb && mcc_cb->read_current_group_obj_id) + { + mcc_cb->read_current_group_obj_id(conn, cb_err, id); + } +} + +static uint8_t mcc_read_current_group_obj_id_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, + const void *data, uint16_t length) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + read_params); + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + mcc_current_group_obj_id_cb(conn, err, data, length); + + return BT_GATT_ITER_STOP; +} + +static void mcs_write_current_group_obj_id_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_write_params *params) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + write_params); + int cb_err = err; + uint64_t obj_id = 0U; + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if (!params->data || params->length != BT_OTS_OBJ_ID_SIZE) + { + LOG_DBG("length: %d, data: %p", params->length, params->data); + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else + { + obj_id = sys_get_le48((const uint8_t *)params->data); + LOG_DBG_OBJ_ID("Object ID: ", obj_id); + + if (!BT_MCS_VALID_OBJ_ID(obj_id)) + { + cb_err = BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + } + + if (mcc_cb && mcc_cb->set_current_group_obj_id) + { + mcc_cb->set_current_group_obj_id(conn, cb_err, obj_id); + } +} +#endif /* CONFIG_BT_MCC_OTS */ + +#if defined(CONFIG_BT_MCC_READ_PLAYING_ORDER) +static void mcc_playing_order_cb( + struct bt_conn *conn, uint8_t err, const void *data, uint16_t length) +{ + int cb_err = err; + uint8_t order = 0U; + + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if ((!data) || (length != sizeof(order))) + { + LOG_DBG("length: %d, data: %p", length, data); + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else + { + order = *(uint8_t *)data; + LOG_DBG("Playing order: %d", order); + LOG_HEXDUMP_DBG(data, sizeof(order), "Playing order"); + } + + if (mcc_cb && mcc_cb->read_playing_order) + { + mcc_cb->read_playing_order(conn, cb_err, order); + } +} + +static uint8_t mcc_read_playing_order_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + read_params); + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + mcc_playing_order_cb(conn, err, data, length); + + return BT_GATT_ITER_STOP; +} +#endif /* defined(CONFIG_BT_MCC_READ_PLAYING_ORDER) */ + +#if defined(CONFIG_BT_MCC_SET_PLAYING_ORDER) +static void mcs_write_playing_order_cb(struct bt_conn *conn, uint8_t err, + struct bt_gatt_write_params *params) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + write_params); + int cb_err = err; + uint8_t order = 0U; + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if (!params->data || params->length != sizeof(order)) + { + LOG_DBG("length: %d, data: %p", params->length, params->data); + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else + { + order = *(uint8_t *)params->data; + LOG_DBG("Playing order: %d", *(uint8_t *)params->data); + } + + if (mcc_cb && mcc_cb->set_playing_order) + { + mcc_cb->set_playing_order(conn, cb_err, order); + } +} +#endif /* defined(CONFIG_BT_MCC_SET_PLAYING_ORDER) */ + +#if defined(CONFIG_BT_MCC_READ_PLAYING_ORDER_SUPPORTED) +static uint8_t mcc_read_playing_orders_supported_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, + const void *data, uint16_t length) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + read_params); + int cb_err = err; + uint16_t orders = 0U; + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if (!data || length != sizeof(orders)) + { + LOG_DBG("length: %d, data: %p", length, data); + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else + { + orders = sys_get_le16((uint8_t *)data); + LOG_DBG("Playing orders_supported: %d", orders); + LOG_HEXDUMP_DBG(data, sizeof(orders), "Playing orders supported"); + } + + if (mcc_cb && mcc_cb->read_playing_orders_supported) + { + mcc_cb->read_playing_orders_supported(conn, cb_err, orders); + } + + return BT_GATT_ITER_STOP; +} +#endif /* defined(CONFIG_BT_MCC_READ_PLAYING_ORDER_SUPPORTED) */ + +#if defined(CONFIG_BT_MCC_READ_MEDIA_STATE) +static void mcc_media_state_cb(struct bt_conn *conn, + uint8_t err, + const void *data, + uint16_t length) +{ + int cb_err = err; + uint8_t state = 0U; + + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if (!data || length != sizeof(state)) + { + LOG_DBG("length: %d, data: %p", length, data); + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else + { + state = *(uint8_t *)data; + LOG_DBG("Media state: %d", state); + LOG_HEXDUMP_DBG(data, sizeof(uint8_t), "Media state"); + } + + if (mcc_cb && mcc_cb->read_media_state) + { + mcc_cb->read_media_state(conn, cb_err, state); + } +} + +static uint8_t mcc_read_media_state_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + read_params); + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + mcc_media_state_cb(conn, err, data, length); + + return BT_GATT_ITER_STOP; +} +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_STATE) */ + +#if defined(CONFIG_BT_MCC_SET_MEDIA_CONTROL_POINT) +static void mcs_write_cp_cb(struct bt_conn *conn, + uint8_t err, struct bt_gatt_write_params *params) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + write_params); + int cb_err = err; + struct mpl_cmd cmd = + { + 0 + }; + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if (!params->data || (params->length != sizeof(cmd.opcode) && + params->length != sizeof(cmd.opcode) + + sizeof(cmd.param))) + { + /* Above: No data pointer, or length not equal to any of the */ + + /* two possible values */ + + LOG_DBG("length: %d, data: %p", params->length, params->data); + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else + { + (void)memcpy(&cmd.opcode, params->data, sizeof(cmd.opcode)); + if (params->length == sizeof(cmd.opcode) + sizeof(cmd.param)) + { + (void)memcpy(&cmd.param, + (char *)(params->data) + sizeof(cmd.opcode), + sizeof(cmd.param)); + cmd.use_param = true; + LOG_DBG("Command in callback: %d, param: %d", cmd.opcode, + cmd.param); + } + } + + if (mcc_cb && mcc_cb->send_cmd) + { + mcc_cb->send_cmd(conn, cb_err, &cmd); + } +} +#endif /* defined(CONFIG_BT_MCC_SET_MEDIA_CONTROL_POINT) */ + +#if defined(CONFIG_BT_MCC_READ_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED) +static void mcc_opcodes_supported_cb( + struct bt_conn *conn, uint8_t err, const void *data, uint16_t length) +{ + int cb_err = err; + int32_t operations = 0; + + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if ((!data) || (length != sizeof(operations))) + { + LOG_DBG("length: %d, data: %p", length, data); + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else + { + operations = sys_get_le32((uint8_t *)data); + LOG_DBG("Opcodes supported: %d", operations); + LOG_HEXDUMP_DBG(data, sizeof(operations), "Opcodes_supported"); + } + + if (mcc_cb && mcc_cb->read_opcodes_supported) + { + mcc_cb->read_opcodes_supported(conn, cb_err, operations); + } +} + +static uint8_t mcc_read_opcodes_supported_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, + const void *data, uint16_t length) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + read_params); + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + mcc_opcodes_supported_cb(conn, err, data, length); + + return BT_GATT_ITER_STOP; +} +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED) */ + +#ifdef CONFIG_BT_MCC_OTS +static void mcs_write_scp_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_write_params *params) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + write_params); + int cb_err = err; + struct mpl_search search = + { + 0 + }; + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if (!params->data || (params->length > SEARCH_LEN_MAX)) + { + LOG_DBG("length: %d, data: %p", params->length, params->data); + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else + { + search.len = params->length; + (void)memcpy(search.search, params->data, params->length); + LOG_DBG("Length of returned value in callback: %d", search.len); + } + + if (mcc_cb && mcc_cb->send_search) + { + mcc_cb->send_search(conn, cb_err, &search); + } +} + +static void mcc_search_results_obj_id_cb( + struct bt_conn *conn, uint8_t err, const void *data, uint16_t length) +{ + int cb_err = err; + uint8_t *pid = (uint8_t *)data; + uint64_t id = 0U; + + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if (length == 0U) + { + /* OK - this characteristic may be zero length */ + + /* cb_err and id already have correct values */ + + LOG_DBG("Zero-length Search Results Object ID"); + } + else if (!pid || (length != BT_OTS_OBJ_ID_SIZE)) + { + LOG_DBG("length: %d, pid: %p", length, pid); + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else + { + id = sys_get_le48(pid); + LOG_DBG_OBJ_ID("Search Results Object ID: ", id); + + if (!BT_MCS_VALID_OBJ_ID(id)) + { + cb_err = BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + } + + if (mcc_cb && mcc_cb->read_search_results_obj_id) + { + mcc_cb->read_search_results_obj_id(conn, cb_err, id); + } +} + +static uint8_t mcc_read_search_results_obj_id_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, + const void *data, uint16_t length) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + read_params); + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + mcc_search_results_obj_id_cb(conn, err, data, length); + + return BT_GATT_ITER_STOP; +} +#endif /* CONFIG_BT_MCC_OTS */ + +#if defined(CONFIG_BT_MCC_READ_CONTENT_CONTROL_ID) +static uint8_t mcc_read_content_control_id_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, + const void *data, uint16_t length) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + read_params); + int cb_err = err; + uint8_t ccid = 0U; + + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + + if (err != 0) + { + LOG_DBG("err: 0x%02x", err); + } + else if ((!data) || (length != sizeof(ccid))) + { + LOG_DBG("length: %d, data: %p", length, data); + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + else + { + ccid = *(uint8_t *)data; + LOG_DBG("Content control ID: %d", ccid); + } + + if (mcc_cb && mcc_cb->read_content_control_id) + { + mcc_cb->read_content_control_id(conn, cb_err, ccid); + } + + return BT_GATT_ITER_STOP; +} +#endif /* defined(CONFIG_BT_MCC_READ_CONTENT_CONTROL_ID) */ + +static uint8_t mcs_notify_handler( + struct bt_conn *conn, struct bt_gatt_subscribe_params *params, + const void *data, uint16_t length) +{ + uint16_t handle = params->value_handle; + struct mcs_instance_t *mcs_inst; + + if (data == NULL) + { + LOG_DBG("[UNSUBSCRIBED] 0x%04X", params->value_handle); + params->value_handle = 0U; + + return BT_GATT_ITER_CONTINUE; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not find MCS instance from conn %p", (void *)conn); + + return BT_GATT_ITER_CONTINUE; + } + + LOG_DBG("Notification, handle: %d", handle); + + if (handle == mcs_inst->player_name_handle) + { + LOG_DBG("Player Name notification"); + mcc_player_name_cb(conn, 0, data, length); + } + else if (handle == mcs_inst->track_changed_handle) + { + /* The Track Changed characteristic can only be */ + + /* notified, so that is handled directly here */ + + int cb_err = 0; + + LOG_DBG("Track Changed notification"); + LOG_DBG("data: %p, length: %u", data, length); + + if (length != 0U) + { + LOG_DBG("Non-zero length: %u", length); + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + if (mcc_cb && mcc_cb->track_changed_ntf) + { + mcc_cb->track_changed_ntf(conn, cb_err); + } + +#if defined(CONFIG_BT_MCC_READ_TRACK_TITLE_ENABLE_SUBSCRIPTION) + } + else if (handle == mcs_inst->track_title_handle) + { + LOG_DBG("Track Title notification"); + mcc_track_title_cb(conn, 0, data, length); +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_TITLE_ENABLE_SUBSCRIPTION) */ + +#if defined(CONFIG_BT_MCC_READ_TRACK_DURATION) + } + else if (handle == mcs_inst->track_duration_handle) + { + LOG_DBG("Track Duration notification"); + mcc_track_duration_cb(conn, 0, data, length); +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_DURATION) */ + +#if defined(CONFIG_BT_MCC_READ_TRACK_POSITION) + } + else if (handle == mcs_inst->track_position_handle) + { + LOG_DBG("Track Position notification"); + mcc_track_position_cb(conn, 0, data, length); +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_POSITION) */ + +#if defined(CONFIG_BT_MCC_READ_PLAYBACK_SPEED) + } + else if (handle == mcs_inst->playback_speed_handle) + { + LOG_DBG("Playback Speed notification"); + mcc_playback_speed_cb(conn, 0, data, length); +#endif /* defined (CONFIG_BT_MCC_READ_PLAYBACK_SPEED) */ + +#if defined(CONFIG_BT_MCC_READ_SEEKING_SPEED) + } + else if (handle == mcs_inst->seeking_speed_handle) + { + LOG_DBG("Seeking Speed notification"); + mcc_seeking_speed_cb(conn, 0, data, length); +#endif /* defined (CONFIG_BT_MCC_READ_SEEKING_SPEED) */ + +#ifdef CONFIG_BT_MCC_OTS + } + else if (handle == mcs_inst->current_track_obj_id_handle) + { + LOG_DBG("Current Track notification"); + mcc_current_track_obj_id_cb(conn, 0, data, length); + } + else if (handle == mcs_inst->next_track_obj_id_handle) + { + LOG_DBG("Next Track notification"); + mcc_next_track_obj_id_cb(conn, 0, data, length); + } + else if (handle == mcs_inst->parent_group_obj_id_handle) + { + LOG_DBG("Parent Group notification"); + mcc_parent_group_obj_id_cb(conn, 0, data, length); + } + else if (handle == mcs_inst->current_group_obj_id_handle) + { + LOG_DBG("Current Group notification"); + mcc_current_group_obj_id_cb(conn, 0, data, length); +#endif /* CONFIG_BT_MCC_OTS */ + +#if defined(CONFIG_BT_MCC_READ_PLAYING_ORDER) + } + else if (handle == mcs_inst->playing_order_handle) + { + LOG_DBG("Playing Order notification"); + mcc_playing_order_cb(conn, 0, data, length); +#endif /* defined(CONFIG_BT_MCC_READ_PLAYING_ORDER) */ + +#if defined(CONFIG_BT_MCC_READ_MEDIA_STATE) + } + else if (handle == mcs_inst->media_state_handle) + { + LOG_DBG("Media State notification"); + mcc_media_state_cb(conn, 0, data, length); +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_STATE) */ + } + else if (handle == mcs_inst->cp_handle) + { + /* The control point is a special case - only */ + + /* writable and notifiable. Handle directly here. */ + + struct mpl_cmd_ntf ntf = + { + 0 + }; + + int cb_err = 0; + + LOG_DBG("Control Point notification"); + if (length == sizeof(ntf.requested_opcode) + sizeof(ntf.result_code)) + { + ntf.requested_opcode = *(uint8_t *)data; + ntf.result_code = + *((uint8_t *)data + sizeof(ntf.requested_opcode)); + LOG_DBG("Command: %d, result: %d", ntf.requested_opcode, + ntf.result_code); + } + else + { + LOG_DBG("length: %d, data: %p", length, data); + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + if (mcc_cb && mcc_cb->cmd_ntf) + { + mcc_cb->cmd_ntf(conn, cb_err, &ntf); + } + +#if defined(CONFIG_BT_MCC_READ_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED) + } + else if (handle == mcs_inst->opcodes_supported_handle) + { + LOG_DBG("Opcodes Supported notification"); + mcc_opcodes_supported_cb(conn, 0, data, length); +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED) */ + +#ifdef CONFIG_BT_MCC_OTS + } + else if (handle == mcs_inst->scp_handle) + { + /* The search control point is a special case - only */ + + /* writable and notifiable. Handle directly here. */ + + int cb_err = 0; + uint8_t result_code = 0U; + + LOG_DBG("Search Control Point notification"); + if (length == sizeof(result_code)) + { + result_code = *(uint8_t *)data; + LOG_DBG("Result: %d", result_code); + } + else + { + LOG_DBG("length: %d, data: %p", length, data); + cb_err = BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + if (mcc_cb && mcc_cb->search_ntf) + { + mcc_cb->search_ntf(conn, cb_err, result_code); + } + } + else if (handle == mcs_inst->search_results_obj_id_handle) + { + LOG_DBG("Search Results notification"); + mcc_search_results_obj_id_cb(conn, 0, data, length); +#endif /* CONFIG_BT_MCC_OTS */ + } + else + { + LOG_DBG("Unknown handle: %d (0x%04X)", handle, handle); + } + + return BT_GATT_ITER_CONTINUE; +} + +static int reset_mcs_inst(struct mcs_instance_t *mcs_inst) +{ + if (mcs_inst->conn != NULL) + { + struct bt_conn *conn = mcs_inst->conn; + struct bt_conn_info info; + int err; + + err = bt_conn_get_info(conn, &info); + if (err != 0) + { + return err; + } + + if (info.state == BT_CONN_STATE_CONNECTED) + { + /* It's okay if these fail with -EINVAL as that means that they are + * not currently subscribed + */ + + err = bt_gatt_unsubscribe(conn, &mcs_inst->player_name_sub_params); + if (err != 0 && err != -EINVAL) + { + LOG_DBG("Failed to unsubscribe to name: %d", err); + + return err; + } + + err = bt_gatt_unsubscribe(conn, + &mcs_inst->track_changed_sub_params); + if (err != 0 && err != -EINVAL) + { + LOG_DBG("Failed to unsubscribe to track change: %d", err); + + return err; + } + +#if defined(CONFIG_BT_MCC_READ_TRACK_TITLE_ENABLE_SUBSCRIPTION) + err = bt_gatt_unsubscribe(conn, &mcs_inst->track_title_sub_params); + if (err != 0 && err != -EINVAL) + { + LOG_DBG("Failed to unsubscribe to track title: %d", err); + + return err; + } +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_TITLE_ENABLE_SUBSCRIPTION) */ + +#if defined(CONFIG_BT_MCC_READ_TRACK_DURATION) + err = bt_gatt_unsubscribe(conn, + &mcs_inst->track_duration_sub_params); + if (err != 0 && err != -EINVAL) + { + LOG_DBG("Failed to unsubscribe to track duration: %d", err); + + return err; + } +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_DURATION) */ + +#if defined(CONFIG_BT_MCC_READ_TRACK_POSITION) + err = bt_gatt_unsubscribe(conn, + &mcs_inst->track_position_sub_params); + if (err != 0 && err != -EINVAL) + { + LOG_DBG("Failed to unsubscribe to track position: %d", err); + + return err; + } +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_POSITION) */ + +#if defined(CONFIG_BT_MCC_READ_PLAYBACK_SPEED) + err = bt_gatt_unsubscribe(conn, + &mcs_inst->playback_speed_sub_params); + if (err != 0 && err != -EINVAL) + { + LOG_DBG("Failed to unsubscribe to playback speed: %d", err); + + return err; + } +#endif /* defined (CONFIG_BT_MCC_READ_PLAYBACK_SPEED) */ + +#if defined(CONFIG_BT_MCC_READ_SEEKING_SPEED) + err = bt_gatt_unsubscribe(conn, + &mcs_inst->seeking_speed_sub_params); + if (err != 0 && err != -EINVAL) + { + LOG_DBG("Failed to unsubscribe to seeking speed: %d", err); + + return err; + } +#endif /* defined (CONFIG_BT_MCC_READ_SEEKING_SPEED) */ + +#ifdef CONFIG_BT_MCC_OTS + err = bt_gatt_unsubscribe(conn, + &mcs_inst->current_track_obj_sub_params); + if (err != 0 && err != -EINVAL) + { + LOG_DBG("Failed to unsubscribe to current track object: %d", + err); + + return err; + } + + err = bt_gatt_unsubscribe(conn, + &mcs_inst->next_track_obj_sub_params); + if (err != 0 && err != -EINVAL) + { + LOG_DBG("Failed to unsubscribe to next track object: %d", err); + + return err; + } + + err = bt_gatt_unsubscribe(conn, + &mcs_inst->parent_group_obj_sub_params); + if (err != 0 && err != -EINVAL) + { + LOG_DBG("Failed to unsubscribe to parent group object: %d", + err); + + return err; + } + + err = bt_gatt_unsubscribe(conn, + &mcs_inst->current_group_obj_sub_params); + if (err != 0 && err != -EINVAL) + { + LOG_DBG("Failed to unsubscribe to current group object: %d", + err); + + return err; + } + +#endif /* CONFIG_BT_MCC_OTS */ +#if defined(CONFIG_BT_MCC_READ_PLAYING_ORDER) + err = bt_gatt_unsubscribe(conn, + &mcs_inst->playing_order_sub_params); + if (err != 0 && err != -EINVAL) + { + LOG_DBG("Failed to unsubscribe to playing order: %d", err); + + return err; + } +#endif /* defined(CONFIG_BT_MCC_READ_PLAYING_ORDER) */ + +#if defined(CONFIG_BT_MCC_READ_MEDIA_STATE) + err = bt_gatt_unsubscribe(conn, &mcs_inst->media_state_sub_params); + if (err != 0 && err != -EINVAL) + { + LOG_DBG("Failed to unsubscribe to media state: %d", err); + + return err; + } +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_STATE) */ + + err = bt_gatt_unsubscribe(conn, &mcs_inst->cp_sub_params); + if (err != 0 && err != -EINVAL) + { + LOG_DBG("Failed to unsubscribe to control point: %d", err); + + return err; + } + +#if defined(CONFIG_BT_MCC_READ_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED) + err = bt_gatt_unsubscribe(conn, + &mcs_inst->opcodes_supported_sub_params); + if (err != 0 && err != -EINVAL) + { + LOG_DBG("Failed to unsubscribe to supported opcodes: %d", err); + + return err; + } +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED) */ + +#ifdef CONFIG_BT_MCC_OTS + err = bt_gatt_unsubscribe(conn, &mcs_inst->scp_sub_params); + if (err != 0 && err != -EINVAL) + { + LOG_DBG("Failed to unsubscribe to search control point: %d", + err); + + return err; + } + + err = bt_gatt_unsubscribe( + conn, + &mcs_inst->search_results_obj_sub_params); + if (err != 0 && err != -EINVAL) + { + LOG_DBG("Failed to unsubscribe to search results: %d", err); + + return err; + } + + err = bt_gatt_unsubscribe(conn, &mcs_inst->otc.oacp_sub_params); + if (err != 0 && err != -EINVAL) + { + LOG_DBG("Failed to unsubscribe to oacp: %d", err); + + return err; + } + + err = bt_gatt_unsubscribe(conn, &mcs_inst->otc.olcp_sub_params); + if (err != 0 && err != -EINVAL) + { + LOG_DBG("Failed to unsubscribe to olcp: %d", err); + + return err; + } +#endif /* CONFIG_BT_MCC_OTS */ + } + + bt_conn_drop(&mcs_inst->conn); + } + + (void)memset(mcs_inst, 0, offsetof(struct mcs_instance_t, + player_name_sub_params)); +#ifdef CONFIG_BT_MCC_OTS + /* Reset OTC instance as well if supported */ + + (void)memset(&mcs_inst->otc, 0, offsetof(struct bt_ots_client, + oacp_sub_params)); +#endif /* CONFIG_BT_MCC_OTS */ + + return 0; +} + +static void disconnected_cb(struct bt_conn *conn, uint8_t reason) +{ + struct mcs_instance_t *mcs_inst; + + ARG_UNUSED(reason); + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst != NULL) + { + (void)reset_mcs_inst(mcs_inst); + } +} + +BT_CONN_CB_DEFINE(conn_callbacks) = +{ + .disconnected = disconnected_cb, +}; + +/* Called when discovery is completed - successfully or with error */ + +static void discovery_complete(struct bt_conn *conn, int err) +{ + struct mcs_instance_t *mcs_inst; + + LOG_DBG("Discovery completed, err: %d", err); + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst != NULL) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + if (err != 0) + { + (void)reset_mcs_inst(mcs_inst); + } + } + + if (mcc_cb && mcc_cb->discover_mcs) + { + mcc_cb->discover_mcs(conn, err); + } +} + +#ifdef CONFIG_BT_MCC_OTS +static uint8_t discover_otc_char_func( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + discover_params); + int err = 0; + struct bt_gatt_chrc *chrc; + struct bt_gatt_subscribe_params *sub_params = NULL; + + if (attr) + { + /* Found an attribute */ + + LOG_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle); + + if (params->type != BT_GATT_DISCOVER_CHARACTERISTIC) + { + /* But it was not a characteristic - continue search */ + + return BT_GATT_ITER_CONTINUE; + } + + /* We have found an attribute, and it is a characteristic */ + + /* Find out which attribute, and subscribe if we should */ + + chrc = (struct bt_gatt_chrc *)attr->user_data; + if (!bt_uuid_cmp(chrc->uuid, BT_UUID_OTS_FEATURE)) + { + LOG_DBG("OTS Features"); + mcs_inst->otc.feature_handle = chrc->value_handle; + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_OTS_NAME)) + { + LOG_DBG("Object Name"); + mcs_inst->otc.obj_name_handle = chrc->value_handle; + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_OTS_TYPE)) + { + LOG_DBG("Object Type"); + mcs_inst->otc.obj_type_handle = chrc->value_handle; + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_OTS_SIZE)) + { + LOG_DBG("Object Size"); + mcs_inst->otc.obj_size_handle = chrc->value_handle; + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_OTS_ID)) + { + LOG_DBG("Object ID"); + mcs_inst->otc.obj_id_handle = chrc->value_handle; + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_OTS_PROPERTIES)) + { + LOG_DBG("Object properties %d", chrc->value_handle); + mcs_inst->otc.obj_properties_handle = chrc->value_handle; + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_OTS_ACTION_CP)) + { + LOG_DBG("Object Action Control Point"); + mcs_inst->otc.oacp_handle = chrc->value_handle; + sub_params = &mcs_inst->otc.oacp_sub_params; + sub_params->disc_params = &mcs_inst->otc.oacp_sub_disc_params; + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_OTS_LIST_CP)) + { + LOG_DBG("Object List Control Point"); + mcs_inst->otc.olcp_handle = chrc->value_handle; + sub_params = &mcs_inst->otc.olcp_sub_params; + sub_params->disc_params = &mcs_inst->otc.olcp_sub_disc_params; + } + + if (sub_params) + { + sub_params->ccc_handle = BT_GATT_AUTO_DISCOVER_CCC_HANDLE; + sub_params->end_handle = mcs_inst->otc.end_handle; + sub_params->value = BT_GATT_CCC_INDICATE; + sub_params->value_handle = chrc->value_handle; + sub_params->notify = bt_ots_client_indicate_handler; + atomic_set_bit(sub_params->flags, BT_GATT_SUBSCRIBE_FLAG_VOLATILE); + + err = bt_gatt_subscribe(conn, sub_params); + if (err != 0) + { + LOG_DBG("Failed to subscribe (err %d)", err); + discovery_complete(conn, err); + + return BT_GATT_ITER_STOP; + } + } + + return BT_GATT_ITER_CONTINUE; + } + + /* No more attributes found */ + + LOG_DBG("Setup complete for included OTS"); + (void)memset(params, 0, sizeof(*params)); + + discovery_complete(conn, err); + + return BT_GATT_ITER_STOP; +} +#endif /* CONFIG_BT_MCC_OTS */ + +#ifdef CONFIG_BT_MCC_OTS +/* This function is called when an included service is found. + * The function will store the start and end handle for the service, and + * continue the search for more instances of included services. Lastly, it + * will start discovery of OTS characteristics. + */ + +static uint8_t discover_include_func( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_gatt_include *include; + int err = 0; + + if (attr) + { + struct mcs_instance_t *mcs_inst = + CONTAINER_OF(params, struct mcs_instance_t, discover_params); + + LOG_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle); + + DEBUGASSERT(params->type == BT_GATT_DISCOVER_INCLUDE); + + /* We have found an included service */ + + include = (struct bt_gatt_include *)attr->user_data; + LOG_DBG("Include UUID %s", bt_uuid_str(include->uuid)); + + if (bt_uuid_cmp(include->uuid, BT_UUID_OTS)) + { + /* But it is not OTS - continue search */ + + LOG_WRN("Included service is not OTS"); + return BT_GATT_ITER_CONTINUE; + } + + /* We have the included OTS service (MCS includes only one) */ + + LOG_DBG("Discover include complete for GMCS: OTS"); + mcs_inst->otc.start_handle = include->start_handle; + mcs_inst->otc.end_handle = include->end_handle; + (void)memset(params, 0, sizeof(*params)); + + /* Discover characteristics of the included OTS */ + + mcs_inst->discover_params.start_handle = mcs_inst->otc.start_handle; + mcs_inst->discover_params.end_handle = mcs_inst->otc.end_handle; + mcs_inst->discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; + mcs_inst->discover_params.func = discover_otc_char_func; + + LOG_DBG("Start discovery of OTS characteristics"); + err = bt_gatt_discover(conn, &mcs_inst->discover_params); + if (err != 0) + { + LOG_DBG("Discovery of OTS chars. failed"); + discovery_complete(conn, err); + } + + return BT_GATT_ITER_STOP; + } + + LOG_DBG("No included OTS found"); + /* This is OK, the server may not support OTS. But in that case, + * discovery stops here. + */ + + discovery_complete(conn, err); + return BT_GATT_ITER_STOP; +} + +/* Start discovery of included services */ + +static void discover_included(struct mcs_instance_t *mcs_inst, + struct bt_conn *conn) +{ + int err; + + memset(&mcs_inst->discover_params, 0, sizeof(mcs_inst->discover_params)); + + mcs_inst->discover_params.start_handle = mcs_inst->start_handle; + mcs_inst->discover_params.end_handle = mcs_inst->end_handle; + mcs_inst->discover_params.type = BT_GATT_DISCOVER_INCLUDE; + mcs_inst->discover_params.func = discover_include_func; + + LOG_DBG("Start discovery of included services"); + err = bt_gatt_discover(conn, &mcs_inst->discover_params); + if (err != 0) + { + LOG_DBG("Discovery of included service failed: %d", err); + discovery_complete(conn, err); + } +} +#endif /* CONFIG_BT_MCC_OTS */ + +static bool subscribe_next_mcs_char(struct mcs_instance_t *mcs_inst, + struct bt_conn *conn); + +/* This function will subscribe to GMCS CCCDs. + * After this, the function will start discovery of included services. + */ + +static void subscribe_mcs_char_func(struct bt_conn *conn, uint8_t err, + struct bt_gatt_subscribe_params *params) +{ + struct mcs_instance_t *mcs_inst = + CONTAINER_OF(params->disc_params, + struct mcs_instance_t, + discover_params); + bool subscription_done; + + if (err != 0) + { + LOG_DBG("Subscription callback error: %u", err); + params->subscribe = NULL; + discovery_complete(conn, err); + return; + } + + LOG_DBG("Subscribed: value handle: %d, ccc handle: %d", + params->value_handle, + params->ccc_handle); + + if (params->value == 0) + { + /* Unsubscribing, ignore */ + + return; + } + + /* Subscribe to next characteristic */ + + subscription_done = subscribe_next_mcs_char(mcs_inst, conn); + + if (subscription_done) + { + params->subscribe = NULL; +#ifdef CONFIG_BT_MCC_OTS + /* Start discovery of included services to find OTS */ + + discover_included(mcs_inst, conn); +#else + /* If OTS is not configured, discovery ends here */ + + discovery_complete(conn, 0); +#endif /* CONFIG_BT_MCC_OTS */ + } +} + +/* Subscribe to a characteristic - helper function */ + +static int do_subscribe( + struct mcs_instance_t *mcs_inst, struct bt_conn *conn, uint16_t handle, + struct bt_gatt_subscribe_params *sub_params) +{ + sub_params->ccc_handle = BT_GATT_AUTO_DISCOVER_CCC_HANDLE; + sub_params->end_handle = mcs_inst->end_handle; + sub_params->value_handle = handle; + sub_params->notify = mcs_notify_handler; + sub_params->subscribe = subscribe_mcs_char_func; + + /* disc_params pointer is also used as subscription flag */ + + sub_params->disc_params = &mcs_inst->discover_params; + atomic_set_bit(sub_params->flags, BT_GATT_SUBSCRIBE_FLAG_VOLATILE); + + LOG_DBG("Subscring to handle %d", handle); + return bt_gatt_subscribe(conn, sub_params); +} + +/* Subscribe to the next GMCS CCCD. + * @return true if there are no more characteristics to subscribe to + */ + +static bool subscribe_next_mcs_char(struct mcs_instance_t *mcs_inst, + struct bt_conn *conn) +{ + struct bt_gatt_subscribe_params *sub_params = NULL; + uint16_t handle; + + /* The characteristics may be in any order on the server, and + * not all of them may exist => need to check all. For each of the + * subscribable characteristics + * - check if we have a handle for it + * - check sub_params.disc_params pointer to see if we have + * already subscribed to it (set in do_subscribe() ). + */ + + if (mcs_inst->player_name_handle && + mcs_inst->player_name_sub_params.value && + mcs_inst->player_name_sub_params.disc_params == NULL) + { + sub_params = &mcs_inst->player_name_sub_params; + handle = mcs_inst->player_name_handle; + } + else if (mcs_inst->track_changed_handle && + mcs_inst->track_changed_sub_params.value && + mcs_inst->track_changed_sub_params.disc_params == NULL) + { + sub_params = &mcs_inst->track_changed_sub_params; + handle = mcs_inst->track_changed_handle; +#if defined(CONFIG_BT_MCC_READ_TRACK_TITLE_ENABLE_SUBSCRIPTION) + } + else if (mcs_inst->track_title_handle && + mcs_inst->track_title_sub_params.value && + mcs_inst->track_title_sub_params.disc_params == NULL) + { + sub_params = &mcs_inst->track_title_sub_params; + handle = mcs_inst->track_title_handle; +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_TITLE_ENABLE_SUBSCRIPTION) */ +#if defined(CONFIG_BT_MCC_READ_TRACK_DURATION) + } + else if (mcs_inst->track_duration_handle && + mcs_inst->track_duration_sub_params.value && + mcs_inst->track_duration_sub_params.disc_params == NULL) + { + sub_params = &mcs_inst->track_duration_sub_params; + handle = mcs_inst->track_duration_handle; +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_DURATION) */ +#if defined(CONFIG_BT_MCC_READ_TRACK_POSITION) + } + else if (mcs_inst->track_position_handle && + mcs_inst->track_position_sub_params.value && + mcs_inst->track_position_sub_params.disc_params == NULL) + { + sub_params = &mcs_inst->track_position_sub_params; + handle = mcs_inst->track_position_handle; +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_POSITION) */ +#if defined(CONFIG_BT_MCC_READ_PLAYBACK_SPEED) + } + else if (mcs_inst->playback_speed_handle && + mcs_inst->playback_speed_sub_params.value && + mcs_inst->playback_speed_sub_params.disc_params == NULL) + { + sub_params = &mcs_inst->playback_speed_sub_params; + handle = mcs_inst->playback_speed_handle; +#endif /* defined (CONFIG_BT_MCC_READ_PLAYBACK_SPEED) */ +#if defined(CONFIG_BT_MCC_READ_SEEKING_SPEED) + } + else if (mcs_inst->seeking_speed_handle && + mcs_inst->seeking_speed_sub_params.value && + mcs_inst->seeking_speed_sub_params.disc_params == NULL) + { + sub_params = &mcs_inst->seeking_speed_sub_params; + handle = mcs_inst->seeking_speed_handle; +#endif /* defined (CONFIG_BT_MCC_READ_SEEKING_SPEED) */ +#ifdef CONFIG_BT_MCC_OTS + } + else if (mcs_inst->current_track_obj_id_handle && + mcs_inst->current_track_obj_sub_params.value && + mcs_inst->current_track_obj_sub_params.disc_params == NULL) + { + sub_params = &mcs_inst->current_track_obj_sub_params; + handle = mcs_inst->current_track_obj_id_handle; + } + else if (mcs_inst->next_track_obj_id_handle && + mcs_inst->next_track_obj_sub_params.value && + mcs_inst->next_track_obj_sub_params.disc_params == NULL) + { + sub_params = &mcs_inst->next_track_obj_sub_params; + handle = mcs_inst->next_track_obj_id_handle; + } + else if (mcs_inst->parent_group_obj_id_handle && + mcs_inst->parent_group_obj_sub_params.value && + mcs_inst->parent_group_obj_sub_params.disc_params == NULL) + { + sub_params = &mcs_inst->parent_group_obj_sub_params; + handle = mcs_inst->parent_group_obj_id_handle; + } + else if (mcs_inst->current_group_obj_id_handle && + mcs_inst->current_group_obj_sub_params.value && + mcs_inst->current_group_obj_sub_params.disc_params == NULL) + { + sub_params = &mcs_inst->current_group_obj_sub_params; + handle = mcs_inst->current_group_obj_id_handle; +#endif /* CONFIG_BT_MCC_OTS */ +#if defined(CONFIG_BT_MCC_READ_PLAYING_ORDER) + } + else if (mcs_inst->playing_order_handle && + mcs_inst->playing_order_sub_params.value && + mcs_inst->playing_order_sub_params.disc_params == NULL) + { + sub_params = &mcs_inst->playing_order_sub_params; + handle = mcs_inst->playing_order_handle; +#endif /* defined(CONFIG_BT_MCC_READ_PLAYING_ORDER) */ +#if defined(CONFIG_BT_MCC_READ_MEDIA_STATE) + } + else if (mcs_inst->media_state_handle && + mcs_inst->media_state_sub_params.value && + mcs_inst->media_state_sub_params.disc_params == NULL) + { + sub_params = &mcs_inst->media_state_sub_params; + handle = mcs_inst->media_state_handle; +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_STATE) */ + } + else if (mcs_inst->cp_handle && mcs_inst->cp_sub_params.value && + mcs_inst->cp_sub_params.disc_params == NULL) + { + sub_params = &mcs_inst->cp_sub_params; + handle = mcs_inst->cp_handle; +#if defined(CONFIG_BT_MCC_READ_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED) + } + else if (mcs_inst->opcodes_supported_handle && + mcs_inst->opcodes_supported_sub_params.value && + mcs_inst->opcodes_supported_sub_params.disc_params == NULL) + { + sub_params = &mcs_inst->opcodes_supported_sub_params; + handle = mcs_inst->opcodes_supported_handle; +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED) */ +#ifdef CONFIG_BT_MCC_OTS + } + else if (mcs_inst->scp_handle && mcs_inst->scp_sub_params.value && + mcs_inst->scp_sub_params.disc_params == NULL) + { + sub_params = &mcs_inst->scp_sub_params; + handle = mcs_inst->scp_handle; + } + else if (mcs_inst->search_results_obj_id_handle && + mcs_inst->search_results_obj_sub_params.value && + mcs_inst->search_results_obj_sub_params.disc_params == NULL) + { + sub_params = &mcs_inst->search_results_obj_sub_params; + handle = mcs_inst->search_results_obj_id_handle; +#endif /* CONFIG_BT_MCC_OTS */ + } + + if (sub_params != NULL) + { + const int err = do_subscribe(mcs_inst, conn, handle, sub_params); + + if (err != 0) + { + LOG_DBG("Could not subscribe: %d", err); + discovery_complete(conn, err); + } + + return false; + } + + /* If we have come here, there are no more characteristics to + * subscribe to, and we are done. + */ + + return true; +} + +/* This function is called when characteristics are found. + * The function will store handles to GMCS characteristics. After this, the + * function will start subscription to characteristics + */ + +static uint8_t discover_mcs_char_func( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct mcs_instance_t *mcs_inst = CONTAINER_OF(params, + struct mcs_instance_t, + discover_params); + struct bt_gatt_chrc *chrc; + bool subscription_done = true; + + if (attr) + { + /* Found an attribute */ + + LOG_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle); + + if (params->type != BT_GATT_DISCOVER_CHARACTERISTIC) + { + /* But it was not a characteristic - continue search */ + + return BT_GATT_ITER_CONTINUE; + } + + /* We have found an attribute, and it is a characteristic */ + + /* Find out which attribute, and subscribe if we should */ + + chrc = (struct bt_gatt_chrc *)attr->user_data; + + if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_PLAYER_NAME)) + { + LOG_DBG("Player name, UUID: %s", bt_uuid_str(chrc->uuid)); + mcs_inst->player_name_handle = chrc->value_handle; + + /* Use discovery params pointer as subscription flag */ + + mcs_inst->player_name_sub_params.disc_params = NULL; + if (chrc->properties & BT_GATT_CHRC_NOTIFY) + { + mcs_inst->player_name_sub_params.value = BT_GATT_CCC_NOTIFY; + } +#ifdef CONFIG_BT_MCC_OTS + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_ICON_OBJ_ID)) + { + LOG_DBG("Icon Object, UUID: %s", bt_uuid_str(chrc->uuid)); + mcs_inst->icon_obj_id_handle = chrc->value_handle; +#endif /* CONFIG_BT_MCC_OTS */ +#if defined(CONFIG_BT_MCC_READ_MEDIA_PLAYER_ICON_URL) + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_ICON_URL)) + { + LOG_DBG("Icon URL, UUID: %s", bt_uuid_str(chrc->uuid)); + mcs_inst->icon_url_handle = chrc->value_handle; +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_PLAYER_ICON_URL) */ + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_TRACK_CHANGED)) + { + LOG_DBG("Track Changed, UUID: %s", bt_uuid_str(chrc->uuid)); + mcs_inst->track_changed_handle = chrc->value_handle; + mcs_inst->track_changed_sub_params.disc_params = NULL; + if (chrc->properties & BT_GATT_CHRC_NOTIFY) + { + mcs_inst->track_changed_sub_params.value = BT_GATT_CCC_NOTIFY; + } +#if defined(CONFIG_BT_MCC_READ_TRACK_TITLE) + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_TRACK_TITLE)) + { + LOG_DBG("Track Title, UUID: %s", bt_uuid_str(chrc->uuid)); + mcs_inst->track_title_handle = chrc->value_handle; +#if defined(BT_MCC_READ_TRACK_TITLE_ENABLE_SUBSCRIPTION) + mcs_inst->track_title_sub_params.disc_params = NULL; + if (chrc->properties & BT_GATT_CHRC_NOTIFY) + { + mcs_inst->track_title_sub_params.value = BT_GATT_CCC_NOTIFY; + } +#endif /* defined(BT_MCC_READ_TRACK_TITLE_ENABLE_SUBSCRIPTION) */ +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_TITLE) */ +#if defined(CONFIG_BT_MCC_READ_TRACK_DURATION) + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_TRACK_DURATION)) + { + LOG_DBG("Track Duration, UUID: %s", bt_uuid_str(chrc->uuid)); + mcs_inst->track_duration_handle = chrc->value_handle; + mcs_inst->track_duration_sub_params.disc_params = NULL; + if (chrc->properties & BT_GATT_CHRC_NOTIFY) + { + mcs_inst->track_duration_sub_params.value = BT_GATT_CCC_NOTIFY; + } +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_DURATION) */ +#if defined(CONFIG_BT_MCC_READ_TRACK_POSITION) || defined(CONFIG_BT_MCC_SET_TRACK_POSITION) + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_TRACK_POSITION)) + { + LOG_DBG("Track Position, UUID: %s", bt_uuid_str(chrc->uuid)); + mcs_inst->track_position_handle = chrc->value_handle; +#if defined(CONFIG_BT_MCC_READ_TRACK_POSITION) + mcs_inst->track_position_sub_params.disc_params = NULL; + if (chrc->properties & BT_GATT_CHRC_NOTIFY) + { + mcs_inst->track_position_sub_params.value = BT_GATT_CCC_NOTIFY; + } +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_POSITION) */ +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_POSITION) || defined(CONFIG_BT_MCC_SET_TRACK_POSITION) \ + + */ +#if defined(CONFIG_BT_MCC_READ_PLAYBACK_SPEED) || defined(CONFIG_BT_MCC_SET_PLAYBACK_SPEED) + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_PLAYBACK_SPEED)) + { + LOG_DBG("Playback Speed, UUID: %s", bt_uuid_str(chrc->uuid)); + mcs_inst->playback_speed_handle = chrc->value_handle; +#if defined(CONFIG_BT_MCC_READ_PLAYBACK_SPEED) + mcs_inst->playback_speed_sub_params.disc_params = NULL; + if (chrc->properties & BT_GATT_CHRC_NOTIFY) + { + mcs_inst->playback_speed_sub_params.value = BT_GATT_CCC_NOTIFY; + } +#endif /* defined (CONFIG_BT_MCC_READ_PLAYBACK_SPEED) */ +#endif /* defined (CONFIG_BT_MCC_READ_PLAYBACK_SPEED) || \ + + * defined (CONFIG_BT_MCC_SET_PLAYBACK_SPEED) \ + */ +#if defined(CONFIG_BT_MCC_READ_SEEKING_SPEED) + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_SEEKING_SPEED)) + { + LOG_DBG("Seeking Speed, UUID: %s", bt_uuid_str(chrc->uuid)); + mcs_inst->seeking_speed_handle = chrc->value_handle; + mcs_inst->seeking_speed_sub_params.disc_params = NULL; + if (chrc->properties & BT_GATT_CHRC_NOTIFY) + { + mcs_inst->seeking_speed_sub_params.value = BT_GATT_CCC_NOTIFY; + } +#endif /* defined (CONFIG_BT_MCC_READ_SEEKING_SPEED) */ +#ifdef CONFIG_BT_MCC_OTS + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_TRACK_SEGMENTS_OBJ_ID)) + { + LOG_DBG("Track Segments Object, UUID: %s", + bt_uuid_str(chrc->uuid)); + mcs_inst->segments_obj_id_handle = chrc->value_handle; + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_CURRENT_TRACK_OBJ_ID)) + { + LOG_DBG("Current Track Object, UUID: %s", bt_uuid_str(chrc->uuid)); + mcs_inst->current_track_obj_id_handle = chrc->value_handle; + mcs_inst->current_track_obj_sub_params.disc_params = NULL; + if (chrc->properties & BT_GATT_CHRC_NOTIFY) + { + mcs_inst->current_track_obj_sub_params.value = + BT_GATT_CCC_NOTIFY; + } + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_NEXT_TRACK_OBJ_ID)) + { + LOG_DBG("Next Track Object, UUID: %s", bt_uuid_str(chrc->uuid)); + mcs_inst->next_track_obj_id_handle = chrc->value_handle; + mcs_inst->next_track_obj_sub_params.disc_params = NULL; + if (chrc->properties & BT_GATT_CHRC_NOTIFY) + { + mcs_inst->next_track_obj_sub_params.value = BT_GATT_CCC_NOTIFY; + } + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_PARENT_GROUP_OBJ_ID)) + { + LOG_DBG("Parent Group Object, UUID: %s", bt_uuid_str(chrc->uuid)); + mcs_inst->parent_group_obj_id_handle = chrc->value_handle; + mcs_inst->parent_group_obj_sub_params.disc_params = NULL; + if (chrc->properties & BT_GATT_CHRC_NOTIFY) + { + mcs_inst->parent_group_obj_sub_params.value = + BT_GATT_CCC_NOTIFY; + } + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_CURRENT_GROUP_OBJ_ID)) + { + LOG_DBG("Group Object, UUID: %s", bt_uuid_str(chrc->uuid)); + mcs_inst->current_group_obj_id_handle = chrc->value_handle; + mcs_inst->current_group_obj_sub_params.disc_params = NULL; + if (chrc->properties & BT_GATT_CHRC_NOTIFY) + { + mcs_inst->current_group_obj_sub_params.value = + BT_GATT_CCC_NOTIFY; + } +#endif /* CONFIG_BT_MCC_OTS */ +#if defined(CONFIG_BT_MCC_READ_PLAYING_ORDER) || defined(CONFIG_BT_MCC_SET_PLAYING_ORDER) + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_PLAYING_ORDER)) + { + LOG_DBG("Playing Order, UUID: %s", bt_uuid_str(chrc->uuid)); + mcs_inst->playing_order_handle = chrc->value_handle; +#if defined(CONFIG_BT_MCC_READ_PLAYING_ORDER) + mcs_inst->playing_order_sub_params.disc_params = NULL; + if (chrc->properties & BT_GATT_CHRC_NOTIFY) + { + mcs_inst->playing_order_sub_params.value = BT_GATT_CCC_NOTIFY; + } +#endif /* defined(CONFIG_BT_MCC_READ_PLAYING_ORDER) */ +#endif /* defined(CONFIG_BT_MCC_READ_PLAYING_ORDER) || defined(CONFIG_BT_MCC_SET_PLAYING_ORDER) */ +#if defined(CONFIG_BT_MCC_READ_PLAYING_ORDER_SUPPORTED) + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_PLAYING_ORDERS)) + { + LOG_DBG("Playing Orders supported, UUID: %s", + bt_uuid_str(chrc->uuid)); + mcs_inst->playing_orders_supported_handle = chrc->value_handle; +#endif /* defined(CONFIG_BT_MCC_READ_PLAYING_ORDER_SUPPORTED) */ +#if defined(CONFIG_BT_MCC_READ_MEDIA_STATE) + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_MEDIA_STATE)) + { + LOG_DBG("Media State, UUID: %s", bt_uuid_str(chrc->uuid)); + mcs_inst->media_state_handle = chrc->value_handle; + mcs_inst->media_state_sub_params.disc_params = NULL; + if (chrc->properties & BT_GATT_CHRC_NOTIFY) + { + mcs_inst->media_state_sub_params.value = BT_GATT_CCC_NOTIFY; + } +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_STATE) */ + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_MEDIA_CONTROL_POINT)) + { + LOG_DBG("Media Control Point, UUID: %s", bt_uuid_str(chrc->uuid)); + mcs_inst->cp_handle = chrc->value_handle; + mcs_inst->cp_sub_params.disc_params = NULL; + if (chrc->properties & BT_GATT_CHRC_NOTIFY) + { + mcs_inst->cp_sub_params.value = BT_GATT_CCC_NOTIFY; + } +#if defined(CONFIG_BT_MCC_READ_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED) + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_MEDIA_CONTROL_OPCODES)) + { + LOG_DBG("Media control opcodes supported, UUID: %s", + bt_uuid_str(chrc->uuid)); + mcs_inst->opcodes_supported_handle = chrc->value_handle; + mcs_inst->opcodes_supported_sub_params.disc_params = NULL; + if (chrc->properties & BT_GATT_CHRC_NOTIFY) + { + mcs_inst->opcodes_supported_sub_params.value = + BT_GATT_CCC_NOTIFY; + } +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED) */ +#ifdef CONFIG_BT_MCC_OTS + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_SEARCH_CONTROL_POINT)) + { + LOG_DBG("Search control point, UUID: %s", bt_uuid_str(chrc->uuid)); + mcs_inst->scp_handle = chrc->value_handle; + mcs_inst->scp_sub_params.disc_params = NULL; + if (chrc->properties & BT_GATT_CHRC_NOTIFY) + { + mcs_inst->scp_sub_params.value = BT_GATT_CCC_NOTIFY; + } + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_MCS_SEARCH_RESULTS_OBJ_ID)) + { + LOG_DBG("Search Results object, UUID: %s", + bt_uuid_str(chrc->uuid)); + mcs_inst->search_results_obj_id_handle = chrc->value_handle; + mcs_inst->search_results_obj_sub_params.disc_params = NULL; + if (chrc->properties & BT_GATT_CHRC_NOTIFY) + { + mcs_inst->search_results_obj_sub_params.value = + BT_GATT_CCC_NOTIFY; + } +#endif /* CONFIG_BT_MCC_OTS */ +#if defined(CONFIG_BT_MCC_READ_CONTENT_CONTROL_ID) + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_CCID)) + { + LOG_DBG("Content Control ID, UUID: %s", bt_uuid_str(chrc->uuid)); + mcs_inst->content_control_id_handle = chrc->value_handle; +#endif /* defined(CONFIG_BT_MCC_READ_CONTENT_CONTROL_ID) */ + } + + /* Continue to search for more attributes */ + + return BT_GATT_ITER_CONTINUE; + } + + /* No more attributes found */ + + LOG_DBG("GMCS characteristics found"); + (void)memset(params, 0, sizeof(*params)); + + /* Either subscribe to characteristics, or continue to discovery of + * included services. + * Subscription is done after discovery, not in parallel with it, to avoid + * queuing many ATT requests that requires buffers. + */ + + if (subscribe_all) + { + subscription_done = subscribe_next_mcs_char(mcs_inst, conn); + } + + if (subscription_done) + { + /* Not subscribing, or there was nothing to subscribe to */ +#ifdef CONFIG_BT_MCC_OTS + /* Start discovery of included services to find OTS */ + + discover_included(mcs_inst, conn); +#else + /* If OTS is not configured, discovery ends here */ + + discovery_complete(conn, 0); +#endif /* CONFIG_BT_MCC_OTS */ + } + + return BT_GATT_ITER_STOP; +} + +/* This function is called when a (primary) GMCS service has been discovered. + * The function will store the start and end handle for the service. It will + * then start discovery of the characteristics of the GMCS service. + */ + +static uint8_t discover_primary_func( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_gatt_service_val *prim_service; + + if (attr) + { + struct mcs_instance_t *mcs_inst = + CONTAINER_OF(params, struct mcs_instance_t, discover_params); + int err; + + /* Found an attribute */ + + LOG_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle); + + if (params->type != BT_GATT_DISCOVER_PRIMARY) + { + /* But it was not a primary service - continue search */ + + LOG_WRN("Unexpected parameters"); + return BT_GATT_ITER_CONTINUE; + } + + /* We have found an attribute, and it is a primary service */ + + /* (Must be GMCS, since that is the one we searched for.) */ + + LOG_DBG("Primary discovery complete"); + LOG_DBG("UUID: %s", bt_uuid_str(attr->uuid)); + prim_service = (struct bt_gatt_service_val *)attr->user_data; + LOG_DBG("UUID: %s", bt_uuid_str(prim_service->uuid)); + + mcs_inst->start_handle = attr->handle + 1; + mcs_inst->end_handle = prim_service->end_handle; + + /* Start discovery of characteristics */ + + mcs_inst->discover_params.uuid = NULL; + mcs_inst->discover_params.start_handle = mcs_inst->start_handle; + mcs_inst->discover_params.end_handle = mcs_inst->end_handle; + mcs_inst->discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; + mcs_inst->discover_params.func = discover_mcs_char_func; + + LOG_DBG("Start discovery of GMCS characteristics"); + err = bt_gatt_discover(conn, &mcs_inst->discover_params); + if (err != 0) + { + LOG_DBG("Discovery failed: %d", err); + discovery_complete(conn, err); + } + + return BT_GATT_ITER_STOP; + } + + /* No attribute of the searched for type found */ + + LOG_DBG("Could not find an GMCS instance on the server"); + + discovery_complete(conn, -ENODATA); + return BT_GATT_ITER_STOP; +} + +int bt_mcc_init(struct bt_mcc_cb *cb) +{ + mcc_cb = cb; + +#ifdef CONFIG_BT_MCC_OTS + static bool ots_client_registered; + + if (!ots_client_registered) + { + static struct bt_ots_client_cb otc_cb; + int err; + + /* Set up the callbacks from OTC */ + + /* TODO: Have one single content callback. */ + + /* For now: Use the icon callback for content - it is the first, */ + + /* and this will anyway be reset later. */ + + otc_cb.obj_data_read = on_icon_content; + otc_cb.obj_selected = on_obj_selected; + otc_cb.obj_metadata_read = on_object_metadata; + + LOG_DBG("Object selected callback: %p", otc_cb.obj_selected); + LOG_DBG("Object content callback: %p", otc_cb.obj_data_read); + LOG_DBG("Object metadata callback: %p", otc_cb.obj_metadata_read); + + mcs_instance.otc.cb = &otc_cb; + err = bt_ots_client_register(&mcs_instance.otc); + if (err != 0) + { + LOG_DBG("Failed to register OTS client: %d", err); + + return err; + } + + ots_client_registered = true; + } +#endif /* CONFIG_BT_MCC_OTS */ + + return 0; +} + +/* Initiate discovery. + * Discovery is handled by a chain of functions, where each function does its + * part, and then initiates a further discovery, with a new callback + * function. + * + * The order of discovery is follows: 1: Discover GMCS primary service + * (started here) 2: Discover characteristics of GMCS 3: Subscribe to + * characteristics of GMCS 4: Discover OTS service included in GMCS 5: + * Discover characteristics of OTS and subscribe to them + */ + +int bt_mcc_discover_mcs(struct bt_conn *conn, bool subscribe) +{ + struct mcs_instance_t *mcs_inst; + struct bt_conn *ref; + int err; + + if (!conn) + { + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + /* TODO: Need to find existing or new MCS instance here */ + + return -EINVAL; + } + + if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + return -EBUSY; + } + + ref = bt_conn_ref(conn); + if (ref == NULL) + { + err = -ENOTCONN; + goto cleanup; + } + + subscribe_all = subscribe; + err = reset_mcs_inst(mcs_inst); + if (err != 0) + { + LOG_DBG("Failed to reset MCS instance %p: %d", mcs_inst, err); + bt_conn_unref(ref); + goto cleanup; + } + + (void)memcpy(&uuid, BT_UUID_GMCS, sizeof(uuid)); + + mcs_inst->discover_params.func = discover_primary_func; + mcs_inst->discover_params.uuid = &uuid.uuid; + mcs_inst->discover_params.type = BT_GATT_DISCOVER_PRIMARY; + mcs_inst->discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; + mcs_inst->discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + + LOG_DBG("start discovery of GMCS primary service"); + err = bt_gatt_discover(conn, &mcs_inst->discover_params); + if (err != 0) + { + bt_conn_unref(ref); + goto cleanup; + } + + mcs_inst->conn = ref; + + return 0; + +cleanup: + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + return err; +} + +int bt_mcc_read_player_name(struct bt_conn *conn) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->player_name_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + mcs_inst->read_params.func = mcc_read_player_name_cb; + mcs_inst->read_params.handle_count = 1U; + mcs_inst->read_params.single.handle = mcs_inst->player_name_handle; + mcs_inst->read_params.single.offset = 0U; + + err = bt_gatt_read(conn, &mcs_inst->read_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} + +#ifdef CONFIG_BT_MCC_OTS +int bt_mcc_read_icon_obj_id(struct bt_conn *conn) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->icon_obj_id_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + mcs_inst->read_params.func = mcc_read_icon_obj_id_cb; + mcs_inst->read_params.handle_count = 1U; + mcs_inst->read_params.single.handle = mcs_inst->icon_obj_id_handle; + mcs_inst->read_params.single.offset = 0U; + + err = bt_gatt_read(conn, &mcs_inst->read_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} +#endif /* CONFIG_BT_MCC_OTS */ + +#if defined(CONFIG_BT_MCC_READ_MEDIA_PLAYER_ICON_URL) +int bt_mcc_read_icon_url(struct bt_conn *conn) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->icon_url_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + mcs_inst->read_params.func = mcc_read_icon_url_cb; + mcs_inst->read_params.handle_count = 1U; + mcs_inst->read_params.single.handle = mcs_inst->icon_url_handle; + mcs_inst->read_params.single.offset = 0U; + + err = bt_gatt_read(conn, &mcs_inst->read_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_PLAYER_ICON_URL) */ + +#if defined(CONFIG_BT_MCC_READ_TRACK_TITLE) +int bt_mcc_read_track_title(struct bt_conn *conn) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->track_title_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + mcs_inst->read_params.func = mcc_read_track_title_cb; + mcs_inst->read_params.handle_count = 1U; + mcs_inst->read_params.single.handle = mcs_inst->track_title_handle; + mcs_inst->read_params.single.offset = 0U; + + err = bt_gatt_read(conn, &mcs_inst->read_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_TITLE) */ + +#if defined(CONFIG_BT_MCC_READ_TRACK_DURATION) +int bt_mcc_read_track_duration(struct bt_conn *conn) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->track_duration_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + mcs_inst->read_params.func = mcc_read_track_duration_cb; + mcs_inst->read_params.handle_count = 1U; + mcs_inst->read_params.single.handle = mcs_inst->track_duration_handle; + mcs_inst->read_params.single.offset = 0U; + + err = bt_gatt_read(conn, &mcs_inst->read_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_DURATION) */ + +#if defined(CONFIG_BT_MCC_READ_TRACK_POSITION) +int bt_mcc_read_track_position(struct bt_conn *conn) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->track_position_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + mcs_inst->read_params.func = mcc_read_track_position_cb; + mcs_inst->read_params.handle_count = 1U; + mcs_inst->read_params.single.handle = mcs_inst->track_position_handle; + mcs_inst->read_params.single.offset = 0U; + + err = bt_gatt_read(conn, &mcs_inst->read_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_POSITION) */ + +#if defined(CONFIG_BT_MCC_SET_TRACK_POSITION) +int bt_mcc_set_track_position(struct bt_conn *conn, int32_t pos) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->track_position_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + (void)memcpy(mcs_inst->write_buf, &pos, sizeof(pos)); + + mcs_inst->write_params.offset = 0; + mcs_inst->write_params.data = mcs_inst->write_buf; + mcs_inst->write_params.length = sizeof(pos); + mcs_inst->write_params.handle = mcs_inst->track_position_handle; + mcs_inst->write_params.func = mcs_write_track_position_cb; + + LOG_HEXDUMP_DBG(mcs_inst->write_params.data, sizeof(pos), + "Track position sent"); + + err = bt_gatt_write(conn, &mcs_inst->write_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} +#endif /* defined(CONFIG_BT_MCC_SET_TRACK_POSITION) */ + +#if defined(CONFIG_BT_MCC_READ_PLAYBACK_SPEED) +int bt_mcc_read_playback_speed(struct bt_conn *conn) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->playback_speed_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + mcs_inst->read_params.func = mcc_read_playback_speed_cb; + mcs_inst->read_params.handle_count = 1U; + mcs_inst->read_params.single.handle = mcs_inst->playback_speed_handle; + mcs_inst->read_params.single.offset = 0U; + + err = bt_gatt_read(conn, &mcs_inst->read_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} +#endif /* defined (CONFIG_BT_MCC_READ_PLAYBACK_SPEED) */ + +#if defined(CONFIG_BT_MCC_SET_PLAYBACK_SPEED) +int bt_mcc_set_playback_speed(struct bt_conn *conn, int8_t speed) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->playback_speed_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + (void)memcpy(mcs_inst->write_buf, &speed, sizeof(speed)); + + mcs_inst->write_params.offset = 0; + mcs_inst->write_params.data = mcs_inst->write_buf; + mcs_inst->write_params.length = sizeof(speed); + mcs_inst->write_params.handle = mcs_inst->playback_speed_handle; + mcs_inst->write_params.func = mcs_write_playback_speed_cb; + + LOG_HEXDUMP_DBG(mcs_inst->write_params.data, sizeof(speed), + "Playback speed"); + + err = bt_gatt_write(conn, &mcs_inst->write_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} +#endif /* defined (CONFIG_BT_MCC_SET_PLAYBACK_SPEED) */ + +#if defined(CONFIG_BT_MCC_READ_SEEKING_SPEED) +int bt_mcc_read_seeking_speed(struct bt_conn *conn) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->seeking_speed_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + mcs_inst->read_params.func = mcc_read_seeking_speed_cb; + mcs_inst->read_params.handle_count = 1U; + mcs_inst->read_params.single.handle = mcs_inst->seeking_speed_handle; + mcs_inst->read_params.single.offset = 0U; + + err = bt_gatt_read(conn, &mcs_inst->read_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} +#endif /* defined (CONFIG_BT_MCC_READ_SEEKING_SPEED) */ + +#ifdef CONFIG_BT_MCC_OTS +int bt_mcc_read_segments_obj_id(struct bt_conn *conn) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->segments_obj_id_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + mcs_inst->read_params.func = mcc_read_segments_obj_id_cb; + mcs_inst->read_params.handle_count = 1U; + mcs_inst->read_params.single.handle = mcs_inst->segments_obj_id_handle; + mcs_inst->read_params.single.offset = 0U; + + err = bt_gatt_read(conn, &mcs_inst->read_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} + +int bt_mcc_read_current_track_obj_id(struct bt_conn *conn) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->current_track_obj_id_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + mcs_inst->read_params.func = mcc_read_current_track_obj_id_cb; + mcs_inst->read_params.handle_count = 1U; + mcs_inst->read_params.single.handle = + mcs_inst->current_track_obj_id_handle; + mcs_inst->read_params.single.offset = 0U; + + err = bt_gatt_read(conn, &mcs_inst->read_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} + +int bt_mcc_set_current_track_obj_id(struct bt_conn *conn, uint64_t obj_id) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + if (!BT_MCS_VALID_OBJ_ID(obj_id)) + { + LOG_DBG("Object ID 0x%016llx invalid", obj_id); + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->current_track_obj_id_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + sys_put_le48(obj_id, mcs_inst->write_buf); + mcs_inst->write_params.offset = 0; + mcs_inst->write_params.data = mcs_inst->write_buf; + mcs_inst->write_params.length = BT_OTS_OBJ_ID_SIZE; + mcs_inst->write_params.handle = mcs_inst->current_track_obj_id_handle; + mcs_inst->write_params.func = mcs_write_current_track_obj_id_cb; + + LOG_HEXDUMP_DBG(mcs_inst->write_params.data, BT_OTS_OBJ_ID_SIZE, + "Object Id"); + + err = bt_gatt_write(conn, &mcs_inst->write_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} + +int bt_mcc_read_next_track_obj_id(struct bt_conn *conn) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->next_track_obj_id_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + mcs_inst->read_params.func = mcc_read_next_track_obj_id_cb; + mcs_inst->read_params.handle_count = 1U; + mcs_inst->read_params.single.handle = mcs_inst->next_track_obj_id_handle; + mcs_inst->read_params.single.offset = 0U; + + err = bt_gatt_read(conn, &mcs_inst->read_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} + +int bt_mcc_set_next_track_obj_id(struct bt_conn *conn, uint64_t obj_id) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + if (!BT_MCS_VALID_OBJ_ID(obj_id)) + { + LOG_DBG("Object ID 0x%016llx invalid", obj_id); + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->next_track_obj_id_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + sys_put_le48(obj_id, mcs_inst->write_buf); + mcs_inst->write_params.offset = 0; + mcs_inst->write_params.data = mcs_inst->write_buf; + mcs_inst->write_params.length = BT_OTS_OBJ_ID_SIZE; + mcs_inst->write_params.handle = mcs_inst->next_track_obj_id_handle; + mcs_inst->write_params.func = mcs_write_next_track_obj_id_cb; + + LOG_HEXDUMP_DBG(mcs_inst->write_params.data, BT_OTS_OBJ_ID_SIZE, + "Object Id"); + + err = bt_gatt_write(conn, &mcs_inst->write_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} + +int bt_mcc_read_parent_group_obj_id(struct bt_conn *conn) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->parent_group_obj_id_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + mcs_inst->read_params.func = mcc_read_parent_group_obj_id_cb; + mcs_inst->read_params.handle_count = 1U; + mcs_inst->read_params.single.handle = mcs_inst->parent_group_obj_id_handle; + mcs_inst->read_params.single.offset = 0U; + + err = bt_gatt_read(conn, &mcs_inst->read_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} + +int bt_mcc_read_current_group_obj_id(struct bt_conn *conn) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->current_group_obj_id_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + mcs_inst->read_params.func = mcc_read_current_group_obj_id_cb; + mcs_inst->read_params.handle_count = 1U; + mcs_inst->read_params.single.handle = + mcs_inst->current_group_obj_id_handle; + mcs_inst->read_params.single.offset = 0U; + + err = bt_gatt_read(conn, &mcs_inst->read_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} + +int bt_mcc_set_current_group_obj_id(struct bt_conn *conn, uint64_t obj_id) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + if (!BT_MCS_VALID_OBJ_ID(obj_id)) + { + LOG_DBG("Object ID 0x%016llx invalid", obj_id); + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->current_group_obj_id_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + sys_put_le48(obj_id, mcs_inst->write_buf); + mcs_inst->write_params.offset = 0; + mcs_inst->write_params.data = mcs_inst->write_buf; + mcs_inst->write_params.length = BT_OTS_OBJ_ID_SIZE; + mcs_inst->write_params.handle = mcs_inst->current_group_obj_id_handle; + mcs_inst->write_params.func = mcs_write_current_group_obj_id_cb; + + LOG_HEXDUMP_DBG(mcs_inst->write_params.data, BT_OTS_OBJ_ID_SIZE, + "Object Id"); + + err = bt_gatt_write(conn, &mcs_inst->write_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} +#endif /* CONFIG_BT_MCC_OTS */ + +#if defined(CONFIG_BT_MCC_READ_PLAYING_ORDER) +int bt_mcc_read_playing_order(struct bt_conn *conn) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->playing_order_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + mcs_inst->read_params.func = mcc_read_playing_order_cb; + mcs_inst->read_params.handle_count = 1U; + mcs_inst->read_params.single.handle = mcs_inst->playing_order_handle; + mcs_inst->read_params.single.offset = 0U; + + err = bt_gatt_read(conn, &mcs_inst->read_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} +#endif /* defined(CONFIG_BT_MCC_READ_PLAYING_ORDER) */ + +#if defined(CONFIG_BT_MCC_SET_PLAYING_ORDER) +int bt_mcc_set_playing_order(struct bt_conn *conn, uint8_t order) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + if (!IN_RANGE(order, BT_MCS_PLAYING_ORDER_SINGLE_ONCE, + BT_MCS_PLAYING_ORDER_SHUFFLE_REPEAT)) + { + LOG_DBG("Invalid playing order 0x%02X", order); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->playing_order_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + (void)memcpy(mcs_inst->write_buf, &order, sizeof(order)); + + mcs_inst->write_params.offset = 0; + mcs_inst->write_params.data = mcs_inst->write_buf; + mcs_inst->write_params.length = sizeof(order); + mcs_inst->write_params.handle = mcs_inst->playing_order_handle; + mcs_inst->write_params.func = mcs_write_playing_order_cb; + + LOG_HEXDUMP_DBG(mcs_inst->write_params.data, sizeof(order), + "Playing order"); + + err = bt_gatt_write(conn, &mcs_inst->write_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} +#endif /* defined(CONFIG_BT_MCC_SET_PLAYING_ORDER) */ + +#if defined(CONFIG_BT_MCC_READ_PLAYING_ORDER_SUPPORTED) +int bt_mcc_read_playing_orders_supported(struct bt_conn *conn) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->playing_orders_supported_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + mcs_inst->read_params.func = mcc_read_playing_orders_supported_cb; + mcs_inst->read_params.handle_count = 1U; + mcs_inst->read_params.single.handle = + mcs_inst->playing_orders_supported_handle; + mcs_inst->read_params.single.offset = 0U; + + err = bt_gatt_read(conn, &mcs_inst->read_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} +#endif /* defined(CONFIG_BT_MCC_READ_PLAYING_ORDER_SUPPORTED) */ + +#if defined(CONFIG_BT_MCC_READ_MEDIA_STATE) +int bt_mcc_read_media_state(struct bt_conn *conn) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->media_state_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + mcs_inst->read_params.func = mcc_read_media_state_cb; + mcs_inst->read_params.handle_count = 1U; + mcs_inst->read_params.single.handle = mcs_inst->media_state_handle; + mcs_inst->read_params.single.offset = 0U; + + err = bt_gatt_read(conn, &mcs_inst->read_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_STATE) */ + +#if defined(CONFIG_BT_MCC_SET_MEDIA_CONTROL_POINT) +int bt_mcc_send_cmd(struct bt_conn *conn, const struct mpl_cmd *cmd) +{ + struct mcs_instance_t *mcs_inst; + size_t length; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + if (cmd == NULL) + { + LOG_DBG("cmd is NULL"); + + return -EINVAL; + } + + if (!BT_MCS_VALID_OP(cmd->opcode)) + { + LOG_DBG("Opcode 0x%02X is invalid", cmd->opcode); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->cp_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + length = sizeof(cmd->opcode); + (void)memcpy(mcs_inst->write_buf, &cmd->opcode, length); + if (cmd->use_param) + { + length += sizeof(cmd->param); + (void)memcpy(&mcs_inst->write_buf[sizeof(cmd->opcode)], &cmd->param, + sizeof(cmd->param)); + } + + mcs_inst->write_params.offset = 0; + mcs_inst->write_params.data = mcs_inst->write_buf; + mcs_inst->write_params.length = length; + mcs_inst->write_params.handle = mcs_inst->cp_handle; + mcs_inst->write_params.func = mcs_write_cp_cb; + + LOG_HEXDUMP_DBG(mcs_inst->write_params.data, sizeof(*cmd), "Command sent"); + + err = bt_gatt_write(conn, &mcs_inst->write_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} +#endif /* defined(CONFIG_BT_MCC_SET_MEDIA_CONTROL_POINT) */ + +#if defined(CONFIG_BT_MCC_READ_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED) +int bt_mcc_read_opcodes_supported(struct bt_conn *conn) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->opcodes_supported_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + mcs_inst->read_params.func = mcc_read_opcodes_supported_cb; + mcs_inst->read_params.handle_count = 1U; + mcs_inst->read_params.single.handle = mcs_inst->opcodes_supported_handle; + mcs_inst->read_params.single.offset = 0U; + + err = bt_gatt_read(conn, &mcs_inst->read_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED) */ + +#ifdef CONFIG_BT_MCC_OTS +int bt_mcc_send_search(struct bt_conn *conn, const struct mpl_search *search) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + if (search == NULL) + { + LOG_DBG("search is NULL"); + + return -EINVAL; + } + + if (!IN_RANGE(search->len, SEARCH_LEN_MIN, SEARCH_LEN_MAX)) + { + LOG_DBG("Invalid search->len: %u", search->len); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->scp_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + (void)memcpy(mcs_inst->write_buf, &search->search, search->len); + + mcs_inst->write_params.offset = 0; + mcs_inst->write_params.data = mcs_inst->write_buf; + mcs_inst->write_params.length = search->len; + mcs_inst->write_params.handle = mcs_inst->scp_handle; + mcs_inst->write_params.func = mcs_write_scp_cb; + + LOG_HEXDUMP_DBG(mcs_inst->write_params.data, search->len, "Search sent"); + + err = bt_gatt_write(conn, &mcs_inst->write_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} + +int bt_mcc_read_search_results_obj_id(struct bt_conn *conn) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->search_results_obj_id_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + mcs_inst->read_params.func = mcc_read_search_results_obj_id_cb; + mcs_inst->read_params.handle_count = 1U; + mcs_inst->read_params.single.handle = + mcs_inst->search_results_obj_id_handle; + mcs_inst->read_params.single.offset = 0U; + + err = bt_gatt_read(conn, &mcs_inst->read_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} +#endif /* CONFIG_BT_MCC_OTS */ + +#if defined(CONFIG_BT_MCC_READ_CONTENT_CONTROL_ID) +int bt_mcc_read_content_control_id(struct bt_conn *conn) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (mcs_inst->content_control_id_handle == 0) + { + LOG_DBG("handle not set"); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + mcs_inst->read_params.func = mcc_read_content_control_id_cb; + mcs_inst->read_params.handle_count = 1U; + mcs_inst->read_params.single.handle = mcs_inst->content_control_id_handle; + mcs_inst->read_params.single.offset = 0U; + + err = bt_gatt_read(conn, &mcs_inst->read_params); + if (err != 0) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} +#endif /* defined(CONFIG_BT_MCC_READ_CONTENT_CONTROL_ID) */ + +#ifdef CONFIG_BT_MCC_OTS + +void on_obj_selected(struct bt_ots_client *otc_inst, + struct bt_conn *conn, int result) +{ + struct mcs_instance_t *mcs_inst = lookup_inst_by_conn(conn); + + ARG_UNUSED(otc_inst); + + LOG_DBG("Current object selected"); + + if (mcs_inst != NULL) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + /* TODO: Read metadata here? */ + + /* For now: Left to the application */ + + /* Only one object at a time is selected in OTS */ + + /* When the selected callback comes, a new object is selected */ + + /* Reset the object buffer */ + + net_buf_simple_reset(&otc_obj_buf); + + if (mcc_cb && mcc_cb->otc_obj_selected) + { + mcc_cb->otc_obj_selected(conn, OLCP_RESULT_TO_ERROR(result)); + } +} + +/* TODO: Merge the object callback functions into one */ + +/* Use a notion of the "active" object, as done in mpl.c, for tracking */ + +int on_icon_content( + struct bt_ots_client *otc_inst, struct bt_conn *conn, uint32_t offset, + uint32_t len, uint8_t *data_p, bool is_complete) +{ + int cb_err = 0; + + ARG_UNUSED(otc_inst); + + LOG_DBG("Received Media Player Icon content, %i bytes at offset %i", len, + offset); + + LOG_HEXDUMP_DBG(data_p, len, "Icon content"); + + if (len > net_buf_simple_tailroom(&otc_obj_buf)) + { + LOG_WRN("Can not fit whole object"); + cb_err = -EMSGSIZE; + } + + net_buf_simple_add_mem(&otc_obj_buf, data_p, + MIN(net_buf_simple_tailroom(&otc_obj_buf), len)); + + if (is_complete) + { + struct mcs_instance_t *mcs_inst = lookup_inst_by_conn(conn); + + LOG_DBG("Icon object received"); + + if (mcs_inst != NULL) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + if (mcc_cb && mcc_cb->otc_icon_object) + { + mcc_cb->otc_icon_object(conn, cb_err, &otc_obj_buf); + } + + /* Reset buf in case the same object is read again without */ + + /* calling select in between */ + + net_buf_simple_reset(&otc_obj_buf); + } + + return BT_OTS_CONTINUE; +} + +#if CONFIG_BT_MCC_LOG_LEVEL_DBG +struct track_seg_t +{ + uint8_t name_len; + char name[CONFIG_BT_MCC_SEGMENT_NAME_MAX]; + int32_t pos; +}; + +struct track_segs_t +{ + uint16_t cnt; + struct track_seg_t segs[CONFIG_BT_MCC_TRACK_SEGS_MAX_CNT]; +}; + +static void decode_track_segments(struct net_buf_simple *buff, + struct track_segs_t *track_segs) +{ + uint16_t i; + struct track_seg_t *seg; + uint8_t *name; + struct net_buf_simple tmp_buf; + + /* Copy the buf, to not consume the original in this debug function */ + + net_buf_simple_clone(buff, &tmp_buf); + + while (tmp_buf.len && track_segs->cnt < CONFIG_BT_MCC_TRACK_SEGS_MAX_CNT) + { + i = track_segs->cnt++; + seg = &track_segs->segs[i]; + + seg->name_len = net_buf_simple_pull_u8(&tmp_buf); + if (seg->name_len + sizeof(int32_t) > tmp_buf.len) + { + LOG_WRN("Segment too long"); + return; + } + + if (seg->name_len) + { + name = net_buf_simple_pull_mem(&tmp_buf, seg->name_len); + + if (seg->name_len >= CONFIG_BT_MCC_SEGMENT_NAME_MAX) + { + seg->name_len = CONFIG_BT_MCC_SEGMENT_NAME_MAX - 1; + } + + (void)memcpy(seg->name, name, seg->name_len); + } + + seg->name[seg->name_len] = '\0'; + + track_segs->segs[i].pos = (int32_t)net_buf_simple_pull_le32(&tmp_buf); + } +} +#endif /* CONFIG_BT_MCC_LOG_LEVEL_DBG */ + +int on_track_segments_content( + struct bt_ots_client *otc_inst, struct bt_conn *conn, uint32_t offset, + uint32_t len, uint8_t *data_p, bool is_complete) +{ + int cb_err = 0; + + ARG_UNUSED(otc_inst); + + LOG_DBG("Received Track Segments content, %i bytes at offset %i", len, + offset); + + if (len > net_buf_simple_tailroom(&otc_obj_buf)) + { + LOG_WRN("Can not fit whole object"); + cb_err = -EMSGSIZE; + } + + net_buf_simple_add_mem(&otc_obj_buf, data_p, + MIN(net_buf_simple_tailroom(&otc_obj_buf), len)); + + if (is_complete) + { + struct mcs_instance_t *mcs_inst = lookup_inst_by_conn(conn); + + LOG_DBG("Track segment object received"); + + if (mcs_inst != NULL) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + +#if CONFIG_BT_MCC_LOG_LEVEL_DBG + struct track_segs_t track_segments; + + track_segments.cnt = 0U; + decode_track_segments(&otc_obj_buf, &track_segments); + for (int i = 0; i < track_segments.cnt; i++) + { + LOG_DBG("Track segment %i:", i); + LOG_DBG("\t-Name\t:%s", track_segments.segs[i].name); + LOG_DBG("\t-Position\t:%d", track_segments.segs[i].pos); + } +#endif /* CONFIG_BT_MCC_LOG_LEVEL_DBG */ + + if (mcc_cb && mcc_cb->otc_track_segments_object) + { + mcc_cb->otc_track_segments_object(conn, cb_err, &otc_obj_buf); + } + + net_buf_simple_reset(&otc_obj_buf); + } + + return BT_OTS_CONTINUE; +} + +int on_current_track_content( + struct bt_ots_client *otc_inst, struct bt_conn *conn, uint32_t offset, + uint32_t len, uint8_t *data_p, bool is_complete) +{ + int cb_err = 0; + + ARG_UNUSED(otc_inst); + + LOG_DBG("Received Current Track content, %i bytes at offset %i", len, + offset); + + LOG_HEXDUMP_DBG(data_p, len, "Track content"); + + if (len > net_buf_simple_tailroom(&otc_obj_buf)) + { + LOG_WRN("Can not fit whole object"); + cb_err = -EMSGSIZE; + } + + net_buf_simple_add_mem(&otc_obj_buf, data_p, + MIN(net_buf_simple_tailroom(&otc_obj_buf), len)); + + if (is_complete) + { + struct mcs_instance_t *mcs_inst = lookup_inst_by_conn(conn); + + LOG_DBG("Current Track Object received"); + + if (mcs_inst != NULL) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + if (mcc_cb && mcc_cb->otc_current_track_object) + { + mcc_cb->otc_current_track_object(conn, cb_err, &otc_obj_buf); + } + + net_buf_simple_reset(&otc_obj_buf); + } + + return BT_OTS_CONTINUE; +} + +int on_next_track_content( + struct bt_ots_client *otc_inst, struct bt_conn *conn, uint32_t offset, + uint32_t len, uint8_t *data_p, bool is_complete) +{ + int cb_err = 0; + + ARG_UNUSED(otc_inst); + + LOG_DBG("Received Next Track content, %i bytes at offset %i", len, offset); + + LOG_HEXDUMP_DBG(data_p, len, "Track content"); + + if (len > net_buf_simple_tailroom(&otc_obj_buf)) + { + LOG_WRN("Can not fit whole object"); + cb_err = -EMSGSIZE; + } + + net_buf_simple_add_mem(&otc_obj_buf, data_p, + MIN(net_buf_simple_tailroom(&otc_obj_buf), len)); + + if (is_complete) + { + struct mcs_instance_t *mcs_inst = lookup_inst_by_conn(conn); + + LOG_DBG("Next Track Object received"); + + if (mcs_inst != NULL) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + if (mcc_cb && mcc_cb->otc_next_track_object) + { + mcc_cb->otc_next_track_object(conn, cb_err, &otc_obj_buf); + } + + net_buf_simple_reset(&otc_obj_buf); + } + + return BT_OTS_CONTINUE; +} + +#if CONFIG_BT_MCC_LOG_LEVEL_DBG +struct id_list_elem_t +{ + uint8_t type; + uint64_t id; +}; + +struct id_list_t +{ + struct id_list_elem_t ids[CONFIG_BT_MCC_GROUP_RECORDS_MAX]; + uint16_t cnt; +}; + +static void decode_group(struct net_buf_simple *buff, struct id_list_t *ids) +{ + struct net_buf_simple tmp_buf; + + /* Copy the buf, to not consume the original in this debug function */ + + net_buf_simple_clone(buff, &tmp_buf); + + while ((tmp_buf.len) && (ids->cnt < CONFIG_BT_MCC_GROUP_RECORDS_MAX)) + { + ids->ids[ids->cnt].type = net_buf_simple_pull_u8(&tmp_buf); + ids->ids[ids->cnt].id = net_buf_simple_pull_le48(&tmp_buf); + ids->cnt++; + } +} +#endif /* CONFIG_BT_MCC_LOG_LEVEL_DBG */ + +int on_parent_group_content( + struct bt_ots_client *otc_inst, struct bt_conn *conn, uint32_t offset, + uint32_t len, uint8_t *data_p, bool is_complete) +{ + int cb_err = 0; + + ARG_UNUSED(otc_inst); + + LOG_DBG("Received Parent Group content, %i bytes at offset %i", len, + offset); + + LOG_HEXDUMP_DBG(data_p, len, "Group content"); + + if (len > net_buf_simple_tailroom(&otc_obj_buf)) + { + LOG_WRN("Can not fit whole object"); + cb_err = -EMSGSIZE; + } + + net_buf_simple_add_mem(&otc_obj_buf, data_p, + MIN(net_buf_simple_tailroom(&otc_obj_buf), len)); + + if (is_complete) + { + struct mcs_instance_t *mcs_inst = lookup_inst_by_conn(conn); + + LOG_DBG("Parent Group object received"); + + if (mcs_inst != NULL) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + +#if CONFIG_BT_MCC_LOG_LEVEL_DBG + struct id_list_t group = + { + 0 + }; + + decode_group(&otc_obj_buf, &group); + for (int i = 0; i < group.cnt; i++) + { + char t[BT_OTS_OBJ_ID_STR_LEN]; + + (void)bt_ots_obj_id_to_str(group.ids[i].id, t, + BT_OTS_OBJ_ID_STR_LEN); + LOG_DBG("Object type: %d, object ID: %s", group.ids[i].type, t); + } +#endif /* CONFIG_BT_MCC_LOG_LEVEL_DBG */ + + if (mcc_cb && mcc_cb->otc_parent_group_object) + { + mcc_cb->otc_parent_group_object(conn, cb_err, &otc_obj_buf); + } + + net_buf_simple_reset(&otc_obj_buf); + } + + return BT_OTS_CONTINUE; +} + +int on_current_group_content( + struct bt_ots_client *otc_inst, struct bt_conn *conn, uint32_t offset, + uint32_t len, uint8_t *data_p, bool is_complete) +{ + int cb_err = 0; + + ARG_UNUSED(otc_inst); + + LOG_DBG("Received Current Group content, %i bytes at offset %i", len, + offset); + + LOG_HEXDUMP_DBG(data_p, len, "Group content"); + + if (len > net_buf_simple_tailroom(&otc_obj_buf)) + { + LOG_WRN("Can not fit whole object"); + cb_err = -EMSGSIZE; + } + + net_buf_simple_add_mem(&otc_obj_buf, data_p, + MIN(net_buf_simple_tailroom(&otc_obj_buf), len)); + + if (is_complete) + { + struct mcs_instance_t *mcs_inst = lookup_inst_by_conn(conn); + + LOG_DBG("Current Group object received"); + + if (mcs_inst != NULL) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + +#if CONFIG_BT_MCC_LOG_LEVEL_DBG + struct id_list_t group = + { + 0 + }; + + decode_group(&otc_obj_buf, &group); + for (int i = 0; i < group.cnt; i++) + { + char t[BT_OTS_OBJ_ID_STR_LEN]; + + (void)bt_ots_obj_id_to_str(group.ids[i].id, t, + BT_OTS_OBJ_ID_STR_LEN); + LOG_DBG("Object type: %d, object ID: %s", group.ids[i].type, t); + } +#endif /* CONFIG_BT_MCC_LOG_LEVEL_DBG */ + + if (mcc_cb && mcc_cb->otc_current_group_object) + { + mcc_cb->otc_current_group_object(conn, cb_err, &otc_obj_buf); + } + + net_buf_simple_reset(&otc_obj_buf); + } + + return BT_OTS_CONTINUE; +} + +void on_object_metadata( + struct bt_ots_client *otc_inst, struct bt_conn *conn, int err, + uint8_t metadata_read) +{ + struct mcs_instance_t *mcs_inst = lookup_inst_by_conn(conn); + + ARG_UNUSED(metadata_read); + + LOG_INF("Object's meta data:"); + LOG_INF("\tCurrent size\t:%u", otc_inst->cur_object.size.cur); + + if (otc_inst->cur_object.size.cur > otc_obj_buf.size) + { + LOG_DBG("Object larger than allocated buffer"); + } + + if (mcs_inst != NULL) + { + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + bt_ots_metadata_display(&otc_inst->cur_object, 1); + + if (mcc_cb && mcc_cb->otc_obj_metadata) + { + mcc_cb->otc_obj_metadata(conn, err); + } +} + +int bt_mcc_otc_read_object_metadata(struct bt_conn *conn) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + err = bt_ots_client_read_object_metadata(&mcs_inst->otc, conn, + BT_OTS_METADATA_REQ_ALL); + if (err != 0) + { + LOG_DBG("Error reading the object: %d", err); + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} + +int bt_mcc_otc_read_icon_object(struct bt_conn *conn) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + mcs_inst->otc.cb->obj_data_read = on_icon_content; + + err = bt_ots_client_read_object_data(&mcs_inst->otc, conn); + if (err != 0) + { + LOG_DBG("Error reading the object: %d", err); + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} + +int bt_mcc_otc_read_track_segments_object(struct bt_conn *conn) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + /* TODO: Assumes object is already selected */ + + mcs_inst->otc.cb->obj_data_read = on_track_segments_content; + + err = bt_ots_client_read_object_data(&mcs_inst->otc, conn); + if (err != 0) + { + LOG_DBG("Error reading the object: %d", err); + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} + +int bt_mcc_otc_read_current_track_object(struct bt_conn *conn) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + /* TODO: Assumes object is already selected */ + + mcs_inst->otc.cb->obj_data_read = on_current_track_content; + + err = bt_ots_client_read_object_data(&mcs_inst->otc, conn); + if (err != 0) + { + LOG_DBG("Error reading the object: %d", err); + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} + +int bt_mcc_otc_read_next_track_object(struct bt_conn *conn) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + /* TODO: Assumes object is already selected */ + + mcs_inst->otc.cb->obj_data_read = on_next_track_content; + + err = bt_ots_client_read_object_data(&mcs_inst->otc, conn); + if (err != 0) + { + LOG_DBG("Error reading the object: %d", err); + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} + +int bt_mcc_otc_read_parent_group_object(struct bt_conn *conn) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + /* TODO: Assumes object is already selected */ + + /* Reuse callback for current group */ + + mcs_inst->otc.cb->obj_data_read = on_parent_group_content; + + err = bt_ots_client_read_object_data(&mcs_inst->otc, conn); + if (err != 0) + { + LOG_DBG("Error reading the object: %d", err); + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} + +int bt_mcc_otc_read_current_group_object(struct bt_conn *conn) +{ + struct mcs_instance_t *mcs_inst; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + + return -EINVAL; + } + + mcs_inst = lookup_inst_by_conn(conn); + if (mcs_inst == NULL) + { + LOG_DBG("Could not lookup mcs_inst from conn %p", (void *)conn); + + return -EINVAL; + } + else if (atomic_test_and_set_bit(mcs_inst->flags, MCC_FLAG_BUSY)) + { + LOG_DBG("mcs_inst busy"); + return -EBUSY; + } + + /* TODO: Assumes object is already selected */ + + mcs_inst->otc.cb->obj_data_read = on_current_group_content; + + err = bt_ots_client_read_object_data(&mcs_inst->otc, conn); + if (err != 0) + { + LOG_DBG("Error reading the object: %d", err); + atomic_clear_bit(mcs_inst->flags, MCC_FLAG_BUSY); + } + + return err; +} + +#endif /* CONFIG_BT_MCC_OTS */ diff --git a/libs/zblue/audio/mcc_internal.h b/libs/zblue/audio/mcc_internal.h new file mode 100644 index 0000000000000..ec98b374bbb27 --- /dev/null +++ b/libs/zblue/audio/mcc_internal.h @@ -0,0 +1,181 @@ +/**************************************************************************** + * libs/zblue/audio/mcc_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Internal APIs for Bluetooth MCP. + */ + +/* Copyright (c) 2019 - 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_MCP_INTERNAL_ +#define ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_MCP_INTERNAL_ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +struct mcs_instance_t *lookup_inst_by_conn(struct bt_conn *conn); + +enum mcc_flag +{ + MCC_FLAG_BUSY, + + MCC_FLAG_NUM_FLAGS, /* keep as last */ +}; + +struct mcs_instance_t +{ + struct bt_conn *conn; + uint16_t start_handle; + uint16_t end_handle; + uint16_t player_name_handle; +#ifdef CONFIG_BT_MCC_OTS + uint16_t icon_obj_id_handle; +#endif /* CONFIG_BT_MCC_OTS */ +#if defined(CONFIG_BT_MCC_READ_MEDIA_PLAYER_ICON_URL) + uint16_t icon_url_handle; +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_PLAYER_ICON_URL) */ + uint16_t track_changed_handle; +#if defined(CONFIG_BT_MCC_READ_TRACK_TITLE) + uint16_t track_title_handle; +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_TITLE) */ +#if defined(CONFIG_BT_MCC_READ_TRACK_DURATION) + uint16_t track_duration_handle; +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_DURATION) */ +#if defined(CONFIG_BT_MCC_READ_TRACK_POSITION) || defined(CONFIG_BT_MCC_SET_TRACK_POSITION) + uint16_t track_position_handle; +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_POSITION) || defined(CONFIG_BT_MCC_SET_TRACK_POSITION) \ + */ +#if defined(CONFIG_BT_MCC_READ_PLAYBACK_SPEED) || defined(CONFIG_BT_MCC_SET_PLAYBACK_SPEED) + uint16_t playback_speed_handle; +#endif /* defined (CONFIG_BT_MCC_READ_PLAYBACK_SPEED) || */ + /* defined (CONFIG_BT_MCC_SET_PLAYBACK_SPEED) */ +#if defined(CONFIG_BT_MCC_READ_SEEKING_SPEED) + uint16_t seeking_speed_handle; +#endif /* defined (CONFIG_BT_MCC_READ_SEEKING_SPEED) */ +#ifdef CONFIG_BT_MCC_OTS + uint16_t segments_obj_id_handle; + uint16_t current_track_obj_id_handle; + uint16_t next_track_obj_id_handle; + uint16_t current_group_obj_id_handle; + uint16_t parent_group_obj_id_handle; +#endif /* CONFIG_BT_MCC_OTS */ +#if defined(CONFIG_BT_MCC_READ_PLAYING_ORDER) || defined(CONFIG_BT_MCC_SET_PLAYING_ORDER) + uint16_t playing_order_handle; +#endif /* defined(CONFIG_BT_MCC_READ_PLAYING_ORDER) || defined(CONFIG_BT_MCC_SET_PLAYING_ORDER) */ +#if defined(CONFIG_BT_MCC_READ_PLAYING_ORDER_SUPPORTED) + uint16_t playing_orders_supported_handle; +#endif /* defined(CONFIG_BT_MCC_READ_PLAYING_ORDER_SUPPORTED) */ +#if defined(CONFIG_BT_MCC_READ_MEDIA_STATE) + uint16_t media_state_handle; +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_STATE) */ + uint16_t cp_handle; +#if defined(CONFIG_BT_MCC_READ_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED) + uint16_t opcodes_supported_handle; +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED) */ +#ifdef CONFIG_BT_MCC_OTS + uint16_t scp_handle; + uint16_t search_results_obj_id_handle; +#endif /* CONFIG_BT_MCC_OTS */ +#if defined(CONFIG_BT_MCC_READ_CONTENT_CONTROL_ID) + uint16_t content_control_id_handle; +#endif /* defined(CONFIG_BT_MCC_READ_CONTENT_CONTROL_ID) */ + + /* The write buffer is used for + * - track position (4 octets) + * - playback speed (1 octet) + * - playing order (1 octet) + * - the control point (5 octets) + * (1 octet opcode + optionally 4 octet param) (mpl_cmd.opcode + + * mpl_cmd.param) If the object transfer client is included, it is also + * used for + * - object IDs (6 octets - BT_OTS_OBJ_ID_SIZE) and + * - the search control point (64 octets - SEARCH_LEN_MAX) + * + * If there is no OTC, the largest is control point If OTC is included, the + * largest is the search control point + */ +#ifdef CONFIG_BT_MCC_OTS + char write_buf[SEARCH_LEN_MAX]; +#else + /* Trick to be able to use sizeof on members of a struct type */ + + /* TODO: Rewrite the mpl_cmd to have the "use_param" parameter */ + + /* separately, and the opcode and param alone as a struct */ + + char write_buf[sizeof(((struct mpl_cmd *)0)->opcode) + + sizeof(((struct mpl_cmd *)0)->param)]; +#endif /* CONFIG_BT_MCC_OTS */ + + struct bt_gatt_discover_params discover_params; + struct bt_gatt_read_params read_params; + struct bt_gatt_write_params write_params; + + /** Any fields below here cannot be memset as part of a reset */ + + struct bt_gatt_subscribe_params player_name_sub_params; + struct bt_gatt_subscribe_params track_changed_sub_params; +#if defined(CONFIG_BT_MCC_READ_TRACK_TITLE_ENABLE_SUBSCRIPTION) + struct bt_gatt_subscribe_params track_title_sub_params; +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_TITLE_ENABLE_SUBSCRIPTION) */ +#if defined(CONFIG_BT_MCC_READ_TRACK_DURATION) + struct bt_gatt_subscribe_params track_duration_sub_params; +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_DURATION) */ +#if defined(CONFIG_BT_MCC_READ_TRACK_POSITION) + struct bt_gatt_subscribe_params track_position_sub_params; +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_POSITION)*/ +#if defined(CONFIG_BT_MCC_READ_PLAYBACK_SPEED) + struct bt_gatt_subscribe_params playback_speed_sub_params; +#endif /* defined (CONFIG_BT_MCC_READ_PLAYBACK_SPEED) */ +#if defined(CONFIG_BT_MCC_READ_SEEKING_SPEED) + struct bt_gatt_subscribe_params seeking_speed_sub_params; +#endif /* defined (CONFIG_BT_MCC_READ_SEEKING_SPEED) */ +#ifdef CONFIG_BT_MCC_OTS + struct bt_gatt_subscribe_params current_track_obj_sub_params; + struct bt_gatt_subscribe_params next_track_obj_sub_params; + struct bt_gatt_subscribe_params parent_group_obj_sub_params; + struct bt_gatt_subscribe_params current_group_obj_sub_params; +#endif /* CONFIG_BT_MCC_OTS */ +#if defined(CONFIG_BT_MCC_READ_PLAYING_ORDER) + struct bt_gatt_subscribe_params playing_order_sub_params; +#endif /* defined(CONFIG_BT_MCC_READ_PLAYING_ORDER) */ +#if defined(CONFIG_BT_MCC_READ_MEDIA_STATE) + struct bt_gatt_subscribe_params media_state_sub_params; +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_STATE) */ + struct bt_gatt_subscribe_params cp_sub_params; +#if defined(CONFIG_BT_MCC_READ_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED) + struct bt_gatt_subscribe_params opcodes_supported_sub_params; +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED) */ +#ifdef CONFIG_BT_MCC_OTS + struct bt_gatt_subscribe_params scp_sub_params; + struct bt_gatt_subscribe_params search_results_obj_sub_params; +#endif /* CONFIG_BT_MCC_OTS */ + +#ifdef CONFIG_BT_MCC_OTS + struct bt_ots_client otc; +#endif /* CONFIG_BT_MCC_OTS */ + + atomic_define(flags, MCC_FLAG_NUM_FLAGS); +}; + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_MCP_INTERNAL_ */ diff --git a/libs/zblue/audio/mcs.c b/libs/zblue/audio/mcs.c new file mode 100644 index 0000000000000..25500b4c9081b --- /dev/null +++ b/libs/zblue/audio/mcs.c @@ -0,0 +1,2275 @@ +/**************************************************************************** + * libs/zblue/audio/mcs.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Bluetooth Media Control Service + */ + +/* Copyright (c) 2019 - 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "audio_internal.h" +#include "common/bt_str.h" +#include "media_proxy_internal.h" +#include "mcs_internal.h" + +LOG_MODULE_REGISTER(bt_mcs, CONFIG_BT_MCS_LOG_LEVEL); + +/* The value 1000 was arbitrarily chosen. No action should take longer than + * this, and using a non-((clock_t)-1) value may help catch any programming + * issues as locking the mutexes may trigger assertions + */ +#define MUTEX_TIMEOUT MSEC2TICK(1000U) + +static int notify(struct bt_conn *conn, + const struct bt_uuid *uuid, const void *data, uint16_t + len); + +static struct media_proxy_sctrl_cbs cbs; + +struct mcs_flags +{ + bool player_name_changed : 1; + bool player_name_dirty : 1; + bool icon_url_dirty : 1; + bool track_changed_changed : 1; + bool track_title_changed : 1; + bool track_title_dirty : 1; + bool track_duration_changed : 1; + bool track_position_changed : 1; + bool playback_speed_changed : 1; + bool seeking_speed_changed : 1; + bool playing_order_changed : 1; + bool media_state_changed : 1; + bool media_control_opcodes_changed : 1; + bool media_control_point_busy : 1; + bool media_control_point_result : 1; +#if defined(CONFIG_BT_OTS) + bool current_track_obj_id_changed : 1; + bool next_track_obj_id_changed : 1; + bool parent_group_obj_id_changed : 1; + bool current_group_obj_id_changed : 1; + bool search_results_obj_id_changed : 1; + bool search_control_point_busy : 1; + bool search_control_point_result : 1; +#endif /* CONFIG_BT_OTS */ +}; + +static struct mcs_inst +{ + rmutex_t mutex; + + /* Client states. Access and modification of these shall be guarded by the + * mutex + */ + + struct client_state + { + struct mcs_flags flags; + struct mpl_cmd_ntf cmd_ntf; +#if defined(CONFIG_BT_OTS) + uint8_t search_control_point_result; +#endif /* CONFIG_BT_OTS */ + } + + clients[CONFIG_BT_MAX_CONN]; + + struct k_work_delayable notify_work; +} mcs_inst; +static struct bt_gatt_service mcs; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + ARG_UNUSED(reason); + + __maybe_unused int err; + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + err = nxrmutex_ticklock(&mcs_inst.mutex, MUTEX_TIMEOUT); + DEBUGASSERT(err == 0); + + /* Clear data on disconnect */ + + memset(&mcs_inst.clients[bt_conn_index(conn)], 0, + sizeof(struct client_state)); + + err = nxrmutex_unlock(&mcs_inst.mutex); + DEBUGASSERT(err == 0); +} + +BT_CONN_CB_DEFINE(conn_callbacks) = +{ + .disconnected = disconnected, +}; + +struct mcs_notify_cb_info +{ + const struct bt_gatt_attr *attr; + void (*value_cb)(struct mcs_flags *flags, bool set); + + /* Will be set to true if there are any pending notifications for any + * connected devices + */ + + bool any_pending_notifications; +}; + +static void set_value_changed_cb(struct bt_conn *conn, void *data) +{ + struct mcs_notify_cb_info *cb_info = data; + const struct bt_gatt_attr *attr = cb_info->attr; + struct bt_conn_info info; + struct mcs_flags *flags; + int err; + + err = bt_conn_get_info(conn, &info); + DEBUGASSERT(err == 0); + + if (info.state != BT_CONN_STATE_CONNECTED) + { + /* Not connected */ + + return; + } + + flags = &mcs_inst.clients[bt_conn_index(conn)].flags; + + if (bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_NOTIFY)) + { + /* Set the specific flag based on the provided callback */ + + cb_info->value_cb(flags, true); + + /* We may schedule the same work multiple times, but that is OK as + * scheduling the same work multiple times is a no-op + */ + + err = k_work_schedule(&mcs_inst.notify_work, 0); + DEBUGASSERT(err >= 0); + cb_info->any_pending_notifications = true; + } + else + { + /* Set the specific flag based on the provided callback */ + + cb_info->value_cb(flags, false); + + /* Check if all flags are cleared; if this is false for all connections + * we can cancel any existing notify work + */ + + if (!cb_info->any_pending_notifications) + { + cb_info->any_pending_notifications = + !util_memeq(flags, &(struct mcs_flags) + { + 0 + }, + + sizeof(*flags)); + } + } +} + +static void set_value_changed( + void (*value_cb)(struct mcs_flags *flags, bool set), + const struct bt_uuid *uuid) +{ + struct mcs_notify_cb_info cb_info = + { + .value_cb = value_cb, + .attr = bt_gatt_find_by_uuid(mcs.attrs, 0, uuid), + .any_pending_notifications = false, + }; + + __maybe_unused int err; + + err = nxrmutex_ticklock(&mcs_inst.mutex, MUTEX_TIMEOUT); + DEBUGASSERT(err == 0); + + DEBUGASSERT(cb_info.attr != NULL); + bt_conn_foreach(BT_CONN_TYPE_LE, set_value_changed_cb, &cb_info); + + if (!cb_info.any_pending_notifications) + { + (void)k_work_cancel_delayable(&mcs_inst.notify_work); + } + + err = nxrmutex_unlock(&mcs_inst.mutex); + DEBUGASSERT(err == 0); +} + +static void set_player_name_changed_cb(struct mcs_flags *flags, bool set) +{ + flags->player_name_changed = set; + flags->player_name_dirty = set; +} + +static void set_icon_url_changed_cb(struct mcs_flags *flags, bool set) +{ + flags->icon_url_dirty = set; +} + +static void set_track_changed_changed_cb(struct mcs_flags *flags, bool set) +{ + flags->track_changed_changed = set; +} + +static void set_track_title_changed_cb(struct mcs_flags *flags, bool set) +{ + flags->track_title_changed = set; + flags->track_title_dirty = set; +} + +static void set_track_position_changed_cb(struct mcs_flags *flags, bool set) +{ + flags->track_position_changed = set; +} + +static void set_track_duration_changed_cb(struct mcs_flags *flags, bool set) +{ + flags->track_duration_changed = set; +} + +static void set_playback_speed_changed_cb(struct mcs_flags *flags, bool set) +{ + flags->playback_speed_changed = set; +} + +static void set_seeking_speed_changed_cb(struct mcs_flags *flags, bool set) +{ + flags->seeking_speed_changed = set; +} +#if defined(CONFIG_BT_OTS) + +static void set_current_track_obj_id_changed_cb(struct mcs_flags *flags, + bool set) +{ + flags->current_track_obj_id_changed = set; +} + +static void set_next_track_obj_id_changed_cb(struct mcs_flags *flags, + bool set) +{ + flags->next_track_obj_id_changed = set; +} + +static void set_parent_group_obj_id_changed_cb(struct mcs_flags *flags, + bool set) +{ + flags->parent_group_obj_id_changed = set; +} + +static void set_current_group_obj_id_changed_cb(struct mcs_flags *flags, + bool set) +{ + flags->current_group_obj_id_changed = set; +} +#endif /* CONFIG_BT_OTS */ + +static void set_playing_order_changed_cb(struct mcs_flags *flags, bool set) +{ + flags->playing_order_changed = set; +} + +static void set_media_state_changed_cb(struct mcs_flags *flags, bool set) +{ + flags->media_state_changed = set; +} + +static void set_media_control_opcodes_changed_cb(struct mcs_flags *flags, + bool set) +{ + flags->media_control_opcodes_changed = set; +} + +static void clear_media_control_ntf_cb(struct mcs_flags *flags, bool set) +{ + DEBUGASSERT(!set); + flags->media_control_point_result = false; +} + +#if defined(CONFIG_BT_OTS) +static void set_search_results_obj_id_changed_cb(struct mcs_flags *flags, + bool set) +{ + flags->search_results_obj_id_changed = set; +} + +static void clear_search_control_ntf_cb(struct mcs_flags *flags, bool set) +{ + DEBUGASSERT(!set); + flags->search_control_point_result = false; +} +#endif /* CONFIG_BT_OTS */ + +/* Functions for reading and writing attributes, and for keeping track + * of attribute configuration changes. Functions for notifications are placed + * after the service definition. + */ + +static ssize_t read_player_name( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + const char *name = media_proxy_sctrl_get_player_name(); + + LOG_DBG("Player name read: %s (offset %u)", name, offset); + + /* Conn is NULL if doing a local read */ + + if (conn != NULL) + { + struct mcs_flags *flags; + __maybe_unused int err; + + err = nxrmutex_ticklock(&mcs_inst.mutex, MUTEX_TIMEOUT); + DEBUGASSERT(err == 0); + + flags = &mcs_inst.clients[bt_conn_index(conn)].flags; + + if (flags->player_name_dirty && offset != 0U) + { + err = nxrmutex_unlock(&mcs_inst.mutex); + DEBUGASSERT(err == 0); + + return BT_GATT_ERR(BT_MCS_ERR_LONG_VAL_CHANGED); + } + + flags->player_name_dirty = false; + + err = nxrmutex_unlock(&mcs_inst.mutex); + DEBUGASSERT(err == 0); + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, name, strlen(name)); +} + +static void player_name_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); + + /* Clear any pending notifications for any connections that unsubscribes */ + + if (value == 0U) + { + set_value_changed(set_player_name_changed_cb, BT_UUID_MCS_PLAYER_NAME); + } +} + +#ifdef CONFIG_BT_OTS +static ssize_t read_icon_id( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + uint64_t icon_id = media_proxy_sctrl_get_icon_id(); + uint8_t icon_id_le[BT_OTS_OBJ_ID_SIZE]; + + sys_put_le48(icon_id, icon_id_le); + + LOG_DBG_OBJ_ID("Icon object read: ", icon_id); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, icon_id_le, + sizeof(icon_id_le)); +} +#endif /* CONFIG_BT_OTS */ + +static ssize_t read_icon_url( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + const char *url = media_proxy_sctrl_get_icon_url(); + + LOG_DBG("Icon URL read, offset: %d, len:%d, URL: %s", offset, len, url); + + /* Conn is NULL if doing a local read */ + + if (conn != NULL) + { + struct mcs_flags *flags; + __maybe_unused int err; + + err = nxrmutex_ticklock(&mcs_inst.mutex, MUTEX_TIMEOUT); + DEBUGASSERT(err == 0); + + flags = &mcs_inst.clients[bt_conn_index(conn)].flags; + + if (flags->icon_url_dirty && offset != 0U) + { + err = nxrmutex_unlock(&mcs_inst.mutex); + DEBUGASSERT(err == 0); + + return BT_GATT_ERR(BT_MCS_ERR_LONG_VAL_CHANGED); + } + + flags->icon_url_dirty = false; + + err = nxrmutex_unlock(&mcs_inst.mutex); + DEBUGASSERT(err == 0); + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, url, strlen(url)); +} + +static void track_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); + + /* Clear any pending notifications for any connections that unsubscribes */ + + if (value == 0U) + { + set_value_changed(set_track_changed_changed_cb, + BT_UUID_MCS_TRACK_CHANGED); + } +} + +static ssize_t read_track_title( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + const char *title = media_proxy_sctrl_get_track_title(); + + LOG_DBG("Track title read, offset: %d, len:%d, title: %s", offset, len, + title); + + /* Conn is NULL if doing a local read */ + + if (conn != NULL) + { + struct mcs_flags *flags; + __maybe_unused int err; + + err = nxrmutex_ticklock(&mcs_inst.mutex, MUTEX_TIMEOUT); + DEBUGASSERT(err == 0); + + flags = &mcs_inst.clients[bt_conn_index(conn)].flags; + + if (flags->track_title_dirty && offset != 0U) + { + err = nxrmutex_unlock(&mcs_inst.mutex); + DEBUGASSERT(err == 0); + + return BT_GATT_ERR(BT_MCS_ERR_LONG_VAL_CHANGED); + } + + flags->track_title_dirty = false; + + err = nxrmutex_unlock(&mcs_inst.mutex); + DEBUGASSERT(err == 0); + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, title, + strlen(title)); +} + +static void track_title_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); + + /* Clear any pending notifications for any connections that unsubscribes */ + + if (value == 0U) + { + set_value_changed(set_track_title_changed_cb, BT_UUID_MCS_TRACK_TITLE); + } +} + +static ssize_t read_track_duration( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + int32_t duration = media_proxy_sctrl_get_track_duration(); + int32_t duration_le = sys_cpu_to_le32(duration); + + LOG_DBG("Track duration read: %d (0x%08x)", duration, duration); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &duration_le, + sizeof(duration_le)); +} + +static void track_duration_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); + + /* Clear any pending notifications for any connections that unsubscribes */ + + if (value == 0U) + { + set_value_changed(set_track_duration_changed_cb, + BT_UUID_MCS_TRACK_DURATION); + } +} + +static ssize_t read_track_position( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + int32_t position = media_proxy_sctrl_get_track_position(); + int32_t position_le = sys_cpu_to_le32(position); + + LOG_DBG("Track position read: %d (0x%08x)", position, position); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &position_le, + sizeof(position_le)); +} + +static ssize_t write_track_position( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + int32_t position; + + ARG_UNUSED(conn); + ARG_UNUSED(attr); + ARG_UNUSED(flags); + + if (offset != 0) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (len != sizeof(position)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + position = sys_get_le32((uint8_t *)buf); + + media_proxy_sctrl_set_track_position(position); + + LOG_DBG("Track position write: %d", position); + + return len; +} + +static void track_position_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); + + /* Clear any pending notifications for any connections that unsubscribes */ + + if (value == 0U) + { + set_value_changed(set_track_position_changed_cb, + BT_UUID_MCS_TRACK_POSITION); + } +} + +static ssize_t read_playback_speed( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + int8_t speed = media_proxy_sctrl_get_playback_speed(); + + LOG_DBG("Playback speed read: %d", speed); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &speed, + sizeof(speed)); +} + +static ssize_t write_playback_speed( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + int8_t speed; + + ARG_UNUSED(conn); + ARG_UNUSED(attr); + ARG_UNUSED(flags); + + if (offset != 0) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (len != sizeof(speed)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + memcpy(&speed, buf, len); + + media_proxy_sctrl_set_playback_speed(speed); + + LOG_DBG("Playback speed write: %d", speed); + + return len; +} + +static void playback_speed_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); + + /* Clear any pending notifications for any connections that unsubscribes */ + + if (value == 0U) + { + set_value_changed(set_playback_speed_changed_cb, + BT_UUID_MCS_PLAYBACK_SPEED); + } +} + +static ssize_t read_seeking_speed( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + int8_t speed = media_proxy_sctrl_get_seeking_speed(); + + LOG_DBG("Seeking speed read: %d", speed); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &speed, + sizeof(speed)); +} + +static void seeking_speed_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); + + /* Clear any pending notifications for any connections that unsubscribes */ + + if (value == 0U) + { + set_value_changed(set_seeking_speed_changed_cb, + BT_UUID_MCS_SEEKING_SPEED); + } +} + +#ifdef CONFIG_BT_OTS +static ssize_t read_track_segments_id( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + uint64_t track_segments_id = media_proxy_sctrl_get_track_segments_id(); + uint8_t track_segments_id_le[BT_OTS_OBJ_ID_SIZE]; + + sys_put_le48(track_segments_id, track_segments_id_le); + + LOG_DBG_OBJ_ID("Track segments ID read: ", track_segments_id); + + return bt_gatt_attr_read(conn, + attr, + buf, + len, + offset, + track_segments_id_le, + + sizeof(track_segments_id_le)); +} + +static ssize_t read_current_track_id( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + uint64_t track_id = media_proxy_sctrl_get_current_track_id(); + uint8_t track_id_le[BT_OTS_OBJ_ID_SIZE]; + + sys_put_le48(track_id, track_id_le); + + LOG_DBG_OBJ_ID("Current track ID read: ", track_id); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, track_id_le, + sizeof(track_id_le)); +} + +static ssize_t write_current_track_id( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + uint64_t id; + + ARG_UNUSED(conn); + ARG_UNUSED(attr); + ARG_UNUSED(flags); + + if (offset != 0) + { + LOG_DBG("Invalid offset"); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (len != BT_OTS_OBJ_ID_SIZE) + { + LOG_DBG("Invalid length"); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + id = sys_get_le48((uint8_t *)buf); + + if (IS_ENABLED(CONFIG_BT_MCS_LOG_LEVEL_DBG)) + { + char str[BT_OTS_OBJ_ID_STR_LEN]; + + (void)bt_ots_obj_id_to_str(id, str, sizeof(str)); + LOG_DBG("Current track write: offset: %d, len: %d, track ID: %s", + offset, + len, str); + } + + media_proxy_sctrl_set_current_track_id(id); + + return BT_OTS_OBJ_ID_SIZE; +} + +static void current_track_id_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); + + /* Clear any pending notifications for any connections that unsubscribes */ + + if (value == 0U) + { + set_value_changed(set_current_track_obj_id_changed_cb, + BT_UUID_MCS_CURRENT_TRACK_OBJ_ID); + } +} + +static ssize_t read_next_track_id( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + uint64_t track_id = media_proxy_sctrl_get_next_track_id(); + uint8_t track_id_le[BT_OTS_OBJ_ID_SIZE]; + + sys_put_le48(track_id, track_id_le); + + if (track_id == MPL_NO_TRACK_ID) + { + LOG_DBG("Next track read, but it is empty"); + + /* "If the media player has no next track, the length of the */ + + /* characteristic shall be zero." */ + + return bt_gatt_attr_read(conn, attr, buf, len, offset, NULL, 0); + } + + LOG_DBG_OBJ_ID("Next track read: ", track_id); + return bt_gatt_attr_read(conn, attr, buf, len, offset, track_id_le, + sizeof(track_id_le)); +} + +static ssize_t write_next_track_id( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + uint64_t id; + + ARG_UNUSED(conn); + ARG_UNUSED(attr); + ARG_UNUSED(flags); + + if (offset != 0) + { + LOG_DBG("Invalid offset"); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (len != BT_OTS_OBJ_ID_SIZE) + { + LOG_DBG("Invalid length"); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + id = sys_get_le48((uint8_t *)buf); + + if (IS_ENABLED(CONFIG_BT_MCS_LOG_LEVEL_DBG)) + { + char str[BT_OTS_OBJ_ID_STR_LEN]; + + (void)bt_ots_obj_id_to_str(id, str, sizeof(str)); + LOG_DBG("Next track write: offset: %d, len: %d, track ID: %s", offset, + len, str); + } + + media_proxy_sctrl_set_next_track_id(id); + + return BT_OTS_OBJ_ID_SIZE; +} + +static void next_track_id_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); + + /* Clear any pending notifications for any connections that unsubscribes */ + + if (value == 0U) + { + set_value_changed(set_next_track_obj_id_changed_cb, + BT_UUID_MCS_NEXT_TRACK_OBJ_ID); + } +} + +static ssize_t read_parent_group_id( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + uint64_t group_id = media_proxy_sctrl_get_parent_group_id(); + uint8_t group_id_le[BT_OTS_OBJ_ID_SIZE]; + + sys_put_le48(group_id, group_id_le); + + LOG_DBG_OBJ_ID("Parent group read: ", group_id); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, group_id_le, + sizeof(group_id_le)); +} + +static void parent_group_id_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); + + /* Clear any pending notifications for any connections that unsubscribes */ + + if (value == 0U) + { + set_value_changed(set_parent_group_obj_id_changed_cb, + BT_UUID_MCS_PARENT_GROUP_OBJ_ID); + } +} + +static ssize_t read_current_group_id( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + uint64_t group_id = media_proxy_sctrl_get_current_group_id(); + uint8_t group_id_le[BT_OTS_OBJ_ID_SIZE]; + + sys_put_le48(group_id, group_id_le); + + LOG_DBG_OBJ_ID("Current group read: ", group_id); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, group_id_le, + sizeof(group_id_le)); +} + +static ssize_t write_current_group_id( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + uint64_t id; + + ARG_UNUSED(conn); + ARG_UNUSED(attr); + ARG_UNUSED(flags); + + if (offset != 0) + { + LOG_DBG("Invalid offset"); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (len != BT_OTS_OBJ_ID_SIZE) + { + LOG_DBG("Invalid length"); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + id = sys_get_le48((uint8_t *)buf); + + if (IS_ENABLED(CONFIG_BT_MCS_LOG_LEVEL_DBG)) + { + char str[BT_OTS_OBJ_ID_STR_LEN]; + + (void)bt_ots_obj_id_to_str(id, str, sizeof(str)); + LOG_DBG("Current group ID write: offset: %d, len: %d, track ID: %s", + offset, len, str); + } + + media_proxy_sctrl_set_current_group_id(id); + + return BT_OTS_OBJ_ID_SIZE; +} + +static void current_group_id_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); + + /* Clear any pending notifications for any connections that unsubscribes */ + + if (value == 0U) + { + set_value_changed(set_current_group_obj_id_changed_cb, + BT_UUID_MCS_CURRENT_GROUP_OBJ_ID); + } +} +#endif /* CONFIG_BT_OTS */ + +static ssize_t read_playing_order( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + uint8_t order = media_proxy_sctrl_get_playing_order(); + + LOG_DBG("Playing order read: %d (0x%02x)", order, order); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &order, + sizeof(order)); +} + +static ssize_t write_playing_order( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + ARG_UNUSED(conn); + ARG_UNUSED(attr); + ARG_UNUSED(flags); + + LOG_DBG("Playing order write"); + + int8_t order; + + if (offset != 0) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (len != sizeof(order)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + memcpy(&order, buf, len); + + media_proxy_sctrl_set_playing_order(order); + + LOG_DBG("Playing order write: %d", order); + + return len; +} + +static void playing_order_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); + + /* Clear any pending notifications for any connections that unsubscribes */ + + if (value == 0U) + { + set_value_changed(set_playing_order_changed_cb, + BT_UUID_MCS_PLAYING_ORDER); + } +} + +static ssize_t read_playing_orders_supported( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + uint16_t orders = media_proxy_sctrl_get_playing_orders_supported(); + uint16_t orders_le = sys_cpu_to_le16(orders); + + LOG_DBG("Playing orders read: %d (0x%04x)", orders, orders); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &orders_le, + sizeof(orders_le)); +} + +static ssize_t read_media_state( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + uint8_t state = media_proxy_sctrl_get_media_state(); + + LOG_DBG("Media state read: %d", state); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &state, + sizeof(state)); +} + +static void media_state_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); + + /* Clear any pending notifications for any connections that unsubscribes */ + + if (value == 0U) + { + set_value_changed(set_media_state_changed_cb, BT_UUID_MCS_MEDIA_STATE); + } +} + +static ssize_t write_control_point( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t write_flags) +{ + struct mpl_cmd command; + + ARG_UNUSED(attr); + ARG_UNUSED(write_flags); + + if (offset != 0) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (len != sizeof(command.opcode) && + len != sizeof(command.opcode) + sizeof(command.param)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + memcpy(&command.opcode, buf, sizeof(command.opcode)); + LOG_DBG("Opcode: %d", command.opcode); + command.use_param = false; + + if (conn != NULL) + { + struct client_state *client; + struct mcs_flags *flags; + __maybe_unused int err; + + err = nxrmutex_ticklock(&mcs_inst.mutex, MUTEX_TIMEOUT); + DEBUGASSERT(err == 0); + + client = &mcs_inst.clients[bt_conn_index(conn)]; + flags = &client->flags; + + /* If we are already handling a control point operation, we cannot + * handle it. Similarly we do not send a notification, as we do not + * want to risk sending the notification for this 2nd request before + * we've sent the notification for the 1st request to avoid confusing + * the client. Unfortunately there is no flow control defined for + * control point operations. + */ + + if (flags->media_control_point_busy || + flags->media_control_point_result) + { + const bool media_control_point_busy = + flags->media_control_point_busy; + const bool media_control_point_result = + flags->media_control_point_result; + + err = nxrmutex_unlock(&mcs_inst.mutex); + DEBUGASSERT(err == 0); + + LOG_DBG("Rejecting CP write as one is already pending (%d %d)", + media_control_point_busy, + media_control_point_result); + + return BT_GATT_ERR(BT_ATT_ERR_PROCEDURE_IN_PROGRESS); + } + + if (!BT_MCS_VALID_OP(command.opcode)) + { + /* MCS does not specify what to return in case of an error - Only + * what to notify + */ + + client->cmd_ntf.requested_opcode = command.opcode; + client->cmd_ntf.result_code = BT_MCS_OPC_NTF_NOT_SUPPORTED; + + flags->media_control_point_result = true; + + err = nxrmutex_unlock(&mcs_inst.mutex); + DEBUGASSERT(err == 0); + LOG_DBG("Opcode 0x%02X is invalid", command.opcode); + + err = k_work_schedule(&mcs_inst.notify_work, 0); + DEBUGASSERT(err >= 0); + + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + + flags->media_control_point_busy = true; + + err = nxrmutex_unlock(&mcs_inst.mutex); + DEBUGASSERT(err == 0); + } + else + { + if (!BT_MCS_VALID_OP(command.opcode)) + { + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + } + + if (len == sizeof(command.opcode) + sizeof(command.param)) + { + command.param = sys_get_le32((char *)buf + sizeof(command.opcode)); + command.use_param = true; + LOG_DBG("Parameter: %d", command.param); + } + + media_proxy_sctrl_send_command(&command); + + return len; +} + +static void control_point_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); + + /* Clear any pending notifications for any connections that unsubscribes */ + + if (value == 0U) + { + set_value_changed(clear_media_control_ntf_cb, + BT_UUID_MCS_MEDIA_CONTROL_POINT); + } +} + +static ssize_t read_opcodes_supported( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + uint32_t opcodes = media_proxy_sctrl_get_commands_supported(); + uint32_t opcodes_le = sys_cpu_to_le32(opcodes); + + LOG_DBG("Opcodes_supported read: %d (0x%08x)", opcodes, opcodes); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &opcodes_le, + sizeof(opcodes_le)); +} + +static void opcodes_supported_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); + + /* Clear any pending notifications for any connections that unsubscribes */ + + if (value == 0U) + { + set_value_changed(set_media_control_opcodes_changed_cb, + BT_UUID_MCS_MEDIA_CONTROL_OPCODES); + } +} + +#ifdef CONFIG_BT_OTS +static ssize_t write_search_control_point( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t write_flags) +{ + struct mpl_search search = + { + 0 + }; + + ARG_UNUSED(attr); + ARG_UNUSED(write_flags); + + if (offset != 0) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (len > SEARCH_LEN_MAX || len < SEARCH_LEN_MIN) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + if (conn != NULL) + { + struct mcs_flags *flags; + __maybe_unused int err; + + err = nxrmutex_ticklock(&mcs_inst.mutex, MUTEX_TIMEOUT); + DEBUGASSERT(err == 0); + + flags = &mcs_inst.clients[bt_conn_index(conn)].flags; + + /* If we are already handling a search control point operation, we + * cannot handle it. Similarly we do not send a notification, as we do + * not want to risk sending the notification for this 2nd request + * before we've sent the notification for the 1st request to avoid + * confusing the client. Unfortunately there is no flow control defined + * for control point operations. + */ + + if (flags->search_control_point_busy || + flags->search_control_point_result) + { + const bool search_control_point_busy = + flags->search_control_point_busy; + const bool search_control_point_result = + flags->search_control_point_result; + + err = nxrmutex_unlock(&mcs_inst.mutex); + DEBUGASSERT(err == 0); + + LOG_DBG("Rejecting CP write as one is already pending (%d %d)", + search_control_point_busy, + search_control_point_result); + + return BT_GATT_ERR(BT_ATT_ERR_PROCEDURE_IN_PROGRESS); + } + + flags->search_control_point_busy = true; + + err = nxrmutex_unlock(&mcs_inst.mutex); + DEBUGASSERT(err == 0); + } + + memcpy(&search.search, (char *)buf, len); + search.len = len; + LOG_DBG("Search length: %d", len); + LOG_HEXDUMP_DBG(&search.search, search.len, "Search content"); + + media_proxy_sctrl_send_search(&search); + + return len; +} + +static void search_control_point_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); + + /* Clear any pending notifications for any connections that unsubscribes */ + + if (value == 0U) + { + set_value_changed(clear_search_control_ntf_cb, + BT_UUID_MCS_SEARCH_CONTROL_POINT); + } +} + +static ssize_t read_search_results_id( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + uint64_t search_id = media_proxy_sctrl_get_search_results_id(); + + LOG_DBG_OBJ_ID("Search results id read: ", search_id); + + /* TODO: The permanent solution here should be that the call to */ + + /* mpl should fill the UUID in a pointed-to value, and return a */ + + /* length or an error code, to indicate whether this ID has a */ + + /* value now. This should be done for all functions of this kind. */ + + /* For now, fix the issue here - send zero-length data if the */ + + /* ID is zero. */ + + /* *Spec requirement - IDs may not be valid, in which case the */ + + /* characteristic shall be zero length. */ + + if (search_id == 0U) + { + return bt_gatt_attr_read(conn, attr, buf, len, offset, NULL, 0); + } + else + { + uint8_t search_id_le[BT_OTS_OBJ_ID_SIZE]; + + sys_put_le48(search_id, search_id_le); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &search_id_le, + sizeof(search_id_le)); + } +} + +static void search_results_id_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); + + /* Clear any pending notifications for any connections that unsubscribes */ + + if (value == 0U) + { + set_value_changed(set_search_results_obj_id_changed_cb, + BT_UUID_MCS_SEARCH_RESULTS_OBJ_ID); + } +} +#endif /* CONFIG_BT_OTS */ + +static ssize_t read_content_ctrl_id( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + uint8_t id = media_proxy_sctrl_get_content_ctrl_id(); + + LOG_DBG("Content control ID read: %d", id); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &id, sizeof(id)); +} + +/* Defines for OTS-dependent characteristics - empty if no OTS */ +#ifdef CONFIG_BT_OTS +#define ICON_OBJ_ID_CHARACTERISTIC_IF_OTS \ + BT_AUDIO_CHRC(BT_UUID_MCS_ICON_OBJ_ID, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT, \ + read_icon_id, NULL, NULL), +#define SEGMENTS_TRACK_GROUP_ID_CHARACTERISTICS_IF_OTS \ + BT_AUDIO_CHRC(BT_UUID_MCS_TRACK_SEGMENTS_OBJ_ID, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT, \ + read_track_segments_id, NULL, NULL), \ + BT_AUDIO_CHRC(BT_UUID_MCS_CURRENT_TRACK_OBJ_ID, \ + BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP | \ + BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT, \ + read_current_track_id, write_current_track_id, NULL), \ + BT_AUDIO_CCC(current_track_id_cfg_changed), \ + BT_AUDIO_CHRC(BT_UUID_MCS_NEXT_TRACK_OBJ_ID, \ + BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP | \ + BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT, \ + read_next_track_id, write_next_track_id, NULL), \ + BT_AUDIO_CCC(next_track_id_cfg_changed), \ + BT_AUDIO_CHRC(BT_UUID_MCS_PARENT_GROUP_OBJ_ID, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, read_parent_group_id, NULL, NULL), \ + BT_AUDIO_CCC(parent_group_id_cfg_changed), \ + BT_AUDIO_CHRC(BT_UUID_MCS_CURRENT_GROUP_OBJ_ID, \ + BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP | \ + BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT, \ + read_current_group_id, write_current_group_id, NULL), \ + BT_AUDIO_CCC(current_group_id_cfg_changed), +#define SEARCH_CHARACTERISTICS_IF_OTS \ + BT_AUDIO_CHRC(BT_UUID_MCS_SEARCH_CONTROL_POINT, \ + BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_WRITE_ENCRYPT, NULL, write_search_control_point, NULL), \ + BT_AUDIO_CCC(search_control_point_cfg_changed), \ + BT_AUDIO_CHRC(BT_UUID_MCS_SEARCH_RESULTS_OBJ_ID, \ + BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_READ_ENCRYPT, \ + read_search_results_id, NULL, NULL), \ + BT_AUDIO_CCC(search_results_id_cfg_changed), +#define INCLUDE_SERVICE_IF_OTS BT_GATT_INCLUDE_SERVICE(NULL), /* To be overwritten */ + +#else +#define ICON_OBJ_ID_CHARACTERISTIC_IF_OTS +#define SEGMENTS_TRACK_GROUP_ID_CHARACTERISTICS_IF_OTS +#define SEARCH_CHARACTERISTICS_IF_OTS +#define INCLUDE_SERVICE_IF_OTS +#endif /* CONFIG_BT_OTS */ + +/* Media control service attributes */ +#define BT_MCS_SERVICE_DEFINITION \ + BT_GATT_PRIMARY_SERVICE(BT_UUID_GMCS), \ + INCLUDE_SERVICE_IF_OTS BT_AUDIO_CHRC( \ + BT_UUID_MCS_PLAYER_NAME, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, read_player_name, NULL, NULL), \ + BT_AUDIO_CCC(player_name_cfg_changed), \ + ICON_OBJ_ID_CHARACTERISTIC_IF_OTS BT_AUDIO_CHRC(BT_UUID_MCS_ICON_URL, BT_GATT_CHRC_READ, \ + BT_GATT_PERM_READ_ENCRYPT, \ + read_icon_url, NULL, NULL), \ + BT_AUDIO_CHRC(BT_UUID_MCS_TRACK_CHANGED, BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_NONE, NULL, \ + NULL, NULL), \ + BT_AUDIO_CCC(track_cfg_changed), \ + BT_AUDIO_CHRC(BT_UUID_MCS_TRACK_TITLE, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, read_track_title, NULL, NULL), \ + BT_AUDIO_CCC(track_title_cfg_changed), \ + BT_AUDIO_CHRC(BT_UUID_MCS_TRACK_DURATION, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, read_track_duration, NULL, NULL), \ + BT_AUDIO_CCC(track_duration_cfg_changed), \ + BT_AUDIO_CHRC(BT_UUID_MCS_TRACK_POSITION, \ + BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP | \ + BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT, \ + read_track_position, write_track_position, NULL), \ + BT_AUDIO_CCC(track_position_cfg_changed), \ + BT_AUDIO_CHRC(BT_UUID_MCS_PLAYBACK_SPEED, \ + BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP | \ + BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT, \ + read_playback_speed, write_playback_speed, NULL), \ + BT_AUDIO_CCC(playback_speed_cfg_changed), \ + BT_AUDIO_CHRC(BT_UUID_MCS_SEEKING_SPEED, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, read_seeking_speed, NULL, NULL), \ + BT_AUDIO_CCC(seeking_speed_cfg_changed), \ + SEGMENTS_TRACK_GROUP_ID_CHARACTERISTICS_IF_OTS BT_AUDIO_CHRC( \ + BT_UUID_MCS_PLAYING_ORDER, \ + BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP | \ + BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT, read_playing_order, \ + write_playing_order, NULL), \ + BT_AUDIO_CCC(playing_order_cfg_changed), \ + BT_AUDIO_CHRC(BT_UUID_MCS_PLAYING_ORDERS, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT, \ + read_playing_orders_supported, NULL, NULL), \ + BT_AUDIO_CHRC(BT_UUID_MCS_MEDIA_STATE, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, read_media_state, NULL, NULL), \ + BT_AUDIO_CCC(media_state_cfg_changed), \ + BT_AUDIO_CHRC(BT_UUID_MCS_MEDIA_CONTROL_POINT, \ + BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP | \ + BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_WRITE_ENCRYPT, NULL, write_control_point, NULL), \ + BT_AUDIO_CCC(control_point_cfg_changed), \ + BT_AUDIO_CHRC(BT_UUID_MCS_MEDIA_CONTROL_OPCODES, \ + BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_READ_ENCRYPT, \ + read_opcodes_supported, NULL, NULL), \ + BT_AUDIO_CCC(opcodes_supported_cfg_changed), \ + SEARCH_CHARACTERISTICS_IF_OTS \ + BT_AUDIO_CHRC(BT_UUID_CCID, BT_GATT_CHRC_READ, \ + BT_GATT_PERM_READ_ENCRYPT, \ + read_content_ctrl_id, NULL, NULL) + +static struct bt_gatt_attr svc_attrs[] = +{ + BT_MCS_SERVICE_DEFINITION +}; +#ifdef CONFIG_BT_OTS + +static struct bt_ots *ots; +#endif /* CONFIG_BT_OTS */ + +#ifdef CONFIG_BT_OTS +struct bt_ots *bt_mcs_get_ots(void) +{ + return ots; +} +#endif /* CONFIG_BT_OTS */ + +/* Callback functions from the media player, notifying attributes */ + +/* Placed here, after the service definition, because they reference it. */ + +static int notify(struct bt_conn *conn, + const struct bt_uuid *uuid, const void *data, uint16_t len) +{ + int max_ntf_size = bt_att_get_max_notify_size(conn, BT_ATT_CHAN_OPT_NONE); + int err; + + if (max_ntf_size < 0) + { + DEBUGASSERT(max_ntf_size != -EINVAL); + LOG_DBG("Failed to get max notification size for %p: %d", conn, + max_ntf_size); + + /* Only return error if it makes sense to retry, else just treat as + * completed (e.g. in case of disconnect) + */ + + return 0; + } + + if (max_ntf_size < len) + { + LOG_DBG("Truncating notification to %u (was %u) for %p", max_ntf_size, + len, conn); + len = max_ntf_size; + } + + err = bt_gatt_notify_uuid(conn, uuid, mcs.attrs, data, len); + if (err == -ENOMEM || err == -EAGAIN) + { + /* Only return error if it makes sense to retry, else just treat as + * completed (e.g. in case of security issues or unsubscribe it does + * not make sense to try) + */ + + return err; + } + + DEBUGASSERT(err != -EINVAL); + + return 0; +} + +static void notify_cb(struct bt_conn *conn, void *data) +{ + struct client_state *client; + struct mcs_flags *flags; + struct bt_conn_info info; + int err; + + ARG_UNUSED(data); + + err = bt_conn_get_info(conn, &info); + if (err != 0) + { + LOG_ERR("Failed to get conn info: %d", err); + return; + } + + if (info.state != BT_CONN_STATE_CONNECTED) + { + /* Not connected */ + + return; + } + + err = nxrmutex_trylock(&mcs_inst.mutex); + if (err != 0) + { + LOG_DBG("Failed to take mutex: %d", err); + err = k_work_schedule(&mcs_inst.notify_work, + USEC2TICK(info.le.interval_us)); + DEBUGASSERT(err >= 0); + return; + } + + client = &mcs_inst.clients[bt_conn_index(conn)]; + flags = &client->flags; + + if (flags->player_name_changed) + { + const char *name = media_proxy_sctrl_get_player_name(); + + LOG_DBG("Notifying player name: %s", name); + err = notify(conn, BT_UUID_MCS_PLAYER_NAME, name, strlen(name)); + if (err == 0) + { + flags->player_name_changed = false; + } + else + { + goto exit; + } + } + + if (flags->track_title_changed) + { + const char *title = media_proxy_sctrl_get_track_title(); + + LOG_DBG("Notifying track title: %s", title); + err = notify(conn, BT_UUID_MCS_TRACK_TITLE, title, strlen(title)); + if (err == 0) + { + flags->track_title_changed = false; + } + else + { + goto exit; + } + } + + if (flags->track_duration_changed) + { + int32_t duration = media_proxy_sctrl_get_track_duration(); + int32_t duration_le = sys_cpu_to_le32(duration); + + LOG_DBG("Notifying track duration: %d", duration); + err = notify(conn, BT_UUID_MCS_TRACK_DURATION, &duration_le, + sizeof(duration_le)); + if (err == 0) + { + flags->track_duration_changed = false; + } + else + { + goto exit; + } + } + + if (flags->track_position_changed) + { + int32_t position = media_proxy_sctrl_get_track_position(); + int32_t position_le = sys_cpu_to_le32(position); + + LOG_DBG("Notifying track position: %d", position); + err = notify(conn, BT_UUID_MCS_TRACK_POSITION, &position_le, + sizeof(position_le)); + if (err == 0) + { + flags->track_position_changed = false; + } + else + { + goto exit; + } + } + + if (flags->playback_speed_changed) + { + int8_t speed = media_proxy_sctrl_get_playback_speed(); + + LOG_DBG("Notifying playback speed: %d", speed); + err = notify(conn, BT_UUID_MCS_PLAYBACK_SPEED, &speed, sizeof(speed)); + if (err == 0) + { + flags->playback_speed_changed = false; + } + else + { + goto exit; + } + } + + if (flags->seeking_speed_changed) + { + int8_t speed = media_proxy_sctrl_get_seeking_speed(); + + LOG_DBG("Notifying seeking speed: %d", speed); + err = notify(conn, BT_UUID_MCS_SEEKING_SPEED, &speed, sizeof(speed)); + if (err == 0) + { + flags->seeking_speed_changed = false; + } + else + { + goto exit; + } + } + +#if defined(CONFIG_BT_OTS) + if (flags->current_track_obj_id_changed) + { + uint64_t track_id = media_proxy_sctrl_get_current_track_id(); + uint8_t track_id_le[BT_OTS_OBJ_ID_SIZE]; + + sys_put_le48(track_id, track_id_le); + + LOG_DBG_OBJ_ID("Notifying current track ID: ", track_id); + err = notify(conn, BT_UUID_MCS_CURRENT_TRACK_OBJ_ID, track_id_le, + sizeof(track_id_le)); + if (err == 0) + { + flags->current_track_obj_id_changed = false; + } + else + { + goto exit; + } + } + + if (flags->next_track_obj_id_changed) + { + uint64_t track_id = media_proxy_sctrl_get_next_track_id(); + + if (track_id == MPL_NO_TRACK_ID) + { + /* "If the media player has no next track, the length of the + * characteristic shall be zero." + */ + + LOG_DBG_OBJ_ID("Notifying EMPTY next track ID: ", track_id); + err = notify(conn, BT_UUID_MCS_NEXT_TRACK_OBJ_ID, NULL, 0); + } + else + { + uint8_t track_id_le[BT_OTS_OBJ_ID_SIZE]; + + sys_put_le48(track_id, track_id_le); + + LOG_DBG_OBJ_ID("Notifying next track ID: ", track_id); + err = notify(conn, BT_UUID_MCS_NEXT_TRACK_OBJ_ID, track_id_le, + sizeof(track_id_le)); + } + + if (err == 0) + { + flags->next_track_obj_id_changed = false; + } + else + { + goto exit; + } + } + + if (flags->parent_group_obj_id_changed) + { + uint64_t group_id = media_proxy_sctrl_get_parent_group_id(); + uint8_t group_id_le[BT_OTS_OBJ_ID_SIZE]; + + sys_put_le48(group_id, group_id_le); + + LOG_DBG_OBJ_ID("Notifying parent group ID: ", group_id); + err = notify(conn, BT_UUID_MCS_PARENT_GROUP_OBJ_ID, &group_id_le, + sizeof(group_id_le)); + if (err == 0) + { + flags->parent_group_obj_id_changed = false; + } + else + { + goto exit; + } + } + + if (flags->current_group_obj_id_changed) + { + uint64_t group_id = media_proxy_sctrl_get_current_group_id(); + uint8_t group_id_le[BT_OTS_OBJ_ID_SIZE]; + + sys_put_le48(group_id, group_id_le); + + LOG_DBG_OBJ_ID("Notifying current group ID: ", group_id); + err = notify(conn, BT_UUID_MCS_CURRENT_GROUP_OBJ_ID, &group_id_le, + sizeof(group_id_le)); + if (err == 0) + { + flags->current_group_obj_id_changed = false; + } + else + { + goto exit; + } + } +#endif /* CONFIG_BT_OTS */ + + if (flags->track_changed_changed) + { + LOG_DBG("Notifying track change"); + err = notify(conn, BT_UUID_MCS_TRACK_CHANGED, NULL, 0); + if (err == 0) + { + flags->track_changed_changed = false; + } + else + { + goto exit; + } + } + + if (flags->playing_order_changed) + { + uint8_t order = media_proxy_sctrl_get_playing_order(); + + LOG_DBG("Notifying playing order: %d", order); + err = notify(conn, BT_UUID_MCS_PLAYING_ORDER, &order, sizeof(order)); + if (err == 0) + { + flags->playing_order_changed = false; + } + else + { + goto exit; + } + } + + if (flags->media_state_changed) + { + uint8_t state = media_proxy_sctrl_get_media_state(); + + LOG_DBG("Notifying media state: %d", state); + err = notify(conn, BT_UUID_MCS_MEDIA_STATE, &state, sizeof(state)); + if (err == 0) + { + flags->media_state_changed = false; + } + else + { + goto exit; + } + } + + if (flags->media_control_opcodes_changed) + { + uint32_t opcodes = media_proxy_sctrl_get_commands_supported(); + uint32_t opcodes_le = sys_cpu_to_le32(opcodes); + + LOG_DBG("Notifying command opcodes supported: %d (0x%08x)", opcodes, + opcodes); + err = notify(conn, BT_UUID_MCS_MEDIA_CONTROL_OPCODES, &opcodes_le, + sizeof(opcodes_le)); + if (err == 0) + { + flags->media_control_opcodes_changed = false; + } + else + { + goto exit; + } + } + +#if defined(CONFIG_BT_OTS) + if (flags->search_results_obj_id_changed) + { + uint64_t search_id = media_proxy_sctrl_get_search_results_id(); + uint8_t search_id_le[BT_OTS_OBJ_ID_SIZE]; + + sys_put_le48(search_id, search_id_le); + + LOG_DBG_OBJ_ID("Notifying search results ID: ", search_id); + err = notify(conn, BT_UUID_MCS_SEARCH_RESULTS_OBJ_ID, &search_id_le, + sizeof(search_id_le)); + if (err == 0) + { + flags->search_results_obj_id_changed = false; + } + else + { + goto exit; + } + } + + if (flags->search_control_point_result) + { + uint8_t result_code = client->search_control_point_result; + + LOG_DBG("Notifying search control point - result: %d", result_code); + err = notify(conn, BT_UUID_MCS_SEARCH_CONTROL_POINT, &result_code, + sizeof(result_code)); + if (err == 0) + { + flags->search_control_point_result = false; + } + else + { + goto exit; + } + } +#endif /* CONFIG_BT_OTS */ + + if (flags->media_control_point_result) + { + LOG_DBG("Notifying control point command - opcode: %d, result: %d", + client->cmd_ntf.requested_opcode, client->cmd_ntf.result_code); + err = notify(conn, BT_UUID_MCS_MEDIA_CONTROL_POINT, &client->cmd_ntf, + sizeof(client->cmd_ntf)); + if (err == 0) + { + flags->media_control_point_result = false; + } + else + { + goto exit; + } + } + +exit: + if (err != 0) + { + LOG_DBG("Notify failed (%d), retrying next connection interval", err); + err = k_work_schedule(&mcs_inst.notify_work, + USEC2TICK(info.le.interval_us)); + DEBUGASSERT(err >= 0); + } + + err = nxrmutex_unlock(&mcs_inst.mutex); + DEBUGASSERT(err == 0); +} + +static void deferred_nfy_work_handler(struct k_work *work) +{ + ARG_UNUSED(work); + + bt_conn_foreach(BT_CONN_TYPE_LE, notify_cb, NULL); +} + +static void media_proxy_sctrl_player_name_cb(const char *name) +{ + ARG_UNUSED(name); + + set_value_changed(set_player_name_changed_cb, BT_UUID_MCS_PLAYER_NAME); +} + +void media_proxy_sctrl_icon_url_cb(const char *name) +{ + ARG_UNUSED(name); + + set_value_changed(set_icon_url_changed_cb, BT_UUID_MCS_ICON_URL); +} + +void media_proxy_sctrl_track_changed_cb(void) +{ + set_value_changed(set_track_changed_changed_cb, BT_UUID_MCS_TRACK_CHANGED); +} + +void media_proxy_sctrl_track_title_cb(const char *title) +{ + ARG_UNUSED(title); + + set_value_changed(set_track_title_changed_cb, BT_UUID_MCS_TRACK_TITLE); +} + +void media_proxy_sctrl_track_position_cb(int32_t position) +{ + ARG_UNUSED(position); + + set_value_changed(set_track_position_changed_cb, + BT_UUID_MCS_TRACK_POSITION); +} + +void media_proxy_sctrl_track_duration_cb(int32_t duration) +{ + ARG_UNUSED(duration); + + set_value_changed(set_track_duration_changed_cb, + BT_UUID_MCS_TRACK_DURATION); +} + +void media_proxy_sctrl_playback_speed_cb(int8_t speed) +{ + ARG_UNUSED(speed); + + set_value_changed(set_playback_speed_changed_cb, + BT_UUID_MCS_PLAYBACK_SPEED); +} + +void media_proxy_sctrl_seeking_speed_cb(int8_t speed) +{ + ARG_UNUSED(speed); + + set_value_changed(set_seeking_speed_changed_cb, BT_UUID_MCS_SEEKING_SPEED); +} + +#if defined(CONFIG_BT_OTS) +void media_proxy_sctrl_current_track_id_cb(uint64_t id) +{ + ARG_UNUSED(id); + + set_value_changed(set_current_track_obj_id_changed_cb, + BT_UUID_MCS_CURRENT_TRACK_OBJ_ID); +} + +void media_proxy_sctrl_next_track_id_cb(uint64_t id) +{ + ARG_UNUSED(id); + + set_value_changed(set_next_track_obj_id_changed_cb, + BT_UUID_MCS_NEXT_TRACK_OBJ_ID); +} + +void media_proxy_sctrl_parent_group_id_cb(uint64_t id) +{ + ARG_UNUSED(id); + + set_value_changed(set_parent_group_obj_id_changed_cb, + BT_UUID_MCS_PARENT_GROUP_OBJ_ID); +} + +void media_proxy_sctrl_current_group_id_cb(uint64_t id) +{ + ARG_UNUSED(id); + + set_value_changed(set_current_group_obj_id_changed_cb, + BT_UUID_MCS_CURRENT_GROUP_OBJ_ID); +} +#endif /* CONFIG_BT_OTS */ + +void media_proxy_sctrl_playing_order_cb(uint8_t order) +{ + ARG_UNUSED(order); + + set_value_changed(set_playing_order_changed_cb, BT_UUID_MCS_PLAYING_ORDER); +} + +void media_proxy_sctrl_media_state_cb(uint8_t state) +{ + ARG_UNUSED(state); + + set_value_changed(set_media_state_changed_cb, BT_UUID_MCS_MEDIA_STATE); +} + +static void defer_media_control_point_ntf(struct bt_conn *conn, void *data) +{ + const struct mpl_cmd_ntf *cmd_ntf = data; + struct client_state *client; + struct mcs_flags *flags; + struct bt_conn_info info; + int err; + + err = bt_conn_get_info(conn, &info); + if (err != 0) + { + LOG_ERR("Failed to get conn info: %d", err); + return; + } + + if (info.state != BT_CONN_STATE_CONNECTED) + { + /* Not connected */ + + return; + } + + err = nxrmutex_ticklock(&mcs_inst.mutex, MUTEX_TIMEOUT); + DEBUGASSERT(err == 0); + + client = &mcs_inst.clients[bt_conn_index(conn)]; + flags = &client->flags; + + if (flags->media_control_point_busy) + { + const struct bt_gatt_attr *cp_attr = + bt_gatt_find_by_uuid(mcs.attrs, 0, + BT_UUID_MCS_MEDIA_CONTROL_POINT); + + DEBUGASSERT(cp_attr != NULL); + + client->cmd_ntf = *cmd_ntf; + flags->media_control_point_busy = false; + + if (bt_gatt_is_subscribed(conn, cp_attr, BT_GATT_CCC_NOTIFY)) + { + flags->media_control_point_result = true; + err = k_work_schedule(&mcs_inst.notify_work, 0); + DEBUGASSERT(err >= 0); + } + } + + err = nxrmutex_unlock(&mcs_inst.mutex); + DEBUGASSERT(err == 0); +} + +void media_proxy_sctrl_command_cb(const struct mpl_cmd_ntf *cmd_ntf) +{ + /* FIXME: Control Point notification shall be sent to operation initiator + * only + */ + + bt_conn_foreach(BT_CONN_TYPE_LE, defer_media_control_point_ntf, + (void *)cmd_ntf); +} + +void media_proxy_sctrl_commands_supported_cb(uint32_t opcodes) +{ + ARG_UNUSED(opcodes); + + set_value_changed(set_media_control_opcodes_changed_cb, + BT_UUID_MCS_MEDIA_CONTROL_OPCODES); +} + +#if defined(CONFIG_BT_OTS) +static void defer_search_control_point_ntf(struct bt_conn *conn, void *data) +{ + struct client_state *client; + struct bt_conn_info info; + struct mcs_flags *flags; + int err; + + err = bt_conn_get_info(conn, &info); + if (err != 0) + { + LOG_ERR("Failed to get conn info: %d", err); + return; + } + + if (info.state != BT_CONN_STATE_CONNECTED) + { + /* Not connected */ + + return; + } + + err = nxrmutex_ticklock(&mcs_inst.mutex, MUTEX_TIMEOUT); + DEBUGASSERT(err == 0); + + client = &mcs_inst.clients[bt_conn_index(conn)]; + flags = &client->flags; + + if (flags->search_control_point_busy) + { + const struct bt_gatt_attr *cp_attr = + bt_gatt_find_by_uuid(mcs.attrs, 0, + BT_UUID_MCS_SEARCH_CONTROL_POINT); + + DEBUGASSERT(cp_attr != NULL); + + client->search_control_point_result = POINTER_TO_UINT(data); + flags->search_control_point_busy = false; + + if (bt_gatt_is_subscribed(conn, cp_attr, BT_GATT_CCC_NOTIFY)) + { + flags->search_control_point_result = true; + err = k_work_schedule(&mcs_inst.notify_work, 0); + DEBUGASSERT(err >= 0); + } + } + + err = nxrmutex_unlock(&mcs_inst.mutex); + DEBUGASSERT(err == 0); +} + +void media_proxy_sctrl_search_cb(uint8_t result_code) +{ + /* FIXME: Control Point notification shall be sent to operation initiator + * only + */ + + bt_conn_foreach(BT_CONN_TYPE_LE, defer_search_control_point_ntf, + UINT_TO_POINTER(result_code)); +} + +void media_proxy_sctrl_search_results_id_cb(uint64_t id) +{ + ARG_UNUSED(id); + + set_value_changed(set_search_results_obj_id_changed_cb, + BT_UUID_MCS_SEARCH_RESULTS_OBJ_ID); +} +#endif /* CONFIG_BT_OTS */ + +/* Register the service */ + +int bt_mcs_init(struct bt_ots_cb *ots_cbs) +{ + static bool initialized; + int err; + + if (initialized) + { + LOG_DBG("Already initialized"); + return -EALREADY; + } + + mcs = (struct bt_gatt_service)BT_GATT_SERVICE(svc_attrs); + +#ifdef CONFIG_BT_OTS + struct bt_ots_init_param ots_init; + + ots = bt_ots_free_instance_get(); + if (!ots) + { + LOG_ERR("Failed to retrieve OTS instance\n"); + return -ENOMEM; + } + + /* Configure OTS initialization. */ + + memset(&ots_init, 0, sizeof(ots_init)); + BT_OTS_OACP_SET_FEAT_READ(ots_init.features.oacp); + BT_OTS_OLCP_SET_FEAT_GO_TO(ots_init.features.olcp); + ots_init.cb = ots_cbs; + + /* Initialize OTS instance. */ + + err = bt_ots_init(ots, &ots_init); + if (err != 0) + { + LOG_ERR("Failed to init OTS (err:%d)\n", err); + return err; + } + + /* TODO: Maybe the user_data pointer can be in a different way */ + + for (int i = 0; i < mcs.attr_count; i++) + { + if (!bt_uuid_cmp(mcs.attrs[i].uuid, BT_UUID_GATT_INCLUDE)) + { + mcs.attrs[i].user_data = bt_ots_svc_decl_get(ots); + } + } +#endif /* CONFIG_BT_OTS */ + + err = bt_gatt_service_register(&mcs); + + if (err != 0) + { + LOG_ERR("Could not register the MCS service"); +#ifdef CONFIG_BT_OTS + /* TODO: How does one un-register the OTS? */ +#endif /* CONFIG_BT_OTS */ + return -ENOEXEC; + } + + /* Set up the callback structure */ + + cbs.player_name = media_proxy_sctrl_player_name_cb; + cbs.icon_url = media_proxy_sctrl_icon_url_cb; + cbs.track_changed = media_proxy_sctrl_track_changed_cb; + cbs.track_title = media_proxy_sctrl_track_title_cb; + cbs.track_duration = media_proxy_sctrl_track_duration_cb; + cbs.track_position = media_proxy_sctrl_track_position_cb; + cbs.playback_speed = media_proxy_sctrl_playback_speed_cb; + cbs.seeking_speed = media_proxy_sctrl_seeking_speed_cb; +#ifdef CONFIG_BT_OTS + cbs.current_track_id = media_proxy_sctrl_current_track_id_cb; + cbs.next_track_id = media_proxy_sctrl_next_track_id_cb; + cbs.parent_group_id = media_proxy_sctrl_parent_group_id_cb; + cbs.current_group_id = media_proxy_sctrl_current_group_id_cb; +#endif /* CONFIG_BT_OTS */ + cbs.playing_order = media_proxy_sctrl_playing_order_cb; + cbs.media_state = media_proxy_sctrl_media_state_cb; + cbs.command = media_proxy_sctrl_command_cb; + cbs.commands_supported = media_proxy_sctrl_commands_supported_cb; +#ifdef CONFIG_BT_OTS + cbs.search = media_proxy_sctrl_search_cb; + cbs.search_results_id = media_proxy_sctrl_search_results_id_cb; +#endif /* CONFIG_BT_OTS */ + + k_work_init_delayable(&mcs_inst.notify_work, deferred_nfy_work_handler); + + media_proxy_sctrl_register(&cbs); + + initialized = true; + return 0; +} + +static int mcs_init(void) +{ + __maybe_unused int err; + + err = nxrmutex_init(&mcs_inst.mutex); + DEBUGASSERT(err == 0); + + return 0; +} + +SYS_INIT(mcs_init, APPLICATION, 0); diff --git a/libs/zblue/audio/mcs_internal.h b/libs/zblue/audio/mcs_internal.h new file mode 100644 index 0000000000000..b2f3cbe89b295 --- /dev/null +++ b/libs/zblue/audio/mcs_internal.h @@ -0,0 +1,47 @@ +/**************************************************************************** + * libs/zblue/audio/mcs_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* @file + * @brief Media Control Service internal header file + * + * Copyright (c) 2020 - 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef ZEPHYR_SUBSYS_BLUETOOTH_HOST_AUDIO_MCS_INTERNAL_H_ +#define ZEPHYR_SUBSYS_BLUETOOTH_HOST_AUDIO_MCS_INTERNAL_H_ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* This differs from BT_OTS_VALID_OBJ_ID as MCS does not use the directory + * list object + */ +#define BT_MCS_VALID_OBJ_ID(id) (IN_RANGE((id), BT_OTS_OBJ_ID_MIN, BT_OTS_OBJ_ID_MAX)) + +#define BT_MCS_VALID_OP(opcode) \ +(IN_RANGE((opcode), BT_MCS_OPC_PLAY, BT_MCS_OPC_STOP) || (opcode == BT_MCS_OPC_MOVE_RELATIVE) || \ + IN_RANGE((opcode), BT_MCS_OPC_PREV_SEGMENT, BT_MCS_OPC_GOTO_SEGMENT) || \ + IN_RANGE((opcode), BT_MCS_OPC_PREV_TRACK, BT_MCS_OPC_GOTO_TRACK) || \ + IN_RANGE((opcode), BT_MCS_OPC_PREV_GROUP, BT_MCS_OPC_GOTO_GROUP)) + +int bt_mcs_init(struct bt_ots_cb *ots_cbs); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_SUBSYS_BLUETOOTH_HOST_AUDIO_MCS_INTERNAL_H_ */ diff --git a/libs/zblue/audio/media_proxy.c b/libs/zblue/audio/media_proxy.c new file mode 100644 index 0000000000000..0ff053840bbfc --- /dev/null +++ b/libs/zblue/audio/media_proxy.c @@ -0,0 +1,2759 @@ +/**************************************************************************** + * libs/zblue/audio/media_proxy.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Media proxy */ + +/* Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "media_proxy_internal.h" +#include "mcs_internal.h" + +LOG_MODULE_REGISTER(bt_media_proxy, CONFIG_MCTL_LOG_LEVEL); + +/* Media player */ + +struct media_player +{ + struct media_proxy_pl_calls *calls; + struct bt_conn *conn; /* TODO: Treat local and remote player differently */ + bool registered; +}; + +/* Synchronous controller - controller using the synchronous API */ + +struct scontroller +{ + struct media_proxy_sctrl_cbs *cbs; +}; + +/* Asynchronous controller - controller using the asynchronous API */ + +struct controller +{ + struct media_proxy_ctrl_cbs *cbs; +}; + +/* Media proxy */ + +struct mprx +{ +#if defined(CONFIG_MCTL_LOCAL_PLAYER_REMOTE_CONTROL) + /* Controller using the synchronous interface - e.g. remote controller via + * the media control service + */ + + struct scontroller sctrlr; +#endif /* CONFIG_MCTL_LOCAL_PLAYER_REMOTE_CONTROL */ + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + /* Controller using the callback interface - i.e. upper layer */ + + struct controller ctrlr; +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_CONTROL) + /* The local media player */ + + struct media_player local_player; +#endif /* CONFIG_MCTL_LOCAL_PLAYER_CONTROL */ + + /* Control of remote players is currently only supported over Bluetooth, + * via MCC + */ +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL) + /* Remote media player - to have a player instance for the user */ + + struct media_player remote_player; + + /* MCC callbacks */ + + struct bt_mcc_cb mcc_cbs; +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL */ +}; + +static struct mprx mprx = +{ + 0 +}; + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_REMOTE_CONTROL) +/* Synchronous controller calls + * ********************************************** + */ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/* The synchronous controller is the media control service, representing a + * remote controller. It only gets access to the local player, not remote + * players. I.e., the calls from the synchronous controller are always routed + * to the local_player. + */ + +int media_proxy_sctrl_register(struct media_proxy_sctrl_cbs *sctrl_cbs) +{ + mprx.sctrlr.cbs = sctrl_cbs; + return 0; +}; + +const char *media_proxy_sctrl_get_player_name(void) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->get_player_name == NULL) + { + return ""; + } + + return mprx.local_player.calls->get_player_name(); +} + +#ifdef CONFIG_BT_MPL_OBJECTS +uint64_t media_proxy_sctrl_get_icon_id(void) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->get_icon_id == NULL) + { + return 0U; + } + + return mprx.local_player.calls->get_icon_id(); +} +#endif /* CONFIG_BT_MPL_OBJECTS */ + +const char *media_proxy_sctrl_get_icon_url(void) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->get_icon_url == NULL) + { + return ""; + } + + return mprx.local_player.calls->get_icon_url(); +} + +const char *media_proxy_sctrl_get_track_title(void) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->get_track_title == NULL) + { + return ""; + } + + return mprx.local_player.calls->get_track_title(); +} + +int32_t media_proxy_sctrl_get_track_duration(void) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->get_track_duration == NULL) + { + return 0U; + } + + return mprx.local_player.calls->get_track_duration(); +} + +int32_t media_proxy_sctrl_get_track_position(void) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->get_track_position == NULL) + { + return 0U; + } + + return mprx.local_player.calls->get_track_position(); +} + +void media_proxy_sctrl_set_track_position(int32_t position) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->set_track_position == NULL) + { + return; + } + + mprx.local_player.calls->set_track_position(position); +} + +int8_t media_proxy_sctrl_get_playback_speed(void) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->get_playback_speed == NULL) + { + return 0; + } + + return mprx.local_player.calls->get_playback_speed(); +} + +void media_proxy_sctrl_set_playback_speed(int8_t speed) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->set_playback_speed == NULL) + { + return; + } + + mprx.local_player.calls->set_playback_speed(speed); +} + +int8_t media_proxy_sctrl_get_seeking_speed(void) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->get_seeking_speed == NULL) + { + return 0; + } + + return mprx.local_player.calls->get_seeking_speed(); +} + +#ifdef CONFIG_BT_MPL_OBJECTS +uint64_t media_proxy_sctrl_get_track_segments_id(void) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->get_track_segments_id == NULL) + { + return 0U; + } + + return mprx.local_player.calls->get_track_segments_id(); +} + +uint64_t media_proxy_sctrl_get_current_track_id(void) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->get_current_track_id == NULL) + { + return 0U; + } + + return mprx.local_player.calls->get_current_track_id(); +} + +void media_proxy_sctrl_set_current_track_id(uint64_t id) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->set_current_track_id == NULL) + { + return; + } + + mprx.local_player.calls->set_current_track_id(id); +} + +uint64_t media_proxy_sctrl_get_next_track_id(void) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->get_next_track_id == NULL) + { + return 0U; + } + + return mprx.local_player.calls->get_next_track_id(); +} + +void media_proxy_sctrl_set_next_track_id(uint64_t id) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->set_next_track_id == NULL) + { + return; + } + + mprx.local_player.calls->set_next_track_id(id); +} + +uint64_t media_proxy_sctrl_get_parent_group_id(void) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->get_parent_group_id == NULL) + { + return 0U; + } + + return mprx.local_player.calls->get_parent_group_id(); +} + +uint64_t media_proxy_sctrl_get_current_group_id(void) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->get_current_group_id == NULL) + { + return 0U; + } + + return mprx.local_player.calls->get_current_group_id(); +} + +void media_proxy_sctrl_set_current_group_id(uint64_t id) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->set_current_group_id == NULL) + { + return; + } + + mprx.local_player.calls->set_current_group_id(id); +} +#endif /* CONFIG_BT_MPL_OBJECTS */ + +uint8_t media_proxy_sctrl_get_playing_order(void) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->get_playing_order == NULL) + { + return MEDIA_PROXY_PLAYING_ORDER_SINGLE_ONCE; + } + + return mprx.local_player.calls->get_playing_order(); +} + +void media_proxy_sctrl_set_playing_order(uint8_t order) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->set_playing_order == NULL) + { + return; + } + + mprx.local_player.calls->set_playing_order(order); +} + +uint16_t media_proxy_sctrl_get_playing_orders_supported(void) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->get_playing_orders_supported == NULL) + { + return 0U; + } + + return mprx.local_player.calls->get_playing_orders_supported(); +} + +uint8_t media_proxy_sctrl_get_media_state(void) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->get_media_state == NULL) + { + return 0U; + } + + return mprx.local_player.calls->get_media_state(); +} + +void media_proxy_sctrl_send_command(const struct mpl_cmd *cmd) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->send_command == NULL) + { + struct mpl_cmd_ntf ntf = + { + .requested_opcode = cmd->opcode, + .result_code = + MEDIA_PROXY_CMD_NOT_SUPPORTED + }; + + media_proxy_pl_command_cb(&ntf); + return; + } + + mprx.local_player.calls->send_command(cmd); +} + +uint32_t media_proxy_sctrl_get_commands_supported(void) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->get_commands_supported == NULL) + { + return 0U; + } + + return mprx.local_player.calls->get_commands_supported(); +} + +#ifdef CONFIG_BT_MPL_OBJECTS +void media_proxy_sctrl_send_search(const struct mpl_search *search) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->send_search == NULL) + { + media_proxy_pl_search_cb(MEDIA_PROXY_SEARCH_FAILURE); + return; + } + + mprx.local_player.calls->send_search(search); +} + +uint64_t media_proxy_sctrl_get_search_results_id(void) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->get_search_results_id == NULL) + { + return 0U; + } + + return mprx.local_player.calls->get_search_results_id(); +} + +void media_proxy_sctrl_search_results_id_cb(uint64_t id); +#endif /* CONFIG_BT_MPL_OBJECTS */ + +uint8_t media_proxy_sctrl_get_content_ctrl_id(void) +{ + if (mprx.local_player.calls == NULL || + mprx.local_player.calls->get_content_ctrl_id == NULL) + { + return 0U; + } + + return mprx.local_player.calls->get_content_ctrl_id(); +} +#endif /* CONFIG_MCTL_LOCAL_PLAYER_REMOTE_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL) +/* Media control client callbacks + * ******************************************** + */ + +static void mcc_discover_mcs_cb(struct bt_conn *conn, int err) +{ + ARG_UNUSED(conn); + + if (err != 0) + { + LOG_ERR("Discovery failed (%d)", err); + } + + LOG_DBG("Discovered player"); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->discover_player) + { + mprx.ctrlr.cbs->discover_player(&mprx.remote_player, err); + } + else + { + LOG_DBG("No callback"); + } +} + +static void mcc_read_player_name_cb(struct bt_conn *conn, + int err, const char *name) +{ + ARG_UNUSED(conn); + + /* Debug statements for at least a couple of the callbacks, to show flow */ + + LOG_DBG("MCC player name callback"); + + if (err != 0) + { + LOG_ERR("Player name failed"); + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->player_name_recv) + { + mprx.ctrlr.cbs->player_name_recv(&mprx.remote_player, err, name); + } + else + { + LOG_DBG("No callback"); + } +} + +#ifdef CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS +static void mcc_read_icon_obj_id_cb(struct bt_conn *conn, + int err, uint64_t id) +{ + ARG_UNUSED(conn); + + LOG_DBG("Icon Object ID callback"); + + if (err != 0) + { + LOG_ERR("Icon Object ID read failed (%d)", err); + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->icon_id_recv) + { + mprx.ctrlr.cbs->icon_id_recv(&mprx.remote_player, err, id); + } + else + { + LOG_DBG("No callback"); + } +} +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS */ + +#if defined(CONFIG_BT_MCC_READ_MEDIA_PLAYER_ICON_URL) +static void mcc_read_icon_url_cb(struct bt_conn *conn, + int err, const char *url) +{ + ARG_UNUSED(conn); + + if (err != 0) + { + LOG_ERR("Icon URL read failed (%d)", err); + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->icon_url_recv) + { + mprx.ctrlr.cbs->icon_url_recv(&mprx.remote_player, err, url); + } + else + { + LOG_DBG("No callback"); + } +} +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_PLAYER_ICON_URL) */ + +static void mcc_track_changed_ntf_cb(struct bt_conn *conn, int err) +{ + ARG_UNUSED(conn); + + if (err != 0) + { + LOG_ERR("Track change notification failed (%d)", err); + return; + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->track_changed_recv) + { + mprx.ctrlr.cbs->track_changed_recv(&mprx.remote_player, err); + } + else + { + LOG_DBG("No callback"); + } +} + +#if defined(CONFIG_BT_MCC_READ_TRACK_TITLE) +static void mcc_read_track_title_cb(struct bt_conn *conn, + int err, const char *title) +{ + ARG_UNUSED(conn); + + if (err != 0) + { + LOG_ERR("Track title read failed (%d)", err); + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->track_title_recv) + { + mprx.ctrlr.cbs->track_title_recv(&mprx.remote_player, err, title); + } + else + { + LOG_DBG("No callback"); + } +} +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_TITLE) */ + +#if defined(CONFIG_BT_MCC_READ_TRACK_DURATION) +static void mcc_read_track_duration_cb(struct bt_conn *conn, + int err, int32_t dur) +{ + ARG_UNUSED(conn); + + if (err != 0) + { + LOG_ERR("Track duration read failed (%d)", err); + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->track_duration_recv) + { + mprx.ctrlr.cbs->track_duration_recv(&mprx.remote_player, err, dur); + } + else + { + LOG_DBG("No callback"); + } +} +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_DURATION) */ + +#if defined(CONFIG_BT_MCC_READ_TRACK_POSITION) +static void mcc_read_track_position_cb(struct bt_conn *conn, + int err, int32_t pos) +{ + ARG_UNUSED(conn); + + if (err != 0) + { + LOG_ERR("Track position read failed (%d)", err); + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->track_position_recv) + { + mprx.ctrlr.cbs->track_position_recv(&mprx.remote_player, err, pos); + } + else + { + LOG_DBG("No callback"); + } +} +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_POSITION) */ + +#if defined(CONFIG_BT_MCC_SET_TRACK_POSITION) +static void mcc_set_track_position_cb(struct bt_conn *conn, + int err, int32_t pos) +{ + ARG_UNUSED(conn); + + if (err != 0) + { + LOG_ERR("Track Position set failed (%d)", err); + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->track_position_write) + { + mprx.ctrlr.cbs->track_position_write(&mprx.remote_player, err, pos); + } + else + { + LOG_DBG("No callback"); + } +} +#endif /* defined(CONFIG_BT_MCC_SET_TRACK_POSITION) */ + +#if defined(CONFIG_BT_MCC_READ_PLAYBACK_SPEED) +static void mcc_read_playback_speed_cb(struct bt_conn *conn, + int err, int8_t speed) +{ + ARG_UNUSED(conn); + + if (err != 0) + { + LOG_ERR("Playback speed read failed (%d)", err); + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->playback_speed_recv) + { + mprx.ctrlr.cbs->playback_speed_recv(&mprx.remote_player, err, speed); + } + else + { + LOG_DBG("No callback"); + } +} +#endif /* defined (CONFIG_BT_MCC_READ_PLAYBACK_SPEED) */ + +#if defined(CONFIG_BT_MCC_SET_PLAYBACK_SPEED) +static void mcc_set_playback_speed_cb(struct bt_conn *conn, + int err, int8_t speed) +{ + ARG_UNUSED(conn); + + if (err != 0) + { + LOG_ERR("Playback speed set failed (%d)", err); + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->playback_speed_write) + { + mprx.ctrlr.cbs->playback_speed_write(&mprx.remote_player, err, speed); + } + else + { + LOG_DBG("No callback"); + } +} +#endif /* defined (CONFIG_BT_MCC_SET_PLAYBACK_SPEED) */ + +#if defined(CONFIG_BT_MCC_READ_SEEKING_SPEED) +static void mcc_read_seeking_speed_cb(struct bt_conn *conn, + int err, int8_t speed) +{ + ARG_UNUSED(conn); + + if (err != 0) + { + LOG_ERR("Seeking speed read failed (%d)", err); + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->seeking_speed_recv) + { + mprx.ctrlr.cbs->seeking_speed_recv(&mprx.remote_player, err, speed); + } + else + { + LOG_DBG("No callback"); + } +} +#endif /* defined (CONFIG_BT_MCC_READ_SEEKING_SPEED) */ + +#ifdef CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS +static void mcc_read_segments_obj_id_cb(struct bt_conn *conn, + int err, uint64_t id) +{ + ARG_UNUSED(conn); + + if (err != 0) + { + LOG_ERR("Track Segments Object ID read failed (%d)", err); + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->track_segments_id_recv) + { + mprx.ctrlr.cbs->track_segments_id_recv(&mprx.remote_player, err, id); + } + else + { + LOG_DBG("No callback"); + } +} + +static void mcc_read_current_track_obj_id_cb(struct bt_conn *conn, + int err, uint64_t id) +{ + ARG_UNUSED(conn); + + if (err != 0) + { + LOG_ERR("Current Track Object ID read failed (%d)", err); + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->current_track_id_recv) + { + mprx.ctrlr.cbs->current_track_id_recv(&mprx.remote_player, err, id); + } + else + { + LOG_DBG("No callback"); + } +} + +/* TODO: current track set callback - must be added to MCC first */ + +static void mcc_read_next_track_obj_id_cb(struct bt_conn *conn, + int err, uint64_t id) +{ + ARG_UNUSED(conn); + + if (err != 0) + { + LOG_ERR("Next Track Object ID read failed (%d)", err); + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->next_track_id_recv) + { + mprx.ctrlr.cbs->next_track_id_recv(&mprx.remote_player, err, id); + } + else + { + LOG_DBG("No callback"); + } +} + +/* TODO: next track set callback - must be added to MCC first */ + +static void mcc_read_parent_group_obj_id_cb(struct bt_conn *conn, + int err, uint64_t id) +{ + ARG_UNUSED(conn); + + if (err != 0) + { + LOG_ERR("Parent Group Object ID read failed (%d)", err); + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->parent_group_id_recv) + { + mprx.ctrlr.cbs->parent_group_id_recv(&mprx.remote_player, err, id); + } + else + { + LOG_DBG("No callback"); + } +} + +static void mcc_read_current_group_obj_id_cb(struct bt_conn *conn, + int err, uint64_t id) +{ + ARG_UNUSED(conn); + + if (err != 0) + { + LOG_ERR("Current Group Object ID read failed (%d)", err); + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->current_group_id_recv) + { + mprx.ctrlr.cbs->current_group_id_recv(&mprx.remote_player, err, id); + } + else + { + LOG_DBG("No callback"); + } +} + +/* TODO: current group set callback - must be added to MCC first */ + +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS */ + +#if defined(CONFIG_BT_MCC_READ_PLAYING_ORDER) +static void mcc_read_playing_order_cb(struct bt_conn *conn, + int err, uint8_t order) +{ + ARG_UNUSED(conn); + + if (err != 0) + { + LOG_ERR("Playing order read failed (%d)", err); + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->playing_order_recv) + { + mprx.ctrlr.cbs->playing_order_recv(&mprx.remote_player, err, order); + } + else + { + LOG_DBG("No callback"); + } +} +#endif /* defined(CONFIG_BT_MCC_READ_PLAYING_ORDER) */ + +#if defined(CONFIG_BT_MCC_SET_PLAYING_ORDER) +static void mcc_set_playing_order_cb(struct bt_conn *conn, + int err, uint8_t order) +{ + ARG_UNUSED(conn); + + if (err != 0) + { + LOG_ERR("Playing order set failed (%d)", err); + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->playing_order_write) + { + mprx.ctrlr.cbs->playing_order_write(&mprx.remote_player, err, order); + } + else + { + LOG_DBG("No callback"); + } +} +#endif /* defined(CONFIG_BT_MCC_SET_PLAYING_ORDER) */ + +#if defined(CONFIG_BT_MCC_READ_PLAYING_ORDER_SUPPORTED) +static void mcc_read_playing_orders_supported_cb(struct bt_conn *conn, + int err, uint16_t orders) +{ + ARG_UNUSED(conn); + + if (err != 0) + { + LOG_ERR("Playing orders supported read failed (%d)", err); + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->playing_orders_supported_recv) + { + mprx.ctrlr.cbs->playing_orders_supported_recv(&mprx.remote_player, err, + orders); + } + else + { + LOG_DBG("No callback"); + } +} +#endif /* defined(CONFIG_BT_MCC_READ_PLAYING_ORDER_SUPPORTED) */ + +#if defined(CONFIG_BT_MCC_READ_MEDIA_STATE) +static void mcc_read_media_state_cb(struct bt_conn *conn, + int err, uint8_t state) +{ + ARG_UNUSED(conn); + + if (err != 0) + { + LOG_ERR("Media State read failed (%d)", err); + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->media_state_recv) + { + mprx.ctrlr.cbs->media_state_recv(&mprx.remote_player, err, state); + } + else + { + LOG_DBG("No callback"); + } +} +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_STATE) */ + +#if defined(CONFIG_BT_MCC_SET_MEDIA_CONTROL_POINT) +static void mcc_send_cmd_cb(struct bt_conn *conn, + int err, const struct mpl_cmd *cmd) +{ + ARG_UNUSED(conn); + + if (err != 0) + { + LOG_ERR("Command send failed (%d) - opcode: %d, param: %d", err, + cmd->opcode, cmd->param); + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->command_send) + { + mprx.ctrlr.cbs->command_send(&mprx.remote_player, err, cmd); + } + else + { + LOG_DBG("No callback"); + } +} +#endif /* defined(CONFIG_BT_MCC_SET_MEDIA_CONTROL_POINT) */ + +static void mcc_cmd_ntf_cb(struct bt_conn *conn, + int err, const struct mpl_cmd_ntf *ntf) +{ + ARG_UNUSED(conn); + + if (err != 0) + { + LOG_ERR("Command notification error (%d) - " + "command opcode: %d, result: %d", + err, + ntf->requested_opcode, ntf->result_code); + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->command_recv) + { + mprx.ctrlr.cbs->command_recv(&mprx.remote_player, err, ntf); + } + else + { + LOG_DBG("No callback"); + } +} + +#if defined(CONFIG_BT_MCC_READ_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED) +static void mcc_read_opcodes_supported_cb(struct bt_conn *conn, + int err, uint32_t opcodes) +{ + ARG_UNUSED(conn); + + if (err != 0) + { + LOG_ERR("Opcodes supported read failed (%d)", err); + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->commands_supported_recv) + { + mprx.ctrlr.cbs->commands_supported_recv(&mprx.remote_player, err, + opcodes); + } + else + { + LOG_DBG("No callback"); + } +} +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED) */ + +#ifdef CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS +static void mcc_send_search_cb(struct bt_conn *conn, + int err, const struct mpl_search *search) +{ + ARG_UNUSED(conn); + + if (err != 0) + { + LOG_ERR("Search send failed (%d)", err); + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->search_send) + { + mprx.ctrlr.cbs->search_send(&mprx.remote_player, err, search); + } + else + { + LOG_DBG("No callback"); + } +} + +static void mcc_search_ntf_cb(struct bt_conn *conn, + int err, uint8_t result_code) +{ + ARG_UNUSED(conn); + + if (err != 0) + { + LOG_ERR("Search notification error (%d), result code: %d", err, + result_code); + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->search_recv) + { + mprx.ctrlr.cbs->search_recv(&mprx.remote_player, err, result_code); + } + else + { + LOG_DBG("No callback"); + } +} + +static void mcc_read_search_results_obj_id_cb(struct bt_conn *conn, + int err, uint64_t id) +{ + ARG_UNUSED(conn); + + if (err != 0) + { + LOG_ERR("Search Results Object ID read failed (%d)", err); + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->search_results_id_recv) + { + mprx.ctrlr.cbs->search_results_id_recv(&mprx.remote_player, err, id); + } + else + { + LOG_DBG("No callback"); + } +} +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS */ + +#if defined(CONFIG_BT_MCC_READ_CONTENT_CONTROL_ID) +static void mcc_read_content_control_id_cb(struct bt_conn *conn, + int err, uint8_t ccid) +{ + ARG_UNUSED(conn); + + if (err != 0) + { + LOG_ERR("Content Control ID read failed (%d)", err); + } + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->content_ctrl_id_recv) + { + mprx.ctrlr.cbs->content_ctrl_id_recv(&mprx.remote_player, err, ccid); + } + else + { + LOG_DBG("No callback"); + } +} +#endif /* defined(CONFIG_BT_MCC_READ_CONTENT_CONTROL_ID) */ + +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL */ + +/* Asynchronous controller calls + * ********************************************* + */ + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) +int media_proxy_ctrl_register(struct media_proxy_ctrl_cbs *ctrl_cbs) +{ + CHECKIF(ctrl_cbs == NULL) + { + LOG_DBG("NULL callback pointer"); + return -EINVAL; + } + + mprx.ctrlr.cbs = ctrl_cbs; + + if (mprx.local_player.registered) + { + if (mprx.ctrlr.cbs->local_player_instance) + { + mprx.ctrlr.cbs->local_player_instance(&mprx.local_player, 0); + } + } + + /* TODO: Return error code if too many controllers registered */ + + return 0; +}; +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#ifdef CONFIG_MCTL_REMOTE_PLAYER_CONTROL +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + ARG_UNUSED(reason); + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + if (mprx.remote_player.conn == conn) + { + bt_conn_drop(&mprx.remote_player.conn); + } +} + +BT_CONN_CB_DEFINE(conn_callbacks) = +{ + .disconnected = disconnected, +}; + +int media_proxy_ctrl_discover_player(struct bt_conn *conn) +{ + struct bt_conn *ref; + int err; + + CHECKIF(!conn) + { + LOG_DBG("NUll conn pointer"); + return -EINVAL; + } + + /* Initialize MCC */ + + mprx.mcc_cbs.discover_mcs = mcc_discover_mcs_cb; + mprx.mcc_cbs.read_player_name = mcc_read_player_name_cb; +#ifdef CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS + mprx.mcc_cbs.read_icon_obj_id = mcc_read_icon_obj_id_cb; +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS */ +#if defined(CONFIG_BT_MCC_READ_MEDIA_PLAYER_ICON_URL) + mprx.mcc_cbs.read_icon_url = mcc_read_icon_url_cb; +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_PLAYER_ICON_URL) */ + mprx.mcc_cbs.track_changed_ntf = mcc_track_changed_ntf_cb; +#if defined(CONFIG_BT_MCC_READ_TRACK_TITLE) + mprx.mcc_cbs.read_track_title = mcc_read_track_title_cb; +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_TITLE) */ +#if defined(CONFIG_BT_MCC_READ_TRACK_DURATION) + mprx.mcc_cbs.read_track_duration = mcc_read_track_duration_cb; +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_DURATION) */ +#if defined(CONFIG_BT_MCC_READ_TRACK_POSITION) + mprx.mcc_cbs.read_track_position = mcc_read_track_position_cb; +#endif /* defined(CONFIG_BT_MCC_READ_TRACK_POSITION) */ +#if defined(CONFIG_BT_MCC_SET_TRACK_POSITION) + mprx.mcc_cbs.set_track_position = mcc_set_track_position_cb; +#endif /* defined(CONFIG_BT_MCC_SET_TRACK_POSITION) */ +#if defined(CONFIG_BT_MCC_READ_PLAYBACK_SPEED) + mprx.mcc_cbs.read_playback_speed = mcc_read_playback_speed_cb; +#endif /* defined (CONFIG_BT_MCC_READ_PLAYBACK_SPEED) */ +#if defined(CONFIG_BT_MCC_SET_PLAYBACK_SPEED) + mprx.mcc_cbs.set_playback_speed = mcc_set_playback_speed_cb; +#endif /* defined (CONFIG_BT_MCC_SET_PLAYBACK_SPEED) */ +#if defined(CONFIG_BT_MCC_READ_SEEKING_SPEED) + mprx.mcc_cbs.read_seeking_speed = mcc_read_seeking_speed_cb; +#endif /* defined (CONFIG_BT_MCC_READ_SEEKING_SPEED) */ +#ifdef CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS + mprx.mcc_cbs.read_segments_obj_id = mcc_read_segments_obj_id_cb; + mprx.mcc_cbs.read_current_track_obj_id = mcc_read_current_track_obj_id_cb; + mprx.mcc_cbs.read_next_track_obj_id = mcc_read_next_track_obj_id_cb; + mprx.mcc_cbs.read_parent_group_obj_id = mcc_read_parent_group_obj_id_cb; + mprx.mcc_cbs.read_current_group_obj_id = mcc_read_current_group_obj_id_cb; +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS */ +#if defined(CONFIG_BT_MCC_READ_PLAYING_ORDER) + mprx.mcc_cbs.read_playing_order = mcc_read_playing_order_cb; +#endif /* defined(CONFIG_BT_MCC_READ_PLAYING_ORDER) */ +#if defined(CONFIG_BT_MCC_SET_PLAYING_ORDER) + mprx.mcc_cbs.set_playing_order = mcc_set_playing_order_cb; +#endif /* defined(CONFIG_BT_MCC_SET_PLAYING_ORDER) */ +#if defined(CONFIG_BT_MCC_READ_PLAYING_ORDER_SUPPORTED) + mprx.mcc_cbs.read_playing_orders_supported = + mcc_read_playing_orders_supported_cb; +#endif /* defined(CONFIG_BT_MCC_READ_PLAYING_ORDER_SUPPORTED) */ +#if defined(CONFIG_BT_MCC_READ_MEDIA_STATE) + mprx.mcc_cbs.read_media_state = mcc_read_media_state_cb; +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_STATE) */ +#if defined(CONFIG_BT_MCC_SET_MEDIA_CONTROL_POINT) + mprx.mcc_cbs.send_cmd = mcc_send_cmd_cb; +#endif /* defined(CONFIG_BT_MCC_SET_MEDIA_CONTROL_POINT) */ + mprx.mcc_cbs.cmd_ntf = mcc_cmd_ntf_cb; +#if defined(CONFIG_BT_MCC_READ_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED) + mprx.mcc_cbs.read_opcodes_supported = mcc_read_opcodes_supported_cb; +#endif /* defined(CONFIG_BT_MCC_READ_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED) */ +#ifdef CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS + mprx.mcc_cbs.send_search = mcc_send_search_cb; + mprx.mcc_cbs.search_ntf = mcc_search_ntf_cb; + mprx.mcc_cbs.read_search_results_obj_id = + mcc_read_search_results_obj_id_cb; +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS */ +#if defined(CONFIG_BT_MCC_READ_CONTENT_CONTROL_ID) + mprx.mcc_cbs.read_content_control_id = mcc_read_content_control_id_cb; +#endif /* defined(CONFIG_BT_MCC_READ_CONTENT_CONTROL_ID) */ + + err = bt_mcc_init(&mprx.mcc_cbs); + if (err != 0) + { + LOG_ERR("Failed to initialize MCC"); + return err; + } + + ref = bt_conn_ref(conn); + if (ref == NULL) + { + return -ENOTCONN; + } + + /* Start discovery of remote MCS, subscribe to notifications */ + + err = bt_mcc_discover_mcs(conn, 1); + if (err != 0) + { + LOG_ERR("Discovery failed"); + bt_conn_unref(ref); + return err; + } + + if (mprx.remote_player.conn != NULL) + { + bt_conn_unref(mprx.remote_player.conn); + } + + mprx.remote_player.conn = ref; + mprx.remote_player.registered = + true; /* TODO: Do MCC init and "registration" at startup */ + + return 0; +} +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL */ + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) || defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL) +int media_proxy_ctrl_get_player_name(struct media_player *player) +{ + CHECKIF(player == NULL) + { + LOG_DBG("player is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + if (mprx.local_player.registered && player == &mprx.local_player) + { + LOG_DBG("Local player"); + if (mprx.local_player.calls->get_player_name) + { + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->player_name_recv) + { + const char *name = mprx.local_player.calls->get_player_name(); + + mprx.ctrlr.cbs->player_name_recv(&mprx.local_player, 0, name); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL) + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + LOG_DBG("Remote player"); + return bt_mcc_read_player_name(mprx.remote_player.conn); + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL */ + + return -EINVAL; +} + +int media_proxy_ctrl_set_player_name(struct media_player *player, + const char *name) +{ + CHECKIF(player == NULL || name == NULL) + { + LOG_DBG("player or name is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->set_player_name != NULL) + { + mprx.local_player.calls->set_player_name(name); + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + + return -EINVAL; +} + +int media_proxy_ctrl_get_icon_id(struct media_player *player) +{ + CHECKIF(player == NULL) + { + LOG_DBG("player is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->get_icon_id) + { + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->icon_id_recv) + { + const uint64_t id = mprx.local_player.calls->get_icon_id(); + + mprx.ctrlr.cbs->icon_id_recv(&mprx.local_player, 0, id); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS) + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + return bt_mcc_read_icon_obj_id(mprx.remote_player.conn); + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS */ + + return -EINVAL; +} + +int media_proxy_ctrl_get_icon_url(struct media_player *player) +{ + CHECKIF(player == NULL) + { + LOG_DBG("player is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->get_icon_url) + { + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->icon_url_recv) + { + const char *url = mprx.local_player.calls->get_icon_url(); + + mprx.ctrlr.cbs->icon_url_recv(player, 0, url); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL) && defined(CONFIG_BT_MCC_READ_MEDIA_PLAYER_ICON_URL) + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + return bt_mcc_read_icon_url(mprx.remote_player.conn); + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL && CONFIG_BT_MCC_READ_MEDIA_PLAYER_ICON_URL */ + + return -EINVAL; +} + +int media_proxy_ctrl_get_track_title(struct media_player *player) +{ + CHECKIF(player == NULL) + { + LOG_DBG("player is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->get_track_title) + { + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->track_title_recv) + { + const char *title = mprx.local_player.calls->get_track_title(); + + mprx.ctrlr.cbs->track_title_recv(player, 0, title); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL) && defined(CONFIG_BT_MCC_READ_TRACK_TITLE) + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + return bt_mcc_read_track_title(mprx.remote_player.conn); + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL && CONFIG_BT_MCC_READ_TRACK_TITLE */ + + return -EINVAL; +} + +int media_proxy_ctrl_get_track_duration(struct media_player *player) +{ + CHECKIF(player == NULL) + { + LOG_DBG("player is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->get_track_duration) + { + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->track_duration_recv) + { + const int32_t duration = + mprx.local_player.calls->get_track_duration(); + + mprx.ctrlr.cbs->track_duration_recv(player, 0, duration); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL) && defined(CONFIG_BT_MCC_READ_TRACK_DURATION) + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + return bt_mcc_read_track_duration(mprx.remote_player.conn); + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL && CONFIG_BT_MCC_READ_TRACK_DURATION */ + + return -EINVAL; +} + +int media_proxy_ctrl_get_track_position(struct media_player *player) +{ + CHECKIF(player == NULL) + { + LOG_DBG("player is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->get_track_position) + { + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->track_position_recv) + { + const int32_t position = + mprx.local_player.calls->get_track_position(); + + mprx.ctrlr.cbs->track_position_recv(player, 0, position); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL) && defined(CONFIG_BT_MCC_READ_TRACK_POSITION) + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + return bt_mcc_read_track_position(mprx.remote_player.conn); + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL && CONFIG_BT_MCC_READ_TRACK_POSITION */ + + return -EINVAL; +} + +int media_proxy_ctrl_set_track_position(struct media_player *player, + int32_t position) +{ + CHECKIF(player == NULL) + { + LOG_DBG("player is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->set_track_position) + { + mprx.local_player.calls->set_track_position(position); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->track_position_write) + { + mprx.ctrlr.cbs->track_position_write(player, 0, position); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL) && CONFIG_BT_MCC_SET_TRACK_POSITION + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + return bt_mcc_set_track_position(mprx.remote_player.conn, position); + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL && CONFIG_BT_MCC_SET_TRACK_POSITION */ + + return -EINVAL; +} + +int media_proxy_ctrl_get_playback_speed(struct media_player *player) +{ + CHECKIF(player == NULL) + { + LOG_DBG("player is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->get_playback_speed) + { + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->playback_speed_recv) + { + const int8_t speed = + mprx.local_player.calls->get_playback_speed(); + + mprx.ctrlr.cbs->playback_speed_recv(player, 0, speed); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL) && defined(CONFIG_BT_MCC_READ_PLAYBACK_SPEED) + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + return bt_mcc_read_playback_speed(mprx.remote_player.conn); + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL && CONFIG_BT_MCC_READ_PLAYBACK_SPEED */ + + return -EINVAL; +} + +int media_proxy_ctrl_set_playback_speed(struct media_player *player, + int8_t speed) +{ + CHECKIF(player == NULL) + { + LOG_DBG("player is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->set_playback_speed) + { + mprx.local_player.calls->set_playback_speed(speed); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->playback_speed_write) + { + mprx.ctrlr.cbs->playback_speed_write(player, 0, speed); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL) && defined(CONFIG_BT_MCC_SET_PLAYBACK_SPEED) + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + return bt_mcc_set_playback_speed(mprx.remote_player.conn, speed); + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL && CONFIG_BT_MCC_SET_PLAYBACK_SPEED */ + + return -EINVAL; +} + +int media_proxy_ctrl_get_seeking_speed(struct media_player *player) +{ + CHECKIF(player == NULL) + { + LOG_DBG("player is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->get_seeking_speed) + { + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->seeking_speed_recv) + { + const int8_t speed = + mprx.local_player.calls->get_seeking_speed(); + + mprx.ctrlr.cbs->seeking_speed_recv(player, 0, speed); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL) && defined(CONFIG_BT_MCC_READ_SEEKING_SPEED) + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + return bt_mcc_read_seeking_speed(mprx.remote_player.conn); + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL && CONFIG_BT_MCC_READ_SEEKING_SPEED */ + + return -EINVAL; +} + +int media_proxy_ctrl_get_track_segments_id(struct media_player *player) +{ + CHECKIF(player == NULL) + { + LOG_DBG("player is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->get_track_segments_id) + { + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->track_segments_id_recv) + { + const uint64_t id = + mprx.local_player.calls->get_track_segments_id(); + + mprx.ctrlr.cbs->track_segments_id_recv(player, 0, id); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS) + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + return bt_mcc_read_segments_obj_id(mprx.remote_player.conn); + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS */ + + return -EINVAL; +} + +int media_proxy_ctrl_get_current_track_id(struct media_player *player) +{ + CHECKIF(player == NULL) + { + LOG_DBG("player is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->get_current_track_id) + { + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->current_track_id_recv) + { + const uint64_t id = + mprx.local_player.calls->get_current_track_id(); + + mprx.ctrlr.cbs->current_track_id_recv(player, 0, id); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS) + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + return bt_mcc_read_current_track_obj_id(mprx.remote_player.conn); + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS */ + + return -EINVAL; +} + +int media_proxy_ctrl_set_current_track_id(struct media_player *player, + uint64_t id) +{ + CHECKIF(player == NULL) + { + LOG_DBG("player is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + CHECKIF(!BT_MCS_VALID_OBJ_ID(id)) + { + LOG_DBG("Object ID 0x%016llx invalid", id); + + return -EINVAL; + } + + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->set_current_track_id) + { + mprx.local_player.calls->set_current_track_id(id); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->current_track_id_write) + { + mprx.ctrlr.cbs->current_track_id_write(player, 0, id); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS) + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + /* TODO: Uncomment when function is implemented */ + + /* return bt_mcc_set_current_track_obj_id(mprx.remote_player.conn, + * position); + */ + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS */ + + return -EINVAL; +} + +int media_proxy_ctrl_get_next_track_id(struct media_player *player) +{ + CHECKIF(player == NULL) + { + LOG_DBG("player is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->get_next_track_id) + { + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->next_track_id_recv) + { + const uint64_t id = + mprx.local_player.calls->get_next_track_id(); + + mprx.ctrlr.cbs->next_track_id_recv(player, 0, id); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS) + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + return bt_mcc_read_next_track_obj_id(mprx.remote_player.conn); + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS */ + + return -EINVAL; +} + +int media_proxy_ctrl_set_next_track_id(struct media_player *player, + uint64_t id) +{ + CHECKIF(player == NULL) + { + LOG_DBG("player is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + CHECKIF(!BT_MCS_VALID_OBJ_ID(id)) + { + LOG_DBG("Object ID 0x%016llx invalid", id); + + return -EINVAL; + } + + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->set_next_track_id) + { + mprx.local_player.calls->set_next_track_id(id); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->next_track_id_write) + { + mprx.ctrlr.cbs->next_track_id_write(player, 0, id); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS) + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + /* TODO: Uncomment when function is implemented */ + + /* return bt_mcc_set_next_track_obj_id(mprx.remote_player.conn, + * position); + */ + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS */ + + return -EINVAL; +} + +int media_proxy_ctrl_get_parent_group_id(struct media_player *player) +{ + CHECKIF(player == NULL) + { + LOG_DBG("player is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->get_parent_group_id) + { + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->parent_group_id_recv) + { + const uint64_t id = + mprx.local_player.calls->get_parent_group_id(); + + mprx.ctrlr.cbs->parent_group_id_recv(player, 0, id); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS) + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + return bt_mcc_read_parent_group_obj_id(mprx.remote_player.conn); + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS */ + + return -EINVAL; +} + +int media_proxy_ctrl_get_current_group_id(struct media_player *player) +{ + CHECKIF(player == NULL) + { + LOG_DBG("player is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->get_current_group_id) + { + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->current_group_id_recv) + { + const uint64_t id = + mprx.local_player.calls->get_current_group_id(); + + mprx.ctrlr.cbs->current_group_id_recv(player, 0, id); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS) + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + return bt_mcc_read_current_group_obj_id(mprx.remote_player.conn); + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS */ + + return -EINVAL; +} + +int media_proxy_ctrl_set_current_group_id(struct media_player *player, + uint64_t id) +{ + CHECKIF(player == NULL) + { + LOG_DBG("player is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + CHECKIF(!BT_MCS_VALID_OBJ_ID(id)) + { + LOG_DBG("Object ID 0x%016llx invalid", id); + + return -EINVAL; + } + + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->set_current_group_id) + { + mprx.local_player.calls->set_current_group_id(id); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->current_group_id_write) + { + mprx.ctrlr.cbs->current_group_id_write(player, 0, id); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS) + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + /* TODO: Uncomment when function is implemented */ + + /* return bt_mcc_set_current_group_obj_id(mprx.remote_player.conn, + * position); + */ + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS */ + + return -EINVAL; +} + +int media_proxy_ctrl_get_playing_order(struct media_player *player) +{ + CHECKIF(player == NULL) + { + LOG_DBG("player is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->get_playing_order) + { + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->playing_order_recv) + { + const uint8_t order = + mprx.local_player.calls->get_playing_order(); + + mprx.ctrlr.cbs->playing_order_recv(player, 0, order); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL) && defined(CONFIG_BT_MCC_READ_PLAYING_ORDER) + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + LOG_DBG("Remote player"); + return bt_mcc_read_playing_order(mprx.remote_player.conn); + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL && CONFIG_BT_MCC_READ_PLAYING_ORDER */ + + return -EINVAL; +} + +int media_proxy_ctrl_set_playing_order(struct media_player *player, + uint8_t order) +{ + CHECKIF(player == NULL) + { + LOG_DBG("player is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->set_playing_order) + { + mprx.local_player.calls->set_playing_order(order); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->playing_order_write) + { + mprx.ctrlr.cbs->playing_order_write(player, 0, order); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL) && defined(CONFIG_BT_MCC_SET_PLAYING_ORDER) + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + return bt_mcc_set_playing_order(mprx.remote_player.conn, order); + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL && CONFIG_BT_MCC_SET_PLAYING_ORDER */ + + return -EINVAL; +} + +int +media_proxy_ctrl_get_playing_orders_supported(struct media_player *player) +{ + CHECKIF(player == NULL) + { + LOG_DBG("player is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->get_playing_orders_supported) + { + if (mprx.ctrlr.cbs && + mprx.ctrlr.cbs->playing_orders_supported_recv) + { + struct media_proxy_pl_calls *calls; + uint16_t (*get_orders)(void); + uint16_t orders; + + calls = mprx.local_player.calls; + get_orders = calls->get_playing_orders_supported; + orders = get_orders(); + + mprx.ctrlr.cbs->playing_orders_supported_recv(player, 0, + orders); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL) && \ + defined(CONFIG_BT_MCC_READ_PLAYING_ORDER_SUPPORTED) + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + return bt_mcc_read_playing_orders_supported(mprx.remote_player.conn); + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL && CONFIG_BT_MCC_READ_PLAYING_ORDER_SUPPORTED */ + + return -EINVAL; +} + +int media_proxy_ctrl_get_media_state(struct media_player *player) +{ + CHECKIF(player == NULL) + { + LOG_DBG("player is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->get_media_state) + { + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->media_state_recv) + { + const uint8_t state = + mprx.local_player.calls->get_media_state(); + + mprx.ctrlr.cbs->media_state_recv(player, 0, state); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL) && defined(CONFIG_BT_MCC_READ_MEDIA_STATE) + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + return bt_mcc_read_media_state(mprx.remote_player.conn); + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL && CONFIG_BT_MCC_READ_MEDIA_STATE */ + + return -EINVAL; +} + +int media_proxy_ctrl_send_command(struct media_player *player, + const struct mpl_cmd *cmd) +{ + CHECKIF(player == NULL || cmd == NULL) + { + LOG_DBG("NULL pointer"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->send_command) + { + mprx.local_player.calls->send_command(cmd); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->command_send) + { + mprx.ctrlr.cbs->command_send(player, 0, cmd); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL) && defined(CONFIG_BT_MCC_SET_MEDIA_CONTROL_POINT) + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + return bt_mcc_send_cmd(mprx.remote_player.conn, cmd); + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL && CONFIG_BT_MCC_SET_MEDIA_CONTROL_POINT */ + + return -EINVAL; +} + +int media_proxy_ctrl_get_commands_supported(struct media_player *player) +{ + CHECKIF(player == NULL) + { + LOG_DBG("player is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->get_commands_supported) + { + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->commands_supported_recv) + { + const uint32_t opcodes = + mprx.local_player.calls->get_commands_supported(); + + mprx.ctrlr.cbs->commands_supported_recv(player, 0, opcodes); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL) && \ + defined(CONFIG_BT_MCC_READ_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED) + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + return bt_mcc_read_opcodes_supported(mprx.remote_player.conn); + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL && \ + + * CONFIG_BT_MCC_READ_MEDIA_CONTROL_POINT_OPCODES_SUPPORTED \ + */ + + return -EINVAL; +} + +int media_proxy_ctrl_send_search(struct media_player *player, + const struct mpl_search *search) +{ + CHECKIF(player == NULL || search == NULL) + { + LOG_DBG("NULL pointer"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->send_search) + { + mprx.local_player.calls->send_search(search); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->search_send) + { + mprx.ctrlr.cbs->search_send(player, 0, search); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS) + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + return bt_mcc_send_search(mprx.remote_player.conn, search); + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS */ + + return -EINVAL; +} + +int media_proxy_ctrl_get_search_results_id(struct media_player *player) +{ + CHECKIF(player == NULL) + { + LOG_DBG("player is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->get_search_results_id) + { + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->search_results_id_recv) + { + const uint64_t id = + mprx.local_player.calls->get_search_results_id(); + + mprx.ctrlr.cbs->search_results_id_recv(player, 0, id); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS) + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + return bt_mcc_read_search_results_obj_id(mprx.remote_player.conn); + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL_OBJECTS */ + + return -EINVAL; +} + +uint8_t media_proxy_ctrl_get_content_ctrl_id(struct media_player *player) +{ + CHECKIF(player == NULL) + { + LOG_DBG("player is NULL"); + return -EINVAL; + } + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL) + if (mprx.local_player.registered && player == &mprx.local_player) + { + if (mprx.local_player.calls->get_content_ctrl_id) + { + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->content_ctrl_id_recv) + { + const uint8_t ccid = + mprx.local_player.calls->get_content_ctrl_id(); + + mprx.ctrlr.cbs->content_ctrl_id_recv(player, 0, ccid); + } + else + { + LOG_DBG("No callback"); + } + + return 0; + } + + LOG_DBG("No call"); + return -EOPNOTSUPP; + } +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL */ + +#if defined(CONFIG_MCTL_REMOTE_PLAYER_CONTROL) && defined(CONFIG_BT_MCC_READ_CONTENT_CONTROL_ID) + if (mprx.remote_player.registered && player == &mprx.remote_player) + { + return bt_mcc_read_content_control_id(mprx.remote_player.conn); + } +#endif /* CONFIG_MCTL_REMOTE_PLAYER_CONTROL && CONFIG_BT_MCC_READ_CONTENT_CONTROL_ID */ + + return -EINVAL; +} +#endif /* CONFIG_MCTL_LOCAL_PLAYER_LOCAL_CONTROL || CONFIG_MCTL_REMOTE_PLAYER_CONTROL */ + +#if defined(CONFIG_MCTL_LOCAL_PLAYER_CONTROL) +/* Player calls */ + +int media_proxy_pl_register(struct media_proxy_pl_calls *pl_calls) +{ + CHECKIF(pl_calls == NULL) + { + LOG_DBG("pl_calls is NULL"); + return -EINVAL; + } + + if (mprx.local_player.registered) + { + LOG_DBG("Player already registered"); + return -EALREADY; + } + + mprx.local_player.calls = pl_calls; + mprx.local_player.registered = true; + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->local_player_instance) + { + mprx.ctrlr.cbs->local_player_instance(&mprx.local_player, 0); + } + + return 0; +}; + +/* Player callbacks */ + +/* All callbacks here must come from the local player - mprx.local_player */ + +void media_proxy_pl_name_cb(const char *name) +{ + mprx.sctrlr.cbs->player_name(name); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->player_name_recv) + { + mprx.ctrlr.cbs->player_name_recv(&mprx.local_player, 0, name); + } + else + { + LOG_DBG("No ctrlr player name callback"); + } +} + +void media_proxy_pl_icon_url_cb(const char *url) +{ + mprx.sctrlr.cbs->icon_url(url); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->player_name_recv) + { + mprx.ctrlr.cbs->icon_url_recv(&mprx.local_player, 0, url); + } + else + { + LOG_DBG("No ctrlr player icon URL callback"); + } +} + +void media_proxy_pl_track_changed_cb(void) +{ + mprx.sctrlr.cbs->track_changed(); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->track_changed_recv) + { + mprx.ctrlr.cbs->track_changed_recv(&mprx.local_player, 0); + } + else + { + LOG_DBG("No ctrlr track changed callback"); + } +} + +void media_proxy_pl_track_title_cb(char *title) +{ + mprx.sctrlr.cbs->track_title(title); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->track_title_recv) + { + mprx.ctrlr.cbs->track_title_recv(&mprx.local_player, 0, title); + } + else + { + LOG_DBG("No ctrlr track title callback"); + } +} + +void media_proxy_pl_track_duration_cb(int32_t duration) +{ + mprx.sctrlr.cbs->track_duration(duration); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->track_duration_recv) + { + mprx.ctrlr.cbs->track_duration_recv(&mprx.local_player, 0, duration); + } + else + { + LOG_DBG("No ctrlr track duration callback"); + } +} + +void media_proxy_pl_track_position_cb(int32_t position) +{ + mprx.sctrlr.cbs->track_position(position); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->track_position_recv) + { + mprx.ctrlr.cbs->track_position_recv(&mprx.local_player, 0, position); + } + else + { + LOG_DBG("No ctrlr track position callback"); + } +} + +void media_proxy_pl_playback_speed_cb(int8_t speed) +{ + mprx.sctrlr.cbs->playback_speed(speed); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->playback_speed_recv) + { + mprx.ctrlr.cbs->playback_speed_recv(&mprx.local_player, 0, speed); + } + else + { + LOG_DBG("No ctrlr playback speed callback"); + } +} + +void media_proxy_pl_seeking_speed_cb(int8_t speed) +{ + mprx.sctrlr.cbs->seeking_speed(speed); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->seeking_speed_recv) + { + mprx.ctrlr.cbs->seeking_speed_recv(&mprx.local_player, 0, speed); + } + else + { + LOG_DBG("No ctrlr seeking speed callback"); + } +} + +#ifdef CONFIG_BT_MPL_OBJECTS +void media_proxy_pl_current_track_id_cb(uint64_t id) +{ + mprx.sctrlr.cbs->current_track_id(id); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->current_track_id_recv) + { + mprx.ctrlr.cbs->current_track_id_recv(&mprx.local_player, 0, id); + } + else + { + LOG_DBG("No ctrlr current track id callback"); + } +} + +void media_proxy_pl_next_track_id_cb(uint64_t id) +{ + mprx.sctrlr.cbs->next_track_id(id); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->next_track_id_recv) + { + mprx.ctrlr.cbs->next_track_id_recv(&mprx.local_player, 0, id); + } + else + { + LOG_DBG("No ctrlr next track id callback"); + } +} + +void media_proxy_pl_parent_group_id_cb(uint64_t id) +{ + mprx.sctrlr.cbs->parent_group_id(id); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->parent_group_id_recv) + { + mprx.ctrlr.cbs->parent_group_id_recv(&mprx.local_player, 0, id); + } + else + { + LOG_DBG("No ctrlr parent group id callback"); + } +} + +void media_proxy_pl_current_group_id_cb(uint64_t id) +{ + mprx.sctrlr.cbs->current_group_id(id); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->current_group_id_recv) + { + mprx.ctrlr.cbs->current_group_id_recv(&mprx.local_player, 0, id); + } + else + { + LOG_DBG("No ctrlr current group id callback"); + } +} +#endif /* CONFIG_BT_MPL_OBJECTS */ + +void media_proxy_pl_playing_order_cb(uint8_t order) +{ + mprx.sctrlr.cbs->playing_order(order); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->playing_order_recv) + { + mprx.ctrlr.cbs->playing_order_recv(&mprx.local_player, 0, order); + } + else + { + LOG_DBG("No ctrlr playing order callback"); + } +} + +void media_proxy_pl_media_state_cb(uint8_t state) +{ + mprx.sctrlr.cbs->media_state(state); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->media_state_recv) + { + mprx.ctrlr.cbs->media_state_recv(&mprx.local_player, 0, state); + } + else + { + LOG_DBG("No ctrlr media state callback"); + } +} + +void media_proxy_pl_command_cb(const struct mpl_cmd_ntf *cmd_ntf) +{ + CHECKIF(cmd_ntf == NULL) + { + LOG_WRN("cmd_ntf is NULL"); + return; + } + + mprx.sctrlr.cbs->command(cmd_ntf); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->command_recv) + { + mprx.ctrlr.cbs->command_recv(&mprx.local_player, 0, cmd_ntf); + } + else + { + LOG_DBG("No ctrlr command callback"); + } +} + +void media_proxy_pl_commands_supported_cb(uint32_t opcodes) +{ + mprx.sctrlr.cbs->commands_supported(opcodes); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->commands_supported_recv) + { + mprx.ctrlr.cbs->commands_supported_recv(&mprx.local_player, 0, + opcodes); + } + else + { + LOG_DBG("No ctrlr commands supported callback"); + } +} + +#ifdef CONFIG_BT_MPL_OBJECTS +void media_proxy_pl_search_cb(uint8_t result_code) +{ + mprx.sctrlr.cbs->search(result_code); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->search_recv) + { + mprx.ctrlr.cbs->search_recv(&mprx.local_player, 0, result_code); + } + else + { + LOG_DBG("No ctrlr search callback"); + } +} + +void media_proxy_pl_search_results_id_cb(uint64_t id) +{ + mprx.sctrlr.cbs->search_results_id(id); + + if (mprx.ctrlr.cbs && mprx.ctrlr.cbs->search_results_id_recv) + { + mprx.ctrlr.cbs->search_results_id_recv(&mprx.local_player, 0, id); + } + else + { + LOG_DBG("No ctrlr search results id callback"); + } +} +#endif /* CONFIG_BT_MPL_OBJECTS */ +#endif /* CONFIG_MCTL_LOCAL_PLAYER_CONTROL */ diff --git a/libs/zblue/audio/media_proxy_internal.h b/libs/zblue/audio/media_proxy_internal.h new file mode 100644 index 0000000000000..d2dec181eba1a --- /dev/null +++ b/libs/zblue/audio/media_proxy_internal.h @@ -0,0 +1,150 @@ +/**************************************************************************** + * libs/zblue/audio/media_proxy_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_SUBSYS_BLUETOOTH_AUDIO_MEDIA_PROXY_INTERNAL_H_ +#define ZEPHYR_SUBSYS_BLUETOOTH_AUDIO_MEDIA_PROXY_INTERNAL_H_ + +/** @brief Internal APIs for Bluetooth Media Control */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#define MPL_NO_TRACK_ID 0U + +/* Debug output of 48 bit Object ID value */ + +/* (Zephyr does not yet support debug output of more than 32 bit values.) */ + +/* Takes a text and a 64-bit integer as input */ +#define LOG_DBG_OBJ_ID(text, id64) \ + do \ + { \ + if (IS_ENABLED(CONFIG_BT_MCS_LOG_LEVEL_DBG)) \ + { \ + char t[BT_OTS_OBJ_ID_STR_LEN]; \ + (void)bt_ots_obj_id_to_str(id64, t, sizeof(t)); \ + LOG_DBG(text "0x%s", t); \ + } \ + } \ + while (0) + +/* SYNCHRONOUS (CALL/RETURN) API FOR CONTROLLERS */ + +/** @brief Callbacks to a controller, from the media proxy */ + +struct media_proxy_sctrl_cbs +{ + void (*player_name)(const char *name); + + void (*icon_url)(const char *url); + + void (*track_changed)(void); + + void (*track_title)(const char *title); + + void (*track_duration)(int32_t duration); + + void (*track_position)(int32_t position); + + void (*playback_speed)(int8_t speed); + + void (*seeking_speed)(int8_t speed); +#ifdef CONFIG_BT_OTS + void (*current_track_id)(uint64_t id); + + void (*next_track_id)(uint64_t id); + + void (*current_group_id)(uint64_t id); + + void (*parent_group_id)(uint64_t id); +#endif /* CONFIG_BT_OTS */ + + void (*playing_order)(uint8_t order); + + void (*media_state)(uint8_t state); + + void (*command)(const struct mpl_cmd_ntf *cmd_ntf); + + void (*commands_supported)(uint32_t opcodes); + +#ifdef CONFIG_BT_OTS + void (*search)(uint8_t result_code); + + void (*search_results_id)(uint64_t id); +#endif /* CONFIG_BT_OTS */ +}; + +/** @brief Register a controller with the media proxy */ + +int media_proxy_sctrl_register(struct media_proxy_sctrl_cbs *sctrl_cbs); + +const char *media_proxy_sctrl_get_player_name(void); + +#ifdef CONFIG_BT_OTS +uint64_t media_proxy_sctrl_get_icon_id(void); +#endif /* CONFIG_BT_OTS */ + +const char *media_proxy_sctrl_get_icon_url(void); + +const char *media_proxy_sctrl_get_track_title(void); + +int32_t media_proxy_sctrl_get_track_duration(void); + +int32_t media_proxy_sctrl_get_track_position(void); +void media_proxy_sctrl_set_track_position(int32_t position); + +int8_t media_proxy_sctrl_get_playback_speed(void); +void media_proxy_sctrl_set_playback_speed(int8_t speed); + +int8_t media_proxy_sctrl_get_seeking_speed(void); + +#ifdef CONFIG_BT_OTS +uint64_t media_proxy_sctrl_get_track_segments_id(void); + +uint64_t media_proxy_sctrl_get_current_track_id(void); +void media_proxy_sctrl_set_current_track_id(uint64_t id); + +uint64_t media_proxy_sctrl_get_next_track_id(void); +void media_proxy_sctrl_set_next_track_id(uint64_t id); + +uint64_t media_proxy_sctrl_get_current_group_id(void); +void media_proxy_sctrl_set_current_group_id(uint64_t id); + +uint64_t media_proxy_sctrl_get_parent_group_id(void); +#endif /* CONFIG_BT_OTS */ + +uint8_t media_proxy_sctrl_get_playing_order(void); +void media_proxy_sctrl_set_playing_order(uint8_t order); + +uint16_t media_proxy_sctrl_get_playing_orders_supported(void); + +uint8_t media_proxy_sctrl_get_media_state(void); + +void media_proxy_sctrl_send_command(const struct mpl_cmd *command); + +uint32_t media_proxy_sctrl_get_commands_supported(void); + +#ifdef CONFIG_BT_OTS +void media_proxy_sctrl_send_search(const struct mpl_search *search); + +uint64_t media_proxy_sctrl_get_search_results_id(void); +#endif /* CONFIG_BT_OTS */ + +uint8_t media_proxy_sctrl_get_content_ctrl_id(void); + +#endif /* ZEPHYR_SUBSYS_BLUETOOTH_AUDIO_MEDIA_PROXY_INTERNAL_H_ */ diff --git a/libs/zblue/audio/micp_internal.h b/libs/zblue/audio/micp_internal.h new file mode 100644 index 0000000000000..5f70ce5b41a27 --- /dev/null +++ b/libs/zblue/audio/micp_internal.h @@ -0,0 +1,62 @@ +/**************************************************************************** + * libs/zblue/audio/micp_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Internal APIs for Bluetooth MICP. + */ + +/* Copyright (c) 2020 Bose Corporation + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_MICP_INTERNAL_ +#define ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_MICP_INTERNAL_ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include + +enum bt_micp_mic_ctlr_flag +{ + BT_MICP_MIC_CTLR_FLAG_BUSY, + + BT_MICP_MIC_CTLR_FLAG_NUM_FLAGS, /* keep as last */ +}; + +struct bt_micp_mic_ctlr +{ + uint16_t start_handle; + uint16_t end_handle; + uint16_t mute_handle; + struct bt_gatt_subscribe_params mute_sub_params; + struct bt_gatt_discover_params mute_sub_disc_params; + + uint8_t mute_val_buf[1]; /* Mute value is a single octet */ + struct bt_gatt_write_params write_params; + struct bt_gatt_read_params read_params; + struct bt_gatt_discover_params discover_params; + struct bt_conn *conn; + +#if defined(CONFIG_BT_MICP_MIC_CTLR_AICS) + uint8_t aics_inst_cnt; + struct bt_aics *aics[CONFIG_BT_MICP_MIC_CTLR_MAX_AICS_INST]; +#endif /* CONFIG_BT_MICP_MIC_CTLR_AICS */ + + atomic_define(flags, BT_MICP_MIC_CTLR_FLAG_NUM_FLAGS); +}; + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_MICP_INTERNAL_ */ diff --git a/libs/zblue/audio/micp_mic_ctlr.c b/libs/zblue/audio/micp_mic_ctlr.c new file mode 100644 index 0000000000000..c1206e27a5d01 --- /dev/null +++ b/libs/zblue/audio/micp_mic_ctlr.c @@ -0,0 +1,949 @@ +/**************************************************************************** + * libs/zblue/audio/micp_mic_ctlr.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Bluetooth MICP - Microphone Control Profile - Microphone Controller */ + +/* Copyright (c) 2020 Bose Corporation + * Copyright (c) 2020-2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "aics_internal.h" +#include "common/bt_str.h" +#include "micp_internal.h" + +LOG_MODULE_REGISTER(bt_micp_mic_ctlr, CONFIG_BT_MICP_MIC_CTLR_LOG_LEVEL); + +/* Callback functions */ + +static struct list_node micp_mic_ctlr_cbs = + LIST_INITIAL_VALUE(micp_mic_ctlr_cbs); + +static struct bt_micp_mic_ctlr mic_ctlrs[CONFIG_BT_MAX_CONN]; +static const struct bt_uuid *mics_uuid = BT_UUID_MICS; + +#if defined(CONFIG_BT_MICP_MIC_CTLR_AICS) + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void micp_mic_ctlr_client_free_aics(void) +{ + ARRAY_FOR_EACH_PTR(mic_ctlrs, mic_ctlr) + { + ARRAY_FOR_EACH_PTR(mic_ctlr->aics, aics) + { + if (*aics != NULL) + { + bt_aics_client_free_instance(*aics); + *aics = NULL; + } + } + } +} +#endif /* CONFIG_BT_MICP_MIC_CTLR_AICS */ + +static struct bt_micp_mic_ctlr *mic_ctlr_get_by_conn( + const struct bt_conn *conn) +{ + return &mic_ctlrs[bt_conn_index(conn)]; +} + +static void micp_mic_ctlr_mute_changed(struct bt_micp_mic_ctlr *mic_ctlr, + int err, uint8_t mute_val) +{ + struct bt_micp_mic_ctlr_cb *listener; + struct bt_micp_mic_ctlr_cb *next; + + list_for_every_container_safe(&micp_mic_ctlr_cbs, listener, next, _node) + { + if (listener->mute) + { + listener->mute(mic_ctlr, err, mute_val); + } + } +} + +static void micp_mic_ctlr_mute_written(struct bt_micp_mic_ctlr *mic_ctlr, + int err, uint8_t mute_val) +{ + struct bt_micp_mic_ctlr_cb *listener; + struct bt_micp_mic_ctlr_cb *next; + + list_for_every_container_safe(&micp_mic_ctlr_cbs, listener, next, _node) + { + if (mute_val == BT_MICP_MUTE_UNMUTED) + { + if (listener->unmute_written) + { + listener->unmute_written(mic_ctlr, err); + } + } + else + { + if (listener->mute_written) + { + listener->mute_written(mic_ctlr, err); + } + } + } +} + +static void micp_mic_ctlr_discover_complete( + struct bt_micp_mic_ctlr *mic_ctlr, int err) +{ + struct bt_micp_mic_ctlr_cb *listener; + struct bt_micp_mic_ctlr_cb *next; + + atomic_clear_bit(mic_ctlr->flags, BT_MICP_MIC_CTLR_FLAG_BUSY); + + list_for_every_container_safe(&micp_mic_ctlr_cbs, listener, next, _node) + { + if (listener->discover) + { + uint8_t aics_cnt = 0U; + +#if defined(CONFIG_BT_MICP_MIC_CTLR_AICS) + if (err == 0) + { + aics_cnt = mic_ctlr->aics_inst_cnt; + } +#endif /* CONFIG_BT_MICP_MIC_CTLR_AICS */ + + listener->discover(mic_ctlr, err, aics_cnt); + } + } +} + +static uint8_t mute_notify_handler( + struct bt_conn *conn, struct bt_gatt_subscribe_params *params, + const void *data, uint16_t length) +{ + uint8_t *mute_val; + struct bt_micp_mic_ctlr *mic_ctlr; + + ARG_UNUSED(params); + + if (conn == NULL) + { + return BT_GATT_ITER_CONTINUE; + } + + mic_ctlr = mic_ctlr_get_by_conn(conn); + + if (data != NULL) + { + if (length == sizeof(*mute_val)) + { + mute_val = (uint8_t *)data; + LOG_DBG("Mute %u", *mute_val); + micp_mic_ctlr_mute_changed(mic_ctlr, 0, *mute_val); + } + } + else + { + LOG_DBG("Invalid length %u (expected %zu)", length, sizeof(*mute_val)); + } + + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t micp_mic_ctlr_read_mute_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct bt_micp_mic_ctlr *mic_ctlr = mic_ctlr_get_by_conn(conn); + uint8_t cb_err = err; + uint8_t mute_val = 0U; + + ARG_UNUSED(params); + + atomic_clear_bit(mic_ctlr->flags, BT_MICP_MIC_CTLR_FLAG_BUSY); + + if (err > 0) + { + LOG_DBG("err: 0x%02X", err); + } + else if (data != NULL) + { + if (length == sizeof(mute_val)) + { + mute_val = ((uint8_t *)data)[0]; + LOG_DBG("Mute %u", mute_val); + } + else + { + LOG_DBG("Invalid length %u (expected %zu)", length, + sizeof(mute_val)); + cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + } + + micp_mic_ctlr_mute_changed(mic_ctlr, cb_err, mute_val); + + return BT_GATT_ITER_STOP; +} + +static void micp_mic_ctlr_write_mics_mute_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_write_params *params) +{ + struct bt_micp_mic_ctlr *mic_ctlr = mic_ctlr_get_by_conn(conn); + uint8_t mute_val = mic_ctlr->mute_val_buf[0]; + + ARG_UNUSED(params); + + LOG_DBG("Write %s (0x%02X)", err ? "failed" : "successful", err); + + atomic_clear_bit(mic_ctlr->flags, BT_MICP_MIC_CTLR_FLAG_BUSY); + + micp_mic_ctlr_mute_written(mic_ctlr, err, mute_val); +} + +#if defined(CONFIG_BT_MICP_MIC_CTLR_AICS) +static struct bt_micp_mic_ctlr *lookup_micp_by_aics( + const struct bt_aics *aics) +{ + DEBUGASSERT(aics != NULL); + + for (int i = 0; i < ARRAY_SIZE(mic_ctlrs); i++) + { + for (int j = 0; j < ARRAY_SIZE(mic_ctlrs[i].aics); j++) + { + if (mic_ctlrs[i].aics[j] == aics) + { + return &mic_ctlrs[i]; + } + } + } + + return NULL; +} + +static void micp_mic_ctlr_aics_state_cb( + struct bt_aics *inst, int err, int8_t gain, uint8_t mute, uint8_t mode) +{ + struct bt_micp_mic_ctlr_cb *listener; + struct bt_micp_mic_ctlr_cb *next; + + list_for_every_container_safe(&micp_mic_ctlr_cbs, listener, next, _node) + { + if (listener->aics_cb.state) + { + listener->aics_cb.state(inst, err, gain, mute, mode); + } + } +} + +static void micp_mic_ctlr_aics_gain_setting_cb( + struct bt_aics *inst, int err, uint8_t units, int8_t minimum, + int8_t maximum) +{ + struct bt_micp_mic_ctlr_cb *listener; + struct bt_micp_mic_ctlr_cb *next; + + list_for_every_container_safe(&micp_mic_ctlr_cbs, listener, next, _node) + { + if (listener->aics_cb.gain_setting) + { + listener->aics_cb.gain_setting(inst, err, units, minimum, maximum); + } + } +} + +static void micp_mic_ctlr_aics_type_cb(struct bt_aics *inst, + int err, uint8_t type) +{ + struct bt_micp_mic_ctlr_cb *listener; + struct bt_micp_mic_ctlr_cb *next; + + list_for_every_container_safe(&micp_mic_ctlr_cbs, listener, next, _node) + { + if (listener->aics_cb.type) + { + listener->aics_cb.type(inst, err, type); + } + } +} + +static void micp_mic_ctlr_aics_status_cb(struct bt_aics *inst, + int err, bool active) +{ + struct bt_micp_mic_ctlr_cb *listener; + struct bt_micp_mic_ctlr_cb *next; + + list_for_every_container_safe(&micp_mic_ctlr_cbs, listener, next, _node) + { + if (listener->aics_cb.status) + { + listener->aics_cb.status(inst, err, active); + } + } +} + +static void micp_mic_ctlr_aics_description_cb(struct bt_aics *inst, + int err, char *description) +{ + struct bt_micp_mic_ctlr_cb *listener; + struct bt_micp_mic_ctlr_cb *next; + + list_for_every_container_safe(&micp_mic_ctlr_cbs, listener, next, _node) + { + if (listener->aics_cb.description) + { + listener->aics_cb.description(inst, err, description); + } + } +} + +static void micp_mic_ctlr_aics_discover_cb(struct bt_aics *inst, int err) +{ + struct bt_micp_mic_ctlr *mic_ctlr = lookup_micp_by_aics(inst); + struct bt_micp_mic_ctlr_cb *listener; + struct bt_micp_mic_ctlr_cb *next; + + if (mic_ctlr == NULL) + { + LOG_ERR("Could not lookup mic_ctlr from aics"); + + return; + } + + if (err == 0) + { + /* Continue discovery of included services */ + + err = bt_gatt_discover(mic_ctlr->conn, &mic_ctlr->discover_params); + } + + if (err != 0) + { + LOG_DBG("Discover failed (err %d)", err); + micp_mic_ctlr_discover_complete(mic_ctlr, err); + } + + list_for_every_container_safe(&micp_mic_ctlr_cbs, listener, next, _node) + { + if (listener->aics_cb.discover) + { + listener->aics_cb.discover(inst, err); + } + } +} + +static void micp_mic_ctlr_aics_set_gain_cb(struct bt_aics *inst, int err) +{ + struct bt_micp_mic_ctlr_cb *listener; + struct bt_micp_mic_ctlr_cb *next; + + list_for_every_container_safe(&micp_mic_ctlr_cbs, listener, next, _node) + { + if (listener->aics_cb.set_gain) + { + listener->aics_cb.set_gain(inst, err); + } + } +} + +static void micp_mic_ctlr_aics_unmute_cb(struct bt_aics *inst, int err) +{ + struct bt_micp_mic_ctlr_cb *listener; + struct bt_micp_mic_ctlr_cb *next; + + list_for_every_container_safe(&micp_mic_ctlr_cbs, listener, next, _node) + { + if (listener->aics_cb.unmute) + { + listener->aics_cb.unmute(inst, err); + } + } +} + +static void micp_mic_ctlr_aics_mute_cb(struct bt_aics *inst, int err) +{ + struct bt_micp_mic_ctlr_cb *listener; + struct bt_micp_mic_ctlr_cb *next; + + list_for_every_container_safe(&micp_mic_ctlr_cbs, listener, next, _node) + { + if (listener->aics_cb.mute) + { + listener->aics_cb.mute(inst, err); + } + } +} + +static void micp_mic_ctlr_aics_set_manual_mode_cb(struct bt_aics *inst, + int err) +{ + struct bt_micp_mic_ctlr_cb *listener; + struct bt_micp_mic_ctlr_cb *next; + + list_for_every_container_safe(&micp_mic_ctlr_cbs, listener, next, _node) + { + if (listener->aics_cb.set_manual_mode) + { + listener->aics_cb.set_manual_mode(inst, err); + } + } +} + +static void micp_mic_ctlr_aics_set_auto_mode_cb(struct bt_aics *inst, + int err) +{ + struct bt_micp_mic_ctlr_cb *listener; + struct bt_micp_mic_ctlr_cb *next; + + list_for_every_container_safe(&micp_mic_ctlr_cbs, listener, next, _node) + { + if (listener->aics_cb.set_auto_mode) + { + listener->aics_cb.set_auto_mode(inst, err); + } + } +} + +static uint8_t micp_discover_include_func( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_micp_mic_ctlr *mic_ctlr = mic_ctlr_get_by_conn(conn); + + if (attr == NULL) + { + LOG_DBG("Discover include complete for MICS: %u AICS", + mic_ctlr->aics_inst_cnt); + (void)memset(params, 0, sizeof(*params)); + + micp_mic_ctlr_discover_complete(mic_ctlr, 0); + + return BT_GATT_ITER_STOP; + } + + LOG_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle); + + if (params->type == BT_GATT_DISCOVER_INCLUDE) + { + struct bt_gatt_include *include = + (struct bt_gatt_include *)attr->user_data; + + LOG_DBG("Include UUID %s", bt_uuid_str(include->uuid)); + + if (bt_uuid_cmp(include->uuid, BT_UUID_AICS) == 0 && + mic_ctlr->aics_inst_cnt < CONFIG_BT_MICP_MIC_CTLR_MAX_AICS_INST) + { + uint8_t inst_idx; + int err; + struct bt_aics_discover_param param = + { + .start_handle = include->start_handle, + .end_handle = include->end_handle, + }; + + /* Update discover params so we can continue where we + * left off after bt_aics_discover + */ + + mic_ctlr->discover_params.start_handle = attr->handle + 1; + + inst_idx = mic_ctlr->aics_inst_cnt++; + err = bt_aics_discover(conn, mic_ctlr->aics[inst_idx], ¶m); + if (err != 0) + { + LOG_DBG("AICS Discover failed (err %d)", err); + micp_mic_ctlr_discover_complete(mic_ctlr, err); + } + + return BT_GATT_ITER_STOP; + } + } + + return BT_GATT_ITER_CONTINUE; +} +#endif /* CONFIG_BT_MICP_MIC_CTLR_AICS */ + +/** + * @brief This will discover all characteristics on the server, retrieving + * the handles of the writeable characteristics and subscribing to all notify + * and indicate characteristics. + */ + +static uint8_t micp_discover_func( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_micp_mic_ctlr *mic_ctlr = mic_ctlr_get_by_conn(conn); + + if (attr == NULL) + { + int err = 0; + + LOG_DBG("Discovery complete"); + (void)memset(params, 0, sizeof(*params)); + +#if defined(CONFIG_BT_MICP_MIC_CTLR_AICS) + /* Discover included services */ + + mic_ctlr->discover_params.start_handle = mic_ctlr->start_handle; + mic_ctlr->discover_params.end_handle = mic_ctlr->end_handle; + mic_ctlr->discover_params.type = BT_GATT_DISCOVER_INCLUDE; + mic_ctlr->discover_params.func = micp_discover_include_func; + + err = bt_gatt_discover(conn, &mic_ctlr->discover_params); + if (err != 0) + { + LOG_DBG("Discover AICS failed (err %d)", err); + micp_mic_ctlr_discover_complete(mic_ctlr, err); + } +#else + micp_mic_ctlr_discover_complete(mic_ctlr, err); +#endif /* CONFIG_BT_MICP_MIC_CTLR_AICS */ + + return BT_GATT_ITER_STOP; + } + + LOG_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle); + + if (params->type == BT_GATT_DISCOVER_CHARACTERISTIC) + { + struct bt_gatt_chrc *chrc = (struct bt_gatt_chrc *)attr->user_data; + struct bt_gatt_subscribe_params *sub_params = NULL; + + if (bt_uuid_cmp(chrc->uuid, BT_UUID_MICS_MUTE) == 0) + { + LOG_DBG("Mute"); + mic_ctlr->mute_handle = chrc->value_handle; + sub_params = &mic_ctlr->mute_sub_params; + sub_params->disc_params = &mic_ctlr->mute_sub_disc_params; + } + + if (sub_params != NULL) + { + int err; + + sub_params->ccc_handle = BT_GATT_AUTO_DISCOVER_CCC_HANDLE; + sub_params->end_handle = mic_ctlr->end_handle; + sub_params->value = BT_GATT_CCC_NOTIFY; + sub_params->value_handle = chrc->value_handle; + sub_params->notify = mute_notify_handler; + atomic_set_bit(sub_params->flags, BT_GATT_SUBSCRIBE_FLAG_VOLATILE); + + err = bt_gatt_subscribe(conn, sub_params); + if (err == 0 || err == -EALREADY) + { + LOG_DBG("Subscribed to handle 0x%04X", attr->handle); + } + else + { + LOG_DBG("Could not subscribe to handle 0x%04X: %d", + attr->handle, + err); + + micp_mic_ctlr_discover_complete(mic_ctlr, err); + + return BT_GATT_ITER_STOP; + } + } + } + + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t primary_discover_func( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_micp_mic_ctlr *mic_ctlr = mic_ctlr_get_by_conn(conn); + + if (attr == NULL) + { + LOG_DBG("Could not find a MICS instance on the server"); + micp_mic_ctlr_discover_complete(mic_ctlr, -ENODATA); + + return BT_GATT_ITER_STOP; + } + + LOG_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle); + + if (params->type == BT_GATT_DISCOVER_PRIMARY) + { + struct bt_gatt_service_val *prim_service = + (struct bt_gatt_service_val *)attr->user_data; + int err; + + LOG_DBG("Primary discover complete"); + mic_ctlr->start_handle = attr->handle + 1; + mic_ctlr->end_handle = prim_service->end_handle; + + /* Discover characteristics */ + + mic_ctlr->discover_params.uuid = NULL; + mic_ctlr->discover_params.start_handle = mic_ctlr->start_handle; + mic_ctlr->discover_params.end_handle = mic_ctlr->end_handle; + mic_ctlr->discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; + mic_ctlr->discover_params.func = micp_discover_func; + + err = bt_gatt_discover(conn, &mic_ctlr->discover_params); + if (err != 0) + { + LOG_DBG("Discover failed (err %d)", err); + micp_mic_ctlr_discover_complete(mic_ctlr, err); + } + + return BT_GATT_ITER_STOP; + } + + return BT_GATT_ITER_CONTINUE; +} + +static void micp_mic_ctlr_reset(struct bt_micp_mic_ctlr *mic_ctlr) +{ + mic_ctlr->start_handle = 0U; + mic_ctlr->end_handle = 0U; + mic_ctlr->mute_handle = 0U; +#if defined(CONFIG_BT_MICP_MIC_CTLR_AICS) + mic_ctlr->aics_inst_cnt = 0U; +#endif /* CONFIG_BT_MICP_MIC_CTLR_AICS */ + + bt_conn_drop(&mic_ctlr->conn); +} + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + struct bt_micp_mic_ctlr *mic_ctlr; + + ARG_UNUSED(reason); + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + mic_ctlr = mic_ctlr_get_by_conn(conn); + if (mic_ctlr->conn == conn) + { + micp_mic_ctlr_reset(mic_ctlr); + } +} + +BT_CONN_CB_DEFINE(conn_callbacks) = +{ + .disconnected = disconnected, +}; + +int bt_micp_mic_ctlr_discover(struct bt_conn *conn, + struct bt_micp_mic_ctlr **mic_ctlr_out) +{ + struct bt_micp_mic_ctlr *mic_ctlr; + struct bt_conn *ref; + int err; + + /* This will initiate a discover procedure. The procedure will do the + * following sequence: 1) Primary discover for the MICS 2) Characteristic + * discover of the MICS 3) Discover services included in MICS (AICS) 4) For + * each included service found; discovery of the characteristics 5) When + * everything above have been discovered, the callback is called + */ + + if (conn == NULL) + { + LOG_DBG("NULL conn"); + return -EINVAL; + } + + mic_ctlr = mic_ctlr_get_by_conn(conn); + if (atomic_test_and_set_bit(mic_ctlr->flags, BT_MICP_MIC_CTLR_FLAG_BUSY)) + { + LOG_DBG("Instance is busy"); + + return -EBUSY; + } + + ref = bt_conn_ref(conn); + if (ref == NULL) + { + err = -ENOTCONN; + goto cleanup; + } + + (void)memset(&mic_ctlr->discover_params, 0, + sizeof(mic_ctlr->discover_params)); + micp_mic_ctlr_reset(mic_ctlr); + +#if defined(CONFIG_BT_MICP_MIC_CTLR_AICS) + static bool initialized; + + if (!initialized) + { + for (size_t i = 0U; i < ARRAY_SIZE(mic_ctlrs); i++) + { + for (size_t j = 0U; j < ARRAY_SIZE(mic_ctlr->aics); j++) + { + static struct bt_aics_cb aics_cb = + { + .state = micp_mic_ctlr_aics_state_cb, + .gain_setting = micp_mic_ctlr_aics_gain_setting_cb, + .type = micp_mic_ctlr_aics_type_cb, + .status = micp_mic_ctlr_aics_status_cb, + .description = micp_mic_ctlr_aics_description_cb, + .discover = micp_mic_ctlr_aics_discover_cb, + .set_gain = micp_mic_ctlr_aics_set_gain_cb, + .unmute = micp_mic_ctlr_aics_unmute_cb, + .mute = micp_mic_ctlr_aics_mute_cb, + .set_manual_mode = + micp_mic_ctlr_aics_set_manual_mode_cb, + .set_auto_mode = micp_mic_ctlr_aics_set_auto_mode_cb, + }; + + mic_ctlrs[i].aics[j] = bt_aics_client_free_instance_get(); + + if (mic_ctlrs[i].aics[j] == NULL) + { + micp_mic_ctlr_client_free_aics(); + bt_conn_unref(ref); + err = -ENOMEM; + goto cleanup; + } + + bt_aics_client_cb_register(mic_ctlrs[i].aics[j], &aics_cb); + } + } + + initialized = true; + } +#endif /* CONFIG_BT_MICP_MIC_CTLR_AICS */ + + mic_ctlr->discover_params.func = primary_discover_func; + mic_ctlr->discover_params.uuid = mics_uuid; + mic_ctlr->discover_params.type = BT_GATT_DISCOVER_PRIMARY; + mic_ctlr->discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; + mic_ctlr->discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + + mic_ctlr->conn = ref; + + err = bt_gatt_discover(conn, &mic_ctlr->discover_params); + if (err != 0) + { + bt_conn_unref(ref); + mic_ctlr->conn = NULL; + goto cleanup; + } + + *mic_ctlr_out = mic_ctlr; + + return 0; + +cleanup: + atomic_clear_bit(mic_ctlr->flags, BT_MICP_MIC_CTLR_FLAG_BUSY); + return err; +} + +int bt_micp_mic_ctlr_cb_register(struct bt_micp_mic_ctlr_cb *cb) +{ + struct bt_micp_mic_ctlr_cb *tmp; + + if (cb == NULL) + { + return -EINVAL; + } + + list_for_every_container(&micp_mic_ctlr_cbs, tmp, _node) + { + if (tmp == cb) + { + LOG_DBG("Already registered"); + return -EALREADY; + } + } + + list_add_tail(&micp_mic_ctlr_cbs, &cb->_node); + + return 0; +} + +#if defined(CONFIG_BT_MICP_MIC_CTLR_AICS) +int bt_micp_mic_ctlr_included_get(struct bt_micp_mic_ctlr *mic_ctlr, + struct bt_micp_included *included) +{ + if (mic_ctlr == NULL) + { + LOG_DBG("NULL mic_ctlr"); + return -EINVAL; + } + + if (included == NULL) + { + return -EINVAL; + } + + included->aics_cnt = mic_ctlr->aics_inst_cnt; + included->aics = mic_ctlr->aics; + + return 0; +} +#endif /* CONFIG_BT_MICP_MIC_CTLR_AICS */ + +struct bt_micp_mic_ctlr *bt_micp_mic_ctlr_get_by_conn( + const struct bt_conn *conn) +{ + struct bt_micp_mic_ctlr *mic_ctlr; + + if (conn == NULL) + { + LOG_DBG("NULL conn pointer"); + return NULL; + } + + mic_ctlr = mic_ctlr_get_by_conn(conn); + if (mic_ctlr->conn == NULL) + { + LOG_DBG("conn %p is not associated with microphone controller. Do " + "discovery first", + (void *)conn); + return NULL; + } + + return mic_ctlr; +} + +int bt_micp_mic_ctlr_conn_get(const struct bt_micp_mic_ctlr *mic_ctlr, + struct bt_conn **conn) +{ + if (mic_ctlr == NULL) + { + LOG_DBG("NULL mic_ctlr pointer"); + return -EINVAL; + } + + if (mic_ctlr->conn == NULL) + { + LOG_DBG("mic_ctlr pointer not associated with a connection. " + "Do discovery first"); + return -ENOTCONN; + } + + *conn = mic_ctlr->conn; + return 0; +} + +int bt_micp_mic_ctlr_mute_get(struct bt_micp_mic_ctlr *mic_ctlr) +{ + int err; + + if (mic_ctlr == NULL) + { + LOG_DBG("NULL mic_ctlr"); + return -EINVAL; + } + + if (mic_ctlr->mute_handle == 0) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + else if (atomic_test_and_set_bit(mic_ctlr->flags, + BT_MICP_MIC_CTLR_FLAG_BUSY)) + { + LOG_DBG("Instance is busy"); + + return -EBUSY; + } + + mic_ctlr->read_params.func = micp_mic_ctlr_read_mute_cb; + mic_ctlr->read_params.handle_count = 1U; + mic_ctlr->read_params.single.handle = mic_ctlr->mute_handle; + mic_ctlr->read_params.single.offset = 0U; + + err = bt_gatt_read(mic_ctlr->conn, &mic_ctlr->read_params); + if (err != 0) + { + atomic_clear_bit(mic_ctlr->flags, BT_MICP_MIC_CTLR_FLAG_BUSY); + } + + return err; +} + +static int bt_micp_mic_ctlr_write_mute(struct bt_micp_mic_ctlr *mic_ctlr, + bool mute) +{ + int err; + + if (mic_ctlr == NULL) + { + LOG_DBG("NULL mic_ctlr"); + return -EINVAL; + } + + if (mic_ctlr->mute_handle == 0) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + else if (atomic_test_and_set_bit(mic_ctlr->flags, + BT_MICP_MIC_CTLR_FLAG_BUSY)) + { + LOG_DBG("Instance is busy"); + + return -EBUSY; + } + + mic_ctlr->mute_val_buf[0] = mute; + mic_ctlr->write_params.offset = 0; + mic_ctlr->write_params.data = mic_ctlr->mute_val_buf; + mic_ctlr->write_params.length = sizeof(mute); + mic_ctlr->write_params.handle = mic_ctlr->mute_handle; + mic_ctlr->write_params.func = micp_mic_ctlr_write_mics_mute_cb; + + err = bt_gatt_write(mic_ctlr->conn, &mic_ctlr->write_params); + if (err != 0) + { + atomic_clear_bit(mic_ctlr->flags, BT_MICP_MIC_CTLR_FLAG_BUSY); + } + + return err; +} + +int bt_micp_mic_ctlr_mute(struct bt_micp_mic_ctlr *mic_ctlr) +{ + return bt_micp_mic_ctlr_write_mute(mic_ctlr, true); +} + +int bt_micp_mic_ctlr_unmute(struct bt_micp_mic_ctlr *mic_ctlr) +{ + return bt_micp_mic_ctlr_write_mute(mic_ctlr, false); +} diff --git a/libs/zblue/audio/micp_mic_dev.c b/libs/zblue/audio/micp_mic_dev.c new file mode 100644 index 0000000000000..6033128c38771 --- /dev/null +++ b/libs/zblue/audio/micp_mic_dev.c @@ -0,0 +1,327 @@ +/**************************************************************************** + * libs/zblue/audio/micp_mic_dev.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Bluetooth MICS + * + * Copyright (c) 2020 Bose Corporation Copyright (c) 2020-2022 Nordic + * Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "audio_internal.h" + +LOG_MODULE_REGISTER(bt_micp, CONFIG_BT_MICP_MIC_DEV_LOG_LEVEL); + +struct bt_micp_server +{ + uint8_t mute; + struct bt_micp_mic_dev_cb *cb; + struct bt_gatt_service *service_p; + struct bt_aics *aics_insts[CONFIG_BT_MICP_MIC_DEV_AICS_INSTANCE_COUNT]; + struct k_work_delayable notify_work; +}; + +static struct bt_micp_server micp_inst; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void mute_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); +} + +static ssize_t read_mute(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, + uint16_t len, + uint16_t offset) +{ + LOG_DBG("Mute %u", micp_inst.mute); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &micp_inst.mute, + sizeof(micp_inst.mute)); +} + +static void notify_work_handler(struct k_work *work) +{ + struct k_work_delayable *d_work = k_work_delayable_from_work(work); + struct bt_micp_server *server = CONTAINER_OF(d_work, struct bt_micp_server, + notify_work); + int err; + + err = bt_gatt_notify_uuid(NULL, BT_UUID_MICS_MUTE, + server->service_p->attrs, + &server->mute, + sizeof(server->mute)); + if (err == 0 || err == -ENOTCONN) + { + return; + } + + if (err == -ENOMEM && k_work_reschedule(d_work, + USEC2TICK(BT_AUDIO_NOTIFY_RETRY_DELAY_US)) >= 0) + { + LOG_WRN("Out of buffers for mute state notification. Will retry in " + "%dms", + BT_AUDIO_NOTIFY_RETRY_DELAY_US); + return; + } + + LOG_ERR("Mute state notification err %d", err); +} + +static ssize_t write_mute(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, + uint16_t len, + uint16_t offset, + uint8_t flags) +{ + const uint8_t *val = buf; + + ARG_UNUSED(attr); + ARG_UNUSED(flags); + + if (offset > 0) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (len != sizeof(micp_inst.mute)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + if ((conn != NULL && *val == BT_MICP_MUTE_DISABLED) || + *val > BT_MICP_MUTE_DISABLED) + { + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + + if (conn != NULL && micp_inst.mute == BT_MICP_MUTE_DISABLED) + { + return BT_GATT_ERR(BT_MICP_ERR_MUTE_DISABLED); + } + + LOG_DBG("%u", *val); + + if (*val != micp_inst.mute) + { + int err; + + micp_inst.mute = *val; + + err = k_work_reschedule(&micp_inst.notify_work, 0); + if (err < 0) + { + LOG_ERR("Failed to schedule mute state notification err %d", err); + } + + if (micp_inst.cb != NULL && micp_inst.cb->mute != NULL) + { + micp_inst.cb->mute(micp_inst.mute); + } + } + + return len; +} + +#define DUMMY_INCLUDE(i, _) BT_GATT_INCLUDE_SERVICE(NULL), +#define AICS_INCLUDES(cnt) LISTIFY(cnt, DUMMY_INCLUDE, ()) + +#define BT_MICP_SERVICE_DEFINITION \ + BT_GATT_PRIMARY_SERVICE(BT_UUID_MICS), \ + AICS_INCLUDES(CONFIG_BT_MICP_MIC_DEV_AICS_INSTANCE_COUNT) \ + BT_AUDIO_CHRC(BT_UUID_MICS_MUTE, \ + BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT, read_mute, \ + write_mute, NULL), \ + BT_AUDIO_CCC(mute_cfg_changed) + +#define MICS_ATTR_COUNT ARRAY_SIZE(((struct bt_gatt_attr[]){BT_MICP_SERVICE_DEFINITION})) +#define MICS_INCL_COUNT (CONFIG_BT_MICP_MIC_DEV_AICS_INSTANCE_COUNT) + +static struct bt_gatt_attr mics_attrs[] = +{ + BT_MICP_SERVICE_DEFINITION +}; + +static struct bt_gatt_service mics_svc; + +#if defined(CONFIG_BT_MICP_MIC_DEV_AICS) +static int prepare_aics_inst(struct bt_micp_mic_dev_register_param *param) +{ + int i; + int j; + int err; + + if (CONFIG_BT_MICP_MIC_DEV_AICS_INSTANCE_COUNT == 0) + { + return 0; + } + + for (j = 0, i = 0; i < ARRAY_SIZE(mics_attrs); i++) + { + if (bt_uuid_cmp(mics_attrs[i].uuid, BT_UUID_GATT_INCLUDE) == 0) + { + micp_inst.aics_insts[j] = bt_aics_free_instance_get(); + if (micp_inst.aics_insts[j] == NULL) + { + LOG_DBG("Could not get free AICS instances[%u]", j); + return -ENOMEM; + } + + err = bt_aics_register(micp_inst.aics_insts[j], + ¶m->aics_param[j]); + if (err != 0) + { + LOG_DBG("Could not register AICS instance[%u]: %d", j, err); + return err; + } + + mics_attrs[i].user_data = + bt_aics_svc_decl_get(micp_inst.aics_insts[j]); + j++; + + if (j == CONFIG_BT_MICP_MIC_DEV_AICS_INSTANCE_COUNT) + { + break; + } + } + } + + DEBUGASSERT(j == CONFIG_BT_MICP_MIC_DEV_AICS_INSTANCE_COUNT); + + return 0; +} +#endif /* CONFIG_BT_MICP_MIC_DEV_AICS */ + +/* Public Api */ + +int bt_micp_mic_dev_register(struct bt_micp_mic_dev_register_param *param) +{ + int err; + static bool registered; + + if (registered) + { + return -EALREADY; + } + + DEBUGASSERT(param); + +#if defined(CONFIG_BT_MICP_MIC_DEV_AICS) + err = prepare_aics_inst(param); + if (err != 0) + { + LOG_DBG("Failed to prepare AICS instances: %d", err); + + return err; + } +#endif /* CONFIG_BT_MICP_MIC_DEV_AICS */ + + mics_svc = (struct bt_gatt_service)BT_GATT_SERVICE(mics_attrs); + micp_inst.service_p = &mics_svc; + err = bt_gatt_service_register(&mics_svc); + + if (err != 0) + { + LOG_ERR("MICS service register failed: %d", err); + } + + micp_inst.cb = param->cb; + + k_work_init_delayable(&micp_inst.notify_work, notify_work_handler); + + registered = true; + + return err; +} + +int bt_micp_mic_dev_mute_disable(void) +{ + uint8_t val = BT_MICP_MUTE_DISABLED; + int err; + + err = write_mute(NULL, NULL, &val, sizeof(val), 0, 0); + + return err > 0 ? 0 : err; +} + +int bt_micp_mic_dev_included_get(struct bt_micp_included *included) +{ + if (included == NULL) + { + LOG_DBG("NULL service pointer"); + return -EINVAL; + } + +#if defined(CONFIG_BT_MICP_MIC_DEV_AICS) + included->aics_cnt = ARRAY_SIZE(micp_inst.aics_insts); + included->aics = micp_inst.aics_insts; +#endif /* CONFIG_BT_MICP_MIC_DEV_AICS */ + + return 0; +} + +int bt_micp_mic_dev_unmute(void) +{ + const uint8_t val = BT_MICP_MUTE_UNMUTED; + const int err = write_mute(NULL, NULL, &val, sizeof(val), 0, 0); + + return err > 0 ? 0 : err; +} + +int bt_micp_mic_dev_mute(void) +{ + const uint8_t val = BT_MICP_MUTE_MUTED; + const int err = write_mute(NULL, NULL, &val, sizeof(val), 0, 0); + + return err > 0 ? 0 : err; +} + +int bt_micp_mic_dev_mute_get(void) +{ + if (micp_inst.cb && micp_inst.cb->mute) + { + micp_inst.cb->mute(micp_inst.mute); + } + + return 0; +} diff --git a/libs/zblue/audio/mpl.c b/libs/zblue/audio/mpl.c new file mode 100644 index 0000000000000..f6876fb8bec95 --- /dev/null +++ b/libs/zblue/audio/mpl.c @@ -0,0 +1,3378 @@ +/**************************************************************************** + * libs/zblue/audio/mpl.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Media player skeleton implementation */ + +/* Copyright (c) 2019 - 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mcs_internal.h" +#include "media_proxy_internal.h" +#include "mpl_internal.h" + +LOG_MODULE_REGISTER(bt_mpl, CONFIG_BT_MPL_LOG_LEVEL); + +#define TRACK_STATUS_INVALID 0x00U +#define TRACK_STATUS_VALID 0x01U + +#define TRACK_POS_WORK_DELAY_MS 1000U +#define TRACK_POS_WORK_DELAY MSEC2TICK(TRACK_POS_WORK_DELAY_MS) + +#define PLAYBACK_SPEED_PARAM_DEFAULT MEDIA_PROXY_PLAYBACK_SPEED_UNITY + +/* Temporary hardcoded setup for groups, tracks and segments */ + +/* There is one parent group, which is the parent of a number of groups. */ + +/* The groups have a number of tracks. */ + +/* (There is only one level of groups, there are no groups of groups.) */ + +/* The first track of the first group has track segments, other tracks not. */ + +/* Track segments */ + +static struct mpl_tseg seg_2; +static struct mpl_tseg seg_3; + +static struct mpl_tseg seg_1 = +{ + .name_len = 5U, + .name = "Start", + .pos = 0, /* Will be updated to a value relative to the track duration */ + .prev = NULL, + .next = &seg_2, +}; + +static struct mpl_tseg seg_2 = +{ + .name_len = 6U, + .name = "Middle", + .pos = 0, /* Will be updated to a value relative to the track duration */ + .prev = &seg_1, + .next = &seg_3, +}; + +static struct mpl_tseg seg_3 = +{ + .name_len = 3U, + .name = "End", + .pos = 0, /* Will be updated to a value relative to the track duration */ + .prev = &seg_2, + .next = NULL, +}; + +static struct mpl_track track_1_2; +static struct mpl_track track_1_3; +static struct mpl_track track_2_1; +static struct mpl_track track_2_2; +static struct mpl_track track_2_3; +static struct mpl_track track_2_4; +static struct mpl_track track_2_5; + +/* Tracks */ + +static struct mpl_track track_1_1 = +{ + .title = "Track 1.1", + .duration = 30000, + .segment = &seg_1, + .prev = NULL, + .next = &track_1_2, +}; + +static struct mpl_track track_1_2 = +{ + .title = "Track 1.2", + .duration = 30000, + .segment = &seg_1, + .prev = &track_1_1, + .next = &track_1_3, +}; + +static struct mpl_track track_1_3 = +{ + .title = "Track 1.3", + .duration = 30000, + .segment = &seg_1, + .prev = &track_1_2, + .next = NULL, +}; + +static struct mpl_track track_2_1 = +{ + .title = "Interlude #1 (Song for Alison)", + .duration = 6300, + .segment = &seg_1, + .prev = NULL, + .next = &track_2_2, +}; + +static struct mpl_track track_2_2 = +{ + .title = "Interlude #2 (For Bobbye)", + .duration = 7500, + .segment = &seg_1, + .prev = &track_2_1, + .next = &track_2_3, +}; + +static struct mpl_track track_2_3 = +{ + .title = "Interlude #3 (Levanto Seventy)", + .duration = 7800, + .segment = &seg_1, + .prev = &track_2_2, + .next = &track_2_4, +}; + +static struct mpl_track track_2_4 = +{ + .title = "Interlude #4 (Vesper Dreams)", + .duration = 13500, + .segment = &seg_1, + .prev = &track_2_3, + .next = &track_2_5, +}; + +static struct mpl_track track_2_5 = +{ + .title = "Interlude #5 (Shasti)", + .duration = 7500, + .segment = &seg_1, + .prev = &track_2_4, + .next = NULL, +}; + +static struct mpl_track track_3_2; +static struct mpl_track track_3_3; + +static struct mpl_track track_3_1 = +{ + .title = "Track 3.1", + .duration = 30000, + .segment = &seg_1, + .prev = NULL, + .next = &track_3_2, +}; + +static struct mpl_track track_3_2 = +{ + .title = "Track 3.2", + .duration = 30000, + .segment = &seg_1, + .prev = &track_3_1, + .next = &track_3_3, +}; + +static struct mpl_track track_3_3 = +{ + .title = "Track 3.3", + .duration = 30000, + .segment = &seg_1, + .prev = &track_3_2, + .next = NULL, +}; + +static struct mpl_track track_4_2; + +static struct mpl_track track_4_1 = +{ + .title = "Track 4.1", + .duration = 30000, + .segment = &seg_1, + .prev = NULL, + .next = &track_4_2, +}; + +static struct mpl_track track_4_2 = +{ + .title = "Track 4.2", + .duration = 30000, + .segment = &seg_1, + .prev = &track_4_1, + .next = NULL, +}; + +/* Groups */ + +static struct mpl_group group_2; +static struct mpl_group group_3; +static struct mpl_group group_4; +static struct mpl_group group_p; + +static struct mpl_group group_1 = +{ + .title = "Group 1", + .track = &track_1_1, + .parent = &group_p, + .prev = NULL, + .next = &group_2, +}; + +static struct mpl_group group_2 = +{ + .title = "Joe Pass - Guitar Interludes", + .track = &track_2_2, + .parent = &group_p, + .prev = &group_1, + .next = &group_3, +}; + +static struct mpl_group group_3 = +{ + .title = "Group 3", + .track = &track_3_3, + .parent = &group_p, + .prev = &group_2, + .next = &group_4, +}; + +static struct mpl_group group_4 = +{ + .title = "Group 4", + .track = &track_4_2, + .parent = &group_p, + .prev = &group_3, + .next = NULL, +}; + +static struct mpl_group group_p = +{ + .title = "Parent group", + .track = &track_4_1, + .parent = &group_p, + .prev = NULL, + .next = NULL, +}; + +static struct mpl_mediaplayer media_player = +{ + .name = CONFIG_BT_MPL_MEDIA_PLAYER_NAME, + .icon_url = CONFIG_BT_MPL_ICON_URL, + .group = &group_1, + .track_pos = 0, + .state = MEDIA_PROXY_STATE_PAUSED, + .playback_speed_param = PLAYBACK_SPEED_PARAM_DEFAULT, + .seeking_speed_factor = MEDIA_PROXY_SEEKING_SPEED_FACTOR_ZERO, + .playing_order = MEDIA_PROXY_PLAYING_ORDER_INORDER_REPEAT, + .playing_orders_supported = + MEDIA_PROXY_PLAYING_ORDERS_SUPPORTED_INORDER_ONCE | + MEDIA_PROXY_PLAYING_ORDERS_SUPPORTED_INORDER_REPEAT, + .opcodes_supported = 0x001fffff, /* All opcodes */ +#ifdef CONFIG_BT_MPL_OBJECTS + .search_results_id = 0U, + .calls = + { + 0 + }, +#endif /* CONFIG_BT_MPL_OBJECTS */ + .next_track_set = false +}; + +static void set_track_position(int32_t position); +static void set_relative_track_position(int32_t rel_pos); +static void set_track_segment(struct mpl_track *track); +static void do_track_change_notifications(struct mpl_mediaplayer *pl); +static void do_group_change_notifications(struct mpl_mediaplayer *pl); + +#ifdef CONFIG_BT_MPL_OBJECTS + +/* The types of objects we keep in the Object Transfer Service */ + +enum mpl_objects +{ + MPL_OBJ_NONE = 0, + MPL_OBJ_ICON, + MPL_OBJ_TRACK_SEGMENTS, + MPL_OBJ_TRACK, + MPL_OBJ_PARENT_GROUP, + MPL_OBJ_GROUP, + MPL_OBJ_SEARCH_RESULTS, +}; + +enum mpl_obj_flag +{ + MPL_OBJ_FLAG_BUSY, + + MPL_OBJ_FLAG_NUM_FLAGS, /* keep as last */ +}; + +/* The active object */ + +/* Only a single object is selected or being added (active) at a time. */ + +/* And, except for the icon object, all objects can be created dynamically. */ + +/* So a single buffer to hold object content is sufficient. */ + +struct obj_t +{ + /* ID of the currently selected object */ + + uint64_t selected_id; + + /* Type of object being added, e.g. MPL_OBJ_ICON */ + + uint8_t add_type; + + /* Descriptor of object being added */ + + struct bt_ots_obj_created_desc *desc; + union + { + /* Pointer to track being added */ + + struct mpl_track *add_track; + + /* Pointer to group being added */ + + struct mpl_group *add_group; + }; + struct net_buf_simple *content; + + atomic_define(flags, MPL_OBJ_FLAG_NUM_FLAGS); +}; + +static struct obj_t obj = +{ + .selected_id = 0U, + .add_type = MPL_OBJ_NONE, + .add_track = NULL, + .content = NET_BUF_SIMPLE(CONFIG_BT_MPL_MAX_OBJ_SIZE), +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/* Set up content buffer for the icon object */ + +static int setup_icon_object(void) +{ + uint16_t index; + uint8_t k; + + /* The icon object is supposed to be a bitmap. */ + + /* For now, fill it with dummy data. */ + + net_buf_simple_reset(obj.content); + + /* Size may be larger than what fits in 8 bits, use 16-bit for index */ + + for (index = 0U, k = 0U; index < MIN(CONFIG_BT_MPL_MAX_OBJ_SIZE, + CONFIG_BT_MPL_ICON_BITMAP_SIZE); + index++, k++) + { + net_buf_simple_add_u8(obj.content, k); + } + + return obj.content->len; +} + +/* Set up content buffer for a track segments object */ + +static uint32_t setup_segments_object(struct mpl_track *track) +{ + struct mpl_tseg *seg = track->segment; + + net_buf_simple_reset(obj.content); + + if (seg) + { + uint32_t tot_size = 0U; + + while (seg->prev) + { + seg = seg->prev; + } + while (seg) + { + uint32_t seg_size = sizeof(seg->name_len); + + seg_size += seg->name_len; + seg_size += sizeof(seg->pos); + if (tot_size + seg_size > obj.content->size) + { + LOG_DBG("Segments object out of space"); + break; + } + + net_buf_simple_add_u8(obj.content, seg->name_len); + net_buf_simple_add_mem(obj.content, seg->name, seg->name_len); + net_buf_simple_add_le32(obj.content, seg->pos); + + tot_size += seg_size; + seg = seg->next; + } + + LOG_HEXDUMP_DBG(obj.content->data, obj.content->len, "Segments " + "Object"); + LOG_DBG("Segments object length: %d", obj.content->len); + } + else + { + LOG_ERR("No seg!"); + } + + return obj.content->len; +} + +/* Set up content buffer for a track object */ + +static uint32_t setup_track_object(void) +{ + uint16_t index; + uint8_t k; + + /* The track object is supposed to be in Id3v2 format */ + + /* For now, fill it with dummy data */ + + net_buf_simple_reset(obj.content); + + /* Size may be larger than what fits in 8 bits, use 16-bit for index */ + + for (index = 0U, k = 0U; index < MIN(CONFIG_BT_MPL_MAX_OBJ_SIZE, + CONFIG_BT_MPL_TRACK_MAX_SIZE); + index++, k++) + { + net_buf_simple_add_u8(obj.content, k); + } + + return obj.content->len; +} + +/* Set up content buffer for the parent group object */ + +static uint32_t setup_parent_group_object(struct mpl_group *group) +{ + /* This function actually does not use the parent. */ + + /* It just follows the list of groups. */ + + /* The implementation has a fixed structure, with one parent group, */ + + /* and one level of groups containing tracks only. */ + + /* The track groups have a pointer to the parent, but there is no */ + + /* pointer in the other direction, so it is not possible to go from */ + + /* the parent group to a group of tracks. */ + + uint8_t type = MEDIA_PROXY_GROUP_OBJECT_GROUP_TYPE; + uint8_t record_size = sizeof(type) + BT_OTS_OBJ_ID_SIZE; + int next_size = record_size; + + net_buf_simple_reset(obj.content); + + if (group) + { + while (group->prev) + { + group = group->prev; + } + + /* While there is a group, and the record fits in the object */ + + while (group && (next_size <= obj.content->size)) + { + net_buf_simple_add_u8(obj.content, type); + net_buf_simple_add_le48(obj.content, group->id); + group = group->next; + next_size += record_size; + } + + if (next_size > obj.content->size) + { + LOG_WRN("Not room for full group in object"); + } + + LOG_HEXDUMP_DBG(obj.content->data, obj.content->len, + "Parent Group Object"); + LOG_DBG("Group object length: %d", obj.content->len); + } + + return obj.content->len; +} + +/* Set up contents for a group object */ + +/* The group object contains a concatenated list of records, where each */ + +/* record consists of a type byte and a UUID */ + +static uint32_t setup_group_object(struct mpl_group *group) +{ + struct mpl_track *track = group->track; + uint8_t type = MEDIA_PROXY_GROUP_OBJECT_TRACK_TYPE; + uint8_t record_size = sizeof(type) + BT_OTS_OBJ_ID_SIZE; + int next_size = record_size; + + net_buf_simple_reset(obj.content); + + if (track) + { + while (track->prev) + { + track = track->prev; + } + + /* While there is a track, and the record fits in the object */ + + while (track && (next_size <= obj.content->size)) + { + net_buf_simple_add_u8(obj.content, type); + net_buf_simple_add_le48(obj.content, track->id); + track = track->next; + next_size += record_size; + } + + if (next_size > obj.content->size) + { + LOG_WRN("Not room for full group in object"); + } + + LOG_HEXDUMP_DBG(obj.content->data, obj.content->len, "Group Object"); + LOG_DBG("Group object length: %d", obj.content->len); + } + + return obj.content->len; +} + +/* Add the icon object to the OTS */ + +static int add_icon_object(void) +{ + int ret; + struct bt_ots_obj_add_param add_param = + { + }; + + struct bt_ots_obj_created_desc created_desc = + { + }; + + const struct bt_uuid *icon_type = BT_UUID_OTS_TYPE_MPL_ICON; + static char *icon_name = "Icon"; + + obj.add_type = MPL_OBJ_ICON; + obj.desc = &created_desc; + + obj.desc->size.alloc = obj.desc->size.cur = setup_icon_object(); + obj.desc->name = icon_name; + BT_OTS_OBJ_SET_PROP_READ(obj.desc->props); + + add_param.size = obj.desc->size.alloc; + add_param.type.uuid.type = BT_UUID_TYPE_16; + add_param.type.uuid_16.val = BT_UUID_16(icon_type)->val; + + ret = bt_ots_obj_add(bt_mcs_get_ots(), &add_param); + if (ret < 0) + { + LOG_WRN("Unable to add icon object, error %d", ret); + + return ret; + } + + return 0; +} + +/* Add a track segments object to the OTS */ + +static int add_current_track_segments_object(struct mpl_mediaplayer *pl) +{ + int ret; + struct bt_ots_obj_add_param add_param = + { + }; + + struct bt_ots_obj_created_desc created_desc = + { + }; + + const struct bt_uuid *segs_type = BT_UUID_OTS_TYPE_TRACK_SEGMENT; + + obj.add_type = MPL_OBJ_TRACK_SEGMENTS; + obj.desc = &created_desc; + + obj.desc->size.alloc = + obj.desc->size.cur = setup_segments_object(pl->group->track); + obj.desc->name = pl->group->track->title; + BT_OTS_OBJ_SET_PROP_READ(obj.desc->props); + + add_param.size = obj.desc->size.alloc; + add_param.type.uuid.type = BT_UUID_TYPE_16; + add_param.type.uuid_16.val = BT_UUID_16(segs_type)->val; + + ret = bt_ots_obj_add(bt_mcs_get_ots(), &add_param); + if (ret < 0) + { + LOG_WRN("Unable to add track segments object: %d", ret); + + return ret; + } + + return 0; +} + +/* Add a single track to the OTS */ + +static int add_track_object(struct mpl_track *track) +{ + struct bt_ots_obj_add_param add_param = + { + }; + + struct bt_ots_obj_created_desc created_desc = + { + }; + + const struct bt_uuid *track_type = BT_UUID_OTS_TYPE_TRACK; + int ret; + + if (!track) + { + LOG_ERR("No track"); + return -EINVAL; + } + + obj.add_type = MPL_OBJ_TRACK; + obj.add_track = track; + obj.desc = &created_desc; + + obj.desc->size.alloc = obj.desc->size.cur = setup_track_object(); + obj.desc->name = track->title; + BT_OTS_OBJ_SET_PROP_READ(obj.desc->props); + + add_param.size = obj.desc->size.alloc; + add_param.type.uuid.type = BT_UUID_TYPE_16; + add_param.type.uuid_16.val = BT_UUID_16(track_type)->val; + + ret = bt_ots_obj_add(bt_mcs_get_ots(), &add_param); + if (ret < 0) + { + LOG_WRN("Unable to add track object: %d", ret); + + return ret; + } + + return 0; +} + +/* Add the parent group to the OTS */ + +static int add_parent_group_object(struct mpl_mediaplayer *pl) +{ + int ret; + struct bt_ots_obj_add_param add_param = + { + }; + + struct bt_ots_obj_created_desc created_desc = + { + }; + + const struct bt_uuid *group_type = BT_UUID_OTS_TYPE_GROUP; + + obj.add_type = MPL_OBJ_PARENT_GROUP; + obj.desc = &created_desc; + + obj.desc->size.alloc = + obj.desc->size.cur = setup_parent_group_object(pl->group); + obj.desc->name = pl->group->parent->title; + BT_OTS_OBJ_SET_PROP_READ(obj.desc->props); + + add_param.size = obj.desc->size.alloc; + add_param.type.uuid.type = BT_UUID_TYPE_16; + add_param.type.uuid_16.val = BT_UUID_16(group_type)->val; + + ret = bt_ots_obj_add(bt_mcs_get_ots(), &add_param); + if (ret < 0) + { + LOG_WRN("Unable to add parent group object"); + + return ret; + } + + return 0; +} + +/* Add a single group to the OTS */ + +static int add_group_object(struct mpl_group *group) +{ + struct bt_ots_obj_add_param add_param = + { + }; + + struct bt_ots_obj_created_desc created_desc = + { + }; + + const struct bt_uuid *group_type = BT_UUID_OTS_TYPE_GROUP; + int ret; + + if (!group) + { + LOG_ERR("No group"); + return -EINVAL; + } + + obj.add_type = MPL_OBJ_GROUP; + obj.add_group = group; + obj.desc = &created_desc; + + obj.desc->size.alloc = obj.desc->size.cur = setup_group_object(group); + obj.desc->name = group->title; + BT_OTS_OBJ_SET_PROP_READ(obj.desc->props); + + add_param.size = obj.desc->size.alloc; + add_param.type.uuid.type = BT_UUID_TYPE_16; + add_param.type.uuid_16.val = BT_UUID_16(group_type)->val; + + ret = bt_ots_obj_add(bt_mcs_get_ots(), &add_param); + if (ret < 0) + { + LOG_WRN("Unable to add group object: %d", ret); + + return ret; + } + + return 0; +} + +/* Add all tracks of a group to the OTS */ + +static int add_group_tracks(struct mpl_group *group) +{ + int ret_overall = 0; + struct mpl_track *track = group->track; + + if (track) + { + while (track->prev) + { + track = track->prev; + } + + while (track) + { + int ret = add_track_object(track); + + if (ret && !ret_overall) + { + ret_overall = ret; + } + + track = track->next; + } + } + + return ret_overall; +} + +/* Add all groups (except the parent group) and their tracks to the OTS */ + +static int add_group_and_track_objects(struct mpl_mediaplayer *pl) +{ + int ret_overall = 0; + int ret; + struct mpl_group *group = pl->group; + + if (group) + { + while (group->prev) + { + group = group->prev; + } + + while (group) + { + ret = add_group_tracks(group); + if (ret && !ret_overall) + { + ret_overall = ret; + } + + ret = add_group_object(group); + if (ret && !ret_overall) + { + ret_overall = ret; + } + + group = group->next; + } + } + + ret = add_parent_group_object(pl); + if (ret && !ret_overall) + { + ret_overall = ret; + } + + return ret_overall; +} + +/** ** Callbacks from the object transfer service + * ***************************** + */ + +static int on_obj_deleted(struct bt_ots *ots, + struct bt_conn *conn, uint64_t id) +{ + ARG_UNUSED(ots); + ARG_UNUSED(conn); + + LOG_DBG_OBJ_ID("Object Id deleted: ", id); + + return 0; +} + +static void on_obj_selected(struct bt_ots *ots, + struct bt_conn *conn, uint64_t id) +{ + ARG_UNUSED(ots); + ARG_UNUSED(conn); + + if (atomic_test_and_set_bit(obj.flags, MPL_OBJ_FLAG_BUSY)) + { + /* TODO: Can there be a collision between select and internal */ + + /* activities, like adding new objects? */ + + LOG_ERR("Object busy - select not performed"); + return; + } + + LOG_DBG_OBJ_ID("Object Id selected: ", id); + + if (id == media_player.icon_id) + { + LOG_DBG("Icon Object ID"); + (void)setup_icon_object(); + } + else if (id == media_player.group->track->segments_id) + { + LOG_DBG("Current Track Segments Object ID"); + (void)setup_segments_object(media_player.group->track); + } + else if (id == media_player.group->track->id) + { + LOG_DBG("Current Track Object ID"); + (void)setup_track_object(); + } + else if (media_player.next_track_set && id == media_player.next.track->id) + { + /* Next track, if the next track has been explicitly set */ + + LOG_DBG("Next Track Object ID"); + (void)setup_track_object(); + } + else if (media_player.group->track->next != NULL && + id == media_player.group->track->next->id) + { + /* Next track, if next track has not been explicitly set */ + + LOG_DBG("Next Track Object ID"); + (void)setup_track_object(); + } + else if (id == media_player.group->parent->id) + { + LOG_DBG("Parent Group Object ID"); + (void)setup_parent_group_object(media_player.group); + } + else if (id == media_player.group->id) + { + LOG_DBG("Current Group Object ID"); + (void)setup_group_object(media_player.group); + } + else + { + LOG_ERR("Unknown Object ID"); + atomic_clear_bit(obj.flags, MPL_OBJ_FLAG_BUSY); + return; + } + + obj.selected_id = id; + atomic_clear_bit(obj.flags, MPL_OBJ_FLAG_BUSY); +} + +static int on_obj_created( + struct bt_ots *ots, struct bt_conn *conn, uint64_t id, + const struct bt_ots_obj_add_param *add_param, + struct bt_ots_obj_created_desc *created_desc) +{ + ARG_UNUSED(ots); + ARG_UNUSED(conn); + + /* Objects are always created locally so we do not need to check for + * MPL_OBJ_FLAG_BUSY + */ + + LOG_DBG_OBJ_ID("Object Id created: ", id); + + *created_desc = *obj.desc; + + if (!bt_uuid_cmp(&add_param->type.uuid, BT_UUID_OTS_TYPE_MPL_ICON)) + { + LOG_DBG("Icon Obj Type"); + if (obj.add_type == MPL_OBJ_ICON) + { + obj.add_type = MPL_OBJ_NONE; + media_player.icon_id = id; + } + else + { + LOG_DBG("Unexpected object creation"); + } + } + else if (!bt_uuid_cmp(&add_param->type.uuid, + BT_UUID_OTS_TYPE_TRACK_SEGMENT)) + { + LOG_DBG("Track Segments Obj Type"); + if (obj.add_type == MPL_OBJ_TRACK_SEGMENTS) + { + obj.add_type = MPL_OBJ_NONE; + media_player.group->track->segments_id = id; + } + else + { + LOG_DBG("Unexpected object creation"); + } + } + else if (!bt_uuid_cmp(&add_param->type.uuid, BT_UUID_OTS_TYPE_TRACK)) + { + LOG_DBG("Track Obj Type"); + if (obj.add_type == MPL_OBJ_TRACK) + { + obj.add_type = MPL_OBJ_NONE; + obj.add_track->id = id; + obj.add_track = NULL; + } + else + { + LOG_DBG("Unexpected object creation"); + } + } + else if (!bt_uuid_cmp(&add_param->type.uuid, BT_UUID_OTS_TYPE_GROUP)) + { + LOG_DBG("Group Obj Type"); + if (obj.add_type == MPL_OBJ_PARENT_GROUP) + { + LOG_DBG("Parent group"); + obj.add_type = MPL_OBJ_NONE; + media_player.group->parent->id = id; + } + else if (obj.add_type == MPL_OBJ_GROUP) + { + LOG_DBG("Other group"); + obj.add_type = MPL_OBJ_NONE; + obj.add_group->id = id; + obj.add_group = NULL; + } + else + { + LOG_DBG("Unexpected object creation"); + } + } + else + { + LOG_DBG("Unknown Object ID"); + } + + return 0; +} + +static ssize_t on_object_send( + struct bt_ots *ots, struct bt_conn *conn, uint64_t id, void **data, + size_t len, off_t offset) +{ + ARG_UNUSED(ots); + ARG_UNUSED(conn); + + if (atomic_test_and_set_bit(obj.flags, MPL_OBJ_FLAG_BUSY)) + { + /* TODO: Can there be a collision between select and internal */ + + /* activities, like adding new objects? */ + + LOG_ERR("Object busy"); + return -EBUSY; + } + + if (IS_ENABLED(CONFIG_BT_MPL_LOG_LEVEL_DBG)) + { + char t[BT_OTS_OBJ_ID_STR_LEN]; + + (void)bt_ots_obj_id_to_str(id, t, sizeof(t)); + LOG_DBG("Object Id %s, offset %lu, length %zu", t, (long)offset, len); + } + + if (id != obj.selected_id) + { + LOG_ERR("Read from unselected object"); + atomic_clear_bit(obj.flags, MPL_OBJ_FLAG_BUSY); + return -EINVAL; + } + + if (!data) + { + LOG_DBG("Read complete"); + atomic_clear_bit(obj.flags, MPL_OBJ_FLAG_BUSY); + return 0; + } + + if (offset >= obj.content->len) + { + LOG_DBG("Offset too large"); + atomic_clear_bit(obj.flags, MPL_OBJ_FLAG_BUSY); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_MPL_LOG_LEVEL_DBG)) + { + if (len > obj.content->len - offset) + { + LOG_DBG("Requested len too large"); + } + } + + *data = &obj.content->data[offset]; + atomic_clear_bit(obj.flags, MPL_OBJ_FLAG_BUSY); + + return MIN(len, obj.content->len - offset); +} + +static struct bt_ots_cb ots_cbs = +{ + .obj_created = on_obj_created, + .obj_read = on_object_send, + .obj_selected = on_obj_selected, + .obj_deleted = on_obj_deleted, +}; + +#endif /* CONFIG_BT_MPL_OBJECTS */ + +/* TODO: It must be possible to replace the do_prev_segment(), do_prev_track + * and do_prev_group() with a generic do_prev() command that can be used at + * all levels. Similarly for do_next, do_prev, and so on. + */ + +static void do_prev_segment(struct mpl_mediaplayer *pl) +{ + LOG_DBG("Segment name before: %s", pl->group->track->segment->name); + + if (pl->group->track->segment->prev != NULL) + { + pl->group->track->segment = pl->group->track->segment->prev; + } + + LOG_DBG("Segment name after: %s", pl->group->track->segment->name); +} + +static void set_current_track(struct mpl_mediaplayer *pl, + struct mpl_track *track) +{ + pl->group->track = track; + pl->track_pos = 0; + set_track_segment(track); + do_track_change_notifications(pl); +} + +static void do_next_segment(struct mpl_mediaplayer *pl) +{ + LOG_DBG("Segment name before: %s", pl->group->track->segment->name); + + if (pl->group->track->segment->next != NULL) + { + pl->group->track->segment = pl->group->track->segment->next; + } + + LOG_DBG("Segment name after: %s", pl->group->track->segment->name); +} + +static void do_first_segment(struct mpl_mediaplayer *pl) +{ + LOG_DBG("Segment name before: %s", pl->group->track->segment->name); + + while (pl->group->track->segment->prev != NULL) + { + pl->group->track->segment = pl->group->track->segment->prev; + } + + LOG_DBG("Segment name after: %s", pl->group->track->segment->name); +} + +static void do_last_segment(struct mpl_mediaplayer *pl) +{ + LOG_DBG("Segment name before: %s", pl->group->track->segment->name); + + while (pl->group->track->segment->next != NULL) + { + pl->group->track->segment = pl->group->track->segment->next; + } + + LOG_DBG("Segment name after: %s", pl->group->track->segment->name); +} + +static void do_goto_segment(struct mpl_mediaplayer *pl, int32_t segnum) +{ + int32_t k; + + LOG_DBG("Segment name before: %s", pl->group->track->segment->name); + + if (segnum > 0) + { + /* Goto first segment */ + + while (pl->group->track->segment->prev != NULL) + { + pl->group->track->segment = pl->group->track->segment->prev; + } + + /* Then go segnum - 1 tracks forward */ + + for (k = 0; k < (segnum - 1); k++) + { + if (pl->group->track->segment->next != NULL) + { + pl->group->track->segment = pl->group->track->segment->next; + } + } + } + else if (segnum < 0) + { + /* Goto last track */ + + while (pl->group->track->segment->next != NULL) + { + pl->group->track->segment = pl->group->track->segment->next; + } + + /* Then go |segnum - 1| tracks back */ + + for (k = 0; k < (-segnum - 1); k++) + { + if (pl->group->track->segment->prev != NULL) + { + pl->group->track->segment = pl->group->track->segment->prev; + } + } + } + + LOG_DBG("Segment name after: %s", pl->group->track->segment->name); + + set_track_position(pl->group->track->segment->pos); +} + +static void do_prev_track(struct mpl_mediaplayer *pl) +{ +#ifdef CONFIG_BT_MPL_OBJECTS + LOG_DBG_OBJ_ID("Track ID before: ", pl->group->track->id); +#endif /* CONFIG_BT_MPL_OBJECTS */ + + if (pl->group->track->prev != NULL) + { + set_current_track(pl, pl->group->track->prev); + } + else + { + /* For previous track, the position is reset to 0 */ + + /* even if we stay at the same track (goto start of */ + + /* track) */ + + set_track_position(0); + } + +#ifdef CONFIG_BT_MPL_OBJECTS + LOG_DBG_OBJ_ID("Track ID after: ", pl->group->track->id); +#endif /* CONFIG_BT_MPL_OBJECTS */ +} + +/* Change to next track according to the current track's next track */ + +static void do_next_track_normal_order(struct mpl_mediaplayer *pl) +{ +#ifdef CONFIG_BT_MPL_OBJECTS + LOG_DBG_OBJ_ID("Track ID before: ", pl->group->track->id); +#endif /* CONFIG_BT_MPL_OBJECTS */ + + if (pl->group->track->next != NULL) + { + set_current_track(pl, pl->group->track->next); + } + +#ifdef CONFIG_BT_MPL_OBJECTS + LOG_DBG_OBJ_ID("Track ID after: ", pl->group->track->id); +#endif /* CONFIG_BT_MPL_OBJECTS */ +} + +/* Change to next track when the next track has been explicitly set + * + * ALWAYS changes the track, changes the group if required Resets the + * next_track_set and the "next" pointers + * + * Returns true if the _group_ has been changed, otherwise false + */ + +static void do_next_track_next_track_set(struct mpl_mediaplayer *pl) +{ + if (pl->next.group != pl->group) + { + pl->group = pl->next.group; + do_group_change_notifications(pl); + } + + set_current_track(pl, pl->next.track); + pl->next.track = NULL; + pl->next.group = NULL; + pl->next_track_set = false; +} + +static void do_next_track(struct mpl_mediaplayer *pl) +{ + if (pl->next_track_set) + { + LOG_DBG("Next track set"); + do_next_track_next_track_set(pl); + } + else + { + do_next_track_normal_order(pl); + } +} + +static void do_first_track(struct mpl_mediaplayer *pl, bool group_change) +{ + bool track_changed = false; + +#ifdef CONFIG_BT_MPL_OBJECTS + LOG_DBG_OBJ_ID("Track ID before: ", pl->group->track->id); +#endif /* CONFIG_BT_MPL_OBJECTS */ + + /* Set first track */ + + while (pl->group->track->prev != NULL) + { + pl->group->track = pl->group->track->prev; + track_changed = true; + } + + /* Notify about new track */ + + if (group_change || track_changed) + { + set_current_track(pl, pl->group->track); + } + else + { + /* For first track, the position is reset to 0 even */ + + /* if we stay at the same track (goto start of track) */ + + set_track_position(0); + } + +#ifdef CONFIG_BT_MPL_OBJECTS + LOG_DBG_OBJ_ID("Track ID after: ", pl->group->track->id); +#endif /* CONFIG_BT_MPL_OBJECTS */ +} + +static void do_last_track(struct mpl_mediaplayer *pl) +{ +#ifdef CONFIG_BT_MPL_OBJECTS + LOG_DBG_OBJ_ID("Track ID before: ", pl->group->track->id); +#endif /* CONFIG_BT_MPL_OBJECTS */ + + if (pl->group->track->next != NULL) + { + pl->group->track = pl->group->track->next; + while (pl->group->track->next != NULL) + { + pl->group->track = pl->group->track->next; + } + + set_current_track(pl, pl->group->track); + } + else + { + /* For last track, the position is reset to 0 even */ + + /* if we stay at the same track (goto start of track) */ + + set_track_position(0); + } + +#ifdef CONFIG_BT_MPL_OBJECTS + LOG_DBG_OBJ_ID("Track ID after: ", pl->group->track->id); +#endif /* CONFIG_BT_MPL_OBJECTS */ +} + +static void do_goto_track(struct mpl_mediaplayer *pl, int32_t tracknum) +{ + int32_t count = 0; + int32_t k; + +#ifdef CONFIG_BT_MPL_OBJECTS + LOG_DBG_OBJ_ID("Track ID before: ", pl->group->track->id); +#endif /* CONFIG_BT_MPL_OBJECTS */ + + if (tracknum > 0) + { + /* Goto first track */ + + while (pl->group->track->prev != NULL) + { + pl->group->track = pl->group->track->prev; + count--; + } + + /* Then go tracknum - 1 tracks forward */ + + for (k = 0; k < (tracknum - 1); k++) + { + if (pl->group->track->next != NULL) + { + pl->group->track = pl->group->track->next; + count++; + } + } + } + else if (tracknum < 0) + { + /* Goto last track */ + + while (pl->group->track->next != NULL) + { + pl->group->track = pl->group->track->next; + count++; + } + + /* Then go |tracknum - 1| tracks back */ + + for (k = 0; k < (-tracknum - 1); k++) + { + if (pl->group->track->prev != NULL) + { + pl->group->track = pl->group->track->prev; + count--; + } + } + } + +#ifdef CONFIG_BT_MPL_OBJECTS + LOG_DBG_OBJ_ID("Track ID after: ", pl->group->track->id); +#endif /* CONFIG_BT_MPL_OBJECTS */ + + /* The track has changed if we have moved more in one direction */ + + /* than in the other */ + + if (count != 0) + { + set_current_track(&media_player, pl->group->track); + } + else + { + /* For goto track, the position is reset to 0 */ + + /* even if we stay at the same track (goto */ + + /* start of track) */ + + set_track_position(0); + } +} + +static void do_prev_group(struct mpl_mediaplayer *pl) +{ +#ifdef CONFIG_BT_MPL_OBJECTS + LOG_DBG_OBJ_ID("Group ID before: ", pl->group->id); +#endif /* CONFIG_BT_MPL_OBJECTS */ + + if (pl->group->prev != NULL) + { + pl->group = pl->group->prev; + do_group_change_notifications(pl); + } + +#ifdef CONFIG_BT_MPL_OBJECTS + LOG_DBG_OBJ_ID("Group ID after: ", pl->group->id); +#endif /* CONFIG_BT_MPL_OBJECTS */ +} + +static void do_next_group(struct mpl_mediaplayer *pl) +{ +#ifdef CONFIG_BT_MPL_OBJECTS + LOG_DBG_OBJ_ID("Group ID before: ", pl->group->id); +#endif /* CONFIG_BT_MPL_OBJECTS */ + + if (pl->group->next != NULL) + { + pl->group = pl->group->next; + do_group_change_notifications(pl); + } + +#ifdef CONFIG_BT_MPL_OBJECTS + LOG_DBG_OBJ_ID("Group ID after: ", pl->group->id); +#endif /* CONFIG_BT_MPL_OBJECTS */ +} + +static void do_first_group(struct mpl_mediaplayer *pl) +{ +#ifdef CONFIG_BT_MPL_OBJECTS + LOG_DBG_OBJ_ID("Group ID before: ", pl->group->id); +#endif /* CONFIG_BT_MPL_OBJECTS */ + + if (pl->group->prev != NULL) + { + pl->group = pl->group->prev; + do_group_change_notifications(pl); + } + + while (pl->group->prev != NULL) + { + pl->group = pl->group->prev; + } + +#ifdef CONFIG_BT_MPL_OBJECTS + LOG_DBG_OBJ_ID("Group ID after: ", pl->group->id); +#endif /* CONFIG_BT_MPL_OBJECTS */ +} + +static void do_last_group(struct mpl_mediaplayer *pl) +{ +#ifdef CONFIG_BT_MPL_OBJECTS + LOG_DBG_OBJ_ID("Group ID before: ", pl->group->id); +#endif /* CONFIG_BT_MPL_OBJECTS */ + + if (pl->group->next != NULL) + { + pl->group = pl->group->next; + do_group_change_notifications(pl); + } + + while (pl->group->next != NULL) + { + pl->group = pl->group->next; + } + +#ifdef CONFIG_BT_MPL_OBJECTS + LOG_DBG_OBJ_ID("Group ID after: ", pl->group->id); +#endif /* CONFIG_BT_MPL_OBJECTS */ +} + +static void do_goto_group(struct mpl_mediaplayer *pl, int32_t groupnum) +{ + int32_t count = 0; + int32_t k; + +#ifdef CONFIG_BT_MPL_OBJECTS + LOG_DBG_OBJ_ID("Group ID before: ", pl->group->id); +#endif /* CONFIG_BT_MPL_OBJECTS */ + + if (groupnum > 0) + { + /* Goto first group */ + + while (pl->group->prev != NULL) + { + pl->group = pl->group->prev; + count--; + } + + /* Then go groupnum - 1 groups forward */ + + for (k = 0; k < (groupnum - 1); k++) + { + if (pl->group->next != NULL) + { + pl->group = pl->group->next; + count++; + } + } + } + else if (groupnum < 0) + { + /* Goto last group */ + + while (pl->group->next != NULL) + { + pl->group = pl->group->next; + count++; + } + + /* Then go |groupnum - 1| groups back */ + + for (k = 0; k < (-groupnum - 1); k++) + { + if (pl->group->prev != NULL) + { + pl->group = pl->group->prev; + count--; + } + } + } + +#ifdef CONFIG_BT_MPL_OBJECTS + LOG_DBG_OBJ_ID("Group ID after: ", pl->group->id); +#endif /* CONFIG_BT_MPL_OBJECTS */ + + /* The group has changed if we have moved more in one direction */ + + /* than in the other */ + + if (count != 0) + { + do_group_change_notifications(pl); + } +} + +static void do_track_change_notifications(struct mpl_mediaplayer *pl) +{ + media_proxy_pl_track_changed_cb(); + media_proxy_pl_track_title_cb(pl->group->track->title); + media_proxy_pl_track_duration_cb(pl->group->track->duration); + media_proxy_pl_track_position_cb(pl->track_pos); +#ifdef CONFIG_BT_MPL_OBJECTS + media_proxy_pl_current_track_id_cb(pl->group->track->id); + if (pl->group->track->next) + { + media_proxy_pl_next_track_id_cb(pl->group->track->next->id); + } + else + { + /* Send a zero value to indicate that there is no next track */ + + media_proxy_pl_next_track_id_cb(MPL_NO_TRACK_ID); + } +#endif /* CONFIG_BT_MPL_OBJECTS */ +} + +static void do_group_change_notifications(struct mpl_mediaplayer *pl) +{ +#ifdef CONFIG_BT_MPL_OBJECTS + media_proxy_pl_current_group_id_cb(pl->group->id); +#endif /* CONFIG_BT_MPL_OBJECTS */ +} + +static void do_full_prev_group(struct mpl_mediaplayer *pl) +{ + /* Change the group (if not already on first group) */ + + do_prev_group(pl); + + /* Whether there is a group change or not, we always go to the first track + */ + + do_first_track(pl, true); +} + +static void do_full_next_group(struct mpl_mediaplayer *pl) +{ + /* Change the group (if not already on last group) */ + + do_next_group(pl); + + /* Whether there is a group change or not, we always go to the first track + */ + + do_first_track(pl, true); +} + +static void do_full_first_group(struct mpl_mediaplayer *pl) +{ + /* Change the group (if not already on first group) */ + + do_first_group(pl); + + /* Whether there is a group change or not, we always go to the first track + */ + + do_first_track(pl, true); +} + +static void do_full_last_group(struct mpl_mediaplayer *pl) +{ + /* Change the group (if not already on last group) */ + + do_last_group(pl); + + /* Whether there is a group change or not, we always go to the first track + */ + + do_first_track(pl, true); +} + +static void do_full_goto_group(struct mpl_mediaplayer *pl, int32_t groupnum) +{ + /* Change the group (if not already on given group) */ + + do_goto_group(pl, groupnum); + + /* Whether there is a group change or not, we always go to the first track + */ + + do_first_track(pl, true); +} + +static void mpl_set_state(uint8_t state) +{ + switch (state) + { + case MEDIA_PROXY_STATE_INACTIVE: + case MEDIA_PROXY_STATE_PLAYING: + case MEDIA_PROXY_STATE_PAUSED: + (void)k_work_cancel_delayable(&media_player.pos_work); + break; + case MEDIA_PROXY_STATE_SEEKING: + (void)k_work_schedule(&media_player.pos_work, TRACK_POS_WORK_DELAY); + break; + default: + DEBUGASSERT(false); + } + + media_player.state = state; + media_proxy_pl_media_state_cb(media_player.state); +} + +/* Command handlers (state machines) */ + +static uint8_t inactive_state_command_handler(const struct mpl_cmd *command) +{ + uint8_t result_code = MEDIA_PROXY_CMD_SUCCESS; + + LOG_DBG("Command opcode: %d", command->opcode); + if (IS_ENABLED(CONFIG_BT_MPL_LOG_LEVEL_DBG)) + { + if (command->use_param) + { + LOG_DBG("Command parameter: %d", command->param); + } + } + + switch (command->opcode) + { + /* Fall-through - handle several cases identically */ + + case MEDIA_PROXY_OP_PLAY: + case MEDIA_PROXY_OP_PAUSE: + case MEDIA_PROXY_OP_FAST_REWIND: + case MEDIA_PROXY_OP_FAST_FORWARD: + case MEDIA_PROXY_OP_STOP: + case MEDIA_PROXY_OP_MOVE_RELATIVE: + case MEDIA_PROXY_OP_PREV_TRACK: + case MEDIA_PROXY_OP_NEXT_TRACK: + case MEDIA_PROXY_OP_PREV_SEGMENT: + case MEDIA_PROXY_OP_NEXT_SEGMENT: + case MEDIA_PROXY_OP_FIRST_SEGMENT: + case MEDIA_PROXY_OP_LAST_SEGMENT: + case MEDIA_PROXY_OP_GOTO_SEGMENT: + result_code = MEDIA_PROXY_CMD_PLAYER_INACTIVE; + break; + case MEDIA_PROXY_OP_FIRST_TRACK: + do_first_track(&media_player, false); + mpl_set_state(MEDIA_PROXY_STATE_PAUSED); + break; + case MEDIA_PROXY_OP_LAST_TRACK: + do_last_track(&media_player); + mpl_set_state(MEDIA_PROXY_STATE_PAUSED); + break; + case MEDIA_PROXY_OP_GOTO_TRACK: + if (command->use_param) + { + do_goto_track(&media_player, command->param); + mpl_set_state(MEDIA_PROXY_STATE_PAUSED); + } + else + { + result_code = MEDIA_PROXY_CMD_CANNOT_BE_COMPLETED; + } + break; + case MEDIA_PROXY_OP_PREV_GROUP: + do_full_prev_group(&media_player); + mpl_set_state(MEDIA_PROXY_STATE_PAUSED); + break; + case MEDIA_PROXY_OP_NEXT_GROUP: + do_full_next_group(&media_player); + mpl_set_state(MEDIA_PROXY_STATE_PAUSED); + break; + case MEDIA_PROXY_OP_FIRST_GROUP: + do_full_first_group(&media_player); + mpl_set_state(MEDIA_PROXY_STATE_PAUSED); + break; + case MEDIA_PROXY_OP_LAST_GROUP: + do_full_last_group(&media_player); + mpl_set_state(MEDIA_PROXY_STATE_PAUSED); + break; + case MEDIA_PROXY_OP_GOTO_GROUP: + if (command->use_param) + { + do_full_goto_group(&media_player, command->param); + mpl_set_state(MEDIA_PROXY_STATE_PAUSED); + } + else + { + result_code = MEDIA_PROXY_CMD_CANNOT_BE_COMPLETED; + } + + break; + default: + LOG_DBG("Invalid command: %d", command->opcode); + result_code = MEDIA_PROXY_CMD_NOT_SUPPORTED; + break; + } + + return result_code; +} + +static uint8_t playing_state_command_handler(const struct mpl_cmd *command) +{ + uint8_t result_code = MEDIA_PROXY_CMD_SUCCESS; + + LOG_DBG("Command opcode: %d", command->opcode); + if (IS_ENABLED(CONFIG_BT_MPL_LOG_LEVEL_DBG)) + { + if (command->use_param) + { + LOG_DBG("Command parameter: %d", command->param); + } + } + + switch (command->opcode) + { + case MEDIA_PROXY_OP_PLAY: + + /* Continue playing - i.e. do nothing */ + + break; + case MEDIA_PROXY_OP_PAUSE: + mpl_set_state(MEDIA_PROXY_STATE_PAUSED); + break; + case MEDIA_PROXY_OP_FAST_REWIND: + + /* We're in playing state, seeking speed must have been zero */ + + media_player.seeking_speed_factor = -MPL_SEEKING_SPEED_FACTOR_STEP; + mpl_set_state(MEDIA_PROXY_STATE_SEEKING); + media_proxy_pl_seeking_speed_cb(media_player.seeking_speed_factor); + break; + case MEDIA_PROXY_OP_FAST_FORWARD: + + /* We're in playing state, seeking speed must have been zero */ + + media_player.seeking_speed_factor = MPL_SEEKING_SPEED_FACTOR_STEP; + mpl_set_state(MEDIA_PROXY_STATE_SEEKING); + media_proxy_pl_seeking_speed_cb(media_player.seeking_speed_factor); + break; + case MEDIA_PROXY_OP_STOP: + set_track_position(0); + mpl_set_state(MEDIA_PROXY_STATE_PAUSED); + break; + case MEDIA_PROXY_OP_MOVE_RELATIVE: + if (command->use_param) + { + set_relative_track_position(command->param); + } + else + { + result_code = MEDIA_PROXY_CMD_CANNOT_BE_COMPLETED; + } + + break; + case MEDIA_PROXY_OP_PREV_SEGMENT: + + /* Switch to previous segment if we are less than */ + + /* into the segment, otherwise go to start of segment */ + + if (media_player.track_pos - + PREV_MARGIN < media_player.group->track->segment->pos) + { + do_prev_segment(&media_player); + } + + set_track_position(media_player.group->track->segment->pos); + break; + case MEDIA_PROXY_OP_NEXT_SEGMENT: + do_next_segment(&media_player); + set_track_position(media_player.group->track->segment->pos); + break; + case MEDIA_PROXY_OP_FIRST_SEGMENT: + do_first_segment(&media_player); + set_track_position(media_player.group->track->segment->pos); + break; + case MEDIA_PROXY_OP_LAST_SEGMENT: + do_last_segment(&media_player); + set_track_position(media_player.group->track->segment->pos); + break; + case MEDIA_PROXY_OP_GOTO_SEGMENT: + if (command->use_param) + { + if (command->param != 0) + { + do_goto_segment(&media_player, command->param); + } + + /* If the argument to "goto segment" is zero, */ + + /* the segment shall stay the same, and the */ + + /* track position shall not change. */ + } + else + { + result_code = MEDIA_PROXY_CMD_CANNOT_BE_COMPLETED; + } + + break; + case MEDIA_PROXY_OP_PREV_TRACK: + do_prev_track(&media_player); + break; + case MEDIA_PROXY_OP_NEXT_TRACK: + do_next_track(&media_player); + break; + case MEDIA_PROXY_OP_FIRST_TRACK: + do_first_track(&media_player, false); + break; + case MEDIA_PROXY_OP_LAST_TRACK: + do_last_track(&media_player); + break; + case MEDIA_PROXY_OP_GOTO_TRACK: + if (command->use_param) + { + do_goto_track(&media_player, command->param); + } + else + { + result_code = MEDIA_PROXY_CMD_CANNOT_BE_COMPLETED; + } + + break; + case MEDIA_PROXY_OP_PREV_GROUP: + do_full_prev_group(&media_player); + break; + case MEDIA_PROXY_OP_NEXT_GROUP: + do_full_next_group(&media_player); + break; + case MEDIA_PROXY_OP_FIRST_GROUP: + do_full_first_group(&media_player); + break; + case MEDIA_PROXY_OP_LAST_GROUP: + do_full_last_group(&media_player); + break; + case MEDIA_PROXY_OP_GOTO_GROUP: + if (command->use_param) + { + do_full_goto_group(&media_player, command->param); + } + else + { + result_code = MEDIA_PROXY_CMD_CANNOT_BE_COMPLETED; + } + break; + default: + LOG_DBG("Invalid command: %d", command->opcode); + result_code = MEDIA_PROXY_CMD_NOT_SUPPORTED; + break; + } + + return result_code; +} + +static uint8_t paused_state_command_handler(const struct mpl_cmd *command) +{ + uint8_t result_code = MEDIA_PROXY_CMD_SUCCESS; + + LOG_DBG("Command opcode: %d", command->opcode); + if (IS_ENABLED(CONFIG_BT_MPL_LOG_LEVEL_DBG)) + { + if (command->use_param) + { + LOG_DBG("Command parameter: %d", command->param); + } + } + + switch (command->opcode) + { + case MEDIA_PROXY_OP_PLAY: + mpl_set_state(MEDIA_PROXY_STATE_PLAYING); + break; + case MEDIA_PROXY_OP_PAUSE: + + /* No change */ + + break; + case MEDIA_PROXY_OP_FAST_REWIND: + + /* We're in paused state, seeking speed must have been zero */ + + media_player.seeking_speed_factor = -MPL_SEEKING_SPEED_FACTOR_STEP; + mpl_set_state(MEDIA_PROXY_STATE_SEEKING); + media_proxy_pl_seeking_speed_cb(media_player.seeking_speed_factor); + break; + case MEDIA_PROXY_OP_FAST_FORWARD: + + /* We're in paused state, seeking speed must have been zero */ + + media_player.seeking_speed_factor = MPL_SEEKING_SPEED_FACTOR_STEP; + mpl_set_state(MEDIA_PROXY_STATE_SEEKING); + media_proxy_pl_seeking_speed_cb(media_player.seeking_speed_factor); + break; + case MEDIA_PROXY_OP_STOP: + set_track_position(0); + mpl_set_state(MEDIA_PROXY_STATE_PAUSED); + break; + case MEDIA_PROXY_OP_MOVE_RELATIVE: + if (command->use_param) + { + set_relative_track_position(command->param); + } + else + { + result_code = MEDIA_PROXY_CMD_CANNOT_BE_COMPLETED; + } + + break; + case MEDIA_PROXY_OP_PREV_SEGMENT: + + /* Switch to previous segment if we are less than 5 seconds */ + + /* into the segment, otherwise go to start of segment */ + + if (media_player.group->track->segment != NULL) + { + if (media_player.track_pos - + PREV_MARGIN < media_player.group->track->segment->pos) + { + do_prev_segment(&media_player); + } + + set_track_position(media_player.group->track->segment->pos); + } + else + { + result_code = MEDIA_PROXY_CMD_CANNOT_BE_COMPLETED; + } + + break; + case MEDIA_PROXY_OP_NEXT_SEGMENT: + if (media_player.group->track->segment != NULL) + { + do_next_segment(&media_player); + set_track_position(media_player.group->track->segment->pos); + } + else + { + result_code = MEDIA_PROXY_CMD_CANNOT_BE_COMPLETED; + } + + break; + case MEDIA_PROXY_OP_FIRST_SEGMENT: + if (media_player.group->track->segment != NULL) + { + do_first_segment(&media_player); + set_track_position(media_player.group->track->segment->pos); + } + else + { + result_code = MEDIA_PROXY_CMD_CANNOT_BE_COMPLETED; + } + + break; + case MEDIA_PROXY_OP_LAST_SEGMENT: + if (media_player.group->track->segment != NULL) + { + do_last_segment(&media_player); + set_track_position(media_player.group->track->segment->pos); + } + else + { + result_code = MEDIA_PROXY_CMD_CANNOT_BE_COMPLETED; + } + + break; + case MEDIA_PROXY_OP_GOTO_SEGMENT: + if (command->use_param && media_player.group->track->segment != NULL) + { + if (command->param != 0) + { + do_goto_segment(&media_player, command->param); + } + + /* If the argument to "goto segment" is zero, */ + + /* the segment shall stay the same, and the */ + + /* track position shall not change. */ + } + else + { + result_code = MEDIA_PROXY_CMD_CANNOT_BE_COMPLETED; + } + + break; + case MEDIA_PROXY_OP_PREV_TRACK: + do_prev_track(&media_player); + break; + case MEDIA_PROXY_OP_NEXT_TRACK: + do_next_track(&media_player); + + /* For next track, the position is kept if the track */ + + /* does not change */ + + break; + case MEDIA_PROXY_OP_FIRST_TRACK: + do_first_track(&media_player, false); + break; + case MEDIA_PROXY_OP_LAST_TRACK: + do_last_track(&media_player); + break; + case MEDIA_PROXY_OP_GOTO_TRACK: + if (command->use_param) + { + do_goto_track(&media_player, command->param); + } + else + { + result_code = MEDIA_PROXY_CMD_CANNOT_BE_COMPLETED; + } + + break; + case MEDIA_PROXY_OP_PREV_GROUP: + do_full_prev_group(&media_player); + break; + case MEDIA_PROXY_OP_NEXT_GROUP: + do_full_next_group(&media_player); + break; + case MEDIA_PROXY_OP_FIRST_GROUP: + do_full_first_group(&media_player); + break; + case MEDIA_PROXY_OP_LAST_GROUP: + do_full_last_group(&media_player); + break; + case MEDIA_PROXY_OP_GOTO_GROUP: + if (command->use_param) + { + do_full_goto_group(&media_player, command->param); + } + else + { + result_code = MEDIA_PROXY_CMD_CANNOT_BE_COMPLETED; + } + + break; + default: + LOG_DBG("Invalid command: %d", command->opcode); + result_code = MEDIA_PROXY_CMD_NOT_SUPPORTED; + break; + } + + return result_code; +} + +static uint8_t seeking_state_command_handler(const struct mpl_cmd *command) +{ + uint8_t result_code = MEDIA_PROXY_CMD_SUCCESS; + + LOG_DBG("Command opcode: %d", command->opcode); + if (IS_ENABLED(CONFIG_BT_MPL_LOG_LEVEL_DBG)) + { + if (command->use_param) + { + LOG_DBG("Command parameter: %d", command->param); + } + } + + switch (command->opcode) + { + case MEDIA_PROXY_OP_PLAY: + media_player.seeking_speed_factor = + MEDIA_PROXY_SEEKING_SPEED_FACTOR_ZERO; + mpl_set_state(MEDIA_PROXY_STATE_PLAYING); + media_proxy_pl_seeking_speed_cb(media_player.seeking_speed_factor); + break; + case MEDIA_PROXY_OP_PAUSE: + media_player.seeking_speed_factor = + MEDIA_PROXY_SEEKING_SPEED_FACTOR_ZERO; + + /* TODO: Set track and track position */ + + mpl_set_state(MEDIA_PROXY_STATE_PAUSED); + media_proxy_pl_seeking_speed_cb(media_player.seeking_speed_factor); + break; + case MEDIA_PROXY_OP_FAST_REWIND: + + /* TODO: Here, and for FAST_FORWARD */ + + /* Decide on algorithm for multiple presses - add step (as */ + + /* now) or double/half? */ + + /* What about FR followed by FF? */ + + /* Currently, the seeking speed may also become zero */ + + /* Lowest value allowed by spec is -64, notify on change only */ + + if (media_player.seeking_speed_factor >= + -(MEDIA_PROXY_SEEKING_SPEED_FACTOR_MAX - + MPL_SEEKING_SPEED_FACTOR_STEP)) + { + media_player.seeking_speed_factor -= + MPL_SEEKING_SPEED_FACTOR_STEP; + media_proxy_pl_seeking_speed_cb( + media_player.seeking_speed_factor); + } + break; + case MEDIA_PROXY_OP_FAST_FORWARD: + + /* Highest value allowed by spec is 64, notify on change only */ + + if (media_player.seeking_speed_factor <= + (MEDIA_PROXY_SEEKING_SPEED_FACTOR_MAX - + MPL_SEEKING_SPEED_FACTOR_STEP)) + { + media_player.seeking_speed_factor += + MPL_SEEKING_SPEED_FACTOR_STEP; + media_proxy_pl_seeking_speed_cb( + media_player.seeking_speed_factor); + } + break; + case MEDIA_PROXY_OP_STOP: + media_player.seeking_speed_factor = + MEDIA_PROXY_SEEKING_SPEED_FACTOR_ZERO; + set_track_position(0); + mpl_set_state(MEDIA_PROXY_STATE_PAUSED); + media_proxy_pl_seeking_speed_cb(media_player.seeking_speed_factor); + break; + case MEDIA_PROXY_OP_MOVE_RELATIVE: + if (command->use_param) + { + set_relative_track_position(command->param); + } + else + { + result_code = MEDIA_PROXY_CMD_CANNOT_BE_COMPLETED; + } + + break; + case MEDIA_PROXY_OP_PREV_SEGMENT: + + /* Switch to previous segment if we are less than 5 seconds */ + + /* into the segment, otherwise go to start of segment */ + + if (media_player.track_pos - + PREV_MARGIN < media_player.group->track->segment->pos) + { + do_prev_segment(&media_player); + } + + set_track_position(media_player.group->track->segment->pos); + break; + case MEDIA_PROXY_OP_NEXT_SEGMENT: + do_next_segment(&media_player); + set_track_position(media_player.group->track->segment->pos); + break; + case MEDIA_PROXY_OP_FIRST_SEGMENT: + do_first_segment(&media_player); + set_track_position(media_player.group->track->segment->pos); + break; + case MEDIA_PROXY_OP_LAST_SEGMENT: + do_last_segment(&media_player); + set_track_position(media_player.group->track->segment->pos); + break; + case MEDIA_PROXY_OP_GOTO_SEGMENT: + if (command->use_param) + { + if (command->param != 0) + { + do_goto_segment(&media_player, command->param); + } + + /* If the argument to "goto segment" is zero, */ + + /* the segment shall stay the same, and the */ + + /* track position shall not change. */ + } + else + { + result_code = MEDIA_PROXY_CMD_CANNOT_BE_COMPLETED; + } + break; + case MEDIA_PROXY_OP_PREV_TRACK: + do_prev_track(&media_player); + media_player.seeking_speed_factor = + MEDIA_PROXY_SEEKING_SPEED_FACTOR_ZERO; + mpl_set_state(MEDIA_PROXY_STATE_PAUSED); + break; + case MEDIA_PROXY_OP_NEXT_TRACK: + do_next_track(&media_player); + + /* For next track, the position is kept if the track */ + + /* does not change */ + + media_player.seeking_speed_factor = + MEDIA_PROXY_SEEKING_SPEED_FACTOR_ZERO; + mpl_set_state(MEDIA_PROXY_STATE_PAUSED); + break; + case MEDIA_PROXY_OP_FIRST_TRACK: + do_first_track(&media_player, false); + media_player.seeking_speed_factor = + MEDIA_PROXY_SEEKING_SPEED_FACTOR_ZERO; + mpl_set_state(MEDIA_PROXY_STATE_PAUSED); + break; + case MEDIA_PROXY_OP_LAST_TRACK: + do_last_track(&media_player); + media_player.seeking_speed_factor = + MEDIA_PROXY_SEEKING_SPEED_FACTOR_ZERO; + mpl_set_state(MEDIA_PROXY_STATE_PAUSED); + break; + case MEDIA_PROXY_OP_GOTO_TRACK: + if (command->use_param) + { + do_goto_track(&media_player, command->param); + media_player.seeking_speed_factor = + MEDIA_PROXY_SEEKING_SPEED_FACTOR_ZERO; + mpl_set_state(MEDIA_PROXY_STATE_PAUSED); + } + else + { + result_code = MEDIA_PROXY_CMD_CANNOT_BE_COMPLETED; + } + break; + case MEDIA_PROXY_OP_PREV_GROUP: + do_full_prev_group(&media_player); + mpl_set_state(MEDIA_PROXY_STATE_PAUSED); + break; + case MEDIA_PROXY_OP_NEXT_GROUP: + do_full_next_group(&media_player); + mpl_set_state(MEDIA_PROXY_STATE_PAUSED); + break; + case MEDIA_PROXY_OP_FIRST_GROUP: + do_full_first_group(&media_player); + mpl_set_state(MEDIA_PROXY_STATE_PAUSED); + break; + case MEDIA_PROXY_OP_LAST_GROUP: + do_full_last_group(&media_player); + mpl_set_state(MEDIA_PROXY_STATE_PAUSED); + break; + case MEDIA_PROXY_OP_GOTO_GROUP: + if (command->use_param) + { + do_full_goto_group(&media_player, command->param); + mpl_set_state(MEDIA_PROXY_STATE_PAUSED); + } + else + { + result_code = MEDIA_PROXY_CMD_CANNOT_BE_COMPLETED; + } + break; + default: + LOG_DBG("Invalid command: %d", command->opcode); + result_code = MEDIA_PROXY_CMD_NOT_SUPPORTED; + break; + } + + return result_code; +} + +static uint8_t (*command_handlers[MEDIA_PROXY_STATE_LAST])( + const struct mpl_cmd *command) = +{ + inactive_state_command_handler, + playing_state_command_handler, + paused_state_command_handler, + seeking_state_command_handler, +}; + +#ifdef CONFIG_BT_MPL_OBJECTS +/* Find a track by ID + * + * If found, return pointers to the group of the track and the track, + * otherwise, the pointers returned are NULL + * + * Returns true if found, false otherwise + */ + +static bool find_track_by_id(const struct mpl_mediaplayer *pl, + uint64_t id, + struct mpl_group **group, + struct mpl_track **track) +{ + struct mpl_group *tmp_group = pl->group; + struct mpl_track *tmp_track; + + while (tmp_group->prev != NULL) + { + tmp_group = tmp_group->prev; + } + + while (tmp_group != NULL) + { + tmp_track = tmp_group->track; + + while (tmp_track->prev != NULL) + { + tmp_track = tmp_track->prev; + } + + while (tmp_track != 0) + { + if (tmp_track->id == id) + { + /* Found the track */ + + *group = tmp_group; + *track = tmp_track; + return true; + } + + tmp_track = tmp_track->next; + } + + tmp_group = tmp_group->next; + } + + /* Track not found */ + + *group = NULL; + *track = NULL; + return false; +} + +/* Find a group by ID + * + * If found, return pointer to the group, otherwise, the pointer returned is + * NULL + * + * Returns true if found, false otherwise + */ + +static bool find_group_by_id(const struct mpl_mediaplayer *pl, uint64_t id, + struct mpl_group **group) +{ + struct mpl_group *tmp_group = pl->group; + + while (tmp_group->prev != NULL) + { + tmp_group = tmp_group->prev; + } + + while (tmp_group != NULL) + { + if (tmp_group->id == id) + { + /* Found the group */ + + *group = tmp_group; + return true; + } + + tmp_group = tmp_group->next; + } + + /* Group not found */ + + *group = NULL; + return false; +} +#endif /* CONFIG_BT_MPL_OBJECTS */ + +static const char *get_player_name(void) +{ + return media_player.name; +} + +static void set_player_name(const char *name) +{ + size_t len = strlen(name); + + if (len >= sizeof(media_player.name)) + { + LOG_DBG("Media Player Name is too long: %zu", len); + return; + } + + if (strcmp(media_player.name, name) != 0) + { + (void)memcpy(media_player.name, name, len + 1U); + media_proxy_pl_name_cb(media_player.name); + } +} + +#ifdef CONFIG_BT_MPL_OBJECTS +static uint64_t get_icon_id(void) +{ + return media_player.icon_id; +} +#endif /* CONFIG_BT_MPL_OBJECTS */ + +static const char *get_icon_url(void) +{ + return media_player.icon_url; +} + +static const char *get_track_title(void) +{ + return media_player.group->track->title; +} + +static int32_t get_track_duration(void) +{ + return media_player.group->track->duration; +} + +static int32_t get_track_position(void) +{ + return media_player.track_pos; +} + +static void set_track_position(int32_t position) +{ + int32_t old_pos = media_player.track_pos; + int32_t new_pos; + + if (position >= 0) + { + if (position > media_player.group->track->duration) + { + /* Do not go beyond end of track */ + + new_pos = media_player.group->track->duration; + } + else + { + new_pos = position; + } + } + else + { + /* Negative position, handle as offset from _end_ of track */ + + /* (Note minus sign below) */ + + if (position < -media_player.group->track->duration) + { + new_pos = 0; + } + else + { + /* (Remember position is negative) */ + + new_pos = media_player.group->track->duration + position; + } + } + + LOG_DBG("Pos. given: %d, resulting pos.: %d (duration is %d)", position, + new_pos, + media_player.group->track->duration); + + /* Notify when the position changes when not in the playing state, or if + * the position is set to 0 which is a special value that typically + * indicates that the track has stopped or changed. Since this might occur + * when media_player.group->track->duration is still 0, we should always + * notify this value. + */ + + if (new_pos != old_pos || new_pos == 0) + { + /* Set new position and notify it */ + + media_player.track_pos = new_pos; + + /* MCS 1.0, section 3.7.1, states: + * to avoid an excessive number of notifications, the Track Position + * should not be notified when the Media State is set to ā€œPlayingā€ + * and playback happens at a constant speed. + */ + + if (media_player.state != MEDIA_PROXY_STATE_PLAYING || new_pos == 0) + { + media_proxy_pl_track_position_cb(new_pos); + } + } +} + +static void set_relative_track_position(int32_t rel_pos) +{ + int64_t pos; + + pos = media_player.track_pos + rel_pos; + + /* Clamp to allowed values */ + + pos = CLAMP(pos, 0, media_player.group->track->duration); + + set_track_position((int32_t)pos); +} + +/* Set segment start positions from the current track duration. + * + * The current track segment is reset to the first segment object. The first + * segment starts at 0, and each following segment is placed at an equal + * fraction of the track duration based on the number of segments. + */ + +static void set_track_segment(struct mpl_track *track) +{ + struct mpl_tseg *seg; + struct mpl_tseg *first_seg; + int32_t segment_count; + int32_t step; + int32_t pos = 0; + + DEBUGASSERT(track != NULL); + seg = track->segment; + DEBUGASSERT(seg != NULL); + + /* Start from the first segment, even if the current segment has moved. */ + + first_seg = seg; + while (first_seg->prev != NULL) + { + first_seg = first_seg->prev; + } + + track->segment = first_seg; + + /* Count total segments to calculate one uniform spacing value. */ + + segment_count = 1; + seg = first_seg->next; + while (seg != NULL) + { + segment_count++; + seg = seg->next; + } + + /* Distance between two neighboring segment start positions. */ + + step = track->duration / segment_count; + seg = first_seg; + + while (seg != NULL) + { + seg->pos = pos; + pos += step; + seg = seg->next; + } +} + +static int8_t get_playback_speed(void) +{ + return media_player.playback_speed_param; +} + +static int8_t get_supported_playback_speed(int8_t speed) +{ + if (speed > media_player.playback_speed_param) + { + /* MCS spec section 3.8.1 states: + * If the value written is not supported and is greater than the + * existing Playback Speed characteristic, then the server should set + * the Playback Speed characteristic to the next higher supported + * playback speed + */ + + if (speed > MEDIA_PROXY_PLAYBACK_SPEED_UNITY) + { + return MEDIA_PROXY_PLAYBACK_SPEED_DOUBLE; + } + else if (speed > MEDIA_PROXY_PLAYBACK_SPEED_HALF) + { + return MEDIA_PROXY_PLAYBACK_SPEED_UNITY; + } + else if (speed > MEDIA_PROXY_PLAYBACK_SPEED_QUARTER) + { + return MEDIA_PROXY_PLAYBACK_SPEED_HALF; + } + else + { + return MEDIA_PROXY_PLAYBACK_SPEED_QUARTER; + } + } + else if (speed < media_player.playback_speed_param) + { + /* MCS spec section 3.8.1 states: + * If the value written is not supported and is less than the existing + * Playback Speed characteristic, then the server should set the + * Playback Speed characteristic to the next lower supported playback + * speed + */ + + if (speed < MEDIA_PROXY_PLAYBACK_SPEED_HALF) + { + return MEDIA_PROXY_PLAYBACK_SPEED_QUARTER; + } + else if (speed < MEDIA_PROXY_PLAYBACK_SPEED_UNITY) + { + return MEDIA_PROXY_PLAYBACK_SPEED_HALF; + } + else if (speed < MEDIA_PROXY_PLAYBACK_SPEED_DOUBLE) + { + return MEDIA_PROXY_PLAYBACK_SPEED_UNITY; + } + else + { + return MEDIA_PROXY_PLAYBACK_SPEED_DOUBLE; + } + } + else + { + return speed; + } +} + +static void set_playback_speed(int8_t speed) +{ + /* Set new speed parameter and notify, if different from current */ + + if (speed != media_player.playback_speed_param) + { + /* This MPL only supports MEDIA_PROXY_PLAYBACK_SPEED_QUARTER, + * MEDIA_PROXY_PLAYBACK_SPEED_HALF, MEDIA_PROXY_PLAYBACK_SPEED_UNITY + * and MEDIA_PROXY_PLAYBACK_SPEED_DOUBLE. For unsupported values the + * MCS specification states: + * + * MCS spec section 3.8.1 states: If the server does not support the + * value written, the server shall set the Playback Speed + * characteristic to a supported value. + */ + + int8_t supported_playback_speed = get_supported_playback_speed(speed); + + if (speed != supported_playback_speed) + { + LOG_DBG("Changed speed from %d to %d", speed, + supported_playback_speed); + } + + media_player.playback_speed_param = supported_playback_speed; + media_proxy_pl_playback_speed_cb(media_player.playback_speed_param); + } +} + +static int8_t get_seeking_speed(void) +{ + return media_player.seeking_speed_factor; +} + +#ifdef CONFIG_BT_MPL_OBJECTS +static uint64_t get_track_segments_id(void) +{ + return media_player.group->track->segments_id; +} + +static uint64_t get_current_track_id(void) +{ + return media_player.group->track->id; +} + +static void set_current_track_id(uint64_t id) +{ + struct mpl_group *group; + struct mpl_track *track; + + LOG_DBG_OBJ_ID("Track ID to set: ", id); + + if (find_track_by_id(&media_player, id, &group, &track)) + { + if (media_player.group != group) + { + media_player.group = group; + do_group_change_notifications(&media_player); + + /* Group change implies track change (even if same track in other + * group) + */ + + set_current_track(&media_player, track); + } + else if (media_player.group->track != track) + { + set_current_track(&media_player, track); + } + + return; + } + + LOG_DBG("Track not found"); + + /* TODO: Should an error be returned here? + * That would require a rewrite of the MPL api to add return values to the + * functions. + */ +} + +static uint64_t get_next_track_id(void) +{ + /* If the next track has been set explicitly */ + + if (media_player.next_track_set) + { + return media_player.next.track->id; + } + + /* Normal playing order */ + + if (media_player.group->track->next) + { + return media_player.group->track->next->id; + } + + /* Return zero value to indicate that there is no next track */ + + return MPL_NO_TRACK_ID; +} + +static void set_next_track_id(uint64_t id) +{ + struct mpl_group *group; + struct mpl_track *track; + + LOG_DBG_OBJ_ID("Next Track ID to set: ", id); + + if (find_track_by_id(&media_player, id, &group, &track)) + { + media_player.next_track_set = true; + media_player.next.group = group; + media_player.next.track = track; + media_proxy_pl_next_track_id_cb(id); + return; + } + + LOG_DBG("Track not found"); +} + +static uint64_t get_parent_group_id(void) +{ + return media_player.group->parent->id; +} + +static uint64_t get_current_group_id(void) +{ + return media_player.group->id; +} + +static void set_current_group_id(uint64_t id) +{ + struct mpl_group *group; + + LOG_DBG_OBJ_ID("Group ID to set: ", id); + + if (find_group_by_id(&media_player, id, &group)) + { + if (media_player.group != group) + { + /* Change to found group */ + + media_player.group = group; + do_group_change_notifications(&media_player); + + /* And change to first track in group */ + + do_first_track(&media_player, false); + } + + return; + } + + LOG_DBG("Group not found"); +} +#endif /* CONFIG_BT_MPL_OBJECTS */ + +static uint8_t get_playing_order(void) +{ + return media_player.playing_order; +} + +static void set_playing_order(uint8_t order) +{ + if (order != media_player.playing_order) + { + if (BIT(order - 1) & media_player.playing_orders_supported) + { + media_player.playing_order = order; + media_proxy_pl_playing_order_cb(media_player.playing_order); + } + } +} + +static uint16_t get_playing_orders_supported(void) +{ + return media_player.playing_orders_supported; +} + +static uint8_t get_media_state(void) +{ + return media_player.state; +} + +static void send_command(const struct mpl_cmd *command) +{ + struct mpl_cmd_ntf ntf; + + if (command->use_param) + { + LOG_DBG("opcode: %d, param: %d", command->opcode, command->param); + } + else + { + LOG_DBG("opcode: %d", command->opcode); + } + + if (media_player.state < MEDIA_PROXY_STATE_LAST) + { + ntf.requested_opcode = command->opcode; + ntf.result_code = command_handlers[media_player.state](command); + + media_proxy_pl_command_cb(&ntf); + } + else + { + LOG_DBG("INVALID STATE"); + } +} + +static uint32_t get_commands_supported(void) +{ + return media_player.opcodes_supported; +} + +#ifdef CONFIG_BT_MPL_OBJECTS + +static bool parse_sci(struct bt_data *data, void *user_data) +{ + ARG_UNUSED(user_data); + + LOG_DBG("type: %u len %u", data->type, data->data_len); + LOG_HEXDUMP_DBG(data->data, data->data_len, "param:"); + + if (data->type < MEDIA_PROXY_SEARCH_TYPE_TRACK_NAME || + data->type > MEDIA_PROXY_SEARCH_TYPE_ONLY_GROUPS) + { + LOG_DBG("Invalid search type: %u", data->type); + return false; + } + + return true; +} + +static void parse_search(const struct mpl_search *search) +{ + bool search_failed = false; + + if (search->len > SEARCH_LEN_MAX) + { + LOG_WRN("Search too long (%d) - aborting", search->len); + search_failed = true; + } + else + { + uint8_t search_ltv[SEARCH_LEN_MAX]; + struct net_buf_simple buf; + + /* Copy so that we can parse it using the net_buf_simple when search is + * const + */ + + memcpy(search_ltv, search->search, search->len); + + net_buf_simple_init_with_data(&buf, search_ltv, search->len); + + bt_data_parse(&buf, parse_sci, NULL); + + if (buf.len != 0U) + { + search_failed = true; + } + } + + /* TODO: Add real search functionality. */ + + /* For now, just fake it. */ + + if (search_failed) + { + media_player.search_results_id = 0U; + media_proxy_pl_search_cb(MEDIA_PROXY_SEARCH_FAILURE); + } + else + { + /* Use current group as search result for now */ + + media_player.search_results_id = media_player.group->id; + media_proxy_pl_search_cb(MEDIA_PROXY_SEARCH_SUCCESS); + } + + media_proxy_pl_search_results_id_cb(media_player.search_results_id); +} + +static void send_search(const struct mpl_search *search) +{ + if (search->len > SEARCH_LEN_MAX) + { + LOG_WRN("Search too long: %d", search->len); + } + + LOG_HEXDUMP_DBG(search->search, search->len, "Search"); + + parse_search(search); +} + +static uint64_t get_search_results_id(void) +{ + return media_player.search_results_id; +} +#endif /* CONFIG_BT_MPL_OBJECTS */ + +static uint8_t get_content_ctrl_id(void) +{ + return media_player.content_ctrl_id; +} + +/** Calculates the new relative position depending on the state and + * seeking/playing speed factor + * + * @return New relative position in centiseconds (may be negative) + */ + +static int32_t get_pos_diff_cs(void) +{ + int32_t pos_diff_ms = TRACK_POS_WORK_DELAY_MS; + + if (media_player.state == MEDIA_PROXY_STATE_SEEKING) + { + /* When seeking, apply the seeking speed factor */ + + pos_diff_ms *= media_player.seeking_speed_factor; + } + else if (media_player.state == MEDIA_PROXY_STATE_PLAYING) + { + /* When playing, apply the playing speed */ + + switch (media_player.playback_speed_param) + { + case MEDIA_PROXY_PLAYBACK_SPEED_QUARTER: + pos_diff_ms /= 4; + break; + case MEDIA_PROXY_PLAYBACK_SPEED_HALF: + pos_diff_ms /= 2; + break; + case MEDIA_PROXY_PLAYBACK_SPEED_UNITY: + + /* no-op */ + + break; + case MEDIA_PROXY_PLAYBACK_SPEED_DOUBLE: + pos_diff_ms *= 2; + break; + default: + LOG_WRN("Unexpected playback_speed_param: %d", + media_player.playback_speed_param); + break; + } + } + else + { + LOG_ERR("Unexpected media_player.state: %u", media_player.state); + } + + return pos_diff_ms / 10; /* position is in centiseconds */ +} + +static void pos_work_cb(struct k_work *work) +{ + ARG_UNUSED(work); + + set_relative_track_position(get_pos_diff_cs()); + + if (media_player.track_pos == media_player.group->track->duration) + { + /* Go to next track */ + + do_next_track(&media_player); + } + + (void)k_work_schedule(&media_player.pos_work, TRACK_POS_WORK_DELAY); +} + +int media_proxy_pl_init(void) +{ + static bool initialized; + int ret; + + if (initialized) + { + LOG_DBG("Already initialized"); + return -EALREADY; + } + + /* Get a Content Control ID */ + + ret = bt_ccid_alloc_value(); + if (ret < 0) + { + LOG_DBG("Could not allocate CCID: %d", ret); + return ret; + } + + media_player.content_ctrl_id = (uint8_t)ret; + + /* Set up the media control service */ +#ifdef CONFIG_BT_MCS +#ifdef CONFIG_BT_MPL_OBJECTS + /* The test here is arguably needed as the objects cannot be accessed + * before bt_mcs_init is called, but the set is to avoid the objects being + * accessed before properly initialized MCS has to be registered before we + * add the objects, as we need the OTS that is registered and included by + * MCS. + */ + + if (atomic_test_and_set_bit(obj.flags, MPL_OBJ_FLAG_BUSY)) + { + LOG_ERR("Object busy"); + return -EBUSY; + } + + ret = bt_mcs_init(&ots_cbs); + if (ret < 0) + { + LOG_ERR("Could not init MCS: %d", ret); + atomic_clear_bit(obj.flags, MPL_OBJ_FLAG_BUSY); + + return ret; + } +#else + ret = bt_mcs_init(NULL); + if (ret < 0) + { + LOG_ERR("Could not init MCS: %d", ret); + return ret; + } +#endif /* CONFIG_BT_MPL_OBJECTS */ +#else + LOG_WRN("MCS not configured"); +#endif /* CONFIG_BT_MCS */ + + set_track_segment(media_player.group->track); + +#ifdef CONFIG_BT_MPL_OBJECTS + /* Initialize the object content buffer */ + + net_buf_simple_init(obj.content, 0); + + /* Icon Object */ + + ret = add_icon_object(); + DEBUGASSERT(ret == 0); + + /* Add all tracks and groups to OTS */ + + ret = add_group_and_track_objects(&media_player); + DEBUGASSERT(ret == 0); + + /* Initial setup of Track Segments Object */ + + /* TODO: Later, this should be done when the tracks are added */ + + /* but for no only one of the tracks has segments . */ + + ret = add_current_track_segments_object(&media_player); + DEBUGASSERT(ret == 0); + + atomic_clear_bit(obj.flags, MPL_OBJ_FLAG_BUSY); +#endif /* CONFIG_BT_MPL_OBJECTS */ + + /* Set up the calls structure */ + + media_player.calls.get_player_name = get_player_name; + media_player.calls.set_player_name = set_player_name; +#ifdef CONFIG_BT_MPL_OBJECTS + media_player.calls.get_icon_id = get_icon_id; +#endif /* CONFIG_BT_MPL_OBJECTS */ + media_player.calls.get_icon_url = get_icon_url; + media_player.calls.get_track_title = get_track_title; + media_player.calls.get_track_duration = get_track_duration; + media_player.calls.get_track_position = get_track_position; + media_player.calls.set_track_position = set_track_position; + media_player.calls.get_playback_speed = get_playback_speed; + media_player.calls.set_playback_speed = set_playback_speed; + media_player.calls.get_seeking_speed = get_seeking_speed; +#ifdef CONFIG_BT_MPL_OBJECTS + media_player.calls.get_track_segments_id = get_track_segments_id; + media_player.calls.get_current_track_id = get_current_track_id; + media_player.calls.set_current_track_id = set_current_track_id; + media_player.calls.get_next_track_id = get_next_track_id; + media_player.calls.set_next_track_id = set_next_track_id; + media_player.calls.get_parent_group_id = get_parent_group_id; + media_player.calls.get_current_group_id = get_current_group_id; + media_player.calls.set_current_group_id = set_current_group_id; +#endif /* CONFIG_BT_MPL_OBJECTS */ + media_player.calls.get_playing_order = get_playing_order; + media_player.calls.set_playing_order = set_playing_order; + media_player.calls.get_playing_orders_supported = + get_playing_orders_supported; + media_player.calls.get_media_state = get_media_state; + media_player.calls.send_command = send_command; + media_player.calls.get_commands_supported = get_commands_supported; +#ifdef CONFIG_BT_MPL_OBJECTS + media_player.calls.send_search = send_search; + media_player.calls.get_search_results_id = get_search_results_id; +#endif /* CONFIG_BT_MPL_OBJECTS */ + media_player.calls.get_content_ctrl_id = get_content_ctrl_id; + + ret = media_proxy_pl_register(&media_player.calls); + DEBUGASSERT(ret == 0); + + k_work_init_delayable(&media_player.pos_work, pos_work_cb); + + initialized = true; + return 0; +} + +#if defined(CONFIG_BT_MPL_LOG_LEVEL_DBG) /* Special commands for debugging */ + +void mpl_debug_dump_state(void) +{ +#if CONFIG_BT_MPL_OBJECTS + char t[BT_OTS_OBJ_ID_STR_LEN]; + struct mpl_group *group; + struct mpl_track *track; +#endif /* CONFIG_BT_MPL_OBJECTS */ + + LOG_DBG("Mediaplayer name: %s", media_player.name); + +#if CONFIG_BT_MPL_OBJECTS + (void)bt_ots_obj_id_to_str(media_player.icon_id, t, sizeof(t)); + LOG_DBG("Icon ID: %s", t); +#endif /* CONFIG_BT_MPL_OBJECTS */ + + LOG_DBG("Icon URL: %s", media_player.icon_url); + LOG_DBG("Track position: %d", media_player.track_pos); + LOG_DBG("Media state: %d", media_player.state); + LOG_DBG("Playback speed parameter: %d", media_player.playback_speed_param); + LOG_DBG("Seeking speed factor: %d", media_player.seeking_speed_factor); + LOG_DBG("Playing order: %d", media_player.playing_order); + LOG_DBG("Playing orders supported: 0x%x", + media_player.playing_orders_supported); + LOG_DBG("Opcodes supported: %d", media_player.opcodes_supported); + LOG_DBG("Content control ID: %d", media_player.content_ctrl_id); + +#if CONFIG_BT_MPL_OBJECTS + (void)bt_ots_obj_id_to_str(media_player.group->parent->id, t, sizeof(t)); + LOG_DBG("Current group's parent: %s", t); + + (void)bt_ots_obj_id_to_str(media_player.group->id, t, sizeof(t)); + LOG_DBG("Current group: %s", t); + + (void)bt_ots_obj_id_to_str(media_player.group->track->id, t, sizeof(t)); + LOG_DBG("Current track: %s", t); + + if (media_player.next_track_set) + { + (void)bt_ots_obj_id_to_str(media_player.next.track->id, t, sizeof(t)); + LOG_DBG("Next track: %s", t); + } + else if (media_player.group->track->next) + { + (void)bt_ots_obj_id_to_str(media_player.group->track->next->id, t, + sizeof(t)); + LOG_DBG("Next track: %s", t); + } + else + { + LOG_DBG("No next track"); + } + + if (media_player.search_results_id) + { + (void)bt_ots_obj_id_to_str(media_player.search_results_id, t, + sizeof(t)); + LOG_DBG("Search results: %s", t); + } + else + { + LOG_DBG("No search results"); + } + + LOG_DBG("Groups and tracks:"); + group = media_player.group; + + while (group->prev != NULL) + { + group = group->prev; + } + + while (group) + { + (void)bt_ots_obj_id_to_str(group->id, t, sizeof(t)); + LOG_DBG("Group: %s, %s", t, group->title); + + (void)bt_ots_obj_id_to_str(group->parent->id, t, sizeof(t)); + LOG_DBG("\tParent: %s, %s", t, group->parent->title); + + track = group->track; + while (track->prev != NULL) + { + track = track->prev; + } + + while (track) + { + (void)bt_ots_obj_id_to_str(track->id, t, sizeof(t)); + LOG_DBG("\tTrack: %s, %s, duration: %d", t, track->title, + track->duration); + track = track->next; + } + + group = group->next; + } +#endif /* CONFIG_BT_MPL_OBJECTS */ +} +#endif /* CONFIG_BT_MPL_LOG_LEVEL_DBG */ + +#if defined(CONFIG_BT_TESTING) /* Special commands for testing */ + +#if CONFIG_BT_MPL_OBJECTS +void mpl_test_unset_parent_group(void) +{ + LOG_DBG("Setting current group to be it's own parent"); + media_player.group->parent = media_player.group; +} +#endif /* CONFIG_BT_MPL_OBJECTS */ + +void mpl_test_media_state_set(uint8_t state) +{ + mpl_set_state(state); +} + +void mpl_test_player_name_changed_cb(void) +{ + media_proxy_pl_name_cb(media_player.name); +} + +void mpl_test_player_icon_url_changed_cb(void) +{ + media_proxy_pl_icon_url_cb(media_player.icon_url); +} + +void mpl_test_track_changed_cb(void) +{ + media_proxy_pl_track_changed_cb(); +} + +void mpl_test_title_changed_cb(void) +{ + media_proxy_pl_track_title_cb(media_player.group->track->title); +} + +void mpl_test_duration_changed_cb(void) +{ + media_proxy_pl_track_duration_cb(media_player.group->track->duration); +} + +void mpl_test_position_changed_cb(void) +{ + media_proxy_pl_track_position_cb(media_player.track_pos); +} + +void mpl_test_playback_speed_changed_cb(void) +{ + media_proxy_pl_playback_speed_cb(media_player.playback_speed_param); +} + +void mpl_test_seeking_speed_changed_cb(void) +{ + media_proxy_pl_seeking_speed_cb(media_player.seeking_speed_factor); +} + +#ifdef CONFIG_BT_MPL_OBJECTS +void mpl_test_current_track_id_changed_cb(void) +{ + media_proxy_pl_current_track_id_cb(media_player.group->track->id); +} + +void mpl_test_next_track_id_changed_cb(void) +{ + media_proxy_pl_next_track_id_cb(media_player.group->track->next->id); +} + +void mpl_test_parent_group_id_changed_cb(void) +{ + media_proxy_pl_parent_group_id_cb(media_player.group->id); +} + +void mpl_test_current_group_id_changed_cb(void) +{ + media_proxy_pl_current_group_id_cb(media_player.group->id); +} +#endif /* CONFIG_BT_MPL_OBJECTS */ + +void mpl_test_playing_order_changed_cb(void) +{ + media_proxy_pl_playing_order_cb(media_player.playing_order); +} + +void mpl_test_media_state_changed_cb(void) +{ + media_proxy_pl_media_state_cb(media_player.playing_order); +} + +void mpl_test_opcodes_supported_changed_cb(void) +{ + media_proxy_pl_commands_supported_cb(media_player.opcodes_supported); +} + +#ifdef CONFIG_BT_MPL_OBJECTS +void mpl_test_search_results_changed_cb(void) +{ + media_proxy_pl_search_cb(media_player.search_results_id); +} +#endif /* CONFIG_BT_MPL_OBJECTS */ + +#endif /* CONFIG_BT_TESTING */ diff --git a/libs/zblue/audio/mpl_internal.h b/libs/zblue/audio/mpl_internal.h new file mode 100644 index 0000000000000..5cd5ba05095f4 --- /dev/null +++ b/libs/zblue/audio/mpl_internal.h @@ -0,0 +1,207 @@ +/**************************************************************************** + * libs/zblue/audio/mpl_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Internal header for the media player (MPL). + * + * Copyright (c) 2019 - 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_SUBSYS_BLUETOOTH_AUDIO_MPL_INTERNAL_ +#define ZEPHYR_SUBSYS_BLUETOOTH_AUDIO_MPL_INTERNAL_ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/* Offset into a segment/track before the "previous" command goes to start of + */ + +/* current segment/track instead to previous */ +#define PREV_MARGIN 500 /* 500 * 0.01 = 5 seconds */ + +/* Increase/decrease in seeking sped factor for fast rewind/forward commands + * The media control specification has a requirement that the speed factor + * shall be [-64, -4], 0, [4, 64]. I.e., values between 0 and +/- 4 are not + * allowed. Set this equal to the minimum speed factor, to ensure only valid + * speed factors are used when changing to/from zero + */ +#define MPL_SEEKING_SPEED_FACTOR_STEP 4 + +/* Track segments */ + +struct mpl_tseg +{ + uint8_t name_len; + char name[CONFIG_BT_MPL_SEGMENT_NAME_MAX]; + int32_t pos; + struct mpl_tseg *prev; + struct mpl_tseg *next; +}; + +/* Tracks */ + +struct mpl_track +{ +#if defined(CONFIG_BT_MPL_OBJECTS) || defined(CONFIG_BT_OTS_CLIENT) + uint64_t id; +#endif /* CONFIG_BT_MPL_OBJECTS || CONFIG_BT_OTS_CLIENT */ + char title[CONFIG_BT_MPL_TRACK_TITLE_MAX]; + int32_t duration; + struct mpl_tseg *segment; +#if defined(CONFIG_BT_MPL_OBJECTS) || defined(CONFIG_BT_OTS_CLIENT) + uint64_t segments_id; +#endif /* CONFIG_BT_MPL_OBJECTS || CONFIG_BT_OTS_CLIENT */ + struct mpl_track *prev; + struct mpl_track *next; +}; + +/* Groups */ + +struct mpl_group +{ +#if defined(CONFIG_BT_MPL_OBJECTS) || defined(CONFIG_BT_OTS_CLIENT) + uint64_t id; +#endif /* CONFIG_BT_MPL_OBJECTS || CONFIG_BT_OTS_CLIENT */ + char title[CONFIG_BT_MPL_GROUP_TITLE_MAX]; + struct mpl_track *track; + struct mpl_group *parent; + struct mpl_group *prev; + struct mpl_group *next; +}; + +/** @brief Media Player */ + +struct mpl_mediaplayer +{ + char name[CONFIG_BT_MPL_MEDIA_PLAYER_NAME_MAX]; +#if defined(CONFIG_BT_MPL_OBJECTS) || defined(CONFIG_BT_OTS_CLIENT) + uint64_t icon_id; +#endif /* CONFIG_BT_MPL_OBJECTS || CONFIG_BT_OTS_CLIENT */ + char icon_url[CONFIG_BT_MPL_ICON_URL_MAX]; + struct mpl_group *group; + int32_t track_pos; + uint8_t state; + int8_t playback_speed_param; + int8_t seeking_speed_factor; + uint8_t playing_order; + uint16_t playing_orders_supported; + uint32_t opcodes_supported; +#if defined(CONFIG_BT_MPL_OBJECTS) || defined(CONFIG_BT_OTS_CLIENT) + uint64_t search_results_id; +#endif /* CONFIG_BT_MPL_OBJECTS || CONFIG_BT_OTS_CLIENT */ + uint8_t content_ctrl_id; + struct media_proxy_pl_calls calls; + + bool next_track_set; /* If next track explicitly set */ + struct + { + struct mpl_track *track; /* The track explicitly set as next track */ + struct mpl_group *group; /* The group of the set track */ + } next; + + struct k_work_delayable pos_work; +}; + +/* Special calls for testing */ + +/* For IOP testing - set current group to be it's own parent */ + +void mpl_test_unset_parent_group(void); + +/* Force the media player into a given state */ + +void mpl_test_media_state_set(uint8_t state); + +/** Trigger player name changed callback */ + +void mpl_test_player_name_changed_cb(void); + +/** Trigger player name changed callback */ + +void mpl_test_player_icon_url_changed_cb(void); + +/* Trigger track changed callback */ + +void mpl_test_track_changed_cb(void); + +/* Trigger title changed callback */ + +void mpl_test_title_changed_cb(void); + +/* Trigger duration changed callback */ + +void mpl_test_duration_changed_cb(void); + +/* Trigger position changed callback */ + +void mpl_test_position_changed_cb(void); + +/* Trigger playback speed changed callback */ + +void mpl_test_playback_speed_changed_cb(void); + +/* Trigger seeking speed changed callback */ + +void mpl_test_seeking_speed_changed_cb(void); + +/* Trigger current track id changed callback */ + +void mpl_test_current_track_id_changed_cb(void); + +/* Trigger next track id changed callback */ + +void mpl_test_next_track_id_changed_cb(void); + +/* Trigger current group id changed callback */ + +void mpl_test_current_group_id_changed_cb(void); + +/* Trigger parent group id changed callback */ + +void mpl_test_parent_group_id_changed_cb(void); + +/* Trigger playing order changed callback */ + +void mpl_test_playing_order_changed_cb(void); + +/* Trigger media state changed callback */ + +void mpl_test_media_state_changed_cb(void); + +/* Trigger operations supported changed callback */ + +void mpl_test_opcodes_supported_changed_cb(void); + +/* Trigger search results changed callback */ + +void mpl_test_search_results_changed_cb(void); + +/* Output the mediaplayer's state information */ + +void mpl_debug_dump_state(void); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_SUBSYS_BLUETOOTH_AUDIO_MPL_INTERNAL_*/ diff --git a/libs/zblue/audio/pacs.c b/libs/zblue/audio/pacs.c new file mode 100644 index 0000000000000..2935ace059ba1 --- /dev/null +++ b/libs/zblue/audio/pacs.c @@ -0,0 +1,2005 @@ +/**************************************************************************** + * libs/zblue/audio/pacs.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* @file + * @brief Bluetooth PACS + */ + +/* Copyright (c) 2020 Intel Corporation + * Copyright (c) 2022-2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/bt_str.h" + +#include "audio_internal.h" +#include "bap_unicast_server.h" +#include "pacs_internal.h" + +LOG_MODULE_REGISTER(bt_pacs, CONFIG_BT_PACS_LOG_LEVEL); + +#define PAC_NOTIFY_TIMEOUT MSEC2TICK(10U) +#define READ_BUF_SEM_TIMEOUT MSEC2TICK(50U) + +#if defined(CONFIG_BT_PAC_SRC) +static struct list_node src_pac_list = LIST_INITIAL_VALUE(src_pac_list); +static uint16_t src_supported_contexts; +#if defined(CONFIG_BT_PAC_SRC_LOC) +static uint32_t pacs_src_location; +#endif /* CONFIG_BT_PAC_SRC_LOC */ +#endif /* CONFIG_BT_PAC_SRC */ + +#if defined(CONFIG_BT_PAC_SNK) +static struct list_node snk_pac_list = LIST_INITIAL_VALUE(snk_pac_list); +static uint16_t snk_supported_contexts; +#if defined(CONFIG_BT_PAC_SNK_LOC) +static uint32_t pacs_snk_location; +#endif /* CONFIG_BT_PAC_SNK_LOC */ +#endif /* CONFIG_BT_PAC_SNK */ + +static uint16_t src_available_contexts = BT_AUDIO_CONTEXT_TYPE_NONE; +static uint16_t snk_available_contexts = BT_AUDIO_CONTEXT_TYPE_NONE; + +enum +{ + FLAG_ACTIVE, + FLAG_SINK_PAC_CHANGED, + FLAG_SINK_AUDIO_LOCATIONS_CHANGED, + FLAG_SOURCE_PAC_CHANGED, + FLAG_SOURCE_AUDIO_LOCATIONS_CHANGED, + FLAG_AVAILABLE_AUDIO_CONTEXT_CHANGED, + FLAG_SUPPORTED_AUDIO_CONTEXT_CHANGED, + FLAG_NUM, +}; + +enum +{ + PACS_FLAG_REGISTERED, + PACS_FLAG_SVC_CHANGING, + PACS_FLAG_NOTIFY_RDY, + PACS_FLAG_SNK_PAC, + PACS_FLAG_SNK_LOC, + PACS_FLAG_SRC_PAC, + PACS_FLAG_SRC_LOC, + PACS_FLAG_NUM, +}; + +struct pacs_client +{ + bt_addr_le_t addr; + +#if defined(CONFIG_BT_PAC_SNK) + /* Sink Available Contexts override value */ + + uint16_t *snk_available_contexts; +#endif /* CONFIG_BT_PAC_SNK */ + +#if defined(CONFIG_BT_PAC_SRC) + /* Source Available Contexts override value */ + + uint16_t *src_available_contexts; +#endif /* CONFIG_BT_PAC_SRC */ + + /* Pending notification flags */ + + atomic_define(flags, FLAG_NUM); +}; + +static struct pacs +{ + atomic_define(flags, PACS_FLAG_NUM); + + struct pacs_client clients[CONFIG_BT_MAX_PAIRED]; + + struct bt_gatt_attr *available_ctx_attr; +#if defined(CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE) + struct bt_gatt_attr *supported_ctx_attr; +#endif /* CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE */ +#if defined(CONFIG_BT_PAC_SNK_NOTIFIABLE) + struct bt_gatt_attr *snk_pac_attr; +#endif /* CONFIG_BT_PAC_SNK_NOTIFIABLE */ +#if defined(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE) + struct bt_gatt_attr *snk_pac_loc_attr; +#endif /* CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE */ +#if defined(CONFIG_BT_PAC_SRC_NOTIFIABLE) + struct bt_gatt_attr *src_pac_attr; +#endif /* CONFIG_BT_PAC_SRC_NOTIFIABLE */ +#if defined(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE) + struct bt_gatt_attr *src_pac_loc_attr; +#endif /* CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE */ +} pacs; + +static sem_t read_buf_sem = NXSEM_INITIALIZER(1, 0); +NET_BUF_SIMPLE_DEFINE_STATIC(read_buf, BT_ATT_MAX_ATTRIBUTE_LEN); + +static int pacs_gatt_notify(struct bt_conn *conn, + const struct bt_uuid *uuid, + const struct bt_gatt_attr *attr, + const void *data, + uint16_t len); +static void deferred_nfy_work_handler(struct k_work *work); + +static K_WORK_DEFINE(deferred_nfy_work, deferred_nfy_work_handler); + +struct pac_records_build_data +{ + struct bt_pacs_read_rsp *rsp; + struct net_buf_simple *buf; +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static struct pacs_client *client_lookup_conn(const struct bt_conn *conn) +{ + DEBUGASSERT(conn != NULL); + + for (size_t i = 0U; i < ARRAY_SIZE(pacs.clients); i++) + { + if (atomic_test_bit(pacs.clients[i].flags, FLAG_ACTIVE) && + bt_addr_le_eq(&pacs.clients[i].addr, bt_conn_get_dst(conn))) + { + return &pacs.clients[i]; + } + } + + return NULL; +} + +static void pacs_set_notify_bit(int bit) +{ + for (size_t i = 0U; i < ARRAY_SIZE(pacs.clients); i++) + { + if (atomic_test_bit(pacs.clients[i].flags, FLAG_ACTIVE)) + { + atomic_set_bit(pacs.clients[i].flags, bit); + } + } +} + +static bool build_pac_records(const struct bt_pacs_cap *cap, void *user_data) +{ + struct pac_records_build_data *data = user_data; + const struct bt_audio_codec_cap *codec_cap = cap->codec_cap; + struct net_buf_simple *buf = data->buf; + struct net_buf_simple_state state; + struct bt_pac_codec *pac_codec; + + net_buf_simple_save(buf, &state); + + if (net_buf_simple_tailroom(buf) < sizeof(*pac_codec)) + { + goto fail; + } + + pac_codec = net_buf_simple_add(buf, sizeof(*pac_codec)); + pac_codec->id = codec_cap->id; + pac_codec->cid = sys_cpu_to_le16(codec_cap->cid); + pac_codec->vid = sys_cpu_to_le16(codec_cap->vid); + + if (net_buf_simple_tailroom(buf) < (sizeof(struct bt_pac_ltv_data) + + codec_cap->data_len)) + { + goto fail; + } + + net_buf_simple_add_u8(buf, codec_cap->data_len); + net_buf_simple_add_mem(buf, codec_cap->data, codec_cap->data_len); + + if (net_buf_simple_tailroom(buf) < (sizeof(struct bt_pac_ltv_data) + + codec_cap->meta_len)) + { + goto fail; + } + + net_buf_simple_add_u8(buf, codec_cap->meta_len); + net_buf_simple_add_mem(buf, codec_cap->meta, codec_cap->meta_len); + + data->rsp->num_pac++; + + return true; + +fail: + DEBUGASSERT(false); + + net_buf_simple_restore(buf, &state); + + return false; +} + +static void foreach_cap(struct list_node *list, + bt_pacs_cap_foreach_func_t func, void *user_data) +{ + struct bt_pacs_cap *cap; + + list_for_every_container(list, cap, _node) + { + if (!func(cap, user_data)) + { + break; + } + } +} + +static void get_pac_records(struct list_node *list, + struct net_buf_simple *buf) +{ + struct pac_records_build_data data; + + /* Reset if buffer before using */ + + net_buf_simple_reset(buf); + + data.rsp = net_buf_simple_add(buf, sizeof(*data.rsp)); + data.rsp->num_pac = 0U; + data.buf = buf; + + foreach_cap(list, build_pac_records, &data); +} + +static void available_context_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + LOG_DBG("attr %p value 0x%04x", attr, value); +} + +static enum bt_audio_context pacs_get_available_contexts_for_conn( + struct bt_conn *conn, enum bt_audio_dir dir) +{ + const struct pacs_client *client; + + client = client_lookup_conn(conn); + if (client == NULL) + { + LOG_DBG("No client context for conn %p", (void *)conn); + return bt_pacs_get_available_contexts(dir); + } + + switch (dir) + { + case BT_AUDIO_DIR_SINK: +#if defined(CONFIG_BT_PAC_SNK) + if (atomic_test_bit(pacs.flags, PACS_FLAG_SNK_PAC) && + client->snk_available_contexts != NULL) + { + return POINTER_TO_UINT(client->snk_available_contexts); + } +#endif /* CONFIG_BT_PAC_SNK */ + break; + case BT_AUDIO_DIR_SOURCE: +#if defined(CONFIG_BT_PAC_SRC) + if (atomic_test_bit(pacs.flags, PACS_FLAG_SRC_PAC) && + client->src_available_contexts != NULL) + { + return POINTER_TO_UINT(client->src_available_contexts); + } +#endif /* CONFIG_BT_PAC_SRC */ + break; + } + + return bt_pacs_get_available_contexts(dir); +} + +static ssize_t available_contexts_read( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct bt_pacs_context context = + { + .snk = sys_cpu_to_le16(pacs_get_available_contexts_for_conn(conn, + BT_AUDIO_DIR_SINK)), + .src = sys_cpu_to_le16(pacs_get_available_contexts_for_conn(conn, + BT_AUDIO_DIR_SOURCE)), + }; + + LOG_DBG("conn %p attr %p buf %p len %u offset %u", (void *)conn, attr, buf, + len, offset); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &context, + sizeof(context)); +} + +#if defined(CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE) +static void supported_context_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + LOG_DBG("attr %p value 0x%04x", attr, value); +} +#endif /* CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE */ + +static uint16_t supported_context_get(enum bt_audio_dir dir) +{ + switch (dir) + { +#if defined(CONFIG_BT_PAC_SNK) + case BT_AUDIO_DIR_SINK: + if (atomic_test_bit(pacs.flags, PACS_FLAG_SNK_PAC)) + { + return snk_supported_contexts | + BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED; + } + break; +#endif /* CONFIG_BT_PAC_SNK */ +#if defined(CONFIG_BT_PAC_SRC) + case BT_AUDIO_DIR_SOURCE: + if (atomic_test_bit(pacs.flags, PACS_FLAG_SRC_PAC)) + { + return src_supported_contexts | + BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED; + } + break; +#endif /* CONFIG_BT_PAC_SRC */ + default: + break; + } + + return BT_AUDIO_CONTEXT_TYPE_NONE; +} + +static ssize_t supported_context_read( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct bt_pacs_context context = + { + .snk = sys_cpu_to_le16(supported_context_get(BT_AUDIO_DIR_SINK)), + .src = sys_cpu_to_le16(supported_context_get(BT_AUDIO_DIR_SOURCE)), + }; + + LOG_DBG("conn %p attr %p buf %p len %u offset %u", (void *)conn, attr, buf, + len, offset); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &context, + sizeof(context)); +} + +static int set_available_contexts(uint16_t contexts, + uint16_t *available, uint16_t supported) +{ + if (contexts & ~supported) + { + return -ENOTSUP; + } + + if (contexts == *available) + { + return 0; + } + + *available = contexts; + + pacs_set_notify_bit(FLAG_AVAILABLE_AUDIO_CONTEXT_CHANGED); + k_work_submit(&deferred_nfy_work); + + return 0; +} + +static int set_supported_contexts(uint16_t contexts, + uint16_t *supported, uint16_t *available) +{ + int err; + uint16_t tmp_supported = *supported; + uint16_t tmp_available = *available; + + /* Ensure unspecified is always supported */ + + contexts |= BT_AUDIO_CONTEXT_TYPE_UNSPECIFIED; + + if (*supported == contexts) + { + return 0; + } + + *supported = contexts; + + /* Update available contexts if needed */ + + if ((contexts & *available) != *available) + { + err = set_available_contexts(contexts & *available, available, + contexts); + if (err != 0) + { + *available = tmp_available; + *supported = tmp_supported; + + return err; + } + } + + if (IS_ENABLED(CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE)) + { + pacs_set_notify_bit(FLAG_SUPPORTED_AUDIO_CONTEXT_CHANGED); + k_work_submit(&deferred_nfy_work); + } + + return 0; +} + +#if defined(CONFIG_BT_PAC_SNK) +static ssize_t snk_read( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + ssize_t ret_val; + int err; + + LOG_DBG("conn %p attr %p buf %p len %u offset %u", (void *)conn, attr, buf, + len, offset); + + err = nxsem_tickwait(&read_buf_sem, READ_BUF_SEM_TIMEOUT); + if (err != 0) + { + LOG_DBG("Failed to take read_buf_sem: %d", err); + + return BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES); + } + + get_pac_records(&snk_pac_list, &read_buf); + + ret_val = bt_gatt_attr_read(conn, attr, buf, len, offset, read_buf.data, + read_buf.len); + + nxsem_post(&read_buf_sem); + + return ret_val; +} + +#if defined(CONFIG_BT_PAC_SNK_NOTIFIABLE) +static const struct bt_uuid *pacs_snk_uuid = BT_UUID_PACS_SNK; + +static void snk_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) +{ + LOG_DBG("attr %p value 0x%04x", attr, value); +} +#endif /* CONFIG_BT_PAC_SNK_NOTIFIABLE */ +#endif /* CONFIG_BT_PAC_SNK */ + +#if defined(CONFIG_BT_PAC_SNK_LOC) +static ssize_t snk_loc_read( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + uint32_t location = sys_cpu_to_le32(pacs_snk_location); + + LOG_DBG("conn %p attr %p buf %p len %u offset %u", (void *)conn, attr, buf, + len, offset); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &location, + sizeof(location)); +} + +#if defined(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE) +static const struct bt_uuid *pacs_snk_loc_uuid = BT_UUID_PACS_SNK_LOC; + +static void snk_loc_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + LOG_DBG("attr %p value 0x%04x", attr, value); +} +#endif /* CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE */ + +static void set_snk_location(enum bt_audio_location audio_location) +{ + if (atomic_test_bit(pacs.flags, PACS_FLAG_SNK_LOC)) + { + if (audio_location == pacs_snk_location) + { + return; + } + + pacs_snk_location = audio_location; + + if (IS_ENABLED(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE)) + { + pacs_set_notify_bit(FLAG_SINK_AUDIO_LOCATIONS_CHANGED); + k_work_submit(&deferred_nfy_work); + } + } +} +#else +static void set_snk_location(enum bt_audio_location location) +{ + ARG_UNUSED(location); + + return; +} +#endif /* CONFIG_BT_PAC_SNK_LOC */ + +#if defined(CONFIG_BT_PAC_SNK_LOC_WRITEABLE) +static ssize_t snk_loc_write( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *data, + uint16_t len, uint16_t offset, uint8_t flags) +{ + enum bt_audio_location location; + + ARG_UNUSED(conn); + ARG_UNUSED(attr); + ARG_UNUSED(flags); + + if (offset) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (len != sizeof(location)) + { + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); + } + + location = (enum bt_audio_location)sys_get_le32(data); + if (location > BT_AUDIO_LOCATION_MASK) + { + LOG_DBG("Invalid location value: 0x%08X", location); + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); + } + + set_snk_location(location); + + return len; +} +#endif /* CONFIG_BT_PAC_SNK_LOC_WRITEABLE */ + +#if defined(CONFIG_BT_PAC_SRC) +static ssize_t src_read( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + ssize_t ret_val; + int err; + + LOG_DBG("conn %p attr %p buf %p len %u offset %u", (void *)conn, attr, buf, + len, offset); + + err = nxsem_tickwait(&read_buf_sem, READ_BUF_SEM_TIMEOUT); + if (err != 0) + { + LOG_DBG("Failed to take read_buf_sem: %d", err); + + return BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES); + } + + get_pac_records(&src_pac_list, &read_buf); + + ret_val = bt_gatt_attr_read(conn, attr, buf, len, offset, read_buf.data, + read_buf.len); + + nxsem_post(&read_buf_sem); + + return ret_val; +} + +#if defined(CONFIG_BT_PAC_SRC_NOTIFIABLE) +static const struct bt_uuid *pacs_src_uuid = BT_UUID_PACS_SRC; + +static void src_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) +{ + LOG_DBG("attr %p value 0x%04x", attr, value); +} +#endif /* CONFIG_BT_PAC_SRC_NOTIFIABLE */ +#endif /* CONFIG_BT_PAC_SRC */ + +#if defined(CONFIG_BT_PAC_SRC_LOC) +static ssize_t src_loc_read( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + uint32_t location = sys_cpu_to_le32(pacs_src_location); + + LOG_DBG("conn %p attr %p buf %p len %u offset %u", (void *)conn, attr, buf, + len, offset); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &location, + sizeof(location)); +} + +#if defined(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE) +static const struct bt_uuid *pacs_src_loc_uuid = BT_UUID_PACS_SRC_LOC; + +static void src_loc_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + LOG_DBG("attr %p value 0x%04x", attr, value); +} +#endif /* CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE */ + +static void set_src_location(enum bt_audio_location audio_location) +{ + if (atomic_test_bit(pacs.flags, PACS_FLAG_SRC_LOC)) + { + if (audio_location == pacs_src_location) + { + return; + } + + pacs_src_location = audio_location; + + if (IS_ENABLED(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE)) + { + pacs_set_notify_bit(FLAG_SOURCE_AUDIO_LOCATIONS_CHANGED); + k_work_submit(&deferred_nfy_work); + } + } +} +#else +static void set_src_location(enum bt_audio_location location) +{ + ARG_UNUSED(location); + + return; +} +#endif /* CONFIG_BT_PAC_SRC_LOC */ + +#if defined(CONFIG_BT_PAC_SRC_LOC_WRITEABLE) +static ssize_t src_loc_write( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *data, + uint16_t len, uint16_t offset, uint8_t flags) +{ + uint32_t location; + + ARG_UNUSED(conn); + ARG_UNUSED(attr); + ARG_UNUSED(flags); + + if (offset) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (len != sizeof(location)) + { + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); + } + + location = (enum bt_audio_location)sys_get_le32(data); + if (location > BT_AUDIO_LOCATION_MASK) + { + LOG_DBG("Invalid location value: 0x%08X", location); + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); + } + + set_src_location(location); + + return len; +} +#endif /* CONFIG_BT_PAC_SRC_LOC_WRITEABLE */ + +static struct list_node *pacs_get_pac(enum bt_audio_dir dir) +{ + switch (dir) + { +#if defined(CONFIG_BT_PAC_SNK) + case BT_AUDIO_DIR_SINK: + if (atomic_test_bit(pacs.flags, PACS_FLAG_SNK_PAC)) + { + return &snk_pac_list; + } + + return NULL; +#endif /* CONFIG_BT_PAC_SNK */ +#if defined(CONFIG_BT_PAC_SRC) + case BT_AUDIO_DIR_SOURCE: + if (atomic_test_bit(pacs.flags, PACS_FLAG_SRC_PAC)) + { + return &src_pac_list; + } + + return NULL; +#endif /* CONFIG_BT_PAC_SRC */ + default: + return NULL; + } +} + +#if defined(CONFIG_BT_PAC_SNK) +#define BT_PACS_SNK_PROP \ + BT_GATT_CHRC_READ \ + IF_ENABLED(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE, (| BT_GATT_CHRC_NOTIFY)) +#define BT_PAC_SNK \ + BT_AUDIO_CHRC(BT_UUID_PACS_SNK, BT_PACS_SNK_PROP, BT_GATT_PERM_READ_ENCRYPT, snk_read, NULL, \ + NULL), \ + IF_ENABLED(CONFIG_BT_PAC_SNK_NOTIFIABLE, (BT_AUDIO_CCC(snk_cfg_changed), )) + +#define BT_PACS_SNK_LOC_PROP \ + BT_GATT_CHRC_READ \ + IF_ENABLED(CONFIG_BT_PAC_SNK_LOC_WRITEABLE, (| BT_GATT_CHRC_WRITE)) \ + IF_ENABLED(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE, (| BT_GATT_CHRC_NOTIFY)) + +#define BT_PACS_SNK_LOC_PERM \ + BT_GATT_PERM_READ_ENCRYPT \ + IF_ENABLED(CONFIG_BT_PAC_SNK_LOC_WRITEABLE, (| BT_GATT_PERM_WRITE_ENCRYPT)) + +/* declaration + value [+ cccd] */ +#define PACS_SNK_PAC_CHAR_ATTR_COUNT COND_CODE_1(IS_ENABLED(CONFIG_BT_PAC_SNK_NOTIFIABLE), (3), (2)) +#else +#define BT_PAC_SNK +#define PACS_SNK_PAC_CHAR_ATTR_COUNT 0U +#endif /* CONFIG_BT_PAC_SNK */ + +#if defined(CONFIG_BT_PAC_SNK_LOC) +#define BT_PACS_SNK_LOC \ + BT_AUDIO_CHRC(BT_UUID_PACS_SNK_LOC, BT_PACS_SNK_LOC_PROP, BT_PACS_SNK_LOC_PERM, snk_loc_read, \ + COND_CODE_1(CONFIG_BT_PAC_SNK_LOC_WRITEABLE, (snk_loc_write), (NULL)), NULL), \ + IF_ENABLED(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE, (BT_AUDIO_CCC(snk_loc_cfg_changed), )) + +/* declaration + value [+ cccd] */ +#define PACS_SNK_PAC_LOC_CHAR_ATTR_COUNT \ + COND_CODE_1(IS_ENABLED(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE), (3), (2)) +#else +#define BT_PACS_SNK_LOC +#define PACS_SNK_PAC_LOC_CHAR_ATTR_COUNT 0U +#endif /* CONFIG_BT_PAC_SNK_LOC*/ + +#if defined(CONFIG_BT_PAC_SRC) +#define BT_PACS_SRC_PROP \ + BT_GATT_CHRC_READ \ + IF_ENABLED(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE, (| BT_GATT_CHRC_NOTIFY)) +#define BT_PAC_SRC \ + BT_AUDIO_CHRC(BT_UUID_PACS_SRC, BT_PACS_SRC_PROP, BT_GATT_PERM_READ_ENCRYPT, src_read, NULL, \ + NULL), \ + IF_ENABLED(CONFIG_BT_PAC_SRC_NOTIFIABLE, (BT_AUDIO_CCC(src_cfg_changed), )) + +#define BT_PACS_SRC_LOC_PROP \ + BT_GATT_CHRC_READ \ + IF_ENABLED(CONFIG_BT_PAC_SRC_LOC_WRITEABLE, (| BT_GATT_CHRC_WRITE)) \ + IF_ENABLED(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE, (| BT_GATT_CHRC_NOTIFY)) + +#define BT_PACS_SRC_LOC_PERM \ + BT_GATT_PERM_READ_ENCRYPT \ + IF_ENABLED(CONFIG_BT_PAC_SRC_LOC_WRITEABLE, (| BT_GATT_PERM_WRITE_ENCRYPT)) + +/* declaration + value [+ cccd] */ +#define PACS_SRC_PAC_CHAR_ATTR_COUNT COND_CODE_1(IS_ENABLED(CONFIG_BT_PAC_SRC_NOTIFIABLE), (3), (2)) +#else +#define BT_PAC_SRC +#define PACS_SRC_PAC_CHAR_ATTR_COUNT 0U +#endif + +#if defined(CONFIG_BT_PAC_SRC_LOC) +#define BT_PACS_SRC_LOC \ + BT_AUDIO_CHRC(BT_UUID_PACS_SRC_LOC, BT_PACS_SRC_LOC_PROP, BT_PACS_SRC_LOC_PERM, src_loc_read, \ + COND_CODE_1(CONFIG_BT_PAC_SRC_LOC_WRITEABLE, (src_loc_write), (NULL)), NULL), \ + IF_ENABLED(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE, (BT_AUDIO_CCC(src_loc_cfg_changed), )) + +/* declaration + value [+ cccd] */ +#define PACS_SRC_PAC_LOC_CHAR_ATTR_COUNT \ + COND_CODE_1(IS_ENABLED(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE), (3), (2)) +#else +#define BT_PACS_SRC_LOC +#define PACS_SRC_PAC_LOC_CHAR_ATTR_COUNT 0U +#endif + +#define BT_PAC_AVAILABLE_CONTEXT \ + BT_AUDIO_CHRC(BT_UUID_PACS_AVAILABLE_CONTEXT, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, available_contexts_read, NULL, NULL), \ + BT_AUDIO_CCC(available_context_cfg_changed), + +#define BT_PACS_SUPPORTED_CONTEXT_PROP \ + BT_GATT_CHRC_READ \ + IF_ENABLED(CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE, (| BT_GATT_CHRC_NOTIFY)) + +#define BT_PAC_SUPPORTED_CONTEXT \ + BT_AUDIO_CHRC(BT_UUID_PACS_SUPPORTED_CONTEXT, BT_PACS_SUPPORTED_CONTEXT_PROP, \ + BT_GATT_PERM_READ_ENCRYPT, supported_context_read, NULL, NULL), \ + IF_ENABLED(CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE, \ + (BT_AUDIO_CCC(supported_context_cfg_changed), )) + +#define BT_PACS_SERVICE_DEFINITION() \ + {BT_GATT_PRIMARY_SERVICE(BT_UUID_PACS), \ + BT_PAC_SNK BT_PACS_SNK_LOC BT_PAC_SRC BT_PACS_SRC_LOC BT_PAC_AVAILABLE_CONTEXT \ + BT_PAC_SUPPORTED_CONTEXT} + +static const struct bt_gatt_attr _pacs_attrs[] = + BT_PACS_SERVICE_DEFINITION(); +static struct bt_gatt_attr pacs_attrs[] = BT_PACS_SERVICE_DEFINITION(); +static struct bt_gatt_service pacs_svc = + (struct bt_gatt_service)BT_GATT_SERVICE(pacs_attrs); + +static void configure_pacs_char(const struct bt_pacs_register_param *param) +{ + const uint8_t first_attr_offset = 1U; + struct bt_gatt_attr *svc_attrs = + + /* first attribute is the service */ + + &pacs_svc.attrs[first_attr_offset]; + uint8_t attrs_to_rem = 0U; + uint8_t first_to_rem = 0U; + + /* Remove the Sink PAC and Location */ +#if defined(CONFIG_BT_PAC_SNK_LOC) + if (!param->snk_loc) + { + first_to_rem = PACS_SNK_PAC_CHAR_ATTR_COUNT; + attrs_to_rem = PACS_SNK_PAC_LOC_CHAR_ATTR_COUNT; + } +#endif /* CONFIG_BT_PAC_SNK_LOC */ +#if defined(CONFIG_BT_PAC_SNK) + + if (!param->snk_pac) + { + first_to_rem = 0U; + attrs_to_rem = + PACS_SNK_PAC_CHAR_ATTR_COUNT + + PACS_SNK_PAC_LOC_CHAR_ATTR_COUNT; + } +#endif /* CONFIG_BT_PAC_SNK */ + + if (attrs_to_rem > 0U) + { + for (uint8_t i = first_to_rem + + attrs_to_rem; i < pacs_svc.attr_count - first_attr_offset; + i++) + { + svc_attrs[i - attrs_to_rem] = svc_attrs[i]; + } + + pacs_svc.attr_count -= attrs_to_rem; + } + +#if defined(CONFIG_BT_PAC_SRC) + /* Set first_to_rem to the start of Source PAC Char, for cleaner offset + * calc + */ + + const uint8_t src_pac_offset = + (PACS_SNK_PAC_CHAR_ATTR_COUNT + + PACS_SNK_PAC_LOC_CHAR_ATTR_COUNT) - attrs_to_rem; + + attrs_to_rem = 0U; + + /* Remove the Source PAC and Location */ +#if defined(CONFIG_BT_PAC_SRC_LOC) + if (!param->src_loc) + { + first_to_rem = src_pac_offset + PACS_SRC_PAC_CHAR_ATTR_COUNT; + attrs_to_rem = PACS_SRC_PAC_LOC_CHAR_ATTR_COUNT; + } +#endif /* CONFIG_BT_PAC_SRC_LOC */ + + if (!param->src_pac) + { + first_to_rem = src_pac_offset; + attrs_to_rem = + PACS_SRC_PAC_CHAR_ATTR_COUNT + + PACS_SRC_PAC_LOC_CHAR_ATTR_COUNT; + } + + if (attrs_to_rem > 0U) + { + for (uint8_t i = first_to_rem + + attrs_to_rem; i < pacs_svc.attr_count - first_attr_offset; + i++) + { + svc_attrs[i - attrs_to_rem] = svc_attrs[i]; + } + + pacs_svc.attr_count -= attrs_to_rem; + } +#endif /* CONFIG_BT_PAC_SRC */ +} + +static bool valid_pacs_register_param( + const struct bt_pacs_register_param *param) +{ + bool any_pac_registered = false; + + if (param == NULL) + { + LOG_DBG("param is NULL"); + return false; + } + +#if defined(CONFIG_BT_PAC_SNK) + any_pac_registered |= param->snk_pac; +#endif /* CONFIG_BT_PAC_SNK */ +#if defined(CONFIG_BT_PAC_SNK_LOC) + if (param->snk_loc && !param->snk_pac) + { + LOG_DBG("Cannot register snk_loc without snk_pac"); + return false; + } +#endif /* CONFIG_BT_PAC_SNK_LOC */ +#if defined(CONFIG_BT_PAC_SRC) + + any_pac_registered |= param->src_pac; +#endif /* CONFIG_BT_PAC_SRC */ +#if defined(CONFIG_BT_PAC_SRC_LOC) + if (param->src_loc && !param->src_pac) + { + LOG_DBG("Cannot register src_loc without src_pac"); + return false; + } +#endif /* CONFIG_BT_PAC_SRC_LOC */ + + if (!any_pac_registered) + { + LOG_DBG("Neither snk_pac or src_pac registered"); + return false; + } + + return true; +} + +int bt_pacs_register(const struct bt_pacs_register_param *param) +{ + int err = 0; + + if (!valid_pacs_register_param(param)) + { + return -EINVAL; + } + + if (atomic_test_and_set_bit(pacs.flags, PACS_FLAG_REGISTERED)) + { + LOG_DBG("PACS already registered"); + + return -EALREADY; + } + + /* Save registration param so we can guard functions accordingly */ +#if defined(CONFIG_BT_PAC_SNK) + atomic_set_bit_to(pacs.flags, PACS_FLAG_SNK_PAC, param->snk_pac); +#endif /* CONFIG_BT_PAC_SNK */ +#if defined(CONFIG_BT_PAC_SNK_LOC) + atomic_set_bit_to(pacs.flags, PACS_FLAG_SNK_LOC, param->snk_loc); +#endif /* CONFIG_BT_PAC_SNK_LOC */ +#if defined(CONFIG_BT_PAC_SRC) + atomic_set_bit_to(pacs.flags, PACS_FLAG_SRC_PAC, param->src_pac); +#endif /* CONFIG_BT_PAC_SRC */ +#if defined(CONFIG_BT_PAC_SRC_LOC) + atomic_set_bit_to(pacs.flags, PACS_FLAG_SRC_LOC, param->src_loc); +#endif /* CONFIG_BT_PAC_SRC_LOC */ + + /* Remove characteristics if necessary */ + + configure_pacs_char(param); + + err = bt_gatt_service_register(&pacs_svc); + if (err != 0) + { + LOG_DBG("Failed to register PACS in gatt DB: %d", err); + atomic_clear_bit(pacs.flags, PACS_FLAG_REGISTERED); + + return -ENOEXEC; + } + + pacs.available_ctx_attr = + bt_gatt_find_by_uuid(pacs_svc.attrs, pacs_svc.attr_count, + BT_UUID_PACS_AVAILABLE_CONTEXT); + DEBUGASSERT(pacs.available_ctx_attr != NULL); +#if defined(CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE) + pacs.supported_ctx_attr = + bt_gatt_find_by_uuid(pacs_svc.attrs, pacs_svc.attr_count, + BT_UUID_PACS_SUPPORTED_CONTEXT); + DEBUGASSERT(pacs.supported_ctx_attr != NULL); +#endif /* CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE */ +#if defined(CONFIG_BT_PAC_SNK_NOTIFIABLE) + if (param->snk_pac) + { + pacs.snk_pac_attr = + bt_gatt_find_by_uuid(pacs_svc.attrs, pacs_svc.attr_count, + BT_UUID_PACS_SNK); + DEBUGASSERT(pacs.snk_pac_attr != NULL); + } +#endif /* CONFIG_BT_PAC_SNK_NOTIFIABLE */ +#if defined(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE) + + if (param->snk_loc) + { + pacs.snk_pac_loc_attr = + bt_gatt_find_by_uuid(pacs_svc.attrs, pacs_svc.attr_count, + BT_UUID_PACS_SNK_LOC); + DEBUGASSERT(pacs.snk_pac_loc_attr != NULL); + } +#endif /* CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE */ +#if defined(CONFIG_BT_PAC_SRC_NOTIFIABLE) + + if (param->src_pac) + { + pacs.src_pac_attr = + bt_gatt_find_by_uuid(pacs_svc.attrs, pacs_svc.attr_count, + BT_UUID_PACS_SRC); + DEBUGASSERT(pacs.src_pac_attr != NULL); + } +#endif /* CONFIG_BT_PAC_SRC_NOTIFIABLE */ +#if defined(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE) + + if (param->src_loc) + { + pacs.src_pac_loc_attr = + bt_gatt_find_by_uuid(pacs_svc.attrs, pacs_svc.attr_count, + BT_UUID_PACS_SRC_LOC); + DEBUGASSERT(pacs.src_pac_loc_attr != NULL); + } +#endif /* CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE */ + + return 0; +} + +int bt_pacs_unregister(void) +{ + int err; + + if (!atomic_test_bit(pacs.flags, PACS_FLAG_REGISTERED)) + { + LOG_DBG("No pacs instance registered"); + + return -EALREADY; + } + + if (atomic_test_and_set_bit(pacs.flags, PACS_FLAG_SVC_CHANGING)) + { + LOG_DBG("Service change already in progress"); + atomic_clear_bit(pacs.flags, PACS_FLAG_SVC_CHANGING); + + return -EBUSY; + } + + err = bt_gatt_service_unregister(&pacs_svc); + + /* If unregistration was successful, make sure to reset pacs_attrs so it + * can be used for new registrations + */ + + if (err != 0) + { + LOG_DBG("Failed to unregister PACS"); + atomic_clear_bit(pacs.flags, PACS_FLAG_SVC_CHANGING); + + return err; + } + + /* Restore to original definition */ + + memcpy(pacs_svc.attrs, &_pacs_attrs, sizeof(_pacs_attrs)); + pacs_svc.attr_count = ARRAY_SIZE(pacs_attrs); + + pacs.available_ctx_attr = NULL; +#if defined(CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE) + pacs.supported_ctx_attr = NULL; +#endif /* CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE */ +#if defined(CONFIG_BT_PAC_SNK_NOTIFIABLE) + pacs.snk_pac_attr = NULL; +#endif /* CONFIG_BT_PAC_SNK_NOTIFIABLE */ +#if defined(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE) + pacs.snk_pac_loc_attr = NULL; +#endif /* CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE */ +#if defined(CONFIG_BT_PAC_SRC_NOTIFIABLE) + pacs.src_pac_attr = NULL; +#endif /* CONFIG_BT_PAC_SRC_NOTIFIABLE */ +#if defined(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE) + pacs.src_pac_loc_attr = NULL; +#endif /* CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE */ + + atomic_clear_bit(pacs.flags, PACS_FLAG_REGISTERED); + atomic_clear_bit(pacs.flags, PACS_FLAG_SVC_CHANGING); + + return err; +} + +#if defined(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE) || defined(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE) +static int pac_notify_loc(struct bt_conn *conn, enum bt_audio_dir dir) +{ + uint32_t location_le; + int err; + const struct bt_uuid *uuid; + + switch (dir) + { +#if defined(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE) + case BT_AUDIO_DIR_SINK: + location_le = sys_cpu_to_le32(pacs_snk_location); + uuid = pacs_snk_loc_uuid; + break; +#endif /* CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE */ +#if defined(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE) + case BT_AUDIO_DIR_SOURCE: + location_le = sys_cpu_to_le32(pacs_src_location); + uuid = pacs_src_loc_uuid; + break; +#endif /* CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE */ + default: + return -EINVAL; + } + + err = pacs_gatt_notify(conn, uuid, pacs_svc.attrs, &location_le, + sizeof(location_le)); + if (err != 0 && err != -ENOTCONN) + { + LOG_WRN("PACS notify_loc failed: %d", err); + return err; + } + + return 0; +} +#endif /* CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE || CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE */ + +#if defined(CONFIG_BT_PAC_SNK_NOTIFIABLE) || defined(CONFIG_BT_PAC_SRC_NOTIFIABLE) +static int pac_notify(struct bt_conn *conn, enum bt_audio_dir dir) +{ + int err = 0; + struct list_node *pac; + const struct bt_uuid *uuid; + + switch (dir) + { +#if defined(CONFIG_BT_PAC_SNK_NOTIFIABLE) + case BT_AUDIO_DIR_SINK: + uuid = pacs_snk_uuid; + break; +#endif /* CONFIG_BT_PAC_SNK_NOTIFIABLE */ +#if defined(CONFIG_BT_PAC_SRC_NOTIFIABLE) + case BT_AUDIO_DIR_SOURCE: + uuid = pacs_src_uuid; + break; +#endif /* CONFIG_BT_PAC_SRC_NOTIFIABLE */ + default: + return -EINVAL; + } + + err = nxsem_trywait(&read_buf_sem); + if (err != 0) + { + LOG_DBG("Failed to take read_buf_sem: %d", err); + + return err; + } + + pac = pacs_get_pac(dir); + DEBUGASSERT(pac); + get_pac_records(pac, &read_buf); + + err = pacs_gatt_notify(conn, uuid, pacs_svc.attrs, read_buf.data, + read_buf.len); + if (err != 0 && err != -ENOTCONN) + { + LOG_WRN("PACS notify failed: %d", err); + } + + nxsem_post(&read_buf_sem); + + if (err == -ENOTCONN) + { + return 0; + } + else + { + return 0; + } +} +#endif /* CONFIG_BT_PAC_SNK_NOTIFIABLE || CONFIG_BT_PAC_SRC_NOTIFIABLE */ + +static int available_contexts_notify(struct bt_conn *conn) +{ + struct bt_pacs_context context = + { + .snk = sys_cpu_to_le16(pacs_get_available_contexts_for_conn(conn, + BT_AUDIO_DIR_SINK)), + .src = sys_cpu_to_le16(pacs_get_available_contexts_for_conn(conn, + BT_AUDIO_DIR_SOURCE)), + }; + + int err; + + err = pacs_gatt_notify(conn, BT_UUID_PACS_AVAILABLE_CONTEXT, + pacs_svc.attrs, + &context, + sizeof(context)); + if (err != 0 && err != -ENOTCONN) + { + LOG_WRN("Available Audio Contexts notify failed: %d", err); + return err; + } + + return 0; +} + +#if defined(CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE) +static int supported_contexts_notify(struct bt_conn *conn) +{ + struct bt_pacs_context context = + { + .snk = sys_cpu_to_le16(supported_context_get(BT_AUDIO_DIR_SINK)), + .src = sys_cpu_to_le16(supported_context_get(BT_AUDIO_DIR_SOURCE)), + }; + + int err; + + err = pacs_gatt_notify(conn, BT_UUID_PACS_SUPPORTED_CONTEXT, + pacs_svc.attrs, + &context, + sizeof(context)); + if (err != 0 && err != -ENOTCONN) + { + LOG_WRN("Supported Audio Contexts notify failed: %d", err); + + return err; + } + + return 0; +} +#endif /* CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE */ + +void pacs_gatt_notify_complete_cb(struct bt_conn *conn, void *user_data) +{ + ARG_UNUSED(conn); + ARG_UNUSED(user_data); + + /* Notification done, clear bit and reschedule work */ + + atomic_clear_bit(pacs.flags, PACS_FLAG_NOTIFY_RDY); + k_work_submit(&deferred_nfy_work); +} + +static int pacs_gatt_notify(struct bt_conn *conn, + const struct bt_uuid *uuid, + const struct bt_gatt_attr *attr, + const void *data, + uint16_t len) +{ + int err; + struct bt_gatt_notify_params params; + + memset(¶ms, 0, sizeof(params)); + params.uuid = uuid; + params.attr = attr; + params.data = data; + params.len = len; + params.func = pacs_gatt_notify_complete_cb; + + /* Mark notification in progress */ + + if (atomic_test_bit(pacs.flags, PACS_FLAG_SVC_CHANGING) || + !atomic_test_bit(pacs.flags, PACS_FLAG_REGISTERED)) + { + return 0; + } + + atomic_set_bit(pacs.flags, PACS_FLAG_NOTIFY_RDY); + + err = bt_gatt_notify_cb(conn, ¶ms); + if (err != 0) + { + atomic_clear_bit(pacs.flags, PACS_FLAG_NOTIFY_RDY); + } + + if (err != 0 && err != -ENOTCONN) + { + return err; + } + + return 0; +} + +static void notify_cb(struct bt_conn *conn, void *data) +{ + struct pacs_client *client; + struct bt_conn_info info; + int err = 0; + + ARG_UNUSED(data); + + LOG_DBG(""); + + err = bt_conn_get_info(conn, &info); + if (err != 0) + { + LOG_ERR("Failed to get conn info: %d", err); + return; + } + + if (info.state != BT_CONN_STATE_CONNECTED) + { + /* Not connected */ + + return; + } + + client = client_lookup_conn(conn); + if (client == NULL) + { + return; + } + + /* Check if we have unverified notifications in progress */ + + if (atomic_test_bit(pacs.flags, PACS_FLAG_NOTIFY_RDY)) + { + return; + } + +#if defined(CONFIG_BT_PAC_SNK_NOTIFIABLE) + if (atomic_test_bit(client->flags, FLAG_SINK_PAC_CHANGED) && + bt_gatt_is_subscribed(conn, pacs.snk_pac_attr, BT_GATT_CCC_NOTIFY)) + { + LOG_DBG("Notifying Sink PAC"); + err = pac_notify(conn, BT_AUDIO_DIR_SINK); + if (err == 0) + { + atomic_clear_bit(client->flags, FLAG_SINK_PAC_CHANGED); + } + } +#endif /* CONFIG_BT_PAC_SNK_NOTIFIABLE */ + +#if defined(CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE) + if (atomic_test_bit(client->flags, FLAG_SINK_AUDIO_LOCATIONS_CHANGED) && + bt_gatt_is_subscribed(conn, pacs.snk_pac_loc_attr, BT_GATT_CCC_NOTIFY)) + { + LOG_DBG("Notifying Sink Audio Location"); + err = pac_notify_loc(conn, BT_AUDIO_DIR_SINK); + if (err == 0) + { + atomic_clear_bit(client->flags, FLAG_SINK_AUDIO_LOCATIONS_CHANGED); + } + } +#endif /* CONFIG_BT_PAC_SNK_LOC_NOTIFIABLE */ + +#if defined(CONFIG_BT_PAC_SRC_NOTIFIABLE) + if (atomic_test_bit(client->flags, FLAG_SOURCE_PAC_CHANGED) && + bt_gatt_is_subscribed(conn, pacs.src_pac_attr, BT_GATT_CCC_NOTIFY)) + { + LOG_DBG("Notifying Source PAC"); + err = pac_notify(conn, BT_AUDIO_DIR_SOURCE); + if (err == 0) + { + atomic_clear_bit(client->flags, FLAG_SOURCE_PAC_CHANGED); + } + } +#endif /* CONFIG_BT_PAC_SRC_NOTIFIABLE */ + +#if defined(CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE) + if (atomic_test_bit(client->flags, FLAG_SOURCE_AUDIO_LOCATIONS_CHANGED) && + bt_gatt_is_subscribed(conn, pacs.src_pac_loc_attr, BT_GATT_CCC_NOTIFY)) + { + LOG_DBG("Notifying Source Audio Location"); + err = pac_notify_loc(conn, BT_AUDIO_DIR_SOURCE); + if (err == 0) + { + atomic_clear_bit(client->flags, + FLAG_SOURCE_AUDIO_LOCATIONS_CHANGED); + } + } +#endif /* CONFIG_BT_PAC_SRC_LOC_NOTIFIABLE */ + + if (atomic_test_bit(client->flags, FLAG_AVAILABLE_AUDIO_CONTEXT_CHANGED) && + bt_gatt_is_subscribed(conn, pacs.available_ctx_attr, + BT_GATT_CCC_NOTIFY)) + { + LOG_DBG("Notifying Available Contexts"); + err = available_contexts_notify(conn); + if (err == 0) + { + atomic_clear_bit(client->flags, + FLAG_AVAILABLE_AUDIO_CONTEXT_CHANGED); + } + } + +#if defined(CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE) + if (atomic_test_bit(client->flags, FLAG_SUPPORTED_AUDIO_CONTEXT_CHANGED) && + bt_gatt_is_subscribed(conn, pacs.supported_ctx_attr, + BT_GATT_CCC_NOTIFY)) + { + LOG_DBG("Notifying Supported Contexts"); + err = supported_contexts_notify(conn); + if (err == 0) + { + atomic_clear_bit(client->flags, + FLAG_SUPPORTED_AUDIO_CONTEXT_CHANGED); + } + } +#endif /* CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE */ +} + +static void deferred_nfy_work_handler(struct k_work *work) +{ + ARG_UNUSED(work); + + bt_conn_foreach(BT_CONN_TYPE_LE, notify_cb, NULL); +} + +static void pacs_auth_pairing_complete(struct bt_conn *conn, bool bonded) +{ + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + LOG_DBG("%s paired (%sbonded)", bt_conn_dst_str(conn), + bonded ? "" : "not "); + + if (!bonded) + { + return; + } + + /* Check if already in list, and do nothing if it is */ + + for (size_t i = 0U; i < ARRAY_SIZE(pacs.clients); i++) + { + if (atomic_test_bit(pacs.clients[i].flags, FLAG_ACTIVE) && + bt_addr_le_eq(bt_conn_get_dst(conn), &pacs.clients[i].addr)) + { + return; + } + } + + /* Else add the device */ + + for (size_t i = 0U; i < ARRAY_SIZE(pacs.clients); i++) + { + if (!atomic_test_bit(pacs.clients[i].flags, FLAG_ACTIVE)) + { + atomic_set_bit(pacs.clients[i].flags, FLAG_ACTIVE); + memcpy(&pacs.clients[i].addr, bt_conn_get_dst(conn), + sizeof(bt_addr_le_t)); + + /* Send out all pending notifications */ + + k_work_submit(&deferred_nfy_work); + return; + } + } +} + +static void pacs_bond_deleted(uint8_t id, const bt_addr_le_t *peer) +{ + ARG_UNUSED(id); + + /* Find the device entry to delete */ + + for (size_t i = 0U; i < ARRAY_SIZE(pacs.clients); i++) + { + /* Check if match, and if active, if so, reset */ + + if (atomic_test_bit(pacs.clients[i].flags, FLAG_ACTIVE) && + bt_addr_le_eq(peer, &pacs.clients[i].addr)) + { + for (size_t j = 0U; j < FLAG_NUM; j++) + { + atomic_clear_bit(pacs.clients[i].flags, j); + } + + (void)memset(&pacs.clients[i].addr, 0, sizeof(bt_addr_le_t)); + return; + } + } +} + +static void pacs_security_changed(struct bt_conn *conn, bt_security_t level, + enum bt_security_err sec_err) +{ + struct bt_conn_info info; + int err; + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + LOG_DBG("%s changed security level to %d", bt_conn_dst_str(conn), level); + + if (sec_err != BT_SECURITY_ERR_SUCCESS || level <= BT_SECURITY_L1) + { + return; + } + + err = bt_conn_get_info(conn, &info); + if (err < 0) + { + DEBUGASSERT(false); + return; + } + + if (!bt_le_bond_exists(info.id, info.le.dst)) + { + return; + } + + for (size_t i = 0U; i < ARRAY_SIZE(pacs.clients); i++) + { + for (size_t j = 0U; j < FLAG_NUM; j++) + { + if (atomic_test_bit(pacs.clients[i].flags, j)) + { +/* It's enough that one flag is set, as the defer work will go + * through all notifiable characteristics + */ + + k_work_submit(&deferred_nfy_work); + return; + } + } + } +} + +static void pacs_disconnected(struct bt_conn *conn, uint8_t reason) +{ + struct pacs_client *client; + + ARG_UNUSED(reason); + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + client = client_lookup_conn(conn); + if (client == NULL) + { + return; + } + +#if defined(CONFIG_BT_PAC_SNK) + if (atomic_test_bit(pacs.flags, PACS_FLAG_SNK_PAC) && + client->snk_available_contexts != NULL) + { + uint16_t old = POINTER_TO_UINT(client->snk_available_contexts); + uint16_t new; + + client->snk_available_contexts = NULL; + new = pacs_get_available_contexts_for_conn(conn, BT_AUDIO_DIR_SINK); + + atomic_set_bit_to(client->flags, FLAG_AVAILABLE_AUDIO_CONTEXT_CHANGED, + old != new); + } +#endif /* CONFIG_BT_PAC_SNK */ + +#if defined(CONFIG_BT_PAC_SRC) + if (atomic_test_bit(pacs.flags, PACS_FLAG_SRC_PAC) && + client->src_available_contexts != NULL) + { + uint16_t old = POINTER_TO_UINT(client->src_available_contexts); + uint16_t new; + + client->src_available_contexts = NULL; + new = pacs_get_available_contexts_for_conn(conn, BT_AUDIO_DIR_SOURCE); + + atomic_set_bit_to(client->flags, FLAG_AVAILABLE_AUDIO_CONTEXT_CHANGED, + old != new); + } +#endif /* CONFIG_BT_PAC_SRC */ +} + +BT_CONN_CB_DEFINE(conn_callbacks) = +{ + .security_changed = pacs_security_changed, + .disconnected = pacs_disconnected, +}; + +static struct bt_conn_auth_info_cb auth_callbacks = +{ + .pairing_complete = pacs_auth_pairing_complete, + .bond_deleted = + pacs_bond_deleted +}; + +void bt_pacs_cap_foreach(enum bt_audio_dir dir, + bt_pacs_cap_foreach_func_t func, void *user_data) +{ + struct list_node *pac; + + if (func == NULL) + { + LOG_ERR("func is NULL"); + return; + } + + pac = pacs_get_pac(dir); + if (!pac) + { + return; + } + + foreach_cap(pac, func, user_data); +} + +static void add_bonded_addr_to_client_list(const struct bt_bond_info *info, + void *data) +{ + ARG_UNUSED(data); + + for (uint8_t i = 0U; i < ARRAY_SIZE(pacs.clients); i++) + { + /* Check if device is registered, it not, add it */ + + if (!atomic_test_bit(pacs.clients[i].flags, FLAG_ACTIVE)) + { + atomic_set_bit(pacs.clients[i].flags, FLAG_ACTIVE); + memcpy(&pacs.clients[i].addr, &info->addr, sizeof(bt_addr_le_t)); + LOG_DBG("Added %s to bonded list\n", + bt_addr_le_str(&pacs.clients[i].addr)); + return; + } + } +} + +/* Register Audio Capability */ + +int bt_pacs_cap_register(enum bt_audio_dir dir, struct bt_pacs_cap *cap) +{ + const struct bt_audio_codec_cap *codec_cap; + static bool first_register; + struct list_node *pac; + + if (!cap || !cap->codec_cap) + { + return -EINVAL; + } + + codec_cap = cap->codec_cap; + + pac = pacs_get_pac(dir); + if (!pac) + { + return -EINVAL; + } + + LOG_DBG("cap %p dir %s codec_cap id 0x%02x codec_cap cid 0x%04x codec_cap " + "vid 0x%04x", + cap, + bt_audio_dir_to_str(dir), codec_cap->id, codec_cap->cid, + codec_cap->vid); + + list_add_tail(pac, &cap->_node); + + if (!first_register) + { + __maybe_unused int err; + + err = bt_conn_auth_info_cb_register(&auth_callbacks); + DEBUGASSERT(err == 0); + + /* Restore bonding list */ + + bt_foreach_bond(BT_ID_DEFAULT, add_bonded_addr_to_client_list, NULL); + + first_register = true; + } + + if (IS_ENABLED(CONFIG_BT_PAC_SNK_NOTIFIABLE) && dir == BT_AUDIO_DIR_SINK) + { + pacs_set_notify_bit(FLAG_SINK_PAC_CHANGED); + k_work_submit(&deferred_nfy_work); + } + + if (IS_ENABLED(CONFIG_BT_PAC_SRC_NOTIFIABLE) && dir == BT_AUDIO_DIR_SOURCE) + { + pacs_set_notify_bit(FLAG_SOURCE_PAC_CHANGED); + k_work_submit(&deferred_nfy_work); + } + + return 0; +} + +/* Unregister Audio Capability */ + +int bt_pacs_cap_unregister(enum bt_audio_dir dir, struct bt_pacs_cap *cap) +{ + struct list_node *pac; + + if (!cap) + { + return -EINVAL; + } + + pac = pacs_get_pac(dir); + if (!pac) + { + return -EINVAL; + } + + LOG_DBG("cap %p dir %s", cap, bt_audio_dir_to_str(dir)); + + if (!list_find_and_remove(pac, &cap->_node)) + { + return -ENOENT; + } + + switch (dir) + { +#if defined(CONFIG_BT_PAC_SNK_NOTIFIABLE) + case BT_AUDIO_DIR_SINK: + pacs_set_notify_bit(FLAG_SINK_PAC_CHANGED); + k_work_submit(&deferred_nfy_work); + break; +#endif /* CONFIG_BT_PAC_SNK_NOTIFIABLE) */ +#if defined(CONFIG_BT_PAC_SRC_NOTIFIABLE) + case BT_AUDIO_DIR_SOURCE: + pacs_set_notify_bit(FLAG_SOURCE_PAC_CHANGED); + k_work_submit(&deferred_nfy_work); + break; +#endif /* CONFIG_BT_PAC_SRC_NOTIFIABLE */ + default: + return -EINVAL; + } + + return 0; +} + +int bt_pacs_set_location(enum bt_audio_dir dir, + enum bt_audio_location location) +{ + switch (dir) + { + case BT_AUDIO_DIR_SINK: + set_snk_location(location); + break; + case BT_AUDIO_DIR_SOURCE: + set_src_location(location); + break; + default: + return -EINVAL; + } + + return 0; +} + +int bt_pacs_set_available_contexts(enum bt_audio_dir dir, + enum bt_audio_context contexts) +{ + if (!atomic_test_bit(pacs.flags, PACS_FLAG_REGISTERED)) + { + return -EINVAL; + } + + switch (dir) + { + case BT_AUDIO_DIR_SINK: + if (atomic_test_bit(pacs.flags, PACS_FLAG_SNK_PAC)) + { + return set_available_contexts(contexts, &snk_available_contexts, + supported_context_get(dir)); + } + + return -EINVAL; + case BT_AUDIO_DIR_SOURCE: + if (atomic_test_bit(pacs.flags, PACS_FLAG_SRC_PAC)) + { + return set_available_contexts(contexts, &src_available_contexts, + supported_context_get(dir)); + } + + return -EINVAL; + } + + return -EINVAL; +} + +int bt_pacs_conn_set_available_contexts_for_conn( + struct bt_conn *conn, enum bt_audio_dir dir, + enum bt_audio_context *contexts) +{ + enum bt_audio_context old = pacs_get_available_contexts_for_conn(conn, + dir); + struct bt_conn_info info = + { + 0 + }; + + struct pacs_client *client; + int err; + + client = client_lookup_conn(conn); + if (client == NULL) + { + return -ENOENT; + } + + err = bt_conn_get_info(conn, &info); + if (err < 0) + { + LOG_ERR("Could not get conn info: %d", err); + return err; + } + + switch (dir) + { +#if defined(CONFIG_BT_PAC_SNK) + case BT_AUDIO_DIR_SINK: + if (atomic_test_bit(pacs.flags, PACS_FLAG_SNK_PAC)) + { + if (contexts != NULL) + { + client->snk_available_contexts = UINT_TO_POINTER(*contexts); + } + else + { + client->snk_available_contexts = NULL; + } + break; + } + + return -EINVAL; +#endif /* CONFIG_BT_PAC_SNK */ +#if defined(CONFIG_BT_PAC_SRC) + case BT_AUDIO_DIR_SOURCE: + if (atomic_test_bit(pacs.flags, PACS_FLAG_SRC_PAC)) + { + if (contexts != NULL) + { + client->src_available_contexts = UINT_TO_POINTER(*contexts); + } + else + { + client->src_available_contexts = NULL; + } + break; + } + + return -EINVAL; +#endif /* CONFIG_BT_PAC_SRC */ + default: + return -EINVAL; + } + + if (pacs_get_available_contexts_for_conn(conn, dir) == old) + { + /* No change. Skip notification */ + + return 0; + } + + atomic_set_bit(client->flags, FLAG_AVAILABLE_AUDIO_CONTEXT_CHANGED); + + /* Send notification on encrypted link only */ + + if (info.security.level > BT_SECURITY_L1) + { + k_work_submit(&deferred_nfy_work); + } + + return 0; +} + +int bt_pacs_set_supported_contexts(enum bt_audio_dir dir, + enum bt_audio_context contexts) +{ + uint16_t *supported_contexts = NULL; + uint16_t *available_contexts = NULL; + + switch (dir) + { + case BT_AUDIO_DIR_SINK: +#if defined(CONFIG_BT_PAC_SNK) + if (atomic_test_bit(pacs.flags, PACS_FLAG_SNK_PAC)) + { + supported_contexts = &snk_supported_contexts; + available_contexts = &snk_available_contexts; + break; + } + + return -EINVAL; +#endif /* CONFIG_BT_PAC_SNK */ + return -ENOTSUP; + case BT_AUDIO_DIR_SOURCE: +#if defined(CONFIG_BT_PAC_SRC) + if (atomic_test_bit(pacs.flags, PACS_FLAG_SRC_PAC)) + { + supported_contexts = &src_supported_contexts; + available_contexts = &src_available_contexts; + break; + } + + return -EINVAL; +#endif /* CONFIG_BT_PAC_SRC */ + return -ENOTSUP; + default: + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_PACS_SUPPORTED_CONTEXT_NOTIFIABLE) || + *supported_contexts == 0U) + { + return set_supported_contexts(contexts, supported_contexts, + available_contexts); + } + + return -EALREADY; +} + +enum bt_audio_context bt_pacs_get_available_contexts(enum bt_audio_dir dir) +{ + switch (dir) + { + case BT_AUDIO_DIR_SINK: + if (atomic_test_bit(pacs.flags, PACS_FLAG_SNK_PAC)) + { + return snk_available_contexts; + } + break; + case BT_AUDIO_DIR_SOURCE: + if (atomic_test_bit(pacs.flags, PACS_FLAG_SRC_PAC)) + { + return src_available_contexts; + } + break; + } + + return BT_AUDIO_CONTEXT_TYPE_NONE; +} + +enum bt_audio_context bt_pacs_get_available_contexts_for_conn( + struct bt_conn *conn, enum bt_audio_dir dir) +{ + if (conn == NULL) + { + LOG_ERR("NULL conn"); + return BT_AUDIO_CONTEXT_TYPE_NONE; + } + + return pacs_get_available_contexts_for_conn(conn, dir); +} + +struct codec_cap_lookup_id_data +{ + const struct bt_pac_codec *codec_id; + const struct bt_audio_codec_cap *codec_cap; +}; + +static bool codec_lookup_id(const struct bt_pacs_cap *cap, void *user_data) +{ + struct codec_cap_lookup_id_data *data = user_data; + + if (cap->codec_cap->id == data->codec_id->id && + cap->codec_cap->cid == data->codec_id->cid && + cap->codec_cap->vid == data->codec_id->vid) + { + data->codec_cap = cap->codec_cap; + + return false; + } + + return true; +} + +const struct bt_audio_codec_cap *bt_pacs_get_codec_cap(enum bt_audio_dir dir, + const struct bt_pac_codec *codec_id) +{ + struct codec_cap_lookup_id_data lookup_data = + { + .codec_id = codec_id, + .codec_cap = NULL, + }; + + bt_pacs_cap_foreach(dir, codec_lookup_id, &lookup_data); + + return lookup_data.codec_cap; +} diff --git a/libs/zblue/audio/pacs_internal.h b/libs/zblue/audio/pacs_internal.h new file mode 100644 index 0000000000000..99aabe9007b09 --- /dev/null +++ b/libs/zblue/audio/pacs_internal.h @@ -0,0 +1,59 @@ +/**************************************************************************** + * libs/zblue/audio/pacs_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* @file + * @brief Internal APIs for PACS handling + * + * Copyright (c) 2020 Intel Corporation Copyright (c) 2022 Nordic + * Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +#define BT_AUDIO_LOCATION_MASK BIT_MASK(28) + +struct bt_pac_codec +{ + uint8_t id; /* Codec ID */ + uint16_t cid; /* Company ID */ + uint16_t vid; /* Vendor specific Codec ID */ +} __packed; + +struct bt_pac_ltv +{ + uint8_t len; + uint8_t type; + uint8_t value[]; +} __packed; + +struct bt_pac_ltv_data +{ + uint8_t len; + struct bt_pac_ltv data[]; +} __packed; + +struct bt_pacs_read_rsp +{ + uint8_t num_pac; /* Number of PAC Records */ +} __packed; + +struct bt_pacs_context +{ + uint16_t snk; + uint16_t src; +} __packed; + +const struct bt_audio_codec_cap *bt_pacs_get_codec_cap(enum bt_audio_dir dir, + const struct bt_pac_codec *codec_id); diff --git a/libs/zblue/audio/pbp.c b/libs/zblue/audio/pbp.c new file mode 100644 index 0000000000000..60b2fd5314900 --- /dev/null +++ b/libs/zblue/audio/pbp.c @@ -0,0 +1,125 @@ +/**************************************************************************** + * libs/zblue/audio/pbp.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright 2023 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(bt_pbp, CONFIG_BT_PBP_LOG_LEVEL); + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bt_pbp_get_announcement(const uint8_t meta[], size_t meta_len, + enum bt_pbp_announcement_feature features, + struct net_buf_simple *pba_data_buf) +{ + if (pba_data_buf == NULL) + { + LOG_DBG("No buffer provided for advertising data!\n"); + + return -EINVAL; + } + + if ((meta == NULL && meta_len != 0U) || (meta != NULL && meta_len == 0U)) + { + LOG_DBG("Invalid metadata combination: %p %zu", meta, meta_len); + + return -EINVAL; + } + + if (pba_data_buf->size < (meta_len + BT_PBP_MIN_PBA_SIZE)) + { + LOG_DBG("Buffer size needs to be at least %d!\n", + meta_len + BT_PBP_MIN_PBA_SIZE); + + return -EINVAL; + } + + /* Fill Announcement data */ + + net_buf_simple_add_le16(pba_data_buf, BT_UUID_PBA_VAL); + net_buf_simple_add_u8(pba_data_buf, features); + net_buf_simple_add_u8(pba_data_buf, meta_len); + net_buf_simple_add_mem(pba_data_buf, meta, meta_len); + + return 0; +} + +int bt_pbp_parse_announcement(struct bt_data *data, + enum bt_pbp_announcement_feature *features, + uint8_t **meta) +{ + struct bt_uuid_16 adv_uuid; + struct net_buf_simple buf; + uint8_t meta_len = 0U; + void *uuid; + + if (!data || !features || !meta) + { + return -EINVAL; + } + + if (data->type != BT_DATA_SVC_DATA16) + { + return -ENOENT; + } + + if (data->data_len < BT_PBP_MIN_PBA_SIZE) + { + return -EMSGSIZE; + } + + net_buf_simple_init_with_data(&buf, (void *)data->data, data->data_len); + uuid = net_buf_simple_pull_mem(&buf, BT_UUID_SIZE_16); + + (void)bt_uuid_create(&adv_uuid.uuid, uuid, + BT_UUID_SIZE_16); /* cannot fail */ + + if (bt_uuid_cmp(&adv_uuid.uuid, BT_UUID_PBA)) + { + return -ENOENT; + } + + /* Copy source features, metadata length and metadata from the Announcement + */ + + *features = net_buf_simple_pull_u8(&buf); + meta_len = net_buf_simple_pull_u8(&buf); + if (buf.len < meta_len) + { + return -EBADMSG; + } + + *meta = (uint8_t *)net_buf_simple_pull_mem(&buf, meta_len); + + return meta_len; +} diff --git a/libs/zblue/audio/tbs.c b/libs/zblue/audio/tbs.c new file mode 100644 index 0000000000000..5934db6c967de --- /dev/null +++ b/libs/zblue/audio/tbs.c @@ -0,0 +1,4196 @@ +/**************************************************************************** + * libs/zblue/audio/tbs.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Bluetooth TBS - Telephone Bearer Service + * + * Copyright (c) 2020 Bose Corporation Copyright (c) 2021-2026 Nordic + * Semiconductor ASA Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "audio_internal.h" +#include "tbs_internal.h" +#include "common/bt_str.h" + +LOG_MODULE_REGISTER(bt_tbs, CONFIG_BT_TBS_LOG_LEVEL); + +#define BT_TBS_VALID_STATUS_FLAGS(val) ((val) <= (BIT(0U) | BIT(1U))) +#define MUTEX_TIMEOUT MSEC2TICK(CONFIG_BT_TBS_LOCK_TIMEOUT) + +struct tbs_flags +{ + bool bearer_provider_name_changed : 1; + bool bearer_provider_name_dirty : 1; + bool bearer_technology_changed : 1; + bool bearer_uri_schemes_supported_list_changed : 1; + bool bearer_uri_schemes_supported_list_dirty : 1; + bool bearer_signal_strength_changed : 1; + bool bearer_list_current_calls_changed : 1; + bool bearer_list_current_calls_dirty : 1; + bool status_flags_changed : 1; + bool incoming_call_target_bearer_uri_changed : 1; + bool incoming_call_target_bearer_uri_dirty : 1; + bool call_state_changed : 1; + bool call_state_dirty : 1; + bool termination_reason_changed : 1; + bool incoming_call_changed : 1; + bool incoming_call_dirty : 1; + bool call_friendly_name_changed : 1; + bool call_friendly_name_dirty : 1; +}; + +/* A service instance can either be a GTBS or a TBS instance */ + +struct tbs_inst +{ + /* Attribute values */ + + /* TODO: The provider name should be removed from the tbs_inst and instead + * by stored by the user of TBS. This will be done once the CCP API is + * complete as the CCP Server will own all the data instead of the TBS + */ + + char provider_name[CONFIG_BT_TBS_MAX_PROVIDER_NAME_LENGTH]; + char uci[BT_TBS_MAX_UCI_SIZE]; + enum bt_bearer_tech 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_friendly_name friendly_name; + struct bt_tbs_in_uri in_call; + char uri_scheme_list[CONFIG_BT_TBS_MAX_SCHEME_LIST_LENGTH]; + struct bt_tbs_terminate_reason terminate_reason; + struct bt_tbs_call calls[CONFIG_BT_TBS_MAX_CALLS]; + + bool pending_signal_strength_notification; + struct k_work_delayable reporting_interval_work; + + /** Service Attributes */ + + const struct bt_gatt_attr *attrs; + + /** Service Attribute count */ + + size_t attr_count; + + bool authorization_required; + + /* Flags for each client. Access and modification of these shall be guarded + * by the global tbs_mutex + */ + + struct tbs_flags flags[CONFIG_BT_MAX_CONN]; + + /* Control point notifications are handled separately from other + * notifications - We will not accept any new control point operations + * while a notification is pending + */ + + struct cp_ntf + { + struct bt_tbs_call_cp_notify notification; + + uint8_t conn_index; /* The conn index that triggered the request */ + bool pending : 1; + } cp_ntf; + + struct k_work_delayable notify_work; +}; + +static struct tbs_inst svc_insts[CONFIG_BT_TBS_BEARER_COUNT]; +static struct tbs_inst gtbs_inst; +static rmutex_t tbs_mutex = NXRMUTEX_INITIALIZER; + +static bool try_change_dialing_call_to_alerting(struct tbs_inst *inst); + +#define READ_BUF_SIZE \ + MAX(BT_ATT_MAX_ATTRIBUTE_LEN, \ + MAX((CONFIG_BT_TBS_MAX_CALLS * sizeof(struct bt_tbs_current_call_item) * \ + (1U + ARRAY_SIZE(svc_insts))), \ + (CONFIG_BT_TBS_BEARER_COUNT * CONFIG_BT_TBS_MAX_SCHEME_LIST_LENGTH + \ + CONFIG_BT_TBS_MAX_SCHEME_LIST_LENGTH))) +NET_BUF_SIMPLE_DEFINE_STATIC(read_buf, READ_BUF_SIZE); + +/* Used to notify app with held calls in case of join */ + +static struct bt_tbs_call *held_calls[CONFIG_BT_TBS_MAX_CALLS]; +static uint8_t held_calls_cnt; + +static struct bt_tbs_cb *tbs_cbs; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static bool inst_is_registered(const struct tbs_inst *inst) +{ + return inst->attrs != NULL; +} + +static bool inst_is_gtbs(const struct tbs_inst *inst) +{ + if (CONFIG_BT_TBS_BEARER_COUNT > 0) + { + return inst == >bs_inst; + } + else + { + return true; + } +} + +static uint8_t inst_index(const struct tbs_inst *inst) +{ + ptrdiff_t index = 0; + + DEBUGASSERT(inst); + + if (inst_is_gtbs(inst)) + { + return BT_TBS_GTBS_INDEX; + } + + index = inst - svc_insts; + DEBUGASSERT(index >= 0 && index < ARRAY_SIZE(svc_insts)); + + return (uint8_t)index; +} + +static struct tbs_inst *inst_lookup_index(uint8_t index) +{ + struct tbs_inst *inst = NULL; + + if (index == BT_TBS_GTBS_INDEX) + { + inst = >bs_inst; + } + else if (ARRAY_SIZE(svc_insts) > 0U && index < ARRAY_SIZE(svc_insts)) + { + inst = &svc_insts[index]; + } + + if (inst == NULL || !inst_is_registered(inst)) + { + return NULL; + } + + return inst; +} + +static struct bt_tbs_call *lookup_call_in_inst(struct tbs_inst *inst, + uint8_t call_index) +{ + if (call_index == BT_TBS_FREE_CALL_INDEX) + { + return NULL; + } + + for (int i = 0; i < ARRAY_SIZE(svc_insts[i].calls); i++) + { + if (inst->calls[i].index == call_index) + { + return &inst->calls[i]; + } + } + + return NULL; +} + +/** + * @brief Finds and returns a call + * + * @param call_index The ID of the call + * @return struct bt_tbs_call* Pointer to the call. NULL if not found + */ + +static struct bt_tbs_call *lookup_call(uint8_t call_index) +{ + struct bt_tbs_call *call; + + if (call_index == BT_TBS_FREE_CALL_INDEX) + { + return NULL; + } + + call = lookup_call_in_inst(>bs_inst, call_index); + if (call != NULL) + { + return call; + } + + for (size_t i = 0U; i < ARRAY_SIZE(svc_insts); i++) + { + call = lookup_call_in_inst(&svc_insts[i], call_index); + if (call != NULL) + { + return call; + } + } + + return NULL; +} + +static bool inst_check_attr(struct tbs_inst *inst, + const struct bt_gatt_attr *attr) +{ + for (size_t j = 0U; j < inst->attr_count; j++) + { + if (&inst->attrs[j] == attr) + { + return true; + } + } + + return false; +} + +static struct tbs_inst *lookup_inst_by_attr(const struct bt_gatt_attr *attr) +{ + if (attr == NULL) + { + return NULL; + } + + for (int i = 0; i < ARRAY_SIZE(svc_insts); i++) + { + if (inst_check_attr(&svc_insts[i], attr)) + { + return &svc_insts[i]; + } + } + + if (inst_check_attr(>bs_inst, attr)) + { + return >bs_inst; + } + + return NULL; +} + +static struct tbs_inst *lookup_inst_by_call_index(uint8_t call_index) +{ + if (call_index == BT_TBS_FREE_CALL_INDEX) + { + return NULL; + } + + if (lookup_call_in_inst(>bs_inst, call_index) != NULL) + { + return >bs_inst; + } + + for (size_t i = 0U; i < ARRAY_SIZE(svc_insts); i++) + { + if (lookup_call_in_inst(&svc_insts[i], call_index) != NULL) + { + return &svc_insts[i]; + } + } + + return NULL; +} + +static bool is_authorized(const struct tbs_inst *inst, struct bt_conn *conn) +{ + if (inst->authorization_required) + { + if (tbs_cbs != NULL && tbs_cbs->authorize != NULL) + { + return tbs_cbs->authorize(conn); + } + else + { + return false; + } + } + + return true; +} + +static bool uri_scheme_in_list(const char *uri_scheme, + const char *uri_scheme_list) +{ + const size_t scheme_len = strlen(uri_scheme); + const char *start = uri_scheme_list; + const char *end; + + if (scheme_len == 0U) + { + return false; + } + + while (*start) + { + end = strchr(start, ','); + if (end == NULL) + { + /* If end is NULL, we set end to the NULL terminator of start */ + + end = start + strlen(start); + } + + if ((size_t)(end - start) == scheme_len && + memcmp(start, uri_scheme, scheme_len) == 0) + { + return true; + } + + if (*end == '\0') + { + break; + } + + /* Set start to the next character after `,` */ + + start = end + 1; + } + + return false; +} + +static struct tbs_inst *lookup_inst_by_uri_scheme(const uint8_t *uri, + uint8_t uri_len) +{ + char uri_scheme[CONFIG_BT_TBS_MAX_URI_LENGTH] = + { + 0 + }; + + if (uri_len == 0U) + { + return NULL; + } + + /* Look for ':' between the first and last char */ + + for (uint8_t i = 1U; i < uri_len - 1U; i++) + { + /* If the size of the URI scheme of `uri` is larger than what we + * support, then we do not need to search any instances, as we cannot + * possibly support it. + */ + + if (i > (sizeof(uri_scheme) - 1U /* NULL terminator */)) + { + return NULL; + } + + if (uri[i] == ':') + { + (void)memcpy(uri_scheme, uri, i); + break; + } + } + + if (uri_scheme[0] == '\0') + { + /* No URI scheme found */ + + return NULL; + } + + for (size_t i = 0U; i < ARRAY_SIZE(svc_insts); i++) + { + for (size_t j = 0U; j < ARRAY_SIZE(svc_insts[i].calls); j++) + { + if (uri_scheme_in_list(uri_scheme, svc_insts[i].uri_scheme_list)) + { + return &svc_insts[i]; + } + } + } + + /* If not found in any TBS instance, check GTBS */ + + if (uri_scheme_in_list(uri_scheme, gtbs_inst.uri_scheme_list)) + { + return >bs_inst; + } + + return NULL; +} + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + uint8_t conn_index; + int err; + + ARG_UNUSED(reason); + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + conn_index = bt_conn_index(conn); + + err = nxrmutex_ticklock(&tbs_mutex, MUTEX_TIMEOUT); + if (err != 0) + { + LOG_WRN("Failed to take mutex: %d", err); + /* In this case we still need to clear the data, so continue and hope + * for the best + */ + } + + /* Clear pending notifications */ + + for (size_t i = 0U; i < ARRAY_SIZE(svc_insts); i++) + { + if (svc_insts[i].cp_ntf.pending && + conn_index == svc_insts[i].cp_ntf.conn_index) + { + (void)memset(&svc_insts[i].cp_ntf, 0, sizeof(svc_insts[i].cp_ntf)); + } + + (void)memset(&svc_insts[i].flags[conn_index], 0, + sizeof(svc_insts[i].flags[conn_index])); + + /* Try to promote after clearing flags */ + + if (err == 0) + { + /* if mutex was locked */ + + (void)try_change_dialing_call_to_alerting(&svc_insts[i]); + } + } + + /* Clear pending GTBS notifications */ + + if (gtbs_inst.cp_ntf.pending && conn_index == gtbs_inst.cp_ntf.conn_index) + { + (void)memset(>bs_inst.cp_ntf, 0, sizeof(gtbs_inst.cp_ntf)); + } + + (void)memset(>bs_inst.flags[conn_index], 0, + sizeof(gtbs_inst.flags[conn_index])); + + if (err == 0) + { + /* if mutex was locked */ + + (void)try_change_dialing_call_to_alerting(>bs_inst); + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + } +} + +BT_CONN_CB_DEFINE(conn_cb) = +{ + .disconnected = disconnected, +}; + +static int notify(struct bt_conn *conn, + const struct bt_uuid *uuid, + const struct bt_gatt_attr *attrs, + const void *value, + size_t value_len) +{ + const int maxlen = bt_att_get_max_notify_size(conn, BT_ATT_CHAN_OPT_NONE); + + if (maxlen < 0) + { + DEBUGASSERT(maxlen != -EINVAL); + LOG_DBG("Failed to get max notification size for %p: %d", + conn, + maxlen); + return maxlen; /* contains errno value */ + } + + if (maxlen < value_len) + { + LOG_DBG("Truncating notification to %u (was %u)", maxlen, value_len); + value_len = maxlen; + } + + /* Send notification potentially truncated to the MTU */ + + return bt_gatt_notify_uuid(conn, uuid, attrs, value, value_len); +} + +struct tbs_notify_cb_info +{ + struct tbs_inst *inst; + const struct bt_gatt_attr *attr; + void (*value_cb)(struct tbs_flags *flags, bool set); +}; + +static void set_value_changed_cb(struct bt_conn *conn, void *data) +{ + struct tbs_notify_cb_info *cb_info = data; + struct tbs_inst *inst = cb_info->inst; + struct tbs_flags *flags = &inst->flags[bt_conn_index(conn)]; + const struct bt_gatt_attr *attr = cb_info->attr; + struct bt_conn_info info; + int err; + + err = bt_conn_get_info(conn, &info); + DEBUGASSERT(err == 0); + + if (info.state != BT_CONN_STATE_CONNECTED) + { + /* Not connected */ + + return; + } + + if (bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_NOTIFY)) + { + /* Set the specific flag based on the provided callback */ + + cb_info->value_cb(flags, true); + + /* We may schedule the same work multiple times, but that is OK as + * scheduling the same work multiple times is a no-op + */ + + err = k_work_schedule(&inst->notify_work, 0); + DEBUGASSERT(err >= 0); + } + else + { + /* Not subscribed, set the specific flag based on the provided callback + */ + + cb_info->value_cb(flags, false); + + /* Check if all flags are cleared, and if so then cancel any pending + * k_work + */ + + /* Ideally only _changed flags would be checked, but _dirty flags are + * included too. A cleaner check would require splitting the struct + */ + + if (util_memeq(flags, &(struct tbs_flags) + { + 0 + }, + + sizeof(*flags))) + { + (void)k_work_cancel_delayable(&inst->notify_work); + } + } +} + +static void set_value_changed(struct tbs_inst *inst, void( + *value_cb)(struct tbs_flags *flags, bool set), + const struct bt_uuid *uuid) +{ + struct tbs_notify_cb_info cb_info = + { + .inst = inst, + .value_cb = value_cb, + .attr = bt_gatt_find_by_uuid(inst->attrs, 0, uuid), + }; + + DEBUGASSERT(cb_info.attr != NULL); + bt_conn_foreach(BT_CONN_TYPE_LE, set_value_changed_cb, &cb_info); +} + +static void set_terminate_reason_changed_cb(struct tbs_flags *flags, + bool set) +{ + if (set && flags->termination_reason_changed) + { + LOG_DBG("pending notification replaced"); + } + + flags->termination_reason_changed = set; +} + +static void tbs_set_terminate_reason(struct tbs_inst *inst, + uint8_t call_index, uint8_t reason) +{ + inst->terminate_reason.call_index = call_index; + inst->terminate_reason.reason = reason; + LOG_DBG("Index %u: call index 0x%02x, reason %s", inst_index(inst), + call_index, + bt_tbs_term_reason_str(reason)); + + set_value_changed(inst, set_terminate_reason_changed_cb, + BT_UUID_TBS_TERMINATE_REASON); +} + +/** + * @brief Gets the next free call_index + * + * For each new call, the call index should be incremented and wrap at 255. + * However, the index = 0 is reserved for outgoing calls + * + * Call indexes are shared among all bearers, so there is always a 1:1 + * between a call index and a bearer + * + * @return uint8_t The next free call index + */ + +static uint8_t next_free_call_index(void) +{ + for (int i = 0; i < CONFIG_BT_TBS_MAX_CALLS; i++) + { + static uint8_t next_call_index; + const struct bt_tbs_call *call; + + /* For each new call, the call index should be incremented */ + + next_call_index++; + + if (next_call_index == BT_TBS_FREE_CALL_INDEX) + { + /* call_index = 0 reserved for outgoing calls */ + + next_call_index = 1U; + } + + call = lookup_call(next_call_index); + if (call == NULL) + { + return next_call_index; + } + } + + LOG_DBG("No more free call spots"); + + return BT_TBS_FREE_CALL_INDEX; +} + +static struct bt_tbs_call *call_alloc( + struct tbs_inst *inst, uint8_t state, const uint8_t *uri, uint16_t uri_len) +{ + struct bt_tbs_call *free_call = NULL; + + for (size_t i = 0U; i < ARRAY_SIZE(inst->calls); i++) + { + if (inst->calls[i].index == BT_TBS_FREE_CALL_INDEX) + { + free_call = &inst->calls[i]; + break; + } + } + + if (free_call == NULL) + { + return NULL; + } + + DEBUGASSERT(uri_len < sizeof(free_call->remote_uri)); + + memset(free_call, 0, sizeof(*free_call)); + + /* Get the next free call_index */ + + free_call->index = next_free_call_index(); + DEBUGASSERT(free_call->index != BT_TBS_FREE_CALL_INDEX); + + free_call->state = state; + (void)memcpy(free_call->remote_uri, uri, uri_len); + free_call->remote_uri[uri_len] = '\0'; + + return free_call; +} + +static void call_free(struct bt_tbs_call *call) +{ + call->index = BT_TBS_FREE_CALL_INDEX; +} + +static void net_buf_put_call_states_by_inst(const struct tbs_inst *inst, + struct net_buf_simple *buf) +{ + const struct bt_tbs_call *call; + const struct bt_tbs_call *calls; + size_t call_count; + + calls = inst->calls; + call_count = ARRAY_SIZE(inst->calls); + + for (size_t i = 0U; i < call_count; i++) + { + call = &calls[i]; + if (call->index == BT_TBS_FREE_CALL_INDEX) + { + continue; + } + + if (buf->len + 3U > buf->size) + { + LOG_WRN("Not able to store all call states in buffer"); + return; + } + + net_buf_simple_add_u8(buf, call->index); + net_buf_simple_add_u8(buf, call->state); + net_buf_simple_add_u8(buf, call->flags); + } +} + +static void net_buf_put_call_states(const struct tbs_inst *inst, + struct net_buf_simple *buf) +{ + net_buf_simple_reset(buf); + + net_buf_put_call_states_by_inst(inst, buf); + + /* For GTBS we add all the calls the GTBS bearer has itself, as well as all + * the other bearers + */ + + if (inst_is_gtbs(inst)) + { + for (size_t i = 0U; i < ARRAY_SIZE(svc_insts); i++) + { + net_buf_put_call_states_by_inst(&svc_insts[i], buf); + } + } +} + +static void net_buf_put_current_calls_by_inst(const struct tbs_inst *inst, + struct net_buf_simple *buf) +{ + const struct bt_tbs_call *call; + const struct bt_tbs_call *calls; + size_t call_count; + size_t uri_length; + size_t item_len; + + calls = inst->calls; + call_count = ARRAY_SIZE(inst->calls); + + for (size_t i = 0U; i < call_count; i++) + { + call = &calls[i]; + if (call->index == BT_TBS_FREE_CALL_INDEX) + { + continue; + } + + uri_length = strlen(call->remote_uri); + item_len = + sizeof(call->index) + + sizeof(call->state) + sizeof(call->flags) + uri_length; + + DEBUGASSERT(item_len <= UINT8_MAX); + + if (buf->len + sizeof(uint8_t) + item_len > buf->size) + { + LOG_WRN("Not able to store all calls in buffer"); + return; + } + + net_buf_simple_add_u8(buf, (uint8_t)item_len); + net_buf_simple_add_u8(buf, call->index); + net_buf_simple_add_u8(buf, call->state); + net_buf_simple_add_u8(buf, call->flags); + net_buf_simple_add_mem(buf, call->remote_uri, uri_length); + } +} + +static void net_buf_put_current_calls(const struct tbs_inst *inst, + struct net_buf_simple *buf) +{ + net_buf_simple_reset(buf); + + net_buf_put_current_calls_by_inst(inst, buf); + + /* For GTBS we add all the calls the GTBS bearer has itself, as well as all + * the other bearers + */ + + if (inst_is_gtbs(inst)) + { + for (size_t i = 0U; i < ARRAY_SIZE(svc_insts); i++) + { + net_buf_put_current_calls_by_inst(&svc_insts[i], buf); + } + } +} + +static bool is_tbs_uri_unique(const char *uri_list, + const char *uri_to_search) +{ + bool is_matched = false; + size_t uri_len = strlen(uri_to_search); + char *uri_pos = strstr(uri_list, uri_to_search); + + /* Below check is to avoid match for string like "aaa" with "aaas" or + * "saaa" string present in uri_list. + */ + + if ((uri_pos != NULL) && ((uri_pos == uri_list) || + (!isalnum(*(uri_pos - 1)))) && + (!isalnum(*(uri_pos + uri_len)))) + { + is_matched = true; + } + + return is_matched; +} + +static void net_buf_put_uri_scheme_list(const struct tbs_inst *inst, + struct net_buf_simple *buf) +{ + net_buf_simple_reset(buf); + + net_buf_simple_add_mem(buf, inst->uri_scheme_list, + strlen(inst->uri_scheme_list)); + + /* For GTBS add all the URI scheme list the GTBS bearer has itself, as well + * as all the other TBS bearers + */ + + if (!inst_is_gtbs(inst)) + { + return; + } + + for (size_t i = 0U; i < ARRAY_SIZE(svc_insts); i++) + { + char *uri_to_search = NULL; + + uri_to_search = strtok(svc_insts[i].uri_scheme_list, ","); + + /* append only unique URI prefix from all TBS instances in GTBS + * instance + */ + + while (uri_to_search != NULL) + { + if (!is_tbs_uri_unique((char *)buf->data, uri_to_search)) + { + if ((buf->len + strlen(uri_to_search) + 1) >= buf->size) + { + LOG_WRN("Cannot fit all TBS URI in GTBS URI list"); + return; + } + + /* add separator after each instance URI prefix list */ + + net_buf_simple_add_mem(buf, ",", 1); + net_buf_simple_add_mem(buf, uri_to_search, + strlen(uri_to_search)); + } + + uri_to_search = strtok(NULL, ","); + } + } +} + +static void set_call_state_changed_cb(struct tbs_flags *flags, bool set) +{ + if (set && flags->call_state_changed) + { + LOG_DBG("pending notification replaced"); + } + + flags->call_state_changed = set; + if (set) + { + flags->call_state_dirty = true; + } +} + +static void set_list_current_calls_changed_cb(struct tbs_flags *flags, + bool set) +{ + if (set && flags->bearer_list_current_calls_changed) + { + LOG_DBG("pending notification replaced"); + } + + flags->bearer_list_current_calls_changed = set; + if (set) + { + flags->bearer_list_current_calls_dirty = true; + } +} + +static void set_incoming_call_target_bearer_uri_changed_cb( + struct tbs_flags *flags, bool set) +{ + if (set && flags->incoming_call_target_bearer_uri_changed) + { + LOG_DBG("pending notification replaced"); + } + + flags->incoming_call_target_bearer_uri_changed = set; + if (set) + { + flags->incoming_call_target_bearer_uri_dirty = true; + } +} + +static void set_incoming_call_changed_cb(struct tbs_flags *flags, bool set) +{ + if (set && flags->incoming_call_changed) + { + LOG_DBG("pending notification replaced"); + } + + flags->incoming_call_changed = set; + if (set) + { + flags->incoming_call_dirty = true; + } +} + +static void set_call_friendly_name_changed_cb(struct tbs_flags *flags, + bool set) +{ + if (set && flags->call_friendly_name_changed) + { + LOG_DBG("pending notification replaced"); + } + + flags->call_friendly_name_changed = set; + if (set) + { + flags->call_friendly_name_dirty = true; + } +} + +static void set_bearer_provider_name_changed_cb(struct tbs_flags *flags, + bool set) +{ + if (set && flags->bearer_provider_name_changed) + { + LOG_DBG("pending notification replaced"); + } + + flags->bearer_provider_name_changed = set; + if (set) + { + flags->bearer_provider_name_dirty = true; + } +} + +static void set_bearer_technology_changed_cb(struct tbs_flags *flags, + bool set) +{ + if (set && flags->bearer_technology_changed) + { + LOG_DBG("pending notification replaced"); + } + + flags->bearer_technology_changed = set; +} + +static void set_signal_strength_changed_cb(struct tbs_flags *flags, bool set) +{ + if (set && flags->bearer_signal_strength_changed) + { + LOG_DBG("pending notification replaced"); + } + + flags->bearer_signal_strength_changed = set; +} + +static void set_status_flags_changed_cb(struct tbs_flags *flags, bool set) +{ + if (set && flags->status_flags_changed) + { + LOG_DBG("pending notification replaced"); + } + + flags->status_flags_changed = set; +} + +static void set_bearer_uri_schemes_supported_list_changed_cb( + struct tbs_flags *flags, bool set) +{ + if (set && flags->bearer_uri_schemes_supported_list_changed) + { + LOG_DBG("pending notification replaced"); + } + + flags->bearer_uri_schemes_supported_list_changed = set; + if (set) + { + flags->bearer_uri_schemes_supported_list_dirty = true; + } +} + +static int inst_notify_calls(struct tbs_inst *inst) +{ + set_value_changed(inst, set_call_state_changed_cb, BT_UUID_TBS_CALL_STATE); + set_value_changed(inst, set_list_current_calls_changed_cb, + BT_UUID_TBS_LIST_CURRENT_CALLS); + + return 0; +} + +static int notify_calls(struct tbs_inst *inst) +{ + int err; + + if (inst == NULL) + { + return -EINVAL; + } + + /* Notify TBS */ + + err = inst_notify_calls(inst); + if (err != 0) + { + return err; + } + + if (!inst_is_gtbs(inst)) + { + /* If the instance is different than the GTBS notify on the GTBS + * instance as well + */ + + err = inst_notify_calls(>bs_inst); + if (err != 0) + { + return err; + } + } + + return 0; +} + +static bool inst_has_pending_call_state_notifications( + const struct tbs_inst *inst) +{ + for (size_t i = 0U; i < ARRAY_SIZE(inst->flags); i++) + { + const struct tbs_flags *flags = &inst->flags[i]; + + if (flags->bearer_list_current_calls_changed || + flags->call_state_changed) + { + return true; + } + } + + return false; +} + +/** + * @brief Promote a BT_TBS_CALL_STATE_DIALING call to + * BT_TBS_CALL_STATE_ALERTING on a single instance. + * + * IMPORTANT: + * - This function MUST NOT be called unless both: + * inst_has_pending_call_state_notifications(inst) == false AND + * inst_has_pending_call_state_notifications(>bs_inst) == false. + * + * - The caller is responsible for checking these conditions. + * This helper performs no validation and assumes that pending notifications + * have already been flushed. + * + * - This split makes the API lightweight but also easy to misuse, + * so all callers must enforce the above preconditions. + * + * @return true if a call was promoted, false otherwise. + */ + +static bool promote_dialing_call_to_alerting(struct tbs_inst *inst) +{ + if (inst_has_pending_call_state_notifications(inst)) + { + return false; + } + + for (size_t i = 0U; i < ARRAY_SIZE(inst->calls); i++) + { + if (inst->calls[i].state == BT_TBS_CALL_STATE_DIALING) + { + inst->calls[i].state = BT_TBS_CALL_STATE_ALERTING; + notify_calls(inst); + return true; + } + } + + return false; +} + +/** + * @brief Attempt to move a call in an instance from DIALING to ALERTING. + * + * This function checks the state of the given instance and promotes any + * BT_TBS_CALL_STATE_DIALING call to the BT_TBS_CALL_STATE_ALERTING state, + * provided that there are no pending. call-state or current-calls + * notifications that must be flushed first. + * + * Notification ordering requirement: + * - We must first notify the BT_TBS_CALL_STATE_DIALING state. + * - Only after all pending notifications have been completed may the call + * be promoted to BT_TBS_CALL_STATE_ALERTING. + * - Once promoted, the BT_TBS_CALL_STATE_ALERTING state is notified + * immediately. + * + * GTBS/TBS behavior: + * - For a regular TBS instance, promotion may only occur once GTBS has + * finished sending its pending notifications. + * - For GTBS, promotion is attempted first on GTBS-owned calls, and if + * none exist, on all registered TBS bearers. + * + * NOTE: The lower-level helper promote_dialing_call_to_alerting() must never + * be called directly unless the caller has ensured that both the target + * instance and GTBS have no pending notifications. This function performs + * all required safety checks and is the only safe entry point. + * + * @param inst The instance (GTBS or TBS) to attempt the promotion on. + * @retval true A call was promoted to ALERTING. + * @retval false No promotion occurred. + */ + +/* Caller must lock tbs_mutex before calling this function */ + +static bool try_change_dialing_call_to_alerting(struct tbs_inst *inst) +{ + bool promoted = false; + + /* Non-GTBS: only promote on this bearer, and only once GTBS + * is done sending its own notifications. + */ + + if (!inst_is_gtbs(inst)) + { + /* Check GTBS pending notifications */ + + if (inst_has_pending_call_state_notifications(>bs_inst)) + { + return false; + } + + if (promote_dialing_call_to_alerting(inst)) + { + promoted = true; + } + + return promoted; + } + + /* GTBS path: Promote GTBS-owned calls */ + + if (promote_dialing_call_to_alerting(>bs_inst)) + { + promoted = true; + } + + /* Promote calls on all TBS bearers */ + + for (size_t i = 0U; i < ARRAY_SIZE(svc_insts); i++) + { + struct tbs_inst *tbs_inst = &svc_insts[i]; + + if (!inst_is_registered(tbs_inst)) + { + continue; + } + + if (promote_dialing_call_to_alerting(tbs_inst)) + { + promoted = true; + } + } + + return promoted; +} + +static void notify_handler_cb(struct bt_conn *conn, void *data) +{ + struct tbs_inst *inst = data; + struct tbs_flags *flags = &inst->flags[bt_conn_index(conn)]; + struct bt_conn_info info; + int err; + bool locked = false; + + err = bt_conn_get_info(conn, &info); + DEBUGASSERT(err == 0); + + if (info.state != BT_CONN_STATE_CONNECTED) + { + /* Not connected */ + + return; + } + + if (!inst_is_gtbs(inst)) + { + notify_handler_cb(conn, >bs_inst); + } + + err = nxrmutex_ticklock(&tbs_mutex, MUTEX_TIMEOUT); + if (err != 0) + { + LOG_DBG("Failed to take mutex: %d", err); + goto reschedule; + } + + locked = true; + + if (flags->bearer_provider_name_changed) + { + LOG_DBG("Notifying Bearer Provider Name: %s", inst->provider_name); + + err = notify(conn, BT_UUID_TBS_PROVIDER_NAME, inst->attrs, + inst->provider_name, + strlen(inst->provider_name)); + if (err == 0) + { + flags->bearer_provider_name_changed = false; + } + else + { + goto fail; + } + } + + if (flags->bearer_technology_changed) + { + const uint8_t tech = (uint8_t)inst->technology; + + LOG_DBG("Notifying Bearer Technology: %s (0x%02x)", + bt_bearer_tech_str(inst->technology), + tech); + + err = notify(conn, BT_UUID_TBS_TECHNOLOGY, inst->attrs, &tech, + sizeof(tech)); + if (err == 0) + { + flags->bearer_technology_changed = false; + } + else + { + goto fail; + } + } + + if (flags->bearer_uri_schemes_supported_list_changed) + { + LOG_DBG("Notifying Bearer URI schemes supported list: %s", + inst->uri_scheme_list); + + net_buf_put_uri_scheme_list(inst, &read_buf); + err = notify(conn, BT_UUID_TBS_URI_LIST, inst->attrs, read_buf.data, + read_buf.len); + if (err == 0) + { + flags->bearer_uri_schemes_supported_list_changed = false; + } + else + { + goto fail; + } + } + + if (flags->bearer_signal_strength_changed) + { + LOG_DBG("Notifying Bearer Signal Strength: 0x%02x", + inst->signal_strength); + + err = notify(conn, BT_UUID_TBS_SIGNAL_STRENGTH, inst->attrs, + &inst->signal_strength, + sizeof(inst->signal_strength)); + if (err == 0) + { + flags->bearer_signal_strength_changed = false; + } + else + { + goto fail; + } + } + + if (flags->bearer_list_current_calls_changed) + { + LOG_DBG("Notifying Bearer List Current Calls"); + + net_buf_put_current_calls(inst, &read_buf); + err = notify(conn, BT_UUID_TBS_LIST_CURRENT_CALLS, inst->attrs, + read_buf.data, read_buf.len); + if (err == 0) + { + flags->bearer_list_current_calls_changed = false; + } + else + { + goto fail; + } + + if (try_change_dialing_call_to_alerting(inst)) + { + goto reschedule; + } + } + + if (flags->status_flags_changed) + { + LOG_DBG("Notifying Status Flags: 0x%02x", inst->status_flags); + + err = notify(conn, BT_UUID_TBS_STATUS_FLAGS, inst->attrs, + &inst->status_flags, + sizeof(inst->status_flags)); + if (err == 0) + { + flags->status_flags_changed = false; + } + else + { + goto fail; + } + } + + if (flags->incoming_call_target_bearer_uri_changed) + { + LOG_DBG("Notifying Incoming Call Target Bearer URI: call index " + "0x%02x, " + "URI %s", + inst->incoming_uri.call_index, inst->incoming_uri.uri); + + err = notify(conn, BT_UUID_TBS_INCOMING_URI, inst->attrs, + &inst->incoming_uri, + sizeof(inst->incoming_uri.call_index) + + strlen(inst->incoming_uri.uri)); + if (err == 0) + { + flags->incoming_call_target_bearer_uri_changed = false; + } + else + { + goto fail; + } + } + + if (flags->call_state_changed) + { + LOG_DBG("Notifying Call States"); + + net_buf_put_call_states(inst, &read_buf); + err = notify(conn, BT_UUID_TBS_CALL_STATE, inst->attrs, read_buf.data, + read_buf.len); + if (err == 0) + { + flags->call_state_changed = false; + } + else + { + goto fail; + } + + if (try_change_dialing_call_to_alerting(inst)) + { + goto reschedule; + } + } + + if (flags->termination_reason_changed) + { + LOG_DBG("Notifying Bearer Provider Name: call_index 0x%02x reason " + "0x%02x", + inst->terminate_reason.call_index, + inst->terminate_reason.reason); + + err = notify(conn, BT_UUID_TBS_TERMINATE_REASON, inst->attrs, + &inst->terminate_reason, + sizeof(inst->terminate_reason)); + if (err == 0) + { + flags->termination_reason_changed = false; + } + else + { + goto fail; + } + } + + if (flags->incoming_call_changed) + { + LOG_DBG("Notifying Incoming Call: call index 0x%02x, URI %s", + inst->in_call.call_index, + inst->in_call.uri); + + err = notify(conn, BT_UUID_TBS_INCOMING_CALL, inst->attrs, + &inst->in_call, + sizeof(inst->in_call.call_index) + + strlen(inst->in_call.uri)); + if (err == 0) + { + flags->incoming_call_changed = false; + } + else + { + goto fail; + } + } + + if (flags->call_friendly_name_changed) + { + LOG_DBG("Notifying Friendly Name: call index 0x%02x, name %s", + inst->friendly_name.call_index, + inst->friendly_name.name); + + err = notify(conn, BT_UUID_TBS_FRIENDLY_NAME, inst->attrs, + &inst->friendly_name, + sizeof(inst->friendly_name.call_index) + + strlen(inst->friendly_name.name)); + if (err == 0) + { + flags->call_friendly_name_changed = false; + } + else + { + goto fail; + } + } + + /* The TBS spec is a bit unclear on this, but the TBS test spec states that + * the control operation notification shall be sent after the current calls + * and call state notifications, this shall be triggered after those. + */ + + if (inst->cp_ntf.pending && + bt_conn_index(conn) == inst->cp_ntf.conn_index && + !flags->bearer_list_current_calls_changed && + !flags->call_state_changed) + { + const struct bt_tbs_call_cp_notify *notification = + &inst->cp_ntf.notification; + + LOG_DBG("Notifying CCP: Call index %u, %s opcode and status %s", + notification->call_index, + bt_tbs_opcode_str(notification->opcode), + bt_tbs_status_str(notification->status)); + + err = notify(conn, BT_UUID_TBS_CALL_CONTROL_POINT, inst->attrs, + notification, + sizeof(*notification)); + if (err == 0) + { + inst->cp_ntf.pending = false; + } + else + { + goto fail; + } + } + +fail: + if (err != 0) + { + LOG_DBG("Notify failed (%d), retrying next connection interval", err); + reschedule: + err = k_work_reschedule(&inst->notify_work, + USEC2TICK(info.le.interval_us)); + DEBUGASSERT(err >= 0); + } + + if (locked) + { + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + } +} + +static void notify_work_handler(struct k_work *work) +{ + struct tbs_inst *inst = + CONTAINER_OF(k_work_delayable_from_work(work), struct tbs_inst, + notify_work); + + bt_conn_foreach(BT_CONN_TYPE_LE, notify_handler_cb, inst); +} + +static ssize_t read_provider_name( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); + struct tbs_flags *flags = &inst->flags[bt_conn_index(conn)]; + ssize_t ret; + int err; + + err = nxrmutex_ticklock(&tbs_mutex, MUTEX_TIMEOUT); + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + return -EBUSY; + } + + if (offset != 0 && flags->bearer_provider_name_dirty) + { + LOG_DBG("Value dirty for %p", (void *)conn); + ret = BT_GATT_ERR(BT_TBS_ERR_VAL_CHANGED); + } + else + { + flags->bearer_provider_name_dirty = false; + + LOG_DBG("Index %u, Provider name %s", inst_index(inst), + inst->provider_name); + + ret = bt_gatt_attr_read(conn, attr, buf, len, offset, + inst->provider_name, + strlen(inst->provider_name)); + } + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + + return ret; +} + +static void cfg_changed_clear_pending(struct tbs_inst *inst, uint16_t value, + void (*value_cb)( + struct tbs_flags *flags, + bool set), + const struct bt_uuid *uuid) +{ + if (value == 0U) + { + int err = nxrmutex_ticklock(&tbs_mutex, MUTEX_TIMEOUT); + + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + } + else + { + set_value_changed(inst, value_cb, uuid); + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + } + } +} + +static void provider_name_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + struct tbs_inst *inst = lookup_inst_by_attr(attr); + + if (inst != NULL) + { + /* Clear any pending notifications for any connections that unsubscribe + */ + + cfg_changed_clear_pending(inst, value, + set_bearer_provider_name_changed_cb, + BT_UUID_TBS_PROVIDER_NAME); + LOG_DBG("Index %u: value 0x%04x", inst_index(inst), value); + } +} + +static ssize_t read_uci( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); + + LOG_DBG("Index %u: UCI %s", inst_index(inst), inst->uci); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, inst->uci, + strlen(inst->uci)); +} + +static ssize_t read_technology( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); + const uint8_t tech = (uint8_t)inst->technology; + + LOG_DBG("Index %u: Technology 0x%02x", inst_index(inst), tech); + + return bt_gatt_attr_read(conn, + attr, + buf, + len, + offset, + &tech, + sizeof(tech)); +} + +static void technology_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + struct tbs_inst *inst = lookup_inst_by_attr(attr); + + if (inst != NULL) + { + /* Clear any pending notifications for any connections that unsubscribe + */ + + cfg_changed_clear_pending(inst, value, + set_bearer_technology_changed_cb, + BT_UUID_TBS_TECHNOLOGY); + LOG_DBG("Index %u: value 0x%04x", inst_index(inst), value); + } +} + +static ssize_t read_uri_scheme_list( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); + struct tbs_flags *flags = &inst->flags[bt_conn_index(conn)]; + ssize_t ret; + int err; + + err = nxrmutex_ticklock(&tbs_mutex, MUTEX_TIMEOUT); + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + return -EBUSY; + } + + if (offset != 0 && flags->bearer_uri_schemes_supported_list_dirty) + { + LOG_DBG("Value dirty for %p", (void *)conn); + ret = BT_GATT_ERR(BT_TBS_ERR_VAL_CHANGED); + } + else + { + flags->bearer_uri_schemes_supported_list_dirty = false; + + net_buf_put_uri_scheme_list(inst, &read_buf); + ret = bt_gatt_attr_read(conn, attr, buf, len, offset, read_buf.data, + read_buf.len); + } + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + + return ret; +} + +static void uri_scheme_list_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + struct tbs_inst *inst = lookup_inst_by_attr(attr); + + if (inst != NULL) + { + /* Clear any pending notifications for any connections that unsubscribe + */ + + cfg_changed_clear_pending(inst, value, + set_bearer_uri_schemes_supported_list_changed_cb, + BT_UUID_TBS_URI_LIST); + LOG_DBG("Index %u: value 0x%04x", inst_index(inst), value); + } +} + +static ssize_t read_signal_strength( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); + + LOG_DBG("Index %u: Signal strength 0x%02x", inst_index(inst), + inst->signal_strength); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + &inst->signal_strength, + sizeof(inst->signal_strength)); +} + +static void signal_strength_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + struct tbs_inst *inst = lookup_inst_by_attr(attr); + + if (inst != NULL) + { + /* Clear any pending notifications for any connections that unsubscribe + */ + + cfg_changed_clear_pending(inst, value, set_signal_strength_changed_cb, + BT_UUID_TBS_SIGNAL_STRENGTH); + LOG_DBG("Index %u: value 0x%04x", inst_index(inst), value); + } +} + +static ssize_t read_signal_strength_interval( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); + + LOG_DBG("Index %u: Signal strength interval 0x%02x", inst_index(inst), + inst->signal_strength_interval); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + &inst->signal_strength_interval, + sizeof(inst->signal_strength_interval)); +} + +static ssize_t write_signal_strength_interval( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); + struct net_buf_simple net_buf; + uint8_t signal_strength_interval; + + ARG_UNUSED(flags); + + if (!is_authorized(inst, conn)) + { + return BT_GATT_ERR(BT_ATT_ERR_AUTHORIZATION); + } + + if (offset != 0) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (len != sizeof(signal_strength_interval)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + net_buf_simple_init_with_data(&net_buf, (void *)buf, len); + signal_strength_interval = net_buf_simple_pull_u8(&net_buf); + + inst->signal_strength_interval = signal_strength_interval; + LOG_DBG("Index %u: 0x%02x", inst_index(inst), signal_strength_interval); + + return len; +} + +static void current_calls_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + struct tbs_inst *inst = lookup_inst_by_attr(attr); + + if (inst == NULL) + { + return; + } + + LOG_DBG("Index %u: value 0x%04x", inst_index(inst), value); + + if (value == 0U) + { + int err = nxrmutex_ticklock(&tbs_mutex, MUTEX_TIMEOUT); + + if (err != 0) + { + LOG_DBG("Failed to lock mutex: %d", err); + return; + } + + (void)try_change_dialing_call_to_alerting(inst); + + /* Clear any pending notifications for any connections that unsubscribe + */ + + set_value_changed(inst, set_list_current_calls_changed_cb, + BT_UUID_TBS_LIST_CURRENT_CALLS); + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + } +} + +static ssize_t read_current_calls( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); + struct tbs_flags *flags = &inst->flags[bt_conn_index(conn)]; + ssize_t ret; + int err; + + err = nxrmutex_ticklock(&tbs_mutex, MUTEX_TIMEOUT); + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + return -EBUSY; + } + + if (offset != 0 && flags->bearer_list_current_calls_dirty) + { + LOG_DBG("Value dirty for %p", (void *)conn); + ret = BT_GATT_ERR(BT_TBS_ERR_VAL_CHANGED); + } + else + { + flags->bearer_list_current_calls_dirty = false; + + LOG_DBG("Index %u", inst_index(inst)); + + net_buf_put_current_calls(inst, &read_buf); + + if (offset == 0) + { + LOG_HEXDUMP_DBG(read_buf.data, read_buf.len, "Current calls"); + } + + ret = bt_gatt_attr_read(conn, attr, buf, len, offset, read_buf.data, + read_buf.len); + } + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + + return ret; +} + +static ssize_t read_ccid( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); + + LOG_DBG("Index %u: CCID 0x%02x", inst_index(inst), inst->ccid); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &inst->ccid, + sizeof(inst->ccid)); +} + +static ssize_t read_status_flags( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); + const uint16_t status_flags_le = sys_cpu_to_le16(inst->optional_opcodes); + + LOG_DBG("Index %u: status_flags 0x%04x", inst_index(inst), + inst->status_flags); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &status_flags_le, + sizeof(status_flags_le)); +} + +static void status_flags_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + struct tbs_inst *inst = lookup_inst_by_attr(attr); + + if (inst != NULL) + { + /* Clear any pending notifications for any connections that unsubscribe + */ + + cfg_changed_clear_pending(inst, value, set_status_flags_changed_cb, + BT_UUID_TBS_STATUS_FLAGS); + LOG_DBG("Index %u: value 0x%04x", inst_index(inst), value); + } +} + +static ssize_t read_incoming_uri( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); + struct tbs_flags *flags = &inst->flags[bt_conn_index(conn)]; + ssize_t ret; + int err; + + err = nxrmutex_ticklock(&tbs_mutex, MUTEX_TIMEOUT); + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + return -EBUSY; + } + + if (offset != 0 && flags->incoming_call_target_bearer_uri_dirty) + { + LOG_DBG("Value dirty for %p", (void *)conn); + ret = BT_GATT_ERR(BT_TBS_ERR_VAL_CHANGED); + } + else + { + const struct bt_tbs_in_uri *inc_call_target; + + flags->incoming_call_target_bearer_uri_dirty = false; + + inc_call_target = &inst->incoming_uri; + + LOG_DBG("Index %u: call index 0x%02x, URI %s", inst_index(inst), + inc_call_target->call_index, + inc_call_target->uri); + + if (!inc_call_target->call_index) + { + LOG_DBG("URI not set"); + + ret = 0U; + } + else + { + const size_t val_len = + sizeof(inc_call_target->call_index) + + strlen(inc_call_target->uri); + ret = bt_gatt_attr_read(conn, attr, buf, len, offset, + inc_call_target, val_len); + } + } + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + + return ret; +} + +static void incoming_uri_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + struct tbs_inst *inst = lookup_inst_by_attr(attr); + + if (inst != NULL) + { + /* Clear any pending notifications for any connections that unsubscribe + */ + + cfg_changed_clear_pending(inst, value, + set_incoming_call_target_bearer_uri_changed_cb, + BT_UUID_TBS_INCOMING_URI); + LOG_DBG("Index %u: value 0x%04x", inst_index(inst), value); + } +} + +static ssize_t read_call_state( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); + struct tbs_flags *flags = &inst->flags[bt_conn_index(conn)]; + ssize_t ret; + int err; + + err = nxrmutex_ticklock(&tbs_mutex, MUTEX_TIMEOUT); + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + return -EBUSY; + } + + if (offset != 0 && flags->call_state_dirty) + { + LOG_DBG("Value dirty for %p", (void *)conn); + ret = BT_GATT_ERR(BT_TBS_ERR_VAL_CHANGED); + } + else + { + flags->call_state_dirty = false; + + LOG_DBG("Index %u", inst_index(inst)); + + net_buf_put_call_states(inst, &read_buf); + + if (offset == 0) + { + LOG_HEXDUMP_DBG(read_buf.data, read_buf.len, "Call state"); + } + + ret = bt_gatt_attr_read(conn, attr, buf, len, offset, read_buf.data, + read_buf.len); + } + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + + return ret; +} + +static void call_state_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + struct tbs_inst *inst = lookup_inst_by_attr(attr); + + if (inst == NULL) + { + return; + } + + LOG_DBG("Index %u: value 0x%04x", inst_index(inst), value); + + if (value == 0U) + { + int err = nxrmutex_ticklock(&tbs_mutex, MUTEX_TIMEOUT); + + if (err != 0) + { + LOG_DBG("Failed to lock mutex: %d", err); + return; + } + + (void)try_change_dialing_call_to_alerting(inst); + + /* Clear any pending notifications for any connections that unsubscribe + */ + + set_value_changed(inst, set_call_state_changed_cb, + BT_UUID_TBS_CALL_STATE); + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + } +} + +static void hold_other_calls(struct tbs_inst *inst, uint8_t call_index_cnt, + const uint8_t *call_indexes) +{ + held_calls_cnt = 0U; + + 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]; + held_calls_cnt++; + } + 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]; + held_calls_cnt++; + } + } +} + +static uint8_t accept_call(struct tbs_inst *inst, + const struct bt_tbs_call_cp_acc *ccp) +{ + struct bt_tbs_call *call = lookup_call_in_inst(inst, ccp->call_index); + + if (call == NULL) + { + return BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; + } + + if (call->state == BT_TBS_CALL_STATE_INCOMING) + { + call->state = BT_TBS_CALL_STATE_ACTIVE; + + hold_other_calls(inst, 1, &ccp->call_index); + + return BT_TBS_RESULT_CODE_SUCCESS; + } + else + { + return BT_TBS_RESULT_CODE_STATE_MISMATCH; + } +} + +static uint8_t terminate_call( + struct tbs_inst *inst, const struct bt_tbs_call_cp_term *ccp, + uint8_t reason) +{ + struct bt_tbs_call *call = lookup_call_in_inst(inst, ccp->call_index); + + if (call == NULL) + { + return BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; + } + + call_free(call); + tbs_set_terminate_reason(inst, ccp->call_index, reason); + + if (!inst_is_gtbs(inst)) + { + /* If the instance is different than the GTBS we set the termination + * reason and notify on the GTBS instance as well + */ + + tbs_set_terminate_reason(>bs_inst, ccp->call_index, reason); + } + + return BT_TBS_RESULT_CODE_SUCCESS; +} + +static uint8_t tbs_hold_call(struct tbs_inst *inst, + const struct bt_tbs_call_cp_hold *ccp) +{ + struct bt_tbs_call *call = lookup_call_in_inst(inst, ccp->call_index); + + if ((inst->optional_opcodes & BT_TBS_OPTIONAL_OPCODE_HOLD) == 0) + { + return BT_TBS_RESULT_CODE_OPCODE_NOT_SUPPORTED; + } + + if (call == NULL) + { + return BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; + } + + if (call->state == BT_TBS_CALL_STATE_ACTIVE) + { + call->state = BT_TBS_CALL_STATE_LOCALLY_HELD; + } + else if (call->state == BT_TBS_CALL_STATE_REMOTELY_HELD) + { + call->state = BT_TBS_CALL_STATE_LOCALLY_AND_REMOTELY_HELD; + } + else if (call->state == BT_TBS_CALL_STATE_INCOMING) + { + call->state = BT_TBS_CALL_STATE_LOCALLY_HELD; + } + else + { + return BT_TBS_RESULT_CODE_STATE_MISMATCH; + } + + return BT_TBS_RESULT_CODE_SUCCESS; +} + +static uint8_t retrieve_call(struct tbs_inst *inst, + const struct bt_tbs_call_cp_retrieve *ccp) +{ + struct bt_tbs_call *call = lookup_call_in_inst(inst, ccp->call_index); + + if ((inst->optional_opcodes & BT_TBS_OPTIONAL_OPCODE_HOLD) == 0) + { + return BT_TBS_RESULT_CODE_OPCODE_NOT_SUPPORTED; + } + + if (call == NULL) + { + return BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; + } + + if (call->state == BT_TBS_CALL_STATE_LOCALLY_HELD) + { + call->state = BT_TBS_CALL_STATE_ACTIVE; + } + else if (call->state == BT_TBS_CALL_STATE_LOCALLY_AND_REMOTELY_HELD) + { + call->state = BT_TBS_CALL_STATE_REMOTELY_HELD; + } + else + { + return BT_TBS_RESULT_CODE_STATE_MISMATCH; + } + + hold_other_calls(inst, 1, &ccp->call_index); + + return BT_TBS_RESULT_CODE_SUCCESS; +} + +static int originate_call( + struct tbs_inst *inst, const struct bt_tbs_call_cp_originate *ccp, + uint16_t uri_len, uint8_t *call_index) +{ + struct bt_tbs_call *call; + + /* Only allow one active outgoing call */ + + for (int i = 0; i < CONFIG_BT_TBS_MAX_CALLS; i++) + { + if (inst->calls[i].state == BT_TBS_CALL_STATE_ALERTING) + { + return BT_TBS_RESULT_CODE_OPERATION_NOT_POSSIBLE; + } + } + + if (!bt_tbs_valid_uri(ccp->uri, uri_len)) + { + return BT_TBS_RESULT_CODE_INVALID_URI; + } + + call = call_alloc(inst, BT_TBS_CALL_STATE_DIALING, ccp->uri, uri_len); + if (call == NULL) + { + return BT_TBS_RESULT_CODE_OUT_OF_RESOURCES; + } + + BT_TBS_CALL_FLAG_SET_OUTGOING(call->flags); + + hold_other_calls(inst, 1, &call->index); + + LOG_DBG("New call with call index %u", call->index); + + *call_index = call->index; + return BT_TBS_RESULT_CODE_SUCCESS; +} + +static uint8_t join_calls( + struct tbs_inst *inst, const struct bt_tbs_call_cp_join *ccp, + uint16_t call_index_cnt) +{ + struct bt_tbs_call *joined_calls[CONFIG_BT_TBS_MAX_CALLS]; + uint8_t call_state; + + if ((inst->optional_opcodes & BT_TBS_OPTIONAL_OPCODE_JOIN) == 0) + { + return BT_TBS_RESULT_CODE_OPERATION_NOT_POSSIBLE; + } + + /* Check length */ + + if (call_index_cnt < 2U || 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; + } + } + + /* 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_ACTIVE) + { + /* Remain active */ + } + else 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_REMOTELY_HELD) + { + /* Remain remotely held */ + } + 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_ALERTING) + { + /* Implementation specific - Treat as no-op */ + } + else if (call_state == BT_TBS_CALL_STATE_DIALING) + { + /* Implementation specific - Treat as no-op */ + } + else + { + DEBUGASSERT(false); + } + } + + hold_other_calls(inst, call_index_cnt, ccp->call_indexes); + + return BT_TBS_RESULT_CODE_SUCCESS; +} + +static void notify_app( + struct bt_conn *conn, struct tbs_inst *inst, uint16_t len, + const union bt_tbs_call_cp_t *ccp, uint8_t status, uint8_t call_index) +{ + ARG_UNUSED(status); + + if (tbs_cbs == NULL) + { + return; + } + + switch (ccp->opcode) + { + case BT_TBS_CALL_OPCODE_ACCEPT: + if (tbs_cbs->accept_call != NULL) + { + tbs_cbs->accept_call(conn, call_index); + } + break; + case BT_TBS_CALL_OPCODE_TERMINATE: + if (tbs_cbs->terminate_call != NULL) + { + tbs_cbs->terminate_call(conn, call_index, + inst->terminate_reason.reason); + } + break; + case BT_TBS_CALL_OPCODE_HOLD: + if (tbs_cbs->hold_call != NULL) + { + tbs_cbs->hold_call(conn, call_index); + } + break; + case BT_TBS_CALL_OPCODE_RETRIEVE: + if (tbs_cbs->retrieve_call != NULL) + { + tbs_cbs->retrieve_call(conn, call_index); + } + break; + case BT_TBS_CALL_OPCODE_ORIGINATE: + { + char uri[CONFIG_BT_TBS_MAX_URI_LENGTH + 1]; + const uint16_t uri_len = len - sizeof(ccp->originate); + bool remote_party_alerted = false; + struct bt_tbs_call *call; + + call = lookup_call_in_inst(inst, call_index); + + if (call == NULL) + { + LOG_DBG("Could not find call by call index 0x%02x", + call_index); + break; + } + + (void)memcpy(uri, ccp->originate.uri, uri_len); + uri[uri_len] = '\0'; + if (tbs_cbs->originate_call != NULL) + { + remote_party_alerted = + tbs_cbs->originate_call(conn, call_index, uri); + } + + if (!remote_party_alerted) + { + 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); + } + + 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 != 0U && tbs_cbs->hold_call != NULL) + { + for (int i = 0; i < held_calls_cnt; i++) + { + tbs_cbs->hold_call(conn, held_calls[i]->index); + } + } +} + +static bool is_valid_cp_len(uint16_t len, const union bt_tbs_call_cp_t *ccp) +{ + if (len < sizeof(ccp->opcode)) + { + return false; + } + + switch (ccp->opcode) + { + case BT_TBS_CALL_OPCODE_ACCEPT: + return len == sizeof(ccp->accept); + case BT_TBS_CALL_OPCODE_TERMINATE: + return len == sizeof(ccp->terminate); + case BT_TBS_CALL_OPCODE_HOLD: + return len == sizeof(ccp->hold); + case BT_TBS_CALL_OPCODE_RETRIEVE: + return len == sizeof(ccp->retrieve); + case BT_TBS_CALL_OPCODE_ORIGINATE: + return len >= sizeof(ccp->originate) + BT_TBS_MIN_URI_LEN; + case BT_TBS_CALL_OPCODE_JOIN: + return len >= sizeof(ccp->join) + 1; /* at least 1 call index */ + default: + return true; /* defer to future checks */ + } +} + +static ssize_t write_call_cp( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); + const union bt_tbs_call_cp_t *ccp = (union bt_tbs_call_cp_t *)buf; + struct tbs_inst *tbs = NULL; + uint8_t status; + uint8_t call_index = 0U; + const bool is_gtbs = inst_is_gtbs(inst); + bool calls_changed = false; + int err; + + ARG_UNUSED(flags); + + if (!is_authorized(inst, conn)) + { + return BT_GATT_ERR(BT_ATT_ERR_AUTHORIZATION); + } + + if (offset != 0) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (!is_valid_cp_len(len, ccp)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + LOG_DBG("Index %u: Processing the %s opcode", inst_index(inst), + bt_tbs_opcode_str(ccp->opcode)); + + err = nxrmutex_ticklock(&tbs_mutex, MUTEX_TIMEOUT); + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + return -EBUSY; + } + + if (inst->cp_ntf.pending) + { + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + + return BT_GATT_ERR(BT_TBS_RESULT_CODE_OPERATION_NOT_POSSIBLE); + } + + switch (ccp->opcode) + { + case BT_TBS_CALL_OPCODE_ACCEPT: + call_index = ccp->accept.call_index; + + if (is_gtbs) + { + tbs = lookup_inst_by_call_index(call_index); + if (tbs == NULL) + { + status = BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; + break; + } + } + else + { + tbs = inst; + } + + status = accept_call(tbs, &ccp->accept); + break; + case BT_TBS_CALL_OPCODE_TERMINATE: + call_index = ccp->terminate.call_index; + + if (is_gtbs) + { + tbs = lookup_inst_by_call_index(call_index); + if (tbs == NULL) + { + status = BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; + break; + } + } + else + { + tbs = inst; + } + + status = terminate_call(tbs, &ccp->terminate, + BT_TBS_REASON_CLIENT_TERMINATED); + break; + case BT_TBS_CALL_OPCODE_HOLD: + call_index = ccp->hold.call_index; + + if (is_gtbs) + { + tbs = lookup_inst_by_call_index(call_index); + if (tbs == NULL) + { + status = BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; + break; + } + } + else + { + tbs = inst; + } + + status = tbs_hold_call(tbs, &ccp->hold); + break; + case BT_TBS_CALL_OPCODE_RETRIEVE: + call_index = ccp->retrieve.call_index; + + if (is_gtbs) + { + tbs = lookup_inst_by_call_index(call_index); + if (tbs == NULL) + { + status = BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; + break; + } + } + else + { + tbs = inst; + } + + status = retrieve_call(tbs, &ccp->retrieve); + break; + case BT_TBS_CALL_OPCODE_ORIGINATE: + { + const uint16_t uri_len = len - sizeof(ccp->originate); + + if (is_gtbs) + { + tbs = lookup_inst_by_uri_scheme(ccp->originate.uri, uri_len); + if (tbs == NULL) + { + status = BT_TBS_RESULT_CODE_INVALID_URI; + break; + } + } + else + { + tbs = inst; + } + + status = originate_call(tbs, &ccp->originate, uri_len, + &call_index); + break; + } + + case BT_TBS_CALL_OPCODE_JOIN: + { + const uint16_t call_index_cnt = len - sizeof(ccp->join); + call_index = ccp->join.call_indexes[0]; + + if (is_gtbs) + { + tbs = lookup_inst_by_call_index(call_index); + if (tbs == NULL) + { + status = BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; + break; + } + } + else + { + tbs = inst; + } + + status = join_calls(tbs, &ccp->join, call_index_cnt); + break; + } + + default: + status = BT_TBS_RESULT_CODE_OPCODE_NOT_SUPPORTED; + call_index = 0U; + break; + } + + if (tbs != NULL) + { + LOG_DBG("Index %u: Processed the %s opcode with status %s for call " + "index %u", + inst_index(inst), bt_tbs_opcode_str(ccp->opcode), + bt_tbs_status_str(status), + call_index); + + if (status == BT_TBS_RESULT_CODE_SUCCESS) + { + const struct bt_tbs_call *call = lookup_call(call_index); + + if (call != NULL) + { + LOG_DBG("Call is now in the %s state", + bt_tbs_state_str(call->state)); + } + else + { + LOG_DBG("Call is now terminated"); + } + } + } + + if (status != BT_TBS_RESULT_CODE_SUCCESS) + { + call_index = 0U; + } + + if (tbs != NULL && status == BT_TBS_RESULT_CODE_SUCCESS) + { + notify_calls(tbs); + calls_changed = true; + (void)try_change_dialing_call_to_alerting(tbs); + } + + if (conn != NULL && bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_NOTIFY)) + { + struct bt_tbs_call_cp_notify *notification = + &inst->cp_ntf.notification; + + notification->call_index = call_index; + notification->opcode = ccp->opcode; + notification->status = status; + inst->cp_ntf.pending = true; + inst->cp_ntf.conn_index = bt_conn_index(conn); + + /* We may schedule the same work multiple times, but that is OK as + * scheduling the same work multiple times is a no-op + */ + + err = k_work_schedule(&inst->notify_work, 0); + DEBUGASSERT(err >= 0); + } + + /* else local operation; don't notify */ + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + + if (calls_changed) + { + notify_app(conn, tbs, len, ccp, status, call_index); + } + + return len; +} + +struct pending_cp_ntf_subscribed_info +{ + const struct bt_gatt_attr *attr; + uint8_t conn_index; + bool subscribed; +}; + +static void pending_cp_ntf_subscribed_cb(struct bt_conn *conn, void *data) +{ + struct pending_cp_ntf_subscribed_info *info = data; + + if (bt_conn_index(conn) == info->conn_index) + { + info->subscribed = bt_gatt_is_subscribed(conn, info->attr, + BT_GATT_CCC_NOTIFY); + } +} + +static bool pending_cp_ntf_subscribed(const struct tbs_inst *inst, + const struct bt_gatt_attr *attr) +{ + struct pending_cp_ntf_subscribed_info info = + { + .attr = attr, + .conn_index = inst->cp_ntf.conn_index, + .subscribed = false, + }; + + bt_conn_foreach(BT_CONN_TYPE_LE, pending_cp_ntf_subscribed_cb, &info); + + return info.subscribed; +} + +static void call_cp_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + struct tbs_inst *inst = lookup_inst_by_attr(attr); + + if (inst == NULL) + { + return; + } + + /* Clear the pending notification only when its owning connection + * unsubscribes + */ + + if (value == 0U) + { + int err = nxrmutex_ticklock(&tbs_mutex, MUTEX_TIMEOUT); + + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + } + else + { + if (inst->cp_ntf.pending && !pending_cp_ntf_subscribed(inst, attr)) + { + inst->cp_ntf.pending = false; + } + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + } + } + + LOG_DBG("Index %u: value 0x%04x", inst_index(inst), value); +} + +static ssize_t read_optional_opcodes( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + const struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); + const uint16_t optional_opcodes_le = + sys_cpu_to_le16(inst->optional_opcodes); + + LOG_DBG("Index %u: Supported opcodes 0x%02x", inst_index(inst), + inst->optional_opcodes); + + return bt_gatt_attr_read(conn, + attr, + buf, + len, + offset, + &optional_opcodes_le, + + sizeof(optional_opcodes_le)); +} + +static void terminate_reason_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + struct tbs_inst *inst = lookup_inst_by_attr(attr); + + if (inst != NULL) + { + /* Clear any pending notifications for any connections that unsubscribe + */ + + cfg_changed_clear_pending(inst, value, set_terminate_reason_changed_cb, + BT_UUID_TBS_TERMINATE_REASON); + LOG_DBG("Index %u: value 0x%04x", inst_index(inst), value); + } +} + +static ssize_t read_friendly_name( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); + struct tbs_flags *flags = &inst->flags[bt_conn_index(conn)]; + ssize_t ret; + int err; + + err = nxrmutex_ticklock(&tbs_mutex, MUTEX_TIMEOUT); + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + return -EBUSY; + } + + if (offset != 0 && flags->call_friendly_name_dirty) + { + LOG_DBG("Value dirty for %p", (void *)conn); + ret = BT_GATT_ERR(BT_TBS_ERR_VAL_CHANGED); + } + else + { + const struct bt_tbs_friendly_name *friendly_name = + &inst->friendly_name; + + flags->call_friendly_name_dirty = false; + + LOG_DBG("Index: 0x%02x call index 0x%02x, name %s", inst_index(inst), + friendly_name->call_index, friendly_name->name); + + if (friendly_name->call_index == BT_TBS_FREE_CALL_INDEX) + { + LOG_DBG("Friendly name not set"); + ret = 0; + } + else + { + const size_t val_len = + sizeof(friendly_name->call_index) + + strlen(friendly_name->name); + + ret = + bt_gatt_attr_read(conn, attr, buf, len, offset, friendly_name, + val_len); + } + } + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + + return ret; +} + +static void friendly_name_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + struct tbs_inst *inst = lookup_inst_by_attr(attr); + + if (inst != NULL) + { + /* Clear any pending notifications for any connections that unsubscribe + */ + + cfg_changed_clear_pending(inst, value, + set_call_friendly_name_changed_cb, + BT_UUID_TBS_FRIENDLY_NAME); + LOG_DBG("Index %u: value 0x%04x", inst_index(inst), value); + } +} + +static ssize_t read_incoming_call( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct tbs_inst *inst = BT_AUDIO_CHRC_USER_DATA(attr); + struct tbs_flags *flags = &inst->flags[bt_conn_index(conn)]; + ssize_t ret; + int err; + + err = nxrmutex_ticklock(&tbs_mutex, MUTEX_TIMEOUT); + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + return -EBUSY; + } + + if (offset != 0 && flags->incoming_call_dirty) + { + LOG_DBG("Value dirty for %p", (void *)conn); + ret = BT_GATT_ERR(BT_TBS_ERR_VAL_CHANGED); + } + else + { + const struct bt_tbs_in_uri *remote_uri = &inst->in_call; + + flags->incoming_call_dirty = false; + + LOG_DBG("Index: 0x%02x call index 0x%02x, URI %s", inst_index(inst), + remote_uri->call_index, + remote_uri->uri); + + if (remote_uri->call_index == BT_TBS_FREE_CALL_INDEX) + { + LOG_DBG("URI not set"); + + ret = 0; + } + else + { + const size_t val_len = + sizeof(remote_uri->call_index) + strlen(remote_uri->uri); + + ret = bt_gatt_attr_read(conn, attr, buf, len, offset, remote_uri, + val_len); + } + } + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + + return ret; +} + +static void in_call_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + struct tbs_inst *inst = lookup_inst_by_attr(attr); + + if (inst != NULL) + { + /* Clear any pending notifications for any connections that unsubscribe + */ + + cfg_changed_clear_pending(inst, value, set_incoming_call_changed_cb, + BT_UUID_TBS_INCOMING_CALL); + LOG_DBG("Index %u: value 0x%04x", inst_index(inst), value); + } +} + +#define BT_TBS_CHR_PROVIDER_NAME(inst) \ + BT_AUDIO_CHRC(BT_UUID_TBS_PROVIDER_NAME, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, read_provider_name, NULL, inst), \ + BT_AUDIO_CCC(provider_name_cfg_changed) + +#define BT_TBS_CHR_UCI(inst) \ + BT_AUDIO_CHRC(BT_UUID_TBS_UCI, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT, read_uci, NULL, inst) + +#define BT_TBS_CHR_TECHNOLOGY(inst) \ + BT_AUDIO_CHRC(BT_UUID_TBS_TECHNOLOGY, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, read_technology, NULL, inst), \ + BT_AUDIO_CCC(technology_cfg_changed) + +#define BT_TBS_CHR_URI_LIST(inst) \ + BT_AUDIO_CHRC(BT_UUID_TBS_URI_LIST, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, read_uri_scheme_list, NULL, inst), \ + BT_AUDIO_CCC(uri_scheme_list_cfg_changed) + +#define BT_TBS_CHR_SIGNAL_STRENGTH(inst) \ + BT_AUDIO_CHRC(BT_UUID_TBS_SIGNAL_STRENGTH, /* Optional */ \ + BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_READ_ENCRYPT, \ + read_signal_strength, NULL, inst), \ + BT_AUDIO_CCC(signal_strength_cfg_changed) + +#define BT_TBS_CHR_SIGNAL_INTERVAL(inst) \ + BT_AUDIO_CHRC(BT_UUID_TBS_SIGNAL_INTERVAL, /* Conditional */ \ + BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE | BT_GATT_CHRC_WRITE_WITHOUT_RESP, \ + BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT, \ + read_signal_strength_interval, write_signal_strength_interval, inst) + +#define BT_TBS_CHR_CURRENT_CALLS(inst) \ + BT_AUDIO_CHRC(BT_UUID_TBS_LIST_CURRENT_CALLS, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, read_current_calls, NULL, inst), \ + BT_AUDIO_CCC(current_calls_cfg_changed) + +#define BT_TBS_CHR_CCID(inst) \ + BT_AUDIO_CHRC(BT_UUID_CCID, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT, read_ccid, NULL, inst) + +#define BT_TBS_CHR_STATUS_FLAGS(inst) \ + BT_AUDIO_CHRC(BT_UUID_TBS_STATUS_FLAGS, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, read_status_flags, NULL, inst), \ + BT_AUDIO_CCC(status_flags_cfg_changed) + +#define BT_TBS_CHR_INCOMING_URI(inst) \ + BT_AUDIO_CHRC(BT_UUID_TBS_INCOMING_URI, /* Optional */ \ + BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_READ_ENCRYPT, \ + read_incoming_uri, NULL, inst), \ + BT_AUDIO_CCC(incoming_uri_cfg_changed) + +#define BT_TBS_CHR_CALL_STATE(inst) \ + BT_AUDIO_CHRC(BT_UUID_TBS_CALL_STATE, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, read_call_state, NULL, inst), \ + BT_AUDIO_CCC(call_state_cfg_changed) + +#define BT_TBS_CHR_CONTROL_POINT(inst) \ + BT_AUDIO_CHRC(BT_UUID_TBS_CALL_CONTROL_POINT, \ + BT_GATT_CHRC_WRITE | BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_WRITE_WITHOUT_RESP, \ + BT_GATT_PERM_WRITE_ENCRYPT, NULL, write_call_cp, inst), \ + BT_AUDIO_CCC(call_cp_cfg_changed) + +#define BT_TBS_CHR_OPTIONAL_OPCODES(inst) \ + BT_AUDIO_CHRC(BT_UUID_TBS_OPTIONAL_OPCODES, BT_GATT_CHRC_READ, BT_GATT_PERM_READ_ENCRYPT, \ + read_optional_opcodes, NULL, inst) + +#define BT_TBS_CHR_TERMINATE_REASON(inst) \ + BT_AUDIO_CHRC(BT_UUID_TBS_TERMINATE_REASON, BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_READ_ENCRYPT, \ + NULL, NULL, inst), \ + BT_AUDIO_CCC(terminate_reason_cfg_changed) + +#define BT_TBS_CHR_INCOMING_CALL(inst) \ + BT_AUDIO_CHRC(BT_UUID_TBS_INCOMING_CALL, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, read_incoming_call, NULL, inst), \ + BT_AUDIO_CCC(in_call_cfg_changed) + +#define BT_TBS_CHR_FRIENDLY_NAME(inst) \ + BT_AUDIO_CHRC(BT_UUID_TBS_FRIENDLY_NAME, /* Optional */ \ + BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, BT_GATT_PERM_READ_ENCRYPT, \ + read_friendly_name, NULL, inst), \ + BT_AUDIO_CCC(friendly_name_cfg_changed) + +#define BT_TBS_SERVICE_DEFINE(_uuid, _inst) \ + BT_GATT_PRIMARY_SERVICE(_uuid), BT_TBS_CHR_PROVIDER_NAME(_inst), BT_TBS_CHR_UCI(_inst), \ + BT_TBS_CHR_TECHNOLOGY(_inst), BT_TBS_CHR_URI_LIST(_inst), \ + BT_TBS_CHR_SIGNAL_STRENGTH(_inst), BT_TBS_CHR_SIGNAL_INTERVAL(_inst), \ + BT_TBS_CHR_CURRENT_CALLS(_inst), BT_TBS_CHR_CCID(_inst), BT_TBS_CHR_STATUS_FLAGS(_inst), \ + BT_TBS_CHR_INCOMING_URI(_inst), BT_TBS_CHR_CALL_STATE(_inst), \ + BT_TBS_CHR_CONTROL_POINT(_inst), BT_TBS_CHR_OPTIONAL_OPCODES(_inst), \ + BT_TBS_CHR_TERMINATE_REASON(_inst), BT_TBS_CHR_INCOMING_CALL(_inst), \ + BT_TBS_CHR_FRIENDLY_NAME(_inst) + +#define BT_TBS_SERVICE_DEFINITION(_inst) {BT_TBS_SERVICE_DEFINE(BT_UUID_TBS, &(_inst))} + +static struct bt_gatt_service gtbs_svc = + BT_GATT_SERVICE(((struct bt_gatt_attr[]) + { + BT_TBS_SERVICE_DEFINE(BT_UUID_GTBS, >bs_inst) + } + + )); + +BT_GATT_SERVICE_INSTANCE_DEFINE(tbs_service_list, + svc_insts, + CONFIG_BT_TBS_BEARER_COUNT, + + BT_TBS_SERVICE_DEFINITION); + +static void signal_interval_timeout(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct tbs_inst *inst = CONTAINER_OF(dwork, struct tbs_inst, + reporting_interval_work); + + if (!inst->pending_signal_strength_notification) + { + return; + } + + bt_gatt_notify_uuid(NULL, BT_UUID_TBS_SIGNAL_STRENGTH, inst->attrs, + &inst->signal_strength, + sizeof(inst->signal_strength)); + + if (inst->signal_strength_interval) + { + k_work_reschedule(&inst->reporting_interval_work, + SEC2TICK(inst->signal_strength_interval)); + } + + inst->pending_signal_strength_notification = false; +} + +static int tbs_inst_init_and_register( + struct tbs_inst *inst, struct bt_gatt_service *svc, + const struct bt_tbs_register_param *param) +{ + int ret; + + LOG_DBG("inst %p index 0x%02x", inst, inst_index(inst)); + + ret = bt_ccid_alloc_value(); + if (ret < 0) + { + LOG_DBG("Could not allocate CCID: %d", ret); + return ret; + } + + inst->ccid = (uint8_t)ret; + (void)utf8_lcpy(inst->provider_name, param->provider_name, + sizeof(inst->provider_name)); + (void)utf8_lcpy(inst->uci, param->uci, sizeof(inst->uci)); + (void)utf8_lcpy(inst->uri_scheme_list, param->uri_schemes_supported, + sizeof(inst->uri_scheme_list)); + inst->optional_opcodes = param->optional_opcodes; + inst->technology = param->technology; + inst->attrs = svc->attrs; + inst->attr_count = svc->attr_count; + inst->authorization_required = param->authorization_required; + + k_work_init_delayable(&inst->reporting_interval_work, + signal_interval_timeout); + k_work_init_delayable(&inst->notify_work, notify_work_handler); + + ret = bt_gatt_service_register(svc); + if (ret != 0) + { + LOG_DBG("Could not register %sTBS: %d", param->gtbs ? "G" : "", ret); + memset(inst, 0, sizeof(*inst)); + + return ret; + } + + return inst_index(inst); +} + +static int gtbs_service_inst_register( + const struct bt_tbs_register_param *param) +{ + return tbs_inst_init_and_register(>bs_inst, >bs_svc, param); +} + +static int tbs_service_inst_register( + const struct bt_tbs_register_param *param) +{ + for (size_t i = 0U; i < ARRAY_SIZE(svc_insts); i++) + { + struct tbs_inst *inst = &svc_insts[i]; + + if (!(inst_is_registered(inst))) + { + return tbs_inst_init_and_register(inst, &tbs_service_list[i], + param); + } + } + + return -ENOMEM; +} + +static bool valid_register_param(const struct bt_tbs_register_param *param) +{ + size_t str_len; + + if (param == NULL) + { + LOG_DBG("param is NULL"); + + return false; + } + + if (param->provider_name == NULL) + { + LOG_DBG("provider_name is NULL"); + + return false; + } + + str_len = strlen(param->provider_name); + if (str_len > CONFIG_BT_TBS_MAX_PROVIDER_NAME_LENGTH) + { + LOG_DBG("Provider name length (%zu) larger than " + "CONFIG_BT_TBS_MAX_PROVIDER_NAME_LENGTH %d", + str_len, CONFIG_BT_TBS_MAX_PROVIDER_NAME_LENGTH); + + return false; + } + + if (param->uci == NULL) + { + LOG_DBG("uci is NULL"); + + return false; + } + + if (param->uri_schemes_supported == NULL) + { + LOG_DBG("uri_schemes_supported is NULL"); + + return false; + } + + if (!IN_RANGE(param->technology, BT_BEARER_TECH_3G, BT_BEARER_TECH_WCDMA)) + { + LOG_DBG("Invalid technology: %u", param->technology); + + return false; + } + + if (param->optional_opcodes > BT_TBS_OPTIONAL_OPCODE_ALL) + { + LOG_DBG("Invalid optional_opcodes: %u", param->optional_opcodes); + + return false; + } + + if (CONFIG_BT_TBS_BEARER_COUNT == 0 && !param->gtbs) + { + LOG_DBG("Cannot register TBS when CONFIG_BT_TBS_BEARER_COUNT=0"); + + return false; + } + + return true; +} + +int bt_tbs_register_bearer(const struct bt_tbs_register_param *param) +{ + int ret = -ENOEXEC; + + if (!valid_register_param(param)) + { + LOG_DBG("Invalid parameters"); + + return -EINVAL; + } + + if (param->gtbs && inst_is_registered(>bs_inst)) + { + LOG_DBG("GTBS already registered"); + + return -EALREADY; + } + + if (!param->gtbs && !inst_is_registered(>bs_inst)) + { + LOG_DBG("GTBS not yet registered"); + + return -EAGAIN; + } + + if (param->gtbs) + { + ret = gtbs_service_inst_register(param); + if (ret < 0) + { + LOG_DBG("Failed to register GTBS: %d", ret); + + return -ENOEXEC; + } + } + else if (CONFIG_BT_TBS_BEARER_COUNT > 0) + { + ret = tbs_service_inst_register(param); + if (ret < 0) + { + LOG_DBG("Failed to register GTBS: %d", ret); + + if (ret == -ENOMEM) + { + return -ENOMEM; + } + + return -ENOEXEC; + } + } + + /* ret will contain the index of the registered service */ + + return ret; +} + +int bt_tbs_unregister_bearer(uint8_t bearer_index) +{ + struct tbs_inst *inst = inst_lookup_index(bearer_index); + struct bt_gatt_service *svc; + struct k_work_sync sync; + bool restart_reporting_interval; + int err; + + if (inst == NULL) + { + LOG_DBG("Could not find inst by index %u", bearer_index); + + return -EINVAL; + } + + if (!inst_is_registered(inst)) + { + LOG_DBG("Instance from index %u is not registered", bearer_index); + + return -EALREADY; + } + + if (inst_is_gtbs(inst)) + { + for (size_t i = 0U; i < ARRAY_SIZE(svc_insts); i++) + { + struct tbs_inst *tbs = &svc_insts[i]; + + if (inst_is_registered(tbs)) + { + LOG_DBG("TBS[%u] is registered, please unregister all TBS " + "first", + bearer_index); + return -EAGAIN; + } + } + + svc = >bs_svc; + } + else + { + svc = &tbs_service_list[bearer_index]; + } + + restart_reporting_interval = + k_work_cancel_delayable_sync(&inst->reporting_interval_work, + &sync); + + err = bt_gatt_service_unregister(svc); + if (err != 0) + { + LOG_DBG("Failed to unregister service %p: %d", svc, err); + + if (restart_reporting_interval && inst->signal_strength_interval != 0U) + { + /* In this unlikely scenario we may report interval later than + * expected if the k_work was cancelled right before it was set to + * trigger. It is not a big deal and not worth trying to reschedule + * in a way that it would trigger at the same time again, as + * specific timing over GATT is a wishful dream anyways + */ + + k_work_schedule(&inst->reporting_interval_work, + SEC2TICK(inst->signal_strength_interval)); + } + + return -ENOEXEC; + } + + memset(inst, 0, sizeof(*inst)); + + return 0; +} + +int bt_tbs_accept(uint8_t call_index) +{ + struct tbs_inst *inst = lookup_inst_by_call_index(call_index); + const struct bt_tbs_call_cp_acc ccp = + { + .call_index = call_index, + .opcode = BT_TBS_CALL_OPCODE_ACCEPT, + }; + + int err; + int ret; + + if (inst == NULL) + { + LOG_DBG("Could not lookup inst from call index %u", call_index); + return BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; + } + + err = nxrmutex_trylock(&tbs_mutex); + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + return -EBUSY; + } + + ret = accept_call(inst, &ccp); + if (ret == BT_TBS_RESULT_CODE_SUCCESS) + { + notify_calls(inst); + } + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + + return ret; +} + +int bt_tbs_hold(uint8_t call_index) +{ + struct tbs_inst *inst = lookup_inst_by_call_index(call_index); + const struct bt_tbs_call_cp_hold ccp = + { + .call_index = call_index, + .opcode = BT_TBS_CALL_OPCODE_HOLD, + }; + + int ret; + int err; + + if (inst == NULL) + { + LOG_DBG("Could not lookup inst from call index %u", call_index); + return BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; + } + + err = nxrmutex_trylock(&tbs_mutex); + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + return -EBUSY; + } + + ret = tbs_hold_call(inst, &ccp); + if (ret == BT_TBS_RESULT_CODE_SUCCESS) + { + notify_calls(inst); + } + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + + return ret; +} + +int bt_tbs_retrieve(uint8_t call_index) +{ + struct tbs_inst *inst = lookup_inst_by_call_index(call_index); + const struct bt_tbs_call_cp_retrieve ccp = + { + .call_index = call_index, + .opcode = BT_TBS_CALL_OPCODE_RETRIEVE, + }; + + int ret; + int err; + + if (inst == NULL) + { + LOG_DBG("Could not lookup inst from call index %u", call_index); + return BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; + } + + err = nxrmutex_trylock(&tbs_mutex); + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + return -EBUSY; + } + + ret = retrieve_call(inst, &ccp); + if (ret == BT_TBS_RESULT_CODE_SUCCESS) + { + notify_calls(inst); + } + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + + return ret; +} + +int bt_tbs_terminate(uint8_t call_index) +{ + struct tbs_inst *inst = lookup_inst_by_call_index(call_index); + const struct bt_tbs_call_cp_term ccp = + { + .call_index = call_index, + .opcode = BT_TBS_CALL_OPCODE_TERMINATE, + }; + + int ret; + int err; + + if (inst == NULL) + { + LOG_DBG("Could not lookup inst from call index %u", call_index); + return BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; + } + + err = nxrmutex_trylock(&tbs_mutex); + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + return -EBUSY; + } + + ret = terminate_call(inst, &ccp, BT_TBS_REASON_SERVER_ENDED_CALL); + if (ret == BT_TBS_RESULT_CODE_SUCCESS) + { + notify_calls(inst); + } + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + + return ret; +} + +int bt_tbs_originate(uint8_t bearer_index, + char *remote_uri, uint8_t *call_index) +{ + struct tbs_inst *inst = inst_lookup_index(bearer_index); + uint8_t buf[CONFIG_BT_TBS_MAX_URI_LENGTH + + sizeof(struct bt_tbs_call_cp_originate)]; + struct bt_tbs_call_cp_originate *ccp = + (struct bt_tbs_call_cp_originate *)buf; + struct tbs_inst *target_inst = NULL; + size_t uri_len; + int err; + int ret; + + if (inst == NULL) + { + LOG_DBG("Could not find TBS instance from index %u", bearer_index); + return -EINVAL; + } + + if (remote_uri == NULL) + { + LOG_DBG("remote_uri is NULL"); + return -EINVAL; + } + + if (call_index == NULL) + { + LOG_DBG("call_index is NULL"); + return -EINVAL; + } + + uri_len = strlen(remote_uri); + if (!bt_tbs_valid_uri((uint8_t *)remote_uri, uri_len)) + { + LOG_DBG("Invalid URI %s", remote_uri); + return -EINVAL; + } + + if (inst_is_gtbs(inst)) + { + target_inst = lookup_inst_by_uri_scheme((uint8_t *)remote_uri, + uri_len); + if (target_inst == NULL) + { + return -ENODEV; + } + } + else + { + target_inst = inst; + } + + err = nxrmutex_trylock(&tbs_mutex); + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + return -EBUSY; + } + + ccp->opcode = BT_TBS_CALL_OPCODE_ORIGINATE; + (void)memcpy(ccp->uri, remote_uri, uri_len); + + ret = originate_call(target_inst, ccp, uri_len, call_index); + + /* In the case that we are not connected to any TBS clients, we won't + * notify and we can attempt to change state from dialing to alerting + * immediately + */ + + (void)try_change_dialing_call_to_alerting(target_inst); + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + + return ret; +} + +int bt_tbs_join(uint8_t call_index_cnt, uint8_t *call_indexes) +{ + struct tbs_inst *inst; + uint8_t buf[CONFIG_BT_TBS_MAX_CALLS + sizeof(struct bt_tbs_call_cp_join)]; + struct bt_tbs_call_cp_join *ccp = (struct bt_tbs_call_cp_join *)buf; + int err; + int ret; + + if (call_index_cnt == 0U) + { + LOG_DBG("call_index_cnt is 0"); + return -EINVAL; + } + + if (call_indexes == NULL) + { + LOG_DBG("call_indexes is NULL"); + return -EINVAL; + } + + inst = lookup_inst_by_call_index(call_indexes[0]); + + if (inst == NULL) + { + LOG_DBG("Could not lookup inst from call index %u", call_indexes[0]); + return BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; + } + + err = nxrmutex_trylock(&tbs_mutex); + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + return -EBUSY; + } + + ccp->opcode = BT_TBS_CALL_OPCODE_JOIN; + (void)memcpy(ccp->call_indexes, call_indexes, MIN(call_index_cnt, + CONFIG_BT_TBS_MAX_CALLS)); + + ret = join_calls(inst, ccp, call_index_cnt); + if (ret == BT_TBS_RESULT_CODE_SUCCESS) + { + notify_calls(inst); + } + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + + return ret; +} + +int bt_tbs_remote_answer(uint8_t call_index) +{ + struct tbs_inst *inst = lookup_inst_by_call_index(call_index); + struct bt_tbs_call *call; + int err; + int ret; + + if (inst == NULL) + { + LOG_DBG("Could not lookup inst from call index %u", call_index); + return BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; + } + + err = nxrmutex_trylock(&tbs_mutex); + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + return -EBUSY; + } + + call = lookup_call_in_inst(inst, call_index); + if (call == NULL) + { + ret = BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; + } + else if (call->state == BT_TBS_CALL_STATE_ALERTING) + { + call->state = BT_TBS_CALL_STATE_ACTIVE; + notify_calls(inst); + ret = BT_TBS_RESULT_CODE_SUCCESS; + } + else + { + LOG_DBG("Call with index %u Invalid state %s", call_index, + bt_tbs_state_str(call->state)); + ret = BT_TBS_RESULT_CODE_STATE_MISMATCH; + } + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + + return ret; +} + +int bt_tbs_remote_hold(uint8_t call_index) +{ + struct tbs_inst *inst = lookup_inst_by_call_index(call_index); + struct bt_tbs_call *call; + int err; + int ret; + + 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; + } + + err = nxrmutex_trylock(&tbs_mutex); + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + return -EBUSY; + } + + if (call->state == BT_TBS_CALL_STATE_ACTIVE) + { + call->state = BT_TBS_CALL_STATE_REMOTELY_HELD; + ret = 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; + ret = BT_TBS_RESULT_CODE_SUCCESS; + } + else + { + ret = BT_TBS_RESULT_CODE_STATE_MISMATCH; + } + + if (ret == BT_TBS_RESULT_CODE_SUCCESS) + { + notify_calls(inst); + } + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + + return ret; +} + +int bt_tbs_remote_retrieve(uint8_t call_index) +{ + struct tbs_inst *inst = lookup_inst_by_call_index(call_index); + struct bt_tbs_call *call; + int err; + int ret; + + 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; + } + + err = nxrmutex_trylock(&tbs_mutex); + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + return -EBUSY; + } + + if (call->state == BT_TBS_CALL_STATE_REMOTELY_HELD) + { + call->state = BT_TBS_CALL_STATE_ACTIVE; + ret = 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; + ret = BT_TBS_RESULT_CODE_SUCCESS; + } + else + { + ret = BT_TBS_RESULT_CODE_STATE_MISMATCH; + } + + if (ret == BT_TBS_RESULT_CODE_SUCCESS) + { + notify_calls(inst); + } + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + + return ret; +} + +int bt_tbs_remote_terminate(uint8_t call_index) +{ + struct tbs_inst *inst = lookup_inst_by_call_index(call_index); + const struct bt_tbs_call_cp_term ccp = + { + .call_index = call_index, + .opcode = BT_TBS_CALL_OPCODE_TERMINATE, + }; + + int err; + int ret; + + if (inst == NULL) + { + LOG_DBG("Could not lookup inst from call index %u", call_index); + return BT_TBS_RESULT_CODE_INVALID_CALL_INDEX; + } + + err = nxrmutex_trylock(&tbs_mutex); + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + return -EBUSY; + } + + ret = terminate_call(inst, &ccp, BT_TBS_REASON_REMOTE_ENDED_CALL); + if (ret == BT_TBS_RESULT_CODE_SUCCESS) + { + notify_calls(inst); + } + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + + return ret; +} + +static void tbs_inst_remote_incoming( + struct tbs_inst *inst, const char *to, const char *from, + const char *friendly_name, const struct bt_tbs_call *call) +{ + DEBUGASSERT(to != NULL); + DEBUGASSERT(from != NULL); + + inst->in_call.call_index = call->index; + (void)utf8_lcpy(inst->in_call.uri, from, sizeof(inst->in_call.uri)); + set_value_changed(inst, set_incoming_call_target_bearer_uri_changed_cb, + BT_UUID_TBS_INCOMING_URI); + + inst->incoming_uri.call_index = call->index; + (void)utf8_lcpy(inst->incoming_uri.uri, to, + sizeof(inst->incoming_uri.uri)); + set_value_changed(inst, set_incoming_call_changed_cb, + BT_UUID_TBS_INCOMING_CALL); + + if (friendly_name) + { + inst->friendly_name.call_index = call->index; + utf8_lcpy(inst->friendly_name.name, friendly_name, + sizeof(inst->friendly_name.name)); + } + else + { + inst->friendly_name.call_index = BT_TBS_FREE_CALL_INDEX; + } + + set_value_changed(inst, set_call_friendly_name_changed_cb, + BT_UUID_TBS_FRIENDLY_NAME); +} + +int bt_tbs_remote_incoming( + uint8_t bearer_index, const char *to, const char *from, + const char *friendly_name) +{ + struct tbs_inst *inst = inst_lookup_index(bearer_index); + struct bt_tbs_call *call = NULL; + int err; + + if (inst == NULL) + { + LOG_DBG("Could not find TBS instance from index %u", bearer_index); + return -EINVAL; + } + else if (!bt_tbs_valid_uri((uint8_t *)to, strlen(to))) + { + LOG_DBG("Invalid \"to\" URI: %s", to); + return -EINVAL; + } + else if (!bt_tbs_valid_uri((uint8_t *)from, strlen(from))) + { + LOG_DBG("Invalid \"from\" URI: %s", from); + return -EINVAL; + } + + call = call_alloc(inst, BT_TBS_CALL_STATE_INCOMING, (uint8_t *)from, + strlen(from)); + if (call == NULL) + { + return -ENOMEM; + } + + err = nxrmutex_trylock(&tbs_mutex); + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + return -EBUSY; + } + + BT_TBS_CALL_FLAG_SET_INCOMING(call->flags); + + /* Notify TBS */ + + tbs_inst_remote_incoming(inst, to, from, friendly_name, call); + + if (!inst_is_gtbs(inst)) + { + /* If the instance is different than the GTBS we set the remote + * incoming and notify on the GTBS instance as well + */ + + tbs_inst_remote_incoming(>bs_inst, to, from, friendly_name, call); + } + + notify_calls(inst); + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + + LOG_DBG("New call with call index %u", call->index); + + return call->index; +} + +int bt_tbs_set_bearer_provider_name(uint8_t bearer_index, const char *name) +{ + struct tbs_inst *inst = inst_lookup_index(bearer_index); + const size_t len = strlen(name); + int err; + + if (len >= CONFIG_BT_TBS_MAX_PROVIDER_NAME_LENGTH || len == 0U) + { + return -EINVAL; + } + else if (inst == NULL) + { + return -EINVAL; + } + + if (strcmp(inst->provider_name, name) == 0) + { + return 0; + } + + err = nxrmutex_trylock(&tbs_mutex); + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + return -EBUSY; + } + + (void)utf8_lcpy(inst->provider_name, name, sizeof(inst->provider_name)); + + set_value_changed(inst, set_bearer_provider_name_changed_cb, + BT_UUID_TBS_PROVIDER_NAME); + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + + return 0; +} + +int bt_tbs_set_bearer_technology(uint8_t bearer_index, + enum bt_bearer_tech new_technology) +{ + struct tbs_inst *inst = inst_lookup_index(bearer_index); + int err; + + if (new_technology < BT_BEARER_TECH_3G || + new_technology > BT_BEARER_TECH_WCDMA) + { + return -EINVAL; + } + else if (inst == NULL) + { + return -EINVAL; + } + + if (inst->technology == new_technology) + { + return 0; + } + + err = nxrmutex_trylock(&tbs_mutex); + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + return -EBUSY; + } + + inst->technology = new_technology; + + set_value_changed(inst, set_bearer_technology_changed_cb, + BT_UUID_TBS_TECHNOLOGY); + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + + return 0; +} + +int bt_tbs_set_signal_strength(uint8_t bearer_index, + uint8_t new_signal_strength) +{ + struct tbs_inst *inst = inst_lookup_index(bearer_index); + uint32_t timer_status; + int err; + + if (new_signal_strength > BT_TBS_SIGNAL_STRENGTH_MAX && + new_signal_strength != BT_TBS_SIGNAL_STRENGTH_UNKNOWN) + { + return -EINVAL; + } + else if (inst == NULL) + { + return -EINVAL; + } + + err = nxrmutex_trylock(&tbs_mutex); + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + return -EBUSY; + } + + if (inst->signal_strength != new_signal_strength) + { + inst->signal_strength = new_signal_strength; + inst->pending_signal_strength_notification = true; + + timer_status = + k_work_delayable_remaining_get(&inst->reporting_interval_work); + if (timer_status == 0U) + { + k_work_reschedule(&inst->reporting_interval_work, 0); + } + + LOG_DBG("Index %u: Reporting signal strength in %u ms", bearer_index, + TICK2MSEC(timer_status)); + } + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + + return 0; +} + +int bt_tbs_set_status_flags(uint8_t bearer_index, uint16_t status_flags) +{ + struct tbs_inst *inst = inst_lookup_index(bearer_index); + int err; + + if (!BT_TBS_VALID_STATUS_FLAGS(status_flags)) + { + return -EINVAL; + } + else if (inst == NULL) + { + return -EINVAL; + } + + if (inst->status_flags == status_flags) + { + return 0; + } + + err = nxrmutex_trylock(&tbs_mutex); + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + return -EBUSY; + } + + inst->status_flags = status_flags; + + set_value_changed(inst, set_status_flags_changed_cb, + BT_UUID_TBS_STATUS_FLAGS); + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + + return 0; +} + +int bt_tbs_set_uri_scheme_list(uint8_t bearer_index, + const char *uri_scheme_list) +{ + struct tbs_inst *inst = inst_lookup_index(bearer_index); + size_t uri_scheme_list_len; + int err; + + if (inst == NULL) + { + LOG_DBG("Could not find TBS instance from index %u", bearer_index); + return -EINVAL; + } + + if (uri_scheme_list == NULL) + { + LOG_DBG("uri_scheme_list is NULL"); + + return -EINVAL; + } + + uri_scheme_list_len = strlen(uri_scheme_list); + if (uri_scheme_list_len >= sizeof(inst->uri_scheme_list)) + { + LOG_DBG("Cannot store uri_scheme_list of size %zu in buffer of size " + "%zu", + uri_scheme_list_len, + sizeof(inst->uri_scheme_list)); + + return -ENOMEM; + } + + err = nxrmutex_trylock(&tbs_mutex); + if (err != 0) + { + LOG_DBG("Failed to lock mutex"); + return -EBUSY; + } + + if (strcmp(inst->uri_scheme_list, uri_scheme_list) == 0) + { + /* No URI scheme changes */ + + LOG_DBG("URI scheme \"%s\" was identical to existing for %p", + uri_scheme_list, inst); + } + else + { + /* Store final result */ + + (void)memcpy(inst->uri_scheme_list, uri_scheme_list, + uri_scheme_list_len); + inst->uri_scheme_list[uri_scheme_list_len] = '\0'; + + set_value_changed(inst, + set_bearer_uri_schemes_supported_list_changed_cb, + BT_UUID_TBS_URI_LIST); + + LOG_DBG("TBS instance %u uri prefix list is updated to {%s}", + bearer_index, + inst->uri_scheme_list); + + if (!inst_is_gtbs(inst)) + { + /* If the instance is different than GTBS notify on the GTBS + * instance as well + */ + + set_value_changed(>bs_inst, + set_bearer_uri_schemes_supported_list_changed_cb, + BT_UUID_TBS_URI_LIST); + } + } + + err = nxrmutex_unlock(&tbs_mutex); + DEBUGASSERT(err == 0); + + return 0; +} + +void bt_tbs_register_cb(struct bt_tbs_cb *cbs) +{ + tbs_cbs = cbs; +} + +#if defined(CONFIG_BT_TBS_LOG_LEVEL_DBG) +void bt_tbs_dbg_print_calls(void) +{ + for (size_t i = 0U; i < ARRAY_SIZE(svc_insts); i++) + { + LOG_DBG("Bearer #%u", i); + for (int j = 0; j < ARRAY_SIZE(svc_insts[i].calls); j++) + { + struct bt_tbs_call *call = &svc_insts[i].calls[j]; + + if (call->index == BT_TBS_FREE_CALL_INDEX) + { + continue; + } + + LOG_DBG(" Call #%u", call->index); + LOG_DBG(" State: %s", bt_tbs_state_str(call->state)); + LOG_DBG(" Flags: 0x%02X", call->flags); + LOG_DBG(" URI : %s", call->remote_uri); + } + } +} +#endif /* defined(CONFIG_BT_TBS_LOG_LEVEL_DBG) */ diff --git a/libs/zblue/audio/tbs_client.c b/libs/zblue/audio/tbs_client.c new file mode 100644 index 0000000000000..e255917fb8b4e --- /dev/null +++ b/libs/zblue/audio/tbs_client.c @@ -0,0 +1,3054 @@ +/**************************************************************************** + * libs/zblue/audio/tbs_client.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Bluetooth TBS - Telephone Bearer Service - Client + * + * Copyright (c) 2020 Bose Corporation Copyright (c) 2021-2026 Nordic + * Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/bt_str.h" +#include "tbs_internal.h" + +LOG_MODULE_REGISTER(bt_tbs_client, CONFIG_BT_TBS_CLIENT_LOG_LEVEL); +/* TODO TBS client attempts to subscribe to all characteristics at once if + * the MTU is large enough. This requires a significant amount of buffers, + * and should be optimized. + */ + +/* Calculate the required buffers for TBS Client discovery */ +#define TBS_CLIENT_BUF_COUNT \ + (1 /* Discover buffer */ + 1 /* terminate reason */ + \ + IS_ENABLED(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) + \ + IS_ENABLED(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) + \ + IS_ENABLED(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) + \ + IS_ENABLED(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) + \ + IS_ENABLED(CONFIG_BT_TBS_CLIENT_INCOMING_URI) + IS_ENABLED(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) + \ + IS_ENABLED(CONFIG_BT_TBS_CLIENT_CP_PROCEDURES) + \ + IS_ENABLED(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) + \ + IS_ENABLED(CONFIG_BT_TBS_CLIENT_INCOMING_CALL)) + +BUILD_ASSERT(CONFIG_BT_ATT_TX_COUNT >= TBS_CLIENT_BUF_COUNT, + "Too few ATT buffers"); + +/* The maximum size of all supported string values */ +#define MAX_STR_LEN \ + MAX(CONFIG_BT_TBS_MAX_URI_LENGTH, \ + (MAX(COND_CODE_1(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME, \ + (CONFIG_BT_TBS_MAX_PROVIDER_NAME_LENGTH), (0U)), \ + COND_CODE_1(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME, \ + (CONFIG_BT_TBS_MAX_FRIENDLY_NAME_LENGTH), (0U))))) + +struct bt_tbs_server_inst +{ +#if defined(CONFIG_BT_TBS_CLIENT_TBS) + struct bt_tbs_instance tbs_insts[CONFIG_BT_TBS_CLIENT_MAX_TBS_INSTANCES]; + uint8_t inst_cnt; +#endif /* CONFIG_BT_TBS_CLIENT_TBS */ +#if defined(CONFIG_BT_TBS_CLIENT_GTBS) + struct bt_tbs_instance gtbs_inst; +#endif /* defined(CONFIG_BT_TBS_CLIENT_GTBS) */ + struct bt_gatt_discover_params discover_params; + struct bt_tbs_instance *current_inst; +}; + +static struct list_node tbs_client_cbs = LIST_INITIAL_VALUE(tbs_client_cbs); + +static struct bt_tbs_server_inst srv_insts[CONFIG_BT_MAX_CONN]; + +static void discover_next_instance(struct bt_conn *conn); + +typedef bool (*tbs_instance_find_func_t)(struct bt_tbs_instance *inst, + void *user_data); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static struct bt_tbs_instance *tbs_instance_find( + struct bt_tbs_server_inst *server, tbs_instance_find_func_t func, + void *user_data) +{ +#if defined(CONFIG_BT_TBS_CLIENT_GTBS) + if (func(&server->gtbs_inst, user_data)) + { + return &server->gtbs_inst; + } +#endif /* CONFIG_BT_TBS_CLIENT_GTBS */ +#if defined(CONFIG_BT_TBS_CLIENT_TBS) + + for (size_t i = 0U; i < server->inst_cnt; i++) + { + if (func(&server->tbs_insts[i], user_data)) + { + return &server->tbs_insts[i]; + } + } +#endif /* CONFIG_BT_TBS_CLIENT_TBS */ + + return NULL; +} + +static struct bt_tbs_instance *tbs_inst_by_index(const struct bt_conn *conn, + uint8_t index) +{ + struct bt_tbs_server_inst *server; + + DEBUGASSERT(conn); + + server = &srv_insts[bt_conn_index(conn)]; + +#if defined(CONFIG_BT_TBS_CLIENT_GTBS) + if (index == BT_TBS_GTBS_INDEX) + { + return &server->gtbs_inst; + } +#endif /* CONFIG_BT_TBS_CLIENT_GTBS */ +#if defined(CONFIG_BT_TBS_CLIENT_TBS) + + if (index < server->inst_cnt) + { + return &server->tbs_insts[index]; + } +#endif /* CONFIG_BT_TBS_CLIENT_TBS */ + + return NULL; +} + +static uint8_t tbs_index(struct bt_conn *conn, + const struct bt_tbs_instance *inst) +{ + struct bt_tbs_server_inst *server; + ptrdiff_t index = 0; + + DEBUGASSERT(conn); + DEBUGASSERT(inst); + + server = &srv_insts[bt_conn_index(conn)]; + +#if defined(CONFIG_BT_TBS_CLIENT_GTBS) + if (inst == &server->gtbs_inst) + { + return BT_TBS_GTBS_INDEX; + } +#endif /* CONFIG_BT_TBS_CLIENT_GTBS */ +#if defined(CONFIG_BT_TBS_CLIENT_TBS) + + index = inst - server->tbs_insts; + DEBUGASSERT(index >= 0 && index < ARRAY_SIZE(server->tbs_insts)); + +#else + LOG_ERR("Invalid bt_tbs_instance pointer"); +#endif /* CONFIG_BT_TBS_CLIENT_TBS */ + + return (uint8_t)index; +} + +#if defined(CONFIG_BT_TBS_CLIENT_ORIGINATE_CALL) +static bool free_call_spot(struct bt_tbs_instance *inst) +{ + for (int i = 0; i < CONFIG_BT_TBS_CLIENT_MAX_CALLS; i++) + { + if (inst->calls[i].index == BT_TBS_FREE_CALL_INDEX) + { + return true; + } + } + + return false; +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_ORIGINATE_CALL) */ + +static bool is_instance_handle(struct bt_tbs_instance *inst, void *user_data) +{ + uint16_t handle = POINTER_TO_UINT(user_data); + + return inst->start_handle <= handle && inst->end_handle >= handle; +} + +static struct bt_tbs_instance *lookup_inst_by_handle(struct bt_conn *conn, + uint16_t handle) +{ + uint8_t conn_index; + struct bt_tbs_server_inst *srv_inst; + struct bt_tbs_instance *inst; + + DEBUGASSERT(conn); + + conn_index = bt_conn_index(conn); + srv_inst = &srv_insts[conn_index]; + + inst = tbs_instance_find(srv_inst, is_instance_handle, + UINT_TO_POINTER(handle)); + if (inst != NULL) + { + return inst; + } + + LOG_DBG("Could not find instance with handle 0x%04x", handle); + + return NULL; +} + +static uint8_t net_buf_pull_call_state(struct net_buf_simple *buf, + struct bt_tbs_client_call_state *call_state) +{ + if (buf->len < sizeof(*call_state)) + { + LOG_DBG("Invalid buffer length %u", buf->len); + return BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + + call_state->index = net_buf_simple_pull_u8(buf); + call_state->state = net_buf_simple_pull_u8(buf); + call_state->flags = net_buf_simple_pull_u8(buf); + + return 0; +} + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) +static uint8_t net_buf_pull_call(struct net_buf_simple *buf, + struct bt_tbs_client_call *call) +{ + const size_t min_item_len = sizeof(call->call_info) + BT_TBS_MIN_URI_LEN; + uint8_t item_len; + uint8_t uri_len; + uint8_t err; + uint8_t *uri; + + DEBUGASSERT(buf); + DEBUGASSERT(call); + + if (buf->len < sizeof(item_len) + min_item_len) + { + LOG_DBG("Invalid buffer length %u", buf->len); + return BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + + item_len = net_buf_simple_pull_u8(buf); + uri_len = item_len - sizeof(call->call_info); + + if (item_len > buf->len || item_len < min_item_len) + { + LOG_DBG("Invalid current call item length %u", item_len); + return BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + + err = net_buf_pull_call_state(buf, &call->call_info); + if (err != 0) + { + return err; + } + + uri = net_buf_simple_pull_mem(buf, uri_len); + if (uri_len > CONFIG_BT_TBS_MAX_URI_LENGTH) + { + LOG_WRN("Current call (" + "index %u) uri length larger than supported %u/%zu", + call->call_info.index, uri_len, CONFIG_BT_TBS_MAX_URI_LENGTH); + return BT_ATT_ERR_INSUFFICIENT_RESOURCES; + } + + (void)memcpy(call->remote_uri, uri, uri_len); + call->remote_uri[uri_len] = '\0'; + + return 0; +} + +static void current_calls_changed( + struct bt_conn *conn, int err, uint8_t inst_index, uint8_t call_count, + const struct bt_tbs_client_call *calls) +{ + struct bt_tbs_client_cb *listener; + struct bt_tbs_client_cb *next; + + list_for_every_container_safe(&tbs_client_cbs, listener, next, _node) + { + if (listener->current_calls != NULL) + { + listener->current_calls(conn, err, inst_index, call_count, calls); + } + } +} + +static void bearer_list_current_calls( + struct bt_conn *conn, const struct bt_tbs_instance *inst, + struct net_buf_simple *buf) +{ + struct bt_tbs_client_call calls[CONFIG_BT_TBS_CLIENT_MAX_CALLS]; + + char + remote_uris + [CONFIG_BT_TBS_CLIENT_MAX_CALLS][CONFIG_BT_TBS_MAX_URI_LENGTH + 1]; + uint8_t cnt = 0U; + int err; + + while (buf->len) + { + struct bt_tbs_client_call *call; + + if (cnt == CONFIG_BT_TBS_CLIENT_MAX_CALLS) + { + LOG_WRN("Could not parse all calls due to memory restrictions"); + break; + } + + call = &calls[cnt]; + call->remote_uri = remote_uris[cnt]; + + err = net_buf_pull_call(buf, call); + if (err == BT_ATT_ERR_INSUFFICIENT_RESOURCES) + { + LOG_WRN("Call with skipped due to too long URI"); + continue; + } + else if (err != 0) + { + LOG_DBG("Invalid current call notification: %d", err); + return; + } + + cnt++; + } + + current_calls_changed(conn, 0, tbs_index(conn, inst), cnt, calls); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) */ + +#if defined(CONFIG_BT_TBS_CLIENT_CP_PROCEDURES) +static void call_cp_callback_handler( + struct bt_conn *conn, int err, uint8_t index, uint8_t opcode, + uint8_t call_index) +{ + struct bt_tbs_client_cb *listener; + struct bt_tbs_client_cb *next; + + LOG_DBG("Status: %s for the %s opcode for call 0x%02x", + bt_tbs_status_str(err), + bt_tbs_opcode_str(opcode), call_index); + + list_for_every_container_safe(&tbs_client_cbs, listener, next, _node) + { + switch (opcode) + { +#if defined(CONFIG_BT_TBS_CLIENT_ACCEPT_CALL) + case BT_TBS_CALL_OPCODE_ACCEPT: + if (listener->accept_call != NULL) + { + listener->accept_call(conn, err, index, call_index); + } + break; +#endif /* defined(CONFIG_BT_TBS_CLIENT_ACCEPT_CALL) */ +#if defined(CONFIG_BT_TBS_CLIENT_TERMINATE_CALL) + case BT_TBS_CALL_OPCODE_TERMINATE: + if (listener->terminate_call != NULL) + { + listener->terminate_call(conn, err, index, call_index); + } + break; +#endif /* defined(CONFIG_BT_TBS_CLIENT_TERMINATE_CALL) */ +#if defined(CONFIG_BT_TBS_CLIENT_HOLD_CALL) + case BT_TBS_CALL_OPCODE_HOLD: + if (listener->hold_call != NULL) + { + listener->hold_call(conn, err, index, call_index); + } + break; +#endif /* defined(CONFIG_BT_TBS_CLIENT_HOLD_CALL) */ +#if defined(CONFIG_BT_TBS_CLIENT_RETRIEVE_CALL) + case BT_TBS_CALL_OPCODE_RETRIEVE: + if (listener->retrieve_call != NULL) + { + listener->retrieve_call(conn, err, index, call_index); + } + break; +#endif /* defined(CONFIG_BT_TBS_CLIENT_RETRIEVE_CALL) */ +#if defined(CONFIG_BT_TBS_CLIENT_ORIGINATE_CALL) + case BT_TBS_CALL_OPCODE_ORIGINATE: + if (listener->originate_call != NULL) + { + listener->originate_call(conn, err, index, call_index); + } + break; +#endif /* defined(CONFIG_BT_TBS_CLIENT_ORIGINATE_CALL) */ +#if defined(CONFIG_BT_TBS_CLIENT_JOIN_CALLS) + case BT_TBS_CALL_OPCODE_JOIN: + if (listener->join_calls != NULL) + { + listener->join_calls(conn, err, index, call_index); + } + break; +#endif /* defined(CONFIG_BT_TBS_CLIENT_JOIN_CALLS) */ + default: + break; + } + } +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_OPTIONAL_OPCODES) */ + +__maybe_unused static const char *parse_string_value( + const void *data, uint16_t length, uint16_t max_len) +{ + static char string_val[MAX_STR_LEN + 1]; + const size_t len = MIN(length, max_len); + + if (len != 0U) + { + (void)memcpy(string_val, data, len); + } + + string_val[len] = '\0'; + + return string_val; +} + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) +static void provider_name_changed( + struct bt_conn *conn, int err, uint8_t inst_index, const char *name) +{ + struct bt_tbs_client_cb *listener; + struct bt_tbs_client_cb *next; + + list_for_every_container_safe(&tbs_client_cbs, listener, next, _node) + { + if (listener->bearer_provider_name != NULL) + { + listener->bearer_provider_name(conn, err, inst_index, name); + } + } +} + +static void provider_name_notify_handler(struct bt_conn *conn, + const struct bt_tbs_instance *tbs_inst, + const void *data, + uint16_t length) +{ + const char *name = parse_string_value(data, length, + CONFIG_BT_TBS_MAX_PROVIDER_NAME_LENGTH); + + LOG_DBG("%s", name); + + provider_name_changed(conn, 0, tbs_index(conn, tbs_inst), name); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) */ + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) +static void technology_changed( + struct bt_conn *conn, int err, uint8_t inst_index, + enum bt_bearer_tech technology) +{ + struct bt_tbs_client_cb *listener; + struct bt_tbs_client_cb *next; + + list_for_every_container_safe(&tbs_client_cbs, listener, next, _node) + { + if (listener->technology != NULL) + { + listener->technology(conn, err, inst_index, technology); + } + } +} + +static void technology_notify_handler( + struct bt_conn *conn, const struct bt_tbs_instance *tbs_inst, + const void *data, uint16_t length) +{ + uint8_t technology; + + LOG_DBG(""); + + if (length == sizeof(technology)) + { + (void)memcpy(&technology, data, length); + LOG_DBG("%s (0x%02x)", + bt_bearer_tech_str((enum bt_bearer_tech)technology), + + technology); + + technology_changed(conn, 0, tbs_index(conn, tbs_inst), + (enum bt_bearer_tech)technology); + } +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) */ + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) +static void signal_strength_changed( + struct bt_conn *conn, int err, uint8_t inst_index, uint8_t signal_strength) +{ + struct bt_tbs_client_cb *listener; + struct bt_tbs_client_cb *next; + + list_for_every_container_safe(&tbs_client_cbs, listener, next, _node) + { + if (listener->signal_strength != NULL) + { + listener->signal_strength(conn, err, inst_index, signal_strength); + } + } +} + +static void signal_strength_notify_handler(struct bt_conn *conn, + const struct bt_tbs_instance *tbs_inst, + const void *data, + uint16_t length) +{ + uint8_t signal_strength; + + LOG_DBG(""); + + if (length == sizeof(signal_strength)) + { + (void)memcpy(&signal_strength, data, length); + LOG_DBG("0x%02x", signal_strength); + + signal_strength_changed(conn, 0, tbs_index(conn, tbs_inst), + signal_strength); + } +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) */ + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) +static void current_calls_notify_handler(struct bt_conn *conn, + const struct bt_tbs_instance *tbs_inst, + const void *data, + uint16_t length) +{ + struct net_buf_simple buf; + + LOG_DBG(""); + + net_buf_simple_init_with_data(&buf, (void *)data, length); + + /* TODO: If length == MTU, do long read for all calls */ + + bearer_list_current_calls(conn, tbs_inst, &buf); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) */ + +#if defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) +static void status_flags_changed( + struct bt_conn *conn, int err, uint8_t inst_index, uint16_t status_flags) +{ + struct bt_tbs_client_cb *listener; + struct bt_tbs_client_cb *next; + + list_for_every_container_safe(&tbs_client_cbs, listener, next, _node) + { + if (listener->status_flags != NULL) + { + listener->status_flags(conn, err, inst_index, status_flags); + } + } +} + +static void status_flags_notify_handler(struct bt_conn *conn, + const struct bt_tbs_instance *tbs_inst, + const void *data, + uint16_t length) +{ + uint16_t status_flags; + + LOG_DBG(""); + + if (length == sizeof(status_flags)) + { + (void)memcpy(&status_flags, data, length); + LOG_DBG("0x%04x", status_flags); + + status_flags_changed(conn, 0, tbs_index(conn, tbs_inst), status_flags); + } +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) */ + +#if defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) +static void call_uri_changed( + struct bt_conn *conn, int err, uint8_t inst_index, const char *call_uri) +{ + struct bt_tbs_client_cb *listener; + struct bt_tbs_client_cb *next; + + list_for_every_container_safe(&tbs_client_cbs, listener, next, _node) + { + if (listener->call_uri != NULL) + { + listener->call_uri(conn, err, inst_index, call_uri); + } + } +} + +static void incoming_uri_notify_handler(struct bt_conn *conn, + const struct bt_tbs_instance *tbs_inst, + const void *data, + uint16_t length) +{ + const char *uri = parse_string_value(data, length, + CONFIG_BT_TBS_MAX_URI_LENGTH); + + LOG_DBG("%s", uri); + + call_uri_changed(conn, 0, tbs_index(conn, tbs_inst), uri); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) */ + +static void call_state_changed( + struct bt_conn *conn, int err, uint8_t inst_index, uint8_t call_count, + const struct bt_tbs_client_call_state *call_states) +{ + struct bt_tbs_client_cb *listener; + struct bt_tbs_client_cb *next; + + list_for_every_container_safe(&tbs_client_cbs, listener, next, _node) + { + if (listener->call_state != NULL) + { + listener->call_state(conn, err, inst_index, call_count, call_states); + } + } +} + +static void call_state_notify_handler( + struct bt_conn *conn, const struct bt_tbs_instance *tbs_inst, + const void *data, uint16_t length) +{ + struct bt_tbs_client_call_state + call_states[CONFIG_BT_TBS_CLIENT_MAX_CALLS]; + uint8_t cnt = 0U; + struct net_buf_simple buf; + + LOG_DBG(""); + + net_buf_simple_init_with_data(&buf, (void *)data, length); + + /* TODO: If length == MTU, do long read for all call states */ + + while (buf.len) + { + struct bt_tbs_client_call_state *call_state; + int err; + + if (cnt == CONFIG_BT_TBS_CLIENT_MAX_CALLS) + { + LOG_WRN("Could not parse all calls due to memory restrictions"); + break; + } + + call_state = &call_states[cnt]; + + err = net_buf_pull_call_state(&buf, call_state); + if (err != 0) + { + LOG_DBG("Invalid current call notification: %d", err); + return; + } + + cnt++; + } + + call_state_changed(conn, 0, tbs_index(conn, tbs_inst), cnt, call_states); +} + +#if defined(CONFIG_BT_TBS_CLIENT_CP_PROCEDURES) +static void call_cp_notify_handler( + struct bt_conn *conn, const struct bt_tbs_instance *tbs_inst, + const void *data, uint16_t length) +{ + struct bt_tbs_call_cp_notify *ind_val; + + LOG_DBG(""); + + if (length == sizeof(*ind_val)) + { + ind_val = (struct bt_tbs_call_cp_notify *)data; + LOG_DBG("Status: %s for the %s opcode for call 0x%02X", + bt_tbs_status_str(ind_val->status), + bt_tbs_opcode_str(ind_val->opcode), ind_val->call_index); + + call_cp_callback_handler(conn, ind_val->status, tbs_index(conn, + tbs_inst), ind_val->opcode, + ind_val->call_index); + } +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_CP_PROCEDURES) */ + +static void terminate_reason_changed( + struct bt_conn *conn, int err, uint8_t inst_index, + struct bt_tbs_terminate_reason reason) +{ + struct bt_tbs_client_cb *listener; + struct bt_tbs_client_cb *next; + + list_for_every_container_safe(&tbs_client_cbs, listener, next, _node) + { + if (listener->termination_reason != NULL) + { + listener->termination_reason(conn, + err, + inst_index, + reason.call_index, + + reason.reason); + } + } +} + +static void termination_reason_notify_handler(struct bt_conn *conn, + const struct bt_tbs_instance *tbs_inst, + const void *data, + uint16_t length) +{ + struct bt_tbs_terminate_reason reason; + + LOG_DBG(""); + + if (length == sizeof(reason)) + { + (void)memcpy(&reason, data, length); + LOG_DBG("ID 0x%02X, reason %s", reason.call_index, + bt_tbs_term_reason_str(reason.reason)); + + terminate_reason_changed(conn, 0, tbs_index(conn, tbs_inst), reason); + } +} + +#if defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) +static void remote_uri_changed( + struct bt_conn *conn, int err, uint8_t inst_index, const char *remote_uri) +{ + struct bt_tbs_client_cb *listener; + struct bt_tbs_client_cb *next; + + list_for_every_container_safe(&tbs_client_cbs, listener, next, _node) + { + if (listener->remote_uri != NULL) + { + listener->remote_uri(conn, err, inst_index, remote_uri); + } + } +} + +static void in_call_notify_handler( + struct bt_conn *conn, const struct bt_tbs_instance *tbs_inst, + const void *data, uint16_t length) +{ + const char *uri = parse_string_value(data, length, + CONFIG_BT_TBS_MAX_URI_LENGTH); + + LOG_DBG("%s", uri); + + remote_uri_changed(conn, 0, tbs_index(conn, tbs_inst), uri); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) */ + +#if defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) +static void friendly_name_changed( + struct bt_conn *conn, int err, uint8_t inst_index, + const char *friendly_name) +{ + struct bt_tbs_client_cb *listener; + struct bt_tbs_client_cb *next; + + list_for_every_container_safe(&tbs_client_cbs, listener, next, _node) + { + if (listener->friendly_name != NULL) + { + listener->friendly_name(conn, err, inst_index, friendly_name); + } + } +} + +static void friendly_name_notify_handler(struct bt_conn *conn, + const struct bt_tbs_instance *tbs_inst, + const void *data, + uint16_t length) +{ + const char *name = parse_string_value(data, length, + CONFIG_BT_TBS_MAX_FRIENDLY_NAME_LENGTH); + + LOG_DBG("%s", name); + + friendly_name_changed(conn, 0, tbs_index(conn, tbs_inst), name); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) */ + +/** @brief Handles notifications and indications from the server */ + +static uint8_t notify_handler( + struct bt_conn *conn, struct bt_gatt_subscribe_params *params, + const void *data, uint16_t length) +{ + uint16_t handle = params->value_handle; + struct bt_tbs_instance *tbs_inst; + + if (data == NULL || conn == NULL) + { + LOG_DBG("[UNSUBSCRIBED] 0x%04X", params->value_handle); + params->value_handle = 0U; + + return BT_GATT_ITER_STOP; + } + + tbs_inst = lookup_inst_by_handle(conn, handle); + if (tbs_inst != NULL) + { + uint8_t inst_index = tbs_index(conn, tbs_inst); + + LOG_DBG("Index %u", inst_index); + + LOG_HEXDUMP_DBG(data, length, "notify handler value"); + + if (handle == tbs_inst->call_state_sub_params.value_handle) + { + call_state_notify_handler(conn, tbs_inst, data, length); +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) + } + else if (handle == tbs_inst->name_sub_params.value_handle) + { + provider_name_notify_handler(conn, tbs_inst, data, length); +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) + } + else if (handle == tbs_inst->technology_sub_params.value_handle) + { + technology_notify_handler(conn, tbs_inst, data, length); +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) + } + else if (handle == tbs_inst->signal_strength_sub_params.value_handle) + { + signal_strength_notify_handler(conn, tbs_inst, data, length); +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) */ +#if defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) + } + else if (handle == tbs_inst->status_flags_sub_params.value_handle) + { + status_flags_notify_handler(conn, tbs_inst, data, length); +#endif /* defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) + } + else if (handle == tbs_inst->current_calls_sub_params.value_handle) + { + current_calls_notify_handler(conn, tbs_inst, data, length); +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) */ +#if defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) + } + else if (handle == tbs_inst->in_target_uri_sub_params.value_handle) + { + incoming_uri_notify_handler(conn, tbs_inst, data, length); +#endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) */ +#if defined(CONFIG_BT_TBS_CLIENT_CP_PROCEDURES) + } + else if (handle == tbs_inst->call_cp_sub_params.value_handle) + { + call_cp_notify_handler(conn, tbs_inst, data, length); +#endif /* defined(CONFIG_BT_TBS_CLIENT_CP_PROCEDURES) */ + } + else if (handle == tbs_inst->termination_reason_handle) + { + termination_reason_notify_handler(conn, tbs_inst, data, length); +#if defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) + } + else if (handle == tbs_inst->incoming_call_sub_params.value_handle) + { + in_call_notify_handler(conn, tbs_inst, data, length); +#endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) */ +#if defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) + } + else if (handle == tbs_inst->friendly_name_sub_params.value_handle) + { + friendly_name_notify_handler(conn, tbs_inst, data, length); +#endif /* defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) */ + } + } + else + { + LOG_DBG("Notification/Indication on unknown TBS inst"); + } + + return BT_GATT_ITER_CONTINUE; +} + +static void initialize_net_buf_read_buffer(struct bt_tbs_instance *inst) +{ + if (inst->net_buf.data == NULL) + { + net_buf_simple_init_with_data(&inst->net_buf, &inst->read_buf, + sizeof(inst->read_buf)); + } + else + { + (void)memset(inst->net_buf.data, 0, inst->net_buf.len); + } + + net_buf_simple_reset(&inst->net_buf); +} + +static void tbs_client_gatt_read_complete(struct bt_tbs_instance *inst) +{ + (void)memset(&inst->read_params, 0, sizeof(inst->read_params)); + atomic_clear_bit(inst->flags, BT_TBS_CLIENT_FLAG_BUSY); +} + +static int tbs_client_gatt_read( + struct bt_conn *conn, struct bt_tbs_instance *inst, uint16_t handle, + bt_gatt_read_func_t func) +{ + int err; + + if (atomic_test_and_set_bit(inst->flags, BT_TBS_CLIENT_FLAG_BUSY)) + { + LOG_DBG("Instance is busy"); + + return -EBUSY; + } + + /* Use read_buf; length may be larger than minimum BT_ATT_MTU */ + + initialize_net_buf_read_buffer(inst); + inst->read_params.func = func; + inst->read_params.handle_count = 1U; + inst->read_params.single.handle = handle; + inst->read_params.single.offset = 0U; + + err = bt_gatt_read(conn, &inst->read_params); + if (err != 0) + { + tbs_client_gatt_read_complete(inst); + + return err; + } + + return 0; +} + +static bool gtbs_found(struct bt_tbs_server_inst *srv_inst) +{ +#if defined(CONFIG_BT_TBS_CLIENT_GTBS) + return srv_inst->gtbs_inst.start_handle != 0; +#else + return false; +#endif /* CONFIG_BT_TBS_CLIENT_GTBS */ +} + +static uint8_t inst_cnt(struct bt_tbs_server_inst *srv_inst) +{ +#if defined(CONFIG_BT_TBS_CLIENT_TBS) + return srv_inst->inst_cnt; +#else + return 0; +#endif /* CONFIG_BT_TBS_CLIENT_TBS */ +} + +static void tbs_client_discover_complete(struct bt_conn *conn, int err) +{ + struct bt_tbs_server_inst *srv_inst = &srv_insts[bt_conn_index(conn)]; + struct bt_tbs_client_cb *listener; + struct bt_tbs_client_cb *next; + + LOG_DBG("conn %p err %d", (void *)conn, err); + + /* Clear the current instance in discovery */ + + srv_inst->current_inst = NULL; + +#if defined(CONFIG_BT_TBS_CLIENT_GTBS) + atomic_clear_bit(srv_inst->gtbs_inst.flags, BT_TBS_CLIENT_FLAG_BUSY); +#endif /* CONFIG_BT_TBS_CLIENT_GTBS */ +#if defined(CONFIG_BT_TBS_CLIENT_TBS) + for (size_t i = 0U; i < ARRAY_SIZE(srv_inst->tbs_insts); i++) + { + atomic_clear_bit(srv_inst->tbs_insts[ + i].flags, BT_TBS_CLIENT_FLAG_BUSY); + } +#endif /* CONFIG_BT_TBS_CLIENT_TBS */ + + list_for_every_container_safe(&tbs_client_cbs, listener, next, _node) + { + if (listener->discover != NULL) + { + listener->discover(conn, err, inst_cnt(srv_inst), + gtbs_found(srv_inst)); + } + } +} + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) || \ + defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) || \ + defined(CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST) || \ + defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) || \ + defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) || \ + defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) + +static bool can_add_string_to_net_buf(const struct net_buf_simple *buf, + size_t len) +{ + return buf->len + len + sizeof(char) /* NULL terminator size */ <= + buf->size; +} + +/* Common function to read tbs_client strings which may require long reads + * + * @return BT_GATT_ITER_CONTINUE, BT_GATT_ITER_STOP or a negative value if + * there's an error + */ + +static int handle_string_long_read( + struct bt_tbs_instance *inst, uint8_t err, const void *data, + uint16_t offset, uint16_t length, bool truncatable) +{ + if (err != 0) + { + LOG_DBG("err: %u", err); + + return BT_GATT_ERR(err); + } + + if (data != NULL) + { + /* Get data and try to read more using read long procedure */ + + LOG_DBG("Read (offset %u): %s", offset, bt_hex(data, length)); + + if (!can_add_string_to_net_buf(&inst->net_buf, length)) + { + LOG_DBG("Read length %u: String buffer full", length); + if (truncatable) + { + /* Use the remaining buffer and stop reading and leave room for + * NULL terminator + */ + + LOG_DBG("Truncating string"); + length = net_buf_simple_tailroom(&inst->net_buf) - + sizeof(char) /* NULL terminator size */; + net_buf_simple_add_mem(&inst->net_buf, data, length); + + /* Ensure that the data is correctly truncated */ + + utf8_trunc(inst->net_buf.data); + } + else + { + return BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES); + } + } + else + { + net_buf_simple_add_mem(&inst->net_buf, data, length); + + return BT_GATT_ITER_CONTINUE; + } + } + +/* else we are done reading and since the buffer is always + * 0-initialized, it will have the + * NULL terminator automatically + */ + + return BT_GATT_ITER_STOP; +} +#endif /* CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME || \ + + * CONFIG_BT_TBS_CLIENT_BEARER_UCI || \ + * CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST || \ + * CONFIG_BT_TBS_CLIENT_INCOMING_URI || \ + * CONFIG_BT_TBS_CLIENT_INCOMING_CALL || \ + * CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME \ + */ + +#if defined(CONFIG_BT_TBS_CLIENT_CP_PROCEDURES) +static int tbs_client_common_call_control( + struct bt_conn *conn, uint8_t inst_index, uint8_t call_index, + uint8_t opcode) +{ + struct bt_tbs_instance *inst; + struct bt_tbs_call_cp_acc common; + + inst = tbs_inst_by_index(conn, inst_index); + if (inst == NULL) + { + return -EINVAL; + } + + if (inst->call_cp_sub_params.value_handle == 0) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + + common.opcode = opcode; + common.call_index = call_index; + + return bt_gatt_write_without_response(conn, + inst->call_cp_sub_params.value_handle, + &common, + sizeof(common), false); +} +#endif /* CONFIG_BT_TBS_CLIENT_CP_PROCEDURES */ + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) +static uint8_t read_bearer_provider_name_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, + const void *data, uint16_t length) +{ + struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, + read_params); + const uint8_t inst_index = tbs_index(conn, inst); + int ret; + + LOG_DBG(""); + + ret = handle_string_long_read(inst, err, data, params->single.offset, + length, + true); + if (ret != BT_GATT_ITER_CONTINUE) + { + if (ret == BT_GATT_ITER_STOP) + { + /* At this point the inst->net_buf.data contains a NULL terminator + * string + */ + + provider_name_changed(conn, 0, inst_index, + (char *)inst->net_buf.data); + } + else + { + provider_name_changed(conn, ret, inst_index, NULL); + } + + tbs_client_gatt_read_complete(inst); + + return BT_GATT_ITER_STOP; + } + + return BT_GATT_ITER_CONTINUE; +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) */ + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) +static void bearer_uci_changed(struct bt_conn *conn, + int err, uint8_t inst_index, const char *uci) +{ + struct bt_tbs_client_cb *listener; + struct bt_tbs_client_cb *next; + + list_for_every_container_safe(&tbs_client_cbs, listener, next, _node) + { + if (listener->bearer_uci != NULL) + { + listener->bearer_uci(conn, err, inst_index, uci); + } + } +} + +static uint8_t read_bearer_uci_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, + read_params); + const uint8_t inst_index = tbs_index(conn, inst); + int ret; + + LOG_DBG(""); + + ret = handle_string_long_read(inst, err, data, params->single.offset, + length, + true); + if (ret != BT_GATT_ITER_CONTINUE) + { + if (ret == BT_GATT_ITER_STOP) + { + /* At this point the inst->net_buf.data contains a NULL terminator + * string + */ + + bearer_uci_changed(conn, + 0, + inst_index, + (char *)inst->net_buf.data); + } + else + { + bearer_uci_changed(conn, ret, inst_index, NULL); + } + + tbs_client_gatt_read_complete(inst); + + return BT_GATT_ITER_STOP; + } + + return BT_GATT_ITER_CONTINUE; +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) */ + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) +static uint8_t read_technology_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, + read_params); + uint8_t inst_index = tbs_index(conn, inst); + uint8_t cb_err = err; + uint8_t technology = 0U; + + LOG_DBG("Index %u", inst_index); + + if (err != 0) + { + LOG_DBG("err: 0x%02X", err); + } + else if (data != NULL) + { + LOG_HEXDUMP_DBG(data, length, "Data read"); + if (length == sizeof(technology)) + { + (void)memcpy(&technology, data, length); + LOG_DBG("%s (0x%02x)", + bt_bearer_tech_str((enum bt_bearer_tech)technology), + + technology); + } + else + { + LOG_DBG("Invalid length"); + cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + } + + tbs_client_gatt_read_complete(inst); + + /* TODO: We should probably verify that the read value is valid before + * calling technology_changed with a non-0 error + */ + + technology_changed(conn, cb_err, inst_index, + (enum bt_bearer_tech)technology); + + return BT_GATT_ITER_STOP; +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) */ + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST) +static void uri_list_changed( + struct bt_conn *conn, int err, uint8_t inst_index, const char *uri_list) +{ + struct bt_tbs_client_cb *listener; + struct bt_tbs_client_cb *next; + + list_for_every_container_safe(&tbs_client_cbs, listener, next, _node) + { + if (listener->uri_list != NULL) + { + listener->uri_list(conn, err, inst_index, uri_list); + } + } +} + +static uint8_t read_uri_list_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, + read_params); + const uint8_t inst_index = tbs_index(conn, inst); + int ret; + + LOG_DBG(""); + + ret = handle_string_long_read(inst, err, data, params->single.offset, + length, + true); + if (ret != BT_GATT_ITER_CONTINUE) + { + if (ret == BT_GATT_ITER_STOP) + { + /* At this point the inst->net_buf.data contains a NULL terminator + * string + */ + + uri_list_changed(conn, 0, inst_index, (char *)inst->net_buf.data); + } + else + { + uri_list_changed(conn, ret, inst_index, NULL); + } + + tbs_client_gatt_read_complete(inst); + + return BT_GATT_ITER_STOP; + } + + return BT_GATT_ITER_CONTINUE; +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST) */ + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) +static uint8_t read_signal_strength_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, + read_params); + uint8_t inst_index = tbs_index(conn, inst); + uint8_t cb_err = err; + uint8_t signal_strength = 0U; + + LOG_DBG("Index %u", inst_index); + + if (err != 0) + { + LOG_DBG("err: 0x%02X", err); + } + else if (data != NULL) + { + LOG_HEXDUMP_DBG(data, length, "Data read"); + if (length == sizeof(signal_strength)) + { + (void)memcpy(&signal_strength, data, length); + LOG_DBG("0x%02x", signal_strength); + } + else + { + LOG_DBG("Invalid length"); + cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + } + + tbs_client_gatt_read_complete(inst); + + signal_strength_changed(conn, cb_err, inst_index, signal_strength); + + return BT_GATT_ITER_STOP; +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) */ + +#if defined(CONFIG_BT_TBS_CLIENT_READ_BEARER_SIGNAL_INTERVAL) +static void signal_interval_changed( + struct bt_conn *conn, int err, uint8_t inst_index, uint8_t signal_interval) +{ + struct bt_tbs_client_cb *listener; + struct bt_tbs_client_cb *next; + + list_for_every_container_safe(&tbs_client_cbs, listener, next, _node) + { + if (listener->signal_interval != NULL) + { + listener->signal_interval(conn, err, inst_index, signal_interval); + } + } +} + +static uint8_t read_signal_interval_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, + read_params); + uint8_t inst_index = tbs_index(conn, inst); + uint8_t cb_err = err; + uint8_t signal_interval = 0U; + + LOG_DBG("Index %u", inst_index); + + if (err != 0) + { + LOG_DBG("err: 0x%02X", err); + } + else if (data != NULL) + { + LOG_HEXDUMP_DBG(data, length, "Data read"); + if (length == sizeof(signal_interval)) + { + (void)memcpy(&signal_interval, data, length); + LOG_DBG("0x%02x", signal_interval); + } + else + { + LOG_DBG("Invalid length"); + cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + } + + tbs_client_gatt_read_complete(inst); + + signal_interval_changed(conn, cb_err, inst_index, signal_interval); + + return BT_GATT_ITER_STOP; +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_READ_BEARER_SIGNAL_INTERVAL) */ + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) +static uint8_t read_current_calls_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, + read_params); + uint8_t inst_index = tbs_index(conn, inst); + int tbs_err = err; + + LOG_DBG("Read bearer list current calls, index %u", inst_index); + + if ((tbs_err == 0) && (data != NULL) && + (net_buf_simple_tailroom(&inst->net_buf) < length)) + { + tbs_err = BT_ATT_ERR_INSUFFICIENT_RESOURCES; + } + + if (tbs_err != 0) + { + LOG_DBG("err: %d", tbs_err); + + tbs_client_gatt_read_complete(inst); + + current_calls_changed(conn, tbs_err, inst_index, 0U, NULL); + + return BT_GATT_ITER_STOP; + } + + if (data != NULL) + { + LOG_DBG("Current calls read (offset %u): %s", params->single.offset, + bt_hex(data, length)); + + net_buf_simple_add_mem(&inst->net_buf, data, length); + + /* Returning continue will try to read more using read + * long procedure + */ + + return BT_GATT_ITER_CONTINUE; + } + + tbs_client_gatt_read_complete(inst); + + if (inst->net_buf.len == 0U) + { + current_calls_changed(conn, 0, inst_index, 0U, NULL); + + return BT_GATT_ITER_STOP; + } + + bearer_list_current_calls(conn, inst, &inst->net_buf); + + return BT_GATT_ITER_STOP; +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) */ + +#if defined(CONFIG_BT_TBS_CLIENT_CCID) +static void ccid_changed(struct bt_conn *conn, + int err, uint8_t inst_index, uint8_t ccid) +{ + struct bt_tbs_client_cb *listener; + struct bt_tbs_client_cb *next; + + list_for_every_container_safe(&tbs_client_cbs, listener, next, _node) + { + if (listener->ccid != NULL) + { + listener->ccid(conn, err, inst_index, ccid); + } + } +} + +static uint8_t read_ccid_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, + const void *data, uint16_t length) +{ + struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, + read_params); + uint8_t inst_index = tbs_index(conn, inst); + uint8_t cb_err = err; + uint8_t ccid = 0U; + + LOG_DBG("Index %u", inst_index); + + if (err != 0) + { + LOG_DBG("err: 0x%02X", err); + } + else if (data != NULL) + { + LOG_HEXDUMP_DBG(data, length, "Data read"); + if (length == sizeof(ccid)) + { + (void)memcpy(&ccid, data, length); + LOG_DBG("0x%02x", ccid); + } + else + { + LOG_DBG("Invalid length"); + cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + } + + tbs_client_gatt_read_complete(inst); + + ccid_changed(conn, cb_err, inst_index, ccid); + + return BT_GATT_ITER_STOP; +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_CCID) */ + +#if defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) +static uint8_t read_status_flags_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, + read_params); + uint8_t inst_index = tbs_index(conn, inst); + uint8_t cb_err = err; + uint16_t status_flags = 0U; + + LOG_DBG("Index %u", inst_index); + + if (err != 0) + { + LOG_DBG("err: 0x%02X", err); + } + else if (data != NULL) + { + LOG_HEXDUMP_DBG(data, length, "Data read"); + if (length == sizeof(status_flags)) + { + (void)memcpy(&status_flags, data, length); + LOG_DBG("0x%04x", status_flags); + } + else + { + LOG_DBG("Invalid length"); + cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + } + + tbs_client_gatt_read_complete(inst); + + status_flags_changed(conn, cb_err, inst_index, status_flags); + + return BT_GATT_ITER_STOP; +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) */ + +#if defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) +static uint8_t read_call_uri_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, + read_params); + const uint8_t inst_index = tbs_index(conn, inst); + int ret; + + LOG_DBG(""); + + ret = handle_string_long_read(inst, err, data, params->single.offset, + length, + true); + if (ret != BT_GATT_ITER_CONTINUE) + { + if (ret == BT_GATT_ITER_STOP) + { + /* At this point the inst->net_buf.data contains a NULL terminator + * string + */ + + call_uri_changed(conn, 0, inst_index, (char *)inst->net_buf.data); + } + else + { + call_uri_changed(conn, ret, inst_index, NULL); + } + + tbs_client_gatt_read_complete(inst); + + return BT_GATT_ITER_STOP; + } + + return BT_GATT_ITER_CONTINUE; +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) */ + +static uint8_t read_call_state_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, + read_params); + uint8_t inst_index = tbs_index(conn, inst); + uint8_t cnt = 0U; + struct bt_tbs_client_call_state + call_states[CONFIG_BT_TBS_CLIENT_MAX_CALLS]; + int tbs_err = err; + + LOG_DBG("Index %u", inst_index); + + if ((tbs_err == 0) && (data != NULL) && + (net_buf_simple_tailroom(&inst->net_buf) < length)) + { + tbs_err = BT_ATT_ERR_INSUFFICIENT_RESOURCES; + } + + if (tbs_err != 0) + { + LOG_DBG("err: %d", tbs_err); + + tbs_client_gatt_read_complete(inst); + + call_state_changed(conn, tbs_err, inst_index, 0, NULL); + + return BT_GATT_ITER_STOP; + } + + if (data != NULL) + { + LOG_DBG("Call states read (offset %u): %s", params->single.offset, + bt_hex(data, length)); + + net_buf_simple_add_mem(&inst->net_buf, data, length); + + /* Returning continue will try to read more using read long procedure */ + + return BT_GATT_ITER_CONTINUE; + } + + if (inst->net_buf.len == 0U) + { + tbs_client_gatt_read_complete(inst); + + call_state_changed(conn, 0, inst_index, 0, NULL); + + return BT_GATT_ITER_STOP; + } + + /* Finished reading, start parsing */ + + while (inst->net_buf.len != 0U) + { + struct bt_tbs_client_call_state *call_state; + + if (cnt == CONFIG_BT_TBS_CLIENT_MAX_CALLS) + { + LOG_WRN("Could not parse all calls due to memory restrictions"); + break; + } + + call_state = &call_states[cnt]; + + tbs_err = net_buf_pull_call_state(&inst->net_buf, call_state); + if (tbs_err != 0) + { + LOG_DBG("Invalid current call notification: %d", err); + break; + } + + cnt++; + } + + tbs_client_gatt_read_complete(inst); + + call_state_changed(conn, tbs_err, inst_index, cnt, call_states); + + return BT_GATT_ITER_STOP; +} + +#if defined(CONFIG_BT_TBS_CLIENT_OPTIONAL_OPCODES) +static void optional_opcodes_changed( + struct bt_conn *conn, int err, uint8_t inst_index, + uint16_t optional_opcodes) +{ + struct bt_tbs_client_cb *listener; + struct bt_tbs_client_cb *next; + + list_for_every_container_safe(&tbs_client_cbs, listener, next, _node) + { + if (listener->optional_opcodes != NULL) + { + listener->optional_opcodes(conn, err, inst_index, optional_opcodes); + } + } +} + +static uint8_t read_optional_opcodes_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, + read_params); + uint8_t inst_index = tbs_index(conn, inst); + uint8_t cb_err = err; + uint16_t optional_opcodes = 0U; + + LOG_DBG("Index %u", inst_index); + + if (err != 0) + { + LOG_DBG("err: 0x%02X", err); + } + else if (data != NULL) + { + LOG_HEXDUMP_DBG(data, length, "Data read"); + if (length == sizeof(optional_opcodes)) + { + (void)memcpy(&optional_opcodes, data, length); + LOG_DBG("0x%04x", optional_opcodes); + } + else + { + LOG_DBG("Invalid length"); + cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + } + + tbs_client_gatt_read_complete(inst); + + optional_opcodes_changed(conn, cb_err, inst_index, optional_opcodes); + + return BT_GATT_ITER_STOP; +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_OPTIONAL_OPCODES) */ + +#if defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) +static uint8_t read_remote_uri_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, + read_params); + const uint8_t inst_index = tbs_index(conn, inst); + int ret; + + LOG_DBG(""); + + ret = handle_string_long_read(inst, err, data, params->single.offset, + length, + true); + if (ret != BT_GATT_ITER_CONTINUE) + { + if (ret == BT_GATT_ITER_STOP) + { + /* At this point the inst->net_buf.data contains a NULL terminator + * string + */ + + remote_uri_changed(conn, + 0, + inst_index, + (char *)inst->net_buf.data); + } + else + { + remote_uri_changed(conn, ret, inst_index, NULL); + } + + tbs_client_gatt_read_complete(inst); + + return BT_GATT_ITER_STOP; + } + + return BT_GATT_ITER_CONTINUE; +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) */ + +#if defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) +static uint8_t read_friendly_name_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, + read_params); + const uint8_t inst_index = tbs_index(conn, inst); + int ret; + + LOG_DBG(""); + + ret = handle_string_long_read(inst, err, data, params->single.offset, + length, + true); + if (ret != BT_GATT_ITER_CONTINUE) + { + if (ret == BT_GATT_ITER_STOP) + { + /* At this point the inst->net_buf.data contains a NULL terminator + * string + */ + + friendly_name_changed(conn, 0, inst_index, + (char *)inst->net_buf.data); + } + else + { + friendly_name_changed(conn, ret, inst_index, NULL); + } + + tbs_client_gatt_read_complete(inst); + + return BT_GATT_ITER_STOP; + } + + return BT_GATT_ITER_CONTINUE; +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) */ + +#if defined(CONFIG_BT_TBS_CLIENT_CCID) +static uint8_t disc_read_ccid_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct bt_tbs_instance *inst = CONTAINER_OF(params, struct bt_tbs_instance, + read_params); + uint8_t inst_index = tbs_index(conn, inst); + int cb_err = err; + + LOG_DBG("Index %u", inst_index); + + if (cb_err != 0) + { + LOG_DBG("err: 0x%02X", cb_err); + } + else if (data != NULL) + { + if (length == sizeof(inst->ccid)) + { + inst->ccid = ((uint8_t *)data)[0]; + LOG_DBG("0x%02x", inst->ccid); + } + else + { + LOG_DBG("Invalid length"); + cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + } + + tbs_client_gatt_read_complete(inst); + + if (cb_err != 0) + { + tbs_client_discover_complete(conn, cb_err); + } + else + { + discover_next_instance(conn); + } + + return BT_GATT_ITER_STOP; +} + +static void tbs_client_disc_read_ccid(struct bt_conn *conn) +{ + const uint8_t conn_index = bt_conn_index(conn); + struct bt_tbs_server_inst *srv_inst = &srv_insts[conn_index]; + struct bt_tbs_instance *inst = srv_inst->current_inst; + int err; + + err = tbs_client_gatt_read(conn, inst, inst->ccid_handle, + disc_read_ccid_cb); + if (err != 0) + { + tbs_client_discover_complete(conn, err); + } +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_CCID) */ + +/** + * @brief This will discover all characteristics on the server, retrieving + * the + * handles of the writeable characteristics and subscribing to all notify and + * indicate characteristics. + */ + +static uint8_t discover_func( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + const uint8_t conn_index = bt_conn_index(conn); + struct bt_tbs_server_inst *srv_inst = &srv_insts[conn_index]; + struct bt_tbs_instance *current_inst = srv_inst->current_inst; + + if (attr == NULL) + { +#if defined(CONFIG_BT_TBS_CLIENT_CCID) + /* Read the CCID as the last part of discovering a TBS instance */ + + tbs_client_disc_read_ccid(conn); +#else + discover_next_instance(conn); +#endif /* defined(CONFIG_BT_TBS_CLIENT_CCID) */ + + return BT_GATT_ITER_STOP; + } + + LOG_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle); + + if (params->type == BT_GATT_DISCOVER_CHARACTERISTIC) + { + const struct bt_gatt_chrc *chrc; + struct bt_gatt_subscribe_params *sub_params = NULL; + + chrc = (struct bt_gatt_chrc *)attr->user_data; + + if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_CALL_STATE) == 0) + { + LOG_DBG("Call state"); + sub_params = ¤t_inst->call_state_sub_params; + sub_params->value_handle = chrc->value_handle; + sub_params->disc_params = + ¤t_inst->call_state_sub_disc_params; +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) + } + else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_PROVIDER_NAME) == 0) + { + LOG_DBG("Provider name"); + sub_params = ¤t_inst->name_sub_params; + sub_params->value_handle = chrc->value_handle; + sub_params->disc_params = ¤t_inst->name_sub_disc_params; +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) + } + else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_UCI) == 0) + { + LOG_DBG("Bearer UCI"); + current_inst->bearer_uci_handle = chrc->value_handle; +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) + } + else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_TECHNOLOGY) == 0) + { + LOG_DBG("Technology"); + sub_params = ¤t_inst->technology_sub_params; + sub_params->value_handle = chrc->value_handle; + sub_params->disc_params = + ¤t_inst->technology_sub_disc_params; +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST) + } + else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_URI_LIST) == 0) + { + LOG_DBG("URI Scheme List"); + current_inst->uri_list_handle = chrc->value_handle; +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST) */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) + } + else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_SIGNAL_STRENGTH) == 0) + { + LOG_DBG("Signal strength"); + sub_params = ¤t_inst->signal_strength_sub_params; + sub_params->value_handle = chrc->value_handle; + sub_params->disc_params = + ¤t_inst->signal_strength_sub_disc_params; +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) */ +#if defined(CONFIG_BT_TBS_CLIENT_READ_BEARER_SIGNAL_INTERVAL) || \ + defined(CONFIG_BT_TBS_CLIENT_SET_BEARER_SIGNAL_INTERVAL) + } + else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_SIGNAL_INTERVAL) == 0) + { + LOG_DBG("Signal strength reporting interval"); + current_inst->signal_interval_handle = chrc->value_handle; +#endif /* defined(CONFIG_BT_TBS_CLIENT_READ_BEARER_SIGNAL_INTERVAL) */ + /* || defined(CONFIG_BT_TBS_CLIENT_SET_BEARER_SIGNAL_INTERVAL) */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) + } + else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_LIST_CURRENT_CALLS) == 0) + { + LOG_DBG("Current calls"); + sub_params = ¤t_inst->current_calls_sub_params; + sub_params->value_handle = chrc->value_handle; + sub_params->disc_params = + ¤t_inst->current_calls_sub_disc_params; +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) */ +#if defined(CONFIG_BT_TBS_CLIENT_CCID) + } + else if (bt_uuid_cmp(chrc->uuid, BT_UUID_CCID) == 0) + { + LOG_DBG("CCID"); + current_inst->ccid_handle = chrc->value_handle; +#endif /* defined(CONFIG_BT_TBS_CLIENT_CCID) */ +#if defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) + } + else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_INCOMING_URI) == 0) + { + LOG_DBG("Incoming target URI"); + sub_params = ¤t_inst->in_target_uri_sub_params; + sub_params->value_handle = chrc->value_handle; + sub_params->disc_params = + ¤t_inst->in_target_uri_sub_disc_params; +#endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) */ +#if defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) + } + else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_STATUS_FLAGS) == 0) + { + LOG_DBG("Status flags"); + sub_params = ¤t_inst->status_flags_sub_params; + sub_params->value_handle = chrc->value_handle; + sub_params->disc_params = ¤t_inst->status_sub_disc_params; +#endif /* defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) */ +#if defined(CONFIG_BT_TBS_CLIENT_CP_PROCEDURES) + } + else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_CALL_CONTROL_POINT) == 0) + { + LOG_DBG("Call control point"); + sub_params = ¤t_inst->call_cp_sub_params; + sub_params->value_handle = chrc->value_handle; + sub_params->disc_params = ¤t_inst->call_cp_sub_disc_params; +#endif /* defined(CONFIG_BT_TBS_CLIENT_CP_PROCEDURES) */ +#if defined(CONFIG_BT_TBS_CLIENT_OPTIONAL_OPCODES) + } + else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_OPTIONAL_OPCODES) == 0) + { + LOG_DBG("Supported opcodes"); + current_inst->optional_opcodes_handle = chrc->value_handle; +#endif /* defined(CONFIG_BT_TBS_CLIENT_OPTIONAL_OPCODES) */ + } + else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_TERMINATE_REASON) == 0) + { + LOG_DBG("Termination reason"); + current_inst->termination_reason_handle = chrc->value_handle; + sub_params = ¤t_inst->termination_sub_params; + sub_params->value_handle = chrc->value_handle; + sub_params->disc_params = + ¤t_inst->termination_sub_disc_params; +#if defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) + } + else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_FRIENDLY_NAME) == 0) + { + LOG_DBG("Incoming friendly name"); + sub_params = ¤t_inst->friendly_name_sub_params; + sub_params->value_handle = chrc->value_handle; + sub_params->disc_params = + ¤t_inst->friendly_name_sub_disc_params; +#endif /* defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) */ +#if defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) + } + else if (bt_uuid_cmp(chrc->uuid, BT_UUID_TBS_INCOMING_CALL) == 0) + { + LOG_DBG("Incoming call"); + sub_params = ¤t_inst->incoming_call_sub_params; + sub_params->value_handle = chrc->value_handle; + sub_params->disc_params = + ¤t_inst->incoming_call_sub_disc_params; +#endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) */ + } + + if (sub_params != NULL) + { + sub_params->value = 0U; + if (chrc->properties & BT_GATT_CHRC_NOTIFY) + { + sub_params->value = BT_GATT_CCC_NOTIFY; + } + else if (chrc->properties & BT_GATT_CHRC_INDICATE) + { + sub_params->value = BT_GATT_CCC_INDICATE; + } + + if (sub_params->value != 0) + { + int err; + + sub_params->ccc_handle = BT_GATT_AUTO_DISCOVER_CCC_HANDLE; + sub_params->end_handle = current_inst->end_handle; + sub_params->notify = notify_handler; + atomic_set_bit(sub_params->flags, + BT_GATT_SUBSCRIBE_FLAG_VOLATILE); + + err = bt_gatt_subscribe(conn, sub_params); + if (err != 0 && err != -EALREADY) + { + LOG_DBG("Could not subscribe to " + "characteristic at handle 0x%04X " + "(%d)", + sub_params->value_handle, err); + tbs_client_discover_complete(conn, err); + + return BT_GATT_ITER_STOP; + } + else + { + LOG_DBG("Subscribed to characteristic at " + "handle 0x%04X", + sub_params->value_handle); + } + } + } + } + + return BT_GATT_ITER_CONTINUE; +} + +static struct bt_tbs_instance *get_next_instance(struct bt_conn *conn, + struct bt_tbs_server_inst *srv_inst) +{ + uint8_t inst_index; + + if (srv_inst->current_inst != NULL) + { + inst_index = tbs_index(conn, srv_inst->current_inst); + if (inst_index == BT_TBS_GTBS_INDEX) + { + inst_index = 0U; + } + else + { + inst_index++; + } + + return tbs_inst_by_index(conn, inst_index); + } + + inst_index = gtbs_found(srv_inst) ? BT_TBS_GTBS_INDEX : 0; + + return tbs_inst_by_index(conn, inst_index); +} + +static void discover_next_instance(struct bt_conn *conn) +{ + int err; + uint8_t conn_index = bt_conn_index(conn); + struct bt_tbs_server_inst *srv_inst = &srv_insts[conn_index]; + + srv_inst->current_inst = get_next_instance(conn, srv_inst); + if (srv_inst->current_inst == NULL) + { + tbs_client_discover_complete(conn, 0); + return; + } + + LOG_DBG("inst_index %u", tbs_index(conn, srv_inst->current_inst)); + + (void)memset(&srv_inst->discover_params, 0, + sizeof(srv_inst->discover_params)); + srv_inst->discover_params.uuid = NULL; + srv_inst->discover_params.start_handle = + srv_inst->current_inst->start_handle; + srv_inst->discover_params.end_handle = srv_inst->current_inst->end_handle; + srv_inst->discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; + srv_inst->discover_params.func = discover_func; + + err = bt_gatt_discover(conn, &srv_inst->discover_params); + if (err != 0) + { + tbs_client_discover_complete(conn, err); + } +} + +static void primary_discover_complete(struct bt_tbs_server_inst *server, + struct bt_conn *conn) +{ + if (IS_ENABLED(CONFIG_BT_TBS_CLIENT_GTBS)) + { + LOG_DBG("Discover complete, found %u instances (GTBS%s found)", + inst_cnt(server), + gtbs_found(server) ? "" : " not"); + } + else + { + LOG_DBG("Discover complete, found %u instances", inst_cnt(server)); + } + + server->current_inst = NULL; + + if (gtbs_found(server) || inst_cnt(server) > 0) + { + discover_next_instance(conn); + } + else + { + tbs_client_discover_complete(conn, 0); + } +} + +/** + * @brief This will discover all characteristics on the server, retrieving + * the + * handles of the writeable characteristics and subscribing to all notify and + * indicate characteristics. + */ +#if defined(CONFIG_BT_TBS_CLIENT_TBS) +static const struct bt_uuid *tbs_uuid = BT_UUID_TBS; + +static uint8_t primary_discover_tbs_cb( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + const uint8_t conn_index = bt_conn_index(conn); + struct bt_tbs_server_inst *srv_inst = &srv_insts[conn_index]; + + ARG_UNUSED(params); + + LOG_DBG("conn %p attr %p", (void *)conn, attr); + + if (attr != NULL) + { + const struct bt_gatt_service_val *prim_service; + + LOG_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle); + + prim_service = (struct bt_gatt_service_val *)attr->user_data; + + srv_inst->current_inst = &srv_inst->tbs_insts[srv_inst->inst_cnt]; + srv_inst->inst_cnt++; + srv_inst->current_inst->start_handle = attr->handle + 1; + srv_inst->current_inst->end_handle = prim_service->end_handle; + + if (srv_inst->inst_cnt < ARRAY_SIZE(srv_inst->tbs_insts)) + { + return BT_GATT_ITER_CONTINUE; + } + } + + primary_discover_complete(srv_inst, conn); + + return BT_GATT_ITER_STOP; +} + +static int primary_discover_tbs(struct bt_conn *conn) +{ + struct bt_tbs_server_inst *srv_inst = &srv_insts[bt_conn_index(conn)]; + struct bt_gatt_discover_params *params = &srv_inst->discover_params; + + LOG_DBG("conn %p", (void *)conn); + + (void)memset(params, 0, sizeof(*params)); + params->uuid = tbs_uuid; + params->func = primary_discover_tbs_cb; + params->type = BT_GATT_DISCOVER_PRIMARY; + params->start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; + params->end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + + return bt_gatt_discover(conn, params); +} +#endif /* CONFIG_BT_TBS_CLIENT_TBS */ + +#if defined(CONFIG_BT_TBS_CLIENT_GTBS) +static const struct bt_uuid *gtbs_uuid = BT_UUID_GTBS; + +static uint8_t primary_discover_gtbs_cb( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + const uint8_t conn_index = bt_conn_index(conn); + struct bt_tbs_server_inst *srv_inst = &srv_insts[conn_index]; + + ARG_UNUSED(params); + + LOG_DBG("conn %p attr %p", (void *)conn, attr); + + if (attr != NULL) + { + const struct bt_gatt_service_val *prim_service; + + LOG_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle); + + prim_service = (struct bt_gatt_service_val *)attr->user_data; + + srv_inst->current_inst = &srv_inst->gtbs_inst; + srv_inst->current_inst->start_handle = attr->handle + 1; + srv_inst->current_inst->end_handle = prim_service->end_handle; + } + +#if defined(CONFIG_BT_TBS_CLIENT_TBS) + int err; + + err = primary_discover_tbs(conn); + if (err == 0) + { + return BT_GATT_ITER_STOP; + } + + LOG_DBG("Discover failed (err %d)", err); +#endif /* CONFIG_BT_TBS_CLIENT_TBS */ + + primary_discover_complete(srv_inst, conn); + + return BT_GATT_ITER_STOP; +} + +static int primary_discover_gtbs(struct bt_conn *conn) +{ + struct bt_tbs_server_inst *srv_inst = &srv_insts[bt_conn_index(conn)]; + struct bt_gatt_discover_params *params = &srv_inst->discover_params; + + LOG_DBG("conn %p", (void *)conn); + + (void)memset(params, 0, sizeof(*params)); + params->uuid = gtbs_uuid; + params->func = primary_discover_gtbs_cb; + params->type = BT_GATT_DISCOVER_PRIMARY; + params->start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; + params->end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + + return bt_gatt_discover(conn, params); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_GTBS) */ + +/* Public Api */ + +#if defined(CONFIG_BT_TBS_CLIENT_HOLD_CALL) +int bt_tbs_client_hold_call(struct bt_conn *conn, + uint8_t inst_index, uint8_t call_index) +{ + return tbs_client_common_call_control(conn, inst_index, call_index, + BT_TBS_CALL_OPCODE_HOLD); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_HOLD_CALL) */ + +#if defined(CONFIG_BT_TBS_CLIENT_ACCEPT_CALL) +int bt_tbs_client_accept_call(struct bt_conn *conn, + uint8_t inst_index, uint8_t call_index) +{ + return tbs_client_common_call_control(conn, inst_index, call_index, + BT_TBS_CALL_OPCODE_ACCEPT); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_ACCEPT_CALL) */ + +#if defined(CONFIG_BT_TBS_CLIENT_RETRIEVE_CALL) +int bt_tbs_client_retrieve_call(struct bt_conn *conn, + uint8_t inst_index, uint8_t call_index) +{ + return tbs_client_common_call_control(conn, inst_index, call_index, + BT_TBS_CALL_OPCODE_RETRIEVE); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_RETRIEVE_CALL) */ + +#if defined(CONFIG_BT_TBS_CLIENT_TERMINATE_CALL) +int bt_tbs_client_terminate_call(struct bt_conn *conn, + uint8_t inst_index, uint8_t call_index) +{ + return tbs_client_common_call_control(conn, inst_index, call_index, + BT_TBS_CALL_OPCODE_TERMINATE); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_TERMINATE_CALL) */ + +#if defined(CONFIG_BT_TBS_CLIENT_ORIGINATE_CALL) +int bt_tbs_client_originate_call(struct bt_conn *conn, + uint8_t inst_index, const char *uri) +{ + struct bt_tbs_instance *inst; + uint8_t write_buf[CONFIG_BT_L2CAP_TX_MTU]; + struct bt_tbs_call_cp_originate *originate; + size_t uri_len; + const size_t max_uri_len = sizeof(write_buf) - sizeof(*originate); + + if (conn == NULL) + { + return -ENOTCONN; + } + else if (!bt_tbs_valid_uri(uri, strlen(uri))) + { + LOG_DBG("Invalid URI: %s", uri); + return -EINVAL; + } + + inst = tbs_inst_by_index(conn, inst_index); + if (inst == NULL) + { + return -EINVAL; + } + + /* Check if there are free spots */ + + if (!free_call_spot(inst)) + { + LOG_DBG("Cannot originate more calls"); + return -ENOMEM; + } + + uri_len = strlen(uri); + + if (uri_len > max_uri_len) + { + LOG_DBG("URI len (%zu) longer than maximum writable %zu", uri_len, + max_uri_len); + return -ENOMEM; + } + + originate = (struct bt_tbs_call_cp_originate *)write_buf; + originate->opcode = BT_TBS_CALL_OPCODE_ORIGINATE; + (void)memcpy(originate->uri, uri, uri_len); + + return bt_gatt_write_without_response(conn, + inst->call_cp_sub_params. + value_handle, originate, + sizeof(*originate) + uri_len, false); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_ORIGINATE_CALL) */ + +#if defined(CONFIG_BT_TBS_CLIENT_JOIN_CALLS) +int bt_tbs_client_join_calls( + struct bt_conn *conn, uint8_t inst_index, const uint8_t *call_indexes, + uint8_t count) +{ + if (conn == NULL) + { + return -ENOTCONN; + } + + /* Write to call control point */ + + if (call_indexes && count > 1 && count <= CONFIG_BT_TBS_CLIENT_MAX_CALLS) + { + struct bt_tbs_instance *inst; + struct bt_tbs_call_cp_join *join; + uint8_t write_buf[CONFIG_BT_L2CAP_TX_MTU]; + const size_t max_call_cnt = sizeof(write_buf) - sizeof(join->opcode); + + inst = tbs_inst_by_index(conn, inst_index); + if (inst == NULL) + { + return -EINVAL; + } + + if (inst->call_cp_sub_params.value_handle == 0) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + + if (count > max_call_cnt) + { + LOG_DBG("Call count (%u) larger than maximum writable %zu", count, + max_call_cnt); + return -ENOMEM; + } + + join = (struct bt_tbs_call_cp_join *)write_buf; + + join->opcode = BT_TBS_CALL_OPCODE_JOIN; + (void)memcpy(join->call_indexes, call_indexes, count); + + return bt_gatt_write_without_response(conn, + inst->call_cp_sub_params.value_handle, + join, + sizeof(*join) + count, false); + } + + return -EINVAL; +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_JOIN_CALLS) */ + +#if defined(CONFIG_BT_TBS_CLIENT_SET_BEARER_SIGNAL_INTERVAL) +int bt_tbs_client_set_signal_strength_interval( + struct bt_conn *conn, uint8_t inst_index, uint8_t interval) +{ + struct bt_tbs_instance *inst; + + if (conn == NULL) + { + return -ENOTCONN; + } + + inst = tbs_inst_by_index(conn, inst_index); + if (inst == NULL) + { + return -EINVAL; + } + + /* Populate Outgoing Remote URI */ + + if (inst->signal_interval_handle == 0) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + + return bt_gatt_write_without_response(conn, inst->signal_interval_handle, + &interval, + sizeof(interval), false); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_SET_BEARER_SIGNAL_INTERVAL) */ + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) +int bt_tbs_client_read_bearer_provider_name(struct bt_conn *conn, + uint8_t inst_index) +{ + struct bt_tbs_instance *inst; + + if (conn == NULL) + { + return -ENOTCONN; + } + + inst = tbs_inst_by_index(conn, inst_index); + if (inst == NULL) + { + return -EINVAL; + } + + if (inst->name_sub_params.value_handle == 0) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + + return tbs_client_gatt_read(conn, inst, inst->name_sub_params.value_handle, + read_bearer_provider_name_cb); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) */ + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) +int bt_tbs_client_read_bearer_uci(struct bt_conn *conn, uint8_t inst_index) +{ + struct bt_tbs_instance *inst; + + if (conn == NULL) + { + return -ENOTCONN; + } + + inst = tbs_inst_by_index(conn, inst_index); + if (inst == NULL) + { + return -EINVAL; + } + + if (inst->bearer_uci_handle == 0) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + + return tbs_client_gatt_read(conn, inst, inst->bearer_uci_handle, + read_bearer_uci_cb); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) */ + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) +int bt_tbs_client_read_technology(struct bt_conn *conn, uint8_t inst_index) +{ + struct bt_tbs_instance *inst; + + if (conn == NULL) + { + return -ENOTCONN; + } + + inst = tbs_inst_by_index(conn, inst_index); + if (inst == NULL) + { + return -EINVAL; + } + + if (inst->technology_sub_params.value_handle == 0) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + + return tbs_client_gatt_read(conn, inst, + inst->technology_sub_params.value_handle, + read_technology_cb); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) */ + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST) +int bt_tbs_client_read_uri_list(struct bt_conn *conn, uint8_t inst_index) +{ + struct bt_tbs_instance *inst; + + if (conn == NULL) + { + return -ENOTCONN; + } + + inst = tbs_inst_by_index(conn, inst_index); + if (inst == NULL) + { + return -EINVAL; + } + + if (inst->uri_list_handle == 0) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + + return tbs_client_gatt_read(conn, inst, inst->uri_list_handle, + read_uri_list_cb); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST) */ + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) +int bt_tbs_client_read_signal_strength(struct bt_conn *conn, + uint8_t inst_index) +{ + struct bt_tbs_instance *inst; + + if (conn == NULL) + { + return -ENOTCONN; + } + + inst = tbs_inst_by_index(conn, inst_index); + if (inst == NULL) + { + return -EINVAL; + } + + if (inst->signal_strength_sub_params.value_handle == 0) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + + return tbs_client_gatt_read(conn, inst, + inst->signal_strength_sub_params.value_handle, + read_signal_strength_cb); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) */ + +#if defined(CONFIG_BT_TBS_CLIENT_READ_BEARER_SIGNAL_INTERVAL) +int bt_tbs_client_read_signal_interval(struct bt_conn *conn, + uint8_t inst_index) +{ + struct bt_tbs_instance *inst; + + if (conn == NULL) + { + return -ENOTCONN; + } + + inst = tbs_inst_by_index(conn, inst_index); + if (inst == NULL) + { + return -EINVAL; + } + + if (inst->signal_interval_handle == 0) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + + return tbs_client_gatt_read(conn, inst, inst->signal_interval_handle, + read_signal_interval_cb); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_READ_BEARER_SIGNAL_INTERVAL) */ + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) +int bt_tbs_client_read_current_calls(struct bt_conn *conn, + uint8_t inst_index) +{ + struct bt_tbs_instance *inst; + + if (conn == NULL) + { + return -ENOTCONN; + } + + inst = tbs_inst_by_index(conn, inst_index); + if (inst == NULL) + { + return -EINVAL; + } + + if (inst->current_calls_sub_params.value_handle == 0) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + + return tbs_client_gatt_read(conn, inst, + inst->current_calls_sub_params.value_handle, + read_current_calls_cb); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) */ + +#if defined(CONFIG_BT_TBS_CLIENT_CCID) +int bt_tbs_client_read_ccid(struct bt_conn *conn, uint8_t inst_index) +{ + struct bt_tbs_instance *inst; + + if (conn == NULL) + { + return -ENOTCONN; + } + + inst = tbs_inst_by_index(conn, inst_index); + if (inst == NULL) + { + return -EINVAL; + } + + if (inst->ccid_handle == 0) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + + return tbs_client_gatt_read(conn, inst, inst->ccid_handle, read_ccid_cb); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_CCID) */ + +#if defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) +int bt_tbs_client_read_call_uri(struct bt_conn *conn, uint8_t inst_index) +{ + struct bt_tbs_instance *inst; + + if (conn == NULL) + { + return -ENOTCONN; + } + + inst = tbs_inst_by_index(conn, inst_index); + if (inst == NULL) + { + return -EINVAL; + } + + if (inst->in_target_uri_sub_params.value_handle == 0) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + + return tbs_client_gatt_read(conn, inst, + inst->in_target_uri_sub_params.value_handle, + read_call_uri_cb); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) */ + +#if defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) +int bt_tbs_client_read_status_flags(struct bt_conn *conn, uint8_t inst_index) +{ + struct bt_tbs_instance *inst; + + if (conn == NULL) + { + return -ENOTCONN; + } + + inst = tbs_inst_by_index(conn, inst_index); + if (inst == NULL) + { + return -EINVAL; + } + + if (inst->status_flags_sub_params.value_handle == 0) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + + return tbs_client_gatt_read(conn, inst, + inst->status_flags_sub_params.value_handle, + read_status_flags_cb); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) */ + +int bt_tbs_client_read_call_state(struct bt_conn *conn, uint8_t inst_index) +{ + struct bt_tbs_instance *inst; + + if (conn == NULL) + { + return -ENOTCONN; + } + + inst = tbs_inst_by_index(conn, inst_index); + if (inst == NULL) + { + return -EINVAL; + } + + if (inst->call_state_sub_params.value_handle == 0) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + + return tbs_client_gatt_read(conn, inst, + inst->call_state_sub_params.value_handle, + read_call_state_cb); +} + +#if defined(CONFIG_BT_TBS_CLIENT_OPTIONAL_OPCODES) +int bt_tbs_client_read_optional_opcodes(struct bt_conn *conn, + uint8_t inst_index) +{ + struct bt_tbs_instance *inst; + + if (conn == NULL) + { + return -ENOTCONN; + } + + inst = tbs_inst_by_index(conn, inst_index); + if (inst == NULL) + { + return -EINVAL; + } + + if (inst->optional_opcodes_handle == 0) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + + return tbs_client_gatt_read(conn, inst, inst->optional_opcodes_handle, + read_optional_opcodes_cb); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_OPTIONAL_OPCODES) */ + +#if defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) +int bt_tbs_client_read_remote_uri(struct bt_conn *conn, uint8_t inst_index) +{ + struct bt_tbs_instance *inst; + + if (conn == NULL) + { + return -ENOTCONN; + } + + inst = tbs_inst_by_index(conn, inst_index); + if (inst == NULL) + { + return -EINVAL; + } + + if (inst->incoming_call_sub_params.value_handle == 0) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + + return tbs_client_gatt_read(conn, inst, + inst->incoming_call_sub_params.value_handle, + read_remote_uri_cb); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) */ + +#if defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) +int bt_tbs_client_read_friendly_name(struct bt_conn *conn, + uint8_t inst_index) +{ + struct bt_tbs_instance *inst; + + if (conn == NULL) + { + return -ENOTCONN; + } + + inst = tbs_inst_by_index(conn, inst_index); + if (inst == NULL) + { + return -EINVAL; + } + + if (inst->friendly_name_sub_params.value_handle == 0) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + + return tbs_client_gatt_read(conn, inst, + inst->friendly_name_sub_params.value_handle, + read_friendly_name_cb); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) */ + +static bool check_and_set_all_busy(struct bt_tbs_server_inst *srv_inst) +{ + bool all_idle = true; + +#if defined(CONFIG_BT_TBS_CLIENT_GTBS) + if (atomic_test_and_set_bit(srv_inst->gtbs_inst.flags, + BT_TBS_CLIENT_FLAG_BUSY)) + { + LOG_DBG("GTBS is busy"); + + return false; + } +#endif /* CONFIG_BT_TBS_CLIENT_GTBS */ + +#if defined(CONFIG_BT_TBS_CLIENT_TBS) + size_t num_free; + + for (num_free = 0U; num_free < ARRAY_SIZE(srv_inst->tbs_insts); num_free++) + { + struct bt_tbs_instance *tbs_inst = &srv_inst->tbs_insts[num_free]; + + if (atomic_test_and_set_bit(tbs_inst->flags, BT_TBS_CLIENT_FLAG_BUSY)) + { + LOG_DBG("inst[%zu] (%p) is busy", num_free, tbs_inst); + all_idle = false; + + break; + } + } +#endif /* CONFIG_BT_TBS_CLIENT_TBS */ + + /* If any is busy, revert any busy states we've set */ + + if (!all_idle) + { +#if defined(CONFIG_BT_TBS_CLIENT_GTBS) + atomic_clear_bit(srv_inst->gtbs_inst.flags, BT_TBS_CLIENT_FLAG_BUSY); +#endif /* CONFIG_BT_TBS_CLIENT_GTBS */ +#if defined(CONFIG_BT_TBS_CLIENT_TBS) + for (uint8_t i = 0U; i < num_free; i++) + { + atomic_clear_bit(srv_inst->tbs_insts[i].flags, + BT_TBS_CLIENT_FLAG_BUSY); + } +#endif /* CONFIG_BT_TBS_CLIENT_TBS */ + } + + return all_idle; +} + +int bt_tbs_client_discover(struct bt_conn *conn) +{ + uint8_t conn_index; + struct bt_tbs_server_inst *srv_inst; + + if (conn == NULL) + { + return -ENOTCONN; + } + + conn_index = bt_conn_index(conn); + srv_inst = &srv_insts[conn_index]; + + /* Before we do discovery we ensure that all TBS instances are currently + * not busy as to not interfere with any procedures in progress + */ + + if (!check_and_set_all_busy(srv_inst)) + { + return -EBUSY; + } + +#if defined(CONFIG_BT_TBS_CLIENT_TBS) + (void)memset(srv_inst->tbs_insts, 0, + sizeof(srv_inst->tbs_insts)); /* reset data */ + srv_inst->inst_cnt = 0U; +#endif /* CONFIG_BT_TBS_CLIENT_TBS */ +#if defined(CONFIG_BT_TBS_CLIENT_GTBS) + (void)memset(&srv_inst->gtbs_inst, 0, + sizeof(srv_inst->gtbs_inst)); /* reset data */ + return primary_discover_gtbs(conn); +#else + return primary_discover_tbs(conn); +#endif /* CONFIG_BT_TBS_CLIENT_GTBS */ +} + +int bt_tbs_client_register_cb(struct bt_tbs_client_cb *cb) +{ + if (cb == NULL) + { + LOG_DBG("cb is NULL"); + + return -EINVAL; + } + + if (list_find(&tbs_client_cbs, &cb->_node, NULL)) + { + return -EEXIST; + } + + list_add_tail(&tbs_client_cbs, &cb->_node); + + return 0; +} + +#if defined(CONFIG_BT_TBS_CLIENT_CCID) +static bool tbs_instance_ccid_is_eq(struct bt_tbs_instance *inst, + void *user_data) +{ + uint8_t ccid = POINTER_TO_UINT(user_data); + + return inst->ccid == ccid; +} + +struct bt_tbs_instance *bt_tbs_client_get_by_ccid(const struct bt_conn *conn, + uint8_t ccid) +{ + struct bt_tbs_server_inst *server; + + if (conn == NULL) + { + LOG_DBG("conn was NULL"); + return NULL; + } + + server = &srv_insts[bt_conn_index(conn)]; + + return tbs_instance_find(server, tbs_instance_ccid_is_eq, + UINT_TO_POINTER(ccid)); +} + +struct bt_tbs_instance *bt_tbs_client_get_by_index( + const struct bt_conn *conn, uint8_t index) +{ + if (conn == NULL) + { + LOG_DBG("conn was NULL"); + return NULL; + } + + return tbs_inst_by_index(conn, index); +} +#endif /* defined(CONFIG_BT_TBS_CLIENT_CCID) */ diff --git a/libs/zblue/audio/tbs_internal.h b/libs/zblue/audio/tbs_internal.h new file mode 100644 index 0000000000000..fa0ea1d2363d8 --- /dev/null +++ b/libs/zblue/audio/tbs_internal.h @@ -0,0 +1,445 @@ +/**************************************************************************** + * libs/zblue/audio/tbs_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Internal APIs for Bluetooth TBS. + */ + +/* Copyright (c) 2019 Bose Corporation + * Copyright (c) 2021-2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define BT_TBS_MIN_URI_LEN 3U /* a:b */ +#define BT_TBS_FREE_CALL_INDEX 0U + +/* Call Control Point Opcodes */ +#define BT_TBS_CALL_OPCODE_ACCEPT 0x00U +#define BT_TBS_CALL_OPCODE_TERMINATE 0x01U +#define BT_TBS_CALL_OPCODE_HOLD 0x02U +#define BT_TBS_CALL_OPCODE_RETRIEVE 0x03U +#define BT_TBS_CALL_OPCODE_ORIGINATE 0x04U +#define BT_TBS_CALL_OPCODE_JOIN 0x05U + +/* 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 0x80U +#define BT_TBS_LOCAL_OPCODE_HOLD 0x81U +#define BT_TBS_LOCAL_OPCODE_RETRIEVE 0x82U +#define BT_TBS_LOCAL_OPCODE_TERMINATE 0x83U +#define BT_TBS_LOCAL_OPCODE_INCOMING 0x84U +#define BT_TBS_LOCAL_OPCODE_SERVER_TERMINATE 0x85U + +#define FIRST_PRINTABLE_ASCII_CHAR ' ' /* space */ + +#define BT_TBS_CALL_FLAG_SET_INCOMING(flag) (flag &= ~BT_TBS_CALL_FLAG_OUTGOING) +#define BT_TBS_CALL_FLAG_SET_OUTGOING(flag) (flag |= BT_TBS_CALL_FLAG_OUTGOING) + +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_bearer_tech_str(enum bt_bearer_tech tech) +{ + switch (tech) + { + case BT_BEARER_TECH_3G: + return "3G"; + case BT_BEARER_TECH_4G: + return "4G"; + case BT_BEARER_TECH_LTE: + return "LTE"; + case BT_BEARER_TECH_WIFI: + return "WIFI"; + case BT_BEARER_TECH_5G: + return "5G"; + case BT_BEARER_TECH_GSM: + return "GSM"; + case BT_BEARER_TECH_CDMA: + return "CDMA"; + case BT_BEARER_TECH_2G: + return "2G"; + case BT_BEARER_TECH_WCDMA: + return "WCDMA"; + 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 @p uri contains a colon (':') followed by a printable + * character. Minimal uri is "a:b". + * + * @param uri The uri "scheme:id" + * @param len The length of uri + * @return true If the above is true + * @return false If the above is not true + */ + +static inline bool bt_tbs_valid_uri(const uint8_t *uri, size_t uri_len) +{ + if (!uri) + { + return false; + } + + if (uri_len > CONFIG_BT_TBS_MAX_URI_LENGTH || uri_len < BT_TBS_MIN_URI_LEN) + { + return false; + } + else if (uri[0] < FIRST_PRINTABLE_ASCII_CHAR) + { + /* Invalid first char */ + + return false; + } + + for (size_t i = 1U; i < uri_len - 1U; i++) + { + if (uri[i] == ':' && uri[i + 1U] >= 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[]; +} __packed; + +struct bt_tbs_call_cp_join +{ + uint8_t opcode; + uint8_t call_indexes[]; +} __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; + +struct bt_tbs_friendly_name +{ + uint8_t call_index; + char name[CONFIG_BT_TBS_MAX_FRIENDLY_NAME_LENGTH + 1]; +} __packed; + +#if defined(CONFIG_BT_TBS_CLIENT) + +/* Features which may require long string reads */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) || \ + defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) || \ + defined(CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST) || \ + defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) || \ + defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) || \ + defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) || \ + defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) +#define BT_TBS_CLIENT_INST_READ_BUF_SIZE (BT_ATT_MAX_ATTRIBUTE_LEN + 1 /* NULL terminator*/) +#else +/* Need only be the size of call state reads which is the largest of the + * remaining characteristic values + */ +#define BT_TBS_CLIENT_INST_READ_BUF_SIZE \ + MIN(BT_ATT_MAX_ATTRIBUTE_LEN, \ + (CONFIG_BT_TBS_CLIENT_MAX_CALLS * sizeof(struct bt_tbs_client_call_state))) +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) */ + +enum bt_tbs_client_flag +{ + BT_TBS_CLIENT_FLAG_BUSY, + + BT_TBS_CLIENT_FLAG_NUM_FLAGS, /* keep as last */ +}; + +/* TODO: The storage of calls, handles and parameters should be moved to the + * user of the TBS client (e.g. the CCP client). This allows for users to use + * the Zephyr CCP client with static allocation or implement their own CCP + * client or even other profile roles that use the TBS client without being + * restricted to static memory allocation + */ + +struct bt_tbs_instance +{ + struct bt_tbs_client_call_state calls[CONFIG_BT_TBS_CLIENT_MAX_CALLS]; + + uint16_t start_handle; + uint16_t end_handle; +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) + uint16_t bearer_uci_handle; +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_UCI) */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST) + uint16_t uri_list_handle; +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_URI_SCHEMES_SUPPORTED_LIST) */ +#if defined(CONFIG_BT_TBS_CLIENT_READ_BEARER_SIGNAL_INTERVAL) || \ + defined(CONFIG_BT_TBS_CLIENT_SET_BEARER_SIGNAL_INTERVAL) + uint16_t signal_interval_handle; +#endif /* defined(CONFIG_BT_TBS_CLIENT_READ_BEARER_SIGNAL_INTERVAL) */ + /* || defined(CONFIG_BT_TBS_CLIENT_SET_BEARER_SIGNAL_INTERVAL) */ +#if defined(CONFIG_BT_TBS_CLIENT_CCID) + uint16_t ccid_handle; +#endif /* defined(CONFIG_BT_TBS_CLIENT_CCID) */ +#if defined(CONFIG_BT_TBS_CLIENT_OPTIONAL_OPCODES) + uint16_t optional_opcodes_handle; +#endif /* defined(CONFIG_BT_TBS_CLIENT_OPTIONAL_OPCODES) */ + uint16_t termination_reason_handle; + +#if defined(CONFIG_BT_TBS_CLIENT_CCID) + uint8_t ccid; +#endif /* defined(CONFIG_BT_TBS_CLIENT_CCID) */ + +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) + struct bt_gatt_subscribe_params name_sub_params; + struct bt_gatt_discover_params name_sub_disc_params; +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_PROVIDER_NAME) */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) + struct bt_gatt_subscribe_params technology_sub_params; + struct bt_gatt_discover_params technology_sub_disc_params; +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_TECHNOLOGY) */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) + struct bt_gatt_subscribe_params signal_strength_sub_params; + struct bt_gatt_discover_params signal_strength_sub_disc_params; +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_SIGNAL_STRENGTH) */ +#if defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) + struct bt_gatt_subscribe_params current_calls_sub_params; + struct bt_gatt_discover_params current_calls_sub_disc_params; +#endif /* defined(CONFIG_BT_TBS_CLIENT_BEARER_LIST_CURRENT_CALLS) */ +#if defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) + struct bt_gatt_subscribe_params status_flags_sub_params; + struct bt_gatt_discover_params status_sub_disc_params; +#endif /* defined(CONFIG_BT_TBS_CLIENT_STATUS_FLAGS) */ +#if defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) + struct bt_gatt_subscribe_params in_target_uri_sub_params; + struct bt_gatt_discover_params in_target_uri_sub_disc_params; +#endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_URI) */ +#if defined(CONFIG_BT_TBS_CLIENT_CP_PROCEDURES) + struct bt_gatt_subscribe_params call_cp_sub_params; + struct bt_gatt_discover_params call_cp_sub_disc_params; +#endif /* defined(CONFIG_BT_TBS_CLIENT_OPTIONAL_OPCODES) */ +#if defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) + struct bt_gatt_subscribe_params friendly_name_sub_params; + struct bt_gatt_discover_params friendly_name_sub_disc_params; +#endif /* defined(CONFIG_BT_TBS_CLIENT_CALL_FRIENDLY_NAME) */ +#if defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) + struct bt_gatt_subscribe_params incoming_call_sub_params; + struct bt_gatt_discover_params incoming_call_sub_disc_params; +#endif /* defined(CONFIG_BT_TBS_CLIENT_INCOMING_CALL) */ + struct bt_gatt_subscribe_params call_state_sub_params; + struct bt_gatt_discover_params call_state_sub_disc_params; + struct bt_gatt_subscribe_params termination_sub_params; + struct bt_gatt_discover_params termination_sub_disc_params; + struct bt_gatt_read_params read_params; + uint8_t read_buf[BT_TBS_CLIENT_INST_READ_BUF_SIZE]; + struct net_buf_simple net_buf; + + atomic_define(flags, BT_TBS_CLIENT_FLAG_NUM_FLAGS); +}; +#endif /* CONFIG_BT_TBS_CLIENT */ diff --git a/libs/zblue/audio/tmap.c b/libs/zblue/audio/tmap.c new file mode 100644 index 0000000000000..af05ee571e87c --- /dev/null +++ b/libs/zblue/audio/tmap.c @@ -0,0 +1,339 @@ +/**************************************************************************** + * libs/zblue/audio/tmap.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright 2023 NXP + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "audio_internal.h" + +LOG_MODULE_REGISTER(bt_tmap, CONFIG_BT_TMAP_LOG_LEVEL); + +/* TMAP mandates the support for CAP Handover if it supports both UMS and BMS + */ + +BUILD_ASSERT(!(IS_ENABLED(CONFIG_BT_TMAP_UMS_SUPPORTED) && + IS_ENABLED(CONFIG_BT_TMAP_BMS_SUPPORTED)) || + IS_ENABLED(CONFIG_BT_CAP_HANDOVER), + "If both TMAP UMS is supported and TMAP BMS is supported, " + "CONFIG_BT_CAP_HANDOVER " + "shall be enabled"); + +/* Hex value if all TMAP role bits are set */ +#define TMAP_ALL_ROLES \ + (BT_TMAP_ROLE_CG | BT_TMAP_ROLE_CT | BT_TMAP_ROLE_UMS | BT_TMAP_ROLE_UMR | BT_TMAP_ROLE_BMS | \ + BT_TMAP_ROLE_BMR) + +static uint16_t tmap_role; +static const struct bt_tmap_cb *cb; +static bool tmas_found; + +static struct bt_uuid_16 uuid[CONFIG_BT_MAX_CONN] = +{ + BT_UUID_INIT_16(0) +}; +static struct bt_gatt_discover_params discover_params[CONFIG_BT_MAX_CONN]; +static struct bt_gatt_read_params read_params[CONFIG_BT_MAX_CONN]; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +uint8_t tmap_char_read(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + uint16_t peer_role; + + ARG_UNUSED(params); + + /* Check read request result */ + + if (err != BT_ATT_ERR_SUCCESS) + { + if (cb->discovery_complete) + { + cb->discovery_complete(0, conn, err); + } + + return BT_GATT_ITER_STOP; + } + + /* Check data length */ + + if (length != sizeof(peer_role)) + { + if (cb->discovery_complete) + { + cb->discovery_complete(0, conn, BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + return BT_GATT_ITER_STOP; + } + + /* Extract the TMAP role of the peer and inform application of the value + * found + */ + + peer_role = sys_get_le16(data); + + if ((peer_role > 0U) && (peer_role <= TMAP_ALL_ROLES)) + { + if (cb->discovery_complete) + { + cb->discovery_complete((enum bt_tmap_role)peer_role, conn, 0); + } + } + else + { + if (cb->discovery_complete) + { + cb->discovery_complete(0, conn, BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + } + + return BT_GATT_ITER_STOP; +} + +static uint8_t discover_func(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + int err; + uint8_t conn_id = bt_conn_index(conn); + + if (!attr) + { + (void)memset(params, 0, sizeof(*params)); + if (!tmas_found) + { + /* TMAS not found on peer */ + + if (cb->discovery_complete) + { + cb->discovery_complete(0, + conn, + BT_ATT_ERR_ATTRIBUTE_NOT_FOUND); + } + } + + tmas_found = false; + + return BT_GATT_ITER_STOP; + } + + if (!bt_uuid_cmp(discover_params[conn_id].uuid, BT_UUID_TMAS)) + { + LOG_DBG("Discovered TMAS\n"); + tmas_found = true; + memcpy(&uuid[conn_id], BT_UUID_GATT_TMAPR, sizeof(uuid[conn_id])); + discover_params[conn_id].uuid = &uuid[conn_id].uuid; + discover_params[conn_id].start_handle = attr->handle + 1; + discover_params[conn_id].type = BT_GATT_DISCOVER_CHARACTERISTIC; + + /* Discovered TMAS - Search for TMAP Role characteristic */ + + err = bt_gatt_discover(conn, &discover_params[conn_id]); + if (err != 0) + { + LOG_DBG("Discover failed (err %d)\n", err); + } + } + else if (!bt_uuid_cmp(discover_params[conn_id].uuid, BT_UUID_GATT_TMAPR)) + { + /* Use 0 for now, will expand later */ + + read_params[conn_id].func = tmap_char_read; + read_params[conn_id].handle_count = 1u; + read_params[conn_id].single.handle = bt_gatt_attr_value_handle(attr); + read_params[conn_id].single.offset = 0; + + /* Discovered TMAP Role characteristic - read value */ + + err = bt_gatt_read(conn, &read_params[0]); + if (err != 0) + { + LOG_DBG("Could not read peer TMAP Role"); + } + } + else + { + return BT_GATT_ITER_CONTINUE; + } + + return BT_GATT_ITER_STOP; +} + +static ssize_t read_role(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, + uint16_t len, + uint16_t offset) +{ + uint16_t role; + + role = sys_cpu_to_le16(tmap_role); + LOG_DBG("TMAP: role 0x%04X", role); + + return bt_gatt_attr_read(conn, + attr, + buf, + len, + offset, + &role, + sizeof(role)); +} + +/* Telephony and Media Audio Service attributes */ +#define BT_TMAS_SERVICE_DEFINITION \ + BT_GATT_PRIMARY_SERVICE(BT_UUID_TMAS), \ + BT_GATT_CHARACTERISTIC(BT_UUID_GATT_TMAPR, BT_GATT_CHRC_READ, BT_GATT_PERM_READ, \ + read_role, NULL, NULL) + +static struct bt_gatt_attr svc_attrs[] = +{ + BT_TMAS_SERVICE_DEFINITION +}; + +static struct bt_gatt_service tmas; + +static bool valid_tmap_role(enum bt_tmap_role role) +{ + if (role == 0U || (role & TMAP_ALL_ROLES) != role) + { + LOG_DBG("Invalid role %d", role); + } + + if ((role & BT_TMAP_ROLE_CG) != 0 && + !IS_ENABLED(CONFIG_BT_TMAP_CG_SUPPORTED)) + { + LOG_DBG("Device does not support the CG role"); + + return false; + } + + if ((role & BT_TMAP_ROLE_CT) != 0 && + !IS_ENABLED(CONFIG_BT_TMAP_CT_SUPPORTED)) + { + LOG_DBG("Device does not support the CT role"); + + return false; + } + + if ((role & BT_TMAP_ROLE_UMS) != 0 && + !IS_ENABLED(CONFIG_BT_TMAP_UMS_SUPPORTED)) + { + LOG_DBG("Device does not support the UMS role"); + + return false; + } + + if ((role & BT_TMAP_ROLE_UMR) != 0 && + !IS_ENABLED(CONFIG_BT_TMAP_UMR_SUPPORTED)) + { + LOG_DBG("Device does not support the UMR role"); + + return false; + } + + if ((role & BT_TMAP_ROLE_BMS) != 0 && + !IS_ENABLED(CONFIG_BT_TMAP_BMS_SUPPORTED)) + { + LOG_DBG("Device does not support the BMS role"); + + return false; + } + + if ((role & BT_TMAP_ROLE_BMR) != 0 && + !IS_ENABLED(CONFIG_BT_TMAP_BMR_SUPPORTED)) + { + LOG_DBG("Device does not support the BMR role"); + + return false; + } + + return true; +} + +int bt_tmap_register(enum bt_tmap_role role) +{ + int err; + + if (!valid_tmap_role(role)) + { + LOG_DBG("Invalid role: %d", role); + + return -EINVAL; + } + + tmas = (struct bt_gatt_service)BT_GATT_SERVICE(svc_attrs); + + err = bt_gatt_service_register(&tmas); + if (err != 0) + { + LOG_DBG("Could not register the TMAS service"); + return -ENOEXEC; + } + + tmap_role = role; + tmas_found = false; + + return 0; +} + +int bt_tmap_discover(struct bt_conn *conn, const struct bt_tmap_cb *tmap_cb) +{ + int err = 0; + uint8_t conn_id = bt_conn_index(conn); + + cb = tmap_cb; + + memcpy(&uuid[conn_id], BT_UUID_TMAS, sizeof(uuid[conn_id])); + discover_params[conn_id].uuid = &uuid[conn_id].uuid; + discover_params[conn_id].func = discover_func; + discover_params[conn_id].start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; + discover_params[conn_id].end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + discover_params[conn_id].type = BT_GATT_DISCOVER_PRIMARY; + + err = bt_gatt_discover(conn, &discover_params[conn_id]); + + return err; +} + +void bt_tmap_set_role(enum bt_tmap_role role) +{ + tmap_role = role; +} diff --git a/libs/zblue/audio/vcp_internal.h b/libs/zblue/audio/vcp_internal.h new file mode 100644 index 0000000000000..a85475c53b222 --- /dev/null +++ b/libs/zblue/audio/vcp_internal.h @@ -0,0 +1,104 @@ +/**************************************************************************** + * libs/zblue/audio/vcp_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file + * @brief Internal Header for Bluetooth Volume Control Service (VCS). + * + * Copyright (c) 2020 Bose Corporation Copyright (c) 2020-2022 Nordic + * Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_VCP_INTERNAL_ +#define ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_VCP_INTERNAL_ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include + +/* VCS opcodes */ +#define BT_VCP_OPCODE_REL_VOL_DOWN 0x00U +#define BT_VCP_OPCODE_REL_VOL_UP 0x01U +#define BT_VCP_OPCODE_UNMUTE_REL_VOL_DOWN 0x02U +#define BT_VCP_OPCODE_UNMUTE_REL_VOL_UP 0x03U +#define BT_VCP_OPCODE_SET_ABS_VOL 0x04U +#define BT_VCP_OPCODE_UNMUTE 0x05U +#define BT_VCP_OPCODE_MUTE 0x06U + +struct vcs_state +{ + uint8_t volume; + uint8_t mute; + uint8_t change_counter; +} __packed; + +struct vcs_control +{ + uint8_t opcode; + uint8_t counter; +} __packed; + +struct vcs_control_vol +{ + struct vcs_control cp; + uint8_t volume; +} __packed; + +enum bt_vcp_vol_ctlr_flag +{ + BT_VCP_VOL_CTLR_FLAG_BUSY, + BT_VCP_VOL_CTLR_FLAG_CP_RETRIED, + + BT_VCP_VOL_CTLR_FLAG_NUM_FLAGS, /* keep as last */ +}; + +struct bt_vcp_vol_ctlr +{ + struct vcs_state state; + uint8_t vol_flags; + + uint16_t start_handle; + uint16_t end_handle; + uint16_t state_handle; + uint16_t control_handle; + uint16_t vol_flag_handle; + struct bt_gatt_subscribe_params state_sub_params; + struct bt_gatt_discover_params state_sub_disc_params; + struct bt_gatt_subscribe_params vol_flag_sub_params; + struct bt_gatt_discover_params vol_flag_sub_disc_params; + + struct vcs_control_vol cp_val; + struct bt_gatt_write_params write_params; + struct bt_gatt_read_params read_params; + struct bt_gatt_discover_params discover_params; + struct bt_uuid_16 uuid; + struct bt_conn *conn; + +#if defined(CONFIG_BT_VCP_VOL_CTLR_VOCS) + uint8_t vocs_inst_cnt; + struct bt_vocs *vocs[CONFIG_BT_VCP_VOL_CTLR_MAX_VOCS_INST]; +#endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS */ +#if defined(CONFIG_BT_VCP_VOL_CTLR_AICS) + uint8_t aics_inst_cnt; + struct bt_aics *aics[CONFIG_BT_VCP_VOL_CTLR_MAX_AICS_INST]; +#endif /* CONFIG_BT_VCP_VOL_CTLR_AICS */ + + atomic_define(flags, BT_VCP_VOL_CTLR_FLAG_NUM_FLAGS); +}; + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_VCP_INTERNAL_*/ diff --git a/libs/zblue/audio/vcp_vol_ctlr.c b/libs/zblue/audio/vcp_vol_ctlr.c new file mode 100644 index 0000000000000..20dad7830d48a --- /dev/null +++ b/libs/zblue/audio/vcp_vol_ctlr.c @@ -0,0 +1,1543 @@ +/**************************************************************************** + * libs/zblue/audio/vcp_vol_ctlr.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Bluetooth vol_ctlr Client - Volume Offset Control Client */ + +/* Copyright (c) 2020 Bose Corporation + * Copyright (c) 2020-2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "aics_internal.h" +#include "common/bt_str.h" +#include "vocs_internal.h" +#include "vcp_internal.h" + +LOG_MODULE_REGISTER(bt_vcp_vol_ctlr, CONFIG_BT_VCP_VOL_CTLR_LOG_LEVEL); + +/* Callback functions */ + +static struct list_node vcp_vol_ctlr_cbs = + LIST_INITIAL_VALUE(vcp_vol_ctlr_cbs); + +static struct bt_vcp_vol_ctlr vol_ctlr_insts[CONFIG_BT_MAX_CONN]; +static int write_common_vcs_cp(struct bt_vcp_vol_ctlr *vol_ctlr); +static int write_set_vol_cp(struct bt_vcp_vol_ctlr *vol_ctlr); + +#if defined(CONFIG_BT_VCP_VOL_CTLR_VOCS) + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void vcp_vol_ctlr_free_vocs_client(void) +{ + ARRAY_FOR_EACH_PTR(vol_ctlr_insts, vol_ctlr) + { + ARRAY_FOR_EACH_PTR(vol_ctlr->vocs, vocs) + { + if (*vocs != NULL) + { + bt_vocs_client_free_instance(*vocs); + *vocs = NULL; + } + } + } +} +#endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS */ + +#if defined(CONFIG_BT_VCP_VOL_CTLR_AICS) +static void vcp_vol_ctlr_free_aics_client(void) +{ + ARRAY_FOR_EACH_PTR(vol_ctlr_insts, vol_ctlr) + { + ARRAY_FOR_EACH_PTR(vol_ctlr->aics, aics) + { + if (*aics != NULL) + { + bt_aics_client_free_instance(*aics); + *aics = NULL; + } + } + } +} +#endif /* CONFIG_BT_VCP_VOL_CTLR_AICS */ + +static struct bt_vcp_vol_ctlr *vol_ctlr_get_by_conn( + const struct bt_conn *conn) +{ + return &vol_ctlr_insts[bt_conn_index(conn)]; +} + +static void vcp_vol_ctlr_state_changed(struct bt_vcp_vol_ctlr *vol_ctlr, + int err) +{ + struct bt_vcp_vol_ctlr_cb *listener; + struct bt_vcp_vol_ctlr_cb *next; + + list_for_every_container_safe(&vcp_vol_ctlr_cbs, listener, next, _node) + { + if (listener->state) + { + listener->state(vol_ctlr, err, err == 0 ? vol_ctlr->state.volume : 0, + err == 0 ? vol_ctlr->state.mute : 0); + } + } +} + +static void vcp_vol_ctlr_vol_flags_changed(struct bt_vcp_vol_ctlr *vol_ctlr, + int err) +{ + struct bt_vcp_vol_ctlr_cb *listener; + struct bt_vcp_vol_ctlr_cb *next; + + list_for_every_container_safe(&vcp_vol_ctlr_cbs, listener, next, _node) + { + if (listener->flags) + { + listener->flags(vol_ctlr, err, err == 0 ? vol_ctlr->vol_flags : 0); + } + } +} + +static void vcp_vol_ctlr_discover_complete(struct bt_vcp_vol_ctlr *vol_ctlr, + int err) +{ + struct bt_vcp_vol_ctlr_cb *listener; + struct bt_vcp_vol_ctlr_cb *next; + + atomic_clear_bit(vol_ctlr->flags, BT_VCP_VOL_CTLR_FLAG_BUSY); + + list_for_every_container_safe(&vcp_vol_ctlr_cbs, listener, next, _node) + { + if (listener->discover) + { + uint8_t vocs_cnt = 0U; + uint8_t aics_cnt = 0U; + +#if defined(CONFIG_BT_VCP_VOL_CTLR_VOCS) + if (err == 0) + { + vocs_cnt = vol_ctlr->vocs_inst_cnt; + } +#endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS */ +#if defined(CONFIG_BT_VCP_VOL_CTLR_AICS) + + if (err == 0) + { + aics_cnt = vol_ctlr->aics_inst_cnt; + } +#endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS */ + + listener->discover(vol_ctlr, err, vocs_cnt, aics_cnt); + } + } +} + +static uint8_t vcp_vol_ctlr_notify_handler(struct bt_conn *conn, + struct bt_gatt_subscribe_params *params, + const void *data, + uint16_t length) +{ + uint16_t handle = params->value_handle; + struct bt_vcp_vol_ctlr *vol_ctlr; + + if (data == NULL || conn == NULL) + { + return BT_GATT_ITER_CONTINUE; + } + + vol_ctlr = vol_ctlr_get_by_conn(conn); + + if (handle == vol_ctlr->state_handle && length == sizeof(vol_ctlr->state)) + { + memcpy(&vol_ctlr->state, data, length); + LOG_DBG("Volume %u, mute %u, counter %u", vol_ctlr->state.volume, + vol_ctlr->state.mute, + vol_ctlr->state.change_counter); + vcp_vol_ctlr_state_changed(vol_ctlr, 0); + } + else if (handle == vol_ctlr->vol_flag_handle && + length == sizeof(vol_ctlr->vol_flags)) + { + memcpy(&vol_ctlr->vol_flags, data, length); + LOG_DBG("Volume Flags %u", vol_ctlr->vol_flags); + vcp_vol_ctlr_vol_flags_changed(vol_ctlr, 0); + } + + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t vcp_vol_ctlr_read_vol_state_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, + const void *data, uint16_t length) +{ + int cb_err = err; + struct bt_vcp_vol_ctlr *vol_ctlr = vol_ctlr_get_by_conn(conn); + + ARG_UNUSED(params); + + atomic_clear_bit(vol_ctlr->flags, BT_VCP_VOL_CTLR_FLAG_BUSY); + + if (cb_err) + { + LOG_DBG("err: %d", cb_err); + } + else if (data != NULL) + { + if (length == sizeof(vol_ctlr->state)) + { + memcpy(&vol_ctlr->state, data, length); + LOG_DBG("Volume %u, mute %u, counter %u", vol_ctlr->state.volume, + vol_ctlr->state.mute, + vol_ctlr->state.change_counter); + } + else + { + LOG_DBG("Invalid length %u (expected %zu)", length, + sizeof(vol_ctlr->state)); + cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + } + + vcp_vol_ctlr_state_changed(vol_ctlr, cb_err); + + return BT_GATT_ITER_STOP; +} + +static uint8_t vcp_vol_ctlr_read_vol_flag_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, + const void *data, uint16_t length) +{ + int cb_err = err; + struct bt_vcp_vol_ctlr *vol_ctlr = vol_ctlr_get_by_conn(conn); + + ARG_UNUSED(params); + + atomic_clear_bit(vol_ctlr->flags, BT_VCP_VOL_CTLR_FLAG_BUSY); + + if (cb_err) + { + LOG_DBG("err: %d", cb_err); + } + else if (data != NULL) + { + if (length == sizeof(vol_ctlr->vol_flags)) + { + memcpy(&vol_ctlr->vol_flags, data, length); + LOG_DBG("Volume Flags %u", vol_ctlr->vol_flags); + } + else + { + LOG_DBG("Invalid length %u (expected %zu)", length, + sizeof(vol_ctlr->vol_flags)); + cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + } + + vcp_vol_ctlr_vol_flags_changed(vol_ctlr, cb_err); + + return BT_GATT_ITER_STOP; +} + +static void vcs_cp_notify_app(struct bt_vcp_vol_ctlr *vol_ctlr, + uint8_t opcode, int err) +{ + struct bt_vcp_vol_ctlr_cb *listener; + struct bt_vcp_vol_ctlr_cb *next; + + LOG_DBG("%p opcode %u err %d", vol_ctlr, opcode, err); + + list_for_every_container_safe(&vcp_vol_ctlr_cbs, listener, next, _node) + { + switch (opcode) + { + case BT_VCP_OPCODE_REL_VOL_DOWN: + if (listener->vol_down) + { + listener->vol_down(vol_ctlr, err); + } + break; + case BT_VCP_OPCODE_REL_VOL_UP: + if (listener->vol_up) + { + listener->vol_up(vol_ctlr, err); + } + break; + case BT_VCP_OPCODE_UNMUTE_REL_VOL_DOWN: + if (listener->vol_down_unmute) + { + listener->vol_down_unmute(vol_ctlr, err); + } + break; + case BT_VCP_OPCODE_UNMUTE_REL_VOL_UP: + if (listener->vol_up_unmute) + { + listener->vol_up_unmute(vol_ctlr, err); + } + break; + case BT_VCP_OPCODE_SET_ABS_VOL: + if (listener->vol_set) + { + listener->vol_set(vol_ctlr, err); + } + break; + case BT_VCP_OPCODE_UNMUTE: + if (listener->unmute) + { + listener->unmute(vol_ctlr, err); + } + break; + case BT_VCP_OPCODE_MUTE: + if (listener->mute) + { + listener->mute(vol_ctlr, err); + } + break; + default: + LOG_DBG("Unknown opcode 0x%02x", opcode); + break; + } + } +} + +static uint8_t internal_read_vol_state_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + int cb_err = 0; + struct bt_vcp_vol_ctlr *vol_ctlr = vol_ctlr_get_by_conn(conn); + uint8_t opcode = vol_ctlr->cp_val.cp.opcode; + + memset(params, 0, sizeof(*params)); + + if (err > 0) + { + LOG_WRN("Volume state read failed: %d", err); + cb_err = BT_ATT_ERR_UNLIKELY; + } + else if (data != NULL) + { + if (length == sizeof(vol_ctlr->state)) + { + int write_err; + + memcpy(&vol_ctlr->state, data, length); + LOG_DBG("Volume %u, mute %u, counter %u", vol_ctlr->state.volume, + vol_ctlr->state.mute, + vol_ctlr->state.change_counter); + + /* clear busy flag to reuse function */ + + if (opcode == BT_VCP_OPCODE_SET_ABS_VOL) + { + write_err = write_set_vol_cp(vol_ctlr); + } + else + { + write_err = write_common_vcs_cp(vol_ctlr); + } + + if (write_err) + { + cb_err = BT_ATT_ERR_UNLIKELY; + } + } + else + { + LOG_DBG("Invalid length %u (expected %zu)", length, + sizeof(vol_ctlr->state)); + cb_err = BT_ATT_ERR_UNLIKELY; + } + } + + if (cb_err) + { + atomic_clear_bit(vol_ctlr->flags, BT_VCP_VOL_CTLR_FLAG_CP_RETRIED); + atomic_clear_bit(vol_ctlr->flags, BT_VCP_VOL_CTLR_FLAG_BUSY); + vcs_cp_notify_app(vol_ctlr, opcode, cb_err); + } + + return BT_GATT_ITER_STOP; +} + +static void vcp_vol_ctlr_write_vcs_cp_cb(struct bt_conn *conn, uint8_t err, + struct bt_gatt_write_params *params) +{ + struct bt_vcp_vol_ctlr *vol_ctlr = vol_ctlr_get_by_conn(conn); + uint8_t opcode = vol_ctlr->cp_val.cp.opcode; + int cb_err = err; + + LOG_DBG("err: 0x%02X", err); + memset(params, 0, sizeof(*params)); + + /* If the change counter is out of data when a write was attempted from + * the application, we automatically initiate a read to get the newest + * state and try again. Once the change counter has been read, we restart + * the applications write request. If it fails the second time, we return + * an error to the application. + */ + + if (cb_err == BT_VCP_ERR_INVALID_COUNTER && + atomic_test_bit(vol_ctlr->flags, BT_VCP_VOL_CTLR_FLAG_CP_RETRIED)) + { + cb_err = BT_ATT_ERR_UNLIKELY; + } + else if (err == BT_VCP_ERR_INVALID_COUNTER && vol_ctlr->state_handle) + { + vol_ctlr->read_params.func = internal_read_vol_state_cb; + vol_ctlr->read_params.handle_count = 1U; + vol_ctlr->read_params.single.handle = vol_ctlr->state_handle; + vol_ctlr->read_params.single.offset = 0U; + + cb_err = bt_gatt_read(conn, &vol_ctlr->read_params); + if (cb_err) + { + LOG_WRN("Could not read Volume state: %d", cb_err); + } + else + { + atomic_set_bit(vol_ctlr->flags, BT_VCP_VOL_CTLR_FLAG_CP_RETRIED); + + /* Wait for read callback */ + + return; + } + } + + atomic_clear_bit(vol_ctlr->flags, BT_VCP_VOL_CTLR_FLAG_BUSY); + atomic_clear_bit(vol_ctlr->flags, BT_VCP_VOL_CTLR_FLAG_CP_RETRIED); + + vcs_cp_notify_app(vol_ctlr, opcode, err); +} + +#if defined(CONFIG_BT_VCP_VOL_CTLR_VOCS) || defined(CONFIG_BT_VCP_VOL_CTLR_AICS) +static uint8_t vcs_discover_include_func( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_gatt_include *include; + uint8_t inst_idx; + int err; + struct bt_vcp_vol_ctlr *vol_ctlr = vol_ctlr_get_by_conn(conn); + + if (attr == NULL) + { + uint8_t vocs_cnt = 0U; + uint8_t aics_cnt = 0U; + +#if defined(CONFIG_BT_VCP_VOL_CTLR_VOCS) + vocs_cnt = vol_ctlr->vocs_inst_cnt; +#endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS */ + +#if defined(CONFIG_BT_VCP_VOL_CTLR_AICS) + aics_cnt = vol_ctlr->aics_inst_cnt; +#endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS */ + + LOG_DBG("Discover include complete for vol_ctlr: %u AICS and %u VOCS", + aics_cnt, vocs_cnt); + (void)memset(params, 0, sizeof(*params)); + + vcp_vol_ctlr_discover_complete(vol_ctlr, 0); + + return BT_GATT_ITER_STOP; + } + + LOG_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle); + + if (params->type == BT_GATT_DISCOVER_INCLUDE) + { + uint8_t conn_index = bt_conn_index(conn); + + include = (struct bt_gatt_include *)attr->user_data; + LOG_DBG("Include UUID %s", bt_uuid_str(include->uuid)); + +#if defined(CONFIG_BT_VCP_VOL_CTLR_AICS) + if (bt_uuid_cmp(include->uuid, BT_UUID_AICS) == 0 && + vol_ctlr->aics_inst_cnt < CONFIG_BT_VCP_VOL_CTLR_MAX_AICS_INST) + { + struct bt_aics_discover_param param = + { + .start_handle = include->start_handle, + .end_handle = include->end_handle, + }; + + /* Update discover params so we can continue where we + * left off after bt_aics_discover + */ + + vol_ctlr->discover_params.start_handle = attr->handle + 1; + + inst_idx = vol_ctlr->aics_inst_cnt++; + err = bt_aics_discover(conn, + vol_ctlr_insts[conn_index].aics[inst_idx], + ¶m); + if (err != 0) + { + LOG_DBG("AICS Discover failed (err %d)", err); + vcp_vol_ctlr_discover_complete(vol_ctlr, err); + } + + return BT_GATT_ITER_STOP; + } +#endif /* CONFIG_BT_VCP_VOL_CTLR_AICS */ +#if defined(CONFIG_BT_VCP_VOL_CTLR_VOCS) + + if (bt_uuid_cmp(include->uuid, BT_UUID_VOCS) == 0 && + vol_ctlr->vocs_inst_cnt < CONFIG_BT_VCP_VOL_CTLR_MAX_VOCS_INST) + { + struct bt_vocs_discover_param param = + { + .start_handle = include->start_handle, + .end_handle = include->end_handle, + }; + + /* Update discover params so we can continue where we + * left off after bt_vocs_discover + */ + + vol_ctlr->discover_params.start_handle = attr->handle + 1; + + inst_idx = vol_ctlr->vocs_inst_cnt++; + err = bt_vocs_discover(conn, + vol_ctlr_insts[conn_index].vocs[inst_idx], + ¶m); + if (err != 0) + { + LOG_DBG("VOCS Discover failed (err %d)", err); + vcp_vol_ctlr_discover_complete(vol_ctlr, err); + } + + return BT_GATT_ITER_STOP; + } +#endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS */ + } + + return BT_GATT_ITER_CONTINUE; +} +#endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS || CONFIG_BT_VCP_VOL_CTLR_AICS */ + +/** + * @brief This will discover all characteristics on the server, retrieving + * the handles of the writeable characteristics and subscribing to all notify + * and indicate characteristics. + */ + +static uint8_t vcs_discover_func( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + int err = 0; + struct bt_gatt_chrc *chrc; + struct bt_gatt_subscribe_params *sub_params = NULL; + struct bt_vcp_vol_ctlr *vol_ctlr = vol_ctlr_get_by_conn(conn); + + if (attr == NULL) + { + LOG_DBG("Setup complete for vol_ctlr"); + (void)memset(params, 0, sizeof(*params)); +#if defined(CONFIG_BT_VCP_VOL_CTLR_VOCS) || defined(CONFIG_BT_VCP_VOL_CTLR_AICS) + /* Discover included services */ + + vol_ctlr->discover_params.start_handle = vol_ctlr->start_handle; + vol_ctlr->discover_params.end_handle = vol_ctlr->end_handle; + vol_ctlr->discover_params.type = BT_GATT_DISCOVER_INCLUDE; + vol_ctlr->discover_params.func = vcs_discover_include_func; + + err = bt_gatt_discover(conn, &vol_ctlr->discover_params); + if (err != 0) + { + LOG_DBG("Discover failed (err %d)", err); + vcp_vol_ctlr_discover_complete(vol_ctlr, err); + } +#else + vcp_vol_ctlr_discover_complete(vol_ctlr, err); +#endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS || CONFIG_BT_VCP_VOL_CTLR_AICS */ + + return BT_GATT_ITER_STOP; + } + + LOG_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle); + + if (params->type == BT_GATT_DISCOVER_CHARACTERISTIC) + { + chrc = (struct bt_gatt_chrc *)attr->user_data; + + if (bt_uuid_cmp(chrc->uuid, BT_UUID_VCS_STATE) == 0) + { + LOG_DBG("Volume state"); + vol_ctlr->state_handle = chrc->value_handle; + sub_params = &vol_ctlr->state_sub_params; + sub_params->disc_params = &vol_ctlr->state_sub_disc_params; + } + else if (bt_uuid_cmp(chrc->uuid, BT_UUID_VCS_CONTROL) == 0) + { + LOG_DBG("Control Point"); + vol_ctlr->control_handle = chrc->value_handle; + } + else if (bt_uuid_cmp(chrc->uuid, BT_UUID_VCS_FLAGS) == 0) + { + LOG_DBG("Volume Flags"); + vol_ctlr->vol_flag_handle = chrc->value_handle; + sub_params = &vol_ctlr->vol_flag_sub_params; + sub_params->disc_params = &vol_ctlr->vol_flag_sub_disc_params; + } + + if (sub_params != NULL) + { + sub_params->value = BT_GATT_CCC_NOTIFY; + sub_params->value_handle = chrc->value_handle; + sub_params->ccc_handle = BT_GATT_AUTO_DISCOVER_CCC_HANDLE; + sub_params->end_handle = vol_ctlr->end_handle; + sub_params->notify = vcp_vol_ctlr_notify_handler; + atomic_set_bit(sub_params->flags, BT_GATT_SUBSCRIBE_FLAG_VOLATILE); + + err = bt_gatt_subscribe(conn, sub_params); + if (err == 0 || err == -EALREADY) + { + LOG_DBG("Subscribed to handle 0x%04X", attr->handle); + } + else + { + LOG_DBG("Could not subscribe to handle 0x%04X (%d)", + attr->handle, err); + vcp_vol_ctlr_discover_complete(vol_ctlr, err); + + return BT_GATT_ITER_STOP; + } + } + } + + return BT_GATT_ITER_CONTINUE; +} + +/** + * @brief This will discover all characteristics on the server, retrieving + * the handles of the writeable characteristics and subscribing to all notify + * and indicate characteristics. + */ + +static uint8_t primary_discover_func( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_gatt_service_val *prim_service; + struct bt_vcp_vol_ctlr *vol_ctlr = vol_ctlr_get_by_conn(conn); + + if (attr == NULL) + { + LOG_DBG("Could not find a vol_ctlr instance on the server"); + vcp_vol_ctlr_discover_complete(vol_ctlr, -ENODATA); + + return BT_GATT_ITER_STOP; + } + + LOG_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle); + + if (params->type == BT_GATT_DISCOVER_PRIMARY) + { + int err; + + LOG_DBG("Primary discover complete"); + prim_service = (struct bt_gatt_service_val *)attr->user_data; + + vol_ctlr->start_handle = attr->handle + 1; + vol_ctlr->end_handle = prim_service->end_handle; + + /* Discover characteristics */ + + vol_ctlr->discover_params.uuid = NULL; + vol_ctlr->discover_params.start_handle = vol_ctlr->start_handle; + vol_ctlr->discover_params.end_handle = vol_ctlr->end_handle; + vol_ctlr->discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; + vol_ctlr->discover_params.func = vcs_discover_func; + + err = bt_gatt_discover(conn, &vol_ctlr->discover_params); + if (err != 0) + { + LOG_DBG("Discover failed (err %d)", err); + vcp_vol_ctlr_discover_complete(vol_ctlr, err); + } + + return BT_GATT_ITER_STOP; + } + + return BT_GATT_ITER_CONTINUE; +} + +static int write_common_vcs_cp(struct bt_vcp_vol_ctlr *vol_ctlr) +{ + int err; + + vol_ctlr->write_params.offset = 0; + vol_ctlr->write_params.data = &vol_ctlr->cp_val.cp; + vol_ctlr->write_params.length = sizeof(vol_ctlr->cp_val.cp); + vol_ctlr->write_params.handle = vol_ctlr->control_handle; + vol_ctlr->write_params.func = vcp_vol_ctlr_write_vcs_cp_cb; + + err = bt_gatt_write(vol_ctlr->conn, &vol_ctlr->write_params); + if (err != 0) + { + atomic_clear_bit(vol_ctlr->flags, BT_VCP_VOL_CTLR_FLAG_BUSY); + } + + return err; +} + +static int vcp_vol_ctlr_common_vcs_cp(struct bt_vcp_vol_ctlr *vol_ctlr, + uint8_t opcode) +{ + if (vol_ctlr == NULL) + { + LOG_DBG("NULL ctlr"); + return -EINVAL; + } + + if (vol_ctlr->conn == NULL) + { + LOG_DBG("NULL ctlr conn"); + return -EINVAL; + } + + if (vol_ctlr->control_handle == 0) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + else if (atomic_test_and_set_bit(vol_ctlr->flags, + BT_VCP_VOL_CTLR_FLAG_BUSY)) + { + return -EBUSY; + } + + vol_ctlr->cp_val.cp.opcode = opcode; + vol_ctlr->cp_val.cp.counter = vol_ctlr->state.change_counter; + + return write_common_vcs_cp(vol_ctlr); +} + +#if defined(CONFIG_BT_VCP_VOL_CTLR_AICS) +static struct bt_vcp_vol_ctlr *lookup_vcp_by_aics(const struct bt_aics *aics) +{ + DEBUGASSERT(aics != NULL); + + for (size_t i = 0U; i < ARRAY_SIZE(vol_ctlr_insts); i++) + { + for (size_t j = 0U; j < ARRAY_SIZE(vol_ctlr_insts[i].aics); j++) + { + if (vol_ctlr_insts[i].aics[j] == aics) + { + return &vol_ctlr_insts[i]; + } + } + } + + return NULL; +} + +static void vcp_vol_ctlr_aics_state_cb( + struct bt_aics *inst, int err, int8_t gain, uint8_t mute, uint8_t mode) +{ + struct bt_vcp_vol_ctlr_cb *listener; + struct bt_vcp_vol_ctlr_cb *next; + + list_for_every_container_safe(&vcp_vol_ctlr_cbs, listener, next, _node) + { + if (listener->aics_cb.state) + { + listener->aics_cb.state(inst, err, gain, mute, mode); + } + } +} + +static void vcp_vol_ctlr_aics_gain_setting_cb( + struct bt_aics *inst, int err, uint8_t units, int8_t minimum, + int8_t maximum) +{ + struct bt_vcp_vol_ctlr_cb *listener; + struct bt_vcp_vol_ctlr_cb *next; + + list_for_every_container_safe(&vcp_vol_ctlr_cbs, listener, next, _node) + { + if (listener->aics_cb.gain_setting) + { + listener->aics_cb.gain_setting(inst, err, units, minimum, maximum); + } + } +} + +static void vcp_vol_ctlr_aics_type_cb(struct bt_aics *inst, + int err, uint8_t type) +{ + struct bt_vcp_vol_ctlr_cb *listener; + struct bt_vcp_vol_ctlr_cb *next; + + list_for_every_container_safe(&vcp_vol_ctlr_cbs, listener, next, _node) + { + if (listener->aics_cb.type) + { + listener->aics_cb.type(inst, err, type); + } + } +} + +static void vcp_vol_ctlr_aics_status_cb(struct bt_aics *inst, + int err, bool active) +{ + struct bt_vcp_vol_ctlr_cb *listener; + struct bt_vcp_vol_ctlr_cb *next; + + list_for_every_container_safe(&vcp_vol_ctlr_cbs, listener, next, _node) + { + if (listener->aics_cb.status) + { + listener->aics_cb.status(inst, err, active); + } + } +} + +static void vcp_vol_ctlr_aics_description_cb(struct bt_aics *inst, + int err, char *description) +{ + struct bt_vcp_vol_ctlr_cb *listener; + struct bt_vcp_vol_ctlr_cb *next; + + list_for_every_container_safe(&vcp_vol_ctlr_cbs, listener, next, _node) + { + if (listener->aics_cb.description) + { + listener->aics_cb.description(inst, err, description); + } + } +} + +static void vcp_vol_ctlr_aics_discover_cb(struct bt_aics *inst, int err) +{ + struct bt_vcp_vol_ctlr *vol_ctlr = lookup_vcp_by_aics(inst); + struct bt_vcp_vol_ctlr_cb *listener; + struct bt_vcp_vol_ctlr_cb *next; + + if (vol_ctlr == NULL) + { + LOG_ERR("Could not lookup vol_ctlr from aics"); + + return; + } + + if (err == 0) + { + /* Continue discovery of included services */ + + err = bt_gatt_discover(vol_ctlr->conn, &vol_ctlr->discover_params); + } + + if (err != 0) + { + LOG_DBG("Discover failed (err %d)", err); + vcp_vol_ctlr_discover_complete(vol_ctlr, err); + } + + list_for_every_container_safe(&vcp_vol_ctlr_cbs, listener, next, _node) + { + if (listener->aics_cb.discover) + { + listener->aics_cb.discover(inst, err); + } + } +} + +static void vcp_vol_ctlr_aics_set_gain_cb(struct bt_aics *inst, int err) +{ + struct bt_vcp_vol_ctlr_cb *listener; + struct bt_vcp_vol_ctlr_cb *next; + + list_for_every_container_safe(&vcp_vol_ctlr_cbs, listener, next, _node) + { + if (listener->aics_cb.set_gain) + { + listener->aics_cb.set_gain(inst, err); + } + } +} + +static void vcp_vol_ctlr_aics_unmute_cb(struct bt_aics *inst, int err) +{ + struct bt_vcp_vol_ctlr_cb *listener; + struct bt_vcp_vol_ctlr_cb *next; + + list_for_every_container_safe(&vcp_vol_ctlr_cbs, listener, next, _node) + { + if (listener->aics_cb.unmute) + { + listener->aics_cb.unmute(inst, err); + } + } +} + +static void vcp_vol_ctlr_aics_mute_cb(struct bt_aics *inst, int err) +{ + struct bt_vcp_vol_ctlr_cb *listener; + struct bt_vcp_vol_ctlr_cb *next; + + list_for_every_container_safe(&vcp_vol_ctlr_cbs, listener, next, _node) + { + if (listener->aics_cb.mute) + { + listener->aics_cb.mute(inst, err); + } + } +} + +static void vcp_vol_ctlr_aics_set_manual_mode_cb(struct bt_aics *inst, + int err) +{ + struct bt_vcp_vol_ctlr_cb *listener; + struct bt_vcp_vol_ctlr_cb *next; + + list_for_every_container_safe(&vcp_vol_ctlr_cbs, listener, next, _node) + { + if (listener->aics_cb.set_manual_mode) + { + listener->aics_cb.set_manual_mode(inst, err); + } + } +} + +static void vcp_vol_ctlr_aics_set_auto_mode_cb(struct bt_aics *inst, int err) +{ + struct bt_vcp_vol_ctlr_cb *listener; + struct bt_vcp_vol_ctlr_cb *next; + + list_for_every_container_safe(&vcp_vol_ctlr_cbs, listener, next, _node) + { + if (listener->aics_cb.set_auto_mode) + { + listener->aics_cb.set_auto_mode(inst, err); + } + } +} +#endif /* CONFIG_BT_VCP_VOL_CTLR_AICS */ + +#if defined(CONFIG_BT_VCP_VOL_CTLR_VOCS) +static struct bt_vcp_vol_ctlr *lookup_vcp_by_vocs(const struct bt_vocs *vocs) +{ + DEBUGASSERT(vocs != NULL); + + for (int i = 0; i < ARRAY_SIZE(vol_ctlr_insts); i++) + { + for (int j = 0; j < ARRAY_SIZE(vol_ctlr_insts[i].vocs); j++) + { + if (vol_ctlr_insts[i].vocs[j] == vocs) + { + return &vol_ctlr_insts[i]; + } + } + } + + return NULL; +} + +static void vcp_vol_ctlr_vocs_state_cb(struct bt_vocs *inst, + int err, int16_t offset) +{ + struct bt_vcp_vol_ctlr_cb *listener; + struct bt_vcp_vol_ctlr_cb *next; + + list_for_every_container_safe(&vcp_vol_ctlr_cbs, listener, next, _node) + { + if (listener->vocs_cb.state) + { + listener->vocs_cb.state(inst, err, offset); + } + } +} + +static void vcp_vol_ctlr_vocs_location_cb(struct bt_vocs *inst, + int err, uint32_t location) +{ + struct bt_vcp_vol_ctlr_cb *listener; + struct bt_vcp_vol_ctlr_cb *next; + + list_for_every_container_safe(&vcp_vol_ctlr_cbs, listener, next, _node) + { + if (listener->vocs_cb.location) + { + listener->vocs_cb.location(inst, err, location); + } + } +} + +static void vcp_vol_ctlr_vocs_description_cb(struct bt_vocs *inst, + int err, char *description) +{ + struct bt_vcp_vol_ctlr_cb *listener; + struct bt_vcp_vol_ctlr_cb *next; + + list_for_every_container_safe(&vcp_vol_ctlr_cbs, listener, next, _node) + { + if (listener->vocs_cb.description) + { + listener->vocs_cb.description(inst, err, description); + } + } +} + +static void vcp_vol_ctlr_vocs_discover_cb(struct bt_vocs *inst, int err) +{ + struct bt_vcp_vol_ctlr *vol_ctlr = lookup_vcp_by_vocs(inst); + struct bt_vcp_vol_ctlr_cb *listener; + struct bt_vcp_vol_ctlr_cb *next; + + if (vol_ctlr == NULL) + { + LOG_ERR("Could not lookup vol_ctlr from vocs"); + return; + } + + if (err == 0) + { + /* Continue discovery of included services */ + + err = bt_gatt_discover(vol_ctlr->conn, &vol_ctlr->discover_params); + } + + if (err != 0) + { + LOG_DBG("Discover failed (err %d)", err); + vcp_vol_ctlr_discover_complete(vol_ctlr, err); + } + + list_for_every_container_safe(&vcp_vol_ctlr_cbs, listener, next, _node) + { + if (listener->vocs_cb.discover) + { + listener->vocs_cb.discover(inst, err); + } + } +} + +static void vcp_vol_ctlr_vocs_set_offset_cb(struct bt_vocs *inst, int err) +{ + struct bt_vcp_vol_ctlr_cb *listener; + struct bt_vcp_vol_ctlr_cb *next; + + list_for_every_container_safe(&vcp_vol_ctlr_cbs, listener, next, _node) + { + if (listener->vocs_cb.set_offset) + { + listener->vocs_cb.set_offset(inst, err); + } + } +} +#endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS */ + +static void vcp_vol_ctlr_reset(struct bt_vcp_vol_ctlr *vol_ctlr) +{ + memset(&vol_ctlr->state, 0, sizeof(vol_ctlr->state)); + vol_ctlr->vol_flags = 0U; + vol_ctlr->start_handle = 0U; + vol_ctlr->end_handle = 0U; + vol_ctlr->state_handle = 0U; + vol_ctlr->control_handle = 0U; + vol_ctlr->vol_flag_handle = 0U; +#if defined(CONFIG_BT_VCP_VOL_CTLR_VOCS) + vol_ctlr->vocs_inst_cnt = 0U; +#endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS */ +#if defined(CONFIG_BT_VCP_VOL_CTLR_AICS) + vol_ctlr->aics_inst_cnt = 0U; +#endif /* CONFIG_BT_VCP_VOL_CTLR_AICS */ + + memset(&vol_ctlr->discover_params, 0, sizeof(vol_ctlr->discover_params)); + + bt_conn_drop(&vol_ctlr->conn); +} + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + struct bt_vcp_vol_ctlr *vol_ctlr; + + ARG_UNUSED(reason); + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + vol_ctlr = vol_ctlr_get_by_conn(conn); + if (vol_ctlr->conn == conn) + { + vcp_vol_ctlr_reset(vol_ctlr); + } +} + +BT_CONN_CB_DEFINE(conn_callbacks) = +{ + .disconnected = disconnected, +}; + +static int bt_vcp_vol_ctlr_init(void) +{ +#if defined(CONFIG_BT_VCP_VOL_CTLR_VOCS) + for (size_t i = 0U; i < ARRAY_SIZE(vol_ctlr_insts); i++) + { + for (size_t j = 0U; j < ARRAY_SIZE(vol_ctlr_insts[i].vocs); j++) + { + static struct bt_vocs_cb vocs_cb = + { + .state = vcp_vol_ctlr_vocs_state_cb, + .location = vcp_vol_ctlr_vocs_location_cb, + .description = vcp_vol_ctlr_vocs_description_cb, + .discover = vcp_vol_ctlr_vocs_discover_cb, + .set_offset = vcp_vol_ctlr_vocs_set_offset_cb, + }; + + vol_ctlr_insts[i].vocs[j] = bt_vocs_client_free_instance_get(); + + if (vol_ctlr_insts[i].vocs[j] == NULL) + { + vcp_vol_ctlr_free_vocs_client(); + return -ENOMEM; + } + + bt_vocs_client_cb_register(vol_ctlr_insts[i].vocs[j], &vocs_cb); + } + } +#endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS */ + +#if defined(CONFIG_BT_VCP_VOL_CTLR_AICS) + for (size_t i = 0U; i < ARRAY_SIZE(vol_ctlr_insts); i++) + { + for (size_t j = 0U; j < ARRAY_SIZE(vol_ctlr_insts[i].aics); j++) + { + static struct bt_aics_cb aics_cb = + { + .state = vcp_vol_ctlr_aics_state_cb, + .gain_setting = vcp_vol_ctlr_aics_gain_setting_cb, + .type = vcp_vol_ctlr_aics_type_cb, + .status = vcp_vol_ctlr_aics_status_cb, + .description = vcp_vol_ctlr_aics_description_cb, + .discover = vcp_vol_ctlr_aics_discover_cb, + .set_gain = vcp_vol_ctlr_aics_set_gain_cb, + .unmute = vcp_vol_ctlr_aics_unmute_cb, + .mute = vcp_vol_ctlr_aics_mute_cb, + .set_manual_mode = vcp_vol_ctlr_aics_set_manual_mode_cb, + .set_auto_mode = vcp_vol_ctlr_aics_set_auto_mode_cb, + }; + + vol_ctlr_insts[i].aics[j] = bt_aics_client_free_instance_get(); + + if (vol_ctlr_insts[i].aics[j] == NULL) + { + vcp_vol_ctlr_free_aics_client(); +#if defined(CONFIG_BT_VCP_VOL_CTLR_VOCS) + vcp_vol_ctlr_free_vocs_client(); +#endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS */ + return -ENOMEM; + } + + bt_aics_client_cb_register(vol_ctlr_insts[i].aics[j], &aics_cb); + } + } +#endif /* CONFIG_BT_VCP_VOL_CTLR_AICS */ + + return 0; +} + +int bt_vcp_vol_ctlr_discover(struct bt_conn *conn, + struct bt_vcp_vol_ctlr **out_vol_ctlr) +{ + static bool initialized; + struct bt_vcp_vol_ctlr *vol_ctlr; + struct bt_conn *ref; + int err; + + /* This will initiate a discover procedure. The procedure will do the + * following sequence: 1) Primary discover for the vol_ctlr 2) + * Characteristic discover of the vol_ctlr 3) Discover services included in + * vol_ctlr (VOCS and AICS) 4) For each included service found; discovery + * of the characteristics 5) When everything above have been discovered, + * the callback is called + */ + + if (conn == NULL) + { + LOG_DBG("NULL conn"); + return -EINVAL; + } + + if (out_vol_ctlr == NULL) + { + LOG_DBG("NULL ctlr"); + return -EINVAL; + } + + vol_ctlr = vol_ctlr_get_by_conn(conn); + + if (atomic_test_and_set_bit(vol_ctlr->flags, BT_VCP_VOL_CTLR_FLAG_BUSY)) + { + LOG_DBG("Volume controller busy"); + return -EBUSY; + } + + if (!initialized) + { + err = bt_vcp_vol_ctlr_init(); + if (err != 0) + { + goto cleanup; + } + + initialized = true; + } + + ref = bt_conn_ref(conn); + if (ref == NULL) + { + err = -ENOTCONN; + goto cleanup; + } + + vcp_vol_ctlr_reset(vol_ctlr); + + memcpy(&vol_ctlr->uuid, BT_UUID_VCS, sizeof(vol_ctlr->uuid)); + + vol_ctlr->discover_params.func = primary_discover_func; + vol_ctlr->discover_params.uuid = &vol_ctlr->uuid.uuid; + vol_ctlr->discover_params.type = BT_GATT_DISCOVER_PRIMARY; + vol_ctlr->discover_params.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; + vol_ctlr->discover_params.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + + vol_ctlr->conn = ref; + + err = bt_gatt_discover(conn, &vol_ctlr->discover_params); + if (err != 0) + { + bt_conn_unref(ref); + vol_ctlr->conn = NULL; + goto cleanup; + } + + *out_vol_ctlr = vol_ctlr; + + return 0; + +cleanup: + atomic_clear_bit(vol_ctlr->flags, BT_VCP_VOL_CTLR_FLAG_BUSY); + return err; +} + +int bt_vcp_vol_ctlr_cb_register(struct bt_vcp_vol_ctlr_cb *cb) +{ + struct bt_vcp_vol_ctlr_cb *tmp; + + if (cb == NULL) + { + return -EINVAL; + } + + list_for_every_container(&vcp_vol_ctlr_cbs, tmp, _node) + { + if (tmp == cb) + { + LOG_DBG("Already registered"); + return -EALREADY; + } + } + + list_add_tail(&vcp_vol_ctlr_cbs, &cb->_node); + + return 0; +} + +int bt_vcp_vol_ctlr_cb_unregister(struct bt_vcp_vol_ctlr_cb *cb) +{ + if (cb == NULL) + { + return -EINVAL; + } + + if (!list_find_and_remove(&vcp_vol_ctlr_cbs, &cb->_node)) + { + return -EALREADY; + } + + return 0; +} + +#if defined(CONFIG_BT_VCP_VOL_CTLR_VOCS) || defined(CONFIG_BT_VCP_VOL_CTLR_AICS) +int bt_vcp_vol_ctlr_included_get(struct bt_vcp_vol_ctlr *vol_ctlr, + struct bt_vcp_included *included) +{ + if (!included || vol_ctlr == NULL) + { + return -EINVAL; + } + + memset(included, 0, sizeof(*included)); + +#if defined(CONFIG_BT_VCP_VOL_CTLR_VOCS) + included->vocs_cnt = vol_ctlr->vocs_inst_cnt; + included->vocs = vol_ctlr->vocs; +#endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS */ + +#if defined(CONFIG_BT_VCP_VOL_CTLR_AICS) + included->aics_cnt = vol_ctlr->aics_inst_cnt; + included->aics = vol_ctlr->aics; +#endif /* CONFIG_BT_VCP_VOL_CTLR_AICS */ + + return 0; +} +#endif /* CONFIG_BT_VCP_VOL_CTLR_VOCS || CONFIG_BT_VCP_VOL_CTLR_AICS*/ + +struct bt_vcp_vol_ctlr *bt_vcp_vol_ctlr_get_by_conn( + const struct bt_conn *conn) +{ + struct bt_vcp_vol_ctlr *vol_ctlr; + + if (conn == NULL) + { + LOG_DBG("NULL conn pointer"); + return NULL; + } + + vol_ctlr = vol_ctlr_get_by_conn(conn); + if (vol_ctlr->conn == NULL) + { + LOG_DBG("conn %p is not associated with volume controller. Do " + "discovery first", + (void *)conn); + return NULL; + } + + return vol_ctlr; +} + +int bt_vcp_vol_ctlr_conn_get(const struct bt_vcp_vol_ctlr *vol_ctlr, + struct bt_conn **conn) +{ + if (vol_ctlr == NULL) + { + LOG_DBG("NULL vol_ctlr pointer"); + return -EINVAL; + } + + if (conn == NULL) + { + LOG_DBG("NULL conn pointer"); + return -EINVAL; + } + + if (vol_ctlr->conn == NULL) + { + LOG_DBG("vol_ctlr pointer not associated with a connection. " + "Do discovery first"); + return -ENOTCONN; + } + + *conn = vol_ctlr->conn; + return 0; +} + +int bt_vcp_vol_ctlr_read_state(struct bt_vcp_vol_ctlr *vol_ctlr) +{ + int err; + + if (vol_ctlr == NULL) + { + LOG_DBG("NULL ctlr"); + return -EINVAL; + } + + if (vol_ctlr->conn == NULL) + { + LOG_DBG("NULL ctlr conn"); + return -EINVAL; + } + + if (vol_ctlr->state_handle == 0) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + else if (atomic_test_and_set_bit(vol_ctlr->flags, + BT_VCP_VOL_CTLR_FLAG_BUSY)) + { + LOG_DBG("Volume controller busy"); + return -EBUSY; + } + + vol_ctlr->read_params.func = vcp_vol_ctlr_read_vol_state_cb; + vol_ctlr->read_params.handle_count = 1U; + vol_ctlr->read_params.single.handle = vol_ctlr->state_handle; + vol_ctlr->read_params.single.offset = 0U; + + err = bt_gatt_read(vol_ctlr->conn, &vol_ctlr->read_params); + if (err != 0) + { + atomic_clear_bit(vol_ctlr->flags, BT_VCP_VOL_CTLR_FLAG_BUSY); + } + + return err; +} + +int bt_vcp_vol_ctlr_read_flags(struct bt_vcp_vol_ctlr *vol_ctlr) +{ + int err; + + if (vol_ctlr == NULL) + { + LOG_DBG("NULL ctlr"); + return -EINVAL; + } + + if (vol_ctlr->conn == NULL) + { + LOG_DBG("NULL ctlr conn"); + return -EINVAL; + } + + if (vol_ctlr->vol_flag_handle == 0) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + else if (atomic_test_and_set_bit(vol_ctlr->flags, + BT_VCP_VOL_CTLR_FLAG_BUSY)) + { + LOG_DBG("Volume controller busy"); + return -EBUSY; + } + + vol_ctlr->read_params.func = vcp_vol_ctlr_read_vol_flag_cb; + vol_ctlr->read_params.handle_count = 1U; + vol_ctlr->read_params.single.handle = vol_ctlr->vol_flag_handle; + vol_ctlr->read_params.single.offset = 0U; + + err = bt_gatt_read(vol_ctlr->conn, &vol_ctlr->read_params); + if (err != 0) + { + atomic_clear_bit(vol_ctlr->flags, BT_VCP_VOL_CTLR_FLAG_BUSY); + } + + return err; +} + +int bt_vcp_vol_ctlr_vol_down(struct bt_vcp_vol_ctlr *vol_ctlr) +{ + return vcp_vol_ctlr_common_vcs_cp(vol_ctlr, BT_VCP_OPCODE_REL_VOL_DOWN); +} + +int bt_vcp_vol_ctlr_vol_up(struct bt_vcp_vol_ctlr *vol_ctlr) +{ + return vcp_vol_ctlr_common_vcs_cp(vol_ctlr, BT_VCP_OPCODE_REL_VOL_UP); +} + +int bt_vcp_vol_ctlr_unmute_vol_down(struct bt_vcp_vol_ctlr *vol_ctlr) +{ + return vcp_vol_ctlr_common_vcs_cp(vol_ctlr, + BT_VCP_OPCODE_UNMUTE_REL_VOL_DOWN); +} + +int bt_vcp_vol_ctlr_unmute_vol_up(struct bt_vcp_vol_ctlr *vol_ctlr) +{ + return vcp_vol_ctlr_common_vcs_cp(vol_ctlr, + BT_VCP_OPCODE_UNMUTE_REL_VOL_UP); +} + +static int write_set_vol_cp(struct bt_vcp_vol_ctlr *vol_ctlr) +{ + int err; + + vol_ctlr->write_params.offset = 0; + vol_ctlr->write_params.data = &vol_ctlr->cp_val; + vol_ctlr->write_params.length = sizeof(vol_ctlr->cp_val); + vol_ctlr->write_params.handle = vol_ctlr->control_handle; + vol_ctlr->write_params.func = vcp_vol_ctlr_write_vcs_cp_cb; + + err = bt_gatt_write(vol_ctlr->conn, &vol_ctlr->write_params); + if (err != 0) + { + atomic_clear_bit(vol_ctlr->flags, BT_VCP_VOL_CTLR_FLAG_BUSY); + } + + return err; +} + +int bt_vcp_vol_ctlr_set_vol(struct bt_vcp_vol_ctlr *vol_ctlr, uint8_t volume) +{ + if (vol_ctlr == NULL) + { + LOG_DBG("NULL ctlr"); + return -EINVAL; + } + + if (vol_ctlr->conn == NULL) + { + LOG_DBG("NULL ctlr conn"); + return -EINVAL; + } + + if (vol_ctlr->control_handle == 0) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + else if (atomic_test_and_set_bit(vol_ctlr->flags, + BT_VCP_VOL_CTLR_FLAG_BUSY)) + { + LOG_DBG("Volume controller busy"); + return -EBUSY; + } + + vol_ctlr->cp_val.cp.opcode = BT_VCP_OPCODE_SET_ABS_VOL; + vol_ctlr->cp_val.cp.counter = vol_ctlr->state.change_counter; + vol_ctlr->cp_val.volume = volume; + + return write_set_vol_cp(vol_ctlr); +} + +int bt_vcp_vol_ctlr_unmute(struct bt_vcp_vol_ctlr *vol_ctlr) +{ + return vcp_vol_ctlr_common_vcs_cp(vol_ctlr, BT_VCP_OPCODE_UNMUTE); +} + +int bt_vcp_vol_ctlr_mute(struct bt_vcp_vol_ctlr *vol_ctlr) +{ + return vcp_vol_ctlr_common_vcs_cp(vol_ctlr, BT_VCP_OPCODE_MUTE); +} diff --git a/libs/zblue/audio/vcp_vol_rend.c b/libs/zblue/audio/vcp_vol_rend.c new file mode 100644 index 0000000000000..ff14aa9fc1670 --- /dev/null +++ b/libs/zblue/audio/vcp_vol_rend.c @@ -0,0 +1,742 @@ +/**************************************************************************** + * libs/zblue/audio/vcp_vol_rend.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Bluetooth VCP */ + +/* Copyright (c) 2018 Intel Corporation + * Copyright (c) 2019-2020 Bose Corporation Copyright (c) 2020-2022 Nordic + * Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "audio_internal.h" +#include "vcp_internal.h" + +#define LOG_LEVEL CONFIG_BT_VCP_VOL_REND_LOG_LEVEL + +LOG_MODULE_REGISTER(bt_vcp_vol_rend); + +#define VOLUME_DOWN(current_vol) ((uint8_t)MAX(0, (int)current_vol - vol_rend.volume_step)) +#define VOLUME_UP(current_vol) ((uint8_t)MIN(UINT8_MAX, (int)current_vol + vol_rend.volume_step)) + +#define VALID_VCP_OPCODE(opcode) ((opcode) <= BT_VCP_OPCODE_MUTE) + +enum vol_rend_notify +{ + NOTIFY_STATE, + NOTIFY_FLAGS, + NOTIFY_NUM, +}; + +struct bt_vcp_vol_rend +{ + struct vcs_state state; + uint8_t flags; + struct bt_vcp_vol_rend_cb *cb; + uint8_t volume_step; + + struct bt_gatt_service *service_p; + struct bt_vocs *vocs_insts[CONFIG_BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT]; + struct bt_aics *aics_insts[CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT]; + + atomic_define(notify, NOTIFY_NUM); + struct k_work_delayable notify_work; +}; + +static struct bt_vcp_vol_rend vol_rend; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void volume_state_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); +} + +static ssize_t read_vol_state( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + LOG_DBG("Volume %u, mute %u, counter %u", vol_rend.state.volume, + vol_rend.state.mute, + vol_rend.state.change_counter); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &vol_rend.state, + sizeof(vol_rend.state)); +} + +static const char *vol_rend_notify_str(enum vol_rend_notify notify) +{ + switch (notify) + { + case NOTIFY_STATE: + return "state"; + case NOTIFY_FLAGS: + return "flags"; + default: + return "unknown"; + } +} + +static void notify_work_reschedule( + struct bt_vcp_vol_rend *inst, enum vol_rend_notify notify, clock_t delay) +{ + int err; + + atomic_set_bit(inst->notify, notify); + + err = k_work_reschedule(&inst->notify_work, delay); + if (err < 0) + { + LOG_ERR("Failed to reschedule %s notification err %d", + vol_rend_notify_str(notify), err); + } + else if (!((delay) == (0))) + { + LOG_DBG("%s notification scheduled in %dms", + vol_rend_notify_str(notify), + TICK2MSEC(k_work_delayable_remaining_get(&inst->notify_work))); + } +} + +static void notify(struct bt_vcp_vol_rend *inst, enum vol_rend_notify notify, + const struct bt_uuid *uuid, + const void *data, + uint16_t len) +{ + int err; + + err = bt_gatt_notify_uuid(NULL, uuid, inst->service_p->attrs, data, len); + if (err == -ENOMEM) + { + notify_work_reschedule(inst, notify, + USEC2TICK(BT_AUDIO_NOTIFY_RETRY_DELAY_US)); + } + else if (err < 0 && err != -ENOTCONN) + { + LOG_ERR("Notify %s err %d", vol_rend_notify_str(notify), err); + } +} + +static void notify_work_handler(struct k_work *work) +{ + struct k_work_delayable *d_work = k_work_delayable_from_work(work); + struct bt_vcp_vol_rend *inst = CONTAINER_OF(d_work, struct bt_vcp_vol_rend, + notify_work); + + if (atomic_test_and_clear_bit(inst->notify, NOTIFY_STATE)) + { + notify(inst, NOTIFY_STATE, BT_UUID_VCS_STATE, &inst->state, + sizeof(inst->state)); + } + + if (IS_ENABLED(CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE) && + atomic_test_and_clear_bit(inst->notify, NOTIFY_FLAGS)) + { + notify(inst, NOTIFY_FLAGS, BT_UUID_VCS_FLAGS, &inst->flags, + sizeof(inst->flags)); + } +} + +static void value_changed(struct bt_vcp_vol_rend *inst, + enum vol_rend_notify notify) +{ + notify_work_reschedule(inst, notify, 0); +} + +static ssize_t write_vcs_control( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + const struct vcs_control_vol *cp_val = buf; + bool notify = false; + bool volume_change = false; + uint8_t opcode; + + ARG_UNUSED(attr); + + if (offset > 0) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (len == 0 || buf == NULL) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + /* Check opcode before length */ + + if (!VALID_VCP_OPCODE(cp_val->cp.opcode)) + { + LOG_DBG("Invalid opcode %u", cp_val->cp.opcode); + return BT_GATT_ERR(BT_VCP_ERR_OP_NOT_SUPPORTED); + } + + if ((len < sizeof(struct vcs_control)) || + (len == sizeof(struct vcs_control_vol) && + cp_val->cp.opcode != BT_VCP_OPCODE_SET_ABS_VOL) || + (len > sizeof(struct vcs_control_vol))) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + opcode = cp_val->cp.opcode; + + LOG_DBG("Opcode %u, counter %u", opcode, cp_val->cp.counter); + + if (cp_val->cp.counter != vol_rend.state.change_counter) + { + return BT_GATT_ERR(BT_VCP_ERR_INVALID_COUNTER); + } + + switch (opcode) + { + case BT_VCP_OPCODE_REL_VOL_DOWN: + LOG_DBG("Relative Volume Down (0x%x)", opcode); + if (vol_rend.state.volume > 0) + { + vol_rend.state.volume = VOLUME_DOWN(vol_rend.state.volume); + notify = true; + } + + volume_change = true; + break; + case BT_VCP_OPCODE_REL_VOL_UP: + LOG_DBG("Relative Volume Up (0x%x)", opcode); + if (vol_rend.state.volume != UINT8_MAX) + { + vol_rend.state.volume = VOLUME_UP(vol_rend.state.volume); + notify = true; + } + + volume_change = true; + break; + case BT_VCP_OPCODE_UNMUTE_REL_VOL_DOWN: + LOG_DBG("(Unmute) relative Volume Down (0x%x)", opcode); + if (vol_rend.state.volume > 0) + { + vol_rend.state.volume = VOLUME_DOWN(vol_rend.state.volume); + notify = true; + } + + if (vol_rend.state.mute) + { + vol_rend.state.mute = BT_VCP_STATE_UNMUTED; + notify = true; + } + + volume_change = true; + break; + case BT_VCP_OPCODE_UNMUTE_REL_VOL_UP: + LOG_DBG("(Unmute) relative Volume Up (0x%x)", opcode); + if (vol_rend.state.volume != UINT8_MAX) + { + vol_rend.state.volume = VOLUME_UP(vol_rend.state.volume); + notify = true; + } + + if (vol_rend.state.mute) + { + vol_rend.state.mute = BT_VCP_STATE_UNMUTED; + notify = true; + } + + volume_change = true; + break; + case BT_VCP_OPCODE_SET_ABS_VOL: + LOG_DBG("Set Absolute Volume (0x%x): Current volume %u", opcode, + vol_rend.state.volume); + if (vol_rend.state.volume != cp_val->volume) + { + vol_rend.state.volume = cp_val->volume; + notify = true; + } + + volume_change = true; + break; + case BT_VCP_OPCODE_UNMUTE: + LOG_DBG("Unmute (0x%x)", opcode); + if (vol_rend.state.mute) + { + vol_rend.state.mute = BT_VCP_STATE_UNMUTED; + notify = true; + } + break; + case BT_VCP_OPCODE_MUTE: + LOG_DBG("Mute (0x%x)", opcode); + if (vol_rend.state.mute == BT_VCP_STATE_UNMUTED) + { + vol_rend.state.mute = BT_VCP_STATE_MUTED; + notify = true; + } + break; + default: + LOG_DBG("Unknown opcode (0x%x)", opcode); + return BT_GATT_ERR(BT_VCP_ERR_OP_NOT_SUPPORTED); + } + + if (notify) + { + vol_rend.state.change_counter++; + LOG_DBG("New state: volume %u, mute %u, counter %u", + vol_rend.state.volume, + vol_rend.state.mute, vol_rend.state.change_counter); + + value_changed(&vol_rend, NOTIFY_STATE); + + if (vol_rend.cb && vol_rend.cb->state) + { + vol_rend.cb->state(conn, 0, vol_rend.state.volume, + vol_rend.state.mute); + } + } + + if (volume_change && !vol_rend.flags) + { + vol_rend.flags = 1U; + + if (IS_ENABLED(CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE)) + { + value_changed(&vol_rend, NOTIFY_FLAGS); + } + + if (vol_rend.cb && vol_rend.cb->flags) + { + vol_rend.cb->flags(conn, 0, vol_rend.flags); + } + } + + return len; +} + +#if defined(CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE) +static void flags_cfg_changed( + const struct bt_gatt_attr *attr, uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); +} +#endif /* CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE */ + +static ssize_t read_flags( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + LOG_DBG("0x%02x", vol_rend.flags); + return bt_gatt_attr_read(conn, attr, buf, len, offset, &vol_rend.flags, + sizeof(vol_rend.flags)); +} + +#define DUMMY_INCLUDE(i, _) BT_GATT_INCLUDE_SERVICE(NULL), +#define VOCS_INCLUDES(cnt) LISTIFY(cnt, DUMMY_INCLUDE, ()) +#define AICS_INCLUDES(cnt) LISTIFY(cnt, DUMMY_INCLUDE, ()) + +/* Volume Control Service GATT Attributes */ + +static struct bt_gatt_attr vcs_attrs[] = +{ + BT_GATT_PRIMARY_SERVICE(BT_UUID_VCS), + VOCS_INCLUDES(CONFIG_BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT) + AICS_INCLUDES(CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT) + BT_AUDIO_CHRC(BT_UUID_VCS_STATE, + BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, + BT_GATT_PERM_READ_ENCRYPT, + read_vol_state, NULL, NULL), + BT_AUDIO_CCC(volume_state_cfg_changed), + BT_AUDIO_CHRC(BT_UUID_VCS_CONTROL, + BT_GATT_CHRC_WRITE, + BT_GATT_PERM_WRITE_ENCRYPT, + NULL, + + write_vcs_control, NULL), +#if defined(CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE) + BT_AUDIO_CHRC(BT_UUID_VCS_FLAGS, + BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, + BT_GATT_PERM_READ_ENCRYPT, read_flags, NULL, NULL), + BT_AUDIO_CCC(flags_cfg_changed) +#else + BT_AUDIO_CHRC(BT_UUID_VCS_FLAGS, + BT_GATT_CHRC_READ, + BT_GATT_PERM_READ_ENCRYPT, + read_flags, + + NULL, NULL) +#endif /* CONFIG_BT_VCP_VOL_REND_VOL_FLAGS_NOTIFIABLE */ +}; + +static struct bt_gatt_service vcs_svc; + +static int prepare_vocs_inst(struct bt_vcp_vol_rend_register_param *param) +{ + int err; + int j; + int i; + + if (CONFIG_BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT == 0) + { + return 0; + } + + DEBUGASSERT(param); + + for (j = 0, i = 0; i < ARRAY_SIZE(vcs_attrs); i++) + { + if (bt_uuid_cmp(vcs_attrs[i].uuid, BT_UUID_GATT_INCLUDE) == 0 && + !vcs_attrs[i].user_data) + { + vol_rend.vocs_insts[j] = bt_vocs_free_instance_get(); + + if (vol_rend.vocs_insts[j] == NULL) + { + LOG_ERR("Could not get free VOCS instances[%d]", j); + return -ENOMEM; + } + + err = bt_vocs_register(vol_rend.vocs_insts[j], + ¶m->vocs_param[j]); + if (err != 0) + { + LOG_DBG("Could not register VOCS instance[%d]: %d", j, err); + return err; + } + + vcs_attrs[i].user_data = + bt_vocs_svc_decl_get(vol_rend.vocs_insts[j]); + j++; + + if (j == CONFIG_BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT) + { + break; + } + } + } + + DEBUGASSERT(j == CONFIG_BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT); + + return 0; +} + +static int prepare_aics_inst(struct bt_vcp_vol_rend_register_param *param) +{ + int err; + int j; + int i; + + if (CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT == 0) + { + return 0; + } + + DEBUGASSERT(param); + + for (j = 0, i = 0; i < ARRAY_SIZE(vcs_attrs); i++) + { + if (bt_uuid_cmp(vcs_attrs[i].uuid, BT_UUID_GATT_INCLUDE) == 0 && + !vcs_attrs[i].user_data) + { + vol_rend.aics_insts[j] = bt_aics_free_instance_get(); + + if (vol_rend.aics_insts[j] == NULL) + { + LOG_ERR("Could not get free AICS instances[%d]", j); + return -ENOMEM; + } + + err = bt_aics_register(vol_rend.aics_insts[j], + ¶m->aics_param[j]); + if (err != 0) + { + LOG_DBG("Could not register AICS instance[%d]: %d", j, err); + return err; + } + + vcs_attrs[i].user_data = + bt_aics_svc_decl_get(vol_rend.aics_insts[j]); + j++; + + LOG_DBG("AICS P %p", vcs_attrs[i].user_data); + + if (j == CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT) + { + break; + } + } + } + + DEBUGASSERT(j == CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT); + + return 0; +} + +/* Public Api */ + +int bt_vcp_vol_rend_register(struct bt_vcp_vol_rend_register_param *param) +{ + static bool registered; + int err; + + if (param == NULL) + { + LOG_DBG("param is NULL"); + return -EINVAL; + } + + if (param->mute > BT_VCP_STATE_MUTED) + { + LOG_DBG("Invalid mute value: %u", param->mute); + return -EINVAL; + } + + if (param->step == 0) + { + LOG_DBG("Invalid step value: %u", param->step); + return -EINVAL; + } + + if (registered) + { + return -EALREADY; + } + + vcs_svc = (struct bt_gatt_service)BT_GATT_SERVICE(vcs_attrs); + + if (CONFIG_BT_VCP_VOL_REND_VOCS_INSTANCE_COUNT > 0) + { + err = prepare_vocs_inst(param); + + if (err != 0) + { + return err; + } + } + + if (CONFIG_BT_VCP_VOL_REND_AICS_INSTANCE_COUNT > 0) + { + err = prepare_aics_inst(param); + + if (err != 0) + { + return err; + } + } + + vol_rend.state.volume = param->volume; + vol_rend.state.mute = param->mute; + vol_rend.volume_step = param->step; + vol_rend.service_p = &vcs_svc; + + err = bt_gatt_service_register(&vcs_svc); + if (err != 0) + { + LOG_DBG("VCP service register failed: %d", err); + } + + vol_rend.cb = param->cb; + + atomic_set(vol_rend.notify, 0); + k_work_init_delayable(&vol_rend.notify_work, notify_work_handler); + + registered = true; + + return err; +} + +int bt_vcp_vol_rend_included_get(struct bt_vcp_included *included) +{ + if (included == NULL) + { + return -EINVAL; + } + + included->vocs_cnt = ARRAY_SIZE(vol_rend.vocs_insts); + included->vocs = vol_rend.vocs_insts; + + included->aics_cnt = ARRAY_SIZE(vol_rend.aics_insts); + included->aics = vol_rend.aics_insts; + + return 0; +} + +int bt_vcp_vol_rend_set_step(uint8_t volume_step) +{ + if (volume_step > 0U) + { + vol_rend.volume_step = volume_step; + return 0; + } + else + { + return -EINVAL; + } +} + +int bt_vcp_vol_rend_get_state(void) +{ + if (vol_rend.cb && vol_rend.cb->state) + { + vol_rend.cb->state(NULL, 0, vol_rend.state.volume, + vol_rend.state.mute); + } + + return 0; +} + +int bt_vcp_vol_rend_get_flags(void) +{ + if (vol_rend.cb && vol_rend.cb->flags) + { + vol_rend.cb->flags(NULL, 0, vol_rend.flags); + } + + return 0; +} + +int bt_vcp_vol_rend_vol_down(void) +{ + const struct vcs_control cp = + { + .opcode = BT_VCP_OPCODE_REL_VOL_DOWN, + .counter = vol_rend.state.change_counter, + }; + + int err; + + err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0); + + return err > 0 ? 0 : err; +} + +int bt_vcp_vol_rend_vol_up(void) +{ + const struct vcs_control cp = + { + .opcode = BT_VCP_OPCODE_REL_VOL_UP, + .counter = vol_rend.state.change_counter, + }; + + int err; + + err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0); + + return err > 0 ? 0 : err; +} + +int bt_vcp_vol_rend_unmute_vol_down(void) +{ + const struct vcs_control cp = + { + .opcode = BT_VCP_OPCODE_UNMUTE_REL_VOL_DOWN, + .counter = vol_rend.state.change_counter, + }; + + int err; + + err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0); + + return err > 0 ? 0 : err; +} + +int bt_vcp_vol_rend_unmute_vol_up(void) +{ + const struct vcs_control cp = + { + .opcode = BT_VCP_OPCODE_UNMUTE_REL_VOL_UP, + .counter = vol_rend.state.change_counter, + }; + + int err; + + err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0); + + return err > 0 ? 0 : err; +} + +int bt_vcp_vol_rend_set_vol(uint8_t volume) +{ + const struct vcs_control_vol cp = + { + .cp = + { + .opcode = + BT_VCP_OPCODE_SET_ABS_VOL, .counter = + vol_rend.state.change_counter + }, + .volume = volume + }; + + int err; + + err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0); + + return err > 0 ? 0 : err; +} + +int bt_vcp_vol_rend_unmute(void) +{ + const struct vcs_control cp = + { + .opcode = BT_VCP_OPCODE_UNMUTE, + .counter = vol_rend.state.change_counter, + }; + + int err; + + err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0); + + return err > 0 ? 0 : err; +} + +int bt_vcp_vol_rend_mute(void) +{ + const struct vcs_control cp = + { + .opcode = BT_VCP_OPCODE_MUTE, + .counter = vol_rend.state.change_counter, + }; + + int err; + + err = write_vcs_control(NULL, NULL, &cp, sizeof(cp), 0, 0); + + return err > 0 ? 0 : err; +} diff --git a/libs/zblue/audio/vocs.c b/libs/zblue/audio/vocs.c new file mode 100644 index 0000000000000..a0b69d74f21d6 --- /dev/null +++ b/libs/zblue/audio/vocs.c @@ -0,0 +1,755 @@ +/**************************************************************************** + * libs/zblue/audio/vocs.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Bluetooth VOCS - Volume offset Control Service + * + * Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/bt_str.h" +#include "audio_internal.h" +#include "vocs_internal.h" + +#define LOG_LEVEL CONFIG_BT_VOCS_LOG_LEVEL +LOG_MODULE_REGISTER(bt_vocs); + +#define VALID_VOCS_OPCODE(opcode) ((opcode) == BT_VOCS_OPCODE_SET_OFFSET) + +#define BT_AUDIO_LOCATION_RFU (~BT_AUDIO_LOCATION_ANY) + +#if defined(CONFIG_BT_VOCS) + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void offset_state_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); +} + +static ssize_t read_offset_state( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct bt_vocs_server *inst = BT_AUDIO_CHRC_USER_DATA(attr); + + LOG_DBG("offset %d, counter %u", inst->state.offset, + inst->state.change_counter); + return bt_gatt_attr_read(conn, attr, buf, len, offset, &inst->state, + sizeof(inst->state)); +} + +static void location_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); +} + +static const char *vocs_notify_str(enum bt_vocs_notify notify) +{ + switch (notify) + { + case NOTIFY_STATE: + return "state"; + case NOTIFY_LOCATION: + return "location"; + case NOTIFY_OUTPUT_DESC: + return "output desc"; + default: + return "unknown"; + } +} + +static void notify_work_reschedule( + struct bt_vocs_server *inst, enum bt_vocs_notify notify, clock_t delay) +{ + int err; + + atomic_set_bit(inst->notify, notify); + + err = k_work_reschedule(&inst->notify_work, delay); + if (err < 0) + { + LOG_ERR("Failed to reschedule %s notification err %d", + vocs_notify_str(notify), err); + } +} + +static void notify(struct bt_vocs_server *inst, enum bt_vocs_notify notify, + const struct bt_uuid *uuid, + const void *data, + uint16_t len) +{ + int err; + + err = bt_gatt_notify_uuid(NULL, uuid, inst->service_p->attrs, data, len); + if (err == -ENOMEM) + { + notify_work_reschedule(inst, notify, + USEC2TICK(BT_AUDIO_NOTIFY_RETRY_DELAY_US)); + } + else if (err < 0 && err != -ENOTCONN) + { + LOG_ERR("Notify %s err %d", vocs_notify_str(notify), err); + } +} + +static void notify_work_handler(struct k_work *work) +{ + struct k_work_delayable *d_work = k_work_delayable_from_work(work); + struct bt_vocs_server *inst = CONTAINER_OF(d_work, struct bt_vocs_server, + notify_work); + + if (atomic_test_and_clear_bit(inst->notify, NOTIFY_STATE)) + { + notify(inst, NOTIFY_STATE, BT_UUID_VOCS_STATE, &inst->state, + sizeof(inst->state)); + } + + if (atomic_test_and_clear_bit(inst->notify, NOTIFY_LOCATION)) + { + notify(inst, NOTIFY_LOCATION, BT_UUID_VOCS_LOCATION, &inst->location, + sizeof(inst->location)); + } + + if (atomic_test_and_clear_bit(inst->notify, NOTIFY_OUTPUT_DESC)) + { + notify(inst, NOTIFY_OUTPUT_DESC, BT_UUID_VOCS_DESCRIPTION, + &inst->output_desc, + strlen(inst->output_desc)); + } +} + +static void value_changed(struct bt_vocs_server *inst, + enum bt_vocs_notify notify) +{ + notify_work_reschedule(inst, notify, 0); +} +#else +#define value_changed(...) +#endif /* CONFIG_BT_VOCS */ + +static ssize_t write_location( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + struct bt_vocs_server *inst = BT_AUDIO_CHRC_USER_DATA(attr); + enum bt_audio_location new_location; + + ARG_UNUSED(conn); + ARG_UNUSED(flags); + + if (offset) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (len != sizeof(inst->location)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + new_location = sys_get_le32(buf); + if ((new_location & BT_AUDIO_LOCATION_RFU) > 0) + { + LOG_DBG("Invalid location %u", new_location); + + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + + if (new_location != inst->location) + { + inst->location = new_location; + + value_changed(inst, NOTIFY_LOCATION); + + if (inst->cb && inst->cb->location) + { + inst->cb->location(&inst->vocs, 0, inst->location); + } + } + + return len; +} + +#if defined(CONFIG_BT_VOCS) +static ssize_t read_location( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct bt_vocs_server *inst = BT_AUDIO_CHRC_USER_DATA(attr); + + LOG_DBG("0x%08x", inst->location); + return bt_gatt_attr_read(conn, attr, buf, len, offset, &inst->location, + sizeof(inst->location)); +} +#endif /* CONFIG_BT_VOCS */ + +static ssize_t write_vocs_control( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + struct bt_vocs_server *inst = BT_AUDIO_CHRC_USER_DATA(attr); + const struct bt_vocs_control *cp = buf; + bool notify = false; + + ARG_UNUSED(conn); + ARG_UNUSED(flags); + + if (offset) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (!len || !buf) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + /* Check opcode before length */ + + if (!VALID_VOCS_OPCODE(cp->opcode)) + { + LOG_DBG("Invalid opcode %u", cp->opcode); + return BT_GATT_ERR(BT_VOCS_ERR_OP_NOT_SUPPORTED); + } + + if (len != sizeof(struct bt_vocs_control)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + LOG_DBG("Opcode %u, counter %u", cp->opcode, cp->counter); + + if (cp->counter != inst->state.change_counter) + { + return BT_GATT_ERR(BT_VOCS_ERR_INVALID_COUNTER); + } + + switch (cp->opcode) + { + case BT_VOCS_OPCODE_SET_OFFSET: + LOG_DBG("Set offset %d", cp->offset); + if (cp->offset > BT_VOCS_MAX_OFFSET || + cp->offset < BT_VOCS_MIN_OFFSET) + { + return BT_GATT_ERR(BT_VOCS_ERR_OUT_OF_RANGE); + } + + if (inst->state.offset != sys_le16_to_cpu(cp->offset)) + { + inst->state.offset = sys_le16_to_cpu(cp->offset); + notify = true; + } + break; + default: + return BT_GATT_ERR(BT_VOCS_ERR_OP_NOT_SUPPORTED); + } + + if (notify) + { + inst->state.change_counter++; + LOG_DBG("New state: offset %d, counter %u", inst->state.offset, + inst->state.change_counter); + + value_changed(inst, NOTIFY_STATE); + + if (inst->cb && inst->cb->state) + { + inst->cb->state(&inst->vocs, 0, inst->state.offset); + } + } + + return len; +} + +#if defined(CONFIG_BT_VOCS) +static void output_desc_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("value 0x%04x", value); +} +#endif /* CONFIG_BT_VOCS */ + +static ssize_t write_output_desc( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + struct bt_vocs_server *inst = BT_AUDIO_CHRC_USER_DATA(attr); + + ARG_UNUSED(conn); + ARG_UNUSED(flags); + + if (offset) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (len >= sizeof(inst->output_desc)) + { + LOG_DBG("Output desc was clipped from length %u to %zu", len, + sizeof(inst->output_desc) - 1); + + /* We just clip the string value if it's too long */ + + len = (uint16_t)sizeof(inst->output_desc) - 1; + } + + if (len != strlen(inst->output_desc) || memcmp(buf, + inst->output_desc, + len)) + { + memcpy(inst->output_desc, buf, len); + inst->output_desc[len] = '\0'; + + value_changed(inst, NOTIFY_OUTPUT_DESC); + + if (inst->cb && inst->cb->description) + { + inst->cb->description(&inst->vocs, 0, inst->output_desc); + } + } + + LOG_DBG("%s", inst->output_desc); + + return len; +} + +static int vocs_write(struct bt_vocs_server *inst, + ssize_t (*write)(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, uint16_t len, + uint16_t offset, + uint8_t flags), + const void *buf, uint16_t len) +{ + struct bt_audio_attr_user_data user_data = + { + .user_data = inst, + }; + + struct bt_gatt_attr attr = + { + .user_data = &user_data, + }; + + int err; + + err = write(NULL, &attr, buf, len, 0, 0); + if (err < 0) + { + return err; + } + + return 0; +} + +#if defined(CONFIG_BT_VOCS) +static ssize_t read_output_desc( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct bt_vocs_server *inst = BT_AUDIO_CHRC_USER_DATA(attr); + + LOG_DBG("%s", inst->output_desc); + return bt_gatt_attr_read(conn, attr, buf, len, offset, &inst->output_desc, + strlen(inst->output_desc)); +} + +#define BT_VOCS_SERVICE_DEFINITION(_vocs) \ + {BT_GATT_SECONDARY_SERVICE(BT_UUID_VOCS), \ + BT_AUDIO_CHRC(BT_UUID_VOCS_STATE, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, read_offset_state, NULL, &_vocs), \ + BT_AUDIO_CCC(offset_state_cfg_changed), \ + BT_AUDIO_CHRC(BT_UUID_VOCS_LOCATION, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, read_location, write_location, &_vocs), \ + BT_AUDIO_CCC(location_cfg_changed), \ + BT_AUDIO_CHRC(BT_UUID_VOCS_CONTROL, BT_GATT_CHRC_WRITE, BT_GATT_PERM_WRITE_ENCRYPT, NULL, \ + write_vocs_control, &_vocs), \ + BT_AUDIO_CHRC(BT_UUID_VOCS_DESCRIPTION, BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, \ + BT_GATT_PERM_READ_ENCRYPT, read_output_desc, write_output_desc, &_vocs), \ + BT_AUDIO_CCC(output_desc_cfg_changed)} + +static struct bt_vocs_server vocs_insts[CONFIG_BT_VOCS_MAX_INSTANCE_COUNT]; +BT_GATT_SERVICE_INSTANCE_DEFINE(vocs_service_list, + vocs_insts, + CONFIG_BT_VOCS_MAX_INSTANCE_COUNT, + + BT_VOCS_SERVICE_DEFINITION); + +struct bt_vocs *bt_vocs_free_instance_get(void) +{ + static uint32_t instance_cnt; + + if (instance_cnt >= CONFIG_BT_VOCS_MAX_INSTANCE_COUNT) + { + return NULL; + } + + return &vocs_insts[instance_cnt++].vocs; +} + +void *bt_vocs_svc_decl_get(struct bt_vocs *vocs) +{ + struct bt_vocs_server *inst; + + if (!vocs) + { + LOG_DBG("Null VOCS pointer"); + return NULL; + } + + if (vocs->client_instance) + { + LOG_DBG("vocs pointer shall be server instance"); + return NULL; + } + + inst = CONTAINER_OF(vocs, struct bt_vocs_server, vocs); + + return inst->service_p->attrs; +} + +static void prepare_vocs_instances(void) +{ + for (int i = 0; i < ARRAY_SIZE(vocs_insts); i++) + { + vocs_insts[i].service_p = &vocs_service_list[i]; + } +} + +int bt_vocs_register(struct bt_vocs *vocs, + const struct bt_vocs_register_param *param) +{ + struct bt_vocs_server *inst; + int err; + struct bt_gatt_attr *attr; + struct bt_gatt_chrc *chrc; + static bool instances_prepared; + + if (!vocs) + { + LOG_DBG("Null VOCS pointer"); + return -EINVAL; + } + + if (vocs->client_instance) + { + LOG_DBG("vocs pointer shall be server instance"); + return -EINVAL; + } + + inst = CONTAINER_OF(vocs, struct bt_vocs_server, vocs); + + if (!param) + { + LOG_DBG("NULL params pointer"); + return -EINVAL; + } + + if (!instances_prepared) + { + prepare_vocs_instances(); + instances_prepared = true; + } + + if (inst->initialized) + { + LOG_DBG("Already initialized VOCS instance"); + return -EALREADY; + } + + if (param->offset > BT_VOCS_MAX_OFFSET || + param->offset < BT_VOCS_MIN_OFFSET) + { + LOG_DBG("Invalid offset %d", param->offset); + return -EINVAL; + } + + inst->location = param->location; + inst->state.offset = param->offset; + inst->cb = param->cb; + + if (param->output_desc) + { + (void)utf8_lcpy(inst->output_desc, param->output_desc, + sizeof(inst->output_desc)); + if (IS_ENABLED(CONFIG_BT_VOCS_LOG_LEVEL_DBG) && + strcmp(inst->output_desc, param->output_desc)) + { + LOG_DBG("Output desc clipped to %s", inst->output_desc); + } + } + + /* Iterate over the attributes in VOCS (starting from i = 1 to skip the + * service declaration) to find the BT_UUID_VOCS_DESCRIPTION or + * BT_UUID_VOCS_LOCATION and update the characteristic value (at [i]), + * update with the write permission and callback, and also update the + * characteristic declaration (always found at [i - 1]) with the + * BT_GATT_CHRC_WRITE_WITHOUT_RESP property. + */ + + for (int i = 1; i < inst->service_p->attr_count; i++) + { + attr = &inst->service_p->attrs[i]; + + if (param->location_writable && !bt_uuid_cmp(attr->uuid, + BT_UUID_VOCS_LOCATION)) + { + /* Update attr and chrc to be writable */ + + chrc = inst->service_p->attrs[i - 1].user_data; + attr->perm |= BT_GATT_PERM_WRITE_ENCRYPT; + chrc->properties |= BT_GATT_CHRC_WRITE_WITHOUT_RESP; + } + else if (param->desc_writable && !bt_uuid_cmp(attr->uuid, + BT_UUID_VOCS_DESCRIPTION)) + { + /* Update attr and chrc to be writable */ + + chrc = inst->service_p->attrs[i - 1].user_data; + attr->perm |= BT_GATT_PERM_WRITE_ENCRYPT; + chrc->properties |= BT_GATT_CHRC_WRITE_WITHOUT_RESP; + } + } + + err = bt_gatt_service_register(inst->service_p); + if (err != 0) + { + LOG_DBG("Could not register VOCS service"); + return err; + } + + atomic_set(inst->notify, 0); + k_work_init_delayable(&inst->notify_work, notify_work_handler); + + inst->initialized = true; + return 0; +} +#endif /* CONFIG_BT_VOCS */ + +int bt_vocs_state_get(struct bt_vocs *inst) +{ + if (!inst) + { + LOG_DBG("Null VOCS pointer"); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_VOCS_CLIENT) && inst->client_instance) + { + struct bt_vocs_client *cli = CONTAINER_OF(inst, struct bt_vocs_client, + vocs); + + return bt_vocs_client_state_get(cli); + } + else if (IS_ENABLED(CONFIG_BT_VOCS) && !inst->client_instance) + { + struct bt_vocs_server *srv = CONTAINER_OF(inst, struct bt_vocs_server, + vocs); + + if (srv->cb && srv->cb->state) + { + srv->cb->state(inst, 0, srv->state.offset); + } + + return 0; + } + + return -ENOTSUP; +} + +int bt_vocs_location_get(struct bt_vocs *inst) +{ + if (!inst) + { + LOG_DBG("Null VOCS pointer"); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_VOCS_CLIENT) && inst->client_instance) + { + struct bt_vocs_client *cli = CONTAINER_OF(inst, struct bt_vocs_client, + vocs); + + return bt_vocs_client_location_get(cli); + } + else if (IS_ENABLED(CONFIG_BT_VOCS) && !inst->client_instance) + { + struct bt_vocs_server *srv = CONTAINER_OF(inst, struct bt_vocs_server, + vocs); + + if (srv->cb && srv->cb->location) + { + srv->cb->location(inst, 0, srv->location); + } + + return 0; + } + + return -ENOTSUP; +} + +int bt_vocs_location_set(struct bt_vocs *inst, uint32_t location) +{ + if (!inst) + { + LOG_DBG("Null VOCS pointer"); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_VOCS_CLIENT) && inst->client_instance) + { + struct bt_vocs_client *cli = CONTAINER_OF(inst, struct bt_vocs_client, + vocs); + + return bt_vocs_client_location_set(cli, location); + } + else if (IS_ENABLED(CONFIG_BT_VOCS) && !inst->client_instance) + { + struct bt_vocs_server *srv = CONTAINER_OF(inst, struct bt_vocs_server, + vocs); + + return vocs_write(srv, write_location, &location, sizeof(location)); + } + + return -ENOTSUP; +} + +int bt_vocs_state_set(struct bt_vocs *inst, int16_t offset) +{ + if (!inst) + { + LOG_DBG("Null VOCS pointer"); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_VOCS_CLIENT) && inst->client_instance) + { + struct bt_vocs_client *cli = CONTAINER_OF(inst, struct bt_vocs_client, + vocs); + + return bt_vocs_client_state_set(cli, offset); + } + else if (IS_ENABLED(CONFIG_BT_VOCS) && !inst->client_instance) + { + struct bt_vocs_server *srv = CONTAINER_OF(inst, struct bt_vocs_server, + vocs); + struct bt_vocs_control cp; + + cp.opcode = BT_VOCS_OPCODE_SET_OFFSET; + cp.counter = srv->state.change_counter; + cp.offset = sys_cpu_to_le16(offset); + + return vocs_write(srv, write_vocs_control, &cp, sizeof(cp)); + } + + return -ENOTSUP; +} + +int bt_vocs_description_get(struct bt_vocs *inst) +{ + if (!inst) + { + LOG_DBG("Null VOCS pointer"); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_VOCS_CLIENT) && inst->client_instance) + { + struct bt_vocs_client *cli = CONTAINER_OF(inst, struct bt_vocs_client, + vocs); + + return bt_vocs_client_description_get(cli); + } + else if (IS_ENABLED(CONFIG_BT_VOCS) && !inst->client_instance) + { + struct bt_vocs_server *srv = CONTAINER_OF(inst, struct bt_vocs_server, + vocs); + + if (srv->cb && srv->cb->description) + { + srv->cb->description(inst, 0, srv->output_desc); + } + + return 0; + } + + return -ENOTSUP; +} + +int bt_vocs_description_set(struct bt_vocs *inst, const char *description) +{ + if (!inst) + { + LOG_DBG("Null VOCS pointer"); + return -EINVAL; + } + + if (!description) + { + LOG_DBG("Null description pointer"); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_VOCS_CLIENT) && inst->client_instance) + { + struct bt_vocs_client *cli = CONTAINER_OF(inst, struct bt_vocs_client, + vocs); + + return bt_vocs_client_description_set(cli, description); + } + else if (IS_ENABLED(CONFIG_BT_VOCS) && !inst->client_instance) + { + struct bt_vocs_server *srv = CONTAINER_OF(inst, struct bt_vocs_server, + vocs); + + return vocs_write(srv, write_output_desc, description, + strlen(description)); + } + + return -ENOTSUP; +} diff --git a/libs/zblue/audio/vocs_client.c b/libs/zblue/audio/vocs_client.c new file mode 100644 index 0000000000000..e2e53f5474a76 --- /dev/null +++ b/libs/zblue/audio/vocs_client.c @@ -0,0 +1,1009 @@ +/**************************************************************************** + * libs/zblue/audio/vocs_client.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Bluetooth VOCS - Volume Offset Control Service - Client */ + +/* Copyright (c) 2021-2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vocs_internal.h" + +LOG_MODULE_REGISTER(bt_vocs_client, CONFIG_BT_VOCS_CLIENT_LOG_LEVEL); + +static struct bt_vocs_client + insts[CONFIG_BT_MAX_CONN * CONFIG_BT_VOCS_CLIENT_MAX_INSTANCE_COUNT]; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static struct bt_vocs_client *lookup_vocs_by_handle(struct bt_conn *conn, + uint16_t handle) +{ + DEBUGASSERT(handle != 0U); + DEBUGASSERT(conn); + + for (int i = 0; i < ARRAY_SIZE(insts); i++) + { + if (insts[i].conn == conn && atomic_test_bit(insts[i].flags, + BT_VOCS_CLIENT_FLAG_ACTIVE) && + insts[i].start_handle <= handle && insts[i].end_handle >= handle) + { + return &insts[i]; + } + } + + LOG_DBG("Could not find VOCS instance with handle 0x%04x", handle); + return NULL; +} + +uint8_t vocs_client_notify_handler( + struct bt_conn *conn, struct bt_gatt_subscribe_params *params, + const void *data, uint16_t length) +{ + uint16_t handle = params->value_handle; + struct bt_vocs_client *inst; + + if (conn == NULL) + { + return BT_GATT_ITER_CONTINUE; + } + + inst = lookup_vocs_by_handle(conn, handle); + + if (!inst) + { + LOG_DBG("Instance not found"); + return BT_GATT_ITER_STOP; + } + + if (!data || !length) + { + return BT_GATT_ITER_CONTINUE; + } + + if (handle == inst->state_handle) + { + if (length == sizeof(inst->state)) + { + memcpy(&inst->state, data, length); + LOG_DBG("Inst %p: Offset %d, counter %u", inst, inst->state.offset, + inst->state.change_counter); + if (inst->cb && inst->cb->state) + { + inst->cb->state(&inst->vocs, 0, inst->state.offset); + } + } + else + { + LOG_DBG("Invalid state length %u", length); + } + } + else if (handle == inst->desc_handle) + { + char desc[MIN(BT_L2CAP_RX_MTU, BT_ATT_MAX_ATTRIBUTE_LEN) + 1]; + + /* Truncate if too large */ + + if (length > sizeof(desc) - 1) + { + LOG_DBG("Description truncated from %u to %zu octets", length, + sizeof(desc) - 1); + } + + length = MIN(sizeof(desc) - 1, length); + + memcpy(desc, data, length); + desc[length] = '\0'; + LOG_DBG("Inst %p: Output description: %s", inst, desc); + if (inst->cb && inst->cb->description) + { + inst->cb->description(&inst->vocs, 0, desc); + } + } + else if (handle == inst->location_handle) + { + if (length == sizeof(inst->location)) + { + memcpy(&inst->location, data, length); + LOG_DBG("Inst %p: Location %u", inst, inst->location); + if (inst->cb && inst->cb->location) + { + inst->cb->location(&inst->vocs, 0, inst->location); + } + } + else + { + LOG_DBG("Invalid location length %u", length); + } + } + + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t vocs_client_read_offset_state_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, + const void *data, uint16_t length) +{ + int cb_err = err; + struct bt_vocs_client *inst = lookup_vocs_by_handle(conn, + params->single.handle); + + memset(params, 0, sizeof(*params)); + + if (!inst) + { + LOG_DBG("Instance not found"); + return BT_GATT_ITER_STOP; + } + + LOG_DBG("Inst %p: err: 0x%02X", inst, err); + atomic_clear_bit(inst->flags, BT_VOCS_CLIENT_FLAG_BUSY); + + if (cb_err) + { + LOG_DBG("Offset state read failed: %d", err); + } + else if (data) + { + if (length == sizeof(inst->state)) + { + memcpy(&inst->state, data, length); + LOG_DBG("Offset %d, counter %u", inst->state.offset, + inst->state.change_counter); + } + else + { + LOG_DBG("Invalid length %u (expected %zu)", length, + sizeof(inst->state)); + cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + } + else + { + LOG_DBG("Invalid state"); + cb_err = BT_ATT_ERR_UNLIKELY; + } + + if (inst->cb && inst->cb->state) + { + inst->cb->state(&inst->vocs, cb_err, cb_err ? 0 : inst->state.offset); + } + + return BT_GATT_ITER_STOP; +} + +static uint8_t vocs_client_read_location_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, + const void *data, uint16_t length) +{ + int cb_err = err; + struct bt_vocs_client *inst = lookup_vocs_by_handle(conn, + params->single.handle); + + memset(params, 0, sizeof(*params)); + + if (!inst) + { + LOG_DBG("Instance not found"); + return BT_GATT_ITER_STOP; + } + + LOG_DBG("Inst %p: err: 0x%02X", inst, err); + atomic_clear_bit(inst->flags, BT_VOCS_CLIENT_FLAG_BUSY); + + if (cb_err) + { + LOG_DBG("Offset state read failed: %d", err); + } + else if (data) + { + if (length == sizeof(inst->location)) + { + memcpy(&inst->location, data, length); + LOG_DBG("Location %u", inst->location); + } + else + { + LOG_DBG("Invalid length %u (expected %zu)", length, + sizeof(inst->location)); + cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + } + else + { + LOG_DBG("Invalid location"); + cb_err = BT_ATT_ERR_UNLIKELY; + } + + if (inst->cb && inst->cb->location) + { + inst->cb->location(&inst->vocs, cb_err, cb_err ? 0 : inst->location); + } + + return BT_GATT_ITER_STOP; +} + +static uint8_t internal_read_volume_offset_state_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, + const void *data, uint16_t length) +{ + int cb_err = err; + struct bt_vocs_client *inst = lookup_vocs_by_handle(conn, + params->single.handle); + + memset(params, 0, sizeof(*params)); + + if (!inst) + { + LOG_ERR("Instance not found"); + return BT_GATT_ITER_STOP; + } + + if (err != 0) + { + LOG_WRN("Volume offset state read failed: %d", err); + cb_err = BT_ATT_ERR_UNLIKELY; + } + else if (data) + { + if (length == sizeof(inst->state)) + { + int write_err; + + memcpy(&inst->state, data, length); + LOG_DBG("Offset %d, counter %u", inst->state.offset, + inst->state.change_counter); + + /* clear busy flag to reuse function */ + + atomic_clear_bit(inst->flags, BT_VOCS_CLIENT_FLAG_BUSY); + write_err = bt_vocs_client_state_set(inst, inst->cp.offset); + if (write_err) + { + cb_err = BT_ATT_ERR_UNLIKELY; + } + } + else + { + LOG_DBG("Invalid length %u (expected %zu)", length, + sizeof(inst->state)); + cb_err = BT_ATT_ERR_UNLIKELY; + } + } + else + { + LOG_DBG("Invalid (empty) offset state read"); + cb_err = BT_ATT_ERR_UNLIKELY; + } + + if (cb_err) + { + atomic_clear_bit(inst->flags, BT_VOCS_CLIENT_FLAG_BUSY); + + if (inst->cb && inst->cb->set_offset) + { + inst->cb->set_offset(&inst->vocs, err); + } + } + + return BT_GATT_ITER_STOP; +} + +static void vocs_client_write_vocs_cp_cb(struct bt_conn *conn, uint8_t err, + struct bt_gatt_write_params *params) +{ + int cb_err = err; + struct bt_vocs_client *inst = lookup_vocs_by_handle(conn, params->handle); + + memset(params, 0, sizeof(*params)); + + if (!inst) + { + LOG_DBG("Instance not found"); + return; + } + + LOG_DBG("Inst %p: err: 0x%02X", inst, err); + + /* If the change counter is out of data when a write was attempted from the + * application, we automatically initiate a read to get the newest state + * and try again. Once the change counter has been read, we restart the + * applications write request. If it fails the second time, we return an + * error to the application. + */ + + if (cb_err == BT_VOCS_ERR_INVALID_COUNTER && + atomic_test_bit(inst->flags, BT_VOCS_CLIENT_FLAG_CP_RETRIED)) + { + cb_err = BT_ATT_ERR_UNLIKELY; + } + else if (cb_err == BT_VOCS_ERR_INVALID_COUNTER && inst->state_handle) + { + LOG_DBG("Invalid change counter. Reading volume offset state from " + "server."); + + inst->read_params.func = internal_read_volume_offset_state_cb; + inst->read_params.handle_count = 1U; + inst->read_params.single.handle = inst->state_handle; + + atomic_set_bit(inst->flags, BT_VOCS_CLIENT_FLAG_CP_RETRIED); + + cb_err = bt_gatt_read(conn, &inst->read_params); + if (cb_err) + { + LOG_WRN("Could not read Volume offset state: %d", cb_err); + } + else + { + /* Wait for read callback */ + + return; + } + } + + atomic_clear_bit(inst->flags, BT_VOCS_CLIENT_FLAG_CP_RETRIED); + atomic_clear_bit(inst->flags, BT_VOCS_CLIENT_FLAG_BUSY); + + if (inst->cb && inst->cb->set_offset) + { + inst->cb->set_offset(&inst->vocs, cb_err); + } +} + +static uint8_t vocs_client_read_output_desc_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, + const void *data, uint16_t length) +{ + int cb_err = err; + struct bt_vocs_client *inst = lookup_vocs_by_handle(conn, + params->single.handle); + char desc[MIN(BT_L2CAP_RX_MTU, BT_ATT_MAX_ATTRIBUTE_LEN) + 1]; + + memset(params, 0, sizeof(*params)); + + if (!inst) + { + LOG_DBG("Instance not found"); + return BT_GATT_ITER_STOP; + } + + LOG_DBG("Inst %p: err: 0x%02X", inst, err); + atomic_clear_bit(inst->flags, BT_VOCS_CLIENT_FLAG_BUSY); + + if (cb_err) + { + LOG_DBG("Description read failed: %d", err); + } + else + { + if (data) + { + LOG_HEXDUMP_DBG(data, length, "Output description read"); + + if (length > sizeof(desc) - 1) + { + LOG_DBG("Description truncated from %u to %zu octets", length, + sizeof(desc) - 1); + } + + length = MIN(sizeof(desc) - 1, length); + + /* TODO: Handle long reads */ + + memcpy(desc, data, length); + } + + desc[length] = '\0'; + LOG_DBG("Output description: %s", desc); + } + + if (inst->cb && inst->cb->description) + { + inst->cb->description(&inst->vocs, cb_err, cb_err ? NULL : desc); + } + + return BT_GATT_ITER_STOP; +} + +static bool valid_inst_discovered(struct bt_vocs_client *inst) +{ + return inst->state_handle && inst->control_handle && + inst->location_handle && + inst->desc_handle; +} + +static uint8_t vocs_discover_func( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_vocs_client *inst = CONTAINER_OF(params, struct bt_vocs_client, + discover_params); + + if (!attr) + { + LOG_DBG("Discovery complete for VOCS %p", inst); + atomic_clear_bit(inst->flags, BT_VOCS_CLIENT_FLAG_BUSY); + (void)memset(params, 0, sizeof(*params)); + + if (inst->cb && inst->cb->discover) + { + int err = valid_inst_discovered(inst) ? 0 : -ENOENT; + + inst->cb->discover(&inst->vocs, err); + } + + return BT_GATT_ITER_STOP; + } + + LOG_DBG("[ATTRIBUTE] handle 0x%04X", attr->handle); + + if (params->type == BT_GATT_DISCOVER_CHARACTERISTIC) + { + struct bt_gatt_subscribe_params *sub_params = NULL; + struct bt_gatt_chrc *chrc; + + chrc = (struct bt_gatt_chrc *)attr->user_data; + if (inst->start_handle == 0) + { + inst->start_handle = chrc->value_handle; + } + + inst->end_handle = chrc->value_handle; + if (!bt_uuid_cmp(chrc->uuid, BT_UUID_VOCS_STATE)) + { + LOG_DBG("Volume offset state"); + inst->state_handle = chrc->value_handle; + sub_params = &inst->state_sub_params; + sub_params->disc_params = &inst->state_ccc_disc_params; + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_VOCS_LOCATION)) + { + LOG_DBG("Location"); + inst->location_handle = chrc->value_handle; + if (chrc->properties & BT_GATT_CHRC_NOTIFY) + { + sub_params = &inst->location_sub_params; + sub_params->disc_params = &inst->location_ccc_disc_params; + } + + if (chrc->properties & BT_GATT_CHRC_WRITE_WITHOUT_RESP) + { + atomic_set_bit(inst->flags, BT_VOCS_CLIENT_FLAG_LOC_WRITABLE); + } + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_VOCS_CONTROL)) + { + LOG_DBG("Control point"); + inst->control_handle = chrc->value_handle; + } + else if (!bt_uuid_cmp(chrc->uuid, BT_UUID_VOCS_DESCRIPTION)) + { + LOG_DBG("Description"); + inst->desc_handle = chrc->value_handle; + if (chrc->properties & BT_GATT_CHRC_NOTIFY) + { + sub_params = &inst->desc_sub_params; + sub_params->disc_params = &inst->desc_ccc_disc_params; + } + + if (chrc->properties & BT_GATT_CHRC_WRITE_WITHOUT_RESP) + { + atomic_set_bit(inst->flags, BT_VOCS_CLIENT_FLAG_DESC_WRITABLE); + } + } + + if (sub_params) + { + int err; + + sub_params->value = BT_GATT_CCC_NOTIFY; + sub_params->value_handle = chrc->value_handle; + + /* Use auto-discovery with the DEDICATED params we assigned above */ + + sub_params->ccc_handle = BT_GATT_AUTO_DISCOVER_CCC_HANDLE; + + /* Set the end handle to the end of the ENTIRE service discovery */ + + sub_params->end_handle = inst->discover_params.end_handle; + sub_params->notify = vocs_client_notify_handler; + atomic_set_bit(sub_params->flags, BT_GATT_SUBSCRIBE_FLAG_VOLATILE); + + err = bt_gatt_subscribe(conn, sub_params); + if (err != 0 && err != -EALREADY) + { + LOG_WRN("Could not subscribe to handle %u", + sub_params->ccc_handle); + + inst->cb->discover(&inst->vocs, err); + + return BT_GATT_ITER_STOP; + } + } + } + + return BT_GATT_ITER_CONTINUE; +} + +int bt_vocs_client_state_get(struct bt_vocs_client *inst) +{ + int err; + + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (inst->conn == NULL) + { + LOG_DBG("NULL conn"); + return -EINVAL; + } + + if (!inst->state_handle) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + + if (atomic_test_and_set_bit(inst->flags, BT_VOCS_CLIENT_FLAG_BUSY)) + { + LOG_DBG("Instance is busy"); + return -EBUSY; + } + + inst->read_params.func = vocs_client_read_offset_state_cb; + inst->read_params.handle_count = 1U; + inst->read_params.single.handle = inst->state_handle; + inst->read_params.single.offset = 0U; + + err = bt_gatt_read(inst->conn, &inst->read_params); + if (err != 0) + { + atomic_clear_bit(inst->flags, BT_VOCS_CLIENT_FLAG_BUSY); + } + + return err; +} + +int bt_vocs_client_location_set(struct bt_vocs_client *inst, + uint32_t location) +{ + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (inst->conn == NULL) + { + LOG_DBG("NULL conn"); + return -EINVAL; + } + + if (location > BT_AUDIO_LOCATION_ANY) + { + LOG_DBG("Invalid location 0x%08X", location); + return -EINVAL; + } + + if (!inst->location_handle) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + else if (!atomic_test_bit(inst->flags, BT_VOCS_CLIENT_FLAG_LOC_WRITABLE)) + { + LOG_DBG("Location is not writable on peer service instance"); + return -EPERM; + } + else if (atomic_test_bit(inst->flags, BT_VOCS_CLIENT_FLAG_BUSY)) + { + LOG_DBG("Instance is busy"); + return -EBUSY; + } + + /* When using write without response we do not set the busy flag */ + + return bt_gatt_write_without_response(inst->conn, inst->location_handle, + &location, + sizeof(location), false); +} + +int bt_vocs_client_location_get(struct bt_vocs_client *inst) +{ + int err; + + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (inst->conn == NULL) + { + LOG_DBG("NULL conn"); + return -EINVAL; + } + + if (!inst->location_handle) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + else if (atomic_test_and_set_bit(inst->flags, BT_VOCS_CLIENT_FLAG_BUSY)) + { + LOG_DBG("Instance is busy"); + return -EBUSY; + } + + inst->read_params.func = vocs_client_read_location_cb; + inst->read_params.handle_count = 1U; + inst->read_params.single.handle = inst->location_handle; + inst->read_params.single.offset = 0U; + + err = bt_gatt_read(inst->conn, &inst->read_params); + if (err != 0) + { + atomic_clear_bit(inst->flags, BT_VOCS_CLIENT_FLAG_BUSY); + } + + return err; +} + +int bt_vocs_client_state_set(struct bt_vocs_client *inst, int16_t offset) +{ + int err; + + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (inst->conn == NULL) + { + LOG_DBG("NULL conn"); + return -EINVAL; + } + + if (!IN_RANGE(offset, BT_VOCS_MIN_OFFSET, BT_VOCS_MAX_OFFSET)) + { + LOG_DBG("Invalid offset: %d", offset); + return -EINVAL; + } + + if (!inst->control_handle) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + else if (atomic_test_and_set_bit(inst->flags, BT_VOCS_CLIENT_FLAG_BUSY)) + { + LOG_DBG("Instance is busy"); + return -EBUSY; + } + + inst->cp.opcode = BT_VOCS_OPCODE_SET_OFFSET; + inst->cp.counter = inst->state.change_counter; + inst->cp.offset = offset; + + inst->write_params.offset = 0; + inst->write_params.data = &inst->cp; + inst->write_params.length = sizeof(inst->cp); + inst->write_params.handle = inst->control_handle; + inst->write_params.func = vocs_client_write_vocs_cp_cb; + + err = bt_gatt_write(inst->conn, &inst->write_params); + if (err != 0) + { + atomic_clear_bit(inst->flags, BT_VOCS_CLIENT_FLAG_BUSY); + } + + return err; +} + +int bt_vocs_client_description_get(struct bt_vocs_client *inst) +{ + int err; + + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (inst->conn == NULL) + { + LOG_DBG("NULL conn"); + return -EINVAL; + } + + if (!inst->desc_handle) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + else if (atomic_test_and_set_bit(inst->flags, BT_VOCS_CLIENT_FLAG_BUSY)) + { + LOG_DBG("Instance is busy"); + return -EBUSY; + } + + inst->read_params.func = vocs_client_read_output_desc_cb; + inst->read_params.handle_count = 1U; + inst->read_params.single.handle = inst->desc_handle; + inst->read_params.single.offset = 0U; + + err = bt_gatt_read(inst->conn, &inst->read_params); + if (err != 0) + { + atomic_clear_bit(inst->flags, BT_VOCS_CLIENT_FLAG_BUSY); + } + + return err; +} + +int bt_vocs_client_description_set(struct bt_vocs_client *inst, + const char *description) +{ + if (!inst) + { + LOG_DBG("NULL instance"); + return -EINVAL; + } + + if (inst->conn == NULL) + { + LOG_DBG("NULL conn"); + return -EINVAL; + } + + if (!inst->desc_handle) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + else if (!atomic_test_bit(inst->flags, BT_VOCS_CLIENT_FLAG_DESC_WRITABLE)) + { + LOG_DBG("Description is not writable on peer service instance"); + return -EPERM; + } + else if (atomic_test_bit(inst->flags, BT_VOCS_CLIENT_FLAG_BUSY)) + { + LOG_DBG("Instance is busy"); + return -EBUSY; + } + + /* When using write without response we do not set the busy flag */ + + return bt_gatt_write_without_response(inst->conn, inst->desc_handle, + description, + strlen(description), false); +} + +struct bt_vocs *bt_vocs_client_free_instance_get(void) +{ + for (int i = 0; i < ARRAY_SIZE(insts); i++) + { + if (!atomic_test_and_set_bit(insts[i].flags, + BT_VOCS_CLIENT_FLAG_ACTIVE)) + { + insts[i].vocs.client_instance = true; + return &insts[i].vocs; + } + } + + return NULL; +} + +int bt_vocs_client_conn_get(const struct bt_vocs *vocs, + struct bt_conn **conn) +{ + struct bt_vocs_client *inst; + + if (vocs == NULL) + { + LOG_DBG("NULL vocs pointer"); + return -EINVAL; + } + + if (!vocs->client_instance) + { + LOG_DBG("vocs pointer shall be client instance"); + return -EINVAL; + } + + inst = CONTAINER_OF(vocs, struct bt_vocs_client, vocs); + + if (inst->conn == NULL) + { + LOG_DBG("vocs pointer not associated with a connection. " + "Do discovery first"); + return -ENOTCONN; + } + + *conn = inst->conn; + return 0; +} + +static void vocs_client_reset(struct bt_vocs_client *inst) +{ + memset(&inst->state, 0, sizeof(inst->state)); + atomic_clear_bit(inst->flags, BT_VOCS_CLIENT_FLAG_LOC_WRITABLE); + atomic_clear_bit(inst->flags, BT_VOCS_CLIENT_FLAG_DESC_WRITABLE); + atomic_clear_bit(inst->flags, BT_VOCS_CLIENT_FLAG_CP_RETRIED); + + inst->location = 0U; + inst->start_handle = 0U; + inst->end_handle = 0U; + inst->state_handle = 0U; + inst->location_handle = 0U; + inst->control_handle = 0U; + inst->desc_handle = 0U; + + bt_conn_drop(&inst->conn); +} + +int bt_vocs_client_free_instance(struct bt_vocs *vocs) +{ + ARRAY_FOR_EACH_PTR(insts, inst) + { + if (vocs == &inst->vocs) + { + if (!atomic_test_bit(inst->flags, BT_VOCS_CLIENT_FLAG_ACTIVE)) + { + return -EALREADY; + } + + /* Test and set the BT_VOCS_CLIENT_FLAG_BUSY flag here to reduce, + * but not eliminate, chance of any operations happening while we + * are free'ing this instance. + */ + + if (atomic_test_and_set_bit(inst->flags, BT_VOCS_CLIENT_FLAG_BUSY)) + { + return -EBUSY; + } + + vocs_client_reset(inst); + + atomic_clear_bit(inst->flags, BT_VOCS_CLIENT_FLAG_BUSY); + atomic_clear_bit(inst->flags, BT_VOCS_CLIENT_FLAG_ACTIVE); + + return 0; + } + } + + return -EINVAL; +} + +int bt_vocs_discover(struct bt_conn *conn, struct bt_vocs *vocs, + const struct bt_vocs_discover_param *param) +{ + struct bt_vocs_client *inst; + struct bt_conn *ref; + int err = 0; + + if (!vocs || !conn || !param) + { + LOG_DBG("%s cannot be NULL", + vocs == NULL ? "vocs" : conn == NULL ? "conn" : "param"); + return -EINVAL; + } + + if (!vocs->client_instance) + { + LOG_DBG("vocs pointer shall be client instance"); + return -EINVAL; + } + + if (param->end_handle < param->start_handle) + { + LOG_DBG("start_handle (%u) shall be less than end_handle (%u)", + param->start_handle, + param->end_handle); + return -EINVAL; + } + + inst = CONTAINER_OF(vocs, struct bt_vocs_client, vocs); + + if (!atomic_test_bit(inst->flags, BT_VOCS_CLIENT_FLAG_ACTIVE)) + { + LOG_DBG("Inactive instance"); + return -EINVAL; + } + else if (atomic_test_and_set_bit(inst->flags, BT_VOCS_CLIENT_FLAG_BUSY)) + { + LOG_DBG("Instance is busy"); + return -EBUSY; + } + + ref = bt_conn_ref(conn); + if (ref == NULL) + { + err = -ENOTCONN; + goto cleanup; + } + + vocs_client_reset(inst); + + inst->discover_params.start_handle = param->start_handle; + inst->discover_params.end_handle = param->end_handle; + inst->discover_params.type = BT_GATT_DISCOVER_CHARACTERISTIC; + inst->discover_params.func = vocs_discover_func; + + inst->conn = ref; + + err = bt_gatt_discover(conn, &inst->discover_params); + if (err != 0) + { + LOG_DBG("Discover failed (err %d)", err); + bt_conn_unref(ref); + inst->conn = NULL; + goto cleanup; + } + + return 0; + +cleanup: + atomic_clear_bit(inst->flags, BT_VOCS_CLIENT_FLAG_BUSY); + return err; +} + +void bt_vocs_client_cb_register(struct bt_vocs *vocs, struct bt_vocs_cb *cb) +{ + struct bt_vocs_client *inst; + + if (!vocs) + { + LOG_DBG("inst cannot be NULL"); + return; + } + + if (!vocs->client_instance) + { + LOG_DBG("vocs pointer shall be client instance"); + return; + } + + inst = CONTAINER_OF(vocs, struct bt_vocs_client, vocs); + + inst->cb = cb; +} diff --git a/libs/zblue/audio/vocs_internal.h b/libs/zblue/audio/vocs_internal.h new file mode 100644 index 0000000000000..c10817a8a4243 --- /dev/null +++ b/libs/zblue/audio/vocs_internal.h @@ -0,0 +1,133 @@ +/**************************************************************************** + * libs/zblue/audio/vocs_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Internal APIs for Bluetooth VOCS. + * + * Copyright (c) 2020 Bose Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_VOCS_INTERNAL_ +#define ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_VOCS_INTERNAL_ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include + +#if defined(CONFIG_BT_VOCS) +#define BT_VOCS_MAX_DESC_SIZE CONFIG_BT_VOCS_MAX_OUTPUT_DESCRIPTION_SIZE +#else +#define BT_VOCS_MAX_DESC_SIZE 1U +#endif /* CONFIG_BT_VOCS */ + +/* VOCS opcodes */ +#define BT_VOCS_OPCODE_SET_OFFSET 0x01U + +struct bt_vocs_control +{ + uint8_t opcode; + uint8_t counter; + int16_t offset; +} __packed; + +struct bt_vocs_state +{ + int16_t offset; + uint8_t change_counter; +} __packed; + +struct bt_vocs +{ + bool client_instance; +}; + +enum bt_vocs_client_flag +{ + BT_VOCS_CLIENT_FLAG_BUSY, + BT_VOCS_CLIENT_FLAG_CP_RETRIED, + BT_VOCS_CLIENT_FLAG_DESC_WRITABLE, + BT_VOCS_CLIENT_FLAG_LOC_WRITABLE, + BT_VOCS_CLIENT_FLAG_ACTIVE, + + BT_VOCS_CLIENT_FLAG_NUM_FLAGS, /* keep as last */ +}; + +struct bt_vocs_client +{ + struct bt_vocs vocs; + struct bt_vocs_state state; + uint32_t location; + + uint16_t start_handle; + uint16_t end_handle; + uint16_t state_handle; + uint16_t location_handle; + uint16_t control_handle; + uint16_t desc_handle; + struct bt_gatt_subscribe_params state_sub_params; + struct bt_gatt_subscribe_params location_sub_params; + struct bt_gatt_subscribe_params desc_sub_params; + + /* Dedicated discovery params for concurrent CCC auto-discovery */ + + struct bt_gatt_discover_params state_ccc_disc_params; + struct bt_gatt_discover_params location_ccc_disc_params; + struct bt_gatt_discover_params desc_ccc_disc_params; + + struct bt_vocs_control cp; + struct bt_gatt_write_params write_params; + struct bt_gatt_read_params read_params; + struct bt_vocs_cb *cb; + struct bt_gatt_discover_params discover_params; + struct bt_conn *conn; + + atomic_define(flags, BT_VOCS_CLIENT_FLAG_NUM_FLAGS); +}; + +enum bt_vocs_notify +{ + NOTIFY_STATE, + NOTIFY_LOCATION, + NOTIFY_OUTPUT_DESC, + NOTIFY_NUM, +}; + +struct bt_vocs_server +{ + struct bt_vocs vocs; + struct bt_vocs_state state; + uint32_t location; + bool initialized; + char output_desc[BT_VOCS_MAX_DESC_SIZE]; + struct bt_vocs_cb *cb; + + struct bt_gatt_service *service_p; + + atomic_define(notify, NOTIFY_NUM); + struct k_work_delayable notify_work; +}; + +int bt_vocs_client_state_get(struct bt_vocs_client *inst); +int bt_vocs_client_state_set(struct bt_vocs_client *inst, int16_t offset); +int bt_vocs_client_location_get(struct bt_vocs_client *inst); +int bt_vocs_client_location_set(struct bt_vocs_client *inst, + uint32_t location); +int bt_vocs_client_description_get(struct bt_vocs_client *inst); +int bt_vocs_client_description_set(struct bt_vocs_client *inst, + const char *description); + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_AUDIO_VOCS_INTERNAL_ */ diff --git a/libs/zblue/common/CMakeLists.txt b/libs/zblue/common/CMakeLists.txt new file mode 100644 index 0000000000000..bee8509e2eacd --- /dev/null +++ b/libs/zblue/common/CMakeLists.txt @@ -0,0 +1,40 @@ +# ############################################################################## +# libs/zblue/common/CMakeLists.txt +# +# Author: chao an +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +if(CONFIG_BT) + set(BT_COMMON_SRCS + ${CMAKE_CURRENT_LIST_DIR}/addr.c ${CMAKE_CURRENT_LIST_DIR}/dummy.c + ${CMAKE_CURRENT_LIST_DIR}/bt_str.c ${CMAKE_CURRENT_LIST_DIR}/hci_pkt.c + ${CMAKE_CURRENT_LIST_DIR}/hci_lockstep.c) + + if(CONFIG_BT_HCI_SET_PUBLIC_ADDR) + list(APPEND BT_COMMON_SRCS ${CMAKE_CURRENT_LIST_DIR}/hci_public_addr.c) + endif() + + if(CONFIG_BT_RPA) + list(APPEND BT_COMMON_SRCS ${CMAKE_CURRENT_LIST_DIR}/rpa.c) + endif() + + target_sources(zblue PRIVATE ${BT_COMMON_SRCS}) +endif() diff --git a/libs/zblue/common/Kconfig b/libs/zblue/common/Kconfig new file mode 100644 index 0000000000000..b099b66db624a --- /dev/null +++ b/libs/zblue/common/Kconfig @@ -0,0 +1,388 @@ +############################################################################ +# libs/zblue/common/Kconfig +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth common configuration options + +# Copyright (c) 2017 Nordic Semiconductor ASA +# Copyright (c) 2016 Intel Corporation +# Copyright (c) 2026 Silicon Laboratories Inc. +# SPDX-License-Identifier: Apache-2.0 + +menu "Bluetooth buffer configuration" + +config BT_BUF_ACL_TX_SIZE + int "Maximum supported ACL size for outgoing data" + range 27 65535 + default 37 if BT_MESH_GATT + default 27 + help + Maximum supported ACL size of data packets sent from the Host to the + Controller. This value does not include the HCI ACL header. + + In a combined build this value will be set in both the Host and the + Controller. + + In a Host-only build the Host will read the maximum ACL size supported + by the Controller and use the smallest value supported by both the + Host and the Controller. + + The Host supports sending of larger L2CAP PDUs than the ACL size and + will fragment L2CAP PDUs into ACL data packets. + The Controller will return this value in the HCI LE Read Buffer + Size command response. If this size if greater than effective Link + Layer transmission size then the Controller will perform + fragmentation before transmitting the packet(s) on air. + If this value is less than the effective Link Layer transmission size + then this will restrict the maximum Link Layer transmission size. + +config BT_BUF_ACL_TX_COUNT + int "Number of outgoing ACL data buffers" + default 7 if BT_HCI_RAW + default 4 if BT_MESH_GATT + default 3 + range 1 255 + help + Number of outgoing ACL data buffers sent from the Host to the + Controller. This determines the maximum amount of data packets the + Host can have queued in the Controller before waiting for the + to notify the Host that more packets can be queued with the Number of + Completed Packets event. + The buffers are shared between all of the connections and the Host + determines how to divide the buffers between the connections. + The Controller will return this value in the HCI LE Read Buffer Size + command response. + +config BT_BUF_ACL_RX_SIZE + int "Maximum supported ACL size for incoming data" + # For a HID Host in Report Protocol Mode, the minimum MTU is 672 octets + # (HID spec v1.1.2 Section 5.2.3.1), so the buffer holds 676 bytes, + # including, + # 672 bytes - the minimum MTU of a HID Host in Report Protocol Mode, + # 4 bytes - L2CAP Header. + default 676 if BT_CLASSIC && BT_HID_HOST + # For GOEP over RFCOMM, the minimum MTU of GOEP is 264, including, + # 255 bytes - the minimum MTU of GOEP, + # 4 bytes - L2CAP Header, + # 5 bytes - 4 bytes for RFCOMM header with extended length, 1 byte for FCS. + # For GOEP over L2CAP, the minimum MTU of GOEP is 265, including, + # 255 bytes - the minimum MTU of GOEP, + # 4 bytes - L2CAP Header, + # 6 bytes - Field length of L2CAP I-frame, including, + # 4 bytes - extended control field, + # 2 bytes - FCS field. + default 265 if BT_CLASSIC && BT_GOEP + default 200 if BT_CLASSIC + default 70 if BT_EATT + default 69 if BT_SMP + default 37 if BT_MESH_GATT + default 27 + range 676 65535 if BT_CLASSIC && BT_HID_HOST + range 264 65535 if BT_CLASSIC && BT_GOEP + range 70 65535 if BT_EATT + range 69 65535 if BT_SMP + range 27 65535 + help + Maximum support ACL size of data packets sent from the Controller to + the Host. This value does not include the HCI ACL header. + + In a combined Host and Controller build, both the + Host and the Controller will use this value for buffer sizes, making + Controller to Host flow control not strictly necessary. + + In a Host only build with Controller to Host flow control enabled + the Host will inform the Controller about the maximum ACL data size it + can send by setting this value in the Host Buffer Size command. + + If Controller to Host flow control is not enabled then the Controller + can assume the Host has to receive and process ACL data faster than + the controller can produce it. + + In a Controller only build this will determine the maximum ACL size + that the Controller will send to the Host. + +config BT_BUF_ACL_RX_COUNT_EXTRA + # As Host implementation unconditionally references this Kconfig, we + # hide it for !BT_CONN and default to 0. + int "Number of extra incoming ACL data buffers" if BT_CONN + range 1 65535 if BT_CONN + default BT_MAX_CONN if BT_CONN + range 0 0 + default 0 + help + Number of incoming extra ACL data buffers sent from the Controller to + the Host. + + By default, the total number of incoming ACL data buffers is equal to + CONFIG_BT_MAX_CONN + 1, to support L2CAP recombination. + + The L2CAP recombination in the Host keeps the first Rx buffer for each + connection and uses one Rx buffer across all connections to receive a + fragment from the Controller. + +config BT_BUF_EVT_RX_SIZE + int "Maximum supported HCI Event buffer length" + default 255 if (BT_EXT_ADV && BT_OBSERVER) || BT_PER_ADV_SYNC || BT_DF_CONNECTION_CTE_RX || BT_CLASSIC || BT_CHANNEL_SOUNDING || BT_LE_EXTENDED_FEAT_SET + # LE Read Supported Commands command complete event. + default 68 + range 68 255 + range 78 255 if BT_CHANNEL_SOUNDING + help + Maximum supported HCI event buffer size. This value does not include + the HCI Event header. + This value is used by both the Host and the Controller for buffer + sizes that include HCI events. It should be set according to the + expected HCI events that can be generated from the configuration. + If the subset of possible HCI events is unknown, this should be set to + the maximum of 255. + +config BT_BUF_EVT_RX_COUNT + int "Number of HCI Event buffers" + default 20 if (BT_MESH && !(BT_BUF_EVT_DISCARDABLE_COUNT > 0)) + default 10 + range 2 255 + help + Number of buffers available for incoming HCI events from the + Controller. + +config BT_BUF_CMD_TX_SIZE + int "Maximum support HCI Command buffer length" + default 255 if (BT_EXT_ADV || BT_CLASSIC || BT_ISO_CENTRAL || BT_CHANNEL_SOUNDING) + # LE Generate DHKey v2 command + default 65 + range 65 255 + help + Maximum data size for each HCI Command buffer. This value does not + include the HCI Command header. + This value is used by both the Host and the Controller for buffer + sizes that include HCI commands. It should be set according to the + expected HCI commands that can be sent from the configuration. + If the subset of possible HCI commands is unknown, this should be set + to the maximum of 255. + +config BT_BUF_CMD_TX_COUNT + # This option is only visible for a user when Host and Controller are build together, or for + # Host-only builds. + int "Number of HCI command buffers" if !BT_HCI_RAW + # This option is present when Host and Controller are build together, or + # for Host only builds, or when Controller-to-Host ACL data flow control + # is disabled. + depends on !BT_HCI_RAW || !BT_HCI_ACL_FLOW_CONTROL + # The Mesh stack usage in applications and tests can start both advertising and scanning in + # parallel. Also, when BT_MESH_WORKQ_SYS is enabled, the Mesh stack is starting Extended + # Advertising in the receive callback run in the system work queue and as the Host also uses + # the system work queue for HCI command Tx towards the Controller, one additional HCI + # command buffer is needed. + range 2 64 if BT_MESH + range 1 64 + default 2 if BT_MESH + default 1 + help + Number of buffers available for outgoing HCI commands from the Host. + + HCI Controllers may not support Num_HCI_Command_Packets > 1, hence they default to 1 when + not enabling Controller to Host data flow control (BT_HCI_ACL_FLOW_CONTROL), Read Remote + Version Information (BT_REMOTE_VERSION), Auto-Initiate PHY update + (BT_AUTO_PHY_CENTRAL and BT_AUTO_PHY_PERIPHERAL choices), or Auto-Initiate Data Length + Update (BT_AUTO_DATA_LEN_UPDATE). + + Normal HCI commands follow the HCI command flow control using Num_HCI_Command_Packets + return in HCI command complete and status. + + The Host Number of Completed Packets command does not follow normal flow control of HCI + commands and the Controller side HCI drivers that allocates HCI command buffers with + K_NO_WAIT can end up running out of command buffers. + + When Controller to Host data flow control is enabled in the Host-only or combined + Host plus Controller build, the internal BT_BUF_CMD_TX_COUNT is adjusted accordingly + taking into considerations the enabled auto-initiated procedures, and to follow the normal + HCI command flow control to be able to send Ncmd normal HCI commands and + BT_BUF_ACL_RX_COUNT of Host Number of Completed Packets command down to the Controller. + + When Controller to Host data flow control is supported in the Controller-only build, + the internal BT_BUF_CMD_TX_COUNT is adjusted to be equal to (BT_BUF_RX_COUNT + Ncmd). + + Where Ncmd is supported maximum Num_HCI_Command_Packets in the Controller implementation. + +endmenu + +# Workaround to have commas on function arguments +ZEPHYR_BT_HCI := zephyr,bt-hci + +config BT_HAS_HCI_VS + bool + default n + help + This option is set by the Bluetooth controller to indicate support + for the HCI Vendor-Specific Commands and Event. + +config BT_HCI_VS + bool "HCI Vendor-Specific Commands" + default y if BT_HAS_HCI_VS + help + Enable support for the HCI Vendor-Specific Commands in the + Host and/or Controller. This enables Set Version Information, + Supported Commands, Supported Features vendor commands. + +config BT_HCI_VS_FATAL_ERROR_SUPPORT + bool + +config BT_HCI_VS_FATAL_ERROR + bool "Allow vendor specific HCI event NuttX Fatal Error" + depends on BT_HCI_VS && BT_HCI_VS_FATAL_ERROR_SUPPORT + help + Enable emitting HCI Vendor-Specific events for system and Controller + errors that are unrecoverable. + +config BT_HCI_VS_EXT_DETECT + bool "Use heuristics to guess HCI vendor extensions support in advance" + depends on BT_HCI_VS + default y if BOARD_QEMU_X86 || BOARD_QEMU_CORTEX_M3 || BOARD_NATIVE_SIM + help + Use some heuristics to try to guess in advance whether the controller + supports the HCI vendor extensions in advance, in order to prevent + sending vendor commands to controller which may interpret them in + completely different ways. + +config BT_HCI_MESH_EXT + bool "Mesh HCI Command support" + depends on BT_BROADCASTER && BT_OBSERVER + help + Enable support for the Bluetooth Mesh HCI Commands. + +config BT_WAIT_NOP + bool "Wait for \"NOP\" Command Complete event during init" + help + Emit a Command Complete event from the Controller (and wait for it + from the Host) for the NOP opcode to indicate that the Controller is + ready to receive commands. + +config BT_RPA + bool + depends on BT_HOST_CRYPTO + +config BT_ASSERT + bool "Custom Bluetooth assert implementation" + default y + help + Use a custom Bluetooth assert implementation instead of the + kernel-wide __ASSERT() when CONFIG_ASSERT is disabled. + +if BT_ASSERT + +config BT_ASSERT_VERBOSE + bool "Print out an assert string when using BT_ASSERT" + default y + help + When CONFIG_BT_ASSERT is enabled, this option turns on printing the + cause of the assert to the console using printk(). + +endif # BT_ASSERT + +config BT_HCI_ERR_TO_STR + bool "Print HCI error codes as strings [EXPERIMENTAL]" + help + This configuration enables printing of HCI error + codes represented as strings. + See bt_hci_err_to_str() for more details. + +config BT_MONITOR + bool "Bluetooth monitor protocol support" + select LOG_OUTPUT + help + Enable Bluetooth monitor protocol support. When this option is + enabled, the host monitor implementation is compiled in and can + emit monitor records through the configured monitor backend. + +choice BT_DEBUG_TYPE + prompt "Bluetooth debug type" + default BT_DEBUG_NONE + +config BT_DEBUG_NONE + bool "No debug log" + help + Select this to disable all Bluetooth debug logs. + +config BT_DEBUG_MONITOR_UART + bool "Monitor protocol over UART" + select LOG + select CONSOLE_HAS_DRIVER + select BT_MONITOR + help + Use a custom logging protocol over the console UART + instead of plain-text output. Requires a special application + on the host side that can decode this protocol. Currently + the 'btmon' tool from BlueZ is capable of doing this. + + If the target board has two or more external UARTs it is + possible to keep using UART_CONSOLE together with this option, + however if there is only a single external UART then + UART_CONSOLE needs to be disabled (in which case printk/printf + will get encoded into the monitor protocol). + +if BT_DEBUG_MONITOR_UART + +config BT_DEBUG_MONITOR_UART_INTERRUPT_DRIVEN + bool "Interrupt-driven UART monitor output" + depends on SERIAL_SUPPORT_INTERRUPT + # Minimal logging has no panic hook, so buffered records could not be + # flushed during a fatal error and crash output would be lost. + depends on !LOG_MODE_MINIMAL + select UART_INTERRUPT_DRIVEN + help + Buffer complete monitor records and transmit them from the UART + interrupt handler. This avoids blocking the Bluetooth host while + waiting for each byte to be transmitted. Records which cannot fit + in the buffer are dropped and reported in the next monitor record. + +config BT_DEBUG_MONITOR_UART_BUFFER_SIZE + int "UART monitor transmit buffer size" + default 1024 + range 64 32767 + depends on BT_DEBUG_MONITOR_UART_INTERRUPT_DRIVEN + help + Size in bytes of the circular buffer used for interrupt-driven monitor + output. A record larger than the buffer can never be sent and is + always dropped, so make sure the buffer can hold the largest + monitor record: the largest HCI packet plus up to 25 bytes of + monitor header. + +endif # BT_DEBUG_MONITOR_UART + +endchoice # Bluetooth debug type + +config BT_LONG_WQ + bool "Dedicated workqueue for long-running tasks." + default y if BT_GATT_CACHING + default y if BT_ECC + help + Adds an API for a workqueue dedicated to long-running tasks. + +if BT_LONG_WQ +config BT_LONG_WQ_STACK_SIZE + int "Long workqueue stack size." + default 4096 if NO_OPTIMIZATIONS + default 1400 if BT_ECC + default 1300 if BT_GATT_CACHING + default 1024 + help + The stack size used for the long workqueue. In most cases it's best to use the default + values, however since the actual requirement depends on the underlying crypto backend + this option can be used to finetune the stack size. + +config BT_LONG_WQ_PRIO + int "Long workqueue priority. Should be preemptible." + default 10 + range 0 255 + +config BT_LONG_WQ_INIT_PRIO + int "Long workqueue init priority" + default 50 + help + Init priority level to setup the long workqueue. + +endif # BT_LONG_WQ diff --git a/libs/zblue/common/Make.defs b/libs/zblue/common/Make.defs new file mode 100644 index 0000000000000..f01d621bcd888 --- /dev/null +++ b/libs/zblue/common/Make.defs @@ -0,0 +1,44 @@ +############################################################################ +# libs/zblue/common/Make.defs +# +# Author: chao an +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +ifeq ($(CONFIG_BT),y) + +BT_CSRCS += $(TOPDIR)/libs/zblue/common/addr.c +BT_CSRCS += $(TOPDIR)/libs/zblue/common/dummy.c +BT_CSRCS += $(TOPDIR)/libs/zblue/common/bt_str.c +BT_CSRCS += $(TOPDIR)/libs/zblue/common/hci_pkt.c +BT_CSRCS += $(TOPDIR)/libs/zblue/common/hci_lockstep.c + +ifeq ($(CONFIG_BT_HCI_SET_PUBLIC_ADDR),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/common/hci_public_addr.c +endif + +ifeq ($(CONFIG_BT_RPA),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/common/rpa.c +endif + +VPATH += $(TOPDIR)/libs/zblue/common +DEPPATH += --dep-path $(TOPDIR)/libs/zblue/common + +endif diff --git a/libs/zblue/common/addr.c b/libs/zblue/common/addr.c new file mode 100644 index 0000000000000..4df5b5b1b307a --- /dev/null +++ b/libs/zblue/common/addr.c @@ -0,0 +1,70 @@ +/**************************************************************************** + * libs/zblue/common/addr.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Stephanos Ioannidis + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +const bt_addr_t bt_addr_any = +{ + { + 0, 0, 0, 0, 0, 0 + } +}; +const bt_addr_t bt_addr_none = +{ + { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + } +}; +const bt_addr_le_t bt_addr_le_any = +{ + 0, + { + { + 0, 0, 0, 0, 0, 0 + } + } +}; +const bt_addr_le_t bt_addr_le_none = +{ + 0, + { + { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + } + } +}; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +struct bt_addr_tmp_str bt_addr_tmp_str(const bt_addr_t *addr) +{ + struct bt_addr_tmp_str val; + + (void)bt_addr_to_str(addr, val.str, sizeof(val.str)); + + return val; +} + +struct bt_addr_le_tmp_str bt_addr_le_tmp_str(const bt_addr_le_t *addr) +{ + struct bt_addr_le_tmp_str val; + + (void)bt_addr_le_to_str(addr, val.str, sizeof(val.str)); + + return val; +} diff --git a/libs/zblue/common/assert.h b/libs/zblue/common/assert.h new file mode 100644 index 0000000000000..933f84e3c6541 --- /dev/null +++ b/libs/zblue/common/assert.h @@ -0,0 +1,20 @@ +/**************************************************************************** + * libs/zblue/common/assert.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#define BT_ASSERT(cond) DEBUGASSERT(cond) +#define BT_ASSERT_MSG(cond, msg, ...) \ + ((void)((cond) || (__assert(__ASSERT_FILE__, __ASSERT_LINE__, msg), 0))) diff --git a/libs/zblue/common/bt_settings_commit.h b/libs/zblue/common/bt_settings_commit.h new file mode 100644 index 0000000000000..5e8564ecc0177 --- /dev/null +++ b/libs/zblue/common/bt_settings_commit.h @@ -0,0 +1,28 @@ +/**************************************************************************** + * libs/zblue/common/bt_settings_commit.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2024 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef BT_SETTINGS_COMMIT_H_ +#define BT_SETTINGS_COMMIT_H_ + +/** + * @brief Bluetooth Settings Commit Priorities + * + * Enum of commit priorities for Bluetooth settings handlers. Lower values + * indicate higher priority. + */ + +enum bt_settings_commit_priority +{ + BT_SETTINGS_CPRIO_0, + BT_SETTINGS_CPRIO_1, + BT_SETTINGS_CPRIO_2, +}; + +#endif /* BT_SETTINGS_COMMIT_H_ */ diff --git a/libs/zblue/common/bt_str.c b/libs/zblue/common/bt_str.c new file mode 100644 index 0000000000000..c3f1ccad9a2ab --- /dev/null +++ b/libs/zblue/common/bt_str.c @@ -0,0 +1,154 @@ +/**************************************************************************** + * libs/zblue/common/bt_str.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Helper for printk parameters to convert from binary to hex. + * We declare multiple buffers so the helper can be used multiple times in a + * single printk call. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +const char *bt_hex(const void *buf, size_t len) +{ + static const char hex[] = "0123456789abcdef"; + static char str[129]; + const uint8_t *b = buf; + size_t i; + + len = MIN(len, (sizeof(str) - 1) / 2); + + for (i = 0; i < len; i++) + { + str[i * 2] = hex[b[i] >> 4]; + str[i * 2 + 1] = hex[b[i] & 0xf]; + } + + str[i * 2] = '\0'; + + return str; +} + +const char *bt_uuid_str(const struct bt_uuid *uuid) +{ + static char str[BT_UUID_STR_LEN]; + + bt_uuid_to_str(uuid, str, sizeof(str)); + + return str; +} + +#if defined(CONFIG_BT_HCI_ERR_TO_STR) +const char *bt_hci_err_to_str(uint8_t hci_err) +{ +#define HCI_ERR(err) [err] = #err + + const char *const mapping_table[] = + { + HCI_ERR(BT_HCI_ERR_SUCCESS), + HCI_ERR(BT_HCI_ERR_UNKNOWN_CMD), + HCI_ERR(BT_HCI_ERR_UNKNOWN_CONN_ID), + HCI_ERR(BT_HCI_ERR_HW_FAILURE), + HCI_ERR(BT_HCI_ERR_PAGE_TIMEOUT), + HCI_ERR(BT_HCI_ERR_AUTH_FAIL), + HCI_ERR(BT_HCI_ERR_PIN_OR_KEY_MISSING), + HCI_ERR(BT_HCI_ERR_MEM_CAPACITY_EXCEEDED), + HCI_ERR(BT_HCI_ERR_CONN_TIMEOUT), + HCI_ERR(BT_HCI_ERR_CONN_LIMIT_EXCEEDED), + HCI_ERR(BT_HCI_ERR_SYNC_CONN_LIMIT_EXCEEDED), + HCI_ERR(BT_HCI_ERR_CONN_ALREADY_EXISTS), + HCI_ERR(BT_HCI_ERR_CMD_DISALLOWED), + HCI_ERR(BT_HCI_ERR_INSUFFICIENT_RESOURCES), + HCI_ERR(BT_HCI_ERR_INSUFFICIENT_SECURITY), + HCI_ERR(BT_HCI_ERR_BD_ADDR_UNACCEPTABLE), + HCI_ERR(BT_HCI_ERR_CONN_ACCEPT_TIMEOUT), + HCI_ERR(BT_HCI_ERR_UNSUPP_FEATURE_PARAM_VAL), + HCI_ERR(BT_HCI_ERR_INVALID_PARAM), + HCI_ERR(BT_HCI_ERR_REMOTE_USER_TERM_CONN), + HCI_ERR(BT_HCI_ERR_REMOTE_LOW_RESOURCES), + HCI_ERR(BT_HCI_ERR_REMOTE_POWER_OFF), + HCI_ERR(BT_HCI_ERR_LOCALHOST_TERM_CONN), + HCI_ERR(BT_HCI_ERR_REPEATED_ATTEMPTS), + HCI_ERR(BT_HCI_ERR_PAIRING_NOT_ALLOWED), + HCI_ERR(BT_HCI_ERR_UNKNOWN_LMP_PDU), + HCI_ERR(BT_HCI_ERR_UNSUPP_REMOTE_FEATURE), + HCI_ERR(BT_HCI_ERR_SCO_OFFSET_REJECTED), + HCI_ERR(BT_HCI_ERR_SCO_INTERVAL_REJECTED), + HCI_ERR(BT_HCI_ERR_SCO_AIR_MODE_REJECTED), + HCI_ERR(BT_HCI_ERR_INVALID_LL_PARAM), + HCI_ERR(BT_HCI_ERR_UNSPECIFIED), + HCI_ERR(BT_HCI_ERR_UNSUPP_LL_PARAM_VAL), + HCI_ERR(BT_HCI_ERR_ROLE_CHANGE_NOT_ALLOWED), + HCI_ERR(BT_HCI_ERR_LL_RESP_TIMEOUT), + HCI_ERR(BT_HCI_ERR_LL_PROC_COLLISION), + HCI_ERR(BT_HCI_ERR_LMP_PDU_NOT_ALLOWED), + HCI_ERR(BT_HCI_ERR_ENC_MODE_NOT_ACCEPTABLE), + HCI_ERR(BT_HCI_ERR_LINK_KEY_CANNOT_BE_CHANGED), + HCI_ERR(BT_HCI_ERR_REQUESTED_QOS_NOT_SUPPORTED), + HCI_ERR(BT_HCI_ERR_INSTANT_PASSED), + HCI_ERR(BT_HCI_ERR_PAIRING_NOT_SUPPORTED), + HCI_ERR(BT_HCI_ERR_DIFF_TRANS_COLLISION), + HCI_ERR(BT_HCI_ERR_QOS_UNACCEPTABLE_PARAM), + HCI_ERR(BT_HCI_ERR_QOS_REJECTED), + HCI_ERR(BT_HCI_ERR_CHAN_ASSESS_NOT_SUPPORTED), + HCI_ERR(BT_HCI_ERR_INSUFF_SECURITY), + HCI_ERR(BT_HCI_ERR_PARAM_OUT_OF_MANDATORY_RANGE), + HCI_ERR(BT_HCI_ERR_ROLE_SWITCH_PENDING), + HCI_ERR(BT_HCI_ERR_RESERVED_SLOT_VIOLATION), + HCI_ERR(BT_HCI_ERR_ROLE_SWITCH_FAILED), + HCI_ERR(BT_HCI_ERR_EXT_INQ_RESP_TOO_LARGE), + HCI_ERR(BT_HCI_ERR_SIMPLE_PAIR_NOT_SUPP_BY_HOST), + HCI_ERR(BT_HCI_ERR_HOST_BUSY_PAIRING), + HCI_ERR(BT_HCI_ERR_CONN_REJECTED_DUE_TO_NO_CHAN), + HCI_ERR(BT_HCI_ERR_CONTROLLER_BUSY), + HCI_ERR(BT_HCI_ERR_UNACCEPT_CONN_PARAM), + HCI_ERR(BT_HCI_ERR_ADV_TIMEOUT), + HCI_ERR(BT_HCI_ERR_TERM_DUE_TO_MIC_FAIL), + HCI_ERR(BT_HCI_ERR_CONN_FAIL_TO_ESTAB), + HCI_ERR(BT_HCI_ERR_MAC_CONN_FAILED), + HCI_ERR(BT_HCI_ERR_CLOCK_ADJUST_REJECTED), + HCI_ERR(BT_HCI_ERR_SUBMAP_NOT_DEFINED), + HCI_ERR(BT_HCI_ERR_UNKNOWN_ADV_IDENTIFIER), + HCI_ERR(BT_HCI_ERR_LIMIT_REACHED), + HCI_ERR(BT_HCI_ERR_OP_CANCELLED_BY_HOST), + HCI_ERR(BT_HCI_ERR_PACKET_TOO_LONG), + HCI_ERR(BT_HCI_ERR_TOO_LATE), + HCI_ERR(BT_HCI_ERR_TOO_EARLY), + }; + + if (hci_err < ARRAY_SIZE(mapping_table) && mapping_table[hci_err]) + { + return mapping_table[hci_err]; + } + else + { + return "(unknown)"; + } + +#undef HCI_ERR +} +#endif /* CONFIG_BT_HCI_ERR_TO_STR */ diff --git a/libs/zblue/common/bt_str.h b/libs/zblue/common/bt_str.h new file mode 100644 index 0000000000000..b34b24dacfed3 --- /dev/null +++ b/libs/zblue/common/bt_str.h @@ -0,0 +1,117 @@ +/**************************************************************************** + * libs/zblue/common/bt_str.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __LIBS_ZBLUE_COMMON_BT_STR_H +#define __LIBS_ZBLUE_COMMON_BT_STR_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include + +/* NOTE: These helper functions always encodes into the same buffer storage. + * It is the responsibility of the user of this function to copy the + * information in this string if needed. + * + * NOTE: These functions are not thread-safe! + */ + +const char *bt_hex(const void *buf, size_t len); +const char *bt_uuid_str(const struct bt_uuid *uuid); + +static inline char *utf8_trunc(char *utf8_str) +{ + mbstate_t state = + { + 0 + }; + char *p = utf8_str; + + if (utf8_str == NULL) + { + return NULL; + } + + while (*p != '\0') + { + size_t len = mbrtowc(NULL, p, strlen(p), &state); + + if (len == (size_t)-1 || len == (size_t)-2) + { + *p = '\0'; + break; + } + + if (len == 0) + { + break; + } + + p += len; + } + + return utf8_str; +} + +static inline char *utf8_lcpy(char *dst, const char *src, size_t n) +{ + if (n == 0) + { + return dst; + } + + strncpy(dst, src, n - 1); + dst[n - 1] = '\0'; + + return utf8_trunc(dst); +} + +static inline int utf8_count_chars(const char *s) +{ + mbstate_t state = + { + 0 + }; + int count = 0; + + if (s == NULL) + { + return 0; + } + + while (*s != '\0') + { + size_t len = mbrtowc(NULL, s, strlen(s), &state); + + if (len == (size_t)-1 || len == (size_t)-2) + { + return -1; + } + + if (len == 0) + { + break; + } + + s += len; + count++; + } + + return count; +} + +#endif /* __LIBS_ZBLUE_COMMON_BT_STR_H */ diff --git a/libs/zblue/common/dummy.c b/libs/zblue/common/dummy.c new file mode 100644 index 0000000000000..db3b7839bc83b --- /dev/null +++ b/libs/zblue/common/dummy.c @@ -0,0 +1,73 @@ +/**************************************************************************** + * libs/zblue/common/dummy.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file dummy.c Static compilation checks. + */ + +/* Copyright (c) 2017 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/* The unpacked structs below are used inside __packed structures that + * reflect what goes on the wire. While alignment is not a problem for those, + * since all their members are bytes or byte arrays, the size is. They must + * not be padded by the compiler, otherwise the on-wire packet will not map + * the packed structure correctly. + * + * The bt_addr structs are not marked __packed because it's considered ugly + * by some. But here is a proof that the structs have all the properties of, + * and can be safely used as packed structs. + */ + +BUILD_ASSERT(sizeof(bt_addr_t) == BT_ADDR_SIZE); +BUILD_ASSERT(alignof(bt_addr_t) == 1); +BUILD_ASSERT(sizeof(bt_addr_le_t) == BT_ADDR_LE_SIZE); +BUILD_ASSERT(alignof(bt_addr_le_t) == 1); + +#if defined(CONFIG_BT_HCI_HOST) +/* The Bluetooth subsystem requires that higher priority events shall be + * given in a priority higher than the Bluetooth Host's Tx and the + * Controller's receive thread priority. This is required in order to + * dispatch Number of Completed Packets event before any new data arrives on + * a connection to the Host threads. + */ + +BUILD_ASSERT(CONFIG_BT_DRIVER_RX_HIGH_PRIO < CONFIG_BT_HCI_TX_PRIO); +#endif /* defined(CONFIG_BT_HCI_HOST) */ + +#if (defined(CONFIG_LOG_BACKEND_RTT) && (defined(CONFIG_SEGGER_RTT_MODE_BLOCK_IF_FIFO_FULL) || \ + defined(CONFIG_LOG_BACKEND_RTT_MODE_BLOCK))) || \ + defined(CONFIG_LOG_BACKEND_NET) || defined(CONFIG_LOG_IMMEDIATE_CLEAN_OUTPUT) +#define INCOMPATIBLE_IMMEDIATE_LOG_BACKEND 1 +#endif + +/* Immediate logging on most backend is not supported + * with the software-based Link Layer since it introduces ISR latency due to + * outputting log messages with interrupts disabled. + */ +#if !defined(CONFIG_TEST) && !defined(CONFIG_ARCH_POSIX) && defined(CONFIG_BT_LL_SW_SPLIT) && \ + defined(INCOMPATIBLE_IMMEDIATE_LOG_BACKEND) +BUILD_ASSERT(!IS_ENABLED(CONFIG_LOG_MODE_IMMEDIATE), + "Immediate logging on selected backend(s) not supported with " + "the software Link Layer"); +#endif diff --git a/libs/zblue/common/hci_common_internal.h b/libs/zblue/common/hci_common_internal.h new file mode 100644 index 0000000000000..801a6f5c58592 --- /dev/null +++ b/libs/zblue/common/hci_common_internal.h @@ -0,0 +1,118 @@ +/**************************************************************************** + * libs/zblue/common/hci_common_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#if !defined(CONFIG_BT_HCI_RAW) || !defined(CONFIG_HAS_BT_CTLR) || \ + !defined(CONFIG_BT_HCI_ACL_FLOW_CONTROL) +/* Following build configurations use configurable + * CONFIG_BT_BUF_CMD_TX_COUNT: 1. Host + Controller build with and without + * Controller to Host data flow control, or 2. Host-only with and without + * Controller to Host data flow control, or 3. Controller-only without + * Controller to Host data flow control support + */ +#if !defined(CONFIG_BT_HCI_RAW) +/* Host + Controller build, and Host-only build */ + +/* Auto initiated additional HCI command buffers enqueued in the Host */ +#define BT_BUF_CMD_TX_REMOTE_VERSION COND_CODE_1(CONFIG_BT_REMOTE_VERSION, (1), (0)) +#define BT_BUF_CMD_TX_AUTO_PHY_UPDATE \ + (IS_ENABLED(CONFIG_BT_PHY_UPDATE) && (!IS_ENABLED(CONFIG_BT_AUTO_PHY_PERIPHERAL_NONE) || \ + !IS_ENABLED(CONFIG_BT_AUTO_PHY_CENTRAL_NONE))) +#define BT_BUF_CMD_TX_AUTO_DATA_LEN_UPDATE COND_CODE_1(CONFIG_BT_AUTO_DATA_LEN_UPDATE, (1), (0)) + +#else /* CONFIG_BT_HCI_RAW */ +#if defined(CONFIG_HAS_BT_CTLR) +/* Controller-only build need no additional HCI command buffers */ + +BUILD_ASSERT(CONFIG_BT_BUF_CMD_TX_COUNT == + CONFIG_BT_CTLR_HCI_NUM_CMD_PKT_MAX, + "Mismatch in allocated HCI command buffers compared to " + "Controller supported value."); +#endif /* CONFIG_HAS_BT_CTLR */ + +/* Controller-only build do not enqueue auto initiated HCI commands */ +#define BT_BUF_CMD_TX_REMOTE_VERSION 0 +#define BT_BUF_CMD_TX_AUTO_PHY_UPDATE 0 +#define BT_BUF_CMD_TX_AUTO_DATA_LEN_UPDATE 0 +#endif /* !CONFIG_BT_HCI_RAW */ + +#if defined(CONFIG_BT_HCI_ACL_FLOW_CONTROL) +/* When Controller to Host data flow control is supported in Host plus + * Controller build, or Host-only build, then we need additional + * BT_BUF_ACL_RX_COUNT number of HCI command buffers for enqueuing Host + * Number of Completed Packets command. + * + * Host keeps the first, and subsequent, Rx buffers (that comes from the + * driver) for each connection to do re-assembly into, up to the L2CAP SDU + * length required number of Rx buffers. BT_BUF_ACL_RX_COUNT_EXTRA holds the + * application configured number of buffers across active connections for + * recombination of HCI data packets to L2CAP SDUs. + * + * BT_BUF_HCI_EVT_RX_COUNT defines the number of available buffers reserved + * for "synchronous" processing of HCI events like Number of Completed + * Packets, disconnection complete etc. + * + * BT_BUF_HCI_ACL_RX_COUNT defines the number of available buffers for + * Controller to Host data flow control; keeping the application configured + * BT_BUF_ACL_RX_COUNT_EXTRA number of buffers available for L2CAP + * recombination, and a reserved number of buffers for processing HCI events. + */ + +/* FIXME: Calculate the maximum number of HCI events of different types that + * a connection can enqueue while the Host is slow at processing HCI events. + * + * 1. Disconnection Complete event 2. LE Connection Update Complete event 3. + * LE Long Term Key Request event 4. LE Remote Connection Parameter Request + * Event 5. LE Data Length Change event 6. LE PHY Update Complete event 7. + * ... + * + * As there is no HCI event flow control defined, implementations will need a + * transport level flow control to restrict buffers required on resource + * constraint devices, i.e. if these events are not processed "synchronous". + */ +#define BT_BUF_HCI_EVT_RX_COUNT 1 +#define BT_BUF_HCI_ACL_RX_COUNT \ + (BT_BUF_RX_COUNT - BT_BUF_HCI_EVT_RX_COUNT - BT_BUF_ACL_RX_COUNT_EXTRA) +#define BT_BUF_CMD_TX_HOST_NUM_CMPLT_PKT (BT_BUF_HCI_ACL_RX_COUNT) + +#else /* !CONFIG_BT_HCI_ACL_FLOW_CONTROL */ +#define BT_BUF_CMD_TX_HOST_NUM_CMPLT_PKT 0 +#endif /* !CONFIG_BT_HCI_ACL_FLOW_CONTROL */ + +/* Based on Host + Controller, Host-only or Controller-only; Calculate the + * HCI Command Tx count. + */ +#define BT_BUF_CMD_TX_COUNT \ + (CONFIG_BT_BUF_CMD_TX_COUNT + BT_BUF_CMD_TX_HOST_NUM_CMPLT_PKT + BT_BUF_CMD_TX_REMOTE_VERSION + \ + BT_BUF_CMD_TX_AUTO_PHY_UPDATE + BT_BUF_CMD_TX_AUTO_DATA_LEN_UPDATE) + +#elif defined(CONFIG_BT_HCI_ACL_FLOW_CONTROL) +/* When Controller to Host data flow control is supported in the + * Controller-only build, ensure that BT_BUF_CMD_TX_COUNT is greater than or + * equal to (BT_BUF_RX_COUNT + Ncmd), where Ncmd is supported maximum + * Num_HCI_Command_Packets in the Controller implementation. + */ + +BUILD_ASSERT(!IS_ENABLED(CONFIG_BT_BUF_CMD_TX_COUNT), + "Configurable HCI command buffer count disallowed."); +BUILD_ASSERT(IS_ENABLED(CONFIG_BT_CTLR_HCI_NUM_CMD_PKT_MAX), + "Undefined Controller implementation supported " + "Num_HCI_Command_Packets value."); + +/** Can use all of buffer count needed for HCI ACL, HCI ISO or Event RX + * buffers for ACL RX + */ +#define BT_BUF_HCI_ACL_RX_COUNT (BT_BUF_RX_COUNT) + +/* Controller-only with Controller to Host data flow control */ +#define BT_BUF_CMD_TX_COUNT (BT_BUF_RX_COUNT + CONFIG_BT_CTLR_HCI_NUM_CMD_PKT_MAX) + +#endif /* CONFIG_BT_HCI_ACL_FLOW_CONTROL */ diff --git a/libs/zblue/common/hci_lockstep.c b/libs/zblue/common/hci_lockstep.c new file mode 100644 index 0000000000000..31ca6bdff6178 --- /dev/null +++ b/libs/zblue/common/hci_lockstep.c @@ -0,0 +1,352 @@ +/**************************************************************************** + * libs/zblue/common/hci_lockstep.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* hci_lockstep.c - Bluetooth HCI lockstep command helper */ + +/* Copyright (c) 2026 Silicon Laboratories Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +/* The log level symbol exists only in builds with the Bluetooth Kconfig + * tree; the helper also builds without it (see tests/bluetooth/hci_pkt). + */ +#if defined(CONFIG_BT_HCI_DRIVER_LOG_LEVEL) +#define LOG_LEVEL CONFIG_BT_HCI_DRIVER_LOG_LEVEL +#endif +#include +LOG_MODULE_REGISTER(bt_hci_lockstep); + +/* The Bluetooth Host's own command timeout */ +#define LOCKSTEP_CMD_TIMEOUT SEC2TICK(10) + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static clock_t lockstep_deadline(clock_t timeout) +{ + return timeout < 0 ? (clock_t)-1 : clock_systime_ticks() + timeout; +} + +static bool lockstep_expired(clock_t deadline) +{ + return deadline >= 0 && !clock_compare(clock_systime_ticks(), deadline); +} + +static clock_t lockstep_remaining(clock_t deadline) +{ + clock_t remaining; + + if (deadline < 0) + { + return (clock_t)-1; + } + + remaining = deadline - clock_systime_ticks(); + return remaining > 0 ? remaining : 0; +} + +/* The helper's state: whether the controller allows a command, and where + * the command in progress stands. One byte under a short spinlock, with a + * single semaphore that only signals a change of it: a waiter re-checks the + * state it waits for, so a stale wakeup or a NOP received while a response + * is awaited cannot pass for what it waits for. + */ +#define CREDIT_MASK BIT_MASK(2) +/* The controller allows no command */ +#define CREDIT_NONE 0U +/* The controller allows one command */ +#define CREDIT_AVAILABLE BIT(0) +/* The allowance went to the command in progress */ +#define CREDIT_RESERVED BIT(1) +/* A command is outstanding, its response not yet received */ +#define PHASE_WAITING BIT(2) +/* The response has been delivered to the sender */ +#define PHASE_DONE BIT(3) + +static void lockstep_state_reset(struct bt_hci_lockstep *ls) +{ + ls->rsp = NULL; + ls->opcode = BT_OP_NOP; + /* The controller allows one command (Core Specification 6.3, Vol 4, + * Part E, Section 4.4). + */ + + ls->state = CREDIT_AVAILABLE; + ls->status = BT_HCI_ERR_SUCCESS; +} + +void bt_hci_lockstep_init( + struct bt_hci_lockstep *ls, const struct device *dev, + bt_hci_lockstep_send_t send) +{ + *ls = (struct bt_hci_lockstep) + { + .dev = dev, + .send = send, + .timeout = LOCKSTEP_CMD_TIMEOUT, + }; + + lockstep_state_reset(ls); + nxsem_init(&ls->changed, 0, 0); +} + +void bt_hci_lockstep_reset(struct bt_hci_lockstep *ls) +{ + irqstate_t key; + + /* Under the lock, so that a packet being fed meanwhile sees the state + * either before or after the reset. + */ + + key = spin_lock_irqsave(&ls->lock); + lockstep_state_reset(ls); + spin_unlock_irqrestore(&ls->lock, key); + + /* A change of state like any other: a sender waiting for the controller + * to allow its command finds that it does. Given rather than reset, which + * would take the wakeup away from a sender that has seen the old state and + * is about to wait, and fail one that already waits. + */ + + nxsem_post(&ls->changed); +} + +bool bt_hci_lockstep_feed(struct bt_hci_lockstep *ls, + const uint8_t *pkt, size_t len) +{ + struct bt_hci_pkt_cmd_rsp rsp; + irqstate_t key; + bool consumed = false; + int err; + + /* A Command Complete without its status (-ENODATA) is still a command + * response: rsp then carries BT_HCI_ERR_UNSPECIFIED. + */ + + err = bt_hci_pkt_parse_cmd_rsp(pkt, len, &rsp); + if (err != 0 && err != -ENODATA) + { + return false; + } + + key = spin_lock_irqsave(&ls->lock); + + /* Every command response tells whether the controller allows another + * command, whether or not it is the awaited one: the NOP Command Complete + * is sent for that alone. The latest word counts. + */ + + ls->state = + (ls->state & ~CREDIT_MASK) | + ((rsp.ncmd > 0) ? CREDIT_AVAILABLE : CREDIT_NONE); + + if ((ls->state & PHASE_WAITING) != 0 && rsp.opcode == ls->opcode) + { + const uint8_t *rp = rsp.rp; + size_t rp_len = rsp.rp_len; + + ls->status = rsp.status; + + /* The return parameters of a Command Complete start with the + * status; a Command Status has nothing but its status. + */ + + if (rp_len == 0 && err == 0) + { + rp = &rsp.status; + rp_len = sizeof(rsp.status); + } + + /* Delivered under the lock, so that the sender cannot return + * to its caller while the caller's buffer is being written. + */ + + if (ls->rsp != NULL) + { + (void)net_buf_simple_add_mem(ls->rsp, rp, MIN(rp_len, + net_buf_simple_tailroom(ls->rsp))); + } + + ls->state = (ls->state & CREDIT_MASK) | PHASE_DONE; + consumed = true; + } + + spin_unlock_irqrestore(&ls->lock, key); + + nxsem_post(&ls->changed); + + return consumed; +} + +int bt_hci_lockstep_cmd_send_sync( + struct bt_hci_lockstep *ls, uint16_t opcode, struct net_buf_simple *cmd, + struct net_buf_simple *rsp) +{ + uint8_t no_params[BT_HCI_PKT_CMD_HDR_SIZE]; + struct net_buf_simple no_params_buf; + irqstate_t key; + clock_t end; + uint8_t status; + int err; + + if (cmd == NULL) + { + net_buf_simple_init_with_data(&no_params_buf, no_params, + sizeof(no_params)); + bt_hci_pkt_reset_cmd(&no_params_buf); + cmd = &no_params_buf; + } + + err = bt_hci_pkt_push_cmd_hdr(cmd, opcode); + if (err != 0) + { + LOG_ERR("opcode 0x%04x: invalid command buffer (err %d)", opcode, err); + return err; + } + + /* One deadline for the whole exchange. Command flow control: a + * response that allowed no further command holds this one until the + * controller allows it, if only with the NOP Command Complete it sends for + * that purpose. + */ + + end = lockstep_deadline(ls->timeout); + while (true) + { + key = spin_lock_irqsave(&ls->lock); + + if ((ls->state & CREDIT_MASK) == CREDIT_AVAILABLE) + { + /* Armed before sending: the response may arrive, and be + * fed, before the send function returns. + */ + + ls->state = CREDIT_RESERVED | PHASE_WAITING; + ls->opcode = opcode; + ls->rsp = rsp; + ls->status = BT_HCI_ERR_SUCCESS; + if (rsp != NULL) + { + net_buf_simple_reset(rsp); + } + + spin_unlock_irqrestore(&ls->lock, key); + break; + } + + spin_unlock_irqrestore(&ls->lock, key); + + if (lockstep_expired(end) || nxsem_tickwait(&ls->changed, + lockstep_remaining(end)) != 0) + { + LOG_ERR("opcode 0x%04x: controller allows no command", opcode); + return -EAGAIN; + } + } + + err = ls->send(ls->dev, cmd->data, cmd->len); + if (err != 0) + { + bool responded; + + key = spin_lock_irqsave(&ls->lock); + + responded = (ls->state & PHASE_DONE) != 0; + if (!responded) + { + /* Nothing reached the controller: the allowance taken is + * intact, unless the controller has spoken since. + */ + + ls->state &= CREDIT_MASK; + if (ls->state == CREDIT_RESERVED) + { + ls->state = CREDIT_AVAILABLE; + } + + ls->rsp = NULL; + } + + spin_unlock_irqrestore(&ls->lock, key); + + if (!responded) + { + LOG_ERR("opcode 0x%04x: send failed (err %d)", opcode, err); + return err; + } + + /* The command evidently reached the controller: its response + * counts, not the transport's verdict. + */ + + LOG_WRN("opcode 0x%04x: send failed (err %d) after the response " + "arrived", + opcode, err); + } + + while (true) + { + key = spin_lock_irqsave(&ls->lock); + + if ((ls->state & PHASE_DONE) != 0) + { + status = ls->status; + ls->state &= CREDIT_MASK; + ls->rsp = NULL; + spin_unlock_irqrestore(&ls->lock, key); + break; + } + + if (lockstep_expired(end)) + { + /* The unanswered command used up the allowance: the next + * one waits for the controller to allow it again. + */ + + ls->state &= CREDIT_MASK; + ls->rsp = NULL; + spin_unlock_irqrestore(&ls->lock, key); + LOG_ERR("opcode 0x%04x: no response", opcode); + return -EAGAIN; + } + + spin_unlock_irqrestore(&ls->lock, key); + + /* A stale wakeup or a NOP only leads back to the check */ + + (void)nxsem_tickwait(&ls->changed, lockstep_remaining(end)); + } + + if (status != BT_HCI_ERR_SUCCESS) + { + LOG_WRN("opcode 0x%04x: status 0x%02x", opcode, status); + return -EIO; + } + + return 0; +} diff --git a/libs/zblue/common/hci_pkt.c b/libs/zblue/common/hci_pkt.c new file mode 100644 index 0000000000000..b9b75e272435d --- /dev/null +++ b/libs/zblue/common/hci_pkt.c @@ -0,0 +1,190 @@ +/**************************************************************************** + * libs/zblue/common/hci_pkt.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* hci_pkt.c - Bluetooth HCI packet helpers */ + +/* Copyright (c) 2026 Silicon Laboratories Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include +#include + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_hci_pkt_reset_cmd(struct net_buf_simple *buf) +{ + DEBUGASSERT(buf->size >= BT_HCI_PKT_CMD_HDR_SIZE); + + net_buf_simple_reset(buf); + net_buf_simple_reserve(buf, BT_HCI_PKT_CMD_HDR_SIZE); +} + +int bt_hci_pkt_push_cmd_hdr(struct net_buf_simple *buf, uint16_t opcode) +{ + struct bt_hci_cmd_hdr *hdr; + + if (net_buf_simple_headroom(buf) < BT_HCI_PKT_CMD_HDR_SIZE) + { + return -EINVAL; + } + + if (buf->len > UINT8_MAX) + { + return -EMSGSIZE; + } + + hdr = (struct bt_hci_cmd_hdr *)net_buf_simple_push(buf, sizeof(*hdr)); + hdr->opcode = sys_cpu_to_le16(opcode); + hdr->param_len = buf->len - sizeof(*hdr); + + net_buf_simple_push_u8(buf, BT_HCI_H4_CMD); + + return 0; +} + +/* Decode the event parameters of an HCI_Command_Complete event */ + +static int decode_cmd_complete(const uint8_t *params, + size_t len, struct bt_hci_pkt_cmd_rsp *rsp) +{ + const struct bt_hci_evt_cmd_complete *evt; + + if (len < sizeof(*evt)) + { + return -EINVAL; + } + + evt = (const struct bt_hci_evt_cmd_complete *)params; + rsp->opcode = sys_le16_to_cpu(evt->opcode); + rsp->ncmd = evt->ncmd; + rsp->rp = ¶ms[sizeof(*evt)]; + rsp->rp_len = len - sizeof(*evt); + + /* Only an event that responds to no command (NOP) carries no return + * parameters; those of every command start with the status. + */ + + if (rsp->opcode == BT_OP_NOP) + { + rsp->status = BT_HCI_ERR_SUCCESS; + return 0; + } + + if (rsp->rp_len == 0) + { + rsp->status = BT_HCI_ERR_UNSPECIFIED; + return -ENODATA; + } + + rsp->status = rsp->rp[0]; + + return 0; +} + +int bt_hci_pkt_pull_cmd_complete(struct net_buf_simple *buf, + struct bt_hci_pkt_cmd_rsp *rsp) +{ + int err; + + err = decode_cmd_complete(buf->data, buf->len, rsp); + if (err != 0 && err != -ENODATA) + { + return err; + } + + net_buf_simple_pull(buf, sizeof(struct bt_hci_evt_cmd_complete)); + + return err; +} + +/* Decode the event parameters of an HCI_Command_Status event */ + +static int decode_cmd_status(const uint8_t *params, + size_t len, struct bt_hci_pkt_cmd_rsp *rsp) +{ + const struct bt_hci_evt_cmd_status *evt; + + if (len < sizeof(*evt)) + { + return -EINVAL; + } + + evt = (const struct bt_hci_evt_cmd_status *)params; + rsp->opcode = sys_le16_to_cpu(evt->opcode); + rsp->ncmd = evt->ncmd; + rsp->status = evt->status; + rsp->rp = ¶ms[sizeof(*evt)]; + rsp->rp_len = 0; + + return 0; +} + +int bt_hci_pkt_pull_cmd_status(struct net_buf_simple *buf, + struct bt_hci_pkt_cmd_rsp *rsp) +{ + int err; + + err = decode_cmd_status(buf->data, buf->len, rsp); + if (err != 0) + { + return err; + } + + net_buf_simple_pull(buf, sizeof(struct bt_hci_evt_cmd_status)); + + return 0; +} + +int bt_hci_pkt_parse_cmd_rsp(const uint8_t *pkt, + size_t len, struct bt_hci_pkt_cmd_rsp *rsp) +{ + const struct bt_hci_evt_hdr *hdr; + const uint8_t *params; + + if (len < sizeof(uint8_t) || pkt[0] != BT_HCI_H4_EVT) + { + return -ENOMSG; + } + + if (len < sizeof(uint8_t) + sizeof(*hdr)) + { + return -EINVAL; + } + + hdr = (const struct bt_hci_evt_hdr *)&pkt[sizeof(uint8_t)]; + if (hdr->len > len - sizeof(uint8_t) - sizeof(*hdr)) + { + return -EINVAL; + } + + params = &pkt[sizeof(uint8_t) + sizeof(*hdr)]; + + switch (hdr->evt) + { + case BT_HCI_EVT_CMD_COMPLETE: + return decode_cmd_complete(params, hdr->len, rsp); + case BT_HCI_EVT_CMD_STATUS: + return decode_cmd_status(params, hdr->len, rsp); + default: + return -ENOMSG; + } +} diff --git a/libs/zblue/common/hci_public_addr.c b/libs/zblue/common/hci_public_addr.c new file mode 100644 index 0000000000000..8bff08949a835 --- /dev/null +++ b/libs/zblue/common/hci_public_addr.c @@ -0,0 +1,43 @@ +/**************************************************************************** + * libs/zblue/common/hci_public_addr.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright The Zephyr Project Contributors + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/* "No address" is BT_ADDR_ANY, as in the setup() op's parameters and, as the + * address part of BT_ADDR_LE_ANY, in the Host's identity table, and not + * BT_ADDR_NONE: zero-initialized driver data then needs no flag to say that + * nothing has been set. + */ + +void bt_hci_set_public_addr(const struct device *dev, const bt_addr_t *addr) +{ + struct bt_hci_driver_data *data = dev->data; + + bt_addr_copy(&data->public_addr, addr); +} + +const bt_addr_t *bt_hci_get_public_addr(const struct device *dev) +{ + const struct bt_hci_driver_data *data = dev->data; + + return &data->public_addr; +} diff --git a/libs/zblue/common/long_wq.c b/libs/zblue/common/long_wq.c new file mode 100644 index 0000000000000..89403df903861 --- /dev/null +++ b/libs/zblue/common/long_wq.c @@ -0,0 +1,63 @@ +/**************************************************************************** + * libs/zblue/common/long_wq.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* long_work.c - Workqueue intended for long-running operations. */ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +static uint64_t bt_lw_stack_area[CONFIG_BT_LONG_WQ_STACK_SIZE / + sizeof(uint64_t)]; +static struct k_work_q bt_long_wq; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bt_long_wq_schedule(struct k_work_delayable *dwork, clock_t timeout) +{ + return k_work_schedule_for_queue(&bt_long_wq, dwork, timeout); +} + +int bt_long_wq_reschedule(struct k_work_delayable *dwork, clock_t timeout) +{ + return k_work_reschedule_for_queue(&bt_long_wq, dwork, timeout); +} + +int bt_long_wq_submit(struct k_work *work) +{ + return k_work_submit_to_queue(&bt_long_wq, work); +} + +static int long_wq_init(void) +{ + const struct k_work_queue_config cfg = + { + .name = "BT LW WQ" + }; + + k_work_queue_init(&bt_long_wq); + + k_work_queue_start(&bt_long_wq, bt_lw_stack_area, sizeof(bt_lw_stack_area), + CONFIG_BT_LONG_WQ_PRIO, &cfg); + + return 0; +} + +SYS_INIT(long_wq_init, POST_KERNEL, CONFIG_BT_LONG_WQ_INIT_PRIO); diff --git a/libs/zblue/common/long_wq.h b/libs/zblue/common/long_wq.h new file mode 100644 index 0000000000000..c3b4fcf15f649 --- /dev/null +++ b/libs/zblue/common/long_wq.h @@ -0,0 +1,24 @@ +/**************************************************************************** + * libs/zblue/common/long_wq.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* long_wq.h - Workqueue API intended for long-running operations. */ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +int bt_long_wq_schedule(struct k_work_delayable *dwork, clock_t timeout); +int bt_long_wq_reschedule(struct k_work_delayable *dwork, clock_t timeout); +int bt_long_wq_submit(struct k_work *dwork); diff --git a/libs/zblue/common/rpa.c b/libs/zblue/common/rpa.c new file mode 100644 index 0000000000000..8e529e51fc9b4 --- /dev/null +++ b/libs/zblue/common/rpa.c @@ -0,0 +1,146 @@ +/**************************************************************************** + * libs/zblue/common/rpa.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file rpa.c Resolvable Private Address Generation and Resolution + */ + +/* Copyright (c) 2017 Nordic Semiconductor ASA + * Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include "common/bt_str.h" + +#include + +#define LOG_LEVEL CONFIG_BT_RPA_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_rpa); + +#if defined(CONFIG_BT_CTLR_CRYPTO) && defined(CONFIG_BT_HOST_CRYPTO) +#include "../controller/util/util.h" +#include "../controller/hal/ecb.h" +#endif /* CONFIG_BT_CTLR_CRYPTO && CONFIG_BT_HOST_CRYPTO */ + +#if defined(CONFIG_BT_PRIVACY) || defined(CONFIG_BT_CTLR_PRIVACY) + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int internal_rand(void *buf, size_t len) +{ + /* Force using controller rand function. */ +#if defined(CONFIG_BT_CTLR_CRYPTO) && defined(CONFIG_BT_HOST_CRYPTO) + return lll_csrand_get(buf, len); +#else /* !CONFIG_BT_CTLR_CRYPTO || !CONFIG_BT_HOST_CRYPTO */ + return bt_rand(buf, len); +#endif /* !CONFIG_BT_CTLR_CRYPTO || !CONFIG_BT_HOST_CRYPTO */ +} +#endif /* defined(CONFIG_BT_PRIVACY) || defined(CONFIG_BT_CTLR_PRIVACY) */ + +static int internal_encrypt_le(const uint8_t key[16], + const uint8_t plaintext[16], + uint8_t enc_data[16]) +{ + /* Force using controller encrypt function if supported. */ +#if defined(CONFIG_BT_CTLR_CRYPTO) && defined(CONFIG_BT_HOST_CRYPTO) + ecb_encrypt(key, plaintext, enc_data, NULL); + return 0; +#else /* !CONFIG_BT_CTLR_CRYPTO || !CONFIG_BT_HOST_CRYPTO */ + return bt_encrypt_le(key, plaintext, enc_data); +#endif /* !CONFIG_BT_CTLR_CRYPTO || !CONFIG_BT_HOST_CRYPTO */ +} + +static int ah(const uint8_t irk[16], const uint8_t r[3], uint8_t out[3]) +{ + uint8_t res[16]; + int err; + + LOG_DBG("irk %s", bt_hex(irk, 16)); + LOG_DBG("r %s", bt_hex(r, 3)); + + /* r' = padding || r */ + + memcpy(res, r, 3); + (void)memset(res + 3, 0, 13); + + err = internal_encrypt_le(irk, res, res); + if (err) + { + return err; + } + + /* The output of the random address function ah is: + * ah(h, r) = e(k, r') mod 2^24 The output of the security function e is + * then truncated to 24 bits by taking the least significant 24 bits of + * the output of e as the result of ah. + */ + + memcpy(out, res, 3); + + return 0; +} + +#if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_CTLR_PRIVACY) +bool bt_rpa_irk_matches(const uint8_t irk[16], const bt_addr_t *addr) +{ + uint8_t hash[3]; + int err; + + LOG_DBG("IRK %s bdaddr %s", bt_hex(irk, 16), bt_addr_str(addr)); + + err = ah(irk, addr->val + 3, hash); + if (err) + { + return false; + } + + return !memcmp(addr->val, hash, 3); +} +#endif + +#if defined(CONFIG_BT_PRIVACY) || defined(CONFIG_BT_CTLR_PRIVACY) +int bt_rpa_create(const uint8_t irk[16], bt_addr_t *rpa) +{ + int err; + + err = internal_rand(rpa->val + 3, 3); + if (err) + { + return err; + } + + BT_ADDR_SET_RPA(rpa); + + err = ah(irk, rpa->val + 3, rpa->val); + if (err) + { + return err; + } + + LOG_DBG("Created RPA %s", bt_addr_str((bt_addr_t *)rpa->val)); + + return 0; +} +#else +int bt_rpa_create(const uint8_t irk[16], bt_addr_t *rpa) +{ + return -ENOTSUP; +} +#endif /* CONFIG_BT_PRIVACY */ diff --git a/libs/zblue/common/rpa.h b/libs/zblue/common/rpa.h new file mode 100644 index 0000000000000..f06b1989dcf48 --- /dev/null +++ b/libs/zblue/common/rpa.h @@ -0,0 +1,26 @@ +/**************************************************************************** + * libs/zblue/common/rpa.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* rpa.h - Bluetooth Resolvable Private Addresses (RPA) generation and + * resolution + */ + +/* Copyright (c) 2017 Nordic Semiconductor ASA Copyright (c) 2015-2016 Intel + * Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +bool bt_rpa_irk_matches(const uint8_t irk[16], const bt_addr_t *addr); +int bt_rpa_create(const uint8_t irk[16], bt_addr_t *rpa); diff --git a/libs/zblue/compat/CMakeLists.txt b/libs/zblue/compat/CMakeLists.txt new file mode 100644 index 0000000000000..7d58340c1a3a4 --- /dev/null +++ b/libs/zblue/compat/CMakeLists.txt @@ -0,0 +1,34 @@ +# ############################################################################## +# libs/zblue/compat/CMakeLists.txt +# +# Author: chao an +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +target_sources( + zblue + PRIVATE ${CMAKE_CURRENT_LIST_DIR}/src/kernel/queue.c + ${CMAKE_CURRENT_LIST_DIR}/src/kernel/timeout.c + ${CMAKE_CURRENT_LIST_DIR}/src/kernel/mem_slab.c + ${CMAKE_CURRENT_LIST_DIR}/src/net_buf/net_buf.c + ${CMAKE_CURRENT_LIST_DIR}/src/net_buf/net_buf_simple.c + ${CMAKE_CURRENT_LIST_DIR}/src/sections/defines.c + ${CMAKE_CURRENT_LIST_DIR}/src/subsys/fs/fs.c + ${CMAKE_CURRENT_LIST_DIR}/src/drivers/bluetooth/hci/hci_driver.c) diff --git a/libs/zblue/compat/Kconfig b/libs/zblue/compat/Kconfig new file mode 100644 index 0000000000000..d7c4f15b141c6 --- /dev/null +++ b/libs/zblue/compat/Kconfig @@ -0,0 +1,46 @@ +############################################################################ +# libs/zblue/compat/Kconfig +# +# Author: chao an +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +# +# Bluetooth Host Stack Compatibility Layer +# + +config BT_COMPAT + bool + default y + depends on BT + +config BT_COMPAT_LOG_LEVEL + int "BT Compat Log Level" + default 3 + range 0 4 + help + Log level for Bluetooth compat layer. + 0 = none, 1 = error, 2 = warning, 3 = info, 4 = debug + +config NET_BUF_ALIGNMENT + int "Buffer alignment" + default 0 + help + Alignment for net_buf buffers. 0 means align to pointer size. diff --git a/libs/zblue/compat/Make.defs b/libs/zblue/compat/Make.defs new file mode 100644 index 0000000000000..f011f602aae96 --- /dev/null +++ b/libs/zblue/compat/Make.defs @@ -0,0 +1,54 @@ +############################################################################ +# libs/zblue/compat/Make.defs +# +# Author: chao an +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +ifeq ($(CONFIG_BT),y) + +# Kernel compat layer +BT_CSRCS += queue.c timeout.c mem_slab.c + +# Network buffer compat layer +BT_CSRCS += net_buf.c net_buf_simple.c + +# Support modules +BT_CSRCS += fs.c + +# Iterable section definitions +BT_CSRCS += defines.c + +# HCI driver +BT_CSRCS += hci_driver.c + +VPATH += $(TOPDIR)/libs/zblue/compat/src/kernel +VPATH += $(TOPDIR)/libs/zblue/compat/src/net_buf +VPATH += $(TOPDIR)/libs/zblue/compat/src/sections +VPATH += $(TOPDIR)/libs/zblue/compat/src/subsys/fs +VPATH += $(TOPDIR)/libs/zblue/compat/src/drivers/bluetooth/hci + +DEPPATH += --dep-path $(TOPDIR)/libs/zblue/compat/src/kernel +DEPPATH += --dep-path $(TOPDIR)/libs/zblue/compat/src/net_buf +DEPPATH += --dep-path $(TOPDIR)/libs/zblue/compat/src/sections +DEPPATH += --dep-path $(TOPDIR)/libs/zblue/compat/src/subsys/fs +DEPPATH += --dep-path $(TOPDIR)/libs/zblue/compat/src/drivers/bluetooth/hci + +endif diff --git a/libs/zblue/compat/src/drivers/bluetooth/hci/hci_driver.c b/libs/zblue/compat/src/drivers/bluetooth/hci/hci_driver.c new file mode 100644 index 0000000000000..455eec8faec91 --- /dev/null +++ b/libs/zblue/compat/src/drivers/bluetooth/hci/hci_driver.c @@ -0,0 +1,309 @@ +/**************************************************************************** + * libs/zblue/compat/src/drivers/bluetooth/hci/hci_driver.c + * + * Author: chao an + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/* NuttX HCI driver integration. + * This provides the bridge between Zephyr BT host and NuttX hardware. For + * now this is a skeleton - actual HCI transport drivers (UART H4, etc.) need + * to be implemented separately. + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static const struct bt_hci_driver_ops *g_hci_ops; +static bt_hci_recv_t g_hci_recv_cb; +static FAR struct bt_driver_s *g_btdev_driver; + +static int btdev_hci_open(void); +static int btdev_hci_send(FAR struct net_buf *buf); +static int btdev_hci_close(void); + +static const struct bt_hci_driver_ops g_btdev_hci_ops = +{ + .open = btdev_hci_open, + .send = btdev_hci_send, + .close = btdev_hci_close, +}; + +/* Buffer pools for HCI data */ + +NET_BUF_POOL_FIXED_DEFINE(hci_cmd_pool, + CONFIG_BT_BUF_CMD_TX_COUNT, + BT_BUF_CMD_TX_SIZE, + 0, + NULL); +NET_BUF_POOL_FIXED_DEFINE(hci_acl_tx_pool, CONFIG_BT_BUF_ACL_TX_COUNT, + BT_BUF_ACL_SIZE(CONFIG_BT_BUF_ACL_TX_SIZE), + 0, + NULL); +NET_BUF_POOL_FIXED_DEFINE(hci_acl_rx_pool, + BT_BUF_ACL_RX_COUNT, + BT_BUF_ACL_RX_SIZE, + 0, + NULL); + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bt_hci_driver_register(const struct bt_hci_driver_ops *ops) +{ + g_hci_ops = ops; + return 0; +} + +static int btdev_hci_open(void) +{ + if (g_btdev_driver == NULL || g_btdev_driver->open == NULL) + { + return 0; + } + + return g_btdev_driver->open(g_btdev_driver); +} + +static int btdev_hci_send(FAR struct net_buf *buf) +{ + FAR struct bt_driver_s *driver = g_btdev_driver; + enum bt_buf_type_e type; + int ret; + + if (driver == NULL || driver->send == NULL) + { + return -ENODEV; + } + + if (buf == NULL || buf->len < 1) + { + return -EINVAL; + } + + switch (buf->data[0]) + { + case BT_HCI_H4_CMD: + type = BT_CMD; + break; + case BT_HCI_H4_ACL: + type = BT_ACL_OUT; + break; + case BT_HCI_H4_ISO: + type = BT_ISO_OUT; + break; + default: + return -EINVAL; + } + + ret = driver->send(driver, type, buf->data + 1, buf->len - 1); + if (ret < 0) + { + return ret; + } + + net_buf_unref(buf); + return 0; +} + +static int btdev_hci_close(void) +{ + if (g_btdev_driver != NULL && g_btdev_driver->close != NULL) + { + g_btdev_driver->close(g_btdev_driver); + } + + return 0; +} + +static FAR struct net_buf *btdev_hci_rx_alloc(enum bt_buf_type_e type, + FAR const uint8_t *data, + size_t len) +{ + FAR struct net_buf *buf; + + switch (type) + { + case BT_EVT: + if (len < sizeof(struct bt_hci_evt_hdr)) + { + return NULL; + } + + buf = bt_buf_get_evt(data[0], false, 0); + break; + case BT_ACL_IN: + buf = bt_buf_get_rx(BT_BUF_ACL_IN, 0); + break; + case BT_ISO_IN: +#ifdef CONFIG_BT_ISO + buf = bt_buf_get_rx(BT_BUF_ISO_IN, 0); + break; +#else + return NULL; +#endif + default: + return NULL; + } + + if (buf == NULL) + { + return NULL; + } + + if (net_buf_tailroom(buf) < len) + { + net_buf_unref(buf); + return NULL; + } + + net_buf_add_mem(buf, data, len); + return buf; +} + +static int btdev_hci_receive(FAR struct bt_driver_s *driver, + enum bt_buf_type_e type, + FAR void *data, + size_t len) +{ + FAR struct net_buf *buf; + int ret; + + if (data == NULL || len == 0) + { + return -EINVAL; + } + + buf = btdev_hci_rx_alloc(type, data, len); + if (buf == NULL) + { + return -ENOBUFS; + } + + ret = bt_recv(buf); + if (ret < 0) + { + net_buf_unref(buf); + } + + return ret; +} + +int bt_netdev_register(FAR struct bt_driver_s *driver) +{ + if (driver == NULL) + { + return -EINVAL; + } + + if (g_btdev_driver != NULL) + { + return -EBUSY; + } + + driver->receive = btdev_hci_receive; + g_btdev_driver = driver; + + return bt_hci_driver_register(&g_btdev_hci_ops); +} + +struct net_buf *bt_buf_get_cmd_complete(clock_t timeout) +{ + return net_buf_alloc(&hci_cmd_pool, timeout); +} + +struct net_buf *bt_buf_get_acl(clock_t timeout) +{ + return net_buf_alloc(&hci_acl_rx_pool, timeout); +} + +struct net_buf *bt_buf_get_iso(clock_t timeout) +{ + (void)timeout; + return NULL; +} + +int bt_recv(struct net_buf *buf) +{ + if (g_hci_recv_cb) + { + return g_hci_recv_cb(NULL, buf); + } + + net_buf_unref(buf); + return -ENODEV; +} + +int bt_recv_iso(struct net_buf *buf) +{ + (void)buf; + return 0; +} + +/* ===== HCI driver API (bt_hci_*) ===== */ + +int bt_hci_open(const struct device *dev, bt_hci_recv_t recv) +{ + g_hci_recv_cb = recv; + if (g_hci_ops && g_hci_ops->open) + { + return g_hci_ops->open(); + } + + return 0; +} + +int bt_hci_send(const struct device *dev, struct net_buf *buf) +{ + if (g_hci_ops && g_hci_ops->send) + { + return g_hci_ops->send(buf); + } + + return -ENODEV; +} + +int bt_hci_close(const struct device *dev) +{ + g_hci_recv_cb = NULL; + if (g_hci_ops && g_hci_ops->close) + { + return g_hci_ops->close(); + } + + return 0; +} + +bool bt_hci_can_close(const struct device *dev) +{ + return true; +} diff --git a/libs/zblue/compat/src/kernel/mem_slab.c b/libs/zblue/compat/src/kernel/mem_slab.c new file mode 100644 index 0000000000000..74af45032be77 --- /dev/null +++ b/libs/zblue/compat/src/kernel/mem_slab.c @@ -0,0 +1,113 @@ +/**************************************************************************** + * libs/zblue/compat/src/kernel/mem_slab.c + * + * Author: chao an + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void k_mem_slab_build_free_list(struct k_mem_slab *slab) +{ + char *p = slab->buffer; + + slab->free_list = NULL; + + for (uint32_t i = 0; i < slab->num_blocks; i++) + { + *(char **)p = slab->free_list; + slab->free_list = p; + p += slab->block_size; + } + + slab->num_used = 0; + slab->initialized = true; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void k_mem_slab_init(struct k_mem_slab *slab, + void *buffer, + size_t block_size, + + uint32_t num_blocks) +{ + slab->buffer = (char *)buffer; + slab->block_size = block_size; + slab->num_blocks = num_blocks; + slab->lock = SP_UNLOCKED; + k_mem_slab_build_free_list(slab); +} + +int k_mem_slab_alloc(struct k_mem_slab *slab, void **mem, clock_t timeout) +{ + (void)timeout; + + irqstate_t flags = spin_lock_irqsave(&slab->lock); + + if (!slab->initialized) + { + k_mem_slab_build_free_list(slab); + } + + if (slab->free_list == NULL) + { + spin_unlock_irqrestore(&slab->lock, flags); + return -ENOMEM; + } + + *mem = slab->free_list; + slab->free_list = *(char **)slab->free_list; + slab->num_used++; + + spin_unlock_irqrestore(&slab->lock, flags); + return 0; +} + +void k_mem_slab_free(struct k_mem_slab *slab, void *mem) +{ + irqstate_t flags = spin_lock_irqsave(&slab->lock); + + *(char **)mem = slab->free_list; + slab->free_list = (char *)mem; + + if (slab->num_used > 0) + { + slab->num_used--; + } + + spin_unlock_irqrestore(&slab->lock, flags); +} + +uint32_t k_mem_slab_num_free_get(struct k_mem_slab *slab) +{ + return slab->num_blocks - slab->num_used; +} diff --git a/libs/zblue/compat/src/kernel/queue.c b/libs/zblue/compat/src/kernel/queue.c new file mode 100644 index 0000000000000..7503581a4eeb0 --- /dev/null +++ b/libs/zblue/compat/src/kernel/queue.c @@ -0,0 +1,474 @@ +/**************************************************************************** + * libs/zblue/compat/src/kernel/queue.c + * + * Author: chao an + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +struct k_poll_waiter +{ + sem_t sem; + int refs; +}; + +static spinlock_t g_poll_lock = SP_UNLOCKED; + +static void k_poll_remove_event_locked(struct k_poll_event *event) +{ + if (event->poller != NULL && list_in_list(&event->node)) + { + list_delete(&event->node); + } + + event->poller = NULL; +} + +static struct k_poll_waiter *k_poll_signal_event_locked( + struct k_poll_event *event, uint32_t state) +{ + struct k_poll_waiter *poller = event->poller; + + k_poll_remove_event_locked(event); + event->state = state; + + if (poller != NULL) + { + poller->refs++; + } + + return poller; +} + +static void k_poll_release_waiter(struct k_poll_waiter *poller) +{ + irqstate_t flags; + + flags = spin_lock_irqsave(&g_poll_lock); + poller->refs--; + spin_unlock_irqrestore(&g_poll_lock, flags); +} + +static void k_poll_drain_waiter(struct k_poll_waiter *poller) +{ + irqstate_t flags; + + for (; ; ) + { + flags = spin_lock_irqsave(&g_poll_lock); + if (poller->refs == 0) + { + spin_unlock_irqrestore(&g_poll_lock, flags); + return; + } + + spin_unlock_irqrestore(&g_poll_lock, flags); + sched_yield(); + } +} + +static struct k_poll_waiter *k_poll_signal_fifo_locked(struct k_fifo *fifo, + uint32_t state) +{ + struct k_poll_event *event; + struct list_node *node; + + node = list_remove_head(&fifo->poll_events); + if (node == NULL) + { + return NULL; + } + + event = list_entry(node, struct k_poll_event, node); + list_clear_node(&event->node); + return k_poll_signal_event_locked(event, state); +} + +static void *k_fifo_remove_locked(struct k_fifo *fifo) +{ + return list_remove_head(&fifo->data_q); +} + +static int k_poll_register_fifo_events(struct k_poll_event *events, + int num_events, + struct k_poll_waiter *poller) +{ + irqstate_t flags; + int i; + int j; + int registered = 0; + + flags = spin_lock_irqsave(&g_poll_lock); + + for (i = 0; i < num_events; i++) + { + events[i].state = K_POLL_STATE_NOT_READY; + events[i].poller = NULL; + list_clear_node(&events[i].node); + + if (events[i].type == K_POLL_TYPE_FIFO_DATA_AVAILABLE && + events[i].fifo != NULL) + { + irqstate_t fifo_flags; + bool ready; + + fifo_flags = spin_lock_irqsave(&events[i].fifo->lock); + ready = !list_is_empty(&events[i].fifo->data_q); + spin_unlock_irqrestore(&events[i].fifo->lock, fifo_flags); + + if (ready) + { + events[i].state = K_POLL_STATE_FIFO_DATA_AVAILABLE; + + for (j = 0; j < i; j++) + { + k_poll_remove_event_locked(&events[j]); + } + + spin_unlock_irqrestore(&g_poll_lock, flags); + return 0; + } + + events[i].poller = poller; + list_add_tail(&events[i].fifo->poll_events, &events[i].node); + registered++; + } + } + + spin_unlock_irqrestore(&g_poll_lock, flags); + return registered; +} + +static void k_poll_unregister_fifo_events(struct k_poll_event *events, + int num_events) +{ + irqstate_t flags; + int i; + + flags = spin_lock_irqsave(&g_poll_lock); + + for (i = 0; i < num_events; i++) + { + k_poll_remove_event_locked(&events[i]); + } + + spin_unlock_irqrestore(&g_poll_lock, flags); +} + +static int k_poll_check_fifo_events(struct k_poll_event *events, + int num_events) +{ + int i; + + for (i = 0; i < num_events; i++) + { + if (events[i].state != K_POLL_STATE_NOT_READY) + { + return 0; + } + + if (events[i].type == K_POLL_TYPE_FIFO_DATA_AVAILABLE && + events[i].fifo != NULL) + { + irqstate_t flags; + bool ready; + + flags = spin_lock_irqsave(&events[i].fifo->lock); + ready = !list_is_empty(&events[i].fifo->data_q); + spin_unlock_irqrestore(&events[i].fifo->lock, flags); + + if (ready) + { + events[i].state = + K_POLL_STATE_FIFO_DATA_AVAILABLE; + return 0; + } + } + } + + return -EAGAIN; +} + +void k_fifo_init(struct k_fifo *fifo) +{ + list_initialize(&fifo->data_q); + list_initialize(&fifo->poll_events); + nxsem_init(&fifo->sem, 0, 0); + fifo->lock = SP_UNLOCKED; + fifo->waiters = 0; + fifo->cancel_waiters = 0; +} + +void k_fifo_cancel_wait(struct k_fifo *fifo) +{ + irqstate_t flags; + struct k_poll_waiter *poller; + unsigned int waiters; + unsigned int i; + + flags = spin_lock_irqsave(&fifo->lock); + waiters = fifo->waiters; + fifo->cancel_waiters += waiters; + spin_unlock_irqrestore(&fifo->lock, flags); + + do + { + flags = spin_lock_irqsave(&g_poll_lock); + poller = k_poll_signal_fifo_locked(fifo, K_POLL_STATE_CANCELLED); + spin_unlock_irqrestore(&g_poll_lock, flags); + + if (poller != NULL) + { + nxsem_post(&poller->sem); + k_poll_release_waiter(poller); + } + } + while (poller != NULL); + + for (i = 0; i < waiters; i++) + { + nxsem_post(&fifo->sem); + } +} + +void k_fifo_put(struct k_fifo *fifo, void *data) +{ + irqstate_t flags = spin_lock_irqsave(&fifo->lock); + struct k_poll_waiter *poller; + + list_add_tail(&fifo->data_q, (struct list_node *)data); + spin_unlock_irqrestore(&fifo->lock, flags); + + flags = spin_lock_irqsave(&g_poll_lock); + poller = k_poll_signal_fifo_locked(fifo, + K_POLL_STATE_FIFO_DATA_AVAILABLE); + spin_unlock_irqrestore(&g_poll_lock, flags); + + if (poller != NULL) + { + nxsem_post(&poller->sem); + k_poll_release_waiter(poller); + } + + nxsem_post(&fifo->sem); +} + +void *k_fifo_get(struct k_fifo *fifo, clock_t timeout) +{ + irqstate_t flags; + struct list_node *node; + clock_t deadline = 0; + int ret; + + if (timeout > 0) + { + deadline = clock_systime_ticks() + timeout; + } + + while (1) + { + flags = spin_lock_irqsave(&fifo->lock); + node = k_fifo_remove_locked(fifo); + spin_unlock_irqrestore(&fifo->lock, flags); + + if (node != NULL) + { + (void)nxsem_trywait(&fifo->sem); + return node; + } + + if (timeout == 0) + { + return NULL; + } + + flags = spin_lock_irqsave(&fifo->lock); + fifo->waiters++; + spin_unlock_irqrestore(&fifo->lock, flags); + + if (timeout < 0) + { + ret = nxsem_wait_uninterruptible(&fifo->sem); + } + else + { + clock_t now = clock_systime_ticks(); + + if ((clock_t)(deadline - now) <= 0) + { + ret = -ETIMEDOUT; + } + else + { + ret = nxsem_tickwait_uninterruptible(&fifo->sem, + deadline - now); + } + } + + flags = spin_lock_irqsave(&fifo->lock); + if (fifo->waiters > 0) + { + fifo->waiters--; + } + + if (ret >= 0 && fifo->cancel_waiters > 0) + { + fifo->cancel_waiters--; + spin_unlock_irqrestore(&fifo->lock, flags); + return NULL; + } + + spin_unlock_irqrestore(&fifo->lock, flags); + + if (ret < 0) + { + return NULL; + } + } +} + +bool k_fifo_is_empty(struct k_fifo *fifo) +{ + irqstate_t flags = spin_lock_irqsave(&fifo->lock); + bool empty = list_is_empty(&fifo->data_q); + + spin_unlock_irqrestore(&fifo->lock, flags); + return empty; +} + +void *k_fifo_peek_head(struct k_fifo *fifo) +{ + irqstate_t flags = spin_lock_irqsave(&fifo->lock); + struct list_node *node = list_peek_head(&fifo->data_q); + + spin_unlock_irqrestore(&fifo->lock, flags); + return node; +} + +void z_k_queue_prepend_fifo(struct k_fifo *fifo, void *data) +{ + irqstate_t flags = spin_lock_irqsave(&fifo->lock); + struct k_poll_waiter *poller; + + list_add_head(&fifo->data_q, (struct list_node *)data); + spin_unlock_irqrestore(&fifo->lock, flags); + + flags = spin_lock_irqsave(&g_poll_lock); + poller = k_poll_signal_fifo_locked(fifo, + K_POLL_STATE_FIFO_DATA_AVAILABLE); + spin_unlock_irqrestore(&g_poll_lock, flags); + + if (poller != NULL) + { + nxsem_post(&poller->sem); + k_poll_release_waiter(poller); + } + + nxsem_post(&fifo->sem); +} + +bool z_k_queue_remove_fifo(struct k_fifo *fifo, void *data) +{ + irqstate_t flags; + bool removed; + + flags = spin_lock_irqsave(&fifo->lock); + removed = list_find_and_remove(&fifo->data_q, + (struct list_node *)data); + spin_unlock_irqrestore(&fifo->lock, flags); + + if (removed) + { + (void)nxsem_trywait(&fifo->sem); + } + + return removed; +} + +int k_poll(struct k_poll_event *events, int num_events, clock_t timeout) +{ + struct k_poll_waiter poller; + int ret; + int registered; + int i; + + for (i = 0; i < num_events; i++) + { + events[i].state = K_POLL_STATE_NOT_READY; + events[i].poller = NULL; + list_clear_node(&events[i].node); + } + + ret = k_poll_check_fifo_events(events, num_events); + if (ret == 0 || timeout == 0) + { + return ret; + } + + nxsem_init(&poller.sem, 0, 0); + poller.refs = 0; + + registered = k_poll_register_fifo_events(events, num_events, &poller); + if (registered == 0) + { + nxsem_destroy(&poller.sem); + return k_poll_check_fifo_events(events, num_events); + } + + if (k_poll_check_fifo_events(events, num_events) == 0) + { + ret = 0; + } + else if (timeout < 0) + { + ret = nxsem_wait_uninterruptible(&poller.sem); + } + else + { + ret = nxsem_tickwait_uninterruptible(&poller.sem, timeout); + if (ret < 0) + { + ret = -EAGAIN; + } + } + + if (ret >= 0) + { + ret = k_poll_check_fifo_events(events, num_events); + } + + k_poll_unregister_fifo_events(events, num_events); + k_poll_drain_waiter(&poller); + nxsem_destroy(&poller.sem); + + return ret; +} diff --git a/libs/zblue/compat/src/kernel/timeout.c b/libs/zblue/compat/src/kernel/timeout.c new file mode 100644 index 0000000000000..fd328e15e8577 --- /dev/null +++ b/libs/zblue/compat/src/kernel/timeout.c @@ -0,0 +1,336 @@ +/**************************************************************************** + * libs/zblue/compat/src/kernel/timeout.c + * + * Author: chao an + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +/* Global system work queue - uses NuttX HP work queue */ + +struct k_work_q k_sys_work_q = +{ + .qid = HPWORK, + .thread_id = INVALID_PROCESS_ID, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int k_work_queue_native(struct k_work_q *queue, + struct work_s *work, + worker_t worker, void *arg, + clock_t delay) +{ + if (queue != NULL && queue->wqueue != NULL) + { + return work_queue_wq(queue->wqueue, work, worker, arg, delay); + } + + return work_queue(queue != NULL ? queue->qid : HPWORK, + work, worker, arg, delay); +} + +static int k_work_cancel_native(struct k_work *work, bool sync) +{ + struct k_work_q *queue = work->queue; + int ret; + + if (queue != NULL && queue->wqueue != NULL) + { + ret = sync ? work_cancel_sync_wq(queue->wqueue, &work->work) : + work_cancel_wq(queue->wqueue, &work->work); + } + else + { + ret = + sync ? work_cancel_sync(queue != NULL ? queue->qid : HPWORK, + &work->work) : + work_cancel(queue != NULL ? queue->qid : HPWORK, + &work->work); + } + + if (ret == 0) + { + work->flags &= ~(K_WORK_QUEUED | K_WORK_DELAYED); + } + + return ret; +} + +static void k_work_handler_wrapper(void *arg) +{ + struct k_work *work = arg; + pid_t previous_thread_id = + work->queue != NULL ? work->queue->thread_id : INVALID_PROCESS_ID; + + if (work->queue != NULL) + { + work->queue->thread_id = nxsched_gettid(); + } + + work->flags &= ~(K_WORK_QUEUED | K_WORK_DELAYED); + work->flags |= K_WORK_RUNNING; + + work->handler(work); + + work->flags &= ~K_WORK_RUNNING; + + if (work->queue != NULL) + { + work->queue->thread_id = previous_thread_id; + } +} + +void k_work_init(struct k_work *work, k_work_handler_t handler) +{ + memset(work, 0, sizeof(*work)); + work->handler = handler; + work->queue = &k_sys_work_q; +} + +void k_work_init_delayable(struct k_work_delayable *dwork, + k_work_handler_t handler) +{ + memset(dwork, 0, sizeof(*dwork)); + dwork->work.handler = handler; + dwork->work.queue = &k_sys_work_q; + dwork->queue = &k_sys_work_q; +} + +int k_work_submit(struct k_work *work) +{ + return k_work_submit_to_queue(&k_sys_work_q, work); +} + +int k_work_submit_to_queue(struct k_work_q *queue, struct k_work *work) +{ + int ret; + + work->queue = queue != NULL ? queue : &k_sys_work_q; + work->flags |= K_WORK_QUEUED; + work->flags &= ~K_WORK_DELAYED; + + ret = k_work_queue_native(work->queue, &work->work, + k_work_handler_wrapper, work, 0); + if (ret < 0) + { + work->flags &= ~K_WORK_QUEUED; + } + + return ret; +} + +int k_work_schedule(struct k_work_delayable *dwork, clock_t delay) +{ + if ((dwork->work.flags & (K_WORK_QUEUED | K_WORK_DELAYED)) != 0) + { + return 0; + } + + return k_work_reschedule(dwork, delay); +} + +int k_work_schedule_for_queue(struct k_work_q *queue, + struct k_work_delayable *dwork, + clock_t delay) +{ + dwork->queue = queue != NULL ? queue : &k_sys_work_q; + dwork->work.queue = dwork->queue; + + return k_work_schedule(dwork, delay); +} + +int k_work_cancel_delayable(struct k_work_delayable *dwork) +{ + return k_work_cancel_native(&dwork->work, false); +} + +int k_work_cancel_delayable_sync(struct k_work_delayable *dwork, + struct k_work_sync *sync) +{ + (void)sync; + return k_work_cancel_native(&dwork->work, true); +} + +int k_work_reschedule(struct k_work_delayable *dwork, clock_t delay) +{ + int ret; + + (void)k_work_cancel_delayable(dwork); + + if (dwork->work.queue == NULL) + { + dwork->work.queue = dwork->queue != NULL ? dwork->queue : + &k_sys_work_q; + } + + dwork->queue = dwork->work.queue; + + if (delay < 0) + { + dwork->work.flags &= ~(K_WORK_QUEUED | K_WORK_DELAYED); + return 0; + } + + dwork->work.flags |= delay > 0 ? K_WORK_DELAYED : K_WORK_QUEUED; + dwork->work.flags &= delay > 0 ? ~K_WORK_QUEUED : ~K_WORK_DELAYED; + + ret = k_work_queue_native(dwork->work.queue, &dwork->work.work, + k_work_handler_wrapper, &dwork->work, delay); + if (ret < 0) + { + dwork->work.flags &= ~(K_WORK_QUEUED | K_WORK_DELAYED); + } + + return ret; +} + +int k_work_reschedule_for_queue(struct k_work_q *queue, + struct k_work_delayable *dwork, + clock_t delay) +{ + dwork->queue = queue != NULL ? queue : &k_sys_work_q; + dwork->work.queue = dwork->queue; + + return k_work_reschedule(dwork, delay); +} + +bool k_work_is_pending(const struct k_work *work) +{ + return (work->flags & (K_WORK_QUEUED | K_WORK_DELAYED)) != 0; +} + +int k_work_busy_get(const struct k_work *work) +{ + return work->flags; +} + +int k_work_flush(struct k_work *work, struct k_work_sync *sync) +{ + (void)sync; + + while (k_work_busy_get(work) != 0) + { + sched_yield(); + } + + return 0; +} + +int k_work_cancel(struct k_work *work) +{ + return k_work_cancel_native(work, false); +} + +int k_work_cancel_sync(struct k_work *work, struct k_work_sync *sync) +{ + (void)sync; + return k_work_cancel_native(work, true); +} + +struct k_work_delayable *k_work_delayable_from_work(struct k_work *work) +{ + return CONTAINER_OF(work, struct k_work_delayable, work); +} + +int k_work_delayable_busy_get(const struct k_work_delayable *dwork) +{ + return k_work_busy_get(&dwork->work); +} + +clock_t k_work_delayable_remaining_get(const struct k_work_delayable *dwork) +{ + clock_t remaining; + + if ((dwork->work.flags & K_WORK_DELAYED) == 0) + { + return 0; + } + + remaining = work_timeleft(&dwork->work.work); + + return remaining > 0 ? remaining : 0; +} + +pid_t k_work_queue_thread_get(struct k_work_q *queue) +{ + return queue != NULL ? queue->thread_id : INVALID_PROCESS_ID; +} + +void k_work_queue_init(struct k_work_q *queue) +{ + if (queue != NULL) + { + memset(queue, 0, sizeof(*queue)); + queue->qid = HPWORK; + queue->thread_id = INVALID_PROCESS_ID; + } +} + +int k_work_queue_start(struct k_work_q *queue, void *stack, + size_t stack_size, int prio, + const struct k_work_queue_config *cfg) +{ + const char *name = cfg != NULL + && cfg->name != NULL ? cfg->name : "bt_wq"; + + if (queue == NULL) + { + return -EINVAL; + } + + queue->wqueue = work_queue_create(name, prio, stack, stack_size, 1); + if (queue->wqueue == NULL) + { + return -ENOMEM; + } + + return 0; +} + +/* ===== Condvar ===== */ + +int k_condvar_init(struct k_condvar *condvar) +{ + return pthread_cond_init(&condvar->cond, NULL); +} + +int k_condvar_wait(struct k_condvar *condvar, rmutex_t *mutex, + clock_t timeout) +{ + (void)mutex; + (void)timeout; + return -ENOSYS; +} + +int k_condvar_broadcast(struct k_condvar *condvar) +{ + return pthread_cond_broadcast(&condvar->cond); +} diff --git a/libs/zblue/compat/src/net_buf/net_buf.c b/libs/zblue/compat/src/net_buf/net_buf.c new file mode 100644 index 0000000000000..cd67e0a3b08a6 --- /dev/null +++ b/libs/zblue/compat/src/net_buf/net_buf.c @@ -0,0 +1,1003 @@ +/**************************************************************************** + * libs/zblue/compat/src/net_buf/net_buf.c + * + * Author: chao an + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: struct_size + * + * Description: + * Calculate the stride between consecutive buffers in the pool based on + * alignment requirements. + ****************************************************************************/ + +static inline size_t struct_size(struct net_buf_pool *pool) +{ + return ((sizeof(struct net_buf) + pool->user_data_size + + (sizeof(void *) - 1)) & ~(sizeof(void *) - 1)); +} + +/**************************************************************************** + * Name: pool_get_uninit + * + * Description: + * Get a pointer to one of the uninitialized buffers from the pool. + ****************************************************************************/ + +static inline struct net_buf *pool_get_uninit(struct net_buf_pool *pool, + uint16_t uninit_count) +{ + size_t stride = struct_size(pool); + size_t byte_offset = (pool->buf_count - uninit_count) * stride; + struct net_buf *buf; + + buf = (struct net_buf *)(((uint8_t *)pool->__bufs) + byte_offset); + + buf->pool = pool; + buf->user_data_size = pool->user_data_size; + + return buf; +} + +/**************************************************************************** + * Name: data_alloc + * + * Description: + * Allocate data storage for a buffer. + ****************************************************************************/ + +static uint8_t *data_alloc(struct net_buf *buf, size_t *size) +{ + struct net_buf_pool *pool = buf->pool; + + return pool->alloc->cb->alloc(buf, size, false); +} + +/**************************************************************************** + * Name: data_ref + * + * Description: + * Add a reference to the data region. + ****************************************************************************/ + +static uint8_t *data_ref(struct net_buf *buf, uint8_t *data) +{ + struct net_buf_pool *pool = buf->pool; + + return pool->alloc->cb->ref(buf, data); +} + +/**************************************************************************** + * Data allocator callbacks -- Fixed + ****************************************************************************/ + +/**************************************************************************** + * Name: fixed_data_alloc + ****************************************************************************/ + +static uint8_t *fixed_data_alloc(struct net_buf *buf, size_t *size, + bool block) +{ + struct net_buf_pool *pool = buf->pool; + const struct net_buf_pool_fixed *fixed = pool->alloc->alloc_data; + + *size = pool->alloc->max_alloc_size; + + return fixed->data_pool + *size * net_buf_id(buf); +} + +/**************************************************************************** + * Name: fixed_data_unref + ****************************************************************************/ + +static void fixed_data_unref(struct net_buf *buf, uint8_t *data) +{ + /* Nothing needed for fixed-size data pools */ +} + +/**************************************************************************** + * Name: fixed_data_ref + * + * Description: + * Fixed pools don't support data referencing (data is per-buffer). + * Returns NULL so net_buf_clone falls back to full copy. + ****************************************************************************/ + +static uint8_t *fixed_data_ref(struct net_buf *buf, uint8_t *data) +{ + return NULL; +} + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +const struct net_buf_data_cb g_net_buf_fixed_cb = +{ + .alloc = fixed_data_alloc, + .ref = fixed_data_ref, + .unref = fixed_data_unref, +}; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: net_buf_alloc_len + * + * Description: + * Allocate a new buffer from a pool with a specific data size. + * For fixed pools, size is ignored (max_alloc_size is used). + * + * timeout < 0 : wait forever until a buffer is available + * timeout == 0 : non-blocking, return NULL immediately if pool empty + * timeout > 0 : wait up to timeout milliseconds + ****************************************************************************/ + +struct net_buf *net_buf_alloc_len(struct net_buf_pool *pool, size_t size, + int32_t timeout) +{ + struct net_buf *buf; + irqstate_t flags; + + DEBUGASSERT(pool != NULL); + + /* Prevent race conditions when accessing pool->uninit_count */ + + flags = spin_lock_irqsave(&pool->lock); + + /* If there are uninitialized buffers we're guaranteed to succeed */ + + if (pool->uninit_count > 0) + { + uint16_t uninit_count; + + /* If this is not the first access to the pool, we can be + * opportunistic and try to fetch a previously used buffer + * from the free list. + */ + + if (pool->uninit_count < pool->buf_count) + { + struct list_node *node; + + node = list_remove_head(&pool->free_list); + if (node != NULL) + { + buf = list_container_of(node, struct net_buf, node); + spin_unlock_irqrestore(&pool->lock, flags); + goto success; + } + } + + uninit_count = pool->uninit_count--; + spin_unlock_irqrestore(&pool->lock, flags); + + buf = pool_get_uninit(pool, uninit_count); + goto success; + } + + /* All buffers initialized. Try to get from free list once. */ + + buf = list_remove_head_type(&pool->free_list, struct net_buf, node); + + spin_unlock_irqrestore(&pool->lock, flags); + + if (buf != NULL) + { + goto success; + } + + /* Non-blocking: no buffer available */ + + if (timeout == 0) + { + return NULL; + } + + /* Blocking path: wait for a buffer to be freed via pool->sem. + * Loop to handle spurious wakeups. + */ + + while (1) + { + int ret; + + if (timeout < 0) + { + ret = nxsem_wait(&pool->sem); + } + else + { + ret = nxsem_tickwait(&pool->sem, MSEC2TICK(timeout)); + } + + if (ret < 0) + { + return NULL; + } + + flags = spin_lock_irqsave(&pool->lock); + + buf = list_remove_head_type(&pool->free_list, struct net_buf, node); + if (buf != NULL) + { + spin_unlock_irqrestore(&pool->lock, flags); + goto success; + } + + spin_unlock_irqrestore(&pool->lock, flags); + } + +success: + if (pool->alloc->max_alloc_size > 0) + { + size_t data_size = size; + + buf->__buf = data_alloc(buf, &data_size); + if (buf->__buf == NULL) + { + net_buf_destroy(buf); + return NULL; + } + } + else + { + buf->__buf = NULL; + } + + buf->ref = 1U; + buf->flags = 0U; + buf->frags = NULL; + list_clear_node(&buf->node); + buf->size = pool->alloc->max_alloc_size > 0 ? + (uint16_t)pool->alloc->max_alloc_size : 0; + memset(buf->user_data, 0, buf->user_data_size); + net_buf_reset(buf); + + return buf; +} + +/**************************************************************************** + * Name: net_buf_alloc_fixed + * + * Description: + * Allocate a buffer from a pool with the pool's default data size. + ****************************************************************************/ + +struct net_buf *net_buf_alloc_fixed(struct net_buf_pool *pool, + int32_t timeout) +{ + return net_buf_alloc_len(pool, pool->alloc->max_alloc_size, timeout); +} + +/**************************************************************************** + * Name: net_buf_alloc_with_data + * + * Description: + * Allocate a new buffer from a pool but with external data pointer. + ****************************************************************************/ + +struct net_buf *net_buf_alloc_with_data(struct net_buf_pool *pool, + void *data, size_t size, + int32_t timeout) +{ + struct net_buf *buf; + + buf = net_buf_alloc_len(pool, 0, timeout); + if (buf == NULL) + { + return NULL; + } + + net_buf_simple_init_with_data(&buf->b, data, size); + buf->flags = NET_BUF_EXTERNAL_DATA; + + return buf; +} + +/**************************************************************************** + * Name: net_buf_reset + * + * Description: + * Reset buffer data and flags so it can be reused for other purposes. + ****************************************************************************/ + +void net_buf_reset(struct net_buf *buf) +{ + DEBUGASSERT(buf != NULL); + DEBUGASSERT(buf->frags == NULL); + + net_buf_simple_reset(&buf->b); +} + +/**************************************************************************** + * Name: net_buf_slist_put + * + * Description: + * Append a buffer to a singly-linked list (thread-safe with spinlock). + ****************************************************************************/ + +static spinlock_t g_net_buf_slist_lock = SP_UNLOCKED; + +void net_buf_slist_put(struct list_node *list, struct net_buf *buf) +{ + irqstate_t flags; + + DEBUGASSERT(list != NULL); + DEBUGASSERT(buf != NULL); + + flags = spin_lock_irqsave(&g_net_buf_slist_lock); + list_add_tail(list, &buf->node); + spin_unlock_irqrestore(&g_net_buf_slist_lock, flags); +} + +/**************************************************************************** + * Name: net_buf_slist_get + * + * Description: + * Get a buffer from a singly-linked list (thread-safe with spinlock). + ****************************************************************************/ + +struct net_buf *net_buf_slist_get(struct list_node *list) +{ + struct list_node *node; + irqstate_t flags; + + DEBUGASSERT(list != NULL); + + flags = spin_lock_irqsave(&g_net_buf_slist_lock); + node = list_remove_head(list); + spin_unlock_irqrestore(&g_net_buf_slist_lock, flags); + + return node != NULL ? list_container_of(node, struct net_buf, node) : NULL; +} + +/**************************************************************************** + * Name: net_buf_lifo_init + * + * Description: + * Initialize a LIFO stack for inter-thread buffer passing. + ****************************************************************************/ + +void net_buf_lifo_init(struct net_buf_lifo *lifo) +{ + DEBUGASSERT(lifo != NULL); + + list_initialize(&lifo->list); + lifo->lock = SP_UNLOCKED; + nxsem_init(&lifo->sem, 0, 0); +} + +/**************************************************************************** + * Name: net_buf_lifo_put + * + * Description: + * Push a buffer onto the LIFO stack (producer side). The caller + * transfers ownership of the buffer reference to the LIFO. This + * function is safe to call from ISR context. + ****************************************************************************/ + +void net_buf_lifo_put(struct net_buf_lifo *lifo, struct net_buf *buf) +{ + irqstate_t flags; + + DEBUGASSERT(lifo != NULL); + DEBUGASSERT(buf != NULL); + + flags = spin_lock_irqsave(&lifo->lock); + + list_add_head(&lifo->list, &buf->node); + + spin_unlock_irqrestore(&lifo->lock, flags); + + /* Wake up one blocked consumer */ + + nxsem_post(&lifo->sem); +} + +/**************************************************************************** + * Name: net_buf_lifo_get + * + * Description: + * Pop a buffer from the LIFO stack (consumer side). + * + * timeout < 0 : wait forever until a buffer is available + * timeout == 0 : non-blocking, return NULL immediately if empty + * timeout > 0 : wait up to timeout milliseconds + * + * The caller takes ownership of the returned buffer reference. + * + * Note: do NOT call with timeout != 0 from ISR context. + ****************************************************************************/ + +struct net_buf *net_buf_lifo_get(struct net_buf_lifo *lifo, int32_t timeout) +{ + struct net_buf *buf; + irqstate_t flags; + + DEBUGASSERT(lifo != NULL); + + /* Non-blocking path */ + + if (timeout == 0) + { + flags = spin_lock_irqsave(&lifo->lock); + + buf = list_remove_head_type(&lifo->list, struct net_buf, node); + + spin_unlock_irqrestore(&lifo->lock, flags); + + return buf; + } + + /* Blocking path (timeout < 0: forever, timeout > 0: ms timeout). + * Loop to handle spurious wakeups and timeout restarts. + */ + + while (1) + { + int ret; + + if (timeout < 0) + { + ret = nxsem_wait(&lifo->sem); + } + else + { + ret = nxsem_tickwait(&lifo->sem, MSEC2TICK(timeout)); + } + + /* Timed out or interrupted */ + + if (ret < 0) + { + return NULL; + } + + flags = spin_lock_irqsave(&lifo->lock); + + buf = list_remove_head_type(&lifo->list, struct net_buf, node); + if (buf != NULL) + { + spin_unlock_irqrestore(&lifo->lock, flags); + return buf; + } + + spin_unlock_irqrestore(&lifo->lock, flags); + } +} + +/**************************************************************************** + * Name: net_buf_unref + * + * Description: + * Decrement the reference count of a buffer and free it back to the + * pool when the count reaches zero. Also unreferences the fragment chain. + ****************************************************************************/ + +void net_buf_unref(struct net_buf *buf) +{ + DEBUGASSERT(buf != NULL); + + while (buf != NULL) + { + struct net_buf *frags = buf->frags; + struct net_buf_pool *pool; + irqstate_t flags; + uint8_t old_ref; + + old_ref = buf->ref; + DEBUGASSERT(old_ref != 0); + + if (old_ref == 1) + { + buf->ref = 0; + } + else + { + buf->ref = old_ref - 1; + return; + } + + buf->data = NULL; + buf->frags = NULL; + + pool = buf->pool; + + if (pool->destroy != NULL) + { + pool->destroy(buf); + } + else + { + /* Return to pool free list (LIFO) */ + + flags = spin_lock_irqsave(&pool->lock); + list_add_head(&pool->free_list, &buf->node); + spin_unlock_irqrestore(&pool->lock, flags); + } + + buf = frags; + } +} + +/**************************************************************************** + * Name: net_buf_ref + * + * Description: + * Increment the reference count of a buffer. + ****************************************************************************/ + +struct net_buf *net_buf_ref(struct net_buf *buf) +{ + DEBUGASSERT(buf != NULL); + DEBUGASSERT(buf->ref < 0xff); + + buf->ref++; + + return buf; +} + +/**************************************************************************** + * Name: net_buf_clone + ****************************************************************************/ + +struct net_buf *net_buf_clone(struct net_buf *buf, int32_t timeout) +{ + struct net_buf_pool *pool; + struct net_buf *clone; + + DEBUGASSERT(buf != NULL); + + pool = buf->pool; + + clone = net_buf_alloc_len(pool, 0, timeout); + if (clone == NULL) + { + return NULL; + } + + /* If the pool supports data referencing use that. Otherwise + * we need to allocate new data and make a copy. + */ + + if (buf->__buf != NULL && + !(buf->flags & NET_BUF_EXTERNAL_DATA)) + { + uint8_t *ref_data = data_ref(clone, buf->__buf); + + if (ref_data != NULL) + { + clone->__buf = ref_data; + clone->data = clone->__buf + net_buf_headroom(buf); + clone->len = buf->len; + clone->size = buf->size; + } + else + { + /* Fall back to full copy */ + + size_t data_size = buf->size; + + clone->__buf = data_alloc(clone, &data_size); + if (clone->__buf == NULL || + data_size < buf->size) + { + net_buf_destroy(clone); + return NULL; + } + + clone->size = data_size; + clone->data = clone->__buf + net_buf_headroom(buf); + net_buf_add_mem(clone, buf->data, buf->len); + } + } + else + { + if (buf->__buf != NULL) + { + size_t data_size = buf->size; + + clone->__buf = data_alloc(clone, &data_size); + if (clone->__buf == NULL || + data_size < buf->size) + { + net_buf_destroy(clone); + return NULL; + } + + clone->size = data_size; + clone->data = clone->__buf + net_buf_headroom(buf); + net_buf_add_mem(clone, buf->data, buf->len); + } + else + { + clone->data = NULL; + clone->len = 0; + clone->size = 0; + } + } + + memcpy(clone->user_data, buf->user_data, clone->user_data_size); + + return clone; +} + +/**************************************************************************** + * Name: net_buf_id + * + * Description: + * Get a zero-based index for a buffer, based on its placement in its + * buffer pool. + ****************************************************************************/ + +int net_buf_id(const struct net_buf *buf) +{ + struct net_buf_pool *pool = buf->pool; + size_t stride = struct_size(pool); + ptrdiff_t offset = (uint8_t *)buf - (uint8_t *)pool->__bufs; + + return offset / stride; +} + +/**************************************************************************** + * Name: net_buf_user_data_copy + ****************************************************************************/ + +int net_buf_user_data_copy(struct net_buf *dst, const struct net_buf *src) +{ + DEBUGASSERT(dst != NULL); + DEBUGASSERT(src != NULL); + + if (dst == src) + { + return 0; + } + + if (dst->user_data_size < src->user_data_size) + { + return -EINVAL; + } + + memcpy(dst->user_data, src->user_data, src->user_data_size); + + return 0; +} + +/**************************************************************************** + * Fragment Operations + ****************************************************************************/ + +/**************************************************************************** + * Name: net_buf_frag_last + ****************************************************************************/ + +struct net_buf *net_buf_frag_last(struct net_buf *buf) +{ + DEBUGASSERT(buf != NULL); + + while (buf->frags != NULL) + { + buf = buf->frags; + } + + return buf; +} + +/**************************************************************************** + * Name: net_buf_frag_insert + ****************************************************************************/ + +void net_buf_frag_insert(struct net_buf *parent, struct net_buf *frag) +{ + DEBUGASSERT(parent != NULL); + DEBUGASSERT(frag != NULL); + + if (parent->frags != NULL) + { + net_buf_frag_last(frag)->frags = parent->frags; + } + + /* Take ownership of the fragment reference */ + + parent->frags = frag; +} + +/**************************************************************************** + * Name: net_buf_frag_add + ****************************************************************************/ + +struct net_buf *net_buf_frag_add(struct net_buf *head, struct net_buf *frag) +{ + DEBUGASSERT(frag != NULL); + + if (head == NULL) + { + return net_buf_ref(frag); + } + + net_buf_frag_insert(net_buf_frag_last(head), frag); + + return head; +} + +/**************************************************************************** + * Name: net_buf_frag_del + ****************************************************************************/ + +struct net_buf *net_buf_frag_del(struct net_buf *parent, + struct net_buf *frag) +{ + struct net_buf *next_frag; + + DEBUGASSERT(frag != NULL); + + if (parent != NULL) + { + DEBUGASSERT(parent->frags != NULL); + DEBUGASSERT(parent->frags == frag); + parent->frags = frag->frags; + } + + next_frag = frag->frags; + + frag->frags = NULL; + + net_buf_unref(frag); + + return next_frag; +} + +/**************************************************************************** + * Utility Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: net_buf_linearize + * + * Description: + * Copy data from a fragment chain into a linear destination buffer. + ****************************************************************************/ + +size_t net_buf_linearize(void *dst, size_t dst_len, + const struct net_buf *src, + size_t offset, size_t len) +{ + const struct net_buf *frag; + size_t to_copy; + size_t copied; + + if (len > dst_len) + { + len = dst_len; + } + + frag = src; + + /* Find the right fragment to start copying from */ + + while (frag != NULL && offset >= frag->len) + { + offset -= frag->len; + frag = frag->frags; + } + + /* Traverse the fragment chain until len bytes are copied */ + + copied = 0; + while (frag != NULL && len > 0) + { + if (len < frag->len - offset) + { + to_copy = len; + } + else + { + to_copy = frag->len - offset; + } + + memcpy((uint8_t *)dst + copied, frag->data + offset, to_copy); + + copied += to_copy; + + /* to_copy is always <= len */ + + len -= to_copy; + frag = frag->frags; + + /* After the first iteration, this value will be 0 */ + + offset = 0; + } + + return copied; +} + +/**************************************************************************** + * Name: net_buf_append_bytes + * + * Description: + * Append multiple bytes to a buffer chain, creating new fragments as + * needed. + ****************************************************************************/ + +size_t net_buf_append_bytes(struct net_buf *buf, size_t len, + const void *value, int32_t timeout, + net_buf_allocator_cb allocate_cb, + void *user_data) +{ + struct net_buf *tail = net_buf_frag_last(buf); + struct net_buf *frag = tail; + size_t added_len = 0; + const uint8_t *value8 = value; + + do + { + uint16_t count; + + if (len < net_buf_tailroom(frag)) + { + count = len; + } + else + { + count = net_buf_tailroom(frag); + } + + net_buf_add_mem(frag, value8, count); + len -= count; + added_len += count; + value8 += count; + + if (len == 0) + { + return added_len; + } + + if (allocate_cb != NULL) + { + frag = allocate_cb(timeout, user_data); + } + else + { + /* Allocate from the same pool */ + + struct net_buf_pool *pool = buf->pool; + size_t max_size = pool->alloc->max_alloc_size; + + frag = net_buf_alloc_len(pool, + max_size > 0 ? (len < max_size ? len : max_size) : len, + timeout); + } + + if (frag == NULL) + { + return added_len; + } + + net_buf_frag_insert(tail, frag); + tail = net_buf_frag_last(frag); + } + while (1); + + /* Unreachable */ + + return 0; +} + +/**************************************************************************** + * Name: net_buf_data_match + * + * Description: + * Match data in a fragment chain against a byte sequence. + ****************************************************************************/ + +size_t net_buf_data_match(const struct net_buf *buf, size_t offset, + const void *data, size_t len) +{ + const uint8_t *dptr = data; + const uint8_t *bptr; + size_t compared = 0; + size_t to_compare; + size_t i; + + if (buf == NULL || data == NULL) + { + return compared; + } + + /* Find the right fragment to start comparison */ + + while (buf != NULL && offset >= buf->len) + { + offset -= buf->len; + buf = buf->frags; + } + + while (buf != NULL && len > 0) + { + bptr = buf->data + offset; + + if (len < buf->len - offset) + { + to_compare = len; + } + else + { + to_compare = buf->len - offset; + } + + for (i = 0; i < to_compare; ++i) + { + if (dptr[compared] != bptr[i]) + { + return compared; + } + + compared++; + } + + len -= to_compare; + buf = buf->frags; + offset = 0; + } + + return compared; +} + +/**************************************************************************** + * Name: net_buf_frags_len + * + * Description: + * Calculate the total length of a fragment chain. + ****************************************************************************/ + +size_t net_buf_frags_len(struct net_buf *buf) +{ + size_t bytes = 0; + + while (buf != NULL) + { + bytes += buf->len; + buf = buf->frags; + } + + return bytes; +} diff --git a/libs/zblue/compat/src/net_buf/net_buf_simple.c b/libs/zblue/compat/src/net_buf/net_buf_simple.c new file mode 100644 index 0000000000000..93b23db51d6c6 --- /dev/null +++ b/libs/zblue/compat/src/net_buf/net_buf_simple.c @@ -0,0 +1,395 @@ +/**************************************************************************** + * libs/zblue/compat/src/net_buf/net_buf_simple.c + * + * Author: chao an + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: net_buf_simple_init_with_data + ****************************************************************************/ + +void net_buf_simple_init_with_data(struct net_buf_simple *buf, + void *data, size_t size) +{ + buf->__buf = data; + buf->data = data; + buf->size = size; + buf->len = size; +} + +/**************************************************************************** + * Name: net_buf_simple_reserve + ****************************************************************************/ + +void net_buf_simple_reserve(struct net_buf_simple *buf, size_t reserve) +{ + DEBUGASSERT(buf != NULL); + DEBUGASSERT(buf->len == 0U); + + buf->data = buf->__buf + reserve; +} + +/**************************************************************************** + * Name: net_buf_simple_clone + ****************************************************************************/ + +void net_buf_simple_clone(const struct net_buf_simple *original, + struct net_buf_simple *clone) +{ + *clone = *original; +} + +/**************************************************************************** + * Name: net_buf_simple_add + ****************************************************************************/ + +void *net_buf_simple_add(struct net_buf_simple *buf, size_t len) +{ + uint8_t *tail; + + DEBUGASSERT(buf != NULL); + DEBUGASSERT(len <= net_buf_simple_tailroom(buf)); + + tail = net_buf_simple_tail(buf); + buf->len += len; + return tail; +} + +/**************************************************************************** + * Name: net_buf_simple_add_mem + ****************************************************************************/ + +void *net_buf_simple_add_mem(struct net_buf_simple *buf, const void *mem, + size_t len) +{ + void *tail; + + tail = net_buf_simple_add(buf, len); + + return memcpy(tail, mem, len); +} + +/**************************************************************************** + * Name: net_buf_simple_add_u8 + ****************************************************************************/ + +uint8_t *net_buf_simple_add_u8(struct net_buf_simple *buf, uint8_t val) +{ + uint8_t *u8; + + u8 = net_buf_simple_add(buf, 1); + + *u8 = val; + return u8; +} + +/**************************************************************************** + * Name: net_buf_simple_add_le16/be16 + ****************************************************************************/ + +void net_buf_simple_add_le16(struct net_buf_simple *buf, uint16_t val) +{ + *(uint16_t *)net_buf_simple_add(buf, sizeof(val)) = htole16(val); +} + +void net_buf_simple_add_be16(struct net_buf_simple *buf, uint16_t val) +{ + *(uint16_t *)net_buf_simple_add(buf, sizeof(val)) = htobe16(val); +} + +/**************************************************************************** + * Name: net_buf_simple_add_le32/be32 + ****************************************************************************/ + +void net_buf_simple_add_le32(struct net_buf_simple *buf, uint32_t val) +{ + *(uint32_t *)net_buf_simple_add(buf, sizeof(val)) = htole32(val); +} + +void net_buf_simple_add_be32(struct net_buf_simple *buf, uint32_t val) +{ + *(uint32_t *)net_buf_simple_add(buf, sizeof(val)) = htobe32(val); +} + +/**************************************************************************** + * Name: net_buf_simple_add_le64/be64 + ****************************************************************************/ + +void net_buf_simple_add_le64(struct net_buf_simple *buf, uint64_t val) +{ + *(uint64_t *)net_buf_simple_add(buf, sizeof(val)) = htole64(val); +} + +void net_buf_simple_add_be64(struct net_buf_simple *buf, uint64_t val) +{ + *(uint64_t *)net_buf_simple_add(buf, sizeof(val)) = htobe64(val); +} + +/**************************************************************************** + * Name: net_buf_simple_remove_mem + ****************************************************************************/ + +void *net_buf_simple_remove_mem(struct net_buf_simple *buf, size_t len) +{ + DEBUGASSERT(buf != NULL); + DEBUGASSERT(buf->len >= len); + + buf->len -= len; + return buf->data + buf->len; +} + +/**************************************************************************** + * Name: net_buf_simple_remove_u8 + ****************************************************************************/ + +uint8_t net_buf_simple_remove_u8(struct net_buf_simple *buf) +{ + return *(uint8_t *)net_buf_simple_remove_mem(buf, sizeof(uint8_t)); +} + +/**************************************************************************** + * Name: net_buf_simple_remove_le16/be16 + ****************************************************************************/ + +uint16_t net_buf_simple_remove_le16(struct net_buf_simple *buf) +{ + return le16toh(*(uint16_t *)net_buf_simple_remove_mem(buf, + sizeof(uint16_t))); +} + +uint16_t net_buf_simple_remove_be16(struct net_buf_simple *buf) +{ + return be16toh(*(uint16_t *)net_buf_simple_remove_mem(buf, + sizeof(uint16_t))); +} + +/**************************************************************************** + * Name: net_buf_simple_remove_le32/be32 + ****************************************************************************/ + +uint32_t net_buf_simple_remove_le32(struct net_buf_simple *buf) +{ + return le32toh(*(uint32_t *)net_buf_simple_remove_mem(buf, + sizeof(uint32_t))); +} + +uint32_t net_buf_simple_remove_be32(struct net_buf_simple *buf) +{ + return be32toh(*(uint32_t *)net_buf_simple_remove_mem(buf, + sizeof(uint32_t))); +} + +/**************************************************************************** + * Name: net_buf_simple_remove_le64/be64 + ****************************************************************************/ + +uint64_t net_buf_simple_remove_le64(struct net_buf_simple *buf) +{ + return le64toh(*(uint64_t *)net_buf_simple_remove_mem(buf, + sizeof(uint64_t))); +} + +uint64_t net_buf_simple_remove_be64(struct net_buf_simple *buf) +{ + return be64toh(*(uint64_t *)net_buf_simple_remove_mem(buf, + sizeof(uint64_t))); +} + +/**************************************************************************** + * Name: net_buf_simple_push + ****************************************************************************/ + +void *net_buf_simple_push(struct net_buf_simple *buf, size_t len) +{ + DEBUGASSERT(buf != NULL); + DEBUGASSERT(len <= net_buf_simple_headroom(buf)); + + buf->data -= len; + buf->len += len; + return buf->data; +} + +/**************************************************************************** + * Name: net_buf_simple_push_mem + ****************************************************************************/ + +void *net_buf_simple_push_mem(struct net_buf_simple *buf, const void *mem, + size_t len) +{ + void *data; + + data = net_buf_simple_push(buf, len); + + return memcpy(data, mem, len); +} + +/**************************************************************************** + * Name: net_buf_simple_push_u8 + ****************************************************************************/ + +void net_buf_simple_push_u8(struct net_buf_simple *buf, uint8_t val) +{ + uint8_t *data = net_buf_simple_push(buf, 1); + + *data = val; +} + +/**************************************************************************** + * Name: net_buf_simple_push_le16/be16 + ****************************************************************************/ + +void net_buf_simple_push_le16(struct net_buf_simple *buf, uint16_t val) +{ + *(uint16_t *)net_buf_simple_push(buf, sizeof(val)) = htole16(val); +} + +void net_buf_simple_push_be16(struct net_buf_simple *buf, uint16_t val) +{ + *(uint16_t *)net_buf_simple_push(buf, sizeof(val)) = htobe16(val); +} + +/**************************************************************************** + * Name: net_buf_simple_push_le32/be32 + ****************************************************************************/ + +void net_buf_simple_push_le32(struct net_buf_simple *buf, uint32_t val) +{ + *(uint32_t *)net_buf_simple_push(buf, sizeof(val)) = htole32(val); +} + +void net_buf_simple_push_be32(struct net_buf_simple *buf, uint32_t val) +{ + *(uint32_t *)net_buf_simple_push(buf, sizeof(val)) = htobe32(val); +} + +/**************************************************************************** + * Name: net_buf_simple_push_le64/be64 + ****************************************************************************/ + +void net_buf_simple_push_le64(struct net_buf_simple *buf, uint64_t val) +{ + *(uint64_t *)net_buf_simple_push(buf, sizeof(val)) = htole64(val); +} + +void net_buf_simple_push_be64(struct net_buf_simple *buf, uint64_t val) +{ + *(uint64_t *)net_buf_simple_push(buf, sizeof(val)) = htobe64(val); +} + +/**************************************************************************** + * Name: net_buf_simple_pull + ****************************************************************************/ + +void *net_buf_simple_pull(struct net_buf_simple *buf, size_t len) +{ + DEBUGASSERT(buf != NULL); + DEBUGASSERT(buf->len >= len); + + buf->len -= len; + return buf->data += len; +} + +/**************************************************************************** + * Name: net_buf_simple_pull_mem + ****************************************************************************/ + +void *net_buf_simple_pull_mem(struct net_buf_simple *buf, size_t len) +{ + void *data = buf->data; + + DEBUGASSERT(buf != NULL); + DEBUGASSERT(buf->len >= len); + + buf->len -= len; + buf->data += len; + + return data; +} + +/**************************************************************************** + * Name: net_buf_simple_pull_u8 + ****************************************************************************/ + +uint8_t net_buf_simple_pull_u8(struct net_buf_simple *buf) +{ + return *(uint8_t *)net_buf_simple_pull_mem(buf, sizeof(uint8_t)); +} + +/**************************************************************************** + * Name: net_buf_simple_pull_le16/be16 + ****************************************************************************/ + +uint16_t net_buf_simple_pull_le16(struct net_buf_simple *buf) +{ + return le16toh(*(uint16_t *)net_buf_simple_pull_mem(buf, + sizeof(uint16_t))); +} + +uint16_t net_buf_simple_pull_be16(struct net_buf_simple *buf) +{ + return be16toh(*(uint16_t *)net_buf_simple_pull_mem(buf, + sizeof(uint16_t))); +} + +/**************************************************************************** + * Name: net_buf_simple_pull_le32/be32 + ****************************************************************************/ + +uint32_t net_buf_simple_pull_le32(struct net_buf_simple *buf) +{ + return le32toh(*(uint32_t *)net_buf_simple_pull_mem(buf, + sizeof(uint32_t))); +} + +uint32_t net_buf_simple_pull_be32(struct net_buf_simple *buf) +{ + return be32toh(*(uint32_t *)net_buf_simple_pull_mem(buf, + sizeof(uint32_t))); +} + +/**************************************************************************** + * Name: net_buf_simple_pull_le64/be64 + ****************************************************************************/ + +uint64_t net_buf_simple_pull_le64(struct net_buf_simple *buf) +{ + return le64toh(*(uint64_t *)net_buf_simple_pull_mem(buf, + sizeof(uint64_t))); +} + +uint64_t net_buf_simple_pull_be64(struct net_buf_simple *buf) +{ + return be64toh(*(uint64_t *)net_buf_simple_pull_mem(buf, + sizeof(uint64_t))); +} diff --git a/libs/zblue/compat/src/sections/defines.c b/libs/zblue/compat/src/sections/defines.c new file mode 100644 index 0000000000000..ae9e926c2e2ca --- /dev/null +++ b/libs/zblue/compat/src/sections/defines.c @@ -0,0 +1,393 @@ +/**************************************************************************** + * libs/zblue/compat/src/sections/defines.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (C) 2024 Xiaomi Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. You may obtain a + * copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include + +/* bt_l2cap_fixed_chan START */ + +extern struct bt_l2cap_fixed_chan bt_l2cap_fixed_chanz_att_fixed_chan; +extern struct bt_l2cap_fixed_chan bt_l2cap_fixed_chanle_fixed_chan; +extern struct bt_l2cap_fixed_chan bt_l2cap_fixed_chansmp_fixed_chan; + +struct bt_l2cap_fixed_chan *_bt_l2cap_fixed_chan_list[] = +{ +#if defined(CONFIG_BT_CONN) + &bt_l2cap_fixed_chanz_att_fixed_chan, + &bt_l2cap_fixed_chanle_fixed_chan, +#endif /* CONFIG_BT_CONN */ +#if defined(CONFIG_BT_SMP) + &bt_l2cap_fixed_chansmp_fixed_chan, +#endif /* CONFIG_BT_SMP */ + NULL, +}; + +/* bt_l2cap_fixed_chan END */ + +/* bt_l2cap_br_fixed_chan START */ +#if defined(CONFIG_BT_CLASSIC) +extern struct bt_l2cap_br_fixed_chan br_fixed_chan; +#if defined(CONFIG_BT_SMP_BR) +extern struct bt_l2cap_br_fixed_chan smp_br_fixed_chan; +#endif /* CONFIG_BT_SMP_BR */ + +struct bt_l2cap_br_fixed_chan *_bt_l2cap_br_fixed_chan_list[] = +{ + &br_fixed_chan, +#if defined(CONFIG_BT_SMP_BR) + &smp_br_fixed_chan, +#endif /* CONFIG_BT_SMP_BR */ + NULL, +}; +#endif /* CONFIG_BT_CLASSIC */ +/* bt_l2cap_br_fixed_chan END */ + +/* bt_gatt_service_static START */ + +extern const struct bt_gatt_service_static cas_svc; +extern const struct bt_gatt_service_static _2_gap_svc; +extern const struct bt_gatt_service_static _1_gatt_svc; +extern const struct bt_gatt_service_static cts_svc; +extern const struct bt_gatt_service_static dis_svc; +extern const struct bt_gatt_service_static hrs_svc; +extern const struct bt_gatt_service_static tps_svc; +extern const struct bt_gatt_service_static bas; +extern const struct bt_gatt_service_static ias_svc; +#if defined(CONFIG_BT_ZEPHYR_NUS_AUTO_START_BLUETOOTH) +extern const struct bt_gatt_service_static bt_nus_gatt_test_svc; +extern const struct bt_gatt_service_static bt_nus_gatt_echo_svc; +#endif /* CONFIG_BT_ZEPHYR_NUS_AUTO_START_BLUETOOTH */ + +const struct bt_gatt_service_static *_bt_gatt_service_static_list[] = +{ +#if defined(CONFIG_BT_CAP_ACCEPTOR) && !defined(CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER) + &cas_svc, +#endif /* CONFIG_BT_CAP_ACCEPTOR && !CONFIG_BT_CAP_ACCEPTOR_SET_MEMBER */ +#if defined(CONFIG_BT_CONN) + &_2_gap_svc, &_1_gatt_svc, +#endif /* CONFIG_BT_CONN */ +#if defined(CONFIG_BT_CTS) + &cts_svc, +#endif /* CONFIG_BT_CTS */ +#if defined(CONFIG_BT_DIS) + &dis_svc, +#endif /* CONFIG_BT_DIS */ +#if defined(CONFIG_BT_HRS) + &hrs_svc, +#endif /* CONFIG_BT_HRS */ +#if defined(CONFIG_BT_TPS) + &tps_svc, +#endif /* CONFIG_BT_TPS */ +#if defined(CONFIG_BT_BAS) + &bas, +#endif /* CONFIG_BT_BAS */ +#if defined(CONFIG_BT_IAS) + &ias_svc, +#endif /* CONFIG_BT_IAS */ +#if defined(CONFIG_BT_ZEPHYR_NUS_AUTO_START_BLUETOOTH) + &bt_nus_gatt_test_svc, + &bt_nus_gatt_echo_svc, +#endif /* CONFIG_BT_ZEPHYR_NUS_AUTO_START_BLUETOOTH */ + NULL, +}; + +/* bt_gatt_service_static END */ + +/* bt_sco_conn_cb START */ + +const struct bt_sco_conn_cb *_bt_sco_conn_cb_list[] = +{ + NULL, +}; + +/* bt_sco_conn_cb END */ + +/* bt_sco_hci_cb START */ + +struct bt_sco_hci_cb *_bt_sco_hci_cb_list[] = +{ + NULL, +}; + +/* bt_sco_hci_cb END */ + +/* bt_conn_cb START */ + +const struct bt_conn_cb *_bt_conn_cb_list[] = +{ + NULL, +}; + +/* bt_conn_cb END */ + +/* bt_mesh_friend_cb START */ + +extern const struct bt_mesh_friend_cb bt_mesh_friend_cb_friend_cb; +const struct bt_mesh_friend_cb *_bt_mesh_friend_cb_list[] = +{ +#if defined(CONFIG_BT_TESTER) +#if defined(CONFIG_BT_MESH) + &bt_mesh_friend_cb_friend_cb, +#endif /* CONFIG_BT_MESH */ +#endif /* CONFIG_BT_TESTER */ + NULL, +}; + +/* bt_mesh_friend_cb END */ + +/* bt_mesh_subnet_cb START */ + +extern const struct bt_mesh_subnet_cb bt_mesh_subnet_cb_app_keys; +extern const struct bt_mesh_subnet_cb bt_mesh_subnet_cb_beacon; +extern const struct bt_mesh_subnet_cb bt_mesh_subnet_cb_sbr; +extern const struct bt_mesh_subnet_cb bt_mesh_subnet_cb_friend; +extern const struct bt_mesh_subnet_cb bt_mesh_subnet_cb_lpn; +extern const struct bt_mesh_subnet_cb bt_mesh_subnet_cb_proxy_cli; +extern const struct bt_mesh_subnet_cb bt_mesh_subnet_cb_gatt_services; +const struct bt_mesh_subnet_cb *_bt_mesh_subnet_cb_list[] = +{ +#if defined(CONFIG_BT_MESH) + &bt_mesh_subnet_cb_app_keys, + &bt_mesh_subnet_cb_beacon, +#if defined(CONFIG_BT_MESH_BRG_CFG_SRV) + &bt_mesh_subnet_cb_sbr, +#endif /* CONFIG_BT_MESH_BRG_CFG_SRV */ +#if defined(CONFIG_BT_MESH_FRIEND) + &bt_mesh_subnet_cb_friend, +#endif /* CONFIG_BT_MESH_FRIEND */ +#if defined(CONFIG_BT_MESH_LPN) + &bt_mesh_subnet_cb_lpn, +#endif /* CONFIG_BT_MESH_LPN */ +#if defined(CONFIG_BT_MESH_PROXY_CLIENT) + &bt_mesh_subnet_cb_proxy_cli, +#endif /* CONFIG_BT_MESH_PROXY_CLIENT */ +#if defined(CONFIG_BT_MESH_GATT_PROXY) + &bt_mesh_subnet_cb_gatt_services, +#endif /* CONFIG_BT_MESH_GATT_PROXY */ +#endif /* CONFIG_BT_MESH */ + NULL, +}; + +/* bt_mesh_subnet_cb END */ + +/* bt_mesh_proxy_cb START */ + +const struct bt_mesh_proxy_cb *_bt_mesh_proxy_cb_list[] = +{ + NULL, +}; + +/* bt_mesh_proxy_cb END */ + +/* bt_mesh_hb_cb START */ + +extern const struct bt_mesh_hb_cb hb_cb; +const struct bt_mesh_hb_cb *_bt_mesh_hb_cb_list[] = +{ +#if defined(CONFIG_BT_MESH) +#if defined(CONFIG_BT_MESH_DEMO) + &hb_cb, +#endif /* CONFIG_BT_MESH_DEMO */ +#endif /* CONFIG_BT_MESH */ + NULL, +}; + +/* bt_mesh_hb_cb END */ + +/* bt_mesh_app_key_cb START */ + +extern const struct bt_mesh_app_key_cb bt_mesh_app_key_cb_app_key_evt; +const struct bt_mesh_app_key_cb *_bt_mesh_app_key_cb_list[] = +{ +#if defined(CONFIG_BT_MESH) + &bt_mesh_app_key_cb_app_key_evt, +#endif /* CONFIG_BT_MESH */ + NULL, +}; + +/* bt_mesh_app_key_cb END */ + +/* bt_ias_cb START */ +#if defined(CONFIG_BT_IAS) +struct bt_ias_cb *_bt_ias_cb_list[] = +{ + NULL, +}; +#endif /* CONFIG_BT_IAS */ +/* bt_ias_cb END */ + +/* init_entry START */ +#if defined(CONFIG_BT_MONITOR) +extern const struct init_entry _bt_init_bt_monitor_init; +#endif /* CONFIG_BT_MONITOR */ +#if defined(CONFIG_BT_LONG_WQ) +extern const struct init_entry _bt_init_long_wq_init; +#endif /* CONFIG_BT_LONG_WQ */ +#if defined(CONFIG_BT_CONN_TX_NOTIFY_WQ) +extern const struct init_entry _bt_init_bt_conn_tx_workq_init; +#endif /* CONFIG_BT_CONN_TX_NOTIFY_WQ */ +#if defined(CONFIG_BT_TX_PROCESSOR_THREAD) +extern const struct init_entry _bt_init_bt_tx_processor_init; +#endif /* CONFIG_BT_TX_PROCESSOR_THREAD */ +#if defined(CONFIG_BT_BAP_SCAN_DELEGATOR) +extern const struct init_entry _bt_init_scan_delegator_init; +#endif /* CONFIG_BT_BAP_SCAN_DELEGATOR */ +#if defined(CONFIG_BT_CCP_CALL_CONTROL_SERVER) +extern const struct init_entry _bt_init_ccp_server_init; +#endif /* CONFIG_BT_CCP_CALL_CONTROL_SERVER */ +#if defined(CONFIG_BT_MCS) +extern const struct init_entry _bt_init_mcs_init; +#endif /* CONFIG_BT_MCS */ +#if defined(CONFIG_BT_OTS) +extern const struct init_entry _bt_init_bt_gatt_ots_instances_prepare; +extern const struct init_entry _bt_init_bt_gatt_ots_l2cap_init; +#endif /* CONFIG_BT_OTS */ +#if defined(CONFIG_BT_BAP_BROADCAST_SINK) +extern const struct init_entry _bt_init_broadcast_sink_init; +#endif /* CONFIG_BT_BAP_BROADCAST_SINK */ +#if defined(CONFIG_BT_ANS) +extern const struct init_entry _bt_init_ans_init; +#endif /* CONFIG_BT_ANS */ +#if defined(CONFIG_BT_BAS) +extern const struct init_entry _bt_init_bas_init; +#endif /* CONFIG_BT_BAS */ +#if defined(CONFIG_BT_HRS) +extern const struct init_entry _bt_init_hrs_init; +#endif /* CONFIG_BT_HRS */ +#if defined(CONFIG_BT_ZEPHYR_NUS_AUTO_START_BLUETOOTH) +extern const struct init_entry _bt_init_bt_nus_auto_start; +#endif /* CONFIG_BT_ZEPHYR_NUS_AUTO_START_BLUETOOTH */ + +static const struct init_entry *_bt_init_entry_list[] = +{ +#if defined(CONFIG_BT_MONITOR) + &_bt_init_bt_monitor_init, +#endif /* CONFIG_BT_MONITOR */ +#if defined(CONFIG_BT_LONG_WQ) + &_bt_init_long_wq_init, +#endif /* CONFIG_BT_LONG_WQ */ +#if defined(CONFIG_BT_CONN_TX_NOTIFY_WQ) + &_bt_init_bt_conn_tx_workq_init, +#endif /* CONFIG_BT_CONN_TX_NOTIFY_WQ */ +#if defined(CONFIG_BT_TX_PROCESSOR_THREAD) + &_bt_init_bt_tx_processor_init, +#endif /* CONFIG_BT_TX_PROCESSOR_THREAD */ +#if defined(CONFIG_BT_BAP_SCAN_DELEGATOR) + &_bt_init_scan_delegator_init, +#endif /* CONFIG_BT_BAP_SCAN_DELEGATOR */ +#if defined(CONFIG_BT_CCP_CALL_CONTROL_SERVER) + &_bt_init_ccp_server_init, +#endif /* CONFIG_BT_CCP_CALL_CONTROL_SERVER */ +#if defined(CONFIG_BT_MCS) + &_bt_init_mcs_init, +#endif /* CONFIG_BT_MCS */ +#if defined(CONFIG_BT_OTS) + &_bt_init_bt_gatt_ots_instances_prepare, + &_bt_init_bt_gatt_ots_l2cap_init, +#endif /* CONFIG_BT_OTS */ +#if defined(CONFIG_BT_BAP_BROADCAST_SINK) + &_bt_init_broadcast_sink_init, +#endif /* CONFIG_BT_BAP_BROADCAST_SINK */ +#if defined(CONFIG_BT_ANS) + &_bt_init_ans_init, +#endif /* CONFIG_BT_ANS */ +#if defined(CONFIG_BT_BAS) + &_bt_init_bas_init, +#endif /* CONFIG_BT_BAS */ +#if defined(CONFIG_BT_HRS) + &_bt_init_hrs_init, +#endif /* CONFIG_BT_HRS */ +#if defined(CONFIG_BT_ZEPHYR_NUS_AUTO_START_BLUETOOTH) + &_bt_init_bt_nus_auto_start, +#endif /* CONFIG_BT_ZEPHYR_NUS_AUTO_START_BLUETOOTH */ + NULL, +}; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bluetooth_initialize(void) +{ + bool done[sizeof(_bt_init_entry_list) / sizeof(_bt_init_entry_list[0])] = + { + 0 + }; + + int ret = 0; + + for (int level = PRE_KERNEL_1; level <= SMP_BOOT; level++) + { + for (; ; ) + { + int prio = INT_MAX; + bool found = false; + + for (size_t i = 0; _bt_init_entry_list[i] != NULL; i++) + { + const struct init_entry *entry = _bt_init_entry_list[i]; + + if (!done[i] && entry->level == level && entry->prio < prio) + { + prio = entry->prio; + found = true; + } + } + + if (!found) + { + break; + } + + for (size_t i = 0; _bt_init_entry_list[i] != NULL; i++) + { + const struct init_entry *entry = _bt_init_entry_list[i]; + + if (!done[i] && entry->level == level && entry->prio == prio) + { + int err; + + done[i] = true; + err = entry->init(); + if (err != 0 && ret == 0) + { + ret = err; + } + } + } + } + } + + return ret; +} + +/* init_entry END */ diff --git a/libs/zblue/compat/src/subsys/fs/fs.c b/libs/zblue/compat/src/subsys/fs/fs.c new file mode 100644 index 0000000000000..4649ddeaad44c --- /dev/null +++ b/libs/zblue/compat/src/subsys/fs/fs.c @@ -0,0 +1,161 @@ +/**************************************************************************** + * libs/zblue/compat/src/subsys/fs/fs.c + * + * Author: chao an + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include + +/* Simple filesystem-backed settings storage */ + +#define BT_SETTINGS_BASE "/data/bt" + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int settings_subsys_init(void) +{ + mkdir(BT_SETTINGS_BASE, 0755); + return 0; +} + +static int settings_path(const char *name, char *path, size_t len) +{ + /* Replace '/' with '_' for filesystem compatibility */ + + char safe[256]; + size_t i = 0; + + while (name[i] && i < sizeof(safe) - 1) + { + safe[i] = (name[i] == '/') ? '_' : name[i]; + i++; + } + + safe[i] = '\0'; + return snprintf(path, len, BT_SETTINGS_BASE "/%s", safe); +} + +int settings_save_one(const char *name, const void *value, size_t val_len) +{ + char path[320]; + + settings_path(name, path, sizeof(path)); + + int fd = open(path, O_WRONLY | O_CREAT | O_TRUNC, 0644); + + if (fd < 0) + { + return -errno; + } + + ssize_t written = write(fd, value, val_len); + + close(fd); + + return (written == (ssize_t)val_len) ? 0 : -EIO; +} + +int settings_load_subtree_direct(const char *subtree, + settings_load_direct_cb cb, void *param) +{ + /* Scan directory for saved settings under subtree */ + + char dirpath[320]; + + settings_path(subtree, dirpath, sizeof(dirpath)); + + /* For now, just return success - no persisted data */ + + (void)cb; + (void)param; + return 0; +} + +int settings_load_subtree(const char *subtree) +{ + return settings_load_subtree_direct(subtree, NULL, NULL); +} + +int settings_delete(const char *name) +{ + char path[320]; + + settings_path(name, path, sizeof(path)); + unlink(path); + return 0; +} + +int settings_commit(void) +{ + return 0; +} + +int settings_register(struct settings_handler *handler) +{ + (void)handler; + return 0; +} + +int settings_name_next(const char *name, const char **next) +{ + const char *slash; + + if (name == NULL) + { + if (next != NULL) + { + *next = NULL; + } + + return 0; + } + + slash = strchr(name, '/'); + if (slash == NULL) + { + if (next != NULL) + { + *next = NULL; + } + + return strlen(name); + } + + if (next != NULL) + { + *next = slash + 1; + } + + return slash - name; +} diff --git a/libs/zblue/crypto/CMakeLists.txt b/libs/zblue/crypto/CMakeLists.txt new file mode 100644 index 0000000000000..a0193920549a5 --- /dev/null +++ b/libs/zblue/crypto/CMakeLists.txt @@ -0,0 +1,30 @@ +# ############################################################################## +# libs/zblue/crypto/CMakeLists.txt +# +# Author: chao an +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +if(CONFIG_BT_CRYPTO) + target_sources(zblue PRIVATE ${CMAKE_CURRENT_LIST_DIR}/bt_crypto.c + ${CMAKE_CURRENT_LIST_DIR}/bt_crypto_psa.c) + + target_include_directories(zblue PRIVATE ${CMAKE_CURRENT_LIST_DIR}) +endif() diff --git a/libs/zblue/crypto/Kconfig b/libs/zblue/crypto/Kconfig new file mode 100644 index 0000000000000..12979b9c6fcb1 --- /dev/null +++ b/libs/zblue/crypto/Kconfig @@ -0,0 +1,20 @@ +############################################################################ +# libs/zblue/crypto/Kconfig +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Copyright (c) 2022 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config BT_CRYPTO + bool + select PSA_CRYPTO + select PSA_WANT_KEY_TYPE_AES + select PSA_WANT_ALG_CMAC + select PSA_WANT_ALG_ECB_NO_PADDING + imply MBEDTLS_AES_ROM_TABLES if MBEDTLS_PSA_CRYPTO_C + help + This option enables the Bluetooth Cryptographic Toolbox. diff --git a/libs/zblue/crypto/Make.defs b/libs/zblue/crypto/Make.defs new file mode 100644 index 0000000000000..ccad617afbc84 --- /dev/null +++ b/libs/zblue/crypto/Make.defs @@ -0,0 +1,35 @@ +############################################################################ +# libs/zblue/crypto/Make.defs +# +# Author: chao an +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +ifeq ($(CONFIG_BT_CRYPTO),y) + +BT_CSRCS += $(TOPDIR)/libs/zblue/crypto/bt_crypto.c +BT_CSRCS += $(TOPDIR)/libs/zblue/crypto/bt_crypto_psa.c + +VPATH += $(TOPDIR)/libs/zblue/crypto +DEPPATH += --dep-path $(TOPDIR)/libs/zblue/crypto + +CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)/libs/zblue/crypto + +endif diff --git a/libs/zblue/crypto/bt_crypto.c b/libs/zblue/crypto/bt_crypto.c new file mode 100644 index 0000000000000..3d6f3dfb0e17d --- /dev/null +++ b/libs/zblue/crypto/bt_crypto.c @@ -0,0 +1,325 @@ +/**************************************************************************** + * libs/zblue/crypto/bt_crypto.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include + +#include "psa/crypto.h" + +#include "common/bt_str.h" +#include "bt_crypto.h" + +#define LOG_LEVEL CONFIG_BT_CRYPTO_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_crypto); + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bt_crypto_f4(const uint8_t *u, + const uint8_t *v, const uint8_t *x, uint8_t z, + uint8_t res[16]) +{ + uint8_t xs[16]; + uint8_t m[65]; + int err; + + LOG_DBG("u %s", bt_hex(u, 32)); + LOG_DBG("v %s", bt_hex(v, 32)); + LOG_DBG("x %s z 0x%x", bt_hex(x, 16), z); + + /* U, V and Z are concatenated and used as input m to the function + * AES-CMAC and X is used as the key k. + * + * Core Spec 4.2 Vol 3 Part H 2.2.5 + * + * note: bt_smp_aes_cmac uses BE data and smp_f4 accept LE so we swap + */ + + sys_memcpy_swap(m, u, 32); + sys_memcpy_swap(m + 32, v, 32); + m[64] = z; + + sys_memcpy_swap(xs, x, 16); + + err = bt_crypto_aes_cmac(xs, m, sizeof(m), res); + if (err) + { + return err; + } + + sys_mem_swap(res, 16); + + LOG_DBG("res %s", bt_hex(res, 16)); + + return err; +} + +int bt_crypto_f5(const uint8_t *w, + const uint8_t *n1, const uint8_t *n2, + const bt_addr_le_t *a1, const bt_addr_le_t *a2, + uint8_t *mackey, uint8_t *ltk) +{ + static const uint8_t salt[16] = + { + 0x6c, 0x88, 0x83, 0x91, 0xaa, 0xf5, 0xa5, 0x38, + 0x60, 0x37, 0x0b, 0xdb, 0x5a, 0x60, 0x83, 0xbe + }; + + uint8_t m[53] = + { + 0x00, /* counter */ + 0x62, 0x74, 0x6c, 0x65, /* keyID */ + 0x00, 0x00, 0x00, 0x00, /* n1 */ + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, /* 2 */ + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, /* a1 */ + 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, /* a2 */ + 0x00, 0x00, 0x00, + 0x01, 0x00 /* length */ + }; + + uint8_t t[16]; + uint8_t ws[32]; + int err; + + LOG_DBG("w %s", bt_hex(w, 32)); + LOG_DBG("n1 %s", bt_hex(n1, 16)); + LOG_DBG("n2 %s", bt_hex(n2, 16)); + + sys_memcpy_swap(ws, w, 32); + + err = bt_crypto_aes_cmac(salt, ws, 32, t); + if (err) + { + return err; + } + + LOG_DBG("t %s", bt_hex(t, 16)); + + sys_memcpy_swap(m + 5, n1, 16); + sys_memcpy_swap(m + 21, n2, 16); + m[37] = a1->type; + sys_memcpy_swap(m + 38, a1->a.val, 6); + m[44] = a2->type; + sys_memcpy_swap(m + 45, a2->a.val, 6); + + err = bt_crypto_aes_cmac(t, m, sizeof(m), mackey); + if (err) + { + return err; + } + + LOG_DBG("mackey %1s", bt_hex(mackey, 16)); + + sys_mem_swap(mackey, 16); + + /* counter for ltk is 1 */ + + m[0] = 0x01; + + err = bt_crypto_aes_cmac(t, m, sizeof(m), ltk); + if (err) + { + return err; + } + + LOG_DBG("ltk %s", bt_hex(ltk, 16)); + + sys_mem_swap(ltk, 16); + + return 0; +} + +int bt_crypto_f6(const uint8_t *w, const uint8_t *n1, + const uint8_t *n2, const uint8_t *r, + const uint8_t *iocap, const bt_addr_le_t *a1, + const bt_addr_le_t *a2, uint8_t *check) +{ + uint8_t ws[16]; + uint8_t m[65]; + int err; + + LOG_DBG("w %s", bt_hex(w, 16)); + LOG_DBG("n1 %s", bt_hex(n1, 16)); + LOG_DBG("n2 %s", bt_hex(n2, 16)); + LOG_DBG("r %s", bt_hex(r, 16)); + LOG_DBG("io_cap %s", bt_hex(iocap, 3)); + LOG_DBG("a1 %s", bt_hex(a1, 7)); + LOG_DBG("a2 %s", bt_hex(a2, 7)); + + sys_memcpy_swap(m, n1, 16); + sys_memcpy_swap(m + 16, n2, 16); + sys_memcpy_swap(m + 32, r, 16); + sys_memcpy_swap(m + 48, iocap, 3); + + m[51] = a1->type; + memcpy(m + 52, a1->a.val, 6); + sys_memcpy_swap(m + 52, a1->a.val, 6); + + m[58] = a2->type; + memcpy(m + 59, a2->a.val, 6); + sys_memcpy_swap(m + 59, a2->a.val, 6); + + sys_memcpy_swap(ws, w, 16); + + err = bt_crypto_aes_cmac(ws, m, sizeof(m), check); + if (err) + { + return err; + } + + LOG_DBG("res %s", bt_hex(check, 16)); + + sys_mem_swap(check, 16); + + return 0; +} + + int bt_crypto_g2( + const uint8_t u[32], const uint8_t v[32], const uint8_t x[16], + const uint8_t y[16], uint32_t *passkey) +{ + uint8_t m[80]; + uint8_t xs[16]; + int err; + + LOG_DBG("u %s", bt_hex(u, 32)); + LOG_DBG("v %s", bt_hex(v, 32)); + LOG_DBG("x %s", bt_hex(x, 16)); + LOG_DBG("y %s", bt_hex(y, 16)); + + sys_memcpy_swap(m, u, 32); + sys_memcpy_swap(m + 32, v, 32); + sys_memcpy_swap(m + 64, y, 16); + + sys_memcpy_swap(xs, x, 16); + + /* reuse xs (key) as buffer for result */ + + err = bt_crypto_aes_cmac(xs, m, sizeof(m), xs); + if (err) + { + return err; + } + + LOG_DBG("res %s", bt_hex(xs, 16)); + + memcpy(passkey, xs + 12, 4); + *passkey = sys_be32_to_cpu(*passkey) % 1000000; + + LOG_DBG("passkey %u", *passkey); + + return 0; +} + + int bt_crypto_h6(const uint8_t w[16], + const uint8_t key_id[4], uint8_t res[16]) +{ + uint8_t ws[16]; + uint8_t key_id_s[4]; + int err; + + LOG_DBG("w %s", bt_hex(w, 16)); + LOG_DBG("key_id %s", bt_hex(key_id, 4)); + + sys_memcpy_swap(ws, w, 16); + sys_memcpy_swap(key_id_s, key_id, 4); + + err = bt_crypto_aes_cmac(ws, key_id_s, 4, res); + if (err) + { + return err; + } + + LOG_DBG("res %s", bt_hex(res, 16)); + + sys_mem_swap(res, 16); + + return 0; +} + + int bt_crypto_h7(const uint8_t salt[16], + const uint8_t w[16], uint8_t res[16]) +{ + uint8_t ws[16]; + uint8_t salt_s[16]; + int err; + + LOG_DBG("w %s", bt_hex(w, 16)); + LOG_DBG("salt %s", bt_hex(salt, 16)); + + sys_memcpy_swap(ws, w, 16); + sys_memcpy_swap(salt_s, salt, 16); + + err = bt_crypto_aes_cmac(salt_s, ws, 16, res); + if (err) + { + return err; + } + + LOG_DBG("res %s", bt_hex(res, 16)); + + sys_mem_swap(res, 16); + + return 0; +} + + int bt_crypto_h8(const uint8_t k[16], + const uint8_t s[16], const uint8_t key_id[4], + uint8_t res[16]) +{ + uint8_t key_id_s[4]; + uint8_t iks[16]; + uint8_t ks[16]; + uint8_t ss[16]; + int err; + + LOG_DBG("k %s", bt_hex(k, 16)); + LOG_DBG("s %s", bt_hex(s, 16)); + LOG_DBG("key_id %s", bt_hex(key_id, 4)); + + sys_memcpy_swap(ks, k, 16); + sys_memcpy_swap(ss, s, 16); + + err = bt_crypto_aes_cmac(ss, ks, 16, iks); + if (err) + { + return err; + } + + sys_memcpy_swap(key_id_s, key_id, 4); + + err = bt_crypto_aes_cmac(iks, key_id_s, 4, res); + if (err) + { + return err; + } + + LOG_DBG("res %s", bt_hex(res, 16)); + + sys_mem_swap(res, 16); + + return 0; +} diff --git a/libs/zblue/crypto/bt_crypto.h b/libs/zblue/crypto/bt_crypto.h new file mode 100644 index 0000000000000..3dd4fd23d77db --- /dev/null +++ b/libs/zblue/crypto/bt_crypto.h @@ -0,0 +1,179 @@ +/**************************************************************************** + * libs/zblue/crypto/bt_crypto.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __BT_CRYPTO_H +#define __BT_CRYPTO_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include + +/** + * @brief Cypher based Message Authentication Code (CMAC) with AES 128 bit + * + * Defined in Core Vol. 3, part H 2.2.5. + * + * @param[in] key 128-bit key + * @param[in] in message to be authenticated + * @param[in] len length of the message in octets + * @param[out] out message authentication code + * + * @retval 0 Computation was successful. @p res contains the result. + * @retval -EIO Computation failed. + */ + +int bt_crypto_aes_cmac(const uint8_t *key, + const uint8_t *in, size_t len, uint8_t *out); + +/** + * @brief Cryptographic Toolbox f4 + * + * Defined in Core Vol. 3, part H 2.2.6. + * + * @param[in] u 256-bit + * @param[in] v 256-bit + * @param[in] x 128-bit key + * @param[in] z 8-bit + * @param[out] res + * + * @retval 0 Computation was successful. @p res contains the result. + * @retval -EIO Computation failed. + */ + +int bt_crypto_f4(const uint8_t *u, + const uint8_t *v, const uint8_t *x, uint8_t z, + uint8_t res[16]); + +/** + * @brief Cryptographic Toolbox f5 + * + * Defined in Core Vol. 3, part H 2.2.7. + * + * @param[in] w 256-bit + * @param[in] n1 128-bit + * @param[in] n2 128-bit + * @param[in] a1 56-bit + * @param[in] a2 56-bit + * @param[out] mackey most significant 128-bit of the result + * @param[out] ltk least significant 128-bit of the result + * + * @retval 0 Computation was successful. @p res contains the result. + * @retval -EIO Computation failed. + */ + +int bt_crypto_f5(const uint8_t *w, + const uint8_t *n1, const uint8_t *n2, + const bt_addr_le_t *a1, const bt_addr_le_t *a2, + uint8_t *mackey, uint8_t *ltk); + +/** + * @brief Cryptographic Toolbox f6 + * + * Defined in Core Vol. 3, part H 2.2.8. + * + * @param[in] w 128-bit + * @param[in] n1 128-bit + * @param[in] n2 128-bit + * @param[in] r 128-bit + * @param[in] iocap 24-bit + * @param[in] a1 56-bit + * @param[in] a2 56-bit + * @param[out] check + * + * @retval 0 Computation was successful. @p res contains the result. + * @retval -EIO Computation failed. + */ + +int bt_crypto_f6( + const uint8_t *w, const uint8_t *n1, const uint8_t *n2, const uint8_t *r, + const uint8_t *iocap, const bt_addr_le_t *a1, const bt_addr_le_t *a2, + uint8_t *check); + +/** + * @brief Cryptographic Toolbox g2 + + * Defined in Core Vol. 3, part H 2.2.9. + * + * @param[in] u 256-bit + * @param[in] v 256-bit + * @param[in] x 128-bit + * @param[in] y 128-bit + * @param[out] passkey + * + * @retval 0 Computation was successful. @p res contains the result. + * @retval -EIO Computation failed. + */ + +int bt_crypto_g2( + const uint8_t u[32], const uint8_t v[32], const uint8_t x[16], + const uint8_t y[16], uint32_t *passkey); + +/** + * @brief Cryptographic Toolbox h6 + * + * Link key conversion defined in Core Vol. 3, part H 2.2.10. + * + * @param[in] w 128-bit key + * @param[in] key_id 32-bit + * @param[out] res 128-bit + * + * @retval 0 Computation was successful. @p res contains the result. + * @retval -EIO Computation failed. + */ + +int bt_crypto_h6(const uint8_t w[16], + const uint8_t key_id[4], uint8_t res[16]); + +/** + * @brief Cryptographic Toolbox h7 + * + * Link key conversion defined in Core Vol. 3, part H 2.2.11. + * + * @param[in] salt 128-bit key + * @param[in] w 128-bit input of the AES-CMAC function + * @param[out] res 128-bit + * + * @retval 0 Computation was successful. @p res contains the result. + * @retval -EIO Computation failed. + */ + +int bt_crypto_h7(const uint8_t salt[16], + const uint8_t w[16], uint8_t res[16]); + +/** + * @brief Cryptographic Toolbox function h8 + * + * Defined in Core Vol. 6, part E 1.1.1. + * + * @note This function is purely a shorthand for the calculation. The + * parameters are therefore intentionally not assigned meaning. + * + * Pseudocode: `aes_cmac(key=aes_cmac(key=s, plaintext=k), plaintext=key_id)` + * + * @param[in] k (128-bit number in big endian) + * @param[in] s (128-bit number in big endian) + * @param[in] key_id (32-bit number in big endian) + * @param[out] res (128-bit number in big endian) + * + * @retval 0 Computation was successful. @p res contains the result. + * @retval -EIO Computation failed. + */ + +int bt_crypto_h8( + const uint8_t k[16], const uint8_t s[16], const uint8_t key_id[4], + uint8_t res[16]); + +#endif /* __BT_CRYPTO_H */ diff --git a/libs/zblue/crypto/bt_crypto_psa.c b/libs/zblue/crypto/bt_crypto_psa.c new file mode 100644 index 0000000000000..47064905b90f5 --- /dev/null +++ b/libs/zblue/crypto/bt_crypto_psa.c @@ -0,0 +1,66 @@ +/**************************************************************************** + * libs/zblue/crypto/bt_crypto_psa.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include + +#include "psa/crypto.h" + +#include "common/bt_str.h" +#include "bt_crypto.h" + +#define LOG_LEVEL CONFIG_BT_CRYPTO_LOG_LEVEL +#include +LOG_MODULE_DECLARE(bt_crypto); + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bt_crypto_aes_cmac(const uint8_t *key, + const uint8_t *in, size_t len, uint8_t *out) +{ + psa_key_id_t key_id; + psa_key_attributes_t key_attr = PSA_KEY_ATTRIBUTES_INIT; + size_t out_size; + psa_status_t status; + psa_status_t destroy_status; + + psa_set_key_type(&key_attr, PSA_KEY_TYPE_AES); + psa_set_key_bits(&key_attr, 128); + psa_set_key_usage_flags(&key_attr, + PSA_KEY_USAGE_SIGN_MESSAGE | PSA_KEY_USAGE_VERIFY_MESSAGE); + psa_set_key_algorithm(&key_attr, PSA_ALG_CMAC); + + status = psa_import_key(&key_attr, key, 16, &key_id); + if (status != PSA_SUCCESS) + { + LOG_ERR("Failed to import AES key %d", status); + return -EIO; + } + + status = psa_mac_compute(key_id, PSA_ALG_CMAC, in, len, out, 16, + &out_size); + destroy_status = psa_destroy_key(key_id); + if ((status != PSA_SUCCESS) || (destroy_status != PSA_SUCCESS)) + { + LOG_ERR("Failed to compute MAC %d", status); + return -EIO; + } + + return 0; +} diff --git a/libs/zblue/host/CMakeLists.txt b/libs/zblue/host/CMakeLists.txt new file mode 100644 index 0000000000000..d85dd7b40ce2a --- /dev/null +++ b/libs/zblue/host/CMakeLists.txt @@ -0,0 +1,189 @@ +# ############################################################################## +# libs/zblue/host/CMakeLists.txt +# +# Author: chao an +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +if(CONFIG_BT_HOST) + # === Core sources (compiled unconditionally for BT_HCI_HOST) === + set(HOST_SRCS + ${CMAKE_CURRENT_LIST_DIR}/uuid.c + ${CMAKE_CURRENT_LIST_DIR}/addr.c + ${CMAKE_CURRENT_LIST_DIR}/buf.c + ${CMAKE_CURRENT_LIST_DIR}/hci_core.c + ${CMAKE_CURRENT_LIST_DIR}/hci_common.c + ${CMAKE_CURRENT_LIST_DIR}/id.c + ${CMAKE_CURRENT_LIST_DIR}/data.c) + + # === Conditional sources === + if(CONFIG_BT_BROADCASTER) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/adv.c) + endif() + + if(CONFIG_BT_OBSERVER) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/scan.c) + endif() + + if(CONFIG_BT_HOST_CRYPTO) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/crypto_psa.c) + endif() + + if(CONFIG_BT_ECC) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/ecc.c) + endif() + + if(CONFIG_BT_HOST_CCM) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/aes_ccm.c) + endif() + + if(CONFIG_BT_LONG_WQ) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/long_wq.c) + endif() + + if(CONFIG_BT_SETTINGS) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/settings.c) + endif() + + if(CONFIG_BT_MONITOR) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/monitor.c) + endif() + + if(CONFIG_BT_HCI_RAW) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/hci_raw.c) + endif() + + # Connection-related sources (CONFIG_BT_CONN or CONFIG_BT_ISO) + if(CONFIG_BT_CONN OR CONFIG_BT_ISO) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/conn.c) + + if(CONFIG_BT_CONN) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/l2cap.c + ${CMAKE_CURRENT_LIST_DIR}/att.c ${CMAKE_CURRENT_LIST_DIR}/gatt.c) + + if(CONFIG_BT_GATT_GAP_SVC_VALIDATE) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/gatt_gap_svc_validate.c) + endif() + + if(CONFIG_BT_SMP) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/smp.c + ${CMAKE_CURRENT_LIST_DIR}/keys.c) + else() + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/smp_null.c) + endif() + endif() + + if(CONFIG_BT_ISO) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/iso.c) + endif() + + if(CONFIG_BT_CHANNEL_SOUNDING) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/cs.c) + endif() + + if(CONFIG_BT_DF) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/direction.c) + endif() + endif() + + # === Classic BR/EDR sources === + if(CONFIG_BT_CLASSIC) + list( + APPEND + HOST_SRCS + ${CMAKE_CURRENT_LIST_DIR}/classic/br.c + ${CMAKE_CURRENT_LIST_DIR}/classic/conn_br.c + ${CMAKE_CURRENT_LIST_DIR}/classic/keys_br.c + ${CMAKE_CURRENT_LIST_DIR}/classic/l2cap_br.c + ${CMAKE_CURRENT_LIST_DIR}/classic/sdp.c + ${CMAKE_CURRENT_LIST_DIR}/classic/ssp.c + ${CMAKE_CURRENT_LIST_DIR}/classic/sco.c) + + if(CONFIG_BT_A2DP) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/classic/a2dp.c + ${CMAKE_CURRENT_LIST_DIR}/classic/a2dp_codec_sbc.c) + endif() + + if(CONFIG_BT_AVDTP) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/classic/avdtp.c) + endif() + + if(CONFIG_BT_AVRCP) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/classic/avrcp.c) + endif() + + if(CONFIG_BT_AVCTP) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/classic/avctp.c) + endif() + + if(CONFIG_BT_RFCOMM) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/classic/rfcomm.c) + endif() + + if(CONFIG_BT_BIP) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/classic/bip.c) + endif() + + if(CONFIG_BT_MAP) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/classic/map.c) + endif() + + if(CONFIG_BT_HFP_HF) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/classic/hfp_hf.c + ${CMAKE_CURRENT_LIST_DIR}/classic/at.c) + endif() + + if(CONFIG_BT_HFP_AG) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/classic/hfp_ag.c) + endif() + + if(CONFIG_BT_GOEP) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/classic/obex.c + ${CMAKE_CURRENT_LIST_DIR}/classic/goep.c) + endif() + + if(CONFIG_BT_DID) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/classic/did.c) + endif() + + if(CONFIG_BT_HID_DEVICE) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/classic/hid_device.c) + endif() + + if(CONFIG_BT_HID_HOST) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/classic/hid_host.c) + endif() + + if(CONFIG_BT_AVRCP_CT_COVER_ART OR CONFIG_BT_AVRCP_TG_COVER_ART) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/classic/avrcp_cover_art.c) + endif() + + if(CONFIG_BT_PBAP) + list(APPEND HOST_SRCS ${CMAKE_CURRENT_LIST_DIR}/classic/pbap.c) + endif() + endif() + + target_sources(zblue PRIVATE ${HOST_SRCS}) + + # Include paths + target_include_directories( + zblue PRIVATE ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/classic + ${CMAKE_CURRENT_LIST_DIR}/..) +endif() diff --git a/libs/zblue/host/Kconfig b/libs/zblue/host/Kconfig new file mode 100644 index 0000000000000..f10b17afc3a75 --- /dev/null +++ b/libs/zblue/host/Kconfig @@ -0,0 +1,1183 @@ +############################################################################ +# libs/zblue/host/Kconfig +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth LE stack configuration options + +# Copyright (c) 2016-2020 Nordic Semiconductor ASA +# Copyright (c) 2015-2016 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +config BT_HCI_HOST + # Hidden option to make the conditions more intuitive + bool + default y if BT_HOST + depends on BT_HOST && !BT_HCI_RAW + select UUID + select BT_SYSTEM_WORKQUEUE_COOP + +config BT_HCI_TX_STACK_SIZE + # NOTE: This value is derived from other symbols and should only be + # changed if required by architecture + int + prompt "HCI Tx thread stack size" if BT_HCI_TX_STACK_SIZE_WITH_PROMPT + # Even if no driver is selected the following default is still + # needed e.g. for unit tests. This default will also server as + # the worst-case stack size if an out-of-tree controller is used. + default 1024 + help + Stack size needed for executing bt_send with specified driver. + NOTE: This is an advanced setting and should not be changed unless + absolutely necessary. To change this you must first select + BT_HCI_TX_STACK_SIZE_WITH_PROMPT. + +config BT_HCI_TX_STACK_SIZE_WITH_PROMPT + bool "Override HCI Tx thread stack size" + +config BT_HCI_TX_PRIO + # Hidden option for Co-Operative Tx thread priority + int + default 7 + +config BT_RX_STACK_SIZE + int "Size of the Bluetooth workqueue stack" + default 768 if BT_HCI_RAW + default 3092 if BT_MESH_GATT_CLIENT + default 2800 if BT_MESH_PB_GATT + default 2600 if BT_MESH + default 2048 if BT_AUDIO + default 1200 + help + Size of the stack of the Bluetooth workqueue, the thread in + which the Host processes received data and runs its deferred + work. Most of the callbacks that the application gets from a + context internal to the stack run in this thread. The default + value is sufficient for basic operation, but if the application + needs extra stack space in its callbacks, this value can be + increased to accommodate for that. + +config BT_RX_PRIO + # Hidden option for Co-Operative Rx thread priority + int + default 8 + +config BT_DRIVER_RX_HIGH_PRIO + # Hidden option for Co-Operative HCI driver RX thread priority + int + default 6 + +config BT_TX_PROCESSOR_THREAD + # This thread is used to send pending HCI Commands, ACL and ISO data to + # Controller. + bool "TX processor thread. Disabling may cause deadlocks." + # This option is automatically selected for all platforms except nRF51 + # due to limited RAM on nRF51 devices. + default y if !SOC_SERIES_NRF51 + +if BT_TX_PROCESSOR_THREAD + +config BT_TX_PROCESSOR_THREAD_PRIO + int + default 10 + +config BT_TX_PROCESSOR_STACK_SIZE + int "TX processor thread stack size" + default 1024 + help + Size of the TX processor thread stack. How much stack is needed depends on which + and how many host features are enabled. One might try to decrease it to save RAM, + or increase it if there are stack overflows. + +endif + +config BT_CONN_TX_NOTIFY_WQ + bool "Use a separate workqueue for connection TX notify processing [EXPERIMENTAL]" + depends on BT_CONN_TX + help + Use a separate workqueue instead of system workqueue for + bt_conn_tx_notify processing. The option can be used to make Bluetooth + stack more independent from the system workqueue. + +if BT_CONN_TX_NOTIFY_WQ + +config BT_CONN_TX_NOTIFY_WQ_STACK_SIZE + int "Stack size of workqueue for connection TX notify processing" + default 1024 + +config BT_CONN_TX_NOTIFY_WQ_PRIO + int "Cooperative priority of workqueue for connection TX notify processing" + default 8 + +config BT_CONN_TX_NOTIFY_WQ_INIT_PRIORITY + int "Init priority of workqueue for connection TX notify processing" + default 50 + help + The connection TX notify processing workqueue is initialized during + system initialization (at POST_KERNEL level). The Kconfig option + controls the initialization priority within level. + +endif # BT_CONN_TX_NOTIFY_WQ + +menu "Bluetooth Host" + +if BT_HCI_HOST + +rsource "../mesh/Kconfig" + +config BT_BUF_EVT_DISCARDABLE_SIZE + int "Maximum supported discardable HCI Event buffer length" + range 43 255 if !BT_EXT_ADV + range 58 255 if BT_EXT_ADV + # LE Extended Advertising Report event + default 255 if BT_CLASSIC + # Le Advertising Report event + default 43 if !BT_EXT_ADV + default 58 if BT_EXT_ADV && BT_MESH + default BT_BUF_EVT_RX_SIZE if BT_EXT_ADV + help + Maximum support discardable HCI event size of buffers in the separate + discardable event buffer pool. This value does not include the + HCI Event header. + The minimum size is set based on the Advertising Report. Setting + the buffer size different than BT_BUF_EVT_RX_SIZE can save memory. + +config BT_BUF_EVT_DISCARDABLE_COUNT + int "Number of discardable HCI Event buffers" + range 1 255 + default 20 if BT_MESH + default 3 + help + Number of buffers in a separate buffer pool for events which + the HCI driver considers discardable. Examples of such events + could be e.g. Advertising Reports. The benefit of having such + a pool is that the if there is a heavy inflow of such events + it will not cause the allocation for other critical events to + block and may even eliminate deadlocks in some cases. + +config BT_HOST_CRYPTO + bool "Use crypto functionality implemented in the Bluetooth host" + default y + select PSA_CRYPTO + select PSA_WANT_KEY_TYPE_AES + select PSA_WANT_ALG_ECB_NO_PADDING + help + The option adds the AES encryption support using PSA Crypto API + library if this is not provided by the controller implementation. + +config BT_HOST_CRYPTO_PRNG + bool "Use PSA crypto API library for random number generation" + default y + depends on BT_HOST_CRYPTO + help + When selected, will use PSA Crypto API library for random number generation. + This will consume additional ram, but may speed up the generation of random + numbers. + + Otherwise, random numbers will be generated through multiple HCI calls, + which will not consume additional resources, but may take a long time, + depending on the length of the random data. + This method is generally recommended within 16 bytes. + +config BT_SETTINGS + bool "Store Bluetooth state and configuration persistently" + depends on SETTINGS + select MPU_ALLOW_FLASH_WRITE if ARM_MPU + help + When selected, the Bluetooth stack will take care of storing + (and restoring) the Bluetooth state (e.g. pairing keys) and + configuration persistently in flash. + + When this option has been enabled, it's important that the + application makes a call to settings_load() after having done + all necessary initialization (e.g. calling bt_enable). The + reason settings_load() is handled externally to the stack, is + that there may be other subsystems using the settings API, in + which case it's more efficient to load all settings in one go, + instead of each subsystem doing it independently. + + Warning: The Bluetooth host expects a settings backend that loads + settings items in handle order. + +if BT_SETTINGS +config BT_SETTINGS_CCC_LAZY_LOADING + bool "Load CCC values from settings when peer connects" + depends on BT_CONN + default y + help + Load Client Configuration Characteristic setting right after a bonded + device connects. + Disabling this option will increase memory usage as CCC values for all + bonded devices will be loaded when calling settings_load. + +config BT_SETTINGS_DELAYED_STORE + # Enables delayed non-volatile storage mechanism + bool + help + Triggers the storage of the CF and CCC right after a write. + This is done in the workqueue context, in order to not block the BT RX + thread for too long. + +config BT_SETTINGS_DELAYED_STORE_MS + int + default 1000 + help + (Advanced) Governs the timeout after which the settings write will + take effect. + +config BT_SETTINGS_CCC_STORE_ON_WRITE + bool "Store CCC value immediately after it has been written" + depends on BT_CONN + select BT_SETTINGS_DELAYED_STORE + default y + help + Store Client Configuration Characteristic value right after it has + been updated. If the option is disabled, the CCC is only stored on + disconnection. + +config BT_SETTINGS_CF_STORE_ON_WRITE + bool "Store CF value immediately after it has been written" + depends on BT_CONN && BT_GATT_CACHING + select BT_SETTINGS_DELAYED_STORE + default y + help + Store Client Supported Features value right after it has been updated. + If the option is disabled, the CF is only stored on disconnection. + +config BT_SETTINGS_USE_PRINTK + bool "Use snprintf to encode Bluetooth settings key strings" + depends on SETTINGS && PRINTK + default y + help + When selected, Bluetooth settings will use snprintf to encode + key strings. + When not selected, Bluetooth settings will use a faster builtin + function to encode the key string. The drawback is that if + printk is enabled then the program memory footprint will be larger. + +config BT_SETTINGS_CCC_STORE_MAX + int "Max number of Client Characteristic Configuration (CCC)" + default 48 + range 1 96 + help + Defines the max number of Client Characteristic Configuration (CCC) + that the stack can handle + +endif # BT_SETTINGS + +config BT_FILTER_ACCEPT_LIST + bool "Filter accept list support" + help + This option enables the filter accept list API. This takes advantage of the + filtering feature of a Bluetooth LE controller. + The filter accept list is a global list and the same list is used + by both scanner and advertiser. The filter accept list cannot be modified while + it is in use. + + An Advertiser can filter which peers can connect or request scan + response data. + A scanner can filter advertisers for which it will generate + advertising reports. + Connections can be established automatically for accepted peers. + +config BT_LIM_ADV_TIMEOUT + int "Timeout for limited advertising in 1s units" + default 30 + range 1 180 + depends on BT_BROADCASTER + help + After this timeout is reached, advertisement with BT_LE_AD_LIMITED flag + set shall be terminated. As per BT Core Spec 5.2, Vol 3, Part C, + Appendix A (NORMATIVE): TIMERS AND CONSTANTS it's required to be no more + than 180s. + +config BT_CONN_TX_USER_DATA_SIZE + int + default 32 if BT_L2CAP_RET_FC + default 32 if 64BIT + default 16 + help + Necessary user_data size for stack usage. Mostly used for passing + callbacks around. See `struct closure` in conn_internal.h. + +config BT_CONN_FRAG_COUNT + int + default BT_MAX_CONN if BT_CONN + default BT_ISO_MAX_CHAN if BT_ISO + help + Internal kconfig that sets the maximum amount of simultaneous data + packets in flight. It should be equal to the number of connections. + +if BT_CONN + +config BT_CONN_TX_MAX + int "Maximum number of pending TX buffers with a callback [DEPRECATED]" + default BT_BUF_ACL_TX_COUNT + range BT_BUF_ACL_TX_COUNT 255 + help + Maximum number of pending TX buffers that have an associated + callback. Normally this can be left to the default value, which + is equal to the number of TX buffers in the controller. + +config BT_CONN_PARAM_ANY + bool "Accept any values for connection parameters" + help + Some controllers support additional connection parameter ranges + beyond what is described in the specification. Enabling this option + allows the application to set any value to all connection parameters. + The Host will perform no limits nor consistency checks on any of the + connection parameters (conn interval min and max, latency and timeout). + However, the Host will still use numerical comparisons between the + min and max connection intervals in order to verify whether the + desired parameters have been established in the connection. + +config BT_CONN_CHECK_NULL_BEFORE_CREATE + bool "Check if *conn is NULL when creating a connection" + help + Enable this option to ensure that bt_conn_le_create and + bt_conn_le_create_synced return an error if *conn is not initialized + to NULL. This option is recommended to use to catch programming + errors where the application reuses the connection pointer of an + active connection object without dereferencing it. Without + dereferencing, the connection object stays alive which can lead to an + unpredictable behavior. + +if BT_PHY_UPDATE + +config BT_USER_PHY_UPDATE + bool "User control of PHY Update Procedure" + help + Enable application access to initiate the PHY Update Procedure. + The application can also register a callback to be notified about PHY + changes on the connection. The current PHY info is available in the + connection info. + +choice BT_AUTO_PHY_PERIPHERAL + prompt "Auto PHY update for peripheral role" + depends on BT_PERIPHERAL + default BT_AUTO_PHY_PERIPHERAL_NONE + +config BT_AUTO_PHY_PERIPHERAL_NONE + bool "No PHY preference" + +config BT_AUTO_PHY_PERIPHERAL_1M + bool "1M PHY" + +config BT_AUTO_PHY_PERIPHERAL_2M + bool "2M PHY" + +config BT_AUTO_PHY_PERIPHERAL_CODED + bool "Coded PHY" + +endchoice # BT_AUTO_PHY_PERIPHERAL + +choice BT_AUTO_PHY_CENTRAL + prompt "Auto PHY update for central role" + depends on BT_CENTRAL + default BT_AUTO_PHY_CENTRAL_2M + +config BT_AUTO_PHY_CENTRAL_NONE + bool "No PHY preference" + +config BT_AUTO_PHY_CENTRAL_1M + bool "1M PHY" + +config BT_AUTO_PHY_CENTRAL_2M + bool "2M PHY" + +config BT_AUTO_PHY_CENTRAL_CODED + bool "Coded PHY" + +endchoice # BT_AUTO_PHY_CENTRAL + +endif # BT_PHY_UPDATE + +config BT_USER_DATA_LEN_UPDATE + bool "User control of Data Length Update Procedure" + depends on BT_DATA_LEN_UPDATE + help + Enable application access to initiate the Data Length Update + Procedure. The application can also a register callback to be notified + about Data Length changes on the connection. The current Data Length + info is available in the connection info. + +config BT_USER_CONN_PARAM_REJECTED + bool "User notification of connection parameter update rejection" + help + Enable application callback when an application-initiated connection + parameter update request is rejected by the peer. + +config BT_AUTO_DATA_LEN_UPDATE + bool "Auto-initiate Data Length Update procedure" + depends on BT_DATA_LEN_UPDATE + default y if !BT_USER_DATA_LEN_UPDATE + help + Initiate Data Length Update Procedure on connection establishment. + + Disable this if you want the Data Length Update Procedure feature + supported but want to rely on the remote device to initiate the + procedure at its discretion or want to initiate manually. + +config BT_REMOTE_INFO + bool "Application access to remote information" + help + Enable application access to the remote information available in the + stack. The remote information is retrieved once a connection has been + established and the application will be notified when this information + is available through the remote_info_available connection callback. + +config BT_SMP + bool "Security Manager Protocol support" + default y if BT_HOST + select BT_CRYPTO + select BT_RPA + select BT_ECC if !BT_SMP_OOB_LEGACY_PAIR_ONLY + help + This option enables support for the Security Manager Protocol + (SMP), making it possible to pair devices over LE. + +if BT_SMP + +config BT_SECURITY_ERR_TO_STR + bool "Print security error codes as strings [EXPERIMENTAL]" + help + This configuration enables printing of security error + codes represented as strings. + See bt_security_err_to_str() for more details. + +config BT_SMP_ERR_TO_STR + bool "Print SMP error codes as strings [EXPERIMENTAL]" + help + This configuration enables printing of SMP error + codes represented as strings. + See bt_smp_err_to_str() for more details. + +config BT_PASSKEY_KEYPRESS + bool "Passkey Keypress Notification support [EXPERIMENTAL]" + help + Enable support for receiving and sending Keypress Notifications during + Passkey Entry during pairing. + +config BT_PRIVACY + bool "Device privacy" + help + Enable privacy for the local device. This makes the device use Resolvable + Private Addresses (RPAs) by default. + + Note: + Establishing connections as a directed advertiser, or to a directed + advertiser is only possible if the controller also supports privacy. + +config BT_PRIVACY_RANDOMIZE_IR + bool "Randomize identity root for fallback identities" + depends on BT_PRIVACY + select BT_SETTINGS + help + Enabling this option will cause the Host to ignore controller-provided + identity roots (IR). The Host will instead use bt_rand to generate + identity resolving keys (IRK) and store them in the settings subsystem. + + Setting this config may come with a performance penalty to boot time, + as the hardware RNG may need time to generate entropy and will block + Bluetooth initialization. + + This option increases privacy, as explained in the following text. + + The IR determines the IRK of the identity. The IRK is used to both + generate and resolve (recognize) the private addresses of an identity. + The IRK is a shared secret, distributed to peers bonded to that + identity. + + An attacker that has stolen or once bonded and retained the IRK can + forever resolve addresses from that IRK, even if that bond has been + deleted locally. + + Deleting an identity should ideally delete the IRK as well and thereby + restore anonymity from previously bonded peers. But unless this config + is set, this does not always happen. + + In particular, a factory reset function that wipes the data in the + settings subsystem may not affect the controller-provided IRs. If + those IRs are reused, this device can be tracked across factory resets. + + For optimal privacy, a new IRK (i.e., identity) should be used per + bond. However, this naturally limits advertisements from that identity + to be recognizable by only that one bonded device. + + A description of the exact effect of this setting follows. + + If the application has not setup an identity before calling + settings_load()/settings_load_subtree("bt") after bt_enable(), the + Host will automatically try to load saved identities from the settings + subsystem, and if there are none, set up the default identity + (BT_ID_DEFAULT). + + If the controller has a public address (HCI_Read_BD_ADDR), that becomes + the address of the default identity. The Host will by default try to + obtain the IR for that identity from the controller (by Zephyr HCI + Read_Key_Hierarchy_Roots). Setting this config randomizes the IR + instead. + + If the controller does not have a public address, the Host will try + to source the default identity from the static address information + from controller (NuttX HCI Read_Static_Addresses). This results in an + identity for each entry in Read_Static_Addresses. Setting this config + randomizes the IRs during this process. + +config BT_RPA_TIMEOUT + int "Resolvable Private Address timeout" if BT_PRIVACY + default 900 + range 1 65535 + help + This option defines how often resolvable private address is rotated. + Value is provided in seconds and defaults to 900 seconds (15 minutes). + +config BT_RPA_TIMEOUT_DYNAMIC + bool "Support setting the Resolvable Private Address timeout at runtime" + depends on BT_PRIVACY + help + This option allows the user to override the default value of + the Resolvable Private Address timeout using dedicated APIs. + +config BT_RPA_SHARING + bool "Share the Resolvable Private Address between advertising sets" + depends on BT_PRIVACY && BT_EXT_ADV + help + This option configures the advertising sets linked with the same + Bluetooth identity to use the same Resolvable Private Address in + a given rotation period. After the RPA timeout, the new RPA is + generated and shared between the advertising sets in the subsequent + rotation period. When this option is disabled, the generated RPAs + of the advertising sets differ from each other in a given rotation + period. + +config BT_SIGNING + bool "Data signing support [DEPRECATED]" + help + This option enables data signing which is used for transferring + authenticated data in an unencrypted connection. + +config BT_SMP_APP_PAIRING_ACCEPT + bool "Accept or reject pairing initiative" + help + When receiving pairing request or pairing response query the + application whether to accept to proceed with pairing or not. This is + for pairing over SMP and does not affect SSP, which will continue + pairing without querying the application. + The application can return an error code, which is translated into + a SMP return value if the pairing is not allowed. + +config BT_SMP_SC_PAIR_ONLY + bool "Disable legacy pairing" + default y + help + This option disables LE legacy pairing and forces LE secure connection + pairing. All Security Mode 1 levels can be used with legacy pairing + disabled, but pairing with devices that do not support secure + connections pairing will not be supported. + To force a higher security level use "Secure Connections Only Mode" + +config BT_SMP_SC_ONLY + bool "Secure Connections Only Mode" + select BT_SMP_SC_PAIR_ONLY + help + This option enables support for Secure Connection Only Mode. In this + mode device shall only use Security Mode 1 Level 4 with exception + for services that only require Security Mode 1 Level 1 (no security). + Security Mode 1 Level 4 stands for authenticated LE Secure Connections + pairing with encryption. Enabling this option disables legacy pairing. + +config BT_SMP_OOB_LEGACY_PAIR_ONLY + bool "Force Out Of Band Legacy pairing" + depends on !(BT_SMP_SC_PAIR_ONLY || BT_SMP_SC_ONLY) + help + This option disables Legacy and LE SC pairing and forces legacy OOB. + +config BT_SMP_DISABLE_LEGACY_JW_PASSKEY + bool "Forbid usage of insecure legacy pairing methods" + depends on !(BT_SMP_SC_PAIR_ONLY || BT_SMP_SC_ONLY || \ + BT_SMP_OOB_LEGACY_PAIR_ONLY) + help + This option disables Just Works and Passkey legacy pairing methods to + increase security. + +config BT_SMP_ALLOW_UNAUTH_OVERWRITE + bool "Allow unauthenticated pairing for paired device" + help + This option allows all unauthenticated pairing attempts made by the + peer where an unauthenticated bond already exists. + This would enable cases where an attacker could copy the peer device + address to connect and start an unauthenticated pairing procedure + to replace the existing bond. When this option is disabled in order + to create a new bond the old bond has to be explicitly deleted with + bt_unpair. + +config BT_SMP_DERIVE_LTK + bool "Derive the LE LTK from the BR/EDR link key" + depends on BT_CLASSIC + default y + help + Accept the Security Manager procedure over BR/EDR, which derives the + LE LTK from the BR/EDR link key as part of Cross-Transport Key + Derivation (Core Specification 6.3, Vol 3, Part H, Section 2.4.2.5). + + Disabling this refuses that procedure as a whole rather than only the + LTK: the IRK, the identity address and the CSRK are not distributed + over BR/EDR either, so RPAs from a peer bonded only over BR/EDR can + no longer be resolved. That is intended, because the identity + information on its own already creates an LE bond entry. The BR/EDR + Security Manager fixed channel is also left out of the L2CAP + information response so that a peer does not start a procedure that + would then be rejected. + + Disable this on a device that must not end up with an LE bond as a + side effect of pairing over BR/EDR. Keys derived while it was enabled + are kept. + +config BT_SMP_DERIVE_LK + bool "Derive the BR/EDR link key from the LE LTK" + depends on BT_CLASSIC + default y + help + Ask for a BR/EDR link key to be derived from the LE LTK during Secure + Connections pairing over LE, as part of Cross-Transport Key Derivation + (Core Specification 6.3, Vol 3, Part H, Section 2.4.2.4). + + Disabling this clears the LinkKey bit from the key distribution of + both the Pairing Request and the Pairing Response, so that the peer + does not keep a link key that this device never derives. + + Disable this on a device that must not end up with a BR/EDR bond as a + side effect of pairing over LE. Keys derived while it was enabled are + kept. + +config BT_ID_UNPAIR_MATCHING_BONDS + bool "Delete bond with same peer with other local identity when bonding" + help + When a bond is about to complete, find any other bond with the same + peer address (or IRK) and `bt_unpair` that bond before the event + `pairing_complete`. + + Important: If this option is not enabled, the current implementation + will automatically fail the bonding. See "RL limitation" below. + + Important: If this option is not enabled, as Peripheral, it may be too + late to abort the bonding. The pairing is failed locally, but it may + still be reported as successful on the Central. When this situation + occurs, the NuttX Peripheral will immediately disconnect. See "SMP + limitation" below. + + [RL limitation]: + The Host implementers have considered it unlikely that applications + would ever want to have multiple bonds with the same peer. The + implementers prioritize the simplicity of the implementation over this + capability. + + The Resolve List on a Controller is not able to accommodate multiple + local addresses/IRKs for a single remote address. This would prevent + the Host from setting up a one-to-one correspondence between the Host + bond database and the Controller Resolve List. The implementation + relies on that capability when using the Resolve List. For performance + reasons, there is the wish to not fallback to Host Address Resolution + in this case. + + [SMP Limitation]: + The Paring Failed command of the Security Manager Protocol cannot be + sent outside of a Pairing Process. A Pairing Process ends when the + last Transport Specific Key to be distributed is acknowledged at + link-layer. The Host does not have control over this acknowledgment, + and the order of distribution is fixed by the specification. + +config BT_ID_ALLOW_UNAUTH_OVERWRITE + bool "Allow unauthenticated pairing with same peer with other local identity" + depends on !BT_SMP_ALLOW_UNAUTH_OVERWRITE + help + This option allows unauthenticated pairing attempts made by the + peer where an unauthenticated bond already exists on other local + identity. This configuration still blocks unauthenticated pairing + attempts on the same local identity. To allow the pairing procedure + unconditionally, please see the BT_SMP_ALLOW_UNAUTH_OVERWRITE + configuration. + +config BT_SMP_USB_HCI_CTLR_WORKAROUND + bool "Workaround for USB HCI controller out-of-order events" + depends on BT_TESTING + help + This option enables support for USB HCI controllers that sometimes + send out-of-order HCI events and ACL Data due to using different USB + endpoints. + Enabling this option will make the central role not require the + encryption-change event to be received before accepting key-distribution + data. + It opens up for a potential vulnerability as the central cannot detect + if the keys are distributed over an encrypted link. + +config BT_APP_PASSKEY + bool "Allow the application to provide passkeys for pairing" + help + With this option enabled, the application will be able to provide passkeys for pairing + using the app_passkey() callback. If the application does not provide a passkey, a + random passkey will be generated by the Host. + + WARNING: It is the responsibility of the application to use random and unique keys. + +config BT_USE_DEBUG_KEYS + bool "Security Manager Debug Mode" + help + This option places Security Manager in a Debug Mode. In this mode + predefined Diffie-Hellman private/public key pair is used as described + in Core Specification Vol. 3, Part H, 2.3.5.6.1. + + WARNING: This option enables anyone to decrypt on-air traffic. + Use of this feature in production is strongly discouraged. + +config BT_BONDABLE + bool "Bondable Mode" + default y + help + This option is the default value of the bonding flag for any ACL connection. + If the option is true, the default bonding flag is true. Or, the default + bonding flag is false. + After a connection is established, the bonding flag of the connection + can also be changed by calling `bt_conn_set_bondable()` if the configuration + `the bonding flag per-connection` (BT_BONDABLE_PER_CONNECTION) is + enabled. Please see the BT_BONDABLE_PER_CONNECTION configuration. + +config BT_BONDING_REQUIRED + bool "Always require bonding" + depends on BT_BONDABLE + help + When this option is enabled remote devices are required to always + set the bondable flag in their pairing request. Any other kind of + requests will be rejected. + +config BT_BONDABLE_PER_CONNECTION + bool "Set/clear the bonding flag per-connection" + help + Enable support for the bt_conn_set_bondable API function that is + used to set/clear the bonding flag on a per-connection basis. + +config BT_STORE_DEBUG_KEYS + bool "Store Debug Mode bonds" + help + This option enables support for storing bonds where either of devices + is using the predefined Diffie-Hellman private/public key pair as + described in the Core Specification Vol 3, Part H, 2.3.5.6.1. + + WARNING: This option potentially enables anyone to decrypt on-air + traffic. + Use of this feature in production is strongly discouraged. + +config BT_SMP_ENFORCE_MITM + bool "Enforce MITM protection" + default y + help + With this option enabled, the Security Manager will set MITM option in + the Authentication Requirements Flags whenever local IO Capabilities + allow the generated key to be authenticated. + +config BT_OOB_DATA_FIXED + bool "Use a fixed random number for LESC OOB pairing" + depends on BT_TESTING + help + With this option enabled, the application will be able to perform LESC + pairing with OOB data that consists of fixed random number and confirm + value. + + WARNING: This option stores a hardcoded Out-of-Band value in the image. + Use of this feature in production is strongly discouraged. + +config BT_KEYS_OVERWRITE_OLDEST + bool "Overwrite the oldest key if key storage is full" + help + If a pairing attempt occurs and the key storage is full then the + oldest key from the set of not currently in use keys will be selected + and overwritten by the pairing device. + +config BT_KEYS_SAVE_AGING_COUNTER_ON_PAIRING + bool "Store aging counter every time a successful paring occurs" + depends on BT_SETTINGS && BT_KEYS_OVERWRITE_OLDEST + help + With this option enabled, aging counter will be stored in settings every + time a successful pairing occurs. This increases flash wear out but offers + a more correct finding of the oldest unused pairing info. + +config BT_SMP_MIN_ENC_KEY_SIZE + int + prompt "Minimum encryption key size accepted in octets" if !BT_SMP_SC_ONLY + range 7 16 + default 16 + help + This option sets the minimum encryption key size accepted during pairing. + +endif # BT_SMP + +rsource "Kconfig.l2cap" +rsource "Kconfig.gatt" +rsource "../services/Kconfig" + +config BT_MAX_PAIRED + int "Maximum number of paired devices" + range 1 250 if BT_SMP + default 5 if BT_SMP + default 1 if BT_SMP + range 0 250 + default 0 + help + Maximum number of paired Bluetooth devices. The minimum (and + default) number is 1. + +config BT_CREATE_CONN_TIMEOUT + int "Timeout for pending LE Create Connection command in seconds" + default 3 + range 1 655 + +config BT_CONN_PARAM_UPDATE_TIMEOUT + int "Peripheral connection parameter update timeout in milliseconds" + default 5000 + range 0 65535 + help + The value is a timeout used by peripheral device to wait until it + starts the first connection parameters update procedure after a + connection has been established. + The connection parameters requested will be the parameters set by the + application, or the peripheral preferred connection parameters if + configured. + The default value is set to 5 seconds, to comply with the Bluetooth + Core specification: Core 4.2 Vol 3, Part C, 9.3.12.2: + "The Peripheral device should not perform a Connection Parameter + Update procedure within 5 seconds after establishing a connection." + +config BT_CONN_PARAM_RETRY_COUNT + int "Peripheral connection parameter update retry attempts" + default 3 + range 0 255 + help + This value corresponds to number of times to retry connection + parameter update to attain the preferred value set in GATT + characteristics in the Peripheral. + +config BT_CONN_PARAM_RETRY_TIMEOUT + int "Peripheral connection parameter update retry timeout in milliseconds" + default 5000 + range 0 65535 + help + The value is a timeout used by peripheral device to wait until retry + to attempt requesting again the preferred connection parameters. + +endif # BT_CONN + +if BT_OBSERVER +config BT_BACKGROUND_SCAN_INTERVAL + int "Scan interval used for background scanning in 0.625 ms units" + default 2048 + range 4 16384 +config BT_BACKGROUND_SCAN_WINDOW + int "Scan window used for background scanning in 0.625 ms units" + default 18 + range 4 16384 + +config BT_EXT_SCAN_BUF_SIZE + int "Maximum advertisement report size" + depends on BT_EXT_ADV + range 1 1650 + default 229 + help + Maximum size of an advertisement report in octets. If the advertisement + provided by the controller is larger than this buffer size, + the remaining data will be discarded. + +config BT_SCAN_EXT_FILTER_POLICY + bool "Extended scanner filter policies" + help + Enable support for the BT_LE_SCAN_OPT_EXT_FILTER_POLICY scanning option. + + The extended scanner filter policies let the Controller report directed + advertisements whose target address is a resolvable private address that + the Controller was unable to resolve, instead of discarding them. This + allows an application to detect a peer that is directing advertisements + at an identity the local device cannot resolve. + + This enables the LE Directed Advertising Report event, which is used to + report these advertisements while scanning with the legacy scanning + commands. It requires a Controller that supports the Extended Scanner + Filter Policies. + +endif # BT_OBSERVER + +config BT_SCAN_WITH_IDENTITY + bool "Perform active scanning using local identity address" + depends on !BT_PRIVACY && (BT_CENTRAL || BT_OBSERVER) + help + Enable this if you want to perform active scanning using the local + identity address as the scanner address. By default the stack will + always use a non-resolvable private address (NRPA) in order to avoid + disclosing local identity information. By not scanning with the + identity address the scanner will receive directed advertise reports + for the local identity. If this use case is required, then enable + this option. + +config BT_SCAN_AND_INITIATE_IN_PARALLEL + bool "Allow concurrent scanning and initiating" + depends on (BT_CENTRAL && BT_OBSERVER) + select BT_EXT_ADV if BT_BROADCASTER + select BT_SCAN_WITH_IDENTITY if !BT_PRIVACY + help + Allow concurrent scanning and initiating. + This will allow the application to initiate a connection + to a peer device without stopping the scanner. + If privacy is disabled, the scanner will use its identity + address. + This feature is only available when extended advertising + HCI commands are used to prevent degraded performance + when the advertiser is used. + Scanning with a timeout is not supported when this + feature is enabled. + +config BT_DEVICE_NAME_DYNAMIC + bool "Allow to set Bluetooth device name on runtime" + help + Enabling this option allows for runtime configuration of Bluetooth + device name. + +config BT_DEVICE_NAME_MAX + int "Maximum size in bytes for device name" + depends on BT_DEVICE_NAME_DYNAMIC + default 28 + range 2 248 + help + Bluetooth device name storage size. Storage can be up to 248 bytes + long (excluding NULL termination). + +config BT_DEVICE_NAME + string "Bluetooth device name" + default "NuttX" + help + Bluetooth device name. Name can be up to 248 bytes long (excluding + NULL termination). Can be empty string. + +config BT_DEVICE_APPEARANCE_DYNAMIC + bool "Runtime Bluetooth Appearance changing" + help + Enables use of bt_set_appearance. + If CONFIG_BT_SETTINGS is set, the appearance is persistently stored. + +config BT_DEVICE_APPEARANCE + int "Bluetooth device appearance" + range 0 65535 + default 0 + default 0 + help + Bluetooth device appearance. For the list of possible values please + consult the following link: + https://www.bluetooth.com/specifications/assigned-numbers + +config BT_ID_MAX + int "Maximum number of local identities" + range 1 1 if BT_SCAN_AND_INITIATE_IN_PARALLEL + range 1 250 + default 1 + help + Maximum number of supported local identity addresses. For most + products this is safe to leave as the default value (1). + +config BT_DF + bool "Direction Finding support [EXPERIMENTAL]" + help + Enable support for Bluetooth 5.1 Direction Finding. + It will allow to: get information about antennae, configure + Constant Tone Extension, transmit CTE and sample incoming CTE. + +if BT_DF + +config BT_DF_CONNECTIONLESS_CTE_RX + bool "Support for receive of CTE in connectionless mode" + help + Enable support for reception and sampling of Constant Tone Extension + in connectionless mode. + +config BT_DF_CONNECTIONLESS_CTE_TX + bool "Support for transmission of CTE in connectionless mode" + help + Enable support for transmission of Constant Tone Extension in + connectionless mode. + +config BT_DF_CONNECTION_CTE_RX + bool "Support for receive of CTE in connection mode" + help + Enable support for reception and sampling of Constant Tone Extension + in connection mode. + +config BT_DF_CONNECTION_CTE_TX + bool "Support for transmission of CTE in connection mode" + help + Enable support for transmission of Constant Tone Extension in + connection mode. + +config BT_DF_CONNECTION_CTE_REQ + bool "Support for CTE request procedure in connection mode" + depends on BT_DF_CONNECTION_CTE_RX + help + Enable support for request of Constant Tone Extension in connection + mode. + +config BT_DF_CONNECTION_CTE_RSP + bool "Support for CTE request procedure in connection mode" + depends on BT_DF_CONNECTION_CTE_TX + help + Enable support for request of Constant Tone Extension in connection + mode. + +config BT_DF_CTE_RX_AOA + bool "Antenna switching during CTE reception (AoA) feature" + depends on BT_DF_CONNECTIONLESS_CTE_RX || BT_DF_CONNECTION_CTE_RX + default y + help + Enable support for antenna switching during CTE reception. + Also known as Angle of Arrival mode. + +config BT_DF_CTE_TX_AOD + bool "Antenna switching during CTE transmission (AoD) feature" + depends on BT_DF_CONNECTIONLESS_CTE_TX || BT_DF_CONNECTION_CTE_TX + default y + help + Enable support for antenna switching during CTE transmission. + Also known as Angle of Departure mode. + +config BT_DF_VS_CL_IQ_REPORT_16_BITS_IQ_SAMPLES + bool "Use 16 bits signed integer IQ samples in connectionless IQ reports" + depends on BT_DF_CONNECTIONLESS_CTE_RX && BT_HCI_VS + help + Direction Finding connectionless IQ reports provide a set of IQ samples collected during + sampling of CTE. Bluetooth 5.3 Core Specification defines IQ samples to be 8 bits signed + integer, see Vol 4, Part E section 7.7.65.21. This option enables a vendor specific Host + extension to handle connectionless IQ reports with samples that are in 16 bit signed + integer format. + +config BT_DF_VS_CONN_IQ_REPORT_16_BITS_IQ_SAMPLES + bool "Use 16 bits signed integer IQ samples in connection IQ reports" + depends on BT_DF_CONNECTION_CTE_RX && BT_HCI_VS + help + Direction Finding connection IQ reports provide a set of IQ samples collected during + sampling of CTE. Bluetooth 5.3 Core Specification defines IQ samples to be 8 bits signed + integer, see Vol 4, Part E sections 7.7.65.22. This option enables a vendor specific Host + extension to handle connection IQ report with samples that are in 16 bit signed integer + format. + +endif # BT_DF + +config BT_ECC + bool + select PSA_CRYPTO + select PSA_WANT_ALG_ECDH + select PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_GENERATE + select PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT + select PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_EXPORT + select PSA_WANT_ECC_SECP_R1_256 + imply MBEDTLS_PSA_P256M_DRIVER_ENABLED if MBEDTLS_PSA_CRYPTO_C + help + If this option is set, internal APIs will be available to perform ECDH operations through + the long work queue (or system work queue). The operations are used e.g. by LE Secure + Connections. + +endif # BT_HCI_HOST + +config BT_HOST_CCM + bool "Host side AES-CCM module" + help + Enables the software based AES-CCM engine in the host. Will use the + controller's AES encryption functions if available, or BT_HOST_CRYPTO + otherwise. + +config BT_PER_ADV_SYNC_BUF_SIZE + int "Maximum periodic advertising report size" + depends on BT_PER_ADV_SYNC + range 0 1650 + default 0 + help + Maximum size of a fragmented periodic advertising report. If the periodic + advertising report provided by the controller is fragmented and larger + than this buffer size, then the data will be discarded. + Unfragmented reports are forwarded as they are received. + +config BT_DEBUG_ISO_DATA + bool "ISO channel data debug" + depends on BT_ISO_LOG_LEVEL_DBG + help + Use this option to enable ISO channels data debug logs for the + Bluetooth Audio functionality. This will enable debug logs for all + ISO data received and sent. + +config BT_SMP_SELFTEST + bool "Bluetooth SMP self tests executed on init" + depends on BT_SMP_LOG_LEVEL_DBG + help + This option enables SMP self-tests executed on startup + to verify security and crypto functions. + +config BT_SMP_FORCE_BREDR + bool "Force Bluetooth SMP over BR/EDR" + depends on BT_SMP_LOG_LEVEL_DBG + help + This option enables SMP over BR/EDR even if controller is not + supporting BR/EDR Secure Connections. This option is solely for + testing and should never be enabled on production devices. + +config BT_LOG_SNIFFER_INFO + bool "Bluetooth log information for sniffer" + help + This option enables the Bluetooth stack to log information such as + DH private key and LTK keys, which can be used by sniffers to decrypt + the connection without the use of Debug keys. + + WARNING: This option prints out private security keys such as + the Long Term Key. + Use of this feature in production is strongly discouraged + +config BT_TESTING + bool "Bluetooth Testing" + help + This option enables custom Bluetooth testing interface. + Shall only be used for testing purposes. + +config BT_CONN_DISABLE_SECURITY + bool "Disable security" + depends on BT_TESTING + help + This option disables security checks for incoming requests enabling + to test accessing GATT attributes and L2CAP channels that would + otherwise require encryption/authentication in order to be accessed. + + WARNING: This option enables anyone to snoop on-air traffic. + Use of this feature in production is strongly discouraged. + +config BT_SMP_LEGACY_PAIR_ONLY + bool "Force legacy pairing" + depends on BT_TESTING + depends on !(BT_SMP_SC_PAIR_ONLY || BT_SMP_SC_ONLY) + help + This option enforces legacy pairing. This is required for testing + legacy pairing between two NuttX Bluetooth devices, as without this + option the devices will default to using Secure Connections pairing. + +rsource "./classic/Kconfig" + +config BT_HCI_VS_EVT_USER + bool "User Vendor-Specific event handling" + help + Enable registering a callback for delegating to the user the handling of + VS events that are not known to the stack + +config BT_BUF_SYNC_EVT_POOL_COUNT + int "Num of Sync event buffer pool count" + range 1 255 + default 1 + help + This option enables vendor to configure number of sync event buffer count + based on application needs. The default value for sync event buf pool + count would be 1. + +endmenu diff --git a/libs/zblue/host/Kconfig.gatt b/libs/zblue/host/Kconfig.gatt new file mode 100644 index 0000000000000..f032d825a14ed --- /dev/null +++ b/libs/zblue/host/Kconfig.gatt @@ -0,0 +1,297 @@ +############################################################################ +# libs/zblue/host/Kconfig.gatt +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth ATT/GATT configuration options + +# Copyright (c) 2019 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +menu "ATT and GATT Options" + +config BT_ATT_ERR_TO_STR + bool "Print ATT error codes as strings [EXPERIMENTAL]" + help + This configuration enables printing of ATT error + codes represented as strings. + See bt_att_err_to_str() for more details. + +config BT_ATT_TX_COUNT + int "Number of ATT buffers" + default BT_BUF_ACL_TX_COUNT + default 3 + range 1 255 + help + These buffers are only used for sending anything over ATT. + Requests, responses, indications, confirmations, notifications. + +config BT_ATT_PREPARE_COUNT + int "Number of ATT prepare write buffers" + default 0 + range 0 64 + help + Number of buffers available for ATT prepare write, setting + this to 0 disables GATT long/reliable writes. + +config BT_ATT_RETRY_ON_SEC_ERR + bool "Automatic security elevation and retry on security errors" + default y + depends on BT_SMP + help + If an ATT request fails due to insufficient security, the host will + try to elevate the security level and retry the ATT request. + +config BT_EATT + bool "Enhanced ATT Bearers support [EXPERIMENTAL]" + depends on BT_L2CAP_ECRED + select BT_GATT_READ_MULT_VAR_LEN + help + This option enables support for Enhanced ATT bearers support. When + enabled additional L2CAP channels can be connected as bearers enabling + multiple outstanding request. + +if BT_EATT + +config BT_EATT_MAX + int "Maximum number of Enhanced ATT bearers" + default 3 + range 1 16 + + help + Number of Enhanced ATT bearers available. + + +config BT_EATT_AUTO_CONNECT + bool "Automatically connect EATT bearers when a link is established" + default y + help + The device will try to connect BT_EATT_MAX enhanced ATT bearers when a + connection to a peer is established. + +endif # BT_EATT + +config BT_GATT_AUTO_RESUBSCRIBE + bool "Automatic re-subscription to characteristics" + default y + imply BT_GATT_AUTO_SEC_REQ + depends on BT_GATT_CLIENT + help + Quirk: upon re-establishing a bonded connection, assumes the remote + forgot the CCC values and sets them again. If this behavior is not + desired for a particular subscription, set the + `BT_GATT_SUBSCRIBE_FLAG_NO_RESUB` flag. + This also means that upon a reconnection, the application will get an + unprompted call to its `subscribe` callback. + +config BT_GATT_AUTO_READ_CENTRAL_ADDR_RES + bool "Automatic read of the peer's Central Address Resolution support" + default y + depends on BT_GATT_CLIENT && BT_PERIPHERAL && BT_SMP + help + Read the Central Address Resolution characteristic of a bonded peer + once, as recommended by the GAP section of the Core specification, + and remember the result for as long as the bond exists. Directed + advertising towards the peer with BT_LE_ADV_OPT_DIR_ADDR_RPA then + fails if the peer is known not to support address resolution, since + such advertising can never be received by that peer. + +config BT_GATT_AUTO_SEC_REQ + bool "Automatic security re-establishment request as a peripheral" + default y + depends on BT_SMP + help + This option requests security re-establishment automatically whenever + a reconnection to a GATT client is established and + there are notifiable or indicatable characteristics for which security + is required. This configuration option only applies to the peripheral + role, which sends a Security Request for this purpose. + When disabled, it is the application's responsibility to call + bt_conn_set_security() to re-establish security prior to sending any + notifications or indications on the characteristics that require + security (unless the central does that first). + This option has been introduced to avoid interoperability issues with + commercially available central devices that react negatively to + receiving a Security Request immediately after reconnection. + +config BT_GATT_SERVICE_CHANGED + bool "GATT Service Changed support" + default y + help + This option enables support for the service changed characteristic. + +config BT_GATT_DYNAMIC_DB + bool "GATT dynamic database support" + depends on BT_GATT_SERVICE_CHANGED + help + This option enables registering/unregistering services at runtime. + +config BT_GATT_CACHING + bool "GATT Caching support" + default y + depends on BT_GATT_SERVICE_CHANGED + depends on PSA_CRYPTO + select PSA_WANT_KEY_TYPE_AES + select PSA_WANT_ALG_CMAC + select PSA_WANT_ALG_ECB_NO_PADDING + imply MBEDTLS_AES_ROM_TABLES if MBEDTLS_PSA_CRYPTO_C + help + This option enables support for GATT Caching. When enabled the stack + will register Client Supported Features and Database Hash + characteristics which can be used by clients to detect if anything has + changed on the GATT database. + +if BT_GATT_CACHING + +config BT_GATT_NOTIFY_MULTIPLE + bool "GATT Notify Multiple Characteristic Values support" + depends on BT_GATT_CACHING + help + This option enables support for the GATT Notify Multiple + Characteristic Values procedure. + +if BT_GATT_NOTIFY_MULTIPLE + +config BT_GATT_NOTIFY_MULTIPLE_FLUSH_MS + int "Delay for batching multiple notifications in a single PDU." + default 1 + range 0 4000 + help + Sets the time (in milliseconds) during which consecutive GATT + notifications will be tentatively appended to form a single + ATT_MULTIPLE_HANDLE_VALUE_NTF PDU. + + If set to 0, batching is disabled. Then, the only way to send + ATT_MULTIPLE_HANDLE_VALUE_NTF PDUs is to use bt_gatt_notify_multiple. + + See the documentation of bt_gatt_notify() for more details. + +endif # BT_GATT_NOTIFY_MULTIPLE + +config BT_GATT_ENFORCE_CHANGE_UNAWARE + bool "GATT Enforce change-unaware state" + depends on BT_GATT_CACHING + help + When enable this option blocks notification and indications to client + to conform to the following statement from the Bluetooth 5.1 + specification: + '...the server shall not send notifications and indications to such + a client until it becomes change-aware." + In case the service cannot deal with sudden errors (-EAGAIN) then it + shall not use this option. + +endif # BT_GATT_CACHING + +config BT_GATT_ENFORCE_SUBSCRIPTION + bool "GATT Enforce characteristic subscription" + default y + help + When enabled, this option will make the server block sending + notifications and indications to a device which has not subscribed to + the supplied characteristic. + +config BT_GATT_CLIENT + bool "GATT client support" + help + This option enables support for the GATT Client role. + +config BT_GATT_READ_MULTIPLE + bool "GATT Read Multiple Characteristic Values support" + default y + help + This option enables support for the GATT Read Multiple Characteristic + Values procedure. + +config BT_GATT_READ_MULT_VAR_LEN + bool "GATT Read Multiple Variable Length Characteristic Values support" + default y + help + This option enables support for the GATT Read Multiple Variable Length + Characteristic Values procedure. Mandatory if EATT is enabled, optional + otherwise (Core spec v5.3, Vol 3, Part G, Section 4.2, Table 4.1). + +config BT_GATT_AUTO_DISCOVER_CCC + bool "Support to automatic discover the CCC handles of characteristics" + depends on BT_GATT_CLIENT + help + This option enables support for GATT to initiate discovery for CCC + handles if the CCC handle is unknown by the application. + +config BT_GATT_AUTO_UPDATE_MTU + bool "Automatically send ATT MTU exchange request on connect" + depends on BT_GATT_CLIENT + help + This option if enabled allows automatically sending request for ATT + MTU exchange. + +config BT_GAP_AUTO_UPDATE_CONN_PARAMS + bool "Automatic Update of Connection Parameters" + default y + depends on BT_PERIPHERAL + help + This option if enabled allows automatically sending request for connection + parameters update after GAP recommended 5 seconds of connection as + peripheral. + +config BT_GAP_PERIPHERAL_PREF_PARAMS + bool "Configure peripheral preferred connection parameters" + default y + depends on BT_PERIPHERAL + help + This allows to configure peripheral preferred connection parameters. + Enabling this option results in adding PPCP characteristic in GAP. + If disabled it is up to application to set expected connection parameters. + +if BT_GAP_PERIPHERAL_PREF_PARAMS +config BT_PERIPHERAL_PREF_MIN_INT + int "Peripheral preferred minimum connection interval in 1.25ms units" + default 24 + range 6 65535 if !BT_CONN_PARAM_ANY + help + Range 3200 to 65534 is invalid. 65535 represents no specific value. + +config BT_PERIPHERAL_PREF_MAX_INT + int "Peripheral preferred maximum connection interval in 1.25ms units" + default 40 + range 6 65535 if !BT_CONN_PARAM_ANY + help + Range 3200 to 65534 is invalid. 65535 represents no specific value. + +config BT_PERIPHERAL_PREF_LATENCY + int "Peripheral preferred peripheral latency in Connection Intervals" + default 0 + range 0 499 if !BT_CONN_PARAM_ANY + +config BT_PERIPHERAL_PREF_TIMEOUT + int "Peripheral preferred supervision timeout in 10ms units" + default 42 + range 10 65535 if !BT_CONN_PARAM_ANY + help + It is up to user to provide valid timeout which pass required minimum + value: in milliseconds it shall be larger than + "(1+ Conn_Latency) * Conn_Interval_Max * 2" + where Conn_Interval_Max is given in milliseconds. + Range 3200 to 65534 is invalid. 65535 represents no specific value. +endif # BT_GAP_PERIPHERAL_PREF_PARAMS + +config BT_GATT_AUTHORIZATION_CUSTOM + bool "Custom authorization of GATT operations" + help + This option allows the user to define application-specific + authorization logic for GATT operations that can be registered + with the bt_gatt_authorization_cb_register API. See the API + documentation for more details. + +config BT_GATT_GAP_SVC_VALIDATE + bool "GATT GAP Service validation" + depends on BT_CONN + default y + help + Validate if in the GATT database exactly one GAP service is present. + This helps to debug any issues related to GAP service that may make + the Bluetooth implementation not compatible with the standard. + +endmenu diff --git a/libs/zblue/host/Kconfig.l2cap b/libs/zblue/host/Kconfig.l2cap new file mode 100644 index 0000000000000..d87d036d16503 --- /dev/null +++ b/libs/zblue/host/Kconfig.l2cap @@ -0,0 +1,90 @@ +############################################################################ +# libs/zblue/host/Kconfig.l2cap +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth L2CAP configuration options + +# Copyright (c) 2019 Intel Corporation +# Copyright (c) 2023 Nordic Semiconductor +# SPDX-License-Identifier: Apache-2.0 + +menu "L2CAP Options" + +config BT_L2CAP_TX_BUF_COUNT + int "Number of L2CAP TX buffers" + default BT_BUF_ACL_TX_COUNT + range 2 255 + help + Number of buffers available for outgoing L2CAP packets. + +config BT_L2CAP_TX_FRAG_COUNT + int "Number of L2CAP TX fragment buffers" + default 2 + range 0 255 + help + Number of buffers available for fragments of TX buffers. Warning: + setting this to 0 means that the application must ensure that + queued TX buffers never need to be fragmented, i.e. that the + controller's buffer size is large enough. If this is not ensured, + and there are no dedicated fragment buffers, a deadlock may occur. + In most cases the default value of 2 is a safe bet. + +config BT_L2CAP_TX_MTU + int "Maximum supported L2CAP MTU for L2CAP TX buffers" + default 253 if BT_CLASSIC + default 66 if BT_EATT + default 65 if BT_SMP + default 64 if BT_BAP_UNICAST_SERVER || \ + BT_BAP_UNICAST_CLIENT || \ + BT_BAP_BROADCAST_SINK || \ + BT_BAP_SCAN_DELEGATOR || \ + BT_BAP_BROADCAST_ASSISTANT + default 49 if BT_HAS || BT_HAS_CLIENT + default 33 if BT_MESH_GATT + default 23 + range 66 2000 if BT_EATT + range 65 2000 if BT_SMP + range 23 2000 + help + Maximum L2CAP MTU for L2CAP TX buffers. + +config BT_L2CAP_DYNAMIC_CHANNEL + bool "L2CAP Dynamic Channel support" + depends on BT_SMP + help + This option enables support for LE Connection oriented Channels, + allowing the creation of dynamic L2CAP Channels. + +config BT_L2CAP_ECRED + bool "L2CAP Enhanced Credit Based Flow Control support" + depends on BT_L2CAP_DYNAMIC_CHANNEL + help + This option enables support for LE Connection oriented Channels with + Enhanced Credit Based Flow Control support on dynamic L2CAP Channels. + +config BT_L2CAP_SEG_RECV + bool "L2CAP Receive segment direct API [EXPERIMENTAL]" + help + Enable API for direct receiving of L2CAP SDU segments, bypassing the + Host's fixed-function SDU re-assembler, RX SDU buffer management and + credit issuer. + + This API enforces conformance with L2CAP TS, but is otherwise as + flexible and semantically simple as possible. + +config BT_L2CAP_RECONFIGURE_EXPLICIT + bool "L2CAP Explicit reconfigure API [EXPERIMENTAL]" + help + Enable API for explicit reconfiguration of an L2CAP channel's MTU and + MPS. + +config BT_L2CAP_CONN_RTX_TIMEOUT + int + prompt "L2CAP RTX timer for conn requests (seconds)" if BT_TESTING + default 40 + range 1 60 +endmenu diff --git a/libs/zblue/host/Make.defs b/libs/zblue/host/Make.defs new file mode 100644 index 0000000000000..ce8800eab56f6 --- /dev/null +++ b/libs/zblue/host/Make.defs @@ -0,0 +1,195 @@ +############################################################################ +# libs/zblue/host/Make.defs +# +# Author: chao an +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +# NuttX Bluetooth Host Stack + +ifeq ($(CONFIG_BT_HOST),y) + +# === Core sources (compiled unconditionally for BT_HCI_HOST) === +BT_CSRCS += $(TOPDIR)/libs/zblue/host/uuid.c +BT_CSRCS += $(TOPDIR)/libs/zblue/host/addr.c +BT_CSRCS += $(TOPDIR)/libs/zblue/host/buf.c +BT_CSRCS += $(TOPDIR)/libs/zblue/host/hci_core.c +BT_CSRCS += $(TOPDIR)/libs/zblue/host/hci_common.c +BT_CSRCS += $(TOPDIR)/libs/zblue/host/id.c +BT_CSRCS += $(TOPDIR)/libs/zblue/host/data.c + +# === Conditional sources === +ifeq ($(CONFIG_BT_BROADCASTER),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/adv.c +endif + +ifeq ($(CONFIG_BT_OBSERVER),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/scan.c +endif + +ifeq ($(CONFIG_BT_HOST_CRYPTO),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/crypto_psa.c +endif + +ifeq ($(CONFIG_BT_ECC),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/ecc.c +endif + +ifeq ($(CONFIG_BT_HOST_CCM),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/aes_ccm.c +endif + +ifeq ($(CONFIG_BT_LONG_WQ),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/long_wq.c +endif + +ifeq ($(CONFIG_BT_SETTINGS),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/settings.c +endif + +ifeq ($(CONFIG_BT_MONITOR),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/monitor.c +endif + +ifeq ($(CONFIG_BT_HCI_RAW),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/hci_raw.c +endif + +# Connection-related sources (CONFIG_BT_CONN or CONFIG_BT_ISO) +ifneq ($(CONFIG_BT_CONN)$(CONFIG_BT_ISO),) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/conn.c + +ifeq ($(CONFIG_BT_CONN),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/l2cap.c +BT_CSRCS += $(TOPDIR)/libs/zblue/host/att.c +BT_CSRCS += $(TOPDIR)/libs/zblue/host/gatt.c + +ifeq ($(CONFIG_BT_GATT_GAP_SVC_VALIDATE),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/gatt_gap_svc_validate.c +endif + +ifeq ($(CONFIG_BT_SMP),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/smp.c +BT_CSRCS += $(TOPDIR)/libs/zblue/host/keys.c +else +BT_CSRCS += $(TOPDIR)/libs/zblue/host/smp_null.c +endif +endif + +ifeq ($(CONFIG_BT_ISO),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/iso.c +endif + +ifeq ($(CONFIG_BT_CHANNEL_SOUNDING),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/cs.c +endif + +ifeq ($(CONFIG_BT_DF),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/direction.c +endif +endif # CONFIG_BT_CONN or CONFIG_BT_ISO + +# === Classic BR/EDR sources === +ifeq ($(CONFIG_BT_CLASSIC),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/classic/br.c +BT_CSRCS += $(TOPDIR)/libs/zblue/host/classic/conn_br.c +BT_CSRCS += $(TOPDIR)/libs/zblue/host/classic/keys_br.c +BT_CSRCS += $(TOPDIR)/libs/zblue/host/classic/l2cap_br.c +BT_CSRCS += $(TOPDIR)/libs/zblue/host/classic/sdp.c +BT_CSRCS += $(TOPDIR)/libs/zblue/host/classic/ssp.c +BT_CSRCS += $(TOPDIR)/libs/zblue/host/classic/sco.c + +ifeq ($(CONFIG_BT_A2DP),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/classic/a2dp.c +BT_CSRCS += $(TOPDIR)/libs/zblue/host/classic/a2dp_codec_sbc.c +endif + +ifeq ($(CONFIG_BT_AVDTP),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/classic/avdtp.c +endif + +ifeq ($(CONFIG_BT_AVRCP),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/classic/avrcp.c +endif + +ifeq ($(CONFIG_BT_AVCTP),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/classic/avctp.c +endif + +ifeq ($(CONFIG_BT_RFCOMM),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/classic/rfcomm.c +endif + +ifeq ($(CONFIG_BT_BIP),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/classic/bip.c +endif + +ifeq ($(CONFIG_BT_MAP),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/classic/map.c +endif + +ifeq ($(CONFIG_BT_HFP_HF),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/classic/hfp_hf.c +BT_CSRCS += $(TOPDIR)/libs/zblue/host/classic/at.c +endif + +ifeq ($(CONFIG_BT_HFP_AG),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/classic/hfp_ag.c +endif + +ifeq ($(CONFIG_BT_GOEP),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/classic/obex.c +BT_CSRCS += $(TOPDIR)/libs/zblue/host/classic/goep.c +endif + +ifeq ($(CONFIG_BT_DID),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/classic/did.c +endif + +ifeq ($(CONFIG_BT_HID_DEVICE),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/classic/hid_device.c +endif + +ifeq ($(CONFIG_BT_HID_HOST),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/classic/hid_host.c +endif + +ifneq ($(CONFIG_BT_AVRCP_CT_COVER_ART)$(CONFIG_BT_AVRCP_TG_COVER_ART),) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/classic/avrcp_cover_art.c +endif + +ifeq ($(CONFIG_BT_PBAP),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/host/classic/pbap.c +endif +endif # CONFIG_BT_CLASSIC + +# === VPATH and DEPPATH for all subdirectories === +VPATH += $(TOPDIR)/libs/zblue/host +VPATH += $(TOPDIR)/libs/zblue/host/classic + +DEPPATH += --dep-path $(TOPDIR)/libs/zblue/host +DEPPATH += --dep-path $(TOPDIR)/libs/zblue/host/classic + +# === Include paths === +CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)/libs/zblue/host +CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)/libs/zblue/host/classic +CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)/libs/zblue + +endif # CONFIG_BT_HOST diff --git a/libs/zblue/host/addr.c b/libs/zblue/host/addr.c new file mode 100644 index 0000000000000..ffb6a8248272f --- /dev/null +++ b/libs/zblue/host/addr.c @@ -0,0 +1,207 @@ +/**************************************************************************** + * libs/zblue/host/addr.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Nordic Semiconductor ASA + * Copyright (c) 2015 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include +#include + +#include "keys.h" + +#define ADDR_RESOLVED_BITMASK (0x02) + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static inline int create_random_addr(bt_addr_le_t *addr) +{ + addr->type = BT_ADDR_LE_RANDOM; + + return bt_rand(addr->a.val, 6); +} + +int bt_addr_le_create_nrpa(bt_addr_le_t *addr) +{ + int err; + + err = create_random_addr(addr); + if (err) + { + return err; + } + + BT_ADDR_SET_NRPA(&addr->a); + + return 0; +} + +int bt_addr_le_create_static(bt_addr_le_t *addr) +{ + int err; + + err = create_random_addr(addr); + if (err) + { + return err; + } + + BT_ADDR_SET_STATIC(&addr->a); + + return 0; +} + +int bt_addr_from_str(const char *str, bt_addr_t *addr) +{ + /* Parse a null-terminated string with a Bluetooth address in + * canonical "XX:XX:XX:XX:XX:XX" format. + */ + + const size_t len = strlen(str); + + /* Verify length. */ + + if (len != BT_ADDR_STR_LEN - 1) + { + return -EINVAL; + } + + /* Verify that all the colons are present. */ + + for (size_t i = 2; i < len; i += 3) + { + if (str[i] != ':') + { + return -EINVAL; + } + } + + /* Parse each octet as hex and populate `addr->val`. It must be + * reversed since `bt_addr_t` is in 'on-air' format. + */ + + for (size_t i = 0; i < ARRAY_SIZE(addr->val); i++) + { + const size_t reverse_i = ARRAY_SIZE(addr->val) - 1 - i; + + if (!hex2bin(&str[i * 3], 2, &addr->val[reverse_i], 1)) + { + return -EINVAL; + } + } + + return 0; +} + +int bt_addr_le_from_str(const char *str, bt_addr_le_t *addr) +{ + uint8_t type; + int err; + + /* Parse a null-terminated string in the "P:XX:XX:XX:XX:XX:XX" (public) + * or "R:XX:XX:XX:XX:XX:XX" (random) format produced by + * bt_addr_le_to_str(). + */ + + switch (str[0]) + { + case 'P': + case 'p': + type = BT_ADDR_LE_PUBLIC; + break; + case 'R': + case 'r': + type = BT_ADDR_LE_RANDOM; + break; + default: + return -EINVAL; + } + + if (str[1] != ':') + { + return -EINVAL; + } + + err = bt_addr_from_str(&str[2], &addr->a); + if (err < 0) + { + return err; + } + + addr->type = type; + + return 0; +} + +void bt_addr_le_copy_resolved(bt_addr_le_t *dst, const bt_addr_le_t *src) +{ + bt_addr_le_copy(dst, src); + + /* translate to "regular" address type */ + + dst->type &= ~ADDR_RESOLVED_BITMASK; +} + +bool bt_addr_le_is_resolved(const bt_addr_le_t *addr) +{ + /* Bit 1 alone is not enough: the anonymous (0xff) and unresolved (0xfe) + * sentinels have it set too. + */ + + return addr->type == BT_ADDR_LE_PUBLIC_ID || + addr->type == BT_ADDR_LE_RANDOM_ID; +} + +#if defined(CONFIG_BT_PRIVACY) +int bt_addr_le_rpa_resolve(uint8_t id, + const bt_addr_le_t *addr, + bt_addr_le_t *resolved_addr) +{ + struct bt_keys *keys; + + if (addr == NULL || resolved_addr == NULL) + { + return -EINVAL; + } + + if (id >= CONFIG_BT_ID_MAX) + { + return -EINVAL; + } + + if (!bt_addr_le_is_rpa(addr)) + { + return -EINVAL; + } + + keys = bt_keys_find_irk(id, addr); + if (keys != NULL) + { + /* Found a matching IRK, return the identity address */ + + bt_addr_le_copy(resolved_addr, &keys->addr); + return 0; + } + + /* No matching IRK found for this RPA */ + + return -ENOENT; +} +#endif /* CONFIG_BT_PRIVACY */ diff --git a/libs/zblue/host/addr_internal.h b/libs/zblue/host/addr_internal.h new file mode 100644 index 0000000000000..922b4a3aaafee --- /dev/null +++ b/libs/zblue/host/addr_internal.h @@ -0,0 +1,75 @@ +/**************************************************************************** + * libs/zblue/host/addr_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Internal header for Bluetooth address functions + */ + +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +void bt_addr_le_copy_resolved(bt_addr_le_t *dst, const bt_addr_le_t *src); + +/** + * @brief Determine whether an HCI LE event address was resolved by the + * Controller + * + * This helper inspects an HCI LE event address field and reports whether the + * Controller resolved a Resolvable Private Address (RPA) to an identity + * address when producing the event. + * + * @warning The parameter is not a regular application-layer @ref + * bt_addr_le_t. It must be the address field taken directly from an HCI LE + * event structure. In those events, the address "type" uses the Identity + * Address values (@c BT_ADDR_LE_PUBLIC_ID and @c BT_ADDR_LE_RANDOM_ID) to + * indicate that resolution has occurred; this function checks for exactly + * those two values. Do not use this with any @ref bt_addr_le_t obtained + * from Zephyr host APIs. + * + * The complete (at time of writing) list of events that contain at least one + * field like this: + * - LE Advertising Report event (@ref bt_hci_evt_le_advertising_report) + * - LE Enhanced Connection Complete event (@ref + * bt_hci_evt_le_enh_conn_complete) + * - LE Directed Advertising Report event (@ref + * bt_hci_evt_le_direct_adv_report) + * - LE Extended Advertising Report event (@ref + * bt_hci_evt_le_ext_advertising_report) + * - LE Scan Request Received event (@ref bt_hci_evt_le_scan_req_received) + * - LE Periodic Advertising Sync Transfer Received event (@ref + * bt_hci_evt_le_past_received_v2) + * - LE Monitored Advertisers Report event + * + * @note The exact "potentially resolved address field" type is not a + * distinct + * type in the Core Specification; it is inferred from the common layout of + * the events above. This function only inspects the address type; it does + * not perform address resolution nor consult the resolve list. + * + * @param hci_addr_field_value Address field taken directly from an HCI LE + * event + * @retval true The Controller resolved the address (the on-air RPA matched + * an + * IRK in the Controller's resolve list) + * @retval false The address was not resolved by the Controller (resolution + * disabled or no match), or the field holds one of the LE Extended + * Advertising Report sentinels @c BT_ADDR_LE_UNRESOLVED or @c + * BT_ADDR_LE_ANONYMOUS + * + * @see bt_addr_le_copy_resolved() to convert a HCI event address type to a + * regular @ref bt_addr_le_t. + */ + +bool bt_addr_le_is_resolved(const bt_addr_le_t *hci_addr_field_value); diff --git a/libs/zblue/host/adv.c b/libs/zblue/host/adv.c new file mode 100644 index 0000000000000..51adaef09710c --- /dev/null +++ b/libs/zblue/host/adv.c @@ -0,0 +1,2937 @@ +/**************************************************************************** + * libs/zblue/host/adv.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017-2026 Nordic Semiconductor ASA + * Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "addr_internal.h" +#include "common/bt_str.h" +#include "conn_internal.h" +#include "hci_core.h" +#include "id.h" +#include "scan.h" +#include "adv.h" + +#define LOG_LEVEL CONFIG_BT_HCI_CORE_LOG_LEVEL +LOG_MODULE_REGISTER(bt_adv); + +struct bt_ad +{ + /* Pointer to an LTV structure */ + + const struct bt_data *data; + + /* Number of elements in @p data */ + + size_t len; +}; + +struct ad_stream +{ + /* ad is a two dimensional array of struct bt_data elements. */ + + const struct bt_ad *ad; + + /* The number of struct bt_ad elements. */ + + size_t ad_len; + + /* The current index in the array of struct bt_ad elements */ + + size_t ad_index; + + /* The current index in the array of ad.data elements */ + + size_t data_index; + + /* Current LTV offset contains the data offset in the ad[x].data[y].data + * value array The length and type are included in this offset. + */ + + uint16_t current_ltv_offset; + + /* The remaining size of total ad[i].data[j].data_len + 2 for LTV header */ + + size_t remaining_size; +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int ad_stream_new(struct ad_stream *stream, + const struct bt_ad *ad, size_t ad_len) +{ + (void)memset(stream, 0, sizeof(*stream)); + stream->ad = ad; + stream->ad_len = ad_len; + + for (size_t i = 0; i < ad_len; i++) + { + for (size_t j = 0; j < ad[i].len; j++) + { + /* LTV length + type + value */ + + stream->remaining_size += ad[i].data[j].data_len + 2; + + if (stream->remaining_size > BT_GAP_ADV_MAX_EXT_ADV_DATA_LEN) + { + return -EINVAL; + } + } + } + + return 0; +} + +/** + * @brief Returns true if the current stream is empty. + * + * @param stream AD stream, @ref ad_stream_new + * + * @returns true if the stream is now empty. + */ + +static bool ad_stream_is_empty(const struct ad_stream *stream) +{ + return stream->remaining_size == 0; +} + +/** + * @brief Returns the bt_data structure that is currently being read + * + * If the structure has been fully read, the function iterates to the next + * + * @param stream AD stream, @ref ad_stream_new + * + * @returns The current LTV structure or NULL if there are no left. + */ + +static const struct bt_data *ad_stream_current_ltv_update( + struct ad_stream *stream) +{ + const struct bt_data *current_ltv = + &stream->ad[stream->ad_index].data[stream->data_index]; + const bool done_reading_ltv = + (stream->current_ltv_offset == current_ltv->data_len + 2); + + if (done_reading_ltv) + { + stream->current_ltv_offset = 0; + + if (stream->data_index + 1 == stream->ad[stream->ad_index].len) + { + stream->data_index = 0; + stream->ad_index++; + } + else + { + stream->data_index++; + } + } + + if (stream->ad_index == stream->ad_len) + { + return NULL; + } + else + { + return &stream->ad[stream->ad_index].data[stream->data_index]; + } +} + +/** + * @brief Read at max buf_len data from the flattened AD stream. + * + * The read data can contain multiple LTV AD structures. + * + * @param stream AD stream, @ref ad_stream_new + * @param buf Buffer where the data will be put + * @param buf_len Buffer length + * + * @returns The number of bytes read from the stream written to the provided + * buffer + */ + +static uint8_t ad_stream_read(struct ad_stream *stream, + uint8_t *buf, uint8_t buf_len) +{ + uint8_t read_len = 0; + + while (read_len < buf_len) + { + const struct bt_data *current_ltv = + ad_stream_current_ltv_update(stream); + + if (!current_ltv) + { + break; + } + + if (stream->current_ltv_offset == 0) + { + buf[read_len] = current_ltv->data_len + 1; + stream->current_ltv_offset++; + read_len++; + } + else if (stream->current_ltv_offset == 1) + { + buf[read_len] = current_ltv->type; + stream->current_ltv_offset++; + read_len++; + } + else + { + const size_t remaining_data_len = + current_ltv->data_len - stream->current_ltv_offset + 2; + const size_t size_to_copy = MIN(buf_len - read_len, + remaining_data_len); + + (void)memcpy(&buf[read_len], + ¤t_ltv->data[stream->current_ltv_offset - 2], + size_to_copy); + stream->current_ltv_offset += size_to_copy; + read_len += size_to_copy; + } + } + + DEBUGASSERT(stream->remaining_size >= read_len); + stream->remaining_size -= read_len; + + return read_len; +} + +#if defined(CONFIG_BT_EXT_ADV) +static struct bt_le_ext_adv adv_pool[CONFIG_BT_EXT_ADV_MAX_ADV_SET]; +#endif /* defined(CONFIG_BT_EXT_ADV) */ + +#if defined(CONFIG_BT_EXT_ADV) +uint8_t bt_le_ext_adv_get_index(const struct bt_le_ext_adv *adv) +{ + DEBUGASSERT(IS_ARRAY_ELEMENT(adv_pool, adv)); + + return (uint8_t)ARRAY_INDEX(adv_pool, adv); +} + +static struct bt_le_ext_adv *adv_new(void) +{ + struct bt_le_ext_adv *adv = NULL; + int i; + + for (i = 0; i < ARRAY_SIZE(adv_pool); i++) + { + if (!atomic_test_bit(adv_pool[i].flags, BT_ADV_CREATED)) + { + adv = &adv_pool[i]; + break; + } + } + + if (!adv) + { + return NULL; + } + + (void)memset(adv, 0, sizeof(*adv)); + atomic_set_bit(adv_pool[i].flags, BT_ADV_CREATED); + adv->handle = i; + +#if defined(CONFIG_BT_PER_ADV_RSP_REASSEMBLY) + net_buf_simple_init_with_data(&adv->pawr_rsp_reassembly.buf, + adv->pawr_rsp_reassembly.reassembly_data, + BT_PER_ADV_RSP_REASSEMBLY_BUF_SIZE); + + net_buf_simple_reset(&adv->pawr_rsp_reassembly.buf); +#endif /* CONFIG_BT_PER_ADV_RSP_REASSEMBLY */ + + return adv; +} + +static void adv_delete(struct bt_le_ext_adv *adv) +{ + atomic_clear_bit(adv->flags, BT_ADV_CREATED); +} + +#if defined(CONFIG_BT_BROADCASTER) +struct bt_le_ext_adv *bt_hci_adv_lookup_handle(uint8_t handle) +{ + if (handle < ARRAY_SIZE(adv_pool) && + atomic_test_bit(adv_pool[handle].flags, BT_ADV_CREATED)) + { + return &adv_pool[handle]; + } + + return NULL; +} +#endif /* CONFIG_BT_BROADCASTER */ +#endif /* defined(CONFIG_BT_EXT_ADV) */ + +void bt_le_ext_adv_foreach( + void (*func)(struct bt_le_ext_adv *adv, void *data), void *data) +{ +#if defined(CONFIG_BT_EXT_ADV) + for (size_t i = 0; i < ARRAY_SIZE(adv_pool); i++) + { + if (atomic_test_bit(adv_pool[i].flags, BT_ADV_CREATED)) + { + func(&adv_pool[i], data); + } + } +#else + func(&bt_dev.adv, data); +#endif /* defined(CONFIG_BT_EXT_ADV) */ +} + +static void clear_ext_adv_instance(struct bt_le_ext_adv *adv, void *data) +{ + bt_le_lim_adv_cancel_timeout(adv); + memset(adv, 0, sizeof(*adv)); +} + +void bt_adv_reset_adv_pool(void) +{ + bt_le_ext_adv_foreach(clear_ext_adv_instance, NULL); + (void)memset(&bt_dev.adv, 0, sizeof(bt_dev.adv)); +} + +static int adv_create_legacy(void) +{ +#if defined(CONFIG_BT_EXT_ADV) + if (bt_dev.adv) + { + return -EALREADY; + } + + bt_dev.adv = adv_new(); + if (bt_dev.adv == NULL) + { + return -ENOMEM; + } +#endif + + return 0; +} + +void bt_le_adv_delete_legacy(void) +{ +#if defined(CONFIG_BT_EXT_ADV) + if (bt_dev.adv) + { + atomic_clear_bit(bt_dev.adv->flags, BT_ADV_CREATED); + bt_dev.adv = NULL; + } +#endif +} + +struct bt_le_ext_adv *bt_le_adv_lookup_legacy(void) +{ +#if defined(CONFIG_BT_EXT_ADV) + return bt_dev.adv; +#else + return &bt_dev.adv; +#endif +} + +int bt_le_adv_set_enable_legacy(struct bt_le_ext_adv *adv, bool enable) +{ + struct net_buf *buf; + struct bt_hci_cmd_state_set state; + int err; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + if (enable) + { + net_buf_add_u8(buf, BT_HCI_LE_ADV_ENABLE); + } + else + { + net_buf_add_u8(buf, BT_HCI_LE_ADV_DISABLE); + } + + bt_hci_cmd_state_set_init(buf, &state, adv->flags, BT_ADV_ENABLED, enable); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_ADV_ENABLE, buf, NULL); + if (err) + { + return err; + } + + return 0; +} + +int bt_le_adv_set_enable_ext(struct bt_le_ext_adv *adv, bool enable, + const struct bt_le_ext_adv_start_param *param) +{ + struct net_buf *buf; + struct bt_hci_cmd_state_set state; + int err; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + if (enable) + { + net_buf_add_u8(buf, BT_HCI_LE_ADV_ENABLE); + } + else + { + net_buf_add_u8(buf, BT_HCI_LE_ADV_DISABLE); + } + + net_buf_add_u8(buf, 1); + + net_buf_add_u8(buf, adv->handle); + net_buf_add_le16(buf, param ? param->timeout : 0); + net_buf_add_u8(buf, param ? param->num_events : 0); + + bt_hci_cmd_state_set_init(buf, &state, adv->flags, BT_ADV_ENABLED, enable); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_EXT_ADV_ENABLE, buf, NULL); + if (err) + { + return err; + } + + return 0; +} + +int bt_le_adv_set_enable(struct bt_le_ext_adv *adv, bool enable) +{ + if (IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features)) + { + return bt_le_adv_set_enable_ext(adv, enable, NULL); + } + + return bt_le_adv_set_enable_legacy(adv, enable); +} + +static bool valid_adv_ext_param(const struct bt_le_adv_param *param) +{ + const bool is_ext_adv = (param->options & BT_LE_ADV_OPT_EXT_ADV) != 0U; + + if (IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features)) + { + if (param->peer && !is_ext_adv && + !(param->options & BT_LE_ADV_OPT_CONN)) + { + /* Cannot do directed non-connectable advertising + * without extended advertising. + */ + + return false; + } + + if (param->peer && is_ext_adv && + !(param->options & BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY)) + { + /* High duty cycle directed connectable advertising + * shall not be used with Extended Advertising. + */ + + return false; + } + + if (!is_ext_adv && + param->options & (BT_LE_ADV_OPT_NO_2M | BT_LE_ADV_OPT_CODED | + BT_LE_ADV_OPT_ANONYMOUS | + BT_LE_ADV_OPT_USE_TX_POWER)) + { + /* Extended options require extended advertising. */ + + return false; + } + + /* For non-ext-adv we ignore the param->sid */ + + if (is_ext_adv && param->sid > BT_GAP_SID_MAX) + { + LOG_DBG("Invalid SID 0x%02X", param->sid); + return false; + } + } + + if (IS_ENABLED(CONFIG_BT_PRIVACY) && param->peer && + (param->options & BT_LE_ADV_OPT_USE_IDENTITY) && + (param->options & BT_LE_ADV_OPT_DIR_ADDR_RPA)) + { + /* own addr type used for both RPAs in directed advertising. */ + + return false; + } + + if (param->id >= bt_dev.id_count || + bt_addr_le_eq(&bt_dev.id_addr[param->id], + BT_ADDR_LE_ANY)) + { + return false; + } + + if (!(param->options & BT_LE_ADV_OPT_CONN)) + { + /* BT Core 4.2 [Vol 2, Part E, 7.8.5] + * The Advertising_Interval_Min and Advertising_Interval_Max shall not + * be set to less than 0x00a0 (100 ms) if the Advertising_Type is set + * to ADV_SCAN_IND or ADV_NONCONN_IND. + */ + + if (bt_dev.hci_version < BT_HCI_VERSION_5_0 && + param->interval_min < 0x00a0) + { + return false; + } + } + + if ((param->options & (BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY | + BT_LE_ADV_OPT_DIR_ADDR_RPA)) && + !param->peer) + { + return false; + } + + if ((param->options & BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY) || !param->peer) + { + uint32_t interval_max_limit = BT_LE_ADV_INTERVAL_MAX; + + if (is_ext_adv) + { + /* BT Core [Vol 4, Part E, 7.8.53]: extended advertising uses + * a 24-bit interval with a permitted range of 0x000020 to 0xffffff + * (~10485s), not the legacy 0x4000 ceiling. + */ + + interval_max_limit = BT_HCI_LE_PRIM_ADV_INTERVAL_MAX; + } + + if (param->interval_min > param->interval_max || + param->interval_min < BT_LE_ADV_INTERVAL_MIN || + param->interval_max > interval_max_limit) + { + return false; + } + } + + if ((param->options & BT_LE_ADV_OPT_DISABLE_CHAN_37) && + (param->options & BT_LE_ADV_OPT_DISABLE_CHAN_38) && + (param->options & BT_LE_ADV_OPT_DISABLE_CHAN_39)) + { + return false; + } + + return true; +} + +static bool valid_adv_param(const struct bt_le_adv_param *param) +{ + if (param->options & BT_LE_ADV_OPT_EXT_ADV) + { + return false; + } + + if (param->peer && !(param->options & BT_LE_ADV_OPT_CONN)) + { + return false; + } + + return valid_adv_ext_param(param); +} + +/* A directed advertisement with an RPA target can only be received by a peer + * that supports address resolution, so when the peer's Central Address + * Resolution characteristic value is known (see + * CONFIG_BT_GATT_AUTO_READ_CENTRAL_ADDR_RES), advertising that the peer + * would be unable to receive is refused. An unknown value does not refuse, + * as recommended by Core 6.3, Vol 3, Part C, 12.4. + */ + +static bool adv_peer_addr_res_ok(const struct bt_le_adv_param *param) +{ + if (!IS_ENABLED(CONFIG_BT_GATT_AUTO_READ_CENTRAL_ADDR_RES)) + { + /* The answer is never known without the option, and this + * avoids depending on the key storage when there is none. + */ + + return true; + } + + if (param->peer == NULL || + (param->options & BT_LE_ADV_OPT_DIR_ADDR_RPA) == 0) + { + return true; + } + + return bt_le_bond_addr_res_support(param->id, + param->peer) != BT_LE_ADDR_RES_SUPPORT_NO; +} + +static int set_data_add_complete(uint8_t *set_data, + uint8_t set_data_len_max, + const struct bt_ad *ad, + size_t ad_len, + uint8_t *data_len) +{ + uint8_t set_data_len = 0; + + for (size_t i = 0; i < ad_len; i++) + { + const struct bt_data *data = ad[i].data; + + for (size_t j = 0; j < ad[i].len; j++) + { + size_t len = data[j].data_len; + uint8_t type = data[j].type; + + /* Check if ad fit in the remaining buffer */ + + if ((set_data_len + len + 2) > set_data_len_max) + { + ssize_t shortened_len = set_data_len_max - (set_data_len + 2); + + if (!(type == BT_DATA_NAME_COMPLETE && shortened_len > 0)) + { + LOG_ERR("Too big advertising data"); + return -EINVAL; + } + + type = BT_DATA_NAME_SHORTENED; + len = shortened_len; + } + + set_data[set_data_len++] = len + 1; + set_data[set_data_len++] = type; + + memcpy(&set_data[set_data_len], data[j].data, len); + set_data_len += len; + } + } + + *data_len = set_data_len; + return 0; +} + +static int hci_set_ad(uint16_t hci_op, const struct bt_ad *ad, size_t ad_len) +{ + struct bt_hci_cp_le_set_adv_data *set_data; + struct net_buf *buf; + int err; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + set_data = net_buf_add(buf, sizeof(*set_data)); + (void)memset(set_data, 0, sizeof(*set_data)); + + err = set_data_add_complete(set_data->data, BT_GAP_ADV_MAX_ADV_DATA_LEN, + ad, + ad_len, + &set_data->len); + if (err) + { + net_buf_unref(buf); + return err; + } + + return bt_hci_cmd_send_sync(hci_op, buf, NULL); +} + +static int hci_set_adv_ext_complete( + struct bt_le_ext_adv *adv, uint16_t hci_op, size_t total_data_len, + const struct bt_ad *ad, size_t ad_len) +{ + struct bt_hci_cp_le_set_ext_adv_data *set_data; + struct net_buf *buf; + size_t cmd_size; + int err; + + /* Provide the opportunity to truncate the complete name */ + + if (!atomic_test_bit(adv->flags, BT_ADV_EXT_ADV) && + total_data_len > BT_GAP_ADV_MAX_ADV_DATA_LEN) + { + total_data_len = BT_GAP_ADV_MAX_ADV_DATA_LEN; + } + + cmd_size = sizeof(*set_data) + total_data_len; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + set_data = net_buf_add(buf, cmd_size); + (void)memset(set_data, 0, cmd_size); + + err = set_data_add_complete(set_data->data, total_data_len, ad, ad_len, + &set_data->len); + if (err) + { + net_buf_unref(buf); + return err; + } + + set_data->handle = adv->handle; + set_data->op = BT_HCI_LE_EXT_ADV_OP_COMPLETE_DATA; + set_data->frag_pref = BT_HCI_LE_EXT_ADV_FRAG_DISABLED; + + return bt_hci_cmd_send_sync(hci_op, buf, NULL); +} + +static int hci_set_adv_ext_fragmented( + struct bt_le_ext_adv *adv, uint16_t hci_op, const struct bt_ad *ad, + size_t ad_len) +{ + int err; + struct ad_stream stream; + bool is_first_iteration = true; + + err = ad_stream_new(&stream, ad, ad_len); + if (err) + { + return err; + } + + while (!ad_stream_is_empty(&stream)) + { + struct bt_hci_cp_le_set_ext_adv_data *set_data; + struct net_buf *buf; + const size_t data_len = MIN(BT_HCI_LE_EXT_ADV_FRAG_MAX_LEN, + stream.remaining_size); + const size_t cmd_size = sizeof(*set_data) + data_len; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + set_data = net_buf_add(buf, cmd_size); + + set_data->handle = adv->handle; + set_data->frag_pref = BT_HCI_LE_EXT_ADV_FRAG_ENABLED; + set_data->len = ad_stream_read(&stream, set_data->data, data_len); + + if (is_first_iteration && ad_stream_is_empty(&stream)) + { + set_data->op = BT_HCI_LE_EXT_ADV_OP_COMPLETE_DATA; + } + else if (is_first_iteration) + { + set_data->op = BT_HCI_LE_EXT_ADV_OP_FIRST_FRAG; + } + else if (ad_stream_is_empty(&stream)) + { + set_data->op = BT_HCI_LE_EXT_ADV_OP_LAST_FRAG; + } + else + { + set_data->op = BT_HCI_LE_EXT_ADV_OP_INTERM_FRAG; + } + + err = bt_hci_cmd_send_sync(hci_op, buf, NULL); + if (err) + { + return err; + } + + is_first_iteration = false; + } + + return 0; +} + +static int hci_set_ad_ext( + struct bt_le_ext_adv *adv, uint16_t hci_op, const struct bt_ad *ad, + size_t ad_len) +{ + size_t total_len_bytes = 0; + + for (size_t i = 0; i < ad_len; i++) + { + for (size_t j = 0; j < ad[i].len; j++) + { + total_len_bytes += ad[i].data[j].data_len + 2; + } + } + + if ((total_len_bytes > BT_HCI_LE_EXT_ADV_FRAG_MAX_LEN) && + atomic_test_bit(adv->flags, BT_ADV_ENABLED)) + { + /* It is not allowed to set advertising data in multiple + * operations while the advertiser is running. + */ + + return -EAGAIN; + } + + if (total_len_bytes > bt_dev.le.max_adv_data_len) + { + LOG_WRN("adv or scan rsp data too large (%zu > max %u)", + total_len_bytes, + bt_dev.le.max_adv_data_len); + return -EDOM; + } + + if (total_len_bytes <= BT_HCI_LE_EXT_ADV_FRAG_MAX_LEN) + { + /* If possible, set all data at once. + * This allows us to update advertising data while advertising. + */ + + return hci_set_adv_ext_complete(adv, hci_op, total_len_bytes, ad, + ad_len); + } + else + { + return hci_set_adv_ext_fragmented(adv, hci_op, ad, ad_len); + } + + return 0; +} + +static int set_ad(struct bt_le_ext_adv *adv, + const struct bt_ad *ad, size_t ad_len) +{ + if (IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features)) + { + return hci_set_ad_ext(adv, BT_HCI_OP_LE_SET_EXT_ADV_DATA, ad, ad_len); + } + + return hci_set_ad(BT_HCI_OP_LE_SET_ADV_DATA, ad, ad_len); +} + +static int set_sd(struct bt_le_ext_adv *adv, + const struct bt_ad *sd, size_t sd_len) +{ + if (IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features)) + { + return hci_set_ad_ext(adv, BT_HCI_OP_LE_SET_EXT_SCAN_RSP_DATA, sd, + sd_len); + } + + return hci_set_ad(BT_HCI_OP_LE_SET_SCAN_RSP_DATA, sd, sd_len); +} + +#if defined(CONFIG_BT_PER_ADV) +static int hci_set_per_adv_data( + const struct bt_le_ext_adv *adv, const struct bt_data *ad, size_t ad_len) +{ + int err; + struct ad_stream stream; + struct bt_ad d = + { + .data = ad, .len = ad_len + }; + + bool is_first_iteration = true; + + err = ad_stream_new(&stream, &d, 1); + if (err) + { + return err; + } + + while (!ad_stream_is_empty(&stream)) + { + struct bt_hci_cp_le_set_per_adv_data *set_data; + struct net_buf *buf; + const size_t data_len = MIN(BT_HCI_LE_PER_ADV_FRAG_MAX_LEN, + stream.remaining_size); + const size_t cmd_size = sizeof(*set_data) + data_len; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + set_data = net_buf_add(buf, cmd_size); + (void)memset(set_data, 0, cmd_size); + + set_data->handle = adv->handle; + set_data->len = ad_stream_read(&stream, set_data->data, data_len); + + if (is_first_iteration && ad_stream_is_empty(&stream)) + { + set_data->op = BT_HCI_LE_EXT_ADV_OP_COMPLETE_DATA; + } + else if (is_first_iteration) + { + set_data->op = BT_HCI_LE_EXT_ADV_OP_FIRST_FRAG; + } + else if (ad_stream_is_empty(&stream)) + { + set_data->op = BT_HCI_LE_EXT_ADV_OP_LAST_FRAG; + } + else + { + set_data->op = BT_HCI_LE_EXT_ADV_OP_INTERM_FRAG; + } + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_PER_ADV_DATA, buf, NULL); + if (err) + { + return err; + } + + is_first_iteration = false; + } + + return 0; +} +#endif /* CONFIG_BT_PER_ADV */ + +static bool ad_is_limited(const struct bt_data *ad, size_t ad_len) +{ + size_t i; + + for (i = 0; i < ad_len; i++) + { + if (ad[i].type == BT_DATA_FLAGS && ad[i].data_len == sizeof(uint8_t) && + ad[i].data != NULL) + { + if (ad[i].data[0] & BT_LE_AD_LIMITED) + { + return true; + } + } + } + + return false; +} + +static int le_adv_update( + struct bt_le_ext_adv *adv, const struct bt_data *ad, size_t ad_len, + const struct bt_data *sd, size_t sd_len, bool ext_adv, bool scannable) +{ + int err; + struct bt_ad wrapper; + + if (!(ext_adv && scannable)) + { + wrapper.data = ad; + wrapper.len = ad_len; + + err = set_ad(adv, &wrapper, 1); + if (err) + { + return err; + } + } + + if (scannable) + { + wrapper.data = sd; + wrapper.len = sd_len; + + err = set_sd(adv, &wrapper, 1); + if (err) + { + return err; + } + } + + atomic_set_bit(adv->flags, BT_ADV_DATA_SET); + return 0; +} + +int bt_le_adv_update_data( + const struct bt_data *ad, size_t ad_len, const struct bt_data *sd, + size_t sd_len) +{ + struct bt_le_ext_adv *adv = bt_le_adv_lookup_legacy(); + bool scannable; + + if (!adv) + { + return -EINVAL; + } + + if (!atomic_test_bit(adv->flags, BT_ADV_ENABLED)) + { + return -EAGAIN; + } + + scannable = atomic_test_bit(adv->flags, BT_ADV_SCANNABLE); + + return le_adv_update(adv, ad, ad_len, sd, sd_len, false, scannable); +} + +static uint8_t get_filter_policy(uint32_t options) +{ + if (!IS_ENABLED(CONFIG_BT_FILTER_ACCEPT_LIST)) + { + return BT_LE_ADV_FP_NO_FILTER; + } + else if ((options & BT_LE_ADV_OPT_FILTER_SCAN_REQ) && + (options & BT_LE_ADV_OPT_FILTER_CONN)) + { + return BT_LE_ADV_FP_FILTER_BOTH; + } + else if (options & BT_LE_ADV_OPT_FILTER_SCAN_REQ) + { + return BT_LE_ADV_FP_FILTER_SCAN_REQ; + } + else if (options & BT_LE_ADV_OPT_FILTER_CONN) + { + return BT_LE_ADV_FP_FILTER_CONN_IND; + } + else + { + return BT_LE_ADV_FP_NO_FILTER; + } +} + +static uint8_t get_adv_channel_map(uint32_t options) +{ + uint8_t channel_map = 0x07; + + if (options & BT_LE_ADV_OPT_DISABLE_CHAN_37) + { + channel_map &= ~0x01; + } + + if (options & BT_LE_ADV_OPT_DISABLE_CHAN_38) + { + channel_map &= ~0x02; + } + + if (options & BT_LE_ADV_OPT_DISABLE_CHAN_39) + { + channel_map &= ~0x04; + } + + return channel_map; +} + +static inline bool adv_is_directed(const struct bt_le_ext_adv *adv) +{ + /* The advertiser is assumed to be directed when the peer address has + * been set. + */ + + return !bt_addr_le_eq(&adv->target_addr, BT_ADDR_LE_ANY); +} + +static int le_adv_start_add_conn(const struct bt_le_ext_adv *adv, + struct bt_conn **out_conn) +{ + struct bt_conn *conn; + + bt_dev.adv_conn_id = adv->id; + + if (!adv_is_directed(adv)) + { + /* Undirected advertising */ + + conn = bt_conn_add_le(adv->id, BT_ADDR_LE_NONE); + if (!conn) + { + return -ENOMEM; + } + + bt_conn_set_state(conn, BT_CONN_ADV_CONNECTABLE); + *out_conn = conn; + return 0; + } + + if (bt_conn_exists_le(adv->id, &adv->target_addr)) + { + return -EINVAL; + } + + conn = bt_conn_add_le(adv->id, &adv->target_addr); + if (!conn) + { + return -ENOMEM; + } + + bt_conn_set_state(conn, BT_CONN_ADV_DIR_CONNECTABLE); + *out_conn = conn; + return 0; +} + +static void le_adv_stop_free_conn(const struct bt_le_ext_adv *adv, + uint8_t status) +{ + struct bt_conn *conn; + + if (!adv_is_directed(adv)) + { + conn = bt_conn_lookup_state_le(adv->id, BT_ADDR_LE_NONE, + BT_CONN_ADV_CONNECTABLE); + } + else + { + conn = bt_conn_lookup_state_le(adv->id, &adv->target_addr, + BT_CONN_ADV_DIR_CONNECTABLE); + } + + if (conn) + { + conn->err = status; + bt_conn_set_state(conn, BT_CONN_DISCONNECTED); + bt_conn_unref(conn); + } +} + +static int adv_start_legacy( + struct bt_le_ext_adv *adv, const struct bt_le_adv_param *param, + const struct bt_data *ad, size_t ad_len, const struct bt_data *sd, + size_t sd_len) +{ + struct bt_hci_cp_le_set_adv_param set_param; + struct bt_conn *conn = NULL; + struct net_buf *buf; + bool dir_adv = (param->peer != NULL); + bool scannable = false; + + int err; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + if (!valid_adv_param(param)) + { + return -EINVAL; + } + + if (!adv_peer_addr_res_ok(param)) + { + return -ENOTSUP; + } + + if (!bt_id_adv_random_addr_check(param)) + { + return -EINVAL; + } + + if (atomic_test_bit(adv->flags, BT_ADV_ENABLED)) + { + return -EALREADY; + } + + /* Add any IRK keys that are still pending (e.g. loaded from settings + * but not yet pushed to the controller RL) before advertising begins. + */ + + if (IS_ENABLED(CONFIG_BT_SMP) && atomic_test_bit(bt_dev.flags, + BT_DEV_ID_PENDING)) + { + bt_id_pending_keys_update(); + } + + (void)memset(&set_param, 0, sizeof(set_param)); + + set_param.min_interval = sys_cpu_to_le16(param->interval_min); + set_param.max_interval = sys_cpu_to_le16(param->interval_max); + set_param.channel_map = get_adv_channel_map(param->options); + set_param.filter_policy = get_filter_policy(param->options); + + atomic_clear_bit(bt_dev.flags, BT_DEV_RPA_VALID); + + adv->id = param->id; + bt_dev.adv_conn_id = adv->id; + + err = bt_id_set_adv_own_addr(adv, param->options, dir_adv, + &set_param.own_addr_type); + if (err) + { + return err; + } + + if (dir_adv) + { + bt_addr_le_copy(&adv->target_addr, param->peer); + } + else + { + bt_addr_le_copy(&adv->target_addr, BT_ADDR_LE_ANY); + } + + if (param->options & BT_LE_ADV_OPT_CONN) + { + if (dir_adv) + { + if (param->options & BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY) + { + set_param.type = BT_HCI_ADV_DIRECT_IND_LOW_DUTY; + } + else + { + set_param.type = BT_HCI_ADV_DIRECT_IND; + } + + bt_addr_le_copy(&set_param.direct_addr, param->peer); + } + else + { + scannable = true; + set_param.type = BT_HCI_ADV_IND; + } + } + else if ((param->options & BT_LE_ADV_OPT_SCANNABLE) || sd) + { + scannable = true; + set_param.type = BT_HCI_ADV_SCAN_IND; + } + else + { + set_param.type = BT_HCI_ADV_NONCONN_IND; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + net_buf_add_mem(buf, &set_param, sizeof(set_param)); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_ADV_PARAM, buf, NULL); + if (err) + { + return err; + } + + bt_id_save_adv_addr(adv, set_param.own_addr_type); + + if (!dir_adv) + { + err = le_adv_update(adv, ad, ad_len, sd, sd_len, false, scannable); + if (err) + { + return err; + } + } + + if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && + (param->options & BT_LE_ADV_OPT_CONN)) + { + err = le_adv_start_add_conn(adv, &conn); + if (err) + { + return err; + } + } + + err = bt_le_adv_set_enable(adv, true); + if (err) + { + LOG_ERR("Failed to start advertiser"); + if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && conn) + { + bt_conn_set_state(conn, BT_CONN_DISCONNECTED); + bt_conn_unref(conn); + } + + return err; + } + + if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && conn) + { + /* If undirected connectable advertiser we have created a + * connection object that we don't yet give to the application. Since + * we don't give the application a reference to manage in this case, we + * need to release this reference here + */ + + bt_conn_unref(conn); + } + +#if defined(CONFIG_BT_EXT_ADV) + adv->sid = BT_GAP_SID_INVALID; +#endif /* CONFIG_BT_EXT_ADV */ + + atomic_set_bit_to(adv->flags, BT_ADV_CONNECTABLE, + param->options & BT_LE_ADV_OPT_CONN); + + atomic_set_bit_to(adv->flags, BT_ADV_SCANNABLE, scannable); + + atomic_set_bit_to(adv->flags, BT_ADV_USE_IDENTITY, + param->options & BT_LE_ADV_OPT_USE_IDENTITY); + + return 0; +} + +static int le_ext_adv_param_set( + struct bt_le_ext_adv *adv, const struct bt_le_adv_param *param, + bool has_scan_data) +{ + const bool is_ext_adv = (param->options & BT_LE_ADV_OPT_EXT_ADV) != 0U; + struct bt_hci_cp_le_set_ext_adv_param_v2 *cp; + + uint16_t opcode; + uint16_t size; + bool dir_adv = param->peer != NULL; + bool scannable; + struct net_buf *buf; + struct net_buf *rsp; + uint8_t own_addr_type; + int err; + uint16_t props = 0; + + if ((param->options & BT_LE_ADV_OPT_TX_POWER) != 0U) + { + if (!IN_RANGE(param->tx_power, BT_HCI_LE_ADV_TX_POWER_MIN, + BT_HCI_LE_ADV_TX_POWER_MAX)) + { + return -EINVAL; + } + } + + err = bt_id_set_adv_own_addr(adv, param->options, dir_adv, &own_addr_type); + if (err) + { + return err; + } + + if (dir_adv) + { + bt_addr_le_copy(&adv->target_addr, param->peer); + } + else + { + bt_addr_le_copy(&adv->target_addr, BT_ADDR_LE_ANY); + } + + if (IS_ENABLED(CONFIG_BT_EXT_ADV_CODING_SELECTION) && + BT_FEAT_LE_ADV_CODING_SEL(bt_dev.le.features)) + { + opcode = BT_HCI_OP_LE_SET_EXT_ADV_PARAM_V2; + size = sizeof(struct bt_hci_cp_le_set_ext_adv_param_v2); + } + else + { + opcode = BT_HCI_OP_LE_SET_EXT_ADV_PARAM; + size = sizeof(struct bt_hci_cp_le_set_ext_adv_param); + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, size); + (void)memset(cp, 0, size); + + cp->handle = adv->handle; + sys_put_le24(param->interval_min, cp->prim_min_interval); + sys_put_le24(param->interval_max, cp->prim_max_interval); + cp->prim_channel_map = get_adv_channel_map(param->options); + cp->own_addr_type = own_addr_type; + cp->filter_policy = get_filter_policy(param->options); + cp->tx_power = + (param->options & BT_LE_ADV_OPT_TX_POWER) != 0U ? param->tx_power + : + BT_HCI_LE_ADV_TX_POWER_NO_PREF; + cp->prim_adv_phy = BT_HCI_LE_PHY_1M; + + if (is_ext_adv && !(param->options & BT_LE_ADV_OPT_NO_2M)) + { + cp->sec_adv_phy = BT_HCI_LE_PHY_2M; + } + else + { + cp->sec_adv_phy = BT_HCI_LE_PHY_1M; + } + + if (param->options & BT_LE_ADV_OPT_CODED) + { + cp->prim_adv_phy = BT_HCI_LE_PHY_CODED; + cp->sec_adv_phy = BT_HCI_LE_PHY_CODED; + + if (IS_ENABLED(CONFIG_BT_EXT_ADV_CODING_SELECTION) && + opcode == BT_HCI_OP_LE_SET_EXT_ADV_PARAM_V2) + { + uint8_t adv_phy_opt; + + if (param->options & BT_LE_ADV_OPT_REQUIRE_S8_CODING) + { + adv_phy_opt = BT_HCI_LE_ADV_PHY_OPTION_REQUIRE_S8; + } + else if (param->options & BT_LE_ADV_OPT_REQUIRE_S2_CODING) + { + adv_phy_opt = BT_HCI_LE_ADV_PHY_OPTION_REQUIRE_S2; + } + else + { + adv_phy_opt = BT_HCI_LE_ADV_PHY_OPTION_NO_REQUIRED; + } + + cp->prim_adv_phy_opt = adv_phy_opt; + cp->sec_adv_phy_opt = adv_phy_opt; + } + } + + if (!is_ext_adv) + { + props |= BT_HCI_LE_ADV_PROP_LEGACY; + } + + if (param->options & BT_LE_ADV_OPT_USE_TX_POWER) + { + props |= BT_HCI_LE_ADV_PROP_TX_POWER; + } + + if (param->options & BT_LE_ADV_OPT_ANONYMOUS) + { + props |= BT_HCI_LE_ADV_PROP_ANON; + } + + if (param->options & BT_LE_ADV_OPT_NOTIFY_SCAN_REQ) + { + cp->scan_req_notify_enable = BT_HCI_LE_ADV_SCAN_REQ_ENABLE; + } + + if (param->options & BT_LE_ADV_OPT_CONN) + { + props |= BT_HCI_LE_ADV_PROP_CONN; + if (!dir_adv && !is_ext_adv) + { + /* When using non-extended adv packets then undirected + * advertising has to be scannable as well. We didn't require this + * option to be set before, so it is implicitly set instead in this + * case. + */ + + props |= BT_HCI_LE_ADV_PROP_SCAN; + } + } + + if ((param->options & BT_LE_ADV_OPT_SCANNABLE) || has_scan_data) + { + props |= BT_HCI_LE_ADV_PROP_SCAN; + } + + scannable = !!(props & BT_HCI_LE_ADV_PROP_SCAN); + + if (dir_adv) + { + props |= BT_HCI_LE_ADV_PROP_DIRECT; + if (!(param->options & BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY)) + { + props |= BT_HCI_LE_ADV_PROP_HI_DC_CONN; + } + + bt_addr_le_copy(&cp->peer_addr, param->peer); + } + + if (is_ext_adv) + { + cp->sid = param->sid; + } + else + { + /* The SID is ignored for legacy advertising, but 0 keeps the parameter + * valid + */ + + cp->sid = 0U; + } + + cp->sec_adv_max_skip = param->secondary_max_skip; + + cp->props = sys_cpu_to_le16(props); + + err = bt_hci_cmd_send_sync(opcode, buf, &rsp); + if (err) + { + return err; + } + +#if defined(CONFIG_BT_EXT_ADV) + struct bt_hci_rp_le_set_ext_adv_param *rp = (void *)rsp->data; + + adv->tx_power = rp->tx_power; +#endif /* defined(CONFIG_BT_EXT_ADV) */ + + net_buf_drop(&rsp); + + atomic_set_bit(adv->flags, BT_ADV_PARAMS_SET); + + if (atomic_test_and_clear_bit(adv->flags, BT_ADV_RANDOM_ADDR_PENDING)) + { + err = bt_id_set_adv_random_addr(adv, &adv->adv_addr.a); + if (err) + { + return err; + } + } + + adv->options = param->options; +#if defined(CONFIG_BT_EXT_ADV) + /* Only set SID for extended advertising sets, else set BT_GAP_SID_INVALID + * for legacy adv + */ + + if (is_ext_adv) + { + adv->sid = param->sid; + } + else + { + adv->sid = BT_GAP_SID_INVALID; + } +#endif /* CONFIG_BT_EXT_ADV */ + + bt_id_save_adv_addr(adv, own_addr_type); + + atomic_set_bit_to(adv->flags, BT_ADV_CONNECTABLE, + param->options & BT_LE_ADV_OPT_CONN); + + atomic_set_bit_to(adv->flags, BT_ADV_SCANNABLE, scannable); + + atomic_set_bit_to(adv->flags, BT_ADV_USE_IDENTITY, + param->options & BT_LE_ADV_OPT_USE_IDENTITY); + + atomic_set_bit_to(adv->flags, BT_ADV_EXT_ADV, is_ext_adv); + + atomic_set_bit_to(adv->flags, BT_ADV_RANDOM_ADDR_UPDATED, + own_addr_type == BT_HCI_OWN_ADDR_RANDOM); + + return 0; +} + +static int adv_start_ext( + struct bt_le_ext_adv *adv, const struct bt_le_adv_param *param, + const struct bt_data *ad, size_t ad_len, const struct bt_data *sd, + size_t sd_len) +{ + struct bt_le_ext_adv_start_param start_param = + { + .timeout = 0, + .num_events = 0, + }; + + bool dir_adv = (param->peer != NULL); + struct bt_conn *conn = NULL; + int err; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + if (!valid_adv_param(param)) + { + return -EINVAL; + } + + if (!adv_peer_addr_res_ok(param)) + { + return -ENOTSUP; + } + + if (atomic_test_bit(adv->flags, BT_ADV_ENABLED)) + { + return -EALREADY; + } + + if (IS_ENABLED(CONFIG_BT_SMP) && atomic_test_bit(bt_dev.flags, + BT_DEV_ID_PENDING)) + { + bt_id_pending_keys_update(); + } + + adv->id = param->id; + + err = le_ext_adv_param_set(adv, param, sd != NULL); + if (err) + { + return err; + } + + if (!dir_adv) + { + if (IS_ENABLED(CONFIG_BT_EXT_ADV)) + { + err = bt_le_ext_adv_set_data(adv, ad, ad_len, sd, sd_len); + if (err) + { + return err; + } + } + } + else + { + if (!(param->options & BT_LE_ADV_OPT_DIR_MODE_LOW_DUTY)) + { + start_param.timeout = BT_GAP_ADV_HIGH_DUTY_CYCLE_MAX_TIMEOUT; + atomic_set_bit(adv->flags, BT_ADV_LIMITED); + } + } + + if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && + (param->options & BT_LE_ADV_OPT_CONN)) + { + err = le_adv_start_add_conn(adv, &conn); + if (err) + { + return err; + } + } + + err = bt_le_adv_set_enable_ext(adv, true, &start_param); + if (err) + { + LOG_ERR("Failed to start advertiser"); + if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && conn) + { + bt_conn_set_state(conn, BT_CONN_DISCONNECTED); + bt_conn_unref(conn); + } + + return err; + } + + if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && conn) + { + /* If undirected connectable advertiser we have created a + * connection object that we don't yet give to the application. Since + * we don't give the application a reference to manage in this case, we + * need to release this reference here + */ + + bt_conn_unref(conn); + } + + return 0; +} + +static void adv_timeout(struct k_work *work); + +int bt_le_lim_adv_cancel_timeout(struct bt_le_ext_adv *adv) +{ + return k_work_cancel_delayable(&adv->lim_adv_timeout_work); +} + +int bt_le_adv_start( + const struct bt_le_adv_param *param, const struct bt_data *ad, + size_t ad_len, const struct bt_data *sd, size_t sd_len) +{ + struct bt_le_ext_adv *adv; + int err; + + err = adv_create_legacy(); + if (err) + { + return err; + } + + adv = bt_le_adv_lookup_legacy(); + + if (IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features)) + { + err = adv_start_ext(adv, param, ad, ad_len, sd, sd_len); + } + else + { + err = adv_start_legacy(adv, param, ad, ad_len, sd, sd_len); + } + + if (err) + { + bt_le_adv_delete_legacy(); + } + + if (ad_is_limited(ad, ad_len)) + { + k_work_init_delayable(&adv->lim_adv_timeout_work, adv_timeout); + bt_work_reschedule(&adv->lim_adv_timeout_work, + SEC2TICK(CONFIG_BT_LIM_ADV_TIMEOUT)); + } + + return err; +} + +int bt_le_adv_stop(void) +{ + struct bt_le_ext_adv *adv = bt_le_adv_lookup_legacy(); + int err; + + if (!adv) + { + LOG_ERR("No valid legacy adv to stop"); + return 0; + } + + (void)bt_le_lim_adv_cancel_timeout(adv); + + if (!atomic_test_bit(adv->flags, BT_ADV_ENABLED)) + { + /* Legacy advertiser exists, but is not currently advertising. + * This happens when keep advertising behavior is active but no conn + * object is available to do connectable advertising. + */ + + bt_le_adv_delete_legacy(); + return 0; + } + + if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && atomic_test_bit(adv->flags, + BT_ADV_CONNECTABLE)) + { + le_adv_stop_free_conn(adv, 0); + } + + if (IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features)) + { + err = bt_le_adv_set_enable_ext(adv, false, NULL); + if (err) + { + return err; + } + } + else + { + err = bt_le_adv_set_enable_legacy(adv, false); + if (err) + { + return err; + } + } + + bt_le_adv_delete_legacy(); + +#if defined(CONFIG_BT_OBSERVER) + if (!(IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features)) && + !IS_ENABLED(CONFIG_BT_PRIVACY) && + !IS_ENABLED(CONFIG_BT_SCAN_WITH_IDENTITY)) + { + /* If scan is ongoing set back NRPA */ + + if (atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING)) + { + bt_le_scan_set_enable(BT_HCI_LE_SCAN_DISABLE); + bt_id_set_private_addr(BT_ID_DEFAULT); + bt_le_scan_set_enable(BT_HCI_LE_SCAN_ENABLE); + } + } +#endif /* defined(CONFIG_BT_OBSERVER) */ + + return 0; +} + +#if defined(CONFIG_BT_EXT_ADV) +int bt_le_ext_adv_get_info(const struct bt_le_ext_adv *adv, + struct bt_le_ext_adv_info *info) +{ + if (!IS_ARRAY_ELEMENT(adv_pool, adv)) + { + LOG_DBG("adv %p is a valid pointer from bt_le_ext_adv_create", adv); + return -EINVAL; + } + + if (!atomic_test_bit(adv->flags, BT_ADV_CREATED)) + { + LOG_DBG("Advertising set %p is not created", adv); + return -EINVAL; + } + + if (info == NULL) + { + LOG_DBG("info is NULL"); + return -EINVAL; + } + + info->id = adv->id; + info->sid = adv->sid; + info->tx_power = adv->tx_power; + info->addr = &adv->adv_addr; + + if (atomic_test_bit(adv->flags, BT_ADV_ENABLED)) + { + info->ext_adv_state = BT_LE_EXT_ADV_STATE_ENABLED; + } + else + { + info->ext_adv_state = BT_LE_EXT_ADV_STATE_DISABLED; + } + + if (IS_ENABLED(CONFIG_BT_PER_ADV)) + { + if (atomic_test_bit(adv->flags, BT_PER_ADV_ENABLED)) + { + info->per_adv_state = BT_LE_PER_ADV_STATE_ENABLED; + } + else if (atomic_test_bit(adv->flags, BT_PER_ADV_PARAMS_SET)) + { + info->per_adv_state = BT_LE_PER_ADV_STATE_DISABLED; + } + else + { + info->per_adv_state = BT_LE_PER_ADV_STATE_NONE; + } + } + else + { + info->per_adv_state = BT_LE_PER_ADV_STATE_NONE; + } + + return 0; +} + +int bt_le_ext_adv_create( + const struct bt_le_adv_param *param, const struct bt_le_ext_adv_cb *cb, + struct bt_le_ext_adv **out_adv) +{ + struct bt_le_ext_adv *adv; + int err; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + if (out_adv == NULL) + { + LOG_DBG("out_adv is NULL"); + + return -EINVAL; + } + + if (!valid_adv_ext_param(param)) + { + return -EINVAL; + } + + if (!adv_peer_addr_res_ok(param)) + { + return -ENOTSUP; + } + + adv = adv_new(); + if (!adv) + { + return -ENOMEM; + } + + adv->id = param->id; + adv->cb = cb; + + err = le_ext_adv_param_set(adv, param, false); + if (err) + { + adv_delete(adv); + return err; + } + + *out_adv = adv; + return 0; +} + +int bt_le_ext_adv_update_param(struct bt_le_ext_adv *adv, + const struct bt_le_adv_param *param) +{ + if (adv == NULL) + { + LOG_DBG("adv is NULL"); + + return -EINVAL; + } + + if (!valid_adv_ext_param(param)) + { + return -EINVAL; + } + + if (!adv_peer_addr_res_ok(param)) + { + return -ENOTSUP; + } + + if (IS_ENABLED(CONFIG_BT_PER_ADV) && atomic_test_bit(adv->flags, + BT_PER_ADV_PARAMS_SET)) + { + /* If params for per adv has been set, do not allow setting + * connectable, scanable or use legacy adv + */ + + if (param->options & BT_LE_ADV_OPT_CONN || + param->options & BT_LE_ADV_OPT_SCANNABLE || + !(param->options & BT_LE_ADV_OPT_EXT_ADV) || + param->options & BT_LE_ADV_OPT_ANONYMOUS) + { + return -EINVAL; + } + } + + if (atomic_test_bit(adv->flags, BT_ADV_ENABLED)) + { + return -EINVAL; + } + + if (param->id != adv->id) + { + atomic_clear_bit(adv->flags, BT_ADV_RPA_VALID); + } + + return le_ext_adv_param_set(adv, param, false); +} + +int bt_le_ext_adv_start(struct bt_le_ext_adv *adv, + const struct bt_le_ext_adv_start_param *param) +{ + struct bt_conn *conn = NULL; + int err; + + if (adv == NULL) + { + LOG_DBG("adv is NULL"); + + return -EINVAL; + } + + if (atomic_test_bit(adv->flags, BT_ADV_ENABLED)) + { + return -EALREADY; + } + + if (IS_ENABLED(CONFIG_BT_SMP) && atomic_test_bit(bt_dev.flags, + BT_DEV_ID_PENDING)) + { + bt_id_pending_keys_update(); + } + + if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && atomic_test_bit(adv->flags, + BT_ADV_CONNECTABLE)) + { + err = le_adv_start_add_conn(adv, &conn); + if (err) + { + return err; + } + } + + atomic_set_bit_to(adv->flags, BT_ADV_LIMITED, + param && (param->timeout > 0 || param->num_events > 0)); + + if (atomic_test_bit(adv->flags, BT_ADV_CONNECTABLE)) + { + if (IS_ENABLED(CONFIG_BT_PRIVACY) && !atomic_test_bit(adv->flags, + BT_ADV_USE_IDENTITY) && + (!atomic_test_and_clear_bit(adv->flags, + BT_ADV_RANDOM_ADDR_UPDATED) || + atomic_test_bit(adv->flags, BT_PER_ADV_ENABLED) || + !atomic_test_bit(adv->flags, BT_ADV_RPA_VALID))) + { + bt_id_set_adv_private_addr(adv); + } + } + else + { + if (!atomic_test_bit(adv->flags, BT_ADV_USE_IDENTITY) && + (!atomic_test_and_clear_bit(adv->flags, + BT_ADV_RANDOM_ADDR_UPDATED) || + atomic_test_bit(adv->flags, BT_PER_ADV_ENABLED) || + (IS_ENABLED(CONFIG_BT_PRIVACY) && !atomic_test_bit(adv->flags, + BT_ADV_RPA_VALID)))) + { + bt_id_set_adv_private_addr(adv); + } + } + + err = bt_le_adv_set_enable_ext(adv, true, param); + if (err) + { + LOG_ERR("Failed to start advertiser"); + if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && conn) + { + bt_conn_set_state(conn, BT_CONN_DISCONNECTED); + bt_conn_unref(conn); + } + + return err; + } + + if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && conn) + { + /* If undirected connectable advertiser we have created a + * connection object that we don't yet give to the application. Since + * we don't give the application a reference to manage in this case, we + * need to release this reference here + */ + + bt_conn_unref(conn); + } + + return 0; +} + +int bt_le_ext_adv_stop(struct bt_le_ext_adv *adv) +{ + if (adv == NULL) + { + LOG_DBG("adv is NULL"); + + return -EINVAL; + } + + (void)bt_le_lim_adv_cancel_timeout(adv); + + if (!atomic_test_bit(adv->flags, BT_ADV_ENABLED)) + { + return 0; + } + + if (atomic_test_and_clear_bit(adv->flags, BT_ADV_LIMITED)) + { + bt_id_adv_limited_stopped(adv); + +#if defined(CONFIG_BT_SMP) + bt_id_pending_keys_update(); +#endif + } + + if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && atomic_test_bit(adv->flags, + BT_ADV_CONNECTABLE)) + { + le_adv_stop_free_conn(adv, 0); + } + + return bt_le_adv_set_enable_ext(adv, false, NULL); +} + +int bt_le_ext_adv_set_data( + struct bt_le_ext_adv *adv, const struct bt_data *ad, size_t ad_len, + const struct bt_data *sd, size_t sd_len) +{ + bool ext_adv; + bool scannable; + + if (adv == NULL) + { + LOG_DBG("adv is NULL"); + + return -EINVAL; + } + + ext_adv = atomic_test_bit(adv->flags, BT_ADV_EXT_ADV); + scannable = atomic_test_bit(adv->flags, BT_ADV_SCANNABLE); + + if (ext_adv) + { + if ((scannable && ad_len) || (!scannable && sd_len)) + { + return -ENOTSUP; + } + } + + return le_adv_update(adv, ad, ad_len, sd, sd_len, ext_adv, scannable); +} + +int bt_le_ext_adv_delete(struct bt_le_ext_adv *adv) +{ + struct bt_hci_cp_le_remove_adv_set *cp; + struct net_buf *buf; + int err; + + if (!BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features)) + { + return -ENOTSUP; + } + + if (adv == NULL) + { + LOG_DBG("adv is NULL"); + + return -EINVAL; + } + + /* Advertising set should be stopped first */ + + if (atomic_test_bit(adv->flags, BT_ADV_ENABLED)) + { + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + LOG_WRN("No HCI buffers"); + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = adv->handle; + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_REMOVE_ADV_SET, buf, NULL); + if (err) + { + return err; + } + + adv_delete(adv); + + return 0; +} +#endif /* defined(CONFIG_BT_EXT_ADV) */ + +static void adv_timeout(struct k_work *work) +{ + int err = 0; + struct k_work_delayable *dwork; + struct bt_le_ext_adv *adv; + + dwork = k_work_delayable_from_work(work); + adv = CONTAINER_OF(dwork, struct bt_le_ext_adv, lim_adv_timeout_work); + +#if defined(CONFIG_BT_EXT_ADV) + if (adv == bt_dev.adv) + { + err = bt_le_adv_stop(); + } + else + { + err = bt_le_ext_adv_stop(adv); + } +#else + err = bt_le_adv_stop(); +#endif + if (err) + { + LOG_WRN("Failed to stop advertising: %d", err); + } +} + +#if defined(CONFIG_BT_PER_ADV) +int bt_le_per_adv_set_param(struct bt_le_ext_adv *adv, + const struct bt_le_per_adv_param *param) +{ +#if defined(CONFIG_BT_PER_ADV_RSP) + /* The v2 struct can be used even if we end up sending a v1 command + * because they have the same layout for the common fields. V2 simply adds + * fields at the end of the v1 command. + */ + + struct bt_hci_cp_le_set_per_adv_param_v2 *cp; +#else + struct bt_hci_cp_le_set_per_adv_param *cp; +#endif /* CONFIG_BT_PER_ADV_RSP */ + + uint16_t opcode; + uint16_t size; + struct net_buf *buf; + int err; + uint16_t props = 0; + + if (IS_ENABLED(CONFIG_BT_PER_ADV_RSP) && + BT_FEAT_LE_PAWR_ADVERTISER(bt_dev.le.features)) + { + opcode = BT_HCI_OP_LE_SET_PER_ADV_PARAM_V2; + size = sizeof(struct bt_hci_cp_le_set_per_adv_param_v2); + } + else if (BT_FEAT_LE_EXT_PER_ADV(bt_dev.le.features)) + { + opcode = BT_HCI_OP_LE_SET_PER_ADV_PARAM; + size = sizeof(struct bt_hci_cp_le_set_per_adv_param); + } + else + { + return -ENOTSUP; + } + + if (adv == NULL) + { + LOG_DBG("adv is NULL"); + + return -EINVAL; + } + + if (atomic_test_bit(adv->flags, BT_ADV_SCANNABLE)) + { + return -EINVAL; + } + else if (atomic_test_bit(adv->flags, BT_ADV_CONNECTABLE)) + { + return -EINVAL; + } + else if (!atomic_test_bit(adv->flags, BT_ADV_EXT_ADV)) + { + return -EINVAL; + } + + if (param->interval_min < BT_GAP_PER_ADV_MIN_INTERVAL || + param->interval_max > BT_GAP_PER_ADV_MAX_INTERVAL || + param->interval_min > param->interval_max) + { + return -EINVAL; + } + + if (!BT_FEAT_LE_PER_ADV_ADI_SUPP(bt_dev.le.features) && + (param->options & BT_LE_PER_ADV_OPT_INCLUDE_ADI)) + { + return -ENOTSUP; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, size); + (void)memset(cp, 0, size); + + cp->handle = adv->handle; + cp->min_interval = sys_cpu_to_le16(param->interval_min); + cp->max_interval = sys_cpu_to_le16(param->interval_max); + + if (param->options & BT_LE_PER_ADV_OPT_USE_TX_POWER) + { + props |= BT_HCI_LE_ADV_PROP_TX_POWER; + } + + cp->props = sys_cpu_to_le16(props); + +#if defined(CONFIG_BT_PER_ADV_RSP) + if (opcode == BT_HCI_OP_LE_SET_PER_ADV_PARAM_V2) + { + cp->num_subevents = param->num_subevents; + cp->subevent_interval = param->subevent_interval; + cp->response_slot_delay = param->response_slot_delay; + cp->response_slot_spacing = param->response_slot_spacing; + cp->num_response_slots = param->num_response_slots; + } +#endif /* CONFIG_BT_PER_ADV_RSP */ + + err = bt_hci_cmd_send_sync(opcode, buf, NULL); + if (err) + { + return err; + } + + if (param->options & BT_LE_PER_ADV_OPT_INCLUDE_ADI) + { + atomic_set_bit(adv->flags, BT_PER_ADV_INCLUDE_ADI); + } + else + { + atomic_clear_bit(adv->flags, BT_PER_ADV_INCLUDE_ADI); + } + + atomic_set_bit(adv->flags, BT_PER_ADV_PARAMS_SET); + + return 0; +} + +int bt_le_per_adv_set_data(const struct bt_le_ext_adv *adv, + const struct bt_data *ad, size_t ad_len) +{ + size_t total_len_bytes = 0; + + if (!BT_FEAT_LE_EXT_PER_ADV(bt_dev.le.features)) + { + return -ENOTSUP; + } + + if (adv == NULL) + { + LOG_DBG("adv is NULL"); + + return -EINVAL; + } + + if (!atomic_test_bit(adv->flags, BT_PER_ADV_PARAMS_SET)) + { + return -EINVAL; + } + + if (ad_len != 0 && ad == NULL) + { + return -EINVAL; + } + + for (size_t i = 0; i < ad_len; i++) + { + total_len_bytes += ad[i].data_len + 2; + } + + if ((total_len_bytes > BT_HCI_LE_PER_ADV_FRAG_MAX_LEN) && + atomic_test_bit(adv->flags, BT_PER_ADV_ENABLED)) + { + /* It is not allowed to set periodic advertising data + * in multiple operations while it is running. + */ + + return -EINVAL; + } + + return hci_set_per_adv_data(adv, ad, ad_len); +} + +int bt_le_per_adv_update_did(const struct bt_le_ext_adv *adv) +{ + struct bt_hci_cp_le_set_per_adv_data *cp; + struct net_buf *buf; + + if (!BT_FEAT_LE_PER_ADV_ADI_SUPP(bt_dev.le.features)) + { + return -ENOTSUP; + } + + if (adv == NULL) + { + return -EINVAL; + } + + if (!atomic_test_bit(adv->flags, BT_PER_ADV_ENABLED)) + { + return -EINVAL; + } + + if (!atomic_test_bit(adv->flags, BT_PER_ADV_INCLUDE_ADI)) + { + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (buf == NULL) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + (void)memset(cp, 0, sizeof(*cp)); + + cp->handle = adv->handle; + cp->op = BT_HCI_LE_EXT_ADV_OP_UNCHANGED_DATA; + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_PER_ADV_DATA, buf, NULL); +} + +int bt_le_per_adv_set_subevent_data( + const struct bt_le_ext_adv *adv, uint8_t num_subevents, + const struct bt_le_per_adv_subevent_data_params *params) +{ + struct bt_hci_cp_le_set_pawr_subevent_data *cp; + struct bt_hci_cp_le_set_pawr_subevent_data_element *element; + struct net_buf *buf; + size_t cmd_length = sizeof(*cp); + + if (!BT_FEAT_LE_PAWR_ADVERTISER(bt_dev.le.features)) + { + return -ENOTSUP; + } + + if (adv == NULL) + { + LOG_DBG("adv is NULL"); + + return -EINVAL; + } + + for (size_t i = 0; i < num_subevents; i++) + { + cmd_length += + sizeof(struct bt_hci_cp_le_set_pawr_subevent_data_element); + cmd_length += params[i].data->len; + } + + if (cmd_length > 0xff) + { + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->adv_handle = adv->handle; + cp->num_subevents = num_subevents; + + for (size_t i = 0; i < num_subevents; i++) + { + element = net_buf_add(buf, sizeof(*element)); + element->subevent = params[i].subevent; + element->response_slot_start = params[i].response_slot_start; + element->response_slot_count = params[i].response_slot_count; + element->subevent_data_length = params[i].data->len; + net_buf_add_mem(buf, params[i].data->data, params[i].data->len); + } + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_PER_ADV_SUBEVENT_DATA, buf, + NULL); +} + +static int bt_le_per_adv_enable(struct bt_le_ext_adv *adv, bool enable) +{ + struct bt_hci_cp_le_set_per_adv_enable *cp; + struct net_buf *buf; + struct bt_hci_cmd_state_set state; + int err; + + if (!BT_FEAT_LE_EXT_PER_ADV(bt_dev.le.features)) + { + return -ENOTSUP; + } + + if (adv == NULL) + { + LOG_DBG("adv is NULL"); + + return -EINVAL; + } + + /* TODO: We could setup some default ext adv params if not already set */ + + if (!atomic_test_bit(adv->flags, BT_PER_ADV_PARAMS_SET)) + { + return -EINVAL; + } + + if (atomic_test_bit(adv->flags, BT_PER_ADV_ENABLED) == enable) + { + return -EALREADY; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + (void)memset(cp, 0, sizeof(*cp)); + + cp->handle = adv->handle; + + if (enable) + { + cp->enable = BT_HCI_LE_SET_PER_ADV_ENABLE_ENABLE; + + if (atomic_test_bit(adv->flags, BT_PER_ADV_INCLUDE_ADI)) + { + cp->enable |= BT_HCI_LE_SET_PER_ADV_ENABLE_ADI; + } + } + else + { + cp->enable = 0U; + } + + bt_hci_cmd_state_set_init(buf, &state, adv->flags, BT_PER_ADV_ENABLED, + enable); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_PER_ADV_ENABLE, buf, NULL); + if (err) + { + return err; + } + + return 0; +} + +int bt_le_per_adv_start(struct bt_le_ext_adv *adv) +{ + return bt_le_per_adv_enable(adv, true); +} + +int bt_le_per_adv_stop(struct bt_le_ext_adv *adv) +{ + return bt_le_per_adv_enable(adv, false); +} + +#if defined(CONFIG_BT_PER_ADV_RSP) +void bt_hci_le_per_adv_subevent_data_request(struct net_buf *buf) +{ + struct bt_hci_evt_le_per_adv_subevent_data_request *evt; + struct bt_le_per_adv_data_request request; + struct bt_le_ext_adv *adv; + + if (buf->len < sizeof(struct bt_hci_evt_le_per_adv_subevent_data_request)) + { + LOG_ERR("Invalid data request"); + + return; + } + + evt = + net_buf_pull_mem(buf, + sizeof(struct bt_hci_evt_le_per_adv_subevent_data_request)); + adv = bt_hci_adv_lookup_handle(evt->adv_handle); + if (!adv) + { + LOG_ERR("Unknown advertising handle %d", evt->adv_handle); + + return; + } + + request.start = evt->subevent_start; + request.count = evt->subevent_data_count; + + if (adv->cb && adv->cb->pawr_data_request) + { + adv->cb->pawr_data_request(adv, &request); + } +} + +#if defined(CONFIG_BT_PER_ADV_RSP_REASSEMBLY) +static void pawr_rsp_reassembly_reset(struct pawr_rsp_reassembly *reassembly) +{ + net_buf_simple_reset(&reassembly->buf); + reassembly->report_truncated = false; +} + +static bool pawr_rsp_reassembly_active( + const struct pawr_rsp_reassembly *reassembly) +{ + return reassembly->buf.len != 0 || reassembly->report_truncated; +} +#endif /* CONFIG_BT_PER_ADV_RSP_REASSEMBLY */ + +void bt_hci_le_per_adv_response_report(struct net_buf *buf) +{ + struct bt_hci_evt_le_per_adv_response_report *evt; + struct bt_hci_evt_le_per_adv_response *response; + struct bt_le_ext_adv *adv; + struct bt_le_per_adv_response_info info; + struct net_buf_simple data; + + if (buf->len < sizeof(struct bt_hci_evt_le_per_adv_response_report)) + { + LOG_ERR("Invalid response report"); + + return; + } + + evt = + net_buf_pull_mem(buf, + sizeof(struct bt_hci_evt_le_per_adv_response_report)); + adv = bt_hci_adv_lookup_handle(evt->adv_handle); + if (!adv) + { + LOG_ERR("Unknown advertising handle %d", evt->adv_handle); + + return; + } + + info.subevent = evt->subevent; + info.tx_status = evt->tx_status; + + for (uint8_t i = 0; i < evt->num_responses; i++) + { + if (buf->len < sizeof(struct bt_hci_evt_le_per_adv_response)) + { + LOG_ERR("Invalid response report"); + + return; + } + + response = net_buf_pull_mem(buf, + sizeof(struct bt_hci_evt_le_per_adv_response)); + info.tx_power = response->tx_power; + info.rssi = response->rssi; + info.cte_type = bt_get_df_cte_type(response->cte_type); + info.response_slot = response->response_slot; + + if (buf->len < response->data_length) + { + LOG_ERR("Invalid response report"); + + return; + } + +#if defined(CONFIG_BT_PER_ADV_RSP_REASSEMBLY) + if (pawr_rsp_reassembly_active(&adv->pawr_rsp_reassembly) && + (adv->pawr_rsp_reassembly.subevent != info.subevent || + adv->pawr_rsp_reassembly.response_slot != info.response_slot)) + { + /* A partial chain is buffered (or was truncated) for a + * different response. Discard its state and start reassembling the + * new one. + */ + + LOG_WRN("Response reassembly interrupted, discarding"); + pawr_rsp_reassembly_reset(&adv->pawr_rsp_reassembly); + } +#endif + + if (response->data_status == + BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS_PARTIAL) + { +#if defined(CONFIG_BT_PER_ADV_RSP_REASSEMBLY) + if (net_buf_simple_tailroom( + &adv->pawr_rsp_reassembly.buf) < response->data_length) + { + /* The fragment does not fit. Mark the chain truncated so + * the remaining fragments, including the terminating COMPLETE + * fragment, are dropped instead of reported. + */ + + LOG_WRN("Response reassembly buffer overflow, discarding"); + net_buf_simple_reset(&adv->pawr_rsp_reassembly.buf); + adv->pawr_rsp_reassembly.subevent = info.subevent; + adv->pawr_rsp_reassembly.response_slot = info.response_slot; + adv->pawr_rsp_reassembly.report_truncated = true; + (void)net_buf_pull_mem(buf, response->data_length); + } + else + { + /* Record the response identity on the first fragment and + * buffer the data. The reassembled data is only reported once + * the matching COMPLETE report arrives. + */ + + adv->pawr_rsp_reassembly.subevent = info.subevent; + adv->pawr_rsp_reassembly.response_slot = info.response_slot; + net_buf_simple_add_mem(&adv->pawr_rsp_reassembly.buf, + net_buf_pull_mem(buf, + response->data_length), + response->data_length); + } +#else + LOG_WRN("Incomplete response report received, discarding"); + (void)net_buf_pull_mem(buf, response->data_length); +#endif /* CONFIG_BT_PER_ADV_RSP_REASSEMBLY */ + } + else if (response->data_status == + BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS_RX_FAILED) + { +#if defined(CONFIG_BT_PER_ADV_RSP_REASSEMBLY) + /* Reception failed, drop any partial chain in progress. */ + + pawr_rsp_reassembly_reset(&adv->pawr_rsp_reassembly); +#endif /* CONFIG_BT_PER_ADV_RSP_REASSEMBLY */ + (void)net_buf_pull_mem(buf, response->data_length); + + if (adv->cb && adv->cb->pawr_response) + { + adv->cb->pawr_response(adv, &info, NULL); + } + } + else if (response->data_status == + BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS_COMPLETE) + { +#if defined(CONFIG_BT_PER_ADV_RSP_REASSEMBLY) + if (adv->pawr_rsp_reassembly.report_truncated || + net_buf_simple_tailroom( + &adv->pawr_rsp_reassembly.buf) < response->data_length) + { + /* The chain was already truncated, or the final fragment + * does not fit. Drop the chain instead of reporting a partial + * response as if it were complete. + */ + + LOG_WRN("Response reassembly buffer overflow, discarding"); + (void)net_buf_pull_mem(buf, response->data_length); + } + else if (adv->pawr_rsp_reassembly.buf.len != 0) + { + /* Final fragment of a buffered chain. */ + + net_buf_simple_add_mem(&adv->pawr_rsp_reassembly.buf, + net_buf_pull_mem(buf, + response->data_length), + response->data_length); + + if (adv->cb && adv->cb->pawr_response) + { + adv->cb->pawr_response(adv, &info, + &adv->pawr_rsp_reassembly.buf); + } + } + else + { + /* Self-contained COMPLETE, no buffered chain. */ + + net_buf_simple_init_with_data(&data, net_buf_pull_mem(buf, + response->data_length), + response->data_length); + + if (adv->cb && adv->cb->pawr_response) + { + adv->cb->pawr_response(adv, &info, &data); + } + } + + pawr_rsp_reassembly_reset(&adv->pawr_rsp_reassembly); +#else + net_buf_simple_init_with_data(&data, net_buf_pull_mem(buf, + response->data_length), + response->data_length); + + if (adv->cb && adv->cb->pawr_response) + { + adv->cb->pawr_response(adv, &info, &data); + } +#endif /* CONFIG_BT_PER_ADV_RSP_REASSEMBLY */ + } + else + { + LOG_ERR("Invalid data status %d", response->data_status); + (void)net_buf_pull_mem(buf, response->data_length); + } + } +} +#endif /* CONFIG_BT_PER_ADV_RSP */ + +#if defined(CONFIG_BT_PER_ADV_SYNC_TRANSFER_SENDER) +int bt_le_per_adv_set_info_transfer( + const struct bt_le_ext_adv *adv, const struct bt_conn *conn, + uint16_t service_data) +{ + struct bt_hci_cp_le_per_adv_set_info_transfer *cp; + struct net_buf *buf; + + if (!BT_FEAT_LE_EXT_PER_ADV(bt_dev.le.features)) + { + return -ENOTSUP; + } + else if (!BT_FEAT_LE_PAST_SEND(bt_dev.le.features)) + { + return -ENOTSUP; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + (void)memset(cp, 0, sizeof(*cp)); + + cp->conn_handle = sys_cpu_to_le16(conn->handle); + cp->adv_handle = adv->handle; + cp->service_data = sys_cpu_to_le16(service_data); + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_PER_ADV_SET_INFO_TRANSFER, buf, + NULL); +} +#endif /* CONFIG_BT_PER_ADV_SYNC_TRANSFER_SENDER */ +#endif /* CONFIG_BT_PER_ADV */ + +#if defined(CONFIG_BT_EXT_ADV) +#if defined(CONFIG_BT_BROADCASTER) +void bt_hci_le_adv_set_terminated(struct net_buf *buf) +{ + struct bt_hci_evt_le_adv_set_terminated *evt; + struct bt_le_ext_adv *adv; + uint16_t conn_handle; +#if defined(CONFIG_BT_CONN) && (CONFIG_BT_EXT_ADV_MAX_ADV_SET > 1) + bool was_adv_enabled; +#endif + + evt = (void *)buf->data; + adv = bt_hci_adv_lookup_handle(evt->adv_handle); + conn_handle = sys_le16_to_cpu(evt->conn_handle); + + LOG_DBG("status 0x%02x %s adv_handle %u conn_handle 0x%02x num %u", + evt->status, + bt_hci_err_to_str(evt->status), evt->adv_handle, conn_handle, + evt->num_completed_ext_adv_evts); + + if (!adv) + { + LOG_ERR("No valid adv"); + return; + } + + (void)bt_le_lim_adv_cancel_timeout(adv); + +#if defined(CONFIG_BT_CONN) && (CONFIG_BT_EXT_ADV_MAX_ADV_SET > 1) + was_adv_enabled = atomic_test_bit(adv->flags, BT_ADV_ENABLED); +#endif + + atomic_clear_bit(adv->flags, BT_ADV_ENABLED); + +#if defined(CONFIG_BT_CONN) && (CONFIG_BT_EXT_ADV_MAX_ADV_SET > 1) + bt_dev.adv_conn_id = adv->id; + for (int i = 0; i < ARRAY_SIZE(bt_dev.cached_conn_complete); i++) + { + if (bt_dev.cached_conn_complete[i].valid && + bt_dev.cached_conn_complete[i].evt.handle == evt->conn_handle) + { + if (was_adv_enabled) + { + /* Process the cached connection complete event with the + * advertising set context. + * + * If the advertiser has been stopped before the connection + * complete event has been raised to the application, we + * discard the event. + */ + + bt_hci_le_enh_conn_complete(&bt_dev.cached_conn_complete[ + i].evt, + adv); + } + + bt_dev.cached_conn_complete[i].valid = false; + } + } +#endif + + if (evt->status && IS_ENABLED(CONFIG_BT_PERIPHERAL) && + atomic_test_bit(adv->flags, BT_ADV_CONNECTABLE)) + { + /* This will call connected callback for high duty cycle + * directed advertiser timeout. + */ + + le_adv_stop_free_conn(adv, evt->status); + } + + if (IS_ENABLED(CONFIG_BT_CONN) && !evt->status) + { + struct bt_conn *conn = bt_conn_lookup_handle(conn_handle, + BT_CONN_TYPE_LE); + + if (conn) + { + if (IS_ENABLED(CONFIG_BT_PRIVACY) && !atomic_test_bit(adv->flags, + BT_ADV_USE_IDENTITY)) + { + /* Set Responder address unless already set */ + + conn->le.resp_addr.type = BT_ADDR_LE_RANDOM; + if (bt_addr_eq(&conn->le.resp_addr.a, BT_ADDR_ANY)) + { + bt_addr_copy(&conn->le.resp_addr.a, &adv->adv_addr.a); + } + } + else if (adv->options & BT_LE_ADV_OPT_USE_NRPA) + { + bt_addr_le_copy(&conn->le.resp_addr, &adv->adv_addr); + } + else + { + bt_addr_le_copy(&conn->le.resp_addr, &bt_dev.id_addr[ + conn->id]); + } + + if (adv->cb && adv->cb->connected) + { + struct bt_le_ext_adv_connected_info info = + { + .conn = conn, + }; + + adv->cb->connected(adv, &info); + } + + bt_conn_unref(conn); + } + } + + if (atomic_test_and_clear_bit(adv->flags, BT_ADV_LIMITED)) + { + bt_id_adv_limited_stopped(adv); + +#if defined(CONFIG_BT_SMP) + bt_id_pending_keys_update(); +#endif + + if (adv->cb && adv->cb->sent) + { + struct bt_le_ext_adv_sent_info info = + { + .num_sent = evt->num_completed_ext_adv_evts, + }; + + adv->cb->sent(adv, &info); + } + } + + if (adv == bt_dev.adv) + { + bt_le_adv_delete_legacy(); + } +} + +void bt_hci_le_scan_req_received(struct net_buf *buf) +{ + struct bt_hci_evt_le_scan_req_received *evt; + struct bt_le_ext_adv *adv; + + evt = (void *)buf->data; + adv = bt_hci_adv_lookup_handle(evt->handle); + + LOG_DBG("handle %u peer %s%s", evt->handle, bt_addr_le_str(&evt->addr), + bt_addr_le_is_resolved(&evt->addr) ? " (resolved)" : ""); + + if (!adv) + { + LOG_ERR("No valid adv"); + return; + } + + if (adv->cb && adv->cb->scanned) + { + struct bt_le_ext_adv_scanned_info info; + bt_addr_le_t id_addr; + + if (bt_addr_le_is_resolved(&evt->addr)) + { + bt_addr_le_copy_resolved(&id_addr, &evt->addr); + } + else + { + bt_addr_le_copy(&id_addr, bt_lookup_id_addr(adv->id, &evt->addr)); + } + + info.addr = &id_addr; + adv->cb->scanned(adv, &info); + } +} +#endif /* defined(CONFIG_BT_BROADCASTER) */ +#endif /* defined(CONFIG_BT_EXT_ADV) */ diff --git a/libs/zblue/host/adv.h b/libs/zblue/host/adv.h new file mode 100644 index 0000000000000..70347a22a3a2b --- /dev/null +++ b/libs/zblue/host/adv.h @@ -0,0 +1,34 @@ +/**************************************************************************** + * libs/zblue/host/adv.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017-2021 Nordic Semiconductor ASA + * Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +struct bt_le_ext_adv *bt_le_adv_lookup_legacy(void); + +void bt_le_adv_delete_legacy(void); +int bt_le_adv_set_enable(struct bt_le_ext_adv *adv, bool enable); + +void bt_le_ext_adv_foreach( + void (*func)(struct bt_le_ext_adv *adv, void *data), void *data); + +int bt_le_adv_set_enable_ext(struct bt_le_ext_adv *adv, bool enable, + const struct bt_le_ext_adv_start_param *param); +int bt_le_adv_set_enable_legacy(struct bt_le_ext_adv *adv, bool enable); +int bt_le_lim_adv_cancel_timeout(struct bt_le_ext_adv *adv); +void bt_adv_reset_adv_pool(void); diff --git a/libs/zblue/host/aes_ccm.c b/libs/zblue/host/aes_ccm.c new file mode 100644 index 0000000000000..c34ccb54436a3 --- /dev/null +++ b/libs/zblue/host/aes_ccm.c @@ -0,0 +1,320 @@ +/**************************************************************************** + * libs/zblue/host/aes_ccm.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "common/bt_str.h" + +#define LOG_LEVEL CONFIG_BT_HCI_CORE_LOG_LEVEL +LOG_MODULE_REGISTER(bt_aes_ccm); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static inline void xor16(uint8_t *dst, const uint8_t *a, const uint8_t *b) +{ + dst[0] = a[0] ^ b[0]; + dst[1] = a[1] ^ b[1]; + dst[2] = a[2] ^ b[2]; + dst[3] = a[3] ^ b[3]; + dst[4] = a[4] ^ b[4]; + dst[5] = a[5] ^ b[5]; + dst[6] = a[6] ^ b[6]; + dst[7] = a[7] ^ b[7]; + dst[8] = a[8] ^ b[8]; + dst[9] = a[9] ^ b[9]; + dst[10] = a[10] ^ b[10]; + dst[11] = a[11] ^ b[11]; + dst[12] = a[12] ^ b[12]; + dst[13] = a[13] ^ b[13]; + dst[14] = a[14] ^ b[14]; + dst[15] = a[15] ^ b[15]; +} + +/* b field is assumed to have the nonce already present in bytes 1-13 */ + +static int ccm_calculate_X0(const uint8_t key[16], + const uint8_t *aad, + uint8_t aad_len, + size_t mic_size, + uint16_t msg_len, + uint8_t b[16], + uint8_t X0[16]) +{ + int i; + int j; + int err; + + /* X_0 = e(AppKey, flags || nonce || length) */ + + b[0] = (((mic_size - 2) / 2) << 3) | ((!!aad_len) << 6) | 0x01; + + sys_put_be16(msg_len, b + 14); + + err = bt_encrypt_be(key, b, X0); + if (err) + { + return err; + } + + /* If AAD is being used to authenticate, include it here */ + + if (aad_len) + { + sys_put_be16(aad_len, b); + + for (i = 0; i < sizeof(uint16_t); i++) + { + b[i] = X0[i] ^ b[i]; + } + + j = 0; + aad_len += sizeof(uint16_t); + while (aad_len > 16) + { + do + { + b[i] = X0[i] ^ aad[j]; + i++, j++; + } + while (i < 16); + + aad_len -= 16; + i = 0; + + err = bt_encrypt_be(key, b, X0); + if (err) + { + return err; + } + } + + for (; i < aad_len; i++, j++) + { + b[i] = X0[i] ^ aad[j]; + } + + for (i = aad_len; i < 16; i++) + { + b[i] = X0[i]; + } + + err = bt_encrypt_be(key, b, X0); + if (err) + { + return err; + } + } + + return 0; +} + +static int ccm_auth(const uint8_t key[16], + uint8_t nonce[13], + const uint8_t *cleartext_msg, + uint16_t msg_len, + const uint8_t *aad, + size_t aad_len, + uint8_t *mic, + size_t mic_size) +{ + uint8_t b[16]; + uint8_t Xn[16]; + uint8_t s0[16]; + uint16_t blk_cnt; + uint16_t last_blk; + int err; + int j; + int i; + + last_blk = msg_len % 16; + blk_cnt = (msg_len + 15) / 16; + if (!last_blk) + { + last_blk = 16U; + } + + b[0] = 0x01; + memcpy(b + 1, nonce, 13); + + /* S[0] = e(AppKey, 0x01 || nonce || 0x0000) */ + + sys_put_be16(0x0000, &b[14]); + + err = bt_encrypt_be(key, b, s0); + if (err) + { + return err; + } + + ccm_calculate_X0(key, aad, aad_len, mic_size, msg_len, b, Xn); + + for (j = 0; j < blk_cnt; j++) + { + /* X_1 = e(AppKey, X_0 ^ Payload[0-15]) */ + + if (j + 1 == blk_cnt) + { + for (i = 0; i < last_blk; i++) + { + b[i] = Xn[i] ^ cleartext_msg[(j * 16) + i]; + } + + memcpy(&b[i], &Xn[i], 16 - i); + } + else + { + xor16(b, Xn, &cleartext_msg[j * 16]); + } + + err = bt_encrypt_be(key, b, Xn); + if (err) + { + return err; + } + } + + /* MIC = C_mic ^ X_1 */ + + for (i = 0; i < mic_size; i++) + { + mic[i] = s0[i] ^ Xn[i]; + } + + return 0; +} + +static int ccm_crypt(const uint8_t key[16], + const uint8_t nonce[13], + const uint8_t *in_msg, + uint8_t *out_msg, + uint16_t msg_len) +{ + uint8_t a_i[16]; + uint8_t s_i[16]; + uint16_t last_blk; + uint16_t blk_cnt; + size_t i; + size_t j; + int err; + + last_blk = msg_len % 16; + blk_cnt = (msg_len + 15) / 16; + if (!last_blk) + { + last_blk = 16U; + } + + a_i[0] = 0x01; + memcpy(&a_i[1], nonce, 13); + + for (j = 0; j < blk_cnt; j++) + { + /* S_1 = e(AppKey, 0x01 || nonce || 0x0001) */ + + sys_put_be16(j + 1, &a_i[14]); + + err = bt_encrypt_be(key, a_i, s_i); + if (err) + { + return err; + } + + /* Encrypted = Payload[0-15] ^ C_1 */ + + if (j < blk_cnt - 1) + { + xor16(&out_msg[j * 16], s_i, &in_msg[j * 16]); + } + else + { + for (i = 0; i < last_blk; i++) + { + out_msg[(j * 16) + i] = in_msg[(j * 16) + i] ^ s_i[i]; + } + } + } + + return 0; +} + +int bt_ccm_decrypt(const uint8_t key[16], + uint8_t nonce[13], + const uint8_t *enc_data, + size_t len, + const uint8_t *aad, + size_t aad_len, + uint8_t *plaintext, + size_t mic_size) +{ + uint8_t mic[16]; + + if (aad_len >= 0xff00 || mic_size > sizeof(mic) || len > UINT16_MAX) + { + return -EINVAL; + } + + ccm_crypt(key, nonce, enc_data, plaintext, len); + + ccm_auth(key, nonce, plaintext, len, aad, aad_len, mic, mic_size); + + if (memcmp(mic, enc_data + len, mic_size)) + { + return -EBADMSG; + } + + return 0; +} + +int bt_ccm_encrypt(const uint8_t key[16], + uint8_t nonce[13], + const uint8_t *plaintext, + size_t len, + const uint8_t *aad, + size_t aad_len, + uint8_t *enc_data, + size_t mic_size) +{ + uint8_t *mic = enc_data + len; + + LOG_DBG("key %s", bt_hex(key, 16)); + LOG_DBG("nonce %s", bt_hex(nonce, 13)); + LOG_DBG("msg (len %zu) %s", len, bt_hex(plaintext, len)); + LOG_DBG("aad_len %zu mic_size %zu", aad_len, mic_size); + + /* Unsupported AAD size */ + + if (aad_len >= 0xff00 || mic_size > 16 || len > UINT16_MAX) + { + return -EINVAL; + } + + ccm_auth(key, nonce, plaintext, len, aad, aad_len, mic, mic_size); + + ccm_crypt(key, nonce, plaintext, enc_data, len); + + return 0; +} diff --git a/libs/zblue/host/att.c b/libs/zblue/host/att.c new file mode 100644 index 0000000000000..965d08bc15bf8 --- /dev/null +++ b/libs/zblue/host/att.c @@ -0,0 +1,5116 @@ +/**************************************************************************** + * libs/zblue/host/att.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* att.c - Attribute protocol handling */ + +/* Copyright (c) 2015-2016 Intel Corporation + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "att_internal.h" +#include "common/bt_str.h" +#include "conn_internal.h" +#include "gatt_internal.h" +#include "hci_core.h" +#include "l2cap_internal.h" +#include "smp.h" + +#define LOG_LEVEL CONFIG_BT_ATT_LOG_LEVEL +LOG_MODULE_REGISTER(bt_att); + +#define ATT_CHAN(_ch) CONTAINER_OF(_ch, struct bt_att_chan, chan.chan) +#define ATT_REQ(_node) CONTAINER_OF(_node, struct bt_att_req, node) + +#define ATT_CMD_MASK 0x40 + +#if defined(CONFIG_BT_EATT) +#define ATT_CHAN_MAX (CONFIG_BT_EATT_MAX + 1) +#else +#define ATT_CHAN_MAX 1 +#endif /* CONFIG_BT_EATT */ + +typedef enum __packed +{ + ATT_COMMAND, + ATT_REQUEST, + ATT_RESPONSE, + ATT_NOTIFICATION, + ATT_CONFIRMATION, + ATT_INDICATION, + ATT_UNKNOWN, +} att_type_t; + +static att_type_t att_op_get_type(uint8_t op); + +#if CONFIG_BT_ATT_PREPARE_COUNT > 0 +struct bt_attr_data +{ + uint16_t handle; + uint16_t offset; +}; + +/* Pool for incoming ATT packets */ + +NET_BUF_POOL_DEFINE(prep_pool, CONFIG_BT_ATT_PREPARE_COUNT, BT_ATT_BUF_SIZE, + sizeof(struct bt_attr_data), NULL); +#endif /* CONFIG_BT_ATT_PREPARE_COUNT */ + +K_MEM_SLAB_DEFINE_STATIC_TYPE(req_slab, + struct bt_att_req, + CONFIG_BT_ATT_TX_COUNT); + +enum +{ + ATT_CONNECTED, + ATT_ENHANCED, + ATT_PENDING_SENT, + ATT_OUT_OF_SYNC_SENT, + + /* Total number of flags - must be at the end of the enum */ + + ATT_NUM_FLAGS, +}; + +struct bt_att_tx_meta_data; +typedef void (*bt_att_tx_cb_t)(struct bt_conn *conn, + struct bt_att_tx_meta_data *user_data); + +struct bt_att_tx_meta_data +{ + int err; + uint8_t opcode; + uint16_t attr_count; + struct bt_att_chan *att_chan; + bt_gatt_complete_func_t func; + void *user_data; + enum bt_att_chan_opt chan_opt; +}; + +/* ATT channel specific data */ + +struct bt_att_chan +{ + /* Connection this channel is associated with */ + + struct bt_att *att; + struct bt_l2cap_le_chan chan; + atomic_define(flags, ATT_NUM_FLAGS); + struct bt_att_req *req; + struct k_fifo tx_queue; + struct k_work_delayable timeout_work; + struct list_node node; +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static bool bt_att_is_enhanced(struct bt_att_chan *chan) +{ + /* Optimization. */ + + if (!IS_ENABLED(CONFIG_BT_EATT)) + { + return false; + } + + return atomic_test_bit(chan->flags, ATT_ENHANCED); +} + +static uint16_t bt_att_mtu(struct bt_att_chan *chan) +{ + /* Core v5.3 Vol 3 Part F 3.4.2: + * + * The server and client shall set ATT_MTU to the minimum of the Client Rx + * MTU and the Server Rx MTU. + */ + + return MIN(chan->chan.rx.mtu, chan->chan.tx.mtu); +} + +/* ATT connection specific data */ + +struct bt_att +{ + struct bt_conn *conn; + + /* Shared request queue */ + + struct list_node reqs; + struct k_fifo tx_queue; +#if CONFIG_BT_ATT_PREPARE_COUNT > 0 + struct list_node prep_queue; +#endif + /* Contains bt_att_chan instance(s) */ + + struct list_node chans; +#if defined(CONFIG_BT_EATT) + struct + { + struct k_work_delayable connection_work; + uint8_t chans_to_connect; + + uint16_t prev_conn_rsp_result; + uint16_t prev_conn_req_result; + uint8_t prev_conn_req_missing_chans; + } eatt; +#endif /* CONFIG_BT_EATT */ +}; + +K_MEM_SLAB_DEFINE_STATIC_TYPE(att_slab, struct bt_att, CONFIG_BT_MAX_CONN); +K_MEM_SLAB_DEFINE_STATIC_TYPE(chan_slab, + struct bt_att_chan, + CONFIG_BT_MAX_CONN *ATT_CHAN_MAX); +static struct bt_att_req cancel; + +/** The thread ATT response handlers likely run on. + * + * Blocking this thread while waiting for an ATT request to resolve can + * cause a deadlock. + * + * This can happen if the application queues ATT requests in the context of + * a callback from the Bluetooth stack. This is because queuing an ATT + * request will block until a request-resource is available, and the + * callbacks run on the same thread as the ATT response handler that frees + * request-resources. + * + * The intended use of this value is to detect the above situation. + */ + +static pid_t att_handle_rsp_thread; + +static struct bt_att_tx_meta_data + tx_meta_data_storage[CONFIG_BT_ATT_TX_COUNT]; + +static struct bt_att_tx_meta_data *att_get_tx_meta_data( + const struct net_buf *buf); +static void att_on_sent_cb(struct bt_att_tx_meta_data *meta); + +#if defined(CONFIG_BT_ATT_ERR_TO_STR) +const char *bt_att_err_to_str(uint8_t att_err) +{ + /* To mapping tables are used to avoid a big gap with NULL-entries. */ +#define ATT_ERR(err) [err] = #err +#define ATT_ERR_SECOND(err) [err - BT_ATT_ERR_WRITE_REQ_REJECTED] = #err + + const char *const first_mapping_table[] = + { + ATT_ERR(BT_ATT_ERR_SUCCESS), + ATT_ERR(BT_ATT_ERR_INVALID_HANDLE), + ATT_ERR(BT_ATT_ERR_READ_NOT_PERMITTED), + ATT_ERR(BT_ATT_ERR_WRITE_NOT_PERMITTED), + ATT_ERR(BT_ATT_ERR_INVALID_PDU), + ATT_ERR(BT_ATT_ERR_AUTHENTICATION), + ATT_ERR(BT_ATT_ERR_NOT_SUPPORTED), + ATT_ERR(BT_ATT_ERR_INVALID_OFFSET), + ATT_ERR(BT_ATT_ERR_AUTHORIZATION), + ATT_ERR(BT_ATT_ERR_PREPARE_QUEUE_FULL), + ATT_ERR(BT_ATT_ERR_ATTRIBUTE_NOT_FOUND), + ATT_ERR(BT_ATT_ERR_ATTRIBUTE_NOT_LONG), + ATT_ERR(BT_ATT_ERR_ENCRYPTION_KEY_SIZE), + ATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN), + ATT_ERR(BT_ATT_ERR_UNLIKELY), + ATT_ERR(BT_ATT_ERR_INSUFFICIENT_ENCRYPTION), + ATT_ERR(BT_ATT_ERR_UNSUPPORTED_GROUP_TYPE), + ATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES), + ATT_ERR(BT_ATT_ERR_DB_OUT_OF_SYNC), + ATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED), + }; + + const char *const second_mapping_table[] = + { + ATT_ERR_SECOND(BT_ATT_ERR_WRITE_REQ_REJECTED), + ATT_ERR_SECOND(BT_ATT_ERR_CCC_IMPROPER_CONF), + ATT_ERR_SECOND(BT_ATT_ERR_PROCEDURE_IN_PROGRESS), + ATT_ERR_SECOND(BT_ATT_ERR_OUT_OF_RANGE), + }; + + if (att_err < ARRAY_SIZE(first_mapping_table) && + first_mapping_table[att_err]) + { + return first_mapping_table[att_err]; + } + else if (att_err >= BT_ATT_ERR_WRITE_REQ_REJECTED) + { + const uint8_t second_index = att_err - BT_ATT_ERR_WRITE_REQ_REJECTED; + + if (second_index < ARRAY_SIZE(second_mapping_table) && + second_mapping_table[second_index]) + { + return second_mapping_table[second_index]; + } + } + + return "(unknown)"; + +#undef ATT_ERR +#undef ATT_ERR_SECOND +} +#endif /* CONFIG_BT_ATT_ERR_TO_STR */ + +static void att_tx_destroy_work_handler(struct k_work *work); +static K_WORK_DEFINE(att_tx_destroy_work, att_tx_destroy_work_handler); +static spinlock_t tx_destroy_queue_lock; +static struct list_node tx_destroy_queue = + LIST_INITIAL_VALUE(tx_destroy_queue); + +static void att_tx_destroy_work_handler(struct k_work *work) +{ + struct net_buf *buf; + struct bt_att_tx_meta_data *p_meta; + struct bt_att_tx_meta_data meta; + struct list_node *buf_node; + irqstate_t key; + bool resubmit; + + key = spin_lock_irqsave(&tx_destroy_queue_lock); + buf_node = list_remove_head(&tx_destroy_queue); + /* If there are more items in the queue, those likely have + * been there before this handler started running and coalesced into a + * single work submission, so we need to resubmit. + */ + + resubmit = !list_is_empty(&tx_destroy_queue); + spin_unlock_irqrestore(&tx_destroy_queue_lock, key); + + /* Spurious wakeups can occur in with some thread interleavings. */ + + if (buf_node == NULL) + { + return; + } + + buf = CONTAINER_OF(buf_node, struct net_buf, node); + p_meta = att_get_tx_meta_data(buf); + + LOG_DBG("%p", buf); + + /* Destroy the buffer first, as the callback may attempt to allocate a + * new one for another operation. + */ + + meta = *p_meta; + + /* Clear the meta storage. This might help catch illegal + * "use-after-free"s. An initial memset is not necessary, as the metadata + * storage array is `static`. + */ + + memset(p_meta, 0x00, sizeof(*p_meta)); + + /* After this point, p_meta doesn't belong to us. The user data will + * be memset to 0 on allocation. + */ + + net_buf_destroy(buf); + + /* ATT opcode 0 is invalid. If we get here, that means the buffer got + * destroyed before it was ready to be sent. Hopefully nobody sets the + * opcode and then destroys the buffer without sending it. :'( + */ + + if (meta.opcode != 0) + { + att_on_sent_cb(&meta); + } + + /* We resubmit this work instead of looping to allow other work on + * the work queue to run. + */ + + if (resubmit) + { + int err = k_work_submit_to_queue(NULL, work); + + if (err < 0) + { + LOG_ERR("Failed to re-submit %s: %d", __func__, err); + PANIC(); + } + } +} + +static void att_tx_destroy(struct net_buf *buf) +{ + int err; + irqstate_t key; + + /* We need to invoke att_on_sent_cb, which may block. We + * don't want to block in a net buf destroy callback, so we defer to a + * sensible workqueue. + * + * bt_workq cannot be used because it currently forms a deadlock with + * att_pool: bt_workq -> bt_att_recv -> send_err_rsp waits for att pool. + * + * We use the system work queue to preserve earlier behavior. The + * tx_processor used to run on the system work queue, and it could end up + * here: tx_processor -> bt_hci_send -> net_buf_unref. + * + * A possible alternative is tx_notify_workqueue_get() since this workqueue + * is processing similar "completion" events. + */ + + key = spin_lock_irqsave(&tx_destroy_queue_lock); + list_add_tail(&tx_destroy_queue, &buf->node); + spin_unlock_irqrestore(&tx_destroy_queue_lock, key); + + err = k_work_submit(&att_tx_destroy_work); + if (err < 0) + { + LOG_ERR("Failed to submit att_tx_destroy_work: %d", err); + PANIC(); + } + + /* Continues in att_tx_destroy_work_handler() */ +} + +NET_BUF_POOL_DEFINE(att_pool, + CONFIG_BT_ATT_TX_COUNT, + BT_L2CAP_SDU_BUF_SIZE(BT_ATT_BUF_SIZE), + CONFIG_BT_CONN_TX_USER_DATA_SIZE, att_tx_destroy); + +static struct bt_att_tx_meta_data *att_get_tx_meta_data( + const struct net_buf *buf) +{ + DEBUGASSERT(buf->pool == &att_pool); + + /* Metadata lifetime is implicitly tied to the buffer lifetime. + * Treat it as part of the buffer itself. + */ + + return &tx_meta_data_storage[net_buf_id((struct net_buf *)buf)]; +} + +static int bt_att_chan_send(struct bt_att_chan *chan, struct net_buf *buf); + +static void att_chan_mtu_updated(struct bt_att_chan *updated_chan); +static void bt_att_disconnected(struct bt_l2cap_chan *chan); + +static struct net_buf *att_create_rsp_pdu(struct bt_att_chan *chan, + uint8_t op); + +static void att_disconnect(struct bt_att_chan *chan) +{ + int err; + + /* In rare circumstances we are "forced" to disconnect the ATT bearer and + * the ACL. Examples of when this is right course of action is when there + * is an ATT timeout, we receive an unexpected response from the server, or + * the response from the server is invalid + */ + + LOG_DBG("ATT disconnecting device %s", bt_conn_dst_str(chan->att->conn)); + + bt_att_disconnected(&chan->chan.chan); + + err = bt_conn_disconnect(chan->chan.chan.conn, + BT_HCI_ERR_REMOTE_USER_TERM_CONN); + if (err) + { + LOG_ERR("Disconnecting failed (err %d)", err); + } +} + +/* In case of success the ownership of the buffer is transferred to the stack + * which takes care of releasing it when it completes transmitting to the + * controller. + * + * In case bt_l2cap_send_cb fails the buffer state and ownership are retained + * so the buffer can be safely pushed back to the queue to be processed + * later. + */ + +static int chan_send(struct bt_att_chan *chan, struct net_buf *buf) +{ + struct bt_att_hdr *hdr; + struct net_buf_simple_state state; + int err; + struct bt_att_tx_meta_data *data = att_get_tx_meta_data(buf); + struct bt_att_chan *prev_chan = data->att_chan; + + hdr = (void *)buf->data; + + LOG_DBG("code 0x%02x", hdr->code); + + if (!atomic_test_bit(chan->flags, ATT_CONNECTED)) + { + LOG_ERR("ATT channel not connected"); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_EATT) && hdr->code == BT_ATT_OP_MTU_REQ && + chan->chan.tx.cid != BT_L2CAP_CID_ATT) + { + /* The Exchange MTU sub-procedure shall only be supported on + * the LE Fixed Channel Unenhanced ATT bearer + */ + + return -ENOTSUP; + } + + DEBUGASSERT(buf->len >= sizeof(struct bt_att_hdr)); + data->opcode = buf->data[0]; + data->err = 0; + + if (IS_ENABLED(CONFIG_BT_EATT) && bt_att_is_enhanced(chan)) + { + /* Check if sent is pending already, if it does it cannot be + * modified so the operation will need to be queued. + */ + + if (atomic_test_bit(chan->flags, ATT_PENDING_SENT)) + { + return -EAGAIN; + } + + if (hdr->code == BT_ATT_OP_SIGNED_WRITE_CMD) + { + return -ENOTSUP; + } + + /* Check if the channel is ready to send in case of a request */ + + if (att_op_get_type(hdr->code) == ATT_REQUEST && + !atomic_test_bit(chan->chan.chan.status, BT_L2CAP_STATUS_OUT)) + { + return -EAGAIN; + } + + atomic_set_bit(chan->flags, ATT_PENDING_SENT); + data->att_chan = chan; + + /* bt_l2cap_chan_send does actually return the number of bytes + * that could be sent immediately. + */ + + err = bt_l2cap_chan_send(&chan->chan.chan, buf); + if (err < 0) + { + data->att_chan = prev_chan; + atomic_clear_bit(chan->flags, ATT_PENDING_SENT); + data->err = err; + + return err; + } + else + { + /* On success, the almighty scheduler might already have + * run the destroy cb on the buffer. In that case, buf and its + * metadata are dangling pointers. + */ + + buf = NULL; + data = NULL; + } + + return 0; + } + + if (hdr->code == BT_ATT_OP_SIGNED_WRITE_CMD) + { + net_buf_simple_save(&buf->b, &state); + + err = bt_smp_sign(chan->att->conn, buf); + if (err != 0) + { + LOG_ERR("Error signing data"); + /* Restore the buffer state, since the callers retain + * ownership of the buffer on failure and may retry sending it + * later. + */ + + net_buf_simple_restore(&buf->b, &state); + return err; + } + + /* Keep the pre-signing state saved above: bt_smp_sign() + * already consumed and persisted a sign counter value, so if the send + * below fails the buffer must be restored to its unsigned form. + * Retrying will then sign it again (consuming the next counter value, + * which is fine since the spec only requires the counter to strictly + * increase). Restoring the already-signed state instead would let a + * retry append a second signature onto a buffer sized for only one. + */ + } + else + { + net_buf_simple_save(&buf->b, &state); + } + + data->att_chan = chan; + + err = bt_l2cap_send_pdu(&chan->chan, buf, NULL, NULL); + if (err) + { + if (err == -ENOBUFS) + { + LOG_ERR("Ran out of TX buffers or contexts."); + } + + /* In case of an error has occurred restore the buffer state */ + + net_buf_simple_restore(&buf->b, &state); + data->att_chan = prev_chan; + data->err = err; + } + + return err; +} + +static bool att_chan_matches_chan_opt(struct bt_att_chan *chan, + enum bt_att_chan_opt chan_opt) +{ + DEBUGASSERT(chan_opt <= BT_ATT_CHAN_OPT_ENHANCED_ONLY); + + if (chan_opt == BT_ATT_CHAN_OPT_NONE) + { + return true; + } + + if (bt_att_is_enhanced(chan)) + { + return (chan_opt & BT_ATT_CHAN_OPT_ENHANCED_ONLY); + } + else + { + return (chan_opt & BT_ATT_CHAN_OPT_UNENHANCED_ONLY); + } +} + +static struct net_buf *get_first_buf_matching_chan(struct k_fifo *fifo, + struct bt_att_chan *chan) +{ + if (IS_ENABLED(CONFIG_BT_EATT)) + { + struct k_fifo skipped; + struct net_buf *buf; + struct net_buf *ret = NULL; + struct bt_att_tx_meta_data *meta; + + k_fifo_init(&skipped); + + while ((buf = k_fifo_get(fifo, 0))) + { + meta = att_get_tx_meta_data(buf); + if (!ret && att_chan_matches_chan_opt(chan, meta->chan_opt)) + { + ret = buf; + } + else + { + k_fifo_put(&skipped, buf); + } + } + + DEBUGASSERT(k_fifo_is_empty(fifo)); + + while ((buf = k_fifo_get(&skipped, 0))) + { + k_fifo_put(fifo, buf); + } + + DEBUGASSERT(k_fifo_is_empty(&skipped)); + + return ret; + } + else + { + return k_fifo_get(fifo, 0); + } +} + +static struct bt_att_req *get_first_req_matching_chan(struct list_node *reqs, + struct bt_att_chan *chan) +{ + if (IS_ENABLED(CONFIG_BT_EATT)) + { + struct list_node *curr; + struct list_node *prev = NULL; + struct bt_att_tx_meta_data *meta = NULL; + + list_for_every(reqs, curr) + { + meta = att_get_tx_meta_data(ATT_REQ(curr)->buf); + if (att_chan_matches_chan_opt(chan, meta->chan_opt)) + { + break; + } + + prev = curr; + } + + if (curr) + { + list_remove(reqs, prev, curr); + + return ATT_REQ(curr); + } + + return NULL; + } + + struct list_node *node = list_remove_head(reqs); + + if (node) + { + return ATT_REQ(node); + } + else + { + return NULL; + } +} + +static int process_queue(struct bt_att_chan *chan, struct k_fifo *queue) +{ + struct net_buf *buf; + int err; + + buf = get_first_buf_matching_chan(queue, chan); + if (buf) + { + err = bt_att_chan_send(chan, buf); + if (err) + { + /* Push it back if it could not be send */ + + k_queue_prepend(queue, buf); + return err; + } + + return 0; + } + + return -ENOENT; +} + +/* Send requests without taking tx_sem */ + +static int bt_att_chan_req_send(struct bt_att_chan *chan, + struct bt_att_req *req) +{ + struct net_buf *buf; + int err; + + if (bt_att_mtu(chan) < net_buf_frags_len(req->buf)) + { + return -EMSGSIZE; + } + + LOG_DBG("chan %p req %p len %zu", chan, req, net_buf_frags_len(req->buf)); + + chan->req = req; + + /* Release since bt_l2cap_send_cb takes ownership of the buffer */ + + buf = net_buf_take(&req->buf); + + /* This lock makes sure the value of `bt_att_mtu(chan)` does not + * change; the scheduler lock keeps the RX-path request handling, which + * does not take the host lock, from preempting a preemptible caller. + */ + + bt_dev_lock(); + sched_lock(); + err = bt_att_chan_send(chan, buf); + if (err) + { + /* We still have the ownership of the buffer */ + + req->buf = buf; + chan->req = NULL; + } + else + { + bt_gatt_req_set_mtu(req, bt_att_mtu(chan)); + } + + sched_unlock(); + bt_dev_unlock(); + + return err; +} + +static void bt_att_sent(struct bt_l2cap_chan *ch) +{ + struct bt_att_chan *chan = ATT_CHAN(ch); + struct bt_att *att = chan->att; + int err; + + LOG_DBG("chan %p", chan); + + atomic_clear_bit(chan->flags, ATT_PENDING_SENT); + + if (!att) + { + LOG_DBG("Ignore sent on detached ATT chan"); + return; + } + + /* Process pending requests first since they require a response they + * can only be processed one at time while if other queues were processed + * before they may always contain a buffer starving the request queue. + */ + + if (!chan->req && !list_is_empty(&att->reqs)) + { + struct list_node *node = list_remove_head(&att->reqs); + + err = bt_att_chan_req_send(chan, ATT_REQ(node)); + if (err == 0) + { + return; + } + + /* Prepend back to the list as it could not be sent */ + + list_add_head(&att->reqs, node); + } + + /* Process channel queue */ + + err = process_queue(chan, &chan->tx_queue); + if (!err) + { + return; + } + + /* Process global queue */ + + (void)process_queue(chan, &att->tx_queue); +} + +static void chan_rebegin_att_timeout(struct bt_att_tx_meta_data *user_data) +{ + struct bt_att_tx_meta_data *data = user_data; + struct bt_att_chan *chan = data->att_chan; + + LOG_DBG("chan %p chan->req %p", chan, chan->req); + + if (!atomic_test_bit(chan->flags, ATT_CONNECTED)) + { + LOG_ERR("ATT channel not connected"); + return; + } + + /* Start timeout work. Only if we are sure that the request is really + * in-flight. + */ + + if (chan->req) + { + bt_work_reschedule(&chan->timeout_work, BT_ATT_TIMEOUT); + } +} + +static void chan_req_notif_sent(struct bt_att_tx_meta_data *user_data) +{ + struct bt_att_tx_meta_data *data = user_data; + struct bt_att_chan *chan = data->att_chan; + struct bt_conn *conn = chan->att->conn; + bt_gatt_complete_func_t func = data->func; + uint16_t attr_count = data->attr_count; + void *ud = data->user_data; + + LOG_DBG("chan %p CID 0x%04X", chan, chan->chan.tx.cid); + + if (!atomic_test_bit(chan->flags, ATT_CONNECTED)) + { + LOG_ERR("ATT channel not connected"); + return; + } + + if (func) + { + for (uint16_t i = 0; i < attr_count; i++) + { + func(conn, ud); + } + } +} + +static void att_on_sent_cb(struct bt_att_tx_meta_data *meta) +{ + const att_type_t op_type = att_op_get_type(meta->opcode); + + LOG_DBG("opcode 0x%x", meta->opcode); + + if (!meta->att_chan || !meta->att_chan->att || !meta->att_chan->att->conn) + { + LOG_DBG("Bearer not connected, dropping ATT cb"); + return; + } + + if (meta->err) + { + LOG_ERR("Got err %d, not calling ATT cb", meta->err); + return; + } + + if (!bt_att_is_enhanced(meta->att_chan)) + { + struct bt_att_chan *att_chan = meta->att_chan; + struct bt_conn *conn = att_chan->att->conn; + struct bt_l2cap_chan *l2cap_chan = &att_chan->chan.chan; + + ARG_UNUSED(conn); + LOG_DBG("UATT bearer, calling bt_att_sent: conn %p chan %p", conn, + l2cap_chan); + + /* For EATT, `bt_att_sent` is assigned to the `.sent` L2 callback. + * L2CAP will then call it once the SDU has finished sending. + * + * For UATT, this won't happen, as static LE l2cap channels don't have + * SDUs. Call it manually instead. + */ + + bt_att_sent(l2cap_chan); + } + + switch (op_type) + { + case ATT_RESPONSE: + return; + case ATT_CONFIRMATION: + return; + case ATT_REQUEST: + case ATT_INDICATION: + chan_rebegin_att_timeout(meta); + return; + case ATT_COMMAND: + case ATT_NOTIFICATION: + chan_req_notif_sent(meta); + return; + default: + DEBUGASSERT(false); + return; + } +} + +static struct net_buf *bt_att_chan_create_pdu(struct bt_att_chan *chan, + uint8_t op, size_t len) +{ + struct bt_att_hdr *hdr; + struct net_buf *buf; + struct bt_att_tx_meta_data *data; + clock_t timeout; + + if (len + sizeof(op) > bt_att_mtu(chan)) + { + LOG_WRN("ATT MTU exceeded, max %u, wanted %zu", bt_att_mtu(chan), + len + sizeof(op)); + return NULL; + } + + switch (att_op_get_type(op)) + { + case ATT_RESPONSE: + case ATT_CONFIRMATION: + + /* Use a timeout only when responding/confirming */ + + timeout = BT_ATT_TIMEOUT; + break; + default: + { + pid_t current_thread = nxsched_gettid(); + + if (current_thread == k_work_queue_thread_get(&k_sys_work_q)) + { + /* No blocking in the sysqueue. */ + + timeout = 0; + } + else if (current_thread == att_handle_rsp_thread) + { + /* Blocking would cause deadlock. */ + + timeout = 0; + } + else + { + timeout = ((clock_t)-1); + } + } + } + + /* This will reserve headspace for lower layers */ + + buf = bt_l2cap_create_pdu_timeout(&att_pool, 0, timeout); + if (!buf) + { + LOG_DBG("Unable to allocate buffer for op 0x%02x", op); + return NULL; + } + + /* If we got a buf from `att_pool`, then the metadata slot at its index + * is officially ours to use. + */ + + data = att_get_tx_meta_data(buf); + + if (IS_ENABLED(CONFIG_BT_EATT)) + { + net_buf_reserve(buf, BT_L2CAP_SDU_BUF_SIZE(0)); + } + + data->att_chan = chan; + + hdr = net_buf_add(buf, sizeof(*hdr)); + hdr->code = op; + + return buf; +} + +static int bt_att_chan_send(struct bt_att_chan *chan, struct net_buf *buf) +{ + LOG_DBG("chan %p flags %lu code 0x%02x", chan, atomic_read(chan->flags), + ((struct bt_att_hdr *)buf->data)->code); + + if (IS_ENABLED(CONFIG_BT_EATT) && + !att_chan_matches_chan_opt(chan, att_get_tx_meta_data(buf)->chan_opt)) + { + return -EINVAL; + } + + return chan_send(chan, buf); +} + +static void att_send_process(struct bt_att *att) +{ + struct bt_att_chan *chan; + struct bt_att_chan *tmp; + struct bt_att_chan *prev = NULL; + int err = 0; + + list_for_every_container_safe(&att->chans, chan, tmp, node) + { + if (err == -ENOENT && prev && + (bt_att_is_enhanced(chan) == bt_att_is_enhanced(prev))) + { + /* If there was nothing to send for the previous channel and the + * current channel has the same "enhancedness", there will be + * nothing + * to send for this channel either. + */ + + continue; + } + + err = process_queue(chan, &att->tx_queue); + if (!err) + { + /* Success */ + + return; + } + + prev = chan; + } +} + +static void bt_att_chan_send_rsp(struct bt_att_chan *chan, + struct net_buf *buf) +{ + int err; + + err = chan_send(chan, buf); + if (err) + { + /* Responses need to be sent back using the same channel */ + + k_fifo_put(&chan->tx_queue, buf); + } +} + +static void send_err_rsp(struct bt_att_chan *chan, + uint8_t req, uint16_t handle, uint8_t err) +{ + struct bt_att_error_rsp *rsp; + struct net_buf *buf; + + /* Ignore opcode 0x00 */ + + if (!req) + { + return; + } + + buf = bt_att_chan_create_pdu(chan, BT_ATT_OP_ERROR_RSP, sizeof(*rsp)); + if (!buf) + { + LOG_ERR("Unable to create err rsp PDU"); + return; + } + + rsp = net_buf_add(buf, sizeof(*rsp)); + rsp->request = req; + rsp->handle = sys_cpu_to_le16(handle); + rsp->error = err; + + bt_att_chan_send_rsp(chan, buf); +} + +static uint8_t att_mtu_req(struct bt_att_chan *chan, struct net_buf *buf) +{ + struct bt_att_exchange_mtu_req *req; + struct bt_att_exchange_mtu_rsp *rsp; + struct net_buf *pdu; + uint16_t mtu_client; + uint16_t mtu_server; + + /* Exchange MTU sub-procedure shall only be supported on the + * LE Fixed Channel Unenhanced ATT bearer. + */ + + if (bt_att_is_enhanced(chan)) + { + return BT_ATT_ERR_NOT_SUPPORTED; + } + + req = (void *)buf->data; + + mtu_client = sys_le16_to_cpu(req->mtu); + + LOG_DBG("Client MTU %u", mtu_client); + + /* Check if MTU is valid */ + + if (mtu_client < BT_ATT_DEFAULT_LE_MTU) + { + return BT_ATT_ERR_INVALID_PDU; + } + + pdu = att_create_rsp_pdu(chan, BT_ATT_OP_MTU_RSP); + if (!pdu) + { + return BT_ATT_ERR_UNLIKELY; + } + + mtu_server = BT_LOCAL_ATT_MTU_UATT; + + LOG_DBG("Server MTU %u", mtu_server); + + rsp = net_buf_add(pdu, sizeof(*rsp)); + rsp->mtu = sys_cpu_to_le16(mtu_server); + + bt_att_chan_send_rsp(chan, pdu); + + /* The ATT_EXCHANGE_MTU_REQ/RSP is just an alternative way of + * communicating the L2CAP MTU. + */ + + chan->chan.rx.mtu = mtu_server; + chan->chan.tx.mtu = mtu_client; + + LOG_DBG("Negotiated MTU %u", bt_att_mtu(chan)); + +#if defined(CONFIG_BT_GATT_CLIENT) + /* Mark the MTU Exchange as complete. + * This will skip sending ATT Exchange MTU from our side. + * + * Core 5.3 | Vol 3, Part F 3.4.2.2: If MTU is exchanged in one direction, + * that is sufficient for both directions. + */ + + atomic_set_bit(chan->att->conn->flags, BT_CONN_ATT_MTU_EXCHANGED); +#endif /* CONFIG_BT_GATT_CLIENT */ + + att_chan_mtu_updated(chan); + + return 0; +} + +static void att_req_send_process(struct bt_att *att) +{ + struct bt_att_req *req = NULL; + struct bt_att_chan *chan; + struct bt_att_chan *tmp; + struct bt_att_chan *prev = NULL; + + list_for_every_container_safe(&att->chans, chan, tmp, node) + { + /* If there is an ongoing transaction, do not use the channel */ + + if (chan->req) + { + continue; + } + + if (!req && prev && + (bt_att_is_enhanced(chan) == bt_att_is_enhanced(prev))) + { + /* If there was nothing to send for the previous channel and the + * current channel has the same "enhancedness", there will be + * nothing + * to send for this channel either. + */ + + continue; + } + + prev = chan; + + /* Pull next request from the list */ + + req = get_first_req_matching_chan(&att->reqs, chan); + if (!req) + { + continue; + } + + if (bt_att_chan_req_send(chan, req) == 0) + { + return; + } + + /* Prepend back to the list as it could not be sent */ + + list_add_head(&att->reqs, &req->node); + } +} + +static uint8_t att_handle_rsp(struct bt_att_chan *chan, + void *pdu, uint16_t len, int err) +{ + bt_att_func_t func = NULL; + void *params; + + LOG_DBG("chan %p err %d len %u: %s", chan, err, len, bt_hex(pdu, len)); + + /* Cancel timeout if ongoing */ + + k_work_cancel_delayable(&chan->timeout_work); + + if (!chan->req) + { + LOG_WRN("No pending ATT request"); + att_disconnect(chan); + + /* Returning a non-0 value would attempt to send an error response */ + + return 0; + } + + /* Check if request has been cancelled */ + + if (chan->req == &cancel) + { + chan->req = NULL; + goto process; + } + + /* Reset func so it can be reused by the callback */ + + func = chan->req->func; + chan->req->func = NULL; + params = chan->req->user_data; + + /* free allocated request so its memory can be reused */ + + bt_att_req_free(chan->req); + chan->req = NULL; + +process: + + /* Process pending requests */ + + att_req_send_process(chan->att); + if (func) + { + func(chan->att->conn, err, pdu, len, params); + } + + return 0; +} + +#if defined(CONFIG_BT_GATT_CLIENT) +static uint8_t att_mtu_rsp(struct bt_att_chan *chan, struct net_buf *buf) +{ + struct bt_att_exchange_mtu_rsp *rsp; + uint16_t mtu; + + rsp = (void *)buf->data; + + mtu = sys_le16_to_cpu(rsp->mtu); + + LOG_DBG("Server MTU %u", mtu); + + /* Check if MTU is valid */ + + if (mtu < BT_ATT_DEFAULT_LE_MTU) + { + return att_handle_rsp(chan, NULL, 0, BT_ATT_ERR_INVALID_PDU); + } + + /* The following must equal the value we sent in the req. We assume this + * is a rsp to `gatt_exchange_mtu_encode`. + */ + + chan->chan.rx.mtu = BT_LOCAL_ATT_MTU_UATT; + /* The ATT_EXCHANGE_MTU_REQ/RSP is just an alternative way of + * communicating the L2CAP MTU. + */ + + chan->chan.tx.mtu = mtu; + + LOG_DBG("Negotiated MTU %u", bt_att_mtu(chan)); + + att_chan_mtu_updated(chan); + + return att_handle_rsp(chan, rsp, buf->len, 0); +} +#endif /* CONFIG_BT_GATT_CLIENT */ + +static bool range_is_valid(uint16_t start, uint16_t end, uint16_t *err) +{ + /* Handle 0 is invalid */ + + if (!start || !end) + { + if (err) + { + *err = 0U; + } + + return false; + } + + /* Check if range is valid */ + + if (start > end) + { + if (err) + { + *err = start; + } + + return false; + } + + return true; +} + +struct find_info_data +{ + struct bt_att_chan *chan; + struct net_buf *buf; + struct bt_att_find_info_rsp *rsp; + union + { + struct bt_att_info_16 *info16; + struct bt_att_info_128 *info128; + }; +}; + +static uint8_t find_info_cb(const struct bt_gatt_attr *attr, + uint16_t handle, void *user_data) +{ + struct find_info_data *data = user_data; + struct bt_att_chan *chan = data->chan; + + LOG_DBG("handle 0x%04x", handle); + + /* Initialize rsp at first entry */ + + if (!data->rsp) + { + data->rsp = net_buf_add(data->buf, sizeof(*data->rsp)); + data->rsp->format = (attr->uuid->type == BT_UUID_TYPE_16) ? + BT_ATT_INFO_16 : BT_ATT_INFO_128; + } + + switch (data->rsp->format) + { + case BT_ATT_INFO_16: + if (attr->uuid->type != BT_UUID_TYPE_16) + { + return BT_GATT_ITER_STOP; + } + + /* Fast forward to next item position */ + + data->info16 = net_buf_add(data->buf, sizeof(*data->info16)); + data->info16->handle = sys_cpu_to_le16(handle); + data->info16->uuid = sys_cpu_to_le16(BT_UUID_16(attr->uuid)->val); + + if (bt_att_mtu(chan) - data->buf->len > sizeof(*data->info16)) + { + return BT_GATT_ITER_CONTINUE; + } + + break; + case BT_ATT_INFO_128: + if (attr->uuid->type != BT_UUID_TYPE_128) + { + return BT_GATT_ITER_STOP; + } + + /* Fast forward to next item position */ + + data->info128 = net_buf_add(data->buf, sizeof(*data->info128)); + data->info128->handle = sys_cpu_to_le16(handle); + memcpy(data->info128->uuid, BT_UUID_128(attr->uuid)->val, + sizeof(data->info128->uuid)); + + if (bt_att_mtu(chan) - data->buf->len > sizeof(*data->info128)) + { + return BT_GATT_ITER_CONTINUE; + } + } + + return BT_GATT_ITER_STOP; +} + +static uint8_t att_find_info_rsp( + struct bt_att_chan *chan, uint16_t start_handle, uint16_t end_handle) +{ + struct find_info_data data; + + (void)memset(&data, 0, sizeof(data)); + + data.buf = att_create_rsp_pdu(chan, BT_ATT_OP_FIND_INFO_RSP); + if (!data.buf) + { + return BT_ATT_ERR_INSUFFICIENT_RESOURCES; + } + + data.chan = chan; + bt_gatt_foreach_attr(start_handle, end_handle, find_info_cb, &data); + + if (!data.rsp) + { + net_buf_unref(data.buf); + + /* Respond since handle is set */ + + send_err_rsp(chan, BT_ATT_OP_FIND_INFO_REQ, start_handle, + BT_ATT_ERR_ATTRIBUTE_NOT_FOUND); + return 0; + } + + bt_att_chan_send_rsp(chan, data.buf); + + return 0; +} + +static uint8_t att_find_info_req(struct bt_att_chan *chan, + struct net_buf *buf) +{ + struct bt_att_find_info_req *req; + uint16_t start_handle; + uint16_t end_handle; + uint16_t err_handle; + + req = (void *)buf->data; + + start_handle = sys_le16_to_cpu(req->start_handle); + end_handle = sys_le16_to_cpu(req->end_handle); + + LOG_DBG("start_handle 0x%04x end_handle 0x%04x", start_handle, end_handle); + + if (!range_is_valid(start_handle, end_handle, &err_handle)) + { + send_err_rsp(chan, BT_ATT_OP_FIND_INFO_REQ, err_handle, + BT_ATT_ERR_INVALID_HANDLE); + return 0; + } + + return att_find_info_rsp(chan, start_handle, end_handle); +} + +struct find_type_data +{ + struct bt_att_chan *chan; + struct net_buf *buf; + struct bt_att_handle_group *group; + const void *value; + uint8_t value_len; + uint8_t err; +}; + +static uint8_t find_type_cb(const struct bt_gatt_attr *attr, + uint16_t handle, void *user_data) +{ + struct find_type_data *data = user_data; + struct bt_att_chan *chan = data->chan; + struct bt_conn *conn = chan->chan.chan.conn; + int read; + uint8_t uuid[16]; + struct net_buf *frag; + size_t len; + + /* Skip secondary services */ + + if (!bt_uuid_cmp(attr->uuid, BT_UUID_GATT_SECONDARY)) + { + goto skip; + } + + /* Update group end_handle if not a primary service */ + + if (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_PRIMARY)) + { + if (data->group && handle > sys_le16_to_cpu(data->group->end_handle)) + { + data->group->end_handle = sys_cpu_to_le16(handle); + } + + return BT_GATT_ITER_CONTINUE; + } + + LOG_DBG("handle 0x%04x", handle); + + /* stop if there is no space left */ + + if (bt_att_mtu(chan) - net_buf_frags_len(data->buf) < sizeof(*data->group)) + { + return BT_GATT_ITER_STOP; + } + + frag = net_buf_frag_last(data->buf); + + len = MIN(bt_att_mtu(chan) - net_buf_frags_len(data->buf), + net_buf_tailroom(frag)); + if (!len) + { + frag = net_buf_alloc(data->buf->pool, 0); + + /* If not buffer can be allocated immediately stop */ + + if (!frag) + { + return BT_GATT_ITER_STOP; + } + + net_buf_frag_add(data->buf, frag); + } + + /* Read attribute value and store in the buffer */ + + read = attr->read(conn, attr, uuid, sizeof(uuid), 0); + if (read < 0) + { + /* Since we don't know if it is the service with requested UUID, + * we cannot respond with an error to this request. + */ + + goto skip; + } + + /* Check if data matches */ + + if (read != data->value_len) + { + /* Use bt_uuid_cmp() to compare UUIDs of different form. */ + + struct bt_uuid_128 ref_uuid; + struct bt_uuid_128 recvd_uuid; + + if (!bt_uuid_create(&recvd_uuid.uuid, data->value, data->value_len)) + { + LOG_WRN("Unable to create UUID: size %u", data->value_len); + goto skip; + } + + if (!bt_uuid_create(&ref_uuid.uuid, uuid, read)) + { + LOG_WRN("Unable to create UUID: size %d", read); + goto skip; + } + + if (bt_uuid_cmp(&recvd_uuid.uuid, &ref_uuid.uuid)) + { + goto skip; + } + } + else if (memcmp(data->value, uuid, read)) + { + goto skip; + } + + /* If service has been found, error should be cleared */ + + data->err = 0x00; + + /* Fast forward to next item position */ + + data->group = net_buf_add(frag, sizeof(*data->group)); + data->group->start_handle = sys_cpu_to_le16(handle); + data->group->end_handle = sys_cpu_to_le16(handle); + + /* continue to find the end_handle */ + + return BT_GATT_ITER_CONTINUE; + +skip: + data->group = NULL; + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t att_find_type_rsp( + struct bt_att_chan *chan, uint16_t start_handle, uint16_t end_handle, + const void *value, uint8_t value_len) +{ + struct find_type_data data; + + (void)memset(&data, 0, sizeof(data)); + + data.buf = att_create_rsp_pdu(chan, BT_ATT_OP_FIND_TYPE_RSP); + if (!data.buf) + { + return BT_ATT_ERR_INSUFFICIENT_RESOURCES; + } + + data.chan = chan; + data.group = NULL; + data.value = value; + data.value_len = value_len; + + /* Pre-set error in case no service will be found */ + + data.err = BT_ATT_ERR_ATTRIBUTE_NOT_FOUND; + + bt_gatt_foreach_attr(start_handle, end_handle, find_type_cb, &data); + + /* If error has not been cleared, no service has been found */ + + if (data.err) + { + net_buf_unref(data.buf); + + /* Respond since handle is set */ + + send_err_rsp(chan, BT_ATT_OP_FIND_TYPE_REQ, start_handle, data.err); + return 0; + } + + bt_att_chan_send_rsp(chan, data.buf); + + return 0; +} + +static uint8_t att_find_type_req(struct bt_att_chan *chan, + struct net_buf *buf) +{ + struct bt_att_find_type_req *req; + uint16_t start_handle; + uint16_t end_handle; + uint16_t err_handle; + uint16_t type; + uint8_t *value; + + req = net_buf_pull_mem(buf, sizeof(*req)); + + start_handle = sys_le16_to_cpu(req->start_handle); + end_handle = sys_le16_to_cpu(req->end_handle); + type = sys_le16_to_cpu(req->type); + value = buf->data; + + LOG_DBG("start_handle 0x%04x end_handle 0x%04x type %u", start_handle, + end_handle, type); + + if (!range_is_valid(start_handle, end_handle, &err_handle)) + { + send_err_rsp(chan, BT_ATT_OP_FIND_TYPE_REQ, err_handle, + BT_ATT_ERR_INVALID_HANDLE); + return 0; + } + + /* The Attribute Protocol Find By Type Value Request shall be used with + * the Attribute Type parameter set to the UUID for "Primary Service" and + * the Attribute Value set to the 16-bit Bluetooth UUID or 128-bit UUID for + * the specific primary service. + */ + + if (bt_uuid_cmp(BT_UUID_DECLARE_16(type), BT_UUID_GATT_PRIMARY)) + { + send_err_rsp(chan, BT_ATT_OP_FIND_TYPE_REQ, start_handle, + BT_ATT_ERR_ATTRIBUTE_NOT_FOUND); + return 0; + } + + return att_find_type_rsp(chan, start_handle, end_handle, value, buf->len); +} + +static uint8_t err_to_att(int err) +{ + LOG_DBG("%d", err); + + if (err < 0 && err >= -0xff) + { + return -err; + } + + return BT_ATT_ERR_UNLIKELY; +} + +struct read_type_data +{ + struct bt_att_chan *chan; + struct bt_uuid *uuid; + struct net_buf *buf; + struct bt_att_read_type_rsp *rsp; + struct bt_att_data *item; + uint8_t err; +}; + +typedef bool (*attr_read_cb)(struct net_buf *buf, + ssize_t read, void *user_data); + +static bool attr_read_type_cb(struct net_buf *frag, + ssize_t read, void *user_data) +{ + struct read_type_data *data = user_data; + + if (!data->rsp->len) + { + /* Set len to be the first item found */ + + data->rsp->len = read + sizeof(*data->item); + } + else if (data->rsp->len != read + sizeof(*data->item)) + { + /* All items should have the same size */ + + frag->len -= sizeof(*data->item); + data->item = NULL; + return false; + } + + return true; +} + +static ssize_t att_chan_read( + struct bt_att_chan *chan, const struct bt_gatt_attr *attr, + struct net_buf *buf, uint16_t offset, attr_read_cb cb, void *user_data) +{ + struct bt_conn *conn = chan->chan.chan.conn; + ssize_t read; + struct net_buf *frag; + size_t len; + size_t total = 0; + + if (bt_att_mtu(chan) <= net_buf_frags_len(buf)) + { + return 0; + } + + frag = net_buf_frag_last(buf); + + /* Create necessary fragments if MTU is bigger than what a buffer can + * hold. + */ + + do + { + len = MIN(bt_att_mtu(chan) - net_buf_frags_len(buf), + net_buf_tailroom(frag)); + if (!len) + { + frag = net_buf_alloc(buf->pool, 0); + + /* If not buffer can be allocated immediately return */ + + if (!frag) + { + return total; + } + + net_buf_frag_add(buf, frag); + + len = MIN(bt_att_mtu(chan) - net_buf_frags_len(buf), + net_buf_tailroom(frag)); + } + + read = attr->read(conn, attr, frag->data + frag->len, len, offset); + if (read < 0) + { + if (total) + { + return total; + } + + return read; + } + + if (cb && !cb(frag, read, user_data)) + { + break; + } + + net_buf_add(frag, read); + total += read; + offset += read; + } + while (bt_att_mtu(chan) > net_buf_frags_len(buf) && read == len); + + return total; +} + +static uint8_t read_type_cb(const struct bt_gatt_attr *attr, + uint16_t handle, void *user_data) +{ + struct read_type_data *data = user_data; + struct bt_att_chan *chan = data->chan; + struct bt_conn *conn = chan->chan.chan.conn; + ssize_t read; + + /* Skip if doesn't match */ + + if (bt_uuid_cmp(attr->uuid, data->uuid)) + { + return BT_GATT_ITER_CONTINUE; + } + + LOG_DBG("handle 0x%04x", handle); + + /* If an attribute in the set of requested attributes would cause an + * Error Response then this attribute cannot be included in a Read By Type + * Response and the attributes before this attribute shall be returned + * + * If the first attribute in the set of requested attributes would cause an + * Error Response then no other attributes in the requested attributes can + * be considered. + */ + + data->err = bt_gatt_check_perm(conn, attr, BT_GATT_PERM_READ_MASK); + if (data->err) + { + if (data->rsp->len) + { + data->err = 0x00; + } + + return BT_GATT_ITER_STOP; + } + + /* Check the attribute authorization logic */ + + if (!bt_gatt_attr_read_authorize(conn, attr)) + { + data->err = BT_ATT_ERR_AUTHORIZATION; + return BT_GATT_ITER_STOP; + } + + /* If any attribute is founded in handle range it means that error + * should be changed from pre-set: attr not found error to no error. + */ + + data->err = 0x00; + + /* Fast forward to next item position */ + + data->item = net_buf_add(net_buf_frag_last(data->buf), + sizeof(*data->item)); + data->item->handle = sys_cpu_to_le16(handle); + + read = att_chan_read(chan, attr, data->buf, 0, attr_read_type_cb, data); + if (read < 0) + { + data->err = err_to_att(read); + return BT_GATT_ITER_STOP; + } + + if (!data->item) + { + return BT_GATT_ITER_STOP; + } + + /* continue only if there are still space for more items */ + + return bt_att_mtu(chan) - + net_buf_frags_len(data->buf) > data->rsp->len ? + BT_GATT_ITER_CONTINUE + : BT_GATT_ITER_STOP; +} + +static uint8_t att_read_type_rsp( + struct bt_att_chan *chan, struct bt_uuid *uuid, uint16_t start_handle, + uint16_t end_handle) +{ + struct read_type_data data; + + (void)memset(&data, 0, sizeof(data)); + + data.buf = att_create_rsp_pdu(chan, BT_ATT_OP_READ_TYPE_RSP); + if (!data.buf) + { + return BT_ATT_ERR_INSUFFICIENT_RESOURCES; + } + + data.chan = chan; + data.uuid = uuid; + data.rsp = net_buf_add(data.buf, sizeof(*data.rsp)); + data.rsp->len = 0U; + + /* Pre-set error if no attr will be found in handle */ + + data.err = BT_ATT_ERR_ATTRIBUTE_NOT_FOUND; + + bt_gatt_foreach_attr(start_handle, end_handle, read_type_cb, &data); + + if (data.err) + { + net_buf_unref(data.buf); + + /* Response here since handle is set */ + + send_err_rsp(chan, BT_ATT_OP_READ_TYPE_REQ, start_handle, data.err); + return 0; + } + + bt_att_chan_send_rsp(chan, data.buf); + + return 0; +} + +static uint8_t att_read_type_req(struct bt_att_chan *chan, + struct net_buf *buf) +{ + struct bt_att_read_type_req *req; + uint16_t start_handle; + uint16_t end_handle; + uint16_t err_handle; + union + { + struct bt_uuid uuid; + struct bt_uuid_16 u16; + struct bt_uuid_128 u128; + } u; + + uint8_t uuid_len = buf->len - sizeof(*req); + + /* Type can only be UUID16 or UUID128 */ + + if (uuid_len != 2 && uuid_len != 16) + { + return BT_ATT_ERR_INVALID_PDU; + } + + req = net_buf_pull_mem(buf, sizeof(*req)); + + start_handle = sys_le16_to_cpu(req->start_handle); + end_handle = sys_le16_to_cpu(req->end_handle); + if (!bt_uuid_create(&u.uuid, req->uuid, uuid_len)) + { + return BT_ATT_ERR_UNLIKELY; + } + + LOG_DBG("start_handle 0x%04x end_handle 0x%04x type %s", start_handle, + end_handle, + bt_uuid_str(&u.uuid)); + + if (!range_is_valid(start_handle, end_handle, &err_handle)) + { + send_err_rsp(chan, BT_ATT_OP_READ_TYPE_REQ, err_handle, + BT_ATT_ERR_INVALID_HANDLE); + return 0; + } + + /* If a client that has indicated support for robust caching (by setting + * the Robust Caching bit in the Client Supported Features characteristic) + * is change-unaware then the server shall send an ATT_ERROR_RSP PDU with + * the Error Code parameter set to Database Out Of Sync (0x12) when either + * of the following happen: • That client requests an operation at any + * Attribute Handle or list of Attribute Handles by sending an ATT request. + * • That client sends an ATT_READ_BY_TYPE_REQ PDU with Attribute Type + * other than Ā«IncludeĀ» or Ā«CharacteristicĀ» and an Attribute Handle + * range other than 0x0001 to 0xFFFF. (Core Specification 5.4 Vol 3. + * Part G. 2.5.2.1 Robust Caching). + */ + + if (!bt_gatt_change_aware(chan->chan.chan.conn, true)) + { + if (bt_uuid_cmp(&u.uuid, BT_UUID_GATT_INCLUDE) != 0 && + bt_uuid_cmp(&u.uuid, BT_UUID_GATT_CHRC) != 0 && + (start_handle != BT_ATT_FIRST_ATTRIBUTE_HANDLE || + end_handle != BT_ATT_LAST_ATTRIBUTE_HANDLE)) + { + if (!atomic_test_and_set_bit(chan->flags, ATT_OUT_OF_SYNC_SENT)) + { + return BT_ATT_ERR_DB_OUT_OF_SYNC; + } + else + { + return 0; + } + } + } + + return att_read_type_rsp(chan, &u.uuid, start_handle, end_handle); +} + +struct read_data +{ + struct bt_att_chan *chan; + uint16_t offset; + struct net_buf *buf; + uint8_t err; +}; + +static uint8_t read_cb(const struct bt_gatt_attr *attr, + uint16_t handle, void *user_data) +{ + struct read_data *data = user_data; + struct bt_att_chan *chan = data->chan; + struct bt_conn *conn = chan->chan.chan.conn; + int ret; + + LOG_DBG("handle 0x%04x", handle); + + /* If any attribute is founded in handle range it means that error + * should be changed from pre-set: invalid handle error to no error. + */ + + data->err = 0x00; + + /* Check attribute permissions */ + + data->err = bt_gatt_check_perm(conn, attr, BT_GATT_PERM_READ_MASK); + if (data->err) + { + return BT_GATT_ITER_STOP; + } + + /* Check the attribute authorization logic */ + + if (!bt_gatt_attr_read_authorize(conn, attr)) + { + data->err = BT_ATT_ERR_AUTHORIZATION; + return BT_GATT_ITER_STOP; + } + + /* Read attribute value and store in the buffer */ + + ret = att_chan_read(chan, attr, data->buf, data->offset, NULL, NULL); + if (ret < 0) + { + data->err = err_to_att(ret); + return BT_GATT_ITER_STOP; + } + + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t att_read_rsp( + struct bt_att_chan *chan, uint8_t op, uint8_t rsp, uint16_t handle, + uint16_t offset) +{ + struct read_data data; + + if (!bt_gatt_change_aware(chan->att->conn, true)) + { + if (!atomic_test_and_set_bit(chan->flags, ATT_OUT_OF_SYNC_SENT)) + { + return BT_ATT_ERR_DB_OUT_OF_SYNC; + } + else + { + return 0; + } + } + + if (!handle) + { + return BT_ATT_ERR_INVALID_HANDLE; + } + + (void)memset(&data, 0, sizeof(data)); + + data.buf = att_create_rsp_pdu(chan, rsp); + if (!data.buf) + { + return BT_ATT_ERR_INSUFFICIENT_RESOURCES; + } + + data.chan = chan; + data.offset = offset; + + /* Pre-set error if no attr will be found in handle */ + + data.err = BT_ATT_ERR_INVALID_HANDLE; + + bt_gatt_foreach_attr(handle, handle, read_cb, &data); + + /* In case of error discard data and respond with an error */ + + if (data.err) + { + net_buf_unref(data.buf); + + /* Respond here since handle is set */ + + send_err_rsp(chan, op, handle, data.err); + return 0; + } + + bt_att_chan_send_rsp(chan, data.buf); + + return 0; +} + +static uint8_t att_read_req(struct bt_att_chan *chan, struct net_buf *buf) +{ + struct bt_att_read_req *req; + uint16_t handle; + + req = (void *)buf->data; + + handle = sys_le16_to_cpu(req->handle); + + LOG_DBG("handle 0x%04x", handle); + + return att_read_rsp(chan, BT_ATT_OP_READ_REQ, BT_ATT_OP_READ_RSP, handle, + 0); +} + +static uint8_t att_read_blob_req(struct bt_att_chan *chan, + struct net_buf *buf) +{ + struct bt_att_read_blob_req *req; + uint16_t handle; + uint16_t offset; + + req = (void *)buf->data; + + handle = sys_le16_to_cpu(req->handle); + offset = sys_le16_to_cpu(req->offset); + + LOG_DBG("handle 0x%04x offset %u", handle, offset); + + return att_read_rsp(chan, BT_ATT_OP_READ_BLOB_REQ, BT_ATT_OP_READ_BLOB_RSP, + handle, offset); +} + +#if defined(CONFIG_BT_GATT_READ_MULTIPLE) +static uint8_t att_read_mult_req(struct bt_att_chan *chan, + struct net_buf *buf) +{ + struct read_data data; + uint16_t handle; + + if (!bt_gatt_change_aware(chan->att->conn, true)) + { + if (!atomic_test_and_set_bit(chan->flags, ATT_OUT_OF_SYNC_SENT)) + { + return BT_ATT_ERR_DB_OUT_OF_SYNC; + } + else + { + return 0; + } + } + + (void)memset(&data, 0, sizeof(data)); + + data.buf = att_create_rsp_pdu(chan, BT_ATT_OP_READ_MULT_RSP); + if (!data.buf) + { + return BT_ATT_ERR_INSUFFICIENT_RESOURCES; + } + + data.chan = chan; + + while (buf->len >= sizeof(uint16_t)) + { + handle = net_buf_pull_le16(buf); + + LOG_DBG("handle 0x%04x ", handle); + + /* An Error Response shall be sent by the server in response to + * the Read Multiple Request [....] if a read operation is not + * permitted on any of the Characteristic Values. + * + * If handle is not valid then return invalid handle error. If handle + * is found error will be cleared by read_cb. + */ + + data.err = BT_ATT_ERR_INVALID_HANDLE; + + bt_gatt_foreach_attr(handle, handle, read_cb, &data); + + /* Stop reading in case of error */ + + if (data.err) + { + net_buf_unref(data.buf); + + /* Respond here since handle is set */ + + send_err_rsp(chan, BT_ATT_OP_READ_MULT_REQ, handle, data.err); + return 0; + } + } + + bt_att_chan_send_rsp(chan, data.buf); + + return 0; +} +#endif /* CONFIG_BT_GATT_READ_MULTIPLE */ + +#if defined(CONFIG_BT_GATT_READ_MULT_VAR_LEN) +static uint8_t read_vl_cb(const struct bt_gatt_attr *attr, + uint16_t handle, void *user_data) +{ + struct read_data *data = user_data; + struct bt_att_chan *chan = data->chan; + struct bt_conn *conn = chan->chan.chan.conn; + struct bt_att_read_mult_vl_rsp *rsp; + int read; + + LOG_DBG("handle 0x%04x", handle); + + /* If any attribute is founded in handle range it means that error + * should be changed from pre-set: invalid handle error to no error. + */ + + data->err = 0x00; + + /* Check attribute permissions */ + + data->err = bt_gatt_check_perm(conn, attr, BT_GATT_PERM_READ_MASK); + if (data->err) + { + return BT_GATT_ITER_STOP; + } + + /* Check the attribute authorization logic */ + + if (!bt_gatt_attr_read_authorize(conn, attr)) + { + data->err = BT_ATT_ERR_AUTHORIZATION; + return BT_GATT_ITER_STOP; + } + + /* The Length Value Tuple List may be truncated within the first two + * octets of a tuple due to the size limits of the current ATT_MTU. + */ + + if (bt_att_mtu(chan) - data->buf->len < 2) + { + return BT_GATT_ITER_STOP; + } + + rsp = net_buf_add(data->buf, sizeof(*rsp)); + + read = att_chan_read(chan, attr, data->buf, data->offset, NULL, NULL); + if (read < 0) + { + data->err = err_to_att(read); + return BT_GATT_ITER_STOP; + } + + rsp->len = read; + + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t att_read_mult_vl_req(struct bt_att_chan *chan, + struct net_buf *buf) +{ + struct read_data data; + uint16_t handle; + + if (!bt_gatt_change_aware(chan->att->conn, true)) + { + if (!atomic_test_and_set_bit(chan->flags, ATT_OUT_OF_SYNC_SENT)) + { + return BT_ATT_ERR_DB_OUT_OF_SYNC; + } + else + { + return 0; + } + } + + (void)memset(&data, 0, sizeof(data)); + + data.buf = att_create_rsp_pdu(chan, BT_ATT_OP_READ_MULT_VL_RSP); + if (!data.buf) + { + return BT_ATT_ERR_INSUFFICIENT_RESOURCES; + } + + data.chan = chan; + + while (buf->len >= sizeof(uint16_t)) + { + handle = net_buf_pull_le16(buf); + + LOG_DBG("handle 0x%04x ", handle); + + /* If handle is not valid then return invalid handle error. + * If handle is found error will be cleared by read_cb. + */ + + data.err = BT_ATT_ERR_INVALID_HANDLE; + + bt_gatt_foreach_attr(handle, handle, read_vl_cb, &data); + + /* Stop reading in case of error */ + + if (data.err) + { + net_buf_unref(data.buf); + + /* Respond here since handle is set */ + + send_err_rsp(chan, BT_ATT_OP_READ_MULT_VL_REQ, handle, data.err); + return 0; + } + } + + bt_att_chan_send_rsp(chan, data.buf); + + return 0; +} +#endif /* CONFIG_BT_GATT_READ_MULT_VAR_LEN */ + +struct read_group_data +{ + struct bt_att_chan *chan; + struct bt_uuid *uuid; + struct net_buf *buf; + struct bt_att_read_group_rsp *rsp; + struct bt_att_group_data *group; +}; + +static bool attr_read_group_cb(struct net_buf *frag, + ssize_t read, void *user_data) +{ + struct read_group_data *data = user_data; + + if (!data->rsp->len) + { + /* Set len to be the first group found */ + + data->rsp->len = read + sizeof(*data->group); + } + else if (data->rsp->len != read + sizeof(*data->group)) + { + /* All groups entries should have the same size */ + + data->buf->len -= sizeof(*data->group); + data->group = NULL; + return false; + } + + return true; +} + +static uint8_t read_group_cb(const struct bt_gatt_attr *attr, + uint16_t handle, void *user_data) +{ + struct read_group_data *data = user_data; + struct bt_att_chan *chan = data->chan; + int read; + + /* Update group end_handle if attribute is not a service */ + + if (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_PRIMARY) && + bt_uuid_cmp(attr->uuid, BT_UUID_GATT_SECONDARY)) + { + if (data->group && handle > sys_le16_to_cpu(data->group->end_handle)) + { + data->group->end_handle = sys_cpu_to_le16(handle); + } + + return BT_GATT_ITER_CONTINUE; + } + + /* If Group Type don't match skip */ + + if (bt_uuid_cmp(attr->uuid, data->uuid)) + { + data->group = NULL; + return BT_GATT_ITER_CONTINUE; + } + + LOG_DBG("handle 0x%04x", handle); + + /* Stop if there is no space left */ + + if (data->rsp->len && bt_att_mtu(chan) - data->buf->len < data->rsp->len) + { + return BT_GATT_ITER_STOP; + } + + /* Fast forward to next group position */ + + data->group = net_buf_add(data->buf, sizeof(*data->group)); + + /* Initialize group handle range */ + + data->group->start_handle = sys_cpu_to_le16(handle); + data->group->end_handle = sys_cpu_to_le16(handle); + + /* Read attribute value and store in the buffer */ + + read = att_chan_read(chan, attr, data->buf, 0, attr_read_group_cb, data); + if (read < 0) + { + /* TODO: Handle read errors */ + + return BT_GATT_ITER_STOP; + } + + if (!data->group) + { + return BT_GATT_ITER_STOP; + } + + /* continue only if there are still space for more items */ + + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t att_read_group_rsp( + struct bt_att_chan *chan, struct bt_uuid *uuid, uint16_t start_handle, + uint16_t end_handle) +{ + struct read_group_data data; + + (void)memset(&data, 0, sizeof(data)); + + data.buf = att_create_rsp_pdu(chan, BT_ATT_OP_READ_GROUP_RSP); + if (!data.buf) + { + return BT_ATT_ERR_INSUFFICIENT_RESOURCES; + } + + data.chan = chan; + data.uuid = uuid; + data.rsp = net_buf_add(data.buf, sizeof(*data.rsp)); + data.rsp->len = 0U; + data.group = NULL; + + bt_gatt_foreach_attr(start_handle, end_handle, read_group_cb, &data); + + if (!data.rsp->len) + { + net_buf_unref(data.buf); + + /* Respond here since handle is set */ + + send_err_rsp(chan, BT_ATT_OP_READ_GROUP_REQ, start_handle, + BT_ATT_ERR_ATTRIBUTE_NOT_FOUND); + return 0; + } + + bt_att_chan_send_rsp(chan, data.buf); + + return 0; +} + +static uint8_t att_read_group_req(struct bt_att_chan *chan, + struct net_buf *buf) +{ + struct bt_att_read_group_req *req; + uint16_t start_handle; + uint16_t end_handle; + uint16_t err_handle; + union + { + struct bt_uuid uuid; + struct bt_uuid_16 u16; + struct bt_uuid_128 u128; + } u; + + uint8_t uuid_len = buf->len - sizeof(*req); + + /* Type can only be UUID16 or UUID128 */ + + if (uuid_len != 2 && uuid_len != 16) + { + return BT_ATT_ERR_INVALID_PDU; + } + + req = net_buf_pull_mem(buf, sizeof(*req)); + + start_handle = sys_le16_to_cpu(req->start_handle); + end_handle = sys_le16_to_cpu(req->end_handle); + + if (!bt_uuid_create(&u.uuid, req->uuid, uuid_len)) + { + return BT_ATT_ERR_UNLIKELY; + } + + LOG_DBG("start_handle 0x%04x end_handle 0x%04x type %s", start_handle, + end_handle, + bt_uuid_str(&u.uuid)); + + if (!range_is_valid(start_handle, end_handle, &err_handle)) + { + send_err_rsp(chan, BT_ATT_OP_READ_GROUP_REQ, err_handle, + BT_ATT_ERR_INVALID_HANDLE); + return 0; + } + + /* Core v4.2, Vol 3, sec 2.5.3 Attribute Grouping: + * Not all of the grouping attributes can be used in the ATT Read By Group + * Type Request. The "Primary Service" and "Secondary Service" grouping + * types may be used in the Read By Group Type Request. The + * "Characteristic" grouping type shall not be used in the ATT Read By + * Group Type Request. + */ + + if (bt_uuid_cmp(&u.uuid, BT_UUID_GATT_PRIMARY) && + bt_uuid_cmp(&u.uuid, BT_UUID_GATT_SECONDARY)) + { + send_err_rsp(chan, BT_ATT_OP_READ_GROUP_REQ, start_handle, + BT_ATT_ERR_UNSUPPORTED_GROUP_TYPE); + return 0; + } + + return att_read_group_rsp(chan, &u.uuid, start_handle, end_handle); +} + +struct write_data +{ + struct bt_conn *conn; + uint8_t op; + const void *value; + uint16_t len; + uint16_t offset; + uint8_t err; +}; + +static uint8_t write_cb(const struct bt_gatt_attr *attr, + uint16_t handle, void *user_data) +{ + struct write_data *data = user_data; + int write; + uint8_t flags = 0U; + + LOG_DBG("handle 0x%04x offset %u", handle, data->offset); + + /* Check attribute permissions */ + + data->err = bt_gatt_check_perm(data->conn, attr, BT_GATT_PERM_WRITE_MASK); + if (data->err) + { + return BT_GATT_ITER_STOP; + } + + /* Check the attribute authorization logic */ + + if (!bt_gatt_attr_write_authorize(data->conn, attr)) + { + data->err = BT_ATT_ERR_AUTHORIZATION; + return BT_GATT_ITER_STOP; + } + + /* Set command flag if not a request */ + + if (data->op == BT_ATT_OP_WRITE_CMD || + data->op == BT_ATT_OP_SIGNED_WRITE_CMD) + { + flags |= BT_GATT_WRITE_FLAG_CMD; + } + else if (data->op == BT_ATT_OP_EXEC_WRITE_REQ) + { + flags |= BT_GATT_WRITE_FLAG_EXECUTE; + } + else + { + DEBUGASSERT(data->op == BT_ATT_OP_WRITE_REQ); + } + + /* Write attribute value */ + + write = attr->write(data->conn, attr, data->value, data->len, data->offset, + flags); + if (write < 0 || write != data->len) + { + data->err = err_to_att(write); + return BT_GATT_ITER_STOP; + } + + data->err = 0U; + + return BT_GATT_ITER_CONTINUE; +} + +static bool is_gatt_request(uint8_t op) +{ + switch (op) + { + case BT_ATT_OP_WRITE_CMD: + case BT_ATT_OP_SIGNED_WRITE_CMD: + return false; + default: + return true; + } +} + +static uint8_t perform_write( + struct bt_att_chan *chan, uint8_t op, uint16_t handle, uint16_t offset, + const void *value, uint16_t len) +{ + struct write_data data; + + if (!bt_gatt_change_aware(chan->att->conn, is_gatt_request(op))) + { + if (!atomic_test_and_set_bit(chan->flags, ATT_OUT_OF_SYNC_SENT)) + { + return BT_ATT_ERR_DB_OUT_OF_SYNC; + } + else + { + return 0; + } + } + + if (!handle) + { + return BT_ATT_ERR_INVALID_HANDLE; + } + + (void)memset(&data, 0, sizeof(data)); + + data.conn = chan->att->conn; + data.op = op; + data.offset = offset; + data.value = value; + data.len = len; + data.err = BT_ATT_ERR_INVALID_HANDLE; + + bt_gatt_foreach_attr(handle, handle, write_cb, &data); + + return data.err; +} + +static uint8_t att_write_req(struct bt_att_chan *chan, struct net_buf *buf) +{ + uint16_t handle; + uint8_t err; + + handle = net_buf_pull_le16(buf); + + LOG_DBG("handle 0x%04x", handle); + + err = perform_write(chan, BT_ATT_OP_WRITE_REQ, handle, 0, buf->data, + buf->len); + if (err == 0) + { + buf = bt_att_chan_create_pdu(chan, BT_ATT_OP_WRITE_RSP, 0); + if (buf == NULL) + { + LOG_ERR("Unable to create rsp PDU"); + return BT_ATT_ERR_INSUFFICIENT_RESOURCES; + } + + bt_att_chan_send_rsp(chan, buf); + } + else + { + send_err_rsp(chan, BT_ATT_OP_WRITE_REQ, handle, err); + } + + return 0; +} + +#if CONFIG_BT_ATT_PREPARE_COUNT > 0 +struct prep_data +{ + struct bt_conn *conn; + struct net_buf *buf; + const void *value; + uint16_t len; + uint16_t offset; + uint8_t err; +}; + +static uint8_t prep_write_cb(const struct bt_gatt_attr *attr, + uint16_t handle, void *user_data) +{ + struct prep_data *data = user_data; + struct bt_attr_data *attr_data; + int write; + + LOG_DBG("handle 0x%04x offset %u", handle, data->offset); + + /* Check attribute permissions */ + + data->err = bt_gatt_check_perm(data->conn, attr, BT_GATT_PERM_WRITE_MASK); + if (data->err) + { + return BT_GATT_ITER_STOP; + } + + /* Check the attribute authorization logic */ + + if (!bt_gatt_attr_write_authorize(data->conn, attr)) + { + data->err = BT_ATT_ERR_AUTHORIZATION; + return BT_GATT_ITER_STOP; + } + + /* Check if attribute requires handler to accept the data */ + + if (!(attr->perm & BT_GATT_PERM_PREPARE_WRITE)) + { + goto append; + } + + /* Write attribute value to check if device is authorized */ + + write = attr->write(data->conn, attr, data->value, data->len, data->offset, + BT_GATT_WRITE_FLAG_PREPARE); + if (write != 0) + { + data->err = err_to_att(write); + return BT_GATT_ITER_STOP; + } + +append: + + /* Copy data into the outstanding queue */ + + data->buf = net_buf_alloc(&prep_pool, 0); + if (!data->buf) + { + data->err = BT_ATT_ERR_PREPARE_QUEUE_FULL; + return BT_GATT_ITER_STOP; + } + + attr_data = net_buf_user_data(data->buf); + attr_data->handle = handle; + attr_data->offset = data->offset; + + net_buf_add_mem(data->buf, data->value, data->len); + + data->err = 0U; + + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t att_prep_write_rsp( + struct bt_att_chan *chan, uint16_t handle, uint16_t offset, + const void *value, uint16_t len) +{ + struct prep_data data; + struct bt_att_prepare_write_rsp *rsp; + + if (!bt_gatt_change_aware(chan->att->conn, true)) + { + if (!atomic_test_and_set_bit(chan->flags, ATT_OUT_OF_SYNC_SENT)) + { + return BT_ATT_ERR_DB_OUT_OF_SYNC; + } + else + { + return 0; + } + } + + if (!handle) + { + return BT_ATT_ERR_INVALID_HANDLE; + } + + (void)memset(&data, 0, sizeof(data)); + + data.conn = chan->att->conn; + data.offset = offset; + data.value = value; + data.len = len; + data.err = BT_ATT_ERR_INVALID_HANDLE; + + bt_gatt_foreach_attr(handle, handle, prep_write_cb, &data); + + if (data.err) + { + /* Respond here since handle is set */ + + send_err_rsp(chan, BT_ATT_OP_PREPARE_WRITE_REQ, handle, data.err); + return 0; + } + + LOG_DBG("buf %p handle 0x%04x offset %u", data.buf, handle, offset); + + /* Store buffer in the outstanding queue */ + + net_buf_slist_put(&chan->att->prep_queue, data.buf); + + /* Generate response */ + + data.buf = att_create_rsp_pdu(chan, BT_ATT_OP_PREPARE_WRITE_RSP); + if (!data.buf) + { + return BT_ATT_ERR_INSUFFICIENT_RESOURCES; + } + + rsp = net_buf_add(data.buf, sizeof(*rsp)); + rsp->handle = sys_cpu_to_le16(handle); + rsp->offset = sys_cpu_to_le16(offset); + net_buf_add(data.buf, len); + memcpy(rsp->value, value, len); + + bt_att_chan_send_rsp(chan, data.buf); + + return 0; +} + +static uint8_t att_prepare_write_req(struct bt_att_chan *chan, + struct net_buf *buf) +{ + struct bt_att_prepare_write_req *req; + uint16_t handle; + uint16_t offset; + + req = net_buf_pull_mem(buf, sizeof(*req)); + + handle = sys_le16_to_cpu(req->handle); + offset = sys_le16_to_cpu(req->offset); + + LOG_DBG("handle 0x%04x offset %u", handle, offset); + + return att_prep_write_rsp(chan, handle, offset, buf->data, buf->len); +} + +static uint8_t exec_write_reassemble( + uint16_t handle, uint16_t offset, struct list_node *list, + struct net_buf_simple *buf) +{ + struct net_buf *entry; + struct net_buf *next; + struct list_node *prev; + + prev = NULL; + list_for_every_container_safe(list, entry, next, node) + { + struct bt_attr_data *tmp_data = net_buf_user_data(entry); + + LOG_DBG("entry %p handle 0x%04x, offset %u", entry, tmp_data->handle, + tmp_data->offset); + + if (tmp_data->handle == handle) + { + if (tmp_data->offset == 0) + { + /* Multiple writes to the same handle can occur + * in a prepare write queue. If the offset is 0, that should + * mean that it's a new write to the same handle, and we break + * to process the first write. + */ + + LOG_DBG("tmp_data->offset == 0"); + break; + } + + if (tmp_data->offset != buf->len + offset) + { + /* We require that the offset is increasing + * properly to avoid badly reassembled buffers + */ + + LOG_DBG("Bad offset %u (%u, %u)", tmp_data->offset, buf->len, + offset); + + return BT_ATT_ERR_INVALID_OFFSET; + } + + if (buf->len + entry->len > buf->size) + { + return BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + + net_buf_simple_add_mem(buf, entry->data, entry->len); + list_remove(list, prev, &entry->node); + net_buf_unref(entry); + } + else + { + prev = &entry->node; + } + } + + return BT_ATT_ERR_SUCCESS; +} + +static uint8_t att_exec_write_rsp(struct bt_att_chan *chan, uint8_t flags) +{ + struct net_buf *buf; + uint8_t err = 0U; + + /* The following code will iterate on all prepare writes in the + * prep_queue, and reassemble those that share the same handle. Once a + * handle has been reassembled, it is sent to the upper layers, and the + * next handle is processed + */ + + while (!list_is_empty(&chan->att->prep_queue)) + { + struct bt_attr_data *data; + uint16_t handle; + + NET_BUF_SIMPLE_DEFINE_STATIC( + reassembled_data, + MIN(BT_ATT_MAX_ATTRIBUTE_LEN, + CONFIG_BT_ATT_PREPARE_COUNT * BT_ATT_BUF_SIZE)); + + buf = net_buf_slist_get(&chan->att->prep_queue); + data = net_buf_user_data(buf); + handle = data->handle; + + LOG_DBG("buf %p handle 0x%04x offset %u", buf, handle, data->offset); + + net_buf_simple_reset(&reassembled_data); + net_buf_simple_add_mem(&reassembled_data, buf->data, buf->len); + + err = exec_write_reassemble(handle, data->offset, + &chan->att->prep_queue, + &reassembled_data); + if (err != BT_ATT_ERR_SUCCESS) + { + /* Discard queued buffers */ + + do + { + net_buf_unref(buf); + buf = net_buf_slist_get(&chan->att->prep_queue); + } + while (buf != NULL); + send_err_rsp(chan, BT_ATT_OP_EXEC_WRITE_REQ, handle, err); + return 0; + } + + /* Just discard the data if an error was set */ + + if (!err && flags == BT_ATT_FLAG_EXEC) + { + err = perform_write(chan, BT_ATT_OP_EXEC_WRITE_REQ, handle, + data->offset, + reassembled_data.data, reassembled_data.len); + if (err) + { + /* Respond here since handle is set */ + + send_err_rsp(chan, BT_ATT_OP_EXEC_WRITE_REQ, data->handle, + err); + } + } + + net_buf_unref(buf); + } + + if (err) + { + return 0; + } + + /* Generate response */ + + buf = att_create_rsp_pdu(chan, BT_ATT_OP_EXEC_WRITE_RSP); + if (!buf) + { + return BT_ATT_ERR_UNLIKELY; + } + + bt_att_chan_send_rsp(chan, buf); + + return 0; +} + +static uint8_t att_exec_write_req(struct bt_att_chan *chan, + struct net_buf *buf) +{ + struct bt_att_exec_write_req *req; + + req = (void *)buf->data; + + LOG_DBG("flags 0x%02x", req->flags); + + return att_exec_write_rsp(chan, req->flags); +} +#endif /* CONFIG_BT_ATT_PREPARE_COUNT > 0 */ + +static uint8_t att_write_cmd(struct bt_att_chan *chan, struct net_buf *buf) +{ + uint16_t handle; + + handle = net_buf_pull_le16(buf); + + LOG_DBG("handle 0x%04x", handle); + + if (perform_write(chan, BT_ATT_OP_WRITE_CMD, handle, 0, buf->data, + buf->len)) + { + /* Nothing we can do here, as this is a write command */ + } + + return 0; +} + +#if defined(CONFIG_BT_SIGNING) +static uint8_t att_signed_write_cmd(struct bt_att_chan *chan, + struct net_buf *buf) +{ + struct bt_conn *conn = chan->chan.chan.conn; + struct bt_att_signed_write_cmd *req; + uint16_t handle; + int err; + + /* The Signed Write Without Response sub-procedure shall only be supported + * on the LE Fixed Channel Unenhanced ATT bearer. + */ + + if (bt_att_is_enhanced(chan)) + { + /* No response for this command */ + + return 0; + } + + req = (void *)buf->data; + + handle = sys_le16_to_cpu(req->handle); + + LOG_DBG("handle 0x%04x", handle); + + /* Verifying data requires full buffer including attribute header */ + + net_buf_push(buf, sizeof(struct bt_att_hdr)); + err = bt_smp_sign_verify(conn, buf); + if (err) + { + LOG_ERR("Error verifying data"); + + /* No response for this command */ + + return 0; + } + + net_buf_pull(buf, sizeof(struct bt_att_hdr)); + net_buf_pull(buf, sizeof(*req)); + + if (perform_write(chan, BT_ATT_OP_SIGNED_WRITE_CMD, handle, 0, buf->data, + buf->len - sizeof(struct bt_att_signature)) != 0) + { + /* Nothing we can do here, as this is a write command */ + } + + return 0; +} +#endif /* CONFIG_BT_SIGNING */ + +#if defined(CONFIG_BT_GATT_CLIENT) +#if defined(CONFIG_BT_ATT_RETRY_ON_SEC_ERR) +static int att_change_security(struct bt_conn *conn, uint8_t err) +{ + bt_security_t sec; + + switch (err) + { + case BT_ATT_ERR_INSUFFICIENT_ENCRYPTION: + if (conn->sec_level >= BT_SECURITY_L2) + { + return -EALREADY; + } + + sec = BT_SECURITY_L2; + break; + case BT_ATT_ERR_AUTHENTICATION: + if (conn->sec_level < BT_SECURITY_L2) + { + /* BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part C] + * page 375: + * + * If an LTK is not available, the service request shall be + * rejected with the error code 'Insufficient Authentication'. + * Note: When the link is not encrypted, the error code + * "Insufficient Authentication" does not indicate that MITM + * protection is required. + */ + + sec = BT_SECURITY_L2; + } + else if (conn->sec_level < BT_SECURITY_L3) + { + /* BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part C] + * page 375: + * + * If an authenticated pairing is required but only an + * unauthenticated pairing has occurred and the link is currently + * encrypted, the service request shall be rejected with the + * error + * code 'Insufficient Authentication'. Note: When unauthenticated + * pairing has occurred and the link is currently encrypted, the + * error code 'Insufficient Authentication' indicates that MITM + * protection is required. + */ + + sec = BT_SECURITY_L3; + } + else if (conn->sec_level < BT_SECURITY_L4) + { + /* BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part C] + * page 375: + * + * If LE Secure Connections authenticated pairing is required but + * LE legacy pairing has occurred and the link is currently + * encrypted, the service request shall be rejected with the + * error + * code ''Insufficient Authentication'. + */ + + sec = BT_SECURITY_L4; + } + else + { + return -EALREADY; + } + break; + default: + return -EINVAL; + } + + return bt_conn_set_security(conn, sec); +} +#endif /* CONFIG_BT_ATT_RETRY_ON_SEC_ERR */ + +static uint8_t att_error_rsp(struct bt_att_chan *chan, struct net_buf *buf) +{ + struct bt_att_error_rsp *rsp; + uint8_t err; + + rsp = (void *)buf->data; + + LOG_DBG("request 0x%02x handle 0x%04x error 0x%02x", rsp->request, + sys_le16_to_cpu(rsp->handle), + rsp->error); + + /* Don't retry if there is no req pending or it has been cancelled. + * + * BLUETOOTH SPECIFICATION Version 5.2 [Vol 3, Part F] page 1423: + * + * If an error code is received in the ATT_ERROR_RSP PDU that is not + * understood by the client, for example an error code that was reserved + * for future use that is now being used in a future version of the + * specification, then the ATT_ERROR_RSP PDU shall still be considered to + * state that the given request cannot be performed for an unknown reason. + */ + + if (!chan->req || chan->req == &cancel || !rsp->error) + { + err = BT_ATT_ERR_UNLIKELY; + goto done; + } + + err = rsp->error; + +#if defined(CONFIG_BT_ATT_RETRY_ON_SEC_ERR) + int ret; + + /* Check if error can be handled by elevating security. */ + + ret = att_change_security(chan->chan.chan.conn, err); + if (ret == 0 || ret == -EBUSY) + { + /* ATT timeout work is normally cancelled in att_handle_rsp. + * However retrying is special case, so the timeout shall be cancelled + * here. + */ + + k_work_cancel_delayable(&chan->timeout_work); + + chan->req->retrying = true; + return 0; + } +#endif /* CONFIG_BT_ATT_RETRY_ON_SEC_ERR */ + +done: + return att_handle_rsp(chan, NULL, 0, err); +} + +static uint8_t att_handle_find_info_rsp(struct bt_att_chan *chan, + struct net_buf *buf) +{ + LOG_DBG(""); + + return att_handle_rsp(chan, buf->data, buf->len, 0); +} + +static uint8_t att_handle_find_type_rsp(struct bt_att_chan *chan, + struct net_buf *buf) +{ + LOG_DBG(""); + + return att_handle_rsp(chan, buf->data, buf->len, 0); +} + +static uint8_t att_handle_read_type_rsp(struct bt_att_chan *chan, + struct net_buf *buf) +{ + LOG_DBG(""); + + return att_handle_rsp(chan, buf->data, buf->len, 0); +} + +static uint8_t att_handle_read_rsp(struct bt_att_chan *chan, + struct net_buf *buf) +{ + LOG_DBG(""); + + return att_handle_rsp(chan, buf->data, buf->len, 0); +} + +static uint8_t att_handle_read_blob_rsp(struct bt_att_chan *chan, + struct net_buf *buf) +{ + LOG_DBG(""); + + return att_handle_rsp(chan, buf->data, buf->len, 0); +} + +#if defined(CONFIG_BT_GATT_READ_MULTIPLE) +static uint8_t att_handle_read_mult_rsp(struct bt_att_chan *chan, + struct net_buf *buf) +{ + LOG_DBG(""); + + return att_handle_rsp(chan, buf->data, buf->len, 0); +} + +#endif /* CONFIG_BT_GATT_READ_MULTIPLE */ + +#if defined(CONFIG_BT_GATT_READ_MULT_VAR_LEN) +static uint8_t att_handle_read_mult_vl_rsp(struct bt_att_chan *chan, + struct net_buf *buf) +{ + LOG_DBG(""); + + return att_handle_rsp(chan, buf->data, buf->len, 0); +} +#endif /* CONFIG_BT_GATT_READ_MULT_VAR_LEN */ + +static uint8_t att_handle_read_group_rsp(struct bt_att_chan *chan, + struct net_buf *buf) +{ + LOG_DBG(""); + + return att_handle_rsp(chan, buf->data, buf->len, 0); +} + +static uint8_t att_handle_write_rsp(struct bt_att_chan *chan, + struct net_buf *buf) +{ + LOG_DBG(""); + + return att_handle_rsp(chan, buf->data, buf->len, 0); +} + +static uint8_t att_handle_prepare_write_rsp(struct bt_att_chan *chan, + struct net_buf *buf) +{ + LOG_DBG(""); + + return att_handle_rsp(chan, buf->data, buf->len, 0); +} + +static uint8_t att_handle_exec_write_rsp(struct bt_att_chan *chan, + struct net_buf *buf) +{ + LOG_DBG(""); + + return att_handle_rsp(chan, buf->data, buf->len, 0); +} + +static uint8_t att_notify(struct bt_att_chan *chan, struct net_buf *buf) +{ + uint16_t handle; + + handle = net_buf_pull_le16(buf); + + LOG_DBG("chan %p handle 0x%04x", chan, handle); + + bt_gatt_notification(chan->att->conn, handle, buf->data, buf->len); + + return 0; +} + +static uint8_t att_indicate(struct bt_att_chan *chan, struct net_buf *buf) +{ + uint16_t handle; + + handle = net_buf_pull_le16(buf); + + LOG_DBG("chan %p handle 0x%04x", chan, handle); + + bt_gatt_notification(chan->att->conn, handle, buf->data, buf->len); + + buf = bt_att_chan_create_pdu(chan, BT_ATT_OP_CONFIRM, 0); + if (!buf) + { + LOG_ERR("Unable to create confirm PDU"); + return 0; + } + + bt_att_chan_send_rsp(chan, buf); + + return 0; +} + +static uint8_t att_notify_mult(struct bt_att_chan *chan, struct net_buf *buf) +{ + LOG_DBG("chan %p", chan); + + bt_gatt_mult_notification(chan->att->conn, buf->data, buf->len); + + return 0; +} +#endif /* CONFIG_BT_GATT_CLIENT */ + +static uint8_t att_confirm(struct bt_att_chan *chan, struct net_buf *buf) +{ + LOG_DBG(""); + + return att_handle_rsp(chan, buf->data, buf->len, 0); +} + +static const struct att_handler +{ + uint8_t op; + uint8_t expect_len; + att_type_t type; + uint8_t (*func)(struct bt_att_chan *chan, struct net_buf *buf); +} + +handlers[] = +{ + { + BT_ATT_OP_MTU_REQ, + sizeof(struct bt_att_exchange_mtu_req), + ATT_REQUEST, + att_mtu_req + }, + + { + BT_ATT_OP_FIND_INFO_REQ, sizeof(struct bt_att_find_info_req), + ATT_REQUEST, + att_find_info_req + }, + + { + BT_ATT_OP_FIND_TYPE_REQ, sizeof(struct bt_att_find_type_req), + ATT_REQUEST, + att_find_type_req + }, + + { + BT_ATT_OP_READ_TYPE_REQ, sizeof(struct bt_att_read_type_req), + ATT_REQUEST, + att_read_type_req + }, + + { + BT_ATT_OP_READ_REQ, + sizeof(struct bt_att_read_req), + ATT_REQUEST, + att_read_req + }, + + { + BT_ATT_OP_READ_BLOB_REQ, sizeof(struct bt_att_read_blob_req), + ATT_REQUEST, + att_read_blob_req + }, +#if defined(CONFIG_BT_GATT_READ_MULTIPLE) + + { + BT_ATT_OP_READ_MULT_REQ, + BT_ATT_READ_MULT_MIN_LEN_REQ, + ATT_REQUEST, + att_read_mult_req + }, +#endif /* CONFIG_BT_GATT_READ_MULTIPLE */ +#if defined(CONFIG_BT_GATT_READ_MULT_VAR_LEN) + + { + BT_ATT_OP_READ_MULT_VL_REQ, BT_ATT_READ_MULT_MIN_LEN_REQ, + ATT_REQUEST, + att_read_mult_vl_req + }, +#endif /* CONFIG_BT_GATT_READ_MULT_VAR_LEN */ + + { + BT_ATT_OP_READ_GROUP_REQ, sizeof(struct bt_att_read_group_req), + ATT_REQUEST, + att_read_group_req + }, + + { + BT_ATT_OP_WRITE_REQ, + sizeof(struct bt_att_write_req), + ATT_REQUEST, + att_write_req + }, +#if CONFIG_BT_ATT_PREPARE_COUNT > 0 + + { + BT_ATT_OP_PREPARE_WRITE_REQ, + sizeof(struct bt_att_prepare_write_req), ATT_REQUEST, + att_prepare_write_req + }, + + { + BT_ATT_OP_EXEC_WRITE_REQ, sizeof(struct bt_att_exec_write_req), + ATT_REQUEST, + att_exec_write_req + }, +#endif /* CONFIG_BT_ATT_PREPARE_COUNT > 0 */ + + { + BT_ATT_OP_CONFIRM, 0, ATT_CONFIRMATION, att_confirm + }, + + { + BT_ATT_OP_WRITE_CMD, + sizeof(struct bt_att_write_cmd), + ATT_COMMAND, + att_write_cmd + }, +#if defined(CONFIG_BT_SIGNING) + + { + BT_ATT_OP_SIGNED_WRITE_CMD, + (sizeof(struct bt_att_write_cmd) + + sizeof(struct bt_att_signature)), ATT_COMMAND, + att_signed_write_cmd + }, +#endif /* CONFIG_BT_SIGNING */ +#if defined(CONFIG_BT_GATT_CLIENT) + + { + BT_ATT_OP_ERROR_RSP, + sizeof(struct bt_att_error_rsp), + ATT_RESPONSE, + att_error_rsp + }, + + { + BT_ATT_OP_MTU_RSP, + sizeof(struct bt_att_exchange_mtu_rsp), + ATT_RESPONSE, + att_mtu_rsp + }, + + { + BT_ATT_OP_FIND_INFO_RSP, sizeof(struct bt_att_find_info_rsp), + ATT_RESPONSE, + att_handle_find_info_rsp + }, + + { + BT_ATT_OP_FIND_TYPE_RSP, sizeof(struct bt_att_handle_group), + ATT_RESPONSE, + att_handle_find_type_rsp + }, + + { + BT_ATT_OP_READ_TYPE_RSP, sizeof(struct bt_att_read_type_rsp), + ATT_RESPONSE, + att_handle_read_type_rsp + }, + + { + BT_ATT_OP_READ_RSP, 0, ATT_RESPONSE, att_handle_read_rsp + }, + + { + BT_ATT_OP_READ_BLOB_RSP, + 0, + ATT_RESPONSE, + att_handle_read_blob_rsp + }, +#if defined(CONFIG_BT_GATT_READ_MULTIPLE) + + { + BT_ATT_OP_READ_MULT_RSP, + 0, + ATT_RESPONSE, + att_handle_read_mult_rsp + }, +#endif /* CONFIG_BT_GATT_READ_MULTIPLE */ +#if defined(CONFIG_BT_GATT_READ_MULT_VAR_LEN) + + { + BT_ATT_OP_READ_MULT_VL_RSP, + sizeof(struct bt_att_read_mult_vl_rsp), ATT_RESPONSE, + att_handle_read_mult_vl_rsp + }, +#endif /* CONFIG_BT_GATT_READ_MULT_VAR_LEN */ + + { + BT_ATT_OP_READ_GROUP_RSP, sizeof(struct bt_att_read_group_rsp), + ATT_RESPONSE, + att_handle_read_group_rsp + }, + + { + BT_ATT_OP_WRITE_RSP, 0, ATT_RESPONSE, att_handle_write_rsp + }, + + { + BT_ATT_OP_PREPARE_WRITE_RSP, + sizeof(struct bt_att_prepare_write_rsp), ATT_RESPONSE, + att_handle_prepare_write_rsp + }, + + { + BT_ATT_OP_EXEC_WRITE_RSP, + 0, + ATT_RESPONSE, + att_handle_exec_write_rsp + }, + + { + BT_ATT_OP_NOTIFY, + sizeof(struct bt_att_notify), + ATT_NOTIFICATION, + att_notify + }, + + { + BT_ATT_OP_INDICATE, + sizeof(struct bt_att_indicate), + ATT_INDICATION, + att_indicate + }, + + { + BT_ATT_OP_NOTIFY_MULT, sizeof(struct bt_att_notify_mult), + ATT_NOTIFICATION, + att_notify_mult + }, +#endif /* CONFIG_BT_GATT_CLIENT */ +}; + +static att_type_t att_op_get_type(uint8_t op) +{ + switch (op) + { + case BT_ATT_OP_MTU_REQ: + case BT_ATT_OP_FIND_INFO_REQ: + case BT_ATT_OP_FIND_TYPE_REQ: + case BT_ATT_OP_READ_TYPE_REQ: + case BT_ATT_OP_READ_REQ: + case BT_ATT_OP_READ_BLOB_REQ: + case BT_ATT_OP_READ_MULT_REQ: + case BT_ATT_OP_READ_MULT_VL_REQ: + case BT_ATT_OP_READ_GROUP_REQ: + case BT_ATT_OP_WRITE_REQ: + case BT_ATT_OP_PREPARE_WRITE_REQ: + case BT_ATT_OP_EXEC_WRITE_REQ: + return ATT_REQUEST; + case BT_ATT_OP_CONFIRM: + return ATT_CONFIRMATION; + case BT_ATT_OP_WRITE_CMD: + case BT_ATT_OP_SIGNED_WRITE_CMD: + return ATT_COMMAND; + case BT_ATT_OP_ERROR_RSP: + case BT_ATT_OP_MTU_RSP: + case BT_ATT_OP_FIND_INFO_RSP: + case BT_ATT_OP_FIND_TYPE_RSP: + case BT_ATT_OP_READ_TYPE_RSP: + case BT_ATT_OP_READ_RSP: + case BT_ATT_OP_READ_BLOB_RSP: + case BT_ATT_OP_READ_MULT_RSP: + case BT_ATT_OP_READ_MULT_VL_RSP: + case BT_ATT_OP_READ_GROUP_RSP: + case BT_ATT_OP_WRITE_RSP: + case BT_ATT_OP_PREPARE_WRITE_RSP: + case BT_ATT_OP_EXEC_WRITE_RSP: + return ATT_RESPONSE; + case BT_ATT_OP_NOTIFY: + case BT_ATT_OP_NOTIFY_MULT: + return ATT_NOTIFICATION; + case BT_ATT_OP_INDICATE: + return ATT_INDICATION; + } + + if (op & ATT_CMD_MASK) + { + return ATT_COMMAND; + } + + return ATT_UNKNOWN; +} + +static struct bt_conn *get_conn(struct bt_att_chan *att_chan) +{ + return att_chan->chan.chan.conn; +} + +static int bt_att_recv(struct bt_l2cap_chan *chan, struct net_buf *buf) +{ + struct bt_att_chan *att_chan = ATT_CHAN(chan); + struct bt_conn *conn = get_conn(att_chan); + struct bt_att_hdr *hdr; + const struct att_handler *handler; + uint8_t err; + size_t i; + + if (buf->len < sizeof(*hdr)) + { + LOG_ERR("Too small ATT PDU received"); + return 0; + } + + hdr = net_buf_pull_mem(buf, sizeof(*hdr)); + LOG_DBG("Received ATT chan %p code 0x%02x len %zu", att_chan, hdr->code, + net_buf_frags_len(buf)); + + if (conn->state != BT_CONN_CONNECTED) + { + LOG_DBG("not connected: conn %p state %u", conn, conn->state); + return 0; + } + + if (!att_chan->att) + { + LOG_DBG("Ignore recv on detached ATT chan"); + return 0; + } + + for (i = 0, handler = NULL; i < ARRAY_SIZE(handlers); i++) + { + if (hdr->code == handlers[i].op) + { + handler = &handlers[i]; + break; + } + } + + if (!handler) + { + LOG_WRN("Unhandled ATT code 0x%02x", hdr->code); + if (att_op_get_type(hdr->code) != ATT_COMMAND && + att_op_get_type(hdr->code) != ATT_INDICATION) + { + send_err_rsp(att_chan, hdr->code, 0, BT_ATT_ERR_NOT_SUPPORTED); + } + + return 0; + } + + if (buf->len < handler->expect_len) + { + LOG_ERR("Invalid len %u for code 0x%02x", buf->len, hdr->code); + err = BT_ATT_ERR_INVALID_PDU; + } + else + { + err = handler->func(att_chan, buf); + } + + if (handler->type == ATT_REQUEST && err) + { + LOG_DBG("ATT error 0x%02x", err); + send_err_rsp(att_chan, hdr->code, 0, err); + } + + return 0; +} + +static struct bt_att *att_get(struct bt_conn *conn) +{ + struct bt_l2cap_chan *chan; + struct bt_att_chan *att_chan; + + if (conn->state != BT_CONN_CONNECTED) + { + LOG_WRN("Not connected"); + return NULL; + } + + chan = bt_l2cap_le_lookup_rx_cid(conn, BT_L2CAP_CID_ATT); + if (!chan) + { + LOG_ERR("Unable to find ATT channel"); + return NULL; + } + + att_chan = ATT_CHAN(chan); + if (!atomic_test_bit(att_chan->flags, ATT_CONNECTED)) + { + LOG_ERR("ATT channel not connected"); + return NULL; + } + + return att_chan->att; +} + +struct net_buf *bt_att_create_pdu(struct bt_conn *conn, + uint8_t op, size_t len) +{ + struct bt_att *att; + struct bt_att_chan *chan; + struct bt_att_chan *tmp; + + att = att_get(conn); + if (!att) + { + return NULL; + } + + /* This allocator should _not_ be used for RSPs. */ + + list_for_every_container_safe(&att->chans, chan, tmp, node) + { + if (len + sizeof(op) > bt_att_mtu(chan)) + { + continue; + } + + return bt_att_chan_create_pdu(chan, op, len); + } + + LOG_WRN("No ATT channel for MTU %zu", len + sizeof(op)); + + return NULL; +} + +static struct net_buf *att_create_rsp_pdu(struct bt_att_chan *chan, + uint8_t op) +{ + size_t headroom; + struct bt_att_hdr *hdr; + struct bt_att_tx_meta_data *data; + struct net_buf *buf; + + buf = net_buf_alloc(&att_pool, BT_ATT_TIMEOUT_SEC * MSEC_PER_SEC); + if (!buf) + { + LOG_ERR("Unable to allocate buffer for op 0x%02x", op); + return NULL; + } + + headroom = BT_L2CAP_BUF_SIZE(0); + + if (bt_att_is_enhanced(chan)) + { + headroom += BT_L2CAP_SDU_HDR_SIZE; + } + + net_buf_reserve(buf, headroom); + + data = att_get_tx_meta_data(buf); + data->att_chan = chan; + + hdr = net_buf_add(buf, sizeof(*hdr)); + hdr->code = op; + + return buf; +} + +static void att_reset(struct bt_att *att) +{ + struct net_buf *buf; + +#if CONFIG_BT_ATT_PREPARE_COUNT > 0 + /* Discard queued buffers */ + + while ((buf = net_buf_slist_get(&att->prep_queue))) + { + net_buf_unref(buf); + } +#endif /* CONFIG_BT_ATT_PREPARE_COUNT > 0 */ + +#if defined(CONFIG_BT_EATT) + struct k_work_sync sync; + + (void)k_work_cancel_delayable_sync(&att->eatt.connection_work, &sync); +#endif /* CONFIG_BT_EATT */ + + while ((buf = k_fifo_get(&att->tx_queue, 0))) + { + net_buf_unref(buf); + } + + /* Notify pending requests */ + + while (!list_is_empty(&att->reqs)) + { + struct bt_att_req *req; + struct list_node *node; + + node = list_remove_head(&att->reqs); + req = CONTAINER_OF(node, struct bt_att_req, node); + if (req->func) + { + req->func(att->conn, -ECONNRESET, NULL, 0, req->user_data); + } + + bt_att_req_free(req); + } + + /* FIXME: `att->conn` is not reference counted. Consider using + * `bt_conn_ref` and `bt_conn_unref` to follow convention. + */ + + att->conn = NULL; + k_mem_slab_free(&att_slab, (void *)att); +} + +static void att_chan_detach(struct bt_att_chan *chan) +{ + struct net_buf *buf; + + LOG_DBG("chan %p", chan); + + list_find_and_remove(&chan->att->chans, &chan->node); + + /* Release pending buffers */ + + while ((buf = k_fifo_get(&chan->tx_queue, 0))) + { + net_buf_unref(buf); + } + + if (chan->req) + { + /* Notify outstanding request */ + + att_handle_rsp(chan, NULL, 0, -ECONNRESET); + } + + chan->att = NULL; + atomic_clear_bit(chan->flags, ATT_CONNECTED); +} + +static void att_timeout(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_att_chan *chan = CONTAINER_OF(dwork, struct bt_att_chan, + timeout_work); + + LOG_ERR("ATT Timeout for device %s. Disconnecting...", + bt_conn_dst_str(chan->att->conn)); + + /* BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part F] page 480: + * + * A transaction not completed within 30 seconds shall time out. Such a + * transaction shall be considered to have failed and the local higher + * layers shall be informed of this failure. No more attribute protocol + * requests, commands, indications or notifications shall be sent to the + * target device on this ATT Bearer. + */ + + /* The timeout state is local and can block new ATT operations, but does + * not affect the remote side. Disconnecting the GATT connection upon ATT + * timeout simplifies error handling for developers. This reduces rare + * failure conditions to a common one, allowing developers to handle + * unexpected disconnections without needing special cases for ATT + * timeouts. + */ + + att_disconnect(chan); +} + +static struct bt_att_chan *att_get_fixed_chan(struct bt_conn *conn) +{ + struct bt_l2cap_chan *chan; + + chan = bt_l2cap_le_lookup_tx_cid(conn, BT_L2CAP_CID_ATT); + DEBUGASSERT(chan); + + return ATT_CHAN(chan); +} + +static void att_chan_attach(struct bt_att *att, struct bt_att_chan *chan) +{ + LOG_DBG("att %p chan %p flags %lu", att, chan, atomic_read(chan->flags)); + + if (list_is_empty(&att->chans)) + { + /* Init general queues when attaching the first channel */ + + k_fifo_init(&att->tx_queue); +#if CONFIG_BT_ATT_PREPARE_COUNT > 0 + list_initialize(&att->prep_queue); +#endif + } + + list_add_head(&att->chans, &chan->node); +} + +static void bt_att_connected(struct bt_l2cap_chan *chan) +{ + struct bt_att_chan *att_chan = ATT_CHAN(chan); + struct bt_l2cap_le_chan *le_chan = BT_L2CAP_LE_CHAN(chan); + + LOG_DBG("chan %p cid 0x%04x", le_chan, le_chan->tx.cid); + + atomic_set_bit(att_chan->flags, ATT_CONNECTED); + + att_chan_mtu_updated(att_chan); + + k_work_init_delayable(&att_chan->timeout_work, att_timeout); + + bt_gatt_connected(le_chan->chan.conn); +} + +static void bt_att_disconnected(struct bt_l2cap_chan *chan) +{ + struct bt_att_chan *att_chan = ATT_CHAN(chan); + struct bt_att *att = att_chan->att; + struct bt_l2cap_le_chan *le_chan = BT_L2CAP_LE_CHAN(chan); + + LOG_DBG("chan %p cid 0x%04x", le_chan, le_chan->tx.cid); + + if (!att_chan->att) + { + LOG_DBG("Ignore disconnect on detached ATT chan"); + return; + } + + att_chan_detach(att_chan); + + /* Don't reset if there are still channels to be used */ + + if (!list_is_empty(&att->chans)) + { + return; + } + + att_reset(att); + + bt_gatt_disconnected(le_chan->chan.conn); +} + +#if defined(CONFIG_BT_SMP) +#if defined(CONFIG_BT_ATT_RETRY_ON_SEC_ERR) +static uint8_t att_req_retry(struct bt_att_chan *att_chan) +{ + struct bt_att_req *req = att_chan->req; + struct net_buf *buf; + + /* Resend buffer */ + + if (!req->encode) + { + /* This request does not support resending */ + + return BT_ATT_ERR_AUTHENTICATION; + } + + buf = bt_att_chan_create_pdu(att_chan, req->att_op, req->len); + if (!buf) + { + LOG_ERR("Unable to create retry PDU (%u)", req->att_op); + return BT_ATT_ERR_UNLIKELY; + } + + if (req->encode(buf, req->len, req->user_data)) + { + net_buf_unref(buf); + return BT_ATT_ERR_UNLIKELY; + } + + if (chan_send(att_chan, buf)) + { + net_buf_unref(buf); + return BT_ATT_ERR_UNLIKELY; + } + + return BT_ATT_ERR_SUCCESS; +} +#endif /* CONFIG_BT_ATT_RETRY_ON_SEC_ERR */ + +static void bt_att_encrypt_change(struct bt_l2cap_chan *chan, + uint8_t hci_status) +{ + struct bt_att_chan *att_chan = ATT_CHAN(chan); + struct bt_l2cap_le_chan *le_chan = BT_L2CAP_LE_CHAN(chan); + struct bt_conn *conn = le_chan->chan.conn; + + LOG_DBG("chan %p conn %p handle %u sec_level 0x%02x status 0x%02x %s", + le_chan, conn, + conn->handle, conn->sec_level, hci_status, + bt_hci_err_to_str(hci_status)); + + if (!att_chan->att) + { + LOG_DBG("Ignore encrypt change on detached ATT chan"); + return; + } + + /* If status (HCI status of security procedure) is non-zero, notify + * outstanding request about security failure. + */ + + if (hci_status) + { +#if defined(CONFIG_BT_ATT_RETRY_ON_SEC_ERR) + if (att_chan->req && att_chan->req->retrying) + { + att_handle_rsp(att_chan, NULL, 0, BT_ATT_ERR_AUTHENTICATION); + } +#endif /* CONFIG_BT_ATT_RETRY_ON_SEC_ERR */ + + return; + } + + bt_gatt_encrypt_change(conn); + + if (conn->sec_level == BT_SECURITY_L1) + { + return; + } + +#if defined(CONFIG_BT_ATT_RETRY_ON_SEC_ERR) + uint8_t err; + + if (!(att_chan->req && att_chan->req->retrying)) + { + return; + } + + LOG_DBG("Retrying"); + + err = att_req_retry(att_chan); + if (err) + { + LOG_DBG("Retry failed (%d)", err); + att_handle_rsp(att_chan, NULL, 0, err); + } +#endif /* CONFIG_BT_ATT_RETRY_ON_SEC_ERR */ +} +#endif /* CONFIG_BT_SMP */ + +static void bt_att_status(struct bt_l2cap_chan *ch, atomic_t *status) +{ + struct bt_att_chan *chan = ATT_CHAN(ch); + struct list_node *node; + + LOG_DBG("chan %p status %p", ch, status); + + if (!atomic_test_bit(status, BT_L2CAP_STATUS_OUT)) + { + return; + } + + if (!chan->att) + { + LOG_DBG("Ignore status on detached ATT chan"); + return; + } + + /* If there is a request pending don't attempt to send */ + + if (chan->req) + { + return; + } + + /* Pull next request from the list */ + + node = list_remove_head(&chan->att->reqs); + if (!node) + { + return; + } + + if (bt_att_chan_req_send(chan, ATT_REQ(node)) == 0) + { + return; + } + + /* Prepend back to the list as it could not be sent */ + + list_add_head(&chan->att->reqs, node); +} + +static void bt_att_released(struct bt_l2cap_chan *ch) +{ + struct bt_att_chan *chan = ATT_CHAN(ch); + + LOG_DBG("chan %p", chan); + + /* Drop any pending/in-flight ATT TX metadata still referencing this + * channel, so the deferred att_on_sent_cb()/bt_att_sent() work cannot + * dereference the channel after it is freed here. Bluetooth uses a + * cooperative system workqueue, so this runs serialized with + * att_tx_destroy_work_handler() and aligned pointer writes are atomic. + */ + + ARRAY_FOR_EACH(tx_meta_data_storage, i) + { + if (tx_meta_data_storage[i].att_chan == chan) + { + tx_meta_data_storage[i].att_chan = NULL; + } + } + + k_mem_slab_free(&chan_slab, (void *)chan); +} + +#if defined(CONFIG_BT_EATT) +static void bt_att_reconfigured(struct bt_l2cap_chan *l2cap_chan) +{ + struct bt_att_chan *att_chan = ATT_CHAN(l2cap_chan); + + LOG_DBG("chan %p", att_chan); + + att_chan_mtu_updated(att_chan); +} +#endif /* CONFIG_BT_EATT */ + +static struct bt_att_chan *att_chan_new(struct bt_att *att, int32_t flags) +{ + int quota = 0; + static struct bt_l2cap_chan_ops ops = + { + .connected = bt_att_connected, + .disconnected = bt_att_disconnected, + .recv = bt_att_recv, + .sent = bt_att_sent, + .status = bt_att_status, +#if defined(CONFIG_BT_SMP) + .encrypt_change = bt_att_encrypt_change, +#endif /* CONFIG_BT_SMP */ + .released = bt_att_released, +#if defined(CONFIG_BT_EATT) + .reconfigured = bt_att_reconfigured, +#endif /* CONFIG_BT_EATT */ + }; + + struct bt_att_chan *chan; + + list_for_every_container(&att->chans, chan, node) + { + if (chan->att == att) + { + quota++; + } + + if (quota == ATT_CHAN_MAX) + { + LOG_DBG("Maximum number of channels reached: %d", quota); + return NULL; + } + } + + if (k_mem_slab_alloc(&chan_slab, (void **)&chan, 0)) + { + LOG_WRN("No available ATT channel for conn %p", att->conn); + return NULL; + } + + (void)memset(chan, 0, sizeof(*chan)); + chan->chan.chan.ops = &ops; + k_fifo_init(&chan->tx_queue); + atomic_set(chan->flags, flags); + chan->att = att; + att_chan_attach(att, chan); + + if (bt_att_is_enhanced(chan)) + { + /* EATT: The MTU will be sent in the ECRED conn req/rsp PDU. The + * TX MTU is received on L2CAP-level. + */ + + chan->chan.rx.mtu = BT_LOCAL_ATT_MTU_EATT; + } + else + { + /* UATT: L2CAP Basic is not able to communicate the L2CAP MTU + * without help. ATT has to manage the MTU. The initial MTU is defined + * by spec. + */ + + chan->chan.tx.mtu = BT_ATT_DEFAULT_LE_MTU; + chan->chan.rx.mtu = BT_ATT_DEFAULT_LE_MTU; + } + + return chan; +} + +#if defined(CONFIG_BT_EATT) +size_t bt_eatt_count(struct bt_conn *conn) +{ + struct bt_att *att; + struct bt_att_chan *chan; + size_t eatt_count = 0; + + if (!conn) + { + return 0; + } + + att = att_get(conn); + if (!att) + { + return 0; + } + + list_for_every_container(&att->chans, chan, node) + { + if (bt_att_is_enhanced(chan) && atomic_test_bit(chan->flags, + ATT_CONNECTED)) + { + eatt_count++; + } + } + + return eatt_count; +} + +static void att_enhanced_connection_work_handler(struct k_work *work) +{ + const struct k_work_delayable *dwork = k_work_delayable_from_work(work); + const struct bt_att *att = CONTAINER_OF(dwork, struct bt_att, + eatt.connection_work); + const int err = bt_eatt_connect(att->conn, att->eatt.chans_to_connect); + + if (err == -ENOMEM) + { + LOG_DBG("Failed to connect %d EATT channels, central has probably " + "already established some.", + att->eatt.chans_to_connect); + } + else if (err < 0) + { + LOG_WRN("Failed to connect %d EATT channels (err: %d)", + att->eatt.chans_to_connect, err); + } +} +#endif /* CONFIG_BT_EATT */ + +static int bt_att_accept(struct bt_conn *conn, struct bt_l2cap_chan **ch) +{ + struct bt_att *att; + struct bt_att_chan *chan; + + LOG_DBG("conn %p handle %u", conn, conn->handle); + + if (k_mem_slab_alloc(&att_slab, (void **)&att, 0)) + { + LOG_ERR("No available ATT context for conn %p", conn); + return -ENOMEM; + } + + att_handle_rsp_thread = nxsched_gettid(); + + (void)memset(att, 0, sizeof(*att)); + att->conn = conn; + list_initialize(&att->reqs); + list_initialize(&att->chans); + +#if defined(CONFIG_BT_EATT) + k_work_init_delayable(&att->eatt.connection_work, + att_enhanced_connection_work_handler); +#endif /* CONFIG_BT_EATT */ + + chan = att_chan_new(att, 0); + if (!chan) + { + k_mem_slab_free(&att_slab, (void *)att); + return -ENOMEM; + } + + *ch = &chan->chan.chan; + + return 0; +} + +/* The L2CAP channel section is sorted lexicographically. Make sure that ATT + * fixed channel will be placed as the last one to ensure that SMP channel is + * properly initialized before bt_att_connected tries to send security + * request. + */ + +BT_L2CAP_FIXED_CHANNEL_DEFINE(z_att_fixed_chan) = +{ + .cid = BT_L2CAP_CID_ATT, + .accept = bt_att_accept, +}; + +#if defined(CONFIG_BT_EATT) +static clock_t credit_based_connection_delay(struct bt_conn *conn) +{ + /* 5.3 Vol 3, Part G, Section 5.4 L2CAP COLLISION MITIGATION + * ... In this situation, the Central may retry immediately but the + * Peripheral shall wait a minimum of 100 ms before retrying; on LE + * connections, the Peripheral shall wait at least 2 * + * (connPeripheralLatency + 1) * connInterval if that is longer. + */ + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && conn->role == BT_CONN_ROLE_CENTRAL) + { + return 0; + } + else if (IS_ENABLED(CONFIG_BT_PERIPHERAL)) + { + uint8_t random; + int err; + + err = bt_rand(&random, sizeof(random)); + if (err) + { + random = 0; + } + + const uint8_t rand_delay = + random & 0x7; /* Small random delay for IOP */ + + /* The maximum value of (latency + 1) * 2 multiplied with the + * maximum connection interval has a maximum value of 4000000000 which + * can be stored in 32-bits, so this won't result in an overflow + */ + + const uint32_t calculated_delay_us = + 2 * (conn->le.latency + 1) * conn->le.interval_us; + const uint32_t calculated_delay_ms = + calculated_delay_us / USEC_PER_MSEC; + + return MSEC2TICK(MAX(100, calculated_delay_ms + rand_delay)); + } + + /* Must be either central or peripheral */ + + DEBUGASSERT(false); + CODE_UNREACHABLE; +} + +static int att_schedule_eatt_connect(struct bt_conn *conn, + uint8_t chans_to_connect) +{ + struct bt_att *att = att_get(conn); + + if (!att) + { + return -ENOTCONN; + } + + att->eatt.chans_to_connect = chans_to_connect; + + return bt_work_reschedule(&att->eatt.connection_work, + credit_based_connection_delay(conn)); +} + +static void handle_potential_collision(struct bt_att *att) +{ + DEBUGASSERT(att); + + int err; + size_t to_connect = att->eatt.prev_conn_req_missing_chans; + + if (att->eatt.prev_conn_rsp_result == BT_L2CAP_LE_ERR_NO_RESOURCES && + att->eatt.prev_conn_req_result == BT_L2CAP_LE_ERR_NO_RESOURCES) + { + LOG_DBG("Credit based connection request collision detected"); + + /* Reset to not keep retrying on repeated failures */ + + att->eatt.prev_conn_rsp_result = 0; + att->eatt.prev_conn_req_result = 0; + att->eatt.prev_conn_req_missing_chans = 0; + + if (to_connect == 0) + { + return; + } + + err = att_schedule_eatt_connect(att->conn, to_connect); + if (err < 0) + { + LOG_ERR("Failed to schedule EATT connection retry (err: %d)", err); + } + } +} + +static void ecred_connect_req_cb(struct bt_conn *conn, + uint16_t result, uint16_t psm) +{ + struct bt_att *att = att_get(conn); + + if (!att) + { + return; + } + + if (psm != BT_EATT_PSM) + { + /* Collision mitigation is only a requirement on the EATT PSM */ + + return; + } + + att->eatt.prev_conn_rsp_result = result; + + handle_potential_collision(att); +} + +static void ecred_connect_rsp_cb(struct bt_conn *conn, + uint16_t result, + uint8_t attempted_to_connect, + uint8_t succeeded_to_connect, + uint16_t psm) +{ + struct bt_att *att = att_get(conn); + + if (!att) + { + return; + } + + if (psm != BT_EATT_PSM) + { + /* Collision mitigation is only a requirement on the EATT PSM */ + + return; + } + + att->eatt.prev_conn_req_result = result; + att->eatt.prev_conn_req_missing_chans = + attempted_to_connect - succeeded_to_connect; + + handle_potential_collision(att); +} + +int bt_eatt_connect(struct bt_conn *conn, size_t num_channels) +{ + struct bt_att_chan *att_chan; + struct bt_att *att; + struct bt_l2cap_chan *chan[CONFIG_BT_EATT_MAX + 1] = + { + }; + + size_t offset = 0; + size_t i = 0; + int err; + + if (!conn) + { + return -EINVAL; + } + + /* Check the encryption level for EATT */ + + if (bt_conn_get_security(conn) < BT_SECURITY_L2) + { + /* Vol 3, Part G, Section 5.3.2 Channel Requirements states: + * The channel shall be encrypted. + */ + + return -EPERM; + } + + if (num_channels > CONFIG_BT_EATT_MAX || num_channels == 0) + { + return -EINVAL; + } + + att_chan = att_get_fixed_chan(conn); + att = att_chan->att; + + while (num_channels--) + { + att_chan = att_chan_new(att, BIT(ATT_ENHANCED)); + if (!att_chan) + { + break; + } + + chan[i] = &att_chan->chan.chan; + i++; + } + + if (!i) + { + return -ENOMEM; + } + + while (offset < i) + { + /* bt_l2cap_ecred_chan_connect() uses the first + * BT_L2CAP_ECRED_CHAN_MAX_PER_REQ elements of the array or until a + * null-terminator is reached. + */ + + err = bt_l2cap_ecred_chan_connect(conn, &chan[offset], BT_EATT_PSM); + if (err < 0) + { + return err; + } + + offset += BT_L2CAP_ECRED_CHAN_MAX_PER_REQ; + } + + return 0; +} + +#if defined(CONFIG_BT_EATT_AUTO_CONNECT) +static void eatt_auto_connect(struct bt_conn *conn, + bt_security_t level, enum bt_security_err err) +{ + int eatt_err; + + if (!bt_conn_is_le(conn) || (err != 0) || level < BT_SECURITY_L2 || + !bt_att_fixed_chan_only(conn)) + { + return; + } + + eatt_err = att_schedule_eatt_connect(conn, CONFIG_BT_EATT_MAX); + if (eatt_err < 0) + { + LOG_WRN("Automatic creation of EATT bearers failed on " + "connection %s with error %d", + bt_conn_dst_str(conn), eatt_err); + } +} + +BT_CONN_CB_DEFINE(conn_callbacks) = +{ + .security_changed = eatt_auto_connect, +}; + +#endif /* CONFIG_BT_EATT_AUTO_CONNECT */ + +int bt_eatt_disconnect(struct bt_conn *conn) +{ + struct bt_att_chan *chan; + struct bt_att *att; + int err = -ENOTCONN; + + if (!conn) + { + return -EINVAL; + } + + chan = att_get_fixed_chan(conn); + att = chan->att; + + list_for_every_container(&att->chans, chan, node) + { + if (bt_att_is_enhanced(chan)) + { + err = bt_l2cap_chan_disconnect(&chan->chan.chan); + } + } + + return err; +} + +#if defined(CONFIG_BT_TESTING) +int bt_eatt_disconnect_one(struct bt_conn *conn) +{ + struct bt_att *att; + struct bt_att_chan *chan; + + if (!conn) + { + return -EINVAL; + } + + chan = att_get_fixed_chan(conn); + att = chan->att; + + list_for_every_container(&att->chans, chan, node) + { + if (bt_att_is_enhanced(chan)) + { + return bt_l2cap_chan_disconnect(&chan->chan.chan); + } + } + + return -ENOTCONN; +} + +int bt_eatt_reconfigure(struct bt_conn *conn, uint16_t mtu) +{ + struct bt_att_chan *att_chan = att_get_fixed_chan(conn); + struct bt_att *att = att_chan->att; + struct bt_l2cap_chan *chans[CONFIG_BT_EATT_MAX + 1] = + { + }; + + size_t offset = 0; + size_t i = 0; + int err; + + list_for_every_container(&att->chans, att_chan, node) + { + if (bt_att_is_enhanced(att_chan)) + { + chans[i] = &att_chan->chan.chan; + i++; + } + } + + while (offset < i) + { + /* bt_l2cap_ecred_chan_reconfigure() uses the first + * BT_L2CAP_ECRED_CHAN_MAX_PER_REQ elements of the array or until a + * null-terminator is reached. + */ + + err = bt_l2cap_ecred_chan_reconfigure(&chans[offset], mtu); + if (err < 0) + { + return err; + } + + offset += BT_L2CAP_ECRED_CHAN_MAX_PER_REQ; + } + + return 0; +} +#endif /* CONFIG_BT_TESTING */ +#endif /* CONFIG_BT_EATT */ + +static int bt_eatt_accept( + struct bt_conn *conn, struct bt_l2cap_server *server, + struct bt_l2cap_chan **chan) +{ + struct bt_att_chan *att_chan = att_get_fixed_chan(conn); + struct bt_att *att = att_chan->att; + + LOG_DBG("conn %p handle %u", conn, conn->handle); + + att_chan = att_chan_new(att, BIT(ATT_ENHANCED)); + if (att_chan) + { + *chan = &att_chan->chan.chan; + return 0; + } + + return -ENOMEM; +} + +static void bt_eatt_init(void) +{ + int err; + static struct bt_l2cap_server eatt_l2cap = + { + .psm = BT_EATT_PSM, + .sec_level = BT_SECURITY_L2, + .accept = bt_eatt_accept, + }; + + struct bt_l2cap_server *registered_server; + + LOG_DBG(""); + + /* Check if eatt_l2cap server has already been registered. */ + + registered_server = bt_l2cap_server_lookup_psm(eatt_l2cap.psm); + if (registered_server != &eatt_l2cap) + { + err = bt_l2cap_server_register(&eatt_l2cap); + if (err < 0) + { + LOG_ERR("EATT Server registration failed %d", err); + } + } + +#if defined(CONFIG_BT_EATT) + static const struct bt_l2cap_ecred_cb cb = + { + .ecred_conn_rsp = ecred_connect_rsp_cb, + .ecred_conn_req = ecred_connect_req_cb, + }; + + bt_l2cap_register_ecred_cb(&cb); +#endif /* CONFIG_BT_EATT */ +} + +void bt_att_init(void) +{ + bt_gatt_init(); + + if (IS_ENABLED(CONFIG_BT_EATT)) + { + bt_eatt_init(); + } +} + +uint16_t bt_att_get_mtu(struct bt_conn *conn) +{ + struct bt_att_chan *chan; + struct bt_att_chan *tmp; + struct bt_att *att; + uint16_t mtu = 0; + + att = att_get(conn); + if (!att) + { + return 0; + } + + list_for_every_container_safe(&att->chans, chan, tmp, node) + { + if (bt_att_mtu(chan) > mtu) + { + mtu = bt_att_mtu(chan); + } + } + + return mtu; +} + +uint16_t bt_att_get_uatt_mtu(struct bt_conn *conn) +{ + struct bt_att_chan *chan; + struct bt_att_chan *tmp; + struct bt_att *att; + + att = att_get(conn); + if (!att) + { + return 0; + } + + list_for_every_container_safe(&att->chans, chan, tmp, node) + { + if (!bt_att_is_enhanced(chan)) + { + return bt_att_mtu(chan); + } + } + + LOG_WRN("No UATT channel found in %p", conn); + + return 0; +} + +int bt_att_get_max_notify_size(struct bt_conn *conn, + enum bt_att_chan_opt chan_opt) +{ + /* Opcode (1 octet) + attribute handle (2 octets) */ + + const uint16_t att_ntf_hdr_size = + sizeof(struct bt_att_hdr) + sizeof(struct bt_att_notify); + struct bt_att_chan *chan; + struct bt_att_chan *tmp; + struct bt_att *att; + uint16_t mtu = 0; + + if (conn == NULL) + { + return -EINVAL; + } + + if (!bt_conn_is_le(conn)) + { + LOG_DBG("conn %p is not LE ACL", conn); + return -EINVAL; + } + + if (conn->state != BT_CONN_CONNECTED) + { + LOG_DBG("conn %p is not connected (%d)", conn, conn->state); + return -ENOTCONN; + } + + if ((!IS_ENABLED(CONFIG_BT_EATT) && + (chan_opt & BT_ATT_CHAN_OPT_ENHANCED_ONLY) != 0) || + !(chan_opt == BT_ATT_CHAN_OPT_NONE || + chan_opt == BT_ATT_CHAN_OPT_UNENHANCED_ONLY || + chan_opt == BT_ATT_CHAN_OPT_ENHANCED_ONLY)) + { + LOG_DBG("Invalid channel options %d for conn %p", chan_opt, conn); + return -EINVAL; + } + + att = att_get(conn); + if (att == NULL) + { + LOG_DBG("Could not get ATT for conn %p", conn); + return -ENOTCONN; + } + + list_for_every_container_safe(&att->chans, chan, tmp, node) + { + if (att_chan_matches_chan_opt(chan, chan_opt) && + (bt_att_mtu(chan) > mtu)) + { + mtu = bt_att_mtu(chan); + } + } + + if (mtu > att_ntf_hdr_size) + { + return (int)(mtu - att_ntf_hdr_size); + } + + return 0; +} + +static void att_chan_mtu_updated(struct bt_att_chan *updated_chan) +{ + struct bt_att *att = updated_chan->att; + struct bt_att_chan *chan; + struct bt_att_chan *tmp; + uint16_t max_tx = 0; + uint16_t max_rx = 0; + + /* Get maximum MTU's of other channels */ + + list_for_every_container_safe(&att->chans, chan, tmp, node) + { + if (chan == updated_chan) + { + continue; + } + + max_tx = MAX(max_tx, chan->chan.tx.mtu); + max_rx = MAX(max_rx, chan->chan.rx.mtu); + } + + /* If either maximum MTU has changed */ + + if ((updated_chan->chan.tx.mtu > max_tx) || + (updated_chan->chan.rx.mtu > max_rx)) + { + max_tx = MAX(max_tx, updated_chan->chan.tx.mtu); + max_rx = MAX(max_rx, updated_chan->chan.rx.mtu); + bt_gatt_att_max_mtu_changed(att->conn, max_tx, max_rx); + } +} + +struct bt_att_req *bt_att_req_alloc(clock_t timeout) +{ + struct bt_att_req *req = NULL; + pid_t current_thread = nxsched_gettid(); + + if (current_thread == att_handle_rsp_thread || + current_thread == k_work_queue_thread_get(&k_sys_work_q)) + { + /* bt_att_req are released by the att_handle_rsp_thread. + * A blocking allocation the same thread would cause a deadlock. + * + * Likewise, the system work queue is used for tx_processor(), which is + * needed to send currently pending requests. Blocking here in a + * situation where all the request holding a bt_att_req have yet to go + * through the tx_processor() would cause a deadlock. + * + * Note the att_handle_rsp_thread might be the system work queue thread + * or the dedicated BT RX WQ thread, depending on kconfig. + */ + + LOG_DBG("Timeout discarded. No blocking on BT RX WQ or SYS WQ " + "threads."); + timeout = 0; + } + + /* Reserve space for request */ + + if (k_mem_slab_alloc(&req_slab, (void **)&req, timeout)) + { + LOG_DBG("No space for req"); + return NULL; + } + + LOG_DBG("req %p", req); + + memset(req, 0, sizeof(*req)); + + return req; +} + +void bt_att_req_free(struct bt_att_req *req) +{ + LOG_DBG("req %p", req); + + net_buf_drop(&req->buf); + + k_mem_slab_free(&req_slab, (void *)req); +} + +int bt_att_send(struct bt_conn *conn, struct net_buf *buf) +{ + struct bt_att *att; + + DEBUGASSERT(conn); + DEBUGASSERT(buf); + + att = att_get(conn); + if (!att) + { + net_buf_unref(buf); + return -ENOTCONN; + } + + k_fifo_put(&att->tx_queue, buf); + att_send_process(att); + + return 0; +} + +int bt_att_req_send(struct bt_conn *conn, struct bt_att_req *req) +{ + struct bt_att *att; + + LOG_DBG("conn %p req %p", conn, req); + + DEBUGASSERT(conn); + DEBUGASSERT(req); + + /* att_handle_rsp() processes the request queue on the RX workqueue + * without the host lock, relying on cooperative scheduling: hold the + * scheduler lock as well so that it cannot preempt a preemptible caller + * mid-update. + */ + + bt_dev_lock(); + sched_lock(); + + att = att_get(conn); + if (!att) + { + sched_unlock(); + bt_dev_unlock(); + return -ENOTCONN; + } + + list_add_tail(&att->reqs, &req->node); + att_req_send_process(att); + + sched_unlock(); + bt_dev_unlock(); + + return 0; +} + +static bool bt_att_chan_req_cancel(struct bt_att_chan *chan, + struct bt_att_req *req) +{ + if (chan->req != req) + { + return false; + } + +#if defined(CONFIG_BT_ATT_RETRY_ON_SEC_ERR) + /* A request waiting for a security retry has already had its error + * response consumed and its timeout stopped, so neither a response nor a + * timeout is left to clear the placeholder: release the bearer now and let + * the next queued request use it. + */ + + if (req->retrying) + { + chan->req = NULL; + bt_att_req_free(req); + att_req_send_process(chan->att); + + return true; + } +#endif /* CONFIG_BT_ATT_RETRY_ON_SEC_ERR */ + + chan->req = &cancel; + + bt_att_req_free(req); + + return true; +} + +void bt_att_req_cancel(struct bt_conn *conn, struct bt_att_req *req) +{ + struct bt_att *att; + struct bt_att_chan *chan; + struct bt_att_chan *tmp; + + LOG_DBG("req %p", req); + + if (!conn || !req) + { + return; + } + + att = att_get(conn); + if (!att) + { + return; + } + + list_for_every_container_safe(&att->chans, chan, tmp, node) + { + /* Check if request is outstanding */ + + if (bt_att_chan_req_cancel(chan, req)) + { + return; + } + } + + /* Remove request from the list */ + + list_find_and_remove(&att->reqs, &req->node); + + bt_att_req_free(req); +} + +struct bt_att_req *bt_att_find_req_by_user_data(struct bt_conn *conn, + const void *user_data) +{ + struct bt_att *att; + struct bt_att_chan *chan; + struct bt_att_req *req; + + if (user_data == NULL) + { + return NULL; + } + + att = att_get(conn); + if (!att) + { + return NULL; + } + + list_for_every_container(&att->chans, chan, node) + { + if (chan->req != NULL && chan->req->user_data == user_data) + { + return chan->req; + } + } + + list_for_every_container(&att->reqs, req, node) + { + if (req->user_data == user_data) + { + return req; + } + } + + return NULL; +} + +bool bt_att_fixed_chan_only(struct bt_conn *conn) +{ +#if defined(CONFIG_BT_EATT) + return bt_eatt_count(conn) == 0; +#else + return true; +#endif /* CONFIG_BT_EATT */ +} + +void bt_att_clear_out_of_sync_sent(struct bt_conn *conn) +{ + struct bt_att *att = att_get(conn); + struct bt_att_chan *chan; + + if (!att) + { + return; + } + + list_for_every_container(&att->chans, chan, node) + { + atomic_clear_bit(chan->flags, ATT_OUT_OF_SYNC_SENT); + } +} + +bool bt_att_out_of_sync_sent_on_fixed(struct bt_conn *conn) +{ + struct bt_l2cap_chan *l2cap_chan; + struct bt_att_chan *att_chan; + + l2cap_chan = bt_l2cap_le_lookup_rx_cid(conn, BT_L2CAP_CID_ATT); + if (!l2cap_chan) + { + return false; + } + + att_chan = ATT_CHAN(l2cap_chan); + return atomic_test_bit(att_chan->flags, ATT_OUT_OF_SYNC_SENT); +} + +void bt_att_set_tx_meta_data( + struct net_buf *buf, bt_gatt_complete_func_t func, void *user_data, + enum bt_att_chan_opt chan_opt) +{ + struct bt_att_tx_meta_data *data = att_get_tx_meta_data(buf); + + data->func = func; + data->user_data = user_data; + data->attr_count = 1; + data->chan_opt = chan_opt; +} + +void bt_att_increment_tx_meta_data_attr_count(struct net_buf *buf, + uint16_t attr_count) +{ + struct bt_att_tx_meta_data *data = att_get_tx_meta_data(buf); + + data->attr_count += attr_count; +} + +bool bt_att_tx_meta_data_match( + const struct net_buf *buf, bt_gatt_complete_func_t func, + const void *user_data, enum bt_att_chan_opt chan_opt) +{ + const struct bt_att_tx_meta_data *meta = att_get_tx_meta_data(buf); + + return ((meta->func == func) && (meta->user_data == user_data) && + (meta->chan_opt == chan_opt)); +} + +bool bt_att_chan_opt_valid(struct bt_conn *conn, + enum bt_att_chan_opt chan_opt) +{ + if ((chan_opt & (BT_ATT_CHAN_OPT_ENHANCED_ONLY | + BT_ATT_CHAN_OPT_UNENHANCED_ONLY)) == + (BT_ATT_CHAN_OPT_ENHANCED_ONLY | BT_ATT_CHAN_OPT_UNENHANCED_ONLY)) + { + /* Enhanced and Unenhanced are mutually exclusive */ + + return false; + } + + /* Choosing EATT requires EATT channels connected and encryption enabled */ + + if (chan_opt & BT_ATT_CHAN_OPT_ENHANCED_ONLY) + { + return (bt_conn_get_security(conn) > BT_SECURITY_L1) && + !bt_att_fixed_chan_only(conn); + } + + return true; +} diff --git a/libs/zblue/host/att_internal.h b/libs/zblue/host/att_internal.h new file mode 100644 index 0000000000000..29aac9442010a --- /dev/null +++ b/libs/zblue/host/att_internal.h @@ -0,0 +1,425 @@ +/**************************************************************************** + * libs/zblue/host/att_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* att_internal.h - Attribute protocol handling */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define BT_EATT_PSM 0x27 +#define BT_ATT_DEFAULT_LE_MTU 23 +#define BT_ATT_TIMEOUT_SEC 30 +#define BT_ATT_TIMEOUT SEC2TICK(BT_ATT_TIMEOUT_SEC) + +/* Local ATT Rx MTU + * + * This is the local 'Client Rx MTU'/'Server Rx MTU'. Core v5.3 Vol 3 Part F + * 3.4.2.2 requires that they are equal. + * + * The local ATT Server Rx MTU is limited to BT_L2CAP_TX_MTU because the GATT + * long attribute read protocol (Core v5.3 Vol 3 Part G 4.8.3) treats the ATT + * MTU as a promise about the read size. This requires the server to + * negotiate the ATT_MTU down to what it is actually able to send. This will + * unfortunately also limit how much the client is allowed to send. + */ +#define BT_LOCAL_ATT_MTU_EATT MIN(BT_L2CAP_SDU_RX_MTU, BT_L2CAP_SDU_TX_MTU) +#define BT_LOCAL_ATT_MTU_UATT MIN(BT_L2CAP_RX_MTU, BT_L2CAP_TX_MTU) + +#define BT_ATT_BUF_SIZE MAX(BT_LOCAL_ATT_MTU_UATT, BT_LOCAL_ATT_MTU_EATT) + +struct bt_att_hdr +{ + uint8_t code; +} __packed; + +#define BT_ATT_OP_ERROR_RSP 0x01 +struct bt_att_error_rsp +{ + uint8_t request; + uint16_t handle; + uint8_t error; +} __packed; + +#define BT_ATT_OP_MTU_REQ 0x02 +struct bt_att_exchange_mtu_req +{ + uint16_t mtu; +} __packed; + +#define BT_ATT_OP_MTU_RSP 0x03 +struct bt_att_exchange_mtu_rsp +{ + uint16_t mtu; +} __packed; + +/* Find Information Request */ +#define BT_ATT_OP_FIND_INFO_REQ 0x04 +struct bt_att_find_info_req +{ + uint16_t start_handle; + uint16_t end_handle; +} __packed; + +/* Format field values for BT_ATT_OP_FIND_INFO_RSP */ +#define BT_ATT_INFO_16 0x01 +#define BT_ATT_INFO_128 0x02 + +struct bt_att_info_16 +{ + uint16_t handle; + uint16_t uuid; +} __packed; + +struct bt_att_info_128 +{ + uint16_t handle; + uint8_t uuid[16]; +} __packed; + +/* Find Information Response */ +#define BT_ATT_OP_FIND_INFO_RSP 0x05 +struct bt_att_find_info_rsp +{ + uint8_t format; + uint8_t info[]; +} __packed; + +/* Find By Type Value Request */ +#define BT_ATT_OP_FIND_TYPE_REQ 0x06 +struct bt_att_find_type_req +{ + uint16_t start_handle; + uint16_t end_handle; + uint16_t type; + uint8_t value[]; +} __packed; + +struct bt_att_handle_group +{ + uint16_t start_handle; + uint16_t end_handle; +} __packed; + +/* Find By Type Value Response */ +#define BT_ATT_OP_FIND_TYPE_RSP 0x07 +struct bt_att_find_type_rsp +{ + FLEXIBLE_ARRAY_DECLARE(struct bt_att_handle_group, list); +} __packed; + +/* Read By Type Request */ +#define BT_ATT_OP_READ_TYPE_REQ 0x08 +struct bt_att_read_type_req +{ + uint16_t start_handle; + uint16_t end_handle; + uint8_t uuid[]; +} __packed; + +struct bt_att_data +{ + uint16_t handle; + uint8_t value[]; +} __packed; + +/* Read By Type Response */ +#define BT_ATT_OP_READ_TYPE_RSP 0x09 +struct bt_att_read_type_rsp +{ + uint8_t len; + struct bt_att_data data[]; +} __packed; + +/* Read Request */ +#define BT_ATT_OP_READ_REQ 0x0a +struct bt_att_read_req +{ + uint16_t handle; +} __packed; + +/* Read Response */ +#define BT_ATT_OP_READ_RSP 0x0b +struct bt_att_read_rsp +{ + FLEXIBLE_ARRAY_DECLARE(uint8_t, value); +} __packed; + +/* Read Blob Request */ +#define BT_ATT_OP_READ_BLOB_REQ 0x0c +struct bt_att_read_blob_req +{ + uint16_t handle; + uint16_t offset; +} __packed; + +/* Read Blob Response */ +#define BT_ATT_OP_READ_BLOB_RSP 0x0d +struct bt_att_read_blob_rsp +{ + FLEXIBLE_ARRAY_DECLARE(uint8_t, value); +} __packed; + +/* Read Multiple Request */ +#define BT_ATT_READ_MULT_MIN_LEN_REQ 0x04 + +#define BT_ATT_OP_READ_MULT_REQ 0x0e +struct bt_att_read_mult_req +{ + FLEXIBLE_ARRAY_DECLARE(uint16_t, handles); +} __packed; + +/* Read Multiple Response */ +#define BT_ATT_OP_READ_MULT_RSP 0x0f +struct bt_att_read_mult_rsp +{ + FLEXIBLE_ARRAY_DECLARE(uint8_t, value); +} __packed; + +/* Read by Group Type Request */ +#define BT_ATT_OP_READ_GROUP_REQ 0x10 +struct bt_att_read_group_req +{ + uint16_t start_handle; + uint16_t end_handle; + uint8_t uuid[]; +} __packed; + +struct bt_att_group_data +{ + uint16_t start_handle; + uint16_t end_handle; + uint8_t value[]; +} __packed; + +/* Read by Group Type Response */ +#define BT_ATT_OP_READ_GROUP_RSP 0x11 +struct bt_att_read_group_rsp +{ + uint8_t len; + struct bt_att_group_data data[]; +} __packed; + +/* Write Request */ +#define BT_ATT_OP_WRITE_REQ 0x12 +struct bt_att_write_req +{ + uint16_t handle; + uint8_t value[]; +} __packed; + +/* Write Response */ +#define BT_ATT_OP_WRITE_RSP 0x13 + +/* Prepare Write Request */ +#define BT_ATT_OP_PREPARE_WRITE_REQ 0x16 +struct bt_att_prepare_write_req +{ + uint16_t handle; + uint16_t offset; + uint8_t value[]; +} __packed; + +/* Prepare Write Respond */ +#define BT_ATT_OP_PREPARE_WRITE_RSP 0x17 +struct bt_att_prepare_write_rsp +{ + uint16_t handle; + uint16_t offset; + uint8_t value[]; +} __packed; + +/* Execute Write Request */ +#define BT_ATT_FLAG_CANCEL 0x00 +#define BT_ATT_FLAG_EXEC 0x01 + +#define BT_ATT_OP_EXEC_WRITE_REQ 0x18 +struct bt_att_exec_write_req +{ + uint8_t flags; +} __packed; + +/* Execute Write Response */ +#define BT_ATT_OP_EXEC_WRITE_RSP 0x19 + +/* Handle Value Notification */ +#define BT_ATT_OP_NOTIFY 0x1b +struct bt_att_notify +{ + uint16_t handle; + uint8_t value[]; +} __packed; + +/* Handle Value Indication */ +#define BT_ATT_OP_INDICATE 0x1d +struct bt_att_indicate +{ + uint16_t handle; + uint8_t value[]; +} __packed; + +/* Handle Value Confirm */ +#define BT_ATT_OP_CONFIRM 0x1e + +struct bt_att_signature +{ + uint8_t value[12]; +} __packed; + +#define BT_ATT_OP_READ_MULT_VL_REQ 0x20 +struct bt_att_read_mult_vl_req +{ + FLEXIBLE_ARRAY_DECLARE(uint16_t, handles); +} __packed; + +/* Read Multiple Response */ +#define BT_ATT_OP_READ_MULT_VL_RSP 0x21 +struct bt_att_read_mult_vl_rsp +{ + uint16_t len; + uint8_t value[]; +} __packed; + +/* Handle Multiple Value Notification */ +#define BT_ATT_OP_NOTIFY_MULT 0x23 +struct bt_att_notify_mult +{ + uint16_t handle; + uint16_t len; + uint8_t value[]; +} __packed; + +/* Write Command */ +#define BT_ATT_OP_WRITE_CMD 0x52 +struct bt_att_write_cmd +{ + uint16_t handle; + uint8_t value[]; +} __packed; + +/* Signed Write Command */ +#define BT_ATT_OP_SIGNED_WRITE_CMD 0xd2 +struct bt_att_signed_write_cmd +{ + uint16_t handle; + uint8_t value[]; +} __packed; + +typedef void( + *bt_att_func_t)(struct bt_conn *conn, int err, const void *pdu, uint16_t + length, void *user_data); + +typedef int (*bt_att_encode_t)(struct net_buf *buf, + size_t len, void *user_data); + +/* ATT request context */ + +struct bt_att_req +{ + struct list_node node; + bt_att_func_t func; + struct net_buf *buf; +#if defined(CONFIG_BT_ATT_RETRY_ON_SEC_ERR) + bt_att_encode_t encode; + bool retrying; + uint8_t att_op; + size_t len; +#endif /* CONFIG_BT_ATT_RETRY_ON_SEC_ERR */ + void *user_data; +}; + +void bt_att_init(void); +uint16_t bt_att_get_mtu(struct bt_conn *conn); +uint16_t bt_att_get_uatt_mtu(struct bt_conn *conn); +struct net_buf *bt_att_create_pdu(struct bt_conn *conn, + uint8_t op, size_t len); + +/* Allocate a new request */ + +struct bt_att_req *bt_att_req_alloc(clock_t timeout); + +/* Free a request */ + +void bt_att_req_free(struct bt_att_req *req); + +/* Send ATT PDU over a connection */ + +int bt_att_send(struct bt_conn *conn, struct net_buf *buf); + +/* Send ATT Request over a connection */ + +int bt_att_req_send(struct bt_conn *conn, struct bt_att_req *req); + +/* Cancel ATT request */ + +void bt_att_req_cancel(struct bt_conn *conn, struct bt_att_req *req); + +/* Disconnect EATT channels */ + +int bt_eatt_disconnect(struct bt_conn *conn); + +/** @brief Find a pending ATT request by its user_data pointer. + * @param conn The connection the request was issued on. + * @param user_data The pointer value to look for. + * @return The found request. NULL if not found. + */ + +struct bt_att_req *bt_att_find_req_by_user_data(struct bt_conn *conn, + const void *user_data); + +/* Checks if only the fixed ATT channel is connected */ + +bool bt_att_fixed_chan_only(struct bt_conn *conn); + +/* Clear the out of sync flag on all channels */ + +void bt_att_clear_out_of_sync_sent(struct bt_conn *conn); + +/* Check if BT_ATT_ERR_DB_OUT_OF_SYNC has been sent on the fixed ATT channel + */ + +bool bt_att_out_of_sync_sent_on_fixed(struct bt_conn *conn); + +typedef void (*bt_gatt_complete_func_t)(struct bt_conn *conn, + void *user_data); +void bt_att_set_tx_meta_data( + struct net_buf *buf, bt_gatt_complete_func_t func, void *user_data, + enum bt_att_chan_opt chan_opt); +void bt_att_increment_tx_meta_data_attr_count(struct net_buf *buf, + uint16_t attr_count); + +bool bt_att_tx_meta_data_match( + const struct net_buf *buf, bt_gatt_complete_func_t func, + const void *user_data, enum bt_att_chan_opt chan_opt); + +#if defined(CONFIG_BT_EATT) +#define BT_ATT_CHAN_OPT(_params) (_params)->chan_opt +#else +#define BT_ATT_CHAN_OPT(_params) BT_ATT_CHAN_OPT_UNENHANCED_ONLY +#endif /* CONFIG_BT_EATT */ + +bool bt_att_chan_opt_valid(struct bt_conn *conn, + enum bt_att_chan_opt chan_opt); + +void bt_gatt_req_set_mtu(struct bt_att_req *req, uint16_t mtu); diff --git a/libs/zblue/host/buf.c b/libs/zblue/host/buf.c new file mode 100644 index 0000000000000..3ba968499af4b --- /dev/null +++ b/libs/zblue/host/buf.c @@ -0,0 +1,317 @@ +/**************************************************************************** + * libs/zblue/host/buf.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Nordic Semiconductor ASA + * Copyright (c) 2015 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "buf_view.h" +#include "common/hci_common_internal.h" +#include "conn_internal.h" +#include "hci_core.h" +#include "iso_internal.h" + +LOG_MODULE_REGISTER(bt_buf, CONFIG_BT_LOG_LEVEL); + +/* Events have a length field of 1 byte. This size fits all events. + * + * It's true that we don't put all kinds of events there (yet). However, the + * command complete event has an arbitrary payload, depending on opcode. + */ +#define SYNC_EVT_SIZE (BT_BUF_RESERVE + BT_HCI_EVT_HDR_SIZE + 255) + +static void *buf_rx_freed_cb; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void buf_rx_freed_notify(enum bt_buf_type mask) +{ + bt_buf_rx_freed_cb_t cb; + bool in_isr = up_interrupt_context(); + + if (!in_isr) + { + sched_lock(); + } + + cb = (bt_buf_rx_freed_cb_t)atomic_ptr_read(&buf_rx_freed_cb); + + if (cb != NULL) + { + cb(mask); + } + + if (!in_isr) + { + sched_unlock(); + } +} + +#if defined(CONFIG_BT_ISO_RX) +static void iso_rx_freed_cb(void) +{ + buf_rx_freed_notify(BT_BUF_ISO_IN); +} +#endif + +/* Pool for RX HCI buffers that are always freed by `bt_recv` + * before it returns. + * + * A singleton buffer shall be sufficient for correct operation. The buffer + * count may be increased as an optimization to allow the HCI transport to + * fill buffers in parallel with `bt_recv` consuming them. + */ + +NET_BUF_POOL_FIXED_DEFINE(sync_evt_pool, + CONFIG_BT_BUF_SYNC_EVT_POOL_COUNT, + SYNC_EVT_SIZE, + 0, + NULL); + +NET_BUF_POOL_FIXED_DEFINE(discardable_pool, + CONFIG_BT_BUF_EVT_DISCARDABLE_COUNT, + + BT_BUF_EVT_SIZE(CONFIG_BT_BUF_EVT_DISCARDABLE_SIZE), + 0, + NULL); + +#if defined(CONFIG_BT_HCI_ACL_FLOW_CONTROL) +static void acl_in_pool_destroy(struct net_buf *buf) +{ + bt_hci_host_num_completed_packets(buf); + buf_rx_freed_notify(BT_BUF_ACL_IN); +} + +static void evt_pool_destroy(struct net_buf *buf) +{ + net_buf_destroy(buf); + buf_rx_freed_notify(BT_BUF_EVT); +} + +NET_BUF_POOL_DEFINE(acl_in_pool, + (BT_BUF_ACL_RX_COUNT_EXTRA + BT_BUF_HCI_ACL_RX_COUNT), + BT_BUF_ACL_SIZE(CONFIG_BT_BUF_ACL_RX_SIZE), + sizeof(struct bt_conn_rx), + acl_in_pool_destroy); + +NET_BUF_POOL_FIXED_DEFINE(evt_pool, + CONFIG_BT_BUF_EVT_RX_COUNT, + BT_BUF_EVT_RX_SIZE, + 0, + + evt_pool_destroy); +#else +static void hci_rx_pool_destroy(struct net_buf *buf) +{ + net_buf_destroy(buf); + + /* When ACL Flow Control is disabled, a single pool is used for events and + * acl data. Therefore the callback will always notify about both types of + * buffers, BT_BUF_EVT and BT_BUF_ACL_IN. + */ + + buf_rx_freed_notify(BT_BUF_EVT | BT_BUF_ACL_IN); +} + +NET_BUF_POOL_FIXED_DEFINE(hci_rx_pool, + BT_BUF_RX_COUNT, + BT_BUF_RX_SIZE, + sizeof(struct bt_conn_rx), + hci_rx_pool_destroy); +#endif /* CONFIG_BT_HCI_ACL_FLOW_CONTROL */ + +struct net_buf *bt_buf_get_rx(enum bt_buf_type type, clock_t timeout) +{ + struct net_buf *buf; + + DEBUGASSERT(type == BT_BUF_EVT + || type == BT_BUF_ACL_IN + || type == BT_BUF_ISO_IN); + + if (IS_ENABLED(CONFIG_BT_ISO_RX) && type == BT_BUF_ISO_IN) + { + return bt_iso_get_rx(timeout); + } + +#if defined(CONFIG_BT_HCI_ACL_FLOW_CONTROL) + if (type == BT_BUF_EVT) + { + buf = net_buf_alloc(&evt_pool, timeout); + } + else + { + buf = net_buf_alloc(&acl_in_pool, timeout); + } +#else + buf = net_buf_alloc(&hci_rx_pool, timeout); +#endif + if (buf) + { + net_buf_add_u8(buf, bt_buf_type_to_h4(type)); + } + + return buf; +} + +void bt_buf_rx_freed_cb_set(bt_buf_rx_freed_cb_t cb) +{ + atomic_ptr_xchg(&buf_rx_freed_cb, (void *)cb); + +#if defined(CONFIG_BT_ISO_RX) + bt_iso_buf_rx_freed_cb_set(cb != NULL ? iso_rx_freed_cb : NULL); +#endif +} + +struct net_buf *bt_buf_get_evt(uint8_t evt, + bool discardable, clock_t timeout) +{ + struct net_buf *buf; + + switch (evt) + { +#if defined(CONFIG_BT_CONN) || defined(CONFIG_BT_ISO) + case BT_HCI_EVT_NUM_COMPLETED_PACKETS: +#endif /* CONFIG_BT_CONN || CONFIG_BT_ISO */ + case BT_HCI_EVT_CMD_STATUS: + case BT_HCI_EVT_CMD_COMPLETE: + buf = net_buf_alloc(&sync_evt_pool, timeout); + break; + default: + if (discardable) + { + /* Discardable, decided in Host-side HCI Transport driver. */ + + buf = net_buf_alloc(&discardable_pool, timeout); + } + else + { + return bt_buf_get_rx(BT_BUF_EVT, timeout); + } + } + + if (buf) + { + net_buf_add_u8(buf, BT_HCI_H4_EVT); + } + + return buf; +} + +#ifdef ZTEST_UNITTEST +#if defined(CONFIG_BT_HCI_ACL_FLOW_CONTROL) +struct net_buf_pool *bt_buf_get_evt_pool(void) +{ + return &evt_pool; +} + +struct net_buf_pool *bt_buf_get_acl_in_pool(void) +{ + return &acl_in_pool; +} +#else +struct net_buf_pool *bt_buf_get_hci_rx_pool(void) +{ + return &hci_rx_pool; +} +#endif /* CONFIG_BT_HCI_ACL_FLOW_CONTROL */ + +#if defined(CONFIG_BT_CONN) || defined(CONFIG_BT_ISO) +struct net_buf_pool *bt_buf_get_num_complete_pool(void) +{ + return &sync_evt_pool; +} +#endif /* CONFIG_BT_CONN || CONFIG_BT_ISO */ +#endif /* ZTEST_UNITTEST */ + +struct net_buf *bt_buf_make_view( + struct net_buf *view, struct net_buf *parent, size_t len, + struct bt_buf_view_meta *meta) +{ + DEBUGASSERT(len); + DEBUGASSERT(view); + /* The whole point of this API is to allow prepending data. If the + * headroom is 0, that will not happen. + */ + + DEBUGASSERT(net_buf_headroom(parent) > 0); + + DEBUGASSERT(!bt_buf_has_view(parent)); + + LOG_DBG("make-view %p viewsize %zu meta %p", view, len, meta); + + net_buf_simple_clone(&parent->b, &view->b); + view->size = net_buf_headroom(parent) + len; + view->len = len; + view->flags = NET_BUF_EXTERNAL_DATA; + + /* we have a view, eat `len`'s worth of data from the parent */ + + (void)net_buf_pull(parent, len); + + meta->backup.data = parent->data; + parent->data = NULL; + + meta->backup.size = parent->size; + parent->size = 0; + + /* The ref to `parent` is moved in by passing `parent` as argument. */ + + /* save backup & "clip" the buffer so the next `make_view` will fail */ + + meta->parent = parent; + parent = NULL; + + return view; +} + +void bt_buf_destroy_view(struct net_buf *view, struct bt_buf_view_meta *meta) +{ + LOG_DBG("destroy-view %p meta %p", view, meta); + DEBUGASSERT(meta->parent); + + /* "unclip" the parent buf */ + + meta->parent->data = meta->backup.data; + meta->parent->size = meta->backup.size; + + net_buf_unref(meta->parent); + + memset(meta, 0, sizeof(*meta)); + net_buf_destroy(view); +} + +bool bt_buf_has_view(const struct net_buf *parent) +{ + /* This is enforced by `make_view`. see comment there. */ + + return parent->size == 0 && parent->data == NULL; +} diff --git a/libs/zblue/host/buf_view.h b/libs/zblue/host/buf_view.h new file mode 100644 index 0000000000000..b2a98f8d49b29 --- /dev/null +++ b/libs/zblue/host/buf_view.h @@ -0,0 +1,102 @@ +/**************************************************************************** + * libs/zblue/host/buf_view.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Bluetooth "view" buffer abstraction + */ + +/* Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_SUBSYS_BLUETOOTH_HOST_BUF_VIEW_H_ +#define ZEPHYR_SUBSYS_BLUETOOTH_HOST_BUF_VIEW_H_ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +struct bt_buf_view_meta +{ + struct net_buf *parent; + + /* saves the data pointers while the parent buffer is locked. */ + + struct net_buf_simple backup; +}; + +/** @internal + * + * @brief Create a "view" or "window" into an existing buffer. + * - enforces one active view at a time per-buffer + * -> this restriction enables prepending data (ie. for headers) + * - forbids appending data to the view + * - pulls the size of the view from said buffer. + * + * The "virtual buffer" that is generated has to be allocated from a buffer + * pool. This is to allow refcounting and attaching a destroy callback. The + * configured size of the buffers in that pool should be zero-length. + * + * The user-data size is application-dependent, but should be minimized to + * save memory. user_data is not used by the view API. + * + * The view mechanism needs to store extra metadata in order to unlock the + * original buffer when the view is destroyed. + * + * The storage and allocation of the view buf pool and the view metadata is + * the application's responsibility. + * + * @note The `headroom` param is only used for DEBUGASSERT(). The idea is + * that it's easier to debug a headroom assert failure at allocation time, + * rather than later down the line when a lower layer tries to add its + * headers and fails. + * + * @param view Uninitialized "View" buffer + * @param parent Buffer data is pulled from into `view` + * @param len Amount to pull + * @param meta Uninitialized metadata storage + * + * @return view if the operation was successful. NULL on error. + */ + +struct net_buf *bt_buf_make_view( + struct net_buf *view, struct net_buf *parent, size_t len, + struct bt_buf_view_meta *meta); + +/** @internal + * + * @brief Check if `parent` has view. + * + * If `parent` has been passed to @ref bt_buf_make_view() and the resulting + * view buffer has not been destroyed. + */ + +bool bt_buf_has_view(const struct net_buf *parent); + +/** @internal + * + * @brief Destroy the view buffer + * + * Equivalent of @ref net_buf_destroy. It is mandatory to call this from the + * view pool's `destroy` callback. + * + * This frees the parent buffer, and allows calling @ref bt_buf_make_view + * again. The metadata is also freed for reuse. + * + * @param view View to destroy + * @param meta Meta that was given to @ref bt_buf_make_view + */ + +void bt_buf_destroy_view(struct net_buf *view, + struct bt_buf_view_meta *meta); + +#endif /* ZEPHYR_SUBSYS_BLUETOOTH_HOST_BUF_VIEW_H_ */ diff --git a/libs/zblue/host/classic/CMakeLists.txt b/libs/zblue/host/classic/CMakeLists.txt new file mode 100644 index 0000000000000..f5c251d3834d3 --- /dev/null +++ b/libs/zblue/host/classic/CMakeLists.txt @@ -0,0 +1,26 @@ +# ############################################################################## +# libs/zblue/host/classic/CMakeLists.txt +# +# Author: chao an +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +# Classic BR/EDR sources are added by libs/zblue/host/CMakeLists.txt so that the +# NuttX build has one authoritative host source list. diff --git a/libs/zblue/host/classic/Kconfig b/libs/zblue/host/classic/Kconfig new file mode 100644 index 0000000000000..e5e90bb1c2542 --- /dev/null +++ b/libs/zblue/host/classic/Kconfig @@ -0,0 +1,888 @@ +############################################################################ +# libs/zblue/host/classic/Kconfig +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth Classic configuration options + +# Copyright (c) 2016-2020 Nordic Semiconductor ASA +# Copyright (c) 2015-2016 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +menu "Bluetooth Classic Options" + +config BT_CLASSIC + bool "Bluetooth BR/EDR support [EXPERIMENTAL]" + depends on BT_HCI_HOST + select BT_PERIPHERAL + select BT_CENTRAL + select BT_SMP + select BT_L2CAP_DYNAMIC_CHANNEL + select CRC + help + This option enables Bluetooth BR/EDR support + +if BT_CLASSIC +config BT_LIMITED_DISCOVERABLE_DURATION + int "Maximum duration that a device is in limited discoverable mode in seconds" + default 60 + range 31 60 + help + This option sets the maximum duration that a device is in limited discoverable mode in + seconds. + +config BT_BR_MIN_ENC_KEY_SIZE + int + prompt "Minimum encryption key size accepted in octets" if !BT_SMP_SC_ONLY + default BT_SMP_MIN_ENC_KEY_SIZE + range 7 16 + help + This option sets the minimum encryption key size accepted during pairing + for classic. + +config BT_MAX_SCO_CONN + int "Maximum number of simultaneous SCO connections" + default 1 + range 1 3 + help + Maximum number of simultaneous Bluetooth synchronous connections + supported. The minimum (and default) number is 1. + +config BT_ACCEPT_CONN_AS_CENTRAL + bool "Accept a new incoming ACL connection request as central role" + help + By default, the device will accept the ACL connection request as the peripheral role. + If the option is enabled, the device will accept the ACL connection request as the + central role. Normally, the HCI Role Change event will be generated after the HCI command + Accept Connection Request is succeeded. + The kconfig option only works when calling function bt_br_set_discoverable to enable + connectable mode with a NULL callback parameter. + +config BT_L2CAP_RX_FLUSH_TO + hex "Minimum Bluetooth L2CAP RX flush Timeout accepted in milliseconds" + default 0x0001 if BT_A2DP_SINK + default 0xFFFF + range 0x0001 0xFFFF + help + This option configures minimum Bluetooth L2CAP RX flush timeout accepted during + processing peer's l2cap flush timeout configuration. + The flush timeout applies to all channels on the same ACL logical transport but + may be overridden on a packet by packet basis by marking individual L2CAP packets + as non-automatically-flushable via the Packet_Boundary_Flag in the HCI ACL Data packet. + +config BT_L2CAP_CONNLESS + bool "Bluetooth L2CAP connectionless data reception [EXPERIMENTAL]" + help + This option enables Bluetooth L2CAP connectionless data reception + +config BT_L2CAP_QOS + bool "Bluetooth L2CAP QoS Option [EXPERIMENTAL]" + help + This option enables negotiation of the Bluetooth L2CAP QoS + configuration option. Only the Best Effort service type is supported, + a peer requesting the Guaranteed service type is counter-proposed + Best Effort. Without this option a QoS option in a Configuration + Request is answered with Unknown Option. + +config BT_L2CAP_RET + bool "Bluetooth L2CAP retransmission mode [EXPERIMENTAL]" + help + This option enables Bluetooth L2CAP retransmission mode + +config BT_L2CAP_FC + bool "Bluetooth L2CAP flow control mode [EXPERIMENTAL]" + help + This option enables Bluetooth L2CAP flow control mode + +config BT_L2CAP_ENH_RET + bool "Bluetooth L2CAP enhance retransmission [EXPERIMENTAL]" + help + This option enables Bluetooth L2CAP enhance retransmission + +config BT_L2CAP_STREAM + bool "Bluetooth L2CAP streaming mode [EXPERIMENTAL]" + help + This option enables Bluetooth L2CAP streaming mode + +config BT_L2CAP_RET_FC + def_bool BT_L2CAP_RET || BT_L2CAP_FC || BT_L2CAP_ENH_RET || BT_L2CAP_STREAM + +if BT_L2CAP_RET_FC +config BT_L2CAP_FCS + bool "Bluetooth L2CAP FCS Option [EXPERIMENTAL]" + depends on BT_L2CAP_STREAM || BT_L2CAP_ENH_RET + help + This option enables Bluetooth L2CAP FCS Option + +config BT_L2CAP_EXT_WIN_SIZE + bool "Bluetooth L2CAP Extended window size Option [EXPERIMENTAL]" + depends on BT_L2CAP_STREAM || BT_L2CAP_ENH_RET + help + This option enables Bluetooth L2CAP Extended window size Option + +config BT_L2CAP_MPS + int "Bluetooth L2CAP MPS for retransmission and Flow control" + default 48 + range 48 BT_L2CAP_TX_MTU + help + Bluetooth L2CAP MPS for retransmission and Flow control + +config BT_L2CAP_MAX_WINDOW_SIZE + int "Maximum Windows Size of Retransmission and Flow Control" + default 1 + range 1 32 + help + Maximum Windows Size of Retransmission and Flow Control. + The minimum (and default) number is 1. + +config BT_L2CAP_BR_RET_TIMEOUT + int "Retransmission timeout (milliseconds)" + default 2000 + range 1000 2000 + help + The Retransmission timeout shall be three times the value of flush timeout, + subject to a minimum of 1000 milliseconds and maximum of 2000 milliseconds. + +config BT_L2CAP_BR_MONITOR_TIMEOUT + int "Monitor timeout (milliseconds)" + default 12000 if BT_L2CAP_ENH_RET + default BT_L2CAP_BR_RET_TIMEOUT + range BT_L2CAP_BR_RET_TIMEOUT 12000 + help + If a flush timeout exists on the link for Enhanced Retransmission mode and + both sides of the link are configured to the same flush timeout value then the + monitor timeout shall be set to a value at least as large as the Retransmission + timeout otherwise the value of the Monitor timeout shall be six times the value + of flush timeout, subject to a minimum of the retransmission timeout value and + a maximum of 12000 milliseconds. +endif # BT_L2CAP_RET_FC + +config BT_RFCOMM + bool "Bluetooth RFCOMM protocol support [EXPERIMENTAL]" + help + This option enables Bluetooth RFCOMM support + +config BT_RFCOMM_L2CAP_MTU + int "L2CAP MTU for RFCOMM frames" + depends on BT_RFCOMM + default BT_BUF_ACL_RX_SIZE + range 23 32767 + help + Maximum size of L2CAP PDU for RFCOMM frames. + RX MTU will be truncated to account for the L2CAP PDU header. + +config BT_RFCOMM_TX_MAX + int "Maximum number of pending TX buffers for RFCOMM" + default BT_BUF_ACL_TX_COUNT + range BT_BUF_ACL_TX_COUNT 255 + help + Maximum number of pending TX buffers that have an associated + sending buf. Normally this can be left to the default value, which + is equal to the number of session in the stack-internal pool. + +config BT_HFP_HF + bool "Bluetooth Handsfree profile HF Role support [EXPERIMENTAL]" + depends on PRINTK + select BT_RFCOMM + help + This option enables Bluetooth HF support + +config BT_HFP_AG + bool "Bluetooth Handsfree profile AG Role support [EXPERIMENTAL]" + depends on PRINTK + select BT_RFCOMM + help + This option enables Bluetooth AG support + +if BT_HFP_HF +config BT_HFP_HF_CLI + bool "CLI presentation capability for HFP HF [EXPERIMENTAL]" + default y + help + This option enables CLI presentation capability for HFP HF + +config BT_HFP_HF_VOLUME + bool "Remote audio volume control for HFP HF [EXPERIMENTAL]" + default y + help + This option enables Remote audio volume control for HFP HF + +config BT_HFP_HF_CODEC_NEG + bool "Codec Negotiation for HFP HF [EXPERIMENTAL]" + help + This option enables Codec Negotiation for HFP HF + +config BT_HFP_HF_ECNR + bool "EC and/or NR function for HFP HF [EXPERIMENTAL]" + help + This option enables EC and/or NR function for HFP HF + +if BT_HFP_HF_CODEC_NEG +config BT_HFP_HF_CODEC_MSBC + bool "Support Codec mSBC for HFP HF [EXPERIMENTAL]" + help + This option enables Codec mSBC for HFP HF + +config BT_HFP_HF_CODEC_LC3_SWB + bool "Support Codec LC3 SWB for HFP HF [EXPERIMENTAL]" + help + This option enables Codec LC3 SWB for HFP HF +endif # BT_HFP_HF_CODEC_NEG + +config BT_HFP_HF_3WAY_CALL + bool "Three-way calling for HFP HF [EXPERIMENTAL]" + help + This option enables Three-way calling for HFP HF + +config BT_HFP_HF_ECS + bool "Enhanced Call Status for HFP HF [EXPERIMENTAL]" + default y if BT_HFP_HF_3WAY_CALL + help + This option enables Enhanced Call Status for HFP HF + +config BT_HFP_HF_ECC + bool "Enhanced Call Control for HFP HF [EXPERIMENTAL]" + default y if BT_HFP_HF_3WAY_CALL + help + This option enables Enhanced Call Control for HFP HF + +config BT_HFP_HF_MAX_CALLS + int "Maximum supported calls for HFP HF [EXPERIMENTAL]" + default 2 if BT_HFP_HF_3WAY_CALL + default 1 + range 1 2 + help + This option sets maximum supported calls for HFP HF + +config BT_HFP_HF_VOICE_RECG + bool "Voice recognition activation for HFP HF [EXPERIMENTAL]" + help + This option enables Voice recognition activation for HFP HF + +config BT_HFP_HF_ENH_VOICE_RECG + bool "Enhanced Voice Recognition Status for HFP HF [EXPERIMENTAL]" + select BT_HFP_HF_VOICE_RECG + help + This option enables Enhanced Voice Recognition Status for HFP HF + +config BT_HFP_HF_VOICE_RECG_TEXT + bool "Voice Recognition Text for HFP HF [EXPERIMENTAL]" + select BT_HFP_HF_ENH_VOICE_RECG + help + This option enables Voice Recognition Text for HFP HF + +config BT_HFP_HF_HF_INDICATORS + bool "HF Indicators for HFP HF [EXPERIMENTAL]" + help + This option enables HF Indicators for HFP HF + +config BT_HFP_HF_HF_INDICATOR_ENH_SAFETY + bool "HF Indicator Enhanced Safety for HFP HF [EXPERIMENTAL]" + select BT_HFP_HF_HF_INDICATORS + help + This option enables HF Indicator Enhanced Safety for HFP HF + +config BT_HFP_HF_HF_INDICATOR_BATTERY + bool "HF Indicator Battery level for HFP HF [EXPERIMENTAL]" + select BT_HFP_HF_HF_INDICATORS + help + This option enables HF Indicator Battery level for HFP HF + +endif # BT_HFP_HF + +if BT_HFP_AG +config BT_HFP_AG_EXT_ERR + bool "Extended Error Result Codes for HFP AG [EXPERIMENTAL]" + default y + help + This option enables Extended Error Result Codes for HFP AG + +config BT_HFP_AG_CODEC_NEG + bool "Codec Negotiation for HFP AG [EXPERIMENTAL]" + help + This option enables Codec Negotiation for HFP AG + +if BT_HFP_AG_CODEC_NEG +config BT_HFP_AG_CODEC_MSBC + bool "Support Codec mSBC for HFP AG [EXPERIMENTAL]" + help + This option enables Codec mSBC for HFP AG + +config BT_HFP_AG_CODEC_LC3_SWB + bool "Support Codec LC3 SWB for HFP AG [EXPERIMENTAL]" + help + This option enables Codec LC3 SWB for HFP AG +endif # BT_HFP_AG_CODEC_NEG + +config BT_HFP_AG_ECNR + bool "EC and/or NR function for HFP AG [EXPERIMENTAL]" + help + This option enables EC and/or NR function for HFP AG + +config BT_HFP_AG_3WAY_CALL + bool "Three-way calling for HFP AG [EXPERIMENTAL]" + help + This option enables Three-way calling for HFP AG + +config BT_HFP_AG_MAX_CALLS + int "Maximum supported calls for HFP AG [EXPERIMENTAL]" + default 2 if BT_HFP_AG_3WAY_CALL + default 1 + range 1 2 + help + This option sets maximum supported calls for HFP AG + +config BT_HFP_AG_ECS + bool "Enhanced call status for HFP AG [EXPERIMENTAL]" + default y if BT_HFP_AG_3WAY_CALL + help + This option enables Enhanced call status for HFP AG + +config BT_HFP_AG_ECC + bool "Enhanced call control for HFP AG [EXPERIMENTAL]" + default y if BT_HFP_AG_3WAY_CALL + help + This option enables Enhanced call control for HFP AG + +config BT_HFP_AG_TX_BUF_COUNT + int "Maximum number of TX buffers for HFP AG [EXPERIMENTAL]" + default BT_RFCOMM_TX_MAX + range BT_RFCOMM_TX_MAX 255 + help + Maximum number of pending TX buffers that have an associated + sending buf. Normally this can be left to the default value, which + is equal to the number of session in the stack-internal pool. + +config BT_HFP_AG_OUTGOING_TIMEOUT + int "Call outgoing timeout value for HFP AG [EXPERIMENTAL]" + default 3 + range 1 10 + help + The option sets the timeout of call outgoing. If the call does + not switch to alerting state before timeout, it will be + stopped by the HFP AG. The unit is seconds. + +config BT_HFP_AG_INCOMING_TIMEOUT + int "Incoming call timeout value for HFP AG [EXPERIMENTAL]" + default 3 + range 1 10 + help + The option sets the timeout of incoming call. If the call does + not switch to ringing state before timeout, it will be + stopped by the HFP AG. The unit is seconds. + +config BT_HFP_AG_ALERTING_TIMEOUT + int "Call alerting/ringing timeout value for HFP AG [EXPERIMENTAL]" + default 60 + range 10 60 + help + The option sets the timeout of call alerting/ringing. If the + call is not active before timeout, it will be stopped + by the HFP AG. The unit is seconds. + +config BT_HFP_AG_PHONE_NUMBER_MAX_LEN + int "Supported max length of phone number for HFP AG [EXPERIMENTAL]" + default 32 + range 1 255 + help + Supported max length of phone number for HFP AG. + +config BT_HFP_AG_RING_NOTIFY_INTERVAL + int "Ring notification interval [EXPERIMENTAL]" + default 3 + help + Ring notification interval if the call is in alert state. + The unit is seconds. + +config BT_HFP_AG_VOICE_RECG + bool "Voice recognition activation for HFP AG [EXPERIMENTAL]" + help + This option enables Voice recognition activation for HFP AG + +config BT_HFP_AG_ENH_VOICE_RECG + bool "Enhanced Voice Recognition Status for HFP AG [EXPERIMENTAL]" + select BT_HFP_AG_VOICE_RECG + help + This option enables Enhanced Voice Recognition Status for HFP AG + +config BT_HFP_AG_VOICE_RECG_TEXT + bool "Voice Recognition Text for HFP AG [EXPERIMENTAL]" + select BT_HFP_AG_ENH_VOICE_RECG + help + This option enables Voice Recognition Text for HFP AG + +config BT_HFP_AG_VOICE_TAG + bool "Attach a phone number for a voice tag for HFP AG [EXPERIMENTAL]" + select BT_HFP_AG_VOICE_RECG + help + This option enables Attach a phone number for a voice tag for HFP AG + +config BT_HFP_AG_HF_INDICATORS + bool "HF Indicators for HFP AG [EXPERIMENTAL]" + help + This option enables HF Indicators for HFP AG + +config BT_HFP_AG_HF_INDICATOR_ENH_SAFETY + bool "HF Indicator Enhanced Safety for HFP AG [EXPERIMENTAL]" + select BT_HFP_AG_HF_INDICATORS + help + This option enables HF Indicator Enhanced Safety for HFP AG + +config BT_HFP_AG_HF_INDICATOR_BATTERY + bool "HF Indicator Battery level for HFP AG [EXPERIMENTAL]" + select BT_HFP_AG_HF_INDICATORS + help + This option enables HF Indicator Battery level for HFP AG + +config BT_HFP_AG_REJECT_CALL + bool "Ability to reject a call for HFP AG [EXPERIMENTAL]" + default y + help + This option enables ability to reject a call for HFP AG + +config BT_HFP_AG_GET_ONGOING_CALL_TIMEOUT + int "Timeout after the get ongoing calls callback notified (milliseconds) [EXPERIMENTAL]" + default 10000 + range 1000 10000 + help + This option sets the timeout after the get ongoing calls callback notified + +config BT_HFP_AG_INBAND_RINGTONE + bool "In-band ring tone capability" + default y + help + This option enables In-band ring tone capability + +endif # BT_HFP_AG + +config BT_AVDTP + bool "Bluetooth AVDTP protocol support [EXPERIMENTAL]" + help + This option enables Bluetooth AVDTP support + +config BT_A2DP + bool "Bluetooth A2DP Profile [EXPERIMENTAL]" + select BT_AVDTP + imply LIBSBC + help + This option enables the A2DP profile + +if BT_AVDTP + +config BT_AVDTP_RTP_VERSION + int "Bluetooth AVDTP RTP version" + default 2 + help + This option sets the AVDTP RTP protocol version + +config BT_AVDTP_SIGNAL_SDU_MAX + int "Maximum size of AVDTP signaling SDU" + default 512 + range 48 65535 + help + This option sets the maximum size of AVDTP signaling SDU. + The default value is 512 bytes. + +endif # BT_AVDTP + +if BT_A2DP + +config BT_A2DP_SOURCE + bool "Bluetooth A2DP Profile Source Function" + help + This option enables the A2DP profile source function + +config BT_A2DP_SINK + bool "Bluetooth A2DP Profile Sink Function" + help + This option enables the A2DP profile sink function + +config BT_A2DP_CODEC_MAX_IE_LEN + int "Maximum size of A2DP codec IE" + default 8 + # The minimum length is 4 bytes, because the codec specific IE for the mandatory Codec SBC + # is 4 bytes. + # The maximum length is 253 bytes, because the length field of Length Of Service Capability + # (LOSC) is one byte and the header of Media Codec Capabilities is 2 bytes. Thus, the + # maximum is 255 - 2. + range 4 253 + help + This option sets the maximum size of A2DP codec IE. + +endif # BT_A2DP + +config BT_AVCTP + bool "Bluetooth AVCTP protocol support [EXPERIMENTAL]" + help + This option enables Bluetooth AVCTP support + +config BT_AVRCP + bool "Bluetooth AVRCP Profile [EXPERIMENTAL]" + select BT_AVCTP + help + This option enables the AVRCP profile + +if BT_AVRCP + +config BT_AVRCP_TARGET + bool "Bluetooth AVRCP Profile Target Function" + help + This option enables the AVRCP profile target function + +config BT_AVRCP_CONTROLLER + bool "Bluetooth AVRCP Profile Controller Function" + help + This option enables the AVRCP profile controller function + +config BT_AVRCP_VD_RX_SIZE + int "AVRCP Vendor Dependent RX data buffer Size" + default 1024 + range 512 32767 + help + Size of the AVRCP Vendor Dependent RX data buffer in bytes. + This buffer is used for reassembling fragmented AVRCP Vendor Dependent + commands and responses received from the CT device. If the remote CT + sends large Vendor-specific PDUs or frequent fragmentation occurs, + consider increasing this value to avoid incomplete message handling. + +config BT_AVRCP_BROWSING + bool "Bluetooth AVRCP Browsing channel support [EXPERIMENTAL]" + select BT_L2CAP_ENH_RET + help + This option enables support for the AVRCP Browsing channel + over Bluetooth BR/EDR. + +if BT_AVRCP_BROWSING + +config BT_AVRCP_BROWSING_L2CAP_MTU + int "L2CAP MTU for AVRCP Browsing Channel" + default 339 + # For AVRCP Browsing Channel over L2CAP, including: + # 335 bytes - the minimum MTU for AVRCP Browsing Channel, + # 4 bytes - L2CAP Header, + range 339 4096 + help + Maximum size of L2CAP MTU for AVCTP Browsing Channel. + RX MTU will be truncated to account for ACL data and type overhead, and + the L2CAP PDUheader. + +endif # BT_AVRCP_BROWSING + +config BT_AVRCP_TG_COVER_ART + bool "Bluetooth AVRCP Target Cover Art support [EXPERIMENTAL]" + select BT_BIP + depends on BT_AVRCP_TARGET + help + This option enables support for Cover Art in the AVRCP Target + role over Bluetooth BR/EDR. + +config BT_AVRCP_CT_COVER_ART + bool + select BT_BIP + depends on BT_AVRCP_CONTROLLER + help + This option enables support for Cover Art in the AVRCP controller + role over Bluetooth BR/EDR. + +config BT_AVRCP_CT_COVER_ART_GET_IMAGE_PROPERTIES + bool "Bluetooth AVRCP Controller Cover Art Get Image Properties support [EXPERIMENTAL]" + select BT_BIP + select BT_AVRCP_CT_COVER_ART + depends on BT_AVRCP_CONTROLLER + help + This option enables support for Cover Art Get Image Properties in the AVRCP Controller + role over Bluetooth BR/EDR. + +config BT_AVRCP_CT_COVER_ART_GET_IMAGE + bool "Bluetooth AVRCP Controller Cover Art Get Image support [EXPERIMENTAL]" + select BT_BIP + select BT_AVRCP_CT_COVER_ART + depends on BT_AVRCP_CONTROLLER + help + This option enables support for Cover Art Get Image in the AVRCP Controller + role over Bluetooth BR/EDR. + +config BT_AVRCP_CT_COVER_ART_GET_LINKED_THUMBNAIL + bool "Bluetooth AVRCP Controller Cover Art Get Linked Thumbnail support [EXPERIMENTAL]" + select BT_BIP + select BT_AVRCP_CT_COVER_ART + depends on BT_AVRCP_CONTROLLER + help + This option enables support for Cover Art Get Linked Thumbnail in the AVRCP Controller + role over Bluetooth BR/EDR. + +endif # BT_AVRCP + +config BT_DID + bool "Bluetooth DID Profile [EXPERIMENTAL]" + help + This option enables the DID profile + +if BT_DID +config BT_DEVICE_VENDOR_ID + hex "Bluetooth Device Vendor ID" + range 0x0000 65535 + default 0x0000 + help + This option sets the device vendor id + +config BT_DEVICE_PRODUCT_ID + hex "Bluetooth Device Product ID" + range 0x0000 65535 + default 0x0000 + help + This option sets the device product id + +config BT_DEVICE_VERSION + hex "Bluetooth Device Version" + range 0x0000 65535 + default 0x0000 + help + This option sets the device version + +endif # BT_DID + +config BT_HID_DEVICE + bool "Bluetooth HID Device Profile [EXPERIMENTAL]" + select BT_DID + help + This option enables the HID Device profile. + +config BT_HID_HOST + bool "Bluetooth HID Host Profile [EXPERIMENTAL]" + help + This option enables the HID Host profile. + +config BT_PAGE_TIMEOUT + hex "Bluetooth Page Timeout" + default 0x2000 + range 0x0001 0xffff + help + This option sets the page timeout value. Value is selected as + (N * 0.625) ms. + +config BT_COD + hex "Bluetooth Class of Device(CoD)" + default 0 + help + This option sets the class of device.For the list of possible values please + consult the following link: + https://www.bluetooth.com/specifications/assigned-numbers + +config BT_DEFAULT_ROLE_SWITCH_ENABLE + bool "Default Bluetooth Role Switch Enable/Disable State" + help + This option sets the controller's default link policy to + enable/disable the role switch. + +config BT_POWER_MODE_CONTROL + bool "Bluetooth Power Mode Control(Active/Sniff)" + help + This option enables the power mode control feature. This feature + allows the application to control the power mode of the Bluetooth + controller. + +config BT_GOEP + bool "Bluetooth GOEP Profile [EXPERIMENTAL]" + select BT_RFCOMM + select BT_L2CAP_ENH_RET + help + This option enables the GOEP profile + +if BT_GOEP +config BT_GOEP_RFCOMM_MTU + int "RFCOMM MTU for GOEP" + default BT_RFCOMM_L2CAP_MTU + # For GOEP over RFCOMM, including, + # 255 bytes - the minimum MTU of GOEP, + # 4 bytes - L2CAP Header, + # 6 bytes - 4 bytes for RFCOMM header with extended length, 1 byte for FCS, and 1 byte for + # credits. + range 265 BT_RFCOMM_L2CAP_MTU + help + Maximum size of RFCOMM MTU for GOEP. + RX MTU will be truncated to account for ACL data and type overhead, the L2CAP PDU header, + and the RFCOMM header. + +config BT_GOEP_L2CAP_MTU + int "L2CAP MTU for GOEP" + default BT_BUF_ACL_RX_SIZE + # For GOEP over L2CAP, including, + # 255 bytes - the minimum MTU of GOEP, + # 4 bytes - L2CAP Header, + # 6 bytes - Field length of L2CAP I-frame, including, + # 4 bytes - extended control field, + # 2 bytes - FCS field. + range 265 BT_BUF_ACL_RX_SIZE + help + Maximum size of L2CAP MTU for GOEP. + RX MTU will be truncated to account for ACL data and type overhead, and the L2CAP PDU + header. + +config BT_OEBX_RSP_CODE_TO_STR + bool "Print OBEX response code as strings" + help + This configuration enables printing of OBEX response + codes represented as strings. + See bt_obex_rsp_code_to_str() for more details. + +config BT_OBEX_AUTH + bool "Bluetooth OBEX Authentication support" + select PSA_WANT_ALG_MD5 + select MBEDTLS + help + This option enables OBEX authentication feature. + OBEX authentication uses MD5, which is cryptographically weak. + It provides only limited security. + +config BT_OBEX_AUTH_PWD_LEN + int "Maximum OBEX Authentication Password Length" + depends on BT_OBEX_AUTH + default 16 + range 1 255 + help + This option sets the maximum length of OBEX authentication password. + +endif # BT_GOEP + +config BT_BIP + bool "Bluetooth BIP Profile [EXPERIMENTAL]" + select BT_GOEP + help + This option enables the BIP profile + +config BT_PBAP + bool + select BT_GOEP + select BT_OBEX_AUTH + help + This option enables the PBAP profile + +config BT_PBAP_PSE + bool "Bluetooth PBAP Profile Server [EXPERIMENTAL]" + select BT_PBAP + help + This option enables the PSE + +config BT_PBAP_PCE + bool "Bluetooth PBAP Profile Client [EXPERIMENTAL]" + select BT_PBAP + help + This option enables the PCE + +if BT_PBAP_PSE + +config BT_PBAP_PSE_DOWNLOAD + bool "PBAP PSE Download feature" + default y + help + Enable support for phone book download feature on PSE. + +config BT_PBAP_PSE_BROWSING + bool "PBAP PSE Browsing feature" + default y + help + Enable support for phone book browsing feature on PSE. + +config BT_PBAP_PSE_DATABASE_IDENTIFIER + bool "PBAP PSE Database Identifier feature" + default y + help + Enable support for database change tracking feature on PSE. + +config BT_PBAP_PSE_FOLDER_VERSION_COUNTERS + bool "PBAP PSE Folder Version Counters feature" + default y + help + Enable support for folder version tracking feature on PSE. + +config BT_PBAP_PSE_VCARD_SELECTOR + bool "PBAP PSE vCard Selector feature" + default y + help + Enable support for vCard property filtering feature on PSE. + +config BT_PBAP_PSE_ENHANCED_MISSED_CALLS + bool "PBAP PSE Enhanced Missed Calls feature" + default y + help + Enable support for enhanced missed call handling feature on PSE. + +config BT_PBAP_PSE_UCI_VCARD_PROPERTY + bool "PBAP PSE UCI vCard Property feature" + default y + help + Enable support for Unique Contact Identifier feature on PSE. + +config BT_PBAP_PSE_UID_VCARD_PROPERTY + bool "PBAP PSE UID vCard Property feature" + default y + help + Enable support for vCard UID property feature on PSE. + +config BT_PBAP_PSE_CONTACT_REFERENCING + bool "PBAP PSE Contact Referencing feature" + default y + help + Enable support for contact reference handling feature on PSE. + +config BT_PBAP_PSE_DEFAULT_CONTACT_IMAGE + bool "PBAP PSE Default Contact Image feature" + default y + help + Enable support for default contact images feature on PSE. + +config BT_PBAP_PSE_REPOSITORY_LOCAL_PHONE_BOOK + bool "PBAP PSE Local phonebook Repository" + default y + help + Enable support for local phone book repository on PSE. + This includes contacts stored in device local memory. + +config BT_PBAP_PSE_REPOSITORY_SIM + bool "PBAP PSE SIM Repository" + default y + help + Enable support for SIM card phone book repository on PSE. + This includes contacts stored on the SIM card. + +config BT_PBAP_PSE_REPOSITORY_SPEED_DIAL + bool "PBAP PSE Speed Dial Repository" + default y + help + Enable support for speed dial repository on PSE. + This includes speed dial entries. + +config BT_PBAP_PSE_REPOSITORY_FAVORITES + bool "PBAP PSE Favorites Repository" + default y + help + Enable support for favorites repository on PSE. + This includes favorite contacts. + +endif # BT_PBAP_PSE + +config BT_MAP + bool + select BT_GOEP + help + This option enables the MAP profile + +config BT_MAP_MCE + bool "Bluetooth MAP Profile Messaging Client Equipment Function" + select BT_MAP + help + This option enables the MAP profile messaging client equipment function + +config BT_MAP_MSE + bool "Bluetooth MAP Profile Messaging Server Equipment Function" + select BT_MAP + help + This option enables the MAP profile messaging server equipment function + +endif # BT_CLASSIC + +endmenu diff --git a/libs/zblue/host/classic/a2dp.c b/libs/zblue/host/classic/a2dp.c new file mode 100644 index 0000000000000..7ede01207419d --- /dev/null +++ b/libs/zblue/host/classic/a2dp.c @@ -0,0 +1,1915 @@ +/**************************************************************************** + * libs/zblue/host/classic/a2dp.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Advance Audio Distribution Profile. + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * Copyright 2021,2024-2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#define A2DP_SBC_PAYLOAD_TYPE (0x60u) + +#define A2DP_AVDTP(_avdtp) CONTAINER_OF(_avdtp, struct bt_a2dp, session) +#define DISCOVER_REQ(_req) CONTAINER_OF(_req, struct bt_avdtp_discover_params, req) +#define DISCOVER_PARAM(_discover_param) \ + CONTAINER_OF(_discover_param, struct bt_a2dp, discover_param) +#define GET_CAP_REQ(_req) CONTAINER_OF(_req, struct bt_avdtp_get_capabilities_params, req) +#define GET_CAP_PARAM(_get_cap_param) \ + CONTAINER_OF(_get_cap_param, struct bt_a2dp, get_capabilities_param) +#define SET_CONF_REQ(_req) CONTAINER_OF(_req, struct bt_avdtp_set_configuration_params, req) +#define SET_CONF_PARAM(_set_conf_param) \ + CONTAINER_OF(_set_conf_param, struct bt_a2dp, set_config_param) +#define CTRL_REQ(_req) CONTAINER_OF(_req, struct bt_avdtp_ctrl_params, req) +#define CTRL_PARAM(_ctrl_param) CONTAINER_OF(_ctrl_param, struct bt_a2dp, ctrl_param) +#define DELAY_REPORT_REQ(_req) CONTAINER_OF(_req, struct bt_avdtp_delay_report_params, req) + +#include +#include +#include +#include "avdtp_internal.h" + +#define LOG_LEVEL CONFIG_BT_A2DP_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_a2dp); + +#define A2DP_NO_SPACE (-1) + +enum bt_a2dp_internal_state +{ + INTERNAL_STATE_IDLE = 0, + INTERNAL_STATE_AVDTP_CONNECTED, +}; + +struct bt_a2dp +{ + struct bt_avdtp session; + struct bt_avdtp_discover_params discover_param; + struct bt_a2dp_discover_param *discover_cb_param; + struct bt_avdtp_get_capabilities_params get_capabilities_param; + struct bt_avdtp_set_configuration_params set_config_param; + struct bt_avdtp_ctrl_params ctrl_param; + struct bt_avdtp_delay_report_params delay_report_param; + uint8_t get_cap_index; + enum bt_a2dp_internal_state a2dp_state; + uint8_t peer_seps_count; +}; + +static struct bt_a2dp_cb *a2dp_cb; + +/* Connections */ + +static struct bt_a2dp connection[CONFIG_BT_MAX_CONN]; + +static int bt_a2dp_get_sep_caps(struct bt_a2dp *a2dp); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static struct bt_a2dp *a2dp_get_connection(struct bt_conn *conn) +{ + struct bt_a2dp *a2dp; + size_t index; + + index = (size_t)bt_conn_index(conn); + DEBUGASSERT(index < ARRAY_SIZE(connection)); + + a2dp = &connection[index]; + + if (a2dp->session.br_chan.chan.conn == NULL) + { + /* The session release work submitted when the previous + * connection was disconnected may still be pending. Wiping a queued + * work item would corrupt the work queue, so reject the object reuse + * until the work has completed. + */ + + if (k_work_busy_get(&a2dp->session._release_work) != 0) + { + return NULL; + } + + /* Clean the memory area before returning */ + + (void)memset(a2dp, 0, sizeof(struct bt_a2dp)); + } + + return a2dp; +} + +/* The AVDTP L2CAP signal channel established */ + +static void a2dp_connected(struct bt_avdtp *session) +{ + struct bt_a2dp *a2dp = A2DP_AVDTP(session); + + a2dp->a2dp_state = INTERNAL_STATE_AVDTP_CONNECTED; + + /* notify a2dp app the connection. */ + + if ((a2dp_cb != NULL) && (a2dp_cb->connected != NULL)) + { + a2dp_cb->connected(a2dp, 0); + } +} + +/* The AVDTP L2CAP signal channel released */ + +static void a2dp_disconnected(struct bt_avdtp *session) +{ + struct bt_a2dp *a2dp = A2DP_AVDTP(session); + + /* notify a2dp app the disconnection. */ + + if ((a2dp_cb != NULL) && (a2dp_cb->disconnected != NULL)) + { + a2dp_cb->disconnected(a2dp); + } +} + +/* avdtp alloc buf from upper layer */ + +static struct net_buf *a2dp_alloc_buf(struct bt_avdtp *session) +{ + return NULL; +} + +static int a2dp_discovery_ind(struct bt_avdtp *session, uint8_t *errcode) +{ + *errcode = 0; + return 0; +} + +static int a2dp_get_capabilities_ind( + struct bt_avdtp *session, struct bt_avdtp_sep *sep, + struct net_buf *rsp_buf, bool get_all_caps, uint8_t *errcode) +{ + /* The Reporting, Recovery, Content Protection, Header Compression and + * Multiplexing are not supported. + */ + + struct bt_a2dp_ep *ep; + struct bt_avdtp_generic_service_cap *cap; + struct bt_avdtp_media_codec_capabilities *media_cap; + + DEBUGASSERT(sep); + *errcode = 0; + + if (net_buf_tailroom(rsp_buf) < sizeof(*cap)) + { + goto set_err_and_return; + } + + /* Service Category: Media Transport */ + + cap = net_buf_add(rsp_buf, sizeof(*cap)); + + cap->service_category = BT_AVDTP_SERVICE_MEDIA_TRANSPORT; + cap->losc = 0; + + ep = CONTAINER_OF(sep, struct bt_a2dp_ep, sep); + + if (net_buf_tailroom(rsp_buf) < + ep->codec_cap->len + sizeof(*cap) + sizeof(*media_cap)) + { + goto set_err_and_return; + } + + /* Service Category: Media Codec */ + + cap = net_buf_add(rsp_buf, sizeof(*cap)); + cap->service_category = BT_AVDTP_SERVICE_MEDIA_CODEC; + cap->losc = ep->codec_cap->len + sizeof(*media_cap); + + media_cap = net_buf_add(rsp_buf, sizeof(*media_cap)); + media_cap->media_type = + AVDTP_SEP_MEDIA_TYPE_PREP(sep->sep_info.media_type); + media_cap->media_code_type = ep->codec_type; + + /* Codec Info Element */ + + net_buf_add_mem(rsp_buf, &ep->codec_cap->codec_ie[0], ep->codec_cap->len); + + if (get_all_caps && ep->delay_report) + { + if (net_buf_tailroom(rsp_buf) < sizeof(*cap)) + { + goto set_err_and_return; + } + + /* Service Category: Delay Report */ + + cap = net_buf_add(rsp_buf, sizeof(*cap)); + + cap->service_category = BT_AVDTP_SERVICE_DELAY_REPORTING; + cap->losc = 0; + } + + return 0; + +set_err_and_return: + *errcode = BT_AVDTP_INVALID_CAPABILITIES; + return -ENOMEM; +} + +#define IS_BIT_DUPLICATED(val) (((val) & ((val) - 1)) != 0) +#define BT_A2DP_SBC_SAMP_FREQ_INVALID(val) \ + ((BT_A2DP_SBC_SAMP_FREQ(val) == 0) || \ + (IS_BIT_DUPLICATED(BT_A2DP_SBC_SAMP_FREQ(val)))) +#define BT_A2DP_SBC_CHAN_MODE_INVALID(val) \ + ((BT_A2DP_SBC_CHAN_MODE(val) == 0) || \ + (IS_BIT_DUPLICATED(BT_A2DP_SBC_CHAN_MODE(val)))) +#define BT_A2DP_SBC_BLK_LEN_INVALID(val) \ + ((BT_A2DP_SBC_BLK_LEN(val) == 0) || (IS_BIT_DUPLICATED(BT_A2DP_SBC_BLK_LEN(val)))) +#define BT_A2DP_SBC_SUB_BAND_INVALID(val) \ + ((BT_A2DP_SBC_SUB_BAND(val) == 0) || \ + (IS_BIT_DUPLICATED(BT_A2DP_SBC_SUB_BAND(val)))) +#define BT_A2DP_SBC_ALLOC_MTHD_INVALID(val) \ + ((BT_A2DP_SBC_ALLOC_MTHD(val) == 0) || \ + (IS_BIT_DUPLICATED(BT_A2DP_SBC_ALLOC_MTHD(val)))) +#define BT_A2DP_SBC_MIN_BITPOOL_VALUE_INVALID(val) \ + (val->min_bitpool < BT_A2DP_SBC_MIN_BITPOOL_VALUE || \ + val->min_bitpool > BT_A2DP_SBC_MAX_BITPOOL_VALUE) +#define BT_A2DP_SBC_MAX_BITPOOL_VALUE_INVALID(val) \ + (val->max_bitpool < BT_A2DP_SBC_MIN_BITPOOL_VALUE || \ + val->max_bitpool > BT_A2DP_SBC_MAX_BITPOOL_VALUE) + +#define BT_A2DP_SBC_SAMP_FREQ_NOT_SUPPORT(set, cap) \ + ((BT_A2DP_SBC_SAMP_FREQ(set) & BT_A2DP_SBC_SAMP_FREQ(cap)) == 0) +#define BT_A2DP_SBC_CHAN_MODE_NOT_SUPPORT(set, cap) \ + ((BT_A2DP_SBC_CHAN_MODE(set) & BT_A2DP_SBC_CHAN_MODE(cap)) == 0) +#define BT_A2DP_SBC_SUB_BAND_NOT_SUPPORT(set, cap) \ + ((BT_A2DP_SBC_SUB_BAND(set) & BT_A2DP_SBC_SUB_BAND(cap)) == 0) +#define BT_A2DP_SBC_ALLOC_MTHD_NOT_SUPPORT(set, cap) \ + ((BT_A2DP_SBC_ALLOC_MTHD(set) & BT_A2DP_SBC_ALLOC_MTHD(cap)) == 0) +#define BT_A2DP_SBC_MIN_BITPOOL_VALUE_NOT_SUPPORT(set, cap) (set->min_bitpool < cap->min_bitpool) +#define BT_A2DP_SBC_MAX_BITPOOL_VALUE_NOT_SUPPORT(set, cap) (set->max_bitpool > cap->max_bitpool) + +static uint8_t a2dp_sbc_check_config_param( + struct bt_a2dp_codec_sbc_params *set, struct bt_a2dp_codec_sbc_params *cap) +{ + if (BT_A2DP_SBC_SAMP_FREQ_INVALID(set)) + { + return BT_A2DP_INVALID_SAMPLING_FREQUENCY; + } + + if (BT_A2DP_SBC_SAMP_FREQ_NOT_SUPPORT(set, cap)) + { + return BT_A2DP_NOT_SUPPORTED_SAMPLING_FREQUENCY; + } + + if (BT_A2DP_SBC_CHAN_MODE_INVALID(set)) + { + return BT_A2DP_INVALID_CHANNEL_MODE; + } + + if (BT_A2DP_SBC_CHAN_MODE_NOT_SUPPORT(set, cap)) + { + return BT_A2DP_NOT_SUPPORTED_CHANNEL_MODE; + } + + if (BT_A2DP_SBC_BLK_LEN_INVALID(set)) + { + return BT_A2DP_INVALID_BLOCK_LENGTH; + } + + if (BT_A2DP_SBC_SUB_BAND_INVALID(set)) + { + return BT_A2DP_INVALID_SUBBANDS; + } + + if (BT_A2DP_SBC_SUB_BAND_NOT_SUPPORT(set, cap)) + { + return BT_A2DP_NOT_SUPPORTED_SUBBANDS; + } + + if (BT_A2DP_SBC_ALLOC_MTHD_INVALID(set)) + { + return BT_A2DP_INVALID_ALLOCATION_METHOD; + } + + if (BT_A2DP_SBC_ALLOC_MTHD_NOT_SUPPORT(set, cap)) + { + return BT_A2DP_NOT_SUPPORTED_ALLOCATION_METHOD; + } + + if (BT_A2DP_SBC_MIN_BITPOOL_VALUE_INVALID(set)) + { + return BT_A2DP_INVALID_MINIMUM_BITPOOL_VALUE; + } + + if (BT_A2DP_SBC_MIN_BITPOOL_VALUE_NOT_SUPPORT(set, cap)) + { + return BT_A2DP_NOT_SUPPORTED_MINIMUM_BITPOOL_VALUE; + } + + if (BT_A2DP_SBC_MAX_BITPOOL_VALUE_INVALID(set)) + { + return BT_A2DP_INVALID_MAXIMUM_BITPOOL_VALUE; + } + + if (BT_A2DP_SBC_MAX_BITPOOL_VALUE_NOT_SUPPORT(set, cap)) + { + return BT_A2DP_NOT_SUPPORTED_MAXIMUM_BITPOOL_VALUE; + } + + return 0; +} + +static uint8_t a2dp_check_codec_type(struct bt_a2dp_ep *local_ep, + uint8_t codec_type) +{ + if (codec_type != BT_A2DP_SBC && codec_type != BT_A2DP_MPEG1 && + codec_type != BT_A2DP_MPEG2 && + codec_type != BT_A2DP_MPEGD && codec_type != BT_A2DP_ATRAC && + codec_type != BT_A2DP_VENDOR) + { + return BT_A2DP_INVALID_CODEC_TYPE; + } + + if (codec_type != local_ep->codec_type) + { + return BT_A2DP_NOT_SUPPORTED_CODEC_TYPE; + } + + return 0; +} + +static int a2dp_process_config_ind( + struct bt_avdtp *session, struct bt_avdtp_sep *sep, uint8_t int_seid, + struct net_buf *buf, uint8_t *errcode, bool reconfig) +{ + struct bt_a2dp *a2dp = A2DP_AVDTP(session); + struct bt_a2dp_ep *ep; + struct bt_a2dp_stream *stream = NULL; + struct bt_a2dp_stream_ops *ops; + uint8_t codec_type; + uint8_t *codec_info_element; + uint16_t codec_info_element_len; + struct bt_a2dp_codec_cfg cfg; + struct bt_a2dp_codec_ie codec_config; + uint8_t rsp_err_code; + int err; + bool delay_report; + + *errcode = 0; + + DEBUGASSERT(sep); + + ep = CONTAINER_OF(sep, struct bt_a2dp_ep, sep); + + /* parse the configuration */ + + err = + bt_avdtp_parse_capability_codec(buf, &codec_type, &codec_info_element, + &codec_info_element_len, + &delay_report); + if (err) + { + *errcode = BT_AVDTP_BAD_ACP_SEID; + return -EINVAL; + } + + *errcode = a2dp_check_codec_type(ep, codec_type); + if (*errcode != 0) + { + return -EINVAL; + } + + if (codec_info_element_len > CONFIG_BT_A2DP_CODEC_MAX_IE_LEN) + { + LOG_WRN("Received IE exceeds the max supported length (%u > %u)", + codec_info_element_len, + CONFIG_BT_A2DP_CODEC_MAX_IE_LEN); + *errcode = BT_AVDTP_UNSUPPORTED_CONFIGURATION; + return -EINVAL; + } + + if (codec_type == BT_A2DP_SBC) + { + struct bt_a2dp_codec_sbc_params *sbc_set; + struct bt_a2dp_codec_sbc_params *sbc; + + if (codec_info_element_len != sizeof(*sbc)) + { + *errcode = BT_AVDTP_BAD_ACP_SEID; + return -EINVAL; + } + + sbc_set = (struct bt_a2dp_codec_sbc_params *)codec_info_element; + sbc = (struct bt_a2dp_codec_sbc_params *)&ep->codec_cap->codec_ie[0]; + + *errcode = a2dp_sbc_check_config_param(sbc_set, sbc); + if (*errcode != 0) + { + return -EINVAL; + } + } + + /* For reconfig, ep->stream must already be valid, callback can be NULL as + * default accept. For !reconfig, config_req must be set to get stream from + * upper layer + */ + + if (reconfig) + { + stream = ep->stream; + if (stream == NULL) + { + *errcode = BT_AVDTP_BAD_ACP_SEID; + return -EINVAL; + } + + if (a2dp_cb == NULL || a2dp_cb->reconfig_req == NULL) + { + goto process_done; + } + } + else if (a2dp_cb == NULL || a2dp_cb->config_req == NULL) + { + *errcode = BT_AVDTP_BAD_ACP_SEID; + return -EINVAL; + } + + cfg.delay_report = delay_report; + cfg.codec_config = &codec_config; + cfg.codec_config->len = codec_info_element_len; + memcpy(&cfg.codec_config->codec_ie[0], codec_info_element, + codec_info_element_len); + + if (!reconfig) + { + err = a2dp_cb->config_req(a2dp, ep, &cfg, &stream, &rsp_err_code); + if (!err && stream) + { + stream->a2dp = a2dp; + stream->local_ep = ep; + stream->remote_ep_id = int_seid; + stream->remote_ep = NULL; + stream->codec_config = *cfg.codec_config; + stream->delay_report = cfg.delay_report; + ep->stream = stream; + } + else + { + *errcode = rsp_err_code != 0 ? rsp_err_code : + BT_AVDTP_BAD_ACP_SEID; + } + } + else + { + err = a2dp_cb->reconfig_req(stream, &cfg, &rsp_err_code); + if (err) + { + *errcode = rsp_err_code; + } + else + { + stream->codec_config = *cfg.codec_config; + } + } + +process_done: + if (*errcode == 0) + { + ops = stream->ops; + if ((ops != NULL) && (ops->configured != NULL)) + { + ops->configured(stream); + } + } + + return (*errcode == 0) ? 0 : -EINVAL; +} + +static int a2dp_set_config_ind( + struct bt_avdtp *session, struct bt_avdtp_sep *sep, uint8_t int_seid, + struct net_buf *buf, uint8_t *errcode) +{ + DEBUGASSERT(sep); + return a2dp_process_config_ind(session, + sep, + int_seid, + buf, + errcode, + false); +} + +static int a2dp_re_config_ind( + struct bt_avdtp *session, struct bt_avdtp_sep *sep, struct net_buf *buf, + uint8_t *errcode) +{ + DEBUGASSERT(sep); + return a2dp_process_config_ind(session, sep, 0, buf, errcode, true); +} + +#if defined(CONFIG_BT_A2DP_SINK) +static void bt_a2dp_media_data_callback(struct bt_avdtp_sep *sep, + struct net_buf *buf) +{ + struct bt_avdtp_media_hdr *media_hdr; + struct bt_a2dp_ep *ep; + struct bt_a2dp_stream *stream; + + DEBUGASSERT(sep); + ep = CONTAINER_OF(sep, struct bt_a2dp_ep, sep); + if (ep->stream == NULL || buf->len < sizeof(*media_hdr)) + { + return; + } + + stream = ep->stream; + media_hdr = net_buf_pull_mem(buf, sizeof(*media_hdr)); + + if (stream->ops == NULL || stream->ops->recv == NULL) + { + LOG_WRN("No recv callback registered for stream"); + return; + } + + stream->ops->recv(stream, buf, sys_be16_to_cpu(media_hdr->sequence_number), + sys_be32_to_cpu(media_hdr->time_stamp)); +} +#endif + +typedef int (*bt_a2dp_ctrl_req_cb)(struct bt_a2dp_stream *stream, + uint8_t *rsp_err_code); +typedef void (*bt_a2dp_ctrl_done_cb)(struct bt_a2dp_stream *stream); + +static int a2dp_ctrl_ind( + struct bt_avdtp *session, struct bt_avdtp_sep *sep, uint8_t *errcode, + bt_a2dp_ctrl_req_cb req_cb, bt_a2dp_ctrl_done_cb done_cb) +{ + struct bt_a2dp_ep *ep; + + *errcode = 0; + ep = CONTAINER_OF(sep, struct bt_a2dp_ep, sep); + if (ep->stream == NULL) + { + *errcode = BT_AVDTP_SEP_NOT_IN_USE; + return -EINVAL; + } + + if (req_cb != NULL) + { + uint8_t rsp_err_code; + int err; + + err = req_cb(ep->stream, &rsp_err_code); + if (err) + { + *errcode = rsp_err_code; + } + } + + if (*errcode == 0) + { + if (done_cb != NULL) + { + done_cb(ep->stream); + } + } + + return (*errcode == 0) ? 0 : -EINVAL; +} + +static int a2dp_open_ind(struct bt_avdtp *session, + struct bt_avdtp_sep *sep, uint8_t *errcode) +{ + bt_a2dp_ctrl_req_cb req_cb; + + DEBUGASSERT(sep); + req_cb = a2dp_cb != NULL ? a2dp_cb->establish_req : NULL; + return a2dp_ctrl_ind(session, sep, errcode, req_cb, NULL); +} + +static int a2dp_start_ind(struct bt_avdtp *session, + struct bt_avdtp_sep *sep, uint8_t *errcode) +{ + struct bt_a2dp_ep *ep = CONTAINER_OF(sep, struct bt_a2dp_ep, sep); + bt_a2dp_ctrl_req_cb req_cb; + bt_a2dp_ctrl_done_cb done_cb; + + DEBUGASSERT(sep); + req_cb = a2dp_cb != NULL ? a2dp_cb->start_req : NULL; + done_cb = (ep->stream != NULL && + ep->stream->ops != NULL) ? ep->stream->ops->started : NULL; + return a2dp_ctrl_ind(session, sep, errcode, req_cb, done_cb); +} + +static int a2dp_suspend_ind(struct bt_avdtp *session, + struct bt_avdtp_sep *sep, uint8_t *errcode) +{ + struct bt_a2dp_ep *ep = CONTAINER_OF(sep, struct bt_a2dp_ep, sep); + bt_a2dp_ctrl_req_cb req_cb; + bt_a2dp_ctrl_done_cb done_cb; + + DEBUGASSERT(sep); + req_cb = a2dp_cb != NULL ? a2dp_cb->suspend_req : NULL; + done_cb = (ep->stream != NULL && + ep->stream->ops != NULL) ? ep->stream->ops->suspended : NULL; + return a2dp_ctrl_ind(session, sep, errcode, req_cb, done_cb); +} + +static int a2dp_close_ind(struct bt_avdtp *session, + struct bt_avdtp_sep *sep, uint8_t *errcode) +{ + bt_a2dp_ctrl_req_cb req_cb; + + DEBUGASSERT(sep); + req_cb = a2dp_cb != NULL ? a2dp_cb->release_req : NULL; + + /* When stream is released, the `stream->ops->released` will be called. */ + + return a2dp_ctrl_ind(session, sep, errcode, req_cb, NULL); +} + +static int a2dp_abort_ind(struct bt_avdtp *session, + struct bt_avdtp_sep *sep, uint8_t *errcode) +{ + bt_a2dp_ctrl_req_cb req_cb; + + DEBUGASSERT(sep); + req_cb = a2dp_cb != NULL ? a2dp_cb->abort_req : NULL; + + /* When stream is released, the `stream->ops->released` will be called. */ + + return a2dp_ctrl_ind(session, sep, errcode, req_cb, NULL); +} + +static int a2dp_get_config_ind( + struct bt_avdtp *session, struct bt_avdtp_sep *sep, + struct net_buf *rsp_buf, uint8_t *errcode) +{ + struct bt_a2dp_ep *ep; + struct bt_avdtp_generic_service_cap *cap; + struct bt_avdtp_media_codec_capabilities *media_cap; + + DEBUGASSERT(sep); + + ep = CONTAINER_OF(sep, struct bt_a2dp_ep, sep); + if (ep->stream == NULL) + { + *errcode = BT_AVDTP_SEP_NOT_IN_USE; + return -EINVAL; + } + + *errcode = 0; + if (a2dp_cb != NULL && a2dp_cb->get_config_req != NULL) + { + int err; + + err = a2dp_cb->get_config_req(ep->stream, errcode); + if (err != 0) + { + if (*errcode == 0) + { + *errcode = BT_AVDTP_BAD_ACP_SEID; + } + + return err; + } + } + + if (net_buf_tailroom(rsp_buf) < sizeof(*cap)) + { + goto set_err_and_return; + } + + /* Service Category: Media Transport */ + + cap = net_buf_add(rsp_buf, sizeof(*cap)); + + cap->service_category = BT_AVDTP_SERVICE_MEDIA_TRANSPORT; + cap->losc = 0; + + if (net_buf_tailroom(rsp_buf) < + ep->stream->codec_config.len + sizeof(*cap) + sizeof(*media_cap)) + { + goto set_err_and_return; + } + + /* Service Category: Media Codec */ + + cap = net_buf_add(rsp_buf, sizeof(*cap)); + cap->service_category = BT_AVDTP_SERVICE_MEDIA_CODEC; + cap->losc = ep->stream->codec_config.len + sizeof(*media_cap); + + media_cap = net_buf_add(rsp_buf, sizeof(*media_cap)); + media_cap->media_type = + AVDTP_SEP_MEDIA_TYPE_PREP(sep->sep_info.media_type); + media_cap->media_code_type = ep->codec_type; + + /* Codec Info Element */ + + net_buf_add_mem(rsp_buf, &ep->stream->codec_config.codec_ie[0], + ep->stream->codec_config.len); + + if (ep->stream->delay_report) + { + if (net_buf_tailroom(rsp_buf) < sizeof(*cap)) + { + goto set_err_and_return; + } + + /* Service Category: Delay Report */ + + cap = net_buf_add(rsp_buf, sizeof(*cap)); + + cap->service_category = BT_AVDTP_SERVICE_DELAY_REPORTING; + cap->losc = 0; + } + + return 0; + +set_err_and_return: + *errcode = BT_AVDTP_INVALID_CAPABILITIES; + return -ENOMEM; +} + +#ifdef CONFIG_BT_A2DP_SOURCE +int a2dp_delay_report_ind( + struct bt_avdtp *session, struct bt_avdtp_sep *sep, struct net_buf *buf, + uint8_t *errcode) +{ + struct bt_a2dp_ep *ep; + struct bt_a2dp_stream *stream; + uint16_t value; + + DEBUGASSERT(sep); + + /* delay report value is 2 bytes */ + + if (buf->len != sizeof(value)) + { + *errcode = BT_AVDTP_BAD_LENGTH; + return -EINVAL; + } + + ep = CONTAINER_OF(sep, struct bt_a2dp_ep, sep); + if (ep->stream == NULL) + { + *errcode = BT_AVDTP_SEP_NOT_IN_USE; + return -EINVAL; + } + + value = net_buf_pull_be16(buf); + stream = ep->stream; + *errcode = 0; + + if (a2dp_cb != NULL && a2dp_cb->delay_report_req != NULL) + { + int err; + + err = a2dp_cb->delay_report_req(stream, value, errcode); + if (err != 0 && *errcode == 0) + { + *errcode = BT_AVDTP_BAD_ACP_SEID; + } + } + + if (*errcode == 0) + { + if (stream->ops != NULL && stream->ops->delay_report != NULL) + { + stream->ops->delay_report(stream, value); + } + } + + return (*errcode == 0) ? 0 : -EINVAL; +} +#endif + +static bool bt_a2dp_req_check_busy(struct bt_avdtp_req *req) +{ + if (req->func != NULL) + { + return true; + } + + return false; +} + +static void bt_a2dp_req_clear_busy(struct bt_avdtp_req *req) +{ + req->func = NULL; +} + +static void bt_a2dp_ctrl_req_clear_busy(struct bt_a2dp *a2dp) +{ + bt_a2dp_req_clear_busy(&a2dp->ctrl_param.req); +} + +static int bt_a2dp_set_config_cb(struct bt_avdtp_req *req, + struct net_buf *buf) +{ + uint8_t status; + struct bt_a2dp_ep *ep; + struct bt_a2dp_stream *stream; + struct bt_a2dp_stream_ops *ops; + + ep = CONTAINER_OF(SET_CONF_REQ(req)->sep, struct bt_a2dp_ep, sep); + + status = req->status; + bt_a2dp_req_clear_busy(req); + + if (ep->stream == NULL) + { + return -EINVAL; + } + + stream = ep->stream; + LOG_DBG("SET CONFIGURATION result:%d", status); + + if (status == BT_AVDTP_SUCCESS) + { + struct bt_avdtp_set_configuration_params *param = SET_CONF_REQ(req); + + DEBUGASSERT(param->codec_specific_ie_len <= + CONFIG_BT_A2DP_CODEC_MAX_IE_LEN); + + stream->codec_config.len = param->codec_specific_ie_len; + memcpy(&stream->codec_config.codec_ie[0], param->codec_specific_ie, + param->codec_specific_ie_len); + } + + if ((a2dp_cb != NULL) && (a2dp_cb->config_rsp != NULL)) + { + a2dp_cb->config_rsp(stream, status); + } + + ops = stream->ops; + if ((status == BT_AVDTP_SUCCESS) && (ops != NULL) && + (ops->configured != NULL)) + { + ops->configured(stream); + } + + return 0; +} + +static int bt_a2dp_get_capabilities_cb(struct bt_avdtp_req *req, + struct net_buf *buf) +{ + int err; + uint8_t *codec_info_element; + struct bt_a2dp *a2dp = GET_CAP_PARAM(GET_CAP_REQ(req)); + struct bt_a2dp_ep *ep = NULL; + struct bt_a2dp_ep_info *info; + uint16_t codec_info_element_len; + uint8_t codec_type; + uint8_t user_ret; + bool delay_report; + + LOG_DBG("GET CAPABILITIES result:%d", req->status); + + if ((a2dp->discover_cb_param == NULL) || + (a2dp->discover_cb_param->cb == NULL)) + { + return 0; + } + + if ((req->status != 0) || (buf == NULL)) + { + a2dp->discover_cb_param->cb(a2dp, NULL, NULL); + a2dp->discover_cb_param = NULL; + return 0; + } + + err = bt_avdtp_parse_capability_codec(buf, &codec_type, + &codec_info_element, + &codec_info_element_len, + &delay_report); + if (err) + { + LOG_DBG("codec capability parsing fail"); + goto next_discover; + } + + if (codec_info_element_len > CONFIG_BT_A2DP_CODEC_MAX_IE_LEN) + { + LOG_WRN("Received IE exceeds the max supported length (%u > %u)", + codec_info_element_len, + CONFIG_BT_A2DP_CODEC_MAX_IE_LEN); + goto next_discover; + } + + info = &a2dp->discover_cb_param->info; + info->codec_type = codec_type; + info->sep_info = &a2dp->discover_cb_param->seps_info[a2dp->get_cap_index]; + memcpy(&info->codec_cap.codec_ie, codec_info_element, + codec_info_element_len); + info->codec_cap.len = codec_info_element_len; + info->delay_report = delay_report; + + user_ret = a2dp->discover_cb_param->cb(a2dp, info, &ep); + + if (ep != NULL) + { + ep->codec_type = info->codec_type; + ep->sep.sep_info = *info->sep_info; + *ep->codec_cap = info->codec_cap; + ep->delay_report = info->delay_report; + ep->stream = NULL; + } + + if (user_ret == BT_A2DP_DISCOVER_EP_CONTINUE) + { + goto next_discover; + } + + a2dp->discover_cb_param = NULL; + return 0; + +next_discover: + if (bt_a2dp_get_sep_caps(a2dp) != 0) + { + a2dp->discover_cb_param->cb(a2dp, NULL, NULL); + a2dp->discover_cb_param = NULL; + } + + return 0; +} + +static void bt_a2dp_init_req(struct bt_avdtp_req *req, bt_avdtp_func_t cb) +{ + memset(req, 0, sizeof(*req)); + req->func = cb; +} + +static int bt_a2dp_get_sep_caps(struct bt_a2dp *a2dp) +{ + int err; + + if ((a2dp->discover_cb_param == NULL) || + (a2dp->discover_cb_param->cb == NULL)) + { + return -EINVAL; + } + + if (a2dp->get_cap_index == 0xffu) + { + a2dp->get_cap_index = 0U; + } + else + { + a2dp->get_cap_index++; + } + + for (; a2dp->get_cap_index < a2dp->peer_seps_count; a2dp->get_cap_index++) + { + if (a2dp->discover_cb_param->seps_info[ + a2dp->get_cap_index].media_type == BT_AVDTP_AUDIO) + { + memset(&a2dp->get_capabilities_param, 0U, + sizeof(a2dp->get_capabilities_param)); + bt_a2dp_init_req(&a2dp->get_capabilities_param.req, + bt_a2dp_get_capabilities_cb); + a2dp->get_capabilities_param.stream_endpoint_id = + a2dp->discover_cb_param->seps_info[a2dp->get_cap_index].id; + + /* The legacy Get Capabilities procedure is deprecated in cases + * where backwards compatibility with AVDTP 1.2 and earlier is + * irrelevant. + */ + + if (AVDTP_VERSION >= AVDTP_VERSION_1_3 && + a2dp->discover_cb_param->avdtp_version >= AVDTP_VERSION_1_3) + { + a2dp->get_capabilities_param.get_all_caps = true; + } + else + { + a2dp->get_capabilities_param.get_all_caps = false; + } + + err = bt_avdtp_get_capabilities(&a2dp->session, + &a2dp->get_capabilities_param); + + if (err) + { + LOG_DBG("AVDTP get codec_cap failed"); + a2dp->discover_cb_param->cb(a2dp, NULL, NULL); + a2dp->discover_cb_param = NULL; + } + + return 0; + } + } + + return -EINVAL; +} + +static int bt_a2dp_discover_cb(struct bt_avdtp_req *req, struct net_buf *buf) +{ + struct bt_a2dp *a2dp = DISCOVER_PARAM(DISCOVER_REQ(req)); + struct bt_avdtp_sep_info *sep_info; + int err; + uint8_t status; + + LOG_DBG("DISCOVER result:%d", req->status); + + status = req->status; + bt_a2dp_req_clear_busy(req); + + if (a2dp->discover_cb_param == NULL) + { + return -EINVAL; + } + + a2dp->peer_seps_count = 0U; + + if ((status == BT_AVDTP_SUCCESS) && (buf != NULL)) + { + if (a2dp->discover_cb_param->sep_count == 0) + { + if (a2dp->discover_cb_param->cb != NULL) + { + a2dp->discover_cb_param->cb(a2dp, NULL, NULL); + a2dp->discover_cb_param = NULL; + } + + return -EINVAL; + } + + do + { + sep_info = + &a2dp->discover_cb_param->seps_info[a2dp->peer_seps_count]; + err = bt_avdtp_parse_sep(buf, sep_info); + + if (err) + { + break; + } + + a2dp->peer_seps_count++; + LOG_DBG("id:%d, inuse:%d, media_type:%d, tsep:%d, ", sep_info->id, + sep_info->inuse, + sep_info->media_type, sep_info->tsep); + } + while (a2dp->peer_seps_count < a2dp->discover_cb_param->sep_count); + + /* trigger the getting capability */ + + a2dp->get_cap_index = 0xffu; + if (bt_a2dp_get_sep_caps(a2dp) != 0) + { + /* the peer_seps' caps is done. */ + + if (a2dp->discover_cb_param->cb != NULL) + { + a2dp->discover_cb_param->cb(a2dp, NULL, NULL); + a2dp->discover_cb_param = NULL; + } + } + } + else + { + if (a2dp->discover_cb_param->cb != NULL) + { + a2dp->discover_cb_param->cb(a2dp, NULL, NULL); + a2dp->discover_cb_param = NULL; + } + } + + return 0; +} + +int bt_a2dp_discover(struct bt_a2dp *a2dp, + struct bt_a2dp_discover_param *param) +{ + int err; + + if ((a2dp == NULL) || (param == NULL)) + { + return -EINVAL; + } + + if (a2dp->a2dp_state != INTERNAL_STATE_AVDTP_CONNECTED) + { + return -EIO; + } + + if (a2dp->discover_cb_param != NULL || + bt_a2dp_req_check_busy(&a2dp->discover_param.req)) + { + return -EBUSY; + } + + a2dp->discover_cb_param = param; + bt_a2dp_init_req(&a2dp->discover_param.req, bt_a2dp_discover_cb); + + err = bt_avdtp_discover(&a2dp->session, &a2dp->discover_param); + if (err) + { + bt_a2dp_req_clear_busy(&a2dp->discover_param.req); + + if (a2dp->discover_cb_param->cb != NULL) + { + a2dp->discover_cb_param->cb(a2dp, NULL, NULL); + } + + a2dp->discover_cb_param = NULL; + } + + return err; +} + +void bt_a2dp_stream_cb_register(struct bt_a2dp_stream *stream, + struct bt_a2dp_stream_ops *ops) +{ + DEBUGASSERT(stream); + stream->ops = ops; +} + +static inline void bt_a2dp_stream_config_set_param(struct bt_a2dp *a2dp, + struct bt_a2dp_codec_cfg *config, + bt_avdtp_func_t cb, + uint8_t remote_id, + uint8_t int_id, + uint8_t codec_type, + struct bt_avdtp_sep *sep) +{ + DEBUGASSERT(config->codec_config->len <= CONFIG_BT_A2DP_CODEC_MAX_IE_LEN); + + memset(&a2dp->set_config_param, 0U, sizeof(a2dp->set_config_param)); + bt_a2dp_init_req(&a2dp->set_config_param.req, cb); + a2dp->set_config_param.acp_stream_ep_id = remote_id; + a2dp->set_config_param.int_stream_endpoint_id = int_id; + a2dp->set_config_param.media_type = BT_AVDTP_AUDIO; + a2dp->set_config_param.media_codec_type = codec_type; + a2dp->set_config_param.codec_specific_ie_len = config->codec_config->len; + a2dp->set_config_param.codec_specific_ie = + &config->codec_config->codec_ie[0]; + a2dp->set_config_param.sep = sep; +} + +int bt_a2dp_stream_config( + struct bt_a2dp *a2dp, struct bt_a2dp_stream *stream, + struct bt_a2dp_ep *local_ep, struct bt_a2dp_ep *remote_ep, + struct bt_a2dp_codec_cfg *config) +{ + int err; + + if ((a2dp == NULL) || (stream == NULL) || (local_ep == NULL) || + (remote_ep == NULL) || + (config == NULL) || (config->codec_config == NULL) || + (config->codec_config->len > CONFIG_BT_A2DP_CODEC_MAX_IE_LEN)) + { + return -EINVAL; + } + + if ((local_ep->sep.sep_info.tsep == remote_ep->sep.sep_info.tsep) || + (local_ep->codec_type != remote_ep->codec_type)) + { + return -EINVAL; + } + + if (config->delay_report && (!local_ep->delay_report || + !remote_ep->delay_report)) + { + return -EINVAL; + } + + if (bt_a2dp_req_check_busy(&a2dp->set_config_param.req)) + { + return -EBUSY; + } + + stream->local_ep = local_ep; + stream->remote_ep = remote_ep; + stream->remote_ep_id = remote_ep->sep.sep_info.id; + stream->a2dp = a2dp; + local_ep->stream = stream; + remote_ep->stream = stream; + bt_a2dp_stream_config_set_param(a2dp, config, bt_a2dp_set_config_cb, + remote_ep->sep.sep_info.id, + local_ep->sep.sep_info.id, + local_ep->codec_type, + &local_ep->sep); + a2dp->set_config_param.delay_report = config->delay_report; + stream->delay_report = config->delay_report; + + err = bt_avdtp_set_configuration(&a2dp->session, &a2dp->set_config_param); + if (err != 0) + { + bt_a2dp_req_clear_busy(&a2dp->set_config_param.req); + } + + return err; +} + +typedef void (*bt_a2dp_rsp_cb)(struct bt_a2dp_stream *stream, + uint8_t rsp_err_code); +typedef void (*bt_a2dp_done_cb)(struct bt_a2dp_stream *stream); + +static int bt_a2dp_ctrl_cb(struct bt_avdtp_req *req, + bt_a2dp_rsp_cb rsp_cb, bt_a2dp_done_cb done_cb) +{ + uint8_t status; + struct bt_a2dp_ep *ep = CONTAINER_OF(CTRL_REQ(req)->sep, struct bt_a2dp_ep, + sep); + + status = req->status; + bt_a2dp_req_clear_busy(req); + + if (ep->stream == NULL) + { + return -EINVAL; + } + + LOG_DBG("ctrl result:%d", status); + + if (rsp_cb != NULL) + { + rsp_cb(ep->stream, status); + } + + if ((status == BT_AVDTP_SUCCESS) && (done_cb != NULL)) + { + done_cb(ep->stream); + } + + return 0; +} + +static int bt_a2dp_open_cb(struct bt_avdtp_req *req, struct net_buf *buf) +{ + bt_a2dp_rsp_cb rsp_cb = a2dp_cb != NULL ? a2dp_cb->establish_rsp : NULL; + + return bt_a2dp_ctrl_cb(req, rsp_cb, NULL); +} + +static int bt_a2dp_start_cb(struct bt_avdtp_req *req, struct net_buf *buf) +{ + struct bt_a2dp_ep *ep = CONTAINER_OF(CTRL_REQ(req)->sep, struct bt_a2dp_ep, + sep); + bt_a2dp_rsp_cb rsp_cb = a2dp_cb != NULL ? a2dp_cb->start_rsp : NULL; + bt_a2dp_done_cb done_cb = + (ep->stream != NULL && + ep->stream->ops != NULL) ? ep->stream->ops->started : NULL; + + return bt_a2dp_ctrl_cb(req, rsp_cb, done_cb); +} + +static int bt_a2dp_suspend_cb(struct bt_avdtp_req *req, struct net_buf *buf) +{ + struct bt_a2dp_ep *ep = CONTAINER_OF(CTRL_REQ(req)->sep, struct bt_a2dp_ep, + sep); + bt_a2dp_rsp_cb rsp_cb = a2dp_cb != NULL ? a2dp_cb->suspend_rsp : NULL; + bt_a2dp_done_cb done_cb = + (ep->stream != NULL && + ep->stream->ops != NULL) ? ep->stream->ops->suspended : NULL; + + return bt_a2dp_ctrl_cb(req, rsp_cb, done_cb); +} + +static int bt_a2dp_close_cb(struct bt_avdtp_req *req, struct net_buf *buf) +{ + bt_a2dp_rsp_cb rsp_cb = a2dp_cb != NULL ? a2dp_cb->release_rsp : NULL; + + /* When stream is released, the `stream->ops->released` will be called. */ + + return bt_a2dp_ctrl_cb(req, rsp_cb, NULL); +} + +static int bt_a2dp_abort_cb(struct bt_avdtp_req *req, struct net_buf *buf) +{ + bt_a2dp_rsp_cb rsp_cb = a2dp_cb != NULL ? a2dp_cb->abort_rsp : NULL; + + /* When stream is released, the `stream->ops->released` will be called. */ + + return bt_a2dp_ctrl_cb(req, rsp_cb, NULL); +} + +static int bt_a2dp_get_config_cb(struct bt_avdtp_req *req, + struct net_buf *buf) +{ + int err; + bool delay_report; + uint8_t status; + uint8_t codec_type; + uint8_t *codec_info_element; + uint16_t codec_info_element_len; + struct bt_a2dp_codec_cfg cfg; + struct bt_a2dp_codec_ie codec_config; + struct bt_a2dp_ep *ep = CONTAINER_OF(CTRL_REQ(req)->sep, struct bt_a2dp_ep, + sep); + + status = req->status; + bt_a2dp_req_clear_busy(req); + if (status != BT_AVDTP_SUCCESS) + { + if (a2dp_cb != NULL && a2dp_cb->get_config_rsp != NULL) + { + a2dp_cb->get_config_rsp(ep->stream, NULL, status); + } + + return 0; + } + + /* parse the configuration */ + + err = bt_avdtp_parse_capability_codec(buf, &codec_type, + &codec_info_element, + &codec_info_element_len, + &delay_report); + if (err != 0) + { + status = BT_AVDTP_BAD_LENGTH; + } + + if (err == 0 && codec_info_element_len > CONFIG_BT_A2DP_CODEC_MAX_IE_LEN) + { + LOG_WRN("Received IE exceeds the max supported length (%u > %u)", + codec_info_element_len, + CONFIG_BT_A2DP_CODEC_MAX_IE_LEN); + status = BT_AVDTP_UNSUPPORTED_CONFIGURATION; + } + + if (status != BT_AVDTP_SUCCESS) + { + if (a2dp_cb != NULL && a2dp_cb->get_config_rsp != NULL) + { + a2dp_cb->get_config_rsp(ep->stream, NULL, status); + } + + return -EINVAL; + } + + cfg.delay_report = delay_report; + cfg.codec_config = &codec_config; + cfg.codec_config->len = codec_info_element_len; + memcpy(&cfg.codec_config->codec_ie[0], codec_info_element, + codec_info_element_len); + + if (a2dp_cb != NULL && a2dp_cb->get_config_rsp != NULL) + { + a2dp_cb->get_config_rsp(ep->stream, &cfg, status); + } + + return 0; +} + +#ifdef CONFIG_BT_A2DP_SINK +static int bt_a2dp_delay_report_cb(struct bt_avdtp_req *req, + struct net_buf *buf) +{ + uint8_t status; + struct bt_a2dp_ep *ep = CONTAINER_OF(DELAY_REPORT_REQ(req)->sep, + struct bt_a2dp_ep, sep); + + status = req->status; + bt_a2dp_req_clear_busy(req); + + if (ep->stream == NULL) + { + return -EINVAL; + } + + if (a2dp_cb != NULL && a2dp_cb->delay_report_rsp != NULL) + { + a2dp_cb->delay_report_rsp(ep->stream, status); + } + + return 0; +} +#endif + +static void bt_a2dp_init_ctrl_req(struct bt_avdtp_ctrl_params *ctrl_param, + bt_avdtp_func_t cb) +{ + memset(ctrl_param, 0U, sizeof(*ctrl_param)); + ctrl_param->req.func = cb; +} + +static int bt_a2dp_stream_ctrl_pre(struct bt_a2dp_stream *stream, + bt_avdtp_func_t cb) +{ + struct bt_a2dp *a2dp; + + if ((stream == NULL) || (stream->local_ep == NULL) || + (stream->a2dp == NULL)) + { + return -EINVAL; + } + + a2dp = stream->a2dp; + if (bt_a2dp_req_check_busy(&a2dp->ctrl_param.req)) + { + return -EBUSY; + } + + bt_a2dp_init_ctrl_req(&a2dp->ctrl_param, cb); + a2dp->ctrl_param.acp_stream_ep_id = + stream->remote_ep != NULL ? + stream->remote_ep->sep.sep_info.id : stream->remote_ep_id; + a2dp->ctrl_param.sep = &stream->local_ep->sep; + return 0; +} + +int bt_a2dp_stream_establish(struct bt_a2dp_stream *stream) +{ + int err; + struct bt_a2dp *a2dp = stream->a2dp; + + err = bt_a2dp_stream_ctrl_pre(stream, bt_a2dp_open_cb); + if (err) + { + return err; + } + + err = bt_avdtp_open(&a2dp->session, &a2dp->ctrl_param); + if (err != 0) + { + bt_a2dp_ctrl_req_clear_busy(a2dp); + } + + return err; +} + +int bt_a2dp_stream_release(struct bt_a2dp_stream *stream) +{ + int err; + struct bt_a2dp *a2dp = stream->a2dp; + + err = bt_a2dp_stream_ctrl_pre(stream, bt_a2dp_close_cb); + if (err) + { + return err; + } + + err = bt_avdtp_close(&a2dp->session, &a2dp->ctrl_param); + if (err != 0) + { + bt_a2dp_ctrl_req_clear_busy(a2dp); + } + + return err; +} + +int bt_a2dp_stream_start(struct bt_a2dp_stream *stream) +{ + int err; + struct bt_a2dp *a2dp = stream->a2dp; + + err = bt_a2dp_stream_ctrl_pre(stream, bt_a2dp_start_cb); + if (err) + { + return err; + } + + err = bt_avdtp_start(&a2dp->session, &a2dp->ctrl_param); + if (err != 0) + { + bt_a2dp_ctrl_req_clear_busy(a2dp); + } + + return err; +} + +int bt_a2dp_stream_suspend(struct bt_a2dp_stream *stream) +{ + int err; + struct bt_a2dp *a2dp = stream->a2dp; + + err = bt_a2dp_stream_ctrl_pre(stream, bt_a2dp_suspend_cb); + if (err) + { + return err; + } + + err = bt_avdtp_suspend(&a2dp->session, &a2dp->ctrl_param); + if (err != 0) + { + bt_a2dp_ctrl_req_clear_busy(a2dp); + } + + return err; +} + +int bt_a2dp_stream_abort(struct bt_a2dp_stream *stream) +{ + int err; + struct bt_a2dp *a2dp = stream->a2dp; + + err = bt_a2dp_stream_ctrl_pre(stream, bt_a2dp_abort_cb); + if (err) + { + return err; + } + + err = bt_avdtp_abort(&a2dp->session, &a2dp->ctrl_param); + if (err != 0) + { + bt_a2dp_ctrl_req_clear_busy(a2dp); + } + + return err; +} + +int bt_a2dp_stream_reconfig(struct bt_a2dp_stream *stream, + struct bt_a2dp_codec_cfg *config) +{ + int err; + uint8_t remote_id; + + if ((stream == NULL) || (config == NULL) || + (config->codec_config == NULL) || + (config->codec_config->len > CONFIG_BT_A2DP_CODEC_MAX_IE_LEN)) + { + return -EINVAL; + } + + if (bt_a2dp_req_check_busy(&stream->a2dp->set_config_param.req)) + { + return -EBUSY; + } + + remote_id = stream->remote_ep != NULL ? + stream->remote_ep->sep.sep_info.id : stream->remote_ep_id; + bt_a2dp_stream_config_set_param(stream->a2dp, config, + bt_a2dp_set_config_cb, + remote_id, + stream->local_ep->sep.sep_info.id, + stream->local_ep->codec_type, + &stream->local_ep->sep); + err = bt_avdtp_reconfigure(&stream->a2dp->session, + &stream->a2dp->set_config_param); + if (err != 0) + { + bt_a2dp_req_clear_busy(&stream->a2dp->set_config_param.req); + } + + return err; +} + +int bt_a2dp_stream_get_config(struct bt_a2dp_stream *stream) +{ + int err; + struct bt_a2dp *a2dp = stream->a2dp; + + err = bt_a2dp_stream_ctrl_pre(stream, bt_a2dp_get_config_cb); + if (err != 0) + { + return err; + } + + err = bt_avdtp_get_configuration(&a2dp->session, &a2dp->ctrl_param); + if (err != 0) + { + bt_a2dp_ctrl_req_clear_busy(a2dp); + } + + return err; +} + +uint32_t bt_a2dp_get_mtu(struct bt_a2dp_stream *stream) +{ + if ((stream == NULL) || (stream->local_ep == NULL)) + { + return 0; + } + + return bt_avdtp_get_media_mtu(&stream->local_ep->sep); +} + +#if defined(CONFIG_BT_A2DP_SOURCE) +struct net_buf *bt_a2dp_stream_create_pdu(struct net_buf_pool *pool, + clock_t timeout) +{ + struct net_buf *buf; + + buf = bt_l2cap_create_pdu_timeout(pool, 0, timeout); + if (buf == NULL) + { + return NULL; + } + + /* add AVDTP header */ + + if (net_buf_tailroom(buf) < sizeof(struct bt_avdtp_media_hdr)) + { + net_buf_unref(buf); + return NULL; + } + + net_buf_add(buf, sizeof(struct bt_avdtp_media_hdr)); + + return buf; +} + +int bt_a2dp_stream_send( + struct bt_a2dp_stream *stream, struct net_buf *buf, uint16_t seq_num, + uint32_t ts) +{ + struct bt_avdtp_media_hdr *media_hdr; + + if (stream == NULL || stream->local_ep == NULL) + { + return -EINVAL; + } + + media_hdr = (struct bt_avdtp_media_hdr *)buf->data; + memset(media_hdr, 0, sizeof(struct bt_avdtp_media_hdr)); + + if (stream->local_ep->codec_type == BT_A2DP_SBC) + { + media_hdr->playload_type = A2DP_SBC_PAYLOAD_TYPE; + } + + media_hdr->RTP_version = BT_AVDTP_RTP_VERSION; + media_hdr->synchronization_source = 0U; + + /* update time_stamp in the buf */ + + sys_put_be32(ts, (uint8_t *)&media_hdr->time_stamp); + + /* update sequence_number in the buf */ + + sys_put_be16(seq_num, (uint8_t *)&media_hdr->sequence_number); + + /* send the buf */ + + return bt_avdtp_send_media_data(&stream->local_ep->sep, buf); +} +#endif + +#if defined(CONFIG_BT_A2DP_SINK) +int bt_a2dp_stream_delay_report(struct bt_a2dp_stream *stream, + uint16_t delay) +{ + int err; + struct bt_a2dp *a2dp; + + if (stream == NULL) + { + return -EINVAL; + } + + if (stream->local_ep == NULL || stream->a2dp == NULL) + { + return -EINVAL; + } + + if (stream->local_ep->sep.sep_info.tsep != BT_AVDTP_SINK) + { + LOG_ERR("Delay report is only supported for sink endpoint"); + return -ENOTSUP; + } + + a2dp = stream->a2dp; + if (bt_a2dp_req_check_busy(&a2dp->delay_report_param.req)) + { + return -EBUSY; + } + + memset(&a2dp->delay_report_param, 0U, sizeof(a2dp->delay_report_param)); + a2dp->delay_report_param.req.func = bt_a2dp_delay_report_cb; + a2dp->delay_report_param.sep = &stream->local_ep->sep; + a2dp->delay_report_param.delay_report = delay; + a2dp->delay_report_param.acp_stream_ep_id = + stream->remote_ep != NULL ? + stream->remote_ep->sep.sep_info.id : stream->remote_ep_id; + + err = bt_avdtp_delay_report(&a2dp->session, &a2dp->delay_report_param); + if (err != 0) + { + bt_a2dp_req_clear_busy(&a2dp->delay_report_param.req); + } + + return err; +} +#endif + +static const struct bt_avdtp_ops_cb signaling_avdtp_ops = +{ + .connected = a2dp_connected, + .disconnected = a2dp_disconnected, + .alloc_buf = a2dp_alloc_buf, + .discovery_ind = a2dp_discovery_ind, + .get_capabilities_ind = a2dp_get_capabilities_ind, + .set_configuration_ind = a2dp_set_config_ind, + .get_configuration_ind = a2dp_get_config_ind, + .re_configuration_ind = a2dp_re_config_ind, + .open_ind = a2dp_open_ind, + .start_ind = a2dp_start_ind, + .close_ind = a2dp_close_ind, + .suspend_ind = a2dp_suspend_ind, + .abort_ind = a2dp_abort_ind, +#ifdef CONFIG_BT_A2DP_SOURCE + .delay_report_ind = a2dp_delay_report_ind, +#endif +}; + +static void stream_connected(struct bt_avdtp_sep *sep) +{ + struct bt_a2dp_ep *ep; + struct bt_a2dp_stream *stream; + + DEBUGASSERT(sep); + ep = CONTAINER_OF(sep, struct bt_a2dp_ep, sep); + if (ep->stream == NULL) + { + return; + } + + stream = ep->stream; + if (stream->ops != NULL && stream->ops->established != NULL) + { + stream->ops->established(stream); + } +} + +static void stream_disconnected(struct bt_avdtp_sep *sep) +{ + struct bt_a2dp_ep *ep; + struct bt_a2dp_stream *stream; + + DEBUGASSERT(sep); + ep = CONTAINER_OF(sep, struct bt_a2dp_ep, sep); + if (ep->stream == NULL) + { + return; + } + + stream = ep->stream; + ep->stream = NULL; + if (stream->ops != NULL && stream->ops->released != NULL) + { + stream->ops->released(stream); + } +} + +static const struct bt_avdtp_sep_ops source_sep_ops = +{ + .connected = stream_connected, + .disconnected = stream_disconnected, +}; + +#if defined(CONFIG_BT_A2DP_SINK) +static const struct bt_avdtp_sep_ops sink_sep_ops = +{ + .connected = stream_connected, + .disconnected = stream_disconnected, + .media_data_cb = bt_a2dp_media_data_callback, +}; +#endif + +int a2dp_accept(struct bt_conn *conn, struct bt_avdtp **session) +{ + struct bt_a2dp *a2dp; + + a2dp = a2dp_get_connection(conn); + if (!a2dp) + { + return -ENOMEM; + } + + *session = &(a2dp->session); + a2dp->session.ops = &signaling_avdtp_ops; + LOG_DBG("session: %p", &(a2dp->session)); + + return 0; +} + +/* The above callback structures need to be packed and passed to AVDTP */ + +static struct bt_avdtp_event_cb avdtp_cb = +{ + .accept = a2dp_accept, +}; + +struct bt_a2dp *bt_a2dp_connect(struct bt_conn *conn) +{ + struct bt_a2dp *a2dp; + int err; + + a2dp = a2dp_get_connection(conn); + if (!a2dp) + { + LOG_ERR("Cannot allocate memory"); + return NULL; + } + + a2dp->a2dp_state = INTERNAL_STATE_IDLE; + a2dp->session.ops = &signaling_avdtp_ops; + + err = bt_avdtp_connect(conn, &(a2dp->session)); + if (err < 0) + { + LOG_DBG("AVDTP Connect failed"); + return NULL; + } + + LOG_DBG("Connect request sent"); + return a2dp; +} + +int bt_a2dp_disconnect(struct bt_a2dp *a2dp) +{ + DEBUGASSERT(a2dp); + return bt_avdtp_disconnect(&a2dp->session); +} + +int bt_a2dp_register_ep(struct bt_a2dp_ep *ep, + uint8_t media_type, uint8_t sep_type) +{ + int err; + + DEBUGASSERT(ep); + +#if defined(CONFIG_BT_A2DP_SINK) + if (sep_type == BT_AVDTP_SINK) + { + ep->sep.ops = &sink_sep_ops; + } + else + { + ep->sep.ops = &source_sep_ops; + } +#else + ep->sep.ops = &source_sep_ops; +#endif + + err = bt_avdtp_register_sep(media_type, sep_type, &(ep->sep)); + if (err < 0) + { + return err; + } + + return 0; +} + +struct bt_conn *bt_a2dp_get_conn(struct bt_a2dp *a2dp) +{ + if (a2dp == NULL) + { + return NULL; + } + + if (!a2dp->session.br_chan.chan.conn) + { + return NULL; + } + + return bt_conn_ref(a2dp->session.br_chan.chan.conn); +} + +int bt_a2dp_register_cb(struct bt_a2dp_cb *cb) +{ + int err; + + if (cb == NULL) + { + return -EINVAL; + } + + if (a2dp_cb != NULL) + { + return -EALREADY; + } + + a2dp_cb = cb; + + /* Register event handlers with AVDTP */ + + err = bt_avdtp_register(&avdtp_cb); + if ((err < 0) && (err != -EALREADY)) + { + a2dp_cb = NULL; + LOG_ERR("A2DP registration failed (err %d)", err); + return err; + } + + LOG_DBG("A2DP cbs registered."); + + return 0; +} diff --git a/libs/zblue/host/classic/a2dp_codec_sbc.c b/libs/zblue/host/classic/a2dp_codec_sbc.c new file mode 100644 index 0000000000000..0eabbf240e3c4 --- /dev/null +++ b/libs/zblue/host/classic/a2dp_codec_sbc.c @@ -0,0 +1,171 @@ +/**************************************************************************** + * libs/zblue/host/classic/a2dp_codec_sbc.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +uint8_t bt_a2dp_sbc_get_channel_num( + struct bt_a2dp_codec_sbc_params *sbc_codec) +{ + DEBUGASSERT(sbc_codec != NULL); + + if (sbc_codec->config[0] & A2DP_SBC_CH_MODE_MONO) + { + return 1U; + } + else if (sbc_codec->config[0] & A2DP_SBC_CH_MODE_DUAL) + { + return 2U; + } + else if (sbc_codec->config[0] & A2DP_SBC_CH_MODE_STEREO) + { + return 2U; + } + else if (sbc_codec->config[0] & A2DP_SBC_CH_MODE_JOINT) + { + return 2U; + } + else + { + return 0U; + } +} + +enum sbc_ch_mode bt_a2dp_sbc_get_channel_mode( + struct bt_a2dp_codec_sbc_params *sbc_codec) +{ + DEBUGASSERT(sbc_codec != NULL); + + if (sbc_codec->config[0] & A2DP_SBC_CH_MODE_MONO) + { + return SBC_CH_MODE_MONO; + } + else if (sbc_codec->config[0] & A2DP_SBC_CH_MODE_DUAL) + { + return SBC_CH_MODE_DUAL_CHANNEL; + } + else if (sbc_codec->config[0] & A2DP_SBC_CH_MODE_STEREO) + { + return SBC_CH_MODE_STEREO; + } + else if (sbc_codec->config[0] & A2DP_SBC_CH_MODE_JOINT) + { + return SBC_CH_MODE_JOINT_STEREO; + } + else + { + return SBC_CH_MODE_MONO; + } +} + +uint32_t bt_a2dp_sbc_get_sampling_frequency( + struct bt_a2dp_codec_sbc_params *sbc_codec) +{ + DEBUGASSERT(sbc_codec != NULL); + + if (sbc_codec->config[0] & A2DP_SBC_SAMP_FREQ_16000) + { + return 16000U; + } + else if (sbc_codec->config[0] & A2DP_SBC_SAMP_FREQ_32000) + { + return 32000U; + } + else if (sbc_codec->config[0] & A2DP_SBC_SAMP_FREQ_44100) + { + return 44100U; + } + else if (sbc_codec->config[0] & A2DP_SBC_SAMP_FREQ_48000) + { + return 48000U; + } + else + { + return 0U; + } +} + +uint8_t bt_a2dp_sbc_get_subband_num( + struct bt_a2dp_codec_sbc_params *sbc_codec) +{ + DEBUGASSERT(sbc_codec != NULL); + + if (sbc_codec->config[1] & A2DP_SBC_SUBBAND_4) + { + return 4U; + } + else if (sbc_codec->config[1] & A2DP_SBC_SUBBAND_8) + { + return 8U; + } + else + { + return 0U; + } +} + +uint8_t bt_a2dp_sbc_get_block_length( + struct bt_a2dp_codec_sbc_params *sbc_codec) +{ + DEBUGASSERT(sbc_codec != NULL); + + if (sbc_codec->config[1] & A2DP_SBC_BLK_LEN_4) + { + return 4U; + } + else if (sbc_codec->config[1] & A2DP_SBC_BLK_LEN_8) + { + return 8U; + } + else if (sbc_codec->config[1] & A2DP_SBC_BLK_LEN_12) + { + return 12U; + } + else if (sbc_codec->config[1] & A2DP_SBC_BLK_LEN_16) + { + return 16U; + } + else + { + return 0U; + } +} + +enum sbc_alloc_mthd bt_a2dp_sbc_get_allocation_method( + struct bt_a2dp_codec_sbc_params *sbc_codec) +{ + DEBUGASSERT(sbc_codec != NULL); + + if (sbc_codec->config[1] & A2DP_SBC_ALLOC_MTHD_SNR) + { + return SBC_ALLOC_MTHD_SNR; + } + else + { + return SBC_ALLOC_MTHD_LOUDNESS; + } +} diff --git a/libs/zblue/host/classic/at.c b/libs/zblue/host/classic/at.c new file mode 100644 index 0000000000000..b08b80838099e --- /dev/null +++ b/libs/zblue/host/classic/at.c @@ -0,0 +1,934 @@ +/**************************************************************************** + * libs/zblue/host/classic/at.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file at.c Generic AT command handling library implementation + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include "at.h" + +typedef bool (*pull_char_t)(uint8_t *c, void *data); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void pull_chars(struct net_buf_simple *buf, + pull_char_t cb, void *data) +{ + while (buf->len > 0) + { + if (!cb(buf->data, data)) + { + break; + } + + net_buf_simple_pull_u8(buf); + } +} + +struct at_comma_data +{ + bool found; +}; + +static bool pull_comma(uint8_t *c, void *data) +{ + struct at_comma_data *comma_data = data; + + if (comma_data->found) + { + return false; + } + + comma_data->found = *c == ','; + return comma_data->found; +} + +static void next_list(struct at_client *at) +{ + struct at_comma_data data; + + data.found = false; + + pull_chars(&at->rsp_buf, pull_comma, &data); +} + +int at_check_byte(struct net_buf *buf, char check_byte) +{ + const unsigned char *str = buf->data; + + if (*str != check_byte) + { + return -EINVAL; + } + + net_buf_pull(buf, 1); + + return 0; +} + +static bool pull_space(uint8_t *c, void *data) +{ + return *c == ' '; +} + +static void skip_space(struct at_client *at) +{ + pull_chars(&at->rsp_buf, pull_space, NULL); +} + +struct at_number_data +{ + uint32_t val; + bool found; +}; + +static bool pull_number(uint8_t *c, void *data) +{ + struct at_number_data *num_data = data; + + if (isdigit((unsigned char)(*c)) == 0) + { + return false; + } + + num_data->found = true; + num_data->val = num_data->val * 10 + *c - (uint8_t)'0'; + return true; +} + +int at_get_number(struct at_client *at, uint32_t *val) +{ + struct at_number_data data; + + skip_space(at); + + data.found = false; + data.val = 0; + + pull_chars(&at->rsp_buf, pull_number, &data); + + if (!data.found) + { + return -ENODATA; + } + + *val = data.val; + + next_list(at); + return 0; +} + +static bool str_has_prefix(struct at_client *at, const char *prefix) +{ + const size_t len = strlen(prefix); + + if (at->rsp_buf.len < len) + { + return false; + } + + if (memcmp(at->rsp_buf.data, prefix, len) != 0) + { + return false; + } + + return true; +} + +static int at_parse_result(struct at_client *at, + struct net_buf *buf, enum at_result *result) +{ + const char *ok = "OK"; + const char *error = "ERROR"; + const size_t ok_len = strlen(ok); + const size_t error_len = strlen(error); + + /* Map the result and check for end lf */ + + if ((at->rsp_buf.len >= ok_len) && (memcmp(at->rsp_buf.data, ok, + ok_len) == 0) && + (at_check_byte(buf, '\n') == 0)) + { + *result = AT_RESULT_OK; + return 0; + } + + if ((at->rsp_buf.len >= error_len) && (memcmp(at->rsp_buf.data, error, + error_len) == 0) && + (at_check_byte(buf, '\n') == 0)) + { + *result = AT_RESULT_ERROR; + return 0; + } + + return -ENOMSG; +} + +static void init_rsp_buffer(struct at_client *at, struct net_buf *buf) +{ + at->rsp_buf.data = buf->data; + at->rsp_buf.__buf = buf->data; + at->rsp_buf.size = buf->len; + /* Although the valid data is buf->data[0:buf->len], at->rsp_buf.len is set + * to 0 here. This is because it is unclear how much data needs to be + * processed. The value at->rsp_buf.len will be updated later when + * preprocess the received AT responses or unsolicited response code. + */ + + at->rsp_buf.len = 0; +} + +static int get_cmd_value( + struct at_client *at, struct net_buf *buf, char stop_byte, + enum at_cmd_state cmd_state) +{ + if (at->rsp_buf.len == 0) + { + init_rsp_buffer(at, buf); + } + + while (buf->len > 0) + { + if (buf->data[0] == stop_byte) + { + at->cmd_state = cmd_state; + net_buf_pull_u8(buf); + break; + } + + net_buf_simple_add(&at->rsp_buf, sizeof(uint8_t)); + net_buf_pull_u8(buf); + } + + if (at->rsp_buf.len == 0) + { + return -ENODATA; + } + + return 0; +} + +static bool is_stop_byte(char target, char *stop_string) +{ + return (strchr(stop_string, target) != NULL); +} + +static bool is_vgm_or_vgs(struct at_client *at) +{ + const char *vgm = "VGM"; + const char *vgs = "VGS"; + const size_t vgm_len = strlen(vgm); + const size_t vgs_len = strlen(vgs); + + if ((at->rsp_buf.len >= vgm_len) && (memcmp(at->rsp_buf.data, vgm, + vgm_len) == 0)) + { + return true; + } + + if ((at->rsp_buf.len >= vgs_len) && (memcmp(at->rsp_buf.data, vgs, + vgs_len) == 0)) + { + return true; + } + + return false; +} + +static int get_response_string( + struct at_client *at, struct net_buf *buf, char *stop_string, + enum at_state state) +{ + if (at->rsp_buf.len == 0) + { + init_rsp_buffer(at, buf); + } + + while (buf->len > 0) + { + if (is_stop_byte(buf->data[0], stop_string)) + { + char stop_byte = buf->data[0]; + + at->state = state; + if ((stop_byte == '=') && !is_vgm_or_vgs(at)) + { + return -EINVAL; + } + + net_buf_pull_u8(buf); + break; + } + + net_buf_simple_add(&at->rsp_buf, sizeof(uint8_t)); + net_buf_pull_u8(buf); + } + + if (at->rsp_buf.len == 0) + { + return -ENODATA; + } + + return 0; +} + +static void reset_buffer(struct at_client *at) +{ + net_buf_simple_init_with_data(&at->rsp_buf, NULL, 0); +} + +static int at_state_start(struct at_client *at, struct net_buf *buf) +{ + int err; + + err = at_check_byte(buf, '\r'); + if (err < 0) + { + return err; + } + + at->state = AT_STATE_START_CR; + + return 0; +} + +static int at_state_start_cr(struct at_client *at, struct net_buf *buf) +{ + int err; + + err = at_check_byte(buf, '\n'); + if (err < 0) + { + return err; + } + + at->state = AT_STATE_START_LF; + + return 0; +} + +static int at_state_start_lf(struct at_client *at, struct net_buf *buf) +{ + reset_buffer(at); + if (at_check_byte(buf, '+') == 0) + { + at->state = AT_STATE_GET_CMD_STRING; + return 0; + } + else if (isalpha(*buf->data) != 0) + { + at->state = AT_STATE_GET_RESULT_STRING; + return 0; + } + + return -ENODATA; +} + +static int at_state_get_cmd_string(struct at_client *at, struct net_buf *buf) +{ + return get_response_string(at, buf, ":=", AT_STATE_PROCESS_CMD); +} + +static bool is_cmer(struct at_client *at) +{ + const char *cmer = "CME ERROR"; + const size_t len = strlen(cmer); + + if (at->rsp_buf.len < len) + { + return false; + } + + if (memcmp(at->rsp_buf.data, cmer, len) == 0) + { + return true; + } + + return false; +} + +static int at_state_process_cmd(struct at_client *at, struct net_buf *buf) +{ + if (is_cmer(at)) + { + at->state = AT_STATE_PROCESS_AG_NW_ERR; + return 0; + } + + if (at->resp) + { + at->resp(at, buf); + at->resp = NULL; + return 0; + } + + at->state = AT_STATE_UNSOLICITED_CMD; + return 0; +} + +static int at_state_get_result_string(struct at_client *at, + struct net_buf *buf) +{ + return get_response_string(at, buf, "\r", AT_STATE_PROCESS_RESULT); +} + +static bool is_ring(struct at_client *at) +{ + const char *ring = "RING"; + const size_t len = strlen(ring); + + if (at->rsp_buf.len < len) + { + return false; + } + + if (memcmp(at->rsp_buf.data, ring, len) == 0) + { + return true; + } + + return false; +} + +static int at_state_process_result(struct at_client *at, struct net_buf *buf) +{ + enum at_cme cme_err; + enum at_result result; + + if (is_ring(at)) + { + at->state = AT_STATE_UNSOLICITED_CMD; + return 0; + } + + if (at_parse_result(at, buf, &result) == 0) + { + if (at->finish) + { + /* cme_err is 0 - Is invalid until result is + * AT_RESULT_CME_ERROR + */ + + cme_err = 0; + at->finish(at, result, cme_err); + } + } + + /* Reset the state to process unsolicited response */ + + at->cmd_state = AT_CMD_START; + at->state = AT_STATE_START; + + return 0; +} + +int cme_handle(struct at_client *at) +{ + enum at_cme cme_err; + uint32_t val; + + if (!at_get_number(at, &val) && val <= CME_ERROR_NETWORK_NOT_ALLOWED) + { + cme_err = val; + } + else + { + cme_err = CME_ERROR_UNKNOWN; + } + + if (at->finish) + { + at->finish(at, AT_RESULT_CME_ERROR, cme_err); + } + + return 0; +} + +static int at_state_process_ag_nw_err(struct at_client *at, + struct net_buf *buf) +{ + at->cmd_state = AT_CMD_GET_VALUE; + return at_parse_cmd_input(at, buf, NULL, cme_handle, AT_CMD_TYPE_NORMAL); +} + +static int at_state_unsolicited_cmd(struct at_client *at, + struct net_buf *buf) +{ + if (at->unsolicited) + { + return at->unsolicited(at, buf); + } + + return -ENODATA; +} + +/* The order of handler function should match the enum at_state */ + +static handle_parse_input_t parser_cb[] = +{ + at_state_start, /* AT_STATE_START */ + at_state_start_cr, /* AT_STATE_START_CR */ + at_state_start_lf, /* AT_STATE_START_LF */ + at_state_get_cmd_string, /* AT_STATE_GET_CMD_STRING */ + at_state_process_cmd, /* AT_STATE_PROCESS_CMD */ + at_state_get_result_string, /* AT_STATE_GET_RESULT_STRING */ + at_state_process_result, /* AT_STATE_PROCESS_RESULT */ + at_state_process_ag_nw_err, /* AT_STATE_PROCESS_AG_NW_ERR */ + at_state_unsolicited_cmd /* AT_STATE_UNSOLICITED_CMD */ +}; + +int at_parse_input(struct at_client *at, struct net_buf *buf) +{ + int ret; + + while (buf->len) + { + if (at->state < AT_STATE_START || at->state >= AT_STATE_END) + { + return -EINVAL; + } + + ret = parser_cb[at->state](at, buf); + if (ret < 0) + { + /* Reset the state in case of error */ + + at->cmd_state = AT_CMD_START; + at->state = AT_STATE_START; + return ret; + } + } + + return 0; +} + +static int at_cmd_start( + struct at_client *at, struct net_buf *buf, const char *prefix, + parse_val_t func, enum at_cmd_type type) +{ + if (!str_has_prefix(at, prefix)) + { + if (type == AT_CMD_TYPE_NORMAL) + { + at->state = AT_STATE_UNSOLICITED_CMD; + } + + return -ENODATA; + } + + if (type == AT_CMD_TYPE_OTHER) + { + /* Skip for Other type such as ..RING.. which does not have + * values to get processed. + */ + + at->cmd_state = AT_CMD_PROCESS_VALUE; + } + else + { + at->cmd_state = AT_CMD_GET_VALUE; + } + + return 0; +} + +static int at_cmd_get_value( + struct at_client *at, struct net_buf *buf, const char *prefix, + parse_val_t func, enum at_cmd_type type) +{ + /* Reset buffer before getting the values */ + + reset_buffer(at); + return get_cmd_value(at, buf, '\r', AT_CMD_PROCESS_VALUE); +} + +static int at_cmd_process_value( + struct at_client *at, struct net_buf *buf, const char *prefix, + parse_val_t func, enum at_cmd_type type) +{ + int ret; + + ret = func(at); + at->cmd_state = AT_CMD_STATE_END_LF; + + return ret; +} + +static int at_cmd_state_end_lf( + struct at_client *at, struct net_buf *buf, const char *prefix, + parse_val_t func, enum at_cmd_type type) +{ + int err; + + err = at_check_byte(buf, '\n'); + if (err < 0) + { + return err; + } + + at->cmd_state = AT_CMD_START; + at->state = AT_STATE_START; + return 0; +} + +/* The order of handler function should match the enum at_cmd_state */ + +static handle_cmd_input_t cmd_parser_cb[] = +{ + at_cmd_start, /* AT_CMD_START */ + at_cmd_get_value, /* AT_CMD_GET_VALUE */ + at_cmd_process_value, /* AT_CMD_PROCESS_VALUE */ + at_cmd_state_end_lf /* AT_CMD_STATE_END_LF */ +}; + +int at_parse_cmd_input( + struct at_client *at, struct net_buf *buf, const char *prefix, + parse_val_t func, enum at_cmd_type type) +{ + int ret; + + while (buf->len) + { + if (at->cmd_state < AT_CMD_START || at->cmd_state >= AT_CMD_STATE_END) + { + return -EINVAL; + } + + ret = cmd_parser_cb[at->cmd_state](at, buf, prefix, func, type); + if (ret < 0) + { + return ret; + } + + /* Check for main state, the end of cmd parsing and return. */ + + if (at->state == AT_STATE_START) + { + return 0; + } + } + + return 0; +} + +struct has_next_list_data +{ + bool found; +}; + +static bool at_has_next_list_cb(uint8_t *c, void *data) +{ + struct has_next_list_data *list_data = data; + + list_data->found = *c != ')'; + return false; +} + +bool at_has_next_list(struct at_client *at) +{ + struct has_next_list_data data; + + data.found = false; + + pull_chars(&at->rsp_buf, at_has_next_list_cb, &data); + + return data.found; +} + +struct open_list_data +{ + bool found; +}; + +static bool at_open_list_cb(uint8_t *c, void *data) +{ + struct open_list_data *list_data = data; + + if (list_data->found) + { + return false; + } + + list_data->found = *c == '('; + return list_data->found; +} + +int at_open_list(struct at_client *at) +{ + struct open_list_data data; + + skip_space(at); + + data.found = false; + + pull_chars(&at->rsp_buf, at_open_list_cb, &data); + + if (!data.found) + { + return -ENODATA; + } + + return 0; +} + +struct close_list_data +{ + bool found; +}; + +static bool at_close_list_cb(uint8_t *c, void *data) +{ + struct close_list_data *list_data = data; + + if (list_data->found) + { + return false; + } + + list_data->found = *c == ')'; + return list_data->found; +} + +int at_close_list(struct at_client *at) +{ + struct close_list_data data; + + skip_space(at); + + data.found = false; + + pull_chars(&at->rsp_buf, at_close_list_cb, &data); + + if (!data.found) + { + return -ENODATA; + } + + next_list(at); + + return 0; +} + +struct get_string_data +{ + uint8_t *start; + uint8_t *end; +}; + +static bool at_get_string_cb(uint8_t *c, void *data) +{ + struct get_string_data *str_data = data; + + if ((str_data->start != NULL) && (str_data->end != NULL)) + { + return false; + } + + if (str_data->start == NULL) + { + if (*c != '"') + { + return false; + } + + str_data->start = c + 1; + return true; + } + + if (*c == '"') + { + str_data->end = c; + } + + return true; +} + +int at_list_get_string(struct at_client *at, char *name, uint8_t len) +{ + struct get_string_data data; + size_t str_len; + + skip_space(at); + + data.start = NULL; + data.end = NULL; + + pull_chars(&at->rsp_buf, at_get_string_cb, &data); + + if (data.start >= data.end) + { + return -ENODATA; + } + + str_len = data.end - data.start; + if (str_len > (len - 1)) + { + return -ENOMEM; + } + + memcpy(name, data.start, str_len); + name[str_len] = '\0'; + + skip_space(at); + next_list(at); + + return 0; +} + +struct get_range_connector_data +{ + bool found; +}; + +static bool at_get_range_connector_cb(uint8_t *c, void *data) +{ + struct get_range_connector_data *connector_data = data; + + if (connector_data->found) + { + return false; + } + + connector_data->found = *c == '-'; + + return connector_data->found; +} + +int at_list_get_range(struct at_client *at, uint32_t *min, uint32_t *max) +{ + struct get_range_connector_data data; + uint32_t low; + uint32_t high; + int err; + + err = at_get_number(at, &low); + if (err < 0) + { + return err; + } + + data.found = false; + pull_chars(&at->rsp_buf, at_get_range_connector_cb, &data); + + err = at_get_number(at, &high); + if (err < 0) + { + return err; + } + + *min = low; + *max = high; + + next_list(at); + + return 0; +} + +void at_register_unsolicited(struct at_client *at, at_resp_cb_t unsolicited) +{ + at->unsolicited = unsolicited; +} + +void at_register(struct at_client *at, + at_resp_cb_t resp, at_finish_cb_t finish) +{ + at->resp = resp; + at->finish = finish; + at->state = AT_STATE_START; +} + +const char *at_get_string(struct at_client *at) +{ + struct get_string_data data; + + skip_space(at); + + data.start = NULL; + data.end = NULL; + + pull_chars(&at->rsp_buf, at_get_string_cb, &data); + + if (data.start >= data.end) + { + return NULL; + } + + data.end[0] = '\0'; + + skip_space(at); + next_list(at); + + return (const char *)data.start; +} + +struct get_raw_string_data +{ + uint8_t *start; + uint8_t *end; +}; + +static bool at_get_raw_string_cb(uint8_t *c, void *data) +{ + struct get_raw_string_data *str_data = data; + + if ((*c == ',') || (*c == ')')) + { + return false; + } + + if (str_data->start == NULL) + { + str_data->start = c; + } + + str_data->end = c; + return true; +} + +const char *at_get_raw_string(struct at_client *at, size_t *string_len) +{ + struct get_raw_string_data data; + + skip_space(at); + + data.start = NULL; + data.end = NULL; + + pull_chars(&at->rsp_buf, at_get_raw_string_cb, &data); + + if (data.start > data.end) + { + return NULL; + } + + if (string_len != NULL) + { + *string_len = data.end - data.start + 1; + } + + skip_space(at); + next_list(at); + + return (const char *)data.start; +} diff --git a/libs/zblue/host/classic/at.h b/libs/zblue/host/classic/at.h new file mode 100644 index 0000000000000..11e354914ac07 --- /dev/null +++ b/libs/zblue/host/classic/at.h @@ -0,0 +1,137 @@ +/**************************************************************************** + * libs/zblue/host/classic/at.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file at.h + * @brief Internal APIs for AT command handling. + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +enum at_result +{ + AT_RESULT_OK, + AT_RESULT_ERROR, + AT_RESULT_CME_ERROR +}; + +enum at_cme +{ + CME_ERROR_AG_FAILURE = 0, + CME_ERROR_NO_CONNECTION_TO_PHONE = 1, + CME_ERROR_OPERATION_NOT_ALLOWED = 3, + CME_ERROR_OPERATION_NOT_SUPPORTED = 4, + CME_ERROR_PH_SIM_PIN_REQUIRED = 5, + CME_ERROR_SIM_NOT_INSERTED = 10, + CME_ERROR_SIM_PIN_REQUIRED = 11, + CME_ERROR_SIM_PUK_REQUIRED = 12, + CME_ERROR_SIM_FAILURE = 13, + CME_ERROR_SIM_BUSY = 14, + CME_ERROR_INCORRECT_PASSWORD = 16, + CME_ERROR_SIM_PIN2_REQUIRED = 17, + CME_ERROR_SIM_PUK2_REQUIRED = 18, + CME_ERROR_MEMORY_FULL = 20, + CME_ERROR_INVALID_INDEX = 21, + CME_ERROR_MEMORY_FAILURE = 23, + CME_ERROR_TEXT_STRING_TOO_LONG = 24, + CME_ERROR_INVALID_CHARS_IN_TEXT_STRING = 25, + CME_ERROR_DIAL_STRING_TO_LONG = 26, + CME_ERROR_INVALID_CHARS_IN_DIAL_STRING = 27, + CME_ERROR_NO_NETWORK_SERVICE = 30, + CME_ERROR_NETWORK_TIMEOUT = 31, + CME_ERROR_NETWORK_NOT_ALLOWED = 32, + CME_ERROR_UNKNOWN = 33, +}; + +enum at_state +{ + AT_STATE_START, + AT_STATE_START_CR, + AT_STATE_START_LF, + AT_STATE_GET_CMD_STRING, + AT_STATE_PROCESS_CMD, + AT_STATE_GET_RESULT_STRING, + AT_STATE_PROCESS_RESULT, + AT_STATE_PROCESS_AG_NW_ERR, + AT_STATE_UNSOLICITED_CMD, + AT_STATE_END +}; + +enum at_cmd_state +{ + AT_CMD_START, + AT_CMD_GET_VALUE, + AT_CMD_PROCESS_VALUE, + AT_CMD_STATE_END_LF, + AT_CMD_STATE_END +}; + +enum at_cmd_type +{ + AT_CMD_TYPE_NORMAL, + AT_CMD_TYPE_UNSOLICITED, + AT_CMD_TYPE_OTHER +}; + +struct at_client; + +/* Callback at_resp_cb_t used to parse response value received for the + * particular AT command. Eg: +CIND= + */ + +typedef int (*at_resp_cb_t)(struct at_client *at, struct net_buf *buf); + +/* Callback at_finish_cb used to monitor the success or failure of the AT + * command received from server. Argument 'cme_err' is valid only when + * argument 'result' is equal to AT_RESULT_CME_ERROR + */ + +typedef int (*at_finish_cb_t)(struct at_client *at, + enum at_result result, enum at_cme cme_err); +typedef int (*parse_val_t)(struct at_client *at); +typedef int (*handle_parse_input_t)(struct at_client *at, + struct net_buf *buf); +typedef int( + *handle_cmd_input_t)(struct at_client *at, struct net_buf *buf, const char + **prefix, parse_val_t func, enum at_cmd_type type); + +struct at_client +{ + struct net_buf_simple rsp_buf; + uint8_t state; + uint8_t cmd_state; + at_resp_cb_t resp; + at_resp_cb_t unsolicited; + at_finish_cb_t finish; +}; + +/* Register the callback functions */ + +void at_register(struct at_client *at, + at_resp_cb_t resp, at_finish_cb_t finish); +void at_register_unsolicited(struct at_client *at, at_resp_cb_t unsolicited); +int at_get_number(struct at_client *at, uint32_t *val); + +/* This parsing will only works for non-fragmented net_buf */ + +int at_parse_input(struct at_client *at, struct net_buf *buf); + +/* This command parsing will only works for non-fragmented net_buf */ + +int at_parse_cmd_input( + struct at_client *at, struct net_buf *buf, const char *prefix, + parse_val_t func, enum at_cmd_type type); +int at_check_byte(struct net_buf *buf, char check_byte); +int at_list_get_range(struct at_client *at, uint32_t *min, uint32_t *max); +int at_list_get_string(struct at_client *at, char *name, uint8_t len); +int at_close_list(struct at_client *at); +int at_open_list(struct at_client *at); +bool at_has_next_list(struct at_client *at); +const char *at_get_string(struct at_client *at); +const char *at_get_raw_string(struct at_client *at, size_t *string_len); diff --git a/libs/zblue/host/classic/avctp.c b/libs/zblue/host/classic/avctp.c new file mode 100644 index 0000000000000..e1d3266eb6277 --- /dev/null +++ b/libs/zblue/host/classic/avctp.c @@ -0,0 +1,905 @@ +/**************************************************************************** + * libs/zblue/host/classic/avctp.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Audio Video Control Transport Protocol + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * Copyright (C) 2024 Xiaomi Corporation Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "avctp_internal.h" +#include +#include +#include "l2cap_br_internal.h" + +#define LOG_LEVEL CONFIG_BT_AVCTP_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_avctp); + +/** @brief AVCTP Header Sizes for different packet types */ +#define BT_AVCTP_HDR_SIZE_SINGLE (sizeof(struct bt_avctp_header_single)) +#define BT_AVCTP_HDR_SIZE_START (sizeof(struct bt_avctp_header_start)) +#define BT_AVCTP_HDR_SIZE_CONTINUE_END (sizeof(struct bt_avctp_header_continue_end)) + +#define AVCTP_TX_RETRY_DELAY (100) + +#define AVCTP_CHAN(_ch) CONTAINER_OF(_ch, struct bt_avctp, br_chan.chan) +/* L2CAP Server list */ + +static struct list_node avctp_l2cap_server = + LIST_INITIAL_VALUE(avctp_l2cap_server); +static sem_t avctp_lock; + +/* tx list for packets */ + +static struct list_node avctp_tx_list = LIST_INITIAL_VALUE(avctp_tx_list); +static struct k_work_delayable avctp_tx_work; + +struct avctp_buf_user_data +{ + struct bt_avctp *session; + uint16_t sent_len; + uint16_t pid; + uint8_t tid; + uint8_t num_packet; + bt_avctp_cr_t cr; + uint8_t ipid; +} __packed; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void avctp_tx_raise(int msec) +{ + if (list_is_empty(&avctp_tx_list)) + { + LOG_DBG("TX list is empty, no work to submit"); + return; + } + + LOG_DBG("kick TX"); + bt_work_schedule(&avctp_tx_work, MSEC2TICK(msec)); +} + +static void bt_avctp_clear_tx(struct bt_avctp *session) +{ + struct net_buf *buf; + struct net_buf *next; + struct avctp_buf_user_data *user_data; + const struct list_node *head = list_peek_head(&avctp_tx_list); + + list_for_every_container_safe(&avctp_tx_list, buf, next, node) + { + user_data = net_buf_user_data(buf); + + if (user_data->session != session) + { + continue; + } + + bool is_head = (&buf->node == head); + + list_find_and_remove(&avctp_tx_list, &buf->node); + net_buf_unref(buf); + + if (is_head) + { + avctp_tx_raise(0); + head = list_peek_head(&avctp_tx_list); + } + } +} + +static void avctp_l2cap_connected(struct bt_l2cap_chan *chan) +{ + struct bt_avctp *session; + + if (!chan) + { + LOG_ERR("Invalid AVCTP chan"); + return; + } + + session = AVCTP_CHAN(chan); + LOG_DBG("chan %p session %p", chan, session); + + if (session->ops && session->ops->connected) + { + session->ops->connected(session); + } +} + +static void avctp_l2cap_disconnected(struct bt_l2cap_chan *chan) +{ + struct bt_avctp *session; + + if (!chan) + { + LOG_ERR("Invalid AVCTP chan"); + return; + } + + session = AVCTP_CHAN(chan); + LOG_DBG("chan %p session %p", chan, session); + + net_buf_drop(&session->reassembly_buf); + + nxsem_wait_uninterruptible(&avctp_lock); + bt_avctp_clear_tx(session); + nxsem_post(&avctp_lock); + + if (session->ops && session->ops->disconnected) + { + session->ops->disconnected(session); + } +} + +static void avctp_l2cap_encrypt_changed(struct bt_l2cap_chan *chan, + uint8_t status) +{ + LOG_DBG(""); +} + +static struct net_buf *avctp_l2cap_alloc_buf(struct bt_l2cap_chan *chan) +{ + struct net_buf *buf; + struct bt_avctp *session; + + if (chan == NULL) + { + LOG_ERR("Invalid AVCTP chan"); + return NULL; + } + + session = AVCTP_CHAN(chan); + LOG_DBG("chan %p session %p", chan, session); + + if (session->ops != NULL && session->ops->alloc_buf != NULL) + { + buf = session->ops->alloc_buf(session); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + } + + return buf; + } + + return NULL; +} + +static void avctp_tx_cb(struct bt_conn *conn, void *user_data, int err) +{ + if (err < 0) + { + LOG_WRN("AVCTP TX completed with err=%d", err); + } + + avctp_tx_raise(0); +} + +static void avctp_tx_remove(struct net_buf *buf) +{ + nxsem_wait_uninterruptible(&avctp_lock); + list_find_and_remove(&avctp_tx_list, &buf->node); + nxsem_post(&avctp_lock); +} + +static int send_single_avctp( + struct bt_avctp *avctp, uint8_t tid, bt_avctp_cr_t cr, uint8_t ipid, + uint16_t pid, struct net_buf *buf) +{ + struct bt_avctp_header_single *avctp_hdr; + int err; + + if ((avctp == NULL) || (buf == NULL)) + { + return -EINVAL; + } + + if (net_buf_headroom(buf) < BT_AVCTP_HDR_SIZE_SINGLE) + { + LOG_ERR("Not enough headroom in buffer for AVCTP SINGLE header"); + return -ENOMEM; + } + + avctp_hdr = net_buf_push(buf, BT_AVCTP_HDR_SIZE_SINGLE); + memset(avctp_hdr, 0, BT_AVCTP_HDR_SIZE_SINGLE); + avctp_hdr->pid = sys_cpu_to_be16(pid); + BT_AVCTP_HDR_SET(&avctp_hdr->common, + ipid, + cr, + BT_AVCTP_PKT_TYPE_SINGLE, + tid); + + avctp_tx_remove(buf); + err = bt_l2cap_br_chan_send_cb(&avctp->br_chan.chan, buf, avctp_tx_cb, + NULL); + if (err < 0) + { + LOG_ERR("Failed to send l2cap chan (err: %d)", err); + net_buf_unref(buf); + } + + return err; +} + +static int send_fragmented_avctp( + struct bt_avctp *avctp, struct avctp_buf_user_data *user_data, + bt_avctp_pkt_type_t pkt_type, uint8_t *data, uint16_t data_len) +{ + struct net_buf *buf; + struct bt_avctp_header_start *avctp_hdr_start; + struct bt_avctp_header_continue_end *avctp_hdr_continue_end; + int err; + + if ((avctp == NULL) || (data == NULL)) + { + return -EINVAL; + } + + switch (pkt_type) + { + case BT_AVCTP_PKT_TYPE_START: + buf = bt_l2cap_create_pdu(avctp->tx_pool, BT_AVCTP_HDR_SIZE_START); + if (buf == NULL) + { + LOG_ERR("No buf for AVCTP START"); + return -ENOBUFS; + } + + avctp_hdr_start = net_buf_push(buf, BT_AVCTP_HDR_SIZE_START); + memset(avctp_hdr_start, 0, BT_AVCTP_HDR_SIZE_START); + avctp_hdr_start->pid = sys_cpu_to_be16(avctp->pid); + avctp_hdr_start->number_packet = user_data->num_packet; + BT_AVCTP_HDR_SET(&avctp_hdr_start->common, + user_data->ipid, + user_data->cr, + pkt_type, + + user_data->tid); + break; + + case BT_AVCTP_PKT_TYPE_CONTINUE: + case BT_AVCTP_PKT_TYPE_END: + buf = bt_l2cap_create_pdu(avctp->tx_pool, + BT_AVCTP_HDR_SIZE_CONTINUE_END); + if (buf == NULL) + { + LOG_ERR("No buf for AVCTP CONTINUE/END"); + return -ENOBUFS; + } + + avctp_hdr_continue_end = + net_buf_push(buf, BT_AVCTP_HDR_SIZE_CONTINUE_END); + memset(avctp_hdr_continue_end, 0, BT_AVCTP_HDR_SIZE_CONTINUE_END); + BT_AVCTP_HDR_SET(&avctp_hdr_continue_end->common, + user_data->ipid, + user_data->cr, + pkt_type, + + user_data->tid); + break; + + default: + LOG_ERR("Invalid packet type: %d", pkt_type); + return -EINVAL; + } + + if (data_len > 0U) + { + if (net_buf_tailroom(buf) < data_len) + { + LOG_WRN("Not enough tailroom for AVCTP payload (len: %d)", + data_len); + net_buf_unref(buf); + return -ENOMEM; + } + + net_buf_add_mem(buf, data, data_len); + } + + err = bt_l2cap_br_chan_send_cb(&avctp->br_chan.chan, buf, avctp_tx_cb, + NULL); + if (err < 0) + { + LOG_ERR("Failed to send l2cap chan (err: %d)", err); + net_buf_unref(buf); + } + + return err; +} + +static inline uint8_t get_avctp_packet_num(uint16_t total_bytes, + uint16_t max_payload_size) +{ + uint16_t first_payload = max_payload_size - BT_AVCTP_HDR_SIZE_START; + uint16_t fragment_payload = + max_payload_size - BT_AVCTP_HDR_SIZE_CONTINUE_END; + uint16_t remaining; + uint8_t additional_packet_num; + + if (total_bytes <= max_payload_size - BT_AVCTP_HDR_SIZE_SINGLE) + { + return 1; + } + + remaining = total_bytes - first_payload; + additional_packet_num = + (remaining + fragment_payload - 1) / fragment_payload; + + return 1 + additional_packet_num; +} + +static inline bool avctp_is_retryable_err(int err) +{ + return (err == -ENOBUFS); +} + +static void avctp_tx_processor(struct k_work *item) +{ + struct bt_avctp *session; + struct avctp_buf_user_data *user_data; + const struct list_node *node; + struct net_buf *buf; + int err = 0; + uint16_t chunk_size; + uint8_t pkt_type; + uint16_t max_payload_size; + + nxsem_wait_uninterruptible(&avctp_lock); + node = list_peek_head(&avctp_tx_list); + if (node == NULL) + { + nxsem_post(&avctp_lock); + LOG_DBG("No pending tx"); + return; + } + + nxsem_post(&avctp_lock); + + buf = CONTAINER_OF(node, struct net_buf, node); + + DEBUGASSERT(buf->user_data_size >= sizeof(*user_data)); + user_data = net_buf_user_data(buf); + + session = user_data->session; + DEBUGASSERT(session != NULL); + max_payload_size = MIN(session->br_chan.tx.mtu, + session->max_tx_payload_size); + + /* The SINGLE packet buf can be sent directly */ + + if (user_data->num_packet == 1) + { + err = send_single_avctp(session, user_data->tid, user_data->cr, + user_data->ipid, + user_data->pid, buf); + if (err < 0) + { + LOG_ERR("Failed to send SINGLE packet, len=%u", buf->len); + avctp_tx_raise(0); + } + + return; + } + + /* Multi-packet fragmented send */ + + if (user_data->sent_len == 0U) + { + pkt_type = BT_AVCTP_PKT_TYPE_START; + chunk_size = max_payload_size - BT_AVCTP_HDR_SIZE_START; + } + else + { + bool is_last = (buf->len <= (max_payload_size - + BT_AVCTP_HDR_SIZE_CONTINUE_END)); + + pkt_type = is_last ? BT_AVCTP_PKT_TYPE_END : + BT_AVCTP_PKT_TYPE_CONTINUE; + chunk_size = + MIN(buf->len, max_payload_size - + BT_AVCTP_HDR_SIZE_CONTINUE_END); + } + + err = send_fragmented_avctp(session, user_data, pkt_type, buf->data, + chunk_size); + if (err < 0) + { + if (avctp_is_retryable_err(err)) + { + LOG_WRN("Retry %p on %p", buf, session); + avctp_tx_raise(AVCTP_TX_RETRY_DELAY); + return; + } + + LOG_ERR("Failed to send fragment at offset %u", user_data->sent_len); + goto failed; + } + + net_buf_pull_mem(buf, chunk_size); + + user_data->sent_len += chunk_size; + if (buf->len == 0) + { + avctp_tx_remove(buf); + net_buf_unref(buf); + } + + return; + +failed: + avctp_tx_remove(buf); + net_buf_unref(buf); + avctp_tx_raise(0); +} + +static int bt_avctp_send_common( + struct bt_avctp *session, struct net_buf *buf, bt_avctp_cr_t cr, + uint8_t tid, uint16_t pid, uint8_t num_packet, uint8_t ipid) +{ + struct avctp_buf_user_data *user_data; + + DEBUGASSERT(buf->user_data_size >= sizeof(*user_data)); + + user_data = net_buf_user_data(buf); + user_data->session = session; + user_data->sent_len = 0; + user_data->cr = cr; + user_data->pid = pid; + user_data->ipid = ipid; + user_data->num_packet = num_packet; + user_data->tid = tid; + + nxsem_wait_uninterruptible(&avctp_lock); + list_add_tail(&avctp_tx_list, &buf->node); + nxsem_post(&avctp_lock); + + avctp_tx_raise(0); + return 0; +} + +static int dispatch_avctp_packet( + struct bt_avctp *session, struct net_buf *buf, + struct bt_avctp_header_common *hdr_common, uint16_t pid) +{ + struct net_buf *rsp; + int err; + bt_avctp_cr_t cr = BT_AVCTP_HDR_GET_CR(hdr_common); + uint8_t tid = BT_AVCTP_HDR_GET_TRANSACTION_LABLE(hdr_common); + + LOG_DBG("AVCTP msg received, cr:0x%X, tid:0x%X, pid: 0x%04X", + cr, + tid, + pid); + + if (pid == session->pid) + { + return session->ops->recv(session, buf, cr, tid); + } + + LOG_ERR("unsupported AVCTP PID received: 0x%04x", pid); + + if (cr == BT_AVCTP_CMD) + { + rsp = bt_avctp_create_pdu(NULL); + if (rsp == NULL) + { + DEBUGASSERT(0); + return -ENOMEM; + } + + err = + bt_avctp_send_common(session, rsp, BT_AVCTP_RESPONSE, tid, pid, 1, + BT_AVCTP_IPID_INVALID); + if (err < 0) + { + net_buf_unref(rsp); + LOG_ERR("AVCTP send fail, err = %d", err); + bt_avctp_disconnect(session); + return err; + } + } + + return 0; /* No need to report to the upper layer */ +} + +static int avctp_recv_fragmented(struct bt_avctp *avctp, struct net_buf *buf) +{ + struct bt_avctp_header_common *hdr_common = (void *)buf->data; + struct bt_avctp_header_start *hdr_reassembly; + bt_avctp_pkt_type_t pkt_type = BT_AVCTP_HDR_GET_PACKET_TYPE(hdr_common); + uint8_t tid = BT_AVCTP_HDR_GET_TRANSACTION_LABLE(hdr_common); + bt_avctp_cr_t cr = BT_AVCTP_HDR_GET_CR(hdr_common); + + if (pkt_type == BT_AVCTP_PKT_TYPE_START) + { + if (buf->len < BT_AVCTP_HDR_SIZE_START) + { + LOG_WRN("AVCTP HDR too short (len=%u, tid=%u, cr=%u)", buf->len, + tid, + cr); + goto failed; + } + + if (avctp->reassembly_buf != NULL) + { + LOG_WRN("Interleaving fragments not allowed (tid=%u, cr=%u)", tid, + cr); + net_buf_drop(&avctp->reassembly_buf); + } + + if (avctp->rx_pool == NULL) + { + LOG_WRN("Missing RX pool for AVCTP reassembly (tid=%u, " + "cr=%u)", tid, + cr); + goto failed; + } + + /* This runs in the Bluetooth RX workqueue, which is also the only + * context that releases reassembly buffers, so waiting here could + * never be satisfied. Drop the fragment instead and let the peer time + * out. + */ + + avctp->reassembly_buf = net_buf_alloc(avctp->rx_pool, 0); + if (avctp->reassembly_buf == NULL) + { + LOG_ERR("Failed to allocate reassembly buffer (tid=%u, " + "cr=%u)", tid, + cr); + goto failed; + } + + DEBUGASSERT(avctp->reassembly_buf->user_data_size >= sizeof( + *hdr_reassembly)); + hdr_reassembly = net_buf_user_data(avctp->reassembly_buf); + memcpy(hdr_reassembly, net_buf_pull_mem(buf, sizeof(*hdr_reassembly)), + sizeof(*hdr_reassembly)); + + if (net_buf_tailroom(avctp->reassembly_buf) < buf->len) + { + LOG_WRN("Not enough tailroom for start fragment"); + goto failed; + } + + net_buf_add_mem(avctp->reassembly_buf, buf->data, buf->len); + + if (hdr_reassembly->number_packet == 0) + { + LOG_WRN("Invalid start number_packet (%u) — expected > 0 (tid=" + "%u, cr=%u)", + hdr_reassembly->number_packet, tid, cr); + goto failed; + } + + hdr_reassembly->number_packet--; + return 0; + } + + if (avctp->reassembly_buf == NULL) + { + LOG_WRN("Continuation/end received without Start (tid=" + "%u, cr=%u, pkt_type=%u)", + tid, cr, + (uint8_t)pkt_type); + goto failed; + } + + DEBUGASSERT(avctp->reassembly_buf->user_data_size >= sizeof( + *hdr_reassembly)); + hdr_reassembly = net_buf_user_data(avctp->reassembly_buf); + + if ((BT_AVCTP_HDR_GET_TRANSACTION_LABLE(&hdr_reassembly->common) == tid) && + (BT_AVCTP_HDR_GET_CR(&hdr_reassembly->common) == cr)) + { + if (buf->len < BT_AVCTP_HDR_SIZE_CONTINUE_END) + { + LOG_WRN("invalid AVCTP continuation/end header received"); + goto failed; + } + + hdr_common = net_buf_pull_mem(buf, BT_AVCTP_HDR_SIZE_CONTINUE_END); + + if (net_buf_tailroom(avctp->reassembly_buf) < buf->len) + { + LOG_WRN("Not enough tailroom for continuation/end"); + goto failed; + } + + net_buf_add_mem(avctp->reassembly_buf, buf->data, buf->len); + + if (hdr_reassembly->number_packet == 0) + { + LOG_WRN("unexpected number_packet underflow (value=" + "%u, tid=%u, cr=%u)", + hdr_reassembly->number_packet, tid, cr); + goto failed; + } + + hdr_reassembly->number_packet--; + + if (pkt_type == BT_AVCTP_PKT_TYPE_END) + { + if (hdr_reassembly->number_packet > 0) + { + LOG_WRN("Fragments remaining on END (remaining=" + "%u, tid=%u, cr=%u)", + hdr_reassembly->number_packet, tid, cr); + goto failed; + } + + dispatch_avctp_packet(avctp, avctp->reassembly_buf, hdr_common, + sys_be16_to_cpu(hdr_reassembly->pid)); + net_buf_drop(&avctp->reassembly_buf); + return 0; + } + + return 0; + } + + LOG_WRN("No matching START packet found for tid=%u, cr=%u", tid, cr); + +failed: + net_buf_drop(&avctp->reassembly_buf); + return 0; /* Need keep L2CAP up */ +} + +static int avctp_l2cap_recv(struct bt_l2cap_chan *chan, struct net_buf *buf) +{ + struct bt_avctp *session = AVCTP_CHAN(chan); + struct bt_avctp_header_common *hdr = (void *)buf->data; + struct bt_avctp_header_single *hdr_single; + bt_avctp_pkt_type_t pkt_type; + int err; + + if (buf->len < sizeof(*hdr)) + { + LOG_ERR("invalid AVCTP header received"); + return -EINVAL; + } + + pkt_type = BT_AVCTP_HDR_GET_PACKET_TYPE(hdr); + + if (pkt_type != BT_AVCTP_PKT_TYPE_SINGLE) + { + err = avctp_recv_fragmented(session, buf); + if (err < 0) + { + LOG_ERR("AVCTP recv fragmented packet fail, err = %d", err); + } + + return err; + } + + if (session->reassembly_buf != NULL) + { + LOG_WRN("AVCTP: aborting in-progress reassembly due to SINGLE pkt"); + net_buf_drop(&session->reassembly_buf); + } + + if (buf->len < BT_AVCTP_HDR_SIZE_SINGLE) + { + LOG_ERR("invalid AVCTP single header received"); + return -EINVAL; + } + + hdr_single = net_buf_pull_mem(buf, BT_AVCTP_HDR_SIZE_SINGLE); + return dispatch_avctp_packet(session, buf, &hdr_single->common, + sys_be16_to_cpu(hdr_single->pid)); +} + +static const struct bt_l2cap_chan_ops ops = +{ + .connected = avctp_l2cap_connected, + .disconnected = avctp_l2cap_disconnected, + .encrypt_change = avctp_l2cap_encrypt_changed, + .recv = avctp_l2cap_recv, + .alloc_buf = avctp_l2cap_alloc_buf, +}; + +int bt_avctp_connect(struct bt_conn *conn, + uint16_t psm, struct bt_avctp *session) +{ + if (!session) + { + return -EINVAL; + } + + session->br_chan.chan.ops = &ops; + session->reassembly_buf = NULL; + return bt_l2cap_chan_connect(conn, &session->br_chan.chan, psm); +} + +int bt_avctp_disconnect(struct bt_avctp *session) +{ + if (!session) + { + return -EINVAL; + } + + LOG_DBG("session %p", session); + + return bt_l2cap_chan_disconnect(&session->br_chan.chan); +} + +struct net_buf *bt_avctp_create_pdu(struct net_buf_pool *pool) +{ + struct net_buf *buf; + + LOG_DBG(""); + + /* reserved avctp_header */ + + buf = bt_l2cap_create_pdu(pool, sizeof(struct bt_avctp_header_start)); + if (buf == NULL) + { + LOG_ERR("No buff available"); + } + + return buf; +} + +int bt_avctp_send(struct bt_avctp *session, + struct net_buf *buf, bt_avctp_cr_t cr, uint8_t tid) +{ + uint8_t num_packet; + uint16_t max_payload_size; + + if ((session == NULL) || (buf == NULL)) + { + return -EINVAL; + } + + if ((session->tx_pool != NULL) && (session->max_tx_payload_size == 0)) + { + LOG_ERR("Invalid max_tx_payload_size " + "%u", session->max_tx_payload_size); + return -EINVAL; + } + + if (session->tx_pool == NULL) + { + if (buf->len > (session->br_chan.tx.mtu - BT_AVCTP_HDR_SIZE_SINGLE)) + { + LOG_ERR("Buffer size %u exceeds MTU %u", buf->len, + session->br_chan.tx.mtu - BT_AVCTP_HDR_SIZE_SINGLE); + return -EMSGSIZE; + } + + num_packet = 1; + } + else + { + max_payload_size = MIN(session->br_chan.tx.mtu, + session->max_tx_payload_size); + num_packet = get_avctp_packet_num(buf->len, max_payload_size); + } + + return bt_avctp_send_common(session, buf, cr, tid, session->pid, + num_packet, + BT_AVCTP_IPID_NONE); +} + +static int avctp_l2cap_accept( + struct bt_conn *conn, struct bt_l2cap_server *server, + struct bt_l2cap_chan **chan) +{ + struct bt_avctp *session = NULL; + struct bt_avctp_server *avctp_server; + int err; + + LOG_DBG("conn %p", conn); + + avctp_server = CONTAINER_OF(server, struct bt_avctp_server, l2cap); + + nxsem_wait_uninterruptible(&avctp_lock); + + if (!list_find(&avctp_l2cap_server, &avctp_server->node, NULL)) + { + LOG_WRN("Invalid l2cap server"); + nxsem_post(&avctp_lock); + return -EINVAL; + } + + nxsem_post(&avctp_lock); + + /* Get the AVCTP session from upper layer */ + + err = avctp_server->accept(conn, &session); + if (err < 0) + { + LOG_ERR("Incoming connection rejected"); + return err; + } + + session->br_chan.chan.ops = &ops; + session->reassembly_buf = NULL; + + *chan = &session->br_chan.chan; + + return 0; +} + +int bt_avctp_server_register(struct bt_avctp_server *server) +{ + int err; + + LOG_DBG(""); + + if ((server == NULL) || (server->accept == NULL)) + { + LOG_DBG("Invalid parameter"); + return -EINVAL; + } + + nxsem_wait_uninterruptible(&avctp_lock); + + if (list_find(&avctp_l2cap_server, &server->node, NULL)) + { + LOG_WRN("L2CAP server has been registered"); + nxsem_post(&avctp_lock); + return -EEXIST; + } + + server->l2cap.accept = avctp_l2cap_accept; + err = bt_l2cap_br_server_register(&server->l2cap); + if (err < 0) + { + LOG_ERR("AVCTP L2CAP registration failed %d", err); + nxsem_post(&avctp_lock); + return err; + } + + LOG_DBG("Register L2CAP server %p", server); + list_add_tail(&avctp_l2cap_server, &server->node); + + nxsem_post(&avctp_lock); + + return err; +} + +void bt_avctp_init(void) +{ + static bool initialized; + + if (initialized) + { + return; + } + + initialized = true; + + LOG_DBG("Initializing AVCTP"); + + /* Locking semaphore initialized to 1 (unlocked) */ + + nxsem_init(&avctp_lock, 0, 1); + k_work_init_delayable(&avctp_tx_work, avctp_tx_processor); +} diff --git a/libs/zblue/host/classic/avctp_internal.h b/libs/zblue/host/classic/avctp_internal.h new file mode 100644 index 0000000000000..10e6b0ff650f6 --- /dev/null +++ b/libs/zblue/host/classic/avctp_internal.h @@ -0,0 +1,212 @@ +/**************************************************************************** + * libs/zblue/host/classic/avctp_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Audio Video Control Transport Protocol internal header. + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * Copyright (C) 2024 Xiaomi Corporation Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +typedef enum __packed +{ + BT_AVCTP_IPID_NONE = 0b0, + BT_AVCTP_IPID_INVALID = 0b1, +} bt_avctp_ipid_t; + +typedef enum __packed +{ + BT_AVCTP_CMD = 0b0, + BT_AVCTP_RESPONSE = 0b1, +} bt_avctp_cr_t; + +typedef enum __packed +{ + BT_AVCTP_PKT_TYPE_SINGLE = 0b00, + BT_AVCTP_PKT_TYPE_START = 0b01, + BT_AVCTP_PKT_TYPE_CONTINUE = 0b10, + BT_AVCTP_PKT_TYPE_END = 0b11, +} bt_avctp_pkt_type_t; + +struct bt_avctp_header_common +{ + uint8_t byte0; /** [7:4]: Transaction label, [3:2]: Packet_type, [1]: C/R, [0]: IPID */ +} __packed; + +struct bt_avctp_header_single +{ + struct bt_avctp_header_common common; + uint16_t pid; +} __packed; + +struct bt_avctp_header_start +{ + struct bt_avctp_header_common common; + uint8_t number_packet; + uint16_t pid; +} __packed; + +struct bt_avctp_header_continue_end +{ + struct bt_avctp_header_common common; +} __packed; + +/** Transaction label provided by the application and is replicated by the + * sender of the message in each packet of the sequence. It isused at the + * receiver side to identify packets that belong to the same message. + */ +#define BT_AVCTP_HDR_GET_TRANSACTION_LABLE(hdr) FIELD_GET(GENMASK(7, 4), ((hdr)->byte0)) +/** Set to zero (00) to indicate that the command/response message is + * transmitted in a single L2CAP packet. Alternatively, set to (01) for + * start, (10) for continue, or (11) for end packet. + */ +#define BT_AVCTP_HDR_GET_PACKET_TYPE(hdr) FIELD_GET(GENMASK(3, 2), ((hdr)->byte0)) +/** Indicates whether the messageconveys a command frame (0) or a response + * frame (1). + */ +#define BT_AVCTP_HDR_GET_CR(hdr) FIELD_GET(BIT(1), ((hdr)->byte0)) +/** The IPID bit is set in a response message to indicate an invalid Profile + * Identifier received in the command message of the same transaction; + * otherwise this bit is set to zero. In command messages this bit is set to + * zero. This field is only present in the start packet of the message. + */ +#define BT_AVCTP_HDR_GET_IPID(hdr) FIELD_GET(BIT(0), ((hdr)->byte0)) + +/** Transaction label provided by the application and is replicated by the + * sender of the message in each packet of the sequence. It isused at the + * receiver side to identify packets that belong to the same message. + */ +#define BT_AVCTP_HDR_SET_TRANSACTION_LABLE(hdr, tl) \ + (hdr)->byte0 = (((hdr)->byte0) & ~GENMASK(7, 4)) | FIELD_PREP(GENMASK(7, 4), (tl)) +/** Set to zero (00) to indicate that the command/response message is + * transmitted in a single L2CAP packet. Alternatively, set to (01) for + * start, (10) for continue, or (11) for end packet. + */ +#define BT_AVCTP_HDR_SET_PACKET_TYPE(hdr, packet_type) \ + (hdr)->byte0 = (((hdr)->byte0) & ~GENMASK(3, 2)) | FIELD_PREP(GENMASK(3, 2), (packet_type)) +/** Indicates whether the messageconveys a command frame (0) or a response + * frame (1). + */ +#define BT_AVCTP_HDR_SET_CR(hdr, cr) \ + (hdr)->byte0 = (((hdr)->byte0) & ~BIT(1)) | FIELD_PREP(BIT(1), (cr)) +/** The IPID bit is set in a response message to indicate an invalid Profile + * Identifier received in the command message of the same transaction; + * otherwise this bit is set to zero. In command messages this bit is set to + * zero. This field is only present in the start packet of the message. + */ +#define BT_AVCTP_HDR_SET_IPID(hdr, ipid) \ + (hdr)->byte0 = (((hdr)->byte0) & ~BIT(0)) | FIELD_PREP(BIT(0), (ipid)) +/** Set all AVCTP header fields into the first octet. Packs the four fields + * (IPID, CR, TYPE, TID) into a single 8-bit header according to the AVRCP + * bit layout. + */ +#define BT_AVCTP_HDR_SET(hdr, ipid, cr, type, tid) \ + (hdr)->byte0 = (FIELD_PREP(BIT(0), (ipid)) | FIELD_PREP(BIT(1), (cr)) | \ + FIELD_PREP(GENMASK(3, 2), (type)) | FIELD_PREP(GENMASK(7, 4), (tid))) + +struct bt_avctp; + +struct bt_avctp_ops_cb +{ + void (*connected)(struct bt_avctp *session); + void (*disconnected)(struct bt_avctp *session); + int (*recv)(struct bt_avctp *session, struct net_buf *buf, + bt_avctp_cr_t cr, uint8_t tid); + struct net_buf *(*alloc_buf)(struct bt_avctp *session); +}; + +struct bt_avctp +{ + struct bt_l2cap_br_chan br_chan; + const struct bt_avctp_ops_cb *ops; + uint16_t pid; /** Profile Identifier */ + uint16_t max_tx_payload_size; + struct net_buf_pool *tx_pool; + struct net_buf_pool *rx_pool; + struct net_buf *reassembly_buf; +}; + +/** + * @brief AVCTP L2CAP Server structure + * + * This structure defines the L2CAP server used for AVCTP over L2CAP + * transport. + */ + +struct bt_avctp_server +{ + /** + * @brief L2CAP server parameters + * + * This field is used to register the L2CAP server. The `psm` field can be + * set to a specific value (not recommended), or set to 0 to allow + * automatic PSM allocation during registration via @ref + * bt_avctp_server_register. + * + * The `sec_level` field specifies the minimum required security level. + * + * @note The `struct bt_l2cap_server::accept` callback of `l2cap` can not + * be used by AVCTP applications. Instead, use the `struct + * bt_avctp_server::accept` callback defined in this structure. + */ + + struct bt_l2cap_server l2cap; + + /** + * @brief Accept callback for incoming AVCTP connections + * + * This callback is invoked when a new incoming AVCTP connection is + * received. The application is responsible for authorizing the connection + * and allocating a new AVCTP session object. + * + * @warning The caller must ensure that the parent object of the AVCTP + * session is properly zero-initialized before use. + * + * @param conn The Bluetooth connection requesting authorization. + * @param session Pointer to receive the allocated AVCTP session object. + * + * @retval 0 Success. + * @retval -ENOMEM No available space for a new session. + * @retval -EACCES Connection not authorized by the application. + * @retval -EPERM Encryption key size is insufficient. + */ + + int (*accept)(struct bt_conn *conn, struct bt_avctp **session); + + /** @brief Internal node for list management */ + + struct list_node node; +}; + +/* Initialize AVCTP layer */ + +void bt_avctp_init(void); + +/* Application register with AVCTP layer */ + +int bt_avctp_server_register(struct bt_avctp_server *server); + +/* AVCTP connect */ + +int bt_avctp_connect(struct bt_conn *conn, + uint16_t psm, struct bt_avctp *session); + +/* AVCTP disconnect */ + +int bt_avctp_disconnect(struct bt_avctp *session); + +/* Create AVCTP PDU */ + +struct net_buf *bt_avctp_create_pdu(struct net_buf_pool *pool); + +/* Send AVCTP PDU */ + +int bt_avctp_send(struct bt_avctp *session, + struct net_buf *buf, bt_avctp_cr_t cr, uint8_t tid); diff --git a/libs/zblue/host/classic/avdtp.c b/libs/zblue/host/classic/avdtp.c new file mode 100644 index 0000000000000..0dea23c8dff68 --- /dev/null +++ b/libs/zblue/host/classic/avdtp.c @@ -0,0 +1,3423 @@ +/**************************************************************************** + * libs/zblue/host/classic/avdtp.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Audio Video Distribution Protocol + * + * Copyright 2024-2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include "l2cap_br_internal.h" +#include "avdtp_internal.h" + +#define LOG_LEVEL CONFIG_BT_AVDTP_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_avdtp); + +#define AVDTP_MSG_MASK GENMASK(1, 0) +#define AVDTP_PKT_MASK GENMASK(3, 2) +#define AVDTP_TID_MASK GENMASK(7, 4) +#define AVDTP_SIGID_MASK GENMASK(5, 0) + +#define AVDTP_MSG_PREP(val) FIELD_PREP(AVDTP_MSG_MASK, val) +#define AVDTP_PKT_PREP(val) FIELD_PREP(AVDTP_PKT_MASK, val) +#define AVDTP_TID_PREP(val) FIELD_PREP(AVDTP_TID_MASK, val) +#define AVDTP_SIGID_PREP(val) FIELD_PREP(AVDTP_SIGID_MASK, val) + +#define AVDTP_MSG_GET(hdr) FIELD_GET(AVDTP_MSG_MASK, hdr) +#define AVDTP_PKT_GET(hdr) FIELD_GET(AVDTP_PKT_MASK, hdr) +#define AVDTP_TID_GET(hdr) FIELD_GET(AVDTP_TID_MASK, hdr) +#define AVDTP_SIGID_GET(s) FIELD_GET(AVDTP_SIGID_MASK, s) + +#define AVDTP_SEID_MASK GENMASK(7, 2) +#define AVDTP_SEID_PREP(val) FIELD_PREP(AVDTP_SEID_MASK, val) +#define AVDTP_SEID_GET(val) FIELD_GET(AVDTP_SEID_MASK, val) + +static struct bt_avdtp_event_cb *event_cb; +static struct list_node seps = LIST_INITIAL_VALUE(seps); + +#define AVDTP_CHAN(_ch) CONTAINER_OF(_ch, struct bt_avdtp, br_chan.chan) + +#define AVDTP_KWORK(_work) \ + CONTAINER_OF(CONTAINER_OF(_work, struct k_work_delayable, work), struct bt_avdtp, timeout_work) + +#define DISCOVER_REQ(_req) CONTAINER_OF(_req, struct bt_avdtp_discover_params, req) +#define GET_CAP_REQ(_req) CONTAINER_OF(_req, struct bt_avdtp_get_capabilities_params, req) +#define SET_CONF_REQ(_req) CONTAINER_OF(_req, struct bt_avdtp_set_configuration_params, req) +#define CTRL_REQ(_req) CONTAINER_OF(_req, struct bt_avdtp_ctrl_params, req) + +#define AVDTP_TIMEOUT SEC2TICK(6) + +sem_t avdtp_sem_lock = NXSEM_INITIALIZER(1U, 0); + +struct avdtp_buf_user_data +{ + struct bt_avdtp *session; + struct bt_avdtp_single_sig_hdr hdr; + uint8_t frag_count; + uint8_t current_frag; +} __packed; + +#define AVDTP_POOL_USER_DATA_SIZE \ + MAX(CONFIG_BT_CONN_TX_USER_DATA_SIZE, sizeof(struct avdtp_buf_user_data)) + +NET_BUF_POOL_DEFINE(avdtp_pool, CONFIG_BT_MAX_CONN * 2, + BT_L2CAP_BUF_SIZE(CONFIG_BT_AVDTP_SIGNAL_SDU_MAX), + AVDTP_POOL_USER_DATA_SIZE, + NULL); + +/* When allocating from acl_tx_pool fail, keep at least one buf to send data, + * then the sending callback trigger the fragmentation process. + */ + +NET_BUF_POOL_DEFINE(avdtp_frag_pool, + 1, + BT_L2CAP_BUF_SIZE(CONFIG_BT_L2CAP_TX_MTU), + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + +/* tx list for packets */ + +struct list_node avdtp_tx_list = LIST_INITIAL_VALUE(avdtp_tx_list); +static void avdtp_tx_processor(struct k_work *item); +static K_WORK_DEFINE(avdtp_tx_work, avdtp_tx_processor); + +enum sep_state +{ + AVDTP_IDLE = BIT(0), + AVDTP_CONFIGURED = BIT(1), + + /* establishing the transport sessions. */ + + AVDTP_OPENING = BIT(2), + AVDTP_OPEN = BIT(3), + AVDTP_STREAMING = BIT(4), + AVDTP_CLOSING = BIT(5), + AVDTP_ABORTING = BIT(6), +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static uint8_t avdtp_get_tid(struct bt_avdtp *session) +{ + return session->tid_sent++ % 16; /* Loop for 16 because only 4 bits */ +} + +static void avdtp_lock(struct bt_avdtp *session) +{ + nxsem_wait_uninterruptible(&session->sem_lock); +} + +static void avdtp_unlock(struct bt_avdtp *session) +{ + nxsem_post(&session->sem_lock); +} + +static void avdtp_sep_lock(struct bt_avdtp_sep *sep) +{ + if (sep != NULL) + { + nxsem_wait_uninterruptible(&sep->sem_lock); + } +} + +static void avdtp_sep_unlock(struct bt_avdtp_sep *sep) +{ + if (sep != NULL) + { + nxsem_post(&sep->sem_lock); + } +} + +static void bt_avdtp_set_state(struct bt_avdtp_sep *sep, uint8_t state) +{ + sep->state = state; + + if (state != AVDTP_IDLE) + { + sep->sep_info.inuse = 1U; + } + else + { + sep->sep_info.inuse = 0U; + } +} + +static void bt_avdtp_set_state_lock(struct bt_avdtp_sep *sep, uint8_t state) +{ + avdtp_sep_lock(sep); + bt_avdtp_set_state(sep, state); + avdtp_sep_unlock(sep); +} + +static inline void bt_avdtp_clear_req(struct bt_avdtp *session) +{ + avdtp_lock(session); + session->req = NULL; + avdtp_unlock(session); +} + +static int avdtp_media_disconnect(struct bt_avdtp_sep *sep) +{ + if (sep == NULL || sep->chan.chan.conn == NULL || + sep->chan.chan.ops == NULL) + { + return -EINVAL; + } + + return bt_l2cap_chan_disconnect(&sep->chan.chan); +} + +static bool avdtp_media_chan_valid(struct bt_avdtp_sep *sep) +{ + /* another way is checking whether the `chan` is in `conn->channels`. */ + + if ((sep->chan.state != BT_L2CAP_DISCONNECTED) && + (sep->chan.chan.conn != NULL) && + (sep->chan.chan.ops != NULL) && (sep->state != AVDTP_IDLE)) + { + return true; + } + + if (((sep->chan.state != BT_L2CAP_DISCONNECTED) || + (sep->chan.chan.conn != NULL)) && + (sep->state != AVDTP_IDLE)) + { + LOG_ERR("Media chan is disconnected, but sep state is %u", sep->state); + } + + if (((sep->chan.state != BT_L2CAP_DISCONNECTED) || + (sep->chan.chan.conn != NULL)) && + (sep->state == AVDTP_IDLE)) + { + LOG_ERR("Sep state is IDLE, but stream chan is not disconnected"); + } + + return false; +} + +static void avdtp_endpoint_established(struct bt_avdtp_sep *sep) +{ + if (sep->ops != NULL && sep->ops->connected != NULL) + { + sep->ops->connected(sep); + } +} + +static void avdtp_endpoint_released(struct bt_avdtp_sep *sep) +{ + if (sep->ops != NULL && sep->ops->disconnected != NULL) + { + sep->ops->disconnected(sep); + } +} + +#define DELAY_WORK_RETRY 1 +#define DELAY_WORK_CHECK 2 +#define RETRY_MEDIA_DISCONNECT_TIMEOUT 100 +#define CHECK_MEDIA_DISCONNECT_TIMEOUT 3000 + +static void avdtp_schedule_media_disconnect_work(struct bt_avdtp_sep *sep, + uint8_t state) +{ + uint32_t timeout; + + sep->_delay_work_state = state; + + if (state == DELAY_WORK_RETRY) + { + timeout = RETRY_MEDIA_DISCONNECT_TIMEOUT; + } + else + { + timeout = CHECK_MEDIA_DISCONNECT_TIMEOUT; + } + + k_work_schedule(&sep->_delay_work, MSEC2TICK(timeout)); +} + +static void avdtp_cancel_media_disconnect_work(struct bt_avdtp_sep *sep) +{ + k_work_cancel_delayable(&sep->_delay_work); +} + +static void avdtp_media_disconnect_work(struct k_work *work) +{ + struct bt_avdtp_sep *sep = CONTAINER_OF(work, struct bt_avdtp_sep, + _delay_work.work); + + if (!avdtp_media_chan_valid(sep)) + { + if (sep->state != AVDTP_IDLE) + { + bt_avdtp_set_state_lock(sep, AVDTP_IDLE); + avdtp_endpoint_released(sep); + } + + return; + } + + if (sep->_delay_work_state == DELAY_WORK_RETRY) + { + int err; + + err = avdtp_media_disconnect(sep); + if (err == 0) + { + avdtp_schedule_media_disconnect_work(sep, DELAY_WORK_CHECK); + return; + } + } + + /* TODO: disconnect acl or notify application? */ +} + +/* L2CAP Interface callbacks */ + +void bt_avdtp_media_l2cap_connected(struct bt_l2cap_chan *chan) +{ + struct bt_avdtp *session; + struct bt_avdtp_sep *sep = CONTAINER_OF(chan, struct bt_avdtp_sep, + chan.chan); + + if (!chan) + { + LOG_ERR("Invalid AVDTP chan"); + return; + } + + session = sep->session; + if (session == NULL) + { + return; + } + + LOG_DBG("chan %p session %p", chan, session); + bt_avdtp_set_state_lock(sep, AVDTP_OPEN); + + if (session->req != NULL) + { + struct bt_avdtp_req *req = session->req; + + req->status = BT_AVDTP_SUCCESS; + bt_avdtp_clear_req(session); + + if (req->func != NULL) + { + req->func(req, NULL); + } + } + + avdtp_endpoint_established(sep); +} + +void bt_avdtp_media_l2cap_disconnected(struct bt_l2cap_chan *chan) +{ + struct bt_avdtp *session; + struct bt_avdtp_sep *sep = CONTAINER_OF(chan, struct bt_avdtp_sep, + chan.chan); + + session = sep->session; + if (session == NULL) + { + return; + } + + LOG_DBG("chan %p", chan); + + avdtp_cancel_media_disconnect_work(sep); + + avdtp_sep_lock(sep); + + if ((sep->state == AVDTP_CLOSING) && (session->req != NULL) && + (session->req->sig == BT_AVDTP_CLOSE)) + { + /* closing the stream */ + + struct bt_avdtp_req *req = session->req; + + bt_avdtp_set_state(sep, AVDTP_IDLE); + avdtp_sep_unlock(sep); + req->status = BT_AVDTP_SUCCESS; + bt_avdtp_clear_req(session); + + if (req->func != NULL) + { + req->func(req, NULL); + } + } + else + { + bt_avdtp_set_state(sep, AVDTP_IDLE); + avdtp_sep_unlock(sep); + } + + /* the media l2cap is disconnected by other unexpected reasons */ + + avdtp_endpoint_released(sep); +} + +int bt_avdtp_media_l2cap_recv(struct bt_l2cap_chan *chan, + struct net_buf *buf) +{ + /* media data is received */ + + struct bt_avdtp_sep *sep = CONTAINER_OF(chan, struct bt_avdtp_sep, + chan.chan); + + if (sep->ops != NULL && sep->ops->media_data_cb != NULL) + { + sep->ops->media_data_cb(sep, buf); + } + + return 0; +} + +static const struct bt_l2cap_chan_ops stream_chan_ops = +{ + .connected = bt_avdtp_media_l2cap_connected, + .disconnected = bt_avdtp_media_l2cap_disconnected, + .recv = bt_avdtp_media_l2cap_recv, +}; + +static int avdtp_media_connect(struct bt_avdtp *session, + struct bt_avdtp_sep *sep) +{ + if (!session) + { + return -EINVAL; + } + + sep->session = session; + sep->chan.rx.mtu = BT_L2CAP_RX_MTU; + sep->chan.chan.ops = &stream_chan_ops; + sep->chan.required_sec_level = BT_SECURITY_L2; + + return bt_l2cap_chan_connect(session->br_chan.chan.conn, &sep->chan.chan, + BT_L2CAP_PSM_AVDTP); +} + +static struct net_buf *avdtp_create_pdu_timeout( + uint8_t msg_type, uint8_t sig_id, uint8_t tid, clock_t timeout) +{ + struct net_buf *buf; + struct bt_avdtp_single_sig_hdr *hdr; + + LOG_DBG(""); + + buf = bt_l2cap_create_pdu_timeout(&avdtp_pool, 0, timeout); + if (!buf) + { + LOG_ERR("Error: No Buff available"); + return NULL; + } + + DEBUGASSERT(net_buf_tailroom(buf) >= sizeof(*hdr)); + hdr = net_buf_add(buf, sizeof(*hdr)); + + hdr->hdr = AVDTP_MSG_PREP(msg_type) | + AVDTP_PKT_PREP(BT_AVDTP_PACKET_TYPE_SINGLE) | + AVDTP_TID_PREP(tid); + hdr->signal_id = AVDTP_SIGID_PREP(sig_id); + + LOG_DBG("hdr = 0x%02X, Signal_ID = 0x%02X", hdr->hdr, hdr->signal_id); + return buf; +} + +static struct net_buf *avdtp_create_pdu(uint8_t msg_type, + uint8_t sig_id, uint8_t tid) +{ + return avdtp_create_pdu_timeout(msg_type, sig_id, tid, ((clock_t)-1)); +} + +static struct net_buf *avdtp_create_rsp(uint8_t msg_type, + uint8_t sig_id, uint8_t tid) +{ + /* Responses are built in the Bluetooth workqueue context, which is also + * the context that returns the transmitted buffers to the same pool, and + * some of the command handlers do this while holding the endpoint lock. + * Waiting for a buffer here would therefore block the workqueue instead of + * making progress. Let the remote side time out instead. + */ + + return avdtp_create_pdu_timeout(msg_type, sig_id, tid, 0); +} + +static void avdtp_tx_raise(void) +{ + if (!list_is_empty(&avdtp_tx_list)) + { + LOG_DBG("kick TX"); + bt_work_submit(&avdtp_tx_work); + } +} + +static void avdtp_tx_cb(struct bt_conn *conn, void *user_data, int err) +{ + avdtp_tx_raise(); +} + +static void avdtp_tx_remove(struct net_buf *buf) +{ + nxsem_wait_uninterruptible(&avdtp_sem_lock); + list_find_and_remove(&avdtp_tx_list, &buf->node); + nxsem_post(&avdtp_sem_lock); +} + +static void avdtp_tx_rel_buf(struct net_buf *buf, struct net_buf *frag) +{ + avdtp_tx_remove(buf); + net_buf_unref(buf); + net_buf_unref(frag); + + avdtp_tx_raise(); +} + +static void avdtp_tx_single(struct bt_avdtp *session, struct net_buf *buf) +{ + int err; + + avdtp_tx_remove(buf); + err = bt_l2cap_br_chan_send_cb(&session->br_chan.chan, buf, avdtp_tx_cb, + NULL); + + if (err != 0) + { + LOG_ERR("Failed to send l2cap: err=%d", err); + net_buf_unref(buf); + } +} + +static void avdtp_tx_multi(struct bt_avdtp *session, struct net_buf *buf, + struct avdtp_buf_user_data *user_data) +{ + struct net_buf *frag; + uint16_t mtu; + int err; + + mtu = MIN(session->br_chan.tx.mtu, CONFIG_BT_L2CAP_TX_MTU); + + while (user_data->frag_count > user_data->current_frag && buf->len > 0) + { + uint16_t len; + + frag = bt_l2cap_create_pdu_timeout(NULL, 0, 0); + if (frag == NULL) + { + frag = bt_l2cap_create_pdu_timeout(&avdtp_frag_pool, 0, 0); + } + + if (frag == NULL) + { + LOG_DBG("No Buff available, wait tx cb to trigger this work " + "again"); + /* Do NOT call `avdtp_tx_raise` here. + * When there is no idle net_buf available while AVDTP TX is still + * pending, the worker cannot proceed. If `avdtp_tx_raise` is + * invoked here, it will immediately reschedule the worker again, + * causing it to spin and occupy the CPU continuously because TX is + * pending but no idle net_buf exists. + * + * `avdtp_tx_raise` should only be triggered when at least one idle + * net_buf is available. After the previously transmitted avdtp + * net_buf is released, `avdtp_tx_cb` will run, and that callback + * will safely trigger `avdtp_tx_raise` again. + */ + + return; + } + + user_data->current_frag++; + + if (user_data->current_frag == 1) + { + struct bt_avdtp_start_sig_hdr *start_hdr; + struct bt_avdtp_single_sig_hdr *sig_hdr; + + DEBUGASSERT(buf->len >= sizeof(*sig_hdr)); + sig_hdr = net_buf_pull_mem(buf, sizeof(*sig_hdr)); + user_data->hdr = *sig_hdr; + + DEBUGASSERT(net_buf_tailroom(frag) >= sizeof(*start_hdr)); + start_hdr = net_buf_add(frag, sizeof(*start_hdr)); + + /* use same transaction label and message type */ + + start_hdr->hdr = (user_data->hdr.hdr & ~AVDTP_PKT_MASK) | + AVDTP_PKT_PREP(BT_AVDTP_PACKET_TYPE_START); + start_hdr->num_of_signal_pkts = user_data->frag_count; + start_hdr->signal_id = user_data->hdr.signal_id; + + DEBUGASSERT(mtu >= sizeof(*start_hdr)); + len = mtu - sizeof(*start_hdr); + if (len >= buf->len) + { + LOG_ERR("The start packet can send all data"); + avdtp_tx_rel_buf(buf, frag); + return; + } + + net_buf_add_mem(frag, net_buf_pull_mem(buf, len), len); + } + else + { + struct bt_avdtp_continue_end_sig_hdr *cont_hdr; + uint8_t pkt_type = + (user_data->frag_count == user_data->current_frag) + ? BT_AVDTP_PACKET_TYPE_END + : BT_AVDTP_PACKET_TYPE_CONTINUE; + + DEBUGASSERT(net_buf_tailroom(frag) >= sizeof(*cont_hdr)); + cont_hdr = net_buf_add(frag, sizeof(*cont_hdr)); + + /* use same transaction label and message type */ + + cont_hdr->hdr = + ( + user_data->hdr.hdr & ~AVDTP_PKT_MASK) | AVDTP_PKT_PREP( + pkt_type); + + DEBUGASSERT(mtu >= sizeof(*cont_hdr)); + len = mtu - sizeof(*cont_hdr); + if (pkt_type == BT_AVDTP_PACKET_TYPE_CONTINUE && len >= buf->len) + { + LOG_ERR("The continue packet can send all data"); + avdtp_tx_rel_buf(buf, frag); + return; + } + else if (pkt_type == BT_AVDTP_PACKET_TYPE_END && len < buf->len) + { + LOG_ERR("The end packet can't send all data"); + avdtp_tx_rel_buf(buf, frag); + return; + } + + len = MIN(len, buf->len); + net_buf_add_mem(frag, net_buf_pull_mem(buf, len), len); + } + + LOG_DBG("Sending fragment %d: len=%d", user_data->current_frag, + frag->len); + err = bt_l2cap_br_chan_send_cb(&session->br_chan.chan, frag, + avdtp_tx_cb, + NULL); + if (err != 0) + { + LOG_ERR("Failed to send fragment: err=%d", err); + avdtp_tx_rel_buf(buf, frag); + return; + } + } + + if ((user_data->frag_count <= user_data->current_frag) || (buf->len == 0)) + { + avdtp_tx_remove(buf); + net_buf_unref(buf); + } + + avdtp_tx_raise(); +} + +static void avdtp_tx_processor(struct k_work *item) +{ + struct bt_avdtp *session; + struct avdtp_buf_user_data *user_data; + const struct list_node *node; + struct net_buf *buf; + + nxsem_wait_uninterruptible(&avdtp_sem_lock); + node = list_peek_head(&avdtp_tx_list); + if (node == NULL) + { + nxsem_post(&avdtp_sem_lock); + LOG_DBG("No pending tx"); + return; + } + + nxsem_post(&avdtp_sem_lock); + + buf = CONTAINER_OF(node, struct net_buf, node); + user_data = net_buf_user_data(buf); + + session = user_data->session; + DEBUGASSERT(session != NULL); + + /* The buf can be sent directly */ + + if (user_data->frag_count == 1) + { + avdtp_tx_single(session, buf); + avdtp_tx_raise(); + return; + } + + avdtp_tx_multi(session, buf, user_data); +} + +static void avdtp_buf_init_user_data(struct bt_avdtp *session, + struct net_buf *buf) +{ + struct avdtp_buf_user_data *user_data; + + user_data = net_buf_user_data(buf); + user_data->session = session; + user_data->current_frag = 0; + + if (buf->len > session->br_chan.tx.mtu) + { + uint8_t num_of_frag_pkts; + uint16_t sdu_len; + uint16_t continue_pkt_size; + uint16_t mtu = MIN(session->br_chan.tx.mtu, CONFIG_BT_L2CAP_TX_MTU); + + /* First start packet's header is different */ + + sdu_len = buf->len - sizeof(struct bt_avdtp_single_sig_hdr) + + sizeof(struct bt_avdtp_start_sig_hdr); + sdu_len -= mtu; + + /* Calculate the number of signal packets needed */ + + continue_pkt_size = mtu - sizeof(struct bt_avdtp_continue_end_sig_hdr); + num_of_frag_pkts = + 1 + (sdu_len + continue_pkt_size - 1) / continue_pkt_size; + LOG_DBG("Fragmenting buffer: len=%d, packets=%d", buf->len, + num_of_frag_pkts); + + user_data->frag_count = num_of_frag_pkts; + } + else + { + user_data->frag_count = 1; + } +} + +static void avdtp_send_common(struct bt_avdtp *session, struct net_buf *buf) +{ + avdtp_buf_init_user_data(session, buf); + nxsem_wait_uninterruptible(&avdtp_sem_lock); + list_add_tail(&avdtp_tx_list, &buf->node); + nxsem_post(&avdtp_sem_lock); + + avdtp_tx_raise(); +} + +static int avdtp_send_rsp(struct bt_avdtp *session, struct net_buf *buf) +{ + /* From all the calls, the session and buf can't be NULL. */ + + DEBUGASSERT((session != NULL && buf != NULL)); + + LOG_DBG("Response sent: buf=%p", buf); + + avdtp_send_common(session, buf); + + return 0; +} + +static void avdtp_set_status(struct bt_avdtp_req *req, + struct net_buf *buf, uint8_t msg_type) +{ + if (msg_type == BT_AVDTP_ACCEPT) + { + req->status = BT_AVDTP_SUCCESS; + } + else if (msg_type == BT_AVDTP_REJECT) + { + if (buf->len >= sizeof(req->status)) + { + req->status = net_buf_pull_u8(buf); + + if (req->status == BT_AVDTP_SUCCESS) + { + LOG_WRN("Reject frame with success status"); + req->status = BT_AVDTP_BAD_HEADER_FORMAT; + } + } + else + { + LOG_WRN("Invalid RSP frame"); + req->status = BT_AVDTP_BAD_LENGTH; + } + } + else if (msg_type == BT_AVDTP_GEN_REJECT) + { + req->status = BT_AVDTP_NOT_SUPPORTED_COMMAND; + } + else + { + req->status = BT_AVDTP_BAD_HEADER_FORMAT; + } +} + +static uint8_t avdtp_get_error_code(int err) +{ + switch (err) + { + case -ENOTSUP: + return BT_AVDTP_NOT_SUPPORTED_COMMAND; + case -EACCES: + return BT_AVDTP_BAD_STATE; + case -EBUSY: + return BT_AVDTP_SEP_IN_USE; + case -ENODATA: + return BT_AVDTP_BAD_LENGTH; + case -ENOENT: + default: + return BT_AVDTP_BAD_ACP_SEID; + } +} + +static void avdtp_discover_cmd(struct bt_avdtp *session, + struct net_buf *buf, uint8_t tid) +{ + int err; + struct bt_avdtp_sep *sep; + struct net_buf *rsp_buf; + uint8_t avdtp_err_code = 0; + + if (session->ops->discovery_ind == NULL) + { + err = -ENOTSUP; + } + else + { + err = session->ops->discovery_ind(session, &avdtp_err_code); + } + + rsp_buf = avdtp_create_rsp(err ? BT_AVDTP_REJECT : BT_AVDTP_ACCEPT, + BT_AVDTP_DISCOVER, tid); + if (!rsp_buf) + { + return; + } + + if (err) + { + if (avdtp_err_code == 0) + { + avdtp_err_code = avdtp_get_error_code(err); + } + + LOG_DBG("discover err code:%d", avdtp_err_code); + + DEBUGASSERT(net_buf_tailroom(rsp_buf) >= sizeof(avdtp_err_code)); + net_buf_add_u8(rsp_buf, avdtp_err_code); + } + else + { + struct bt_avdtp_sep_data sep_data; + + list_for_every_container(&seps, sep, _node) + { + memset(&sep_data, 0, sizeof(sep_data)); + sep_data.inuse = sep->sep_info.inuse; + sep_data.id = sep->sep_info.id; + sep_data.tsep = sep->sep_info.tsep; + sep_data.media_type = sep->sep_info.media_type; + + if (net_buf_tailroom(rsp_buf) >= sizeof(sep_data)) + { + net_buf_add_mem(rsp_buf, &sep_data, sizeof(sep_data)); + } + } + } + + (void)avdtp_send_rsp(session, rsp_buf); +} + +static void avdtp_discover_rsp(struct bt_avdtp *session, + struct net_buf *buf, uint8_t msg_type) +{ + struct bt_avdtp_req *req = session->req; + + if (req == NULL) + { + return; + } + + k_work_cancel_delayable(&session->timeout_work); + avdtp_set_status(req, buf, msg_type); + bt_avdtp_clear_req(session); + + if (req->func != NULL) + { + req->func(req, buf); + } +} + +static struct bt_avdtp_sep *avdtp_get_sep(uint8_t stream_endpoint_id) +{ + struct bt_avdtp_sep *sep; + + list_for_every_container(&seps, sep, _node) + { + if (sep->sep_info.id == stream_endpoint_id) + { + return sep; + } + } + + return NULL; +} + +static struct bt_avdtp_sep *avdtp_get_cmd_sep( + struct net_buf *buf, uint8_t *error_code, uint8_t *seid) +{ + struct bt_avdtp_sep *sep; + uint8_t id; + + if (buf->len < sizeof(id)) + { + *error_code = BT_AVDTP_BAD_LENGTH; + LOG_WRN("Malformed packet"); + return NULL; + } + + id = AVDTP_SEID_GET(net_buf_pull_u8(buf)); + if ((id < BT_AVDTP_MIN_SEID) || (id > BT_AVDTP_MAX_SEID)) + { + *error_code = BT_AVDTP_BAD_ACP_SEID; + LOG_WRN("Invalid ACP SEID"); + return NULL; + } + + if (seid != NULL) + { + *seid = id; + } + + sep = avdtp_get_sep(id); + return sep; +} + +static void avdtp_get_caps_cmd_internal( + struct bt_avdtp *session, struct net_buf *buf, uint8_t tid, + bool get_all_caps) +{ + int err = 0; + struct net_buf *rsp_buf; + struct bt_avdtp_sep *sep; + uint8_t avdtp_err_code = 0; + + sep = avdtp_get_cmd_sep(buf, &avdtp_err_code, NULL); + + if (sep == NULL) + { + err = -ENOENT; + } + else if (session->ops->get_capabilities_ind == NULL) + { + err = -ENOTSUP; + } + else + { + rsp_buf = avdtp_create_rsp( + BT_AVDTP_ACCEPT, + get_all_caps ? + BT_AVDTP_GET_ALL_CAPABILITIES : BT_AVDTP_GET_CAPABILITIES, + tid); + if (!rsp_buf) + { + return; + } + + err = session->ops->get_capabilities_ind(session, sep, rsp_buf, + get_all_caps, + &avdtp_err_code); + if (err) + { + net_buf_unref(rsp_buf); + } + } + + if (err) + { + rsp_buf = avdtp_create_rsp( + BT_AVDTP_REJECT, + get_all_caps ? + BT_AVDTP_GET_ALL_CAPABILITIES : BT_AVDTP_GET_CAPABILITIES, + tid); + if (!rsp_buf) + { + return; + } + + if (avdtp_err_code == 0) + { + avdtp_err_code = avdtp_get_error_code(err); + } + + LOG_DBG("get cap err code:%d", avdtp_err_code); + + DEBUGASSERT(net_buf_tailroom(rsp_buf) >= sizeof(avdtp_err_code)); + net_buf_add_u8(rsp_buf, avdtp_err_code); + } + + (void)avdtp_send_rsp(session, rsp_buf); +} + +static void avdtp_get_capabilities_cmd(struct bt_avdtp *session, + struct net_buf *buf, uint8_t tid) +{ + avdtp_get_caps_cmd_internal(session, buf, tid, false); +} + +static void avdtp_get_all_capabilities_cmd( + struct bt_avdtp *session, struct net_buf *buf, uint8_t tid) +{ + avdtp_get_caps_cmd_internal(session, buf, tid, true); +} + +static void avdtp_get_capabilities_rsp( + struct bt_avdtp *session, struct net_buf *buf, uint8_t msg_type) +{ + struct bt_avdtp_req *req = session->req; + + if (req == NULL) + { + return; + } + + k_work_cancel_delayable(&session->timeout_work); + avdtp_set_status(req, buf, msg_type); + bt_avdtp_clear_req(session); + + if (req->func != NULL) + { + req->func(req, buf); + } +} + +struct bt_avdtp_service_category_handler +{ + uint8_t min_len; + uint8_t max_len; + bool reconfig_support; + uint8_t err_code; + uint8_t (*handler)(struct net_buf *buf); +}; + +uint8_t bt_avdtp_check_media_recovery_type(struct net_buf *buf) +{ + struct bt_avdtp_recovery_capabilities *cap = + (struct bt_avdtp_recovery_capabilities *)buf->data; + + if (cap->recovery_type != BT_AVDTP_RECOVERY_TYPE_FORBIDDEN && + cap->recovery_type != BT_ADVTP_RECOVERY_TYPE_RFC2733) + { + return BT_AVDTP_BAD_RECOVERY_TYPE; + } + + return BT_AVDTP_SUCCESS; +} + +static struct bt_avdtp_service_category_handler category_handler[] = +{ + { + 0, + 0, + false, + 0, + NULL + }, + + /* None */ + + { + 0, 0, false, BT_AVDTP_BAD_MEDIA_TRANSPORT_FORMAT, + NULL + }, /* BT_AVDTP_SERVICE_MEDIA_TRANSPORT */ + + { + 0, + 0, + false, + BT_AVDTP_BAD_LENGTH, + NULL + }, + + /* BT_AVDTP_SERVICE_REPORTING */ + + { + sizeof(struct bt_avdtp_recovery_capabilities), + sizeof(struct bt_avdtp_recovery_capabilities), false, + BT_AVDTP_BAD_RECOVERY_FORMAT, + bt_avdtp_check_media_recovery_type + }, /* BT_AVDTP_SERVICE_MEDIA_RECOVERY */ + + { + sizeof(struct bt_avdtp_content_protection_capabilities), + UINT8_MAX, + true, + BT_AVDTP_BAD_CP_FORMAT, + NULL + }, /* BT_AVDTP_SERVICE_CONTENT_PROTECTION */ + + { + sizeof(struct bt_avdtp_header_compression_capabilities), + sizeof(struct bt_avdtp_header_compression_capabilities), false, + BT_AVDTP_BAD_LENGTH, + NULL + }, /* BT_AVDTP_SERVICE_HEADER_COMPRESSION */ + + { + sizeof(struct bt_avdtp_multiplexing_capabilities), + sizeof(struct bt_avdtp_multiplexing_capabilities), false, + BT_AVDTP_BAD_MULTIPLEXING_FORMAT, + NULL + }, /* BT_AVDTP_SERVICE_MULTIPLEXING */ + + { + sizeof(struct bt_avdtp_media_codec_capabilities), + UINT8_MAX, + true, + BT_AVDTP_BAD_LENGTH, + NULL + }, /* BT_AVDTP_SERVICE_MEDIA_CODEC */ + + { + 0, 0, 0, BT_AVDTP_BAD_LENGTH, + NULL + }, /* BT_AVDTP_SERVICE_DELAY_REPORTING */ +}; + +uint8_t bt_avdtp_check_service_category( + struct net_buf *buf, uint8_t *service_category, bool reconfig) +{ + struct bt_avdtp_generic_service_cap *hdr; + uint8_t err; + struct bt_avdtp_service_category_handler *handler; + struct net_buf_simple_state state; + + if (buf->len == 0U) + { + LOG_DBG("Error: buf not valid"); + return BT_AVDTP_BAD_LENGTH; + } + + while (buf->len > 0U) + { + if (buf->len < sizeof(*hdr)) + { + LOG_DBG("Error: buf not valid"); + return BT_AVDTP_BAD_LENGTH; + } + + hdr = net_buf_pull_mem(buf, sizeof(*hdr)); + *service_category = hdr->service_category; + + if (hdr->service_category != 0 && + hdr->service_category < ARRAY_SIZE(category_handler)) + { + handler = &category_handler[hdr->service_category]; + + if (hdr->losc > buf->len || hdr->losc > handler->max_len || + hdr->losc < handler->min_len) + { + return handler->err_code; + } + + if (!handler->reconfig_support && reconfig) + { + return BT_AVDTP_INVALID_CAPABILITIES; + } + + if (handler->handler != NULL) + { + net_buf_simple_save(&buf->b, &state); + err = handler->handler(buf); + net_buf_simple_restore(&buf->b, &state); + if (err != BT_AVDTP_SUCCESS) + { + return err; + } + } + + net_buf_pull_mem(buf, hdr->losc); + } + else + { + return BT_AVDTP_BAD_SERV_CATEGORY; + } + } + + return BT_AVDTP_SUCCESS; +} + +static void avdtp_process_configuration_cmd( + struct bt_avdtp *session, struct net_buf *buf, uint8_t tid, bool reconfig) +{ + int err = 0; + struct bt_avdtp_sep *sep; + struct net_buf *rsp_buf; + uint8_t avdtp_err_code = 0; + struct net_buf_simple_state state; + uint8_t service_category = 0; + + sep = avdtp_get_cmd_sep(buf, &avdtp_err_code, NULL); + avdtp_sep_lock(sep); + + if (sep == NULL) + { + err = -ENOENT; + } + else if (!reconfig && session->ops->set_configuration_ind == NULL) + { + err = -ENOTSUP; + } + else if (reconfig && session->ops->re_configuration_ind == NULL) + { + err = -ENOTSUP; + } + else if (!reconfig && sep->sep_info.inuse == 1) + { + err = -EBUSY; + } + else + { + uint8_t expected_state; + + if (reconfig) + { + expected_state = AVDTP_OPEN | AVDTP_OPENING; + } + else + { + expected_state = AVDTP_IDLE; + } + + if (!(sep->state & expected_state)) + { + err = -EACCES; + } + else if (buf->len >= 1U) + { + uint8_t int_seid = 0; + uint8_t err_code = 0; + + /* INT Stream Endpoint ID */ + + if (!reconfig) + { + /* int seid not in reconfig cmd */ + + int_seid = AVDTP_SEID_GET(net_buf_pull_u8(buf)); + } + + net_buf_simple_save(&buf->b, &state); + err_code = bt_avdtp_check_service_category(buf, &service_category, + reconfig); + net_buf_simple_restore(&buf->b, &state); + if (err_code) + { + avdtp_err_code = err_code; + err = -EINVAL; + } + else + { + if (!reconfig) + { + err = session->ops->set_configuration_ind(session, sep, + int_seid, buf, + &avdtp_err_code); + } + else + { + err = session->ops->re_configuration_ind(session, sep, buf, + &avdtp_err_code); + } + } + } + else + { + LOG_WRN("Invalid INT SEID"); + err = -ENODATA; + } + } + + rsp_buf = avdtp_create_rsp(err ? BT_AVDTP_REJECT : BT_AVDTP_ACCEPT, + reconfig ? + BT_AVDTP_RECONFIGURE : + BT_AVDTP_SET_CONFIGURATION, + tid); + if (!rsp_buf) + { + avdtp_sep_unlock(sep); + return; + } + + if (err) + { + if (avdtp_err_code == 0) + { + avdtp_err_code = avdtp_get_error_code(err); + } + + LOG_DBG("set configuration err code:%d", avdtp_err_code); + + DEBUGASSERT(net_buf_tailroom(rsp_buf) >= + (sizeof(service_category) + sizeof(avdtp_err_code))); + + /* error Service Category */ + + net_buf_add_u8(rsp_buf, service_category); + + /* ERROR CODE */ + + net_buf_add_u8(rsp_buf, avdtp_err_code); + } + + err = avdtp_send_rsp(session, rsp_buf); + + if (!reconfig && !err && !avdtp_err_code) + { + sep->session = session; + bt_avdtp_set_state(sep, AVDTP_CONFIGURED); + } + + avdtp_sep_unlock(sep); +} + +static void avdtp_process_configuration_rsp( + struct bt_avdtp *session, struct net_buf *buf, uint8_t msg_type, + bool reconfig) +{ + struct bt_avdtp_req *req = session->req; + + if (req == NULL) + { + return; + } + + k_work_cancel_delayable(&session->timeout_work); + + if (msg_type == BT_AVDTP_ACCEPT) + { + if (!reconfig) + { + bt_avdtp_set_state_lock(SET_CONF_REQ(req)->sep, AVDTP_CONFIGURED); + } + } + else if (msg_type == BT_AVDTP_REJECT) + { + if (buf->len < sizeof(uint8_t)) + { + LOG_WRN("Invalid RSP frame"); + req->status = BT_AVDTP_BAD_LENGTH; + } + else + { + /* Service Category */ + + net_buf_pull_u8(buf); + } + } + + if (req->status == BT_AVDTP_SUCCESS) + { + avdtp_set_status(req, buf, msg_type); + } + + if (req->status == BT_AVDTP_SUCCESS) + { + SET_CONF_REQ(req)->sep->session = session; + } + + bt_avdtp_clear_req(session); + + if (req->func != NULL) + { + req->func(req, NULL); + } +} + +static void avdtp_set_configuration_cmd(struct bt_avdtp *session, + struct net_buf *buf, uint8_t tid) +{ + avdtp_process_configuration_cmd(session, buf, tid, false); +} + +static void avdtp_set_configuration_rsp( + struct bt_avdtp *session, struct net_buf *buf, uint8_t msg_type) +{ + avdtp_process_configuration_rsp(session, buf, msg_type, false); +} + +static void avdtp_re_configure_cmd(struct bt_avdtp *session, + struct net_buf *buf, uint8_t tid) +{ + avdtp_process_configuration_cmd(session, buf, tid, true); +} + +static void avdtp_re_configure_rsp(struct bt_avdtp *session, + struct net_buf *buf, uint8_t msg_type) +{ + avdtp_process_configuration_rsp(session, buf, msg_type, true); +} + +static void avdtp_open_cmd(struct bt_avdtp *session, + struct net_buf *buf, uint8_t tid) +{ + int err = 0; + struct bt_avdtp_sep *sep; + struct net_buf *rsp_buf; + uint8_t avdtp_err_code = 0; + + sep = avdtp_get_cmd_sep(buf, &avdtp_err_code, NULL); + avdtp_sep_lock(sep); + + if (sep == NULL) + { + err = -ENOENT; + } + else if (session->ops->open_ind == NULL) + { + err = -ENOTSUP; + } + else + { + if (sep->state != AVDTP_CONFIGURED) + { + err = -EACCES; + } + else + { + err = session->ops->open_ind(session, sep, &avdtp_err_code); + } + } + + rsp_buf = avdtp_create_rsp(err ? BT_AVDTP_REJECT : BT_AVDTP_ACCEPT, + BT_AVDTP_OPEN, tid); + if (!rsp_buf) + { + avdtp_sep_unlock(sep); + return; + } + + if (err) + { + if (avdtp_err_code == 0) + { + avdtp_err_code = avdtp_get_error_code(err); + } + + LOG_DBG("open_ind err code:%d", avdtp_err_code); + + DEBUGASSERT(net_buf_tailroom(rsp_buf) >= sizeof(avdtp_err_code)); + net_buf_add_u8(rsp_buf, avdtp_err_code); + } + else + { + session->current_sep = sep; + } + + err = avdtp_send_rsp(session, rsp_buf); + + if (!err && !avdtp_err_code) + { + bt_avdtp_set_state(sep, AVDTP_OPENING); + } + + avdtp_sep_unlock(sep); +} + +static void avdtp_open_rsp(struct bt_avdtp *session, + struct net_buf *buf, uint8_t msg_type) +{ + struct bt_avdtp_req *req = session->req; + + if (req == NULL) + { + return; + } + + k_work_cancel_delayable(&session->timeout_work); + avdtp_set_status(req, buf, msg_type); + + if (req->status == BT_AVDTP_SUCCESS) + { + bt_avdtp_set_state_lock(CTRL_REQ(req)->sep, AVDTP_OPENING); + + /* wait the media l2cap is established */ + + if (!avdtp_media_connect(session, CTRL_REQ(req)->sep)) + { + return; + } + } + + if (req->status != BT_AVDTP_SUCCESS) + { + bt_avdtp_clear_req(session); + + if (req->func != NULL) + { + req->func(req, NULL); + } + } +} + +static void avdtp_handle_reject_with_acp_seid(struct net_buf *buf, + struct bt_avdtp_req *req) +{ + if (buf->len >= sizeof(uint8_t)) + { + uint8_t acp_seid; + + acp_seid = net_buf_pull_u8(buf); + + if (acp_seid != CTRL_REQ(req)->acp_stream_ep_id) + { + req->status = BT_AVDTP_BAD_ACP_SEID; + } + } + else + { + req->status = BT_AVDTP_BAD_LENGTH; + } +} + +static void avdtp_start_cmd(struct bt_avdtp *session, + struct net_buf *buf, uint8_t tid) +{ + int err = 0; + struct bt_avdtp_sep *sep; + struct net_buf *rsp_buf; + uint8_t avdtp_err_code = 0; + uint8_t acp_seid = 0; + + sep = avdtp_get_cmd_sep(buf, &avdtp_err_code, &acp_seid); + + avdtp_sep_lock(sep); + + if (sep == NULL) + { + err = -ENOENT; + } + else if (session->ops->start_ind == NULL) + { + err = -ENOTSUP; + } + else + { + if (sep->state != AVDTP_OPEN) + { + err = -EACCES; + } + else + { + err = session->ops->start_ind(session, sep, &avdtp_err_code); + } + } + + rsp_buf = avdtp_create_rsp(err ? BT_AVDTP_REJECT : BT_AVDTP_ACCEPT, + BT_AVDTP_START, tid); + if (!rsp_buf) + { + avdtp_sep_unlock(sep); + return; + } + + if (err) + { + if (avdtp_err_code == 0) + { + avdtp_err_code = avdtp_get_error_code(err); + } + + LOG_DBG("start err code:%d", avdtp_err_code); + + DEBUGASSERT(net_buf_tailroom(rsp_buf) >= (sizeof(acp_seid) + + sizeof(avdtp_err_code))); + net_buf_add_u8(rsp_buf, acp_seid); + net_buf_add_u8(rsp_buf, avdtp_err_code); + } + + err = avdtp_send_rsp(session, rsp_buf); + + if (!err && !avdtp_err_code) + { + bt_avdtp_set_state(sep, AVDTP_STREAMING); + } + + avdtp_sep_unlock(sep); +} + +static void avdtp_start_rsp(struct bt_avdtp *session, + struct net_buf *buf, uint8_t msg_type) +{ + struct bt_avdtp_req *req = session->req; + + if (req == NULL) + { + return; + } + + k_work_cancel_delayable(&session->timeout_work); + + if (msg_type == BT_AVDTP_REJECT) + { + avdtp_handle_reject_with_acp_seid(buf, req); + } + + if (req->status == BT_AVDTP_SUCCESS) + { + avdtp_set_status(req, buf, msg_type); + } + + if (req->status == BT_AVDTP_SUCCESS) + { + bt_avdtp_set_state_lock(CTRL_REQ(req)->sep, AVDTP_STREAMING); + } + else + { + /* From spec, if start cmd's initiator is sink, the endpoint state is + * set as AVDTP_STREAMING after sending start cmd. So if cmd fail, need + * to change back as AVDTP_OPEN. + */ + + if (CTRL_REQ(req)->sep->sep_info.tsep == BT_AVDTP_SINK) + { + bt_avdtp_set_state_lock(CTRL_REQ(req)->sep, AVDTP_OPEN); + } + } + + bt_avdtp_clear_req(session); + + if (req->func != NULL) + { + req->func(req, NULL); + } +} + +static void avdtp_close_cmd(struct bt_avdtp *session, + struct net_buf *buf, uint8_t tid) +{ + int err = 0; + struct bt_avdtp_sep *sep; + struct net_buf *rsp_buf; + uint8_t avdtp_err_code = 0; + + sep = avdtp_get_cmd_sep(buf, &avdtp_err_code, NULL); + avdtp_sep_lock(sep); + + if (sep == NULL) + { + err = -ENOENT; + } + else if (session->ops->close_ind == NULL) + { + err = -ENOTSUP; + } + else + { + if (!(sep->state & (AVDTP_OPEN | AVDTP_STREAMING))) + { + err = -EACCES; + } + else + { + err = session->ops->close_ind(session, sep, &avdtp_err_code); + } + } + + rsp_buf = avdtp_create_rsp(err ? BT_AVDTP_REJECT : BT_AVDTP_ACCEPT, + BT_AVDTP_CLOSE, tid); + if (!rsp_buf) + { + avdtp_sep_unlock(sep); + return; + } + + if (err) + { + if (avdtp_err_code == 0) + { + avdtp_err_code = avdtp_get_error_code(err); + } + + LOG_DBG("close err code:%d", avdtp_err_code); + + DEBUGASSERT(net_buf_tailroom(rsp_buf) >= sizeof(avdtp_err_code)); + net_buf_add_u8(rsp_buf, avdtp_err_code); + } + else + { + bt_avdtp_set_state(sep, AVDTP_CLOSING); + } + + err = avdtp_send_rsp(session, rsp_buf); + + /* From AVDTP spec, endpoint state should be idle after responding CLOSE. + * But before the sep->chan is released, the sep can't be used from stack + * perspective, so waiting the stream chan released. + */ + + if (err == 0 && avdtp_err_code == 0) + { + if (avdtp_media_chan_valid(sep)) + { + avdtp_sep_unlock(sep); + + /* TODO: If the INT does not close the transport channels within 3 + * seconds the ACP device may initiate the ABORT procedure to reset + * the states on both sides. + */ + + avdtp_schedule_media_disconnect_work(sep, DELAY_WORK_CHECK); + } + else + { + bt_avdtp_set_state(sep, AVDTP_IDLE); + avdtp_sep_unlock(sep); + + avdtp_endpoint_released(sep); + } + } + else + { + avdtp_sep_unlock(sep); + } +} + +static void avdtp_close_rsp(struct bt_avdtp *session, + struct net_buf *buf, uint8_t msg_type) +{ + struct bt_avdtp_req *req = session->req; + + if (req == NULL) + { + return; + } + + k_work_cancel_delayable(&session->timeout_work); + avdtp_set_status(req, buf, msg_type); + + if (req->status == BT_AVDTP_SUCCESS) + { + struct bt_avdtp_sep *sep = CTRL_REQ(req)->sep; + + /* release stream */ + + avdtp_sep_lock(sep); + if (avdtp_media_chan_valid(sep)) + { + int err; + uint8_t work_state; + + bt_avdtp_set_state(sep, AVDTP_CLOSING); + avdtp_sep_unlock(sep); + + err = avdtp_media_disconnect(sep); + if (err != 0) + { + work_state = DELAY_WORK_RETRY; + } + else + { + work_state = DELAY_WORK_CHECK; + } + + avdtp_schedule_media_disconnect_work(sep, work_state); + } + else + { + bt_avdtp_set_state(sep, AVDTP_IDLE); + avdtp_sep_unlock(sep); + + avdtp_endpoint_released(sep); + } + } + + bt_avdtp_clear_req(session); + + if (req->func != NULL) + { + req->func(req, NULL); + } +} + +static void avdtp_suspend_cmd(struct bt_avdtp *session, + struct net_buf *buf, uint8_t tid) +{ + int err = 0; + struct bt_avdtp_sep *sep; + struct net_buf *rsp_buf; + uint8_t avdtp_err_code = 0; + uint8_t acp_seid = 0; + + sep = avdtp_get_cmd_sep(buf, &avdtp_err_code, &acp_seid); + avdtp_sep_lock(sep); + + if (sep == NULL) + { + err = -ENOENT; + } + else if (session->ops->suspend_ind == NULL) + { + err = -ENOTSUP; + } + else + { + if (sep->state != AVDTP_STREAMING) + { + err = -EACCES; + } + else + { + err = session->ops->suspend_ind(session, sep, &avdtp_err_code); + } + } + + rsp_buf = avdtp_create_rsp(err ? BT_AVDTP_REJECT : BT_AVDTP_ACCEPT, + BT_AVDTP_SUSPEND, tid); + if (!rsp_buf) + { + avdtp_sep_unlock(sep); + return; + } + + if (err) + { + if (avdtp_err_code == 0) + { + avdtp_err_code = avdtp_get_error_code(err); + } + + LOG_DBG("suspend err code:%d", avdtp_err_code); + + DEBUGASSERT(net_buf_tailroom(rsp_buf) >= (sizeof(acp_seid) + + sizeof(avdtp_err_code))); + net_buf_add_u8(rsp_buf, acp_seid); + net_buf_add_u8(rsp_buf, avdtp_err_code); + } + + err = avdtp_send_rsp(session, rsp_buf); + + if (!err && !avdtp_err_code) + { + bt_avdtp_set_state(sep, AVDTP_OPEN); + } + + avdtp_sep_unlock(sep); +} + +static void avdtp_suspend_rsp(struct bt_avdtp *session, + struct net_buf *buf, uint8_t msg_type) +{ + struct bt_avdtp_req *req = session->req; + + if (req == NULL) + { + return; + } + + k_work_cancel_delayable(&session->timeout_work); + + if (msg_type == BT_AVDTP_REJECT) + { + avdtp_handle_reject_with_acp_seid(buf, req); + } + + if (req->status == BT_AVDTP_SUCCESS) + { + avdtp_set_status(req, buf, msg_type); + } + + if (req->status == BT_AVDTP_SUCCESS) + { + bt_avdtp_set_state_lock(CTRL_REQ(req)->sep, AVDTP_OPEN); + } + + bt_avdtp_clear_req(session); + + if (req->func != NULL) + { + req->func(req, NULL); + } +} + +static void avdtp_abort_cmd(struct bt_avdtp *session, + struct net_buf *buf, uint8_t tid) +{ + int err = 0; + struct bt_avdtp_sep *sep; + struct net_buf *rsp_buf; + uint8_t avdtp_err_code = 0; + + sep = avdtp_get_cmd_sep(buf, &avdtp_err_code, NULL); + avdtp_sep_lock(sep); + + if (sep == NULL) + { + err = -ENOENT; + } + else if (session->ops->abort_ind == NULL) + { + err = -ENOTSUP; + } + else + { + /* all current sep state is OK for abort operation */ + + err = session->ops->abort_ind(session, sep, &avdtp_err_code); + } + + rsp_buf = avdtp_create_rsp(err ? BT_AVDTP_REJECT : BT_AVDTP_ACCEPT, + BT_AVDTP_ABORT, tid); + if (!rsp_buf) + { + avdtp_sep_unlock(sep); + return; + } + + if (err) + { + if (avdtp_err_code == 0) + { + avdtp_err_code = avdtp_get_error_code(err); + } + + LOG_DBG("abort err code:%d", avdtp_err_code); + + DEBUGASSERT(net_buf_tailroom(rsp_buf) >= sizeof(avdtp_err_code)); + net_buf_add_u8(rsp_buf, avdtp_err_code); + } + + err = avdtp_send_rsp(session, rsp_buf); + + if (!err && !avdtp_err_code) + { + if (avdtp_media_chan_valid(sep)) + { + bt_avdtp_set_state(sep, AVDTP_ABORTING); + avdtp_sep_unlock(sep); + + avdtp_schedule_media_disconnect_work(sep, DELAY_WORK_CHECK); + } + else + { + bt_avdtp_set_state(sep, AVDTP_IDLE); + avdtp_sep_unlock(sep); + + avdtp_endpoint_released(sep); + } + } + else + { + avdtp_sep_unlock(sep); + } +} + +static void avdtp_abort_rsp(struct bt_avdtp *session, + struct net_buf *buf, uint8_t msg_type) +{ + struct bt_avdtp_req *req = session->req; + + if (req == NULL) + { + return; + } + + k_work_cancel_delayable(&session->timeout_work); + + if (msg_type == BT_AVDTP_ACCEPT) + { + struct bt_avdtp_sep *sep = CTRL_REQ(req)->sep; + + req->status = BT_AVDTP_SUCCESS; + + /* release stream */ + + avdtp_sep_lock(sep); + if (avdtp_media_chan_valid(sep)) + { + int err; + uint8_t work_state; + + bt_avdtp_set_state(sep, AVDTP_ABORTING); + avdtp_sep_unlock(sep); + + err = avdtp_media_disconnect(sep); + if (err != 0) + { + work_state = DELAY_WORK_RETRY; + } + else + { + work_state = DELAY_WORK_CHECK; + } + + avdtp_schedule_media_disconnect_work(sep, work_state); + } + else + { + bt_avdtp_set_state(sep, AVDTP_IDLE); + avdtp_sep_unlock(sep); + + avdtp_endpoint_released(sep); + } + } + else + { + /* Spec only allows accept, spec doesn't define the packet format of + * reject. + */ + + req->status = BT_AVDTP_BAD_STATE; + } + + bt_avdtp_clear_req(session); + + if (req->func != NULL) + { + req->func(req, NULL); + } +} + +static void avdtp_delay_report_cmd(struct bt_avdtp *session, + struct net_buf *buf, uint8_t tid) +{ + int err = 0; + struct bt_avdtp_sep *sep; + struct net_buf *rsp_buf; + uint8_t avdtp_err_code = 0; + + sep = avdtp_get_cmd_sep(buf, &avdtp_err_code, NULL); + + if (sep == NULL) + { + err = -ENOENT; + } + else if (session->ops->delay_report_ind == NULL) + { + err = -ENOTSUP; + } + else + { + if ((sep->state & (AVDTP_CONFIGURED | AVDTP_OPENING | AVDTP_OPEN | + AVDTP_STREAMING)) == 0) + { + err = -EACCES; + } + else + { + err = session->ops->delay_report_ind(session, sep, buf, + &avdtp_err_code); + } + } + + rsp_buf = avdtp_create_rsp(err ? BT_AVDTP_REJECT : BT_AVDTP_ACCEPT, + BT_AVDTP_DELAYREPORT, tid); + if (rsp_buf == NULL) + { + return; + } + + if (err != 0) + { + if (avdtp_err_code == 0) + { + avdtp_err_code = avdtp_get_error_code(err); + } + + LOG_DBG("delay report err code:%d", avdtp_err_code); + + DEBUGASSERT(net_buf_tailroom(rsp_buf) >= sizeof(avdtp_err_code)); + net_buf_add_u8(rsp_buf, avdtp_err_code); + } + + (void)avdtp_send_rsp(session, rsp_buf); +} + +static void avdtp_delay_report_rsp(struct bt_avdtp *session, + struct net_buf *buf, uint8_t msg_type) +{ + struct bt_avdtp_req *req = session->req; + + if (req == NULL) + { + return; + } + + k_work_cancel_delayable(&session->timeout_work); + avdtp_set_status(req, buf, msg_type); + bt_avdtp_clear_req(session); + + if (req->func != NULL) + { + req->func(req, buf); + } +} + +static void avdtp_get_configuration_cmd(struct bt_avdtp *session, + struct net_buf *buf, uint8_t tid) +{ + int err = 0; + struct net_buf *rsp_buf; + struct bt_avdtp_sep *sep; + uint8_t avdtp_err_code = 0; + + sep = avdtp_get_cmd_sep(buf, &avdtp_err_code, NULL); + + if (sep == NULL) + { + err = -ENOENT; + } + else if (session->ops->get_configuration_ind == NULL) + { + err = -ENOTSUP; + } + else if (sep->state & (AVDTP_IDLE | AVDTP_CLOSING | AVDTP_ABORTING)) + { + /* The procedure shall fail when the addressed SEP is in Idle, + * Closing or Aborting state. + */ + + err = -EACCES; + } + else + { + rsp_buf = avdtp_create_rsp(BT_AVDTP_ACCEPT, BT_AVDTP_GET_CONFIGURATION, + tid); + if (rsp_buf == NULL) + { + return; + } + + err = session->ops->get_configuration_ind(session, sep, rsp_buf, + &avdtp_err_code); + if (err != 0) + { + net_buf_unref(rsp_buf); + } + } + + if (err != 0) + { + rsp_buf = avdtp_create_rsp(BT_AVDTP_REJECT, BT_AVDTP_GET_CONFIGURATION, + tid); + if (rsp_buf == NULL) + { + return; + } + + if (avdtp_err_code == 0) + { + avdtp_err_code = avdtp_get_error_code(err); + } + + LOG_DBG("get config err code:%d", avdtp_err_code); + + DEBUGASSERT(net_buf_tailroom(rsp_buf) >= sizeof(avdtp_err_code)); + net_buf_add_u8(rsp_buf, avdtp_err_code); + } + + (void)avdtp_send_rsp(session, rsp_buf); +} + +static void avdtp_get_configuration_rsp( + struct bt_avdtp *session, struct net_buf *buf, uint8_t msg_type) +{ + struct bt_avdtp_req *req = session->req; + + if (req == NULL) + { + return; + } + + k_work_cancel_delayable(&session->timeout_work); + avdtp_set_status(req, buf, msg_type); + bt_avdtp_clear_req(session); + + if (req->func != NULL) + { + req->func(req, buf); + } +} + +/* Timeout handler */ + +static void avdtp_timeout(struct k_work *work) +{ + struct bt_avdtp_req *req = (AVDTP_KWORK(work))->req; + + /* add process code */ + + /* Gracefully Disconnect the Signalling and streaming L2cap chann */ + + if (req) + { + LOG_DBG("Failed Signal_id = %d", req->sig); + + switch (req->sig) + { + case BT_AVDTP_DISCOVER: + case BT_AVDTP_GET_CAPABILITIES: + case BT_AVDTP_SET_CONFIGURATION: + case BT_AVDTP_RECONFIGURE: + case BT_AVDTP_OPEN: + case BT_AVDTP_CLOSE: + case BT_AVDTP_START: + case BT_AVDTP_SUSPEND: + req->status = BT_AVDTP_TIME_OUT; + req->func(req, NULL); + break; + default: + break; + } + + AVDTP_KWORK(work)->req = NULL; + } +} + +static int avdtp_send_cmd(struct bt_avdtp *session, + struct net_buf *buf, struct bt_avdtp_req *req) +{ + struct bt_avdtp_single_sig_hdr *hdr; + + /* From all the calls, the session, buf and req can't be NULL. */ + + DEBUGASSERT((session != NULL && buf != NULL && req != NULL)); + + avdtp_lock(session); + + if (session->req != NULL) + { + avdtp_unlock(session); + net_buf_unref(buf); + return -EBUSY; + } + + session->req = req; + avdtp_unlock(session); + + hdr = (struct bt_avdtp_single_sig_hdr *)buf->data; + + /* Save the sent request information */ + + req->sig = AVDTP_SIGID_GET(hdr->signal_id); + req->tid = AVDTP_TID_GET(hdr->hdr); + LOG_DBG("Command sent: buf=%p, sig=0x%02X, tid=0x%02X", buf, req->sig, + req->tid); + + avdtp_send_common(session, buf); + + /* Start timeout work */ + + bt_work_reschedule(&session->timeout_work, AVDTP_TIMEOUT); + + return 0; +} + +/* L2CAP Interface callbacks */ + +void bt_avdtp_l2cap_connected(struct bt_l2cap_chan *chan) +{ + struct bt_avdtp *session; + + if (!chan) + { + LOG_ERR("Invalid AVDTP chan"); + return; + } + + session = AVDTP_CHAN(chan); + LOG_DBG("chan %p session %p", chan, session); + + /* notify a2dp connection result */ + + session->ops->connected(session); +} + +static void avdtp_release_work(struct k_work *work) +{ + struct bt_avdtp_sep *sep; + struct bt_avdtp_sep *next; + struct bt_avdtp *session = CONTAINER_OF(work, struct bt_avdtp, + _release_work); + int err; + uint8_t work_state; + + list_for_every_container_safe(&seps, sep, next, _node) + { + if (sep->session != session || sep->state == AVDTP_IDLE) + { + continue; + } + + if (avdtp_media_chan_valid(sep)) + { + err = avdtp_media_disconnect(sep); + if (err != 0) + { + work_state = DELAY_WORK_RETRY; + } + else + { + work_state = DELAY_WORK_CHECK; + } + + avdtp_schedule_media_disconnect_work(sep, work_state); + } + else + { + bt_avdtp_set_state_lock(sep, AVDTP_IDLE); + avdtp_endpoint_released(sep); + } + } +} + +void bt_avdtp_clear_tx(struct bt_avdtp *session) +{ + struct net_buf *buf; + struct net_buf *next; + struct avdtp_buf_user_data *user_data; + + list_for_every_container_safe(&avdtp_tx_list, buf, next, node) + { + user_data = net_buf_user_data(buf); + + if (user_data->session == session) + { + list_find_and_remove(&avdtp_tx_list, &buf->node); + net_buf_unref(buf); + } + } +} + +void bt_avdtp_l2cap_disconnected(struct bt_l2cap_chan *chan) +{ + struct bt_avdtp *session = AVDTP_CHAN(chan); + + LOG_DBG("chan %p session %p", chan, session); + + k_work_cancel_delayable(&session->timeout_work); + + /* Clear the Pending req if set */ + + if (session->req) + { + struct bt_avdtp_req *req = session->req; + + req->status = BT_AVDTP_BAD_STATE; + bt_avdtp_clear_req(session); + + if (req->func != NULL) + { + req->func(req, NULL); + } + } + + net_buf_drop(&session->reasm_buf); + + nxsem_wait_uninterruptible(&avdtp_sem_lock); + bt_avdtp_clear_tx(session); + nxsem_post(&avdtp_sem_lock); + + /* notify a2dp disconnect */ + + session->ops->disconnected(session); + + k_work_submit(&session->_release_work); +} + +void (*cmd_handler[])(struct bt_avdtp *session, struct net_buf *buf, + uint8_t tid) = +{ + avdtp_discover_cmd, /* BT_AVDTP_DISCOVER */ + avdtp_get_capabilities_cmd, /* BT_AVDTP_GET_CAPABILITIES */ + avdtp_set_configuration_cmd, /* BT_AVDTP_SET_CONFIGURATION */ + avdtp_get_configuration_cmd, /* BT_AVDTP_GET_CONFIGURATION */ + avdtp_re_configure_cmd, /* BT_AVDTP_RECONFIGURE */ + avdtp_open_cmd, /* BT_AVDTP_OPEN */ + avdtp_start_cmd, /* BT_AVDTP_START */ + avdtp_close_cmd, /* BT_AVDTP_CLOSE */ + avdtp_suspend_cmd, /* BT_AVDTP_SUSPEND */ + avdtp_abort_cmd, /* BT_AVDTP_ABORT */ + NULL, /* BT_AVDTP_SECURITY_CONTROL */ + avdtp_get_all_capabilities_cmd, /* BT_AVDTP_GET_ALL_CAPABILITIES */ + avdtp_delay_report_cmd, /* BT_AVDTP_DELAYREPORT */ +}; + +void (*rsp_handler[])(struct bt_avdtp *session, struct net_buf *buf, + uint8_t msg_type) = +{ + avdtp_discover_rsp, /* BT_AVDTP_DISCOVER */ + avdtp_get_capabilities_rsp, /* BT_AVDTP_GET_CAPABILITIES */ + avdtp_set_configuration_rsp, /* BT_AVDTP_SET_CONFIGURATION */ + avdtp_get_configuration_rsp, /* BT_AVDTP_GET_CONFIGURATION */ + avdtp_re_configure_rsp, /* BT_AVDTP_RECONFIGURE */ + avdtp_open_rsp, /* BT_AVDTP_OPEN */ + avdtp_start_rsp, /* BT_AVDTP_START */ + avdtp_close_rsp, /* BT_AVDTP_CLOSE */ + avdtp_suspend_rsp, /* BT_AVDTP_SUSPEND */ + avdtp_abort_rsp, /* BT_AVDTP_ABORT */ + NULL, /* BT_AVDTP_SECURITY_CONTROL */ + avdtp_get_capabilities_rsp, /* BT_AVDTP_GET_ALL_CAPABILITIES */ + avdtp_delay_report_rsp, /* BT_AVDTP_DELAYREPORT */ +}; + +static int avdtp_rel_and_return(struct bt_avdtp *session) +{ + net_buf_drop(&session->reasm_buf); + + return 0; +} + +static bool avdtp_rsp_expected(struct bt_avdtp *session, + uint8_t tid, uint8_t sigid) +{ + /* validate if the response is expected */ + + if (session->req == NULL) + { + LOG_DBG("Drop unexpected peer response"); + return false; + } + + if (session->req->sig != sigid || session->req->tid != tid) + { + LOG_DBG("Peer mismatch resp, expected sig[0x%02x] tid[0x%02x], " + "dropped", + session->req->sig, + session->req->tid); + return false; + } + + return true; +} + +static int bt_avdtp_l2cap_frags_recv( + struct bt_avdtp *session, struct net_buf *buf, uint8_t pack_type, + bool *finish) +{ + struct net_buf *sdu_buf; + struct bt_avdtp_single_sig_hdr *single_hdr; + struct bt_avdtp_continue_end_sig_hdr *cont_hdr; + + *finish = false; + + if (pack_type == BT_AVDTP_PACKET_TYPE_START) + { + struct bt_avdtp_start_sig_hdr *start_hdr; + uint32_t total_len; + uint32_t start_len; + + if (buf->len < sizeof(*start_hdr)) + { + LOG_ERR("Recvd Wrong AVDTP Header"); + return 0; + } + + start_len = buf->len; + start_hdr = net_buf_pull_mem(buf, sizeof(*start_hdr)); + + if (session->reasm_buf != NULL) + { + LOG_ERR("get start packet during reassembly"); + net_buf_drop(&session->reasm_buf); + } + + if ((AVDTP_MSG_GET(start_hdr->hdr) != BT_AVDTP_CMD) && + (!avdtp_rsp_expected(session, AVDTP_TID_GET(start_hdr->hdr), + AVDTP_SIGID_GET(start_hdr->signal_id)))) + { + return avdtp_rel_and_return(session); + } + + session->num_of_signal_pkts = start_hdr->num_of_signal_pkts; + + if (session->num_of_signal_pkts < 2) + { + LOG_ERR("Unexpected number of signal packets: %d", + session->num_of_signal_pkts); + return avdtp_rel_and_return(session); + } + + session->reasm_buf = net_buf_alloc(&avdtp_pool, 0); + if (session->reasm_buf == NULL) + { + LOG_ERR("fail to alloc reasm buf"); + return 0; + } + + sdu_buf = session->reasm_buf; + + /* 1. The L2CAP payload of the start and all continue packets of a + * fragmented message shall have the same length. 2. The L2CAP payload + * of end packets shall not be larger than the start and any possible + * continue packet that belong to the same message. + */ + + /* Since the end packet length is unknown, + * calculate the length by assuming end packet has one byte data. + */ + + total_len = sizeof(*single_hdr) + (start_len - sizeof(*start_hdr)) + + (start_len - sizeof(*cont_hdr)) * + (session->num_of_signal_pkts - 2) + 1; + if (total_len > net_buf_tailroom(sdu_buf)) + { + LOG_ERR("Not enough buffer space"); + return avdtp_rel_and_return(session); + } + + if (net_buf_tailroom(sdu_buf) < sizeof(*single_hdr)) + { + return avdtp_rel_and_return(session); + } + + single_hdr = net_buf_add(sdu_buf, sizeof(*single_hdr)); + + /* unnecessary to change the packet type as single, not used later. */ + + single_hdr->hdr = start_hdr->hdr; + single_hdr->signal_id = start_hdr->signal_id; + + session->num_of_signal_pkts--; + + /* The total length is less than sdu_buf, so the start packet len is + * less than sdu_buf too. + */ + + net_buf_add_mem(sdu_buf, buf->data, buf->len); + + return 0; /* wait other packets */ + } + + /* continue packet or end packet */ + + sdu_buf = session->reasm_buf; + if (sdu_buf == NULL) + { + LOG_ERR("Discard unexpected continue or end packet"); + return 0; + } + + if (buf->len < sizeof(*cont_hdr)) + { + LOG_ERR("Recvd Wrong AVDTP Header"); + return 0; + } + + cont_hdr = net_buf_pull_mem(buf, sizeof(*cont_hdr)); + + single_hdr = (struct bt_avdtp_single_sig_hdr *)&sdu_buf->data[0]; + + /* compare message type with start packet */ + + if (AVDTP_MSG_GET(cont_hdr->hdr) != AVDTP_MSG_GET(single_hdr->hdr) || + AVDTP_TID_GET(cont_hdr->hdr) != AVDTP_TID_GET(single_hdr->hdr)) + { + LOG_ERR("Recvd Wrong msg type or transaction label"); + return avdtp_rel_and_return(session); + } + + if (session->num_of_signal_pkts == 0) + { + LOG_ERR("num_of_signal_pkts is already 0"); + return avdtp_rel_and_return(session); + } + + if (buf->len < net_buf_tailroom(sdu_buf)) + { + net_buf_add_mem(sdu_buf, buf->data, buf->len); + } + else + { + LOG_ERR("Not enough buffer space"); + return avdtp_rel_and_return(session); + } + + session->num_of_signal_pkts--; + if (pack_type == BT_AVDTP_PACKET_TYPE_END) + { + if (session->num_of_signal_pkts == 0) + { + /* all frags are received */ + + *finish = true; + } + else + { + LOG_ERR("Unexpected packet end"); + return avdtp_rel_and_return(session); + } + } + + return 0; +} + +static int avdtp_rel_and_rej(struct bt_avdtp *session, + uint8_t sigid, uint8_t tid) +{ + struct net_buf *rsp_buf; + int err; + + net_buf_drop(&session->reasm_buf); + + rsp_buf = avdtp_create_rsp(BT_AVDTP_GEN_REJECT, sigid, tid); + if (!rsp_buf) + { + LOG_ERR("Error: No Buff available"); + return 0; + } + + err = bt_l2cap_chan_send(&session->br_chan.chan, rsp_buf); + if (err < 0) + { + net_buf_unref(rsp_buf); + LOG_ERR("Error:L2CAP send fail - result = %d", err); + } + + return 0; +} + +int bt_avdtp_l2cap_recv(struct bt_l2cap_chan *chan, struct net_buf *buf) +{ + struct bt_avdtp_single_sig_hdr *single_hdr; + struct bt_avdtp *session = AVDTP_CHAN(chan); + uint8_t pack_type; + uint8_t sigid; + int err = 0; + uint8_t hdr; + + if (buf->len < sizeof(hdr)) + { + LOG_ERR("Recvd Wrong AVDTP Header"); + return -EINVAL; + } + + hdr = buf->data[0]; + pack_type = AVDTP_PKT_GET(hdr); + + LOG_DBG("pack_type[%d] msg_type[%d] tid[%d]", pack_type, + (uint8_t)AVDTP_MSG_GET(hdr), + (uint8_t)AVDTP_TID_GET(hdr)); + + if (pack_type != BT_AVDTP_PACKET_TYPE_SINGLE) + { + bool finish; + + err = bt_avdtp_l2cap_frags_recv(session, buf, pack_type, &finish); + + if (err != 0 || !finish) + { + return err; + } + + buf = session->reasm_buf; + } + else + { + if (session->reasm_buf != NULL) + { + LOG_DBG("get single packet during reassembly"); + + net_buf_drop(&session->reasm_buf); + } + + if (buf->len < sizeof(*single_hdr)) + { + LOG_ERR("Recvd Wrong AVDTP Header"); + return -EINVAL; + } + } + + /* process the buf as single packet no matter it is reassembly or not */ + + single_hdr = net_buf_pull_mem(buf, sizeof(*single_hdr)); + sigid = AVDTP_SIGID_GET(single_hdr->signal_id); + + if (AVDTP_MSG_GET(single_hdr->hdr) == BT_AVDTP_CMD) + { + if (sigid != 0U && sigid <= BT_AVDTP_DELAYREPORT && + cmd_handler[sigid - 1U] != NULL) + { + cmd_handler[sigid - 1U](session, buf, + AVDTP_TID_GET(single_hdr->hdr)); + + return avdtp_rel_and_return(session); + } + + return avdtp_rel_and_rej(session, sigid, + AVDTP_TID_GET(single_hdr->hdr)); + } + + if (avdtp_rsp_expected(session, AVDTP_TID_GET(single_hdr->hdr), sigid) && + sigid != 0U && + sigid <= BT_AVDTP_DELAYREPORT && rsp_handler[sigid - 1U] != NULL) + { + rsp_handler[sigid - 1U](session, buf, AVDTP_MSG_GET(single_hdr->hdr)); + } + + return avdtp_rel_and_return(session); +} + +static const struct bt_l2cap_chan_ops signal_chan_ops = +{ + .connected = bt_avdtp_l2cap_connected, + .disconnected = bt_avdtp_l2cap_disconnected, + .recv = bt_avdtp_l2cap_recv, +}; + +/* A2DP Layer interface */ + +int bt_avdtp_connect(struct bt_conn *conn, struct bt_avdtp *session) +{ + int err; + + if (!session) + { + return -EINVAL; + } + + /* there are headsets that initiate the AVDTP signal l2cap connection + * at the same time when DUT initiates the same l2cap connection. Use the + * `conn` to check whether the l2cap creation is already started. The whole + * `session` is cleared by upper layer if it is new l2cap connection. + */ + + nxsem_wait_uninterruptible(&avdtp_sem_lock); + + if (session->br_chan.chan.conn != NULL) + { + nxsem_post(&avdtp_sem_lock); + return -ENOMEM; + } + + /* The release work submitted when the previous connection was + * disconnected may still be pending. Re-initializing a queued work item + * would corrupt the work queue, so reject the session reuse until the + * release work has completed. + */ + + if (k_work_busy_get(&session->_release_work) != 0) + { + nxsem_post(&avdtp_sem_lock); + return -EBUSY; + } + + /* Locking semaphore initialized to 1 (unlocked). It has to be + * initialized before the session is published, since the session memory is + * owned and cleared by the upper layer. + */ + + nxsem_init(&session->sem_lock, 0, 1); + session->br_chan.chan.conn = conn; + bt_avdtp_clear_tx(session); + nxsem_post(&avdtp_sem_lock); + + k_work_init(&session->_release_work, avdtp_release_work); + k_work_init_delayable(&session->timeout_work, avdtp_timeout); + session->br_chan.rx.mtu = BT_L2CAP_RX_MTU; + session->br_chan.chan.ops = &signal_chan_ops; + session->br_chan.required_sec_level = BT_SECURITY_L2; + + err = bt_l2cap_chan_connect(conn, &session->br_chan.chan, + BT_L2CAP_PSM_AVDTP); + if (err != 0) + { + /* If the L2CAP connection creation failed before the channel + * was added to the connection, no disconnected callback will ever run + * to clear the connection marker set above, which would prevent the + * session from ever being used again. Clear the marker so that the + * session can be reused. If the failure happened after the channel was + * added, the marker has already been cleared by + * bt_l2cap_br_chan_del(), so only clear it if it still holds the value + * set above. + */ + + nxsem_wait_uninterruptible(&avdtp_sem_lock); + if (session->br_chan.chan.conn == conn) + { + session->br_chan.chan.conn = NULL; + } + + nxsem_post(&avdtp_sem_lock); + } + + return err; +} + +int bt_avdtp_disconnect(struct bt_avdtp *session) +{ + struct bt_avdtp_sep *sep; + struct bt_avdtp_sep *next; + int err; + + if (!session) + { + return -EINVAL; + } + + LOG_DBG("session %p", session); + + list_for_every_container_safe(&seps, sep, next, _node) + { + if ((sep->session != session) || (!avdtp_media_chan_valid(sep))) + { + continue; + } + + err = avdtp_media_disconnect(sep); + if (err != 0) + { + LOG_ERR("fail to disconnect media connection"); + return err; + } + + avdtp_schedule_media_disconnect_work(sep, DELAY_WORK_CHECK); + } + + return bt_l2cap_chan_disconnect(&session->br_chan.chan); +} + +int bt_avdtp_l2cap_accept( + struct bt_conn *conn, struct bt_l2cap_server *server, + struct bt_l2cap_chan **chan) +{ + struct bt_avdtp *session = NULL; + int result; + + LOG_DBG("conn %p", conn); + + /* Get the AVDTP session from upper layer */ + + result = event_cb->accept(conn, &session); + if (result < 0) + { + return result; + } + + /* there are headsets that initiate the AVDTP signal l2cap connection + * at the same time when DUT initiates the same l2cap connection. Use the + * `conn` to check whether the l2cap creation is already started. The whole + * `session` is cleared by upper layer if it is new l2cap connection. + */ + + nxsem_wait_uninterruptible(&avdtp_sem_lock); + + if (session->br_chan.chan.conn == NULL) + { + /* The release work submitted when the previous connection + * was disconnected may still be pending. Re-initializing a queued work + * item would corrupt the work queue, so reject the session reuse until + * the release work has completed. + */ + + if (k_work_busy_get(&session->_release_work) != 0) + { + nxsem_post(&avdtp_sem_lock); + return -ENOMEM; + } + + /* Locking semaphore initialized to 1 (unlocked). It has to be + * initialized before the session is published, since the session + * memory is owned and cleared by the upper layer. + */ + + nxsem_init(&session->sem_lock, 0, 1); + session->br_chan.chan.conn = conn; + bt_avdtp_clear_tx(session); + nxsem_post(&avdtp_sem_lock); + + k_work_init(&session->_release_work, avdtp_release_work); + k_work_init_delayable(&session->timeout_work, avdtp_timeout); + session->br_chan.chan.ops = &signal_chan_ops; + session->br_chan.rx.mtu = BT_L2CAP_RX_MTU; + *chan = &session->br_chan.chan; + } + else + { + nxsem_post(&avdtp_sem_lock); + + /* get the current opening endpoint */ + + if (session->current_sep != NULL) + { + session->current_sep->session = session; + session->current_sep->chan.chan.ops = &stream_chan_ops; + session->current_sep->chan.rx.mtu = BT_L2CAP_RX_MTU; + session->current_sep->chan.required_sec_level = BT_SECURITY_L2; + *chan = &session->current_sep->chan.chan; + session->current_sep = NULL; + } + else + { + return -ENOMEM; + } + } + + return 0; +} + +/* Application will register its callback */ + +int bt_avdtp_register(struct bt_avdtp_event_cb *cb) +{ + int err; + static struct bt_l2cap_server avdtp_l2cap = + { + .psm = BT_L2CAP_PSM_AVDTP, + .sec_level = BT_SECURITY_L2, + .accept = bt_avdtp_l2cap_accept, + }; + + LOG_DBG(""); + + if (cb == NULL) + { + return -EINVAL; + } + + if (event_cb == cb) + { + return -EALREADY; + } + + if (event_cb != NULL) + { + return -EEXIST; + } + + event_cb = cb; + + /* Register AVDTP PSM with L2CAP */ + + err = bt_l2cap_br_server_register(&avdtp_l2cap); + if ((err < 0) && (err != -EEXIST)) + { + event_cb = NULL; + LOG_ERR("AVDTP L2CAP Registration failed %d", err); + return err; + } + + return 0; +} + +int bt_avdtp_register_sep(uint8_t media_type, + uint8_t sep_type, struct bt_avdtp_sep *sep) +{ + LOG_DBG(""); + + static uint8_t bt_avdtp_sep = BT_AVDTP_MIN_SEID; + + if (!sep) + { + return -EIO; + } + + if (bt_avdtp_sep == BT_AVDTP_MAX_SEID) + { + return -EIO; + } + + nxsem_wait_uninterruptible(&avdtp_sem_lock); + if (list_find(&seps, &sep->_node, NULL)) + { + nxsem_post(&avdtp_sem_lock); + LOG_ERR("Endpoint is already registered"); + return -EEXIST; + } + + /* the id allocation need be locked to protect it */ + + sep->sep_info.id = bt_avdtp_sep++; + sep->sep_info.inuse = 0U; + sep->sep_info.media_type = media_type; + sep->sep_info.tsep = sep_type; + + /* Locking semaphore initialized to 1 (unlocked) */ + + nxsem_init(&sep->sem_lock, 0, 1); + k_work_init_delayable(&sep->_delay_work, avdtp_media_disconnect_work); + /* The endpoint is not in the `seps` list yet, so it cannot be accessed + * by any other context. Set the state without taking `sep->sem_lock`, + * which would invert the lock order used by the command handlers. + */ + + bt_avdtp_set_state(sep, AVDTP_IDLE); + + list_add_tail(&seps, &sep->_node); + nxsem_post(&avdtp_sem_lock); + + return 0; +} + +/* AVDTP Discover Request */ + +int bt_avdtp_discover(struct bt_avdtp *session, + struct bt_avdtp_discover_params *param) +{ + struct net_buf *buf; + + LOG_DBG(""); + if (!param || !session) + { + LOG_DBG("Error: Callback/Session not valid"); + return -EINVAL; + } + + buf = avdtp_create_pdu(BT_AVDTP_CMD, BT_AVDTP_DISCOVER, + avdtp_get_tid(session)); + if (!buf) + { + LOG_ERR("Error: No Buff available"); + return -ENOMEM; + } + + return avdtp_send_cmd(session, buf, ¶m->req); +} + +int bt_avdtp_parse_sep(struct net_buf *buf, + struct bt_avdtp_sep_info *sep_info) +{ + struct bt_avdtp_sep_data *sep_data; + + if ((sep_info != NULL) && (buf != NULL)) + { + if (buf->len >= sizeof(*sep_data)) + { + sep_data = net_buf_pull_mem(buf, sizeof(*sep_data)); + sep_info->inuse = sep_data->inuse; + sep_info->id = sep_data->id; + sep_info->tsep = sep_data->tsep; + sep_info->media_type = sep_data->media_type; + return 0; + } + } + + return -EINVAL; +} + +/* AVDTP Get Capabilities Request */ + +int bt_avdtp_get_capabilities(struct bt_avdtp *session, + struct bt_avdtp_get_capabilities_params *param) +{ + struct net_buf *buf; + + LOG_DBG(""); + if (!param || !session) + { + LOG_DBG("Error: Callback/Session not valid"); + return -EINVAL; + } + + buf = avdtp_create_pdu(BT_AVDTP_CMD, + param->get_all_caps ? BT_AVDTP_GET_ALL_CAPABILITIES + : BT_AVDTP_GET_CAPABILITIES, + avdtp_get_tid(session)); + if (!buf) + { + LOG_ERR("Error: No Buff available"); + return -ENOMEM; + } + + DEBUGASSERT(net_buf_tailroom(buf) >= sizeof(uint8_t)); + + /* Body of the message */ + + net_buf_add_u8(buf, AVDTP_SEID_PREP(param->stream_endpoint_id)); + + return avdtp_send_cmd(session, buf, ¶m->req); +} + +int bt_avdtp_parse_capability_codec(struct net_buf *buf, + uint8_t *codec_type, + uint8_t **codec_info_element, + uint16_t *codec_info_element_len, + bool *delay_report) +{ + uint8_t data; + uint8_t length; + + if (!buf) + { + LOG_DBG("Error: buf not valid"); + return -EINVAL; + } + + if (codec_type == NULL || codec_info_element == NULL || + codec_info_element_len == NULL) + { + LOG_DBG("Error: parameters not valid"); + return -EINVAL; + } + + *codec_type = 0; + *codec_info_element = NULL; + *codec_info_element_len = 0; + + if (delay_report != NULL) + { + *delay_report = false; + } + + while (buf->len) + { + data = net_buf_pull_u8(buf); + + if (buf->len < sizeof(length)) + { + return -EINVAL; + } + + length = net_buf_pull_u8(buf); + if (buf->len < length) + { + return -EINVAL; + } + + switch (data) + { + case BT_AVDTP_SERVICE_DELAY_REPORTING: + if (length != 0) + { + return -EINVAL; + } + + if (delay_report != NULL) + { + *delay_report = true; + } + break; + + case BT_AVDTP_SERVICE_MEDIA_CODEC: + if (length < sizeof(struct bt_avdtp_media_codec_capabilities)) + { + return -EINVAL; + } + + data = net_buf_pull_u8(buf); + length--; + if ((data & AVDTP_SEP_MEDIA_TYPE_MASK) == BT_AVDTP_AUDIO) + { + data = net_buf_pull_u8(buf); + length--; + *codec_type = data; + *codec_info_element_len = length; + *codec_info_element = net_buf_pull_mem(buf, length); + break; + } + + __fallthrough; + case BT_AVDTP_SERVICE_MEDIA_TRANSPORT: + case BT_AVDTP_SERVICE_REPORTING: + case BT_AVDTP_SERVICE_MEDIA_RECOVERY: + case BT_AVDTP_SERVICE_CONTENT_PROTECTION: + case BT_AVDTP_SERVICE_HEADER_COMPRESSION: + case BT_AVDTP_SERVICE_MULTIPLEXING: + default: + net_buf_pull_mem(buf, length); + break; + } + } + + return (*codec_info_element != NULL) ? 0 : -EINVAL; +} + +static int avdtp_process_configure_command( + struct bt_avdtp *session, uint8_t cmd, + struct bt_avdtp_set_configuration_params *param) +{ + struct net_buf *buf; + struct bt_avdtp_generic_service_cap *cap; + struct bt_avdtp_media_codec_capabilities *media_cap; + + LOG_DBG(""); + if (!param || !session) + { + LOG_DBG("Error: Callback/Session not valid"); + return -EINVAL; + } + + buf = avdtp_create_pdu(BT_AVDTP_CMD, cmd, avdtp_get_tid(session)); + if (!buf) + { + LOG_ERR("Error: No Buff available"); + return -ENOMEM; + } + + /* Body of the message */ + + /* ACP Stream Endpoint ID */ + + DEBUGASSERT(net_buf_tailroom(buf) >= sizeof(uint8_t)); + net_buf_add_u8(buf, AVDTP_SEID_PREP(param->acp_stream_ep_id)); + + if (cmd == BT_AVDTP_SET_CONFIGURATION) + { + if (net_buf_tailroom(buf) < sizeof(*cap) + sizeof(uint8_t)) + { + goto unref_and_return; + } + + /* INT Stream Endpoint ID */ + + net_buf_add_u8(buf, AVDTP_SEID_PREP(param->int_stream_endpoint_id)); + + /* Service Category: Media Transport */ + + cap = net_buf_add(buf, sizeof(*cap)); + cap->service_category = BT_AVDTP_SERVICE_MEDIA_TRANSPORT; + cap->losc = 0; + } + + if (net_buf_tailroom(buf) < + param->codec_specific_ie_len + sizeof(*cap) + sizeof(*media_cap)) + { + goto unref_and_return; + } + + /* Service Category: Media Codec */ + + cap = net_buf_add(buf, sizeof(*cap)); + cap->service_category = BT_AVDTP_SERVICE_MEDIA_CODEC; + cap->losc = param->codec_specific_ie_len + sizeof(*media_cap); + + media_cap = net_buf_add(buf, sizeof(*media_cap)); + media_cap->media_type = AVDTP_SEP_MEDIA_TYPE_PREP(param->media_type); + media_cap->media_code_type = param->media_codec_type; + + /* Codec Info Element */ + + net_buf_add_mem(buf, param->codec_specific_ie, + param->codec_specific_ie_len); + + if (cmd == BT_AVDTP_SET_CONFIGURATION && param->delay_report) + { + if (net_buf_tailroom(buf) < sizeof(*cap)) + { + goto unref_and_return; + } + + /* Service Category: Delay Report */ + + cap = net_buf_add(buf, sizeof(*cap)); + cap->service_category = BT_AVDTP_SERVICE_DELAY_REPORTING; + cap->losc = 0; + } + + return avdtp_send_cmd(session, buf, ¶m->req); + +unref_and_return: + net_buf_unref(buf); + return -ENOMEM; +} + +int bt_avdtp_set_configuration(struct bt_avdtp *session, + struct bt_avdtp_set_configuration_params *param) +{ + if (!param || !session || !param->sep) + { + LOG_DBG("Error: parameters not valid"); + return -EINVAL; + } + + if (param->sep->state != AVDTP_IDLE) + { + return -EINVAL; + } + + return avdtp_process_configure_command(session, BT_AVDTP_SET_CONFIGURATION, + param); +} + +int bt_avdtp_reconfigure(struct bt_avdtp *session, + struct bt_avdtp_set_configuration_params *param) +{ + if (!param || !session || !param->sep) + { + LOG_DBG("Error: parameters not valid"); + return -EINVAL; + } + + if (param->sep->state != AVDTP_OPEN) + { + return -EINVAL; + } + + return avdtp_process_configure_command(session, BT_AVDTP_RECONFIGURE, + param); +} + +static int bt_avdtp_ctrl( + struct bt_avdtp *session, struct bt_avdtp_ctrl_params *param, uint8_t ctrl, + uint8_t check_state) +{ + struct net_buf *buf; + + LOG_DBG(""); + if (!param || !session || !param->sep) + { + LOG_DBG("Error: parameters not valid"); + return -EINVAL; + } + + if (!(param->sep->state & check_state)) + { + return -EINVAL; + } + + buf = avdtp_create_pdu(BT_AVDTP_CMD, ctrl, avdtp_get_tid(session)); + if (!buf) + { + LOG_ERR("Error: No Buff available"); + return -ENOMEM; + } + + /* Body of the message */ + + /* ACP Stream Endpoint ID */ + + DEBUGASSERT(net_buf_tailroom(buf) >= sizeof(uint8_t)); + net_buf_add_u8(buf, AVDTP_SEID_PREP(param->acp_stream_ep_id)); + + return avdtp_send_cmd(session, buf, ¶m->req); +} + +int bt_avdtp_open(struct bt_avdtp *session, + struct bt_avdtp_ctrl_params *param) +{ + return bt_avdtp_ctrl(session, param, BT_AVDTP_OPEN, AVDTP_CONFIGURED); +} + +int bt_avdtp_close(struct bt_avdtp *session, + struct bt_avdtp_ctrl_params *param) +{ + return bt_avdtp_ctrl(session, param, BT_AVDTP_CLOSE, + AVDTP_OPEN | AVDTP_STREAMING); +} + +int bt_avdtp_start(struct bt_avdtp *session, + struct bt_avdtp_ctrl_params *param) +{ + int err; + + err = bt_avdtp_ctrl(session, param, BT_AVDTP_START, AVDTP_OPEN); + if (!err && param->sep->sep_info.tsep == BT_AVDTP_SINK) + { + bt_avdtp_set_state_lock(param->sep, AVDTP_STREAMING); + } + + return err; +} + +int bt_avdtp_suspend(struct bt_avdtp *session, + struct bt_avdtp_ctrl_params *param) +{ + return bt_avdtp_ctrl(session, param, BT_AVDTP_SUSPEND, AVDTP_STREAMING); +} + +int bt_avdtp_abort(struct bt_avdtp *session, + struct bt_avdtp_ctrl_params *param) +{ + /* from ACP and INT point of views, when the state is CONFIGURED, OPEN, + * STREAMING or CLOSING. However, AVDTP_ABORT_CMD can be sent or received + * in IDLE state. + */ + + return bt_avdtp_ctrl(session, param, BT_AVDTP_ABORT, + AVDTP_IDLE | AVDTP_CONFIGURED | AVDTP_OPENING | + AVDTP_OPEN | + AVDTP_STREAMING | AVDTP_CLOSING); +} + +int bt_avdtp_get_configuration(struct bt_avdtp *session, + struct bt_avdtp_ctrl_params *param) +{ + return bt_avdtp_ctrl(session, param, BT_AVDTP_GET_CONFIGURATION, + AVDTP_CONFIGURED | AVDTP_OPENING | AVDTP_OPEN | + AVDTP_STREAMING); +} + +int bt_avdtp_delay_report(struct bt_avdtp *session, + struct bt_avdtp_delay_report_params *param) +{ + struct net_buf *buf; + + if (param == NULL || session == NULL || param->sep == NULL) + { + LOG_DBG("Error: parameters not valid"); + return -EINVAL; + } + + if (param->sep->sep_info.tsep != BT_AVDTP_SINK) + { + LOG_ERR("Delay report is only supported for sink endpoint"); + return -ENOTSUP; + } + + if (!(param->sep->state & (AVDTP_CONFIGURED | AVDTP_OPENING | AVDTP_OPEN | + AVDTP_STREAMING))) + { + return -EINVAL; + } + + buf = avdtp_create_pdu(BT_AVDTP_CMD, BT_AVDTP_DELAYREPORT, + avdtp_get_tid(session)); + if (!buf) + { + LOG_ERR("Error: No Buff available"); + return -ENOMEM; + } + + DEBUGASSERT(net_buf_tailroom(buf) >= (sizeof(uint8_t) + sizeof(uint16_t))); + net_buf_add_u8(buf, AVDTP_SEID_PREP(param->acp_stream_ep_id)); + net_buf_add_be16(buf, param->delay_report); + + return avdtp_send_cmd(session, buf, ¶m->req); +} + +int bt_avdtp_send_media_data(struct bt_avdtp_sep *sep, struct net_buf *buf) +{ + int err; + + if (sep->state != AVDTP_STREAMING || sep->sep_info.tsep != BT_AVDTP_SOURCE) + { + return -EIO; + } + + err = bt_l2cap_chan_send(&sep->chan.chan, buf); + if (err < 0) + { + LOG_ERR("Error:L2CAP send fail - err = %d", err); + return err; + } + + return err; +} + +uint32_t bt_avdtp_get_media_mtu(struct bt_avdtp_sep *sep) +{ + return sep->chan.tx.mtu; +} diff --git a/libs/zblue/host/classic/avdtp_internal.h b/libs/zblue/host/classic/avdtp_internal.h new file mode 100644 index 0000000000000..0c197aa2db176 --- /dev/null +++ b/libs/zblue/host/classic/avdtp_internal.h @@ -0,0 +1,424 @@ +/**************************************************************************** + * libs/zblue/host/classic/avdtp_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* avdtp_internal.h - avdtp handling + + * Copyright (c) 2015-2016 Intel Corporation Copyright 2021,2024-2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/* @brief A2DP ROLE's */ +#define A2DP_SRC_ROLE 0x00 +#define A2DP_SNK_ROLE 0x01 + +/* @brief AVDTP Role */ +#define BT_AVDTP_INT 0x00 +#define BT_AVDTP_ACP 0x01 + +#define BT_L2CAP_PSM_AVDTP 0x0019 + +/* AVDTP SIGNAL HEADER - Packet Type */ +#define BT_AVDTP_PACKET_TYPE_SINGLE 0x00 +#define BT_AVDTP_PACKET_TYPE_START 0x01 +#define BT_AVDTP_PACKET_TYPE_CONTINUE 0x02 +#define BT_AVDTP_PACKET_TYPE_END 0x03 + +/* AVDTP SIGNAL HEADER - MESSAGE TYPE */ +#define BT_AVDTP_CMD 0x00 +#define BT_AVDTP_GEN_REJECT 0x01 +#define BT_AVDTP_ACCEPT 0x02 +#define BT_AVDTP_REJECT 0x03 + +/* @brief AVDTP SIGNAL HEADER - Signal Identifier */ +#define BT_AVDTP_DISCOVER 0x01 +#define BT_AVDTP_GET_CAPABILITIES 0x02 +#define BT_AVDTP_SET_CONFIGURATION 0x03 +#define BT_AVDTP_GET_CONFIGURATION 0x04 +#define BT_AVDTP_RECONFIGURE 0x05 +#define BT_AVDTP_OPEN 0x06 +#define BT_AVDTP_START 0x07 +#define BT_AVDTP_CLOSE 0x08 +#define BT_AVDTP_SUSPEND 0x09 +#define BT_AVDTP_ABORT 0x0a +#define BT_AVDTP_SECURITY_CONTROL 0x0b +#define BT_AVDTP_GET_ALL_CAPABILITIES 0x0c +#define BT_AVDTP_DELAYREPORT 0x0d + +/* @brief AVDTP STREAM STATE */ +#define BT_AVDTP_STREAM_STATE_IDLE 0x01 +#define BT_AVDTP_STREAM_STATE_CONFIGURED 0x02 +#define BT_AVDTP_STREAM_STATE_OPEN 0x03 +#define BT_AVDTP_STREAM_STATE_STREAMING 0x04 +#define BT_AVDTP_STREAM_STATE_CLOSING 0x05 + +/* @brief AVDTP Media TYPE */ +#define BT_AVDTP_SERVICE_CAT_MEDIA_TRANSPORT 0x01 +#define BT_AVDTP_SERVICE_CAT_REPORTING 0x02 +#define BT_AVDTP_SERVICE_CAT_RECOVERY 0x03 +#define BT_AVDTP_SERVICE_CAT_CONTENT_PROTECTION 0x04 +#define BT_AVDTP_SERVICE_CAT_HDR_COMPRESSION 0x05 +#define BT_AVDTP_SERVICE_CAT_MULTIPLEXING 0x06 +#define BT_AVDTP_SERVICE_CAT_MEDIA_CODEC 0x07 +#define BT_AVDTP_SERVICE_CAT_DELAYREPORTING 0x08 + +#define BT_AVDTP_MIN_SEID 0x01 +#define BT_AVDTP_MAX_SEID 0x3E + +#define BT_AVDTP_RTP_VERSION 2 + +#define AVDTP_SEP_MEDIA_TYPE_MASK GENMASK(7, 4) +#define AVDTP_SEP_MEDIA_TYPE_PREP(val) FIELD_PREP(AVDTP_SEP_MEDIA_TYPE_MASK, val) + +struct bt_avdtp; +struct bt_avdtp_req; +struct bt_avdtp_sep_info; + +/** @brief AVDTP SEID Information AVDTP_SPEC V13 Table 8.8 */ + +struct bt_avdtp_sep_data +{ +#ifndef CONFIG_ENDIAN_BIG + uint8_t rfa0 : 1; + uint8_t inuse : 1; + uint8_t id : 6; + uint8_t rfa1 : 3; + uint8_t tsep : 1; + uint8_t media_type : 4; +#else + uint8_t id : 6; + uint8_t inuse : 1; + uint8_t rfa0 : 1; + uint8_t media_type : 4; + uint8_t tsep : 1; + uint8_t rfa1 : 3; +#endif +} __packed; + +typedef int (*bt_avdtp_func_t)(struct bt_avdtp_req *req, + struct net_buf *buf); + +struct bt_avdtp_req +{ + uint8_t sig; + uint8_t tid; + + /* set it as 0 (BT_AVDTP_SUCCESS) before giving the req to avdtp */ + + uint8_t status; + bt_avdtp_func_t func; +}; + +struct bt_avdtp_single_sig_hdr +{ + uint8_t hdr; + uint8_t signal_id; +} __packed; + +struct bt_avdtp_start_sig_hdr +{ + uint8_t hdr; + uint8_t num_of_signal_pkts; + uint8_t signal_id; +} __packed; + +struct bt_avdtp_continue_end_sig_hdr +{ + uint8_t hdr; +} __packed; + +struct bt_avdtp_media_hdr +{ +#ifndef CONFIG_ENDIAN_BIG + uint8_t CSRC_count : 4; + uint8_t header_extension : 1; + uint8_t padding : 1; + uint8_t RTP_version : 2; + uint8_t playload_type : 7; + uint8_t marker : 1; +#else + uint8_t RTP_version : 2; + uint8_t padding : 1; + uint8_t header_extension : 1; + uint8_t CSRC_count : 4; + uint8_t marker : 1; + uint8_t playload_type : 7; +#endif + uint16_t sequence_number; + uint32_t time_stamp; + uint32_t synchronization_source; +} __packed; + +struct bt_avdtp_discover_params +{ + struct bt_avdtp_req req; +}; + +struct bt_avdtp_get_capabilities_params +{ + struct bt_avdtp_req req; + uint8_t stream_endpoint_id; + bool get_all_caps; +}; + +struct bt_avdtp_set_configuration_params +{ + struct bt_avdtp_req req; + struct bt_avdtp_sep *sep; + uint8_t acp_stream_ep_id; + uint8_t int_stream_endpoint_id; + uint8_t media_type; + uint8_t media_codec_type; + uint8_t codec_specific_ie_len; + uint8_t *codec_specific_ie; + bool delay_report; +}; + +/* avdtp_open, avdtp_close, avdtp_start, avdtp_suspend, + * avdtp_get_configuration + */ + +struct bt_avdtp_ctrl_params +{ + struct bt_avdtp_req req; + struct bt_avdtp_sep *sep; + uint8_t acp_stream_ep_id; +}; + +struct bt_avdtp_delay_report_params +{ + struct bt_avdtp_req req; + struct bt_avdtp_sep *sep; + uint8_t acp_stream_ep_id; + uint16_t delay_report; +}; + +struct bt_avdtp_generic_service_cap +{ + uint8_t service_category; + uint8_t losc; +} __packed; + +/* avdtp service capabilities */ + +struct bt_avdtp_recovery_capabilities +{ + uint8_t recovery_type; + uint8_t MRWS; + uint8_t MNMP; +} __packed; + +struct bt_avdtp_media_codec_capabilities +{ + uint8_t media_type; + uint8_t media_code_type; + uint8_t media_codec_spec_info[]; +} __packed; + +struct bt_avdtp_content_protection_capabilities +{ + uint8_t cp_type_lsb; + uint8_t cp_type_msb; + uint8_t cp_type_spec_value[]; +} __packed; + +struct bt_avdtp_header_compression_capabilities +{ +#ifndef CONFIG_ENDIAN_BIG + uint8_t reserved : 5; + uint8_t recovery : 1; + uint8_t media : 1; + uint8_t backch : 1; +#else + uint8_t backch : 1; + uint8_t media : 1; + uint8_t recovery : 1; + uint8_t reserved : 5; +#endif /* CONFIG_ENDIAN_BIG */ +} __packed; + +struct bt_avdtp_multiplexing_capabilities +{ + uint8_t frag; + uint8_t tsid_media; + uint8_t tcid_media; + uint8_t tsid_reporting; + uint8_t tcid_reporting; + uint8_t tsid_recovery; + uint8_t tcid_recovery; +} __packed; + +struct bt_avdtp_ops_cb +{ + void (*connected)(struct bt_avdtp *session); + + void (*disconnected)(struct bt_avdtp *session); + + struct net_buf *(*alloc_buf)(struct bt_avdtp *session); + + int (*discovery_ind)(struct bt_avdtp *session, uint8_t *errcode); + + int (*get_capabilities_ind)(struct bt_avdtp *session, + struct bt_avdtp_sep *sep, + struct net_buf *rsp_buf, bool get_all_caps, + uint8_t *errcode); + + int (*set_configuration_ind)(struct bt_avdtp *session, + struct bt_avdtp_sep *sep, uint8_t int_seid, + struct net_buf *buf, uint8_t *errcode); + + int (*get_configuration_ind)(struct bt_avdtp *session, + struct bt_avdtp_sep *sep, + struct net_buf *rsp_buf, uint8_t *errcode); + + int (*re_configuration_ind)(struct bt_avdtp *session, + struct bt_avdtp_sep *sep, + struct net_buf *buf, uint8_t *errcode); + + int (*open_ind)(struct bt_avdtp *session, struct bt_avdtp_sep *sep, + uint8_t *errcode); + + int (*close_ind)(struct bt_avdtp *session, struct bt_avdtp_sep *sep, + uint8_t *errcode); + + int (*start_ind)(struct bt_avdtp *session, struct bt_avdtp_sep *sep, + uint8_t *errcode); + + int (*suspend_ind)(struct bt_avdtp *session, struct bt_avdtp_sep *sep, + uint8_t *errcode); + + int (*abort_ind)(struct bt_avdtp *session, struct bt_avdtp_sep *sep, + uint8_t *errcode); + + int (*delay_report_ind)(struct bt_avdtp *session, + struct bt_avdtp_sep *sep, struct net_buf *buf, + uint8_t *errcode); +}; + +/** @brief Global AVDTP session structure. */ + +struct bt_avdtp +{ + struct bt_l2cap_br_chan br_chan; + struct bt_avdtp_req *req; + const struct bt_avdtp_ops_cb *ops; + struct bt_avdtp_sep *current_sep; + struct k_work_delayable timeout_work; + struct k_work _release_work; + + /* semaphore for lock/unlock */ + + sem_t sem_lock; + struct net_buf *reasm_buf; + uint8_t num_of_signal_pkts; + uint8_t tid_sent; +}; + +struct bt_avdtp_event_cb +{ + int (*accept)(struct bt_conn *conn, struct bt_avdtp **session); +}; + +/* Application register with AVDTP layer */ + +int bt_avdtp_register(struct bt_avdtp_event_cb *cb); + +/* AVDTP connect */ + +int bt_avdtp_connect(struct bt_conn *conn, struct bt_avdtp *session); + +/* AVDTP disconnect */ + +int bt_avdtp_disconnect(struct bt_avdtp *session); + +/* AVDTP SEP register function */ + +int bt_avdtp_register_sep(uint8_t media_type, + uint8_t sep_type, struct bt_avdtp_sep *sep); + +/* AVDTP Discover Request */ + +int bt_avdtp_discover(struct bt_avdtp *session, + struct bt_avdtp_discover_params *param); + +/* Parse the sep of discovered result */ + +int bt_avdtp_parse_sep(struct net_buf *buf, + struct bt_avdtp_sep_info *sep_info); + +/* AVDTP Get Capabilities */ + +int bt_avdtp_get_capabilities(struct bt_avdtp *session, + struct bt_avdtp_get_capabilities_params + *param); + +/* Parse the codec type of capabilities */ + +int bt_avdtp_parse_capability_codec(struct net_buf *buf, uint8_t *codec_type, + uint8_t **codec_info_element, + uint16_t *codec_info_element_len, + bool *delay_report); + +/* AVDTP Set Configuration */ + +int bt_avdtp_set_configuration( + struct bt_avdtp *session, + struct bt_avdtp_set_configuration_params *param); + +/* AVDTP reconfigure */ + +int bt_avdtp_reconfigure(struct bt_avdtp *session, + struct bt_avdtp_set_configuration_params *param); + +/* AVDTP OPEN */ + +int bt_avdtp_open(struct bt_avdtp *session, + struct bt_avdtp_ctrl_params *param); + +/* AVDTP CLOSE */ + +int bt_avdtp_close(struct bt_avdtp *session, + struct bt_avdtp_ctrl_params *param); + +/* AVDTP START */ + +int bt_avdtp_start(struct bt_avdtp *session, + struct bt_avdtp_ctrl_params *param); + +/* AVDTP SUSPEND */ + +int bt_avdtp_suspend(struct bt_avdtp *session, + struct bt_avdtp_ctrl_params *param); + +/* AVDTP ABORT */ + +int bt_avdtp_abort(struct bt_avdtp *session, + struct bt_avdtp_ctrl_params *param); + +/* AVDTP Get Configuration */ + +int bt_avdtp_get_configuration(struct bt_avdtp *session, + struct bt_avdtp_ctrl_params *param); + +/* AVDTP send delay report */ + +int bt_avdtp_delay_report(struct bt_avdtp *session, + struct bt_avdtp_delay_report_params *param); + +/* AVDTP send data */ + +int bt_avdtp_send_media_data(struct bt_avdtp_sep *sep, struct net_buf *buf); + +/* get media l2cap connection MTU */ + +uint32_t bt_avdtp_get_media_mtu(struct bt_avdtp_sep *sep); diff --git a/libs/zblue/host/classic/avrcp.c b/libs/zblue/host/classic/avrcp.c new file mode 100644 index 0000000000000..9c7940b91b868 --- /dev/null +++ b/libs/zblue/host/classic/avrcp.c @@ -0,0 +1,6248 @@ +/**************************************************************************** + * libs/zblue/host/classic/avrcp.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Audio Video Remote Control Profile + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * Copyright (C) 2024 Xiaomi Corporation Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include "avctp_internal.h" +#include "avrcp_internal.h" + +#define LOG_LEVEL CONFIG_BT_AVRCP_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_avrcp); + +struct bt_avrcp +{ + struct bt_avctp session; + + /* ACL connection handle */ + + struct bt_conn *acl_conn; + + struct bt_avctp browsing_session; +}; + +struct bt_avrcp_ct +{ + struct bt_avrcp *avrcp; + + struct net_buf *reassembly_buf; /**< Buffer for reassembling fragments */ + + struct bt_avrcp_ct_notify_registration ct_notify + [BT_AVRCP_EVT_VOLUME_CHANGED + 1]; +}; + +struct bt_avrcp_tg +{ + struct bt_avrcp *avrcp; + + /* AVRCP vendor dependent response TX pending */ + + struct list_node vd_rsp_tx_pending; + + /* Critical locker */ + + sem_t lock; + + /* AVRCP vendor dependent response TX work */ + + struct k_work_delayable vd_rsp_tx_work; + + struct bt_avrcp_tg_notify_state tg_notify[BT_AVRCP_EVT_VOLUME_CHANGED + 1]; +}; + +struct avrcp_handler +{ + bt_avrcp_opcode_t opcode; + void (*func)(struct bt_avrcp *avrcp, uint8_t tid, struct net_buf *buf); +}; + +struct avrcp_pdu_vendor_handler +{ + bt_avrcp_pdu_id_t pdu_id; + uint8_t min_len; + bt_avrcp_ctype_t cmd_type; + int (*func)(struct bt_avrcp *avrcp, + uint8_t tid, + uint8_t result, + struct net_buf *buf); +}; + +NET_BUF_POOL_FIXED_DEFINE(avrcp_vd_rx_pool, CONFIG_BT_MAX_CONN, + CONFIG_BT_AVRCP_VD_RX_SIZE, + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + +struct avrcp_pdu_handler +{ + bt_avrcp_pdu_id_t pdu_id; + uint8_t min_len; + int (*func)(struct bt_avrcp *avrcp, uint8_t tid, struct net_buf *buf); +}; + +#define AVRCP_AVCTP(_avctp) CONTAINER_OF(_avctp, struct bt_avrcp, session) +#define AVRCP_BROW_AVCTP(_avctp) CONTAINER_OF(_avctp, struct bt_avrcp, browsing_session) + +NET_BUF_POOL_FIXED_DEFINE(avctp_ctrl_rx_pool, CONFIG_BT_MAX_CONN, + BT_AVRCP_FRAGMENT_SIZE, + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + +NET_BUF_POOL_FIXED_DEFINE(avctp_ctrl_tx_pool, CONFIG_BT_MAX_CONN, + BT_L2CAP_BUF_SIZE(CONFIG_BT_L2CAP_TX_MTU), + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + +NET_BUF_POOL_DEFINE(avctp_browsing_rx_pool, BT_BUF_ACL_RX_COUNT, + CONFIG_BT_AVRCP_BROWSING_L2CAP_MTU, + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); +/* This macros returns true if the CT/TG has been initialized, which + * typically happens after the avrcp callback have been registered. Use these + * macros to determine whether the CT/TG role is supported. + */ +#define IS_CT_ROLE_SUPPORTED() (avrcp_ct_cb != NULL) +#define IS_TG_ROLE_SUPPORTED() (avrcp_tg_cb != NULL) + +#define AVRCP_TX_RETRY_DELAY_MS (100) + +#define AVRCP_STATUS_IS_REJECTED(status) \ + (((uint8_t)(status) <= BT_AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED) && \ + ((uint8_t)(status) != BT_AVRCP_STATUS_OPERATION_COMPLETED)) + +static const struct bt_avrcp_ct_cb *avrcp_ct_cb; +static const struct bt_avrcp_tg_cb *avrcp_tg_cb; +static struct bt_avrcp avrcp_connection[CONFIG_BT_MAX_CONN]; +static struct bt_avrcp_ct bt_avrcp_ct_pool[CONFIG_BT_MAX_CONN]; +static struct bt_avrcp_tg bt_avrcp_tg_pool[CONFIG_BT_MAX_CONN]; + +#if defined(CONFIG_BT_AVRCP_TG_COVER_ART) +static uint16_t bt_avrcp_tg_cover_art_psm; +#endif /* CONFIG_BT_AVRCP_TG_COVER_ART */ + +static struct bt_avctp_server avctp_server; +#if defined(CONFIG_BT_AVRCP_BROWSING) +static struct bt_avctp_server avctp_browsing_server; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +#endif /* CONFIG_BT_AVRCP_BROWSING */ + +static void avrcp_tx_buf_destroy(struct net_buf *buf) +{ + for (int i = 0; i < CONFIG_BT_MAX_CONN; i++) + { + struct bt_avrcp_tg *tg = &bt_avrcp_tg_pool[i]; + + if ((tg->avrcp != NULL) && (tg->avrcp->acl_conn != NULL)) + { + (void)k_work_reschedule(&tg->vd_rsp_tx_work, 0); + } + } + + net_buf_destroy(buf); +} + +NET_BUF_POOL_FIXED_DEFINE(avrcp_vd_tx_pool, CONFIG_BT_MAX_CONN, + BT_L2CAP_BUF_SIZE(BT_AVRCP_FRAGMENT_SIZE) + + sizeof(struct bt_avctp_header_start), + CONFIG_BT_CONN_TX_USER_DATA_SIZE, + avrcp_tx_buf_destroy); + +#if defined(CONFIG_BT_AVRCP_TARGET) +static struct bt_sdp_attribute avrcp_tg_attrs[] = +{ + BT_SDP_NEW_SERVICE, + BT_SDP_LIST(BT_SDP_ATTR_SVCLASS_ID_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_AV_REMOTE_TARGET_SVCLASS) + }, + + ) + ), + + BT_SDP_LIST(BT_SDP_ATTR_PROTO_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 16), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) + }, + + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_UUID_AVCTP_VAL) + }, + + ) + }, + + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_UUID_AVCTP_VAL) + }, + + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(AVCTP_VER_1_4) + }, + + ) + }, + + ) + ), + + /* Browsing channel */ +#if defined(CONFIG_BT_AVRCP_BROWSING) || defined(CONFIG_BT_AVRCP_TG_COVER_ART) + BT_SDP_LIST(BT_SDP_ATTR_ADD_PROTO_DESC_LIST, +#if defined(CONFIG_BT_AVRCP_BROWSING) && defined(CONFIG_BT_AVRCP_TG_COVER_ART) + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 33), +#elif defined(CONFIG_BT_AVRCP_BROWSING) + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 18), +#elif defined(CONFIG_BT_AVRCP_TG_COVER_ART) + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 15), +#else +#error Invalid configuration +#endif /* CONFIG_BT_AVRCP_BROWSING && CONFIG_BT_AVRCP_TG_COVER_ART */ + BT_SDP_DATA_ELEM_LIST( +#if defined(CONFIG_BT_AVRCP_BROWSING) + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 16), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) + }, + + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_L2CAP_PSM_AVRCP_BROWSING) + } + + ) + }, + + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_UUID_AVCTP_VAL) + }, + + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(AVCTP_VER_1_4) + } + + ) + } + + ) + } +#endif /* CONFIG_BT_AVRCP_BROWSING */ +#if defined(CONFIG_BT_AVRCP_BROWSING) && defined(CONFIG_BT_AVRCP_TG_COVER_ART) + + , +#endif /* CONFIG_BT_AVRCP_BROWSING && CONFIG_BT_AVRCP_TG_COVER_ART */ +#if defined(CONFIG_BT_AVRCP_TG_COVER_ART) + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 13), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) + }, + + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + &bt_avrcp_tg_cover_art_psm + } + + ) + }, + + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_UUID_OBEX_VAL) + } + + ) + } + + ) + } +#endif /* CONFIG_BT_AVRCP_TG_COVER_ART */ + + )), +#endif /* CONFIG_BT_AVRCP_BROWSING || CONFIG_BT_AVRCP_TG_COVER_ART*/ + BT_SDP_LIST(BT_SDP_ATTR_PROFILE_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 8), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_AV_REMOTE_SVCLASS) + }, + + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(AVRCP_VER_1_6) + }, + + ) + }, + + ) + ), + + BT_SDP_SUPPORTED_FEATURES(AVRCP_TG_CAT_1 | AVRCP_TG_CAT_2 | + AVRCP_TG_BROWSING_ENABLE | AVRCP_TG_COVER_ART_ENABLE), + + /* O: Provider Name not presented */ + + BT_SDP_SERVICE_NAME("AVRCP Target"), +}; + +static struct bt_sdp_record avrcp_tg_rec = BT_SDP_RECORD(avrcp_tg_attrs); +#endif /* CONFIG_BT_AVRCP_TARGET */ + +#if defined(CONFIG_BT_AVRCP_CONTROLLER) +static struct bt_sdp_attribute avrcp_ct_attrs[] = +{ + BT_SDP_NEW_SERVICE, + BT_SDP_LIST(BT_SDP_ATTR_SVCLASS_ID_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_AV_REMOTE_SVCLASS) + }, + + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_AV_REMOTE_CONTROLLER_SVCLASS) + }, + + ) + ), + + BT_SDP_LIST(BT_SDP_ATTR_PROTO_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 16), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) + }, + + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_UUID_AVCTP_VAL) + }, + + ) + }, + + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_UUID_AVCTP_VAL) + }, + + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(AVCTP_VER_1_4) + }, + + ) + }, + + ) + ), + + /* Browsing channel */ +#if defined(CONFIG_BT_AVRCP_BROWSING) + BT_SDP_LIST(BT_SDP_ATTR_ADD_PROTO_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 18), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 16), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) + }, + + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_L2CAP_PSM_AVRCP_BROWSING) + } + + ) + }, + + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_UUID_AVCTP_VAL) + }, + + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(AVCTP_VER_1_4) + } + + ) + } + + ) + } + + ) + ), +#endif /* CONFIG_BT_AVRCP_BROWSING */ + /* C2: Cover Art not supported */ + BT_SDP_LIST(BT_SDP_ATTR_PROFILE_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 8), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_AV_REMOTE_SVCLASS) + }, + + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(AVRCP_VER_1_6) + }, + + ) + }, + + ) + ), + + BT_SDP_SUPPORTED_FEATURES(AVRCP_CT_CAT_1 | AVRCP_CT_CAT_2 | + AVRCP_CT_BROWSING_ENABLE | AVRCP_CT_COVER_ART_FEAT_ENABLE), + BT_SDP_SERVICE_NAME("AVRCP Controller"), +}; + +static struct bt_sdp_record avrcp_ct_rec = BT_SDP_RECORD(avrcp_ct_attrs); +#endif /* CONFIG_BT_AVRCP_CONTROLLER */ + +static void avrcp_tg_lock(struct bt_avrcp_tg *tg) +{ + nxsem_wait_uninterruptible(&tg->lock); +} + +static void avrcp_tg_unlock(struct bt_avrcp_tg *tg) +{ + nxsem_post(&tg->lock); +} + +static struct bt_avrcp *avrcp_get_connection(struct bt_conn *conn) +{ + size_t index; + + if (!conn) + { + LOG_ERR("Invalid parameter"); + return NULL; + } + + index = (size_t)bt_conn_index(conn); + DEBUGASSERT(index < ARRAY_SIZE(avrcp_connection)); + + return &avrcp_connection[index]; +} + +static inline struct bt_avrcp_ct *get_avrcp_ct(struct bt_avrcp *avrcp) +{ + size_t index; + + if (avrcp == NULL) + { + LOG_ERR("Invalid parameter"); + return NULL; + } + + index = (size_t)bt_conn_index(avrcp->acl_conn); + DEBUGASSERT(index < ARRAY_SIZE(avrcp_connection)); + + return &bt_avrcp_ct_pool[index]; +} + +static inline struct bt_avrcp_tg *get_avrcp_tg(struct bt_avrcp *avrcp) +{ + size_t index; + + if (avrcp == NULL) + { + LOG_ERR("Invalid parameter"); + return NULL; + } + + index = (size_t)bt_conn_index(avrcp->acl_conn); + DEBUGASSERT(index < ARRAY_SIZE(avrcp_connection)); + + return &bt_avrcp_tg_pool[index]; +} + +static int bt_avrcp_rsp_to_status(uint8_t rsp, + struct net_buf *buf, uint8_t *status) +{ + if ((status == NULL) || (buf == NULL)) + { + return -EINVAL; + } + + switch (rsp) + { + case BT_AVRCP_RSP_STABLE: + *status = BT_AVRCP_STATUS_SUCCESS; + break; + case BT_AVRCP_RSP_IN_TRANSITION: + *status = BT_AVRCP_STATUS_IN_TRANSITION; + break; + case BT_AVRCP_RSP_ACCEPTED: + *status = BT_AVRCP_STATUS_SUCCESS; + break; + case BT_AVRCP_RSP_NOT_IMPLEMENTED: + *status = BT_AVRCP_STATUS_NOT_IMPLEMENTED; + break; + case BT_AVRCP_RSP_REJECTED: + if (buf->len < sizeof(*status)) + { + LOG_ERR("AVRCP rejected response missing error code"); + *status = BT_AVRCP_STATUS_INTERNAL_ERROR; + return -EINVAL; + } + + *status = net_buf_pull_u8(buf); + break; + default: + *status = BT_AVRCP_STATUS_INTERNAL_ERROR; + break; + } + + return 0; +} + +/* The AVCTP L2CAP channel established */ + +static void avrcp_connected(struct bt_avctp *session) +{ + struct bt_avrcp *avrcp = AVRCP_AVCTP(session); + + if ((avrcp_ct_cb != NULL) && (avrcp_ct_cb->connected != NULL)) + { + avrcp_ct_cb->connected(session->br_chan.chan.conn, + get_avrcp_ct(avrcp)); + } + + if ((avrcp_tg_cb != NULL) && (avrcp_tg_cb->connected != NULL)) + { + avrcp_tg_cb->connected(session->br_chan.chan.conn, + get_avrcp_tg(avrcp)); + } +} + +static void cleanup_fragmentation_context(struct bt_avrcp_ct *ct) +{ + if (ct == NULL) + { + return; + } + + if (ct->reassembly_buf != NULL) + { + net_buf_drop(&ct->reassembly_buf); + } +} + +/* The AVCTP L2CAP channel released */ + +static void avrcp_disconnected(struct bt_avctp *session) +{ + struct bt_avrcp *avrcp = AVRCP_AVCTP(session); + struct bt_avrcp_ct *ct = get_avrcp_ct(avrcp); + struct bt_avrcp_tg *tg = get_avrcp_tg(avrcp); + struct list_node *node; + + if ((avrcp_ct_cb != NULL) && (avrcp_ct_cb->disconnected != NULL)) + { + avrcp_ct_cb->disconnected(ct); + } + + if ((avrcp_tg_cb != NULL) && (avrcp_tg_cb->disconnected != NULL)) + { + avrcp_tg_cb->disconnected(tg); + } + + /* Cancel the vendor dependent response TX machine and drop any + * queued or partially sent responses. The TX state, including the + * TX_ONGOING flag, lives in the buffer user data, so releasing the buffers + * also resets the TX state. Without this, a disconnection in the middle of + * a fragmented response leaks the buffer and leaves the TX machine stalled + * on the stale list head when the object is reused for a new connection. + */ + + k_work_cancel_delayable(&tg->vd_rsp_tx_work); + + avrcp_tg_lock(tg); + node = list_remove_head(&tg->vd_rsp_tx_pending); + while (node != NULL) + { + net_buf_unref(CONTAINER_OF(node, struct net_buf, node)); + node = list_remove_head(&tg->vd_rsp_tx_pending); + } + + avrcp_tg_unlock(tg); + + /* Drop any partially reassembled vendor dependent response */ + + cleanup_fragmentation_context(ct); + + memset(&ct->ct_notify, 0, sizeof(ct->ct_notify)); + memset(&tg->tg_notify, 0, sizeof(tg->tg_notify)); + + bt_conn_drop(&avrcp->acl_conn); +} + +static struct net_buf *avrcp_create_unit_pdu(struct bt_avrcp *avrcp, + uint8_t ctype_or_rsp) +{ + struct net_buf *buf; + struct bt_avrcp_frame *cmd; + + buf = bt_avctp_create_pdu(NULL); + if (!buf) + { + return NULL; + } + + cmd = net_buf_add(buf, sizeof(*cmd)); + memset(cmd, 0, sizeof(*cmd)); + BT_AVRCP_HDR_SET_CTYPE_OR_RSP(&cmd->hdr, ctype_or_rsp); + BT_AVRCP_HDR_SET_SUBUNIT_ID(&cmd->hdr, BT_AVRCP_SUBUNIT_ID_IGNORE); + BT_AVRCP_HDR_SET_SUBUNIT_TYPE(&cmd->hdr, BT_AVRCP_SUBUNIT_TYPE_UNIT); + cmd->hdr.opcode = BT_AVRCP_OPC_UNIT_INFO; + + return buf; +} + +static struct net_buf *avrcp_create_subunit_pdu(struct bt_avrcp *avrcp, + uint8_t ctype_or_rsp) +{ + struct net_buf *buf; + struct bt_avrcp_frame *cmd; + + buf = bt_avctp_create_pdu(NULL); + if (!buf) + { + return NULL; + } + + cmd = net_buf_add(buf, sizeof(*cmd)); + memset(cmd, 0, sizeof(*cmd)); + BT_AVRCP_HDR_SET_CTYPE_OR_RSP(&cmd->hdr, ctype_or_rsp); + BT_AVRCP_HDR_SET_SUBUNIT_ID(&cmd->hdr, BT_AVRCP_SUBUNIT_ID_IGNORE); + BT_AVRCP_HDR_SET_SUBUNIT_TYPE(&cmd->hdr, BT_AVRCP_SUBUNIT_TYPE_UNIT); + cmd->hdr.opcode = BT_AVRCP_OPC_SUBUNIT_INFO; + + return buf; +} + +static void avrcp_set_passthrough_header(struct bt_avrcp_header *hdr, + uint8_t ctype_or_rsp) +{ + BT_AVRCP_HDR_SET_CTYPE_OR_RSP(hdr, ctype_or_rsp); + BT_AVRCP_HDR_SET_SUBUNIT_ID(hdr, BT_AVRCP_SUBUNIT_ID_ZERO); + BT_AVRCP_HDR_SET_SUBUNIT_TYPE(hdr, BT_AVRCP_SUBUNIT_TYPE_PANEL); + hdr->opcode = BT_AVRCP_OPC_PASS_THROUGH; +} + +static struct net_buf *avrcp_create_passthrough_pdu(struct bt_avrcp *avrcp, + uint8_t ctype_or_rsp) +{ + struct net_buf *buf; + struct bt_avrcp_frame *cmd; + + buf = bt_avctp_create_pdu(NULL); + if (!buf) + { + return NULL; + } + + cmd = net_buf_add(buf, sizeof(*cmd)); + memset(cmd, 0, sizeof(*cmd)); + avrcp_set_passthrough_header(&cmd->hdr, ctype_or_rsp); + + return buf; +} + +static struct net_buf *avrcp_create_vendor_pdu(struct bt_avrcp *avrcp, + uint8_t ctype_or_rsp) +{ + struct net_buf *buf; + struct bt_avrcp_frame *cmd; + + buf = bt_avctp_create_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) + { + return NULL; + } + + cmd = net_buf_add(buf, sizeof(*cmd)); + memset(cmd, 0, sizeof(*cmd)); + BT_AVRCP_HDR_SET_CTYPE_OR_RSP(&cmd->hdr, ctype_or_rsp); + BT_AVRCP_HDR_SET_SUBUNIT_ID(&cmd->hdr, BT_AVRCP_SUBUNIT_ID_ZERO); + BT_AVRCP_HDR_SET_SUBUNIT_TYPE(&cmd->hdr, BT_AVRCP_SUBUNIT_TYPE_PANEL); + cmd->hdr.opcode = BT_AVRCP_OPC_VENDOR_DEPENDENT; + + return buf; +} + +static int avrcp_send(struct bt_avrcp *avrcp, + struct net_buf *buf, bt_avctp_cr_t cr, uint8_t tid) +{ + int err; + struct bt_avrcp_header *avrcp_hdr = (struct bt_avrcp_header *)(buf->data); + bt_avrcp_ctype_t ctype = BT_AVRCP_HDR_GET_CTYPE_OR_RSP(avrcp_hdr); + + LOG_DBG("AVRCP send cr:0x%X, tid:0x%X, ctype: 0x%X, opc:0x%02X\n", cr, tid, + ctype, + avrcp_hdr->opcode); + err = bt_avctp_send(&(avrcp->session), buf, cr, tid); + if (err < 0) + { + LOG_ERR("AVCTP send fail, err = %d", err); + return err; + } + + return 0; +} + +static struct net_buf *avrcp_create_browsing_pdu(struct bt_avrcp *avrcp) +{ + return bt_avrcp_create_pdu(NULL); +} + +static int avrcp_browsing_send( + struct bt_avrcp *avrcp, struct net_buf *buf, bt_avctp_cr_t cr, uint8_t tid) +{ + int err; + struct bt_avrcp_brow_pdu *hdr = (struct bt_avrcp_brow_pdu *)(buf->data); + + LOG_DBG("AVRCP browsing send cr:0x%X, tid:0x%X, pdu_id:0x%02X\n", cr, tid, + hdr->pdu_id); + err = bt_avctp_send(&(avrcp->browsing_session), buf, cr, tid); + if (err < 0) + { + LOG_ERR("AVCTP browsing send fail, err = %d", err); + return err; + } + + return 0; +} + +static int bt_avrcp_send_unit_info_err_rsp(struct bt_avrcp *avrcp, + uint8_t tid) +{ + struct net_buf *buf; + int err; + + buf = avrcp_create_unit_pdu(avrcp, BT_AVRCP_RSP_REJECTED); + if (!buf) + { + LOG_WRN("Insufficient buffer"); + return -ENOMEM; + } + + err = avrcp_send(avrcp, buf, BT_AVCTP_RESPONSE, tid); + if (err < 0) + { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + net_buf_unref(buf); + } + + return err; +} + +static void avrcp_fill_subunit_info_param( + uint8_t param[BT_AVRCP_SUBUNIT_INFO_RSP_SIZE], uint8_t subunit_type, + uint8_t max_subunit_id) +{ + memset(param, 0xff, BT_AVRCP_SUBUNIT_INFO_RSP_SIZE); + + param[0] = FIELD_PREP(GENMASK(6, 4), AVRCP_SUBUNIT_PAGE) | + FIELD_PREP(GENMASK(2, 0), AVRCP_SUBUNIT_EXTENSION_CODE); + + param[1] = FIELD_PREP(GENMASK(7, 3), subunit_type) | FIELD_PREP(GENMASK(2, + 0), max_subunit_id); +} + +static int bt_avrcp_send_subunit_info(struct bt_avrcp *avrcp, + uint8_t tid, uint8_t rsp_type) +{ + struct net_buf *buf; + uint8_t param[BT_AVRCP_SUBUNIT_INFO_RSP_SIZE] = + { + 0 + }; + + int err; + + buf = avrcp_create_subunit_pdu(avrcp, rsp_type); + if (buf == NULL) + { + LOG_WRN("Insufficient buffer"); + return -ENOMEM; + } + + /* If the unit implements this profile, it shall return PANEL subunit in + * the subunit_type field, and value 0 in the max_subunit_ID field in the + * response frame. + */ + + avrcp_fill_subunit_info_param(param, BT_AVRCP_SUBUNIT_TYPE_PANEL, 0); + + if (net_buf_tailroom(buf) < sizeof(param)) + { + LOG_WRN("Not enough tailroom in buffer"); + net_buf_unref(buf); + return -ENOMEM; + } + + net_buf_add_mem(buf, param, sizeof(param)); + + err = avrcp_send(avrcp, buf, BT_AVCTP_RESPONSE, tid); + if (err < 0) + { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + net_buf_unref(buf); + } + + return err; +} + +static int init_fragmentation_context( + struct bt_avrcp_ct *ct, uint8_t tid, uint8_t pdu_id, uint8_t rsp) +{ + struct bt_avrcp_ct_frag_reassembly_ctx *ctx; + + if (ct == NULL) + { + return -EINVAL; + } + + /* Clean up any existing reassembly buffer */ + + if (ct->reassembly_buf != NULL) + { + LOG_WRN("Interleaving fragments not allowed (tid=%u, rsp=%u)", tid, + rsp); + net_buf_drop(&ct->reassembly_buf); + } + + /* Allocate reassembly buffer */ + + ct->reassembly_buf = net_buf_alloc(&avrcp_vd_rx_pool, 0); + if (ct->reassembly_buf == NULL) + { + LOG_ERR("Failed to allocate reassembly buffer"); + return -ENOBUFS; + } + + DEBUGASSERT(ct->reassembly_buf->user_data_size >= sizeof(*ctx)); + ctx = net_buf_user_data(ct->reassembly_buf); + + ctx->tid = tid; + ctx->pdu_id = pdu_id; + ctx->rsp = rsp; + return 0; +} + +static struct bt_avrcp_ct_frag_reassembly_ctx *get_fragmentation_context( + struct bt_avrcp_ct *ct) +{ + struct bt_avrcp_ct_frag_reassembly_ctx *ctx; + + if (ct == NULL) + { + return NULL; + } + + ctx = net_buf_user_data(ct->reassembly_buf); + + return ctx; +} + +static int add_fragment_data(struct bt_avrcp_ct *ct, + const uint8_t *data, uint16_t data_len) +{ + if ((ct == NULL) || (data == NULL) || (ct->reassembly_buf == NULL)) + { + return -EINVAL; + } + + if (net_buf_tailroom(ct->reassembly_buf) < data_len) + { + LOG_ERR("Insufficient space in reassembly buffer"); + return -ENOMEM; + } + + /* Add fragment data to reassembly buffer */ + + net_buf_add_mem(ct->reassembly_buf, data, data_len); + return 0; +} + +static struct net_buf *avrcp_prepare_vendor_pdu(struct bt_avrcp *avrcp, + bt_avrcp_pkt_type_t pkt_type, + uint8_t avrcp_type, + uint8_t pdu_id, + uint16_t param_len) +{ + struct net_buf *buf; + struct bt_avrcp_avc_vendor_pdu *pdu; + + if (avrcp == NULL) + { + return NULL; + } + + buf = avrcp_create_vendor_pdu(avrcp, avrcp_type); + if (buf == NULL) + { + LOG_WRN("Insufficient buffer"); + return NULL; + } + + if (net_buf_tailroom(buf) < sizeof(*pdu)) + { + LOG_WRN("Not enough tailroom: required"); + net_buf_unref(buf); + return NULL; + } + + pdu = net_buf_add(buf, sizeof(*pdu)); + sys_put_be24(BT_AVRCP_COMPANY_ID_BLUETOOTH_SIG, pdu->company_id); + pdu->pdu_id = pdu_id; + BT_AVRCP_AVC_PDU_SET_PACKET_TYPE(pdu, pkt_type); + pdu->param_len = sys_cpu_to_be16(param_len); + if (net_buf_tailroom(buf) < param_len) + { + LOG_WRN("Not enough tailroom: required for param"); + net_buf_unref(buf); + return NULL; + } + + return buf; +} + +static int send_single_vendor_rsp( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t rsp, uint8_t pdu_id, + struct net_buf *buf) +{ + struct bt_avrcp_header *hdr; + struct bt_avrcp_avc_vendor_pdu *pdu; + uint16_t param_len; + + if ((tg == NULL) || (tg->avrcp == NULL)) + { + return -EINVAL; + } + + param_len = buf->len; + + if (net_buf_headroom(buf) < (sizeof(*pdu) + sizeof(*hdr))) + { + LOG_WRN("Not enough headroom: for vendor dependent PDU"); + net_buf_unref(buf); + return -ENOMEM; + } + + pdu = net_buf_push(buf, sizeof(*pdu)); + sys_put_be24(BT_AVRCP_COMPANY_ID_BLUETOOTH_SIG, pdu->company_id); + pdu->pdu_id = pdu_id; + BT_AVRCP_AVC_PDU_SET_PACKET_TYPE(pdu, BT_AVRCP_PKT_TYPE_SINGLE); + pdu->param_len = sys_cpu_to_be16(param_len); + + hdr = net_buf_push(buf, sizeof(struct bt_avrcp_header)); + memset(hdr, 0, sizeof(struct bt_avrcp_header)); + BT_AVRCP_HDR_SET_CTYPE_OR_RSP(hdr, rsp); + BT_AVRCP_HDR_SET_SUBUNIT_ID(hdr, BT_AVRCP_SUBUNIT_ID_ZERO); + BT_AVRCP_HDR_SET_SUBUNIT_TYPE(hdr, BT_AVRCP_SUBUNIT_TYPE_PANEL); + hdr->opcode = BT_AVRCP_OPC_VENDOR_DEPENDENT; + + return avrcp_send(tg->avrcp, buf, BT_AVCTP_RESPONSE, tid); +} + +static int send_fragmented_vendor_rsp(struct bt_avrcp_tg_vd_rsp_tx *tx, + bt_avrcp_pkt_type_t pkt_type, + uint8_t *data, + uint16_t data_len) +{ + struct bt_avrcp_tg *tg = tx->tg; + struct net_buf *buf; + + if ((tg == NULL) || (tg->avrcp == NULL)) + { + return -EINVAL; + } + + buf = avrcp_prepare_vendor_pdu(tg->avrcp, pkt_type, tx->rsp, tx->pdu_id, + data_len); + if (buf == NULL) + { + return -ENOBUFS; + } + + if (data_len > 0U) + { + net_buf_add_mem(buf, data, data_len); + } + + return avrcp_send(tg->avrcp, buf, BT_AVCTP_RESPONSE, tx->tid); +} + +static int bt_avrcp_ct_continuing_rsp_cmd_send( + struct bt_avrcp_ct *ct, uint8_t tid, uint8_t op_pdu_id, + uint8_t target_pdu_id) +{ + struct net_buf *buf; + + if ((ct == NULL) || (ct->avrcp == NULL)) + { + return -EINVAL; + } + + buf = avrcp_prepare_vendor_pdu(ct->avrcp, BT_AVRCP_PKT_TYPE_SINGLE, + BT_AVRCP_CTYPE_CONTROL, + op_pdu_id, sizeof(target_pdu_id)); + if (buf == NULL) + { + return -ENOBUFS; + } + + net_buf_add_u8(buf, target_pdu_id); + + return avrcp_send(ct->avrcp, buf, BT_AVCTP_CMD, tid); +} + +static int bt_avrcp_ct_send_req_continuing_rsp(struct bt_avrcp_ct *ct, + uint8_t tid, uint8_t pdu_id) +{ + return bt_avrcp_ct_continuing_rsp_cmd_send(ct, tid, + BT_AVRCP_PDU_ID_REQ_CONTINUING_RSP, pdu_id); +} + +static int bt_avrcp_ct_send_abort_continuing_rsp( + struct bt_avrcp_ct *ct, uint8_t tid, uint8_t pdu_id) +{ + return bt_avrcp_ct_continuing_rsp_cmd_send(ct, tid, + BT_AVRCP_PDU_ID_ABORT_CONTINUING_RSP, + pdu_id); +} + +static void bt_avrcp_tg_process_continuing_cmd( + struct bt_avrcp_tg *tg, bool abort_continuing, uint8_t tid, + struct net_buf *buf) +{ + struct list_node *node; + struct bt_avrcp_tg_vd_rsp_tx *tx; + struct net_buf *tx_buf; + uint8_t pdu_id; + + avrcp_tg_lock(tg); + + node = list_peek_head(&tg->vd_rsp_tx_pending); + if (!node) + { + LOG_WRN("No pending tx"); + avrcp_tg_unlock(tg); + return; + } + + tx_buf = CONTAINER_OF(node, struct net_buf, node); + + DEBUGASSERT(tx_buf->user_data_size >= sizeof(*tx)); + tx = net_buf_user_data(tx_buf); + + if (buf->len < sizeof(pdu_id)) + { + LOG_WRN("Invalid AVRCP buffer: no PDU id"); + avrcp_tg_unlock(tg); + return; + } + + pdu_id = net_buf_pull_u8(buf); + + /* Clear TX_ONGOING and update TID */ + + atomic_clear_bit(tx->flags, BT_AVRCP_TG_FLAG_TX_ONGOING); + tx->tid = tid; + + if (abort_continuing) + { + /* ABORT_CONTINUING: set abort flag, clear sending flag */ + + atomic_set_bit(tx->flags, BT_AVRCP_TG_FLAG_ABORT_CONTINUING); + + /* Same PDU is ACCEPTED; otherwise REJECTED */ + + tx->rsp = (pdu_id == tx->pdu_id) ? + BT_AVRCP_RSP_ACCEPTED : BT_AVRCP_RSP_REJECTED; + tx->pdu_id = BT_AVRCP_PDU_ID_ABORT_CONTINUING_RSP; + } + else + { + /* Different PDU REJECTED with CONTINUING_RSP PDU ID */ + + if (pdu_id != tx->pdu_id) + { + tx->rsp = BT_AVRCP_RSP_REJECTED; + tx->pdu_id = BT_AVRCP_PDU_ID_REQ_CONTINUING_RSP; + } + } + + LOG_DBG("Continuing cmd processed: abort=%d, pdu_id=0x%02x", + abort_continuing, pdu_id); + + avrcp_tg_unlock(tg); +} + +static void avrcp_tg_tx_remove(struct bt_avrcp_tg *tg, struct net_buf *buf) +{ + avrcp_tg_lock(tg); + list_find_and_remove(&tg->vd_rsp_tx_pending, &buf->node); + avrcp_tg_unlock(tg); +} + +static void bt_avrcp_tg_vendor_tx_work(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_avrcp_tg *tg = CONTAINER_OF(dwork, struct bt_avrcp_tg, + vd_rsp_tx_work); + struct list_node *node; + struct net_buf *buf; + struct bt_avrcp_tg_vd_rsp_tx *tx; + uint16_t max_payload_size; + int err; + + avrcp_tg_lock(tg); + + node = list_peek_head(&tg->vd_rsp_tx_pending); + if (node == NULL) + { + LOG_DBG("No pending tx"); + avrcp_tg_unlock(tg); + return; + } + + avrcp_tg_unlock(tg); + + buf = CONTAINER_OF(node, struct net_buf, node); + + DEBUGASSERT(buf->user_data_size >= sizeof(*tx)); + tx = net_buf_user_data(buf); + + if (atomic_test_bit(tx->flags, BT_AVRCP_TG_FLAG_TX_ONGOING)) + { + return; + } + + /* Calculate maximum payload size per fragment */ + + max_payload_size = BT_AVRCP_FRAGMENT_SIZE - + sizeof(struct bt_avrcp_header) - + sizeof(struct bt_avrcp_avc_vendor_pdu); + + /* Check if fragmentation is needed */ + + if ((tx->sent_len == 0) && (buf->len <= max_payload_size)) + { + /* Single packet response */ + + err = send_single_vendor_rsp(tg, tx->tid, tx->rsp, tx->pdu_id, buf); + if (err < 0) + { + LOG_ERR("Failed to send single len: %u", buf->len); + goto done; + } + + avrcp_tg_tx_remove(tg, buf); + k_work_reschedule(&tg->vd_rsp_tx_work, 0); + return; + } + + /* Multi-packet fragmented response */ + + bt_avrcp_pkt_type_t pkt_type = BT_AVRCP_PKT_TYPE_SINGLE; + uint16_t chunk_size = MIN(max_payload_size, buf->len); + + if (atomic_test_and_clear_bit(tx->flags, + BT_AVRCP_TG_FLAG_ABORT_CONTINUING) || + (tx->rsp == BT_AVRCP_RSP_REJECTED)) + { + LOG_WRN("Abort to continuing send OR REJECT"); + struct net_buf *rsp_buf; + uint8_t error_code = BT_AVRCP_STATUS_INVALID_PARAMETER; + + rsp_buf = bt_avrcp_create_vendor_pdu(NULL); + if (rsp_buf == NULL) + { + LOG_ERR("Failed to allocate response buffer"); + goto done; + } + + if (tx->rsp == BT_AVRCP_RSP_REJECTED) + { + if (net_buf_tailroom(rsp_buf) < sizeof(uint8_t)) + { + LOG_ERR("Insufficient space in response buffer"); + goto done; + } + + net_buf_add_u8(rsp_buf, error_code); + } + + err = send_single_vendor_rsp(tg, tx->tid, tx->rsp, tx->pdu_id, + rsp_buf); + if (err < 0) + { + LOG_ERR("Failed to send rsp_buf"); + net_buf_unref(rsp_buf); + } + + goto done; + } + + /* Determine packet type */ + + if (tx->sent_len == 0U) + { + pkt_type = BT_AVRCP_PKT_TYPE_START; + } + else + { + /* Last fragment */ + + if (chunk_size >= buf->len) + { + pkt_type = BT_AVRCP_PKT_TYPE_END; + } + else + { + pkt_type = BT_AVRCP_PKT_TYPE_CONTINUE; + } + } + + /* Send fragment */ + + err = send_fragmented_vendor_rsp(tx, pkt_type, buf->data, chunk_size); + if (err < 0) + { + if (err == -ENOBUFS) + { + LOG_WRN("Retry send fragment %p", buf); + k_work_reschedule(&tg->vd_rsp_tx_work, + MSEC2TICK(AVRCP_TX_RETRY_DELAY_MS)); + return; + } + + LOG_ERR("Failed to send fragment offset %u len: %u", tx->sent_len, + chunk_size); + goto done; + } + + atomic_set_bit(tx->flags, BT_AVRCP_TG_FLAG_TX_ONGOING); + net_buf_pull_mem(buf, chunk_size); + + tx->sent_len += chunk_size; + if (buf->len == 0) + { + avrcp_tg_tx_remove(tg, buf); + net_buf_unref(buf); + } + + return; + +done: + avrcp_tg_tx_remove(tg, buf); + if (buf != NULL) + { + net_buf_unref(buf); + } + + /* restart the tx work */ + + k_work_reschedule(&tg->vd_rsp_tx_work, 0); +} + +static int bt_avrcp_tg_send_vendor_rsp( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t pdu_id, uint8_t result, + struct net_buf *buf) +{ + struct bt_avrcp_tg_vd_rsp_tx *tx; + + if ((tg == NULL) || (tg->avrcp == NULL) || (buf == NULL)) + { + return -EINVAL; + } + + DEBUGASSERT(buf->user_data_size >= sizeof(*tx)); + tx = net_buf_user_data(buf); + + tx->tg = tg; + tx->tid = tid; + tx->rsp = result; + tx->pdu_id = pdu_id; + tx->sent_len = 0U; + atomic_clear_bit(tx->flags, BT_AVRCP_TG_FLAG_TX_ONGOING); + atomic_clear_bit(tx->flags, BT_AVRCP_TG_FLAG_ABORT_CONTINUING); + + LOG_DBG("Sending vendor dependent response: tid=%u, total_len=%u", tid, + buf->len); + avrcp_tg_lock(tg); + list_add_tail(&tg->vd_rsp_tx_pending, &buf->node); + avrcp_tg_unlock(tg); + + k_work_reschedule(&tg->vd_rsp_tx_work, 0); + + return 0; +} + +static int process_get_caps_rsp( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + int err; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->get_caps == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + err = bt_avrcp_rsp_to_status(rsp_code, buf, &status); + if ((err < 0) || (status != BT_AVRCP_STATUS_SUCCESS)) + { + buf = NULL; + } + + avrcp_ct_cb->get_caps(get_avrcp_ct(avrcp), tid, status, buf); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_register_notification_rsp( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_avrcp_event_data *event_data; + uint8_t event_id; + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + uint16_t expected_len; + struct bt_avrcp_ct *ct = get_avrcp_ct(avrcp); + uint8_t failed_evt = 0; + bool found = false; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->notification == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + if (rsp_code == BT_AVRCP_RSP_REJECTED) + { + if (buf->len < sizeof(status)) + { + LOG_ERR("Invalid notification status response length"); + return BT_AVRCP_STATUS_INVALID_PARAMETER; + } + + status = net_buf_pull_u8(buf); + goto notify_callback; + } + + if (rsp_code == BT_AVRCP_RSP_NOT_IMPLEMENTED) + { + status = BT_AVRCP_STATUS_NOT_IMPLEMENTED; + goto notify_callback; + } + + /* The first byte is the event_id */ + + if (buf->len < sizeof(event_id)) + { + LOG_ERR("Invalid notification response length"); + return BT_AVRCP_STATUS_INVALID_PARAMETER; + } + + event_id = net_buf_pull_u8(buf); + if (event_id >= ARRAY_SIZE(ct->ct_notify)) + { + LOG_ERR("Invalid event_id"); + return BT_AVRCP_STATUS_INVALID_PARAMETER; + } + + event_data = (struct bt_avrcp_event_data *)buf->data; + + /* Parse event-specific data */ + + switch (event_id) + { + case BT_AVRCP_EVT_PLAYBACK_STATUS_CHANGED: + if (buf->len < sizeof(event_data->play_status)) + { + LOG_ERR("Invalid PLAYBACK_STATUS_CHANGED response length"); + return BT_AVRCP_STATUS_INVALID_PARAMETER; + } + + if (event_data->play_status > BT_AVRCP_PLAYBACK_STATUS_REV_SEEK && + event_data->play_status != BT_AVRCP_PLAYBACK_STATUS_ERROR) + { + LOG_ERR("Invalid playback status: %d", event_data->play_status); + return BT_AVRCP_STATUS_INVALID_PARAMETER; + } + break; + case BT_AVRCP_EVT_TRACK_CHANGED: + if (buf->len < sizeof(event_data->identifier)) + { + LOG_ERR("Invalid TRACK_CHANGED response length"); + return BT_AVRCP_STATUS_INVALID_PARAMETER; + } + + uint64_t identifier = + sys_get_be64((const uint8_t *)event_data->identifier); + + memcpy(event_data->identifier, &identifier, sizeof(uint64_t)); + break; + case BT_AVRCP_EVT_PLAYBACK_POS_CHANGED: + if (buf->len < sizeof(event_data->playback_pos)) + { + LOG_ERR("Invalid PLAYBACK_POS_CHANGED response length"); + return BT_AVRCP_STATUS_INVALID_PARAMETER; + } + + event_data->playback_pos = sys_be32_to_cpu(event_data->playback_pos); + break; + case BT_AVRCP_EVT_BATT_STATUS_CHANGED: + if (buf->len < sizeof(event_data->battery_status)) + { + LOG_ERR("Invalid BATT_STATUS_CHANGED response length"); + return BT_AVRCP_STATUS_INVALID_PARAMETER; + } + break; + case BT_AVRCP_EVT_SYSTEM_STATUS_CHANGED: + if (buf->len < sizeof(event_data->system_status)) + { + LOG_ERR("Invalid SYSTEM_STATUS_CHANGED response length"); + return BT_AVRCP_STATUS_INVALID_PARAMETER; + } + + if (event_data->system_status > BT_AVRCP_SYSTEM_STATUS_UNPLUGGED) + { + LOG_ERR("Invalid system status: %d", event_data->system_status); + return BT_AVRCP_STATUS_INVALID_PARAMETER; + } + break; + case BT_AVRCP_EVT_PLAYER_APP_SETTING_CHANGED: + expected_len = sizeof(event_data->setting_changed.num_of_attr) + + event_data->setting_changed.num_of_attr * + sizeof(struct bt_avrcp_app_setting_attr_val); + if (buf->len < expected_len) + { + LOG_ERR("Invalid PLAYER_APP_SETTING_CHANGED attribute length"); + return BT_AVRCP_STATUS_INVALID_PARAMETER; + } + break; + case BT_AVRCP_EVT_ADDRESSED_PLAYER_CHANGED: + if (buf->len < sizeof(event_data->addressed_player_changed)) + { + LOG_ERR("Invalid ADDRESSED_PLAYER_CHANGED response length"); + return BT_AVRCP_STATUS_INVALID_PARAMETER; + } + + event_data->addressed_player_changed.player_id = + sys_be16_to_cpu( + event_data->addressed_player_changed.player_id); + event_data->addressed_player_changed.uid_counter = + sys_be16_to_cpu( + event_data->addressed_player_changed.uid_counter); + break; + case BT_AVRCP_EVT_UIDS_CHANGED: + if (buf->len < sizeof(event_data->uid_counter)) + { + LOG_ERR("Invalid UIDS_CHANGED response length"); + return BT_AVRCP_STATUS_INVALID_PARAMETER; + } + + event_data->uid_counter = sys_be16_to_cpu(event_data->uid_counter); + break; + case BT_AVRCP_EVT_VOLUME_CHANGED: + if (buf->len < sizeof(event_data->absolute_volume)) + { + LOG_ERR("Invalid VOLUME_CHANGED response length"); + return BT_AVRCP_STATUS_INVALID_PARAMETER; + } + + if (event_data->absolute_volume > BT_AVRCP_MAX_ABSOLUTE_VOLUME) + { + LOG_ERR("Invalid absolute volume: " + "%d", event_data->absolute_volume); + return BT_AVRCP_STATUS_INVALID_PARAMETER; + } + break; + case BT_AVRCP_EVT_TRACK_REACHED_END: + case BT_AVRCP_EVT_TRACK_REACHED_START: + case BT_AVRCP_EVT_AVAILABLE_PLAYERS_CHANGED: + case BT_AVRCP_EVT_NOW_PLAYING_CONTENT_CHANGED: + if (buf->len != 0) + { + LOG_WRN("Zero-parameter event (0x%02X) with %u trailing bytes", + event_id, buf->len); + } + + event_data = NULL; + break; + default: + LOG_WRN("Unknown notification event_id: 0x%02X", event_id); + return BT_AVRCP_STATUS_INVALID_PARAMETER; + } + + if (tid != ct->ct_notify[event_id].tid) + { + LOG_WRN("Mismatched transaction ID: received %u, expected %u", tid, + ct->ct_notify[event_id].tid); + } + + if (rsp_code != BT_AVRCP_RSP_INTERIM && rsp_code != BT_AVRCP_RSP_CHANGED) + { + LOG_WRN("Unexpected rsp_code: 0x%02X", rsp_code); + } + + if ((rsp_code == BT_AVRCP_RSP_INTERIM) && + (ct->ct_notify[event_id].interim_received == 0)) + { + /* Mark as interim_received flag on interim response */ + + ct->ct_notify[event_id].interim_received = true; + avrcp_ct_cb->notification(get_avrcp_ct(avrcp), tid, + BT_AVRCP_STATUS_SUCCESS, event_id, + (struct bt_avrcp_event_data *)event_data); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; + } + + if ((ct->ct_notify[event_id].interim_received == 1) && + (rsp_code == BT_AVRCP_RSP_CHANGED)) + { + ct->ct_notify[event_id].interim_received = false; + if (ct->ct_notify[event_id].cb != NULL) + { + bt_avrcp_notify_changed_cb_t cb; + + cb = ct->ct_notify[event_id].cb; + ct->ct_notify[event_id].cb = NULL; + cb(ct, event_id, (struct bt_avrcp_event_data *)event_data); + } + } + + return BT_AVRCP_STATUS_OPERATION_COMPLETED; + +notify_callback: + + /* Find the event registered with this TID and clear ONLY that one */ + + ARRAY_FOR_EACH(ct->ct_notify, i) + { + if (ct->ct_notify[i].tid == tid && ct->ct_notify[i].cb != NULL) + { + failed_evt = i; + ct->ct_notify[i].cb = NULL; + ct->ct_notify[i].interim_received = false; + found = true; + break; + } + } + + avrcp_ct_cb->notification(get_avrcp_ct(avrcp), tid, status, + found ? failed_evt : 0, NULL); + + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_set_absolute_volume_rsp( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + uint8_t absolute_volume; + int err; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->set_absolute_volume == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + err = bt_avrcp_rsp_to_status(rsp_code, buf, &status); + if ((err < 0) || (status != BT_AVRCP_STATUS_SUCCESS)) + { + avrcp_ct_cb->set_absolute_volume(get_avrcp_ct(avrcp), tid, status, 0); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; + } + + if (buf->len < sizeof(absolute_volume)) + { + LOG_ERR("Invalid absolute volume response length"); + return BT_AVRCP_STATUS_INVALID_PARAMETER; + } + + absolute_volume = net_buf_pull_u8(buf); + + /* PTS may respond with bit 7 set in the absolute volume parameter invalid + * behavior + */ + + absolute_volume &= BT_AVRCP_MAX_ABSOLUTE_VOLUME; + + avrcp_ct_cb->set_absolute_volume(get_avrcp_ct(avrcp), tid, status, + absolute_volume); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_list_player_app_setting_attrs_rsp( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + int err; + + if ( + (avrcp_ct_cb == NULL) || + (avrcp_ct_cb->list_player_app_setting_attrs == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + err = bt_avrcp_rsp_to_status(rsp_code, buf, &status); + if ((err < 0) || (status != BT_AVRCP_STATUS_SUCCESS)) + { + buf = NULL; + } + + avrcp_ct_cb->list_player_app_setting_attrs(get_avrcp_ct(avrcp), tid, + status, + buf); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_list_player_app_setting_vals_rsp( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + int err; + + if ( + (avrcp_ct_cb == NULL) || + (avrcp_ct_cb->list_player_app_setting_vals == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + err = bt_avrcp_rsp_to_status(rsp_code, buf, &status); + if ((err < 0) || (status != BT_AVRCP_STATUS_SUCCESS)) + { + buf = NULL; + } + + avrcp_ct_cb->list_player_app_setting_vals(get_avrcp_ct(avrcp), tid, status, + buf); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_get_curr_player_app_setting_val_rsp( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + int err; + + if ( + (avrcp_ct_cb == NULL) || + (avrcp_ct_cb->get_curr_player_app_setting_val == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + err = bt_avrcp_rsp_to_status(rsp_code, buf, &status); + if ((err < 0) || (status != BT_AVRCP_STATUS_SUCCESS)) + { + buf = NULL; + } + + avrcp_ct_cb->get_curr_player_app_setting_val(get_avrcp_ct(avrcp), tid, + status, buf); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_set_player_app_setting_val_rsp( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + + if ( + (avrcp_ct_cb == NULL) || + (avrcp_ct_cb->set_player_app_setting_val == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + bt_avrcp_rsp_to_status(rsp_code, buf, &status); + + avrcp_ct_cb->set_player_app_setting_val(get_avrcp_ct(avrcp), tid, status); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_get_player_app_setting_attr_text_rsp( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + int err; + + if ( + (avrcp_ct_cb == NULL) || + (avrcp_ct_cb->get_player_app_setting_attr_text == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + err = bt_avrcp_rsp_to_status(rsp_code, buf, &status); + if ((err < 0) || (status != BT_AVRCP_STATUS_SUCCESS)) + { + buf = NULL; + } + + avrcp_ct_cb->get_player_app_setting_attr_text(get_avrcp_ct(avrcp), tid, + status, buf); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_get_player_app_setting_val_text_rsp( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + int err; + + if ( + (avrcp_ct_cb == NULL) || + (avrcp_ct_cb->get_player_app_setting_val_text == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + err = bt_avrcp_rsp_to_status(rsp_code, buf, &status); + if ((err < 0) || (status != BT_AVRCP_STATUS_SUCCESS)) + { + buf = NULL; + } + + avrcp_ct_cb->get_player_app_setting_val_text(get_avrcp_ct(avrcp), tid, + status, buf); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_inform_displayable_char_set_rsp( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + + if ( + (avrcp_ct_cb == NULL) || + (avrcp_ct_cb->inform_displayable_char_set == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + bt_avrcp_rsp_to_status(rsp_code, buf, &status); + + avrcp_ct_cb->inform_displayable_char_set(get_avrcp_ct(avrcp), tid, status); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_inform_batt_status_of_ct_rsp( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + + if ( + (avrcp_ct_cb == NULL) || (avrcp_ct_cb->inform_batt_status_of_ct == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + bt_avrcp_rsp_to_status(rsp_code, buf, &status); + + avrcp_ct_cb->inform_batt_status_of_ct(get_avrcp_ct(avrcp), tid, status); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_get_element_attrs_rsp( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + int err; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->get_element_attrs == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + err = bt_avrcp_rsp_to_status(rsp_code, buf, &status); + if ((err < 0) || (status != BT_AVRCP_STATUS_SUCCESS)) + { + buf = NULL; + } + + avrcp_ct_cb->get_element_attrs(get_avrcp_ct(avrcp), tid, status, buf); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_get_play_status_rsp( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + int err; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->get_play_status == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + err = bt_avrcp_rsp_to_status(rsp_code, buf, &status); + if ((err < 0) || (status != BT_AVRCP_STATUS_SUCCESS)) + { + buf = NULL; + } + + avrcp_ct_cb->get_play_status(get_avrcp_ct(avrcp), tid, status, buf); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_set_addressed_player_rsp( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->set_addressed_player == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + bt_avrcp_rsp_to_status(rsp_code, buf, &status); + + avrcp_ct_cb->set_addressed_player(get_avrcp_ct(avrcp), tid, status); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_play_item_rsp( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->play_item == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + bt_avrcp_rsp_to_status(rsp_code, buf, &status); + avrcp_ct_cb->play_item(get_avrcp_ct(avrcp), tid, status); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_add_to_now_playing_rsp( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->add_to_now_playing == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + bt_avrcp_rsp_to_status(rsp_code, buf, &status); + avrcp_ct_cb->add_to_now_playing(get_avrcp_ct(avrcp), tid, status); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +/** Response vendor handlers table. + * Note: The min_len field specifies the minimum payload/parameter length, + * not including status/error codes. For REJECTED responses, only 1 byte for + * the error code is required regardless of the min_len specified here. + */ + +static const struct avrcp_pdu_vendor_handler rsp_vendor_handlers[] = +{ + { + BT_AVRCP_PDU_ID_REQ_CONTINUING_RSP, + sizeof(uint8_t), + BT_AVRCP_CTYPE_CONTROL, + NULL + }, + + { + BT_AVRCP_PDU_ID_ABORT_CONTINUING_RSP, + sizeof(uint8_t), + BT_AVRCP_CTYPE_CONTROL, + NULL + }, + + { + BT_AVRCP_PDU_ID_GET_CAPS, + sizeof(struct bt_avrcp_get_caps_rsp), + BT_AVRCP_CTYPE_STATUS, + process_get_caps_rsp + }, + + { + BT_AVRCP_PDU_ID_REGISTER_NOTIFICATION, + sizeof(uint8_t), + BT_AVRCP_CTYPE_NOTIFY, + process_register_notification_rsp + }, + + { + BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_ATTRS, + sizeof(struct bt_avrcp_list_player_app_setting_attrs_rsp), + BT_AVRCP_CTYPE_STATUS, + process_list_player_app_setting_attrs_rsp + }, + + { + BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_VALS, + sizeof(struct bt_avrcp_list_player_app_setting_vals_rsp), + BT_AVRCP_CTYPE_STATUS, + process_list_player_app_setting_vals_rsp + }, + + { + BT_AVRCP_PDU_ID_GET_CURR_PLAYER_APP_SETTING_VAL, + sizeof(struct bt_avrcp_get_curr_player_app_setting_val_rsp), + BT_AVRCP_CTYPE_STATUS, + process_get_curr_player_app_setting_val_rsp + }, + + { + BT_AVRCP_PDU_ID_SET_PLAYER_APP_SETTING_VAL, + 0, + BT_AVRCP_CTYPE_CONTROL, + process_set_player_app_setting_val_rsp + }, + + { + BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_ATTR_TEXT, + sizeof(struct bt_avrcp_get_player_app_setting_attr_text_rsp), + BT_AVRCP_CTYPE_STATUS, + process_get_player_app_setting_attr_text_rsp + }, + + { + BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_VAL_TEXT, + sizeof(struct bt_avrcp_get_player_app_setting_val_text_rsp), + BT_AVRCP_CTYPE_STATUS, + process_get_player_app_setting_val_text_rsp + }, + + { + BT_AVRCP_PDU_ID_INFORM_DISPLAYABLE_CHAR_SET, + 0, + BT_AVRCP_CTYPE_CONTROL, + process_inform_displayable_char_set_rsp + }, + + { + BT_AVRCP_PDU_ID_INFORM_BATT_STATUS_OF_CT, + 0, + BT_AVRCP_CTYPE_CONTROL, + process_inform_batt_status_of_ct_rsp + }, + + { + BT_AVRCP_PDU_ID_SET_ABSOLUTE_VOLUME, + sizeof(uint8_t), + BT_AVRCP_CTYPE_CONTROL, + process_set_absolute_volume_rsp + }, + + { + BT_AVRCP_PDU_ID_GET_ELEMENT_ATTRS, + sizeof(struct bt_avrcp_get_element_attrs_rsp), + BT_AVRCP_CTYPE_STATUS, + process_get_element_attrs_rsp + }, + + { + BT_AVRCP_PDU_ID_GET_PLAY_STATUS, + sizeof(struct bt_avrcp_get_play_status_rsp), + BT_AVRCP_CTYPE_STATUS, + process_get_play_status_rsp + }, + + { + BT_AVRCP_PDU_ID_SET_ADDRESSED_PLAYER, + sizeof(uint8_t), + BT_AVRCP_CTYPE_CONTROL, + process_set_addressed_player_rsp + }, + + { + BT_AVRCP_PDU_ID_PLAY_ITEM, + sizeof(uint8_t), + BT_AVRCP_CTYPE_CONTROL, + process_play_item_rsp + }, + + { + BT_AVRCP_PDU_ID_ADD_TO_NOW_PLAYING, + sizeof(uint8_t), + BT_AVRCP_CTYPE_CONTROL, + process_add_to_now_playing_rsp + }, +}; + +static inline uint8_t get_cmd_type_by_pdu(uint8_t pdu_id) +{ + ARRAY_FOR_EACH(rsp_vendor_handlers, i) + { + if (rsp_vendor_handlers[i].pdu_id == pdu_id) + { + return rsp_vendor_handlers[i].cmd_type; + } + } + + DEBUGASSERT(false); + return BT_AVRCP_RSP_NOT_IMPLEMENTED; +} + +static inline uint8_t get_rsp_min_len_by_pdu(uint8_t pdu_id) +{ + ARRAY_FOR_EACH(rsp_vendor_handlers, i) + { + if (rsp_vendor_handlers[i].pdu_id == pdu_id) + { + return rsp_vendor_handlers[i].min_len; + } + } + + DEBUGASSERT(false); + return 0; +} + +static inline int bt_avrcp_status_to_rsp(uint8_t pdu_id, + uint8_t status, uint8_t *rsp_code) +{ + if (rsp_code == NULL) + { + return -EINVAL; + } + + switch (status) + { + case BT_AVRCP_STATUS_SUCCESS: + if (get_cmd_type_by_pdu(pdu_id) == BT_AVRCP_CTYPE_CONTROL) + { + *rsp_code = BT_AVRCP_RSP_ACCEPTED; + } + else if (get_cmd_type_by_pdu(pdu_id) == BT_AVRCP_CTYPE_STATUS) + { + *rsp_code = BT_AVRCP_RSP_STABLE; + } + else + { + DEBUGASSERT(false); + *rsp_code = BT_AVRCP_RSP_REJECTED; + return -EINVAL; + } + break; + case BT_AVRCP_STATUS_IN_TRANSITION: + *rsp_code = BT_AVRCP_RSP_IN_TRANSITION; + break; + case BT_AVRCP_STATUS_NOT_IMPLEMENTED: + *rsp_code = BT_AVRCP_RSP_NOT_IMPLEMENTED; + break; + default: + if (AVRCP_STATUS_IS_REJECTED(status) == false) + { + return -EINVAL; + } + + *rsp_code = BT_AVRCP_RSP_REJECTED; + break; + } + + return 0; +} + +static int bt_avrcp_tg_send_vendor_err_rsp( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t pdu_id, uint8_t status) +{ + struct net_buf *buf; + uint8_t rsp_code; + int err; + size_t data_len; + + if ((tg == NULL) || (tg->avrcp == NULL)) + { + return -EINVAL; + } + + err = bt_avrcp_status_to_rsp(pdu_id, status, &rsp_code); + if (err < 0) + { + return err; + } + + /* Only REJECTED response needs the status byte */ + + data_len = (rsp_code == BT_AVRCP_RSP_REJECTED) ? sizeof(status) : 0; + + buf = avrcp_prepare_vendor_pdu(tg->avrcp, BT_AVRCP_PKT_TYPE_SINGLE, + rsp_code, + pdu_id, data_len); + if (buf == NULL) + { + return -ENOBUFS; + } + + if (rsp_code == BT_AVRCP_RSP_REJECTED) + { + if (net_buf_tailroom(buf) < sizeof(status)) + { + LOG_WRN("Not enough tailroom for err_code"); + net_buf_unref(buf); + return -ENOMEM; + } + + net_buf_add_u8(buf, status); + } + + err = avrcp_send(tg->avrcp, buf, BT_AVCTP_RESPONSE, tid); + if (err < 0) + { + net_buf_unref(buf); + if (bt_avrcp_disconnect(tg->avrcp->acl_conn)) + { + LOG_ERR("Failed to disconnect AVRCP connection"); + } + } + + return err; +} + +static int handle_vendor_pdu( + struct bt_avrcp *avrcp, uint8_t tid, struct net_buf *buf, uint8_t ctype, + uint8_t pdu_id, const struct avrcp_pdu_vendor_handler *handlers, + size_t num_handlers) +{ + size_t min_len; + + for (size_t i = 0; i < num_handlers; i++) + { + const struct avrcp_pdu_vendor_handler *handler = &handlers[i]; + + if (handler->pdu_id != pdu_id) + { + continue; + } + + if (handlers != rsp_vendor_handlers && ctype != handler->cmd_type) + { + LOG_ERR("Invalid ctype 0x%02x for pdu_id 0x%02x", ctype, pdu_id); + return BT_AVRCP_STATUS_INVALID_COMMAND; + } + + /** For REJECTED responses, only need 1 byte for error code. + * For NOT_IMPLEMENTED and IN_TRANSITION, no additional data is + * needed. For other responses, use the handler's minimum length + * requirement. + */ + + if (ctype == BT_AVRCP_RSP_REJECTED) + { + min_len = sizeof(uint8_t); + } + else if (ctype == BT_AVRCP_RSP_NOT_IMPLEMENTED || + ctype == BT_AVRCP_RSP_IN_TRANSITION) + { + min_len = 0; + } + else + { + min_len = handler->min_len; + } + + if (buf->len < min_len) + { + LOG_ERR("Too small (%u < %zu) for pdu_id 0x%02x (rsp=0x%02x)", + buf->len, min_len, pdu_id, + ctype); + return BT_AVRCP_STATUS_PARAMETER_CONTENT_ERROR; + } + + return handler->func(avrcp, tid, ctype, buf); + } + + return BT_AVRCP_STATUS_INVALID_COMMAND; +} + +static void process_common_vendor_rsp( + struct bt_avrcp *avrcp, uint8_t pdu_id, uint8_t tid, uint8_t rsp_code, + struct net_buf *buf) +{ + if (buf == NULL) + { + LOG_ERR("Invalid parameters"); + return; + } + + handle_vendor_pdu(avrcp, tid, buf, rsp_code, pdu_id, rsp_vendor_handlers, + ARRAY_SIZE(rsp_vendor_handlers)); +} + +static void avrcp_vendor_dependent_rsp_handler( + struct bt_avrcp *avrcp, uint8_t tid, struct net_buf *buf) +{ + struct bt_avrcp_avc_vendor_pdu *pdu; + struct bt_avrcp_header *avrcp_hdr; + bt_avrcp_subunit_type_t subunit_type; + bt_avrcp_subunit_id_t subunit_id; + bt_avrcp_rsp_t rsp; + uint16_t len; + int err; + + if (buf->len < (sizeof(*avrcp_hdr) + sizeof(*pdu))) + { + LOG_ERR("Invalid vendor frame length: %d", buf->len); + return; + } + + avrcp_hdr = net_buf_pull_mem(buf, sizeof(*avrcp_hdr)); + subunit_type = BT_AVRCP_HDR_GET_SUBUNIT_TYPE(avrcp_hdr); + subunit_id = BT_AVRCP_HDR_GET_SUBUNIT_ID(avrcp_hdr); + rsp = BT_AVRCP_HDR_GET_CTYPE_OR_RSP(avrcp_hdr); + if ((subunit_type != BT_AVRCP_SUBUNIT_TYPE_PANEL) || + (subunit_id != BT_AVRCP_SUBUNIT_ID_ZERO)) + { + LOG_ERR("Invalid vendor dependent command"); + return; + } + + pdu = net_buf_pull_mem(buf, sizeof(*pdu)); + if (sys_get_be24(pdu->company_id) != BT_AVRCP_COMPANY_ID_BLUETOOTH_SIG) + { + LOG_ERR("Invalid company id: 0x%06x", sys_get_be24(pdu->company_id)); + return; + } + + len = sys_be16_to_cpu(pdu->param_len); + if (buf->len != len) + { + LOG_ERR("Invalid length: %d, buf length = %d", len, buf->len); + return; + } + + switch (pdu->pkt_type) + { + case BT_AVRCP_PKT_TYPE_SINGLE: + + /* Single packet should not have incomplete fragment */ + + if (NULL != get_avrcp_ct(avrcp)->reassembly_buf) + { + LOG_ERR("Single packet should not have incomplete fragment"); + goto failure; + } + + process_common_vendor_rsp(avrcp, pdu->pdu_id, tid, rsp, buf); + break; + + case BT_AVRCP_PKT_TYPE_START: + err = init_fragmentation_context(get_avrcp_ct(avrcp), tid, + pdu->pdu_id, + rsp); + if (err < 0) + { + LOG_ERR("init fragmentation context: %d", err); + goto failure; + } + + /* Add first fragment data */ + + err = add_fragment_data(get_avrcp_ct(avrcp), buf->data, buf->len); + if (err < 0) + { + LOG_ERR("Failed to add first fragment: %d", err); + goto failure; + } + + LOG_DBG("First fragment added: %u", buf->len); + bt_avrcp_ct_send_req_continuing_rsp(get_avrcp_ct(avrcp), tid, + pdu->pdu_id); + break; + + case BT_AVRCP_PKT_TYPE_CONTINUE: + + /* Continuation fragment */ + + if ((get_avrcp_ct(avrcp)->reassembly_buf == NULL) || + (get_fragmentation_context(get_avrcp_ct(avrcp))->tid != tid) || + ( + get_fragmentation_context( + get_avrcp_ct(avrcp))->pdu_id != pdu->pdu_id) || + (get_fragmentation_context(get_avrcp_ct(avrcp))->rsp != rsp)) + { + LOG_ERR("Unexpected continue packet"); + goto failure; + } + + /* Add fragment data */ + + err = add_fragment_data(get_avrcp_ct(avrcp), buf->data, buf->len); + if (err < 0) + { + LOG_ERR("Failed to add continue fragment: %d", err); + goto failure; + } + + LOG_DBG("Continue frag added: %u ", buf->len); + bt_avrcp_ct_send_req_continuing_rsp(get_avrcp_ct(avrcp), tid, + pdu->pdu_id); + break; + + case BT_AVRCP_PKT_TYPE_END: + + /* Final fragment */ + + if ((get_avrcp_ct(avrcp)->reassembly_buf == NULL) || + (get_fragmentation_context(get_avrcp_ct(avrcp))->tid != tid) || + ( + get_fragmentation_context( + get_avrcp_ct(avrcp))->pdu_id != pdu->pdu_id) || + (get_fragmentation_context(get_avrcp_ct(avrcp))->rsp != rsp)) + { + LOG_ERR("Unexpected END packet"); + goto failure; + } + + err = add_fragment_data(get_avrcp_ct(avrcp), buf->data, buf->len); + if (err < 0) + { + LOG_ERR("Failed to add end fragment: %d", err); + goto failure; + } + + LOG_DBG("Continue frag added: %u ", buf->len); + + /* Parse complete reassembled response */ + + process_common_vendor_rsp(avrcp, pdu->pdu_id, tid, rsp, + get_avrcp_ct(avrcp)->reassembly_buf); + + /* Clean up fragmentation context */ + + cleanup_fragmentation_context(get_avrcp_ct(avrcp)); + break; + + default: + LOG_DBG("Unhandled response: 0x%02x", pdu->pdu_id); + break; + } + + return; + +failure: + LOG_ERR("Failed to handle vendor dependent response"); + cleanup_fragmentation_context(get_avrcp_ct(avrcp)); + if (bt_avrcp_ct_send_abort_continuing_rsp(get_avrcp_ct(avrcp), tid, + pdu->pdu_id) < 0) + { + LOG_ERR("Failed to send abort continuing response"); + if (bt_avrcp_disconnect(avrcp->acl_conn)) + { + LOG_ERR("Failed to disconnect AVRCP connection"); + } + } +} + +static void avrcp_unit_info_rsp_handler(struct bt_avrcp *avrcp, + uint8_t tid, struct net_buf *buf) +{ + struct bt_avrcp_header *avrcp_hdr; + struct bt_avrcp_unit_info_rsp rsp; + + avrcp_hdr = net_buf_pull_mem(buf, sizeof(*avrcp_hdr)); + + if ((avrcp_ct_cb != NULL) && (avrcp_ct_cb->unit_info_rsp != NULL)) + { + if (buf->len != BT_AVRCP_UNIT_INFO_RSP_SIZE) + { + LOG_ERR("Invalid unit info length: %d", buf->len); + return; + } + + net_buf_pull_u8(buf); /* Always 0x07 */ + rsp.unit_type = FIELD_GET(GENMASK(7, 3), net_buf_pull_u8(buf)); + rsp.company_id = net_buf_pull_be24(buf); + avrcp_ct_cb->unit_info_rsp(get_avrcp_ct(avrcp), tid, &rsp); + } +} + +static void avrcp_subunit_info_rsp_handler(struct bt_avrcp *avrcp, + uint8_t tid, struct net_buf *buf) +{ + struct bt_avrcp_header *avrcp_hdr; + struct bt_avrcp_subunit_info_rsp rsp; + uint8_t tmp; + + avrcp_hdr = net_buf_pull_mem(buf, sizeof(*avrcp_hdr)); + + if ((avrcp_ct_cb != NULL) && (avrcp_ct_cb->subunit_info_rsp != NULL)) + { + if (buf->len < BT_AVRCP_SUBUNIT_INFO_RSP_SIZE) + { + LOG_ERR("Invalid subunit info length: %d", buf->len); + return; + } + + net_buf_pull_u8(buf); /* Always 0x07 */ + tmp = net_buf_pull_u8(buf); + rsp.subunit_type = FIELD_GET(GENMASK(7, 3), tmp); + rsp.max_subunit_id = FIELD_GET(GENMASK(2, 0), tmp); + if (buf->len < (rsp.max_subunit_id << 1)) + { + LOG_ERR("Invalid subunit info response"); + return; + } + + rsp.extended_subunit_type = buf->data; + rsp.extended_subunit_id = + rsp.extended_subunit_type + rsp.max_subunit_id; + avrcp_ct_cb->subunit_info_rsp(get_avrcp_ct(avrcp), tid, &rsp); + } +} + +static void avrcp_pass_through_rsp_handler(struct bt_avrcp *avrcp, + uint8_t tid, struct net_buf *buf) +{ + struct bt_avrcp_header *avrcp_hdr; + struct bt_avrcp_passthrough_rsp *rsp; + bt_avrcp_rsp_t result; + + avrcp_hdr = net_buf_pull_mem(buf, sizeof(*avrcp_hdr)); + + if ((avrcp_ct_cb != NULL) && (avrcp_ct_cb->passthrough_rsp != NULL)) + { + if (buf->len < sizeof(*rsp)) + { + LOG_ERR("Invalid passthrough length: %d", buf->len); + return; + } + + result = BT_AVRCP_HDR_GET_CTYPE_OR_RSP(avrcp_hdr); + rsp = (struct bt_avrcp_passthrough_rsp *)buf->data; + + avrcp_ct_cb->passthrough_rsp(get_avrcp_ct(avrcp), tid, result, rsp); + } +} + +static const struct avrcp_handler rsp_handlers[] = +{ + { + BT_AVRCP_OPC_VENDOR_DEPENDENT, + avrcp_vendor_dependent_rsp_handler + }, + + { + BT_AVRCP_OPC_UNIT_INFO, + avrcp_unit_info_rsp_handler + }, + + { + BT_AVRCP_OPC_SUBUNIT_INFO, + avrcp_subunit_info_rsp_handler + }, + + { + BT_AVRCP_OPC_PASS_THROUGH, + avrcp_pass_through_rsp_handler + }, +}; + +static void avrcp_unit_info_cmd_handler(struct bt_avrcp *avrcp, + uint8_t tid, struct net_buf *buf) +{ + struct bt_avrcp_header *avrcp_hdr; + bt_avrcp_subunit_type_t subunit_type; + bt_avrcp_subunit_id_t subunit_id; + bt_avrcp_ctype_t ctype; + int err; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->unit_info_req == NULL)) + { + goto err_rsp; + } + + if (buf->len < sizeof(*avrcp_hdr)) + { + goto err_rsp; + } + + avrcp_hdr = net_buf_pull_mem(buf, sizeof(*avrcp_hdr)); + if (buf->len != BT_AVRCP_UNIT_INFO_CMD_SIZE) + { + LOG_ERR("Invalid unit info length"); + goto err_rsp; + } + + subunit_type = BT_AVRCP_HDR_GET_SUBUNIT_TYPE(avrcp_hdr); + subunit_id = BT_AVRCP_HDR_GET_SUBUNIT_ID(avrcp_hdr); + ctype = BT_AVRCP_HDR_GET_CTYPE_OR_RSP(avrcp_hdr); + if ((subunit_type != BT_AVRCP_SUBUNIT_TYPE_UNIT) || + (ctype != BT_AVRCP_CTYPE_STATUS) || + (subunit_id != BT_AVRCP_SUBUNIT_ID_IGNORE) || + (avrcp_hdr->opcode != BT_AVRCP_OPC_UNIT_INFO)) + { + LOG_ERR("Invalid unit info command"); + goto err_rsp; + } + + avrcp_tg_cb->unit_info_req(get_avrcp_tg(avrcp), tid); + return; + +err_rsp: + err = bt_avrcp_send_unit_info_err_rsp(avrcp, tid); + if (err) + { + LOG_ERR("Failed to send unit info error response"); + if (bt_avrcp_disconnect(avrcp->acl_conn)) + { + LOG_ERR("Failed to disconnect AVRCP connection"); + } + } +} + +static int process_get_caps_cmd( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t ctype_or_rsp, + struct net_buf *buf) +{ + uint8_t cap_id; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->get_caps == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + cap_id = net_buf_pull_u8(buf); + + /* Validate capability ID */ + + if ((cap_id != BT_AVRCP_CAP_COMPANY_ID) && + (cap_id != BT_AVRCP_CAP_EVENTS_SUPPORTED)) + { + LOG_ERR("Invalid capability ID: 0x%02x", cap_id); + return BT_AVRCP_STATUS_INVALID_PARAMETER; + } + + avrcp_tg_cb->get_caps(get_avrcp_tg(avrcp), tid, cap_id); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int avrcp_register_notification_cmd_handler( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t ctype_or_rsp, + struct net_buf *buf) +{ + struct bt_avrcp_register_notification_cmd *cmd; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->register_notification == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + cmd = net_buf_pull_mem(buf, sizeof(*cmd)); + if (cmd->event_id >= ARRAY_SIZE(get_avrcp_tg(avrcp)->tg_notify)) + { + LOG_ERR("Invalid event_id"); + return BT_AVRCP_STATUS_INVALID_PARAMETER; + } + + get_avrcp_tg(avrcp)->tg_notify[cmd->event_id].registered = true; + get_avrcp_tg(avrcp)->tg_notify[cmd->event_id].interim_sent = false; + avrcp_tg_cb->register_notification(get_avrcp_tg(avrcp), tid, cmd->event_id, + cmd->interval); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int handle_avrcp_continuing_rsp( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t ctype_or_rsp, + struct net_buf *buf) +{ + LOG_DBG("Received Continuing Response"); + bt_avrcp_tg_process_continuing_cmd(get_avrcp_tg(avrcp), false, tid, buf); + k_work_reschedule(&get_avrcp_tg(avrcp)->vd_rsp_tx_work, 0); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int handle_avrcp_abort_continuing_rsp( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t ctype_or_rsp, + struct net_buf *buf) +{ + LOG_DBG("Received Abort Continuing Response"); + bt_avrcp_tg_process_continuing_cmd(get_avrcp_tg(avrcp), true, tid, buf); + k_work_reschedule(&get_avrcp_tg(avrcp)->vd_rsp_tx_work, 0); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_list_player_app_setting_attrs_cmd( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t ctype_or_rsp, + struct net_buf *buf) +{ + if ( + (avrcp_tg_cb == NULL) || + (avrcp_tg_cb->list_player_app_setting_attrs == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + avrcp_tg_cb->list_player_app_setting_attrs(get_avrcp_tg(avrcp), tid); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_list_player_app_setting_vals_cmd( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t ctype_or_rsp, + struct net_buf *buf) +{ + uint8_t attr_id; + + if ( + (avrcp_tg_cb == NULL) || + (avrcp_tg_cb->list_player_app_setting_vals == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + attr_id = net_buf_pull_u8(buf); + if (attr_id > BT_AVRCP_PLAYER_ATTR_SCAN) + { + LOG_ERR("Invalid attr_id: %d", attr_id); + return BT_AVRCP_STATUS_INVALID_PARAMETER; + } + + avrcp_tg_cb->list_player_app_setting_vals(get_avrcp_tg(avrcp), tid, + attr_id); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_get_curr_player_app_setting_val_cmd( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t ctype_or_rsp, + struct net_buf *buf) +{ + if ( + (avrcp_tg_cb == NULL) || + (avrcp_tg_cb->get_curr_player_app_setting_val == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + avrcp_tg_cb->get_curr_player_app_setting_val(get_avrcp_tg(avrcp), tid, + buf); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_set_player_app_setting_val_cmd( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t ctype_or_rsp, + struct net_buf *buf) +{ + if ( + (avrcp_tg_cb == NULL) || + (avrcp_tg_cb->set_player_app_setting_val == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + avrcp_tg_cb->set_player_app_setting_val(get_avrcp_tg(avrcp), tid, buf); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_get_player_app_setting_attr_text_cmd( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t ctype_or_rsp, + struct net_buf *buf) +{ + if ( + (avrcp_tg_cb == NULL) || + (avrcp_tg_cb->get_player_app_setting_attr_text == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + avrcp_tg_cb->get_player_app_setting_attr_text(get_avrcp_tg(avrcp), tid, + buf); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_get_player_app_setting_val_text_cmd( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t ctype_or_rsp, + struct net_buf *buf) +{ + if ( + (avrcp_tg_cb == NULL) || + (avrcp_tg_cb->get_player_app_setting_val_text == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + avrcp_tg_cb->get_player_app_setting_val_text(get_avrcp_tg(avrcp), tid, + buf); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_inform_displayable_char_set_cmd( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t ctype_or_rsp, + struct net_buf *buf) +{ + if ( + (avrcp_tg_cb == NULL) || + (avrcp_tg_cb->inform_displayable_char_set == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + avrcp_tg_cb->inform_displayable_char_set(get_avrcp_tg(avrcp), tid, buf); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_inform_batt_status_of_ct_cmd( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t ctype_or_rsp, + struct net_buf *buf) +{ + uint8_t battery_status; + + if ( + (avrcp_tg_cb == NULL) || (avrcp_tg_cb->inform_batt_status_of_ct == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + battery_status = net_buf_pull_u8(buf); + if (battery_status > BT_AVRCP_BATTERY_STATUS_FULL) + { + LOG_ERR("Invalid battery_status: %d", battery_status); + return BT_AVRCP_STATUS_INVALID_PARAMETER; + } + + avrcp_tg_cb->inform_batt_status_of_ct(get_avrcp_tg(avrcp), tid, + battery_status); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_set_absolute_volume_cmd( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t ctype_or_rsp, + struct net_buf *buf) +{ + uint8_t absolute_volume; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->set_absolute_volume == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + absolute_volume = net_buf_pull_u8(buf); + + /* PTS may Set command with bit 7 set in the absolute volume parameter + * invalid behavior + */ + + absolute_volume &= BT_AVRCP_MAX_ABSOLUTE_VOLUME; + + avrcp_tg_cb->set_absolute_volume(get_avrcp_tg(avrcp), tid, + absolute_volume); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_get_element_attrs_cmd( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t ctype_or_rsp, + struct net_buf *buf) +{ + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->get_element_attrs == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + avrcp_tg_cb->get_element_attrs(get_avrcp_tg(avrcp), tid, buf); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_get_play_status_cmd( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t ctype_or_rsp, + struct net_buf *buf) +{ + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->get_play_status == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + avrcp_tg_cb->get_play_status(get_avrcp_tg(avrcp), tid); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_set_addressed_player_cmd( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t ctype_or_rsp, + struct net_buf *buf) +{ + uint16_t player_id; + + player_id = net_buf_pull_be16(buf); + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->set_addressed_player == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + avrcp_tg_cb->set_addressed_player(get_avrcp_tg(avrcp), tid, player_id); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_play_item_cmd( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t ctype_or_rsp, + struct net_buf *buf) +{ + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->play_item == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + avrcp_tg_cb->play_item(get_avrcp_tg(avrcp), tid, buf); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int process_add_to_now_playing_cmd( + struct bt_avrcp *avrcp, uint8_t tid, uint8_t ctype_or_rsp, + struct net_buf *buf) +{ + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->add_to_now_playing == NULL)) + { + return BT_AVRCP_STATUS_NOT_IMPLEMENTED; + } + + avrcp_tg_cb->add_to_now_playing(get_avrcp_tg(avrcp), tid, buf); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static const struct avrcp_pdu_vendor_handler cmd_vendor_handlers[] = +{ + { + BT_AVRCP_PDU_ID_REQ_CONTINUING_RSP, + sizeof(uint8_t), + BT_AVRCP_CTYPE_CONTROL, + handle_avrcp_continuing_rsp + }, + + { + BT_AVRCP_PDU_ID_ABORT_CONTINUING_RSP, + sizeof(uint8_t), + BT_AVRCP_CTYPE_CONTROL, + handle_avrcp_abort_continuing_rsp + }, + + { + BT_AVRCP_PDU_ID_GET_CAPS, + sizeof(uint8_t), + BT_AVRCP_CTYPE_STATUS, + process_get_caps_cmd + }, + + { + BT_AVRCP_PDU_ID_REGISTER_NOTIFICATION, + sizeof(struct bt_avrcp_register_notification_cmd), + BT_AVRCP_CTYPE_NOTIFY, + avrcp_register_notification_cmd_handler + }, + + { + BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_ATTRS, + 0, + BT_AVRCP_CTYPE_STATUS, + process_list_player_app_setting_attrs_cmd + }, + + { + BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_VALS, + sizeof(struct bt_avrcp_list_player_app_setting_vals_cmd), + BT_AVRCP_CTYPE_STATUS, + process_list_player_app_setting_vals_cmd + }, + + { + BT_AVRCP_PDU_ID_GET_CURR_PLAYER_APP_SETTING_VAL, + sizeof(struct bt_avrcp_get_curr_player_app_setting_val_cmd), + BT_AVRCP_CTYPE_STATUS, + process_get_curr_player_app_setting_val_cmd + }, + + { + BT_AVRCP_PDU_ID_SET_PLAYER_APP_SETTING_VAL, + sizeof(struct bt_avrcp_set_player_app_setting_val_cmd), + BT_AVRCP_CTYPE_CONTROL, + process_set_player_app_setting_val_cmd + }, + + { + BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_ATTR_TEXT, + sizeof(struct bt_avrcp_get_player_app_setting_attr_text_cmd), + BT_AVRCP_CTYPE_STATUS, + process_get_player_app_setting_attr_text_cmd + }, + + { + BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_VAL_TEXT, + sizeof(struct bt_avrcp_get_player_app_setting_val_text_cmd), + BT_AVRCP_CTYPE_STATUS, + process_get_player_app_setting_val_text_cmd + }, + + { + BT_AVRCP_PDU_ID_INFORM_DISPLAYABLE_CHAR_SET, + sizeof(struct bt_avrcp_inform_displayable_char_set_cmd), + BT_AVRCP_CTYPE_CONTROL, + process_inform_displayable_char_set_cmd + }, + + { + BT_AVRCP_PDU_ID_INFORM_BATT_STATUS_OF_CT, + sizeof(struct bt_avrcp_inform_batt_status_of_ct_cmd), + BT_AVRCP_CTYPE_CONTROL, + process_inform_batt_status_of_ct_cmd + }, + + { + BT_AVRCP_PDU_ID_SET_ABSOLUTE_VOLUME, + sizeof(uint8_t), + BT_AVRCP_CTYPE_CONTROL, + process_set_absolute_volume_cmd + }, + + { + BT_AVRCP_PDU_ID_GET_ELEMENT_ATTRS, + sizeof(struct bt_avrcp_get_element_attrs_cmd), + BT_AVRCP_CTYPE_STATUS, + process_get_element_attrs_cmd + }, + + { + BT_AVRCP_PDU_ID_GET_PLAY_STATUS, + 0, + BT_AVRCP_CTYPE_STATUS, + process_get_play_status_cmd + }, + + { + BT_AVRCP_PDU_ID_SET_ADDRESSED_PLAYER, + sizeof(struct bt_avrcp_set_addressed_player_cmd), + BT_AVRCP_CTYPE_CONTROL, + process_set_addressed_player_cmd + }, + + { + BT_AVRCP_PDU_ID_PLAY_ITEM, + sizeof(struct bt_avrcp_play_item_cmd), + BT_AVRCP_CTYPE_CONTROL, + process_play_item_cmd + }, + + { + BT_AVRCP_PDU_ID_ADD_TO_NOW_PLAYING, + sizeof(struct bt_avrcp_add_to_now_playing_cmd), + BT_AVRCP_CTYPE_CONTROL, + process_add_to_now_playing_cmd + }, +}; + +static inline uint8_t get_cmd_min_len_by_pdu(uint8_t pdu_id) +{ + ARRAY_FOR_EACH(cmd_vendor_handlers, i) + { + if (cmd_vendor_handlers[i].pdu_id == pdu_id) + { + return cmd_vendor_handlers[i].min_len; + } + } + + DEBUGASSERT(false); + return 0; +} + +static void avrcp_vendor_dependent_cmd_handler( + struct bt_avrcp *avrcp, uint8_t tid, struct net_buf *buf) +{ + struct bt_avrcp_header *avrcp_hdr; + bt_avrcp_subunit_type_t subunit_type; + bt_avrcp_subunit_id_t subunit_id; + struct bt_avrcp_avc_vendor_pdu *pdu; + uint8_t pdu_id = 0; + int error_code = BT_AVRCP_STATUS_OPERATION_COMPLETED; + uint8_t ctype_or_rsp; + uint16_t len; + int err; + + if (buf->len < (sizeof(*avrcp_hdr) + sizeof(*pdu))) + { + LOG_ERR("Invalid vendor frame length: %d", buf->len); + error_code = BT_AVRCP_STATUS_INVALID_PARAMETER; + goto err_rsp; + } + + avrcp_hdr = net_buf_pull_mem(buf, sizeof(*avrcp_hdr)); + subunit_type = BT_AVRCP_HDR_GET_SUBUNIT_TYPE(avrcp_hdr); + subunit_id = BT_AVRCP_HDR_GET_SUBUNIT_ID(avrcp_hdr); + ctype_or_rsp = BT_AVRCP_HDR_GET_CTYPE_OR_RSP(avrcp_hdr); + if ((subunit_type != BT_AVRCP_SUBUNIT_TYPE_PANEL) || + (subunit_id != BT_AVRCP_SUBUNIT_ID_ZERO)) + { + LOG_ERR("Invalid vendor dependent command"); + error_code = BT_AVRCP_STATUS_INVALID_PARAMETER; + goto err_rsp; + } + + pdu = net_buf_pull_mem(buf, sizeof(*pdu)); + pdu_id = pdu->pdu_id; + if (sys_get_be24(pdu->company_id) != BT_AVRCP_COMPANY_ID_BLUETOOTH_SIG) + { + LOG_ERR("Invalid company id: 0x%06x", sys_get_be24(pdu->company_id)); + error_code = BT_AVRCP_STATUS_INVALID_PARAMETER; + goto err_rsp; + } + + if (BT_AVRCP_AVC_PDU_GET_PACKET_TYPE(pdu) != BT_AVRCP_PKT_TYPE_SINGLE) + { + LOG_ERR("Invalid packet type"); + error_code = BT_AVRCP_STATUS_INVALID_PARAMETER; + goto err_rsp; + } + + len = sys_be16_to_cpu(pdu->param_len); + + if (buf->len != len) + { + LOG_ERR("Invalid length: %d, buf length = %d", len, buf->len); + error_code = BT_AVRCP_STATUS_INVALID_PARAMETER; + goto err_rsp; + } + + error_code = handle_vendor_pdu(avrcp, tid, buf, ctype_or_rsp, pdu->pdu_id, + cmd_vendor_handlers, + ARRAY_SIZE(cmd_vendor_handlers)); + if (error_code != BT_AVRCP_STATUS_OPERATION_COMPLETED) + { + goto err_rsp; + } + + return; + +err_rsp: + err = bt_avrcp_tg_send_vendor_err_rsp(get_avrcp_tg(avrcp), tid, pdu_id, + error_code); + if (err < 0) + { + LOG_ERR("Failed to send vendor dependent error response %d", err); + } +} + +static void avrcp_subunit_info_cmd_handler(struct bt_avrcp *avrcp, + uint8_t tid, struct net_buf *buf) +{ + struct bt_avrcp_header *avrcp_hdr; + bt_avrcp_subunit_type_t subunit_type; + bt_avrcp_subunit_id_t subunit_id; + bt_avrcp_ctype_t ctype; + uint8_t page; + uint8_t extension_code; + int err; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->subunit_info_req == NULL)) + { + goto err_rsp; + } + + if (buf->len < sizeof(*avrcp_hdr)) + { + goto err_rsp; + } + + avrcp_hdr = net_buf_pull_mem(buf, sizeof(*avrcp_hdr)); + if (buf->len != BT_AVRCP_SUBUNIT_INFO_CMD_SIZE) + { + LOG_ERR("Invalid subunit info length"); + goto err_rsp; + } + + subunit_type = BT_AVRCP_HDR_GET_SUBUNIT_TYPE(avrcp_hdr); + subunit_id = BT_AVRCP_HDR_GET_SUBUNIT_ID(avrcp_hdr); + ctype = BT_AVRCP_HDR_GET_CTYPE_OR_RSP(avrcp_hdr); + + /* First byte contains page and extension code */ + + page = FIELD_GET(GENMASK(6, 4), buf->data[0]); + extension_code = FIELD_GET(GENMASK(2, 0), buf->data[0]); + + if ((subunit_type != BT_AVRCP_SUBUNIT_TYPE_UNIT) || + (ctype != BT_AVRCP_CTYPE_STATUS) || + (subunit_id != BT_AVRCP_SUBUNIT_ID_IGNORE) || + (page != AVRCP_SUBUNIT_PAGE) || + (avrcp_hdr->opcode != BT_AVRCP_OPC_SUBUNIT_INFO) || + (extension_code != AVRCP_SUBUNIT_EXTENSION_CODE)) + { + LOG_ERR("Invalid subunit info command"); + goto err_rsp; + } + + avrcp_tg_cb->subunit_info_req(get_avrcp_tg(avrcp), tid); + return; + +err_rsp: + err = bt_avrcp_send_subunit_info(avrcp, tid, BT_AVRCP_RSP_REJECTED); + if (err < 0) + { + LOG_ERR("Failed to send subunit info error response"); + if (bt_avrcp_disconnect(avrcp->acl_conn)) + { + LOG_ERR("Failed to disconnect AVRCP connection"); + } + } +} + +static void avrcp_pass_through_cmd_handler(struct bt_avrcp *avrcp, + uint8_t tid, struct net_buf *buf) +{ + struct bt_avrcp_header *avrcp_hdr; + struct net_buf *rsp_buf; + int err; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->passthrough_req == NULL)) + { + goto err_rsp; + } + + if (buf->len < (sizeof(*avrcp_hdr) + + sizeof(struct bt_avrcp_passthrough_cmd))) + { + LOG_ERR("Invalid passthrough command length: %d", buf->len); + goto err_rsp; + } + + avrcp_hdr = net_buf_pull_mem(buf, sizeof(*avrcp_hdr)); + + if (BT_AVRCP_HDR_GET_SUBUNIT_TYPE( + avrcp_hdr) != BT_AVRCP_SUBUNIT_TYPE_PANEL || + BT_AVRCP_HDR_GET_SUBUNIT_ID(avrcp_hdr) != BT_AVRCP_SUBUNIT_ID_ZERO || + BT_AVRCP_HDR_GET_CTYPE_OR_RSP(avrcp_hdr) != BT_AVRCP_CTYPE_CONTROL) + { + LOG_ERR("Invalid passthrough command "); + goto err_rsp; + } + + avrcp_tg_cb->passthrough_req(get_avrcp_tg(avrcp), tid, buf); + return; + +err_rsp: + rsp_buf = bt_avrcp_create_pdu(NULL); + if (rsp_buf == NULL) + { + LOG_ERR("Failed to allocate response buffer"); + return; + } + + err = bt_avrcp_tg_send_passthrough_rsp(get_avrcp_tg(avrcp), tid, + BT_AVRCP_RSP_REJECTED, rsp_buf); + if (err < 0) + { + LOG_ERR("Failed to send passthrough error response"); + net_buf_unref(rsp_buf); + if (bt_avrcp_disconnect(avrcp->acl_conn)) + { + LOG_ERR("Failed to disconnect AVRCP connection"); + } + } +} + +static const struct avrcp_handler cmd_handlers[] = +{ + { + BT_AVRCP_OPC_VENDOR_DEPENDENT, + avrcp_vendor_dependent_cmd_handler + }, + + { + BT_AVRCP_OPC_UNIT_INFO, + avrcp_unit_info_cmd_handler + }, + + { + BT_AVRCP_OPC_SUBUNIT_INFO, + avrcp_subunit_info_cmd_handler + }, + + { + BT_AVRCP_OPC_PASS_THROUGH, + avrcp_pass_through_cmd_handler + }, +}; + +/* An AVRCP message received */ + +static int avrcp_recv(struct bt_avctp *session, + struct net_buf *buf, bt_avctp_cr_t cr, uint8_t tid) +{ + struct bt_avrcp *avrcp = AVRCP_AVCTP(session); + struct bt_avrcp_header *avrcp_hdr; + bt_avrcp_rsp_t rsp; + + if (buf->len < sizeof(*avrcp_hdr)) + { + LOG_ERR("invalid AVRCP header received"); + return -EINVAL; + } + + avrcp_hdr = (void *)buf->data; + rsp = BT_AVRCP_HDR_GET_CTYPE_OR_RSP(avrcp_hdr); + + LOG_DBG("AVRCP msg received, cr:0x%X, tid:0x%X, rsp: 0x%X, " + "opc:0x%02X,", cr, + tid, rsp, + avrcp_hdr->opcode); + if (cr == BT_AVCTP_RESPONSE) + { + ARRAY_FOR_EACH(rsp_handlers, i) + { + if (avrcp_hdr->opcode == rsp_handlers[i].opcode) + { + rsp_handlers[i].func(avrcp, tid, buf); + return 0; + } + } + } + else + { + ARRAY_FOR_EACH(cmd_handlers, i) + { + if (avrcp_hdr->opcode == cmd_handlers[i].opcode) + { + cmd_handlers[i].func(avrcp, tid, buf); + return 0; + } + } + } + + LOG_WRN("received unknown opcode : 0x%02X", avrcp_hdr->opcode); + return 0; +} + +static void init_avctp_control_channel(struct bt_avctp *session) +{ + LOG_DBG("session %p", session); + + session->br_chan.rx.mtu = BT_L2CAP_RX_MTU; + session->br_chan.required_sec_level = BT_SECURITY_L2; + session->pid = BT_SDP_AV_REMOTE_SVCLASS; + session->tx_pool = &avctp_ctrl_tx_pool; + session->max_tx_payload_size = CONFIG_BT_L2CAP_TX_MTU; + session->rx_pool = &avctp_ctrl_rx_pool; +} + +static const struct bt_avctp_ops_cb avctp_ops = +{ + .connected = avrcp_connected, + .disconnected = avrcp_disconnected, + .recv = avrcp_recv, +}; + +static int avrcp_accept(struct bt_conn *conn, struct bt_avctp **session) +{ + struct bt_avrcp *avrcp; + + avrcp = avrcp_get_connection(conn); + if (avrcp == NULL) + { + return -ENOMEM; + } + + if (avrcp->acl_conn != NULL) + { + return -EALREADY; + } + + init_avctp_control_channel(&(avrcp->session)); + *session = &(avrcp->session); + avrcp->session.ops = &avctp_ops; + avrcp->acl_conn = bt_conn_ref(conn); + + LOG_DBG("session: %p", &(avrcp->session)); + + return 0; +} + +#if defined(CONFIG_BT_AVRCP_BROWSING) +static void init_avctp_browsing_channel(struct bt_avctp *session) +{ + LOG_DBG("session %p", session); + + session->br_chan.rx.mtu = CONFIG_BT_AVRCP_BROWSING_L2CAP_MTU; + session->br_chan.required_sec_level = BT_SECURITY_L2; + session->br_chan.rx.optional = false; + session->br_chan.rx.max_window = CONFIG_BT_L2CAP_MAX_WINDOW_SIZE; + session->br_chan.rx.max_transmit = 3; + session->br_chan.rx.mode = BT_L2CAP_BR_LINK_MODE_ERET; + session->br_chan.tx.monitor_timeout = CONFIG_BT_L2CAP_BR_MONITOR_TIMEOUT; + session->pid = BT_SDP_AV_REMOTE_SVCLASS; + session->tx_pool = NULL; + session->max_tx_payload_size = 0; + session->rx_pool = NULL; +} + +/* The AVCTP L2CAP channel established */ + +static void browsing_avrcp_connected(struct bt_avctp *session) +{ + struct bt_avrcp *avrcp = AVRCP_BROW_AVCTP(session); + + if ((avrcp_ct_cb != NULL) && (avrcp_ct_cb->browsing_connected != NULL)) + { + avrcp_ct_cb->browsing_connected(session->br_chan.chan.conn, + get_avrcp_ct(avrcp)); + } + + if ((avrcp_tg_cb != NULL) && (avrcp_tg_cb->browsing_connected != NULL)) + { + avrcp_tg_cb->browsing_connected(session->br_chan.chan.conn, + get_avrcp_tg(avrcp)); + } +} + +/* The AVCTP L2CAP channel released */ + +static void browsing_avrcp_disconnected(struct bt_avctp *session) +{ + struct bt_avrcp *avrcp = AVRCP_BROW_AVCTP(session); + + if ((avrcp_ct_cb != NULL) && (avrcp_ct_cb->disconnected != NULL)) + { + avrcp_ct_cb->browsing_disconnected(get_avrcp_ct(avrcp)); + } + + if ((avrcp_tg_cb != NULL) && (avrcp_tg_cb->disconnected != NULL)) + { + avrcp_tg_cb->browsing_disconnected(get_avrcp_tg(avrcp)); + } +} + +static int avrcp_ct_handle_set_browsed_player( + struct bt_avrcp *avrcp, uint8_t tid, struct net_buf *buf) +{ + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->set_browsed_player == NULL)) + { + return BT_AVRCP_STATUS_INTERNAL_ERROR; + } + + avrcp_ct_cb->set_browsed_player(get_avrcp_ct(avrcp), tid, buf); + + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int avrcp_ct_handle_get_folder_items( + struct bt_avrcp *avrcp, uint8_t tid, struct net_buf *buf) +{ + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->get_folder_items == NULL)) + { + return BT_AVRCP_STATUS_INTERNAL_ERROR; + } + + avrcp_ct_cb->get_folder_items(get_avrcp_ct(avrcp), tid, buf); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int avrcp_ct_handle_change_path(struct bt_avrcp *avrcp, + uint8_t tid, struct net_buf *buf) +{ + uint8_t status; + uint32_t num_items = 0; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->change_path == NULL)) + { + return BT_AVRCP_STATUS_INTERNAL_ERROR; + } + + status = net_buf_pull_u8(buf); + if (status == BT_AVRCP_STATUS_OPERATION_COMPLETED) + { + num_items = net_buf_pull_be32(buf); + } + + avrcp_ct_cb->change_path(get_avrcp_ct(avrcp), tid, status, num_items); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int avrcp_ct_handle_get_item_attrs(struct bt_avrcp *avrcp, + uint8_t tid, struct net_buf *buf) +{ + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->get_item_attrs == NULL)) + { + return BT_AVRCP_STATUS_INTERNAL_ERROR; + } + + avrcp_ct_cb->get_item_attrs(get_avrcp_ct(avrcp), tid, buf); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int avrcp_ct_handle_get_total_number_of_items( + struct bt_avrcp *avrcp, uint8_t tid, struct net_buf *buf) +{ + if ( + (avrcp_ct_cb == NULL) || + (avrcp_ct_cb->get_total_number_of_items == NULL)) + { + return BT_AVRCP_STATUS_INTERNAL_ERROR; + } + + avrcp_ct_cb->get_total_number_of_items(get_avrcp_ct(avrcp), tid, buf); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int avrcp_ct_handle_search(struct bt_avrcp *avrcp, + uint8_t tid, struct net_buf *buf) +{ + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->search == NULL)) + { + return BT_AVRCP_STATUS_INTERNAL_ERROR; + } + + avrcp_ct_cb->search(get_avrcp_ct(avrcp), tid, buf); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int avrcp_ct_handle_browsing_general_reject( + struct bt_avrcp *avrcp, uint8_t tid, struct net_buf *buf) +{ + uint8_t status; + + if ( + (avrcp_ct_cb == NULL) || (avrcp_ct_cb->browsing_general_reject == NULL)) + { + return BT_AVRCP_STATUS_INTERNAL_ERROR; + } + + status = net_buf_pull_u8(buf); + avrcp_ct_cb->browsing_general_reject(get_avrcp_ct(avrcp), tid, status); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static const struct avrcp_pdu_handler rsp_brow_handlers[] = +{ + { + BT_AVRCP_PDU_ID_SET_BROWSED_PLAYER, + sizeof(uint8_t), + avrcp_ct_handle_set_browsed_player + }, + + { + BT_AVRCP_PDU_ID_GET_FOLDER_ITEMS, + sizeof(uint8_t), + avrcp_ct_handle_get_folder_items + }, + + { + BT_AVRCP_PDU_ID_CHANGE_PATH, + sizeof(uint8_t), + avrcp_ct_handle_change_path + }, + + { + BT_AVRCP_PDU_ID_GET_ITEM_ATTRS, + sizeof(uint8_t), + avrcp_ct_handle_get_item_attrs + }, + + { + BT_AVRCP_PDU_ID_GET_TOTAL_NUMBER_OF_ITEMS, + sizeof(uint8_t), + avrcp_ct_handle_get_total_number_of_items + }, + + { + BT_AVRCP_PDU_ID_SEARCH, + sizeof(uint8_t), + avrcp_ct_handle_search + }, + + { + BT_AVRCP_PDU_ID_GENERAL_REJECT, + sizeof(uint8_t), + avrcp_ct_handle_browsing_general_reject + }, +}; + +static int avrcp_tg_handle_set_browsed_player_req(struct bt_avrcp *avrcp, + uint8_t tid, struct net_buf *buf) +{ + uint16_t player_id; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->set_browsed_player == NULL)) + { + return BT_AVRCP_STATUS_INTERNAL_ERROR; + } + + player_id = net_buf_pull_be16(buf); + + LOG_DBG("Set browsed player request: player_id=0x%04x", player_id); + + avrcp_tg_cb->set_browsed_player(get_avrcp_tg(avrcp), tid, player_id); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int avrcp_tg_handle_get_folder_items_req( + struct bt_avrcp *avrcp, uint8_t tid, struct net_buf *buf) +{ + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->get_folder_items == NULL)) + { + return BT_AVRCP_STATUS_INTERNAL_ERROR; + } + + avrcp_tg_cb->get_folder_items(get_avrcp_tg(avrcp), tid, buf); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int avrcp_tg_handle_change_path_req(struct bt_avrcp *avrcp, + uint8_t tid, struct net_buf *buf) +{ + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->change_path == NULL)) + { + return BT_AVRCP_STATUS_INTERNAL_ERROR; + } + + avrcp_tg_cb->change_path(get_avrcp_tg(avrcp), tid, buf); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int avrcp_tg_handle_get_item_attrs_req( + struct bt_avrcp *avrcp, uint8_t tid, struct net_buf *buf) +{ + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->get_item_attrs == NULL)) + { + return BT_AVRCP_STATUS_INTERNAL_ERROR; + } + + avrcp_tg_cb->get_item_attrs(get_avrcp_tg(avrcp), tid, buf); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int avrcp_tg_handle_get_total_number_of_items_req( + struct bt_avrcp *avrcp, uint8_t tid, struct net_buf *buf) +{ + uint8_t scope; + + if ( + (avrcp_tg_cb == NULL) || + (avrcp_tg_cb->get_total_number_of_items == NULL)) + { + return BT_AVRCP_STATUS_INTERNAL_ERROR; + } + + scope = net_buf_pull_u8(buf); + + avrcp_tg_cb->get_total_number_of_items(get_avrcp_tg(avrcp), tid, scope); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static int avrcp_tg_handle_search_req(struct bt_avrcp *avrcp, + uint8_t tid, struct net_buf *buf) +{ + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->search == NULL)) + { + return BT_AVRCP_STATUS_INTERNAL_ERROR; + } + + avrcp_tg_cb->search(get_avrcp_tg(avrcp), tid, buf); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; +} + +static const struct avrcp_pdu_handler cmd_brow_handlers[] = +{ + { + BT_AVRCP_PDU_ID_SET_BROWSED_PLAYER, + sizeof(uint16_t), + avrcp_tg_handle_set_browsed_player_req + }, + + { + BT_AVRCP_PDU_ID_GET_FOLDER_ITEMS, + sizeof(struct bt_avrcp_get_folder_items_cmd), + avrcp_tg_handle_get_folder_items_req + }, + + { + BT_AVRCP_PDU_ID_CHANGE_PATH, + sizeof(struct bt_avrcp_change_path_cmd), + avrcp_tg_handle_change_path_req + }, + + { + BT_AVRCP_PDU_ID_GET_ITEM_ATTRS, + sizeof(struct bt_avrcp_get_item_attrs_cmd), + avrcp_tg_handle_get_item_attrs_req + }, + + { + BT_AVRCP_PDU_ID_GET_TOTAL_NUMBER_OF_ITEMS, + sizeof(struct bt_avrcp_get_total_number_of_items_cmd), + avrcp_tg_handle_get_total_number_of_items_req + }, + + { + BT_AVRCP_PDU_ID_SEARCH, + sizeof(struct bt_avrcp_search_cmd), + avrcp_tg_handle_search_req + }, +}; + +static int handle_pdu(struct bt_avrcp *avrcp, uint8_t tid, + struct net_buf *buf, uint8_t pdu_id, + const struct avrcp_pdu_handler *handlers, size_t num_handlers) +{ + for (size_t i = 0; i < num_handlers; i++) + { + const struct avrcp_pdu_handler *handler = &handlers[i]; + + if (handler->pdu_id != pdu_id) + { + continue; + } + + if (buf->len < handler->min_len) + { + LOG_ERR("Too small (%u bytes) pdu_id 0x%02x", buf->len, pdu_id); + return BT_AVRCP_STATUS_INVALID_PARAMETER; + } + + return handler->func(avrcp, tid, buf); + } + + return BT_AVRCP_STATUS_INVALID_COMMAND; +} + +static int browsing_avrcp_recv( + struct bt_avctp *session, struct net_buf *buf, bt_avctp_cr_t cr, + uint8_t tid) +{ + struct bt_avrcp *avrcp = AVRCP_BROW_AVCTP(session); + struct bt_avrcp_brow_pdu *brow; + int status; + int err; + + if (buf->len < sizeof(struct bt_avrcp_brow_pdu)) + { + LOG_ERR("Invalid AVRCP browsing header received: buffer too short " + "(%u)", + buf->len); + return -EMSGSIZE; + } + + brow = net_buf_pull_mem(buf, sizeof(struct bt_avrcp_brow_pdu)); + + if (buf->len != sys_be16_to_cpu(brow->param_len)) + { + LOG_ERR("Invalid AVRCP browsing PDU length: expected %u, got %u", + sys_be16_to_cpu(brow->param_len), buf->len); + return -EMSGSIZE; + } + + LOG_DBG("AVRCP browsing msg received, cr:0x%X, tid:0x%X, " + "pdu_id:0x%02X", cr, + tid, brow->pdu_id); + + if (cr == BT_AVCTP_RESPONSE) + { + handle_pdu(avrcp, tid, buf, brow->pdu_id, rsp_brow_handlers, + ARRAY_SIZE(rsp_brow_handlers)); + return 0; + } + + status = handle_pdu(avrcp, tid, buf, brow->pdu_id, cmd_brow_handlers, + ARRAY_SIZE(cmd_brow_handlers)); + if (status != BT_AVRCP_STATUS_OPERATION_COMPLETED) + { + err = bt_avrcp_tg_browsing_general_reject(get_avrcp_tg(avrcp), tid, + status); + if (err < 0) + { + LOG_ERR("Failed to send browsing general reject: %d", err); + } + + return err; + } + + return 0; +} + +static struct net_buf *browsing_avrcp_l2cap_alloc_buf( + struct bt_avctp *session) +{ + struct net_buf *buf; + + /* Called from the Bluetooth RX workqueue, which is also the context that + * releases these buffers once an SDU has been delivered, so waiting here + * could never be satisfied. + */ + + buf = net_buf_alloc(&avctp_browsing_rx_pool, 0); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + } + + return buf; +} + +static const struct bt_avctp_ops_cb browsing_avctp_ops = +{ + .connected = browsing_avrcp_connected, + .disconnected = browsing_avrcp_disconnected, + .recv = browsing_avrcp_recv, + .alloc_buf = browsing_avrcp_l2cap_alloc_buf, +}; + +static int avrcp_browsing_accept(struct bt_conn *conn, + struct bt_avctp **session) +{ + struct bt_avrcp *avrcp; + + avrcp = avrcp_get_connection(conn); + if (avrcp == NULL) + { + LOG_ERR("Cannot allocate memory"); + return -ENOTCONN; + } + + if (avrcp->acl_conn == NULL) + { + LOG_ERR("The control channel not established"); + return -ENOTCONN; + } + + if (avrcp->browsing_session.br_chan.chan.conn != NULL) + { + LOG_ERR("Browsing session already connected"); + return -EALREADY; + } + + init_avctp_browsing_channel(&(avrcp->browsing_session)); + avrcp->browsing_session.ops = &browsing_avctp_ops; + *session = &(avrcp->browsing_session); + + LOG_DBG("browsing_session: %p", &(avrcp->browsing_session)); + + return 0; +} +#endif /* CONFIG_BT_AVRCP_BROWSING */ + +void bt_avrcp_init(void) +{ + __maybe_unused int err; + + static bool initialized; + + /* Init CT and TG connection pool */ + + DEBUGASSERT(ARRAY_SIZE(bt_avrcp_ct_pool) == ARRAY_SIZE(avrcp_connection)); + DEBUGASSERT(ARRAY_SIZE(bt_avrcp_tg_pool) == ARRAY_SIZE(avrcp_connection)); + + ARRAY_FOR_EACH(avrcp_connection, i) + { + bt_avrcp_ct_pool[i].avrcp = &avrcp_connection[i]; + bt_avrcp_tg_pool[i].avrcp = &avrcp_connection[i]; + + if (!initialized) + { + /* Init delay work */ + + k_work_init_delayable(&bt_avrcp_tg_pool[i].vd_rsp_tx_work, + bt_avrcp_tg_vendor_tx_work); + list_initialize(&bt_avrcp_tg_pool[i].vd_rsp_tx_pending); + + nxsem_init(&bt_avrcp_tg_pool[i].lock, 0, 1); + } + + memset(bt_avrcp_ct_pool[i].ct_notify, 0, + sizeof(bt_avrcp_ct_pool[i].ct_notify)); + memset(bt_avrcp_tg_pool[i].tg_notify, 0, + sizeof(bt_avrcp_tg_pool[i].tg_notify)); + } + + if (initialized) + { + return; + } + + LOG_DBG("AVRCP Initialized successfully."); + + initialized = true; +} + +int bt_avrcp_connect(struct bt_conn *conn) +{ + struct bt_avrcp *avrcp; + int err; + + avrcp = avrcp_get_connection(conn); + if (avrcp == NULL) + { + LOG_ERR("Cannot allocate memory"); + return -ENOTCONN; + } + + if (avrcp->acl_conn != NULL) + { + return -EALREADY; + } + + avrcp->session.ops = &avctp_ops; + init_avctp_control_channel(&(avrcp->session)); + err = bt_avctp_connect(conn, BT_L2CAP_PSM_AVRCP, &(avrcp->session)); + if (err < 0) + { + /* If error occurs, undo the saving and return the error */ + + memset(avrcp, 0, sizeof(struct bt_avrcp)); + LOG_DBG("AVCTP Connect failed"); + return err; + } + + avrcp->acl_conn = bt_conn_ref(conn); + + LOG_DBG("Connection request sent"); + return err; +} + +int bt_avrcp_disconnect(struct bt_conn *conn) +{ + int err; + struct bt_avrcp *avrcp; + + avrcp = avrcp_get_connection(conn); + if (avrcp == NULL) + { + LOG_ERR("Get avrcp connection failure"); + return -ENOTCONN; + } + +#if defined(CONFIG_BT_AVRCP_BROWSING) + if (avrcp->browsing_session.br_chan.chan.conn != NULL) + { + /* If browsing session is still active, disconnect it first */ + + err = bt_avrcp_browsing_disconnect(conn); + if (err < 0) + { + LOG_ERR("Browsing session disconnect failed: %d", err); + return err; + } + } +#endif /* CONFIG_BT_AVRCP_BROWSING */ + + err = bt_avctp_disconnect(&(avrcp->session)); + if (err < 0) + { + LOG_DBG("AVCTP Disconnect failed"); + return err; + } + + return err; +} + +struct net_buf *bt_avrcp_create_pdu(struct net_buf_pool *pool) +{ + return bt_conn_create_pdu(pool, BT_AVRCP_HEADROOM); +} + +struct net_buf *bt_avrcp_create_vendor_pdu(struct net_buf_pool *pool) +{ + return bt_conn_create_pdu(pool, + BT_AVRCP_HEADROOM + sizeof(struct bt_avrcp_avc_vendor_pdu)); +} + +#if defined(CONFIG_BT_AVRCP_BROWSING) +int bt_avrcp_browsing_connect(struct bt_conn *conn) +{ + struct bt_avrcp *avrcp; + int err; + + avrcp = avrcp_get_connection(conn); + if (avrcp == NULL) + { + LOG_ERR("Cannot allocate memory"); + return -ENOTCONN; + } + + if (avrcp->acl_conn == NULL) + { + LOG_ERR("The control channel not established"); + return -ENOTCONN; + } + + if (avrcp->browsing_session.br_chan.chan.conn != NULL) + { + return -EALREADY; + } + + avrcp->browsing_session.ops = &browsing_avctp_ops; + init_avctp_browsing_channel(&(avrcp->browsing_session)); + err = bt_avctp_connect(conn, BT_L2CAP_PSM_AVRCP_BROWSING, + &(avrcp->browsing_session)); + if (err < 0) + { + LOG_ERR("AVCTP browsing connect failed"); + return err; + } + + LOG_DBG("Browsing connection request sent"); + + return 0; +} + +int bt_avrcp_browsing_disconnect(struct bt_conn *conn) +{ + int err; + struct bt_avrcp *avrcp; + + avrcp = avrcp_get_connection(conn); + if (avrcp == NULL) + { + LOG_ERR("Get avrcp connection failure"); + return -ENOTCONN; + } + + err = bt_avctp_disconnect(&(avrcp->browsing_session)); + if (err < 0) + { + LOG_ERR("AVCTP browsing disconnect failed"); + return err; + } + + return err; +} +#endif /* CONFIG_BT_AVRCP_BROWSING */ + +int bt_avrcp_ct_get_unit_info(struct bt_avrcp_ct *ct, uint8_t tid) +{ + struct net_buf *buf; + int err; + uint8_t param[5]; + + if ((ct == NULL) || (ct->avrcp == NULL)) + { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + buf = avrcp_create_unit_pdu(ct->avrcp, BT_AVRCP_CTYPE_STATUS); + if (!buf) + { + return -ENOMEM; + } + + memset(param, 0xff, ARRAY_SIZE(param)); + net_buf_add_mem(buf, param, sizeof(param)); + + err = avrcp_send(ct->avrcp, buf, BT_AVCTP_CMD, tid); + if (err < 0) + { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + net_buf_unref(buf); + } + + return err; +} + +int bt_avrcp_ct_get_subunit_info(struct bt_avrcp_ct *ct, uint8_t tid) +{ + struct net_buf *buf; + uint8_t param[5]; + int err; + + if ((ct == NULL) || (ct->avrcp == NULL)) + { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + buf = avrcp_create_subunit_pdu(ct->avrcp, BT_AVRCP_CTYPE_STATUS); + if (!buf) + { + return -ENOMEM; + } + + memset(param, 0xff, ARRAY_SIZE(param)); + param[0] = FIELD_PREP(GENMASK(6, 4), AVRCP_SUBUNIT_PAGE) | + FIELD_PREP(GENMASK(2, 0), AVRCP_SUBUNIT_EXTENSION_CODE); + net_buf_add_mem(buf, param, sizeof(param)); + + err = avrcp_send(ct->avrcp, buf, BT_AVCTP_CMD, tid); + if (err < 0) + { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + net_buf_unref(buf); + } + + return err; +} + +int bt_avrcp_ct_passthrough( + struct bt_avrcp_ct *ct, uint8_t tid, uint8_t opid, uint8_t state, + const uint8_t *payload, uint8_t len) +{ + struct net_buf *buf; + int err; + + if ((ct == NULL) || (ct->avrcp == NULL)) + { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + buf = avrcp_create_passthrough_pdu(ct->avrcp, BT_AVRCP_CTYPE_CONTROL); + if (!buf) + { + return -ENOMEM; + } + + net_buf_add_u8(buf, FIELD_PREP(BIT(7), state) | FIELD_PREP(GENMASK(6, 0), + opid)); + net_buf_add_u8(buf, len); + if (len) + { + net_buf_add_mem(buf, payload, len); + } + + err = avrcp_send(ct->avrcp, buf, BT_AVCTP_CMD, tid); + if (err < 0) + { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + net_buf_unref(buf); + } + + return err; +} + +int bt_avrcp_ct_register_notification( + struct bt_avrcp_ct *ct, uint8_t tid, uint8_t event_id, uint32_t interval, + bt_avrcp_notify_changed_cb_t cb) +{ + struct net_buf *buf; + uint16_t param_len = sizeof(event_id) + sizeof(interval); + int err; + + if ((ct == NULL) || (ct->avrcp == NULL)) + { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + if (avrcp_ct_cb->notification == NULL) + { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + if (event_id >= ARRAY_SIZE(ct->ct_notify)) + { + return -EINVAL; + } + + if (ct->ct_notify[event_id].cb != NULL) + { + return -EBUSY; + } + + buf = avrcp_prepare_vendor_pdu(ct->avrcp, BT_AVRCP_PKT_TYPE_SINGLE, + BT_AVRCP_CTYPE_NOTIFY, + BT_AVRCP_PDU_ID_REGISTER_NOTIFICATION, + param_len); + if (buf == NULL) + { + return -ENOBUFS; + } + + /* Add event ID */ + + net_buf_add_u8(buf, event_id); + + /* Add playback interval */ + + net_buf_add_be32(buf, interval); + + err = avrcp_send(ct->avrcp, buf, BT_AVCTP_CMD, tid); + if (err < 0) + { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + net_buf_unref(buf); + return err; + } + + ct->ct_notify[event_id].cb = cb; + ct->ct_notify[event_id].interim_received = false; + ct->ct_notify[event_id].tid = tid; + + return 0; +} + +static int bt_avrcp_ct_vendor_dependent( + struct bt_avrcp_ct *ct, uint8_t tid, uint8_t pdu_id, struct net_buf *buf) +{ + struct bt_avrcp_header *hdr; + struct bt_avrcp_avc_vendor_pdu *pdu; + uint16_t param_len; + uint8_t min_len; + int err; + + if ((ct == NULL) || (ct->avrcp == NULL) || (buf == NULL)) + { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + min_len = get_cmd_min_len_by_pdu(pdu_id); + if (buf->len < min_len) + { + LOG_ERR("CMD Buffer too small for PDU 0x%02X (len=%u, min=%u)", pdu_id, + buf->len, min_len); + return -EINVAL; + } + + param_len = buf->len; + + if (net_buf_headroom(buf) < (sizeof(*pdu) + sizeof(*hdr))) + { + LOG_WRN("Not enough headroom: for vendor dependent PDU"); + net_buf_unref(buf); + return -ENOMEM; + } + + pdu = net_buf_push(buf, sizeof(*pdu)); + sys_put_be24(BT_AVRCP_COMPANY_ID_BLUETOOTH_SIG, pdu->company_id); + pdu->pdu_id = pdu_id; + BT_AVRCP_AVC_PDU_SET_PACKET_TYPE(pdu, BT_AVRCP_PKT_TYPE_SINGLE); + pdu->param_len = sys_cpu_to_be16(param_len); + + hdr = net_buf_push(buf, sizeof(struct bt_avrcp_header)); + memset(hdr, 0, sizeof(struct bt_avrcp_header)); + BT_AVRCP_HDR_SET_CTYPE_OR_RSP(hdr, get_cmd_type_by_pdu(pdu_id)); + BT_AVRCP_HDR_SET_SUBUNIT_ID(hdr, BT_AVRCP_SUBUNIT_ID_ZERO); + BT_AVRCP_HDR_SET_SUBUNIT_TYPE(hdr, BT_AVRCP_SUBUNIT_TYPE_PANEL); + hdr->opcode = BT_AVRCP_OPC_VENDOR_DEPENDENT; + + err = avrcp_send(ct->avrcp, buf, BT_AVCTP_CMD, tid); + if (err < 0) + { + LOG_ERR("Failed to send vendor PDU (err: %d)", err); + } + + return err; +} + +int bt_avrcp_ct_get_caps(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t cap_id) +{ + struct net_buf *buf; + int err; + + if ((ct == NULL) || (ct->avrcp == NULL)) + { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + if (avrcp_ct_cb->get_caps == NULL) + { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) + { + return -ENOBUFS; + } + + if (net_buf_tailroom(buf) < sizeof(cap_id)) + { + LOG_WRN("Not enough tailroom: for cap_id"); + net_buf_unref(buf); + return -ENOMEM; + } + + net_buf_add_u8(buf, cap_id); + + err = bt_avrcp_ct_vendor_dependent(ct, tid, BT_AVRCP_PDU_ID_GET_CAPS, buf); + if (err < 0) + { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + net_buf_unref(buf); + } + + return err; +} + +int bt_avrcp_ct_list_player_app_setting_attrs(struct bt_avrcp_ct *ct, + uint8_t tid) +{ + struct net_buf *buf; + int err; + + if ((ct == NULL) || (ct->avrcp == NULL)) + { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + if (avrcp_ct_cb->list_player_app_setting_attrs == NULL) + { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) + { + return -ENOBUFS; + } + + err = bt_avrcp_ct_vendor_dependent(ct, tid, + BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_ATTRS, + buf); + if (err < 0) + { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + net_buf_unref(buf); + } + + return err; +} + +int bt_avrcp_ct_list_player_app_setting_vals(struct bt_avrcp_ct *ct, + uint8_t tid, uint8_t attr_id) +{ + int err; + struct net_buf *buf; + + if ((ct == NULL) || (ct->avrcp == NULL)) + { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + if (avrcp_ct_cb->list_player_app_setting_vals == NULL) + { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) + { + return -ENOBUFS; + } + + if (net_buf_tailroom(buf) < sizeof(attr_id)) + { + LOG_WRN("Not enough tailroom: for attr_id"); + net_buf_unref(buf); + return -ENOMEM; + } + + net_buf_add_u8(buf, attr_id); + + err = bt_avrcp_ct_vendor_dependent(ct, tid, + BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_VALS, + buf); + if (err < 0) + { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + net_buf_unref(buf); + } + + return err; +} + +int bt_avrcp_ct_get_curr_player_app_setting_val( + struct bt_avrcp_ct *ct, uint8_t tid, struct net_buf *buf) +{ + int err; + + if ((ct == NULL) || (ct->avrcp == NULL) || (buf == NULL)) + { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + if (avrcp_ct_cb->get_curr_player_app_setting_val == NULL) + { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + err = bt_avrcp_ct_vendor_dependent(ct, tid, + BT_AVRCP_PDU_ID_GET_CURR_PLAYER_APP_SETTING_VAL, + buf); + if (err < 0) + { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + net_buf_unref(buf); + } + + return err; +} + +int bt_avrcp_ct_set_player_app_setting_val(struct bt_avrcp_ct *ct, + uint8_t tid, struct net_buf *buf) +{ + int err; + + if ((ct == NULL) || (ct->avrcp == NULL) || (buf == NULL)) + { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + if (avrcp_ct_cb->set_player_app_setting_val == NULL) + { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + err = bt_avrcp_ct_vendor_dependent(ct, tid, + BT_AVRCP_PDU_ID_SET_PLAYER_APP_SETTING_VAL, + buf); + if (err < 0) + { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + } + + return err; +} + +int bt_avrcp_ct_get_player_app_setting_attr_text( + struct bt_avrcp_ct *ct, uint8_t tid, struct net_buf *buf) +{ + int err; + + if ((ct == NULL) || (ct->avrcp == NULL) || (buf == NULL)) + { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + if (avrcp_ct_cb->get_player_app_setting_attr_text == NULL) + { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + err = bt_avrcp_ct_vendor_dependent(ct, tid, + BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_ATTR_TEXT, + buf); + if (err < 0) + { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + } + + return err; +} + +int bt_avrcp_ct_get_player_app_setting_val_text( + struct bt_avrcp_ct *ct, uint8_t tid, struct net_buf *buf) +{ + int err; + + if ((ct == NULL) || (ct->avrcp == NULL) || (buf == NULL)) + { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + if (avrcp_ct_cb->get_player_app_setting_val_text == NULL) + { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + err = bt_avrcp_ct_vendor_dependent(ct, tid, + BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_VAL_TEXT, + buf); + if (err < 0) + { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + } + + return err; +} + +int bt_avrcp_ct_inform_displayable_char_set( + struct bt_avrcp_ct *ct, uint8_t tid, struct net_buf *buf) +{ + int err; + + if ((ct == NULL) || (ct->avrcp == NULL) || (buf == NULL)) + { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + if (avrcp_ct_cb->inform_displayable_char_set == NULL) + { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + err = bt_avrcp_ct_vendor_dependent(ct, tid, + BT_AVRCP_PDU_ID_INFORM_DISPLAYABLE_CHAR_SET, + buf); + if (err < 0) + { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + } + + return err; +} + +int bt_avrcp_ct_inform_batt_status_of_ct(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t battery_status) +{ + int err; + struct net_buf *buf; + + if ((ct == NULL) || (ct->avrcp == NULL)) + { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + if (avrcp_ct_cb->inform_batt_status_of_ct == NULL) + { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + if (battery_status > BT_AVRCP_BATTERY_STATUS_FULL) + { + LOG_ERR("Invalid battery status: %d", battery_status); + return -EINVAL; + } + + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) + { + return -ENOBUFS; + } + + if (net_buf_tailroom(buf) < sizeof(battery_status)) + { + LOG_WRN("Not enough tailroom: for battery_status"); + net_buf_unref(buf); + return -ENOMEM; + } + + net_buf_add_u8(buf, battery_status); + + err = bt_avrcp_ct_vendor_dependent(ct, tid, + BT_AVRCP_PDU_ID_INFORM_BATT_STATUS_OF_CT, + buf); + if (err < 0) + { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + } + + return err; +} + +int bt_avrcp_ct_set_absolute_volume(struct bt_avrcp_ct *ct, + uint8_t tid, uint8_t absolute_volume) +{ + struct net_buf *buf; + int err; + + if ((ct == NULL) || (ct->avrcp == NULL)) + { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + if (avrcp_ct_cb->set_absolute_volume == NULL) + { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + if (absolute_volume > BT_AVRCP_MAX_ABSOLUTE_VOLUME) + { + LOG_ERR("Invalid absolute volume: %d", absolute_volume); + return -EINVAL; + } + + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) + { + return -ENOBUFS; + } + + if (net_buf_tailroom(buf) < sizeof(absolute_volume)) + { + LOG_WRN("Not enough tailroom: for absolute_volume"); + net_buf_unref(buf); + return -ENOMEM; + } + + net_buf_add_u8(buf, absolute_volume); + + err = bt_avrcp_ct_vendor_dependent(ct, tid, + BT_AVRCP_PDU_ID_SET_ABSOLUTE_VOLUME, + buf); + if (err < 0) + { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + net_buf_unref(buf); + } + + return err; +} + +int bt_avrcp_ct_get_element_attrs(struct bt_avrcp_ct *ct, + uint8_t tid, struct net_buf *buf) +{ + int err; + + if ((ct == NULL) || (ct->avrcp == NULL) || (buf == NULL)) + { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + if (avrcp_ct_cb->get_element_attrs == NULL) + { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + err = bt_avrcp_ct_vendor_dependent(ct, tid, + BT_AVRCP_PDU_ID_GET_ELEMENT_ATTRS, + buf); + if (err < 0) + { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + } + + return err; +} + +int bt_avrcp_ct_get_play_status(struct bt_avrcp_ct *ct, uint8_t tid) +{ + struct net_buf *buf; + int err; + + if ((ct == NULL) || (ct->avrcp == NULL)) + { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + if (avrcp_ct_cb->get_play_status == NULL) + { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) + { + return -ENOBUFS; + } + + err = bt_avrcp_ct_vendor_dependent(ct, tid, + BT_AVRCP_PDU_ID_GET_PLAY_STATUS, + buf); + if (err < 0) + { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + net_buf_unref(buf); + } + + return err; +} + +int bt_avrcp_ct_set_addressed_player(struct bt_avrcp_ct *ct, + uint8_t tid, uint16_t player_id) +{ + int err; + struct net_buf *buf; + + if ((ct == NULL) || (ct->avrcp == NULL)) + { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + if (avrcp_ct_cb->set_addressed_player == NULL) + { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) + { + return -ENOBUFS; + } + + if (net_buf_tailroom(buf) < sizeof(player_id)) + { + LOG_WRN("Not enough tailroom: for player_id"); + net_buf_unref(buf); + return -ENOMEM; + } + + net_buf_add_be16(buf, player_id); + + err = bt_avrcp_ct_vendor_dependent(ct, tid, + BT_AVRCP_PDU_ID_SET_ADDRESSED_PLAYER, + buf); + if (err < 0) + { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + net_buf_unref(buf); + } + + return err; +} + +int bt_avrcp_ct_play_item(struct bt_avrcp_ct *ct, + uint8_t tid, struct net_buf *buf) +{ + int err; + + if ((ct == NULL) || (ct->avrcp == NULL) || (buf == NULL)) + { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + if (avrcp_ct_cb->play_item == NULL) + { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + err = bt_avrcp_ct_vendor_dependent(ct, tid, BT_AVRCP_PDU_ID_PLAY_ITEM, + buf); + if (err < 0) + { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + } + + return err; +} + +int bt_avrcp_ct_add_to_now_playing(struct bt_avrcp_ct *ct, + uint8_t tid, struct net_buf *buf) +{ + int err; + + if ((ct == NULL) || (ct->avrcp == NULL) || (buf == NULL)) + { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + if (avrcp_ct_cb->add_to_now_playing == NULL) + { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + err = bt_avrcp_ct_vendor_dependent(ct, tid, + BT_AVRCP_PDU_ID_ADD_TO_NOW_PLAYING, + buf); + if (err < 0) + { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + } + + return err; +} + +static int avctp_server_register(void) +{ + int err; + + /* Register event handlers with AVCTP */ + + avctp_server.l2cap.psm = BT_L2CAP_PSM_AVRCP; + avctp_server.l2cap.sec_level = BT_SECURITY_L2; + avctp_server.accept = avrcp_accept; + err = bt_avctp_server_register(&avctp_server); + if (err < 0 && err != -EEXIST) + { + LOG_ERR("AVCTP server registration failed (err %d)", err); + goto failed; + } + +#if defined(CONFIG_BT_AVRCP_BROWSING) + avctp_browsing_server.l2cap.psm = BT_L2CAP_PSM_AVRCP_BROWSING; + avctp_browsing_server.accept = avrcp_browsing_accept; + avctp_browsing_server.l2cap.sec_level = BT_SECURITY_L2; + err = bt_avctp_server_register(&avctp_browsing_server); + if (err < 0 && err != -EEXIST) + { + LOG_ERR("AVCTP browsing server registration failed (err %d)", err); + goto failed; + } +#endif /* CONFIG_BT_AVRCP_BROWSING */ + + LOG_DBG("AVCTP server registered"); + return 0; + +failed: + + /* TODO: Consider remove registered AVCTP servers here. */ + + return err; +} + +int bt_avrcp_ct_register_cb(const struct bt_avrcp_ct_cb *cb) +{ + int err; + + if (!cb) + { + return -EINVAL; + } + + if (avrcp_ct_cb) + { + return -EALREADY; + } + + avrcp_ct_cb = cb; + +#if defined(CONFIG_BT_AVRCP_CONTROLLER) + /* Register SDP record when CT callback is registered */ + + err = bt_sdp_register_service(&avrcp_ct_rec); + if (err < 0 && err != -EEXIST) + { + LOG_ERR("AVRCP CT SDP registration failed (err %d)", err); + goto failed; + } + + LOG_DBG("AVRCP CT SDP record registered"); +#endif /* CONFIG_BT_AVRCP_CONTROLLER */ + + /* Register AVCTP server on first role registration */ + + err = avctp_server_register(); + if (err < 0) + { + goto failed; + } + + return 0; + +failed: + avrcp_ct_cb = NULL; + return err; +} + +int bt_avrcp_tg_register_cb(const struct bt_avrcp_tg_cb *cb) +{ + int err; + + if (!cb) + { + return -EINVAL; + } + + if (avrcp_tg_cb) + { + return -EALREADY; + } + + avrcp_tg_cb = cb; + +#if defined(CONFIG_BT_AVRCP_TG_COVER_ART) + err = bt_avrcp_tg_cover_art_init(&bt_avrcp_tg_cover_art_psm); + if (err < 0) + { + LOG_ERR("AVRCP Cover Art initialization failed (err %d)", err); + goto failed; + } +#endif /* CONFIG_BT_AVRCP_TG_COVER_ART */ + +#if defined(CONFIG_BT_AVRCP_TARGET) + /* Register SDP record when TG callback is registered */ + + err = bt_sdp_register_service(&avrcp_tg_rec); + if (err < 0 && err != -EEXIST) + { + LOG_ERR("AVRCP TG SDP registration failed (err %d)", err); + goto failed; + } + + LOG_DBG("AVRCP TG SDP record registered"); +#endif /* CONFIG_BT_AVRCP_TARGET */ + + /* Register AVCTP server on first role registration */ + + err = avctp_server_register(); + if (err < 0) + { + goto failed; + } + + return 0; + +failed: + avrcp_tg_cb = NULL; + return err; +} + +int bt_avrcp_tg_send_unit_info_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + struct bt_avrcp_unit_info_rsp *rsp) +{ + struct net_buf *buf; + int err; + + if ((tg == NULL) || (tg->avrcp == NULL) || (rsp == NULL)) + { + return -EINVAL; + } + + if (!IS_TG_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + buf = avrcp_create_unit_pdu(tg->avrcp, BT_AVRCP_RSP_STABLE); + if (!buf) + { + LOG_WRN("Insufficient buffer"); + return -ENOMEM; + } + + /* The 0x7 is hard-coded in the spec. */ + + net_buf_add_u8(buf, 0x07); + + /* Add Unit Type info */ + + net_buf_add_u8(buf, FIELD_PREP(GENMASK(7, 3), (rsp->unit_type))); + + /* Company ID */ + + net_buf_add_be24(buf, (rsp->company_id)); + + err = avrcp_send(tg->avrcp, buf, BT_AVCTP_RESPONSE, tid); + if (err < 0) + { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + net_buf_unref(buf); + } + + return err; +} + +int bt_avrcp_tg_send_subunit_info_rsp(struct bt_avrcp_tg *tg, uint8_t tid) +{ + if ((tg == NULL) || (tg->avrcp == NULL)) + { + return -EINVAL; + } + + if (!IS_TG_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + return bt_avrcp_send_subunit_info(tg->avrcp, tid, BT_AVRCP_RSP_STABLE); +} + +static int bt_avrcp_browsing_send( + struct bt_avrcp *avrcp, struct net_buf *buf, uint8_t pdu_id, + uint8_t avctp_type, uint8_t tid) +{ + struct bt_avrcp_brow_pdu *hdr; + uint16_t param_len; + + if ((avrcp == NULL) || (buf == NULL)) + { + LOG_ERR("Invalid AVRCP context or buffer"); + return -EINVAL; + } + + if (avrcp->browsing_session.br_chan.chan.conn == NULL) + { + LOG_ERR("Browsing session not connected"); + return -ENOTCONN; + } + + param_len = buf->len; + + /* Add minimum size check based on avctp_type */ + + if (avctp_type == BT_AVCTP_CMD) + { + for (size_t i = 0; i < ARRAY_SIZE(cmd_brow_handlers); i++) + { + if (cmd_brow_handlers[i].pdu_id == pdu_id) + { + if (param_len < cmd_brow_handlers[i].min_len) + { + LOG_ERR("Too small (%u < %u) for cmd pdu_id 0x%02x", + param_len, + cmd_brow_handlers[i].min_len, pdu_id); + return -EINVAL; + } + break; + } + } + } + else + { + for (size_t i = 0; i < ARRAY_SIZE(rsp_brow_handlers); i++) + { + if (rsp_brow_handlers[i].pdu_id == pdu_id) + { + if (param_len < rsp_brow_handlers[i].min_len) + { + LOG_ERR("Too small (%u < %u) for rsp pdu_id 0x%02x", + param_len, + rsp_brow_handlers[i].min_len, pdu_id); + return -EINVAL; + } + break; + } + } + } + + if (net_buf_headroom(buf) < sizeof(struct bt_avrcp_brow_pdu)) + { + LOG_ERR("Not enough headroom for browsing PDU header"); + return -ENOMEM; + } + + hdr = net_buf_push(buf, sizeof(struct bt_avrcp_brow_pdu)); + memset(hdr, 0, sizeof(*hdr)); + hdr->pdu_id = pdu_id; + hdr->param_len = sys_cpu_to_be16(param_len); + + return avrcp_browsing_send(avrcp, buf, avctp_type, tid); +} + +#if defined(CONFIG_BT_AVRCP_BROWSING) +static inline int avrcp_ct_precheck(struct bt_avrcp_ct *ct, + struct net_buf *buf) +{ + if ((ct == NULL) || (ct->avrcp == NULL) || (buf == NULL)) + { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + return 0; +} + +int bt_avrcp_ct_set_browsed_player(struct bt_avrcp_ct *ct, + uint8_t tid, uint16_t player_id) +{ + struct net_buf *buf; + int err; + + if (avrcp_ct_cb->set_browsed_player == NULL) + { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + buf = avrcp_create_browsing_pdu(ct->avrcp); + if (buf == NULL) + { + return -ENOBUFS; + } + + err = avrcp_ct_precheck(ct, buf); + if (err < 0) + { + LOG_ERR("AVRCP CT precheck failed: %d", err); + net_buf_unref(buf); + return err; + } + + if (net_buf_tailroom(buf) < sizeof(player_id)) + { + LOG_ERR("Not enough tailroom for SET_BROWSED_PLAYER cmd"); + net_buf_unref(buf); + return -ENOMEM; + } + + net_buf_add_be16(buf, player_id); + + err = bt_avrcp_browsing_send(ct->avrcp, buf, + BT_AVRCP_PDU_ID_SET_BROWSED_PLAYER, + BT_AVCTP_CMD, + tid); + if (err < 0) + { + LOG_ERR("Failed to send AVRCP browsing PDU (err: %d)", err); + net_buf_unref(buf); + } + + return err; +} + +int bt_avrcp_ct_get_folder_items(struct bt_avrcp_ct *ct, + uint8_t tid, struct net_buf *buf) +{ + int err; + + err = avrcp_ct_precheck(ct, buf); + if (err < 0) + { + LOG_ERR("AVRCP CT precheck failed: %d", err); + return err; + } + + if (avrcp_ct_cb->get_folder_items == NULL) + { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + err = bt_avrcp_browsing_send(ct->avrcp, buf, + BT_AVRCP_PDU_ID_GET_FOLDER_ITEMS, + BT_AVCTP_CMD, tid); + if (err < 0) + { + LOG_ERR("Failed to send GET_FOLDER_ITEMS cmd (err: %d)", err); + } + + return err; +} + +int bt_avrcp_ct_change_path(struct bt_avrcp_ct *ct, + uint8_t tid, struct net_buf *buf) +{ + int err; + + err = avrcp_ct_precheck(ct, buf); + if (err < 0) + { + LOG_ERR("AVRCP CT precheck failed: %d", err); + return err; + } + + if (avrcp_ct_cb->change_path == NULL) + { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + err = bt_avrcp_browsing_send(ct->avrcp, buf, BT_AVRCP_PDU_ID_CHANGE_PATH, + BT_AVCTP_CMD, tid); + if (err < 0) + { + LOG_ERR("Failed to send CHANGE_PATH cmd (err: %d)", err); + } + + return err; +} + +int bt_avrcp_ct_get_item_attrs(struct bt_avrcp_ct *ct, + uint8_t tid, struct net_buf *buf) +{ + int err; + + err = avrcp_ct_precheck(ct, buf); + if (err < 0) + { + LOG_ERR("AVRCP CT precheck failed: %d", err); + return err; + } + + if (avrcp_ct_cb->get_item_attrs == NULL) + { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + err = bt_avrcp_browsing_send(ct->avrcp, buf, + BT_AVRCP_PDU_ID_GET_ITEM_ATTRS, + BT_AVCTP_CMD, tid); + if (err < 0) + { + LOG_ERR("Failed to send GET_ITEM_ATTRIBUTES cmd (err: %d)", err); + } + + return err; +} + +int bt_avrcp_ct_get_total_number_of_items(struct bt_avrcp_ct *ct, + uint8_t tid, uint8_t scope) +{ + struct net_buf *buf; + int err; + + if (scope > BT_AVRCP_SCOPE_NOW_PLAYING) + { + return -EINVAL; + } + + if (avrcp_ct_cb->get_total_number_of_items == NULL) + { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + buf = avrcp_create_browsing_pdu(ct->avrcp); + if (buf == NULL) + { + return -ENOBUFS; + } + + err = avrcp_ct_precheck(ct, buf); + if (err < 0) + { + LOG_ERR("AVRCP CT precheck failed: %d", err); + net_buf_unref(buf); + return err; + } + + if (net_buf_tailroom(buf) < sizeof(scope)) + { + LOG_ERR("Not enough tailroom for scope"); + net_buf_unref(buf); + return -ENOMEM; + } + + net_buf_add_u8(buf, scope); + + err = bt_avrcp_browsing_send(ct->avrcp, buf, + BT_AVRCP_PDU_ID_GET_TOTAL_NUMBER_OF_ITEMS, + BT_AVCTP_CMD, tid); + if (err < 0) + { + LOG_ERR("Failed to send GET_TOTAL_NUMBER_OF_ITEMS cmd (err: %d)", err); + net_buf_unref(buf); + } + + return err; +} + +int bt_avrcp_ct_search(struct bt_avrcp_ct *ct, + uint8_t tid, struct net_buf *buf) +{ + int err; + + err = avrcp_ct_precheck(ct, buf); + if (err < 0) + { + LOG_ERR("AVRCP CT precheck failed: %d", err); + return err; + } + + if (avrcp_ct_cb->search == NULL) + { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + err = bt_avrcp_browsing_send(ct->avrcp, buf, BT_AVRCP_PDU_ID_SEARCH, + BT_AVCTP_CMD, tid); + if (err < 0) + { + LOG_ERR("Failed to send SEARCH cmd (err: %d)", err); + } + + return err; +} + +static inline int avrcp_tg_precheck(struct bt_avrcp_tg *tg, + struct net_buf *buf) +{ + if ((tg == NULL) || (tg->avrcp == NULL) || (buf == NULL)) + { + return -EINVAL; + } + + if (!IS_TG_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + return 0; +} + +int bt_avrcp_tg_set_browsed_player(struct bt_avrcp_tg *tg, + uint8_t tid, struct net_buf *buf) +{ + int err; + + err = avrcp_tg_precheck(tg, buf); + if (err < 0) + { + LOG_ERR("AVRCP TG precheck failed: %d", err); + return err; + } + + err = bt_avrcp_browsing_send(tg->avrcp, buf, + BT_AVRCP_PDU_ID_SET_BROWSED_PLAYER, + BT_AVCTP_RESPONSE, tid); + if (err < 0) + { + LOG_ERR("Failed to send AVRCP browsing PDU (err: %d)", err); + } + + return err; +} + +int bt_avrcp_tg_get_folder_items(struct bt_avrcp_tg *tg, + uint8_t tid, struct net_buf *buf) +{ + int err; + + err = avrcp_tg_precheck(tg, buf); + if (err < 0) + { + LOG_ERR("AVRCP TG precheck failed: %d", err); + return err; + } + + err = bt_avrcp_browsing_send(tg->avrcp, buf, + BT_AVRCP_PDU_ID_GET_FOLDER_ITEMS, + BT_AVCTP_RESPONSE, + tid); + if (err < 0) + { + LOG_ERR("Failed to send GET_FOLDER_ITEMS rsp (err: %d)", err); + } + + return err; +} + +int bt_avrcp_tg_change_path(struct bt_avrcp_tg *tg, + uint8_t tid, uint8_t status, uint32_t num_items) +{ + struct net_buf *buf; + int err; + + buf = avrcp_create_browsing_pdu(tg->avrcp); + if (buf == NULL) + { + return -ENOBUFS; + } + + err = avrcp_tg_precheck(tg, buf); + if (err < 0) + { + LOG_ERR("AVRCP TG precheck failed: %d", err); + net_buf_unref(buf); + return err; + } + + if (net_buf_tailroom(buf) < + sizeof(status) + + ((status == BT_AVRCP_STATUS_OPERATION_COMPLETED) ? + sizeof(num_items) : 0U)) + { + LOG_ERR("Not enough tailroom for CHANGE_PATH rsp"); + net_buf_unref(buf); + return -ENOMEM; + } + + net_buf_add_u8(buf, status); + if (status == BT_AVRCP_STATUS_OPERATION_COMPLETED) + { + net_buf_add_be32(buf, num_items); + } + + err = bt_avrcp_browsing_send(tg->avrcp, buf, BT_AVRCP_PDU_ID_CHANGE_PATH, + BT_AVCTP_RESPONSE, tid); + if (err < 0) + { + LOG_ERR("Failed to send CHANGE_PATH rsp (err: %d)", err); + net_buf_unref(buf); + } + + return err; +} + +int bt_avrcp_tg_get_item_attrs(struct bt_avrcp_tg *tg, + uint8_t tid, struct net_buf *buf) +{ + int err; + + err = avrcp_tg_precheck(tg, buf); + if (err < 0) + { + LOG_ERR("AVRCP TG precheck failed: %d", err); + return err; + } + + err = bt_avrcp_browsing_send(tg->avrcp, buf, + BT_AVRCP_PDU_ID_GET_ITEM_ATTRS, + BT_AVCTP_RESPONSE, + tid); + if (err < 0) + { + LOG_ERR("Failed to send GET_ITEM_ATTRIBUTES rsp (err: %d)", err); + } + + return err; +} + +int bt_avrcp_tg_get_total_number_of_items(struct bt_avrcp_tg *tg, + uint8_t tid, struct net_buf *buf) +{ + int err; + + err = avrcp_tg_precheck(tg, buf); + if (err < 0) + { + LOG_ERR("AVRCP TG precheck failed: %d", err); + return err; + } + + err = bt_avrcp_browsing_send(tg->avrcp, buf, + BT_AVRCP_PDU_ID_GET_TOTAL_NUMBER_OF_ITEMS, + BT_AVCTP_RESPONSE, tid); + if (err < 0) + { + LOG_ERR("Failed to send GET_TOTAL_NUMBER_OF_ITEMS rsp (err: %d)", err); + } + + return err; +} + +int bt_avrcp_tg_search(struct bt_avrcp_tg *tg, + uint8_t tid, struct net_buf *buf) +{ + int err; + + err = avrcp_tg_precheck(tg, buf); + if (err < 0) + { + LOG_ERR("AVRCP TG precheck failed: %d", err); + return err; + } + + err = bt_avrcp_browsing_send(tg->avrcp, buf, BT_AVRCP_PDU_ID_SEARCH, + BT_AVCTP_RESPONSE, tid); + if (err < 0) + { + LOG_ERR("Failed to send SEARCH rsp (err: %d)", err); + } + + return err; +} + +int bt_avrcp_tg_browsing_general_reject(struct bt_avrcp_tg *tg, + uint8_t tid, uint8_t status) +{ + struct net_buf *buf; + int err; + + if (AVRCP_STATUS_IS_REJECTED(status) != true) + { + LOG_ERR("Invalid status"); + return -EINVAL; + } + + buf = avrcp_create_browsing_pdu(tg->avrcp); + if (buf == NULL) + { + return -ENOBUFS; + } + + err = avrcp_tg_precheck(tg, buf); + if (err < 0) + { + LOG_ERR("AVRCP TG precheck failed: %d", err); + net_buf_unref(buf); + return err; + } + + if (net_buf_tailroom(buf) < sizeof(status)) + { + LOG_ERR("Not enough tailroom for GENERAL_REJECT rsp"); + net_buf_unref(buf); + return -ENOMEM; + } + + net_buf_add_u8(buf, status); + + err = bt_avrcp_browsing_send(tg->avrcp, buf, + BT_AVRCP_PDU_ID_GENERAL_REJECT, + BT_AVCTP_RESPONSE, + tid); + if (err < 0) + { + LOG_ERR("Failed to send GENERAL_REJECT rsp (err: %d)", err); + net_buf_unref(buf); + } + + return err; +} +#endif /* CONFIG_BT_AVRCP_BROWSING */ + +static int build_notification_rsp_data( + uint8_t event_id, struct bt_avrcp_event_data *data, struct net_buf *buf) +{ + uint16_t param_len = sizeof(event_id); + + /* Calculate parameter length based on event type */ + + switch (event_id) + { + case BT_AVRCP_EVT_PLAYBACK_STATUS_CHANGED: + param_len += sizeof(data->play_status); + break; + case BT_AVRCP_EVT_TRACK_CHANGED: + param_len += sizeof(data->identifier); + break; + case BT_AVRCP_EVT_PLAYBACK_POS_CHANGED: + param_len += sizeof(data->playback_pos); + break; + case BT_AVRCP_EVT_BATT_STATUS_CHANGED: + param_len += sizeof(data->battery_status); + break; + case BT_AVRCP_EVT_SYSTEM_STATUS_CHANGED: + param_len += sizeof(data->system_status); + break; + case BT_AVRCP_EVT_PLAYER_APP_SETTING_CHANGED: + param_len += sizeof(data->setting_changed.num_of_attr) + + data->setting_changed.num_of_attr * sizeof( + struct bt_avrcp_app_setting_attr_val); + break; + case BT_AVRCP_EVT_ADDRESSED_PLAYER_CHANGED: + param_len += sizeof(data->addressed_player_changed); + break; + case BT_AVRCP_EVT_UIDS_CHANGED: + param_len += sizeof(data->uid_counter); + break; + case BT_AVRCP_EVT_VOLUME_CHANGED: + param_len += sizeof(data->absolute_volume); + break; + case BT_AVRCP_EVT_TRACK_REACHED_END: + case BT_AVRCP_EVT_TRACK_REACHED_START: + case BT_AVRCP_EVT_AVAILABLE_PLAYERS_CHANGED: + case BT_AVRCP_EVT_NOW_PLAYING_CONTENT_CHANGED: + break; + default: + return -EINVAL; + } + + if (net_buf_tailroom(buf) < param_len) + { + LOG_ERR("Not enough space in net_buf"); + return -ENOMEM; + } + + net_buf_add_u8(buf, event_id); + switch (event_id) + { + case BT_AVRCP_EVT_PLAYBACK_STATUS_CHANGED: + if (data->play_status > BT_AVRCP_PLAYBACK_STATUS_REV_SEEK && + data->play_status != BT_AVRCP_PLAYBACK_STATUS_ERROR) + { + LOG_ERR("Invalid playback status: %d", data->play_status); + return -EINVAL; + } + + net_buf_add_u8(buf, data->play_status); + break; + case BT_AVRCP_EVT_TRACK_CHANGED: + { + uint64_t identifier = sys_get_be64(data->identifier); + + net_buf_add_be64(buf, identifier); + break; + } + + case BT_AVRCP_EVT_PLAYBACK_POS_CHANGED: + net_buf_add_be32(buf, data->playback_pos); + break; + case BT_AVRCP_EVT_BATT_STATUS_CHANGED: + if (data->battery_status > BT_AVRCP_BATTERY_STATUS_FULL) + { + LOG_ERR("Invalid battery status: %d", data->battery_status); + return -EINVAL; + } + + net_buf_add_u8(buf, data->battery_status); + break; + case BT_AVRCP_EVT_SYSTEM_STATUS_CHANGED: + if (data->system_status > BT_AVRCP_SYSTEM_STATUS_UNPLUGGED) + { + LOG_ERR("Invalid system status: %d", data->system_status); + return -EINVAL; + } + + net_buf_add_u8(buf, data->system_status); + break; + case BT_AVRCP_EVT_PLAYER_APP_SETTING_CHANGED: + net_buf_add_u8(buf, data->setting_changed.num_of_attr); + net_buf_add_mem(buf, data->setting_changed.attr_vals, + data->setting_changed.num_of_attr * + sizeof(struct bt_avrcp_app_setting_attr_val)); + break; + case BT_AVRCP_EVT_ADDRESSED_PLAYER_CHANGED: + net_buf_add_be16(buf, data->addressed_player_changed.player_id); + net_buf_add_be16(buf, data->addressed_player_changed.uid_counter); + break; + case BT_AVRCP_EVT_UIDS_CHANGED: + net_buf_add_be16(buf, data->uid_counter); + break; + case BT_AVRCP_EVT_VOLUME_CHANGED: + if (data->absolute_volume > BT_AVRCP_MAX_ABSOLUTE_VOLUME) + { + LOG_ERR("Invalid absolute volume: %d", data->absolute_volume); + return -EINVAL; + } + + net_buf_add_u8(buf, data->absolute_volume); + break; + case BT_AVRCP_EVT_TRACK_REACHED_END: + case BT_AVRCP_EVT_TRACK_REACHED_START: + case BT_AVRCP_EVT_AVAILABLE_PLAYERS_CHANGED: + case BT_AVRCP_EVT_NOW_PLAYING_CONTENT_CHANGED: + break; + default: + return -EINVAL; + } + + return 0; +} + +int bt_avrcp_tg_notification( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, uint8_t event_id, + struct bt_avrcp_event_data *data) +{ + uint8_t rsp_code = BT_AVRCP_RSP_INTERIM; + struct net_buf *buf; + int err; + + if ((tg == NULL) || (tg->avrcp == NULL)) + { + return -EINVAL; + } + + if (!IS_TG_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + if (event_id >= ARRAY_SIZE(tg->tg_notify)) + { + LOG_ERR("Invalid event_id"); + return -EINVAL; + } + + if (tg->tg_notify[event_id].registered == false) + { + LOG_ERR("Notification response failed: event not registered"); + return -ENOENT; + } + + switch (status) + { + case BT_AVRCP_STATUS_SUCCESS: + if (tg->tg_notify[event_id].interim_sent == false) + { + rsp_code = BT_AVRCP_RSP_INTERIM; + } + else + { + rsp_code = BT_AVRCP_RSP_CHANGED; + } + break; + case BT_AVRCP_STATUS_NOT_IMPLEMENTED: + if (tg->tg_notify[event_id].interim_sent == true) + { + LOG_ERR("Not support INTERIM has been responded"); + return -EINVAL; + } + + rsp_code = BT_AVRCP_RSP_NOT_IMPLEMENTED; + break; + case BT_AVRCP_STATUS_IN_TRANSITION: + LOG_ERR("Not support IN_TRANSITION"); + return -EINVAL; + + default: + rsp_code = BT_AVRCP_RSP_REJECTED; + break; + } + + buf = bt_avrcp_create_vendor_pdu(NULL); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } + + if (rsp_code == BT_AVRCP_RSP_REJECTED) + { + if (net_buf_tailroom(buf) < sizeof(status)) + { + LOG_ERR("Not enough space in net_buf"); + net_buf_unref(buf); + return -ENOMEM; + } + + net_buf_add_u8(buf, status); + } + else + { + err = build_notification_rsp_data(event_id, data, buf); + if (err < 0) + { + net_buf_unref(buf); + return err; + } + } + + err = bt_avrcp_tg_send_vendor_rsp(tg, tid, + BT_AVRCP_PDU_ID_REGISTER_NOTIFICATION, + rsp_code, buf); + if (err < 0) + { + LOG_ERR("Failed to send notification response (err: %d)", err); + net_buf_unref(buf); + return err; + } + + if (rsp_code == BT_AVRCP_RSP_INTERIM) + { + tg->tg_notify[event_id].interim_sent = true; + } + else + { + tg->tg_notify[event_id].registered = false; + tg->tg_notify[event_id].interim_sent = false; + } + + return err; +} + +int bt_avrcp_tg_send_passthrough_rsp( + struct bt_avrcp_tg *tg, uint8_t tid, bt_avrcp_rsp_t result, + struct net_buf *buf) +{ + struct bt_avrcp_header *avrcp_hdr; + int err; + + if ((tg == NULL) || (tg->avrcp == NULL) || (buf == NULL)) + { + return -EINVAL; + } + + if (!IS_TG_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + if (net_buf_headroom(buf) < sizeof(struct bt_avrcp_header)) + { + LOG_ERR("Not enough headroom in buffer for bt_avrcp_header"); + return -ENOMEM; + } + + avrcp_hdr = net_buf_push(buf, sizeof(struct bt_avrcp_header)); + memset(avrcp_hdr, 0, sizeof(struct bt_avrcp_header)); + + avrcp_set_passthrough_header(avrcp_hdr, result); + + err = avrcp_send(tg->avrcp, buf, BT_AVCTP_RESPONSE, tid); + if (err < 0) + { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + } + + return err; +} + +static int bt_avrcp_tg_send_vendor_dependent_rsp( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t pdu_id, uint8_t status, + struct net_buf *buf) +{ + uint8_t rsp_code; + struct net_buf *status_buf = NULL; + int err; + + if ((tg == NULL) || (tg->avrcp == NULL)) + { + return -EINVAL; + } + + if (!IS_TG_ROLE_SUPPORTED()) + { + return -ENOTSUP; + } + + err = bt_avrcp_status_to_rsp(pdu_id, status, &rsp_code); + if (err < 0) + { + return err; + } + + if (status != BT_AVRCP_STATUS_SUCCESS) + { + status_buf = bt_avrcp_create_vendor_pdu(NULL); + if (status_buf == NULL) + { + LOG_ERR("Failed to allocate status buffer"); + return -ENOBUFS; + } + } + else + { + DEBUGASSERT(buf != NULL); + } + + /* Set the REJECTED response with the status payload */ + + if (rsp_code == BT_AVRCP_RSP_REJECTED) + { + if (net_buf_tailroom(status_buf) < sizeof(status)) + { + LOG_ERR("Not enough space in status net_buf"); + net_buf_unref(status_buf); + return -ENOMEM; + } + + net_buf_add_u8(status_buf, status); + } + + if (status != BT_AVRCP_STATUS_SUCCESS) + { + err = bt_avrcp_tg_send_vendor_rsp(tg, tid, pdu_id, rsp_code, + status_buf); + if (err < 0) + { + LOG_ERR("Failed to send vendor status PDU (err: %d)", err); + net_buf_unref(status_buf); + return err; + } + + if (buf != NULL) + { + net_buf_unref(buf); + } + + return 0; + } + + /* SUCCESS response send caller-provided buffer */ + + uint8_t min_len = get_rsp_min_len_by_pdu(pdu_id); + + if (buf->len < min_len) + { + LOG_ERR("Buffer too small for PDU 0x%02X (len=%u, min=%u)", pdu_id, + buf->len, min_len); + return -EINVAL; + } + + err = bt_avrcp_tg_send_vendor_rsp(tg, tid, pdu_id, rsp_code, buf); + if (err < 0) + { + LOG_ERR("Failed to send vendor PDU (err: %d)", err); + } + + return err; +} + +int bt_avrcp_tg_get_caps(struct bt_avrcp_tg *tg, + uint8_t tid, uint8_t status, struct net_buf *buf) +{ + int err; + + err = bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, + BT_AVRCP_PDU_ID_GET_CAPS, + status, buf); + if (err < 0) + { + LOG_ERR("Failed to send Get Capabilities response (err: %d)", err); + } + + return err; +} + +int bt_avrcp_tg_list_player_app_setting_attrs( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, struct net_buf *buf) +{ + return bt_avrcp_tg_send_vendor_dependent_rsp( + tg, tid, BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_ATTRS, status, + buf); +} + +int bt_avrcp_tg_list_player_app_setting_vals( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, struct net_buf *buf) +{ + return bt_avrcp_tg_send_vendor_dependent_rsp( + tg, tid, BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_VALS, status, + buf); +} + +int bt_avrcp_tg_get_curr_player_app_setting_val( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, struct net_buf *buf) +{ + return bt_avrcp_tg_send_vendor_dependent_rsp( + tg, tid, BT_AVRCP_PDU_ID_GET_CURR_PLAYER_APP_SETTING_VAL, status, + buf); +} + +int bt_avrcp_tg_set_player_app_setting_val(struct bt_avrcp_tg *tg, + uint8_t tid, uint8_t status) +{ + struct net_buf *buf; + int err; + + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } + + err = bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, + BT_AVRCP_PDU_ID_SET_PLAYER_APP_SETTING_VAL, + status, buf); + if (err < 0) + { + LOG_ERR("Failed to send vendor dependent (err: %d)", err); + net_buf_unref(buf); + } + + return err; +} + +int bt_avrcp_tg_get_player_app_setting_attr_text( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, struct net_buf *buf) +{ + uint8_t pdu_id = BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_ATTR_TEXT; + + return bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, pdu_id, status, buf); +} + +int bt_avrcp_tg_get_player_app_setting_val_text( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, struct net_buf *buf) +{ + uint8_t pdu_id = BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_VAL_TEXT; + + return bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, pdu_id, status, buf); +} + +int bt_avrcp_tg_inform_displayable_char_set(struct bt_avrcp_tg *tg, + uint8_t tid, uint8_t status) +{ + struct net_buf *buf; + int err; + + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } + + err = bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, + BT_AVRCP_PDU_ID_INFORM_DISPLAYABLE_CHAR_SET, + status, buf); + if (err < 0) + { + LOG_ERR("Failed to send vendor dependent (err: %d)", err); + net_buf_unref(buf); + } + + return err; +} + +int bt_avrcp_tg_inform_batt_status_of_ct(struct bt_avrcp_tg *tg, + uint8_t tid, uint8_t status) +{ + struct net_buf *buf; + int err; + + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } + + err = bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, + BT_AVRCP_PDU_ID_INFORM_BATT_STATUS_OF_CT, + status, buf); + if (err < 0) + { + LOG_ERR("Failed to send vendor dependent (err: %d)", err); + net_buf_unref(buf); + } + + return err; +} + +int bt_avrcp_tg_absolute_volume( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, + uint8_t absolute_volume) +{ + struct net_buf *buf; + int err; + + if (absolute_volume > BT_AVRCP_MAX_ABSOLUTE_VOLUME) + { + return -EINVAL; + } + + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } + + if (net_buf_tailroom(buf) < sizeof(absolute_volume)) + { + LOG_ERR("Not enough space in net_buf"); + net_buf_unref(buf); + return -ENOMEM; + } + + net_buf_add_u8(buf, absolute_volume); + + err = bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, + BT_AVRCP_PDU_ID_SET_ABSOLUTE_VOLUME, + status, + buf); + if (err < 0) + { + LOG_ERR("Failed to absolute volume (err: %d)", err); + net_buf_unref(buf); + } + + return err; +} + +int bt_avrcp_tg_get_element_attrs( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, struct net_buf *buf) +{ + return bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, + BT_AVRCP_PDU_ID_GET_ELEMENT_ATTRS, + status, + buf); +} + +int bt_avrcp_tg_get_play_status( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, struct net_buf *buf) +{ + return bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, + BT_AVRCP_PDU_ID_GET_PLAY_STATUS, + status, + buf); +} + +int bt_avrcp_tg_set_addressed_player(struct bt_avrcp_tg *tg, + uint8_t tid, uint8_t status) +{ + struct net_buf *buf; + int err; + + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } + + if (status <= BT_AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED) + { + if (net_buf_tailroom(buf) < sizeof(status)) + { + LOG_ERR("Not enough space in net_buf"); + net_buf_unref(buf); + return -ENOMEM; + } + + net_buf_add_u8(buf, status); + } + + err = bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, + BT_AVRCP_PDU_ID_SET_ADDRESSED_PLAYER, status, + buf); + if (err < 0) + { + LOG_ERR("Failed to send vendor dependent (err: %d)", err); + net_buf_unref(buf); + } + + return err; +} + +int bt_avrcp_tg_play_item(struct bt_avrcp_tg *tg, + uint8_t tid, uint8_t status) +{ + struct net_buf *buf; + int err; + + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } + + if (status <= BT_AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED) + { + if (net_buf_tailroom(buf) < sizeof(status)) + { + LOG_ERR("Not enough space in net_buf"); + net_buf_unref(buf); + return -ENOMEM; + } + + net_buf_add_u8(buf, status); + } + + err = bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, + BT_AVRCP_PDU_ID_PLAY_ITEM, + status, buf); + if (err < 0) + { + LOG_ERR("Failed to send vendor dependent (err: %d)", err); + net_buf_unref(buf); + } + + return err; +} + +int bt_avrcp_tg_add_to_now_playing(struct bt_avrcp_tg *tg, + uint8_t tid, uint8_t status) +{ + struct net_buf *buf; + int err; + + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } + + if (status <= BT_AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED) + { + if (net_buf_tailroom(buf) < sizeof(status)) + { + LOG_ERR("Not enough space in net_buf"); + net_buf_unref(buf); + return -ENOMEM; + } + + net_buf_add_u8(buf, status); + } + + err = bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, + BT_AVRCP_PDU_ID_ADD_TO_NOW_PLAYING, status, + buf); + if (err < 0) + { + LOG_ERR("Failed to send vendor dependent (err: %d)", err); + net_buf_unref(buf); + } + + return err; +} + +struct bt_avrcp_tg *bt_avrcp_get_tg(struct bt_conn *conn, uint16_t psm) +{ + size_t index; + + ARG_UNUSED(psm); + + if (conn == NULL) + { + LOG_ERR("Invalid parameter"); + return NULL; + } + + index = (size_t)bt_conn_index(conn); + DEBUGASSERT(index < ARRAY_SIZE(bt_avrcp_tg_pool)); + + return &bt_avrcp_tg_pool[index]; +} + +struct bt_conn *bt_avrcp_ct_get_acl_conn(struct bt_avrcp_ct *ct) +{ + if (ct == NULL) + { + LOG_ERR("Invalid parameter"); + return NULL; + } + + if (ct->avrcp->acl_conn == NULL) + { + LOG_ERR("No connection"); + return NULL; + } + + return bt_conn_ref(ct->avrcp->acl_conn); +} diff --git a/libs/zblue/host/classic/avrcp_cover_art.c b/libs/zblue/host/classic/avrcp_cover_art.c new file mode 100644 index 0000000000000..8473188ec9f6e --- /dev/null +++ b/libs/zblue/host/classic/avrcp_cover_art.c @@ -0,0 +1,960 @@ +/**************************************************************************** + * libs/zblue/host/classic/avrcp_cover_art.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Audio Video Remote Control Profile + */ + +/* Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "avctp_internal.h" +#include "avrcp_internal.h" + +#define LOG_LEVEL CONFIG_BT_AVRCP_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_avrcp_cover_art); + +/** @brief AVRCP Cover Art service UUID */ +#define BT_AVRCP_COVER_ART_UUID \ + (const struct bt_uuid_128 *)BT_UUID_DECLARE_128( \ + BT_UUID_128_ENCODE(0x7163DD54, 0x4A7E, 0x11E2, 0xB47C, 0x0050C2490048)) + +static const struct bt_uuid_128 *cover_art_uuid = BT_AVRCP_COVER_ART_UUID; + +#if defined(CONFIG_BT_AVRCP_CT_COVER_ART) +static struct bt_avrcp_cover_art_ct avrcp_cover_art_ct[CONFIG_BT_MAX_CONN]; + +static struct bt_avrcp_cover_art_ct_cb *cover_art_ct_cb; + +#define COVER_ART_CT_FROM_BIP(_bip) CONTAINER_OF(_bip, struct bt_avrcp_cover_art_ct, bip); +#define COVER_ART_CT_FROM_CLIENT(_c) CONTAINER_OF(_c, struct bt_avrcp_cover_art_ct, client); +#endif /* CONFIG_BT_AVRCP_CT_COVER_ART */ +#if defined(CONFIG_BT_AVRCP_TG_COVER_ART) +static struct bt_avrcp_cover_art_tg avrcp_cover_art_tg[CONFIG_BT_MAX_CONN]; + +static struct bt_avrcp_cover_art_tg_cb *cover_art_tg_cb; + +#define COVER_ART_TG_FROM_BIP(_bip) CONTAINER_OF(_bip, struct bt_avrcp_cover_art_tg, bip); +#define COVER_ART_TG_FROM_SERVER(_s) CONTAINER_OF(_s, struct bt_avrcp_cover_art_tg, server); +#endif /* CONFIG_BT_AVRCP_TG_COVER_ART */ + +#if CONFIG_BT_AVRCP_TG_COVER_ART + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void tg_bip_connect( + struct bt_bip_server *server, uint8_t version, uint16_t mopl, + struct net_buf *buf) +{ + struct bt_avrcp_cover_art_tg *tg = COVER_ART_TG_FROM_SERVER(server); + + LOG_DBG("BIP server %p conn req, version %02x, mopl %04x", server, version, + mopl); + + if ((cover_art_tg_cb != NULL) && (cover_art_tg_cb->connect != NULL)) + { + cover_art_tg_cb->connect(tg, version, mopl, buf); + } +} + +static void tg_bip_disconnect(struct bt_bip_server *server, + struct net_buf *buf) +{ + struct bt_avrcp_cover_art_tg *tg = COVER_ART_TG_FROM_SERVER(server); + + LOG_DBG("BIP server %p disconn req", server); + + if ((cover_art_tg_cb != NULL) && (cover_art_tg_cb->disconnect != NULL)) + { + cover_art_tg_cb->disconnect(tg, buf); + } +} + +static void tg_bip_abort(struct bt_bip_server *server, struct net_buf *buf) +{ + struct bt_avrcp_cover_art_tg *tg = COVER_ART_TG_FROM_SERVER(server); + + LOG_DBG("BIP server %p abort req", server); + + if ((cover_art_tg_cb != NULL) && (cover_art_tg_cb->abort != NULL)) + { + cover_art_tg_cb->abort(tg, buf); + } +} + +static void tg_bip_get_image_properties( + struct bt_bip_server *server, bool final, struct net_buf *buf) +{ + struct bt_avrcp_cover_art_tg *tg = COVER_ART_TG_FROM_SERVER(server); + + LOG_DBG("BIP server %p get_image_properties req, final %s, data len %d", + server, + final ? "true" : "false", buf->len); + + if ((cover_art_tg_cb != NULL) + && (cover_art_tg_cb->get_image_properties != NULL)) + { + cover_art_tg_cb->get_image_properties(tg, final, buf); + } +} + +static void tg_bip_get_image(struct bt_bip_server *server, + bool final, struct net_buf *buf) +{ + struct bt_avrcp_cover_art_tg *tg = COVER_ART_TG_FROM_SERVER(server); + + LOG_DBG("BIP server %p get_image req, final %s, data len %d", server, + final ? "true" : "false", + buf->len); + + if ((cover_art_tg_cb != NULL) && (cover_art_tg_cb->get_image != NULL)) + { + cover_art_tg_cb->get_image(tg, final, buf); + } +} + +static void tg_bip_get_linked_thumbnail( + struct bt_bip_server *server, bool final, struct net_buf *buf) +{ + struct bt_avrcp_cover_art_tg *tg = COVER_ART_TG_FROM_SERVER(server); + + LOG_DBG("BIP server %p get_linked_thumbnail req, final %s, data len %d", + server, + final ? "true" : "false", buf->len); + + if ((cover_art_tg_cb != NULL) + && (cover_art_tg_cb->get_linked_thumbnail != NULL)) + { + cover_art_tg_cb->get_linked_thumbnail(tg, final, buf); + } +} + +static struct bt_bip_server_cb tg_bip_server_cb = +{ + .connect = tg_bip_connect, + .disconnect = tg_bip_disconnect, + .abort = tg_bip_abort, + .get_image_properties = tg_bip_get_image_properties, + .get_image = tg_bip_get_image, + .get_linked_thumbnail = tg_bip_get_linked_thumbnail, +}; + +static void tg_bip_transport_connected(struct bt_conn *conn, + struct bt_bip *bip) +{ + struct bt_avrcp_cover_art_tg *tg = COVER_ART_TG_FROM_BIP(bip); + int err; + + LOG_DBG("BIP %p transport connected on %p", bip, conn); + + tg->conn = bt_conn_ref(conn); + + err = bt_bip_primary_image_pull_server_register(&tg->bip, &tg->server, + cover_art_uuid, + &tg_bip_server_cb); + if (err != 0) + { + LOG_ERR("Failed to register BIP server: %d", err); + goto failed; + } + + if ((cover_art_tg_cb != NULL) && (cover_art_tg_cb->l2cap_connected != + NULL)) + { + cover_art_tg_cb->l2cap_connected(tg->tg, tg); + } + + return; + +failed: + err = bt_avrcp_cover_art_tg_l2cap_disconnect(tg); + if (err != 0) + { + LOG_ERR("Failed to send l2cap disconnect: %d", err); + } +} + +static void tg_bip_transport_disconnected(struct bt_bip *bip) +{ + struct bt_avrcp_cover_art_tg *tg = COVER_ART_TG_FROM_BIP(bip); + int err; + + LOG_DBG("BIP %p transport disconnected", bip); + + bt_conn_drop(&tg->conn); + + err = bt_bip_server_unregister(&tg->server); + if (err != 0) + { + LOG_ERR("Failed to unregister BIP server: %d", err); + } + + if ((cover_art_tg_cb != NULL) && + (cover_art_tg_cb->l2cap_disconnected != NULL)) + { + cover_art_tg_cb->l2cap_disconnected(tg); + } +} + +static struct bt_bip_transport_ops tg_bip_transport_ops = +{ + .connected = tg_bip_transport_connected, + .disconnected = tg_bip_transport_disconnected, +}; + +static int bt_avrcp_tg_cover_art_accept( + struct bt_conn *conn, struct bt_bip_l2cap_server *server, + struct bt_bip **bip) +{ + uint8_t index = bt_conn_index(conn); + + DEBUGASSERT(index < ARRAY_SIZE(avrcp_cover_art_tg)); + + avrcp_cover_art_tg[index].tg = bt_avrcp_get_tg(conn, + server->server.l2cap.psm); + if (avrcp_cover_art_tg[index].tg == NULL) + { + LOG_ERR("Failed to get AVRCP target for connection"); + return -ENOTCONN; + } + + avrcp_cover_art_tg[index].bip.ops = &tg_bip_transport_ops; + *bip = &avrcp_cover_art_tg[index].bip; + + return 0; +} + +struct bt_bip_l2cap_server tg_cover_art_server = +{ + .server.l2cap.psm = 0, + .server.l2cap.sec_level = BT_SECURITY_L2, + .accept = bt_avrcp_tg_cover_art_accept, +}; + +int bt_avrcp_tg_cover_art_init(uint16_t *psm) +{ + int err; + + if (psm == NULL) + { + return -EINVAL; + } + + if (*psm != 0) + { + LOG_DBG("AVRCP Cover Art already initialized (PSM 0x%04x)", *psm); + return 0; + } + + tg_cover_art_server.server.l2cap.psm = 0; + err = bt_bip_l2cap_register(&tg_cover_art_server); + if (err < 0) + { + LOG_ERR("Failed to register AVRCP Cover Art BIP transport server %d", + err); + return err; + } + + LOG_DBG("AVRCP Cover Art PSM " + "0x%04x", tg_cover_art_server.server.l2cap.psm); + *psm = tg_cover_art_server.server.l2cap.psm; + + return 0; +} + +int bt_avrcp_cover_art_tg_cb_register(struct bt_avrcp_cover_art_tg_cb *cb) +{ + if (cb == NULL) + { + LOG_ERR("Invalid TG cb"); + return -EINVAL; + } + + if (cover_art_tg_cb != NULL) + { + LOG_ERR("TG cb has been registered"); + return -EBUSY; + } + + cover_art_tg_cb = cb; + + return 0; +} + +int bt_avrcp_cover_art_tg_l2cap_disconnect( + struct bt_avrcp_cover_art_tg *cover_art_tg) +{ + if (cover_art_tg == NULL) + { + LOG_ERR("Invalid cover art target"); + return -EINVAL; + } + + if (cover_art_tg->conn == NULL) + { + LOG_ERR("TG is not connected"); + return -ENOTCONN; + } + + return bt_bip_l2cap_disconnect(&cover_art_tg->bip); +} + +struct net_buf *bt_avrcp_cover_art_tg_create_pdu( + struct bt_avrcp_cover_art_tg *tg, struct net_buf_pool *pool) +{ + if (tg == NULL) + { + LOG_ERR("Invalid parameter"); + return NULL; + } + + return bt_goep_create_pdu(&tg->bip.goep, pool); +} + +int bt_avrcp_cover_art_tg_connect(struct bt_avrcp_cover_art_tg *tg, + uint8_t rsp_code) +{ + int err; + + if (tg == NULL) + { + LOG_ERR("Invalid cover art target"); + return -EINVAL; + } + + if (tg->conn == NULL) + { + LOG_ERR("TG is not connected"); + return -ENOTCONN; + } + + err = bt_bip_connect_rsp(&tg->server, rsp_code, NULL); + if (err != 0) + { + LOG_ERR("Failed to send OBEX connect rsp %d", err); + } + + return err; +} + +int bt_avrcp_cover_art_tg_disconnect(struct bt_avrcp_cover_art_tg *tg, + uint8_t rsp_code) +{ + int err; + + if (tg == NULL) + { + LOG_ERR("Invalid cover art target"); + return -EINVAL; + } + + if (tg->conn == NULL) + { + LOG_ERR("TG is not connected"); + return -ENOTCONN; + } + + err = bt_bip_disconnect_rsp(&tg->server, rsp_code, NULL); + if (err != 0) + { + LOG_ERR("Failed to send OBEX disconnect rsp %d", err); + } + + return err; +} + +int bt_avrcp_cover_art_tg_abort( + struct bt_avrcp_cover_art_tg *tg, uint8_t rsp_code, struct net_buf *buf) +{ + int err; + + if (tg == NULL) + { + LOG_ERR("Invalid cover art target"); + return -EINVAL; + } + + if (tg->conn == NULL) + { + LOG_ERR("TG is not connected"); + return -ENOTCONN; + } + + err = bt_bip_abort_rsp(&tg->server, rsp_code, buf); + if (err != 0) + { + LOG_ERR("Failed to send OBEX abort rsp %d", err); + } + + return err; +} + +int bt_avrcp_cover_art_tg_get_image_properties( + struct bt_avrcp_cover_art_tg *tg, uint8_t rsp_code, struct net_buf *buf) +{ + int err; + + if (tg == NULL) + { + LOG_ERR("Invalid cover art target"); + return -EINVAL; + } + + if (tg->conn == NULL) + { + LOG_ERR("TG is not connected"); + return -ENOTCONN; + } + + err = bt_bip_get_image_properties_rsp(&tg->server, rsp_code, buf); + if (err != 0) + { + LOG_ERR("Failed to send OBEX get image properties rsp %d", err); + } + + return err; +} + +int bt_avrcp_cover_art_tg_get_image( + struct bt_avrcp_cover_art_tg *tg, uint8_t rsp_code, struct net_buf *buf) +{ + int err; + + if (tg == NULL) + { + LOG_ERR("Invalid cover art target"); + return -EINVAL; + } + + if (tg->conn == NULL) + { + LOG_ERR("TG is not connected"); + return -ENOTCONN; + } + + err = bt_bip_get_image_rsp(&tg->server, rsp_code, buf); + if (err != 0) + { + LOG_ERR("Failed to send OBEX get image rsp %d", err); + } + + return err; +} + +int bt_avrcp_cover_art_tg_get_linked_thumbnail( + struct bt_avrcp_cover_art_tg *tg, uint8_t rsp_code, struct net_buf *buf) +{ + int err; + + if (tg == NULL) + { + LOG_ERR("Invalid cover art target"); + return -EINVAL; + } + + if (tg->conn == NULL) + { + LOG_ERR("TG is not connected"); + return -ENOTCONN; + } + + err = bt_bip_get_linked_thumbnail_rsp(&tg->server, rsp_code, buf); + if (err != 0) + { + LOG_ERR("Failed to send OBEX get linked thumbnail rsp %d", err); + } + + return err; +} +#endif /* CONFIG_BT_AVRCP_TG_COVER_ART */ + +#if defined(CONFIG_BT_AVRCP_CT_COVER_ART) +static void ct_bip_connect( + struct bt_bip_client *client, uint8_t rsp_code, uint8_t version, + uint16_t mopl, struct net_buf *buf) +{ + struct bt_avrcp_cover_art_ct *ct = COVER_ART_CT_FROM_CLIENT(client); + + LOG_DBG("BIP client %p conn rsp, rsp_code %s, version %02x, mopl %04x", + client, + bt_obex_rsp_code_to_str(rsp_code), version, mopl); + + if ((cover_art_ct_cb != NULL) && (cover_art_ct_cb->connect != NULL)) + { + cover_art_ct_cb->connect(ct, rsp_code, version, mopl, buf); + } +} + +static void ct_bip_disconnect(struct bt_bip_client *client, + uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_avrcp_cover_art_ct *ct = COVER_ART_CT_FROM_CLIENT(client); + + LOG_DBG("BIP client %p disconn rsp, rsp_code %s", client, + bt_obex_rsp_code_to_str(rsp_code)); + + if ((cover_art_ct_cb != NULL) && (cover_art_ct_cb->disconnect != NULL)) + { + cover_art_ct_cb->disconnect(ct, rsp_code, buf); + } +} + +static void ct_bip_abort(struct bt_bip_client *client, + uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_avrcp_cover_art_ct *ct = COVER_ART_CT_FROM_CLIENT(client); + + LOG_DBG("BIP client %p abort rsp, rsp_code %s", client, + bt_obex_rsp_code_to_str(rsp_code)); + + if ((cover_art_ct_cb != NULL) && (cover_art_ct_cb->abort != NULL)) + { + cover_art_ct_cb->abort(ct, rsp_code, buf); + } +} + +#if defined(CONFIG_BT_AVRCP_CT_COVER_ART_GET_IMAGE_PROPERTIES) +static void ct_bip_get_image_properties( + struct bt_bip_client *client, uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_avrcp_cover_art_ct *ct = COVER_ART_CT_FROM_CLIENT(client); + + LOG_DBG("BIP client %p get_image_properties rsp, rsp_code %s, data len %d", + client, + bt_obex_rsp_code_to_str(rsp_code), buf->len); + + if ((cover_art_ct_cb != NULL) + && (cover_art_ct_cb->get_image_properties != NULL)) + { + cover_art_ct_cb->get_image_properties(ct, rsp_code, buf); + } +} +#endif /* CONFIG_BT_AVRCP_CT_COVER_ART_GET_IMAGE_PROPERTIES */ + +#if defined(CONFIG_BT_AVRCP_CT_COVER_ART_GET_IMAGE) +static void ct_bip_get_image(struct bt_bip_client *client, + uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_avrcp_cover_art_ct *ct = COVER_ART_CT_FROM_CLIENT(client); + + LOG_DBG("BIP client %p get_image rsp, rsp_code %s, data len %d", client, + bt_obex_rsp_code_to_str(rsp_code), buf->len); + + if ((cover_art_ct_cb != NULL) && (cover_art_ct_cb->get_image != NULL)) + { + cover_art_ct_cb->get_image(ct, rsp_code, buf); + } +} +#endif /* CONFIG_BT_AVRCP_CT_COVER_ART_GET_IMAGE */ + +#if defined(CONFIG_BT_AVRCP_CT_COVER_ART_GET_LINKED_THUMBNAIL) +static void ct_bip_get_linked_thumbnail( + struct bt_bip_client *client, uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_avrcp_cover_art_ct *ct = COVER_ART_CT_FROM_CLIENT(client); + + LOG_DBG("BIP client %p get_linked_thumbnail rsp, rsp_code %s, data len %d", + client, + bt_obex_rsp_code_to_str(rsp_code), buf->len); + + if ((cover_art_ct_cb != NULL) + && (cover_art_ct_cb->get_linked_thumbnail != NULL)) + { + cover_art_ct_cb->get_linked_thumbnail(ct, rsp_code, buf); + } +} +#endif /* CONFIG_BT_AVRCP_CT_COVER_ART_GET_LINKED_THUMBNAIL */ + +static struct bt_bip_client_cb ct_bip_client_cb = +{ + .connect = ct_bip_connect, + .disconnect = ct_bip_disconnect, + .abort = ct_bip_abort, +#if defined(CONFIG_BT_AVRCP_CT_COVER_ART_GET_IMAGE_PROPERTIES) + .get_image_properties = ct_bip_get_image_properties, +#endif /* CONFIG_BT_AVRCP_CT_COVER_ART_GET_IMAGE_PROPERTIES */ +#if defined(CONFIG_BT_AVRCP_CT_COVER_ART_GET_IMAGE) + .get_image = ct_bip_get_image, +#endif /* CONFIG_BT_AVRCP_CT_COVER_ART_GET_IMAGE */ +#if defined(CONFIG_BT_AVRCP_CT_COVER_ART_GET_LINKED_THUMBNAIL) + .get_linked_thumbnail = ct_bip_get_linked_thumbnail, +#endif /* CONFIG_BT_AVRCP_CT_COVER_ART_GET_LINKED_THUMBNAIL */ +}; + +static void ct_bip_transport_connected(struct bt_conn *conn, + struct bt_bip *bip) +{ + struct bt_avrcp_cover_art_ct *ct = COVER_ART_CT_FROM_BIP(bip); + int err; + uint32_t functions; + + LOG_DBG("Cover Art CT %p transport connected on %p", ct, conn); + + ct->conn = bt_conn_ref(conn); + + err = bt_bip_set_supported_capabilities( + &ct->bip, BIT(BT_BIP_SUPP_CAP_GENERIC_IMAGE)); + if (err != 0) + { + LOG_ERR("Failed to set supported capabilities: %d", err); + goto failed; + } + + err = bt_bip_set_supported_features(&ct->bip, + BIT(BT_BIP_SUPP_FEAT_IMAGE_PULL)); + if (err != 0) + { + LOG_ERR("Failed to set supported features: %d", err); + goto failed; + } + + functions = + BIT(BT_BIP_SUPP_FUNC_GET_IMAGE_PROPERTIES) | + BIT(BT_BIP_SUPP_FUNC_GET_IMAGE) | + BIT(BT_BIP_SUPP_FUNC_GET_LINKED_THUMBNAIL); + err = bt_bip_set_supported_functions(&ct->bip, functions); + if (err != 0) + { + LOG_ERR("Failed to set supported functions: %d", err); + goto failed; + } + + if ((cover_art_ct_cb != NULL) && (cover_art_ct_cb->l2cap_connected != + NULL)) + { + cover_art_ct_cb->l2cap_connected(ct->ct, ct); + } + + return; + +failed: + err = bt_avrcp_cover_art_ct_l2cap_disconnect(ct); + if (err != 0) + { + LOG_ERR("Failed to send l2cap disconnect: %d", err); + } +} + +static void ct_bip_transport_disconnected(struct bt_bip *bip) +{ + struct bt_avrcp_cover_art_ct *ct = COVER_ART_CT_FROM_BIP(bip); + + LOG_DBG("Cover Art CT %p transport disconnected", ct); + + bt_conn_drop(&ct->conn); + + if ((cover_art_ct_cb != NULL) && + (cover_art_ct_cb->l2cap_disconnected != NULL)) + { + cover_art_ct_cb->l2cap_disconnected(ct); + } +} + +static struct bt_bip_transport_ops ct_bip_transport_ops = +{ + .connected = ct_bip_transport_connected, + .disconnected = ct_bip_transport_disconnected, +}; + +int bt_avrcp_cover_art_ct_cb_register(struct bt_avrcp_cover_art_ct_cb *cb) +{ + if (cb == NULL) + { + LOG_ERR("Invalid CT cb"); + return -EINVAL; + } + + if (cover_art_ct_cb != NULL) + { + LOG_ERR("CT cb has been registered"); + return -EBUSY; + } + + cover_art_ct_cb = cb; + + return 0; +} + +int bt_avrcp_cover_art_ct_l2cap_connect(struct bt_avrcp_ct *ct, + struct bt_avrcp_cover_art_ct **cover_art_ct, + uint16_t psm) +{ + uint8_t index; + int err; + struct bt_conn *conn; + + conn = bt_avrcp_ct_get_acl_conn(ct); + + if (conn == NULL) + { + LOG_ERR("No ACL connect"); + return -ENOTCONN; + } + + index = bt_conn_index(conn); + + if (index >= ARRAY_SIZE(avrcp_cover_art_ct)) + { + LOG_ERR("Conn index out of bounds"); + bt_conn_unref(conn); + return -EINVAL; + } + + avrcp_cover_art_ct[index].bip.ops = &ct_bip_transport_ops; + + err = bt_bip_l2cap_connect(conn, &avrcp_cover_art_ct[index].bip, psm); + bt_conn_unref(conn); + if (err != 0) + { + LOG_ERR("Failed to connect AVRCP Cover Art L2CAP channel (err %d)", + err); + return err; + } + + if (cover_art_ct != NULL) + { + *cover_art_ct = &avrcp_cover_art_ct[index]; + } + + return 0; +} + +int bt_avrcp_cover_art_ct_l2cap_disconnect(struct bt_avrcp_cover_art_ct *ct) +{ + if (ct == NULL) + { + LOG_ERR("Invalid cover art controller"); + return -EINVAL; + } + + if (ct->conn == NULL) + { + LOG_ERR("CT is not connected"); + return -ENOTCONN; + } + + return bt_bip_l2cap_disconnect(&ct->bip); +} + +struct net_buf *bt_avrcp_cover_art_ct_create_pdu( + struct bt_avrcp_cover_art_ct *ct, struct net_buf_pool *pool) +{ + if (ct == NULL) + { + LOG_ERR("Invalid parameter"); + return NULL; + } + + return bt_goep_create_pdu(&ct->bip.goep, pool); +} + +int bt_avrcp_cover_art_ct_connect(struct bt_avrcp_cover_art_ct *ct) +{ + int err; + struct net_buf *buf; + uint8_t val[BT_UUID_SIZE_128]; + + if (ct == NULL) + { + LOG_ERR("Invalid cover art controller"); + return -EINVAL; + } + + if (ct->conn == NULL) + { + LOG_ERR("CT is not connected"); + return -ENOTCONN; + } + + buf = bt_avrcp_cover_art_ct_create_pdu(ct, NULL); + if (buf == NULL) + { + return -ENOBUFS; + } + + sys_memcpy_swap(val, cover_art_uuid->val, sizeof(val)); + err = bt_obex_add_header_target(buf, sizeof(val), val); + if (err != 0) + { + LOG_ERR("Failed to add target header: %d", err); + goto cleanup; + } + + err = bt_bip_primary_image_pull_client_connect(&ct->bip, &ct->client, + &ct_bip_client_cb, buf); + if (err != 0) + { + LOG_ERR("Failed to send OBEX connect %d", err); + goto cleanup; + } + + return 0; + +cleanup: + net_buf_unref(buf); + return err; +} + +int bt_avrcp_cover_art_ct_disconnect(struct bt_avrcp_cover_art_ct *ct) +{ + int err; + + if (ct == NULL) + { + LOG_ERR("Invalid cover art controller"); + return -EINVAL; + } + + if (ct->conn == NULL) + { + LOG_ERR("CT is not connected"); + return -ENOTCONN; + } + + err = bt_bip_disconnect(&ct->client, NULL); + if (err != 0) + { + LOG_ERR("Failed to send OBEX disconnect %d", err); + } + + return err; +} + +int bt_avrcp_cover_art_ct_abort(struct bt_avrcp_cover_art_ct *ct, + struct net_buf *buf) +{ + int err; + + if (ct == NULL) + { + LOG_ERR("Invalid cover art controller"); + return -EINVAL; + } + + if (ct->conn == NULL) + { + LOG_ERR("CT is not connected"); + return -ENOTCONN; + } + + err = bt_bip_abort(&ct->client, buf); + if (err != 0) + { + LOG_ERR("Failed to send OBEX abort %d", err); + } + + return err; +} + +#if defined(CONFIG_BT_AVRCP_CT_COVER_ART_GET_IMAGE_PROPERTIES) +int bt_avrcp_cover_art_ct_get_image_properties( + struct bt_avrcp_cover_art_ct *ct, bool final, struct net_buf *buf) +{ + int err; + + if (ct == NULL) + { + LOG_ERR("Invalid cover art controller"); + return -EINVAL; + } + + if (ct->conn == NULL) + { + LOG_ERR("CT is not connected"); + return -ENOTCONN; + } + + err = bt_bip_get_image_properties(&ct->client, final, buf); + if (err != 0) + { + LOG_ERR("Failed to send OBEX get image properties %d", err); + } + + return err; +} +#endif /* CONFIG_BT_AVRCP_CT_COVER_ART_GET_IMAGE_PROPERTIES */ + +#if defined(CONFIG_BT_AVRCP_CT_COVER_ART_GET_IMAGE) +int bt_avrcp_cover_art_ct_get_image( + struct bt_avrcp_cover_art_ct *ct, bool final, struct net_buf *buf) +{ + int err; + + if (ct == NULL) + { + LOG_ERR("Invalid cover art controller"); + return -EINVAL; + } + + if (ct->conn == NULL) + { + LOG_ERR("CT is not connected"); + return -ENOTCONN; + } + + err = bt_bip_get_image(&ct->client, final, buf); + if (err != 0) + { + LOG_ERR("Failed to send OBEX get image %d", err); + } + + return err; +} +#endif /* CONFIG_BT_AVRCP_CT_COVER_ART_GET_IMAGE */ + +#if defined(CONFIG_BT_AVRCP_CT_COVER_ART_GET_LINKED_THUMBNAIL) +int bt_avrcp_cover_art_ct_get_linked_thumbnail( + struct bt_avrcp_cover_art_ct *ct, bool final, struct net_buf *buf) +{ + int err; + + if (ct == NULL) + { + LOG_ERR("Invalid cover art controller"); + return -EINVAL; + } + + if (ct->conn == NULL) + { + LOG_ERR("CT is not connected"); + return -ENOTCONN; + } + + err = bt_bip_get_linked_thumbnail(&ct->client, final, buf); + if (err != 0) + { + LOG_ERR("Failed to send OBEX get linked thumbnail %d", err); + } + + return err; +} +#endif /* CONFIG_BT_AVRCP_CT_COVER_ART_GET_LINKED_THUMBNAIL */ +#endif /* CONFIG_BT_AVRCP_CT_COVER_ART */ diff --git a/libs/zblue/host/classic/avrcp_internal.h b/libs/zblue/host/classic/avrcp_internal.h new file mode 100644 index 0000000000000..0e9f5578c3271 --- /dev/null +++ b/libs/zblue/host/classic/avrcp_internal.h @@ -0,0 +1,324 @@ +/**************************************************************************** + * libs/zblue/host/classic/avrcp_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Audio Video Remote Control Profile internal header. + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * Copyright (C) 2024 Xiaomi Corporation Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#define AVCTP_VER_1_4 (0x0104u) +#define AVRCP_VER_1_6 (0x0106u) + +#define AVRCP_TG_CAT_1 BIT(0) /* Player/Recorder */ +#define AVRCP_TG_CAT_2 BIT(1) /* Monitor/Amplifier */ +#define AVRCP_TG_CAT_3 BIT(2) /* Tuner */ +#define AVRCP_TG_CAT_4 BIT(3) /* Menu */ +#define AVRCP_TG_PLAYER_APPLICATION_SETTINGS BIT(4) /* Bit 0 must also be set */ +#define AVRCP_TG_GROUP_NAVIGATION BIT(5) /* Bit 0 must also be set */ +#define AVRCP_TG_BROWSING_SUPPORT BIT(6) +#define AVRCP_TG_MULTIPLE_MEDIA_PLAYERS BIT(7) +#define AVRCP_TG_COVER_ART_SUPPORT BIT(8) + +#define AVRCP_CT_CAT_1 BIT(0) /* Player/Recorder */ +#define AVRCP_CT_CAT_2 BIT(1) /* Monitor/Amplifier */ +#define AVRCP_CT_CAT_3 BIT(2) /* Tuner */ +#define AVRCP_CT_CAT_4 BIT(3) /* Menu */ +#define AVRCP_CT_BROWSING_SUPPORT BIT(6) +#define AVRCP_CT_COVER_ART_GET_IMAGE_PROPERTIES BIT(7) /* Cover Art GetImageProperties */ +#define AVRCP_CT_COVER_ART_GET_IMAGE BIT(8) /* Cover Art GetImage */ +#define AVRCP_CT_COVER_ART_GET_LINKED_THUMBNAIL BIT(9) /* Cover Art GetLinkedThumbnail */ + +#define AVRCP_SUBUNIT_PAGE (0) /* Fixed value according to AVRCP */ +#define AVRCP_SUBUNIT_EXTENSION_CODE (7) /* Fixed value according to TA Document 2001012 */ +#define BT_AVRCP_UNIT_INFO_CMD_SIZE (5) +#define BT_AVRCP_UNIT_INFO_RSP_SIZE (5) +#define BT_AVRCP_SUBUNIT_INFO_RSP_SIZE (5) +#define BT_AVRCP_SUBUNIT_INFO_CMD_SIZE (5) +#define BT_AVRCP_FRAGMENT_SIZE (512) + +#define BT_L2CAP_PSM_AVRCP 0x0017 +#define BT_L2CAP_PSM_AVRCP_BROWSING 0x001b + +#define BT_AVRCP_HEADROOM \ + (sizeof(struct bt_l2cap_hdr) + sizeof(struct bt_avctp_header_start) + \ + sizeof(struct bt_avrcp_header)) + +#if defined(CONFIG_BT_AVRCP_BROWSING) +#define AVRCP_CT_BROWSING_ENABLE AVRCP_CT_BROWSING_SUPPORT +#define AVRCP_TG_BROWSING_ENABLE AVRCP_TG_BROWSING_SUPPORT +#else +#define AVRCP_CT_BROWSING_ENABLE 0 +#define AVRCP_TG_BROWSING_ENABLE 0 +#endif /* CONFIG_BT_AVRCP_BROWSING */ + +#if defined(CONFIG_BT_AVRCP_TG_COVER_ART) +#define AVRCP_TG_COVER_ART_ENABLE AVRCP_TG_COVER_ART_SUPPORT +#else +#define AVRCP_TG_COVER_ART_ENABLE 0 +#endif /* CONFIG_BT_AVRCP_TG_COVER_ART */ + +#if defined(CONFIG_BT_AVRCP_CT_COVER_ART_GET_IMAGE_PROPERTIES) +#define AVRCP_CT_COVER_ART_FEAT_GET_IMAGE_PROPERTIES_ENABLE AVRCP_CT_COVER_ART_GET_IMAGE_PROPERTIES +#else +#define AVRCP_CT_COVER_ART_FEAT_GET_IMAGE_PROPERTIES_ENABLE 0 +#endif /* CONFIG_BT_AVRCP_CT_COVER_ART_GET_IMAGE_PROPERTIES */ + +#if defined(CONFIG_BT_AVRCP_CT_COVER_ART_GET_IMAGE) +#define AVRCP_CT_COVER_ART_FEAT_GET_IMAGE_ENABLE AVRCP_CT_COVER_ART_GET_IMAGE +#else +#define AVRCP_CT_COVER_ART_FEAT_GET_IMAGE_ENABLE 0 +#endif /* CONFIG_BT_AVRCP_CT_COVER_ART_GET_IMAGE */ + +#if defined(CONFIG_BT_AVRCP_CT_COVER_ART_GET_LINKED_THUMBNAIL) +#define AVRCP_CT_COVER_ART_FEAT_GET_LINKED_THUMBNAIL_ENABLE AVRCP_CT_COVER_ART_GET_LINKED_THUMBNAIL +#else +#define AVRCP_CT_COVER_ART_FEAT_GET_LINKED_THUMBNAIL_ENABLE 0 +#endif /* CONFIG_BT_AVRCP_CT_COVER_ART_GET_LINKED_THUMBNAIL */ + +#define AVRCP_CT_COVER_ART_FEAT_ENABLE \ + (AVRCP_CT_COVER_ART_FEAT_GET_IMAGE_PROPERTIES_ENABLE | \ + AVRCP_CT_COVER_ART_FEAT_GET_IMAGE_ENABLE | AVRCP_CT_COVER_ART_FEAT_GET_LINKED_THUMBNAIL_ENABLE) + +typedef enum __packed +{ + BT_AVRCP_SUBUNIT_ID_ZERO = 0x0, + BT_AVRCP_SUBUNIT_ID_IGNORE = 0x7, +} bt_avrcp_subunit_id_t; + +typedef enum __packed +{ + BT_AVRCP_OPC_VENDOR_DEPENDENT = 0x0, + BT_AVRCP_OPC_UNIT_INFO = 0x30, + BT_AVRCP_OPC_SUBUNIT_INFO = 0x31, + BT_AVRCP_OPC_PASS_THROUGH = 0x7c, +} bt_avrcp_opcode_t; + +typedef enum __packed +{ + BT_AVRCP_PKT_TYPE_SINGLE = 0b00, + BT_AVRCP_PKT_TYPE_START = 0b01, + BT_AVRCP_PKT_TYPE_CONTINUE = 0b10, + BT_AVRCP_PKT_TYPE_END = 0b11, +} bt_avrcp_pkt_type_t; + +typedef enum __packed +{ + /** Capabilities */ + + BT_AVRCP_PDU_ID_GET_CAPS = 0x10, + + /** Player Application Settings */ + + BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_ATTRS = 0x11, + BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_VALS = 0x12, + BT_AVRCP_PDU_ID_GET_CURR_PLAYER_APP_SETTING_VAL = 0x13, + BT_AVRCP_PDU_ID_SET_PLAYER_APP_SETTING_VAL = 0x14, + BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_ATTR_TEXT = 0x15, + BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_VAL_TEXT = 0x16, + BT_AVRCP_PDU_ID_INFORM_DISPLAYABLE_CHAR_SET = 0x17, + BT_AVRCP_PDU_ID_INFORM_BATT_STATUS_OF_CT = 0x18, + + /** Metadata Attributes for Current Media Item */ + + BT_AVRCP_PDU_ID_GET_ELEMENT_ATTRS = 0x20, + + /** Notifications */ + + BT_AVRCP_PDU_ID_GET_PLAY_STATUS = 0x30, + BT_AVRCP_PDU_ID_REGISTER_NOTIFICATION = 0x31, + BT_AVRCP_PDU_ID_EVT_PLAYBACK_STATUS_CHANGED = 0x31, + BT_AVRCP_PDU_ID_EVT_TRACK_CHANGED = 0x31, + BT_AVRCP_PDU_ID_EVT_TRACK_REACHED_END = 0x31, + BT_AVRCP_PDU_ID_EVT_TRACK_REACHED_START = 0x31, + BT_AVRCP_PDU_ID_EVT_PLAYBACK_POS_CHANGED = 0x31, + BT_AVRCP_PDU_ID_EVT_BATT_STATUS_CHANGED = 0x31, + BT_AVRCP_PDU_ID_EVT_SYSTEM_STATUS_CHANGED = 0x31, + BT_AVRCP_PDU_ID_EVT_PLAYER_APP_SETTING_CHANGED = 0x31, + BT_AVRCP_PDU_ID_EVT_VOLUME_CHANGED = 0x31, + BT_AVRCP_PDU_ID_EVT_ADDRESSED_PLAYER_CHANGED = 0x31, + BT_AVRCP_PDU_ID_EVT_AVAILABLE_PLAYERS_CHANGED = 0x31, + BT_AVRCP_PDU_ID_EVT_UIDS_CHANGED = 0x31, + + /** Continuation */ + + BT_AVRCP_PDU_ID_REQ_CONTINUING_RSP = 0x40, + BT_AVRCP_PDU_ID_ABORT_CONTINUING_RSP = 0x41, + + /** Absolute Volume */ + + BT_AVRCP_PDU_ID_SET_ABSOLUTE_VOLUME = 0x50, + + /** Media Player Selection */ + + BT_AVRCP_PDU_ID_SET_ADDRESSED_PLAYER = 0x60, + + /** Browsing */ + + BT_AVRCP_PDU_ID_SET_BROWSED_PLAYER = 0x70, + BT_AVRCP_PDU_ID_GET_FOLDER_ITEMS = 0x71, + BT_AVRCP_PDU_ID_CHANGE_PATH = 0x72, + BT_AVRCP_PDU_ID_GET_ITEM_ATTRS = 0x73, + BT_AVRCP_PDU_ID_PLAY_ITEM = 0x74, + BT_AVRCP_PDU_ID_GET_TOTAL_NUMBER_OF_ITEMS = 0x75, + + /** Search */ + + BT_AVRCP_PDU_ID_SEARCH = 0x80, + + /** Now Playing */ + + BT_AVRCP_PDU_ID_ADD_TO_NOW_PLAYING = 0x90, + + /** Error Response */ + + BT_AVRCP_PDU_ID_GENERAL_REJECT = 0xa0, +} bt_avrcp_pdu_id_t; + +/* bt_avrcp_tg flags: the flags defined */ + +enum +{ + BT_AVRCP_TG_FLAG_TX_ONGOING, + BT_AVRCP_TG_FLAG_ABORT_CONTINUING, /* AVRCP TG abort continuing is pending */ + + /* Total number of flags - must be at the end of the enum */ + + BT_AVRCP_TG_NUM_FLAGS, +}; + +struct bt_avrcp_ct_frag_reassembly_ctx +{ + uint8_t tid; + uint8_t rsp; + uint8_t pdu_id; +}; + +struct bt_avrcp_tg_vd_rsp_tx +{ + struct bt_avrcp_tg *tg; + uint16_t sent_len; + uint8_t tid; + uint8_t pdu_id; + uint8_t rsp; + + atomic_define(flags, BT_AVRCP_TG_NUM_FLAGS); +}; + +struct bt_avrcp_ct_notify_registration +{ + uint8_t tid; + bool interim_received; + bt_avrcp_notify_changed_cb_t cb; +}; + +struct bt_avrcp_tg_notify_state +{ + bool registered; /* CT has an active registration for this event */ + bool interim_sent; /* we have already sent INTERIM */ +}; + +struct bt_avrcp_header +{ + uint8_t byte0; /**< [7:4]: RFA, [3:0]: Ctype */ + uint8_t byte1; /**< [7:3]: Subunit_type, [2:0]: Subunit_ID */ + uint8_t opcode; /**< Unit Info, Subunit Info, Vendor Dependent, or Pass Through */ +} __packed; + +struct bt_avrcp_avc_vendor_pdu +{ + uint8_t company_id[BT_AVRCP_COMPANY_ID_SIZE]; + uint8_t pdu_id; + uint8_t pkt_type; /**< [7:2]: Reserved, [1:0]: Packet Type */ + uint16_t param_len; + uint8_t param[]; +} __packed; + +struct bt_avrcp_brow_pdu +{ + uint8_t pdu_id; + uint16_t param_len; + uint8_t param[]; +} __packed; + +/** The 4-bit command type or the 4-bit response code. */ +#define BT_AVRCP_HDR_GET_CTYPE_OR_RSP(hdr) FIELD_GET(GENMASK(3, 0), ((hdr)->byte0)) +/** Taken together, the subunit_type and subunit_ID fields define the command + * recipient’s address within the target. These fields enable the target to + * determine whether the command is addressed to the target unit, or to a + * specific subunit within the target. The values in these fields remain + * unchanged in the response frame. + */ +#define BT_AVRCP_HDR_GET_SUBUNIT_ID(hdr) FIELD_GET(GENMASK(2, 0), ((hdr)->byte1)) +/** Taken together, the subunit_type and subunit_ID fields define the command + * recipient’s address within the target. These fields enable the target to + * determine whether the command is addressed to the target unit, or to a + * specific subunit within the target. The values in these fields remain + * unchanged in the response frame. + */ +#define BT_AVRCP_HDR_GET_SUBUNIT_TYPE(hdr) FIELD_GET(GENMASK(7, 3), ((hdr)->byte1)) + +/** The 4-bit command type or the 4-bit response code. */ +#define BT_AVRCP_HDR_SET_CTYPE_OR_RSP(hdr, ctype) \ + (hdr)->byte0 = (((hdr)->byte0) & ~GENMASK(3, 0)) | FIELD_PREP(GENMASK(3, 0), (ctype)) +/** Taken together, the subunit_type and subunit_ID fields define the command + * recipient’s address within the target. These fields enable the target to + * determine whether the command is addressed to the target unit, or to a + * specific subunit within the target. The values in these fields remain + * unchanged in the response frame. + */ +#define BT_AVRCP_HDR_SET_SUBUNIT_ID(hdr, subunit_id) \ + (hdr)->byte1 = (((hdr)->byte1) & ~GENMASK(2, 0)) | FIELD_PREP(GENMASK(2, 0), (subunit_id)) +/** Taken together, the subunit_type and subunit_ID fields define the command + * recipient’s address within the target. These fields enable the target to + * determine whether the command is addressed to the target unit, or to a + * specific subunit within the target. The values in these fields remain + * unchanged in the response frame. + */ +#define BT_AVRCP_HDR_SET_SUBUNIT_TYPE(hdr, subunit_type) \ + (hdr)->byte1 = (((hdr)->byte1) & ~GENMASK(7, 3)) | FIELD_PREP(GENMASK(7, 3), (subunit_type)) + +/** The Packet Type field qualifies each packet as either start (Packet + * Type=01), continue (Packet Type=10), or end packet (Packet Type=11). In + * the case of a non-fragmented message, this field (Packet Type=00) simply + * indicates that the message is sent in a single AV/C frame. + */ +#define BT_AVRCP_AVC_PDU_GET_PACKET_TYPE(pdu) FIELD_GET(GENMASK(1, 0), ((pdu)->pkt_type)) + +/** The Packet Type field qualifies each packet as either start (Packet + * Type=01), continue (Packet Type=10), or end packet (Packet Type=11). In + * the case of a non-fragmented message, this field (Packet Type=00) simply + * indicates that the message is sent in a single AV/C frame. + */ +#define BT_AVRCP_AVC_PDU_SET_PACKET_TYPE(pdu, packet_type) \ + (pdu)->pkt_type = FIELD_PREP(GENMASK(1, 0), (packet_type)) + +struct bt_avrcp_frame +{ + struct bt_avrcp_header hdr; + uint8_t data[]; +} __packed; + +void bt_avrcp_init(void); + +int bt_avrcp_tg_cover_art_init(uint16_t *psm); + +struct bt_avrcp_tg *bt_avrcp_get_tg(struct bt_conn *conn, uint16_t psm); + +struct bt_avrcp_ct; + +struct bt_conn *bt_avrcp_ct_get_acl_conn(struct bt_avrcp_ct *ct); diff --git a/libs/zblue/host/classic/bip.c b/libs/zblue/host/classic/bip.c new file mode 100644 index 0000000000000..2b0c0987c448f --- /dev/null +++ b/libs/zblue/host/classic/bip.c @@ -0,0 +1,2685 @@ +/**************************************************************************** + * libs/zblue/host/classic/bip.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright 2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#include +#include +#include "l2cap_br_internal.h" +#include "obex_internal.h" + +#define LOG_LEVEL CONFIG_BT_BIP_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_bip); + +typedef void (*bt_bip_server_cb_t)(struct bt_bip_server *server, + bool final, struct net_buf *buf); +typedef void( + *bt_bip_client_cb_t)(struct bt_bip_client *client, uint8_t rsp_code, struct + *net_buf *buf); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static bool is_bip_primary_connect(enum bt_bip_conn_type type) +{ + switch (type) + { + case BT_BIP_PRIM_CONN_TYPE_IMAGE_PUSH: + case BT_BIP_PRIM_CONN_TYPE_IMAGE_PULL: + case BT_BIP_PRIM_CONN_TYPE_ADVANCED_IMAGE_PRINTING: + case BT_BIP_PRIM_CONN_TYPE_AUTO_ARCHIVE: + case BT_BIP_PRIM_CONN_TYPE_REMOTE_CAMERA: + case BT_BIP_PRIM_CONN_TYPE_REMOTE_DISPLAY: + return true; + default: + break; + } + + return false; +} + +#define BIP_RFDCOMM_SERVER(server) CONTAINER_OF(server, struct bt_bip_rfcomm_server, server); + +static void bip_rfcomm_connected(struct bt_conn *conn, struct bt_goep *goep) +{ + struct bt_bip *bip = CONTAINER_OF(goep, struct bt_bip, goep); + + atomic_set(&bip->_transport_state, BT_BIP_TRANSPORT_STATE_CONNECTED); + + if (bip->ops != NULL && bip->ops->connected != NULL) + { + bip->ops->connected(conn, bip); + } +} + +static void bip_rfcomm_disconnected(struct bt_goep *goep) +{ + struct bt_bip *bip = CONTAINER_OF(goep, struct bt_bip, goep); + struct bt_bip_client *client; + struct bt_bip_client *cnext; + struct bt_bip_server *server; + struct bt_bip_server *snext; + + atomic_set(&bip->_transport_state, BT_BIP_TRANSPORT_STATE_DISCONNECTED); + + list_for_every_container_safe(&bip->_clients, client, cnext, _node) + { + if (is_bip_primary_connect(client->_type) && + client->_secondary_server != NULL && + client->_secondary_server->_bip != bip) + { + bt_bip_rfcomm_disconnect(client->_secondary_server->_bip); + } + + atomic_set(&client->_state, BT_BIP_STATE_DISCONNECTED); + } + + list_for_every_container_safe(&bip->_servers, server, snext, _node) + { + if (is_bip_primary_connect(server->_type) && + server->_secondary_client != NULL && + server->_secondary_client->_bip != bip) + { + bt_bip_rfcomm_disconnect(server->_secondary_client->_bip); + } + + atomic_set(&server->_state, BT_BIP_STATE_DISCONNECTED); + } + + list_initialize(&bip->_clients); + bip->role = BT_BIP_ROLE_UNKNOWN; + bip->_supp_caps = 0; + bip->_supp_feats = 0; + bip->_supp_funcs = 0; + + if (bip->ops != NULL && bip->ops->disconnected != NULL) + { + bip->ops->disconnected(bip); + } +} + +static struct bt_goep_transport_ops bip_rfcomm_ops = +{ + .connected = bip_rfcomm_connected, + .disconnected = bip_rfcomm_disconnected, +}; + +static int bip_rfcomm_accept( + struct bt_conn *conn, struct bt_goep_transport_rfcomm_server *server, + struct bt_goep **goep) +{ + struct bt_bip_rfcomm_server *bip_server = BIP_RFDCOMM_SERVER(server); + struct bt_bip *bip = NULL; + int err; + + if (bip_server->accept == NULL) + { + return -ENOTSUP; + } + + err = bip_server->accept(conn, bip_server, &bip); + if (err != 0) + { + LOG_WRN("Incoming connection rejected"); + return err; + } + + DEBUGASSERT(bip != NULL && bip->ops != NULL); + + bip->role = BT_BIP_ROLE_RESPONDER; + bip->goep.transport_ops = &bip_rfcomm_ops; + BT_GOEP_INIT_V1(&bip->goep, &bip->goep_transport.v1); + atomic_set(&bip->_transport_state, BT_BIP_TRANSPORT_STATE_CONNECTING); + + *goep = &bip->goep; + return 0; +} + +int bt_bip_rfcomm_register(struct bt_bip_rfcomm_server *server) +{ + if (server == NULL || server->accept == NULL) + { + return -EINVAL; + } + + server->server.accept = bip_rfcomm_accept; + + return bt_goep_transport_rfcomm_server_register(&server->server); +} + +int bt_bip_rfcomm_connect(struct bt_conn *conn, + struct bt_bip *bip, uint8_t channel) +{ + int err; + + if (conn == NULL || bip == NULL || channel == 0) + { + return -EINVAL; + } + + if (atomic_read( + &bip->_transport_state) != BT_BIP_TRANSPORT_STATE_DISCONNECTED) + { + return -EINPROGRESS; + } + + bip->role = BT_BIP_ROLE_INITIATOR; + bip->goep.transport_ops = &bip_rfcomm_ops; + BT_GOEP_INIT_V1(&bip->goep, &bip->goep_transport.v1); + atomic_set(&bip->_transport_state, BT_BIP_TRANSPORT_STATE_CONNECTING); + + err = bt_goep_transport_rfcomm_connect(conn, &bip->goep, channel); + if (err != 0) + { + atomic_set(&bip->_transport_state, + BT_BIP_TRANSPORT_STATE_DISCONNECTED); + } + + return err; +} + +int bt_bip_rfcomm_disconnect(struct bt_bip *bip) +{ + int err; + + if (bip == NULL) + { + return -EINVAL; + } + + if (atomic_read(&bip->_transport_state) != + BT_BIP_TRANSPORT_STATE_CONNECTED) + { + return -EINPROGRESS; + } + + atomic_set(&bip->_transport_state, BT_BIP_TRANSPORT_STATE_DISCONNECTING); + + err = bt_goep_transport_rfcomm_disconnect(&bip->goep); + if (err != 0) + { + atomic_set(&bip->_transport_state, BT_BIP_TRANSPORT_STATE_CONNECTED); + } + + return err; +} + +#define BIP_L2CAP_SERVER(server) CONTAINER_OF(server, struct bt_bip_l2cap_server, server); + +static void bip_l2cap_connected(struct bt_conn *conn, struct bt_goep *goep) +{ + struct bt_bip *bip = CONTAINER_OF(goep, struct bt_bip, goep); + + atomic_set(&bip->_transport_state, BT_BIP_TRANSPORT_STATE_CONNECTED); + + if (bip->ops != NULL && bip->ops->connected != NULL) + { + bip->ops->connected(conn, bip); + } +} + +static void bip_l2cap_disconnected(struct bt_goep *goep) +{ + struct bt_bip *bip = CONTAINER_OF(goep, struct bt_bip, goep); + struct bt_bip_client *client; + struct bt_bip_client *cnext; + struct bt_bip_server *server; + struct bt_bip_server *snext; + + atomic_set(&bip->_transport_state, BT_BIP_TRANSPORT_STATE_DISCONNECTED); + + list_for_every_container_safe(&bip->_clients, client, cnext, _node) + { + if (is_bip_primary_connect(client->_type) && + client->_secondary_server != NULL && + client->_secondary_server->_bip != bip) + { + bt_bip_l2cap_disconnect(client->_secondary_server->_bip); + } + + atomic_set(&client->_state, BT_BIP_STATE_DISCONNECTED); + } + + list_for_every_container_safe(&bip->_servers, server, snext, _node) + { + if (is_bip_primary_connect(server->_type) && + server->_secondary_client != NULL && + server->_secondary_client->_bip != bip) + { + bt_bip_l2cap_disconnect(server->_secondary_client->_bip); + } + + atomic_set(&server->_state, BT_BIP_STATE_DISCONNECTED); + } + + list_initialize(&bip->_clients); + bip->role = BT_BIP_ROLE_UNKNOWN; + bip->_supp_caps = 0; + bip->_supp_feats = 0; + bip->_supp_funcs = 0; + + if (bip->ops != NULL && bip->ops->disconnected != NULL) + { + bip->ops->disconnected(bip); + } +} + +static struct bt_goep_transport_ops bip_l2cap_ops = +{ + .connected = bip_l2cap_connected, + .disconnected = bip_l2cap_disconnected, +}; + +static int bip_l2cap_accept( + struct bt_conn *conn, struct bt_goep_transport_l2cap_server *server, + struct bt_goep **goep) +{ + struct bt_bip_l2cap_server *bip_server = BIP_L2CAP_SERVER(server); + struct bt_bip *bip = NULL; + int err; + + if (bip_server->accept == NULL) + { + return -ENOTSUP; + } + + err = bip_server->accept(conn, bip_server, &bip); + if (err != 0) + { + LOG_WRN("Incoming connection rejected"); + return err; + } + + DEBUGASSERT(bip != NULL && bip->ops != NULL); + + bip->role = BT_BIP_ROLE_RESPONDER; + bip->goep.transport_ops = &bip_l2cap_ops; + BT_GOEP_INIT_V2(&bip->goep, &bip->goep_transport.v2); + atomic_set(&bip->_transport_state, BT_BIP_TRANSPORT_STATE_CONNECTING); + + *goep = &bip->goep; + return 0; +} + +int bt_bip_l2cap_register(struct bt_bip_l2cap_server *server) +{ + if (server == NULL || server->accept == NULL) + { + return -EINVAL; + } + + server->server.accept = bip_l2cap_accept; + + return bt_goep_transport_l2cap_server_register(&server->server); +} + +int bt_bip_l2cap_connect(struct bt_conn *conn, + struct bt_bip *bip, uint16_t psm) +{ + int err; + + if (conn == NULL || bip == NULL || psm == 0) + { + return -EINVAL; + } + + if (atomic_read( + &bip->_transport_state) != BT_BIP_TRANSPORT_STATE_DISCONNECTED) + { + return -EINPROGRESS; + } + + bip->role = BT_BIP_ROLE_INITIATOR; + bip->goep.transport_ops = &bip_l2cap_ops; + BT_GOEP_INIT_V2(&bip->goep, &bip->goep_transport.v2); + atomic_set(&bip->_transport_state, BT_BIP_TRANSPORT_STATE_CONNECTING); + + err = bt_goep_transport_l2cap_connect(conn, &bip->goep, psm); + if (err != 0) + { + atomic_set(&bip->_transport_state, + BT_BIP_TRANSPORT_STATE_DISCONNECTED); + } + + return err; +} + +int bt_bip_l2cap_disconnect(struct bt_bip *bip) +{ + int err; + + if (bip == NULL) + { + return -EINVAL; + } + + if (atomic_read(&bip->_transport_state) != + BT_BIP_TRANSPORT_STATE_CONNECTED) + { + return -EINPROGRESS; + } + + atomic_set(&bip->_transport_state, BT_BIP_TRANSPORT_STATE_DISCONNECTING); + + err = bt_goep_transport_l2cap_disconnect(&bip->goep); + if (err != 0) + { + atomic_set(&bip->_transport_state, BT_BIP_TRANSPORT_STATE_CONNECTED); + } + + return err; +} + +int bt_bip_set_supported_capabilities(struct bt_bip *bip, + uint8_t capabilities) +{ + if (bip == NULL) + { + return -EINVAL; + } + + if (bip->role != BT_BIP_ROLE_INITIATOR) + { + LOG_ERR("Invalid role %u", bip->role); + return -EINVAL; + } + + bip->_supp_caps = capabilities; + return 0; +} + +int bt_bip_set_supported_features(struct bt_bip *bip, uint16_t features) +{ + if (bip == NULL) + { + return -EINVAL; + } + + if (bip->role != BT_BIP_ROLE_INITIATOR) + { + LOG_ERR("Invalid role %u", bip->role); + return -EINVAL; + } + + bip->_supp_feats = features; + return 0; +} + +int bt_bip_set_supported_functions(struct bt_bip *bip, uint32_t functions) +{ + if (bip == NULL) + { + return -EINVAL; + } + + if (bip->role != BT_BIP_ROLE_INITIATOR) + { + LOG_ERR("Invalid role %u", bip->role); + return -EINVAL; + } + + bip->_supp_funcs = functions; + return 0; +} + +static const struct bt_uuid_128 *image_push = BT_BIP_UUID_IMAGE_PUSH; +static const struct bt_uuid_128 *image_pull = BT_BIP_UUID_IMAGE_PULL; +static const struct bt_uuid_128 *image_print = BT_BIP_UUID_IMAGE_PRINT; +static const struct bt_uuid_128 *auto_archive = BT_BIP_UUID_AUTO_ARCHIVE; +static const struct bt_uuid_128 *remote_camera = BT_BIP_UUID_REMOTE_CAMERA; +static const struct bt_uuid_128 *remote_display = BT_BIP_UUID_REMOTE_DISPLAY; +static const struct bt_uuid_128 *referenced_obj = BT_BIP_UUID_REFERENCED_OBJ; +static const struct bt_uuid_128 *archived_obj = BT_BIP_UUID_ARCHIVED_OBJ; + +static const struct bt_uuid_128 *btp_get_uuid_from_type( + enum bt_bip_conn_type type) +{ + switch (type) + { + case BT_BIP_PRIM_CONN_TYPE_IMAGE_PUSH: + return image_push; + case BT_BIP_PRIM_CONN_TYPE_IMAGE_PULL: + return image_pull; + case BT_BIP_PRIM_CONN_TYPE_ADVANCED_IMAGE_PRINTING: + return image_print; + case BT_BIP_PRIM_CONN_TYPE_AUTO_ARCHIVE: + return auto_archive; + case BT_BIP_PRIM_CONN_TYPE_REMOTE_CAMERA: + return remote_camera; + case BT_BIP_PRIM_CONN_TYPE_REMOTE_DISPLAY: + return remote_display; + case BT_BIP_2ND_CONN_TYPE_REFERENCED_OBJECTS: + return referenced_obj; + case BT_BIP_2ND_CONN_TYPE_ARCHIVED_OBJECTS: + return archived_obj; + } + + return NULL; +} + +static void bip_client_connect( + struct bt_obex_client *client, uint8_t rsp_code, uint8_t version, + uint16_t mopl, struct net_buf *buf) +{ + struct bt_bip_client *c = CONTAINER_OF(client, struct bt_bip_client, + _client); + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + atomic_set(&c->_state, BT_BIP_STATE_CONNECTED); + bt_obex_get_header_conn_id(buf, &c->_conn_id); + } + else + { + atomic_set(&c->_state, BT_BIP_STATE_DISCONNECTED); + list_find_and_remove(&c->_bip->_clients, &c->_node); + } + + if (c->_cb->connect != NULL) + { + c->_cb->connect(c, rsp_code, version, mopl, buf); + } +} + +static void bip_clinet_clear_pending_request(struct bt_bip_client *client) +{ + client->_rsp_cb = NULL; + client->_req_type = NULL; +} + +static void bip_client_disconnect( + struct bt_obex_client *client, uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_bip_client *c = CONTAINER_OF(client, struct bt_bip_client, + _client); + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + atomic_set(&c->_state, BT_BIP_STATE_DISCONNECTED); + + if (!is_bip_primary_connect(c->_type)) + { + if (c->_primary_server != NULL) + { + c->_primary_server->_secondary_client = NULL; + } + } + else + { + if (c->_secondary_server != NULL) + { + LOG_WRN("Secondary connect should be disconnected"); + + /* Consider removing transport */ + } + } + + list_find_and_remove(&c->_bip->_clients, &c->_node); + + bip_clinet_clear_pending_request(c); + } + else + { + atomic_set(&c->_state, BT_BIP_STATE_CONNECTED); + } + + if (c->_cb->disconnect != NULL) + { + c->_cb->disconnect(c, rsp_code, buf); + } +} + +static void bip_client_put(struct bt_obex_client *client, + uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_bip_client *c = CONTAINER_OF(client, struct bt_bip_client, + _client); + + if (c->_rsp_cb != NULL) + { + c->_rsp_cb(c, rsp_code, buf); + } + else + { + LOG_WRN("No cb for rsp"); + } + + if (rsp_code != BT_OBEX_RSP_CODE_CONTINUE) + { + bip_clinet_clear_pending_request(c); + } +} + +static void bip_client_get(struct bt_obex_client *client, + uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_bip_client *c = CONTAINER_OF(client, struct bt_bip_client, + _client); + + if (c->_rsp_cb != NULL) + { + c->_rsp_cb(c, rsp_code, buf); + } + else + { + LOG_WRN("No cb for rsp"); + } + + if (rsp_code != BT_OBEX_RSP_CODE_CONTINUE) + { + bip_clinet_clear_pending_request(c); + } +} + +static void bip_client_abort(struct bt_obex_client *client, + uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_bip_client *c = CONTAINER_OF(client, struct bt_bip_client, + _client); + int err; + + if (c->_cb->abort != NULL) + { + c->_cb->abort(c, rsp_code, buf); + } + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + bip_clinet_clear_pending_request(c); + return; + } + + err = bt_bip_disconnect(c, NULL); + if (err != 0) + { + LOG_ERR("Failed to send BIP disconnect"); + } +} + +static const struct bt_obex_client_ops bip_client_ops = +{ + .connect = bip_client_connect, + .disconnect = bip_client_disconnect, + .put = bip_client_put, + .get = bip_client_get, + .abort = bip_client_abort, + .setpath = NULL, + .action = NULL, +}; + +static void bip_server_connect( + struct bt_obex_server *server, uint8_t version, uint16_t mopl, + struct net_buf *buf) +{ + struct bt_bip_server *s = CONTAINER_OF(server, struct bt_bip_server, + _server); + + if (!is_bip_primary_connect(s->_type) && s->_primary_client != NULL && + atomic_read(&s->_primary_client->_state) != BT_BIP_STATE_CONNECTED) + { + LOG_ERR("Primary conn needs to be connected firstly"); + bt_bip_connect_rsp(s, BT_OBEX_RSP_CODE_NOT_ALLOW, NULL); + return; + } + + atomic_set(&s->_state, BT_BIP_STATE_CONNECTING); + + if (s->_cb->connect != NULL) + { + s->_cb->connect(s, version, mopl, buf); + } +} + +static void bip_server_disconnect(struct bt_obex_server *server, + struct net_buf *buf) +{ + struct bt_bip_server *s = CONTAINER_OF(server, struct bt_bip_server, + _server); + + if (is_bip_primary_connect(s->_type)) + { + if (s->_secondary_client != NULL) + { + bt_bip_disconnect(s->_secondary_client, NULL); + } + } + else + { + if (s->_primary_client != NULL) + { + s->_primary_client->_secondary_server = NULL; + } + } + + atomic_set(&s->_state, BT_BIP_STATE_DISCONNECTING); + + if (s->_cb->disconnect != NULL) + { + s->_cb->disconnect(s, buf); + } +} + +struct bip_required_hdr +{ + uint8_t count; + const uint8_t *hdrs; +}; + +#define BIP_REQUIRED_HDR(_count, _hdrs) \ + { \ + .count = (_count), \ + .hdrs = (const uint8_t *)(_hdrs), \ + } + +#define _BIP_REQUIRED_HDR_LIST(...) \ + BIP_REQUIRED_HDR(sizeof((uint8_t[]){__VA_ARGS__}), ((uint8_t[]){__VA_ARGS__})) + +#define BIP_REQUIRED_HDR_LIST(...) _BIP_REQUIRED_HDR_LIST(__VA_ARGS__) + +struct bip_function +{ + const char *type; + bool op_get; + uint8_t func_bit; + uint32_t supported_features; + uint32_t required_ap_tag_id; + struct bip_required_hdr hdr; + bt_bip_server_cb_t (*get_server_cb)(struct bt_bip_server *server); + bt_bip_client_cb_t (*get_client_cb)(struct bt_bip_client *client); +}; + +#define IMAGE_PUSH BIT(BT_BIP_PRIM_CONN_TYPE_IMAGE_PUSH) +#define IMAGE_PULL BIT(BT_BIP_PRIM_CONN_TYPE_IMAGE_PULL) +#define IMAGE_PRINT BIT(BT_BIP_PRIM_CONN_TYPE_ADVANCED_IMAGE_PRINTING) +#define AUTO_ARCHIVE BIT(BT_BIP_PRIM_CONN_TYPE_AUTO_ARCHIVE) +#define REMOTE_CAMERA BIT(BT_BIP_PRIM_CONN_TYPE_REMOTE_CAMERA) +#define REMOTE_DISPLAY BIT(BT_BIP_PRIM_CONN_TYPE_REMOTE_DISPLAY) +#define REFERENCED_OBJ BIT(BT_BIP_2ND_CONN_TYPE_REFERENCED_OBJECTS) +#define ARCHIVED_OBJ BIT(BT_BIP_2ND_CONN_TYPE_ARCHIVED_OBJECTS) + +#define AP_RETURNED_HANDLES BIT(BT_BIP_APPL_PARAM_TAG_ID_RETURNED_HANDLES) +#define AP_LIST_START_OFFSET BIT(BT_BIP_APPL_PARAM_TAG_ID_LIST_START_OFFSET) +#define AP_LATEST_CAPTURED_IMAGES BIT(BT_BIP_APPL_PARAM_TAG_ID_LATEST_CAPTURED_IMAGES) +#define AP_PARTIAL_FILE_LEN BIT(BT_BIP_APPL_PARAM_TAG_ID_PARTIAL_FILE_LEN) +#define AP_PARTIAL_FILE_START_OFFSET BIT(BT_BIP_APPL_PARAM_TAG_ID_PARTIAL_FILE_START_OFFSET) +#define AP_TOTAL_FILE_SIZE BIT(BT_BIP_APPL_PARAM_TAG_ID_TOTAL_FILE_SIZE) +#define AP_END_FLAG BIT(BT_BIP_APPL_PARAM_TAG_ID_END_FLAG) +#define AP_REMOTE_DISPLAY BIT(BT_BIP_APPL_PARAM_TAG_ID_REMOTE_DISPLAY) +#define AP_SERVICE_ID BIT(BT_BIP_APPL_PARAM_TAG_ID_SERVICE_ID) +#define AP_STORE_FLAG BIT(BT_BIP_APPL_PARAM_TAG_ID_STORE_FLAG) + +#define GET_CAPS_FUNC_BIT BT_BIP_SUPP_FUNC_GET_CAPS +#define GET_CAPS_SUPPORT_FEATS IMAGE_PUSH | IMAGE_PULL | IMAGE_PRINT | REMOTE_DISPLAY | ARCHIVED_OBJ +#define GET_CAPS_REQUIRED_HDR BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE +#define GET_CAPS_AP 0 + +#define GET_IMAGE_LIST_FUNC_BIT BT_BIP_SUPP_FUNC_GET_IMAGE_LIST +#define GET_IMAGE_LIST_SUPPORT_FEATS IMAGE_PULL | REMOTE_DISPLAY | ARCHIVED_OBJ +#define GET_IMAGE_LIST_REQUIRED_HDR \ + BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE, BT_OBEX_HEADER_ID_APP_PARAM, \ + BT_BIP_HEADER_ID_IMG_DESC +#define GET_IMAGE_LIST_AP AP_RETURNED_HANDLES | AP_LIST_START_OFFSET | AP_LATEST_CAPTURED_IMAGES + +#define GET_IMAGE_PROPERTIES_FUNC_BIT BT_BIP_SUPP_FUNC_GET_IMAGE_PROPERTIES +#define GET_IMAGE_PROPERTIES_SUPPORT_FEATS IMAGE_PULL | REMOTE_CAMERA | ARCHIVED_OBJ +#define GET_IMAGE_PROPERTIES_REQUIRED_HDR \ + BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE, BT_BIP_HEADER_ID_IMG_HANDLE +#define GET_IMAGE_PROPERTIES_AP 0 + +#define GET_IMAGE_FUNC_BIT BT_BIP_SUPP_FUNC_GET_IMAGE +#define GET_IMAGE_SUPPORT_FEATS IMAGE_PULL | REMOTE_CAMERA | ARCHIVED_OBJ +#define GET_IMAGE_REQUIRED_HDR \ + BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE, BT_BIP_HEADER_ID_IMG_DESC, \ + BT_BIP_HEADER_ID_IMG_HANDLE +#define GET_IMAGE_AP 0 + +#define GET_LINKED_THUMBNAIL_FUNC_BIT BT_BIP_SUPP_FUNC_GET_LINKED_THUMBNAIL +#define GET_LINKED_THUMBNAIL_SUPPORT_FEATS IMAGE_PULL | REMOTE_CAMERA | ARCHIVED_OBJ +#define GET_LINKED_THUMBNAIL_REQUIRED_HDR \ + BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE, BT_BIP_HEADER_ID_IMG_HANDLE +#define GET_LINKED_THUMBNAIL_AP 0 + +#define GET_LINKED_ATTACHMENT_FUNC_BIT BT_BIP_SUPP_FUNC_GET_LINKED_ATTACHMENT +#define GET_LINKED_ATTACHMENT_SUPPORT_FEATS IMAGE_PULL | ARCHIVED_OBJ +#define GET_LINKED_ATTACHMENT_REQUIRED_HDR \ + BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE, BT_BIP_HEADER_ID_IMG_HANDLE, \ + BT_OBEX_HEADER_ID_NAME +#define GET_LINKED_ATTACHMENT_AP 0 + +#define GET_PARTIAL_IMAGE_FUNC_BIT BT_BIP_SUPP_FUNC_GET_PARTIAL_IMAGE +#define GET_PARTIAL_IMAGE_SUPPORT_FEATS REFERENCED_OBJ +#define GET_PARTIAL_IMAGE_REQUIRED_HDR \ + BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE, BT_OBEX_HEADER_ID_NAME, \ + BT_OBEX_HEADER_ID_APP_PARAM +#define GET_PARTIAL_IMAGE_AP AP_PARTIAL_FILE_LEN | AP_PARTIAL_FILE_START_OFFSET + +#define GET_MONITORING_IMAGE_FUNC_BIT BT_BIP_SUPP_FUNC_GET_MONITORING_IMAGE +#define GET_MONITORING_IMAGE_SUPPORT_FEATS REMOTE_CAMERA +#define GET_MONITORING_IMAGE_REQUIRED_HDR \ + BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE, BT_OBEX_HEADER_ID_APP_PARAM +#define GET_MONITORING_IMAGE_AP AP_STORE_FLAG + +#define GET_STATUS_FUNC_BIT BT_BIP_SUPP_FUNC_GET_STATUS +#define GET_STATUS_SUPPORT_FEATS IMAGE_PRINT | AUTO_ARCHIVE +#define GET_STATUS_REQUIRED_HDR BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE +#define GET_STATUS_AP 0 + +#define PUT_IMAGE_FUNC_BIT BT_BIP_SUPP_FUNC_PUT_IMAGE +#define PUT_IMAGE_SUPPORT_FEATS IMAGE_PUSH | REMOTE_DISPLAY +#define PUT_IMAGE_REQUIRED_HDR \ + BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE, BT_OBEX_HEADER_ID_NAME, \ + BT_BIP_HEADER_ID_IMG_DESC +#define PUT_IMAGE_AP 0 + +#define PUT_LINKED_THUMBNAIL_FUNC_BIT BT_BIP_SUPP_FUNC_PUT_LINKED_THUMBNAIL +#define PUT_LINKED_THUMBNAIL_SUPPORT_FEATS IMAGE_PUSH | REMOTE_DISPLAY +#define PUT_LINKED_THUMBNAIL_REQUIRED_HDR \ + BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE, BT_BIP_HEADER_ID_IMG_HANDLE +#define PUT_LINKED_THUMBNAIL_AP 0 + +#define PUT_LINKED_ATTACHMENT_FUNC_BIT BT_BIP_SUPP_FUNC_PUT_LINKED_ATTACHMENT +#define PUT_LINKED_ATTACHMENT_SUPPORT_FEATS IMAGE_PUSH | REMOTE_DISPLAY +#define PUT_LINKED_ATTACHMENT_REQUIRED_HDR \ + BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE, BT_BIP_HEADER_ID_IMG_HANDLE +#define PUT_LINKED_ATTACHMENT_AP 0 + +#define REMOTE_DISPLAY_FUNC_BIT BT_BIP_SUPP_FUNC_REMOTE_DISPLAY +#define REMOTE_DISPLAY_SUPPORT_FEATS REMOTE_DISPLAY +#define REMOTE_DISPLAY_REQUIRED_HDR \ + BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE, BT_BIP_HEADER_ID_IMG_HANDLE, \ + BT_OBEX_HEADER_ID_APP_PARAM +#define REMOTE_DISPLAY_AP AP_REMOTE_DISPLAY + +#define DELETE_IMAGE_FUNC_BIT BT_BIP_SUPP_FUNC_DELETE_IMAGE +#define DELETE_IMAGE_SUPPORT_FEATS IMAGE_PULL | ARCHIVED_OBJ +#define DELETE_IMAGE_REQUIRED_HDR \ + BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE, BT_BIP_HEADER_ID_IMG_HANDLE +#define DELETE_IMAGE_AP 0 + +#define START_PRINT_FUNC_BIT BT_BIP_SUPP_FUNC_START_PRINT +#define START_PRINT_SUPPORT_FEATS IMAGE_PRINT +#define START_PRINT_REQUIRED_HDR \ + BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE, BT_OBEX_HEADER_ID_APP_PARAM +#define START_PRINT_AP AP_SERVICE_ID + +#define START_ARCHIVE_FUNC_BIT BT_BIP_SUPP_FUNC_START_ARCHIVE +#define START_ARCHIVE_SUPPORT_FEATS AUTO_ARCHIVE +#define START_ARCHIVE_REQUIRED_HDR \ + BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE, BT_OBEX_HEADER_ID_APP_PARAM +#define START_ARCHIVE_AP AP_SERVICE_ID + +static bt_bip_server_cb_t bip_server_get_cb_get_caps( + struct bt_bip_server *server) +{ + return server->_cb->get_caps; +} + +static bt_bip_server_cb_t bip_server_get_cb_get_image_list( + struct bt_bip_server *server) +{ + return server->_cb->get_image_list; +} + +static bt_bip_server_cb_t bip_server_get_cb_get_image_properties( + struct bt_bip_server *server) +{ + return server->_cb->get_image_properties; +} + +static bt_bip_server_cb_t bip_server_get_cb_get_image( + struct bt_bip_server *server) +{ + return server->_cb->get_image; +} + +static bt_bip_server_cb_t bip_server_get_cb_get_linked_thumbnail( + struct bt_bip_server *server) +{ + return server->_cb->get_linked_thumbnail; +} + +static bt_bip_server_cb_t bip_server_get_cb_get_linked_attachment( + struct bt_bip_server *server) +{ + return server->_cb->get_linked_attachment; +} + +static bt_bip_server_cb_t bip_server_get_cb_get_partial_image( + struct bt_bip_server *server) +{ + return server->_cb->get_partial_image; +} + +static bt_bip_server_cb_t bip_server_get_cb_get_monitoring_image( + struct bt_bip_server *server) +{ + return server->_cb->get_monitoring_image; +} + +static bt_bip_server_cb_t bip_server_get_cb_get_status( + struct bt_bip_server *server) +{ + return server->_cb->get_status; +} + +static bt_bip_server_cb_t bip_server_get_cb_put_image( + struct bt_bip_server *server) +{ + return server->_cb->put_image; +} + +static bt_bip_server_cb_t bip_server_get_cb_put_linked_thumbnail( + struct bt_bip_server *server) +{ + return server->_cb->put_linked_thumbnail; +} + +static bt_bip_server_cb_t bip_server_get_cb_put_linked_attachment( + struct bt_bip_server *server) +{ + return server->_cb->put_linked_attachment; +} + +static bt_bip_server_cb_t bip_server_get_cb_remote_display( + struct bt_bip_server *server) +{ + return server->_cb->remote_display; +} + +static bt_bip_server_cb_t bip_server_get_cb_delete_image( + struct bt_bip_server *server) +{ + return server->_cb->delete_image; +} + +static bt_bip_server_cb_t bip_server_get_cb_start_print( + struct bt_bip_server *server) +{ + return server->_cb->start_print; +} + +static bt_bip_server_cb_t bip_server_get_cb_start_archive( + struct bt_bip_server *server) +{ + return server->_cb->start_archive; +} + +static bt_bip_client_cb_t bip_client_get_cb_get_caps( + struct bt_bip_client *client) +{ + return client->_cb->get_caps; +} + +static bt_bip_client_cb_t bip_client_get_cb_get_image_list( + struct bt_bip_client *client) +{ + return client->_cb->get_image_list; +} + +static bt_bip_client_cb_t bip_client_get_cb_get_image_properties( + struct bt_bip_client *client) +{ + return client->_cb->get_image_properties; +} + +static bt_bip_client_cb_t bip_client_get_cb_get_image( + struct bt_bip_client *client) +{ + return client->_cb->get_image; +} + +static bt_bip_client_cb_t bip_client_get_cb_get_linked_thumbnail( + struct bt_bip_client *client) +{ + return client->_cb->get_linked_thumbnail; +} + +static bt_bip_client_cb_t bip_client_get_cb_get_linked_attachment( + struct bt_bip_client *client) +{ + return client->_cb->get_linked_attachment; +} + +static bt_bip_client_cb_t bip_client_get_cb_get_partial_image( + struct bt_bip_client *client) +{ + return client->_cb->get_partial_image; +} + +static bt_bip_client_cb_t bip_client_get_cb_get_monitoring_image( + struct bt_bip_client *client) +{ + return client->_cb->get_monitoring_image; +} + +static bt_bip_client_cb_t bip_client_get_cb_get_status( + struct bt_bip_client *client) +{ + return client->_cb->get_status; +} + +static bt_bip_client_cb_t bip_client_get_cb_put_image( + struct bt_bip_client *client) +{ + return client->_cb->put_image; +} + +static bt_bip_client_cb_t bip_client_get_cb_put_linked_thumbnail( + struct bt_bip_client *client) +{ + return client->_cb->put_linked_thumbnail; +} + +static bt_bip_client_cb_t bip_client_get_cb_put_linked_attachment( + struct bt_bip_client *client) +{ + return client->_cb->put_linked_attachment; +} + +static bt_bip_client_cb_t bip_client_get_cb_remote_display( + struct bt_bip_client *client) +{ + return client->_cb->remote_display; +} + +static bt_bip_client_cb_t bip_client_get_cb_delete_image( + struct bt_bip_client *client) +{ + return client->_cb->delete_image; +} + +static bt_bip_client_cb_t bip_client_get_cb_start_print( + struct bt_bip_client *client) +{ + return client->_cb->start_print; +} + +static bt_bip_client_cb_t bip_client_get_cb_start_archive( + struct bt_bip_client *client) +{ + return client->_cb->start_archive; +} + +static const struct bip_function bip_functions[] = +{ + { + BT_BIP_HDR_TYPE_GET_CAPS, true, GET_CAPS_FUNC_BIT, + GET_CAPS_SUPPORT_FEATS, GET_CAPS_AP, + BIP_REQUIRED_HDR_LIST(GET_CAPS_REQUIRED_HDR), + bip_server_get_cb_get_caps, + bip_client_get_cb_get_caps + }, + + { + BT_BIP_HDR_TYPE_GET_IMAGE_LIST, true, GET_IMAGE_LIST_FUNC_BIT, + GET_IMAGE_LIST_SUPPORT_FEATS, GET_IMAGE_LIST_AP, + BIP_REQUIRED_HDR_LIST(GET_IMAGE_LIST_REQUIRED_HDR), + bip_server_get_cb_get_image_list, + bip_client_get_cb_get_image_list + }, + + { + BT_BIP_HDR_TYPE_GET_IMAGE_PROPERTIES, true, + GET_IMAGE_PROPERTIES_FUNC_BIT, + GET_IMAGE_PROPERTIES_SUPPORT_FEATS, GET_IMAGE_PROPERTIES_AP, + BIP_REQUIRED_HDR_LIST(GET_IMAGE_PROPERTIES_REQUIRED_HDR), + bip_server_get_cb_get_image_properties, + bip_client_get_cb_get_image_properties + }, + + { + BT_BIP_HDR_TYPE_GET_IMAGE, true, GET_IMAGE_FUNC_BIT, + GET_IMAGE_SUPPORT_FEATS, GET_IMAGE_AP, + BIP_REQUIRED_HDR_LIST(GET_IMAGE_REQUIRED_HDR), + bip_server_get_cb_get_image, + bip_client_get_cb_get_image + }, + + { + BT_BIP_HDR_TYPE_GET_LINKED_THUMBNAIL, true, + GET_LINKED_THUMBNAIL_FUNC_BIT, + GET_LINKED_THUMBNAIL_SUPPORT_FEATS, GET_LINKED_THUMBNAIL_AP, + BIP_REQUIRED_HDR_LIST(GET_LINKED_THUMBNAIL_REQUIRED_HDR), + bip_server_get_cb_get_linked_thumbnail, + bip_client_get_cb_get_linked_thumbnail + }, + + { + BT_BIP_HDR_TYPE_GET_LINKED_ATTACHMENT, true, + GET_LINKED_ATTACHMENT_FUNC_BIT, + GET_LINKED_ATTACHMENT_SUPPORT_FEATS, GET_LINKED_ATTACHMENT_AP, + BIP_REQUIRED_HDR_LIST(GET_LINKED_ATTACHMENT_REQUIRED_HDR), + bip_server_get_cb_get_linked_attachment, + bip_client_get_cb_get_linked_attachment + }, + + { + BT_BIP_HDR_TYPE_GET_PARTIAL_IMAGE, true, GET_PARTIAL_IMAGE_FUNC_BIT, + GET_PARTIAL_IMAGE_SUPPORT_FEATS, GET_PARTIAL_IMAGE_AP, + BIP_REQUIRED_HDR_LIST(GET_PARTIAL_IMAGE_REQUIRED_HDR), + bip_server_get_cb_get_partial_image, + bip_client_get_cb_get_partial_image + }, + + { + BT_BIP_HDR_TYPE_GET_MONITORING_IMAGE, true, + GET_MONITORING_IMAGE_FUNC_BIT, + GET_MONITORING_IMAGE_SUPPORT_FEATS, GET_MONITORING_IMAGE_AP, + BIP_REQUIRED_HDR_LIST(GET_MONITORING_IMAGE_REQUIRED_HDR), + bip_server_get_cb_get_monitoring_image, + bip_client_get_cb_get_monitoring_image + }, + + { + BT_BIP_HDR_TYPE_GET_STATUS, true, GET_STATUS_FUNC_BIT, + GET_STATUS_SUPPORT_FEATS, + GET_STATUS_AP, BIP_REQUIRED_HDR_LIST(GET_STATUS_REQUIRED_HDR), + bip_server_get_cb_get_status, bip_client_get_cb_get_status + }, + + { + BT_BIP_HDR_TYPE_PUT_IMAGE, false, PUT_IMAGE_FUNC_BIT, + PUT_IMAGE_SUPPORT_FEATS, + PUT_IMAGE_AP, BIP_REQUIRED_HDR_LIST(PUT_IMAGE_REQUIRED_HDR), + bip_server_get_cb_put_image, + bip_client_get_cb_put_image + }, + + { + BT_BIP_HDR_TYPE_PUT_LINKED_THUMBNAIL, false, + PUT_LINKED_THUMBNAIL_FUNC_BIT, + PUT_LINKED_THUMBNAIL_SUPPORT_FEATS, PUT_LINKED_THUMBNAIL_AP, + BIP_REQUIRED_HDR_LIST(PUT_LINKED_THUMBNAIL_REQUIRED_HDR), + bip_server_get_cb_put_linked_thumbnail, + bip_client_get_cb_put_linked_thumbnail + }, + + { + BT_BIP_HDR_TYPE_PUT_LINKED_ATTACHMENT, false, + PUT_LINKED_ATTACHMENT_FUNC_BIT, + PUT_LINKED_ATTACHMENT_SUPPORT_FEATS, PUT_LINKED_ATTACHMENT_AP, + BIP_REQUIRED_HDR_LIST(PUT_LINKED_ATTACHMENT_REQUIRED_HDR), + bip_server_get_cb_put_linked_attachment, + bip_client_get_cb_put_linked_attachment + }, + + { + BT_BIP_HDR_TYPE_REMOTE_DISPLAY, false, REMOTE_DISPLAY_FUNC_BIT, + REMOTE_DISPLAY_SUPPORT_FEATS, REMOTE_DISPLAY_AP, + BIP_REQUIRED_HDR_LIST(REMOTE_DISPLAY_REQUIRED_HDR), + bip_server_get_cb_remote_display, + bip_client_get_cb_remote_display + }, + + { + BT_BIP_HDR_TYPE_DELETE_IMAGE, false, DELETE_IMAGE_FUNC_BIT, + DELETE_IMAGE_SUPPORT_FEATS, + DELETE_IMAGE_AP, BIP_REQUIRED_HDR_LIST(DELETE_IMAGE_REQUIRED_HDR), + bip_server_get_cb_delete_image, bip_client_get_cb_delete_image + }, + + { + BT_BIP_HDR_TYPE_START_PRINT, false, START_PRINT_FUNC_BIT, + START_PRINT_SUPPORT_FEATS, + START_PRINT_AP, BIP_REQUIRED_HDR_LIST(START_PRINT_REQUIRED_HDR), + bip_server_get_cb_start_print, bip_client_get_cb_start_print + }, + + { + BT_BIP_HDR_TYPE_START_ARCHIVE, false, START_ARCHIVE_FUNC_BIT, + START_ARCHIVE_SUPPORT_FEATS, + START_ARCHIVE_AP, BIP_REQUIRED_HDR_LIST(START_ARCHIVE_REQUIRED_HDR), + bip_server_get_cb_start_archive, bip_client_get_cb_start_archive + }, +}; + +static bool has_required_hdrs(struct net_buf *buf, + const struct bip_required_hdr *hdr) +{ + for (uint8_t index = 0; index < hdr->count; index++) + { + if (!bt_obex_has_header(buf, hdr->hdrs[index])) + { + return false; + } + } + + return true; +} + +static bool has_required_ap_tag_id(struct net_buf *buf, uint32_t tag_mask) +{ + while (tag_mask != 0) + { + uint8_t id = __builtin_ctz(tag_mask); + + if (!bt_obex_has_app_param(buf, id)) + { + return false; + } + + tag_mask &= ~(1U << id); + } + + return true; +} + +static enum bt_obex_rsp_code bip_server_get_req_cb( + struct bt_bip_server *server, struct net_buf *buf, bool is_get, + bt_bip_server_cb_t *cb) +{ + const uint8_t *type; + uint16_t len; + int err; + + err = bt_obex_get_header_type(buf, &len, &type); + if (err != 0) + { + LOG_WRN("Failed to get type header %d", err); + return BT_OBEX_RSP_CODE_BAD_REQ; + } + + if (len <= strlen(type)) + { + LOG_WRN("Invalid type string len %u <= %u", len, strlen(type)); + return BT_OBEX_RSP_CODE_BAD_REQ; + } + + *cb = NULL; + ARRAY_FOR_EACH(bip_functions, i) + { + if (server->_optype != NULL && bip_functions[i].type != server->_optype) + { + continue; + } + + if (strcmp(bip_functions[i].type, type) != 0) + { + continue; + } + + if (bip_functions[i].op_get != is_get) + { + continue; + } + + if ((bip_functions[i].supported_features & BIT(server->_type)) == 0) + { + continue; + } + + if (!has_required_hdrs(buf, &bip_functions[i].hdr)) + { + continue; + } + + if (!has_required_ap_tag_id(buf, bip_functions[i].required_ap_tag_id)) + { + continue; + } + + if (bip_functions[i].get_server_cb == NULL) + { + continue; + } + + *cb = bip_functions[i].get_server_cb(server); + if (*cb == NULL) + { + continue; + } + + server->_optype = bip_functions[i].type; + server->_opcode = is_get ? BT_OBEX_OPCODE_GET : BT_OBEX_OPCODE_PUT; + break; + } + + if (*cb == NULL) + { + LOG_WRN("Unsupported request"); + return BT_OBEX_RSP_CODE_NOT_IMPL; + } + + return BT_OBEX_RSP_CODE_SUCCESS; +} + +static void bip_server_put(struct bt_obex_server *server, + bool final, struct net_buf *buf) +{ + struct bt_bip_server *s = CONTAINER_OF(server, struct bt_bip_server, + _server); + int err; + enum bt_obex_rsp_code rsp_code; + + if (s->_optype == NULL || bt_obex_has_header(buf, BT_OBEX_HEADER_ID_TYPE)) + { + rsp_code = bip_server_get_req_cb(s, buf, false, &s->_req_cb); + if (rsp_code != BT_OBEX_RSP_CODE_SUCCESS) + { + LOG_WRN("Failed to parse req %u", (uint8_t)rsp_code); + goto failed; + } + } + + if (s->_optype == NULL || s->_opcode != BT_OBEX_OPCODE_PUT || + s->_req_cb == NULL) + { + rsp_code = BT_OBEX_RSP_CODE_NOT_IMPL; + LOG_WRN("Invalid request"); + goto failed; + } + + s->_req_cb(s, final, buf); + + return; + +failed: + s->_optype = NULL; + s->_opcode = 0; + s->_req_cb = NULL; + err = bt_obex_put_rsp(server, rsp_code, NULL); + if (err != 0) + { + LOG_ERR("Failed to send put rsp %d", err); + } +} + +static void bip_server_get(struct bt_obex_server *server, + bool final, struct net_buf *buf) +{ + struct bt_bip_server *s = CONTAINER_OF(server, struct bt_bip_server, + _server); + int err; + enum bt_obex_rsp_code rsp_code; + + if (s->_optype == NULL || bt_obex_has_header(buf, BT_OBEX_HEADER_ID_TYPE)) + { + rsp_code = bip_server_get_req_cb(s, buf, true, &s->_req_cb); + if (rsp_code != BT_OBEX_RSP_CODE_SUCCESS) + { + LOG_WRN("Failed to parse req %u", (uint8_t)rsp_code); + goto failed; + } + } + + if (s->_optype == NULL || s->_opcode != BT_OBEX_OPCODE_GET || + s->_req_cb == NULL) + { + rsp_code = BT_OBEX_RSP_CODE_NOT_IMPL; + LOG_WRN("Invalid request"); + goto failed; + } + + s->_req_cb(s, final, buf); + + return; + +failed: + s->_optype = NULL; + s->_opcode = 0; + s->_req_cb = NULL; + err = bt_obex_get_rsp(server, rsp_code, NULL); + if (err != 0) + { + LOG_ERR("Failed to send get rsp %d", err); + } +} + +static void bip_server_abort(struct bt_obex_server *server, + struct net_buf *buf) +{ + struct bt_bip_server *s = CONTAINER_OF(server, struct bt_bip_server, + _server); + int err; + + if (s->_cb->abort != NULL) + { + s->_cb->abort(s, buf); + return; + } + + err = bt_obex_abort_rsp(server, BT_OBEX_RSP_CODE_NOT_IMPL, NULL); + if (err != 0) + { + LOG_ERR("Failed to send abort rsp %d", err); + } +} + +static const struct bt_obex_server_ops bip_server_ops = +{ + .connect = bip_server_connect, + .disconnect = bip_server_disconnect, + .put = bip_server_put, + .get = bip_server_get, + .abort = bip_server_abort, + .setpath = NULL, + .action = NULL, +}; + +static uint32_t bip_get_connect_id(void) +{ + static uint32_t connect_id; + + connect_id++; + + return connect_id; +} + +static int bt_bip_server_register( + struct bt_bip *bip, struct bt_bip_server *server, + enum bt_bip_conn_type type, const struct bt_uuid_128 *uuid, + struct bt_bip_server_cb *cb, struct bt_bip_client *primary_client) +{ + struct bt_bip_server *s; + struct bt_bip_server *next; + int err; + + if (atomic_read(&server->_state) != BT_BIP_STATE_DISCONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&server->_state)); + return -EINVAL; + } + + if (is_bip_primary_connect(type)) + { + if (bip->role == BT_BIP_ROLE_INITIATOR) + { + LOG_ERR("Invalid role initiator"); + return -EINVAL; + } + + if (primary_client != NULL) + { + LOG_ERR("primary client should be NULL"); + return -EINVAL; + } + } + else + { + struct bt_bip *primary_bip; + + if (bip->role == BT_BIP_ROLE_RESPONDER) + { + LOG_ERR("Invalid role responder"); + return -EINVAL; + } + + if (primary_client == NULL || primary_client->_bip == NULL) + { + LOG_ERR("Invalid primary client"); + return -EINVAL; + } + + primary_bip = primary_client->_bip; + if (!list_find(&primary_bip->_clients, &primary_client->_node, NULL)) + { + LOG_ERR("Primary client %p is not found", primary_client); + return -EINVAL; + } + } + + if (list_find(&bip->_servers, &server->_node, NULL)) + { + LOG_ERR("Server %p has been registered", server); + return -EALREADY; + } + + if (uuid == NULL) + { + uuid = btp_get_uuid_from_type(type); + } + + DEBUGASSERT(uuid != NULL); + + list_for_every_container_safe(&bip->_servers, s, next, _node) + { + if (bt_uuid_cmp(&uuid->uuid, &s->_uuid->uuid) == 0) + { + LOG_ERR("UUID has been registered"); + return -EALREADY; + } + } + + server->_uuid = uuid; + server->_cb = cb; + server->_bip = bip; + server->_type = type; + server->_server.ops = &bip_server_ops; + server->_server.obex = &bip->goep.obex; + server->_secondary_client = primary_client; + server->_conn_id = bip_get_connect_id(); + err = bt_obex_server_register(&server->_server, server->_uuid); + if (err != 0) + { + return err; + } + + list_add_tail(&bip->_servers, &server->_node); + return 0; +} + +int bt_bip_primary_server_register( + struct bt_bip *bip, struct bt_bip_server *server, + enum bt_bip_conn_type type, const struct bt_uuid_128 *uuid, + struct bt_bip_server_cb *cb) +{ + return bt_bip_server_register(bip, server, type, uuid, cb, NULL); +} + +int bt_bip_secondary_server_register( + struct bt_bip *bip, struct bt_bip_server *server, + enum bt_bip_conn_type type, const struct bt_uuid_128 *uuid, + struct bt_bip_server_cb *cb, struct bt_bip_client *primary_client) +{ + return bt_bip_server_register(bip, server, type, uuid, cb, primary_client); +} + +int bt_bip_server_unregister(struct bt_bip_server *server) +{ + int err; + + if (server == NULL || server->_bip == NULL) + { + return -EINVAL; + } + + if (atomic_read(&server->_state) != BT_BIP_STATE_DISCONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&server->_state)); + return -EINVAL; + } + + if (!list_find(&server->_bip->_servers, &server->_node, NULL)) + { + LOG_ERR("Server %p has not found", server); + return -EALREADY; + } + + err = bt_obex_server_unregister(&server->_server); + if (err != 0) + { + LOG_ERR("Failed to unregister %d", err); + return err; + } + + list_find_and_remove(&server->_bip->_servers, &server->_node); + return 0; +} + +static int bip_check_conn_id(uint32_t id, struct net_buf *buf) +{ + uint32_t conn_id; + int err; + + err = bt_obex_get_header_conn_id(buf, &conn_id); + if (err != 0) + { + LOG_ERR("Failed to get conn id %d", err); + return err; + } + + if (conn_id != id) + { + LOG_ERR("Conn id is mismatched %u != %u", conn_id, id); + return -EINVAL; + } + + return 0; +} + +static int bip_check_who(const struct bt_uuid *uuid, struct net_buf *buf) +{ + uint16_t len; + const uint8_t *who; + union bt_obex_uuid obex_uuid; + int err; + + err = bt_obex_get_header_who(buf, &len, &who); + if (err != 0) + { + LOG_ERR("Failed to get who %d", err); + return err; + } + + err = bt_obex_make_uuid(&obex_uuid, who, len); + if (err != 0) + { + LOG_ERR("Invalid UUID of who %d", err); + return err; + } + + if (bt_uuid_cmp(uuid, &obex_uuid.uuid) != 0) + { + LOG_ERR("WHO is mismatched"); + return -EINVAL; + } + + return 0; +} + +#define BIP_FEAT_IMAGE_PUSH_BITS \ + (BIT(BT_BIP_SUPP_FEAT_IMAGE_PUSH) | BIT(BT_BIP_SUPP_FEAT_IMAGE_PUSH_STORE) | \ + BIT(BT_BIP_SUPP_FEAT_IMAGE_PUSH_PRINT) | BIT(BT_BIP_SUPP_FEAT_IMAGE_PUSH_DISPLAY)) + +#define BT_BIP_SUPP_FEAT_IMAGE_PRINT BT_BIP_SUPP_FEAT_ADVANCED_IMAGE_PRINT + +#define BIP_FEAT_IMAGE_PUSH(feature) ((feature) & BIP_FEAT_IMAGE_PUSH_BITS) != 0 +#define BIP_FEAT_IMAGE_PULL(feature) ((feature) & BIT(BT_BIP_SUPP_FEAT_IMAGE_PULL)) != 0 +#define BIP_FEAT_IMAGE_PRINT(feature) ((feature) & BIT(BT_BIP_SUPP_FEAT_IMAGE_PRINT)) != 0 +#define BIP_FEAT_AUTO_ARCHIVE(feature) ((feature) & BIT(BT_BIP_SUPP_FEAT_AUTO_ARCHIVE)) != 0 +#define BIP_FEAT_REMOTE_CAMERA(feature) ((feature) & BIT(BT_BIP_SUPP_FEAT_REMOTE_CAMERA)) != 0 +#define BIP_FEAT_REMOTE_DISPLAY(feature) ((feature) & BIT(BT_BIP_SUPP_FEAT_REMOTE_DISPLAY)) != 0 + +static int bip_check_features(struct bt_bip *bip, enum bt_bip_conn_type type) +{ + switch (type) + { + case BT_BIP_PRIM_CONN_TYPE_IMAGE_PUSH: + if (BIP_FEAT_IMAGE_PUSH(bip->_supp_feats)) + { + return 0; + } + break; + case BT_BIP_PRIM_CONN_TYPE_IMAGE_PULL: + if (BIP_FEAT_IMAGE_PULL(bip->_supp_feats)) + { + return 0; + } + break; + case BT_BIP_PRIM_CONN_TYPE_ADVANCED_IMAGE_PRINTING: + if (BIP_FEAT_IMAGE_PRINT(bip->_supp_feats)) + { + return 0; + } + break; + case BT_BIP_PRIM_CONN_TYPE_AUTO_ARCHIVE: + if (BIP_FEAT_AUTO_ARCHIVE(bip->_supp_feats)) + { + return 0; + } + break; + case BT_BIP_PRIM_CONN_TYPE_REMOTE_CAMERA: + if (BIP_FEAT_REMOTE_CAMERA(bip->_supp_feats)) + { + return 0; + } + break; + case BT_BIP_PRIM_CONN_TYPE_REMOTE_DISPLAY: + if (BIP_FEAT_REMOTE_DISPLAY(bip->_supp_feats)) + { + return 0; + } + break; + case BT_BIP_2ND_CONN_TYPE_REFERENCED_OBJECTS: + break; + case BT_BIP_2ND_CONN_TYPE_ARCHIVED_OBJECTS: + break; + } + + return -ENOTSUP; +} + +static int bt_bip_client_connect( + struct bt_bip *bip, struct bt_bip_client *client, + enum bt_bip_conn_type type, struct bt_bip_client_cb *cb, + struct net_buf *buf, struct bt_bip_server *primary_server) +{ + struct bt_bip_client *c; + struct bt_bip_client *next; + const struct bt_uuid_128 *uuid; + union bt_obex_uuid obex_uuid; + uint8_t val[BT_UUID_SIZE_128]; + int err; + bool allocated = false; + + if (atomic_read(&client->_state) != BT_BIP_STATE_DISCONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&client->_state)); + return -EINVAL; + } + + if (bip->role == BT_BIP_ROLE_RESPONDER) + { + LOG_ERR("Invalid role responder"); + return -EINVAL; + } + + if (is_bip_primary_connect(type)) + { + if (primary_server != NULL) + { + LOG_ERR("primary server should be NULL"); + return -EINVAL; + } + + err = bip_check_features(bip, type); + if (err != 0) + { + LOG_ERR("Unsupported features for connection type %d", type); + return err; + } + } + else + { + struct bt_bip *primary_bip; + + if (primary_server == NULL || primary_server->_bip == NULL) + { + LOG_ERR("Invalid primary client"); + return -EINVAL; + } + + if (type == BT_BIP_2ND_CONN_TYPE_REFERENCED_OBJECTS && + primary_server->_type != + BT_BIP_PRIM_CONN_TYPE_ADVANCED_IMAGE_PRINTING) + { + LOG_ERR("Invalid primary connection type for referenced objects"); + return -EINVAL; + } + + if (type == BT_BIP_2ND_CONN_TYPE_ARCHIVED_OBJECTS && + primary_server->_type != BT_BIP_PRIM_CONN_TYPE_AUTO_ARCHIVE) + { + LOG_ERR("Invalid primary connection type for referenced objects"); + return -EINVAL; + } + + if (atomic_read(&primary_server->_state) != BT_BIP_STATE_CONNECTED) + { + LOG_ERR("Invalid primary server state %u", + (uint8_t)atomic_read(&primary_server->_state)); + return -EINVAL; + } + + primary_bip = primary_server->_bip; + if (!list_find(&primary_bip->_servers, &primary_server->_node, NULL)) + { + LOG_ERR("Primary server %p is not found", primary_server); + return -EINVAL; + } + } + + if (list_find(&bip->_clients, &client->_node, NULL)) + { + LOG_ERR("Client %p is not idle", client); + return -EALREADY; + } + + if (!bt_obex_has_header(buf, BT_OBEX_HEADER_ID_TARGET)) + { + uuid = btp_get_uuid_from_type(type); + } + else + { + uint16_t len; + const uint8_t *target; + + err = bt_obex_get_header_target(buf, &len, &target); + if (err != 0) + { + LOG_ERR("Failed to get target %d", err); + return err; + } + + err = bt_obex_make_uuid(&obex_uuid, target, len); + if (err != 0) + { + LOG_ERR("Invalid UUID of target %d", err); + return err; + } + + uuid = &obex_uuid.u128; + } + + if (uuid == NULL) + { + LOG_ERR("Invalid UUID"); + return -EINVAL; + } + + list_for_every_container_safe(&bip->_clients, c, next, _node) + { + if (bt_uuid_cmp(&uuid->uuid, &c->_uuid.uuid) == 0) + { + LOG_ERR("UUID has been registered"); + return -EALREADY; + } + } + + if (buf == NULL) + { + buf = bt_goep_create_pdu(&bip->goep, NULL); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } + + allocated = true; + } + + memcpy(&client->_uuid, uuid, sizeof(client->_uuid)); + client->_cb = cb; + client->_bip = bip; + client->_type = type; + client->_client.ops = &bip_client_ops; + client->_client.obex = &bip->goep.obex; + client->_primary_server = primary_server; + + if (!bt_obex_has_header(buf, BT_OBEX_HEADER_ID_TARGET)) + { + sys_memcpy_swap(val, client->_uuid.val, sizeof(val)); + err = bt_obex_add_header_target(buf, sizeof(val), val); + if (err != 0) + { + LOG_ERR("Failed to add header target"); + goto failed; + } + } + + err = bt_obex_connect(&client->_client, bip->goep.obex.rx.mtu, buf); + if (err != 0) + { + LOG_ERR("Failed to send conn req"); + goto failed; + } + + atomic_set(&client->_state, BT_BIP_STATE_CONNECTING); + list_add_tail(&bip->_clients, &client->_node); + return 0; + +failed: + if (allocated) + { + net_buf_unref(buf); + } + + return err; +} + +int bt_bip_primary_client_connect( + struct bt_bip *bip, struct bt_bip_client *client, + enum bt_bip_conn_type type, struct bt_bip_client_cb *cb, + struct net_buf *buf) +{ + return bt_bip_client_connect(bip, client, type, cb, buf, NULL); +} + +int bt_bip_secondary_client_connect( + struct bt_bip *bip, struct bt_bip_client *client, + enum bt_bip_conn_type type, struct bt_bip_client_cb *cb, + struct net_buf *buf, struct bt_bip_server *primary_server) +{ + return bt_bip_client_connect(bip, client, type, cb, buf, primary_server); +} + +int bt_bip_connect_rsp(struct bt_bip_server *server, + uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t val[BT_UUID_SIZE_128]; + int err; + bool allocated = false; + + if (server == NULL || server->_bip == NULL) + { + return -EINVAL; + } + + if (atomic_read(&server->_state) != BT_BIP_STATE_CONNECTING) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&server->_state)); + return -EINVAL; + } + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS && + !is_bip_primary_connect(server->_type) && + server->_primary_client != NULL && + atomic_read(&server->_primary_client->_state) != + BT_BIP_STATE_CONNECTED) + { + LOG_ERR("Primary conn needs to be connected firstly"); + return -EINVAL; + } + + if (buf == NULL) + { + buf = bt_goep_create_pdu(&server->_bip->goep, NULL); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } + + allocated = true; + } + else + { + if (bt_obex_has_header(buf, BT_OBEX_HEADER_ID_WHO) && + server->_uuid != NULL) + { + err = bip_check_who(&server->_uuid->uuid, buf); + if (err != 0) + { + return err; + } + } + + if (bt_obex_has_header(buf, BT_OBEX_HEADER_ID_CONN_ID)) + { + err = bip_check_conn_id(server->_conn_id, buf); + if (err != 0) + { + return err; + } + } + } + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + if (!bt_obex_has_header(buf, BT_OBEX_HEADER_ID_WHO)) + { + sys_memcpy_swap(val, server->_uuid->val, sizeof(val)); + err = bt_obex_add_header_who(buf, sizeof(val), val); + if (err != 0) + { + LOG_ERR("Failed to add header target %d", err); + goto failed; + } + } + + if (!bt_obex_has_header(buf, BT_OBEX_HEADER_ID_CONN_ID)) + { + err = bt_obex_add_header_conn_id(buf, server->_conn_id); + if (err != 0) + { + LOG_ERR("Failed to add header conn id %d", err); + goto failed; + } + } + } + + err = bt_obex_connect_rsp(&server->_server, rsp_code, + server->_bip->goep.obex.rx.mtu, buf); + if (err != 0) + { + LOG_ERR("Failed to send conn rsp %d", err); + goto failed; + } + + atomic_set(&server->_state, BT_BIP_STATE_CONNECTED); + return 0; + +failed: + if (allocated) + { + net_buf_unref(buf); + } + + return err; +} + +int bt_bip_disconnect(struct bt_bip_client *client, struct net_buf *buf) +{ + int err; + bool allocated = false; + + if (client == NULL || client->_bip == NULL) + { + return -EINVAL; + } + + if (atomic_read(&client->_state) != BT_BIP_STATE_CONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&client->_state)); + return -EINVAL; + } + + if (is_bip_primary_connect(client->_type) && + client->_secondary_server != NULL && + atomic_read(&client->_secondary_server->_state) == + BT_BIP_STATE_CONNECTED) + { + LOG_ERR("Secondary conn needs to be disconnected firstly"); + return -EINVAL; + } + + if (buf == NULL) + { + buf = bt_goep_create_pdu(&client->_bip->goep, NULL); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } + + allocated = true; + } + + if (!bt_obex_has_header(buf, BT_OBEX_HEADER_ID_CONN_ID)) + { + err = bt_obex_add_header_conn_id(buf, client->_conn_id); + if (err != 0) + { + LOG_ERR("Failed to add header conn id %d", err); + goto failed; + } + } + else + { + err = bip_check_conn_id(client->_conn_id, buf); + if (err != 0) + { + goto failed; + } + } + + err = bt_obex_disconnect(&client->_client, buf); + if (err != 0) + { + LOG_ERR("Failed to send conn rsp %d", err); + goto failed; + } + + atomic_set(&client->_state, BT_BIP_STATE_DISCONNECTING); + return 0; +failed: + if (allocated) + { + net_buf_unref(buf); + } + + return err; +} + +int bt_bip_disconnect_rsp(struct bt_bip_server *server, + uint8_t rsp_code, struct net_buf *buf) +{ + int err; + + if (server == NULL || server->_bip == NULL) + { + return -EINVAL; + } + + if (atomic_read(&server->_state) != BT_BIP_STATE_DISCONNECTING) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&server->_state)); + return -EINVAL; + } + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS && + is_bip_primary_connect(server->_type) && + server->_secondary_client != NULL && + atomic_read(&server->_secondary_client->_state) == + BT_BIP_STATE_CONNECTED) + { + LOG_ERR("Secondary conn needs to be disconnected firstly"); + return -EINVAL; + } + + err = bt_obex_disconnect_rsp(&server->_server, rsp_code, buf); + if (err != 0) + { + LOG_ERR("Failed to send disconnect rsp %d", err); + return err; + } + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + atomic_set(&server->_state, BT_BIP_STATE_DISCONNECTED); + } + else + { + atomic_set(&server->_state, BT_BIP_STATE_CONNECTED); + } + + return 0; +} + +int bt_bip_abort(struct bt_bip_client *client, struct net_buf *buf) +{ + int err; + bool allocated = false; + + if (client == NULL || client->_bip == NULL) + { + return -EINVAL; + } + + if (atomic_read(&client->_state) != BT_BIP_STATE_CONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&client->_state)); + return -EINVAL; + } + + if (client->_rsp_cb == NULL) + { + LOG_ERR("No operation is ongoing"); + return -EINVAL; + } + + if (buf == NULL) + { + buf = bt_goep_create_pdu(&client->_bip->goep, NULL); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } + + allocated = true; + } + + if (bt_obex_has_header(buf, BT_OBEX_HEADER_ID_CONN_ID)) + { + err = bip_check_conn_id(client->_conn_id, buf); + if (err != 0) + { + goto failed; + } + } + else + { + err = bt_obex_add_header_conn_id(buf, client->_conn_id); + if (err != 0) + { + LOG_ERR("Failed to add header conn id %d", err); + goto failed; + } + } + + err = bt_obex_abort(&client->_client, buf); + if (err != 0) + { + LOG_ERR("Failed to send abort request %d", err); + goto failed; + } + + return 0; + +failed: + if (allocated) + { + net_buf_unref(buf); + } + + return err; +} + +int bt_bip_abort_rsp(struct bt_bip_server *server, + uint8_t rsp_code, struct net_buf *buf) +{ + int err; + + if (server == NULL) + { + return -EINVAL; + } + + if (atomic_read(&server->_state) != BT_BIP_STATE_CONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&server->_state)); + return -EINVAL; + } + + err = bt_obex_abort_rsp(&server->_server, rsp_code, buf); + if (err != 0) + { + LOG_ERR("Failed to send abort rsp %d", err); + return err; + } + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + server->_opcode = 0; + server->_optype = NULL; + server->_req_cb = NULL; + } + + return 0; +} + +static int bip_client_get_req_cb( + struct bt_bip_client *client, const char *type, struct net_buf *buf, + bool is_get, bt_bip_client_cb_t *cb, const char **req_type) +{ + const uint8_t *type_data; + uint16_t len; + int err; + + err = bt_obex_get_header_type(buf, &len, &type_data); + if (err != 0) + { + LOG_WRN("Failed to get type header %d", err); + return -EINVAL; + } + + if (len <= strlen(type_data)) + { + LOG_WRN("Invalid type string len %u <= %u", len, strlen(type_data)); + return -EINVAL; + } + + if (client->_bip == NULL) + { + LOG_ERR("Invalid BIP client context"); + return -EINVAL; + } + + *cb = NULL; + ARRAY_FOR_EACH(bip_functions, i) + { + if (strcmp(bip_functions[i].type, type) != 0) + { + continue; + } + + if (strcmp(bip_functions[i].type, type_data) != 0) + { + continue; + } + + if (bip_functions[i].op_get != is_get) + { + continue; + } + + if ((bip_functions[i].supported_features & BIT(client->_type)) == 0) + { + continue; + } + + if ((client->_bip->_supp_funcs & BIT(bip_functions[i].func_bit)) == 0) + { + continue; + } + + if (!has_required_hdrs(buf, &bip_functions[i].hdr)) + { + continue; + } + + if (!has_required_ap_tag_id(buf, bip_functions[i].required_ap_tag_id)) + { + continue; + } + + if (bip_functions[i].get_client_cb == NULL) + { + continue; + } + + *cb = bip_functions[i].get_client_cb(client); + if (*cb == NULL) + { + continue; + } + + if (req_type != NULL) + { + *req_type = bip_functions[i].type; + } + break; + } + + if (*cb == NULL) + { + LOG_WRN("Unsupported request"); + return -EINVAL; + } + + return 0; +} + +static int bip_get_or_put( + struct bt_bip_client *client, bool is_get, const char *type, bool final, + struct net_buf *buf) +{ + int err; + bt_bip_client_cb_t cb; + bt_bip_client_cb_t old_cb; + const char *req_type; + const char *old_req_type; + + if (client == NULL || buf == NULL) + { + return -EINVAL; + } + + if (atomic_read(&client->_state) != BT_BIP_STATE_CONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&client->_state)); + return -EINVAL; + } + + old_cb = client->_rsp_cb; + old_req_type = client->_req_type; + + if (client->_rsp_cb == NULL || bt_obex_has_header(buf, + BT_OBEX_HEADER_ID_TYPE)) + { + err = bip_client_get_req_cb(client, type, buf, is_get, &cb, &req_type); + if (err != 0) + { + LOG_ERR("Invalid request %d", err); + return err; + } + + if (client->_rsp_cb != NULL && cb != client->_rsp_cb) + { + LOG_ERR("Previous operation is not completed"); + return -EINVAL; + } + + client->_rsp_cb = cb; + client->_req_type = req_type; + } + + if (client->_req_type != NULL && strcmp(client->_req_type, type) != 0) + { + LOG_ERR("Invalid request type %s != %s", client->_req_type, type); + err = -EINVAL; + goto failed; + } + + if (bt_obex_has_header(buf, BT_OBEX_HEADER_ID_CONN_ID)) + { + err = bip_check_conn_id(client->_conn_id, buf); + if (err != 0) + { + goto failed; + } + } + + if (is_get) + { + err = bt_obex_get(&client->_client, final, buf); + } + else + { + err = bt_obex_put(&client->_client, final, buf); + } + +failed: + if (err != 0) + { + client->_rsp_cb = old_cb; + client->_req_type = old_req_type; + LOG_ERR("Failed to send get/put req %d", err); + } + + return err; +} + +static int bip_get(struct bt_bip_client *client, + const char *type, bool final, struct net_buf *buf) +{ + return bip_get_or_put(client, true, type, final, buf); +} + +static int bip_put(struct bt_bip_client *client, + const char *type, bool final, struct net_buf *buf) +{ + return bip_get_or_put(client, false, type, final, buf); +} + +int bt_bip_get_capabilities(struct bt_bip_client *client, + bool final, struct net_buf *buf) +{ + return bip_get(client, BT_BIP_HDR_TYPE_GET_CAPS, final, buf); +} + +static int bip_get_or_put_rsp( + struct bt_bip_server *server, bool is_get, const char *type, + uint8_t rsp_code, struct net_buf *buf) +{ + int err; + + if (server == NULL) + { + return -EINVAL; + } + + if (atomic_read(&server->_state) != BT_BIP_STATE_CONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&server->_state)); + return -EINVAL; + } + + if (server->_optype != NULL && strcmp(server->_optype, type) != 0) + { + LOG_ERR("Invalid operation type %s != %s", server->_optype, type); + return -EINVAL; + } + + if (is_get) + { + if (server->_opcode != BT_OBEX_OPCODE_GET) + { + LOG_ERR("Operation %u != %u", server->_opcode, BT_OBEX_OPCODE_GET); + return -EINVAL; + } + + err = bt_obex_get_rsp(&server->_server, rsp_code, buf); + } + else + { + if (server->_opcode != BT_OBEX_OPCODE_PUT) + { + LOG_ERR("Operation %u != %u", server->_opcode, BT_OBEX_OPCODE_PUT); + return -EINVAL; + } + + err = bt_obex_put_rsp(&server->_server, rsp_code, buf); + } + + if (err != 0) + { + LOG_ERR("Failed to send get/put rsp %d", err); + return err; + } + + if (rsp_code != BT_OBEX_RSP_CODE_CONTINUE) + { + server->_opcode = 0; + server->_optype = NULL; + server->_req_cb = NULL; + } + + return 0; +} + +int bt_bip_get_capabilities_rsp(struct bt_bip_server *server, + uint8_t rsp_code, struct net_buf *buf) +{ + return bip_get_or_put_rsp(server, true, BT_BIP_HDR_TYPE_GET_CAPS, rsp_code, + buf); +} + +int bt_bip_get_image_list(struct bt_bip_client *client, + bool final, struct net_buf *buf) +{ + return bip_get(client, BT_BIP_HDR_TYPE_GET_IMAGE_LIST, final, buf); +} + +int bt_bip_get_image_list_rsp(struct bt_bip_server *server, + uint8_t rsp_code, struct net_buf *buf) +{ + return bip_get_or_put_rsp(server, true, BT_BIP_HDR_TYPE_GET_IMAGE_LIST, + rsp_code, buf); +} + +int bt_bip_get_image_properties(struct bt_bip_client *client, + bool final, struct net_buf *buf) +{ + return bip_get(client, BT_BIP_HDR_TYPE_GET_IMAGE_PROPERTIES, final, buf); +} + +int bt_bip_get_image_properties_rsp( + struct bt_bip_server *server, uint8_t rsp_code, struct net_buf *buf) +{ + return bip_get_or_put_rsp(server, true, + BT_BIP_HDR_TYPE_GET_IMAGE_PROPERTIES, + rsp_code, buf); +} + +int bt_bip_get_image(struct bt_bip_client *client, + bool final, struct net_buf *buf) +{ + return bip_get(client, BT_BIP_HDR_TYPE_GET_IMAGE, final, buf); +} + +int bt_bip_get_image_rsp(struct bt_bip_server *server, + uint8_t rsp_code, struct net_buf *buf) +{ + return bip_get_or_put_rsp(server, true, BT_BIP_HDR_TYPE_GET_IMAGE, + rsp_code, + buf); +} + +int bt_bip_get_linked_thumbnail(struct bt_bip_client *client, + bool final, struct net_buf *buf) +{ + return bip_get(client, BT_BIP_HDR_TYPE_GET_LINKED_THUMBNAIL, final, buf); +} + +int bt_bip_get_linked_thumbnail_rsp( + struct bt_bip_server *server, uint8_t rsp_code, struct net_buf *buf) +{ + return bip_get_or_put_rsp(server, true, + BT_BIP_HDR_TYPE_GET_LINKED_THUMBNAIL, + rsp_code, buf); +} + +int bt_bip_get_linked_attachment(struct bt_bip_client *client, + bool final, struct net_buf *buf) +{ + return bip_get(client, BT_BIP_HDR_TYPE_GET_LINKED_ATTACHMENT, final, buf); +} + +int bt_bip_get_linked_attachment_rsp( + struct bt_bip_server *server, uint8_t rsp_code, struct net_buf *buf) +{ + return bip_get_or_put_rsp(server, true, + BT_BIP_HDR_TYPE_GET_LINKED_ATTACHMENT, + rsp_code, buf); +} + +int bt_bip_get_partial_image(struct bt_bip_client *client, + bool final, struct net_buf *buf) +{ + return bip_get(client, BT_BIP_HDR_TYPE_GET_PARTIAL_IMAGE, final, buf); +} + +int bt_bip_get_partial_image_rsp( + struct bt_bip_server *server, uint8_t rsp_code, struct net_buf *buf) +{ + return bip_get_or_put_rsp(server, true, BT_BIP_HDR_TYPE_GET_PARTIAL_IMAGE, + rsp_code, buf); +} + +int bt_bip_get_monitoring_image(struct bt_bip_client *client, + bool final, struct net_buf *buf) +{ + return bip_get(client, BT_BIP_HDR_TYPE_GET_MONITORING_IMAGE, final, buf); +} + +int bt_bip_get_monitoring_image_rsp( + struct bt_bip_server *server, uint8_t rsp_code, struct net_buf *buf) +{ + return bip_get_or_put_rsp(server, true, + BT_BIP_HDR_TYPE_GET_MONITORING_IMAGE, + rsp_code, buf); +} + +int bt_bip_get_status(struct bt_bip_client *client, + bool final, struct net_buf *buf) +{ + return bip_get(client, BT_BIP_HDR_TYPE_GET_STATUS, final, buf); +} + +int bt_bip_get_status_rsp(struct bt_bip_server *server, + uint8_t rsp_code, struct net_buf *buf) +{ + return bip_get_or_put_rsp(server, true, BT_BIP_HDR_TYPE_GET_STATUS, + rsp_code, + buf); +} + +int bt_bip_put_image(struct bt_bip_client *client, + bool final, struct net_buf *buf) +{ + return bip_put(client, BT_BIP_HDR_TYPE_PUT_IMAGE, final, buf); +} + +int bt_bip_put_image_rsp(struct bt_bip_server *server, + uint8_t rsp_code, struct net_buf *buf) +{ + return bip_get_or_put_rsp(server, false, BT_BIP_HDR_TYPE_PUT_IMAGE, + rsp_code, + buf); +} + +int bt_bip_put_linked_thumbnail(struct bt_bip_client *client, + bool final, struct net_buf *buf) +{ + return bip_put(client, BT_BIP_HDR_TYPE_PUT_LINKED_THUMBNAIL, final, buf); +} + +int bt_bip_put_linked_thumbnail_rsp( + struct bt_bip_server *server, uint8_t rsp_code, struct net_buf *buf) +{ + return bip_get_or_put_rsp(server, false, + BT_BIP_HDR_TYPE_PUT_LINKED_THUMBNAIL, + rsp_code, buf); +} + +int bt_bip_put_linked_attachment(struct bt_bip_client *client, + bool final, struct net_buf *buf) +{ + return bip_put(client, BT_BIP_HDR_TYPE_PUT_LINKED_ATTACHMENT, final, buf); +} + +int bt_bip_put_linked_attachment_rsp( + struct bt_bip_server *server, uint8_t rsp_code, struct net_buf *buf) +{ + return bip_get_or_put_rsp(server, false, + BT_BIP_HDR_TYPE_PUT_LINKED_ATTACHMENT, + rsp_code, buf); +} + +int bt_bip_remote_display(struct bt_bip_client *client, + bool final, struct net_buf *buf) +{ + return bip_put(client, BT_BIP_HDR_TYPE_REMOTE_DISPLAY, final, buf); +} + +int bt_bip_remote_display_rsp(struct bt_bip_server *server, + uint8_t rsp_code, struct net_buf *buf) +{ + return bip_get_or_put_rsp(server, false, BT_BIP_HDR_TYPE_REMOTE_DISPLAY, + rsp_code, buf); +} + +int bt_bip_delete_image(struct bt_bip_client *client, + bool final, struct net_buf *buf) +{ + return bip_put(client, BT_BIP_HDR_TYPE_DELETE_IMAGE, final, buf); +} + +int bt_bip_delete_image_rsp(struct bt_bip_server *server, + uint8_t rsp_code, struct net_buf *buf) +{ + return bip_get_or_put_rsp(server, false, BT_BIP_HDR_TYPE_DELETE_IMAGE, + rsp_code, buf); +} + +int bt_bip_start_print(struct bt_bip_client *client, + bool final, struct net_buf *buf) +{ + return bip_put(client, BT_BIP_HDR_TYPE_START_PRINT, final, buf); +} + +int bt_bip_start_print_rsp(struct bt_bip_server *server, + uint8_t rsp_code, struct net_buf *buf) +{ + return bip_get_or_put_rsp(server, false, BT_BIP_HDR_TYPE_START_PRINT, + rsp_code, buf); +} + +int bt_bip_start_archive(struct bt_bip_client *client, + bool final, struct net_buf *buf) +{ + return bip_put(client, BT_BIP_HDR_TYPE_START_ARCHIVE, final, buf); +} + +int bt_bip_start_archive_rsp(struct bt_bip_server *server, + uint8_t rsp_code, struct net_buf *buf) +{ + return bip_get_or_put_rsp(server, false, BT_BIP_HDR_TYPE_START_ARCHIVE, + rsp_code, buf); +} + +int bt_bip_add_header_image_desc(struct net_buf *buf, + uint16_t len, const uint8_t *desc) +{ + size_t total; + + if (buf == NULL || (len != 0 && desc == NULL)) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + if (!bt_obex_string_is_valid(BT_BIP_HEADER_ID_IMG_DESC, len, desc)) + { + LOG_WRN("Invalid string"); + return -EINVAL; + } + + net_buf_add_u8(buf, BT_BIP_HEADER_ID_IMG_DESC); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, desc, len); + return 0; +} + +int bt_bip_add_header_image_handle(struct net_buf *buf, + uint16_t len, const uint8_t *handle) +{ + size_t total; + + if (buf == NULL || handle == NULL || len == 0) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + if (!bt_obex_string_is_valid(BT_BIP_HEADER_ID_IMG_HANDLE, len, handle)) + { + LOG_WRN("Invalid string"); + return -EINVAL; + } + + net_buf_add_u8(buf, BT_BIP_HEADER_ID_IMG_HANDLE); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, handle, len); + return 0; +} diff --git a/libs/zblue/host/classic/br.c b/libs/zblue/host/classic/br.c new file mode 100644 index 0000000000000..fb27c901d2150 --- /dev/null +++ b/libs/zblue/host/classic/br.c @@ -0,0 +1,2043 @@ +/**************************************************************************** + * libs/zblue/host/classic/br.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017-2021 Nordic Semiconductor ASA + * Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include "br.h" +#include "sco_internal.h" + +#define LOG_LEVEL CONFIG_BT_HCI_CORE_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_br); + +#define RSSI_INVALID 127 + +enum __packed resolve_name_state +{ + RESOLVE_REMOTE_NAME_PENDING, + RESOLVE_REMOTE_NAME_RESOLVING, + RESOLVE_REMOTE_NAME_RESOLVED, +}; + +struct bt_br_discovery_result *discovery_results; +static size_t discovery_results_size; +static size_t discovery_results_count; +static struct list_node discovery_cbs = LIST_INITIAL_VALUE(discovery_cbs); + +static bt_br_conn_req_func_t br_conn_req_func; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int reject_conn(const bt_addr_t *bdaddr, uint8_t reason) +{ + struct bt_hci_cp_reject_conn_req *cp; + struct net_buf *buf; + int err; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + bt_addr_copy(&cp->bdaddr, bdaddr); + cp->reason = reason; + + err = bt_hci_cmd_send_sync(BT_HCI_OP_REJECT_CONN_REQ, buf, NULL); + if (err) + { + return err; + } + + return 0; +} + +static uint8_t accept_conn(const struct bt_hci_evt_conn_request *evt) +{ + struct bt_hci_cp_accept_conn_req *cp; + struct net_buf *buf; + uint32_t cod; + int err; + enum bt_br_conn_req_rsp rsp; + uint8_t role; + + if (br_conn_req_func != NULL) + { + cod = sys_get_le24(evt->dev_class); + rsp = br_conn_req_func(&evt->bdaddr, cod); + + switch (rsp) + { + case BT_BR_CONN_REQ_ACCEPT_CENTRAL: + case BT_BR_CONN_REQ_ACCEPT_PERIPHERAL: + role = (uint8_t)rsp; + break; + case BT_BR_CONN_REQ_REJECT_NO_RESOURCES: + case BT_BR_CONN_REQ_REJECT_SECURITY: + case BT_BR_CONN_REQ_REJECT_ADDR: + return (uint8_t)rsp; + default: + return BT_HCI_ERR_UNSPECIFIED; + } + } + else + { + role = + IS_ENABLED(CONFIG_BT_ACCEPT_CONN_AS_CENTRAL) ? + BT_HCI_ROLE_CENTRAL + : BT_HCI_ROLE_PERIPHERAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (buf == NULL) + { + return BT_HCI_ERR_INSUFFICIENT_RESOURCES; + } + + cp = net_buf_add(buf, sizeof(*cp)); + bt_addr_copy(&cp->bdaddr, &evt->bdaddr); + cp->role = role; + + err = bt_hci_cmd_send_sync(BT_HCI_OP_ACCEPT_CONN_REQ, buf, NULL); + if (err != 0) + { + return BT_HCI_ERR_UNSPECIFIED; + } + + return BT_HCI_ERR_SUCCESS; +} + +void bt_hci_conn_req(struct net_buf *buf) +{ + struct bt_hci_evt_conn_request *evt = (void *)buf->data; + struct bt_conn *conn; + uint8_t err; + + LOG_DBG("conn req from %s, type 0x%02x", bt_addr_str(&evt->bdaddr), + evt->link_type); + + if (evt->link_type != BT_HCI_ACL) + { + err = bt_esco_conn_req(evt); + if (err != BT_HCI_ERR_SUCCESS) + { + reject_conn(&evt->bdaddr, err); + } + + return; + } + + conn = bt_conn_add_br(&evt->bdaddr); + if (conn == NULL) + { + reject_conn(&evt->bdaddr, BT_HCI_ERR_INSUFFICIENT_RESOURCES); + return; + } + + err = accept_conn(evt); + if (err != BT_HCI_ERR_SUCCESS) + { + LOG_ERR("Failed to accept conn from %s (err %u)", + bt_addr_str(&evt->bdaddr), err); + reject_conn(&evt->bdaddr, err); + bt_conn_unref(conn); + return; + } + + /* The role is peripheral by default. If CONFIG_BT_ACCEPT_CONN_AS_CENTRAL + * is enabled, the role will be updated by the role change event if the + * role switch is accepted by the peer device. + */ + + conn->role = BT_CONN_ROLE_PERIPHERAL; + bt_conn_set_state(conn, BT_CONN_INITIATING); + bt_conn_unref(conn); +} + +static bool br_sufficient_key_size(struct bt_conn *conn) +{ + struct bt_hci_cp_read_encryption_key_size *cp; + struct bt_hci_rp_read_encryption_key_size *rp; + struct net_buf *buf; + struct net_buf *rsp; + uint8_t key_size; + int err; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + LOG_ERR("Failed to allocate command buffer"); + return false; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_ENCRYPTION_KEY_SIZE, buf, &rsp); + if (err) + { + LOG_ERR("Failed to read encryption key size (err %d)", err); + return false; + } + + if (rsp->len < sizeof(*rp)) + { + LOG_ERR("Too small command complete for encryption key size"); + net_buf_unref(rsp); + return false; + } + + rp = (void *)rsp->data; + key_size = rp->key_size; + net_buf_unref(rsp); + + if (conn->br.link_key) + { + conn->br.link_key->enc_key_size = key_size; + } + + LOG_DBG("Encryption key size is %u", key_size); + + if (conn->sec_level == BT_SECURITY_L4) + { + return key_size == BT_HCI_ENCRYPTION_KEY_SIZE_MAX; + } + + return key_size >= CONFIG_BT_BR_MIN_ENC_KEY_SIZE; +} + +bool bt_br_update_sec_level(struct bt_conn *conn) +{ + if (!conn->encrypt) + { + conn->sec_level = BT_SECURITY_L1; + return true; + } + + if (conn->br.link_key) + { + if (conn->br.link_key->flags & BT_LINK_KEY_AUTHENTICATED) + { + if (conn->encrypt == BT_HCI_ENCRYPTION_ON_BR_AES_CCM) + { + conn->sec_level = BT_SECURITY_L4; + } + else + { + conn->sec_level = BT_SECURITY_L3; + } + } + else + { + conn->sec_level = BT_SECURITY_L2; + } + } + else + { + LOG_WRN("No BR/EDR link key found"); + conn->sec_level = BT_SECURITY_L2; + } + + if (!br_sufficient_key_size(conn)) + { + LOG_ERR("Encryption key size is not sufficient"); + bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL); + return false; + } + + if (conn->required_sec_level > conn->sec_level) + { + LOG_ERR("Failed to set required security level"); + bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL); + return false; + } + + return true; +} + +void bt_hci_synchronous_conn_complete(struct net_buf *buf) +{ + struct bt_hci_evt_sync_conn_complete *evt = (void *)buf->data; + struct bt_conn *sco_conn; + uint16_t handle = sys_le16_to_cpu(evt->handle); + + LOG_DBG("status 0x%02x, handle %u, type 0x%02x", evt->status, handle, + evt->link_type); + + sco_conn = bt_conn_lookup_addr_sco(&evt->bdaddr); + if (!sco_conn) + { + LOG_ERR("Unable to find conn for %s", bt_addr_str(&evt->bdaddr)); + return; + } + + if (evt->status) + { + sco_conn->err = evt->status; + bt_conn_set_state(sco_conn, BT_CONN_DISCONNECTED); + bt_conn_unref(sco_conn); + return; + } + + sco_conn->handle = handle; + sco_conn->sco.air_mode = evt->air_mode; + + if (sco_conn->sco.link_type != evt->link_type) + { + LOG_WRN("link type mismatch %u != %u", sco_conn->sco.link_type, + evt->link_type); + sco_conn->sco.link_type = evt->link_type; + } + + bt_conn_set_state(sco_conn, BT_CONN_CONNECTED); + bt_conn_unref(sco_conn); +} + +void bt_hci_conn_complete(struct net_buf *buf) +{ + struct bt_hci_evt_conn_complete *evt = (void *)buf->data; + struct bt_conn *conn; + struct bt_hci_cp_read_remote_features *cp; + uint16_t handle = sys_le16_to_cpu(evt->handle); + + LOG_DBG("status 0x%02x, handle %u, type 0x%02x", evt->status, handle, + evt->link_type); + + conn = bt_conn_lookup_addr_br(&evt->bdaddr); + if (!conn) + { + LOG_ERR("Unable to find conn for %s", bt_addr_str(&evt->bdaddr)); + return; + } + + if (evt->status) + { + conn->err = evt->status; + bt_conn_set_state(conn, BT_CONN_DISCONNECTED); + bt_conn_unref(conn); + return; + } + + conn->handle = handle; + conn->err = 0U; + conn->encrypt = evt->encr_enabled; + + if (!bt_br_update_sec_level(conn)) + { + bt_conn_unref(conn); + return; + } + + bt_conn_set_state(conn, BT_CONN_CONNECTED); + + atomic_set_bit_to(conn->flags, BT_CONN_BR_BONDABLE, bt_get_bondable()); + + bt_conn_connected(conn); + + bt_conn_unref(conn); + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = evt->handle; + + bt_hci_cmd_send_sync(BT_HCI_OP_READ_REMOTE_FEATURES, buf, NULL); +} + +static int request_name(const bt_addr_t *addr, + uint8_t pscan, uint16_t offset) +{ + struct bt_hci_cp_remote_name_request *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + + bt_addr_copy(&cp->bdaddr, addr); + cp->pscan_rep_mode = pscan; + cp->reserved = 0x00; /* reserved, should be set to 0x00 */ + cp->clock_offset = offset; + + return bt_hci_cmd_send_sync(BT_HCI_OP_REMOTE_NAME_REQUEST, buf, NULL); +} + +#define EIR_SHORT_NAME 0x08 +#define EIR_COMPLETE_NAME 0x09 + +static bool eir_has_name(const uint8_t *eir) +{ + int len = 240; + + while (len) + { + if (len < 2) + { + break; + } + + /* Look for early termination */ + + if (!eir[0]) + { + break; + } + + /* Check if field length is correct */ + + if (eir[0] > len - 1) + { + break; + } + + switch (eir[1]) + { + case EIR_SHORT_NAME: + case EIR_COMPLETE_NAME: + if (eir[0] > 1) + { + return true; + } + break; + default: + break; + } + + /* Parse next AD Structure */ + + len -= eir[0] + 1; + eir += eir[0] + 1; + } + + return false; +} + +void bt_br_discovery_reset(void) +{ + discovery_results = NULL; + discovery_results_size = 0; + discovery_results_count = 0; +} + +static bool check_request_name(void) +{ + int i; + bool resolving_names = false; + + for (i = 0; i < discovery_results_count; i++) + { + struct bt_br_discovery_priv *priv; + + priv = &discovery_results[i]._priv; + + if (eir_has_name(discovery_results[i].eir)) + { + continue; + } + + if (priv->resolve_state != RESOLVE_REMOTE_NAME_PENDING) + { + continue; + } + + if (request_name(&discovery_results[i].addr, priv->pscan_rep_mode, + priv->clock_offset)) + { + priv->resolve_state = RESOLVE_REMOTE_NAME_RESOLVED; + continue; + } + + priv->resolve_state = RESOLVE_REMOTE_NAME_RESOLVING; + resolving_names = true; + break; + } + + return resolving_names; +} + +static void report_discovery_results(void) +{ + int i; + struct bt_br_discovery_cb *listener; + struct bt_br_discovery_cb *next; + + for (i = 0; i < discovery_results_count; i++) + { + struct bt_br_discovery_priv *priv; + + priv = &discovery_results[i]._priv; + priv->resolve_state = RESOLVE_REMOTE_NAME_PENDING; + } + + if (check_request_name()) + { + return; + } + + atomic_clear_bit(bt_dev.flags, BT_DEV_INQUIRY); + + list_for_every_container_safe(&discovery_cbs, listener, next, node) + { + if (listener->timeout) + { + listener->timeout(discovery_results, discovery_results_count); + } + } + + bt_br_discovery_reset(); +} + +void bt_hci_inquiry_complete(struct net_buf *buf) +{ + struct bt_hci_evt_inquiry_complete *evt = (void *)buf->data; + + if (evt->status) + { + LOG_ERR("Failed to complete inquiry"); + } + + report_discovery_results(); +} + +static struct bt_br_discovery_result *get_result_slot(const bt_addr_t *addr, + int8_t rssi) +{ + struct bt_br_discovery_result *result = NULL; + size_t i; + + /* check if already present in results */ + + for (i = 0; i < discovery_results_count; i++) + { + if (bt_addr_eq(addr, &discovery_results[i].addr)) + { + return &discovery_results[i]; + } + } + + /* Pick a new slot (if available) */ + + if (discovery_results_count < discovery_results_size) + { + bt_addr_copy(&discovery_results[discovery_results_count].addr, addr); + return &discovery_results[discovery_results_count++]; + } + + /* ignore if invalid RSSI */ + + if (rssi == RSSI_INVALID) + { + return NULL; + } + + /* Pick slot with smallest RSSI that is smaller then passed RSSI + * TODO handle TX if present + */ + + for (i = 0; i < discovery_results_size; i++) + { + if (discovery_results[i].rssi > rssi) + { + continue; + } + + if (!result || result->rssi > discovery_results[i].rssi) + { + result = &discovery_results[i]; + } + } + + if (result) + { + LOG_DBG("Reusing slot (old %s rssi %d " + "dBm)", bt_addr_str(&result->addr), + result->rssi); + + bt_addr_copy(&result->addr, addr); + } + + return result; +} + +void bt_hci_inquiry_result_with_rssi(struct net_buf *buf) +{ + uint8_t num_reports = net_buf_pull_u8(buf); + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_INQUIRY)) + { + return; + } + + LOG_DBG("number of results: %u", num_reports); + + while (num_reports--) + { + struct bt_hci_evt_inquiry_result_with_rssi *evt; + struct bt_br_discovery_result *result; + struct bt_br_discovery_priv *priv; + struct bt_br_discovery_cb *listener; + struct bt_br_discovery_cb *next; + + if (buf->len < sizeof(*evt)) + { + LOG_ERR("Unexpected end to buffer"); + return; + } + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + LOG_DBG("%s rssi %d dBm", bt_addr_str(&evt->addr), evt->rssi); + + result = get_result_slot(&evt->addr, evt->rssi); + if (!result) + { + return; + } + + priv = &result->_priv; + priv->pscan_rep_mode = evt->pscan_rep_mode; + priv->clock_offset = evt->clock_offset; + + memcpy(result->cod, evt->cod, 3); + result->rssi = evt->rssi; + + /* we could reuse slot so make sure EIR is cleared */ + + (void)memset(result->eir, 0, sizeof(result->eir)); + + list_for_every_container_safe(&discovery_cbs, listener, next, node) + { + if (listener->recv) + { + listener->recv(result); + } + } + } +} + +void bt_hci_extended_inquiry_result(struct net_buf *buf) +{ + struct bt_hci_evt_extended_inquiry_result *evt = (void *)buf->data; + struct bt_br_discovery_result *result; + struct bt_br_discovery_priv *priv; + struct bt_br_discovery_cb *listener; + struct bt_br_discovery_cb *next; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_INQUIRY)) + { + return; + } + + LOG_DBG("%s rssi %d dBm", bt_addr_str(&evt->addr), evt->rssi); + + result = get_result_slot(&evt->addr, evt->rssi); + if (!result) + { + return; + } + + priv = &result->_priv; + priv->pscan_rep_mode = evt->pscan_rep_mode; + priv->clock_offset = evt->clock_offset; + + result->rssi = evt->rssi; + memcpy(result->cod, evt->cod, 3); + memcpy(result->eir, evt->eir, sizeof(result->eir)); + + list_for_every_container_safe(&discovery_cbs, listener, next, node) + { + if (listener->recv) + { + listener->recv(result); + } + } +} + +void bt_hci_remote_name_request_complete(struct net_buf *buf) +{ + struct bt_hci_evt_remote_name_req_complete *evt = (void *)buf->data; + struct bt_br_discovery_result *result; + struct bt_br_discovery_priv *priv; + int eir_len = 240; + uint8_t *eir; + struct bt_br_discovery_cb *listener; + struct bt_br_discovery_cb *next; + + result = get_result_slot(&evt->bdaddr, RSSI_INVALID); + if (!result) + { + return; + } + + priv = &result->_priv; + priv->resolve_state = RESOLVE_REMOTE_NAME_RESOLVED; + + if (evt->status) + { + goto check_names; + } + + eir = result->eir; + + while (eir_len) + { + if (eir_len < 2) + { + break; + } + + /* Look for early termination */ + + if (!eir[0]) + { + size_t name_len; + + eir_len -= 2; + + /* name is null terminated */ + + name_len = strlen((const char *)evt->name); + + if (name_len > eir_len) + { + eir[0] = eir_len + 1; + eir[1] = EIR_SHORT_NAME; + } + else + { + eir[0] = name_len + 1; + eir[1] = EIR_SHORT_NAME; + } + + memcpy(&eir[2], evt->name, eir[0] - 1); + + break; + } + + /* Check if field length is correct */ + + if (eir[0] > eir_len - 1) + { + break; + } + + /* next EIR Structure */ + + eir_len -= eir[0] + 1; + eir += eir[0] + 1; + } + + list_for_every_container_safe(&discovery_cbs, listener, next, node) + { + if (listener->recv) + { + listener->recv(result); + } + } + +check_names: + + /* if still need to request name */ + + if (check_request_name()) + { + return; + } + + /* all names resolved, report discovery results */ + + atomic_clear_bit(bt_dev.flags, BT_DEV_INQUIRY); + + list_for_every_container_safe(&discovery_cbs, listener, next, node) + { + if (listener->timeout) + { + listener->timeout(discovery_results, discovery_results_count); + } + } +} + +void bt_hci_read_remote_features_complete(struct net_buf *buf) +{ + struct bt_hci_evt_remote_features *evt = (void *)buf->data; + uint16_t handle = sys_le16_to_cpu(evt->handle); + struct bt_hci_cp_read_remote_ext_features *cp; + struct bt_conn *conn; + + LOG_DBG("status 0x%02x handle %u", evt->status, handle); + + conn = bt_conn_lookup_handle(handle, BT_CONN_TYPE_BR); + if (!conn) + { + LOG_ERR("Can't find conn for handle %u", handle); + return; + } + + if (evt->status) + { + goto done; + } + + memcpy(conn->br.features[0], evt->features, sizeof(evt->features)); + + if (!BT_FEAT_EXT_FEATURES(conn->br.features)) + { + goto done; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + goto done; + } + + /* Read remote host features (page 1) */ + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = evt->handle; + cp->page = 0x01; + + bt_hci_cmd_send_sync(BT_HCI_OP_READ_REMOTE_EXT_FEATURES, buf, NULL); + +done: + bt_conn_unref(conn); +} + +void bt_hci_read_remote_ext_features_complete(struct net_buf *buf) +{ + struct bt_hci_evt_remote_ext_features *evt = (void *)buf->data; + uint16_t handle = sys_le16_to_cpu(evt->handle); + struct bt_conn *conn; + + LOG_DBG("status 0x%02x handle %u", evt->status, handle); + + conn = bt_conn_lookup_handle(handle, BT_CONN_TYPE_BR); + if (!conn) + { + LOG_ERR("Can't find conn for handle %u", handle); + return; + } + + if (!evt->status && evt->page == 0x01) + { + memcpy(conn->br.features[1], evt->features, + sizeof(conn->br.features[1])); + } + + bt_conn_unref(conn); +} + +void bt_hci_role_change(struct net_buf *buf) +{ + struct bt_hci_evt_role_change *evt = (void *)buf->data; + struct bt_conn *conn; + + LOG_DBG("status 0x%02x role %u addr %s", evt->status, evt->role, + bt_addr_str(&evt->bdaddr)); + + conn = bt_conn_lookup_addr_br(&evt->bdaddr); + if (!conn) + { + LOG_ERR("Can't find conn for %s", bt_addr_str(&evt->bdaddr)); + return; + } + + if (evt->status == 0) + { + if (evt->role == BT_HCI_ROLE_PERIPHERAL) + { + conn->role = BT_CONN_ROLE_PERIPHERAL; + } + else + { + conn->role = BT_CONN_ROLE_CENTRAL; + } + } + + bt_conn_br_role_changed(conn, evt->status); + + bt_conn_unref(conn); +} + +void bt_hci_conn_pkt_type_changed(struct net_buf *buf) +{ + struct bt_hci_evt_conn_pkt_type_changed *evt = (void *)buf->data; + uint16_t handle = sys_le16_to_cpu(evt->handle); + uint16_t packet_type = sys_le16_to_cpu(evt->packet_type); + struct bt_conn *conn; + + LOG_DBG("status 0x%02x handle %u packet_type 0x%04x", evt->status, handle, + packet_type); + + conn = bt_conn_lookup_handle(handle, BT_CONN_TYPE_BR); + if (conn == NULL) + { + LOG_ERR("Can't find conn for handle %u", handle); + return; + } + + bt_conn_br_packet_type_changed(conn, evt->status, packet_type); + + bt_conn_unref(conn); +} + +#if defined(CONFIG_BT_POWER_MODE_CONTROL) +void bt_hci_link_mode_change(struct net_buf *buf) +{ + struct bt_hci_evt_mode_change *evt = (void *)buf->data; + uint16_t handle = sys_le16_to_cpu(evt->handle); + uint16_t interval = sys_le16_to_cpu(evt->interval); + struct bt_conn *conn; + + conn = bt_conn_lookup_handle(handle, BT_CONN_TYPE_BR); + if (!conn) + { + LOG_ERR("Can't find conn for handle 0x%x", handle); + return; + } + + if (conn->state != BT_CONN_CONNECTED) + { + LOG_ERR("Invalid state %d", conn->state); + bt_conn_unref(conn); + return; + } + + if (evt->status) + { + LOG_ERR("Error %d, type %d", evt->status, conn->type); + bt_conn_unref(conn); + return; + } + + LOG_DBG("hdl 0x%x mode %d intervel %d", handle, evt->mode, interval); + + conn->br.mode = evt->mode; + bt_conn_notify_mode_changed(conn, evt->mode, interval); + bt_conn_unref(conn); +} +#endif /* CONFIG_BT_POWER_MODE_CONTROL */ + +static int read_ext_features(void) +{ + int i; + + /* Read Local Supported Extended Features */ + + for (i = 1; i < LMP_FEAT_PAGES_COUNT; i++) + { + struct bt_hci_cp_read_local_ext_features *cp; + struct bt_hci_rp_read_local_ext_features *rp; + struct net_buf *buf; + struct net_buf *rsp; + int err; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->page = i; + + err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_LOCAL_EXT_FEATURES, buf, + &rsp); + if (err) + { + return err; + } + + rp = (void *)rsp->data; + + memcpy(&bt_dev.features[i], rp->ext_features, + sizeof(bt_dev.features[i])); + + if (rp->max_page <= i) + { + net_buf_unref(rsp); + break; + } + + net_buf_unref(rsp); + } + + return 0; +} + +void device_supported_pkt_type(void) +{ + /* Device supported features and sco packet types */ + + if (BT_FEAT_LMP_SCO_CAPABLE(bt_dev.features)) + { + bt_dev.br.esco_pkt_type |= (HCI_PKT_TYPE_SCO_HV1); + } + + if (BT_FEAT_HV2_PKT(bt_dev.features)) + { + bt_dev.br.esco_pkt_type |= (HCI_PKT_TYPE_SCO_HV2); + } + + if (BT_FEAT_HV3_PKT(bt_dev.features)) + { + bt_dev.br.esco_pkt_type |= (HCI_PKT_TYPE_SCO_HV3); + } + + if (BT_FEAT_LMP_ESCO_CAPABLE(bt_dev.features)) + { + bt_dev.br.esco_pkt_type |= (HCI_PKT_TYPE_ESCO_EV3); + } + + if (BT_FEAT_EV4_PKT(bt_dev.features)) + { + bt_dev.br.esco_pkt_type |= (HCI_PKT_TYPE_ESCO_EV4); + } + + if (BT_FEAT_EV5_PKT(bt_dev.features)) + { + bt_dev.br.esco_pkt_type |= (HCI_PKT_TYPE_ESCO_EV5); + } + + if (BT_FEAT_2EV3_PKT(bt_dev.features)) + { + bt_dev.br.esco_pkt_type |= (HCI_PKT_TYPE_ESCO_2EV3); + } + + if (BT_FEAT_3EV3_PKT(bt_dev.features)) + { + bt_dev.br.esco_pkt_type |= (HCI_PKT_TYPE_ESCO_3EV3); + } + + if (BT_FEAT_3SLOT_PKT(bt_dev.features)) + { + bt_dev.br.esco_pkt_type |= + (HCI_PKT_TYPE_ESCO_2EV5 | HCI_PKT_TYPE_ESCO_3EV5); + } +} + +static void read_buffer_size_complete(struct net_buf *buf) +{ + struct bt_hci_rp_read_buffer_size *rp = (void *)buf->data; + uint16_t pkts; + + LOG_DBG("status 0x%02x", rp->status); + + bt_dev.br.mtu = sys_le16_to_cpu(rp->acl_max_len); + pkts = sys_le16_to_cpu(rp->acl_max_num); + + LOG_DBG("ACL BR/EDR buffers: pkts %u mtu %u", pkts, bt_dev.br.mtu); + + nxsem_init(&bt_dev.br.pkts, 0, pkts); +} + +int bt_br_init(void) +{ + struct net_buf *buf; + struct bt_hci_cp_write_ssp_mode *ssp_cp; + struct bt_hci_cp_write_inquiry_mode *inq_cp; + struct bt_hci_rp_read_default_link_policy_settings *rp; + struct net_buf *rsp; + int err; + uint16_t default_link_policy_settings; + + /* Read extended local features */ + + if (BT_FEAT_EXT_FEATURES(bt_dev.features)) + { + err = read_ext_features(); + if (err) + { + return err; + } + } + + /* Add local supported packet types to bt_dev */ + + device_supported_pkt_type(); + + /* Get BR/EDR buffer size */ + + err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_BUFFER_SIZE, NULL, &buf); + if (err) + { + return err; + } + + read_buffer_size_complete(buf); + net_buf_unref(buf); + + /* Set SSP mode */ + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + ssp_cp = net_buf_add(buf, sizeof(*ssp_cp)); + ssp_cp->mode = 0x01; + err = bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_SSP_MODE, buf, NULL); + if (err) + { + return err; + } + + /* Enable Inquiry results with RSSI or extended Inquiry */ + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + inq_cp = net_buf_add(buf, sizeof(*inq_cp)); + inq_cp->mode = 0x02; + err = bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_INQUIRY_MODE, buf, NULL); + if (err) + { + return err; + } + + /* Skip if settings commit will handle the name (dynamic + settings). */ + + if (!(IS_ENABLED(CONFIG_BT_DEVICE_NAME_DYNAMIC) && + IS_ENABLED(CONFIG_BT_SETTINGS))) + { + err = bt_br_write_local_name(CONFIG_BT_DEVICE_NAME); + if (err) + { + return err; + } + } + + /* Set Class of device */ + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + net_buf_add_le24(buf, CONFIG_BT_COD); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_CLASS_OF_DEVICE, buf, NULL); + if (err) + { + return err; + } + + /* Set page timeout */ + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + net_buf_add_le16(buf, CONFIG_BT_PAGE_TIMEOUT); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_PAGE_TIMEOUT, buf, NULL); + if (err) + { + return err; + } + + /* Enable BR/EDR SC if supported */ + + if (BT_FEAT_SC(bt_dev.features)) + { + struct bt_hci_cp_write_sc_host_supp *sc_cp; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + sc_cp = net_buf_add(buf, sizeof(*sc_cp)); + sc_cp->sc_support = 0x01; + + err = bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_SC_HOST_SUPP, buf, NULL); + if (err) + { + return err; + } + } + + err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_DEFAULT_LINK_POLICY_SETTINGS, + NULL, + &rsp); + if (err) + { + return err; + } + + rp = (void *)rsp->data; + default_link_policy_settings = rp->default_link_policy_settings; + net_buf_drop(&rsp); + + bool should_enable = IS_ENABLED(CONFIG_BT_DEFAULT_ROLE_SWITCH_ENABLE); + bool is_enabled = + (default_link_policy_settings & + BT_HCI_LINK_POLICY_SETTINGS_ENABLE_ROLE_SWITCH); + + /* Enable/Disable the default role switch */ + + if (should_enable != is_enabled) + { + struct bt_hci_cp_write_default_link_policy_settings *policy_cp; + + default_link_policy_settings ^= + BT_HCI_LINK_POLICY_SETTINGS_ENABLE_ROLE_SWITCH; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + policy_cp = net_buf_add(buf, sizeof(*policy_cp)); + policy_cp->default_link_policy_settings = default_link_policy_settings; + + err = bt_hci_cmd_send_sync( + BT_HCI_OP_WRITE_DEFAULT_LINK_POLICY_SETTINGS, + buf, NULL); + if (err) + { + return err; + } + } + + return 0; +} + +static int br_start_inquiry(const struct bt_br_discovery_param *param) +{ + const uint8_t iac[3] = + { + 0x33, 0x8b, 0x9e + }; + + struct bt_hci_op_inquiry *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + + cp->length = param->length; + cp->num_rsp = 0xff; /* we limit discovery only by time */ + + memcpy(cp->lap, iac, 3); + if (param->limited) + { + cp->lap[0] = 0x00; + } + + return bt_hci_cmd_send_sync(BT_HCI_OP_INQUIRY, buf, NULL); +} + +static bool valid_br_discov_param(const struct bt_br_discovery_param *param, + size_t num_results) +{ + if (!num_results || num_results > 255) + { + return false; + } + + if (!param->length || param->length > 0x30) + { + return false; + } + + return true; +} + +int bt_br_discovery_start(const struct bt_br_discovery_param *param, + struct bt_br_discovery_result *results, size_t cnt) +{ + int err; + + LOG_DBG(""); + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + if (!valid_br_discov_param(param, cnt)) + { + return -EINVAL; + } + + if (atomic_test_bit(bt_dev.flags, BT_DEV_INQUIRY)) + { + return -EALREADY; + } + + err = br_start_inquiry(param); + if (err) + { + return err; + } + + atomic_set_bit(bt_dev.flags, BT_DEV_INQUIRY); + + (void)memset(results, 0, sizeof(*results) * cnt); + + discovery_results = results; + discovery_results_size = cnt; + discovery_results_count = 0; + + return 0; +} + +int bt_br_discovery_stop(void) +{ + int err; + int i; + + LOG_DBG(""); + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_INQUIRY)) + { + return -EALREADY; + } + + err = bt_hci_cmd_send_sync(BT_HCI_OP_INQUIRY_CANCEL, NULL, NULL); + if (err) + { + return err; + } + + for (i = 0; i < discovery_results_count; i++) + { + struct bt_br_discovery_priv *priv; + struct bt_hci_cp_remote_name_cancel *cp; + struct net_buf *buf; + + priv = &discovery_results[i]._priv; + + if (priv->resolve_state != RESOLVE_REMOTE_NAME_RESOLVING) + { + continue; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + continue; + } + + cp = net_buf_add(buf, sizeof(*cp)); + bt_addr_copy(&cp->bdaddr, &discovery_results[i].addr); + + bt_hci_cmd_send_sync(BT_HCI_OP_REMOTE_NAME_CANCEL, buf, NULL); + } + + atomic_clear_bit(bt_dev.flags, BT_DEV_INQUIRY); + + discovery_results = NULL; + discovery_results_size = 0; + discovery_results_count = 0; + + return 0; +} + +void bt_br_discovery_cb_register(struct bt_br_discovery_cb *cb) +{ + list_add_tail(&discovery_cbs, &cb->node); +} + +void bt_br_discovery_cb_unregister(struct bt_br_discovery_cb *cb) +{ + list_find_and_remove(&discovery_cbs, &cb->node); +} + +static int write_scan_enable(uint8_t scan) +{ + struct net_buf *buf; + int err; + + LOG_DBG("type %u", scan); + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + net_buf_add_u8(buf, scan); + err = bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_SCAN_ENABLE, buf, NULL); + if (err) + { + return err; + } + + atomic_set_bit_to(bt_dev.flags, BT_DEV_ISCAN, + (scan & BT_BREDR_SCAN_INQUIRY)); + atomic_set_bit_to(bt_dev.flags, BT_DEV_PSCAN, (scan & BT_BREDR_SCAN_PAGE)); + + return 0; +} + +int bt_br_set_connectable(bool enable, bt_br_conn_req_func_t func) +{ + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + if (enable) + { + if (atomic_test_bit(bt_dev.flags, BT_DEV_PSCAN)) + { + return -EALREADY; + } + + br_conn_req_func = func; + return write_scan_enable(BT_BREDR_SCAN_PAGE); + } + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_PSCAN)) + { + return -EALREADY; + } + + return write_scan_enable(BT_BREDR_SCAN_DISABLED); +} + +#define BT_LIAC 0x9e8b00 +#define BT_GIAC 0x9e8b33 + +static int bt_br_write_current_iac_lap(bool limited) +{ + struct bt_hci_cp_write_current_iac_lap *iac_lap; + struct net_buf *buf; + uint8_t param_len; + uint8_t num_current_iac = limited ? 2 : 1; + + LOG_DBG("limited discoverable mode? %s", limited ? "Yes" : "No"); + + param_len = + sizeof(*iac_lap) + + (num_current_iac * sizeof(struct bt_hci_iac_lap)); + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + iac_lap = net_buf_add(buf, param_len); + iac_lap->num_current_iac = num_current_iac; + sys_put_le24(BT_GIAC, iac_lap->lap[0].iac); + if (num_current_iac > 1) + { + sys_put_le24(BT_LIAC, iac_lap->lap[1].iac); + } + + return bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_CURRENT_IAC_LAP, buf, NULL); +} + +#define BT_COD_MAJOR_SVC_CLASS_LIMITED_DISCOVER BIT(13) + +static int bt_br_read_cod(uint32_t *cod) +{ + struct net_buf *rsp; + struct bt_hci_rp_read_class_of_device *rp; + int err; + + LOG_DBG("Read COD"); + + if (cod == NULL) + { + return -EINVAL; + } + + /* Read Class of device */ + + err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_CLASS_OF_DEVICE, NULL, &rsp); + if (err) + { + LOG_WRN("Fail to read COD"); + return err; + } + + if (!rsp || (rsp->len < sizeof(*rp))) + { + LOG_WRN("Invalid response"); + return -EIO; + } + + rp = (void *)rsp->data; + *cod = sys_get_le24(rp->class_of_device); + + LOG_DBG("Current COD %06x", *cod); + + net_buf_unref(rsp); + + return 0; +} + +static int bt_br_write_cod(uint32_t cod) +{ + struct net_buf *buf; + + /* Set Class of device */ + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + net_buf_add_le24(buf, cod); + + return bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_CLASS_OF_DEVICE, buf, NULL); +} + +static int bt_br_update_cod(bool limited) +{ + int err; + uint32_t cod; + + LOG_DBG("Update COD"); + + err = bt_br_read_cod(&cod); + if (err) + { + return err; + } + + if (limited) + { + cod |= BT_COD_MAJOR_SVC_CLASS_LIMITED_DISCOVER; + } + else + { + cod &= ~BT_COD_MAJOR_SVC_CLASS_LIMITED_DISCOVER; + } + + err = bt_br_write_cod(cod); + return err; +} + +static void bt_br_limited_discoverable_timeout_handler(struct k_work *work) +{ + int err; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_LIMITED_DISCOVERABLE_MODE)) + { + LOG_INF("Limited discoverable mode has been disabled"); + return; + } + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + /* bt_disable() is in progress or has completed, so the + * transport may already be closed and no HCI commands can be sent + * here. The controller scan state is handled by the disable procedure + * itself (HCI Reset), or deliberately left intact for controllers with + * the no-reset quirk. If disabling fails and the stack resumes + * operation, BT_DEV_READY gets restored with this timer still armed, + * so the limited discoverable deadline keeps being honored. + */ + + return; + } + + err = bt_br_set_discoverable(false, false); + if (err) + { + LOG_WRN("Disable discoverable failure (err %d)", err); + } +} + +/* Work used for limited discoverable mode time span */ + +static K_WORK_DELAYABLE_DEFINE(bt_br_limited_discoverable_timeout, + bt_br_limited_discoverable_timeout_handler); + +int bt_br_set_discoverable(bool enable, bool limited) +{ + int err; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + if (enable) + { + if (atomic_test_bit(bt_dev.flags, BT_DEV_ISCAN)) + { + return -EALREADY; + } + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_PSCAN)) + { + return -EPERM; + } + + err = bt_br_write_current_iac_lap(limited); + if (err) + { + return err; + } + + err = bt_br_update_cod(limited); + if (err) + { + return err; + } + + err = write_scan_enable(BT_BREDR_SCAN_INQUIRY | BT_BREDR_SCAN_PAGE); + if (!err && (limited == true)) + { + atomic_set_bit(bt_dev.flags, BT_DEV_LIMITED_DISCOVERABLE_MODE); + bt_work_reschedule(&bt_br_limited_discoverable_timeout, + SEC2TICK(CONFIG_BT_LIMITED_DISCOVERABLE_DURATION)); + } + + return err; + } + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_ISCAN)) + { + return -EALREADY; + } + + err = write_scan_enable(BT_BREDR_SCAN_PAGE); + if (err) + { + return err; + } + + if (atomic_test_bit(bt_dev.flags, BT_DEV_LIMITED_DISCOVERABLE_MODE)) + { + err = bt_br_write_current_iac_lap(false); + if (err) + { + return err; + } + + err = bt_br_update_cod(false); + if (err) + { + return err; + } + + atomic_clear_bit(bt_dev.flags, BT_DEV_LIMITED_DISCOVERABLE_MODE); + k_work_cancel_delayable(&bt_br_limited_discoverable_timeout); + } + + return 0; +} + +static int bt_br_write_page_scan_activity(uint16_t interval, uint16_t window) +{ + struct bt_hci_cp_write_page_scan_activity *cp; + struct net_buf *buf; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + if (interval < BT_BR_SCAN_INTERVAL_MIN || + interval > BT_BR_SCAN_INTERVAL_MAX) + { + return -EINVAL; + } + + if (window < BT_BR_SCAN_WINDOW_MIN || window > BT_BR_SCAN_WINDOW_MAX) + { + return -EINVAL; + } + + if (interval < window) + { + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (buf == NULL) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->interval = sys_cpu_to_le16(interval); + cp->window = sys_cpu_to_le16(window); + + return bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_PAGE_SCAN_ACTIVITY, buf, NULL); +} + +static int bt_br_write_inquiry_scan_activity(uint16_t interval, + uint16_t window) +{ + struct bt_hci_cp_write_inquiry_scan_activity *cp; + struct net_buf *buf; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + if (interval < BT_BR_SCAN_INTERVAL_MIN || + interval > BT_BR_SCAN_INTERVAL_MAX) + { + return -EINVAL; + } + + if (window < BT_BR_SCAN_WINDOW_MIN || window > BT_BR_SCAN_WINDOW_MAX) + { + return -EINVAL; + } + + if (interval < window) + { + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (buf == NULL) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->interval = sys_cpu_to_le16(interval); + cp->window = sys_cpu_to_le16(window); + + return bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_INQUIRY_SCAN_ACTIVITY, buf, + NULL); +} + +static int bt_br_write_page_scan_type(uint8_t type) +{ + struct bt_hci_cp_write_page_scan_type *cp; + struct net_buf *buf; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + if (type != BT_BR_SCAN_TYPE_STANDARD && type != BT_BR_SCAN_TYPE_INTERLACED) + { + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (buf == NULL) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->type = type; + + return bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_PAGE_SCAN_TYPE, buf, NULL); +} + +static int bt_br_write_inquiry_scan_type(uint8_t type) +{ + struct bt_hci_cp_write_inquiry_scan_type *cp; + struct net_buf *buf; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + if (type != BT_BR_SCAN_TYPE_STANDARD && type != BT_BR_SCAN_TYPE_INTERLACED) + { + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (buf == NULL) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->type = type; + + return bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_INQUIRY_SCAN_TYPE, buf, NULL); +} + +int bt_br_page_scan_update_param(const struct bt_br_page_scan_param *param) +{ + int err; + + if (param == NULL) + { + return -EINVAL; + } + + err = bt_br_write_page_scan_activity(param->interval, param->window); + if (err != 0) + { + LOG_ERR("write page scan activity failed (err %d)", err); + return err; + } + + err = bt_br_write_page_scan_type(param->type); + if (err != 0) + { + LOG_ERR("write page scan type failed (err %d)", err); + return err; + } + + return 0; +} + +int bt_br_inquiry_scan_update_param( + const struct bt_br_inquiry_scan_param *param) +{ + int err; + + if (param == NULL) + { + return -EINVAL; + } + + err = bt_br_write_inquiry_scan_activity(param->interval, param->window); + if (err != 0) + { + LOG_ERR("write inquiry scan activity failed (err %d)", err); + return err; + } + + err = bt_br_write_inquiry_scan_type(param->type); + if (err != 0) + { + LOG_ERR("write inquiry scan type failed (err %d)", err); + return err; + } + + return 0; +} + +int bt_br_set_class_of_device(uint32_t cod) +{ + int err; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + /* Limited discoverable mode could be set with bt_br_set_discoverable */ + + if (cod & BT_COD_MAJOR_SVC_CLASS_LIMITED_DISCOVER) + { + LOG_ERR("Cannot set limited discoverable bit directly in COD"); + return -EINVAL; + } + + /* If limited discoverable mode is set, then it should be set in COD. + * If it is not set, then clear the bit from COD. + */ + + if (atomic_test_bit(bt_dev.flags, BT_DEV_LIMITED_DISCOVERABLE_MODE)) + { + cod |= BT_COD_MAJOR_SVC_CLASS_LIMITED_DISCOVER; + } + else + { + cod &= ~BT_COD_MAJOR_SVC_CLASS_LIMITED_DISCOVER; + } + + err = bt_br_write_cod(cod); + if (err != 0) + { + LOG_ERR("write cod:0x%06x failed (err %d)", cod, err); + return err; + } + + return 0; +} + +int bt_br_get_class_of_device(uint32_t *cod) +{ + int err; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + err = bt_br_read_cod(cod); + if (err != 0) + { + LOG_ERR("read cod failed (err %d)", err); + return err; + } + + return 0; +} + +bool bt_br_bond_exists(const bt_addr_t *addr) +{ + struct bt_keys_link_key *key = bt_keys_find_link_key(addr); + + /* if there are any keys stored then device is bonded */ + + return key != NULL; +} + +static void unpair(const bt_addr_t *addr) +{ + struct bt_conn *conn = bt_conn_lookup_addr_br(addr); + struct bt_conn_auth_info_cb *listener; + struct bt_conn_auth_info_cb *next; + + if (conn) + { + bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + bt_conn_unref(conn); + } + + bt_keys_link_key_clear_addr(addr); + + list_for_every_container_safe(&bt_auth_info_cbs, listener, next, node) + { + if (listener->br_bond_deleted) + { + listener->br_bond_deleted(addr); + } + } +} + +static void bt_br_unpair_remote(const struct bt_br_bond_info *info, + void *data) +{ + ARG_UNUSED(data); + + unpair(&info->addr); +} + +int bt_br_unpair(const bt_addr_t *addr) +{ + if (!addr || bt_addr_eq(addr, BT_ADDR_ANY)) + { + bt_br_foreach_bond(bt_br_unpair_remote, NULL); + } + else + { + unpair(addr); + } + + return 0; +} + +int bt_br_write_local_name(const char *name) +{ + struct net_buf *buf; + struct bt_hci_write_local_name *name_cp; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (buf == NULL) + { + return -ENOBUFS; + } + + if (net_buf_tailroom(buf) < sizeof(*name_cp)) + { + net_buf_unref(buf); + return -ENOMEM; + } + + name_cp = net_buf_add(buf, sizeof(*name_cp)); + strncpy((char *)name_cp->local_name, name, sizeof(name_cp->local_name)); + + return bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_LOCAL_NAME, buf, NULL); +} + +int bt_br_write_eir(const struct bt_data *eir, + size_t eir_count, bool fec_required) +{ + struct bt_hci_cp_write_ext_inquiry_response *cp; + struct net_buf *buf; + size_t offset = 0; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (buf == NULL) + { + return -ENOBUFS; + } + + if (net_buf_tailroom(buf) < sizeof(*cp)) + { + net_buf_unref(buf); + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->fec_required = fec_required ? 0x01 : 0x00; + (void)memset(cp->eir, 0, sizeof(cp->eir)); + + for (size_t i = 0; i < eir_count; i++) + { + if (offset + + BT_DATA_SERIALIZED_SIZE( + eir[i].data_len) > BT_HCI_EIR_MAX_DATA_LEN) + { + net_buf_unref(buf); + return -EINVAL; + } + + offset += bt_data_serialize(&eir[i], &cp->eir[offset]); + } + + return bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_EXT_INQUIRY_RESPONSE, buf, + NULL); +} diff --git a/libs/zblue/host/classic/br.h b/libs/zblue/host/classic/br.h new file mode 100644 index 0000000000000..81a32d379aaeb --- /dev/null +++ b/libs/zblue/host/classic/br.h @@ -0,0 +1,20 @@ +/**************************************************************************** + * libs/zblue/host/classic/br.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2025 Xiaomi Corporation Copyright (c) 2017-2021 Nordic + * Semiconductor ASA Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +int bt_br_init(void); + +void bt_br_discovery_reset(void); + +bool bt_br_update_sec_level(struct bt_conn *conn); + +int bt_br_write_local_name(const char *name); diff --git a/libs/zblue/host/classic/conn_br.c b/libs/zblue/host/classic/conn_br.c new file mode 100644 index 0000000000000..767309e208d73 --- /dev/null +++ b/libs/zblue/host/classic/conn_br.c @@ -0,0 +1,461 @@ +/**************************************************************************** + * libs/zblue/host/classic/conn_br.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* conn_br.c - Bluetooth Classic connection handling */ + +/* Copyright (c) 2015-2016 Intel Corporation + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include "ssp.h" +#include "sco_internal.h" + +#define LOG_LEVEL CONFIG_BT_CONN_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_conn_br); + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_sco_cleanup(struct bt_conn *sco_conn) +{ + bt_sco_cleanup_acl(sco_conn); + bt_conn_unref(sco_conn); +} + +struct bt_conn *bt_conn_create_br(const bt_addr_t *peer, + const struct bt_br_conn_param *param) +{ + struct bt_hci_cp_connect *cp; + struct bt_conn *conn; + struct net_buf *buf; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return NULL; + } + + conn = bt_conn_lookup_addr_br(peer); + if (conn) + { + switch (conn->state) + { + case BT_CONN_INITIATING: + case BT_CONN_CONNECTED: + return conn; + default: + bt_conn_unref(conn); + return NULL; + } + } + + conn = bt_conn_add_br(peer); + if (!conn) + { + return NULL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + bt_conn_unref(conn); + return NULL; + } + + cp = net_buf_add(buf, sizeof(*cp)); + + (void)memset(cp, 0, sizeof(*cp)); + + memcpy(&cp->bdaddr, peer, sizeof(cp->bdaddr)); + cp->packet_type = sys_cpu_to_le16(0xcc18); /* DM1 DH1 DM3 DH5 DM5 DH5 */ + cp->pscan_rep_mode = 0x02; /* R2 */ + cp->allow_role_switch = param->allow_role_switch ? 0x01 : 0x00; + cp->clock_offset = 0x0000; /* TODO used cached clock offset */ + + bt_conn_set_state(conn, BT_CONN_INITIATING); + conn->role = BT_CONN_ROLE_CENTRAL; + + if (bt_hci_cmd_send_sync(BT_HCI_OP_CONNECT, buf, NULL) < 0) + { + bt_conn_unref(conn); + return NULL; + } + + return conn; +} + +int bt_hci_connect_br_cancel(struct bt_conn *conn) +{ + struct bt_hci_cp_connect_cancel *cp; + struct bt_hci_rp_connect_cancel *rp; + struct net_buf *buf; + struct net_buf *rsp; + int err; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + memcpy(&cp->bdaddr, &conn->br.dst, sizeof(cp->bdaddr)); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_CONNECT_CANCEL, buf, &rsp); + if (err) + { + return err; + } + + rp = (void *)rsp->data; + + err = rp->status ? -EIO : 0; + + net_buf_unref(rsp); + + return err; +} + +const bt_addr_t *bt_conn_get_dst_br(const struct bt_conn *conn) +{ + if (conn == NULL) + { + LOG_DBG("Invalid connect"); + return NULL; + } + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_BR)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return NULL; + } + + return &conn->br.dst; +} + +void bt_br_acl_recv(struct bt_conn *conn, struct net_buf *buf, bool complete) +{ + uint16_t acl_total_len; + struct bt_l2cap_hdr *hdr; + struct net_buf_simple_state state; + + do + { + uint16_t l2cap_len; + + net_buf_simple_save(&buf->b, &state); + + hdr = (void *)buf->data; + l2cap_len = sys_le16_to_cpu(hdr->len); + if (l2cap_len > UINT16_MAX - sizeof(*hdr)) + { + LOG_ERR("L2CAP PDU length overflow"); + break; + } + + acl_total_len = l2cap_len + sizeof(*hdr); + if (buf->len > acl_total_len) + { + LOG_DBG("Multiple L2CAP packet (%u > " + "%u)", buf->len, acl_total_len); + buf->len = acl_total_len; + } + else if (buf->len < acl_total_len) + { + LOG_ERR("Short packet (%u < %u)", buf->len, acl_total_len); + break; + } + + bt_l2cap_recv(conn, net_buf_ref(buf), complete); + + net_buf_simple_restore(&buf->b, &state); + net_buf_pull(buf, acl_total_len); + } + while (buf->len > 0); + + net_buf_unref(buf); +} + +int bt_conn_br_switch_role(const struct bt_conn *conn, uint8_t role) +{ + struct net_buf *buf; + struct bt_hci_cp_switch_role *cp; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + return -EINVAL; + } + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_BR)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + bt_addr_copy(&cp->bdaddr, &conn->br.dst); + cp->role = role; + + return bt_hci_cmd_send_sync(BT_HCI_OP_SWITCH_ROLE, buf, NULL); +} + +static int bt_conn_br_read_link_policy_settings(const struct bt_conn *conn, + uint16_t *link_policy_settings) +{ + int err; + struct net_buf *buf; + struct bt_hci_cp_read_link_policy_settings *cp; + struct bt_hci_rp_read_link_policy_settings *rp; + struct net_buf *rsp; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_LINK_POLICY_SETTINGS, buf, &rsp); + if (!err) + { + rp = (void *)rsp->data; + *link_policy_settings = rp->link_policy_settings; + } + + return err; +} + +static int bt_conn_br_write_link_policy_settings(const struct bt_conn *conn, + uint16_t link_policy_settings) +{ + struct net_buf *buf; + struct bt_hci_cp_write_link_policy_settings *cp; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + cp->link_policy_settings = link_policy_settings; + + return bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_LINK_POLICY_SETTINGS, buf, + NULL); +} + +int bt_conn_br_set_role_switch_enable(const struct bt_conn *conn, + bool enable) +{ + int err; + uint16_t link_policy_settings; + bool is_enabled; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + return -EINVAL; + } + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_BR)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + err = bt_conn_br_read_link_policy_settings(conn, &link_policy_settings); + if (err) + { + return err; + } + + is_enabled = + (link_policy_settings & + BT_HCI_LINK_POLICY_SETTINGS_ENABLE_ROLE_SWITCH); + if (enable == is_enabled) + { + return 0; + } + + link_policy_settings ^= BT_HCI_LINK_POLICY_SETTINGS_ENABLE_ROLE_SWITCH; + return bt_conn_br_write_link_policy_settings(conn, link_policy_settings); +} + +int bt_conn_br_get_supervision_timeout(struct bt_conn *conn, + uint16_t *timeout) +{ + struct bt_hci_cp_read_link_supervision_timeout *cp; + struct bt_hci_rp_read_link_supervision_timeout *rp; + struct net_buf *buf; + struct net_buf *rsp; + int err; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + return -EINVAL; + } + + if (timeout == NULL) + { + LOG_DBG("timeout pointer is NULL"); + return -EINVAL; + } + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_BR)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (buf == NULL) + { + return -ENOBUFS; + } + + if (net_buf_tailroom(buf) < sizeof(*cp)) + { + net_buf_unref(buf); + return -ENOMEM; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_LINK_SUPERVISION_TIMEOUT, buf, + &rsp); + if (err != 0) + { + return err; + } + + rp = (void *)rsp->data; + *timeout = sys_le16_to_cpu(rp->timeout); + net_buf_unref(rsp); + + return 0; +} + +int bt_conn_br_set_supervision_timeout(struct bt_conn *conn, + uint16_t timeout) +{ + struct bt_hci_cp_write_link_supervision_timeout *cp; + struct net_buf *buf; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + return -EINVAL; + } + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_BR)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (buf == NULL) + { + return -ENOBUFS; + } + + if (net_buf_tailroom(buf) < sizeof(*cp)) + { + net_buf_unref(buf); + return -ENOMEM; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + cp->timeout = sys_cpu_to_le16(timeout); + + return bt_hci_cmd_send_sync(BT_HCI_OP_WRITE_LINK_SUPERVISION_TIMEOUT, buf, + NULL); +} + +int bt_conn_br_change_packet_type(const struct bt_conn *conn, + uint16_t packet_type) +{ + struct bt_hci_cp_change_conn_packet_type *cp; + struct net_buf *buf; + + if (conn == NULL) + { + LOG_DBG("conn is NULL"); + return -EINVAL; + } + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_BR)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + if ((packet_type & BT_HCI_ACL_PKT_TYPE_BR_MASK) == 0) + { + LOG_DBG("No BR packet type set in 0x%04x", packet_type); + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (buf == NULL) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + cp->packet_type = sys_cpu_to_le16(packet_type); + + return bt_hci_cmd_send_sync(BT_HCI_OP_CHANGE_CONN_PACKET_TYPE, buf, NULL); +} diff --git a/libs/zblue/host/classic/conn_br_internal.h b/libs/zblue/host/classic/conn_br_internal.h new file mode 100644 index 0000000000000..4c0ef5fab653a --- /dev/null +++ b/libs/zblue/host/classic/conn_br_internal.h @@ -0,0 +1,21 @@ +/**************************************************************************** + * libs/zblue/host/classic/conn_br_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Internal APIs for Bluetooth classic connection handling. + */ + +/* Copyright (c) 2015 Intel Corporation Copyright (c) 2021 Nordic + * Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +int bt_hci_connect_br_cancel(struct bt_conn *conn); + +void bt_br_acl_recv(struct bt_conn *conn, + struct net_buf *buf, bool complete); diff --git a/libs/zblue/host/classic/did.c b/libs/zblue/host/classic/did.c new file mode 100644 index 0000000000000..34c61e691098f --- /dev/null +++ b/libs/zblue/host/classic/did.c @@ -0,0 +1,91 @@ +/**************************************************************************** + * libs/zblue/host/classic/did.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright 2025 Xiaomi Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#include "did_internal.h" + +#include +LOG_MODULE_REGISTER(bt_did); + +#define DID_VER_1_3 (0x0103u) + +#define BLUETOOTH_DEVICE_IDENTIFY_SPEC_VERSION 0x0103 +#define BLUETOOTH_DEVICE_IDENTIFY_ATTR_VERSION 0x0100 + +#define BLUETOOTH_DEVICE_VENDOR_ID_SOURCE_BTSIG 0x0001 /* Bluetooth SIG assigned */ +#define BLUETOOTH_DEVICE_VENDOR_ID_SOURCE_USBIF 0x0002 /* USB Implementer's Forum */ + +static struct bt_sdp_attribute did_attrs[] = +{ + BT_SDP_NEW_SERVICE, + BT_SDP_LIST(BT_SDP_ATTR_SVCLASS_ID_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PNP_INFO_SVCLASS) + }, + + ) + ), + + BT_SDP_LIST(BT_SDP_ATTR_SPECIFICATION_ID, + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BLUETOOTH_DEVICE_IDENTIFY_SPEC_VERSION)), + BT_SDP_LIST(BT_SDP_ATTR_VENDOR_ID, BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(CONFIG_BT_DEVICE_VENDOR_ID)), + BT_SDP_LIST(BT_SDP_ATTR_PRODUCT_ID, BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(CONFIG_BT_DEVICE_PRODUCT_ID)), + BT_SDP_LIST(BT_SDP_ATTR_VERSION, BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(CONFIG_BT_DEVICE_VERSION)), + BT_SDP_LIST(BT_SDP_ATTR_PRIMARY_RECORD, + BT_SDP_TYPE_SIZE(BT_SDP_BOOL), + BT_SDP_ARRAY_8(1)), + BT_SDP_LIST(BT_SDP_ATTR_VENDOR_ID_SOURCE, + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BLUETOOTH_DEVICE_VENDOR_ID_SOURCE_BTSIG)), +}; + +static struct bt_sdp_record did_rec = BT_SDP_RECORD(did_attrs); + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_did_init(void) +{ + __maybe_unused int err; + + static bool initialized; + + if (initialized) + { + return; + } + + err = bt_sdp_register_service(&did_rec); + if (err != 0) + { + LOG_ERR("Failed to register DID SDP record (err %d)", err); + return; + } + + initialized = true; +} diff --git a/libs/zblue/host/classic/did_internal.h b/libs/zblue/host/classic/did_internal.h new file mode 100644 index 0000000000000..fe93ff24a831b --- /dev/null +++ b/libs/zblue/host/classic/did_internal.h @@ -0,0 +1,19 @@ +/**************************************************************************** + * libs/zblue/host/classic/did_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Device Identification Protocol handling. + */ + +/* Copyright 2025 Xiaomi Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** Device Identification Init. **/ + +void bt_did_init(void); diff --git a/libs/zblue/host/classic/goep.c b/libs/zblue/host/classic/goep.c new file mode 100644 index 0000000000000..06c2b8565927e --- /dev/null +++ b/libs/zblue/host/classic/goep.c @@ -0,0 +1,737 @@ +/**************************************************************************** + * libs/zblue/host/classic/goep.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* goep.c - Bluetooth Generic Object Exchange Profile handling */ + +/* Copyright 2024-2026 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include "l2cap_br_internal.h" +#include "rfcomm_internal.h" +#include "obex_internal.h" + +#define LOG_LEVEL CONFIG_BT_GOEP_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_goep); + +#define GOEP_MIN_MTU BT_OBEX_MIN_MTU + +/* RFCOMM Server list */ + +static struct list_node goep_rfcomm_server = + LIST_INITIAL_VALUE(goep_rfcomm_server); + +#define GOEP_GET_TRANSPORT_V1(_dlc) CONTAINER_OF((_dlc), struct bt_goep_transport_v1, dlc) + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int goep_rfcomm_recv(struct bt_rfcomm_dlc *dlc, struct net_buf *buf) +{ + struct bt_goep_transport_v1 + *goep_transport_v1 = GOEP_GET_TRANSPORT_V1(dlc); + struct bt_goep *goep = goep_transport_v1->goep; + int err; + + err = bt_obex_recv(&goep->obex, buf); + if (err != 0) + { + LOG_WRN("Fail to handle OBEX packet (err %d)", err); + } + + return 0; +} + +static void goep_rfcomm_connected(struct bt_rfcomm_dlc *dlc) +{ + struct bt_goep_transport_v1 + *goep_transport_v1 = GOEP_GET_TRANSPORT_V1(dlc); + struct bt_goep *goep = goep_transport_v1->goep; + int err; + + LOG_DBG("RFCOMM %p connected", dlc); + + if (dlc->mtu < GOEP_MIN_MTU) + { + LOG_WRN("Invalid MTU %d", dlc->mtu); + bt_rfcomm_dlc_disconnect(dlc); + return; + } + + goep->obex.rx.mtu = dlc->mtu; + goep->obex.tx.mtu = dlc->mtu; + + atomic_set(&goep->_state, BT_GOEP_TRANSPORT_CONNECTED); + + err = bt_obex_transport_connected(&goep->obex); + if (err != 0) + { + LOG_WRN("Notify OBEX (err %d). Disconnecting transport...", err); + bt_rfcomm_dlc_disconnect(dlc); + return; + } + + if (goep->transport_ops->connected) + { + goep->transport_ops->connected(goep->_acl, goep); + } +} + +static void goep_rfcomm_disconnected(struct bt_rfcomm_dlc *dlc) +{ + struct bt_goep_transport_v1 + *goep_transport_v1 = GOEP_GET_TRANSPORT_V1(dlc); + struct bt_goep *goep = goep_transport_v1->goep; + int err; + + LOG_DBG("RFCOMM %p disconnected", dlc); + + atomic_set(&goep->_state, BT_GOEP_TRANSPORT_DISCONNECTED); + + err = bt_obex_transport_disconnected(&goep->obex); + if (err != 0) + { + LOG_WRN("Notify OBEX (err %d).", err); + } + + if (goep->transport_ops->disconnected) + { + goep->transport_ops->disconnected(goep); + } +} + +static struct bt_rfcomm_dlc_ops goep_rfcomm_ops = +{ + .recv = goep_rfcomm_recv, + .connected = goep_rfcomm_connected, + .disconnected = goep_rfcomm_disconnected, +}; + +static int goep_rfcomm_send(struct bt_obex *obex, struct net_buf *buf) +{ + struct bt_goep *goep = CONTAINER_OF(obex, struct bt_goep, obex); + struct bt_goep_transport_v1 *goep_transport_v1; + int err; + + if (goep->v1 == NULL) + { + LOG_WRN("Invalid transport"); + return -EINVAL; + } + + if (buf->len > obex->tx.mtu) + { + LOG_WRN("Packet size exceeds MTU"); + return -EMSGSIZE; + } + + if (net_buf_tailroom(buf) < BT_RFCOMM_FCS_SIZE) + { + LOG_WRN("No tailroom for RFCOMM FCS field"); + return -EMSGSIZE; + } + + goep_transport_v1 = goep->v1; + + err = bt_rfcomm_dlc_send(&goep_transport_v1->dlc, buf); + if (err < 0) + { + return err; + } + + return 0; +} + +static struct net_buf *goep_rfcomm_alloc_buf(struct bt_obex *obex, + struct net_buf_pool *pool) +{ + struct bt_goep *goep = CONTAINER_OF(obex, struct bt_goep, obex); + + if (goep->v1 == NULL) + { + LOG_WRN("Invalid transport"); + return NULL; + } + + return bt_goep_create_pdu(goep, pool); +} + +static int goep_rfcomm_disconnect(struct bt_obex *obex) +{ + struct bt_goep *goep = CONTAINER_OF(obex, struct bt_goep, obex); + struct bt_goep_transport_v1 *goep_transport_v1; + + if (goep->v1 == NULL) + { + LOG_WRN("Invalid transport"); + return -EINVAL; + } + + goep_transport_v1 = goep->v1; + + return bt_rfcomm_dlc_disconnect(&goep_transport_v1->dlc); +} + +static const struct bt_obex_transport_ops goep_rfcomm_transport_ops = +{ + .alloc_buf = goep_rfcomm_alloc_buf, + .send = goep_rfcomm_send, + .disconnect = goep_rfcomm_disconnect, +}; + +static void goep_rfcomm_init(struct bt_conn *conn, struct bt_goep *goep) +{ + struct bt_goep_transport_v1 *goep_transport_v1 = goep->v1; + uint32_t mtu; + uint32_t hdr_size; + + hdr_size = BT_L2CAP_HDR_SIZE + BT_RFCOMM_OVERHEAD_SIZE; + + mtu = CONFIG_BT_GOEP_RFCOMM_MTU - hdr_size; + + /* Set the default MTU to the largest value that the configuration can + * support + */ + + goep->obex.rx.mtu = mtu; + + DEBUGASSERT(goep->obex.rx.mtu >= GOEP_MIN_MTU); + + bt_obex_reg_transport(&goep->obex, &goep_rfcomm_transport_ops); + + goep->_acl = conn; + goep_transport_v1->goep = goep; + goep_transport_v1->dlc.mtu = goep->obex.rx.mtu; + goep_transport_v1->dlc.ops = &goep_rfcomm_ops; + goep_transport_v1->dlc.required_sec_level = BT_SECURITY_L2; +} + +static int goep_rfcomm_accept( + struct bt_conn *conn, struct bt_rfcomm_server *server, + struct bt_rfcomm_dlc **dlc) +{ + struct bt_goep_transport_rfcomm_server *rfcomm_server; + struct bt_goep *goep = NULL; + int err; + + rfcomm_server = + CONTAINER_OF(server, struct bt_goep_transport_rfcomm_server, + rfcomm); + + if (!list_find(&goep_rfcomm_server, &rfcomm_server->node, NULL)) + { + LOG_WRN("Invalid rfcomm server"); + return -ENOMEM; + } + + err = rfcomm_server->accept(conn, rfcomm_server, &goep); + if (err != 0) + { + LOG_DBG("Incoming connection rejected"); + return err; + } + + DEBUGASSERT(goep != NULL && goep->v1 != NULL && goep->v2 == NULL && + goep->transport_ops != NULL); + + goep_rfcomm_init(conn, goep); + + *dlc = &goep->v1->dlc; + + atomic_set(&goep->_state, BT_GOEP_TRANSPORT_CONNECTING); + + return 0; +} + +int bt_goep_transport_rfcomm_server_register( + struct bt_goep_transport_rfcomm_server *server) +{ + int err; + + if (server == NULL || server->accept == NULL) + { + LOG_DBG("Invalid parameter"); + return -EINVAL; + } + + if (list_find(&goep_rfcomm_server, &server->node, NULL)) + { + LOG_WRN("RFCOMM server has been registered"); + return -EEXIST; + } + + server->rfcomm.accept = goep_rfcomm_accept; + err = bt_rfcomm_server_register(&server->rfcomm); + if (err != 0) + { + LOG_WRN("Fail to register RFCOMM Server %p", server); + return err; + } + + LOG_DBG("Register RFCOMM server %p", server); + list_add_tail(&goep_rfcomm_server, &server->node); + + return 0; +} + +int bt_goep_transport_rfcomm_connect(struct bt_conn *conn, + struct bt_goep *goep, uint8_t channel) +{ + int err; + + if (conn == NULL || goep == NULL || goep->v1 == NULL || goep->v2 != NULL || + goep->transport_ops == NULL) + { + LOG_DBG("Invalid parameter"); + return -EINVAL; + } + + goep_rfcomm_init(conn, goep); + + err = bt_rfcomm_dlc_connect(conn, &goep->v1->dlc, channel); + if (err != 0) + { + LOG_ERR("Fail to create RFCOMM connection"); + return err; + } + + atomic_set(&goep->_state, BT_GOEP_TRANSPORT_CONNECTING); + + return 0; +} + +int bt_goep_transport_rfcomm_disconnect(struct bt_goep *goep) +{ + int err; + uint32_t state; + + if (goep == NULL || goep->v1 == NULL) + { + LOG_DBG("Invalid parameter"); + return -EINVAL; + } + + state = atomic_read(&goep->_state); + if (state != BT_GOEP_TRANSPORT_CONNECTED) + { + LOG_DBG("Invalid stats %d", state); + return -ENOTCONN; + } + + err = bt_rfcomm_dlc_disconnect(&goep->v1->dlc); + if (err != 0) + { + LOG_WRN("Fail to disconnect RFCOMM DLC"); + return err; + } + + atomic_set(&goep->_state, BT_GOEP_TRANSPORT_DISCONNECTING); + + return 0; +} + +/* L2CAP Server list */ + +static struct list_node goep_l2cap_server = + LIST_INITIAL_VALUE(goep_l2cap_server); + +#define GOEP_GET_TRANSPORT_V2(_chan) CONTAINER_OF((_chan), struct bt_goep_transport_v2, chan.chan) + +NET_BUF_POOL_DEFINE(goep_rx_pool, + BT_BUF_ACL_RX_COUNT, + BT_BUF_ACL_SIZE(CONFIG_BT_BUF_ACL_RX_SIZE), + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + +static int goep_l2cap_recv(struct bt_l2cap_chan *chan, struct net_buf *buf) +{ + struct bt_goep_transport_v2 *goep_transport_v2 = + GOEP_GET_TRANSPORT_V2(chan); + struct bt_goep *goep = goep_transport_v2->goep; + int err; + + err = bt_obex_recv(&goep->obex, buf); + if (err != 0) + { + LOG_WRN("Fail to handle OBEX packet (err %d)", err); + } + + return err; +} + +static void goep_l2cap_connected(struct bt_l2cap_chan *chan) +{ + struct bt_goep_transport_v2 *goep_transport_v2 = + GOEP_GET_TRANSPORT_V2(chan); + struct bt_goep *goep = goep_transport_v2->goep; + int err; + + LOG_DBG("L2CAP channel %p connected", chan); + + if ((goep_transport_v2->chan.tx.mtu < GOEP_MIN_MTU) || + (goep_transport_v2->chan.rx.mtu < GOEP_MIN_MTU)) + { + LOG_WRN("Invalid MTU (local %d, peer %d)", + goep_transport_v2->chan.rx.mtu, + goep_transport_v2->chan.tx.mtu); + bt_l2cap_chan_disconnect(chan); + return; + } + + goep->obex.rx.mtu = goep_transport_v2->chan.rx.mtu; + goep->obex.tx.mtu = goep_transport_v2->chan.tx.mtu; + + atomic_set(&goep->_state, BT_GOEP_TRANSPORT_CONNECTED); + + err = bt_obex_transport_connected(&goep->obex); + if (err != 0) + { + LOG_WRN("Notify OBEX (err %d). Disconnecting transport...", err); + bt_l2cap_chan_disconnect(chan); + return; + } + + if (goep->transport_ops->connected) + { + goep->transport_ops->connected(goep->_acl, goep); + } +} + +static void goep_l2cap_disconnected(struct bt_l2cap_chan *chan) +{ + struct bt_goep_transport_v2 *goep_transport_v2 = + GOEP_GET_TRANSPORT_V2(chan); + struct bt_goep *goep = goep_transport_v2->goep; + int err; + + LOG_DBG("L2CAP channel %p disconnected", chan); + + atomic_set(&goep->_state, BT_GOEP_TRANSPORT_DISCONNECTED); + + err = bt_obex_transport_disconnected(&goep->obex); + if (err != 0) + { + LOG_WRN("Notify OBEX (err %d).", err); + } + + if (goep->transport_ops->disconnected) + { + goep->transport_ops->disconnected(goep); + } +} + +static struct net_buf *l2cap_alloc_buf(struct bt_l2cap_chan *chan) +{ + struct net_buf *buf; + + /* Called from the Bluetooth RX workqueue, which is also the context that + * releases these buffers once an SDU has been delivered, so waiting here + * could never be satisfied. + */ + + buf = net_buf_alloc(&goep_rx_pool, 0); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + } + + return buf; +} + +static const struct bt_l2cap_chan_ops goep_l2cap_ops = +{ + .alloc_buf = l2cap_alloc_buf, + .recv = goep_l2cap_recv, + .connected = goep_l2cap_connected, + .disconnected = goep_l2cap_disconnected, +}; + +static int goep_l2cap_send(struct bt_obex *obex, struct net_buf *buf) +{ + struct bt_goep *goep = CONTAINER_OF(obex, struct bt_goep, obex); + struct bt_goep_transport_v2 *goep_transport_v2; + + if (goep->v2 == NULL) + { + LOG_WRN("Invalid transport"); + return -EINVAL; + } + + if (buf->len > obex->tx.mtu) + { + LOG_WRN("Packet size exceeds MTU"); + return -EMSGSIZE; + } + + goep_transport_v2 = goep->v2; + + return bt_l2cap_chan_send(&goep_transport_v2->chan.chan, buf); +} + +static struct net_buf *goep_l2cap_alloc_buf(struct bt_obex *obex, + struct net_buf_pool *pool) +{ + struct bt_goep *goep = CONTAINER_OF(obex, struct bt_goep, obex); + + if (goep->v2 == NULL) + { + LOG_WRN("Invalid transport"); + return NULL; + } + + return bt_goep_create_pdu(goep, pool); +} + +static int goep_l2cap_disconnect(struct bt_obex *obex) +{ + struct bt_goep *goep = CONTAINER_OF(obex, struct bt_goep, obex); + struct bt_goep_transport_v2 *goep_transport_v2; + + if (goep->v2 == NULL) + { + LOG_WRN("Invalid transport"); + return -EINVAL; + } + + goep_transport_v2 = goep->v2; + + return bt_l2cap_chan_disconnect(&goep_transport_v2->chan.chan); +} + +static const struct bt_obex_transport_ops goep_l2cap_transport_ops = +{ + .alloc_buf = goep_l2cap_alloc_buf, + .send = goep_l2cap_send, + .disconnect = goep_l2cap_disconnect, +}; + +static void goep_l2cap_init(struct bt_conn *conn, struct bt_goep *goep) +{ + struct bt_goep_transport_v2 *goep_transport_v2 = goep->v2; + uint32_t mtu; + uint32_t hdr_size; + + hdr_size = sizeof(struct bt_l2cap_hdr); + + mtu = CONFIG_BT_GOEP_L2CAP_MTU - hdr_size; + + /* Set the default MTU to the largest value that the configuration can + * support + */ + + goep->obex.rx.mtu = mtu; + + DEBUGASSERT(goep->obex.rx.mtu >= GOEP_MIN_MTU); + + bt_obex_reg_transport(&goep->obex, &goep_l2cap_transport_ops); + + goep->_acl = conn; + goep_transport_v2->goep = goep; + goep_transport_v2->chan.rx.mode = BT_L2CAP_BR_LINK_MODE_ERET; + goep_transport_v2->chan.rx.optional = false; + goep_transport_v2->chan.rx.max_transmit = 3; + goep_transport_v2->chan.rx.mtu = goep->obex.rx.mtu; + goep_transport_v2->chan.rx.extended_control = false; + /* There is an issue found that the FCS option is not correctly set when + * connecting to an iphone when the profile is PBAP or MAP. The issue is + * that iphone expects the No FCS option will be applied while the 'No FCS' + * option is not added in an L2CAP_CONFIGURATION_REQ packet sent by the + * iphone. Force 16 bits FCS option for enhance retransmission mode by + * default. + */ + + goep_transport_v2->chan.rx.fcs = BT_L2CAP_BR_FCS_16BIT; + goep_transport_v2->chan.chan.ops = &goep_l2cap_ops; + goep_transport_v2->chan.required_sec_level = BT_SECURITY_L2; +} + +static int goep_l2cap_accept( + struct bt_conn *conn, struct bt_l2cap_server *server, + struct bt_l2cap_chan **chan) +{ + struct bt_goep_transport_l2cap_server *l2cap_server; + struct bt_goep *goep = NULL; + int err; + + l2cap_server = CONTAINER_OF(server, struct bt_goep_transport_l2cap_server, + l2cap); + + if (!list_find(&goep_l2cap_server, &l2cap_server->node, NULL)) + { + LOG_WRN("Invalid l2cap server"); + return -ENOMEM; + } + + err = l2cap_server->accept(conn, l2cap_server, &goep); + if (err != 0) + { + LOG_DBG("Incoming connection rejected"); + return err; + } + + DEBUGASSERT(goep != NULL && goep->v2 != NULL && goep->v1 == NULL && + goep->transport_ops != NULL); + + goep_l2cap_init(conn, goep); + + *chan = &goep->v2->chan.chan; + atomic_set(&goep->_state, BT_GOEP_TRANSPORT_CONNECTING); + + return 0; +} + +int bt_goep_transport_l2cap_server_register( + struct bt_goep_transport_l2cap_server *server) +{ + int err; + + if (server == NULL || server->accept == NULL) + { + LOG_DBG("Invalid parameter"); + return -EINVAL; + } + + if (list_find(&goep_l2cap_server, &server->node, NULL)) + { + LOG_WRN("L2CAP server has been registered"); + return -EEXIST; + } + + server->l2cap.accept = goep_l2cap_accept; + err = bt_l2cap_br_server_register(&server->l2cap); + if (err != 0) + { + LOG_WRN("Fail to register L2CAP Server %p", server); + return err; + } + + LOG_DBG("Register L2CAP server %p", server); + list_add_tail(&goep_l2cap_server, &server->node); + + return 0; +} + +int bt_goep_transport_l2cap_connect(struct bt_conn *conn, + struct bt_goep *goep, uint16_t psm) +{ + int err; + uint32_t state; + + if (conn == NULL || goep == NULL || goep->v2 == NULL || goep->v1 != NULL || + goep->transport_ops == NULL) + { + LOG_DBG("Invalid parameter"); + return -EINVAL; + } + + state = atomic_read(&goep->_state); + if (state != BT_GOEP_TRANSPORT_DISCONNECTED) + { + LOG_DBG("Invalid stats %d", state); + return -EBUSY; + } + + goep_l2cap_init(conn, goep); + + err = bt_l2cap_chan_connect(conn, &goep->v2->chan.chan, psm); + if (err != 0) + { + LOG_ERR("Fail to create L2CAP connection"); + return err; + } + + atomic_set(&goep->_state, BT_GOEP_TRANSPORT_CONNECTING); + + return 0; +} + +int bt_goep_transport_l2cap_disconnect(struct bt_goep *goep) +{ + int err; + uint32_t state; + + if (goep == NULL || goep->v2 == NULL) + { + LOG_DBG("Invalid parameter"); + return -EINVAL; + } + + state = atomic_read(&goep->_state); + if (state != BT_GOEP_TRANSPORT_CONNECTED) + { + LOG_DBG("Invalid stats %d", state); + return -ENOTCONN; + } + + err = bt_l2cap_chan_disconnect(&goep->v2->chan.chan); + if (err != 0) + { + LOG_WRN("Fail to disconnect L2CAP channel"); + return err; + } + + atomic_set(&goep->_state, BT_GOEP_TRANSPORT_DISCONNECTING); + + return 0; +} + +struct net_buf *bt_goep_create_pdu(struct bt_goep *goep, + struct net_buf_pool *pool) +{ + struct net_buf *buf; + size_t len; + + if (goep == NULL || (goep->v1 == NULL && goep->v2 == NULL) || + (goep->v1 != NULL && goep->v2 != NULL)) + { + LOG_WRN("Invalid parameter"); + return NULL; + } + + if (goep->v1 != NULL) + { + buf = bt_rfcomm_create_pdu(pool); + } + else + { + buf = bt_conn_create_pdu(pool, sizeof(struct bt_l2cap_hdr)); + } + + if (buf != NULL) + { + len = net_buf_headroom(buf); + net_buf_reserve(buf, len + BT_OBEX_SEND_BUF_RESERVE); + } + + return buf; +} diff --git a/libs/zblue/host/classic/hfp_ag.c b/libs/zblue/host/classic/hfp_ag.c new file mode 100644 index 0000000000000..843aa001a5d51 --- /dev/null +++ b/libs/zblue/host/classic/hfp_ag.c @@ -0,0 +1,7007 @@ +/**************************************************************************** + * libs/zblue/host/classic/hfp_ag.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#include +#include +#include "l2cap_br_internal.h" +#include "rfcomm_internal.h" +#include "at.h" +#include "sco_internal.h" +#include "hfp_ag_internal.h" + +#define LOG_LEVEL CONFIG_BT_HFP_AG_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_hfp_ag); + +typedef int (*bt_hfp_ag_parse_command_t)(struct bt_hfp_ag *ag, + struct net_buf *buf); + +struct bt_hfp_ag_at_cmd_handler +{ + const char *cmd; + bt_hfp_ag_parse_command_t handler; +}; + +static const struct +{ + const char *name; + const char *connector; + uint32_t min; + uint32_t max; +} + +ag_ind[] = +{ + { + "service", "-", 0, 1 + }, + + /* BT_HFP_AG_SERVICE_IND */ + + { + "call", ",", 0, 1 + }, + + /* BT_HFP_AG_CALL_IND */ + + { + "callsetup", "-", 0, 3 + }, + + /* BT_HFP_AG_CALL_SETUP_IND */ + + { + "callheld", "-", 0, 2 + }, + + /* BT_HFP_AG_CALL_HELD_IND */ + + { + "signal", "-", 0, 5 + }, + + /* BT_HFP_AG_SIGNAL_IND */ + + { + "roam", ",", 0, 1 + }, + + /* BT_HFP_AG_ROAM_IND */ + + { + "battchg", "-", 0, 5 + } + + /* BT_HFP_AG_BATTERY_IND */ +}; + +typedef void (*bt_hfp_ag_tx_cb_t)(struct bt_hfp_ag *ag, void *user_data); + +struct bt_ag_tx +{ + struct list_node node; + + struct bt_hfp_ag *ag; + struct net_buf *buf; + bt_hfp_ag_tx_cb_t cb; + void *user_data; + int err; +}; + +NET_BUF_POOL_FIXED_DEFINE(ag_pool, CONFIG_BT_HFP_AG_TX_BUF_COUNT, + BT_RFCOMM_BUF_SIZE(BT_HF_CLIENT_MAX_PDU), + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + +static struct bt_hfp_ag bt_hfp_ag_pool[CONFIG_BT_MAX_CONN]; + +static struct bt_hfp_ag_cb *bt_ag; + +#define AG_SUPT_FEAT(ag, _feature) ((ag->ag_features & (_feature)) != 0) +#define HF_SUPT_FEAT(ag, _feature) ((ag->hf_features & (_feature)) != 0) +#define BOTH_SUPT_FEAT(ag, _hf_feature, _ag_feature) \ + (HF_SUPT_FEAT(ag, _hf_feature) && AG_SUPT_FEAT(ag, _ag_feature)) + +/* Sent but not acknowledged TX packets with a callback */ + +static struct bt_ag_tx ag_tx[CONFIG_BT_HFP_AG_TX_BUF_COUNT * 2]; +static K_FIFO_DEFINE(ag_tx_free); +static K_FIFO_DEFINE(ag_tx_notify); + +#define BT_HFP_AG_VERSION BT_HFP_VERSION_1_9 + +/* HFP Gateway SDP record */ + +static struct bt_sdp_attribute hfp_ag_attrs[] = +{ + BT_SDP_NEW_SERVICE, + BT_SDP_LIST(BT_SDP_ATTR_SVCLASS_ID_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_HANDSFREE_AGW_SVCLASS) + }, + + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_GENERIC_AUDIO_SVCLASS) + }, + + ) + ), + + BT_SDP_LIST(BT_SDP_ATTR_PROTO_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 12), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) + }, + + ) + }, + + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 5), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_RFCOMM) + }, + + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT8), + BT_SDP_ARRAY_8(BT_RFCOMM_CHAN_HFP_AG) + }, + + ) + }, + + ) + ), + + BT_SDP_LIST(BT_SDP_ATTR_PROFILE_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 8), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_HANDSFREE_SVCLASS) + }, + + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_HFP_AG_VERSION) + }, + + ) + }, + + ) + ), + + BT_SDP_LIST(BT_SDP_ATTR_NETWORK, BT_SDP_TYPE_SIZE(BT_SDP_UINT8), + BT_SDP_ARRAY_8(IS_ENABLED(CONFIG_BT_HFP_AG_REJECT_CALL))), +/* The values of the ā€œSupportedFeaturesā€ bitmap shall be the same as the + * values of the Bits 0 to 4 of the AT-command AT+BRSF (see Section 5.3). + */ + + BT_SDP_SUPPORTED_FEATURES(BT_HFP_AG_SDP_SUPPORTED_FEATURES), +}; +static struct bt_sdp_record hfp_ag_rec = BT_SDP_RECORD(hfp_ag_attrs); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static enum at_cme bt_hfp_ag_get_cme_err(int err) +{ + enum at_cme cme_err; + + switch (err) + { + case -ENOEXEC: + cme_err = CME_ERROR_OPERATION_NOT_SUPPORTED; + break; + case -EFAULT: + cme_err = CME_ERROR_AG_FAILURE; + break; + case -ENOSR: + cme_err = CME_ERROR_MEMORY_FAILURE; + break; + case -ENOMEM: + case -ENOBUFS: + cme_err = CME_ERROR_MEMORY_FULL; + break; + case -ENAMETOOLONG: + cme_err = CME_ERROR_DIAL_STRING_TO_LONG; + break; + case -EINVAL: + cme_err = CME_ERROR_INVALID_INDEX; + break; + case -ENOTSUP: + cme_err = CME_ERROR_OPERATION_NOT_ALLOWED; + break; + case -ENOTCONN: + cme_err = CME_ERROR_NO_CONNECTION_TO_PHONE; + break; + default: + cme_err = CME_ERROR_AG_FAILURE; + break; + } + + return cme_err; +} + +static void hfp_ag_lock(struct bt_hfp_ag *ag) +{ + nxsem_wait_uninterruptible(&ag->lock); +} + +static void hfp_ag_unlock(struct bt_hfp_ag *ag) +{ + nxsem_post(&ag->lock); +} + +static void bt_hfp_ag_set_state(struct bt_hfp_ag *ag, bt_hfp_state_t state) +{ + bt_hfp_state_t old_state; + + hfp_ag_lock(ag); + old_state = ag->state; + ag->state = state; + hfp_ag_unlock(ag); + + LOG_DBG("update state %p, old %d -> new %d", ag, old_state, state); + + if (old_state == state) + { + return; + } + + switch (state) + { + case BT_HFP_DISCONNECTED: + if (bt_ag && bt_ag->disconnected) + { + bt_ag->disconnected(ag); + } + break; + case BT_HFP_CONNECTING: + break; + case BT_HFP_CONFIG: + break; + case BT_HFP_CONNECTED: + if (bt_ag && bt_ag->connected) + { + bt_ag->connected(ag->acl_conn, ag); + } + break; + case BT_HFP_DISCONNECTING: + break; + default: + LOG_WRN("no valid (%u) state was set", state); + break; + } +} + +static struct bt_hfp_ag_call *get_call_from_number( + struct bt_hfp_ag *ag, const char *number, uint8_t type) +{ + struct bt_hfp_ag_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) + { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) + { + continue; + } + + if (call->type != type) + { + continue; + } + + if (strcmp(call->number, number)) + { + continue; + } + + return call; + } + + return NULL; +} + +static void bt_ag_deferred_work(struct k_work *work); +static void bt_ag_ringing_work(struct k_work *work); + +static struct bt_hfp_ag_call *get_new_call(struct bt_hfp_ag *ag, + const char *number, uint8_t type) +{ + struct bt_hfp_ag_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) + { + call = &ag->calls[index]; + + if (!atomic_test_and_set_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) + { + memset(call->number, 0, sizeof(call->number)); + if (number != NULL) + { + strncpy(call->number, number, sizeof(call->number) - 1); + } + + call->type = type; + call->ag = ag; + k_work_init_delayable(&call->deferred_work, bt_ag_deferred_work); + k_work_init_delayable(&call->ringing_work, bt_ag_ringing_work); + return call; + } + } + + return NULL; +} + +static int get_none_released_calls(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_call *call; + int count = 0; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) + { + call = &ag->calls[index]; + + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) + { + count++; + } + } + + return count; +} + +static struct bt_ag_tx *bt_ag_tx_alloc(void) +{ + /* The TX context always get freed in the system workqueue, + * so if we're in the same workqueue but there are no immediate contexts + * available, there's no chance we'll get one by waiting. + */ + + if (nxsched_gettid() == k_sys_work_q.thread_id) + { + return k_fifo_get(&ag_tx_free, 0); + } + + if (IS_ENABLED(CONFIG_BT_HFP_AG_LOG_LEVEL_DBG)) + { + struct bt_ag_tx *tx = k_fifo_get(&ag_tx_free, 0); + + if (tx) + { + return tx; + } + + LOG_WRN("Unable to get an immediate free bt_ag_tx"); + } + + return k_fifo_get(&ag_tx_free, ((clock_t)-1)); +} + +static void bt_ag_tx_free(struct bt_ag_tx *tx) +{ + LOG_DBG("Free tx buffer %p", tx); + + (void)memset(tx, 0, sizeof(*tx)); + + k_fifo_put(&ag_tx_free, tx); +} + +static int hfp_ag_next_step(struct bt_hfp_ag *ag, + bt_hfp_ag_tx_cb_t cb, void *user_data) +{ + struct bt_ag_tx *tx; + + LOG_DBG("cb %p user_data %p", cb, user_data); + + tx = bt_ag_tx_alloc(); + if (tx == NULL) + { + LOG_ERR("No tx buffers!"); + return -ENOMEM; + } + + LOG_DBG("Alloc tx buffer %p", tx); + + tx->ag = ag; + tx->buf = NULL; + tx->cb = cb; + tx->user_data = user_data; + tx->err = 0; + + hfp_ag_lock(ag); + if (atomic_test_bit(ag->flags, BT_HFP_AG_AT_PROCESS)) + { + list_add_tail(&ag->tx_submit_pending, &tx->node); + } + else + { + list_add_tail(&ag->tx_pending, &tx->node); + + /* Always active tx work */ + + bt_work_reschedule(&ag->tx_work, 0); + } + + hfp_ag_unlock(ag); + + return 0; +} + +static int hfp_ag_send_data( + struct bt_hfp_ag *ag, bt_hfp_ag_tx_cb_t cb, void *user_data, + const char *format, ...) +{ + struct net_buf *buf = NULL; + struct bt_ag_tx *tx = NULL; + va_list vargs; + int err; + bt_hfp_state_t state; + + LOG_DBG("AG %p sending data cb %p user_data %p", ag, cb, user_data); + + state = ag->state; + if ((state == BT_HFP_DISCONNECTED) || (state == BT_HFP_DISCONNECTING)) + { + LOG_ERR("AG %p is not connected", ag); + err = -ENOTCONN; + goto failed; + } + + buf = bt_rfcomm_create_pdu(&ag_pool); + if (!buf) + { + LOG_ERR("No Buffers!"); + err = -ENOMEM; + goto failed; + } + + tx = bt_ag_tx_alloc(); + if (tx == NULL) + { + LOG_ERR("No tx buffers!"); + err = -ENOMEM; + goto failed; + } + + LOG_DBG("buf %p tx %p", buf, tx); + + tx->ag = ag; + tx->buf = buf; + tx->cb = cb; + tx->user_data = user_data; + + va_start(vargs, format); + err = vsnprintf((char *)buf->data, (net_buf_tailroom(buf) - 1), format, + vargs); + va_end(vargs); + + if (err < 0) + { + LOG_ERR("Unable to format variable arguments"); + goto failed; + } + + net_buf_add(buf, err); + + LOG_HEXDUMP_DBG(buf->data, buf->len, "Sending:"); + + hfp_ag_lock(ag); + list_add_tail(&ag->tx_pending, &tx->node); + hfp_ag_unlock(ag); + + /* Always active tx work */ + + bt_work_reschedule(&ag->tx_work, 0); + + return 0; + +failed: + if (buf) + { + net_buf_unref(buf); + } + + if (tx) + { + bt_ag_tx_free(tx); + } + + bt_hfp_ag_disconnect(ag); + return err; +} + +static int hfp_ag_update_indicator( + struct bt_hfp_ag *ag, enum bt_hfp_ag_indicator index, uint8_t value, + bt_hfp_ag_tx_cb_t cb, void *user_data) +{ + int err; + uint8_t old_value; + + if (index >= BT_HFP_AG_IND_MAX) + { + return -EINVAL; + } + + if ((ag_ind[index].max < value) || (ag_ind[index].min > value)) + { + return -EINVAL; + } + + hfp_ag_lock(ag); + old_value = ag->indicator_value[index]; + if (value == old_value) + { + LOG_ERR("Duplicate value setting, indicator %d, old %d -> new %d", + index, + old_value, value); + hfp_ag_unlock(ag); + if (cb) + { + cb(ag, user_data); + } + + return -EINVAL; + } + + ag->indicator_value[index] = value; + hfp_ag_unlock(ag); + + LOG_DBG("indicator %d, old %d -> new %d", index, old_value, value); + + if (!(ag->indicator & BIT(index))) + { + LOG_INF("The indicator %d is deactivated", index); + + /* If the indicator is deactivated, consider it a successful set. */ + + return 0; + } + + err = hfp_ag_send_data(ag, cb, user_data, "\r\n+CIEV:%d,%d\r\n", + (uint8_t)index + 1, value); + if (err) + { + hfp_ag_lock(ag); + ag->indicator_value[index] = old_value; + hfp_ag_unlock(ag); + LOG_ERR("Fail to update indicator %d, current %d", index, old_value); + } + + return err; +} + +static int hfp_ag_force_update_indicator( + struct bt_hfp_ag *ag, enum bt_hfp_ag_indicator index, uint8_t value, + bt_hfp_ag_tx_cb_t cb, void *user_data) +{ + int err; + uint8_t old_value; + + hfp_ag_lock(ag); + old_value = ag->indicator_value[index]; + ag->indicator_value[index] = value; + hfp_ag_unlock(ag); + + LOG_DBG("indicator %d, old %d -> new %d", index, old_value, value); + + err = hfp_ag_send_data(ag, cb, user_data, "\r\n+CIEV:%d,%d\r\n", + (uint8_t)index + 1, value); + if (err) + { + hfp_ag_lock(ag); + ag->indicator_value[index] = old_value; + hfp_ag_unlock(ag); + LOG_ERR("Fail to update indicator %d, current %d", index, old_value); + } + + return err; +} + +static void clear_call_setup_ind_cb(struct bt_hfp_ag *ag, void *user_data) +{ + uint8_t call_ind; + int err; + + hfp_ag_lock(ag); + call_ind = ag->indicator_value[BT_HFP_AG_CALL_IND]; + hfp_ag_unlock(ag); + + if (call_ind) + { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, NULL, NULL); + if (err) + { + LOG_ERR("Fail to clear call_setup ind"); + } + } +} + +static void clear_call_ind_cb(struct bt_hfp_ag *ag, void *user_data) +{ + uint8_t call_setup_ind; + int err; + + hfp_ag_lock(ag); + call_setup_ind = ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND]; + hfp_ag_unlock(ag); + + if (call_setup_ind) + { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, + BT_HFP_CALL_SETUP_NONE, NULL, + NULL); + if (err) + { + LOG_ERR("Fail to clear call_setup ind"); + } + } +} + +static void clear_call_and_call_setup_ind(struct bt_hfp_ag *ag) +{ + uint8_t call_setup_ind; + uint8_t call_ind; + int err; + + hfp_ag_lock(ag); + call_setup_ind = ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND]; + call_ind = ag->indicator_value[BT_HFP_AG_CALL_IND]; + hfp_ag_unlock(ag); + + if (call_setup_ind) + { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, + BT_HFP_CALL_SETUP_NONE, + clear_call_setup_ind_cb, NULL); + if (err) + { + LOG_ERR("Fail to clear call_setup ind"); + } + } + + if (call_ind) + { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, + clear_call_ind_cb, NULL); + if (err) + { + LOG_ERR("Fail to clear call ind"); + } + } +} + +static int get_active_calls(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_call *call; + int count = 0; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) + { + call = &ag->calls[index]; + + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) + { + hfp_ag_lock(ag); + if ((call->call_state == BT_HFP_CALL_ACTIVE) && + (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD))) + { + count++; + } + + hfp_ag_unlock(ag); + } + } + + return count; +} + +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) +static int get_active_held_calls(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_call *call; + int count = 0; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) + { + call = &ag->calls[index]; + + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) + { + hfp_ag_lock(ag); + if ((call->call_state == BT_HFP_CALL_HOLD) || + ((call->call_state == BT_HFP_CALL_ACTIVE) && + (!atomic_test_bit(call->flags, + BT_HFP_AG_CALL_INCOMING_HELD)))) + { + count++; + } + + hfp_ag_unlock(ag); + } + } + + return count; +} +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + +static int get_held_calls(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_call *call; + int count = 0; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) + { + call = &ag->calls[index]; + + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) + { + hfp_ag_lock(ag); + if ((call->call_state == BT_HFP_CALL_ACTIVE) && + (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD))) + { + count++; + } + else if (call->call_state == BT_HFP_CALL_HOLD) + { + count++; + } + + hfp_ag_unlock(ag); + } + } + + return count; +} + +static void ag_notify_call_held_ind(struct bt_hfp_ag *ag, void *user_data) +{ + int active_call_count; + int held_call_count; + int err; + uint8_t value; + + active_call_count = get_active_calls(ag); + held_call_count = get_held_calls(ag); + if (active_call_count && held_call_count) + { + value = BT_HFP_CALL_HELD_ACTIVE_HELD; + } + else if (held_call_count) + { + value = BT_HFP_CALL_HELD_HELD; + } + else + { + value = BT_HFP_CALL_HELD_NONE; + } + + err = hfp_ag_force_update_indicator(ag, BT_HFP_AG_CALL_HELD_IND, value, + NULL, + NULL); + if (err) + { + LOG_ERR("Fail to notify call_held ind :(%d)", err); + } +} + +static void release_call(struct bt_hfp_ag_call *call) +{ + atomic_clear_bit(call->flags, BT_HFP_AG_CALL_IN_USING); + k_work_cancel_delayable(&call->ringing_work); + k_work_cancel_delayable(&call->deferred_work); + /* Because it is uncertain whether `ringing_work` and `deferred_work` have + * been cancelled, do not clear ringing_work and deferred_work. Use + * `offsetof()` to get the offset of `ringing_work` to avoid access the + * fields `ringing_work` and `deferred_work`. + */ + + (void)memset(call, 0, offsetof(struct bt_hfp_ag_call, ringing_work)); +} + +static void free_call(struct bt_hfp_ag_call *call) +{ + int call_count; + struct bt_hfp_ag *ag; + int err; + + ag = call->ag; + + release_call(call); + + call_count = get_none_released_calls(ag); + + if (!call_count) + { + clear_call_and_call_setup_ind(ag); + } + else + { + err = hfp_ag_next_step(ag, ag_notify_call_held_ind, NULL); + if (err) + { + LOG_ERR("Fail to notify call_held ind :(%d)", err); + } + } +} + +static void bt_hfp_ag_set_call_state(struct bt_hfp_ag_call *call, + bt_hfp_call_state_t call_state) +{ + bt_hfp_state_t state; + struct bt_hfp_ag *ag = call->ag; + + LOG_DBG("update call state %p, old %d -> new %d", call, call->call_state, + call_state); + + hfp_ag_lock(ag); + call->call_state = call_state; + state = ag->state; + hfp_ag_unlock(ag); + + switch (call_state) + { + case BT_HFP_CALL_TERMINATE: + atomic_clear_bit(ag->flags, BT_HFP_AG_CREATING_SCO); + free_call(call); + break; + case BT_HFP_CALL_OUTGOING: + bt_work_reschedule(&call->deferred_work, + SEC2TICK(CONFIG_BT_HFP_AG_OUTGOING_TIMEOUT)); + break; + case BT_HFP_CALL_INCOMING: + bt_work_reschedule(&call->deferred_work, + SEC2TICK(CONFIG_BT_HFP_AG_INCOMING_TIMEOUT)); + break; + case BT_HFP_CALL_ALERTING: + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY)) + { + bt_work_reschedule(&call->ringing_work, 0); + } + else + { + k_work_cancel_delayable(&call->ringing_work); + } + + bt_work_reschedule(&call->deferred_work, + SEC2TICK(CONFIG_BT_HFP_AG_ALERTING_TIMEOUT)); + break; + case BT_HFP_CALL_ACTIVE: + k_work_cancel_delayable(&call->ringing_work); + k_work_cancel_delayable(&call->deferred_work); + break; + case BT_HFP_CALL_HOLD: + break; + default: + + /* Invalid state */ + + break; + } + + if (state == BT_HFP_DISCONNECTING) + { + int err = bt_rfcomm_dlc_disconnect(&ag->rfcomm_dlc); + + if (err) + { + LOG_ERR("Fail to disconnect DLC %p", &ag->rfcomm_dlc); + } + } +} + +static void hfp_ag_close_sco(struct bt_hfp_ag *ag) +{ + struct bt_conn *sco; + int call_count; + + LOG_DBG(""); + + hfp_ag_lock(ag); + call_count = get_none_released_calls(ag); + if (call_count > 0) + { + LOG_INF("Do not close sco because not all calls are released"); + hfp_ag_unlock(ag); + return; + } + + sco = atomic_ptr_xchg(&ag->sco_conn, NULL); + if (sco != NULL) + { + bt_conn_unref(sco); + sco = ag->sco_chan.sco; + } + + hfp_ag_unlock(ag); + + if (sco != NULL) + { + LOG_DBG("Disconnect sco %p", sco); + bt_conn_disconnect(sco, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + } +} + +static void ag_reject_call(struct bt_hfp_ag_call *call) +{ + struct bt_hfp_ag *ag; + + ag = call->ag; + + if (bt_ag && bt_ag->reject) + { + bt_ag->reject(call); + } + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_TERMINATE); + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_AUDIO_CONN)) + { + LOG_DBG("It is not audio connection"); + hfp_ag_close_sco(ag); + } +} + +static void ag_terminate_call(struct bt_hfp_ag_call *call) +{ + struct bt_hfp_ag *ag; + + ag = call->ag; + + if (bt_ag && bt_ag->terminate) + { + bt_ag->terminate(call); + } + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_TERMINATE); + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_AUDIO_CONN)) + { + LOG_DBG("It is not audio connection"); + hfp_ag_close_sco(ag); + } +} + +static int hfp_ag_send(struct bt_hfp_ag *ag, struct bt_ag_tx *tx) +{ + int err = bt_rfcomm_dlc_send(&ag->rfcomm_dlc, tx->buf); + + if (err < 0) + { + net_buf_unref(tx->buf); + } + + return err; +} + +static void bt_ag_notify_work(struct k_work *work); + +/* Keep TX context release on the system workqueue because Bluetooth + * workqueue handlers may block waiting for a context to become available. + */ + +struct k_work ag_notify_work = Z_WORK_INITIALIZER(bt_ag_notify_work); + +static void bt_ag_notify_work(struct k_work *work) +{ + struct bt_ag_tx *tx; + bt_hfp_ag_tx_cb_t cb; + struct bt_hfp_ag *ag; + void *user_data; + bt_hfp_state_t state; + int err; + + tx = (struct bt_ag_tx *)k_fifo_get(&ag_tx_notify, 0); + + if (tx == NULL) + { + return; + } + + cb = tx->cb; + ag = tx->ag; + user_data = tx->user_data; + err = tx->err; + + bt_ag_tx_free(tx); + + state = ag->state; + + if (err < 0) + { + if ((state != BT_HFP_DISCONNECTED) && (state != BT_HFP_DISCONNECTING)) + { + bt_hfp_ag_set_state(ag, BT_HFP_DISCONNECTING); + bt_rfcomm_dlc_disconnect(&ag->rfcomm_dlc); + } + } + + /* If the AG connection has been disconnected, ignore the callback of tx + * node. + */ + + if ((state != BT_HFP_DISCONNECTED) && (cb != NULL)) + { + cb(ag, user_data); + } + + if (!k_fifo_is_empty(&ag_tx_notify)) + { + /* Submit worker if the fifo ag_tx_notify is not empty. */ + + k_work_submit(&ag_notify_work); + } +} + +static void bt_ag_tx_notify(struct bt_ag_tx *tx) +{ + k_fifo_put(&ag_tx_notify, tx); + + k_work_submit(&ag_notify_work); +} + +static void bt_ag_tx_done_with_err(struct bt_hfp_ag *ag, + struct bt_ag_tx *tx, int err) +{ + list_find_and_remove(&ag->tx_pending, &tx->node); + tx->err = err; + bt_ag_tx_notify(tx); + + /* Clear the tx ongoing flag */ + + if (!atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_TX_ONGOING)) + { + LOG_WRN("tx ongoing flag is not set"); + } + + /* Due to the work is done, restart the tx work */ + + bt_work_reschedule(&ag->tx_work, 0); +} + +static void bt_ag_tx_work(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_hfp_ag *ag = CONTAINER_OF(dwork, struct bt_hfp_ag, tx_work); + struct list_node *node; + struct bt_ag_tx *tx; + bt_hfp_state_t state; + + hfp_ag_lock(ag); + state = ag->state; + if ((state == BT_HFP_DISCONNECTED) || (state == BT_HFP_DISCONNECTING)) + { + LOG_ERR("AG %p is not connected", ag); + goto unlock; + } + + node = list_peek_head(&ag->tx_pending); + if (!node) + { + LOG_DBG("No pending tx"); + goto unlock; + } + + tx = CONTAINER_OF(node, struct bt_ag_tx, node); + + if (!atomic_test_and_set_bit(ag->flags, BT_HFP_AG_TX_ONGOING)) + { + int err; + + LOG_DBG("AG %p sending tx %p", ag, tx); + + if (tx->buf == NULL) + { + /* Goto next state and remove the current tx */ + + bt_ag_tx_done_with_err(ag, tx, 0); + goto unlock; + } + + err = hfp_ag_send(ag, tx); + + if (err < 0) + { + LOG_ERR("Rfcomm send error :(%d)", err); + bt_ag_tx_done_with_err(ag, tx, err); + } + } + +unlock: + hfp_ag_unlock(ag); +} + +static void skip_space(struct net_buf *buf) +{ + size_t count = 0; + + while ((buf->len > count) && (buf->data[count] == ' ')) + { + count++; + } + + if (count > 0) + { + (void)net_buf_pull(buf, count); + } +} + +static int get_number(struct net_buf *buf, uint32_t *number) +{ + int err = -EINVAL; + + *number = 0; + + skip_space(buf); + while (buf->len > 0) + { + if ((buf->data[0] >= '0') && (buf->data[0] <= '9')) + { + *number = *number * 10 + buf->data[0] - '0'; + (void)net_buf_pull(buf, 1); + err = 0; + } + else + { + break; + } + } + + skip_space(buf); + + return err; +} + +static int get_char(struct net_buf *buf, char *c) +{ + int err = -EINVAL; + + skip_space(buf); + if (buf->len > 0) + { + *c = (char)buf->data[0]; + (void)net_buf_pull(buf, 1); + err = 0; + } + + skip_space(buf); + + return err; +} + +static bool is_char(struct net_buf *buf, uint8_t c) +{ + bool found = false; + + skip_space(buf); + if (buf->len > 0) + { + if (buf->data[0] == c) + { + (void)net_buf_pull(buf, 1); + found = true; + } + } + + skip_space(buf); + + return found; +} + +static int bt_hfp_ag_brsf_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + uint32_t hf_features; + int err; + + if (!is_char(buf, '=')) + { + return -ENOTSUP; + } + + err = get_number(buf, &hf_features); + if (err != 0) + { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + + ag->hf_features = hf_features; + + return hfp_ag_send_data(ag, NULL, NULL, "\r\n+BRSF:%d\r\n", + ag->ag_features); +} + +static int bt_hfp_ag_bac_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + uint32_t codec; + uint32_t codec_ids = 0U; + int err = 0; + + if (!is_char(buf, '=')) + { + return -ENOTSUP; + } + + if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_CODEC_NEG, + BT_HFP_AG_FEATURE_CODEC_NEG)) + { + return -ENOEXEC; + } + + while (buf->len > 0) + { + err = get_number(buf, &codec); + if (err != 0) + { + return -ENOTSUP; + } + + if (!is_char(buf, ',')) + { + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + } + + if (codec < NUM_BITS(sizeof(codec_ids))) + { + codec_ids |= BIT(codec); + } + } + + if (!(codec_ids & BIT(BT_HFP_AG_CODEC_CVSD))) + { + return -ENOEXEC; + } + + hfp_ag_lock(ag); + ag->hf_codec_ids = codec_ids; + ag->selected_codec_id = 0; + hfp_ag_unlock(ag); + + atomic_set_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED); + + if (atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_CODEC_CONN)) + { + /* Codec connection is ended. It needs to be restarted. */ + + LOG_DBG("Codec connection is ended. It needs to be restarted."); + if (bt_ag && bt_ag->codec_negotiate) + { + bt_ag->codec_negotiate(ag, -EAGAIN); + } + } + + if (bt_ag && bt_ag->codec) + { + bt_ag->codec(ag, ag->hf_codec_ids); + } + + return 0; +} + +static void bt_hfp_ag_check_ind_value(struct bt_hfp_ag *ag, uint8_t ind) +{ + if ((ag_ind[ind].max < ag->indicator_value[ind]) || + (ag_ind[ind].min > ag->indicator_value[ind])) + { + LOG_WRN("Invalid value of indicator[%u] %u", ind, + ag->indicator_value[ind]); + ag->indicator_value[ind] = 0; + } +} + +static void bt_hfp_ag_get_ind_values(struct bt_hfp_ag *ag) +{ + int err; + + if ((bt_ag == NULL) || (bt_ag->get_indicator_value == NULL)) + { + LOG_DBG("No indicator value retrieval method available"); + return; + } + + if (ag->state == BT_HFP_CONNECTED) + { + LOG_ERR("Only works during SLC establishment phase"); + return; + } + + err = bt_ag->get_indicator_value(ag, + &ag->indicator_value[BT_HFP_AG_SERVICE_IND], + &ag->indicator_value[ + BT_HFP_AG_SIGNAL_IND], + &ag->indicator_value[BT_HFP_AG_ROAM_IND], + &ag->indicator_value[BT_HFP_AG_BATTERY_IND]); + if (err != 0) + { + LOG_DBG("No indicator value call retrieved"); + return; + } + + bt_hfp_ag_check_ind_value(ag, BT_HFP_AG_SERVICE_IND); + bt_hfp_ag_check_ind_value(ag, BT_HFP_AG_SIGNAL_IND); + bt_hfp_ag_check_ind_value(ag, BT_HFP_AG_ROAM_IND); + bt_hfp_ag_check_ind_value(ag, BT_HFP_AG_BATTERY_IND); +} + +static int bt_hfp_ag_get_ongoing_calls(struct bt_hfp_ag *ag) +{ + int err; + + ag->ongoing_call_count = 0; + + if ((bt_ag == NULL) || (bt_ag->get_ongoing_call == NULL)) + { + LOG_DBG("No ongoing call retrieval method available"); + return -EINVAL; + } + + if (ag->state == BT_HFP_CONNECTED) + { + LOG_ERR("Only works during SLC establishment phase"); + return -EINVAL; + } + + err = bt_ag->get_ongoing_call(ag); + if (err != 0) + { + LOG_DBG("No ongoing call retrieved"); + } + + return err; +} + +static int bt_hfp_ag_notify_cind_value(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_ongoing_call *call; + uint8_t call_value = 0; + uint8_t call_setup_value = 0; + uint8_t call_held_value = 0; + uint8_t call_active_count = 0; + uint8_t call_held_count = 0; + + if (ag->ongoing_call_count == 0) + { + return hfp_ag_send_data( + ag, NULL, NULL, "\r\n+CIND:%u,%u,%u,%u,%u,%u,%u\r\n", + ag->indicator_value[BT_HFP_AG_SERVICE_IND], + ag->indicator_value[BT_HFP_AG_CALL_IND], + ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND], + ag->indicator_value[BT_HFP_AG_CALL_HELD_IND], + ag->indicator_value[BT_HFP_AG_SIGNAL_IND], + ag->indicator_value[BT_HFP_AG_ROAM_IND], + ag->indicator_value[BT_HFP_AG_BATTERY_IND]); + } + + for (size_t index = 0; index < ag->ongoing_call_count; index++) + { + call = &ag->ongoing_calls[index]; + + switch (call->status) + { + case BT_HFP_AG_CALL_STATUS_ACTIVE: + call_value = 1; + call_active_count++; + break; + case BT_HFP_AG_CALL_STATUS_HELD: + call_value = 1; + call_held_count++; + break; + case BT_HFP_AG_CALL_STATUS_DIALING: + call_setup_value = BT_HFP_CALL_SETUP_OUTGOING; + break; + case BT_HFP_AG_CALL_STATUS_ALERTING: + call_setup_value = BT_HFP_CALL_SETUP_REMOTE_ALERTING; + break; + case BT_HFP_AG_CALL_STATUS_INCOMING: + call_setup_value = BT_HFP_CALL_SETUP_INCOMING; + break; + case BT_HFP_AG_CALL_STATUS_WAITING: + call_setup_value = BT_HFP_CALL_SETUP_INCOMING; + break; + case BT_HFP_AG_CALL_STATUS_INCOMING_HELD: + call_value = 1; + break; + } + } + + if ((call_active_count != 0) && (call_held_count != 0)) + { + call_held_value = BT_HFP_CALL_HELD_ACTIVE_HELD; + } + else if (call_held_count != 0) + { + call_held_value = BT_HFP_CALL_HELD_HELD; + } + else + { + call_held_value = BT_HFP_CALL_HELD_NONE; + } + + return hfp_ag_send_data(ag, NULL, NULL, + "\r\n+CIND:%u,%u,%u,%u,%u,%u,%u\r\n", + ag->indicator_value[BT_HFP_AG_SERVICE_IND], + call_value, call_setup_value, + call_held_value, + ag->indicator_value[BT_HFP_AG_SIGNAL_IND], + ag->indicator_value[BT_HFP_AG_ROAM_IND], + ag->indicator_value[BT_HFP_AG_BATTERY_IND]); +} + +static int bt_hfp_ag_cind_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + bool inquiry_status = true; + + if (is_char(buf, '=')) + { + inquiry_status = false; + } + + if (!is_char(buf, '?')) + { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + + if (!inquiry_status) + { + err = hfp_ag_send_data( + ag, NULL, NULL, + "\r\n+CIND:(\"%s\",(%d%s%d)),(\"%s\",(%d%s%d))," + "(\"%s\",(%d%s%d)),(\"%s\",(%d%s%d))," + "(\"%s\",(%d%s%d)),(\"%s\",(%d%s%d))," + "(\"%s\",(%d%s%d))\r\n", + ag_ind[BT_HFP_AG_SERVICE_IND].name, + ag_ind[BT_HFP_AG_SERVICE_IND].min, + ag_ind[BT_HFP_AG_SERVICE_IND].connector, + ag_ind[BT_HFP_AG_SERVICE_IND].max, + ag_ind[BT_HFP_AG_CALL_IND].name, ag_ind[ + BT_HFP_AG_CALL_IND].min, + ag_ind[BT_HFP_AG_CALL_IND].connector, + ag_ind[BT_HFP_AG_CALL_IND].max, + ag_ind[BT_HFP_AG_CALL_SETUP_IND].name, + ag_ind[BT_HFP_AG_CALL_SETUP_IND].min, + ag_ind[BT_HFP_AG_CALL_SETUP_IND].connector, + ag_ind[BT_HFP_AG_CALL_SETUP_IND].max, + ag_ind[BT_HFP_AG_CALL_HELD_IND].name, + ag_ind[BT_HFP_AG_CALL_HELD_IND].min, + ag_ind[BT_HFP_AG_CALL_HELD_IND].connector, + ag_ind[BT_HFP_AG_CALL_HELD_IND].max, + ag_ind[BT_HFP_AG_SIGNAL_IND].name, + ag_ind[BT_HFP_AG_SIGNAL_IND].min, + ag_ind[BT_HFP_AG_SIGNAL_IND].connector, + ag_ind[BT_HFP_AG_SIGNAL_IND].max, + ag_ind[BT_HFP_AG_ROAM_IND].name, ag_ind[ + BT_HFP_AG_ROAM_IND].min, + ag_ind[BT_HFP_AG_ROAM_IND].connector, + ag_ind[BT_HFP_AG_ROAM_IND].max, + ag_ind[BT_HFP_AG_BATTERY_IND].name, + ag_ind[BT_HFP_AG_BATTERY_IND].min, + ag_ind[BT_HFP_AG_BATTERY_IND].connector, + ag_ind[BT_HFP_AG_BATTERY_IND].max); + } + else + { + bt_hfp_ag_get_ind_values(ag); + + err = bt_hfp_ag_get_ongoing_calls(ag); + if (err != 0) + { + err = bt_hfp_ag_notify_cind_value(ag); + } + else + { + err = -EINPROGRESS; + atomic_set_bit(ag->flags, BT_HGP_AG_ONGOING_CALLS); + bt_work_reschedule(&ag->ongoing_call_work, + MSEC2TICK(CONFIG_BT_HFP_AG_GET_ONGOING_CALL_TIMEOUT)); + } + } + + return err; +} + +static void bt_hfp_ag_update_call_flags(struct bt_hfp_ag_call *call, + enum bt_hfp_ag_call_dir dir) +{ + int call_count; + + call_count = get_none_released_calls(call->ag); + + atomic_set_bit_to(call->flags, BT_HFP_AG_CALL_INCOMING, + dir == BT_HFP_AG_CALL_DIR_INCOMING); + + if (call_count > 1) + { + if (dir == BT_HFP_AG_CALL_DIR_INCOMING) + { + atomic_set_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY); + } + else + { + atomic_set_bit(call->flags, BT_HFP_AG_CALL_OUTGOING_3WAY); + } + } +} + +static void bt_hfp_ag_notify_new_call(struct bt_hfp_ag *ag, + struct bt_hfp_ag_call *call) +{ + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING) || + atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY)) + { + if (bt_ag && bt_ag->incoming) + { + bt_ag->incoming(ag, call, call->number); + } + } + else + { + if (bt_ag && bt_ag->outgoing) + { + bt_ag->outgoing(ag, call, call->number); + } + } +} + +static void bt_hfp_ag_add_active_call(struct bt_hfp_ag *ag, + struct bt_hfp_ag_ongoing_call *ongoing_call) +{ + struct bt_hfp_ag_call *call; + + call = get_new_call(ag, ongoing_call->number, ongoing_call->type); + if (call == NULL) + { + return; + } + + LOG_DBG("Call %p is active", call); + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); + bt_hfp_ag_update_call_flags(call, ongoing_call->dir); + bt_hfp_ag_notify_new_call(ag, call); + + if (bt_ag && bt_ag->accept) + { + bt_ag->accept(call); + } +} + +static void bt_hfp_ag_add_held_call(struct bt_hfp_ag *ag, + struct bt_hfp_ag_ongoing_call *ongoing_call) +{ + struct bt_hfp_ag_call *call; + + call = get_new_call(ag, ongoing_call->number, ongoing_call->type); + if (call == NULL) + { + return; + } + + LOG_DBG("Call %p is held", call); + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_HOLD); + bt_hfp_ag_update_call_flags(call, ongoing_call->dir); + bt_hfp_ag_notify_new_call(ag, call); + + if (bt_ag && bt_ag->held) + { + bt_ag->held(call); + } +} + +static void bt_hfp_ag_add_incoming_held_call(struct bt_hfp_ag *ag, + struct bt_hfp_ag_ongoing_call *ongoing_call) +{ + struct bt_hfp_ag_call *call; + + call = get_new_call(ag, ongoing_call->number, ongoing_call->type); + if (call == NULL) + { + return; + } + + LOG_DBG("Incoming call %p is held", call); + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); + atomic_set_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD); + bt_hfp_ag_update_call_flags(call, ongoing_call->dir); + bt_hfp_ag_notify_new_call(ag, call); + + if (bt_ag && bt_ag->incoming_held) + { + bt_ag->incoming_held(call); + } +} + +static struct bt_hfp_ag_call *get_call_with_flag(struct bt_hfp_ag *ag, + int bit) +{ + struct bt_hfp_ag_call *call; + + ARRAY_FOR_EACH(ag->calls, i) + { + call = &ag->calls[i]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) + { + continue; + } + + if (atomic_test_bit(call->flags, bit)) + { + return call; + } + } + + return NULL; +} + +static struct bt_hfp_ag_call *get_call_with_flag_and_state( + struct bt_hfp_ag *ag, int bit, bt_hfp_call_state_t state) +{ + struct bt_hfp_ag_call *call; + + ARRAY_FOR_EACH(ag->calls, i) + { + call = &ag->calls[i]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) + { + continue; + } + + if ((call->call_state & state) && atomic_test_bit(call->flags, bit)) + { + return call; + } + } + + return NULL; +} + +static void bt_hfp_ag_reject_cb(struct bt_hfp_ag *ag, void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + if (call) + { + ag_reject_call(call); + } + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_AUDIO_CONN)) + { + LOG_DBG("It is not audio connection"); + hfp_ag_close_sco(ag); + } +} + +static void bt_hfp_ag_call_reject(struct bt_hfp_ag *ag, void *user_data) +{ + int err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, + BT_HFP_CALL_SETUP_NONE, + bt_hfp_ag_reject_cb, user_data); + + if (err != 0) + { + LOG_ERR("Fail to send err :(%d)", err); + } +} + +static void bt_hfp_ag_call_ringing_cb(struct bt_hfp_ag_call *call, + bool in_bond) +{ + if (bt_ag && bt_ag->ringing) + { + bt_ag->ringing(call, in_bond); + } +} + +static void hfp_ag_sco_connected(struct bt_sco_chan *chan) +{ + struct bt_hfp_ag *ag = CONTAINER_OF(chan, struct bt_hfp_ag, sco_chan); + + if (atomic_ptr_cmpxchg(&ag->sco_conn, NULL, chan->sco)) + { + bt_conn_ref(chan->sco); + } + + if ((bt_ag) && bt_ag->sco_connected) + { + bt_ag->sco_connected(ag, chan->sco); + } +} + +static void hfp_ag_sco_disconnected(struct bt_sco_chan *chan, uint8_t reason) +{ + struct bt_hfp_ag *ag = CONTAINER_OF(chan, struct bt_hfp_ag, sco_chan); + struct bt_conn *sco; + + if ((bt_ag != NULL) && bt_ag->sco_disconnected) + { + bt_ag->sco_disconnected(chan->sco, reason); + } + + sco = atomic_ptr_xchg(&ag->sco_conn, NULL); + if (sco != NULL) + { + bt_conn_unref(sco); + } +} + +static int hfp_ag_set_voice_setting(struct bt_hfp_ag *ag) +{ + uint16_t air_coding_fmt; + + switch (ag->selected_codec_id) + { + case BT_HFP_AG_CODEC_CVSD: + air_coding_fmt = BT_HCI_VOICE_SETTING_AIR_CODING_FMT_CVSD; + break; +#if defined(CONFIG_BT_HFP_AG_CODEC_NEG) + case BT_HFP_AG_CODEC_MSBC: + case BT_HFP_AG_CODEC_LC3_SWB: + air_coding_fmt = BT_HCI_VOICE_SETTING_AIR_CODING_FMT_TRANSPARENT; + break; +#endif /* CONFIG_BT_HFP_AG_CODEC_NEG */ + default: + LOG_ERR("Unsupported codec ID %u", ag->selected_codec_id); + return -EINVAL; + } + + ag->sco_chan.voice_setting = BT_HCI_VOICE_SETTINGS( + air_coding_fmt, BT_HCI_VOICE_SETTING_PCM_BIT_POS_DEFAULT, + BT_HCI_VOICE_SETTING_SAMPLE_SIZE_16_BITS, + BT_HCI_VOICE_SETTING_DATA_FMT_2_COMPLEMENT, + BT_HCI_VOICE_SETTING_CODING_FMT_LINEAR); + + return 0; +} + +static struct bt_conn *bt_hfp_ag_create_sco(struct bt_hfp_ag *ag) +{ + static const struct bt_sco_chan_ops ops = + { + .connected = hfp_ag_sco_connected, + .disconnected = hfp_ag_sco_disconnected, + }; + + struct bt_conn *sco; + int err; + bool updated; + + LOG_DBG(""); + + sco = atomic_ptr_read(&ag->sco_conn); + if (sco != NULL) + { + return sco; + } + + ag->sco_chan.ops = &ops; + + err = hfp_ag_set_voice_setting(ag); + if (err < 0) + { + LOG_ERR("Fail to set voice setting :(%d)", err); + return NULL; + } + + /* create SCO connection */ + + sco = bt_conn_create_sco(&ag->acl_conn->br.dst, &ag->sco_chan); + updated = atomic_ptr_cmpxchg(&ag->sco_conn, NULL, sco); + if (!updated) + { + LOG_WRN("SCO is not NULL (%p), target (%p)", + atomic_ptr_read(&ag->sco_conn), sco); + DEBUGASSERT(atomic_ptr_read(&ag->sco_conn) == sco); + /* The `ag->sco_conn` has been updated in callback + * `hfp_ag_sco_connected()`. The reference count has been increased in + * callback `hfp_ag_sco_connected()`. The reference count should be + * decreased in this case. + */ + + if (sco != NULL) + { + LOG_DBG("Unreference SCO connection %p", sco); + bt_conn_unref(sco); + } + } + + if (sco != NULL) + { + LOG_DBG("Created sco %p", sco); + if (ag->sco_chan.sco == NULL) + { + /* SCO connection exists */ + + LOG_WRN("SCO conn has been created outside"); + } + } + + return sco; +} + +static int hfp_ag_open_sco(struct bt_hfp_ag *ag, struct bt_hfp_ag_call *call) +{ + bool create_sco; + + if (atomic_test_bit(ag->flags, BT_HFP_AG_CREATING_SCO)) + { + LOG_WRN("SCO connection is creating!"); + return 0; + } + + hfp_ag_lock(ag); + create_sco = atomic_ptr_read(&ag->sco_conn) == NULL ? true : false; + if (create_sco) + { + atomic_set_bit(ag->flags, BT_HFP_AG_CREATING_SCO); + } + + hfp_ag_unlock(ag); + + if (create_sco) + { + struct bt_conn *sco_conn = bt_hfp_ag_create_sco(ag); + + atomic_clear_bit(ag->flags, BT_HFP_AG_CREATING_SCO); + atomic_clear_bit(ag->flags, BT_HFP_AG_AUDIO_CONN); + + if (sco_conn == NULL) + { + LOG_ERR("Fail to create sco connection!"); + return -ENOTCONN; + } + + atomic_set_bit_to(ag->flags, BT_HFP_AG_AUDIO_CONN, call == NULL); + + LOG_DBG("SCO connection created (%p)", sco_conn); + } + + if (call == NULL) + { + return 0; + } + + if ((call->call_state == BT_HFP_CALL_INCOMING) || + atomic_test_and_clear_bit(call->flags, BT_HFP_AG_CALL_ALERTING)) + { + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ALERTING); + bt_hfp_ag_call_ringing_cb(call, true); + } + + return 0; +} + +static void bt_hfp_ag_auto_select_codec(struct bt_hfp_ag *ag) +{ + uint32_t supported_codec; + + supported_codec = BT_HFP_AG_SUPPORTED_CODEC_IDS & ag->hf_codec_ids; + + /* Automatically select the best available codec */ + + if (supported_codec == 0) + { + LOG_WRN("No supported Codec. Selected Codec CVSD as default"); + ag->selected_codec_id = BT_HFP_AG_CODEC_CVSD; + + return; + } + + ag->selected_codec_id = fls(supported_codec) - 1; + LOG_DBG("Selected codec ID: %u", ag->selected_codec_id); +} + +static int bt_hfp_ag_codec_select(struct bt_hfp_ag *ag) +{ + int err; + + LOG_DBG(""); + + hfp_ag_lock(ag); + if (ag->selected_codec_id == 0) + { + bt_hfp_ag_auto_select_codec(ag); + LOG_WRN("Codec is invalid, selected codec ID: %u", + ag->selected_codec_id); + } + + if (!(ag->hf_codec_ids & BIT(ag->selected_codec_id))) + { + LOG_ERR("Codec is unsupported (codec id %d)", ag->selected_codec_id); + hfp_ag_unlock(ag); + return -EINVAL; + } + + hfp_ag_unlock(ag); + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BCS:%d\r\n", + ag->selected_codec_id); + if (err != 0) + { + LOG_ERR("Fail to send err :(%d)", err); + } + + return err; +} + +static int bt_hfp_ag_create_audio_connection(struct bt_hfp_ag *ag, + struct bt_hfp_ag_call *call) +{ + int err; + uint32_t hf_codec_ids; + + hfp_ag_lock(ag); + hf_codec_ids = ag->hf_codec_ids; + hfp_ag_unlock(ag); + + if ((hf_codec_ids != 0) && atomic_test_bit(ag->flags, + BT_HFP_AG_CODEC_CHANGED)) + { + err = bt_hfp_ag_codec_select(ag); + atomic_set_bit_to(ag->flags, BT_HFP_AG_CODEC_CONN, err == 0); + if (call) + { + atomic_set_bit_to(call->flags, BT_HFP_AG_CALL_OPEN_SCO, err == 0); + } + } + else + { + err = hfp_ag_open_sco(ag, call); + } + + return err; +} + +static void bt_hfp_ag_notify_ongoing_calls(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_ongoing_call *ongoing_call; + struct bt_hfp_ag_call *call; + int err; + int active_call_count; + int held_call_count; + bool sco_created = false; + uint8_t call_setup_value = BT_HFP_CALL_SETUP_NONE; + + for (size_t index = 0; index < ag->ongoing_call_count; index++) + { + ongoing_call = &ag->ongoing_calls[index]; + + switch (ongoing_call->status) + { + case BT_HFP_AG_CALL_STATUS_ACTIVE: + bt_hfp_ag_add_active_call(ag, ongoing_call); + break; + case BT_HFP_AG_CALL_STATUS_HELD: + bt_hfp_ag_add_held_call(ag, ongoing_call); + break; + case BT_HFP_AG_CALL_STATUS_DIALING: + case BT_HFP_AG_CALL_STATUS_ALERTING: + sco_created = true; + err = bt_hfp_ag_outgoing(ag, ongoing_call->number); + if (err != 0) + { + sco_created = false; + LOG_ERR("Failed to initiate outgoing call: %d", err); + } + else if (ongoing_call->status == BT_HFP_AG_CALL_STATUS_ALERTING) + { + call = get_call_with_flag_and_state(ag, + BT_HFP_AG_CALL_IN_USING, + BT_HFP_CALL_OUTGOING); + if (call != NULL) + { + atomic_set_bit(call->flags, BT_HFP_AG_CALL_ALERTING); + } + + call_setup_value = BT_HFP_CALL_SETUP_REMOTE_ALERTING; + } + else + { + call_setup_value = BT_HFP_CALL_SETUP_OUTGOING; + } + break; + case BT_HFP_AG_CALL_STATUS_INCOMING: + case BT_HFP_AG_CALL_STATUS_WAITING: + sco_created = true; + err = bt_hfp_ag_remote_incoming(ag, ongoing_call->number); + if (err != 0) + { + sco_created = false; + LOG_ERR("Failed to initiate remote incoming call: %d", err); + } + else + { + call_setup_value = BT_HFP_CALL_SETUP_INCOMING; + } + break; + case BT_HFP_AG_CALL_STATUS_INCOMING_HELD: + bt_hfp_ag_add_incoming_held_call(ag, ongoing_call); + break; + } + } + + call = get_call_with_flag_and_state(ag, BT_HFP_AG_CALL_IN_USING, + BT_HFP_CALL_ACTIVE | BT_HFP_CALL_HOLD); + if ((sco_created == false) && (call != NULL)) + { + err = bt_hfp_ag_create_audio_connection(ag, call); + if (err) + { + LOG_ERR("Failed to create audio connection: %d", err); + } + } + + active_call_count = get_active_calls(ag); + held_call_count = get_held_calls(ag); + + if ((active_call_count > 0) && (held_call_count > 0)) + { + ag->indicator_value[BT_HFP_AG_CALL_HELD_IND] = + BT_HFP_CALL_HELD_ACTIVE_HELD; + } + else if (held_call_count > 0) + { + ag->indicator_value[BT_HFP_AG_CALL_HELD_IND] = BT_HFP_CALL_HELD_HELD; + } + else + { + ag->indicator_value[BT_HFP_AG_CALL_HELD_IND] = BT_HFP_CALL_HELD_NONE; + } + + if ((active_call_count > 0) || (held_call_count > 0) || + (get_call_with_flag(ag, BT_HFP_AG_CALL_INCOMING_HELD) != NULL)) + { + ag->indicator_value[BT_HFP_AG_CALL_IND] = 1; + } + + ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND] = call_setup_value; +} + +static void bt_hfp_ag_set_in_band_ring(struct bt_hfp_ag *ag) +{ + bool is_inband_ringtone; + + is_inband_ringtone = AG_SUPT_FEAT( + ag, BT_HFP_AG_FEATURE_INBAND_RINGTONE_ENABLE); + + if (is_inband_ringtone && !atomic_test_bit(ag->flags, + BT_HFP_AG_INBAND_RING)) + { + int err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BSIR:1\r\n"); + + atomic_set_bit_to(ag->flags, BT_HFP_AG_INBAND_RING, err == 0); + } +} + +static void bt_hfp_ag_slc_connected(struct bt_hfp_ag *ag, void *user_data) +{ + ARG_UNUSED(user_data); + + bt_hfp_ag_set_state(ag, BT_HFP_CONNECTED); + + bt_hfp_ag_set_in_band_ring(ag); + + bt_hfp_ag_notify_ongoing_calls(ag); +} + +static int bt_hfp_ag_cmer_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + uint32_t number; + int err; + static const uint32_t command_line_prefix[] = + { + 3, 0, 0 + }; + + if (!is_char(buf, '=')) + { + return -ENOTSUP; + } + + for (int i = 0; i < ARRAY_SIZE(command_line_prefix); i++) + { + err = get_number(buf, &number); + if (err != 0) + { + return -ENOTSUP; + } + + if (!is_char(buf, ',')) + { + return -ENOTSUP; + } + + if (command_line_prefix[i] != number) + { + return -ENOTSUP; + } + } + + err = get_number(buf, &number); + if (err != 0) + { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + + if (number == 1) + { + atomic_set_bit(ag->flags, BT_HFP_AG_CMER_ENABLE); + if (BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_3WAY_CALL, + BT_HFP_AG_FEATURE_3WAY_CALL)) + { + LOG_DBG("Waiting for AT+CHLD=?"); + return 0; + } + + /* SLC connected event needs to be notified. */ + + atomic_set_bit(ag->flags, BT_HFP_AG_SLC_CONNECTED); + return 0; + } + else if (number == 0) + { + atomic_clear_bit(ag->flags, BT_HFP_AG_CMER_ENABLE); + } + else + { + return -ENOTSUP; + } + + return 0; +} + +static void bt_hfp_ag_accept_cb(struct bt_hfp_ag *ag, void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); + + if (bt_ag && bt_ag->accept) + { + bt_ag->accept(call); + } +} + +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) +static int chld_release_all(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_call *call; + bt_hfp_call_state_t call_state; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) + { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) + { + continue; + } + + call_state = call->call_state; + if ((call_state != BT_HFP_CALL_ACTIVE) || + ((call_state == BT_HFP_CALL_ACTIVE) && + atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD))) + { + ag_terminate_call(call); + } + } + + ag_notify_call_held_ind(ag, NULL); + + return 0; +} + +static void bt_hfp_ag_accept_other_cb(struct bt_hfp_ag *ag, void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + bt_hfp_call_state_t call_state; + int err; + + if (!call) + { + return; + } + + call_state = call->call_state; + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); + + if (call_state == BT_HFP_CALL_HOLD) + { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_HELD_IND, + BT_HFP_CALL_HELD_ACTIVE_HELD, NULL, + NULL); + if (err) + { + LOG_ERR("Fail to send err :(%d)", err); + } + } + + if (call_state == BT_HFP_CALL_HOLD) + { + if (bt_ag && bt_ag->retrieve) + { + bt_ag->retrieve(call); + } + } + else + { + if (bt_ag && bt_ag->accept) + { + bt_ag->accept(call); + } + } + + ag_notify_call_held_ind(ag, NULL); +} + +static void bt_hfp_ag_deactivate_accept_other_cb(struct bt_hfp_ag *ag, + void *user_data) +{ + int err; + uint8_t call_setup; + + hfp_ag_lock(ag); + call_setup = ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND]; + hfp_ag_unlock(ag); + + if (call_setup) + { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, + BT_HFP_CALL_SETUP_NONE, + bt_hfp_ag_accept_other_cb, user_data); + if (err) + { + LOG_ERR("Fail to send err :(%d)", err); + } + } + else + { + bt_hfp_ag_accept_other_cb(ag, user_data); + } +} + +static struct bt_hfp_ag_call *get_none_accept_calls(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_call *call; + bt_hfp_call_state_t call_state; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) + { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) + { + continue; + } + + call_state = call->call_state; + if ((call_state == BT_HFP_CALL_OUTGOING) || + (call_state == BT_HFP_CALL_INCOMING) || + (call_state == BT_HFP_CALL_ALERTING)) + { + return call; + } + } + + return NULL; +} + +static struct bt_hfp_ag_call *get_none_active_calls(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_call *call; + bt_hfp_call_state_t call_state; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) + { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) + { + continue; + } + + call_state = call->call_state; + if (call_state != BT_HFP_CALL_ACTIVE) + { + return call; + } + else if ((call_state == BT_HFP_CALL_ACTIVE) && + atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)) + { + return call; + } + } + + return NULL; +} + +static int chld_deactivate_calls_and_accept_other_call(struct bt_hfp_ag *ag, + bool release) +{ + struct bt_hfp_ag_call *call; + struct bt_hfp_ag_call *none_active_call; + + none_active_call = get_none_accept_calls(ag); + + if (!none_active_call) + { + none_active_call = get_none_active_calls(ag); + } + else + { + if (!(atomic_test_bit(none_active_call->flags, + BT_HFP_AG_CALL_INCOMING_3WAY) || + atomic_test_bit(none_active_call->flags, + BT_HFP_AG_CALL_INCOMING))) + { + return -ENOTSUP; + } + } + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) + { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) + { + continue; + } + + if ((call->call_state == BT_HFP_CALL_ACTIVE) && + !atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)) + { + if (release) + { + ag_terminate_call(call); + } + else + { + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_HOLD); + + if (bt_ag && bt_ag->held) + { + bt_ag->held(call); + } + } + } + } + + return hfp_ag_next_step(ag, bt_hfp_ag_deactivate_accept_other_cb, + none_active_call); +} + +static int chld_activate_held_call(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) + { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) + { + continue; + } + + if (call->call_state == BT_HFP_CALL_HOLD) + { + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); + + if (bt_ag && bt_ag->retrieve) + { + bt_ag->retrieve(call); + } + } + } + + ag_notify_call_held_ind(ag, NULL); + + return 0; +} + +static int chld_drop_conversation(struct bt_hfp_ag *ag) +{ + struct bt_hfp_ag_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) + { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) + { + continue; + } + + if (call->call_state != BT_HFP_CALL_ACTIVE) + { + return -ENOTSUP; + } + } + + if (!bt_ag || !bt_ag->explicit_call_transfer) + { + return -ENOEXEC; + } + + bt_ag->explicit_call_transfer(ag); + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) + { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) + { + continue; + } + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_TERMINATE); + } + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_AUDIO_CONN)) + { + LOG_DBG("Close SCO connection"); + hfp_ag_close_sco(ag); + } + + ag_notify_call_held_ind(ag, NULL); + + return 0; +} + +static int chld_release_call(struct bt_hfp_ag *ag, uint8_t call_index) +{ + struct bt_hfp_ag_call *call; + bt_hfp_call_state_t call_state; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) + { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) + { + continue; + } + + if (index != call_index) + { + continue; + } + + call_state = call->call_state; + if ((call_state == BT_HFP_CALL_HOLD) || + ((call_state == BT_HFP_CALL_ACTIVE) && + !atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD))) + { + ag_terminate_call(call); + } + else + { + ag_reject_call(call); + } + } + + ag_notify_call_held_ind(ag, NULL); + + return 0; +} + +static int chld_held_other_calls(struct bt_hfp_ag *ag, uint8_t call_index) +{ + struct bt_hfp_ag_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) + { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) + { + continue; + } + + if (index == call_index) + { + continue; + } + + if ((call->call_state == BT_HFP_CALL_ACTIVE) && + !atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)) + { + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_HOLD); + + if (bt_ag && bt_ag->held) + { + bt_ag->held(call); + } + } + } + + call = &ag->calls[call_index]; + if (call->call_state == BT_HFP_CALL_HOLD) + { + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); + if (bt_ag && bt_ag->retrieve) + { + bt_ag->retrieve(call); + } + } + + ag_notify_call_held_ind(ag, NULL); + + return 0; +} + +static int chld_other(struct bt_hfp_ag *ag, uint32_t value) +{ + uint8_t index; + uint8_t command; + + index = value % 10; + command = value / 10; + + if (!index) + { + return -ENOEXEC; + } + + index = index - 1; + + if (index >= ARRAY_SIZE(ag->calls)) + { + return -ENOEXEC; + } + + switch (command) + { + case BT_HFP_CHLD_RELEASE_ACTIVE_ACCEPT_OTHER: + return chld_release_call(ag, index); + case BT_HFP_CALL_HOLD_ACTIVE_ACCEPT_OTHER: + return chld_held_other_calls(ag, index); + } + + return -ENOEXEC; +} +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + +static int bt_hfp_ag_chld_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) + uint32_t value; + int err; + char *response; + + if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_3WAY_CALL, + BT_HFP_AG_FEATURE_3WAY_CALL)) + { + return -ENOEXEC; + } + + if (!is_char(buf, '=')) + { + return -ENOTSUP; + } + + if (is_char(buf, '?')) + { + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + +#if defined(CONFIG_BT_HFP_AG_ECC) + response = "+CHLD:(0,1,1x,2,2x,3,4)"; +#else + response = "+CHLD:(0,1,2,3,4)"; +#endif /* CONFIG_BT_HFP_AG_ECC */ + if (BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_HF_IND, + BT_HFP_AG_FEATURE_HF_IND)) + { + /* Notify the SLC connected after the procedure of HF Indicators is + * done + */ + + LOG_DBG("Waiting for AT+BIND?"); + } + else + { + /* SLC connected event needs to be notified. */ + + atomic_set_bit(ag->flags, BT_HFP_AG_SLC_CONNECTED); + } + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n%s\r\n", response); + return err; + } + + err = get_number(buf, &value); + if (err != 0) + { + return -ENOTSUP; + } + + switch (value) + { + case BT_HFP_CHLD_RELEASE_ALL: + return chld_release_all(ag); + case BT_HFP_CHLD_RELEASE_ACTIVE_ACCEPT_OTHER: + return chld_deactivate_calls_and_accept_other_call(ag, true); + case BT_HFP_CALL_HOLD_ACTIVE_ACCEPT_OTHER: + return chld_deactivate_calls_and_accept_other_call(ag, false); + case BT_HFP_CALL_ACTIVE_HELD: + return chld_activate_held_call(ag); + case BT_HFP_CALL_QUITE: + return chld_drop_conversation(ag); + } + + return chld_other(ag, value); +#else + return -ENOEXEC; +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ +} + +static int bt_hfp_ag_bind_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + uint32_t indicator; + uint32_t hf_indicators = 0U; + uint32_t supported_indicators = 0U; + int err; + char *data; + uint32_t len; + + if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_HF_IND, + BT_HFP_AG_FEATURE_HF_IND)) + { + return -ENOEXEC; + } + + if (is_char(buf, '?')) + { + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + + hf_indicators = ag->hf_indicators; + supported_indicators = + ag->hf_indicators_of_ag & ag->hf_indicators_of_hf; + len = MIN(NUM_BITS(sizeof(supported_indicators)), HFP_HF_IND_MAX); + for (int i = 1; i < len; i++) + { + bool enabled; + + if (!(BIT(i) & supported_indicators)) + { + continue; + } + + enabled = BIT(i) & hf_indicators; + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BIND:%d,%d\r\n", i, + enabled ? 1 : 0); + if (err) + { + return err; + } + + supported_indicators &= ~BIT(i); + + if (!supported_indicators) + { + break; + } + } + + /* SLC connected event needs to be notified. */ + + atomic_set_bit(ag->flags, BT_HFP_AG_SLC_CONNECTED); + return 0; + } + + if (!is_char(buf, '=')) + { + return -ENOTSUP; + } + + if (is_char(buf, '?')) + { + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + + data = &ag->buffer[0]; + *data = '('; + data++; + hf_indicators = ag->hf_indicators_of_ag; + len = MIN(NUM_BITS(sizeof(hf_indicators)), HFP_HF_IND_MAX); + for (int i = 1; (i < len) && (hf_indicators != 0); i++) + { + if (BIT(i) & hf_indicators) + { + int length = + snprintf(data, + (char *)&ag->buffer[HF_MAX_BUF_LEN - 1] - + data - 2, + "%d", i); + + data += length; + hf_indicators &= ~BIT(i); + } + + if (hf_indicators != 0) + { + *data = ','; + data++; + } + } + + *data = ')'; + data++; + *data = '\0'; + data++; + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BIND:%s\r\n", + &ag->buffer[0]); + return err; + } + + while (buf->len > 0) + { + err = get_number(buf, &indicator); + if (err != 0) + { + return -ENOTSUP; + } + + if (!is_char(buf, ',')) + { + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + } + + if (indicator < NUM_BITS(sizeof(hf_indicators))) + { + hf_indicators |= BIT(indicator); + } + } + + ag->hf_indicators_of_hf = hf_indicators; + + return 0; +} + +static int bt_hfp_ag_cmee_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + uint32_t cmee; + int err; + + if (!is_char(buf, '=')) + { + return -ENOTSUP; + } + + err = get_number(buf, &cmee); + if (err != 0) + { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + + if (cmee > 1) + { + return -ENOTSUP; + } + + if (BT_HFP_AG_FEATURE_EXT_ERR_ENABLE) + { + atomic_set_bit_to(ag->flags, BT_HFP_AG_CMEE_ENABLE, cmee == 1); + return 0; + } + + return -ENOTSUP; +} + +static void bt_hfp_ag_terminate_cb(struct bt_hfp_ag *ag, void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + if (call) + { + ag_terminate_call(call); + } + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_AUDIO_CONN)) + { + LOG_DBG("It is not audio connection"); + hfp_ag_close_sco(ag); + } +} + +static void bt_hfp_ag_unit_call_terminate(struct bt_hfp_ag *ag, + void *user_data) +{ + int err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, + bt_hfp_ag_terminate_cb, user_data); + + if (err != 0) + { + LOG_ERR("Fail to send err :(%d)", err); + } +} + +static void bt_hfp_ag_active_terminate_cb(struct bt_hfp_ag *ag, + void *user_data) +{ + bt_hfp_ag_unit_call_terminate(ag, user_data); +} + +static void bt_hfp_ag_call_terminate(struct bt_hfp_ag *ag, void *user_data) +{ + int err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, + BT_HFP_CALL_SETUP_NONE, + bt_hfp_ag_active_terminate_cb, + user_data); + + if (err) + { + LOG_ERR("Fail to send err :(%d)", err); + } +} + +static int bt_hfp_ag_chup_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + bt_hfp_call_state_t call_state; + struct bt_hfp_ag_call *call; + int call_count; + + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + + call_count = get_none_released_calls(ag); + + if (call_count == 1) + { + bt_hfp_ag_tx_cb_t next_step = NULL; + + call = get_call_with_flag(ag, BT_HFP_AG_CALL_IN_USING); + if (!call) + { + return 0; + } + + call_state = call->call_state; + if (call_state == BT_HFP_CALL_ALERTING) + { + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) + { + next_step = bt_hfp_ag_call_terminate; + } + else + { + next_step = bt_hfp_ag_call_reject; + } + } + else if (call_state == BT_HFP_CALL_ACTIVE) + { + next_step = bt_hfp_ag_unit_call_terminate; + } + else if (call_state == BT_HFP_CALL_OUTGOING) + { + next_step = bt_hfp_ag_call_terminate; + } + + if (next_step) + { + err = hfp_ag_next_step(ag, next_step, call); + return err; + } + + return -ENOTSUP; + } + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) + { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) + { + continue; + } + + if (call->call_state == BT_HFP_CALL_ACTIVE) + { + err = hfp_ag_next_step(ag, bt_hfp_ag_unit_call_terminate, call); + } + } + + return 0; +} + +static uint8_t bt_hfp_get_call_state(struct bt_hfp_ag_call *call) +{ + uint8_t status = 0; + struct bt_hfp_ag *ag = call->ag; + + hfp_ag_lock(ag); + switch (call->call_state) + { + case BT_HFP_CALL_TERMINATE: + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) + { + status = BT_HFP_CLCC_STATUS_INCOMING; + } + else + { + status = BT_HFP_CLCC_STATUS_DIALING; + } + break; + case BT_HFP_CALL_OUTGOING: + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_ALERTING)) + { + status = BT_HFP_CLCC_STATUS_ALERTING; + } + else + { + status = BT_HFP_CLCC_STATUS_DIALING; + } + break; + case BT_HFP_CALL_INCOMING: + status = BT_HFP_CLCC_STATUS_INCOMING; + break; + case BT_HFP_CALL_ALERTING: + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) + { + status = BT_HFP_CLCC_STATUS_WAITING; + } + else + { + status = BT_HFP_CLCC_STATUS_ALERTING; + } + break; + case BT_HFP_CALL_ACTIVE: + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)) + { + status = BT_HFP_CLCC_STATUS_CALL_HELD_HOLD; + } + else + { + status = BT_HFP_CLCC_STATUS_ACTIVE; + } + break; + case BT_HFP_CALL_HOLD: + status = BT_HFP_CLCC_STATUS_HELD; + break; + default: + break; + } + + hfp_ag_unlock(ag); + + return status; +} + +static int bt_hfp_ag_clcc_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + struct bt_hfp_ag_call *call; + uint8_t dir; + uint8_t status; + uint8_t mode; + uint8_t mpty; + int active_calls; + + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + + active_calls = get_active_calls(ag); + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) + { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) + { + continue; + } + + dir = atomic_test_bit(call->flags, + BT_HFP_AG_CALL_INCOMING) ? + BT_HFP_CLCC_DIR_INCOMING + : BT_HFP_CLCC_DIR_OUTGOING; + status = bt_hfp_get_call_state(call); + mode = 0; + mpty = (status == BT_HFP_CLCC_STATUS_ACTIVE) && + (active_calls > 1) ? 1 : 0; + err = hfp_ag_send_data(ag, NULL, NULL, + "\r\n+CLCC:%d,%d,%d,%d,%d,\"%s\",%d\r\n", + index + 1, + dir, status, mode, mpty, call->number, + call->type); + if (err) + { + LOG_ERR("Fail to send err :(%d)", err); + } + } + + /* AG shall always send OK response to HF */ + + return 0; +} + +static int bt_hfp_ag_bia_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + uint32_t number; + int err; + int index = 0; + uint32_t indicator; + + if (!is_char(buf, '=')) + { + return -ENOTSUP; + } + + hfp_ag_lock(ag); + indicator = ag->indicator; + hfp_ag_unlock(ag); + + while (buf->len > 0) + { + err = get_number(buf, &number); + if (err == 0) + { + /* Valid number */ + + if (number) + { + indicator |= BIT(index); + } + else + { + indicator &= ~BIT(index); + } + } + + if (is_char(buf, ',')) + { + index++; + } + else + { + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + + if (buf->len != 0) + { + return -ENOTSUP; + } + } + } + + /* Force call, call setup and held call indicators are enabled. */ + + indicator |= + BIT(BT_HFP_AG_CALL_IND) | BIT(BT_HFP_AG_CALL_SETUP_IND) | + BIT(BT_HFP_AG_CALL_HELD_IND); + + hfp_ag_lock(ag); + ag->indicator = indicator; + hfp_ag_unlock(ag); + + return 0; +} + +static void bt_hfp_ag_audio_connection(struct bt_hfp_ag *ag, void *user_data) +{ + int err; + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + err = bt_hfp_ag_create_audio_connection(ag, call); + if (err != 0) + { + LOG_ERR("Failed to create audio conn: %d", err); + } +} + +static void bt_hfp_ag_call_setup_none_cb(struct bt_hfp_ag *ag, + void *user_data) +{ + int err; + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_OUTGOING_3WAY)) + { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_HELD_IND, + BT_HFP_CALL_HELD_ACTIVE_HELD, + bt_hfp_ag_accept_cb, call); + if (err) + { + bt_hfp_ag_unit_call_terminate(ag, user_data); + } + + return; + } + + bt_hfp_ag_audio_connection(ag, user_data); +} + +static void bt_hfp_ag_unit_call_accept(struct bt_hfp_ag *ag, void *user_data) +{ + int err; + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 1, + bt_hfp_ag_accept_cb, + user_data); + if (err) + { + LOG_ERR("Fail to send err :(%d)", err); + } + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, + BT_HFP_CALL_SETUP_NONE, + bt_hfp_ag_call_setup_none_cb, user_data); + if (err) + { + LOG_ERR("Fail to send err :(%d)", err); + } +} + +static int bt_hfp_ag_ata_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + int call_count; + struct bt_hfp_ag_call *call; + + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + + hfp_ag_lock(ag); + call_count = get_none_released_calls(ag); + if (call_count != 1) + { + hfp_ag_unlock(ag); + return -ENOTSUP; + } + + call = get_call_with_flag(ag, BT_HFP_AG_CALL_IN_USING); + DEBUGASSERT(call); + + if (call->call_state != BT_HFP_CALL_ALERTING) + { + hfp_ag_unlock(ag); + return -ENOTSUP; + } + + hfp_ag_unlock(ag); + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) + { + return -ENOTSUP; + } + + err = hfp_ag_next_step(ag, bt_hfp_ag_unit_call_accept, call); + + return err; +} + +static int bt_hfp_ag_cops_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + uint32_t number; + + if (is_char(buf, '=')) + { + static const uint32_t command_line_prefix[] = + { + 3, 0 + }; + + for (int i = 0; i < ARRAY_SIZE(command_line_prefix); i++) + { + err = get_number(buf, &number); + if (err != 0) + { + return -ENOTSUP; + } + + if (command_line_prefix[i] != number) + { + return -ENOTSUP; + } + + if (!is_char(buf, ',')) + { + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + } + } + + atomic_set_bit(ag->flags, BT_HFP_AG_COPS_SET); + return 0; + } + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_COPS_SET)) + { + return -ENOTSUP; + } + + if (!is_char(buf, '?')) + { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+COPS:%d,%d,\"%s\"\r\n", + ag->mode, 0, ag->operator); + if (err) + { + LOG_ERR("Fail to send err :(%d)", err); + } + + return err; +} + +static int bt_hfp_ag_bcc_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ +#if BT_HFP_AG_FEATURE_CODEC_NEG_ENABLE + int err; + + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + + hfp_ag_lock(ag); + if (ag->selected_codec_id && + (!(ag->hf_codec_ids & BIT(ag->selected_codec_id)))) + { + hfp_ag_unlock(ag); + return -ENOTSUP; + } + + if (atomic_ptr_read(&ag->sco_conn) != NULL) + { + hfp_ag_unlock(ag); + return -ECONNREFUSED; + } + + hfp_ag_unlock(ag); + + if (bt_ag && bt_ag->audio_connect_req) + { + bt_ag->audio_connect_req(ag); + return 0; + } + + err = hfp_ag_next_step(ag, bt_hfp_ag_audio_connection, NULL); + + return err; +#else + return -ENOTSUP; +#endif /* BT_HFP_AG_FEATURE_CODEC_NEG_ENABLE */ +} + +static void bt_hfp_ag_unit_codec_conn_setup(struct bt_hfp_ag *ag, + void *user_data) +{ + int err = hfp_ag_open_sco(ag, user_data); + + if (err != 0) + { + LOG_ERR("Failed to open sco: %d", err); + } +} + +static int bt_hfp_ag_bcs_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + uint32_t number; + bool codec_conn; + struct bt_hfp_ag_call *call; + + if (!is_char(buf, '=')) + { + return -ENOTSUP; + } + + err = get_number(buf, &number); + if (err != 0) + { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_CODEC_CONN)) + { + return -ESRCH; + } + + hfp_ag_lock(ag); + if (ag->selected_codec_id != number) + { + LOG_ERR("Received codec id %d is not aligned with selected %d", number, + ag->selected_codec_id); + err = -ENOTSUP; + } + else if (!(ag->hf_codec_ids & BIT(ag->selected_codec_id))) + { + LOG_ERR("Selected codec id %d is unsupported " + "%d", ag->selected_codec_id, + ag->hf_codec_ids); + err = -ENOTSUP; + } + + hfp_ag_unlock(ag); + + codec_conn = atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_CODEC_CONN); + atomic_clear_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED); + + call = get_call_with_flag(ag, BT_HFP_AG_CALL_OPEN_SCO); + + if (err == 0) + { + if (codec_conn && bt_ag && bt_ag->codec_negotiate) + { + bt_ag->codec_negotiate(ag, err); + } + + err = hfp_ag_next_step(ag, bt_hfp_ag_unit_codec_conn_setup, call); + } + else + { + if (codec_conn && bt_ag && bt_ag->codec_negotiate) + { + bt_ag->codec_negotiate(ag, err); + } + + if (call) + { + if (call->call_state != BT_HFP_CALL_TERMINATE) + { + (void)hfp_ag_next_step(ag, bt_hfp_ag_unit_call_terminate, + call); + } + } + } + + return err; +} + +static void bt_hfp_ag_outgoing_cb(struct bt_hfp_ag *ag, void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + bool in_bond = false; + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_OUTGOING); + + if (bt_ag && bt_ag->outgoing) + { + bt_ag->outgoing(ag, call, call->number); + } + + if (atomic_test_bit(ag->flags, BT_HFP_AG_INBAND_RING) || + atomic_test_bit(call->flags, BT_HFP_AG_CALL_OUTGOING_3WAY)) + { + int err; + + err = bt_hfp_ag_create_audio_connection(ag, call); + if (err != 0) + { + LOG_ERR("Failed to create audio conn: %d", err); + } + else + { + in_bond = true; + } + } + + if (atomic_test_and_clear_bit(call->flags, BT_HFP_AG_CALL_ALERTING)) + { + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ALERTING); + bt_hfp_ag_call_ringing_cb(call, in_bond); + } +} + +static void bt_hfp_ag_unit_call_outgoing(struct bt_hfp_ag *ag, + void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + int err; + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, + BT_HFP_CALL_SETUP_OUTGOING, + bt_hfp_ag_outgoing_cb, call); + + if (err) + { + LOG_ERR("Fail to send err :(%d)", err); + } +} + +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) +static void bt_hfp_ag_call_held_cb(struct bt_hfp_ag *ag, void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) + { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) + { + continue; + } + + if ((call->call_state == BT_HFP_CALL_ACTIVE) && + !atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)) + { + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_HOLD); + if (bt_ag && bt_ag->held) + { + bt_ag->held(call); + } + } + } + + bt_hfp_ag_unit_call_outgoing(ag, user_data); +} + +static void bt_hfp_ag_unit_call_outgoing_3way(struct bt_hfp_ag *ag, + void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + int err; + uint8_t old_value; + + atomic_set_bit(call->flags, BT_HFP_AG_CALL_OUTGOING_3WAY); + + hfp_ag_lock(ag); + old_value = ag->indicator_value[BT_HFP_AG_CALL_HELD_IND]; + hfp_ag_unlock(ag); + if (old_value == BT_HFP_CALL_HELD_HELD) + { + LOG_WRN("No active call"); + bt_hfp_ag_call_held_cb(ag, user_data); + return; + } + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_HELD_IND, + BT_HFP_CALL_HELD_HELD, + bt_hfp_ag_call_held_cb, call); + if (err) + { + LOG_ERR("Fail to send err :(%d)", err); + } +} +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + +static int bt_hfp_ag_outgoing_call(struct bt_hfp_ag *ag, + const char *number, uint8_t type) +{ + int err; + size_t len; + struct bt_hfp_ag_call *call; + int call_count; + + len = strlen(number); + if (len == 0) + { + return -ENOTSUP; + } + + if (len > CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN) + { + return -ENAMETOOLONG; + } + + call = get_call_from_number(ag, number, type); + if (call) + { + return -EBUSY; + } + + call_count = get_none_released_calls(ag); + + if (call_count) + { +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) + if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_3WAY_CALL, + BT_HFP_AG_FEATURE_3WAY_CALL)) + { + return -ENOEXEC; + } + + if (!get_active_held_calls(ag)) + { + LOG_WRN("The first call is not accepted"); + return -EBUSY; + } +#else + return -EBUSY; +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + } + + call = get_new_call(ag, number, type); + if (!call) + { + LOG_WRN("Cannot allocate a new call object"); + return -ENOMEM; + } + + atomic_clear_bit(call->flags, BT_HFP_AG_CALL_INCOMING); + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_OUTGOING); + +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) + if (call_count) + { + err = hfp_ag_next_step(ag, bt_hfp_ag_unit_call_outgoing_3way, call); + if (err) + { + free_call(call); + } + + return err; + } +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + + err = hfp_ag_next_step(ag, bt_hfp_ag_unit_call_outgoing, call); + if (err) + { + free_call(call); + } + + return err; +} + +static int bt_hfp_ag_atd_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + char *number = NULL; + uint8_t *data; + bool is_memory_dial = false; + uint16_t len; + + if (is_char(buf, '>')) + { + is_memory_dial = true; + } + + len = sizeof(uint8_t) + sizeof(uint8_t); + if (buf->len <= len) + { + LOG_WRN("Short packet"); + return -EINVAL; + } + + len = buf->len - len; + data = net_buf_pull_mem(buf, len); + + if (!is_char(buf, ';')) + { + LOG_WRN("Missing semicolon character"); + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) + { + LOG_WRN("Missing enter character"); + return -ENOTSUP; + } + + /* Change the `;` to `\0` */ + + data[len] = 0; + + if (len > CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN) + { + return -ENAMETOOLONG; + } + + if (is_memory_dial) + { + if ((bt_ag != NULL) && (bt_ag->memory_dial != NULL)) + { + err = bt_ag->memory_dial(ag, data, &number); + if ((err != 0) || (number == NULL)) + { + return -ENOTSUP; + } + } + else + { + return -ENOTSUP; + } + } + else + { + number = (char *)data; + if ((bt_ag != NULL) && (bt_ag->number_call != NULL)) + { + err = bt_ag->number_call(ag, number); + if (err != 0) + { + return err; + } + } + else + { + return -ENOTSUP; + } + } + + return bt_hfp_ag_outgoing_call(ag, number, 0); +} + +static int bt_hfp_ag_bldn_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + char number[CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN + 1]; + + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + + if ((bt_ag == NULL) || (bt_ag->redial == NULL)) + { + return -ENOTSUP; + } + + memset(number, 0, sizeof(number)); + err = bt_ag->redial(ag, number); + if (err != 0) + { + return err; + } + + /* Add null-terminated to avoid unexpected issue. */ + + number[CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN] = '\0'; + + return bt_hfp_ag_outgoing_call(ag, number, 0); +} + +static int bt_hfp_ag_clip_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + uint32_t clip; + + if (!is_char(buf, '=')) + { + return -ENOTSUP; + } + + err = get_number(buf, &clip); + if (err != 0) + { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + + if (clip > 1) + { + return -ENOTSUP; + } + + atomic_set_bit_to(ag->flags, BT_HFP_AG_CLIP_ENABLE, clip == 1); + + return err; +} + +static int bt_hfp_ag_vgm_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + uint32_t vgm; + + if (!is_char(buf, '=')) + { + return -ENOTSUP; + } + + err = get_number(buf, &vgm); + if (err != 0) + { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + + if (vgm > BT_HFP_HF_VGM_GAIN_MAX) + { + LOG_ERR("Invalid vgm (%d>%d)", vgm, BT_HFP_HF_VGM_GAIN_MAX); + return -ENOTSUP; + } + + if (bt_ag && bt_ag->vgm) + { + bt_ag->vgm(ag, (uint8_t)vgm); + } + + return err; +} + +static int bt_hfp_ag_vgs_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + uint32_t vgs; + + if (!is_char(buf, '=')) + { + return -ENOTSUP; + } + + err = get_number(buf, &vgs); + if (err != 0) + { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + + if (vgs > BT_HFP_HF_VGS_GAIN_MAX) + { + LOG_ERR("Invalid vgs (%d>%d)", vgs, BT_HFP_HF_VGS_GAIN_MAX); + return -ENOTSUP; + } + + if (bt_ag && bt_ag->vgs) + { + bt_ag->vgs(ag, (uint8_t)vgs); + } + + return err; +} + +static int bt_hfp_ag_nrec_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + uint32_t disable; + + if (!is_char(buf, '=')) + { + return -ENOTSUP; + } + + err = get_number(buf, &disable); + if (err != 0) + { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + + if (!AG_SUPT_FEAT(ag, BT_HFP_AG_FEATURE_ECNR)) + { + return -ENOTSUP; + } + + if (disable) + { + LOG_ERR("Only support EC NR disable"); + return -ENOTSUP; + } + +#if defined(CONFIG_BT_HFP_AG_ECNR) + if (bt_ag && bt_ag->ecnr_turn_off) + { + bt_ag->ecnr_turn_off(ag); + return 0; + } +#endif /* CONFIG_BT_HFP_AG_ECNR */ + + return -ENOTSUP; +} + +static void btrh_held_call_updated_cb(struct bt_hfp_ag *ag, void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); +} + +static void btrh_held_call_setup_updated_cb(struct bt_hfp_ag *ag, + void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + if (bt_ag && bt_ag->incoming_held) + { + bt_ag->incoming_held(call); + } +} + +static void btrh_held_cb(struct bt_hfp_ag *ag, void *user_data) +{ + int err; + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 1, + btrh_held_call_updated_cb, user_data); + if (err) + { + LOG_ERR("Fail to send err :(%d)", err); + bt_hfp_ag_unit_call_terminate(ag, user_data); + } + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, + BT_HFP_CALL_SETUP_NONE, + btrh_held_call_setup_updated_cb, user_data); + if (err) + { + LOG_ERR("Fail to send err :(%d)", err); + bt_hfp_ag_unit_call_terminate(ag, user_data); + } +} + +static void btrh_accept_cb(struct bt_hfp_ag *ag, void *user_data) +{ + int err; + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + if (bt_ag && bt_ag->accept) + { + bt_ag->accept(call); + } + + err = bt_hfp_ag_create_audio_connection(ag, call); + if (err != 0) + { + LOG_ERR("Failed to create audio connection: %d", err); + } +} + +static void btrh_reject_cb(struct bt_hfp_ag *ag, void *user_data) +{ + int err; + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, + bt_hfp_ag_reject_cb, + user_data); + if (err) + { + bt_hfp_ag_unit_call_terminate(ag, user_data); + } +} + +static int bt_hfp_ag_btrh_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + uint32_t action; + struct bt_hfp_ag_call *call; + + if (is_char(buf, '?')) + { + call = get_call_with_flag(ag, BT_HFP_AG_CALL_INCOMING_HELD); + if (call && (call->call_state == BT_HFP_CALL_ACTIVE)) + { + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_ON_HOLD); + if (err) + { + LOG_ERR("Fail to send err :(%d)", err); + } + + return err; + } + + return 0; + } + + if (!is_char(buf, '=')) + { + return -ENOTSUP; + } + + err = get_number(buf, &action); + if (err != 0) + { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + + if (action == BT_HFP_BTRH_ON_HOLD) + { + call = get_call_with_flag_and_state(ag, BT_HFP_AG_CALL_INCOMING, + BT_HFP_CALL_ALERTING | + BT_HFP_CALL_INCOMING); + if (call) + { + atomic_set_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD); + err = hfp_ag_send_data(ag, btrh_held_cb, call, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_ON_HOLD); + if (err) + { + LOG_ERR("Fail to send err :(%d)", err); + } + + return err; + } + } + else if (action == BT_HFP_BTRH_ACCEPTED) + { + call = get_call_with_flag_and_state(ag, BT_HFP_AG_CALL_INCOMING_HELD, + BT_HFP_CALL_ACTIVE); + if (call) + { + atomic_clear_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD); + err = hfp_ag_send_data(ag, btrh_accept_cb, call, + "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_ACCEPTED); + if (err) + { + LOG_ERR("Fail to send err :(%d)", err); + } + + return err; + } + } + else if (action == BT_HFP_BTRH_REJECTED) + { + call = get_call_with_flag_and_state(ag, BT_HFP_AG_CALL_INCOMING_HELD, + BT_HFP_CALL_ACTIVE); + if (call) + { + atomic_clear_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD); + err = hfp_ag_send_data(ag, btrh_reject_cb, call, + "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_REJECTED); + if (err) + { + LOG_ERR("Fail to send err :(%d)", err); + } + + return err; + } + } + + return -ENOTSUP; +} + +static int bt_hfp_ag_ccwa_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + uint32_t value; + + if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_3WAY_CALL, + BT_HFP_AG_FEATURE_3WAY_CALL)) + { + return -ENOEXEC; + } + + if (!is_char(buf, '=')) + { + return -ENOTSUP; + } + + err = get_number(buf, &value); + if (err != 0) + { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + + if (value > 1) + { + return -ENOTSUP; + } + + atomic_set_bit_to(ag->flags, BT_HFP_AG_CCWA_ENABLE, value); + return 0; +} + +static void bt_hfp_ag_vr_activate(struct bt_hfp_ag *ag, void *user_data) +{ + bool feature; + + feature = BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_ENH_VOICE_RECG, + BT_HFP_AG_FEATURE_ENH_VOICE_RECG); + +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG) + if (bt_ag && bt_ag->voice_recognition) + { + bt_ag->voice_recognition(ag, true); + } + else + { + (void)bt_hfp_ag_audio_connect(ag, BT_HFP_AG_CODEC_CVSD); + } +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG */ + + atomic_set_bit_to(ag->flags, BT_HFP_AG_VRE_R2A, !feature); + +#if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) + if (atomic_test_bit(ag->flags, BT_HFP_AG_VRE_R2A)) + { + if (bt_ag && bt_ag->ready_to_accept_audio) + { + bt_ag->ready_to_accept_audio(ag); + } + } +#endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ +} + +static void bt_hfp_ag_vr_deactivate(struct bt_hfp_ag *ag, void *user_data) +{ +#if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) + if (bt_ag && bt_ag->voice_recognition) + { + bt_ag->voice_recognition(ag, false); + } +#endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ +} + +static void bt_hfp_ag_vr_ready2accept(struct bt_hfp_ag *ag, void *user_data) +{ + atomic_set_bit(ag->flags, BT_HFP_AG_VRE_R2A); + +#if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) + if (bt_ag && bt_ag->ready_to_accept_audio) + { + bt_ag->ready_to_accept_audio(ag); + } +#endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ +} + +static int bt_hfp_ag_bvra_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + uint32_t value; + + if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_VOICE_RECG, + BT_HFP_AG_FEATURE_VOICE_RECG)) + { + return -ENOEXEC; + } + + if (!is_char(buf, '=')) + { + return -ENOTSUP; + } + + err = get_number(buf, &value); + if (err != 0) + { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + + switch (value) + { + case BT_HFP_BVRA_DEACTIVATION: + if (!atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) + { + LOG_WRN("VR is not activated"); + return -ENOTSUP; + } + + err = hfp_ag_next_step(ag, bt_hfp_ag_vr_deactivate, NULL); + break; + case BT_HFP_BVRA_ACTIVATION: + if (atomic_test_and_set_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) + { + LOG_WRN("VR has been activated"); + return -ENOTSUP; + } + + atomic_clear_bit(ag->flags, BT_HFP_AG_VRE_R2A); + err = hfp_ag_next_step(ag, bt_hfp_ag_vr_activate, NULL); + break; + case BT_HFP_BVRA_READY_TO_ACCEPT: + if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_ENH_VOICE_RECG, + BT_HFP_AG_FEATURE_ENH_VOICE_RECG)) + { + LOG_WRN("Enhance voice recognition is not supported"); + return -ENOEXEC; + } + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) + { + LOG_WRN("Voice recognition is not activated"); + return -ENOTSUP; + } + + err = hfp_ag_next_step(ag, bt_hfp_ag_vr_ready2accept, NULL); + break; + default: + return -ENOTSUP; + } + + return err; +} + +static int bt_hfp_ag_binp_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + uint32_t value; +#if defined(CONFIG_BT_HFP_AG_VOICE_TAG) + char *number = NULL; +#endif /* CONFIG_BT_HFP_AG_VOICE_TAG */ + + if (!AG_SUPT_FEAT(ag, BT_HFP_AG_FEATURE_VOICE_TAG)) + { + return -ENOEXEC; + } + + if (!is_char(buf, '=')) + { + return -ENOTSUP; + } + + err = get_number(buf, &value); + if (err != 0) + { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + + if (value != 1) + { + return -ENOTSUP; + } + +#if defined(CONFIG_BT_HFP_AG_VOICE_TAG) + if (bt_ag && bt_ag->request_phone_number) + { + err = bt_ag->request_phone_number(ag, &number); + if (err) + { + LOG_DBG("Cannot request phone number :(%d)", err); + return err; + } + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BINP:\"%s\"\r\n", number); + if (err) + { + LOG_ERR("Fail to send err :(%d)", err); + } + + return err; + } +#endif /* CONFIG_BT_HFP_AG_VOICE_TAG */ + + return -ENOTSUP; +} + +static int bt_hfp_ag_vts_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + char code = 0; + + if (!is_char(buf, '=')) + { + return -ENOTSUP; + } + + err = get_char(buf, &code); + if (err != 0) + { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + + if (!IS_VALID_DTMF(code)) + { + LOG_ERR("Invalid code"); + return -EINVAL; + } + + if (!get_active_calls(ag)) + { + LOG_ERR("Not valid ongoing call"); + return -ENOTSUP; + } + + if (bt_ag && bt_ag->transmit_dtmf_code) + { + bt_ag->transmit_dtmf_code(ag, code); + } + else + { + return -ENOTSUP; + } + + return 0; +} + +static int send_subscriber_number(struct bt_hfp_ag *ag, + char *number, + uint8_t type, + uint8_t service) +{ + int err; + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+CNUM:,\"%s\",%d,,%d\r\n", + number, type, service); + if (err) + { + LOG_ERR("Fail to send subscriber number :(%d)", err); + } + + return err; +} + +static int bt_hfp_ag_cnum_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + int err; + + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + + if (bt_ag && bt_ag->subscriber_number) + { + err = bt_ag->subscriber_number(ag, send_subscriber_number); + return err; + } + + return 0; +} + +static int bt_hfp_ag_biev_handler(struct bt_hfp_ag *ag, struct net_buf *buf) +{ + uint32_t indicator; + uint32_t value; + + if (!is_char(buf, '=')) + { + return -ENOTSUP; + } + + if (get_number(buf, &indicator)) + { + return -ENOTSUP; + } + + if (!is_char(buf, ',')) + { + return -ENOTSUP; + } + + if (get_number(buf, &value)) + { + return -ENOTSUP; + } + + if (!is_char(buf, '\r')) + { + return -ENOTSUP; + } + + hfp_ag_lock(ag); + if (!(ag->hf_indicators_of_ag & BIT(indicator))) + { + hfp_ag_unlock(ag); + return -ENOTSUP; + } + + hfp_ag_unlock(ag); + +#if defined(CONFIG_BT_HFP_AG_HF_INDICATORS) + if (bt_ag && bt_ag->hf_indicator_value) + { + bt_ag->hf_indicator_value(ag, indicator, value); + } +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ + + return 0; +} + +static struct bt_hfp_ag_at_cmd_handler cmd_handlers[] = +{ + { + "AT+BRSF", bt_hfp_ag_brsf_handler + }, + + { + "AT+BAC", bt_hfp_ag_bac_handler + }, + + { + "AT+CIND", bt_hfp_ag_cind_handler + }, + + { + "AT+CMER", bt_hfp_ag_cmer_handler + }, + + { + "AT+CHLD", bt_hfp_ag_chld_handler + }, + + { + "AT+BIND", bt_hfp_ag_bind_handler + }, + + { + "AT+CMEE", bt_hfp_ag_cmee_handler + }, + + { + "AT+CHUP", bt_hfp_ag_chup_handler + }, + + { + "AT+CLCC", bt_hfp_ag_clcc_handler + }, + + { + "AT+BIA", bt_hfp_ag_bia_handler + }, + + { + "ATA", bt_hfp_ag_ata_handler + }, + + { + "AT+COPS", bt_hfp_ag_cops_handler + }, + + { + "AT+BCC", bt_hfp_ag_bcc_handler + }, + + { + "AT+BCS", bt_hfp_ag_bcs_handler + }, + + { + "ATD", bt_hfp_ag_atd_handler + }, + + { + "AT+BLDN", bt_hfp_ag_bldn_handler + }, + + { + "AT+CLIP", bt_hfp_ag_clip_handler + }, + + { + "AT+VGM", bt_hfp_ag_vgm_handler + }, + + { + "AT+VGS", bt_hfp_ag_vgs_handler + }, + + { + "AT+NREC", bt_hfp_ag_nrec_handler + }, + + { + "AT+BTRH", bt_hfp_ag_btrh_handler + }, + + { + "AT+CCWA", bt_hfp_ag_ccwa_handler + }, + + { + "AT+BVRA", bt_hfp_ag_bvra_handler + }, + + { + "AT+BINP", bt_hfp_ag_binp_handler + }, + + { + "AT+VTS", bt_hfp_ag_vts_handler + }, + + { + "AT+CNUM", bt_hfp_ag_cnum_handler + }, + + { + "AT+BIEV", + bt_hfp_ag_biev_handler + }, +}; + +static void hfp_ag_connected(struct bt_rfcomm_dlc *dlc) +{ + struct bt_hfp_ag *ag = CONTAINER_OF(dlc, struct bt_hfp_ag, rfcomm_dlc); + + bt_hfp_ag_set_state(ag, BT_HFP_CONFIG); + + LOG_DBG("AG %p", ag); +} + +static struct bt_ag_tx *ag_get_tx(struct bt_hfp_ag *ag, + struct list_node *list) +{ + struct list_node *node; + + hfp_ag_lock(ag); + node = list_remove_head(list); + hfp_ag_unlock(ag); + if (node == NULL) + { + return NULL; + } + + return CONTAINER_OF(node, struct bt_ag_tx, node); +} + +static void hfp_ag_disconnected(struct bt_rfcomm_dlc *dlc) +{ + struct bt_hfp_ag *ag = CONTAINER_OF(dlc, struct bt_hfp_ag, rfcomm_dlc); + struct bt_ag_tx *tx; + struct bt_hfp_ag_call *call; + + k_work_cancel_delayable(&ag->tx_work); + k_work_cancel_delayable(&ag->ongoing_call_work); + k_work_cancel(&ag->slc_work); + + tx = ag_get_tx(ag, &ag->tx_pending); + while (tx != NULL) + { + if ((tx->buf != NULL) && !atomic_test_and_clear_bit(ag->flags, + BT_HFP_AG_TX_ONGOING)) + { + net_buf_unref(tx->buf); + } + + tx->err = -ESHUTDOWN; + bt_ag_tx_notify(tx); + tx = ag_get_tx(ag, &ag->tx_pending); + } + + tx = ag_get_tx(ag, &ag->tx_submit_pending); + while (tx != NULL) + { + if (tx->buf != NULL) + { + net_buf_unref(tx->buf); + } + + tx->err = -ESHUTDOWN; + bt_ag_tx_notify(tx); + tx = ag_get_tx(ag, &ag->tx_submit_pending); + } + + bt_hfp_ag_set_state(ag, BT_HFP_DISCONNECTED); + + for (size_t index = 0; index < ARRAY_SIZE(ag->calls); index++) + { + call = &ag->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) + { + continue; + } + + release_call(call); + } + + hfp_ag_close_sco(ag); + + ag->acl_conn = NULL; + + LOG_DBG("AG %p", ag); +} + +static void hfp_ag_preprocess_at_cmd(struct bt_hfp_ag *ag) +{ + atomic_set_bit(ag->flags, BT_HFP_AG_AT_PROCESS); +} + +static void hfp_ag_postprocess_at_cmd(struct bt_hfp_ag *ag) +{ + struct list_node *node; + + if (!atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_AT_PROCESS)) + { + LOG_WRN("No AT CMD is processing"); + } + + hfp_ag_lock(ag); + node = list_remove_head(&ag->tx_submit_pending); + while (node != NULL) + { + list_add_tail(&ag->tx_pending, node); + node = list_remove_head(&ag->tx_submit_pending); + } + + hfp_ag_unlock(ag); + + /* Always active tx work */ + + bt_work_reschedule(&ag->tx_work, 0); +} + +static int hfp_ag_at_cmd_ack(struct bt_hfp_ag *ag, int err) +{ + enum at_cme cme_err; + + if ((err != 0) && atomic_test_bit(ag->flags, BT_HFP_AG_CMEE_ENABLE)) + { + cme_err = bt_hfp_ag_get_cme_err(err); + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+CME ERROR:%d\r\n", + (uint32_t)cme_err); + } + else + { + bt_hfp_ag_tx_cb_t cb; + + cb = atomic_test_and_clear_bit(ag->flags, + BT_HFP_AG_SLC_CONNECTED) ? + bt_hfp_ag_slc_connected + : NULL; + err = hfp_ag_send_data(ag, cb, NULL, "\r\n%s\r\n", + (err == 0) ? "OK" : "ERROR"); + } + + return err; +} + +static int hfp_ag_recv(struct bt_rfcomm_dlc *dlc, struct net_buf *buf) +{ + struct bt_hfp_ag *ag = CONTAINER_OF(dlc, struct bt_hfp_ag, rfcomm_dlc); + uint8_t *data = buf->data; + uint16_t len = buf->len; + int err = -ENOEXEC; + + LOG_HEXDUMP_DBG(data, len, "Received:"); + + hfp_ag_preprocess_at_cmd(ag); + + for (uint32_t index = 0; index < ARRAY_SIZE(cmd_handlers); index++) + { + if (strlen(cmd_handlers[index].cmd) > len) + { + continue; + } + + if (memcmp(data, cmd_handlers[index].cmd, + strlen(cmd_handlers[index].cmd)) != 0) + { + continue; + } + + if (NULL != cmd_handlers[index].handler) + { + (void)net_buf_pull(buf, strlen(cmd_handlers[index].cmd)); + err = cmd_handlers[index].handler(ag, buf); + LOG_DBG("AT commander is handled (err %d)", err); + break; + } + } + + if (err == -EINPROGRESS) + { + LOG_DBG("OK code will be replied later"); + return 0; + } + + if (!atomic_test_and_set_bit(ag->flags, BT_HFP_AG_1ST_AT_RECV)) + { + LOG_DBG("First AT command ack will be replied later"); + ag->ack_err = err; + bt_work_submit(&ag->slc_work); + return 0; + } + + err = hfp_ag_at_cmd_ack(ag, err); + + hfp_ag_postprocess_at_cmd(ag); + + if (err != 0) + { + LOG_ERR("HFP AG send response err :(%d)", err); + } + + return 0; +} + +static void hfp_ag_sent(struct bt_rfcomm_dlc *dlc, int err) +{ + struct bt_hfp_ag *ag = CONTAINER_OF(dlc, struct bt_hfp_ag, rfcomm_dlc); + struct list_node *node; + struct bt_ag_tx *tx; + + hfp_ag_lock(ag); + + /* Clear the tx ongoing flag */ + + if (!atomic_test_and_clear_bit(ag->flags, BT_HFP_AG_TX_ONGOING)) + { + LOG_WRN("tx ongoing flag is not set"); + hfp_ag_unlock(ag); + return; + } + + node = list_remove_head(&ag->tx_pending); + hfp_ag_unlock(ag); + if (!node) + { + LOG_ERR("No pending tx"); + return; + } + + tx = CONTAINER_OF(node, struct bt_ag_tx, node); + LOG_DBG("Completed pending tx %p", tx); + + /* Restart the tx work */ + + bt_work_reschedule(&ag->tx_work, 0); + + tx->err = err; + bt_ag_tx_notify(tx); +} + +static const char * +bt_ag_get_call_state_string(bt_hfp_call_state_t call_state) +{ + const char *s; + + switch (call_state) + { + case BT_HFP_CALL_TERMINATE: + s = "terminate"; + break; + case BT_HFP_CALL_ACTIVE: + s = "active"; + break; + case BT_HFP_CALL_HOLD: + s = "hold"; + break; + case BT_HFP_CALL_OUTGOING: + s = "outgoing"; + break; + case BT_HFP_CALL_INCOMING: + s = "incoming"; + break; + case BT_HFP_CALL_ALERTING: + s = "alerting"; + break; + default: + s = "unknown"; + break; + } + + return s; +} + +static void bt_ag_deferred_work_cb(struct bt_hfp_ag *ag, void *user_data) +{ + int err; + bt_hfp_call_state_t call_state; + uint8_t call_setup_ind; + uint8_t call_ind; + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + LOG_DBG(""); + + hfp_ag_lock(ag); + call_state = call->call_state; + call_setup_ind = ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND]; + call_ind = ag->indicator_value[BT_HFP_AG_CALL_IND]; + hfp_ag_unlock(ag); + + switch (call_state) + { + case BT_HFP_CALL_TERMINATE: + break; + case BT_HFP_CALL_ACTIVE: + break; + case BT_HFP_CALL_HOLD: + break; + case BT_HFP_CALL_OUTGOING: + case BT_HFP_CALL_INCOMING: + case BT_HFP_CALL_ALERTING: + default: + LOG_WRN("Call timeout, status %s", + bt_ag_get_call_state_string(call_state)); + + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_OUTGOING_3WAY) || + atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY)) + { + if (!call_setup_ind) + { + return; + } + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, + BT_HFP_CALL_SETUP_NONE, + bt_hfp_ag_reject_cb, call); + if (err) + { + LOG_ERR("Fail to send indicator"); + bt_hfp_ag_terminate_cb(ag, call); + } + + return; + } + + if (call_setup_ind && call_ind) + { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, + BT_HFP_CALL_SETUP_NONE, NULL, + NULL); + if (err) + { + LOG_ERR("Fail to send indicator"); + bt_hfp_ag_terminate_cb(ag, call); + } + else + { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, + bt_hfp_ag_terminate_cb, + call); + if (err) + { + LOG_ERR("Fail to send indicator"); + bt_hfp_ag_terminate_cb(ag, call); + } + } + } + else if (call_setup_ind) + { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, + BT_HFP_CALL_SETUP_NONE, + bt_hfp_ag_reject_cb, call); + if (err) + { + LOG_ERR("Fail to send indicator"); + bt_hfp_ag_terminate_cb(ag, call); + } + } + else if (call_ind) + { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 0, + bt_hfp_ag_terminate_cb, call); + if (err) + { + LOG_ERR("Fail to send indicator"); + bt_hfp_ag_terminate_cb(ag, call); + } + } + break; + } +} + +static void bt_ag_deferred_work(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_hfp_ag_call *call = CONTAINER_OF(dwork, struct bt_hfp_ag_call, + deferred_work); + struct bt_hfp_ag *ag = call->ag; + + LOG_DBG(""); + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) + { + return; + } + + (void)hfp_ag_next_step(ag, bt_ag_deferred_work_cb, call); +} + +static void bt_ag_ringing_work_cb(struct bt_hfp_ag *ag, void *user_data) +{ + int err; + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + LOG_DBG(""); + + if (call->call_state == BT_HFP_CALL_ALERTING) + { + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) + { + return; + } + + bt_work_reschedule(&call->ringing_work, + SEC2TICK(CONFIG_BT_HFP_AG_RING_NOTIFY_INTERVAL)); + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\nRING\r\n"); + if (err) + { + LOG_ERR("Fail to send RING %d", err); + } + else + { + if (atomic_test_bit(ag->flags, BT_HFP_AG_CLIP_ENABLE)) + { + err = hfp_ag_send_data(ag, NULL, NULL, + "\r\n+CLIP:\"%s\",%d\r\n", + call->number, 0); + if (err) + { + LOG_ERR("Fail to send CLIP %d", err); + } + } + } + } +} + +static void bt_ag_ringing_work(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_hfp_ag_call *call = CONTAINER_OF(dwork, struct bt_hfp_ag_call, + ringing_work); + + LOG_DBG(""); + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_IN_USING)) + { + return; + } + + (void)hfp_ag_next_step(call->ag, bt_ag_ringing_work_cb, call); +} + +static void bt_ag_send_ok_code(struct bt_hfp_ag *ag) +{ + if (hfp_ag_send_data(ag, NULL, NULL, "\r\nOK\r\n") != 0) + { + LOG_ERR("Failed to send OK code"); + } + + hfp_ag_postprocess_at_cmd(ag); +} + +static void bt_ag_ongoing_call_work(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_hfp_ag *ag = CONTAINER_OF(dwork, struct bt_hfp_ag, + ongoing_call_work); + + LOG_DBG(""); + + if (!atomic_test_and_clear_bit(ag->flags, BT_HGP_AG_ONGOING_CALLS)) + { + return; + } + + ag->ongoing_call_count = 0; + bt_hfp_ag_notify_cind_value(ag); + bt_ag_send_ok_code(ag); +} + +static void bt_ag_slc_work(struct k_work *work) +{ + struct bt_hfp_ag *ag = CONTAINER_OF(work, struct bt_hfp_ag, slc_work); + int err; + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_DISCOVER_DONE)) + { + return; + } + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_1ST_AT_RECV)) + { + return; + } + + if (atomic_test_and_set_bit(ag->flags, BT_HFP_AG_FEAT_UPDATED)) + { + return; + } + + if (atomic_test_bit(ag->flags, BT_HFP_AG_RECORD_FOUND)) + { + err = hfp_ag_at_cmd_ack(ag, ag->ack_err); + hfp_ag_postprocess_at_cmd(ag); + if (err != 0) + { + LOG_ERR("Failed to send AT command ACK: %d", err); + } + + return; + } + + err = bt_hfp_ag_disconnect(ag); + if (err != 0) + { + LOG_ERR("Failed to disconnect HF: %d", err); + } +} + +#define HFP_SDP_FEAT_MASK GENMASK(4, 0) + +static uint8_t bt_hfp_ag_discover_cb( + struct bt_conn *conn, struct bt_sdp_client_result *result, + const struct bt_sdp_discover_params *params) +{ + size_t index; + struct bt_hfp_ag *ag; + int err; + + index = (size_t)bt_conn_index(conn); + DEBUGASSERT(index < ARRAY_SIZE(bt_hfp_ag_pool)); + + ag = &bt_hfp_ag_pool[index]; + + if (ag->acl_conn == NULL) + { + /* The AG object was released by hfp_ag_disconnected() while + * the discovery was still ongoing. Do not touch the object state or + * re-submit any work in that case. + */ + + LOG_WRN("AG %p released before discovery completed", ag); + return BT_SDP_DISCOVER_UUID_STOP; + } + + if ((result == NULL) || (result->resp_buf == NULL)) + { + LOG_ERR("SDP discovery failed"); + goto failed; + } + + err = + bt_sdp_get_profile_version(result->resp_buf, BT_SDP_HANDSFREE_SVCLASS, + &ag->hf_sdp_version); + if (err != 0) + { + LOG_ERR("Failed to get HF profile version"); + goto failed; + } + + err = bt_sdp_get_features(result->resp_buf, &ag->hf_sdp_features); + if (err != 0) + { + LOG_ERR("Failed to get HF feature"); + goto failed; + } + + if ((ag->hf_sdp_version <= BT_HFP_VERSION_1_5) || + (BT_HFP_AG_VERSION <= BT_HFP_VERSION_1_5)) + { + if (ag->hf_sdp_features & BT_HFP_HF_SDP_FEATURE_WBS) + { + LOG_WRN("Unsupported SDP feature (WBS) is enabled."); + ag->hf_sdp_features &= ~BT_HFP_HF_SDP_FEATURE_WBS; + } + + if (ag->hf_sdp_features & BT_HFP_HF_SDP_FEATURE_SUPER_WBS) + { + LOG_WRN("Unsupported SDP feature (Super WBS) is enabled."); + ag->hf_sdp_features &= ~BT_HFP_HF_SDP_FEATURE_SUPER_WBS; + } + } + + if ((ag->hf_sdp_version <= BT_HFP_VERSION_0_96) || + (BT_HFP_AG_VERSION <= BT_HFP_VERSION_0_96)) + { + /* Update the AG features according to the SDP features for HFP version + * 0.96. + * + * Hands-Free Profile Specification V1.9, 6.3 SDP Interoperability + * Requirements The values of the ā€œSupportedFeaturesā€ bitmap given + * in + * Table 6.6 shall be the same as the values of the Bits 0 to 4 of the + * unsolicited result code + * +BRSF. + */ + + ag->hf_features = ag->hf_sdp_features & HFP_SDP_FEAT_MASK; + } + + atomic_set_bit(ag->flags, BT_HFP_AG_RECORD_FOUND); +failed: + atomic_set_bit(ag->flags, BT_HFP_AG_DISCOVER_DONE); + if (atomic_test_bit(ag->flags, BT_HFP_AG_RELEASING)) + { + /* The RFCOMM connection creation failed and the object was + * kept allocated only to keep the SDP discovery request valid. Release + * the object now that the discovery has completed. + */ + + ag->acl_conn = NULL; + return BT_SDP_DISCOVER_UUID_STOP; + } + + bt_work_submit(&ag->slc_work); + + return BT_SDP_DISCOVER_UUID_STOP; +} + +static struct bt_hfp_ag *hfp_ag_create(struct bt_conn *conn) +{ + size_t index; + struct bt_hfp_ag *ag; + int err; + + static struct bt_rfcomm_dlc_ops ops = + { + .connected = hfp_ag_connected, + .disconnected = hfp_ag_disconnected, + .recv = hfp_ag_recv, + .sent = hfp_ag_sent, + }; + + static struct bt_sdp_attribute_id_range id_range[] = + { + { + BT_SDP_ATTR_PROTO_DESC_LIST, + BT_SDP_ATTR_PROTO_DESC_LIST + }, + { + BT_SDP_ATTR_PROFILE_DESC_LIST, + BT_SDP_ATTR_PROFILE_DESC_LIST + }, + { + BT_SDP_ATTR_SUPPORTED_FEATURES, + BT_SDP_ATTR_SUPPORTED_FEATURES + }, + }; + + static struct bt_sdp_attribute_id_list id_list = + { + .count = ARRAY_SIZE(id_range), + .ranges = id_range, + }; + + static struct bt_uuid_16 uuid; + + LOG_DBG("conn %p", conn); + + index = (size_t)bt_conn_index(conn); + DEBUGASSERT(index < ARRAY_SIZE(bt_hfp_ag_pool)); + + ag = &bt_hfp_ag_pool[index]; + if (ag->acl_conn) + { + LOG_ERR("AG connection (%p) is established", conn); + return NULL; + } + + (void)memset(ag, 0, sizeof(struct bt_hfp_ag)); + + uuid.uuid.type = BT_UUID_TYPE_16; + uuid.val = BT_SDP_HANDSFREE_SVCLASS; + + ag->sdp_param.func = bt_hfp_ag_discover_cb; + ag->sdp_param.type = BT_SDP_DISCOVER_SERVICE_SEARCH_ATTR; + ag->sdp_param.uuid = &uuid.uuid; + ag->sdp_param.pool = &ag_pool; + ag->sdp_param.ids = &id_list; + + ag->acl_conn = conn; + + err = bt_sdp_discover(conn, &ag->sdp_param); + if (err != 0) + { + ag->acl_conn = NULL; + return NULL; + } + + list_initialize(&ag->tx_pending); + list_initialize(&ag->tx_submit_pending); + + nxsem_init(&ag->lock, 0, 1); + + ag->rfcomm_dlc.ops = &ops; + ag->rfcomm_dlc.mtu = BT_HFP_MAX_MTU; + + /* Set the supported features */ + + ag->ag_features = BT_HFP_AG_SUPPORTED_FEATURES; + ag->ag_features |= + BT_FEAT_SC(bt_dev.features) ? BT_HFP_AG_FEATURE_ESCO_S4 : 0; + + /* Set the default HF infrmation */ + + ag->hf_sdp_features = 0; + ag->hf_sdp_version = BT_HFP_VERSION_0_96; + + /* Support HF indicators */ + + if (IS_ENABLED(CONFIG_BT_HFP_AG_HF_INDICATOR_ENH_SAFETY)) + { + ag->hf_indicators_of_ag |= BIT(HFP_HF_ENHANCED_SAFETY_IND); + } + + if (IS_ENABLED(CONFIG_BT_HFP_AG_HF_INDICATOR_BATTERY)) + { + ag->hf_indicators_of_ag |= BIT(HFP_HF_BATTERY_LEVEL_IND); + } + + ag->hf_indicators = ag->hf_indicators_of_ag; + + /* If supported codec ids cannot be notified, disable codec negotiation. */ + + if (!(bt_ag && bt_ag->codec)) + { + ag->ag_features &= ~BT_HFP_AG_FEATURE_CODEC_NEG; + } + + ag->hf_features = 0; + ag->hf_codec_ids = 0; + + /* Set AG indicator value */ + + ag->indicator_value[BT_HFP_AG_SERVICE_IND] = 0; + ag->indicator_value[BT_HFP_AG_CALL_IND] = 0; + ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND] = 0; + ag->indicator_value[BT_HFP_AG_CALL_HELD_IND] = 0; + ag->indicator_value[BT_HFP_AG_SIGNAL_IND] = 0; + ag->indicator_value[BT_HFP_AG_ROAM_IND] = 0; + ag->indicator_value[BT_HFP_AG_BATTERY_IND] = 0; + + /* Set AG indicator status */ + + ag->indicator = BIT(BT_HFP_AG_SERVICE_IND) | BIT(BT_HFP_AG_CALL_IND) | + BIT(BT_HFP_AG_CALL_SETUP_IND) | + BIT(BT_HFP_AG_CALL_HELD_IND) | + BIT(BT_HFP_AG_SIGNAL_IND) | BIT(BT_HFP_AG_ROAM_IND) | + BIT(BT_HFP_AG_BATTERY_IND); + + /* Set AG operator */ + + memcpy(ag->operator, "UNKNOWN", sizeof("UNKNOWN")); + + /* Set Codec ID */ + + ag->selected_codec_id = BT_HFP_AG_CODEC_CVSD; + + k_work_init(&ag->slc_work, bt_ag_slc_work); + + /* Init delay work */ + + k_work_init_delayable(&ag->tx_work, bt_ag_tx_work); + + /* Init delay work */ + + k_work_init_delayable(&ag->ongoing_call_work, bt_ag_ongoing_call_work); + + return ag; +} + +int bt_hfp_ag_connect(struct bt_conn *conn, + struct bt_hfp_ag **ag, uint8_t channel) +{ + struct bt_hfp_ag *new_ag; + int err; + + LOG_DBG(""); + + if (!conn || !ag || !channel) + { + return -EINVAL; + } + + if (!bt_ag) + { + return -EFAULT; + } + + new_ag = hfp_ag_create(conn); + if (!new_ag) + { + return -ECONNREFUSED; + } + + err = bt_rfcomm_dlc_connect(conn, &new_ag->rfcomm_dlc, channel); + if (err != 0) + { + /* The SDP discovery request started by hfp_ag_create() is + * linked in the SDP client's request list, so the object cannot be + * wiped here. Mark the object for release and let the SDP discovery + * callback release it. If the discovery has already completed, release + * the object immediately. + */ + + atomic_set_bit(new_ag->flags, BT_HFP_AG_RELEASING); + if (atomic_test_bit(new_ag->flags, BT_HFP_AG_DISCOVER_DONE)) + { + k_work_cancel(&new_ag->slc_work); + new_ag->acl_conn = NULL; + } + + *ag = NULL; + } + else + { + *ag = new_ag; + bt_hfp_ag_set_state(*ag, BT_HFP_CONNECTING); + } + + return err; +} + +int bt_hfp_ag_disconnect(struct bt_hfp_ag *ag) +{ + LOG_DBG(""); + + if (ag == NULL) + { + return -EINVAL; + } + + bt_hfp_ag_set_state(ag, BT_HFP_DISCONNECTING); + + return bt_rfcomm_dlc_disconnect(&ag->rfcomm_dlc); +} + +static int hfp_ag_accept( + struct bt_conn *conn, struct bt_rfcomm_server *server, + struct bt_rfcomm_dlc **dlc) +{ + struct bt_hfp_ag *ag; + + ag = hfp_ag_create(conn); + + if (!ag) + { + return -ECONNREFUSED; + } + + *dlc = &ag->rfcomm_dlc; + + return 0; +} + +static int bt_hfp_ag_sco_accept(const struct bt_sco_accept_info *info, + struct bt_sco_chan **chan) +{ + static const struct bt_sco_chan_ops ops = + { + .connected = hfp_ag_sco_connected, + .disconnected = hfp_ag_sco_disconnected, + }; + + size_t index; + struct bt_hfp_ag *ag; + + LOG_DBG("conn %p", info->acl); + + index = (size_t)bt_conn_index(info->acl); + DEBUGASSERT(index < ARRAY_SIZE(bt_hfp_ag_pool)); + + ag = &bt_hfp_ag_pool[index]; + if (ag->acl_conn != info->acl) + { + LOG_ERR("ACL %p of AG is unaligned with SCO's %p", ag->acl_conn, + info->acl); + return -EINVAL; + } + + if (ag->sco_chan.sco) + { + return -ECONNREFUSED; + } + + ag->sco_chan.ops = &ops; + + *chan = &ag->sco_chan; + + return 0; +} + +static void ag_sco_connected(struct bt_conn *conn, uint8_t err) +{ + ARG_UNUSED(conn); + ARG_UNUSED(err); +} + +static void ag_sco_disconnected(struct bt_conn *conn, uint8_t reason) +{ + ARG_UNUSED(reason); + + DEBUGASSERT(conn != NULL); + + ARRAY_FOR_EACH(bt_hfp_ag_pool, i) + { + if (atomic_ptr_cmpxchg(&bt_hfp_ag_pool[i].sco_conn, conn, NULL)) + { + bt_conn_unref(conn); + } + } +} + +static struct bt_sco_conn_cb ag_sco_conn_cb = +{ + .connected = ag_sco_connected, + .disconnected = + ag_sco_disconnected +}; + +static void hfp_ag_init(void) +{ + static struct bt_rfcomm_server chan = + { + .channel = BT_RFCOMM_CHAN_HFP_AG, + .accept = hfp_ag_accept, + }; + + bt_rfcomm_server_register(&chan); + + static struct bt_sco_server sco_server = + { + .sec_level = BT_SECURITY_L0, + .accept = bt_hfp_ag_sco_accept, + }; + + bt_sco_server_register(&sco_server); + + bt_sdp_register_service(&hfp_ag_rec); + + bt_sco_conn_cb_register(&ag_sco_conn_cb); + + k_fifo_init(&ag_tx_free); + k_fifo_init(&ag_tx_notify); + + ARRAY_FOR_EACH(ag_tx, index) + { + k_fifo_put(&ag_tx_free, &ag_tx[index]); + } +} + +int bt_hfp_ag_register(struct bt_hfp_ag_cb *cb) +{ + if (!cb) + { + return -EINVAL; + } + + if (bt_ag) + { + return -EALREADY; + } + + bt_ag = cb; + + hfp_ag_init(); + + return 0; +} + +static void bt_hfp_ag_incoming_cb(struct bt_hfp_ag *ag, void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + bool in_bond = false; + + DEBUGASSERT(call); + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_INCOMING); + + if (bt_ag && bt_ag->incoming) + { + bt_ag->incoming(ag, call, call->number); + } + + if (atomic_test_bit(ag->flags, BT_HFP_AG_INBAND_RING)) + { + int err; + + err = bt_hfp_ag_create_audio_connection(ag, call); + if (err != 0) + { + LOG_ERR("Failed to create audio conn: %d", err); + } + else + { + in_bond = true; + } + } + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ALERTING); + bt_hfp_ag_call_ringing_cb(call, in_bond); +} + +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) +static void bt_hfp_ag_2nd_incoming_cb(struct bt_hfp_ag *ag, void *user_data) +{ + struct bt_hfp_ag_call *call; + + call = (struct bt_hfp_ag_call *)user_data; + + DEBUGASSERT(call); + + if (bt_ag && bt_ag->incoming) + { + bt_ag->incoming(ag, call, call->number); + } + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ALERTING); +} + +static void bt_hfp_ag_ccwa_cb(struct bt_hfp_ag *ag, void *user_data) +{ + int err; + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, + BT_HFP_CALL_SETUP_INCOMING, + bt_hfp_ag_2nd_incoming_cb, user_data); + if (err) + { + LOG_ERR("Fail to send call setup"); + } +} +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + +int bt_hfp_ag_remote_incoming(struct bt_hfp_ag *ag, const char *number) +{ + int err = 0; + size_t len; + struct bt_hfp_ag_call *call; + int call_count; + + LOG_DBG(""); + + if (ag == NULL) + { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + hfp_ag_unlock(ag); + + len = strlen(number); + if ((len == 0) || (len > CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN)) + { + return -EINVAL; + } + + call = get_call_from_number(ag, number, 0); + if (call) + { + return -EBUSY; + } + + call_count = get_none_released_calls(ag); + if (call_count) + { +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) + if (!BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_3WAY_CALL, + BT_HFP_AG_FEATURE_3WAY_CALL)) + { + LOG_WRN("3 Way call feature is not supported on both sides"); + return -ENOEXEC; + } + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_CCWA_ENABLE)) + { + LOG_WRN("Call waiting notification is not enabled"); + return -ENOTSUP; + } + + if (!get_active_held_calls(ag)) + { + LOG_WRN("The first call is not accepted"); + return -EBUSY; + } +#else + return -EBUSY; +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + } + + call = get_new_call(ag, number, 0); + if (!call) + { + LOG_WRN("Cannot allocate a new call object"); + return -ENOMEM; + } + + atomic_set_bit(call->flags, BT_HFP_AG_CALL_INCOMING); + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_INCOMING); + +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) + if (call_count) + { + atomic_set_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY); + err = hfp_ag_send_data(ag, bt_hfp_ag_ccwa_cb, call, + "\r\n+CCWA:\"%s\",%d\r\n", call->number, + call->type); + if (err) + { + LOG_WRN("Fail to send call waiting notification"); + free_call(call); + } + + return err; + } +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, + BT_HFP_CALL_SETUP_INCOMING, + bt_hfp_ag_incoming_cb, call); + if (err) + { + free_call(call); + } + + return err; +} + +int bt_hfp_ag_hold_incoming(struct bt_hfp_ag_call *call) +{ + int err = 0; + struct bt_hfp_ag *ag; + bt_hfp_call_state_t call_state; + + LOG_DBG(""); + + if (call == NULL) + { + return -EINVAL; + } + + ag = call->ag; + + if (ag == NULL) + { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + call_state = call->call_state; + hfp_ag_unlock(ag); + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) + { + return -EINVAL; + } + + if ((call_state == BT_HFP_CALL_ALERTING) || + (call_state == BT_HFP_CALL_INCOMING)) + { + atomic_set_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD); + err = hfp_ag_send_data(ag, btrh_held_cb, call, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_ON_HOLD); + if (err) + { + LOG_ERR("Fail to send err :(%d)", err); + } + + return err; + } + + return -EINVAL; +} + +int bt_hfp_ag_reject(struct bt_hfp_ag_call *call) +{ + int err = 0; + struct bt_hfp_ag *ag; + bt_hfp_call_state_t call_state; + + LOG_DBG(""); + + if (call == NULL) + { + return -EINVAL; + } + + ag = call->ag; + + if (ag == NULL) + { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + call_state = call->call_state; + hfp_ag_unlock(ag); + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) + { + return -EINVAL; + } + + if (!AG_SUPT_FEAT(ag, BT_HFP_AG_FEATURE_REJECT_CALL)) + { + LOG_ERR("AG has not ability to reject call"); + return -ENOTSUP; + } + + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY)) + { + if ((call_state == BT_HFP_CALL_ALERTING) || + (call_state == BT_HFP_CALL_INCOMING)) + { + uint8_t call_setup; + + hfp_ag_lock(ag); + call_setup = ag->indicator_value[BT_HFP_AG_CALL_SETUP_IND]; + hfp_ag_unlock(ag); + + if (call_setup) + { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, + BT_HFP_CALL_SETUP_NONE, + NULL, NULL); + if (err) + { + return err; + } + } + + ag_reject_call(call); + + return 0; + } + } + + if ((call_state == BT_HFP_CALL_ALERTING) || + (call_state == BT_HFP_CALL_INCOMING)) + { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, + BT_HFP_CALL_SETUP_NONE, + bt_hfp_ag_reject_cb, call); + + return err; + } + + if ((call_state == BT_HFP_CALL_ACTIVE) && + atomic_test_and_clear_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD)) + { + err = hfp_ag_send_data(ag, btrh_reject_cb, call, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_REJECTED); + if (err) + { + LOG_ERR("Fail to send err :(%d)", err); + } + + return err; + } + + return -EINVAL; +} + +int bt_hfp_ag_accept(struct bt_hfp_ag_call *call) +{ + int err = 0; + struct bt_hfp_ag *ag; + bt_hfp_call_state_t call_state; + + LOG_DBG(""); + + if (call == NULL) + { + return -EINVAL; + } + + ag = call->ag; + + if (ag == NULL) + { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + call_state = call->call_state; + hfp_ag_unlock(ag); + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) + { + return -EINVAL; + } + +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_3WAY)) + { + if ((call_state == BT_HFP_CALL_ALERTING) || + (call_state == BT_HFP_CALL_INCOMING)) + { + return chld_deactivate_calls_and_accept_other_call(ag, false); + } + } +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + + if (call_state == BT_HFP_CALL_ALERTING) + { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 1, + bt_hfp_ag_accept_cb, call); + if (err) + { + LOG_ERR("Fail to send err :(%d)", err); + } + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, + BT_HFP_CALL_SETUP_NONE, + bt_hfp_ag_call_setup_none_cb, call); + if (err) + { + LOG_ERR("Fail to send err :(%d)", err); + } + + return err; + } + + if (atomic_test_and_clear_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD) && + (call_state == BT_HFP_CALL_ACTIVE)) + { + err = hfp_ag_send_data(ag, btrh_accept_cb, call, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_ACCEPTED); + if (err) + { + LOG_ERR("Fail to send err :(%d)", err); + } + + return err; + } + + return -EINVAL; +} + +int bt_hfp_ag_terminate(struct bt_hfp_ag_call *call) +{ + int err = 0; + struct bt_hfp_ag *ag; + bt_hfp_call_state_t call_state; + + LOG_DBG(""); + + if (call == NULL) + { + return -EINVAL; + } + + ag = call->ag; + + if (ag == NULL) + { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + call_state = call->call_state; + hfp_ag_unlock(ag); + + if (atomic_test_and_clear_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD) && + atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING) && + (call_state == BT_HFP_CALL_ACTIVE)) + { + err = hfp_ag_send_data(ag, btrh_reject_cb, call, "\r\n+BTRH:%d\r\n", + BT_HFP_BTRH_REJECTED); + if (err) + { + LOG_ERR("Fail to send err :(%d)", err); + } + + return err; + } + + if ((call_state != BT_HFP_CALL_ACTIVE) && (call_state != BT_HFP_CALL_HOLD)) + { + return -EINVAL; + } + + ag_terminate_call(call); + + return 0; +} + +int bt_hfp_ag_retrieve(struct bt_hfp_ag_call *call) +{ + struct bt_hfp_ag *ag; + bt_hfp_call_state_t call_state; + + LOG_DBG(""); + + if (call == NULL) + { + return -EINVAL; + } + + ag = call->ag; + + if (ag == NULL) + { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + call_state = call->call_state; + hfp_ag_unlock(ag); + + if (call_state != BT_HFP_CALL_HOLD) + { + return -EINVAL; + } + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ACTIVE); + + if (bt_ag && bt_ag->retrieve) + { + bt_ag->retrieve(call); + } + + ag_notify_call_held_ind(ag, NULL); + + return 0; +} + +int bt_hfp_ag_hold(struct bt_hfp_ag_call *call) +{ + struct bt_hfp_ag *ag; + bt_hfp_call_state_t call_state; + + LOG_DBG(""); + + if (call == NULL) + { + return -EINVAL; + } + + ag = call->ag; + + if (ag == NULL) + { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + call_state = call->call_state; + hfp_ag_unlock(ag); + + if (call_state != BT_HFP_CALL_ACTIVE) + { + return -EINVAL; + } + + if ((call_state == BT_HFP_CALL_ACTIVE) && + (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING_HELD))) + { + return -EINVAL; + } + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_HOLD); + + if (bt_ag && bt_ag->held) + { + bt_ag->held(call); + } + + ag_notify_call_held_ind(ag, NULL); + + return 0; +} + +int bt_hfp_ag_outgoing(struct bt_hfp_ag *ag, const char *number) +{ + LOG_DBG(""); + + if (ag == NULL) + { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + hfp_ag_unlock(ag); + + return bt_hfp_ag_outgoing_call(ag, number, 0); +} + +static void bt_hfp_ag_ringing_cb(struct bt_hfp_ag *ag, void *user_data) +{ + struct bt_hfp_ag_call *call = (struct bt_hfp_ag_call *)user_data; + + bt_hfp_ag_set_call_state(call, BT_HFP_CALL_ALERTING); + + if (atomic_test_bit(ag->flags, BT_HFP_AG_INBAND_RING)) + { + bt_hfp_ag_call_ringing_cb(call, true); + } + else + { + bt_hfp_ag_call_ringing_cb(call, false); + } +} + +int bt_hfp_ag_remote_ringing(struct bt_hfp_ag_call *call) +{ + int err = 0; + struct bt_hfp_ag *ag; + + LOG_DBG(""); + + if (call == NULL) + { + return -EINVAL; + } + + ag = call->ag; + + if (ag == NULL) + { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + if (call->call_state != BT_HFP_CALL_OUTGOING) + { + hfp_ag_unlock(ag); + return -EBUSY; + } + + hfp_ag_unlock(ag); + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, + BT_HFP_CALL_SETUP_REMOTE_ALERTING, + bt_hfp_ag_ringing_cb, call); + + return err; +} + +int bt_hfp_ag_remote_reject(struct bt_hfp_ag_call *call) +{ + int err; + struct bt_hfp_ag *ag; + + LOG_DBG(""); + + if (call == NULL) + { + return -EINVAL; + } + + ag = call->ag; + + if (ag == NULL) + { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + if ((call->call_state != BT_HFP_CALL_ALERTING) && + (call->call_state != BT_HFP_CALL_OUTGOING)) + { + hfp_ag_unlock(ag); + return -EINVAL; + } + + hfp_ag_unlock(ag); + + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) + { + return -EINVAL; + } + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, + BT_HFP_CALL_SETUP_NONE, + bt_hfp_ag_reject_cb, call); + + return err; +} + +int bt_hfp_ag_remote_accept(struct bt_hfp_ag_call *call) +{ + int err; + struct bt_hfp_ag *ag; + + LOG_DBG(""); + + if (call == NULL) + { + return -EINVAL; + } + + ag = call->ag; + + if (ag == NULL) + { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + if (call->call_state != BT_HFP_CALL_ALERTING) + { + hfp_ag_unlock(ag); + return -EINVAL; + } + + hfp_ag_unlock(ag); + + if (atomic_test_bit(call->flags, BT_HFP_AG_CALL_INCOMING)) + { + return -EINVAL; + } + + if (!atomic_test_bit(call->flags, BT_HFP_AG_CALL_OUTGOING_3WAY)) + { + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_IND, 1, + bt_hfp_ag_accept_cb, call); + if (err) + { + LOG_ERR("Fail to send err :(%d)", err); + } + } + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_CALL_SETUP_IND, + BT_HFP_CALL_SETUP_NONE, + bt_hfp_ag_call_setup_none_cb, call); + if (err) + { + LOG_ERR("Fail to send err :(%d)", err); + } + + return err; +} + +int bt_hfp_ag_remote_terminate(struct bt_hfp_ag_call *call) +{ + struct bt_hfp_ag *ag; + + LOG_DBG(""); + + if (call == NULL) + { + return -EINVAL; + } + + ag = call->ag; + + if (ag == NULL) + { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + if ((call->call_state != BT_HFP_CALL_ACTIVE) && + (call->call_state != BT_HFP_CALL_HOLD)) + { + hfp_ag_unlock(ag); + return -EINVAL; + } + + hfp_ag_unlock(ag); + + ag_terminate_call(call); + + return 0; +} + +int bt_hfp_ag_explicit_call_transfer(struct bt_hfp_ag *ag) +{ +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) + if (ag == NULL) + { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + hfp_ag_unlock(ag); + + return chld_drop_conversation(ag); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ +} + +int bt_hfp_ag_set_indicator(struct bt_hfp_ag *ag, + enum bt_hfp_ag_indicator index, uint8_t value) +{ + int err; + + LOG_DBG(""); + + if (ag == NULL) + { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + hfp_ag_unlock(ag); + + switch (index) + { + case BT_HFP_AG_SERVICE_IND: + case BT_HFP_AG_SIGNAL_IND: + case BT_HFP_AG_ROAM_IND: + case BT_HFP_AG_BATTERY_IND: + if ((ag_ind[(uint8_t)index].min > value) || + (ag_ind[(uint8_t)index].max < value)) + { + return -EINVAL; + } + break; + case BT_HFP_AG_CALL_IND: + case BT_HFP_AG_CALL_SETUP_IND: + case BT_HFP_AG_CALL_HELD_IND: + default: + return -EINVAL; + } + + err = hfp_ag_update_indicator(ag, index, value, NULL, NULL); + + return err; +} + +int bt_hfp_ag_set_operator(struct bt_hfp_ag *ag, uint8_t mode, char *name) +{ + int len; + + LOG_DBG(""); + + if (ag == NULL) + { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + len = strlen(name); + len = MIN(sizeof(ag->operator) - 1, len); + memcpy(ag->operator, name, len); + ag->operator[len] = '\0'; + ag->mode = mode; + hfp_ag_unlock(ag); + + return 0; +} + +int bt_hfp_ag_audio_connect(struct bt_hfp_ag *ag, uint8_t id) +{ + int err; + + LOG_DBG(""); + + if (ag == NULL) + { + return -EINVAL; + } + + if ((BT_HFP_AG_SUPPORTED_CODEC_IDS & BIT(id)) == 0) + { + LOG_ERR("Unsupported Codec ID %u", id); + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + if (ag->hf_codec_ids) + { + if (!(ag->hf_codec_ids & BIT(id))) + { + hfp_ag_unlock(ag); + return -EINVAL; + } + } + else + { + if (id != BT_HFP_AG_CODEC_CVSD) + { + hfp_ag_unlock(ag); + return -ENOTSUP; + } + } + + if (atomic_ptr_read(&ag->sco_conn) != NULL) + { + LOG_ERR("Audio connection has been connected"); + hfp_ag_unlock(ag); + return -ECONNREFUSED; + } + + hfp_ag_unlock(ag); + + if (atomic_test_bit(ag->flags, BT_HFP_AG_CODEC_CONN)) + { + return -EBUSY; + } + + hfp_ag_lock(ag); + if (ag->selected_codec_id != id) + { + atomic_set_bit(ag->flags, BT_HFP_AG_CODEC_CHANGED); + } + + ag->selected_codec_id = id; + hfp_ag_unlock(ag); + + err = bt_hfp_ag_create_audio_connection(ag, NULL); + + return err; +} + +int bt_hfp_ag_vgm(struct bt_hfp_ag *ag, uint8_t vgm) +{ + int err; + + LOG_DBG(""); + + if (ag == NULL) + { + return -EINVAL; + } + + if (vgm > BT_HFP_HF_VGM_GAIN_MAX) + { + LOG_ERR("Invalid VGM (%d>%d)", vgm, BT_HFP_HF_VGM_GAIN_MAX); + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + hfp_ag_unlock(ag); + + if (!HF_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_VOLUME)) + { + LOG_ERR("Remote Audio Volume Control is unsupported"); + return -ENOTSUP; + } + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+VGM=%d\r\n", vgm); + if (err) + { + LOG_ERR("Fail to notify vgm err :(%d)", err); + } + + return err; +} + +int bt_hfp_ag_vgs(struct bt_hfp_ag *ag, uint8_t vgs) +{ + int err; + + LOG_DBG(""); + + if (ag == NULL) + { + return -EINVAL; + } + + if (vgs > BT_HFP_HF_VGS_GAIN_MAX) + { + LOG_ERR("Invalid VGM (%d>%d)", vgs, BT_HFP_HF_VGS_GAIN_MAX); + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + hfp_ag_unlock(ag); + + if (!HF_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_VOLUME)) + { + LOG_ERR("Remote Audio Volume Control is unsupported"); + return -ENOTSUP; + } + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+VGS=%d\r\n", vgs); + if (err) + { + LOG_ERR("Fail to notify vgs err :(%d)", err); + } + + return err; +} + +int bt_hfp_ag_inband_ringtone(struct bt_hfp_ag *ag, bool inband) +{ + int err; + + LOG_DBG(""); + + if (!IS_ENABLED(CONFIG_BT_HFP_AG_INBAND_RINGTONE)) + { + LOG_ERR("In-band ring tone is unsupported!"); + return -ENOTSUP; + } + + if (ag == NULL) + { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + hfp_ag_unlock(ag); + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BSIR: %d\r\n", inband ? 1 : + 0); + if (err) + { + LOG_ERR("Fail to set inband ringtone err :(%d)", err); + return err; + } + + atomic_set_bit_to(ag->flags, BT_HFP_AG_INBAND_RING, inband); + return 0; +} + +int bt_hfp_ag_voice_recognition(struct bt_hfp_ag *ag, bool activate) +{ +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG) + int err; + bool feature; + char *bvra; + + LOG_DBG(""); + + if (ag == NULL) + { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + hfp_ag_unlock(ag); + + feature = BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_VOICE_RECG, + BT_HFP_AG_FEATURE_VOICE_RECG); + if (!feature) + { + LOG_WRN("VR feature is unsupported"); + return -ENOTSUP; + } + + if (activate && atomic_test_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) + { + LOG_WRN("VR has been activated"); + return -ENOTSUP; + } + else if (!activate && !atomic_test_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) + { + LOG_WRN("VR is not activated"); + return -ENOTSUP; + } + + feature = BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_ENH_VOICE_RECG, + BT_HFP_AG_FEATURE_ENH_VOICE_RECG); + if (!feature) + { + bvra = ""; + } + else + { + bvra = ",0"; + } + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BVRA:%d%s\r\n", activate, + bvra); + if (err) + { + LOG_ERR("Fail to notify VR activation :(%d)", err); + return err; + } + + atomic_set_bit_to(ag->flags, BT_HFP_AG_VRE_ACTIVATE, activate); + atomic_clear_bit(ag->flags, BT_HFP_AG_VRE_R2A); + return 0; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG */ +} + +int bt_hfp_ag_vre_state(struct bt_hfp_ag *ag, uint8_t state) +{ +#if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) + int err; + bool feature; + + LOG_DBG(""); + + if (ag == NULL) + { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + hfp_ag_unlock(ag); + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) + { + LOG_WRN("Voice Recognition is not activated"); + return -EINVAL; + } + + feature = BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_ENH_VOICE_RECG, + BT_HFP_AG_FEATURE_ENH_VOICE_RECG); + if (!feature) + { + return -ENOTSUP; + } + + if ((state & BT_HFP_BVRA_STATE_SEND_AUDIO) && !atomic_test_bit(ag->flags, + BT_HFP_AG_VRE_R2A)) + { + LOG_ERR("HFP HF is not ready to accept audio input"); + return -EINVAL; + } + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BVRA:1,%d\r\n", state); + if (err) + { + LOG_ERR("Fail to send state of VRE :(%d)", err); + return err; + } + + return 0; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ +} + +int bt_hfp_ag_vre_textual_representation( + struct bt_hfp_ag *ag, uint8_t state, const char *id, uint8_t type, + uint8_t operation, const char *text) +{ +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG_TEXT) + int err; + bool feature; + + LOG_DBG(""); + + if (ag == NULL) + { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + hfp_ag_unlock(ag); + + if (!atomic_test_bit(ag->flags, BT_HFP_AG_VRE_ACTIVATE)) + { + LOG_WRN("Voice Recognition is not activated"); + return -EINVAL; + } + + feature = BOTH_SUPT_FEAT(ag, BT_HFP_HF_FEATURE_ENH_VOICE_RECG, + BT_HFP_AG_FEATURE_ENH_VOICE_RECG); + if (!feature) + { + return -ENOTSUP; + } + + feature = + BOTH_SUPT_FEAT(ag, + BT_HFP_HF_FEATURE_VOICE_RECG_TEXT, + BT_HFP_AG_FEATURE_VOICE_RECG_TEXT); + if (!feature) + { + return -ENOTSUP; + } + + if ((state & BT_HFP_BVRA_STATE_SEND_AUDIO) && !atomic_test_bit(ag->flags, + BT_HFP_AG_VRE_R2A)) + { + LOG_ERR("HFP HF is not ready to accept audio input"); + return -EINVAL; + } + + err = hfp_ag_send_data(ag, + NULL, + NULL, + "\r\n+BVRA:1,%d,%s,%d,%d,\"%s\"\r\n", + + state, id, type, + operation, text); + if (err) + { + LOG_ERR("Fail to send state of VRE :(%d)", err); + return err; + } + + return 0; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG_TEXT */ +} + +int bt_hfp_ag_signal_strength(struct bt_hfp_ag *ag, uint8_t strength) +{ + int err; + + LOG_DBG(""); + + if (ag == NULL) + { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + hfp_ag_unlock(ag); + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_SIGNAL_IND, strength, NULL, + NULL); + if (err) + { + LOG_ERR("Fail to set signal strength err :(%d)", err); + } + + return err; +} + +int bt_hfp_ag_roaming_status(struct bt_hfp_ag *ag, uint8_t status) +{ + int err; + + LOG_DBG(""); + + if (ag == NULL) + { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + hfp_ag_unlock(ag); + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_ROAM_IND, status, NULL, NULL); + if (err) + { + LOG_ERR("Fail to set roaming status err :(%d)", err); + } + + return err; +} + +int bt_hfp_ag_battery_level(struct bt_hfp_ag *ag, uint8_t level) +{ + int err; + + LOG_DBG(""); + + if (ag == NULL) + { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + hfp_ag_unlock(ag); + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_BATTERY_IND, level, NULL, + NULL); + if (err) + { + LOG_ERR("Fail to set battery level err :(%d)", err); + } + + return err; +} + +int bt_hfp_ag_service_availability(struct bt_hfp_ag *ag, bool available) +{ + int err; + + LOG_DBG(""); + + if (ag == NULL) + { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + hfp_ag_unlock(ag); + + err = hfp_ag_update_indicator(ag, BT_HFP_AG_SERVICE_IND, available ? 1 : 0, + NULL, NULL); + if (err) + { + LOG_ERR("Fail to set service availability err :(%d)", err); + } + + return err; +} + +int bt_hfp_ag_hf_indicator(struct bt_hfp_ag *ag, + enum hfp_ag_hf_indicators indicator, bool enable) +{ +#if defined(CONFIG_BT_HFP_AG_HF_INDICATORS) + int err; + uint32_t supported_indicators; + + LOG_DBG(""); + + if (ag == NULL) + { + return -EINVAL; + } + + hfp_ag_lock(ag); + if (ag->state != BT_HFP_CONNECTED) + { + hfp_ag_unlock(ag); + return -ENOTCONN; + } + + supported_indicators = ag->hf_indicators_of_ag & ag->hf_indicators_of_hf; + hfp_ag_unlock(ag); + + if (!(supported_indicators & BIT(indicator))) + { + LOG_ERR("Unsupported indicator %d", indicator); + return -ENOTSUP; + } + + hfp_ag_lock(ag); + if (enable) + { + ag->hf_indicators |= BIT(indicator); + } + else + { + ag->hf_indicators &= ~BIT(indicator); + } + + hfp_ag_unlock(ag); + + err = hfp_ag_send_data(ag, NULL, NULL, "\r\n+BIND:%d,%d\r\n", indicator, + enable ? 1 : 0); + if (err) + { + LOG_ERR("Fail to update registration status of indicator:(%d)", err); + } + + return err; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ +} + +int bt_hfp_ag_ongoing_calls( + struct bt_hfp_ag *ag, struct bt_hfp_ag_ongoing_call *calls, size_t count) +{ + struct bt_hfp_ag_ongoing_call *call; + bool valid; + size_t len; + int err = -EINVAL; + + LOG_DBG(""); + + if (ag == NULL) + { + LOG_ERR("Invalid AG object"); + return -EINVAL; + } + + if (count > ARRAY_SIZE(ag->ongoing_calls)) + { + LOG_ERR("Out of buffer (%u > %u)", count, + ARRAY_SIZE(ag->ongoing_calls)); + return -ENOMEM; + } + + if (!atomic_test_and_clear_bit(ag->flags, BT_HGP_AG_ONGOING_CALLS)) + { + LOG_ERR("Cannot set ongoing calls"); + return -ESRCH; + } + + for (ag->ongoing_call_count = + 0; ag->ongoing_call_count < count; ag->ongoing_call_count++) + { + call = &calls[ag->ongoing_call_count]; + valid = true; + + len = strlen(call->number); + if ((len == 0) || (len >= ARRAY_SIZE(call->number))) + { + LOG_WRN("Invalid call number"); + + /* Clear all calls */ + + ag->ongoing_call_count = 0; + goto failed; + } + + switch (call->status) + { + case BT_HFP_AG_CALL_STATUS_DIALING: + case BT_HFP_AG_CALL_STATUS_ALERTING: + if (call->dir == BT_HFP_AG_CALL_DIR_INCOMING) + { + LOG_ERR("Dialing call cannot be incoming"); + valid = false; + } + break; + case BT_HFP_AG_CALL_STATUS_INCOMING: + case BT_HFP_AG_CALL_STATUS_WAITING: + case BT_HFP_AG_CALL_STATUS_INCOMING_HELD: + if (call->dir == BT_HFP_AG_CALL_DIR_OUTGOING) + { + LOG_ERR("Incoming call cannot be outgoing"); + valid = false; + } + break; + case BT_HFP_AG_CALL_STATUS_ACTIVE: + case BT_HFP_AG_CALL_STATUS_HELD: + break; + default: + LOG_ERR("Invalid status"); + valid = false; + break; + } + + if (!valid) + { + /* Clear all calls */ + + ag->ongoing_call_count = 0; + goto failed; + } + + memcpy(&ag->ongoing_calls[ag->ongoing_call_count], call, + sizeof(ag->ongoing_calls[ag->ongoing_call_count])); + } + + if (ag->ongoing_call_count > 1) + { + switch (ag->ongoing_calls[0].status) + { + case BT_HFP_AG_CALL_STATUS_ACTIVE: + case BT_HFP_AG_CALL_STATUS_HELD: + case BT_HFP_AG_CALL_STATUS_INCOMING_HELD: + break; + default: + LOG_ERR("Unexpected call status for multiple calls"); + + /* Clear all calls */ + + ag->ongoing_call_count = 0; + goto failed; + } + } + + err = 0; + +failed: + k_work_cancel_delayable(&ag->ongoing_call_work); + bt_hfp_ag_notify_cind_value(ag); + bt_ag_send_ok_code(ag); + return err; +} diff --git a/libs/zblue/host/classic/hfp_ag_internal.h b/libs/zblue/host/classic/hfp_ag_internal.h new file mode 100644 index 0000000000000..b039a8358c991 --- /dev/null +++ b/libs/zblue/host/classic/hfp_ag_internal.h @@ -0,0 +1,368 @@ +/**************************************************************************** + * libs/zblue/host/classic/hfp_ag_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Internal APIs for Bluetooth Handsfree profile handling. + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include "hfp_internal.h" + +#if defined(CONFIG_BT_HFP_AG_EXT_ERR) +#define BT_HFP_AG_FEATURE_EXT_ERR_ENABLE BT_HFP_AG_FEATURE_EXT_ERR +#else +#define BT_HFP_AG_FEATURE_EXT_ERR_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_EXT_ERR */ + +#if defined(CONFIG_BT_HFP_AG_CODEC_NEG) +#define BT_HFP_AG_FEATURE_CODEC_NEG_ENABLE BT_HFP_AG_FEATURE_CODEC_NEG +#else +#define BT_HFP_AG_FEATURE_CODEC_NEG_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_CODEC_NEG */ + +#if defined(CONFIG_BT_HFP_AG_ECNR) +#define BT_HFP_AG_FEATURE_ECNR_ENABLE BT_HFP_AG_FEATURE_ECNR +#define BT_HFP_AG_SDP_FEATURE_ECNR_ENABLE BT_HFP_AG_SDP_FEATURE_ECNR +#else +#define BT_HFP_AG_FEATURE_ECNR_ENABLE 0 +#define BT_HFP_AG_SDP_FEATURE_ECNR_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_CODEC_NEG */ + +#if defined(CONFIG_BT_HFP_AG_ECS) +#define BT_HFP_AG_FEATURE_ECS_ENABLE BT_HFP_AG_FEATURE_ECS +#else +#define BT_HFP_AG_FEATURE_ECS_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_ECS*/ + +#if defined(CONFIG_BT_HFP_AG_3WAY_CALL) +#define BT_HFP_AG_FEATURE_3WAY_CALL_ENABLE BT_HFP_AG_FEATURE_3WAY_CALL +#define BT_HFP_AG_SDP_FEATURE_3WAY_CALL_ENABLE BT_HFP_AG_SDP_FEATURE_3WAY_CALL +#else +#define BT_HFP_AG_FEATURE_3WAY_CALL_ENABLE 0 +#define BT_HFP_AG_SDP_FEATURE_3WAY_CALL_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_3WAY_CALL */ + +#if defined(CONFIG_BT_HFP_AG_ECC) +#define BT_HFP_AG_FEATURE_ECC_ENABLE BT_HFP_AG_FEATURE_ECC +#else +#define BT_HFP_AG_FEATURE_ECC_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_ECC */ + +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG) +#define BT_HFP_AG_FEATURE_VOICE_RECG_ENABLE BT_HFP_AG_FEATURE_VOICE_RECG +#define BT_HFP_AG_SDP_FEATURE_VOICE_RECG_ENABLE BT_HFP_AG_SDP_FEATURE_VOICE_RECG +#else +#define BT_HFP_AG_FEATURE_VOICE_RECG_ENABLE 0 +#define BT_HFP_AG_SDP_FEATURE_VOICE_RECG_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG */ + +#if defined(CONFIG_BT_HFP_AG_ENH_VOICE_RECG) +#define BT_HFP_AG_FEATURE_ENH_VOICE_RECG_ENABLE BT_HFP_AG_FEATURE_ENH_VOICE_RECG +#define BT_HFP_AG_SDP_FEATURE_ENH_VOICE_RECG_ENABLE BT_HFP_AG_SDP_FEATURE_ENH_VOICE_RECG +#else +#define BT_HFP_AG_FEATURE_ENH_VOICE_RECG_ENABLE 0 +#define BT_HFP_AG_SDP_FEATURE_ENH_VOICE_RECG_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_ENH_VOICE_RECG */ + +#if defined(CONFIG_BT_HFP_AG_VOICE_RECG_TEXT) +#define BT_HFP_AG_FEATURE_VOICE_RECG_TEXT_ENABLE BT_HFP_AG_FEATURE_VOICE_RECG_TEXT +#define BT_HFP_AG_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE BT_HFP_AG_SDP_FEATURE_VOICE_RECG_TEXT +#else +#define BT_HFP_AG_FEATURE_VOICE_RECG_TEXT_ENABLE 0 +#define BT_HFP_AG_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_VOICE_RECG_TEXT */ + +#if defined(CONFIG_BT_HFP_AG_VOICE_TAG) +#define BT_HFP_AG_FEATURE_VOICE_TAG_ENABLE BT_HFP_AG_FEATURE_VOICE_TAG +#define BT_HFP_AG_SDP_FEATURE_VOICE_TAG_ENABLE BT_HFP_AG_SDP_FEATURE_VOICE_TAG +#else +#define BT_HFP_AG_FEATURE_VOICE_TAG_ENABLE 0 +#define BT_HFP_AG_SDP_FEATURE_VOICE_TAG_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_VOICE_TAG */ + +#if defined(CONFIG_BT_HFP_AG_HF_INDICATORS) +#define BT_HFP_AG_FEATURE_HF_IND_ENABLE BT_HFP_AG_FEATURE_HF_IND +#else +#define BT_HFP_AG_FEATURE_HF_IND_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ + +#if defined(CONFIG_BT_HFP_AG_REJECT_CALL) +#define BT_HFP_AG_FEATURE_REJECT_CALL_ENABLE BT_HFP_AG_FEATURE_REJECT_CALL +#else +#define BT_HFP_AG_FEATURE_REJECT_CALL_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_REJECT_CALL */ + +#if defined(CONFIG_BT_HFP_AG_CODEC_MSBC) +#define BT_HFP_AG_SDP_FEATURE_WBS_ENABLE BT_HFP_AG_SDP_FEATURE_WBS +#else +#define BT_HFP_AG_SDP_FEATURE_WBS_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_CODEC_MSBC */ + +#if defined(CONFIG_BT_HFP_AG_CODEC_LC3_SWB) +#define BT_HFP_AG_SDP_FEATURE_SUPER_WBS_ENABLE BT_HFP_AG_SDP_FEATURE_SUPER_WBS +#else +#define BT_HFP_AG_SDP_FEATURE_SUPER_WBS_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_CODEC_LC3_SWB */ + +#if defined(CONFIG_BT_HFP_AG_INBAND_RINGTONE) +#define BT_HFP_AG_FEATURE_INBAND_RINGTONE_ENABLE BT_HFP_AG_FEATURE_INBAND_RINGTONE +#define BT_HFP_AG_SDP_FEATURE_INBAND_RINGTONE_ENABLE BT_HFP_AG_SDP_FEATURE_INBAND_RINGTONE +#else +#define BT_HFP_AG_FEATURE_INBAND_RINGTONE_ENABLE 0 +#define BT_HFP_AG_SDP_FEATURE_INBAND_RINGTONE_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_INBAND_RINGTONE */ + +/* HFP AG Supported features */ +#define BT_HFP_AG_SUPPORTED_FEATURES \ + (BT_HFP_AG_FEATURE_3WAY_CALL_ENABLE | BT_HFP_AG_FEATURE_INBAND_RINGTONE_ENABLE | \ + BT_HFP_AG_FEATURE_EXT_ERR_ENABLE | BT_HFP_AG_FEATURE_CODEC_NEG_ENABLE | \ + BT_HFP_AG_FEATURE_ECNR_ENABLE | BT_HFP_AG_FEATURE_ECS_ENABLE | BT_HFP_AG_FEATURE_ECC_ENABLE | \ + BT_HFP_AG_FEATURE_VOICE_RECG_ENABLE | BT_HFP_AG_FEATURE_ENH_VOICE_RECG_ENABLE | \ + BT_HFP_AG_FEATURE_VOICE_RECG_TEXT_ENABLE | BT_HFP_AG_FEATURE_VOICE_TAG_ENABLE | \ + BT_HFP_AG_FEATURE_HF_IND_ENABLE | BT_HFP_AG_FEATURE_REJECT_CALL_ENABLE) + +/* HFP AG Supported features in SDP */ +#define BT_HFP_AG_SDP_SUPPORTED_FEATURES \ + (BT_HFP_AG_SDP_FEATURE_3WAY_CALL_ENABLE | BT_HFP_AG_SDP_FEATURE_INBAND_RINGTONE_ENABLE | \ + BT_HFP_AG_SDP_FEATURE_ECNR_ENABLE | BT_HFP_AG_SDP_FEATURE_VOICE_RECG_ENABLE | \ + BT_HFP_AG_SDP_FEATURE_ENH_VOICE_RECG_ENABLE | BT_HFP_AG_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE | \ + BT_HFP_AG_SDP_FEATURE_VOICE_TAG_ENABLE | BT_HFP_AG_SDP_FEATURE_WBS_ENABLE | \ + BT_HFP_AG_SDP_FEATURE_SUPER_WBS_ENABLE) + +#define BT_HFP_AG_CODEC_CVSD_ENABLE BIT(BT_HFP_AG_CODEC_CVSD) + +#if defined(CONFIG_BT_HFP_AG_CODEC_MSBC) +#define BT_HFP_AG_CODEC_MSBC_ENABLE BIT(BT_HFP_AG_CODEC_MSBC) +#else +#define BT_HFP_AG_CODEC_MSBC_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_CODEC_MSBC */ + +#if defined(CONFIG_BT_HFP_AG_CODEC_LC3_SWB) +#define BT_HFP_AG_CODEC_LC3_SWB_ENABLE BIT(BT_HFP_AG_CODEC_LC3_SWB) +#else +#define BT_HFP_AG_CODEC_LC3_SWB_ENABLE 0 +#endif /* CONFIG_BT_HFP_AG_CODEC_LC3_SWB */ + +/* HFP HF Supported Codec IDs */ +#define BT_HFP_AG_SUPPORTED_CODEC_IDS \ + (BT_HFP_AG_CODEC_CVSD_ENABLE | BT_HFP_AG_CODEC_MSBC_ENABLE | BT_HFP_AG_CODEC_LC3_SWB_ENABLE) + +/* bt_hfp_ag flags: the flags defined here represent HFP AG parameters */ + +enum +{ + BT_HFP_AG_CMEE_ENABLE, /* Extended Audio Gateway Error Result Code */ + BT_HFP_AG_CMER_ENABLE, /* Indicator Events Reporting */ + BT_HFP_AG_CLIP_ENABLE, /* Calling Line Identification notification */ + BT_HFP_AG_CCWA_ENABLE, /* Call Waiting notification */ + BT_HFP_AG_INBAND_RING, /* In-band ring */ + BT_HFP_AG_COPS_SET, /* Query Operator selection */ + BT_HFP_AG_AUDIO_CONN, /* Audio connection */ + BT_HFP_AG_CODEC_CONN, /* Codec connection is ongoing */ + BT_HFP_AG_CODEC_CHANGED, /* Codec Id Changed */ + BT_HFP_AG_TX_ONGOING, /* TX is ongoing */ + BT_HFP_AG_CREATING_SCO, /* SCO is creating */ + BT_HFP_AG_VRE_ACTIVATE, /* VRE is activated */ + BT_HFP_AG_VRE_R2A, /* HF is ready to accept audio */ + BT_HGP_AG_ONGOING_CALLS, /* Waiting ongoing calls */ + BT_HFP_AG_SLC_CONNECTED, /* SLC connected event needs to be notified */ + BT_HFP_AG_AT_PROCESS, /* AT command is in processing */ + BT_HFP_AG_DISCOVER_DONE, /* SDP Record discovered done */ + BT_HFP_AG_RECORD_FOUND, /* SDP HF Record found */ + BT_HFP_AG_1ST_AT_RECV, /* 1st AT is received */ + BT_HFP_AG_FEAT_UPDATED, /* Remote feature has been updated */ + BT_HFP_AG_RELEASING, /* Release object when SDP discovery completes */ + + /* Total number of flags - must be at the end of the enum */ + + BT_HFP_AG_NUM_FLAGS, +}; + +/* bt_hfp_ag_call flags: the flags defined here represent HFP AG parameters */ + +enum +{ + BT_HFP_AG_CALL_IN_USING, /* Object is in using */ + BT_HFP_AG_CALL_INCOMING, /* Incoming call */ + BT_HFP_AG_CALL_INCOMING_HELD, /* Incoming call held */ + BT_HFP_AG_CALL_OPEN_SCO, /* Open SCO */ + BT_HFP_AG_CALL_OUTGOING_3WAY, /* Outgoing 3 way call */ + BT_HFP_AG_CALL_INCOMING_3WAY, /* Incoming 3 way call */ + BT_HFP_AG_CALL_ALERTING, /* Pending for alerting */ + + /* Total number of flags - must be at the end of the enum */ + + BT_HFP_AG_CALL_NUM_FLAGS, +}; + +typedef enum __packed +{ + /** Session disconnected */ + + BT_HFP_DISCONNECTED, + + /** Session in connecting state */ + + BT_HFP_CONNECTING, + + /** Session in config state, HFP configuration */ + + BT_HFP_CONFIG, + + /** Session ready for upper layer traffic on it */ + + BT_HFP_CONNECTED, + + /** Session in disconnecting state */ + + BT_HFP_DISCONNECTING, +} bt_hfp_state_t; + +typedef enum __packed +{ + /** Call terminate */ + + BT_HFP_CALL_TERMINATE = 1, + + /** Call outgoing */ + + BT_HFP_CALL_OUTGOING = 2, + + /** Call incoming */ + + BT_HFP_CALL_INCOMING = 4, + + /** Call alerting */ + + BT_HFP_CALL_ALERTING = 8, + + /** Call active */ + + BT_HFP_CALL_ACTIVE = 16, + + /** Call hold */ + + BT_HFP_CALL_HOLD = 32, +} bt_hfp_call_state_t; + +#define AT_COPS_OPERATOR_MAX_LEN 16 + +struct bt_hfp_ag_call +{ + struct bt_hfp_ag *ag; + + char number[CONFIG_BT_HFP_AG_PHONE_NUMBER_MAX_LEN + 1]; + uint8_t type; + + atomic_define(flags, BT_HFP_AG_CALL_NUM_FLAGS); + + /* HFP Call state */ + + bt_hfp_call_state_t call_state; + + /* Calling Line Identification notification */ + + struct k_work_delayable ringing_work; + + struct k_work_delayable deferred_work; +}; + +struct bt_hfp_ag +{ + struct bt_rfcomm_dlc rfcomm_dlc; + char buffer[HF_MAX_BUF_LEN]; + uint32_t hf_features; + uint32_t ag_features; + + /* HF Supported Codec Ids */ + + uint32_t hf_codec_ids; + uint8_t selected_codec_id; + + atomic_define(flags, BT_HFP_AG_NUM_FLAGS); + + /* ACL Connection Object */ + + struct bt_conn *acl_conn; + + /* HFP Connection state */ + + bt_hfp_state_t state; + + /* AG Indicators */ + + uint8_t indicator_value[BT_HFP_AG_IND_MAX]; + uint32_t indicator; + + /* HF Indicators */ + + uint32_t hf_indicators_of_ag; + uint32_t hf_indicators_of_hf; + uint32_t hf_indicators; + + /* AG profile information */ + + uint16_t hf_sdp_version; + uint16_t hf_sdp_features; + + /* operator */ + + uint8_t mode; + char operator[AT_COPS_OPERATOR_MAX_LEN + 1]; + + /* calls */ + + struct bt_hfp_ag_call calls[CONFIG_BT_HFP_AG_MAX_CALLS]; + + /* ongoing calls */ + + struct bt_hfp_ag_ongoing_call ongoing_calls[CONFIG_BT_HFP_AG_MAX_CALLS]; + size_t ongoing_call_count; + + /* ongoing calls work */ + + struct k_work_delayable ongoing_call_work; + + /* SCO Connection Object */ + + struct bt_sco_chan sco_chan; + + /* SCO connect */ + + void *sco_conn; + + /* SDP discover params */ + + struct bt_sdp_discover_params sdp_param; + + /* SCL work */ + + struct k_work slc_work; + int ack_err; + + /* HFP TX pending */ + + struct list_node tx_pending; + struct list_node tx_submit_pending; + + /* Critical locker */ + + sem_t lock; + + /* TX work */ + + struct k_work_delayable tx_work; +}; diff --git a/libs/zblue/host/classic/hfp_hf.c b/libs/zblue/host/classic/hfp_hf.c new file mode 100644 index 0000000000000..d6969ee69c7e6 --- /dev/null +++ b/libs/zblue/host/classic/hfp_hf.c @@ -0,0 +1,5556 @@ +/**************************************************************************** + * libs/zblue/host/classic/hfp_hf.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* hfp_hf.c - Hands free Profile - Handsfree side handling */ + +/* Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#include +#include +#include "l2cap_br_internal.h" +#include "rfcomm_internal.h" +#include "at.h" +#include "sco_internal.h" +#include "hfp_hf_internal.h" + +#define LOG_LEVEL CONFIG_BT_HFP_HF_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_hfp_hf); + +#define MAX_IND_STR_LEN 17 + +#define HFP_HF_INDICATOR_INVALID -1 + +struct bt_hfp_hf_cb *bt_hf; + +NET_BUF_POOL_FIXED_DEFINE(hf_pool, + CONFIG_BT_MAX_CONN + 1, + BT_RFCOMM_BUF_SIZE(BT_HF_CLIENT_MAX_PDU), + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + +static struct bt_hfp_hf bt_hfp_hf_pool[CONFIG_BT_MAX_CONN]; + +#define HF_ENHANCED_CALL_STATUS_TIMEOUT 50 /* ms */ + +struct at_callback_set +{ + void *resp; + void *finish; +} __packed; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static inline void make_at_callback_set(void *storage, + void *resp, void *finish) +{ + ((struct at_callback_set *)storage)->resp = resp; + ((struct at_callback_set *)storage)->finish = finish; +} + +static inline void *at_callback_set_resp(void *storage) +{ + return ((struct at_callback_set *)storage)->resp; +} + +static inline void *at_callback_set_finish(void *storage) +{ + return ((struct at_callback_set *)storage)->finish; +} + +/* The order should follow the enum hfp_hf_ag_indicators */ + +static const struct +{ + char *name; + uint32_t min; + uint32_t max; +} + +ag_ind[] = +{ + { + "service", 0, 1 + }, + + /* HF_SERVICE_IND */ + + { + "call", 0, 1 + }, + + /* HF_CALL_IND */ + + { + "callsetup", 0, 3 + }, + + /* HF_CALL_SETUP_IND */ + + { + "callheld", 0, 2 + }, + + /* HF_CALL_HELD_IND */ + + { + "signal", 0, 5 + }, + + /* HF_SIGNAL_IND */ + + { + "roam", 0, 1 + }, + + /* HF_ROAM_IND */ + + { + "battchg", 0, 5 + } + + /* HF_BATTERY_IND */ +}; + +#define BT_HFP_HF_FEAT_TEST(feat, mask) (((feat) & (mask)) != 0) + +#define BT_HFP_HF_VERSION BT_HFP_VERSION_1_9 + +/* HFP Hands-Free SDP record */ + +static struct bt_sdp_attribute hfp_attrs[] = +{ + BT_SDP_NEW_SERVICE, + BT_SDP_LIST(BT_SDP_ATTR_SVCLASS_ID_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_HANDSFREE_SVCLASS) + }, + + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_GENERIC_AUDIO_SVCLASS) + } + + ) + ), + + BT_SDP_LIST(BT_SDP_ATTR_PROTO_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 12), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) + }, + + ) + }, + + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 5), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_RFCOMM) + }, + + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT8), + BT_SDP_ARRAY_8(BT_RFCOMM_CHAN_HFP_HF) + }, + + ) + }, + + ) + ), + + BT_SDP_LIST(BT_SDP_ATTR_PROFILE_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 8), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_HANDSFREE_SVCLASS) + }, + + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_HFP_HF_VERSION) + }, + + ) + }, + + ) + ), +/* The values of the ā€œSupportedFeaturesā€ bitmap shall be the same as the + * values of the Bits 0 to 4 of the AT-command AT+BRSF (see Section 5.3). + */ + + BT_SDP_SUPPORTED_FEATURES(BT_HFP_HF_SDP_SUPPORTED_FEATURES), +}; + +static struct bt_sdp_record hfp_rec = BT_SDP_RECORD(hfp_attrs); + +void hf_slc_error(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; + + LOG_ERR("SLC error: disconnecting"); + err = bt_rfcomm_dlc_disconnect(&hf->rfcomm_dlc); + if (err) + { + LOG_ERR("Rfcomm: Unable to disconnect :%d", -err); + } +} + +static void hfp_hf_send_failed(struct bt_hfp_hf *hf) +{ + int err; + + LOG_ERR("SLC error: disconnecting"); + + err = bt_rfcomm_dlc_disconnect(&hf->rfcomm_dlc); + if (err) + { + LOG_ERR("Fail to disconnect: %d", err); + } +} + +static void hfp_hf_send_data(struct bt_hfp_hf *hf); + +static int hfp_hf_common_finish(struct at_client *at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(at, struct bt_hfp_hf, at); + int err = 0; + + if (result != AT_RESULT_OK) + { + LOG_WRN("Fail to send AT command (result %d, cme err %d) on " + "%p", result, + cme_err, hf); + } + + if (hf->backup_finish) + { + err = hf->backup_finish(at, result, cme_err); + hf->backup_finish = NULL; + } + + if (atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_TX_ONGOING)) + { + LOG_DBG("TX is done on %p", hf); + } + else + { + LOG_WRN("Tx is not ongoing on %p", hf); + } + + hfp_hf_send_data(hf); + return err; +} + +static void hfp_hf_send_data(struct bt_hfp_hf *hf) +{ + struct net_buf *buf; + at_resp_cb_t resp; + at_finish_cb_t finish; + int err; + + if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_RX_ONGOING)) + { + return; + } + + if (atomic_test_and_set_bit(hf->flags, BT_HFP_HF_FLAG_TX_ONGOING)) + { + return; + } + + buf = k_fifo_get(&hf->tx_pending, 0); + if (!buf) + { + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_TX_ONGOING); + return; + } + + resp = (at_resp_cb_t)at_callback_set_resp(buf->user_data); + finish = (at_finish_cb_t)at_callback_set_finish(buf->user_data); + + /* Backup the `finish` callback. + * Provide a default finish callback to drive the next sending. + */ + + hf->backup_finish = finish; + finish = hfp_hf_common_finish; + + make_at_callback_set(buf->user_data, NULL, NULL); + at_register(&hf->at, resp, finish); + + err = bt_rfcomm_dlc_send(&hf->rfcomm_dlc, buf); + if (err < 0) + { + LOG_ERR("Rfcomm send error :(%d)", err); + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_TX_ONGOING); + net_buf_unref(buf); + hfp_hf_send_failed(hf); + } +} + +int hfp_hf_send_cmd( + struct bt_hfp_hf *hf, at_resp_cb_t resp, at_finish_cb_t finish, + const char *format, ...) +{ + struct net_buf *buf; + va_list vargs; + int ret; + + buf = bt_rfcomm_create_pdu(&hf_pool); + if (!buf) + { + LOG_ERR("No Buffers!"); + return -ENOMEM; + } + + make_at_callback_set(buf->user_data, resp, finish); + + va_start(vargs, format); + ret = vsnprintf((char *)buf->data, (net_buf_tailroom(buf) - 1), format, + vargs); + if (ret < 0) + { + LOG_ERR("Unable to format variable arguments"); + return ret; + } + + va_end(vargs); + + net_buf_add(buf, ret); + net_buf_add_u8(buf, '\r'); + + LOG_DBG("HF %p, DLC %p sending buf %p", hf, &hf->rfcomm_dlc, buf); + + k_fifo_put(&hf->tx_pending, buf); + hfp_hf_send_data(hf); + + return 0; +} + +int brsf_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + uint32_t val; + int ret; + + ret = at_get_number(hf_at, &val); + if (ret < 0) + { + LOG_ERR("Error getting value"); + return ret; + } + + hf->ag_features = val; + if ((hf->ag_sdp_version <= BT_HFP_VERSION_1_5) || + (BT_HFP_HF_VERSION <= BT_HFP_VERSION_1_5)) + { + if (hf->ag_features & BT_HFP_AG_FEATURE_CODEC_NEG) + { + LOG_WRN("Unsupported feature (Codec Negotiation) is enabled."); + hf->ag_features &= ~BT_HFP_AG_FEATURE_CODEC_NEG; + } + } + + return 0; +} + +int brsf_resp(struct at_client *hf_at, struct net_buf *buf) +{ + int err; + + LOG_DBG(""); + + err = at_parse_cmd_input(hf_at, buf, "BRSF", brsf_handle, + AT_CMD_TYPE_NORMAL); + if (err < 0) + { + /* Returning negative value is avoided before SLC connection + * established. + */ + + LOG_ERR("Error parsing CMD input"); + hf_slc_error(hf_at); + } + + return 0; +} + +static void cind_handle_values( + struct at_client *hf_at, uint32_t index, char *name, uint32_t min, + uint32_t max) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int i; + + LOG_DBG("index: %u, name: %s, min: %u, max:%u", index, name, min, max); + + if (index >= ARRAY_SIZE(hf->ind_table)) + { + LOG_WRN("Invalid indicator index: %u", index); + return; + } + + for (i = 0; i < ARRAY_SIZE(ag_ind); i++) + { + if (strcmp(name, ag_ind[i].name) != 0) + { + continue; + } + + if (min != ag_ind[i].min || max != ag_ind[i].max) + { + LOG_ERR("%s indicator min/max value not matching", name); + } + + hf->ind_table[index] = i; + break; + } +} + +int cind_handle(struct at_client *hf_at) +{ + uint32_t index = 0U; + + /* Parsing Example: CIND: ("call",(0,1)) etc.. */ + + while (at_has_next_list(hf_at)) + { + char name[MAX_IND_STR_LEN]; + uint32_t min; + uint32_t max; + + if (at_open_list(hf_at) < 0) + { + LOG_ERR("Could not get open list"); + goto error; + } + + if (at_list_get_string(hf_at, name, sizeof(name)) < 0) + { + LOG_ERR("Could not get string"); + goto error; + } + + if (at_open_list(hf_at) < 0) + { + LOG_ERR("Could not get open list"); + goto error; + } + + if (at_list_get_range(hf_at, &min, &max) < 0) + { + LOG_ERR("Could not get range"); + goto error; + } + + if (at_close_list(hf_at) < 0) + { + LOG_ERR("Could not get close list"); + goto error; + } + + if (at_close_list(hf_at) < 0) + { + LOG_ERR("Could not get close list"); + goto error; + } + + cind_handle_values(hf_at, index, name, min, max); + index++; + } + + return 0; +error: + LOG_ERR("Error on CIND response"); + hf_slc_error(hf_at); + return -EINVAL; +} + +int cind_resp(struct at_client *hf_at, struct net_buf *buf) +{ + int err; + + err = at_parse_cmd_input(hf_at, buf, "CIND", cind_handle, + AT_CMD_TYPE_NORMAL); + if (err < 0) + { + LOG_ERR("Error parsing CMD input"); + hf_slc_error(hf_at); + } + + return 0; +} + +static void free_call(struct bt_hfp_hf_call *call) +{ + memset(call, 0, sizeof(*call)); +} + +#if defined(CONFIG_BT_HFP_HF_ECS) +static struct bt_hfp_hf_call *get_call_with_index(struct bt_hfp_hf *hf, + uint8_t index) +{ + struct bt_hfp_hf_call *call; + + for (size_t i = 0; i < ARRAY_SIZE(hf->calls); i++) + { + call = &hf->calls[i]; + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) + { + continue; + } + + if (call->index == index) + { + return call; + } + } + + return NULL; +} + +static struct bt_hfp_hf_call *get_call_without_index(struct bt_hfp_hf *hf) +{ + struct bt_hfp_hf_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) + { + call = &hf->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) + { + continue; + } + + if (!call->index) + { + return call; + } + } + + return NULL; +} +#endif /* CONFIG_BT_HFP_HF_ECS */ + +static void hf_reject_call(struct bt_hfp_hf_call *call) +{ + if (bt_hf->reject) + { + bt_hf->reject(call); + } + + free_call(call); +} + +static void hf_terminate_call(struct bt_hfp_hf_call *call) +{ + if (bt_hf->terminate) + { + bt_hf->terminate(call); + } + + free_call(call); +} + +static void clear_call_without_clcc(struct bt_hfp_hf *hf) +{ + struct bt_hfp_hf_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) + { + call = &hf->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) + { + continue; + } + + if (atomic_test_and_clear_bit(call->flags, BT_HFP_HF_CALL_CLCC)) + { + continue; + } + + switch (atomic_read(call->state)) + { + case BT_HFP_HF_CALL_STATE_TERMINATE: + break; + case BT_HFP_HF_CALL_STATE_OUTGOING: + case BT_HFP_HF_CALL_STATE_INCOMING: + case BT_HFP_HF_CALL_STATE_ALERTING: + case BT_HFP_HF_CALL_STATE_WAITING: + hf_reject_call(call); + break; + case BT_HFP_HF_CALL_STATE_ACTIVE: + case BT_HFP_HF_CALL_STATE_HELD: + hf_terminate_call(call); + break; + default: + free_call(call); + break; + } + } +} + +static int clcc_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+CLCC (result %d) on %p", result, hf); + + if (result == AT_RESULT_OK) + { + clear_call_without_clcc(hf); + } + + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_USR_CLCC_CMD); + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_CLCC_PENDING); + + return 0; +} + +static void clear_call_clcc_state(struct bt_hfp_hf *hf) +{ + struct bt_hfp_hf_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) + { + call = &hf->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) + { + continue; + } + + atomic_clear_bit(call->flags, BT_HFP_HF_CALL_CLCC); + } +} + +static int hf_query_current_calls(struct bt_hfp_hf *hf) +{ + int err; + + LOG_DBG(""); + + if (!hf) + { + LOG_ERR("No HF connection found"); + return -EINVAL; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) + { + return -ENOTCONN; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_ECS)) + { + return -ENOTSUP; + } + + if (!(hf->hf_features & BT_HFP_HF_FEATURE_ECS)) + { + return -ENOTSUP; + } + + if (atomic_test_and_set_bit(hf->flags, BT_HFP_HF_FLAG_CLCC_PENDING)) + { + k_work_reschedule(&hf->deferred_work, + MSEC2TICK(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + return 0; + } + + if (atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_USR_CLCC_PND)) + { + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_USR_CLCC_CMD); + } + + clear_call_clcc_state(hf); + + err = hfp_hf_send_cmd(hf, NULL, clcc_finish, "AT+CLCC"); + if (err < 0) + { + LOG_ERR("Fail to query current calls on %p", hf); + } + + return err; +} + +static void hf_call_state_update(struct bt_hfp_hf_call *call, int state) +{ + int old_state; + + old_state = atomic_read(call->state); + atomic_set(call->state, state); + + LOG_DBG("Call %p state update %d->%d", call, old_state, state); + + switch (state) + { + case BT_HFP_HF_CALL_STATE_TERMINATE: + free_call(call); + break; + case BT_HFP_HF_CALL_STATE_OUTGOING: + break; + case BT_HFP_HF_CALL_STATE_INCOMING: + break; + case BT_HFP_HF_CALL_STATE_ALERTING: + k_work_reschedule(&call->hf->deferred_work, + MSEC2TICK(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + break; + case BT_HFP_HF_CALL_STATE_WAITING: + k_work_reschedule(&call->hf->deferred_work, + MSEC2TICK(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + break; + case BT_HFP_HF_CALL_STATE_ACTIVE: + break; + case BT_HFP_HF_CALL_STATE_HELD: + break; + } +} + +static int get_using_call_count(struct bt_hfp_hf *hf) +{ + struct bt_hfp_hf_call *call; + int count = 0; + + ARRAY_FOR_EACH(hf->calls, i) + { + call = &hf->calls[i]; + + if (atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) + { + count++; + } + } + + return count; +} + +static struct bt_hfp_hf_call *get_new_call(struct bt_hfp_hf *hf) +{ + struct bt_hfp_hf_call *call; + + ARRAY_FOR_EACH(hf->calls, i) + { + call = &hf->calls[i]; + + if (atomic_test_and_set_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) + { + continue; + } + + call->hf = hf; + + return call; + } + + return NULL; +} + +#if defined(CONFIG_BT_HFP_HF_ECS) +static void call_state_update(struct bt_hfp_hf_call *call, uint32_t status) +{ + switch (status) + { + case BT_HFP_CLCC_STATUS_ACTIVE: + if ((atomic_read(call->state) != BT_HFP_HF_CALL_STATE_ACTIVE) || + ((atomic_read(call->state) == BT_HFP_HF_CALL_STATE_ACTIVE) && + atomic_test_and_clear_bit(call->flags, + BT_HFP_HF_CALL_INCOMING_HELD))) + { + int32_t state; + + state = atomic_read(call->state); + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_ACTIVE); + if (state == BT_HFP_HF_CALL_STATE_HELD) + { + if (bt_hf->retrieve) + { + bt_hf->retrieve(call); + } + } + else + { + if (bt_hf->accept) + { + bt_hf->accept(call); + } + } + } + break; + case BT_HFP_CLCC_STATUS_HELD: + if ((atomic_read(call->state) == BT_HFP_HF_CALL_STATE_ACTIVE) && + !atomic_test_and_clear_bit(call->flags, + BT_HFP_HF_CALL_INCOMING_HELD)) + { + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_HELD); + if (bt_hf->held) + { + bt_hf->held(call); + } + } + break; + case BT_HFP_CLCC_STATUS_DIALING: + break; + case BT_HFP_CLCC_STATUS_ALERTING: + break; + case BT_HFP_CLCC_STATUS_INCOMING: + break; + case BT_HFP_CLCC_STATUS_WAITING: + break; + case BT_HFP_CLCC_STATUS_CALL_HELD_HOLD: + if ((atomic_read(call->state) == BT_HFP_HF_CALL_STATE_INCOMING) || + (atomic_read(call->state) == BT_HFP_HF_CALL_STATE_WAITING)) + { + atomic_set_bit(call->flags, BT_HFP_HF_CALL_INCOMING_HELD); + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_ACTIVE); + if (bt_hf->incoming_held) + { + bt_hf->incoming_held(call); + } + } + break; + } +} + +static void new_call_state_update(struct bt_hfp_hf_call *call, + bool incoming, uint32_t status) +{ + switch (status) + { + case BT_HFP_CLCC_STATUS_ACTIVE: + case BT_HFP_CLCC_STATUS_HELD: + case BT_HFP_CLCC_STATUS_DIALING: + case BT_HFP_CLCC_STATUS_ALERTING: + case BT_HFP_CLCC_STATUS_INCOMING: + case BT_HFP_CLCC_STATUS_WAITING: + case BT_HFP_CLCC_STATUS_CALL_HELD_HOLD: + if (incoming) + { + if (bt_hf->incoming) + { + bt_hf->incoming(call->hf, call); + } + } + else + { + if (bt_hf->outgoing) + { + bt_hf->outgoing(call->hf, call); + } + } + break; + default: + LOG_WRN("Invalid call status %u", status); + free_call(call); + return; + } + + switch (status) + { + case BT_HFP_CLCC_STATUS_ACTIVE: + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_ACTIVE); + if (bt_hf->accept) + { + bt_hf->accept(call); + } + break; + case BT_HFP_CLCC_STATUS_HELD: + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_HELD); + if (bt_hf->held) + { + bt_hf->held(call); + } + break; + case BT_HFP_CLCC_STATUS_DIALING: + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_OUTGOING); + if (bt_hf->dialing) + { + bt_hf->dialing(call->hf, 0); + } + break; + case BT_HFP_CLCC_STATUS_ALERTING: + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_ALERTING); + if (bt_hf->remote_ringing) + { + bt_hf->remote_ringing(call); + } + break; + case BT_HFP_CLCC_STATUS_INCOMING: + case BT_HFP_CLCC_STATUS_WAITING: + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_WAITING); + break; + case BT_HFP_CLCC_STATUS_CALL_HELD_HOLD: + atomic_set_bit(call->flags, BT_HFP_HF_CALL_INCOMING_HELD); + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_ACTIVE); + if (bt_hf->incoming_held) + { + bt_hf->incoming_held(call); + } + break; + default: + break; + } +} + +static void set_call_incoming_flag(struct bt_hfp_hf_call *call, + bool incoming) +{ + int call_count; + + call_count = get_using_call_count(call->hf); + if (call_count > 1) + { + if (incoming) + { + atomic_set_bit(call->flags, BT_HFP_HF_CALL_INCOMING_3WAY); + } + else + { + atomic_set_bit(call->flags, BT_HFP_HF_CALL_OUTGOING_3WAY); + } + } + else + { + atomic_set_bit_to(call->flags, BT_HFP_HF_CALL_INCOMING, incoming); + } +} + +static int clcc_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; + struct bt_hfp_hf_call *call; + uint32_t index; + uint32_t dir; + uint32_t status; + uint32_t mode; + uint32_t mpty; + const char *number; + uint32_t type = 0; + bool incoming = false; + bool new_call = false; + + err = at_get_number(hf_at, &index); + if (err < 0) + { + LOG_ERR("Error getting index"); + return err; + } + + call = get_call_with_index(hf, (uint8_t)index); + if (!call) + { + LOG_INF("Valid call with index %d not found", index); + call = get_call_without_index(hf); + if (!call) + { + call = get_new_call(hf); + if (!call) + { + LOG_INF("Not available call"); + return 0; + } + + new_call = true; + } + + call->index = (uint8_t)index; + } + + atomic_set_bit(call->flags, BT_HFP_HF_CALL_CLCC); + + err = at_get_number(hf_at, &dir); + if (err < 0) + { + LOG_ERR("Error getting dir"); + return err; + } + + err = at_get_number(hf_at, &status); + if (err < 0) + { + LOG_ERR("Error getting status"); + return err; + } + + err = at_get_number(hf_at, &mode); + if (err < 0) + { + LOG_ERR("Error getting mode"); + return err; + } + + err = at_get_number(hf_at, &mpty); + if (err < 0) + { + LOG_ERR("Error getting mpty"); + return err; + } + + number = at_get_string(hf_at); + + if (number != NULL) + { + (void)at_get_number(hf_at, &type); + } + + if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_USR_CLCC_CMD) && + (bt_hf->query_call != NULL)) + { + struct bt_hfp_hf_current_call current_call; + + current_call.index = (uint8_t)index; + current_call.dir = (enum bt_hfp_hf_call_dir)dir; + current_call.status = (enum bt_hfp_hf_call_status)status; + current_call.mode = (enum bt_hfp_hf_call_mode)mode; + current_call.multiparty = mpty > 0 ? true : false; + current_call.number = number; + current_call.type = (uint8_t)type; + + bt_hf->query_call(hf, ¤t_call); + } + + LOG_DBG("CLCC idx %d dir %d status %d mode %d mpty %d number %s type %d", + index, dir, status, + mode, mpty, number, type); + + if (new_call) + { + set_call_incoming_flag(call, dir == BT_HFP_CLCC_DIR_INCOMING); + } + + if (atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING) || + atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING_3WAY)) + { + incoming = true; + } + + if (incoming != (dir == BT_HFP_CLCC_DIR_INCOMING)) + { + LOG_ERR("Call dir of HF is not aligned with AG"); + return 0; + } + + if (new_call) + { + new_call_state_update(call, incoming, status); + } + else + { + call_state_update(call, status); + } + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_ECS */ + +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG) +static int bvra_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; + uint32_t activate; + uint32_t state; + const char *id; + char text_id[BT_HFP_BVRA_TEXT_ID_MAX_LEN + 1]; + size_t id_len; + uint32_t type; + uint32_t operation; + const char *text; + + err = at_get_number(hf_at, &activate); + if (err < 0) + { + LOG_ERR("Error getting activate"); + return err; + } + + if (activate) + { + if (!atomic_test_and_set_bit(hf->flags, BT_HFP_HF_FLAG_VRE_ACTIVATE)) + { + if (bt_hf->voice_recognition) + { + bt_hf->voice_recognition(hf, true); + } + } + } + else + { + if (atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_VRE_ACTIVATE)) + { + if (bt_hf->voice_recognition) + { + bt_hf->voice_recognition(hf, false); + } + } + } + +#if defined(CONFIG_BT_HFP_HF_ENH_VOICE_RECG) + err = at_get_number(hf_at, &state); + if (err < 0) + { + LOG_INF("Error getting VRE state"); + return 0; + } + + if (bt_hf->vre_state) + { + bt_hf->vre_state(hf, (uint8_t)state); + } +#endif /* CONFIG_BT_HFP_HF_ENH_VOICE_RECG */ + +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG_TEXT) + id = at_get_raw_string(hf_at, &id_len); + if (id == NULL) + { + LOG_INF("Error getting text ID"); + return 0; + } + + if (id_len > BT_HFP_BVRA_TEXT_ID_MAX_LEN) + { + LOG_ERR("Invalid text ID length %d", id_len); + return -ENOTSUP; + } + + strncpy(text_id, id, MIN(id_len, BT_HFP_BVRA_TEXT_ID_MAX_LEN)); + text_id[MIN(id_len, BT_HFP_BVRA_TEXT_ID_MAX_LEN)] = '\0'; + + err = at_get_number(hf_at, &type); + if (err < 0) + { + LOG_INF("Error getting text type"); + return 0; + } + + err = at_get_number(hf_at, &operation); + if (err < 0) + { + LOG_INF("Error getting text operation"); + return 0; + } + + text = at_get_string(hf_at); + if (text == NULL) + { + LOG_INF("Error getting text string"); + return 0; + } + + if (bt_hf->textual_representation) + { + bt_hf->textual_representation(hf, text_id, (uint8_t)type, + (uint8_t)operation, text); + } +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG_TEXT */ + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ + +static struct bt_hfp_hf_call *get_dialing_call(struct bt_hfp_hf *hf) +{ + struct bt_hfp_hf_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) + { + call = &hf->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) + { + continue; + } + + switch (atomic_read(call->state)) + { + case BT_HFP_HF_CALL_STATE_OUTGOING: + case BT_HFP_HF_CALL_STATE_INCOMING: + case BT_HFP_HF_CALL_STATE_ALERTING: + case BT_HFP_HF_CALL_STATE_WAITING: + return call; + } + } + + return NULL; +} + +static struct bt_hfp_hf_call *get_call_with_state(struct bt_hfp_hf *hf, + int state) +{ + struct bt_hfp_hf_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) + { + call = &hf->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) + { + continue; + } + + if (atomic_read(call->state) == state) + { + return call; + } + } + + return NULL; +} + +static struct bt_hfp_hf_call *get_call_with_flag(struct bt_hfp_hf *hf, + int flag) +{ + struct bt_hfp_hf_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) + { + call = &hf->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) + { + continue; + } + + if (atomic_test_bit(call->flags, flag)) + { + return call; + } + } + + return NULL; +} + +static struct bt_hfp_hf_call *get_call_with_state_and_flag( + struct bt_hfp_hf *hf, int state, int flag) +{ + struct bt_hfp_hf_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) + { + call = &hf->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) + { + continue; + } + + if ((atomic_read(call->state) == state) && + atomic_test_bit(call->flags, flag)) + { + return call; + } + } + + return NULL; +} + +static struct bt_hfp_hf_call *get_using_call(struct bt_hfp_hf *hf) +{ + struct bt_hfp_hf_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) + { + call = &hf->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) + { + continue; + } + + return call; + } + + return NULL; +} + +static void bt_hf_deferred_work(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_hfp_hf + *hf = CONTAINER_OF(dwork, struct bt_hfp_hf, deferred_work); + int err; + + err = hf_query_current_calls(hf); + if (err != 0) + { + LOG_ERR("Failed to query current calls: %d", err); + } +} + +static void set_all_calls_held_state(struct bt_hfp_hf *hf, bool held) +{ + struct bt_hfp_hf_call *call; + + for (size_t index = 0; index < ARRAY_SIZE(hf->calls); index++) + { + call = &hf->calls[index]; + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) + { + continue; + } + + if (held && (atomic_read(call->state) == BT_HFP_HF_CALL_STATE_ACTIVE)) + { + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_HELD); + if (bt_hf->held) + { + bt_hf->held(call); + } + } + + if (!held && (atomic_read(call->state) == BT_HFP_HF_CALL_STATE_HELD)) + { + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_ACTIVE); + if (bt_hf->retrieve) + { + bt_hf->retrieve(call); + } + } + } +} + +static void ag_indicator_handle_call(struct bt_hfp_hf *hf, uint32_t value) +{ + struct bt_hfp_hf_call *call; + + LOG_DBG("call %d", value); + + if ((value != 0) && atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_INITIATING)) + { + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_QUERY_CALLS); + } + + if (value) + { + call = get_dialing_call(hf); + if (!call) + { + return; + } + + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_ACTIVE); + + if (atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING_HELD)) + { + if (bt_hf->incoming_held) + { + bt_hf->incoming_held(call); + } + } + else + { + if (bt_hf->accept) + { + bt_hf->accept(call); + } + } + } + else + { + do + { + call = get_using_call(hf); + if (!call) + { + return; + } + + switch (atomic_read(call->state)) + { + case BT_HFP_HF_CALL_STATE_OUTGOING: + case BT_HFP_HF_CALL_STATE_INCOMING: + case BT_HFP_HF_CALL_STATE_ALERTING: + case BT_HFP_HF_CALL_STATE_WAITING: + hf_reject_call(call); + break; + case BT_HFP_HF_CALL_STATE_ACTIVE: + if (atomic_test_and_clear_bit(call->flags, + BT_HFP_HF_CALL_INCOMING_HELD)) + { + hf_reject_call(call); + break; + } + + __fallthrough; + case BT_HFP_HF_CALL_STATE_HELD: + hf_terminate_call(call); + break; + default: + free_call(call); + break; + } + } + while (call); + } +} + +static void ag_indicator_handle_call_setup(struct bt_hfp_hf *hf, + uint32_t value) +{ + struct bt_hfp_hf_call *call; + int call_count; + + call_count = get_using_call_count(hf); + + LOG_DBG("call setup %d", value); + + if ((value != BT_HFP_CALL_SETUP_NONE) && atomic_test_bit(hf->flags, + BT_HFP_HF_FLAG_INITIATING)) + { + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_QUERY_CALLS); + } + + switch (value) + { + case BT_HFP_CALL_SETUP_NONE: + if (call_count == 1) + { + call = get_using_call(hf); + if (!call) + { + break; + } + + if ((atomic_read(call->state) == BT_HFP_HF_CALL_STATE_ACTIVE) || + (atomic_read(call->state) == BT_HFP_HF_CALL_STATE_HELD)) + { + break; + } + + hf_reject_call(call); + } + else + { + call = get_dialing_call(hf); + if (!call) + { + break; + } + + if (atomic_read(call->state) == BT_HFP_HF_CALL_STATE_OUTGOING) + { + LOG_INF("The outgoing is not alerted"); + hf_reject_call(call); + } + else + { + LOG_INF("Waiting for +CIEV: (callheld = 1)"); + k_work_reschedule(&hf->deferred_work, + MSEC2TICK(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + } + } + break; + case BT_HFP_CALL_SETUP_INCOMING: + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_INCOMING); + if (!call) + { + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) + { + LOG_INF("SLC is not connected. Will get call status via " + "AT+CLCC"); + break; + } + + call = get_new_call(hf); + if (!call) + { + break; + } + + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_INCOMING); + } + + if (call_count == 0) + { + atomic_set_bit(call->flags, BT_HFP_HF_CALL_INCOMING); + } + else + { + atomic_set_bit(call->flags, BT_HFP_HF_CALL_INCOMING_3WAY); + } + + if (bt_hf->incoming) + { + bt_hf->incoming(hf, call); + } + + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_WAITING); + break; + case BT_HFP_CALL_SETUP_OUTGOING: + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_OUTGOING); + if (!call) + { + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) + { + LOG_INF("SLC is not connected. Will get call status via " + "AT+CLCC"); + break; + } + + call = get_new_call(hf); + if (!call) + { + break; + } + + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_OUTGOING); + } + + if (call_count) + { + atomic_set_bit(call->flags, BT_HFP_HF_CALL_OUTGOING_3WAY); + } + + if (bt_hf->outgoing) + { + bt_hf->outgoing(hf, call); + } + break; + case BT_HFP_CALL_SETUP_REMOTE_ALERTING: + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_OUTGOING); + if (!call) + { + break; + } + + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_ALERTING); + if (bt_hf->remote_ringing) + { + bt_hf->remote_ringing(call); + } + break; + default: + break; + } +} + +static void ag_indicator_handle_call_held(struct bt_hfp_hf *hf, + uint32_t value) +{ + struct bt_hfp_hf_call *call; + + if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) + { + k_work_reschedule(&hf->deferred_work, + MSEC2TICK(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + } + + LOG_DBG("call setup %d", value); + + if ((value != BT_HFP_CALL_HELD_NONE) && atomic_test_bit(hf->flags, + BT_HFP_HF_FLAG_INITIATING)) + { + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_QUERY_CALLS); + } + + switch (value) + { + case BT_HFP_CALL_HELD_NONE: + set_all_calls_held_state(hf, false); + break; + case BT_HFP_CALL_HELD_ACTIVE_HELD: + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_ALERTING); + if (!call) + { + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_WAITING); + if (!call) + { + break; + } + } + + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_ACTIVE); + if (bt_hf->accept) + { + bt_hf->accept(call); + } + break; + case BT_HFP_CALL_HELD_HELD: + set_all_calls_held_state(hf, true); + break; + default: + break; + } +} + +void ag_indicator_handle_values(struct at_client *hf_at, + uint32_t index, uint32_t value) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("Index :%u, Value :%u", index, value); + + if (index >= ARRAY_SIZE(hf->ind_table)) + { + LOG_ERR("Invalid indicator index: %u", index); + return; + } + + if (hf->ind_table[index] == HFP_HF_INDICATOR_INVALID) + { + LOG_ERR("Indicator index %u not found", index); + return; + } + + if (hf->ind_table[index] >= ARRAY_SIZE(ag_ind)) + { + LOG_ERR("Max only %zu indicators are supported", ARRAY_SIZE(ag_ind)); + return; + } + + if (value > ag_ind[hf->ind_table[index]].max || + value < ag_ind[hf->ind_table[index]].min) + { + LOG_ERR("Indicators out of range - value: %u", value); + return; + } + + switch (hf->ind_table[index]) + { + case HF_SERVICE_IND: + if (bt_hf->service) + { + bt_hf->service(hf, value); + } + break; + case HF_CALL_IND: + ag_indicator_handle_call(hf, value); + break; + case HF_CALL_SETUP_IND: + ag_indicator_handle_call_setup(hf, value); + break; + case HF_CALL_HELD_IND: + ag_indicator_handle_call_held(hf, value); + break; + case HF_SIGNAL_IND: + if (bt_hf->signal) + { + bt_hf->signal(hf, value); + } + break; + case HF_ROAM_IND: + if (bt_hf->roam) + { + bt_hf->roam(hf, value); + } + break; + case HF_BATTERY_IND: + if (bt_hf->battery) + { + bt_hf->battery(hf, value); + } + break; + default: + LOG_ERR("Unknown AG indicator"); + break; + } +} + +int cind_status_handle(struct at_client *hf_at) +{ + uint32_t index = 0U; + + while (at_has_next_list(hf_at)) + { + uint32_t value; + int ret; + + ret = at_get_number(hf_at, &value); + if (ret < 0) + { + LOG_ERR("could not get the value"); + return ret; + } + + ag_indicator_handle_values(hf_at, index, value); + + index++; + } + + return 0; +} + +int cind_status_resp(struct at_client *hf_at, struct net_buf *buf) +{ + int err; + + err = at_parse_cmd_input(hf_at, buf, "CIND", cind_status_handle, + AT_CMD_TYPE_NORMAL); + if (err < 0) + { + LOG_ERR("Error parsing CMD input"); + hf_slc_error(hf_at); + } + + return 0; +} + +int ciev_handle(struct at_client *hf_at) +{ + uint32_t index; + uint32_t value; + int ret; + + ret = at_get_number(hf_at, &index); + if (ret < 0) + { + LOG_ERR("could not get the Index"); + return ret; + } + + /* The first element of the list shall have 1 */ + + if (!index) + { + LOG_ERR("Invalid index value '0'"); + return 0; + } + + ret = at_get_number(hf_at, &value); + if (ret < 0) + { + LOG_ERR("could not get the value"); + return ret; + } + + ag_indicator_handle_values(hf_at, (index - 1), value); + + return 0; +} + +int ring_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + struct bt_hfp_hf_call *call; + + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_WAITING); + if (!call) + { + return 0; + } + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING)) + { + LOG_WRN("Invalid call dir (outgoing)"); + } + + if (bt_hf->ring_indication) + { + bt_hf->ring_indication(call); + } + + return 0; +} + +#if defined(CONFIG_BT_HFP_HF_CLI) +int clip_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + const char *number; + uint32_t type; + int err; + struct bt_hfp_hf_call *call; + + number = at_get_string(hf_at); + + err = at_get_number(hf_at, &type); + if (err) + { + LOG_WRN("could not get the type"); + } + else + { + type = 0; + } + + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_WAITING); + if (!call) + { + return 0; + } + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING)) + { + LOG_WRN("Invalid call dir (outgoing)"); + } + + if (bt_hf->clip) + { + bt_hf->clip(call, number, (uint8_t)type); + } + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_CLI */ + +#if defined(CONFIG_BT_HFP_HF_VOLUME) +int vgm_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + uint32_t gain; + int err; + + err = at_get_number(hf_at, &gain); + if (err) + { + LOG_ERR("could not get the microphone gain"); + return err; + } + + if (gain > BT_HFP_HF_VGM_GAIN_MAX) + { + LOG_ERR("Invalid microphone gain (%d > %d)", gain, + BT_HFP_HF_VGM_GAIN_MAX); + return -EINVAL; + } + + if (bt_hf->vgm) + { + bt_hf->vgm(hf, (uint8_t)gain); + } + + return 0; +} + +int vgs_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + uint32_t gain; + int err; + + err = at_get_number(hf_at, &gain); + if (err) + { + LOG_ERR("could not get the speaker gain"); + return err; + } + + if (gain > BT_HFP_HF_VGS_GAIN_MAX) + { + LOG_ERR("Invalid speaker gain (%d > " + "%d)", gain, BT_HFP_HF_VGS_GAIN_MAX); + return -EINVAL; + } + + if (bt_hf->vgs) + { + bt_hf->vgs(hf, (uint8_t)gain); + } + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + +int bsir_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + uint32_t inband; + int err; + + err = at_get_number(hf_at, &inband); + if (err) + { + LOG_ERR("could not get bsir value"); + return err; + } + + if (inband > 1) + { + LOG_ERR("Invalid %d bsir value", inband); + return -EINVAL; + } + + if (bt_hf->inband_ring) + { + bt_hf->inband_ring(hf, (bool)inband); + } + + return 0; +} + +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +int bcs_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + uint32_t codec_id; + int err; + + err = at_get_number(hf_at, &codec_id); + if (err) + { + LOG_ERR("could not get bcs value"); + return err; + } + + if (!(hf->hf_codec_ids & BIT(codec_id))) + { + LOG_ERR("Invalid codec id %d", codec_id); + err = bt_hfp_hf_set_codecs(hf, hf->hf_codec_ids); + return err; + } + + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_CODEC_CONN); + + if (bt_hf->codec_negotiate) + { + bt_hf->codec_negotiate(hf, codec_id); + return 0; + } + + err = bt_hfp_hf_select_codec(hf, codec_id); + return err; +} +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + +static int btrh_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + uint32_t on_hold; + int err; + struct bt_hfp_hf_call *call; + + err = at_get_number(hf_at, &on_hold); + if (err < 0) + { + LOG_ERR("Error getting value"); + return err; + } + + if (on_hold == BT_HFP_BTRH_ON_HOLD) + { + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_WAITING); + if (!call) + { + return 0; + } + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING)) + { + LOG_WRN("Invalid call dir (outgoing)"); + } + + atomic_set_bit(call->flags, BT_HFP_HF_CALL_INCOMING_HELD); + } + else + { + call = get_call_with_state_and_flag(hf, BT_HFP_HF_CALL_STATE_ACTIVE, + BT_HFP_HF_CALL_INCOMING_HELD); + if (!call) + { + return 0; + } + + if (on_hold == BT_HFP_BTRH_ACCEPTED) + { + atomic_clear_bit(call->flags, BT_HFP_HF_CALL_INCOMING_HELD); + if (bt_hf && bt_hf->accept) + { + bt_hf->accept(call); + } + } + else if (on_hold == BT_HFP_BTRH_REJECTED) + { + hf_reject_call(call); + } + else + { + return -EINVAL; + } + } + + return 0; +} + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int ccwa_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + const char *number; + uint32_t type; + int err; + struct bt_hfp_hf_call *call; + + number = at_get_string(hf_at); + + err = at_get_number(hf_at, &type); + if (err) + { + LOG_WRN("could not get the type"); + } + else + { + type = 0; + } + + call = get_new_call(hf); + if (!call) + { + LOG_ERR("Not available call object"); + return 0; + } + + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_INCOMING); + if (bt_hf->call_waiting) + { + bt_hf->call_waiting(call, number, (uint8_t)type); + } + + return 0; +} + +static struct _chld_feature +{ + const char *name; + int value; +} + +chld_feature_map[] = +{ + { + "1x", BT_HFP_CALL_RELEASE_SPECIFIED_ACTIVE + }, + + { + "2x", BT_HFP_CALL_PRIVATE_CNLTN_MODE + }, + + { + "0", BT_HFP_CHLD_RELEASE_ALL + }, + + { + "1", BT_HFP_CHLD_RELEASE_ACTIVE_ACCEPT_OTHER + }, + + { + "2", BT_HFP_CALL_HOLD_ACTIVE_ACCEPT_OTHER + }, + + { + "3", BT_HFP_CALL_ACTIVE_HELD + }, + + { + "4", BT_HFP_CALL_QUITE + }, +}; + +static int get_chld_feature(const char *name) +{ + struct _chld_feature *chld; + + for (size_t index = 0; index < ARRAY_SIZE(chld_feature_map); index++) + { + chld = &chld_feature_map[index]; + if (!strncmp(name, chld->name, strlen(chld->name))) + { + return chld->value; + } + } + + return -EINVAL; +} + +int chld_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + const char *value; + uint32_t chld_features = 0; + int err; + + /* Parsing Example: CHLD: (0,1,2,3,4) */ + + if (at_open_list(hf_at) < 0) + { + LOG_ERR("Could not get open list"); + goto error; + } + + while (at_has_next_list(hf_at)) + { + value = at_get_raw_string(hf_at, NULL); + if (value == NULL) + { + LOG_ERR("Could not get value"); + goto error; + } + + err = get_chld_feature(value); + if (err < 0) + { + LOG_ERR("Cannot parse the value %s", value); + goto error; + } + + if (NUM_BITS(sizeof(chld_features)) > err) + { + chld_features |= BIT(err); + } + } + + if (at_close_list(hf_at) < 0) + { + LOG_ERR("Could not get close list"); + goto error; + } + + if (!((chld_features & BIT(BT_HFP_CHLD_RELEASE_ACTIVE_ACCEPT_OTHER)) && + (chld_features & BIT(BT_HFP_CALL_HOLD_ACTIVE_ACCEPT_OTHER)))) + { + LOG_ERR("AT+CHLD values 1 and 2 should be supported by AG"); + goto error; + } + + hf->chld_features = chld_features; + + return 0; + +error: + LOG_ERR("Error on AT+CHLD=? response"); + hf_slc_error(hf_at); + return -EINVAL; +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +static int cnum_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; + __maybe_unused const char *alpha; + const char *number; + uint32_t type; + __maybe_unused const char *speed; + uint32_t service = 4; + + alpha = at_get_raw_string(hf_at, NULL); + number = at_get_string(hf_at); + if (!number) + { + LOG_INF("Cannot get number"); + return -EINVAL; + } + + err = at_get_number(hf_at, &type); + if (err) + { + LOG_INF("Cannot get type"); + return -EINVAL; + } + + speed = at_get_raw_string(hf_at, NULL); + + err = at_get_number(hf_at, &service); + if (err) + { + LOG_INF("Cannot get service"); + } + + if (bt_hf->subscriber_number) + { + bt_hf->subscriber_number(hf, number, (uint8_t)type, (uint8_t)service); + } + + LOG_DBG("CNUM number %s type %d service %d", number, type, service); + + return 0; +} + +#if defined(CONFIG_BT_HFP_HF_HF_INDICATORS) +static int bind_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; + uint32_t index; + uint32_t value; + uint32_t ind = 0; + uint32_t ind_enable = hf->ind_enable; + + err = at_open_list(hf_at); + if (!err) + { + /* It is a list. */ + + while (at_has_next_list(hf_at)) + { + err = at_get_number(hf_at, &index); + if (err) + { + LOG_INF("Cannot get indicator"); + goto failed; + } + + ind |= BIT(index); + } + + if (at_close_list(hf_at) < 0) + { + LOG_ERR("Could not get close list"); + goto failed; + } + + hf->ag_ind = ind; + return 0; + } + + err = at_get_number(hf_at, &index); + if (err) + { + LOG_INF("Cannot get indicator"); + goto failed; + } + + err = at_get_number(hf_at, &value); + if (err) + { + LOG_INF("Cannot get status"); + goto failed; + } + + if (!value) + { + ind_enable &= ~BIT(index); + } + else + { + ind_enable |= BIT(index); + } + + hf->ind_enable = ind_enable; + return 0; + +failed: + LOG_ERR("Error on AT+BIND response"); + hf_slc_error(hf_at); + return -EINVAL; +} +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ + +static const struct unsolicited +{ + const char *cmd; + enum at_cmd_type type; + int (*func)(struct at_client *hf_at); +} + +handlers[] = +{ + { + "CIEV", AT_CMD_TYPE_UNSOLICITED, ciev_handle + }, + + { + "RING", AT_CMD_TYPE_OTHER, ring_handle + }, +#if defined(CONFIG_BT_HFP_HF_CLI) + + { + "CLIP", AT_CMD_TYPE_UNSOLICITED, clip_handle + }, +#endif /* CONFIG_BT_HFP_HF_CLI */ +#if defined(CONFIG_BT_HFP_HF_VOLUME) + + { + "VGM", AT_CMD_TYPE_UNSOLICITED, vgm_handle + }, + + { + "VGS", AT_CMD_TYPE_UNSOLICITED, vgs_handle + }, +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + + { + "BSIR", AT_CMD_TYPE_UNSOLICITED, bsir_handle + }, +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) + + { + "BCS", AT_CMD_TYPE_UNSOLICITED, bcs_handle + }, +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + + { + "BTRH", AT_CMD_TYPE_UNSOLICITED, btrh_handle + }, +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + + { + "CCWA", AT_CMD_TYPE_UNSOLICITED, ccwa_handle + }, + + { + "CHLD", AT_CMD_TYPE_UNSOLICITED, chld_handle + }, +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +#if defined(CONFIG_BT_HFP_HF_ECS) + + { + "CLCC", AT_CMD_TYPE_UNSOLICITED, clcc_handle + }, +#endif /* CONFIG_BT_HFP_HF_ECS */ +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG) + + { + "BVRA", AT_CMD_TYPE_UNSOLICITED, bvra_handle + }, +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ + + { + "CNUM", AT_CMD_TYPE_UNSOLICITED, cnum_handle + }, +#if defined(CONFIG_BT_HFP_HF_HF_INDICATORS) + + { + "BIND", AT_CMD_TYPE_UNSOLICITED, bind_handle + }, +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ +}; + +static const struct unsolicited *hfp_hf_unsol_lookup(struct at_client *hf_at) +{ + ARRAY_FOR_EACH(handlers, i) + { + size_t len = strlen(handlers[i].cmd); + + if ((hf_at->rsp_buf.len >= len) && (memcmp(hf_at->rsp_buf.data, + handlers[i].cmd, len) == 0)) + { + return &handlers[i]; + } + } + + return NULL; +} + +int unsolicited_cb(struct at_client *hf_at, struct net_buf *buf) +{ + const struct unsolicited *handler; + + handler = hfp_hf_unsol_lookup(hf_at); + if (!handler) + { + LOG_ERR("Unhandled unsolicited response"); + return -ENOMSG; + } + + if (!at_parse_cmd_input(hf_at, buf, handler->cmd, handler->func, + handler->type)) + { + return 0; + } + + return -ENOMSG; +} + +static int send_at_cmee(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + if (hf->ag_features & BT_HFP_AG_FEATURE_EXT_ERR) + { + return hfp_hf_send_cmd(hf, NULL, cb, "AT+CMEE=1"); + } + else + { + return -ENOTSUP; + } +} + +static int at_cmee_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("CMEE set (result %d) on %p", result, hf); + + return 0; +} + +static int send_at_cops(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, NULL, cb, "AT+COPS=3,0"); +} + +static int at_cops_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("COPS set (result %d) on %p", result, hf); + + return 0; +} + +#if defined(CONFIG_BT_HFP_HF_CLI) +static int send_at_clip(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, NULL, cb, "AT+CLIP=1"); +} + +static int at_clip_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("CLIP set (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_CLI */ + +#if defined(CONFIG_BT_HFP_HF_VOLUME) +static int send_at_vgm(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, NULL, cb, "AT+VGM=%d", hf->vgm); +} + +static int at_vgm_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("VGM set (result %d) on %p", result, hf); + + return 0; +} + +static int send_at_vgs(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, NULL, cb, "AT+VGS=%d", hf->vgs); +} + +static int at_vgs_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("VGS set (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int send_at_ccwa(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) + { + return -ENOTSUP; + } + + if (!(hf->hf_features & BT_HFP_HF_FEATURE_3WAY_CALL)) + { + return -ENOTSUP; + } + + return hfp_hf_send_cmd(hf, NULL, cb, "AT+CCWA=1"); +} + +static int at_ccwa_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("CCWA set (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +typedef int (*at_send_t)(struct bt_hfp_hf *hf, at_finish_cb_t cb); + +static struct at_cmd_init +{ + at_send_t send; + at_finish_cb_t finish; + bool disconnect; /* Disconnect if command failed. */ +} + +cmd_init_list[] = +{ +#if defined(CONFIG_BT_HFP_HF_VOLUME) + { + send_at_vgm, at_vgm_finish, false + }, + + { + send_at_vgs, at_vgs_finish, false + }, +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + + { + send_at_cmee, at_cmee_finish, false + }, + + { + send_at_cops, at_cops_finish, false + }, +#if defined(CONFIG_BT_HFP_HF_CLI) + + { + send_at_clip, at_clip_finish, false + }, +#endif /* CONFIG_BT_HFP_HF_CLI */ +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + + { + send_at_ccwa, at_ccwa_finish, false + }, +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +}; + +static int at_cmd_init_start(struct bt_hfp_hf *hf); + +static int at_cmd_init_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + at_finish_cb_t finish; + + if (result != AT_RESULT_OK) + { + LOG_WRN("It is ERROR response of AT command %d.", hf->cmd_init_seq); + } + + if (ARRAY_SIZE(cmd_init_list) > hf->cmd_init_seq) + { + finish = cmd_init_list[hf->cmd_init_seq].finish; + if (finish) + { + (void)finish(hf_at, result, cme_err); + } + } + else + { + LOG_ERR("Invalid indicator (%d>=%d)", hf->cmd_init_seq, + ARRAY_SIZE(cmd_init_list)); + } + + /* Goto next AT command */ + + hf->cmd_init_seq++; + (void)at_cmd_init_start(hf); + return 0; +} + +static int at_cmd_init_start(struct bt_hfp_hf *hf) +{ + at_send_t send; + int err = -EINVAL; + + while (ARRAY_SIZE(cmd_init_list) > hf->cmd_init_seq) + { + LOG_DBG("Fetch AT command (%d)", hf->cmd_init_seq); + send = cmd_init_list[hf->cmd_init_seq].send; + if (send) + { + LOG_DBG("Send AT command"); + err = send(hf, at_cmd_init_finish); + } + else + { + LOG_WRN("Invalid send func of AT command"); + } + + if (!err) + { + break; + } + + LOG_WRN("AT command sending failed"); + if (cmd_init_list[hf->cmd_init_seq].disconnect) + { + hfp_hf_send_failed(hf); + break; + } + + /* Goto next AT command */ + + LOG_WRN("Send next AT command"); + hf->cmd_init_seq++; + } + + if (ARRAY_SIZE(cmd_init_list) <= hf->cmd_init_seq) + { + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_INITIATING); + if (atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_QUERY_CALLS)) + { + k_work_reschedule(&hf->deferred_work, + MSEC2TICK(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + } + } + + return err; +} + +static void slc_completed(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + struct bt_conn *conn = hf->acl; + + if (bt_hf->connected) + { + bt_hf->connected(conn, hf); + } + + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED); + + /* Start with first AT command */ + + hf->cmd_init_seq = 0; + if (at_cmd_init_start(hf)) + { + LOG_ERR("Fail to start AT command initialization"); + } +} + +#if defined(CONFIG_BT_HFP_HF_HF_INDICATORS) +static int send_at_bind_status(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, NULL, cb, "AT+BIND?"); +} + +static int send_at_bind_hf_supported(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + char buffer[4]; + char *bind; + + hf->hf_ind = 0; + + bind = &buffer[0]; + if (IS_ENABLED(CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY)) + { + *bind = '0' + HFP_HF_ENHANCED_SAFETY_IND; + bind++; + *bind = ','; + bind++; + hf->hf_ind |= BIT(HFP_HF_ENHANCED_SAFETY_IND); + } + + if (IS_ENABLED(CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY)) + { + *bind = '0' + HFP_HF_BATTERY_LEVEL_IND; + bind++; + *bind = ','; + bind++; + hf->hf_ind |= BIT(HFP_HF_BATTERY_LEVEL_IND); + } + + if (bind <= &buffer[0]) + { + return -EINVAL; + } + + bind--; + *bind = '\0'; + return hfp_hf_send_cmd(hf, NULL, cb, "AT+BIND=%s", buffer); +} + +static int send_at_bind_supported(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, NULL, cb, "AT+BIND=?"); +} +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int send_at_chld_supported(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, NULL, cb, "AT+CHLD=?"); +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +static int send_at_cmer(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + at_register_unsolicited(&hf->at, unsolicited_cb); + return hfp_hf_send_cmd(hf, NULL, cb, "AT+CMER=3,0,0,1"); +} + +static int send_at_cind_status(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, cind_status_resp, cb, "AT+CIND?"); +} + +static int send_at_cind_supported(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + return hfp_hf_send_cmd(hf, cind_resp, cb, "AT+CIND=?"); +} + +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +static void get_codec_ids(struct bt_hfp_hf *hf, + char *buffer, size_t buffer_len) +{ + size_t len = 0; + uint8_t ids = hf->hf_codec_ids; + int index = 0; + + while (ids && (len < (buffer_len - 2))) + { + if (ids & 0x01) + { + buffer[len++] = index + '0'; + buffer[len++] = ','; + } + + index++; + ids = ids >> 1; + } + + if (len > 0) + { + len--; + } + + buffer[len] = '\0'; +} + +static int send_at_bac(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + char ids[sizeof(hf->hf_codec_ids) * 2 * 8 + 1]; + + get_codec_ids(hf, &ids[0], ARRAY_SIZE(ids)); + return hfp_hf_send_cmd(hf, NULL, cb, "AT+BAC=%s", ids); +} +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + +#define HFP_SDP_FEAT_MASK GENMASK(4, 0) + +static int send_at_brsf(struct bt_hfp_hf *hf, at_finish_cb_t cb) +{ + if ((hf->ag_sdp_version <= BT_HFP_VERSION_0_96) || + (BT_HFP_HF_VERSION <= BT_HFP_VERSION_0_96)) + { + /* Update the AG features according to the SDP features for HFP version + * 0.96. + * + * Hands-Free Profile Specification V1.9, 6.3 SDP Interoperability + * Requirements The values of the ā€œSupportedFeaturesā€ bitmap given + * in + * Table 6.6 shall be the same as the values of the Bits 0 to 4 of the + * unsolicited result code + * +BRSF. + */ + + hf->ag_features = hf->ag_sdp_features & HFP_SDP_FEAT_MASK; + + /* Due to the AT command `AT+BRSF` cannot be sent if the peer version + * is v.96, notify the upper layer directly. + */ + + if (cb != NULL) + { + cb(&hf->at, AT_RESULT_OK, CME_ERROR_AG_FAILURE); + } + + LOG_WRN("CMD AT+BRSF not sent, AG features %04x", hf->ag_features); + return 0; + } + + return hfp_hf_send_cmd(hf, brsf_resp, cb, "AT+BRSF=%u", hf->hf_features); +} + +static struct slc_init +{ + at_send_t send; + bool disconnect; /* Disconnect if command failed. */ + uint32_t ag_feature_mask; /* AG feature mask */ +} + +slc_init_list[] = +{ + { + send_at_brsf, true, 0 + }, +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) + + { + send_at_bac, true, BT_HFP_AG_FEATURE_CODEC_NEG + }, +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + + { + send_at_cind_supported, true, 0 + }, + + { + send_at_cind_status, true, 0 + }, + + { + send_at_cmer, true, 0 + }, +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + + { + send_at_chld_supported, true, BT_HFP_AG_FEATURE_3WAY_CALL + }, +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +#if defined(CONFIG_BT_HFP_HF_HF_INDICATORS) + + { + send_at_bind_hf_supported, true, BT_HFP_AG_FEATURE_HF_IND + }, + + { + send_at_bind_supported, true, BT_HFP_AG_FEATURE_HF_IND + }, + + { + send_at_bind_status, true, BT_HFP_AG_FEATURE_HF_IND + }, +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ +}; + +static int slc_init_start(struct bt_hfp_hf *hf); + +static int slc_init_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + if (result != AT_RESULT_OK) + { + LOG_WRN("It is ERROR response of AT command %d.", hf->cmd_init_seq); + if (slc_init_list[hf->cmd_init_seq].disconnect) + { + hf_slc_error(&hf->at); + return 0; + } + } + + if (ARRAY_SIZE(slc_init_list) <= hf->cmd_init_seq) + { + LOG_ERR("Invalid indicator (%d>=%d)", hf->cmd_init_seq, + ARRAY_SIZE(slc_init_list)); + } + + /* Goto next AT command */ + + hf->cmd_init_seq++; + (void)slc_init_start(hf); + return 0; +} + +static int slc_init_start(struct bt_hfp_hf *hf) +{ + at_send_t send; + uint32_t feture_mask; + int err = -EINVAL; + + while (ARRAY_SIZE(slc_init_list) > hf->cmd_init_seq) + { + LOG_DBG("Fetch AT command (%d)", hf->cmd_init_seq); + feture_mask = slc_init_list[hf->cmd_init_seq].ag_feature_mask; + if (feture_mask && (!(feture_mask & hf->ag_features))) + { + /* The feature is not supported by AG. Skip the step. */ + + LOG_INF("Skip SLC init step %d", hf->cmd_init_seq); + hf->cmd_init_seq++; + continue; + } + + send = slc_init_list[hf->cmd_init_seq].send; + if (send) + { + LOG_DBG("Send AT command"); + err = send(hf, slc_init_finish); + } + else + { + LOG_WRN("Invalid send func of AT command"); + } + + if (!err) + { + break; + } + + LOG_WRN("AT command sending failed"); + if (slc_init_list[hf->cmd_init_seq].disconnect) + { + hfp_hf_send_failed(hf); + break; + } + + /* Goto next AT command */ + + LOG_WRN("Send next AT command"); + hf->cmd_init_seq++; + } + + if (ARRAY_SIZE(slc_init_list) <= hf->cmd_init_seq) + { + slc_completed(&hf->at); + } + + return err; +} + +int hf_slc_establish(struct bt_hfp_hf *hf) +{ + int err; + + LOG_DBG(""); + + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_INITIATING); + + hf->cmd_init_seq = 0; + err = slc_init_start(hf); + if (err < 0) + { + hf_slc_error(&hf->at); + return err; + } + + return 0; +} + +#if defined(CONFIG_BT_HFP_HF_CLI) +static int cli_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("CLIP set (result %d) on %p", result, hf); + + /* AT+CLI is done. */ + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_CLI */ + +int bt_hfp_hf_cli(struct bt_hfp_hf *hf, bool enable) +{ +#if defined(CONFIG_BT_HFP_HF_CLI) + int err; + + LOG_DBG(""); + + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) + { + LOG_ERR("SLC is not established on %p", hf); + return -EINVAL; + } + + err = hfp_hf_send_cmd(hf, NULL, cli_finish, "AT+CLIP=%d", enable ? 1 : 0); + if (err < 0) + { + LOG_ERR("HFP HF CLI set failed on %p", hf); + } + + return err; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_CLI */ +} + +#if defined(CONFIG_BT_HFP_HF_VOLUME) +static int vgm_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("VGM set (result %d) on %p", result, hf); + + /* AT+VGM is done. */ + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + +int bt_hfp_hf_vgm(struct bt_hfp_hf *hf, uint8_t gain) +{ +#if defined(CONFIG_BT_HFP_HF_VOLUME) + int err; + + LOG_DBG(""); + + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (gain > BT_HFP_HF_VGM_GAIN_MAX) + { + LOG_ERR("Invalid gain %d>%d", gain, BT_HFP_HF_VGM_GAIN_MAX); + return -EINVAL; + } + + hf->vgm = gain; + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) + { + return 0; + } + + err = hfp_hf_send_cmd(hf, NULL, vgm_finish, "AT+VGM=%d", gain); + if (err < 0) + { + LOG_ERR("HFP HF VGM set failed on %p", hf); + } + + return err; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_VOLUME */ +} + +#if defined(CONFIG_BT_HFP_HF_VOLUME) +static int vgs_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("VGS set (result %d) on %p", result, hf); + + /* AT+VGS is done. */ + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + +int bt_hfp_hf_vgs(struct bt_hfp_hf *hf, uint8_t gain) +{ +#if defined(CONFIG_BT_HFP_HF_VOLUME) + int err; + + LOG_DBG(""); + + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (gain > BT_HFP_HF_VGM_GAIN_MAX) + { + LOG_ERR("Invalid gain %d>%d", gain, BT_HFP_HF_VGM_GAIN_MAX); + return -EINVAL; + } + + hf->vgs = gain; + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) + { + return 0; + } + + err = hfp_hf_send_cmd(hf, NULL, vgs_finish, "AT+VGS=%d", gain); + if (err < 0) + { + LOG_ERR("HFP HF VGS set failed on %p", hf); + } + + return err; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_VOLUME */ +} + +static int cops_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + uint32_t mode; + uint32_t format; + const char *operator; + int err; + + err = at_get_number(hf_at, &mode); + if (err < 0) + { + LOG_ERR("Error getting value"); + return err; + } + + err = at_get_number(hf_at, &format); + if (err < 0) + { + LOG_ERR("Error getting value"); + return err; + } + + operator = at_get_string(hf_at); + + if (bt_hf && bt_hf->operator) + { + bt_hf->operator(hf, (uint8_t)mode, (uint8_t)format, operator); + } + + return 0; +} + +static int cops_resp(struct at_client *hf_at, struct net_buf *buf) +{ + int err; + + LOG_DBG(""); + + err = at_parse_cmd_input(hf_at, buf, "COPS", cops_handle, + AT_CMD_TYPE_NORMAL); + if (err < 0) + { + LOG_ERR("Cannot parse response of AT+COPS?"); + return err; + } + + return 0; +} + +static int cops_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+COPS? (result %d) on %p", result, hf); + + return 0; +} + +int bt_hfp_hf_get_operator(struct bt_hfp_hf *hf) +{ + int err; + + LOG_DBG(""); + + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) + { + return 0; + } + + err = hfp_hf_send_cmd(hf, cops_resp, cops_finish, "AT+COPS?"); + if (err < 0) + { + LOG_ERR("Fail to read the currently selected operator on %p", hf); + } + + return err; +} + +static int binp_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + const char *number; + + number = at_get_string(hf_at); + + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_BINP); + + if (bt_hf && bt_hf->request_phone_number) + { + bt_hf->request_phone_number(hf, number); + } + + return 0; +} + +static int binp_resp(struct at_client *hf_at, struct net_buf *buf) +{ + int err; + + LOG_DBG(""); + + err = at_parse_cmd_input(hf_at, buf, "BINP", binp_handle, + AT_CMD_TYPE_NORMAL); + if (err < 0) + { + LOG_ERR("Cannot parse response of AT+BINP=1"); + return err; + } + + return 0; +} + +static int binp_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BINP=1 (result %d) on %p", result, hf); + + if (!atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_BINP)) + { + if (bt_hf && bt_hf->request_phone_number) + { + bt_hf->request_phone_number(hf, NULL); + } + } + + return 0; +} + +int bt_hfp_hf_request_phone_number(struct bt_hfp_hf *hf) +{ + int err; + + LOG_DBG(""); + + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) + { + return 0; + } + + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_BINP); + + err = hfp_hf_send_cmd(hf, binp_resp, binp_finish, "AT+BINP=1"); + if (err < 0) + { + LOG_ERR("Fail to request phone number to the AG on %p", hf); + } + + return err; +} + +static int vts_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+VTS (result %d) on %p", result, hf); + + return 0; +} + +int bt_hfp_hf_transmit_dtmf_code(struct bt_hfp_hf_call *call, char code) +{ + struct bt_hfp_hf *hf; + int err; + + LOG_DBG(""); + + if (!call) + { + LOG_ERR("Invalid call"); + return -ENOTCONN; + } + + hf = call->hf; + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) + { + LOG_ERR("SLC is not established on %p", hf); + return -ENOTCONN; + } + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING) || + (!(!atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING_HELD) && + (atomic_read(call->state) == BT_HFP_HF_CALL_STATE_ACTIVE)))) + { + LOG_ERR("Invalid call status"); + return -EINVAL; + } + + if (!IS_VALID_DTMF(code)) + { + LOG_ERR("Invalid code"); + return -EINVAL; + } + + err = hfp_hf_send_cmd(hf, NULL, vts_finish, "AT+VTS=%c", code); + if (err < 0) + { + LOG_ERR("Fail to tramsit DTMF Codes on %p", hf); + } + + return err; +} + +static int cnum_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+CNUM (result %d) on %p", result, hf); + + return 0; +} + +int bt_hfp_hf_query_subscriber(struct bt_hfp_hf *hf) +{ + int err; + + LOG_DBG(""); + + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) + { + LOG_ERR("SLC is not established on %p", hf); + return -ENOTCONN; + } + + err = hfp_hf_send_cmd(hf, NULL, cnum_finish, "AT+CNUM"); + if (err < 0) + { + LOG_ERR("Fail to query subscriber number information on %p", hf); + } + + return err; +} + +static int bia_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BIA (result %d) on %p", result, hf); + + return 0; +} + +int bt_hfp_hf_indicator_status(struct bt_hfp_hf *hf, uint8_t status) +{ + int err; + size_t index; + char buffer[HF_MAX_AG_INDICATORS * 2 + 1]; + char *bia_status; + + LOG_DBG(""); + + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) + { + LOG_ERR("SLC is not established on %p", hf); + return -ENOTCONN; + } + + bia_status = &buffer[0]; + for (index = 0; index < ARRAY_SIZE(hf->ind_table); index++) + { + if ((hf->ind_table[index] != HFP_HF_INDICATOR_INVALID) && + (index < NUM_BITS(sizeof(status)))) + { + if (status & BIT(hf->ind_table[index])) + { + *bia_status = '1'; + } + else + { + *bia_status = '0'; + } + + bia_status++; + *bia_status = ','; + bia_status++; + } + else + { + break; + } + } + + if (bia_status <= &buffer[0]) + { + LOG_ERR("Not found valid AG indicator on %p", hf); + return -EINVAL; + } + + bia_status--; + *bia_status = '\0'; + + err = hfp_hf_send_cmd(hf, NULL, bia_finish, "AT+BIA=%s", buffer); + if (err < 0) + { + LOG_ERR("Fail to activated/deactivated AG indicators on %p", hf); + } + + return err; +} + +#if defined(CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY) +static int biev_enh_safety_finish( + struct at_client *hf_at, enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BIEV (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY */ + +int bt_hfp_hf_enhanced_safety(struct bt_hfp_hf *hf, bool enable) +{ +#if defined(CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY) + int err; + + LOG_DBG(""); + + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) + { + LOG_ERR("SLC is not established on %p", hf); + return -ENOTCONN; + } + + if (!((hf->hf_ind & BIT(HFP_HF_ENHANCED_SAFETY_IND)) && + (hf->ag_ind & BIT(HFP_HF_ENHANCED_SAFETY_IND)))) + { + LOG_ERR("The indicator is unsupported"); + return -ENOTSUP; + } + + if (!(hf->ind_enable & BIT(HFP_HF_ENHANCED_SAFETY_IND))) + { + LOG_ERR("The indicator is disabled"); + return -EINVAL; + } + + err = hfp_hf_send_cmd(hf, NULL, biev_enh_safety_finish, "AT+BIEV=%d,%d", + HFP_HF_ENHANCED_SAFETY_IND, enable ? 1 : 0); + if (err < 0) + { + LOG_ERR("Fail to transfer enhanced safety value on %p", hf); + } + + return err; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_HF_INDICATOR_ENH_SAFETY */ +} + +#if defined(CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY) +static int biev_battery_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BIEV (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY */ + +int bt_hfp_hf_battery(struct bt_hfp_hf *hf, uint8_t level) +{ +#if defined(CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY) + int err; + + LOG_DBG(""); + + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED)) + { + LOG_ERR("SLC is not established on %p", hf); + return -ENOTCONN; + } + + if (!((hf->hf_ind & BIT(HFP_HF_BATTERY_LEVEL_IND)) && + (hf->ag_ind & BIT(HFP_HF_BATTERY_LEVEL_IND)))) + { + LOG_ERR("The indicator is unsupported"); + return -ENOTSUP; + } + + if (!(hf->ind_enable & BIT(HFP_HF_BATTERY_LEVEL_IND))) + { + LOG_ERR("The indicator is disabled"); + return -EINVAL; + } + + if (!IS_VALID_BATTERY_LEVEL(level)) + { + LOG_ERR("Invalid battery level %d", level); + return -EINVAL; + } + + err = hfp_hf_send_cmd(hf, NULL, biev_battery_finish, "AT+BIEV=%d,%d", + HFP_HF_BATTERY_LEVEL_IND, + level); + if (err < 0) + { + LOG_ERR("Fail to transfer remaining battery level on %p", hf); + } + + return err; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_HF_INDICATOR_BATTERY */ +} + +static int ata_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("ATA (result %d) on %p", result, hf); + + return 0; +} + +static int btrh_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BTRH (result %d) on %p", result, hf); + + return 0; +} + +int bt_hfp_hf_accept(struct bt_hfp_hf_call *call) +{ + int err; + struct bt_hfp_hf *hf; + int count; + + LOG_DBG(""); + + if (!call) + { + LOG_ERR("Invalid call"); + return -EINVAL; + } + + hf = call->hf; + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) + { + LOG_ERR("No valid call"); + return -EINVAL; + } + + count = get_using_call_count(call->hf); + if (count > 1) + { + LOG_ERR("Unsupported 3Way call"); + return -EINVAL; + } + + if (atomic_read(call->state) == BT_HFP_HF_CALL_STATE_WAITING) + { + err = hfp_hf_send_cmd(hf, NULL, ata_finish, "ATA"); + if (err < 0) + { + LOG_ERR("Fail to accept the incoming call on %p", hf); + } + + return err; + } + + if (atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING_HELD) && + (atomic_read(call->state) == BT_HFP_HF_CALL_STATE_ACTIVE)) + { + err = hfp_hf_send_cmd(hf, NULL, btrh_finish, "AT+BTRH=%d", + BT_HFP_BTRH_ACCEPTED); + if (err < 0) + { + LOG_ERR("Fail to accept the held incoming call on %p", hf); + } + + return err; + } + + return -EINVAL; +} + +static int chup_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+CHUP (result %d) on %p", result, hf); + + return 0; +} + +int bt_hfp_hf_reject(struct bt_hfp_hf_call *call) +{ + int err; + struct bt_hfp_hf *hf; + int count; + + LOG_DBG(""); + + if (!call) + { + LOG_ERR("Invalid call"); + return -EINVAL; + } + + hf = call->hf; + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) + { + LOG_ERR("No valid call"); + return -EINVAL; + } + + count = get_using_call_count(call->hf); + if (count > 1) + { + LOG_ERR("Unsupported 3Way call"); + return -EINVAL; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_REJECT_CALL)) + { + LOG_ERR("AG has not ability to reject call"); + return -ENOTSUP; + } + + if (atomic_read(call->state) == BT_HFP_HF_CALL_STATE_WAITING) + { + err = hfp_hf_send_cmd(hf, NULL, chup_finish, "AT+CHUP"); + if (err < 0) + { + LOG_ERR("Fail to reject the incoming call on %p", hf); + } + + return err; + } + + if (atomic_test_bit(call->flags, BT_HFP_HF_CALL_INCOMING_HELD) && + (atomic_read(call->state) == BT_HFP_HF_CALL_STATE_ACTIVE)) + { + err = hfp_hf_send_cmd(hf, NULL, btrh_finish, "AT+BTRH=%d", + BT_HFP_BTRH_REJECTED); + if (err < 0) + { + LOG_ERR("Fail to reject the held incoming call on %p", hf); + } + + return err; + } + + return -EINVAL; +} + +int bt_hfp_hf_terminate(struct bt_hfp_hf_call *call) +{ + int err; + struct bt_hfp_hf *hf; + int count; + + LOG_DBG(""); + + if (!call) + { + LOG_ERR("Invalid call"); + return -EINVAL; + } + + hf = call->hf; + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) + { + LOG_ERR("No valid call"); + return -EINVAL; + } + + count = get_using_call_count(call->hf); + if (count > 1) + { + LOG_ERR("Unsupported 3Way call"); + return -EINVAL; + } + + if (atomic_read(call->state) == BT_HFP_HF_CALL_STATE_HELD) + { + LOG_ERR("Held call cannot be terminated"); + return -EINVAL; + } + + err = hfp_hf_send_cmd(hf, NULL, chup_finish, "AT+CHUP"); + if (err < 0) + { + LOG_ERR("Fail to terminate the none held call on %p", hf); + } + + return err; +} + +int bt_hfp_hf_hold_incoming(struct bt_hfp_hf_call *call) +{ + int err; + struct bt_hfp_hf *hf; + int count; + + LOG_DBG(""); + + if (!call) + { + LOG_ERR("Invalid call"); + return -EINVAL; + } + + hf = call->hf; + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!atomic_test_bit(call->flags, BT_HFP_HF_CALL_IN_USING)) + { + LOG_ERR("No valid call"); + return -EINVAL; + } + + count = get_using_call_count(call->hf); + if (count > 1) + { + LOG_ERR("Unsupported 3Way call"); + return -EINVAL; + } + + if (!(atomic_read(call->state) == BT_HFP_HF_CALL_STATE_WAITING)) + { + LOG_ERR("No incoming call setup in progress"); + return -EINVAL; + } + + err = hfp_hf_send_cmd(hf, NULL, btrh_finish, "AT+BTRH=%d", + BT_HFP_BTRH_ON_HOLD); + if (err < 0) + { + LOG_ERR("Fail to hold the incoming call on %p", hf); + } + + return err; +} + +static int query_btrh_handle(struct at_client *hf_at) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; + struct bt_hfp_hf_call *call; + uint32_t value; + + err = at_get_number(hf_at, &value); + if (err < 0) + { + LOG_ERR("Cannot get value"); + return err; + } + + if (value) + { + LOG_ERR("Only support value 0"); + return 0; + } + + call = get_call_with_flag(hf, BT_HFP_HF_CALL_INCOMING_HELD); + if (!call) + { + LOG_ERR("Held incoming call is not found"); + return -EINVAL; + } + + if (bt_hf->incoming_held) + { + bt_hf->incoming_held(call); + } + + return 0; +} + +static int query_btrh_resp(struct at_client *hf_at, struct net_buf *buf) +{ + int err; + + err = at_parse_cmd_input(hf_at, buf, "BTRH", query_btrh_handle, + AT_CMD_TYPE_NORMAL); + if (err < 0) + { + LOG_ERR("Error parsing CMD input"); + hf_slc_error(hf_at); + } + + return 0; +} + +static int query_btrh_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BTRH? (result %d) on %p", result, hf); + + return 0; +} + +int bt_hfp_hf_query_respond_hold_status(struct bt_hfp_hf *hf) +{ + int err; + + LOG_DBG(""); + + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + err = hfp_hf_send_cmd(hf, query_btrh_resp, query_btrh_finish, "AT+BTRH?"); + if (err < 0) + { + LOG_ERR("Fail to query respond and hold status of AG on %p", hf); + } + + return err; +} + +static int bt_hfp_ag_get_cme_err(enum at_cme cme_err) +{ + int err; + + switch (cme_err) + { + case CME_ERROR_OPERATION_NOT_SUPPORTED: + err = -EOPNOTSUPP; + break; + case CME_ERROR_AG_FAILURE: + err = -EFAULT; + break; + case CME_ERROR_MEMORY_FAILURE: + err = -ENOSR; + break; + case CME_ERROR_MEMORY_FULL: + err = -ENOMEM; + break; + case CME_ERROR_DIAL_STRING_TO_LONG: + err = -ENAMETOOLONG; + break; + case CME_ERROR_INVALID_INDEX: + err = -EINVAL; + break; + case CME_ERROR_OPERATION_NOT_ALLOWED: + err = -ENOTSUP; + break; + case CME_ERROR_NO_CONNECTION_TO_PHONE: + err = -ENOTCONN; + break; + default: + err = -ENOTSUP; + break; + } + + return err; +} + +static int atd_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; + struct bt_hfp_hf_call *call; + + LOG_DBG("ATD (result %d) on %p", result, hf); + + if (result == AT_RESULT_CME_ERROR) + { + err = bt_hfp_ag_get_cme_err(cme_err); + } + else if (result == AT_RESULT_ERROR) + { + err = -ENOTSUP; + } + else + { + err = 0; + } + + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_OUTGOING); + if (!call) + { + return -EINVAL; + } + + if (err) + { + free_call(call); + } + + if (bt_hf && bt_hf->dialing) + { + bt_hf->dialing(hf, err); + } + + return 0; +} + +int bt_hfp_hf_number_call(struct bt_hfp_hf *hf, const char *number) +{ + struct bt_hfp_hf_call *call; + int err; + + LOG_DBG(""); + + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + call = get_dialing_call(hf); + if (call) + { + LOG_ERR("There is a call in alerting or waiting"); + return -EBUSY; + } + + call = get_new_call(hf); + if (!call) + { + LOG_ERR("Not available call object"); + return -ENOMEM; + } + + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_OUTGOING); + + err = hfp_hf_send_cmd(hf, NULL, atd_finish, "ATD%s;", number); + if (err < 0) + { + LOG_ERR("Fail to start phone number call on %p", hf); + } + + return err; +} + +int bt_hfp_hf_memory_dial(struct bt_hfp_hf *hf, const char *location) +{ + struct bt_hfp_hf_call *call; + int err; + + LOG_DBG(""); + + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + call = get_dialing_call(hf); + if (call) + { + LOG_ERR("There is a call in alerting or waiting"); + return -EBUSY; + } + + call = get_new_call(hf); + if (!call) + { + LOG_ERR("Not available call object"); + return -ENOMEM; + } + + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_OUTGOING); + + err = hfp_hf_send_cmd(hf, NULL, atd_finish, "ATD>%s;", location); + if (err < 0) + { + LOG_ERR("Fail to last number re-Dial on %p", hf); + } + + return err; +} + +static int bldn_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; + struct bt_hfp_hf_call *call; + + LOG_DBG("AT+BLDN (result %d) on %p", result, hf); + + if (result == AT_RESULT_CME_ERROR) + { + err = bt_hfp_ag_get_cme_err(cme_err); + } + else if (result == AT_RESULT_ERROR) + { + err = -ENOTSUP; + } + else + { + err = 0; + } + + call = get_call_with_state(hf, BT_HFP_HF_CALL_STATE_OUTGOING); + if (!call) + { + return -EINVAL; + } + + if (err) + { + free_call(call); + } + + if (bt_hf && bt_hf->dialing) + { + bt_hf->dialing(hf, err); + } + + return 0; +} + +int bt_hfp_hf_redial(struct bt_hfp_hf *hf) +{ + struct bt_hfp_hf_call *call; + int err; + + LOG_DBG(""); + + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + call = get_dialing_call(hf); + if (call) + { + LOG_ERR("There is a call in alerting or waiting"); + return -EBUSY; + } + + call = get_new_call(hf); + if (!call) + { + LOG_ERR("Not available call object"); + return -ENOMEM; + } + + hf_call_state_update(call, BT_HFP_HF_CALL_STATE_OUTGOING); + + err = hfp_hf_send_cmd(hf, NULL, bldn_finish, "AT+BLDN"); + if (err < 0) + { + LOG_ERR("Fail to start memory dialing on %p", hf); + } + + return err; +} + +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +static int bcc_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("BCC (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + +static void hfp_hf_sco_connected(struct bt_sco_chan *chan) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(chan, struct bt_hfp_hf, chan); + + if (atomic_ptr_cmpxchg(&hf->sco_conn, NULL, chan->sco)) + { + bt_conn_ref(chan->sco); + } + + if ((bt_hf != NULL) && (bt_hf->sco_connected != NULL)) + { + bt_hf->sco_connected(hf, chan->sco); + } +} + +static void hfp_hf_sco_disconnected(struct bt_sco_chan *chan, uint8_t reason) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(chan, struct bt_hfp_hf, chan); + struct bt_conn *sco; + + if ((bt_hf != NULL) && (bt_hf->sco_disconnected != NULL)) + { + bt_hf->sco_disconnected(chan->sco, reason); + } + + sco = atomic_ptr_xchg(&hf->sco_conn, NULL); + if (sco != NULL) + { + bt_conn_unref(sco); + } +} + +static int hfp_hf_set_voice_setting(struct bt_hfp_hf *hf) +{ + uint16_t air_coding_fmt; + + switch (hf->active_codec_id) + { + case BT_HFP_HF_CODEC_CVSD: + air_coding_fmt = BT_HCI_VOICE_SETTING_AIR_CODING_FMT_CVSD; + break; +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) + case BT_HFP_HF_CODEC_MSBC: + if (!IS_ENABLED(CONFIG_BT_HFP_HF_CODEC_MSBC)) + { + LOG_ERR("mSBC is not enabled"); + return -EINVAL; + } + + air_coding_fmt = BT_HCI_VOICE_SETTING_AIR_CODING_FMT_TRANSPARENT; + break; + case BT_HFP_HF_CODEC_LC3_SWB: + if (!IS_ENABLED(CONFIG_BT_HFP_HF_CODEC_LC3_SWB)) + { + LOG_ERR("LC3 SWB is not enabled"); + return -EINVAL; + } + + air_coding_fmt = BT_HCI_VOICE_SETTING_AIR_CODING_FMT_TRANSPARENT; + break; +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + default: + LOG_ERR("Unsupported codec ID %u", hf->active_codec_id); + return -EINVAL; + } + + hf->chan.voice_setting = BT_HCI_VOICE_SETTINGS( + air_coding_fmt, BT_HCI_VOICE_SETTING_PCM_BIT_POS_DEFAULT, + BT_HCI_VOICE_SETTING_SAMPLE_SIZE_16_BITS, + BT_HCI_VOICE_SETTING_DATA_FMT_2_COMPLEMENT, + BT_HCI_VOICE_SETTING_CODING_FMT_LINEAR); + + return 0; +} + +static int hfp_hf_create_sco(struct bt_hfp_hf *hf) +{ + static const struct bt_sco_chan_ops ops = + { + .connected = hfp_hf_sco_connected, + .disconnected = hfp_hf_sco_disconnected, + }; + + struct bt_conn *sco; + int err; + bool updated; + + LOG_DBG("Creating SCO connection"); + + hf->chan.ops = &ops; + + err = hfp_hf_set_voice_setting(hf); + if (err < 0) + { + LOG_ERR("Failed to set voice setting"); + return err; + } + + sco = bt_conn_create_sco(&hf->acl->br.dst, &hf->chan); + updated = atomic_ptr_cmpxchg(&hf->sco_conn, NULL, sco); + if (!updated) + { + LOG_WRN("SCO is not NULL (%p), target (%p)", + atomic_ptr_read(&hf->sco_conn), sco); + DEBUGASSERT(atomic_ptr_read(&hf->sco_conn) == sco); + /* The `hf->sco_conn` has been updated in callback + * `hfp_hf_sco_connected()`. The reference count has been updated in + * callback `hfp_hf_sco_connected()`. The reference count should be + * unreferred in this case. + */ + + if (sco != NULL) + { + bt_conn_unref(sco); + } + } + + if (sco == NULL) + { + LOG_ERR("Failed to create SCO"); + return -ENOMEM; + } + + return 0; +} + +int bt_hfp_hf_audio_connect(struct bt_hfp_hf *hf) +{ + int err; + + LOG_DBG(""); + + if (hf == NULL) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (atomic_ptr_read(&hf->sco_conn) != NULL) + { + LOG_ERR("Audio connection has been connected"); + return -ECONNREFUSED; + } + + if (!(BT_HFP_HF_FEAT_TEST(hf->ag_features, BT_HFP_AG_FEATURE_CODEC_NEG) && + BT_HFP_HF_FEAT_TEST(hf->hf_features, BT_HFP_HF_FEATURE_CODEC_NEG))) + { + err = hfp_hf_create_sco(hf); + return err; + } + +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) + err = hfp_hf_send_cmd(hf, NULL, bcc_finish, "AT+BCC"); + if (err < 0) + { + LOG_ERR("Fail to setup audio connection on %p", hf); + } + + return err; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ +} + +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +static int bcs_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("BCS (result %d) on %p", result, hf); + + if (result == AT_RESULT_OK) + { + hf->active_codec_id = hf->neg_codec_id; + } + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + +int bt_hfp_hf_select_codec(struct bt_hfp_hf *hf, uint8_t codec_id) +{ +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) + int err; + + LOG_DBG(""); + + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!(hf->hf_codec_ids & BIT(codec_id))) + { + LOG_ERR("Codec ID is unsupported"); + return -ENOTSUP; + } + + if (!atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_CODEC_CONN)) + { + LOG_ERR("Invalid context"); + return -ESRCH; + } + + err = hfp_hf_send_cmd(hf, NULL, bcs_finish, "AT+BCS=%d", codec_id); + if (err < 0) + { + LOG_ERR("Fail to select codec on %p", hf); + return err; + } + + hf->neg_codec_id = codec_id; + return 0; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ +} + +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +static int bac_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("BAC (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + +int bt_hfp_hf_set_codecs(struct bt_hfp_hf *hf, uint8_t codec_ids) +{ +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) + char ids[sizeof(hf->hf_codec_ids) * 2 * 8 + 1]; + + LOG_DBG(""); + + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (codec_ids & BT_HFP_HF_CODEC_CVSD) + { + LOG_ERR("CVSD should be supported"); + return -EINVAL; + } + + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_CODEC_CONN); + hf->hf_codec_ids = codec_ids; + + get_codec_ids(hf, &ids[0], ARRAY_SIZE(ids)); + return hfp_hf_send_cmd(hf, NULL, bac_finish, "AT+BAC=%s", ids); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ +} + +#if defined(CONFIG_BT_HFP_HF_ECNR) +static int nrec_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + int err; + + LOG_DBG("AT+NREC=0 (result %d) on %p", result, hf); + + if (result == AT_RESULT_CME_ERROR) + { + err = bt_hfp_ag_get_cme_err(cme_err); + } + else if (result == AT_RESULT_ERROR) + { + err = -ENOTSUP; + } + else + { + err = 0; + } + + if (bt_hf && bt_hf->ecnr_turn_off) + { + bt_hf->ecnr_turn_off(hf, err); + } + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_ECNR */ + +int bt_hfp_hf_turn_off_ecnr(struct bt_hfp_hf *hf) +{ +#if defined(CONFIG_BT_HFP_HF_ECNR) + LOG_DBG(""); + + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_ECNR)) + { + LOG_ERR("EC and/or NR functions is unsupported by AG"); + return -ENOTSUP; + } + + if (hf->chan.sco) + { + LOG_ERR("Audio connection has been connected"); + return -EBUSY; + } + + return hfp_hf_send_cmd(hf, NULL, nrec_finish, "AT+NREC=0"); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_ECNR */ +} + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int ccwa_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+CCWA (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +int bt_hfp_hf_call_waiting_notify(struct bt_hfp_hf *hf, bool enable) +{ +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + LOG_DBG(""); + + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) + { + LOG_ERR("Three-way calling is unsupported by AG"); + return -ENOTSUP; + } + + return hfp_hf_send_cmd(hf, NULL, ccwa_finish, "AT+CCWA=%d", enable ? 1 : + 0); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +} + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int chld_release_all_held_finish( + struct at_client *hf_at, enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+CHLD=0 (result %d) on %p", result, hf); + + k_work_reschedule(&hf->deferred_work, + MSEC2TICK(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +int bt_hfp_hf_release_all_held(struct bt_hfp_hf *hf) +{ +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + LOG_DBG(""); + + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) + { + LOG_ERR("Three-way calling is unsupported by AG"); + return -ENOTSUP; + } + + if (!(hf->chld_features & BIT(BT_HFP_CHLD_RELEASE_ALL))) + { + LOG_ERR("Releasing all held calls is unsupported by AG"); + return -ENOTSUP; + } + + return hfp_hf_send_cmd(hf, NULL, chld_release_all_held_finish, + "AT+CHLD=0"); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +} + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int chld_set_udub_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+CHLD=0 (result %d) on %p", result, hf); + + k_work_reschedule(&hf->deferred_work, + MSEC2TICK(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +int bt_hfp_hf_set_udub(struct bt_hfp_hf *hf) +{ +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + LOG_DBG(""); + + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) + { + LOG_ERR("Three-way calling is unsupported by AG"); + return -ENOTSUP; + } + + if (!(hf->chld_features & BIT(BT_HFP_CHLD_RELEASE_ALL))) + { + LOG_ERR("UDUB is unsupported by AG"); + return -ENOTSUP; + } + + return hfp_hf_send_cmd(hf, NULL, chld_set_udub_finish, "AT+CHLD=0"); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +} + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int chld_release_active_accept_other_finish( + struct at_client *hf_at, enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+CHLD=1 (result %d) on %p", result, hf); + + k_work_reschedule(&hf->deferred_work, + MSEC2TICK(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +int bt_hfp_hf_release_active_accept_other(struct bt_hfp_hf *hf) +{ +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + LOG_DBG(""); + + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) + { + LOG_ERR("Three-way calling is unsupported by AG"); + return -ENOTSUP; + } + + return hfp_hf_send_cmd(hf, NULL, chld_release_active_accept_other_finish, + "AT+CHLD=1"); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +} + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int chld_hold_active_accept_other_finish( + struct at_client *hf_at, enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+CHLD=2 (result %d) on %p", result, hf); + + k_work_reschedule(&hf->deferred_work, + MSEC2TICK(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +int bt_hfp_hf_hold_active_accept_other(struct bt_hfp_hf *hf) +{ +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + LOG_DBG(""); + + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) + { + LOG_ERR("Three-way calling is unsupported by AG"); + return -ENOTSUP; + } + + return hfp_hf_send_cmd(hf, NULL, chld_hold_active_accept_other_finish, + "AT+CHLD=2"); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +} + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int chld_join_conversation_finish( + struct at_client *hf_at, enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+CHLD=3 (result %d) on %p", result, hf); + + k_work_reschedule(&hf->deferred_work, + MSEC2TICK(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +int bt_hfp_hf_join_conversation(struct bt_hfp_hf *hf) +{ +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + LOG_DBG(""); + + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) + { + LOG_ERR("Three-way calling is unsupported by AG"); + return -ENOTSUP; + } + + if (!(hf->chld_features & BIT(BT_HFP_CALL_ACTIVE_HELD))) + { + LOG_ERR("Adding a held call to the conversation is unsupported by AG"); + return -ENOTSUP; + } + + return hfp_hf_send_cmd(hf, NULL, chld_join_conversation_finish, + "AT+CHLD=3"); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +} + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +static int chld_explicit_call_transfer_finish( + struct at_client *hf_at, enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+CHLD=4 (result %d) on %p", result, hf); + + k_work_reschedule(&hf->deferred_work, + MSEC2TICK(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +int bt_hfp_hf_explicit_call_transfer(struct bt_hfp_hf *hf) +{ +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) + LOG_DBG(""); + + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) + { + LOG_ERR("Three-way calling is unsupported by AG"); + return -ENOTSUP; + } + + if (!(hf->chld_features & BIT(BT_HFP_CALL_QUITE))) + { + LOG_ERR("Expliciting Call Transfer is unsupported by AG"); + return -ENOTSUP; + } + + return hfp_hf_send_cmd(hf, NULL, chld_explicit_call_transfer_finish, + "AT+CHLD=4"); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ +} + +#if defined(CONFIG_BT_HFP_HF_ECC) +static int chld_release_specified_call_finish( + struct at_client *hf_at, enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+CHLD=1 (result %d) on %p", result, hf); + + k_work_reschedule(&hf->deferred_work, + MSEC2TICK(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_ECC */ + +int bt_hfp_hf_release_specified_call(struct bt_hfp_hf_call *call) +{ +#if defined(CONFIG_BT_HFP_HF_ECC) + struct bt_hfp_hf *hf; + + LOG_DBG(""); + + if (!call) + { + LOG_ERR("Invalid call"); + return -ENOTCONN; + } + + hf = call->hf; + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) + { + LOG_ERR("Three-way calling is unsupported by AG"); + return -ENOTSUP; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_ECC)) + { + LOG_ERR("Enhanced Call Control is unsupported by AG"); + return -ENOTSUP; + } + + if (!(hf->hf_features & BT_HFP_HF_FEATURE_ECC)) + { + LOG_ERR("Enhanced Call Control is unsupported by HF"); + return -ENOTSUP; + } + + if (!(hf->chld_features & BIT(BT_HFP_CALL_RELEASE_SPECIFIED_ACTIVE))) + { + LOG_ERR("Releasing a specific active call is unsupported by AG"); + return -ENOTSUP; + } + + if (!call->index) + { + LOG_ERR("Invalid call index"); + return -EINVAL; + } + + return hfp_hf_send_cmd(hf, NULL, chld_release_specified_call_finish, + "AT+CHLD=1%d", call->index); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_ECC */ +} + +#if defined(CONFIG_BT_HFP_HF_ECC) +static int chld_private_consultation_mode_finish( + struct at_client *hf_at, enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+CHLD=2 (result %d) on %p", result, hf); + + k_work_reschedule(&hf->deferred_work, + MSEC2TICK(HF_ENHANCED_CALL_STATUS_TIMEOUT)); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_ECC */ + +int bt_hfp_hf_private_consultation_mode(struct bt_hfp_hf_call *call) +{ +#if defined(CONFIG_BT_HFP_HF_ECC) + struct bt_hfp_hf *hf; + + LOG_DBG(""); + + if (!call) + { + LOG_ERR("Invalid call"); + return -ENOTCONN; + } + + hf = call->hf; + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_3WAY_CALL)) + { + LOG_ERR("Three-way calling is unsupported by AG"); + return -ENOTSUP; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_ECC)) + { + LOG_ERR("Enhanced Call Control is unsupported by AG"); + return -ENOTSUP; + } + + if (!(hf->hf_features & BT_HFP_HF_FEATURE_ECC)) + { + LOG_ERR("Enhanced Call Control is unsupported by HF"); + return -ENOTSUP; + } + + if (!(hf->chld_features & BIT(BT_HFP_CALL_PRIVATE_CNLTN_MODE))) + { + LOG_ERR("Private Consultation Mode is unsupported by AG"); + return -ENOTSUP; + } + + if (!call->index) + { + LOG_ERR("Invalid call index"); + return -EINVAL; + } + + return hfp_hf_send_cmd(hf, NULL, chld_private_consultation_mode_finish, + "AT+CHLD=2%d", + call->index); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_ECC */ +} + +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG) +static int bvra_1_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BVRA=1 (result %d) on %p", result, hf); + + if (result == AT_RESULT_OK) + { + if (!atomic_test_and_set_bit(hf->flags, BT_HFP_HF_FLAG_VRE_ACTIVATE)) + { + if (bt_hf->voice_recognition) + { + bt_hf->voice_recognition(hf, true); + } + } + } + + return 0; +} + +static int bvra_0_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BVRA=0 (result %d) on %p", result, hf); + + if (atomic_test_and_clear_bit(hf->flags, BT_HFP_HF_FLAG_VRE_ACTIVATE)) + { + if (bt_hf->voice_recognition) + { + bt_hf->voice_recognition(hf, false); + } + } + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ + +int bt_hfp_hf_voice_recognition(struct bt_hfp_hf *hf, bool activate) +{ +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG) + at_finish_cb_t finish; + + LOG_DBG(""); + + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_VOICE_RECG)) + { + LOG_ERR("Voice recognition is unsupported by AG"); + return -ENOTSUP; + } + + if (activate) + { + finish = bvra_1_finish; + } + else + { + finish = bvra_0_finish; + } + + return hfp_hf_send_cmd(hf, NULL, finish, "AT+BVRA=%d", activate ? 1 : 0); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ +} + +#if defined(CONFIG_BT_HFP_HF_ENH_VOICE_RECG) +static int bvra_2_finish(struct at_client *hf_at, + enum at_result result, enum at_cme cme_err) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(hf_at, struct bt_hfp_hf, at); + + LOG_DBG("AT+BVRA=2 (result %d) on %p", result, hf); + + return 0; +} +#endif /* CONFIG_BT_HFP_HF_ENH_VOICE_RECG */ + +int bt_hfp_hf_ready_to_accept_audio(struct bt_hfp_hf *hf) +{ +#if defined(CONFIG_BT_HFP_HF_ENH_VOICE_RECG) + LOG_DBG(""); + + if (!hf) + { + LOG_ERR("No HF connection found"); + return -ENOTCONN; + } + + if (!(hf->ag_features & BT_HFP_AG_FEATURE_VOICE_RECG)) + { + LOG_ERR("Voice recognition is unsupported by AG"); + return -ENOTSUP; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_VRE_ACTIVATE)) + { + LOG_ERR("Voice recognition is not activated"); + return -EINVAL; + } + + if (!hf->chan.sco) + { + LOG_ERR("SCO channel is not ready"); + return -ENOTCONN; + } + + return hfp_hf_send_cmd(hf, NULL, bvra_2_finish, "AT+BVRA=2"); +#else + return -ENOTSUP; +#endif /* CONFIG_BT_HFP_HF_ENH_VOICE_RECG */ +} + +static void hfp_hf_connected(struct bt_rfcomm_dlc *dlc) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(dlc, struct bt_hfp_hf, rfcomm_dlc); + + LOG_DBG("hf connected"); + + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_DLC_CONNECTED); + k_work_submit(&hf->slc_work); +} + +static void hfp_hf_disconnected(struct bt_rfcomm_dlc *dlc) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(dlc, struct bt_hfp_hf, rfcomm_dlc); + struct net_buf *buf; + + LOG_DBG("hf disconnected!"); + if (bt_hf->disconnected) + { + bt_hf->disconnected(hf); + } + + /* Stop the senders before draining the TX queue below: with the flag + * cleared, command senders are rejected with -ENOTCONN instead of queueing + * buffers that nothing will ever send. + */ + + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_CONNECTED); + + k_work_cancel(&hf->work); + k_work_cancel(&hf->slc_work); + k_work_cancel_delayable(&hf->deferred_work); + + /* Drop queued TX buffers. Nothing will send them any more, and + * leaving them on the FIFO would leak them from hf_pool for good: + * hfp_hf_create() wipes the FIFO with memset() when the slot is reused. + * Freeing them also unblocks a work handler that is stuck in the + * ((clock_t)-1) allocation in hfp_hf_send_cmd() waiting for the exhausted + * pool to be refilled. + */ + + buf = k_fifo_get(&hf->tx_pending, 0); + while (buf != NULL) + { + net_buf_unref(buf); + buf = k_fifo_get(&hf->tx_pending, 0); + } + + hf->acl = NULL; +} + +static int hfp_hf_recv(struct bt_rfcomm_dlc *dlc, struct net_buf *buf) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(dlc, struct bt_hfp_hf, rfcomm_dlc); + + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_RX_ONGOING); + if (at_parse_input(&hf->at, buf) < 0) + { + LOG_ERR("Parsing failed"); + } + + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_RX_ONGOING); + k_work_submit(&hf->work); + + return 0; +} + +static void hfp_hf_sent(struct bt_rfcomm_dlc *dlc, int err) +{ + LOG_DBG("DLC %p sent cb (err %d)", dlc, err); +} + +static void bt_hf_work(struct k_work *work) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(work, struct bt_hfp_hf, work); + + hfp_hf_send_data(hf); +} + +static void bt_hf_slc_work(struct k_work *work) +{ + struct bt_hfp_hf *hf = CONTAINER_OF(work, struct bt_hfp_hf, slc_work); + int err; + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_DISCOVER_DONE)) + { + return; + } + + if (!atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_DLC_CONNECTED)) + { + return; + } + + if (atomic_test_and_set_bit(hf->flags, BT_HFP_HF_FLAG_FEAT_UPDATED)) + { + return; + } + + if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_RECORD_FOUND)) + { + hf_slc_establish(hf); + return; + } + + err = bt_hfp_hf_disconnect(hf); + if (err != 0) + { + LOG_ERR("Failed to disconnect AG: %d", err); + } +} + +static uint8_t bt_hfp_hf_discover_cb( + struct bt_conn *conn, struct bt_sdp_client_result *result, + const struct bt_sdp_discover_params *params) +{ + size_t index; + struct bt_hfp_hf *hf; + int err; + + index = (size_t)bt_conn_index(conn); + DEBUGASSERT(index < ARRAY_SIZE(bt_hfp_hf_pool)); + + hf = &bt_hfp_hf_pool[index]; + + if (hf->acl == NULL) + { + /* The HF object was released by hfp_hf_disconnected() while + * the discovery was still ongoing. Do not touch the object state or + * re-submit any work in that case. + */ + + LOG_WRN("HF %p released before discovery completed", hf); + return BT_SDP_DISCOVER_UUID_STOP; + } + + if ((result == NULL) || (result->resp_buf == NULL)) + { + LOG_ERR("SDP discovery failed"); + goto failed; + } + + err = + bt_sdp_get_profile_version(result->resp_buf, BT_SDP_HANDSFREE_SVCLASS, + &hf->ag_sdp_version); + if (err != 0) + { + LOG_ERR("Failed to get AG profile version"); + goto failed; + } + + err = bt_sdp_get_features(result->resp_buf, &hf->ag_sdp_features); + if (err != 0) + { + LOG_ERR("Failed to get AG feature"); + goto failed; + } + + if ((hf->ag_sdp_version <= BT_HFP_VERSION_1_5) || + (BT_HFP_HF_VERSION <= BT_HFP_VERSION_1_5)) + { + if (hf->ag_sdp_features & BT_HFP_AG_SDP_FEATURE_WBS) + { + LOG_WRN("Unsupported SDP feature (WBS) is enabled."); + hf->ag_sdp_features &= ~BT_HFP_AG_SDP_FEATURE_WBS; + } + + if (hf->ag_sdp_features & BT_HFP_AG_SDP_FEATURE_SUPER_WBS) + { + LOG_WRN("Unsupported SDP feature (Super WBS) is enabled."); + hf->ag_sdp_features &= ~BT_HFP_AG_SDP_FEATURE_SUPER_WBS; + } + } + + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_RECORD_FOUND); +failed: + atomic_set_bit(hf->flags, BT_HFP_HF_FLAG_DISCOVER_DONE); + if (atomic_test_bit(hf->flags, BT_HFP_HF_FLAG_RELEASING)) + { + /* The RFCOMM connection creation failed and the object was + * kept allocated only to keep the SDP discovery request valid. Release + * the object now that the discovery has completed. + */ + + hf->acl = NULL; + return BT_SDP_DISCOVER_UUID_STOP; + } + + k_work_submit(&hf->slc_work); + + return BT_SDP_DISCOVER_UUID_STOP; +} + +static struct bt_hfp_hf *hfp_hf_create(struct bt_conn *conn) +{ + size_t index; + struct bt_hfp_hf *hf; + int err; + + static struct bt_rfcomm_dlc_ops ops = + { + .connected = hfp_hf_connected, + .disconnected = hfp_hf_disconnected, + .recv = hfp_hf_recv, + .sent = hfp_hf_sent, + }; + + static struct bt_sdp_attribute_id_range id_range[] = + { + { + BT_SDP_ATTR_PROTO_DESC_LIST, + BT_SDP_ATTR_PROTO_DESC_LIST + }, + { + BT_SDP_ATTR_PROFILE_DESC_LIST, + BT_SDP_ATTR_PROFILE_DESC_LIST + }, + { + BT_SDP_ATTR_SUPPORTED_FEATURES, + BT_SDP_ATTR_SUPPORTED_FEATURES + }, + }; + + static struct bt_sdp_attribute_id_list id_list = + { + .count = ARRAY_SIZE(id_range), + .ranges = id_range, + }; + + static struct bt_uuid_16 uuid; + + LOG_DBG("conn %p", conn); + + index = (size_t)bt_conn_index(conn); + DEBUGASSERT(index < ARRAY_SIZE(bt_hfp_hf_pool)); + + hf = &bt_hfp_hf_pool[index]; + if (hf->acl) + { + LOG_ERR("HF connection (%p) is established", conn); + return NULL; + } + + /* The work items are canceled on disconnect, but a handler that was + * already running at that point may still be executing: cancellation does + * not wait for a running handler, and the handlers can block for a long + * time in the ((clock_t)-1) buffer allocation in hfp_hf_send_cmd(). Wiping + * the object under a running handler would corrupt the work items and the + * TX FIFO, so refuse to reuse the object until the handlers have finished. + * This is a transient failure: retry once the pending work has drained. + */ + + if ((k_work_busy_get(&hf->work) != 0) || + (k_work_busy_get(&hf->slc_work) != 0) || + (k_work_delayable_busy_get(&hf->deferred_work) != 0)) + { + LOG_WRN("Work of HF %p is still pending or running", hf); + return NULL; + } + + memset(hf, 0, sizeof(*hf)); + + uuid.uuid.type = BT_UUID_TYPE_16; + uuid.val = BT_SDP_HANDSFREE_AGW_SVCLASS; + + hf->sdp_param.func = bt_hfp_hf_discover_cb; + hf->sdp_param.type = BT_SDP_DISCOVER_SERVICE_SEARCH_ATTR; + hf->sdp_param.uuid = &uuid.uuid; + hf->sdp_param.pool = &hf_pool; + hf->sdp_param.ids = &id_list; + + hf->acl = conn; + + err = bt_sdp_discover(conn, &hf->sdp_param); + if (err != 0) + { + hf->acl = NULL; + return NULL; + } + + hf->rfcomm_dlc.ops = &ops; + hf->rfcomm_dlc.mtu = BT_HFP_MAX_MTU; + + /* Set the supported features */ + + hf->hf_features = BT_HFP_HF_SUPPORTED_FEATURES; + hf->hf_features |= + BT_FEAT_SC(bt_dev.features) ? BT_HFP_HF_FEATURE_ESCO_S4 : 0; + + /* Set supported codec ids */ + + hf->hf_codec_ids = BT_HFP_HF_SUPPORTED_CODEC_IDS; + hf->active_codec_id = BT_HFP_HF_CODEC_CVSD; + + /* Set the default AG infrmation */ + + hf->ag_sdp_features = + BT_HFP_AG_SDP_FEATURE_3WAY_CALL | + BT_HFP_AG_SDP_FEATURE_INBAND_RINGTONE; + hf->ag_sdp_version = BT_HFP_VERSION_0_96; + + k_fifo_init(&hf->tx_pending); + + k_work_init(&hf->slc_work, bt_hf_slc_work); + + k_work_init(&hf->work, bt_hf_work); + + k_work_init_delayable(&hf->deferred_work, bt_hf_deferred_work); + + ARRAY_FOR_EACH(hf->ind_table, i) + { + hf->ind_table[i] = HFP_HF_INDICATOR_INVALID; + } + + return hf; +} + +static int hfp_hf_accept( + struct bt_conn *conn, struct bt_rfcomm_server *server, + struct bt_rfcomm_dlc **dlc) +{ + struct bt_hfp_hf *hf; + + hf = hfp_hf_create(conn); + + if (!hf) + { + return -ECONNREFUSED; + } + + *dlc = &hf->rfcomm_dlc; + + return 0; +} + +static int bt_hfp_hf_sco_accept(const struct bt_sco_accept_info *info, + struct bt_sco_chan **chan) +{ + static const struct bt_sco_chan_ops ops = + { + .connected = hfp_hf_sco_connected, + .disconnected = hfp_hf_sco_disconnected, + }; + + size_t index; + struct bt_hfp_hf *hf; + int err; + + LOG_DBG("conn %p", info->acl); + + index = (size_t)bt_conn_index(info->acl); + DEBUGASSERT(index < ARRAY_SIZE(bt_hfp_hf_pool)); + + hf = &bt_hfp_hf_pool[index]; + if (hf->acl != info->acl) + { + LOG_ERR("ACL %p of HF is unaligned with SCO's %p", hf->acl, info->acl); + return -EINVAL; + } + + hf->chan.ops = &ops; + + err = hfp_hf_set_voice_setting(hf); + if (err < 0) + { + LOG_ERR("Fail to set voice setting"); + return err; + } + + *chan = &hf->chan; + + return 0; +} + +static void hf_sco_connected(struct bt_conn *conn, uint8_t err) +{ + ARG_UNUSED(conn); + ARG_UNUSED(err); +} + +static void hf_sco_disconnected(struct bt_conn *conn, uint8_t reason) +{ + ARG_UNUSED(reason); + + DEBUGASSERT(conn != NULL); + + ARRAY_FOR_EACH(bt_hfp_hf_pool, i) + { + if (atomic_ptr_cmpxchg(&bt_hfp_hf_pool[i].sco_conn, conn, NULL)) + { + bt_conn_unref(conn); + } + } +} + +static struct bt_sco_conn_cb hf_sco_conn_cb = +{ + .connected = hf_sco_connected, + .disconnected = + hf_sco_disconnected +}; + +static void hfp_hf_init(void) +{ + static struct bt_rfcomm_server chan = + { + .channel = BT_RFCOMM_CHAN_HFP_HF, + .accept = hfp_hf_accept, + }; + + bt_rfcomm_server_register(&chan); + + static struct bt_sco_server sco_server = + { + .sec_level = BT_SECURITY_L0, + .accept = bt_hfp_hf_sco_accept, + }; + + bt_sco_server_register(&sco_server); + + bt_sdp_register_service(&hfp_rec); + + bt_sco_conn_cb_register(&hf_sco_conn_cb); +} + +int bt_hfp_hf_register(struct bt_hfp_hf_cb *cb) +{ + if (!cb) + { + return -EINVAL; + } + + if (bt_hf) + { + return -EALREADY; + } + + bt_hf = cb; + + hfp_hf_init(); + + return 0; +} + +int bt_hfp_hf_connect(struct bt_conn *conn, + struct bt_hfp_hf **hf, uint8_t channel) +{ + struct bt_hfp_hf *new_hf; + int err; + + if (!conn || !hf || !channel) + { + return -EINVAL; + } + + if (!bt_hf) + { + return -EFAULT; + } + + new_hf = hfp_hf_create(conn); + if (!new_hf) + { + return -ECONNREFUSED; + } + + err = bt_rfcomm_dlc_connect(conn, &new_hf->rfcomm_dlc, channel); + if (err != 0) + { + /* The SDP discovery request started by hfp_hf_create() is + * linked in the SDP client's request list, so the object cannot be + * wiped here. Mark the object for release and let the SDP discovery + * callback release it. If the discovery has already completed, release + * the object immediately. + */ + + atomic_set_bit(new_hf->flags, BT_HFP_HF_FLAG_RELEASING); + if (atomic_test_bit(new_hf->flags, BT_HFP_HF_FLAG_DISCOVER_DONE)) + { + k_work_cancel(&new_hf->slc_work); + new_hf->acl = NULL; + } + + *hf = NULL; + } + else + { + *hf = new_hf; + } + + return err; +} + +int bt_hfp_hf_disconnect(struct bt_hfp_hf *hf) +{ + LOG_DBG(""); + + if (!hf) + { + return -EINVAL; + } + + return bt_rfcomm_dlc_disconnect(&hf->rfcomm_dlc); +} + +int bt_hfp_hf_query_list_of_current_calls(struct bt_hfp_hf *hf) +{ + int err; + + if ((hf == NULL) || (bt_hf == NULL) || (bt_hf->query_call == NULL)) + { + return -EINVAL; + } + + if (!IS_ENABLED(CONFIG_BT_HFP_HF_ECS)) + { + return -ENOTSUP; + } + + if (atomic_test_and_set_bit(hf->flags, BT_HFP_HF_FLAG_USR_CLCC_PND)) + { + return -EBUSY; + } + + err = hf_query_current_calls(hf); + if (err != 0) + { + atomic_clear_bit(hf->flags, BT_HFP_HF_FLAG_USR_CLCC_PND); + LOG_ERR("Failed to query current calls, err %d", err); + } + + return err; +} diff --git a/libs/zblue/host/classic/hfp_hf_internal.h b/libs/zblue/host/classic/hfp_hf_internal.h new file mode 100644 index 0000000000000..6ef32b65d68d2 --- /dev/null +++ b/libs/zblue/host/classic/hfp_hf_internal.h @@ -0,0 +1,291 @@ +/**************************************************************************** + * libs/zblue/host/classic/hfp_hf_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Internal APIs for Bluetooth Handsfree profile handling. + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include "hfp_internal.h" + +#if defined(CONFIG_BT_HFP_HF_CLI) +#define BT_HFP_HF_FEATURE_CLI_ENABLE BT_HFP_HF_FEATURE_CLI +#define BT_HFP_HF_SDP_FEATURE_CLI_ENABLE BT_HFP_HF_SDP_FEATURE_CLI +#else +#define BT_HFP_HF_FEATURE_CLI_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_CLI_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_CLI */ + +#if defined(CONFIG_BT_HFP_HF_VOLUME) +#define BT_HFP_HF_FEATURE_VOLUME_ENABLE BT_HFP_HF_FEATURE_VOLUME +#define BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE BT_HFP_HF_SDP_FEATURE_VOLUME +#else +#define BT_HFP_HF_FEATURE_VOLUME_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_VOLUME */ + +#if defined(CONFIG_BT_HFP_HF_CODEC_NEG) +#define BT_HFP_HF_FEATURE_CODEC_NEG_ENABLE BT_HFP_HF_FEATURE_CODEC_NEG +#else +#define BT_HFP_HF_FEATURE_CODEC_NEG_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + +#if defined(CONFIG_BT_HFP_HF_ECNR) +#define BT_HFP_HF_FEATURE_ECNR_ENABLE BT_HFP_HF_FEATURE_ECNR +#define BT_HFP_HF_SDP_FEATURE_ECNR_ENABLE BT_HFP_HF_SDP_FEATURE_ECNR +#else +#define BT_HFP_HF_FEATURE_ECNR_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_ECNR_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_CODEC_NEG */ + +#if defined(CONFIG_BT_HFP_HF_3WAY_CALL) +#define BT_HFP_HF_FEATURE_3WAY_CALL_ENABLE BT_HFP_HF_FEATURE_3WAY_CALL +#define BT_HFP_HF_SDP_FEATURE_3WAY_CALL_ENABLE BT_HFP_HF_SDP_FEATURE_3WAY_CALL +#else +#define BT_HFP_HF_FEATURE_3WAY_CALL_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_3WAY_CALL_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_3WAY_CALL */ + +#if defined(CONFIG_BT_HFP_HF_ECS) +#define BT_HFP_HF_FEATURE_ECS_ENABLE BT_HFP_HF_FEATURE_ECS +#else +#define BT_HFP_HF_FEATURE_ECS_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_ECS */ + +#if defined(CONFIG_BT_HFP_HF_ECC) +#define BT_HFP_HF_FEATURE_ECC_ENABLE BT_HFP_HF_FEATURE_ECC +#else +#define BT_HFP_HF_FEATURE_ECC_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_ECC */ + +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG) +#define BT_HFP_HF_FEATURE_VOICE_RECG_ENABLE BT_HFP_HF_FEATURE_VOICE_RECG +#define BT_HFP_HF_SDP_FEATURE_VOICE_RECG_ENABLE BT_HFP_HF_SDP_FEATURE_VOICE_RECG +#else +#define BT_HFP_HF_FEATURE_VOICE_RECG_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_VOICE_RECG_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG */ + +#if defined(CONFIG_BT_HFP_HF_ENH_VOICE_RECG) +#define BT_HFP_HF_FEATURE_ENH_VOICE_RECG_ENABLE BT_HFP_HF_FEATURE_ENH_VOICE_RECG +#define BT_HFP_HF_SDP_FEATURE_ENH_VOICE_RECG_ENABLE BT_HFP_HF_SDP_FEATURE_ENH_VOICE_RECG +#else +#define BT_HFP_HF_FEATURE_ENH_VOICE_RECG_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_ENH_VOICE_RECG_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_ENH_VOICE_RECG */ + +#if defined(CONFIG_BT_HFP_HF_VOICE_RECG_TEXT) +#define BT_HFP_HF_FEATURE_VOICE_RECG_TEXT_ENABLE BT_HFP_HF_FEATURE_VOICE_RECG_TEXT +#define BT_HFP_HF_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE BT_HFP_HF_SDP_FEATURE_VOICE_RECG_TEXT +#else +#define BT_HFP_HF_FEATURE_VOICE_RECG_TEXT_ENABLE 0 +#define BT_HFP_HF_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_VOICE_RECG_TEXT */ + +#if defined(CONFIG_BT_HFP_HF_HF_INDICATORS) +#define BT_HFP_HF_FEATURE_HF_IND_ENABLE BT_HFP_HF_FEATURE_HF_IND +#else +#define BT_HFP_HF_FEATURE_HF_IND_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_HF_INDICATORS */ + +#if defined(CONFIG_BT_HFP_HF_CODEC_MSBC) +#define BT_HFP_HF_SDP_FEATURE_WBS_ENABLE BT_HFP_HF_SDP_FEATURE_WBS +#else +#define BT_HFP_HF_SDP_FEATURE_WBS_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_CODEC_MSBC */ + +#if defined(CONFIG_BT_HFP_HF_CODEC_LC3_SWB) +#define BT_HFP_HF_SDP_FEATURE_SUPER_WBS_ENABLE BT_HFP_HF_SDP_FEATURE_SUPER_WBS +#else +#define BT_HFP_HF_SDP_FEATURE_SUPER_WBS_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_CODEC_LC3_SWB */ + +/* HFP HF Supported features */ +#define BT_HFP_HF_SUPPORTED_FEATURES \ + (BT_HFP_HF_FEATURE_CLI_ENABLE | BT_HFP_HF_FEATURE_VOLUME_ENABLE | \ + BT_HFP_HF_FEATURE_CODEC_NEG_ENABLE | BT_HFP_HF_FEATURE_ECNR_ENABLE | \ + BT_HFP_HF_FEATURE_3WAY_CALL_ENABLE | BT_HFP_HF_FEATURE_ECS_ENABLE | \ + BT_HFP_HF_FEATURE_ECC_ENABLE | BT_HFP_HF_FEATURE_VOICE_RECG_ENABLE | \ + BT_HFP_HF_FEATURE_ENH_VOICE_RECG_ENABLE | BT_HFP_HF_FEATURE_VOICE_RECG_TEXT_ENABLE | \ + BT_HFP_HF_FEATURE_HF_IND_ENABLE) + +/* HFP HF Supported features in SDP */ +#define BT_HFP_HF_SDP_SUPPORTED_FEATURES \ + (BT_HFP_HF_SDP_FEATURE_CLI_ENABLE | BT_HFP_HF_SDP_FEATURE_VOLUME_ENABLE | \ + BT_HFP_HF_SDP_FEATURE_ECNR_ENABLE | BT_HFP_HF_SDP_FEATURE_3WAY_CALL_ENABLE | \ + BT_HFP_HF_SDP_FEATURE_VOICE_RECG_ENABLE | BT_HFP_HF_SDP_FEATURE_ENH_VOICE_RECG_ENABLE | \ + BT_HFP_HF_SDP_FEATURE_VOICE_RECG_TEXT_ENABLE | BT_HFP_HF_SDP_FEATURE_WBS_ENABLE | \ + BT_HFP_HF_SDP_FEATURE_SUPER_WBS_ENABLE) + +#define BT_HFP_HF_CODEC_CVSD_MASK BIT(BT_HFP_HF_CODEC_CVSD) + +#if defined(CONFIG_BT_HFP_HF_CODEC_MSBC) +#define BT_HFP_HF_CODEC_MSBC_ENABLE BIT(BT_HFP_HF_CODEC_MSBC) +#else +#define BT_HFP_HF_CODEC_MSBC_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_CODEC_MSBC */ + +#if defined(CONFIG_BT_HFP_HF_CODEC_LC3_SWB) +#define BT_HFP_HF_CODEC_LC3_SWB_ENABLE BIT(BT_HFP_HF_CODEC_LC3_SWB) +#else +#define BT_HFP_HF_CODEC_LC3_SWB_ENABLE 0 +#endif /* CONFIG_BT_HFP_HF_CODEC_LC3_SWB */ + +/* HFP HF Supported Codec IDs */ +#define BT_HFP_HF_SUPPORTED_CODEC_IDS \ + BT_HFP_HF_CODEC_CVSD_MASK | BT_HFP_HF_CODEC_MSBC_ENABLE | BT_HFP_HF_CODEC_LC3_SWB_ENABLE + +/* bt_hfp_hf flags: the flags defined here represent hfp hf parameters */ + +enum +{ + BT_HFP_HF_FLAG_CONNECTED, /* HFP HF SLC Established */ + BT_HFP_HF_FLAG_TX_ONGOING, /* HFP HF TX is ongoing */ + BT_HFP_HF_FLAG_RX_ONGOING, /* HFP HF RX is ongoing */ + BT_HFP_HF_FLAG_CODEC_CONN, /* HFP HF codec connection setup */ + BT_HFP_HF_FLAG_CLCC_PENDING, /* HFP HF CLCC is pending */ + BT_HFP_HF_FLAG_VRE_ACTIVATE, /* VRE is activated */ + BT_HFP_HF_FLAG_BINP, /* +BINP result code is received */ + BT_HFP_HF_FLAG_INITIATING, /* HF is in initiating state */ + BT_HFP_HF_FLAG_QUERY_CALLS, /* Require to query list of current calls */ + BT_HFP_HF_FLAG_USR_CLCC_CMD, /* User-initiated AT+CLCC command */ + BT_HFP_HF_FLAG_USR_CLCC_PND, /* User-initiated AT+CLCC command is pending */ + BT_HFP_HF_FLAG_DISCOVER_DONE, /* SDP discovered done */ + BT_HFP_HF_FLAG_RECORD_FOUND, /* SDP AG Record found */ + BT_HFP_HF_FLAG_DLC_CONNECTED, /* HFP HF DLC is connected */ + BT_HFP_HF_FLAG_FEAT_UPDATED, /* Remote feature has been updated */ + BT_HFP_HF_FLAG_RELEASING, /* Release object when SDP discovery completes */ + + /* Total number of flags - must be at the end of the enum */ + + BT_HFP_HF_NUM_FLAGS, +}; + +/* bt_hfp_hf_call flags: the flags defined here represent hfp hf call + * parameters + */ + +enum +{ + BT_HFP_HF_CALL_IN_USING, /* Object is in using */ + BT_HFP_HF_CALL_CLCC, /* CLCC report received */ + BT_HFP_HF_CALL_INCOMING, /* Incoming call */ + BT_HFP_HF_CALL_INCOMING_HELD, /* Incoming call held */ + BT_HFP_HF_CALL_OUTGOING_3WAY, /* Outgoing 3 way call */ + BT_HFP_HF_CALL_INCOMING_3WAY, /* Incoming 3 way call */ + + /* Total number of flags - must be at the end of the enum */ + + BT_HFP_HF_CALL_NUM_FLAGS, +}; + +/* bt_hfp_hf_call state: the flags defined here represent hfp hf call state + * parameters + */ + +enum +{ + /* Call state flags */ + + BT_HFP_HF_CALL_STATE_TERMINATE, /* Call terminate */ + BT_HFP_HF_CALL_STATE_OUTGOING, /* Call outgoing */ + BT_HFP_HF_CALL_STATE_INCOMING, /* Call incoming */ + BT_HFP_HF_CALL_STATE_ALERTING, /* Call alerting */ + BT_HFP_HF_CALL_STATE_WAITING, /* Call waiting */ + BT_HFP_HF_CALL_STATE_ACTIVE, /* Call active */ + BT_HFP_HF_CALL_STATE_HELD, /* Call held */ + + /* Total number of flags - must be at the end of the enum */ + + BT_HFP_HF_CALL_STATE_NUM_FLAGS, +}; + +struct bt_hfp_hf_call +{ + struct bt_hfp_hf *hf; + uint8_t index; + + atomic_define(flags, BT_HFP_HF_CALL_NUM_FLAGS); + atomic_define(state, BT_HFP_HF_CALL_STATE_NUM_FLAGS); +}; + +struct bt_hfp_hf +{ + struct bt_rfcomm_dlc rfcomm_dlc; + + /* ACL connection handle */ + + struct bt_conn *acl; + + /* AT command sending queue */ + + at_finish_cb_t backup_finish; + struct k_fifo tx_pending; + + /* SCO Channel */ + + struct bt_sco_chan chan; + + /* SCO connect */ + + void *sco_conn; + + /* SDP discover params */ + + struct bt_sdp_discover_params sdp_param; + + /* SCL work */ + + struct k_work slc_work; + + struct at_client at; + uint32_t hf_features; + uint32_t ag_features; + uint8_t hf_codec_ids; + uint8_t active_codec_id; + uint8_t neg_codec_id; + uint8_t vgm; + uint8_t vgs; + int8_t ind_table[HF_MAX_AG_INDICATORS]; + + uint32_t hf_ind; + uint32_t ag_ind; + uint32_t ind_enable; + + /* AG profile information */ + + uint16_t ag_sdp_version; + uint16_t ag_sdp_features; + + /* AT command initialization indicator */ + + uint8_t cmd_init_seq; + + /* The features supported by AT+CHLD */ + + uint32_t chld_features; + + /* Worker for pending TX */ + + struct k_work work; + + struct k_work_delayable deferred_work; + + /* calls */ + + struct bt_hfp_hf_call calls[CONFIG_BT_HFP_HF_MAX_CALLS]; + + atomic_define(flags, BT_HFP_HF_NUM_FLAGS); +}; diff --git a/libs/zblue/host/classic/hfp_internal.h b/libs/zblue/host/classic/hfp_internal.h new file mode 100644 index 0000000000000..04dca5443a5c0 --- /dev/null +++ b/libs/zblue/host/classic/hfp_internal.h @@ -0,0 +1,197 @@ +/**************************************************************************** + * libs/zblue/host/classic/hfp_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Internal APIs for Bluetooth Handsfree profile handling. + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* HFP Profile Version */ +#define BT_HFP_VERSION_0_96 0x0096 /* HFP Rev. 0.96 */ +#define BT_HFP_VERSION_1_0 0x0100 /* HFP Rev. 1.0 */ +#define BT_HFP_VERSION_1_5 0x0105 /* HFP Rev. 1.5 */ +#define BT_HFP_VERSION_1_6 0x0106 /* HFP Rev. 1.6 */ +#define BT_HFP_VERSION_1_7 0x0107 /* HFP Rev. 1.7 */ +#define BT_HFP_VERSION_1_8 0x0108 /* HFP Rev. 1.8 */ +#define BT_HFP_VERSION_1_9 0x0109 /* HFP Rev. 1.9 */ + +#define BT_HFP_MAX_MTU (BT_L2CAP_RX_MTU - BT_RFCOMM_OVERHEAD_SIZE) +#define BT_HF_CLIENT_MAX_PDU BT_HFP_MAX_MTU + +/* HFP AG Features */ +#define BT_HFP_AG_FEATURE_3WAY_CALL 0x00000001 /* Three-way calling */ +#define BT_HFP_AG_FEATURE_ECNR 0x00000002 /* EC and/or NR function */ +#define BT_HFP_AG_FEATURE_VOICE_RECG 0x00000004 /* Voice recognition */ +#define BT_HFP_AG_FEATURE_INBAND_RINGTONE 0x00000008 /* In-band ring capability */ +#define BT_HFP_AG_FEATURE_VOICE_TAG 0x00000010 /* Attach no. to voice tag */ +#define BT_HFP_AG_FEATURE_REJECT_CALL 0x00000020 /* Ability to reject call */ +#define BT_HFP_AG_FEATURE_ECS 0x00000040 /* Enhanced call status */ +#define BT_HFP_AG_FEATURE_ECC 0x00000080 /* Enhanced call control */ +#define BT_HFP_AG_FEATURE_EXT_ERR 0x00000100 /* Extended error codes */ +#define BT_HFP_AG_FEATURE_CODEC_NEG 0x00000200 /* Codec negotiation */ +#define BT_HFP_AG_FEATURE_HF_IND 0x00000400 /* HF Indicators */ +#define BT_HFP_AG_FEATURE_ESCO_S4 0x00000800 /* eSCO S4 Settings */ +#define BT_HFP_AG_FEATURE_ENH_VOICE_RECG 0x00001000 /* Enhanced Voice Recognition Status */ +#define BT_HFP_AG_FEATURE_VOICE_RECG_TEXT 0x00002000 /* Voice Recognition Text */ + +/* HFP HF Features */ +#define BT_HFP_HF_FEATURE_ECNR 0x00000001 /* EC and/or NR function */ +#define BT_HFP_HF_FEATURE_3WAY_CALL 0x00000002 /* Three-way calling */ +#define BT_HFP_HF_FEATURE_CLI 0x00000004 /* CLI presentation */ +#define BT_HFP_HF_FEATURE_VOICE_RECG 0x00000008 /* Voice recognition */ +#define BT_HFP_HF_FEATURE_VOLUME 0x00000010 /* Remote volume control */ +#define BT_HFP_HF_FEATURE_ECS 0x00000020 /* Enhanced call status */ +#define BT_HFP_HF_FEATURE_ECC 0x00000040 /* Enhanced call control */ +#define BT_HFP_HF_FEATURE_CODEC_NEG 0x00000080 /* CODEC Negotiation */ +#define BT_HFP_HF_FEATURE_HF_IND 0x00000100 /* HF Indicators */ +#define BT_HFP_HF_FEATURE_ESCO_S4 0x00000200 /* eSCO S4 Settings */ +#define BT_HFP_HF_FEATURE_ENH_VOICE_RECG 0x00000400 /* Enhanced Voice Recognition Status */ +#define BT_HFP_HF_FEATURE_VOICE_RECG_TEXT 0x00000800 /* Voice Recognition Text */ + +/* HFP HF Features in SDP */ +#define BT_HFP_HF_SDP_FEATURE_ECNR BIT(0) /* EC and/or NR function */ +#define BT_HFP_HF_SDP_FEATURE_3WAY_CALL BIT(1) /* Three-way calling */ +#define BT_HFP_HF_SDP_FEATURE_CLI BIT(2) /* CLI presentation */ +#define BT_HFP_HF_SDP_FEATURE_VOICE_RECG BIT(3) /* Voice recognition */ +#define BT_HFP_HF_SDP_FEATURE_VOLUME BIT(4) /* Remote volume control */ +#define BT_HFP_HF_SDP_FEATURE_WBS BIT(5) /* Wide Band Speech */ +#define BT_HFP_HF_SDP_FEATURE_ENH_VOICE_RECG BIT(6) /* Enhanced Voice Recognition Status */ +#define BT_HFP_HF_SDP_FEATURE_VOICE_RECG_TEXT BIT(7) /* Voice Recognition Text */ +#define BT_HFP_HF_SDP_FEATURE_SUPER_WBS BIT(8) /* Super Wide Band Speech */ + +/* HFP AG Features in SDP */ +#define BT_HFP_AG_SDP_FEATURE_3WAY_CALL BIT(0) /* Three-way calling */ +#define BT_HFP_AG_SDP_FEATURE_ECNR BIT(1) /* EC and/or NR function */ +#define BT_HFP_AG_SDP_FEATURE_VOICE_RECG BIT(2) /* Voice recognition */ +#define BT_HFP_AG_SDP_FEATURE_INBAND_RINGTONE BIT(3) /* In-band ring tone capability */ +#define BT_HFP_AG_SDP_FEATURE_VOICE_TAG BIT(4) /* Attach no. to voice tag */ +#define BT_HFP_AG_SDP_FEATURE_WBS BIT(5) /* Wide Band Speech */ +#define BT_HFP_AG_SDP_FEATURE_ENH_VOICE_RECG BIT(6) /* Enhanced Voice Recognition Status */ +#define BT_HFP_AG_SDP_FEATURE_VOICE_RECG_TEXT BIT(7) /* Voice Recognition Text */ +#define BT_HFP_AG_SDP_FEATURE_SUPER_WBS BIT(8) /* Super Wide Band Speech */ +#define HF_MAX_BUF_LEN BT_HF_CLIENT_MAX_PDU +#define HF_MAX_AG_INDICATORS 20 + +#define BT_HFP_HF_VGM_GAIN_MAX 15 +#define BT_HFP_HF_VGM_GAIN_MIN 0 + +#define BT_HFP_HF_VGS_GAIN_MAX 15 +#define BT_HFP_HF_VGS_GAIN_MIN 0 + +/* HFP call setup status */ +#define BT_HFP_CALL_SETUP_NONE 0 +#define BT_HFP_CALL_SETUP_INCOMING 1 +#define BT_HFP_CALL_SETUP_OUTGOING 2 +#define BT_HFP_CALL_SETUP_REMOTE_ALERTING 3 + +/* HFP incoming call status */ +#define BT_HFP_BTRH_ON_HOLD 0 +#define BT_HFP_BTRH_ACCEPTED 1 +#define BT_HFP_BTRH_REJECTED 2 + +/* HFP Call held status */ + +/* No calls held */ +#define BT_HFP_CALL_HELD_NONE 0 +/* Call is placed on hold or active/held calls swapped + * The AG has both an active AND a held call + */ +#define BT_HFP_CALL_HELD_ACTIVE_HELD 1 +/* Call on hold, no active call */ +#define BT_HFP_CALL_HELD_HELD 2 + +/* HFP AT+CHLD command value */ + +/* Releases all held calls or sets User Determined User Busy + * (UDUB) for a waiting call + */ +#define BT_HFP_CHLD_RELEASE_ALL 0 +/* Releases all active calls (if any exist) and accepts the + * other (held or waiting) call. + */ +#define BT_HFP_CHLD_RELEASE_ACTIVE_ACCEPT_OTHER 1 +/* Places all active calls (if any exist) on hold and accepts + * the other (held or waiting) call + */ +#define BT_HFP_CALL_HOLD_ACTIVE_ACCEPT_OTHER 2 +/* Adds a held call to the conversation */ +#define BT_HFP_CALL_ACTIVE_HELD 3 +/* Connects the two calls and disconnects the subscriber from + * both calls (Explicit Call Transfer). Support for this value and its + * associated functionality is optional for the HF. + */ +#define BT_HFP_CALL_QUITE 4 +/* Release a specific active call */ +#define BT_HFP_CALL_RELEASE_SPECIFIED_ACTIVE 10 +/* Private Consultation Mode + * place all parties of a multiparty call on hold with the exception of the + * specified call. + */ +#define BT_HFP_CALL_PRIVATE_CNLTN_MODE 20 + +/* Active */ +#define BT_HFP_CLCC_STATUS_ACTIVE 0 +/* Held */ +#define BT_HFP_CLCC_STATUS_HELD 1 +/* Dialing (outgoing calls only) */ +#define BT_HFP_CLCC_STATUS_DIALING 2 +/* Alerting (outgoing calls only) */ +#define BT_HFP_CLCC_STATUS_ALERTING 3 +/* Incoming (incoming calls only) */ +#define BT_HFP_CLCC_STATUS_INCOMING 4 +/* Waiting (incoming calls only) */ +#define BT_HFP_CLCC_STATUS_WAITING 5 +/* Call held by Response and Hold */ +#define BT_HFP_CLCC_STATUS_CALL_HELD_HOLD 6 + +/* CLCC direction */ + +/* CLCC direction: incoming */ +#define BT_HFP_CLCC_DIR_INCOMING 1 +/* CLCC direction: outgoing */ +#define BT_HFP_CLCC_DIR_OUTGOING 0 + +/* BVRA Value */ + +/* BVRA Deactivation */ +#define BT_HFP_BVRA_DEACTIVATION 0 +/* BVRA Activation */ +#define BT_HFP_BVRA_ACTIVATION 1 +/* Ready to accept audio */ +#define BT_HFP_BVRA_READY_TO_ACCEPT 2 + +/* a maximum of 4 characters in length, but + * less than 4 characters in length is valid. + */ +#define BT_HFP_BVRA_TEXT_ID_MAX_LEN 4 + +/* BVRA VRE state */ + +/* BVRA VRE state: the AG is ready to accept audio input */ +#define BT_HFP_BVRA_STATE_ACCEPT_INPUT BIT(0) +/* BVRA VRE state: the AG is sending audio to the HF */ +#define BT_HFP_BVRA_STATE_SEND_AUDIO BIT(1) +/* BVRA VRE state: the AG is processing the audio input */ +#define BT_HFP_BVRA_STATE_PROCESS_AUDIO BIT(2) + +#define IS_VALID_DTMF(c) \ + ((((c) >= '0') && ((c) <= '9')) || (((c) >= 'A') && ((c) <= 'D')) || ((c) == '#') || ((c) == '*')) + +/* HFP HF Indicators */ + +enum +{ + HFP_HF_ENHANCED_SAFETY_IND = 1, /* Enhanced Safety */ + HFP_HF_BATTERY_LEVEL_IND = 2, /* Remaining level of Battery */ + HFP_HF_IND_MAX +}; + +#define IS_VALID_BATTERY_LEVEL(level) (((level) >= 0) && ((level) <= 100)) diff --git a/libs/zblue/host/classic/hid_device.c b/libs/zblue/host/classic/hid_device.c new file mode 100644 index 0000000000000..f338a3fc3a80f --- /dev/null +++ b/libs/zblue/host/classic/hid_device.c @@ -0,0 +1,1160 @@ +/**************************************************************************** + * libs/zblue/host/classic/hid_device.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* hid_device.c - HID Profile - HID Device side handling */ + +/* Copyright 2025 Xiaomi Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "hid_internal.h" + +#include +LOG_MODULE_REGISTER(bt_hid_device); + +/* Get the HID device from CTRL L2CAP channel */ +#define HID_DEVICE_BY_CTRL_CHAN(ch) \ + CONTAINER_OF(ch, struct bt_hid_device, ctrl_session.br_chan.chan) + +/* Get the HID device from INTR L2CAP channel */ +#define HID_DEVICE_BY_INTR_CHAN(ch) \ + CONTAINER_OF(ch, struct bt_hid_device, intr_session.br_chan.chan) + +/* Get the HID session from L2CAP channel */ +#define HID_SESSION_BY_CHAN(ch) CONTAINER_OF(ch, struct bt_hid_session, br_chan.chan) + +#define HID_CHAN_TYPE(ch) \ + (((ch) == NULL) ? BT_HID_CHANNEL_UNKNOWN : HID_SESSION_BY_CHAN((ch))->type) + +/* Timeout for INTR channel connection after CTRL is established (seconds) */ +#define HID_INTR_CONN_TIMEOUT SEC2TICK(30) + +/* Fallback timeout after a device-initiated Virtual Cable Unplug PDU is + * sent. After sending VC unplug the initiator waits for the remote to tear + * down the L2CAP channels. This timer only fires as a safety net when the + * remote never disconnects, so the local link is not left up forever. + */ +#define HID_VCU_DISCONNECT_TIMEOUT SEC2TICK(5) + +/* Dedicated buffer pool for HANDSHAKE responses. HANDSHAKE is a mandatory + * protocol response (HID spec v1.1.2 Section 3.2) that must not fail due to + * buffer contention with data traffic. 2 buffers covers back-to-back error + * handshakes before the first TX completes. Payload is a single-byte header. + */ + +NET_BUF_POOL_FIXED_DEFINE(hid_hs_pool, + 2, + BT_L2CAP_BUF_SIZE(sizeof(struct bt_hid_hdr)), + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + +/* HID device callback */ + +static const struct bt_hid_device_cb *hid_cb; + +/* HID L2CAP servers registration state */ + +static bool hid_l2cap_registered; + +/* HID device connection (single session only) */ + +static struct bt_hid_device hid_device_conn; + +static void bt_hid_device_cleanup(struct bt_hid_device *hid); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static struct bt_hid_device *hid_allocate(void) +{ + if (hid_device_conn.state == BT_HID_STATE_DISCONNECTED) + { + return &hid_device_conn; + } + + return NULL; +} + +static struct bt_hid_device *hid_find(const struct bt_conn *conn, + uint8_t state) +{ + if (hid_device_conn.state != state) + { + return NULL; + } + + if (hid_device_conn.ctrl_session.br_chan.chan.conn != conn) + { + return NULL; + } + + return &hid_device_conn; +} + +struct bt_conn *bt_hid_device_get_conn(struct bt_hid_device *hid) +{ + struct bt_conn *conn; + + if (hid == NULL) + { + return NULL; + } + + conn = hid->ctrl_session.br_chan.chan.conn; + if (conn == NULL) + { + return NULL; + } + + return bt_conn_ref(conn); +} + +struct net_buf *bt_hid_device_create_pdu(struct net_buf_pool *pool) +{ + return bt_l2cap_create_pdu(pool, sizeof(struct bt_hid_hdr)); +} + +static void hid_send_handshake(struct bt_hid_device *hid, + uint8_t result_code) +{ + struct net_buf *buf; + struct bt_hid_hdr *hdr; + int err; + + buf = bt_l2cap_create_pdu(&hid_hs_pool, 0); + if (buf == NULL) + { + LOG_ERR("Handshake buf alloc failed"); + return; + } + + hdr = net_buf_add(buf, sizeof(struct bt_hid_hdr)); + hdr->header = BT_HID_BUILD_HDR(BT_HID_MSG_TYPE_HANDSHAKE, result_code); + + /* HANDSHAKE is only ever sent on the Control channel. */ + + err = bt_l2cap_chan_send(&hid->ctrl_session.br_chan.chan, buf); + if (err < 0) + { + LOG_ERR("Handshake send failed (%d)", err); + net_buf_unref(buf); + } +} + +static uint8_t hid_err_to_handshake(int err) +{ + switch (err) + { + case 0: + return BT_HID_HS_RSP_SUCCESS; + case -EOPNOTSUPP: + case -ENOSYS: + return BT_HID_HS_RSP_ERR_UNSUPPORTED_REQ; + case -EINVAL: + return BT_HID_HS_RSP_ERR_INVALID_PARAM; + case -ENOENT: + return BT_HID_HS_RSP_ERR_INVALID_REPORT_ID; + case -EBUSY: + case -EAGAIN: + return BT_HID_HS_RSP_NOT_READY; + default: + return BT_HID_HS_RSP_ERR_UNKNOWN; + } +} + +static int hid_control_handle(struct bt_hid_device *hid, + struct net_buf *buf, uint8_t control) +{ + int err; + + switch (control) + { + case BT_HID_CONTROL_VIRTUAL_CABLE_UNPLUG: + if ((hid_cb != NULL) && (hid_cb->vc_unplug != NULL)) + { + hid_cb->vc_unplug(hid); + } + + /* HID spec v1.1.2 Section 3.1.2.2.3 requires the recipient to + * destroy bonding/Virtual Cable info on VC unplug, then disconnect + * INTR followed by CTRL without sending HANDSHAKE. Clearing bonding + * is + * delegated to the application through the vc_unplug callback above: + * the only host API, bt_br_unpair(), unconditionally disconnects the + * whole ACL, which would tear down any co-located profile + * (A2DP/AVRCP/HFP) sharing it. The spec only allows the ACL to be + * dropped "if there are no other profiles using the ACL", so the + * stack + * does not clear bonding here; the application decides when it is + * safe + * to do so (see bt_hid_device_get_conn()). + */ + + err = bt_hid_device_disconnect(hid); + if ((err != 0) && (err != -ENOTCONN)) + { + /* Disconnect is requested as part of VC unplug teardown. + * There is no recovery path here and no HANDSHAKE is sent for VC + * unplug, so only log the failure for diagnostics. + * -ENOTCONN means teardown is already in progress, which + * matches the intent and is not treated as an error. + */ + + LOG_WRN("VC unplug: disconnect failed (%d)", err); + } + break; + case BT_HID_CONTROL_SUSPEND: + /* Suspend is host-owned state; the device only relays the + * request to the application. No local state is tracked here (see + * the + * suspend callback documentation). + */ + + if ((hid_cb != NULL) && (hid_cb->suspend != NULL)) + { + hid_cb->suspend(hid, true); + } + break; + case BT_HID_CONTROL_EXIT_SUSPEND: + if ((hid_cb != NULL) && (hid_cb->suspend != NULL)) + { + hid_cb->suspend(hid, false); + } + break; + default: + /* HID spec v1.1.2 Section 3.1.2.2: unsupported HID_CONTROL + * operations shall be silently ignored. + */ + + LOG_WRN("control %u not handled, ignoring", control); + break; + } + + return 0; +} + +static int hid_get_report_handle(struct bt_hid_device *hid, + struct net_buf *buf, uint8_t param) +{ + struct bt_hid_hdr *hdr; + struct net_buf *rsp; + uint8_t report_type; + bool size_present; + int err; + + report_type = FIELD_GET(BT_HID_PARAM_REPORT_TYPE_MASK, param); + size_present = (param & BT_HID_PARAM_REPORT_SIZE_MASK) != 0; + + if (size_present && (buf->len < sizeof(uint16_t))) + { + return -EINVAL; + } + + if ((hid_cb == NULL) || (hid_cb->get_report == NULL)) + { + return -EOPNOTSUPP; + } + + rsp = bt_hid_device_create_pdu(NULL); + if (rsp == NULL) + { + return -EAGAIN; + } + + /* GET_REPORT is synchronous: the application fills rsp with the report + * payload and returns 0, or returns a negative errno (e.g. -EAGAIN for + * NOT_READY). The transaction is fully resolved on return, so no pending + * state needs to be tracked here. The error handshake is sent by the + * dispatcher; on success the DATA response below is the reply. + */ + + err = hid_cb->get_report(hid, report_type, size_present, buf, rsp); + if (err < 0) + { + LOG_ERR("Get_Report cb err %d", err); + net_buf_unref(rsp); + return err; + } + + hdr = net_buf_push(rsp, sizeof(struct bt_hid_hdr)); + hdr->header = BT_HID_BUILD_HDR(BT_HID_MSG_TYPE_DATA, report_type); + + err = bt_l2cap_chan_send(&hid->ctrl_session.br_chan.chan, rsp); + if (err < 0) + { + LOG_ERR("Get_Report rsp send failed (%d)", err); + net_buf_unref(rsp); + return err; + } + + return 0; +} + +static int hid_set_report_handle(struct bt_hid_device *hid, + struct net_buf *buf, uint8_t param) +{ + uint8_t report_type; + int err; + + report_type = FIELD_GET(BT_HID_PARAM_REPORT_TYPE_MASK, param); + + if ((hid_cb == NULL) || (hid_cb->set_report == NULL)) + { + return -EOPNOTSUPP; + } + + err = hid_cb->set_report(hid, report_type, buf); + if (err < 0) + { + LOG_ERR("Set_Report cb err %d", err); + return err; + } + + hid_send_handshake(hid, BT_HID_HS_RSP_SUCCESS); + return 0; +} + +static int hid_get_protocol_handle(struct bt_hid_device *hid, + struct net_buf *buf, uint8_t param) +{ + struct net_buf *rsp; + struct bt_hid_hdr *hdr; + int err; + + rsp = bt_hid_device_create_pdu(NULL); + if (rsp == NULL) + { + return -EAGAIN; + } + + hdr = net_buf_add(rsp, sizeof(struct bt_hid_hdr)); + hdr->header = BT_HID_BUILD_HDR(BT_HID_MSG_TYPE_DATA, + BT_HID_REPORT_TYPE_OTHER); + net_buf_add_u8(rsp, + hid->boot_mode ? + BT_HID_PROTOCOL_BOOT_MODE : BT_HID_PROTOCOL_REPORT_MODE); + + err = bt_l2cap_chan_send(&hid->ctrl_session.br_chan.chan, rsp); + if (err < 0) + { + LOG_ERR("Get_Protocol rsp send failed (%d)", err); + net_buf_unref(rsp); + return err; + } + + return 0; +} + +static int hid_set_protocol_handle(struct bt_hid_device *hid, + struct net_buf *buf, uint8_t param) +{ + uint8_t protocol; + int err; + + protocol = FIELD_GET(BT_HID_PROTOCOL_MASK, param); + + if ((hid_cb == NULL) || (hid_cb->set_protocol == NULL)) + { + return -EOPNOTSUPP; + } + + err = hid_cb->set_protocol(hid, protocol); + if (err != 0) + { + return err; + } + + hid->boot_mode = (protocol == BT_HID_PROTOCOL_BOOT_MODE); + hid_send_handshake(hid, BT_HID_HS_RSP_SUCCESS); + + return 0; +} + +static int hid_intr_handle(struct bt_hid_device *hid, struct net_buf *buf) +{ + if ((hid_cb != NULL) && (hid_cb->output_report != NULL)) + { + hid_cb->output_report(hid, buf); + } + + return 0; +} + +typedef int (*hid_ctrl_handler_t)(struct bt_hid_device *hid, + struct net_buf *buf, uint8_t param); + +static const struct +{ + uint8_t type; + hid_ctrl_handler_t handler; +} + +hid_ctrl_handlers[] = +{ + { + BT_HID_MSG_TYPE_CONTROL, hid_control_handle + }, + + { + BT_HID_MSG_TYPE_GET_REPORT, hid_get_report_handle + }, + + { + BT_HID_MSG_TYPE_SET_REPORT, hid_set_report_handle + }, + + { + BT_HID_MSG_TYPE_GET_PROTOCOL, hid_get_protocol_handle + }, + + { + BT_HID_MSG_TYPE_SET_PROTOCOL, hid_set_protocol_handle + }, +}; + +static void hid_intr_timeout_handler(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_hid_device *hid = CONTAINER_OF(dwork, struct bt_hid_device, + intr_timeout); + int err; + + LOG_WRN("INTR connection timeout"); + + /* Timer is started when CTRL connects and cancelled when INTR connects + * or when cleanup runs. If this fires, the HID connection did not complete + * in time — tear down whatever is up. + */ + + err = bt_hid_device_disconnect(hid); + if (err != 0) + { + LOG_WRN("INTR timeout: disconnect failed (%d)", err); + } +} + +static void hid_vcu_disconnect_work(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_hid_device *hid = CONTAINER_OF(dwork, struct bt_hid_device, + vcu_disconnect); + int err; + + LOG_DBG("VCU disconnect fallback timeout"); + + /* After a device-initiated VC unplug the initiator waits for the remote + * to disconnect the L2CAP channels. This handler only runs when that + * fallback timer expires because the remote never disconnected; tear the + * link down locally as a safety net. + */ + + err = bt_hid_device_disconnect(hid); + if (err != 0) + { + LOG_WRN("VCU disconnect failed (%d)", err); + } +} + +static void bt_hid_l2cap_ctrl_connected(struct bt_l2cap_chan *chan) +{ + struct bt_hid_device *hid = HID_DEVICE_BY_CTRL_CHAN(chan); + __maybe_unused enum bt_hid_channel_type chtype; + int err; + + chtype = HID_CHAN_TYPE(chan); + + LOG_DBG("session %d connected (state %d)", chtype, hid->state); + + DEBUGASSERT(chtype == BT_HID_CHANNEL_CTRL); + + hid->state = BT_HID_STATE_CTRL_CONNECTED; + hid->ctrl_connected = true; + + if (hid->role == BT_HID_ROLE_ACCEPTOR) + { + /* Wait for INTR channel connection from remote */ + + LOG_DBG("wait for INTR connection"); + bt_work_schedule(&hid->intr_timeout, HID_INTR_CONN_TIMEOUT); + return; + } + + err = bt_l2cap_chan_connect(hid->ctrl_session.br_chan.chan.conn, + &hid->intr_session.br_chan.chan, + BT_L2CAP_PSM_HID_INTR); + if (err != 0) + { + LOG_ERR("INTR connect failed"); + hid->state = BT_HID_STATE_DISCONNECTING; + bt_l2cap_chan_disconnect(&hid->ctrl_session.br_chan.chan); + return; + } + + hid->state = BT_HID_STATE_INTR_CONNECTING; +} + +static void bt_hid_l2cap_intr_connected(struct bt_l2cap_chan *chan) +{ + struct bt_hid_device *hid = HID_DEVICE_BY_INTR_CHAN(chan); + __maybe_unused enum bt_hid_channel_type chtype; + + chtype = HID_CHAN_TYPE(chan); + + LOG_DBG("session %d connected (state %d)", chtype, hid->state); + + DEBUGASSERT(chtype == BT_HID_CHANNEL_INTR); + + /* HID spec v1.1.1 Section 5.2.2: both CTRL and INTR channels + * must be established for the HID connection to be valid. + */ + + if (!hid->ctrl_connected) + { + LOG_WRN("CTRL not connected, reject INTR"); + bt_l2cap_chan_disconnect(chan); + return; + } + + hid->state = BT_HID_STATE_CONNECTED; + hid->intr_connected = true; + k_work_cancel_delayable(&hid->intr_timeout); + + if ((hid_cb != NULL) && (hid_cb->connected != NULL)) + { + hid_cb->connected(hid); + } +} + +static void bt_hid_l2cap_ctrl_disconnected(struct bt_l2cap_chan *chan) +{ + struct bt_hid_device *hid = HID_DEVICE_BY_CTRL_CHAN(chan); + __maybe_unused enum bt_hid_channel_type chtype; + + chtype = HID_CHAN_TYPE(chan); + + LOG_DBG("session %d disconnected (state %d)", chtype, hid->state); + + DEBUGASSERT(chtype == BT_HID_CHANNEL_CTRL); + + hid->ctrl_connected = false; + + /* HID spec v1.1.2 Section 5.2.2: INTR shall be disconnected before + * CTRL. If remote disconnects CTRL first, clean up INTR. + */ + + if (hid->intr_connected) + { + int err = bt_l2cap_chan_disconnect(&hid->intr_session.br_chan.chan); + + if ((err != 0) && (err != -EALREADY)) + { + LOG_WRN("INTR disconnect failed (%d)", err); + } + + return; + } + + if ((hid_cb != NULL) && (hid_cb->disconnected != NULL)) + { + hid_cb->disconnected(hid); + } + + bt_hid_device_cleanup(hid); +} + +static void bt_hid_l2cap_intr_disconnected(struct bt_l2cap_chan *chan) +{ + struct bt_hid_device *hid = HID_DEVICE_BY_INTR_CHAN(chan); + __maybe_unused enum bt_hid_channel_type chtype; + + chtype = HID_CHAN_TYPE(chan); + + LOG_DBG("session %d disconnected (state %d)", chtype, hid->state); + + DEBUGASSERT(chtype == BT_HID_CHANNEL_INTR); + + hid->intr_connected = false; + + /* Local disconnect of INTR must be followed by disconnecting CTRL. */ + + if (hid->state == BT_HID_STATE_DISCONNECTING && hid->ctrl_connected) + { + int err = bt_l2cap_chan_disconnect(&hid->ctrl_session.br_chan.chan); + + if ((err != 0) && (err != -EALREADY)) + { + LOG_WRN("CTRL disconnect failed (%d)", err); + } + + return; + } + + /* Wait for remote to disconnect CTRL. */ + + if (hid->ctrl_connected) + { + hid->state = BT_HID_STATE_CTRL_CONNECTED; + LOG_DBG("wait for remote CTRL disconnect"); + return; + } + + if ((hid_cb != NULL) && (hid_cb->disconnected != NULL)) + { + hid_cb->disconnected(hid); + } + + bt_hid_device_cleanup(hid); +} + +static int bt_hid_l2cap_ctrl_recv(struct bt_l2cap_chan *chan, + struct net_buf *buf) +{ + struct bt_hid_hdr *hdr; + struct bt_hid_device *hid = HID_DEVICE_BY_CTRL_CHAN(chan); + uint8_t type; + uint8_t param; + + DEBUGASSERT(HID_CHAN_TYPE(chan) == BT_HID_CHANNEL_CTRL); + + /* HID spec v1.1.2 Section 5.2.2: once the Control channel is open the + * host may send Control channel commands; the Interrupt channel need not + * be established for them. Drop anything received before the Control + * channel itself is connected. + */ + + if (hid->state < BT_HID_STATE_CTRL_CONNECTED) + { + LOG_WRN("CTRL recv in state %d, ignoring", hid->state); + return 0; + } + + if (buf->len < sizeof(*hdr)) + { + LOG_ERR("CTRL buf len %u invalid", buf->len); + hid_send_handshake(hid, BT_HID_HS_RSP_ERR_INVALID_PARAM); + return 0; + } + + hdr = net_buf_pull_mem(buf, sizeof(*hdr)); + param = BT_HID_HDR_GET_PARAM(hdr->header); + type = BT_HID_HDR_GET_TYPE(hdr->header); + + LOG_DBG("CTRL recv type 0x%x param 0x%x", type, param); + + ARRAY_FOR_EACH(hid_ctrl_handlers, i) + { + if (hid_ctrl_handlers[i].type == type) + { + int err = hid_ctrl_handlers[i].handler(hid, buf, param); + +/* HID_CONTROL has no handshake response (HID spec v1.1.2 + * Section 3.2.1.3): errors are ignored. For every other + * transaction an error handshake is sent here, so the handlers + * only need to return the result code. SUCCESS handshakes remain + * in the SET_* handlers, and the GET_* handlers send their own + * DATA response. + */ + + if ((type != BT_HID_MSG_TYPE_CONTROL) && (err != 0)) + { + hid_send_handshake(hid, hid_err_to_handshake(err)); + } + + return 0; + } + } + + LOG_WRN("type %u not supported", type); + hid_send_handshake(hid, BT_HID_HS_RSP_ERR_UNSUPPORTED_REQ); + + return 0; +} + +static int bt_hid_l2cap_intr_recv(struct bt_l2cap_chan *chan, + struct net_buf *buf) +{ + struct bt_hid_hdr *hdr; + struct bt_hid_device *hid = HID_DEVICE_BY_INTR_CHAN(chan); + uint8_t type; + uint8_t param; + uint8_t report_type; + + if (buf->len < sizeof(*hdr)) + { + LOG_ERR("INTR buf len %u invalid", buf->len); + return 0; + } + + DEBUGASSERT(HID_CHAN_TYPE(chan) == BT_HID_CHANNEL_INTR); + + if (hid->state != BT_HID_STATE_CONNECTED) + { + LOG_WRN("INTR recv in state %d, ignoring", hid->state); + return 0; + } + + hdr = net_buf_pull_mem(buf, sizeof(*hdr)); + type = BT_HID_HDR_GET_TYPE(hdr->header); + param = BT_HID_HDR_GET_PARAM(hdr->header); + + LOG_DBG("INTR recv type 0x%x param 0x%x", type, param); + + if (type != BT_HID_MSG_TYPE_DATA) + { + LOG_WRN("INTR type %u not supported", type); + return 0; + } + + /* HID spec v1.1.2 Section 7.4.3: only Output reports are sent from the + * host to the device on the Interrupt channel. The Interrupt channel has + * no handshake mechanism, so a non-Output report is silently dropped. + */ + + report_type = FIELD_GET(BT_HID_PARAM_REPORT_TYPE_MASK, param); + if (report_type != BT_HID_REPORT_TYPE_OUTPUT) + { + LOG_WRN("INTR DATA report type %u not Output, ignoring", report_type); + return 0; + } + + return hid_intr_handle(hid, buf); +} + +static const struct bt_l2cap_chan_ops ctrl_ops = +{ + .connected = bt_hid_l2cap_ctrl_connected, + .disconnected = bt_hid_l2cap_ctrl_disconnected, + .recv = bt_hid_l2cap_ctrl_recv, +}; + +static const struct bt_l2cap_chan_ops intr_ops = +{ + .connected = bt_hid_l2cap_intr_connected, + .disconnected = bt_hid_l2cap_intr_disconnected, + .recv = bt_hid_l2cap_intr_recv, +}; + +static void bt_hid_session_init(struct bt_hid_device *hid, + enum bt_hid_role role) +{ + hid->ctrl_session.br_chan.chan.ops = &ctrl_ops; + hid->ctrl_session.br_chan.rx.mtu = BT_L2CAP_RX_MTU; + hid->ctrl_session.type = BT_HID_CHANNEL_CTRL; + + hid->intr_session.br_chan.chan.ops = &intr_ops; + hid->intr_session.br_chan.rx.mtu = BT_L2CAP_RX_MTU; + hid->intr_session.type = BT_HID_CHANNEL_INTR; + + hid->role = role; + hid->boot_mode = false; + hid->ctrl_connected = false; + hid->intr_connected = false; + + k_work_init_delayable(&hid->intr_timeout, hid_intr_timeout_handler); + k_work_init_delayable(&hid->vcu_disconnect, hid_vcu_disconnect_work); +} + +static void bt_hid_device_cleanup(struct bt_hid_device *hid) +{ + k_work_cancel_delayable(&hid->intr_timeout); + k_work_cancel_delayable(&hid->vcu_disconnect); + + /* HID spec v1.1.2 Section 2.1.2: default protocol mode is Report + * Protocol Mode. Reset on disconnect for next connection. + */ + + hid->boot_mode = false; + hid->ctrl_connected = false; + hid->intr_connected = false; + hid->state = BT_HID_STATE_DISCONNECTED; +} + +static int hid_l2cap_ctrl_accept( + struct bt_conn *conn, struct bt_l2cap_server *server, + struct bt_l2cap_chan **chan) +{ + struct bt_hid_device *hid; + + hid = hid_allocate(); + if (hid == NULL) + { + return -EBUSY; + } + + bt_hid_session_init(hid, BT_HID_ROLE_ACCEPTOR); + hid->state = BT_HID_STATE_CTRL_CONNECTING; + + *chan = &hid->ctrl_session.br_chan.chan; + return 0; +} + +static int hid_l2cap_intr_accept( + struct bt_conn *conn, struct bt_l2cap_server *server, + struct bt_l2cap_chan **chan) +{ + struct bt_hid_device *hid; + + /* HID spec v1.1.2 Section 5.2.1: the control channel must be fully + * established (and belong to this conn) before the interrupt channel. + */ + + hid = hid_find(conn, BT_HID_STATE_CTRL_CONNECTED); + if (hid == NULL) + { + LOG_ERR("CTRL channel not connected"); + return -ENOTCONN; + } + + hid->state = BT_HID_STATE_INTR_CONNECTING; + + *chan = &hid->intr_session.br_chan.chan; + return 0; +} + +static struct bt_l2cap_server hid_server_ctrl = +{ + .psm = BT_L2CAP_PSM_HID_CTRL, + .sec_level = BT_SECURITY_L2, + .accept = hid_l2cap_ctrl_accept, +}; + +static struct bt_l2cap_server hid_server_intr = +{ + .psm = BT_L2CAP_PSM_HID_INTR, + .sec_level = BT_SECURITY_L2, + .accept = hid_l2cap_intr_accept, +}; + +static int bt_hid_device_init(void) +{ + int err; + + LOG_DBG("init"); + + err = bt_l2cap_br_server_register(&hid_server_ctrl); + if (err < 0) + { + LOG_ERR("CTRL L2CAP register failed (%d)", err); + return err; + } + + err = bt_l2cap_br_server_register(&hid_server_intr); + if (err < 0) + { + LOG_ERR("INTR L2CAP register failed (%d)", err); + bt_l2cap_br_server_unregister(&hid_server_ctrl); + return err; + } + + return 0; +} + +static void bt_hid_device_deinit(void) +{ + int err; + + LOG_DBG("deinit"); + + err = bt_l2cap_br_server_unregister(&hid_server_ctrl); + if (err < 0) + { + LOG_ERR("CTRL L2CAP unregister failed (%d)", err); + } + + err = bt_l2cap_br_server_unregister(&hid_server_intr); + if (err < 0) + { + LOG_ERR("INTR L2CAP unregister failed (%d)", err); + } +} + +int bt_hid_device_register(const struct bt_hid_device_cb *cb) +{ + int err; + + LOG_DBG("register"); + + /* GET_REPORT and SET_REPORT are mandatory HID Device transactions + * (HID Profile spec v1.1.2), so both callbacks are required. + */ + + if ((cb == NULL) || (cb->get_report == NULL) || (cb->set_report == NULL)) + { + return -EINVAL; + } + + if (hid_cb != NULL) + { + return -EALREADY; + } + + if (!hid_l2cap_registered) + { + err = bt_hid_device_init(); + if (err < 0) + { + return err; + } + + hid_l2cap_registered = true; + } + + hid_cb = cb; + return 0; +} + +int bt_hid_device_unregister(void) +{ + LOG_DBG("unregister"); + + if (hid_device_conn.state != BT_HID_STATE_DISCONNECTED) + { + LOG_ERR("cannot unregister while connected (state %d)", + hid_device_conn.state); + return -EBUSY; + } + + hid_cb = NULL; + + if (hid_l2cap_registered) + { + bt_hid_device_deinit(); + hid_l2cap_registered = false; + } + + return 0; +} + +int bt_hid_device_connect(struct bt_conn *conn, struct bt_hid_device **hid) +{ + struct bt_hid_device *inst; + int err; + + LOG_DBG("connect"); + + if (conn == NULL || hid == NULL) + { + return -EINVAL; + } + + inst = hid_allocate(); + if (inst == NULL) + { + LOG_ERR("busy (state %d)", hid_device_conn.state); + return -EBUSY; + } + + bt_hid_session_init(inst, BT_HID_ROLE_INITIATOR); + + inst->state = BT_HID_STATE_CTRL_CONNECTING; + + err = bt_l2cap_chan_connect(conn, &inst->ctrl_session.br_chan.chan, + BT_L2CAP_PSM_HID_CTRL); + if (err != 0) + { + LOG_ERR("connect failed (%d)", err); + bt_hid_device_cleanup(inst); + return err; + } + + *hid = inst; + return 0; +} + +int bt_hid_device_disconnect(struct bt_hid_device *hid) +{ + int err; + + if (hid == NULL) + { + return -EINVAL; + } + + LOG_DBG("disconnect"); + + if (hid->state == BT_HID_STATE_DISCONNECTED || + hid->state == BT_HID_STATE_DISCONNECTING) + { + LOG_ERR("not connected (state %d)", hid->state); + return -ENOTCONN; + } + + if (hid->intr_connected) + { + err = bt_l2cap_chan_disconnect(&hid->intr_session.br_chan.chan); + } + else if (hid->ctrl_connected) + { + err = bt_l2cap_chan_disconnect(&hid->ctrl_session.br_chan.chan); + } + else + { + return -ENOTCONN; + } + + /* -EALREADY means the channel is already tearing down, which matches + * the intent of this call; only update state once the request is accepted + * so a failed disconnect leaves the state untouched. + */ + + if ((err != 0) && (err != -EALREADY)) + { + LOG_ERR("chan disconnect failed (%d)", err); + return err; + } + + hid->state = BT_HID_STATE_DISCONNECTING; + + return 0; +} + +int bt_hid_device_input_report(struct bt_hid_device *hid, + struct net_buf *buf) +{ + struct bt_hid_hdr *hdr; + int err; + + if (hid == NULL || buf == NULL) + { + return -EINVAL; + } + + if (hid->state != BT_HID_STATE_CONNECTED) + { + LOG_ERR("not connected (state %d)", hid->state); + return -ENOTCONN; + } + + if (net_buf_headroom(buf) < sizeof(struct bt_hid_hdr)) + { + return -ENOMEM; + } + + hdr = net_buf_push(buf, sizeof(struct bt_hid_hdr)); + hdr->header = BT_HID_BUILD_HDR(BT_HID_MSG_TYPE_DATA, + BT_HID_REPORT_TYPE_INPUT); + + err = bt_l2cap_chan_send(&hid->intr_session.br_chan.chan, buf); + if (err < 0) + { + LOG_ERR("Input_Report send failed (%d)", err); + return err; + } + + return 0; +} + +static void virtual_cable_unplug_tx_cb(struct bt_conn *conn, + void *user_data, int err) +{ + struct bt_hid_device *hid; + + if (conn == NULL || user_data == NULL) + { + return; + } + + hid = (struct bt_hid_device *)user_data; + + /* After a device-initiated VC unplug the initiator sends the PDU and + * then waits for the remote to tear down the L2CAP channels; it must not + * disconnect the channels itself. Arm a fallback timer so the local link + * is still torn down if the remote never disconnects. If the remote + * disconnects first (spec-correct behavior), cleanup cancels this pending + * work. + * + * If the PDU send failed the remote may never have seen the unplug, so + * disconnect immediately rather than waiting for a remote that will not + * act. The work is deferred to the Bluetooth workqueue because this + * callback runs in the TX context. + */ + + if (err != 0) + { + LOG_WRN("VCU control message send failed (%d)", err); + bt_work_schedule(&hid->vcu_disconnect, 0); + return; + } + + bt_work_schedule(&hid->vcu_disconnect, HID_VCU_DISCONNECT_TIMEOUT); +} + +int bt_hid_device_virtual_cable_unplug(struct bt_hid_device *hid) +{ + struct net_buf *buf; + struct bt_hid_hdr *hdr; + int err; + + LOG_DBG("virtual cable unplug"); + + if (hid == NULL) + { + return -EINVAL; + } + + if (hid->state != BT_HID_STATE_CONNECTED) + { + return -ENOTCONN; + } + + buf = bt_hid_device_create_pdu(NULL); + if (buf == NULL) + { + return -ENOBUFS; + } + + hdr = net_buf_add(buf, sizeof(struct bt_hid_hdr)); + hdr->header = BT_HID_BUILD_HDR(BT_HID_MSG_TYPE_CONTROL, + BT_HID_CONTROL_VIRTUAL_CABLE_UNPLUG); + + /* Disconnect only after the VCU PDU has actually been sent: the + * tx callback tears down the L2CAP channel so the PDU is not lost to an + * early disconnect. If the remote host disconnects first (spec-correct + * behavior), cleanup cancels the pending work. + */ + + err = bt_l2cap_br_chan_send_cb(&hid->ctrl_session.br_chan.chan, buf, + virtual_cable_unplug_tx_cb, + hid); + if (err < 0) + { + net_buf_unref(buf); + return err; + } + +/* HID spec v1.1.2 Section 3.1.2.2.3: the VCU initiator shall also + * destroy bonding/Virtual Cable info. This is intentionally left out for + * now for the same reason as in hid_control_handle() — see the note + * there: + * bt_br_unpair() would drop the shared ACL, and clearing the key without + * an ACL disconnect needs a new host API pending community agreement. + */ + + return 0; +} diff --git a/libs/zblue/host/classic/hid_host.c b/libs/zblue/host/classic/hid_host.c new file mode 100644 index 0000000000000..850725c11c03d --- /dev/null +++ b/libs/zblue/host/classic/hid_host.c @@ -0,0 +1,1933 @@ +/**************************************************************************** + * libs/zblue/host/classic/hid_host.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* hid_host.c - HID Profile - HID Host side handling */ + +/* Copyright 2026 Xiaomi Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include "common/assert.h" + +#include +#include +#include +#include +#include +#include +#include "host/hci_core.h" +#include "host/conn_internal.h" +#include "host/l2cap_internal.h" + +#include "hid_internal.h" + +#define LOG_LEVEL CONFIG_BT_HID_HOST_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_hid_host); + +/* Get the HID host from CTRL L2CAP channel */ +#define HID_HOST_BY_CTRL_CHAN(ch) CONTAINER_OF(ch, struct bt_hid_host, ctrl_session.br_chan.chan) + +/* Get the HID host from INTR L2CAP channel */ +#define HID_HOST_BY_INTR_CHAN(ch) CONTAINER_OF(ch, struct bt_hid_host, intr_session.br_chan.chan) + +/* Get the HID session from L2CAP channel */ +#define HID_SESSION_BY_CHAN(ch) CONTAINER_OF(ch, struct bt_hid_host_session, br_chan.chan) + +#define HID_CHAN_TYPE(ch) \ + (((ch) == NULL) ? BT_HID_CHANNEL_UNKNOWN : HID_SESSION_BY_CHAN((ch))->type) + +/* Timeout for INTR channel connection after CTRL is established (seconds) */ +#define HID_INTR_CONN_TIMEOUT SEC2TICK(30) + +/* Timeout for a Control channel transaction to be answered by the device. + * HID spec v1.1.2 Section 5.2.6 notes that default supervisory timeouts are + * typically 30 seconds, after which the connection to the device is + * considered lost, so the same value is used to bound a pending transaction. + */ +#define HID_TRANS_TIMEOUT SEC2TICK(30) + +/* HID host callback. Non-NULL exactly while the L2CAP servers are + * registered. + */ + +static const struct bt_hid_host_cb *hid_cb; + +/* HID host connections, indexed by ACL connection index */ + +static struct bt_hid_host hid_conn_pool[CONFIG_BT_MAX_CONN]; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static const char *hid_state_str(uint8_t state) +{ + switch (state) + { + case BT_HID_STATE_DISCONNECTED: + return "disconnected"; + case BT_HID_STATE_CTRL_CONNECTING: + return "ctrl-connecting"; + case BT_HID_STATE_CTRL_CONNECTED: + return "ctrl-connected"; + case BT_HID_STATE_INTR_CONNECTING: + return "intr-connecting"; + case BT_HID_STATE_CONNECTED: + return "connected"; + case BT_HID_STATE_DISCONNECTING: + return "disconnecting"; + default: + return "unknown"; + } +} + +#if defined(CONFIG_BT_HID_HOST_LOG_LEVEL_DBG) +/* Single mutation point for the connection state. The transition is logged + * and checked only in debug builds, and an unexpected one is reported but + * still applied so a peer that deviates cannot wedge the state machine. HID + * spec v1.1.2 Section 5.2.2 defines the ordering the table below encodes. + */ + +static void hid_set_state_debug( + struct bt_hid_host *hid, enum bt_hid_state state, const char *func, + int line) +{ + LOG_DBG("hid %p %s -> %s", hid, hid_state_str(hid->state), + hid_state_str(state)); + + /* check transitions validness */ + + switch (state) + { + case BT_HID_STATE_DISCONNECTED: + + /* Cleanup after teardown or link loss, allowed from any state. */ + + break; + case BT_HID_STATE_CTRL_CONNECTING: + if (hid->state != BT_HID_STATE_DISCONNECTED) + { + LOG_WRN("%s()%d: invalid transition", func, line); + } + break; + case BT_HID_STATE_CTRL_CONNECTED: + if (hid->state != BT_HID_STATE_CTRL_CONNECTING) + { + LOG_WRN("%s()%d: invalid transition", func, line); + } + break; + case BT_HID_STATE_INTR_CONNECTING: + if (hid->state != BT_HID_STATE_CTRL_CONNECTED) + { + LOG_WRN("%s()%d: invalid transition", func, line); + } + break; + case BT_HID_STATE_CONNECTED: + if (hid->state != BT_HID_STATE_INTR_CONNECTING) + { + LOG_WRN("%s()%d: invalid transition", func, line); + } + break; + case BT_HID_STATE_DISCONNECTING: + /* A teardown spans both channels and re-enters this state once per + * channel, so only starting one on a released association is wrong. + */ + + if (hid->state == BT_HID_STATE_DISCONNECTED) + { + LOG_WRN("%s()%d: invalid transition", func, line); + } + break; + default: + LOG_ERR("%s()%d: unknown (%u) state was set", func, line, state); + return; + } + + hid->state = state; +} +#define hid_set_state(_hid, _state) hid_set_state_debug(_hid, _state, __func__, __LINE__) +#else +static void hid_set_state(struct bt_hid_host *hid, enum bt_hid_state state) +{ + hid->state = state; +} +#endif /* CONFIG_BT_HID_HOST_LOG_LEVEL_DBG */ + +static struct bt_hid_host *hid_allocate(const struct bt_conn *conn) +{ + struct bt_hid_host *hid; + size_t index; + + index = (size_t)bt_conn_index(conn); + DEBUGASSERT(index < ARRAY_SIZE(hid_conn_pool)); + + hid = &hid_conn_pool[index]; + + if (hid->state != BT_HID_STATE_DISCONNECTED) + { + LOG_ERR("busy (state %s)", hid_state_str(hid->state)); + return NULL; + } + + return hid; +} + +static struct bt_hid_host *hid_find(const struct bt_conn *conn) +{ + struct bt_hid_host *hid; + size_t index; + + if (conn == NULL) + { + return NULL; + } + + index = (size_t)bt_conn_index(conn); + DEBUGASSERT(index < ARRAY_SIZE(hid_conn_pool)); + + hid = &hid_conn_pool[index]; + + if (hid->state == BT_HID_STATE_DISCONNECTED) + { + return NULL; + } + + return hid; +} + +struct bt_conn *bt_hid_host_get_conn(struct bt_hid_host *hid) +{ + struct bt_conn *conn; + + /* A released instance still points at the connection it was last attached + * to, so the association has to be up before a reference is handed out. + */ + + if ((hid == NULL) || (hid->state == BT_HID_STATE_DISCONNECTED)) + { + return NULL; + } + + conn = hid->ctrl_session.br_chan.chan.conn; + if (conn == NULL) + { + return NULL; + } + + return bt_conn_ref(conn); +} + +struct net_buf *bt_hid_host_create_pdu(struct net_buf_pool *pool) +{ + return bt_l2cap_create_pdu(pool, sizeof(struct bt_hid_hdr)); +} + +static const char *hid_ctrl_type_str(uint8_t type) +{ + switch (type) + { + case BT_HID_CTRL_TYPE_NONE: + return "none"; + case BT_HID_CTRL_TYPE_GET_REPORT: + return "get-report"; + case BT_HID_CTRL_TYPE_SET_REPORT: + return "set-report"; + case BT_HID_CTRL_TYPE_GET_PROTOCOL: + return "get-protocol"; + case BT_HID_CTRL_TYPE_SET_PROTOCOL: + return "set-protocol"; + default: + return "unknown"; + } +} + +/* Claim the Control channel for a request. + * + * HID spec v1.1.2 Section 3.2.1 allows one outstanding transaction, so the + * claim is also the serialization point. It is taken before the PDU goes out + * because the reply is handled on the RX thread and may run as soon as the + * send returns, and it is taken atomically because requests come from + * application threads while the RX thread and the timeout release them. + */ + +static int hid_req_claim(struct bt_hid_host *hid, + const struct bt_hid_req *req) +{ + if (atomic_test_and_set_bit(&hid->flags, BT_HID_HOST_FLAG_REQ_PENDING)) + { + LOG_ERR("transaction %s pending", hid_ctrl_type_str(hid->req.type)); + return -EBUSY; + } + + /* The claim is published before the contents and the timer are in place, + * but neither the RX thread nor the timeout can consume it until the PDU + * this claim guards has been sent, which happens after this returns. See + * the ordering note in hid_req_take(). + */ + + hid->req = *req; + + /* Rescheduled rather than scheduled: k_work_schedule() keeps the delay of + * an item that is already pending, so a leftover timeout would decide the + * deadline of this transaction. + */ + + bt_work_reschedule(&hid->trans_work, HID_TRANS_TIMEOUT); + + /* hid_cleanup() may have released the association since the claim above, + * in which case the timeout just armed is left on a released instance. See + * the ordering note in hid_cleanup(). + */ + + if (!atomic_test_bit(&hid->flags, BT_HID_HOST_FLAG_REQ_PENDING)) + { + k_work_cancel_delayable(&hid->trans_work); + LOG_WRN("association released while claiming transaction %s", + hid_ctrl_type_str(req->type)); + return -ENOTCONN; + } + + LOG_DBG("transaction %s started", hid_ctrl_type_str(req->type)); + + return 0; +} + +/* Release a claim whose PDU never made it out, so a failed request does not + * block the Control channel. + */ + +static void hid_req_abort(struct bt_hid_host *hid) +{ + if (atomic_test_and_clear_bit(&hid->flags, BT_HID_HOST_FLAG_REQ_PENDING)) + { + k_work_cancel_delayable(&hid->trans_work); + } +} + +/* Consume the outstanding transaction. + * + * Returns false when there is nothing to consume or when another context got + * there first, in which case the caller must do nothing: the reply and the + * timeout race for the same transaction and only one of them may act on it. + */ + +static bool hid_req_take(struct bt_hid_host *hid, struct bt_hid_req *req) +{ + /* Copy the contents before clearing the claim. hid_req_take() only runs on + * the RX thread, when a reply or a VIRTUAL_CABLE_UNPLUG arrives, or from + * the transaction timeout, all of which happen after hid_req_claim() + * finished filling hid->req in, so the copy can never observe a + * half-written transaction. + */ + + *req = hid->req; + + if (!atomic_test_and_clear_bit(&hid->flags, BT_HID_HOST_FLAG_REQ_PENDING)) + { + return false; + } + + LOG_DBG("transaction %s done", hid_ctrl_type_str(req->type)); + k_work_cancel_delayable(&hid->trans_work); + + return true; +} + +/* Closes whichever channel L2CAP still owns. + * + * This runs on the Bluetooth workqueue rather than from the L2CAP callback + * that reported the first channel gone, because that callback runs while + * L2CAP is still processing the peer's signalling packet and holding the + * buffer for its response: br_sig_pool has one buffer per connection and + * bt_l2cap_br_chan_disconnect() waits for one with ((clock_t)-1) + * (l2cap_br.c), so issuing a request from there deadlocks the receive + * thread. + * + * It also serves as the INTR channel connect timeout on the acceptor path, + * where CTRL came up but the device never opened INTR: HID spec v1.1.2 + * Section 5.2.2 requires both channels, so an association that cannot make + * progress is torn down. The two uses never overlap: the timeout is + * cancelled as soon as INTR is connected, and hid_cleanup() cancels whatever + * is left. + */ + +static void hid_close_handler(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_hid_host *hid = CONTAINER_OF(dwork, struct bt_hid_host, + close_work); + struct bt_l2cap_chan *chan; + int err; + + /* HID spec v1.1.2 Section 5.2.2: INTR is closed before CTRL. Whichever + * channel is still attached is the one to close; a released association + * has neither and nothing is left to do. + */ + + if (atomic_test_bit(&hid->flags, BT_HID_HOST_FLAG_INTR_ATTACHED)) + { + chan = &hid->intr_session.br_chan.chan; + } + else if (atomic_test_bit(&hid->flags, BT_HID_HOST_FLAG_CTRL_ATTACHED)) + { + chan = &hid->ctrl_session.br_chan.chan; + } + else + { + LOG_DBG("nothing left to close"); + return; + } + + LOG_DBG("closing remaining channel in state " + "%s", hid_state_str(hid->state)); + + if (hid->state != BT_HID_STATE_DISCONNECTING) + { + hid_set_state(hid, BT_HID_STATE_DISCONNECTING); + } + + err = bt_l2cap_chan_disconnect(chan); + if ((err != 0) && (err != -EALREADY)) + { + LOG_WRN("chan disconnect failed (%d)", err); + } +} + +static void hid_trans_timeout_handler(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_hid_host *hid = CONTAINER_OF(dwork, struct bt_hid_host, + trans_work); + struct bt_hid_req req; + int err; + + /* The transaction is taken here only to settle a race: + * k_work_cancel_delayable() cannot stop this work once it is queued, so it + * can still run after the reply was consumed or after the association was + * released. Losing the transaction means one of those happened and nothing + * timed out. + */ + + if (!hid_req_take(hid, &req)) + { + return; + } + + /* HID spec v1.1.2 Section 5.2.6: a transaction that is never answered + * means the connection to the device is lost, so the association is torn + * down and the outcome is reported through the disconnected callback + * rather than through the result callback of the request. Only one + * transaction can be outstanding (Section 3.2.1), so the application can + * attribute the loss without being told which request it was. + */ + + LOG_WRN("transaction %s timed out in state " + "%s", hid_ctrl_type_str(req.type), + hid_state_str(hid->state)); + + err = bt_hid_host_disconnect(hid); + if (err != 0) + { + LOG_WRN("timeout: disconnect failed (%d)", err); + } +} + +static void hid_vcu_work_handler(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_hid_host + *hid = CONTAINER_OF(dwork, struct bt_hid_host, vcu_work); + int err; + + LOG_DBG("VCU disconnect"); + + err = bt_hid_host_disconnect(hid); + if (err != 0) + { + LOG_WRN("VCU disconnect failed (%d)", err); + } +} + +static void hid_l2cap_ctrl_connected(struct bt_l2cap_chan *chan) +{ + struct bt_hid_host *hid = HID_HOST_BY_CTRL_CHAN(chan); + __maybe_unused enum bt_hid_channel_type chtype; + int err; + + chtype = HID_CHAN_TYPE(chan); + + LOG_DBG("session %d connected (state %s)", chtype, + hid_state_str(hid->state)); + + DEBUGASSERT(chtype == BT_HID_CHANNEL_CTRL); + + /* A disconnect requested before this connect completed left the instance + * in DISCONNECTING, so close the channel instead of advancing an abandoned + * one. + */ + + if (hid->state == BT_HID_STATE_DISCONNECTING) + { + LOG_DBG("CTRL connected after disconnect requested, closing"); + (void)bt_l2cap_chan_disconnect(&hid->ctrl_session.br_chan.chan); + return; + } + + hid_set_state(hid, BT_HID_STATE_CTRL_CONNECTED); + atomic_set_bit(&hid->flags, BT_HID_HOST_FLAG_CTRL_CONNECTED); + + if (!atomic_test_bit(&hid->flags, BT_HID_HOST_FLAG_INITIATOR)) + { + /* HID spec v1.1.2 Section 5.2.2: the device opens the Interrupt + * channel next, so the association is not reported to the application + * until that channel is up. + */ + + LOG_DBG("wait for INTR connection"); + bt_work_reschedule(&hid->close_work, HID_INTR_CONN_TIMEOUT); + return; + } + + atomic_set_bit(&hid->flags, BT_HID_HOST_FLAG_INTR_ATTACHED); + + err = bt_l2cap_chan_connect(chan->conn, &hid->intr_session.br_chan.chan, + BT_L2CAP_PSM_HID_INTR); + if (err != 0) + { + LOG_ERR("INTR connect failed (%d)", err); + atomic_clear_bit(&hid->flags, BT_HID_HOST_FLAG_INTR_ATTACHED); + hid_set_state(hid, BT_HID_STATE_DISCONNECTING); + bt_work_reschedule(&hid->close_work, 0); + return; + } + + hid_set_state(hid, BT_HID_STATE_INTR_CONNECTING); +} + +static void hid_l2cap_intr_connected(struct bt_l2cap_chan *chan) +{ + struct bt_hid_host *hid = HID_HOST_BY_INTR_CHAN(chan); + __maybe_unused enum bt_hid_channel_type chtype; + + chtype = HID_CHAN_TYPE(chan); + + LOG_DBG("session %d connected (state %s)", chtype, + hid_state_str(hid->state)); + + DEBUGASSERT(chtype == BT_HID_CHANNEL_INTR); + + /* HID spec v1.1.2 Section 5.2.2: both CTRL and INTR channels + * must be established for the HID connection to be valid. + */ + + if (!atomic_test_bit(&hid->flags, BT_HID_HOST_FLAG_CTRL_CONNECTED)) + { + LOG_WRN("CTRL not connected, reject INTR"); + bt_work_reschedule(&hid->close_work, 0); + return; + } + + hid_set_state(hid, BT_HID_STATE_CONNECTED); + atomic_set_bit(&hid->flags, BT_HID_HOST_FLAG_INTR_CONNECTED); + k_work_cancel_delayable(&hid->close_work); + + if ((hid_cb != NULL) && (hid_cb->connected != NULL)) + { + hid_cb->connected(hid); + } +} + +static void hid_cleanup(struct bt_hid_host *hid) +{ + /* Releasing the instance only detaches it from the connection, so L2CAP + * has to be done with both channels first: a callback for a channel of + * this association would otherwise be dispatched to whatever is allocated + * next. A channel that is only connecting is attached as well, which is + * why the ATTACHED flags are checked here and not the CONNECTED ones. They + * are cleared only on the RX callback chain, so reading them together is + * not an atomic snapshot but is still correct: nothing can set them while + * this runs. + */ + + DEBUGASSERT(!atomic_test_bit(&hid->flags, + BT_HID_HOST_FLAG_CTRL_ATTACHED) && + !atomic_test_bit(&hid->flags, BT_HID_HOST_FLAG_INTR_ATTACHED)); + + /* Publish the release before cancelling, so a context that arms a work + * item concurrently settles the race itself: an arm before this point is + * undone by the cancels below, one after it sees the released instance and + * cancels its own work. hid_req_claim() and hid_vcu_tx_cb() rely on this. + */ + + atomic_set(&hid->flags, 0); + hid_set_state(hid, BT_HID_STATE_DISCONNECTED); + + k_work_cancel_delayable(&hid->close_work); + k_work_cancel_delayable(&hid->trans_work); + k_work_cancel_delayable(&hid->vcu_work); +} + +static void hid_l2cap_ctrl_disconnected(struct bt_l2cap_chan *chan) +{ + struct bt_hid_host *hid = HID_HOST_BY_CTRL_CHAN(chan); + __maybe_unused enum bt_hid_channel_type chtype; + + chtype = HID_CHAN_TYPE(chan); + + LOG_DBG("session %d disconnected (state %s)", chtype, + hid_state_str(hid->state)); + + DEBUGASSERT(chtype == BT_HID_CHANNEL_CTRL); + + atomic_clear_bit(&hid->flags, BT_HID_HOST_FLAG_CTRL_CONNECTED); + atomic_clear_bit(&hid->flags, BT_HID_HOST_FLAG_CTRL_ATTACHED); + + /* HID spec v1.1.2 Section 5.2.2: INTR shall be disconnected before CTRL, + * so the peer closing CTRL first leaves INTR to be cleaned up here. + */ + + if (atomic_test_bit(&hid->flags, BT_HID_HOST_FLAG_INTR_CONNECTED)) + { + hid_set_state(hid, BT_HID_STATE_DISCONNECTING); + + /* Closed from hid_close_handler(), not from here: see its comment. */ + + bt_work_reschedule(&hid->close_work, 0); + + return; + } + + /* INTR never came up but is still attached, so its disconnected callback + * is still to come and finishes the teardown there. Releasing the instance + * here would hand it to the next association, which that callback would + * then tear down. hid_l2cap_intr_connected() closes the channel if it does + * come up. + */ + + if (atomic_test_bit(&hid->flags, BT_HID_HOST_FLAG_INTR_ATTACHED)) + { + hid_set_state(hid, BT_HID_STATE_DISCONNECTING); + return; + } + + if ((hid_cb != NULL) && (hid_cb->disconnected != NULL)) + { + hid_cb->disconnected(hid); + } + + hid_cleanup(hid); +} + +static void hid_l2cap_intr_disconnected(struct bt_l2cap_chan *chan) +{ + struct bt_hid_host *hid = HID_HOST_BY_INTR_CHAN(chan); + __maybe_unused enum bt_hid_channel_type chtype; + + chtype = HID_CHAN_TYPE(chan); + + LOG_DBG("session %d disconnected (state %s)", chtype, + hid_state_str(hid->state)); + + DEBUGASSERT(chtype == BT_HID_CHANNEL_INTR); + + atomic_clear_bit(&hid->flags, BT_HID_HOST_FLAG_INTR_CONNECTED); + atomic_clear_bit(&hid->flags, BT_HID_HOST_FLAG_INTR_ATTACHED); + + /* HID spec v1.1.2 Section 5.2.2: the Interrupt channel closes before the + * Control channel. Whichever side closed Interrupt, the association is + * over, so Control is closed from here instead of waiting for the peer to + * do it, which would leave the association unusable for an unbounded time. + */ + + if (atomic_test_bit(&hid->flags, BT_HID_HOST_FLAG_CTRL_CONNECTED)) + { + hid_set_state(hid, BT_HID_STATE_DISCONNECTING); + + /* Closed from hid_close_handler(), not from here: see its comment. */ + + bt_work_reschedule(&hid->close_work, 0); + + return; + } + + /* CTRL is not up but still attached, so its disconnected callback finishes + * the teardown, see hid_l2cap_ctrl_disconnected(). + */ + + if (atomic_test_bit(&hid->flags, BT_HID_HOST_FLAG_CTRL_ATTACHED)) + { + hid_set_state(hid, BT_HID_STATE_DISCONNECTING); + return; + } + + if ((hid_cb != NULL) && (hid_cb->disconnected != NULL)) + { + hid_cb->disconnected(hid); + } + + hid_cleanup(hid); +} + +static int hid_handshake_handle(struct bt_hid_host *hid, + struct net_buf *buf, uint8_t param) +{ + struct bt_hid_req req; + + ARG_UNUSED(buf); + + /* HID spec v1.1.2 Section 3.2.1: a HANDSHAKE only ever acknowledges a + * request issued by this host, so one that arrives without an outstanding + * transaction is dropped. + */ + + if (!hid_req_take(hid, &req)) + { + LOG_DBG("Drop unexpected HANDSHAKE 0x%x", param); + return 0; + } + + switch (req.type) + { + case BT_HID_CTRL_TYPE_GET_REPORT: + /* HID spec v1.1.2 Section 3.2.1.1: a GET_REPORT the device refuses + * is + * answered with a HANDSHAKE, so there is no payload to hand up. + */ + + if ((hid_cb != NULL) && (hid_cb->get_report != NULL)) + { + hid_cb->get_report(hid, param, req.report_type, NULL); + } + break; + + case BT_HID_CTRL_TYPE_SET_REPORT: + if ((hid_cb != NULL) && (hid_cb->set_report != NULL)) + { + hid_cb->set_report(hid, param); + } + break; + + case BT_HID_CTRL_TYPE_GET_PROTOCOL: + if ((hid_cb != NULL) && (hid_cb->get_protocol != NULL)) + { + hid_cb->get_protocol(hid, param, + atomic_test_bit(&hid->flags, + BT_HID_HOST_FLAG_BOOT_MODE) + ? BT_HID_PROTOCOL_BOOT_MODE + : BT_HID_PROTOCOL_REPORT_MODE); + } + break; + + case BT_HID_CTRL_TYPE_SET_PROTOCOL: + /* HID spec v1.1.2 Section 3.1.2.6: the device acknowledges + * SET_PROTOCOL with a HANDSHAKE, so the new mode is only adopted + * once + * it has been accepted. Applications need it because Boot Protocol + * Mode always carries a Report ID (Section 3.3.1). + */ + + if (param == BT_HID_HS_RSP_SUCCESS) + { + bool boot = (req.protocol == BT_HID_PROTOCOL_BOOT_MODE); + + atomic_set_bit_to(&hid->flags, BT_HID_HOST_FLAG_BOOT_MODE, boot); + LOG_DBG("protocol mode %s", boot ? "boot" : "report"); + } + + if ((hid_cb != NULL) && (hid_cb->set_protocol != NULL)) + { + hid_cb->set_protocol(hid, param); + } + break; + + default: + LOG_WRN("HANDSHAKE for unknown transaction %u", req.type); + break; + } + + return 0; +} + +static int hid_data_handle(struct bt_hid_host *hid, + struct net_buf *buf, uint8_t param) +{ + uint8_t report_type = FIELD_GET(BT_HID_PARAM_REPORT_TYPE_MASK, param); + struct bt_hid_req req; + + /* A Control-channel DATA only ever answers a GET_* (a SET_* is acked by a + * HANDSHAKE), so peek at the pending type without consuming; the type is + * only acted on once hid_req_take() wins the atomic claim below. + */ + + if (!atomic_test_bit(&hid->flags, BT_HID_HOST_FLAG_REQ_PENDING)) + { + LOG_DBG("Drop unexpected CTRL DATA"); + return 0; + } + + switch (hid->req.type) + { + case BT_HID_CTRL_TYPE_GET_PROTOCOL: + { + bool boot; + + /* HID spec v1.1.2 Section 3.1.2.5: the reply to GET_PROTOCOL is a + * DATA message carrying a single protocol mode octet. + */ + + if (!hid_req_take(hid, &req)) + { + return 0; + } + + /* The transaction is now consumed, so a malformed reply is + * reported to the application as an error rather than dropped, + * which would leave the application waiting for a reply that can + * no longer arrive. + */ + + if (buf->len < sizeof(uint8_t)) + { + LOG_WRN("Malformed GET_PROTOCOL DATA (len %u)", buf->len); + if ((hid_cb != NULL) && (hid_cb->get_protocol != NULL)) + { + hid_cb->get_protocol(hid, BT_HID_HS_RSP_ERR_UNKNOWN, + atomic_test_bit( + &hid->flags, + BT_HID_HOST_FLAG_BOOT_MODE) ? + BT_HID_PROTOCOL_BOOT_MODE : + BT_HID_PROTOCOL_REPORT_MODE); + } + break; + } + + boot = (FIELD_GET(BT_HID_PROTOCOL_MASK, + net_buf_pull_u8(buf)) == + BT_HID_PROTOCOL_BOOT_MODE); + atomic_set_bit_to(&hid->flags, BT_HID_HOST_FLAG_BOOT_MODE, boot); + + if ((hid_cb != NULL) && (hid_cb->get_protocol != NULL)) + { + hid_cb->get_protocol(hid, BT_HID_HS_RSP_SUCCESS, + boot ? BT_HID_PROTOCOL_BOOT_MODE : + BT_HID_PROTOCOL_REPORT_MODE); + } + break; + } + + case BT_HID_CTRL_TYPE_GET_REPORT: + /* HID spec v1.1.2 Section 3.2.1 allows a single outstanding Control + * channel transaction, so a DATA message that arrives while a + * GET_REPORT is pending is the reply to it, whatever report type it + * carries. + */ + + if (!hid_req_take(hid, &req)) + { + return 0; + } + + if ((hid_cb != NULL) && (hid_cb->get_report != NULL)) + { + hid_cb->get_report(hid, BT_HID_HS_RSP_SUCCESS, report_type, buf); + } + break; + + default: + /* The outstanding transaction is a SET_*, which a HANDSHAKE (not a + * DATA) acknowledges, so this message does not belong to it and the + * transaction is left pending for its HANDSHAKE. + */ + + LOG_DBG("Drop unexpected CTRL DATA for transaction %u", + hid->req.type); + break; + } + + return 0; +} + +static int hid_control_handle(struct bt_hid_host *hid, + struct net_buf *buf, uint8_t control) +{ + struct bt_hid_req req; + + ARG_UNUSED(buf); + + /* HID spec v1.1.2 Section 3.1.2.2.3: VIRTUAL_CABLE_UNPLUG is the only + * HID_CONTROL message a device may send to a host, and a host shall ignore + * all other HID_CONTROL messages sent by devices. + */ + + if (control != BT_HID_CONTROL_VIRTUAL_CABLE_UNPLUG) + { + LOG_WRN("Control operation %u not handled, ignoring", control); + return 0; + } + + LOG_DBG("VC unplug"); + + /* HID spec v1.1.2 Section 3.1.2.2.3: the recipient should discard any + * control transfer that is pending when the unplug arrives, so the result + * of a request issued before it is never reported afterwards. + */ + + if (hid_req_take(hid, &req)) + { + LOG_DBG("transaction %s discarded on VC unplug", + hid_ctrl_type_str(req.type)); + } + + if ((hid_cb != NULL) && (hid_cb->vc_unplug != NULL)) + { + hid_cb->vc_unplug(hid); + } + + /* HID spec v1.1.2 Section 3.1.2.2.3: the recipient acknowledges the unplug + * by disconnecting INTR then CTRL rather than by a HANDSHAKE. The teardown + * is deferred to the Bluetooth workqueue so the channels are not + * disconnected from inside the receive callback. Destroying the bonding is + * left to the application, see the vc_unplug callback. + */ + + bt_work_schedule(&hid->vcu_work, 0); + + return 0; +} + +static int hid_intr_handle(struct bt_hid_host *hid, struct net_buf *buf) +{ + /* HID spec v1.1.2 Section 3.1.2.9: the payload is a report and is handed + * up as received. Whether it starts with a Report ID follows from the + * report descriptor and from the protocol mode, which the application + * owns. + */ + + if ((hid_cb != NULL) && (hid_cb->input_report != NULL)) + { + hid_cb->input_report(hid, buf); + } + + return 0; +} + +typedef int (*hid_ctrl_handler_t)(struct bt_hid_host *hid, + struct net_buf *buf, uint8_t param); + +static const struct +{ + uint8_t type; + hid_ctrl_handler_t handler; +} + +hid_l2cap_ctrl_handlers[] = +{ + { + BT_HID_MSG_TYPE_HANDSHAKE, hid_handshake_handle + }, + + { + BT_HID_MSG_TYPE_DATA, hid_data_handle + }, + + { + BT_HID_MSG_TYPE_CONTROL, hid_control_handle + }, +}; + +static int hid_l2cap_ctrl_recv(struct bt_l2cap_chan *chan, + struct net_buf *buf) +{ + struct bt_hid_hdr *hdr; + struct bt_hid_host *hid = HID_HOST_BY_CTRL_CHAN(chan); + uint8_t type; + uint8_t param; + + DEBUGASSERT(HID_CHAN_TYPE(chan) == BT_HID_CHANNEL_CTRL); + + /* HID spec v1.1.2 Section 5.2.2: the Control channel carries traffic as + * soon as it is open, independently of the Interrupt channel. Drop + * anything received before the Control channel itself is connected. + */ + + if (hid->state < BT_HID_STATE_CTRL_CONNECTED) + { + LOG_WRN("CTRL recv in state %s, ignoring", hid_state_str(hid->state)); + return 0; + } + + if (buf->len < sizeof(*hdr)) + { + LOG_ERR("CTRL buf len %u invalid", buf->len); + return 0; + } + + hdr = net_buf_pull_mem(buf, sizeof(*hdr)); + param = BT_HID_HDR_GET_PARAM(hdr->header); + type = BT_HID_HDR_GET_TYPE(hdr->header); + + LOG_DBG("CTRL recv type 0x%x param 0x%x", type, param); + + ARRAY_FOR_EACH(hid_l2cap_ctrl_handlers, i) + { + if (hid_l2cap_ctrl_handlers[i].type == type) + { + int err = hid_l2cap_ctrl_handlers[i].handler(hid, buf, param); + +/* HID spec v1.1.2 Section 3.1.2.1: HANDSHAKE shall not be + * sent by a host, so unlike the device side there is no way to + * report a malformed message to the peer and the error is only + * logged. + */ + + if (err != 0) + { + LOG_WRN("type %u handling failed (%d)", type, err); + } + + return 0; + } + } + + LOG_WRN("type %u not supported", type); + + return 0; +} + +static int hid_l2cap_intr_recv(struct bt_l2cap_chan *chan, + struct net_buf *buf) +{ + struct bt_hid_hdr *hdr; + struct bt_hid_host *hid = HID_HOST_BY_INTR_CHAN(chan); + uint8_t type; + uint8_t param; + uint8_t report_type; + + if (buf->len < sizeof(*hdr)) + { + LOG_ERR("INTR buf len %u invalid", buf->len); + return 0; + } + + DEBUGASSERT(HID_CHAN_TYPE(chan) == BT_HID_CHANNEL_INTR); + + if (hid->state != BT_HID_STATE_CONNECTED) + { + LOG_WRN("INTR recv in state %s, ignoring", hid_state_str(hid->state)); + return 0; + } + + hdr = net_buf_pull_mem(buf, sizeof(*hdr)); + type = BT_HID_HDR_GET_TYPE(hdr->header); + param = BT_HID_HDR_GET_PARAM(hdr->header); + + LOG_DBG("INTR recv type 0x%x param 0x%x", type, param); + + if (type != BT_HID_MSG_TYPE_DATA) + { + LOG_WRN("INTR type %u not supported", type); + return 0; + } + + /* HID spec v1.1.2 Section 3.2.2.1: an Interrupt IN transfer is a DATA + * message carrying an Input report from the device. Section 3.2.2 gives + * the Interrupt channel no acknowledgment mechanism, so anything else is + * silently dropped. + */ + + report_type = FIELD_GET(BT_HID_PARAM_REPORT_TYPE_MASK, param); + if (report_type != BT_HID_REPORT_TYPE_INPUT) + { + LOG_WRN("INTR DATA report type %u not Input, ignoring", report_type); + return 0; + } + + return hid_intr_handle(hid, buf); +} + +static const struct bt_l2cap_chan_ops ctrl_ops = +{ + .connected = hid_l2cap_ctrl_connected, + .disconnected = hid_l2cap_ctrl_disconnected, + .recv = hid_l2cap_ctrl_recv, +}; + +static const struct bt_l2cap_chan_ops intr_ops = +{ + .connected = hid_l2cap_intr_connected, + .disconnected = hid_l2cap_intr_disconnected, + .recv = hid_l2cap_intr_recv, +}; + +/* Attach a pool instance to a new association. The work items are + * initialized once for the whole pool from hid_init(), so this cannot fail. + */ + +static void hid_session_init(struct bt_hid_host *hid, enum bt_hid_role role) +{ + hid->ctrl_session.br_chan.chan.ops = &ctrl_ops; + /* HID spec v1.1.2 Section 5.2.3.1: a host in Report Protocol Mode shall + * use an L2CAP MTU of 672 octets or greater on both channels, and in all + * cases at least the L2CAP default minimum of 48 octets. + */ + + hid->ctrl_session.br_chan.rx.mtu = BT_L2CAP_RX_MTU; + hid->ctrl_session.type = BT_HID_CHANNEL_CTRL; + + hid->intr_session.br_chan.chan.ops = &intr_ops; + hid->intr_session.br_chan.rx.mtu = BT_L2CAP_RX_MTU; + hid->intr_session.type = BT_HID_CHANNEL_INTR; + + /* HID spec v1.1.2 Section 5.4.3.4.2: a HID Host shall use Security Mode 4, + * and Section 5.4.3.4.4 recommends Just Works unless active attacks are a + * concern, so encryption without authentication. An initiator sets the + * level itself; on the acceptor path L2CAP takes it from the server. + */ + + hid->ctrl_session.br_chan.required_sec_level = BT_SECURITY_L2; + hid->intr_session.br_chan.required_sec_level = BT_SECURITY_L2; + + /* Clears every state flag: no transaction is outstanding, both channels + * are disconnected, and per HID spec v1.1.2 Section 2.1.2 the default + * protocol mode is Report Protocol Mode (BT_HID_HOST_FLAG_BOOT_MODE + * unset). The role is stored right after, so it survives the reset. + */ + + atomic_set(&hid->flags, 0); + atomic_set_bit_to(&hid->flags, BT_HID_HOST_FLAG_INITIATOR, + role == BT_HID_ROLE_INITIATOR); +} + +static int hid_l2cap_ctrl_accept( + struct bt_conn *conn, struct bt_l2cap_server *server, + struct bt_l2cap_chan **chan) +{ + struct bt_hid_host *hid; + + LOG_DBG("CTRL accept conn %p", conn); + + if (hid_cb == NULL) + { + LOG_ERR("HID Host not registered"); + return -ENOTSUP; + } + + hid = hid_allocate(conn); + if (hid == NULL) + { + return -EBUSY; + } + + hid_session_init(hid, BT_HID_ROLE_ACCEPTOR); + + hid_set_state(hid, BT_HID_STATE_CTRL_CONNECTING); + atomic_set_bit(&hid->flags, BT_HID_HOST_FLAG_CTRL_ATTACHED); + + *chan = &hid->ctrl_session.br_chan.chan; + return 0; +} + +static int hid_l2cap_intr_accept( + struct bt_conn *conn, struct bt_l2cap_server *server, + struct bt_l2cap_chan **chan) +{ + struct bt_hid_host *hid; + + LOG_DBG("INTR accept conn %p", conn); + + /* HID spec v1.1.2 Section 5.2.2: the control channel is opened first and + * has to be established before the interrupt channel. The state also rules + * out a second interrupt channel: it leaves BT_HID_STATE_CTRL_CONNECTED as + * soon as one is accepted. + */ + + hid = hid_find(conn); + if ((hid == NULL) || (hid->state != BT_HID_STATE_CTRL_CONNECTED)) + { + LOG_ERR("CTRL channel not connected"); + return -ENOTCONN; + } + + hid_set_state(hid, BT_HID_STATE_INTR_CONNECTING); + atomic_set_bit(&hid->flags, BT_HID_HOST_FLAG_INTR_ATTACHED); + + *chan = &hid->intr_session.br_chan.chan; + return 0; +} + +/* Security Mode 4 on both PSMs, see hid_session_init() for the level. */ + +static struct bt_l2cap_server hid_server_ctrl = +{ + .psm = BT_L2CAP_PSM_HID_CTRL, + .sec_level = BT_SECURITY_L2, + .accept = hid_l2cap_ctrl_accept, +}; + +static struct bt_l2cap_server hid_server_intr = +{ + .psm = BT_L2CAP_PSM_HID_INTR, + .sec_level = BT_SECURITY_L2, + .accept = hid_l2cap_intr_accept, +}; + +static int hid_init(void) +{ + static bool initialized; + int err; + + LOG_DBG("init"); + + /* A k_work_delayable may not be re-initialized while the kernel still + * holds it, and an item cancelled on teardown can outlive the association + * that armed it. Initializing the pool once keeps instance reuse free of + * that. + */ + + if (!initialized) + { + ARRAY_FOR_EACH(hid_conn_pool, i) + { + k_work_init_delayable(&hid_conn_pool[i].close_work, + hid_close_handler); + k_work_init_delayable(&hid_conn_pool[i].trans_work, + hid_trans_timeout_handler); + k_work_init_delayable(&hid_conn_pool[i].vcu_work, + hid_vcu_work_handler); + } + + initialized = true; + } + + err = bt_l2cap_br_server_register(&hid_server_ctrl); + if (err != 0) + { + LOG_ERR("CTRL L2CAP register failed (%d)", err); + return err; + } + + err = bt_l2cap_br_server_register(&hid_server_intr); + if (err != 0) + { + LOG_ERR("INTR L2CAP register failed (%d)", err); + bt_l2cap_br_server_unregister(&hid_server_ctrl); + return err; + } + + return 0; +} + +static void hid_deinit(void) +{ + int err; + + LOG_DBG("deinit"); + + err = bt_l2cap_br_server_unregister(&hid_server_ctrl); + if (err != 0) + { + LOG_ERR("CTRL L2CAP unregister failed (%d)", err); + } + + err = bt_l2cap_br_server_unregister(&hid_server_intr); + if (err != 0) + { + LOG_ERR("INTR L2CAP unregister failed (%d)", err); + } +} + +int bt_hid_host_register(const struct bt_hid_host_cb *cb) +{ + int err; + + LOG_DBG("register"); + + if (cb == NULL) + { + return -EINVAL; + } + + if (hid_cb != NULL) + { + return -EALREADY; + } + + err = hid_init(); + if (err != 0) + { + return err; + } + + hid_cb = cb; + return 0; +} + +int bt_hid_host_unregister(void) +{ + LOG_DBG("unregister"); + + if (hid_cb == NULL) + { + return -EALREADY; + } + + ARRAY_FOR_EACH(hid_conn_pool, i) + { + if (hid_conn_pool[i].state != BT_HID_STATE_DISCONNECTED) + { + LOG_ERR("cannot unregister while connected (state %s)", + hid_state_str(hid_conn_pool[i].state)); + return -EBUSY; + } + } + + hid_deinit(); + hid_cb = NULL; + + return 0; +} + +int bt_hid_host_connect(struct bt_conn *conn, struct bt_hid_host **hid) +{ + struct bt_hid_host *inst; + int err; + + LOG_DBG("connect"); + + if ((conn == NULL) || (hid == NULL)) + { + return -EINVAL; + } + + if (hid_cb == NULL) + { + LOG_ERR("HID Host not registered"); + return -ENOTSUP; + } + + inst = hid_allocate(conn); + if (inst == NULL) + { + return -EBUSY; + } + + hid_session_init(inst, BT_HID_ROLE_INITIATOR); + + hid_set_state(inst, BT_HID_STATE_CTRL_CONNECTING); + + /* HID spec v1.1.2 Section 5.2.2: the Control channel is opened first and + * the Interrupt channel follows from hid_l2cap_ctrl_connected(). + */ + + atomic_set_bit(&inst->flags, BT_HID_HOST_FLAG_CTRL_ATTACHED); + + err = bt_l2cap_chan_connect(conn, &inst->ctrl_session.br_chan.chan, + BT_L2CAP_PSM_HID_CTRL); + if (err != 0) + { + LOG_ERR("CTRL connect failed (%d)", err); + /* L2CAP can fail through its own channel cleanup, which already ran + * the disconnected callback and released the instance. + */ + + if (inst->state != BT_HID_STATE_DISCONNECTED) + { + atomic_clear_bit(&inst->flags, BT_HID_HOST_FLAG_CTRL_ATTACHED); + hid_cleanup(inst); + } + + return err; + } + + *hid = inst; + return 0; +} + +int bt_hid_host_disconnect(struct bt_hid_host *hid) +{ + int err = 0; + + if (hid == NULL) + { + return -EINVAL; + } + + LOG_DBG("disconnect"); + + if ((hid->state == BT_HID_STATE_DISCONNECTED) || + (hid->state == BT_HID_STATE_DISCONNECTING)) + { + LOG_ERR("not connected (state %s)", hid_state_str(hid->state)); + return -ENOTCONN; + } + + /* HID spec v1.1.2 Section 5.2.2: the channels are closed in reverse + * order, INTR first and CTRL once INTR has closed. + */ + + if (atomic_test_bit(&hid->flags, BT_HID_HOST_FLAG_INTR_CONNECTED)) + { + err = bt_l2cap_chan_disconnect(&hid->intr_session.br_chan.chan); + } + else if (atomic_test_bit(&hid->flags, BT_HID_HOST_FLAG_CTRL_CONNECTED)) + { + err = bt_l2cap_chan_disconnect(&hid->ctrl_session.br_chan.chan); + } + + /* -EALREADY means the channel is already tearing down, which matches + * the intent of this call; only update state once the request is accepted + * so a failed disconnect leaves the state untouched. + */ + + if ((err != 0) && (err != -EALREADY)) + { + LOG_ERR("chan disconnect failed (%d)", err); + return err; + } + + hid_set_state(hid, BT_HID_STATE_DISCONNECTING); + + return 0; +} + +/* Common pre-flight validation for everything sent on the Control channel: + * the instance is valid and its Control channel is ready to carry a request. + * + * The message header depends on the report type and on whether a ReportID is + * in use, so the PDU has to be built by the caller. Validating first avoids + * allocating and formatting one that would then be rejected. Serializing + * against a concurrent request is left to hid_req_claim(), not checked here. + */ + +static int hid_check_ready(const struct bt_hid_host *hid) +{ + if (hid == NULL) + { + return -EINVAL; + } + + /* HID spec v1.1.2 Section 5.2.2: Control channel commands may be sent once + * the Control channel is open, the Interrupt channel need not be + * established. A teardown in progress is still refused, and the Control + * channel outlives the Interrupt one there, hence the state check. + */ + + if (!atomic_test_bit(&hid->flags, BT_HID_HOST_FLAG_CTRL_CONNECTED) || + (hid->state == BT_HID_STATE_DISCONNECTING)) + { + LOG_ERR("CTRL not ready (state %s)", hid_state_str(hid->state)); + return -ENOTCONN; + } + + return 0; +} + +/* Build a Control channel PDU holding only the HIDP header. */ + +static struct net_buf *hid_create_ctrl_pdu(uint8_t type, uint8_t param) +{ + struct net_buf *buf; + struct bt_hid_hdr *hdr; + + buf = bt_hid_host_create_pdu(NULL); + if (buf == NULL) + { + return NULL; + } + + hdr = net_buf_add(buf, sizeof(struct bt_hid_hdr)); + hdr->header = BT_HID_BUILD_HDR(type, param); + + return buf; +} + +/* Send a Control channel PDU and, when it is a request, claim the + * transaction and arm its timer first. + * + * Claiming and sending are kept in one function so that a request can never + * become outstanding without a timer, a timer can never outlive its request, + * and every path that fails to reach the air releases the claim again. + * + * @param req NULL for HID_CONTROL, which HID spec v1.1.2 Section 3.1.2.2 + * leaves unacknowledged. + */ + +static int hid_send_ctrl(struct bt_hid_host *hid, + struct net_buf *buf, const struct bt_hid_req *req) +{ + int err; + + if (req != NULL) + { + err = hid_req_claim(hid, req); + if (err != 0) + { + return err; + } + } + + err = bt_l2cap_chan_send(&hid->ctrl_session.br_chan.chan, buf); + if (err != 0) + { + if (req != NULL) + { + hid_req_abort(hid); + } + + return err; + } + + return 0; +} + +int bt_hid_host_get_report( + struct bt_hid_host *hid, uint8_t type, uint8_t report_id, + uint16_t buffer_size) +{ + struct bt_hid_req req = + { + .type = BT_HID_CTRL_TYPE_GET_REPORT, + .report_type = type, + .buffer_size = buffer_size, + }; + + struct net_buf *buf; + uint8_t param; + int err; + + LOG_DBG("get report type %u size %u", type, buffer_size); + + err = hid_check_ready(hid); + if (err != 0) + { + return err; + } + + /* HID spec v1.1.2 Table 3.4: report type 0 is reserved. */ + + if ((type != BT_HID_REPORT_TYPE_INPUT) && + (type != BT_HID_REPORT_TYPE_OUTPUT) && + (type != BT_HID_REPORT_TYPE_FEATURE)) + { + return -EINVAL; + } + + /* HID spec v1.1.2 Section 3.1.2.3: BufferSize is the maximum length of the + * DATA message the device may answer with, so asking for more than the + * Control channel can receive would only guarantee the reply is lost. + */ + + if (buffer_size > + (hid->ctrl_session.br_chan.rx.mtu - sizeof(struct bt_hid_hdr))) + { + LOG_ERR("buffer size %u exceeds CTRL RX MTU %u", buffer_size, + hid->ctrl_session.br_chan.rx.mtu); + return -EINVAL; + } + + param = FIELD_PREP(BT_HID_PARAM_REPORT_TYPE_MASK, type); + if (buffer_size > 0U) + { + param |= BT_HID_PARAM_REPORT_SIZE_MASK; + } + + buf = hid_create_ctrl_pdu(BT_HID_MSG_TYPE_GET_REPORT, param); + if (buf == NULL) + { + return -ENOBUFS; + } + + /* HID spec v1.1.2 Section 3.1.2.3: the ReportID field, when present, + * immediately follows the request octet and the BufferSize field follows + * the ReportID. Report ID 0 is reserved by the USB HID specification, so + * it stands for "no Report ID in use" here, the same way a buffer size of + * 0 leaves out the BufferSize field. Only the application knows the report + * descriptor, so it decides whether the field is sent. + */ + + if (report_id != 0U) + { + net_buf_add_u8(buf, report_id); + } + + if (buffer_size > 0U) + { + net_buf_add_le16(buf, buffer_size); + } + + err = hid_send_ctrl(hid, buf, &req); + if (err != 0) + { + LOG_ERR("Get_Report send failed (%d)", err); + net_buf_unref(buf); + return err; + } + + return 0; +} + +int bt_hid_host_set_report(struct bt_hid_host *hid, + uint8_t type, struct net_buf *buf) +{ + struct bt_hid_req req = + { + .type = BT_HID_CTRL_TYPE_SET_REPORT, + .report_type = type, + }; + + struct bt_hid_hdr *hdr; + int err; + + LOG_DBG("set report type %u len %u", type, buf != NULL ? buf->len : 0U); + + err = hid_check_ready(hid); + if (err != 0) + { + return err; + } + + if (buf == NULL) + { + return -EINVAL; + } + + /* HID spec v1.1.2 Table 3.6: report type 0 is reserved. */ + + if ((type != BT_HID_REPORT_TYPE_INPUT) && + (type != BT_HID_REPORT_TYPE_OUTPUT) && + (type != BT_HID_REPORT_TYPE_FEATURE)) + { + return -EINVAL; + } + + if (net_buf_headroom(buf) < sizeof(struct bt_hid_hdr)) + { + return -ENOMEM; + } + + hdr = net_buf_push(buf, sizeof(struct bt_hid_hdr)); + hdr->header = BT_HID_BUILD_HDR(BT_HID_MSG_TYPE_SET_REPORT, + FIELD_PREP(BT_HID_PARAM_REPORT_TYPE_MASK, + type)); + + err = hid_send_ctrl(hid, buf, &req); + if (err != 0) + { + LOG_ERR("Set_Report send failed (%d)", err); + /* The caller keeps the buffer when the request fails, so the header + * added above is removed again to hand it back unchanged. Otherwise a + * retry would prepend a second header. + */ + + net_buf_pull(buf, sizeof(struct bt_hid_hdr)); + return err; + } + + return 0; +} + +int bt_hid_host_get_protocol(struct bt_hid_host *hid) +{ + struct bt_hid_req req = + { + .type = BT_HID_CTRL_TYPE_GET_PROTOCOL, + }; + + struct net_buf *buf; + int err; + + LOG_DBG("get protocol"); + + err = hid_check_ready(hid); + if (err != 0) + { + return err; + } + + buf = hid_create_ctrl_pdu(BT_HID_MSG_TYPE_GET_PROTOCOL, 0U); + if (buf == NULL) + { + return -ENOBUFS; + } + + err = hid_send_ctrl(hid, buf, &req); + if (err != 0) + { + LOG_ERR("Get_Protocol send failed (%d)", err); + net_buf_unref(buf); + return err; + } + + return 0; +} + +int bt_hid_host_set_protocol(struct bt_hid_host *hid, uint8_t protocol) +{ + struct bt_hid_req req = + { + .type = BT_HID_CTRL_TYPE_SET_PROTOCOL, + .protocol = protocol, + }; + + struct net_buf *buf; + int err; + + LOG_DBG("set protocol %u", protocol); + + err = hid_check_ready(hid); + if (err != 0) + { + return err; + } + + if ((protocol != BT_HID_PROTOCOL_BOOT_MODE) && + (protocol != BT_HID_PROTOCOL_REPORT_MODE)) + { + return -EINVAL; + } + + buf = hid_create_ctrl_pdu(BT_HID_MSG_TYPE_SET_PROTOCOL, + FIELD_PREP(BT_HID_PROTOCOL_MASK, protocol)); + if (buf == NULL) + { + return -ENOBUFS; + } + + /* The mode is adopted only when the device acknowledges the request with a + * successful HANDSHAKE, see hid_handshake_handle(). It travels with the + * transaction, which is claimed before the PDU is sent, so the RX thread + * always sees it. + */ + + err = hid_send_ctrl(hid, buf, &req); + if (err != 0) + { + LOG_ERR("Set_Protocol send failed (%d)", err); + net_buf_unref(buf); + return err; + } + + return 0; +} + +int bt_hid_host_output_report(struct bt_hid_host *hid, struct net_buf *buf) +{ + struct bt_hid_hdr *hdr; + int err; + + LOG_DBG("output report len %u", buf != NULL ? buf->len : 0U); + + if ((hid == NULL) || (buf == NULL)) + { + return -EINVAL; + } + + if (hid->state != BT_HID_STATE_CONNECTED) + { + LOG_ERR("not connected (state %s)", hid_state_str(hid->state)); + return -ENOTCONN; + } + + if (net_buf_headroom(buf) < sizeof(struct bt_hid_hdr)) + { + return -ENOMEM; + } + + hdr = net_buf_push(buf, sizeof(struct bt_hid_hdr)); + hdr->header = BT_HID_BUILD_HDR(BT_HID_MSG_TYPE_DATA, + FIELD_PREP(BT_HID_PARAM_REPORT_TYPE_MASK, + BT_HID_REPORT_TYPE_OUTPUT)); + + err = bt_l2cap_chan_send(&hid->intr_session.br_chan.chan, buf); + if (err != 0) + { + LOG_ERR("Output_Report send failed (%d)", err); + /* Restore the buffer to the caller's layout so a retry does not + * prepend a second header, mirroring bt_hid_host_set_report(). + */ + + net_buf_pull(buf, sizeof(struct bt_hid_hdr)); + return err; + } + + return 0; +} + +int bt_hid_host_suspend(struct bt_hid_host *hid) +{ + struct net_buf *buf; + int err; + + LOG_DBG("suspend"); + + err = hid_check_ready(hid); + if (err != 0) + { + return err; + } + + /* HID spec v1.1.2 Section 3.1.2.2: HID_CONTROL has no HANDSHAKE + * response, so no transaction state is tracked for it. + */ + + buf = hid_create_ctrl_pdu(BT_HID_MSG_TYPE_CONTROL, BT_HID_CONTROL_SUSPEND); + if (buf == NULL) + { + return -ENOBUFS; + } + + err = hid_send_ctrl(hid, buf, NULL); + if (err != 0) + { + LOG_ERR("Suspend send failed (%d)", err); + net_buf_unref(buf); + return err; + } + + return 0; +} + +int bt_hid_host_exit_suspend(struct bt_hid_host *hid) +{ + struct net_buf *buf; + int err; + + LOG_DBG("exit suspend"); + + err = hid_check_ready(hid); + if (err != 0) + { + return err; + } + + buf = hid_create_ctrl_pdu(BT_HID_MSG_TYPE_CONTROL, + BT_HID_CONTROL_EXIT_SUSPEND); + if (buf == NULL) + { + return -ENOBUFS; + } + + err = hid_send_ctrl(hid, buf, NULL); + if (err != 0) + { + LOG_ERR("Exit_Suspend send failed (%d)", err); + net_buf_unref(buf); + return err; + } + + return 0; +} + +static void hid_vcu_tx_cb(struct bt_conn *conn, void *user_data, int err) +{ + struct bt_hid_host *hid; + + if ((conn == NULL) || (user_data == NULL)) + { + return; + } + + hid = (struct bt_hid_host *)user_data; + + /* Runs on the TX notify workqueue, so it can arrive after the peer already + * acknowledged the unplug and the pool slot was reused. Consuming the + * claim taken before the send ties this completion to the association that + * sent the PDU: hid_cleanup() clears the flags. + */ + + if (!atomic_test_and_clear_bit(&hid->flags, BT_HID_HOST_FLAG_VCU_SENT)) + { + LOG_DBG("VCU completion for a released association, ignoring"); + return; + } + + /* HID spec v1.1.2 Section 3.1.2.2.3: the recipient acknowledges the unplug + * by disconnecting the Interrupt channel and then the Control channel, so + * the sender waits for that instead of closing the channels itself. The + * teardown scheduled here only covers a peer that never acknowledges; one + * that does closes the association first, which cancels this work. It runs + * on the Bluetooth workqueue because this callback runs in the TX context. + */ + + if (err != 0) + { + /* The PDU never reached the controller, so the device cannot have + * seen the unplug and no acknowledgment is coming. + */ + + LOG_WRN("VCU control message send failed (%d)", err); + bt_work_schedule(&hid->vcu_work, 0); + } + else + { + bt_work_schedule(&hid->vcu_work, HID_TRANS_TIMEOUT); + } + + /* hid_cleanup() may have raced the claim above; a released instance here + * means the work just armed has to be taken back. + */ + + if (hid->state == BT_HID_STATE_DISCONNECTED) + { + k_work_cancel_delayable(&hid->vcu_work); + } +} + +int bt_hid_host_virtual_cable_unplug(struct bt_hid_host *hid) +{ + struct net_buf *buf; + int err; + + LOG_DBG("virtual cable unplug"); + + err = hid_check_ready(hid); + if (err != 0) + { + return err; + } + + buf = hid_create_ctrl_pdu(BT_HID_MSG_TYPE_CONTROL, + BT_HID_CONTROL_VIRTUAL_CABLE_UNPLUG); + if (buf == NULL) + { + return -ENOBUFS; + } + + /* The tx callback arms the fallback teardown only once the PDU is on its + * way, so a peer that acknowledges the unplug by closing the channels is + * never raced by a local disconnect. The claim lets that callback tell + * whether the association it was sent for still exists. + */ + + atomic_set_bit(&hid->flags, BT_HID_HOST_FLAG_VCU_SENT); + + err = bt_l2cap_br_chan_send_cb(&hid->ctrl_session.br_chan.chan, buf, + hid_vcu_tx_cb, hid); + if (err != 0) + { + LOG_ERR("VCU send failed (%d)", err); + atomic_clear_bit(&hid->flags, BT_HID_HOST_FLAG_VCU_SENT); + net_buf_unref(buf); + return err; + } + + /* Destroying the bonding is left to the application, see the API doc. */ + + return 0; +} diff --git a/libs/zblue/host/classic/hid_internal.h b/libs/zblue/host/classic/hid_internal.h new file mode 100644 index 0000000000000..327ccb3361208 --- /dev/null +++ b/libs/zblue/host/classic/hid_internal.h @@ -0,0 +1,342 @@ +/**************************************************************************** + * libs/zblue/host/classic/hid_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Internal APIs shared by the Bluetooth HID Device and Host + * profiles. + */ + +/* Copyright 2025 Xiaomi Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/** @brief HID header size (1 byte). */ +#define BT_HID_HDR_SIZE 1U + +/** @brief HID PSM values for control/interrupt channels. */ +#define BT_L2CAP_PSM_HID_CTRL 0x0011 +#define BT_L2CAP_PSM_HID_INTR 0x0013 + +/** @brief HIDP message types (upper nibble of HID header). */ +#define BT_HID_MSG_TYPE_HANDSHAKE 0x00 +#define BT_HID_MSG_TYPE_CONTROL 0x01 +#define BT_HID_MSG_TYPE_GET_REPORT 0x04 +#define BT_HID_MSG_TYPE_SET_REPORT 0x05 +#define BT_HID_MSG_TYPE_GET_PROTOCOL 0x06 +#define BT_HID_MSG_TYPE_SET_PROTOCOL 0x07 +#define BT_HID_MSG_TYPE_GET_IDLE 0x08 +#define BT_HID_MSG_TYPE_SET_IDLE 0x09 +#define BT_HID_MSG_TYPE_DATA 0x0a +#define BT_HID_MSG_TYPE_DATAC 0x0b + +/** @brief HID_CONTROL operations (lower nibble of HID header). */ +#define BT_HID_CONTROL_NOP 0x00 +#define BT_HID_CONTROL_HARD_RESET 0x01 +#define BT_HID_CONTROL_SOFT_RESET 0x02 +#define BT_HID_CONTROL_SUSPEND 0x03 +#define BT_HID_CONTROL_EXIT_SUSPEND 0x04 +#define BT_HID_CONTROL_VIRTUAL_CABLE_UNPLUG 0x05 + +/** @brief Mask for HID protocol parameter in SET/GET_PROTOCOL (bit 0). */ +#define BT_HID_PROTOCOL_MASK GENMASK(0, 0) + +/** @brief Report type field mask (lower two bits of parameter). */ +#define BT_HID_PARAM_REPORT_TYPE_MASK GENMASK(1, 0) +/** @brief Report size present flag in parameter field. + * + * HID spec v1.1.2 Table 3.4: within the GET_REPORT header octet bit 3 is + * the Size flag and bit 2 is reserved, so the flag is bit 3 of the + * parameter. + */ +#define BT_HID_PARAM_REPORT_SIZE_MASK BIT(3) + +/** @brief HID header field masks (1 byte): upper nibble = message type, + * lower nibble = parameter. + */ +#define BT_HID_HDR_TYPE_MASK GENMASK(7, 4) +#define BT_HID_HDR_PARAM_MASK GENMASK(3, 0) + +/** @brief HID header encoding/decoding helpers. */ +#define BT_HID_BUILD_HDR(t, p) \ + (uint8_t)(FIELD_PREP(BT_HID_HDR_TYPE_MASK, (t)) | FIELD_PREP(BT_HID_HDR_PARAM_MASK, (p))) +#define BT_HID_HDR_GET_TYPE(x) FIELD_GET(BT_HID_HDR_TYPE_MASK, (x)) +#define BT_HID_HDR_GET_PARAM(x) FIELD_GET(BT_HID_HDR_PARAM_MASK, (x)) + +/** @brief HID header byte stored in a packed struct for buffer access. */ + +struct bt_hid_hdr +{ + uint8_t header; +} __packed; + +/** @brief Type of the HID channel */ + +enum bt_hid_channel_type +{ + /** Channel type unknown or not initialized. */ + + BT_HID_CHANNEL_UNKNOWN = 0, + + /** Control channel. */ + + BT_HID_CHANNEL_CTRL, + + /** Interrupt channel. */ + + BT_HID_CHANNEL_INTR, +}; + +/** @brief Role of the HID device relative to the remote peer */ + +enum bt_hid_role +{ + /** Local device accepted an incoming connection (server role). */ + + BT_HID_ROLE_ACCEPTOR = 0, + + /** Local device initiated the connection (client role). */ + + BT_HID_ROLE_INITIATOR +}; + +/** @brief HID device state machine values. */ + +enum bt_hid_state +{ + BT_HID_STATE_DISCONNECTED = 0x00, + BT_HID_STATE_CTRL_CONNECTING = 0x01, + BT_HID_STATE_CTRL_CONNECTED = 0x02, + BT_HID_STATE_INTR_CONNECTING = 0x03, + BT_HID_STATE_CONNECTED = 0x04, + BT_HID_STATE_DISCONNECTING = 0x05, +}; + +/** @brief HID session wrapper for an L2CAP channel */ + +struct bt_hid_session +{ + /** Underlying BR/EDR L2CAP channel. */ + + struct bt_l2cap_br_chan br_chan; + + /** Channel type (control or interrupt). */ + + enum bt_hid_channel_type type; +}; + +/** @brief HID device instance (opaque to applications) */ + +struct bt_hid_device +{ + /** Role of this device (initiator or acceptor). */ + + enum bt_hid_role role; + + /** Control channel session. */ + + struct bt_hid_session ctrl_session; + + /** Interrupt channel session. */ + + struct bt_hid_session intr_session; + + /** True when the HID device is in Boot Protocol Mode. */ + + bool boot_mode; + + /** Runtime connection state. */ + + uint8_t state; + + /** True while the control channel is connected. */ + + bool ctrl_connected; + + /** True while the interrupt channel is connected. */ + + bool intr_connected; + + struct k_work_delayable intr_timeout; + struct k_work_delayable vcu_disconnect; +}; + +/** @brief Type of a Control channel request the HID Host can issue. + * + * HID spec v1.1.2 Section 3.2.1 allows a single outstanding transaction on + * the Control channel, so the type of the outstanding request also + * identifies which reply is expected. + */ + +enum bt_hid_ctrl_type +{ + /** No transaction outstanding. */ + + BT_HID_CTRL_TYPE_NONE = 0, + BT_HID_CTRL_TYPE_GET_REPORT, + BT_HID_CTRL_TYPE_SET_REPORT, + BT_HID_CTRL_TYPE_GET_PROTOCOL, + BT_HID_CTRL_TYPE_SET_PROTOCOL, +}; + +/** @brief HID Host per-connection state flags stored in @ref + * bt_hid_host.flags. + * + * @ref BT_HID_HOST_FLAG_REQ_PENDING and @ref BT_HID_HOST_FLAG_VCU_SENT are + * claimed and released from different contexts, the application thread and + * the TX notify workqueue against the L2CAP receive chain, so they have to + * be atomic. @ref BT_HID_HOST_FLAG_INITIATOR is written once while the + * session is set up and only read afterwards. The two @c ATTACHED flags are + * set by whoever hands the channel to L2CAP; the rest are written from + * the receive chain. + * + * The whole set is cleared when the instance is released, which is also what + * tells a concurrent context that the association is gone, see + * hid_cleanup(). + */ + +enum bt_hid_host_flag +{ + /** A Control channel transaction is outstanding, see @ref bt_hid_req. */ + + BT_HID_HOST_FLAG_REQ_PENDING, + /** A Virtual Cable Unplug has been handed to L2CAP and its completion + * callback has not run yet. + */ + + BT_HID_HOST_FLAG_VCU_SENT, + /** Set when the local device initiated the connection, see @ref + * bt_hid_role. Cleared for @ref BT_HID_ROLE_ACCEPTOR. + */ + + BT_HID_HOST_FLAG_INITIATOR, + + /** The remote device is in Boot Protocol Mode. */ + + BT_HID_HOST_FLAG_BOOT_MODE, + + /** The control channel is connected. */ + + BT_HID_HOST_FLAG_CTRL_CONNECTED, + /** The control channel is attached to the ACL connection, so L2CAP still + * owns it and its disconnected() callback is still to come. Unlike + * @ref BT_HID_HOST_FLAG_CTRL_CONNECTED this also covers a channel that is + * only connecting. + */ + + BT_HID_HOST_FLAG_CTRL_ATTACHED, + + /** The interrupt channel is connected. */ + + BT_HID_HOST_FLAG_INTR_CONNECTED, + + /** As @ref BT_HID_HOST_FLAG_CTRL_ATTACHED, for the interrupt channel. */ + + BT_HID_HOST_FLAG_INTR_ATTACHED, +}; + +/** @brief Outstanding Control channel transaction of a HID Host connection. + * + * Filled in by the requesting thread and consumed either by the RX thread + * when the reply arrives or by the transaction timeout, see + * @ref BT_HID_HOST_FLAG_REQ_PENDING. + */ + +struct bt_hid_req +{ + /** Transaction type. */ + + uint8_t type; + + /** Report type sent with GET_REPORT, echoed by the DATA reply. */ + + uint8_t report_type; + + /** BufferSize sent with GET_REPORT, 0 when the field was omitted. */ + + uint16_t buffer_size; + + /** Protocol mode sent with SET_PROTOCOL. */ + + uint8_t protocol; +}; + +/** @brief HID Host session wrapper for an L2CAP channel. + * + * Each HID Host connection maintains two sessions: control and interrupt. + */ + +struct bt_hid_host_session +{ + /** Underlying BR/EDR L2CAP channel. */ + + struct bt_l2cap_br_chan br_chan; + + /** Channel type: control or interrupt. */ + + uint8_t type; +}; + +/** @brief HID Host instance (opaque to applications) */ + +struct bt_hid_host +{ + /** Control channel session (PSM 0x0011). */ + + struct bt_hid_host_session ctrl_session; + + /** Interrupt channel session (PSM 0x0013). */ + + struct bt_hid_host_session intr_session; + + /** Runtime connection state. */ + + uint8_t state; + + /** Per-connection state flags, see @ref bt_hid_host_flag. + * + * Also carries the connection role, see @ref BT_HID_HOST_FLAG_INITIATOR. + */ + + atomic_t flags; + /** Outstanding Control channel transaction. + * + * The contents are only valid while @ref BT_HID_HOST_FLAG_REQ_PENDING is + * set. + */ + + struct bt_hid_req req; + /** Closes whichever channel L2CAP still owns. + * + * Used both as the INTR channel connect timeout, where CTRL came up but + * the device never opened INTR, and to close the second channel of an + * association whose first channel is already gone. The latter cannot be + * done from the L2CAP callback that reported it, see hid_close_handler(). + */ + + struct k_work_delayable close_work; + + /** Control channel transaction timeout. */ + + struct k_work_delayable trans_work; + /** Virtual Cable Unplug teardown. + * + * Runs immediately when the unplug was received, where HID spec v1.1.2 + * Section 3.1.2.2.3 makes this host the one that disconnects, and after a + * delay when the unplug was sent, where it only covers a peer that never + * acknowledges. + */ + + struct k_work_delayable vcu_work; +}; diff --git a/libs/zblue/host/classic/keys_br.c b/libs/zblue/host/classic/keys_br.c new file mode 100644 index 0000000000000..fe6c7bfba4202 --- /dev/null +++ b/libs/zblue/host/classic/keys_br.c @@ -0,0 +1,297 @@ +/**************************************************************************** + * libs/zblue/host/classic/keys_br.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* keys_br.c - Bluetooth BR/EDR key handling */ + +/* Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#define LOG_LEVEL CONFIG_BT_KEYS_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_keys_br); + +static struct bt_keys_link_key key_pool[CONFIG_BT_MAX_PAIRED]; + +#if defined(CONFIG_BT_KEYS_OVERWRITE_OLDEST) +static uint32_t aging_counter_val; +static struct bt_keys_link_key *last_keys_updated; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +#endif /* CONFIG_BT_KEYS_OVERWRITE_OLDEST */ + +struct bt_keys_link_key *bt_keys_find_link_key(const bt_addr_t *addr) +{ + struct bt_keys_link_key *key; + int i; + + LOG_DBG("%s", bt_addr_str(addr)); + + for (i = 0; i < ARRAY_SIZE(key_pool); i++) + { + key = &key_pool[i]; + + if (bt_addr_eq(&key->addr, addr)) + { + return key; + } + } + + return NULL; +} + +struct bt_keys_link_key *bt_keys_get_link_key(const bt_addr_t *addr) +{ + struct bt_keys_link_key *key; + + key = bt_keys_find_link_key(addr); + if (key) + { + return key; + } + + key = bt_keys_find_link_key(BT_ADDR_ANY); +#if defined(CONFIG_BT_KEYS_OVERWRITE_OLDEST) + if (!key) + { + int i; + + key = &key_pool[0]; + for (i = 1; i < ARRAY_SIZE(key_pool); i++) + { + struct bt_keys_link_key *current = &key_pool[i]; + + if (current->aging_counter < key->aging_counter) + { + key = current; + } + } + + if (key) + { + bt_keys_link_key_clear(key); + } + } +#endif + + if (key) + { + bt_addr_copy(&key->addr, addr); +#if defined(CONFIG_BT_KEYS_OVERWRITE_OLDEST) + key->aging_counter = ++aging_counter_val; + last_keys_updated = key; +#endif + LOG_DBG("created %p for %s", key, bt_addr_str(addr)); + return key; + } + + LOG_DBG("unable to create keys for %s", bt_addr_str(addr)); + + return NULL; +} + +void bt_keys_link_key_clear(struct bt_keys_link_key *link_key) +{ + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bt_addr_le_t le_addr; + + le_addr.type = BT_ADDR_LE_PUBLIC; + bt_addr_copy(&le_addr.a, &link_key->addr); + + bt_settings_delete_link_key(&le_addr); + } + + LOG_DBG("%s", bt_addr_str(&link_key->addr)); + (void)memset(link_key, 0, sizeof(*link_key)); +} + +void bt_keys_link_key_clear_addr(const bt_addr_t *addr) +{ + int i; + struct bt_keys_link_key *key; + + if (!addr) + { + for (i = 0; i < ARRAY_SIZE(key_pool); i++) + { + key = &key_pool[i]; + bt_keys_link_key_clear(key); + } + + return; + } + + key = bt_keys_find_link_key(addr); + if (key) + { + bt_keys_link_key_clear(key); + } +} + +void bt_keys_link_key_store(struct bt_keys_link_key *link_key) +{ + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + int err; + bt_addr_le_t le_addr; + + le_addr.type = BT_ADDR_LE_PUBLIC; + bt_addr_copy(&le_addr.a, &link_key->addr); + + err = bt_settings_store_link_key(&le_addr, link_key->storage_start, + BT_KEYS_LINK_KEY_STORAGE_LEN); + if (err) + { + LOG_ERR("Failed to save link key (err %d)", err); + } + } +} + +void bt_br_foreach_bond( + void (*func)(const struct bt_br_bond_info *info, void *user_data), + void *user_data) +{ + DEBUGASSERT(func != NULL); + + for (size_t i = 0; i < ARRAY_SIZE(key_pool); i++) + { + const struct bt_keys_link_key *key = &key_pool[i]; + + if (!bt_addr_eq(&key->addr, BT_ADDR_ANY)) + { + struct bt_br_bond_info info; + + bt_addr_copy(&info.addr, &key->addr); + func(&info, user_data); + } + } +} + +#if defined(CONFIG_BT_SETTINGS) + +static int link_key_set(const char *name, + size_t len_rd, + settings_read_cb read_cb, + void *cb_arg) +{ + int err; + ssize_t len; + bt_addr_le_t le_addr; + struct bt_keys_link_key *link_key; + char val[BT_KEYS_LINK_KEY_STORAGE_LEN]; + + if (!name) + { + LOG_ERR("Insufficient number of arguments"); + return -EINVAL; + } + + len = read_cb(cb_arg, val, sizeof(val)); + if (len < 0) + { + LOG_ERR("Failed to read value (err %zu)", len); + return -EINVAL; + } + + LOG_DBG("name %s val %s", name, len ? bt_hex(val, sizeof(val)) : "(null)"); + + err = bt_settings_decode_key(name, &le_addr); + if (err) + { + LOG_ERR("Unable to decode address %s", name); + return -EINVAL; + } + + link_key = bt_keys_get_link_key(&le_addr.a); + if (len != BT_KEYS_LINK_KEY_STORAGE_LEN) + { + if (link_key) + { + bt_keys_link_key_clear(link_key); + LOG_DBG("Clear keys for %s", bt_addr_le_str(&le_addr)); + } + else + { + LOG_WRN("Unable to find deleted keys for %s", + bt_addr_le_str(&le_addr)); + } + + return 0; + } + + memcpy(link_key->storage_start, val, len); + LOG_DBG("Successfully restored link key for %s", bt_addr_le_str(&le_addr)); +#if defined(CONFIG_BT_KEYS_OVERWRITE_OLDEST) + if (aging_counter_val < link_key->aging_counter) + { + aging_counter_val = link_key->aging_counter; + } +#endif /* CONFIG_BT_KEYS_OVERWRITE_OLDEST */ + + return 0; +} + +SETTINGS_STATIC_HANDLER_DEFINE(bt_link_key, + "bt/link_key", + NULL, + link_key_set, + NULL, + NULL); + +#if defined(CONFIG_BT_KEYS_OVERWRITE_OLDEST) +void bt_keys_link_key_update_usage(const bt_addr_t *addr) +{ + struct bt_keys_link_key *link_key = bt_keys_find_link_key(addr); + + if (!link_key) + { + return; + } + + if (last_keys_updated == link_key) + { + return; + } + + link_key->aging_counter = ++aging_counter_val; + last_keys_updated = link_key; + + LOG_DBG("Aging counter for %s is set to %u", bt_addr_str(addr), + link_key->aging_counter); + + if (IS_ENABLED(CONFIG_BT_KEYS_SAVE_AGING_COUNTER_ON_PAIRING)) + { + bt_keys_link_key_store(link_key); + } +} +#endif /* CONFIG_BT_KEYS_OVERWRITE_OLDEST */ + +#endif /* defined(CONFIG_BT_SETTINGS) */ diff --git a/libs/zblue/host/classic/l2cap_br.c b/libs/zblue/host/classic/l2cap_br.c new file mode 100644 index 0000000000000..12cdd5bb75d54 --- /dev/null +++ b/libs/zblue/host/classic/l2cap_br.c @@ -0,0 +1,8308 @@ +/**************************************************************************** + * libs/zblue/host/classic/l2cap_br.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* l2cap_br.c - L2CAP BREDR oriented handling */ + +/* Copyright (c) 2016 Intel Corporation + * Copyright 2024-2026 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include "l2cap_br_internal.h" +#include "avctp_internal.h" +#include "avrcp_internal.h" +#include "did_internal.h" +#include "rfcomm_internal.h" +#include "sdp_internal.h" + +#include +LOG_MODULE_REGISTER(bt_l2cap_br, CONFIG_BT_L2CAP_LOG_LEVEL); + +#define BR_CHAN_RTX(_w) \ + CONTAINER_OF(k_work_delayable_from_work(_w), struct bt_l2cap_br_chan, rtx_work) + +#define BR_CHAN_RET(_w) \ + CONTAINER_OF(k_work_delayable_from_work(_w), struct bt_l2cap_br_chan, ret_work) + +#define BR_CHAN_MONITOR(_w) \ + CONTAINER_OF(k_work_delayable_from_work(_w), struct bt_l2cap_br_chan, monitor_work) + +#define L2CAP_BR_PSM_START 0x0001 +#define L2CAP_BR_PSM_END 0xffff +#define L2CAP_BR_PSM_DYN_START 0x1000 +#define L2CAP_BR_PSM_DYN_END L2CAP_BR_PSM_END + +#define L2CAP_BR_CID_DYN_START 0x0040 +#define L2CAP_BR_CID_DYN_END 0xffff +#define L2CAP_BR_CID_IS_DYN(_cid) (_cid >= L2CAP_BR_CID_DYN_START && _cid <= L2CAP_BR_CID_DYN_END) + +#define L2CAP_BR_MIN_MTU 48 +#define L2CAP_BR_DEFAULT_MTU 672 + +#define L2CAP_BR_PSM_SDP 0x0001 + +#define L2CAP_BR_INFO_TIMEOUT SEC2TICK(4) +#define L2CAP_BR_CFG_TIMEOUT SEC2TICK(4) +#define L2CAP_BR_DISCONN_TIMEOUT SEC2TICK(1) +#define L2CAP_BR_CONN_TIMEOUT SEC2TICK(40) +#define L2CAP_BR_ECHO_TIMEOUT SEC2TICK(30) + +#define L2CAP_FEAT_FC_MASK BIT(0) +#define L2CAP_FEAT_RET_MASK BIT(1) +#define L2CAP_FEAT_QOS_MASK BIT(2) +#define L2CAP_FEAT_ENH_RET_MASK BIT(3) +#define L2CAP_FEAT_STREAM_MASK BIT(4) +#define L2CAP_FEAT_FCS_MASK BIT(5) +#define L2CAP_FEAT_EXT_FS_MASK BIT(6) +#define L2CAP_FEAT_FIXED_CHAN_MASK BIT(7) +#define L2CAP_FEAT_EXT_WIN_SIZE_MASK BIT(8) +#define L2CAP_FEAT_CONNLESS_MASK BIT(9) +#define L2CAP_FEAT_ECBFC_MASK BIT(10) + +#if defined(CONFIG_BT_L2CAP_CONNLESS) +#define L2CAP_FEAT_CONNLESS_ENABLE_MASK L2CAP_FEAT_CONNLESS_MASK +#else +#define L2CAP_FEAT_CONNLESS_ENABLE_MASK 0 +#endif /* CONFIG_BT_L2CAP_CONNLESS */ + +#if defined(CONFIG_BT_L2CAP_QOS) +#define L2CAP_FEAT_QOS_ENABLE_MASK L2CAP_FEAT_QOS_MASK +#else +#define L2CAP_FEAT_QOS_ENABLE_MASK 0 +#endif /* CONFIG_BT_L2CAP_QOS */ + +#if defined(CONFIG_BT_L2CAP_RET) +#define L2CAP_FEAT_RET_ENABLE_MASK L2CAP_FEAT_RET_MASK +#else +#define L2CAP_FEAT_RET_ENABLE_MASK 0 +#endif /* CONFIG_BT_L2CAP_RET */ + +#if defined(CONFIG_BT_L2CAP_FC) +#define L2CAP_FEAT_FC_ENABLE_MASK L2CAP_FEAT_FC_MASK +#else +#define L2CAP_FEAT_FC_ENABLE_MASK 0 +#endif /* CONFIG_BT_L2CAP_FC */ + +#if defined(CONFIG_BT_L2CAP_ENH_RET) +#define L2CAP_FEAT_ENH_RET_ENABLE_MASK L2CAP_FEAT_ENH_RET_MASK +#else +#define L2CAP_FEAT_ENH_RET_ENABLE_MASK 0 +#endif /* CONFIG_BT_L2CAP_ENH_RET */ + +#if defined(CONFIG_BT_L2CAP_STREAM) +#define L2CAP_FEAT_STREAM_ENABLE_MASK L2CAP_FEAT_STREAM_MASK +#else +#define L2CAP_FEAT_STREAM_ENABLE_MASK 0 +#endif /* CONFIG_BT_L2CAP_STREAM */ + +#if defined(CONFIG_BT_L2CAP_FCS) +#define L2CAP_FEAT_FCS_ENABLE_MASK L2CAP_FEAT_FCS_MASK +#else +#define L2CAP_FEAT_FCS_ENABLE_MASK 0 +#endif /* CONFIG_BT_L2CAP_FCS */ + +#if defined(CONFIG_BT_L2CAP_EXT_WIN_SIZE) +#define L2CAP_FEAT_EXT_WIN_SIZE_ENABLE_MASK L2CAP_FEAT_EXT_WIN_SIZE_MASK +#else +#define L2CAP_FEAT_EXT_WIN_SIZE_ENABLE_MASK 0 +#endif /* CONFIG_BT_L2CAP_EXT_WIN_SIZE */ + +/* L2CAP extended feature mask: + * BR/EDR fixed channel support enabled + */ +#define L2CAP_EXTENDED_FEAT_MASK \ + (L2CAP_FEAT_FIXED_CHAN_MASK | L2CAP_FEAT_RET_ENABLE_MASK | L2CAP_FEAT_FC_ENABLE_MASK | \ + L2CAP_FEAT_ENH_RET_ENABLE_MASK | L2CAP_FEAT_STREAM_ENABLE_MASK | L2CAP_FEAT_FCS_ENABLE_MASK | \ + L2CAP_FEAT_EXT_WIN_SIZE_ENABLE_MASK | L2CAP_FEAT_CONNLESS_ENABLE_MASK | \ + L2CAP_FEAT_QOS_ENABLE_MASK) + +enum +{ + /* Connection oriented channels flags */ + + L2CAP_FLAG_CONN_LCONF_DONE, /* local config accepted by remote */ + L2CAP_FLAG_CONN_RCONF_DONE, /* remote config accepted by local */ + L2CAP_FLAG_CONN_ACCEPTOR, /* getting incoming connection req */ + L2CAP_FLAG_CONN_PENDING, /* remote sent pending result in rsp */ + L2CAP_FLAG_CONN_CONF_RSP_SENDING, /* waiting for config response sent */ + + /* Signaling channel flags */ + + L2CAP_FLAG_SIG_INFO_PENDING, /* retrieving remote l2cap info */ + L2CAP_FLAG_SIG_INFO_DONE, /* remote l2cap info is done */ + + /* fixed channels flags */ + + L2CAP_FLAG_FIXED_CONNECTED, /* fixed connected */ + + /* Retransmission and flow control flags */ + + L2CAP_FLAG_RET_TIMER, /* Retransmission timer is working */ + + /* Receiving S-frame/I-frame flags */ + + L2CAP_FLAG_RECV_FRAME_P, /* Poll (P) flag of received frame */ + + /* Sending S-frame/I-frame flags */ + + L2CAP_FLAG_SEND_FRAME_REJ, /* Report an REJ in received frame */ + L2CAP_FLAG_SEND_FRAME_REJ_CHANGED, /* Flag the REJ flag changed. + * After send received frame with REJ + * flag, clear the flag. + */ + L2CAP_FLAG_SEND_FRAME_P, /* Poll (P) flag */ + L2CAP_FLAG_SEND_FRAME_P_CHANGED, /* Poll (P) flag changed */ + L2CAP_FLAG_SEND_S_FRAME, /* Need to send S-frame */ + + /* Remote State */ + + L2CAP_FLAG_REMOTE_BUSY, /* Remote Busy Flag */ + + /* Local State */ + + L2CAP_FLAG_LOCAL_BUSY, /* Local Busy Flag */ + L2CAP_FLAG_LOCAL_BUSY_CHANGED, /* Local Busy Flag changed */ + L2CAP_FLAG_REJ_ACTIONED, /* REJ Actioned */ + L2CAP_FLAG_SREJ_ACTIONED, /* SREJ Actioned */ + L2CAP_FLAG_RET_I_FRAME, /* Could Retransmit timeout I-frame */ + L2CAP_FLAG_NEW_I_FRAME, /* Could Send Pending I-frames */ + L2CAP_FLAG_RET_REQ_I_FRAME, /* Could Retransmit Requested I-frames */ + L2CAP_FLAG_RET_I_FRAMES, /* Could Retransmit all unacked I-frames */ + + /* Auth failed, disconnect ACL */ + + L2CAP_FLAG_DISCONNECT_ACL, /* Disconnect ACL */ +}; + +static struct list_node br_servers = LIST_INITIAL_VALUE(br_servers); + +/* Pool for outgoing BR/EDR signaling packets, min MTU is 48 */ + +NET_BUF_POOL_FIXED_DEFINE(br_sig_pool, + CONFIG_BT_MAX_CONN, + BT_L2CAP_BUF_SIZE(L2CAP_BR_MIN_MTU), + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + +#if defined(CONFIG_BT_L2CAP_RET_FC) + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void br_tx_buf_destroy(struct net_buf *buf) +{ + net_buf_destroy(buf); + + LOG_DBG(""); + + /* Kick the TX processor to send the rest of the frags. */ + + bt_tx_irq_raise(); +} + +/* Pool for outgoing BR/EDR RET/FC transmit PDUs (I-frames/S-frames) */ + +NET_BUF_POOL_FIXED_DEFINE(br_tx_pool, CONFIG_BT_L2CAP_TX_BUF_COUNT, + BT_L2CAP_RT_FC_MAX_SDU_BUF_SIZE(CONFIG_BT_L2CAP_MPS), + CONFIG_BT_CONN_TX_USER_DATA_SIZE, + br_tx_buf_destroy); +#endif /* CONFIG_BT_L2CAP_RET_FC */ + +/* BR/EDR L2CAP signalling channel specific context */ + +struct bt_l2cap_br +{ + /* The channel this context is associated with */ + + struct bt_l2cap_br_chan chan; + /* 2.1 CHANNEL IDENTIFIERS in + * BLUETOOTH CORE SPECIFICATION Version 5.4 | Vol 3, Part A. The range of + * fixed L2CAP CID is 0x0001 ~ 0x0007 both for LE and BR. So use one octet + * buffer to keep the `Fixed channels supported` of peer device. + */ + + uint8_t info_fixed_chan; + uint32_t info_feat_mask; +}; + +static struct bt_l2cap_br bt_l2cap_br_pool[CONFIG_BT_MAX_CONN]; + +static struct list_node bt_l2cap_br_echo_cbs = + LIST_INITIAL_VALUE(bt_l2cap_br_echo_cbs); + +struct bt_l2cap_chan *bt_l2cap_br_lookup_rx_cid(struct bt_conn *conn, + uint16_t cid) +{ + struct bt_l2cap_chan *chan; + + list_for_every_container(&conn->channels, chan, node) + { + if (BR_CHAN(chan)->rx.cid == cid) + { + return chan; + } + } + + return NULL; +} + +struct bt_l2cap_chan *bt_l2cap_br_lookup_tx_cid(struct bt_conn *conn, + uint16_t cid) +{ + struct bt_l2cap_chan *chan; + + list_for_every_container(&conn->channels, chan, node) + { + if (BR_CHAN(chan)->tx.cid == cid) + { + return chan; + } + } + + return NULL; +} + +uint8_t bt_l2cap_br_get_remote_fixed_chan(struct bt_conn *conn) +{ + struct bt_l2cap_chan *chan_sig; + struct bt_l2cap_br *br_chan_sig; + + chan_sig = bt_l2cap_br_lookup_rx_cid(conn, BT_L2CAP_CID_BR_SIG); + if (!chan_sig) + { + return (uint8_t)0U; + } + + br_chan_sig = CONTAINER_OF(chan_sig, struct bt_l2cap_br, chan.chan); + + return br_chan_sig->info_fixed_chan; +} + +static uint16_t br_cid_next[CONFIG_BT_MAX_CONN]; + +static inline void bt_l2cap_br_cid_init(struct bt_conn *conn) +{ + size_t index; + + index = (size_t)bt_conn_index(conn); + DEBUGASSERT(index < ARRAY_SIZE(br_cid_next)); + + br_cid_next[index] = L2CAP_BR_CID_DYN_START; +} + +static inline uint16_t bt_l2cap_br_cid_increase(uint16_t cid) +{ + uint32_t next = cid; + + next = next + 1; + + if (next > L2CAP_BR_CID_DYN_END) + { + next = L2CAP_BR_CID_DYN_START; + } + + return (uint16_t)next; +} + +static struct bt_l2cap_br_chan *l2cap_br_chan_alloc_cid(struct bt_conn *conn, + struct bt_l2cap_chan *chan) +{ + struct bt_l2cap_br_chan *br_chan = BR_CHAN(chan); + uint16_t start; + uint16_t sentinel; + size_t index; + + /* No action needed if there's already a CID allocated, e.g. in + * the case of a fixed channel. + */ + + if (br_chan->rx.cid > 0) + { + return br_chan; + } + + index = (size_t)bt_conn_index(conn); + DEBUGASSERT(index < ARRAY_SIZE(br_cid_next)); + + start = br_cid_next[index]; + sentinel = br_cid_next[index]; + + /* L2CAP_BR_CID_DYN_END is 0xffff so we don't check against it since + * cid is uint16_t, just check against the start of the range. + */ + + if (start < L2CAP_BR_CID_DYN_START) + { + start = L2CAP_BR_CID_DYN_START; + sentinel = L2CAP_BR_CID_DYN_START; + } + + do + { + if (bt_l2cap_br_lookup_rx_cid(conn, start) == NULL) + { + br_chan->rx.cid = start; + br_cid_next[index] = bt_l2cap_br_cid_increase(start); + return br_chan; + } + + start = bt_l2cap_br_cid_increase(start); + } + while (start != sentinel); + + return NULL; +} + +static void l2cap_br_chan_cleanup(struct bt_l2cap_chan *chan) +{ + bt_l2cap_chan_remove(chan->conn, chan); + bt_l2cap_br_chan_del(chan); +} + +static void l2cap_br_chan_destroy(struct bt_l2cap_chan *chan) +{ + struct bt_l2cap_br_chan *br_chan = BR_CHAN(chan); + + LOG_DBG("chan %p cid 0x%04x", br_chan, br_chan->rx.cid); + + /* Cancel ongoing work. Since the channel can be reused after this + * we need to sync to make sure that the kernel does not have it in its + * queue anymore. + * + * In the case where we are in the context of executing the rtx_work item, + * we don't sync as it will deadlock the workqueue. + */ + + struct k_work_q *rtx_work_queue = br_chan->rtx_work.queue; + + if (rtx_work_queue == NULL || nxsched_gettid() != + rtx_work_queue->thread_id) + { + k_work_cancel_delayable_sync(&br_chan->rtx_work, &br_chan->rtx_sync); + } + else + { + k_work_cancel_delayable(&br_chan->rtx_work); + } + +#if defined(CONFIG_BT_L2CAP_RET_FC) + k_work_cancel_delayable(&br_chan->ret_work); + k_work_cancel_delayable(&br_chan->monitor_work); +#endif /* CONFIG_BT_L2CAP_RET_FC */ + + atomic_set(BR_CHAN(chan)->flags, 0); +} + +static void l2cap_br_rtx_timeout(struct k_work *work) +{ + struct bt_l2cap_br_chan *chan = BR_CHAN_RTX(work); + + if (chan->chan.conn == NULL || chan->chan.conn->state != BT_CONN_CONNECTED) + { + /* The ACL connection is already gone; there is nothing left + * for the timeout to act on. + */ + + return; + } + + LOG_WRN("chan %p timeout", chan); + + if (chan->rx.cid == BT_L2CAP_CID_BR_SIG) + { + LOG_DBG("Skip BR/EDR signalling channel "); + atomic_clear_bit(chan->flags, L2CAP_FLAG_SIG_INFO_PENDING); + chan->ident = 0; + return; + } + + LOG_DBG("chan %p %s scid 0x%04x", chan, + bt_l2cap_chan_state_str(chan->state), + chan->rx.cid); + + switch (chan->state) + { + case BT_L2CAP_CONFIG: + bt_l2cap_br_chan_disconnect(&chan->chan); + break; + case BT_L2CAP_DISCONNECTING: + case BT_L2CAP_CONNECTING: + l2cap_br_chan_cleanup(&chan->chan); + break; + default: + break; + } +} + +static uint8_t l2cap_br_get_ident(void) +{ + static uint8_t ident; + + ident++; + + /* handle integer overflow (0 is not valid) */ + + if (!ident) + { + ident++; + } + + return ident; +} + +/* L2CAP channel wants to send a PDU */ + +static bool chan_has_data(struct bt_l2cap_br_chan *br_chan) +{ + return !list_is_empty(&br_chan->_pdu_tx_queue); +} + +static void raise_data_ready(struct bt_l2cap_br_chan *br_chan) +{ + /* The l2cap_data_ready list is only ever modified under the host + * lock: here (append, any thread context), in cancel_data_ready() (remove, + * any thread context) and in lower_data_ready() (remove, TX processor + * context, which holds the lock across the whole pass). + */ + + bt_dev_lock(); + + if (!atomic_xchg(&br_chan->_pdu_ready_lock, 1)) + { + list_add_tail(&br_chan->chan.conn->l2cap_data_ready, + &br_chan->_pdu_ready); + LOG_DBG("data ready raised"); + } + else + { + LOG_DBG("data ready already"); + } + + bt_dev_unlock(); + + bt_conn_data_ready(br_chan->chan.conn); +} + +static void lower_data_ready(struct bt_l2cap_br_chan *br_chan) +{ + struct bt_conn *conn = br_chan->chan.conn; + __maybe_unused struct list_node *s; + + /* Only called from the TX processor, which holds the host lock + * across the whole processing pass. + */ + + BT_DEV_LOCK_ASSERT(); + + s = list_remove_head(&conn->l2cap_data_ready); + + DEBUGASSERT(s == &br_chan->_pdu_ready); + + __maybe_unused int32_t old = atomic_xchg(&br_chan->_pdu_ready_lock, 0); + + DEBUGASSERT(old); +} + +static void cancel_data_ready(struct bt_l2cap_br_chan *br_chan) +{ + struct bt_conn *conn = br_chan->chan.conn; + + /* Take the host lock as this function can be called from any + * thread context and the data ready list must not be modified while we are + * removing the channel from it (see raise_data_ready()). + */ + + bt_dev_lock(); + + list_find_and_remove(&conn->l2cap_data_ready, &br_chan->_pdu_ready); + + atomic_set(&br_chan->_pdu_ready_lock, 0); + + bt_dev_unlock(); +} + +#if defined(CONFIG_BT_L2CAP_RET_FC) +struct l2cap_br_closure +{ + struct closure closure; + uint16_t sdu_len; + bool sent; +} __packed; + +BUILD_ASSERT(sizeof(struct l2cap_br_closure) <= + CONFIG_BT_CONN_TX_USER_DATA_SIZE); + +static inline void l2cap_br_make_closure(void *storage, + void *cb, void *data, + uint16_t sdu_len) +{ + ((struct l2cap_br_closure *)storage)->closure.cb = cb; + ((struct l2cap_br_closure *)storage)->closure.data = data; + ((struct l2cap_br_closure *)storage)->sdu_len = sdu_len; + ((struct l2cap_br_closure *)storage)->sent = false; +} + +static inline void *l2cap_br_closure_cb(void *storage) +{ + return ((struct l2cap_br_closure *)storage)->closure.cb; +} + +static inline void *l2cap_br_closure_data(void *storage) +{ + return ((struct l2cap_br_closure *)storage)->closure.data; +} + +static inline uint16_t l2cap_br_closure_sdu_len(void *storage) +{ + return ((struct l2cap_br_closure *)storage)->sdu_len; +} + +static inline bool l2cap_br_closure_sent(void *storage) +{ + return ((struct l2cap_br_closure *)storage)->sent; +} + +static inline void l2cap_br_update_closure_sent(void *storage, bool sent) +{ + ((struct l2cap_br_closure *)storage)->sent = sent; +} + +static inline bool l2cap_br_sdu_is_pending(struct net_buf *sdu) +{ + return !l2cap_br_closure_sent(sdu->user_data); +} + +static inline void l2cap_br_sdu_pull(struct net_buf *sdu, uint16_t len) +{ + net_buf_pull(sdu, len); + if (sdu->len == 0) + { + l2cap_br_update_closure_sent(sdu->user_data, true); + } +} + +static inline void l2cap_br_sdu_restore(struct net_buf *sdu, + struct net_buf_simple_state *state) +{ + net_buf_simple_restore(&sdu->b, state); + l2cap_br_update_closure_sent(sdu->user_data, false); +} + +enum l2cap_br_timer_type +{ + BT_L2CAP_BR_TIMER_RET, + BT_L2CAP_BR_TIMER_MONITOR, +}; + +static void l2cap_br_start_timer( + struct bt_l2cap_br_chan *br_chan, enum l2cap_br_timer_type type, + bool restart) +{ + if (type == BT_L2CAP_BR_TIMER_RET) + { + if (!atomic_test_and_set_bit(br_chan->flags, L2CAP_FLAG_RET_TIMER)) + { + k_work_cancel_delayable(&br_chan->monitor_work); + bt_work_schedule(&br_chan->ret_work, + MSEC2TICK(br_chan->tx.ret_timeout)); + LOG_DBG("Start ret timer"); + } + else + { + if (!restart) + { + return; + } + + bt_work_reschedule(&br_chan->ret_work, + MSEC2TICK(br_chan->tx.ret_timeout)); + LOG_DBG("Restart ret timer"); + } + } + else + { + if (atomic_test_and_clear_bit(br_chan->flags, L2CAP_FLAG_RET_TIMER)) + { + k_work_cancel_delayable(&br_chan->ret_work); + bt_work_schedule(&br_chan->monitor_work, + MSEC2TICK(br_chan->tx.monitor_timeout)); + LOG_DBG("Start monitor timer"); + } + else + { + if (!restart) + { + return; + } + + bt_work_reschedule(&br_chan->monitor_work, + MSEC2TICK(br_chan->tx.monitor_timeout)); + LOG_DBG("Restart monitor timer"); + } + } +} + +static uint16_t bt_l2cap_br_get_outstanding_count( + struct bt_l2cap_br_chan *br_chan) +{ + struct bt_l2cap_br_window *tx_win; + struct bt_l2cap_br_window *next; + uint16_t count = 0; + + list_for_every_container_safe(&br_chan->_pdu_outstanding, tx_win, next, + node) + { + count++; + } + + return count; +} + +static struct bt_l2cap_br_window *l2cap_br_find_window( + struct bt_l2cap_br_chan *br_chan) +{ + struct bt_l2cap_br_window *win; + uint16_t count; + + count = bt_l2cap_br_get_outstanding_count(br_chan); + if (count >= br_chan->tx.max_window) + { + return NULL; + } + + win = k_fifo_get(&br_chan->_free_tx_win, 0); + if (win) + { + memset(win, 0, sizeof(*win)); + } + + return win; +} + +static void l2cap_br_free_window(struct bt_l2cap_br_chan *br_chan, + struct bt_l2cap_br_window *tx_win) +{ + if (tx_win) + { + memset(tx_win, 0, sizeof(*tx_win)); + k_fifo_put(&br_chan->_free_tx_win, tx_win); + } +} + +static uint16_t bt_l2cap_br_update_seq(struct bt_l2cap_br_chan *br_chan, + uint16_t seq) +{ + if ((br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_ERET) || + (br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_STREAM)) + { + if (br_chan->tx.extended_control) + { + seq = seq % BT_L2CAP_EXT_CONTROL_SEQ_MAX; + } + else + { + seq = seq % BT_L2CAP_CONTROL_SEQ_MAX; + } + } + else if ((br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_RET) || + (br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_FC)) + { + seq = seq % BT_L2CAP_CONTROL_SEQ_MAX; + } + + return seq; +} + +static struct bt_l2cap_br_window *l2cap_br_find_srej( + struct bt_l2cap_br_chan *br_chan) +{ + struct bt_l2cap_br_window *tx_win; + struct bt_l2cap_br_window *next; + + list_for_every_container_safe(&br_chan->_pdu_outstanding, tx_win, next, + node) + { + if (tx_win->srej) + { + return tx_win; + } + } + + return NULL; +} + +static bool bt_l2cap_br_check_req_seq_valid(struct bt_l2cap_br_chan *br_chan, + uint16_t req_seq) +{ + uint16_t outstanding_frames; + uint16_t ack_frames; + uint16_t outstanding_count; + + outstanding_frames = + (uint16_t)(br_chan->next_tx_seq - br_chan->expected_ack_seq); + ack_frames = (uint16_t)(req_seq - br_chan->expected_ack_seq); + + if ((br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_ERET) || + (br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_STREAM)) + { + if (br_chan->tx.extended_control) + { + outstanding_frames = + outstanding_frames % BT_L2CAP_EXT_CONTROL_SEQ_MAX; + ack_frames = ack_frames % BT_L2CAP_EXT_CONTROL_SEQ_MAX; + } + else + { + outstanding_frames = outstanding_frames % BT_L2CAP_CONTROL_SEQ_MAX; + ack_frames = ack_frames % BT_L2CAP_CONTROL_SEQ_MAX; + } + } + else if ((br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_RET) || + (br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_FC)) + { + outstanding_frames = outstanding_frames % BT_L2CAP_CONTROL_SEQ_MAX; + ack_frames = ack_frames % BT_L2CAP_CONTROL_SEQ_MAX; + } + + outstanding_count = bt_l2cap_br_get_outstanding_count(br_chan); + DEBUGASSERT(outstanding_count == outstanding_frames); + + if (ack_frames <= outstanding_frames) + { + return true; + } + + return false; +} + +static void l2cap_br_sdu_is_done(struct bt_l2cap_br_chan *br_chan, + struct net_buf *sdu, int err) +{ + bt_conn_tx_cb_t cb; + + DEBUGASSERT(sdu); + + /* The SDU is done */ + + LOG_DBG("SDU is done, removing %p", sdu); + + if (!list_find_and_remove(&br_chan->_pdu_tx_queue, &sdu->node)) + { + LOG_WRN("SDU %p is not found", sdu); + } + + cb = l2cap_br_closure_cb(sdu->user_data); + if (cb) + { + cb(br_chan->chan.conn, l2cap_br_closure_data(sdu->user_data), err); + } + + /* Remove the pdu */ + + net_buf_unref(sdu); + + LOG_DBG("chan %p done", br_chan); + + /* Append channel to list if it still has data */ + + if (chan_has_data(br_chan)) + { + LOG_DBG("chan %p ready", br_chan); + raise_data_ready(br_chan); + } +} + +static int bt_l2cap_br_update_req_seq_direct( + struct bt_l2cap_br_chan *br_chan, uint16_t req_seq, bool rej) +{ + struct bt_l2cap_br_window *tx_win; + struct net_buf *sdu; + + tx_win = (void *)list_peek_head(&br_chan->_pdu_outstanding); + while (tx_win && (tx_win->tx_seq != req_seq)) + { + sdu = tx_win->sdu; + if ((tx_win->sar == BT_L2CAP_CONTROL_SAR_UNSEG) || + (tx_win->sar == BT_L2CAP_CONTROL_SAR_END)) + { + l2cap_br_sdu_is_done(br_chan, sdu, 0); + } + + tx_win = (void *)list_remove_head(&br_chan->_pdu_outstanding); + l2cap_br_free_window(br_chan, tx_win); + tx_win = (void *)list_peek_head(&br_chan->_pdu_outstanding); + } + + br_chan->expected_ack_seq = req_seq; + + if (rej) + { + tx_win = (void *)list_peek_head(&br_chan->_pdu_outstanding); + if (tx_win) + { + sdu = tx_win->sdu; + DEBUGASSERT(sdu); + l2cap_br_sdu_restore(sdu, &tx_win->sdu_state); + br_chan->next_tx_seq = tx_win->tx_seq; + } + + while (tx_win) + { + tx_win = (void *)list_remove_head(&br_chan->_pdu_outstanding); + l2cap_br_free_window(br_chan, tx_win); + tx_win = (void *)list_peek_head(&br_chan->_pdu_outstanding); + if (tx_win) + { + sdu = tx_win->sdu; + DEBUGASSERT(sdu); + if ((tx_win->sar == BT_L2CAP_CONTROL_SAR_UNSEG) || + (tx_win->sar == BT_L2CAP_CONTROL_SAR_START)) + { + l2cap_br_sdu_restore(sdu, &tx_win->sdu_state); + } + } + } + } + + if (!atomic_test_bit(br_chan->flags, L2CAP_FLAG_REMOTE_BUSY)) + { + if (bt_l2cap_br_get_outstanding_count(br_chan)) + { + /* If unacknowledged I-frames have been sent but the retransmission + * timer has not elapsed, then the ongoing retransmission timer + * should not be restarted. + */ + + l2cap_br_start_timer(br_chan, BT_L2CAP_BR_TIMER_RET, false); + } + else + { + l2cap_br_start_timer(br_chan, BT_L2CAP_BR_TIMER_MONITOR, false); + } + } + + if (chan_has_data(br_chan)) + { + LOG_DBG("chan %p ready", br_chan); + raise_data_ready(br_chan); + } + + return 0; +} + +static int bt_l2cap_br_update_req_seq(struct bt_l2cap_br_chan *br_chan, + uint16_t req_seq, bool rej) +{ + int err = 0; + + if (bt_l2cap_br_check_req_seq_valid(br_chan, req_seq)) + { + err = bt_l2cap_br_update_req_seq_direct(br_chan, req_seq, rej); + } + else + { + LOG_ERR("Invalid req seq %d received on %p", req_seq, br_chan); + /* The L2CAP entity shall close the channel as a consequence + * of an ReqSeq Sequence error. + */ + + bt_l2cap_br_chan_disconnect(&br_chan->chan); + err = -ESHUTDOWN; + } + + return err; +} + +static void l2cap_br_send_s_frame(struct bt_l2cap_br_chan *br_chan) +{ + if (atomic_test_and_set_bit(br_chan->flags, L2CAP_FLAG_SEND_S_FRAME)) + { + LOG_DBG("S-frame is in pending on %p", br_chan); + } + + LOG_DBG("chan %p ready", br_chan); + raise_data_ready(br_chan); +} + +static void l2cap_br_ret_timeout(struct k_work *work) +{ + struct bt_l2cap_br_chan *br_chan = BR_CHAN_RET(work); + uint16_t expected_ack_seq; + + if (!br_chan->chan.conn || br_chan->chan.conn->state != BT_CONN_CONNECTED) + { + /* The ACL connection may have been disconnected while this + * work item was pending. Nothing to do in that case. + */ + + return; + } + + if (br_chan->state != BT_L2CAP_CONNECTED) + { + /* The channel is not connected */ + + return; + } + + /* Restart the timer */ + + if (atomic_test_bit(br_chan->flags, L2CAP_FLAG_RET_TIMER)) + { + bt_work_schedule(&br_chan->ret_work, + MSEC2TICK(br_chan->tx.ret_timeout)); + } + + LOG_DBG("chan %p retransmission timeout", br_chan); + + if (list_peek_head(&br_chan->_pdu_outstanding) == NULL) + { + return; + } + + switch (br_chan->tx.mode) + { + case BT_L2CAP_BR_LINK_MODE_RET: + LOG_WRN("Retrans on chan %p", br_chan); + atomic_set_bit(br_chan->flags, L2CAP_FLAG_RET_I_FRAME); + + /* Append channel to list if it still has data */ + + if (chan_has_data(br_chan)) + { + LOG_DBG("chan %p ready", br_chan); + raise_data_ready(br_chan); + } + break; + case BT_L2CAP_BR_LINK_MODE_FC: + expected_ack_seq = bt_l2cap_br_update_seq(br_chan, + br_chan->expected_ack_seq + + 1); + + LOG_WRN("unacknowledged I-frame with sequence number %d", + br_chan->expected_ack_seq); + bt_l2cap_br_update_req_seq(br_chan, expected_ack_seq, false); + break; + case BT_L2CAP_BR_LINK_MODE_ERET: + if (!atomic_test_and_set_bit(br_chan->flags, + L2CAP_FLAG_SEND_FRAME_P)) + { + /* State: XMIT + * Retrans Timer expires Or Local Busy Clears Action: send + * RR(P=1) + * or RNR(P=1) + */ + + br_chan->retry_count = 1; + + LOG_WRN("Send RR(P=1) on chan %p", br_chan); + + l2cap_br_start_timer(br_chan, BT_L2CAP_BR_TIMER_MONITOR, true); + + atomic_set_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_P_CHANGED); + l2cap_br_send_s_frame(br_chan); + } + break; + default: + + /* Nothing to do */ + + break; + } +} + +static void l2cap_br_monitor_timeout(struct k_work *work) +{ + struct bt_l2cap_br_chan *br_chan = BR_CHAN_MONITOR(work); + + if (!br_chan->chan.conn || br_chan->chan.conn->state != BT_CONN_CONNECTED) + { + /* The ACL connection may have been disconnected while this + * work item was pending. Nothing to do in that case. + */ + + return; + } + + if (br_chan->state != BT_L2CAP_CONNECTED) + { + /* The channel is not connected */ + + return; + } + + /* Restart the timer */ + + if (!atomic_test_bit(br_chan->flags, L2CAP_FLAG_RET_TIMER)) + { + bt_work_schedule(&br_chan->monitor_work, + MSEC2TICK(br_chan->tx.monitor_timeout)); + } + + LOG_DBG("chan %p monitor timeout", br_chan); + + if (br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_ERET) + { + if (!atomic_test_bit(br_chan->flags, + L2CAP_FLAG_SEND_FRAME_P_CHANGED) && + atomic_test_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_P)) + { + /* State: WAIT_F + * Monitor Timer expires Action: send RR(P=1) or RNR(P=1) + */ + + br_chan->retry_count++; + if (br_chan->tx.max_transmit && + (br_chan->retry_count > br_chan->tx.max_transmit)) + { + bt_l2cap_chan_disconnect(&br_chan->chan); + return; + } + + LOG_WRN("Resend RR(P=1) on chan %p", br_chan); + + atomic_set_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_P_CHANGED); + l2cap_br_start_timer(br_chan, BT_L2CAP_BR_TIMER_MONITOR, true); + + l2cap_br_send_s_frame(br_chan); + } + } + else if ((br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_FC) || + (br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_RET)) + { + /* Send S-Frame if there is not any pending I-frame */ + + l2cap_br_send_s_frame(br_chan); + } +} +#endif /* CONFIG_BT_L2CAP_RET_FC */ + +static bool l2cap_br_chan_add( + struct bt_conn *conn, struct bt_l2cap_chan *chan, + bt_l2cap_chan_destroy_t destroy) +{ + struct bt_l2cap_br_chan *ch = l2cap_br_chan_alloc_cid(conn, chan); + + if (!ch) + { + LOG_WRN("Unable to allocate L2CAP CID"); + return false; + } + + list_initialize(&ch->_pdu_tx_queue); + + /* All dynamic channels have the destroy handler which makes sure that + * the RTX work structure is properly released with a cancel sync. The + * fixed signal channel is only removed when disconnected and the + * disconnected handler is always called from the Bluetooth workqueue, + * which the RTX work also runs on, so canceling from there should always + * succeed. + */ + + k_work_init_delayable(&ch->rtx_work, l2cap_br_rtx_timeout); +#if defined(CONFIG_BT_L2CAP_RET_FC) + k_work_init_delayable(&ch->ret_work, l2cap_br_ret_timeout); + k_work_init_delayable(&ch->monitor_work, l2cap_br_monitor_timeout); +#endif /* CONFIG_BT_L2CAP_RET_FC */ + bt_l2cap_chan_add(conn, chan, destroy); + + return true; +} + +static int bt_l2cap_br_basic_mode_send_buf_init( + struct bt_l2cap_br_chan *chan, struct net_buf *buf, bt_conn_tx_cb_t cb, + void *user_data) +{ + struct bt_l2cap_hdr *hdr; + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->len = sys_cpu_to_le16(buf->len - sizeof(*hdr)); + hdr->cid = sys_cpu_to_le16(chan->tx.cid); + + if (buf->user_data_size < sizeof(struct closure)) + { + LOG_WRN("not enough room in user_data %d < %d pool %u", + buf->user_data_size, + CONFIG_BT_CONN_TX_USER_DATA_SIZE, net_buf_id(buf)); + return -EINVAL; + } + + LOG_DBG("push PDU: cb %p userdata %p", cb, user_data); + + make_closure(buf->user_data, cb, user_data); + + return 0; +} + +#if defined(CONFIG_BT_L2CAP_RET_FC) +static int bt_l2cap_br_non_basic_mode_send_buf_init( + struct net_buf *buf, bt_conn_tx_cb_t cb, void *user_data) +{ + if (buf->user_data_size < sizeof(struct l2cap_br_closure)) + { + LOG_WRN("not enough room in user_data %d < %d pool %u", + buf->user_data_size, + CONFIG_BT_CONN_TX_USER_DATA_SIZE, net_buf_id(buf)); + return -EINVAL; + } + + LOG_DBG("push PDU: cb %p userdata %p sdu_len %u", cb, user_data, buf->len); + + l2cap_br_make_closure(buf->user_data, cb, user_data, buf->len); + return 0; +} +#endif /* CONFIG_BT_L2CAP_RET_FC */ + +static int bt_l2cap_br_send_buf_init( + struct bt_l2cap_br_chan *chan, struct net_buf *buf, bt_conn_tx_cb_t cb, + void *user_data) +{ +#if defined(CONFIG_BT_L2CAP_RET_FC) + if (chan->tx.mode != BT_L2CAP_BR_LINK_MODE_BASIC) + { + return bt_l2cap_br_non_basic_mode_send_buf_init(buf, cb, user_data); + } +#endif /* CONFIG_BT_L2CAP_RET_FC */ + + return bt_l2cap_br_basic_mode_send_buf_init(chan, buf, cb, user_data); +} + +int bt_l2cap_br_send_cb( + struct bt_conn *conn, uint16_t cid, struct net_buf *buf, + bt_conn_tx_cb_t cb, void *user_data) +{ + struct bt_l2cap_chan *ch = bt_l2cap_br_lookup_tx_cid(conn, cid); + struct bt_l2cap_br_chan *br_chan; + int err; + + if (ch == NULL) + { + LOG_WRN("CID %d is not found on conn %p", cid, conn); + return -ESHUTDOWN; + } + + if (ch->conn == NULL) + { + LOG_WRN("ACL conn of chan %p is invalid", ch); + return -ENOTCONN; + } + + br_chan = CONTAINER_OF(ch, struct bt_l2cap_br_chan, chan); + + LOG_DBG("chan %p buf %p len %u", br_chan, buf, buf->len); + + if (net_buf_headroom(buf) < BT_L2CAP_CHAN_SEND_RESERVE) + { + /* Call `net_buf_reserve(buf, BT_L2CAP_CHAN_SEND_RESERVE)` + * when allocating buffers intended for bt_l2cap_chan_send(). + */ + + LOG_ERR("Not enough headroom in buf %p", buf); + return -EINVAL; + } + + err = bt_l2cap_br_send_buf_init(br_chan, buf, cb, user_data); + if (err != 0) + { + return err; + } + + list_add_tail(&br_chan->_pdu_tx_queue, &buf->node); + raise_data_ready(br_chan); + + return 0; +} + +/* Send the buffer and release it in case of failure. + * Any other cleanup in failure to send should be handled by the disconnected + * handler. + */ + +static inline void l2cap_send(struct bt_conn *conn, + uint16_t cid, struct net_buf *buf) +{ + if (bt_l2cap_br_send_cb(conn, cid, buf, NULL, NULL)) + { + net_buf_unref(buf); + } +} + +static void l2cap_br_chan_send_req( + struct bt_l2cap_br_chan *chan, struct net_buf *buf, clock_t timeout) +{ + if (bt_l2cap_br_send_cb(chan->chan.conn, BT_L2CAP_CID_BR_SIG, buf, NULL, + NULL)) + { + net_buf_unref(buf); + return; + } + + /* BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part A] page 126: + * + * The value of this timer is implementation-dependent but the minimum + * initial value is 1 second and the maximum initial value is 60 seconds. + * One RTX timer shall exist for each outstanding signaling request, + * including each Echo Request. The timer disappears on the final + * expiration, when the response is received, or the physical link is lost. + */ + + bt_work_reschedule(&chan->rtx_work, timeout); +} + +#if defined(CONFIG_BT_L2CAP_RET_FC) +static uint16_t get_pdu_len(struct bt_l2cap_br_chan *br_chan, + struct net_buf *buf) +{ + uint16_t pdu_len = buf->len; + uint16_t actual_mps; + + if (br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_BASIC) + { + return pdu_len; + } + + /* MPS is Max PDU Size. */ + + actual_mps = br_chan->tx.mps; + + if (pdu_len <= actual_mps) + { + return pdu_len; + } + + return actual_mps; +} + +static int bt_l2cap_br_pack_s_frame_header( + struct bt_l2cap_br_chan *br_chan, struct net_buf *buf, uint8_t s) +{ + struct bt_l2cap_hdr *hdr; + uint16_t std_control; + uint16_t enh_control; + uint32_t ext_control; + uint8_t f_bit = 0; + uint8_t r_bit = 0; + uint8_t p_bit = 0; + uint16_t pdu_len; + + pdu_len = + BT_L2CAP_RT_FC_SDU_HDR_SIZE(br_chan) + + BT_L2CAP_RT_FC_SDU_TAIL_SIZE(br_chan) - + BT_L2CAP_RT_FC_SDU_LEN_SIZE; + + hdr = net_buf_push(buf, sizeof(*hdr)); + + hdr->len = sys_cpu_to_le16(pdu_len); + hdr->cid = sys_cpu_to_le16(br_chan->tx.cid); + + if (s == BT_L2CAP_CONTROL_S_RR) + { + if (atomic_test_and_clear_bit(br_chan->flags, + L2CAP_FLAG_SEND_FRAME_REJ_CHANGED) && + atomic_test_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_REJ)) + { + s = BT_L2CAP_CONTROL_S_REJ; + } + } + + if (s == BT_L2CAP_CONTROL_S_RR) + { + if (atomic_test_and_clear_bit(br_chan->flags, L2CAP_FLAG_RECV_FRAME_P)) + { + f_bit = 1; + } + else if (atomic_test_and_clear_bit(br_chan->flags, + L2CAP_FLAG_SEND_FRAME_P_CHANGED) && + atomic_test_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_P)) + { + p_bit = 1; + } + else if (atomic_test_and_clear_bit(br_chan->flags, + L2CAP_FLAG_LOCAL_BUSY_CHANGED) && + atomic_test_bit(br_chan->flags, L2CAP_FLAG_LOCAL_BUSY)) + { + s = BT_L2CAP_CONTROL_S_RNR; + } + + if (atomic_test_bit(br_chan->flags, L2CAP_FLAG_LOCAL_BUSY)) + { + s = BT_L2CAP_CONTROL_S_RNR; + } + } + + if (atomic_test_bit(br_chan->flags, L2CAP_FLAG_LOCAL_BUSY)) + { + if ((br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_ERET) && + (s != BT_L2CAP_CONTROL_S_RNR)) + { + LOG_WRN("Local is busy and cannot send S-frame on %p", br_chan); + return -EINVAL; + } + + if (br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_RET) + { + r_bit = 1; + s = BT_L2CAP_CONTROL_S_REJ; + } + } + + if (br_chan->tx.mode != BT_L2CAP_BR_LINK_MODE_STREAM) + { + br_chan->req_seq = br_chan->expected_tx_seq; + } + else + { + br_chan->req_seq = 0; + } + + if ((br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_ERET) || + (br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_STREAM)) + { + if (br_chan->tx.extended_control) + { + /* Space occupied for extended control field. */ + + ext_control = sys_cpu_to_le32( + BT_L2CAP_S_FRAME_EXT_CONTROL_SET(f_bit, + br_chan->req_seq, + s, + p_bit)); + net_buf_add_le32(buf, ext_control); + } + else + { + /* Space occupied for enhanced control field. */ + + enh_control = sys_cpu_to_le16( + BT_L2CAP_S_FRAME_ENH_CONTROL_SET(s, + p_bit, + f_bit, + br_chan->req_seq)); + net_buf_add_le16(buf, enh_control); + } + } + else + { + /* Space occupied for standard control field. */ + + std_control = + sys_cpu_to_le16(BT_L2CAP_S_FRAME_STD_CONTROL_SET(s, r_bit, + br_chan->req_seq)); + net_buf_add_le16(buf, std_control); + } + + return 0; +} + +static void bt_l2cap_br_pack_i_frame_header( + struct bt_l2cap_br_chan *br_chan, struct net_buf *buf, uint16_t pdu_len, + uint8_t sar, uint8_t tx_seq) +{ + struct bt_l2cap_hdr *hdr; + uint16_t std_control; + uint16_t enh_control; + uint32_t ext_control; + uint8_t f_bit = 0; + uint8_t r_bit = 0; + + /* HDR space has been reserved */ + + hdr = net_buf_push(buf, sizeof(*hdr)); + + hdr->len = sys_cpu_to_le16(pdu_len); + hdr->cid = sys_cpu_to_le16(br_chan->tx.cid); + + if (atomic_test_and_clear_bit(br_chan->flags, L2CAP_FLAG_RECV_FRAME_P)) + { + f_bit = 1; + } + + if (atomic_test_bit(br_chan->flags, L2CAP_FLAG_LOCAL_BUSY) && + (br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_RET)) + { + r_bit = 1; + } + + if (br_chan->tx.mode != BT_L2CAP_BR_LINK_MODE_STREAM) + { + br_chan->req_seq = br_chan->expected_tx_seq; + } + else + { + br_chan->req_seq = 0; + } + + if ((br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_ERET) || + (br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_STREAM)) + { + if (br_chan->tx.extended_control) + { + /* Space occupied for extended control field. */ + + ext_control = BT_L2CAP_I_FRAME_EXT_CONTROL_SET(f_bit, tx_seq, sar, + br_chan->req_seq); + net_buf_add_le32(buf, ext_control); + } + else + { + /* Space occupied for enhanced control field. */ + + enh_control = BT_L2CAP_I_FRAME_ENH_CONTROL_SET(tx_seq, f_bit, + br_chan->req_seq, sar); + net_buf_add_le16(buf, enh_control); + } + } + else + { + /* Space occupied for standard control field. */ + + std_control = BT_L2CAP_I_FRAME_STD_CONTROL_SET(tx_seq, r_bit, + br_chan->req_seq, sar); + net_buf_add_le16(buf, std_control); + } +} + +static struct net_buf * +l2cap_br_get_next_sdu(struct bt_l2cap_br_chan *br_chan) +{ + struct net_buf *sdu; + struct net_buf *next; + + if (br_chan->_pdu_buf) + { + return br_chan->_pdu_buf; + } + + list_for_every_container_safe(&br_chan->_pdu_tx_queue, sdu, next, node) + { + if (l2cap_br_sdu_is_pending(sdu)) + { + return sdu; + } + } + + return NULL; +} + +static bool l2cap_br_retransmit_is_required(struct bt_l2cap_br_chan *br_chan) +{ + struct bt_l2cap_br_window *tx_win; + struct bt_l2cap_br_window *next; + + list_for_every_container_safe(&br_chan->_pdu_outstanding, tx_win, next, + node) + { + if (tx_win->retransmit) + { + return true; + } + } + + return false; +} + +static bool l2cap_br_send_i_frame(struct bt_l2cap_br_chan *br_chan, + struct net_buf *sdu) +{ + if (atomic_test_bit(br_chan->flags, L2CAP_FLAG_REMOTE_BUSY)) + { + return false; + } + + if (atomic_test_bit(br_chan->flags, L2CAP_FLAG_RET_REQ_I_FRAME)) + { + if (l2cap_br_find_srej(br_chan)) + { + return true; + } + } + + if (atomic_test_bit(br_chan->flags, L2CAP_FLAG_RET_I_FRAME)) + { + if (list_peek_head(&br_chan->_pdu_outstanding) != NULL) + { + return true; + } + } + + if (atomic_test_bit(br_chan->flags, L2CAP_FLAG_RET_I_FRAMES)) + { + return l2cap_br_retransmit_is_required(br_chan); + } + + if (atomic_test_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_P)) + { + return false; + } + + if (!atomic_test_bit(br_chan->flags, L2CAP_FLAG_NEW_I_FRAME)) + { + return false; + } + + if (!sdu) + { + return false; + } + + if (bt_l2cap_br_get_outstanding_count(br_chan) >= br_chan->tx.max_window) + { + return false; + } + + if (k_fifo_peek_head(&br_chan->_free_tx_win)) + { + return true; + } + + return false; +} + +static struct bt_l2cap_br_window *l2cap_br_get_ret_win( + struct bt_l2cap_br_chan *br_chan) +{ + struct bt_l2cap_br_window *tx_win; + struct bt_l2cap_br_window *next; + + list_for_every_container_safe(&br_chan->_pdu_outstanding, tx_win, next, + node) + { + if (tx_win->retransmit) + { + return tx_win; + } + } + + atomic_clear_bit(br_chan->flags, L2CAP_FLAG_RET_I_FRAMES); + return NULL; +} + +static struct net_buf *l2cap_br_ret_fc_data_pull( + struct bt_conn *conn, size_t amount, size_t *length) +{ + const struct list_node *pdu_ready = + list_peek_head(&conn->l2cap_data_ready); + + if (!pdu_ready) + { + LOG_DBG("nothing to send on this conn"); + return NULL; + } + + struct bt_l2cap_br_chan *br_chan = CONTAINER_OF(pdu_ready, + struct bt_l2cap_br_chan, _pdu_ready); + + if (br_chan->_pdu_buf && bt_buf_has_view(br_chan->_pdu_buf)) + { + LOG_ERR("already have view on %p", br_chan->_pdu_buf); + return NULL; + } + + /* Leave the PDU buffer in the queue until we have sent all its + * fragments. + */ + + struct net_buf *pdu = l2cap_br_get_next_sdu(br_chan); + struct net_buf *send_buf = NULL; + + if (br_chan->_pdu_remaining == 0) + { + struct bt_l2cap_br_window *tx_win; + bool first = true; + uint8_t s_bit = BT_L2CAP_CONTROL_S_RR; + bool alloc = false; + + if ((atomic_test_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_REJ) && + atomic_test_bit(br_chan->flags, + L2CAP_FLAG_SEND_FRAME_REJ_CHANGED)) || + (atomic_test_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_P) && + atomic_test_bit(br_chan->flags, + L2CAP_FLAG_SEND_FRAME_P_CHANGED)) || + (atomic_test_bit(br_chan->flags, L2CAP_FLAG_LOCAL_BUSY) && + atomic_test_bit(br_chan->flags, L2CAP_FLAG_LOCAL_BUSY_CHANGED))) + { + alloc = true; + } + + if (atomic_test_bit(br_chan->flags, L2CAP_FLAG_RECV_FRAME_P) && + !l2cap_br_send_i_frame(br_chan, pdu)) + { + alloc = true; + } + + /* Only send the S-frame if there is no any I-frames to be sent. */ + + if (atomic_test_bit(br_chan->flags, L2CAP_FLAG_SEND_S_FRAME) && + !l2cap_br_send_i_frame(br_chan, pdu)) + { + alloc = true; + } + + if (alloc) + { + int err; + + /* Send S-Frame with R bit set. */ + + send_buf = bt_l2cap_create_pdu_timeout(&br_tx_pool, 0, 0); + if (send_buf == NULL) + { + /* No buffer can be used for transmission. + * Waiting for buffer available. + */ + + LOG_WRN("no buf for sending S-frame on %p", br_chan); + return NULL; + } + + err = bt_l2cap_br_pack_s_frame_header(br_chan, send_buf, s_bit); + if (err < 0) + { + net_buf_unref(send_buf); + LOG_WRN("Cannot sending S-frame on %p", br_chan); + return NULL; + } + + if (BT_L2CAP_RT_FC_SDU_TAIL_SIZE(br_chan)) + { + uint16_t fcs = crc16_reflect(0xa001, 0, send_buf->data, + send_buf->len); + + net_buf_add_le16(send_buf, fcs); + } + + br_chan->_pdu_remaining = send_buf->len; + br_chan->_pdu_buf = send_buf; + + make_closure(send_buf->user_data, NULL, NULL); + + goto done; + } + + if (atomic_test_bit(br_chan->flags, L2CAP_FLAG_REMOTE_BUSY)) + { + /* RemoteBusy = TRUE, Pending sending data */ + + LOG_DBG("Remote is busy, cannot send I-frame on %p", br_chan); + lower_data_ready(br_chan); + return NULL; + } + + /* Found SREJ frame */ + + if (atomic_test_bit(br_chan->flags, L2CAP_FLAG_RET_REQ_I_FRAME)) + { + tx_win = l2cap_br_find_srej(br_chan); + if (tx_win) + { + first = false; + goto send_i_frame; + } + } + + /* Retransmission i-Frame */ + + if (atomic_test_and_clear_bit(br_chan->flags, L2CAP_FLAG_RET_I_FRAME)) + { + tx_win = (void *)list_peek_head(&br_chan->_pdu_outstanding); + if (tx_win != NULL) + { + first = false; + goto send_i_frame; + } + } + + if (atomic_test_bit(br_chan->flags, L2CAP_FLAG_RET_I_FRAMES)) + { + tx_win = l2cap_br_get_ret_win(br_chan); + if (tx_win != NULL) + { + first = false; + goto send_i_frame; + } + } + + if (atomic_test_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_P)) + { + /* Cannot send any new I-frames if waiting for F bit. + * New I-frames cannot be sent while in the WAIT_F state to prevent + * the situation where retransmission of I-frames could result in + * the channel being disconnected. + */ + + LOG_DBG("Waiting for F flag, cannot send I-frame on %p", br_chan); + lower_data_ready(br_chan); + return NULL; + } + + /* Cannot send out new I-frame */ + + if (!atomic_test_bit(br_chan->flags, L2CAP_FLAG_NEW_I_FRAME)) + { + /* Sending is blocked */ + + LOG_DBG("Sending is blocked, cannot send I-frame on %p", br_chan); + lower_data_ready(br_chan); + return NULL; + } + + /* Check whether all PDUs have been sent. But not of all are confirmed. + */ + + if (!pdu) + { + /* No pending I-frame needs to be sent */ + + LOG_DBG("No pending I-frame needs to be sent on %p", br_chan); + lower_data_ready(br_chan); + return NULL; + } + + tx_win = l2cap_br_find_window(br_chan); + if (!tx_win) + { + /* No more window for sending I-frame PDU. + * Remove the channel from the ready-list, it will be added back + * later when the S-frame PDU has been received. + */ + + LOG_DBG("no more window for sending I-frame on %p", br_chan); + lower_data_ready(br_chan); + return NULL; + } + +send_i_frame: + send_buf = bt_l2cap_create_pdu_timeout(&br_tx_pool, 0, 0); + if (send_buf == NULL) + { + /* No buffer can be used for transmission. + * Waiting for buffer available. + */ + + LOG_WRN("no buf for sending I-frame on %p", br_chan); + + /* Maybe we can disconnect channel here if retransmission I-frame. + */ + + /* Free allocated tx_win. */ + + if (first) + { + l2cap_br_free_window(br_chan, tx_win); + } + + return NULL; + } + + make_closure(send_buf->user_data, NULL, NULL); + + uint16_t pdu_len; + uint16_t actual_pdu_len; + uint8_t sar; + uint16_t fcs; + + if (first) + { + bool last_seg; + bool start_seg; + uint16_t sdu_len; + + sdu_len = l2cap_br_closure_sdu_len(pdu->user_data); + start_seg = sdu_len > pdu->len ? false : true; + DEBUGASSERT(sdu_len >= pdu->len); + + net_buf_simple_save(&pdu->b, &tx_win->sdu_state); + + pdu_len = get_pdu_len(br_chan, pdu); + + actual_pdu_len = pdu_len + BT_L2CAP_RT_FC_SDU_HDR_SIZE(br_chan) + + BT_L2CAP_RT_FC_SDU_TAIL_SIZE(br_chan); + + last_seg = pdu_len == pdu->len; + + sar = + start_seg ? + (last_seg ? + BT_L2CAP_CONTROL_SAR_UNSEG : + BT_L2CAP_CONTROL_SAR_START) + : (last_seg ? + BT_L2CAP_CONTROL_SAR_END : + BT_L2CAP_CONTROL_SAR_CONTI); + if (sar == BT_L2CAP_CONTROL_SAR_START) + { + net_buf_push_le16(pdu, pdu->len); + pdu_len += BT_L2CAP_RT_FC_SDU_LEN_SIZE; + } + else + { + /* Remove the SDU length from Header */ + + actual_pdu_len -= BT_L2CAP_RT_FC_SDU_LEN_SIZE; + } + + if (net_buf_tailroom(send_buf) < actual_pdu_len) + { + LOG_ERR("Tailroom of the buffer cannot be filled %zu / %zu", + net_buf_tailroom(send_buf), actual_pdu_len); + DEBUGASSERT(false); + net_buf_unref(send_buf); + l2cap_br_free_window(br_chan, tx_win); + return NULL; + } + + if (br_chan->tx.mode != BT_L2CAP_BR_LINK_MODE_STREAM) + { + br_chan->req_seq = br_chan->expected_tx_seq; + } + else + { + br_chan->req_seq = 0; + } + + br_chan->tx_seq = br_chan->next_tx_seq; + tx_win->tx_seq = br_chan->tx_seq; + tx_win->data = pdu->data; + tx_win->len = pdu_len; + tx_win->srej = false; + tx_win->retransmit = false; + tx_win->sar = sar; + tx_win->transmit_counter = 1; + tx_win->sdu = pdu; + + LOG_DBG("Sending I-frame %u: buf %p chan %p len %zu", + tx_win->tx_seq, + pdu, br_chan, + pdu_len); + } + else + { + bool disconnect = false; + + pdu_len = tx_win->len; + actual_pdu_len = pdu_len + BT_L2CAP_RT_FC_SDU_HDR_SIZE(br_chan) + + BT_L2CAP_RT_FC_SDU_TAIL_SIZE(br_chan); + sar = tx_win->sar; + /* Remove the SDU length from Header. + * Do not care SDU length, it has been handled in the first time. + */ + + actual_pdu_len -= BT_L2CAP_RT_FC_SDU_LEN_SIZE; + + tx_win->transmit_counter++; + tx_win->srej = false; + tx_win->retransmit = false; + + if (br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_ERET) + { + if (br_chan->tx.max_transmit && + (tx_win->transmit_counter > br_chan->tx.max_transmit)) + { + disconnect = true; + } + } + else + { + if (tx_win->transmit_counter > br_chan->tx.max_transmit) + { + disconnect = true; + } + } + + if (disconnect) + { + /* If the TransmitCounter is equal to MaxTransmit this channel + * to the peer entity shall be assumed lost. The channel shall + * move to the CLOSED state and appropriate action shall be + * taken to report this to the upper layers. + */ + + bt_l2cap_br_chan_disconnect(&br_chan->chan); + net_buf_unref(send_buf); + return NULL; + } + + LOG_WRN("Retransmission I-frame %u: buf %p chan %p len %zu", + tx_win->tx_seq, pdu, br_chan, + pdu_len); + } + + bt_l2cap_br_pack_i_frame_header(br_chan, send_buf, actual_pdu_len, sar, + tx_win->tx_seq); + + net_buf_add_mem(send_buf, tx_win->data, pdu_len); + + if (BT_L2CAP_RT_FC_SDU_TAIL_SIZE(br_chan)) + { + fcs = crc16_reflect(0xa001, 0, send_buf->data, send_buf->len); + net_buf_add_le16(send_buf, fcs); + } + + br_chan->_pdu_remaining = send_buf->len; + br_chan->_pdu_buf = send_buf; + + if (first) + { + br_chan->next_tx_seq = bt_l2cap_br_update_seq(br_chan, + br_chan->next_tx_seq + 1); + + l2cap_br_sdu_pull(pdu, pdu_len); + + if (br_chan->rx.mode != BT_L2CAP_BR_LINK_MODE_STREAM) + { + list_add_tail(&br_chan->_pdu_outstanding, &tx_win->node); + } + else + { + l2cap_br_free_window(br_chan, tx_win); + + if (pdu->len == 0) + { + l2cap_br_sdu_is_done(br_chan, pdu, 0); + } + } + } + } + + if (br_chan->_pdu_buf != NULL) + { + if (bt_l2cap_br_get_outstanding_count(br_chan) == 1) + { + /* It is the first one */ + + l2cap_br_start_timer(br_chan, BT_L2CAP_BR_TIMER_RET, true); + } + else + { + l2cap_br_start_timer(br_chan, BT_L2CAP_BR_TIMER_RET, false); + } + } + +done: + + /* Clear S frame flag if any frame sent */ + + atomic_clear_bit(br_chan->flags, L2CAP_FLAG_SEND_S_FRAME); + + /* This is used by `conn.c` to figure out if the PDU is done sending. */ + + *length = br_chan->_pdu_remaining; + + struct net_buf *buf; + + buf = net_buf_ref(br_chan->_pdu_buf); + + if (br_chan->_pdu_remaining > amount) + { + br_chan->_pdu_remaining -= amount; + } + else + { + net_buf_drop(&br_chan->_pdu_buf); + br_chan->_pdu_remaining = 0; + + LOG_DBG("done sending PDU"); + + /* Lowering the "request to send" and raising it again allows + * fair scheduling of channels on an ACL link: the channel is marked as + * "ready to send" by adding a reference to it on a FIFO on `conn`. + * Adding it again will send it to the back of the queue. + * + * TODO: add a user-controlled QoS function. + */ + + LOG_DBG("chan %p done", br_chan); + lower_data_ready(br_chan); + + /* Append channel to list if it still has data */ + + if (chan_has_data(br_chan)) + { + LOG_DBG("chan %p ready", br_chan); + raise_data_ready(br_chan); + } + } + + return buf; +} +#endif /* CONFIG_BT_L2CAP_RET_FC */ + +struct net_buf *l2cap_br_data_pull(struct bt_conn *conn, + size_t amount, size_t *length) +{ + const struct list_node *pdu_ready = + list_peek_head(&conn->l2cap_data_ready); + + if (!pdu_ready) + { + LOG_DBG("nothing to send on this conn"); + return NULL; + } + + DEBUGASSERT(conn->state == BT_CONN_CONNECTED); + + struct bt_l2cap_br_chan *br_chan; + + br_chan = CONTAINER_OF(pdu_ready, struct bt_l2cap_br_chan, _pdu_ready); + +#if defined(CONFIG_BT_L2CAP_RET_FC) + if (br_chan->tx.mode != BT_L2CAP_BR_LINK_MODE_BASIC) + { + struct net_buf *buf; + const struct list_node *next_pdu_ready; + + buf = l2cap_br_ret_fc_data_pull(conn, amount, length); + + /* When the returned buffer is a `NULL`, it means there is not any data + * needs to be sent. However maybe there is any frame pending on other + * L2CAP channel needs to be sent over the same ACL connection. + * Re-trigger the TX processor. It will call the function + * `l2cap_br_data_pull()` again and the pending buffer will be pulled + * from following L2CAP. + */ + + next_pdu_ready = list_peek_head(&conn->l2cap_data_ready); + if ((buf == NULL) && (next_pdu_ready != NULL) && + (next_pdu_ready != pdu_ready)) + { + bt_tx_irq_raise(); + } + + return buf; + } +#endif /* CONFIG_BT_L2CAP_RET_FC */ + + /* Leave the PDU buffer in the queue until we have sent all its + * fragments. + */ + + const struct list_node *tx_pdu = list_peek_head(&br_chan->_pdu_tx_queue); + + DEBUGASSERT(tx_pdu); + + struct net_buf *q_pdu = CONTAINER_OF(tx_pdu, struct net_buf, node); + + if (bt_buf_has_view(q_pdu)) + { + LOG_ERR("already have view on %p", q_pdu); + return NULL; + } + + struct net_buf *pdu = net_buf_ref(q_pdu); + + /* We can't interleave ACL fragments from different channels for the + * same ACL conn -> we have to wait until a full L2 PDU is transferred + * before switching channels. + */ + + bool last_frag = amount >= pdu->len; + + if (last_frag) + { + LOG_DBG("last frag, removing %p", q_pdu); + __maybe_unused bool found; + + found = list_find_and_remove(&br_chan->_pdu_tx_queue, &q_pdu->node); + + DEBUGASSERT(found); + + net_buf_unref(q_pdu); + + LOG_DBG("chan %p done", br_chan); + lower_data_ready(br_chan); + + /* Append channel to list if it still has data */ + + if (chan_has_data(br_chan)) + { + LOG_DBG("chan %p ready", br_chan); + raise_data_ready(br_chan); + } + } + + *length = pdu->len; + + return pdu; +} + +static struct net_buf *l2cap_br_create_pdu(struct net_buf_pool *pool, + clock_t timeout) +{ + struct net_buf *buf; + + buf = bt_l2cap_create_pdu_timeout(pool, 0, timeout); + if ((buf == NULL) && (pool != NULL)) + { + LOG_WRN("Fail to allocate buffer from pool %p", pool); + + /* If the pool is empty, try to allocate from the default pool without + * waiting. + */ + + buf = bt_l2cap_create_pdu_timeout(NULL, 0, 0); + } + + if (buf == NULL) + { + LOG_ERR("Fail to allocate buffer"); + return NULL; + } + + LOG_DBG("allocated buffer %p", buf); + return buf; +} + +static void l2cap_br_get_info(struct bt_l2cap_br *l2cap, uint16_t info_type) +{ + struct bt_l2cap_info_req *info; + struct net_buf *buf; + struct bt_l2cap_sig_hdr *hdr; + + LOG_DBG("info type %u", info_type); + + if (atomic_test_bit(l2cap->chan.flags, L2CAP_FLAG_SIG_INFO_PENDING)) + { + return; + } + + switch (info_type) + { + case BT_L2CAP_INFO_FEAT_MASK: + case BT_L2CAP_INFO_FIXED_CHAN: + case BT_L2CAP_INFO_CONNLESS_MTU: + break; + default: + LOG_WRN("Unsupported info type %u", info_type); + return; + } + + buf = l2cap_br_create_pdu(&br_sig_pool, ((clock_t)-1)); + if (buf == NULL) + { + LOG_ERR("Fail to send info req"); + + /* Consider disconnect ACL here. */ + + return; + } + + atomic_set_bit(l2cap->chan.flags, L2CAP_FLAG_SIG_INFO_PENDING); + l2cap->chan.ident = l2cap_br_get_ident(); + + hdr = net_buf_add(buf, sizeof(*hdr)); + hdr->code = BT_L2CAP_INFO_REQ; + hdr->ident = l2cap->chan.ident; + hdr->len = sys_cpu_to_le16(sizeof(*info)); + + info = net_buf_add(buf, sizeof(*info)); + info->type = sys_cpu_to_le16(info_type); + + l2cap_br_chan_send_req(&l2cap->chan, buf, L2CAP_BR_INFO_TIMEOUT); +} + +static void connect_fixed_channel(struct bt_l2cap_br_chan *chan) +{ + if (atomic_test_and_set_bit(chan->flags, L2CAP_FLAG_FIXED_CONNECTED)) + { + return; + } + + bt_l2cap_br_chan_set_state(&chan->chan, BT_L2CAP_CONNECTED); + if (chan->chan.ops && chan->chan.ops->connected) + { + chan->chan.ops->connected(&chan->chan); + } +} + +static void connect_optional_fixed_channels(struct bt_l2cap_br *l2cap) +{ + /* can be change to loop if more BR/EDR fixed channels are added */ + + if (l2cap->info_fixed_chan & BIT(BT_L2CAP_CID_BR_SMP)) + { + struct bt_l2cap_chan *chan; + + chan = bt_l2cap_br_lookup_rx_cid(l2cap->chan.chan.conn, + BT_L2CAP_CID_BR_SMP); + if (chan) + { + connect_fixed_channel(BR_CHAN(chan)); + } + } +} + +static int l2cap_br_info_rsp(struct bt_l2cap_br *l2cap, + uint8_t ident, struct net_buf *buf) +{ + struct bt_l2cap_info_rsp *rsp; + uint16_t type; + uint16_t result; + int err = 0; + + if (atomic_test_bit(l2cap->chan.flags, L2CAP_FLAG_SIG_INFO_DONE)) + { + return 0; + } + + if (atomic_test_and_clear_bit(l2cap->chan.flags, + L2CAP_FLAG_SIG_INFO_PENDING)) + { + /* Release RTX timer since got the response & there's pending + * command request. + */ + + k_work_cancel_delayable(&l2cap->chan.rtx_work); + } + + if (buf->len < sizeof(*rsp)) + { + LOG_ERR("Too small info rsp packet size"); + err = -EINVAL; + goto done; + } + + if (ident != l2cap->chan.ident) + { + LOG_WRN("Idents mismatch"); + err = -EINVAL; + goto done; + } + + rsp = net_buf_pull_mem(buf, sizeof(*rsp)); + result = sys_le16_to_cpu(rsp->result); + if (result != BT_L2CAP_INFO_SUCCESS) + { + LOG_WRN("Result unsuccessful"); + err = -EINVAL; + goto done; + } + + type = sys_le16_to_cpu(rsp->type); + + switch (type) + { + case BT_L2CAP_INFO_FEAT_MASK: + if (buf->len < sizeof(uint32_t)) + { + LOG_ERR("Invalid remote info feat mask"); + err = -EINVAL; + break; + } + + l2cap->info_feat_mask = net_buf_pull_le32(buf); + LOG_DBG("remote info mask 0x%08x", l2cap->info_feat_mask); + + if (!(l2cap->info_feat_mask & L2CAP_FEAT_FIXED_CHAN_MASK)) + { + break; + } + + l2cap_br_get_info(l2cap, BT_L2CAP_INFO_FIXED_CHAN); + return 0; + case BT_L2CAP_INFO_FIXED_CHAN: + if (buf->len < sizeof(uint8_t)) + { + LOG_ERR("Invalid remote info fixed chan"); + err = -EINVAL; + break; + } + + /* 2.1 CHANNEL IDENTIFIERS in + * BLUETOOTH CORE SPECIFICATION Version 5.4 | Vol 3, Part A. The info + * length of `Fixed channels supported` is 8 octets. Then the range + * of + * fixed L2CAP CID is 0x0001 ~ 0x0007 both for LE and BR. So use one + * octet buffer to keep the `Fixed channels supported` of peer + * device. + */ + + l2cap->info_fixed_chan = net_buf_pull_u8(buf); + LOG_DBG("remote fixed channel mask 0x%02x", l2cap->info_fixed_chan); + + connect_optional_fixed_channels(l2cap); + +#if defined(CONFIG_BT_L2CAP_CONNLESS) + if (!(l2cap->info_feat_mask & L2CAP_FEAT_CONNLESS_MASK)) + { + break; + } + + l2cap_br_get_info(l2cap, BT_L2CAP_INFO_CONNLESS_MTU); + return 0; + case BT_L2CAP_INFO_CONNLESS_MTU: + { + struct bt_l2cap_chan *connless_chan; + + if (buf->len < sizeof(uint16_t)) + { + LOG_ERR("Invalid remote info connectionless MTU"); + err = -EINVAL; + break; + } + + connless_chan = bt_l2cap_br_lookup_rx_cid(l2cap->chan.chan.conn, + BT_L2CAP_CID_CONNLESS); + if (connless_chan != NULL) + { + BR_CHAN(connless_chan)->tx.mtu = net_buf_pull_le16(buf); + break; + } + } + + __fallthrough; +#else + break; +#endif /* CONFIG_BT_L2CAP_CONNLESS */ + default: + LOG_WRN("type 0x%04x unsupported", type); + err = -EINVAL; + break; + } + +done: + atomic_set_bit(l2cap->chan.flags, L2CAP_FLAG_SIG_INFO_DONE); + l2cap->chan.ident = 0U; + return err; +} + +static uint8_t get_fixed_channels_mask(void) +{ + uint8_t mask = 0U; + + /* this needs to be enhanced if AMP Test Manager support is added */ + + STRUCT_SECTION_FOREACH(bt_l2cap_br_fixed_chan, fchan) + { + if (!IS_ENABLED(CONFIG_BT_SMP_DERIVE_LTK) && + fchan->cid == BT_L2CAP_CID_BR_SMP) + { + /* Nothing would be accepted on this channel, so do not + * invite the peer to start a derivation on it. + */ + + continue; + } + + mask |= BIT(fchan->cid); + } + + return mask; +} + +static int l2cap_br_info_req(struct bt_l2cap_br *l2cap, + uint8_t ident, struct net_buf *buf) +{ + struct bt_conn *conn = l2cap->chan.chan.conn; + struct bt_l2cap_info_req *req = (void *)buf->data; + struct bt_l2cap_info_rsp *rsp; + struct net_buf *rsp_buf; + struct bt_l2cap_sig_hdr *hdr_info; + uint16_t type; + + if (buf->len < sizeof(*req)) + { + LOG_ERR("Too small info req packet size"); + return -EINVAL; + } + + rsp_buf = l2cap_br_create_pdu(&br_sig_pool, L2CAP_BR_INFO_TIMEOUT); + if (rsp_buf == NULL) + { + LOG_ERR("Fail to response info req"); + return -ENOBUFS; + } + + type = sys_le16_to_cpu(req->type); + LOG_DBG("type 0x%04x", type); + + hdr_info = net_buf_add(rsp_buf, sizeof(*hdr_info)); + hdr_info->code = BT_L2CAP_INFO_RSP; + hdr_info->ident = ident; + + rsp = net_buf_add(rsp_buf, sizeof(*rsp)); + + switch (type) + { + case BT_L2CAP_INFO_FEAT_MASK: + rsp->type = sys_cpu_to_le16(BT_L2CAP_INFO_FEAT_MASK); + rsp->result = sys_cpu_to_le16(BT_L2CAP_INFO_SUCCESS); + net_buf_add_le32(rsp_buf, L2CAP_EXTENDED_FEAT_MASK); + hdr_info->len = sys_cpu_to_le16(sizeof(*rsp) + sizeof(uint32_t)); + break; + case BT_L2CAP_INFO_FIXED_CHAN: + rsp->type = sys_cpu_to_le16(BT_L2CAP_INFO_FIXED_CHAN); + rsp->result = sys_cpu_to_le16(BT_L2CAP_INFO_SUCCESS); + + /* fixed channel mask protocol data is 8 octets wide */ + + (void)memset(net_buf_add(rsp_buf, 8), 0, 8); + rsp->data[0] = get_fixed_channels_mask(); + + hdr_info->len = sys_cpu_to_le16(sizeof(*rsp) + 8); + break; +#if defined(CONFIG_BT_L2CAP_CONNLESS) + case BT_L2CAP_INFO_CONNLESS_MTU: + { + struct bt_l2cap_chan *connless_chan; + + connless_chan = bt_l2cap_br_lookup_rx_cid(conn, + BT_L2CAP_CID_CONNLESS); + if (connless_chan != NULL) + { + rsp->type = sys_cpu_to_le16(BT_L2CAP_INFO_CONNLESS_MTU); + rsp->result = sys_cpu_to_le16(BT_L2CAP_INFO_SUCCESS); + net_buf_add_le16(rsp_buf, BR_CHAN(connless_chan)->rx.mtu); + hdr_info->len = + sys_cpu_to_le16(sizeof(*rsp) + sizeof(uint16_t)); + break; + } + } + + __fallthrough; +#endif /* CONFIG_BT_L2CAP_CONNLESS */ + default: + rsp->type = req->type; + rsp->result = sys_cpu_to_le16(BT_L2CAP_INFO_NOTSUPP); + hdr_info->len = sys_cpu_to_le16(sizeof(*rsp)); + break; + } + + l2cap_send(conn, BT_L2CAP_CID_BR_SIG, rsp_buf); + + return 0; +} + +void bt_l2cap_br_connected(struct bt_conn *conn) +{ + struct bt_l2cap_chan *chan; + + bt_l2cap_br_cid_init(conn); + + STRUCT_SECTION_FOREACH(bt_l2cap_br_fixed_chan, fchan) + { + struct bt_l2cap_br_chan *br_chan; + + if (!fchan->accept) + { + continue; + } + + if (fchan->accept(conn, &chan) < 0) + { + continue; + } + + br_chan = BR_CHAN(chan); + + br_chan->rx.cid = fchan->cid; + br_chan->tx.cid = fchan->cid; + + if (!l2cap_br_chan_add(conn, chan, NULL)) + { + return; + } + + bt_l2cap_br_chan_set_state(chan, BT_L2CAP_CONNECTING); + +/* other fixed channels will be connected after Information + * Response is received + */ + + if (fchan->cid == BT_L2CAP_CID_BR_SIG) + { + struct bt_l2cap_br *sig_ch; + + connect_fixed_channel(br_chan); + + sig_ch = CONTAINER_OF(br_chan, struct bt_l2cap_br, chan); + l2cap_br_get_info(sig_ch, BT_L2CAP_INFO_FEAT_MASK); + } + } +} + +void bt_l2cap_br_disconnected(struct bt_conn *conn) +{ + struct bt_l2cap_chan *chan; + struct bt_l2cap_chan *next; + + list_for_every_container_safe(&conn->channels, chan, next, node) + { + bt_l2cap_br_chan_del(chan); + } +} + +static struct bt_l2cap_server *l2cap_br_server_lookup_psm(uint16_t psm) +{ + struct bt_l2cap_server *server; + + list_for_every_container(&br_servers, server, node) + { + if (server->psm == psm) + { + return server; + } + } + + return NULL; +} + +static void l2cap_br_conf_add_mtu(struct net_buf *buf, const uint16_t mtu) +{ + if (net_buf_tailroom(buf) < (sizeof(struct bt_l2cap_conf_opt) + + sizeof(mtu))) + { + LOG_WRN("No tail room for opt mtu"); + return; + } + + net_buf_add_u8(buf, BT_L2CAP_CONF_OPT_MTU); + net_buf_add_u8(buf, sizeof(mtu)); + net_buf_add_le16(buf, mtu); +} + +#if defined(CONFIG_BT_L2CAP_RET_FC) +static void l2cap_br_conf_add_ret_fc(struct net_buf *buf, + const struct bt_l2cap_conf_opt_ret_fc *ret_fc) +{ + if (net_buf_tailroom(buf) < + (sizeof(struct bt_l2cap_conf_opt) + sizeof(*ret_fc))) + { + LOG_WRN("No tail room for opt flash_timeout"); + return; + } + + net_buf_add_u8(buf, BT_L2CAP_CONF_OPT_RET_FC); + net_buf_add_u8(buf, sizeof(*ret_fc)); + net_buf_add_mem(buf, ret_fc, sizeof(*ret_fc)); +} + +static void l2cap_br_conf_add_fcs(struct net_buf *buf, + const struct bt_l2cap_conf_opt_fcs *fcs) +{ + if (net_buf_tailroom(buf) < (sizeof(struct bt_l2cap_conf_opt) + + sizeof(*fcs))) + { + LOG_WRN("No tail room for opt flash_timeout"); + return; + } + + net_buf_add_u8(buf, BT_L2CAP_CONF_OPT_FCS); + net_buf_add_u8(buf, sizeof(*fcs)); + net_buf_add_mem(buf, fcs, sizeof(*fcs)); +} + +static void +l2cap_br_conf_add_ext_win_size(struct net_buf *buf, + const struct bt_l2cap_conf_opt_ext_win_size *ext_win_size) +{ + if (net_buf_tailroom(buf) < + (sizeof(struct bt_l2cap_conf_opt) + sizeof(*ext_win_size))) + { + LOG_WRN("No tail room for opt flash_timeout"); + return; + } + + net_buf_add_u8(buf, BT_L2CAP_CONF_OPT_EXT_WIN_SIZE); + net_buf_add_u8(buf, sizeof(*ext_win_size)); + net_buf_add_mem(buf, ext_win_size, sizeof(*ext_win_size)); +} +#endif /* CONFIG_BT_L2CAP_RET_FC */ + +static void l2cap_br_conf_add_opt(struct net_buf *buf, + const struct bt_l2cap_conf_opt *opt) +{ + if (net_buf_tailroom(buf) < (sizeof(*opt) + opt->len)) + { + LOG_WRN("No tail room for opt flash_timeout"); + return; + } + + net_buf_add_u8(buf, opt->type & BT_L2CAP_CONF_MASK); + net_buf_add_u8(buf, opt->len); + net_buf_add_mem(buf, opt->data, opt->len); +} + +#if defined(CONFIG_BT_L2CAP_RET_FC) +static uint16_t l2cap_br_get_rx_mtu(struct bt_l2cap_br_chan *br_chan) +{ + uint16_t rx_mtu; + + rx_mtu = BT_L2CAP_RX_MTU; + + if ((br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_BASIC) && + !br_chan->rx.optional) + { + return rx_mtu; + } + + /* Discount the overhead of the I-frame for non-basic modes. */ + + if (IS_ENABLED(CONFIG_BT_L2CAP_ENH_RET) || + IS_ENABLED(CONFIG_BT_L2CAP_STREAM)) + { + /* Due to the extended_control being used if any one side of the L2CAP + * entities supports Extended Window Size option, discount the maximum + * size of control field to avoid the issue that the MTU exceeding the + * maximum payload of the I-frame. + */ + + rx_mtu -= BT_L2CAP_EXT_CONTROL_SIZE; + } + else + { + rx_mtu -= BT_L2CAP_STD_CONTROL_SIZE; + } + + /* Due to the FCS field being used if any one side of the L2CAP entities + * does not support the FCS option, discount the FCS size to avoid the + * issue that the MTU exceeding the maximum payload of the I-frame. + */ + + rx_mtu -= BT_L2CAP_FCS_SIZE; + + DEBUGASSERT(rx_mtu >= L2CAP_BR_MIN_MTU); + + return rx_mtu; +} + +static int l2cap_br_check_chan_config(struct bt_conn *conn, + struct bt_l2cap_br_chan *br_chan) +{ + struct bt_l2cap_chan *chan_sig; + struct bt_l2cap_br *br_chan_sig; + + if (!conn) + { + return -ENOTCONN; + } + + chan_sig = bt_l2cap_br_lookup_rx_cid(conn, BT_L2CAP_CID_BR_SIG); + if (!chan_sig) + { + LOG_WRN("Ignoring data for unknown channel ID 0x%04x", + BT_L2CAP_CID_BR_SIG); + return -EINVAL; + } + + br_chan_sig = CONTAINER_OF(chan_sig, struct bt_l2cap_br, chan.chan); + + br_chan->rx.mtu = MIN(br_chan->rx.mtu, l2cap_br_get_rx_mtu(br_chan)); + + /* Disable segment/reassemble of l2cap rx pdu by default. */ + + br_chan->rx.mps = br_chan->rx.mtu; + + br_chan->tx.mps = CONFIG_BT_L2CAP_MPS; + br_chan->tx.mtu = L2CAP_BR_DEFAULT_MTU; + + /* Set FCS to default value */ + + br_chan->tx.fcs = BT_L2CAP_BR_FCS_16BIT; + + /* Set default value */ + + br_chan->rx.max_window = CONFIG_BT_L2CAP_MAX_WINDOW_SIZE; + br_chan->rx.ret_timeout = CONFIG_BT_L2CAP_BR_RET_TIMEOUT; + br_chan->rx.monitor_timeout = CONFIG_BT_L2CAP_BR_MONITOR_TIMEOUT; + + br_chan->tx.extended_control = false; + br_chan->tx.optional = false; + br_chan->tx.max_transmit = 1; + br_chan->tx.max_window = CONFIG_BT_L2CAP_MAX_WINDOW_SIZE; + br_chan->tx.ret_timeout = CONFIG_BT_L2CAP_BR_RET_TIMEOUT; + br_chan->tx.monitor_timeout = CONFIG_BT_L2CAP_BR_MONITOR_TIMEOUT; + + /* Set all variables and sequence numbers to zero */ + + br_chan->tx_seq = 0; + br_chan->next_tx_seq = 0; + br_chan->expected_ack_seq = 0; + br_chan->req_seq = 0; + br_chan->expected_tx_seq = 0; + br_chan->buffer_seq = 0; + + memset(&br_chan->tx_win[0], 0, sizeof(br_chan->tx_win)); + + list_initialize(&br_chan->_pdu_outstanding); + k_fifo_init(&br_chan->_free_tx_win); + + for (int i = 0; i < ARRAY_SIZE(br_chan->tx_win); i++) + { + k_fifo_put(&br_chan->_free_tx_win, &br_chan->tx_win[i]); + } + + switch (br_chan->rx.mode) + { + case BT_L2CAP_BR_LINK_MODE_BASIC: + break; + case BT_L2CAP_BR_LINK_MODE_RET: + __fallthrough; + case BT_L2CAP_BR_LINK_MODE_FC: + if ((br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_FC) && + (((br_chan_sig->info_feat_mask & L2CAP_FEAT_FC_MASK) == 0) || + ((L2CAP_EXTENDED_FEAT_MASK & L2CAP_FEAT_FC_MASK) == 0))) + { + if (!br_chan->rx.optional) + { + LOG_ERR("Invalid mode %u", br_chan->rx.mode); + return -ENOTSUP; + } + + LOG_WRN("Unsupp mode %u, set basic mode", br_chan->rx.mode); + br_chan->rx.mode = BT_L2CAP_BR_LINK_MODE_BASIC; + break; + } + + if ((br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_RET) && + (((br_chan_sig->info_feat_mask & L2CAP_FEAT_RET_MASK) == 0) || + ((L2CAP_EXTENDED_FEAT_MASK & L2CAP_FEAT_RET_MASK) == 0))) + { + if (!br_chan->rx.optional) + { + LOG_ERR("Invalid mode %u", br_chan->rx.mode); + return -ENOTSUP; + } + + LOG_WRN("Unsupp mode %u, set basic mode", br_chan->rx.mode); + br_chan->rx.mode = BT_L2CAP_BR_LINK_MODE_BASIC; + break; + } + + if (((br_chan_sig->info_feat_mask & L2CAP_FEAT_ENH_RET_MASK) != 0) && + ((L2CAP_EXTENDED_FEAT_MASK & L2CAP_FEAT_ENH_RET_MASK) != 0)) + { + /* If enhance transmission mode is supported by both side, + * the enhance transmission mode should be used instead of + * transmission or flow control mode. + */ + + br_chan->rx.mode = BT_L2CAP_BR_LINK_MODE_ERET; + br_chan->tx.mode = BT_L2CAP_BR_LINK_MODE_ERET; + } + break; + case BT_L2CAP_BR_LINK_MODE_ERET: + if (((br_chan_sig->info_feat_mask & L2CAP_FEAT_ENH_RET_MASK) == 0) || + ((L2CAP_EXTENDED_FEAT_MASK & L2CAP_FEAT_ENH_RET_MASK) == 0)) + { + if (!br_chan->rx.optional) + { + LOG_ERR("Invalid mode %u", br_chan->rx.mode); + return -ENOTSUP; + } + + LOG_WRN("Unsupp mode %u, set basic mode", br_chan->rx.mode); + br_chan->rx.mode = BT_L2CAP_BR_LINK_MODE_BASIC; + break; + } + break; + case BT_L2CAP_BR_LINK_MODE_STREAM: + if (((br_chan_sig->info_feat_mask & L2CAP_FEAT_STREAM_MASK) == 0) || + ((L2CAP_EXTENDED_FEAT_MASK & L2CAP_FEAT_STREAM_MASK) == 0)) + { + if (!br_chan->rx.optional) + { + LOG_ERR("Invalid mode %u", br_chan->rx.mode); + return -ENOTSUP; + } + + LOG_WRN("Unsupp mode %u, set basic mode", br_chan->rx.mode); + br_chan->rx.mode = BT_L2CAP_BR_LINK_MODE_BASIC; + break; + } + break; + default: + br_chan->rx.mode = BT_L2CAP_BR_LINK_MODE_BASIC; + br_chan->tx.mode = BT_L2CAP_BR_LINK_MODE_BASIC; + break; + } + + atomic_set_bit(br_chan->flags, L2CAP_FLAG_NEW_I_FRAME); + + switch (br_chan->rx.mode) + { + case BT_L2CAP_BR_LINK_MODE_BASIC: + br_chan->tx.fcs = 0; + br_chan->rx.fcs = 0; + break; + case BT_L2CAP_BR_LINK_MODE_RET: + __fallthrough; + case BT_L2CAP_BR_LINK_MODE_FC: + if (br_chan->rx.max_window < 1) + { + br_chan->rx.max_window = 1; + } + else if (br_chan->rx.max_window > 32) + { + br_chan->rx.max_window = 32; + } + + if (br_chan->rx.max_transmit < 1) + { + br_chan->rx.max_transmit = 1; + } + + br_chan->tx.fcs = BT_L2CAP_BR_FCS_16BIT; + br_chan->rx.fcs = BT_L2CAP_BR_FCS_16BIT; + break; + case BT_L2CAP_BR_LINK_MODE_ERET: + if (((br_chan_sig->info_feat_mask & + L2CAP_FEAT_EXT_WIN_SIZE_MASK) == 0) || + ((L2CAP_EXTENDED_FEAT_MASK & L2CAP_FEAT_EXT_WIN_SIZE_MASK) == 0)) + { + LOG_WRN("Unsupported extended window size"); + br_chan->rx.extended_control = false; + br_chan->tx.extended_control = false; + } + + if (br_chan->rx.max_window < 1) + { + br_chan->rx.max_window = 1; + } + else if (br_chan->rx.max_window > 63) + { + if (br_chan->rx.extended_control) + { + br_chan->rx.max_window = MIN(br_chan->rx.max_window, 0x3fff); + } + else + { + br_chan->rx.max_window = 63; + } + } + + if (br_chan->rx.max_transmit < 1) + { + br_chan->rx.max_transmit = 1; + } + + if (((br_chan_sig->info_feat_mask & L2CAP_FEAT_FCS_MASK) == 0) || + ((L2CAP_EXTENDED_FEAT_MASK & L2CAP_FEAT_FCS_MASK) == 0)) + { + LOG_WRN("Unsupported FCS option, force to enable FCS"); + br_chan->tx.fcs = BT_L2CAP_BR_FCS_16BIT; + br_chan->rx.fcs = BT_L2CAP_BR_FCS_16BIT; + } + break; + case BT_L2CAP_BR_LINK_MODE_STREAM: + if (((br_chan_sig->info_feat_mask & + L2CAP_FEAT_EXT_WIN_SIZE_MASK) == 0) || + ((L2CAP_EXTENDED_FEAT_MASK & L2CAP_FEAT_EXT_WIN_SIZE_MASK) == 0)) + { + LOG_WRN("Unsupported extended window size"); + br_chan->rx.extended_control = false; + br_chan->tx.extended_control = false; + } + + br_chan->rx.max_transmit = 1; + if (((br_chan_sig->info_feat_mask & L2CAP_FEAT_FCS_MASK) == 0) || + ((L2CAP_EXTENDED_FEAT_MASK & L2CAP_FEAT_FCS_MASK) == 0)) + { + LOG_WRN("Unsupported FCS option, force to enable FCS"); + br_chan->tx.fcs = BT_L2CAP_BR_FCS_16BIT; + br_chan->rx.fcs = BT_L2CAP_BR_FCS_16BIT; + } + break; + default: + + /* Should not have gotten here */ + + break; + } + + return 0; +} +#endif /* CONFIG_BT_L2CAP_RET_FC */ + +static void l2cap_br_chan_disconn(struct bt_l2cap_chan *chan) +{ + int err = -EINVAL; + + /* Only send disconnect req when the L2CAP channel has been connected. */ + + if ((BR_CHAN(chan)->state == BT_L2CAP_CONFIG) || + (BR_CHAN(chan)->state == BT_L2CAP_CONNECTED)) + { + err = bt_l2cap_br_chan_disconnect(chan); + } + + if (err != 0) + { + /* Fail to send disconnect request. Clean up the channel directly. */ + + l2cap_br_chan_cleanup(chan); + } +} + +static void l2cap_br_conf(struct bt_l2cap_chan *chan, bool init) +{ + struct bt_l2cap_sig_hdr *hdr; + struct bt_l2cap_conf_req *conf; + struct net_buf *buf; + +#if defined(CONFIG_BT_L2CAP_RET_FC) + if (init) + { + int err; + + err = l2cap_br_check_chan_config(chan->conn, BR_CHAN(chan)); + if (err) + { + bt_l2cap_chan_disconnect(chan); + return; + } + } +#else + (void)init; +#endif /* CONFIG_BT_L2CAP_RET_FC */ + + buf = l2cap_br_create_pdu(&br_sig_pool, ((clock_t)-1)); + if (buf == NULL) + { + l2cap_br_chan_disconn(chan); + return; + } + + hdr = net_buf_add(buf, sizeof(*hdr)); + hdr->code = BT_L2CAP_CONF_REQ; + hdr->ident = l2cap_br_get_ident(); + conf = net_buf_add(buf, sizeof(*conf)); + (void)memset(conf, 0, sizeof(*conf)); + + conf->dcid = sys_cpu_to_le16(BR_CHAN(chan)->tx.cid); + + /* Set the ident for the signaling request */ + + BR_CHAN(chan)->ident = hdr->ident; + /* Add MTU option if app set non default BR/EDR L2CAP MTU, + * otherwise sent empty configuration data meaning default MTU to be used. + */ + + if (BR_CHAN(chan)->rx.mtu != L2CAP_BR_DEFAULT_MTU) + { + l2cap_br_conf_add_mtu(buf, BR_CHAN(chan)->rx.mtu); + } + +#if defined(CONFIG_BT_L2CAP_RET_FC) + if (BR_CHAN(chan)->rx.mode != BT_L2CAP_BR_LINK_MODE_BASIC) + { + struct bt_l2cap_conf_opt_ret_fc ret_fc; + struct bt_l2cap_conf_opt_fcs fcs; + + ret_fc.mode = BR_CHAN(chan)->rx.mode; + ret_fc.tx_windows_size = BR_CHAN(chan)->rx.max_window; + ret_fc.max_transmit = BR_CHAN(chan)->rx.max_transmit; + ret_fc.retransmission_timeout = + sys_cpu_to_le16(BR_CHAN(chan)->rx.ret_timeout); + ret_fc.monitor_timeout = + sys_cpu_to_le16(BR_CHAN(chan)->rx.monitor_timeout); + ret_fc.mps = sys_cpu_to_le16(BR_CHAN(chan)->rx.mps); + l2cap_br_conf_add_ret_fc(buf, &ret_fc); + + fcs.type = BR_CHAN(chan)->rx.fcs; + if (fcs.type == BT_L2CAP_FCS_TYPE_NO) + { + l2cap_br_conf_add_fcs(buf, &fcs); + } + + if (BR_CHAN(chan)->rx.extended_control && + ((BR_CHAN(chan)->rx.mode == BT_L2CAP_BR_LINK_MODE_ERET) || + (BR_CHAN(chan)->rx.mode == BT_L2CAP_BR_LINK_MODE_STREAM))) + { + struct bt_l2cap_conf_opt_ext_win_size ext_win_size; + + if (BR_CHAN(chan)->rx.mode == BT_L2CAP_BR_LINK_MODE_STREAM) + { + ext_win_size.max_windows_size = 0; + } + else + { + ext_win_size.max_windows_size = + sys_cpu_to_le16(BR_CHAN(chan)->rx.max_window); + } + + l2cap_br_conf_add_ext_win_size(buf, &ext_win_size); + } + } +#endif /* CONFIG_BT_L2CAP_RET_FC */ + + hdr->len = sys_cpu_to_le16(buf->len - sizeof(*hdr)); + + /* TODO: + * might be needed to start tracking number of configuration iterations on + * both directions + */ + + l2cap_br_chan_send_req(BR_CHAN(chan), buf, L2CAP_BR_CFG_TIMEOUT); +} + +enum l2cap_br_conn_security_result +{ + L2CAP_CONN_SECURITY_PASSED, + L2CAP_CONN_SECURITY_REJECT, + L2CAP_CONN_SECURITY_PENDING +}; + +/* Security helper against channel connection. + * Returns L2CAP_CONN_SECURITY_PASSED if: + * - existing security on link is applicable for requested PSM in connection, + * - legacy (non SSP) devices connecting with low security requirements, + * Returns L2CAP_CONN_SECURITY_PENDING if: + * - channel connection process is on hold since there were valid security + * conditions triggering authentication indirectly in subcall. Returns + * L2CAP_CONN_SECURITY_REJECT if: + * - bt_conn_set_security API returns < 0, + * - Or, the ACL connection has been encrypted, the security level of link + * key cannot be upgraded, and the security level is less than the required + * security level. + */ + +static enum l2cap_br_conn_security_result l2cap_br_conn_security( + struct bt_l2cap_chan *chan, const uint16_t psm) +{ + int check; + struct bt_l2cap_br_chan *br_chan = BR_CHAN(chan); + + /* For SDP PSM there's no need to change existing security on link */ + + if (br_chan->required_sec_level == BT_SECURITY_L0) + { + return L2CAP_CONN_SECURITY_PASSED; + } + + /* No link key needed for legacy devices (pre 2.1) and when low security + * level is required. + */ + + if (br_chan->required_sec_level == BT_SECURITY_L1 && + !BT_FEAT_HOST_SSP(chan->conn->br.features)) + { + return L2CAP_CONN_SECURITY_PASSED; + } + + switch (br_chan->required_sec_level) + { + case BT_SECURITY_L4: + case BT_SECURITY_L3: + case BT_SECURITY_L2: + break; + default: + /* For non SDP PSM connections GAP's Security Mode 4 requires at + * least unauthenticated link key and enabled encryption if remote + * supports SSP before any L2CAP CoC traffic. So preset local to + * MEDIUM + * security to trigger it if needed. + */ + + if (BT_FEAT_HOST_SSP(chan->conn->br.features)) + { + br_chan->required_sec_level = BT_SECURITY_L2; + } + break; + } + + if (chan->conn->sec_level < br_chan->required_sec_level && + chan->conn->encrypt && + chan->conn->br.link_key + && (chan->conn->br.link_key->flags & BT_LINK_KEY_AUTHENTICATED)) + { + /* If the ACL link has been encrypted and it has a authenticated link + * key, it means the pairing procedure has been done. And the security + * level of the link key can not be upgraded. In this case, if + * `conn->sec_level` is less than the required security level of the + * L2CAP channel, reject the L2CAP conn request. + */ + + return L2CAP_CONN_SECURITY_REJECT; + } + + check = bt_conn_set_security(chan->conn, br_chan->required_sec_level); + + /* Check case when on existing connection security level already covers + * channel (service) security requirements against link security and + * bt_conn_set_security API returns 0 what implies also there was no need + * to trigger authentication. + */ + + if (check == 0 && chan->conn->sec_level >= br_chan->required_sec_level) + { + return L2CAP_CONN_SECURITY_PASSED; + } + + /* If 'check' still holds 0, it means local host just sent HCI + * authentication command to start procedure to increase link security + * since service/profile requires that. + */ + + if (check == 0) + { + /* General Bonding refers to the process of performing bonding + * during connection setup or channel establishment procedures as a + * precursor to accessing a service. For current case, it is dedicated + * bonding. + */ + + atomic_set_bit(chan->conn->flags, BT_CONN_BR_GENERAL_BONDING); + return L2CAP_CONN_SECURITY_PENDING; + } + + /* For any other values in 'check' it means there was internal + * validation condition forbidding to start authentication at this moment. + */ + + return L2CAP_CONN_SECURITY_REJECT; +} + +static void l2cap_br_conn_rsp_sent_cb(struct bt_conn *conn, + void *user_data, int err) +{ + uint16_t scid = POINTER_TO_UINT(user_data); + struct bt_l2cap_chan *chan; + + chan = bt_l2cap_br_lookup_tx_cid(conn, scid); + if (!chan) + { + return; + } + + /* Check whether the ACL connection needs to be disconnected. */ + + if (atomic_test_and_clear_bit(BR_CHAN(chan)->flags, + L2CAP_FLAG_DISCONNECT_ACL)) + { + bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL); + } +} + +static void l2cap_br_send_conn_rsp( + struct bt_conn *conn, uint16_t scid, uint16_t dcid, uint8_t ident, + uint16_t result) +{ + struct net_buf *buf; + struct bt_l2cap_conn_rsp *rsp; + struct bt_l2cap_sig_hdr *hdr; + + buf = l2cap_br_create_pdu(&br_sig_pool, L2CAP_BR_CONN_TIMEOUT); + if (buf == NULL) + { + struct bt_l2cap_chan *chan = bt_l2cap_br_lookup_tx_cid(conn, scid); + + if (chan != NULL) + { + l2cap_br_chan_disconn(chan); + } + + return; + } + + hdr = net_buf_add(buf, sizeof(*hdr)); + hdr->code = BT_L2CAP_CONN_RSP; + hdr->ident = ident; + hdr->len = sys_cpu_to_le16(sizeof(*rsp)); + + rsp = net_buf_add(buf, sizeof(*rsp)); + rsp->dcid = sys_cpu_to_le16(dcid); + rsp->scid = sys_cpu_to_le16(scid); + rsp->result = sys_cpu_to_le16(result); + + if (result == BT_L2CAP_BR_PENDING) + { + rsp->status = sys_cpu_to_le16(BT_L2CAP_CS_AUTHEN_PEND); + } + else + { + rsp->status = sys_cpu_to_le16(BT_L2CAP_CS_NO_INFO); + } + + if (bt_l2cap_br_send_cb(conn, BT_L2CAP_CID_BR_SIG, buf, + l2cap_br_conn_rsp_sent_cb, + UINT_TO_POINTER(scid))) + { + net_buf_unref(buf); + } +} + +static int l2cap_br_conn_req_reply(struct bt_l2cap_chan *chan, + uint16_t result) +{ + /* Send response to connection request only when in acceptor role */ + + if (!atomic_test_bit(BR_CHAN(chan)->flags, L2CAP_FLAG_CONN_ACCEPTOR)) + { + return -ESRCH; + } + + l2cap_br_send_conn_rsp(chan->conn, BR_CHAN(chan)->tx.cid, + BR_CHAN(chan)->rx.cid, + BR_CHAN(chan)->ident, result); + BR_CHAN(chan)->ident = 0U; + + return 0; +} + +#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) && defined(CONFIG_BT_L2CAP_LOG_LEVEL_DBG) +void bt_l2cap_br_chan_set_state_debug( + struct bt_l2cap_chan *chan, bt_l2cap_chan_state_t state, const char *func, + int line) +{ + struct bt_l2cap_br_chan *br_chan; + + br_chan = BR_CHAN(chan); + + LOG_DBG("chan %p psm 0x%04x %s -> %s", chan, br_chan->psm, + bt_l2cap_chan_state_str(br_chan->state), + bt_l2cap_chan_state_str(state)); + + /* check transitions validness */ + + switch (state) + { + case BT_L2CAP_DISCONNECTED: + + /* regardless of old state always allows this state */ + + break; + case BT_L2CAP_CONNECTING: + if (br_chan->state != BT_L2CAP_DISCONNECTED) + { + LOG_WRN("%s()%d: invalid transition", func, line); + } + break; + case BT_L2CAP_CONFIG: + if (br_chan->state != BT_L2CAP_CONNECTING) + { + LOG_WRN("%s()%d: invalid transition", func, line); + } + break; + case BT_L2CAP_CONNECTED: + if (br_chan->state != BT_L2CAP_CONFIG && + br_chan->state != BT_L2CAP_CONNECTING) + { + LOG_WRN("%s()%d: invalid transition", func, line); + } + break; + case BT_L2CAP_DISCONNECTING: + if (br_chan->state != BT_L2CAP_CONFIG && + br_chan->state != BT_L2CAP_CONNECTED) + { + LOG_WRN("%s()%d: invalid transition", func, line); + } + break; + default: + LOG_ERR("%s()%d: unknown (%u) state was set", func, line, state); + return; + } + + br_chan->state = state; +} +#else +void bt_l2cap_br_chan_set_state(struct bt_l2cap_chan *chan, + bt_l2cap_chan_state_t state) +{ + BR_CHAN(chan)->state = state; +} +#endif /* CONFIG_BT_L2CAP_LOG_LEVEL_DBG && CONFIG_BT_L2CAP_DYNAMIC_CHANNEL*/ + +void bt_l2cap_br_chan_del(struct bt_l2cap_chan *chan) +{ + const struct bt_l2cap_chan_ops *ops = chan->ops; + struct bt_l2cap_br_chan *br_chan = CONTAINER_OF(chan, + struct bt_l2cap_br_chan, chan); + + LOG_DBG("conn %p chan %p", chan->conn, chan); + + if (!chan->conn) + { + goto destroy; + } + + cancel_data_ready(br_chan); + + /* Remove buffers on the PDU TX queue. */ + + while (chan_has_data(br_chan)) + { + const struct list_node *tx_buf = + list_remove_head(&br_chan->_pdu_tx_queue); + + struct net_buf *buf = CONTAINER_OF(tx_buf, struct net_buf, node); + + net_buf_unref(buf); + } + + if (ops->disconnected) + { + ops->disconnected(chan); + } + + chan->conn = NULL; + +destroy: +#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) + /* Reset internal members of common channel */ + + bt_l2cap_br_chan_set_state(chan, BT_L2CAP_DISCONNECTED); + BR_CHAN(chan)->psm = 0U; + if (L2CAP_BR_CID_IS_DYN(BR_CHAN(chan)->rx.cid)) + { + BR_CHAN(chan)->rx.cid = 0U; + } +#endif + + if (chan->destroy) + { + chan->destroy(chan); + } + + if (ops->released) + { + ops->released(chan); + } +} + +static void l2cap_br_conn_req(struct bt_l2cap_br *l2cap, + uint8_t ident, struct net_buf *buf) +{ + struct bt_conn *conn = l2cap->chan.chan.conn; + struct bt_l2cap_chan *chan; + struct bt_l2cap_server *server; + struct bt_l2cap_conn_req *req = (void *)buf->data; + uint16_t psm; + uint16_t scid; + uint16_t result; + struct bt_l2cap_br_chan *br_chan; + + if (buf->len < sizeof(*req)) + { + LOG_ERR("Too small L2CAP conn req packet size"); + return; + } + + psm = sys_le16_to_cpu(req->psm); + scid = sys_le16_to_cpu(req->scid); + + LOG_DBG("psm 0x%02x scid 0x%04x", psm, scid); + + /* Check if there is a server registered */ + + server = l2cap_br_server_lookup_psm(psm); + if (!server) + { + result = BT_L2CAP_BR_ERR_PSM_NOT_SUPP; + goto no_chan; + } + + /* Report security violation for non SDP channel without encryption when + * remote supports SSP. + */ + + if (server->sec_level != BT_SECURITY_L0 && + BT_FEAT_HOST_SSP(conn->br.features) && + !conn->encrypt) + { + result = BT_L2CAP_BR_ERR_SEC_BLOCK; + goto no_chan; + } + + if (!L2CAP_BR_CID_IS_DYN(scid)) + { + result = BT_L2CAP_BR_ERR_INVALID_SCID; + goto no_chan; + } + + chan = bt_l2cap_br_lookup_tx_cid(conn, scid); + if (chan) + { + /* we have a chan here but this is due to SCID being already in + * use so it is not channel we are suppose to pass to + * l2cap_br_conn_req_reply as wrong DCID would be used + */ + + result = BT_L2CAP_BR_ERR_SCID_IN_USE; + goto no_chan; + } + + /* Request server to accept the new connection and allocate the + * channel. If no free channels available for PSM server reply with proper + * result and quit since chan pointer is uninitialized then. + */ + + if (server->accept(conn, server, &chan) < 0) + { + result = BT_L2CAP_BR_ERR_NO_RESOURCES; + goto no_chan; + } + + br_chan = BR_CHAN(chan); + br_chan->required_sec_level = server->sec_level; + br_chan->psm = psm; + + if (!l2cap_br_chan_add(conn, chan, l2cap_br_chan_destroy)) + { + /* Give the channel the server just accepted back to it, + * following the normal channel lifecycle so that the server knows the + * channel object is no longer in use. + */ + + bt_l2cap_br_chan_del(chan); + result = BT_L2CAP_BR_ERR_NO_RESOURCES; + goto no_chan; + } + + BR_CHAN(chan)->tx.cid = scid; + br_chan->ident = ident; + bt_l2cap_br_chan_set_state(chan, BT_L2CAP_CONNECTING); + atomic_set_bit(BR_CHAN(chan)->flags, L2CAP_FLAG_CONN_ACCEPTOR); + +#if !CONFIG_BT_L2CAP_RET_FC + /* Disable fragmentation of l2cap rx pdu */ + + BR_CHAN(chan)->rx.mtu = MIN(BR_CHAN(chan)->rx.mtu, BT_L2CAP_RX_MTU); +#endif /* CONFIG_BT_L2CAP_RET_FC */ + + switch (l2cap_br_conn_security(chan, psm)) + { + case L2CAP_CONN_SECURITY_PENDING: + result = BT_L2CAP_BR_PENDING; + + /* TODO: auth timeout */ + + break; + case L2CAP_CONN_SECURITY_PASSED: + result = BT_L2CAP_BR_SUCCESS; + break; + case L2CAP_CONN_SECURITY_REJECT: + default: + result = BT_L2CAP_BR_ERR_SEC_BLOCK; + + /* Set disconnect ACL flag. */ + + atomic_set_bit(BR_CHAN(chan)->flags, L2CAP_FLAG_DISCONNECT_ACL); + break; + } + + /* Reply on connection request as acceptor */ + + l2cap_br_conn_req_reply(chan, result); + + if (result != BT_L2CAP_BR_SUCCESS) + { + /* Disconnect link when security rules were violated */ + + if (result == BT_L2CAP_BR_ERR_SEC_BLOCK) + { + /* Disconnect the ACL after the packet of response has been sent. + * The `L2CAP_FLAG_DISCONNECT_ACL` is used to flag whether ACL + * disconnect request needs to be sent when the L2CAP conn rsp sent + * out callback is triggered. + */ + } + else if (result == BT_L2CAP_BR_PENDING) + { + /* Recover the ident when conn is pending */ + + br_chan->ident = ident; + } + + return; + } + + bt_l2cap_br_chan_set_state(chan, BT_L2CAP_CONFIG); + l2cap_br_conf(chan, true); + return; + +no_chan: + l2cap_br_send_conn_rsp(conn, scid, 0, ident, result); +} + +#define L2CAP_QOS_TOKEN_RATE_DEFAULT 0x00000000 +#define L2CAP_QOS_TOKEN_BUCKET_SIZE_DEFAULT 0x00000000 +#define L2CAP_QOS_PEAK_BANDWIDTH_DEFAULT 0x00000000 +#define L2CAP_QOS_LATENCY_DEFAULT 0xffffffff +#define L2CAP_QOS_DELAY_DEFAULT 0xffffffff + +static uint16_t l2cap_br_conf_rsp_opt_mtu( + struct bt_l2cap_chan *chan, struct net_buf *buf, size_t len) +{ + uint16_t mtu; + uint16_t result = BT_L2CAP_CONF_SUCCESS; + struct bt_l2cap_conf_opt_mtu *opt_mtu; + + /* Core 4.2 [Vol 3, Part A, 5.1] MTU payload length */ + + if (len != sizeof(*opt_mtu)) + { + LOG_ERR("Proposed MTU length %zu invalid", len); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + opt_mtu = (struct bt_l2cap_conf_opt_mtu *)buf->data; + + mtu = sys_le16_to_cpu(opt_mtu->mtu); + if (mtu < L2CAP_BR_MIN_MTU) + { + result = BT_L2CAP_CONF_REJECT; + opt_mtu->mtu = sys_cpu_to_le16(BR_CHAN(chan)->rx.mtu); + LOG_WRN("Proposed MTU %u invalid", mtu); + goto done; + } + + if (mtu < BR_CHAN(chan)->rx.mtu) + { + BR_CHAN(chan)->rx.mtu = mtu; + } + else if (mtu > BR_CHAN(chan)->rx.mtu) + { + LOG_WRN("Proposed MTU is more than given value (%u > %u)", mtu, + BR_CHAN(chan)->rx.mtu); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + else + { + LOG_WRN("Proposed MTU is the same as given value (%u == %u)", mtu, + BR_CHAN(chan)->rx.mtu); + } + + LOG_DBG("rx MTU %u", BR_CHAN(chan)->rx.mtu); +done: + return result; +} + +static uint16_t l2cap_br_conf_rsp_opt_flush_timeout( + struct bt_l2cap_chan *chan, struct net_buf *buf, size_t len) +{ + uint16_t result = BT_L2CAP_CONF_SUCCESS; + struct bt_l2cap_conf_opt_flush_timeout *opt_to; + + if (len != sizeof(*opt_to)) + { + LOG_ERR("qos frame length %zu invalid", len); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + opt_to = (struct bt_l2cap_conf_opt_flush_timeout *)buf->data; + + LOG_DBG("Flash timeout %u", opt_to->timeout); + + opt_to->timeout = sys_le16_to_cpu(opt_to->timeout); + + if (opt_to->timeout == 0) + { + /* 0 is illegal value */ + + LOG_ERR("Flush timeout cannot be 0"); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + /* For tx, only support the default value (0xffff - infinite amount of + * retransmissions) + */ + + if (opt_to->timeout != 0xffff) + { + result = BT_L2CAP_CONF_UNACCEPT; + } + +done: + return result; +} + +static uint16_t l2cap_br_conf_rsp_opt_qos( + struct bt_l2cap_chan *chan, struct net_buf *buf, size_t len) +{ + uint16_t result = BT_L2CAP_CONF_SUCCESS; + struct bt_l2cap_conf_opt_qos *opt_qos; + struct bt_l2cap_chan *chan_sig; + struct bt_l2cap_br *br_chan_sig; + + chan_sig = bt_l2cap_br_lookup_rx_cid(chan->conn, BT_L2CAP_CID_BR_SIG); + if (!chan_sig) + { + LOG_WRN("Ignoring data for unknown channel ID 0x%04x", + BT_L2CAP_CID_BR_SIG); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + br_chan_sig = CONTAINER_OF(chan_sig, struct bt_l2cap_br, chan.chan); + if (((br_chan_sig->info_feat_mask & L2CAP_FEAT_QOS_MASK) == 0) || + ((L2CAP_EXTENDED_FEAT_MASK & L2CAP_FEAT_QOS_MASK) == 0)) + { + LOG_WRN("Unsupported extended flow spec"); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + if (len != sizeof(*opt_qos)) + { + LOG_ERR("qos frame length %zu invalid", len); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + opt_qos = (struct bt_l2cap_conf_opt_qos *)buf->data; + + LOG_DBG("QOS Type %u", opt_qos->service_type); + + if (opt_qos->service_type == BT_L2CAP_QOS_TYPE_GUARANTEED) + { + result = BT_L2CAP_CONF_UNACCEPT; + + /* Set to default value */ + + opt_qos->flags = 0x00; + + /* do not care */ + + opt_qos->token_rate = sys_cpu_to_le32(L2CAP_QOS_TOKEN_RATE_DEFAULT); + + /* no token bucket is needed */ + + opt_qos->token_bucket_size = + sys_cpu_to_le32(L2CAP_QOS_TOKEN_BUCKET_SIZE_DEFAULT); + + /* do not care */ + + opt_qos->peak_bandwidth = + sys_cpu_to_le32(L2CAP_QOS_PEAK_BANDWIDTH_DEFAULT); + + /* do not care */ + + opt_qos->latency = sys_cpu_to_le32(L2CAP_QOS_LATENCY_DEFAULT); + + /* do not care */ + + opt_qos->delay_variation = sys_cpu_to_le32(L2CAP_QOS_DELAY_DEFAULT); + } + +done: + return result; +} + +#if defined(CONFIG_BT_L2CAP_RET_FC) +static uint16_t l2cap_br_conf_rsp_opt_ret_fc( + struct bt_l2cap_chan *chan, struct net_buf *buf, size_t len) +{ + uint16_t result = BT_L2CAP_CONF_UNACCEPT; + struct bt_l2cap_conf_opt_ret_fc *opt_ret_fc; + struct bt_l2cap_br_chan *br_chan; + struct bt_l2cap_chan *chan_sig; + bool accept = true; + + if (len != sizeof(*opt_ret_fc)) + { + LOG_ERR("ret_fc frame length %zu invalid", len); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + chan_sig = bt_l2cap_br_lookup_rx_cid(chan->conn, BT_L2CAP_CID_BR_SIG); + if (!chan_sig) + { + LOG_WRN("Ignoring data for unknown channel ID 0x%04x", + BT_L2CAP_CID_BR_SIG); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + opt_ret_fc = (struct bt_l2cap_conf_opt_ret_fc *)buf->data; + + LOG_DBG("ret_fc mode %u", opt_ret_fc->mode); + + br_chan = BR_CHAN(chan); + + if (opt_ret_fc->mode != br_chan->rx.mode) + { + if (br_chan->rx.optional) + { + br_chan->rx.mode = opt_ret_fc->mode; + } + else + { + opt_ret_fc->mode = br_chan->rx.mode; + accept = false; + } + } + + if (opt_ret_fc->mode != BT_L2CAP_BR_LINK_MODE_BASIC) + { + if (sys_le16_to_cpu(opt_ret_fc->mps) < br_chan->rx.mps) + { + br_chan->rx.mps = sys_le16_to_cpu(opt_ret_fc->mps); + } + else + { + opt_ret_fc->mps = sys_cpu_to_le16(br_chan->rx.mps); + } + + if ((opt_ret_fc->mode == BT_L2CAP_BR_LINK_MODE_RET) || + (opt_ret_fc->mode == BT_L2CAP_BR_LINK_MODE_FC)) + { + if ((opt_ret_fc->tx_windows_size < 1) || + (opt_ret_fc->tx_windows_size > 32)) + { + opt_ret_fc->tx_windows_size = CONFIG_BT_L2CAP_MAX_WINDOW_SIZE; + accept = false; + } + + if (opt_ret_fc->max_transmit < 1) + { + opt_ret_fc->max_transmit = 1; + accept = false; + } + } + + if (opt_ret_fc->mode == BT_L2CAP_BR_LINK_MODE_ERET) + { + if ((opt_ret_fc->tx_windows_size < 1) || + (opt_ret_fc->tx_windows_size > 63)) + { + opt_ret_fc->tx_windows_size = CONFIG_BT_L2CAP_MAX_WINDOW_SIZE; + accept = false; + } + } + + opt_ret_fc->retransmission_timeout = + sys_cpu_to_le16(CONFIG_BT_L2CAP_BR_RET_TIMEOUT); + opt_ret_fc->monitor_timeout = + sys_cpu_to_le16(CONFIG_BT_L2CAP_BR_MONITOR_TIMEOUT); + + if (opt_ret_fc->tx_windows_size > CONFIG_BT_L2CAP_MAX_WINDOW_SIZE) + { + opt_ret_fc->tx_windows_size = + sys_cpu_to_le16(CONFIG_BT_L2CAP_MAX_WINDOW_SIZE); + } + + if (!accept) + { + goto done; + } + + br_chan->rx.ret_timeout = CONFIG_BT_L2CAP_BR_RET_TIMEOUT; + br_chan->rx.monitor_timeout = CONFIG_BT_L2CAP_BR_MONITOR_TIMEOUT; + br_chan->rx.max_transmit = opt_ret_fc->max_transmit; + br_chan->rx.max_window = sys_le16_to_cpu(opt_ret_fc->tx_windows_size); + br_chan->rx.mps = sys_le16_to_cpu(opt_ret_fc->mps); + + if ((opt_ret_fc->mode == BT_L2CAP_BR_LINK_MODE_RET) || + (opt_ret_fc->mode == BT_L2CAP_BR_LINK_MODE_FC)) + { + /* Bluetooth Core specification Version 6.0 | Vol 3, Part A, + * section 5.4. + * + * In Retransmission mode and Flow Control mode this parameter + * should be negotiated to reflect the buffer sizes allocated for + * the connection on both sides. + */ + + br_chan->tx.max_window = br_chan->rx.max_window; + } + } + + if (!accept) + { + goto done; + } + + result = BT_L2CAP_CONF_SUCCESS; + +done: + return result; +} + +static uint16_t l2cap_br_conf_rsp_opt_fcs( + struct bt_l2cap_chan *chan, struct net_buf *buf, size_t len) +{ + uint16_t result = BT_L2CAP_CONF_SUCCESS; + struct bt_l2cap_conf_opt_fcs *opt_fcs; + struct bt_l2cap_br_chan *br_chan; + + if (len != sizeof(*opt_fcs)) + { + LOG_ERR("fcs frame length %zu invalid", len); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + opt_fcs = (struct bt_l2cap_conf_opt_fcs *)buf->data; + + LOG_DBG("FCS type %u", opt_fcs->type); + + if ((opt_fcs->type != BT_L2CAP_FCS_TYPE_NO) && + (opt_fcs->type != BT_L2CAP_FCS_TYPE_16BIT)) + { + LOG_ERR("fcs type %u invalid", opt_fcs->type); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + br_chan = BR_CHAN(chan); + + switch (br_chan->rx.mode) + { + case BT_L2CAP_BR_LINK_MODE_BASIC: + result = BT_L2CAP_CONF_REJECT; + goto done; + case BT_L2CAP_BR_LINK_MODE_RET: + __fallthrough; + case BT_L2CAP_BR_LINK_MODE_FC: + LOG_ERR("fcs opt is mandatory in mode %u", br_chan->rx.mode); + if (opt_fcs->type != BT_L2CAP_BR_FCS_16BIT) + { + result = BT_L2CAP_CONF_UNACCEPT; + opt_fcs->type = BT_L2CAP_BR_FCS_16BIT; + br_chan->rx.fcs = opt_fcs->type; + goto done; + } + break; + case BT_L2CAP_BR_LINK_MODE_ERET: + __fallthrough; + case BT_L2CAP_BR_LINK_MODE_STREAM: + br_chan->rx.fcs = opt_fcs->type; + break; + default: + + /* Should not have been here */ + + LOG_ERR("Unknown mode %u", br_chan->rx.mode); + break; + } + +done: + return result; +} + +#define L2CAP_EXT_FLOW_SPEC_SDU_DEFAULT 0xffff +#define L2CAP_EXT_FLOW_SPEC_DEFAULT 0xffffffff + +static uint16_t l2cap_br_conf_rsp_opt_ext_flow_spec( + struct bt_l2cap_chan *chan, struct net_buf *buf, size_t len) +{ + uint16_t result = BT_L2CAP_CONF_SUCCESS; + struct bt_l2cap_conf_opt_ext_flow_spec *ext_flow_spec; + + if (len != sizeof(*ext_flow_spec)) + { + LOG_ERR("fcs frame length %zu invalid", len); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + ext_flow_spec = (struct bt_l2cap_conf_opt_ext_flow_spec *)buf->data; + + LOG_DBG("Ext Flow Spec %u %u %u %u %u %u", ext_flow_spec->identifier, + ext_flow_spec->service_type, + ext_flow_spec->sdu, ext_flow_spec->sdu_inter_arrival_time, + ext_flow_spec->access_latency, + ext_flow_spec->flush_timeout); + + /* Set to default value */ + + result = BT_L2CAP_CONF_UNACCEPT; + ext_flow_spec->identifier = 0x01; + ext_flow_spec->service_type = 0x01; + ext_flow_spec->sdu = sys_cpu_to_le16(L2CAP_EXT_FLOW_SPEC_SDU_DEFAULT); + ext_flow_spec->sdu_inter_arrival_time = + sys_cpu_to_le32(L2CAP_EXT_FLOW_SPEC_DEFAULT); + ext_flow_spec->access_latency = + sys_cpu_to_le32(L2CAP_EXT_FLOW_SPEC_DEFAULT); + ext_flow_spec->flush_timeout = + sys_cpu_to_le32(L2CAP_EXT_FLOW_SPEC_DEFAULT); + +done: + return result; +} + +static uint16_t l2cap_br_conf_rsp_opt_ext_win_size( + struct bt_l2cap_chan *chan, struct net_buf *buf, size_t len) +{ + uint16_t result = BT_L2CAP_CONF_SUCCESS; + struct bt_l2cap_conf_opt_ext_win_size *opt_ext_win_size; + struct bt_l2cap_br_chan *br_chan; + uint16_t win_size; + + if (len != sizeof(*opt_ext_win_size)) + { + LOG_ERR("fcs frame length %zu invalid", len); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + opt_ext_win_size = (struct bt_l2cap_conf_opt_ext_win_size *)buf->data; + win_size = sys_le16_to_cpu(opt_ext_win_size->max_windows_size); + + LOG_DBG("EXT Win Size value %u", win_size); + + br_chan = BR_CHAN(chan); + + switch (br_chan->rx.mode) + { + case BT_L2CAP_BR_LINK_MODE_BASIC: + __fallthrough; + case BT_L2CAP_BR_LINK_MODE_RET: + __fallthrough; + case BT_L2CAP_BR_LINK_MODE_FC: + + /* ignore */ + + LOG_WRN("Ignored by mode %u", br_chan->rx.mode); + break; + case BT_L2CAP_BR_LINK_MODE_ERET: + if ((win_size < 1) || (win_size > CONFIG_BT_L2CAP_MAX_WINDOW_SIZE)) + { + /* Set to default value */ + + opt_ext_win_size->max_windows_size = + sys_cpu_to_le16(CONFIG_BT_L2CAP_MAX_WINDOW_SIZE); + result = BT_L2CAP_CONF_UNACCEPT; + goto done; + } + + br_chan->tx.extended_control = true; + br_chan->tx.max_window = win_size; + break; + case BT_L2CAP_BR_LINK_MODE_STREAM: + if (win_size) + { + result = BT_L2CAP_CONF_UNACCEPT; + opt_ext_win_size->max_windows_size = 0; + goto done; + } + + br_chan->tx.extended_control = true; + break; + default: + + /* Should not have been here */ + + LOG_ERR("Unknown mode %u", br_chan->rx.mode); + break; + } + +done: + return result; +} +#endif /* CONFIG_BT_L2CAP_RET_FC */ + +static int l2cap_br_conf_rsp_opt_check( + struct bt_l2cap_chan *chan, uint16_t opt_len, struct net_buf *buf) +{ + struct bt_l2cap_conf_opt *opt = NULL; + uint16_t result = BT_L2CAP_CONF_SUCCESS; + uint8_t hint; + + if (opt_len > buf->len) + { + return -EINVAL; + } + + while (opt_len >= sizeof(*opt)) + { + opt = net_buf_pull_mem(buf, sizeof(*opt)); + + opt_len -= sizeof(*opt); + + if (opt_len < opt->len) + { + LOG_ERR("Received too short option data"); + return -EINVAL; + } + + opt_len -= opt->len; + + hint = opt->type & BT_L2CAP_CONF_HINT; + if (hint) + { + return -EINVAL; + } + + switch (opt->type & BT_L2CAP_CONF_MASK) + { + case BT_L2CAP_CONF_OPT_MTU: + + /* getting MTU modifies buf internals */ + + result = l2cap_br_conf_rsp_opt_mtu(chan, buf, opt->len); + if (result != BT_L2CAP_CONF_SUCCESS) + { + goto invalid_opt; + } + break; + case BT_L2CAP_CONF_OPT_FLUSH_TIMEOUT: + result = l2cap_br_conf_rsp_opt_flush_timeout(chan, buf, + opt->len); + if (result != BT_L2CAP_CONF_SUCCESS) + { + goto invalid_opt; + } + break; + case BT_L2CAP_CONF_OPT_QOS: + result = l2cap_br_conf_rsp_opt_qos(chan, buf, opt->len); + if (result != BT_L2CAP_CONF_SUCCESS) + { + goto invalid_opt; + } + break; +#if defined(CONFIG_BT_L2CAP_RET_FC) + case BT_L2CAP_CONF_OPT_RET_FC: + result = l2cap_br_conf_rsp_opt_ret_fc(chan, buf, opt->len); + if (result != BT_L2CAP_CONF_SUCCESS) + { + goto invalid_opt; + } + break; + case BT_L2CAP_CONF_OPT_FCS: + result = l2cap_br_conf_rsp_opt_fcs(chan, buf, opt->len); + if (result != BT_L2CAP_CONF_SUCCESS) + { + goto invalid_opt; + } + break; + case BT_L2CAP_CONF_OPT_EXT_FLOW_SPEC: + result = l2cap_br_conf_rsp_opt_ext_flow_spec(chan, buf, + opt->len); + if (result != BT_L2CAP_CONF_SUCCESS) + { + goto invalid_opt; + } + break; + case BT_L2CAP_CONF_OPT_EXT_WIN_SIZE: + result = l2cap_br_conf_rsp_opt_ext_win_size(chan, buf, opt->len); + if (result != BT_L2CAP_CONF_SUCCESS) + { + goto invalid_opt; + } + break; +#endif /* CONFIG_BT_L2CAP_RET_FC */ + default: + result = BT_L2CAP_CONF_UNKNOWN_OPT; + goto invalid_opt; + } + + net_buf_pull(buf, opt->len); + } + +#if defined(CONFIG_BT_L2CAP_RET_FC) + if (BR_CHAN(chan)->rx.fcs == BT_L2CAP_BR_FCS_16BIT) + { + /* If local enable FCS, peer also needs to enable it. */ + + BR_CHAN(chan)->tx.fcs = BT_L2CAP_BR_FCS_16BIT; + } + + if (BR_CHAN(chan)->rx.extended_control) + { + /* If peer enables extended control field, + * local also needs to enable it. + */ + + BR_CHAN(chan)->tx.extended_control = true; + } +#endif /* CONFIG_BT_L2CAP_RET_FC */ + + return 0; + +invalid_opt: + return -EINVAL; +} + +static uint16_t l2cap_br_conf_rsp_unaccept_opt_mtu( + struct bt_l2cap_chan *chan, struct net_buf *buf, size_t len) +{ + uint16_t mtu; + uint16_t result = BT_L2CAP_CONF_SUCCESS; + struct bt_l2cap_conf_opt_mtu *opt_mtu; + + /* Core 4.2 [Vol 3, Part A, 5.1] MTU payload length */ + + if (len != sizeof(*opt_mtu)) + { + LOG_ERR("Proposed MTU length %zu invalid", len); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + opt_mtu = (struct bt_l2cap_conf_opt_mtu *)buf->data; + + mtu = sys_le16_to_cpu(opt_mtu->mtu); + if (mtu < L2CAP_BR_MIN_MTU) + { + LOG_WRN("Proposed MTU %u invalid", mtu); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + if (mtu < BR_CHAN(chan)->rx.mtu) + { + BR_CHAN(chan)->rx.mtu = mtu; + } + else if (mtu > BR_CHAN(chan)->rx.mtu) + { + LOG_WRN("Proposed MTU is more than given value (%u > %u)", mtu, + BR_CHAN(chan)->rx.mtu); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + else + { + LOG_WRN("Proposed MTU is the same as given value (%u == %u)", mtu, + BR_CHAN(chan)->rx.mtu); + } + +done: + LOG_DBG("rx MTU %u", BR_CHAN(chan)->rx.mtu); + return result; +} + +static uint16_t l2cap_br_conf_rsp_unaccept_opt_flush_timeout( + struct bt_l2cap_chan *chan, struct net_buf *buf, size_t len) +{ + uint16_t result = BT_L2CAP_CONF_SUCCESS; + struct bt_l2cap_conf_opt_flush_timeout *opt_to; + + if (len != sizeof(*opt_to)) + { + LOG_ERR("qos frame length %zu invalid", len); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + opt_to = (struct bt_l2cap_conf_opt_flush_timeout *)buf->data; + + LOG_DBG("Flash timeout %u", opt_to->timeout); + +done: + return result; +} + +static uint16_t l2cap_br_conf_rsp_unaccept_opt_qos( + struct bt_l2cap_chan *chan, struct net_buf *buf, size_t len) +{ + uint16_t result = BT_L2CAP_CONF_SUCCESS; + struct bt_l2cap_conf_opt_qos *opt_qos; + struct bt_l2cap_chan *chan_sig; + struct bt_l2cap_br *br_chan_sig; + + chan_sig = bt_l2cap_br_lookup_rx_cid(chan->conn, BT_L2CAP_CID_BR_SIG); + if (!chan_sig) + { + LOG_WRN("Ignoring data for unknown channel ID 0x%04x", + BT_L2CAP_CID_BR_SIG); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + br_chan_sig = CONTAINER_OF(chan_sig, struct bt_l2cap_br, chan.chan); + if (((br_chan_sig->info_feat_mask & L2CAP_FEAT_QOS_MASK) == 0) || + ((L2CAP_EXTENDED_FEAT_MASK & L2CAP_FEAT_QOS_MASK) == 0)) + { + LOG_WRN("Unsupported extended flow spec"); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + if (len != sizeof(*opt_qos)) + { + LOG_ERR("qos frame length %zu invalid", len); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + opt_qos = (struct bt_l2cap_conf_opt_qos *)buf->data; + + LOG_DBG("QOS Type %u", opt_qos->service_type); + +done: + return result; +} + +#if defined(CONFIG_BT_L2CAP_RET_FC) +static uint16_t l2cap_br_conf_rsp_unaccept_opt_ret_fc( + struct bt_l2cap_chan *chan, struct net_buf *buf, size_t len) +{ + uint16_t result = BT_L2CAP_CONF_UNACCEPT; + struct bt_l2cap_conf_opt_ret_fc *opt_ret_fc; + struct bt_l2cap_br_chan *br_chan; + + if (len != sizeof(*opt_ret_fc)) + { + LOG_ERR("ret_fc frame length %zu invalid", len); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + opt_ret_fc = (struct bt_l2cap_conf_opt_ret_fc *)buf->data; + + LOG_DBG("ret_fc mode %u", opt_ret_fc->mode); + + br_chan = BR_CHAN(chan); + + if (opt_ret_fc->mode != br_chan->rx.mode) + { + if (br_chan->rx.optional) + { + br_chan->rx.mode = opt_ret_fc->mode; + } + else + { + goto done; + } + } + + if (opt_ret_fc->mode != BT_L2CAP_BR_LINK_MODE_BASIC) + { + if (sys_le16_to_cpu(opt_ret_fc->mps) > br_chan->rx.mtu) + { + opt_ret_fc->mps = sys_cpu_to_le16(br_chan->rx.mtu); + } + + if ((opt_ret_fc->mode == BT_L2CAP_BR_LINK_MODE_RET) || + (opt_ret_fc->mode == BT_L2CAP_BR_LINK_MODE_FC)) + { + if ((opt_ret_fc->tx_windows_size < 1) || + (opt_ret_fc->tx_windows_size > 32)) + { + opt_ret_fc->tx_windows_size = CONFIG_BT_L2CAP_MAX_WINDOW_SIZE; + } + + if (opt_ret_fc->max_transmit < 1) + { + opt_ret_fc->max_transmit = 1; + } + } + + if (opt_ret_fc->mode == BT_L2CAP_BR_LINK_MODE_ERET) + { + if ((opt_ret_fc->tx_windows_size < 1) || + (opt_ret_fc->tx_windows_size > 63)) + { + opt_ret_fc->tx_windows_size = CONFIG_BT_L2CAP_MAX_WINDOW_SIZE; + } + } + + opt_ret_fc->retransmission_timeout = + sys_cpu_to_le16(CONFIG_BT_L2CAP_BR_RET_TIMEOUT); + opt_ret_fc->monitor_timeout = + sys_cpu_to_le16(CONFIG_BT_L2CAP_BR_MONITOR_TIMEOUT); + + if (opt_ret_fc->tx_windows_size > CONFIG_BT_L2CAP_MAX_WINDOW_SIZE) + { + opt_ret_fc->tx_windows_size = CONFIG_BT_L2CAP_MAX_WINDOW_SIZE; + } + + br_chan->rx.ret_timeout = CONFIG_BT_L2CAP_BR_RET_TIMEOUT; + br_chan->rx.monitor_timeout = CONFIG_BT_L2CAP_BR_MONITOR_TIMEOUT; + br_chan->rx.max_transmit = opt_ret_fc->max_transmit; + br_chan->rx.max_window = opt_ret_fc->tx_windows_size; + br_chan->rx.mps = sys_le16_to_cpu(opt_ret_fc->mps); + + if ((opt_ret_fc->mode == BT_L2CAP_BR_LINK_MODE_RET) || + (opt_ret_fc->mode == BT_L2CAP_BR_LINK_MODE_FC)) + { + /* Bluetooth Core specification Version 6.0 | Vol 3, Part A, + * section 5.4. + * + * In Retransmission mode and Flow Control mode this parameter + * should be negotiated to reflect the buffer sizes allocated for + * the connection on both sides. + */ + + br_chan->tx.max_window = br_chan->rx.max_window; + } + } + + if (br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_BASIC) + { + br_chan->tx.mode = BT_L2CAP_BR_LINK_MODE_BASIC; + } + + result = BT_L2CAP_CONF_SUCCESS; + +done: + return result; +} + +static uint16_t l2cap_br_conf_rsp_unaccept_opt_fcs( + struct bt_l2cap_chan *chan, struct net_buf *buf, size_t len) +{ + uint16_t result = BT_L2CAP_CONF_SUCCESS; + struct bt_l2cap_conf_opt_fcs *opt_fcs; + struct bt_l2cap_br_chan *br_chan; + + if (len != sizeof(*opt_fcs)) + { + LOG_ERR("fcs frame length %zu invalid", len); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + opt_fcs = (struct bt_l2cap_conf_opt_fcs *)buf->data; + + LOG_DBG("FCS type %u", opt_fcs->type); + + if ((opt_fcs->type != BT_L2CAP_FCS_TYPE_NO) && + (opt_fcs->type != BT_L2CAP_FCS_TYPE_16BIT)) + { + LOG_ERR("fcs type %u invalid", opt_fcs->type); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + br_chan = BR_CHAN(chan); + + switch (br_chan->rx.mode) + { + case BT_L2CAP_BR_LINK_MODE_BASIC: + goto done; + case BT_L2CAP_BR_LINK_MODE_RET: + __fallthrough; + case BT_L2CAP_BR_LINK_MODE_FC: + LOG_ERR("fcs opt is mandatory in mode %u", br_chan->rx.mode); + br_chan->rx.fcs = BT_L2CAP_BR_FCS_16BIT; + break; + case BT_L2CAP_BR_LINK_MODE_ERET: + __fallthrough; + case BT_L2CAP_BR_LINK_MODE_STREAM: + br_chan->rx.fcs = opt_fcs->type; + break; + default: + + /* Should not have been here */ + + LOG_ERR("Unknown mode %u", br_chan->rx.mode); + break; + } + +done: + return result; +} + +static uint16_t l2cap_br_conf_rsp_unaccept_opt_ext_flow_spec( + struct bt_l2cap_chan *chan, struct net_buf *buf, size_t len) +{ + uint16_t result = BT_L2CAP_CONF_SUCCESS; + struct bt_l2cap_conf_opt_ext_flow_spec *ext_flow_spec; + + if (len != sizeof(*ext_flow_spec)) + { + LOG_ERR("fcs frame length %zu invalid", len); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + ext_flow_spec = (struct bt_l2cap_conf_opt_ext_flow_spec *)buf->data; + + LOG_DBG("Ext Flow Spec %u %u %u %u %u %u", ext_flow_spec->identifier, + ext_flow_spec->service_type, + ext_flow_spec->sdu, ext_flow_spec->sdu_inter_arrival_time, + ext_flow_spec->access_latency, + ext_flow_spec->flush_timeout); +done: + return result; +} + +static uint16_t l2cap_br_conf_rsp_unaccept_opt_ext_win_size( + struct bt_l2cap_chan *chan, struct net_buf *buf, size_t len) +{ + uint16_t result = BT_L2CAP_CONF_SUCCESS; + struct bt_l2cap_conf_opt_ext_win_size *opt_ext_win_size; + struct bt_l2cap_br_chan *br_chan; + uint16_t win_size; + + if (len != sizeof(*opt_ext_win_size)) + { + LOG_ERR("fcs frame length %zu invalid", len); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + opt_ext_win_size = (struct bt_l2cap_conf_opt_ext_win_size *)buf->data; + win_size = sys_le16_to_cpu(opt_ext_win_size->max_windows_size); + + LOG_DBG("EXT Win Size value %u", win_size); + + br_chan = BR_CHAN(chan); + + switch (br_chan->rx.mode) + { + case BT_L2CAP_BR_LINK_MODE_BASIC: + __fallthrough; + case BT_L2CAP_BR_LINK_MODE_RET: + __fallthrough; + case BT_L2CAP_BR_LINK_MODE_FC: + + /* ignore */ + + LOG_WRN("Ignored by mode %u", br_chan->rx.mode); + break; + case BT_L2CAP_BR_LINK_MODE_ERET: + __fallthrough; + case BT_L2CAP_BR_LINK_MODE_STREAM: + if ((win_size < 1) || (win_size > CONFIG_BT_L2CAP_MAX_WINDOW_SIZE)) + { + /* Set to default value */ + + br_chan->rx.max_window = CONFIG_BT_L2CAP_MAX_WINDOW_SIZE; + } + else + { + br_chan->rx.max_window = win_size; + } + break; + default: + + /* Should not have been here */ + + LOG_ERR("Unknown mode %u", br_chan->rx.mode); + break; + } + +done: + return result; +} +#endif /* CONFIG_BT_L2CAP_RET_FC */ + +static int l2cap_br_conf_rsp_unaccept_opt( + struct bt_l2cap_chan *chan, uint16_t opt_len, struct net_buf *buf) +{ + struct bt_l2cap_conf_opt *opt = NULL; + uint16_t result = BT_L2CAP_CONF_SUCCESS; + uint8_t hint; + + if (opt_len > buf->len) + { + return -EINVAL; + } + + while (opt_len >= sizeof(*opt)) + { + opt = net_buf_pull_mem(buf, sizeof(*opt)); + + opt_len -= sizeof(*opt); + + if (opt_len < opt->len) + { + LOG_ERR("Received too short option data"); + return -EINVAL; + } + + opt_len -= opt->len; + + hint = opt->type & BT_L2CAP_CONF_HINT; + if (hint) + { + return -EINVAL; + } + + switch (opt->type & BT_L2CAP_CONF_MASK) + { + case BT_L2CAP_CONF_OPT_MTU: + + /* getting MTU modifies buf internals */ + + result = l2cap_br_conf_rsp_unaccept_opt_mtu(chan, buf, opt->len); + if (result != BT_L2CAP_CONF_SUCCESS) + { + goto invalid_opt; + } + break; + case BT_L2CAP_CONF_OPT_FLUSH_TIMEOUT: + result = l2cap_br_conf_rsp_unaccept_opt_flush_timeout(chan, buf, + opt->len); + if (result != BT_L2CAP_CONF_SUCCESS) + { + goto invalid_opt; + } + break; + case BT_L2CAP_CONF_OPT_QOS: + result = l2cap_br_conf_rsp_unaccept_opt_qos(chan, buf, opt->len); + if (result != BT_L2CAP_CONF_SUCCESS) + { + goto invalid_opt; + } + break; +#if defined(CONFIG_BT_L2CAP_RET_FC) + case BT_L2CAP_CONF_OPT_RET_FC: + result = l2cap_br_conf_rsp_unaccept_opt_ret_fc(chan, + buf, + opt->len); + if (result != BT_L2CAP_CONF_SUCCESS) + { + goto invalid_opt; + } + break; + case BT_L2CAP_CONF_OPT_FCS: + result = l2cap_br_conf_rsp_unaccept_opt_fcs(chan, buf, opt->len); + if (result != BT_L2CAP_CONF_SUCCESS) + { + goto invalid_opt; + } + break; + case BT_L2CAP_CONF_OPT_EXT_FLOW_SPEC: + result = l2cap_br_conf_rsp_unaccept_opt_ext_flow_spec(chan, buf, + opt->len); + if (result != BT_L2CAP_CONF_SUCCESS) + { + goto invalid_opt; + } + break; + case BT_L2CAP_CONF_OPT_EXT_WIN_SIZE: + result = l2cap_br_conf_rsp_unaccept_opt_ext_win_size(chan, buf, + opt->len); + if (result != BT_L2CAP_CONF_SUCCESS) + { + goto invalid_opt; + } + break; +#endif /* CONFIG_BT_L2CAP_RET_FC */ + default: + result = BT_L2CAP_CONF_UNKNOWN_OPT; + goto invalid_opt; + } + + net_buf_pull(buf, opt->len); + } + +#if defined(CONFIG_BT_L2CAP_RET_FC) + if (BR_CHAN(chan)->rx.fcs == BT_L2CAP_BR_FCS_16BIT) + { + /* If local enable FCS, peer also needs to enable it. */ + + BR_CHAN(chan)->tx.fcs = BT_L2CAP_BR_FCS_16BIT; + } +#endif /* CONFIG_BT_L2CAP_RET_FC */ + + return 0; + +invalid_opt: + return -EINVAL; +} + +static void l2cap_br_conf_rsp( + struct bt_l2cap_br *l2cap, uint8_t ident, uint16_t len, + struct net_buf *buf) +{ + struct bt_conn *conn = l2cap->chan.chan.conn; + struct bt_l2cap_chan *chan; + struct bt_l2cap_conf_rsp *rsp = (void *)buf->data; + uint16_t flags; + uint16_t scid; + uint16_t result; + uint16_t opt_len; + struct bt_l2cap_br_chan *br_chan; + int err; + + if (len < sizeof(*rsp)) + { + LOG_ERR("Too small L2CAP conf rsp packet size"); + return; + } + + net_buf_pull(buf, sizeof(*rsp)); + + flags = sys_le16_to_cpu(rsp->flags); + scid = sys_le16_to_cpu(rsp->scid); + result = sys_le16_to_cpu(rsp->result); + opt_len = len - sizeof(*rsp); + + LOG_DBG("scid 0x%04x flags 0x%02x result 0x%02x len %u", scid, flags, + result, + opt_len); + + chan = bt_l2cap_br_lookup_rx_cid(conn, scid); + if (!chan) + { + LOG_ERR("channel mismatch!"); + return; + } + + br_chan = BR_CHAN(chan); + if (br_chan->ident != ident) + { + LOG_WRN("ident mismatch (%u != %u)!", br_chan->ident, ident); + return; + } + + br_chan->ident = 0; + + /* Release RTX work since got the response */ + + k_work_cancel_delayable(&br_chan->rtx_work); + + /* TODO: handle other results than success and parse response data if + * available + */ + + switch (result) + { + case BT_L2CAP_CONF_SUCCESS: + atomic_set_bit(br_chan->flags, L2CAP_FLAG_CONN_LCONF_DONE); + err = l2cap_br_conf_rsp_opt_check(chan, opt_len, buf); + if (err) + { + /* currently disconnect channel if opt is invalid */ + + bt_l2cap_chan_disconnect(chan); + break; + } + + if (br_chan->state == BT_L2CAP_CONFIG && + atomic_test_bit(br_chan->flags, L2CAP_FLAG_CONN_RCONF_DONE)) + { + LOG_DBG("scid 0x%04x rx MTU %u dcid 0x%04x tx MTU %u", + br_chan->rx.cid, br_chan->rx.mtu, + br_chan->tx.cid, br_chan->tx.mtu); + + bt_l2cap_br_chan_set_state(chan, BT_L2CAP_CONNECTED); + if (chan->ops && chan->ops->connected) + { + chan->ops->connected(chan); + } + } + break; + case BT_L2CAP_CONF_UNACCEPT: + err = l2cap_br_conf_rsp_unaccept_opt(chan, opt_len, buf); + if (!err) + { + l2cap_br_conf(chan, false); + break; + } + + __fallthrough; + default: + + /* currently disconnect channel on non success result */ + + bt_l2cap_chan_disconnect(chan); + break; + } +} + +static int bt_l2cap_br_allocate_psm(uint16_t *psm) +{ + /* DYN_END is UINT16_MAX, so to be able to do a psm <= DYN_END comparison + * we need to use uint32_t as the type. + */ + + static uint32_t allocated_psm = L2CAP_BR_PSM_DYN_START; + + if (allocated_psm < L2CAP_BR_PSM_DYN_END) + { + allocated_psm = allocated_psm + 1; + } + else + { + goto failed; + } + + for (; allocated_psm <= L2CAP_BR_PSM_DYN_END; allocated_psm++) + { + /* Bluetooth Core Specification Version 6.0 | Vol 3, Part A, section + * 4.2 + * + * The PSM field is at least two octets in length. All PSM values shall + * have the least significant bit of the most significant octet equal + * to 0 and the least significant bit of all other octets equal to 1. + */ + + if ((allocated_psm & 0x0101) != 0x0001) + { + continue; + } + + if (l2cap_br_server_lookup_psm((uint16_t)allocated_psm)) + { + LOG_DBG("PSM 0x%04x has been used", allocated_psm); + continue; + } + + LOG_DBG("Allocated PSM 0x%04x for new server", allocated_psm); + *psm = (uint16_t)allocated_psm; + return 0; + } + +failed: + LOG_WRN("No free dynamic PSMs available"); + return -EADDRNOTAVAIL; +} + +bt_security_t bt_l2cap_br_get_max_sec_level(void) +{ + struct bt_l2cap_server *server; + bt_security_t sec_level = BT_SECURITY_L0; + + list_for_every_container(&br_servers, server, node) + { + if (sec_level < server->sec_level) + { + sec_level = server->sec_level; + } + } + + return sec_level; +} + +int bt_l2cap_br_server_register(struct bt_l2cap_server *server) +{ + int err; + + if (server == NULL) + { + return -EINVAL; + } + + if (list_find(&br_servers, &server->node, NULL)) + { + return -EEXIST; + } + + if (!server->accept) + { + return -EINVAL; + } + + if (server->sec_level > BT_SECURITY_L4) + { + return -EINVAL; + } + else if (server->sec_level == BT_SECURITY_L0 && + server->psm != L2CAP_BR_PSM_SDP) + { + server->sec_level = BT_SECURITY_L1; + } + + if (!server->psm) + { + err = bt_l2cap_br_allocate_psm(&server->psm); + if (err) + { + return err; + } + } + else + { + /* PSM must be odd and lsb of upper byte must be 0 */ + + if ((server->psm & 0x0101) != 0x0001) + { + LOG_WRN("PSM must be odd and lsb of upper byte must be 0"); + return -EINVAL; + } + + /* Check if given PSM is already in use */ + + if (l2cap_br_server_lookup_psm(server->psm)) + { + LOG_WRN("PSM already registered"); + return -EADDRINUSE; + } + } + + LOG_DBG("PSM 0x%04x", server->psm); + + list_add_tail(&br_servers, &server->node); + + return 0; +} + +int bt_l2cap_br_server_unregister(struct bt_l2cap_server *server) +{ + if (server == NULL) + { + return -EINVAL; + } + + if (!list_find_and_remove(&br_servers, &server->node)) + { + return -ENOENT; + } + + LOG_DBG("PSM 0x%04x unregistered", server->psm); + + return 0; +} + +static void l2cap_br_send_reject( + struct bt_conn *conn, uint8_t ident, uint16_t reason, void *data, + uint8_t data_len) +{ + struct bt_l2cap_cmd_reject *rej; + struct bt_l2cap_sig_hdr *hdr; + struct net_buf *buf; + + buf = l2cap_br_create_pdu(&br_sig_pool, ((clock_t)-1)); + if (buf == NULL) + { + LOG_ERR("Fail to send reject packet"); + + /* Consider disconnect ACL here. */ + + return; + } + + hdr = net_buf_add(buf, sizeof(*hdr)); + hdr->code = BT_L2CAP_CMD_REJECT; + hdr->ident = ident; + hdr->len = sys_cpu_to_le16(sizeof(*rej) + data_len); + + rej = net_buf_add(buf, sizeof(*rej)); + rej->reason = sys_cpu_to_le16(reason); + + /* optional data if available must be already in little-endian format + * made by caller.and be compliant with Core 4.2 [Vol 3, Part A, 4.1, table + * 4.4] + */ + + if (data) + { + net_buf_add_mem(buf, data, data_len); + } + + l2cap_send(conn, BT_L2CAP_CID_BR_SIG, buf); +} + +static uint16_t l2cap_br_conf_opt_mtu(struct bt_l2cap_chan *chan, + struct net_buf *buf, size_t len) +{ + uint16_t mtu; + uint16_t result = BT_L2CAP_CONF_SUCCESS; + struct bt_l2cap_conf_opt_mtu *opt_mtu; + + /* Core 4.2 [Vol 3, Part A, 5.1] MTU payload length */ + + if (len != sizeof(*opt_mtu)) + { + LOG_ERR("tx MTU length %zu invalid", len); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + opt_mtu = (struct bt_l2cap_conf_opt_mtu *)buf->data; + + mtu = sys_le16_to_cpu(opt_mtu->mtu); + if (mtu < L2CAP_BR_MIN_MTU) + { + result = BT_L2CAP_CONF_UNACCEPT; + BR_CHAN(chan)->tx.mtu = L2CAP_BR_MIN_MTU; + opt_mtu->mtu = sys_cpu_to_le16(L2CAP_BR_MIN_MTU); + LOG_DBG("tx MTU %u invalid", mtu); + goto done; + } + + BR_CHAN(chan)->tx.mtu = mtu; + LOG_DBG("tx MTU %u", mtu); +done: + return result; +} + +static uint16_t l2cap_br_conf_opt_flush_timeout( + struct bt_l2cap_chan *chan, struct net_buf *buf, size_t len) +{ + uint16_t result = BT_L2CAP_CONF_SUCCESS; + struct bt_l2cap_conf_opt_flush_timeout *opt_to; + + if (len != sizeof(*opt_to)) + { + LOG_ERR("qos frame length %zu invalid", len); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + opt_to = (struct bt_l2cap_conf_opt_flush_timeout *)buf->data; + + LOG_DBG("Flush timeout %u", opt_to->timeout); + + opt_to->timeout = sys_le16_to_cpu(opt_to->timeout); + + if (opt_to->timeout == 0) + { + /* 0 is illegal value */ + + LOG_ERR("Flush timeout cannot be 0"); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + if (opt_to->timeout < CONFIG_BT_L2CAP_RX_FLUSH_TO) + { + LOG_DBG("Flush timeout %u is too small", opt_to->timeout); + opt_to->timeout = sys_cpu_to_le16(CONFIG_BT_L2CAP_RX_FLUSH_TO); + result = BT_L2CAP_CONF_UNACCEPT; + } + +done: + return result; +} + +static uint16_t l2cap_br_conf_opt_qos(struct bt_l2cap_chan *chan, + struct net_buf *buf, size_t len) +{ + uint16_t result = BT_L2CAP_CONF_SUCCESS; + struct bt_l2cap_conf_opt_qos *opt_qos; + struct bt_l2cap_chan *chan_sig; + struct bt_l2cap_br *br_chan_sig; + + chan_sig = bt_l2cap_br_lookup_rx_cid(chan->conn, BT_L2CAP_CID_BR_SIG); + if (!chan_sig) + { + LOG_WRN("Ignoring data for unknown channel ID 0x%04x", + BT_L2CAP_CID_BR_SIG); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + br_chan_sig = CONTAINER_OF(chan_sig, struct bt_l2cap_br, chan.chan); + if (((br_chan_sig->info_feat_mask & L2CAP_FEAT_QOS_MASK) == 0) || + ((L2CAP_EXTENDED_FEAT_MASK & L2CAP_FEAT_QOS_MASK) == 0)) + { + LOG_WRN("Unsupported extended flow spec"); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + if (len != sizeof(*opt_qos)) + { + LOG_ERR("qos frame length %zu invalid", len); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + opt_qos = (struct bt_l2cap_conf_opt_qos *)buf->data; + + LOG_DBG("QOS Type %u", opt_qos->service_type); + + if (opt_qos->service_type == BT_L2CAP_QOS_TYPE_GUARANTEED) + { + result = BT_L2CAP_CONF_UNACCEPT; + opt_qos->service_type = BT_L2CAP_QOS_TYPE_BEST_EFFORT; + + /* Set to default value */ + + opt_qos->flags = 0x00; + + /* do not care */ + + opt_qos->token_rate = sys_cpu_to_le32(L2CAP_QOS_TOKEN_RATE_DEFAULT); + + /* no token bucket is needed */ + + opt_qos->token_bucket_size = + sys_cpu_to_le32(L2CAP_QOS_TOKEN_BUCKET_SIZE_DEFAULT); + + /* do not care */ + + opt_qos->peak_bandwidth = + sys_cpu_to_le32(L2CAP_QOS_PEAK_BANDWIDTH_DEFAULT); + + /* do not care */ + + opt_qos->latency = sys_cpu_to_le32(L2CAP_QOS_LATENCY_DEFAULT); + + /* do not care */ + + opt_qos->delay_variation = sys_cpu_to_le32(L2CAP_QOS_DELAY_DEFAULT); + } + +done: + return result; +} + +#if defined(CONFIG_BT_L2CAP_RET_FC) +static uint16_t l2cap_br_conf_opt_ret_fc( + struct bt_l2cap_chan *chan, struct net_buf *buf, size_t len) +{ + uint16_t result = BT_L2CAP_CONF_UNACCEPT; + struct bt_l2cap_conf_opt_ret_fc *opt_ret_fc; + struct bt_l2cap_br_chan *br_chan; + struct bt_l2cap_chan *chan_sig; + struct bt_l2cap_br *br_chan_sig; + uint16_t retransmission_timeout; + uint16_t monitor_timeout; + bool accept = true; + uint8_t mode; + + if (len != sizeof(*opt_ret_fc)) + { + LOG_ERR("ret_fc frame length %zu invalid", len); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + chan_sig = bt_l2cap_br_lookup_rx_cid(chan->conn, BT_L2CAP_CID_BR_SIG); + if (!chan_sig) + { + LOG_WRN("Ignoring data for unknown channel ID 0x%04x", + BT_L2CAP_CID_BR_SIG); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + br_chan_sig = CONTAINER_OF(chan_sig, struct bt_l2cap_br, chan.chan); + + opt_ret_fc = (struct bt_l2cap_conf_opt_ret_fc *)buf->data; + + LOG_DBG("ret_fc mode %u", opt_ret_fc->mode); + + br_chan = BR_CHAN(chan); + + mode = opt_ret_fc->mode; + + switch (opt_ret_fc->mode) + { + case BT_L2CAP_BR_LINK_MODE_BASIC: + break; + case BT_L2CAP_BR_LINK_MODE_RET: + __fallthrough; + case BT_L2CAP_BR_LINK_MODE_FC: + if (((br_chan_sig->info_feat_mask & L2CAP_FEAT_ENH_RET_MASK) != 0) && + ((L2CAP_EXTENDED_FEAT_MASK & L2CAP_FEAT_ENH_RET_MASK) != 0)) + { + /* If enhance transmission mode is supported by both side, + * the enhance transmission mode should be used instead of + * transmission or flow control mode. + */ + + mode = BT_L2CAP_BR_LINK_MODE_ERET; + } + else + { + if ((opt_ret_fc->mode == BT_L2CAP_BR_LINK_MODE_FC) && + (((br_chan_sig->info_feat_mask & L2CAP_FEAT_FC_MASK) == 0) || + ((L2CAP_EXTENDED_FEAT_MASK & L2CAP_FEAT_FC_MASK) == 0))) + { + mode = BT_L2CAP_BR_LINK_MODE_BASIC; + } + + if ((opt_ret_fc->mode == BT_L2CAP_BR_LINK_MODE_RET) && + (((br_chan_sig->info_feat_mask & + L2CAP_FEAT_RET_MASK) == 0) || + ((L2CAP_EXTENDED_FEAT_MASK & L2CAP_FEAT_RET_MASK) == 0))) + { + mode = BT_L2CAP_BR_LINK_MODE_BASIC; + } + } + break; + case BT_L2CAP_BR_LINK_MODE_ERET: + if (((br_chan_sig->info_feat_mask & L2CAP_FEAT_ENH_RET_MASK) == 0) || + ((L2CAP_EXTENDED_FEAT_MASK & L2CAP_FEAT_ENH_RET_MASK) == 0)) + { + /* If enhance transmission mode is unsupported by any side, + * the transmission mode should be used instead of enhance + * transmission mode. + */ + + if (((br_chan_sig->info_feat_mask & L2CAP_FEAT_RET_MASK) != 0) && + ((L2CAP_EXTENDED_FEAT_MASK & L2CAP_FEAT_RET_MASK) != 0)) + { + mode = BT_L2CAP_BR_LINK_MODE_RET; + } + else if (((br_chan_sig->info_feat_mask & L2CAP_FEAT_FC_MASK) != + 0) && + ((L2CAP_EXTENDED_FEAT_MASK & L2CAP_FEAT_FC_MASK) != 0)) + { + mode = BT_L2CAP_BR_LINK_MODE_FC; + } + else + { + mode = BT_L2CAP_BR_LINK_MODE_BASIC; + } + } + break; + case BT_L2CAP_BR_LINK_MODE_STREAM: + if (((br_chan_sig->info_feat_mask & L2CAP_FEAT_STREAM_MASK) == 0) || + ((L2CAP_EXTENDED_FEAT_MASK & L2CAP_FEAT_STREAM_MASK) == 0)) + { + /* Unsupported mode */ + + mode = BT_L2CAP_BR_LINK_MODE_BASIC; + } + break; + default: + mode = BT_L2CAP_BR_LINK_MODE_BASIC; + break; + } + + if (!br_chan->rx.optional) + { + switch (mode) + { + case BT_L2CAP_BR_LINK_MODE_BASIC: + __fallthrough; + case BT_L2CAP_BR_LINK_MODE_ERET: + __fallthrough; + case BT_L2CAP_BR_LINK_MODE_STREAM: + + /* The tx mode should be same as rx mode */ + + if (mode != br_chan->rx.mode) + { + mode = br_chan->rx.mode; + } + break; + case BT_L2CAP_BR_LINK_MODE_RET: + __fallthrough; + case BT_L2CAP_BR_LINK_MODE_FC: + + /* The tx mode should be aligned with rx mode */ + + if ((br_chan->rx.mode != BT_L2CAP_BR_LINK_MODE_RET) && + (br_chan->rx.mode != BT_L2CAP_BR_LINK_MODE_FC)) + { + mode = br_chan->rx.mode; + } + break; + default: + break; + } + } + else + { + if (br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_BASIC) + { + mode = br_chan->rx.mode; + } + else + { + br_chan->rx.mode = mode; + } + } + + if (opt_ret_fc->mode != mode) + { + accept = false; + opt_ret_fc->mode = mode; + } + + br_chan->tx.mode = mode; + + if (opt_ret_fc->mode != BT_L2CAP_BR_LINK_MODE_BASIC) + { + uint16_t mps = CONFIG_BT_L2CAP_MPS; + + if (sys_le16_to_cpu(opt_ret_fc->mps) > br_chan->tx.mtu) + { + /* There is an issue that the mps is bigger than tx mtu sent from + * the peer device. The L2CAP configuration request from the peer + * device contains a mps value that is bigger its tx mtu (sent in + * the same L2CAP_CONFIGURATION_REQ packet or omitted). The issue + * can occur when connecting to an iphone with the enhanced + * retransmission mode if the test profile is MAP or PBAP. + * + * Just adjust it to the tx mtu, and give the suggested value in + * the L2CAP_CONFIGURATION_RSP packet. + */ + + opt_ret_fc->mps = sys_cpu_to_le16(br_chan->tx.mtu); + } + + if (sys_le16_to_cpu(opt_ret_fc->mps) > mps) + { + opt_ret_fc->mps = sys_cpu_to_le16(mps); + } + + if ((opt_ret_fc->mode == BT_L2CAP_BR_LINK_MODE_RET) || + (opt_ret_fc->mode == BT_L2CAP_BR_LINK_MODE_FC)) + { + if ((opt_ret_fc->tx_windows_size < 1) || + (opt_ret_fc->tx_windows_size > 32)) + { + opt_ret_fc->tx_windows_size = CONFIG_BT_L2CAP_MAX_WINDOW_SIZE; + accept = false; + } + + if (opt_ret_fc->max_transmit < 1) + { + opt_ret_fc->max_transmit = 1; + accept = false; + } + } + + if (opt_ret_fc->mode == BT_L2CAP_BR_LINK_MODE_ERET) + { + if ((opt_ret_fc->tx_windows_size < 1) || + (opt_ret_fc->tx_windows_size > 63)) + { + opt_ret_fc->tx_windows_size = CONFIG_BT_L2CAP_MAX_WINDOW_SIZE; + accept = false; + } + } + + retransmission_timeout = + sys_le16_to_cpu(opt_ret_fc->retransmission_timeout); + + monitor_timeout = sys_le16_to_cpu(opt_ret_fc->monitor_timeout); + if (monitor_timeout < retransmission_timeout) + { + monitor_timeout = retransmission_timeout; + accept = false; + } + + if (opt_ret_fc->tx_windows_size > CONFIG_BT_L2CAP_MAX_WINDOW_SIZE) + { + opt_ret_fc->tx_windows_size = CONFIG_BT_L2CAP_MAX_WINDOW_SIZE; + } + + if (monitor_timeout == 0) + { + monitor_timeout = CONFIG_BT_L2CAP_BR_MONITOR_TIMEOUT; + } + + if (retransmission_timeout == 0) + { + retransmission_timeout = CONFIG_BT_L2CAP_BR_RET_TIMEOUT; + } + + opt_ret_fc->retransmission_timeout = + sys_cpu_to_le16(retransmission_timeout); + opt_ret_fc->monitor_timeout = sys_cpu_to_le16(monitor_timeout); + + if (!accept) + { + goto done; + } + + br_chan->tx.ret_timeout = retransmission_timeout; + br_chan->tx.monitor_timeout = monitor_timeout; + br_chan->tx.max_transmit = opt_ret_fc->max_transmit; + br_chan->tx.max_window = opt_ret_fc->tx_windows_size; + br_chan->tx.mps = sys_le16_to_cpu(opt_ret_fc->mps); + + if ((opt_ret_fc->mode == BT_L2CAP_BR_LINK_MODE_RET) || + (opt_ret_fc->mode == BT_L2CAP_BR_LINK_MODE_FC)) + { + /* Bluetooth Core specification Version 6.0 | Vol 3, Part A, + * section 5.4. + * + * In Retransmission mode and Flow Control mode this parameter + * should be negotiated to reflect the buffer sizes allocated for + * the connection on both sides. + */ + + br_chan->rx.max_window = br_chan->tx.max_window; + } + } + + if (!accept) + { + goto done; + } + + result = BT_L2CAP_CONF_SUCCESS; + +done: + return result; +} + +static uint16_t l2cap_br_conf_opt_fcs(struct bt_l2cap_chan *chan, + struct net_buf *buf, size_t len) +{ + uint16_t result = BT_L2CAP_CONF_SUCCESS; + struct bt_l2cap_conf_opt_fcs *opt_fcs; + struct bt_l2cap_br_chan *br_chan; + struct bt_l2cap_chan *chan_sig; + struct bt_l2cap_br *br_chan_sig; + + chan_sig = bt_l2cap_br_lookup_rx_cid(chan->conn, BT_L2CAP_CID_BR_SIG); + if (!chan_sig) + { + LOG_WRN("Ignoring data for unknown channel ID 0x%04x", + BT_L2CAP_CID_BR_SIG); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + br_chan_sig = CONTAINER_OF(chan_sig, struct bt_l2cap_br, chan.chan); + if (((br_chan_sig->info_feat_mask & L2CAP_FEAT_FCS_MASK) == 0) || + ((L2CAP_EXTENDED_FEAT_MASK & L2CAP_FEAT_FCS_MASK) == 0)) + { + LOG_WRN("Unsupported extended flow spec"); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + if (len != sizeof(*opt_fcs)) + { + LOG_ERR("fcs frame length %zu invalid", len); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + opt_fcs = (struct bt_l2cap_conf_opt_fcs *)buf->data; + + LOG_DBG("FCS type %u", opt_fcs->type); + + if ((opt_fcs->type != BT_L2CAP_FCS_TYPE_NO) && + (opt_fcs->type != BT_L2CAP_FCS_TYPE_16BIT)) + { + LOG_ERR("fcs type %u invalid", opt_fcs->type); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + br_chan = BR_CHAN(chan); + + switch (br_chan->tx.mode) + { + case BT_L2CAP_BR_LINK_MODE_BASIC: + result = BT_L2CAP_CONF_REJECT; + goto done; + case BT_L2CAP_BR_LINK_MODE_RET: + __fallthrough; + case BT_L2CAP_BR_LINK_MODE_FC: + LOG_ERR("fcs opt is mandatory in mode %u", br_chan->tx.mode); + if (opt_fcs->type != BT_L2CAP_BR_FCS_16BIT) + { + result = BT_L2CAP_CONF_UNACCEPT; + opt_fcs->type = BT_L2CAP_BR_FCS_16BIT; + br_chan->tx.fcs = opt_fcs->type; + goto done; + } + + br_chan->tx.fcs = BT_L2CAP_BR_FCS_16BIT; + break; + case BT_L2CAP_BR_LINK_MODE_ERET: + __fallthrough; + case BT_L2CAP_BR_LINK_MODE_STREAM: + br_chan->tx.fcs = opt_fcs->type; + break; + default: + + /* Should not have been here */ + + LOG_ERR("Unknown mode %u", br_chan->tx.mode); + break; + } + +done: + return result; +} + +static uint16_t l2cap_br_conf_opt_ext_flow_spec( + struct bt_l2cap_chan *chan, struct net_buf *buf, size_t len) +{ + uint16_t result = BT_L2CAP_CONF_SUCCESS; + struct bt_l2cap_conf_opt_ext_flow_spec *ext_flow_spec; + struct bt_l2cap_chan *chan_sig; + struct bt_l2cap_br *br_chan_sig; + + chan_sig = bt_l2cap_br_lookup_rx_cid(chan->conn, BT_L2CAP_CID_BR_SIG); + if (!chan_sig) + { + LOG_WRN("Ignoring data for unknown channel ID 0x%04x", + BT_L2CAP_CID_BR_SIG); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + br_chan_sig = CONTAINER_OF(chan_sig, struct bt_l2cap_br, chan.chan); + if (((br_chan_sig->info_feat_mask & L2CAP_FEAT_EXT_FS_MASK) == 0) || + ((L2CAP_EXTENDED_FEAT_MASK & L2CAP_FEAT_EXT_FS_MASK) == 0)) + { + LOG_WRN("Unsupported extended flow spec"); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + if (len != sizeof(*ext_flow_spec)) + { + LOG_ERR("fcs frame length %zu invalid", len); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + ext_flow_spec = (struct bt_l2cap_conf_opt_ext_flow_spec *)buf->data; + + LOG_DBG("Ext Flow Spec %u %u %u %u %u %u", ext_flow_spec->identifier, + ext_flow_spec->service_type, + ext_flow_spec->sdu, ext_flow_spec->sdu_inter_arrival_time, + ext_flow_spec->access_latency, + ext_flow_spec->flush_timeout); + + /* Set to default value */ + + result = BT_L2CAP_CONF_REJECT; + ext_flow_spec->identifier = 0x01; + ext_flow_spec->service_type = 0x01; + ext_flow_spec->sdu = sys_cpu_to_le16(L2CAP_EXT_FLOW_SPEC_SDU_DEFAULT); + ext_flow_spec->sdu_inter_arrival_time = + sys_cpu_to_le32(L2CAP_EXT_FLOW_SPEC_DEFAULT); + ext_flow_spec->access_latency = + sys_cpu_to_le32(L2CAP_EXT_FLOW_SPEC_DEFAULT); + ext_flow_spec->flush_timeout = + sys_cpu_to_le32(L2CAP_EXT_FLOW_SPEC_DEFAULT); + +done: + return result; +} + +static uint16_t l2cap_br_conf_opt_ext_win_size( + struct bt_l2cap_chan *chan, struct net_buf *buf, size_t len) +{ + uint16_t result = BT_L2CAP_CONF_SUCCESS; + struct bt_l2cap_conf_opt_ext_win_size *opt_ext_win_size; + struct bt_l2cap_br_chan *br_chan; + uint16_t win_size; + struct bt_l2cap_chan *chan_sig; + struct bt_l2cap_br *br_chan_sig; + + chan_sig = bt_l2cap_br_lookup_rx_cid(chan->conn, BT_L2CAP_CID_BR_SIG); + if (!chan_sig) + { + LOG_WRN("Ignoring data for unknown channel ID 0x%04x", + BT_L2CAP_CID_BR_SIG); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + br_chan_sig = CONTAINER_OF(chan_sig, struct bt_l2cap_br, chan.chan); + if (((br_chan_sig->info_feat_mask & L2CAP_FEAT_EXT_WIN_SIZE_MASK) == 0) || + ((L2CAP_EXTENDED_FEAT_MASK & L2CAP_FEAT_EXT_WIN_SIZE_MASK) == 0)) + { + LOG_WRN("Unsupported extended window size"); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + if (len != sizeof(*opt_ext_win_size)) + { + LOG_ERR("fcs frame length %zu invalid", len); + result = BT_L2CAP_CONF_REJECT; + goto done; + } + + opt_ext_win_size = (struct bt_l2cap_conf_opt_ext_win_size *)buf->data; + win_size = sys_le16_to_cpu(opt_ext_win_size->max_windows_size); + + LOG_DBG("EXT Win Size value %u", win_size); + + br_chan = BR_CHAN(chan); + + switch (br_chan->tx.mode) + { + case BT_L2CAP_BR_LINK_MODE_BASIC: + __fallthrough; + case BT_L2CAP_BR_LINK_MODE_RET: + __fallthrough; + case BT_L2CAP_BR_LINK_MODE_FC: + + /* ignore */ + + LOG_WRN("Ignored by mode %u", br_chan->tx.mode); + break; + case BT_L2CAP_BR_LINK_MODE_ERET: + if ((win_size < 1) || (win_size > 0x3fff)) + { + /* Set to default value */ + + opt_ext_win_size->max_windows_size = + sys_cpu_to_le16(CONFIG_BT_L2CAP_MAX_WINDOW_SIZE); + result = BT_L2CAP_CONF_UNACCEPT; + goto done; + } + + if (win_size > CONFIG_BT_L2CAP_MAX_WINDOW_SIZE) + { + opt_ext_win_size->max_windows_size = + sys_cpu_to_le16(CONFIG_BT_L2CAP_MAX_WINDOW_SIZE); + } + + br_chan->tx.extended_control = true; + br_chan->tx.max_window = win_size; + break; + case BT_L2CAP_BR_LINK_MODE_STREAM: + if (win_size) + { + result = BT_L2CAP_CONF_UNACCEPT; + opt_ext_win_size->max_windows_size = 0; + goto done; + } + + br_chan->tx.extended_control = true; + break; + default: + + /* Should not have been here */ + + LOG_ERR("Unknown mode %u", br_chan->tx.mode); + break; + } + +done: + return result; +} +#endif /* CONFIG_BT_L2CAP_RET_FC */ + +static void l2cap_br_config_rsp_complete(struct bt_l2cap_chan *chan) +{ + if (!atomic_test_and_clear_bit(BR_CHAN(chan)->flags, + L2CAP_FLAG_CONN_CONF_RSP_SENDING)) + { + LOG_DBG("No config response pending for chan %p", BR_CHAN(chan)); + return; + } + + atomic_set_bit(BR_CHAN(chan)->flags, L2CAP_FLAG_CONN_RCONF_DONE); + + if (!atomic_test_bit(BR_CHAN(chan)->flags, L2CAP_FLAG_CONN_LCONF_DONE)) + { + LOG_DBG("Local config req is not done"); + return; + } + + if (BR_CHAN(chan)->state == BT_L2CAP_CONFIG) + { + LOG_DBG("scid 0x%04x rx MTU %u dcid 0x%04x tx MTU %u", + BR_CHAN(chan)->rx.cid, + BR_CHAN(chan)->rx.mtu, BR_CHAN(chan)->tx.cid, + BR_CHAN(chan)->tx.mtu); + + bt_l2cap_br_chan_set_state(chan, BT_L2CAP_CONNECTED); + if (chan->ops != NULL && chan->ops->connected != NULL) + { + chan->ops->connected(chan); + } + } +} + +static void l2cap_br_config_rsp_sent_cb(struct bt_conn *conn, + void *user_data, int err) +{ + uint16_t scid = POINTER_TO_UINT(user_data); + struct bt_l2cap_chan *chan; + + chan = bt_l2cap_br_lookup_tx_cid(conn, scid); + if (chan == NULL) + { + return; + } + + if (err != 0) + { + LOG_ERR("Config response of chan %p failed to send " + "(%d)", BR_CHAN(chan), + err); + l2cap_br_chan_disconn(chan); + return; + } + + l2cap_br_config_rsp_complete(chan); +} + +static void l2cap_br_conf_req( + struct bt_l2cap_br *l2cap, uint8_t ident, uint16_t len, + struct net_buf *buf) +{ + struct bt_conn *conn = l2cap->chan.chan.conn; + struct bt_l2cap_chan *chan; + struct bt_l2cap_conf_req *req; + struct bt_l2cap_sig_hdr *hdr; + struct bt_l2cap_conf_rsp *rsp; + struct bt_l2cap_conf_opt *opt = NULL; + uint16_t flags; + uint16_t dcid; + uint16_t opt_len; + uint16_t hint; + uint16_t result = BT_L2CAP_CONF_SUCCESS; + struct net_buf *rsp_buf; + int err; + + if (len < sizeof(*req)) + { + LOG_ERR("Too small L2CAP conf req packet size"); + return; + } + + req = net_buf_pull_mem(buf, sizeof(*req)); + flags = sys_le16_to_cpu(req->flags); + dcid = sys_le16_to_cpu(req->dcid); + opt_len = len - sizeof(*req); + + LOG_DBG("dcid 0x%04x flags 0x%02x len %u", dcid, flags, opt_len); + + chan = bt_l2cap_br_lookup_rx_cid(conn, dcid); + if (!chan) + { + LOG_ERR("rx channel mismatch!"); + struct bt_l2cap_cmd_reject_cid_data data = + { + .scid = req->dcid, + .dcid = 0, + }; + + l2cap_br_send_reject(conn, ident, BT_L2CAP_REJ_INVALID_CID, &data, + sizeof(data)); + return; + } + + rsp_buf = l2cap_br_create_pdu(&br_sig_pool, L2CAP_BR_CFG_TIMEOUT); + if (rsp_buf == NULL) + { + l2cap_br_chan_disconn(chan); + return; + } + + hdr = net_buf_add(rsp_buf, sizeof(*hdr)); + hdr->code = BT_L2CAP_CONF_RSP; + hdr->ident = ident; + rsp = net_buf_add(rsp_buf, sizeof(*rsp)); + (void)memset(rsp, 0, sizeof(*rsp)); + + rsp->scid = sys_cpu_to_le16(BR_CHAN(chan)->tx.cid); + + /* Core 5.4, Vol 3, Part A, section 4.5. + * When used in the L2CAP_CONFIGURATION_RSP packet, the continuation flag + * shall be set to one if the flag is set to one in the Request, except for + * those error conditions more appropriate for an L2CAP_COMMAND_REJECT_RSP + * packet. + */ + + rsp->flags = sys_cpu_to_le16(flags & BT_L2CAP_CONF_FLAGS_MASK); + + if (!opt_len) + { + LOG_DBG("tx default MTU %u", L2CAP_BR_DEFAULT_MTU); + BR_CHAN(chan)->tx.mtu = L2CAP_BR_DEFAULT_MTU; + goto send_rsp; + } + + while (opt_len >= sizeof(*opt)) + { + opt = net_buf_pull_mem(buf, sizeof(*opt)); + + opt_len -= sizeof(*opt); + + /* make sure opt object can get safe dereference in iteration */ + + if (opt_len < opt->len) + { + LOG_ERR("Received too short option data"); + result = BT_L2CAP_CONF_REJECT; + break; + } + + opt_len -= opt->len; + + hint = opt->type & BT_L2CAP_CONF_HINT; + + switch (opt->type & BT_L2CAP_CONF_MASK) + { + case BT_L2CAP_CONF_OPT_MTU: + + /* getting MTU modifies buf internals */ + + result = l2cap_br_conf_opt_mtu(chan, buf, opt->len); + if (result != BT_L2CAP_CONF_SUCCESS) + { + goto send_rsp; + } + break; + case BT_L2CAP_CONF_OPT_FLUSH_TIMEOUT: + result = l2cap_br_conf_opt_flush_timeout(chan, buf, opt->len); + if (result != BT_L2CAP_CONF_SUCCESS) + { + goto send_rsp; + } + break; + case BT_L2CAP_CONF_OPT_QOS: + result = l2cap_br_conf_opt_qos(chan, buf, opt->len); + if (result != BT_L2CAP_CONF_SUCCESS) + { + goto send_rsp; + } + + l2cap_br_conf_add_opt(rsp_buf, opt); + break; +#if defined(CONFIG_BT_L2CAP_RET_FC) + case BT_L2CAP_CONF_OPT_RET_FC: + result = l2cap_br_conf_opt_ret_fc(chan, buf, opt->len); + if (result != BT_L2CAP_CONF_SUCCESS) + { + goto send_rsp; + } + else + { + l2cap_br_conf_add_opt(rsp_buf, opt); + } + break; + case BT_L2CAP_CONF_OPT_FCS: + result = l2cap_br_conf_opt_fcs(chan, buf, opt->len); + if (result != BT_L2CAP_CONF_SUCCESS) + { + goto send_rsp; + } + break; + case BT_L2CAP_CONF_OPT_EXT_FLOW_SPEC: + result = l2cap_br_conf_opt_ext_flow_spec(chan, buf, opt->len); + if (result != BT_L2CAP_CONF_SUCCESS) + { + goto send_rsp; + } + break; + case BT_L2CAP_CONF_OPT_EXT_WIN_SIZE: + result = l2cap_br_conf_opt_ext_win_size(chan, buf, opt->len); + if (result != BT_L2CAP_CONF_SUCCESS) + { + goto send_rsp; + } + else + { + l2cap_br_conf_add_opt(rsp_buf, opt); + } + break; +#endif /* CONFIG_BT_L2CAP_RET_FC */ + default: + if (!hint) + { + LOG_DBG("option %u not handled", opt->type); + result = BT_L2CAP_CONF_UNKNOWN_OPT; + goto send_rsp; + } + break; + } + + /* Update buffer to point at next option */ + + net_buf_pull(buf, opt->len); + } + +send_rsp: + rsp->result = sys_cpu_to_le16(result); + + /* TODO: If options other than MTU became meaningful then processing + * the options chain need to be modified and taken into account when + * sending back to peer. + */ + + if ((result == BT_L2CAP_CONF_UNKNOWN_OPT) || + (result == BT_L2CAP_CONF_UNACCEPT)) + { + if (opt) + { + l2cap_br_conf_add_opt(rsp_buf, opt); + } + } + + hdr->len = sys_cpu_to_le16(rsp_buf->len - sizeof(*hdr)); + + if (result != BT_L2CAP_CONF_SUCCESS) + { + l2cap_send(conn, BT_L2CAP_CID_BR_SIG, rsp_buf); + return; + } + +#if defined(CONFIG_BT_L2CAP_RET_FC) + if (BR_CHAN(chan)->tx.fcs != BR_CHAN(chan)->rx.fcs) + { + /* If FCS flag is not consistent of both sides, FCS should be used as + * default. + */ + + BR_CHAN(chan)->rx.fcs = BT_L2CAP_BR_FCS_16BIT; + BR_CHAN(chan)->tx.fcs = BT_L2CAP_BR_FCS_16BIT; + } + + if (BR_CHAN(chan)->tx.extended_control) + { + /* If peer enables extended control field, + * local also needs to enable it. + */ + + BR_CHAN(chan)->rx.extended_control = true; + } +#endif /* CONFIG_BT_L2CAP_RET_FC */ + + atomic_set_bit(BR_CHAN(chan)->flags, L2CAP_FLAG_CONN_CONF_RSP_SENDING); + err = bt_l2cap_br_send_cb(conn, BT_L2CAP_CID_BR_SIG, rsp_buf, + l2cap_br_config_rsp_sent_cb, + UINT_TO_POINTER(BR_CHAN(chan)->tx.cid)); + if (err != 0) + { + atomic_clear_bit(BR_CHAN(chan)->flags, + L2CAP_FLAG_CONN_CONF_RSP_SENDING); + LOG_ERR("Failed to send config response of chan %p " + "(%d)", BR_CHAN(chan), + err); + net_buf_unref(rsp_buf); + l2cap_br_chan_disconn(chan); + } +} + +static struct bt_l2cap_br_chan *l2cap_br_remove_tx_cid(struct bt_conn *conn, + uint16_t cid) +{ + struct bt_l2cap_chan *chan; + struct list_node *prev = NULL; + + /* Protect fixed channels against accidental removal */ + + if (!L2CAP_BR_CID_IS_DYN(cid)) + { + return NULL; + } + + list_for_every_container(&conn->channels, chan, node) + { + if (BR_CHAN(chan)->tx.cid == cid) + { + list_remove(&conn->channels, prev, &chan->node); + return BR_CHAN(chan); + } + + prev = &chan->node; + } + + return NULL; +} + +static void l2cap_br_disconn_req(struct bt_l2cap_br *l2cap, + uint8_t ident, struct net_buf *buf) +{ + struct bt_conn *conn = l2cap->chan.chan.conn; + struct bt_l2cap_br_chan *chan; + struct bt_l2cap_disconn_req *req = (void *)buf->data; + struct bt_l2cap_disconn_rsp *rsp; + struct bt_l2cap_sig_hdr *hdr; + uint16_t scid; + uint16_t dcid; + + if (buf->len < sizeof(*req)) + { + LOG_ERR("Too small disconn req packet size"); + return; + } + + dcid = sys_le16_to_cpu(req->dcid); + scid = sys_le16_to_cpu(req->scid); + + LOG_DBG("scid 0x%04x dcid 0x%04x", dcid, scid); + + chan = l2cap_br_remove_tx_cid(conn, scid); + if (!chan) + { + struct bt_l2cap_cmd_reject_cid_data data; + + data.scid = req->scid; + data.dcid = req->dcid; + l2cap_br_send_reject(conn, ident, BT_L2CAP_REJ_INVALID_CID, &data, + sizeof(data)); + return; + } + + buf = l2cap_br_create_pdu(&br_sig_pool, L2CAP_BR_DISCONN_TIMEOUT); + if (buf == NULL) + { + l2cap_br_chan_disconn(&chan->chan); + return; + } + + hdr = net_buf_add(buf, sizeof(*hdr)); + hdr->code = BT_L2CAP_DISCONN_RSP; + hdr->ident = ident; + hdr->len = sys_cpu_to_le16(sizeof(*rsp)); + + rsp = net_buf_add(buf, sizeof(*rsp)); + rsp->dcid = sys_cpu_to_le16(chan->rx.cid); + rsp->scid = sys_cpu_to_le16(chan->tx.cid); + + bt_l2cap_br_chan_del(&chan->chan); + + l2cap_send(conn, BT_L2CAP_CID_BR_SIG, buf); +} + +static void l2cap_br_connected(struct bt_l2cap_chan *chan) +{ + LOG_DBG("ch %p cid 0x%04x", BR_CHAN(chan), BR_CHAN(chan)->rx.cid); +} + +static void l2cap_br_disconnected(struct bt_l2cap_chan *chan) +{ + struct bt_l2cap_br_chan *br_chan = BR_CHAN(chan); + + LOG_DBG("ch %p cid 0x%04x", br_chan, br_chan->rx.cid); + + if (atomic_test_and_clear_bit(br_chan->flags, L2CAP_FLAG_SIG_INFO_PENDING)) + { + /* Cancel RTX work on signal channel. + * Disconnected callback is always called from the Bluetooth workqueue, + * which the RTX work also runs on, so this should always succeed. + */ + + (void)k_work_cancel_delayable(&br_chan->rtx_work); + br_chan->ident = 0; + } +} + +int bt_l2cap_br_chan_disconnect(struct bt_l2cap_chan *chan) +{ + struct bt_conn *conn = chan->conn; + struct net_buf *buf; + struct bt_l2cap_disconn_req *req; + struct bt_l2cap_sig_hdr *hdr; + struct bt_l2cap_br_chan *br_chan; + + if (!conn) + { + return -ENOTCONN; + } + + br_chan = BR_CHAN(chan); + + if (br_chan->state == BT_L2CAP_DISCONNECTING) + { + return -EALREADY; + } + + LOG_DBG("chan %p scid 0x%04x dcid 0x%04x", chan, br_chan->rx.cid, + br_chan->tx.cid); + + buf = l2cap_br_create_pdu(&br_sig_pool, ((clock_t)-1)); + if (buf == NULL) + { + return -ENOBUFS; + } + + hdr = net_buf_add(buf, sizeof(*hdr)); + hdr->code = BT_L2CAP_DISCONN_REQ; + hdr->ident = l2cap_br_get_ident(); + hdr->len = sys_cpu_to_le16(sizeof(*req)); + + req = net_buf_add(buf, sizeof(*req)); + req->dcid = sys_cpu_to_le16(br_chan->tx.cid); + req->scid = sys_cpu_to_le16(br_chan->rx.cid); + + /* Set the ident for the signaling request */ + + br_chan->ident = hdr->ident; + + l2cap_br_chan_send_req(br_chan, buf, L2CAP_BR_DISCONN_TIMEOUT); + bt_l2cap_br_chan_set_state(chan, BT_L2CAP_DISCONNECTING); + + return 0; +} + +static void l2cap_br_disconn_rsp(struct bt_l2cap_br *l2cap, + uint8_t ident, struct net_buf *buf) +{ + struct bt_conn *conn = l2cap->chan.chan.conn; + struct bt_l2cap_br_chan *chan; + struct bt_l2cap_disconn_rsp *rsp = (void *)buf->data; + uint16_t dcid; + uint16_t scid; + + if (buf->len < sizeof(*rsp)) + { + LOG_ERR("Too small disconn rsp packet size"); + return; + } + + dcid = sys_le16_to_cpu(rsp->dcid); + scid = sys_le16_to_cpu(rsp->scid); + + LOG_DBG("dcid 0x%04x scid 0x%04x", dcid, scid); + + chan = l2cap_br_remove_tx_cid(conn, dcid); + if (!chan) + { + LOG_WRN("No dcid 0x%04x channel found", dcid); + return; + } + + if (chan->ident != ident) + { + LOG_WRN("ident mismatch (%u != %u)!", chan->ident, ident); + return; + } + + chan->ident = 0; + + bt_l2cap_br_chan_del(&chan->chan); +} + +int bt_l2cap_br_chan_connect(struct bt_conn *conn, + struct bt_l2cap_chan *chan, uint16_t psm) +{ + struct net_buf *buf; + struct bt_l2cap_sig_hdr *hdr; + struct bt_l2cap_conn_req *req; + struct bt_l2cap_br_chan *br_chan = BR_CHAN(chan); +#if defined(CONFIG_BT_L2CAP_RET_FC) + int err; +#endif /* CONFIG_BT_L2CAP_RET_FC */ + + if (!psm) + { + return -EINVAL; + } + + if (br_chan->psm) + { + return -EEXIST; + } + + /* PSM must be odd and lsb of upper byte must be 0 */ + + if ((psm & 0x0101) != 0x0001) + { + return -EINVAL; + } + + if (br_chan->required_sec_level > BT_SECURITY_L4) + { + return -EINVAL; + } + else if (br_chan->required_sec_level == BT_SECURITY_L0 && + psm != L2CAP_BR_PSM_SDP) + { + br_chan->required_sec_level = BT_SECURITY_L1; + } + + switch (br_chan->state) + { + case BT_L2CAP_CONNECTED: + + /* Already connected */ + + return -EISCONN; + case BT_L2CAP_DISCONNECTED: + + /* Can connect */ + + break; + case BT_L2CAP_CONFIG: + case BT_L2CAP_DISCONNECTING: + default: + + /* Bad context */ + + return -EBUSY; + } + + if (!br_chan->rx.mtu) + { + br_chan->rx.mtu = BT_L2CAP_RX_MTU; + } +#if defined(CONFIG_BT_L2CAP_RET_FC) + + err = l2cap_br_check_chan_config(conn, br_chan); + if (err) + { + return err; + } +#else + br_chan->rx.mtu = MIN(br_chan->rx.mtu, BT_L2CAP_RX_MTU); +#endif /* CONFIG_BT_L2CAP_RET_FC */ + + if (!l2cap_br_chan_add(conn, chan, l2cap_br_chan_destroy)) + { + return -ENOMEM; + } + + br_chan->psm = psm; + bt_l2cap_br_chan_set_state(chan, BT_L2CAP_CONNECTING); + atomic_set_bit(BR_CHAN(chan)->flags, L2CAP_FLAG_CONN_PENDING); + + switch (l2cap_br_conn_security(chan, psm)) + { + case L2CAP_CONN_SECURITY_PENDING: + /* Authentication was triggered, wait with sending request on + * connection security changed callback context. + */ + + return 0; + case L2CAP_CONN_SECURITY_PASSED: + break; + case L2CAP_CONN_SECURITY_REJECT: + default: + l2cap_br_chan_cleanup(chan); + return -EIO; + } + + buf = l2cap_br_create_pdu(&br_sig_pool, ((clock_t)-1)); + if (buf == NULL) + { + l2cap_br_chan_cleanup(chan); + return -ENOBUFS; + } + + hdr = net_buf_add(buf, sizeof(*hdr)); + hdr->code = BT_L2CAP_CONN_REQ; + hdr->ident = l2cap_br_get_ident(); + hdr->len = sys_cpu_to_le16(sizeof(*req)); + + req = net_buf_add(buf, sizeof(*req)); + req->psm = sys_cpu_to_le16(psm); + req->scid = sys_cpu_to_le16(BR_CHAN(chan)->rx.cid); + + /* Set the ident for the signaling request */ + + BR_CHAN(chan)->ident = hdr->ident; + + l2cap_br_chan_send_req(BR_CHAN(chan), buf, L2CAP_BR_CONN_TIMEOUT); + + return 0; +} + +static void l2cap_br_conn_rsp(struct bt_l2cap_br *l2cap, + uint8_t ident, struct net_buf *buf) +{ + struct bt_conn *conn = l2cap->chan.chan.conn; + struct bt_l2cap_chan *chan; + struct bt_l2cap_conn_rsp *rsp = (void *)buf->data; + uint16_t dcid; + uint16_t scid; + uint16_t result; + uint16_t status; + struct bt_l2cap_br_chan *br_chan; + + if (buf->len < sizeof(*rsp)) + { + LOG_ERR("Too small L2CAP conn rsp packet size"); + return; + } + + dcid = sys_le16_to_cpu(rsp->dcid); + scid = sys_le16_to_cpu(rsp->scid); + result = sys_le16_to_cpu(rsp->result); + status = sys_le16_to_cpu(rsp->status); + + LOG_DBG("dcid 0x%04x scid 0x%04x result %u status %u", dcid, scid, result, + status); + + chan = bt_l2cap_br_lookup_rx_cid(conn, scid); + if (!chan) + { + LOG_ERR("No scid 0x%04x channel found", scid); + return; + } + + br_chan = BR_CHAN(chan); + if (br_chan->ident != ident) + { + LOG_WRN("ident mismatch (%u != %u)!", br_chan->ident, ident); + return; + } + + br_chan->ident = 0; + + /* Release RTX work since got the response */ + + k_work_cancel_delayable(&br_chan->rtx_work); + + if (br_chan->state != BT_L2CAP_CONNECTING) + { + LOG_DBG("Invalid channel %p state %s", chan, + bt_l2cap_chan_state_str(br_chan->state)); + return; + } + + switch (result) + { + case BT_L2CAP_BR_SUCCESS: + br_chan->ident = 0U; + BR_CHAN(chan)->tx.cid = dcid; + l2cap_br_conf(chan, true); + bt_l2cap_br_chan_set_state(chan, BT_L2CAP_CONFIG); + atomic_clear_bit(BR_CHAN(chan)->flags, L2CAP_FLAG_CONN_PENDING); + break; + case BT_L2CAP_BR_PENDING: + br_chan->ident = ident; + bt_work_reschedule(&br_chan->rtx_work, L2CAP_BR_CONN_TIMEOUT); + break; + default: + l2cap_br_chan_cleanup(chan); + break; + } +} + +int bt_l2cap_br_chan_send_cb( + struct bt_l2cap_chan *chan, struct net_buf *buf, bt_conn_tx_cb_t cb, + void *user_data) +{ + struct bt_l2cap_br_chan *br_chan; + + if (!buf || !chan) + { + return -EINVAL; + } + + br_chan = BR_CHAN(chan); + + LOG_DBG("chan %p buf %p len %zu", chan, buf, net_buf_frags_len(buf)); + + if (!chan->conn || chan->conn->state != BT_CONN_CONNECTED) + { + return -ENOTCONN; + } + + if (atomic_test_bit(chan->status, BT_L2CAP_STATUS_SHUTDOWN)) + { + return -ESHUTDOWN; + } + + if (buf->len > br_chan->tx.mtu) + { + LOG_ERR("attempt to send %u bytes on %u MTU chan", buf->len, + br_chan->tx.mtu); + return -EMSGSIZE; + } + + return bt_l2cap_br_send_cb(br_chan->chan.conn, br_chan->tx.cid, buf, cb, + user_data); +} + +int bt_l2cap_br_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf) +{ + return bt_l2cap_br_chan_send_cb(chan, buf, NULL, NULL); +} + +static struct bt_l2cap_br_chan *bt_l2cap_br_lookup_ident( + struct bt_conn *conn, uint8_t ident) +{ + struct bt_l2cap_chan *chan; + + list_for_every_container(&conn->channels, chan, node) + { + if (BR_CHAN(chan)->ident == ident) + { + return BR_CHAN(chan); + } + } + + return NULL; +} + +static void l2cap_br_reject_rsp(struct bt_l2cap_br *l2cap, + uint8_t ident, struct net_buf *buf) +{ + struct bt_conn *conn = l2cap->chan.chan.conn; + struct bt_l2cap_br_chan *chan; + + do + { + chan = bt_l2cap_br_lookup_ident(conn, ident); + if (chan != NULL) + { + l2cap_br_chan_disconn(&chan->chan); + } + } + while (chan != NULL); +} + +static void l2cap_br_echo_req(struct bt_l2cap_br *l2cap, + uint8_t ident, struct net_buf *buf) +{ + struct bt_conn *conn = l2cap->chan.chan.conn; + struct bt_l2cap_br_echo_cb *callback; + + list_for_every_container(&bt_l2cap_br_echo_cbs, callback, _node) + { + if (callback->req) + { + callback->req(conn, ident, buf); + } + } +} + +static void l2cap_br_echo_rsp(struct bt_l2cap_br *l2cap, + uint8_t ident, struct net_buf *buf) +{ + struct bt_conn *conn = l2cap->chan.chan.conn; + struct bt_l2cap_br_echo_cb *callback; + + if (ident != l2cap->chan.ident) + { + LOG_WRN("ident mismatch (%u != %u)!", l2cap->chan.ident, ident); + goto failed; + } + + list_for_every_container(&bt_l2cap_br_echo_cbs, callback, _node) + { + if (callback->rsp) + { + callback->rsp(conn, buf); + } + } + +failed: + l2cap->chan.ident = 0; + + /* Release RTX work since got the response */ + + k_work_cancel_delayable(&l2cap->chan.rtx_work); +} + +static void l2cap_br_sig_handle( + struct bt_l2cap_br *l2cap, struct bt_l2cap_sig_hdr *hdr, + struct net_buf *buf) +{ + uint16_t len; + struct net_buf_simple_state state; + + len = sys_le16_to_cpu(hdr->len); + + net_buf_simple_save(&buf->b, &state); + + switch (hdr->code) + { + case BT_L2CAP_INFO_RSP: + l2cap_br_info_rsp(l2cap, hdr->ident, buf); + break; + case BT_L2CAP_INFO_REQ: + l2cap_br_info_req(l2cap, hdr->ident, buf); + break; + case BT_L2CAP_DISCONN_REQ: + l2cap_br_disconn_req(l2cap, hdr->ident, buf); + break; + case BT_L2CAP_CONN_REQ: + l2cap_br_conn_req(l2cap, hdr->ident, buf); + break; + case BT_L2CAP_CONF_RSP: + l2cap_br_conf_rsp(l2cap, hdr->ident, len, buf); + break; + case BT_L2CAP_CONF_REQ: + l2cap_br_conf_req(l2cap, hdr->ident, len, buf); + break; + case BT_L2CAP_DISCONN_RSP: + l2cap_br_disconn_rsp(l2cap, hdr->ident, buf); + break; + case BT_L2CAP_CONN_RSP: + l2cap_br_conn_rsp(l2cap, hdr->ident, buf); + break; + case BT_L2CAP_CMD_REJECT: + l2cap_br_reject_rsp(l2cap, hdr->ident, buf); + break; + case BT_L2CAP_ECHO_REQ: + l2cap_br_echo_req(l2cap, hdr->ident, buf); + break; + case BT_L2CAP_ECHO_RSP: + l2cap_br_echo_rsp(l2cap, hdr->ident, buf); + break; + default: + LOG_WRN("Unknown/Unsupported L2CAP PDU code 0x%02x", hdr->code); + l2cap_br_send_reject(l2cap->chan.chan.conn, hdr->ident, + BT_L2CAP_REJ_NOT_UNDERSTOOD, NULL, 0); + break; + } + + net_buf_simple_restore(&buf->b, &state); + (void)net_buf_pull_mem(buf, len); +} + +static int l2cap_br_recv(struct bt_l2cap_chan *chan, struct net_buf *buf) +{ + struct bt_l2cap_br *l2cap = CONTAINER_OF(chan, struct bt_l2cap_br, + chan.chan); + struct bt_l2cap_sig_hdr *hdr; + uint8_t ident = 0; + uint16_t len; + + while (buf->len > 0) + { + if (buf->len < sizeof(*hdr)) + { + LOG_ERR("Too small L2CAP signaling PDU"); + goto reject; + } + + hdr = net_buf_pull_mem(buf, sizeof(*hdr)); + len = sys_le16_to_cpu(hdr->len); + + LOG_DBG("Signaling code 0x%02x ident %u len %u", hdr->code, hdr->ident, + len); + + if (buf->len < len) + { + LOG_ERR("L2CAP length is short (%u < %u)", buf->len, len); + goto reject; + } + + if (!hdr->ident) + { + LOG_ERR("Invalid ident value in L2CAP PDU"); + (void)net_buf_pull_mem(buf, len); + continue; + } + + if (ident == 0) + { + LOG_DBG("Save identifier of the first request in the L2CAP " + "packet."); + ident = hdr->ident; + } + + l2cap_br_sig_handle(l2cap, hdr, buf); + } + + return 0; + +reject: + if (ident != 0) + { + l2cap_br_send_reject(chan->conn, ident, BT_L2CAP_REJ_NOT_UNDERSTOOD, + NULL, 0); + } + + return 0; +} + +static void l2cap_br_conn_pend(struct bt_l2cap_chan *chan, uint8_t status) +{ + struct net_buf *buf; + struct bt_l2cap_sig_hdr *hdr; + struct bt_l2cap_conn_req *req; + + if (BR_CHAN(chan)->state != BT_L2CAP_CONNECTING) + { + return; + } + + LOG_DBG("chan %p status 0x%02x encr 0x%02x", chan, status, + chan->conn->encrypt); + + if (status) + { + /* Security procedure status is non-zero so respond with + * security violation only as channel acceptor. + */ + + l2cap_br_conn_req_reply(chan, BT_L2CAP_BR_ERR_SEC_BLOCK); + + /* Release channel allocated to outgoing connection request */ + + if (atomic_test_bit(BR_CHAN(chan)->flags, L2CAP_FLAG_CONN_PENDING)) + { + l2cap_br_chan_cleanup(chan); + } + + return; + } + + if (!chan->conn->encrypt) + { + return; + } + + /* For incoming connection state send confirming outstanding + * response and initiate configuration request. + */ + + if (l2cap_br_conn_req_reply(chan, BT_L2CAP_BR_SUCCESS) == 0) + { + bt_l2cap_br_chan_set_state(chan, BT_L2CAP_CONFIG); + /* Initialize config request since remote needs to know + * local MTU segmentation. + */ + + l2cap_br_conf(chan, true); + } + else if (atomic_test_and_clear_bit(BR_CHAN(chan)->flags, + L2CAP_FLAG_CONN_PENDING)) + { + buf = l2cap_br_create_pdu(&br_sig_pool, ((clock_t)-1)); + if (buf == NULL) + { + l2cap_br_chan_disconn(chan); + return; + } + + hdr = net_buf_add(buf, sizeof(*hdr)); + hdr->code = BT_L2CAP_CONN_REQ; + hdr->ident = l2cap_br_get_ident(); + hdr->len = sys_cpu_to_le16(sizeof(*req)); + + req = net_buf_add(buf, sizeof(*req)); + req->psm = sys_cpu_to_le16(BR_CHAN(chan)->psm); + req->scid = sys_cpu_to_le16(BR_CHAN(chan)->rx.cid); + + /* Set the ident for the signaling request */ + + BR_CHAN(chan)->ident = hdr->ident; + + l2cap_br_chan_send_req(BR_CHAN(chan), buf, L2CAP_BR_CONN_TIMEOUT); + } +} + +void l2cap_br_encrypt_change(struct bt_conn *conn, uint8_t hci_status) +{ + struct bt_l2cap_chan *chan; + + list_for_every_container(&conn->channels, chan, node) + { + l2cap_br_conn_pend(chan, hci_status); + + if (chan->conn == NULL) + { + LOG_WRN("Invalid ACL conn for chan %p", chan); + continue; + } + + if (chan->ops && chan->ops->encrypt_change) + { + chan->ops->encrypt_change(chan, hci_status); + } + } +} + +static void check_fixed_channel(struct bt_l2cap_chan *chan) +{ + struct bt_l2cap_br_chan *br_chan = BR_CHAN(chan); + + if (br_chan->rx.cid < L2CAP_BR_CID_DYN_START) + { + connect_fixed_channel(br_chan); + } +} + +#if defined(CONFIG_BT_L2CAP_RET_FC) +static bool bt_l2cap_br_check_tx_seq_out_of_sequence( + struct bt_l2cap_br_chan *br_chan, uint16_t tx_seq) +{ + uint16_t outstanding_frames; + uint16_t tx_frames; + + outstanding_frames = + (br_chan->buffer_seq + + br_chan->rx.max_window) - br_chan->expected_tx_seq; + tx_frames = (uint16_t)(tx_seq - br_chan->expected_tx_seq); + + if ((br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_ERET) || + (br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_STREAM)) + { + if (br_chan->tx.extended_control) + { + outstanding_frames = + outstanding_frames % BT_L2CAP_EXT_CONTROL_SEQ_MAX; + tx_frames = tx_frames % BT_L2CAP_EXT_CONTROL_SEQ_MAX; + } + else + { + outstanding_frames = outstanding_frames % BT_L2CAP_CONTROL_SEQ_MAX; + tx_frames = tx_frames % BT_L2CAP_CONTROL_SEQ_MAX; + } + } + else if ((br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_RET) || + (br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_FC)) + { + outstanding_frames = outstanding_frames % BT_L2CAP_CONTROL_SEQ_MAX; + tx_frames = tx_frames % BT_L2CAP_CONTROL_SEQ_MAX; + } + + if (tx_frames && (tx_frames < outstanding_frames)) + { + return true; + } + + return false; +} + +static bool bt_l2cap_br_check_tx_seq_duplicated( + struct bt_l2cap_br_chan *br_chan, uint16_t tx_seq) +{ + uint16_t outstanding_frames; + uint16_t tx_frames; + + outstanding_frames = + (uint16_t)(br_chan->expected_tx_seq - br_chan->buffer_seq - 1); + tx_frames = (uint16_t)(tx_seq - br_chan->buffer_seq); + + if ((br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_ERET) || + (br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_STREAM)) + { + if (br_chan->tx.extended_control) + { + outstanding_frames = + outstanding_frames % BT_L2CAP_EXT_CONTROL_SEQ_MAX; + tx_frames = tx_frames % BT_L2CAP_EXT_CONTROL_SEQ_MAX; + } + else + { + outstanding_frames = outstanding_frames % BT_L2CAP_CONTROL_SEQ_MAX; + tx_frames = tx_frames % BT_L2CAP_CONTROL_SEQ_MAX; + } + } + else if ((br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_RET) || + (br_chan->tx.mode == BT_L2CAP_BR_LINK_MODE_FC)) + { + outstanding_frames = outstanding_frames % BT_L2CAP_CONTROL_SEQ_MAX; + tx_frames = tx_frames % BT_L2CAP_CONTROL_SEQ_MAX; + } + + if (tx_frames <= outstanding_frames) + { + return true; + } + + return false; +} + +static void bt_l2cap_br_update_srej(struct bt_l2cap_br_chan *br_chan, + uint16_t req_seq) +{ + if (bt_l2cap_br_check_req_seq_valid(br_chan, req_seq)) + { + struct bt_l2cap_br_window *tx_win; + struct bt_l2cap_br_window *next; + + list_for_every_container_safe(&br_chan->_pdu_outstanding, tx_win, next, + node) + { + if (tx_win->tx_seq == req_seq) + { + tx_win->srej = true; + break; + } + } + } + else + { + LOG_WRN("Invalid req seq %d received on %p", req_seq, br_chan); + /* The L2CAP entity shall close the channel as a consequence + * of an ReqSeq Sequence error. + */ + + bt_l2cap_br_chan_disconnect(&br_chan->chan); + } + + /* Append channel to list if it still has data */ + + if (chan_has_data(br_chan)) + { + LOG_DBG("chan %p ready", br_chan); + raise_data_ready(br_chan); + } +} + +static void l2cap_br_retransmit_i_frames(struct bt_l2cap_br_chan *br_chan) +{ + struct bt_l2cap_br_window *tx_win; + struct bt_l2cap_br_window *next; + + if (atomic_test_bit(br_chan->flags, L2CAP_FLAG_RET_I_FRAMES)) + { + LOG_WRN("Retransmit-I-frames is ongoing"); + return; + } + + list_for_every_container_safe(&br_chan->_pdu_outstanding, tx_win, next, + node) + { + tx_win->retransmit = true; + } + + if (list_peek_head(&br_chan->_pdu_outstanding) != NULL) + { + atomic_set_bit(br_chan->flags, L2CAP_FLAG_RET_I_FRAMES); + } +} + +static void bt_l2cap_br_update_r(struct bt_l2cap_br_chan *br_chan, uint8_t r) +{ + if (br_chan->rx.mode != BT_L2CAP_BR_LINK_MODE_RET) + { + LOG_DBG("Only Support retransmission mode"); + return; + } + + if (!r) + { + if (!atomic_test_and_clear_bit(br_chan->flags, L2CAP_FLAG_REMOTE_BUSY)) + { + /* R bit without any change */ + + return; + } + + /* R bit is changed from 1 -> 0 */ + + if (bt_l2cap_br_get_outstanding_count(br_chan)) + { + /* If any unacknowledged I-frames have been sent then + * RetransmissionTimer shall be restarted. + */ + + l2cap_br_start_timer(br_chan, BT_L2CAP_BR_TIMER_RET, true); + } + else + { + /* If the RetransmissionTimer is not running and the MonitorTimer + * is not running, then start the MonitorTimer. + */ + + l2cap_br_start_timer(br_chan, BT_L2CAP_BR_TIMER_MONITOR, false); + } + + /* Retransmit I-frames */ + + l2cap_br_retransmit_i_frames(br_chan); + } + else + { + if (atomic_test_and_set_bit(br_chan->flags, L2CAP_FLAG_REMOTE_BUSY)) + { + /* R bit without any change */ + + return; + } + + /* R bit is changed from 0 -> 1 */ + + l2cap_br_start_timer(br_chan, BT_L2CAP_BR_TIMER_MONITOR, false); + } + + l2cap_br_send_s_frame(br_chan); +} + +static int bt_l2cap_br_update_f(struct bt_l2cap_br_chan *br_chan, uint8_t f) +{ + if (br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_STREAM) + { + /* Ignore f bit if the mode is streaming. */ + + return 0; + } + + if (f) + { + if (!atomic_test_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_P_CHANGED)) + { + if (!atomic_test_and_clear_bit(br_chan->flags, + L2CAP_FLAG_SEND_FRAME_P)) + { + LOG_WRN("Unexpected F flag set on %p", br_chan); + return -EINVAL; + } + + if (bt_l2cap_br_get_outstanding_count(br_chan)) + { + /* If any unacknowledged I-frames have been sent then + * RetransmissionTimer shall be restarted. + */ + + l2cap_br_start_timer(br_chan, BT_L2CAP_BR_TIMER_RET, true); + } + else + { + /* If the RetransmissionTimer is not running and the + * MonitorTimer is not running, then start the MonitorTimer. + */ + + l2cap_br_start_timer(br_chan, BT_L2CAP_BR_TIMER_MONITOR, + false); + } + } + + /* Append channel to list if it still has data */ + + if (chan_has_data(br_chan)) + { + LOG_DBG("chan %p ready", br_chan); + raise_data_ready(br_chan); + } + } + + return 0; +} + +static void l2cap_br_stop_retransmit_i_frames( + struct bt_l2cap_br_chan *br_chan) +{ + struct bt_l2cap_br_window *tx_win; + struct bt_l2cap_br_window *next; + + if (!atomic_test_and_clear_bit(br_chan->flags, L2CAP_FLAG_RET_I_FRAMES)) + { + LOG_WRN("Retransmit-I-frames was not started"); + } + + list_for_every_container_safe(&br_chan->_pdu_outstanding, tx_win, next, + node) + { + tx_win->retransmit = false; + } +} + +static void bt_l2cap_br_ret_fc_s_recv(struct bt_l2cap_br_chan *br_chan, + struct net_buf *buf) +{ + uint16_t control; + uint32_t ext_control; + uint16_t req_seq; + uint8_t r = 0; + uint8_t s; + uint8_t f = 0; + uint8_t p = 0; + bool remote_busy = false; + int err; + + if (br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_STREAM) + { + /* Ignore S-frame if the mode is streaming. */ + + return; + } + + if ((br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_ERET) || + (br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_STREAM)) + { + if (br_chan->rx.extended_control) + { + ext_control = net_buf_pull_le32(buf); + req_seq = BT_L2CAP_S_FRAME_EXT_CONTROL_GET_REQ_SEQ(ext_control); + f = BT_L2CAP_S_FRAME_EXT_CONTROL_GET_F(ext_control); + s = BT_L2CAP_S_FRAME_EXT_CONTROL_GET_S(ext_control); + p = BT_L2CAP_S_FRAME_EXT_CONTROL_GET_P(ext_control); + } + else + { + control = net_buf_pull_le16(buf); + req_seq = BT_L2CAP_S_FRAME_ENH_CONTROL_GET_REQ_SEQ(control); + f = BT_L2CAP_S_FRAME_ENH_CONTROL_GET_F(control); + s = BT_L2CAP_S_FRAME_ENH_CONTROL_GET_S(control); + p = BT_L2CAP_S_FRAME_ENH_CONTROL_GET_P(control); + } + + err = bt_l2cap_br_update_f(br_chan, f); + if (err) + { + /* Discard the S-frame with invalid f bit */ + + return; + } + } + else + { + control = net_buf_pull_le16(buf); + req_seq = BT_L2CAP_S_FRAME_STD_CONTROL_GET_REQ_SEQ(control); + s = BT_L2CAP_S_FRAME_STD_CONTROL_GET_S(control); + r = BT_L2CAP_S_FRAME_STD_CONTROL_GET_R(control); + + bt_l2cap_br_update_r(br_chan, r); + } + + switch (s) + { + case BT_L2CAP_CONTROL_S_RR: + if (p) + { + atomic_set_bit(br_chan->flags, L2CAP_FLAG_RECV_FRAME_P); + } + else + { + if (!atomic_test_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_REJ)) + { + atomic_set_bit(br_chan->flags, L2CAP_FLAG_NEW_I_FRAME); + } + else if (f) + { + atomic_set_bit(br_chan->flags, L2CAP_FLAG_NEW_I_FRAME); + } + } + + bt_l2cap_br_update_req_seq(br_chan, req_seq, false); + + if (br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_RET) + { + break; + } + + remote_busy = atomic_test_and_clear_bit(br_chan->flags, + L2CAP_FLAG_REMOTE_BUSY); + + if (f && !atomic_test_and_clear_bit(br_chan->flags, + L2CAP_FLAG_REJ_ACTIONED)) + { + /* Retransmit I-frames */ + + l2cap_br_retransmit_i_frames(br_chan); + } + + if (p) + { + /* Send IorRRorRNR(F=1) */ + + atomic_set_bit(br_chan->flags, L2CAP_FLAG_NEW_I_FRAME); + l2cap_br_send_s_frame(br_chan); + + if (remote_busy && bt_l2cap_br_get_outstanding_count(br_chan)) + { + /* Start-RetransTimer */ + + l2cap_br_start_timer(br_chan, BT_L2CAP_BR_TIMER_RET, true); + } + } + break; + case BT_L2CAP_CONTROL_S_REJ: + if (br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_FC) + { + /* Unsupported */ + + LOG_DBG("Ignore REJ frame"); + break; + } + + if (br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_RET) + { + bt_l2cap_br_update_req_seq(br_chan, req_seq, true); + break; + } + + atomic_clear_bit(br_chan->flags, L2CAP_FLAG_REMOTE_BUSY); + atomic_set_bit(br_chan->flags, L2CAP_FLAG_NEW_I_FRAME); + + if (f == 0) + { + /* Retransmit I-frames */ + + l2cap_br_retransmit_i_frames(br_chan); + if (atomic_test_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_P)) + { + atomic_set_bit(br_chan->flags, L2CAP_FLAG_REJ_ACTIONED); + } + } + + if (f && !atomic_test_and_clear_bit(br_chan->flags, + L2CAP_FLAG_REJ_ACTIONED)) + { + /* Retransmit I-frames */ + + l2cap_br_retransmit_i_frames(br_chan); + } + + bt_l2cap_br_update_req_seq(br_chan, req_seq, true); + /* Do not change br_chan->next_tx_seq to req_seq. Because the + * TxSeq are saved in tx_win. + */ + + break; + case BT_L2CAP_CONTROL_S_RNR: + if (br_chan->rx.mode != BT_L2CAP_BR_LINK_MODE_ERET) + { + break; + } + + LOG_WRN("RNR p=%d f=%d on chan %p", p, f, br_chan); + if (p) + { + atomic_set_bit(br_chan->flags, L2CAP_FLAG_RECV_FRAME_P); + } + + atomic_set_bit(br_chan->flags, L2CAP_FLAG_REMOTE_BUSY); + bt_l2cap_br_update_req_seq(br_chan, req_seq, false); + + if (!p) + { + l2cap_br_start_timer(br_chan, BT_L2CAP_BR_TIMER_MONITOR, true); + } + + if (!atomic_test_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_REJ)) + { + l2cap_br_start_timer(br_chan, BT_L2CAP_BR_TIMER_MONITOR, true); + atomic_clear_bit(br_chan->flags, L2CAP_FLAG_RET_I_FRAME); + l2cap_br_stop_retransmit_i_frames(br_chan); + } + + if ((atomic_test_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_REJ) && + !atomic_test_bit(br_chan->flags, L2CAP_FLAG_LOCAL_BUSY)) || + (p != 0)) + { + /* Send RRorRNR (F=1) or Send RR (F=0) */ + + l2cap_br_send_s_frame(br_chan); + } + break; + case BT_L2CAP_CONTROL_S_SREJ: + if (br_chan->rx.mode != BT_L2CAP_BR_LINK_MODE_ERET) + { + break; + } + + bt_l2cap_br_update_srej(br_chan, req_seq); + + if (p) + { + atomic_set_bit(br_chan->flags, L2CAP_FLAG_RECV_FRAME_P); + atomic_set_bit(br_chan->flags, L2CAP_FLAG_NEW_I_FRAME); + } + + atomic_clear_bit(br_chan->flags, L2CAP_FLAG_REMOTE_BUSY); + + if (f) + { + if (atomic_test_and_clear_bit(br_chan->flags, + L2CAP_FLAG_SREJ_ACTIONED) && + (br_chan->srej_save_req_seq == req_seq)) + { + /* Clear flag L2CAP_FLAG_SREJ_ACTIONED */ + } + else + { + /* Retransmit-Requested-I-frame */ + + atomic_set_bit(br_chan->flags, L2CAP_FLAG_RET_REQ_I_FRAME); + } + } + else + { + atomic_set_bit(br_chan->flags, L2CAP_FLAG_RET_REQ_I_FRAME); + if (atomic_test_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_P)) + { + atomic_set_bit(br_chan->flags, L2CAP_FLAG_SREJ_ACTIONED); + br_chan->srej_save_req_seq = req_seq; + } + } + + if (p) + { + bt_l2cap_br_update_req_seq(br_chan, req_seq, false); + } + break; + } +} + +#if defined(CONFIG_BT_L2CAP_SEG_RECV) +static void bt_l2cap_br_recv_seg_direct( + struct bt_l2cap_br_chan *br_chan, struct net_buf *seg, uint8_t sar) +{ + uint16_t seg_offset = 0; + uint16_t sdu_remaining = 0; + + switch (sar) + { + case BT_L2CAP_CONTROL_SAR_UNSEG: + case BT_L2CAP_CONTROL_SAR_START: + if (sar == BT_L2CAP_CONTROL_SAR_START) + { + br_chan->_sdu_len = net_buf_pull_le16(seg); + } + else + { + br_chan->_sdu_len = seg->len; + } + + if (br_chan->_sdu_len > br_chan->rx.mtu) + { + LOG_WRN("SDU exceeds MTU"); + goto failed; + } + + if (br_chan->_sdu_len < seg->len) + { + LOG_WRN("Short data packet %u < %u", br_chan->_sdu_len, + seg->len); + goto failed; + } + + br_chan->_sdu_len_done = seg->len; + break; + case BT_L2CAP_CONTROL_SAR_END: + case BT_L2CAP_CONTROL_SAR_CONTI: + seg_offset = br_chan->_sdu_len_done; + sdu_remaining = br_chan->_sdu_len - br_chan->_sdu_len_done; + + br_chan->_sdu_len_done += seg->len; + + if (sdu_remaining < seg->len) + { + LOG_WRN("L2CAP RX PDU total exceeds SDU"); + goto failed; + } + + if ((sar == BT_L2CAP_CONTROL_SAR_END) && + (br_chan->_sdu_len_done < br_chan->_sdu_len)) + { + LOG_WRN("Short data packet %u < %u", br_chan->_sdu_len_done, + br_chan->_sdu_len); + goto failed; + } + break; + } + + /* Tail call. */ + + br_chan->chan.ops->seg_recv(&br_chan->chan, br_chan->_sdu_len, seg_offset, + &seg->b); + + return; + +failed: + br_chan->_sdu_len = 0; + br_chan->_sdu_len_done = 0; + bt_l2cap_chan_disconnect(&br_chan->chan); +} +#endif /* CONFIG_BT_L2CAP_SEG_RECV */ + +static struct net_buf *l2cap_br_alloc_frag(int32_t timeout, void *user_data) +{ + struct bt_l2cap_br_chan *chan = user_data; + struct net_buf *frag = NULL; + + frag = chan->chan.ops->alloc_buf(&chan->chan); + if (!frag) + { + return NULL; + } + + LOG_DBG("frag %p tailroom %zu", frag, net_buf_tailroom(frag)); + + return frag; +} + +static int bt_l2cap_br_recv_seg(struct bt_l2cap_br_chan *br_chan, + struct net_buf *seg, uint8_t sar) +{ + uint16_t len; + struct net_buf *buf; + + LOG_DBG("seg on chan %p with len %zu", br_chan, seg->len); + + if ((sar == BT_L2CAP_CONTROL_SAR_UNSEG) || + (sar == BT_L2CAP_CONTROL_SAR_START)) + { + if (br_chan->_sdu) + { + LOG_ERR("Last SDU is not done"); + net_buf_drop(&br_chan->_sdu); + bt_l2cap_chan_disconnect(&br_chan->chan); + return -ESHUTDOWN; + } + + br_chan->_sdu = br_chan->chan.ops->alloc_buf(&br_chan->chan); + if (!br_chan->_sdu) + { + LOG_WRN("Unable to allocate buffer for SDU"); + return -ENOBUFS; + } + + if (sar == BT_L2CAP_CONTROL_SAR_UNSEG) + { + br_chan->_sdu_len = seg->len; + } + else + { + br_chan->_sdu_len = net_buf_pull_le16(seg); + } + + if (br_chan->_sdu_len > br_chan->rx.mtu) + { + LOG_WRN("SDU exceeds MTU"); + net_buf_drop(&br_chan->_sdu); + bt_l2cap_chan_disconnect(&br_chan->chan); + return -ESHUTDOWN; + } + } + + if (!br_chan->_sdu) + { + LOG_ERR("Insufficient memory for SDU"); + if ((br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_FC) || + (br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_STREAM)) + { + return -ENOBUFS; + } + + bt_l2cap_chan_disconnect(&br_chan->chan); + return -ESHUTDOWN; + } + + if ((net_buf_frags_len(br_chan->_sdu) + seg->len) > br_chan->_sdu_len) + { + LOG_ERR("SDU length mismatch"); + net_buf_drop(&br_chan->_sdu); + bt_l2cap_chan_disconnect(&br_chan->chan); + return -ESHUTDOWN; + } + + /* Append received segment to SDU */ + + len = net_buf_append_bytes(br_chan->_sdu, seg->len, seg->data, 0, + l2cap_br_alloc_frag, br_chan); + if (len != seg->len) + { + LOG_ERR("Unable to store SDU"); + net_buf_drop(&br_chan->_sdu); + bt_l2cap_chan_disconnect(&br_chan->chan); + return -ESHUTDOWN; + } + + LOG_DBG("chan %p len %zu / %zu", br_chan, net_buf_frags_len(br_chan->_sdu), + br_chan->_sdu_len); + + if ((sar == BT_L2CAP_CONTROL_SAR_UNSEG) || + (sar == BT_L2CAP_CONTROL_SAR_END)) + { + if (net_buf_frags_len(br_chan->_sdu) < br_chan->_sdu_len) + { + LOG_ERR("SDU length mismatch"); + net_buf_drop(&br_chan->_sdu); + bt_l2cap_chan_disconnect(&br_chan->chan); + return -ESHUTDOWN; + } + + buf = net_buf_take(&br_chan->_sdu); + br_chan->_sdu_len = 0; + + /* Receiving complete SDU, notify channel and reset SDU buf */ + + int err; + + err = br_chan->chan.ops->recv(&br_chan->chan, buf); + if (err < 0) + { + if (err != -EINPROGRESS) + { + LOG_ERR("err %d", err); + bt_l2cap_chan_disconnect(&br_chan->chan); + net_buf_unref(buf); + return -ESHUTDOWN; + } + + /* If the buf in progress, it will be freed by upper layer. */ + + return err; + } + + net_buf_unref(buf); + } + + return 0; +} + +static void bt_l2cap_br_update_expected_tx_seq( + struct bt_l2cap_br_chan *br_chan, uint16_t seq) +{ + enum l2cap_br_timer_type type; + + if (br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_STREAM) + { + /* Ignore expected_tx_seq if the mode is streaming. */ + + return; + } + + if ((br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_ERET) || + (br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_STREAM)) + { + if (br_chan->rx.extended_control) + { + seq = seq % BT_L2CAP_EXT_CONTROL_SEQ_MAX; + } + else + { + seq = seq % BT_L2CAP_CONTROL_SEQ_MAX; + } + } + else if ((br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_RET) || + (br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_FC)) + { + seq = seq % BT_L2CAP_CONTROL_SEQ_MAX; + } + + /* Restart monitor timer if it is active */ + + if (atomic_test_bit(br_chan->flags, L2CAP_FLAG_RET_TIMER)) + { + type = BT_L2CAP_BR_TIMER_RET; + } + else + { + type = BT_L2CAP_BR_TIMER_MONITOR; + } + + l2cap_br_start_timer(br_chan, type, true); + + /* Currently, buffer seq is unsupported. */ + + br_chan->buffer_seq = br_chan->expected_tx_seq; + + br_chan->expected_tx_seq = seq; + + /* Send S-Frame */ + + l2cap_br_send_s_frame(br_chan); +} + +static void bt_l2cap_br_rej_exception(struct bt_l2cap_br_chan *br_chan) +{ + if (atomic_test_and_set_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_REJ)) + { + return; + } + + LOG_DBG("Set REJ Exception received flag"); + atomic_set_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_REJ_CHANGED); + l2cap_br_send_s_frame(br_chan); +} + +static void bt_l2cap_br_ret_fc_i_recv(struct bt_l2cap_br_chan *br_chan, + struct net_buf *buf) +{ + uint16_t control; + uint32_t ext_control; + uint16_t req_seq; + uint16_t tx_seq; + uint8_t r = 0; + uint8_t sar = 0; + uint8_t f = 0; + bool discard = false; + bool expected_tx_seq = false; + int err; + + if ((br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_ERET) || + (br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_STREAM)) + { + if (br_chan->rx.extended_control) + { + ext_control = net_buf_pull_le32(buf); + req_seq = BT_L2CAP_I_FRAME_EXT_CONTROL_GET_REQ_SEQ(ext_control); + f = BT_L2CAP_I_FRAME_EXT_CONTROL_GET_F(ext_control); + sar = BT_L2CAP_I_FRAME_EXT_CONTROL_GET_SAR(ext_control); + tx_seq = BT_L2CAP_I_FRAME_EXT_CONTROL_GET_TX_SEQ(ext_control); + } + else + { + control = net_buf_pull_le16(buf); + req_seq = BT_L2CAP_I_FRAME_ENH_CONTROL_GET_REQ_SEQ(control); + f = BT_L2CAP_I_FRAME_ENH_CONTROL_GET_F(control); + sar = BT_L2CAP_I_FRAME_ENH_CONTROL_GET_SAR(control); + tx_seq = BT_L2CAP_I_FRAME_ENH_CONTROL_GET_TX_SEQ(control); + } + + err = bt_l2cap_br_update_f(br_chan, f); + if (err) + { + /* Invalid F bit. Ignore the I-frame. */ + + return; + } + } + else + { + control = net_buf_pull_le16(buf); + req_seq = BT_L2CAP_I_FRAME_STD_CONTROL_GET_REQ_SEQ(control); + sar = BT_L2CAP_I_FRAME_STD_CONTROL_GET_SAR(control); + r = BT_L2CAP_I_FRAME_STD_CONTROL_GET_R(control); + tx_seq = BT_L2CAP_I_FRAME_STD_CONTROL_GET_TX_SEQ(control); + bt_l2cap_br_update_r(br_chan, r); + } + + if (br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_STREAM) + { + bt_l2cap_br_update_expected_tx_seq(br_chan, tx_seq + 1); + + /* Missing report */ + + goto valid_frame; + } + + /* The first valid I-frame received after an REJ was sent, with a TxSeq of + * the received I-frame equal to ReqSeq of the REJ, shall clear the REJ + * Exception condition. + */ + + if (br_chan->req_seq == tx_seq) + { + LOG_DBG("Seq %zu equals to req_seq, clears the REJ exception", tx_seq); + atomic_clear_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_REJ); + } + + if (br_chan->expected_tx_seq == tx_seq) + { + /* Valid TX seq received */ + + LOG_DBG("Valid information received seq %zu", tx_seq); + expected_tx_seq = true; + } + else + { + if (bt_l2cap_br_check_tx_seq_duplicated(br_chan, tx_seq)) + { + /* The Information field shall be discarded since it has already + * been received. + */ + + discard = true; + LOG_DBG("Duplicated Information received %zu", tx_seq); + } + else if (bt_l2cap_br_check_tx_seq_out_of_sequence(br_chan, tx_seq)) + { + /* The missing I-frame(s) are considered lost and ExpectedTXSeq is + * set equal to TxSeq+1. + */ + + LOG_DBG("Missing I-frame detected %zu", tx_seq); + if (br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_FC) + { + expected_tx_seq = true; + + /* TODO: Missing report */ + } + else + { + /* A REJ exception is triggered, and an REJ frame with + * ReqSeq equal to ExpectedTxSeq shall be sent to initiate + * recovery. The received I-frame shall be discarded. + */ + + discard = true; + LOG_DBG("Set REJ Exception"); + bt_l2cap_br_rej_exception(br_chan); + } + } + else + { + /* An invalid TxSeq value is a value that does not meet either of + * the above conditions and TxSeq is not equal to ExpectedTxSeq. An + * I-frame with an invalid TxSeq is likely to have errors in the + * control field and shall be silently discarded. + */ + + return; + } + } + + err = bt_l2cap_br_update_req_seq(br_chan, req_seq, false); + if (err) + { + return; + } + + if (discard) + { + return; + } + + if (br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_ERET) + { + if (f && !atomic_test_and_clear_bit(br_chan->flags, + L2CAP_FLAG_REJ_ACTIONED)) + { + /* Retransmit I-frames */ + + l2cap_br_retransmit_i_frames(br_chan); + + /* Send-Pending-I-frames */ + + atomic_set_bit(br_chan->flags, L2CAP_FLAG_NEW_I_FRAME); + } + } + +valid_frame: + + /* Redirect to experimental API. */ + + IF_ENABLED(CONFIG_BT_L2CAP_SEG_RECV, ( + { + if (br_chan->chan.ops->seg_recv) + { + bt_l2cap_br_recv_seg_direct(br_chan, buf, sar); + goto done; + } + } + + )) + + if (br_chan->chan.ops->alloc_buf) + { + err = bt_l2cap_br_recv_seg(br_chan, buf, sar); + if (err != -ENOBUFS) + { + goto done; + } + + if ((br_chan->rx.mode != BT_L2CAP_BR_LINK_MODE_RET) && + (br_chan->rx.mode != BT_L2CAP_BR_LINK_MODE_ERET)) + { + goto done; + } + + expected_tx_seq = false; + + if (!atomic_test_and_set_bit(br_chan->flags, L2CAP_FLAG_LOCAL_BUSY)) + { + atomic_set_bit(br_chan->flags, L2CAP_FLAG_LOCAL_BUSY_CHANGED); + l2cap_br_send_s_frame(br_chan); + } + + goto done; + } + + err = br_chan->chan.ops->recv(&br_chan->chan, buf); + if (err < 0) + { + if (err != -EINPROGRESS) + { + LOG_ERR("err %d", err); + bt_l2cap_chan_disconnect(&br_chan->chan); + } + + goto done; + } + +done: + if (expected_tx_seq) + { + bt_l2cap_br_update_expected_tx_seq(br_chan, tx_seq + 1); + } +} + +static bool bt_l2cap_br_check_valid_fcs(struct bt_l2cap_br_chan *br_chan, + struct net_buf *buf) +{ + if (br_chan->rx.mode != BT_L2CAP_BR_LINK_MODE_BASIC) + { + if (br_chan->rx.fcs == BT_L2CAP_BR_FCS_16BIT) + { + uint16_t lfcs; + uint16_t cfcs; + + lfcs = net_buf_remove_le16(buf); + cfcs = crc16_reflect(0xa001, 0, &buf->data[0], buf->len); + + if (lfcs != cfcs) + { + return false; + } + } + } + + return true; +} + +static uint16_t +bt_l2cap_br_get_required_len(struct bt_l2cap_br_chan *br_chan) +{ + uint16_t len = 0; + + if (br_chan->rx.mode != BT_L2CAP_BR_LINK_MODE_BASIC) + { + if (br_chan->rx.fcs == BT_L2CAP_BR_FCS_16BIT) + { + len += BT_L2CAP_FCS_SIZE; + } + } + + if (br_chan->rx.mode != BT_L2CAP_BR_LINK_MODE_BASIC) + { + if (br_chan->rx.extended_control) + { + len += BT_L2CAP_EXT_CONTROL_SIZE; + } + else + { + len += BT_L2CAP_STD_CONTROL_SIZE; + } + } + + return len; +} + +static void bt_l2cap_br_ret_fc_recv(struct bt_l2cap_br_chan *br_chan, + struct net_buf *buf) +{ + struct bt_l2cap_hdr *hdr; + uint16_t control; + uint32_t ext_control; + uint8_t type; + uint8_t sar = BT_L2CAP_CONTROL_SAR_UNSEG; + struct net_buf_simple_state state; + + hdr = (struct bt_l2cap_hdr *)buf->data; + + if (bt_l2cap_br_get_required_len(br_chan) > hdr->len) + { + LOG_WRN("Invalid Pdu len %u > %u", + bt_l2cap_br_get_required_len(br_chan), + hdr->len); + bt_l2cap_chan_disconnect(&br_chan->chan); + return; + } + + if (buf->len != (hdr->len + sizeof(*hdr))) + { + LOG_WRN("Invalid frame %zd != %zd with short packet on%p", buf->len, + (hdr->len + sizeof(*hdr)), br_chan); + + /* Discard the frame */ + + return; + } + + if (!bt_l2cap_br_check_valid_fcs(br_chan, buf)) + { + LOG_WRN("Invalid frame with incorrect FCS on %p", br_chan); + + /* Discard the frame */ + + return; + } + + /* Pull L2CAP Header from received frame */ + + net_buf_pull_mem(buf, sizeof(*hdr)); + + net_buf_simple_save(&buf->b, &state); + if ((br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_ERET) || + (br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_STREAM)) + { + if (br_chan->rx.extended_control) + { + ext_control = net_buf_pull_le32(buf); + type = (uint8_t)BT_L2CAP_S_FRAME_EXT_CONTROL_GET_TYPE(ext_control); + if (type == BT_L2CAP_CONTROL_TYPE_I) + { + sar = + (uint8_t)BT_L2CAP_I_FRAME_EXT_CONTROL_GET_SAR( + ext_control); + } + } + else + { + control = net_buf_pull_le16(buf); + type = (uint8_t)BT_L2CAP_S_FRAME_ENH_CONTROL_GET_TYPE(control); + if (type == BT_L2CAP_CONTROL_TYPE_I) + { + sar = (uint8_t)BT_L2CAP_I_FRAME_ENH_CONTROL_GET_SAR(control); + } + } + } + else + { + control = net_buf_pull_le16(buf); + type = (uint8_t)BT_L2CAP_S_FRAME_STD_CONTROL_GET_TYPE(control); + if (type == BT_L2CAP_CONTROL_TYPE_I) + { + sar = (uint8_t)BT_L2CAP_I_FRAME_STD_CONTROL_GET_SAR(control); + } + } + + if (sar == BT_L2CAP_CONTROL_SAR_START) + { + __maybe_unused uint16_t sdu_length; + + if (buf->len < sizeof(sdu_length)) + { + LOG_WRN("Too short data packet"); + bt_l2cap_chan_disconnect(&br_chan->chan); + return; + } + + sdu_length = net_buf_pull_le16(buf); + + LOG_DBG("New segment i-frame arrived (SDU Len %u)", sdu_length); + } + + if (buf->len > br_chan->rx.mps) + { + LOG_WRN("PDU size > MPS (%u > %u)", buf->len, br_chan->rx.mps); + bt_l2cap_chan_disconnect(&br_chan->chan); + return; + } + + net_buf_simple_restore(&buf->b, &state); + + if (type == BT_L2CAP_CONTROL_TYPE_S) + { + bt_l2cap_br_ret_fc_s_recv(br_chan, buf); + } + else + { + bt_l2cap_br_ret_fc_i_recv(br_chan, buf); + } +} +#endif /* CONFIG_BT_L2CAP_RET_FC */ + +void bt_l2cap_br_recv(struct bt_conn *conn, struct net_buf *buf) +{ + struct bt_l2cap_hdr *hdr; + struct bt_l2cap_chan *chan; + uint16_t cid; + + if (buf->len < sizeof(*hdr)) + { + LOG_ERR("Too small L2CAP PDU received"); + goto done; + } + + hdr = net_buf_pull_mem(buf, sizeof(*hdr)); + cid = sys_le16_to_cpu(hdr->cid); + + chan = bt_l2cap_br_lookup_rx_cid(conn, cid); + if (!chan) + { + LOG_WRN("Ignoring data for unknown channel ID 0x%04x", cid); + goto done; + } + + /* if data was received for fixed channel before Information + * Response we connect channel here. + */ + + check_fixed_channel(chan); + + /* If data arrives while still in the config state, the peer must have + * already received the config response and moved to the connected state, + * so there's no need to wait for config response tx callback. Complete the + * pending config response now, otherwise this packet would be dropped + * since the channel isn't marked connected yet. + */ + + if (BR_CHAN(chan)->state == BT_L2CAP_CONFIG) + { + l2cap_br_config_rsp_complete(chan); + } + + if (BR_CHAN(chan)->state < BT_L2CAP_CONNECTED) + { + LOG_ERR("Chan %p in invalid state %u, ignoring data", chan, + BR_CHAN(chan)->state); + goto done; + } + +#if defined(CONFIG_BT_L2CAP_RET_FC) + if (BR_CHAN(chan)->rx.mode != BT_L2CAP_BR_LINK_MODE_BASIC) + { + /* Add back HDR for FCS check */ + + net_buf_push(buf, sizeof(*hdr)); + bt_l2cap_br_ret_fc_recv(BR_CHAN(chan), buf); + } + else + { +#endif /* CONFIG_BT_L2CAP_RET_FC */ + chan->ops->recv(chan, buf); +#if defined(CONFIG_BT_L2CAP_RET_FC) + } +#endif /* CONFIG_BT_L2CAP_RET_FC */ + +done: + net_buf_unref(buf); +} + +int bt_l2cap_br_chan_recv_complete(struct bt_l2cap_chan *chan) +{ +#if defined(CONFIG_BT_L2CAP_RET_FC) + struct bt_l2cap_br_chan *br_chan; + + br_chan = BR_CHAN(chan); + + LOG_DBG("Receiving completed on %p", br_chan); + + if ((br_chan->rx.mode != BT_L2CAP_BR_LINK_MODE_ERET) && + (br_chan->rx.mode != BT_L2CAP_BR_LINK_MODE_RET)) + { + LOG_DBG("Unsupported mode (%u)", br_chan->rx.mode); + return 0; + } + + if (!atomic_test_and_clear_bit(br_chan->flags, L2CAP_FLAG_LOCAL_BUSY)) + { + LOG_DBG("Local is not busy"); + return 0; + } + + if (br_chan->rx.mode == BT_L2CAP_BR_LINK_MODE_RET) + { + /* Ignore local busy changed flag */ + + atomic_clear_bit(br_chan->flags, L2CAP_FLAG_LOCAL_BUSY_CHANGED); + l2cap_br_send_s_frame(br_chan); + return 0; + } + + /* ERET Mode */ + + if (atomic_test_and_clear_bit(br_chan->flags, + L2CAP_FLAG_LOCAL_BUSY_CHANGED)) + { + return 0; + } + + if (!atomic_test_and_set_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_P)) + { + /* State: XMIT + * Local Busy Clears Action: send RR(P=1) + */ + + br_chan->retry_count = 1; + l2cap_br_start_timer(br_chan, BT_L2CAP_BR_TIMER_MONITOR, true); + atomic_set_bit(br_chan->flags, L2CAP_FLAG_SEND_FRAME_P_CHANGED); + l2cap_br_send_s_frame(br_chan); + } + + return 0; +#else + return -ENOTSUP; +#endif /* CONFIG_BT_L2CAP_RET_FC */ +} + +static int l2cap_br_accept(struct bt_conn *conn, struct bt_l2cap_chan **chan) +{ + struct bt_l2cap_br *l2cap; + uint8_t index; + static const struct bt_l2cap_chan_ops ops = + { + .connected = l2cap_br_connected, + .disconnected = l2cap_br_disconnected, + .recv = l2cap_br_recv, + }; + + LOG_DBG("conn %p handle %u", conn, conn->handle); + + index = bt_conn_index(conn); + DEBUGASSERT(index < ARRAY_SIZE(bt_l2cap_br_pool)); + + l2cap = &bt_l2cap_br_pool[index]; + + if (l2cap->chan.state != BT_L2CAP_DISCONNECTED) + { + LOG_ERR("Signal chan %p is not idle (state %u)", &l2cap->chan, + l2cap->chan.state); + return -EBUSY; + } + + l2cap->chan.chan.ops = &ops; + *chan = &l2cap->chan.chan; + atomic_set(l2cap->chan.flags, 0); + return 0; +} + +BT_L2CAP_BR_CHANNEL_DEFINE(br_fixed_chan, + BT_L2CAP_CID_BR_SIG, + l2cap_br_accept); + +void bt_l2cap_br_init(void) +{ + if (IS_ENABLED(CONFIG_BT_RFCOMM)) + { + bt_rfcomm_init(); + } + + if (IS_ENABLED(CONFIG_BT_AVCTP)) + { + bt_avctp_init(); + } + + bt_sdp_init(); + + if (IS_ENABLED(CONFIG_BT_AVRCP)) + { + bt_avrcp_init(); + } + + if (IS_ENABLED(CONFIG_BT_DID)) + { + bt_did_init(); + } +} + +int bt_l2cap_br_echo_cb_register(struct bt_l2cap_br_echo_cb *cb) +{ + if (cb == NULL) + { + return -EINVAL; + } + + if (list_find(&bt_l2cap_br_echo_cbs, &cb->_node, NULL)) + { + return -EEXIST; + } + + list_add_tail(&bt_l2cap_br_echo_cbs, &cb->_node); + + return 0; +} + +int bt_l2cap_br_echo_cb_unregister(struct bt_l2cap_br_echo_cb *cb) +{ + if (cb == NULL) + { + return -EINVAL; + } + + if (!list_find_and_remove(&bt_l2cap_br_echo_cbs, &cb->_node)) + { + return -ENOENT; + } + + return 0; +} + +int bt_l2cap_br_echo_req(struct bt_conn *conn, struct net_buf *buf) +{ + struct bt_l2cap_chan *chan; + struct bt_l2cap_sig_hdr *hdr; + int err; + + if ((conn == NULL) || (buf == NULL)) + { + return -EINVAL; + } + + LOG_DBG("ACL conn %p buf %p len %u", conn, buf, buf->len); + + if (buf->ref != 1) + { + LOG_WRN("Expecting 1 ref, got %d", buf->ref); + return -EINVAL; + } + + if (buf->len >= (L2CAP_BR_MIN_MTU - sizeof(*hdr))) + { + LOG_ERR("attempt to send %u bytes on %u MTU chan", buf->len, + L2CAP_BR_MIN_MTU); + return -EMSGSIZE; + } + + if (net_buf_headroom(buf) < BT_L2CAP_BR_ECHO_REQ_RESERVE) + { + /* Call `net_buf_reserve(buf, BT_L2CAP_BR_ECHO_REQ_RESERVE)` + * when allocating buffers intended for bt_l2cap_br_echo_req(). + */ + + LOG_ERR("Not enough headroom in buf %p", buf); + return -EINVAL; + } + + chan = bt_l2cap_br_lookup_rx_cid(conn, BT_L2CAP_CID_BR_SIG); + if (chan == NULL) + { + LOG_ERR("Signaling Channel %u not found", BT_L2CAP_CID_BR_SIG); + return -ENOTCONN; + } + + if (BR_CHAN(chan)->ident) + { + LOG_ERR("Waiting for ECHO RSP"); + return -EBUSY; + } + + hdr = net_buf_push(buf, sizeof(*hdr)); + + hdr->code = BT_L2CAP_ECHO_REQ; + hdr->ident = l2cap_br_get_ident(); + hdr->len = sys_cpu_to_le16(buf->len - sizeof(*hdr)); + + /* Set the ident for the signaling request */ + + BR_CHAN(chan)->ident = hdr->ident; + + err = bt_l2cap_br_send_cb(conn, BT_L2CAP_CID_BR_SIG, buf, NULL, NULL); + if (err == 0) + { + bt_work_reschedule(&BR_CHAN(chan)->rtx_work, L2CAP_BR_ECHO_TIMEOUT); + } + + return err; +} + +int bt_l2cap_br_echo_rsp(struct bt_conn *conn, + uint8_t identifier, struct net_buf *buf) +{ + struct bt_l2cap_chan *chan; + struct bt_l2cap_sig_hdr *hdr; + + if ((conn == NULL) || (buf == NULL) || (identifier == 0)) + { + return -EINVAL; + } + + LOG_DBG("ACL conn %p buf %p len %u", conn, buf, buf->len); + + if (buf->ref != 1) + { + LOG_WRN("Expecting 1 ref, got %d", buf->ref); + return -EINVAL; + } + + if (buf->len >= (L2CAP_BR_MIN_MTU - sizeof(*hdr))) + { + LOG_ERR("attempt to send %u bytes on %u MTU chan", buf->len, + L2CAP_BR_MIN_MTU); + return -EMSGSIZE; + } + + if (net_buf_headroom(buf) < BT_L2CAP_BR_ECHO_RSP_RESERVE) + { + /* Call `net_buf_reserve(buf, BT_L2CAP_BR_ECHO_RSP_RESERVE)` + * when allocating buffers intended for bt_l2cap_br_echo_rsp(). + */ + + LOG_ERR("Not enough headroom in buf %p", buf); + return -EINVAL; + } + + chan = bt_l2cap_br_lookup_rx_cid(conn, BT_L2CAP_CID_BR_SIG); + if (chan == NULL) + { + LOG_ERR("Signaling Channel %u not found", BT_L2CAP_CID_BR_SIG); + return -ENOTCONN; + } + + hdr = net_buf_push(buf, sizeof(*hdr)); + + hdr->code = BT_L2CAP_ECHO_RSP; + hdr->ident = identifier; + hdr->len = sys_cpu_to_le16(buf->len - sizeof(*hdr)); + + return bt_l2cap_br_send_cb(conn, BT_L2CAP_CID_BR_SIG, buf, NULL, NULL); +} + +#if defined(CONFIG_BT_L2CAP_CONNLESS) + +static struct list_node br_connless_cbs = + LIST_INITIAL_VALUE(br_connless_cbs); + +int bt_l2cap_br_connless_register(struct bt_l2cap_br_connless_cb *cb) +{ + if (cb == NULL) + { + return -EINVAL; + } + + if (cb->recv == NULL) + { + LOG_ERR("Recv callback should not be NULL"); + return -EINVAL; + } + + /* PSM must be odd and lsb of upper byte must be 0 */ + + if ((cb->psm != 0) && (cb->psm & 0x0101) != 0x0001) + { + LOG_ERR("PSM must be odd and lsb of upper byte must be 0"); + return -EINVAL; + } + + if (cb->sec_level > BT_SECURITY_L4) + { + LOG_ERR("Invalid security level %u", cb->sec_level); + return -EINVAL; + } + + if (list_find(&br_connless_cbs, &cb->_node, NULL)) + { + return -EEXIST; + } + + list_add_tail(&br_connless_cbs, &cb->_node); + return 0; +} + +int bt_l2cap_br_connless_unregister(struct bt_l2cap_br_connless_cb *cb) +{ + if (cb == NULL) + { + return -EINVAL; + } + + if (!list_find_and_remove(&br_connless_cbs, &cb->_node)) + { + return -ENOENT; + } + + return 0; +} + +static uint32_t bt_l2cap_br_get_remote_features(struct bt_conn *conn) +{ + struct bt_l2cap_chan *chan_sig; + struct bt_l2cap_br *br_chan_sig; + + chan_sig = bt_l2cap_br_lookup_rx_cid(conn, BT_L2CAP_CID_BR_SIG); + if (chan_sig == NULL) + { + return (uint32_t)0U; + } + + br_chan_sig = CONTAINER_OF(chan_sig, struct bt_l2cap_br, chan.chan); + + return br_chan_sig->info_feat_mask; +} + +int bt_l2cap_br_connless_send(struct bt_conn *conn, + uint16_t psm, struct net_buf *buf) +{ + struct bt_l2cap_chan *chan; + uint32_t remote_features; + uint16_t mtu; + + if ((conn == NULL) || (buf == NULL)) + { + LOG_ERR("Invalid parameters"); + return -EINVAL; + } + + /* PSM must be odd and lsb of upper byte must be 0 */ + + if ((psm & 0x0101) != 0x0001) + { + LOG_ERR("Invalid PSM"); + return -EINVAL; + } + + LOG_DBG("ACL conn %p buf %p len %u", conn, buf, buf->len); + + if (buf->ref != 1) + { + LOG_WRN("Expecting 1 ref, got %d", buf->ref); + return -EINVAL; + } + + if (net_buf_headroom(buf) < BT_L2CAP_CONNLESS_RESERVE) + { + /* Call `net_buf_reserve(buf, BT_L2CAP_CONNLESS_RESERVE)` + * when allocating buffers intended for bt_l2cap_br_connless_send(). + */ + + LOG_ERR("Not enough headroom in buf %p", buf); + return -EINVAL; + } + + remote_features = bt_l2cap_br_get_remote_features(conn); + if ((remote_features & L2CAP_FEAT_CONNLESS_MASK) == 0) + { + LOG_ERR("Remote device does not support connectionless reception"); + return -ENOTSUP; + } + + chan = bt_l2cap_br_lookup_rx_cid(conn, BT_L2CAP_CID_CONNLESS); + if (chan == NULL) + { + LOG_ERR("Connectionless data channel %u not found", + BT_L2CAP_CID_CONNLESS); + return -ENOTCONN; + } + + mtu = BR_CHAN(chan)->tx.mtu - sizeof(psm); + if (buf->len > mtu) + { + LOG_ERR("attempt to send %u bytes on %u MTU chan", buf->len, mtu); + return -EMSGSIZE; + } + + net_buf_push_le16(buf, psm); + + return bt_l2cap_br_send_cb(conn, BT_L2CAP_CID_CONNLESS, buf, NULL, NULL); +} + +static struct bt_l2cap_br_chan bt_l2cap_br_connless_pool[CONFIG_BT_MAX_CONN]; + +static void l2cap_br_connless_connected(struct bt_l2cap_chan *chan) +{ + LOG_DBG("ch %p cid 0x%04x", BR_CHAN(chan), BR_CHAN(chan)->rx.cid); +} + +static void l2cap_br_connless_disconnected(struct bt_l2cap_chan *chan) +{ + LOG_DBG("ch %p cid 0x%04x", BR_CHAN(chan), BR_CHAN(chan)->rx.cid); +} + +static int l2cap_br_conless_recv(struct bt_l2cap_chan *chan, + struct net_buf *buf) +{ + struct bt_conn *conn; + uint16_t psm; + struct bt_l2cap_br_connless_cb *cb; + + if (buf->len < sizeof(psm)) + { + LOG_ERR("Invalid buffer length for connless receive"); + return -EINVAL; + } + + conn = chan->conn; + psm = net_buf_pull_le16(buf); + + /* Iterate through registered connless callbacks to find matching PSM */ + + list_for_every_container(&br_connless_cbs, cb, _node) + { + if ((cb->psm == 0) || (cb->psm == psm)) + { + /* Found matching PSM, call registered callback */ + + if ((conn->sec_level >= cb->sec_level) && (cb->recv != NULL)) + { + cb->recv(conn, psm, buf); + } + else + { + LOG_WRN("No matching sec level (%u < %u)", conn->sec_level, + cb->sec_level); + } + } + else + { + LOG_DBG("Ignore the received connectionless data, no matching PSM"); + } + } + + return 0; +} + +static int l2cap_br_connless_accept(struct bt_conn *conn, + struct bt_l2cap_chan **chan) +{ + struct bt_l2cap_br_chan *br_chan; + uint8_t index; + static const struct bt_l2cap_chan_ops ops = + { + .connected = l2cap_br_connless_connected, + .disconnected = l2cap_br_connless_disconnected, + .recv = l2cap_br_conless_recv, + }; + + LOG_DBG("conn %p handle %u", conn, conn->handle); + + index = bt_conn_index(conn); + DEBUGASSERT(index < ARRAY_SIZE(bt_l2cap_br_connless_pool)); + + br_chan = &bt_l2cap_br_connless_pool[index]; + + if (br_chan->state != BT_L2CAP_DISCONNECTED) + { + LOG_ERR("Connectionless chan %p is not idle (state %u)", br_chan, + br_chan->state); + return -EBUSY; + } + + br_chan->chan.ops = &ops; + br_chan->rx.mtu = BT_L2CAP_RX_MTU - BT_L2CAP_CONNLESS_SDU_HDR_SIZE; + br_chan->tx.mtu = L2CAP_BR_MIN_MTU; + *chan = &br_chan->chan; + atomic_set(br_chan->flags, 0); + return 0; +} + +BT_L2CAP_BR_CHANNEL_DEFINE(br_fixed_chan_connless, + BT_L2CAP_CID_CONNLESS, + l2cap_br_connless_accept); +#endif /* CONFIG_BT_L2CAP_CONNLESS */ diff --git a/libs/zblue/host/classic/l2cap_br_interface.h b/libs/zblue/host/classic/l2cap_br_interface.h new file mode 100644 index 0000000000000..81de3a893c85d --- /dev/null +++ b/libs/zblue/host/classic/l2cap_br_interface.h @@ -0,0 +1,99 @@ +/**************************************************************************** + * libs/zblue/host/classic/l2cap_br_interface.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* l2cap_br.c - L2CAP BREDR internal interface + * + * This is the only interface between l2cap.c and l2cap_br.c. + * + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Need a name different than bt_l2cap_fixed_chan for a different section */ + +struct bt_l2cap_br_fixed_chan +{ + uint16_t cid; + int (*accept)(struct bt_conn *conn, struct bt_l2cap_chan **chan); +}; + +#define BT_L2CAP_BR_CHANNEL_DEFINE(_name, _cid, _accept) \ + const STRUCT_SECTION_ITERABLE(bt_l2cap_br_fixed_chan, _name) = \ + { \ + .cid = _cid, \ + .accept = _accept, \ + } + +/* Initialize BR/EDR L2CAP signal layer */ + +void bt_l2cap_br_init(void); + +/* Notify BR/EDR L2CAP channels about established new ACL connection */ + +void bt_l2cap_br_connected(struct bt_conn *conn); + +/* Notify BR/EDR L2CAP channels about ACL disconnection */ + +void bt_l2cap_br_disconnected(struct bt_conn *conn); + +/* Lookup BR/EDR L2CAP channel by Receiver CID */ + +struct bt_l2cap_chan *bt_l2cap_br_lookup_rx_cid(struct bt_conn *conn, + uint16_t cid); + +/* Disconnects dynamic channel */ + +int bt_l2cap_br_chan_disconnect(struct bt_l2cap_chan *chan); + +/* Make connection to peer psm server */ + +int bt_l2cap_br_chan_connect(struct bt_conn *conn, + struct bt_l2cap_chan *chan, uint16_t psm); + +/* Send packet data to connected peer */ + +int bt_l2cap_br_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf); +int bt_l2cap_br_chan_send_cb( + struct bt_l2cap_chan *chan, struct net_buf *buf, bt_conn_tx_cb_t cb, + void *user_data); + +/* Send a single PDU over a BR channel. + * Used by e.g. SMP. + */ + +int bt_l2cap_br_send_cb( + struct bt_conn *conn, uint16_t cid, struct net_buf *buf, + bt_conn_tx_cb_t cb, void *user_data); + +/* Handle security level changed on link passing HCI status of performed + * security procedure. + */ + +void l2cap_br_encrypt_change(struct bt_conn *conn, uint8_t hci_status); + +/* Handle received data */ + +void bt_l2cap_br_recv(struct bt_conn *conn, struct net_buf *buf); + +/* Pull HCI fragments from buffers intended for `conn` */ + +struct net_buf *l2cap_br_data_pull(struct bt_conn *conn, + size_t amount, size_t *length); + +/* Find L2CAP BR channel by using specific cid on specific connection */ + +struct bt_l2cap_chan *bt_l2cap_br_lookup_tx_cid(struct bt_conn *conn, + uint16_t cid); + +/* Get remote supported fixed channels */ + +uint8_t bt_l2cap_br_get_remote_fixed_chan(struct bt_conn *conn); + +/* L2CAP data receiving complete. */ + +int bt_l2cap_br_chan_recv_complete(struct bt_l2cap_chan *chan); diff --git a/libs/zblue/host/classic/l2cap_br_internal.h b/libs/zblue/host/classic/l2cap_br_internal.h new file mode 100644 index 0000000000000..93cca67ad84bd --- /dev/null +++ b/libs/zblue/host/classic/l2cap_br_internal.h @@ -0,0 +1,467 @@ +/**************************************************************************** + * libs/zblue/host/classic/l2cap_br_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Internal APIs for Bluetooth L2CAP BR/EDR handling. + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include "l2cap_br_interface.h" + +#define BT_L2CAP_CID_BR_SIG 0x0001 +#define BT_L2CAP_CID_CONNLESS 0x0002 +#define BT_L2CAP_CID_BR_SMP 0x0007 +#define BT_L2CAP_PSM_RFCOMM 0x0003 + +struct bt_l2cap_hdr +{ + uint16_t len; + uint16_t cid; +} __packed; + +struct bt_l2cap_sig_hdr +{ + uint8_t code; + uint8_t ident; + uint16_t len; +} __packed; + +#define BT_L2CAP_REJ_NOT_UNDERSTOOD 0x0000 +#define BT_L2CAP_REJ_MTU_EXCEEDED 0x0001 +#define BT_L2CAP_REJ_INVALID_CID 0x0002 + +#define BT_L2CAP_CMD_REJECT 0x01 +struct bt_l2cap_cmd_reject +{ + uint16_t reason; + uint8_t data[]; +} __packed; + +struct bt_l2cap_cmd_reject_cid_data +{ + uint16_t scid; + uint16_t dcid; +} __packed; + +#define BT_L2CAP_CONN_REQ 0x02 +struct bt_l2cap_conn_req +{ + uint16_t psm; + uint16_t scid; +} __packed; + +/* command statuses in response */ +#define BT_L2CAP_CS_NO_INFO 0x0000 +#define BT_L2CAP_CS_AUTHEN_PEND 0x0001 + +/* valid results in conn response on BR/EDR */ +#define BT_L2CAP_BR_SUCCESS 0x0000 +#define BT_L2CAP_BR_PENDING 0x0001 +#define BT_L2CAP_BR_ERR_PSM_NOT_SUPP 0x0002 +#define BT_L2CAP_BR_ERR_SEC_BLOCK 0x0003 +#define BT_L2CAP_BR_ERR_NO_RESOURCES 0x0004 +#define BT_L2CAP_BR_ERR_INVALID_SCID 0x0006 +#define BT_L2CAP_BR_ERR_SCID_IN_USE 0x0007 + +#define BT_L2CAP_CONN_RSP 0x03 +struct bt_l2cap_conn_rsp +{ + uint16_t dcid; + uint16_t scid; + uint16_t result; + uint16_t status; +} __packed; + +#define BT_L2CAP_CONF_SUCCESS 0x0000 +#define BT_L2CAP_CONF_UNACCEPT 0x0001 +#define BT_L2CAP_CONF_REJECT 0x0002 +#define BT_L2CAP_CONF_UNKNOWN_OPT 0x0003 +#define BT_L2CAP_CONF_PENDING 0x0004 +#define BT_L2CAP_CONF_FLOW_SPEC_REJECT 0x0005 + +#define BT_L2CAP_CONF_FLAGS_C BIT(0) +#define BT_L2CAP_CONF_FLAGS_MASK BT_L2CAP_CONF_FLAGS_C + +#define BT_L2CAP_CONF_REQ 0x04 +struct bt_l2cap_conf_req +{ + uint16_t dcid; + uint16_t flags; + uint8_t data[]; +} __packed; + +#define BT_L2CAP_CONF_RSP 0x05 +struct bt_l2cap_conf_rsp +{ + uint16_t scid; + uint16_t flags; + uint16_t result; + uint8_t data[]; +} __packed; + +/* Option type used by MTU config request data */ +#define BT_L2CAP_CONF_OPT_MTU 0x01 +#define BT_L2CAP_CONF_OPT_FLUSH_TIMEOUT 0x02 +#define BT_L2CAP_CONF_OPT_QOS 0x03 +#define BT_L2CAP_CONF_OPT_RET_FC 0x04 +#define BT_L2CAP_CONF_OPT_FCS 0x05 +#define BT_L2CAP_CONF_OPT_EXT_FLOW_SPEC 0x06 +#define BT_L2CAP_CONF_OPT_EXT_WIN_SIZE 0x07 + +/* Options bits selecting most significant bit (hint) in type field */ +#define BT_L2CAP_CONF_HINT 0x80 +#define BT_L2CAP_CONF_MASK 0x7f + +struct bt_l2cap_conf_opt +{ + uint8_t type; + uint8_t len; + uint8_t data[]; +} __packed; + +struct bt_l2cap_conf_opt_mtu +{ + uint16_t mtu; +} __packed; + +struct bt_l2cap_conf_opt_flush_timeout +{ + uint16_t timeout; +} __packed; + +#define BT_L2CAP_QOS_TYPE_NO_TRAFFIC 0x00 +#define BT_L2CAP_QOS_TYPE_BEST_EFFORT 0x01 +#define BT_L2CAP_QOS_TYPE_GUARANTEED 0x02 +struct bt_l2cap_conf_opt_qos +{ + uint8_t flags; + uint8_t service_type; + uint32_t token_rate; + uint32_t token_bucket_size; + uint32_t peak_bandwidth; + uint32_t latency; + uint32_t delay_variation; +} __packed; + +struct bt_l2cap_conf_opt_ret_fc +{ + uint8_t mode; + uint8_t tx_windows_size; + uint8_t max_transmit; + uint16_t retransmission_timeout; + uint16_t monitor_timeout; + uint16_t mps; +} __packed; + +#define BT_L2CAP_FCS_TYPE_NO 0x00 +#define BT_L2CAP_FCS_TYPE_16BIT 0x01 +struct bt_l2cap_conf_opt_fcs +{ + uint8_t type; +} __packed; + +struct bt_l2cap_conf_opt_ext_flow_spec +{ + uint8_t identifier; + uint8_t service_type; + uint16_t sdu; + uint32_t sdu_inter_arrival_time; + uint32_t access_latency; + uint32_t flush_timeout; +} __packed; + +struct bt_l2cap_conf_opt_ext_win_size +{ + uint16_t max_windows_size; +} __packed; + +#define BT_L2CAP_DISCONN_REQ 0x06 +struct bt_l2cap_disconn_req +{ + uint16_t dcid; + uint16_t scid; +} __packed; + +#define BT_L2CAP_DISCONN_RSP 0x07 +struct bt_l2cap_disconn_rsp +{ + uint16_t dcid; + uint16_t scid; +} __packed; + +#define BT_L2CAP_ECHO_REQ 0x08 +struct bt_l2cap_echo_req +{ + FLEXIBLE_ARRAY_DECLARE(uint8_t, data); +} __packed; + +#define BT_L2CAP_ECHO_RSP 0x09 +struct bt_l2cap_echo_rsp +{ + FLEXIBLE_ARRAY_DECLARE(uint8_t, data); +} __packed; + +#define BT_L2CAP_INFO_CONNLESS_MTU 0x0001 +#define BT_L2CAP_INFO_FEAT_MASK 0x0002 +#define BT_L2CAP_INFO_FIXED_CHAN 0x0003 + +#define BT_L2CAP_INFO_REQ 0x0a +struct bt_l2cap_info_req +{ + uint16_t type; +} __packed; + +/* info result */ +#define BT_L2CAP_INFO_SUCCESS 0x0000 +#define BT_L2CAP_INFO_NOTSUPP 0x0001 + +#define BT_L2CAP_INFO_RSP 0x0b +struct bt_l2cap_info_rsp +{ + uint16_t type; + uint16_t result; + uint8_t data[]; +} __packed; + +/* I Frame Standard Control Field Format definition */ +#define BT_L2CAP_I_FRAME_STD_CONTROL_GET_TYPE(control) ((control) & 0x01) +#define BT_L2CAP_I_FRAME_STD_CONTROL_GET_TX_SEQ(control) (((control) >> 0x01) & 0x3f) +#define BT_L2CAP_I_FRAME_STD_CONTROL_GET_R(control) (((control) >> 0x07) & 0x01) +#define BT_L2CAP_I_FRAME_STD_CONTROL_GET_REQ_SEQ(control) (((control) >> 0x08) & 0x3f) +#define BT_L2CAP_I_FRAME_STD_CONTROL_GET_SAR(control) (((control) >> 0x0e) & 0x03) + +#define BT_L2CAP_I_FRAME_STD_CONTROL_SET(tx_seq, r, req_seq, sar) \ + ((((tx_seq) & 0x3f) << 0x01) | (((r) & 0x01) << 0x07) | (((req_seq) & 0x3f) << 0x08) | \ + (((sar) & 0x03) << 0x0e)) + +/* I Frame Enhanced Control Field Format definition */ +#define BT_L2CAP_I_FRAME_ENH_CONTROL_GET_TYPE(control) ((control) & 0x01) +#define BT_L2CAP_I_FRAME_ENH_CONTROL_GET_TX_SEQ(control) (((control) >> 0x01) & 0x3f) +#define BT_L2CAP_I_FRAME_ENH_CONTROL_GET_F(control) (((control) >> 0x07) & 0x01) +#define BT_L2CAP_I_FRAME_ENH_CONTROL_GET_REQ_SEQ(control) (((control) >> 0x08) & 0x3f) +#define BT_L2CAP_I_FRAME_ENH_CONTROL_GET_SAR(control) (((control) >> 0x0e) & 0x03) + +#define BT_L2CAP_I_FRAME_ENH_CONTROL_SET(tx_seq, f, req_seq, sar) \ + ((((tx_seq) & 0x3f) << 0x01) | (((f) & 0x01) << 0x07) | (((req_seq) & 0x3f) << 0x08) | \ + (((sar) & 0x03) << 0x0e)) + +/* I Frame Extended Control Field Format definition */ +#define BT_L2CAP_I_FRAME_EXT_CONTROL_GET_TYPE(control) ((control) & 0x01) +#define BT_L2CAP_I_FRAME_EXT_CONTROL_GET_F(control) (((control) >> 0x01) & 0x01) +#define BT_L2CAP_I_FRAME_EXT_CONTROL_GET_REQ_SEQ(control) (((control) >> 0x02) & 0x3fff) +#define BT_L2CAP_I_FRAME_EXT_CONTROL_GET_SAR(control) (((control) >> 0x10) & 0x03) +#define BT_L2CAP_I_FRAME_EXT_CONTROL_GET_TX_SEQ(control) (((control) >> 0x12) & 0x3fff) + +#define BT_L2CAP_I_FRAME_EXT_CONTROL_SET(f, tx_seq, sar, req_seq) \ + ((((f) & 0x01) << 0x01) | (((req_seq) & 0x3fff) << 0x02) | (((sar) & 0x03) << 0x10) | \ + (((tx_seq) & 0x3fff) << 0x12)) + +/* S Frame Standard Control Field Format definition */ +#define BT_L2CAP_S_FRAME_STD_CONTROL_GET_TYPE(control) ((control) & 0x01) +#define BT_L2CAP_S_FRAME_STD_CONTROL_GET_S(control) (((control) >> 0x02) & 0x03) +#define BT_L2CAP_S_FRAME_STD_CONTROL_GET_R(control) (((control) >> 0x07) & 0x01) +#define BT_L2CAP_S_FRAME_STD_CONTROL_GET_REQ_SEQ(control) (((control) >> 0x08) & 0x3f) + +#define BT_L2CAP_S_FRAME_STD_CONTROL_SET(s, r, req_seq) \ + (((1) & 0x01) | (((s) & 0x03) << 0x02) | (((r) & 0x01) << 0x07) | (((req_seq) & 0x3f) << 0x08)) + +/* S Frame Enhanced Control Field Format definition */ +#define BT_L2CAP_S_FRAME_ENH_CONTROL_GET_TYPE(control) ((control) & 0x01) +#define BT_L2CAP_S_FRAME_ENH_CONTROL_GET_S(control) (((control) >> 0x02) & 0x03) +#define BT_L2CAP_S_FRAME_ENH_CONTROL_GET_P(control) (((control) >> 0x04) & 0x01) +#define BT_L2CAP_S_FRAME_ENH_CONTROL_GET_F(control) (((control) >> 0x07) & 0x01) +#define BT_L2CAP_S_FRAME_ENH_CONTROL_GET_REQ_SEQ(control) (((control) >> 0x08) & 0x3f) + +#define BT_L2CAP_S_FRAME_ENH_CONTROL_SET(s, p, f, req_seq) \ + (((1) & 0x01) | (((s) & 0x03) << 0x02) | (((p) & 0x01) << 0x04) | (((f) & 0x01) << 0x07) | \ + (((req_seq) & 0x3f) << 0x08)) + +/* S Frame Extended Control Field Format definition */ +#define BT_L2CAP_S_FRAME_EXT_CONTROL_GET_TYPE(control) ((control) & 0x01) +#define BT_L2CAP_S_FRAME_EXT_CONTROL_GET_F(control) (((control) >> 0x01) & 0x01) +#define BT_L2CAP_S_FRAME_EXT_CONTROL_GET_REQ_SEQ(control) (((control) >> 0x02) & 0x3fff) +#define BT_L2CAP_S_FRAME_EXT_CONTROL_GET_S(control) (((control) >> 0x10) & 0x03) +#define BT_L2CAP_S_FRAME_EXT_CONTROL_GET_P(control) (((control) >> 0x12) & 0x01) + +#define BT_L2CAP_S_FRAME_EXT_CONTROL_SET(f, req_seq, s, p) \ + (((1) & 0x01) | (((f) & 0x01) << 0x01) | (((req_seq) & 0x3fff) << 0x02) | \ + (((s) & 0x03) << 0x10) | (((p) & 0x01) << 0x12)) + +#define BT_L2CAP_CONTROL_TYPE_I 0x00 +#define BT_L2CAP_CONTROL_TYPE_S 0x01 + +#define BT_L2CAP_CONTROL_SEQ_MAX 0x40 +#define BT_L2CAP_EXT_CONTROL_SEQ_MAX 0x4000 + +#define BT_L2CAP_CONTROL_SAR_UNSEG 0x00 +#define BT_L2CAP_CONTROL_SAR_START 0x01 +#define BT_L2CAP_CONTROL_SAR_END 0x02 +#define BT_L2CAP_CONTROL_SAR_CONTI 0x03 + +#define BT_L2CAP_CONTROL_S_RR 0x00 +#define BT_L2CAP_CONTROL_S_REJ 0x01 +#define BT_L2CAP_CONTROL_S_RNR 0x02 +#define BT_L2CAP_CONTROL_S_SREJ 0x03 + +#define BT_L2CAP_RT_FC_SDU_LEN_SIZE 2 + +#define BT_L2CAP_STD_CONTROL_SIZE 2 +#define BT_L2CAP_ENH_CONTROL_SIZE 2 +#define BT_L2CAP_EXT_CONTROL_SIZE 4 + +#define BT_L2CAP_FCS_SIZE 2 + +#if defined(CONFIG_BT_L2CAP_RET_FC) +/** + * + * @brief Helper to calculate L2CAP SDU header size. + * Useful for creating buffer pools. + * + * @param mtu Required BT_L2CAP_*_SDU. + * + * @return Header size of the L2CAP channel. + */ + +static inline size_t bt_l2cap_br_get_ret_fc_hdr_size( + struct bt_l2cap_br_chan *chan) +{ + if (chan->tx.mode != BT_L2CAP_BR_LINK_MODE_BASIC) + { + if (chan->tx.extended_control) + { + return BT_L2CAP_EXT_CONTROL_SIZE + BT_L2CAP_RT_FC_SDU_LEN_SIZE; + } + else + { + return BT_L2CAP_STD_CONTROL_SIZE + BT_L2CAP_RT_FC_SDU_LEN_SIZE; + } + } + + return 0; +} + +static inline size_t bt_l2cap_br_get_ret_fc_tail_size( + struct bt_l2cap_br_chan *chan) +{ + if (chan->tx.mode != BT_L2CAP_BR_LINK_MODE_BASIC) + { + if (chan->tx.fcs == BT_L2CAP_BR_FCS_16BIT) + { + return BT_L2CAP_FCS_SIZE; + } + } + + return 0; +} + +/** + * + * @brief Helper to calculate L2CAP SDU header size. + * Useful for creating buffer pools. + * + * @param chan the BR channel object point to `struct bt_l2cap_br_chan`. + * + * @return Header size of the L2CAP channel. + */ +#define BT_L2CAP_RT_FC_SDU_HDR_SIZE(chan) bt_l2cap_br_get_ret_fc_hdr_size(chan) + +/** + * + * @brief Helper to calculate L2CAP SDU tail size. + * Useful for creating buffer pools. + * + * @param chan the BR channel object point to `struct bt_l2cap_br_chan`. + * + * @return Header size of the L2CAP channel. + */ +#define BT_L2CAP_RT_FC_SDU_TAIL_SIZE(chan) bt_l2cap_br_get_ret_fc_tail_size(chan) + +/** + * + * @brief Helper to calculate needed buffer size for L2CAP SDUs. + * Useful for creating buffer pools. + * + * @param chan the BR channel object point to `struct bt_l2cap_br_chan`. + * @param mtu Required BT_L2CAP_*_SDU. + * + * @return Needed buffer size to match the requested L2CAP SDU MTU. + */ +#define BT_L2CAP_RT_FC_SDU_BUF_SIZE(chan, mtu) \ + (BT_L2CAP_BUF_SIZE(BT_L2CAP_RT_FC_SDU_HDR_SIZE((chan)) + (mtu) + \ + BT_L2CAP_RT_FC_SDU_TAIL_SIZE((chan)))) + +/** + * + * @brief Helper to calculate needed buffer size for L2CAP SDUs. + * Useful for creating buffer pools. + * + * @param mtu Required BT_L2CAP_*_SDU. + * + * @return Needed buffer size to match the requested L2CAP SDU MTU. + */ +#define BT_L2CAP_RT_FC_MAX_SDU_BUF_SIZE(mtu) \ + BT_L2CAP_BUF_SIZE((mtu) + BT_L2CAP_EXT_CONTROL_SIZE + BT_L2CAP_RT_FC_SDU_LEN_SIZE + \ + BT_L2CAP_FCS_SIZE) + +/** + * @brief Headroom needed for outgoing L2CAP PDUs if channel in one of + * following mode, including retransmission, flow control, enhance + * retransmission, and streaming. + * + * @param chan the BR channel object point to `struct bt_l2cap_br_chan`. + */ +#define BT_L2CAP_RET_FC_SDU_CHAN_SEND_RESERVE(chan) (BT_L2CAP_RT_FC_SDU_HDR_SIZE(chan)) +#endif /* CONFIG_BT_L2CAP_RET_FC */ + +#define BR_CHAN(_ch) CONTAINER_OF(_ch, struct bt_l2cap_br_chan, chan) + +/* Add channel to the connection */ + +void bt_l2cap_chan_add(struct bt_conn *conn, struct bt_l2cap_chan *chan, + bt_l2cap_chan_destroy_t destroy); + +/* Remove channel from the connection */ + +void bt_l2cap_chan_remove(struct bt_conn *conn, struct bt_l2cap_chan *chan); + +/* Delete channel */ + +void bt_l2cap_br_chan_del(struct bt_l2cap_chan *chan); + +const char *bt_l2cap_chan_state_str(bt_l2cap_chan_state_t state); + +#if defined(CONFIG_BT_L2CAP_LOG_LEVEL_DBG) +void bt_l2cap_br_chan_set_state_debug( + struct bt_l2cap_chan *chan, bt_l2cap_chan_state_t state, const char *func, + int line); +#define bt_l2cap_br_chan_set_state(_chan, _state) \ + bt_l2cap_br_chan_set_state_debug(_chan, _state, __func__, __LINE__) +#else +void bt_l2cap_br_chan_set_state(struct bt_l2cap_chan *chan, + bt_l2cap_chan_state_t state); +#endif /* CONFIG_BT_L2CAP_LOG_LEVEL_DBG */ + +/* Prepare an L2CAP PDU to be sent over a connection */ + +struct net_buf *bt_l2cap_create_pdu_timeout( + struct net_buf_pool *pool, size_t reserve, clock_t timeout); + +#define bt_l2cap_create_pdu(_pool, _reserve) \ + bt_l2cap_create_pdu_timeout(_pool, _reserve, ((clock_t)-1)) + +bt_security_t bt_l2cap_br_get_max_sec_level(void); diff --git a/libs/zblue/host/classic/map.c b/libs/zblue/host/classic/map.c new file mode 100644 index 0000000000000..478066d1ae440 --- /dev/null +++ b/libs/zblue/host/classic/map.c @@ -0,0 +1,3482 @@ +/**************************************************************************** + * libs/zblue/host/classic/map.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright 2026 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#include +#include +#include "l2cap_br_internal.h" +#include "obex_internal.h" + +#define LOG_LEVEL CONFIG_BT_MAP_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_map); + +#define MAP_TRANSPORT_TYPE_RFCOMM (0x00U) +#define MAP_TRANSPORT_TYPE_L2CAP (0x01U) + +typedef void (*mce_mas_cb_t)(struct bt_map_mce_mas *mce_mas, + uint8_t rsp_code, struct net_buf *buf); +typedef void (*mce_mns_cb_t)(struct bt_map_mce_mns *mce_mns, + bool final, struct net_buf *buf); +typedef void (*mse_mas_cb_t)(struct bt_map_mse_mas *mse_mns, + bool final, struct net_buf *buf); +typedef void (*mse_mns_cb_t)(struct bt_map_mse_mns *mse_mas, + uint8_t rsp_code, struct net_buf *buf); + +/** @brief MAP transport connection states + * + * States of the underlying transport layer (RFCOMM or L2CAP) connection. + */ + +enum __packed bt_map_transport_state +{ + /** Transport is disconnected */ + + BT_MAP_TRANSPORT_STATE_DISCONNECTED = 0, + + /** Transport is connecting */ + + BT_MAP_TRANSPORT_STATE_CONNECTING = 1, + + /** Transport is connected */ + + BT_MAP_TRANSPORT_STATE_CONNECTED = 2, + + /** Transport is disconnecting */ + + BT_MAP_TRANSPORT_STATE_DISCONNECTING = 3, +}; + +/** @brief MAP connection states + * + * States of the MAP OBEX connection layer. + */ + +enum __packed bt_map_state +{ + /** Connection is disconnected */ + + BT_MAP_STATE_DISCONNECTED = 0, + + /** Connection is being established */ + + BT_MAP_STATE_CONNECTING = 1, + + /** Connection is established */ + + BT_MAP_STATE_CONNECTED = 2, + + /** Connection is being terminated */ + + BT_MAP_STATE_DISCONNECTING = 3, +}; + +struct map_required_app_param +{ + uint8_t count; + const uint8_t *tags; +}; + +struct map_required_hdr +{ + uint8_t count; + const uint8_t *hdrs; +}; + +#define MAP_REQUIRED_APP_PARAM_LIST(...) \ + COND_CODE_1(IS_EMPTY(__VA_ARGS__), ({.count = 0, .tags = NULL}), \ + (_MAP_REQUIRED_APP_PARAM_LIST(__VA_ARGS__))) + +#define _MAP_REQUIRED_APP_PARAM_LIST(...) \ + MAP_REQUIRED_APP_PARAM(sizeof((uint8_t[]){__VA_ARGS__}), ((uint8_t[]){__VA_ARGS__})) +#define MAP_REQUIRED_APP_PARAM(_count, _tags) {.count = (_count), .tags = (const uint8_t *)(_tags)} + +#define MAP_REQUIRED_HDR_LIST(...) \ + COND_CODE_1(IS_EMPTY(__VA_ARGS__), ({.count = 0, .hdrs = NULL}), \ + (_MAP_REQUIRED_HDR_LIST(__VA_ARGS__))) +#define _MAP_REQUIRED_HDR_LIST(...) \ + MAP_REQUIRED_HDR(sizeof((uint8_t[]){__VA_ARGS__}), ((uint8_t[]){__VA_ARGS__})) +#define MAP_REQUIRED_HDR(_count, _hdrs) {.count = (_count), .hdrs = (const uint8_t *)(_hdrs)} + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static bool has_required_hdrs(struct net_buf *buf, + const struct map_required_hdr *hdr) +{ + for (uint8_t index = 0; index < hdr->count; index++) + { + if (!bt_obex_has_header(buf, hdr->hdrs[index])) + { + LOG_ERR("Not found required header %u", hdr->hdrs[index]); + return false; + } + } + + return true; +} + +static bool has_required_app_params(struct net_buf *buf, + const struct map_required_app_param *ap) +{ + for (uint8_t index = 0; index < ap->count; index++) + { + if (!bt_obex_has_app_param(buf, ap->tags[index])) + { + LOG_ERR("Not found required app param %u", ap->tags[index]); + return false; + } + } + + return true; +} + +#define SEND_EVENT_REQUIRED_HDR BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE +#define SEND_EVENT_REQUIRED_AP BT_MAP_APPL_PARAM_TAG_ID_MAS_INST_ID + +#define SET_NTF_REG_REQUIRED_HDR \ + BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE, BT_OBEX_HEADER_ID_APP_PARAM +#define SET_NTF_REG_REQUIRED_AP BT_MAP_APPL_PARAM_TAG_ID_NTF_STATUS + +#define GET_FOLDER_LISTING_REQUIRED_HDR BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE +#define GET_FOLDER_LISTING_REQUIRED_AP + +#define GET_MSG_LISTING_REQUIRED_HDR \ + BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE, BT_OBEX_HEADER_ID_NAME +#define GET_MSG_LISTING_REQUIRED_AP + +#define GET_MSG_REQUIRED_HDR \ + BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE, BT_OBEX_HEADER_ID_NAME, \ + BT_OBEX_HEADER_ID_APP_PARAM +#define GET_MSG_REQUIRED_AP BT_MAP_APPL_PARAM_TAG_ID_ATTACHMENT, BT_MAP_APPL_PARAM_TAG_ID_CHARSET + +#define SET_MSG_STATUS_REQUIRED_HDR \ + BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE, BT_OBEX_HEADER_ID_NAME, \ + BT_OBEX_HEADER_ID_APP_PARAM +#define SET_MSG_STATUS_REQUIRED_AP BT_MAP_APPL_PARAM_TAG_ID_STATUS_IND + +#define PUSH_MSG_REQUIRED_HDR \ + BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE, BT_OBEX_HEADER_ID_NAME, \ + BT_OBEX_HEADER_ID_APP_PARAM +#define PUSH_MSG_REQUIRED_AP BT_MAP_APPL_PARAM_TAG_ID_CHARSET + +#define UPDATE_INBOX_REQUIRED_HDR BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE +#define UPDATE_INBOX_REQUIRED_AP + +#define GET_MAS_INST_INFO_REQUIRED_HDR \ + BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE, BT_OBEX_HEADER_ID_APP_PARAM +#define GET_MAS_INST_INFO_REQUIRED_AP BT_MAP_APPL_PARAM_TAG_ID_MAS_INST_ID + +#define SET_OWNER_STATUS_REQUIRED_HDR \ + BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE, BT_OBEX_HEADER_ID_APP_PARAM +#define SET_OWNER_STATUS_REQUIRED_AP + +#define GET_OWNER_STATUS_REQUIRED_HDR BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE +#define GET_OWNER_STATUS_REQUIRED_AP + +#define GET_CONVO_LISTING_REQUIRED_HDR BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE +#define GET_CONVO_LISTING_REQUIRED_AP + +#define SET_NTF_FILTER_REQUIRED_HDR \ + BT_OBEX_HEADER_ID_CONN_ID, BT_OBEX_HEADER_ID_TYPE, BT_OBEX_HEADER_ID_APP_PARAM +#define SET_NTF_FILTER_REQUIRED_AP BT_MAP_APPL_PARAM_TAG_ID_NTF_FILTER_MASK + +struct map_mas_function +{ + const char *type; + bool op_get; + struct map_required_app_param ap; + struct map_required_hdr hdr; + mce_mas_cb_t (*get_mce_mas_cb)(struct bt_map_mce_mas *mce_mas); + mse_mas_cb_t (*get_mse_mas_cb)(struct bt_map_mse_mas *mse_mas); +}; + +struct map_mns_function +{ + const char *type; + bool op_get; + struct map_required_app_param ap; + struct map_required_hdr hdr; + mce_mns_cb_t (*get_mce_mns_cb)(struct bt_map_mce_mns *mce_mns); + mse_mns_cb_t (*get_mse_mns_cb)(struct bt_map_mse_mns *mse_mns); +}; + +/* Generates a paired MCE+MSE MAS callback getter function for field _name */ +#define MAP_DEFINE_MAS_CB_GETTERS(_name) \ + static mce_mas_cb_t mce_mas_get_cb_##_name(struct bt_map_mce_mas *mce_mas) \ + { \ + return mce_mas->_cb->_name; \ + } \ + static mse_mas_cb_t mse_mas_get_cb_##_name(struct bt_map_mse_mas *mse_mas) \ + { \ + return mse_mas->_cb->_name; \ + } + +/* Generates a paired MCE+MSE MNS callback getter function for field _name */ +#define MAP_DEFINE_MNS_CB_GETTERS(_name) \ + static mce_mns_cb_t mce_mns_get_cb_##_name(struct bt_map_mce_mns *mce_mns) \ + { \ + return mce_mns->_cb->_name; \ + } \ + static mse_mns_cb_t mse_mns_get_cb_##_name(struct bt_map_mse_mns *mse_mns) \ + { \ + return mse_mns->_cb->_name; \ + } + +/* Declares one entry in map_mas_functions[]. + * _op_get : bool – true for GET operations, + * false for PUT operations _NAME : + * uppercase operation token (e.g. SET_NTF_REG) _name : lowercase field name + * matching the cb struct member (e.g. set_ntf_reg) + */ +#define MAP_MAS_FUNCTION(_op_get, _NAME, _name) \ + { \ + .type = BT_MAP_HDR_TYPE_##_NAME, \ + .op_get = (_op_get), \ + .ap = MAP_REQUIRED_APP_PARAM_LIST(_NAME##_REQUIRED_AP), \ + .hdr = MAP_REQUIRED_HDR_LIST(_NAME##_REQUIRED_HDR), \ + .get_mce_mas_cb = mce_mas_get_cb_##_name, \ + .get_mse_mas_cb = mse_mas_get_cb_##_name, \ + } + +/* Declares one entry in map_mns_functions[]. + * _op_get : bool – true for GET operations, + * false for PUT operations _NAME : + * uppercase operation token (e.g. SEND_EVENT) _name : lowercase field name + * matching the cb struct member (e.g. send_event) + */ +#define MAP_MNS_FUNCTION(_op_get, _NAME, _name) \ + { \ + .type = BT_MAP_HDR_TYPE_##_NAME, \ + .op_get = (_op_get), \ + .ap = MAP_REQUIRED_APP_PARAM_LIST(_NAME##_REQUIRED_AP), \ + .hdr = MAP_REQUIRED_HDR_LIST(_NAME##_REQUIRED_HDR), \ + .get_mce_mns_cb = mce_mns_get_cb_##_name, \ + .get_mse_mns_cb = mse_mns_get_cb_##_name, \ + } + +MAP_DEFINE_MNS_CB_GETTERS(send_event) +MAP_DEFINE_MAS_CB_GETTERS(set_ntf_reg) +MAP_DEFINE_MAS_CB_GETTERS(get_folder_listing) +MAP_DEFINE_MAS_CB_GETTERS(get_msg_listing) +MAP_DEFINE_MAS_CB_GETTERS(get_msg) +MAP_DEFINE_MAS_CB_GETTERS(set_msg_status) +MAP_DEFINE_MAS_CB_GETTERS(push_msg) +MAP_DEFINE_MAS_CB_GETTERS(update_inbox) +MAP_DEFINE_MAS_CB_GETTERS(get_mas_inst_info) +MAP_DEFINE_MAS_CB_GETTERS(set_owner_status) +MAP_DEFINE_MAS_CB_GETTERS(get_owner_status) +MAP_DEFINE_MAS_CB_GETTERS(get_convo_listing) +MAP_DEFINE_MAS_CB_GETTERS(set_ntf_filter) + +static const struct map_mas_function map_mas_functions[] = +{ + MAP_MAS_FUNCTION(false, SET_NTF_REG, set_ntf_reg), + MAP_MAS_FUNCTION(true, GET_FOLDER_LISTING, get_folder_listing), + MAP_MAS_FUNCTION(true, GET_MSG_LISTING, get_msg_listing), + MAP_MAS_FUNCTION(true, GET_MSG, get_msg), + MAP_MAS_FUNCTION(false, SET_MSG_STATUS, set_msg_status), + MAP_MAS_FUNCTION(false, PUSH_MSG, push_msg), + MAP_MAS_FUNCTION(false, UPDATE_INBOX, update_inbox), + MAP_MAS_FUNCTION(true, GET_MAS_INST_INFO, get_mas_inst_info), + MAP_MAS_FUNCTION(false, SET_OWNER_STATUS, set_owner_status), + MAP_MAS_FUNCTION(true, GET_OWNER_STATUS, get_owner_status), + MAP_MAS_FUNCTION(true, GET_CONVO_LISTING, get_convo_listing), + MAP_MAS_FUNCTION(false, SET_NTF_FILTER, set_ntf_filter), +}; + +static const struct map_mns_function map_mns_functions[] = +{ + MAP_MNS_FUNCTION(false, SEND_EVENT, send_event), +}; + +static const struct bt_uuid_128 *map_mas_uuid = BT_MAP_UUID_MAS; +static const struct bt_uuid_128 *map_mns_uuid = BT_MAP_UUID_MNS; + +static uint32_t map_get_connect_id(void) +{ + static uint32_t connect_id; + + connect_id++; + + if (connect_id == 0) + { + connect_id = 1; + } + + return connect_id; +} + +static int map_check_conn_id(uint32_t id, struct net_buf *buf) +{ + uint32_t conn_id; + int err; + + err = bt_obex_get_header_conn_id(buf, &conn_id); + if (err != 0) + { + LOG_ERR("Failed to get connection ID: %d (expected: %u)", err, id); + return err; + } + + if (conn_id != id) + { + LOG_ERR("Conn id is mismatched %u != %u", conn_id, id); + return -EINVAL; + } + + return 0; +} + +static int map_check_who(const struct bt_uuid *uuid, struct net_buf *buf) +{ + uint16_t len; + const uint8_t *who; + union bt_obex_uuid obex_uuid; + int err; + + err = bt_obex_get_header_who(buf, &len, &who); + if (err != 0) + { + LOG_ERR("Failed to get who: %d", err); + return err; + } + + err = bt_obex_make_uuid(&obex_uuid, who, len); + if (err != 0) + { + LOG_ERR("Invalid UUID of who: %d", err); + return err; + } + + if (bt_uuid_cmp(uuid, &obex_uuid.uuid) != 0) + { + LOG_ERR("WHO is mismatched"); + return -EINVAL; + } + + return 0; +} + +static int map_check_target(const struct bt_uuid *uuid, struct net_buf *buf) +{ + uint16_t len; + const uint8_t *target; + union bt_obex_uuid obex_uuid; + int err; + + err = bt_obex_get_header_target(buf, &len, &target); + if (err != 0) + { + LOG_ERR("Failed to get target: %d", err); + return err; + } + + err = bt_obex_make_uuid(&obex_uuid, target, len); + if (err != 0) + { + LOG_ERR("Invalid UUID of target: %d", err); + return err; + } + + if (bt_uuid_cmp(uuid, &obex_uuid.u128.uuid) != 0) + { + LOG_ERR("Invalid UUID of target"); + return -EINVAL; + } + + return 0; +} + +#if defined(CONFIG_BT_MAP_MCE) + +static void mce_mas_clear_pending_request(struct bt_map_mce_mas *mce_mas) +{ + mce_mas->_rsp_cb = NULL; + mce_mas->_req_type = NULL; +} + +static void mce_mas_transport_connected(struct bt_conn *conn, + struct bt_goep *goep) +{ + struct bt_map_mce_mas *mce_mas = CONTAINER_OF(goep, struct bt_map_mce_mas, + goep); + + LOG_DBG("MCE MAS transport connected"); + atomic_set(&mce_mas->_transport_state, BT_MAP_TRANSPORT_STATE_CONNECTED); + + if (mce_mas->goep.v2 != NULL) + { + if (mce_mas->_cb != NULL && mce_mas->_cb->l2cap_connected != NULL) + { + mce_mas->_cb->l2cap_connected(conn, mce_mas); + } + } + else + { + if (mce_mas->_cb != NULL && mce_mas->_cb->rfcomm_connected != NULL) + { + mce_mas->_cb->rfcomm_connected(conn, mce_mas); + } + } +} + +static void mce_mas_transport_disconnected(struct bt_goep *goep) +{ + struct bt_map_mce_mas *mce_mas = CONTAINER_OF(goep, struct bt_map_mce_mas, + goep); + + LOG_DBG("MCE MAS transport disconnected"); + atomic_set(&mce_mas->_transport_state, + BT_MAP_TRANSPORT_STATE_DISCONNECTED); + atomic_set(&mce_mas->_state, BT_MAP_STATE_DISCONNECTED); + mce_mas_clear_pending_request(mce_mas); + + if (mce_mas->goep.v2 != NULL) + { + if (mce_mas->_cb != NULL && mce_mas->_cb->l2cap_disconnected != NULL) + { + mce_mas->_cb->l2cap_disconnected(mce_mas); + } + } + else + { + if (mce_mas->_cb != NULL && mce_mas->_cb->rfcomm_disconnected != NULL) + { + mce_mas->_cb->rfcomm_disconnected(mce_mas); + } + } +} + +static struct bt_goep_transport_ops mce_mas_transport_ops = +{ + .connected = mce_mas_transport_connected, + .disconnected = mce_mas_transport_disconnected, +}; + +static int mce_mas_transport_connect( + struct bt_conn *conn, struct bt_map_mce_mas *mce_mas, + const struct bt_map_mce_mas_cb *cb, uint8_t type, uint16_t psm, + uint8_t channel) +{ + int err; + + if (conn == NULL || mce_mas == NULL || cb == NULL) + { + return -EINVAL; + } + + mce_mas->_cb = cb; + + if (atomic_read(&mce_mas->_transport_state) != + BT_MAP_TRANSPORT_STATE_DISCONNECTED) + { + LOG_DBG("MCE MAS transport connect in progress"); + return -EINPROGRESS; + } + + LOG_DBG("MCE MAS transport connecting, type %u", type); + mce_mas->goep.transport_ops = &mce_mas_transport_ops; + atomic_set(&mce_mas->_transport_state, BT_MAP_TRANSPORT_STATE_CONNECTING); + + if (type == MAP_TRANSPORT_TYPE_L2CAP) + { + BT_GOEP_INIT_V2(&mce_mas->goep, &mce_mas->goep_transport.v2); + LOG_DBG("Connecting via L2CAP PSM 0x%04x", psm); + err = bt_goep_transport_l2cap_connect(conn, &mce_mas->goep, psm); + } + else + { + BT_GOEP_INIT_V1(&mce_mas->goep, &mce_mas->goep_transport.v1); + LOG_DBG("Connecting via RFCOMM channel %u", channel); + err = bt_goep_transport_rfcomm_connect(conn, &mce_mas->goep, channel); + } + + if (err != 0) + { + LOG_ERR("Transport connect failed: %d", err); + atomic_set(&mce_mas->_transport_state, + BT_MAP_TRANSPORT_STATE_DISCONNECTED); + } + + return err; +} + +int bt_map_mce_mas_rfcomm_connect( + struct bt_conn *conn, struct bt_map_mce_mas *mce_mas, + const struct bt_map_mce_mas_cb *cb, uint8_t channel) +{ + if (channel == 0) + { + return -EINVAL; + } + + return mce_mas_transport_connect(conn, mce_mas, cb, + MAP_TRANSPORT_TYPE_RFCOMM, 0, channel); +} + +int bt_map_mce_mas_l2cap_connect( + struct bt_conn *conn, struct bt_map_mce_mas *mce_mas, + const struct bt_map_mce_mas_cb *cb, uint16_t psm) +{ + if (psm == 0) + { + return -EINVAL; + } + + return mce_mas_transport_connect(conn, mce_mas, cb, + MAP_TRANSPORT_TYPE_L2CAP, + psm, 0); +} + +static int mce_mas_transport_disconnect(struct bt_map_mce_mas *mce_mas, + uint8_t type) +{ + int err; + + if (mce_mas == NULL) + { + return -EINVAL; + } + + if (atomic_read( + &mce_mas->_transport_state) != BT_MAP_TRANSPORT_STATE_CONNECTED) + { + LOG_DBG("MCE MAS transport disconnect in progress"); + return -ENOTCONN; + } + + LOG_DBG("MCE MAS transport disconnecting, type %u", type); + atomic_set(&mce_mas->_transport_state, + BT_MAP_TRANSPORT_STATE_DISCONNECTING); + + if (type == MAP_TRANSPORT_TYPE_L2CAP) + { + err = bt_goep_transport_l2cap_disconnect(&mce_mas->goep); + } + else + { + err = bt_goep_transport_rfcomm_disconnect(&mce_mas->goep); + } + + if (err != 0) + { + LOG_ERR("Transport disconnect failed: %d", err); + atomic_set(&mce_mas->_transport_state, + BT_MAP_TRANSPORT_STATE_CONNECTED); + } + + return err; +} + +int bt_map_mce_mas_rfcomm_disconnect(struct bt_map_mce_mas *mce_mas) +{ + return mce_mas_transport_disconnect(mce_mas, MAP_TRANSPORT_TYPE_RFCOMM); +} + +int bt_map_mce_mas_l2cap_disconnect(struct bt_map_mce_mas *mce_mas) +{ + return mce_mas_transport_disconnect(mce_mas, MAP_TRANSPORT_TYPE_L2CAP); +} + +static void mce_mas_connect( + struct bt_obex_client *client, uint8_t rsp_code, uint8_t version, + uint16_t mopl, struct net_buf *buf) +{ + struct bt_map_mce_mas *c = CONTAINER_OF(client, struct bt_map_mce_mas, + _client); + int err; + + LOG_DBG("MCE MAS connected, rsp_code: 0x%02x, version: 0x%02x, mopl: %u", + rsp_code, version, + mopl); + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + atomic_set(&c->_state, BT_MAP_STATE_CONNECTED); + err = bt_obex_get_header_conn_id(buf, &c->_conn_id); + if (err != 0) + { + LOG_ERR("Failed to get connection ID: %d", err); + mce_mas_transport_disconnect(c, + c->goep.v2 != + NULL ? MAP_TRANSPORT_TYPE_L2CAP + : + MAP_TRANSPORT_TYPE_RFCOMM); + } + else + { + LOG_DBG("Connection ID: %u", c->_conn_id); + } + } + else + { + atomic_set(&c->_state, BT_MAP_STATE_DISCONNECTED); + } + + if ((c->_cb != NULL) && (c->_cb->connect != NULL)) + { + c->_cb->connect(c, rsp_code, version, mopl, buf); + } +} + +static void mce_mas_disconnect(struct bt_obex_client *client, + uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_map_mce_mas *c = CONTAINER_OF(client, struct bt_map_mce_mas, + _client); + + LOG_DBG("MCE MAS disconnected, rsp_code: 0x%02x", rsp_code); + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + atomic_set(&c->_state, BT_MAP_STATE_DISCONNECTED); + mce_mas_clear_pending_request(c); + } + else + { + atomic_set(&c->_state, BT_MAP_STATE_CONNECTED); + } + + if ((c->_cb != NULL) && (c->_cb->disconnect != NULL)) + { + c->_cb->disconnect(c, rsp_code, buf); + } +} + +static void mce_mas_put(struct bt_obex_client *client, + uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_map_mce_mas *c = CONTAINER_OF(client, struct bt_map_mce_mas, + _client); + mce_mas_cb_t cb = c->_rsp_cb; + + LOG_DBG("MCE MAS PUT response, rsp_code: 0x%02x", rsp_code); + + if (rsp_code != BT_OBEX_RSP_CODE_CONTINUE) + { + mce_mas_clear_pending_request(c); + } + + if (cb != NULL) + { + cb(c, rsp_code, buf); + } + else + { + LOG_WRN("No cb for rsp"); + } +} + +static void mce_mas_get(struct bt_obex_client *client, + uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_map_mce_mas *c = CONTAINER_OF(client, struct bt_map_mce_mas, + _client); + mce_mas_cb_t cb = c->_rsp_cb; + + LOG_DBG("MCE MAS GET response, rsp_code: 0x%02x", rsp_code); + + if (rsp_code != BT_OBEX_RSP_CODE_CONTINUE) + { + mce_mas_clear_pending_request(c); + } + + if (cb != NULL) + { + cb(c, rsp_code, buf); + } + else + { + LOG_WRN("No cb for rsp"); + } +} + +static void mce_mas_abort(struct bt_obex_client *client, + uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_map_mce_mas *c = CONTAINER_OF(client, struct bt_map_mce_mas, + _client); + int err; + + LOG_DBG("MCE MAS abort response, rsp_code: 0x%02x", rsp_code); + + if ((c->_cb != NULL) && (c->_cb->abort != NULL)) + { + c->_cb->abort(c, rsp_code, buf); + } + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + mce_mas_clear_pending_request(c); + return; + } + + err = bt_map_mce_mas_disconnect(c, NULL); + if (err != 0) + { + LOG_ERR("Failed to send MCE MAS disconnect: %d", err); + } +} + +static void mce_mas_setpath(struct bt_obex_client *client, + uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_map_mce_mas *c = CONTAINER_OF(client, struct bt_map_mce_mas, + _client); + + LOG_DBG("MCE MAS setpath response, rsp_code: 0x%02x", rsp_code); + + if ((c->_cb != NULL) && (c->_cb->set_folder != NULL)) + { + c->_cb->set_folder(c, rsp_code, buf); + } +} + +static const struct bt_obex_client_ops mce_mas_ops = +{ + .connect = mce_mas_connect, + .disconnect = mce_mas_disconnect, + .put = mce_mas_put, + .get = mce_mas_get, + .abort = mce_mas_abort, + .setpath = mce_mas_setpath, + .action = NULL, +}; + +struct net_buf *bt_map_mce_mas_create_pdu(struct bt_map_mce_mas *mce_mas, + struct net_buf_pool *pool) +{ + if (mce_mas == NULL) + { + LOG_ERR("Invalid parameter"); + return NULL; + } + + return bt_goep_create_pdu(&mce_mas->goep, pool); +} + +int bt_map_mce_mas_connect(struct bt_map_mce_mas *mce_mas, + struct net_buf *buf) +{ + uint8_t val[BT_UUID_SIZE_128]; + int err; + bool allocated = false; + + if (mce_mas == NULL) + { + return -EINVAL; + } + + if (atomic_read(&mce_mas->_state) != BT_MAP_STATE_DISCONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&mce_mas->_state)); + return -EINVAL; + } + + LOG_DBG("MCE MAS connecting"); + + if (buf == NULL) + { + buf = bt_goep_create_pdu(&mce_mas->goep, NULL); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } + + allocated = true; + } + + if (bt_obex_has_header(buf, BT_OBEX_HEADER_ID_TARGET)) + { + err = map_check_target(&map_mas_uuid->uuid, buf); + if (err != 0) + { + goto failed; + } + } + else + { + sys_memcpy_swap(val, map_mas_uuid->val, sizeof(val)); + err = bt_obex_add_header_target(buf, sizeof(val), val); + if (err != 0) + { + LOG_ERR("Failed to add header target: %d", err); + goto failed; + } + } + + mce_mas->_client.ops = &mce_mas_ops; + mce_mas->_client.obex = &mce_mas->goep.obex; + + err = bt_obex_connect(&mce_mas->_client, mce_mas->goep.obex.rx.mtu, buf); + if (err != 0) + { + LOG_ERR("Failed to send conn req: %d", err); + goto failed; + } + + atomic_set(&mce_mas->_state, BT_MAP_STATE_CONNECTING); + return 0; + +failed: + if (allocated) + { + net_buf_unref(buf); + } + + return err; +} + +int bt_map_mce_mas_disconnect(struct bt_map_mce_mas *mce_mas, + struct net_buf *buf) +{ + int err; + bool allocated = false; + + if (mce_mas == NULL) + { + return -EINVAL; + } + + if (atomic_read(&mce_mas->_state) != BT_MAP_STATE_CONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&mce_mas->_state)); + return -EINVAL; + } + + LOG_DBG("MCE MAS disconnecting"); + + if (buf == NULL) + { + buf = bt_goep_create_pdu(&mce_mas->goep, NULL); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } + + allocated = true; + } + + if (!bt_obex_has_header(buf, BT_OBEX_HEADER_ID_CONN_ID)) + { + err = bt_obex_add_header_conn_id(buf, mce_mas->_conn_id); + if (err != 0) + { + LOG_ERR("Failed to add header conn id: %d", err); + goto failed; + } + } + else + { + err = map_check_conn_id(mce_mas->_conn_id, buf); + if (err != 0) + { + goto failed; + } + } + + err = bt_obex_disconnect(&mce_mas->_client, buf); + if (err != 0) + { + LOG_ERR("Failed to send disconn req: %d", err); + goto failed; + } + + atomic_set(&mce_mas->_state, BT_MAP_STATE_DISCONNECTING); + return 0; + +failed: + if (allocated) + { + net_buf_unref(buf); + } + + return err; +} + +int bt_map_mce_mas_abort(struct bt_map_mce_mas *mce_mas, struct net_buf *buf) +{ + int err; + bool allocated = false; + + if (mce_mas == NULL) + { + return -EINVAL; + } + + if (atomic_read(&mce_mas->_state) != BT_MAP_STATE_CONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&mce_mas->_state)); + return -EINVAL; + } + + if (mce_mas->_rsp_cb == NULL) + { + LOG_ERR("No operation is ongoing"); + return -EINVAL; + } + + LOG_DBG("MCE MAS aborting"); + + if (buf == NULL) + { + buf = bt_goep_create_pdu(&mce_mas->goep, NULL); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } + + allocated = true; + } + + if (bt_obex_has_header(buf, BT_OBEX_HEADER_ID_CONN_ID)) + { + err = map_check_conn_id(mce_mas->_conn_id, buf); + if (err != 0) + { + goto failed; + } + } + else + { + err = bt_obex_add_header_conn_id(buf, mce_mas->_conn_id); + if (err != 0) + { + LOG_ERR("Failed to add header conn id: %d", err); + goto failed; + } + } + + err = bt_obex_abort(&mce_mas->_client, buf); + if (err != 0) + { + LOG_ERR("Failed to send abort request: %d", err); + goto failed; + } + + return 0; + +failed: + if (allocated) + { + net_buf_unref(buf); + } + + return err; +} + +int bt_map_mce_mas_set_folder(struct bt_map_mce_mas *mce_mas, + uint8_t flags, struct net_buf *buf) +{ + int err; + bool allocated = false; + + if (mce_mas == NULL) + { + return -EINVAL; + } + + if (atomic_read(&mce_mas->_state) != BT_MAP_STATE_CONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&mce_mas->_state)); + return -EINVAL; + } + + if (mce_mas->_rsp_cb != NULL) + { + LOG_ERR("Previous operation is not completed"); + return -EINVAL; + } + + if ((flags == BT_MAP_SET_FOLDER_FLAGS_DOWN || + flags == BT_MAP_SET_FOLDER_FLAGS_ROOT) && + (!bt_obex_has_header(buf, BT_OBEX_HEADER_ID_NAME))) + { + LOG_ERR("Failed to get name when flags is root or down"); + return -EINVAL; + } + + LOG_DBG("MCE MAS set folder, flags: 0x%02x", flags); + + if (buf == NULL) + { + buf = bt_goep_create_pdu(&mce_mas->goep, NULL); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } + + allocated = true; + } + + if (!bt_obex_has_header(buf, BT_OBEX_HEADER_ID_CONN_ID)) + { + err = bt_obex_add_header_conn_id(buf, mce_mas->_conn_id); + if (err != 0) + { + LOG_ERR("Failed to add header conn id: %d", err); + goto failed; + } + } + else + { + err = map_check_conn_id(mce_mas->_conn_id, buf); + if (err != 0) + { + goto failed; + } + } + + err = bt_obex_setpath(&mce_mas->_client, flags, buf); + if (err != 0) + { + LOG_ERR("Failed to send setpath: %d", err); + goto failed; + } + + return 0; + +failed: + if (allocated) + { + net_buf_unref(buf); + } + + return err; +} + +static int mce_mas_get_req_cb( + struct bt_map_mce_mas *mce_mas, const char *type, struct net_buf *buf, + bool is_get, mce_mas_cb_t *cb, const char **req_type) +{ + const uint8_t *type_data; + uint16_t len; + int err; + + err = bt_obex_get_header_type(buf, &len, &type_data); + if (err != 0) + { + LOG_WRN("Failed to get type header: %d", err); + return -EINVAL; + } + + if (len <= strlen(type_data)) + { + LOG_WRN("Invalid type string len %u <= %u", len, strlen(type_data)); + return -EINVAL; + } + + *cb = NULL; + ARRAY_FOR_EACH(map_mas_functions, i) + { + if (strcmp(map_mas_functions[i].type, type) != 0) + { + continue; + } + + if (strcmp(map_mas_functions[i].type, type_data) != 0) + { + continue; + } + + if (map_mas_functions[i].op_get != is_get) + { + continue; + } + + if (!has_required_hdrs(buf, &map_mas_functions[i].hdr)) + { + continue; + } + + if (!has_required_app_params(buf, &map_mas_functions[i].ap)) + { + continue; + } + + if (map_mas_functions[i].get_mce_mas_cb == NULL) + { + continue; + } + + *cb = map_mas_functions[i].get_mce_mas_cb(mce_mas); + if (*cb == NULL) + { + continue; + } + + if (req_type != NULL) + { + *req_type = map_mas_functions[i].type; + } + break; + } + + if (*cb == NULL) + { + LOG_WRN("Unsupported request"); + return -EINVAL; + } + + return 0; +} + +static int mce_mas_get_or_put( + struct bt_map_mce_mas *mce_mas, bool is_get, const char *type, bool final, + struct net_buf *buf) +{ + int err; + mce_mas_cb_t cb; + mce_mas_cb_t old_cb; + const char *req_type; + const char *old_req_type; + + if (mce_mas == NULL || buf == NULL) + { + return -EINVAL; + } + + if (atomic_read(&mce_mas->_state) != BT_MAP_STATE_CONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&mce_mas->_state)); + return -EINVAL; + } + + LOG_DBG("MCE MAS %s request, type: %s, final: %d", is_get ? "GET" : "PUT", + type, final); + + old_cb = mce_mas->_rsp_cb; + old_req_type = mce_mas->_req_type; + + if (mce_mas->_rsp_cb == NULL || bt_obex_has_header(buf, + BT_OBEX_HEADER_ID_TYPE)) + { + if (is_get && mce_mas->goep.v2 != NULL && !bt_obex_has_header(buf, + BT_OBEX_HEADER_ID_SRM)) + { + LOG_ERR("Failed to get SRM"); + return -ENODATA; + } + + err = mce_mas_get_req_cb(mce_mas, type, buf, is_get, &cb, &req_type); + if (err != 0) + { + LOG_ERR("Invalid request: %d", err); + return err; + } + + if (mce_mas->_rsp_cb != NULL && cb != mce_mas->_rsp_cb) + { + LOG_ERR("Previous operation is not completed"); + return -EINVAL; + } + + mce_mas->_rsp_cb = cb; + mce_mas->_req_type = req_type; + } + + if (mce_mas->_req_type != NULL && strcmp(mce_mas->_req_type, type) != 0) + { + LOG_ERR("Invalid request type %s != %s", mce_mas->_req_type, type); + err = -EINVAL; + goto failed; + } + + if (bt_obex_has_header(buf, BT_OBEX_HEADER_ID_CONN_ID)) + { + err = map_check_conn_id(mce_mas->_conn_id, buf); + if (err != 0) + { + goto failed; + } + } + + if (!is_get && final && !bt_obex_has_header(buf, + BT_OBEX_HEADER_ID_END_BODY)) + { + LOG_ERR("OBEX header (End of Body) is missing"); + err = -ENODATA; + goto failed; + } + + if (is_get) + { + err = bt_obex_get(&mce_mas->_client, final, buf); + } + else + { + err = bt_obex_put(&mce_mas->_client, final, buf); + } + +failed: + if (err != 0) + { + mce_mas->_rsp_cb = old_cb; + mce_mas->_req_type = old_req_type; + LOG_ERR("Failed to send get/put req: %d", err); + } + + return err; +} + +/* Defines a public bt_map_mce_mas_xxx() API wrapper. + * _op_get : bool – true for GET, false for PUT _NAME : uppercase operation + * token (e.g. SET_NTF_REG) _name : lowercase function name suffix (e.g. + * set_ntf_reg) + */ +#define MAP_MCE_MAS_API(_op_get, _NAME, _name) \ + int bt_map_mce_mas_##_name( \ + struct bt_map_mce_mas *mce_mas, bool final, \ + struct net_buf *buf) \ + { \ + return mce_mas_get_or_put(mce_mas, (_op_get), \ + BT_MAP_HDR_TYPE_##_NAME, final, buf); \ + } + +MAP_MCE_MAS_API(false, SET_NTF_REG, set_ntf_reg) +MAP_MCE_MAS_API(true, GET_FOLDER_LISTING, get_folder_listing) +MAP_MCE_MAS_API(true, GET_MSG_LISTING, get_msg_listing) +MAP_MCE_MAS_API(true, GET_MSG, get_msg) +MAP_MCE_MAS_API(false, SET_MSG_STATUS, set_msg_status) +MAP_MCE_MAS_API(false, PUSH_MSG, push_msg) +MAP_MCE_MAS_API(false, UPDATE_INBOX, update_inbox) +MAP_MCE_MAS_API(true, GET_MAS_INST_INFO, get_mas_inst_info) +MAP_MCE_MAS_API(false, SET_OWNER_STATUS, set_owner_status) +MAP_MCE_MAS_API(true, GET_OWNER_STATUS, get_owner_status) +MAP_MCE_MAS_API(true, GET_CONVO_LISTING, get_convo_listing) +MAP_MCE_MAS_API(false, SET_NTF_FILTER, set_ntf_filter) + +/* MCE MNS Implementation */ + +static void mce_mns_clear_pending_request(struct bt_map_mce_mns *mce_mns) +{ + mce_mns->_opcode = 0; + mce_mns->_optype = NULL; + mce_mns->_req_cb = NULL; +} + +static void mce_mns_transport_connected(struct bt_conn *conn, + struct bt_goep *goep) +{ + struct bt_map_mce_mns *mce_mns = CONTAINER_OF(goep, struct bt_map_mce_mns, + goep); + + LOG_DBG("MCE MNS transport connected"); + atomic_set(&mce_mns->_transport_state, BT_MAP_TRANSPORT_STATE_CONNECTED); + + if (mce_mns->goep.v2 != NULL) + { + if (mce_mns->_cb != NULL && mce_mns->_cb->l2cap_connected != NULL) + { + mce_mns->_cb->l2cap_connected(conn, mce_mns); + } + } + else + { + if (mce_mns->_cb != NULL && mce_mns->_cb->rfcomm_connected != NULL) + { + mce_mns->_cb->rfcomm_connected(conn, mce_mns); + } + } +} + +static void mce_mns_transport_disconnected(struct bt_goep *goep) +{ + struct bt_map_mce_mns *mce_mns = CONTAINER_OF(goep, struct bt_map_mce_mns, + goep); + + LOG_DBG("MCE MNS transport disconnected"); + atomic_set(&mce_mns->_transport_state, + BT_MAP_TRANSPORT_STATE_DISCONNECTED); + atomic_set(&mce_mns->_state, BT_MAP_STATE_DISCONNECTED); + mce_mns_clear_pending_request(mce_mns); + + if (mce_mns->goep.v2 != NULL) + { + if (mce_mns->_cb != NULL && mce_mns->_cb->l2cap_disconnected != NULL) + { + mce_mns->_cb->l2cap_disconnected(mce_mns); + } + } + else + { + if (mce_mns->_cb != NULL && mce_mns->_cb->rfcomm_disconnected != NULL) + { + mce_mns->_cb->rfcomm_disconnected(mce_mns); + } + } +} + +static struct bt_goep_transport_ops mce_mns_transport_ops = +{ + .connected = mce_mns_transport_connected, + .disconnected = mce_mns_transport_disconnected, +}; + +static int mce_mns_transport_disconnect(struct bt_map_mce_mns *mce_mns, + uint8_t type) +{ + int err; + + if (mce_mns == NULL) + { + return -EINVAL; + } + + if (atomic_read( + &mce_mns->_transport_state) != BT_MAP_TRANSPORT_STATE_CONNECTED) + { + LOG_DBG("MCE MNS transport disconnect in progress"); + return -ENOTCONN; + } + + LOG_DBG("MCE MNS transport disconnecting, type %u", type); + atomic_set(&mce_mns->_transport_state, + BT_MAP_TRANSPORT_STATE_DISCONNECTING); + + if (type == MAP_TRANSPORT_TYPE_L2CAP) + { + err = bt_goep_transport_l2cap_disconnect(&mce_mns->goep); + } + else + { + err = bt_goep_transport_rfcomm_disconnect(&mce_mns->goep); + } + + if (err != 0) + { + LOG_ERR("Transport disconnect failed: %d", err); + atomic_set(&mce_mns->_transport_state, + BT_MAP_TRANSPORT_STATE_CONNECTED); + } + + return err; +} + +int bt_map_mce_mns_rfcomm_disconnect(struct bt_map_mce_mns *mce_mns) +{ + return mce_mns_transport_disconnect(mce_mns, MAP_TRANSPORT_TYPE_RFCOMM); +} + +int bt_map_mce_mns_l2cap_disconnect(struct bt_map_mce_mns *mce_mns) +{ + return mce_mns_transport_disconnect(mce_mns, MAP_TRANSPORT_TYPE_L2CAP); +} + +static void mce_mns_connect( + struct bt_obex_server *server, uint8_t version, uint16_t mopl, + struct net_buf *buf) +{ + struct bt_map_mce_mns *s = CONTAINER_OF(server, struct bt_map_mce_mns, + _server); + + LOG_DBG("MCE MNS connected, version: 0x%02x, mopl: %u", version, mopl); + atomic_set(&s->_state, BT_MAP_STATE_CONNECTING); + + if ((s->_cb != NULL) && (s->_cb->connect != NULL)) + { + s->_cb->connect(s, version, mopl, buf); + } +} + +static void mce_mns_disconnect(struct bt_obex_server *server, + struct net_buf *buf) +{ + struct bt_map_mce_mns *s = CONTAINER_OF(server, struct bt_map_mce_mns, + _server); + + LOG_DBG("MCE MNS disconnected"); + atomic_set(&s->_state, BT_MAP_STATE_DISCONNECTING); + + if ((s->_cb != NULL) && (s->_cb->disconnect != NULL)) + { + s->_cb->disconnect(s, buf); + } +} + +static enum bt_obex_rsp_code mce_mns_get_req_cb( + struct bt_map_mce_mns *mce_mns, struct net_buf *buf, bool is_get, + mce_mns_cb_t *cb) +{ + const uint8_t *type; + uint16_t len; + int err; + + err = bt_obex_get_header_type(buf, &len, &type); + if (err != 0) + { + LOG_WRN("Failed to get type header: %d", err); + return BT_OBEX_RSP_CODE_BAD_REQ; + } + + if (len <= strlen(type)) + { + LOG_WRN("Invalid type string len %u <= %u", len, strlen(type)); + return BT_OBEX_RSP_CODE_BAD_REQ; + } + + *cb = NULL; + ARRAY_FOR_EACH(map_mns_functions, i) + { + if (mce_mns->_optype != NULL + && map_mns_functions[i].type != mce_mns->_optype) + { + continue; + } + + if (strcmp(map_mns_functions[i].type, type) != 0) + { + continue; + } + + if (map_mns_functions[i].op_get != is_get) + { + continue; + } + + if (!has_required_hdrs(buf, &map_mns_functions[i].hdr)) + { + continue; + } + + if (!has_required_app_params(buf, &map_mns_functions[i].ap)) + { + continue; + } + + if (map_mns_functions[i].get_mce_mns_cb == NULL) + { + continue; + } + + *cb = map_mns_functions[i].get_mce_mns_cb(mce_mns); + if (*cb == NULL) + { + continue; + } + + mce_mns->_optype = map_mns_functions[i].type; + mce_mns->_opcode = is_get ? BT_OBEX_OPCODE_GET : BT_OBEX_OPCODE_PUT; + break; + } + + if (*cb == NULL) + { + LOG_WRN("Unsupported request"); + return BT_OBEX_RSP_CODE_NOT_IMPL; + } + + return BT_OBEX_RSP_CODE_SUCCESS; +} + +static void mce_mns_put(struct bt_obex_server *server, + bool final, struct net_buf *buf) +{ + struct bt_map_mce_mns *s = CONTAINER_OF(server, struct bt_map_mce_mns, + _server); + int err; + enum bt_obex_rsp_code rsp_code; + + LOG_DBG("MCE MNS PUT request, final: %d", final); + + if (s->_optype == NULL || bt_obex_has_header(buf, BT_OBEX_HEADER_ID_TYPE)) + { + rsp_code = mce_mns_get_req_cb(s, buf, false, &s->_req_cb); + if (rsp_code != BT_OBEX_RSP_CODE_SUCCESS) + { + LOG_WRN("Failed to parse req %u", (uint8_t)rsp_code); + goto failed; + } + } + + if (s->_optype == NULL || s->_opcode != BT_OBEX_OPCODE_PUT || + s->_req_cb == NULL) + { + rsp_code = BT_OBEX_RSP_CODE_NOT_IMPL; + LOG_WRN("Invalid request"); + goto failed; + } + + s->_req_cb(s, final, buf); + + return; + +failed: + mce_mns_clear_pending_request(s); + err = bt_obex_put_rsp(server, rsp_code, NULL); + if (err != 0) + { + LOG_ERR("Failed to send put rsp: %d", err); + } +} + +static void mce_mns_abort(struct bt_obex_server *server, struct net_buf *buf) +{ + struct bt_map_mce_mns *s = CONTAINER_OF(server, struct bt_map_mce_mns, + _server); + int err; + + LOG_DBG("MCE MNS abort request"); + + if ((s->_cb != NULL) && (s->_cb->abort != NULL)) + { + s->_cb->abort(s, buf); + return; + } + + LOG_WRN("No cb for abort req"); + err = bt_obex_abort_rsp(server, BT_OBEX_RSP_CODE_NOT_IMPL, NULL); + if (err != 0) + { + LOG_ERR("Failed to send abort rsp: %d", err); + } +} + +static const struct bt_obex_server_ops mce_mns_ops = +{ + .connect = mce_mns_connect, + .disconnect = mce_mns_disconnect, + .put = mce_mns_put, + .get = NULL, + .abort = mce_mns_abort, + .setpath = NULL, + .action = NULL, +}; + +int bt_map_mce_mns_register(struct bt_map_mce_mns *mce_mns, + const struct bt_map_mce_mns_cb *cb) +{ + int err; + + if (mce_mns == NULL || cb == NULL) + { + return -EINVAL; + } + + mce_mns->_cb = cb; + mce_mns->_server.ops = &mce_mns_ops; + mce_mns->_server.obex = &mce_mns->goep.obex; + mce_mns->_conn_id = map_get_connect_id(); + + err = bt_obex_server_register(&mce_mns->_server, map_mns_uuid); + if (err != 0) + { + LOG_ERR("Failed to register MCE MNS obex server: %d", err); + return err; + } + + LOG_DBG("MCE MNS registered"); + return 0; +} + +#define MNS_RFCOMM_SERVER(server) CONTAINER_OF(server, struct bt_map_mce_mns_rfcomm_server, server) +#define MNS_L2CAP_SERVER(server) CONTAINER_OF(server, struct bt_map_mce_mns_l2cap_server, server) +static int mce_mns_accept(struct bt_conn *conn, + void *server, uint8_t type, struct bt_goep **goep) +{ + struct bt_map_mce_mns *mce_mns = NULL; + int err; + + LOG_DBG("MCE MNS %s accept", + type == MAP_TRANSPORT_TYPE_RFCOMM ? "RFCOMM" : "L2CAP"); + + if (type == MAP_TRANSPORT_TYPE_RFCOMM) + { + struct bt_map_mce_mns_rfcomm_server *rfcomm_server = + MNS_RFCOMM_SERVER(server); + + if (rfcomm_server->accept == NULL) + { + return -ENOTSUP; + } + + err = rfcomm_server->accept(conn, rfcomm_server, &mce_mns); + } + else + { + struct bt_map_mce_mns_l2cap_server *l2cap_server = + MNS_L2CAP_SERVER(server); + + if (l2cap_server->accept == NULL) + { + return -ENOTSUP; + } + + err = l2cap_server->accept(conn, l2cap_server, &mce_mns); + } + + if (err != 0) + { + LOG_WRN("Incoming connection rejected"); + return err; + } + + DEBUGASSERT(mce_mns != NULL && mce_mns->_cb != NULL); + + mce_mns->goep.transport_ops = &mce_mns_transport_ops; + if (type == MAP_TRANSPORT_TYPE_RFCOMM) + { + BT_GOEP_INIT_V1(&mce_mns->goep, &mce_mns->goep_transport.v1); + } + else + { + BT_GOEP_INIT_V2(&mce_mns->goep, &mce_mns->goep_transport.v2); + } + + *goep = &mce_mns->goep; + atomic_set(&mce_mns->_transport_state, BT_MAP_TRANSPORT_STATE_CONNECTING); + + return 0; +} + +static int mce_mns_rfcomm_accept(struct bt_conn *conn, + struct bt_goep_transport_rfcomm_server *server, + struct bt_goep **goep) +{ + return mce_mns_accept(conn, server, MAP_TRANSPORT_TYPE_RFCOMM, goep); +} + +int bt_map_mce_mns_rfcomm_register( + struct bt_map_mce_mns_rfcomm_server *server) +{ + if (server == NULL || server->accept == NULL) + { + return -EINVAL; + } + + LOG_DBG("Registering MCE MNS RFCOMM server"); + server->server.accept = mce_mns_rfcomm_accept; + + return bt_goep_transport_rfcomm_server_register(&server->server); +} + +static int mce_mns_l2cap_accept( + struct bt_conn *conn, struct bt_goep_transport_l2cap_server *server, + struct bt_goep **goep) +{ + return mce_mns_accept(conn, server, MAP_TRANSPORT_TYPE_L2CAP, goep); +} + +int bt_map_mce_mns_l2cap_register(struct bt_map_mce_mns_l2cap_server *server) +{ + if (server == NULL || server->accept == NULL) + { + return -EINVAL; + } + + LOG_DBG("Registering MCE MNS L2CAP server"); + server->server.accept = mce_mns_l2cap_accept; + + return bt_goep_transport_l2cap_server_register(&server->server); +} + +struct net_buf *bt_map_mce_mns_create_pdu(struct bt_map_mce_mns *mce_mns, + struct net_buf_pool *pool) +{ + if (mce_mns == NULL) + { + LOG_ERR("Invalid parameter"); + return NULL; + } + + return bt_goep_create_pdu(&mce_mns->goep, pool); +} + +int bt_map_mce_mns_connect(struct bt_map_mce_mns *mce_mns, + uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t val[BT_UUID_SIZE_128]; + int err; + bool allocated = false; + + if (mce_mns == NULL) + { + return -EINVAL; + } + + if (atomic_read(&mce_mns->_state) != BT_MAP_STATE_CONNECTING) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&mce_mns->_state)); + return -EINVAL; + } + + LOG_DBG("MCE MNS connect response, rsp_code: 0x%02x", rsp_code); + + if (buf == NULL) + { + buf = bt_goep_create_pdu(&mce_mns->goep, NULL); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } + + allocated = true; + } + else + { + if (bt_obex_has_header(buf, BT_OBEX_HEADER_ID_WHO)) + { + err = map_check_who(&map_mns_uuid->uuid, buf); + if (err != 0) + { + return err; + } + } + + if (bt_obex_has_header(buf, BT_OBEX_HEADER_ID_CONN_ID)) + { + err = map_check_conn_id(mce_mns->_conn_id, buf); + if (err != 0) + { + return err; + } + } + } + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + if (!bt_obex_has_header(buf, BT_OBEX_HEADER_ID_WHO)) + { + sys_memcpy_swap(val, map_mns_uuid->val, sizeof(val)); + err = bt_obex_add_header_who(buf, sizeof(val), val); + if (err != 0) + { + LOG_ERR("Failed to add header target: %d", err); + goto failed; + } + } + + if (!bt_obex_has_header(buf, BT_OBEX_HEADER_ID_CONN_ID)) + { + err = bt_obex_add_header_conn_id(buf, mce_mns->_conn_id); + if (err != 0) + { + LOG_ERR("Failed to add header conn id: %d", err); + goto failed; + } + } + } + + err = bt_obex_connect_rsp(&mce_mns->_server, rsp_code, + mce_mns->goep.obex.rx.mtu, buf); + if (err != 0) + { + LOG_ERR("Failed to send conn rsp: %d", err); + goto failed; + } + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + atomic_set(&mce_mns->_state, BT_MAP_STATE_CONNECTED); + } + else + { + atomic_set(&mce_mns->_state, BT_MAP_STATE_DISCONNECTED); + } + + return 0; + +failed: + if (allocated) + { + net_buf_unref(buf); + } + + return err; +} + +int bt_map_mce_mns_disconnect(struct bt_map_mce_mns *mce_mns, + uint8_t rsp_code, struct net_buf *buf) +{ + int err; + + if (mce_mns == NULL) + { + return -EINVAL; + } + + if (atomic_read(&mce_mns->_state) != BT_MAP_STATE_DISCONNECTING) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&mce_mns->_state)); + return -EINVAL; + } + + LOG_DBG("MCE MNS disconnect response, rsp_code: 0x%02x", rsp_code); + + err = bt_obex_disconnect_rsp(&mce_mns->_server, rsp_code, buf); + if (err != 0) + { + LOG_ERR("Failed to send disconn rsp: %d", err); + return err; + } + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + atomic_set(&mce_mns->_state, BT_MAP_STATE_DISCONNECTED); + } + else + { + atomic_set(&mce_mns->_state, BT_MAP_STATE_CONNECTED); + } + + return 0; +} + +int bt_map_mce_mns_abort(struct bt_map_mce_mns *mce_mns, + uint8_t rsp_code, struct net_buf *buf) +{ + int err; + + if (mce_mns == NULL) + { + return -EINVAL; + } + + if (atomic_read(&mce_mns->_state) != BT_MAP_STATE_CONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&mce_mns->_state)); + return -EINVAL; + } + + LOG_DBG("MCE MNS abort response, rsp_code: 0x%02x", rsp_code); + + err = bt_obex_abort_rsp(&mce_mns->_server, rsp_code, buf); + if (err != 0) + { + LOG_ERR("Failed to send abort rsp: %d", err); + return err; + } + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + mce_mns_clear_pending_request(mce_mns); + } + + return 0; +} + +static int mce_mns_get_or_put_rsp( + struct bt_map_mce_mns *mce_mns, bool is_get, const char *type, + uint8_t rsp_code, struct net_buf *buf) +{ + int err; + + if (mce_mns == NULL) + { + return -EINVAL; + } + + if (atomic_read(&mce_mns->_state) != BT_MAP_STATE_CONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&mce_mns->_state)); + return -EINVAL; + } + + if (mce_mns->_optype != NULL && strcmp(mce_mns->_optype, type) != 0) + { + LOG_ERR("Invalid operation type %s != %s", mce_mns->_optype, type); + return -EINVAL; + } + + LOG_DBG("MCE MNS %s response, type: %s, rsp_code: 0x%02x", + is_get ? "GET" : "PUT", type, + rsp_code); + + if (is_get) + { + if (mce_mns->_opcode != BT_OBEX_OPCODE_GET) + { + LOG_ERR("Operation %u != %u", + mce_mns->_opcode, + BT_OBEX_OPCODE_GET); + return -EINVAL; + } + + err = bt_obex_get_rsp(&mce_mns->_server, rsp_code, buf); + } + else + { + if (mce_mns->_opcode != BT_OBEX_OPCODE_PUT) + { + LOG_ERR("Operation %u != %u", + mce_mns->_opcode, + BT_OBEX_OPCODE_PUT); + return -EINVAL; + } + + err = bt_obex_put_rsp(&mce_mns->_server, rsp_code, buf); + } + + if (err != 0) + { + LOG_ERR("Failed to send get/put rsp: %d", err); + return err; + } + + if (rsp_code != BT_OBEX_RSP_CODE_CONTINUE) + { + mce_mns_clear_pending_request(mce_mns); + } + + return 0; +} + +int bt_map_mce_mns_send_event(struct bt_map_mce_mns *mce_mns, + uint8_t rsp_code, struct net_buf *buf) +{ + return mce_mns_get_or_put_rsp(mce_mns, false, BT_MAP_HDR_TYPE_SEND_EVENT, + rsp_code, buf); +} +#endif /* CONFIG_BT_MAP_MCE */ + +#if defined(CONFIG_BT_MAP_MSE) +static void mse_mas_clear_pending_request(struct bt_map_mse_mas *mse_mas) +{ + mse_mas->_opcode = 0; + mse_mas->_optype = NULL; + mse_mas->_req_cb = NULL; +} + +static void mse_mas_transport_connected(struct bt_conn *conn, + struct bt_goep *goep) +{ + struct bt_map_mse_mas *mse_mas = CONTAINER_OF(goep, struct bt_map_mse_mas, + goep); + + LOG_DBG("MSE MAS transport connected"); + atomic_set(&mse_mas->_transport_state, BT_MAP_TRANSPORT_STATE_CONNECTED); + + if (mse_mas->goep.v2 != NULL) + { + if (mse_mas->_cb != NULL && mse_mas->_cb->l2cap_connected != NULL) + { + mse_mas->_cb->l2cap_connected(conn, mse_mas); + } + } + else + { + if (mse_mas->_cb != NULL && mse_mas->_cb->rfcomm_connected != NULL) + { + mse_mas->_cb->rfcomm_connected(conn, mse_mas); + } + } +} + +static void mse_mas_transport_disconnected(struct bt_goep *goep) +{ + struct bt_map_mse_mas *mse_mas = CONTAINER_OF(goep, struct bt_map_mse_mas, + goep); + + LOG_DBG("MSE MAS transport disconnected"); + atomic_set(&mse_mas->_transport_state, + BT_MAP_TRANSPORT_STATE_DISCONNECTED); + atomic_set(&mse_mas->_state, BT_MAP_STATE_DISCONNECTED); + mse_mas_clear_pending_request(mse_mas); + + if (mse_mas->goep.v2 != NULL) + { + if (mse_mas->_cb != NULL && mse_mas->_cb->l2cap_disconnected != NULL) + { + mse_mas->_cb->l2cap_disconnected(mse_mas); + } + } + else + { + if (mse_mas->_cb != NULL && mse_mas->_cb->rfcomm_disconnected != NULL) + { + mse_mas->_cb->rfcomm_disconnected(mse_mas); + } + } +} + +static struct bt_goep_transport_ops mse_mas_transport_ops = +{ + .connected = mse_mas_transport_connected, + .disconnected = mse_mas_transport_disconnected, +}; + +static int mse_mas_transport_disconnect(struct bt_map_mse_mas *mse_mas, + uint8_t type) +{ + int err; + + if (mse_mas == NULL) + { + return -EINVAL; + } + + if (atomic_read( + &mse_mas->_transport_state) != BT_MAP_TRANSPORT_STATE_CONNECTED) + { + LOG_DBG("MSE MAS transport disconnect in progress"); + return -ENOTCONN; + } + + LOG_DBG("MSE MAS transport disconnecting, type %u", type); + atomic_set(&mse_mas->_transport_state, + BT_MAP_TRANSPORT_STATE_DISCONNECTING); + + if (type == MAP_TRANSPORT_TYPE_L2CAP) + { + err = bt_goep_transport_l2cap_disconnect(&mse_mas->goep); + } + else + { + err = bt_goep_transport_rfcomm_disconnect(&mse_mas->goep); + } + + if (err != 0) + { + LOG_ERR("Transport disconnect failed: %d", err); + atomic_set(&mse_mas->_transport_state, + BT_MAP_TRANSPORT_STATE_CONNECTED); + } + + return err; +} + +int bt_map_mse_mas_rfcomm_disconnect(struct bt_map_mse_mas *mse_mas) +{ + return mse_mas_transport_disconnect(mse_mas, MAP_TRANSPORT_TYPE_RFCOMM); +} + +int bt_map_mse_mas_l2cap_disconnect(struct bt_map_mse_mas *mse_mas) +{ + return mse_mas_transport_disconnect(mse_mas, MAP_TRANSPORT_TYPE_L2CAP); +} + +static void mse_mas_connect( + struct bt_obex_server *server, uint8_t version, uint16_t mopl, + struct net_buf *buf) +{ + struct bt_map_mse_mas *s = CONTAINER_OF(server, struct bt_map_mse_mas, + _server); + + LOG_DBG("MSE MAS connected, version: 0x%02x, mopl: %u", version, mopl); + atomic_set(&s->_state, BT_MAP_STATE_CONNECTING); + + if ((s->_cb != NULL) && (s->_cb->connect != NULL)) + { + s->_cb->connect(s, version, mopl, buf); + } +} + +static void mse_mas_disconnect(struct bt_obex_server *server, + struct net_buf *buf) +{ + struct bt_map_mse_mas *s = CONTAINER_OF(server, struct bt_map_mse_mas, + _server); + + LOG_DBG("MSE MAS disconnected"); + atomic_set(&s->_state, BT_MAP_STATE_DISCONNECTING); + + if ((s->_cb != NULL) && (s->_cb->disconnect != NULL)) + { + s->_cb->disconnect(s, buf); + } +} + +static enum bt_obex_rsp_code mse_mas_get_req_cb( + struct bt_map_mse_mas *mse_mas, struct net_buf *buf, bool is_get, + mse_mas_cb_t *cb) +{ + const uint8_t *type; + uint16_t len; + int err; + + err = bt_obex_get_header_type(buf, &len, &type); + if (err != 0) + { + LOG_WRN("Failed to get type header %d", err); + return BT_OBEX_RSP_CODE_BAD_REQ; + } + + if (len <= strlen(type)) + { + LOG_WRN("Invalid type string len %u <= %u", len, strlen(type)); + return BT_OBEX_RSP_CODE_BAD_REQ; + } + + *cb = NULL; + ARRAY_FOR_EACH(map_mas_functions, i) + { + if (mse_mas->_optype != NULL + && map_mas_functions[i].type != mse_mas->_optype) + { + continue; + } + + if (strcmp(map_mas_functions[i].type, type) != 0) + { + continue; + } + + if (map_mas_functions[i].op_get != is_get) + { + continue; + } + + if (!has_required_hdrs(buf, &map_mas_functions[i].hdr)) + { + continue; + } + + if (!has_required_app_params(buf, &map_mas_functions[i].ap)) + { + continue; + } + + if (map_mas_functions[i].get_mse_mas_cb == NULL) + { + continue; + } + + *cb = map_mas_functions[i].get_mse_mas_cb(mse_mas); + if (*cb == NULL) + { + continue; + } + + mse_mas->_optype = map_mas_functions[i].type; + mse_mas->_opcode = is_get ? BT_OBEX_OPCODE_GET : BT_OBEX_OPCODE_PUT; + break; + } + + if (*cb == NULL) + { + LOG_WRN("Unsupported request"); + return BT_OBEX_RSP_CODE_NOT_IMPL; + } + + return BT_OBEX_RSP_CODE_SUCCESS; +} + +static void mse_mas_put(struct bt_obex_server *server, + bool final, struct net_buf *buf) +{ + struct bt_map_mse_mas *s = CONTAINER_OF(server, struct bt_map_mse_mas, + _server); + int err; + enum bt_obex_rsp_code rsp_code; + + LOG_DBG("MSE MAS PUT request, final: %d", final); + + if (s->_optype == NULL || bt_obex_has_header(buf, BT_OBEX_HEADER_ID_TYPE)) + { + rsp_code = mse_mas_get_req_cb(s, buf, false, &s->_req_cb); + if (rsp_code != BT_OBEX_RSP_CODE_SUCCESS) + { + LOG_WRN("Failed to parse req %u", (uint8_t)rsp_code); + goto failed; + } + } + + if (s->_optype == NULL || s->_opcode != BT_OBEX_OPCODE_PUT || + s->_req_cb == NULL) + { + rsp_code = BT_OBEX_RSP_CODE_NOT_IMPL; + LOG_WRN("Invalid request"); + goto failed; + } + + s->_req_cb(s, final, buf); + + return; + +failed: + mse_mas_clear_pending_request(s); + err = bt_obex_put_rsp(server, rsp_code, NULL); + if (err != 0) + { + LOG_ERR("Failed to send put rsp: %d", err); + } +} + +static void mse_mas_get(struct bt_obex_server *server, + bool final, struct net_buf *buf) +{ + struct bt_map_mse_mas *s = CONTAINER_OF(server, struct bt_map_mse_mas, + _server); + int err; + enum bt_obex_rsp_code rsp_code; + + LOG_DBG("MSE MAS GET request, final: %d", final); + + if (s->_optype == NULL || bt_obex_has_header(buf, BT_OBEX_HEADER_ID_TYPE)) + { + rsp_code = mse_mas_get_req_cb(s, buf, true, &s->_req_cb); + if (rsp_code != BT_OBEX_RSP_CODE_SUCCESS) + { + LOG_WRN("Failed to parse req %u", (uint8_t)rsp_code); + goto failed; + } + } + + if (s->_optype == NULL || s->_opcode != BT_OBEX_OPCODE_GET || + s->_req_cb == NULL) + { + rsp_code = BT_OBEX_RSP_CODE_NOT_IMPL; + LOG_WRN("Invalid request"); + goto failed; + } + + s->_req_cb(s, final, buf); + + return; + +failed: + mse_mas_clear_pending_request(s); + err = bt_obex_get_rsp(server, rsp_code, NULL); + if (err != 0) + { + LOG_ERR("Failed to send get rsp: %d", err); + } +} + +static void mse_mas_abort(struct bt_obex_server *server, struct net_buf *buf) +{ + struct bt_map_mse_mas *s = CONTAINER_OF(server, struct bt_map_mse_mas, + _server); + int err; + + LOG_DBG("MSE MAS abort request"); + + if ((s->_cb != NULL) && (s->_cb->abort != NULL)) + { + s->_cb->abort(s, buf); + return; + } + + LOG_WRN("No cb for abort req"); + err = bt_obex_abort_rsp(server, BT_OBEX_RSP_CODE_NOT_IMPL, NULL); + if (err != 0) + { + LOG_ERR("Failed to send abort rsp: %d", err); + } +} + +static void mse_mas_setpath(struct bt_obex_server *server, + uint8_t flags, struct net_buf *buf) +{ + struct bt_map_mse_mas *s = CONTAINER_OF(server, struct bt_map_mse_mas, + _server); + int err; + + LOG_DBG("MSE MAS setpath request, flags: 0x%02x", flags); + + if ((s->_cb != NULL) && (s->_cb->set_folder != NULL)) + { + s->_cb->set_folder(s, flags, buf); + return; + } + + LOG_WRN("No cb for set_folder req"); + err = bt_obex_setpath_rsp(server, BT_OBEX_RSP_CODE_NOT_IMPL, NULL); + if (err != 0) + { + LOG_ERR("Failed to send set_folder rsp: %d", err); + } +} + +static const struct bt_obex_server_ops mse_mas_ops = +{ + .connect = mse_mas_connect, + .disconnect = mse_mas_disconnect, + .put = mse_mas_put, + .get = mse_mas_get, + .abort = mse_mas_abort, + .setpath = mse_mas_setpath, + .action = NULL, +}; + +int bt_map_mse_mas_register(struct bt_map_mse_mas *mse_mas, + const struct bt_map_mse_mas_cb *cb) +{ + int err; + + if (mse_mas == NULL || cb == NULL) + { + return -EINVAL; + } + + mse_mas->_cb = cb; + mse_mas->_server.ops = &mse_mas_ops; + mse_mas->_server.obex = &mse_mas->goep.obex; + mse_mas->_conn_id = map_get_connect_id(); + + err = bt_obex_server_register(&mse_mas->_server, map_mas_uuid); + if (err != 0) + { + LOG_ERR("Failed to register MSE MAS obex server: %d", err); + return err; + } + + LOG_DBG("MSE MAS registered"); + return 0; +} + +#define MAS_RFCOMM_SERVER(server) CONTAINER_OF(server, struct bt_map_mse_mas_rfcomm_server, server) +#define MAS_L2CAP_SERVER(server) CONTAINER_OF(server, struct bt_map_mse_mas_l2cap_server, server) +static int mse_mas_accept(struct bt_conn *conn, + void *server, uint8_t type, struct bt_goep **goep) +{ + struct bt_map_mse_mas *mse_mas = NULL; + int err; + + LOG_DBG("MSE MAS %s accept", + type == MAP_TRANSPORT_TYPE_RFCOMM ? "RFCOMM" : "L2CAP"); + + if (type == MAP_TRANSPORT_TYPE_RFCOMM) + { + struct bt_map_mse_mas_rfcomm_server *rfcomm_server = + MAS_RFCOMM_SERVER(server); + + if (rfcomm_server->accept == NULL) + { + return -ENOTSUP; + } + + err = rfcomm_server->accept(conn, rfcomm_server, &mse_mas); + } + else + { + struct bt_map_mse_mas_l2cap_server *l2cap_server = + MAS_L2CAP_SERVER(server); + + if (l2cap_server->accept == NULL) + { + return -ENOTSUP; + } + + err = l2cap_server->accept(conn, l2cap_server, &mse_mas); + } + + if (err != 0) + { + LOG_WRN("Incoming connection rejected"); + return err; + } + + DEBUGASSERT(mse_mas != NULL && mse_mas->_cb != NULL); + + mse_mas->goep.transport_ops = &mse_mas_transport_ops; + if (type == MAP_TRANSPORT_TYPE_RFCOMM) + { + BT_GOEP_INIT_V1(&mse_mas->goep, &mse_mas->goep_transport.v1); + } + else + { + BT_GOEP_INIT_V2(&mse_mas->goep, &mse_mas->goep_transport.v2); + } + + *goep = &mse_mas->goep; + atomic_set(&mse_mas->_transport_state, BT_MAP_TRANSPORT_STATE_CONNECTING); + + return 0; +} + +static int mse_mas_rfcomm_accept(struct bt_conn *conn, + struct bt_goep_transport_rfcomm_server *server, + struct bt_goep **goep) +{ + return mse_mas_accept(conn, server, MAP_TRANSPORT_TYPE_RFCOMM, goep); +} + +int bt_map_mse_mas_rfcomm_register( + struct bt_map_mse_mas_rfcomm_server *server) +{ + if (server == NULL || server->accept == NULL) + { + return -EINVAL; + } + + LOG_DBG("Registering MSE MAS RFCOMM server"); + server->server.accept = mse_mas_rfcomm_accept; + + return bt_goep_transport_rfcomm_server_register(&server->server); +} + +static int mse_mas_l2cap_accept( + struct bt_conn *conn, struct bt_goep_transport_l2cap_server *server, + struct bt_goep **goep) +{ + return mse_mas_accept(conn, server, MAP_TRANSPORT_TYPE_L2CAP, goep); +} + +int bt_map_mse_mas_l2cap_register(struct bt_map_mse_mas_l2cap_server *server) +{ + if (server == NULL || server->accept == NULL) + { + return -EINVAL; + } + + LOG_DBG("Registering MSE MAS L2CAP server"); + server->server.accept = mse_mas_l2cap_accept; + + return bt_goep_transport_l2cap_server_register(&server->server); +} + +struct net_buf *bt_map_mse_mas_create_pdu(struct bt_map_mse_mas *mse_mas, + struct net_buf_pool *pool) +{ + if (mse_mas == NULL) + { + LOG_ERR("Invalid parameter"); + return NULL; + } + + return bt_goep_create_pdu(&mse_mas->goep, pool); +} + +int bt_map_mse_mas_connect(struct bt_map_mse_mas *mse_mas, + uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t val[BT_UUID_SIZE_128]; + int err; + bool allocated = false; + + if (mse_mas == NULL) + { + return -EINVAL; + } + + if (atomic_read(&mse_mas->_state) != BT_MAP_STATE_CONNECTING) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&mse_mas->_state)); + return -EINVAL; + } + + LOG_DBG("MSE MAS connect response, rsp_code: 0x%02x", rsp_code); + + if (buf == NULL) + { + buf = bt_goep_create_pdu(&mse_mas->goep, NULL); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } + + allocated = true; + } + else + { + if (bt_obex_has_header(buf, BT_OBEX_HEADER_ID_WHO)) + { + err = map_check_who(&map_mas_uuid->uuid, buf); + if (err != 0) + { + return err; + } + } + + if (bt_obex_has_header(buf, BT_OBEX_HEADER_ID_CONN_ID)) + { + err = map_check_conn_id(mse_mas->_conn_id, buf); + if (err != 0) + { + return err; + } + } + } + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + if (!bt_obex_has_header(buf, BT_OBEX_HEADER_ID_WHO)) + { + sys_memcpy_swap(val, map_mas_uuid->val, sizeof(val)); + err = bt_obex_add_header_who(buf, sizeof(val), val); + if (err != 0) + { + LOG_ERR("Failed to add header who: %d", err); + goto failed; + } + } + + if (!bt_obex_has_header(buf, BT_OBEX_HEADER_ID_CONN_ID)) + { + err = bt_obex_add_header_conn_id(buf, mse_mas->_conn_id); + if (err != 0) + { + LOG_ERR("Failed to add header conn id: %d", err); + goto failed; + } + } + } + + err = bt_obex_connect_rsp(&mse_mas->_server, rsp_code, + mse_mas->goep.obex.rx.mtu, buf); + if (err != 0) + { + LOG_ERR("Failed to send conn rsp: %d", err); + goto failed; + } + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + atomic_set(&mse_mas->_state, BT_MAP_STATE_CONNECTED); + } + else + { + atomic_set(&mse_mas->_state, BT_MAP_STATE_DISCONNECTED); + } + + return 0; + +failed: + if (allocated) + { + net_buf_unref(buf); + } + + return err; +} + +int bt_map_mse_mas_disconnect(struct bt_map_mse_mas *mse_mas, + uint8_t rsp_code, struct net_buf *buf) +{ + int err; + + if (mse_mas == NULL) + { + return -EINVAL; + } + + if (atomic_read(&mse_mas->_state) != BT_MAP_STATE_DISCONNECTING) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&mse_mas->_state)); + return -EINVAL; + } + + LOG_DBG("MSE MAS disconnect response, rsp_code: 0x%02x", rsp_code); + + err = bt_obex_disconnect_rsp(&mse_mas->_server, rsp_code, buf); + if (err != 0) + { + LOG_ERR("Failed to send disconnect rsp: %d", err); + return err; + } + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + atomic_set(&mse_mas->_state, BT_MAP_STATE_DISCONNECTED); + } + else + { + atomic_set(&mse_mas->_state, BT_MAP_STATE_CONNECTED); + } + + return 0; +} + +int bt_map_mse_mas_abort(struct bt_map_mse_mas *mse_mas, + uint8_t rsp_code, struct net_buf *buf) +{ + int err; + + if (mse_mas == NULL) + { + return -EINVAL; + } + + if (atomic_read(&mse_mas->_state) != BT_MAP_STATE_CONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&mse_mas->_state)); + return -EINVAL; + } + + LOG_DBG("MSE MAS abort response, rsp_code: 0x%02x", rsp_code); + + err = bt_obex_abort_rsp(&mse_mas->_server, rsp_code, buf); + if (err != 0) + { + LOG_ERR("Failed to send abort rsp: %d", err); + return err; + } + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + mse_mas_clear_pending_request(mse_mas); + } + + return 0; +} + +int bt_map_mse_mas_set_folder(struct bt_map_mse_mas *mse_mas, + uint8_t rsp_code, struct net_buf *buf) +{ + int err; + + if (mse_mas == NULL) + { + return -EINVAL; + } + + if (atomic_read(&mse_mas->_state) != BT_MAP_STATE_CONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&mse_mas->_state)); + return -EINVAL; + } + + LOG_DBG("MSE MAS set folder response, rsp_code: 0x%02x", rsp_code); + + err = bt_obex_setpath_rsp(&mse_mas->_server, rsp_code, buf); + if (err != 0) + { + LOG_ERR("Failed to send setpath rsp: %d", err); + return err; + } + + return 0; +} + +static int mse_mas_get_or_put_rsp( + struct bt_map_mse_mas *mse_mas, bool is_get, const char *type, + uint8_t rsp_code, struct net_buf *buf) +{ + int err; + + if (mse_mas == NULL) + { + return -EINVAL; + } + + if (atomic_read(&mse_mas->_state) != BT_MAP_STATE_CONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&mse_mas->_state)); + return -EINVAL; + } + + if (mse_mas->_optype != NULL && strcmp(mse_mas->_optype, type) != 0) + { + LOG_ERR("Invalid operation type %s != %s", mse_mas->_optype, type); + return -EINVAL; + } + + LOG_DBG("MSE MAS %s response, type: %s, rsp_code: 0x%02x", + is_get ? "GET" : "PUT", type, + rsp_code); + + if (is_get) + { + if (mse_mas->_opcode != BT_OBEX_OPCODE_GET) + { + LOG_ERR("Operation %u != %u", + mse_mas->_opcode, + BT_OBEX_OPCODE_GET); + return -EINVAL; + } + + err = bt_obex_get_rsp(&mse_mas->_server, rsp_code, buf); + } + else + { + if (mse_mas->_opcode != BT_OBEX_OPCODE_PUT) + { + LOG_ERR("Operation %u != %u", + mse_mas->_opcode, + BT_OBEX_OPCODE_PUT); + return -EINVAL; + } + + err = bt_obex_put_rsp(&mse_mas->_server, rsp_code, buf); + } + + if (err != 0) + { + LOG_ERR("Failed to send get/put rsp: %d", err); + return err; + } + + if (rsp_code != BT_OBEX_RSP_CODE_CONTINUE) + { + mse_mas_clear_pending_request(mse_mas); + } + + return 0; +} + +/* Defines a public bt_map_mse_mas_xxx() API wrapper. + * _op_get : bool – true for GET, false for PUT _NAME : uppercase operation + * token (e.g. SET_NTF_REG) _name : lowercase function name suffix (e.g. + * set_ntf_reg) + */ +#define MAP_MSE_MAS_API(_op_get, _NAME, _name) \ + int bt_map_mse_mas_##_name( \ + struct bt_map_mse_mas *mse_mas, uint8_t rsp_code, \ + struct net_buf *buf) \ + { \ + return mse_mas_get_or_put_rsp(mse_mas, (_op_get), \ + BT_MAP_HDR_TYPE_##_NAME, rsp_code, \ + buf); \ + } + +MAP_MSE_MAS_API(false, SET_NTF_REG, set_ntf_reg) +MAP_MSE_MAS_API(true, GET_FOLDER_LISTING, get_folder_listing) +MAP_MSE_MAS_API(true, GET_MSG_LISTING, get_msg_listing) +MAP_MSE_MAS_API(true, GET_MSG, get_msg) +MAP_MSE_MAS_API(false, SET_MSG_STATUS, set_msg_status) +MAP_MSE_MAS_API(false, PUSH_MSG, push_msg) +MAP_MSE_MAS_API(false, UPDATE_INBOX, update_inbox) +MAP_MSE_MAS_API(true, GET_MAS_INST_INFO, get_mas_inst_info) +MAP_MSE_MAS_API(false, SET_OWNER_STATUS, set_owner_status) +MAP_MSE_MAS_API(true, GET_OWNER_STATUS, get_owner_status) +MAP_MSE_MAS_API(true, GET_CONVO_LISTING, get_convo_listing) +MAP_MSE_MAS_API(false, SET_NTF_FILTER, set_ntf_filter) + +/* MSE MNS Implementation */ + +static void mse_mns_clear_pending_request(struct bt_map_mse_mns *mse_mns) +{ + mse_mns->_rsp_cb = NULL; + mse_mns->_req_type = NULL; +} + +static void mse_mns_transport_connected(struct bt_conn *conn, + struct bt_goep *goep) +{ + struct bt_map_mse_mns *mse_mns = CONTAINER_OF(goep, struct bt_map_mse_mns, + goep); + + LOG_DBG("MSE MNS transport connected"); + atomic_set(&mse_mns->_transport_state, BT_MAP_TRANSPORT_STATE_CONNECTED); + + if (mse_mns->goep.v2 != NULL) + { + if (mse_mns->_cb != NULL && mse_mns->_cb->l2cap_connected != NULL) + { + mse_mns->_cb->l2cap_connected(conn, mse_mns); + } + } + else + { + if (mse_mns->_cb != NULL && mse_mns->_cb->rfcomm_connected != NULL) + { + mse_mns->_cb->rfcomm_connected(conn, mse_mns); + } + } +} + +static void mse_mns_transport_disconnected(struct bt_goep *goep) +{ + struct bt_map_mse_mns *mse_mns = CONTAINER_OF(goep, struct bt_map_mse_mns, + goep); + + LOG_DBG("MSE MNS transport disconnected"); + atomic_set(&mse_mns->_transport_state, + BT_MAP_TRANSPORT_STATE_DISCONNECTED); + atomic_set(&mse_mns->_state, BT_MAP_STATE_DISCONNECTED); + mse_mns_clear_pending_request(mse_mns); + + if (mse_mns->goep.v2 != NULL) + { + if (mse_mns->_cb != NULL && mse_mns->_cb->l2cap_disconnected != NULL) + { + mse_mns->_cb->l2cap_disconnected(mse_mns); + } + } + else + { + if (mse_mns->_cb != NULL && mse_mns->_cb->rfcomm_disconnected != NULL) + { + mse_mns->_cb->rfcomm_disconnected(mse_mns); + } + } +} + +static struct bt_goep_transport_ops mse_mns_transport_ops = +{ + .connected = mse_mns_transport_connected, + .disconnected = mse_mns_transport_disconnected, +}; + +static int mse_mns_transport_connect( + struct bt_conn *conn, struct bt_map_mse_mns *mse_mns, + const struct bt_map_mse_mns_cb *cb, uint8_t type, uint16_t psm, + uint8_t channel) +{ + int err; + + if (conn == NULL || mse_mns == NULL || cb == NULL) + { + return -EINVAL; + } + + mse_mns->_cb = cb; + + if (atomic_read(&mse_mns->_transport_state) != + BT_MAP_TRANSPORT_STATE_DISCONNECTED) + { + LOG_DBG("MSE MNS transport connect in progress"); + return -EINPROGRESS; + } + + LOG_DBG("MSE MNS transport connecting, type %u", type); + mse_mns->goep.transport_ops = &mse_mns_transport_ops; + atomic_set(&mse_mns->_transport_state, BT_MAP_TRANSPORT_STATE_CONNECTING); + + if (type == MAP_TRANSPORT_TYPE_L2CAP) + { + BT_GOEP_INIT_V2(&mse_mns->goep, &mse_mns->goep_transport.v2); + LOG_DBG("Connecting via L2CAP PSM 0x%04x", psm); + err = bt_goep_transport_l2cap_connect(conn, &mse_mns->goep, psm); + } + else + { + BT_GOEP_INIT_V1(&mse_mns->goep, &mse_mns->goep_transport.v1); + LOG_DBG("Connecting via RFCOMM channel %u", channel); + err = bt_goep_transport_rfcomm_connect(conn, &mse_mns->goep, channel); + } + + if (err != 0) + { + LOG_ERR("Transport connect failed: %d", err); + atomic_set(&mse_mns->_transport_state, + BT_MAP_TRANSPORT_STATE_DISCONNECTED); + } + + return err; +} + +int bt_map_mse_mns_rfcomm_connect( + struct bt_conn *conn, struct bt_map_mse_mns *mse_mns, + const struct bt_map_mse_mns_cb *cb, uint8_t channel) +{ + if (channel == 0) + { + return -EINVAL; + } + + return mse_mns_transport_connect(conn, mse_mns, cb, + MAP_TRANSPORT_TYPE_RFCOMM, 0, channel); +} + +int bt_map_mse_mns_l2cap_connect( + struct bt_conn *conn, struct bt_map_mse_mns *mse_mns, + const struct bt_map_mse_mns_cb *cb, uint16_t psm) +{ + if (psm == 0) + { + return -EINVAL; + } + + return mse_mns_transport_connect(conn, mse_mns, cb, + MAP_TRANSPORT_TYPE_L2CAP, + psm, 0); +} + +static int mse_mns_transport_disconnect(struct bt_map_mse_mns *mse_mns, + uint8_t type) +{ + int err; + + if (mse_mns == NULL) + { + return -EINVAL; + } + + if (atomic_read( + &mse_mns->_transport_state) != BT_MAP_TRANSPORT_STATE_CONNECTED) + { + LOG_DBG("MSE MNS transport disconnect in progress"); + return -ENOTCONN; + } + + LOG_DBG("MSE MNS transport disconnecting, type %u", type); + atomic_set(&mse_mns->_transport_state, + BT_MAP_TRANSPORT_STATE_DISCONNECTING); + + if (type == MAP_TRANSPORT_TYPE_L2CAP) + { + err = bt_goep_transport_l2cap_disconnect(&mse_mns->goep); + } + else + { + err = bt_goep_transport_rfcomm_disconnect(&mse_mns->goep); + } + + if (err != 0) + { + LOG_ERR("Transport disconnect failed: %d", err); + atomic_set(&mse_mns->_transport_state, + BT_MAP_TRANSPORT_STATE_CONNECTED); + } + + return err; +} + +int bt_map_mse_mns_rfcomm_disconnect(struct bt_map_mse_mns *mse_mns) +{ + return mse_mns_transport_disconnect(mse_mns, MAP_TRANSPORT_TYPE_RFCOMM); +} + +int bt_map_mse_mns_l2cap_disconnect(struct bt_map_mse_mns *mse_mns) +{ + return mse_mns_transport_disconnect(mse_mns, MAP_TRANSPORT_TYPE_L2CAP); +} + +static void mse_mns_connect( + struct bt_obex_client *client, uint8_t rsp_code, uint8_t version, + uint16_t mopl, struct net_buf *buf) +{ + struct bt_map_mse_mns *c = CONTAINER_OF(client, struct bt_map_mse_mns, + _client); + int err; + + LOG_DBG("MSE MNS connected, rsp_code: 0x%02x, version: 0x%02x, mopl: %u", + rsp_code, version, + mopl); + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + atomic_set(&c->_state, BT_MAP_STATE_CONNECTED); + err = bt_obex_get_header_conn_id(buf, &c->_conn_id); + if (err != 0) + { + LOG_ERR("Failed to get connection ID: %d", err); + mse_mns_transport_disconnect(c, + c->goep.v2 != + NULL ? MAP_TRANSPORT_TYPE_L2CAP + : + MAP_TRANSPORT_TYPE_RFCOMM); + } + else + { + LOG_DBG("Connection ID: %u", c->_conn_id); + } + } + else + { + atomic_set(&c->_state, BT_MAP_STATE_DISCONNECTED); + } + + if ((c->_cb != NULL) && (c->_cb->connect != NULL)) + { + c->_cb->connect(c, rsp_code, version, mopl, buf); + } +} + +static void mse_mns_disconnect(struct bt_obex_client *client, + uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_map_mse_mns *c = CONTAINER_OF(client, struct bt_map_mse_mns, + _client); + + LOG_DBG("MSE MNS disconnected, rsp_code: 0x%02x", rsp_code); + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + atomic_set(&c->_state, BT_MAP_STATE_DISCONNECTED); + mse_mns_clear_pending_request(c); + } + else + { + atomic_set(&c->_state, BT_MAP_STATE_CONNECTED); + } + + if ((c->_cb != NULL) && (c->_cb->disconnect != NULL)) + { + c->_cb->disconnect(c, rsp_code, buf); + } +} + +static void mse_mns_put(struct bt_obex_client *client, + uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_map_mse_mns *c = CONTAINER_OF(client, struct bt_map_mse_mns, + _client); + mse_mns_cb_t cb = c->_rsp_cb; + + LOG_DBG("MSE MNS PUT response, rsp_code: 0x%02x", rsp_code); + + if (rsp_code != BT_OBEX_RSP_CODE_CONTINUE) + { + mse_mns_clear_pending_request(c); + } + + if (cb != NULL) + { + cb(c, rsp_code, buf); + } + else + { + LOG_WRN("No cb for rsp"); + } +} + +static void mse_mns_abort(struct bt_obex_client *client, + uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_map_mse_mns *c = CONTAINER_OF(client, struct bt_map_mse_mns, + _client); + int err; + + LOG_DBG("MSE MNS abort response, rsp_code: 0x%02x", rsp_code); + + if ((c->_cb != NULL) && (c->_cb->abort != NULL)) + { + c->_cb->abort(c, rsp_code, buf); + } + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + mse_mns_clear_pending_request(c); + return; + } + + err = bt_map_mse_mns_disconnect(c, NULL); + if (err != 0) + { + LOG_ERR("Failed to send MSE MNS disconnect: %d", err); + } +} + +static const struct bt_obex_client_ops mse_mns_ops = +{ + .connect = mse_mns_connect, + .disconnect = mse_mns_disconnect, + .put = mse_mns_put, + .get = NULL, + .abort = mse_mns_abort, + .setpath = NULL, + .action = NULL, +}; + +struct net_buf *bt_map_mse_mns_create_pdu(struct bt_map_mse_mns *mse_mns, + struct net_buf_pool *pool) +{ + if (mse_mns == NULL) + { + LOG_ERR("Invalid parameter"); + return NULL; + } + + return bt_goep_create_pdu(&mse_mns->goep, pool); +} + +int bt_map_mse_mns_connect(struct bt_map_mse_mns *mse_mns, + struct net_buf *buf) +{ + uint8_t val[BT_UUID_SIZE_128]; + int err; + bool allocated = false; + + if (mse_mns == NULL) + { + return -EINVAL; + } + + if (atomic_read(&mse_mns->_state) != BT_MAP_STATE_DISCONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&mse_mns->_state)); + return -EINVAL; + } + + LOG_DBG("MSE MNS connecting"); + + if (buf == NULL) + { + buf = bt_goep_create_pdu(&mse_mns->goep, NULL); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } + + allocated = true; + } + + if (bt_obex_has_header(buf, BT_OBEX_HEADER_ID_TARGET)) + { + err = map_check_target(&map_mns_uuid->uuid, buf); + if (err != 0) + { + goto failed; + } + } + else + { + sys_memcpy_swap(val, map_mns_uuid->val, sizeof(val)); + err = bt_obex_add_header_target(buf, sizeof(val), val); + if (err != 0) + { + LOG_ERR("Failed to add header target: %d", err); + goto failed; + } + } + + mse_mns->_client.ops = &mse_mns_ops; + mse_mns->_client.obex = &mse_mns->goep.obex; + + err = bt_obex_connect(&mse_mns->_client, mse_mns->goep.obex.rx.mtu, buf); + if (err != 0) + { + LOG_ERR("Failed to send conn req: %d", err); + goto failed; + } + + atomic_set(&mse_mns->_state, BT_MAP_STATE_CONNECTING); + return 0; + +failed: + if (allocated) + { + net_buf_unref(buf); + } + + return err; +} + +int bt_map_mse_mns_disconnect(struct bt_map_mse_mns *mse_mns, + struct net_buf *buf) +{ + int err; + bool allocated = false; + + if (mse_mns == NULL) + { + return -EINVAL; + } + + if (atomic_read(&mse_mns->_state) != BT_MAP_STATE_CONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&mse_mns->_state)); + return -EINVAL; + } + + LOG_DBG("MSE MNS disconnecting"); + + if (buf == NULL) + { + buf = bt_goep_create_pdu(&mse_mns->goep, NULL); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } + + allocated = true; + } + + if (!bt_obex_has_header(buf, BT_OBEX_HEADER_ID_CONN_ID)) + { + err = bt_obex_add_header_conn_id(buf, mse_mns->_conn_id); + if (err != 0) + { + LOG_ERR("Failed to add header conn id: %d", err); + goto failed; + } + } + else + { + err = map_check_conn_id(mse_mns->_conn_id, buf); + if (err != 0) + { + goto failed; + } + } + + err = bt_obex_disconnect(&mse_mns->_client, buf); + if (err != 0) + { + LOG_ERR("Failed to send disconn req: %d", err); + goto failed; + } + + atomic_set(&mse_mns->_state, BT_MAP_STATE_DISCONNECTING); + return 0; +failed: + if (allocated) + { + net_buf_unref(buf); + } + + return err; +} + +int bt_map_mse_mns_abort(struct bt_map_mse_mns *mse_mns, struct net_buf *buf) +{ + int err; + bool allocated = false; + + if (mse_mns == NULL) + { + return -EINVAL; + } + + if (atomic_read(&mse_mns->_state) != BT_MAP_STATE_CONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&mse_mns->_state)); + return -EINVAL; + } + + if (mse_mns->_rsp_cb == NULL) + { + LOG_ERR("No operation is ongoing"); + return -EINVAL; + } + + LOG_DBG("MSE MNS aborting"); + + if (buf == NULL) + { + buf = bt_goep_create_pdu(&mse_mns->goep, NULL); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } + + allocated = true; + } + + if (bt_obex_has_header(buf, BT_OBEX_HEADER_ID_CONN_ID)) + { + err = map_check_conn_id(mse_mns->_conn_id, buf); + if (err != 0) + { + goto failed; + } + } + else + { + err = bt_obex_add_header_conn_id(buf, mse_mns->_conn_id); + if (err != 0) + { + LOG_ERR("Failed to add header conn id: %d", err); + goto failed; + } + } + + err = bt_obex_abort(&mse_mns->_client, buf); + if (err != 0) + { + LOG_ERR("Failed to send abort request: %d", err); + goto failed; + } + + return 0; + +failed: + if (allocated) + { + net_buf_unref(buf); + } + + return err; +} + +static int mse_mns_get_req_cb( + struct bt_map_mse_mns *mse_mns, const char *type, struct net_buf *buf, + bool is_get, mse_mns_cb_t *cb, const char **req_type) +{ + const uint8_t *type_data; + uint16_t len; + int err; + + err = bt_obex_get_header_type(buf, &len, &type_data); + if (err != 0) + { + LOG_WRN("Failed to get type header %d", err); + return -EINVAL; + } + + if (len <= strlen(type_data)) + { + LOG_WRN("Invalid type string len %u <= %u", len, strlen(type_data)); + return -EINVAL; + } + + *cb = NULL; + ARRAY_FOR_EACH(map_mns_functions, i) + { + if (strcmp(map_mns_functions[i].type, type) != 0) + { + continue; + } + + if (strcmp(map_mns_functions[i].type, type_data) != 0) + { + continue; + } + + if (map_mns_functions[i].op_get != is_get) + { + continue; + } + + if (!has_required_hdrs(buf, &map_mns_functions[i].hdr)) + { + continue; + } + + if (!has_required_app_params(buf, &map_mns_functions[i].ap)) + { + continue; + } + + if (map_mns_functions[i].get_mse_mns_cb == NULL) + { + continue; + } + + *cb = map_mns_functions[i].get_mse_mns_cb(mse_mns); + if (*cb == NULL) + { + continue; + } + + if (req_type != NULL) + { + *req_type = map_mns_functions[i].type; + } + break; + } + + if (*cb == NULL) + { + LOG_WRN("Unsupported request"); + return -EINVAL; + } + + return 0; +} + +static int mse_mns_get_or_put( + struct bt_map_mse_mns *mse_mns, bool is_get, const char *type, bool final, + struct net_buf *buf) +{ + int err; + mse_mns_cb_t cb; + mse_mns_cb_t old_cb; + const char *req_type; + const char *old_req_type; + + if (mse_mns == NULL || buf == NULL) + { + return -EINVAL; + } + + if (atomic_read(&mse_mns->_state) != BT_MAP_STATE_CONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&mse_mns->_state)); + return -EINVAL; + } + + LOG_DBG("MSE MNS %s request, type: %s, final: %d", is_get ? "GET" : "PUT", + type, final); + + old_cb = mse_mns->_rsp_cb; + old_req_type = mse_mns->_req_type; + + if (mse_mns->_rsp_cb == NULL || bt_obex_has_header(buf, + BT_OBEX_HEADER_ID_TYPE)) + { + if (is_get && mse_mns->goep.v2 != NULL && !bt_obex_has_header(buf, + BT_OBEX_HEADER_ID_SRM)) + { + LOG_ERR("Failed to get SRM"); + return -ENODATA; + } + + err = mse_mns_get_req_cb(mse_mns, type, buf, is_get, &cb, &req_type); + if (err != 0) + { + LOG_ERR("Invalid request: %d", err); + return err; + } + + if (mse_mns->_rsp_cb != NULL && cb != mse_mns->_rsp_cb) + { + LOG_ERR("Previous operation is not completed"); + return -EINVAL; + } + + mse_mns->_rsp_cb = cb; + mse_mns->_req_type = req_type; + } + + if (mse_mns->_req_type != NULL && strcmp(mse_mns->_req_type, type) != 0) + { + LOG_ERR("Invalid request type %s != %s", mse_mns->_req_type, type); + err = -EINVAL; + goto failed; + } + + if (bt_obex_has_header(buf, BT_OBEX_HEADER_ID_CONN_ID)) + { + err = map_check_conn_id(mse_mns->_conn_id, buf); + if (err != 0) + { + goto failed; + } + } + + if (!is_get && final && !bt_obex_has_header(buf, + BT_OBEX_HEADER_ID_END_BODY)) + { + LOG_ERR("OBEX header (End of Body) is missing"); + err = -ENODATA; + goto failed; + } + + if (is_get) + { + err = bt_obex_get(&mse_mns->_client, final, buf); + } + else + { + err = bt_obex_put(&mse_mns->_client, final, buf); + } + +failed: + if (err != 0) + { + mse_mns->_rsp_cb = old_cb; + mse_mns->_req_type = old_req_type; + LOG_ERR("Failed to send get/put req: %d", err); + } + + return err; +} + +int bt_map_mse_mns_send_event(struct bt_map_mse_mns *mse_mns, + bool final, struct net_buf *buf) +{ + return mse_mns_get_or_put(mse_mns, false, BT_MAP_HDR_TYPE_SEND_EVENT, + final, + buf); +} +#endif /* CONFIG_BT_MAP_MSE */ diff --git a/libs/zblue/host/classic/obex.c b/libs/zblue/host/classic/obex.c new file mode 100644 index 0000000000000..6346b90accddc --- /dev/null +++ b/libs/zblue/host/classic/obex.c @@ -0,0 +1,5759 @@ +/**************************************************************************** + * libs/zblue/host/classic/obex.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* obex.c - IrDA Object Exchange Protocol handling */ + +/* Copyright 2024-2026 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_BT_OBEX_AUTH) +#include +#include +#endif /* CONFIG_BT_OBEX_AUTH */ + +#include "obex_internal.h" + +#define LOG_LEVEL CONFIG_BT_GOEP_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_obex); + +#define OBEX_SERVER(node) CONTAINER_OF(node, struct bt_obex_server, _node) + +/* bt_obex flags */ + +enum +{ + BT_OBEX_HAS_TARGET, /* Has target_header */ + BT_OBEX_REQ_1ST, /* First Request */ + BT_OBEX_REQ_SRM, /* Request SRM Set */ + BT_OBEX_REQ_SRMP, /* Request SRMP Set */ + BT_OBEX_RSP_SRM, /* Response SRM Set */ + BT_OBEX_RSP_SRMP, /* Response SRMP Set */ + BT_OBEX_RSP_RECV, /* Response received */ + BT_OBEX_REQ_RECV, /* Request received */ + BT_OBEX_REQ_F_BIT, /* Request Final bit set */ +}; + +/* SRM disable value */ +#define BT_OBEX_SRM_DISABLE 0 + +/* SRM enable value */ +#define BT_OBEX_SRM_ENABLE 1 + +/* SRMP wait value */ +#define BT_OBEX_SRMP_WAIT 1 + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static struct net_buf *obex_alloc_buf(struct bt_obex *obex) +{ + struct net_buf *buf; + + if (obex == NULL || obex->_transport_ops == NULL || + obex->_transport_ops->alloc_buf == NULL) + { + LOG_WRN("Buffer allocation is unsupported"); + return NULL; + } + + buf = obex->_transport_ops->alloc_buf(obex, NULL); + if (buf == NULL) + { + LOG_WRN("Fail to alloc buffer"); + } + + return buf; +} + +static int obex_send(struct bt_obex *obex, + uint16_t mopl, struct net_buf *buf) +{ + if (obex == NULL || obex->_transport_ops == NULL || + obex->_transport_ops->send == NULL) + { + LOG_WRN("OBEX sending is unsupported"); + return -EIO; + } + + if (buf->len > mopl) + { + LOG_WRN("Data length too long (%d > %d)", buf->len, mopl); + return -EMSGSIZE; + } + + return obex->_transport_ops->send(obex, buf); +} + +static int obex_transport_disconn(struct bt_obex *obex) +{ + int err; + + if (obex == NULL || obex->_transport_ops == NULL || + obex->_transport_ops->disconnect == NULL) + { + LOG_WRN("OBEX transport disconn is unsupported"); + return -EIO; + } + + err = obex->_transport_ops->disconnect(obex); + if (err != 0) + { + LOG_ERR("Fail to disconnect transport (err %d)", err); + } + + return err; +} + +struct bt_obex_has_header +{ + uint8_t id; + bool found; +}; + +static bool bt_obex_has_header_cb(struct bt_obex_hdr *hdr, void *user_data) +{ + struct bt_obex_has_header *data; + + data = user_data; + + if (hdr->id == data->id) + { + data->found = true; + return false; + } + + return true; +} + +bool bt_obex_has_header(struct net_buf *buf, uint8_t id) +{ + struct bt_obex_has_header data; + int err; + + if (buf == NULL) + { + LOG_WRN("Invalid parameter"); + return false; + } + + data.id = id; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_has_header_cb, &data); + if (err != 0) + { + return false; + } + + return data.found; +} + +int bt_obex_make_uuid(union bt_obex_uuid *uuid, + const uint8_t *data, uint16_t len) +{ + switch (len) + { + case BT_UUID_SIZE_16: + uuid->uuid.type = BT_UUID_TYPE_16; + uuid->u16.val = sys_get_be16(data); + break; + case BT_UUID_SIZE_32: + uuid->uuid.type = BT_UUID_TYPE_32; + uuid->u32.val = sys_get_be32(data); + break; + case BT_UUID_SIZE_128: + uuid->uuid.type = BT_UUID_TYPE_128; + sys_memcpy_swap(uuid->u128.val, data, len); + break; + default: + LOG_WRN("Unsupported UUID len %u", len); + return -EINVAL; + } + + return 0; +} + +struct server_handler +{ + uint8_t opcode; + uint16_t min_len; + int (*handler)(struct bt_obex_server *server, + uint16_t len, + struct net_buf *buf); +}; + +static int obex_server_connect(struct bt_obex_server *server, + uint16_t len, struct net_buf *buf) +{ + struct bt_obex_conn_req_hdr *hdr; + struct bt_obex_conn_rsp_hdr *rsp_conn_hdr; + struct bt_obex_rsp_hdr *rsp_hdr; + uint8_t version; + __maybe_unused uint8_t flags; + uint16_t mopl; + uint8_t rsp_code; + int err; + uint16_t target_len; + const uint8_t *target_data; + + LOG_DBG(""); + + if ((len != buf->len) || (buf->len < sizeof(*hdr))) + { + LOG_WRN("Invalid packet size"); + rsp_code = BT_OBEX_RSP_CODE_PRECON_FAIL; + goto failed; + } + + if (atomic_read(&server->_state) != BT_OBEX_DISCONNECTED) + { + LOG_WRN("Invalid state, connect refused"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + if (server->ops->connect == NULL) + { + LOG_WRN("Conn req handling not implemented"); + rsp_code = BT_OBEX_RSP_CODE_NOT_IMPL; + goto failed; + } + + if (!atomic_cmpxchg_value(&server->_opcode, 0, BT_OBEX_OPCODE_CONNECT)) + { + LOG_WRN("Unexpected conn request"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + version = net_buf_pull_u8(buf); + flags = net_buf_pull_u8(buf); + mopl = net_buf_pull_be16(buf); + + atomic_set_bit_to(&server->_flags, BT_OBEX_HAS_TARGET, + bt_obex_has_header(buf, BT_OBEX_HEADER_ID_TARGET)); + + err = bt_obex_get_header_target(buf, &target_len, &target_data); + if (err != 0 && atomic_test_bit(&server->_flags, BT_OBEX_HAS_TARGET)) + { + LOG_WRN("Invalid target header"); + rsp_code = BT_OBEX_RSP_CODE_PRECON_FAIL; + goto failed; + } + + if (err == 0) + { + err = bt_obex_make_uuid(&server->_target, target_data, target_len); + if (err != 0) + { + LOG_WRN("Invalid UUID header"); + rsp_code = BT_OBEX_RSP_CODE_PRECON_FAIL; + goto failed; + } + } + + LOG_DBG("version %u, flags %u, mopl %u", version, flags, mopl); + + if (mopl < BT_OBEX_MIN_MTU) + { + LOG_WRN("Invalid MOPL (%d < %d)", mopl, BT_OBEX_MIN_MTU); + rsp_code = BT_OBEX_RSP_CODE_PRECON_FAIL; + goto failed; + } + + if (mopl > server->obex->tx.mtu) + { + LOG_WRN("MOPL exceeds MTU (%d > %d)", mopl, server->obex->tx.mtu); + /* In mainstream mobile operating system settings, such as IPhone and + * Android, MOPL is usually greater than the MTU of rfcomm or l2cap. + * Therefore, the smaller value among them is selected as the final + * MOPL and transmitted to the application by callback. + */ + + mopl = server->obex->tx.mtu; + } + + server->tx.mopl = mopl; + + atomic_set(&server->_state, BT_OBEX_CONNECTING); + server->ops->connect(server, version, mopl, buf); + return 0; + +failed: + buf = obex_alloc_buf(server->obex); + if (buf == NULL) + { + LOG_WRN("Cannot allocate buffer"); + return -ENOBUFS; + } + + rsp_hdr = net_buf_add(buf, sizeof(*rsp_hdr)); + rsp_hdr->code = rsp_code; + rsp_conn_hdr = net_buf_add(buf, sizeof(*rsp_conn_hdr)); + rsp_conn_hdr->flags = 0; + rsp_conn_hdr->mopl = 0; + rsp_conn_hdr->version = BT_OBEX_VERSION; + rsp_hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(server->obex, server->tx.mopl, buf); + if (err != 0) + { + net_buf_unref(buf); + } + + return err; +} + +static int obex_server_disconn(struct bt_obex_server *server, + uint16_t len, struct net_buf *buf) +{ + struct bt_obex_rsp_hdr *rsp_hdr; + uint8_t rsp_code; + int err; + + LOG_DBG(""); + + if (len != buf->len) + { + LOG_WRN("Invalid packet size"); + rsp_code = BT_OBEX_RSP_CODE_PRECON_FAIL; + goto failed; + } + + if (atomic_read(&server->_state) != BT_OBEX_CONNECTED) + { + LOG_WRN("Invalid state, connect is not established"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + if (server->ops->disconnect == NULL) + { + LOG_WRN("Conn req handling not implemented"); + rsp_code = BT_OBEX_RSP_CODE_NOT_IMPL; + goto failed; + } + + if (!atomic_cmpxchg_value(&server->_opcode, 0, BT_OBEX_OPCODE_DISCONN)) + { + LOG_WRN("Unexpected disconn request"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + atomic_set(&server->_state, BT_OBEX_DISCONNECTING); + server->ops->disconnect(server, buf); + return 0; + +failed: + buf = obex_alloc_buf(server->obex); + if (buf == NULL) + { + LOG_WRN("Cannot allocate buffer"); + return -ENOBUFS; + } + + rsp_hdr = net_buf_add(buf, sizeof(*rsp_hdr)); + rsp_hdr->code = rsp_code; + rsp_hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(server->obex, server->tx.mopl, buf); + if (err != 0) + { + net_buf_unref(buf); + } + + return err; +} + +static bool obex_srm_is_valid(uint8_t srm) +{ + if (srm == BT_OBEX_SRM_DISABLE) + { + return true; + } + + if (srm == BT_OBEX_SRM_ENABLE) + { + return true; + } + + return false; +} + +static bool obex_srm_is_enabled(uint8_t srm) +{ + if (srm == BT_OBEX_SRM_ENABLE) + { + return true; + } + + return false; +} + +static int obex_server_parse_srm( + struct bt_obex_server *server, struct net_buf *buf, bool first, int bit) +{ + int err; + uint8_t srm; + + err = bt_obex_get_header_srm(buf, &srm); + if (err != 0) + { + /* No SRM header included */ + + return 0; + } + + if (!obex_srm_is_valid(srm)) + { + LOG_ERR("SRM value is invalid"); + return -EINVAL; + } + + if (!obex_srm_is_enabled(srm)) + { + /* SRM is disabled */ + + return 0; + } + + if (!first) + { + LOG_WRN("SRM header should not be included"); + return -EINVAL; + } + + atomic_set_bit(&server->_flags, bit); + + return 0; +} + +static int obex_server_parse_req_srm(struct bt_obex_server *server, + struct net_buf *buf, bool first) +{ + int err; + + if (first) + { + atomic_set(&server->_flags, 0); + } + + err = obex_server_parse_srm(server, buf, first, BT_OBEX_REQ_SRM); + if (err != 0) + { + return err; + } + + atomic_set_bit_to(&server->_flags, BT_OBEX_REQ_1ST, first); + return 0; +} + +static int obex_server_parse_rsp_srm(struct bt_obex_server *server, + struct net_buf *buf, bool first) +{ + return obex_server_parse_srm(server, buf, first, BT_OBEX_RSP_SRM); +} + +static int obex_server_parse_srmp( + struct bt_obex_server *server, struct net_buf *buf, bool first, + int check_bit, int update_bit) +{ + int err; + uint8_t srmp; + + err = bt_obex_get_header_srm_param(buf, &srmp); + if (err == 0 && srmp != BT_OBEX_SRMP_WAIT) + { + LOG_WRN("SRMP value is invalid"); + return -EINVAL; + } + + if (err == 0 && !atomic_test_bit(&server->_flags, check_bit)) + { + LOG_WRN("BIT %d is not set", check_bit); + return -EINVAL; + } + + if (!first && !atomic_test_bit(&server->_flags, update_bit) && err == 0) + { + LOG_WRN("SRMP header should not be included"); + return -EINVAL; + } + + atomic_set_bit_to(&server->_flags, update_bit, err == 0); + + return 0; +} + +static int obex_server_rsp_check(struct bt_obex_server *server, bool first) +{ + if (first) + { + return 0; + } + + if (!(atomic_test_bit(&server->_flags, BT_OBEX_REQ_SRM) && + atomic_test_bit(&server->_flags, BT_OBEX_RSP_SRM))) + { + if (atomic_test_bit(&server->_flags, BT_OBEX_REQ_RECV)) + { + return 0; + } + + LOG_ERR("SRM is not enabled, request is not received"); + return -EPROTO; + } + + LOG_DBG("SRM is enabled"); + + if (atomic_test_bit(&server->_flags, BT_OBEX_REQ_SRMP) || + atomic_test_bit(&server->_flags, BT_OBEX_RSP_SRMP) || + !atomic_test_bit(&server->_flags, BT_OBEX_REQ_F_BIT)) + { + if (atomic_test_bit(&server->_flags, BT_OBEX_REQ_RECV)) + { + return 0; + } + + LOG_ERR("SRM is enabled but waiting or final bit is not set, " + "request is not received"); + return -EPROTO; + } + + return 0; +} + +static int obex_server_put_common( + struct bt_obex_server *server, bool final, uint16_t len, + struct net_buf *buf) +{ + struct bt_obex_rsp_hdr *rsp_hdr; + uint8_t rsp_code; + uint8_t req_code; + uint8_t opcode; + int err; + + LOG_DBG(""); + + if (len != buf->len) + { + LOG_WRN("Invalid packet size"); + rsp_code = BT_OBEX_RSP_CODE_PRECON_FAIL; + goto failed; + } + + if (atomic_read(&server->_state) != BT_OBEX_CONNECTED) + { + LOG_WRN("Invalid state, connect is not established"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + if (server->ops->put == NULL) + { + LOG_WRN("Put req handling not implemented"); + rsp_code = BT_OBEX_RSP_CODE_NOT_IMPL; + goto failed; + } + + opcode = atomic_read(&server->_opcode); + req_code = final ? BT_OBEX_OPCODE_PUT_F : BT_OBEX_OPCODE_PUT; + if (!atomic_cmpxchg_value(&server->_opcode, 0, req_code)) + { + if ((opcode != BT_OBEX_OPCODE_PUT_F) && (opcode != BT_OBEX_OPCODE_PUT)) + { + LOG_WRN("Unexpected put request"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + if (!final && (opcode == BT_OBEX_OPCODE_PUT_F)) + { + LOG_WRN("Unexpected put request without final bit"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + if ((opcode != req_code) && !atomic_cmpxchg_value(&server->_opcode, + opcode, req_code)) + { + LOG_WRN("OP code mismatch %u != %u", + (uint8_t)atomic_read(&server->_opcode), opcode); + rsp_code = BT_OBEX_RSP_CODE_INTER_ERROR; + goto failed; + } + } + + err = obex_server_parse_req_srm(server, buf, opcode == 0); + if (err != 0) + { + LOG_WRN("Invalid SRM header received"); + } + + err = obex_server_parse_srmp(server, buf, opcode == 0, BT_OBEX_REQ_SRM, + BT_OBEX_REQ_SRMP); + if (err != 0) + { + LOG_WRN("Invalid SRMP header received"); + } + + atomic_set_bit(&server->_flags, BT_OBEX_REQ_RECV); + atomic_set_bit_to(&server->_flags, BT_OBEX_REQ_F_BIT, final); + + server->ops->put(server, final, buf); + return 0; + +failed: + buf = obex_alloc_buf(server->obex); + if (buf == NULL) + { + LOG_WRN("Cannot allocate buffer"); + return -ENOBUFS; + } + + rsp_hdr = net_buf_add(buf, sizeof(*rsp_hdr)); + rsp_hdr->code = rsp_code; + rsp_hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(server->obex, server->tx.mopl, buf); + if (err != 0) + { + net_buf_unref(buf); + } + + return err; +} + +static int obex_server_put(struct bt_obex_server *server, + uint16_t len, struct net_buf *buf) +{ + return obex_server_put_common(server, false, len, buf); +} + +static int obex_server_put_final(struct bt_obex_server *server, + uint16_t len, struct net_buf *buf) +{ + return obex_server_put_common(server, true, len, buf); +} + +static int obex_server_get_common( + struct bt_obex_server *server, bool final, uint16_t len, + struct net_buf *buf) +{ + struct bt_obex_rsp_hdr *rsp_hdr; + uint8_t rsp_code; + uint8_t req_code; + uint8_t opcode; + int err; + + LOG_DBG(""); + + if (len != buf->len) + { + LOG_WRN("Invalid packet size"); + rsp_code = BT_OBEX_RSP_CODE_PRECON_FAIL; + goto failed; + } + + if (atomic_read(&server->_state) != BT_OBEX_CONNECTED) + { + LOG_WRN("Invalid state, connect is not established"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + if (server->ops->get == NULL) + { + LOG_WRN("Get req handling not implemented"); + rsp_code = BT_OBEX_RSP_CODE_NOT_IMPL; + goto failed; + } + + opcode = atomic_read(&server->_opcode); + req_code = final ? BT_OBEX_OPCODE_GET_F : BT_OBEX_OPCODE_GET; + if (!atomic_cmpxchg_value(&server->_opcode, 0, req_code)) + { + if ((opcode != BT_OBEX_OPCODE_GET_F) && (opcode != BT_OBEX_OPCODE_GET)) + { + LOG_WRN("Unexpected get request"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + if (!final && (opcode == BT_OBEX_OPCODE_GET_F)) + { + LOG_WRN("Unexpected get request without final bit"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + if ((opcode != req_code) && !atomic_cmpxchg_value(&server->_opcode, + opcode, req_code)) + { + LOG_WRN("OP code mismatch %u != %u", + (uint8_t)atomic_read(&server->_opcode), opcode); + rsp_code = BT_OBEX_RSP_CODE_INTER_ERROR; + goto failed; + } + } + + err = obex_server_parse_req_srm(server, buf, opcode == 0); + if (err != 0) + { + LOG_WRN("Invalid SRM header received"); + } + + err = obex_server_parse_srmp(server, buf, opcode == 0, BT_OBEX_REQ_SRM, + BT_OBEX_REQ_SRMP); + if (err != 0) + { + LOG_WRN("Invalid SRMP header received"); + } + + atomic_set_bit(&server->_flags, BT_OBEX_REQ_RECV); + atomic_set_bit_to(&server->_flags, BT_OBEX_REQ_F_BIT, final); + + server->ops->get(server, final, buf); + return 0; + +failed: + buf = obex_alloc_buf(server->obex); + if (buf == NULL) + { + LOG_WRN("Cannot allocate buffer"); + return -ENOBUFS; + } + + rsp_hdr = net_buf_add(buf, sizeof(*rsp_hdr)); + rsp_hdr->code = rsp_code; + rsp_hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(server->obex, server->tx.mopl, buf); + if (err != 0) + { + net_buf_unref(buf); + } + + return err; +} + +static int obex_server_get(struct bt_obex_server *server, + uint16_t len, struct net_buf *buf) +{ + return obex_server_get_common(server, false, len, buf); +} + +static int obex_server_get_final(struct bt_obex_server *server, + uint16_t len, struct net_buf *buf) +{ + return obex_server_get_common(server, true, len, buf); +} + +static int obex_server_setpath(struct bt_obex_server *server, + uint16_t len, struct net_buf *buf) +{ + struct bt_obex_rsp_hdr *rsp_hdr; + struct bt_obex_setpath_req_hdr *req_hdr; + uint8_t rsp_code; + int err; + + LOG_DBG(""); + + if ((len != buf->len) || (buf->len < sizeof(*req_hdr))) + { + LOG_WRN("Invalid packet size"); + rsp_code = BT_OBEX_RSP_CODE_PRECON_FAIL; + goto failed; + } + + if (atomic_read(&server->_state) != BT_OBEX_CONNECTED) + { + LOG_WRN("Invalid state, connect is not established"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + if (server->ops->setpath == NULL) + { + LOG_WRN("Setpath req handling not implemented"); + rsp_code = BT_OBEX_RSP_CODE_NOT_IMPL; + goto failed; + } + + if (!atomic_cmpxchg_value(&server->_opcode, 0, BT_OBEX_OPCODE_SETPATH)) + { + LOG_WRN("Unexpected setpath request"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + req_hdr = net_buf_pull_mem(buf, sizeof(*req_hdr)); + server->ops->setpath(server, req_hdr->flags, buf); + return 0; + +failed: + buf = obex_alloc_buf(server->obex); + if (buf == NULL) + { + LOG_WRN("Cannot allocate buffer"); + return -ENOBUFS; + } + + rsp_hdr = net_buf_add(buf, sizeof(*rsp_hdr)); + rsp_hdr->code = rsp_code; + rsp_hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(server->obex, server->tx.mopl, buf); + if (err != 0) + { + net_buf_unref(buf); + } + + return err; +} + +static int obex_server_action_common( + struct bt_obex_server *server, bool final, uint16_t len, + struct net_buf *buf) +{ + struct bt_obex_rsp_hdr *rsp_hdr; + uint8_t rsp_code; + uint8_t req_code; + uint8_t opcode; + int err; + + LOG_DBG(""); + + if (len != buf->len) + { + LOG_WRN("Invalid packet size"); + rsp_code = BT_OBEX_RSP_CODE_PRECON_FAIL; + goto failed; + } + + if (atomic_read(&server->_state) != BT_OBEX_CONNECTED) + { + LOG_WRN("Invalid state, connect is not established"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + if (server->ops->action == NULL) + { + LOG_WRN("Action req handling not implemented"); + rsp_code = BT_OBEX_RSP_CODE_NOT_IMPL; + goto failed; + } + + req_code = final ? BT_OBEX_OPCODE_ACTION_F : BT_OBEX_OPCODE_ACTION; + if (!atomic_cmpxchg_value(&server->_opcode, 0, req_code)) + { + opcode = atomic_read(&server->_opcode); + if ((opcode != BT_OBEX_OPCODE_ACTION_F) && + (opcode != BT_OBEX_OPCODE_ACTION)) + { + LOG_WRN("Unexpected action request"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + if (!final && (opcode == BT_OBEX_OPCODE_ACTION_F)) + { + LOG_WRN("Unexpected action request without final bit"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + if ((opcode != req_code) && !atomic_cmpxchg_value(&server->_opcode, + opcode, req_code)) + { + LOG_WRN("OP code mismatch %u != %u", + (uint8_t)atomic_read(&server->_opcode), opcode); + rsp_code = BT_OBEX_RSP_CODE_INTER_ERROR; + goto failed; + } + } + + server->ops->action(server, final, buf); + return 0; + +failed: + buf = obex_alloc_buf(server->obex); + if (buf == NULL) + { + LOG_WRN("Cannot allocate buffer"); + return -ENOBUFS; + } + + rsp_hdr = net_buf_add(buf, sizeof(*rsp_hdr)); + rsp_hdr->code = rsp_code; + rsp_hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(server->obex, server->tx.mopl, buf); + if (err != 0) + { + net_buf_unref(buf); + } + + return err; +} + +static int obex_server_action(struct bt_obex_server *server, + uint16_t len, struct net_buf *buf) +{ + return obex_server_action_common(server, false, len, buf); +} + +static int obex_server_action_final( + struct bt_obex_server *server, uint16_t len, struct net_buf *buf) +{ + return obex_server_action_common(server, true, len, buf); +} + +static int obex_server_session(struct bt_obex_server *server, + uint16_t len, struct net_buf *buf) +{ + struct bt_obex_rsp_hdr *rsp_hdr; + uint8_t rsp_code; + int err; + + LOG_DBG(""); + + if (len != buf->len) + { + LOG_WRN("Invalid packet size"); + rsp_code = BT_OBEX_RSP_CODE_PRECON_FAIL; + goto failed; + } + + if (atomic_read(&server->_state) != BT_OBEX_CONNECTED) + { + LOG_WRN("Invalid state, connect is not established"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + LOG_WRN("Action req handling not implemented"); + rsp_code = BT_OBEX_RSP_CODE_NOT_IMPL; + +failed: + buf = obex_alloc_buf(server->obex); + if (buf == NULL) + { + LOG_WRN("Cannot allocate buffer"); + return -ENOBUFS; + } + + rsp_hdr = net_buf_add(buf, sizeof(*rsp_hdr)); + rsp_hdr->code = rsp_code; + rsp_hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(server->obex, server->tx.mopl, buf); + if (err != 0) + { + net_buf_unref(buf); + } + + return err; +} + +static int obex_server_abort(struct bt_obex_server *server, + uint16_t len, struct net_buf *buf) +{ + struct bt_obex_rsp_hdr *rsp_hdr; + uint8_t rsp_code; + int err; + + LOG_DBG(""); + + if (len != buf->len) + { + LOG_WRN("Invalid packet size"); + rsp_code = BT_OBEX_RSP_CODE_PRECON_FAIL; + goto failed; + } + + if (atomic_read(&server->_state) != BT_OBEX_CONNECTED) + { + LOG_WRN("Invalid state, connect is not established"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + if (server->ops->abort == NULL) + { + LOG_WRN("Abort req handling not implemented"); + rsp_code = BT_OBEX_RSP_CODE_NOT_IMPL; + goto failed; + } + + atomic_set(&server->_opcode, BT_OBEX_OPCODE_ABORT); + + server->ops->abort(server, buf); + return 0; + +failed: + buf = obex_alloc_buf(server->obex); + if (buf == NULL) + { + LOG_WRN("Cannot allocate buffer"); + return -ENOBUFS; + } + + rsp_hdr = net_buf_add(buf, sizeof(*rsp_hdr)); + rsp_hdr->code = rsp_code; + rsp_hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(server->obex, server->tx.mopl, buf); + if (err != 0) + { + net_buf_unref(buf); + } + + return err; +} + +struct server_handler server_handler[] = +{ + { + BT_OBEX_OPCODE_CONNECT, + sizeof(struct bt_obex_conn_req_hdr), + obex_server_connect + }, + + { + BT_OBEX_OPCODE_DISCONN, + 0, + obex_server_disconn + }, + + { + BT_OBEX_OPCODE_PUT, + 0, + obex_server_put + }, + + { + BT_OBEX_OPCODE_PUT_F, + 0, + obex_server_put_final + }, + + { + BT_OBEX_OPCODE_GET, + 0, + obex_server_get + }, + + { + BT_OBEX_OPCODE_GET_F, + 0, + obex_server_get_final + }, + + { + BT_OBEX_OPCODE_SETPATH, + sizeof(struct bt_obex_setpath_req_hdr), + obex_server_setpath + }, + + { + BT_OBEX_OPCODE_ACTION, + 0, + obex_server_action + }, + + { + BT_OBEX_OPCODE_ACTION_F, + 0, + obex_server_action_final + }, + + { + BT_OBEX_OPCODE_SESSION, + 0, + obex_server_session + }, + + { + BT_OBEX_OPCODE_ABORT, + 0, + obex_server_abort + }, +}; + +static struct bt_obex_server * +obex_get_server_from_conn_id(struct bt_obex *obex, + uint32_t conn_id) +{ + struct bt_obex_server *server; + struct bt_obex_server *next; + + list_for_every_container_safe(&obex->_servers, server, next, _node) + { + if (server->_conn_id == conn_id) + { + return server; + } + } + + return NULL; +} + +static struct bt_obex_server *obex_get_server_from_uuid(struct bt_obex *obex, + struct bt_uuid *uuid) +{ + struct bt_obex_server *server; + struct bt_obex_server *next; + + list_for_every_container_safe(&obex->_servers, server, next, _node) + { + if (server->uuid != NULL && bt_uuid_cmp(&server->uuid->uuid, uuid) == 0) + { + return server; + } + } + + return NULL; +} + +static struct bt_obex_server *obex_get_server_with_state( + struct bt_obex *obex, enum bt_obex_state state) +{ + struct bt_obex_server *server; + struct bt_obex_server *next; + + list_for_every_container_safe(&obex->_servers, server, next, _node) + { + if (server->uuid == NULL && atomic_read(&server->_state) == state) + { + return server; + } + } + + return NULL; +} + +static uint16_t obex_server_get_hdr_len(uint8_t opcode) +{ + ARRAY_FOR_EACH(server_handler, i) + { + if (server_handler[i].opcode == opcode) + { + return server_handler[i].min_len; + } + } + + return 0; +} + +static int obex_server_recv(struct bt_obex *obex, struct net_buf *buf) +{ + struct bt_obex_req_hdr *hdr = NULL; + struct bt_obex_rsp_hdr *rsp_hdr; + struct bt_obex_server *server = NULL; + uint32_t conn_id; + uint16_t len; + uint16_t target_len; + const uint8_t *target_data; + uint16_t min_hdr_len; + int err; + enum bt_obex_rsp_code rsp_code = BT_OBEX_RSP_CODE_BAD_REQ; + + if (buf->len < sizeof(*hdr)) + { + LOG_WRN("Too small header size (%d < %d)", buf->len, sizeof(*hdr)); + goto failed; + } + + hdr = net_buf_pull_mem(buf, sizeof(*hdr)); + len = sys_be16_to_cpu(hdr->len); + min_hdr_len = obex_server_get_hdr_len(hdr->code); + + if (min_hdr_len > buf->len) + { + LOG_WRN("Malformed request (%d > %d)", min_hdr_len, buf->len); + goto failed; + } + + if (hdr->code == BT_OBEX_OPCODE_CONNECT) + { + struct net_buf_simple_state state; + + net_buf_simple_save(&buf->b, &state); + net_buf_pull(buf, min_hdr_len); + err = bt_obex_get_header_target(buf, &target_len, &target_data); + net_buf_simple_restore(&buf->b, &state); + if (err == 0) + { + union bt_obex_uuid u; + + err = bt_obex_make_uuid(&u, target_data, target_len); + if (err != 0) + { + LOG_WRN("Unsupported target header len %u", target_len); + goto failed; + } + + server = obex_get_server_from_uuid(obex, &u.uuid); + } + + if (server == NULL) + { + server = obex_get_server_with_state(obex, BT_OBEX_DISCONNECTED); + } + } + else + { + struct net_buf_simple_state state; + + net_buf_simple_save(&buf->b, &state); + net_buf_pull(buf, min_hdr_len); + err = bt_obex_get_header_conn_id(buf, &conn_id); + net_buf_simple_restore(&buf->b, &state); + if (err == 0) + { + server = obex_get_server_from_conn_id(obex, conn_id); + } + else if (atomic_ptr_read(&obex->_active_server) != NULL) + { + server = atomic_ptr_read(&obex->_active_server); + } + else if (list_length(&obex->_servers) == 1) + { + /* Only if the count of servers is 1, use it as default. */ + + server = OBEX_SERVER(list_peek_head(&obex->_servers)); + } + } + + if (server == NULL) + { + LOG_WRN("No server found from %p", obex); + rsp_code = BT_OBEX_RSP_CODE_NOT_FOUND; + goto failed; + } + + ARRAY_FOR_EACH(server_handler, i) + { + if (server_handler[i].opcode == hdr->code) + { + err = server_handler[i].handler(server, len - sizeof(*hdr), buf); + if (err != 0) + { + atomic_ptr_xchg(&obex->_active_server, NULL); + LOG_WRN("Handler err %d", err); + } + else + { + /* Save the server */ + + atomic_ptr_xchg(&obex->_active_server, server); + } + + return err; + } + } + +failed: + LOG_WRN("Failed to process request"); + buf = obex_alloc_buf(obex); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buf"); + return -ENOBUFS; + } + + rsp_hdr = (void *)net_buf_add(buf, sizeof(*rsp_hdr)); + rsp_hdr->code = rsp_code; + if (hdr != NULL && hdr->code == BT_OBEX_OPCODE_CONNECT) + { + struct bt_obex_conn_rsp_hdr *conn_rsp_hdr; + + conn_rsp_hdr = (void *)net_buf_add(buf, sizeof(*conn_rsp_hdr)); + conn_rsp_hdr->flags = 0; + conn_rsp_hdr->mopl = sys_cpu_to_be16(obex->rx.mtu); + conn_rsp_hdr->version = BT_OBEX_VERSION; + } + + rsp_hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(obex, BT_OBEX_MIN_MTU, buf); + if (err != 0) + { + LOG_ERR("Failed to send obex rep err %d", err); + net_buf_unref(buf); + } + + return err; +} + +struct client_handler +{ + uint8_t opcode; + int (*handler)(struct bt_obex_client *client, + uint8_t rsp_code, + uint16_t len, + struct net_buf *buf); +}; + +static void obex_client_save_last_operation(struct bt_obex_client *client, + uint8_t opcode) +{ + atomic_set(&client->_pre_opcode, opcode); + atomic_ptr_xchg(&client->obex->_last_client, client); +} + +static void obex_client_clear_active_state(struct bt_obex_client *client) +{ + atomic_set(&client->_opcode, 0); + atomic_ptr_xchg(&client->obex->_active_client, NULL); +} + +static void obex_client_restore_active_state(struct bt_obex_client *client, + struct bt_obex_client *active_client, + uint8_t opcode) +{ + atomic_set(&client->_opcode, opcode); + (void)atomic_ptr_cmpxchg(&client->obex->_active_client, client, + active_client); +} + +static void obex_client_req_complete(struct bt_obex_client *client) +{ + obex_client_save_last_operation(client, + (uint8_t)atomic_read(&client->_opcode)); + obex_client_clear_active_state(client); +} + +static int obex_client_connect( + struct bt_obex_client *client, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + struct bt_obex_conn_rsp_hdr *rsp_conn_hdr; + uint8_t version = 0; + __maybe_unused uint8_t flags; + uint16_t mopl = 0; + + LOG_DBG(""); + + obex_client_clear_active_state(client); + + if (atomic_read(&client->_state) != BT_OBEX_CONNECTING) + { + LOG_WRN("Invalid connection state %u", + (uint8_t)atomic_read(&client->_state)); + goto failed; + } + + if ((len != buf->len) || (buf->len < sizeof(*rsp_conn_hdr))) + { + LOG_WRN("Invalid packet size"); + goto failed; + } + + version = net_buf_pull_u8(buf); + flags = net_buf_pull_u8(buf); + mopl = net_buf_pull_be16(buf); + + LOG_DBG("version %u, flags %u, mopl %u", version, flags, mopl); + + if (atomic_test_bit(&client->_flags, BT_OBEX_HAS_TARGET) && + rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + union bt_obex_uuid u; + int err; + uint16_t who_len; + const uint8_t *who_data; + + if (bt_obex_has_header(buf, BT_OBEX_HEADER_ID_WHO) != + bt_obex_has_header(buf, BT_OBEX_HEADER_ID_CONN_ID)) + { + LOG_WRN("Missing required who or connect id headers"); + goto failed; + } + + if (atomic_test_bit(&client->_flags, BT_OBEX_HAS_TARGET) != + bt_obex_has_header(buf, BT_OBEX_HEADER_ID_WHO)) + { + LOG_WRN("Missing required who header"); + goto failed; + } + + err = bt_obex_get_header_conn_id(buf, &client->_conn_id); + if (err != 0) + { + LOG_WRN("Invalid connection id header"); + goto failed; + } + + err = bt_obex_get_header_who(buf, &who_len, &who_data); + if (err != 0) + { + LOG_WRN("Invalid who header"); + goto failed; + } + + err = bt_obex_make_uuid(&u, who_data, who_len); + if (err != 0) + { + LOG_WRN("Unsupported who header len %u", who_len); + goto failed; + } + + if (bt_uuid_cmp(&u.uuid, &client->_target.uuid) != 0) + { + LOG_WRN("Who header is mismatched with target"); + goto failed; + } + } + + if (mopl < BT_OBEX_MIN_MTU) + { + LOG_WRN("Invalid MOPL (%d < %d)", mopl, BT_OBEX_MIN_MTU); + goto failed; + } + + if (mopl > client->obex->tx.mtu) + { + LOG_WRN("MOPL exceeds MTU (%d > %d)", mopl, client->obex->tx.mtu); + /* In mainstream mobile operating system settings, such as IPhone and + * Android, MOPL is usually greater than the MTU of rfcomm or l2cap. + * Therefore, the smaller value among them is selected as the final + * MOPL and transmitted to the application by callback. + */ + + mopl = client->obex->tx.mtu; + } + + client->tx.mopl = mopl; + + atomic_set(&client->_state, + rsp_code == BT_OBEX_RSP_CODE_SUCCESS ? + BT_OBEX_CONNECTED : BT_OBEX_DISCONNECTED); + + if (atomic_read(&client->_state) == BT_OBEX_DISCONNECTED) + { + list_find_and_remove(&client->obex->_clients, &client->_node); + } + + obex_client_save_last_operation(client, BT_OBEX_OPCODE_CONNECT); + + if (client->ops->connect) + { + client->ops->connect(client, rsp_code, version, mopl, buf); + } + + return 0; + +failed: + LOG_WRN("Disconnect transport"); + + return obex_transport_disconn(client->obex); +} + +static int obex_client_disconn( + struct bt_obex_client *client, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + LOG_DBG(""); + + obex_client_clear_active_state(client); + + if (atomic_read(&client->_state) != BT_OBEX_DISCONNECTING) + { + return -EINVAL; + } + + atomic_set(&client->_state, + rsp_code == BT_OBEX_RSP_CODE_SUCCESS ? + BT_OBEX_DISCONNECTED : BT_OBEX_CONNECTED); + + if (atomic_read(&client->_state) == BT_OBEX_DISCONNECTED) + { + list_find_and_remove(&client->obex->_clients, &client->_node); + } + + obex_client_save_last_operation(client, BT_OBEX_OPCODE_DISCONN); + + if (client->ops->disconnect) + { + client->ops->disconnect(client, rsp_code, buf); + } + + return 0; +} + +static int obex_client_parse_srm( + struct bt_obex_client *client, struct net_buf *buf, bool first, int bit) +{ + int err; + uint8_t srm; + + err = bt_obex_get_header_srm(buf, &srm); + if (err != 0) + { + /* No SRM header included */ + + return 0; + } + + if (!obex_srm_is_valid(srm)) + { + LOG_ERR("SRM value is invalid"); + return -EINVAL; + } + + if (!obex_srm_is_enabled(srm)) + { + /* SRM is disabled */ + + return 0; + } + + if (!first) + { + LOG_WRN("SRM header should not be included"); + return -EINVAL; + } + + atomic_set_bit(&client->_flags, bit); + + return 0; +} + +static int obex_client_parse_req_srm(struct bt_obex_client *client, + struct net_buf *buf, bool first) +{ + int err; + + if (first) + { + atomic_set(&client->_flags, 0); + } + + err = obex_client_parse_srm(client, buf, first, BT_OBEX_REQ_SRM); + if (err != 0) + { + return err; + } + + if (first) + { + atomic_set_bit(&client->_flags, BT_OBEX_REQ_1ST); + } + + return 0; +} + +static int obex_client_parse_rsp_srm(struct bt_obex_client *client, + struct net_buf *buf, bool first) +{ + return obex_client_parse_srm(client, buf, first, BT_OBEX_RSP_SRM); +} + +static int obex_client_parse_srmp( + struct bt_obex_client *client, struct net_buf *buf, bool first, + int check_bit, int update_bit) +{ + int err; + uint8_t srmp; + + err = bt_obex_get_header_srm_param(buf, &srmp); + if (err == 0 && srmp != BT_OBEX_SRMP_WAIT) + { + LOG_WRN("SRMP value is invalid"); + return -EINVAL; + } + + if (err == 0 && !atomic_test_bit(&client->_flags, check_bit)) + { + LOG_WRN("BIT %d is not set", check_bit); + return -EINVAL; + } + + if (!first && !atomic_test_bit(&client->_flags, update_bit) && err == 0) + { + LOG_WRN("SRMP header should not be included"); + return -EINVAL; + } + + atomic_set_bit_to(&client->_flags, update_bit, err == 0); + + return 0; +} + +static int obex_client_put_common( + struct bt_obex_client *client, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + int err; + bool first; + + LOG_DBG(""); + + if (len != buf->len) + { + LOG_WRN("Invalid packet size"); + return -EINVAL; + } + + if (atomic_read(&client->_state) != BT_OBEX_CONNECTED) + { + LOG_WRN("Invalid state, connect is not established"); + return -EINVAL; + } + + if (client->ops->put == NULL) + { + LOG_WRN("Put rsp handling not implemented"); + return -ENOTSUP; + } + + atomic_set_bit(&client->_flags, BT_OBEX_RSP_RECV); + + first = atomic_test_and_clear_bit(&client->_flags, BT_OBEX_REQ_1ST); + + err = obex_client_parse_rsp_srm(client, buf, first); + if (err != 0) + { + LOG_WRN("Invalid SRM header received"); + } + + err = obex_client_parse_srmp(client, buf, first, BT_OBEX_RSP_SRM, + BT_OBEX_RSP_SRMP); + if (err != 0) + { + LOG_WRN("Invalid SRMP header received"); + } + + if (rsp_code != BT_OBEX_RSP_CODE_CONTINUE) + { + obex_client_req_complete(client); + } + + client->ops->put(client, rsp_code, buf); + return 0; +} + +static int obex_client_put( + struct bt_obex_client *client, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + return obex_client_put_common(client, rsp_code, len, buf); +} + +static int obex_client_put_final( + struct bt_obex_client *client, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + return obex_client_put_common(client, rsp_code, len, buf); +} + +static int obex_client_get_common( + struct bt_obex_client *client, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + int err; + bool first; + + LOG_DBG(""); + + if (len != buf->len) + { + LOG_WRN("Invalid packet size"); + return -EINVAL; + } + + if (atomic_read(&client->_state) != BT_OBEX_CONNECTED) + { + LOG_WRN("Invalid state, connect is not established"); + return -EINVAL; + } + + if (client->ops->get == NULL) + { + LOG_WRN("Get rsp handling not implemented"); + return -ENOTSUP; + } + + atomic_set_bit(&client->_flags, BT_OBEX_RSP_RECV); + + first = atomic_test_and_clear_bit(&client->_flags, BT_OBEX_REQ_1ST); + + err = obex_client_parse_rsp_srm(client, buf, first); + if (err != 0) + { + LOG_WRN("Invalid SRM header received"); + } + + err = obex_client_parse_srmp(client, buf, first, BT_OBEX_RSP_SRM, + BT_OBEX_RSP_SRMP); + if (err != 0) + { + LOG_WRN("Invalid SRMP header received"); + } + + if (rsp_code != BT_OBEX_RSP_CODE_CONTINUE) + { + obex_client_req_complete(client); + } + + client->ops->get(client, rsp_code, buf); + return 0; +} + +static int obex_client_get( + struct bt_obex_client *client, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + return obex_client_get_common(client, rsp_code, len, buf); +} + +static int obex_client_get_final( + struct bt_obex_client *client, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + return obex_client_get_common(client, rsp_code, len, buf); +} + +static int obex_client_setpath( + struct bt_obex_client *client, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + LOG_DBG(""); + + if (len != buf->len) + { + LOG_WRN("Invalid packet size"); + return -EINVAL; + } + + if (atomic_read(&client->_state) != BT_OBEX_CONNECTED) + { + LOG_WRN("Invalid state, connect is not established"); + return -EINVAL; + } + + if (client->ops->setpath == NULL) + { + LOG_WRN("Setpath rsp handling not implemented"); + return -ENOTSUP; + } + + if (rsp_code != BT_OBEX_RSP_CODE_CONTINUE) + { + obex_client_req_complete(client); + } + + client->ops->setpath(client, rsp_code, buf); + return 0; +} + +static int obex_client_action_common( + struct bt_obex_client *client, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + LOG_DBG(""); + + if (len != buf->len) + { + LOG_WRN("Invalid packet size"); + return -EINVAL; + } + + if (atomic_read(&client->_state) != BT_OBEX_CONNECTED) + { + LOG_WRN("Invalid state, connect is not established"); + return -EINVAL; + } + + if (client->ops->action == NULL) + { + LOG_WRN("Setpath rsp handling not implemented"); + return -ENOTSUP; + } + + if (rsp_code != BT_OBEX_RSP_CODE_CONTINUE) + { + obex_client_req_complete(client); + } + + client->ops->action(client, rsp_code, buf); + return 0; +} + +static int obex_client_action( + struct bt_obex_client *client, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + return obex_client_action_common(client, rsp_code, len, buf); +} + +static int obex_client_action_final( + struct bt_obex_client *client, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + return obex_client_action_common(client, rsp_code, len, buf); +} + +static int obex_client_session( + struct bt_obex_client *client, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + return -ENOTSUP; +} + +static struct client_handler *obex_client_find_handler(uint8_t opcode); + +static int obex_client_abort( + struct bt_obex_client *client, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + LOG_DBG(""); + + if (len != buf->len) + { + LOG_WRN("Invalid packet size"); + return -EINVAL; + } + + if (atomic_read(&client->_state) != BT_OBEX_CONNECTED) + { + LOG_WRN("Invalid state, connect is not established"); + return -EINVAL; + } + + if (len != 0) + { + struct client_handler *handler; + int err = -EINVAL; + + handler = obex_client_find_handler(atomic_read(&client->_pre_opcode)); + if (handler != NULL) + { + err = handler->handler(client, rsp_code, len, buf); + if (err != 0) + { + LOG_WRN("Handler err %d", err); + } + + return err; + } + } + + if (client->ops->abort == NULL) + { + LOG_WRN("Abort rsp handling not implemented"); + return -ENOTSUP; + } + + atomic_set(&client->_pre_opcode, 0); + atomic_ptr_xchg(&client->obex->_last_client, NULL); + obex_client_clear_active_state(client); + + if (rsp_code != BT_OBEX_RSP_CODE_SUCCESS) + { + LOG_WRN("Disconnect transport"); + + return obex_transport_disconn(client->obex); + } + + client->ops->abort(client, rsp_code, buf); + return 0; +} + +#define BT_OBEX_REQUEST_CODE_START 0x00 +#define BT_OBEX_REQUEST_CODE_END 0x0f +#define BT_OBEX_REQUEST_CODE_F_BIT BIT(7) +#define IN_OBEX_REQUEST_RANGE(code) \ + ((code) >= BT_OBEX_REQUEST_CODE_START && (code) <= BT_OBEX_REQUEST_CODE_END) +#define IS_OBEX_REQUEST(code) \ + (IN_OBEX_REQUEST_RANGE((code) & ~(BT_OBEX_REQUEST_CODE_F_BIT)) || \ + ((code) == BT_OBEX_OPCODE_ABORT)) + +struct client_handler client_handler[] = +{ + { + BT_OBEX_OPCODE_CONNECT, + obex_client_connect + }, + + { + BT_OBEX_OPCODE_DISCONN, + obex_client_disconn + }, + + { + BT_OBEX_OPCODE_PUT, + obex_client_put + }, + + { + BT_OBEX_OPCODE_PUT_F, + obex_client_put_final + }, + + { + BT_OBEX_OPCODE_GET, + obex_client_get + }, + + { + BT_OBEX_OPCODE_GET_F, + obex_client_get_final + }, + + { + BT_OBEX_OPCODE_SETPATH, + obex_client_setpath + }, + + { + BT_OBEX_OPCODE_ACTION, + obex_client_action + }, + + { + BT_OBEX_OPCODE_ACTION_F, + obex_client_action_final + }, + + { + BT_OBEX_OPCODE_SESSION, + obex_client_session + }, + + { + BT_OBEX_OPCODE_ABORT, + obex_client_abort + }, +}; + +static struct client_handler *obex_client_find_handler(uint8_t opcode) +{ + ARRAY_FOR_EACH(client_handler, i) + { + if (client_handler[i].opcode == opcode) + { + return &client_handler[i]; + } + } + + return NULL; +} + +static struct bt_obex_client *obex_get_last_client(struct bt_obex *obex) +{ + struct bt_obex_client *client; + + client = atomic_ptr_read(&obex->_last_client); + if (client == NULL) + { + LOG_WRN("Last client not found"); + return NULL; + } + + if (!atomic_cmpxchg_value(&client->_opcode, 0, BT_OBEX_OPCODE_ABORT)) + { + LOG_WRN("opcode cannot be set"); + return NULL; + } + + if (!atomic_cmpxchg_value(&client->_pre_opcode, BT_OBEX_OPCODE_ABORT, 0)) + { + LOG_WRN("Last opcode is not ABORT"); + (void)atomic_cmpxchg_value(&client->_opcode, BT_OBEX_OPCODE_ABORT, 0); + return NULL; + } + + return client; +} + +static int obex_client_recv(struct bt_obex *obex, struct net_buf *buf) +{ + struct client_handler *handler; + struct bt_obex_rsp_hdr *hdr; + struct bt_obex_client *client = atomic_ptr_read(&obex->_active_client); + uint16_t len; + int err; + + if (buf->len < sizeof(*hdr)) + { + LOG_WRN("Too small header size (%d < %d)", buf->len, sizeof(*hdr)); + return -EINVAL; + } + + if (client == NULL) + { + LOG_WRN("No active OBEX client."); + client = obex_get_last_client(obex); + } + + if (client == NULL) + { + LOG_WRN("No executing OBEX request"); + return -EINVAL; + } + + hdr = net_buf_pull_mem(buf, sizeof(*hdr)); + len = sys_be16_to_cpu(hdr->len); + handler = obex_client_find_handler(atomic_read(&client->_opcode)); + if (handler != NULL) + { + err = handler->handler(client, hdr->code, len - sizeof(*hdr), buf); + if (err != 0) + { + LOG_WRN("Handler err %d", err); + } + + return err; + } + + LOG_WRN("Unknown OBEX rsp (rsp_code %02x, len %d)", hdr->code, len); + return -EINVAL; +} + +int bt_obex_transport_connected(struct bt_obex *obex) +{ + if (obex == NULL) + { + return -EINVAL; + } + + if (obex->rx.mtu < BT_OBEX_MIN_MTU) + { + LOG_WRN("Invalid MTU (%d < %d)", obex->rx.mtu, BT_OBEX_MIN_MTU); + return -EINVAL; + } + + if (obex->tx.mtu < BT_OBEX_MIN_MTU) + { + LOG_WRN("Invalid MTU (%d < %d)", obex->tx.mtu, BT_OBEX_MIN_MTU); + return -EINVAL; + } + + atomic_ptr_xchg(&obex->_active_client, NULL); + atomic_ptr_xchg(&obex->_last_client, NULL); + + return 0; +} + +int bt_obex_transport_disconnected(struct bt_obex *obex) +{ + struct bt_obex_client *client; + struct bt_obex_client *cnext; + struct bt_obex_server *server; + struct bt_obex_server *snext; + + obex->_transport_ops = NULL; + atomic_ptr_xchg(&obex->_active_client, NULL); + atomic_ptr_xchg(&obex->_last_client, NULL); + + list_for_every_container_safe(&obex->_clients, client, cnext, _node) + { + atomic_set(&client->_opcode, 0); + atomic_set(&client->_pre_opcode, 0); + atomic_set(&client->_state, BT_OBEX_DISCONNECTED); + } + + list_for_every_container_safe(&obex->_servers, server, snext, _node) + { + atomic_set(&server->_opcode, 0); + atomic_set(&server->_state, BT_OBEX_DISCONNECTED); + } + + list_initialize(&obex->_clients); + return 0; +} + +void bt_obex_reg_transport(struct bt_obex *obex, + const struct bt_obex_transport_ops *ops) +{ + DEBUGASSERT(obex != NULL && ops != NULL); + + obex->_transport_ops = ops; +} + +int bt_obex_recv(struct bt_obex *obex, struct net_buf *buf) +{ + struct bt_obex_comm_hdr *hdr; + + if (obex == NULL || buf == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (buf->len < sizeof(*hdr)) + { + LOG_WRN("Malformed packet"); + return -EINVAL; + } + + hdr = (struct bt_obex_comm_hdr *)buf->data; + if (!IS_OBEX_REQUEST(hdr->code)) + { + return obex_client_recv(obex, buf); + } + + return obex_server_recv(obex, buf); +} + +int bt_obex_server_register(struct bt_obex_server *server, + const struct bt_uuid_128 *uuid) +{ + if (server == NULL || server->obex == NULL) + { + LOG_ERR("Invalid parameter"); + return -EINVAL; + } + + if (server->ops == NULL || server->ops->connect == NULL || + server->ops->disconnect == NULL) + { + LOG_ERR("Invalid OBEX role"); + return -EINVAL; + } + + if (atomic_read(&server->_state) != BT_OBEX_DISCONNECTED) + { + LOG_ERR("Invalid state, connect is %u", + (uint8_t)atomic_read(&server->_state)); + return -EINPROGRESS; + } + + if (list_find(&server->obex->_servers, &server->_node, NULL)) + { + LOG_ERR("server %p has been registered", server); + return -EALREADY; + } + + if (!list_is_empty(&server->obex->_servers) && server->uuid == NULL) + { + LOG_ERR("UUID of server %p should not be NULL", server); + return -EINVAL; + } + + server->rx.mopl = BT_OBEX_MIN_MTU; + + /* Set MOPL of TX to MTU by default to avoid the OBEX connect rsp cannot be + * sent. + */ + + server->tx.mopl = BT_OBEX_MIN_MTU; + + server->uuid = uuid; + + list_add_tail(&server->obex->_servers, &server->_node); + + return 0; +} + +int bt_obex_server_unregister(struct bt_obex_server *server) +{ + if (server == NULL || server->obex == NULL) + { + LOG_ERR("Invalid parameter"); + return -EINVAL; + } + + if (!list_find(&server->obex->_servers, &server->_node, NULL)) + { + LOG_ERR("server %p has not been registered", server); + return -EALREADY; + } + + if (atomic_read(&server->_state) != BT_OBEX_DISCONNECTED) + { + LOG_ERR("Invalid state, connect is %u", + (uint8_t)atomic_read(&server->_state)); + return -EINPROGRESS; + } + + list_find_and_remove(&server->obex->_servers, &server->_node); + + return 0; +} + +int bt_obex_connect(struct bt_obex_client *client, + uint16_t mopl, struct net_buf *buf) +{ + struct bt_obex_conn_req_hdr *req_hdr; + struct bt_obex_req_hdr *hdr; + const uint8_t *target_data; + int err; + bool allocated = false; + uint16_t target_len; + int32_t flags; + bool appended = false; + + if (client == NULL || client->obex == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (client->ops == NULL || client->ops->connect == NULL || + client->ops->disconnect == NULL) + { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (mopl > client->obex->rx.mtu) + { + mopl = client->obex->rx.mtu; + } + + if (mopl < BT_OBEX_MIN_MTU) + { + LOG_WRN("Invalid MOPL"); + return -EINVAL; + } + + if (atomic_read(&client->_state) != BT_OBEX_DISCONNECTED) + { + LOG_WRN("Invalid state, connect is %u", + (uint8_t)atomic_read(&client->_state)); + return -EINPROGRESS; + } + + if (!list_is_empty(&client->obex->_clients) && + !bt_obex_has_header(buf, BT_OBEX_HEADER_ID_TARGET)) + { + LOG_ERR("The Header target should be added"); + return -EINVAL; + } + + if (!atomic_ptr_cmpxchg(&client->obex->_active_client, NULL, client)) + { + LOG_WRN("One OBEX request is executing"); + return -EBUSY; + } + + if (!atomic_cmpxchg_value(&client->_opcode, 0, BT_OBEX_OPCODE_CONNECT)) + { + LOG_WRN("Operation inprogress"); + atomic_ptr_xchg(&client->obex->_active_client, NULL); + return -EBUSY; + } + + flags = atomic_read(&client->_flags); + atomic_set_bit_to(&client->_flags, BT_OBEX_HAS_TARGET, + bt_obex_has_header(buf, BT_OBEX_HEADER_ID_TARGET)); + + if (atomic_test_bit(&client->_flags, BT_OBEX_HAS_TARGET)) + { + err = bt_obex_get_header_target(buf, &target_len, &target_data); + if (err != 0) + { + LOG_ERR("Invalid target header"); + goto failed; + } + + err = bt_obex_make_uuid(&client->_target, target_data, target_len); + if (err != 0) + { + LOG_WRN("Unsupported target header len %u", target_len); + goto failed; + } + } + + if (buf == NULL) + { + buf = obex_alloc_buf(client->obex); + if (buf == NULL) + { + LOG_WRN("No buffers"); + err = -ENOBUFS; + goto failed; + } + + allocated = true; + } + + client->rx.mopl = mopl; + + /* Set MOPL of TX to MTU by default to avoid the OBEX connect req cannot be + * sent. + */ + + client->tx.mopl = BT_OBEX_MIN_MTU; + atomic_set(&client->_state, BT_OBEX_CONNECTING); + + if (!list_find(&client->obex->_clients, &client->_node, NULL)) + { + appended = true; + list_add_tail(&client->obex->_clients, &client->_node); + } + + req_hdr = net_buf_push(buf, sizeof(*req_hdr)); + req_hdr->flags = 0; + req_hdr->mopl = sys_cpu_to_be16(mopl); + req_hdr->version = BT_OBEX_VERSION; + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = BT_OBEX_OPCODE_CONNECT; + hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(client->obex, client->tx.mopl, buf); + if (err == 0) + { + return 0; + } + +failed: + atomic_set(&client->_state, BT_OBEX_DISCONNECTED); + if (appended) + { + list_find_and_remove(&client->obex->_clients, &client->_node); + } + + atomic_set(&client->_flags, flags); + obex_client_clear_active_state(client); + + if (allocated) + { + net_buf_unref(buf); + } + + return err; +} + +int bt_obex_connect_rsp( + struct bt_obex_server *server, uint8_t rsp_code, uint16_t mopl, + struct net_buf *buf) +{ + struct bt_obex_conn_rsp_hdr *rsp_hdr; + struct bt_obex_rsp_hdr *hdr; + int err; + int32_t old_state; + uint16_t who_len; + const uint8_t *who_data; + + bool allocated = false; + + if (server == NULL || server->obex == NULL || + (rsp_code == BT_OBEX_RSP_CODE_CONTINUE)) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (server->ops == NULL) + { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (mopl > server->obex->rx.mtu) + { + mopl = server->obex->rx.mtu; + } + + if (mopl < BT_OBEX_MIN_MTU) + { + LOG_WRN("Invalid MOPL"); + return -EINVAL; + } + + if (atomic_read(&server->_state) != BT_OBEX_CONNECTING) + { + LOG_WRN("Invalid state"); + return -ENOTCONN; + } + + if (atomic_read(&server->_opcode) != BT_OBEX_OPCODE_CONNECT) + { + LOG_WRN("Invalid response"); + return -EINVAL; + } + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + if (bt_obex_has_header(buf, BT_OBEX_HEADER_ID_WHO) != + bt_obex_has_header(buf, BT_OBEX_HEADER_ID_CONN_ID)) + { + LOG_ERR("Missing required who or connect id headers"); + return -EINVAL; + } + + if (atomic_test_bit(&server->_flags, BT_OBEX_HAS_TARGET) != + bt_obex_has_header(buf, BT_OBEX_HEADER_ID_WHO)) + { + LOG_ERR("Missing required who header"); + return -EINVAL; + } + + if (atomic_test_bit(&server->_flags, BT_OBEX_HAS_TARGET)) + { + union bt_obex_uuid u; + + err = bt_obex_get_header_conn_id(buf, &server->_conn_id); + if (err != 0) + { + LOG_ERR("Invalid connection id header"); + return err; + } + + err = bt_obex_get_header_who(buf, &who_len, &who_data); + if (err != 0) + { + LOG_ERR("Invalid who header"); + return err; + } + + err = bt_obex_make_uuid(&u, who_data, who_len); + if (err != 0) + { + LOG_WRN("Unsupported who header len %u", who_len); + return err; + } + + if (bt_uuid_cmp(&u.uuid, &server->_target.uuid) != 0) + { + LOG_WRN("Who header is mismatched with target"); + return -EINVAL; + } + } + } + + if (buf == NULL) + { + buf = obex_alloc_buf(server->obex); + if (buf == NULL) + { + LOG_WRN("No buffers"); + return -ENOBUFS; + } + + allocated = true; + } + + server->rx.mopl = mopl; + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + old_state = atomic_xchg(&server->_state, BT_OBEX_CONNECTED); + } + else + { + old_state = atomic_xchg(&server->_state, BT_OBEX_DISCONNECTED); + } + + rsp_hdr = net_buf_push(buf, sizeof(*rsp_hdr)); + rsp_hdr->flags = 0; + rsp_hdr->mopl = sys_cpu_to_be16(mopl); + rsp_hdr->version = BT_OBEX_VERSION; + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = rsp_code; + hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(server->obex, server->tx.mopl, buf); + if (err != 0) + { + atomic_set(&server->_state, old_state); + + if (allocated) + { + net_buf_unref(buf); + } + } + else + { + atomic_set(&server->_opcode, 0); + atomic_ptr_xchg(&server->obex->_active_server, NULL); + } + + return err; +} + +int bt_obex_disconnect(struct bt_obex_client *client, struct net_buf *buf) +{ + struct bt_obex_req_hdr *hdr; + int err; + bool allocated = false; + + if (client == NULL || client->obex == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (client->ops == NULL) + { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (atomic_read(&client->_state) != BT_OBEX_CONNECTED) + { + LOG_WRN("Invalid state, connect is not established"); + return -ENOTCONN; + } + + if (!atomic_ptr_cmpxchg(&client->obex->_active_client, NULL, client)) + { + LOG_WRN("One OBEX request is executing"); + return -EBUSY; + } + + if (!atomic_cmpxchg_value(&client->_opcode, 0, BT_OBEX_OPCODE_DISCONN)) + { + LOG_WRN("Operation inprogress"); + atomic_ptr_xchg(&client->obex->_active_client, NULL); + return -EBUSY; + } + + if (buf == NULL) + { + buf = obex_alloc_buf(client->obex); + if (buf == NULL) + { + LOG_WRN("No buffers"); + err = -ENOBUFS; + goto failed; + } + + allocated = true; + } + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = BT_OBEX_OPCODE_DISCONN; + hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(client->obex, client->tx.mopl, buf); + if (err == 0) + { + atomic_set(&client->_state, BT_OBEX_DISCONNECTING); + return 0; + } + +failed: + obex_client_clear_active_state(client); + + if (allocated) + { + net_buf_unref(buf); + } + + return err; +} + +int bt_obex_disconnect_rsp(struct bt_obex_server *server, + uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_obex_rsp_hdr *hdr; + int err; + int32_t old_state; + bool allocated = false; + + if (server == NULL || server->obex == NULL || + (rsp_code == BT_OBEX_RSP_CODE_CONTINUE)) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (server->ops == NULL) + { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (atomic_read(&server->_state) != BT_OBEX_DISCONNECTING) + { + LOG_WRN("Invalid state"); + return -EINVAL; + } + + if (atomic_read(&server->_opcode) != BT_OBEX_OPCODE_DISCONN) + { + LOG_WRN("Invalid response"); + return -EINVAL; + } + + if (buf == NULL) + { + buf = obex_alloc_buf(server->obex); + if (buf == NULL) + { + LOG_WRN("No buffers"); + return -ENOBUFS; + } + + allocated = true; + } + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + old_state = atomic_xchg(&server->_state, BT_OBEX_DISCONNECTED); + } + else + { + old_state = atomic_xchg(&server->_state, BT_OBEX_CONNECTED); + } + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = rsp_code; + hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(server->obex, server->tx.mopl, buf); + if (err != 0) + { + atomic_set(&server->_state, old_state); + + if (allocated) + { + net_buf_unref(buf); + } + } + else + { + atomic_set(&server->_opcode, 0); + atomic_ptr_xchg(&server->obex->_active_server, NULL); + } + + return err; +} + +static int obex_client_req_check(struct bt_obex_client *client, bool first) +{ + if (first) + { + return 0; + } + + if (!(atomic_test_bit(&client->_flags, BT_OBEX_REQ_SRM) && + atomic_test_bit(&client->_flags, BT_OBEX_RSP_SRM))) + { + if (atomic_test_bit(&client->_flags, BT_OBEX_RSP_RECV)) + { + return 0; + } + + LOG_ERR("SRM is not enabled, response is not received"); + return -EPROTO; + } + + LOG_DBG("SRM is enabled"); + + if (atomic_test_bit(&client->_flags, BT_OBEX_RSP_SRMP) || + atomic_test_bit(&client->_flags, BT_OBEX_REQ_SRMP)) + { + if (atomic_test_bit(&client->_flags, BT_OBEX_RSP_RECV)) + { + return 0; + } + + LOG_ERR("SRM is enabled but in wait state, response is not received"); + return -EPROTO; + } + + if (!atomic_test_bit(&client->_flags, BT_OBEX_REQ_SRMP) && + atomic_test_bit(&client->_flags, BT_OBEX_REQ_F_BIT)) + { + LOG_ERR("SRM is enabled, no requests can be sent anymore"); + return -EPROTO; + } + + return 0; +} + +int bt_obex_put(struct bt_obex_client *client, + bool final, struct net_buf *buf) +{ + struct bt_obex_req_hdr *hdr; + struct bt_obex_client *active_client = NULL; + int err; + uint8_t req_code; + uint8_t opcode = 0; + bool allocated = false; + int32_t flags; + + if (client == NULL || client->obex == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (client->ops == NULL) + { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (atomic_read(&client->_state) != BT_OBEX_CONNECTED) + { + LOG_WRN("Invalid state, connect is not established"); + return -ENOTCONN; + } + + if (!atomic_ptr_cmpxchg(&client->obex->_active_client, NULL, client)) + { + active_client = atomic_ptr_read(&client->obex->_active_client); + if (active_client != client) + { + LOG_WRN("One OBEX request is executing"); + return -EBUSY; + } + } + + req_code = final ? BT_OBEX_OPCODE_PUT_F : BT_OBEX_OPCODE_PUT; + if (!atomic_cmpxchg_value(&client->_opcode, 0, req_code)) + { + opcode = atomic_read(&client->_opcode); + if ((opcode != BT_OBEX_OPCODE_PUT_F) && (opcode != BT_OBEX_OPCODE_PUT)) + { + LOG_WRN("Operation inprogress"); + (void)atomic_ptr_cmpxchg(&client->obex->_active_client, client, + active_client); + return -EBUSY; + } + + if (!final && (opcode == BT_OBEX_OPCODE_PUT_F)) + { + LOG_WRN("Unexpected put request without final bit"); + (void)atomic_ptr_cmpxchg(&client->obex->_active_client, client, + active_client); + return -EBUSY; + } + + if ((opcode != req_code) && !atomic_cmpxchg_value(&client->_opcode, + opcode, req_code)) + { + LOG_WRN("OP code mismatch %u != %u", + (uint8_t)atomic_read(&client->_opcode), opcode); + (void)atomic_ptr_cmpxchg(&client->obex->_active_client, client, + active_client); + return -EINVAL; + } + } + + flags = atomic_read(&client->_flags); + + if (buf == NULL) + { + buf = obex_alloc_buf(client->obex); + if (buf == NULL) + { + LOG_WRN("No buffers"); + err = -ENOBUFS; + goto failed; + } + + allocated = true; + } + + err = obex_client_req_check(client, opcode == 0); + if (err != 0) + { + goto failed; + } + + err = obex_client_parse_req_srm(client, buf, opcode == 0); + if (err != 0) + { + goto failed; + } + + err = obex_client_parse_srmp(client, buf, opcode == 0, BT_OBEX_REQ_SRM, + BT_OBEX_REQ_SRMP); + if (err != 0) + { + goto failed; + } + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = req_code; + hdr->len = sys_cpu_to_be16(buf->len); + + atomic_clear_bit(&client->_flags, BT_OBEX_RSP_RECV); + + if (final) + { + atomic_set_bit(&client->_flags, BT_OBEX_REQ_F_BIT); + } + + err = obex_send(client->obex, client->tx.mopl, buf); + if (err == 0) + { + return 0; + } + +failed: + atomic_set(&client->_flags, flags); + obex_client_restore_active_state(client, active_client, opcode); + + if (allocated) + { + net_buf_unref(buf); + } + + return err; +} + +int bt_obex_put_rsp(struct bt_obex_server *server, + uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_obex_rsp_hdr *hdr; + int err; + uint8_t opcode; + bool allocated = false; + bool first; + int32_t flags; + + if (server == NULL || server->obex == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (server->ops == NULL) + { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (atomic_read(&server->_state) != BT_OBEX_CONNECTED) + { + LOG_WRN("Invalid state, connect is not established"); + return -EINVAL; + } + + opcode = atomic_read(&server->_opcode); + if ((opcode != BT_OBEX_OPCODE_PUT_F) && (opcode != BT_OBEX_OPCODE_PUT)) + { + LOG_WRN("Invalid response"); + return -EINVAL; + } + + if ((opcode != BT_OBEX_OPCODE_PUT_F) && + (rsp_code == BT_OBEX_RSP_CODE_SUCCESS)) + { + LOG_WRN("Success cannot be responded without final"); + return -EINVAL; + } + + if (buf == NULL) + { + buf = obex_alloc_buf(server->obex); + if (buf == NULL) + { + LOG_WRN("No buffers"); + return -ENOBUFS; + } + + allocated = true; + } + + flags = atomic_read(&server->_flags); + first = atomic_test_and_clear_bit(&server->_flags, BT_OBEX_REQ_1ST); + + err = obex_server_rsp_check(server, first); + if (err != 0) + { + goto failed; + } + + err = obex_server_parse_rsp_srm(server, buf, first); + if (err != 0) + { + goto failed; + } + + err = obex_server_parse_srmp(server, buf, first, BT_OBEX_RSP_SRM, + BT_OBEX_RSP_SRMP); + if (err != 0) + { + goto failed; + } + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = rsp_code; + hdr->len = sys_cpu_to_be16(buf->len); + + atomic_clear_bit(&server->_flags, BT_OBEX_REQ_RECV); + + err = obex_send(server->obex, server->tx.mopl, buf); + if (err != 0) + { + goto failed; + } + + if (rsp_code != BT_OBEX_RSP_CODE_CONTINUE) + { + atomic_set(&server->_opcode, 0); + atomic_ptr_xchg(&server->obex->_active_server, NULL); + } + + return 0; + +failed: + atomic_set(&server->_flags, flags); + if (allocated) + { + net_buf_unref(buf); + } + + return err; +} + +int bt_obex_get(struct bt_obex_client *client, + bool final, struct net_buf *buf) +{ + struct bt_obex_req_hdr *hdr; + struct bt_obex_client *active_client = NULL; + int err; + uint8_t req_code; + uint8_t opcode = 0; + bool allocated = false; + int32_t flags; + + if (client == NULL || client->obex == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (client->ops == NULL) + { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (atomic_read(&client->_state) != BT_OBEX_CONNECTED) + { + LOG_WRN("Invalid state, connect is not established"); + return -ENOTCONN; + } + + if (!atomic_ptr_cmpxchg(&client->obex->_active_client, NULL, client)) + { + active_client = atomic_ptr_read(&client->obex->_active_client); + if (active_client != client) + { + LOG_WRN("One OBEX request is executing"); + return -EBUSY; + } + } + + req_code = final ? BT_OBEX_OPCODE_GET_F : BT_OBEX_OPCODE_GET; + if (!atomic_cmpxchg_value(&client->_opcode, 0, req_code)) + { + opcode = atomic_read(&client->_opcode); + if ((opcode != BT_OBEX_OPCODE_GET_F) && (opcode != BT_OBEX_OPCODE_GET)) + { + LOG_WRN("Operation inprogress"); + (void)atomic_ptr_cmpxchg(&client->obex->_active_client, client, + active_client); + return -EBUSY; + } + + if (!final && (opcode == BT_OBEX_OPCODE_GET_F)) + { + LOG_WRN("Unexpected get request without final bit"); + (void)atomic_ptr_cmpxchg(&client->obex->_active_client, client, + active_client); + return -EBUSY; + } + + if ((opcode != req_code) && !atomic_cmpxchg_value(&client->_opcode, + opcode, req_code)) + { + LOG_WRN("OP code mismatch %u != %u", + (uint8_t)atomic_read(&client->_opcode), opcode); + (void)atomic_ptr_cmpxchg(&client->obex->_active_client, client, + active_client); + return -EINVAL; + } + } + + flags = atomic_read(&client->_flags); + + if (buf == NULL) + { + buf = obex_alloc_buf(client->obex); + if (buf == NULL) + { + LOG_WRN("No buffers"); + err = -ENOBUFS; + goto failed; + } + + allocated = true; + } + + err = obex_client_req_check(client, opcode == 0); + if (err != 0) + { + goto failed; + } + + err = obex_client_parse_req_srm(client, buf, opcode == 0); + if (err != 0) + { + goto failed; + } + + err = obex_client_parse_srmp(client, buf, opcode == 0, BT_OBEX_REQ_SRM, + BT_OBEX_REQ_SRMP); + if (err != 0) + { + goto failed; + } + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = req_code; + hdr->len = sys_cpu_to_be16(buf->len); + + atomic_clear_bit(&client->_flags, BT_OBEX_RSP_RECV); + + if (final) + { + atomic_set_bit(&client->_flags, BT_OBEX_REQ_F_BIT); + } + + err = obex_send(client->obex, client->tx.mopl, buf); + if (err == 0) + { + return 0; + } + +failed: + atomic_set(&client->_flags, flags); + obex_client_restore_active_state(client, active_client, opcode); + + if (allocated) + { + net_buf_unref(buf); + } + + return err; +} + +int bt_obex_get_rsp(struct bt_obex_server *server, + uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_obex_rsp_hdr *hdr; + int err; + uint8_t opcode; + bool allocated = false; + bool first; + int32_t flags; + + if (server == NULL || server->obex == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (server->ops == NULL) + { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (atomic_read(&server->_state) != BT_OBEX_CONNECTED) + { + LOG_WRN("Invalid state, connect is not established"); + return -EINVAL; + } + + opcode = atomic_read(&server->_opcode); + if ((opcode != BT_OBEX_OPCODE_GET_F) && (opcode != BT_OBEX_OPCODE_GET)) + { + LOG_WRN("Invalid response"); + return -EINVAL; + } + + if ((opcode != BT_OBEX_OPCODE_GET_F) && + (rsp_code == BT_OBEX_RSP_CODE_SUCCESS)) + { + LOG_WRN("Success cannot be responded without final"); + return -EINVAL; + } + + if (buf == NULL) + { + buf = obex_alloc_buf(server->obex); + if (buf == NULL) + { + LOG_WRN("No buffers"); + return -ENOBUFS; + } + + allocated = true; + } + + flags = atomic_read(&server->_flags); + first = atomic_test_and_clear_bit(&server->_flags, BT_OBEX_REQ_1ST); + + err = obex_server_rsp_check(server, first); + if (err != 0) + { + goto failed; + } + + err = obex_server_parse_rsp_srm(server, buf, first); + if (err != 0) + { + goto failed; + } + + err = obex_server_parse_srmp(server, buf, first, BT_OBEX_RSP_SRM, + BT_OBEX_RSP_SRMP); + if (err != 0) + { + goto failed; + } + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = rsp_code; + hdr->len = sys_cpu_to_be16(buf->len); + + atomic_clear_bit(&server->_flags, BT_OBEX_REQ_RECV); + + err = obex_send(server->obex, server->tx.mopl, buf); + if (err != 0) + { + goto failed; + } + + if (rsp_code != BT_OBEX_RSP_CODE_CONTINUE) + { + atomic_set(&server->_opcode, 0); + atomic_ptr_xchg(&server->obex->_active_server, NULL); + } + + return 0; + +failed: + atomic_set(&server->_flags, flags); + if (allocated) + { + net_buf_unref(buf); + } + + return err; +} + +static const uint8_t abort_opcode[] = +{ + BT_OBEX_OPCODE_PUT, BT_OBEX_OPCODE_PUT_F, + BT_OBEX_OPCODE_GET, + BT_OBEX_OPCODE_GET_F, + BT_OBEX_OPCODE_ACTION, + BT_OBEX_OPCODE_ACTION_F +}; + +static bool obex_op_support_abort(uint8_t opcode) +{ + for (size_t index = 0; index < ARRAY_SIZE(abort_opcode); index++) + { + if (opcode == abort_opcode[index]) + { + return true; + } + } + + return false; +} + +int bt_obex_abort(struct bt_obex_client *client, struct net_buf *buf) +{ + struct bt_obex_req_hdr *hdr; + struct bt_obex_client *active_client; + int err; + uint8_t opcode; + bool allocated = false; + int32_t pre_opcode; + + if (client == NULL || client->obex == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (client->ops == NULL) + { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (atomic_read(&client->_state) != BT_OBEX_CONNECTED) + { + LOG_WRN("Invalid state, connect is not established"); + return -ENOTCONN; + } + + active_client = atomic_ptr_read(&client->obex->_active_client); + if ((active_client != NULL) && (active_client != client)) + { + LOG_WRN("One OBEX request is executing"); + return -EBUSY; + } + + if (!atomic_read(&client->_opcode)) + { + LOG_WRN("No operation is inprogress"); + return -EINVAL; + } + else if (atomic_read(&client->_opcode) == BT_OBEX_OPCODE_ABORT) + { + LOG_WRN("Abort is inprogress"); + return -EINPROGRESS; + } + else if (!obex_op_support_abort((uint8_t)atomic_read(&client->_opcode))) + { + LOG_WRN("Opcode %02x cannot be aborted", + (uint8_t)atomic_read(&client->_opcode)); + return -ENOTSUP; + } + + if (buf == NULL) + { + buf = obex_alloc_buf(client->obex); + if (buf == NULL) + { + LOG_WRN("No buffers"); + return -ENOBUFS; + } + + allocated = true; + } + + opcode = atomic_xchg(&client->_opcode, BT_OBEX_OPCODE_ABORT); + pre_opcode = atomic_xchg(&client->_pre_opcode, 0); + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = BT_OBEX_OPCODE_ABORT; + hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(client->obex, client->tx.mopl, buf); + if (err != 0) + { + atomic_set(&client->_pre_opcode, pre_opcode); + obex_client_restore_active_state(client, active_client, opcode); + + if (allocated) + { + net_buf_unref(buf); + } + } + else + { + atomic_set(&client->_pre_opcode, opcode); + } + + return err; +} + +int bt_obex_abort_rsp(struct bt_obex_server *server, + uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_obex_rsp_hdr *hdr; + int err; + bool allocated = false; + + if (server == NULL || server->obex == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (server->ops == NULL) + { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (atomic_read(&server->_state) != BT_OBEX_CONNECTED) + { + LOG_WRN("Invalid state, connect is not established"); + return -EINVAL; + } + + if (atomic_read(&server->_opcode) != BT_OBEX_OPCODE_ABORT) + { + LOG_WRN("Invalid response"); + return -EINVAL; + } + + if (buf == NULL) + { + buf = obex_alloc_buf(server->obex); + if (buf == NULL) + { + LOG_WRN("No buffers"); + return -ENOBUFS; + } + + allocated = true; + } + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = rsp_code; + hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(server->obex, server->tx.mopl, buf); + if (err == 0) + { + atomic_set(&server->_opcode, 0); + atomic_ptr_xchg(&server->obex->_active_server, NULL); + } + else + { + if (allocated) + { + net_buf_unref(buf); + } + } + + return err; +} + +int bt_obex_setpath(struct bt_obex_client *client, + uint8_t flags, struct net_buf *buf) +{ + struct bt_obex_req_hdr *hdr; + struct bt_obex_setpath_req_hdr *req_hdr; + int err; + bool allocated = false; + + if (client == NULL || client->obex == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (client->ops == NULL) + { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (atomic_read(&client->_state) != BT_OBEX_CONNECTED) + { + LOG_WRN("Invalid state, connect is not established"); + return -ENOTCONN; + } + + if (!atomic_ptr_cmpxchg(&client->obex->_active_client, NULL, client)) + { + LOG_WRN("One OBEX request is executing"); + return -EBUSY; + } + + if (!atomic_cmpxchg_value(&client->_opcode, 0, BT_OBEX_OPCODE_SETPATH)) + { + LOG_WRN("Operation inprogress"); + atomic_ptr_xchg(&client->obex->_active_client, NULL); + return -EINPROGRESS; + } + + if (buf == NULL) + { + buf = obex_alloc_buf(client->obex); + if (buf == NULL) + { + LOG_WRN("No buffers"); + err = -ENOBUFS; + goto failed; + } + + allocated = true; + } + + req_hdr = net_buf_push(buf, sizeof(*req_hdr)); + req_hdr->flags = flags; + req_hdr->constants = 0; + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = BT_OBEX_OPCODE_SETPATH; + hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(client->obex, client->tx.mopl, buf); + if (err == 0) + { + return 0; + } + +failed: + obex_client_clear_active_state(client); + + if (allocated) + { + net_buf_unref(buf); + } + + return err; +} + +int bt_obex_setpath_rsp(struct bt_obex_server *server, + uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_obex_rsp_hdr *hdr; + int err; + bool allocated = false; + + if (server == NULL || server->obex == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (server->ops == NULL) + { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (atomic_read(&server->_state) != BT_OBEX_CONNECTED) + { + LOG_WRN("Invalid state, connect is not established"); + return -EINVAL; + } + + if (atomic_read(&server->_opcode) != BT_OBEX_OPCODE_SETPATH) + { + LOG_WRN("Invalid response"); + return -EINVAL; + } + + if (buf == NULL) + { + buf = obex_alloc_buf(server->obex); + if (buf == NULL) + { + LOG_WRN("No buffers"); + return -ENOBUFS; + } + + allocated = true; + } + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = rsp_code; + hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(server->obex, server->tx.mopl, buf); + if (err == 0) + { + atomic_set(&server->_opcode, 0); + atomic_ptr_xchg(&server->obex->_active_server, NULL); + } + else + { + if (allocated) + { + net_buf_unref(buf); + } + } + + return err; +} + +int bt_obex_action(struct bt_obex_client *client, + bool final, struct net_buf *buf) +{ + struct bt_obex_req_hdr *hdr; + struct bt_obex_client *active_client = NULL; + int err; + uint8_t req_code; + uint8_t opcode = 0; + bool allocated = false; + + if (client == NULL || client->obex == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (client->ops == NULL) + { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (atomic_read(&client->_state) != BT_OBEX_CONNECTED) + { + LOG_WRN("Invalid state, connect is not established"); + return -ENOTCONN; + } + + if (!atomic_ptr_cmpxchg(&client->obex->_active_client, NULL, client)) + { + active_client = atomic_ptr_read(&client->obex->_active_client); + if (active_client != client) + { + LOG_WRN("One OBEX request is executing"); + return -EBUSY; + } + } + + req_code = final ? BT_OBEX_OPCODE_ACTION_F : BT_OBEX_OPCODE_ACTION; + if (!atomic_cmpxchg_value(&client->_opcode, 0, req_code)) + { + opcode = atomic_read(&client->_opcode); + if ((opcode != BT_OBEX_OPCODE_ACTION_F) && + (opcode != BT_OBEX_OPCODE_ACTION)) + { + LOG_WRN("Operation inprogress"); + (void)atomic_ptr_cmpxchg(&client->obex->_active_client, client, + active_client); + return -EBUSY; + } + + if (!final && (opcode == BT_OBEX_OPCODE_ACTION_F)) + { + LOG_WRN("Unexpected action request without final bit"); + (void)atomic_ptr_cmpxchg(&client->obex->_active_client, client, + active_client); + return -EBUSY; + } + + if ((opcode != req_code) && !atomic_cmpxchg_value(&client->_opcode, + opcode, req_code)) + { + LOG_WRN("OP code mismatch %u != %u", + (uint8_t)atomic_read(&client->_opcode), opcode); + (void)atomic_ptr_cmpxchg(&client->obex->_active_client, client, + active_client); + return -EINVAL; + } + } + + if (buf == NULL) + { + buf = obex_alloc_buf(client->obex); + if (buf == NULL) + { + LOG_WRN("No buffers"); + err = -ENOBUFS; + goto failed; + } + + allocated = true; + } + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = req_code; + hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(client->obex, client->tx.mopl, buf); + if (err == 0) + { + return 0; + } + +failed: + obex_client_restore_active_state(client, active_client, opcode); + + if (allocated) + { + net_buf_unref(buf); + } + + return err; +} + +int bt_obex_action_rsp(struct bt_obex_server *server, + uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_obex_rsp_hdr *hdr; + int err; + uint8_t opcode; + bool allocated = false; + + if (server == NULL || server->obex == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (server->ops == NULL) + { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (atomic_read(&server->_state) != BT_OBEX_CONNECTED) + { + LOG_WRN("Invalid state, connect is not established"); + return -EINVAL; + } + + if (buf == NULL) + { + buf = obex_alloc_buf(server->obex); + if (buf == NULL) + { + LOG_WRN("No buffers"); + return -ENOBUFS; + } + + allocated = true; + } + + opcode = atomic_read(&server->_opcode); + if ((opcode != BT_OBEX_OPCODE_ACTION_F) && + (opcode != BT_OBEX_OPCODE_ACTION)) + { + LOG_WRN("Invalid response"); + return -EINVAL; + } + + if ((opcode != BT_OBEX_OPCODE_ACTION_F) && + (rsp_code == BT_OBEX_RSP_CODE_SUCCESS)) + { + LOG_WRN("Success cannot be responded without final"); + return -EINVAL; + } + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = rsp_code; + hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(server->obex, server->tx.mopl, buf); + if (err == 0) + { + if (rsp_code != BT_OBEX_RSP_CODE_CONTINUE) + { + atomic_set(&server->_opcode, 0); + atomic_ptr_xchg(&server->obex->_active_server, NULL); + } + } + else + { + if (allocated) + { + net_buf_unref(buf); + } + } + + return err; +} + +#define BT_OBEX_HEADER_ENCODING(header_id) (0xc0 & (header_id)) +#define BT_OBEX_HEADER_ENCODING_UNICODE 0x00 +#define BT_OBEX_HEADER_ENCODING_BYTE_SEQ 0x40 +#define BT_OBEX_HEADER_ENCODING_1_BYTE 0x80 +#define BT_OBEX_HEADER_ENCODING_4_BYTES 0xc0 + +#define BT_OBEX_UFT16_LEAD_SURROGATE_START 0xd800 +#define BT_OBEX_UFT16_LEAD_SURROGATE_END 0xdbff +#define BT_OBEX_UFT16_TRAIL_SURROGATE_START 0xdc00 +#define BT_OBEX_UFT16_TRAIL_SURROGATE_END 0xdfff + +#define BT_OBEX_UFT16_NULL_TERMINATED 0x0000 + +#define BT_OBEX_UFT16_CODE_OCTETS 0x02 + +static bool bt_obex_unicode_is_valid(uint16_t len, const uint8_t *str) +{ + uint16_t index = 0; + uint16_t code; + + if ((len < BT_OBEX_UFT16_CODE_OCTETS) || (len % BT_OBEX_UFT16_CODE_OCTETS)) + { + LOG_WRN("Invalid string length %d", len); + return false; + } + + code = sys_get_be16(&str[len - BT_OBEX_UFT16_CODE_OCTETS]); + if (code != BT_OBEX_UFT16_NULL_TERMINATED) + { + LOG_WRN("Invalid terminated unicode %04x", code); + return false; + } + + while ((index + 1) < len) + { + code = sys_get_be16(&str[index]); + if ((code >= BT_OBEX_UFT16_LEAD_SURROGATE_START) && + (code <= BT_OBEX_UFT16_LEAD_SURROGATE_END)) + { + /* Find the trail surrogate */ + + index += sizeof(code); + if ((index + 1) >= len) + { + LOG_WRN("Invalid length, trail surrogate missing"); + return false; + } + + code = sys_get_be16(&str[index]); + if ((code < BT_OBEX_UFT16_TRAIL_SURROGATE_START) || + (code > BT_OBEX_UFT16_TRAIL_SURROGATE_END)) + { + LOG_WRN("Invalid trail surrogate %04x at %d", code, index); + return false; + } + } + else if ((code >= BT_OBEX_UFT16_TRAIL_SURROGATE_START) && + (code <= BT_OBEX_UFT16_TRAIL_SURROGATE_END)) + { + LOG_WRN("Abnormal trail surrogate %04x at %d", code, index); + return false; + } + + index += sizeof(code); + } + + return true; +} + +bool bt_obex_string_is_valid(uint8_t id, uint16_t len, const uint8_t *str) +{ + if (BT_OBEX_HEADER_ENCODING(id) == BT_OBEX_HEADER_ENCODING_UNICODE) + { + return bt_obex_unicode_is_valid(len, str); + } + + return true; +} + +int bt_obex_add_header_count(struct net_buf *buf, uint32_t count) +{ + size_t total; + + if (buf == NULL) + { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(count); + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_COUNT); + net_buf_add_be32(buf, count); + return 0; +} + +int bt_obex_add_header_name(struct net_buf *buf, + uint16_t len, const uint8_t *name) +{ + size_t total; + + /* OBEX Version 1.5, section 2.2.2 Name + * The `name` could be a NULL, so the `len` of the name could 0. In some + * cases an empty Name header is used to specify a particular behavior; see + * the GET and SETPATH Operations. An empty Name header is defined as a + * Name header of length 3 (one byte opcode + two byte length). + */ + + if (buf == NULL || (len > 0 && name == NULL)) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + if (len > 0 && !bt_obex_string_is_valid(BT_OBEX_HEADER_ID_NAME, len, name)) + { + LOG_WRN("Invalid string"); + return -EINVAL; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_NAME); + net_buf_add_be16(buf, (uint16_t)total); + if (len > 0) + { + net_buf_add_mem(buf, name, len); + } + + return 0; +} + +int bt_obex_add_header_type(struct net_buf *buf, + uint16_t len, const uint8_t *type) +{ + size_t total; + + if (buf == NULL || type == NULL || len == 0) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_TYPE, len, type)) + { + LOG_WRN("Invalid string"); + return -EINVAL; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_TYPE); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, type, len); + return 0; +} + +int bt_obex_add_header_len(struct net_buf *buf, uint32_t len) +{ + size_t total; + + if (buf == NULL) + { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len); + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_LEN); + net_buf_add_be32(buf, len); + return 0; +} + +int bt_obex_add_header_time_iso_8601(struct net_buf *buf, + uint16_t len, const uint8_t *t) +{ + size_t total; + + if (buf == NULL || t == NULL || len == 0) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_TIME_ISO_8601, len, t)) + { + LOG_WRN("Invalid string"); + return -EINVAL; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_TIME_ISO_8601); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, t, len); + return 0; +} + +int bt_obex_add_header_time(struct net_buf *buf, uint32_t t) +{ + size_t total; + + if (buf == NULL) + { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(t); + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_TIME); + net_buf_add_be32(buf, t); + return 0; +} + +int bt_obex_add_header_description(struct net_buf *buf, + uint16_t len, const uint8_t *dec) +{ + size_t total; + + if (buf == NULL || dec == NULL || len == 0) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_DES, len, dec)) + { + LOG_WRN("Invalid string"); + return -EINVAL; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_DES); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, dec, len); + return 0; +} + +int bt_obex_add_header_target(struct net_buf *buf, + uint16_t len, const uint8_t *target) +{ + size_t total; + + if (buf == NULL || target == NULL || len == 0) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_TARGET, len, target)) + { + LOG_WRN("Invalid string"); + return -EINVAL; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_TARGET); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, target, len); + return 0; +} + +int bt_obex_add_header_http(struct net_buf *buf, + uint16_t len, const uint8_t *http) +{ + size_t total; + + if (buf == NULL || http == NULL || len == 0) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_HTTP, len, http)) + { + LOG_WRN("Invalid string"); + return -EINVAL; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_HTTP); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, http, len); + return 0; +} + +int bt_obex_add_header_body(struct net_buf *buf, + uint16_t len, const uint8_t *body) +{ + size_t total; + + if (buf == NULL || body == NULL || len == 0) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_BODY, len, body)) + { + LOG_WRN("Invalid string"); + return -EINVAL; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_BODY); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, body, len); + return 0; +} + +int bt_obex_add_header_end_body(struct net_buf *buf, + uint16_t len, const uint8_t *body) +{ + size_t total; + + /* OBEX Version 1.5, section 2.2.9 Body, End-of-Body + * The `body` could be a NULL, so the `len` of the name could 0. In some + * cases, the object body data is generated on the fly and the end cannot + * be anticipated, so it is legal to send a zero length End-of-Body header. + */ + + if ((buf == NULL) || ((len != 0) && (body == NULL))) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_END_BODY, len, body)) + { + LOG_WRN("Invalid string"); + return -EINVAL; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_END_BODY); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, body, len); + return 0; +} + +int bt_obex_add_header_who(struct net_buf *buf, + uint16_t len, const uint8_t *who) +{ + size_t total; + + if (buf == NULL || who == NULL || len == 0) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_WHO, len, who)) + { + LOG_WRN("Invalid string"); + return -EINVAL; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_WHO); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, who, len); + return 0; +} + +int bt_obex_add_header_conn_id(struct net_buf *buf, uint32_t conn_id) +{ + size_t total; + + if (buf == NULL) + { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(conn_id); + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_CONN_ID); + net_buf_add_be32(buf, conn_id); + return 0; +} + +int bt_obex_add_header_app_param(struct net_buf *buf, + size_t count, + const struct bt_obex_tlv data[]) +{ + size_t total; + uint16_t len = 0; + + if (buf == NULL || data == NULL || count == 0) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + for (size_t i = 0; i < count; i++) + { + if (data[i].data_len > 0 && data[i].data == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + len += + data[i].data_len + sizeof(data[i].type) + + sizeof(data[i].data_len); + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_APP_PARAM); + net_buf_add_be16(buf, (uint16_t)total); + for (size_t i = 0; i < count; i++) + { + len += + data[i].data_len + sizeof(data[i].type) + + sizeof(data[i].data_len); + net_buf_add_u8(buf, data[i].type); + net_buf_add_u8(buf, data[i].data_len); + net_buf_add_mem(buf, data[i].data, (size_t)data[i].data_len); + } + + return 0; +} + +int bt_obex_add_header_auth_challenge(struct net_buf *buf, size_t count, + const struct bt_obex_tlv data[]) +{ + size_t total; + uint16_t len = 0; + + if (buf == NULL || data == NULL || count == 0) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + for (size_t i = 0; i < count; i++) + { + if (data[i].data_len > 0 && data[i].data == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + len += + data[i].data_len + + sizeof(data[i].type) + sizeof(data[i].data_len); + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_AUTH_CHALLENGE); + net_buf_add_be16(buf, (uint16_t)total); + for (size_t i = 0; i < count; i++) + { + len += + data[i].data_len + + sizeof(data[i].type) + sizeof(data[i].data_len); + net_buf_add_u8(buf, data[i].type); + net_buf_add_u8(buf, data[i].data_len); + net_buf_add_mem(buf, data[i].data, (size_t)data[i].data_len); + } + + return 0; +} + +int bt_obex_add_header_auth_rsp(struct net_buf *buf, + size_t count, + const struct bt_obex_tlv data[]) +{ + size_t total; + uint16_t len = 0; + + if (buf == NULL || data == NULL || count == 0) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + for (size_t i = 0; i < count; i++) + { + if (data[i].data_len > 0 && data[i].data == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + len += + data[i].data_len + sizeof(data[i].type) + + sizeof(data[i].data_len); + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_AUTH_RSP); + net_buf_add_be16(buf, (uint16_t)total); + for (size_t i = 0; i < count; i++) + { + len += + data[i].data_len + sizeof(data[i].type) + + sizeof(data[i].data_len); + net_buf_add_u8(buf, data[i].type); + net_buf_add_u8(buf, data[i].data_len); + net_buf_add_mem(buf, data[i].data, (size_t)data[i].data_len); + } + + return 0; +} + +int bt_obex_add_header_creator_id(struct net_buf *buf, uint32_t creator_id) +{ + size_t total; + + if (buf == NULL) + { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(creator_id); + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_CREATE_ID); + net_buf_add_be32(buf, creator_id); + return 0; +} + +int bt_obex_add_header_wan_uuid(struct net_buf *buf, + uint16_t len, const uint8_t *uuid) +{ + size_t total; + + if (buf == NULL || uuid == NULL || len == 0) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_WAN_UUID, len, uuid)) + { + LOG_WRN("Invalid string"); + return -EINVAL; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_WAN_UUID); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, uuid, len); + return 0; +} + +int bt_obex_add_header_obj_class(struct net_buf *buf, + uint16_t len, const uint8_t *obj_class) +{ + size_t total; + + if (buf == NULL || obj_class == NULL || len == 0) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_OBJECT_CLASS, len, + obj_class)) + { + LOG_WRN("Invalid string"); + return -EINVAL; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_OBJECT_CLASS); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, obj_class, len); + return 0; +} + +int bt_obex_add_header_session_param(struct net_buf *buf, uint16_t len, + const uint8_t *session_param) +{ + size_t total; + + if (buf == NULL || session_param == NULL || len == 0) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_SESSION_PARAM, len, + session_param)) + { + LOG_WRN("Invalid string"); + return -EINVAL; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_SESSION_PARAM); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, session_param, len); + return 0; +} + +int bt_obex_add_header_session_seq_number(struct net_buf *buf, + uint32_t session_seq_number) +{ + size_t total; + + if (buf == NULL) + { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(session_seq_number); + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_SESSION_SEQ_NUM); + net_buf_add_be32(buf, session_seq_number); + return 0; +} + +int bt_obex_add_header_action_id(struct net_buf *buf, uint8_t action_id) +{ + size_t total; + + if (buf == NULL) + { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(action_id); + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_ACTION_ID); + net_buf_add_u8(buf, action_id); + return 0; +} + +int bt_obex_add_header_dest_name(struct net_buf *buf, + uint16_t len, const uint8_t *dest_name) +{ + size_t total; + + if (buf == NULL || dest_name == NULL || len == 0) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_DEST_NAME, len, dest_name)) + { + LOG_WRN("Invalid string"); + return -EINVAL; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_DEST_NAME); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, dest_name, len); + return 0; +} + +int bt_obex_add_header_perm(struct net_buf *buf, uint32_t perm) +{ + size_t total; + + if (buf == NULL) + { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(perm); + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_PERM); + net_buf_add_be32(buf, perm); + return 0; +} + +int bt_obex_add_header_srm(struct net_buf *buf, uint8_t srm) +{ + size_t total; + + if (buf == NULL) + { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(srm); + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_SRM); + net_buf_add_u8(buf, srm); + return 0; +} + +int bt_obex_add_header_srm_param(struct net_buf *buf, uint8_t srm_param) +{ + size_t total; + + if (buf == NULL) + { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(srm_param); + if (net_buf_tailroom(buf) < total) + { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_SRM_PARAM); + net_buf_add_u8(buf, srm_param); + return 0; +} + +int bt_obex_header_parse(struct net_buf *buf, bool( + *func)(struct bt_obex_hdr *hdr, void *user_data), + void *user_data) +{ + uint16_t len = 0; + struct bt_obex_hdr hdr; + uint8_t header_id; + uint16_t header_value_len; + + if (buf == NULL || func == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + while (len < buf->len) + { + header_id = buf->data[len]; + len++; + + switch (BT_OBEX_HEADER_ENCODING(header_id)) + { + case BT_OBEX_HEADER_ENCODING_1_BYTE: + header_value_len = sizeof(header_id); + break; + case BT_OBEX_HEADER_ENCODING_4_BYTES: + header_value_len = 4; + break; + case BT_OBEX_HEADER_ENCODING_UNICODE: + case BT_OBEX_HEADER_ENCODING_BYTE_SEQ: + default: + if ((len + sizeof(header_value_len)) > buf->len) + { + return -EINVAL; + } + + header_value_len = sys_get_be16(&buf->data[len]); + if (header_value_len < (sizeof(header_id) + + sizeof(header_value_len))) + { + return -EINVAL; + } + + header_value_len -= sizeof(header_id) + sizeof(header_value_len); + len += sizeof(header_value_len); + break; + } + + if ((len + header_value_len) > buf->len) + { + return -EINVAL; + } + + hdr.id = header_id; + hdr.data = &buf->data[len]; + hdr.len = header_value_len; + len += header_value_len; + + if (!func(&hdr, user_data)) + { + return 0; + } + } + + return 0; +} + +struct bt_obex_find_header_data +{ + struct bt_obex_hdr hdr; + bool found; +}; + +static bool bt_obex_find_header_cb(struct bt_obex_hdr *hdr, void *user_data) +{ + struct bt_obex_find_header_data *data; + + data = user_data; + + if (hdr->id == data->hdr.id) + { + data->hdr.data = hdr->data; + data->hdr.len = hdr->len; + data->found = true; + return false; + } + + return true; +} + +int bt_obex_get_header_count(struct net_buf *buf, uint32_t *count) +{ + struct bt_obex_find_header_data data; + int err; + + if (buf == NULL || count == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_COUNT; + data.hdr.len = 0; + data.hdr.data = NULL; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err != 0) + { + return err; + } + + if ((data.hdr.len != sizeof(*count)) || data.hdr.data == NULL) + { + return -ENODATA; + } + + *count = sys_get_be32(data.hdr.data); + return 0; +} + +int bt_obex_get_header_name(struct net_buf *buf, + uint16_t *len, const uint8_t **name) +{ + struct bt_obex_find_header_data data; + int err; + + if (buf == NULL || len == NULL || name == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_NAME; + data.hdr.len = 0; + data.hdr.data = NULL; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err != 0) + { + return err; + } + + if (!data.found) + { + return -ENODATA; + } + + *len = data.hdr.len; + *name = data.hdr.data; + return 0; +} + +int bt_obex_get_header_type(struct net_buf *buf, + uint16_t *len, const uint8_t **type) +{ + struct bt_obex_find_header_data data; + int err; + + if (buf == NULL || len == NULL || type == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_TYPE; + data.hdr.len = 0; + data.hdr.data = NULL; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err != 0) + { + return err; + } + + if (data.hdr.len == 0 || data.hdr.data == NULL) + { + return -ENODATA; + } + + *len = data.hdr.len; + *type = data.hdr.data; + return 0; +} + +int bt_obex_get_header_len(struct net_buf *buf, uint32_t *len) +{ + struct bt_obex_find_header_data data; + int err; + + if (buf == NULL || len == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_LEN; + data.hdr.len = 0; + data.hdr.data = NULL; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err != 0) + { + return err; + } + + if ((data.hdr.len != sizeof(*len)) || data.hdr.data == NULL) + { + return -ENODATA; + } + + *len = sys_get_be32(data.hdr.data); + return 0; +} + +int bt_obex_get_header_time_iso_8601(struct net_buf *buf, + uint16_t *len, const uint8_t **t) +{ + struct bt_obex_find_header_data data; + int err; + + if (buf == NULL || len == NULL || t == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_TIME_ISO_8601; + data.hdr.len = 0; + data.hdr.data = NULL; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err != 0) + { + return err; + } + + if (data.hdr.len == 0 || data.hdr.data == NULL) + { + return -ENODATA; + } + + *len = data.hdr.len; + *t = data.hdr.data; + return 0; +} + +int bt_obex_get_header_time(struct net_buf *buf, uint32_t *t) +{ + struct bt_obex_find_header_data data; + int err; + + if (buf == NULL || t == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_TIME; + data.hdr.len = 0; + data.hdr.data = NULL; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err != 0) + { + return err; + } + + if ((data.hdr.len != sizeof(*t)) || data.hdr.data == NULL) + { + return -ENODATA; + } + + *t = sys_get_be32(data.hdr.data); + return 0; +} + +int bt_obex_get_header_description(struct net_buf *buf, + uint16_t *len, const uint8_t **dec) +{ + struct bt_obex_find_header_data data; + int err; + + if (buf == NULL || len == NULL || dec == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_DES; + data.hdr.len = 0; + data.hdr.data = NULL; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err != 0) + { + return err; + } + + if (data.hdr.len == 0 || data.hdr.data == NULL) + { + return -ENODATA; + } + + *len = data.hdr.len; + *dec = data.hdr.data; + return 0; +} + +int bt_obex_get_header_target(struct net_buf *buf, + uint16_t *len, const uint8_t **target) +{ + struct bt_obex_find_header_data data; + int err; + + if (buf == NULL || len == NULL || target == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_TARGET; + data.hdr.len = 0; + data.hdr.data = NULL; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err != 0) + { + return err; + } + + if (data.hdr.len == 0 || data.hdr.data == NULL) + { + return -ENODATA; + } + + *len = data.hdr.len; + *target = data.hdr.data; + return 0; +} + +int bt_obex_get_header_http(struct net_buf *buf, + uint16_t *len, const uint8_t **http) +{ + struct bt_obex_find_header_data data; + int err; + + if (buf == NULL || len == NULL || http == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_HTTP; + data.hdr.len = 0; + data.hdr.data = NULL; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err != 0) + { + return err; + } + + if (data.hdr.len == 0 || data.hdr.data == NULL) + { + return -ENODATA; + } + + *len = data.hdr.len; + *http = data.hdr.data; + return 0; +} + +int bt_obex_get_header_body(struct net_buf *buf, + uint16_t *len, const uint8_t **body) +{ + struct bt_obex_find_header_data data; + int err; + + if (buf == NULL || len == NULL || body == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_BODY; + data.hdr.len = 0; + data.hdr.data = NULL; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err != 0) + { + return err; + } + + if (data.hdr.len == 0 || data.hdr.data == NULL) + { + return -ENODATA; + } + + *len = data.hdr.len; + *body = data.hdr.data; + return 0; +} + +int bt_obex_get_header_end_body(struct net_buf *buf, + uint16_t *len, const uint8_t **body) +{ + struct bt_obex_find_header_data data; + int err; + + if (buf == NULL || len == NULL || body == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_END_BODY; + data.hdr.len = 0; + data.hdr.data = NULL; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err != 0) + { + return err; + } + + if (data.hdr.len == 0 || data.hdr.data == NULL) + { + return -ENODATA; + } + + *len = data.hdr.len; + *body = data.hdr.data; + return 0; +} + +int bt_obex_get_header_who(struct net_buf *buf, + uint16_t *len, const uint8_t **who) +{ + struct bt_obex_find_header_data data; + int err; + + if (buf == NULL || len == NULL || who == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_WHO; + data.hdr.len = 0; + data.hdr.data = NULL; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err != 0) + { + return err; + } + + if (data.hdr.len == 0 || data.hdr.data == NULL) + { + return -ENODATA; + } + + *len = data.hdr.len; + *who = data.hdr.data; + return 0; +} + +int bt_obex_get_header_conn_id(struct net_buf *buf, uint32_t *conn_id) +{ + struct bt_obex_find_header_data data; + int err; + + if (buf == NULL || conn_id == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_CONN_ID; + data.hdr.len = 0; + data.hdr.data = NULL; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err != 0) + { + return err; + } + + if ((data.hdr.len != sizeof(*conn_id)) || data.hdr.data == NULL) + { + return -ENODATA; + } + + *conn_id = sys_get_be32(data.hdr.data); + return 0; +} + +int bt_obex_tlv_parse(uint16_t len, const uint8_t *data, bool( + *func)(struct bt_obex_tlv *tlv, void *user_data), + void *user_data) +{ + uint16_t index = 0; + struct bt_obex_tlv tlv; + + if (len == 0 || data == NULL || func == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + while ((index + 2) <= len) + { + tlv.type = data[index]; + tlv.data_len = data[index + 1]; + index += 2; + if ((index + tlv.data_len) > len) + { + break; + } + + tlv.data = &data[index]; + index += tlv.data_len; + if (!func(&tlv, user_data)) + { + return 0; + } + } + + return 0; +} + +int bt_obex_get_header_app_param(struct net_buf *buf, + uint16_t *len, const uint8_t **app_param) +{ + struct bt_obex_find_header_data data; + int err; + + if (buf == NULL || len == NULL || app_param == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_APP_PARAM; + data.hdr.len = 0; + data.hdr.data = NULL; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err != 0) + { + return err; + } + + if (data.hdr.len == 0 || data.hdr.data == NULL) + { + return -ENODATA; + } + + *len = data.hdr.len; + *app_param = data.hdr.data; + return 0; +} + +struct bt_obex_has_app_param +{ + uint8_t id; + bool found; +}; + +static bool bt_obex_has_app_param_cb(struct bt_obex_tlv *tlv, + void *user_data) +{ + struct bt_obex_has_app_param *data = user_data; + + if (tlv->type == data->id) + { + data->found = true; + return false; + } + + return true; +} + +bool bt_obex_has_app_param(struct net_buf *buf, uint8_t id) +{ + struct bt_obex_has_app_param ap; + uint16_t len = 0; + const uint8_t *data = NULL; + int err; + + if (bt_obex_get_header_app_param(buf, &len, &data) != 0) + { + return false; + } + + if (len == 0U || data == NULL) + { + return false; + } + + ap.id = id; + ap.found = false; + + err = bt_obex_tlv_parse(len, data, bt_obex_has_app_param_cb, &ap); + if (err != 0) + { + return false; + } + + return ap.found; +} + +int bt_obex_get_header_auth_challenge(struct net_buf *buf, + uint16_t *len, const uint8_t **auth) +{ + struct bt_obex_find_header_data data; + int err; + + if (buf == NULL || len == NULL || auth == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_AUTH_CHALLENGE; + data.hdr.len = 0; + data.hdr.data = NULL; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err != 0) + { + return err; + } + + if (data.hdr.len == 0 || data.hdr.data == NULL) + { + return -ENODATA; + } + + *len = data.hdr.len; + *auth = data.hdr.data; + return 0; +} + +int bt_obex_get_header_auth_rsp(struct net_buf *buf, + uint16_t *len, const uint8_t **auth) +{ + struct bt_obex_find_header_data data; + int err; + + if (buf == NULL || len == NULL || auth == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_AUTH_RSP; + data.hdr.len = 0; + data.hdr.data = NULL; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err != 0) + { + return err; + } + + if (data.hdr.len == 0 || data.hdr.data == NULL) + { + return -ENODATA; + } + + *len = data.hdr.len; + *auth = data.hdr.data; + return 0; +} + +int bt_obex_get_header_creator_id(struct net_buf *buf, uint32_t *creator_id) +{ + struct bt_obex_find_header_data data; + int err; + + if (buf == NULL || creator_id == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_CREATE_ID; + data.hdr.len = 0; + data.hdr.data = NULL; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err != 0) + { + return err; + } + + if ((data.hdr.len != sizeof(*creator_id)) || data.hdr.data == NULL) + { + return -ENODATA; + } + + *creator_id = sys_get_be32(data.hdr.data); + return 0; +} + +int bt_obex_get_header_wan_uuid(struct net_buf *buf, + uint16_t *len, const uint8_t **uuid) +{ + struct bt_obex_find_header_data data; + int err; + + if (buf == NULL || len == NULL || uuid == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_WAN_UUID; + data.hdr.len = 0; + data.hdr.data = NULL; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err != 0) + { + return err; + } + + if (data.hdr.len == 0 || data.hdr.data == NULL) + { + return -ENODATA; + } + + *len = data.hdr.len; + *uuid = data.hdr.data; + return 0; +} + +int bt_obex_get_header_obj_class(struct net_buf *buf, + uint16_t *len, const uint8_t **obj_class) +{ + struct bt_obex_find_header_data data; + int err; + + if (buf == NULL || len == NULL || obj_class == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_OBJECT_CLASS; + data.hdr.len = 0; + data.hdr.data = NULL; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err != 0) + { + return err; + } + + if (data.hdr.len == 0 || data.hdr.data == NULL) + { + return -ENODATA; + } + + *len = data.hdr.len; + *obj_class = data.hdr.data; + return 0; +} + +int bt_obex_get_header_session_param(struct net_buf *buf, uint16_t *len, + const uint8_t **session_param) +{ + struct bt_obex_find_header_data data; + int err; + + if (buf == NULL || len == NULL || session_param == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_SESSION_PARAM; + data.hdr.len = 0; + data.hdr.data = NULL; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err != 0) + { + return err; + } + + if (data.hdr.len == 0 || data.hdr.data == NULL) + { + return -ENODATA; + } + + *len = data.hdr.len; + *session_param = data.hdr.data; + return 0; +} + +int bt_obex_get_header_session_seq_number(struct net_buf *buf, + uint32_t *session_seq_number) +{ + struct bt_obex_find_header_data data; + int err; + + if (buf == NULL || session_seq_number == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_SESSION_SEQ_NUM; + data.hdr.len = 0; + data.hdr.data = NULL; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err != 0) + { + return err; + } + + if ((data.hdr.len != sizeof(*session_seq_number)) || data.hdr.data == NULL) + { + return -ENODATA; + } + + *session_seq_number = sys_get_be32(data.hdr.data); + return 0; +} + +int bt_obex_get_header_action_id(struct net_buf *buf, uint8_t *action_id) +{ + struct bt_obex_find_header_data data; + int err; + + if (buf == NULL || action_id == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_ACTION_ID; + data.hdr.len = 0; + data.hdr.data = NULL; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err != 0) + { + return err; + } + + if ((data.hdr.len != sizeof(*action_id)) || data.hdr.data == NULL) + { + return -ENODATA; + } + + *action_id = data.hdr.data[0]; + return 0; +} + +int bt_obex_get_header_dest_name(struct net_buf *buf, + uint16_t *len, const uint8_t **dest_name) +{ + struct bt_obex_find_header_data data; + int err; + + if (buf == NULL || len == NULL || dest_name == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_DEST_NAME; + data.hdr.len = 0; + data.hdr.data = NULL; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err != 0) + { + return err; + } + + if (data.hdr.len == 0 || data.hdr.data == NULL) + { + return -ENODATA; + } + + *len = data.hdr.len; + *dest_name = data.hdr.data; + return 0; +} + +int bt_obex_get_header_perm(struct net_buf *buf, uint32_t *perm) +{ + struct bt_obex_find_header_data data; + int err; + + if (buf == NULL || perm == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_PERM; + data.hdr.len = 0; + data.hdr.data = NULL; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err != 0) + { + return err; + } + + if ((data.hdr.len != sizeof(*perm)) || data.hdr.data == NULL) + { + return -ENODATA; + } + + *perm = sys_get_be32(data.hdr.data); + return 0; +} + +int bt_obex_get_header_srm(struct net_buf *buf, uint8_t *srm) +{ + struct bt_obex_find_header_data data; + int err; + + if (buf == NULL || srm == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_SRM; + data.hdr.len = 0; + data.hdr.data = NULL; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err != 0) + { + return err; + } + + if ((data.hdr.len != sizeof(*srm)) || data.hdr.data == NULL) + { + return -ENODATA; + } + + *srm = data.hdr.data[0]; + return 0; +} + +int bt_obex_get_header_srm_param(struct net_buf *buf, uint8_t *srm_param) +{ + struct bt_obex_find_header_data data; + int err; + + if (buf == NULL || srm_param == NULL) + { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_SRM_PARAM; + data.hdr.len = 0; + data.hdr.data = NULL; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err != 0) + { + return err; + } + + if ((data.hdr.len != sizeof(*srm_param)) || data.hdr.data == NULL) + { + return -ENODATA; + } + + *srm_param = data.hdr.data[0]; + return 0; +} + +#if defined(CONFIG_BT_OEBX_RSP_CODE_TO_STR) +const char *bt_obex_rsp_code_to_str(enum bt_obex_rsp_code rsp_code) +{ + const char *rsp_code_str; + + switch (rsp_code) + { + case BT_OBEX_RSP_CODE_CONTINUE: + rsp_code_str = "Continue"; + break; + case BT_OBEX_RSP_CODE_SUCCESS: + rsp_code_str = "Success"; + break; + case BT_OBEX_RSP_CODE_CREATED: + rsp_code_str = "Created"; + break; + case BT_OBEX_RSP_CODE_ACCEPTED: + rsp_code_str = "Accepted"; + break; + case BT_OBEX_RSP_CODE_NON_AUTH_INFO: + rsp_code_str = "Non-Authoritative Information"; + break; + case BT_OBEX_RSP_CODE_NO_CONTENT: + rsp_code_str = "No Content"; + break; + case BT_OBEX_RSP_CODE_RESET_CONTENT: + rsp_code_str = "Reset Content"; + break; + case BT_OBEX_RSP_CODE_PARTIAL_CONTENT: + rsp_code_str = "Partial Content"; + break; + case BT_OBEX_RSP_CODE_MULTI_CHOICES: + rsp_code_str = "Multiple Choices"; + break; + case BT_OBEX_RSP_CODE_MOVED_PERM: + rsp_code_str = "Moved Permanently"; + break; + case BT_OBEX_RSP_CODE_MOVED_TEMP: + rsp_code_str = "Moved temporarily"; + break; + case BT_OBEX_RSP_CODE_SEE_OTHER: + rsp_code_str = "See Other"; + break; + case BT_OBEX_RSP_CODE_NOT_MODIFIED: + rsp_code_str = "Not modified"; + break; + case BT_OBEX_RSP_CODE_USE_PROXY: + rsp_code_str = "Use Proxy"; + break; + case BT_OBEX_RSP_CODE_BAD_REQ: + rsp_code_str = "Bad Request - server couldn't understand request"; + break; + case BT_OBEX_RSP_CODE_UNAUTH: + rsp_code_str = "Unauthorized"; + break; + case BT_OBEX_RSP_CODE_PAY_REQ: + rsp_code_str = "Payment Required"; + break; + case BT_OBEX_RSP_CODE_FORBIDDEN: + rsp_code_str = "Forbidden - operation is understood but refused"; + break; + case BT_OBEX_RSP_CODE_NOT_FOUND: + rsp_code_str = "Not Found"; + break; + case BT_OBEX_RSP_CODE_NOT_ALLOW: + rsp_code_str = "Method Not Allowed"; + break; + case BT_OBEX_RSP_CODE_NOT_ACCEPT: + rsp_code_str = "Not Acceptable"; + break; + case BT_OBEX_RSP_CODE_PROXY_AUTH_REQ: + rsp_code_str = "Proxy Authentication Required"; + break; + case BT_OBEX_RSP_CODE_REQ_TIMEOUT: + rsp_code_str = "Request Time Out"; + break; + case BT_OBEX_RSP_CODE_CONFLICT: + rsp_code_str = "Conflict"; + break; + case BT_OBEX_RSP_CODE_GONE: + rsp_code_str = "Gone"; + break; + case BT_OBEX_RSP_CODE_LEN_REQ: + rsp_code_str = "Length Required"; + break; + case BT_OBEX_RSP_CODE_PRECON_FAIL: + rsp_code_str = "Precondition Failed"; + break; + case BT_OBEX_RSP_CODE_ENTITY_TOO_LARGE: + rsp_code_str = "Requested Entity Too Large"; + break; + case BT_OBEX_RSP_CODE_URL_TOO_LARGE: + rsp_code_str = "Requested URL Too Large"; + break; + case BT_OBEX_RSP_CODE_UNSUPP_MEDIA_TYPE: + rsp_code_str = "Unsupported media type"; + break; + case BT_OBEX_RSP_CODE_INTER_ERROR: + rsp_code_str = "Internal serve Error"; + break; + case BT_OBEX_RSP_CODE_NOT_IMPL: + rsp_code_str = "Not Implemented"; + break; + case BT_OBEX_RSP_CODE_BAD_GATEWAY: + rsp_code_str = "Bad Gateway"; + break; + case BT_OBEX_RSP_CODE_UNAVAIL: + rsp_code_str = "Service Unavailable"; + break; + case BT_OBEX_RSP_CODE_GATEWAY_TIMEOUT: + rsp_code_str = "Gateway Timeout"; + break; + case BT_OBEX_RSP_CODE_VER_UNSUPP: + rsp_code_str = "HTTP Version not supported"; + break; + case BT_OBEX_RSP_CODE_DB_FULL: + rsp_code_str = "Database Full"; + break; + case BT_OBEX_RSP_CODE_DB_LOCK: + rsp_code_str = "Database Locked"; + break; + default: + rsp_code_str = "Unknown"; + break; + } + + return rsp_code_str; +} +#endif /* CONFIG_BT_OEBX_RSP_CODE_TO_STR */ + +#if defined(CONFIG_BT_OBEX_AUTH) +int bt_obex_generate_nonce(const uint8_t *pwd, size_t pwd_len, + uint8_t nonce[BT_OBEX_CHALLENGE_TAG_NONCE_LEN]) +{ + int64_t timestamp = TICK2MSEC(clock_systime_ticks()); + uint8_t data[CONFIG_BT_OBEX_AUTH_PWD_LEN + 1U + sizeof(timestamp)]; + struct net_buf_simple buf; + size_t len; + int err; + + if (pwd == NULL || nonce == NULL || pwd_len == 0 || + pwd_len > CONFIG_BT_OBEX_AUTH_PWD_LEN) + { + return -EINVAL; + } + + LOG_WRN("OBEX authentication relies on legacy MD5-based OBEX " + "authentication"); + + net_buf_simple_init_with_data(&buf, data, sizeof(data)); + net_buf_simple_reset(&buf); + + net_buf_simple_add_mem(&buf, (uint8_t *)×tamp, sizeof(timestamp)); + net_buf_simple_add_u8(&buf, (uint8_t)':'); + net_buf_simple_add_mem(&buf, pwd, pwd_len); + err = psa_hash_compute(PSA_ALG_MD5, (const unsigned char *)buf.data, + buf.len, + nonce, + BT_OBEX_CHALLENGE_TAG_NONCE_LEN, &len); + if (err != 0) + { + LOG_ERR("Generate nonce failed %d", err); + return err; + } + + if (len != BT_OBEX_CHALLENGE_TAG_NONCE_LEN) + { + LOG_ERR("Generated nonce len is invalid (%zu != %zu)", len, + BT_OBEX_CHALLENGE_TAG_NONCE_LEN); + return -EINVAL; + } + + return 0; +} + +int bt_obex_calculate_request_digest(const uint8_t *pwd, + size_t pwd_len, + const uint8_t nonce[BT_OBEX_CHALLENGE_TAG_NONCE_LEN], + uint8_t digest[BT_OBEX_RESPONSE_TAG_REQ_DIGEST_LEN]) +{ + uint8_t + data[CONFIG_BT_OBEX_AUTH_PWD_LEN + BT_OBEX_CHALLENGE_TAG_NONCE_LEN + 1U]; + struct net_buf_simple buf; + size_t len; + int err; + + if (pwd == NULL || nonce == NULL || digest == NULL || pwd_len == 0 || + pwd_len > CONFIG_BT_OBEX_AUTH_PWD_LEN) + { + return -EINVAL; + } + + LOG_WRN("OBEX authentication relies on legacy MD5-based OBEX " + "authentication"); + + net_buf_simple_init_with_data(&buf, data, sizeof(data)); + net_buf_simple_reset(&buf); + + net_buf_simple_add_mem(&buf, nonce, BT_OBEX_CHALLENGE_TAG_NONCE_LEN); + net_buf_simple_add_u8(&buf, (uint8_t)':'); + net_buf_simple_add_mem(&buf, pwd, pwd_len); + + err = psa_hash_compute(PSA_ALG_MD5, (const unsigned char *)buf.data, + buf.len, + digest, + BT_OBEX_RESPONSE_TAG_REQ_DIGEST_LEN, &len); + if (err != 0) + { + LOG_ERR("Generate request-digest failed %d", err); + return err; + } + + if (len != BT_OBEX_RESPONSE_TAG_REQ_DIGEST_LEN) + { + LOG_ERR("Generated request-digest len is invalid (%zu != %zu)", len, + BT_OBEX_RESPONSE_TAG_REQ_DIGEST_LEN); + return -EINVAL; + } + + return 0; +} + +int bt_obex_verify_auth_response(const uint8_t *pwd, + size_t pwd_len, + const uint8_t nonce[BT_OBEX_CHALLENGE_TAG_NONCE_LEN], + const uint8_t digest[BT_OBEX_RESPONSE_TAG_REQ_DIGEST_LEN]) +{ + uint8_t calc_digest[BT_OBEX_RESPONSE_TAG_REQ_DIGEST_LEN]; + int err; + + if (pwd == NULL || nonce == NULL || digest == NULL || pwd_len == 0 || + pwd_len > CONFIG_BT_OBEX_AUTH_PWD_LEN) + { + return -EINVAL; + } + + err = bt_obex_calculate_request_digest(pwd, pwd_len, nonce, calc_digest); + if (err != 0) + { + LOG_ERR("Failed to calculate request digest %d", err); + return err; + } + + err = mbedtls_ct_memcmp(calc_digest, digest, + BT_OBEX_RESPONSE_TAG_REQ_DIGEST_LEN); + if (err != 0) + { + LOG_ERR("Request-digest is invalid"); + return -EINVAL; + } + + return 0; +} +#endif /* CONFIG_BT_OBEX_AUTH */ diff --git a/libs/zblue/host/classic/obex_internal.h b/libs/zblue/host/classic/obex_internal.h new file mode 100644 index 0000000000000..b3a5daa8e776e --- /dev/null +++ b/libs/zblue/host/classic/obex_internal.h @@ -0,0 +1,81 @@ +/**************************************************************************** + * libs/zblue/host/classic/obex_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* obex.c - Internal for IrDA Object Exchange Protocol handling */ + +/* Copyright 2024-2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define BT_OBEX_VERSION 0x10 + +#define BT_OBEX_OPCODE_CONNECT 0x80 +#define BT_OBEX_OPCODE_DISCONN 0x81 +#define BT_OBEX_OPCODE_PUT 0x02 +#define BT_OBEX_OPCODE_PUT_F 0x82 +#define BT_OBEX_OPCODE_GET 0x03 +#define BT_OBEX_OPCODE_GET_F 0x83 +#define BT_OBEX_OPCODE_SETPATH 0x85 +#define BT_OBEX_OPCODE_ACTION 0x06 +#define BT_OBEX_OPCODE_ACTION_F 0x86 +#define BT_OBEX_OPCODE_SESSION 0x87 +#define BT_OBEX_OPCODE_ABORT 0xff + +struct bt_obex_comm_hdr +{ + uint8_t code; +} __packed; + +struct bt_obex_req_hdr +{ + uint8_t code; + uint16_t len; +} __packed; + +struct bt_obex_rsp_hdr +{ + uint8_t code; + uint16_t len; +} __packed; + +struct bt_obex_conn_req_hdr +{ + uint8_t version; + uint8_t flags; + uint16_t mopl; +} __packed; + +struct bt_obex_conn_rsp_hdr +{ + uint8_t version; + uint8_t flags; + uint16_t mopl; +} __packed; + +struct bt_obex_setpath_req_hdr +{ + uint8_t flags; + uint8_t constants; +} __packed; + +/* OBEX initialization */ + +void bt_obex_reg_transport(struct bt_obex *obex, + const struct bt_obex_transport_ops *ops); + +/* Process the received OBEX packet */ + +int bt_obex_recv(struct bt_obex *obex, struct net_buf *buf); + +/* Notify OBEX that the transport has been connected */ + +int bt_obex_transport_connected(struct bt_obex *obex); + +/* Notify OBEX that the transport has been disconnected */ + +int bt_obex_transport_disconnected(struct bt_obex *obex); diff --git a/libs/zblue/host/classic/pbap.c b/libs/zblue/host/classic/pbap.c new file mode 100644 index 0000000000000..fe43799c79d15 --- /dev/null +++ b/libs/zblue/host/classic/pbap.c @@ -0,0 +1,1801 @@ +/**************************************************************************** + * libs/zblue/host/classic/pbap.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* pbap.c - Phone Book Access Profile handling */ + +/* Copyright 2025-2026 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "l2cap_br_internal.h" +#include "rfcomm_internal.h" +#include "obex_internal.h" +#include "pbap_internal.h" + +#define LOG_LEVEL CONFIG_BT_PBAP_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_pbap); + +#define PBAP_PWD_MAX_LENGTH 16U +#define PBAP_POOL_BUF_SIZE \ + MAX(BT_RFCOMM_BUF_SIZE(CONFIG_BT_GOEP_RFCOMM_MTU), BT_L2CAP_BUF_SIZE(CONFIG_BT_GOEP_L2CAP_MTU)) + +NET_BUF_POOL_FIXED_DEFINE(bt_pbap_pool, + CONFIG_BT_MAX_CONN, + PBAP_POOL_BUF_SIZE, + + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + +static const struct bt_uuid_128 *pbap_uuid = BT_PBAP_UUID; + +#define PBAP_REQUIRED_HDR(_count, _hdrs) \ + { \ + .count = (_count), \ + .hdrs = (const uint8_t *)(_hdrs), \ +} + +#define _PBAP_REQUIRED_HDR_LIST(...) \ + PBAP_REQUIRED_HDR(sizeof((uint8_t[]){__VA_ARGS__}), ((uint8_t[]){__VA_ARGS__})) + +#define PBAP_REQUIRED_HDR_LIST(...) _PBAP_REQUIRED_HDR_LIST(__VA_ARGS__) + +struct pbap_required_hdr +{ + uint8_t count; + const uint8_t *hdrs; +}; + +#if defined(CONFIG_BT_PBAP_PCE) +typedef void( + *bt_pbap_pce_cb_t)(struct bt_pbap_pce *pbap_pce, uint8_t rsp_code, struct + *net_buf *buf); +#endif /* CONFIG_BT_PBAP_PCE */ + +#if defined(CONFIG_BT_PBAP_PSE) +typedef void (*bt_pbap_pse_cb_t)(struct bt_pbap_pse *pbap_pse, + struct net_buf *buf); +#endif /* CONFIG_BT_PBAP_PSE */ + +struct pbap_pull_function +{ + const char *type; + struct pbap_required_hdr req_hdr; +#if defined(CONFIG_BT_PBAP_PSE) + bt_pbap_pse_cb_t (*get_server_cb)(struct bt_pbap_pse *pbap_pse); +#endif /* CONFIG_BT_PBAP_PSE */ + +#if defined(CONFIG_BT_PBAP_PCE) + bt_pbap_pce_cb_t (*get_client_cb)(struct bt_pbap_pce *pbap_pce); +#endif /* CONFIG_BT_PBAP_PCE */ +}; + +#define PULL_PHONEBOOK_REQUIRED_HDR \ + BT_OBEX_HEADER_ID_NAME, BT_OBEX_HEADER_ID_TYPE, BT_OBEX_HEADER_ID_CONN_ID + +#define PULL_VCARDLISTING_REQUIRED_HDR \ + BT_OBEX_HEADER_ID_NAME, BT_OBEX_HEADER_ID_TYPE, BT_OBEX_HEADER_ID_CONN_ID + +#define PULL_VCARDENTRY_REQUIRED_HDR \ + BT_OBEX_HEADER_ID_NAME, BT_OBEX_HEADER_ID_TYPE, BT_OBEX_HEADER_ID_CONN_ID + +#if defined(CONFIG_BT_PBAP_PCE) + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static bt_pbap_pce_cb_t pbap_pce_pull_phonebook_cb( + struct bt_pbap_pce *pbap_pce) +{ + return pbap_pce->cb->pull_phone_book; +} + +static bt_pbap_pce_cb_t pbap_pce_pull_vcardlisting_cb( + struct bt_pbap_pce *pbap_pce) +{ + return pbap_pce->cb->pull_vcard_listing; +} + +static bt_pbap_pce_cb_t pbap_pce_pull_vcardentry_cb( + struct bt_pbap_pce *pbap_pce) +{ + return pbap_pce->cb->pull_vcard_entry; +} +#endif /* CONFIG_BT_PBAP_PCE */ + +#if defined(CONFIG_BT_PBAP_PSE) +static bt_pbap_pse_cb_t pbap_pse_pull_phonebook_cb( + struct bt_pbap_pse *pbap_pse) +{ + return pbap_pse->cb->pull_phone_book; +} + +static bt_pbap_pse_cb_t pbap_pse_pull_vcardlisting_cb( + struct bt_pbap_pse *pbap_pse) +{ + return pbap_pse->cb->pull_vcard_listing; +} + +static bt_pbap_pse_cb_t pbap_pse_pull_vcardentry_cb( + struct bt_pbap_pse *pbap_pse) +{ + return pbap_pse->cb->pull_vcard_entry; +} +#endif /* CONFIG_BT_PBAP_PSE */ + +static struct pbap_pull_function pbap_pull_functions[] = +{ + { + BT_PBAP_PULL_PHONE_BOOK_TYPE, PBAP_REQUIRED_HDR_LIST( + PULL_PHONEBOOK_REQUIRED_HDR), #if defined(CONFIG_BT_PBAP_PSE) + pbap_pse_pull_phonebook_cb, +#endif /* CONFIG_BT_PBAP_PSE */ + +#if defined(CONFIG_BT_PBAP_PCE) + pbap_pce_pull_phonebook_cb +#endif /* CONFIG_BT_PBAP_PCE */ + }, + + { + BT_PBAP_PULL_VCARD_LISTING_TYPE, PBAP_REQUIRED_HDR_LIST( + PULL_VCARDLISTING_REQUIRED_HDR), #if defined(CONFIG_BT_PBAP_PSE) + pbap_pse_pull_vcardlisting_cb, +#endif /* CONFIG_BT_PBAP_PSE */ + +#if defined(CONFIG_BT_PBAP_PCE) + pbap_pce_pull_vcardlisting_cb +#endif /* CONFIG_BT_PBAP_PCE */ + }, + + { + BT_PBAP_PULL_VCARD_ENTRY_TYPE, PBAP_REQUIRED_HDR_LIST( + PULL_VCARDENTRY_REQUIRED_HDR), #if defined(CONFIG_BT_PBAP_PSE) + pbap_pse_pull_vcardentry_cb, +#endif /* CONFIG_BT_PBAP_PSE */ + +#if defined(CONFIG_BT_PBAP_PCE) + pbap_pce_pull_vcardentry_cb +#endif /* CONFIG_BT_PBAP_PCE */ + }, +}; + +static bool has_required_hdrs(struct net_buf *buf, + const struct pbap_required_hdr *hdr) +{ + for (uint8_t index = 0; index < hdr->count; index++) + { + if (!bt_obex_has_header(buf, hdr->hdrs[index])) + { + return false; + } + } + + return true; +}; + +static int pbap_check_header_conn_id(uint32_t id, struct net_buf *buf) +{ + uint32_t conn_id; + int err; + + err = bt_obex_get_header_conn_id(buf, &conn_id); + if (err != 0) + { + LOG_ERR("Failed to get conn id %d", err); + return err; + } + + if (conn_id != id) + { + LOG_ERR("Conn id is mismatched %u != %u", conn_id, id); + return -EINVAL; + } + + return 0; +} + +static int bt_pbap_check_srm(struct net_buf *buf) +{ + uint8_t srm; + int err; + + err = bt_obex_get_header_srm(buf, &srm); + if (err != 0) + { + LOG_ERR("Failed to get SRM header %d", err); + return err; + } + + if (srm != 0x01) + { + LOG_ERR("SRM is not supported"); + return -EINVAL; + } + + return 0; +} + +#if defined(CONFIG_BT_PBAP_PCE) +static void pbap_pce_clear_pending_request(struct bt_pbap_pce *pbap_pce) +{ + pbap_pce->_rsp_cb = NULL; + pbap_pce->_req_type = NULL; +} + +static void pbap_pce_rfcomm_transport_connected(struct bt_conn *conn, + struct bt_goep *goep) +{ + struct bt_pbap_pce *pbap_pce = CONTAINER_OF(goep, struct bt_pbap_pce, + _goep); + + atomic_set(&pbap_pce->_transport_state, BT_PBAP_TRANSPORT_STATE_CONNECTED); + + if (pbap_pce->cb != NULL && pbap_pce->cb->rfcomm_connected != NULL) + { + pbap_pce->cb->rfcomm_connected(conn, pbap_pce); + } +} + +static void pbap_pce_rfcomm_transport_disconnected(struct bt_goep *goep) +{ + struct bt_pbap_pce *pbap_pce = CONTAINER_OF(goep, struct bt_pbap_pce, + _goep); + + atomic_set(&pbap_pce->_transport_state, + BT_PBAP_TRANSPORT_STATE_DISCONNECTED); + atomic_set(&pbap_pce->_state, BT_PBAP_STATE_DISCONNECTED); + pbap_pce_clear_pending_request(pbap_pce); + + if (pbap_pce->cb != NULL && pbap_pce->cb->rfcomm_disconnected != NULL) + { + pbap_pce->cb->rfcomm_disconnected(pbap_pce); + } +} + +static struct bt_goep_transport_ops pbap_rfcomm_transport_ops = +{ + .connected = pbap_pce_rfcomm_transport_connected, + .disconnected = pbap_pce_rfcomm_transport_disconnected, +}; + +static void pbap_pce_l2cap_transport_connected(struct bt_conn *conn, + struct bt_goep *goep) +{ + struct bt_pbap_pce *pbap_pce = CONTAINER_OF(goep, struct bt_pbap_pce, + _goep); + + atomic_set(&pbap_pce->_transport_state, BT_PBAP_TRANSPORT_STATE_CONNECTED); + + if (pbap_pce->cb != NULL && pbap_pce->cb->l2cap_connected != NULL) + { + pbap_pce->cb->l2cap_connected(conn, pbap_pce); + } +} + +static void pbap_pce_l2cap_transport_disconnected(struct bt_goep *goep) +{ + struct bt_pbap_pce *pbap_pce = CONTAINER_OF(goep, struct bt_pbap_pce, + _goep); + + atomic_set(&pbap_pce->_transport_state, + BT_PBAP_TRANSPORT_STATE_DISCONNECTED); + atomic_set(&pbap_pce->_state, BT_PBAP_STATE_DISCONNECTED); + pbap_pce_clear_pending_request(pbap_pce); + + if (pbap_pce->cb != NULL && pbap_pce->cb->l2cap_disconnected != NULL) + { + pbap_pce->cb->l2cap_disconnected(pbap_pce); + } +} + +static struct bt_goep_transport_ops pbap_l2cap_transport_ops = +{ + .connected = pbap_pce_l2cap_transport_connected, + .disconnected = pbap_pce_l2cap_transport_disconnected, +}; + +static int bt_pbap_transport_connect( + struct bt_conn *conn, struct bt_pbap_pce *pbap_pce, + struct bt_pbap_pce_cb *cb, uint8_t channel, uint16_t psm) +{ + int err; + + if (conn == NULL || pbap_pce == NULL || (channel == 0 && psm == 0) || + cb == NULL) + { + return -EINVAL; + } + + if (atomic_read(&pbap_pce->_transport_state) != + BT_PBAP_TRANSPORT_STATE_DISCONNECTED) + { + return -EINPROGRESS; + } + + pbap_pce->cb = cb; + + if (channel != 0 && psm == 0) + { + pbap_pce->_goep.transport_ops = &pbap_rfcomm_transport_ops; + BT_GOEP_INIT_V1(&pbap_pce->_goep, &pbap_pce->_goep_transport.v1); + err = bt_goep_transport_rfcomm_connect(conn, + &pbap_pce->_goep, + channel); + } + else + { + pbap_pce->_goep.transport_ops = &pbap_l2cap_transport_ops; + BT_GOEP_INIT_V2(&pbap_pce->_goep, &pbap_pce->_goep_transport.v2); + err = bt_goep_transport_l2cap_connect(conn, &pbap_pce->_goep, psm); + } + + if (err == 0) + { + atomic_set(&pbap_pce->_transport_state, + BT_PBAP_TRANSPORT_STATE_CONNECTING); + } + + return err; +} + +static int bt_pbap_transport_disconnect(struct bt_pbap_pce *pbap_pce, + bool is_rfcomm) +{ + int err; + + if (pbap_pce == NULL) + { + return -EINVAL; + } + + if (atomic_read( + &pbap_pce->_transport_state) != BT_PBAP_TRANSPORT_STATE_CONNECTED) + { + return -EINPROGRESS; + } + + if (is_rfcomm) + { + err = bt_goep_transport_rfcomm_disconnect(&pbap_pce->_goep); + } + else + { + err = bt_goep_transport_l2cap_disconnect(&pbap_pce->_goep); + } + + if (err == 0) + { + atomic_set(&pbap_pce->_transport_state, + BT_PBAP_TRANSPORT_STATE_DISCONNECTING); + } + + return err; +} + +int bt_pbap_pce_rfcomm_connect( + struct bt_conn *conn, struct bt_pbap_pce *pbap_pce, + struct bt_pbap_pce_cb *cb, uint8_t channel) +{ + return bt_pbap_transport_connect(conn, pbap_pce, cb, channel, 0); +} + +int bt_pbap_pce_rfcomm_disconnect(struct bt_pbap_pce *pbap_pce) +{ + return bt_pbap_transport_disconnect(pbap_pce, true); +} + +int bt_pbap_pce_l2cap_connect( + struct bt_conn *conn, struct bt_pbap_pce *pbap_pce, + struct bt_pbap_pce_cb *cb, uint16_t psm) +{ + return bt_pbap_transport_connect(conn, pbap_pce, cb, 0, psm); +} + +int bt_pbap_pce_l2cap_disconnect(struct bt_pbap_pce *pbap_pce) +{ + return bt_pbap_transport_disconnect(pbap_pce, false); +} + +static void pce_connect( + struct bt_obex_client *client, uint8_t rsp_code, uint8_t version, + uint16_t mopl, struct net_buf *buf) +{ + struct bt_pbap_pce *pbap_pce; + + pbap_pce = CONTAINER_OF(client, struct bt_pbap_pce, _client); + + if (rsp_code == BT_PBAP_RSP_CODE_OK) + { + atomic_set(&pbap_pce->_state, BT_PBAP_STATE_CONNECTED); + bt_obex_get_header_conn_id(buf, &pbap_pce->_conn_id); + } + else if (rsp_code != BT_PBAP_RSP_CODE_UNAUTH) + { + atomic_set(&pbap_pce->_state, BT_PBAP_STATE_DISCONNECTED); + } + else + { + /* nothing to do */ + } + + if (pbap_pce->cb != NULL && pbap_pce->cb->connect != NULL) + { + pbap_pce->cb->connect(pbap_pce, rsp_code, version, mopl, buf); + } +} + +static void pce_disconnect(struct bt_obex_client *client, + uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_pbap_pce *pbap_pce = CONTAINER_OF(client, struct bt_pbap_pce, + _client); + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + atomic_set(&pbap_pce->_state, BT_PBAP_STATE_DISCONNECTED); + pbap_pce_clear_pending_request(pbap_pce); + } + else + { + atomic_set(&pbap_pce->_state, BT_PBAP_STATE_CONNECTED); + } + + if (pbap_pce->cb != NULL && pbap_pce->cb->disconnect != NULL) + { + pbap_pce->cb->disconnect(pbap_pce, rsp_code, buf); + } +} + +static void pce_get(struct bt_obex_client *client, + uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_pbap_pce *pbap_pce; + bt_pbap_pce_cb_t rsp_cb; + + pbap_pce = CONTAINER_OF(client, struct bt_pbap_pce, _client); + rsp_cb = pbap_pce->_rsp_cb; + + if (rsp_code != BT_PBAP_RSP_CODE_CONTINUE) + { + pbap_pce_clear_pending_request(pbap_pce); + } + + if (rsp_cb == NULL) + { + LOG_WRN("No response callback available"); + } + else + { + rsp_cb(pbap_pce, rsp_code, buf); + } +} + +static void pce_set_phone_book(struct bt_obex_client *client, + uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_pbap_pce *pbap_pce; + + pbap_pce = CONTAINER_OF(client, struct bt_pbap_pce, _client); + + if (pbap_pce->cb != NULL && pbap_pce->cb->set_phone_book != NULL) + { + pbap_pce->cb->set_phone_book(pbap_pce, rsp_code, buf); + } +} + +static void pce_abort(struct bt_obex_client *client, + uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_pbap_pce *pbap_pce; + + pbap_pce = CONTAINER_OF(client, struct bt_pbap_pce, _client); + + if (pbap_pce->cb != NULL && pbap_pce->cb->abort != NULL) + { + pbap_pce->cb->abort(pbap_pce, rsp_code, buf); + } + + if (rsp_code == BT_PBAP_RSP_CODE_SUCCESS) + { + pbap_pce_clear_pending_request(pbap_pce); + } +} + +struct bt_obex_client_ops pbap_pce_ops = +{ + .connect = pce_connect, + .disconnect = pce_disconnect, + .get = pce_get, + .setpath = pce_set_phone_book, + .abort = pce_abort, +}; + +static int pbap_check_header_target(struct net_buf *buf) +{ + uint16_t len; + const uint8_t *target; + int err; + union bt_obex_uuid uuid; + + err = bt_obex_get_header_target(buf, &len, &target); + if (err != 0) + { + LOG_ERR("Failed to get target %d", err); + return err; + } + + err = bt_obex_make_uuid(&uuid, target, len); + if (err != 0) + { + LOG_ERR("Failed to make UUID from target %d", err); + return err; + } + + return bt_uuid_cmp(&(uuid.uuid), &pbap_uuid->uuid); +} + +int bt_pbap_pce_connect(struct bt_pbap_pce *pbap_pce, + uint16_t mopl, struct net_buf *buf) +{ + int err; + bool allocated = false; + uint8_t uuid128[BT_UUID_SIZE_128]; + + if (atomic_read( + &pbap_pce->_transport_state) != BT_PBAP_TRANSPORT_STATE_CONNECTED) + { + LOG_ERR("Transport connection is not established"); + return -EINVAL; + } + + if (atomic_read(&pbap_pce->_state) != BT_PBAP_STATE_DISCONNECTED && + atomic_read(&pbap_pce->_state) != BT_PBAP_STATE_CONNECTING) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&pbap_pce->_state)); + return -EINVAL; + } + + if (buf == NULL) + { + buf = bt_goep_create_pdu(&pbap_pce->_goep, NULL); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } + + allocated = true; + } + + if (bt_obex_has_header(buf, BT_OBEX_HEADER_ID_TARGET)) + { + err = pbap_check_header_target(buf); + if (err != 0) + { + goto failed; + } + } + else + { + sys_memcpy_swap(uuid128, pbap_uuid->val, BT_UUID_SIZE_128); + err = bt_obex_add_header_target(buf, BT_UUID_SIZE_128, uuid128); + if (err != 0) + { + LOG_ERR("Failed to add target header"); + goto failed; + } + } + + pbap_pce->_client.ops = &pbap_pce_ops; + pbap_pce->_client.obex = &pbap_pce->_goep.obex; + + err = bt_obex_connect(&pbap_pce->_client, mopl, buf); + if (err != 0) + { + LOG_ERR("Failed to send conn req"); + goto failed; + } + + atomic_set(&pbap_pce->_state, BT_PBAP_STATE_CONNECTING); + return 0; + +failed: + if (allocated) + { + net_buf_unref(buf); + } + + return err; +} + +int bt_pbap_pce_disconnect(struct bt_pbap_pce *pbap_pce, struct net_buf *buf) +{ + int err; + bool allocated = false; + + if (pbap_pce == NULL) + { + return -EINVAL; + } + + if (atomic_read(&pbap_pce->_state) != BT_PBAP_STATE_CONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&pbap_pce->_state)); + return -EINVAL; + } + + if (buf == NULL) + { + buf = bt_goep_create_pdu(&pbap_pce->_goep, NULL); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } + + allocated = true; + } + + if (bt_obex_has_header(buf, BT_OBEX_HEADER_ID_CONN_ID)) + { + err = pbap_check_header_conn_id(pbap_pce->_conn_id, buf); + if (err != 0) + { + LOG_ERR("Failed to check conn id header"); + goto failed; + } + } + else + { + err = bt_obex_add_header_conn_id(buf, pbap_pce->_conn_id); + if (err != 0) + { + LOG_ERR("Failed to add conn id header"); + goto failed; + } + } + + err = bt_obex_disconnect(&pbap_pce->_client, buf); + if (err != 0) + { + LOG_ERR("Failed to send conn rsp %d", err); + goto failed; + } + + atomic_set(&pbap_pce->_state, BT_PBAP_STATE_DISCONNECTING); + return 0; +failed: + if (allocated) + { + net_buf_unref(buf); + } + + return err; +} + +static int pbap_pce_get_req_cb( + struct bt_pbap_pce *pbap_pce, const char *type, struct net_buf *buf, + bt_pbap_pce_cb_t *cb, const char **req_type) +{ + int err; + uint16_t len; + const uint8_t *type_data; + + err = bt_obex_get_header_type(buf, &len, &type_data); + if (err != 0) + { + LOG_WRN("Failed to get type header %d", err); + return -EINVAL; + } + + if (len < strlen(type_data)) + { + LOG_WRN("Invalid type string len %u < %u", len, strlen(type_data)); + return -EINVAL; + } + + *cb = NULL; + ARRAY_FOR_EACH(pbap_pull_functions, i) + { + err = strcmp(pbap_pull_functions[i].type, type); + if (err != 0) + { + continue; + } + + err = strcmp(pbap_pull_functions[i].type, type_data); + if (err != 0) + { + continue; + } + + if (!has_required_hdrs(buf, &pbap_pull_functions[i].req_hdr)) + { + continue; + } + + /* Application parameter tag id is not checked. */ + + if (pbap_pull_functions[i].get_client_cb == NULL) + { + continue; + } + + *cb = pbap_pull_functions[i].get_client_cb(pbap_pce); + if (*cb == NULL) + { + continue; + } + + if (req_type != NULL) + { + *req_type = pbap_pull_functions[i].type; + } + break; + } + + if (*cb == NULL) + { + LOG_WRN("Unsupported request"); + return -EINVAL; + } + + return 0; +} + +static int bt_pbap_pce_get(struct bt_pbap_pce *pbap_pce, + struct net_buf *buf, const char *type) +{ + int err = 0; + bt_pbap_pce_cb_t cb; + bt_pbap_pce_cb_t old_cb; + const char *req_type; + const char *old_req_type; + + if (pbap_pce == NULL || buf == NULL) + { + return -EINVAL; + } + + old_cb = pbap_pce->_rsp_cb; + old_req_type = pbap_pce->_req_type; + + if (pbap_pce->_rsp_cb == NULL || bt_obex_has_header(buf, + BT_OBEX_HEADER_ID_TYPE)) + { + err = pbap_pce_get_req_cb(pbap_pce, type, buf, &cb, &req_type); + if (err != 0) + { + LOG_ERR("Invalid request %d", err); + return err; + } + + if (pbap_pce->_rsp_cb != NULL && cb != pbap_pce->_rsp_cb) + { + LOG_ERR("Previous operation is not completed"); + return -EINVAL; + } + + if (pbap_pce->_goep.v2 != NULL && pbap_pce->_rsp_cb == NULL) + { + err = bt_pbap_check_srm(buf); + if (err != 0) + { + LOG_ERR("SRM check failed %d", err); + return err; + } + } + + pbap_pce->_rsp_cb = cb; + pbap_pce->_req_type = req_type; + } + + if (pbap_pce->_req_type != NULL && strcmp(pbap_pce->_req_type, type) != 0) + { + LOG_ERR("Invalid request type %s != %s", pbap_pce->_req_type, type); + err = -EINVAL; + goto failed; + } + + if (bt_obex_has_header(buf, BT_OBEX_HEADER_ID_CONN_ID)) + { + err = pbap_check_header_conn_id(pbap_pce->_conn_id, buf); + if (err != 0) + { + goto failed; + } + } + + err = bt_obex_get(&pbap_pce->_client, true, buf); + +failed: + if (err != 0) + { + pbap_pce->_rsp_cb = old_cb; + pbap_pce->_req_type = old_req_type; + LOG_ERR("Failed to send get req %d", err); + } + + return err; +} + +int bt_pbap_pce_pull_phone_book(struct bt_pbap_pce *pbap_pce, + struct net_buf *buf) +{ + return bt_pbap_pce_get(pbap_pce, buf, BT_PBAP_PULL_PHONE_BOOK_TYPE); +} + +int bt_pbap_pce_pull_vcard_listing(struct bt_pbap_pce *pbap_pce, + struct net_buf *buf) +{ + return bt_pbap_pce_get(pbap_pce, buf, BT_PBAP_PULL_VCARD_LISTING_TYPE); +} + +int bt_pbap_pce_pull_vcard_entry(struct bt_pbap_pce *pbap_pce, + struct net_buf *buf) +{ + return bt_pbap_pce_get(pbap_pce, buf, BT_PBAP_PULL_VCARD_ENTRY_TYPE); +} + +int bt_pbap_pce_set_phone_book(struct bt_pbap_pce *pbap_pce, + uint8_t flags, struct net_buf *buf) +{ + int err; + + if (atomic_read(&pbap_pce->_state) != BT_PBAP_STATE_CONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&pbap_pce->_state)); + return -EINVAL; + } + + if (pbap_pce->_rsp_cb != NULL) + { + LOG_ERR("other operation is ongoing"); + return -EINVAL; + } + + if ((flags != BT_PBAP_SET_PHONE_BOOK_FLAGS_UP) && + (flags != BT_PBAP_SET_PHONE_BOOK_FLAGS_DOWN_OR_ROOT)) + { + LOG_ERR("Invalid flags %u", flags); + return -EINVAL; + } + + err = pbap_check_header_conn_id(pbap_pce->_conn_id, buf); + if (err != 0) + { + LOG_ERR("Failed to check connection ID %d", err); + return err; + } + + err = bt_obex_setpath(&pbap_pce->_client, flags, buf); + if (err != 0) + { + LOG_WRN("Fail to send set phone book request %d", err); + } + + return err; +} + +int bt_pbap_pce_abort(struct bt_pbap_pce *pbap_pce, struct net_buf *buf) +{ + int err; + bool allocated = false; + + if (pbap_pce == NULL) + { + return -EINVAL; + } + + if (atomic_read(&pbap_pce->_state) != BT_PBAP_STATE_CONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&pbap_pce->_state)); + return -EINVAL; + } + + if (pbap_pce->_rsp_cb == NULL) + { + LOG_ERR("No operation is ongoing"); + return -EINVAL; + } + + if (buf == NULL) + { + buf = bt_goep_create_pdu(&pbap_pce->_goep, NULL); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } + + allocated = true; + } + + if (bt_obex_has_header(buf, BT_OBEX_HEADER_ID_CONN_ID)) + { + err = pbap_check_header_conn_id(pbap_pce->_conn_id, buf); + if (err != 0) + { + goto failed; + } + } + else + { + err = bt_obex_add_header_conn_id(buf, pbap_pce->_conn_id); + if (err != 0) + { + LOG_ERR("Failed to add header conn id %d", err); + goto failed; + } + } + + err = bt_obex_abort(&pbap_pce->_client, buf); + if (err != 0) + { + LOG_ERR("Failed to send abort request %d", err); + goto failed; + } + + return 0; + +failed: + if (allocated) + { + net_buf_unref(buf); + } + + return err; +} +#endif /* CONFIG_BT_PBAP_PCE */ + +static struct net_buf *bt_pbap_create_pdu(struct bt_goep *goep, + struct net_buf_pool *pool) +{ + if (pool == NULL) + { + pool = &bt_pbap_pool; + } + + return bt_goep_create_pdu(goep, pool); +} + +struct net_buf *bt_pbap_pce_create_pdu(struct bt_pbap_pce *pbap_pce, + struct net_buf_pool *pool) +{ + return bt_pbap_create_pdu(&pbap_pce->_goep, pool); +} + +struct net_buf *bt_pbap_pse_create_pdu(struct bt_pbap_pse *pbap_pse, + struct net_buf_pool *pool) +{ + return bt_pbap_create_pdu(&pbap_pse->_goep, pool); +} + +#if defined(CONFIG_BT_PBAP_PSE) +static uint8_t rfcomm_channel; +static uint16_t l2cap_psm; +static uint8_t supported_repositories = BT_PBAP_PSE_SUPPORTED_REPOSITORIES; +static uint32_t supported_features = BT_PBAP_PSE_SUPPORTED_FEATURES; + +static struct bt_sdp_attribute pbap_pse_attrs[] = +{ + BT_SDP_NEW_SERVICE, + BT_SDP_LIST(BT_SDP_ATTR_SVCLASS_ID_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PBAP_PSE_SVCLASS) + }, + + ) + ), + + BT_SDP_LIST(BT_SDP_ATTR_PROTO_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 17), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) + }, + + ) + }, + + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 5), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_RFCOMM) + }, + + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT8), + &rfcomm_channel + }, + + ) + }, + + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_OBEX) + }, + + ) + }, + + ) + ), + + BT_SDP_SERVICE_NAME("Phonebook Access PSE"), + BT_SDP_LIST(BT_SDP_ATTR_PROFILE_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 8), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PBAP_SVCLASS) + }, + + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(0x0102) + }, + + ) + }, + + ) + ), + + { + BT_SDP_ATTR_GOEP_L2CAP_PSM, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + &l2cap_psm + } + }, + + { + BT_SDP_ATTR_SUPPORTED_REPOSITORIES, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT8), + &supported_repositories + } + }, + + { + BT_SDP_ATTR_PBAP_SUPPORTED_FEATURES, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT32), + &supported_features + } + }, +}; + +static struct bt_sdp_record pbap_pse_rec = BT_SDP_RECORD(pbap_pse_attrs); + +static void pbap_pse_clear_pending_request(struct bt_pbap_pse *pbap_pse) +{ + pbap_pse->_req_cb = NULL; + pbap_pse->_optype = NULL; + atomic_set(&pbap_pse->_flags, 0); +} + +static void pse_connect( + struct bt_obex_server *server, uint8_t version, uint16_t mopl, + struct net_buf *buf) +{ + struct bt_pbap_pse *pbap_pse = CONTAINER_OF(server, struct bt_pbap_pse, + _server); + + atomic_set(&pbap_pse->_state, BT_PBAP_STATE_CONNECTING); + + if (pbap_pse->cb != NULL && pbap_pse->cb->connect != NULL) + { + pbap_pse->cb->connect(pbap_pse, version, mopl, buf); + } +} + +static void pse_disconnect(struct bt_obex_server *server, + struct net_buf *buf) +{ + struct bt_pbap_pse *pbap_pse = CONTAINER_OF(server, struct bt_pbap_pse, + _server); + + atomic_set(&pbap_pse->_state, BT_PBAP_STATE_DISCONNECTING); + + if (pbap_pse->cb != NULL && pbap_pse->cb->disconnect != NULL) + { + pbap_pse->cb->disconnect(pbap_pse, buf); + } +} + +static enum bt_obex_rsp_code pbap_pse_get_req_cb( + struct bt_pbap_pse *pbap_pse, struct net_buf *buf, bt_pbap_pse_cb_t *cb) +{ + const uint8_t *type; + uint16_t len; + int err; + + err = bt_obex_get_header_type(buf, &len, &type); + if (err != 0) + { + LOG_WRN("Failed to get type header %d", err); + return BT_OBEX_RSP_CODE_BAD_REQ; + } + + if (len <= strlen(type)) + { + LOG_WRN("Invalid type string len %u <= %u", len, strlen(type)); + return BT_OBEX_RSP_CODE_BAD_REQ; + } + + *cb = NULL; + ARRAY_FOR_EACH(pbap_pull_functions, i) + { + if (pbap_pse->_optype != NULL + && pbap_pull_functions[i].type != pbap_pse->_optype) + { + continue; + } + + if (strcmp(pbap_pull_functions[i].type, type) != 0) + { + continue; + } + + if (!has_required_hdrs(buf, &pbap_pull_functions[i].req_hdr)) + { + continue; + } + + /* Application parameter tag id is not checked. */ + + if (pbap_pull_functions[i].get_server_cb == NULL) + { + continue; + } + + *cb = pbap_pull_functions[i].get_server_cb(pbap_pse); + if (*cb == NULL) + { + continue; + } + + pbap_pse->_optype = pbap_pull_functions[i].type; + break; + } + + if (*cb == NULL) + { + LOG_WRN("Unsupported request"); + return BT_OBEX_RSP_CODE_NOT_IMPL; + } + + return BT_OBEX_RSP_CODE_SUCCESS; +} + +static void pse_get(struct bt_obex_server *server, + bool final, struct net_buf *buf) +{ + struct bt_pbap_pse *pbap_pse = CONTAINER_OF(server, struct bt_pbap_pse, + _server); + int err = 0; + enum bt_obex_rsp_code rsp_code; + + if (pbap_pse->_optype == NULL || bt_obex_has_header(buf, + BT_OBEX_HEADER_ID_TYPE)) + { + rsp_code = pbap_pse_get_req_cb(pbap_pse, buf, &pbap_pse->_req_cb); + if (rsp_code != BT_OBEX_RSP_CODE_SUCCESS) + { + LOG_WRN("Failed to parse req %u", (uint8_t)rsp_code); + goto failed; + } + } + + if (pbap_pse->_optype == NULL || pbap_pse->_req_cb == NULL) + { + rsp_code = BT_OBEX_RSP_CODE_NOT_IMPL; + LOG_WRN("Invalid request"); + goto failed; + } + + pbap_pse->_req_cb(pbap_pse, buf); + + return; + +failed: + pbap_pse->_optype = NULL; + pbap_pse->_req_cb = NULL; + err = bt_obex_get_rsp(server, rsp_code, NULL); + if (err != 0) + { + LOG_ERR("Failed to send get rsp %d", err); + } +} + +static void pse_set_phone_book(struct bt_obex_server *server, + uint8_t flags, struct net_buf *buf) +{ + struct bt_pbap_pse *pbap_pse = CONTAINER_OF(server, struct bt_pbap_pse, + _server); + + if (pbap_pse->cb != NULL && pbap_pse->cb->set_phone_book != NULL) + { + pbap_pse->cb->set_phone_book(pbap_pse, flags, buf); + } +} + +void pse_abort(struct bt_obex_server *server, struct net_buf *buf) +{ + struct bt_pbap_pse *pbap_pse = CONTAINER_OF(server, struct bt_pbap_pse, + _server); + + if (pbap_pse->cb != NULL && pbap_pse->cb->abort != NULL) + { + pbap_pse->cb->abort(pbap_pse, buf); + } +} + +struct bt_obex_server_ops pbap_pse_ops = +{ + .connect = pse_connect, + .disconnect = pse_disconnect, + .get = pse_get, + .abort = pse_abort, + .setpath = pse_set_phone_book, +}; + +static void pse_rfcomm_transport_connected(struct bt_conn *conn, + struct bt_goep *goep) +{ + struct bt_pbap_pse *pbap_pse = CONTAINER_OF(goep, struct bt_pbap_pse, + _goep); + + atomic_set(&pbap_pse->_transport_state, BT_PBAP_TRANSPORT_STATE_CONNECTED); + + if (pbap_pse->cb != NULL && pbap_pse->cb->rfcomm_connected != NULL) + { + pbap_pse->cb->rfcomm_connected(conn, pbap_pse); + } +} + +static void pse_rfcomm_transport_disconnected(struct bt_goep *goep) +{ + struct bt_pbap_pse *pbap_pse = CONTAINER_OF(goep, struct bt_pbap_pse, + _goep); + + atomic_set(&pbap_pse->_transport_state, + BT_PBAP_TRANSPORT_STATE_DISCONNECTED); + atomic_set(&pbap_pse->_state, BT_PBAP_STATE_DISCONNECTED); + pbap_pse_clear_pending_request(pbap_pse); + + if (pbap_pse->cb != NULL && pbap_pse->cb->rfcomm_disconnected != NULL) + { + pbap_pse->cb->rfcomm_disconnected(pbap_pse); + } +} + +struct bt_goep_transport_ops pse_rfcomm_transport_ops = +{ + .connected = pse_rfcomm_transport_connected, + .disconnected = pse_rfcomm_transport_disconnected, +}; + +static void pse_l2cap_transport_connected(struct bt_conn *conn, + struct bt_goep *goep) +{ + struct bt_pbap_pse *pbap_pse = CONTAINER_OF(goep, struct bt_pbap_pse, + _goep); + + atomic_set(&pbap_pse->_transport_state, BT_PBAP_TRANSPORT_STATE_CONNECTED); + + if (pbap_pse->cb != NULL && pbap_pse->cb->l2cap_connected != NULL) + { + pbap_pse->cb->l2cap_connected(conn, pbap_pse); + } +} + +static void pse_l2cap_transport_disconnected(struct bt_goep *goep) +{ + struct bt_pbap_pse *pbap_pse = CONTAINER_OF(goep, struct bt_pbap_pse, + _goep); + + atomic_set(&pbap_pse->_transport_state, + BT_PBAP_TRANSPORT_STATE_DISCONNECTED); + atomic_set(&pbap_pse->_state, BT_PBAP_STATE_DISCONNECTED); + pbap_pse_clear_pending_request(pbap_pse); + + if (pbap_pse->cb != NULL && pbap_pse->cb->l2cap_disconnected != NULL) + { + pbap_pse->cb->l2cap_disconnected(pbap_pse); + } +} + +struct bt_goep_transport_ops pse_l2cap_transport_ops = +{ + .connected = pse_l2cap_transport_connected, + .disconnected = pse_l2cap_transport_disconnected, +}; + +static int pbap_pse_rfcomm_accept(struct bt_conn *conn, + struct bt_goep_transport_rfcomm_server *server, + struct bt_goep **goep) +{ + struct bt_pbap_pse_rfcomm *pbap_pse_rfcomm; + struct bt_pbap_pse *pbap_pse = NULL; + int err; + + pbap_pse_rfcomm = CONTAINER_OF(server, struct bt_pbap_pse_rfcomm, server); + if (pbap_pse_rfcomm->accept == NULL) + { + return -ENOTSUP; + } + + err = pbap_pse_rfcomm->accept(conn, pbap_pse_rfcomm, &pbap_pse); + if (err != 0) + { + return err; + } + + DEBUGASSERT(pbap_pse != NULL); + + pbap_pse->_goep.transport_ops = &pse_rfcomm_transport_ops; + BT_GOEP_INIT_V1(&pbap_pse->_goep, &pbap_pse->_goep_transport.v1); + *goep = &pbap_pse->_goep; + + atomic_set(&pbap_pse->_transport_state, + BT_PBAP_TRANSPORT_STATE_CONNECTING); + + return 0; +} + +static int pbap_pse_l2cap_accept(struct bt_conn *conn, + struct bt_goep_transport_l2cap_server *server, + struct bt_goep **goep) +{ + struct bt_pbap_pse_l2cap *pbap_pse_l2cap; + struct bt_pbap_pse *pbap_pse = NULL; + int err; + + pbap_pse_l2cap = CONTAINER_OF(server, struct bt_pbap_pse_l2cap, server); + if (pbap_pse_l2cap->accept == NULL) + { + return -ENOTSUP; + } + + err = pbap_pse_l2cap->accept(conn, pbap_pse_l2cap, &pbap_pse); + if (err != 0) + { + return err; + } + + DEBUGASSERT(pbap_pse != NULL); + + pbap_pse->_goep.transport_ops = &pse_l2cap_transport_ops; + BT_GOEP_INIT_V2(&pbap_pse->_goep, &pbap_pse->_goep_transport.v2); + *goep = &pbap_pse->_goep; + + atomic_set(&pbap_pse->_transport_state, + BT_PBAP_TRANSPORT_STATE_CONNECTING); + + return 0; +} + +int bt_pbap_pse_rfcomm_register(struct bt_pbap_pse_rfcomm *server) +{ + int err; + + if (server == NULL || server->accept == NULL) + { + return -EINVAL; + } + + server->server.accept = pbap_pse_rfcomm_accept; + + err = bt_goep_transport_rfcomm_server_register(&server->server); + if (err != 0) + { + LOG_ERR("Fail to register RFCOMM server (error %d)", err); + return err; + } + + rfcomm_channel = server->server.rfcomm.channel; + return 0; +} + +int bt_pbap_pse_l2cap_register(struct bt_pbap_pse_l2cap *server) +{ + int err; + + if (server == NULL || server->accept == NULL) + { + return -EINVAL; + } + + server->server.accept = pbap_pse_l2cap_accept; + + err = bt_goep_transport_l2cap_server_register(&server->server); + if (err != 0) + { + LOG_ERR("Fail to register l2cap server (error %d)", err); + return err; + } + + l2cap_psm = server->server.l2cap.psm; + return 0; +} + +static uint32_t pbap_pse_get_connect_id(void) +{ + static uint32_t connect_id; + + connect_id++; + + return connect_id; +} + +static int pbap_check_header_who(struct net_buf *buf) +{ + uint16_t len; + const uint8_t *who; + int err; + union bt_obex_uuid uuid; + + err = bt_obex_get_header_who(buf, &len, &who); + if (err != 0) + { + LOG_ERR("Failed to get target %d", err); + return err; + } + + err = bt_obex_make_uuid(&uuid, who, len); + if (err != 0) + { + LOG_ERR("Failed to make UUID from who %d", err); + return err; + } + + return bt_uuid_cmp(&(uuid.uuid), &pbap_uuid->uuid); +} + +int bt_pbap_pse_register(struct bt_pbap_pse *pbap_pse, + struct bt_pbap_pse_cb *cb) +{ + int err; + + if (pbap_pse == NULL || cb == NULL) + { + return -EINVAL; + } + + err = bt_sdp_register_service(&pbap_pse_rec); + if (err != 0) + { + LOG_ERR("Failed to register SDP record"); + return err; + } + + pbap_pse->_server.ops = &pbap_pse_ops; + pbap_pse->_server.obex = &pbap_pse->_goep.obex; + pbap_pse->cb = cb; + pbap_pse->_conn_id = pbap_pse_get_connect_id(); + + err = bt_obex_server_register(&pbap_pse->_server, pbap_uuid); + if (err != 0) + { + LOG_ERR("Fail to register obex server %d", err); + return err; + } + + return 0; +} + +int bt_pbap_pse_connect_rsp( + struct bt_pbap_pse *pbap_pse, uint16_t mopl, uint8_t rsp_code, + struct net_buf *buf) +{ + int err; + bool allocated = false; + uint8_t uuid128[BT_UUID_SIZE_128]; + + if (pbap_pse == NULL) + { + return -EINVAL; + } + + if (atomic_read(&pbap_pse->_state) != BT_PBAP_STATE_CONNECTING) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&pbap_pse->_state)); + return -EINVAL; + } + + if (buf == NULL) + { + buf = bt_goep_create_pdu(&pbap_pse->_goep, NULL); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } + + allocated = true; + } + + if (bt_obex_has_header(buf, BT_OBEX_HEADER_ID_WHO)) + { + err = pbap_check_header_who(buf); + if (err != 0) + { + LOG_ERR("Invalid WHO header"); + goto failed; + } + } + else + { + sys_memcpy_swap(uuid128, pbap_uuid->val, BT_UUID_SIZE_128); + err = bt_obex_add_header_who(buf, BT_UUID_SIZE_128, uuid128); + if (err != 0) + { + LOG_ERR("Failed to add WHO header"); + goto failed; + } + } + + if (bt_obex_has_header(buf, BT_OBEX_HEADER_ID_CONN_ID)) + { + err = pbap_check_header_conn_id(pbap_pse->_conn_id, buf); + if (err != 0) + { + LOG_ERR("Invalid connection ID"); + goto failed; + } + } + else + { + err = bt_obex_add_header_conn_id(buf, pbap_pse->_conn_id); + if (err != 0) + { + LOG_ERR("Failed to add connection ID header"); + goto failed; + } + } + + err = bt_obex_connect_rsp(&pbap_pse->_server, rsp_code, mopl, buf); + if (err != 0) + { + LOG_ERR("Failed to send CONNECT response (error %d)", err); + goto failed; + } + + atomic_set(&pbap_pse->_state, BT_PBAP_STATE_CONNECTED); + atomic_set(&pbap_pse->_flags, 0); + return 0; +failed: + if (allocated) + { + net_buf_unref(buf); + } + + return err; +} + +static int pbap_pse_get_rsp( + struct bt_pbap_pse *pbap_pse, uint8_t rsp_code, struct net_buf *buf, + const char *type) +{ + int err; + bool clear_bit = false; + + if (atomic_read(&pbap_pse->_state) != BT_PBAP_STATE_CONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&pbap_pse->_state)); + return -EINVAL; + } + + if (pbap_pse->_optype != NULL && strcmp(pbap_pse->_optype, type) != 0) + { + LOG_ERR("Invalid operation type %s != %s", pbap_pse->_optype, type); + return -EINVAL; + } + + if (!atomic_test_and_set_bit(&pbap_pse->_flags, BT_PBAP_FLAG_RSP_ONGOING)) + { + clear_bit = true; + if (pbap_pse->_goep.v2 != NULL && rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + err = bt_pbap_check_srm(buf); + if (err != 0) + { + LOG_ERR("SRM check failed %d", err); + goto failed; + } + } + } + + err = bt_obex_get_rsp(&pbap_pse->_server, rsp_code, buf); + if (err != 0) + { + LOG_ERR("Failed to send get rsp %d", err); + goto failed; + } + + if (rsp_code != BT_OBEX_RSP_CODE_CONTINUE) + { + pbap_pse_clear_pending_request(pbap_pse); + } + + return 0; + +failed: + atomic_set_bit_to(&pbap_pse->_flags, BT_PBAP_FLAG_RSP_ONGOING, !clear_bit); + return err; +} + +int bt_pbap_pse_pull_phone_book_rsp( + struct bt_pbap_pse *pbap_pse, uint8_t rsp_code, struct net_buf *buf) +{ + return pbap_pse_get_rsp(pbap_pse, rsp_code, buf, + BT_PBAP_PULL_PHONE_BOOK_TYPE); +} + +int bt_pbap_pse_pull_vcard_listing_rsp( + struct bt_pbap_pse *pbap_pse, uint8_t rsp_code, struct net_buf *buf) +{ + return pbap_pse_get_rsp(pbap_pse, rsp_code, buf, + BT_PBAP_PULL_VCARD_LISTING_TYPE); +} + +int bt_pbap_pse_pull_vcard_entry_rsp( + struct bt_pbap_pse *pbap_pse, uint8_t rsp_code, struct net_buf *buf) +{ + return pbap_pse_get_rsp(pbap_pse, rsp_code, buf, + BT_PBAP_PULL_VCARD_ENTRY_TYPE); +} + +int bt_pbap_pse_set_phone_book_rsp( + struct bt_pbap_pse *pbap_pse, uint8_t rsp_code, struct net_buf *buf) +{ + int err; + bool allocated = false; + + if (atomic_read(&pbap_pse->_state) != BT_PBAP_STATE_CONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&pbap_pse->_state)); + return -EINVAL; + } + + if (buf == NULL) + { + buf = bt_goep_create_pdu(&pbap_pse->_goep, NULL); + if (buf == NULL) + { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } + + allocated = true; + } + + err = bt_obex_setpath_rsp(&pbap_pse->_server, rsp_code, buf); + if (err != 0) + { + LOG_ERR("Failed to send SETPATH response (error %d)", err); + if (allocated) + { + net_buf_unref(buf); + } + } + + return 0; +} + +int bt_pbap_pse_disconnect_rsp(struct bt_pbap_pse *pbap_pse, + uint8_t rsp_code, struct net_buf *buf) +{ + int err; + + if (pbap_pse == NULL) + { + return -EINVAL; + } + + if (atomic_read(&pbap_pse->_state) != BT_PBAP_STATE_DISCONNECTING) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&pbap_pse->_state)); + return -EINVAL; + } + + err = bt_obex_disconnect_rsp(&pbap_pse->_server, rsp_code, buf); + if (err != 0) + { + LOG_ERR("Failed to send conn rsp %d", err); + return err; + } + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + atomic_set(&pbap_pse->_state, BT_PBAP_STATE_DISCONNECTED); + pbap_pse_clear_pending_request(pbap_pse); + } + else + { + atomic_set(&pbap_pse->_state, BT_PBAP_STATE_CONNECTED); + } + + return 0; +} + +int bt_pbap_pse_abort_rsp(struct bt_pbap_pse *pbap_pse, + uint8_t rsp_code, struct net_buf *buf) +{ + int err; + + if (pbap_pse == NULL) + { + return -EINVAL; + } + + if (atomic_read(&pbap_pse->_state) != BT_PBAP_STATE_CONNECTED) + { + LOG_ERR("Invalid state %u", (uint8_t)atomic_read(&pbap_pse->_state)); + return -EINVAL; + } + + err = bt_obex_abort_rsp(&pbap_pse->_server, rsp_code, buf); + if (err != 0) + { + LOG_ERR("Failed to send abort rsp %d", err); + return err; + } + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) + { + pbap_pse_clear_pending_request(pbap_pse); + } + + return 0; +} +#endif /* CONFIG_BT_PBAP_PSE */ diff --git a/libs/zblue/host/classic/pbap_internal.h b/libs/zblue/host/classic/pbap_internal.h new file mode 100644 index 0000000000000..3ef54f320152a --- /dev/null +++ b/libs/zblue/host/classic/pbap_internal.h @@ -0,0 +1,202 @@ +/**************************************************************************** + * libs/zblue/host/classic/pbap_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* pbap_internal.h - Internal for Phone Book Access Protocol handling */ + +/* Copyright 2026 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#define BT_PBAP_FLAG_RSP_ONGOING BIT(0) + +/** @brief PBAP connection state enumeration */ + +enum __packed bt_pbap_state +{ + /** PBAP disconnected */ + + BT_PBAP_STATE_DISCONNECTED = 0, + + /** PBAP PBAP in connecting state */ + + BT_PBAP_STATE_CONNECTING = 1, + + /** PBAP ready for upper layer traffic on it */ + + BT_PBAP_STATE_CONNECTED = 2, + + /** PBAP in disconnecting state */ + + BT_PBAP_STATE_DISCONNECTING = 3, +}; + +/** @brief Transport layer state enumeration */ + +enum __packed bt_pbap_transport_state +{ + /** @brief Transport is disconnected */ + + BT_PBAP_TRANSPORT_STATE_DISCONNECTED = 0, + + /** @brief Transport is connecting */ + + BT_PBAP_TRANSPORT_STATE_CONNECTING = 1, + + /** @brief Transport is connected */ + + BT_PBAP_TRANSPORT_STATE_CONNECTED = 2, + + /** @brief Transport is disconnecting */ + + BT_PBAP_TRANSPORT_STATE_DISCONNECTING = 3, +}; + +/* PBAP PSE Supported features */ +#if defined(CONFIG_BT_PBAP_PSE_DOWNLOAD) +/** @brief Enable download feature for PBAP PSE. */ +#define BT_PBAP_PSE_FEATURE_DOWNLOAD_ENABLE BT_PBAP_SUPPORTED_FEATURE_DOWNLOAD +#else +/** @brief Disable download feature for PBAP PSE. */ +#define BT_PBAP_PSE_FEATURE_DOWNLOAD_ENABLE 0 +#endif /* CONFIG_BT_PBAP_PSE_DOWNLOAD */ + +#if defined(CONFIG_BT_PBAP_PSE_BROWSING) +/** @brief Enable browsing feature for PBAP PSE. */ +#define BT_PBAP_PSE_FEATURE_BROWSING_ENABLE BT_PBAP_SUPPORTED_FEATURE_BROWSING +#else +/** @brief Disable browsing feature for PBAP PSE. */ +#define BT_PBAP_PSE_FEATURE_BROWSING_ENABLE 0 +#endif /* CONFIG_BT_PBAP_PSE_BROWSING */ + +#if defined(CONFIG_BT_PBAP_PSE_DATABASE_IDENTIFIER) +/** @brief Enable database identifier feature for PBAP PSE. */ +#define BT_PBAP_PSE_FEATURE_DATABASE_IDENTIFIER_ENABLE BT_PBAP_SUPPORTED_FEATURE_DATABASE_IDENTIFIER +#else +/** @brief Disable database identifier feature for PBAP PSE. */ +#define BT_PBAP_PSE_FEATURE_DATABASE_IDENTIFIER_ENABLE 0 +#endif /* CONFIG_BT_PBAP_PSE_DATABASE_IDENTIFIER */ + +#if defined(CONFIG_BT_PBAP_PSE_FOLDER_VERSION_COUNTERS) +/** @brief Enable folder version counters feature for PBAP PSE. */ +#define BT_PBAP_PSE_FEATURE_FOLDER_VERSION_COUNTERS_ENABLE \ + BT_PBAP_SUPPORTED_FEATURE_FOLDER_VERSION_COUNTERS +#else +/** @brief Disable folder version counters feature for PBAP PSE. */ +#define BT_PBAP_PSE_FEATURE_FOLDER_VERSION_COUNTERS_ENABLE 0 +#endif /* CONFIG_BT_PBAP_PSE_FOLDER_VERSION_COUNTERS */ + +#if defined(CONFIG_BT_PBAP_PSE_VCARD_SELECTOR) +/** @brief Enable vCard selector feature for PBAP PSE. */ +#define BT_PBAP_PSE_FEATURE_VCARD_SELECTOR_ENABLE BT_PBAP_SUPPORTED_FEATURE_VCARD_SELECTOR +#else +/** @brief Disable vCard selector feature for PBAP PSE. */ +#define BT_PBAP_PSE_FEATURE_VCARD_SELECTOR_ENABLE 0 +#endif /* CONFIG_BT_PBAP_PSE_VCARD_SELECTOR */ + +#if defined(CONFIG_BT_PBAP_PSE_ENHANCED_MISSED_CALLS) +/** @brief Enable enhanced missed calls feature for PBAP PSE. */ +#define BT_PBAP_PSE_FEATURE_ENHANCED_MISSED_CALLS_ENABLE \ + BT_PBAP_SUPPORTED_FEATURE_ENHANCED_MISSED_CALLS +#else +/** @brief Disable enhanced missed calls feature for PBAP PSE. */ +#define BT_PBAP_PSE_FEATURE_ENHANCED_MISSED_CALLS_ENABLE 0 +#endif /* CONFIG_BT_PBAP_PSE_ENHANCED_MISSED_CALLS */ + +#if defined(CONFIG_BT_PBAP_PSE_UCI_VCARD_PROPERTY) +/** @brief Enable UCI vCard property feature for PBAP PSE. */ +#define BT_PBAP_PSE_FEATURE_UCI_VCARD_PROPERTY_ENABLE BT_PBAP_SUPPORTED_FEATURE_UCI_VCARD_PROPERTY +#else +/** @brief Disable UCI vCard property feature for PBAP PSE. */ +#define BT_PBAP_PSE_FEATURE_UCI_VCARD_PROPERTY_ENABLE 0 +#endif /* CONFIG_BT_PBAP_PSE_UCI_VCARD_PROPERTY */ + +#if defined(CONFIG_BT_PBAP_PSE_UID_VCARD_PROPERTY) +/** @brief Enable UID vCard property feature for PBAP PSE. */ +#define BT_PBAP_PSE_FEATURE_UID_VCARD_PROPERTY_ENABLE BT_PBAP_SUPPORTED_FEATURE_UID_VCARD_PROPERTY +#else +/** @brief Disable UID vCard property feature for PBAP PSE. */ +#define BT_PBAP_PSE_FEATURE_UID_VCARD_PROPERTY_ENABLE 0 +#endif /* CONFIG_BT_PBAP_PSE_UID_VCARD_PROPERTY */ + +#if defined(CONFIG_BT_PBAP_PSE_CONTACT_REFERENCING) +/** @brief Enable contact referencing feature for PBAP PSE. */ +#define BT_PBAP_PSE_FEATURE_CONTACT_REFERENCING_ENABLE BT_PBAP_SUPPORTED_FEATURE_CONTACT_REFERENCING +#else +/** @brief Disable contact referencing feature for PBAP PSE. */ +#define BT_PBAP_PSE_FEATURE_CONTACT_REFERENCING_ENABLE 0 +#endif /* CONFIG_BT_PBAP_PSE_CONTACT_REFERENCING */ + +#if defined(CONFIG_BT_PBAP_PSE_DEFAULT_CONTACT_IMAGE) +/** @brief Enable default contact image feature for PBAP PSE. */ +#define BT_PBAP_PSE_FEATURE_DEFAULT_CONTACT_IMAGE_ENABLE \ + BT_PBAP_SUPPORTED_FEATURE_DEFAULT_CONTACT_IMAGE +#else +/** @brief Disable default contact image feature for PBAP PSE. */ +#define BT_PBAP_PSE_FEATURE_DEFAULT_CONTACT_IMAGE_ENABLE 0 +#endif /* CONFIG_BT_PBAP_PSE_DEFAULT_CONTACT_IMAGE */ + +/** @brief PBAP PSE supported features bitmask. + * + * Combination of all enabled PBAP PSE features based on configuration. + */ +#define BT_PBAP_PSE_SUPPORTED_FEATURES \ + (BT_PBAP_PSE_FEATURE_DOWNLOAD_ENABLE | BT_PBAP_PSE_FEATURE_BROWSING_ENABLE | \ + BT_PBAP_PSE_FEATURE_DATABASE_IDENTIFIER_ENABLE | \ + BT_PBAP_PSE_FEATURE_FOLDER_VERSION_COUNTERS_ENABLE | \ + BT_PBAP_PSE_FEATURE_VCARD_SELECTOR_ENABLE | BT_PBAP_PSE_FEATURE_ENHANCED_MISSED_CALLS_ENABLE | \ + BT_PBAP_PSE_FEATURE_UCI_VCARD_PROPERTY_ENABLE | BT_PBAP_PSE_FEATURE_UID_VCARD_PROPERTY_ENABLE | \ + BT_PBAP_PSE_FEATURE_CONTACT_REFERENCING_ENABLE | \ + BT_PBAP_PSE_FEATURE_DEFAULT_CONTACT_IMAGE_ENABLE) + +#if defined(CONFIG_BT_PBAP_PSE_REPOSITORY_LOCAL_PHONE_BOOK) +/** @brief Enable local phone book repository for PBAP PSE. */ +#define BT_PBAP_PSE_REPOSITORY_LOCAL_PHONE_BOOK_ENABLE \ + BT_PBAP_SUPPORTED_REPOSITORIES_LOCAL_PHONE_BOOK +#else +/** @brief Disable local phone book repository for PBAP PSE. */ +#define BT_PBAP_PSE_REPOSITORY_LOCAL_PHONE_BOOK_ENABLE 0 +#endif /* CONFIG_BT_PBAP_PSE_REPOSITORY_LOCAL_PHONE_BOOK */ + +#if defined(CONFIG_BT_PBAP_PSE_REPOSITORY_SIM) +/** @brief Enable SIM repository for PBAP PSE. */ +#define BT_PBAP_PSE_REPOSITORY_SIM_ENABLE BT_PBAP_SUPPORTED_REPOSITORIES_SIM +#else +/** @brief Disable SIM repository for PBAP PSE. */ +#define BT_PBAP_PSE_REPOSITORY_SIM_ENABLE 0 +#endif /* CONFIG_BT_PBAP_PSE_REPOSITORY_SIM */ + +#if defined(CONFIG_BT_PBAP_PSE_REPOSITORY_SPEED_DIAL) +/** @brief Enable speed dial repository for PBAP PSE. */ +#define BT_PBAP_PSE_REPOSITORY_SPEED_DIAL_ENABLE BT_PBAP_SUPPORTED_REPOSITORIES_SPEED_DIAL +#else +/** @brief Disable speed dial repository for PBAP PSE. */ +#define BT_PBAP_PSE_REPOSITORY_SPEED_DIAL_ENABLE 0 +#endif /* CONFIG_BT_PBAP_PSE_REPOSITORY_SPEED_DIAL */ + +#if defined(CONFIG_BT_PBAP_PSE_REPOSITORY_FAVORITES) +/** @brief Enable favorites repository for PBAP PSE. */ +#define BT_PBAP_PSE_REPOSITORY_FAVORITES_ENABLE BT_PBAP_SUPPORTED_REPOSITORIES_FAVORITES +#else +/** @brief Disable favorites repository for PBAP PSE. */ +#define BT_PBAP_PSE_REPOSITORY_FAVORITES_ENABLE 0 +#endif /* CONFIG_BT_PBAP_PSE_REPOSITORY_FAVORITES */ + +/** @brief PBAP PSE supported repositories bitmask. + * + * Combination of all enabled PBAP PSE repositories based on configuration. + */ +#define BT_PBAP_PSE_SUPPORTED_REPOSITORIES \ + (BT_PBAP_PSE_REPOSITORY_LOCAL_PHONE_BOOK_ENABLE | BT_PBAP_PSE_REPOSITORY_SIM_ENABLE | \ + BT_PBAP_PSE_REPOSITORY_SPEED_DIAL_ENABLE | BT_PBAP_PSE_REPOSITORY_FAVORITES_ENABLE) diff --git a/libs/zblue/host/classic/rfcomm.c b/libs/zblue/host/classic/rfcomm.c new file mode 100644 index 0000000000000..164fc0f52f139 --- /dev/null +++ b/libs/zblue/host/classic/rfcomm.c @@ -0,0 +1,3280 @@ +/**************************************************************************** + * libs/zblue/host/classic/rfcomm.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* rfcomm.c - RFCOMM handling */ + +/* Copyright (c) 2016 Intel Corporation + * Copyright 2024-2026 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include "l2cap_br_internal.h" +#include "rfcomm_internal.h" + +#define LOG_LEVEL CONFIG_BT_RFCOMM_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_rfcomm); + +#define RFCOMM_CHANNEL_START 0x01 +#define RFCOMM_CHANNEL_END 0x1e + +#define RFCOMM_MIN_MTU BT_RFCOMM_SIG_MIN_MTU +#define RFCOMM_DEFAULT_MTU 127 + +#define RFCOMM_MAX_RX_CREDITS 255 + +#define RFCOMM_MAX_CREDITS (MIN((BT_BUF_ACL_RX_COUNT - 1), RFCOMM_MAX_RX_CREDITS)) + +#define RFCOMM_DLC_CREDITS_MAX(_dlc) ((_dlc)->rx_credit_limit) + +#define _RFCOMM_DLC_CREDITS_THRESHOLD(_dlc) (RFCOMM_DLC_CREDITS_MAX(_dlc) / 2) +#define RFCOMM_DLC_CREDITS_THRESHOLD(_dlc) MAX(_RFCOMM_DLC_CREDITS_THRESHOLD(_dlc), 1) + +#define RFCOMM_CONN_TIMEOUT SEC2TICK(60) +#define RFCOMM_DISC_TIMEOUT SEC2TICK(20) +#define RFCOMM_IDLE_TIMEOUT SEC2TICK(2) + +#define DLC_RTX(_w) CONTAINER_OF(k_work_delayable_from_work(_w), struct bt_rfcomm_dlc, rtx_work) +#define DLC_TX(_w) CONTAINER_OF(k_work_delayable_from_work(_w), struct bt_rfcomm_dlc, tx_work) +#define DLC_RETRY_TIMEOUT(_w) \ + CONTAINER_OF(k_work_delayable_from_work(_w), struct bt_rfcomm_dlc, retry_timeout_work) + +#define SESSION_RTX(_w) \ + CONTAINER_OF(k_work_delayable_from_work(_w), struct bt_rfcomm_session, rtx_work) +#define SESSION_RETRY(_w) \ + CONTAINER_OF(k_work_delayable_from_work(_w), struct bt_rfcomm_session, retry_work) +#define SESSION_RETRY_TIMEOUT(_w) \ + CONTAINER_OF(k_work_delayable_from_work(_w), struct bt_rfcomm_session, retry_timeout_work) + +static struct list_node servers = LIST_INITIAL_VALUE(servers); + +#define RFCOMM_SESSION(_ch) CONTAINER_OF(_ch, struct bt_rfcomm_session, br_chan.chan) + +static struct bt_rfcomm_session bt_rfcomm_pool[CONFIG_BT_MAX_CONN]; + +#define RFCOMM_RETRY_INTERVAL_MAX 100 +#define RFCOMM_RETRY_INTERVAL (MSEC2TICK((arc4random() % RFCOMM_RETRY_INTERVAL_MAX))) + +#define RFCOMM_RETRY_TIMEOUT (SEC2TICK(5)) + +#define RFCOMM_CRC_POLY CRC8_REFLECT_POLY +#define RFCOMM_CRC_INIT 0xff +#define RFCOMM_CRC_REVD true + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static uint8_t rfcomm_calc_fcs(uint16_t len, const uint8_t *data) +{ + uint8_t fcs; + + fcs = crc8(data, len, RFCOMM_CRC_POLY, RFCOMM_CRC_INIT, RFCOMM_CRC_REVD); + + /* Ones compliment */ + + fcs = (0xff - fcs); + + return fcs; +} + +static bool rfcomm_check_fcs(uint16_t len, + const uint8_t *data, uint8_t recvd_fcs) +{ + uint8_t fcs; + + fcs = rfcomm_calc_fcs(len, data); + + return (fcs == recvd_fcs); +} + +static struct bt_rfcomm_dlc *rfcomm_dlcs_lookup_dlci( + struct bt_rfcomm_session *session, uint8_t dlci) +{ + struct bt_rfcomm_dlc *dlc; + struct bt_rfcomm_dlc *tmp; + + if (session == NULL) + { + return NULL; + } + + list_for_every_container_safe(&session->dlcs, dlc, tmp, _node) + { + if (dlc->dlci == dlci) + { + return dlc; + } + } + + return NULL; +} + +static struct bt_rfcomm_dlc *rfcomm_dlcs_remove_dlci( + struct bt_rfcomm_session *session, uint8_t dlci) +{ + struct bt_rfcomm_dlc *tmp; + bool found; + + tmp = rfcomm_dlcs_lookup_dlci(session, dlci); + if (tmp == NULL) + { + return NULL; + } + + found = list_find_and_remove(&session->dlcs, &tmp->_node); + if (!found) + { + LOG_WRN("DLC %p has been removed", tmp); + } + + return tmp; +} + +static struct bt_rfcomm_server *rfcomm_server_lookup_channel(uint8_t channel) +{ + struct bt_rfcomm_server *server; + struct bt_rfcomm_server *next; + + list_for_every_container_safe(&servers, server, next, node) + { + if (server->channel == channel) + { + return server; + } + } + + return NULL; +} + +static struct bt_rfcomm_session *rfcomm_sessions_lookup_bt_conn( + struct bt_conn *conn) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bt_rfcomm_pool); i++) + { + struct bt_rfcomm_session *session = &bt_rfcomm_pool[i]; + + if (session->br_chan.chan.conn == conn) + { + return session; + } + } + + return NULL; +} + +int bt_rfcomm_server_register(struct bt_rfcomm_server *server) +{ + if (server->channel > RFCOMM_CHANNEL_END || !server->accept) + { + return -EINVAL; + } + + if (!server->channel) + { + uint8_t chan = (uint8_t)BT_RFCOMM_CHAN_DYNAMIC_START; + + for (; chan <= RFCOMM_CHANNEL_END; chan++) + { + /* Check if given channel is already in use */ + + if (!rfcomm_server_lookup_channel(chan)) + { + server->channel = chan; + LOG_DBG("Allocated channel 0x%02x for new server", chan); + break; + } + } + + if (!server->channel) + { + LOG_WRN("No free dynamic rfcomm channels available"); + return -EADDRNOTAVAIL; + } + } + else + { + /* Check if given channel is already in use */ + + if (rfcomm_server_lookup_channel(server->channel)) + { + LOG_WRN("Channel already registered"); + return -EADDRINUSE; + } + } + + LOG_DBG("Channel 0x%02x", server->channel); + + list_add_head(&servers, &server->node); + + return 0; +} + +int bt_rfcomm_server_unregister(struct bt_rfcomm_server *server) +{ + if (!list_find_and_remove(&servers, &server->node)) + { + return -ENOENT; + } + + return 0; +} + +static void rfcomm_dlc_tx_trigger(struct bt_rfcomm_dlc *dlc) +{ + int err; + + if ((dlc == NULL) || (dlc->tx_work.work.handler == NULL)) + { + return; + } + + LOG_DBG("DLC %p TX trigger", dlc); + + err = bt_work_reschedule(&dlc->tx_work, 0); + if (err < 0) + { + LOG_ERR("Failed to reschedule tx work: %d", err); + } +} + +static void rfcomm_dlcs_tx_trigger(struct bt_rfcomm_session *session) +{ + struct bt_rfcomm_dlc *dlc; + struct bt_rfcomm_dlc *tmp; + + list_for_every_container_safe(&session->dlcs, dlc, tmp, _node) + { + rfcomm_dlc_tx_trigger(dlc); + } +} + +static void rfcomm_dlc_tx_give_credits(struct bt_rfcomm_dlc *dlc, + uint8_t credits) +{ + int sem_count; + + LOG_DBG("dlc %p credits %u", dlc, credits); + + while (credits--) + { + nxsem_post(&dlc->tx_credits); + rfcomm_dlc_tx_trigger(dlc); + } + + (void)nxsem_get_value(&dlc->tx_credits, &sem_count); + LOG_DBG("dlc %p updated credits %d", dlc, sem_count); +} + +static void rfcomm_dlc_destroy(struct bt_rfcomm_dlc *dlc) +{ + LOG_DBG("dlc %p", dlc); + + k_work_cancel_delayable(&dlc->rtx_work); + k_work_cancel_delayable(&dlc->tx_work); + k_work_cancel_delayable(&dlc->retry_timeout_work); + + dlc->state = BT_RFCOMM_STATE_IDLE; + dlc->session = NULL; + + if (dlc->ops && dlc->ops->disconnected) + { + dlc->ops->disconnected(dlc); + } +} + +static void rfcomm_dlc_disconnect(struct bt_rfcomm_dlc *dlc) +{ + uint8_t old_state = dlc->state; + + LOG_DBG("dlc %p", dlc); + + if (dlc->state == BT_RFCOMM_STATE_DISCONNECTED) + { + return; + } + + dlc->state = BT_RFCOMM_STATE_DISCONNECTED; + + switch (old_state) + { + case BT_RFCOMM_STATE_CONNECTED: + rfcomm_dlc_tx_trigger(dlc); + break; + default: + rfcomm_dlc_destroy(dlc); + break; + } +} + +static void rfcomm_session_disconnected(struct bt_rfcomm_session *session) +{ + struct bt_rfcomm_dlc *dlc; + struct bt_rfcomm_dlc *tmp; + + LOG_DBG("Session %p", session); + + if (session->state == BT_RFCOMM_STATE_DISCONNECTED) + { + return; + } + + list_for_every_container_safe(&session->dlcs, dlc, tmp, _node) + { + rfcomm_dlc_disconnect(dlc); + } + + list_initialize(&session->dlcs); + /* If the function is called, it means the session disconnection process + * has been requested by peer device. Or the L2CAP connection has been + * closed (it is not counted in the case). While, if the session is in + * disconnecting state, it means the disconnection process has been started + * by the local device. In this case, only set the session to disconnected + * state if the local session is not in disconnecting state. It is used to + * avoid the race condition where the disconnection process is triggered by + * both the local and peer devices at the same time. Without performing the + * check, the race condition might cause the session state to be set to + * disconnected on both sides, and the disconnection process will not + * complete. And the l2cap connection of session will not be released. As a + * result, the rfcomm channel connection request will not work properly. + */ + + if (session->state != BT_RFCOMM_STATE_DISCONNECTING) + { + session->state = BT_RFCOMM_STATE_DISCONNECTED; + } +} + +struct net_buf *bt_rfcomm_create_pdu(struct net_buf_pool *pool) +{ + /* Length in RFCOMM header can be 2 bytes depending on length of user + * data + */ + + return bt_conn_create_pdu(pool, + sizeof(struct bt_l2cap_hdr) + sizeof(struct bt_rfcomm_hdr) + 1); +} + +static int rfcomm_send_cb( + struct bt_rfcomm_session *session, struct net_buf *buf, bt_conn_tx_cb_t cb, + void *user_data) +{ + int err; + + err = bt_l2cap_br_chan_send_cb(&session->br_chan.chan, buf, cb, user_data); + if (err < 0) + { + net_buf_unref(buf); + } + + return err; +} + +static int rfcomm_send(struct bt_rfcomm_session *session, + struct net_buf *buf) +{ + return rfcomm_send_cb(session, buf, NULL, NULL); +} + +static struct net_buf *rfcomm_create_pdu(uint16_t len) +{ + struct net_buf *buf; + + buf = bt_l2cap_create_pdu_timeout(NULL, 0, 0); + if (buf == NULL) + { + LOG_WRN("net buffer allocation failure"); + return NULL; + } + + if (net_buf_tailroom(buf) < len) + { + LOG_WRN("Tail room too small (%zu < %u)", net_buf_tailroom(buf), len); + net_buf_unref(buf); + return NULL; + } + + return buf; +} + +static int rfcomm_send_sabm(struct bt_rfcomm_session *session, uint8_t dlci) +{ + struct bt_rfcomm_hdr *hdr; + struct net_buf *buf; + uint8_t cr; + uint8_t fcs; + + buf = rfcomm_create_pdu(sizeof(*hdr) + sizeof(fcs)); + if (buf == NULL) + { + return -ENOBUFS; + } + + hdr = net_buf_add(buf, sizeof(*hdr)); + cr = BT_RFCOMM_CMD_CR(session->role); + hdr->address = BT_RFCOMM_SET_ADDR(dlci, cr); + hdr->control = BT_RFCOMM_SET_CTRL(BT_RFCOMM_SABM, BT_RFCOMM_PF_NON_UIH); + hdr->length = BT_RFCOMM_SET_LEN_8(0); + + fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_NON_UIH, buf->data); + net_buf_add_u8(buf, fcs); + + return rfcomm_send(session, buf); +} + +static int rfcomm_send_disc(struct bt_rfcomm_session *session, uint8_t dlci) +{ + struct bt_rfcomm_hdr *hdr; + struct net_buf *buf; + uint8_t fcs; + uint8_t cr; + + LOG_DBG("dlci %d", dlci); + + buf = rfcomm_create_pdu(sizeof(*hdr) + sizeof(fcs)); + if (buf == NULL) + { + return -ENOBUFS; + } + + hdr = net_buf_add(buf, sizeof(*hdr)); + cr = BT_RFCOMM_CMD_CR(session->role); + hdr->address = BT_RFCOMM_SET_ADDR(dlci, cr); + hdr->control = BT_RFCOMM_SET_CTRL(BT_RFCOMM_DISC, BT_RFCOMM_PF_NON_UIH); + hdr->length = BT_RFCOMM_SET_LEN_8(0); + fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_NON_UIH, buf->data); + net_buf_add_u8(buf, fcs); + + return rfcomm_send(session, buf); +} + +static void rfcomm_session_disconnect(struct bt_rfcomm_session *session) +{ + int err; + + if (!list_is_empty(&session->dlcs)) + { + return; + } + + session->state = BT_RFCOMM_STATE_DISCONNECTING; + err = rfcomm_send_disc(session, 0); + if (err == 0) + { + bt_work_reschedule(&session->rtx_work, RFCOMM_DISC_TIMEOUT); + } + else + { + LOG_ERR("Failed to send disc on session %p (%d)", session, err); + bt_work_reschedule(&session->rtx_work, 0); + } +} + +static struct net_buf *rfcomm_make_uih_msg( + struct bt_rfcomm_session *session, uint8_t cr, uint8_t type, uint8_t len) +{ + struct bt_rfcomm_hdr *hdr; + struct bt_rfcomm_msg_hdr *msg_hdr; + struct net_buf *buf; + uint16_t total_len; + uint8_t hdr_cr; + + total_len = len + sizeof(*hdr) + sizeof(*msg_hdr) + sizeof(uint8_t); + + buf = rfcomm_create_pdu(total_len); + if (buf == NULL) + { + return NULL; + } + + hdr = net_buf_add(buf, sizeof(*hdr)); + hdr_cr = BT_RFCOMM_UIH_CR(session->role); + hdr->address = BT_RFCOMM_SET_ADDR(0, hdr_cr); + hdr->control = BT_RFCOMM_SET_CTRL(BT_RFCOMM_UIH, BT_RFCOMM_PF_UIH); + hdr->length = BT_RFCOMM_SET_LEN_8(sizeof(*msg_hdr) + len); + + msg_hdr = net_buf_add(buf, sizeof(*msg_hdr)); + msg_hdr->type = BT_RFCOMM_SET_MSG_TYPE(type, cr); + msg_hdr->len = BT_RFCOMM_SET_LEN_8(len); + + return buf; +} + +static void rfcomm_session_disconn_req(struct bt_rfcomm_session *session) +{ + int err; + + if (session->br_chan.chan.conn == NULL) + { + /* The underlying connection is already gone; there is nothing + * left for the timeout to act on. + */ + + return; + } + + err = bt_l2cap_chan_disconnect(&session->br_chan.chan); + if (err == 0) + { + k_work_cancel_delayable(&session->retry_timeout_work); + return; + } + + LOG_ERR("Failed to disconnect RFCOMM session %p (%d)", session, err); + + if (err == -ENOBUFS) + { + atomic_set_bit(&session->flags, RFCOMM_SESSION_FLAG_DISCONN_REQ); + bt_work_schedule(&session->retry_timeout_work, RFCOMM_RETRY_TIMEOUT); + bt_work_schedule(&session->retry_work, RFCOMM_RETRY_INTERVAL); + return; + } + + bt_work_reschedule(&session->rtx_work, 0); +} + +static void rfcomm_connected(struct bt_l2cap_chan *chan) +{ + struct bt_rfcomm_session *session = RFCOMM_SESSION(chan); + int err; + + LOG_DBG("Session %p", session); + + /* Take the minimum value supported by both ends. */ + + session->mtu = MIN(session->br_chan.rx.mtu, session->br_chan.tx.mtu); + + if (session->mtu < (BT_RFCOMM_OVERHEAD_SIZE + RFCOMM_MIN_MTU)) + { + LOG_ERR("Invalid RFCOMM session MTU %u < %u", session->mtu, + BT_RFCOMM_OVERHEAD_SIZE + RFCOMM_MIN_MTU); + + goto disconnect; + } + + /* Discount the overhead size from the L2CAP MTU. + * + * The overhead size of RFCOMM includes the maximum header size, FCS size, + * and credits size. + * + * For the field credits size, in the CFC supported case, the space of + * credits should be discounted from the maximum frame size. It is used to + * avoid the SDU length exceeding the maximum frame size if the credits + * field is included. + */ + + session->mtu -= BT_RFCOMM_OVERHEAD_SIZE; + + if (session->state == BT_RFCOMM_STATE_CONNECTING) + { + err = rfcomm_send_sabm(session, 0); + if (err != 0) + { + LOG_ERR("Failed to send sabm on session %p (%d)", session, err); + goto disconnect; + } + } + + return; + +disconnect: + rfcomm_session_disconn_req(session); +} + +static void rfcomm_disconnected(struct bt_l2cap_chan *chan) +{ + struct bt_rfcomm_session *session = RFCOMM_SESSION(chan); + + LOG_DBG("Session %p", session); + + k_work_cancel_delayable(&session->rtx_work); + k_work_cancel_delayable(&session->retry_work); + k_work_cancel_delayable(&session->retry_timeout_work); + rfcomm_session_disconnected(session); + session->state = BT_RFCOMM_STATE_IDLE; +} + +static void rfcomm_dlc_rtx_timeout(struct k_work *work) +{ + struct bt_rfcomm_dlc *dlc = DLC_RTX(work); + struct bt_rfcomm_session *session = dlc->session; + + LOG_WRN("dlc %p state %d timeout", dlc, dlc->state); + + rfcomm_dlcs_remove_dlci(session, dlc->dlci); + rfcomm_dlc_disconnect(dlc); + rfcomm_session_disconnect(session); +} + +static void rfcomm_dlc_retry_timeout(struct k_work *work) +{ + struct bt_rfcomm_dlc *dlc = DLC_RETRY_TIMEOUT(work); + + LOG_WRN("dlc %p state %d retry timeout", dlc, dlc->state); + + bt_work_reschedule(&dlc->rtx_work, 0); +} + +static void rfcomm_dlc_tx_worker(struct k_work *work); + +static void rfcomm_dlc_init( + struct bt_rfcomm_dlc *dlc, struct bt_rfcomm_session *session, uint8_t dlci, + bt_rfcomm_role_t role) +{ + struct k_work_sync sync; + + LOG_DBG("dlc %p", dlc); + + if (dlc->tx_work.work.handler != NULL) + { + /* Make sure the tx_work is cancelled before reinitializing */ + + k_work_cancel_delayable_sync(&dlc->tx_work, &sync); + + /* Reset the work handler to NULL before the DLC connected */ + + dlc->tx_work.work.handler = NULL; + } + + dlc->dlci = dlci; + dlc->session = session; + if (dlc->rx_credit_limit == 0) + { + dlc->rx_credit_limit = RFCOMM_MAX_CREDITS; + } + else + { + dlc->rx_credit_limit = MIN(dlc->rx_credit_limit, RFCOMM_MAX_CREDITS); + } + + dlc->rx_credit = dlc->rx_credit_limit; + atomic_set(&dlc->rx_credit_inprogress, 0); + atomic_set(&dlc->flags, 0); + dlc->state = BT_RFCOMM_STATE_INIT; + dlc->role = role; + k_work_init_delayable(&dlc->rtx_work, rfcomm_dlc_rtx_timeout); + k_work_init_delayable(&dlc->tx_work, rfcomm_dlc_tx_worker); + k_work_init_delayable(&dlc->retry_timeout_work, rfcomm_dlc_retry_timeout); + + /* Start a conn timer which includes auth as well */ + + bt_work_schedule(&dlc->rtx_work, RFCOMM_CONN_TIMEOUT); + + list_add_head(&session->dlcs, &dlc->_node); +} + +static struct bt_rfcomm_dlc *rfcomm_dlc_accept( + struct bt_rfcomm_session *session, uint8_t dlci) +{ + struct bt_rfcomm_server *server; + struct bt_rfcomm_dlc *dlc; + uint8_t channel; + + channel = BT_RFCOMM_GET_CHANNEL(dlci); + server = rfcomm_server_lookup_channel(channel); + if (!server) + { + LOG_ERR("Server Channel not registered"); + return NULL; + } + + if (server->accept(session->br_chan.chan.conn, server, &dlc) < 0) + { + LOG_DBG("Incoming connection rejected"); + return NULL; + } + + if (!BT_RFCOMM_CHECK_MTU(dlc->mtu)) + { + rfcomm_dlc_destroy(dlc); + return NULL; + } + + rfcomm_dlc_init(dlc, session, dlci, BT_RFCOMM_ROLE_ACCEPTOR); + dlc->mtu = MIN(dlc->mtu, session->mtu); + + return dlc; +} + +static int rfcomm_send_dm(struct bt_rfcomm_session *session, uint8_t dlci) +{ + struct bt_rfcomm_hdr *hdr; + struct net_buf *buf; + uint8_t fcs; + uint8_t cr; + + LOG_DBG("dlci %d", dlci); + + buf = rfcomm_create_pdu(sizeof(*hdr) + sizeof(fcs)); + if (buf == NULL) + { + return -ENOBUFS; + } + + hdr = net_buf_add(buf, sizeof(*hdr)); + cr = BT_RFCOMM_RESP_CR(session->role); + hdr->address = BT_RFCOMM_SET_ADDR(dlci, cr); + + /* For DM PF bit is not relevant, we set it 1 */ + + hdr->control = BT_RFCOMM_SET_CTRL(BT_RFCOMM_DM, BT_RFCOMM_PF_NON_UIH); + hdr->length = BT_RFCOMM_SET_LEN_8(0); + fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_NON_UIH, buf->data); + net_buf_add_u8(buf, fcs); + + return rfcomm_send(session, buf); +} + +static bool rfcomm_check_fc(struct bt_rfcomm_dlc *dlc) +{ + int err; + + LOG_DBG("Wait for credits or MSC FC %p", dlc); + + /* Wait for credits or MSC FC */ + + err = nxsem_trywait(&dlc->tx_credits); + if (err != 0) + { + LOG_DBG("Credits not available"); + return false; + } + + LOG_DBG("Credits available"); + if (dlc->session->cfc == BT_RFCOMM_CFC_SUPPORTED) + { + LOG_DBG("Credits available"); + return true; + } + + err = nxsem_trywait(&dlc->session->fc); + if (err != 0) + { + nxsem_post(&dlc->tx_credits); + LOG_DBG("Flow control not available"); + return false; + } + + /* Give the semaphores immediately so that sem will be available for all + * the bufs in the queue. It will be blocked only once all the bufs are + * sent (which will preempt this thread) and FCOFF / FC bit with 1, is + * received. + */ + + nxsem_post(&dlc->session->fc); + nxsem_post(&dlc->tx_credits); + + LOG_DBG("Flow control available"); + return true; +} + +static void bt_rfcomm_tx_destroy(struct bt_rfcomm_dlc *dlc, + struct net_buf *buf) +{ + LOG_DBG("dlc %p, buf %p", dlc, buf); + + if ((buf == NULL) || (buf->len == 0)) + { + return; + } + + if (dlc && dlc->ops && dlc->ops->sent) + { + dlc->ops->sent(dlc, -ESHUTDOWN); + } +} + +static void rfcomm_sent(struct bt_conn *conn, void *user_data, int err) +{ + struct bt_rfcomm_dlc *dlc; + + LOG_DBG("conn %p", conn); + + if (user_data == NULL) + { + return; + } + + dlc = (struct bt_rfcomm_dlc *)user_data; + + rfcomm_dlc_tx_trigger(dlc); + + if (dlc && dlc->ops && dlc->ops->sent) + { + dlc->ops->sent(dlc, err); + } +} + +static int rfcomm_send_ua(struct bt_rfcomm_session *session, uint8_t dlci) +{ + struct bt_rfcomm_hdr *hdr; + struct net_buf *buf; + uint8_t cr; + uint8_t fcs; + + buf = rfcomm_create_pdu(sizeof(*hdr) + sizeof(fcs)); + if (buf == NULL) + { + return -ENOBUFS; + } + + hdr = net_buf_add(buf, sizeof(*hdr)); + cr = BT_RFCOMM_RESP_CR(session->role); + hdr->address = BT_RFCOMM_SET_ADDR(dlci, cr); + hdr->control = BT_RFCOMM_SET_CTRL(BT_RFCOMM_UA, BT_RFCOMM_PF_NON_UIH); + hdr->length = BT_RFCOMM_SET_LEN_8(0); + + fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_NON_UIH, buf->data); + net_buf_add_u8(buf, fcs); + + return rfcomm_send(session, buf); +} + +static int rfcomm_send_msc(struct bt_rfcomm_dlc *dlc, + uint8_t cr, uint8_t v24_signal) +{ + struct bt_rfcomm_msc *msc; + struct net_buf *buf; + uint8_t fcs; + + buf = rfcomm_make_uih_msg(dlc->session, cr, BT_RFCOMM_MSC, sizeof(*msc)); + if (buf == NULL) + { + return -ENOBUFS; + } + + msc = net_buf_add(buf, sizeof(*msc)); + + /* cr bit should be always 1 in MSC */ + + msc->dlci = BT_RFCOMM_SET_ADDR(dlc->dlci, 1); + msc->v24_signal = v24_signal; + + fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_UIH, buf->data); + net_buf_add_u8(buf, fcs); + + return rfcomm_send(dlc->session, buf); +} + +static int rfcomm_send_rls(struct bt_rfcomm_dlc *dlc, + uint8_t cr, uint8_t line_status) +{ + struct bt_rfcomm_rls *rls; + struct net_buf *buf; + uint8_t fcs; + + buf = rfcomm_make_uih_msg(dlc->session, cr, BT_RFCOMM_RLS, sizeof(*rls)); + if (buf == NULL) + { + return -ENOBUFS; + } + + rls = net_buf_add(buf, sizeof(*rls)); + + /* cr bit should be always 1 in RLS */ + + rls->dlci = BT_RFCOMM_SET_ADDR(dlc->dlci, 1); + rls->line_status = line_status; + + fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_UIH, buf->data); + net_buf_add_u8(buf, fcs); + + return rfcomm_send(dlc->session, buf); +} + +#define RFCOMM_RLS_MASK GENMASK(3, 0) +#define RFCOMM_RLS_ERROR_FLAG_MASK BIT(0) +#define RFCOMM_RLS_ERROR_CODE_MASK GENMASK(3, 1) + +#define RFCOMM_RLS_VALID(line_status) \ + (((line_status) & ~RFCOMM_RLS_MASK) == 0 && \ + (((line_status) & RFCOMM_RLS_ERROR_FLAG_MASK) == 0 || \ + ((line_status) & RFCOMM_RLS_ERROR_CODE_MASK) != 0)) + +int bt_rfcomm_send_rls_cmd(struct bt_rfcomm_dlc *dlc, uint8_t line_status) +{ + if (dlc == NULL) + { + return -EINVAL; + } + + if (!RFCOMM_RLS_VALID(line_status)) + { + LOG_ERR("Invalid line status: 0x%02x", line_status); + return -EINVAL; + } + + if (dlc->session == NULL || dlc->state != BT_RFCOMM_STATE_CONNECTED) + { + LOG_ERR("dlc %p not connected", dlc); + return -ENOTCONN; + } + + LOG_DBG("dlc %p sending RLS(0x%02x) on session %p", dlc, line_status, + dlc->session); + + /* Send the RLS command */ + + return rfcomm_send_rls(dlc, BT_RFCOMM_MSG_CMD_CR, line_status); +} + +static int rfcomm_send_rpn(struct bt_rfcomm_session *session, + uint8_t cr, struct bt_rfcomm_rpn *rpn) +{ + struct net_buf *buf; + uint8_t fcs; + + buf = rfcomm_make_uih_msg(session, cr, BT_RFCOMM_RPN, sizeof(*rpn)); + if (buf == NULL) + { + return -ENOBUFS; + } + + net_buf_add_mem(buf, rpn, sizeof(*rpn)); + + fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_UIH, buf->data); + net_buf_add_u8(buf, fcs); + + return rfcomm_send(session, buf); +} + +int bt_rfcomm_send_rpn_cmd(struct bt_rfcomm_dlc *dlc, + struct bt_rfcomm_rpn *rpn) +{ + struct bt_rfcomm_session *session; + + if (!dlc || !rpn) + { + return -EINVAL; + } + + /* Validate baud rate */ + + if (rpn->baud_rate > BT_RFCOMM_RPN_BAUD_RATE_230400) + { + LOG_ERR("Invalid baud rate: %u", rpn->baud_rate); + return -EINVAL; + } + + session = dlc->session; + if (!session) + { + return -ENOTCONN; + } + + if (dlc->state != BT_RFCOMM_STATE_CONNECTED) + { + return -ENOTCONN; + } + + LOG_DBG("dlc %p", dlc); + + /* Set DLCI in the RPN command */ + + rpn->dlci = BT_RFCOMM_SET_ADDR(dlc->dlci, 1); + + /* Send the RPN command */ + + return rfcomm_send_rpn(session, BT_RFCOMM_MSG_CMD_CR, rpn); +} + +static int rfcomm_send_test( + struct bt_rfcomm_session *session, uint8_t cr, uint8_t *pattern, + uint8_t len) +{ + struct net_buf *buf; + uint8_t fcs; + + buf = rfcomm_make_uih_msg(session, cr, BT_RFCOMM_TEST, len); + if (buf == NULL) + { + return -ENOBUFS; + } + + net_buf_add_mem(buf, pattern, len); + + fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_UIH, buf->data); + net_buf_add_u8(buf, fcs); + + return rfcomm_send(session, buf); +} + +static int rfcomm_send_nsc(struct bt_rfcomm_session *session, + uint8_t cmd_type) +{ + struct net_buf *buf; + uint8_t fcs; + + buf = rfcomm_make_uih_msg(session, BT_RFCOMM_MSG_RESP_CR, BT_RFCOMM_NSC, + sizeof(cmd_type)); + if (buf == NULL) + { + return -ENOBUFS; + } + + net_buf_add_u8(buf, cmd_type); + + fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_UIH, buf->data); + net_buf_add_u8(buf, fcs); + + return rfcomm_send(session, buf); +} + +static int rfcomm_send_fcon(struct bt_rfcomm_session *session, uint8_t cr) +{ + struct net_buf *buf; + uint8_t fcs; + + buf = rfcomm_make_uih_msg(session, cr, BT_RFCOMM_FCON, 0); + if (buf == NULL) + { + return -ENOBUFS; + } + + fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_UIH, buf->data); + net_buf_add_u8(buf, fcs); + + return rfcomm_send(session, buf); +} + +static int rfcomm_send_fcoff(struct bt_rfcomm_session *session, uint8_t cr) +{ + struct net_buf *buf; + uint8_t fcs; + + buf = rfcomm_make_uih_msg(session, cr, BT_RFCOMM_FCOFF, 0); + if (buf == NULL) + { + return -ENOBUFS; + } + + fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_UIH, buf->data); + net_buf_add_u8(buf, fcs); + + return rfcomm_send(session, buf); +} + +enum security_result +{ + RFCOMM_SECURITY_PASSED, + RFCOMM_SECURITY_REJECT, + RFCOMM_SECURITY_PENDING +}; + +static enum security_result rfcomm_dlc_security(struct bt_rfcomm_dlc *dlc) +{ + struct bt_conn *conn = dlc->session->br_chan.chan.conn; + + LOG_DBG("dlc %p", dlc); + + /* If current security level is greater than or equal to required + * security level then return SUCCESS. For SSP devices the current security + * will be at least MEDIUM since L2CAP is enforcing it + */ + + if (conn->sec_level >= dlc->required_sec_level) + { + return RFCOMM_SECURITY_PASSED; + } + + if (!bt_conn_set_security(conn, dlc->required_sec_level)) + { + /* General Bonding refers to the process of performing bonding + * during connection setup or channel establishment procedures as a + * precursor to accessing a service. For current case, it is dedicated + * bonding. + */ + + atomic_set_bit(conn->flags, BT_CONN_BR_GENERAL_BONDING); + + /* If Security elevation is initiated or in progress */ + + return RFCOMM_SECURITY_PENDING; + } + + /* Security request failed */ + + return RFCOMM_SECURITY_REJECT; +} + +static void rfcomm_dlc_drop(struct bt_rfcomm_dlc *dlc) +{ + LOG_DBG("dlc %p", dlc); + + (void)rfcomm_dlcs_remove_dlci(dlc->session, dlc->dlci); + rfcomm_dlc_destroy(dlc); +} + +static void rfcomm_dlc_disconn(struct bt_rfcomm_dlc *dlc) +{ + int err; + + dlc->state = BT_RFCOMM_STATE_DISCONNECTING; + err = rfcomm_send_disc(dlc->session, dlc->dlci); + if (err == 0) + { + k_work_cancel_delayable(&dlc->retry_timeout_work); + bt_work_reschedule(&dlc->rtx_work, RFCOMM_DISC_TIMEOUT); + } + else if (err == -ENOBUFS) + { + bt_work_schedule(&dlc->retry_timeout_work, RFCOMM_RETRY_TIMEOUT); + bt_work_schedule(&dlc->tx_work, RFCOMM_RETRY_INTERVAL); + } + else + { + LOG_ERR("Failed to send disc on DLC %p (%d)", dlc, err); + bt_work_reschedule(&dlc->rtx_work, 0); + } +} + +static int rfcomm_dlc_send_dm(struct bt_rfcomm_dlc *dlc) +{ + int err; + + if (dlc->session == NULL) + { + return -ENOTCONN; + } + + if (dlc->state != BT_RFCOMM_STATE_SECURITY_PENDING && + dlc->state != BT_RFCOMM_STATE_SECURITY_FAILED) + { + return -EINVAL; + } + + if (dlc->role != BT_RFCOMM_ROLE_ACCEPTOR) + { + return -EINVAL; + } + + err = rfcomm_send_dm(dlc->session, dlc->dlci); + if (err == 0) + { + k_work_cancel_delayable(&dlc->retry_timeout_work); + rfcomm_dlc_drop(dlc); + return 0; + } + + if (err == -ENOBUFS) + { + atomic_set_bit(&dlc->flags, RFCOMM_FLAG_DM); + bt_work_schedule(&dlc->retry_timeout_work, RFCOMM_RETRY_TIMEOUT); + bt_work_schedule(&dlc->tx_work, RFCOMM_RETRY_INTERVAL); + return 0; + } + + LOG_ERR("Failed to send dm on DLC %p (%d)", dlc, err); + return err; +} + +static int rfcomm_dlc_close(struct bt_rfcomm_dlc *dlc) +{ + int err; + + LOG_DBG("dlc %p", dlc); + + switch (dlc->state) + { + case BT_RFCOMM_STATE_SECURITY_PENDING: + if (dlc->role == BT_RFCOMM_ROLE_ACCEPTOR) + { + err = rfcomm_dlc_send_dm(dlc); + if (err == 0) + { + return 0; + } + + LOG_ERR("Failed to send dm on DLC %p (%d)", dlc, err); + } + + __fallthrough; + case BT_RFCOMM_STATE_SECURITY_FAILED: + case BT_RFCOMM_STATE_INIT: + rfcomm_dlc_drop(dlc); + break; + case BT_RFCOMM_STATE_CONNECTING: + case BT_RFCOMM_STATE_CONFIG: + dlc->state = BT_RFCOMM_STATE_DISCONNECTING; + rfcomm_dlc_disconn(dlc); + break; + case BT_RFCOMM_STATE_CONNECTED: + dlc->state = BT_RFCOMM_STATE_DISCONNECTING; + rfcomm_dlc_tx_trigger(dlc); + break; + case BT_RFCOMM_STATE_DISCONNECTING: + case BT_RFCOMM_STATE_DISCONNECTED: + break; + case BT_RFCOMM_STATE_IDLE: + default: + return -EINVAL; + } + + return 0; +} + +#define RFCOMM_PN_MAX_RX_CREDITS 7 + +static int rfcomm_send_pn(struct bt_rfcomm_dlc *dlc, uint8_t cr) +{ + struct bt_rfcomm_pn *pn; + struct net_buf *buf; + uint8_t fcs; + + buf = rfcomm_make_uih_msg(dlc->session, cr, BT_RFCOMM_PN, sizeof(*pn)); + if (buf == NULL) + { + return -ENOBUFS; + } + + LOG_DBG("mtu %x", dlc->mtu); + + pn = net_buf_add(buf, sizeof(*pn)); + pn->dlci = dlc->dlci; + pn->mtu = sys_cpu_to_le16(dlc->mtu); + if (dlc->state == BT_RFCOMM_STATE_CONFIG && + (dlc->session->cfc == BT_RFCOMM_CFC_UNKNOWN || + dlc->session->cfc == BT_RFCOMM_CFC_SUPPORTED)) + { + pn->credits = MIN(dlc->rx_credit_limit, RFCOMM_PN_MAX_RX_CREDITS); + if (cr) + { + pn->flow_ctrl = BT_RFCOMM_PN_CFC_CMD; + } + else + { + pn->flow_ctrl = BT_RFCOMM_PN_CFC_RESP; + } + } + else + { + /* If PN comes in already opened dlc or cfc not supported + * these should be 0 + */ + + pn->credits = 0U; + pn->flow_ctrl = 0U; + } + + pn->max_retrans = 0U; + pn->ack_timer = 0U; + pn->priority = 0U; + + fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_UIH, buf->data); + net_buf_add_u8(buf, fcs); + + return rfcomm_send(dlc->session, buf); +} + +static int rfcomm_send_credit(struct bt_rfcomm_dlc *dlc, uint8_t credits) +{ + struct bt_rfcomm_hdr *hdr; + struct net_buf *buf; + uint8_t fcs; + uint8_t cr; + + LOG_DBG("Dlc %p credits %d", dlc, credits); + + buf = rfcomm_create_pdu(sizeof(*hdr) + sizeof(credits) + sizeof(fcs)); + if (buf == NULL) + { + return -ENOBUFS; + } + + hdr = net_buf_add(buf, sizeof(*hdr)); + cr = BT_RFCOMM_UIH_CR(dlc->session->role); + hdr->address = BT_RFCOMM_SET_ADDR(dlc->dlci, cr); + hdr->control = BT_RFCOMM_SET_CTRL(BT_RFCOMM_UIH, BT_RFCOMM_PF_UIH_CREDIT); + hdr->length = BT_RFCOMM_SET_LEN_8(0); + net_buf_add_u8(buf, credits); + fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_UIH, buf->data); + net_buf_add_u8(buf, fcs); + + return rfcomm_send(dlc->session, buf); +} + +static int rfcomm_dlc_send_pn_req(struct bt_rfcomm_dlc *dlc) +{ + int err; + uint8_t state; + + if (dlc->session == NULL) + { + return -EINVAL; + } + + if (dlc->role != BT_RFCOMM_ROLE_INITIATOR) + { + return -EINVAL; + } + + if (dlc->state != BT_RFCOMM_STATE_INIT && + dlc->state != BT_RFCOMM_STATE_SECURITY_PENDING) + { + return -EINVAL; + } + + dlc->mtu = MIN(dlc->mtu, dlc->session->mtu); + state = dlc->state; + dlc->state = BT_RFCOMM_STATE_CONFIG; + err = rfcomm_send_pn(dlc, BT_RFCOMM_MSG_CMD_CR); + if (err == 0) + { + k_work_cancel_delayable(&dlc->retry_timeout_work); + return 0; + } + + dlc->state = state; + + if (err == -ENOBUFS) + { + atomic_set_bit(&dlc->flags, RFCOMM_FLAG_PN_REQ); + bt_work_schedule(&dlc->retry_timeout_work, RFCOMM_RETRY_TIMEOUT); + bt_work_schedule(&dlc->tx_work, RFCOMM_RETRY_INTERVAL); + return 0; + } + + LOG_ERR("Failed to send pn on DLC %p (%d)", dlc, err); + return err; +} + +static int rfcomm_dlc_send_pn_rsp(struct bt_rfcomm_dlc *dlc) +{ + int err; + + if (dlc->session == NULL) + { + return -EINVAL; + } + + if (dlc->role != BT_RFCOMM_ROLE_ACCEPTOR) + { + return -EINVAL; + } + + if (dlc->state != BT_RFCOMM_STATE_CONFIG) + { + return -EINVAL; + } + + err = rfcomm_send_pn(dlc, BT_RFCOMM_MSG_RESP_CR); + if (err == 0) + { + k_work_cancel_delayable(&dlc->retry_timeout_work); + return 0; + } + + if (err == -ENOBUFS) + { + atomic_set_bit(&dlc->flags, RFCOMM_FLAG_PN_RSP); + bt_work_schedule(&dlc->retry_timeout_work, RFCOMM_RETRY_TIMEOUT); + bt_work_schedule(&dlc->tx_work, RFCOMM_RETRY_INTERVAL); + return 0; + } + + LOG_ERR("Failed to send pn rsp on DLC %p (%d)", dlc, err); + return err; +} + +static int rfcomm_dlc_start(struct bt_rfcomm_dlc *dlc) +{ + enum security_result result; + + LOG_DBG("dlc %p", dlc); + + result = rfcomm_dlc_security(dlc); + switch (result) + { + case RFCOMM_SECURITY_PASSED: + return rfcomm_dlc_send_pn_req(dlc); + case RFCOMM_SECURITY_PENDING: + dlc->state = BT_RFCOMM_STATE_SECURITY_PENDING; + break; + case RFCOMM_SECURITY_REJECT: + default: + return -EIO; + } + + return 0; +} + +static inline uint8_t rfcomm_dlc_get_available_credits( + struct bt_rfcomm_dlc *dlc) +{ + uint8_t max_credits = RFCOMM_DLC_CREDITS_MAX(dlc); + int32_t inprogress_credits = atomic_read(&dlc->rx_credit_inprogress); + + DEBUGASSERT(inprogress_credits >= 0); + DEBUGASSERT(inprogress_credits <= max_credits); + + /* The case only occurs when there is no inprogress receiving buffer, but + * the app calls bt_rfcomm_dlc_recv_complete(). + */ + + if (inprogress_credits < 0) + { + /* In this case, treat as if there is no inprogress receiving buffer */ + + LOG_ERR("rx_credit_inprogress of %p is underflow", dlc); + inprogress_credits = 0; + } + + return max_credits - inprogress_credits; +} + +static void rfcomm_session_send_fc_on_req(struct bt_rfcomm_session *session) +{ + int err; + + if (session->cfc != BT_RFCOMM_CFC_NOT_SUPPORTED) + { + return; + } + + if (atomic_test_and_set_bit(&session->flags, RFCOMM_SESSION_FLAG_FC_ON)) + { + return; + } + + err = rfcomm_send_fcon(session, BT_RFCOMM_MSG_CMD_CR); + if (err == 0) + { + k_work_cancel_delayable(&session->retry_timeout_work); + return; + } + + atomic_clear_bit(&session->flags, RFCOMM_SESSION_FLAG_FC_ON); + atomic_set_bit(&session->flags, RFCOMM_SESSION_FLAG_FC_ON_REQ); + LOG_ERR("Failed to send fcon command on session %p (%d)", session, err); + + if (err == -ENOBUFS) + { + /* Retry. */ + + bt_work_schedule(&session->retry_timeout_work, RFCOMM_RETRY_TIMEOUT); + bt_work_schedule(&session->retry_work, RFCOMM_RETRY_INTERVAL); + return; + } + + rfcomm_session_disconn_req(session); +} + +static void rfcomm_session_send_fc_on_rsp(struct bt_rfcomm_session *session) +{ + int err; + + if (session->cfc != BT_RFCOMM_CFC_NOT_SUPPORTED) + { + return; + } + + err = rfcomm_send_fcon(session, BT_RFCOMM_MSG_RESP_CR); + if (err == 0) + { + /* Give the sem so that it will unblock the waiting dlc threads + * of this session in sem_take(). + */ + + nxsem_post(&session->fc); + k_work_cancel_delayable(&session->retry_timeout_work); + return; + } + + atomic_set_bit(&session->flags, RFCOMM_SESSION_FLAG_FC_ON_RSP); + LOG_ERR("Failed to send fcon rsp on session %p (%d)", session, err); + + if (err == -ENOBUFS) + { + /* Retry. */ + + bt_work_schedule(&session->retry_timeout_work, RFCOMM_RETRY_TIMEOUT); + bt_work_schedule(&session->retry_work, RFCOMM_RETRY_INTERVAL); + return; + } + + rfcomm_session_disconn_req(session); +} + +static void rfcomm_session_send_fc_off_rsp(struct bt_rfcomm_session *session) +{ + int err; + + if (session->cfc != BT_RFCOMM_CFC_NOT_SUPPORTED) + { + return; + } + + err = rfcomm_send_fcoff(session, BT_RFCOMM_MSG_RESP_CR); + if (err == 0) + { + k_work_cancel_delayable(&session->retry_timeout_work); + return; + } + + atomic_set_bit(&session->flags, RFCOMM_SESSION_FLAG_FC_OFF_RSP); + LOG_ERR("Failed to send fcoff rsp on session %p (%d)", session, err); + + if (err == -ENOBUFS) + { + /* Retry. */ + + bt_work_schedule(&session->retry_timeout_work, RFCOMM_RETRY_TIMEOUT); + bt_work_schedule(&session->retry_work, RFCOMM_RETRY_INTERVAL); + return; + } + + rfcomm_session_disconn_req(session); +} + +static void rfcomm_dlc_fc_check_and_on(struct bt_rfcomm_dlc *dlc) +{ + int err; + + if (!atomic_test_and_clear_bit(&dlc->flags, RFCOMM_FLAG_MSC_FC_CLEAR_REQ)) + { + return; + } + + if (atomic_test_and_set_bit(&dlc->flags, RFCOMM_FLAG_MSC_FC_CLEARED)) + { + return; + } + + err = rfcomm_send_msc(dlc, BT_RFCOMM_MSG_CMD_CR, + BT_RFCOMM_DEFAULT_V24_SIG); + if (err == 0) + { + k_work_cancel_delayable(&dlc->retry_timeout_work); + return; + } + + if (err == -ENOBUFS) + { + atomic_set_bit(&dlc->flags, RFCOMM_FLAG_MSC_FC_CLEAR_REQ); + atomic_clear_bit(&dlc->flags, RFCOMM_FLAG_MSC_FC_CLEARED); + bt_work_schedule(&dlc->retry_timeout_work, RFCOMM_RETRY_TIMEOUT); + bt_work_schedule(&dlc->tx_work, RFCOMM_RETRY_INTERVAL); + } + else + { + LOG_ERR("Failed to send MSC with FC cleared on DLC %p (%d)", dlc, err); + rfcomm_dlc_close(dlc); + } +} + +static void rfcomm_dlc_update_credits(struct bt_rfcomm_dlc *dlc) +{ + uint8_t credits; + uint8_t credit_available; + int err; + + if (dlc->session->cfc == BT_RFCOMM_CFC_NOT_SUPPORTED) + { + /* FC Check and on if required */ + + rfcomm_dlc_fc_check_and_on(dlc); + return; + } + + LOG_DBG("dlc %p credits %u", dlc, dlc->rx_credit); + + /* Only give more credits if it went below the defined threshold */ + + if (dlc->rx_credit > RFCOMM_DLC_CREDITS_THRESHOLD(dlc)) + { + return; + } + + /* Restore credits */ + + credit_available = rfcomm_dlc_get_available_credits(dlc); + if (credit_available > dlc->rx_credit) + { + credits = credit_available - dlc->rx_credit; + dlc->rx_credit += credits; + err = rfcomm_send_credit(dlc, credits); + if (err == 0) + { + k_work_cancel_delayable(&dlc->retry_timeout_work); + return; + } + + LOG_ERR("Failed to send credit (%d)", err); + dlc->rx_credit -= credits; + + /* Retry. */ + + if (err == -ENOBUFS) + { + bt_work_schedule(&dlc->retry_timeout_work, RFCOMM_RETRY_TIMEOUT); + bt_work_schedule(&dlc->tx_work, RFCOMM_RETRY_INTERVAL); + } + else + { + /* Disconnected */ + + rfcomm_dlc_close(dlc); + } + } +} + +static void rfcomm_dlc_connected(struct bt_rfcomm_dlc *dlc) +{ + dlc->state = BT_RFCOMM_STATE_CONNECTED; + + atomic_set_bit(&dlc->flags, RFCOMM_FLAG_MSC_FC_CLEAR_REQ); + rfcomm_dlc_fc_check_and_on(dlc); + + if (dlc->session->cfc == BT_RFCOMM_CFC_UNKNOWN) + { + /* This means PN negotiation is not done for this session and + * can happen only for 1.0b device. + */ + + dlc->session->cfc = BT_RFCOMM_CFC_NOT_SUPPORTED; + } + + if (dlc->session->cfc == BT_RFCOMM_CFC_NOT_SUPPORTED) + { + LOG_DBG("CFC not supported %p", dlc); + rfcomm_session_send_fc_on_req(dlc->session); + + /* Use tx_credits as binary sem for MSC FC */ + + nxsem_init(&dlc->tx_credits, 0, 0); + } + + /* Cancel conn timer */ + + k_work_cancel_delayable(&dlc->rtx_work); + + k_fifo_init(&dlc->tx_queue); + + if (dlc->ops != NULL && dlc->ops->connected != NULL) + { + dlc->ops->connected(dlc); + } +} + +static int rfcomm_dlc_send_ua_for_sabm(struct bt_rfcomm_dlc *dlc) +{ + int err; + + if (dlc->session == NULL) + { + return -EINVAL; + } + + if (dlc->role != BT_RFCOMM_ROLE_ACCEPTOR) + { + return -EINVAL; + } + + if (dlc->state != BT_RFCOMM_STATE_INIT && + dlc->state != BT_RFCOMM_STATE_SECURITY_PENDING && + dlc->state != BT_RFCOMM_STATE_CONFIG) + { + return -EINVAL; + } + + err = rfcomm_send_ua(dlc->session, dlc->dlci); + if (err == 0) + { + k_work_cancel_delayable(&dlc->retry_timeout_work); + + /* Cancel idle timer if any */ + + k_work_cancel_delayable(&dlc->session->rtx_work); + rfcomm_dlc_connected(dlc); + return 0; + } + + if (err == -ENOBUFS) + { + atomic_set_bit(&dlc->flags, RFCOMM_FLAG_UA_FOR_SABM); + bt_work_schedule(&dlc->retry_timeout_work, RFCOMM_RETRY_TIMEOUT); + bt_work_schedule(&dlc->tx_work, RFCOMM_RETRY_INTERVAL); + return 0; + } + + LOG_ERR("Failed to send UA for SABM on DLC %p (%d)", dlc, err); + return err; +} + +static void rfcomm_dlc_process_disc(struct bt_rfcomm_dlc *dlc) +{ + struct bt_rfcomm_session *session; + int err; + + if (dlc->session == NULL) + { + return; + } + + session = dlc->session; + + err = rfcomm_send_ua(session, dlc->dlci); + if (err == 0) + { + k_work_cancel_delayable(&dlc->retry_timeout_work); + goto done; + } + + if (err == -ENOBUFS) + { + atomic_set_bit(&dlc->flags, RFCOMM_FLAG_UA_FOR_DISC); + bt_work_schedule(&dlc->retry_timeout_work, RFCOMM_RETRY_TIMEOUT); + bt_work_schedule(&dlc->tx_work, RFCOMM_RETRY_INTERVAL); + return; + } + + LOG_ERR("Failed to send UA for DISC on DLC %p (%d)", dlc, err); +done: + rfcomm_dlc_disconnect(dlc); + + if (list_is_empty(&session->dlcs)) + { + /* Start a session idle timer */ + + bt_work_reschedule(&session->rtx_work, RFCOMM_IDLE_TIMEOUT); + } +} + +static void rfcomm_dlc_reprocess_disc(struct bt_rfcomm_dlc *dlc) +{ + if (atomic_test_and_clear_bit(&dlc->flags, RFCOMM_FLAG_UA_FOR_DISC)) + { + rfcomm_dlc_process_disc(dlc); + } +} + +static void rfcomm_dlc_retry(struct bt_rfcomm_dlc *dlc) +{ + int err = 0; + + if (atomic_test_and_clear_bit(&dlc->flags, RFCOMM_FLAG_PN_RSP)) + { + err = rfcomm_dlc_send_pn_rsp(dlc); + if (err != 0) + { + goto failed; + } + } + + if (atomic_test_and_clear_bit(&dlc->flags, RFCOMM_FLAG_PN_REQ)) + { + err = rfcomm_dlc_send_pn_req(dlc); + if (err != 0) + { + goto failed; + } + } + + if (atomic_test_and_clear_bit(&dlc->flags, RFCOMM_FLAG_UA_FOR_SABM)) + { + err = rfcomm_dlc_send_ua_for_sabm(dlc); + if (err != 0) + { + goto failed; + } + } + + if (atomic_test_and_clear_bit(&dlc->flags, RFCOMM_FLAG_DM)) + { + err = rfcomm_dlc_send_dm(dlc); + if (err != 0) + { + goto failed; + } + } + +failed: + if (err != 0) + { + rfcomm_dlc_drop(dlc); + } +} + +static void rfcomm_dlc_tx_worker(struct k_work *work) +{ + struct bt_rfcomm_dlc *dlc = DLC_TX(work); + struct net_buf *buf; + + LOG_DBG("Work for dlc %p state %u", dlc, dlc->state); + + rfcomm_dlc_reprocess_disc(dlc); + + if (dlc->state < BT_RFCOMM_STATE_CONNECTED) + { + rfcomm_dlc_retry(dlc); + return; + } + + if (dlc->state == BT_RFCOMM_STATE_CONNECTED || + dlc->state == BT_RFCOMM_STATE_USER_DISCONNECT) + { + if (k_fifo_is_empty(&dlc->tx_queue) == true) + { + goto user_disconnect; + } + + if (rfcomm_check_fc(dlc) == false) + { + LOG_DBG("FC or credit not available"); + goto user_disconnect; + } + + buf = k_fifo_get(&dlc->tx_queue, 0); + LOG_DBG("Tx buf %p", buf); + if (rfcomm_send_cb(dlc->session, buf, rfcomm_sent, dlc) < 0) + { + /* This fails only if channel is disconnected */ + + dlc->state = BT_RFCOMM_STATE_DISCONNECTED; + bt_rfcomm_tx_destroy(dlc, buf); + LOG_ERR("Failed to send buffer, disconnected"); + goto disconnect; + } + + if (k_fifo_is_empty(&dlc->tx_queue) == false) + { + rfcomm_dlc_tx_trigger(dlc); + return; + } + +user_disconnect: + if (dlc->state == BT_RFCOMM_STATE_USER_DISCONNECT) + { + LOG_DBG("Process user disconnect"); + goto disconnect; + } + + /* Update RX credits */ + + rfcomm_dlc_update_credits(dlc); + + return; + } + +disconnect: + LOG_DBG("dlc %p disconnected - cleaning up", dlc); + + /* Give back any allocated buffers */ + + while ((buf = k_fifo_get(&dlc->tx_queue, 0))) + { + bt_rfcomm_tx_destroy(dlc, buf); + net_buf_unref(buf); + } + + if (dlc->state == BT_RFCOMM_STATE_USER_DISCONNECT) + { + dlc->state = BT_RFCOMM_STATE_DISCONNECTING; + } + + if (dlc->state == BT_RFCOMM_STATE_DISCONNECTING) + { + rfcomm_dlc_disconn(dlc); + } + else + { + rfcomm_dlc_destroy(dlc); + } + + LOG_DBG("dlc %p exiting", dlc); +} + +static void +rfcomm_session_send_ua_for_sabm(struct bt_rfcomm_session *session) +{ + int err; + + err = rfcomm_send_ua(session, 0); + if (err == 0) + { + session->state = BT_RFCOMM_STATE_CONNECTED; + k_work_cancel_delayable(&session->retry_timeout_work); + return; + } + + LOG_ERR("Failed to send UA for SABM to DLCI0 (%d)", err); + + if (err == -ENOBUFS) + { + atomic_set_bit(&session->flags, RFCOMM_SESSION_FLAG_UA_FOR_SABM); + bt_work_schedule(&session->retry_timeout_work, RFCOMM_RETRY_TIMEOUT); + bt_work_schedule(&session->retry_work, RFCOMM_RETRY_INTERVAL); + return; + } + + rfcomm_session_disconn_req(session); +} + +static void rfcomm_handle_sabm(struct bt_rfcomm_session *session, + uint8_t dlci) +{ + int err; + + if (dlci == 0) + { + rfcomm_session_send_ua_for_sabm(session); + } + else + { + struct bt_rfcomm_dlc *dlc; + enum security_result result; + + dlc = rfcomm_dlcs_lookup_dlci(session, dlci); + if (dlc == NULL) + { + dlc = rfcomm_dlc_accept(session, dlci); + } + + if (dlc == NULL) + { + err = rfcomm_send_dm(session, dlci); + if (err != 0) + { + LOG_ERR("Failed to send dm to %u (%d)", dlci, err); + } + + return; + } + + if (dlc->state == BT_RFCOMM_STATE_CONNECTED) + { + err = rfcomm_send_ua(dlc->session, dlc->dlci); + if (err != 0) + { + LOG_ERR("Failed to send UA for SABM on DLC %p (%d)", dlc, err); + } + + /* Just ignore the error if the DLC has been established. */ + + return; + } + + result = rfcomm_dlc_security(dlc); + switch (result) + { + case RFCOMM_SECURITY_PENDING: + dlc->state = BT_RFCOMM_STATE_SECURITY_PENDING; + return; + case RFCOMM_SECURITY_PASSED: + break; + case RFCOMM_SECURITY_REJECT: + default: + dlc->state = BT_RFCOMM_STATE_SECURITY_FAILED; + err = rfcomm_dlc_send_dm(dlc); + if (err != 0) + { + rfcomm_dlc_drop(dlc); + LOG_ERR("Failed to send dm on DLC %p (%d)", dlc, err); + } + + return; + } + + err = rfcomm_dlc_send_ua_for_sabm(dlc); + if (err != 0) + { + rfcomm_dlc_close(dlc); + } + } +} + +static void rfcomm_session_control_channel_connected( + struct bt_rfcomm_session *session) +{ + struct bt_rfcomm_dlc *dlc; + int err; + + list_for_every_container(&session->dlcs, dlc, _node) + { + if (dlc->role == BT_RFCOMM_ROLE_INITIATOR && + dlc->state == BT_RFCOMM_STATE_INIT) + { + err = rfcomm_dlc_start(dlc); + if (err != 0) + { + LOG_ERR("Failed to start DLC %p (%d)", dlc, err); + rfcomm_dlc_drop(dlc); + } + } + } +} + +static void rfcomm_handle_ua(struct bt_rfcomm_session *session, uint8_t dlci) +{ + struct bt_rfcomm_dlc *dlc; + + if (!dlci) + { + switch (session->state) + { + case BT_RFCOMM_STATE_CONNECTING: + session->state = BT_RFCOMM_STATE_CONNECTED; + rfcomm_session_control_channel_connected(session); + + /* Disconnect session if there is no dlcs left */ + + rfcomm_session_disconnect(session); + break; + case BT_RFCOMM_STATE_DISCONNECTING: + + /* Cancel disc timer */ + + k_work_cancel_delayable(&session->rtx_work); + rfcomm_session_disconn_req(session); + break; + default: + break; + } + } + else + { + dlc = rfcomm_dlcs_lookup_dlci(session, dlci); + if (dlc == NULL) + { + return; + } + + switch (dlc->state) + { + case BT_RFCOMM_STATE_CONNECTING: + rfcomm_dlc_connected(dlc); + break; + case BT_RFCOMM_STATE_DISCONNECTING: + rfcomm_dlc_drop(dlc); + rfcomm_session_disconnect(session); + break; + default: + break; + } + } +} + +static void rfcomm_handle_dm(struct bt_rfcomm_session *session, uint8_t dlci) +{ + struct bt_rfcomm_dlc *dlc; + + LOG_DBG("dlci %d", dlci); + + dlc = rfcomm_dlcs_remove_dlci(session, dlci); + if (dlc == NULL) + { + return; + } + + rfcomm_dlc_disconnect(dlc); + rfcomm_session_disconnect(session); +} + +static void rfcomm_handle_msc(struct bt_rfcomm_session *session, + struct net_buf *buf, uint8_t cr) +{ + struct bt_rfcomm_msc *msc; + struct bt_rfcomm_dlc *dlc; + uint8_t dlci; + int err; + + if (buf->len < sizeof(*msc)) + { + LOG_WRN("Malformed MSC command %u < %zu", buf->len, sizeof(*msc)); + return; + } + + msc = net_buf_pull_mem(buf, sizeof(*msc)); + dlci = BT_RFCOMM_GET_DLCI(msc->dlci); + + LOG_DBG("dlci %d", dlci); + + dlc = rfcomm_dlcs_lookup_dlci(session, dlci); + if (dlc == NULL) + { + return; + } + + if (cr == BT_RFCOMM_MSG_RESP_CR) + { + return; + } + + if (dlc->session->cfc == BT_RFCOMM_CFC_NOT_SUPPORTED) + { + /* Only FC bit affects the flow on RFCOMM level */ + + if (BT_RFCOMM_V24_SIGNALS_GET_FC(msc->v24_signal)) + { + /* If FC bit is 1 the device is unable to accept frames. + * Take the semaphore with timeout 0 so that dlc thread will be + * blocked when it tries sem_take before sending the data. 0 + * timeout will make sure that RX thread will not be blocked while + * taking the semaphore. + */ + + nxsem_trywait(&dlc->tx_credits); + } + else + { + /* Give the sem so that it will unblock the waiting dlc + * thread in sem_take(). + */ + + nxsem_post(&dlc->tx_credits); + rfcomm_dlc_tx_trigger(dlc); + } + } + + err = rfcomm_send_msc(dlc, BT_RFCOMM_MSG_RESP_CR, msc->v24_signal); + if (err != 0) + { + LOG_ERR("Failed to send msc on DLC %p (%d)", dlc, err); + } +} + +static void rfcomm_handle_rls(struct bt_rfcomm_session *session, + struct net_buf *buf, uint8_t cr) +{ + struct bt_rfcomm_rls *rls; + uint8_t dlci; + struct bt_rfcomm_dlc *dlc; + int err; + + if (buf->len < sizeof(*rls)) + { + LOG_WRN("Malformed RLS command %u < %zu", buf->len, sizeof(*rls)); + return; + } + + rls = net_buf_pull_mem(buf, sizeof(*rls)); + dlci = BT_RFCOMM_GET_DLCI(rls->dlci); + + LOG_DBG("dlci %d", dlci); + + if (!cr) + { + /* Ignore if its a response */ + + return; + } + + dlc = rfcomm_dlcs_lookup_dlci(session, dlci); + if (dlc == NULL) + { + return; + } + + /* As per the ETSI same line status has to returned in the response */ + + err = rfcomm_send_rls(dlc, BT_RFCOMM_MSG_RESP_CR, rls->line_status); + if (err != 0) + { + LOG_ERR("Failed to send rls on DLC %p (%d)", dlc, err); + } +} + +static void rfcomm_handle_rpn(struct bt_rfcomm_session *session, + struct net_buf *buf, uint8_t cr) +{ + struct bt_rfcomm_rpn rsp; + uint8_t dlci; + uint8_t data_bits; + uint8_t stop_bits; + uint8_t parity_bits; + int err; + + if (!cr) + { + /* Ignore if its a response */ + + return; + } + + if (buf->len == sizeof(struct bt_rfcomm_rpn)) + { + struct bt_rfcomm_rpn *rpn; + + rpn = net_buf_pull_mem(buf, sizeof(*rpn)); + dlci = BT_RFCOMM_GET_DLCI(rpn->dlci); + + LOG_DBG("Set RPN setting: dlci %d", dlci); + + rsp = *rpn; + + /* Accept all the values proposed by the sender */ + + rsp.param_mask = sys_cpu_to_le16(BT_RFCOMM_RPN_PARAM_MASK_ALL); + err = rfcomm_send_rpn(session, BT_RFCOMM_MSG_RESP_CR, &rsp); + if (err != 0) + { + LOG_ERR("Failed to send rpn setting rsp (%d)", err); + } + + return; + } + + if (buf->len != sizeof(dlci)) + { + LOG_WRN("Invalid RPN command"); + return; + } + + dlci = BT_RFCOMM_GET_DLCI(net_buf_pull_u8(buf)); + + LOG_DBG("Get RPN setting: dlci %d", dlci); + + /* If only one value byte then current port settings has to be returned + * We will send default values + */ + + rsp.dlci = BT_RFCOMM_SET_ADDR(dlci, 1); + rsp.baud_rate = BT_RFCOMM_RPN_BAUD_RATE_9600; + rsp.flow_control = BT_RFCOMM_RPN_FLOW_NONE; + rsp.xoff_char = BT_RFCOMM_RPN_XOFF_CHAR; + rsp.xon_char = BT_RFCOMM_RPN_XON_CHAR; + data_bits = BT_RFCOMM_RPN_DATA_BITS_8; + stop_bits = BT_RFCOMM_RPN_STOP_BITS_1; + parity_bits = BT_RFCOMM_RPN_PARITY_NONE; + rsp.line_settings = BT_RFCOMM_SET_LINE_SETTINGS(data_bits, stop_bits, + parity_bits); + rsp.param_mask = sys_cpu_to_le16(BT_RFCOMM_RPN_PARAM_MASK_ALL); + + err = rfcomm_send_rpn(session, BT_RFCOMM_MSG_RESP_CR, &rsp); + if (err != 0) + { + LOG_ERR("Failed to send rpn getting rsp (%d)", err); + } +} + +static void rfcomm_handle_test(struct bt_rfcomm_session *session, + struct net_buf *buf, uint8_t cr) +{ + int err; + + if (!cr) + { + return; + } + + if (buf->len > UINT8_MAX) + { + LOG_WRN("TEST command too long %u > %u", buf->len, UINT8_MAX); + return; + } + + err = rfcomm_send_test(session, BT_RFCOMM_MSG_RESP_CR, buf->data, + buf->len); + if (err != 0) + { + LOG_ERR("Failed to send test rsp on session %p (%d)", session, err); + } +} + +static void rfcomm_dlc_connect(struct bt_rfcomm_session *session, + struct bt_rfcomm_dlc *dlc) +{ + int err; + uint8_t state; + + state = dlc->state; + dlc->state = BT_RFCOMM_STATE_CONNECTING; + err = rfcomm_send_sabm(session, dlc->dlci); + if (err == 0) + { + return; + } + + LOG_ERR("Failed to send sabm on DLC %p (%d)", dlc, err); + + dlc->state = state; + err = rfcomm_dlc_close(dlc); + if (err != 0) + { + LOG_ERR("Failed to close DLC %p state %u (%d)", dlc, state, err); + } +} + +static void rfcomm_handle_pn(struct bt_rfcomm_session *session, + struct net_buf *buf, uint8_t cr) +{ + struct bt_rfcomm_pn *pn; + struct bt_rfcomm_dlc *dlc; + int err; + + if (buf->len < sizeof(*pn)) + { + LOG_WRN("Malformed PN command %u < %zu", buf->len, sizeof(*pn)); + return; + } + + pn = net_buf_pull_mem(buf, sizeof(*pn)); + dlc = rfcomm_dlcs_lookup_dlci(session, pn->dlci); + if (dlc == NULL) + { + /* Ignore if it is a response */ + + if (!cr) + { + return; + } + + if (!BT_RFCOMM_CHECK_MTU(pn->mtu)) + { + LOG_ERR("Invalid mtu %d", pn->mtu); + err = rfcomm_send_dm(session, pn->dlci); + if (err != 0) + { + LOG_ERR("Failed to send dm to %u (%d)", pn->dlci, err); + } + + return; + } + + dlc = rfcomm_dlc_accept(session, pn->dlci); + if (dlc == NULL) + { + err = rfcomm_send_dm(session, pn->dlci); + if (err != 0) + { + LOG_ERR("Failed to send dm to %u (%d)", pn->dlci, err); + } + + return; + } + + LOG_DBG("Incoming connection accepted dlc %p", dlc); + + dlc->mtu = MIN(dlc->mtu, sys_le16_to_cpu(pn->mtu)); + + if (pn->flow_ctrl == BT_RFCOMM_PN_CFC_CMD) + { + if (session->cfc == BT_RFCOMM_CFC_UNKNOWN) + { + session->cfc = BT_RFCOMM_CFC_SUPPORTED; + } + + nxsem_init(&dlc->tx_credits, 0, 0); + rfcomm_dlc_tx_give_credits(dlc, pn->credits); + } + else + { + session->cfc = BT_RFCOMM_CFC_NOT_SUPPORTED; + } + + dlc->state = BT_RFCOMM_STATE_CONFIG; + err = rfcomm_dlc_send_pn_rsp(dlc); + if (err != 0) + { + LOG_ERR("Failed to send pn rsp on DLC %p (%d)", dlc, err); + rfcomm_dlc_drop(dlc); + } + + /* Cancel idle timer if any */ + + k_work_cancel_delayable(&session->rtx_work); + } + else + { + /* If its a command */ + + if (cr) + { + if (!BT_RFCOMM_CHECK_MTU(pn->mtu)) + { + LOG_ERR("Invalid mtu %d", pn->mtu); + rfcomm_dlc_close(dlc); + return; + } + + dlc->mtu = MIN(dlc->mtu, sys_le16_to_cpu(pn->mtu)); + err = rfcomm_send_pn(dlc, BT_RFCOMM_MSG_RESP_CR); + if (err != 0) + { + LOG_ERR("Failed to send pn rsp on DLC %p (%d)", dlc, err); + } + } + else + { + if (dlc->state != BT_RFCOMM_STATE_CONFIG) + { + return; + } + + dlc->mtu = MIN(dlc->mtu, sys_le16_to_cpu(pn->mtu)); + if (pn->flow_ctrl == BT_RFCOMM_PN_CFC_RESP) + { + if (session->cfc == BT_RFCOMM_CFC_UNKNOWN) + { + session->cfc = BT_RFCOMM_CFC_SUPPORTED; + } + + nxsem_init(&dlc->tx_credits, 0, 0); + rfcomm_dlc_tx_give_credits(dlc, pn->credits); + } + else + { + session->cfc = BT_RFCOMM_CFC_NOT_SUPPORTED; + } + + rfcomm_dlc_connect(session, dlc); + } + } +} + +static void +rfcomm_session_send_ua_for_disc(struct bt_rfcomm_session *session) +{ + int err; + + err = rfcomm_send_ua(session, 0); + if (err == 0) + { + goto disconnected; + } + + LOG_ERR("Failed to send ua to DLCI0 (%d)", err); + + if (err == -ENOBUFS) + { + atomic_set_bit(&session->flags, RFCOMM_SESSION_FLAG_UA_FOR_DISC); + bt_work_schedule(&session->retry_timeout_work, RFCOMM_RETRY_TIMEOUT); + bt_work_schedule(&session->retry_work, RFCOMM_RETRY_INTERVAL); + return; + } + +disconnected: + k_work_cancel_delayable(&session->retry_timeout_work); + rfcomm_session_disconnected(session); +} + +static void rfcomm_handle_disc(struct bt_rfcomm_session *session, + uint8_t dlci) +{ + struct bt_rfcomm_dlc *dlc; + int err; + + LOG_DBG("Dlci %d", dlci); + + if (dlci) + { + dlc = rfcomm_dlcs_remove_dlci(session, dlci); + if (dlc == NULL) + { + err = rfcomm_send_dm(session, dlci); + if (err != 0) + { + LOG_ERR("Failed to send dm to %u (%d)", dlci, err); + } + + return; + } + + rfcomm_dlc_process_disc(dlc); + } + else + { + /* Cancel idle timer */ + + k_work_cancel_delayable(&session->rtx_work); + rfcomm_session_send_ua_for_disc(session); + } +} + +static void rfcomm_handle_msg(struct bt_rfcomm_session *session, + struct net_buf *buf) +{ + struct bt_rfcomm_msg_hdr *hdr; + uint8_t msg_type; + uint8_t len; + uint8_t cr; + int err; + + if (buf->len < sizeof(*hdr)) + { + LOG_ERR("Too small RFCOMM message"); + return; + } + + hdr = net_buf_pull_mem(buf, sizeof(*hdr)); + msg_type = BT_RFCOMM_GET_MSG_TYPE(hdr->type); + cr = BT_RFCOMM_GET_MSG_CR(hdr->type); + len = BT_RFCOMM_GET_LEN(hdr->len); + + LOG_DBG("msg type %x cr %x", msg_type, cr); + + if (buf->len < len) + { + LOG_WRN("Invalid message length %u < %u", buf->len, len); + return; + } + + switch (msg_type) + { + case BT_RFCOMM_PN: + rfcomm_handle_pn(session, buf, cr); + break; + case BT_RFCOMM_MSC: + rfcomm_handle_msc(session, buf, cr); + break; + case BT_RFCOMM_RLS: + rfcomm_handle_rls(session, buf, cr); + break; + case BT_RFCOMM_RPN: + rfcomm_handle_rpn(session, buf, cr); + break; + case BT_RFCOMM_TEST: + rfcomm_handle_test(session, buf, cr); + break; + case BT_RFCOMM_FCON: + if (session->cfc == BT_RFCOMM_CFC_SUPPORTED) + { + LOG_ERR("FCON received when CFC is supported "); + return; + } + + if (!cr) + { + break; + } + + atomic_clear_bit(&session->flags, RFCOMM_SESSION_FLAG_FC_OFF_RSP); + rfcomm_session_send_fc_on_rsp(session); + rfcomm_dlcs_tx_trigger(session); + break; + case BT_RFCOMM_FCOFF: + if (session->cfc == BT_RFCOMM_CFC_SUPPORTED) + { + LOG_ERR("FCOFF received when CFC is supported "); + return; + } + + if (!cr) + { + break; + } + + atomic_clear_bit(&session->flags, RFCOMM_SESSION_FLAG_FC_ON_RSP); + /* Take the semaphore with timeout 0 so that all the + * dlc threads in this session will be blocked when it tries sem_take + * before sending the data. 0 timeout will make sure that RX thread + * will not be blocked while taking the semaphore. + */ + + nxsem_trywait(&session->fc); + rfcomm_session_send_fc_off_rsp(session); + break; + default: + LOG_WRN("Unknown/Unsupported RFCOMM Msg type 0x%02x", msg_type); + err = rfcomm_send_nsc(session, hdr->type); + if (err != 0) + { + LOG_ERR("Failed to send nsc rsp on session %p (%d)", session, + err); + } + break; + } +} + +static int rfcomm_handle_recv_inprogress(struct bt_rfcomm_dlc *dlc) +{ + int err = 0; + int32_t old_rx_credit_inprogress; + uint8_t v24_signals = BT_RFCOMM_DEFAULT_V24_SIG; + + old_rx_credit_inprogress = atomic_add(&dlc->rx_credit_inprogress, 1); + + if (dlc->session->cfc == BT_RFCOMM_CFC_SUPPORTED) + { + return 0; + } + + if (old_rx_credit_inprogress != 0) + { + LOG_DBG("Only set FC bit at the first time for DLC %p", dlc); + return 0; + } + + if (atomic_test_and_clear_bit(&dlc->flags, RFCOMM_FLAG_MSC_FC_CLEARED)) + { + v24_signals = BT_RFCOMM_V24_SIGNALS_SET_FC(v24_signals); + err = rfcomm_send_msc(dlc, BT_RFCOMM_MSG_CMD_CR, v24_signals); + if (err != 0) + { + atomic_set_bit(&dlc->flags, RFCOMM_FLAG_MSC_FC_CLEARED); + LOG_ERR("Failed to send MSC with FC set (%d)", err); + } + } + + return err; +} + +static void rfcomm_handle_data( + struct bt_rfcomm_session *session, struct net_buf *buf, uint8_t dlci, + uint8_t pf) +{ + struct bt_rfcomm_dlc *dlc; + int err = 0; + + LOG_DBG("dlci %d, pf %d", dlci, pf); + + dlc = rfcomm_dlcs_lookup_dlci(session, dlci); + if (dlc == NULL) + { + LOG_ERR("Data recvd in non existing DLC"); + err = rfcomm_send_dm(session, dlci); + if (err != 0) + { + LOG_ERR("Failed to send dm to %u (%d)", dlci, err); + } + + return; + } + + LOG_DBG("dlc %p rx credit %u", dlc, dlc->rx_credit); + + if (dlc->state != BT_RFCOMM_STATE_CONNECTED) + { + return; + } + + if (pf == BT_RFCOMM_PF_UIH_CREDIT) + { + if (buf->len == 0) + { + LOG_WRN("Data recvd is invalid"); + return; + } + + rfcomm_dlc_tx_give_credits(dlc, net_buf_pull_u8(buf)); + } + + if (buf->len == 0) + { + LOG_DBG("Credit frame only, no payload"); + return; + } + + if (dlc->session->cfc == BT_RFCOMM_CFC_SUPPORTED && dlc->rx_credit == 0) + { + LOG_ERR("Data recvd when rx credit is 0"); + rfcomm_dlc_close(dlc); + return; + } + + if (dlc->ops != NULL && dlc->ops->recv != NULL) + { + err = dlc->ops->recv(dlc, net_buf_ref(buf)); + if (err != -EINPROGRESS) + { + net_buf_unref(buf); + } + } + + if (err != 0 && err != -EINPROGRESS) + { + LOG_ERR("DLC data recvd error (%d)", err); + rfcomm_dlc_close(dlc); + return; + } + + if (err == -EINPROGRESS) + { + err = rfcomm_handle_recv_inprogress(dlc); + if (err != 0) + { + /* The buffer should not be released here, because the rfcomm does + * not own the buffer reference any more. + */ + + rfcomm_dlc_close(dlc); + return; + } + } + + if (dlc->session->cfc == BT_RFCOMM_CFC_SUPPORTED) + { + dlc->rx_credit--; + } + + rfcomm_dlc_update_credits(dlc); +} + +int bt_rfcomm_dlc_send(struct bt_rfcomm_dlc *dlc, struct net_buf *buf) +{ + uint8_t fcs; + uint8_t cr; + int sem_count; + + if (!buf) + { + return -EINVAL; + } + + (void)nxsem_get_value(&dlc->tx_credits, &sem_count); + LOG_DBG("dlc %p tx credit %d", dlc, sem_count); + + if (dlc->state != BT_RFCOMM_STATE_CONNECTED) + { + return -ENOTCONN; + } + + if (buf->len > dlc->mtu) + { + return -EMSGSIZE; + } + + /* length */ + + if (buf->len > BT_RFCOMM_MAX_LEN_8) + { + /* Length is 2 byte */ + + net_buf_push_le16(buf, BT_RFCOMM_SET_LEN_16(buf->len)); + } + else + { + net_buf_push_u8(buf, BT_RFCOMM_SET_LEN_8(buf->len)); + } + + /* control */ + + net_buf_push_u8(buf, BT_RFCOMM_SET_CTRL(BT_RFCOMM_UIH, + BT_RFCOMM_PF_UIH_NO_CREDIT)); + + /* address */ + + cr = BT_RFCOMM_UIH_CR(dlc->session->role); + net_buf_push_u8(buf, BT_RFCOMM_SET_ADDR(dlc->dlci, cr)); + + fcs = rfcomm_calc_fcs(BT_RFCOMM_FCS_LEN_UIH, buf->data); + net_buf_add_u8(buf, fcs); + + k_fifo_put(&dlc->tx_queue, buf); + rfcomm_dlc_tx_trigger(dlc); + + return buf->len; +} + +static int rfcomm_recv(struct bt_l2cap_chan *chan, struct net_buf *buf) +{ + struct bt_rfcomm_session *session = RFCOMM_SESSION(chan); + struct bt_rfcomm_hdr *hdr = (void *)buf->data; + struct bt_rfcomm_hdr_ext *hdr_ext = (void *)buf->data; + uint8_t dlci; + uint8_t frame_type; + uint8_t fcs; + uint8_t fcs_len; + uint16_t msg_len; + uint16_t hdr_len; + + /* Need to consider FCS also */ + + if (buf->len < (sizeof(*hdr) + sizeof(fcs))) + { + LOG_ERR("Too small RFCOMM Frame"); + return 0; + } + + dlci = BT_RFCOMM_GET_DLCI(hdr->address); + frame_type = BT_RFCOMM_GET_FRAME_TYPE(hdr->control); + + LOG_DBG("session %p dlci %x type %x", session, dlci, frame_type); + + if (BT_RFCOMM_LEN_EXTENDED(hdr->length)) + { + msg_len = BT_RFCOMM_GET_LEN_EXTENDED(hdr_ext->hdr.length, + hdr_ext->second_length); + hdr_len = sizeof(*hdr_ext); + } + else + { + msg_len = BT_RFCOMM_GET_LEN(hdr->length); + hdr_len = sizeof(*hdr); + } + + if (buf->len < (hdr_len + msg_len + sizeof(fcs))) + { + LOG_ERR("Too small RFCOMM information (%u < %zu)", buf->len, + hdr_len + msg_len + sizeof(fcs)); + return 0; + } + + fcs_len = (frame_type == BT_RFCOMM_UIH) ? BT_RFCOMM_FCS_LEN_UIH : hdr_len; + fcs = *(net_buf_tail(buf) - sizeof(fcs)); + if (!rfcomm_check_fcs(fcs_len, buf->data, fcs)) + { + LOG_ERR("FCS check failed"); + return 0; + } + + net_buf_pull(buf, hdr_len); + net_buf_remove_mem(buf, sizeof(fcs)); + + switch (frame_type) + { + case BT_RFCOMM_SABM: + rfcomm_handle_sabm(session, dlci); + break; + case BT_RFCOMM_UIH: + if (!dlci) + { + rfcomm_handle_msg(session, buf); + } + else + { + rfcomm_handle_data(session, buf, dlci, + BT_RFCOMM_GET_PF(hdr->control)); + } + break; + case BT_RFCOMM_DISC: + rfcomm_handle_disc(session, dlci); + break; + case BT_RFCOMM_UA: + rfcomm_handle_ua(session, dlci); + break; + case BT_RFCOMM_DM: + rfcomm_handle_dm(session, dlci); + break; + default: + LOG_WRN("Unknown/Unsupported RFCOMM Frame type 0x%02x", frame_type); + break; + } + + return 0; +} + +static void rfcomm_encrypt_change(struct bt_l2cap_chan *chan, + uint8_t hci_status) +{ + struct bt_rfcomm_session *session = RFCOMM_SESSION(chan); + struct bt_conn *conn = chan->conn; + struct bt_rfcomm_dlc *dlc; + struct bt_rfcomm_dlc *tmp; + int err; + + LOG_DBG("session %p status 0x%02x encr 0x%02x", session, hci_status, + conn->encrypt); + + list_for_every_container_safe(&session->dlcs, dlc, tmp, _node) + { + if (dlc->state != BT_RFCOMM_STATE_SECURITY_PENDING) + { + continue; + } + + if (hci_status || !conn->encrypt || + conn->sec_level < dlc->required_sec_level) + { + rfcomm_dlc_close(dlc); + continue; + } + + if (dlc->role == BT_RFCOMM_ROLE_ACCEPTOR) + { + err = rfcomm_dlc_send_ua_for_sabm(dlc); + } + else + { + err = rfcomm_dlc_send_pn_req(dlc); + } + + if (err != 0) + { + rfcomm_dlc_close(dlc); + } + } +} + +static void rfcomm_session_rtx_timeout(struct k_work *work) +{ + struct bt_rfcomm_session *session = SESSION_RTX(work); + + if (session->br_chan.chan.conn == NULL) + { + /* The underlying connection is already gone; there is nothing + * left for the timeout to act on. + */ + + return; + } + + LOG_WRN("session %p state %d timeout", session, session->state); + + switch (session->state) + { + case BT_RFCOMM_STATE_CONNECTED: + rfcomm_session_disconnect(session); + break; + case BT_RFCOMM_STATE_DISCONNECTING: + session->state = BT_RFCOMM_STATE_DISCONNECTED; + if (bt_l2cap_chan_disconnect(&session->br_chan.chan) < 0) + { + LOG_ERR("Failed to disconnect RFCOMM session %p", session); + session->state = BT_RFCOMM_STATE_IDLE; + } + break; + } +} + +static void rfcomm_session_retry_handler(struct k_work *work) +{ + struct bt_rfcomm_session *session = SESSION_RETRY(work); + + if (session->br_chan.chan.conn == NULL) + { + /* The underlying connection is already gone; there is nothing + * left for the timeout to act on. + */ + + return; + } + + if (atomic_test_and_clear_bit(&session->flags, + RFCOMM_SESSION_FLAG_FC_ON_REQ)) + { + rfcomm_session_send_fc_on_req(session); + } + + if (atomic_test_and_clear_bit(&session->flags, + RFCOMM_SESSION_FLAG_FC_ON_RSP)) + { + rfcomm_session_send_fc_on_rsp(session); + } + + if (atomic_test_and_clear_bit(&session->flags, + RFCOMM_SESSION_FLAG_FC_OFF_RSP)) + { + rfcomm_session_send_fc_off_rsp(session); + } + + if (atomic_test_and_clear_bit(&session->flags, + RFCOMM_SESSION_FLAG_UA_FOR_SABM)) + { + rfcomm_session_send_ua_for_sabm(session); + } + + if (atomic_test_and_clear_bit(&session->flags, + RFCOMM_SESSION_FLAG_UA_FOR_DISC)) + { + rfcomm_session_send_ua_for_disc(session); + } + + if (atomic_test_and_clear_bit(&session->flags, + RFCOMM_SESSION_FLAG_DISCONN_REQ)) + { + rfcomm_session_disconn_req(session); + } +} + +static void rfcomm_session_retry_timeout(struct k_work *work) +{ + struct bt_rfcomm_session *session = SESSION_RETRY_TIMEOUT(work); + + if (session->br_chan.chan.conn == NULL) + { + /* The underlying connection is already gone; there is nothing + * left for the timeout to act on. + */ + + return; + } + + bt_work_reschedule(&session->rtx_work, 0); +} + +static struct bt_rfcomm_session *rfcomm_session_new(bt_rfcomm_role_t role) +{ + static const struct bt_l2cap_chan_ops ops = + { + .connected = rfcomm_connected, + .disconnected = rfcomm_disconnected, + .recv = rfcomm_recv, + .encrypt_change = rfcomm_encrypt_change, + }; + + ARRAY_FOR_EACH(bt_rfcomm_pool, i) + { + struct bt_rfcomm_session *session = &bt_rfcomm_pool[i]; + + if (session->br_chan.chan.conn != NULL) + { + continue; + } + + LOG_DBG("session %p initialized", session); + + session->br_chan.chan.ops = &ops; + session->br_chan.rx.mtu = CONFIG_BT_RFCOMM_L2CAP_MTU; + session->state = BT_RFCOMM_STATE_INIT; + session->role = role; + session->cfc = BT_RFCOMM_CFC_UNKNOWN; + list_initialize(&session->dlcs); + k_work_init_delayable(&session->rtx_work, rfcomm_session_rtx_timeout); + k_work_init_delayable(&session->retry_work, + rfcomm_session_retry_handler); + k_work_init_delayable(&session->retry_timeout_work, + rfcomm_session_retry_timeout); + nxsem_init(&session->fc, 0, 0); + atomic_set(&session->flags, 0); + return session; + } + + return NULL; +} + +int bt_rfcomm_dlc_connect(struct bt_conn *conn, + struct bt_rfcomm_dlc *dlc, uint8_t channel) +{ + struct bt_rfcomm_session *session; + struct bt_l2cap_chan *chan; + uint8_t dlci; + int ret; + + LOG_DBG("conn %p dlc %p channel %d", conn, dlc, channel); + + if (!dlc) + { + return -EINVAL; + } + + if (!conn || conn->state != BT_CONN_CONNECTED) + { + return -ENOTCONN; + } + + if (channel < RFCOMM_CHANNEL_START || channel > RFCOMM_CHANNEL_END) + { + return -EINVAL; + } + + if (!BT_RFCOMM_CHECK_MTU(dlc->mtu)) + { + return -EINVAL; + } + + session = rfcomm_sessions_lookup_bt_conn(conn); + if (!session) + { + session = rfcomm_session_new(BT_RFCOMM_ROLE_INITIATOR); + if (!session) + { + return -ENOMEM; + } + } + + dlci = BT_RFCOMM_DLCI(session->role, channel); + + if (rfcomm_dlcs_lookup_dlci(session, dlci) != NULL) + { + return -EBUSY; + } + + rfcomm_dlc_init(dlc, session, dlci, BT_RFCOMM_ROLE_INITIATOR); + + switch (session->state) + { + case BT_RFCOMM_STATE_INIT: + if (session->role == BT_RFCOMM_ROLE_ACCEPTOR) + { + /* There is an ongoing incoming conn */ + + break; + } + + chan = &session->br_chan.chan; + BR_CHAN(chan)->required_sec_level = dlc->required_sec_level; + ret = bt_l2cap_chan_connect(conn, chan, BT_L2CAP_PSM_RFCOMM); + if (ret < 0) + { + session->state = BT_RFCOMM_STATE_IDLE; + goto fail; + } + + session->state = BT_RFCOMM_STATE_CONNECTING; + break; + case BT_RFCOMM_STATE_CONNECTING: + break; + case BT_RFCOMM_STATE_CONNECTED: + ret = rfcomm_dlc_start(dlc); + if (ret < 0) + { + goto fail; + } + + /* Cancel idle timer if any */ + + k_work_cancel_delayable(&session->rtx_work); + break; + default: + LOG_ERR("Invalid session state %d", session->state); + ret = -EINVAL; + goto fail; + } + + return 0; + +fail: + (void)rfcomm_dlcs_remove_dlci(session, dlc->dlci); + dlc->state = BT_RFCOMM_STATE_IDLE; + dlc->session = NULL; + return ret; +} + +int bt_rfcomm_dlc_disconnect(struct bt_rfcomm_dlc *dlc) +{ + LOG_DBG("dlc %p", dlc); + + if (!dlc) + { + return -EINVAL; + } + + if (dlc->state == BT_RFCOMM_STATE_CONNECTED) + { + /* This is to handle user initiated disconnect to send pending + * bufs in the queue before disconnecting Queue a dummy buffer (in case + * if queue is empty) to wake up and stop the tx thread. + */ + + dlc->state = BT_RFCOMM_STATE_USER_DISCONNECT; + rfcomm_dlc_tx_trigger(dlc); + bt_work_reschedule(&dlc->rtx_work, RFCOMM_DISC_TIMEOUT); + + return 0; + } + + return rfcomm_dlc_close(dlc); +} + +static int rfcomm_accept( + struct bt_conn *conn, struct bt_l2cap_server *server, + struct bt_l2cap_chan **chan) +{ + struct bt_rfcomm_session *session; + + LOG_DBG("conn %p", conn); + + session = rfcomm_session_new(BT_RFCOMM_ROLE_ACCEPTOR); + if (session) + { + *chan = &session->br_chan.chan; + return 0; + } + + LOG_ERR("No available RFCOMM context for conn %p", conn); + + return -ENOMEM; +} + +void bt_rfcomm_init(void) +{ + __maybe_unused int err; + + static bool initialized; + static struct bt_l2cap_server server = + { + .psm = BT_L2CAP_PSM_RFCOMM, + .accept = rfcomm_accept, + .sec_level = BT_SECURITY_L1, + }; + + if (initialized) + { + return; + } + + err = bt_l2cap_br_server_register(&server); + if ((err != 0) && (err != -EEXIST)) + { + LOG_ERR("Failed to register L2CAP server for RFCOMM (err %d)", err); + return; + } + + initialized = true; +} + +int bt_rfcomm_dlc_recv_complete(struct bt_rfcomm_dlc *dlc, + struct net_buf *buf) +{ + struct bt_rfcomm_session *session; + int32_t old_rx_credit_inprogress; + + if (dlc == NULL || buf == NULL) + { + return -EINVAL; + } + + net_buf_unref(buf); + + session = dlc->session; + if (session == NULL) + { + LOG_ERR("Invalid session of dlc %p", dlc); + return -ENOTCONN; + } + + if (dlc->state != BT_RFCOMM_STATE_CONNECTED) + { + LOG_ERR("Invalid state %u for dlc %p", dlc->state, dlc); + return -ENOTCONN; + } + + if (session->cfc != BT_RFCOMM_CFC_NOT_SUPPORTED && + session->cfc != BT_RFCOMM_CFC_SUPPORTED) + { + LOG_ERR("Invalid CFC state UNKNOWN for dlc %p", dlc); + return -EINVAL; + } + + old_rx_credit_inprogress = atomic_sub(&dlc->rx_credit_inprogress, 1); + if (old_rx_credit_inprogress <= 0) + { + atomic_add(&dlc->rx_credit_inprogress, 1); + LOG_ERR("rx_credit_inprogress underflow for dlc %p", dlc); + return -EINVAL; + } + + if (session->cfc == BT_RFCOMM_CFC_NOT_SUPPORTED) + { + if (old_rx_credit_inprogress > 1) + { + LOG_DBG("Only clear FC bit at the last time for DLC %p", dlc); + return 0; + } + + atomic_set_bit(&dlc->flags, RFCOMM_FLAG_MSC_FC_CLEAR_REQ); + } + + rfcomm_dlc_tx_trigger(dlc); + + return 0; +} diff --git a/libs/zblue/host/classic/rfcomm_internal.h b/libs/zblue/host/classic/rfcomm_internal.h new file mode 100644 index 0000000000000..1d2ac237ecf17 --- /dev/null +++ b/libs/zblue/host/classic/rfcomm_internal.h @@ -0,0 +1,276 @@ +/**************************************************************************** + * libs/zblue/host/classic/rfcomm_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Internal APIs for Bluetooth RFCOMM handling. + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +typedef enum +{ + BT_RFCOMM_CFC_UNKNOWN, + BT_RFCOMM_CFC_NOT_SUPPORTED, + BT_RFCOMM_CFC_SUPPORTED, +} +__packed bt_rfcomm_cfc_t; + +/* RFCOMM signalling connection specific context */ + +struct bt_rfcomm_session +{ + /* L2CAP channel this context is associated with */ + + struct bt_l2cap_br_chan br_chan; + + /* Response Timeout eXpired (RTX) timer */ + + struct k_work_delayable rtx_work; + + /* Retry work */ + + struct k_work_delayable retry_work; + + /* Retry timeout expired timer */ + + struct k_work_delayable retry_timeout_work; + + /* Binary sem for aggregate fc */ + + sem_t fc; + + /* DLC list */ + + struct list_node dlcs; + uint16_t mtu; + uint8_t state; + bt_rfcomm_role_t role; + bt_rfcomm_cfc_t cfc; + atomic_t flags; +}; + +enum +{ + BT_RFCOMM_STATE_IDLE, + BT_RFCOMM_STATE_INIT, + BT_RFCOMM_STATE_SECURITY_FAILED, + BT_RFCOMM_STATE_SECURITY_PENDING, + BT_RFCOMM_STATE_CONFIG, + BT_RFCOMM_STATE_CONNECTING, + BT_RFCOMM_STATE_CONNECTED, + BT_RFCOMM_STATE_USER_DISCONNECT, + BT_RFCOMM_STATE_DISCONNECTING, + BT_RFCOMM_STATE_DISCONNECTED, +}; + +struct bt_rfcomm_hdr +{ + uint8_t address; + uint8_t control; + uint8_t length; +} __packed; + +struct bt_rfcomm_hdr_ext +{ + struct bt_rfcomm_hdr hdr; + uint8_t second_length; +} __packed; + +#define BT_RFCOMM_SABM 0x2f +#define BT_RFCOMM_UA 0x63 +#define BT_RFCOMM_UIH 0xef + +struct bt_rfcomm_msg_hdr +{ + uint8_t type; + uint8_t len; +} __packed; + +#define BT_RFCOMM_PN 0x20 +struct bt_rfcomm_pn +{ + uint8_t dlci; + uint8_t flow_ctrl; + uint8_t priority; + uint8_t ack_timer; + uint16_t mtu; + uint8_t max_retrans; + uint8_t credits; +} __packed; + +/** @brief Bitmask for the FC (Flow Control) field in the RFCOMM MSC V.24 + * signals octet. + */ +#define BT_RFCOMM_V24_SIGNALS_FC_MASK GENMASK(1, 1) + +/** + * @brief Extract the FC (Flow Control) field from the RFCOMM MSC V.24 + * signals octet. + * + * @param _v V.24 signals octet value. + * + * @return FC field value (0 = ready to accept frames, 1 = unable to accept + * frames). + */ +#define BT_RFCOMM_V24_SIGNALS_GET_FC(_v) FIELD_GET(BT_RFCOMM_V24_SIGNALS_FC_MASK, _v) + +/** + * @brief Clear the FC (Flow Control) field from the RFCOMM MSC V.24 signals + * octet. + * + * @param _v V.24 signals octet value. + * + * @return V.24 signals octet with FC field cleared. + */ +#define BT_RFCOMM_V24_SIGNALS_CLR_FC(_v) ((_v) & (~BT_RFCOMM_V24_SIGNALS_FC_MASK)) + +/** + * @brief Set the FC (Flow Control) field to the RFCOMM MSC V.24 signals + * octet. + * + * @param _v V.24 signals octet value. + * + * @return V.24 signals octet with FC field set. + */ +#define BT_RFCOMM_V24_SIGNALS_SET_FC(_v) \ + (BT_RFCOMM_V24_SIGNALS_CLR_FC(_v) | FIELD_PREP(BT_RFCOMM_V24_SIGNALS_FC_MASK, 1)) + +#define BT_RFCOMM_MSC 0x38 +struct bt_rfcomm_msc +{ + uint8_t dlci; + uint8_t v24_signal; +} __packed; + +#define BT_RFCOMM_DISC 0x43 +#define BT_RFCOMM_DM 0x0f + +#define BT_RFCOMM_RLS 0x14 +struct bt_rfcomm_rls +{ + uint8_t dlci; + uint8_t line_status; +} __packed; + +#define BT_RFCOMM_RPN 0x24 + +#define BT_RFCOMM_TEST 0x08 +#define BT_RFCOMM_NSC 0x04 + +#define BT_RFCOMM_FCON 0x28 +#define BT_RFCOMM_FCOFF 0x18 + +/* DV = 1 IC = 0 RTR = 1 RTC = 1 FC = 0 EXT = 0 */ +#define BT_RFCOMM_DEFAULT_V24_SIG 0x8d + +#define BT_RFCOMM_SIG_MIN_MTU 23 +#define BT_RFCOMM_SIG_MAX_MTU 32767 + +#define BT_RFCOMM_CHECK_MTU(mtu) \ + (!!((mtu) >= BT_RFCOMM_SIG_MIN_MTU && (mtu) <= BT_RFCOMM_SIG_MAX_MTU)) + +#define BT_RFCOMM_GET_DLCI(addr) (((addr) & 0xfc) >> 2) +#define BT_RFCOMM_GET_FRAME_TYPE(ctrl) ((ctrl) & 0xef) +#define BT_RFCOMM_GET_MSG_TYPE(type) (((type) & 0xfc) >> 2) +#define BT_RFCOMM_GET_MSG_CR(type) (((type) & 0x02) >> 1) +#define BT_RFCOMM_GET_LEN(len) (((len) & 0xfe) >> 1) +#define BT_RFCOMM_GET_LEN_EXTENDED(first, second) ((((first) & 0xfe) >> 1) | ((second) << 7)) +#define BT_RFCOMM_GET_CHANNEL(dlci) ((dlci) >> 1) +#define BT_RFCOMM_GET_PF(ctrl) (((ctrl) & 0x10) >> 4) + +#define BT_RFCOMM_SET_ADDR(dlci, cr) ((((dlci) & 0x3f) << 2) | ((cr) << 1) | 0x01) +#define BT_RFCOMM_SET_CTRL(type, pf) (((type) & 0xef) | ((pf) << 4)) +#define BT_RFCOMM_SET_LEN_8(len) (((len) << 1) | 1) +#define BT_RFCOMM_SET_LEN_16(len) ((len) << 1) +#define BT_RFCOMM_SET_MSG_TYPE(type, cr) (((type) << 2) | (cr << 1) | 0x01) + +#define BT_RFCOMM_LEN_EXTENDED(len) (!((len) & 0x01)) + +/* For CR in UIH Packet header + * Initiating station have the C/R bit set to 1 and those sent by the + * responding station have the C/R bit set to 0 + */ +#define BT_RFCOMM_UIH_CR(role) ((role) == BT_RFCOMM_ROLE_INITIATOR) + +/* For CR in Non UIH Packet header + * Command Initiator --> Responder 1 Responder --> Initiator 0 Response + * Initiator --> Responder 0 Responder --> Initiator 1 + */ +#define BT_RFCOMM_CMD_CR(role) ((role) == BT_RFCOMM_ROLE_INITIATOR) +#define BT_RFCOMM_RESP_CR(role) ((role) == BT_RFCOMM_ROLE_ACCEPTOR) + +/* For CR in MSG header + * If the C/R bit is set to 1 the message is a command, if it is set to 0 the + * message is a response. + */ +#define BT_RFCOMM_MSG_CMD_CR 1 +#define BT_RFCOMM_MSG_RESP_CR 0 + +#define BT_RFCOMM_DLCI(role, channel) \ + ((((channel) & 0x1f) << 1) | ((role) == BT_RFCOMM_ROLE_ACCEPTOR)) + +/* Excluding ext bit */ +#define BT_RFCOMM_MAX_LEN_8 127 + +/* Length can be 2 bytes depending on data size */ +#define BT_RFCOMM_HDR_SIZE (sizeof(struct bt_rfcomm_hdr) + 1) + +#define BT_RFCOMM_FCS_LEN_UIH 2 +#define BT_RFCOMM_FCS_LEN_NON_UIH 3 + +/* For non UIH packets + * The P bit set to 1 shall be used to solicit a response frame with the F + * bit set to 1 from the other station. + */ +#define BT_RFCOMM_PF_NON_UIH 1 + +/* For UIH packets + * Both stations set the P-bit to 0 If credit based flow control is used, If + * P/F is 1 then one credit byte will be there after control in the frame + * else no credit byte. + */ +#define BT_RFCOMM_PF_UIH 0 +#define BT_RFCOMM_PF_UIH_CREDIT 1 +#define BT_RFCOMM_PF_UIH_NO_CREDIT 0 + +#define BT_RFCOMM_PN_CFC_CMD 0xf0 +#define BT_RFCOMM_PN_CFC_RESP 0xe0 + +/* Initialize RFCOMM signal layer */ + +void bt_rfcomm_init(void); + +enum +{ + RFCOMM_FLAG_MSC_FC_CLEARED = 0, + RFCOMM_FLAG_MSC_FC_CLEAR_REQ = 1, + RFCOMM_FLAG_PN_REQ = 2, + RFCOMM_FLAG_PN_RSP = 3, + RFCOMM_FLAG_UA_FOR_SABM = 4, + RFCOMM_FLAG_UA_FOR_DISC = 5, + RFCOMM_FLAG_DM = 6, +}; + +enum +{ + RFCOMM_SESSION_FLAG_FC_ON = 0, + RFCOMM_SESSION_FLAG_FC_ON_REQ = 1, + RFCOMM_SESSION_FLAG_FC_ON_RSP = 2, + RFCOMM_SESSION_FLAG_FC_OFF_RSP = 3, + RFCOMM_SESSION_FLAG_UA_FOR_SABM = 4, + RFCOMM_SESSION_FLAG_UA_FOR_DISC = 5, + RFCOMM_SESSION_FLAG_DISCONN_REQ = 6, +}; diff --git a/libs/zblue/host/classic/sco.c b/libs/zblue/host/classic/sco.c new file mode 100644 index 0000000000000..858826359839c --- /dev/null +++ b/libs/zblue/host/classic/sco.c @@ -0,0 +1,642 @@ +/**************************************************************************** + * libs/zblue/host/classic/sco.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* sco.c - Bluetooth sco handling */ + +/* Copyright (c) 2015-2016 Intel Corporation + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include + +#include +#include +#include + +#include + +#include +#include +#include "br.h" +#include +#include "sco_internal.h" + +#define LOG_LEVEL CONFIG_BT_CONN_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_sco); + +struct bt_sco_server *sco_server; + +#define SCO_CHAN(_sco) ((_sco)->sco.chan); + +static struct list_node sco_conn_cbs = LIST_INITIAL_VALUE(sco_conn_cbs); +static struct list_node sco_hci_cbs = LIST_INITIAL_VALUE(sco_hci_cbs); + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bt_sco_server_register(struct bt_sco_server *server) +{ + if (!server) + { + LOG_DBG("Invalid parameter: server %p", server); + return -EINVAL; + } + + if (sco_server) + { + return -EADDRINUSE; + } + + if (!server->accept) + { + return -EINVAL; + } + + if (server->sec_level > BT_SECURITY_L3) + { + return -EINVAL; + } + + LOG_DBG("%p", server); + + sco_server = server; + + return 0; +} + +int bt_sco_server_unregister(struct bt_sco_server *server) +{ + if (!server) + { + LOG_DBG("Invalid parameter: server %p", server); + return -EINVAL; + } + + if (sco_server != server) + { + return -EINVAL; + } + + sco_server = NULL; + + return 0; +} + +static void notify_connected(struct bt_conn *conn) +{ + struct bt_sco_conn_cb *callback; + + list_for_every_container(&sco_conn_cbs, callback, _node) + { + if (callback->connected) + { + callback->connected(conn, conn->err); + } + } + + STRUCT_SECTION_FOREACH(bt_sco_conn_cb, cb) + { + if (cb->connected) + { + cb->connected(conn, conn->err); + } + } +} + +static void notify_disconnected(struct bt_conn *conn) +{ + struct bt_sco_conn_cb *callback; + + list_for_every_container(&sco_conn_cbs, callback, _node) + { + if (callback->disconnected) + { + callback->disconnected(conn, conn->err); + } + } + + STRUCT_SECTION_FOREACH(bt_sco_conn_cb, cb) + { + if (cb->disconnected) + { + cb->disconnected(conn, conn->err); + } + } +} + +static void notify_setup_sco_cmd(struct bt_conn *conn, + struct bt_hci_cp_setup_sync_conn *cp) +{ + struct bt_sco_hci_cb *callback; + + list_for_every_container(&sco_hci_cbs, callback, _node) + { + if (callback->setup != NULL) + { + callback->setup(conn, cp); + } + } + + STRUCT_SECTION_FOREACH(bt_sco_hci_cb, cb) + { + if (cb->setup != NULL) + { + cb->setup(conn, cp); + } + } +} + +static void notify_accept_sco_req_cmd( + struct bt_hci_cp_accept_sync_conn_req *cp) +{ + struct bt_sco_hci_cb *callback; + + list_for_every_container(&sco_hci_cbs, callback, _node) + { + if (callback->accept != NULL) + { + callback->accept(cp); + } + } + + STRUCT_SECTION_FOREACH(bt_sco_hci_cb, cb) + { + if (cb->accept != NULL) + { + cb->accept(cp); + } + } +} + +void bt_sco_connected(struct bt_conn *sco) +{ + struct bt_sco_chan *chan; + + if (sco == NULL || sco->type != BT_CONN_TYPE_SCO) + { + LOG_ERR("Invalid parameters: sco %p sco->type %u", sco, + sco ? sco->type : 0); + return; + } + + LOG_DBG("%p", sco); + + notify_connected(sco); + + chan = SCO_CHAN(sco); + if (chan == NULL) + { + LOG_ERR("Could not lookup chan from connected SCO"); + return; + } + + bt_sco_chan_set_state(chan, BT_SCO_STATE_CONNECTED); + + if (chan->ops && chan->ops->connected) + { + chan->ops->connected(chan); + } +} + +void bt_sco_disconnected(struct bt_conn *sco) +{ + struct bt_sco_chan *chan; + + if (sco == NULL || sco->type != BT_CONN_TYPE_SCO) + { + LOG_ERR("Invalid parameters: sco %p sco->type %u", sco, + sco ? sco->type : 0); + return; + } + + LOG_DBG("%p", sco); + + notify_disconnected(sco); + + bt_sco_cleanup_acl(sco); + + chan = SCO_CHAN(sco); + if (chan == NULL) + { + LOG_ERR("Could not lookup chan from connected SCO"); + return; + } + + bt_sco_chan_set_state(chan, BT_SCO_STATE_DISCONNECTED); + + if (chan->ops && chan->ops->disconnected) + { + chan->ops->disconnected(chan, sco->err); + } + + chan->sco = NULL; +} + +static uint8_t sco_server_check_security(struct bt_conn *conn) +{ + if (IS_ENABLED(CONFIG_BT_CONN_DISABLE_SECURITY)) + { + return BT_HCI_ERR_SUCCESS; + } + + if (conn->sec_level >= sco_server->sec_level) + { + return BT_HCI_ERR_SUCCESS; + } + + return BT_HCI_ERR_INSUFFICIENT_SECURITY; +} + +#if defined(CONFIG_BT_CONN_LOG_LEVEL_DBG) +const char *bt_sco_chan_state_str(uint8_t state) +{ + switch (state) + { + case BT_SCO_STATE_DISCONNECTED: + return "disconnected"; + case BT_SCO_STATE_CONNECTING: + return "connecting"; + case BT_SCO_STATE_ENCRYPT_PENDING: + return "encryption pending"; + case BT_SCO_STATE_CONNECTED: + return "connected"; + case BT_SCO_STATE_DISCONNECTING: + return "disconnecting"; + default: + return "unknown"; + } +} + +void bt_sco_chan_set_state_debug( + struct bt_sco_chan *chan, enum bt_sco_state state, const char *func, + int line) +{ + LOG_DBG("chan %p sco %p %s -> %s", chan, chan->sco, + bt_sco_chan_state_str(chan->state), + bt_sco_chan_state_str(state)); + + /* check transitions validness */ + + switch (state) + { + case BT_SCO_STATE_DISCONNECTED: + + /* regardless of old state always allows this states */ + + break; + case BT_SCO_STATE_ENCRYPT_PENDING: + __fallthrough; + case BT_SCO_STATE_CONNECTING: + if (chan->state != BT_SCO_STATE_DISCONNECTED) + { + LOG_WRN("%s()%d: invalid transition", func, line); + } + break; + case BT_SCO_STATE_CONNECTED: + if (chan->state != BT_SCO_STATE_CONNECTING) + { + LOG_WRN("%s()%d: invalid transition", func, line); + } + break; + case BT_SCO_STATE_DISCONNECTING: + if (chan->state != BT_SCO_STATE_CONNECTING && + chan->state != BT_SCO_STATE_CONNECTED) + { + LOG_WRN("%s()%d: invalid transition", func, line); + } + break; + default: + LOG_ERR("%s()%d: unknown (%u) state was set", func, line, state); + return; + } + + chan->state = state; +} +#else +void bt_sco_chan_set_state(struct bt_sco_chan *chan, enum bt_sco_state state) +{ + chan->state = state; +} +#endif /* CONFIG_BT_CONN_LOG_LEVEL_DBG */ + +static void bt_sco_chan_add(struct bt_conn *sco, struct bt_sco_chan *chan) +{ + /* Attach SCO channel to the connection */ + + chan->sco = sco; + sco->sco.chan = chan; + + LOG_DBG("sco %p chan %p", sco, chan); +} + +static int sco_accept(struct bt_conn *acl, struct bt_conn *sco) +{ + struct bt_sco_accept_info accept_info; + struct bt_sco_chan *chan; + int err; + + if (!sco || sco->type != BT_CONN_TYPE_SCO) + { + LOG_ERR("Invalid parameters: sco %p sco->type %u", sco, + sco ? sco->type : 0); + return -EINVAL; + } + + LOG_DBG("%p", sco); + + accept_info.acl = acl; + memcpy(accept_info.dev_class, sco->sco.dev_class, + sizeof(accept_info.dev_class)); + accept_info.link_type = sco->sco.link_type; + + err = sco_server->accept(&accept_info, &chan); + if (err < 0) + { + LOG_ERR("Server failed to accept: %d", err); + return err; + } + + if (chan->ops == NULL) + { + LOG_ERR("invalid parameter: chan %p chan->ops %p", chan, chan->ops); + return -EINVAL; + } + + bt_sco_chan_add(sco, chan); + bt_sco_chan_set_state(chan, BT_SCO_STATE_CONNECTING); + + return 0; +} + +static int accept_sco_conn(const bt_addr_t *bdaddr, struct bt_conn *sco_conn) +{ + struct bt_hci_cp_accept_sync_conn_req *cp; + struct net_buf *buf; + int err; + + err = sco_accept(sco_conn->sco.acl, sco_conn); + if (err) + { + return err; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + bt_addr_copy(&cp->bdaddr, bdaddr); + cp->pkt_type = sys_cpu_to_le16(sco_conn->sco.pkt_type); + cp->tx_bandwidth = sys_cpu_to_le32(0x00001f40); + cp->rx_bandwidth = sys_cpu_to_le32(0x00001f40); + cp->max_latency = sys_cpu_to_le16(BT_HCI_SCO_MAX_LATENCY_DEFAULT); + cp->retrans_effort = BT_HCI_SCO_RETRANS_EFFORT_DEFAULT; + cp->content_format = sys_cpu_to_le16(sco_conn->sco.chan->voice_setting); + + notify_accept_sco_req_cmd(cp); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_ACCEPT_SYNC_CONN_REQ, buf, NULL); + if (err) + { + return err; + } + + return 0; +} + +uint8_t bt_esco_conn_req(struct bt_hci_evt_conn_request *evt) +{ + struct bt_conn *sco_conn; + uint8_t sec_err; + + if (sco_server == NULL) + { + LOG_ERR("No SCO server registered"); + return BT_HCI_ERR_UNSPECIFIED; + } + + sco_conn = bt_conn_add_sco(&evt->bdaddr, evt->link_type); + if (!sco_conn) + { + return BT_HCI_ERR_INSUFFICIENT_RESOURCES; + } + + sec_err = sco_server_check_security(sco_conn->sco.acl); + if (BT_HCI_ERR_SUCCESS != sec_err) + { + LOG_DBG("Insufficient security %u", sec_err); + bt_sco_cleanup(sco_conn); + return sec_err; + } + + memcpy(sco_conn->sco.dev_class, evt->dev_class, + sizeof(sco_conn->sco.dev_class)); + sco_conn->sco.link_type = evt->link_type; + + if (accept_sco_conn(&evt->bdaddr, sco_conn)) + { + struct bt_sco_chan *chan = sco_conn->sco.chan; + + LOG_ERR("Error accepting connection from %s", + bt_addr_str(&evt->bdaddr)); + + /* If the server accepted the connection request, a channel was + * attached in sco_accept(). Detach it before releasing the connection + * object so that the channel does not keep a dangling reference to it. + * The disconnected callback is not called since the connection was + * never established. + */ + + if (chan != NULL) + { + bt_sco_chan_set_state(chan, BT_SCO_STATE_DISCONNECTED); + chan->sco = NULL; + } + + bt_sco_cleanup(sco_conn); + return BT_HCI_ERR_UNSPECIFIED; + } + + sco_conn->role = BT_HCI_ROLE_PERIPHERAL; + bt_conn_set_state(sco_conn, BT_CONN_INITIATING); + bt_conn_unref(sco_conn); + + return BT_HCI_ERR_SUCCESS; +} + +void bt_sco_cleanup_acl(struct bt_conn *sco) +{ + LOG_DBG("%p", sco); + + bt_conn_drop(&sco->sco.acl); +} + +static int sco_setup_sync_conn(struct bt_conn *sco_conn) +{ + struct net_buf *buf; + struct bt_hci_cp_setup_sync_conn *cp; + int err; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + + (void)memset(cp, 0, sizeof(*cp)); + + LOG_DBG("handle : %x", sco_conn->sco.acl->handle); + + cp->handle = sys_cpu_to_le16(sco_conn->sco.acl->handle); + cp->pkt_type = sys_cpu_to_le16(sco_conn->sco.pkt_type); + cp->tx_bandwidth = sys_cpu_to_le32(0x00001f40); + cp->rx_bandwidth = sys_cpu_to_le32(0x00001f40); + cp->max_latency = sys_cpu_to_le16(BT_HCI_SCO_MAX_LATENCY_DEFAULT); + cp->retrans_effort = BT_HCI_SCO_RETRANS_EFFORT_DEFAULT; + cp->content_format = sys_cpu_to_le16(sco_conn->sco.chan->voice_setting); + + notify_setup_sco_cmd(sco_conn->sco.acl, cp); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_SETUP_SYNC_CONN, buf, NULL); + if (err < 0) + { + return err; + } + + return 0; +} + +struct bt_conn *bt_conn_create_sco(const bt_addr_t *peer, + struct bt_sco_chan *chan) +{ + struct bt_conn *sco_conn; + int link_type; + + sco_conn = bt_conn_lookup_addr_sco(peer); + if (sco_conn) + { + switch (sco_conn->state) + { + case BT_CONN_INITIATING: + case BT_CONN_CONNECTED: + return sco_conn; + default: + bt_conn_unref(sco_conn); + return NULL; + } + } + + if (BT_FEAT_LMP_ESCO_CAPABLE(bt_dev.features)) + { + link_type = BT_HCI_ESCO; + } + else + { + link_type = BT_HCI_SCO; + } + + sco_conn = bt_conn_add_sco(peer, link_type); + if (!sco_conn) + { + return NULL; + } + + sco_conn->sco.link_type = link_type; + + bt_sco_chan_add(sco_conn, chan); + bt_conn_set_state(chan->sco, BT_CONN_INITIATING); + bt_sco_chan_set_state(chan, BT_SCO_STATE_CONNECTING); + + if (sco_setup_sync_conn(sco_conn) < 0) + { + bt_conn_set_state(chan->sco, BT_CONN_DISCONNECTED); + bt_sco_chan_set_state(chan, BT_SCO_STATE_DISCONNECTED); + bt_sco_cleanup(sco_conn); + return NULL; + } + + return sco_conn; +} + +int bt_sco_conn_cb_register(struct bt_sco_conn_cb *cb) +{ + if (cb == NULL) + { + return -EINVAL; + } + + if (list_find(&sco_conn_cbs, &cb->_node, NULL)) + { + return -EEXIST; + } + + list_add_tail(&sco_conn_cbs, &cb->_node); + + return 0; +} + +int bt_sco_conn_cb_unregister(struct bt_sco_conn_cb *cb) +{ + if (cb == NULL) + { + return -EINVAL; + } + + if (!list_find_and_remove(&sco_conn_cbs, &cb->_node)) + { + return -ENOENT; + } + + return 0; +} + +int bt_sco_hci_cb_register(struct bt_sco_hci_cb *cb) +{ + if (cb == NULL) + { + return -EINVAL; + } + + if (list_find(&sco_hci_cbs, &cb->_node, NULL)) + { + return -EEXIST; + } + + list_add_tail(&sco_hci_cbs, &cb->_node); + + return 0; +} + +int bt_sco_hci_cb_unregister(struct bt_sco_hci_cb *cb) +{ + if (cb == NULL) + { + return -EINVAL; + } + + if (!list_find_and_remove(&sco_hci_cbs, &cb->_node)) + { + return -ENOENT; + } + + return 0; +} diff --git a/libs/zblue/host/classic/sco_internal.h b/libs/zblue/host/classic/sco_internal.h new file mode 100644 index 0000000000000..502bf0a247f22 --- /dev/null +++ b/libs/zblue/host/classic/sco_internal.h @@ -0,0 +1,353 @@ +/**************************************************************************** + * libs/zblue/host/classic/sco_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Internal APIs for Bluetooth SCO handling. + */ + +/* Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** @brief Life-span states of SCO channel. Used only by internal APIs + * dealing with setting channel to proper state depending on operational + * context. + */ + +enum bt_sco_state +{ + /** Channel disconnected */ + + BT_SCO_STATE_DISCONNECTED, + + /** Channel is pending ACL encryption before connecting */ + + BT_SCO_STATE_ENCRYPT_PENDING, + + /** Channel in connecting state */ + + BT_SCO_STATE_CONNECTING, + + /** Channel ready for upper layer traffic on it */ + + BT_SCO_STATE_CONNECTED, + + /** Channel in disconnecting state */ + + BT_SCO_STATE_DISCONNECTING, +}; + +struct bt_sco_chan; +struct bt_sco_chan_ops +{ + /** @brief Channel connected callback + * + * If this callback is provided it will be called whenever the connection + * completes. + * + * @param chan The channel that has been connected + */ + + void (*connected)(struct bt_sco_chan *chan); + + /** @brief Channel disconnected callback + * + * If this callback is provided it will be called whenever the channel is + * disconnected, including when a connection gets rejected or when setting + * security fails. + * + * @param chan The channel that has been Disconnected + * @param reason BT_HCI_ERR_* reason for the disconnection. + */ + + void (*disconnected)(struct bt_sco_chan *chan, uint8_t reason); +}; + +struct bt_sco_chan +{ + struct bt_conn *sco; + + /** Channel operations reference */ + + const struct bt_sco_chan_ops *ops; + + /** Voice setting for the connection */ + + uint8_t voice_setting; + + enum bt_sco_state state; +}; + +/** @brief Initiate an SCO connection to a remote device. + * + * Allows initiate new SCO link to remote peer using its address. + * + * The caller gets a new reference to the connection object which must be + * released with bt_conn_unref() once done using the object. + * + * @param peer Remote address. + * @param chan sco chan object. + * + * @return Valid connection object on success or NULL otherwise. + */ + +struct bt_conn *bt_conn_create_sco(const bt_addr_t *peer, + struct bt_sco_chan *chan); + +/** @brief SCO Accept Info Structure */ + +struct bt_sco_accept_info +{ + /** The ACL connection that is requesting authorization */ + + struct bt_conn *acl; + + /** class code of peer device */ + + uint8_t dev_class[3]; + + /** link type */ + + uint8_t link_type; +}; + +/** @brief SCO Server structure. */ + +struct bt_sco_server +{ + /** Required minimum security level. Only available when + * @kconfig{CONFIG_BT_SMP} is enabled. + */ + + bt_security_t sec_level; + /** @brief Server accept callback + * + * This callback is called whenever a new incoming connection requires + * authorization. + * + * @param info The SCO accept information structure + * @param chan Pointer to receive the allocated channel + * + * @return 0 in case of success or negative value in case of error. + */ + + int (*accept)(const struct bt_sco_accept_info *info, + struct bt_sco_chan **chan); +}; + +/** @brief Register SCO server. + * + * Register SCO server, each new connection is authorized using the accept() + * callback which in case of success shall allocate the channel structure to + * be used by the new connection. + * + * @param server Server structure. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_sco_server_register(struct bt_sco_server *server); + +/** @brief Unregister SCO server. + * + * Unregister previously registered SCO server. + * + * @param server Server structure. + * + * @return 0 in case of success or negative value in case of error. + */ + +int bt_sco_server_unregister(struct bt_sco_server *server); + +/** @brief sco channel connected. + * + * sco channel connected + * + * @param sco SCO connection object. + */ + +void bt_sco_connected(struct bt_conn *sco); + +/** @brief sco channel disconnected. + * + * sco channel disconnected + * + * @param sco SCO connection object. + */ + +void bt_sco_disconnected(struct bt_conn *sco); + +uint8_t bt_esco_conn_req(struct bt_hci_evt_conn_request *evt); + +#if defined(CONFIG_BT_CONN_LOG_LEVEL_DBG) +void bt_sco_chan_set_state_debug( + struct bt_sco_chan *chan, enum bt_sco_state state, const char *func, + int line); +#define bt_sco_chan_set_state(_chan, _state) \ + bt_sco_chan_set_state_debug(_chan, _state, __func__, __LINE__) +#else +void bt_sco_chan_set_state(struct bt_sco_chan *chan, + enum bt_sco_state state); +#endif /* CONFIG_BT_CONN_LOG_LEVEL_DBG */ + +/** @brief SCO connection callback structure. + * + * This structure is used for tracking the state of a SCO connection. It is + * registered with the help of the bt_sco_conn_cb_register() API. It's + * permissible to register multiple instances of this @ref bt_sco_conn_cb + * type, in case different modules of an application are interested in + * tracking the connection state. If a callback is not of interest for an + * instance, it may be set to NULL and will as a consequence not be used for + * that instance. + */ + +struct bt_sco_conn_cb +{ + /** @brief A new SCO connection has been established. + * + * This callback notifies the application of a new connection. In case the + * err parameter is non-zero it means that the connection establishment + * failed. + * + * @param conn New SCO connection object. + * @param err HCI error. Zero for success, non-zero otherwise. + */ + + void (*connected)(struct bt_conn *conn, uint8_t err); + + /** @brief A SCO connection has been disconnected. + * + * This callback notifies the application that a SCO connection has been + * disconnected. When this callback is called the stack still has one + * reference to the connection object. + * + * @param conn SCO connection object. + * @param reason BT_HCI_ERR_* reason for the disconnection. + */ + + void (*disconnected)(struct bt_conn *conn, uint8_t reason); + + /** @internal Internally used field for list handling */ + + struct list_node _node; +}; + +/** @brief Register SCO connection callbacks. + * + * Register callbacks to monitor the state of SCO connections. + * + * @param cb Callback struct. Must point to memory that remains valid. + * + * @retval 0 Success. + * @retval -EINVAL If @p cb is NULL. + * @retval -EEXIST if @p cb was already registered. + */ + +int bt_sco_conn_cb_register(struct bt_sco_conn_cb *cb); + +/** + * @brief Unregister SCO connection callbacks. + * + * Unregister the state of SCO connections callbacks. + * + * @param cb Callback struct point to memory that remains valid. + * + * @retval 0 Success. + * @retval -EINVAL If @p cb is NULL. + * @retval -ENOENT if @p cb was not registered. + */ + +int bt_sco_conn_cb_unregister(struct bt_sco_conn_cb *cb); + +/** + * @brief Register a callback structure for connection events. + * + * @param _name Name of callback structure. + */ +#define BT_SCO_CONN_CB_DEFINE(_name) \ + static const STRUCT_SECTION_ITERABLE(bt_sco_conn_cb, _CONCAT(bt_sco_conn_cb_, _name)) + +/** + * @brief SCO HCI callback structure for handling SCO connection events + * + * This structure defines callback functions that are invoked during SCO + * (Synchronous Connection-Oriented) connection establishment process. It + * allows upper layer protocols to customize HCI command parameters before + * they are sent to the controller. + * + * The callbacks are typically used by audio profiles like HFP/HSP to + * configure codec parameters, packet types, and other connection-specific + * settings based on negotiated audio codec and quality requirements. + * + * @note Callbacks are optional and may be NULL if default behavior is + * desired + */ + +struct bt_sco_hci_cb +{ + /* @brief Setup callback for outgoing SCO connection + * + * Called before sending HCI_Setup_Synchronous_Connection command to + * monitor the HCI activity of SCO connections. + * + * @param acl_conn Pointer to the underlying ACL connection. + * @param cp Pointer to HCI setup synchronous connection command + * parameters. + */ + + void (*setup)(struct bt_conn *acl_conn, + struct bt_hci_cp_setup_sync_conn *cp); + + /* @brief Accept callback for incoming SCO connection + * + * Called before sending HCI_Accept_Synchronous_Connection_Request command + * to monitor the HCI activity of SCO connections. + * + * @param cp Pointer to HCI accept synchronous connection request command + * parameters. + */ + + void (*accept)(struct bt_hci_cp_accept_sync_conn_req *cp); + + struct list_node _node; +}; + +/** @brief Register SCO HCI activity callbacks. + * + * Register callbacks to monitor the HCI activity of SCO. + * + * @param cb Callback struct. Must point to memory that remains valid. + * + * @retval 0 Success. + * @retval -EINVAL If @p cb is NULL. + * @retval -EEXIST if @p cb was already registered. + */ + +int bt_sco_hci_cb_register(struct bt_sco_hci_cb *cb); + +/** + * @brief Unregister SCO HCI activity callbacks. + * + * Unregister the HCI activity monitor of SCO callbacks. + * + * @param cb Callback struct point to memory that remains valid. + * + * @retval 0 Success. + * @retval -EINVAL If @p cb is NULL. + * @retval -ENOENT if @p cb was not registered. + */ + +int bt_sco_hci_cb_unregister(struct bt_sco_hci_cb *cb); + +/** + * @brief Register a callback structure for SCO HCI activity. + * + * @param _name Name of callback structure. + */ +#define BT_SCO_HCI_CB_DEFINE(_name) \ + static const STRUCT_SECTION_ITERABLE(bt_sco_hci_cb, _CONCAT(bt_sco_hci_cb_, _name)) diff --git a/libs/zblue/host/classic/sdp.c b/libs/zblue/host/classic/sdp.c new file mode 100644 index 0000000000000..3ea9a710e489e --- /dev/null +++ b/libs/zblue/host/classic/sdp.c @@ -0,0 +1,5003 @@ +/**************************************************************************** + * libs/zblue/host/classic/sdp.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Service Discovery Protocol handling. + */ + +/* Copyright (c) 2016 Intel Corporation + * Copyright 2024-2026 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include + +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include "l2cap_br_internal.h" +#include "sdp_internal.h" + +#define LOG_LEVEL CONFIG_BT_SDP_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_sdp); + +#define SDP_PSM 0x0001 + +#define SDP_CHAN(_ch) CONTAINER_OF(_ch, struct bt_sdp, chan.chan) + +#define SDP_DATA_MTU 200 + +#define SDP_MTU (SDP_DATA_MTU + sizeof(struct bt_sdp_hdr)) + +#define MAX_NUM_ATT_ID_FILTER 10 + +#define MAX_NUM_SSP_UUID 12 + +#define SDP_SERVICE_HANDLE_BASE 0x10000 + +#define SDP_DATA_ELEM_NEST_LEVEL_MAX 5 + +/* Size of Cont state length */ +#define SDP_CONT_STATE_LEN_SIZE 1 + +/* 1 byte for the no. of services searched till this response */ + +/* 2 bytes for the total no. of matching records */ +#define SDP_SS_CONT_STATE_SIZE 3 + +/* 1 byte for the no. of attributes searched till this response */ + +/* 4 bytes for the index of current attribute searched till this response */ +#define SDP_SA_CONT_STATE_SIZE 5 + +/* 1 byte for the no. of services searched till this response */ + +/* 1 byte for the no. of attributes searched till this response */ + +/* 4 bytes for the index of current attribute searched till this response */ +#define SDP_SSA_CONT_STATE_SIZE 6 + +#define SDP_INVALID 0xff + +/* SDP record handle size */ +#define SDP_RECORD_HANDLE_SIZE 4 + +struct bt_sdp +{ + struct bt_l2cap_br_chan chan; + struct net_buf *rsp_buf; + struct net_buf *recv_pending; + struct net_buf_simple_state recv_state; +}; + +static struct list_node sdp_db = LIST_INITIAL_VALUE(sdp_db); + +static struct bt_sdp bt_sdp_pool[CONFIG_BT_MAX_CONN]; + +static void sdp_destroy(struct net_buf *buf); + +/* Pool for outgoing SDP packets */ + +NET_BUF_POOL_FIXED_DEFINE(sdp_pool, + CONFIG_BT_MAX_CONN, + BT_L2CAP_BUF_SIZE(SDP_MTU), + CONFIG_BT_CONN_TX_USER_DATA_SIZE, sdp_destroy); + +#define SDP_CLIENT_CHAN(_ch) CONTAINER_OF(_ch, struct bt_sdp_client, chan.chan) + +#define SDP_CLIENT_MTU 64 + +/* Largest encoded AttributeIDList a request PDU can carry. The PDU + * parameters have to fit in SDP_DATA_MTU together with a + * ServiceSearchPattern holding a 128-bit UUID (2-byte sequence header, + * 1-byte UUID header, 16-byte UUID), the 2-byte MaximumAttributeByteCount + * and a ContinuationState of the maximum length (1-byte length, + * BT_SDP_MAX_PDU_CSTATE_LEN bytes of state). A ServiceAttribute request + * carries a 4-byte ServiceRecordHandle instead of the search pattern, so the + * bound holds for it as well. The resulting size is documented at + * bt_sdp_attribute_id_list::count. + */ +#define SDP_CLIENT_ATTR_ID_LIST_MAX_LEN \ + (SDP_DATA_MTU - (2 + 1 + BT_UUID_SIZE_128) - 2 - (1 + BT_SDP_MAX_PDU_CSTATE_LEN)) + +#define SDP_SA_MAX_ATTR_BYTE_COUNT 0xffff +#define SDP_SA_MIN_ATTR_BYTE_COUNT 0x0007 + +#define SDP_SA_ATTR_BYTE_IN_RANGE(count) \ + ((count) >= SDP_SA_MIN_ATTR_BYTE_COUNT && \ + (count) <= SDP_SA_MAX_ATTR_BYTE_COUNT) + +#define SDP_SSA_MAX_ATTR_BYTE_COUNT 0xffff +#define SDP_SSA_MIN_ATTR_BYTE_COUNT 0x0007 + +#define SDP_SSA_ATTR_BYTE_IN_RANGE(count) \ + ((count) >= SDP_SSA_MIN_ATTR_BYTE_COUNT && \ + (count) <= SDP_SSA_MAX_ATTR_BYTE_COUNT) + +enum sdp_client_state +{ + SDP_CLIENT_DISCONNECTED, + SDP_CLIENT_CONNECTING, + SDP_CLIENT_CONNECTED, + SDP_CLIENT_DISCONNECTING, +}; + +/* SDP client flags */ + +enum +{ + SDP_CLIENT_FLAG_WAIT_FOR_TX_BUF = 0, /* Wait for TX buffer availability */ +}; + +struct bt_sdp_client +{ + /* L2CAP channel for SDP client */ + + struct bt_l2cap_br_chan chan; + + /* list of waiting to be resolved UUID params */ + + struct k_queue reqs_pending; + + /* UUID params holder being now resolved */ + + const struct bt_sdp_discover_params *param; + + /* required SDP transaction ID */ + + uint16_t tid; + + /* PDU continuation state object */ + + struct bt_sdp_pdu_cstate cstate; + + /* buffer for collecting record data */ + + struct net_buf *rec_buf; + + /* The total length of response */ + + uint32_t total_len; + + /* Received data length */ + + uint32_t recv_len; + + /* client state */ + + enum sdp_client_state state; + + /* flags */ + + atomic_t flags[1]; +}; + +static struct bt_sdp_client bt_sdp_client_pool[CONFIG_BT_MAX_CONN]; + +enum +{ + BT_SDP_ITER_STOP, + BT_SDP_ITER_CONTINUE, +}; + +struct search_state +{ + uint16_t att_list_size; + uint8_t current_svc; + uint8_t last_att; + uint32_t last_att_index; + bool pkt_full; +}; + +struct select_attrs_data +{ + struct bt_sdp_record *rec; + struct net_buf *rsp_buf; + struct bt_sdp *sdp; + struct bt_sdp_data_elem_seq *seq; + struct search_state *state; + uint32_t *filter; + uint16_t max_att_len; + uint16_t att_list_len; + uint8_t cont_state_size; + size_t num_filters; + bool new_service; +}; + +/* @typedef bt_sdp_attr_func_t + * @brief SDP attribute iterator callback. + * + * @param attr Attribute found. + * @param att_idx Index of the found attribute in the attribute database. + * @param user_data Data given. + * + * @return BT_SDP_ITER_CONTINUE if should continue to the next attribute + * or BT_SDP_ITER_STOP to stop. + */ + +typedef uint8_t( + *bt_sdp_attr_func_t)(struct bt_sdp_attribute *attr, uint8_t att_idx, void + *user_data); + +/* @typedef bt_sdp_svc_func_t + * @brief SDP service record iterator callback. + * + * @param rec Service record found. + * @param user_data Data given. + * + * @return BT_SDP_ITER_CONTINUE if should continue to the next service record + * or BT_SDP_ITER_STOP to stop. + */ + +typedef uint8_t (*bt_sdp_svc_func_t)(struct bt_sdp_record *rec, + void *user_data); + +static int sdp_client_new_session(struct bt_conn *conn, + struct bt_sdp_client *session); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/* @brief Callback for SDP connection + * + * Gets called when an SDP connection is established + * + * @param chan L2CAP channel + * + * @return None + */ + +static void bt_sdp_connected(struct bt_l2cap_chan *chan) +{ + struct bt_l2cap_br_chan *ch = CONTAINER_OF(chan, struct bt_l2cap_br_chan, + chan); + + struct bt_sdp *sdp __unused = CONTAINER_OF(ch, struct bt_sdp, chan); + + LOG_DBG("chan %p cid 0x%04x", ch, ch->tx.cid); +} + +/** @brief Callback for SDP disconnection + * + * Gets called when an SDP connection is terminated + * + * @param chan L2CAP channel + * + * @return None + */ + +static void bt_sdp_disconnected(struct bt_l2cap_chan *chan) +{ + struct bt_l2cap_br_chan *ch = CONTAINER_OF(chan, struct bt_l2cap_br_chan, + chan); + struct bt_sdp *sdp = CONTAINER_OF(ch, struct bt_sdp, chan); + + LOG_DBG("chan %p cid 0x%04x", ch, ch->tx.cid); + + net_buf_drop(&sdp->rsp_buf); + net_buf_drop(&sdp->recv_pending); +} + +/* @brief Allocates a net buffer for SDP service + * + * Allocates a net buffer without waiting for SDP service and returns the + * buffer + * + * @param None + * + * @return Pointer to the net_buf buffer + */ + +static struct net_buf *sdp_svc_create_pdu(void) +{ + return bt_l2cap_create_pdu_timeout(&sdp_pool, sizeof(struct bt_sdp_hdr), + 0); +} + +/* @brief Allocates a net buffer for SDP Client + * + * Allocates a net buffer without waiting for SDP Client and returns the + * buffer + * + * @param None + * + * @return Pointer to the net_buf buffer + */ + +static struct net_buf *sdp_client_create_pdu(void) +{ + return bt_l2cap_create_pdu_timeout(&sdp_pool, sizeof(struct bt_sdp_hdr), + 0); +} + +/* @brief Sends out an SDP PDU + * + * Sends out an SDP PDU after adding the relevant header + * + * @param chan L2CAP channel + * @param buf Buffer to be sent out + * @param op Opcode to be used in the packet header + * @param tid Transaction ID to be used in the packet header + * + * @return None + */ + +static int bt_sdp_send(struct bt_l2cap_chan *chan, + struct net_buf *buf, uint8_t op, uint16_t tid) +{ + struct bt_sdp_hdr *hdr; + uint16_t param_len = buf->len; + int err; + + hdr = net_buf_push(buf, sizeof(struct bt_sdp_hdr)); + hdr->op_code = op; + hdr->tid = sys_cpu_to_be16(tid); + hdr->param_len = sys_cpu_to_be16(param_len); + + err = bt_l2cap_chan_send(chan, buf); + if (err < 0) + { + net_buf_unref(buf); + } + + return err; +} + +/* @brief Sends an error response PDU + * + * Creates and sends an error response PDU + * + * @param sdp SDP service instance + * @param err Error code to be sent in the packet + * @param tid Transaction ID to be used in the packet header + * + * @return None + */ + +static void send_err_rsp(struct bt_sdp *sdp, uint16_t err, uint16_t tid) +{ + struct net_buf *buf; + + LOG_DBG("tid %u, error %u", tid, err); + + buf = net_buf_take(&sdp->rsp_buf); + if (buf == NULL) + { + LOG_ERR("No valid response buffer"); + return; + } + + net_buf_add_be16(buf, err); + + bt_sdp_send(&sdp->chan.chan, buf, BT_SDP_ERROR_RSP, tid); +} + +/* @brief Parses data elements + * + * Parses the first data element from a buffer and splits it into type, size, + * data. Used for parsing incoming requests. Net buf is advanced to the data + * part of the element. + * + * @param buf Buffer to be advanced + * @param data_elem Pointer to the parsed data element structure + * + * @return 0 for success, or relevant error code + */ + +static uint16_t parse_data_elem(struct net_buf_simple *buf, + struct bt_sdp_data_elem *data_elem) +{ + uint8_t size_field_len = 0U; /* Space used to accommodate the size */ + + if (buf->len < 1) + { + LOG_WRN("Malformed packet"); + return BT_SDP_INVALID_SYNTAX; + } + + data_elem->type = net_buf_simple_pull_u8(buf); + + switch (data_elem->type & BT_SDP_TYPE_DESC_MASK) + { + case BT_SDP_UINT8: + case BT_SDP_INT8: + case BT_SDP_UUID_UNSPEC: + case BT_SDP_BOOL: + data_elem->data_size = BIT(data_elem->type & BT_SDP_SIZE_DESC_MASK); + break; + case BT_SDP_TEXT_STR_UNSPEC: + case BT_SDP_SEQ_UNSPEC: + case BT_SDP_ALT_UNSPEC: + case BT_SDP_URL_STR_UNSPEC: + size_field_len = + BIT((data_elem->type & BT_SDP_SIZE_DESC_MASK) - + BT_SDP_SIZE_INDEX_OFFSET); + if (buf->len < size_field_len) + { + LOG_WRN("Malformed packet"); + return BT_SDP_INVALID_SYNTAX; + } + + switch (size_field_len) + { + case 1: + data_elem->data_size = net_buf_simple_pull_u8(buf); + break; + case 2: + data_elem->data_size = net_buf_simple_pull_be16(buf); + break; + case 4: + data_elem->data_size = net_buf_simple_pull_be32(buf); + break; + default: + LOG_WRN("Invalid size in remote request"); + return BT_SDP_INVALID_SYNTAX; + } + break; + default: + LOG_WRN("Invalid type in remote request"); + return BT_SDP_INVALID_SYNTAX; + } + + if (buf->len < data_elem->data_size) + { + LOG_WRN("Malformed packet"); + return BT_SDP_INVALID_SYNTAX; + } + + data_elem->total_size = data_elem->data_size + size_field_len + 1; + data_elem->data = buf->data; + + return 0; +} + +/* @brief Searches for an UUID within an attribute + * + * Searches for an UUID within an attribute. If the attribute has data + * element sequences, it recursively searches within them as well. On finding + * a match with the UUID, it sets the found flag. + * + * @param elem Attribute to be used as the search space (haystack) + * @param uuid UUID to be looked for (needle) + * @param found Flag set to true if the UUID is found (to be returned) + * @param nest_level Used to limit the extent of recursion into nested data + * elements, to avoid potential stack overflows + * + * @return Size of the last data element that has been searched + * (used in recursion) + */ + +static uint32_t search_uuid( + struct bt_sdp_data_elem *elem, struct bt_uuid *uuid, bool *found, + uint8_t nest_level) +{ + const uint8_t *cur_elem; + uint32_t seq_size; + uint32_t size; + union + { + struct bt_uuid uuid; + struct bt_uuid_16 u16; + struct bt_uuid_32 u32; + struct bt_uuid_128 u128; + } u; + + if (*found) + { + return 0; + } + + /* Limit recursion depth to avoid stack overflows */ + + if (nest_level == SDP_DATA_ELEM_NEST_LEVEL_MAX) + { + return 0; + } + + seq_size = elem->data_size; + cur_elem = elem->data; + + if ((elem->type & BT_SDP_TYPE_DESC_MASK) == BT_SDP_UUID_UNSPEC) + { + if (seq_size == 2U) + { + u.uuid.type = BT_UUID_TYPE_16; + u.u16.val = *((uint16_t *)cur_elem); + if (!bt_uuid_cmp(&u.uuid, uuid)) + { + *found = true; + } + } + else if (seq_size == 4U) + { + u.uuid.type = BT_UUID_TYPE_32; + u.u32.val = *((uint32_t *)cur_elem); + if (!bt_uuid_cmp(&u.uuid, uuid)) + { + *found = true; + } + } + else if (seq_size == 16U) + { + u.uuid.type = BT_UUID_TYPE_128; + memcpy(u.u128.val, cur_elem, seq_size); + if (!bt_uuid_cmp(&u.uuid, uuid)) + { + *found = true; + } + } + else + { + LOG_WRN("Invalid UUID size in local database"); + BT_ASSERT(0); + } + } + + if ((elem->type & BT_SDP_TYPE_DESC_MASK) == BT_SDP_SEQ_UNSPEC || + (elem->type & BT_SDP_TYPE_DESC_MASK) == BT_SDP_ALT_UNSPEC) + { + do + { + /* Recursively parse data elements */ + + size = search_uuid((struct bt_sdp_data_elem *)cur_elem, + uuid, + found, + nest_level + 1); + if (*found) + { + return 0; + } + + cur_elem += sizeof(struct bt_sdp_data_elem); + seq_size -= size; + } + while (seq_size); + } + + return elem->total_size; +} + +/* @brief SDP service record iterator. + * + * Iterate over service records from a starting point. + * + * @param func Callback function. + * @param user_data Data to pass to the callback. + * + * @return Pointer to the record where the iterator stopped, or NULL if all + * records are covered + */ + +static struct bt_sdp_record *bt_sdp_foreach_svc(bt_sdp_svc_func_t func, + void *user_data) +{ + struct bt_sdp_record *rec; + struct bt_sdp_record *next; + + list_for_every_container_safe(&sdp_db, rec, next, node) + { + if (func(rec, user_data) == BT_SDP_ITER_STOP) + { + break; + } + } + + return rec; +} + +/* @brief Parse service search pattern + * + * Parse service search pattern + * + * @param buf Request net buf + * @param ssp Service search pattern buffer + * + * @return 0 for success, or relevant error code + */ + +static uint16_t parse_service_search_pattern(struct net_buf *buf, + struct net_buf_simple *ssp) +{ + struct net_buf_simple_state state; + struct bt_sdp_data_elem data_elem; + uint16_t res; + uint8_t uuid_count = 0U; + + res = parse_data_elem(&buf->b, &data_elem); + if (res != 0) + { + return res; + } + + if (((data_elem.type & BT_SDP_TYPE_DESC_MASK) != BT_SDP_SEQ_UNSPEC) && + ((data_elem.type & BT_SDP_TYPE_DESC_MASK) != BT_SDP_ALT_UNSPEC)) + { + LOG_WRN("Invalid type %x in service search pattern", data_elem.type); + return BT_SDP_INVALID_SYNTAX; + } + + if (buf->len < data_elem.data_size) + { + LOG_WRN("Malformed packet"); + return BT_SDP_INVALID_SYNTAX; + } + + net_buf_simple_init_with_data(ssp, net_buf_pull_mem(buf, + data_elem.data_size), + data_elem.data_size); + + net_buf_simple_save(ssp, &state); + + /* Check the service search pattern is valid or not */ + + while (ssp->len > 0) + { + res = parse_data_elem(ssp, &data_elem); + if (res != 0) + { + return res; + } + + if ((data_elem.type & BT_SDP_TYPE_DESC_MASK) != BT_SDP_UUID_UNSPEC) + { + LOG_WRN("Invalid type %u in service search pattern", + data_elem.type); + return BT_SDP_INVALID_SYNTAX; + } + + if (ssp->len < data_elem.data_size) + { + LOG_WRN("Malformed packet"); + return BT_SDP_INVALID_SYNTAX; + } + + if (!((data_elem.data_size == BT_UUID_SIZE_16) || + (data_elem.data_size == BT_UUID_SIZE_32) || + (data_elem.data_size == BT_UUID_SIZE_128))) + { + LOG_WRN("INvalid UUID size"); + return BT_SDP_INVALID_SYNTAX; + } + + net_buf_simple_pull(ssp, data_elem.data_size); + uuid_count++; + + if (uuid_count > MAX_NUM_SSP_UUID) + { + LOG_WRN("Too many UUIDs in ssp %u> %u", uuid_count, + MAX_NUM_SSP_UUID); + return BT_SDP_INVALID_SYNTAX; + } + } + + net_buf_simple_restore(ssp, &state); + + return 0; +} + +/* @brief Match service search pattern in the specific SDP record + * + * Match service search pattern in the specific SDP record + * + * @param buf Service search pattern + * @param record SDP record to match against + * + * @return true if matched, false for no match + */ + +static bool service_search_pattern_matched(struct net_buf_simple *buf, + struct bt_sdp_record *record) +{ + struct net_buf_simple_state state; + struct bt_sdp_data_elem data_elem; + uint16_t res; + union + { + struct bt_uuid uuid; + struct bt_uuid_16 u16; + struct bt_uuid_32 u32; + struct bt_uuid_128 u128; + } u; + + bool found = false; + + net_buf_simple_save(buf, &state); + + while (buf->len > 0) + { + res = parse_data_elem(buf, &data_elem); + if (res != 0) + { + break; + } + + if ((data_elem.type & BT_SDP_TYPE_DESC_MASK) != BT_SDP_UUID_UNSPEC) + { + LOG_WRN("Invalid type %u in service search pattern", + data_elem.type); + break; + } + + if (buf->len < data_elem.data_size) + { + LOG_WRN("Malformed packet"); + break; + } + + if (data_elem.data_size == BT_UUID_SIZE_16) + { + u.uuid.type = BT_UUID_TYPE_16; + u.u16.val = net_buf_simple_pull_be16(buf); + } + else if (data_elem.data_size == BT_UUID_SIZE_32) + { + u.uuid.type = BT_UUID_TYPE_32; + u.u32.val = net_buf_simple_pull_be32(buf); + } + else if (data_elem.data_size == BT_UUID_SIZE_128) + { + u.uuid.type = BT_UUID_TYPE_128; + + /* Change big endian to little endian */ + + sys_memcpy_swap(u.u128.val, buf->data, data_elem.data_size); + net_buf_simple_pull(buf, data_elem.data_size); + } + else + { + LOG_WRN("Invalid UUID len %u in service search pattern", + data_elem.data_size); + net_buf_simple_pull(buf, data_elem.data_size); + continue; + } + + for (size_t index = 0; index < record->attr_count; index++) + { + struct bt_sdp_attribute *attr; + + attr = &record->attrs[index]; + + (void)search_uuid(&attr->val, &u.uuid, &found, 1); + if (found) + { + goto matched; + } + } + } + +matched: + net_buf_simple_restore(buf, &state); + + return found; +} + +/* @brief Handler for Service Search Request + * + * Parses, processes and responds to a Service Search Request + * + * @param sdp Pointer to the SDP structure + * @param buf Request net buf + * @param tid Transaction ID + * + * @return 0 for success, or relevant error code + */ + +static uint16_t sdp_svc_search_req(struct bt_sdp *sdp, + struct net_buf *buf, uint16_t tid) +{ + struct bt_sdp_svc_rsp *rsp; + struct net_buf *resp_buf; + struct bt_sdp_record *record; + struct bt_sdp_record *next; + uint16_t max_rec_count; + uint16_t total_recs = 0U; + uint16_t res; + uint8_t cont_state_size; + uint8_t cont_recs = 0U; + uint8_t matched_recs = 0U; + uint8_t current_recs = 0U; + bool pkt_full = false; + struct net_buf_simple ssp; + + res = parse_service_search_pattern(buf, &ssp); + if (res) + { + /* Error in parsing */ + + return res; + } + + if (buf->len < (sizeof(max_rec_count) + sizeof(cont_state_size))) + { + LOG_WRN("Malformed packet"); + return BT_SDP_INVALID_SYNTAX; + } + + max_rec_count = net_buf_pull_be16(buf); + cont_state_size = net_buf_pull_u8(buf); + + /* We send out only SDP_SS_CONT_STATE_SIZE bytes continuation state in + * responses, so expect only SDP_SS_CONT_STATE_SIZE bytes in requests + */ + + if (cont_state_size) + { + if (cont_state_size != SDP_SS_CONT_STATE_SIZE) + { + LOG_WRN("Invalid cont state size %u", cont_state_size); + return BT_SDP_INVALID_CSTATE; + } + + if (buf->len < cont_state_size) + { + LOG_WRN("Malformed packet"); + return BT_SDP_INVALID_SYNTAX; + } + + cont_recs = net_buf_pull_u8(buf); + /* We include total_recs in the continuation state. We calculate + * it once and preserve it across all the partial responses + */ + + total_recs = net_buf_pull_be16(buf); + } + + LOG_DBG("max_rec_count %u, cont_recs %u", max_rec_count, cont_recs); + + resp_buf = net_buf_take(&sdp->rsp_buf); + DEBUGASSERT(resp_buf != NULL); + rsp = net_buf_add(resp_buf, sizeof(*rsp)); + + list_for_every_container_safe(&sdp_db, record, next, node) + { + uint16_t require_len; + + if (!service_search_pattern_matched(&ssp, record)) + { + continue; + } + + matched_recs++; + + /* Calculate total recs only if it is first packet */ + + if (!cont_state_size) + { + total_recs++; + } + else if (matched_recs > total_recs) + { + LOG_WRN("Reached total records %u > %u", matched_recs, total_recs); + break; + } + + if (matched_recs > max_rec_count) + { + LOG_WRN("Reached MAX record count %u > %u", matched_recs, + max_rec_count); + break; + } + + if (matched_recs < cont_recs) + { + continue; + } + + if (pkt_full) + { + continue; + } + + require_len = + sizeof(record->handle) + sizeof(cont_state_size) + + SDP_SS_CONT_STATE_SIZE + + sizeof(struct bt_sdp_hdr); + if ((MIN(SDP_MTU, sdp->chan.tx.mtu) - resp_buf->len) < require_len) + { + pkt_full = true; + } + + if (pkt_full) + { + /* Packet exhausted: Add continuation state and break */ + + LOG_DBG("Packet full, num_services_covered %u", current_recs); + net_buf_add_u8(resp_buf, SDP_SS_CONT_STATE_SIZE); + net_buf_add_u8(resp_buf, matched_recs); + + /* If it is the first packet of a partial response, + * continue dry-running to calculate total_recs. Else break + */ + + if (cont_state_size) + { + break; + } + + continue; + } + + /* Add Service Record Handle */ + + net_buf_add_be32(resp_buf, record->handle); + current_recs++; + } + + /* If packet is not exhausted, add 0 continuation state. + * Else, add total records count for continuation state. + */ + + if (!pkt_full) + { + net_buf_add_u8(resp_buf, 0); + } + else + { + net_buf_add_be16(resp_buf, total_recs); + } + + rsp->total_recs = sys_cpu_to_be16(total_recs); + rsp->current_recs = sys_cpu_to_be16(current_recs); + + LOG_DBG("Sending response, len %u", resp_buf->len); + bt_sdp_send(&sdp->chan.chan, resp_buf, BT_SDP_SVC_SEARCH_RSP, tid); + + return 0; +} + +/* @brief Copies an attribute into an outgoing buffer + * + * This function handles partial copying of attributes into a response buffer + * with careful management of offsets, indices, and remaining space. + * + * @param buf Destination network buffer for attribute data + * @param data Source attribute data + * @param length Total length of attribute data + * @param offset Current offset in the attribute data + * @param index Current index tracking progress + * @param space Remaining available space in the buffer + * + * @return Number of bytes copied or 0 if no bytes copied + */ + +static uint16_t copy_attribute_from_buf( + struct net_buf *buf, const uint8_t *data, uint32_t length, + uint32_t *offset, uint32_t *index, uint16_t *space) +{ + uint32_t end; + uint32_t start = *offset; + uint16_t len; + + end = start + length; + *offset = end; + + /* Copied data range is start ~ end */ + + if (*index < start) + { + len = MIN(length, *space); + net_buf_add_mem(buf, data, len); + *space -= len; + *index += len; + return len; + } + + /* Copied data range is *index ~ end */ + + if (*index < end) + { + len = end - *index; + len = MIN(len, *space); + net_buf_add_mem(buf, &data[*index - start], len); + *space -= len; + *index += len; + return len; + } + + return 0; +} + +/* @brief Copies an attribute into an outgoing buffer + * + * Copies an attribute into a buffer. Recursively calls itself for complex + * attributes. + * + * @param elem Attribute to be copied to the buffer + * @param buf Buffer where the attribute is to be copied + * @param offset Pointer to the current offset in the attribute + * @param index Pointer to the current index for partial copying + * @param space Pointer to the remaining buffer space + * @param nest_level Current nesting level of the attribute + * + * @return Size of the last data element that has been searched + * (used in recursion) + */ + +static uint32_t copy_attribute( + const struct bt_sdp_data_elem *elem, struct net_buf *buf, uint32_t *offset, + uint32_t *index, uint16_t *space, uint8_t nest_level) +{ + const struct bt_sdp_data_elem *sub_elem; + uint32_t size; + uint32_t seq_size; + uint32_t total_size; + uint16_t copy_len = 0; + uint32_t total_len = 0; + + NET_BUF_SIMPLE_DEFINE(attr_buf, 21); /* 1 + 4 + 16 bytes */ + + /* Limit recursion depth to avoid stack overflows */ + + if (nest_level == SDP_DATA_ELEM_NEST_LEVEL_MAX) + { + return 0; + } + + if (*space == 0) + { + return 0; + } + + seq_size = elem->data_size; + total_size = elem->total_size; + sub_elem = (const struct bt_sdp_data_elem *)elem->data; + + DEBUGASSERT(!((nest_level == 1) && (*index >= total_size))); + + /* Copy the header */ + + net_buf_simple_reset(&attr_buf); + + net_buf_simple_add_u8(&attr_buf, elem->type); + + switch (total_size - (seq_size + 1U)) + { + case 1: + net_buf_simple_add_u8(&attr_buf, elem->data_size); + break; + case 2: + net_buf_simple_add_be16(&attr_buf, elem->data_size); + break; + case 4: + net_buf_simple_add_be32(&attr_buf, elem->data_size); + break; + } + + /* Recursively parse (till the last element is not another data element) + * and then fill the elements + */ + + if ((elem->type & BT_SDP_TYPE_DESC_MASK) == BT_SDP_SEQ_UNSPEC || + (elem->type & BT_SDP_TYPE_DESC_MASK) == BT_SDP_ALT_UNSPEC) + { + copy_len = copy_attribute_from_buf(buf, attr_buf.data, attr_buf.len, + offset, index, space); + total_len += copy_len; + if (*space == 0) + { + goto exit; + } + + do + { + size = copy_attribute(sub_elem, buf, offset, index, space, + nest_level + 1); + total_len += size; + if (*space == 0) + { + goto exit; + } + + DEBUGASSERT(seq_size >= sub_elem->total_size); + seq_size -= sub_elem->total_size; + sub_elem++; + } + while (seq_size); + } + else if ((elem->type & BT_SDP_TYPE_DESC_MASK) == BT_SDP_UINT8 || + (elem->type & BT_SDP_TYPE_DESC_MASK) == BT_SDP_INT8 || + (elem->type & BT_SDP_TYPE_DESC_MASK) == BT_SDP_UUID_UNSPEC) + { + if (seq_size == 1U) + { + net_buf_simple_add_u8(&attr_buf, *((const uint8_t *)elem->data)); + } + else if (seq_size == 2U) + { + net_buf_simple_add_be16(&attr_buf, + *((const uint16_t *)elem->data)); + } + else if (seq_size == 4U) + { + net_buf_simple_add_be32(&attr_buf, + *((const uint32_t *)elem->data)); + } + else if (seq_size == 8U) + { + net_buf_simple_add_be64(&attr_buf, + *((const uint64_t *)elem->data)); + } + else + { + DEBUGASSERT(seq_size == 0x10); + + uint8_t val[seq_size]; + + sys_memcpy_swap(val, elem->data, sizeof(val)); + net_buf_simple_add_mem(&attr_buf, val, seq_size); + } + + copy_len = copy_attribute_from_buf(buf, attr_buf.data, attr_buf.len, + offset, index, space); + total_len += copy_len; + if (*space == 0) + { + goto exit; + } + } + else + { + copy_len = copy_attribute_from_buf(buf, attr_buf.data, attr_buf.len, + offset, index, space); + total_len += copy_len; + if (*space == 0) + { + goto exit; + } + + copy_len = copy_attribute_from_buf(buf, (const uint8_t *)elem->data, + elem->data_size, offset, + index, space); + total_len += copy_len; + if (*space == 0) + { + goto exit; + } + } + +exit: + if ((nest_level == 1) && (*index >= total_size)) + { + /* Reset index to zero */ + + *index = 0; + } + + return total_len; +} + +/* @brief SDP attribute iterator. + * + * Iterate over attributes of a service record from a starting index. + * + * @param record Service record whose attributes are to be iterated over. + * @param idx Index in the attribute list from where to start. + * @param func Callback function. + * @param user_data Data to pass to the callback. + * + * @return Index of the attribute where the iterator stopped + */ + +static uint8_t bt_sdp_foreach_attr(struct bt_sdp_record *record, uint8_t idx, + bt_sdp_attr_func_t func, void *user_data) +{ + for (; idx < record->attr_count; idx++) + { + if (func(&record->attrs[idx], idx, user_data) == BT_SDP_ITER_STOP) + { + break; + } + } + + return idx; +} + +/* @brief Check if an attribute matches a range, and include it in the + * response + * + * Checks if an attribute matches a given attribute ID or range, and if so, + * includes it in the response packet + * + * @param attr The current attribute + * @param att_idx Index of the current attribute in the database + * @param user_data Pointer to the structure containing response packet, + * byte count, states, etc + * + * @return BT_SDP_ITER_CONTINUE if should continue to the next attribute + * or BT_SDP_ITER_STOP to stop. + */ + +static uint8_t select_attrs(struct bt_sdp_attribute *attr, + uint8_t att_idx, void *user_data) +{ + struct select_attrs_data *sad = user_data; + uint16_t att_id_lower; + uint16_t att_id_upper; + uint16_t att_id_cur; + uint16_t space; + uint32_t attr_size = 0; + uint32_t seq_size; + uint32_t attr_data_index; + size_t idx_filter; + uint32_t offset = 0; + uint32_t copy_len; + uint32_t required_len; + + for (idx_filter = 0U; idx_filter < sad->num_filters; idx_filter++) + { + att_id_lower = (sad->filter[idx_filter] >> 16); + att_id_upper = (sad->filter[idx_filter]); + att_id_cur = attr->id; + + /* Check for range values */ + + if (att_id_lower != 0xffff && (!IN_RANGE(att_id_cur, att_id_lower, + att_id_upper))) + { + continue; + } + + /* Check for match values */ + + if (att_id_lower == 0xffff && att_id_cur != att_id_upper) + { + continue; + } + + /* Attribute ID matches */ + + /* 3 bytes for Attribute ID */ + + attr_size = 3 + attr->val.total_size; + + /* If this is the first attribute of the service, then we need + * to account for the space required to add the per-service data + * element sequence header as well. + */ + + if ((sad->state->last_att_index == 0) && (sad->state->last_att == 0) && + sad->new_service) + { + /* 3 bytes for Per-Service Data Elem Seq declaration */ + + seq_size = attr_size + 3; + } + else + { + seq_size = attr_size; + } + + if (sad->rsp_buf) + { + space = MIN(SDP_MTU, sad->sdp->chan.tx.mtu) - sad->rsp_buf->len - + sizeof(struct bt_sdp_hdr); + space = MIN(space, sad->max_att_len); + + if ((!sad->state->pkt_full) && (space <= sad->cont_state_size)) + { + /* Packet exhausted */ + + sad->state->pkt_full = true; + } + else + { + space -= sad->cont_state_size; + } + } + + /* Keep filling data only if packet is not exhausted */ + + if (!sad->state->pkt_full && sad->rsp_buf) + { + attr_data_index = sad->state->last_att_index; + required_len = 0; + + /* Add Per-Service Data Element Seq declaration once + * only when we are starting from the first attribute + */ + + if (!sad->seq && (sad->state->last_att_index == 0) && + (sad->state->last_att == 0)) + { + required_len += sizeof(*sad->seq); + } + + if (attr_data_index == 0) + { + required_len += sizeof(uint8_t) + sizeof(uint16_t); + } + + if (space <= required_len) + { + /* Packet exhausted */ + + sad->state->pkt_full = true; + goto out; + } + + /* Add Per-Service Data Element Seq declaration once + * only when we are starting from the first attribute + */ + + if (!sad->seq && (sad->state->last_att_index == 0) && + (sad->state->last_att == 0)) + { + sad->seq = net_buf_add(sad->rsp_buf, sizeof(*sad->seq)); + sad->seq->type = BT_SDP_SEQ16; + sad->seq->size = 0U; + space -= sizeof(*sad->seq); + + sad->max_att_len -= sizeof(*sad->seq); + sad->att_list_len += sizeof(*sad->seq); + } + + if (attr_data_index == 0) + { + /* Add attribute ID */ + + net_buf_add_u8(sad->rsp_buf, BT_SDP_UINT16); + net_buf_add_be16(sad->rsp_buf, att_id_cur); + space -= sizeof(uint8_t) + sizeof(uint16_t); + + sad->max_att_len -= sizeof(uint8_t) + sizeof(uint16_t); + sad->att_list_len += sizeof(uint8_t) + sizeof(uint16_t); + } + + /* Add attribute value */ + + copy_len = copy_attribute(&attr->val, sad->rsp_buf, &offset, + &attr_data_index, &space, 1); + sad->max_att_len -= copy_len; + sad->att_list_len += copy_len; + sad->state->last_att = att_idx; + sad->state->last_att_index = attr_data_index; + sad->state->current_svc = sad->rec->index; + + if (attr_data_index == 0) + { + /* It means the all data of attribute is copied. + * The att index needs to be incremented. + */ + + sad->state->last_att++; + } + + if (space == 0) + { + /* Packet exhausted */ + + sad->state->pkt_full = true; + goto out; + } + } + +out: + if (sad->seq) + { + /* Keep adding the sequence size if this packet contains + * the Per-Service Data Element Seq declaration header + */ + + sad->seq->size += attr_size; + } + + /* Calculate the total sequence size */ + + sad->state->att_list_size += seq_size; + + sad->new_service = false; + break; + } + + return BT_SDP_ITER_CONTINUE; +} + +/* @brief Creates attribute list in the given buffer + * + * Populates the attribute list of a service record in the buffer. To be + * used for responding to Service Attribute and Service Search Attribute + * requests + * + * @param sdp Pointer to the SDP structure + * @param record Service record whose attributes are to be included + * in the response + * @param filter Attribute values/ranges to be used as a filter + * @param num_filters Number of elements in the attribute filter + * @param max_att_len Maximum size of attributes to be included in the + * response + * @param cont_state_size No. of additional continuation state bytes to keep + * space for in the packet. This will vary based on + * the type of the request + * @param state State of the overall search + * @param rsp_buf Response buffer which is filled in + * + * @return len Length of the attribute list created + */ + +static uint16_t create_attr_list( + struct bt_sdp *sdp, struct bt_sdp_record *record, uint32_t *filter, + size_t num_filters, uint16_t max_att_len, uint8_t cont_state_size, + struct search_state *state, struct net_buf *rsp_buf) +{ + struct select_attrs_data sad; + __maybe_unused uint8_t idx_att; + + sad.num_filters = num_filters; + sad.rec = record; + sad.rsp_buf = rsp_buf; + sad.sdp = sdp; + sad.max_att_len = max_att_len; + sad.cont_state_size = cont_state_size; + sad.seq = NULL; + sad.filter = filter; + sad.state = state; + sad.att_list_len = 0U; + sad.new_service = true; + + idx_att = bt_sdp_foreach_attr(sad.rec, + state->last_att, + select_attrs, + &sad); + + if (sad.seq) + { + sad.seq->size = sys_cpu_to_be16(sad.seq->size); + } + + return sad.att_list_len; +} + +/* @brief Extracts the attribute search list from a buffer + * + * Parses a buffer to extract the attribute search list (list of attribute + * IDs and ranges) which are to be used to filter attributes. + * + * @param buf Buffer to be parsed for extracting the attribute + * search list + * @param filter Empty list of 4byte filters that are filled in. For + * attribute IDs, the lower 2 bytes contain the ID and + * the upper 2 bytes are set to 0xFFFF. For attribute + * ranges, the lower 2bytes indicate the start ID and the + * upper 2bytes indicate the end ID + * @param max_filters Max element slots of filter to be filled in + * @param num_filters No. of filter elements filled in (to be returned) + * + * @return 0 for success, or relevant error code + */ + +static uint16_t get_att_search_list( + struct net_buf *buf, uint32_t *filter, size_t max_filters, + size_t *num_filters) +{ + struct bt_sdp_data_elem data_elem; + uint16_t res; + uint32_t size; + + *num_filters = 0U; + res = parse_data_elem(&buf->b, &data_elem); + if (res) + { + return res; + } + + size = data_elem.data_size; + + while (size) + { + if (*num_filters >= max_filters) + { + LOG_WRN("Exceeded maximum array length %zu of %p", max_filters, + filter); + return 0; + } + + res = parse_data_elem(&buf->b, &data_elem); + if (res) + { + return res; + } + + if ((data_elem.type & BT_SDP_TYPE_DESC_MASK) != BT_SDP_UINT8) + { + LOG_WRN("Invalid type %u in attribute ID list", data_elem.type); + return BT_SDP_INVALID_SYNTAX; + } + + if (buf->len < data_elem.data_size) + { + LOG_WRN("Malformed packet"); + return BT_SDP_INVALID_SYNTAX; + } + + /* This is an attribute ID */ + + if (data_elem.data_size == 2U) + { + filter[(*num_filters)++] = 0xffff0000 | net_buf_pull_be16(buf); + } + + /* This is an attribute ID range */ + + if (data_elem.data_size == 4U) + { + filter[(*num_filters)++] = net_buf_pull_be32(buf); + } + + size -= data_elem.total_size; + } + + return 0; +} + +/* @brief Check if a given handle matches that of the current service + * + * Checks if a given handle matches that of the current service + * + * @param rec The current service record + * @param user_data Pointer to the service record handle to be matched + * + * @return BT_SDP_ITER_CONTINUE if should continue to the next record + * or BT_SDP_ITER_STOP to stop. + */ + +static uint8_t find_handle(struct bt_sdp_record *rec, void *user_data) +{ + uint32_t *svc_rec_hdl = user_data; + + if (rec->handle == *svc_rec_hdl) + { + return BT_SDP_ITER_STOP; + } + + return BT_SDP_ITER_CONTINUE; +} + +/* @brief Handler for Service Attribute Request + * + * Parses, processes and responds to a Service Attribute Request + * + * @param sdp Pointer to the SDP structure + * @param buf Request buffer + * @param tid Transaction ID + * + * @return 0 for success, or relevant error code + */ + +static uint16_t sdp_svc_att_req(struct bt_sdp *sdp, + struct net_buf *buf, uint16_t tid) +{ + uint32_t filter[MAX_NUM_ATT_ID_FILTER]; + struct search_state state = + { + .current_svc = + SDP_INVALID, .last_att = + 0, .last_att_index = 0, .pkt_full = false + }; + + struct bt_sdp_record *record; + struct bt_sdp_att_rsp *rsp; + struct net_buf *rsp_buf; + uint32_t svc_rec_hdl; + uint16_t max_att_len; + uint16_t res; + uint16_t att_list_len; + size_t num_filters; + uint8_t cont_state_size; + + if (buf->len < 6) + { + LOG_WRN("Malformed packet"); + return BT_SDP_INVALID_SYNTAX; + } + + svc_rec_hdl = net_buf_pull_be32(buf); + max_att_len = net_buf_pull_be16(buf); + if (!SDP_SA_ATTR_BYTE_IN_RANGE(max_att_len)) + { + LOG_WRN("Invalid max attribute length %u", max_att_len); + return BT_SDP_INVALID_SYNTAX; + } + + /* Set up the filters */ + + res = get_att_search_list(buf, filter, ARRAY_SIZE(filter), &num_filters); + if (res) + { + /* Error in parsing */ + + return res; + } + + if (buf->len < 1) + { + LOG_WRN("Malformed packet"); + return BT_SDP_INVALID_SYNTAX; + } + + cont_state_size = net_buf_pull_u8(buf); + + /* We only send out 1 byte continuation state in responses, + * so expect only 1 byte in requests + */ + + if (cont_state_size) + { + if (cont_state_size != SDP_SA_CONT_STATE_SIZE) + { + LOG_WRN("Invalid cont state size %u", cont_state_size); + return BT_SDP_INVALID_CSTATE; + } + + if (buf->len < cont_state_size) + { + LOG_WRN("Malformed packet"); + return BT_SDP_INVALID_SYNTAX; + } + + state.last_att = net_buf_pull_u8(buf); + state.last_att_index = net_buf_pull_be32(buf); + } + + LOG_DBG("svc_rec_hdl %u, max_att_len 0x%04x, cont_state %u " + "%u", svc_rec_hdl, + max_att_len, + state.last_att, state.last_att_index); + + /* Find the service */ + + record = bt_sdp_foreach_svc(find_handle, &svc_rec_hdl); + + if (!record) + { + LOG_WRN("Handle %u not found", svc_rec_hdl); + return BT_SDP_INVALID_RECORD_HANDLE; + } + + /* For partial responses, restore the search state */ + + if (cont_state_size) + { + state.current_svc = record->index; + } + + rsp_buf = net_buf_take(&sdp->rsp_buf); + DEBUGASSERT(rsp_buf != NULL); + rsp = net_buf_add(rsp_buf, sizeof(*rsp)); + + /* cont_state_size should include 1 byte header */ + + att_list_len = create_attr_list(sdp, record, filter, num_filters, + max_att_len, + SDP_SA_CONT_STATE_SIZE + 1, &state, + rsp_buf); + + if (!att_list_len) + { + /* For empty responses, add an empty data element sequence */ + + net_buf_add_u8(rsp_buf, BT_SDP_SEQ8); + net_buf_add_u8(rsp_buf, 0); + att_list_len = 2U; + } + + if (state.current_svc != record->index) + { + /* It is a corner case that the remaining free space of the responding + * is empty, and all attributes are sent, clear state.pkt_full to avoid + * further processing. + */ + + state.pkt_full = false; + } + + /* Add continuation state */ + + if (state.pkt_full) + { + LOG_DBG("Packet full, state.last_att %u", state.last_att); + net_buf_add_u8(rsp_buf, SDP_SA_CONT_STATE_SIZE); + net_buf_add_u8(rsp_buf, state.last_att); + net_buf_add_be32(rsp_buf, state.last_att_index); + } + else + { + net_buf_add_u8(rsp_buf, 0); + } + + rsp->att_list_len = sys_cpu_to_be16(att_list_len); + + LOG_DBG("Sending response, len %u", rsp_buf->len); + bt_sdp_send(&sdp->chan.chan, rsp_buf, BT_SDP_SVC_ATTR_RSP, tid); + + return 0; +} + +/* @brief Handler for Service Search Attribute Request + * + * Parses, processes and responds to a Service Search Attribute Request + * + * @param sdp Pointer to the SDP structure + * @param buf Request buffer + * @param tid Transaction ID + * + * @return 0 for success, or relevant error code + */ + +static uint16_t sdp_svc_search_att_req(struct bt_sdp *sdp, + struct net_buf *buf, uint16_t tid) +{ + uint32_t filter[MAX_NUM_ATT_ID_FILTER]; + struct search_state state = + { + .att_list_size = 0, + .current_svc = SDP_INVALID, + .last_att = 0, + .last_att_index = 0, + .pkt_full = false + }; + + struct net_buf *rsp_buf; + struct net_buf *rsp_buf_cpy; + struct bt_sdp_record *record; + struct bt_sdp_record *next; + struct bt_sdp_att_rsp *rsp; + struct bt_sdp_data_elem_seq *seq = NULL; + uint16_t max_att_len; + uint16_t res; + uint16_t att_list_len = 0U; + uint16_t sending_len; + size_t num_filters; + uint8_t cont_state_size; + uint8_t next_svc = 0U; + bool dry_run = false; + struct net_buf_simple ssp; + struct net_buf_simple_state rsp_state; + + res = parse_service_search_pattern(buf, &ssp); + if (res) + { + /* Error in parsing */ + + return res; + } + + if (buf->len < sizeof(max_att_len)) + { + LOG_WRN("Malformed packet - Maximum Attribute Byte Count"); + return BT_SDP_INVALID_SYNTAX; + } + + max_att_len = net_buf_pull_be16(buf); + if (!SDP_SSA_ATTR_BYTE_IN_RANGE(max_att_len)) + { + LOG_WRN("Invalid max attribute length %u", max_att_len); + return BT_SDP_INVALID_SYNTAX; + } + + if (max_att_len < sizeof(*seq)) + { + LOG_WRN("Invalid maximum attribute byte count %u < %u", max_att_len, + sizeof(*seq)); + return BT_SDP_INVALID_SYNTAX; + } + + /* Set up the filters */ + + res = get_att_search_list(buf, filter, ARRAY_SIZE(filter), &num_filters); + if (res) + { + /* Error in parsing */ + + return res; + } + + if (buf->len < sizeof(cont_state_size)) + { + LOG_WRN("Malformed packet - Continuation State"); + return BT_SDP_INVALID_SYNTAX; + } + + cont_state_size = net_buf_pull_u8(buf); + + /* We only send out 2 bytes continuation state in responses, + * so expect only 2 bytes in requests + */ + + if (cont_state_size) + { + if (cont_state_size != SDP_SSA_CONT_STATE_SIZE) + { + LOG_WRN("Invalid cont state size %u", cont_state_size); + return BT_SDP_INVALID_CSTATE; + } + + if (buf->len < cont_state_size) + { + LOG_WRN("Malformed packet - Continuation State Size"); + return BT_SDP_INVALID_SYNTAX; + } + + state.current_svc = net_buf_pull_u8(buf); + state.last_att = net_buf_pull_u8(buf); + state.last_att_index = net_buf_pull_be32(buf); + next_svc = state.current_svc; + } + + LOG_DBG("max_att_len 0x%04x, cont_state %u %u %u", max_att_len, next_svc, + state.last_att, + state.last_att_index); + + rsp_buf = net_buf_take(&sdp->rsp_buf); + DEBUGASSERT(rsp_buf != NULL); + net_buf_simple_save(&rsp_buf->b, &rsp_state); + + rsp = net_buf_add(rsp_buf, sizeof(*rsp)); + + /* Add headers only if this is not a partial response */ + + if (!cont_state_size) + { + seq = net_buf_add(rsp_buf, sizeof(*seq)); + seq->type = BT_SDP_SEQ16; + seq->size = 0U; + + /* 3 bytes for Outer Data Element Sequence declaration */ + + att_list_len = sizeof(*seq); + max_att_len -= sizeof(*seq); + } + + rsp_buf_cpy = rsp_buf; + + list_for_every_container_safe(&sdp_db, record, next, node) + { + if (!service_search_pattern_matched(&ssp, record)) + { + continue; + } + + if (record->index < next_svc) + { + continue; + } + +/* reset the `state.last_att` and `state.last_att_index` + * if the index of current record is not same with `state.current_svc`. + */ + + if (state.current_svc != record->index) + { + state.current_svc = record->index; + state.last_att = 0; + state.last_att_index = 0; + } + + sending_len = create_attr_list(sdp, record, filter, num_filters, + max_att_len, + SDP_SSA_CONT_STATE_SIZE + 1, &state, + rsp_buf_cpy); + att_list_len += sending_len; + + if (max_att_len < sending_len) + { + LOG_ERR("Att len exceeds %u < %u", max_att_len, sending_len); + net_buf_simple_restore(&rsp_buf->b, &rsp_state); + sdp->rsp_buf = net_buf_take(&rsp_buf); + return BT_SDP_INVALID_SYNTAX; + } + + max_att_len -= sending_len; + + /* Check if packet is full and not dry run */ + + if (state.pkt_full && !dry_run) + { + LOG_DBG("Packet full, state.last_att %u", state.last_att); + + dry_run = true; + + /* Add continuation state */ + + net_buf_add_u8(rsp_buf, SDP_SSA_CONT_STATE_SIZE); + net_buf_add_u8(rsp_buf, state.current_svc); + net_buf_add_u8(rsp_buf, state.last_att); + net_buf_add_be32(rsp_buf, state.last_att_index); + + /* Break if it's not a partial response, else dry-run + * Dry run: Look for other services that match + */ + + if (cont_state_size) + { + break; + } + + rsp_buf_cpy = NULL; + } + + if (dry_run) + { + /* Reset the state.last_att and state.last_att_index to calculate + * all sequence size + */ + + state.last_att = 0; + state.last_att_index = 0; + } + } + + if (!dry_run) + { + if (!att_list_len) + { + /* For empty responses, add an empty data elem seq */ + + net_buf_add_u8(rsp_buf, BT_SDP_SEQ8); + net_buf_add_u8(rsp_buf, 0); + att_list_len = 2U; + } + + /* Search exhausted */ + + net_buf_add_u8(rsp_buf, 0); + } + + rsp->att_list_len = sys_cpu_to_be16(att_list_len); + if (seq) + { + seq->size = sys_cpu_to_be16(state.att_list_size); + } + + LOG_DBG("Sending response, len %u", rsp_buf->len); + bt_sdp_send(&sdp->chan.chan, rsp_buf, BT_SDP_SVC_SEARCH_ATTR_RSP, tid); + + return 0; +} + +static const struct +{ + uint8_t op_code; + uint16_t (*func)(struct bt_sdp *sdp, struct net_buf *buf, uint16_t tid); +} + +handlers[] = +{ + { + BT_SDP_SVC_SEARCH_REQ, sdp_svc_search_req + }, + + { + BT_SDP_SVC_ATTR_REQ, sdp_svc_att_req + }, + + { + BT_SDP_SVC_SEARCH_ATTR_REQ, sdp_svc_search_att_req + }, +}; + +/* @brief Callback for SDP data receive + * + * Gets called when an SDP PDU is received. Calls the corresponding handler + * based on the op code of the PDU. + * + * @param chan L2CAP channel + * @param buf Received PDU + * + * @return None + */ + +static int bt_sdp_recv(struct bt_l2cap_chan *chan, struct net_buf *buf) +{ + struct bt_l2cap_br_chan *ch = CONTAINER_OF(chan, struct bt_l2cap_br_chan, + chan); + struct bt_sdp *sdp = CONTAINER_OF(ch, struct bt_sdp, chan); + struct bt_sdp_hdr *hdr; + uint16_t err = BT_SDP_INVALID_SYNTAX; + size_t i; + + LOG_DBG("chan %p, ch %p, cid 0x%04x", chan, ch, ch->tx.cid); + + BT_ASSERT(sdp); + + if (buf->len < sizeof(*hdr)) + { + LOG_ERR("Too small SDP PDU received"); + return 0; + } + + if (sdp->recv_pending != NULL) + { + LOG_WRN("Last SDP request is not completed, drop it"); + net_buf_drop(&sdp->recv_pending); + } + + /* Take the reference of receiving buffer. Only release it after the + * progress is done. + */ + + sdp->recv_pending = net_buf_ref(buf); + net_buf_simple_save(&buf->b, &sdp->recv_state); + + net_buf_drop(&sdp->rsp_buf); + sdp->rsp_buf = sdp_svc_create_pdu(); + if (sdp->rsp_buf == NULL) + { + LOG_WRN("No net buffers available"); + return 0; + } + + hdr = net_buf_pull_mem(buf, sizeof(*hdr)); + LOG_DBG("Received SDP code 0x%02x len %u", hdr->op_code, buf->len); + + if (sys_cpu_to_be16(hdr->param_len) != buf->len) + { + err = BT_SDP_INVALID_PDU_SIZE; + } + else + { + for (i = 0; i < ARRAY_SIZE(handlers); i++) + { + if (hdr->op_code != handlers[i].op_code) + { + continue; + } + + err = handlers[i].func(sdp, buf, sys_be16_to_cpu(hdr->tid)); + break; + } + } + + if (err != 0) + { + LOG_WRN("SDP error 0x%02x", err); + send_err_rsp(sdp, err, sys_be16_to_cpu(hdr->tid)); + } + + net_buf_drop(&sdp->rsp_buf); + net_buf_drop(&sdp->recv_pending); + return 0; +} + +static void sdp_svc_work_handler(struct k_work *work) +{ + ARG_UNUSED(work); + + ARRAY_FOR_EACH(bt_sdp_pool, i) + { + struct bt_sdp *sdp = &bt_sdp_pool[i]; + struct net_buf *buf; + + buf = net_buf_take(&sdp->recv_pending); + if (buf == NULL) + { + continue; + } + + if (sdp->chan.chan.conn == NULL) + { + net_buf_unref(buf); + continue; + } + + net_buf_simple_restore(&buf->b, &sdp->recv_state); + (void)bt_sdp_recv(&sdp->chan.chan, buf); + net_buf_unref(buf); + } +} + +static K_WORK_DEFINE(sdp_svc_worker, sdp_svc_work_handler); + +/* @brief Callback for SDP connection accept + * + * Gets called when an incoming SDP connection needs to be authorized. + * Registers the L2CAP callbacks and allocates an SDP context to the + * connection + * + * @param conn BT connection object + * @param chan L2CAP channel structure (to be returned) + * + * @return 0 for success, or relevant error code + */ + +static int bt_sdp_accept( + struct bt_conn *conn, struct bt_l2cap_server *server, + struct bt_l2cap_chan **chan) +{ + static const struct bt_l2cap_chan_ops ops = + { + .connected = bt_sdp_connected, + .disconnected = bt_sdp_disconnected, + .recv = bt_sdp_recv, + }; + + int i; + + LOG_DBG("conn %p", conn); + + for (i = 0; i < ARRAY_SIZE(bt_sdp_pool); i++) + { + struct bt_sdp *sdp = &bt_sdp_pool[i]; + + if (sdp->chan.chan.conn) + { + continue; + } + + sdp->chan.chan.ops = &ops; + sdp->chan.rx.mtu = SDP_MTU; + + *chan = &sdp->chan.chan; + + return 0; + } + + LOG_ERR("No available SDP context for conn %p", conn); + + return -ENOMEM; +} + +void bt_sdp_init(void) +{ + __maybe_unused int err; + + static bool initialized; + static struct bt_l2cap_server server = + { + .psm = SDP_PSM, + .accept = bt_sdp_accept, + .sec_level = BT_SECURITY_L0, + }; + + if (initialized) + { + return; + } + + err = bt_l2cap_br_server_register(&server); + if ((err != 0) && (err != -EEXIST)) + { + LOG_ERR("Failed to register SDP L2CAP server (error %d)", err); + return; + } + + ARRAY_FOR_EACH(bt_sdp_client_pool, i) + { + k_queue_init(&bt_sdp_client_pool[i].reqs_pending); + } + + initialized = true; +} + +int bt_sdp_register_service(struct bt_sdp_record *service) +{ + uint8_t index = 0; + + if (!service) + { + LOG_ERR("No service record specified"); + return 0; + } + + if (list_find(&sdp_db, &service->node, NULL)) + { + LOG_ERR("Service already registered"); + return -EEXIST; + } + + if (!list_is_empty(&sdp_db)) + { + struct bt_sdp_record *last; + + last = CONTAINER_OF(list_peek_tail(&sdp_db), struct bt_sdp_record, + node); + index = last->index + 1; + + if (last->index > index) + { + LOG_ERR("Registered record is full"); + return -EOVERFLOW; + } + } + + service->index = index; + service->handle = SDP_SERVICE_HANDLE_BASE + index; + *((uint32_t *)(service->attrs[0].val.data)) = service->handle; + + list_add_tail(&sdp_db, &service->node); + + LOG_DBG("Service registered at %u", service->handle); + + return 0; +} + +int bt_sdp_unregister_service(struct bt_sdp_record *service) +{ + if (service == NULL) + { + return -EINVAL; + } + + ARRAY_FOR_EACH(bt_sdp_pool, i) + { + if (bt_sdp_pool[i].chan.chan.conn != NULL) + { + LOG_WRN("Active SDP channel exists on conn %p", + (void *)bt_sdp_pool[i].chan.chan.conn); + return -EBUSY; + } + } + + if (!list_find_and_remove(&sdp_db, &service->node)) + { + return -ENOENT; + } + + LOG_DBG("Service unregistered at %u", service->handle); + + return 0; +} + +static void sdp_client_req_cleanup(struct bt_sdp_client *session) +{ + /* Clean current TID */ + + session->tid = 0U; + + /* Invalidate cached param in context */ + + session->param = NULL; + + /* Reset continuation state in current context */ + + memset(&session->cstate, 0, sizeof(session->cstate)); + + /* Release RX buffer */ + + net_buf_drop(&session->rec_buf); + + /* Clear total length */ + + session->total_len = 0U; + + /* Clear received length */ + + session->recv_len = 0U; + + /* Clear flags */ + + atomic_set(session->flags, 0); +} + +static void sdp_client_cleanup(struct bt_sdp_client *session) +{ + sdp_client_req_cleanup(session); + session->state = SDP_CLIENT_DISCONNECTED; +} + +static int sdp_client_discover(struct bt_sdp_client *session); + +static int sdp_client_disconnect(struct bt_sdp_client *session) +{ + struct bt_l2cap_chan *chan = &session->chan.chan; + int err; + enum sdp_client_state state; + + if (session->state == SDP_CLIENT_DISCONNECTING || + session->state == SDP_CLIENT_DISCONNECTED) + { + return 0; + } + + state = session->state; + session->state = SDP_CLIENT_DISCONNECTING; + err = bt_l2cap_chan_disconnect(chan); + if (err != 0) + { + session->state = state; + LOG_ERR("Failed to send l2cap disconnect req on session %p (%d)", + session, err); + } + + return err; +} + +static void sdp_client_params_iterator(struct bt_sdp_client *session) +{ + LOG_DBG(""); + + /* Cleanup current SDP discovery state */ + + sdp_client_req_cleanup(session); + + /* Check if there's valid next pending request */ + + if (k_queue_peek_head(&session->reqs_pending) != NULL) + { + sdp_client_discover(session); + return; + } + + /* No pending requests, disconnect channel */ + + (void)sdp_client_disconnect(session); +} + +static int sdp_client_get_total( + struct bt_sdp_client *session, struct net_buf *buf, uint16_t *total, + uint16_t *removed) +{ + struct net_buf_simple_state state; + int err; + uint8_t seq; + uint32_t len; + + *total = 0; + *removed = 0; + + /* Pull value of total octets of all attributes available to be + * collected when response gets completed for given UUID. Such info can be + * get from the very first response frame after initial SSA request was + * sent. For subsequent calls related to the same SSA request input buf and + * in/out function parameters stays neutral. + */ + + if (session->cstate.length != 0U) + { + return 0; + } + + net_buf_simple_save(&buf->b, &state); + + if (buf->len < sizeof(seq)) + { + LOG_WRN("Buffer size %u too small for sequence type", buf->len); + err = -EMSGSIZE; + goto failed; + } + + seq = net_buf_pull_u8(buf); + if (((seq & BT_SDP_TYPE_DESC_MASK) != BT_SDP_SEQ_UNSPEC) || + ((seq & BT_SDP_SIZE_DESC_MASK) < BT_SDP_SIZE_INDEX_OFFSET)) + { + LOG_WRN("Sequence type 0x%02x not valid", seq); + err = -EINVAL; + goto failed; + } + + if (buf->len < + BIT((seq & BT_SDP_SIZE_DESC_MASK) - BT_SDP_SIZE_INDEX_OFFSET)) + { + LOG_WRN("Malformed packet"); + err = -EMSGSIZE; + goto failed; + } + + switch (seq) + { + case BT_SDP_SEQ8: + *total = net_buf_pull_u8(buf); + break; + case BT_SDP_SEQ16: + *total = net_buf_pull_be16(buf); + break; + case BT_SDP_SEQ32: + len = net_buf_pull_be32(buf); + if (len > UINT16_MAX) + { + LOG_WRN("Sequence length exceeds uint16_t max"); + err = -E2BIG; + goto failed; + } + + *total = len; + break; + default: + LOG_ERR("seq has been checked, should not reach here"); + CODE_UNREACHABLE; + } + + *removed = state.len - buf->len; + LOG_DBG("Total %u octets of all attributes", *total); + return 0; + +failed: + net_buf_simple_restore(&buf->b, &state); + return err; +} + +static uint16_t get_ss_record_len(struct net_buf *buf) +{ + if (buf->len >= SDP_RECORD_HANDLE_SIZE) + { + return SDP_RECORD_HANDLE_SIZE; + } + + LOG_WRN("Invalid service record handle length"); + return 0; +} + +static uint16_t get_ssa_record_len(struct net_buf *buf) +{ + uint16_t len; + uint8_t seq; + + seq = net_buf_pull_u8(buf); + + switch (seq) + { + case BT_SDP_SEQ8: + len = net_buf_pull_u8(buf); + break; + case BT_SDP_SEQ16: + len = net_buf_pull_be16(buf); + break; + case BT_SDP_SEQ32: + len = net_buf_pull_be32(buf); + break; + default: + LOG_WRN("Sequence type 0x%02x not handled", seq); + len = 0U; + break; + } + + return len; +} + +static uint16_t get_record_len(struct bt_sdp_client *session) +{ + struct net_buf *buf; + uint16_t len; + + buf = session->rec_buf; + + if (!session->param) + { + return buf->len; + } + + switch (session->param->type) + { + case BT_SDP_DISCOVER_SERVICE_SEARCH: + len = get_ss_record_len(buf); + break; + case BT_SDP_DISCOVER_SERVICE_SEARCH_ATTR: + len = get_ssa_record_len(buf); + break; + case BT_SDP_DISCOVER_SERVICE_ATTR: + default: + len = buf->len; + break; + } + + LOG_DBG("Record len %u", len); + + return len; +} + +enum uuid_state +{ + UUID_NOT_RESOLVED, + UUID_RESOLVED, + UUID_PARTIAL_RESOLVED, +}; + +static int sdp_client_notify_result( + struct bt_conn *conn, struct bt_sdp_client *session, enum uuid_state state) +{ + struct bt_sdp_client_result result; + uint16_t rec_len; + uint8_t user_ret; + + if ((state == UUID_NOT_RESOLVED) || (session->rec_buf->len == 0U)) + { + result.resp_buf = NULL; + result.next_record_hint = false; + session->param->func(conn, &result, session->param); + return 0; + } + + while (session->rec_buf->len) + { + struct net_buf_simple_state buf_state; + + net_buf_simple_save(&session->rec_buf->b, &buf_state); + rec_len = get_record_len(session); + + /* tell the user about multi record resolution */ + + if (session->rec_buf->len > rec_len) + { + result.next_record_hint = true; + } + else + { + if (state == UUID_PARTIAL_RESOLVED) + { + struct net_buf *buf; + uint8_t *src; + uint8_t *dst; + uint16_t len; + + net_buf_simple_restore(&session->rec_buf->b, &buf_state); + + /* Partial resolution, continue processing */ + + src = session->rec_buf->data; + len = session->rec_buf->len; + + /* The allocated buffer is full. Try to allocate a new buffer + * from the same pool. Use allocated buffer to continue the SDP + * discovery if the new buffer allocated. Otherwise, use the + * current allocated to continue the SDP discovery. + */ + + buf = net_buf_alloc(session->param->pool, 0); + if (buf != NULL) + { + if (net_buf_tailroom(buf) < len) + { + goto no_more_space; + } + + net_buf_add_mem(buf, src, len); + if (net_buf_tailroom(buf) <= + net_buf_tailroom(session->rec_buf)) + { + goto no_more_space; + } + + net_buf_unref(session->rec_buf); + session->rec_buf = buf; + LOG_DBG("Continue discovery with new buf %p", buf); + return 0; + + no_more_space: + LOG_ERR("Allocated buffer has not more space for the next " + "SDP discover. Need to increase date size of the " + "receiving pool."); + net_buf_unref(buf); + return -ENOMEM; + } + + net_buf_reset(session->rec_buf); + dst = net_buf_add(session->rec_buf, len); + if (dst == src) + { + LOG_ERR("No more buffer space for SDP discover. Need to " + "increase buffer size of the receiving pool."); + return -ENOMEM; + } + + memmove(dst, src, len); + LOG_DBG("Continue discovery with current buf"); + return 0; + } + + result.next_record_hint = false; + } + + /* save the original session buffer */ + + net_buf_simple_save(&session->rec_buf->b, &buf_state); + + /* initialize internal result buffer instead of memcpy */ + + result.resp_buf = session->rec_buf; + /* Set user internal result buffer length as same as record + * length to fake user. User will see the individual record length as + * rec_len instead of whole session rec_buf length. + */ + + result.resp_buf->len = rec_len; + + user_ret = session->param->func(conn, &result, session->param); + + /* restore original session buffer */ + + net_buf_simple_restore(&session->rec_buf->b, &buf_state); + /* sync session buffer data length with next record chunk not + * send to user so far + */ + + net_buf_pull(session->rec_buf, rec_len); + if (user_ret == BT_SDP_DISCOVER_UUID_STOP) + { + return -ECANCELED; + } + } + + return 0; +} + +static int sdp_client_ssa_sa_notify(struct bt_conn *conn, + struct bt_sdp_client *session) +{ + return sdp_client_notify_result(conn, session, UUID_PARTIAL_RESOLVED); +} + +#define GET_PARAM(__node) CONTAINER_OF(__node, struct bt_sdp_discover_params, _node) + +static void sdp_client_req_not_resolved(struct bt_conn *conn, + struct bt_sdp_client *session) +{ + if (session->param != NULL) + { + sdp_client_notify_result(conn, session, UUID_NOT_RESOLVED); + session->param = NULL; + } +} + +static spinlock_t sdp_client_lock; + +static void sdp_client_reqs_pending_not_resolved(struct bt_conn *conn, + struct bt_sdp_client *session) +{ + void *node; + irqstate_t key; + + key = spin_lock_irqsave(&sdp_client_lock); + node = k_queue_get(&session->reqs_pending, 0); + while (node != NULL) + { + session->param = GET_PARAM(node); + spin_unlock_irqrestore(&sdp_client_lock, key); + sdp_client_req_not_resolved(conn, session); + bt_conn_unref(conn); + + key = spin_lock_irqsave(&sdp_client_lock); + node = k_queue_get(&session->reqs_pending, 0); + } + + spin_unlock_irqrestore(&sdp_client_lock, key); +} + +static void sdp_client_work_handler(struct k_work *work) +{ + ARG_UNUSED(work); + + ARRAY_FOR_EACH(bt_sdp_client_pool, i) + { + struct bt_sdp_client *session = &bt_sdp_client_pool[i]; + struct bt_conn *conn = bt_conn_lookup_index(i); + int err; + + /* The check should remain in the first position */ + + if (conn == NULL) + { + continue; + } + + /* The check should remain in the second position */ + + if (conn->state != BT_CONN_CONNECTED) + { + goto failed; + } + + if (session->state == SDP_CLIENT_DISCONNECTING) + { + bt_conn_unref(conn); + continue; + } + +/* There are two cases for this condition: + * Case 1: It is for the case that the session is failed to disconnect, + * Case 2: Avoid to call sdp_client_params_iterator() in the function + * sdp_client_discover(). + */ + + if (session->state == SDP_CLIENT_CONNECTED && session->param == NULL) + { + /* Get next request and start resolving it */ + + sdp_client_params_iterator(session); + bt_conn_unref(conn); + continue; + } + + /* Retry the SDP discovery request */ + + if (session->state == SDP_CLIENT_CONNECTED && + atomic_test_and_clear_bit(session->flags, + SDP_CLIENT_FLAG_WAIT_FOR_TX_BUF)) + { + sdp_client_discover(session); + bt_conn_unref(conn); + continue; + } + + if (k_queue_peek_head(&session->reqs_pending) == NULL) + { + bt_conn_unref(conn); + continue; + } + + if (session->state == SDP_CLIENT_DISCONNECTED) + { + err = sdp_client_new_session(conn, session); + if (err != 0) + { + LOG_ERR("Failed to create sdp session (%d)", err); + goto failed; + } + } + + bt_conn_unref(conn); + continue; + +failed: + sdp_client_req_not_resolved(conn, session); + sdp_client_reqs_pending_not_resolved(conn, session); + sdp_client_cleanup(session); + bt_conn_unref(conn); + } +} + +static K_WORK_DEFINE(sdp_client_worker, sdp_client_work_handler); + +static void sdp_destroy(struct net_buf *buf) +{ + net_buf_destroy(buf); + + bt_work_submit(&sdp_svc_worker); + bt_work_submit(&sdp_client_worker); +} + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + size_t index; + struct bt_sdp_client *session; + + index = bt_conn_index(conn); + DEBUGASSERT(index < ARRAY_SIZE(bt_sdp_client_pool)); + + session = &bt_sdp_client_pool[index]; + + sdp_client_req_not_resolved(conn, session); + sdp_client_reqs_pending_not_resolved(conn, session); + sdp_client_cleanup(session); +} + +BT_CONN_CB_DEFINE(sdp_client_conn_callbacks) = +{ + .disconnected = disconnected, +}; + +/* ServiceSearch PDU, ref to BT Core 5.4, Vol 3, part B, 4.5.1 */ + +static int sdp_client_ss_search(struct bt_sdp_client *session, + const struct bt_sdp_discover_params *param) +{ + struct net_buf *buf; + uint8_t uuid128[BT_UUID_SIZE_128]; + + buf = sdp_client_create_pdu(); + if (buf == NULL) + { + LOG_WRN("No net buffers available"); + return -ENOBUFS; + } + + /* BT_SDP_SEQ8 means length of sequence is on additional next byte */ + + net_buf_add_u8(buf, BT_SDP_SEQ8); + + switch (param->uuid->type) + { + case BT_UUID_TYPE_16: + + /* Seq length */ + + net_buf_add_u8(buf, sizeof(uint8_t) + BT_UUID_SIZE_16); + + /* Seq type */ + + net_buf_add_u8(buf, BT_SDP_UUID16); + + /* Seq value */ + + net_buf_add_be16(buf, BT_UUID_16(param->uuid)->val); + break; + case BT_UUID_TYPE_32: + net_buf_add_u8(buf, sizeof(uint8_t) + BT_UUID_SIZE_32); + net_buf_add_u8(buf, BT_SDP_UUID32); + net_buf_add_be32(buf, BT_UUID_32(param->uuid)->val); + break; + case BT_UUID_TYPE_128: + net_buf_add_u8(buf, sizeof(uint8_t) + BT_UUID_SIZE_128); + net_buf_add_u8(buf, BT_SDP_UUID128); + sys_memcpy_swap(uuid128, BT_UUID_128(param->uuid)->val, + sizeof(uuid128)); + net_buf_add_mem(buf, uuid128, sizeof(uuid128)); + break; + default: + LOG_ERR("Unknown UUID type %u", param->uuid->type); + net_buf_unref(buf); + return -EINVAL; + } + + /* Set maximum number of service record handles */ + + net_buf_add_be16(buf, + net_buf_tailroom(session->rec_buf) / SDP_RECORD_HANDLE_SIZE); + /* Update and validate PDU ContinuationState. Initial SSA Request has + * zero length continuation state since no interaction has place with + * server so far, otherwise use the original state taken from remote's last + * response PDU that is cached by SDP client context. + */ + + if (session->cstate.length == 0U) + { + net_buf_add_u8(buf, 0x00); + } + else + { + net_buf_add_u8(buf, session->cstate.length); + net_buf_add_mem(buf, session->cstate.data, session->cstate.length); + } + + session->tid++; + + return bt_sdp_send(&session->chan.chan, buf, BT_SDP_SVC_SEARCH_REQ, + session->tid); +} + +/* Encoded size of the AttributeIDList elements, excluding the sequence + * header + */ + +static size_t sdp_client_get_attribute_id_list_len( + const struct bt_sdp_attribute_id_list *ids) +{ + size_t len = 0; + + if (ids == NULL || ids->count == 0) + { + return sizeof(uint8_t) + sizeof(uint32_t); + } + + for (size_t i = 0; i < ids->count; i++) + { + if (ids->ranges[i].beginning == ids->ranges[i].ending) + { + len += sizeof(uint8_t) + sizeof(uint16_t); + } + else + { + len += sizeof(uint8_t) + sizeof(uint32_t); + } + } + + return len; +} + +/* Size of the sequence header of an AttributeIDList with @p len bytes of + * elements + */ + +static size_t sdp_client_get_attribute_id_list_hdr_len(size_t len) +{ + if (len > UINT8_MAX) + { + return sizeof(uint8_t) + sizeof(uint16_t); + } + + return sizeof(uint8_t) + sizeof(uint8_t); +} + +/* Add the AttributeIDList to the PDU. @p len is the size of its elements as + * returned by sdp_client_get_attribute_id_list_len(), which the caller has + * checked against the tailroom. + */ + +static void sdp_client_add_attribute_id(struct net_buf *buf, + const struct bt_sdp_attribute_id_list *ids, + size_t len) +{ + /* Sequence definition where data is sequence of elements and where + * additional next byte points the size of elements within + */ + + if (len > UINT8_MAX) + { + net_buf_add_u8(buf, BT_SDP_SEQ16); + net_buf_add_be16(buf, len); + } + else + { + net_buf_add_u8(buf, BT_SDP_SEQ8); + net_buf_add_u8(buf, len); + } + + if (ids == NULL || ids->count == 0) + { + /* Data element definition for two following 16bits range elements */ + + net_buf_add_u8(buf, BT_SDP_UINT32); + + /* Get all attributes. It enables filter out wanted only attributes */ + + net_buf_add_be16(buf, 0x0000); + net_buf_add_be16(buf, 0xffff); + return; + } + + for (size_t i = 0; i < ids->count; i++) + { + if (ids->ranges[i].beginning == ids->ranges[i].ending) + { + /* Data element definition for one following 16bits range elements + */ + + net_buf_add_u8(buf, BT_SDP_UINT16); + + /* Get all attributes. It enables filter out wanted only attributes + */ + + net_buf_add_be16(buf, ids->ranges[i].beginning); + } + else + { + /* Data element definition for two following 16bits range elements + */ + + net_buf_add_u8(buf, BT_SDP_UINT32); + + /* Get all attributes. It enables filter out wanted only attributes + */ + + net_buf_add_be16(buf, ids->ranges[i].beginning); + net_buf_add_be16(buf, ids->ranges[i].ending); + } + } +} + +/* Size of the AttributeIDList and the ContinuationState of the next request + */ + +static size_t sdp_client_get_total_len(const struct bt_sdp_client *session, + size_t ids_len) +{ + return sdp_client_get_attribute_id_list_hdr_len(ids_len) + ids_len + + sizeof(session->cstate.length) + session->cstate.length; +} + +/* ServiceAttribute PDU, ref to BT Core 5.4, Vol 3, part B, 4.6.1 */ + +static int sdp_client_sa_search(struct bt_sdp_client *session, + const struct bt_sdp_discover_params *param) +{ + struct net_buf *buf; + uint16_t len; + size_t ids_len; + + len = net_buf_tailroom(session->rec_buf); + if (!SDP_SA_ATTR_BYTE_IN_RANGE(len)) + { + LOG_WRN("No more space to start next SDP discovery"); + return -ENOMEM; + } + + buf = sdp_client_create_pdu(); + if (buf == NULL) + { + LOG_WRN("No net buffers available"); + return -ENOBUFS; + } + + /* Add service record handle */ + + net_buf_add_be32(buf, param->handle); + + /* Set attribute max bytes count to be returned from server */ + + net_buf_add_be16(buf, len); + + /* Check the tailroom of the buffer */ + + ids_len = sdp_client_get_attribute_id_list_len(param->ids); + if (sdp_client_get_total_len(session, ids_len) > net_buf_tailroom(buf)) + { + LOG_ERR("No space to add attribute ID"); + net_buf_unref(buf); + return -ENOMEM; + } + + /* Add attribute ID List */ + + sdp_client_add_attribute_id(buf, param->ids, ids_len); + + /* Update and validate PDU ContinuationState. Initial SSA Request has + * zero length continuation state since no interaction has place with + * server so far, otherwise use the original state taken from remote's last + * response PDU that is cached by SDP client context. + */ + + if (session->cstate.length == 0U) + { + net_buf_add_u8(buf, 0x00); + } + else + { + net_buf_add_u8(buf, session->cstate.length); + net_buf_add_mem(buf, session->cstate.data, session->cstate.length); + } + + session->tid++; + + return bt_sdp_send(&session->chan.chan, buf, BT_SDP_SVC_ATTR_REQ, + session->tid); +} + +/* ServiceSearchAttribute PDU, ref to BT Core 4.2, Vol 3, part B, 4.7.1 */ + +static int sdp_client_ssa_search(struct bt_sdp_client *session, + const struct bt_sdp_discover_params *param) +{ + struct net_buf *buf; + uint8_t uuid128[BT_UUID_SIZE_128]; + uint16_t len; + size_t ids_len; + + len = net_buf_tailroom(session->rec_buf); + if (!SDP_SSA_ATTR_BYTE_IN_RANGE(len)) + { + int err; + + LOG_WRN("No more space to start next SDP discovery"); + + if (session->rec_buf->len == 0) + { + return -ENOMEM; + } + + /* Notify current received data */ + + err = sdp_client_ssa_sa_notify(session->chan.chan.conn, session); + if (err != 0) + { + LOG_ERR("Failed to notify received data: %d", err); + return err; + } + + len = net_buf_tailroom(session->rec_buf); + if (!SDP_SSA_ATTR_BYTE_IN_RANGE(len)) + { + LOG_WRN("No more space of buffer for SDP discovery"); + return -ENOMEM; + } + } + + buf = sdp_client_create_pdu(); + if (buf == NULL) + { + LOG_WRN("No net buffers available"); + return -ENOBUFS; + } + + /* BT_SDP_SEQ8 means length of sequence is on additional next byte */ + + net_buf_add_u8(buf, BT_SDP_SEQ8); + + switch (param->uuid->type) + { + case BT_UUID_TYPE_16: + + /* Seq length */ + + net_buf_add_u8(buf, sizeof(uint8_t) + BT_UUID_SIZE_16); + + /* Seq type */ + + net_buf_add_u8(buf, BT_SDP_UUID16); + + /* Seq value */ + + net_buf_add_be16(buf, BT_UUID_16(param->uuid)->val); + break; + case BT_UUID_TYPE_32: + net_buf_add_u8(buf, sizeof(uint8_t) + BT_UUID_SIZE_32); + net_buf_add_u8(buf, BT_SDP_UUID32); + net_buf_add_be32(buf, BT_UUID_32(param->uuid)->val); + break; + case BT_UUID_TYPE_128: + net_buf_add_u8(buf, sizeof(uint8_t) + BT_UUID_SIZE_128); + net_buf_add_u8(buf, BT_SDP_UUID128); + sys_memcpy_swap(uuid128, BT_UUID_128(param->uuid)->val, + sizeof(uuid128)); + net_buf_add_mem(buf, uuid128, sizeof(uuid128)); + break; + default: + LOG_ERR("Unknown UUID type %u", param->uuid->type); + net_buf_unref(buf); + return -EINVAL; + } + + /* Set attribute max bytes count to be returned from server */ + + net_buf_add_be16(buf, len); + + /* Check the tailroom of the buffer */ + + ids_len = sdp_client_get_attribute_id_list_len(param->ids); + if (sdp_client_get_total_len(session, ids_len) > net_buf_tailroom(buf)) + { + LOG_ERR("No space to add attribute ID"); + net_buf_unref(buf); + return -ENOMEM; + } + + /* Add attribute ID List */ + + sdp_client_add_attribute_id(buf, param->ids, ids_len); + + /* Update and validate PDU ContinuationState. Initial SSA Request has + * zero length continuation state since no interaction has place with + * server so far, otherwise use the original state taken from remote's last + * response PDU that is cached by SDP client context. + */ + + if (session->cstate.length == 0U) + { + net_buf_add_u8(buf, 0x00); + } + else + { + net_buf_add_u8(buf, session->cstate.length); + net_buf_add_mem(buf, session->cstate.data, session->cstate.length); + } + + session->tid++; + + return bt_sdp_send(&session->chan.chan, buf, BT_SDP_SVC_SEARCH_ATTR_REQ, + session->tid); +} + +static void sdp_client_get_pending_req(struct bt_sdp_client *session) +{ + void *node; + irqstate_t key; + + key = spin_lock_irqsave(&sdp_client_lock); + node = k_queue_get(&session->reqs_pending, 0); + if (node != NULL) + { + session->param = GET_PARAM(node); + } + + spin_unlock_irqrestore(&sdp_client_lock, key); + + if (node != NULL) + { + bt_conn_unref(session->chan.chan.conn); + } +} + +static int sdp_client_discover(struct bt_sdp_client *session) +{ + int err; + + /* Select proper user params, if session->param is invalid it means + * getting new UUID from top of to be resolved params list. Otherwise the + * context is in a middle of partial SDP PDU responses and cached value + * from context can be used. + */ + + if (session->param == NULL) + { + sdp_client_get_pending_req(session); + } + + if (session->param == NULL) + { + /* No pending requests, disconnect channel */ + + return sdp_client_disconnect(session); + } + + if (session->rec_buf == NULL) + { + session->rec_buf = net_buf_alloc(session->param->pool, 0); + } + + if (session->rec_buf == NULL) + { + LOG_WRN("Unable to allocate a receive buffer"); + + /* Notify the result */ + + sdp_client_req_not_resolved(session->chan.chan.conn, session); + + /* Cleanup current SDP discovery state */ + + sdp_client_req_cleanup(session); + + /* Trigger client worker to start the next SDP discovery procedure */ + + bt_work_submit(&sdp_client_worker); + return 0; + } + + switch (session->param->type) + { + case BT_SDP_DISCOVER_SERVICE_SEARCH: + err = sdp_client_ss_search(session, session->param); + break; + case BT_SDP_DISCOVER_SERVICE_ATTR: + err = sdp_client_sa_search(session, session->param); + break; + case BT_SDP_DISCOVER_SERVICE_SEARCH_ATTR: + err = sdp_client_ssa_search(session, session->param); + break; + default: + err = -EINVAL; + break; + } + + if (err == -ENOBUFS) + { + atomic_set_bit(session->flags, SDP_CLIENT_FLAG_WAIT_FOR_TX_BUF); + return 0; + } + + if (err != 0) + { + /* Notify the result */ + + sdp_client_req_not_resolved(session->chan.chan.conn, session); + + /* Cleanup current SDP discovery state */ + + sdp_client_req_cleanup(session); + + /* Trigger client worker to start the next SDP discovery procedure */ + + bt_work_submit(&sdp_client_worker); + } + + return 0; +} + +static int sdp_client_receive_ss(struct bt_sdp_client *session, + struct net_buf *buf) +{ + struct bt_sdp_pdu_cstate *cstate; + uint16_t current_count; + uint16_t total_count; + uint32_t record_len; + uint32_t received_count; + + /* Check the buffer len for the total_count field */ + + if (buf->len < sizeof(total_count)) + { + LOG_ERR("Invalid frame payload length"); + return -EINVAL; + } + + /* Get total service record count. */ + + total_count = net_buf_pull_be16(buf); + + /* Check the buffer len for the current_count field */ + + if (buf->len < sizeof(current_count)) + { + LOG_ERR("Invalid frame payload length"); + return -EINVAL; + } + + /* Get current service record count. */ + + current_count = net_buf_pull_be16(buf); + + /* Check valid of current service record count */ + + if (current_count > total_count) + { + LOG_ERR("Invalid current service record count"); + return -EINVAL; + } + + received_count = session->rec_buf->len / SDP_RECORD_HANDLE_SIZE; + if ((received_count + current_count) > total_count) + { + LOG_ERR("Excess data received"); + return -EINVAL; + } + + record_len = current_count * SDP_RECORD_HANDLE_SIZE; + if (record_len >= buf->len) + { + LOG_ERR("Invalid packet"); + return -EINVAL; + } + + /* Get PDU continuation state */ + + cstate = (struct bt_sdp_pdu_cstate *)(buf->data + record_len); + + if (cstate->length > BT_SDP_MAX_PDU_CSTATE_LEN) + { + LOG_ERR("Invalid SDP PDU Continuation State length " + "%u", cstate->length); + return -EINVAL; + } + + if ((record_len + SDP_CONT_STATE_LEN_SIZE + cstate->length) > buf->len) + { + LOG_ERR("Invalid payload length"); + return -EINVAL; + } + + /* No record found for given UUID. The check catches case when + * current response frame has Continuation State shortest and valid and + * this is the first response frame as well. + */ + + if (!current_count && cstate->length == 0U && session->cstate.length == 0U) + { + LOG_WRN("Service record handle 0x%x not " + "found", session->param->handle); + return -EINVAL; + } + + if (record_len > net_buf_tailroom(session->rec_buf)) + { + LOG_WRN("Not enough room for getting records data"); + return -EINVAL; + } + + net_buf_add_mem(session->rec_buf, buf->data, record_len); + net_buf_pull(buf, record_len); + + /* check if current response says there's next portion to be fetched */ + + if (cstate->length) + { + /* Cache original Continuation State in context */ + + memcpy(&session->cstate, cstate, sizeof(struct bt_sdp_pdu_cstate)); + + net_buf_pull(buf, cstate->length + sizeof(cstate->length)); + + /* Request for next portion of attributes data. + * All failure case are handled internally in the function. Ignore the + * return value. + */ + + (void)sdp_client_discover(session); + + return 0; + } + + net_buf_pull(buf, sizeof(cstate->length)); + + LOG_DBG("UUID 0x%s resolved", bt_uuid_str(session->param->uuid)); + sdp_client_notify_result(session->chan.chan.conn, session, UUID_RESOLVED); + + /* Get next UUID and start resolving it */ + + sdp_client_params_iterator(session); + + return 0; +} + +static int sdp_client_receive_ssa_sa(struct bt_sdp_client *session, + struct net_buf *buf) +{ + struct bt_sdp_pdu_cstate *cstate; + uint16_t frame_len; + uint16_t total; + uint16_t removed; + int err; + + /* Check the buffer len for the frame_len field */ + + if (buf->len < sizeof(frame_len)) + { + LOG_ERR("Invalid frame payload length"); + return -EINVAL; + } + + /* Get number of attributes in this frame. */ + + frame_len = net_buf_pull_be16(buf); + + /* Check valid buf len for attribute list and cont state */ + + if (buf->len < frame_len + SDP_CONT_STATE_LEN_SIZE) + { + LOG_ERR("Invalid frame payload length"); + return -EINVAL; + } + + /* Check valid range of attributes length */ + + if ((session->cstate.length == 0) && (frame_len < 2)) + { + LOG_ERR("Invalid attributes data length"); + return -EINVAL; + } + + /* Get PDU continuation state */ + + cstate = (struct bt_sdp_pdu_cstate *)(buf->data + frame_len); + + if (cstate->length > BT_SDP_MAX_PDU_CSTATE_LEN) + { + LOG_ERR("Invalid SDP PDU Continuation State length " + "%u", cstate->length); + return -EINVAL; + } + + if ((frame_len + SDP_CONT_STATE_LEN_SIZE + cstate->length) > buf->len) + { + LOG_ERR("Invalid frame payload length"); + return -EINVAL; + } + + /* No more data found for given UUID and Continuation State length is not + * zero. It means the remaining tailroom of the RX buffer is not enough to + * store the data. Try to notify the received data, and request the next + * portion of data by sending a continuation request. + */ + + if (frame_len == 0 && cstate->length != 0) + { + /* Notify current received data */ + + err = sdp_client_ssa_sa_notify(session->chan.chan.conn, session); + if (err != 0) + { + LOG_ERR("Failed to notify received data: %d", err); + return err; + } + } + + /* No record found for given UUID. The check catches case when + * current response frame has Continuation State shortest and valid and + * this is the first response frame as well. + */ + + if (frame_len == 2U && cstate->length == 0U && session->cstate.length == + 0U) + { + LOG_WRN("Record for UUID 0x%s not found", + bt_uuid_str(session->param->uuid)); + return -EINVAL; + } + + /* Get total value of all attributes to be collected */ + + err = sdp_client_get_total(session, buf, &total, &removed); + if (err != 0) + { + LOG_ERR("Failed to get total value: %d", err); + return err; + } + + if (frame_len < removed) + { + LOG_ERR("Invalid frame length"); + return -EINVAL; + } + + frame_len -= removed; + + /* If total is not 0, there are two valid cases, + * Case 1, the continuation state length is 0, the frame_len should equal + * total, Case 2, the continuation state length is not 0, it means there + * are more data will be received. So the frame_len is less than total. + */ + + if (total && (frame_len > total)) + { + LOG_ERR("Invalid attribute lists"); + return -EINVAL; + } + + if (session->cstate.length == 0U) + { + session->total_len = total; + } + + session->recv_len += frame_len; + + if (frame_len > net_buf_tailroom(session->rec_buf)) + { + LOG_WRN("Not enough room for getting records data"); + return -EINVAL; + } + + net_buf_add_mem(session->rec_buf, buf->data, frame_len); + net_buf_pull(buf, frame_len); + + /* check if current response says there's next portion to be fetched */ + + if (cstate->length) + { + /* Cache original Continuation State in context */ + + memcpy(&session->cstate, cstate, sizeof(struct bt_sdp_pdu_cstate)); + + net_buf_pull(buf, cstate->length + sizeof(cstate->length)); + + /* Request for next portion of attributes data. + * All failure case are handled internally in the function. Ignore the + * return value. + */ + + (void)sdp_client_discover(session); + + return 0; + } + + if (session->total_len && (session->recv_len != session->total_len)) + { + LOG_WRN("The received len %d is mismatched with total len %d", + session->recv_len, + session->total_len); + return -EINVAL; + } + + net_buf_pull(buf, sizeof(cstate->length)); + + LOG_DBG("UUID 0x%s resolved", bt_uuid_str(session->param->uuid)); + sdp_client_notify_result(session->chan.chan.conn, session, UUID_RESOLVED); + + /* Get next UUID and start resolving it */ + + sdp_client_params_iterator(session); + + return 0; +} + +static int sdp_client_receive(struct bt_l2cap_chan *chan, + struct net_buf *buf) +{ + struct bt_sdp_client *session = SDP_CLIENT_CHAN(chan); + struct bt_sdp_hdr *hdr; + uint16_t len; + uint16_t tid; + int err = -EINVAL; + + LOG_DBG("session %p buf %p", session, buf); + + if (buf->len < sizeof(*hdr)) + { + LOG_ERR("Too small SDP PDU"); + return 0; + } + + hdr = net_buf_pull_mem(buf, sizeof(*hdr)); + len = sys_be16_to_cpu(hdr->param_len); + tid = sys_be16_to_cpu(hdr->tid); + + LOG_DBG("SDP PDU tid %u len %u", tid, len); + + if (buf->len != len) + { + LOG_ERR("SDP PDU length mismatch (%u != %u)", buf->len, len); + return 0; + } + + if (tid != session->tid) + { + LOG_ERR("Mismatch transaction ID value in SDP PDU"); + return 0; + } + + if (session->param == NULL) + { + LOG_WRN("No request in progress"); + return 0; + } + + switch (hdr->op_code) + { + case BT_SDP_SVC_SEARCH_RSP: + err = sdp_client_receive_ss(session, buf); + break; + case BT_SDP_SVC_ATTR_RSP: + case BT_SDP_SVC_SEARCH_ATTR_RSP: + err = sdp_client_receive_ssa_sa(session, buf); + break; + case BT_SDP_ERROR_RSP: + LOG_INF("Invalid SDP request"); + break; + default: + LOG_DBG("PDU 0x%0x response not handled", hdr->op_code); + break; + } + + if (err < 0) + { + sdp_client_req_not_resolved(session->chan.chan.conn, session); + sdp_client_params_iterator(session); + } + + return 0; +} + +static void sdp_client_connected(struct bt_l2cap_chan *chan) +{ + struct bt_sdp_client *session = SDP_CLIENT_CHAN(chan); + + LOG_DBG("session %p chan %p connected", session, chan); + + session->state = SDP_CLIENT_CONNECTED; + + sdp_client_discover(session); +} + +static void sdp_client_disconnected(struct bt_l2cap_chan *chan) +{ + struct bt_sdp_client *session = SDP_CLIENT_CHAN(chan); + + LOG_DBG("session %p chan %p disconnected", session, chan); + + sdp_client_req_not_resolved(chan->conn, session); + sdp_client_cleanup(session); + + /* Check if there is any pending request in fifo */ + + bt_work_submit(&sdp_client_worker); +} + +static int sdp_client_chan_connect(struct bt_conn *conn, + struct bt_sdp_client *session) +{ + return bt_l2cap_br_chan_connect(conn, &session->chan.chan, SDP_PSM); +} + +static int sdp_client_new_session(struct bt_conn *conn, + struct bt_sdp_client *session) +{ + int err; + static const struct bt_l2cap_chan_ops sdp_client_chan_ops = + { + .connected = sdp_client_connected, + .disconnected = sdp_client_disconnected, + .recv = sdp_client_receive, + }; + + session->chan.chan.ops = &sdp_client_chan_ops; + session->chan.rx.mtu = SDP_CLIENT_MTU; + + err = sdp_client_chan_connect(conn, session); + if (err != 0) + { + LOG_ERR("Cannot connect %d", err); + return err; + } + + session->state = SDP_CLIENT_CONNECTING; + return err; +} + +int bt_sdp_discover(struct bt_conn *conn, + struct bt_sdp_discover_params *params) +{ + struct bt_sdp_client *session; + struct bt_conn *acl; + size_t index; + size_t ids_len; + + if (conn == NULL || conn->state != BT_CONN_CONNECTED || + !bt_conn_is_type(conn, BT_CONN_TYPE_BR)) + { + return -ENOTCONN; + } + + if (params == NULL || params->uuid == NULL || params->func == NULL || + params->pool == NULL || + (params->ids != NULL && params->ids->count != 0 && + params->ids->ranges == NULL)) + { + LOG_WRN("Invalid user params"); + return -EINVAL; + } + + if (params->ids != NULL) + { + for (index = 0; index < params->ids->count; index++) + { + struct bt_sdp_attribute_id_range *range; + + range = ¶ms->ids->ranges[index]; + if (range->beginning <= range->ending) + { + continue; + } + + LOG_WRN("Invalid range %u > %u", range->beginning, range->ending); + return -EINVAL; + } + } + + ids_len = sdp_client_get_attribute_id_list_len(params->ids); + if (sdp_client_get_attribute_id_list_hdr_len(ids_len) + + ids_len > SDP_CLIENT_ATTR_ID_LIST_MAX_LEN) + { + LOG_WRN("Attribute ID list of %zu bytes does not fit in a request PDU", + ids_len); + return -EINVAL; + } + + acl = bt_conn_ref(conn); + if (acl == NULL) + { + LOG_ERR("Invalid connection"); + return -ENOTCONN; + } + + index = (size_t)bt_conn_index(acl); + DEBUGASSERT(index < ARRAY_SIZE(bt_sdp_client_pool)); + + session = &bt_sdp_client_pool[index]; + + k_queue_append(&session->reqs_pending, ¶ms->_node); + + bt_work_submit(&sdp_client_worker); + return 0; +} + +int bt_sdp_discover_cancel(struct bt_conn *conn, + struct bt_sdp_discover_params *params) +{ + struct bt_sdp_client *session; + size_t index; + irqstate_t key; + bool found; + + if (conn == NULL || params == NULL) + { + LOG_WRN("Invalid user params"); + return -EINVAL; + } + + index = (size_t)bt_conn_index(conn); + DEBUGASSERT(index < ARRAY_SIZE(bt_sdp_client_pool)); + + session = &bt_sdp_client_pool[index]; + + key = spin_lock_irqsave(&sdp_client_lock); + if (session->param == params) + { + /* The request is being resolved: a request PDU is out and its + * response may already be under processing, so it is too late to keep + * the callback from being called. + */ + + spin_unlock_irqrestore(&sdp_client_lock, key); + return -EINPROGRESS; + } + + found = k_queue_remove(&session->reqs_pending, ¶ms->_node); + spin_unlock_irqrestore(&sdp_client_lock, key); + + if (!found) + { + return -ESRCH; + } + + bt_conn_unref(conn); + return 0; +} + +/* Helper getting length of data determined by DTD for integers */ + +static int sdp_pass_value_u16(struct bt_sdp_attr_value *value, + uint16_t *param) +{ + if (value->type != BT_SDP_ATTR_VALUE_TYPE_UINT) + { + LOG_WRN("Mismatched type %u != %u", value->type, + BT_SDP_ATTR_VALUE_TYPE_UINT); + return -EINVAL; + } + + switch (value->uint.size) + { + case sizeof(uint8_t): + *param = value->uint.u8; + break; + case sizeof(uint16_t): + *param = value->uint.u16; + break; + default: + LOG_WRN("Mismatched size %u != %u", value->uint.size, + sizeof(*param)); + return -EINVAL; + } + + return 0; +} + +int bt_sdp_get_proto_param(const struct net_buf *buf, + uint16_t proto, uint16_t *param) +{ + struct bt_sdp_attribute attr; + struct bt_sdp_attr_value value; + int err; + + if ((buf == NULL) || (param == NULL)) + { + LOG_ERR("Invalid parameter"); + return -EINVAL; + } + + if ((proto != BT_SDP_PROTO_RFCOMM) && (proto != BT_SDP_PROTO_L2CAP) && + (proto != BT_SDP_PROTO_AVDTP)) + { + LOG_ERR("Invalid protocol specifier"); + return -EINVAL; + } + + err = bt_sdp_get_attr(buf, BT_SDP_ATTR_PROTO_DESC_LIST, &attr); + if (err < 0) + { + LOG_WRN("Attribute 0x%04x not found, err %d", + BT_SDP_ATTR_PROTO_DESC_LIST, err); + return err; + } + + err = bt_sdp_attr_read(&attr, BT_UUID_DECLARE_16(proto), &value); + if (err < 0) + { + LOG_WRN("Protocol specifier 0x%04x not found, err %d", proto, err); + return err; + } + + return sdp_pass_value_u16(&value, param); +} + +int bt_sdp_get_addl_proto_param( + const struct net_buf *buf, uint16_t proto, uint8_t index, uint16_t *param) +{ + struct bt_sdp_attribute attr; + struct bt_sdp_attr_value value; + ssize_t count; + int err; + + if ((buf == NULL) || (param == NULL)) + { + LOG_ERR("Invalid parameter"); + return -EINVAL; + } + + if ((proto != BT_SDP_PROTO_RFCOMM) && (proto != BT_SDP_PROTO_L2CAP) && + (proto != BT_SDP_PROTO_AVDTP)) + { + LOG_ERR("Invalid protocol specifier"); + return -EINVAL; + } + + err = bt_sdp_get_attr(buf, BT_SDP_ATTR_ADD_PROTO_DESC_LIST, &attr); + if (err < 0) + { + LOG_WRN("Attribute 0x%04x not found, err %d", + BT_SDP_ATTR_PROTO_DESC_LIST, err); + return err; + } + + count = bt_sdp_attr_addl_proto_count(&attr); + if (count <= 0) + { + LOG_ERR("No attribute value"); + return -EINVAL; + } + + if (index >= count) + { + LOG_ERR("Index out of range 0 ~ %d", count - 1); + return -EINVAL; + } + + err = bt_sdp_attr_addl_proto_read(&attr, index, BT_UUID_DECLARE_16(proto), + &value); + if (err < 0) + { + LOG_WRN("Protocol specifier 0x%04x not found, err %d", proto, err); + return err; + } + + return sdp_pass_value_u16(&value, param); +} + +int bt_sdp_get_profile_version(const struct net_buf *buf, + uint16_t profile, uint16_t *version) +{ + struct bt_sdp_attribute attr; + struct bt_sdp_attr_value value; + int err; + + if ((buf == NULL) || (version == NULL)) + { + LOG_ERR("Invalid parameter"); + return -EINVAL; + } + + err = bt_sdp_get_attr(buf, BT_SDP_ATTR_PROFILE_DESC_LIST, &attr); + if (err < 0) + { + LOG_WRN("Attribute 0x%04x not found, err %d", + BT_SDP_ATTR_PROFILE_DESC_LIST, err); + return err; + } + + err = bt_sdp_attr_read(&attr, BT_UUID_DECLARE_16(profile), &value); + if (err < 0) + { + LOG_WRN("Profile 0x%04x not found, err %d", profile, err); + return err; + } + + return sdp_pass_value_u16(&value, version); +} + +int bt_sdp_get_features(const struct net_buf *buf, uint16_t *features) +{ + struct bt_sdp_attribute attr; + struct bt_sdp_attr_value value; + int err; + + if ((buf == NULL) || (features == NULL)) + { + LOG_ERR("Invalid parameter"); + return -EINVAL; + } + + err = bt_sdp_get_attr(buf, BT_SDP_ATTR_SUPPORTED_FEATURES, &attr); + if (err < 0) + { + LOG_WRN("Attribute 0x%04x not found, err %d", + BT_SDP_ATTR_SUPPORTED_FEATURES, err); + return err; + } + + err = bt_sdp_attr_read(&attr, NULL, &value); + if (err < 0) + { + LOG_WRN("Attribute has not value, err %d", err); + return err; + } + + return sdp_pass_value_u16(&value, features); +} + +int bt_sdp_get_vendor_id(const struct net_buf *buf, uint16_t *vendor_id) +{ + struct bt_sdp_attribute attr; + struct bt_sdp_attr_value value; + int err; + + if ((buf == NULL) || (vendor_id == NULL)) + { + LOG_ERR("Invalid parameter"); + return -EINVAL; + } + + err = bt_sdp_get_attr(buf, BT_SDP_ATTR_VENDOR_ID, &attr); + if (err < 0) + { + LOG_WRN("Attribute 0x%04x not found, err %d", BT_SDP_ATTR_VENDOR_ID, + err); + return err; + } + + err = bt_sdp_attr_read(&attr, NULL, &value); + if (err < 0) + { + LOG_WRN("Attribute has not value, err %d", err); + return err; + } + + return sdp_pass_value_u16(&value, vendor_id); +} + +int bt_sdp_get_product_id(const struct net_buf *buf, uint16_t *product_id) +{ + struct bt_sdp_attribute attr; + struct bt_sdp_attr_value value; + int err; + + if ((buf == NULL) || (product_id == NULL)) + { + LOG_ERR("Invalid parameter"); + return -EINVAL; + } + + err = bt_sdp_get_attr(buf, BT_SDP_ATTR_PRODUCT_ID, &attr); + if (err < 0) + { + LOG_WRN("Attribute 0x%04x not found, err %d", BT_SDP_ATTR_PRODUCT_ID, + err); + return err; + } + + err = bt_sdp_attr_read(&attr, NULL, &value); + if (err < 0) + { + LOG_WRN("Attribute has not value, err %d", err); + return err; + } + + return sdp_pass_value_u16(&value, product_id); +} + +static bool sdp_attr_has_len_field(uint8_t type) +{ + switch (type & BT_SDP_TYPE_DESC_MASK) + { + case BT_SDP_TEXT_STR_UNSPEC: + case BT_SDP_SEQ_UNSPEC: + case BT_SDP_ALT_UNSPEC: + case BT_SDP_URL_STR_UNSPEC: + return true; + default: + break; + } + + return false; +} + +static int sdp_attr_pull_len(uint8_t size, + struct net_buf_simple *buf, uint32_t *len) +{ + switch (size) + { + case sizeof(uint8_t): + *len = net_buf_simple_pull_u8(buf); + break; + case sizeof(uint16_t): + *len = net_buf_simple_pull_be16(buf); + break; + case sizeof(uint32_t): + *len = net_buf_simple_pull_be32(buf); + break; + default: + LOG_WRN("Invalid size %u for sequence", size); + return -EINVAL; + } + + return 0; +} + +static int sdp_attr_get_len(uint8_t type, + struct net_buf_simple *buf, uint32_t *len) +{ + if (sdp_attr_has_len_field(type)) + { + int err; + uint8_t size = + BIT((type & BT_SDP_SIZE_DESC_MASK) - BT_SDP_SIZE_INDEX_OFFSET); + + if (buf->len < size) + { + LOG_WRN("Malformed packet"); + return -EBADMSG; + } + + err = sdp_attr_pull_len(size, buf, len); + if (err != 0) + { + return err; + } + } + else + { + *len = BIT(type & BT_SDP_SIZE_DESC_MASK); + } + + if (buf->len < *len) + { + LOG_WRN("Invalid packet %u < %u", buf->len, *len); + return -EBADMSG; + } + + return 0; +} + +static int bt_sdp_parse_attribute(struct net_buf_simple *buf, + struct bt_sdp_attribute *attr) +{ + uint8_t type; + uint32_t len; + int err; + uint8_t *src; + + if (buf->len < (sizeof(uint8_t) + sizeof(attr->id) + sizeof(type))) + { + LOG_WRN("Malformed packet"); + return -EBADMSG; + } + + if (net_buf_simple_pull_u8(buf) != BT_SDP_UINT16) + { + LOG_WRN("Invalid attribute"); + return -EINVAL; + } + + /* Parse attribute ID */ + + attr->id = net_buf_simple_pull_be16(buf); + + attr->val.data = buf->data; + src = buf->data; + + type = net_buf_simple_pull_u8(buf); + err = sdp_attr_get_len(type, buf, &len); + if (err != 0) + { + LOG_WRN("Failed to parse attr %u (err %u)", attr->id, err); + return -EINVAL; + } + + net_buf_simple_pull(buf, len); + + attr->val.total_size = buf->data - src; + attr->val.data_size = attr->val.total_size; + + return 0; +} + +int bt_sdp_record_parse(const struct net_buf *buf, bool( + *func)(const struct bt_sdp_attribute *attr, void *user_data), + void *user_data) +{ + struct net_buf_simple sbuf; + struct bt_sdp_attribute attr; + int err = -ENODATA; + bool stop; + + if ((buf == NULL) || (func == NULL)) + { + return -EINVAL; + } + + net_buf_simple_init_with_data(&sbuf, buf->data, buf->len); + while (sbuf.len > 0) + { + err = bt_sdp_parse_attribute(&sbuf, &attr); + if (err != 0) + { + break; + } + + stop = !func(&attr, user_data); + if (stop) + { + break; + } + } + + return err; +} + +struct bt_sdp_has_attr_data +{ + uint16_t attr_id; + bool found; +}; + +static bool bt_sdp_has_attr_func(const struct bt_sdp_attribute *attr, + void *user_data) +{ + struct bt_sdp_has_attr_data *data; + + data = (struct bt_sdp_has_attr_data *)user_data; + if (attr->id == data->attr_id) + { + data->found = true; + return false; + } + + return true; +} + +bool bt_sdp_has_attr(const struct net_buf *buf, uint16_t attr_id) +{ + struct bt_sdp_has_attr_data data; + int err; + + if (buf == NULL) + { + return false; + } + + data.attr_id = attr_id; + data.found = false; + + err = bt_sdp_record_parse(buf, bt_sdp_has_attr_func, &data); + if (err != 0) + { + return false; + } + + return data.found; +} + +struct bt_sdp_get_attr_data +{ + struct bt_sdp_attribute *attr; + uint16_t attr_id; + bool found; +}; + +static bool bt_sdp_get_attr_func(const struct bt_sdp_attribute *attr, + void *user_data) +{ + struct bt_sdp_get_attr_data *data; + + data = (struct bt_sdp_get_attr_data *)user_data; + + if (attr->id != data->attr_id) + { + return true; + } + + *data->attr = *attr; + data->found = true; + LOG_DBG("Stop iteration after finding the target attribute"); + return false; +} + +int bt_sdp_get_attr(const struct net_buf *buf, + uint16_t attr_id, struct bt_sdp_attribute *attr) +{ + struct bt_sdp_get_attr_data data; + int err; + + if ((buf == NULL) || (attr == NULL)) + { + return -EINVAL; + } + + data.attr = attr; + data.attr_id = attr_id; + data.found = false; + + err = bt_sdp_record_parse(buf, bt_sdp_get_attr_func, &data); + if (err != 0) + { + return err; + } + + if (!data.found) + { + LOG_WRN("Attribute %u not found", attr_id); + return -ENODATA; + } + + return 0; +} + +static bool sdp_attr_is_seq(uint8_t type) +{ + switch (type & BT_SDP_TYPE_DESC_MASK) + { + case BT_SDP_SEQ_UNSPEC: + case BT_SDP_ALT_UNSPEC: + return true; + default: + break; + } + + return false; +} + +static int sdp_attr_get_uint(uint8_t size, struct net_buf_simple *buf, + struct bt_sdp_attr_value_uint *uint) +{ + switch (size) + { + case sizeof(uint8_t): + uint->u8 = net_buf_simple_pull_u8(buf); + break; + case sizeof(uint16_t): + uint->u16 = net_buf_simple_pull_be16(buf); + break; + case sizeof(uint32_t): + uint->u32 = net_buf_simple_pull_be32(buf); + break; + case sizeof(uint64_t): + uint->u64 = net_buf_simple_pull_be64(buf); + break; + case BIT(BT_SDP_UINT128 & BT_SDP_SIZE_DESC_MASK): + memcpy(uint->u128, net_buf_simple_pull_mem(buf, size), size); + break; + default: + LOG_WRN("Invalid size %u", size); + return -EINVAL; + } + + uint->size = size; + return 0; +} + +static int sdp_attr_get_sint(uint8_t size, struct net_buf_simple *buf, + struct bt_sdp_attr_value_int *sint) +{ + switch (size) + { + case sizeof(int8_t): + sint->s8 = (int8_t)net_buf_simple_pull_u8(buf); + break; + case sizeof(int16_t): + sint->s16 = (int16_t)net_buf_simple_pull_be16(buf); + break; + case sizeof(int32_t): + sint->s32 = (int32_t)net_buf_simple_pull_be32(buf); + break; + case sizeof(int64_t): + sint->s64 = (int64_t)net_buf_simple_pull_be64(buf); + break; + case BIT(BT_SDP_INT128 & BT_SDP_SIZE_DESC_MASK): + memcpy(sint->s128, net_buf_simple_pull_mem(buf, size), size); + break; + default: + LOG_WRN("Invalid size %u", size); + return -EINVAL; + } + + sint->size = size; + return 0; +} + +union sdp_attr_uuid +{ + struct bt_uuid u; + struct bt_uuid_16 u16; + struct bt_uuid_32 u32; + struct bt_uuid_128 u128; +}; + +static int sdp_attr_get_uuid(uint8_t size, + struct net_buf_simple *buf, + union sdp_attr_uuid *u) +{ + switch (size) + { + case sizeof(uint16_t): + u->u.type = BT_UUID_TYPE_16; + u->u16.val = net_buf_simple_pull_be16(buf); + break; + case sizeof(uint32_t): + u->u.type = BT_UUID_TYPE_32; + u->u32.val = net_buf_simple_pull_be32(buf); + break; + case BT_UUID_SIZE_128: + u->u.type = BT_UUID_TYPE_128; + sys_memcpy_swap(u->u128.val, net_buf_simple_pull_mem(buf, size), + size); + break; + default: + LOG_WRN("Invalid size %u for sequence", size); + return -EINVAL; + } + + return 0; +} + +static int sdp_attr_val_parse( + uint8_t type, uint32_t len, struct net_buf_simple *buf, + struct bt_sdp_attr_value_pair *pair, union sdp_attr_uuid *u, + struct bt_sdp_attr_value *v) +{ + int err = 0; + + switch (type & BT_SDP_TYPE_DESC_MASK) + { + case BT_SDP_UINT8: + v->type = BT_SDP_ATTR_VALUE_TYPE_UINT; + err = sdp_attr_get_uint(len, buf, &v->uint); + pair->value = v; + break; + case BT_SDP_INT8: + v->type = BT_SDP_ATTR_VALUE_TYPE_SINT; + err = sdp_attr_get_sint(len, buf, &v->sint); + pair->value = v; + break; + case BT_SDP_UUID_UNSPEC: + if (pair->uuid != NULL) + { + LOG_WRN("Unsupported case: ATTR Value is a UUID"); + return -ENOTSUP; + } + + v->type = BT_SDP_ATTR_VALUE_TYPE_NONE; + pair->uuid = &u->u; + err = sdp_attr_get_uuid(len, buf, u); + break; + case BT_SDP_BOOL: + v->type = BT_SDP_ATTR_VALUE_TYPE_BOOL; + v->value = net_buf_simple_pull_u8(buf) > 0 ? true : false; + pair->value = v; + break; + case BT_SDP_TEXT_STR_UNSPEC: + v->type = BT_SDP_ATTR_VALUE_TYPE_TEXT; + v->text.len = len; + v->text.text = net_buf_simple_pull_mem(buf, len); + pair->value = v; + break; + case BT_SDP_URL_STR_UNSPEC: + v->type = BT_SDP_ATTR_VALUE_TYPE_URL; + v->url.len = len; + v->url.url = net_buf_simple_pull_mem(buf, len); + pair->value = v; + break; + case BT_SDP_DATA_NIL: + pair->value = NULL; + break; + default: + LOG_WRN("Unsupported type %u", type); + err = -ENOTSUP; + break; + } + + return err; +} + +static int sdp_attr_parse(struct net_buf_simple *buf, bool( + *func)(const struct bt_sdp_attr_value_pair *value, void *user_data), + void *user_data, + uint8_t nest_level) +{ + struct bt_sdp_attr_value_pair value; + union sdp_attr_uuid u; + struct bt_sdp_attr_value v; + struct net_buf_simple vbuf; + uint32_t len; + int err; + uint8_t type; + + if (buf->len < sizeof(uint8_t)) + { + return 0; + } + + type = net_buf_simple_pull_u8(buf); + + err = sdp_attr_get_len(type, buf, &len); + if (err != 0) + { + return err; + } + + if (len == 0) + { + LOG_DBG("No valid data found"); + return 0; + } + + if (nest_level == SDP_DATA_ELEM_NEST_LEVEL_MAX) + { + LOG_WRN("Exceed max nesting level (%u). Ignore ATTR data (len %u)", + SDP_DATA_ELEM_NEST_LEVEL_MAX, len); + net_buf_simple_pull_mem(buf, len); + return 0; + } + + /* The following is a data ele sequence, so recursively parse */ + + if (sdp_attr_is_seq(type) && (buf->len > 0) && + sdp_attr_is_seq(buf->data[0])) + { + LOG_DBG("Recursively parse"); + + return sdp_attr_parse(buf, func, user_data, nest_level + 1); + } + + net_buf_simple_init_with_data(&vbuf, net_buf_simple_pull_mem(buf, len), + len); + + if (sdp_attr_is_seq(type)) + { + type = net_buf_simple_pull_u8(&vbuf); + + err = sdp_attr_get_len(type, &vbuf, &len); + if (err != 0) + { + return err; + } + } + + while (vbuf.len > 0) + { + memset(&value, 0, sizeof(value)); + + err = sdp_attr_val_parse(type, len, &vbuf, &value, &u, &v); + if (err != 0) + { + return err; + } + + if ((value.uuid != NULL) && (vbuf.len > 0)) + { + type = vbuf.data[0]; + + /* If the next data is also UUID, parse it in the next sequence. */ + + if ((type & BT_SDP_TYPE_DESC_MASK) == BT_SDP_UUID_UNSPEC) + { + goto out; + } + + type = net_buf_simple_pull_u8(&vbuf); + + err = sdp_attr_get_len(type, &vbuf, &len); + if (err != 0) + { + return err; + } + + err = sdp_attr_val_parse(type, len, &vbuf, &value, &u, &v); + if (err != 0) + { + return err; + } + } + +out: + if (!func(&value, user_data)) + { + return -ECANCELED; + } + + if ((vbuf.len > 0) && sdp_attr_is_seq(vbuf.data[0])) + { + LOG_DBG("Recursively parse if the following data is a sequence"); + + err = sdp_attr_parse(&vbuf, func, user_data, nest_level + 1); + if (err != 0) + { + return err; + } + } + + if (vbuf.len < sizeof(uint8_t)) + { + break; + } + + type = net_buf_simple_pull_u8(&vbuf); + + err = sdp_attr_get_len(type, &vbuf, &len); + if (err != 0) + { + return err; + } + } + + return 0; +} + +int bt_sdp_attr_value_parse(const struct bt_sdp_attribute *attr, bool( + *func)(const struct bt_sdp_attr_value_pair *value, void *user_data), + void *user_data) +{ + struct net_buf_simple buf; + + if ((attr == NULL) || (func == NULL) || + (attr->val.data_size != attr->val.total_size)) + { + return -EINVAL; + } + + net_buf_simple_init_with_data(&buf, (uint8_t *)attr->val.data, + attr->val.data_size); + + while (buf.len > 0) + { + int err; + + err = sdp_attr_parse(&buf, func, user_data, 1); + if (err == -ECANCELED) + { + /* Stopped by upper layer */ + + return 0; + } + + if (err != 0) + { + return err; + } + } + + return 0; +} + +struct bt_sdp_attr_has_uuid_data +{ + const struct bt_uuid *uuid; + bool found; +}; + +static bool sdp_attr_has_uuid_cb(const struct bt_sdp_attr_value_pair *value, + void *user_data) +{ + struct bt_sdp_attr_has_uuid_data *data; + + data = (struct bt_sdp_attr_has_uuid_data *)user_data; + if (value->uuid == NULL) + { + return true; + } + + if (bt_uuid_cmp(value->uuid, data->uuid) != 0) + { + return true; + } + + data->found = true; + return false; +} + +bool bt_sdp_attr_has_uuid(const struct bt_sdp_attribute *attr, + const struct bt_uuid *uuid) +{ + struct bt_sdp_attr_has_uuid_data data; + int err; + + if ((attr == NULL) || (uuid == NULL) || + (attr->val.data_size != attr->val.total_size)) + { + return false; + } + + data.uuid = uuid; + data.found = false; + + err = bt_sdp_attr_value_parse(attr, sdp_attr_has_uuid_cb, &data); + if (err != 0) + { + LOG_WRN("Reported error %d", err); + } + + return data.found; +} + +struct bt_sdp_attr_read_data +{ + const struct bt_uuid *uuid; + struct bt_sdp_attr_value *value; + bool read; +}; + +static bool sdp_attr_read_cb(const struct bt_sdp_attr_value_pair *value, + void *user_data) +{ + struct bt_sdp_attr_read_data *data; + + data = (struct bt_sdp_attr_read_data *)user_data; + if (data->uuid == NULL) + { + goto found; + } + + if (value->uuid == NULL) + { + return true; + } + + if (bt_uuid_cmp(value->uuid, data->uuid) != 0) + { + return true; + } + +found: + if (value->value == NULL) + { + data->value->type = BT_SDP_ATTR_VALUE_TYPE_NONE; + data->read = false; + } + else + { + *data->value = *value->value; + data->read = true; + } + + return false; +} + +int bt_sdp_attr_read( + const struct bt_sdp_attribute *attr, const struct bt_uuid *uuid, + struct bt_sdp_attr_value *value) +{ + struct bt_sdp_attr_read_data data; + int err; + + if ((attr == NULL) || (value == NULL) || + (attr->val.data_size != attr->val.total_size)) + { + return -EINVAL; + } + + data.uuid = uuid; + data.value = value; + data.read = false; + + err = bt_sdp_attr_value_parse(attr, sdp_attr_read_cb, &data); + if (err != 0) + { + LOG_WRN("Reported error %d", err); + } + + if (data.read == true) + { + return 0; + } + + return -ENOENT; +} + +static int bt_sdp_parse_addl_proto(struct net_buf_simple *buf, + struct bt_sdp_attribute *attr) +{ + uint8_t type; + uint32_t len; + int err; + uint8_t *src; + + if (!sdp_attr_is_seq(buf->data[0])) + { + LOG_DBG("Invalid protocol sequence"); + return -EINVAL; + } + + attr->id = BT_SDP_ATTR_PROTO_DESC_LIST; + + attr->val.data = buf->data; + src = buf->data; + + type = net_buf_simple_pull_u8(buf); + err = sdp_attr_get_len(type, buf, &len); + if (err != 0) + { + LOG_WRN("Failed to parse attr %u (err %u)", + BT_SDP_ATTR_ADD_PROTO_DESC_LIST, err); + return -EINVAL; + } + + net_buf_simple_pull(buf, len); + + attr->val.total_size = buf->data - src; + attr->val.data_size = attr->val.total_size; + + return 0; +} + +static int bt_sdp_attr_addl_proto_pre_parse(struct net_buf_simple *buf) +{ + uint8_t type; + uint32_t len; + int err; + struct net_buf_simple_state state; + + if (buf->len < sizeof(type)) + { + LOG_WRN("No attribute value"); + return -EINVAL; + } + + if (!sdp_attr_is_seq(buf->data[0])) + { + LOG_DBG("Invalid protocol sequence"); + return -EINVAL; + } + + net_buf_simple_save(buf, &state); + + type = net_buf_simple_pull_u8(buf); + err = sdp_attr_get_len(type, buf, &len); + if (err != 0) + { + LOG_WRN("Failed to parse attr %u (err %u)", + BT_SDP_ATTR_ADD_PROTO_DESC_LIST, err); + return -EINVAL; + } + + if ((buf->len == 0) || !sdp_attr_is_seq(buf->data[0])) + { + LOG_DBG("Only one protocol descriptor"); + net_buf_simple_restore(buf, &state); + } + + return 0; +} + +int bt_sdp_attr_addl_proto_parse(const struct bt_sdp_attribute *attr, bool( + *func)(const struct bt_sdp_attribute *attr, void *user_data), + void *user_data) +{ + struct net_buf_simple sbuf; + struct bt_sdp_attribute sattr; + int err; + bool stop; + + if ((attr == NULL) || (func == NULL) || + (attr->val.data_size != attr->val.total_size)) + { + return -EINVAL; + } + + if (attr->id != BT_SDP_ATTR_ADD_PROTO_DESC_LIST) + { + LOG_ERR("Unsupported ATTR ID %u != %u", attr->id, + BT_SDP_ATTR_ADD_PROTO_DESC_LIST); + return -ENOTSUP; + } + + net_buf_simple_init_with_data(&sbuf, (uint8_t *)attr->val.data, + attr->val.data_size); + + err = bt_sdp_attr_addl_proto_pre_parse(&sbuf); + if (err != 0) + { + return err; + } + + while (sbuf.len > 0) + { + err = bt_sdp_parse_addl_proto(&sbuf, &sattr); + if (err != 0) + { + break; + } + + stop = !func(&sattr, user_data); + if (stop) + { + break; + } + } + + return err; +} + +struct bt_sdp_attr_addl_proto_count_data +{ + uint16_t count; +}; + +static bool sdp_attr_addl_proto_count_cb(const struct bt_sdp_attribute *attr, + void *user_data) +{ + struct bt_sdp_attr_addl_proto_count_data *data; + + data = (struct bt_sdp_attr_addl_proto_count_data *)user_data; + + data->count++; + + return true; +} + +ssize_t bt_sdp_attr_addl_proto_count(const struct bt_sdp_attribute *attr) +{ + struct bt_sdp_attr_addl_proto_count_data data; + int err; + + if ((attr == NULL) || (attr->val.data_size != attr->val.total_size)) + { + return -EINVAL; + } + + if (attr->id != BT_SDP_ATTR_ADD_PROTO_DESC_LIST) + { + LOG_ERR("Unsupported ATTR ID %u != %u", attr->id, + BT_SDP_ATTR_ADD_PROTO_DESC_LIST); + return -ENOTSUP; + } + + data.count = 0; + + err = bt_sdp_attr_addl_proto_parse(attr, sdp_attr_addl_proto_count_cb, + &data); + if (err < 0) + { + return (ssize_t)err; + } + + return (ssize_t)data.count; +} + +struct bt_sdp_attr_addl_proto_read_data +{ + const struct bt_uuid *uuid; + struct bt_sdp_attr_value *value; + uint16_t target_index; + uint16_t current_index; + bool read; +}; + +static bool bt_sdp_attr_addl_proto_read_cb( + const struct bt_sdp_attribute *attr, void *user_data) +{ + struct bt_sdp_attr_addl_proto_read_data *data; + int err; + + data = (struct bt_sdp_attr_addl_proto_read_data *)user_data; + + if (data->current_index != data->target_index) + { + data->current_index++; + return true; + } + + err = bt_sdp_attr_read(attr, data->uuid, data->value); + if (err == 0) + { + data->read = true; + } + + return false; +} + +int bt_sdp_attr_addl_proto_read( + const struct bt_sdp_attribute *attr, uint16_t index, + const struct bt_uuid *uuid, struct bt_sdp_attr_value *value) +{ + struct bt_sdp_attr_addl_proto_read_data data; + int err; + + if ((attr == NULL) || (value == NULL) || + (attr->val.data_size != attr->val.total_size)) + { + return -EINVAL; + } + + if (attr->id != BT_SDP_ATTR_ADD_PROTO_DESC_LIST) + { + LOG_ERR("Unsupported ATTR ID %u != %u", attr->id, + BT_SDP_ATTR_ADD_PROTO_DESC_LIST); + return -ENOTSUP; + } + + data.current_index = 0; + data.target_index = index; + data.read = false; + data.uuid = uuid; + data.value = value; + + err = bt_sdp_attr_addl_proto_parse(attr, bt_sdp_attr_addl_proto_read_cb, + &data); + if (err != 0) + { + LOG_WRN("Reported error %d", err); + } + + if (data.read == true) + { + return 0; + } + + return -ENOENT; +} diff --git a/libs/zblue/host/classic/sdp_internal.h b/libs/zblue/host/classic/sdp_internal.h new file mode 100644 index 0000000000000..e3985387a8ec8 --- /dev/null +++ b/libs/zblue/host/classic/sdp_internal.h @@ -0,0 +1,79 @@ +/**************************************************************************** + * libs/zblue/host/classic/sdp_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* sdp_internal.h - Service Discovery Protocol handling */ + +/* Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* The PDU identifiers of SDP packets between client and server */ +#define BT_SDP_ERROR_RSP 0x01 +#define BT_SDP_SVC_SEARCH_REQ 0x02 +#define BT_SDP_SVC_SEARCH_RSP 0x03 +#define BT_SDP_SVC_ATTR_REQ 0x04 +#define BT_SDP_SVC_ATTR_RSP 0x05 +#define BT_SDP_SVC_SEARCH_ATTR_REQ 0x06 +#define BT_SDP_SVC_SEARCH_ATTR_RSP 0x07 + +/* Some additions to support service registration. + * These are outside the scope of the Bluetooth specification + */ +#define BT_SDP_SVC_REGISTER_REQ 0x75 +#define BT_SDP_SVC_REGISTER_RSP 0x76 +#define BT_SDP_SVC_UPDATE_REQ 0x77 +#define BT_SDP_SVC_UPDATE_RSP 0x78 +#define BT_SDP_SVC_REMOVE_REQ 0x79 +#define BT_SDP_SVC_REMOVE_RSP 0x80 + +/* SDP Error codes */ +#define BT_SDP_INVALID_VERSION 0x0001 +#define BT_SDP_INVALID_RECORD_HANDLE 0x0002 +#define BT_SDP_INVALID_SYNTAX 0x0003 +#define BT_SDP_INVALID_PDU_SIZE 0x0004 +#define BT_SDP_INVALID_CSTATE 0x0005 + +struct bt_sdp_data_elem_seq +{ + uint8_t type; /* Type: Will be data element sequence */ + uint16_t size; /* We only support 2 byte sizes for now */ +} __packed; + +struct bt_sdp_hdr +{ + uint8_t op_code; + uint16_t tid; + uint16_t param_len; +} __packed; + +struct bt_sdp_svc_rsp +{ + uint16_t total_recs; + uint16_t current_recs; +} __packed; + +struct bt_sdp_att_rsp +{ + uint16_t att_list_len; +} __packed; + +/* Allowed attributes length in SSA Request PDU to be taken from server */ +#define BT_SDP_MAX_ATTR_LEN 0xffff + +/* Max allowed length of PDU Continuation State */ +#define BT_SDP_MAX_PDU_CSTATE_LEN 16 + +/* Type mapping SDP PDU Continuation State */ + +struct bt_sdp_pdu_cstate +{ + uint8_t length; + uint8_t data[BT_SDP_MAX_PDU_CSTATE_LEN]; +} __packed; + +void bt_sdp_init(void); diff --git a/libs/zblue/host/classic/ssp.c b/libs/zblue/host/classic/ssp.c new file mode 100644 index 0000000000000..24e689ad8085a --- /dev/null +++ b/libs/zblue/host/classic/ssp.c @@ -0,0 +1,1044 @@ +/**************************************************************************** + * libs/zblue/host/classic/ssp.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017-2025 Nordic Semiconductor ASA + * Copyright (c) 2015 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#include +#include +#include "l2cap_br_internal.h" + +#define LOG_LEVEL CONFIG_BT_HCI_CORE_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_ssp); + +enum pairing_method +{ + LEGACY, /* Legacy (pre-SSP) pairing */ + JUST_WORKS, /* JustWorks pairing */ + PASSKEY_INPUT, /* Passkey Entry input */ + PASSKEY_DISPLAY, /* Passkey Entry display */ + PASSKEY_CONFIRM, /* Passkey confirm */ +}; + +/* based on table 5.7, Core Spec 4.2, Vol.3 Part C, 5.2.2.6 */ + +static const uint8_t ssp_method[4 /* remote */][4 /* local */] = +{ + { + JUST_WORKS, + JUST_WORKS, + PASSKEY_INPUT, + JUST_WORKS + }, + { + JUST_WORKS, + PASSKEY_CONFIRM, + PASSKEY_INPUT, + JUST_WORKS + }, + { + PASSKEY_DISPLAY, + PASSKEY_DISPLAY, + PASSKEY_INPUT, + JUST_WORKS + }, + { + JUST_WORKS, + JUST_WORKS, + JUST_WORKS, + JUST_WORKS + }, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int pin_code_neg_reply(const bt_addr_t *bdaddr) +{ + struct bt_hci_cp_pin_code_neg_reply *cp; + struct net_buf *buf; + + LOG_DBG(""); + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + bt_addr_copy(&cp->bdaddr, bdaddr); + + return bt_hci_cmd_send_sync(BT_HCI_OP_PIN_CODE_NEG_REPLY, buf, NULL); +} + +static int pin_code_reply(struct bt_conn *conn, const char *pin, uint8_t len) +{ + struct bt_hci_cp_pin_code_reply *cp; + struct net_buf *buf; + + LOG_DBG(""); + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + + bt_addr_copy(&cp->bdaddr, &conn->br.dst); + cp->pin_len = len; + strncpy((char *)cp->pin_code, pin, sizeof(cp->pin_code)); + + return bt_hci_cmd_send_sync(BT_HCI_OP_PIN_CODE_REPLY, buf, NULL); +} + +int bt_conn_auth_pincode_entry(struct bt_conn *conn, const char *pin) +{ + size_t len; + + if (!bt_auth) + { + return -EINVAL; + } + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_BR)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + len = strlen(pin); + if (len > 16) + { + return -EINVAL; + } + + if (conn->required_sec_level == BT_SECURITY_L3 && len < 16) + { + LOG_WRN("PIN code for %s is not 16 bytes wide", + bt_addr_str(&conn->br.dst)); + return -EPERM; + } + + /* Allow user send entered PIN to remote, then reset user state. */ + + if (!atomic_test_and_clear_bit(conn->flags, BT_CONN_USER)) + { + return -EPERM; + } + + if (len == 16) + { + atomic_set_bit(conn->flags, BT_CONN_BR_LEGACY_SECURE); + } + + return pin_code_reply(conn, pin, len); +} + +static void pin_code_req(struct bt_conn *conn) +{ + if (bt_auth && bt_auth->pincode_entry) + { + bool secure = false; + + if (conn->required_sec_level == BT_SECURITY_L3) + { + secure = true; + } + + atomic_set_bit(conn->flags, BT_CONN_USER); + atomic_set_bit(conn->flags, BT_CONN_BR_PAIRING); + bt_auth->pincode_entry(conn, secure); + } + else + { + pin_code_neg_reply(&conn->br.dst); + } +} + +static uint8_t get_io_capa(void) +{ + if (!bt_auth) + { + return BT_IO_NO_INPUT_OUTPUT; + } + + if (bt_auth->passkey_confirm && bt_auth->passkey_display) + { + return BT_IO_DISPLAY_YESNO; + } + + if (bt_auth->passkey_entry) + { + return BT_IO_KEYBOARD_ONLY; + } + + if (bt_auth->passkey_display) + { + return BT_IO_DISPLAY_ONLY; + } + + return BT_IO_NO_INPUT_OUTPUT; +} + +static uint8_t ssp_pair_method(const struct bt_conn *conn) +{ + return ssp_method[conn->br.remote_io_capa][get_io_capa()]; +} + +static uint8_t ssp_get_auth(const struct bt_conn *conn) +{ + bt_security_t max_sec_level; + + /* Check if the MITM is required by service */ + + max_sec_level = bt_l2cap_br_get_max_sec_level(); + + /* The local device shall only set the MITM protection required flag + * if the local device itself requires MITM protection. + */ + + if ((max_sec_level > BT_SECURITY_L2) && + (ssp_pair_method(conn) > JUST_WORKS)) + { + return conn->br.remote_auth | BT_MITM; + } + + /* No MITM protection possible so ignore remote MITM requirement. */ + + return (conn->br.remote_auth & ~BT_MITM); +} + +static int ssp_confirm_reply(struct bt_conn *conn) +{ + struct bt_hci_cp_user_confirm_reply *cp; + struct net_buf *buf; + + LOG_DBG(""); + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + bt_addr_copy(&cp->bdaddr, &conn->br.dst); + + return bt_hci_cmd_send_sync(BT_HCI_OP_USER_CONFIRM_REPLY, buf, NULL); +} + +static int ssp_confirm_neg_reply(struct bt_conn *conn) +{ + struct bt_hci_cp_user_confirm_reply *cp; + struct net_buf *buf; + + LOG_DBG(""); + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + bt_addr_copy(&cp->bdaddr, &conn->br.dst); + + return bt_hci_cmd_send_sync(BT_HCI_OP_USER_CONFIRM_NEG_REPLY, buf, NULL); +} + +static void ssp_pairing_complete(struct bt_conn *conn, uint8_t status) +{ + /* When the ssp pairing complete event notified, + * clear the pairing flag. + */ + + atomic_clear_bit(conn->flags, BT_CONN_BR_PAIRING); + atomic_set_bit_to(conn->flags, BT_CONN_BR_PAIRED, !status); + + LOG_DBG("Pairing completed status %d", status); + + if (!status) + { + bool bond = !atomic_test_bit(conn->flags, BT_CONN_BR_NOBOND); + struct bt_conn_auth_info_cb *listener; + struct bt_conn_auth_info_cb *next; + + list_for_every_container_safe(&bt_auth_info_cbs, listener, next, node) + { + if (listener->pairing_complete) + { + listener->pairing_complete(conn, bond); + } + } + } + else + { + struct bt_conn_auth_info_cb *listener; + struct bt_conn_auth_info_cb *next; + + list_for_every_container_safe(&bt_auth_info_cbs, listener, next, node) + { + if (listener->pairing_failed) + { + listener->pairing_failed(conn, status); + } + } + } +} + +#define BR_SSP_AUTH_MITM_DISABLED(auth) (((auth) & BT_MITM) == 0) + +static void ssp_auth(struct bt_conn *conn, uint32_t passkey) +{ + conn->br.pairing_method = ssp_pair_method(conn); + + if (BR_SSP_AUTH_MITM_DISABLED(conn->br.local_auth) && + BR_SSP_AUTH_MITM_DISABLED(conn->br.remote_auth)) + { + /* If the MITM flag of both sides is false, the pairing method is `just + * works`. + */ + + conn->br.pairing_method = JUST_WORKS; + } + + /* If local required security is HIGH then MITM is mandatory. + * MITM protection is no achievable when SSP 'justworks' is applied. + */ + + if (conn->required_sec_level > BT_SECURITY_L2 && + conn->br.pairing_method == JUST_WORKS) + { + LOG_DBG("MITM protection infeasible for required security"); + ssp_confirm_neg_reply(conn); + return; + } + + switch (conn->br.pairing_method) + { + case PASSKEY_CONFIRM: + atomic_set_bit(conn->flags, BT_CONN_USER); + bt_auth->passkey_confirm(conn, passkey); + break; + case PASSKEY_DISPLAY: + atomic_set_bit(conn->flags, BT_CONN_USER); + bt_auth->passkey_display(conn, passkey); + break; + case PASSKEY_INPUT: + atomic_set_bit(conn->flags, BT_CONN_USER); + bt_auth->passkey_entry(conn); + break; + case JUST_WORKS: + /* When local host works as pairing acceptor and 'justworks' + * model is applied then notify user about such pairing request. [BT + * Core 4.2 table 5.7, Vol 3, Part C, 5.2.2.6] + */ + + if (bt_auth && bt_auth->pairing_confirm && + !atomic_test_bit(conn->flags, BT_CONN_BR_PAIRING_INITIATOR)) + { + atomic_set_bit(conn->flags, BT_CONN_USER); + bt_auth->pairing_confirm(conn); + break; + } + + ssp_confirm_reply(conn); + break; + default: + break; + } +} + +static int ssp_passkey_reply(struct bt_conn *conn, unsigned int passkey) +{ + struct bt_hci_cp_user_passkey_reply *cp; + struct net_buf *buf; + + LOG_DBG(""); + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + bt_addr_copy(&cp->bdaddr, &conn->br.dst); + cp->passkey = sys_cpu_to_le32(passkey); + + return bt_hci_cmd_send_sync(BT_HCI_OP_USER_PASSKEY_REPLY, buf, NULL); +} + +static int ssp_passkey_neg_reply(struct bt_conn *conn) +{ + struct bt_hci_cp_user_passkey_neg_reply *cp; + struct net_buf *buf; + + LOG_DBG(""); + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + bt_addr_copy(&cp->bdaddr, &conn->br.dst); + + return bt_hci_cmd_send_sync(BT_HCI_OP_USER_PASSKEY_NEG_REPLY, buf, NULL); +} + +static int conn_auth(struct bt_conn *conn) +{ + struct bt_hci_cp_auth_requested *auth; + struct net_buf *buf; + + LOG_DBG(""); + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + auth = net_buf_add(buf, sizeof(*auth)); + auth->handle = sys_cpu_to_le16(conn->handle); + + atomic_set_bit(conn->flags, BT_CONN_BR_PAIRING_INITIATOR); + + return bt_hci_cmd_send_sync(BT_HCI_OP_AUTH_REQUESTED, buf, NULL); +} + +int bt_ssp_start_security(struct bt_conn *conn) +{ + if (atomic_test_bit(conn->flags, BT_CONN_BR_PAIRING)) + { + return -EBUSY; + } + + if (get_io_capa() == BT_IO_NO_INPUT_OUTPUT && + conn->required_sec_level > BT_SECURITY_L2) + { + return -EINVAL; + } + + return conn_auth(conn); +} + +int bt_ssp_auth_passkey_entry(struct bt_conn *conn, unsigned int passkey) +{ + /* User entered passkey, reset user state. */ + + if (!atomic_test_and_clear_bit(conn->flags, BT_CONN_USER)) + { + return -EPERM; + } + + if (conn->br.pairing_method == PASSKEY_INPUT) + { + return ssp_passkey_reply(conn, passkey); + } + + return -EINVAL; +} + +int bt_ssp_auth_passkey_confirm(struct bt_conn *conn) +{ + /* Allow user confirm passkey value, then reset user state. */ + + if (!atomic_test_and_clear_bit(conn->flags, BT_CONN_USER)) + { + return -EPERM; + } + + return ssp_confirm_reply(conn); +} + +int bt_ssp_auth_pairing_confirm(struct bt_conn *conn) +{ + return ssp_confirm_reply(conn); +} + +int bt_ssp_auth_cancel(struct bt_conn *conn) +{ + /* Allow user cancel authentication, then reset user state. */ + + if (!atomic_test_and_clear_bit(conn->flags, BT_CONN_USER)) + { + return -EPERM; + } + + switch (conn->br.pairing_method) + { + case JUST_WORKS: + case PASSKEY_CONFIRM: + return ssp_confirm_neg_reply(conn); + case PASSKEY_INPUT: + return ssp_passkey_neg_reply(conn); + case PASSKEY_DISPLAY: + return bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL); + case LEGACY: + return pin_code_neg_reply(&conn->br.dst); + default: + break; + } + + return -EINVAL; +} + +void bt_hci_pin_code_req(struct net_buf *buf) +{ + struct bt_hci_evt_pin_code_req *evt = (void *)buf->data; + struct bt_conn *conn; + + LOG_DBG(""); + + conn = bt_conn_lookup_addr_br(&evt->bdaddr); + if (!conn) + { + LOG_ERR("Can't find conn for %s", bt_addr_str(&evt->bdaddr)); + return; + } + + pin_code_req(conn); + bt_conn_unref(conn); +} + +void bt_hci_link_key_notify(struct net_buf *buf) +{ + struct bt_hci_evt_link_key_notify *evt = (void *)buf->data; + struct bt_conn *conn; + + conn = bt_conn_lookup_addr_br(&evt->bdaddr); + if (!conn) + { + LOG_ERR("Can't find conn for %s", bt_addr_str(&evt->bdaddr)); + return; + } + + LOG_DBG("%s, link type 0x%02x", bt_addr_str(&evt->bdaddr), evt->key_type); + + if (IS_ENABLED(CONFIG_BT_SMP_SC_ONLY) && + (evt->key_type != BT_LK_AUTH_COMBINATION_P256)) + { + /* When in Secure Connections Only mode, all services + * (except those allowed to have Security Mode 4, Level 0) available on + * the BR/EDR physical transport require Security Mode 4, Level 4. Link + * key type should be P-256 based Secure Simple Pairing and Secure + * Authentication. + */ + + LOG_WRN("For SC only mode, link key type should be %d", + BT_LK_AUTH_COMBINATION_P256); + ssp_pairing_complete(conn, bt_security_err_get(BT_HCI_ERR_AUTH_FAIL)); + bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL); + bt_conn_unref(conn); + return; + } + + if (!conn->br.link_key) + { + conn->br.link_key = bt_keys_get_link_key(&evt->bdaddr); + } + + if (!conn->br.link_key) + { + LOG_ERR("Can't update keys for %s", bt_addr_str(&evt->bdaddr)); + bt_conn_unref(conn); + return; + } + + /* clear any old Link Key flags */ + + conn->br.link_key->flags = 0U; + + switch (evt->key_type) + { + case BT_LK_COMBINATION: + /* Setting Combination Link Key as AUTHENTICATED means it was + * successfully generated by 16 digits wide PIN code. + */ + + if (atomic_test_and_clear_bit(conn->flags, BT_CONN_BR_LEGACY_SECURE)) + { + conn->br.link_key->flags |= BT_LINK_KEY_AUTHENTICATED; + } + + memcpy(conn->br.link_key->val, evt->link_key, 16); + break; + case BT_LK_AUTH_COMBINATION_P192: + conn->br.link_key->flags |= BT_LINK_KEY_AUTHENTICATED; + __fallthrough; + case BT_LK_UNAUTH_COMBINATION_P192: + memcpy(conn->br.link_key->val, evt->link_key, 16); + break; + case BT_LK_AUTH_COMBINATION_P256: + conn->br.link_key->flags |= BT_LINK_KEY_AUTHENTICATED; + __fallthrough; + case BT_LK_UNAUTH_COMBINATION_P256: + conn->br.link_key->flags |= BT_LINK_KEY_SC; + + memcpy(conn->br.link_key->val, evt->link_key, 16); + break; + default: + LOG_WRN("Unsupported Link Key type %u", evt->key_type); + (void)memset(conn->br.link_key->val, 0, + sizeof(conn->br.link_key->val)); + break; + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS) && + !atomic_test_bit(conn->flags, BT_CONN_BR_NOBOND)) + { + bt_keys_link_key_store(conn->br.link_key); + } + + bt_conn_unref(conn); +} + +void link_key_neg_reply(const bt_addr_t *bdaddr) +{ + struct bt_hci_cp_link_key_neg_reply *cp; + struct net_buf *buf; + + LOG_DBG(""); + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + LOG_ERR("Out of command buffers"); + return; + } + + cp = net_buf_add(buf, sizeof(*cp)); + bt_addr_copy(&cp->bdaddr, bdaddr); + bt_hci_cmd_send_sync(BT_HCI_OP_LINK_KEY_NEG_REPLY, buf, NULL); +} + +void link_key_reply(const bt_addr_t *bdaddr, const uint8_t *lk) +{ + struct bt_hci_cp_link_key_reply *cp; + struct net_buf *buf; + + LOG_DBG(""); + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + LOG_ERR("Out of command buffers"); + return; + } + + cp = net_buf_add(buf, sizeof(*cp)); + bt_addr_copy(&cp->bdaddr, bdaddr); + memcpy(cp->link_key, lk, 16); + bt_hci_cmd_send_sync(BT_HCI_OP_LINK_KEY_REPLY, buf, NULL); +} + +void bt_hci_link_key_req(struct net_buf *buf) +{ + struct bt_hci_evt_link_key_req *evt = (void *)buf->data; + struct bt_conn *conn; + + LOG_DBG("%s", bt_addr_str(&evt->bdaddr)); + + conn = bt_conn_lookup_addr_br(&evt->bdaddr); + if (!conn) + { + LOG_ERR("Can't find conn for %s", bt_addr_str(&evt->bdaddr)); + link_key_neg_reply(&evt->bdaddr); + return; + } + + if (!conn->br.link_key) + { + conn->br.link_key = bt_keys_find_link_key(&evt->bdaddr); + } + + if (!conn->br.link_key) + { + link_key_neg_reply(&evt->bdaddr); + bt_conn_unref(conn); + return; + } + + /* Enforce regenerate by controller stronger link key since found one + * in database not covers requested security level. + */ + + if (!(conn->br.link_key->flags & BT_LINK_KEY_AUTHENTICATED) && + conn->required_sec_level > BT_SECURITY_L2) + { + link_key_neg_reply(&evt->bdaddr); + bt_conn_unref(conn); + return; + } + + link_key_reply(&evt->bdaddr, conn->br.link_key->val); + bt_conn_unref(conn); +} + +void io_capa_neg_reply(const bt_addr_t *bdaddr, const uint8_t reason) +{ + struct bt_hci_cp_io_capability_neg_reply *cp; + struct net_buf *resp_buf; + + resp_buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!resp_buf) + { + LOG_ERR("Out of command buffers"); + return; + } + + cp = net_buf_add(resp_buf, sizeof(*cp)); + bt_addr_copy(&cp->bdaddr, bdaddr); + cp->reason = reason; + bt_hci_cmd_send_sync(BT_HCI_OP_IO_CAPABILITY_NEG_REPLY, resp_buf, NULL); +} + +void bt_hci_io_capa_resp(struct net_buf *buf) +{ + struct bt_hci_evt_io_capa_resp *evt = (void *)buf->data; + struct bt_conn *conn; + + LOG_DBG("remote %s, IOcapa 0x%02x, auth 0x%02x", bt_addr_str(&evt->bdaddr), + evt->capability, + evt->authentication); + + if (evt->authentication > BT_HCI_GENERAL_BONDING_MITM) + { + LOG_ERR("Invalid remote authentication requirements"); + io_capa_neg_reply(&evt->bdaddr, BT_HCI_ERR_UNSUPP_FEATURE_PARAM_VAL); + return; + } + + if (evt->capability > BT_IO_NO_INPUT_OUTPUT) + { + LOG_ERR("Invalid remote io capability requirements"); + io_capa_neg_reply(&evt->bdaddr, BT_HCI_ERR_UNSUPP_FEATURE_PARAM_VAL); + return; + } + + conn = bt_conn_lookup_addr_br(&evt->bdaddr); + if (!conn) + { + LOG_ERR("Unable to find conn for %s", bt_addr_str(&evt->bdaddr)); + return; + } + + if (atomic_test_bit(conn->flags, BT_CONN_BR_PAIRING_INITIATOR) && + (evt->authentication > BT_HCI_NO_BONDING_MITM) && + !atomic_test_bit(conn->flags, BT_CONN_BR_BONDABLE)) + { + /* BLUETOOTH CORE SPECIFICATION Version 6.0 | Vol 3, Part C, section + * 9.4.2. + * A device in the non-bondable mode does not allow a bond to be + * created with a peer device. + * + * If the local is SSP initiator and non-bondable mode, and the bonding + * is required by peer device, reports the pairing failure and + * disconnects the ACL connection with error `BT_HCI_ERR_AUTH_FAIL`. + */ + + LOG_WRN("Bonding flag mismatch (initiator:false != responder:true)"); + ssp_pairing_complete(conn, bt_security_err_get(BT_HCI_ERR_AUTH_FAIL)); + bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL); + bt_conn_unref(conn); + return; + } + + conn->br.remote_io_capa = evt->capability; + conn->br.remote_auth = evt->authentication; + atomic_set_bit(conn->flags, BT_CONN_BR_PAIRING); + bt_conn_unref(conn); +} + +/* Clear Bonding flag */ +#define BT_HCI_SET_NO_BONDING(auth) ((auth) & 0x01) + +/* Clear MITM flag */ +#define BT_HCI_SET_NO_MITM(auth) ((auth) & (~0x01)) + +void bt_hci_io_capa_req(struct net_buf *buf) +{ + struct bt_hci_evt_io_capa_req *evt = (void *)buf->data; + struct net_buf *resp_buf; + struct bt_conn *conn; + struct bt_hci_cp_io_capability_reply *cp; + uint8_t auth; + + LOG_DBG(""); + + conn = bt_conn_lookup_addr_br(&evt->bdaddr); + if (!conn) + { + LOG_ERR("Can't find conn for %s", bt_addr_str(&evt->bdaddr)); + return; + } + +#if defined(CONFIG_BT_SMP_APP_PAIRING_ACCEPT) + if (bt_auth && bt_auth->pairing_accept) + { + enum bt_security_err err; + + err = bt_auth->pairing_accept(conn, NULL); + if (err != BT_SECURITY_ERR_SUCCESS) + { + io_capa_neg_reply(&evt->bdaddr, BT_HCI_ERR_PAIRING_NOT_ALLOWED); + bt_conn_unref(conn); + return; + } + } +#endif + + /* Set authentication requirements when acting as pairing initiator to + * 'dedicated bond' with MITM protection set if local IO capa potentially + * allows it, and for acceptor, based on local IO capa and remote's + * authentication set. + */ + + if (atomic_test_bit(conn->flags, BT_CONN_BR_PAIRING_INITIATOR)) + { + if (get_io_capa() != BT_IO_NO_INPUT_OUTPUT) + { + if (atomic_test_bit(conn->flags, BT_CONN_BR_GENERAL_BONDING)) + { + auth = BT_HCI_GENERAL_BONDING_MITM; + } + else + { + auth = BT_HCI_DEDICATED_BONDING_MITM; + } + } + else + { + if (atomic_test_bit(conn->flags, BT_CONN_BR_GENERAL_BONDING)) + { + auth = BT_HCI_GENERAL_BONDING; + } + else + { + auth = BT_HCI_DEDICATED_BONDING; + } + } + + if (conn->required_sec_level < BT_SECURITY_L3) + { + /* If security level less than L3, clear MITM flag. */ + + auth = BT_HCI_SET_NO_MITM(auth); + } + } + else + { + auth = ssp_get_auth(conn); + + /* Core v6.0, Vol 3, Part C, Section 4.3.1 Non-bondable mode + * When a Bluetooth device is in non-bondable mode it shall not accept + * a pairing request that results in bonding. Devices in non-bondable + * mode may accept connections that do not request or require bonding. + * + * If the peer supports bonding mode, but the local is in non-bondable + * mode, it will send a negative response with error code + * `BT_HCI_ERR_PAIRING_NOT_ALLOWED`. + */ + + if (!atomic_test_bit(conn->flags, BT_CONN_BR_BONDABLE) && + (conn->br.remote_auth > BT_HCI_NO_BONDING_MITM)) + { + LOG_WRN("Invalid remote bonding requirements"); + io_capa_neg_reply(&evt->bdaddr, BT_HCI_ERR_PAIRING_NOT_ALLOWED); + bt_conn_unref(conn); + return; + } + } + + if (!atomic_test_bit(conn->flags, BT_CONN_BR_BONDABLE)) + { + /* If bondable is false, clear bonding flag. */ + + auth = BT_HCI_SET_NO_BONDING(auth); + } + + conn->br.local_auth = auth; + + resp_buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!resp_buf) + { + LOG_ERR("Out of command buffers"); + bt_conn_unref(conn); + return; + } + + cp = net_buf_add(resp_buf, sizeof(*cp)); + bt_addr_copy(&cp->bdaddr, &evt->bdaddr); + cp->capability = get_io_capa(); + cp->authentication = auth; + cp->oob_data = 0U; + bt_hci_cmd_send_sync(BT_HCI_OP_IO_CAPABILITY_REPLY, resp_buf, NULL); + bt_conn_unref(conn); +} + +void bt_hci_ssp_complete(struct net_buf *buf) +{ + struct bt_hci_evt_ssp_complete *evt = (void *)buf->data; + struct bt_conn *conn; + + LOG_DBG("status 0x%02x", evt->status); + + conn = bt_conn_lookup_addr_br(&evt->bdaddr); + if (!conn) + { + LOG_ERR("Can't find conn for %s", bt_addr_str(&evt->bdaddr)); + return; + } + + /* Mark no-bond so that link-key will be removed on disconnection */ + + if (ssp_get_auth(conn) < BT_HCI_DEDICATED_BONDING) + { + atomic_set_bit(conn->flags, BT_CONN_BR_NOBOND); + } + + ssp_pairing_complete(conn, bt_security_err_get(evt->status)); + if (evt->status) + { + bt_conn_disconnect(conn, BT_HCI_ERR_AUTH_FAIL); + } + + bt_conn_unref(conn); +} + +void bt_hci_user_confirm_req(struct net_buf *buf) +{ + struct bt_hci_evt_user_confirm_req *evt = (void *)buf->data; + struct bt_conn *conn; + + conn = bt_conn_lookup_addr_br(&evt->bdaddr); + if (!conn) + { + LOG_ERR("Can't find conn for %s", bt_addr_str(&evt->bdaddr)); + return; + } + + ssp_auth(conn, sys_le32_to_cpu(evt->passkey)); + bt_conn_unref(conn); +} + +void bt_hci_user_passkey_notify(struct net_buf *buf) +{ + struct bt_hci_evt_user_passkey_notify *evt = (void *)buf->data; + struct bt_conn *conn; + + LOG_DBG(""); + + conn = bt_conn_lookup_addr_br(&evt->bdaddr); + if (!conn) + { + LOG_ERR("Can't find conn for %s", bt_addr_str(&evt->bdaddr)); + return; + } + + ssp_auth(conn, sys_le32_to_cpu(evt->passkey)); + bt_conn_unref(conn); +} + +void bt_hci_user_passkey_req(struct net_buf *buf) +{ + struct bt_hci_evt_user_passkey_req *evt = (void *)buf->data; + struct bt_conn *conn; + + conn = bt_conn_lookup_addr_br(&evt->bdaddr); + if (!conn) + { + LOG_ERR("Can't find conn for %s", bt_addr_str(&evt->bdaddr)); + return; + } + + ssp_auth(conn, 0); + bt_conn_unref(conn); +} + +static void link_encr(const uint16_t handle) +{ + struct bt_hci_cp_set_conn_encrypt *encr; + struct net_buf *buf; + + LOG_DBG(""); + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + LOG_ERR("Out of command buffers"); + return; + } + + encr = net_buf_add(buf, sizeof(*encr)); + encr->handle = sys_cpu_to_le16(handle); + encr->encrypt = 0x01; + + bt_hci_cmd_send_sync(BT_HCI_OP_SET_CONN_ENCRYPT, buf, NULL); +} + +void bt_hci_auth_complete(struct net_buf *buf) +{ + struct bt_hci_evt_auth_complete *evt = (void *)buf->data; + struct bt_conn *conn; + uint16_t handle = sys_le16_to_cpu(evt->handle); + + LOG_DBG("status 0x%02x, handle %u", evt->status, handle); + + conn = bt_conn_lookup_handle(handle, BT_CONN_TYPE_BR); + if (!conn) + { + LOG_ERR("Can't find conn for handle %u", handle); + return; + } + + if (evt->status) + { + /* Inform layers above HCI about non-zero authentication + * status to make them able cleanup pending jobs. + */ + + bt_conn_security_changed(conn, evt->status, + bt_security_err_get(evt->status)); + } + else + { + link_encr(handle); + } + + bt_conn_unref(conn); +} diff --git a/libs/zblue/host/classic/ssp.h b/libs/zblue/host/classic/ssp.h new file mode 100644 index 0000000000000..31be5caf0f2df --- /dev/null +++ b/libs/zblue/host/classic/ssp.h @@ -0,0 +1,19 @@ +/**************************************************************************** + * libs/zblue/host/classic/ssp.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA Copyright (c) 2017 Intel + * Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +int bt_ssp_start_security(struct bt_conn *conn); + +int bt_ssp_auth_passkey_entry(struct bt_conn *conn, unsigned int passkey); +int bt_ssp_auth_passkey_confirm(struct bt_conn *conn); +int bt_ssp_auth_cancel(struct bt_conn *conn); +int bt_ssp_auth_pairing_confirm(struct bt_conn *conn); diff --git a/libs/zblue/host/conn.c b/libs/zblue/host/conn.c new file mode 100644 index 0000000000000..f9a5916965190 --- /dev/null +++ b/libs/zblue/host/conn.c @@ -0,0 +1,5642 @@ +/**************************************************************************** + * libs/zblue/host/conn.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* conn.c - Bluetooth connection handling */ + +/* Copyright (c) 2015-2016 Intel Corporation + * Copyright (c) 2025 Nordic Semiconductor ASA Copyright (c) 2025 Xiaomi + * Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "addr_internal.h" +#include "adv.h" +#include "att_internal.h" +#include "buf_view.h" +#include "classic/conn_br_internal.h" +#include "classic/sco_internal.h" +#include "classic/ssp.h" +#include "common/assert.h" +#include "common/bt_str.h" +#include "conn_internal.h" +#include "direction_internal.h" +#include "gatt_gap_svc_validate.h" +#include "hci_core.h" +#include "id.h" +#include "iso_internal.h" +#include "keys.h" +#include "l2cap_internal.h" +#include "scan.h" +#include "smp.h" + +#define LOG_LEVEL CONFIG_BT_CONN_LOG_LEVEL +LOG_MODULE_REGISTER(bt_conn); + +K_FIFO_DEFINE(free_tx); + +#if defined(CONFIG_BT_CONN_TX_NOTIFY_WQ) +static struct k_work_q conn_tx_workq; +static uint64_t + conn_tx_workq_thread_stack + [CONFIG_BT_CONN_TX_NOTIFY_WQ_STACK_SIZE / sizeof(uint64_t)]; +#endif /* CONFIG_BT_CONN_TX_NOTIFY_WQ */ + +static void tx_free(struct bt_conn_tx *tx); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void conn_tx_destroy(struct bt_conn *conn, struct bt_conn_tx *tx) +{ + DEBUGASSERT(tx); + + bt_conn_tx_cb_t cb = tx->cb; + void *user_data = tx->user_data; + + LOG_DBG("conn %p tx %p cb %p ud %p", conn, tx, cb, user_data); + + /* Free up TX metadata before calling callback in case the callback + * tries to allocate metadata + */ + + tx_free(tx); + + if (cb) + { + cb(conn, user_data, -ESHUTDOWN); + } +} + +#if defined(CONFIG_BT_CONN_TX) +static void tx_complete_work(struct k_work *work); +#endif /* CONFIG_BT_CONN_TX */ + +void bt_tx_irq_raise(void); + +/* Group Connected BT_CONN only in this */ +#if defined(CONFIG_BT_CONN) +static void notify_recycled_conn_slot(void); + +/* Peripheral timeout to initialize Connection Parameter Update procedure */ +#define CONN_UPDATE_TIMEOUT MSEC2TICK(CONFIG_BT_CONN_PARAM_UPDATE_TIMEOUT) + +static void deferred_work(struct k_work *work); +static void notify_connected(struct bt_conn *conn); + +static struct bt_conn acl_conns[CONFIG_BT_MAX_CONN]; +NET_BUF_POOL_DEFINE(acl_tx_pool, CONFIG_BT_L2CAP_TX_BUF_COUNT, + BT_L2CAP_BUF_SIZE(CONFIG_BT_L2CAP_TX_MTU), + CONFIG_BT_CONN_TX_USER_DATA_SIZE, + NULL); + +#if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_CLASSIC) +const struct bt_conn_auth_cb *bt_auth; +struct list_node bt_auth_info_cbs = LIST_INITIAL_VALUE(bt_auth_info_cbs); +#endif /* CONFIG_BT_SMP || CONFIG_BT_CLASSIC */ + +#if defined(CONFIG_BT_CONN_DYNAMIC_CALLBACKS) +static struct list_node conn_cbs = LIST_INITIAL_VALUE(conn_cbs); + +#define BT_CONN_CB_DYNAMIC_FOREACH(_cn) \ + for (struct bt_conn_cb *_cn = list_peek_head_container(&conn_cbs, _cn, _node); _cn != NULL; \ + _cn = list_next_container(&conn_cbs, _cn, _node)) +#else +#define BT_CONN_CB_DYNAMIC_FOREACH(_cn) for (struct bt_conn_cb *_cn = NULL; false;) +#endif + +static struct bt_conn_tx conn_tx[CONFIG_BT_BUF_ACL_TX_COUNT]; + +#if defined(CONFIG_BT_CLASSIC) +static struct bt_conn sco_conns[CONFIG_BT_MAX_SCO_CONN]; +#endif /* CONFIG_BT_CLASSIC */ +#endif /* CONFIG_BT_CONN */ + +#if defined(CONFIG_BT_CONN_TX) +static void frag_destroy(struct net_buf *buf); + +/* Storage for fragments (views) into the upper layers' PDUs. No user data: + * the HCI driver may use a sent fragment's user data, so the view metadata + * lives in frag_md_pool below instead. + */ + +NET_BUF_POOL_FIXED_DEFINE(fragments, + CONFIG_BT_CONN_FRAG_COUNT, + 0, + 0, + frag_destroy); + +struct frag_md +{ + struct bt_buf_view_meta view_meta; +}; + +static struct frag_md frag_md_pool[CONFIG_BT_CONN_FRAG_COUNT]; + +static struct frag_md *get_frag_md(struct net_buf *fragment) +{ + return &frag_md_pool[net_buf_id(fragment)]; +} + +static void frag_destroy(struct net_buf *frag) +{ + /* allow next view to be allocated (and unlock the parent buf) */ + + bt_buf_destroy_view(frag, &get_frag_md(frag)->view_meta); + + LOG_DBG(""); + + /* Kick the TX processor to send the rest of the frags. */ + + bt_tx_irq_raise(); +} + +static struct net_buf *get_data_frag(struct net_buf *outside, size_t winsize) +{ + struct net_buf *window; + + DEBUGASSERT(!bt_buf_has_view(outside)); + + /* Keeping a ref is the caller's responsibility */ + + window = net_buf_alloc_len(&fragments, 0, 0); /* NuttX: 0 -> 0 ticks */ + if (!window) + { + return window; + } + + window = bt_buf_make_view(window, outside, winsize, + &get_frag_md(window)->view_meta); + + LOG_DBG("get-acl-frag: outside %p window %p size %zu", outside, window, + winsize); + + return window; +} +#else /* !CONFIG_BT_CONN_TX */ +static struct net_buf *get_data_frag(struct net_buf *outside, size_t winsize) +{ + ARG_UNUSED(outside); + ARG_UNUSED(winsize); + + /* This will never get called. It's only to allow compilation to take + * place and the later linker stage to remove this implementation. + */ + + return NULL; +} +#endif /* CONFIG_BT_CONN_TX */ + +#if defined(CONFIG_BT_ISO) +extern struct bt_conn iso_conns[CONFIG_BT_ISO_MAX_CHAN]; + +/* Callback TX buffers for ISO */ + +static struct bt_conn_tx iso_tx[CONFIG_BT_ISO_TX_BUF_COUNT]; + +int bt_conn_iso_init(void) +{ + for (size_t i = 0; i < ARRAY_SIZE(iso_tx); i++) + { + k_fifo_put(&free_tx, &iso_tx[i]); + } + + return 0; +} +#endif /* CONFIG_BT_ISO */ + +sem_t *bt_conn_get_pkts(struct bt_conn *conn) +{ +#if defined(CONFIG_BT_CLASSIC) + if (bt_conn_is_br(conn) || !bt_dev.le.acl_mtu) + { + return &bt_dev.br.pkts; + } +#endif /* CONFIG_BT_CLASSIC */ + +#if defined(CONFIG_BT_ISO) + /* Use ISO pkts semaphore if LE Read Buffer Size command returned + * dedicated ISO buffers. + */ + + if (bt_conn_is_iso(conn)) + { + if (bt_dev.le.iso_mtu && bt_dev.le.iso_limit != 0) + { + return &bt_dev.le.iso_pkts; + } + + return NULL; + } +#endif /* CONFIG_BT_ISO */ + +#if defined(CONFIG_BT_CONN) + if (bt_dev.le.acl_mtu) + { + return &bt_dev.le.acl_pkts; + } +#endif /* CONFIG_BT_CONN */ + + return NULL; +} + +static inline const char *state2str(bt_conn_state_t state) +{ + switch (state) + { + case BT_CONN_DISCONNECTED: + return "disconnected"; + case BT_CONN_DISCONNECT_COMPLETE: + return "disconnect-complete"; + case BT_CONN_INITIATING: + return "initiating"; + case BT_CONN_SCAN_BEFORE_INITIATING: + return "scan-before-initiating"; + case BT_CONN_INITIATING_FILTER_LIST: + return "initiating-filter-list"; + case BT_CONN_ADV_CONNECTABLE: + return "adv-connectable"; + case BT_CONN_ADV_DIR_CONNECTABLE: + return "adv-dir-connectable"; + case BT_CONN_CONNECTED: + return "connected"; + case BT_CONN_DISCONNECTING: + return "disconnecting"; + default: + return "(unknown)"; + } +} + +static void tx_free(struct bt_conn_tx *tx) +{ + LOG_DBG("%p", tx); + tx->cb = NULL; + tx->user_data = NULL; + k_fifo_put(&free_tx, tx); +} + +#if defined(CONFIG_BT_CONN_TX) +static struct k_work_q *tx_notify_workqueue_get(void) +{ +#if defined(CONFIG_BT_CONN_TX_NOTIFY_WQ) + return &conn_tx_workq; +#else + return &k_sys_work_q; +#endif /* CONFIG_BT_CONN_TX_NOTIFY_WQ */ +} + +static void tx_notify_process(struct bt_conn *conn) +{ + /* TX notify processing is done only from a single thread. */ + + DEBUGASSERT(nxsched_gettid() == k_work_queue_thread_get( + tx_notify_workqueue_get())); + + LOG_DBG("conn %p", (void *)conn); + + while (1) + { + struct bt_conn_tx *tx = NULL; + irqstate_t key; + bt_conn_tx_cb_t cb; + void *user_data; + + key = enter_critical_section(); + if (!list_is_empty(&conn->tx_complete)) + { + const struct list_node + *node = list_remove_head(&conn->tx_complete); + + tx = CONTAINER_OF(node, struct bt_conn_tx, node); + } + + leave_critical_section(key); + + if (!tx) + { + return; + } + + LOG_DBG("tx %p cb %p user_data %p", tx, tx->cb, tx->user_data); + + /* Copy over the params */ + + cb = tx->cb; + user_data = tx->user_data; + + /* Free up TX notify since there may be user waiting */ + + tx_free(tx); + + /* Run the callback, at this point it should be safe to + * allocate new buffers since the TX should have been unblocked by + * tx_free. + */ + + if (cb) + { + cb(conn, user_data, 0); + } + + LOG_DBG("raise TX IRQ"); + bt_tx_irq_raise(); + } +} +#endif /* CONFIG_BT_CONN_TX */ + +void bt_conn_tx_notify(struct bt_conn *conn, bool wait_for_completion) +{ +#if defined(CONFIG_BT_CONN_TX) + /* Ensure that function is called only from a single context. */ + + if (nxsched_gettid() == k_work_queue_thread_get(tx_notify_workqueue_get())) + { + tx_notify_process(conn); + } + else + { + struct k_work_sync sync; + __maybe_unused int err; + + err = k_work_submit_to_queue(tx_notify_workqueue_get(), + &conn->tx_complete_work); + DEBUGASSERT(err >= 0); + + if (wait_for_completion) + { + (void)k_work_flush(&conn->tx_complete_work, &sync); + } + } +#else + ARG_UNUSED(conn); + ARG_UNUSED(wait_for_completion); +#endif /* CONFIG_BT_CONN_TX */ +} + +struct bt_conn *bt_conn_new(struct bt_conn *conns, size_t size) +{ + struct bt_conn *conn = NULL; + int i; + + for (i = 0; i < size; i++) + { + if (atomic_cmpxchg_value(&conns[i].ref, 0, 1)) + { + conn = &conns[i]; + break; + } + } + + if (!conn) + { + return NULL; + } + + (void)memset(conn, 0, offsetof(struct bt_conn, ref)); + list_initialize(&conn->tx_pending); + list_initialize(&conn->tx_complete); + list_initialize(&conn->channels); + list_initialize(&conn->l2cap_data_ready); + +#if defined(CONFIG_BT_CONN) + /* The deferred work must run on the Bluetooth workqueue: it performs + * channel and profile teardown, and the non-blocking work cancellations in + * those paths are only guaranteed to be effective against work items + * running on the same workqueue. + */ + + k_work_init_delayable(&conn->deferred_work, deferred_work); +#endif /* CONFIG_BT_CONN */ +#if defined(CONFIG_BT_CONN_TX) + k_work_init(&conn->tx_complete_work, tx_complete_work); +#endif /* CONFIG_BT_CONN_TX */ + + return conn; +} + +void bt_conn_reset_rx_state(struct bt_conn *conn) +{ + if (!conn->rx) + { + return; + } + + net_buf_drop(&conn->rx); +} + +static void bt_acl_recv(struct bt_conn *conn, + struct net_buf *buf, uint8_t flags) +{ + uint16_t acl_total_len; + + /* Check packet boundary flags */ + + switch (flags) + { + case BT_ACL_START: + if (conn->rx) + { + LOG_ERR("Unexpected first L2CAP frame"); + bt_conn_reset_rx_state(conn); + } + + LOG_DBG("First, len %u final %u", buf->len, + (buf->len < sizeof(uint16_t)) ? 0 : sys_get_le16(buf->data)); + + conn->rx = buf; + break; + case BT_ACL_CONT: + if (!conn->rx) + { + LOG_ERR("Unexpected L2CAP continuation"); + bt_conn_reset_rx_state(conn); + net_buf_unref(buf); + return; + } + + if (!buf->len) + { + LOG_DBG("Empty ACL_CONT"); + net_buf_unref(buf); + return; + } + + if (buf->len > net_buf_tailroom(conn->rx)) + { + LOG_ERR("Not enough buffer space for L2CAP data"); + + /* Frame is not complete but we still pass it to L2CAP + * so that it may handle error on protocol level eg disconnect + * channel. + */ + + bt_l2cap_recv(conn, conn->rx, false); + conn->rx = NULL; + net_buf_unref(buf); + return; + } + + net_buf_add_mem(conn->rx, buf->data, buf->len); + net_buf_unref(buf); + break; + default: + /* BT_ACL_START_NO_FLUSH and BT_ACL_COMPLETE are not allowed on + * LE-U from Controller to Host. Only BT_ACL_POINT_TO_POINT is + * supported. + */ + + LOG_ERR("Unexpected ACL flags (0x%02x)", flags); + bt_conn_reset_rx_state(conn); + net_buf_unref(buf); + return; + } + + if (conn->rx->len < sizeof(uint16_t)) + { + /* Still not enough data received to retrieve the L2CAP header + * length field. + */ + + return; + } + + acl_total_len = sys_get_le16(conn->rx->data) + sizeof(struct bt_l2cap_hdr); + + if (conn->rx->len < acl_total_len) + { + /* L2CAP frame not complete. */ + + return; + } + + if (!bt_conn_is_br(conn) && (conn->rx->len > acl_total_len)) + { + LOG_ERR("ACL len mismatch (%u > %u)", conn->rx->len, acl_total_len); + bt_conn_reset_rx_state(conn); + return; + } + + /* L2CAP frame complete. */ + + buf = net_buf_take(&conn->rx); + + LOG_DBG("Successfully parsed %u byte L2CAP packet", buf->len); + if (bt_conn_is_br(conn)) + { + bt_br_acl_recv(conn, buf, true); + } + else + { + bt_l2cap_recv(conn, buf, true); + } +} + +void bt_conn_recv(struct bt_conn *conn, struct net_buf *buf, uint8_t flags) +{ + /* Make sure we notify any pending TX callbacks before processing + * new data for this connection. + * + * Always do so from the same context for sanity. In this case that will be + * either a dedicated Bluetooth connection TX workqueue or system + * workqueue. + */ + + bt_conn_tx_notify(conn, true); + + LOG_DBG("handle %u len %u flags %02x", conn->handle, buf->len, flags); + + if (IS_ENABLED(CONFIG_BT_ISO_RX) && bt_conn_is_iso(conn)) + { + bt_iso_recv(conn, buf, flags); + return; + } + else if (IS_ENABLED(CONFIG_BT_CONN)) + { + bt_acl_recv(conn, buf, flags); + } + else + { + DEBUGASSERT(false); + } +} + +static bool dont_have_tx_context(struct bt_conn *conn) +{ + return k_fifo_is_empty(&free_tx); +} + +static struct bt_conn_tx *conn_tx_alloc(void) +{ + struct bt_conn_tx *ret = k_fifo_get(&free_tx, 0); + + LOG_DBG("%p", ret); + + return ret; +} + +enum +{ + FRAG_START, + FRAG_CONT, + FRAG_SINGLE, + FRAG_END +}; + +static int send_acl(struct bt_conn *conn, struct net_buf *buf, uint8_t flags) +{ + struct bt_hci_acl_hdr *hdr; + + switch (flags) + { + case FRAG_START: + case FRAG_SINGLE: + flags = BT_ACL_START_NO_FLUSH; + break; + case FRAG_CONT: + case FRAG_END: + flags = BT_ACL_CONT; + break; + default: + return -EINVAL; + } + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->handle = sys_cpu_to_le16(bt_acl_handle_pack(conn->handle, flags)); + hdr->len = sys_cpu_to_le16(buf->len - sizeof(*hdr)); + + net_buf_push_u8(buf, BT_HCI_H4_ACL); + + return bt_send(buf); +} + +static enum bt_iso_timestamp contains_iso_timestamp(struct net_buf *buf) +{ + enum bt_iso_timestamp ts; + + if (net_buf_headroom(buf) == (BT_BUF_ISO_SIZE(0) - + sizeof(struct bt_hci_iso_sdu_ts_hdr))) + { + ts = BT_ISO_TS_PRESENT; + } + else + { + ts = BT_ISO_TS_ABSENT; + } + + return ts; +} + +static int send_iso(struct bt_conn *conn, struct net_buf *buf, uint8_t flags) +{ + struct bt_hci_iso_hdr *hdr; + enum bt_iso_timestamp ts; + + switch (flags) + { + case FRAG_START: + flags = BT_ISO_START; + break; + case FRAG_CONT: + flags = BT_ISO_CONT; + break; + case FRAG_SINGLE: + flags = BT_ISO_SINGLE; + break; + case FRAG_END: + flags = BT_ISO_END; + break; + default: + return -EINVAL; + } + + /* The TS bit is set by `iso.c:conn_iso_send`. This special byte + * prepends the whole SDU, and won't be there for individual fragments. + * + * Conveniently, it is only legal to set the TS bit on the first HCI + * fragment, so we don't have to pass this extra metadata around for every + * fragment, only the first one. + */ + + if (flags == BT_ISO_SINGLE || flags == BT_ISO_START) + { + ts = contains_iso_timestamp(buf); + } + else + { + ts = BT_ISO_TS_ABSENT; + } + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->handle = sys_cpu_to_le16(bt_iso_handle_pack(conn->handle, flags, ts)); + hdr->len = sys_cpu_to_le16(buf->len - sizeof(*hdr)); + + net_buf_push_u8(buf, BT_HCI_H4_ISO); + + return bt_send(buf); +} + +static inline uint16_t conn_mtu(struct bt_conn *conn) +{ +#if defined(CONFIG_BT_CLASSIC) + if (bt_conn_is_br(conn) || (!bt_conn_is_iso(conn) && !bt_dev.le.acl_mtu)) + { + return bt_dev.br.mtu; + } +#endif /* CONFIG_BT_CLASSIC */ +#if defined(CONFIG_BT_ISO) + + if (bt_conn_is_iso(conn)) + { + return bt_dev.le.iso_mtu; + } +#endif /* CONFIG_BT_ISO */ +#if defined(CONFIG_BT_CONN) + + return bt_dev.le.acl_mtu; +#else + return 0; +#endif /* CONFIG_BT_CONN */ +} + +static bool is_iso_tx_conn(struct bt_conn *conn) +{ + return IS_ENABLED(CONFIG_BT_ISO_TX) && bt_conn_is_iso(conn); +} + +static bool is_acl_conn(struct bt_conn *conn) +{ + return bt_conn_is_le(conn) || bt_conn_is_br(conn); +} + +static int send_buf( + struct bt_conn *conn, struct net_buf *buf, size_t len, bt_conn_tx_cb_t cb, + void *ud) +{ + struct net_buf *frag = NULL; + struct bt_conn_tx *tx = NULL; + uint8_t flags; + int err; + + if (buf->len == 0) + { + DEBUGASSERT(0); + + err = -EMSGSIZE; + goto error_return; + } + + if (bt_buf_has_view(buf)) + { + DEBUGASSERT(0); + + err = -EIO; + goto error_return; + } + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + LOG_WRN("Dropping buffer since Bluetooth is not ready"); + err = -EHOSTDOWN; + goto error_return; + } + + LOG_DBG("conn %p buf %p len %zu buf->len %u cb %p ud %p", conn, buf, len, + buf->len, cb, ud); + + /* Acquire the right to send 1 packet to the controller */ + + if (nxsem_trywait(bt_conn_get_pkts(conn)) != 0) + { + /* This shouldn't happen now that we acquire the resources + * before calling `send_buf` (in `get_conn_ready`). All consumers of + * this semaphore run under the host lock (held across the whole TX + * processing pass), and givers only ever increase the count. So the + * sem value cannot have decreased since the get_conn_ready() check. + */ + + DEBUGASSERT(0); + + err = -ENOMEM; + goto error_return; + } + + /* Allocate and set the TX context */ + + tx = conn_tx_alloc(); + + /* See big comment above */ + + if (!tx) + { + DEBUGASSERT(0); + nxsem_post(bt_conn_get_pkts(conn)); + err = -ENOMEM; + goto error_return; + } + + tx->cb = cb; + tx->user_data = ud; + + uint16_t frag_len = MIN(conn_mtu(conn), len); + + /* Check that buf->ref is 1 or 2. It would be 1 if this + * was the only reference (e.g. buf was removed from the conn tx_queue). It + * would be 2 if the tx_data_pull kept it on the tx_queue for segmentation. + */ + + DEBUGASSERT((buf->ref == 1) || (buf->ref == 2)); + + /* The reference is always transferred to the frag, so when + * the frag is destroyed, the parent reference is decremented. + */ + + frag = get_data_frag(buf, frag_len); + + /* Caller is supposed to check we have all resources to send */ + + DEBUGASSERT(frag != NULL); + + /* If the current buffer doesn't fit a controller buffer */ + + if (len > conn_mtu(conn)) + { + flags = conn->next_is_frag ? FRAG_CONT : FRAG_START; + conn->next_is_frag = true; + } + else + { + flags = conn->next_is_frag ? FRAG_END : FRAG_SINGLE; + conn->next_is_frag = false; + } + + LOG_DBG("send frag: buf %p len %d", frag, frag_len); + + /* At this point, the buffer is either a fragment or a full HCI packet. + * The flags are also valid. + */ + + LOG_DBG("conn %p buf %p len %u flags 0x%02x", + conn, + frag, + frag->len, + flags); + + /* Keep track of sent buffers. We have to append _before_ + * sending, as we might get preempted if the HCI driver calls + * sched_yield() before returning. + * + * In that case, the controller could also send a num-complete-packets + * event and our handler will be confused that there is no corresponding + * callback node in the `tx_pending` list. + */ + + atomic_add(&conn->in_ll, 1); + list_add_tail(&conn->tx_pending, &tx->node); + + if (is_iso_tx_conn(conn)) + { + err = send_iso(conn, frag, flags); + } + else if (is_acl_conn(conn)) + { + err = send_acl(conn, frag, flags); + } + else + { + err = -EINVAL; /* asserts may be disabled */ + DEBUGASSERT(false); + } + + if (!err) + { + return 0; + } + + /* Remove buf from pending list */ + + atomic_sub(&conn->in_ll, 1); + (void)list_find_and_remove(&conn->tx_pending, &tx->node); + + LOG_ERR("Unable to send to driver (err %d)", err); + + /* If we get here, something has seriously gone wrong: the `parent` buf + * (of which the current fragment belongs) should also be destroyed. + */ + + net_buf_unref(frag); + + /* `buf` might not get destroyed right away because it may + * still be on a conn tx_queue, and its `tx` pointer will still be + * reachable. Make sure that we don't try to use the destroyed context + * later. + */ + + conn_tx_destroy(conn, tx); + nxsem_post(bt_conn_get_pkts(conn)); + + /* Merge HCI driver errors */ + + return -EIO; + +error_return: + /* Runtime handling of fatal errors when ASSERTS are disabled. + * Unref the buf and invoke callback with the error. + */ + + net_buf_unref(buf); + if (cb) + { + cb(conn, ud, err); + } + + return err; +} + +static void conn_destroy(struct bt_conn *conn, void *data) +{ + if (conn->state == BT_CONN_CONNECTED || + conn->state == BT_CONN_DISCONNECTING) + { + bt_conn_set_state(conn, BT_CONN_DISCONNECT_COMPLETE); + } + + if (conn->state != BT_CONN_DISCONNECTED) + { + bt_conn_set_state(conn, BT_CONN_DISCONNECTED); + } +} + +void bt_conn_cleanup_all(void) +{ + bt_conn_foreach(BT_CONN_TYPE_ALL, conn_destroy, NULL); +} + +#if defined(CONFIG_BT_CONN) +/* Returns true if L2CAP has data to send on this conn */ + +static bool acl_has_data(struct bt_conn *conn) +{ + return list_peek_head(&conn->l2cap_data_ready) != NULL; +} +#endif /* defined(CONFIG_BT_CONN) */ + +/* Connection "Scheduler" of sorts: + * + * Will try to get the optimal number of queued buffers for the connection. + * + * Partitions the controller's buffers to each connection according to some + * heuristic. This is made to be tunable, fairness, simplicity, throughput + * etc. + * + * In the future, this will be a hook exposed to the application. + */ + +static bool should_stop_tx(struct bt_conn *conn) +{ + LOG_DBG("%p", conn); + + if (conn->state != BT_CONN_CONNECTED) + { + return true; + } + + /* TODO: This function should be overridable by the application: they + * should be able to provide their own heuristic. + */ + + if (!conn->has_data(conn)) + { + LOG_DBG("No more data for %p", conn); + return true; + } + + /* Queue only 3 buffers per-conn for now */ + + if (atomic_read(&conn->in_ll) < 3) + { + /* The goal of this heuristic is to allow the link-layer to + * extend an ACL connection event as long as the application layer can + * provide data. + * + * Here we chose three buffers, as some LLs need two enqueued packets + * to be able to set the more-data bit, and one more buffer to allow + * refilling by the app while one of them is being sent over-the-air. + */ + + return false; + } + + return true; +} + +void bt_conn_data_ready(struct bt_conn *conn) +{ + bool added; + + LOG_DBG("DR"); + + bt_conn_ref(conn); + + /* The conn_ready list is only ever modified under the host lock: + * here (append, any thread context) and in get_conn_ready() (remove, TX + * processor context, which holds the lock across the whole pass). + */ + + bt_dev_lock(); + + if (!list_find(&bt_dev.le.conn_ready, &conn->_conn_ready, NULL)) + { + list_add_tail(&bt_dev.le.conn_ready, &conn->_conn_ready); + + added = true; + } + else + { + added = false; + } + + bt_dev_unlock(); + + if (!added) + { + bt_conn_unref(conn); + } + + LOG_DBG("Connection %p %s conn_ready list", conn, + added ? "added to" : "already in"); + + /* Kick the TX processor */ + + bt_tx_irq_raise(); +} + +static bool cannot_send_to_controller(struct bt_conn *conn) +{ + int count; + + return nxsem_get_value(bt_conn_get_pkts(conn), &count) < 0 || count == 0; +} + +static bool dont_have_viewbufs(void) +{ +#if defined(CONFIG_BT_CONN_TX) + /* The LIFO only tracks buffers that have been destroyed at least once, + * hence the uninit check beforehand. + */ + + if (fragments.uninit_count > 0) + { + /* If there are uninitialized bufs, we are guaranteed allocation. */ + + return false; + } + + /* In practice k_fifo == k_lifo ABI. NuttX uses free_list directly. */ + + return list_is_empty(&fragments.free_list); + +#else /* !CONFIG_BT_CONN_TX */ + return false; +#endif /* CONFIG_BT_CONN_TX */ +} + +__maybe_unused static bool dont_have_methods(struct bt_conn *conn) +{ + return (conn->tx_data_pull == NULL) || (conn->get_and_clear_cb == NULL) || + (conn->has_data == NULL); +} + +static struct bt_conn *get_conn_ready(void) +{ + struct bt_conn *conn; + struct bt_conn *tmp; + struct list_node *prev = NULL; + + /* Called from the TX processor with the host lock held; the lock + * serializes conn_ready list access against bt_conn_data_ready(). + */ + + BT_DEV_LOCK_ASSERT(); + + if (dont_have_viewbufs()) + { + /* We will get scheduled again when the (view) buffers are freed. If + * you hit this a lot, try increasing `CONFIG_BT_CONN_FRAG_COUNT` + */ + + LOG_DBG("no view bufs"); + return NULL; + } + + list_for_every_container_safe(&bt_dev.le.conn_ready, conn, tmp, + _conn_ready) + { + DEBUGASSERT(tmp != conn); + +/* Iterate over the list of connections that have data to send + * and return the first one that can be sent. + */ + + if (cannot_send_to_controller(conn)) + { + /* When buffers are full, try next connection. */ + + LOG_DBG("no LL bufs for %p", conn); + prev = &conn->_conn_ready; + continue; + } + + if (dont_have_tx_context(conn)) + { + /* When TX contexts are not available, try next connection. */ + + LOG_DBG("no TX contexts for %p", conn); + prev = &conn->_conn_ready; + continue; + } + + if (dont_have_methods(conn)) + { + /* Try next connection if mandatory methods are missing. */ + + LOG_DBG("conn %p (type %d) is missing mandatory methods", conn, + conn->type); + prev = &conn->_conn_ready; + continue; + } + + if (should_stop_tx(conn)) + { + /* Move reference off the list */ + + DEBUGASSERT(prev != &conn->_conn_ready); + list_remove(&bt_dev.le.conn_ready, prev, &conn->_conn_ready); + + /* Append connection to list if it is connected and still has data */ + + if (conn->has_data(conn) && (conn->state == BT_CONN_CONNECTED)) + { + LOG_DBG("appending %p to back of TX queue", conn); + bt_conn_data_ready(conn); + } + + return conn; + } + + return bt_conn_ref(conn); + } + + /* No connection has data to send */ + + return NULL; +} + +/* Crazy that this file is compiled even if this is not true, but here we + * are. + */ +#if defined(CONFIG_BT_CONN) +static void acl_get_and_clear_cb( + struct bt_conn *conn, struct net_buf *buf, bt_conn_tx_cb_t *cb, void **ud) +{ + DEBUGASSERT(is_acl_conn(conn)); + + *cb = closure_cb(buf->user_data); + *ud = closure_data(buf->user_data); + memset(buf->user_data, 0, buf->user_data_size); +} +#endif /* defined(CONFIG_BT_CONN) */ + +static volatile bool _suspend_tx; + +#if defined(CONFIG_BT_TESTING) +void bt_conn_suspend_tx(bool suspend) +{ + _suspend_tx = suspend; + + LOG_DBG("%sing all data TX", suspend ? "suspend" : "resum"); + + bt_tx_irq_raise(); +} +#endif /* CONFIG_BT_TESTING */ + +void bt_conn_tx_processor(void) +{ + LOG_DBG("start"); + struct bt_conn *conn; + struct net_buf *buf; + bt_conn_tx_cb_t cb = NULL; + size_t buf_len; + void *ud = NULL; + + if (!IS_ENABLED(CONFIG_BT_CONN_TX)) + { + /* Mom, can we have a real compiler? */ + + return; + } + + if (IS_ENABLED(CONFIG_BT_TESTING) && _suspend_tx) + { + return; + } + + conn = get_conn_ready(); + + if (!conn) + { + LOG_DBG("no connection wants to do stuff"); + return; + } + + LOG_DBG("processing conn %p", conn); + + if (conn->state != BT_CONN_CONNECTED) + { + LOG_DBG("conn %p: not connected: state %d", conn, conn->state); + goto raise_and_exit; + } + + /* now that we are guaranteed resources, we can pull data from the upper + * layer (L2CAP or ISO). + */ + + buf = conn->tx_data_pull(conn, conn_mtu(conn), &buf_len); + if (!buf) + { + /* Either there is no more data, or the buffer is already in-use + * by a view on it. In both cases, the TX processor will be triggered + * again, either by the view's destroy callback, or by the upper layer + * when it has more data. + */ + + LOG_DBG("no buf returned"); + + goto exit; + } + + bool last_buf = conn_mtu(conn) >= buf_len; + + if (last_buf) + { + /* Only pull the callback info from the last buffer. + * We still allocate one TX context per-fragment though. + */ + + conn->get_and_clear_cb(conn, buf, &cb, &ud); + LOG_DBG("pop: cb %p userdata %p", cb, ud); + } + + LOG_DBG("TX process: conn %p buf %p (%s)", conn, buf, + last_buf ? "last" : "frag"); + + int err = send_buf(conn, buf, buf_len, cb, ud); + + if (err) + { + LOG_ERR("Fatal error (%d). Disconnecting %p", err, conn); + bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + goto exit; + } + +raise_and_exit: + /* Always kick the TX work. It will self-suspend if it doesn't get + * resources or there is nothing left to send. + */ + + bt_tx_irq_raise(); + +exit: + + /* Give back the ref that `get_conn_ready()` gave us */ + + bt_conn_unref(conn); +} + +static void process_unack_tx(struct bt_conn *conn) +{ + LOG_DBG("%p", conn); + + /* Return any unacknowledged packets */ + + while (1) + { + struct bt_conn_tx *tx; + struct list_node *node; + + node = list_remove_head(&conn->tx_pending); + + if (!node) + { + bt_tx_irq_raise(); + return; + } + + tx = CONTAINER_OF(node, struct bt_conn_tx, node); + + conn_tx_destroy(conn, tx); + nxsem_post(bt_conn_get_pkts(conn)); + } +} + +static struct bt_conn *conn_lookup_handle(struct bt_conn *conns, + size_t size, uint16_t handle) +{ + int i; + + for (i = 0; i < size; i++) + { + struct bt_conn *conn = bt_conn_ref(&conns[i]); + + if (!conn) + { + continue; + } + + /* We only care about connections with a valid handle */ + + if (!bt_conn_is_handle_valid(conn)) + { + bt_conn_unref(conn); + continue; + } + + if (conn->handle != handle) + { + bt_conn_unref(conn); + continue; + } + + return conn; + } + + return NULL; +} + +void bt_conn_set_state(struct bt_conn *conn, bt_conn_state_t state) +{ + bt_conn_state_t old_state; + + LOG_DBG("%s -> %s", state2str(conn->state), state2str(state)); + + if (conn->state == state) + { + LOG_WRN("no transition %s", state2str(state)); + return; + } + + old_state = conn->state; + conn->state = state; + + /* Actions needed for exiting the old state */ + + switch (old_state) + { + case BT_CONN_DISCONNECTED: + /* Take a reference for the first state transition after + * bt_conn_add_le() and keep it until reaching DISCONNECTED again. + */ + + if (!bt_conn_is_iso(conn)) + { + bt_conn_ref(conn); + } + break; + case BT_CONN_INITIATING: + if (IS_ENABLED(CONFIG_BT_CENTRAL) && bt_conn_is_le(conn)) + { + k_work_cancel_delayable(&conn->deferred_work); + } + break; + default: + break; + } + + /* Actions needed for entering the new state */ + + switch (conn->state) + { + case BT_CONN_CONNECTED: + if (bt_conn_is_sco(conn)) + { + if (IS_ENABLED(CONFIG_BT_CLASSIC)) + { + bt_sco_connected(conn); + } + break; + } + + if (bt_conn_is_iso(conn)) + { + bt_iso_connected(conn); + break; + } + +#if defined(CONFIG_BT_CONN) + list_initialize(&conn->channels); + + if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && + conn->role == BT_CONN_ROLE_PERIPHERAL) + { +#if defined(CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS) + if (bt_conn_is_le(conn)) + { + conn->le.conn_param_retry_countdown = + CONFIG_BT_CONN_PARAM_RETRY_COUNT; + } +#endif /* CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS */ + + bt_work_schedule(&conn->deferred_work, CONN_UPDATE_TIMEOUT); + } +#endif /* CONFIG_BT_CONN */ + + break; + case BT_CONN_DISCONNECTED: +#if defined(CONFIG_BT_CONN) + if (bt_conn_is_sco(conn)) + { + if (IS_ENABLED(CONFIG_BT_CLASSIC)) + { + bt_sco_disconnected(conn); + } + + bt_conn_unref(conn); + break; + } + + /* Notify disconnection and queue a dummy buffer to wake + * up and stop the tx thread for states where it was running. + */ + + switch (old_state) + { + case BT_CONN_DISCONNECT_COMPLETE: + /* Any previously scheduled deferred work now becomes invalid + * so cancel it here, before we yield to tx thread. + */ + + k_work_cancel_delayable(&conn->deferred_work); + + bt_conn_tx_notify(conn, true); + + bt_conn_reset_rx_state(conn); + + LOG_DBG("trigger disconnect work"); + bt_work_reschedule(&conn->deferred_work, 0); + + /* The last ref will be dropped during cleanup */ + + break; + case BT_CONN_INITIATING: + /* LE Create Connection command failed. This might be + * directly from the API, don't notify application in this + * case. + */ + + if (conn->err) + { + notify_connected(conn); + } + + bt_conn_unref(conn); + break; + case BT_CONN_SCAN_BEFORE_INITIATING: + /* This indicates that connection establishment + * has been stopped. This could either be triggered by the + * application through bt_conn_disconnect or by timeout set by + * bt_conn_le_create_param.timeout. + */ + + if (IS_ENABLED(CONFIG_BT_CENTRAL)) + { + int err = bt_le_scan_user_remove(BT_LE_SCAN_USER_CONN); + + if (err) + { + LOG_WRN( + "Error while removing conn user from scanner (%d)", + err); + } + + if (conn->err) + { + notify_connected(conn); + } + } + + bt_conn_unref(conn); + break; + case BT_CONN_ADV_DIR_CONNECTABLE: + + /* this indicate Directed advertising stopped */ + + if (conn->err) + { + notify_connected(conn); + } + + bt_conn_unref(conn); + break; + case BT_CONN_INITIATING_FILTER_LIST: + /* this indicates LE Create Connection with filter + * policy has been stopped. This can only be triggered by the + * application, so don't notify. + */ + + bt_conn_unref(conn); + break; + case BT_CONN_ADV_CONNECTABLE: + /* This can only happen when application stops the + * advertiser, conn->err is never set in this case. + */ + + bt_conn_unref(conn); + break; + case BT_CONN_CONNECTED: + case BT_CONN_DISCONNECTING: + case BT_CONN_DISCONNECTED: + + /* Cannot happen. */ + + LOG_WRN("Invalid (%u) old state", state); + break; + } + break; + case BT_CONN_INITIATING_FILTER_LIST: + break; + case BT_CONN_ADV_CONNECTABLE: + break; + case BT_CONN_SCAN_BEFORE_INITIATING: + break; + case BT_CONN_ADV_DIR_CONNECTABLE: + break; + case BT_CONN_INITIATING: + if (bt_conn_is_sco(conn)) + { + break; + } + + /* Timer is needed only for LE. For other link types controller + * will handle connection timeout. + */ + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && bt_conn_is_le(conn) && + bt_dev.create_param.timeout != 0) + { + bt_work_schedule(&conn->deferred_work, + MSEC2TICK(10 * bt_dev.create_param.timeout)); + } + + break; + case BT_CONN_DISCONNECTING: + break; +#endif /* CONFIG_BT_CONN */ + case BT_CONN_DISCONNECT_COMPLETE: + if (conn->err == BT_HCI_ERR_CONN_FAIL_TO_ESTAB) + { + /* No ACK or data was ever received. The peripheral may be + * unaware of the connection attempt. + * + * Beware of confusing higher layer errors. Anything that looks + * like it's from the remote is synthetic. + */ + + LOG_WRN("conn %p failed to establish. RF noise?", conn); + } + + process_unack_tx(conn); + break; + default: + LOG_WRN("no valid (%u) state was set", state); + + break; + } +} + +struct bt_conn *bt_conn_lookup_handle(uint16_t handle, + enum bt_conn_type type) +{ + struct bt_conn *conn; + +#if defined(CONFIG_BT_CONN) + conn = conn_lookup_handle(acl_conns, ARRAY_SIZE(acl_conns), handle); + if (conn) + { + goto found; + } +#endif /* CONFIG_BT_CONN */ + +#if defined(CONFIG_BT_ISO) + conn = conn_lookup_handle(iso_conns, ARRAY_SIZE(iso_conns), handle); + if (conn) + { + goto found; + } +#endif + +#if defined(CONFIG_BT_CLASSIC) + conn = conn_lookup_handle(sco_conns, ARRAY_SIZE(sco_conns), handle); + if (conn) + { + goto found; + } +#endif + +found: + if (conn) + { + if (type & conn->type) + { + return conn; + } + + LOG_WRN("incompatible handle %u", handle); + bt_conn_unref(conn); + } + + return NULL; +} + +struct bt_conn *bt_hci_conn_lookup_handle(uint16_t handle) +{ + return bt_conn_lookup_handle(handle, BT_CONN_TYPE_ALL); +} + +void bt_conn_foreach( + enum bt_conn_type type, void (*func)(struct bt_conn *conn, void *data), + void *data) +{ + int i; + +#if defined(CONFIG_BT_CONN) + for (i = 0; i < ARRAY_SIZE(acl_conns); i++) + { + struct bt_conn *conn = bt_conn_ref(&acl_conns[i]); + + if (!conn) + { + continue; + } + + if (!(conn->type & type)) + { + bt_conn_unref(conn); + continue; + } + + func(conn, data); + bt_conn_unref(conn); + } +#if defined(CONFIG_BT_CLASSIC) + + if (type & BT_CONN_TYPE_SCO) + { + for (i = 0; i < ARRAY_SIZE(sco_conns); i++) + { + struct bt_conn *conn = bt_conn_ref(&sco_conns[i]); + + if (!conn) + { + continue; + } + + func(conn, data); + bt_conn_unref(conn); + } + } +#endif /* defined(CONFIG_BT_CLASSIC) */ +#endif /* CONFIG_BT_CONN */ + +#if defined(CONFIG_BT_ISO) + if (type & BT_CONN_TYPE_ISO) + { + for (i = 0; i < ARRAY_SIZE(iso_conns); i++) + { + struct bt_conn *conn = bt_conn_ref(&iso_conns[i]); + + if (!conn) + { + continue; + } + + func(conn, data); + bt_conn_unref(conn); + } + } +#endif /* defined(CONFIG_BT_ISO) */ +} + +struct bt_conn *bt_conn_ref(struct bt_conn *conn) +{ + int32_t old; + + DEBUGASSERT(conn); + + /* Reference counter must be checked to avoid incrementing ref from + * zero, then we should return NULL instead. Loop on clear-and-set in case + * someone has modified the reference count since the read, and start over + * again when that happens. + */ + + do + { + old = atomic_read(&conn->ref); + + if (!old) + { + return NULL; + } + } + while (!atomic_cmpxchg_value(&conn->ref, old, old + 1)); + + LOG_DBG("handle %u ref %ld -> %ld", conn->handle, old, old + 1); + + return conn; +} + +#if defined(CONFIG_BT_CONN) +static sem_t pending_recycled_events = NXSEM_INITIALIZER(0, 0); + +static void recycled_work_handler(struct k_work *work) +{ + if (nxsem_trywait(&pending_recycled_events) == 0) + { + notify_recycled_conn_slot(); + k_work_submit(work); + } +} + +static K_WORK_DEFINE(recycled_work, recycled_work_handler); +#endif /* CONFIG_BT_CONN */ + +void bt_conn_unref(struct bt_conn *conn) +{ + int32_t old; + bool deallocated; + uint16_t conn_handle; + + /* Used only if CONFIG_ASSERT and CONFIG_BT_CONN_TX. */ + + __maybe_unused bool conn_tx_is_pending; + + DEBUGASSERT(conn); + + /* If we're removing the last reference, the connection object will be + * considered freed and possibly re-allocated by the Bluetooth Host stack + * as soon as we decrement the counter. To prevent inconsistencies when + * accessing the connection's state, we store its properties of interest + * before decrementing the ref-count, then unset the local pointer. + */ + + conn_handle = conn->handle; +#if defined(CONFIG_BT_CONN_TX) && defined(CONFIG_ASSERT) + conn_tx_is_pending = k_work_is_pending(&conn->tx_complete_work); +#endif + old = atomic_sub(&conn->ref, 1); + + LOG_DBG("handle %u ref %ld -> %ld", conn_handle, old, (old - 1)); + + DEBUGASSERT(old > 0); + + /* Whether we removed the last reference. */ + + deallocated = (old == 1); + if (!deallocated) + { + return; + } + + IF_ENABLED(CONFIG_BT_CONN_TX, (DEBUGASSERT(!conn_tx_is_pending))); + +#if defined(CONFIG_BT_CONN) + /* Notify listeners that a slot has been freed and can be taken. + * No guarantees are made on requests to claim connection object as only + * the first claim will be served. + */ + + if (IS_ARRAY_ELEMENT(acl_conns, conn)) + { + nxsem_post(&pending_recycled_events); + k_work_submit(&recycled_work); + } +#endif /* CONFIG_BT_CONN */ +} + +void bt_conn_drop(struct bt_conn **orig) +{ + struct bt_conn *conn = bt_conn_take(orig); + + if (conn != NULL) + { + bt_conn_unref(conn); + } +} + +uint8_t bt_conn_index(const struct bt_conn *conn) +{ + ptrdiff_t index = 0; + + switch (conn->type) + { +#if defined(CONFIG_BT_ISO) + case BT_CONN_TYPE_ISO: + DEBUGASSERT(IS_ARRAY_ELEMENT(iso_conns, conn)); + index = ARRAY_INDEX(iso_conns, conn); + break; +#endif +#if defined(CONFIG_BT_CLASSIC) + case BT_CONN_TYPE_SCO: + DEBUGASSERT(IS_ARRAY_ELEMENT(sco_conns, conn)); + index = ARRAY_INDEX(sco_conns, conn); + break; +#endif + default: +#if defined(CONFIG_BT_CONN) + DEBUGASSERT(IS_ARRAY_ELEMENT(acl_conns, conn)); + index = ARRAY_INDEX(acl_conns, conn); +#else + DEBUGASSERT(false); +#endif /* CONFIG_BT_CONN */ + break; + } + + return (uint8_t)index; +} + +#if defined(CONFIG_NET_BUF_LOG) +struct net_buf *bt_conn_create_pdu_timeout_debug( + struct net_buf_pool *pool, size_t reserve, clock_t timeout, + const char *func, int line) +#else +struct net_buf *bt_conn_create_pdu_timeout( + struct net_buf_pool *pool, size_t reserve, clock_t timeout) +#endif +{ + struct net_buf *buf; + + /* PDU must not be allocated from ISR as we block with '((clock_t)-1)' + * during the allocation + */ + + DEBUGASSERT(!up_interrupt_context()); + + if (!((timeout) == (0)) + && nxsched_gettid() == k_work_queue_thread_get(&k_sys_work_q)) + { + LOG_WRN("Timeout discarded. No blocking in syswq."); + timeout = 0; + } + + if (!pool) + { +#if defined(CONFIG_BT_CONN) + pool = &acl_tx_pool; +#else + return NULL; +#endif /* CONFIG_BT_CONN */ + } + + if (IS_ENABLED(CONFIG_BT_CONN_LOG_LEVEL_DBG)) + { +#if defined(CONFIG_NET_BUF_LOG) + buf = net_buf_alloc(pool, 0); +#else + buf = net_buf_alloc(pool, 0); +#endif + if (!buf) + { + LOG_WRN("Unable to allocate buffer with 0"); +#if defined(CONFIG_NET_BUF_LOG) + buf = net_buf_alloc(pool, timeout); +#else + buf = net_buf_alloc(pool, timeout); +#endif + } + } + else + { +#if defined(CONFIG_NET_BUF_LOG) + buf = net_buf_alloc(pool, timeout); +#else + buf = net_buf_alloc(pool, timeout); +#endif + } + + if (!buf) + { + LOG_DBG("Unable to allocate buffer within timeout"); + return NULL; + } + + reserve += sizeof(struct bt_hci_acl_hdr) + BT_BUF_RESERVE; + net_buf_reserve(buf, reserve); + + return buf; +} + +#if defined(CONFIG_BT_CONN_TX) +static void tx_complete_work(struct k_work *work) +{ + struct bt_conn + *conn = CONTAINER_OF(work, struct bt_conn, tx_complete_work); + + tx_notify_process(conn); +} +#endif /* CONFIG_BT_CONN_TX */ + +#if defined(CONFIG_BT_CONN) +static void notify_recycled_conn_slot(void) +{ + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->recycled) + { + callback->recycled(); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->recycled) + { + cb->recycled(); + } + } +} +#endif + +#if !defined(CONFIG_BT_CONN) +int bt_conn_disconnect(struct bt_conn *conn, uint8_t reason) +{ + ARG_UNUSED(conn); + ARG_UNUSED(reason); + + /* Dummy implementation to satisfy the compiler */ + + return 0; +} +#endif /* !CONFIG_BT_CONN */ + +/* Group Connected BT_CONN only in this */ +#if defined(CONFIG_BT_CONN) + +#if defined(CONFIG_BT_AUTO_PHY_PERIPHERAL_1M) +#define AUTO_PHY_PERIPHERAL BT_HCI_LE_PHY_1M +#define AUTO_PHY_PERIPHERAL_PREF BT_HCI_LE_PHY_PREFER_1M +#define AUTO_PHY_PERIPHERAL_SUPPORTED(_feat) (true) +#elif defined(CONFIG_BT_AUTO_PHY_PERIPHERAL_2M) +#define AUTO_PHY_PERIPHERAL BT_HCI_LE_PHY_2M +#define AUTO_PHY_PERIPHERAL_PREF BT_HCI_LE_PHY_PREFER_2M +#define AUTO_PHY_PERIPHERAL_SUPPORTED(feat) BT_FEAT_LE_PHY_2M(feat) +#elif defined(CONFIG_BT_AUTO_PHY_PERIPHERAL_CODED) +#define AUTO_PHY_PERIPHERAL BT_HCI_LE_PHY_CODED +#define AUTO_PHY_PERIPHERAL_PREF BT_HCI_LE_PHY_PREFER_CODED +#define AUTO_PHY_PERIPHERAL_SUPPORTED(feat) BT_FEAT_LE_PHY_CODED(feat) +#else +/* Dummy values when there's no preference */ +#define AUTO_PHY_PERIPHERAL (0) +#define AUTO_PHY_PERIPHERAL_PREF (0) +#define AUTO_PHY_PERIPHERAL_SUPPORTED(_feat) (false) +#endif + +#if defined(CONFIG_BT_AUTO_PHY_CENTRAL_1M) +#define AUTO_PHY_CENTRAL BT_HCI_LE_PHY_1M +#define AUTO_PHY_CENTRAL_PREF BT_HCI_LE_PHY_PREFER_1M +#define AUTO_PHY_CENTRAL_SUPPORTED(_feat) (true) +#elif defined(CONFIG_BT_AUTO_PHY_CENTRAL_2M) +#define AUTO_PHY_CENTRAL BT_HCI_LE_PHY_2M +#define AUTO_PHY_CENTRAL_PREF BT_HCI_LE_PHY_PREFER_2M +#define AUTO_PHY_CENTRAL_SUPPORTED(feat) BT_FEAT_LE_PHY_2M(feat) +#elif defined(CONFIG_BT_AUTO_PHY_CENTRAL_CODED) +#define AUTO_PHY_CENTRAL BT_HCI_LE_PHY_CODED +#define AUTO_PHY_CENTRAL_PREF BT_HCI_LE_PHY_PREFER_CODED +#define AUTO_PHY_CENTRAL_SUPPORTED(feat) BT_FEAT_LE_PHY_CODED(feat) +#else +/* Dummy values when there's no preference */ +#define AUTO_PHY_CENTRAL (0) +#define AUTO_PHY_CENTRAL_PREF (0) +#define AUTO_PHY_CENTRAL_SUPPORTED(_feat) (false) +#endif + +static int do_phy_update(struct bt_conn *conn) +{ + uint8_t phy; + uint8_t pref; + bool supported; + + switch (conn->role) + { +#if !defined(CONFIG_BT_AUTO_PHY_CENTRAL_NONE) + case BT_HCI_ROLE_CENTRAL: + phy = AUTO_PHY_CENTRAL; + pref = AUTO_PHY_CENTRAL_PREF; + supported = AUTO_PHY_CENTRAL_SUPPORTED(bt_dev.le.features); + break; +#endif +#if !defined(CONFIG_BT_AUTO_PHY_PERIPHERAL_NONE) + case BT_HCI_ROLE_PERIPHERAL: + phy = AUTO_PHY_PERIPHERAL; + pref = AUTO_PHY_PERIPHERAL_PREF; + supported = AUTO_PHY_PERIPHERAL_SUPPORTED(bt_dev.le.features); + break; +#endif + default: + return 0; + } + + if (!supported) + { + LOG_WRN("PHY 0x%02x not supported", phy); + return 0; + } + +#if defined(CONFIG_BT_USER_PHY_UPDATE) + if (IS_ENABLED(CONFIG_BT_EXT_ADV)) + { + /* If the current PHYs are already the preferred PHYs, no need to issue + * a PHY update procedure. + */ + + if (conn->le.phy.tx_phy == phy && conn->le.phy.rx_phy == phy) + { + return 0; + } + } +#endif + + return bt_le_set_phy(conn, 0U, pref, pref, BT_HCI_LE_PHY_CODED_ANY); +} + +static bool can_initiate_feature_exchange(struct bt_conn *conn) +{ + /* Spec says both central and peripheral can send the command. However, + * peripheral-initiated feature exchange is an optional feature. + * + * We provide an optimization if we are in the same image as the + * controller, as we know at compile time whether it supports or not + * peripheral feature exchange. + */ + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && (conn->role == BT_HCI_ROLE_CENTRAL)) + { + return true; + } + + if (IS_ENABLED(CONFIG_HAS_BT_CTLR) && + IS_ENABLED(CONFIG_BT_CTLR_PER_INIT_FEAT_XCHG)) + { + return true; + } + + return BT_FEAT_LE_PER_INIT_FEAT_XCHG(bt_dev.le.features); +} + +static void perform_auto_initiated_procedures(struct bt_conn *conn, + void *unused) +{ + int err; + + ARG_UNUSED(unused); + + LOG_DBG("[%p] Running auto-initiated procedures", conn); + + if (conn->state != BT_CONN_CONNECTED) + { + /* It is possible that connection was disconnected directly from + * connected callback so we must check state before doing connection + * parameters update. + */ + + return; + } + + if (atomic_test_and_set_bit(conn->flags, + BT_CONN_AUTO_INIT_PROCEDURES_DONE)) + { + /* We have already run the auto-initiated procedures */ + + return; + } + + if (!atomic_test_bit(conn->flags, BT_CONN_LE_FEATURES_EXCHANGED) && + can_initiate_feature_exchange(conn)) + { + err = bt_hci_le_read_remote_features(conn); + if (err) + { + LOG_ERR("Failed read remote features (%d)", err); + } + + if (conn->state != BT_CONN_CONNECTED) + { + return; + } + } + + if (IS_ENABLED(CONFIG_BT_REMOTE_VERSION) && + !atomic_test_bit(conn->flags, BT_CONN_AUTO_VERSION_INFO)) + { + err = bt_hci_read_remote_version(conn); + if (err) + { + LOG_ERR("Failed read remote version (%d)", err); + } + + if (conn->state != BT_CONN_CONNECTED) + { + return; + } + } + + if (IS_ENABLED(CONFIG_BT_PHY_UPDATE) && + (!IS_ENABLED(CONFIG_BT_AUTO_PHY_CENTRAL_NONE) || + !IS_ENABLED(CONFIG_BT_AUTO_PHY_PERIPHERAL_NONE))) + { + err = do_phy_update(conn); + if (err) + { + LOG_ERR("Failed LE Set PHY (%d)", err); + } + + if (conn->state != BT_CONN_CONNECTED) + { + return; + } + } + + /* Data length should be automatically updated to the maximum by the + * controller. Not updating it is a quirk and this is the workaround. + */ + + if (IS_ENABLED(CONFIG_BT_AUTO_DATA_LEN_UPDATE) && + BT_FEAT_LE_DLE(bt_dev.le.features) && + bt_drv_quirk_no_auto_dle()) + { + uint16_t tx_octets; + uint16_t tx_time; + + err = bt_hci_le_read_max_data_len(&tx_octets, &tx_time); + if (!err) + { + err = bt_le_set_data_len(conn, tx_octets, tx_time); + if (err) + { + LOG_ERR("Failed to set data len (%d)", err); + } + } + } + + LOG_DBG("[%p] Successfully ran auto-initiated procedures", conn); +} + +/* Executes procedures after a connection is established: + * - read remote features + * - read remote version + * - update PHY + * - update data length + */ + +static void auto_initiated_procedures(struct k_work *unused) +{ + ARG_UNUSED(unused); + + bt_conn_foreach(BT_CONN_TYPE_LE, perform_auto_initiated_procedures, NULL); +} + +static K_WORK_DEFINE(procedures_on_connect, auto_initiated_procedures); + +static void schedule_auto_initiated_procedures(struct bt_conn *conn) +{ + LOG_DBG("[%p] Scheduling auto-init procedures", conn); + bt_work_submit(&procedures_on_connect); +} + +void bt_conn_connected(struct bt_conn *conn) +{ + schedule_auto_initiated_procedures(conn); + bt_l2cap_connected(conn); + notify_connected(conn); +} + +#if defined(CONFIG_BT_CLASSIC) +void bt_conn_br_role_changed(struct bt_conn *conn, uint8_t status) +{ + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->br.role_changed) + { + callback->br.role_changed(conn, status); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->br.role_changed) + { + cb->br.role_changed(conn, status); + } + } +} + +void bt_conn_br_packet_type_changed(struct bt_conn *conn, + uint8_t status, uint16_t packet_type) +{ + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->br.packet_type_changed != NULL) + { + callback->br.packet_type_changed(conn, status, packet_type); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->br.packet_type_changed != NULL) + { + cb->br.packet_type_changed(conn, status, packet_type); + } + } +} +#endif + +static int conn_disconnect(struct bt_conn *conn, uint8_t reason) +{ + int err; + + err = bt_hci_disconnect(conn->handle, reason); + if (err) + { + return err; + } + + if (conn->state == BT_CONN_CONNECTED) + { + bt_conn_set_state(conn, BT_CONN_DISCONNECTING); + } + + return 0; +} + +int bt_conn_disconnect(struct bt_conn *conn, uint8_t reason) +{ + switch (conn->state) + { + case BT_CONN_SCAN_BEFORE_INITIATING: + conn->err = reason; + bt_conn_set_state(conn, BT_CONN_DISCONNECTED); + if (IS_ENABLED(CONFIG_BT_CENTRAL)) + { + return bt_le_scan_user_add(BT_LE_SCAN_USER_CONN); + } + + return 0; + case BT_CONN_INITIATING: + if (bt_conn_is_le(conn)) + { + if (IS_ENABLED(CONFIG_BT_CENTRAL)) + { + k_work_cancel_delayable(&conn->deferred_work); + return bt_le_create_conn_cancel(); + } + } +#if defined(CONFIG_BT_ISO) + else if (bt_conn_is_iso(conn)) + { + return conn_disconnect(conn, reason); + } +#endif /* CONFIG_BT_ISO */ +#if defined(CONFIG_BT_CLASSIC) + else if (bt_conn_is_br(conn)) + { + return bt_hci_connect_br_cancel(conn); + } + else if (bt_conn_is_sco(conn)) + { + /* There is no HCI cmd to cancel SCO connecting from spec */ + + return -EPROTONOSUPPORT; + } +#endif /* CONFIG_BT_CLASSIC */ + else + { + DEBUGASSERT(false); + } + + return 0; + case BT_CONN_CONNECTED: + return conn_disconnect(conn, reason); + case BT_CONN_DISCONNECTING: + return 0; + case BT_CONN_DISCONNECTED: + default: + return -ENOTCONN; + } +} + +static void notify_connected(struct bt_conn *conn) +{ + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->connected) + { + callback->connected(conn, conn->err); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->connected) + { + cb->connected(conn, conn->err); + } + } +} + +static void notify_disconnected(struct bt_conn *conn) +{ + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->disconnected) + { + callback->disconnected(conn, conn->err); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->disconnected) + { + cb->disconnected(conn, conn->err); + } + } +} + +#if defined(CONFIG_BT_REMOTE_INFO) +void bt_conn_notify_remote_info(struct bt_conn *conn) +{ + struct bt_conn_remote_info remote_info; + int err; + + err = bt_conn_get_remote_info(conn, &remote_info); + if (err) + { + LOG_DBG("Notify remote info failed %d", err); + return; + } + + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->remote_info_available) + { + callback->remote_info_available(conn, &remote_info); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->remote_info_available) + { + cb->remote_info_available(conn, &remote_info); + } + } +} +#endif /* defined(CONFIG_BT_REMOTE_INFO) */ + +#if defined(CONFIG_BT_USER_CONN_PARAM_REJECTED) +void bt_conn_notify_le_param_rejected(struct bt_conn *conn, uint8_t hci_err) +{ + if (IS_ENABLED(CONFIG_BT_CONN_DYNAMIC_CALLBACKS)) + { + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->le_param_update_rejected != NULL) + { + callback->le_param_update_rejected(conn, hci_err); + } + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->le_param_update_rejected != NULL) + { + cb->le_param_update_rejected(conn, hci_err); + } + } +} +#endif /* defined(CONFIG_BT_USER_CONN_PARAM_REJECTED) */ + +void bt_conn_notify_le_param_updated(struct bt_conn *conn) +{ + uint16_t interval_1250us = + conn->le.interval_us / BT_HCI_LE_INTERVAL_UNIT_US; + + /* If new connection parameters meet requirement of pending + * parameters don't send peripheral conn param request anymore on timeout + */ + + if (atomic_test_bit(conn->flags, BT_CONN_PERIPHERAL_PARAM_SET) && + interval_1250us >= conn->le.interval_min && + interval_1250us <= conn->le.interval_max && + conn->le.latency == conn->le.pending_latency && + conn->le.timeout == conn->le.pending_timeout) + { + atomic_clear_bit(conn->flags, BT_CONN_PERIPHERAL_PARAM_SET); + } + + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->le_param_updated) + { + callback->le_param_updated(conn, interval_1250us, conn->le.latency, + conn->le.timeout); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->le_param_updated) + { + cb->le_param_updated(conn, interval_1250us, conn->le.latency, + conn->le.timeout); + } + } +} + +#if defined(CONFIG_BT_USER_DATA_LEN_UPDATE) +void bt_conn_notify_le_data_len_updated(struct bt_conn *conn) +{ + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->le_data_len_updated) + { + callback->le_data_len_updated(conn, &conn->le.data_len); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->le_data_len_updated) + { + cb->le_data_len_updated(conn, &conn->le.data_len); + } + } +} +#endif + +#if defined(CONFIG_BT_USER_PHY_UPDATE) +void bt_conn_notify_le_phy_updated(struct bt_conn *conn) +{ + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->le_phy_updated) + { + callback->le_phy_updated(conn, &conn->le.phy); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->le_phy_updated) + { + cb->le_phy_updated(conn, &conn->le.phy); + } + } +} +#endif + +bool bt_conn_le_param_req(struct bt_conn *conn, + struct bt_le_conn_param *param) +{ + if (!bt_le_conn_params_valid(param)) + { + return false; + } + + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (!callback->le_param_req) + { + continue; + } + + if (!callback->le_param_req(conn, param)) + { + return false; + } + +/* The callback may modify the parameters so we need to + * double-check that it returned valid parameters. + */ + + if (!bt_le_conn_params_valid(param)) + { + return false; + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (!cb->le_param_req) + { + continue; + } + + if (!cb->le_param_req(conn, param)) + { + return false; + } + +/* The callback may modify the parameters so we need to + * double-check that it returned valid parameters. + */ + + if (!bt_le_conn_params_valid(param)) + { + return false; + } + } + + /* Default to accepting if there's no app callback */ + + return true; +} + +static int send_conn_le_param_update(struct bt_conn *conn, + const struct bt_le_conn_param *param) +{ + LOG_DBG("conn %p features 0x%02x params (%d-%d %d %d)", conn, + conn->le.features[0], + param->interval_min, param->interval_max, param->latency, + param->timeout); + + /* Proceed only if connection parameters contains valid values */ + + if (!bt_le_conn_params_valid(param)) + { + return -EINVAL; + } + + /* Use LE connection parameter request if both local and remote support + * it; or if local role is central then use LE connection update. + */ + + if ((BT_FEAT_LE_CONN_PARAM_REQ_PROC(bt_dev.le.features) && + BT_FEAT_LE_CONN_PARAM_REQ_PROC(conn->le.features) && + !atomic_test_bit(conn->flags, BT_CONN_PERIPHERAL_PARAM_L2CAP)) || + (conn->role == BT_HCI_ROLE_CENTRAL)) + { + int rc; + + rc = bt_conn_le_conn_update(conn, param); + + /* store those in case of fallback to L2CAP */ + + if (rc == 0) + { + conn->le.interval_min = param->interval_min; + conn->le.interval_max = param->interval_max; + conn->le.pending_latency = param->latency; + conn->le.pending_timeout = param->timeout; + } + + return rc; + } + + /* If remote central does not support LL Connection Parameters Request + * Procedure + */ + + return bt_l2cap_update_conn_param(conn, param); +} + +#if defined(CONFIG_BT_CLASSIC) +static struct bt_conn *conn_lookup_sco(struct bt_conn *conn) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sco_conns); i++) + { + struct bt_conn *sco = bt_conn_ref(&sco_conns[i]); + + if (sco == NULL) + { + continue; + } + + if (sco->sco.acl == conn) + { + return sco; + } + + bt_conn_unref(sco); + } + + return NULL; +} +#endif /* CONFIG_BT_CLASSIC */ + +static void deferred_work(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_conn *conn = CONTAINER_OF(dwork, struct bt_conn, deferred_work); + const struct bt_le_conn_param *param; + + LOG_DBG("conn %p", conn); + + if (conn->state == BT_CONN_DISCONNECTED) + { +#if defined(CONFIG_BT_ISO_UNICAST) + bool acl_coupled_with_cis; + + if (bt_conn_is_iso(conn)) + { + /* bt_iso_disconnected is responsible for unref'ing the + * connection pointer, as it is conditional on whether the + * connection is a central or peripheral. + */ + + bt_iso_disconnected(conn); + return; + } + + /* Mark all CIS still associated with the ACL conn as disconnecting. + * If any CIS are associated with the ACL, we postpone the disconnect + * work until after the CIS has been disconnected from a HCI Disconnect + * event. + */ + + acl_coupled_with_cis = false; + ARRAY_FOR_EACH_PTR(iso_conns, iso_conn) + { + struct bt_conn *iso = bt_conn_ref(iso_conn); + + if (iso == NULL) + { + continue; + } + + if (iso->iso.acl == conn) + { + struct bt_iso_chan *chan = iso->iso.chan; + + if (chan != NULL) + { + bt_iso_chan_set_state(chan, BT_ISO_STATE_DISCONNECTING); + } + + acl_coupled_with_cis = true; + } + + bt_conn_unref(iso); + } + + if (acl_coupled_with_cis) + { + LOG_DBG("acl %p is pending on CIS disconnects, wait for CIS " + "disconnects", + conn); + + return; + } +#endif +#if defined(CONFIG_BT_CLASSIC) + + struct bt_conn *sco; + + /* Mark all SCO channels associated + * with ACL conn as not connected, and remove ACL reference + */ + + sco = conn_lookup_sco(conn); + while (sco != NULL) + { + struct bt_sco_chan *chan = sco->sco.chan; + + if (chan != NULL) + { + bt_sco_chan_set_state(chan, BT_SCO_STATE_DISCONNECTING); + } + + bt_sco_cleanup_acl(sco); + + bt_conn_unref(sco); + sco = conn_lookup_sco(conn); + } +#endif /* CONFIG_BT_CLASSIC */ + + bt_l2cap_disconnected(conn); + notify_disconnected(conn); + + /* Release the reference we took for the very first + * state transition. + */ + + bt_conn_unref(conn); + return; + } + + if (!bt_conn_is_le(conn)) + { + return; + } + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && conn->role == BT_CONN_ROLE_CENTRAL) + { + /* we don't call bt_conn_disconnect as it would also clear + * auto connect flag if it was set, instead just cancel connection + * directly + */ + + if (bt_le_create_conn_cancel() == -ENOBUFS) + { + LOG_WRN("No buffers to cancel connection, retrying in 10 ms"); + bt_work_reschedule(dwork, MSEC2TICK(10)); + } + + return; + } + + /* if application set own params use those, otherwise use defaults. */ + + if (atomic_test_and_clear_bit(conn->flags, BT_CONN_PERIPHERAL_PARAM_SET)) + { + int err; + + param = BT_LE_CONN_PARAM(conn->le.interval_min, conn->le.interval_max, + conn->le.pending_latency, + conn->le.pending_timeout); + + err = send_conn_le_param_update(conn, param); + if (!err) + { + atomic_clear_bit(conn->flags, + BT_CONN_PERIPHERAL_PARAM_AUTO_UPDATE); + } + else + { + LOG_WRN("Send LE param update failed (err %d)", err); + } + } + else if (IS_ENABLED(CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS)) + { +#if defined(CONFIG_BT_GAP_PERIPHERAL_PREF_PARAMS) + int err; + + param = BT_LE_CONN_PARAM(CONFIG_BT_PERIPHERAL_PREF_MIN_INT, + CONFIG_BT_PERIPHERAL_PREF_MAX_INT, + CONFIG_BT_PERIPHERAL_PREF_LATENCY, + CONFIG_BT_PERIPHERAL_PREF_TIMEOUT); + + err = send_conn_le_param_update(conn, param); + if (!err) + { + atomic_set_bit(conn->flags, BT_CONN_PERIPHERAL_PARAM_AUTO_UPDATE); + } + else + { + LOG_WRN("Send auto LE param update failed (err %d)", err); + } +#endif + } + else + { + /* Neither the application nor the configuration + * set LE connection parameters. + */ + } + + atomic_set_bit(conn->flags, BT_CONN_PERIPHERAL_PARAM_UPDATE); +} + +static struct bt_conn *acl_conn_new(void) +{ + return bt_conn_new(acl_conns, ARRAY_SIZE(acl_conns)); +} + +#if defined(CONFIG_BT_CLASSIC) +static struct bt_conn *sco_conn_new(void) +{ + return bt_conn_new(sco_conns, ARRAY_SIZE(sco_conns)); +} + +struct bt_conn *bt_conn_lookup_addr_sco(const bt_addr_t *peer) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sco_conns); i++) + { + struct bt_conn *conn = bt_conn_ref(&sco_conns[i]); + + if (!conn) + { + continue; + } + + if (!bt_conn_is_sco(conn)) + { + bt_conn_unref(conn); + continue; + } + + if (!bt_addr_eq(peer, &conn->sco.acl->br.dst)) + { + bt_conn_unref(conn); + continue; + } + + return conn; + } + + return NULL; +} + +struct bt_conn *bt_conn_lookup_addr_br(const bt_addr_t *peer) +{ + int i; + + if (peer == NULL) + { + LOG_DBG("Invalid peer address"); + return NULL; + } + + for (i = 0; i < ARRAY_SIZE(acl_conns); i++) + { + struct bt_conn *conn = bt_conn_ref(&acl_conns[i]); + + if (!conn) + { + continue; + } + + if (!bt_conn_is_br(conn)) + { + bt_conn_unref(conn); + continue; + } + + if (!bt_addr_eq(peer, &conn->br.dst)) + { + bt_conn_unref(conn); + continue; + } + + return conn; + } + + return NULL; +} + +struct bt_conn *bt_conn_add_sco(const bt_addr_t *peer, int link_type) +{ + struct bt_conn *sco_conn = sco_conn_new(); + + if (!sco_conn) + { + return NULL; + } + + sco_conn->sco.acl = bt_conn_lookup_addr_br(peer); + if (!sco_conn->sco.acl) + { + bt_conn_unref(sco_conn); + return NULL; + } + + sco_conn->type = BT_CONN_TYPE_SCO; + + if (link_type == BT_HCI_SCO) + { + if (BT_FEAT_LMP_ESCO_CAPABLE(bt_dev.features)) + { + sco_conn->sco.pkt_type = (bt_dev.br.esco_pkt_type & ESCO_PKT_MASK); + } + else + { + sco_conn->sco.pkt_type = (bt_dev.br.esco_pkt_type & SCO_PKT_MASK); + } + } + else if (link_type == BT_HCI_ESCO) + { + sco_conn->sco.pkt_type = + (bt_dev.br.esco_pkt_type & ~EDR_ESCO_PKT_MASK); + } + else + { + /* Ignoring unexpected link type BT_HCI_ACL. */ + } + + return sco_conn; +} + +struct bt_conn *bt_conn_add_br(const bt_addr_t *peer) +{ + struct bt_conn *conn = acl_conn_new(); + + if (!conn) + { + return NULL; + } + + bt_addr_copy(&conn->br.dst, peer); + conn->type = BT_CONN_TYPE_BR; + conn->tx_data_pull = l2cap_br_data_pull; + conn->get_and_clear_cb = acl_get_and_clear_cb; + conn->has_data = acl_has_data; + +#if defined(CONFIG_BT_POWER_MODE_CONTROL) + conn->br.mode = BT_ACTIVE_MODE; +#endif /* CONFIG_BT_POWER_MODE_CONTROL */ + + return conn; +} +#endif /* CONFIG_BT_CLASSIC */ + +#if defined(CONFIG_BT_SMP) +bool bt_conn_ltk_present(const struct bt_conn *conn) +{ + const struct bt_keys *keys = conn->le.keys; + + if (!keys) + { + keys = bt_keys_find_addr(conn->id, &conn->le.dst); + } + + if (keys) + { + if (conn->role == BT_HCI_ROLE_CENTRAL) + { + return keys->keys & (BT_KEYS_LTK_P256 | BT_KEYS_PERIPH_LTK); + } + else + { + return keys->keys & (BT_KEYS_LTK_P256 | BT_KEYS_LTK); + } + } + + return false; +} + +void bt_conn_identity_resolved(struct bt_conn *conn) +{ + const bt_addr_le_t *rpa; + + if (conn->role == BT_HCI_ROLE_CENTRAL) + { + rpa = &conn->le.resp_addr; + } + else + { + rpa = &conn->le.init_addr; + } + + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->identity_resolved) + { + callback->identity_resolved(conn, rpa, &conn->le.dst); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->identity_resolved) + { + cb->identity_resolved(conn, rpa, &conn->le.dst); + } + } +} + +int bt_conn_le_start_encryption( + struct bt_conn *conn, uint8_t rand[8], uint8_t ediv[2], const uint8_t *ltk, + size_t len) +{ + struct bt_hci_cp_le_start_encryption *cp; + struct net_buf *buf; + + if (len > sizeof(cp->ltk)) + { + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + (void)memcpy(cp->rand, rand, sizeof(cp->rand)); + (void)memcpy(cp->ediv, ediv, sizeof(cp->ediv)); + + memcpy(cp->ltk, ltk, len); + if (len < sizeof(cp->ltk)) + { + (void)memset(cp->ltk + len, 0, sizeof(cp->ltk) - len); + } + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_START_ENCRYPTION, buf, NULL); +} +#endif /* CONFIG_BT_SMP */ + +#if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_CLASSIC) +uint8_t bt_conn_enc_key_size(const struct bt_conn *conn) +{ + if (!bt_conn_is_le(conn) && !bt_conn_is_br(conn)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return 0; + } + + if (!conn->encrypt) + { + return 0; + } + +#if defined(CONFIG_BT_CLASSIC) + if (bt_conn_is_br(conn)) + { + return conn->br.link_key ? conn->br.link_key->enc_key_size : 0; + } +#endif /* CONFIG_BT_CLASSIC */ + + if (IS_ENABLED(CONFIG_BT_SMP)) + { + return conn->le.keys ? conn->le.keys->enc_size : 0; + } + + return 0; +} + +static void reset_pairing(struct bt_conn *conn) +{ +#if defined(CONFIG_BT_CLASSIC) + if (bt_conn_is_br(conn)) + { + atomic_clear_bit(conn->flags, BT_CONN_BR_PAIRING); + atomic_clear_bit(conn->flags, BT_CONN_BR_PAIRED); + atomic_clear_bit(conn->flags, BT_CONN_BR_PAIRING_INITIATOR); + atomic_clear_bit(conn->flags, BT_CONN_BR_LEGACY_SECURE); + atomic_clear_bit(conn->flags, BT_CONN_BR_GENERAL_BONDING); + } +#endif /* CONFIG_BT_CLASSIC */ + + /* Reset required security level to current operational */ + + conn->required_sec_level = conn->sec_level; +} + +void bt_conn_security_changed(struct bt_conn *conn, + uint8_t hci_err, enum bt_security_err err) +{ + reset_pairing(conn); + bt_l2cap_security_changed(conn, hci_err); + + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->security_changed) + { + callback->security_changed(conn, conn->sec_level, err); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->security_changed) + { + cb->security_changed(conn, conn->sec_level, err); + } + } + +#if defined(CONFIG_BT_KEYS_OVERWRITE_OLDEST) + if (!err && conn->sec_level >= BT_SECURITY_L2) + { + if (bt_conn_is_le(conn)) + { + bt_keys_update_usage(conn->id, bt_conn_get_dst(conn)); + } + +#if defined(CONFIG_BT_CLASSIC) + if (bt_conn_is_br(conn)) + { + bt_keys_link_key_update_usage(&conn->br.dst); + } +#endif /* CONFIG_BT_CLASSIC */ + } +#endif +} + +static int start_security(struct bt_conn *conn) +{ + if (bt_conn_is_br(conn)) + { + return bt_ssp_start_security(conn); + } + + if (IS_ENABLED(CONFIG_BT_SMP)) + { + return bt_smp_start_security(conn); + } + + return -EINVAL; +} + +int bt_conn_set_security(struct bt_conn *conn, bt_security_t sec) +{ + bt_security_t prev_sec_level; + bool force_pair; + bool prev_force_pair; + int err; + + if (!bt_conn_is_le(conn) && !bt_conn_is_br(conn)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + if (conn->state != BT_CONN_CONNECTED) + { + return -ENOTCONN; + } + + force_pair = sec & BT_SECURITY_FORCE_PAIR; + sec &= ~BT_SECURITY_FORCE_PAIR; + + if (IS_ENABLED(CONFIG_BT_SMP_SC_ONLY)) + { + sec = BT_SECURITY_L4; + } + + if (IS_ENABLED(CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY)) + { + sec = BT_SECURITY_L3; + } + + /* nothing to do */ + + if (!force_pair && (conn->sec_level >= sec || + conn->required_sec_level >= sec)) + { + return 0; + } + + prev_sec_level = conn->required_sec_level; + prev_force_pair = atomic_test_bit(conn->flags, BT_CONN_FORCE_PAIR); + + atomic_set_bit_to(conn->flags, BT_CONN_FORCE_PAIR, force_pair); + conn->required_sec_level = sec; + + err = start_security(conn); + + if (err == -EALREADY) + { + /* keep the settings of the request already in flight */ + + conn->required_sec_level = prev_sec_level; + atomic_set_bit_to(conn->flags, BT_CONN_FORCE_PAIR, prev_force_pair); + } + else if (err) + { + /* reset required security level in case of error */ + + conn->required_sec_level = conn->sec_level; + } + + return err; +} + +bt_security_t bt_conn_get_security(const struct bt_conn *conn) +{ + if (!bt_conn_is_le(conn) && !bt_conn_is_br(conn)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return BT_SECURITY_L0; + } + + return conn->sec_level; +} +#else +bt_security_t bt_conn_get_security(const struct bt_conn *conn) +{ + return BT_SECURITY_L1; +} +#endif /* CONFIG_BT_SMP */ + +#if defined(CONFIG_BT_CONN_DYNAMIC_CALLBACKS) + +int bt_conn_cb_register(struct bt_conn_cb *cb) +{ + if (list_find(&conn_cbs, &cb->_node, NULL)) + { + return -EEXIST; + } + + list_add_tail(&conn_cbs, &cb->_node); + + return 0; +} + +int bt_conn_cb_unregister(struct bt_conn_cb *cb) +{ + if (cb == NULL) + { + return -EINVAL; + } + + if (!list_find_and_remove(&conn_cbs, &cb->_node)) + { + return -ENOENT; + } + + return 0; +} + +#endif /* CONFIG_BT_CONN_DYNAMIC_CALLBACKS */ + +bool bt_conn_exists_le(uint8_t id, const bt_addr_le_t *peer) +{ + struct bt_conn *conn = bt_conn_lookup_addr_le(id, peer); + + if (conn) + { + /* Connection object already exists. + * If the connection state is not "disconnected",then the connection + * was created but has not yet been disconnected. If the connection + * state is "disconnected" then the connection still has valid + * references. The last reference of the stack is released after the + * disconnected callback. + */ + + LOG_WRN("Found valid connection (%p) with address %s in %s state ", + conn, + bt_addr_le_str(peer), state2str(conn->state)); + bt_conn_unref(conn); + return true; + } + + return false; +} + +struct bt_conn *bt_conn_add_le(uint8_t id, const bt_addr_le_t *peer) +{ + struct bt_conn *conn = acl_conn_new(); + + if (!conn) + { + return NULL; + } + + conn->id = id; + bt_addr_le_copy(&conn->le.dst, peer); +#if defined(CONFIG_BT_SMP) + conn->sec_level = BT_SECURITY_L1; + conn->required_sec_level = BT_SECURITY_L1; +#endif /* CONFIG_BT_SMP */ + conn->type = BT_CONN_TYPE_LE; + conn->tx_data_pull = l2cap_data_pull; + conn->get_and_clear_cb = acl_get_and_clear_cb; + conn->has_data = acl_has_data; + conn->le.interval_min = BT_GAP_INIT_CONN_INT_MIN; + conn->le.interval_max = BT_GAP_INIT_CONN_INT_MAX; + + return conn; +} + +bool bt_conn_is_peer_addr_le(const struct bt_conn *conn, + uint8_t id, const bt_addr_le_t *peer) +{ + if (id != conn->id) + { + return false; + } + + /* Check against conn dst address as it may be the identity address */ + + if (bt_addr_le_eq(peer, &conn->le.dst)) + { + return true; + } + + /* Check against initial connection address */ + + if (conn->role == BT_HCI_ROLE_CENTRAL) + { + return bt_addr_le_eq(peer, &conn->le.resp_addr); + } + + return bt_addr_le_eq(peer, &conn->le.init_addr); +} + +struct bt_conn *bt_conn_lookup_addr_le(uint8_t id, const bt_addr_le_t *peer) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(acl_conns); i++) + { + struct bt_conn *conn = bt_conn_ref(&acl_conns[i]); + + if (!conn) + { + continue; + } + + if (!bt_conn_is_le(conn)) + { + bt_conn_unref(conn); + continue; + } + + if (!bt_conn_is_peer_addr_le(conn, id, peer)) + { + bt_conn_unref(conn); + continue; + } + + return conn; + } + + return NULL; +} + +struct bt_conn *bt_conn_lookup_state_le(uint8_t id, const bt_addr_le_t *peer, + const bt_conn_state_t state) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(acl_conns); i++) + { + struct bt_conn *conn = bt_conn_ref(&acl_conns[i]); + + if (!conn) + { + continue; + } + + if (!bt_conn_is_le(conn)) + { + bt_conn_unref(conn); + continue; + } + + if (peer && !bt_conn_is_peer_addr_le(conn, id, peer)) + { + bt_conn_unref(conn); + continue; + } + + if (!(conn->state == state && conn->id == id)) + { + bt_conn_unref(conn); + continue; + } + + return conn; + } + + return NULL; +} + +const bt_addr_le_t *bt_conn_get_dst(const struct bt_conn *conn) +{ + if (!bt_conn_is_le(conn)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return NULL; + } + + return &conn->le.dst; +} + +struct bt_conn_tmp_str bt_conn_dst_tmp_str(const struct bt_conn *conn) +{ + struct bt_conn_tmp_str val; + + switch (conn->type) + { +#if defined(CONFIG_BT_CLASSIC) + case BT_CONN_TYPE_BR: + (void)bt_addr_to_str(&conn->br.dst, val.str, sizeof(val.str)); + break; + case BT_CONN_TYPE_SCO: + if (conn->sco.acl != NULL) + { + (void)bt_addr_to_str(&conn->sco.acl->br.dst, + val.str, + sizeof(val.str)); + } + else + { + val.str[0] = '\0'; + } + break; +#endif /* CONFIG_BT_CLASSIC */ + case BT_CONN_TYPE_LE: + (void)bt_addr_le_to_str(&conn->le.dst, val.str, sizeof(val.str)); + break; +#if defined(CONFIG_BT_ISO_UNICAST) + case BT_CONN_TYPE_ISO: + if (conn->iso.acl != NULL) + { + (void)bt_addr_le_to_str(&conn->iso.acl->le.dst, + val.str, + sizeof(val.str)); + } + else + { + val.str[0] = '\0'; + } + break; +#endif /* CONFIG_BT_ISO_UNICAST */ + default: + val.str[0] = '\0'; + break; + } + + return val; +} + +static enum bt_conn_state +conn_internal_to_public_state(bt_conn_state_t state) +{ + switch (state) + { + case BT_CONN_DISCONNECTED: + case BT_CONN_DISCONNECT_COMPLETE: + return BT_CONN_STATE_DISCONNECTED; + case BT_CONN_SCAN_BEFORE_INITIATING: + case BT_CONN_INITIATING_FILTER_LIST: + case BT_CONN_ADV_CONNECTABLE: + case BT_CONN_ADV_DIR_CONNECTABLE: + case BT_CONN_INITIATING: + return BT_CONN_STATE_CONNECTING; + case BT_CONN_CONNECTED: + return BT_CONN_STATE_CONNECTED; + case BT_CONN_DISCONNECTING: + return BT_CONN_STATE_DISCONNECTING; + default: + DEBUGASSERT(false); + return 0; + } +} + +int bt_conn_get_info(const struct bt_conn *conn, struct bt_conn_info *info) +{ + info->type = conn->type; + info->role = conn->role; + info->id = conn->id; + info->state = conn_internal_to_public_state(conn->state); + info->security.flags = 0; + info->security.level = bt_conn_get_security(conn); +#if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_CLASSIC) + info->security.enc_key_size = bt_conn_enc_key_size(conn); +#else + info->security.enc_key_size = 0; +#endif /* CONFIG_BT_SMP || CONFIG_BT_CLASSIC */ + + switch (conn->type) + { + case BT_CONN_TYPE_LE: + info->le.dst = &conn->le.dst; + info->le.src = &bt_dev.id_addr[conn->id]; + if (conn->role == BT_HCI_ROLE_CENTRAL) + { + info->le.local = &conn->le.init_addr; + info->le.remote = &conn->le.resp_addr; + } + else + { + info->le.local = &conn->le.resp_addr; + info->le.remote = &conn->le.init_addr; + } + + info->le.interval_us = conn->le.interval_us; +#if !defined(CONFIG_BT_SHORTER_CONNECTION_INTERVALS) + info->le._interval = + conn->le.interval_us / BT_HCI_LE_INTERVAL_UNIT_US; +#endif + info->le.latency = conn->le.latency; + info->le.timeout = conn->le.timeout; +#if defined(CONFIG_BT_USER_PHY_UPDATE) + info->le.phy = &conn->le.phy; +#endif +#if defined(CONFIG_BT_USER_DATA_LEN_UPDATE) + info->le.data_len = &conn->le.data_len; +#endif +#if defined(CONFIG_BT_SUBRATING) + info->le.subrate = &conn->le.subrate; +#endif + if (conn->le.keys && (conn->le.keys->flags & BT_KEYS_SC)) + { + info->security.flags |= BT_SECURITY_FLAG_SC; + } + + if (conn->le.keys && (conn->le.keys->flags & BT_KEYS_OOB)) + { + info->security.flags |= BT_SECURITY_FLAG_OOB; + } + + return 0; +#if defined(CONFIG_BT_CLASSIC) + case BT_CONN_TYPE_BR: + info->br.dst = &conn->br.dst; + return 0; + case BT_CONN_TYPE_SCO: + info->sco.air_mode = conn->sco.air_mode; + info->sco.link_type = conn->sco.link_type; + return 0; +#endif +#if defined(CONFIG_BT_ISO) + case BT_CONN_TYPE_ISO: +#if defined(CONFIG_BT_ISO_UNICAST) + if ((conn->iso.info.type == BT_ISO_CHAN_TYPE_CENTRAL || + conn->iso.info.type == BT_ISO_CHAN_TYPE_PERIPHERAL) && + conn->iso.acl != NULL) + { + info->le.dst = &conn->iso.acl->le.dst; + info->le.src = &bt_dev.id_addr[conn->iso.acl->id]; + } + else + { + info->le.src = BT_ADDR_LE_NONE; + info->le.dst = BT_ADDR_LE_NONE; + } +#else + info->le.src = BT_ADDR_LE_NONE; + info->le.dst = BT_ADDR_LE_NONE; +#endif /* CONFIG_BT_ISO_UNICAST */ + return 0; +#endif /* CONFIG_BT_ISO */ + default: + break; + } + + return -EINVAL; +} + +bool bt_conn_is_type(const struct bt_conn *conn, enum bt_conn_type type) +{ + if (conn == NULL) + { + return false; + } + + return (conn->type & type) != 0; +} + +int bt_conn_get_remote_info(const struct bt_conn *conn, + struct bt_conn_remote_info *remote_info) +{ + if (!bt_conn_is_le(conn) && !bt_conn_is_br(conn)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + if (!atomic_test_bit(conn->flags, BT_CONN_LE_FEATURES_EXCHANGED) || + (IS_ENABLED(CONFIG_BT_REMOTE_VERSION) && + !atomic_test_bit(conn->flags, BT_CONN_AUTO_VERSION_INFO))) + { + return -EBUSY; + } + + remote_info->type = conn->type; +#if defined(CONFIG_BT_REMOTE_VERSION) + /* The conn->rv values will be just zeroes if the operation failed */ + + remote_info->version = conn->rv.version; + remote_info->manufacturer = conn->rv.manufacturer; + remote_info->subversion = conn->rv.subversion; +#else + remote_info->version = 0; + remote_info->manufacturer = 0; + remote_info->subversion = 0; +#endif + + switch (conn->type) + { + case BT_CONN_TYPE_LE: + remote_info->le.features = conn->le.features; + return 0; +#if defined(CONFIG_BT_CLASSIC) + case BT_CONN_TYPE_BR: + /* TODO: Make sure the HCI commands to read br features and + * extended features has finished. + */ + + return -ENOTSUP; +#endif + default: + return -EINVAL; + } +} + +/* Read Transmit Power Level HCI command */ + +static int bt_conn_get_tx_power_level(struct bt_conn *conn, + uint8_t type, int8_t *tx_power_level) +{ + int err; + struct bt_hci_rp_read_tx_power_level *rp; + struct net_buf *rsp; + struct bt_hci_cp_read_tx_power_level *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->type = type; + cp->handle = sys_cpu_to_le16(conn->handle); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_TX_POWER_LEVEL, buf, &rsp); + if (err) + { + return err; + } + + rp = (void *)rsp->data; + *tx_power_level = rp->tx_power_level; + net_buf_unref(rsp); + + return 0; +} + +#if defined(CONFIG_BT_TRANSMIT_POWER_CONTROL) +void bt_conn_notify_tx_power_report(struct bt_conn *conn, + struct bt_conn_le_tx_power_report report) +{ + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->tx_power_report) + { + callback->tx_power_report(conn, &report); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->tx_power_report) + { + cb->tx_power_report(conn, &report); + } + } +} + +int bt_conn_le_enhanced_get_tx_power_level(struct bt_conn *conn, + struct bt_conn_le_tx_power *tx_power) +{ + int err; + struct bt_hci_rp_le_read_tx_power_level *rp; + struct net_buf *rsp; + struct bt_hci_cp_le_read_tx_power_level *cp; + struct net_buf *buf; + + if (!bt_conn_is_le(conn)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + if (!tx_power->phy) + { + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + cp->phy = tx_power->phy; + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_ENH_READ_TX_POWER_LEVEL, + buf, + &rsp); + if (err) + { + return err; + } + + rp = (void *)rsp->data; + tx_power->phy = rp->phy; + tx_power->current_level = rp->current_tx_power_level; + tx_power->max_level = rp->max_tx_power_level; + net_buf_unref(rsp); + + return 0; +} + +int bt_conn_le_get_remote_tx_power_level(struct bt_conn *conn, + enum bt_conn_le_tx_power_phy phy) +{ + struct bt_hci_cp_le_read_tx_power_level *cp; + struct net_buf *buf; + + if (!bt_conn_is_le(conn)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + if (!phy) + { + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + cp->phy = phy; + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_READ_REMOTE_TX_POWER_LEVEL, buf, + NULL); +} + +int bt_conn_le_set_tx_power_report_enable( + struct bt_conn *conn, bool local_enable, bool remote_enable) +{ + struct bt_hci_cp_le_set_tx_power_report_enable *cp; + struct net_buf *buf; + + if (!bt_conn_is_le(conn)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + cp->local_enable = + local_enable ? + BT_HCI_LE_TX_POWER_REPORT_ENABLE : + BT_HCI_LE_TX_POWER_REPORT_DISABLE; + cp->remote_enable = + remote_enable ? + BT_HCI_LE_TX_POWER_REPORT_ENABLE : + BT_HCI_LE_TX_POWER_REPORT_DISABLE; + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_TX_POWER_REPORT_ENABLE, buf, + NULL); +} +#endif /* CONFIG_BT_TRANSMIT_POWER_CONTROL */ + +int bt_conn_le_get_tx_power_level(struct bt_conn *conn, + struct bt_conn_le_tx_power *tx_power_level) +{ + int err; + + if (!bt_conn_is_le(conn)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + if (tx_power_level->phy != 0) + { + if (IS_ENABLED(CONFIG_BT_TRANSMIT_POWER_CONTROL)) + { + return bt_conn_le_enhanced_get_tx_power_level(conn, + tx_power_level); + } + else + { + return -ENOTSUP; + } + } + + err = bt_conn_get_tx_power_level(conn, BT_TX_POWER_LEVEL_CURRENT, + &tx_power_level->current_level); + if (err) + { + return err; + } + + err = bt_conn_get_tx_power_level(conn, BT_TX_POWER_LEVEL_MAX, + &tx_power_level->max_level); + return err; +} + +#if defined(CONFIG_BT_PATH_LOSS_MONITORING) +void bt_conn_notify_path_loss_threshold_report(struct bt_conn *conn, + struct bt_conn_le_path_loss_threshold_report report) +{ + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->path_loss_threshold_report) + { + callback->path_loss_threshold_report(conn, &report); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->path_loss_threshold_report) + { + cb->path_loss_threshold_report(conn, &report); + } + } +} + +int bt_conn_le_set_path_loss_mon_param(struct bt_conn *conn, + const struct bt_conn_le_path_loss_reporting_param *params) +{ + struct bt_hci_cp_le_set_path_loss_reporting_parameters *cp; + struct net_buf *buf; + + if (!bt_conn_is_le(conn)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + cp->high_threshold = params->high_threshold; + cp->high_hysteresis = params->high_hysteresis; + cp->low_threshold = params->low_threshold; + cp->low_hysteresis = params->low_hysteresis; + cp->min_time_spent = sys_cpu_to_le16(params->min_time_spent); + + return + bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_PATH_LOSS_REPORTING_PARAMETERS, + buf, NULL); +} + +int bt_conn_le_set_path_loss_mon_enable(struct bt_conn *conn, + bool reporting_enable) +{ + struct bt_hci_cp_le_set_path_loss_reporting_enable *cp; + struct net_buf *buf; + + if (!bt_conn_is_le(conn)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + cp->enable = reporting_enable ? BT_HCI_LE_PATH_LOSS_REPORTING_ENABLE + : BT_HCI_LE_PATH_LOSS_REPORTING_DISABLE; + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_PATH_LOSS_REPORTING_ENABLE, + buf, + NULL); +} +#endif /* CONFIG_BT_PATH_LOSS_MONITORING */ + +#if defined(CONFIG_BT_SUBRATING) +void bt_conn_notify_subrate_change(struct bt_conn *conn, + const struct bt_conn_le_subrate_changed params) +{ + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->subrate_changed) + { + callback->subrate_changed(conn, ¶ms); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->subrate_changed) + { + cb->subrate_changed(conn, ¶ms); + } + } +} + +#if defined(CONFIG_BT_SHORTER_CONNECTION_INTERVALS) +void bt_conn_notify_conn_rate_change(struct bt_conn *conn, + uint8_t status, + const struct bt_conn_le_conn_rate_changed *params) +{ + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->conn_rate_changed != NULL) + { + callback->conn_rate_changed(conn, status, params); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->conn_rate_changed != NULL) + { + cb->conn_rate_changed(conn, status, params); + } + } +} +#endif /* CONFIG_BT_SHORTER_CONNECTION_INTERVALS */ + +static bool le_subrate_common_params_valid( + const struct bt_conn_le_subrate_param *param) +{ + /* All limits according to BT Core spec 5.4 [Vol 4, Part E, 7.8.123] */ + + if (param->subrate_min < 0x0001 || param->subrate_min > 0x01f4 || + param->subrate_max < 0x0001 || + param->subrate_max > 0x01f4 || param->subrate_min > param->subrate_max) + { + return false; + } + + if (param->max_latency > 0x01f3 || + param->subrate_max * (param->max_latency + 1) > 500) + { + return false; + } + + if (param->continuation_number > 0x01f3 || + param->continuation_number >= param->subrate_max) + { + return false; + } + + if (param->supervision_timeout < 0x000a || + param->supervision_timeout > 0xc80) + { + return false; + } + + return true; +} + +int bt_conn_le_subrate_set_defaults( + const struct bt_conn_le_subrate_param *params) +{ + struct bt_hci_cp_le_set_default_subrate *cp; + struct net_buf *buf; + + if (!IS_ENABLED(CONFIG_BT_CENTRAL)) + { + return -ENOTSUP; + } + + if (!le_subrate_common_params_valid(params)) + { + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->subrate_min = sys_cpu_to_le16(params->subrate_min); + cp->subrate_max = sys_cpu_to_le16(params->subrate_max); + cp->max_latency = sys_cpu_to_le16(params->max_latency); + cp->continuation_number = sys_cpu_to_le16(params->continuation_number); + cp->supervision_timeout = sys_cpu_to_le16(params->supervision_timeout); + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_DEFAULT_SUBRATE, buf, NULL); +} + +int bt_conn_le_subrate_request(struct bt_conn *conn, + const struct bt_conn_le_subrate_param *params) +{ + struct bt_hci_cp_le_subrate_request *cp; + struct net_buf *buf; + + if (!bt_conn_is_le(conn)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + if (!le_subrate_common_params_valid(params)) + { + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + cp->subrate_min = sys_cpu_to_le16(params->subrate_min); + cp->subrate_max = sys_cpu_to_le16(params->subrate_max); + cp->max_latency = sys_cpu_to_le16(params->max_latency); + cp->continuation_number = sys_cpu_to_le16(params->continuation_number); + cp->supervision_timeout = sys_cpu_to_le16(params->supervision_timeout); + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_SUBRATE_REQUEST, buf, NULL); +} +#endif /* CONFIG_BT_SUBRATING */ + +#if defined(CONFIG_BT_SHORTER_CONNECTION_INTERVALS) +int bt_conn_le_read_min_conn_interval_groups( + struct bt_conn_le_min_conn_interval_info *info) +{ + struct net_buf *rsp; + struct bt_hci_op_le_read_min_supported_conn_interval *rp; + int err; + + if (info == NULL) + { + return -EINVAL; + } + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_READ_MIN_SUPPORTED_CONN_INTERVAL, + NULL, &rsp); + if (err != 0) + { + return err; + } + + rp = (struct bt_hci_op_le_read_min_supported_conn_interval *)rsp->data; + + if (rp->num_groups > BT_CONN_LE_MAX_CONN_INTERVAL_GROUPS) + { + LOG_ERR("Too many groups: %d (max %d)", rp->num_groups, + BT_CONN_LE_MAX_CONN_INTERVAL_GROUPS); + net_buf_unref(rsp); + return -ENOMEM; + } + + info->min_supported_conn_interval_us = + BT_CONN_SCI_INTERVAL_TO_US(rp->min_supported_conn_interval); + info->num_groups = rp->num_groups; + + /* Copy groups up to the number allocated by the application */ + + for (uint8_t i = 0; i < rp->num_groups; i++) + { + info->groups[i].min_125us = sys_le16_to_cpu(rp->groups[i].group_min); + info->groups[i].max_125us = sys_le16_to_cpu(rp->groups[i].group_max); + info->groups[i].stride_125us = + sys_le16_to_cpu(rp->groups[i].group_stride); + } + + net_buf_unref(rsp); + return 0; +} + +int bt_conn_le_read_min_conn_interval(uint16_t *min_interval_us) +{ + int err; + struct net_buf *rsp; + struct bt_hci_op_le_read_min_supported_conn_interval *rp; + + if (min_interval_us == NULL) + { + return -EINVAL; + } + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_READ_MIN_SUPPORTED_CONN_INTERVAL, + NULL, &rsp); + if (err != 0) + { + return err; + } + + rp = (struct bt_hci_op_le_read_min_supported_conn_interval *)rsp->data; + *min_interval_us = + BT_CONN_SCI_INTERVAL_TO_US(rp->min_supported_conn_interval); + + net_buf_unref(rsp); + return 0; +} + +static bool le_conn_rate_common_params_valid( + const struct bt_conn_le_conn_rate_param *param) +{ + /* All limits according to BT Core spec 6.2 [Vol 4, Part E, 7.8.154] */ + + if (!IN_RANGE(param->interval_min_125us, BT_HCI_LE_SCI_INTERVAL_MIN_125US, + BT_HCI_LE_SCI_INTERVAL_MAX_125US) || + !IN_RANGE(param->interval_max_125us, BT_HCI_LE_SCI_INTERVAL_MIN_125US, + BT_HCI_LE_SCI_INTERVAL_MAX_125US) || + param->interval_min_125us > param->interval_max_125us) + { + return false; + } + + if (!IN_RANGE(param->subrate_min, BT_HCI_LE_SUBRATE_FACTOR_MIN, + BT_HCI_LE_SUBRATE_FACTOR_MAX) || + !IN_RANGE(param->subrate_max, BT_HCI_LE_SUBRATE_FACTOR_MIN, + BT_HCI_LE_SUBRATE_FACTOR_MAX) || + param->subrate_min > param->subrate_max) + { + return false; + } + + if (!IN_RANGE(param->max_latency, 0, BT_HCI_LE_PERIPHERAL_LATENCY_MAX) || + param->subrate_max * (param->max_latency + 1) > 500) + { + return false; + } + + if (!IN_RANGE(param->continuation_number, 0, + BT_HCI_LE_CONTINUATION_NUM_MAX) || + param->continuation_number >= param->subrate_max) + { + return false; + } + + if (!IN_RANGE(param->supervision_timeout_10ms, + BT_HCI_LE_SUPERVISION_TIMEOUT_MIN, + BT_HCI_LE_SUPERVISION_TIMEOUT_MAX)) + { + return false; + } + + if (!IN_RANGE(param->min_ce_len_125us, BT_HCI_LE_SCI_CE_LEN_MIN_125US, + BT_HCI_LE_SCI_CE_LEN_MAX_125US) || + !IN_RANGE(param->max_ce_len_125us, BT_HCI_LE_SCI_CE_LEN_MIN_125US, + BT_HCI_LE_SCI_CE_LEN_MAX_125US) || + param->max_ce_len_125us < param->min_ce_len_125us) + { + return false; + } + + return true; +} + +#if defined(CONFIG_BT_CENTRAL) +int bt_conn_le_conn_rate_set_defaults( + const struct bt_conn_le_conn_rate_param *params) +{ + struct bt_hci_op_le_set_default_rate_parameters *cp; + struct net_buf *buf; + + if (params == NULL || !le_conn_rate_common_params_valid(params)) + { + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (buf == NULL) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->conn_interval_min = sys_cpu_to_le16(params->interval_min_125us); + cp->conn_interval_max = sys_cpu_to_le16(params->interval_max_125us); + cp->subrate_min = sys_cpu_to_le16(params->subrate_min); + cp->subrate_max = sys_cpu_to_le16(params->subrate_max); + cp->max_latency = sys_cpu_to_le16(params->max_latency); + cp->continuation_number = sys_cpu_to_le16(params->continuation_number); + cp->supervision_timeout = + sys_cpu_to_le16(params->supervision_timeout_10ms); + cp->min_ce_len = sys_cpu_to_le16(params->min_ce_len_125us); + cp->max_ce_len = sys_cpu_to_le16(params->max_ce_len_125us); + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_DEFAULT_RATE_PARAMETERS, buf, + NULL); +} +#endif /* CONFIG_BT_CENTRAL */ + +int bt_conn_le_conn_rate_request(struct bt_conn *conn, + const struct bt_conn_le_conn_rate_param *params) +{ + struct bt_hci_op_le_connection_rate_request *cp; + struct net_buf *buf; + + if (!bt_conn_is_le(conn)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + if (params == NULL || !le_conn_rate_common_params_valid(params)) + { + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (buf == NULL) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + cp->conn_interval_min = sys_cpu_to_le16(params->interval_min_125us); + cp->conn_interval_max = sys_cpu_to_le16(params->interval_max_125us); + cp->subrate_min = sys_cpu_to_le16(params->subrate_min); + cp->subrate_max = sys_cpu_to_le16(params->subrate_max); + cp->max_latency = sys_cpu_to_le16(params->max_latency); + cp->continuation_number = sys_cpu_to_le16(params->continuation_number); + cp->supervision_timeout = + sys_cpu_to_le16(params->supervision_timeout_10ms); + cp->min_ce_len = sys_cpu_to_le16(params->min_ce_len_125us); + cp->max_ce_len = sys_cpu_to_le16(params->max_ce_len_125us); + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CONNECTION_RATE_REQUEST, buf, + NULL); +} +#endif /* CONFIG_BT_SHORTER_CONNECTION_INTERVALS */ + +#if defined(CONFIG_BT_LE_EXTENDED_FEAT_SET) +void bt_conn_notify_read_all_remote_feat_complete(struct bt_conn *conn, + struct bt_conn_le_read_all_remote_feat_complete *params) +{ + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->read_all_remote_feat_complete != NULL) + { + callback->read_all_remote_feat_complete(conn, params); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->read_all_remote_feat_complete != NULL) + { + cb->read_all_remote_feat_complete(conn, params); + } + } +} + +int bt_conn_le_read_all_remote_features(struct bt_conn *conn, + uint8_t pages_requested) +{ + struct bt_hci_cp_le_read_all_remote_features *cp; + struct net_buf *buf; + + if (!bt_conn_is_le(conn)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + if (pages_requested > BT_HCI_LE_FEATURE_PAGE_MAX) + { + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (buf == NULL) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + cp->pages_requested = pages_requested; + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_READ_ALL_REMOTE_FEATURES, buf, + NULL); +} +#endif /* CONFIG_BT_LE_EXTENDED_FEAT_SET */ + +#if defined(CONFIG_BT_FRAME_SPACE_UPDATE) +void bt_conn_notify_frame_space_update_complete(struct bt_conn *conn, + struct bt_conn_le_frame_space_updated *params) +{ + if (IS_ENABLED(CONFIG_BT_CONN_DYNAMIC_CALLBACKS)) + { + struct bt_conn_cb *callback; + + list_for_every_container(&conn_cbs, callback, _node) + { + if (callback->frame_space_updated != NULL) + { + callback->frame_space_updated(conn, params); + } + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->frame_space_updated != NULL) + { + cb->frame_space_updated(conn, params); + } + } +} + +static bool frame_space_update_param_valid( + const struct bt_conn_le_frame_space_update_param *param) +{ + if (param->frame_space_min > BT_CONN_LE_FRAME_SPACE_MAX || + param->frame_space_max > BT_CONN_LE_FRAME_SPACE_MAX || + param->frame_space_min > param->frame_space_max) + { + return false; + } + + if (param->spacing_types == 0) + { + return false; + } + + if (param->phys == 0) + { + return false; + } + + return true; +} + +int bt_conn_le_frame_space_update(struct bt_conn *conn, + const struct bt_conn_le_frame_space_update_param *param) +{ + struct bt_hci_cp_le_frame_space_update *cp; + struct net_buf *buf; + + if (!bt_conn_is_le(conn)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + if (!frame_space_update_param_valid(param)) + { + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (buf == NULL) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + cp->frame_space_min = sys_cpu_to_le16(param->frame_space_min); + cp->frame_space_max = sys_cpu_to_le16(param->frame_space_max); + cp->spacing_types = sys_cpu_to_le16(param->spacing_types); + cp->phys = param->phys; + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_FRAME_SPACE_UPDATE, buf, NULL); +} +#endif /* CONFIG_BT_FRAME_SPACE_UPDATE */ + +#if defined(CONFIG_BT_CHANNEL_SOUNDING) +void bt_conn_notify_remote_cs_capabilities( + struct bt_conn *conn, uint8_t status, + struct bt_conn_le_cs_capabilities *params) +{ + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->le_cs_read_remote_capabilities_complete) + { + callback->le_cs_read_remote_capabilities_complete(conn, status, + params); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->le_cs_read_remote_capabilities_complete) + { + cb->le_cs_read_remote_capabilities_complete(conn, status, params); + } + } +} + +void bt_conn_notify_remote_cs_fae_table(struct bt_conn *conn, + uint8_t status, + struct bt_conn_le_cs_fae_table *params) +{ + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->le_cs_read_remote_fae_table_complete) + { + callback->le_cs_read_remote_fae_table_complete(conn, status, params); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->le_cs_read_remote_fae_table_complete) + { + cb->le_cs_read_remote_fae_table_complete(conn, status, params); + } + } +} + +void bt_conn_notify_cs_config_created(struct bt_conn *conn, uint8_t status, + struct bt_conn_le_cs_config *params) +{ + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->le_cs_config_complete) + { + callback->le_cs_config_complete(conn, status, params); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->le_cs_config_complete) + { + cb->le_cs_config_complete(conn, status, params); + } + } +} + +void bt_conn_notify_cs_config_removed(struct bt_conn *conn, + uint8_t config_id) +{ + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->le_cs_config_removed) + { + callback->le_cs_config_removed(conn, config_id); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->le_cs_config_removed) + { + cb->le_cs_config_removed(conn, config_id); + } + } +} + +void bt_conn_notify_cs_security_enable_available(struct bt_conn *conn, + uint8_t status) +{ + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->le_cs_security_enable_complete) + { + callback->le_cs_security_enable_complete(conn, status); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->le_cs_security_enable_complete) + { + cb->le_cs_security_enable_complete(conn, status); + } + } +} + +void bt_conn_notify_cs_procedure_enable_available(struct bt_conn *conn, + uint8_t status, + struct bt_conn_le_cs_procedure_enable_complete *params) +{ + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->le_cs_procedure_enable_complete) + { + callback->le_cs_procedure_enable_complete(conn, status, params); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->le_cs_procedure_enable_complete) + { + cb->le_cs_procedure_enable_complete(conn, status, params); + } + } +} + +void bt_conn_notify_cs_subevent_result(struct bt_conn *conn, + struct bt_conn_le_cs_subevent_result *result) +{ + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->le_cs_subevent_data_available) + { + callback->le_cs_subevent_data_available(conn, result); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->le_cs_subevent_data_available) + { + cb->le_cs_subevent_data_available(conn, result); + } + } +} +#endif /* CONFIG_BT_CHANNEL_SOUNDING */ + +int bt_conn_le_param_update(struct bt_conn *conn, + const struct bt_le_conn_param *param) +{ + if (!bt_conn_is_le(conn)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + LOG_DBG("conn %p features 0x%02x params (%d-%d %d %d)", conn, + conn->le.features[0], + param->interval_min, param->interval_max, param->latency, + param->timeout); + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && conn->role == BT_CONN_ROLE_CENTRAL) + { + return send_conn_le_param_update(conn, param); + } + + if (IS_ENABLED(CONFIG_BT_PERIPHERAL)) + { + /* if peripheral conn param update timer expired just send request */ + + if (atomic_test_bit(conn->flags, BT_CONN_PERIPHERAL_PARAM_UPDATE)) + { + return send_conn_le_param_update(conn, param); + } + + /* store new conn params to be used by update timer */ + + conn->le.interval_min = param->interval_min; + conn->le.interval_max = param->interval_max; + conn->le.pending_latency = param->latency; + conn->le.pending_timeout = param->timeout; + atomic_set_bit(conn->flags, BT_CONN_PERIPHERAL_PARAM_SET); + } + + return 0; +} + +#if defined(CONFIG_BT_USER_DATA_LEN_UPDATE) +int bt_conn_le_data_len_update(struct bt_conn *conn, + const struct bt_conn_le_data_len_param *param) +{ + if (!bt_conn_is_le(conn)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + if (conn->le.data_len.tx_max_len == param->tx_max_len && + conn->le.data_len.tx_max_time == param->tx_max_time) + { + return -EALREADY; + } + + return bt_le_set_data_len(conn, param->tx_max_len, param->tx_max_time); +} +#endif /* CONFIG_BT_USER_DATA_LEN_UPDATE */ + +#if defined(CONFIG_BT_USER_PHY_UPDATE) +int bt_conn_le_phy_update(struct bt_conn *conn, + const struct bt_conn_le_phy_param *param) +{ + uint8_t phy_opts; + uint8_t all_phys; + + if (!bt_conn_is_le(conn)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + if ((param->options & BT_CONN_LE_PHY_OPT_CODED_S2) && + (param->options & BT_CONN_LE_PHY_OPT_CODED_S8)) + { + phy_opts = BT_HCI_LE_PHY_CODED_ANY; + } + else if (param->options & BT_CONN_LE_PHY_OPT_CODED_S2) + { + phy_opts = BT_HCI_LE_PHY_CODED_S2; + } + else if (param->options & BT_CONN_LE_PHY_OPT_CODED_S8) + { + phy_opts = BT_HCI_LE_PHY_CODED_S8; + } + else + { + phy_opts = BT_HCI_LE_PHY_CODED_ANY; + } + + all_phys = 0U; + if (param->pref_tx_phy == BT_GAP_LE_PHY_NONE) + { + all_phys |= BT_HCI_LE_PHY_TX_ANY; + } + + if (param->pref_rx_phy == BT_GAP_LE_PHY_NONE) + { + all_phys |= BT_HCI_LE_PHY_RX_ANY; + } + + return bt_le_set_phy(conn, all_phys, param->pref_tx_phy, + param->pref_rx_phy, + phy_opts); +} + +int bt_conn_le_set_default_phy(uint8_t pref_tx_phy, uint8_t pref_rx_phy) +{ + uint8_t all_phys = 0U; + + if (pref_tx_phy == BT_GAP_LE_PHY_NONE) + { + all_phys |= BT_HCI_LE_PHY_TX_ANY; + } + + if (pref_rx_phy == BT_GAP_LE_PHY_NONE) + { + all_phys |= BT_HCI_LE_PHY_RX_ANY; + } + + return bt_le_set_default_phy(all_phys, pref_tx_phy, pref_rx_phy); +} +#endif + +#if defined(CONFIG_BT_CENTRAL) +static void bt_conn_set_param_le(struct bt_conn *conn, + const struct bt_le_conn_param *param) +{ + conn->le.interval_min = param->interval_min; + conn->le.interval_max = param->interval_max; + conn->le.latency = param->latency; + conn->le.timeout = param->timeout; +} + +static void create_param_setup(const struct bt_conn_le_create_param *param) +{ + bt_dev.create_param = *param; + + bt_dev.create_param.timeout = (bt_dev.create_param.timeout != 0) + ? bt_dev.create_param.timeout + : (MSEC_PER_SEC / 10) * CONFIG_BT_CREATE_CONN_TIMEOUT; + + bt_dev.create_param.interval_coded = + (bt_dev.create_param.interval_coded != 0) + ? bt_dev.create_param.interval_coded + : bt_dev.create_param. + interval; + + bt_dev.create_param.window_coded = (bt_dev.create_param.window_coded != 0) + ? bt_dev.create_param.window_coded + : bt_dev.create_param.window; +} + +#if defined(CONFIG_BT_FILTER_ACCEPT_LIST) +int bt_conn_le_create_auto( + const struct bt_conn_le_create_param *create_param, + const struct bt_le_conn_param *param) +{ + struct bt_conn *conn; + int err; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + if (!bt_le_conn_params_valid(param)) + { + return -EINVAL; + } + + conn = bt_conn_lookup_state_le(BT_ID_DEFAULT, BT_ADDR_LE_NONE, + BT_CONN_INITIATING_FILTER_LIST); + if (conn) + { + bt_conn_unref(conn); + return -EALREADY; + } + + /* Scanning either to connect or explicit scan, either case scanner was + * started by application and should not be stopped. + */ + + if (!BT_LE_STATES_SCAN_INIT(bt_dev.le.states) && + atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING)) + { + return -EINVAL; + } + + if (atomic_test_bit(bt_dev.flags, BT_DEV_INITIATING)) + { + return -EINVAL; + } + + if (!bt_id_scan_random_addr_check()) + { + return -EINVAL; + } + + conn = bt_conn_add_le(BT_ID_DEFAULT, BT_ADDR_LE_NONE); + if (!conn) + { + return -ENOMEM; + } + + bt_conn_set_param_le(conn, param); + create_param_setup(create_param); + + if (IS_ENABLED(CONFIG_BT_SMP) && atomic_test_bit(bt_dev.flags, + BT_DEV_ID_PENDING)) + { + bt_id_pending_keys_update(); + } + + atomic_set_bit(conn->flags, BT_CONN_AUTO_CONNECT); + bt_conn_set_state(conn, BT_CONN_INITIATING_FILTER_LIST); + + err = bt_le_create_conn(conn); + if (err) + { + LOG_ERR("Failed to start filtered scan"); + conn->err = 0; + bt_conn_set_state(conn, BT_CONN_DISCONNECTED); + bt_conn_unref(conn); + return err; + } + + /* Since we don't give the application a reference to manage in + * this case, we need to release this reference here. + */ + + bt_conn_unref(conn); + return 0; +} + +int bt_conn_create_auto_stop(void) +{ + struct bt_conn *conn; + int err; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EINVAL; + } + + conn = bt_conn_lookup_state_le(BT_ID_DEFAULT, BT_ADDR_LE_NONE, + BT_CONN_INITIATING_FILTER_LIST); + if (!conn) + { + return -EINVAL; + } + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_INITIATING)) + { + return -EINVAL; + } + + bt_conn_set_state(conn, BT_CONN_DISCONNECTED); + bt_conn_unref(conn); + + err = bt_le_create_conn_cancel(); + if (err) + { + LOG_ERR("Failed to stop initiator"); + return err; + } + + return 0; +} +#endif /* defined(CONFIG_BT_FILTER_ACCEPT_LIST) */ + +static int conn_le_create_common_checks(const bt_addr_le_t *peer, + const struct bt_le_conn_param *conn_param) +{ + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + LOG_DBG("Conn check failed: BT dev not ready."); + return -EAGAIN; + } + + if (!bt_le_conn_params_valid(conn_param)) + { + LOG_DBG("Conn check failed: invalid parameters."); + return -EINVAL; + } + + if (!BT_LE_STATES_SCAN_INIT(bt_dev.le.states) && + bt_le_explicit_scanner_running()) + { + LOG_DBG("Conn check failed: scanner was explicitly requested."); + return -EAGAIN; + } + + if (atomic_test_bit(bt_dev.flags, BT_DEV_INITIATING)) + { + LOG_DBG("Conn check failed: device is already initiating."); + return -EALREADY; + } + + if (!bt_id_scan_random_addr_check()) + { + LOG_DBG("Conn check failed: invalid random address."); + return -EINVAL; + } + + if (bt_conn_exists_le(BT_ID_DEFAULT, peer)) + { + LOG_DBG("Conn check failed: ACL connection already exists."); + return -EINVAL; + } + + return 0; +} + +static struct bt_conn *conn_le_create_helper(const bt_addr_le_t *peer, + const struct bt_le_conn_param *conn_param) +{ + bt_addr_le_t dst; + struct bt_conn *conn; + + if (bt_addr_le_is_resolved(peer)) + { + bt_addr_le_copy_resolved(&dst, peer); + } + else + { + bt_addr_le_copy(&dst, bt_lookup_id_addr(BT_ID_DEFAULT, peer)); + } + + /* Only default identity supported for now */ + + conn = bt_conn_add_le(BT_ID_DEFAULT, &dst); + if (!conn) + { + return NULL; + } + + bt_conn_set_param_le(conn, conn_param); + + return conn; +} + +int bt_conn_le_create(const bt_addr_le_t *peer, + const struct bt_conn_le_create_param *create_param, + const struct bt_le_conn_param *conn_param, + struct bt_conn **ret_conn) +{ + struct bt_conn *conn; + int err; + + if (ret_conn == NULL) + { + return -EINVAL; + } + + if (*ret_conn != NULL) + { + /* This rule helps application developers prevent leaks of connection + * references. If a bt_conn variable is not null, it presumably holds a + * reference and must not be overwritten. To avoid this warning, + * initialize the variables to null, and set them to null when moving + * the reference. + */ + + LOG_WRN("*conn should be unreferenced and initialized to NULL"); + + if (IS_ENABLED(CONFIG_BT_CONN_CHECK_NULL_BEFORE_CREATE)) + { + return -EINVAL; + } + } + + err = conn_le_create_common_checks(peer, conn_param); + if (err) + { + return err; + } + + conn = conn_le_create_helper(peer, conn_param); + if (!conn) + { + return -ENOMEM; + } + + if (BT_LE_STATES_SCAN_INIT(bt_dev.le.states) && + bt_le_explicit_scanner_running() && + !bt_le_explicit_scanner_uses_same_params(create_param)) + { + LOG_WRN("Use same scan and connection create params to obtain best " + "performance"); + } + + create_param_setup(create_param); + + if (IS_ENABLED(CONFIG_BT_SMP) && atomic_test_bit(bt_dev.flags, + BT_DEV_ID_PENDING)) + { + bt_id_pending_keys_update(); + } + +#if defined(CONFIG_BT_SMP) + if (bt_dev.le.rl_entries > bt_dev.le.rl_size) + { + /* Use host-based identity resolving. */ + + bt_conn_set_state(conn, BT_CONN_SCAN_BEFORE_INITIATING); + + err = bt_le_scan_user_add(BT_LE_SCAN_USER_CONN); + if (err) + { + bt_le_scan_user_remove(BT_LE_SCAN_USER_CONN); + bt_conn_set_state(conn, BT_CONN_DISCONNECTED); + bt_conn_unref(conn); + + return err; + } + + *ret_conn = conn; + return 0; + } +#endif + + bt_conn_set_state(conn, BT_CONN_INITIATING); + + err = bt_le_create_conn(conn); + if (err) + { + conn->err = 0; + bt_conn_set_state(conn, BT_CONN_DISCONNECTED); + bt_conn_unref(conn); + + /* Best-effort attempt to inform the scanner that the initiator + * stopped. + */ + + int scan_check_err = bt_le_scan_user_add(BT_LE_SCAN_USER_NONE); + + if (scan_check_err) + { + LOG_WRN("Error while updating the scanner (%d)", scan_check_err); + } + + return err; + } + + *ret_conn = conn; + return 0; +} + +int bt_conn_le_create_synced(const struct bt_le_ext_adv *adv, + const struct bt_conn_le_create_synced_param *synced_param, + const struct bt_le_conn_param *conn_param, + struct bt_conn **ret_conn) +{ + struct bt_conn *conn; + int err; + + if (ret_conn == NULL) + { + return -EINVAL; + } + + if (*ret_conn != NULL) + { + /* This rule helps application developers prevent leaks of connection + * references. If a bt_conn variable is not null, it presumably holds a + * reference and must not be overwritten. To avoid this warning, + * initialize the variables to null, and set them to null when moving + * the reference. + */ + + LOG_WRN("*conn should be unreferenced and initialized to NULL"); + + if (IS_ENABLED(CONFIG_BT_CONN_CHECK_NULL_BEFORE_CREATE)) + { + return -EINVAL; + } + } + + err = conn_le_create_common_checks(synced_param->peer, conn_param); + if (err) + { + return err; + } + + if (!atomic_test_bit(adv->flags, BT_PER_ADV_ENABLED)) + { + return -EINVAL; + } + + if (!BT_FEAT_LE_PAWR_ADVERTISER(bt_dev.le.features)) + { + return -ENOTSUP; + } + + if (synced_param->subevent >= BT_HCI_PAWR_SUBEVENT_MAX) + { + return -EINVAL; + } + + conn = conn_le_create_helper(synced_param->peer, conn_param); + if (!conn) + { + return -ENOMEM; + } + + /* The connection creation timeout is not really useful for PAwR. + * The controller will give a result for the connection attempt within a + * periodic interval. We do not know the periodic interval used, so disable + * the timeout. + */ + + bt_dev.create_param.timeout = 0; + bt_conn_set_state(conn, BT_CONN_INITIATING); + + err = bt_le_create_conn_synced(conn, adv, synced_param->subevent); + if (err) + { + conn->err = 0; + bt_conn_set_state(conn, BT_CONN_DISCONNECTED); + bt_conn_unref(conn); + + return err; + } + + *ret_conn = conn; + return 0; +} +#endif /* CONFIG_BT_CENTRAL */ + +int bt_conn_le_conn_update(struct bt_conn *conn, + const struct bt_le_conn_param *param) +{ + struct hci_cp_le_conn_update *conn_update; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + conn_update = net_buf_add(buf, sizeof(*conn_update)); + (void)memset(conn_update, 0, sizeof(*conn_update)); + conn_update->handle = sys_cpu_to_le16(conn->handle); + conn_update->conn_interval_min = sys_cpu_to_le16(param->interval_min); + conn_update->conn_interval_max = sys_cpu_to_le16(param->interval_max); + conn_update->conn_latency = sys_cpu_to_le16(param->latency); + conn_update->supervision_timeout = sys_cpu_to_le16(param->timeout); + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CONN_UPDATE, buf, NULL); +} + +#if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_CLASSIC) +int bt_conn_auth_cb_register(const struct bt_conn_auth_cb *cb) +{ + if (!cb) + { + bt_auth = NULL; + return 0; + } + + if (bt_auth) + { + return -EALREADY; + } + + /* The cancel callback must always be provided if the app provides + * interactive callbacks. + */ + + if (!cb->cancel && (cb->passkey_display || cb->passkey_entry || + cb->passkey_confirm || +#if defined(CONFIG_BT_CLASSIC) + cb->pincode_entry || +#endif + cb->pairing_confirm)) + { + return -EINVAL; + } + + bt_auth = cb; + return 0; +} + +#if defined(CONFIG_BT_SMP) +int bt_conn_auth_cb_overlay(struct bt_conn *conn, + const struct bt_conn_auth_cb *cb) +{ + if (!bt_conn_is_le(conn) && !bt_conn_is_br(conn)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + /* The cancel callback must always be provided if the app provides + * interactive callbacks. + */ + + if (cb && !cb->cancel && + (cb->passkey_display || cb->passkey_entry || cb->passkey_confirm || + cb->pairing_confirm)) + { + return -EINVAL; + } + + if (bt_conn_is_le(conn)) + { + return bt_smp_auth_cb_overlay(conn, cb); + } + + return -ENOTSUP; +} +#endif + +int bt_conn_auth_info_cb_register(struct bt_conn_auth_info_cb *cb) +{ + if (cb == NULL) + { + return -EINVAL; + } + + if (list_find(&bt_auth_info_cbs, &cb->node, NULL)) + { + return -EALREADY; + } + + list_add_tail(&bt_auth_info_cbs, &cb->node); + + return 0; +} + +int bt_conn_auth_info_cb_unregister(struct bt_conn_auth_info_cb *cb) +{ + if (cb == NULL) + { + return -EINVAL; + } + + if (!list_find_and_remove(&bt_auth_info_cbs, &cb->node)) + { + return -EALREADY; + } + + return 0; +} + +int bt_conn_auth_passkey_entry(struct bt_conn *conn, unsigned int passkey) +{ + if (!bt_conn_is_le(conn) && !bt_conn_is_br(conn)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_SMP) && bt_conn_is_le(conn)) + { + return bt_smp_auth_passkey_entry(conn, passkey); + } + + if (bt_conn_is_br(conn)) + { + if (!bt_auth) + { + return -EINVAL; + } + + return bt_ssp_auth_passkey_entry(conn, passkey); + } + + return -EINVAL; +} + +#if defined(CONFIG_BT_PASSKEY_KEYPRESS) +int bt_conn_auth_keypress_notify(struct bt_conn *conn, + enum bt_conn_auth_keypress type) +{ + if (!bt_conn_is_le(conn)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_SMP)) + { + return bt_smp_auth_keypress_notify(conn, type); + } + + LOG_ERR("Not implemented for conn type %d", conn->type); + return -EINVAL; +} +#endif + +int bt_conn_auth_passkey_confirm(struct bt_conn *conn) +{ + if (!bt_conn_is_le(conn) && !bt_conn_is_br(conn)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_SMP) && bt_conn_is_le(conn)) + { + return bt_smp_auth_passkey_confirm(conn); + } + + if (bt_conn_is_br(conn)) + { + if (!bt_auth) + { + return -EINVAL; + } + + return bt_ssp_auth_passkey_confirm(conn); + } + + return -EINVAL; +} + +int bt_conn_auth_cancel(struct bt_conn *conn) +{ + if (!bt_conn_is_le(conn) && !bt_conn_is_br(conn)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_SMP) && bt_conn_is_le(conn)) + { + return bt_smp_auth_cancel(conn); + } + + if (bt_conn_is_br(conn)) + { + if (!bt_auth) + { + return -EINVAL; + } + + return bt_ssp_auth_cancel(conn); + } + + return -EINVAL; +} + +int bt_conn_auth_pairing_confirm(struct bt_conn *conn) +{ + if (!bt_conn_is_le(conn) && !bt_conn_is_br(conn)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_SMP) && bt_conn_is_le(conn)) + { + return bt_smp_auth_pairing_confirm(conn); + } + + if (bt_conn_is_br(conn)) + { + if (!bt_auth) + { + return -EINVAL; + } + + return bt_ssp_auth_pairing_confirm(conn); + } + + return -EINVAL; +} +#endif /* CONFIG_BT_SMP || CONFIG_BT_CLASSIC */ + +struct bt_conn *bt_conn_lookup_index(uint8_t index) +{ + if (index >= ARRAY_SIZE(acl_conns)) + { + return NULL; + } + + return bt_conn_ref(&acl_conns[index]); +} + +int bt_conn_init(void) +{ + int err; + int i; + + k_fifo_init(&free_tx); + for (i = 0; i < ARRAY_SIZE(conn_tx); i++) + { + k_fifo_put(&free_tx, &conn_tx[i]); + } + + bt_att_init(); + + if (IS_ENABLED(CONFIG_BT_GATT_GAP_SVC_VALIDATE)) + { + err = gatt_gap_svc_validate(); + if (err != 0) + { + LOG_DBG("GATT GAP service validation failed (err %d)", err); + return err; + } + } + + err = bt_smp_init(); + if (err) + { + return err; + } + + bt_l2cap_init(); + + /* Initialize background scan */ + + if (IS_ENABLED(CONFIG_BT_CENTRAL)) + { + for (i = 0; i < ARRAY_SIZE(acl_conns); i++) + { + struct bt_conn *conn = bt_conn_ref(&acl_conns[i]); + + if (!conn) + { + continue; + } + +#if !defined(CONFIG_BT_FILTER_ACCEPT_LIST) + if (atomic_test_bit(conn->flags, BT_CONN_AUTO_CONNECT)) + { + /* Only the default identity is supported */ + + conn->id = BT_ID_DEFAULT; + bt_conn_set_state(conn, BT_CONN_SCAN_BEFORE_INITIATING); + } +#endif /* !defined(CONFIG_BT_FILTER_ACCEPT_LIST) */ + + bt_conn_unref(conn); + } + } + + return 0; +} + +#if defined(CONFIG_BT_DF_CONNECTION_CTE_RX) +void bt_hci_le_df_connection_iq_report_common(uint8_t event, + struct net_buf *buf) +{ + struct bt_df_conn_iq_samples_report iq_report; + struct bt_conn *conn; + int err; + + if (event == BT_HCI_EVT_LE_CONNECTION_IQ_REPORT) + { + err = hci_df_prepare_connection_iq_report(buf, &iq_report, &conn); + if (err) + { + LOG_ERR("Prepare CTE conn IQ report failed %d", err); + return; + } + } + else if (IS_ENABLED(CONFIG_BT_DF_VS_CONN_IQ_REPORT_16_BITS_IQ_SAMPLES) && + event == BT_HCI_EVT_VS_LE_CONNECTION_IQ_REPORT) + { + err = hci_df_vs_prepare_connection_iq_report(buf, &iq_report, &conn); + if (err) + { + LOG_ERR("Prepare CTE conn IQ report failed %d", err); + return; + } + } + else + { + LOG_ERR("Unhandled VS connection IQ report"); + return; + } + + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->cte_report_cb) + { + callback->cte_report_cb(conn, &iq_report); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->cte_report_cb) + { + cb->cte_report_cb(conn, &iq_report); + } + } + + bt_conn_unref(conn); +} + +void bt_hci_le_df_connection_iq_report(struct net_buf *buf) +{ + bt_hci_le_df_connection_iq_report_common( + BT_HCI_EVT_LE_CONNECTION_IQ_REPORT, buf); +} + +#if defined(CONFIG_BT_DF_VS_CONN_IQ_REPORT_16_BITS_IQ_SAMPLES) +void bt_hci_le_vs_df_connection_iq_report(struct net_buf *buf) +{ + bt_hci_le_df_connection_iq_report_common( + BT_HCI_EVT_VS_LE_CONNECTION_IQ_REPORT, + buf); +} +#endif /* CONFIG_BT_DF_VS_CONN_IQ_REPORT_16_BITS_IQ_SAMPLES */ +#endif /* CONFIG_BT_DF_CONNECTION_CTE_RX */ + +#if defined(CONFIG_BT_DF_CONNECTION_CTE_REQ) +void bt_hci_le_df_cte_req_failed(struct net_buf *buf) +{ + struct bt_df_conn_iq_samples_report iq_report; + struct bt_conn *conn; + int err; + + err = hci_df_prepare_conn_cte_req_failed(buf, &iq_report, &conn); + if (err) + { + LOG_ERR("Prepare CTE REQ failed IQ report failed %d", err); + return; + } + + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->cte_report_cb) + { + callback->cte_report_cb(conn, &iq_report); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->cte_report_cb) + { + cb->cte_report_cb(conn, &iq_report); + } + } + + bt_conn_unref(conn); +} +#endif /* CONFIG_BT_DF_CONNECTION_CTE_REQ */ + +#if defined(CONFIG_BT_POWER_MODE_CONTROL) +int bt_conn_br_enter_sniff_mode( + struct bt_conn *conn, uint16_t min_interval, uint16_t max_interval, + uint16_t attempt, uint16_t timeout) +{ + struct bt_hci_cp_sniff_mode *cp; + struct net_buf *buf; + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_BR)) + { + return -EINVAL; + } + + if (conn->state != BT_CONN_CONNECTED) + { + return -ENOTCONN; + } + + if (conn->br.mode == BT_SNIFF_MODE) + { + return -EBUSY; + } + + /* Check if the parameters are valid */ + + if (min_interval < 0x0006 || min_interval > 0x0540 || + max_interval < 0x0006 || + max_interval > 0x0540 || min_interval > max_interval || attempt == 0 || + attempt > 0x01f3 || + timeout > 0x0028) + { + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + cp->max_interval = sys_cpu_to_le16(max_interval); + cp->min_interval = sys_cpu_to_le16(min_interval); + cp->attempt = sys_cpu_to_le16(attempt); + cp->timeout = sys_cpu_to_le16(timeout); + + return bt_hci_cmd_send_sync(BT_HCI_OP_SNIFF_MODE, buf, NULL); +} + +int bt_conn_br_exit_sniff_mode(struct bt_conn *conn) +{ + struct bt_hci_cp_exit_sniff_mode *cp; + struct net_buf *buf; + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_BR)) + { + return -EINVAL; + } + + if (conn->state != BT_CONN_CONNECTED) + { + return -ENOTCONN; + } + + if (conn->br.mode == BT_ACTIVE_MODE) + { + return -EBUSY; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + + return bt_hci_cmd_send_sync(BT_HCI_OP_EXIT_SNIFF_MODE, buf, NULL); +} + +int bt_conn_br_set_sniff_subrating( + struct bt_conn *conn, uint16_t max_latency, uint16_t min_remote_timeout, + uint16_t min_local_timeout) +{ + struct bt_hci_cp_sniff_subrating *cp; + struct net_buf *buf; + + if (!bt_conn_is_type(conn, BT_CONN_TYPE_BR)) + { + return -EINVAL; + } + + if (conn->state != BT_CONN_CONNECTED) + { + return -ENOTCONN; + } + + /* Core Spec Vol 2, Part E, 7.2.14 */ + + if (max_latency < 0x0002 || max_latency > 0xfffe || + min_remote_timeout > 0xfffe || + min_local_timeout > 0xfffe) + { + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + cp->max_latency = sys_cpu_to_le16(max_latency); + cp->min_remote_timeout = sys_cpu_to_le16(min_remote_timeout); + cp->min_local_timeout = sys_cpu_to_le16(min_local_timeout); + + return bt_hci_cmd_send_sync(BT_HCI_OP_SNIFF_SUBRATING, buf, NULL); +} + +void bt_conn_notify_mode_changed(struct bt_conn *conn, + uint8_t mode, uint16_t interval) +{ + BT_CONN_CB_DYNAMIC_FOREACH(callback) + { + if (callback->br.mode_changed) + { + callback->br.mode_changed(conn, mode, interval); + } + } + + STRUCT_SECTION_FOREACH(bt_conn_cb, cb) + { + if (cb->br.mode_changed) + { + cb->br.mode_changed(conn, mode, interval); + } + } +} + +#endif /* CONFIG_BT_POWER_MODE_CONTROL */ +#endif /* CONFIG_BT_CONN */ + +#if defined(CONFIG_BT_CONN_TX_NOTIFY_WQ) +static int bt_conn_tx_workq_init(void) +{ + const struct k_work_queue_config cfg = + { + .name = "BT CONN TX WQ", + .no_yield = false, + .essential = false, + }; + + k_work_queue_init(&conn_tx_workq); + k_work_queue_start(&conn_tx_workq, conn_tx_workq_thread_stack, + sizeof(conn_tx_workq_thread_stack), + K_PRIO_COOP(CONFIG_BT_CONN_TX_NOTIFY_WQ_PRIO), &cfg); + + return 0; +} + +SYS_INIT(bt_conn_tx_workq_init, + POST_KERNEL, + CONFIG_BT_CONN_TX_NOTIFY_WQ_INIT_PRIORITY); +#endif /* CONFIG_BT_CONN_TX_NOTIFY_WQ */ diff --git a/libs/zblue/host/conn_internal.h b/libs/zblue/host/conn_internal.h new file mode 100644 index 0000000000000..d1bfeb1cb7443 --- /dev/null +++ b/libs/zblue/host/conn_internal.h @@ -0,0 +1,754 @@ +/**************************************************************************** + * libs/zblue/host/conn_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Internal APIs for Bluetooth connection handling. + */ + +/* Copyright (c) 2015 Intel Corporation + * Copyright (c) 2021 Nordic Semiconductor ASA Copyright (c) 2025 Xiaomi + * Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef enum __packed +{ + BT_CONN_DISCONNECTED, /* Disconnected, conn is completely down */ + BT_CONN_DISCONNECT_COMPLETE, /* Received disconn comp event, transition to DISCONNECTED */ + + BT_CONN_INITIATING, /* Central connection establishment */ + + /** Central scans for a device preceding establishing a connection to it. + * + * This can happen when: + * - The application has explicitly configured the stack to connect to the + * device, + * but the controller resolving list is too small. The stack therefore + * first scans to be able to retrieve the currently used (private) + * address, resolving the address in the host if needed. + * - The stack uses this connection context for automatic connection + * establishment + * without the use of filter accept list. Instead of immediately starting + * the initiator, it first starts scanning. This allows the application to + * start scanning while automatic connection establishment in ongoing. It + * also allows the stack to use host based privacy for cases where this is + * needed. + */ + + BT_CONN_SCAN_BEFORE_INITIATING, + + /** Central initiates a connection to a device in the filter accept list. + * + * For this type of connection establishment, the controller's initiator is + * started immediately. That is, it is assumed that the controller + * resolving list holds all entries that are part of the filter accept list + * if private addresses are used. + */ + + BT_CONN_INITIATING_FILTER_LIST, + + BT_CONN_ADV_CONNECTABLE, /* Peripheral connectable advertising */ + BT_CONN_ADV_DIR_CONNECTABLE, /* Peripheral directed advertising */ + BT_CONN_CONNECTED, /* Peripheral or Central connected */ + BT_CONN_DISCONNECTING, /* Peripheral or Central issued disconnection command */ +} bt_conn_state_t; + +/* bt_conn flags: the flags defined here represent connection parameters */ + +enum +{ + /** The connection context is used for automatic connection establishment + * + * That is, with @ref bt_conn_le_create_auto(). This flag is set even after + * the connection has been established so that the connection can be + * reestablished once disconnected. The connection establishment may be + * performed with or without the filter accept list. + */ + + BT_CONN_AUTO_CONNECT, + BT_CONN_BR_LEGACY_SECURE, /* 16 digits legacy PIN tracker */ + BT_CONN_BR_BONDABLE, /* BR connection is bondable */ + BT_CONN_USER, /* user I/O when pairing */ + BT_CONN_BR_PAIRING, /* BR connection in pairing context */ + BT_CONN_BR_PAIRED, /* BR connection pairing is done */ + BT_CONN_BR_NOBOND, /* SSP no bond pairing tracker */ + BT_CONN_BR_GENERAL_BONDING, /* BR general bonding */ + BT_CONN_BR_PAIRING_INITIATOR, /* local host starts authentication */ + BT_CONN_CLEANUP, /* Disconnected, pending cleanup */ + BT_CONN_AUTO_INIT_PROCEDURES_DONE, /* Auto-initiated procedures have run */ + BT_CONN_PERIPHERAL_PARAM_UPDATE, /* If periph param update timer fired */ + BT_CONN_PERIPHERAL_PARAM_AUTO_UPDATE, /* If periph param auto update on timer fired */ + BT_CONN_PERIPHERAL_PARAM_SET, /* If periph param were set from app */ + BT_CONN_PERIPHERAL_PARAM_L2CAP, /* If should force L2CAP for CPUP */ + BT_CONN_FORCE_PAIR, /* Pairing even with existing keys. */ +#if defined(CONFIG_BT_GATT_CLIENT) + BT_CONN_ATT_MTU_EXCHANGED, /* If ATT MTU has been exchanged. */ +#endif /* CONFIG_BT_GATT_CLIENT */ + + BT_CONN_LE_FEATURES_EXCHANGED, /* bt_conn.le.features is valid */ + BT_CONN_AUTO_VERSION_INFO, /* Auto-initiated LE version done */ + + BT_CONN_CTE_RX_ENABLED, /* CTE receive and sampling is enabled */ + BT_CONN_CTE_RX_PARAMS_SET, /* CTE parameters are set */ + BT_CONN_CTE_TX_PARAMS_SET, /* CTE transmission parameters are set */ + BT_CONN_CTE_REQ_ENABLED, /* CTE request procedure is enabled */ + BT_CONN_CTE_RSP_ENABLED, /* CTE response procedure is enabled */ + + /* Total number of flags - must be at the end of the enum */ + + BT_CONN_NUM_FLAGS, +}; + +struct bt_conn_le +{ + bt_addr_le_t dst; + + bt_addr_le_t init_addr; + bt_addr_le_t resp_addr; + + uint32_t interval_us; + uint16_t interval_min; + uint16_t interval_max; + + uint16_t latency; + uint16_t timeout; + uint16_t pending_latency; + uint16_t pending_timeout; + +#if defined(CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS) + uint8_t conn_param_retry_countdown; +#endif + + /** @brief Remote LE features + * + * Available after `atomic_test_bit(conn->flags, + * BT_CONN_LE_FEATURES_EXCHANGED)`. Signaled by + * bt_conn_cb.remote_info_available(). + */ + + uint8_t features[8]; + + struct bt_keys *keys; + +#if defined(CONFIG_BT_USER_PHY_UPDATE) + struct bt_conn_le_phy_info phy; +#endif + +#if defined(CONFIG_BT_USER_DATA_LEN_UPDATE) + struct bt_conn_le_data_len_info data_len; +#endif + +#if defined(CONFIG_BT_SUBRATING) + struct bt_conn_le_subrating_info subrate; +#endif +}; + +/* For now reserve space for 2 pages of LMP remote features */ +#define LMP_MAX_PAGES 2 + +struct bt_conn_br +{ + bt_addr_t dst; + uint8_t remote_io_capa; + uint8_t remote_auth; + uint8_t local_auth; + uint8_t pairing_method; + + /* remote LMP features pages per 8 bytes each */ + + uint8_t features[LMP_MAX_PAGES][8]; + + struct bt_keys_link_key *link_key; + +#if defined(CONFIG_BT_POWER_MODE_CONTROL) + /* For power mode */ + + uint8_t mode; +#endif /* CONFIG_BT_POWER_MODE_CONTROL */ +}; + +struct bt_conn_sco +{ + /* Reference to ACL Connection */ + + struct bt_conn *acl; + + /* Reference to the struct bt_sco_chan */ + + struct bt_sco_chan *chan; + + uint16_t pkt_type; + uint8_t dev_class[3]; + uint8_t link_type; + + /* Reference to BT_HCI_CODING_FORMAT_* */ + + uint8_t air_mode; +}; + +struct bt_conn_iso +{ +#if defined(CONFIG_BT_ISO_UNICAST) + /* Reference to ACL Connection */ + + struct bt_conn *acl; +#endif /* CONFIG_BT_ISO_UNICAST */ + + /* Reference to the struct bt_iso_chan */ + + struct bt_iso_chan *chan; + +#if defined(CONFIG_BT_ISO_RX) + /* Expected SDU size of current parsing data `conn->rx` */ + + uint16_t sdu_len; +#endif /* CONFIG_BT_ISO_RX */ + + /** Stored information about the ISO stream */ + + struct bt_iso_info info; + +#if defined(CONFIG_BT_ISO_TX) + /** Queue from which conn will pull data */ + + struct k_fifo txq; +#endif /* CONFIG_BT_ISO_TX */ +}; + +typedef void (*bt_conn_tx_cb_t)(struct bt_conn *conn, + void *user_data, int err); + +struct bt_conn_tx +{ + struct list_node node; + + bt_conn_tx_cb_t cb; + void *user_data; +}; + +struct bt_conn_rx +{ + /* Index into the bt_conn storage array */ + + uint8_t index; + + /** Connection handle */ + + uint16_t handle; +}; + +struct bt_conn +{ + uint16_t handle; + enum bt_conn_type type; + uint8_t role; + + atomic_define(flags, BT_CONN_NUM_FLAGS); + + /* Which local identity address this connection uses */ + + uint8_t id; + +#if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_CLASSIC) + bt_security_t sec_level; + bt_security_t required_sec_level; + uint8_t encrypt; +#endif /* CONFIG_BT_SMP || CONFIG_BT_CLASSIC */ + +#if defined(CONFIG_BT_DF_CONNECTION_CTE_RX) + /* @brief Bitfield with allowed CTE types. + * + * Allowed values are defined by @ref bt_df_cte_type, except + * BT_DF_CTE_TYPE_NONE. + */ + + uint8_t cte_types; +#endif /* CONFIG_BT_DF_CONNECTION_CTE_RX */ + + /* Connection error or reason for disconnect */ + + uint8_t err; + + bt_conn_state_t state; + struct net_buf *rx; + + /* Pending TX that are awaiting the NCP event. len(tx_pending) == in_ll */ + + struct list_node tx_pending; + + /* Completed TX for which we need to call the callback */ + + struct list_node tx_complete; +#if defined(CONFIG_BT_CONN_TX) + struct k_work tx_complete_work; +#endif /* CONFIG_BT_CONN_TX */ + + /* Active L2CAP channels */ + + struct list_node channels; + + /* Delayed work deferred tasks: + * - Peripheral delayed connection update. + * - Initiator connect create cancel. + * - Connection cleanup. + */ + + struct k_work_delayable deferred_work; + + union + { + struct bt_conn_le le; +#if defined(CONFIG_BT_CLASSIC) + struct bt_conn_br br; + struct bt_conn_sco sco; +#endif +#if defined(CONFIG_BT_ISO) + struct bt_conn_iso iso; +#endif + }; + +#if defined(CONFIG_BT_REMOTE_VERSION) + struct bt_conn_rv + { + uint8_t version; + uint16_t manufacturer; + uint16_t subversion; + } rv; +#endif + + /* Callback into the higher-layers (L2CAP / ISO) to return a buffer for + * sending `amount` of bytes to HCI. Will only be called when the state is + * connected. The higher-layer is responsible for purging the remaining + * buffers on disconnect. + * + * Scheduling from which channel to pull (e.g. for L2CAP) is done at the + * upper layer's discretion. + * + * Details about the returned net_buf when it is not NULL: + * - If the net_buf->len <= *length, then the net_buf has been removed + * from the tx_queue of the connection and the caller is now the owner of + * the only reference to the net_buf. The caller now has to invoke + * get_and_clear_cb to get the callback and user-data for the net_buf and + * is responsible for invoking the callback eventually after the + * Controller gives a Number of Completed Packets Event for this PDU or + * when the connection is disconnected, in which case the callback must be + * invoked with the error code -ESHUTDOWN. + * - Otherwise, the net_buf is still on the tx_queue of the connection, + * and the callback has incremented the reference count to account for it + * having a reference still. + * - The caller must consume *length bytes from the net_buf before + * calling this function again. + */ + + struct net_buf * (*tx_data_pull)(struct bt_conn *conn, + size_t amount, size_t *length); + + /* Get (and clears for ACL conns) callback and user-data for `buf`. */ + + void (*get_and_clear_cb)(struct bt_conn *conn, + struct net_buf *buf, + bt_conn_tx_cb_t *cb, + void **ud); + + /* Return true if upper layer has data to send over HCI */ + + bool (*has_data)(struct bt_conn *conn); + + /* For ACL: List of data-ready L2 channels. Used by TX processor for + * pulling HCI fragments. Channels are only ever removed from this list + * when a whole PDU (ie all its frags) have been sent. + */ + + struct list_node l2cap_data_ready; + + /* Node for putting this connection in a data-ready mode for the bt_dev. + * This will be used by the TX processor to then fetch HCI frags from it. + */ + + struct list_node _conn_ready; + + /* Holds the number of packets that have been sent to the controller but + * not yet ACKd (by receiving an Number of Completed Packets). This + * variable can be used for deriving a QoS or waterlevel scheme in order to + * maximize throughput/latency. It's an optimization so we don't chase + * `tx_pending` all the time. + */ + + atomic_t in_ll; + + /* Next buffer should be an ACL/ISO HCI fragment */ + + bool next_is_frag; + + /* Must be at the end so that everything else in the structure can be + * memset to zero without affecting the ref. + */ + + atomic_t ref; +}; + +/* Holds the callback and a user-data field for the upper layer. This + * callback shall be called when the buffer is ACK'd by the controller (by a + * Num Complete Packets event) or if the connection dies. + * + * Flow control in the spec be crazy, look it up. LL is allowed to choose + * between sending NCP events always or not at all on disconnect. + * + * We pack the struct to make sure it fits in the net_buf user_data field. + */ + +struct closure +{ + void *cb; + void *data; +} __packed; + +#if defined(CONFIG_BT_CONN_TX_USER_DATA_SIZE) +BUILD_ASSERT(sizeof(struct closure) <= CONFIG_BT_CONN_TX_USER_DATA_SIZE); +#endif + +static inline void make_closure(void *storage, void *cb, void *data) +{ + ((struct closure *)storage)->cb = cb; + ((struct closure *)storage)->data = data; +} + +static inline void *closure_cb(void *storage) +{ + return ((struct closure *)storage)->cb; +} + +static inline void *closure_data(void *storage) +{ + return ((struct closure *)storage)->data; +} + +#if defined(CONFIG_BT_CLASSIC) +static inline bool bt_conn_is_br(const struct bt_conn *conn) +{ + return conn->type == BT_CONN_TYPE_BR; +} +#else +#define bt_conn_is_br(conn) (false) +#endif + +static inline bool bt_conn_is_le(const struct bt_conn *conn) +{ + return conn->type == BT_CONN_TYPE_LE; +} + +#if defined(CONFIG_BT_ISO) +static inline bool bt_conn_is_iso(const struct bt_conn *conn) +{ + return conn->type == BT_CONN_TYPE_ISO; +} +#else +#define bt_conn_is_iso(conn) (false) +#endif + +#if defined(CONFIG_BT_CLASSIC) +static inline bool bt_conn_is_sco(const struct bt_conn *conn) +{ + return conn->type == BT_CONN_TYPE_SCO; +} +#else +#define bt_conn_is_sco(conn) (false) +#endif + +void bt_conn_tx_notify(struct bt_conn *conn, bool wait_for_completion); + +void bt_conn_reset_rx_state(struct bt_conn *conn); + +/* Process incoming data for a connection */ + +void bt_conn_recv(struct bt_conn *conn, struct net_buf *buf, uint8_t flags); + +/* Send data over a connection + * + * Buffer ownership is transferred to stack in case of success. + * + * Calling this from RX thread is assumed to never fail so the return can be + * ignored. + */ + +int bt_conn_send_cb(struct bt_conn *conn, + struct net_buf *buf, bt_conn_tx_cb_t cb, void + *user_data); + +/* Thin wrapper over `bt_conn_send_cb` + * + * Used to set the TS_Flag bit in `buf`'s metadata. + * + * Return values & buf ownership same as parent. + */ + +int bt_conn_send_iso_cb(struct bt_conn *conn, + struct net_buf *buf, bt_conn_tx_cb_t cb, bool + has_ts); + +/* Check if a connection object with the peer already exists */ + +bool bt_conn_exists_le(uint8_t id, const bt_addr_le_t *peer); + +/* Add a new LE connection */ + +struct bt_conn *bt_conn_add_le(uint8_t id, const bt_addr_le_t *peer); + +int bt_conn_iso_init(void); + +/* Cleanup ISO references */ + +void bt_iso_cleanup_acl(struct bt_conn *iso_conn); + +void bt_iso_reset(void); + +/* Add a new BR/EDR connection */ + +struct bt_conn *bt_conn_add_br(const bt_addr_t *peer); + +/* Add a new SCO connection */ + +struct bt_conn *bt_conn_add_sco(const bt_addr_t *peer, int link_type); + +/* Cleanup SCO ACL reference */ + +void bt_sco_cleanup_acl(struct bt_conn *sco_conn); + +/* Cleanup SCO references */ + +void bt_sco_cleanup(struct bt_conn *sco_conn); + +/* Look up an existing sco connection by BT address */ + +struct bt_conn *bt_conn_lookup_addr_sco(const bt_addr_t *peer); + +void bt_conn_disconnect_all(uint8_t id); + +/* Allocate new connection object */ + +struct bt_conn *bt_conn_new(struct bt_conn *conns, size_t size); + +/* Look up an existing connection */ + +struct bt_conn *bt_conn_lookup_handle(uint16_t handle, + enum bt_conn_type type); + +static inline bool bt_conn_is_handle_valid(struct bt_conn *conn) +{ + switch (conn->state) + { + case BT_CONN_CONNECTED: + case BT_CONN_DISCONNECTING: + case BT_CONN_DISCONNECT_COMPLETE: + return true; + case BT_CONN_INITIATING: + + /* ISO connection handle assigned at connect state */ + + if (bt_conn_is_iso(conn)) + { + return true; + } + __fallthrough; + default: + return false; + } +} + +/* Check if the connection is with the given peer. */ + +bool bt_conn_is_peer_addr_le(const struct bt_conn *conn, + uint8_t id, const bt_addr_le_t *peer); + +/* Helpers for identifying & looking up connections based on the index to + * the connection list. This is useful for O(1) lookups, but can't be used + * e.g. as the handle since that's assigned to us by the controller. + */ +#define BT_CONN_INDEX_INVALID 0xff +struct bt_conn *bt_conn_lookup_index(uint8_t index); + +/* Look up a connection state. For BT_ADDR_LE_ANY, returns the first + * connection with the specific state + */ + +struct bt_conn *bt_conn_lookup_state_le(uint8_t id, const bt_addr_le_t *peer, + const bt_conn_state_t state); + +/* Set connection object in certain state and perform action related to state + */ + +void bt_conn_set_state(struct bt_conn *conn, bt_conn_state_t state); + +void bt_conn_connected(struct bt_conn *conn); + +void bt_conn_br_role_changed(struct bt_conn *conn, uint8_t status); + +void bt_conn_br_packet_type_changed(struct bt_conn *conn, + uint8_t status, uint16_t packet_type); + +int bt_conn_le_conn_update(struct bt_conn *conn, + const struct bt_le_conn_param *param); + +void bt_conn_notify_remote_info(struct bt_conn *conn); + +void bt_conn_notify_le_param_updated(struct bt_conn *conn); + +void bt_conn_notify_le_param_rejected(struct bt_conn *conn, uint8_t hci_err); + +void bt_conn_notify_le_data_len_updated(struct bt_conn *conn); + +void bt_conn_notify_le_phy_updated(struct bt_conn *conn); + +bool bt_conn_le_param_req(struct bt_conn *conn, + struct bt_le_conn_param *param); + +void bt_conn_notify_tx_power_report(struct bt_conn *conn, + struct bt_conn_le_tx_power_report + report); + +void bt_conn_notify_path_loss_threshold_report(struct bt_conn *conn, + struct bt_conn_le_path_loss_threshold_report report); + +void bt_conn_notify_subrate_change(struct bt_conn *conn, + struct bt_conn_le_subrate_changed params); + +void bt_conn_notify_conn_rate_change(struct bt_conn *conn, + uint8_t status, + const struct bt_conn_le_conn_rate_changed *params); + +void bt_conn_notify_read_all_remote_feat_complete(struct bt_conn *conn, + struct bt_conn_le_read_all_remote_feat_complete *params); + +void bt_conn_notify_frame_space_update_complete(struct bt_conn *conn, + struct bt_conn_le_frame_space_updated *params); + +void bt_conn_notify_remote_cs_capabilities( + struct bt_conn *conn, uint8_t status, + struct bt_conn_le_cs_capabilities *params); + +void bt_conn_notify_remote_cs_fae_table(struct bt_conn *conn, + uint8_t status, + struct bt_conn_le_cs_fae_table *params); + +void bt_conn_notify_cs_config_created(struct bt_conn *conn, uint8_t status, + struct bt_conn_le_cs_config *params); + +void bt_conn_notify_cs_config_removed(struct bt_conn *conn, + uint8_t config_id); + +void bt_conn_notify_cs_subevent_result(struct bt_conn *conn, + struct bt_conn_le_cs_subevent_result *result); + +void bt_conn_notify_cs_security_enable_available(struct bt_conn *conn, + uint8_t status); + +void bt_conn_notify_cs_procedure_enable_available(struct bt_conn *conn, + uint8_t status, + struct bt_conn_le_cs_procedure_enable_complete *params); + +/* If role specific LTK is present */ + +bool bt_conn_ltk_present(const struct bt_conn *conn); + +/* rand and ediv should be in BT order */ + +int bt_conn_le_start_encryption( + struct bt_conn *conn, uint8_t rand[8], uint8_t ediv[2], const uint8_t *ltk, + size_t len); + +/* Notify higher layers that RPA was resolved */ + +void bt_conn_identity_resolved(struct bt_conn *conn); + +/* Notify higher layers that connection security changed */ + +void bt_conn_security_changed(struct bt_conn *conn, + uint8_t hci_err, enum bt_security_err err); + +#if defined(CONFIG_BT_POWER_MODE_CONTROL) +/* Notify higher layers that connection sniff mode changed */ + +void bt_conn_notify_mode_changed(struct bt_conn *conn, + uint8_t mode, uint16_t interval); +#endif /* CONFIG_BT_POWER_MODE_CONTROL */ + +/* Prepare a PDU to be sent over a connection */ +#if defined(CONFIG_NET_BUF_LOG) +struct net_buf *bt_conn_create_pdu_timeout_debug( + struct net_buf_pool *pool, size_t reserve, clock_t timeout, + const char *func, int line); +#define bt_conn_create_pdu_timeout(_pool, _reserve, _timeout) \ + bt_conn_create_pdu_timeout_debug(_pool, _reserve, _timeout, __func__, __LINE__) + +#define bt_conn_create_pdu(_pool, _reserve) \ + bt_conn_create_pdu_timeout_debug(_pool, _reserve, ((clock_t)-1), __func__, __LINE__) +#else +struct net_buf *bt_conn_create_pdu_timeout( + struct net_buf_pool *pool, size_t reserve, clock_t timeout); + +#define bt_conn_create_pdu(_pool, _reserve) \ + bt_conn_create_pdu_timeout(_pool, _reserve, ((clock_t)-1)) +#endif + +/* Prepare a PDU to be sent over a connection */ +#if defined(CONFIG_NET_BUF_LOG) +struct net_buf *bt_conn_create_frag_timeout_debug( + size_t reserve, clock_t timeout, const char *func, int line); + +#define bt_conn_create_frag_timeout(_reserve, _timeout) \ + bt_conn_create_frag_timeout_debug(_reserve, _timeout, __func__, __LINE__) + +#define bt_conn_create_frag(_reserve) \ + bt_conn_create_frag_timeout_debug(_reserve, ((clock_t)-1), __func__, __LINE__) +#else +struct net_buf *bt_conn_create_frag_timeout(size_t reserve, clock_t timeout); + +#define bt_conn_create_frag(_reserve) bt_conn_create_frag_timeout(_reserve, ((clock_t)-1)) +#endif + +/* Initialize connection management */ + +int bt_conn_init(void); + +/* Reset states of connections and set state to BT_CONN_DISCONNECTED. */ + +void bt_conn_cleanup_all(void); + +/* Selects based on connection type right semaphore for ACL packets */ + +sem_t *bt_conn_get_pkts(struct bt_conn *conn); + +void bt_conn_tx_processor(void); + +/* To be called by upper layers when they want to send something. + * Functions just like an IRQ. + * + * Note: This fn will take and hold a reference to `conn` until the IRQ for + * that conn is serviced. For the current implementation, that means: + * - ref the conn when putting on an "conn-ready" slist + * - unref the conn when popping the conn from the slist + */ + +void bt_conn_data_ready(struct bt_conn *conn); diff --git a/libs/zblue/host/crypto.h b/libs/zblue/host/crypto.h new file mode 100644 index 0000000000000..3bfdd48db0d73 --- /dev/null +++ b/libs/zblue/host/crypto.h @@ -0,0 +1,14 @@ +/**************************************************************************** + * libs/zblue/host/crypto.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2016-2017 Nordic Semiconductor ASA Copyright (c) 2015-2016 + * Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +int bt_crypto_init(void); diff --git a/libs/zblue/host/crypto_psa.c b/libs/zblue/host/crypto_psa.c new file mode 100644 index 0000000000000..79b302857aadc --- /dev/null +++ b/libs/zblue/host/crypto_psa.c @@ -0,0 +1,191 @@ +/**************************************************************************** + * libs/zblue/host/crypto_psa.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Nordic Semiconductor ASA + * Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/bt_str.h" +#include "hci_core.h" + +#define LOG_LEVEL CONFIG_BT_HCI_CORE_LOG_LEVEL +LOG_MODULE_REGISTER(bt_host_crypto); + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bt_crypto_init(void) +{ + psa_status_t status = psa_crypto_init(); + + if (status != PSA_SUCCESS) + { + LOG_ERR("psa_crypto_init() failed %d", status); + return -EIO; + } + + return 0; +} + +#if defined(CONFIG_BT_HOST_CRYPTO_PRNG) && defined(CONFIG_CRYPTO_MBEDTLS) +int bt_rand(void *buf, size_t len) +{ + psa_status_t status = psa_generate_random(buf, len); + + if (status == PSA_SUCCESS) + { + return 0; + } + + LOG_ERR("psa_generate_random() failed %d", status); + return -EIO; +} +#else /* !CONFIG_BT_HOST_CRYPTO_PRNG || !CONFIG_CRYPTO_MBEDTLS */ +int bt_rand(void *buf, size_t len) +{ + if (buf == NULL || len == 0) + { + return -EINVAL; + } + + return bt_hci_le_rand(buf, len); +} +#endif /* CONFIG_BT_HOST_CRYPTO_PRNG && CONFIG_CRYPTO_MBEDTLS */ + +int bt_encrypt_le(const uint8_t key[16], + const uint8_t plaintext[16], uint8_t enc_data[16]) +{ + psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT; + psa_key_id_t key_id = MBEDTLS_SVC_KEY_ID_INIT; + psa_status_t status; + psa_status_t destroy_status; + size_t out_len; + uint8_t tmp[16]; + + if (key == NULL || plaintext == NULL || enc_data == NULL) + { + return -EINVAL; + } + + LOG_DBG("key %s", bt_hex(key, 16)); + LOG_DBG("plaintext %s", bt_hex(plaintext, 16)); + + sys_memcpy_swap(tmp, key, 16); + + psa_set_key_type(&attr, PSA_KEY_TYPE_AES); + psa_set_key_bits(&attr, 128); + psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_ENCRYPT); + psa_set_key_algorithm(&attr, PSA_ALG_ECB_NO_PADDING); + status = psa_import_key(&attr, tmp, 16, &key_id); + if (status != PSA_SUCCESS) + { + LOG_ERR("Failed to import AES key %d", status); + return -EINVAL; + } + + sys_memcpy_swap(tmp, plaintext, 16); + + status = psa_cipher_encrypt(key_id, PSA_ALG_ECB_NO_PADDING, tmp, 16, + enc_data, 16, &out_len); + if (status != PSA_SUCCESS) + { + LOG_ERR("AES encryption failed %d", status); + } + + destroy_status = psa_destroy_key(key_id); + if (destroy_status != PSA_SUCCESS) + { + LOG_ERR("Failed to destroy AES key %d", destroy_status); + } + + if ((status != PSA_SUCCESS) || (destroy_status != PSA_SUCCESS)) + { + return -EIO; + } + + sys_mem_swap(enc_data, 16); + + LOG_DBG("enc_data %s", bt_hex(enc_data, 16)); + + return 0; +} + +int bt_encrypt_be(const uint8_t key[16], + const uint8_t plaintext[16], uint8_t enc_data[16]) +{ + psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT; + psa_key_id_t key_id = MBEDTLS_SVC_KEY_ID_INIT; + psa_status_t status; + psa_status_t destroy_status; + size_t out_len; + + if (key == NULL || plaintext == NULL || enc_data == NULL) + { + return -EINVAL; + } + + LOG_DBG("key %s", bt_hex(key, 16)); + LOG_DBG("plaintext %s", bt_hex(plaintext, 16)); + + psa_set_key_type(&attr, PSA_KEY_TYPE_AES); + psa_set_key_bits(&attr, 128); + psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_ENCRYPT); + psa_set_key_algorithm(&attr, PSA_ALG_ECB_NO_PADDING); + status = psa_import_key(&attr, key, 16, &key_id); + if (status != PSA_SUCCESS) + { + LOG_ERR("Failed to import AES key %d", status); + return -EINVAL; + } + + status = + psa_cipher_encrypt(key_id, PSA_ALG_ECB_NO_PADDING, plaintext, 16, + enc_data, 16, &out_len); + if (status != PSA_SUCCESS) + { + LOG_ERR("AES encryption failed %d", status); + } + + destroy_status = psa_destroy_key(key_id); + if (destroy_status != PSA_SUCCESS) + { + LOG_ERR("Failed to destroy AES key %d", destroy_status); + } + + if ((status != PSA_SUCCESS) || (destroy_status != PSA_SUCCESS)) + { + return -EIO; + } + + LOG_DBG("enc_data %s", bt_hex(enc_data, 16)); + + return 0; +} diff --git a/libs/zblue/host/cs.c b/libs/zblue/host/cs.c new file mode 100644 index 0000000000000..173c98f18aba3 --- /dev/null +++ b/libs/zblue/host/cs.c @@ -0,0 +1,2355 @@ +/**************************************************************************** + * libs/zblue/host/cs.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* cs.c - Bluetooth Channel Sounding handling */ + +/* Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "conn_internal.h" + +#define LOG_LEVEL CONFIG_BT_HCI_CORE_LOG_LEVEL +LOG_MODULE_REGISTER(bt_cs); + +#if defined(CONFIG_BT_CHANNEL_SOUNDING_TEST) +static struct bt_le_cs_test_cb cs_test_callbacks; +#endif + +#define A1 (0) +#define A2 (1) +#define A3 (2) +#define A4 (3) + +struct reassembly_buf_meta_data +{ + uint16_t conn_handle; +}; + +NET_BUF_POOL_FIXED_DEFINE(reassembly_buf_pool, + CONFIG_BT_CHANNEL_SOUNDING_REASSEMBLY_BUFFER_CNT, + + CONFIG_BT_CHANNEL_SOUNDING_REASSEMBLY_BUFFER_SIZE, + sizeof(struct reassembly_buf_meta_data), NULL); + +static struct list_node reassembly_bufs = + LIST_INITIAL_VALUE(reassembly_bufs); + +struct bt_conn_le_cs_subevent_result reassembled_result; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/** @brief Allocates new reassembly buffer identified by the connection + * handle + * + * @param conn_handle Connection handle + * @return struct net_buf* Reassembly buffer, NULL if allocation fails + */ + +static struct net_buf *alloc_reassembly_buf(uint16_t conn_handle) +{ + struct net_buf *buf = net_buf_alloc(&reassembly_buf_pool, 0); + + if (!buf) + { + LOG_ERR("Failed to allocate new reassembly buffer"); + return NULL; + } + + struct reassembly_buf_meta_data *buf_meta_data = + (struct reassembly_buf_meta_data *)buf->user_data; + + buf_meta_data->conn_handle = conn_handle; + net_buf_slist_put(&reassembly_bufs, buf); + + LOG_DBG("Allocated new reassembly buffer for conn handle %d", conn_handle); + return buf; +} + +/** @brief Frees a reassembly buffer + * + * @note Takes the ownership of the pointer and sets it to NULL + * + * @param buf Double pointer to reassembly buffer + */ + +static void free_reassembly_buf(struct net_buf **buf) +{ + if (!buf) + { + LOG_ERR("NULL double pointer was passed when attempting to free " + "reassembly buffer"); + return; + } + + if (!(*buf)) + { + LOG_WRN("Attempted double free on reassembly buffer"); + return; + } + + struct reassembly_buf_meta_data *buf_meta_data = + (struct reassembly_buf_meta_data *)((*buf)->user_data); + + LOG_DBG("De-allocating reassembly buffer for conn handle %d", + buf_meta_data->conn_handle); + if (!list_find_and_remove(&reassembly_bufs, &(*buf)->node)) + { + LOG_WRN("The buffer was not in the list"); + } + + net_buf_drop(buf); +} + +/** @brief Gets the reassembly buffer identified by the connection handle + * + * @param conn_handle Connection handle + * @param allocate Allocates a new reassembly buffer if it's not allocated + * already + * @return struct net_buf* Reassembly buffer, NULL if it doesn't exist or + * failed when allocating new + */ + +static struct net_buf *get_reassembly_buf(uint16_t conn_handle, + bool allocate) +{ + struct list_node *node; + + list_for_every(&reassembly_bufs, node) + { + struct net_buf *buf = CONTAINER_OF(node, struct net_buf, node); + struct reassembly_buf_meta_data *buf_meta_data = + (struct reassembly_buf_meta_data *)(buf->user_data); + + if (buf_meta_data->conn_handle == conn_handle) + { + return buf; + } + } + + return allocate ? alloc_reassembly_buf(conn_handle) : NULL; +} + +/** @brief Adds step data to a reassembly buffer + * + * @param reassembly_buf Reassembly buffer + * @param data Step data + * @param data_len Step data length + * @return true if successful, false if there is insufficient space + */ + +static bool add_reassembly_data( + struct net_buf *reassembly_buf, const uint8_t *data, uint16_t data_len) +{ + if (data_len > net_buf_tailroom(reassembly_buf)) + { + LOG_ERR("Not enough reassembly buffer space for subevent result"); + return false; + } + + net_buf_add_mem(reassembly_buf, data, data_len); + return true; +} + +/** @brief Initializes a reassembly buffer from partial step data + * + * @note Upon first call, this function also registers the disconnection + * callback to ensure any dangling reassembly buffer is freed + * + * @param conn_handle Connection handle + * @param steps Step data + * @param step_data_len Step data length + * @return struct net_buf* Pointer to reassembly buffer, NULL if fails to + * allocate or insert data + */ + +static struct net_buf *start_reassembly( + uint16_t conn_handle, const uint8_t *steps, uint16_t step_data_len) +{ + struct net_buf *reassembly_buf = get_reassembly_buf(conn_handle, true); + + if (!reassembly_buf) + { + LOG_ERR("No buffer allocated for the result reassembly"); + return NULL; + } + + if (reassembly_buf->len) + { + LOG_WRN("Over-written incomplete CS subevent results"); + } + + net_buf_reset(reassembly_buf); + + bool success = add_reassembly_data(reassembly_buf, steps, step_data_len); + + return success ? reassembly_buf : NULL; +} + +/** @brief Adds more step data to reassembly buffer identified by the + * connection handle + * + * @param conn_handle Connection handle + * @param steps Step data + * @param step_data_len Step data length + * @return struct net_buf* Pointer to reassembly buffer, NULL if fails to + * insert data + */ + +static struct net_buf *continue_reassembly( + uint16_t conn_handle, const uint8_t *steps, uint16_t step_data_len) +{ + struct net_buf *reassembly_buf = get_reassembly_buf(conn_handle, false); + + if (!reassembly_buf) + { + LOG_ERR("No reassembly buffer was allocated for this CS procedure, " + "possibly due to " + "an out-of-order subevent result continue event"); + return NULL; + } + + if (!reassembly_buf->len) + { + LOG_WRN("Discarded out-of-order partial CS subevent results"); + return NULL; + } + + if (!step_data_len) + { + return reassembly_buf; + } + + bool success = add_reassembly_data(reassembly_buf, steps, step_data_len); + + return success ? reassembly_buf : NULL; +} + +/** + * @brief Disconnect callback to clear any dangling reassembly buffer + * + * @param conn Connection + * @param reason Reason + */ + +static void clear_on_disconnect(struct bt_conn *conn, uint8_t reason) +{ + struct net_buf *buf = get_reassembly_buf(conn->handle, false); + + if (buf) + { + free_reassembly_buf(&buf); + } +} + +BT_CONN_CB_DEFINE(cs_conn_callbacks) = +{ + .disconnected = clear_on_disconnect, +}; + +/** @brief Invokes user callback for new subevent results + * + * @param conn Connection context, NULL for CS Test subevent results + * @param p_result Pointer to subevent results + */ + +static void invoke_subevent_result_callback(struct bt_conn *conn, + struct bt_conn_le_cs_subevent_result *p_result) +{ +#if defined(CONFIG_BT_CHANNEL_SOUNDING_TEST) + if (!conn) + { + cs_test_callbacks.le_cs_test_subevent_data_available(p_result); + } + else +#endif /* CONFIG_BT_CHANNEL_SOUNDING_TEST */ + { + bt_conn_notify_cs_subevent_result(conn, p_result); + } +} + +/** @brief Resets reassembly results + * + */ + +static void reset_reassembly_results(void) +{ + memset(&reassembled_result, 0, + sizeof(struct bt_conn_le_cs_subevent_result)); +} + +/** @brief Converts PCT to a pair of int16_t + * + */ + +struct bt_le_cs_iq_sample bt_le_cs_parse_pct(const uint8_t pct[3]) +{ + uint32_t pct_u32 = sys_get_le24(pct); + + /* Extract I and Q. */ + + uint16_t i_u16 = pct_u32 & BT_HCI_LE_CS_PCT_I_MASK; + uint16_t q_u16 = (pct_u32 & BT_HCI_LE_CS_PCT_Q_MASK) >> 12; + + /* Convert from 12-bit 2's complement to int16_t */ + + int16_t i = (i_u16 ^ BIT(11)) - BIT(11); + int16_t q = (q_u16 ^ BIT(11)) - BIT(11); + + return (struct bt_le_cs_iq_sample) + { + .i = i, .q = q + }; +} + +void bt_le_cs_set_valid_chmap_bits(uint8_t channel_map[10]) +{ + memset(channel_map, 0xff, 10); + + /** Channels n = 0, 1, 23, 24, 25, 77, and 78 are not allowed and shall be + * set to zero. Channel 79 is reserved for future use and shall be set to + * zero. + */ + + BT_LE_CS_CHANNEL_BIT_SET_VAL(channel_map, 0, 0); + BT_LE_CS_CHANNEL_BIT_SET_VAL(channel_map, 1, 0); + BT_LE_CS_CHANNEL_BIT_SET_VAL(channel_map, 23, 0); + BT_LE_CS_CHANNEL_BIT_SET_VAL(channel_map, 24, 0); + BT_LE_CS_CHANNEL_BIT_SET_VAL(channel_map, 25, 0); + BT_LE_CS_CHANNEL_BIT_SET_VAL(channel_map, 77, 0); + BT_LE_CS_CHANNEL_BIT_SET_VAL(channel_map, 78, 0); + BT_LE_CS_CHANNEL_BIT_SET_VAL(channel_map, 79, 0); +} + +int bt_le_cs_read_remote_supported_capabilities(struct bt_conn *conn) +{ + struct bt_hci_cp_le_read_remote_supported_capabilities *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + + return bt_hci_cmd_send_sync( + BT_HCI_OP_LE_CS_READ_REMOTE_SUPPORTED_CAPABILITIES, + buf, NULL); +} + +void bt_hci_le_cs_read_remote_supported_capabilities_complete( + struct net_buf *buf) +{ + struct bt_conn *conn; + struct bt_conn_le_cs_capabilities remote_cs_capabilities; + struct bt_hci_evt_le_cs_read_remote_supported_capabilities_complete *evt; + + if (buf->len < sizeof(*evt)) + { + LOG_ERR("Unexpected end of buffer"); + return; + } + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + if (evt->status) + { + LOG_WRN("Read Remote Supported Capabilities failed (status 0x%02X)", + evt->status); + } + + conn = bt_conn_lookup_handle(sys_le16_to_cpu(evt->conn_handle), + BT_CONN_TYPE_LE); + if (!conn) + { + LOG_ERR("Could not lookup connection handle when reading remote CS " + "capabilities"); + return; + } + + if (evt->status == BT_HCI_ERR_SUCCESS) + { + remote_cs_capabilities.num_config_supported = + evt->num_config_supported; + remote_cs_capabilities.max_consecutive_procedures_supported = + sys_le16_to_cpu(evt->max_consecutive_procedures_supported); + remote_cs_capabilities.num_antennas_supported = + evt->num_antennas_supported; + remote_cs_capabilities.max_antenna_paths_supported = + evt->max_antenna_paths_supported; + + remote_cs_capabilities.initiator_supported = + evt->roles_supported & BT_HCI_LE_CS_INITIATOR_ROLE_MASK; + remote_cs_capabilities.reflector_supported = + evt->roles_supported & BT_HCI_LE_CS_REFLECTOR_ROLE_MASK; + remote_cs_capabilities.mode_3_supported = + evt->modes_supported & + BT_HCI_LE_CS_MODES_SUPPORTED_MODE_3_MASK; + + remote_cs_capabilities.rtt_aa_only_n = evt->rtt_aa_only_n; + remote_cs_capabilities.rtt_sounding_n = evt->rtt_sounding_n; + remote_cs_capabilities.rtt_random_payload_n = + evt->rtt_random_payload_n; + + if (evt->rtt_aa_only_n) + { + if (evt->rtt_capability & BT_HCI_LE_CS_RTT_AA_ONLY_N_10NS_MASK) + { + remote_cs_capabilities.rtt_aa_only_precision = + BT_CONN_LE_CS_RTT_AA_ONLY_10NS; + } + else + { + remote_cs_capabilities.rtt_aa_only_precision = + BT_CONN_LE_CS_RTT_AA_ONLY_150NS; + } + } + else + { + remote_cs_capabilities.rtt_aa_only_precision = + BT_CONN_LE_CS_RTT_AA_ONLY_NOT_SUPP; + } + + if (evt->rtt_sounding_n) + { + if (evt->rtt_capability & BT_HCI_LE_CS_RTT_SOUNDING_N_10NS_MASK) + { + remote_cs_capabilities.rtt_sounding_precision = + BT_CONN_LE_CS_RTT_SOUNDING_10NS; + } + else + { + remote_cs_capabilities.rtt_sounding_precision = + BT_CONN_LE_CS_RTT_SOUNDING_150NS; + } + } + else + { + remote_cs_capabilities.rtt_sounding_precision = + BT_CONN_LE_CS_RTT_SOUNDING_NOT_SUPP; + } + + if (evt->rtt_random_payload_n) + { + if (evt->rtt_capability & + BT_HCI_LE_CS_RTT_RANDOM_PAYLOAD_N_10NS_MASK) + { + remote_cs_capabilities.rtt_random_payload_precision = + BT_CONN_LE_CS_RTT_RANDOM_PAYLOAD_10NS; + } + else + { + remote_cs_capabilities.rtt_random_payload_precision = + BT_CONN_LE_CS_RTT_RANDOM_PAYLOAD_150NS; + } + } + else + { + remote_cs_capabilities.rtt_random_payload_precision = + BT_CONN_LE_CS_RTT_RANDOM_PAYLOAD_NOT_SUPP; + } + + remote_cs_capabilities.phase_based_nadm_sounding_supported = + sys_le16_to_cpu(evt->nadm_sounding_capability) & + BT_HCI_LE_CS_NADM_SOUNDING_CAPABILITY_PHASE_BASED_MASK; + + remote_cs_capabilities.phase_based_nadm_random_supported = + sys_le16_to_cpu(evt->nadm_random_capability) & + BT_HCI_LE_CS_NADM_RANDOM_CAPABILITY_PHASE_BASED_MASK; + + remote_cs_capabilities.cs_sync_2m_phy_supported = + evt->cs_sync_phys_supported & BT_HCI_LE_CS_SYNC_PHYS_2M_MASK; + + remote_cs_capabilities.cs_sync_2m_2bt_phy_supported = + evt->cs_sync_phys_supported & + BT_HCI_LE_CS_SYNC_PHYS_2M_2BT_MASK; + + remote_cs_capabilities.cs_without_fae_supported = + sys_le16_to_cpu(evt->subfeatures_supported) & + BT_HCI_LE_CS_SUBFEATURE_NO_TX_FAE_MASK; + + remote_cs_capabilities.chsel_alg_3c_supported = + sys_le16_to_cpu(evt->subfeatures_supported) & + BT_HCI_LE_CS_SUBFEATURE_CHSEL_ALG_3C_MASK; + + remote_cs_capabilities.pbr_from_rtt_sounding_seq_supported = + sys_le16_to_cpu(evt->subfeatures_supported) & + BT_HCI_LE_CS_SUBFEATURE_PBR_FROM_RTT_SOUNDING_SEQ_MASK; + + remote_cs_capabilities.t_ip1_times_supported = + sys_le16_to_cpu(evt->t_ip1_times_supported); + remote_cs_capabilities.t_ip2_times_supported = + sys_le16_to_cpu(evt->t_ip2_times_supported); + remote_cs_capabilities.t_fcs_times_supported = + sys_le16_to_cpu(evt->t_fcs_times_supported); + remote_cs_capabilities.t_pm_times_supported = + sys_le16_to_cpu(evt->t_pm_times_supported); + + remote_cs_capabilities.t_sw_time = evt->t_sw_time_supported; + remote_cs_capabilities.tx_snr_capability = evt->tx_snr_capability; + + remote_cs_capabilities.t_ip2_ipt_times_supported = 0; + remote_cs_capabilities.t_sw_ipt_time_supported = 0; + + bt_conn_notify_remote_cs_capabilities(conn, BT_HCI_ERR_SUCCESS, + &remote_cs_capabilities); + } + else + { + bt_conn_notify_remote_cs_capabilities(conn, evt->status, NULL); + } + + bt_conn_unref(conn); +} + +void bt_hci_le_cs_read_remote_supported_capabilities_complete_v2( + struct net_buf *buf) +{ + struct bt_conn *conn; + struct bt_conn_le_cs_capabilities remote_cs_capabilities; + struct bt_hci_evt_le_cs_read_remote_supported_capabilities_complete_v2 + *evt; + + if (buf->len < sizeof(*evt)) + { + LOG_ERR("Unexpected end of buffer"); + return; + } + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + if (evt->status) + { + LOG_WRN("Read Remote Supported Capabilities failed (status 0x%02X)", + evt->status); + } + + conn = bt_conn_lookup_handle(sys_le16_to_cpu(evt->conn_handle), + BT_CONN_TYPE_LE); + if (!conn) + { + LOG_ERR("Could not lookup connection handle when reading remote CS " + "capabilities"); + return; + } + + if (evt->status != BT_HCI_ERR_SUCCESS) + { + bt_conn_notify_remote_cs_capabilities(conn, evt->status, NULL); + goto out; + } + + remote_cs_capabilities.num_config_supported = evt->num_config_supported; + remote_cs_capabilities.max_consecutive_procedures_supported = + sys_le16_to_cpu(evt->max_consecutive_procedures_supported); + remote_cs_capabilities.num_antennas_supported = + evt->num_antennas_supported; + remote_cs_capabilities.max_antenna_paths_supported = + evt->max_antenna_paths_supported; + + remote_cs_capabilities.initiator_supported = + evt->roles_supported & BT_HCI_LE_CS_INITIATOR_ROLE_MASK; + remote_cs_capabilities.reflector_supported = + evt->roles_supported & BT_HCI_LE_CS_REFLECTOR_ROLE_MASK; + remote_cs_capabilities.mode_3_supported = + evt->modes_supported & BT_HCI_LE_CS_MODES_SUPPORTED_MODE_3_MASK; + + remote_cs_capabilities.rtt_aa_only_n = evt->rtt_aa_only_n; + remote_cs_capabilities.rtt_sounding_n = evt->rtt_sounding_n; + remote_cs_capabilities.rtt_random_payload_n = evt->rtt_random_payload_n; + + if (evt->rtt_aa_only_n) + { + if (evt->rtt_capability & BT_HCI_LE_CS_RTT_AA_ONLY_N_10NS_MASK) + { + remote_cs_capabilities.rtt_aa_only_precision = + BT_CONN_LE_CS_RTT_AA_ONLY_10NS; + } + else + { + remote_cs_capabilities.rtt_aa_only_precision = + BT_CONN_LE_CS_RTT_AA_ONLY_150NS; + } + } + else + { + remote_cs_capabilities.rtt_aa_only_precision = + BT_CONN_LE_CS_RTT_AA_ONLY_NOT_SUPP; + } + + if (evt->rtt_sounding_n) + { + if (evt->rtt_capability & BT_HCI_LE_CS_RTT_SOUNDING_N_10NS_MASK) + { + remote_cs_capabilities.rtt_sounding_precision = + BT_CONN_LE_CS_RTT_SOUNDING_10NS; + } + else + { + remote_cs_capabilities.rtt_sounding_precision = + BT_CONN_LE_CS_RTT_SOUNDING_150NS; + } + } + else + { + remote_cs_capabilities.rtt_sounding_precision = + BT_CONN_LE_CS_RTT_SOUNDING_NOT_SUPP; + } + + if (evt->rtt_random_payload_n) + { + if (evt->rtt_capability & BT_HCI_LE_CS_RTT_RANDOM_PAYLOAD_N_10NS_MASK) + { + remote_cs_capabilities.rtt_random_payload_precision = + BT_CONN_LE_CS_RTT_RANDOM_PAYLOAD_10NS; + } + else + { + remote_cs_capabilities.rtt_random_payload_precision = + BT_CONN_LE_CS_RTT_RANDOM_PAYLOAD_150NS; + } + } + else + { + remote_cs_capabilities.rtt_random_payload_precision = + BT_CONN_LE_CS_RTT_RANDOM_PAYLOAD_NOT_SUPP; + } + + remote_cs_capabilities.phase_based_nadm_sounding_supported = + sys_le16_to_cpu(evt->nadm_sounding_capability) & + BT_HCI_LE_CS_NADM_SOUNDING_CAPABILITY_PHASE_BASED_MASK; + + remote_cs_capabilities.phase_based_nadm_random_supported = + sys_le16_to_cpu(evt->nadm_random_capability) & + BT_HCI_LE_CS_NADM_RANDOM_CAPABILITY_PHASE_BASED_MASK; + + remote_cs_capabilities.cs_sync_2m_phy_supported = + evt->cs_sync_phys_supported & BT_HCI_LE_CS_SYNC_PHYS_2M_MASK; + + remote_cs_capabilities.cs_sync_2m_2bt_phy_supported = + evt->cs_sync_phys_supported & BT_HCI_LE_CS_SYNC_PHYS_2M_2BT_MASK; + + remote_cs_capabilities.cs_without_fae_supported = + sys_le16_to_cpu(evt->subfeatures_supported) & + BT_HCI_LE_CS_SUBFEATURE_NO_TX_FAE_MASK; + + remote_cs_capabilities.chsel_alg_3c_supported = + sys_le16_to_cpu(evt->subfeatures_supported) & + BT_HCI_LE_CS_SUBFEATURE_CHSEL_ALG_3C_MASK; + + remote_cs_capabilities.pbr_from_rtt_sounding_seq_supported = + sys_le16_to_cpu(evt->subfeatures_supported) & + BT_HCI_LE_CS_SUBFEATURE_PBR_FROM_RTT_SOUNDING_SEQ_MASK; + + remote_cs_capabilities.cs_ipt_reflector = + sys_le16_to_cpu(evt->subfeatures_supported) & + BT_HCI_LE_CS_SUBFEATURE_CS_IPT_REFLECTOR_MASK; + + remote_cs_capabilities.t_ip1_times_supported = + sys_le16_to_cpu(evt->t_ip1_times_supported); + remote_cs_capabilities.t_ip2_times_supported = + sys_le16_to_cpu(evt->t_ip2_times_supported); + remote_cs_capabilities.t_fcs_times_supported = + sys_le16_to_cpu(evt->t_fcs_times_supported); + remote_cs_capabilities.t_pm_times_supported = + sys_le16_to_cpu(evt->t_pm_times_supported); + + remote_cs_capabilities.t_sw_time = evt->t_sw_time_supported; + remote_cs_capabilities.tx_snr_capability = evt->tx_snr_capability; + + remote_cs_capabilities.t_ip2_ipt_times_supported = + sys_le16_to_cpu(evt->t_ip2_ipt_times_supported); + remote_cs_capabilities.t_sw_ipt_time_supported = + evt->t_sw_ipt_time_supported; + + bt_conn_notify_remote_cs_capabilities(conn, BT_HCI_ERR_SUCCESS, + &remote_cs_capabilities); + +out: + bt_conn_unref(conn); +} + +int bt_le_cs_set_default_settings(struct bt_conn *conn, + const struct bt_le_cs_set_default_settings_param *params) +{ + struct bt_hci_cp_le_cs_set_default_settings *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + cp->max_tx_power = params->max_tx_power; + cp->cs_sync_antenna_selection = params->cs_sync_antenna_selection; + cp->role_enable = 0; + + if (params->enable_initiator_role) + { + cp->role_enable |= BT_HCI_OP_LE_CS_INITIATOR_ROLE_MASK; + } + + if (params->enable_reflector_role) + { + cp->role_enable |= BT_HCI_OP_LE_CS_REFLECTOR_ROLE_MASK; + } + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CS_SET_DEFAULT_SETTINGS, buf, + NULL); +} + +int bt_le_cs_read_remote_fae_table(struct bt_conn *conn) +{ + struct bt_hci_cp_le_read_remote_fae_table *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CS_READ_REMOTE_FAE_TABLE, buf, + NULL); +} + +void bt_hci_le_cs_read_remote_fae_table_complete(struct net_buf *buf) +{ + struct bt_conn *conn; + struct bt_conn_le_cs_fae_table fae_table; + struct bt_hci_evt_le_cs_read_remote_fae_table_complete *evt; + + if (buf->len < sizeof(*evt)) + { + LOG_ERR("Unexpected end of buffer"); + return; + } + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + if (evt->status) + { + LOG_WRN("Read Remote FAE Table failed with status " + "0x%02X", evt->status); + } + + conn = bt_conn_lookup_handle(sys_le16_to_cpu(evt->conn_handle), + BT_CONN_TYPE_LE); + if (!conn) + { + LOG_ERR("Could not lookup connection handle when reading remote FAE " + "Table"); + return; + } + + if (evt->status == BT_HCI_ERR_SUCCESS) + { + fae_table.remote_fae_table = evt->remote_fae_table; + + bt_conn_notify_remote_cs_fae_table(conn, BT_HCI_ERR_SUCCESS, + &fae_table); + } + else + { + bt_conn_notify_remote_cs_fae_table(conn, evt->status, NULL); + } + + bt_conn_unref(conn); +} + +#if defined(CONFIG_BT_CHANNEL_SOUNDING_TEST) +int bt_le_cs_test_cb_register(struct bt_le_cs_test_cb cb) +{ + cs_test_callbacks = cb; + return 0; +} + +int bt_le_cs_start_test(const struct bt_le_cs_test_param *params) +{ + struct bt_hci_op_le_cs_test *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + + cp->main_mode_type = BT_CONN_LE_CS_MODE_MAIN_MODE_PART(params->mode); + + uint8_t sub_mode_type = BT_CONN_LE_CS_MODE_SUB_MODE_PART(params->mode); + + if (sub_mode_type) + { + cp->sub_mode_type = sub_mode_type; + } + else + { + cp->sub_mode_type = BT_HCI_OP_LE_CS_SUB_MODE_UNUSED; + } + + cp->main_mode_repetition = params->main_mode_repetition; + cp->mode_0_steps = params->mode_0_steps; + cp->role = params->role; + cp->rtt_type = params->rtt_type; + cp->cs_sync_phy = params->cs_sync_phy; + cp->cs_sync_antenna_selection = params->cs_sync_antenna_selection; + sys_put_le24(params->subevent_len, cp->subevent_len); + cp->subevent_interval = sys_cpu_to_le16(params->subevent_interval); + cp->max_num_subevents = params->max_num_subevents; + cp->transmit_power_level = params->transmit_power_level; + cp->t_ip1_time = params->t_ip1_time; + cp->t_ip2_time = params->t_ip2_time; + cp->t_fcs_time = params->t_fcs_time; + cp->t_pm_time = params->t_pm_time; + cp->t_sw_time = params->t_sw_time; + cp->tone_antenna_config_selection = params->tone_antenna_config_selection; + cp->cs_enhancements_1 = 0; + cp->snr_control_initiator = params->initiator_snr_control; + cp->snr_control_reflector = params->reflector_snr_control; + cp->drbg_nonce = sys_cpu_to_le16(params->drbg_nonce); + cp->channel_map_repetition = + params->override_config_0.channel_map_repetition; + cp->override_config = sys_cpu_to_le16(params->override_config); + + uint8_t override_parameters_length = 0; + + if (params->override_config & BT_HCI_OP_LE_CS_TEST_OVERRIDE_CONFIG_0_MASK) + { + const uint8_t num_channels = + params->override_config_0.set.num_channels; + + net_buf_add_u8(buf, num_channels); + override_parameters_length++; + net_buf_add_mem(buf, params->override_config_0.set.channels, + num_channels); + override_parameters_length += num_channels; + } + else + { + net_buf_add_mem(buf, params->override_config_0.not_set.channel_map, + sizeof(params->override_config_0.not_set.channel_map)); + net_buf_add_u8(buf, + params->override_config_0.not_set.channel_selection_type); + net_buf_add_u8(buf, params->override_config_0.not_set.ch3c_shape); + net_buf_add_u8(buf, params->override_config_0.not_set.ch3c_jump); + + override_parameters_length += + (sizeof(params->override_config_0.not_set.channel_map) + + sizeof(params->override_config_0.not_set. + channel_selection_type) + + sizeof(params->override_config_0.not_set.ch3c_shape) + + sizeof(params->override_config_0.not_set.ch3c_jump)); + } + + if (params->override_config & BT_HCI_OP_LE_CS_TEST_OVERRIDE_CONFIG_2_MASK) + { + net_buf_add_mem(buf, ¶ms->override_config_2, + sizeof(params->override_config_2)); + override_parameters_length += sizeof(params->override_config_2); + } + + if (params->override_config & BT_HCI_OP_LE_CS_TEST_OVERRIDE_CONFIG_3_MASK) + { + net_buf_add_mem(buf, ¶ms->override_config_3, + sizeof(params->override_config_3)); + override_parameters_length += sizeof(params->override_config_3); + } + + if (params->override_config & BT_HCI_OP_LE_CS_TEST_OVERRIDE_CONFIG_4_MASK) + { + net_buf_add_mem(buf, ¶ms->override_config_4, + sizeof(params->override_config_4)); + override_parameters_length += sizeof(params->override_config_4); + } + + if (params->override_config & BT_HCI_OP_LE_CS_TEST_OVERRIDE_CONFIG_5_MASK) + { + net_buf_add_le32(buf, params->override_config_5.cs_sync_aa_initiator); + net_buf_add_le32(buf, params->override_config_5.cs_sync_aa_reflector); + override_parameters_length += sizeof(params->override_config_5); + } + + if (params->override_config & BT_HCI_OP_LE_CS_TEST_OVERRIDE_CONFIG_6_MASK) + { + net_buf_add_mem(buf, ¶ms->override_config_6, + sizeof(params->override_config_6)); + override_parameters_length += sizeof(params->override_config_6); + } + + if (params->override_config & BT_HCI_OP_LE_CS_TEST_OVERRIDE_CONFIG_7_MASK) + { + net_buf_add_mem(buf, ¶ms->override_config_7, + sizeof(params->override_config_7)); + override_parameters_length += sizeof(params->override_config_7); + } + + if (params->override_config & BT_HCI_OP_LE_CS_TEST_OVERRIDE_CONFIG_8_MASK) + { + net_buf_add_mem(buf, ¶ms->override_config_8, + sizeof(params->override_config_8)); + override_parameters_length += sizeof(params->override_config_8); + } + + cp->override_parameters_length = override_parameters_length; + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CS_TEST, buf, NULL); +} +#endif /* CONFIG_BT_CHANNEL_SOUNDING_TEST */ + +void bt_hci_le_cs_subevent_result(struct net_buf *buf) +{ + struct bt_conn *conn = NULL; + struct bt_hci_evt_le_cs_subevent_result *evt; + struct bt_conn_le_cs_subevent_result result; + struct bt_conn_le_cs_subevent_result *p_result = &result; + struct net_buf_simple step_data_buf; + struct net_buf *reassembly_buf = NULL; + + if (buf->len < sizeof(*evt)) + { + LOG_ERR("Unexpected end of buffer"); + return; + } + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + uint16_t conn_handle = sys_le16_to_cpu(evt->conn_handle); + +#if defined(CONFIG_BT_CHANNEL_SOUNDING_TEST) + if (conn_handle == BT_HCI_LE_CS_TEST_CONN_HANDLE) + { + if (!cs_test_callbacks.le_cs_test_subevent_data_available) + { + LOG_WRN("No callback registered. Discarded subevent results from " + "CS " + "Test."); + return; + } + } + else +#endif /* CONFIG_BT_CHANNEL_SOUNDING_TEST */ + { + conn = bt_conn_lookup_handle(conn_handle, BT_CONN_TYPE_LE); + if (!conn) + { + LOG_ERR("Unknown connection handle when processing subevent " + "results"); + return; + } + } + + if (evt->subevent_done_status != BT_HCI_LE_CS_SUBEVENT_DONE_STATUS_PARTIAL) + { + p_result->step_data_buf = NULL; + if (evt->num_steps_reported) + { + net_buf_simple_init_with_data(&step_data_buf, evt->steps, + buf->len); + p_result->step_data_buf = &step_data_buf; + } + } + else + { + if (evt->procedure_done_status != + BT_HCI_LE_CS_PROCEDURE_DONE_STATUS_PARTIAL) + { + LOG_WRN("Procedure status is inconsistent with subevent status. " + "Discarding " + "subevent results"); + goto abort; + } + + if (!evt->num_steps_reported) + { + LOG_WRN("Discarding partial results without step data"); + goto abort; + } + + reassembly_buf = start_reassembly(conn_handle, evt->steps, buf->len); + if (!reassembly_buf) + { + goto abort; + } + + p_result = &reassembled_result; + p_result->step_data_buf = + (struct net_buf_simple *)&reassembly_buf->data; + } + + p_result->header.procedure_counter = + sys_le16_to_cpu(evt->procedure_counter); + p_result->header.frequency_compensation = + sys_le16_to_cpu(evt->frequency_compensation); + p_result->header.procedure_done_status = evt->procedure_done_status; + p_result->header.subevent_done_status = evt->subevent_done_status; + p_result->header.procedure_abort_reason = evt->procedure_abort_reason; + p_result->header.subevent_abort_reason = evt->subevent_abort_reason; + p_result->header.reference_power_level = evt->reference_power_level; + p_result->header.num_antenna_paths = evt->num_antenna_paths; + p_result->header.num_steps_reported = evt->num_steps_reported; + p_result->header.abort_step = + evt->subevent_done_status == + BT_HCI_LE_CS_SUBEVENT_DONE_STATUS_ABORTED ? 0 : 255; + + p_result->header.config_id = 0; + p_result->header.start_acl_conn_event = 0; + if (conn) + { + p_result->header.config_id = evt->config_id; + p_result->header.start_acl_conn_event = + sys_le16_to_cpu(evt->start_acl_conn_event_counter); + } + + if (evt->subevent_done_status != BT_HCI_LE_CS_SUBEVENT_DONE_STATUS_PARTIAL) + { + invoke_subevent_result_callback(conn, p_result); + } + + if (evt->procedure_done_status != BT_CONN_LE_CS_PROCEDURE_INCOMPLETE) + { + /* We can now clear the any reassembly buffer allocated for this + * procedure, to avoid code duplication, we're using the abort label to + * do so + */ + + goto abort; + } + + bt_conn_drop(&conn); + + return; + +abort: + bt_conn_drop(&conn); + + reassembly_buf = get_reassembly_buf(conn_handle, false); + if (reassembly_buf) + { + free_reassembly_buf(&reassembly_buf); + } +} + +void bt_hci_le_cs_subevent_result_continue(struct net_buf *buf) +{ + struct bt_conn *conn = NULL; + struct bt_hci_evt_le_cs_subevent_result_continue *evt; + struct net_buf *reassembly_buf = NULL; + uint16_t conn_handle; + + if (buf->len < sizeof(*evt)) + { + LOG_ERR("Unexpected end of buffer"); + return; + } + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + conn_handle = sys_le16_to_cpu(evt->conn_handle); + +#if defined(CONFIG_BT_CHANNEL_SOUNDING_TEST) + if (conn_handle == BT_HCI_LE_CS_TEST_CONN_HANDLE) + { + if (!cs_test_callbacks.le_cs_test_subevent_data_available) + { + LOG_WRN("No callback registered. Discarded subevent results from " + "CS " + "Test."); + return; + } + } + else +#endif /* CONFIG_BT_CHANNEL_SOUNDING_TEST */ + { + conn = bt_conn_lookup_handle(conn_handle, BT_CONN_TYPE_LE); + if (!conn) + { + LOG_ERR("Unknown connection handle when processing subevent " + "results"); + return; + } + } + + uint16_t step_data_len = evt->num_steps_reported ? buf->len : 0; + + reassembly_buf = continue_reassembly(conn_handle, evt->steps, + step_data_len); + if (!reassembly_buf) + { + goto abort; + } + + reassembled_result.header.procedure_done_status = + evt->procedure_done_status; + reassembled_result.header.subevent_done_status = evt->subevent_done_status; + reassembled_result.header.procedure_abort_reason = + evt->procedure_abort_reason; + reassembled_result.header.subevent_abort_reason = + evt->subevent_abort_reason; + + if (evt->num_antenna_paths != reassembled_result.header.num_antenna_paths) + { + LOG_WRN("Received inconsistent number of antenna paths from the " + "controller: %d, " + "previous number was: %d", + evt->num_antenna_paths, + reassembled_result.header.num_antenna_paths); + } + + if (evt->subevent_done_status == + BT_HCI_LE_CS_SUBEVENT_DONE_STATUS_ABORTED && + reassembled_result.header.num_steps_reported < + reassembled_result.header.abort_step) + { + reassembled_result.header.abort_step = + reassembled_result.header.num_steps_reported; + } + + reassembled_result.header.num_steps_reported += evt->num_steps_reported; + + if (evt->subevent_done_status != BT_HCI_LE_CS_SUBEVENT_DONE_STATUS_PARTIAL) + { + invoke_subevent_result_callback(conn, &reassembled_result); + net_buf_reset(reassembly_buf); + reset_reassembly_results(); + } + + if (evt->procedure_done_status != + BT_HCI_LE_CS_PROCEDURE_DONE_STATUS_PARTIAL) + { + if (evt->subevent_done_status == + BT_HCI_LE_CS_SUBEVENT_DONE_STATUS_PARTIAL) + { + LOG_WRN("Procedure status is inconsistent with subevent status. " + "Discarding " + "subevent results"); + goto abort; + } + + free_reassembly_buf(&reassembly_buf); + } + + bt_conn_drop(&conn); + + return; + +abort: + bt_conn_drop(&conn); + + if (reassembly_buf) + { + free_reassembly_buf(&reassembly_buf); + } +} + +void bt_hci_le_cs_config_complete_event(struct net_buf *buf) +{ + struct bt_hci_evt_le_cs_config_complete *evt; + struct bt_conn_le_cs_config config; + struct bt_conn *conn; + + if (buf->len < sizeof(*evt)) + { + LOG_ERR("Unexpected end of buffer"); + return; + } + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + if (evt->status) + { + LOG_WRN("CS Config failed (status 0x%02X)", evt->status); + } + + conn = bt_conn_lookup_handle(sys_le16_to_cpu(evt->handle), + BT_CONN_TYPE_LE); + if (!conn) + { + LOG_ERR("Could not lookup connection handle when reading CS " + "configuration"); + return; + } + + if (evt->status == BT_HCI_ERR_SUCCESS) + { + if (evt->action == BT_HCI_LE_CS_CONFIG_ACTION_REMOVED) + { + bt_conn_notify_cs_config_removed(conn, evt->config_id); + bt_conn_unref(conn); + return; + } + + if (evt->sub_mode_type == BT_HCI_OP_LE_CS_SUB_MODE_UNUSED) + { + config.mode = evt->main_mode_type; + } + else + { + config.mode = evt->main_mode_type | (evt->sub_mode_type << 4); + } + + config.id = evt->config_id; + config.min_main_mode_steps = evt->min_main_mode_steps; + config.max_main_mode_steps = evt->max_main_mode_steps; + config.main_mode_repetition = evt->main_mode_repetition; + config.mode_0_steps = evt->mode_0_steps; + config.role = evt->role; + config.rtt_type = evt->rtt_type; + config.cs_sync_phy = evt->cs_sync_phy; + config.channel_map_repetition = evt->channel_map_repetition; + config.channel_selection_type = evt->channel_selection_type; + config.ch3c_shape = evt->ch3c_shape; + config.ch3c_jump = evt->ch3c_jump; + config.cs_enhancements_1 = evt->cs_enhancements_1; + config.t_ip1_time_us = evt->t_ip1_time; + config.t_ip2_time_us = evt->t_ip2_time; + config.t_fcs_time_us = evt->t_fcs_time; + config.t_pm_time_us = evt->t_pm_time; + memcpy(config.channel_map, evt->channel_map, + ARRAY_SIZE(config.channel_map)); + + bt_conn_notify_cs_config_created(conn, BT_HCI_ERR_SUCCESS, &config); + } + else + { + bt_conn_notify_cs_config_created(conn, evt->status, NULL); + } + + bt_conn_unref(conn); +} + +int bt_le_cs_create_config( + struct bt_conn *conn, struct bt_le_cs_create_config_params *params, + enum bt_le_cs_create_config_context context) +{ + struct bt_hci_cp_le_cs_create_config *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + cp->config_id = params->id; + cp->create_context = context; + cp->main_mode_type = BT_CONN_LE_CS_MODE_MAIN_MODE_PART(params->mode); + + uint8_t sub_mode_type = BT_CONN_LE_CS_MODE_SUB_MODE_PART(params->mode); + + if (sub_mode_type) + { + cp->sub_mode_type = sub_mode_type; + } + else + { + cp->sub_mode_type = BT_HCI_OP_LE_CS_SUB_MODE_UNUSED; + } + + cp->min_main_mode_steps = params->min_main_mode_steps; + cp->max_main_mode_steps = params->max_main_mode_steps; + cp->main_mode_repetition = params->main_mode_repetition; + cp->mode_0_steps = params->mode_0_steps; + cp->role = params->role; + cp->rtt_type = params->rtt_type; + cp->cs_sync_phy = params->cs_sync_phy; + cp->channel_map_repetition = params->channel_map_repetition; + cp->channel_selection_type = params->channel_selection_type; + cp->ch3c_shape = params->ch3c_shape; + cp->ch3c_jump = params->ch3c_jump; + cp->cs_enhancements_1 = params->cs_enhancements_1; + memcpy(cp->channel_map, params->channel_map, ARRAY_SIZE(cp->channel_map)); + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CS_CREATE_CONFIG, buf, NULL); +} + +int bt_le_cs_remove_config(struct bt_conn *conn, uint8_t config_id) +{ + struct bt_hci_cp_le_cs_remove_config *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + cp->config_id = config_id; + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CS_REMOVE_CONFIG, buf, NULL); +} + +int bt_le_cs_security_enable(struct bt_conn *conn) +{ + struct bt_hci_cp_le_security_enable *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CS_SECURITY_ENABLE, buf, NULL); +} + +int bt_le_cs_procedure_enable(struct bt_conn *conn, + const struct bt_le_cs_procedure_enable_param *params) +{ + struct bt_hci_cp_le_procedure_enable *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + cp->config_id = params->config_id; + cp->enable = params->enable; + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CS_PROCEDURE_ENABLE, buf, NULL); +} + +int bt_le_cs_set_procedure_parameters(struct bt_conn *conn, + const struct bt_le_cs_set_procedure_parameters_param *params) +{ + struct bt_hci_cp_le_set_procedure_parameters *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + cp->config_id = params->config_id; + cp->max_procedure_len = sys_cpu_to_le16(params->max_procedure_len); + cp->min_procedure_interval = + sys_cpu_to_le16(params->min_procedure_interval); + cp->max_procedure_interval = + sys_cpu_to_le16(params->max_procedure_interval); + cp->max_procedure_count = sys_cpu_to_le16(params->max_procedure_count); + sys_put_le24(params->min_subevent_len, cp->min_subevent_len); + sys_put_le24(params->max_subevent_len, cp->max_subevent_len); + cp->tone_antenna_config_selection = params->tone_antenna_config_selection; + cp->phy = params->phy; + cp->tx_power_delta = params->tx_power_delta; + cp->preferred_peer_antenna = params->preferred_peer_antenna; + cp->snr_control_initiator = params->snr_control_initiator; + cp->snr_control_reflector = params->snr_control_reflector; + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CS_SET_PROCEDURE_PARAMETERS, buf, + NULL); +} + +int bt_le_cs_set_channel_classification(uint8_t channel_classification[10]) +{ + uint8_t *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, 10); + memcpy(cp, channel_classification, 10); + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CS_SET_CHANNEL_CLASSIFICATION, + buf, + NULL); +} + +int bt_le_cs_read_local_supported_capabilities( + struct bt_conn_le_cs_capabilities *ret) +{ + struct bt_hci_rp_le_read_local_supported_capabilities *rp; + struct net_buf *rsp; + + int err = + bt_hci_cmd_send_sync( + BT_HCI_OP_LE_CS_READ_LOCAL_SUPPORTED_CAPABILITIES, + NULL, &rsp); + + if (err) + { + return err; + } + + rp = (void *)rsp->data; + + uint8_t status = rp->status; + + ret->num_config_supported = rp->num_config_supported; + ret->max_consecutive_procedures_supported = + sys_le16_to_cpu(rp->max_consecutive_procedures_supported); + ret->num_antennas_supported = rp->num_antennas_supported; + ret->max_antenna_paths_supported = rp->max_antenna_paths_supported; + + ret->initiator_supported = + rp->roles_supported & BT_HCI_LE_CS_INITIATOR_ROLE_MASK; + ret->reflector_supported = + rp->roles_supported & BT_HCI_LE_CS_REFLECTOR_ROLE_MASK; + ret->mode_3_supported = + rp->modes_supported & BT_HCI_LE_CS_MODES_SUPPORTED_MODE_3_MASK; + + ret->rtt_aa_only_n = rp->rtt_aa_only_n; + ret->rtt_sounding_n = rp->rtt_sounding_n; + ret->rtt_random_payload_n = rp->rtt_random_payload_n; + + if (rp->rtt_aa_only_n) + { + if (rp->rtt_capability & BT_HCI_LE_CS_RTT_AA_ONLY_N_10NS_MASK) + { + ret->rtt_aa_only_precision = BT_CONN_LE_CS_RTT_AA_ONLY_10NS; + } + else + { + ret->rtt_aa_only_precision = BT_CONN_LE_CS_RTT_AA_ONLY_150NS; + } + } + else + { + ret->rtt_aa_only_precision = BT_CONN_LE_CS_RTT_AA_ONLY_NOT_SUPP; + } + + if (rp->rtt_sounding_n) + { + if (rp->rtt_capability & BT_HCI_LE_CS_RTT_SOUNDING_N_10NS_MASK) + { + ret->rtt_sounding_precision = BT_CONN_LE_CS_RTT_SOUNDING_10NS; + } + else + { + ret->rtt_sounding_precision = BT_CONN_LE_CS_RTT_SOUNDING_150NS; + } + } + else + { + ret->rtt_sounding_precision = BT_CONN_LE_CS_RTT_SOUNDING_NOT_SUPP; + } + + if (rp->rtt_random_payload_n) + { + if (rp->rtt_capability & BT_HCI_LE_CS_RTT_RANDOM_PAYLOAD_N_10NS_MASK) + { + ret->rtt_random_payload_precision = + BT_CONN_LE_CS_RTT_RANDOM_PAYLOAD_10NS; + } + else + { + ret->rtt_random_payload_precision = + BT_CONN_LE_CS_RTT_RANDOM_PAYLOAD_150NS; + } + } + else + { + ret->rtt_random_payload_precision = + BT_CONN_LE_CS_RTT_RANDOM_PAYLOAD_NOT_SUPP; + } + + ret->phase_based_nadm_sounding_supported = + sys_le16_to_cpu(rp->nadm_sounding_capability) & + BT_HCI_LE_CS_NADM_SOUNDING_CAPABILITY_PHASE_BASED_MASK; + + ret->phase_based_nadm_random_supported = + sys_le16_to_cpu(rp->nadm_random_capability) & + BT_HCI_LE_CS_NADM_RANDOM_CAPABILITY_PHASE_BASED_MASK; + + ret->cs_sync_2m_phy_supported = + rp->cs_sync_phys_supported & BT_HCI_LE_CS_SYNC_PHYS_2M_MASK; + + ret->cs_sync_2m_2bt_phy_supported = + rp->cs_sync_phys_supported & BT_HCI_LE_CS_SYNC_PHYS_2M_2BT_MASK; + + ret->cs_without_fae_supported = + sys_le16_to_cpu(rp->subfeatures_supported) & + BT_HCI_LE_CS_SUBFEATURE_NO_TX_FAE_MASK; + + ret->chsel_alg_3c_supported = + sys_le16_to_cpu(rp->subfeatures_supported) & + BT_HCI_LE_CS_SUBFEATURE_CHSEL_ALG_3C_MASK; + + ret->pbr_from_rtt_sounding_seq_supported = + sys_le16_to_cpu(rp->subfeatures_supported) & + BT_HCI_LE_CS_SUBFEATURE_PBR_FROM_RTT_SOUNDING_SEQ_MASK; + + ret->t_ip1_times_supported = sys_le16_to_cpu(rp->t_ip1_times_supported); + ret->t_ip2_times_supported = sys_le16_to_cpu(rp->t_ip2_times_supported); + ret->t_fcs_times_supported = sys_le16_to_cpu(rp->t_fcs_times_supported); + ret->t_pm_times_supported = sys_le16_to_cpu(rp->t_pm_times_supported); + + ret->t_sw_time = rp->t_sw_time_supported; + ret->tx_snr_capability = rp->tx_snr_capability; + + ret->t_ip2_ipt_times_supported = 0; + ret->t_sw_ipt_time_supported = 0; + + net_buf_unref(rsp); + return status; +} + +int bt_le_cs_read_local_supported_capabilities_v2( + struct bt_conn_le_cs_capabilities *ret) +{ + struct bt_hci_rp_le_read_local_supported_capabilities_v2 *rp; + struct net_buf *rsp; + + int err = + bt_hci_cmd_send_sync( + BT_HCI_OP_LE_CS_READ_LOCAL_SUPPORTED_CAPABILITIES_V2, + NULL, &rsp); + + if (err) + { + return err; + } + + rp = (void *)rsp->data; + + uint8_t status = rp->status; + + ret->num_config_supported = rp->num_config_supported; + ret->max_consecutive_procedures_supported = + sys_le16_to_cpu(rp->max_consecutive_procedures_supported); + ret->num_antennas_supported = rp->num_antennas_supported; + ret->max_antenna_paths_supported = rp->max_antenna_paths_supported; + + ret->initiator_supported = + rp->roles_supported & BT_HCI_LE_CS_INITIATOR_ROLE_MASK; + ret->reflector_supported = + rp->roles_supported & BT_HCI_LE_CS_REFLECTOR_ROLE_MASK; + ret->mode_3_supported = + rp->modes_supported & BT_HCI_LE_CS_MODES_SUPPORTED_MODE_3_MASK; + + ret->rtt_aa_only_n = rp->rtt_aa_only_n; + ret->rtt_sounding_n = rp->rtt_sounding_n; + ret->rtt_random_payload_n = rp->rtt_random_payload_n; + + if (rp->rtt_aa_only_n) + { + if (rp->rtt_capability & BT_HCI_LE_CS_RTT_AA_ONLY_N_10NS_MASK) + { + ret->rtt_aa_only_precision = BT_CONN_LE_CS_RTT_AA_ONLY_10NS; + } + else + { + ret->rtt_aa_only_precision = BT_CONN_LE_CS_RTT_AA_ONLY_150NS; + } + } + else + { + ret->rtt_aa_only_precision = BT_CONN_LE_CS_RTT_AA_ONLY_NOT_SUPP; + } + + if (rp->rtt_sounding_n) + { + if (rp->rtt_capability & BT_HCI_LE_CS_RTT_SOUNDING_N_10NS_MASK) + { + ret->rtt_sounding_precision = BT_CONN_LE_CS_RTT_SOUNDING_10NS; + } + else + { + ret->rtt_sounding_precision = BT_CONN_LE_CS_RTT_SOUNDING_150NS; + } + } + else + { + ret->rtt_sounding_precision = BT_CONN_LE_CS_RTT_SOUNDING_NOT_SUPP; + } + + if (rp->rtt_random_payload_n) + { + if (rp->rtt_capability & BT_HCI_LE_CS_RTT_RANDOM_PAYLOAD_N_10NS_MASK) + { + ret->rtt_random_payload_precision = + BT_CONN_LE_CS_RTT_RANDOM_PAYLOAD_10NS; + } + else + { + ret->rtt_random_payload_precision = + BT_CONN_LE_CS_RTT_RANDOM_PAYLOAD_150NS; + } + } + else + { + ret->rtt_random_payload_precision = + BT_CONN_LE_CS_RTT_RANDOM_PAYLOAD_NOT_SUPP; + } + + ret->phase_based_nadm_sounding_supported = + sys_le16_to_cpu(rp->nadm_sounding_capability) & + BT_HCI_LE_CS_NADM_SOUNDING_CAPABILITY_PHASE_BASED_MASK; + + ret->phase_based_nadm_random_supported = + sys_le16_to_cpu(rp->nadm_random_capability) & + BT_HCI_LE_CS_NADM_RANDOM_CAPABILITY_PHASE_BASED_MASK; + + ret->cs_sync_2m_phy_supported = + rp->cs_sync_phys_supported & BT_HCI_LE_CS_SYNC_PHYS_2M_MASK; + + ret->cs_sync_2m_2bt_phy_supported = + rp->cs_sync_phys_supported & BT_HCI_LE_CS_SYNC_PHYS_2M_2BT_MASK; + + ret->cs_without_fae_supported = + sys_le16_to_cpu(rp->subfeatures_supported) & + BT_HCI_LE_CS_SUBFEATURE_NO_TX_FAE_MASK; + + ret->chsel_alg_3c_supported = + sys_le16_to_cpu(rp->subfeatures_supported) & + BT_HCI_LE_CS_SUBFEATURE_CHSEL_ALG_3C_MASK; + + ret->pbr_from_rtt_sounding_seq_supported = + sys_le16_to_cpu(rp->subfeatures_supported) & + BT_HCI_LE_CS_SUBFEATURE_PBR_FROM_RTT_SOUNDING_SEQ_MASK; + + ret->cs_ipt_reflector = sys_le16_to_cpu(rp->subfeatures_supported) & + BT_HCI_LE_CS_SUBFEATURE_CS_IPT_REFLECTOR_MASK; + + ret->t_ip1_times_supported = sys_le16_to_cpu(rp->t_ip1_times_supported); + ret->t_ip2_times_supported = sys_le16_to_cpu(rp->t_ip2_times_supported); + ret->t_fcs_times_supported = sys_le16_to_cpu(rp->t_fcs_times_supported); + ret->t_pm_times_supported = sys_le16_to_cpu(rp->t_pm_times_supported); + + ret->t_sw_time = rp->t_sw_time_supported; + ret->tx_snr_capability = rp->tx_snr_capability; + + ret->t_ip2_ipt_times_supported = + sys_le16_to_cpu(rp->t_ip2_ipt_times_supported); + ret->t_sw_ipt_time_supported = rp->t_sw_ipt_time_supported; + + net_buf_unref(rsp); + return status; +} + +int bt_le_cs_write_cached_remote_supported_capabilities(struct bt_conn *conn, + const struct bt_conn_le_cs_capabilities *params) +{ + struct bt_hci_cp_le_write_cached_remote_supported_capabilities *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + + cp->handle = sys_cpu_to_le16(conn->handle); + + cp->num_config_supported = params->num_config_supported; + + cp->max_consecutive_procedures_supported = + sys_cpu_to_le16(params->max_consecutive_procedures_supported); + + cp->num_antennas_supported = params->num_antennas_supported; + cp->max_antenna_paths_supported = params->max_antenna_paths_supported; + + cp->roles_supported = 0; + if (params->initiator_supported) + { + cp->roles_supported |= BT_HCI_LE_CS_INITIATOR_ROLE_MASK; + } + + if (params->reflector_supported) + { + cp->roles_supported |= BT_HCI_LE_CS_REFLECTOR_ROLE_MASK; + } + + cp->modes_supported = 0; + if (params->mode_3_supported) + { + cp->modes_supported |= BT_HCI_LE_CS_MODES_SUPPORTED_MODE_3_MASK; + } + + cp->rtt_aa_only_n = params->rtt_aa_only_n; + cp->rtt_sounding_n = params->rtt_sounding_n; + cp->rtt_random_payload_n = params->rtt_random_payload_n; + + cp->rtt_capability = 0; + if (params->rtt_aa_only_precision == BT_CONN_LE_CS_RTT_AA_ONLY_10NS) + { + cp->rtt_capability |= BT_HCI_LE_CS_RTT_AA_ONLY_N_10NS_MASK; + } + + if (params->rtt_sounding_precision == BT_CONN_LE_CS_RTT_SOUNDING_10NS) + { + cp->rtt_capability |= BT_HCI_LE_CS_RTT_SOUNDING_N_10NS_MASK; + } + + if (params->rtt_random_payload_precision == + BT_CONN_LE_CS_RTT_RANDOM_PAYLOAD_10NS) + { + cp->rtt_capability |= BT_HCI_LE_CS_RTT_RANDOM_PAYLOAD_N_10NS_MASK; + } + + cp->nadm_sounding_capability = 0; + if (params->phase_based_nadm_sounding_supported) + { + cp->nadm_sounding_capability |= + sys_cpu_to_le16( + BT_HCI_LE_CS_NADM_SOUNDING_CAPABILITY_PHASE_BASED_MASK); + } + + cp->nadm_random_capability = 0; + if (params->phase_based_nadm_random_supported) + { + cp->nadm_random_capability |= + sys_cpu_to_le16( + BT_HCI_LE_CS_NADM_RANDOM_CAPABILITY_PHASE_BASED_MASK); + } + + cp->cs_sync_phys_supported = 0; + if (params->cs_sync_2m_phy_supported) + { + cp->cs_sync_phys_supported |= BT_HCI_LE_CS_SYNC_PHYS_2M_MASK; + } + + if (params->cs_sync_2m_2bt_phy_supported) + { + cp->cs_sync_phys_supported |= BT_HCI_LE_CS_SYNC_PHYS_2M_2BT_MASK; + } + + cp->subfeatures_supported = 0; + if (params->cs_without_fae_supported) + { + cp->subfeatures_supported |= + sys_cpu_to_le16(BT_HCI_LE_CS_SUBFEATURE_NO_TX_FAE_MASK); + } + + if (params->chsel_alg_3c_supported) + { + cp->subfeatures_supported |= + sys_cpu_to_le16(BT_HCI_LE_CS_SUBFEATURE_CHSEL_ALG_3C_MASK); + } + + if (params->pbr_from_rtt_sounding_seq_supported) + { + cp->subfeatures_supported |= + sys_cpu_to_le16( + BT_HCI_LE_CS_SUBFEATURE_PBR_FROM_RTT_SOUNDING_SEQ_MASK); + } + + cp->t_ip1_times_supported = sys_cpu_to_le16(params->t_ip1_times_supported); + cp->t_ip2_times_supported = sys_cpu_to_le16(params->t_ip2_times_supported); + cp->t_fcs_times_supported = sys_cpu_to_le16(params->t_fcs_times_supported); + cp->t_pm_times_supported = sys_cpu_to_le16(params->t_pm_times_supported); + cp->t_sw_time_supported = params->t_sw_time; + cp->tx_snr_capability = params->tx_snr_capability; + + return + bt_hci_cmd_send_sync( + BT_HCI_OP_LE_CS_WRITE_CACHED_REMOTE_SUPPORTED_CAPABILITIES, + buf, + NULL); +} + +int bt_le_cs_write_cached_remote_supported_capabilities_v2( + struct bt_conn *conn, const struct bt_conn_le_cs_capabilities *params) +{ + struct bt_hci_cp_le_write_cached_remote_supported_capabilities_v2 *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + + cp->handle = sys_cpu_to_le16(conn->handle); + + cp->num_config_supported = params->num_config_supported; + + cp->max_consecutive_procedures_supported = + sys_cpu_to_le16(params->max_consecutive_procedures_supported); + + cp->num_antennas_supported = params->num_antennas_supported; + cp->max_antenna_paths_supported = params->max_antenna_paths_supported; + + cp->roles_supported = 0; + if (params->initiator_supported) + { + cp->roles_supported |= BT_HCI_LE_CS_INITIATOR_ROLE_MASK; + } + + if (params->reflector_supported) + { + cp->roles_supported |= BT_HCI_LE_CS_REFLECTOR_ROLE_MASK; + } + + cp->modes_supported = 0; + if (params->mode_3_supported) + { + cp->modes_supported |= BT_HCI_LE_CS_MODES_SUPPORTED_MODE_3_MASK; + } + + cp->rtt_aa_only_n = params->rtt_aa_only_n; + cp->rtt_sounding_n = params->rtt_sounding_n; + cp->rtt_random_payload_n = params->rtt_random_payload_n; + + cp->rtt_capability = 0; + if (params->rtt_aa_only_precision == BT_CONN_LE_CS_RTT_AA_ONLY_10NS) + { + cp->rtt_capability |= BT_HCI_LE_CS_RTT_AA_ONLY_N_10NS_MASK; + } + + if (params->rtt_sounding_precision == BT_CONN_LE_CS_RTT_SOUNDING_10NS) + { + cp->rtt_capability |= BT_HCI_LE_CS_RTT_SOUNDING_N_10NS_MASK; + } + + if (params->rtt_random_payload_precision == + BT_CONN_LE_CS_RTT_RANDOM_PAYLOAD_10NS) + { + cp->rtt_capability |= BT_HCI_LE_CS_RTT_RANDOM_PAYLOAD_N_10NS_MASK; + } + + cp->nadm_sounding_capability = 0; + if (params->phase_based_nadm_sounding_supported) + { + cp->nadm_sounding_capability |= + sys_cpu_to_le16( + BT_HCI_LE_CS_NADM_SOUNDING_CAPABILITY_PHASE_BASED_MASK); + } + + cp->nadm_random_capability = 0; + if (params->phase_based_nadm_random_supported) + { + cp->nadm_random_capability |= + sys_cpu_to_le16( + BT_HCI_LE_CS_NADM_RANDOM_CAPABILITY_PHASE_BASED_MASK); + } + + cp->cs_sync_phys_supported = 0; + if (params->cs_sync_2m_phy_supported) + { + cp->cs_sync_phys_supported |= BT_HCI_LE_CS_SYNC_PHYS_2M_MASK; + } + + if (params->cs_sync_2m_2bt_phy_supported) + { + cp->cs_sync_phys_supported |= BT_HCI_LE_CS_SYNC_PHYS_2M_2BT_MASK; + } + + cp->subfeatures_supported = 0; + if (params->cs_without_fae_supported) + { + cp->subfeatures_supported |= + sys_cpu_to_le16(BT_HCI_LE_CS_SUBFEATURE_NO_TX_FAE_MASK); + } + + if (params->chsel_alg_3c_supported) + { + cp->subfeatures_supported |= + sys_cpu_to_le16(BT_HCI_LE_CS_SUBFEATURE_CHSEL_ALG_3C_MASK); + } + + if (params->pbr_from_rtt_sounding_seq_supported) + { + cp->subfeatures_supported |= + sys_cpu_to_le16( + BT_HCI_LE_CS_SUBFEATURE_PBR_FROM_RTT_SOUNDING_SEQ_MASK); + } + + if (params->cs_ipt_reflector) + { + cp->subfeatures_supported |= + sys_cpu_to_le16(BT_HCI_LE_CS_SUBFEATURE_CS_IPT_REFLECTOR_MASK); + } + + cp->t_ip1_times_supported = sys_cpu_to_le16(params->t_ip1_times_supported); + cp->t_ip2_times_supported = sys_cpu_to_le16(params->t_ip2_times_supported); + cp->t_fcs_times_supported = sys_cpu_to_le16(params->t_fcs_times_supported); + cp->t_pm_times_supported = sys_cpu_to_le16(params->t_pm_times_supported); + cp->t_sw_time_supported = params->t_sw_time; + cp->tx_snr_capability = params->tx_snr_capability; + + cp->t_ip2_ipt_times_supported = + sys_cpu_to_le16(params->t_ip2_ipt_times_supported); + cp->t_sw_ipt_time_supported = params->t_sw_ipt_time_supported; + + return + bt_hci_cmd_send_sync( + BT_HCI_OP_LE_CS_WRITE_CACHED_REMOTE_SUPPORTED_CAPABILITIES_V2, + buf, + NULL); +} + +int bt_le_cs_write_cached_remote_fae_table(struct bt_conn *conn, + int8_t remote_fae_table[72]) +{ + struct bt_hci_cp_le_write_cached_remote_fae_table *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + + cp->handle = sys_cpu_to_le16(conn->handle); + memcpy(cp->remote_fae_table, remote_fae_table, + sizeof(cp->remote_fae_table)); + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CS_WRITE_CACHED_REMOTE_FAE_TABLE, + buf, NULL); +} + +void bt_hci_le_cs_security_enable_complete(struct net_buf *buf) +{ + struct bt_conn *conn; + + struct bt_hci_evt_le_cs_security_enable_complete *evt; + + if (buf->len < sizeof(*evt)) + { + LOG_ERR("Unexpected end of buffer"); + return; + } + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + if (evt->status) + { + LOG_WRN("Security Enable failed with status 0x%02X", evt->status); + } + + conn = bt_conn_lookup_handle(sys_le16_to_cpu(evt->handle), + BT_CONN_TYPE_LE); + if (!conn) + { + LOG_ERR("Can't lookup conn handle when reading Security Enable " + "Complete " + "event"); + return; + } + + bt_conn_notify_cs_security_enable_available(conn, evt->status); + + bt_conn_unref(conn); +} + +void bt_hci_le_cs_procedure_enable_complete(struct net_buf *buf) +{ + struct bt_conn *conn; + + struct bt_hci_evt_le_cs_procedure_enable_complete *evt; + struct bt_conn_le_cs_procedure_enable_complete params; + + if (buf->len < sizeof(*evt)) + { + LOG_ERR("Unexpected end of buffer"); + return; + } + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + if (evt->status) + { + LOG_WRN("Procedure Enable failed with status 0x%02X", evt->status); + } + + conn = bt_conn_lookup_handle(sys_le16_to_cpu(evt->handle), + BT_CONN_TYPE_LE); + if (!conn) + { + LOG_ERR("Can't lookup conn handle when reading Procedure Enable " + "Complete event"); + return; + } + + if (evt->state == BT_HCI_OP_LE_CS_PROCEDURES_DISABLED) + { + struct net_buf *reassembly_buf = get_reassembly_buf(conn->handle, + false); + + if (reassembly_buf) + { + LOG_WRN("De-allocating a dangling reassembly buffer"); + free_reassembly_buf(&reassembly_buf); + } + } + + if (evt->status == BT_HCI_ERR_SUCCESS) + { + params.config_id = evt->config_id; + params.state = evt->state; + params.tone_antenna_config_selection = + evt->tone_antenna_config_selection; + params.selected_tx_power = evt->selected_tx_power; + params.subevent_len = sys_get_le24(evt->subevent_len); + params.subevents_per_event = evt->subevents_per_event; + params.subevent_interval = sys_le16_to_cpu(evt->subevent_interval); + params.event_interval = sys_le16_to_cpu(evt->event_interval); + params.procedure_interval = sys_le16_to_cpu(evt->procedure_interval); + params.procedure_count = sys_le16_to_cpu(evt->procedure_count); + params.max_procedure_len = sys_le16_to_cpu(evt->max_procedure_len); + + bt_conn_notify_cs_procedure_enable_available(conn, BT_HCI_ERR_SUCCESS, + ¶ms); + } + else + { + bt_conn_notify_cs_procedure_enable_available(conn, evt->status, NULL); + } + + bt_conn_unref(conn); +} + +#if defined(CONFIG_BT_CHANNEL_SOUNDING_TEST) +int bt_le_cs_stop_test(void) +{ + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CS_TEST_END, buf, NULL); +} + +void bt_hci_le_cs_test_end_complete(struct net_buf *buf) +{ + struct bt_hci_evt_le_cs_test_end_complete *evt; + + if (buf->len < sizeof(*evt)) + { + LOG_ERR("Unexpected end of buffer"); + return; + } + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + if (evt->status) + { + LOG_WRN("CS Test End failed with status 0x%02X", evt->status); + return; + } + + struct net_buf *reassembly_buf = + get_reassembly_buf(BT_HCI_LE_CS_TEST_CONN_HANDLE, + false); + + if (reassembly_buf) + { + LOG_WRN("De-allocating a dangling reassembly buffer"); + free_reassembly_buf(&reassembly_buf); + } + + if (cs_test_callbacks.le_cs_test_end_complete) + { + cs_test_callbacks.le_cs_test_end_complete(); + } +} +#endif /* CONFIG_BT_CHANNEL_SOUNDING_TEST */ + +void bt_le_cs_step_data_parse(struct net_buf_simple *step_data_buf, bool( + *func)(struct bt_le_cs_subevent_step *step, void *user_data), + void *user_data) +{ + if (!step_data_buf) + { + LOG_INF("Tried to parse empty step data."); + return; + } + + while (step_data_buf->len > 1) + { + struct bt_le_cs_subevent_step step; + + step.mode = net_buf_simple_pull_u8(step_data_buf); + step.channel = net_buf_simple_pull_u8(step_data_buf); + step.data_len = net_buf_simple_pull_u8(step_data_buf); + + if (step.data_len == 0) + { + LOG_WRN("Encountered zero-length step data."); + return; + } + + step.data = step_data_buf->data; + + if (step.data_len > step_data_buf->len) + { + LOG_WRN("Step data appears malformed."); + return; + } + + if (!func(&step, user_data)) + { + return; + } + + net_buf_simple_pull(step_data_buf, step.data_len); + } +} + +/* Bluetooth Core Specification 6.0, Table 4.13, Antenna Path Permutation for + * N_AP=2. The last element corresponds to extension slot + */ + +static const uint8_t antenna_path_lut_n_ap_2[2][3] = +{ + { + A1, + A2, + A2 + }, + + { + A2, + A1, + A1 + }, +}; + +/* Bluetooth Core Specification 6.0, Table 4.14, Antenna Path Permutation for + * N_AP=3. The last element corresponds to extension slot + */ + +static const uint8_t antenna_path_lut_n_ap_3[6][4] = +{ + { + A1, A2, A3, A3 + }, + + { + A2, A1, A3, A3 + }, + + { + A1, A3, A2, A2 + }, + + { + A3, A1, A2, A2 + }, + + { + A3, A2, A1, A1 + }, + + { + A2, A3, A1, A1 + }, +}; + +/* Bluetooth Core Specification 6.0, Table 4.15, Antenna Path Permutation for + * N_AP=4. The last element corresponds to extension slot + */ + +static const uint8_t antenna_path_lut_n_ap_4[24][5] = +{ + { + A1, A2, A3, A4, A4 + }, + + { + A2, A1, A3, A4, A4 + }, + + { + A1, A3, A2, A4, A4 + }, + + { + A3, A1, A2, A4, A4 + }, + + { + A3, A2, A1, A4, A4 + }, + + { + A2, A3, A1, A4, A4 + }, + + { + A1, A2, A4, A3, A3 + }, + + { + A2, A1, A4, A3, A3 + }, + + { + A1, A4, A2, A3, A3 + }, + + { + A4, A1, A2, A3, A3 + }, + + { + A4, A2, A1, A3, A3 + }, + + { + A2, A4, A1, A3, A3 + }, + + { + A1, A4, A3, A2, A2 + }, + + { + A4, A1, A3, A2, A2 + }, + + { + A1, A3, A4, A2, A2 + }, + + { + A3, A1, A4, A2, A2 + }, + + { + A3, A4, A1, A2, A2 + }, + + { + A4, A3, A1, A2, A2 + }, + + { + A4, A2, A3, A1, A1 + }, + + { + A2, A4, A3, A1, A1 + }, + + { + A4, A3, A2, A1, A1 + }, + + { + A3, A4, A2, A1, A1 + }, + + { + A3, A2, A4, A1, A1 + }, + + { + A2, A3, A4, A1, A1 + }, +}; + +int bt_le_cs_get_antenna_path( + uint8_t n_ap, uint8_t antenna_path_permutation_index, uint8_t tone_index) +{ + switch (n_ap) + { + case 1: + { + uint8_t antenna_path_permutations = 1; + uint8_t num_tones = n_ap + 1; /* one additional tone extension slot */ + + if (antenna_path_permutation_index >= antenna_path_permutations || + tone_index >= num_tones) + { + return -EINVAL; + } + + return A1; + } + + case 2: + { + if (antenna_path_permutation_index >= + ARRAY_SIZE(antenna_path_lut_n_ap_2) || + tone_index >= ARRAY_SIZE(antenna_path_lut_n_ap_2[0])) + { + return -EINVAL; + } + + return + antenna_path_lut_n_ap_2 + [antenna_path_permutation_index][tone_index]; + } + + case 3: + { + if (antenna_path_permutation_index >= + ARRAY_SIZE(antenna_path_lut_n_ap_3) || + tone_index >= ARRAY_SIZE(antenna_path_lut_n_ap_3[0])) + { + return -EINVAL; + } + + return + antenna_path_lut_n_ap_3 + [antenna_path_permutation_index][tone_index]; + } + + case 4: + { + if (antenna_path_permutation_index >= + ARRAY_SIZE(antenna_path_lut_n_ap_4) || + tone_index >= ARRAY_SIZE(antenna_path_lut_n_ap_4[0])) + { + return -EINVAL; + } + + return + antenna_path_lut_n_ap_4 + [antenna_path_permutation_index][tone_index]; + } + + default: + return -EINVAL; + } +} diff --git a/libs/zblue/host/data.c b/libs/zblue/host/data.c new file mode 100644 index 0000000000000..df21dcd5f63d7 --- /dev/null +++ b/libs/zblue/host/data.c @@ -0,0 +1,108 @@ +/**************************************************************************** + * libs/zblue/host/data.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017-2021 Nordic Semiconductor ASA + * Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#define LOG_LEVEL CONFIG_BT_HCI_CORE_LOG_LEVEL +LOG_MODULE_REGISTER(bt_data); + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_data_parse(struct net_buf_simple *ad, + bool (*func)(struct bt_data *data, void *user_data), + void *user_data) +{ + while (ad->len > 1) + { + struct bt_data data; + uint8_t len; + + len = net_buf_simple_pull_u8(ad); + if (len == 0U) + { + /* Early termination */ + + return; + } + + if (len > ad->len) + { + LOG_WRN("malformed advertising data %u / %u", len, ad->len); + return; + } + + data.type = net_buf_simple_pull_u8(ad); + data.data_len = len - 1; + data.data = ad->data; + + if (!func(&data, user_data)) + { + return; + } + + net_buf_simple_pull(ad, len - 1); + } +} + +size_t bt_data_get_len(const struct bt_data data[], size_t data_count) +{ + size_t total_len = 0; + + for (size_t i = 0; i < data_count; i++) + { + total_len += + sizeof(data[i].data_len) + sizeof(data[i].type) + + data[i].data_len; + } + + return total_len; +} + +size_t bt_data_serialize(const struct bt_data *input, uint8_t *output) +{ + if (input == NULL) + { + LOG_DBG("input is NULL"); + return 0; + } + + if (output == NULL) + { + LOG_DBG("output is NULL"); + return 0; + } + + uint8_t ad_data_len = input->data_len; + uint8_t data_len = ad_data_len + 1; + + output[0] = data_len; + output[1] = input->type; + + memcpy(&output[2], input->data, ad_data_len); + + return data_len + 1; +} diff --git a/libs/zblue/host/direction.c b/libs/zblue/host/direction.c new file mode 100644 index 0000000000000..6467440c8b076 --- /dev/null +++ b/libs/zblue/host/direction.c @@ -0,0 +1,1396 @@ +/**************************************************************************** + * libs/zblue/host/direction.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020-2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hci_core.h" +#include "conn_internal.h" +#include "direction_internal.h" + +LOG_MODULE_REGISTER(bt_df, CONFIG_BT_DF_LOG_LEVEL); + +/* @brief Antenna information for LE Direction Finding */ + +struct bt_le_df_ant_info +{ + /* Bitfield holding optional switching and sampling rates */ + + uint8_t switch_sample_rates; + + /* Available antennae number */ + + uint8_t num_ant; + + /* Maximum supported antennae switching pattern length */ + + uint8_t max_switch_pattern_len; + + /* Maximum length of CTE in 8[us] units */ + + uint8_t max_cte_len; +}; + +static struct bt_le_df_ant_info df_ant_info; +#if defined(CONFIG_BT_DF_CONNECTIONLESS_CTE_RX) || defined(CONFIG_BT_DF_CONNECTION_CTE_RX) +const static uint8_t + df_dummy_switch_pattern[BT_HCI_LE_SWITCH_PATTERN_LEN_MIN] = +{ + 0, 0 +}; +#endif /* CONFIG_BT_DF_CONNECTIONLESS_CTE_RX || CONFIG_BT_DF_CONNECTION_CTE_RX */ + +#define DF_AOD_TX_1US_SUPPORT(supp) (supp & BT_HCI_LE_1US_AOD_TX) +#define DF_AOD_RX_1US_SUPPORT(supp) (supp & BT_HCI_LE_1US_AOD_RX) +#define DF_AOA_RX_1US_SUPPORT(supp) (supp & BT_HCI_LE_1US_AOA_RX) + +#define DF_SAMPLING_ANTENNA_NUMBER_MIN 0x2 + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static uint8_t get_hci_cte_type(enum bt_df_cte_type type) +{ + switch (type) + { + case BT_DF_CTE_TYPE_AOA: + return BT_HCI_LE_AOA_CTE; + case BT_DF_CTE_TYPE_AOD_1US: + return BT_HCI_LE_AOD_CTE_1US; + case BT_DF_CTE_TYPE_AOD_2US: + return BT_HCI_LE_AOD_CTE_2US; + default: + LOG_ERR("Wrong CTE type"); + return BT_HCI_LE_NO_CTE; + } +} + +static int hci_df_set_cl_cte_tx_params(const struct bt_le_ext_adv *adv, + const struct bt_df_adv_cte_tx_param *params) +{ + struct bt_hci_cp_le_set_cl_cte_tx_params *cp; + struct net_buf *buf; + + /* If AoD is not enabled, ant_ids are ignored by controller: + * BT Core spec 5.2 Vol 4, Part E sec. 7.8.80. + */ + + if (params->cte_type == BT_DF_CTE_TYPE_AOD_1US || + params->cte_type == BT_DF_CTE_TYPE_AOD_2US) + { + if (!BT_FEAT_LE_ANT_SWITCH_TX_AOD(bt_dev.le.features)) + { + return -EINVAL; + } + + if (params->cte_type == BT_DF_CTE_TYPE_AOD_1US && + !DF_AOD_TX_1US_SUPPORT(df_ant_info.switch_sample_rates)) + { + return -EINVAL; + } + + if (params->num_ant_ids < BT_HCI_LE_SWITCH_PATTERN_LEN_MIN || + params->num_ant_ids > BT_HCI_LE_SWITCH_PATTERN_LEN_MAX + || !params->ant_ids) + { + return -EINVAL; + } + } + else if (params->cte_type != BT_DF_CTE_TYPE_AOA) + { + return -EINVAL; + } + + if (params->cte_len < BT_HCI_LE_CTE_LEN_MIN || + params->cte_len > BT_HCI_LE_CTE_LEN_MAX) + { + return -EINVAL; + } + + if (params->cte_count < BT_HCI_LE_CTE_COUNT_MIN || + params->cte_count > BT_HCI_LE_CTE_COUNT_MAX) + { + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = adv->handle; + cp->cte_len = params->cte_len; + cp->cte_type = get_hci_cte_type(params->cte_type); + cp->cte_count = params->cte_count; + + if (params->num_ant_ids) + { + uint8_t *dest_ant_ids = net_buf_add(buf, params->num_ant_ids); + + memcpy(dest_ant_ids, params->ant_ids, params->num_ant_ids); + cp->switch_pattern_len = params->num_ant_ids; + } + else + { + cp->switch_pattern_len = 0; + } + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_CL_CTE_TX_PARAMS, buf, NULL); +} + +/* @brief Function provides information about DF antennae number and + * controller capabilities related with Constant Tone Extension. + * + * @param[out] switch_sample_rates Optional switching and sampling + * rates. + * @param[out] num_ant Antennae number. + * @param[out] max_switch_pattern_len Maximum supported antennae switching + * patterns length. + * @param[out] max_cte_len Maximum length of CTE in 8[us] units. + * + * @return Zero in case of success, other value in case of failure. + */ + +static int hci_df_read_ant_info( + uint8_t *switch_sample_rates, uint8_t *num_ant, + uint8_t *max_switch_pattern_len, uint8_t *max_cte_len) +{ + DEBUGASSERT(switch_sample_rates); + DEBUGASSERT(num_ant); + DEBUGASSERT(max_switch_pattern_len); + DEBUGASSERT(max_cte_len); + + struct bt_hci_rp_le_read_ant_info *rp; + struct net_buf *rsp; + int err; + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_READ_ANT_INFO, NULL, &rsp); + if (err) + { + LOG_ERR("Failed to read antenna information"); + return err; + } + + rp = (void *)rsp->data; + + LOG_DBG("DF: sw. sample rates: %x ant num: %u , max sw. pattern len: %u, " + "max CTE len %d", + rp->switch_sample_rates, + rp->num_ant, + rp->max_switch_pattern_len, + rp->max_cte_len); + + *switch_sample_rates = rp->switch_sample_rates; + *num_ant = rp->num_ant; + *max_switch_pattern_len = rp->max_switch_pattern_len; + *max_cte_len = rp->max_cte_len; + + net_buf_unref(rsp); + + return 0; +} + +/* @brief Function handles send of HCI command to enable or disables CTE + * transmission for given advertising set. + * + * @param[in] adv Pointer to advertising set + * @param[in] enable Enable or disable CTE TX + * + * @return Zero in case of success, other value in case of failure. + */ + +static int hci_df_set_adv_cte_tx_enable(struct bt_le_ext_adv *adv, + bool enable) +{ + struct bt_hci_cp_le_set_cl_cte_tx_enable *cp; + struct bt_hci_cmd_state_set state; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + (void)memset(cp, 0, sizeof(*cp)); + + cp->handle = adv->handle; + cp->cte_enable = enable ? 1 : 0; + + bt_hci_cmd_state_set_init(buf, &state, adv->flags, BT_PER_ADV_CTE_ENABLED, + enable); + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_CL_CTE_TX_ENABLE, buf, NULL); +} + +#if defined(CONFIG_BT_DF_CONNECTIONLESS_CTE_RX) || defined(CONFIG_BT_DF_CONNECTION_CTE_RX) +static bool valid_cte_rx_common_params( + uint8_t cte_types, uint8_t slot_durations, uint8_t num_ant_ids, + const uint8_t *ant_ids) +{ + if (!(cte_types & BT_DF_CTE_TYPE_ALL)) + { + return false; + } + + if (cte_types & BT_DF_CTE_TYPE_AOA) + { + if (df_ant_info.num_ant < DF_SAMPLING_ANTENNA_NUMBER_MIN || + !BT_FEAT_LE_ANT_SWITCH_RX_AOA(bt_dev.le.features)) + { + return false; + } + + if (!(slot_durations == BT_HCI_LE_ANTENNA_SWITCHING_SLOT_2US || + (slot_durations == BT_HCI_LE_ANTENNA_SWITCHING_SLOT_1US && + DF_AOA_RX_1US_SUPPORT(df_ant_info.switch_sample_rates)))) + { + return false; + } + + if (num_ant_ids < BT_HCI_LE_SWITCH_PATTERN_LEN_MIN || + num_ant_ids > df_ant_info.max_switch_pattern_len || !ant_ids) + { + return false; + } + } + + return true; +} +#endif /* CONFIG_BT_DF_CONNECTIONLESS_CTE_RX || CONFIG_BT_DF_CONNECTION_CTE_RX */ + +#if defined(CONFIG_BT_DF_CONNECTIONLESS_CTE_RX) +static bool valid_cl_cte_rx_params( + const struct bt_df_per_adv_sync_cte_rx_param *params) +{ + if (params->max_cte_count > BT_HCI_LE_SAMPLE_CTE_COUNT_MAX) + { + return false; + } + + if (params->cte_types & BT_DF_CTE_TYPE_AOA) + { + return valid_cte_rx_common_params(params->cte_types, + params->slot_durations, + params->num_ant_ids, + params->ant_ids); + } + + return true; +} + +static int prepare_cl_cte_rx_enable_cmd_params(struct net_buf **buf, + struct bt_le_per_adv_sync *sync, + const struct bt_df_per_adv_sync_cte_rx_param *params, + bool enable) +{ + struct bt_hci_cp_le_set_cl_cte_sampling_enable *cp; + uint8_t switch_pattern_len; + + if (params->cte_types & BT_DF_CTE_TYPE_AOA) + { + switch_pattern_len = params->num_ant_ids; + } + else + { + switch_pattern_len = ARRAY_SIZE(df_dummy_switch_pattern); + } + + /* If CTE Rx is enabled, command parameters total length must include + * antenna ids, so command size if extended by num_and_ids. + */ + + *buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!(*buf)) + { + return -ENOBUFS; + } + + cp = net_buf_add(*buf, sizeof(*cp)); + (void)memset(cp, 0, sizeof(*cp)); + + cp->sync_handle = sys_cpu_to_le16(sync->handle); + cp->sampling_enable = enable ? 1 : 0; + + if (enable) + { + const uint8_t *ant_ids; + uint8_t *dest_ant_ids; + + cp->max_sampled_cte = params->max_cte_count; + + if (params->cte_types & BT_DF_CTE_TYPE_AOA) + { + cp->slot_durations = params->slot_durations; + ant_ids = params->ant_ids; + } + else + { + /* Those values are put here due to constraints from HCI command + * specification: Bluetooth Core Spec. 5.3 Vol 4,Part E, sec + * 7.8.82. There is no right way to successfully send the command + * to enable CTE receive for AoD mode (e.g. device equipped with + * single antenna). + */ + + cp->slot_durations = BT_HCI_LE_ANTENNA_SWITCHING_SLOT_2US; + ant_ids = &df_dummy_switch_pattern[0]; + } + + cp->switch_pattern_len = switch_pattern_len; + dest_ant_ids = net_buf_add(*buf, cp->switch_pattern_len); + memcpy(dest_ant_ids, ant_ids, cp->switch_pattern_len); + } + + return 0; +} + +static int hci_df_set_cl_cte_rx_enable( + struct bt_le_per_adv_sync *sync, bool enable, + const struct bt_df_per_adv_sync_cte_rx_param *params) +{ + struct bt_hci_rp_le_set_cl_cte_sampling_enable *rp; + struct bt_hci_cmd_state_set state; + struct net_buf *buf; + struct net_buf *rsp; + int err; + + if (enable) + { + if (!valid_cl_cte_rx_params(params)) + { + return -EINVAL; + } + } + + err = prepare_cl_cte_rx_enable_cmd_params(&buf, sync, params, enable); + if (err) + { + return err; + } + + bt_hci_cmd_state_set_init(buf, &state, sync->flags, + BT_PER_ADV_SYNC_CTE_ENABLED, enable); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_CL_CTE_SAMPLING_ENABLE, buf, + &rsp); + if (err) + { + return err; + } + + rp = (void *)rsp->data; + if (sync->handle != sys_le16_to_cpu(rp->sync_handle)) + { + err = -EIO; + } + else + { + sync->cte_types = (enable ? params->cte_types : 0); + } + + net_buf_unref(rsp); + + return err; +} + +int hci_df_prepare_connectionless_iq_report(struct net_buf *buf, + struct bt_df_per_adv_sync_iq_samples_report *report, + struct bt_le_per_adv_sync **per_adv_sync_to_report) +{ + struct bt_hci_evt_le_connectionless_iq_report *evt; + struct bt_le_per_adv_sync *per_adv_sync; + + if (buf->len < sizeof(*evt)) + { + LOG_ERR("Unexpected end of buffer"); + return -EINVAL; + } + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + + per_adv_sync = + bt_hci_per_adv_sync_lookup_handle( + sys_le16_to_cpu(evt->sync_handle)); + + if (!per_adv_sync) + { + LOG_ERR("Unknown handle 0x%04X for iq samples report", + sys_le16_to_cpu(evt->sync_handle)); + return -EINVAL; + } + + if (!atomic_test_bit(per_adv_sync->flags, BT_PER_ADV_SYNC_CTE_ENABLED)) + { + LOG_ERR("Received PA CTE report when CTE receive disabled"); + return -EINVAL; + } + + if (!(per_adv_sync->cte_types & BIT(evt->cte_type))) + { + LOG_DBG("CTE filtered out by cte_type: %u", evt->cte_type); + return -EINVAL; + } + + report->chan_idx = evt->chan_idx; + report->rssi = sys_le16_to_cpu(evt->rssi); + report->rssi_ant_id = evt->rssi_ant_id; + report->cte_type = BIT(evt->cte_type); + report->packet_status = evt->packet_status; + report->slot_durations = evt->slot_durations; + report->per_evt_counter = sys_le16_to_cpu(evt->per_evt_counter); + report->sample_type = BT_DF_IQ_SAMPLE_8_BITS_INT; + report->sample_count = evt->sample_count; + report->sample = &evt->sample[0]; + + *per_adv_sync_to_report = per_adv_sync; + + return 0; +} + +int hci_df_vs_prepare_connectionless_iq_report(struct net_buf *buf, + struct bt_df_per_adv_sync_iq_samples_report *report, + struct bt_le_per_adv_sync **per_adv_sync_to_report) +{ + struct bt_hci_evt_vs_le_connectionless_iq_report *evt; + struct bt_le_per_adv_sync *per_adv_sync; + + if (buf->len < sizeof(*evt)) + { + LOG_ERR("Unexpected end of buffer"); + return -EINVAL; + } + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + + per_adv_sync = + bt_hci_per_adv_sync_lookup_handle( + sys_le16_to_cpu(evt->sync_handle)); + + if (!per_adv_sync) + { + LOG_ERR("Unknown handle 0x%04X for iq samples report", + sys_le16_to_cpu(evt->sync_handle)); + return -EINVAL; + } + + if (!atomic_test_bit(per_adv_sync->flags, BT_PER_ADV_SYNC_CTE_ENABLED)) + { + LOG_ERR("Received PA CTE report when CTE receive disabled"); + return -EINVAL; + } + + if (!(per_adv_sync->cte_types & BIT(evt->cte_type))) + { + LOG_DBG("CTE filtered out by cte_type: %u", evt->cte_type); + return -EINVAL; + } + + report->chan_idx = evt->chan_idx; + report->rssi = sys_le16_to_cpu(evt->rssi); + report->rssi_ant_id = evt->rssi_ant_id; + report->cte_type = BIT(evt->cte_type); + report->packet_status = evt->packet_status; + report->slot_durations = evt->slot_durations; + report->per_evt_counter = sys_le16_to_cpu(evt->per_evt_counter); + report->sample_count = evt->sample_count; + report->sample_type = BT_DF_IQ_SAMPLE_16_BITS_INT; + report->sample16 = &evt->sample[0]; + + *per_adv_sync_to_report = per_adv_sync; + + return 0; +} +#endif /* CONFIG_BT_DF_CONNECTIONLESS_CTE_RX */ + +#if defined(CONFIG_BT_DF_CONNECTION_CTE_TX) +static bool valid_conn_cte_tx_params( + const struct bt_df_conn_cte_tx_param *params) +{ + if (!(params->cte_types & BT_DF_CTE_TYPE_ALL)) + { + return false; + } + + /* If AoD is not enabled, ant_ids are ignored by controller: + * BT Core spec 5.2 Vol 4, Part E sec. 7.8.84. + */ + + if ((params->cte_types & (BT_DF_CTE_TYPE_AOD_1US | + BT_DF_CTE_TYPE_AOD_2US)) && + (params->num_ant_ids < BT_HCI_LE_SWITCH_PATTERN_LEN_MIN || + params->num_ant_ids > BT_HCI_LE_SWITCH_PATTERN_LEN_MAX || + !params->ant_ids || + !BT_FEAT_LE_ANT_SWITCH_TX_AOD(bt_dev.le.features))) + { + return false; + } + + return true; +} + +static void prepare_conn_cte_tx_params_cmd( + struct net_buf *buf, const struct bt_conn *conn, + const struct bt_df_conn_cte_tx_param *params) +{ + struct bt_hci_cp_le_set_conn_cte_tx_params *cp; + uint8_t *ant_ids; + + cp = net_buf_add(buf, sizeof(*cp)); + (void)memset(cp, 0, sizeof(*cp)); + + cp->handle = sys_cpu_to_le16(conn->handle); + cp->cte_types = params->cte_types; + + if (params->cte_types & (BT_DF_CTE_TYPE_AOD_1US | BT_DF_CTE_TYPE_AOD_2US)) + { + cp->switch_pattern_len = params->num_ant_ids; + + ant_ids = net_buf_add(buf, cp->switch_pattern_len); + (void)memcpy(ant_ids, params->ant_ids, cp->switch_pattern_len); + } + else + { + cp->switch_pattern_len = 0U; + } +} + +/** + * @brief Function sets CTE parameters for connection object + * + * @param conn Connection object + * @param params CTE transmission parameters. + * + * @return Zero in case of success, other value in case of failure. + */ + +static int hci_df_set_conn_cte_tx_param(struct bt_conn *conn, + const struct bt_df_conn_cte_tx_param *params) +{ + struct bt_hci_rp_le_set_conn_cte_tx_params *rp; + struct bt_hci_cmd_state_set state; + struct net_buf *buf; + struct net_buf *rsp; + int err; + + /* If AoD is not enabled, ant_ids are ignored by controller: + * BT Core spec 5.2 Vol 4, Part E sec. 7.8.84. + */ + + if (!valid_conn_cte_tx_params(params)) + { + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + prepare_conn_cte_tx_params_cmd(buf, conn, params); + + /* CTE transmission parameters must be set only once for connection + * lifetime, hence the flag BT_CONN_CTE_TX_PARAMS_SET is always set to true + * and never set to false. + */ + + bt_hci_cmd_state_set_init(buf, &state, conn->flags, + BT_CONN_CTE_TX_PARAMS_SET, true); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_CONN_CTE_TX_PARAMS, buf, &rsp); + if (err) + { + return err; + } + + rp = (void *)rsp->data; + if (conn->handle != sys_le16_to_cpu(rp->handle)) + { + err = -EIO; + } + + net_buf_unref(rsp); + + return err; +} +#endif /* CONFIG_BT_DF_CONNECTION_CTE_TX */ + +#if defined(CONFIG_BT_DF_CONNECTION_CTE_RX) +static int prepare_conn_cte_rx_enable_cmd_params( + struct net_buf **buf, struct bt_conn *conn, + const struct bt_df_conn_cte_rx_param *params, bool enable) +{ + struct bt_hci_cp_le_set_conn_cte_rx_params *cp; + uint8_t switch_pattern_len; + + if (params->cte_types & BT_DF_CTE_TYPE_AOA) + { + switch_pattern_len = params->num_ant_ids; + } + else + { + switch_pattern_len = ARRAY_SIZE(df_dummy_switch_pattern); + } + + /* If CTE Rx is enabled, command parameters total length must include + * antenna ids, so command size if extended by num_and_ids. + */ + + *buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!(*buf)) + { + return -ENOBUFS; + } + + cp = net_buf_add(*buf, sizeof(*cp)); + (void)memset(cp, 0, sizeof(*cp)); + + cp->handle = sys_cpu_to_le16(conn->handle); + cp->sampling_enable = enable ? 1 : 0; + + if (enable) + { + const uint8_t *ant_ids; + uint8_t *dest_ant_ids; + + if (params->cte_types & BT_DF_CTE_TYPE_AOA) + { + cp->slot_durations = params->slot_durations; + ant_ids = params->ant_ids; + } + else + { + /* Those values are put here due to constraints from HCI command + * specification: Bluetooth Core Spec. 5.3 Vol 4,Part E, sec + * 7.8.85. There is no right way to successfully send the command + * to enable CTE receive for AoD mode (e.g. device equipped with + * single antenna). There is no CTE type in the parameters list, so + * controller is forced to check correctness of all parameters + * always. + */ + + cp->slot_durations = BT_HCI_LE_ANTENNA_SWITCHING_SLOT_2US; + ant_ids = &df_dummy_switch_pattern[0]; + } + + cp->switch_pattern_len = switch_pattern_len; + dest_ant_ids = net_buf_add(*buf, cp->switch_pattern_len); + (void)memcpy(dest_ant_ids, ant_ids, cp->switch_pattern_len); + } + + return 0; +} + +static int hci_df_set_conn_cte_rx_enable(struct bt_conn *conn, + bool enable, + const struct bt_df_conn_cte_rx_param *params) +{ + struct bt_hci_rp_le_set_conn_cte_rx_params *rp; + struct bt_hci_cmd_state_set state; + struct net_buf *buf; + struct net_buf *rsp; + int err; + + if (enable) + { + if (!valid_cte_rx_common_params(params->cte_types, + params->slot_durations, + params->num_ant_ids, params->ant_ids)) + { + return -EINVAL; + } + } + + err = prepare_conn_cte_rx_enable_cmd_params(&buf, conn, params, enable); + if (err) + { + return err; + } + + bt_hci_cmd_state_set_init(buf, &state, conn->flags, BT_CONN_CTE_RX_ENABLED, + enable); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_CONN_CTE_RX_PARAMS, buf, &rsp); + if (err) + { + return err; + } + + rp = (void *)rsp->data; + if (conn->handle != sys_le16_to_cpu(rp->handle)) + { + err = -EIO; + } + else + { + conn->cte_types = (enable ? params->cte_types : 0); + /* This flag is set once for connection object. It is never cleared + * because CTE RX params must be set at least once for connection + * object to successfully execute CTE REQ procedure. + */ + + atomic_set_bit(conn->flags, BT_CONN_CTE_RX_PARAMS_SET); + } + + net_buf_unref(rsp); + + return err; +} + +int hci_df_prepare_connection_iq_report(struct net_buf *buf, + struct bt_df_conn_iq_samples_report *report, + struct bt_conn **conn_to_report) +{ + struct bt_hci_evt_le_connection_iq_report *evt; + struct bt_conn *conn; + + if (buf->len < sizeof(*evt)) + { + LOG_ERR("Unexpected end of buffer"); + return -EINVAL; + } + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + + conn = bt_conn_lookup_handle(sys_le16_to_cpu(evt->conn_handle), + BT_CONN_TYPE_LE); + if (!conn) + { + LOG_ERR("Unknown conn handle 0x%04X for iq samples report", + sys_le16_to_cpu(evt->conn_handle)); + return -EINVAL; + } + + if (!atomic_test_bit(conn->flags, BT_CONN_CTE_RX_ENABLED)) + { + LOG_ERR("Received conn CTE report when CTE receive disabled"); + bt_conn_unref(conn); + return -EINVAL; + } + + if (!(conn->cte_types & BIT(evt->cte_type))) + { + LOG_DBG("CTE filtered out by cte_type: %u", evt->cte_type); + bt_conn_unref(conn); + return -EINVAL; + } + + report->err = BT_DF_IQ_REPORT_ERR_SUCCESS; + report->chan_idx = evt->data_chan_idx; + report->rx_phy = evt->rx_phy; + report->chan_idx = evt->data_chan_idx; + report->rssi = evt->rssi; + report->rssi_ant_id = evt->rssi_ant_id; + report->cte_type = BIT(evt->cte_type); + report->packet_status = evt->packet_status; + report->slot_durations = evt->slot_durations; + report->conn_evt_counter = sys_le16_to_cpu(evt->conn_evt_counter); + report->sample_type = BT_DF_IQ_SAMPLE_8_BITS_INT; + report->sample_count = evt->sample_count; + report->sample = evt->sample; + + *conn_to_report = conn; + + return 0; +} + +int hci_df_vs_prepare_connection_iq_report(struct net_buf *buf, + struct bt_df_conn_iq_samples_report *report, + struct bt_conn **conn_to_report) +{ + struct bt_hci_evt_vs_le_connection_iq_report *evt; + struct bt_conn *conn; + + if (buf->len < sizeof(*evt)) + { + LOG_ERR("Unexpected end of buffer"); + return -EINVAL; + } + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + + conn = bt_conn_lookup_handle(sys_le16_to_cpu(evt->conn_handle), + BT_CONN_TYPE_LE); + if (!conn) + { + LOG_ERR("Unknown conn handle 0x%04X for iq samples report", + sys_le16_to_cpu(evt->conn_handle)); + return -EINVAL; + } + + if (!atomic_test_bit(conn->flags, BT_CONN_CTE_RX_ENABLED)) + { + LOG_ERR("Received conn CTE report when CTE receive disabled"); + bt_conn_unref(conn); + return -EINVAL; + } + + if (!(conn->cte_types & BIT(evt->cte_type))) + { + LOG_DBG("CTE filtered out by cte_type: %u", evt->cte_type); + bt_conn_unref(conn); + return -EINVAL; + } + + report->err = BT_DF_IQ_REPORT_ERR_SUCCESS; + report->chan_idx = evt->data_chan_idx; + report->rx_phy = evt->rx_phy; + report->chan_idx = evt->data_chan_idx; + report->rssi = evt->rssi; + report->rssi_ant_id = evt->rssi_ant_id; + report->cte_type = BIT(evt->cte_type); + report->packet_status = evt->packet_status; + report->slot_durations = evt->slot_durations; + report->conn_evt_counter = sys_le16_to_cpu(evt->conn_evt_counter); + report->sample_type = BT_DF_IQ_SAMPLE_16_BITS_INT; + report->sample_count = evt->sample_count; + report->sample16 = evt->sample; + + *conn_to_report = conn; + + return 0; +} +#endif /* CONFIG_BT_DF_CONNECTION_CTE_RX */ + +#if defined(CONFIG_BT_DF_CONNECTION_CTE_REQ) +static bool valid_cte_req_params(const struct bt_conn *conn, + uint8_t cte_type, uint8_t cte_length) +{ + if (!(conn->cte_types & cte_type)) + { + return false; + } + + if (cte_length < BT_HCI_LE_CTE_LEN_MIN || + cte_length > BT_HCI_LE_CTE_LEN_MAX) + { + return false; + } + + return true; +} + +static void prepare_conn_cte_req_enable_cmd_params( + struct net_buf *buf, const struct bt_conn *conn, + const struct bt_df_conn_cte_req_params *params, bool enable) +{ + struct bt_hci_cp_le_conn_cte_req_enable *cp; + + cp = net_buf_add(buf, sizeof(*cp)); + (void)memset(cp, 0, sizeof(*cp)); + + cp->handle = sys_cpu_to_le16(conn->handle); + cp->enable = enable ? 1 : 0; + + if (enable) + { + cp->cte_request_interval = params->interval; + cp->requested_cte_length = sys_cpu_to_le16(params->cte_length); + cp->requested_cte_type = get_hci_cte_type(params->cte_type); + } +} + +static int hci_df_set_conn_cte_req_enable(struct bt_conn *conn, + bool enable, + const struct bt_df_conn_cte_req_params *params) +{ + struct bt_hci_cp_le_conn_cte_req_enable *rp; + struct bt_hci_cmd_state_set state; + struct net_buf *buf; + struct net_buf *rsp; + int err; + + if (enable && !valid_cte_req_params(conn, params->cte_type, + params->cte_length)) + { + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + prepare_conn_cte_req_enable_cmd_params(buf, conn, params, enable); + + bt_hci_cmd_state_set_init(buf, + &state, + conn->flags, + BT_CONN_CTE_REQ_ENABLED, + + enable); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_CONN_CTE_REQ_ENABLE, buf, &rsp); + if (err) + { + return err; + } + + rp = (void *)rsp->data; + if (conn->handle != sys_le16_to_cpu(rp->handle)) + { + err = -EIO; + } + + net_buf_unref(rsp); + + return err; +} + +int hci_df_prepare_conn_cte_req_failed(struct net_buf *buf, + struct bt_df_conn_iq_samples_report *report, + struct bt_conn **conn_to_report) +{ + struct bt_hci_evt_le_cte_req_failed *evt; + struct bt_conn *conn; + + if (buf->len < sizeof(*evt)) + { + LOG_ERR("Unexpected end of buffer"); + return -EINVAL; + } + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + + conn = bt_conn_lookup_handle(sys_le16_to_cpu(evt->conn_handle), + BT_CONN_TYPE_LE); + if (!conn) + { + LOG_ERR("Unknown conn handle 0x%04X for iq samples report", + sys_le16_to_cpu(evt->conn_handle)); + return -EINVAL; + } + + if (!atomic_test_bit(conn->flags, BT_CONN_CTE_REQ_ENABLED)) + { + LOG_ERR("Received conn CTE request notification when CTE REQ " + "disabled"); + bt_conn_unref(conn); + return -EINVAL; + } + + (void)memset(report, 0U, sizeof(*report)); + + if (evt->status == BT_HCI_CTE_REQ_STATUS_RSP_WITHOUT_CTE) + { + report->err = BT_DF_IQ_REPORT_ERR_NO_CTE; + } + else + { + report->err = BT_DF_IQ_REPORT_ERR_PEER_REJECTED; + } + + *conn_to_report = conn; + + return 0; +} +#endif /* CONFIG_BT_DF_CONNECTION_CTE_REQ */ + +#if defined(CONFIG_BT_DF_CONNECTION_CTE_RSP) +static void prepare_conn_cte_rsp_enable_cmd_params( + struct net_buf *buf, const struct bt_conn *conn, bool enable) +{ + struct bt_hci_cp_le_conn_cte_rsp_enable *cp; + + cp = net_buf_add(buf, sizeof(*cp)); + (void)memset(cp, 0, sizeof(*cp)); + + cp->handle = sys_cpu_to_le16(conn->handle); + cp->enable = enable ? 1U : 0U; +} + +static int hci_df_set_conn_cte_rsp_enable(struct bt_conn *conn, bool enable) +{ + struct bt_hci_rp_le_conn_cte_rsp_enable *rp; + struct bt_hci_cmd_state_set state; + struct net_buf *buf; + struct net_buf *rsp; + int err; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + prepare_conn_cte_rsp_enable_cmd_params(buf, conn, enable); + + bt_hci_cmd_state_set_init(buf, &state, conn->flags, + BT_CONN_CTE_RSP_ENABLED, + enable); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_CONN_CTE_RSP_ENABLE, buf, &rsp); + if (err) + { + return err; + } + + rp = (void *)rsp->data; + if (conn->handle != sys_le16_to_cpu(rp->handle)) + { + err = -EIO; + } + + net_buf_unref(rsp); + + return err; +} +#endif /* CONFIG_BT_DF_CONNECTION_CTE_RSP */ + +/* @brief Function initializes Direction Finding in Host + * + * @return Zero in case of success, other value in case of failure. + */ + +int le_df_init(void) +{ + uint8_t max_switch_pattern_len; + uint8_t switch_sample_rates; + uint8_t max_cte_len; + uint8_t num_ant; + int err; + + err = hci_df_read_ant_info(&switch_sample_rates, &num_ant, + &max_switch_pattern_len, &max_cte_len); + if (err) + { + return err; + } + + df_ant_info.max_switch_pattern_len = max_switch_pattern_len; + df_ant_info.switch_sample_rates = switch_sample_rates; + df_ant_info.max_cte_len = max_cte_len; + df_ant_info.num_ant = num_ant; + + LOG_DBG("DF initialized."); + return 0; +} + +int bt_df_set_adv_cte_tx_param(struct bt_le_ext_adv *adv, + const struct bt_df_adv_cte_tx_param *params) +{ + DEBUGASSERT(adv); + DEBUGASSERT(params); + + int err; + + if (!BT_FEAT_LE_CONNECTIONLESS_CTE_TX(bt_dev.le.features)) + { + return -ENOTSUP; + } + + /* Check if BT_ADV_PARAMS_SET is set, because it implies the set + * has already been created. + */ + + if (!atomic_test_bit(adv->flags, BT_ADV_PARAMS_SET)) + { + return -EINVAL; + } + + if (atomic_test_bit(adv->flags, BT_PER_ADV_CTE_ENABLED)) + { + return -EINVAL; + } + + err = hci_df_set_cl_cte_tx_params(adv, params); + if (err) + { + return err; + } + + atomic_set_bit(adv->flags, BT_PER_ADV_CTE_PARAMS_SET); + + return 0; +} + +static int bt_df_set_adv_cte_tx_enabled(struct bt_le_ext_adv *adv, + bool enable) +{ + if (!atomic_test_bit(adv->flags, BT_PER_ADV_PARAMS_SET)) + { + return -EINVAL; + } + + if (!atomic_test_bit(adv->flags, BT_PER_ADV_CTE_PARAMS_SET)) + { + return -EINVAL; + } + + if (enable == atomic_test_bit(adv->flags, BT_PER_ADV_CTE_ENABLED)) + { + return -EALREADY; + } + + return hci_df_set_adv_cte_tx_enable(adv, enable); +} + +int bt_df_adv_cte_tx_enable(struct bt_le_ext_adv *adv) +{ + DEBUGASSERT(adv); + return bt_df_set_adv_cte_tx_enabled(adv, true); +} + +int bt_df_adv_cte_tx_disable(struct bt_le_ext_adv *adv) +{ + DEBUGASSERT(adv); + return bt_df_set_adv_cte_tx_enabled(adv, false); +} + +#if defined(CONFIG_BT_DF_CONNECTIONLESS_CTE_RX) +static int +bt_df_set_per_adv_sync_cte_rx_enable(struct bt_le_per_adv_sync *sync, + bool enable, + const struct bt_df_per_adv_sync_cte_rx_param *params) +{ + if (!BT_FEAT_LE_CONNECTIONLESS_CTE_RX(bt_dev.le.features)) + { + return -ENOTSUP; + } + + if (!atomic_test_bit(sync->flags, BT_PER_ADV_SYNC_SYNCED)) + { + return -EINVAL; + } + + if (!enable && !atomic_test_bit(sync->flags, BT_PER_ADV_SYNC_CTE_ENABLED)) + { + return -EALREADY; + } + + return hci_df_set_cl_cte_rx_enable(sync, enable, params); +} + +int bt_df_per_adv_sync_cte_rx_enable(struct bt_le_per_adv_sync *sync, + const struct bt_df_per_adv_sync_cte_rx_param *params) +{ + if (!sync) + { + return -EINVAL; + } + + if (!params) + { + return -EINVAL; + } + + return bt_df_set_per_adv_sync_cte_rx_enable(sync, true, params); +} + +int bt_df_per_adv_sync_cte_rx_disable(struct bt_le_per_adv_sync *sync) +{ + if (!sync) + { + return -EINVAL; + } + + return bt_df_set_per_adv_sync_cte_rx_enable(sync, false, NULL); +} +#endif /* CONFIG_BT_DF_CONNECTIONLESS_CTE_RX */ + +#if defined(CONFIG_BT_DF_CONNECTION_CTE_RX) +static int bt_df_set_conn_cte_rx_enable(struct bt_conn *conn, + bool enable, + const struct bt_df_conn_cte_rx_param *params) +{ + if (!BT_FEAT_LE_RX_CTE(bt_dev.le.features)) + { + LOG_WRN("Receiving Constant Tone Extensions is not supported"); + return -ENOTSUP; + } + + if (conn->state != BT_CONN_CONNECTED) + { + LOG_ERR("not connected!"); + return -ENOTCONN; + } + + return hci_df_set_conn_cte_rx_enable(conn, enable, params); +} + +int bt_df_conn_cte_rx_enable(struct bt_conn *conn, + const struct bt_df_conn_cte_rx_param *params) +{ + if (!conn) + { + return -EINVAL; + } + + if (!params) + { + return -EINVAL; + } + + return bt_df_set_conn_cte_rx_enable(conn, true, params); +} + +int bt_df_conn_cte_rx_disable(struct bt_conn *conn) +{ + if (!conn) + { + return -EINVAL; + } + + return bt_df_set_conn_cte_rx_enable(conn, false, NULL); +} +#endif /* CONFIG_BT_DF_CONNECTION_CTE_RX */ + +#if defined(CONFIG_BT_DF_CONNECTION_CTE_TX) +int bt_df_set_conn_cte_tx_param(struct bt_conn *conn, + const struct bt_df_conn_cte_tx_param *params) +{ + if (!conn) + { + return -EINVAL; + } + + if (!params) + { + return -EINVAL; + } + + if (conn->state != BT_CONN_CONNECTED) + { + LOG_ERR("not connected!"); + return -ENOTCONN; + } + + if (atomic_test_bit(conn->flags, BT_CONN_CTE_RSP_ENABLED)) + { + LOG_WRN("CTE response procedure is enabled"); + return -EINVAL; + } + + return hci_df_set_conn_cte_tx_param(conn, params); +} +#endif /* CONFIG_BT_DF_CONNECTION_CTE_TX */ + +#if defined(CONFIG_BT_DF_CONNECTION_CTE_REQ) +static int bt_df_set_conn_cte_req_enable(struct bt_conn *conn, + bool enable, + const struct bt_df_conn_cte_req_params *params) +{ + if (!BT_FEAT_LE_CONNECTION_CTE_REQ(bt_dev.le.features)) + { + LOG_WRN("Constant Tone Extensions request procedure is not supported"); + return -ENOTSUP; + } + + if (conn->state != BT_CONN_CONNECTED) + { + LOG_ERR("not connected!"); + return -ENOTCONN; + } + + if (!atomic_test_bit(conn->flags, BT_CONN_CTE_RX_PARAMS_SET)) + { + LOG_ERR("Can't start CTE request procedure before CTE RX params " + "setup"); + return -EINVAL; + } + + return hci_df_set_conn_cte_req_enable(conn, enable, params); +} + +int bt_df_conn_cte_req_enable(struct bt_conn *conn, + const struct bt_df_conn_cte_req_params *params) +{ + if (!conn) + { + return -EINVAL; + } + + if (!params) + { + return -EINVAL; + } + + return bt_df_set_conn_cte_req_enable(conn, true, params); +} + +int bt_df_conn_cte_req_disable(struct bt_conn *conn) +{ + if (!conn) + { + return -EINVAL; + } + + return bt_df_set_conn_cte_req_enable(conn, false, NULL); +} +#endif /* CONFIG_BT_DF_CONNECTION_CTE_REQ */ + +#if defined(CONFIG_BT_DF_CONNECTION_CTE_RSP) +static int bt_df_set_conn_cte_rsp_enable(struct bt_conn *conn, bool enable) +{ + if (!conn) + { + return -EINVAL; + } + + if (!BT_FEAT_LE_CONNECTION_CTE_RESP(bt_dev.le.features)) + { + LOG_WRN("CTE response procedure is not supported"); + return -ENOTSUP; + } + + if (conn->state != BT_CONN_CONNECTED) + { + LOG_ERR("not connected"); + return -ENOTCONN; + } + + if (!atomic_test_bit(conn->flags, BT_CONN_CTE_TX_PARAMS_SET)) + { + LOG_ERR("Can't start CTE response procedure before CTE TX params " + "setup"); + return -EINVAL; + } + + return hci_df_set_conn_cte_rsp_enable(conn, enable); +} + +int bt_df_conn_cte_rsp_enable(struct bt_conn *conn) +{ + return bt_df_set_conn_cte_rsp_enable(conn, true); +} + +int bt_df_conn_cte_rsp_disable(struct bt_conn *conn) +{ + return bt_df_set_conn_cte_rsp_enable(conn, false); +} +#endif /* CONFIG_BT_DF_CONNECTION_CTE_RSP */ diff --git a/libs/zblue/host/direction_internal.h b/libs/zblue/host/direction_internal.h new file mode 100644 index 0000000000000..cf419368183cd --- /dev/null +++ b/libs/zblue/host/direction_internal.h @@ -0,0 +1,39 @@ +/**************************************************************************** + * libs/zblue/host/direction_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +/* Performs initialization of Direction Finding in Host */ + +int le_df_init(void); + +int hci_df_prepare_connectionless_iq_report(struct net_buf *buf, + struct bt_df_per_adv_sync_iq_samples_report *report, + struct bt_le_per_adv_sync **per_adv_sync_to_report); +int hci_df_vs_prepare_connectionless_iq_report(struct net_buf *buf, + struct bt_df_per_adv_sync_iq_samples_report *report, + struct bt_le_per_adv_sync **per_adv_sync_to_report); +int hci_df_prepare_connection_iq_report(struct net_buf *buf, + struct bt_df_conn_iq_samples_report *report, + struct bt_conn **conn_to_report); +int hci_df_vs_prepare_connection_iq_report(struct net_buf *buf, + struct bt_df_conn_iq_samples_report *report, + struct bt_conn **conn_to_report); +int hci_df_prepare_conn_cte_req_failed(struct net_buf *buf, + struct bt_df_conn_iq_samples_report *report, + struct bt_conn **conn_to_report); diff --git a/libs/zblue/host/ecc.c b/libs/zblue/host/ecc.c new file mode 100644 index 0000000000000..4d0aa81339648 --- /dev/null +++ b/libs/zblue/host/ecc.c @@ -0,0 +1,521 @@ +/**************************************************************************** + * libs/zblue/host/ecc.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Nordic Semiconductor ASA + * Copyright (c) 2015 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "long_wq.h" +#include "ecc.h" +#include "hci_core.h" + +#define LOG_LEVEL CONFIG_BT_HCI_CORE_LOG_LEVEL +LOG_MODULE_REGISTER(bt_ecc); + +static uint8_t pub_key[BT_PUB_KEY_LEN]; +static struct list_node pub_key_cb_slist = LIST_INITIAL_VALUE( + pub_key_cb_slist); +static bt_dh_key_cb_t dh_key_cb; + +/* Incremented by bt_pub_key_hci_disrupted() under the host lock. A public + * key generation that was in flight at that point publishes nothing when it + * completes: the disruption completed the callbacks registered with it, and + * any request made since has re-queued the work item. + */ + +static uint32_t pub_key_disruptions; + +enum +{ + PENDING_PUB_KEY, + PENDING_DHKEY, + + /* Total number of flags - must be at the end of the enum */ + + NUM_FLAGS, +}; + +static atomic_define(flags, NUM_FLAGS); + +static struct +{ + uint8_t private_key_be[BT_PRIV_KEY_LEN]; + + union + { + uint8_t public_key_be[BT_PUB_KEY_LEN]; + uint8_t dhkey_be[BT_DH_KEY_LEN]; + }; +} ecc; + +/* based on Core Specification 4.2 Vol 3. Part H 2.3.5.6.1 */ + +static const uint8_t debug_private_key_be[BT_PRIV_KEY_LEN] = +{ + 0x3f, 0x49, 0xf6, 0xd4, 0xa3, 0xc5, 0x5f, 0x38, 0x74, 0xc9, 0xb3, + 0xe3, 0xd2, 0x10, 0x3f, 0x50, 0x4a, 0xff, 0x60, 0x7b, 0xeb, 0x40, + 0xb7, 0x99, 0x58, 0x99, 0xb8, 0xa6, 0xcd, 0x3c, 0x1a, 0xbd, +}; + +static const uint8_t debug_public_key[BT_PUB_KEY_LEN] = +{ + /* X */ + + 0xe6, 0x9d, 0x35, 0x0e, 0x48, 0x01, 0x03, 0xcc, 0xdb, 0xfd, 0xf4, + 0xac, 0x11, 0x91, 0xf4, + 0xef, 0xb9, 0xa5, 0xf9, 0xe9, 0xa7, 0x83, 0x2c, 0x5e, 0x2c, 0xbe, + 0x97, 0xf2, 0xd2, 0x03, + 0xb0, 0x20, + + /* Y */ + + 0x8b, 0xd2, 0x89, 0x15, 0xd0, 0x8e, 0x1c, 0x74, 0x24, 0x30, 0xed, + 0x8f, 0xc2, 0x45, 0x63, + 0x76, 0x5c, 0x15, 0x52, 0x5a, 0xbf, 0x9a, 0x32, 0x63, 0x6d, 0xeb, + 0x2a, 0x65, 0x49, 0x9c, + 0x80, 0xdc +}; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +bool bt_pub_key_is_debug(uint8_t *cmp_pub_key) +{ + return memcmp(cmp_pub_key, debug_public_key, BT_PUB_KEY_LEN) == 0; +} + +bool bt_pub_key_is_valid(const uint8_t key[BT_PUB_KEY_LEN]) +{ + uint8_t key_be[BT_PUB_KEY_LEN + 1]; + psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT; + psa_status_t ret; + psa_key_id_t handle; + + psa_set_key_type(&attr, + PSA_KEY_TYPE_ECC_PUBLIC_KEY(PSA_ECC_FAMILY_SECP_R1)); + psa_set_key_bits(&attr, 256); + psa_set_key_usage_flags(&attr, PSA_KEY_USAGE_DERIVE); + psa_set_key_algorithm(&attr, PSA_ALG_ECDH); + + /* PSA expects secp256r1 public key to start with a predefined 0x04 byte */ + + key_be[0] = 0x04; + sys_memcpy_swap(&key_be[1], key, BT_PUB_KEY_COORD_LEN); + sys_memcpy_swap(&key_be[1 + BT_PUB_KEY_COORD_LEN], + &key[BT_PUB_KEY_COORD_LEN], + BT_PUB_KEY_COORD_LEN); + + ret = psa_import_key(&attr, key_be, sizeof(key_be), &handle); + psa_reset_key_attributes(&attr); + + if (ret == PSA_SUCCESS) + { + psa_destroy_key(handle); + return true; + } + + LOG_ERR("psa_import_key() returned status %d", ret); + return false; +} + +static void set_key_attributes(psa_key_attributes_t *attr) +{ + psa_set_key_type(attr, PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); + psa_set_key_bits(attr, 256); + psa_set_key_usage_flags(attr, PSA_KEY_USAGE_EXPORT | PSA_KEY_USAGE_DERIVE); + psa_set_key_algorithm(attr, PSA_ALG_ECDH); +} + +static void generate_pub_key(struct k_work *work) +{ + psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT; + struct bt_pub_key_cb *cb; + struct bt_pub_key_cb *next; + struct list_node cbs; + psa_key_id_t key_id; + uint8_t tmp_pub_key_buf[BT_PUB_KEY_LEN + 1]; + size_t tmp_len; + uint32_t disruptions; + int err; + psa_status_t ret; + + bt_dev_lock(); + disruptions = pub_key_disruptions; + bt_dev_unlock(); + + set_key_attributes(&attr); + + ret = psa_generate_key(&attr, &key_id); + if (ret != PSA_SUCCESS) + { + LOG_ERR("Failed to generate ECC key %d", ret); + err = BT_HCI_ERR_UNSPECIFIED; + goto done; + } + + ret = psa_export_public_key(key_id, tmp_pub_key_buf, + sizeof(tmp_pub_key_buf), + &tmp_len); + if (ret != PSA_SUCCESS) + { + LOG_ERR("Failed to export ECC public key %d", ret); + err = BT_HCI_ERR_UNSPECIFIED; + goto done; + } + + /* secp256r1 PSA exported public key has an extra 0x04 predefined byte at + * the beginning of the buffer which is not part of the coordinate so we + * remove that. + */ + + memcpy(ecc.public_key_be, &tmp_pub_key_buf[1], BT_PUB_KEY_LEN); + + ret = psa_export_key(key_id, ecc.private_key_be, BT_PRIV_KEY_LEN, + &tmp_len); + if (ret != PSA_SUCCESS) + { + LOG_ERR("Failed to export ECC private key %d", ret); + err = BT_HCI_ERR_UNSPECIFIED; + goto done; + } + + ret = psa_destroy_key(key_id); + if (ret != PSA_SUCCESS) + { + LOG_ERR("Failed to destroy ECC key ID %d", ret); + err = BT_HCI_ERR_UNSPECIFIED; + goto done; + } + + err = 0; + +done: + /* Publish the key, detach the registered callbacks and clear the + * pending state under the host lock, then invoke the callbacks without it, + * so that the lock is never held across application callbacks. + */ + + bt_dev_lock(); + + if (disruptions != pub_key_disruptions) + { + /* Disrupted while in flight: the disruption completed the + * registered callbacks, and any request made since has re-queued this + * work item, so leave the list and the pending state to that run. + */ + + bt_dev_unlock(); + return; + } + + if (err == 0) + { + sys_memcpy_swap(pub_key, ecc.public_key_be, BT_PUB_KEY_COORD_LEN); + sys_memcpy_swap(&pub_key[BT_PUB_KEY_COORD_LEN], + &ecc.public_key_be[BT_PUB_KEY_COORD_LEN], + BT_PUB_KEY_COORD_LEN); + atomic_set_bit(bt_dev.flags, BT_DEV_HAS_PUB_KEY); + } + + list_initialize(&cbs); + list_merge(&cbs, &pub_key_cb_slist); + list_initialize(&pub_key_cb_slist); + atomic_clear_bit(flags, PENDING_PUB_KEY); + bt_dev_unlock(); + + list_for_every_container_safe(&cbs, cb, next, node) + { + if (cb->func) + { + cb->func(err ? NULL : pub_key); + } + } +} + +K_WORK_DEFINE(pub_key_work, generate_pub_key); + +static void generate_dh_key(struct k_work *work) +{ + uint8_t dhkey[BT_DH_KEY_LEN]; + bt_dh_key_cb_t cb; + int err; + + psa_key_attributes_t attr = PSA_KEY_ATTRIBUTES_INIT; + psa_key_id_t key_id; + psa_status_t ret; + /* PSA expects secp256r1 public key to start with a predefined 0x04 byte + * at the beginning the buffer. + */ + + uint8_t tmp_pub_key_buf[BT_PUB_KEY_LEN + 1] = + { + 0x04 + }; + + size_t tmp_len; + + set_key_attributes(&attr); + + const uint8_t *priv_key = + (IS_ENABLED(CONFIG_BT_USE_DEBUG_KEYS) ? + debug_private_key_be : ecc.private_key_be); + + ret = psa_import_key(&attr, priv_key, BT_PRIV_KEY_LEN, &key_id); + if (ret != PSA_SUCCESS) + { + err = -EIO; + LOG_ERR("Failed to import the private key for key agreement %d", ret); + goto exit; + } + + memcpy(&tmp_pub_key_buf[1], ecc.public_key_be, BT_PUB_KEY_LEN); + ret = psa_raw_key_agreement(PSA_ALG_ECDH, key_id, tmp_pub_key_buf, + sizeof(tmp_pub_key_buf), + ecc.dhkey_be, BT_DH_KEY_LEN, &tmp_len); + if (ret != PSA_SUCCESS) + { + err = -EIO; + LOG_ERR("Raw key agreement failed %d", ret); + goto exit; + } + + ret = psa_destroy_key(key_id); + if (ret != PSA_SUCCESS) + { + LOG_ERR("Failed to destroy the key %d", ret); + err = -EIO; + goto exit; + } + + err = 0; + +exit: + /* Take the callback and the result and clear the pending state under + * the host lock, then invoke the callback without it. + */ + + bt_dev_lock(); + cb = dh_key_cb; + dh_key_cb = NULL; + if (err == 0) + { + sys_memcpy_swap(dhkey, ecc.dhkey_be, sizeof(ecc.dhkey_be)); + } + + atomic_clear_bit(flags, PENDING_DHKEY); + bt_dev_unlock(); + + if (cb != NULL) + { + cb(err ? NULL : dhkey); + } +} + +K_WORK_DEFINE(dh_key_work, generate_dh_key); + +int bt_pub_key_gen(struct bt_pub_key_cb *new_cb) +{ + struct bt_pub_key_cb *cb; + + if (IS_ENABLED(CONFIG_BT_USE_DEBUG_KEYS)) + { + atomic_set_bit(bt_dev.flags, BT_DEV_HAS_PUB_KEY); + DEBUGASSERT(new_cb->func != NULL); + new_cb->func(debug_public_key); + return 0; + } + + if (!new_cb) + { + return -EINVAL; + } + + /* The callback list and the pending state are only accessed under + * the host lock (see generate_pub_key()). + */ + + bt_dev_lock(); + + list_for_every_container(&pub_key_cb_slist, cb, node) + { + if (cb == new_cb) + { + bt_dev_unlock(); + LOG_DBG("Callback already registered"); + return -EALREADY; + } + } + + if (atomic_test_bit(flags, PENDING_DHKEY)) + { + bt_dev_unlock(); + LOG_WRN("Busy performing another ECDH operation"); + return -EBUSY; + } + + list_add_head(&pub_key_cb_slist, &new_cb->node); + + if (atomic_test_and_set_bit(flags, PENDING_PUB_KEY)) + { + bt_dev_unlock(); + return 0; + } + + atomic_clear_bit(bt_dev.flags, BT_DEV_HAS_PUB_KEY); + + bt_dev_unlock(); + + if (IS_ENABLED(CONFIG_BT_LONG_WQ)) + { + bt_long_wq_submit(&pub_key_work); + } + else + { + k_work_submit(&pub_key_work); + } + + return 0; +} + +void bt_pub_key_hci_disrupted(void) +{ + struct bt_pub_key_cb *cb; + struct bt_pub_key_cb *next; + struct list_node cbs; + + bt_dev_lock(); + pub_key_disruptions++; + list_initialize(&cbs); + list_merge(&cbs, &pub_key_cb_slist); + list_initialize(&pub_key_cb_slist); + atomic_clear_bit(flags, PENDING_PUB_KEY); + bt_dev_unlock(); + + list_for_every_container_safe(&cbs, cb, next, node) + { + if (cb->func) + { + cb->func(NULL); + } + } +} + +const uint8_t *bt_pub_key_get(void) +{ + if (IS_ENABLED(CONFIG_BT_USE_DEBUG_KEYS)) + { + return debug_public_key; + } + + if (atomic_test_bit(bt_dev.flags, BT_DEV_HAS_PUB_KEY)) + { + return pub_key; + } + + return NULL; +} + +int bt_dh_key_gen(const uint8_t remote_pk[BT_PUB_KEY_LEN], bt_dh_key_cb_t cb) +{ + /* The pending state and the callback are only accessed under the + * host lock; the shared key storage is written under it here and then + * owned by the worker until it clears the pending state (see + * generate_dh_key()). + */ + + bt_dev_lock(); + + if (dh_key_cb == cb) + { + bt_dev_unlock(); + return -EALREADY; + } + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_HAS_PUB_KEY)) + { + bt_dev_unlock(); + return -EADDRNOTAVAIL; + } + + if (dh_key_cb || atomic_test_bit(flags, PENDING_PUB_KEY) || + atomic_test_and_set_bit(flags, PENDING_DHKEY)) + { + bt_dev_unlock(); + return -EBUSY; + } + + dh_key_cb = cb; + + /* Convert X and Y coordinates from little-endian to + * big-endian (expected by the crypto API). + */ + + sys_memcpy_swap(ecc.public_key_be, remote_pk, BT_PUB_KEY_COORD_LEN); + sys_memcpy_swap(&ecc.public_key_be[BT_PUB_KEY_COORD_LEN], + &remote_pk[BT_PUB_KEY_COORD_LEN], + BT_PUB_KEY_COORD_LEN); + + bt_dev_unlock(); + + if (IS_ENABLED(CONFIG_BT_LONG_WQ)) + { + bt_long_wq_submit(&dh_key_work); + } + else + { + k_work_submit(&dh_key_work); + } + + return 0; +} + +#ifdef ZTEST_UNITTEST +uint8_t const *bt_ecc_get_public_key(void) +{ + return pub_key; +} + +uint8_t const *bt_ecc_get_internal_debug_public_key(void) +{ + return debug_public_key; +} + +struct list_node *bt_ecc_get_pub_key_cb_slist(void) +{ + return &pub_key_cb_slist; +} + +bt_dh_key_cb_t *bt_ecc_get_dh_key_cb(void) +{ + return &dh_key_cb; +} +#endif /* ZTEST_UNITTEST */ diff --git a/libs/zblue/host/ecc.h b/libs/zblue/host/ecc.h new file mode 100644 index 0000000000000..a396287803175 --- /dev/null +++ b/libs/zblue/host/ecc.h @@ -0,0 +1,132 @@ +/**************************************************************************** + * libs/zblue/host/ecc.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* ecc.h - ECDH helpers */ + +/* Copyright (c) 2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include + +/** Key size used in Bluetooth's ECC domain. */ +#define BT_ECC_KEY_SIZE 32 +/** Length of a Bluetooth ECC public key coordinate. */ +#define BT_PUB_KEY_COORD_LEN (BT_ECC_KEY_SIZE) +/** Length of a Bluetooth ECC public key. */ +#define BT_PUB_KEY_LEN (2 * (BT_PUB_KEY_COORD_LEN)) +/** Length of a Bluetooth ECC private key. */ +#define BT_PRIV_KEY_LEN (BT_ECC_KEY_SIZE) +/** Length of a Bluetooth Diffie-Hellman key. */ +#define BT_DH_KEY_LEN (BT_ECC_KEY_SIZE) + +/* @brief Container for public key callback */ + +struct bt_pub_key_cb +{ + /** @brief Callback type for Public Key generation. + * + * Used to notify of the local public key or that the local key is not + * available (either because of a failure to read it or because it is + * being regenerated). + * + * @param key The local public key, or NULL in case of no key. + */ + + void (*func)(const uint8_t key[BT_PUB_KEY_LEN]); + + /* Internal */ + + struct list_node node; +}; + +/* @brief Check if public key is equal to the debug public key. + * + * Compare the Public key to the Bluetooth specification defined debug + * public key. + * + * @param cmp_pub_key The public key to compare. + * + * @return True if the public key is the debug public key. + */ + +bool bt_pub_key_is_debug(uint8_t *cmp_pub_key); + +/* @brief Check if public key is valid. + * + * Verify that the public key is valid, e.g. that its coordinates lie on the + * elliptic curve. + * + * @param key The public key to validate. + * + * @return True if the public key is valid. + */ + +bool bt_pub_key_is_valid(const uint8_t key[BT_PUB_KEY_LEN]); + +/* @brief Generate a new Public Key. + * + * Generate a new ECC Public Key. Provided cb must persists until callback + * is called. Callee adds the callback structure to a linked list. + * Registering multiple callbacks requires multiple calls to + * bt_pub_key_gen() and separate callback structures. This method cannot be + * called directly from result callback. After calling all the registered + * callbacks the linked list is cleared. + * + * @param cb Callback to notify the new key. + * + * @return Zero on success or negative error code otherwise + */ + +int bt_pub_key_gen(struct bt_pub_key_cb *cb); + +/* @brief Cleanup public key callbacks when HCI is disrupted. + * + * Clear the pub_key_cb_slist and clear the BT_DEV_PUB_KEY_BUSY flag. + */ + +void bt_pub_key_hci_disrupted(void); + +/* @brief Get the current Public Key. + * + * Get the current ECC Public Key. + * + * @return Current key, or NULL if not available. + */ + +const uint8_t *bt_pub_key_get(void); + +/* @typedef bt_dh_key_cb_t + * @brief Callback type for DH Key calculation. + * + * Used to notify of the calculated DH Key. + * + * @param key The DH Key, or NULL in case of failure. + */ + +typedef void (*bt_dh_key_cb_t)(const uint8_t key[BT_DH_KEY_LEN]); + +/* @brief Calculate a DH Key from a remote Public Key. + * + * Calculate a DH Key from the remote Public Key. + * + * @param remote_pk Remote Public Key. + * @param cb Callback to notify the calculated key. + * + * @return Zero on success or negative error code otherwise + */ + +int bt_dh_key_gen(const uint8_t remote_pk[BT_PUB_KEY_LEN], + bt_dh_key_cb_t cb); diff --git a/libs/zblue/host/gatt.c b/libs/zblue/host/gatt.c new file mode 100644 index 0000000000000..28940b5445ddd --- /dev/null +++ b/libs/zblue/host/gatt.c @@ -0,0 +1,8092 @@ +/**************************************************************************** + * libs/zblue/host/gatt.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* gatt.c - Generic Attribute Profile handling */ + +/* Copyright (c) 2015-2016 Intel Corporation + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include "sys/types.h" +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(CONFIG_BT_GATT_CACHING) +#include +#include +#include +#include +#endif /* CONFIG_BT_GATT_CACHING */ + +#include "att_internal.h" +#include "conn_internal.h" +#include "common/bt_str.h" +#include "gatt_internal.h" +#include "hci_core.h" +#include "keys.h" +#include "long_wq.h" +#include "l2cap_internal.h" +#include "settings.h" +#include "smp.h" + +#define LOG_LEVEL CONFIG_BT_GATT_LOG_LEVEL +LOG_MODULE_REGISTER(bt_gatt); + +#define SC_TIMEOUT MSEC2TICK(10) +#define DB_HASH_TIMEOUT MSEC2TICK(10) + +static uint16_t last_static_handle; + +/* Persistent storage format for GATT CCC */ + +struct ccc_store +{ + uint16_t handle; + uint16_t value; +}; + +struct gatt_sub +{ + uint8_t id; + bt_addr_le_t peer; + struct list_node list; +}; + +#if defined(CONFIG_BT_GATT_CLIENT) +#define SUB_MAX (CONFIG_BT_MAX_PAIRED + CONFIG_BT_MAX_CONN) +#else +#define SUB_MAX 0 +#endif /* CONFIG_BT_GATT_CLIENT */ + +/* Entry x is free for reuse whenever (subscriptions[x].peer == + * BT_ADDR_LE_ANY). Invariant: (list_is_empty(subscriptions[x].list)) <=> + * (subscriptions[x].peer == BT_ADDR_LE_ANY). + */ + +static struct gatt_sub subscriptions[SUB_MAX]; +static struct list_node callback_list = LIST_INITIAL_VALUE(callback_list); + +#if defined(CONFIG_BT_GATT_DYNAMIC_DB) +static struct list_node db = LIST_INITIAL_VALUE(db); +#endif /* CONFIG_BT_GATT_DYNAMIC_DB */ + +enum gatt_global_flags +{ + GATT_INITIALIZED, + GATT_SERVICE_INITIALIZED, + + GATT_NUM_FLAGS, +}; + +static atomic_define(gatt_flags, GATT_NUM_FLAGS); + +#if defined(CONFIG_BT_GAP_PERIPHERAL_PREF_PARAMS) +/* This checks if the range entered is valid */ + +BUILD_ASSERT(!(CONFIG_BT_PERIPHERAL_PREF_MIN_INT > 3200 && + CONFIG_BT_PERIPHERAL_PREF_MIN_INT < 0xffff)); +BUILD_ASSERT(!(CONFIG_BT_PERIPHERAL_PREF_MAX_INT > 3200 && + CONFIG_BT_PERIPHERAL_PREF_MAX_INT < 0xffff)); +BUILD_ASSERT(!(CONFIG_BT_PERIPHERAL_PREF_TIMEOUT > 3200 && + CONFIG_BT_PERIPHERAL_PREF_TIMEOUT < 0xffff)); +BUILD_ASSERT((CONFIG_BT_PERIPHERAL_PREF_MIN_INT == 0xffff) || + (CONFIG_BT_PERIPHERAL_PREF_MIN_INT <= + CONFIG_BT_PERIPHERAL_PREF_MAX_INT)); +BUILD_ASSERT((CONFIG_BT_PERIPHERAL_PREF_TIMEOUT * 4U) > + ((1U + CONFIG_BT_PERIPHERAL_PREF_LATENCY) * + CONFIG_BT_PERIPHERAL_PREF_MAX_INT)); +#endif +struct sc_data +{ + uint16_t start; + uint16_t end; +} __packed; + +struct gatt_sc_cfg +{ + uint8_t id; + bt_addr_le_t peer; + struct + { + uint16_t start; + uint16_t end; + } data; +}; + +#if defined(CONFIG_BT_GATT_SERVICE_CHANGED) +#define SC_CFG_MAX (CONFIG_BT_MAX_PAIRED + CONFIG_BT_MAX_CONN) +#else +#define SC_CFG_MAX 0 +#endif +static struct gatt_sc_cfg sc_cfg[SC_CFG_MAX]; +BUILD_ASSERT(sizeof(struct sc_data) == sizeof(sc_cfg[0].data)); + +enum +{ + SC_RANGE_CHANGED, /* SC range changed */ + SC_INDICATE_PENDING, /* SC indicate pending */ + SC_LOAD, /* SC has been loaded from settings */ + + DB_HASH_VALID, /* Database hash needs to be calculated */ + DB_HASH_LOAD, /* Database hash loaded from settings. */ + DB_HASH_LOAD_PROC, /* DB hash loaded from settings has been processed. */ + + /* Total number of flags - must be at the end of the enum */ + + SC_NUM_FLAGS, +}; + +#if defined(CONFIG_BT_GATT_SERVICE_CHANGED) +static struct gatt_sc +{ + struct bt_gatt_indicate_params params; + uint16_t start; + uint16_t end; + struct k_work_delayable work; + + atomic_define(flags, SC_NUM_FLAGS); +} gatt_sc; +#endif /* defined(CONFIG_BT_GATT_SERVICE_CHANGED) */ + +#if defined(CONFIG_BT_GATT_CACHING) +static struct db_hash +{ + uint8_t hash[16]; +#if defined(CONFIG_BT_SETTINGS) + uint8_t stored_hash[16]; +#endif + struct k_work_delayable work; + struct k_work_sync sync; +} db_hash; +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static struct gatt_sc_cfg *find_sc_cfg(uint8_t id, const bt_addr_le_t *addr) +{ + LOG_DBG("id: %u, addr: %s", id, bt_addr_le_str(addr)); + + for (size_t i = 0; i < ARRAY_SIZE(sc_cfg); i++) + { + if (id == sc_cfg[i].id && bt_addr_le_eq(&sc_cfg[i].peer, addr)) + { + return &sc_cfg[i]; + } + } + + return NULL; +} + +static void sc_store(struct gatt_sc_cfg *cfg) +{ + int err; + + err = bt_settings_store_sc(cfg->id, &cfg->peer, &cfg->data, + sizeof(cfg->data)); + if (err) + { + LOG_ERR("failed to store SC (err %d)", err); + return; + } + + LOG_DBG("stored SC for %s (0x%04x-0x%04x)", bt_addr_le_str(&cfg->peer), + cfg->data.start, + cfg->data.end); +} + +static void clear_sc_cfg(struct gatt_sc_cfg *cfg) +{ + memset(cfg, 0, sizeof(*cfg)); +} + +static int bt_gatt_clear_sc(uint8_t id, const bt_addr_le_t *addr) +{ + struct gatt_sc_cfg *cfg; + + cfg = find_sc_cfg(id, (bt_addr_le_t *)addr); + if (!cfg) + { + return 0; + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + int err; + + err = bt_settings_delete_sc(cfg->id, &cfg->peer); + if (err) + { + LOG_ERR("failed to delete SC (err %d)", err); + } + else + { + LOG_DBG("deleted SC for %s", bt_addr_le_str(&cfg->peer)); + } + } + + clear_sc_cfg(cfg); + + return 0; +} + +static void sc_clear(struct bt_conn *conn) +{ + if (bt_le_bond_exists(conn->id, &conn->le.dst)) + { + int err; + + err = bt_gatt_clear_sc(conn->id, &conn->le.dst); + if (err) + { + LOG_ERR("Failed to clear SC %d", err); + } + } + else + { + struct gatt_sc_cfg *cfg; + + cfg = find_sc_cfg(conn->id, &conn->le.dst); + if (cfg) + { + clear_sc_cfg(cfg); + } + } +} + +static void sc_reset(struct gatt_sc_cfg *cfg) +{ + LOG_DBG("peer %s", bt_addr_le_str(&cfg->peer)); + + memset(&cfg->data, 0, sizeof(cfg->data)); + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + sc_store(cfg); + } +} + +static bool update_range(uint16_t *start, + uint16_t *end, uint16_t new_start, uint16_t new_end) +{ + LOG_DBG("start 0x%04x end 0x%04x new_start 0x%04x new_end 0x%04x", *start, + *end, new_start, + new_end); + + DEBUGASSERT(new_start <= new_end); + + /* Check if inside existing range */ + + if (new_start >= *start && new_end <= *end) + { + return false; + } + + /* Update range */ + + if (*start > new_start) + { + *start = new_start; + } + + if (*end < new_end) + { + *end = new_end; + } + + return true; +} + +static void sc_save(uint8_t id, + bt_addr_le_t *peer, uint16_t start, uint16_t end) +{ + struct gatt_sc_cfg *cfg; + bool modified = false; + + LOG_DBG("peer %s start 0x%04x end 0x%04x", bt_addr_le_str(peer), start, + end); + + cfg = find_sc_cfg(id, peer); + if (!cfg) + { + /* Find and initialize a free sc_cfg entry */ + + cfg = find_sc_cfg(BT_ID_DEFAULT, BT_ADDR_LE_ANY); + if (!cfg) + { + LOG_ERR("unable to save SC: no cfg left"); + return; + } + + cfg->id = id; + bt_addr_le_copy(&cfg->peer, peer); + } + + /* Check if there is any change stored */ + + if (!(cfg->data.start || cfg->data.end)) + { + cfg->data.start = start; + cfg->data.end = end; + modified = true; + goto done; + } + + modified = update_range(&cfg->data.start, &cfg->data.end, start, end); + +done: + if (IS_ENABLED(CONFIG_BT_SETTINGS) && modified && + bt_le_bond_exists(cfg->id, &cfg->peer)) + { + sc_store(cfg); + } +} + +static ssize_t sc_ccc_cfg_write( + struct bt_conn *conn, const struct bt_gatt_attr *attr, uint16_t value) +{ + LOG_DBG("value 0x%04x", value); + + if ((value & BT_GATT_CCC_INDICATE) != 0U) + { + /* Create a new SC configuration entry if subscribed; the CCC + * value is a bit field and Reserved bits are ignored. + */ + + sc_save(conn->id, &conn->le.dst, 0, 0); + } + else + { + sc_clear(conn); + } + + return sizeof(value); +} + +static struct bt_gatt_ccc_managed_user_data sc_ccc = + BT_GATT_CCC_MANAGED_USER_DATA_INIT(NULL, sc_ccc_cfg_write, NULL); + +/* Do not shuffle the values in this enum, they are used as bit offsets when + * saving the CF flags to NVS (i.e. NVS persists between FW upgrades). + */ + +enum +{ + CF_CHANGE_AWARE, /* Client is changed aware */ + CF_DB_HASH_READ, /* The client has read the database hash */ + + /* Total number of flags - must be at the end of the enum */ + + CF_NUM_FLAGS, +}; + +#define CF_BIT_ROBUST_CACHING 0 +#define CF_BIT_EATT 1 +#define CF_BIT_NOTIFY_MULTI 2 +#define CF_BIT_LAST CF_BIT_NOTIFY_MULTI + +#define CF_NUM_BITS (CF_BIT_LAST + 1) +#define CF_NUM_BYTES ((CF_BIT_LAST / 8) + 1) +#define CF_FLAGS_STORE_LEN 1 + +#define CF_ROBUST_CACHING(_cfg) (_cfg->data[0] & BIT(CF_BIT_ROBUST_CACHING)) +#define CF_EATT(_cfg) (_cfg->data[0] & BIT(CF_BIT_EATT)) +#define CF_NOTIFY_MULTI(_cfg) (_cfg->data[0] & BIT(CF_BIT_NOTIFY_MULTI)) + +struct gatt_cf_cfg +{ + uint8_t id; + bt_addr_le_t peer; + uint8_t data[CF_NUM_BYTES]; + atomic_define(flags, CF_NUM_FLAGS); +}; + +#if defined(CONFIG_BT_GATT_CACHING) +#define CF_CFG_MAX (CONFIG_BT_MAX_PAIRED + CONFIG_BT_MAX_CONN) +#else +#define CF_CFG_MAX 0 +#endif /* CONFIG_BT_GATT_CACHING */ + +static struct gatt_cf_cfg cf_cfg[CF_CFG_MAX] = +{ +}; + +static void clear_cf_cfg(struct gatt_cf_cfg *cfg) +{ + bt_addr_le_copy(&cfg->peer, BT_ADDR_LE_ANY); + memset(cfg->data, 0, sizeof(cfg->data)); + atomic_set(cfg->flags, 0); +} + +enum delayed_store_flags +{ + DELAYED_STORE_CCC, + DELAYED_STORE_CF, + DELAYED_STORE_NUM_FLAGS +}; + +#if defined(CONFIG_BT_SETTINGS_DELAYED_STORE) +static void gatt_delayed_store_enqueue( + uint8_t id, const bt_addr_le_t *peer_addr, enum delayed_store_flags flag); +#endif + +bool bt_gatt_is_db_hash_valid(void) +{ +#if defined(CONFIG_BT_GATT_SERVICE_CHANGED) + return atomic_test_bit(gatt_sc.flags, DB_HASH_VALID); +#else + return false; +#endif +} + +#if defined(CONFIG_BT_GATT_CACHING) +static bool set_change_aware_no_store(struct gatt_cf_cfg *cfg, bool aware) +{ + bool changed; + + if (aware) + { + changed = !atomic_test_and_set_bit(cfg->flags, CF_CHANGE_AWARE); + } + else + { + changed = atomic_test_and_clear_bit(cfg->flags, CF_CHANGE_AWARE); + } + + LOG_DBG("peer is now change-%saware", aware ? "" : "un"); + + return changed; +} + +static void set_change_aware(struct gatt_cf_cfg *cfg, bool aware) +{ + bool changed = set_change_aware_no_store(cfg, aware); + +#if defined(CONFIG_BT_SETTINGS_DELAYED_STORE) + if (changed) + { + gatt_delayed_store_enqueue(cfg->id, &cfg->peer, DELAYED_STORE_CF); + } +#else + (void)changed; +#endif +} + +static int bt_gatt_store_cf(uint8_t id, const bt_addr_le_t *peer); + +static void set_all_change_unaware(void) +{ +#if defined(CONFIG_BT_SETTINGS) + /* Mark all bonded peers as change-unaware. + * - Can be called when not in a connection with said peers + * - Doesn't have any effect when no bonds are in memory. This is the + * case when the device has just booted and `settings_load` hasn't yet + * been called. + * - Expensive to call, as it will write the new status to settings + * right away. + */ + + for (size_t i = 0; i < ARRAY_SIZE(cf_cfg); i++) + { + struct gatt_cf_cfg *cfg = &cf_cfg[i]; + + if (!bt_addr_le_eq(&cfg->peer, BT_ADDR_LE_ANY)) + { + set_change_aware_no_store(cfg, false); + bt_gatt_store_cf(cfg->id, &cfg->peer); + } + } +#endif /* CONFIG_BT_SETTINGS */ +} + +static struct gatt_cf_cfg *find_cf_cfg(struct bt_conn *conn) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cf_cfg); i++) + { + struct gatt_cf_cfg *cfg = &cf_cfg[i]; + + if (!conn) + { + if (bt_addr_le_eq(&cfg->peer, BT_ADDR_LE_ANY)) + { + return cfg; + } + } + else if (bt_conn_is_peer_addr_le(conn, cfg->id, &cfg->peer)) + { + return cfg; + } + } + + return NULL; +} + +static ssize_t cf_read( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct gatt_cf_cfg *cfg; + uint8_t data[1] = + { + }; + + cfg = find_cf_cfg(conn); + if (cfg) + { + memcpy(data, cfg->data, sizeof(data)); + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, data, sizeof(data)); +} + +static bool cf_set_value(struct gatt_cf_cfg *cfg, + const uint8_t *value, uint16_t len) +{ + uint16_t i; + + /* Validate the bits */ + + for (i = 0U; i <= CF_BIT_LAST && (i / 8) < len; i++) + { + if ((cfg->data[i / 8] & BIT(i % 8)) && !(value[i / 8] & BIT(i % 8))) + { + /* A client shall never clear a bit it has set */ + + return false; + } + } + + /* Set the bits for each octet */ + + for (i = 0U; i < len && i < CF_NUM_BYTES; i++) + { + if (i == (CF_NUM_BYTES - 1)) + { + cfg->data[i] |= value[i] & BIT_MASK(CF_NUM_BITS % BITS_PER_BYTE); + } + else + { + cfg->data[i] |= value[i]; + } + + LOG_DBG("byte %u: data 0x%02x value " + "0x%02x", i, cfg->data[i], value[i]); + } + + return true; +} + +static ssize_t cf_write( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + struct gatt_cf_cfg *cfg; + const uint8_t *value = buf; + + if (offset > sizeof(cfg->data)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (offset + len > sizeof(cfg->data)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + cfg = find_cf_cfg(conn); + if (!cfg) + { + cfg = find_cf_cfg(NULL); + } + + if (!cfg) + { + LOG_WRN("No space to store Client Supported Features"); + return BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES); + } + + LOG_DBG("handle 0x%04x len %u", attr->handle, len); + + if (!cf_set_value(cfg, value, len)) + { + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + + bt_addr_le_copy(&cfg->peer, &conn->le.dst); + cfg->id = conn->id; + set_change_aware(cfg, true); + + return len; +} + +struct gen_hash_state +{ + psa_mac_operation_t operation; + psa_key_id_t key; + int err; +}; + +static int db_hash_setup(struct gen_hash_state *state, uint8_t *key) +{ + psa_key_attributes_t key_attr = PSA_KEY_ATTRIBUTES_INIT; + psa_status_t ret; + + psa_set_key_type(&key_attr, PSA_KEY_TYPE_AES); + psa_set_key_bits(&key_attr, 128); + psa_set_key_usage_flags(&key_attr, PSA_KEY_USAGE_SIGN_MESSAGE); + psa_set_key_algorithm(&key_attr, PSA_ALG_CMAC); + + ret = psa_import_key(&key_attr, key, 16, &(state->key)); + if (ret != PSA_SUCCESS) + { + LOG_ERR("Unable to import the key for AES CMAC %d", ret); + return -EIO; + } + + memset(&state->operation, 0, sizeof(state->operation)); + + ret = psa_mac_sign_setup(&(state->operation), state->key, PSA_ALG_CMAC); + if (ret != PSA_SUCCESS) + { + LOG_ERR("CMAC operation init failed %d", ret); + + ret = psa_destroy_key(state->key); + if (ret != PSA_SUCCESS) + { + LOG_ERR("key destroy failed %d", ret); + } + + return -EIO; + } + + return 0; +} + +static int db_hash_update(struct gen_hash_state *state, + uint8_t *data, size_t len) +{ + psa_status_t ret = psa_mac_update(&(state->operation), data, len); + + if (ret != PSA_SUCCESS) + { + LOG_ERR("CMAC update failed %d", ret); + return -EIO; + } + + return 0; +} + +static int db_hash_finish(struct gen_hash_state *state) +{ + size_t mac_length; + psa_status_t ret = psa_mac_sign_finish(&(state->operation), db_hash.hash, + 16, + &mac_length); + + psa_destroy_key(state->key); + + if (ret != PSA_SUCCESS) + { + LOG_ERR("CMAC finish failed %d", ret); + return -EIO; + } + + return 0; +} + +union hash_attr_value +{ + /* Bluetooth Core Specification Version 5.3 | Vol 3, Part G + * Table 3.1: Service declaration + */ + + union + { + uint16_t uuid16; + uint8_t uuid128[BT_UUID_SIZE_128]; + } + + __packed service; + /* Bluetooth Core Specification Version 5.3 | Vol 3, Part G + * Table 3.2: Include declaration + */ + + struct + { + uint16_t attribute_handle; + uint16_t end_group_handle; + uint16_t uuid16; + } + + __packed inc; + /* Bluetooth Core Specification Version 5.3 | Vol 3, Part G + * Table 3.3: Characteristic declaration + */ + + struct + { + uint8_t properties; + uint16_t value_handle; + union + { + uint16_t uuid16; + uint8_t uuid128[BT_UUID_SIZE_128]; + } __packed; + } + + __packed chrc; + /* Bluetooth Core Specification Version 5.3 | Vol 3, Part G + * Table 3.5: Characteristic Properties bit field + */ + + struct + { + uint16_t properties; + } + + __packed cep; +} __packed; + +static uint8_t gen_hash_m(const struct bt_gatt_attr *attr, + uint16_t handle, void *user_data) +{ + struct gen_hash_state *state = user_data; + struct bt_uuid_16 *u16; + uint8_t data[sizeof(union hash_attr_value)]; + ssize_t len; + uint16_t value; + + if (attr->uuid->type != BT_UUID_TYPE_16) + { + return BT_GATT_ITER_CONTINUE; + } + + u16 = (struct bt_uuid_16 *)attr->uuid; + + switch (u16->val) + { + /* Attributes to hash: handle + UUID + value */ + + case BT_UUID_GATT_PRIMARY_VAL: + case BT_UUID_GATT_SECONDARY_VAL: + case BT_UUID_GATT_INCLUDE_VAL: + case BT_UUID_GATT_CHRC_VAL: + case BT_UUID_GATT_CEP_VAL: + value = sys_cpu_to_le16(handle); + if (db_hash_update(state, (uint8_t *)&value, sizeof(handle)) != 0) + { + state->err = -EINVAL; + return BT_GATT_ITER_STOP; + } + + value = sys_cpu_to_le16(u16->val); + if (db_hash_update(state, (uint8_t *)&value, sizeof(u16->val)) != 0) + { + state->err = -EINVAL; + return BT_GATT_ITER_STOP; + } + + len = attr->read(NULL, attr, data, sizeof(data), 0); + if (len < 0) + { + state->err = len; + return BT_GATT_ITER_STOP; + } + + if (db_hash_update(state, data, len) != 0) + { + state->err = -EINVAL; + return BT_GATT_ITER_STOP; + } + + break; + + /* Attributes to hash: handle + UUID */ + + case BT_UUID_GATT_CUD_VAL: + case BT_UUID_GATT_CCC_VAL: + case BT_UUID_GATT_SCC_VAL: + case BT_UUID_GATT_CPF_VAL: + case BT_UUID_GATT_CAF_VAL: + value = sys_cpu_to_le16(handle); + if (db_hash_update(state, (uint8_t *)&value, sizeof(handle)) != 0) + { + state->err = -EINVAL; + return BT_GATT_ITER_STOP; + } + + value = sys_cpu_to_le16(u16->val); + if (db_hash_update(state, (uint8_t *)&value, sizeof(u16->val)) != 0) + { + state->err = -EINVAL; + return BT_GATT_ITER_STOP; + } + + break; + default: + return BT_GATT_ITER_CONTINUE; + } + + return BT_GATT_ITER_CONTINUE; +} + +static void db_hash_store(void) +{ +#if defined(CONFIG_BT_SETTINGS) + int err; + + err = bt_settings_store_hash(&db_hash.hash, sizeof(db_hash.hash)); + if (err) + { + LOG_ERR("Failed to save Database Hash (err %d)", err); + } + + LOG_DBG("Database Hash stored"); +#endif /* CONFIG_BT_SETTINGS */ +} + +static void db_hash_gen(void) +{ + uint8_t key[16] = + { + }; + + struct gen_hash_state state; + + if (db_hash_setup(&state, key) != 0) + { + return; + } + + bt_gatt_foreach_attr(0x0001, 0xffff, gen_hash_m, &state); + + if (db_hash_finish(&state) != 0) + { + return; + } + +/* Core 5.1 does not state the endianness of the hash. However Vol 3, Part + * F, 3.3.1 says that multi-octet Characteristic Values shall be LE unless + * otherwise defined. PTS expects hash to be in little endianness as well. + * bt_smp_aes_cmac calculates the hash in big endianness so we have to + * swap. + */ + + sys_mem_swap(db_hash.hash, sizeof(db_hash.hash)); + + LOG_HEXDUMP_DBG(db_hash.hash, sizeof(db_hash.hash), "Hash: "); + + atomic_set_bit(gatt_sc.flags, DB_HASH_VALID); +} + +static void sc_indicate(uint16_t start, uint16_t end); + +static void do_db_hash(void) +{ + bool new_hash = !atomic_test_bit(gatt_sc.flags, DB_HASH_VALID); + + if (new_hash) + { + db_hash_gen(); + } + +#if defined(CONFIG_BT_SETTINGS) + bool hash_loaded_from_settings = atomic_test_bit(gatt_sc.flags, + DB_HASH_LOAD); + bool already_processed = atomic_test_bit(gatt_sc.flags, DB_HASH_LOAD_PROC); + + if (!hash_loaded_from_settings) + { + /* we want to generate the hash, but not overwrite the hash + * stored in settings, that we haven't yet loaded. + */ + + return; + } + + if (already_processed) + { + /* hash has been loaded from settings and we have already + * executed the special case below once. we can now safely save the + * calculated hash to settings (if it has changed). + */ + + if (new_hash) + { + set_all_change_unaware(); + db_hash_store(); + } + } + else + { + /* this is only supposed to run once, on boot up, after the hash + * has been loaded from settings. + */ + + atomic_set_bit(gatt_sc.flags, DB_HASH_LOAD_PROC); + + /* Check if hash matches then skip SC update */ + + if (!memcmp(db_hash.stored_hash, db_hash.hash, + sizeof(db_hash.stored_hash))) + { + LOG_DBG("Database Hash matches"); + k_work_cancel_delayable(&gatt_sc.work); + atomic_clear_bit(gatt_sc.flags, SC_RANGE_CHANGED); + return; + } + + LOG_HEXDUMP_DBG(db_hash.hash, sizeof(db_hash.hash), "New Hash: "); + + /* GATT database has been modified since last boot, likely due + * to a firmware update or a dynamic service that was not re-registered + * on boot. Indicate Service Changed to all bonded devices for the full + * database range to invalidate client-side cache and force discovery + * on reconnect. + */ + + sc_indicate(0x0001, 0xffff); + + /* Hash did not match, overwrite with current hash. + * Also immediately set all peers (in settings) as change-unaware. + */ + + set_all_change_unaware(); + db_hash_store(); + } +#endif /* defined(CONFIG_BT_SETTINGS) */ +} + +static void db_hash_process(struct k_work *work) +{ + do_db_hash(); +} + +static ssize_t db_hash_read( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct gatt_cf_cfg *cfg; + + /* Check if db_hash is already pending in which case it shall be + * generated immediately instead of waiting for the work to complete. + */ + + (void)k_work_cancel_delayable_sync(&db_hash.work, &db_hash.sync); + if (!atomic_test_bit(gatt_sc.flags, DB_HASH_VALID)) + { + db_hash_gen(); + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + set_all_change_unaware(); + db_hash_store(); + } + } + + /* BLUETOOTH CORE SPECIFICATION Version 5.1 | Vol 3, Part G page 2347: + * 2.5.2.1 Robust Caching A connected client becomes change-aware when... + * The client reads the Database Hash characteristic and then the server + * receives another ATT request from the client. + */ + + cfg = find_cf_cfg(conn); + if (cfg && CF_ROBUST_CACHING(cfg) && + !atomic_test_bit(cfg->flags, CF_CHANGE_AWARE)) + { + atomic_set_bit(cfg->flags, CF_DB_HASH_READ); + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, db_hash.hash, + sizeof(db_hash.hash)); +} + +static void remove_cf_cfg(struct bt_conn *conn) +{ + struct gatt_cf_cfg *cfg; + + cfg = find_cf_cfg(conn); + if (!cfg) + { + return; + } + + /* BLUETOOTH CORE SPECIFICATION Version 5.1 | Vol 3, Part G page 2405: + * For clients with a trusted relationship, the characteristic value shall + * be persistent across connections. For clients without a trusted + * relationship the characteristic value shall be set to the default value + * at each connection. + */ + + if (!bt_le_bond_exists(conn->id, &conn->le.dst)) + { + clear_cf_cfg(cfg); + } + else + { + /* Update address in case it has changed */ + + bt_addr_le_copy(&cfg->peer, &conn->le.dst); + } +} + +#if defined(CONFIG_BT_EATT) +#define SF_BIT_EATT 0 +#define SF_BIT_LAST SF_BIT_EATT + +static ssize_t sf_read( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + uint8_t value = BIT(SF_BIT_EATT); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &value, + sizeof(value)); +} +#endif /* CONFIG_BT_EATT */ +#endif /* CONFIG_BT_GATT_CACHING */ + +static struct gatt_cf_cfg *find_cf_cfg_by_addr(uint8_t id, + const bt_addr_le_t *addr); + +static int bt_gatt_store_cf(uint8_t id, const bt_addr_le_t *peer) +{ +#if defined(CONFIG_BT_GATT_CACHING) + struct gatt_cf_cfg *cfg; + char dst[CF_NUM_BYTES + CF_FLAGS_STORE_LEN]; + char *str; + size_t len; + int err; + + cfg = find_cf_cfg_by_addr(id, peer); + if (!cfg) + { + /* No cfg found, just clear it */ + + LOG_DBG("No config for CF"); + str = NULL; + len = 0; + } + else + { + str = (char *)cfg->data; + len = sizeof(cfg->data); + + /* add the CF data to a temp array */ + + memcpy(dst, str, len); + + /* add the change-aware flag */ + + bool is_change_aware = atomic_test_bit(cfg->flags, CF_CHANGE_AWARE); + + dst[len] = 0; + WRITE_BIT(dst[len], CF_CHANGE_AWARE, is_change_aware); + len += CF_FLAGS_STORE_LEN; + + str = dst; + } + + err = bt_settings_store_cf(id, peer, str, len); + if (err) + { + LOG_ERR("Failed to store Client Features (err %d)", err); + return err; + } + + LOG_DBG("Stored CF for %s", bt_addr_le_str(peer)); + LOG_HEXDUMP_DBG(str, len, "Saved data"); +#endif /* CONFIG_BT_GATT_CACHING */ + return 0; +} + +static bool is_host_managed_ccc(const struct bt_gatt_attr *attr) +{ + return (attr->write == bt_gatt_attr_write_ccc); +} + +#if defined(CONFIG_BT_SETTINGS) +static int gatt_store_ccc(uint8_t id, const bt_addr_le_t *addr); +#else +static int gatt_store_ccc(uint8_t id, const bt_addr_le_t *addr) +{ + /* This function shall never be used without CONFIG_BT_SETTINGS. */ + + DEBUGASSERT(false); + + return -ENOTSUP; +} +#endif /* CONFIG_BT_SETTINGS */ + +#if defined(CONFIG_BT_SETTINGS) && defined(CONFIG_BT_SMP) +/** Struct used to store both the id and the random address of a device when + * replacing random addresses in the ccc attribute's cfg array with the + * device's id address after pairing complete. + */ + +struct addr_match +{ + const bt_addr_le_t *private_addr; + const bt_addr_le_t *id_addr; +}; + +static uint8_t convert_to_id_on_match( + const struct bt_gatt_attr *attr, uint16_t handle, void *user_data) +{ + struct bt_gatt_ccc_managed_user_data *ccc; + struct addr_match *match = user_data; + + if (!is_host_managed_ccc(attr)) + { + return BT_GATT_ITER_CONTINUE; + } + + ccc = attr->user_data; + + /* Copy the device's id address to the config's address if the config's + * address is the same as the device's private address + */ + + for (size_t i = 0; i < ARRAY_SIZE(ccc->cfg); i++) + { + if (bt_addr_le_eq(&ccc->cfg[i].peer, match->private_addr)) + { + bt_addr_le_copy(&ccc->cfg[i].peer, match->id_addr); + } + } + + return BT_GATT_ITER_CONTINUE; +} + +static void bt_gatt_identity_resolved( + struct bt_conn *conn, const bt_addr_le_t *private_addr, + const bt_addr_le_t *id_addr) +{ + /* Update the ccc cfg addresses */ + + struct addr_match user_data = + { + .private_addr = private_addr, .id_addr = id_addr + }; + + bool is_bonded = bt_le_bond_exists(conn->id, &conn->le.dst); + + bt_gatt_foreach_attr(0x0001, 0xffff, convert_to_id_on_match, &user_data); + + /* Store the ccc */ + + if (is_bonded) + { + gatt_store_ccc(conn->id, &conn->le.dst); + } + + /* Update the cf addresses and store it if we get a match */ + + struct gatt_cf_cfg *cfg = find_cf_cfg_by_addr(conn->id, private_addr); + + if (cfg) + { + bt_addr_le_copy(&cfg->peer, id_addr); + if (is_bonded) + { + bt_gatt_store_cf(conn->id, &conn->le.dst); + } + } + + if (IS_ENABLED(CONFIG_BT_GATT_SERVICE_CHANGED)) + { + /* Keep Service Changed bookkeeping aligned with the bonded identity + * address so restore-on-reconnect works when privacy is enabled. + */ + + struct gatt_sc_cfg *sc = find_sc_cfg(conn->id, private_addr); + + if (sc != NULL) + { + struct gatt_sc_cfg *sc_id = find_sc_cfg(conn->id, id_addr); + + if ((sc_id != NULL) && (sc_id != sc)) + { + if (sc->data.start != 0U || sc->data.end != 0U) + { + (void)update_range(&sc_id->data.start, &sc_id->data.end, + sc->data.start, + sc->data.end); + } + + clear_sc_cfg(sc); + sc = sc_id; + } + else + { + bt_addr_le_copy(&sc->peer, id_addr); + } + + if (is_bonded) + { + sc_store(sc); + } + } + } +} + +static void bt_gatt_pairing_complete(struct bt_conn *conn, bool bonded) +{ + if (bonded) + { + /* Store the ccc and cf data */ + + gatt_store_ccc(conn->id, &(conn->le.dst)); + bt_gatt_store_cf(conn->id, &conn->le.dst); + + if (IS_ENABLED(CONFIG_BT_GATT_SERVICE_CHANGED)) + { + struct gatt_sc_cfg *cfg = find_sc_cfg(conn->id, &conn->le.dst); + + /* A client may subscribe to Service Changed before it + * bonds, in which case sc_save() created the entry but had no bond + * to persist it against. Store it now that there is one. + */ + + if (cfg != NULL) + { + sc_store(cfg); + } + } + } +} +#endif /* CONFIG_BT_SETTINGS && CONFIG_BT_SMP */ + +BT_GATT_SERVICE_DEFINE(_1_gatt_svc, BT_GATT_PRIMARY_SERVICE(BT_UUID_GATT), +#if defined(CONFIG_BT_GATT_SERVICE_CHANGED) + /* Bluetooth 5.0, Vol3 Part G: + * The Service Changed characteristic Attribute Handle + * on the server shall not change if the server has a + * trusted relationship with any client. + */ + + BT_GATT_CHARACTERISTIC(BT_UUID_GATT_SC, + BT_GATT_CHRC_INDICATE, + + BT_GATT_PERM_NONE, NULL, NULL, + NULL), + BT_GATT_CCC_MANAGED(&sc_ccc, + BT_GATT_PERM_READ | + BT_GATT_PERM_WRITE), +#if defined(CONFIG_BT_GATT_CACHING) + BT_GATT_CHARACTERISTIC(BT_UUID_GATT_CLIENT_FEATURES, + BT_GATT_CHRC_READ | + BT_GATT_CHRC_WRITE, + BT_GATT_PERM_READ | + BT_GATT_PERM_WRITE, + cf_read, + cf_write, NULL), + BT_GATT_CHARACTERISTIC(BT_UUID_GATT_DB_HASH, + BT_GATT_CHRC_READ, + + BT_GATT_PERM_READ, + db_hash_read, NULL, NULL), +#if defined(CONFIG_BT_EATT) + BT_GATT_CHARACTERISTIC(BT_UUID_GATT_SERVER_FEATURES, + BT_GATT_CHRC_READ, + + BT_GATT_PERM_READ, sf_read, + NULL, NULL), +#endif /* CONFIG_BT_EATT */ +#endif /* CONFIG_BT_GATT_CACHING */ +#endif /* CONFIG_BT_GATT_SERVICE_CHANGED */ +); + +static uint8_t found_attr(const struct bt_gatt_attr *attr, + uint16_t handle, void *user_data) +{ + const struct bt_gatt_attr **found = user_data; + + *found = attr; + + return BT_GATT_ITER_STOP; +} + +static const struct bt_gatt_attr *find_attr(uint16_t handle) +{ + const struct bt_gatt_attr *attr = NULL; + + bt_gatt_foreach_attr(handle, handle, found_attr, &attr); + + return attr; +} + +#if defined(CONFIG_BT_GATT_DYNAMIC_DB) +static void gatt_insert(struct bt_gatt_service *svc, uint16_t last_handle) +{ + struct bt_gatt_service *tmp; + struct bt_gatt_service *prev = NULL; + + if (last_handle == 0 || svc->attrs[0].handle > last_handle) + { + list_add_tail(&db, &svc->node); + return; + } + + /* DB shall always have its service in ascending order */ + + list_for_every_container(&db, tmp, node) + { + if (tmp->attrs[0].handle > svc->attrs[0].handle) + { + if (prev) + { + list_insert_after(&db, &prev->node, &svc->node); + } + else + { + list_add_head(&db, &svc->node); + } + + return; + } + + prev = tmp; + } +} + +static int gatt_register(struct bt_gatt_service *svc) +{ + struct bt_gatt_service *last; + uint16_t handle; + uint16_t last_handle; + struct bt_gatt_attr *attrs = svc->attrs; + uint16_t count = svc->attr_count; + + BT_DEV_LOCK_ASSERT(); + + if (list_is_empty(&db)) + { + handle = last_static_handle; + last_handle = 0; + goto populate; + } + + last = list_peek_tail_container(&db, last, node); + handle = last->attrs[last->attr_count - 1].handle; + last_handle = handle; + +populate: + + /* Populate the handles and append them to the list */ + + for (; attrs && count; attrs++, count--) + { + attrs->_auto_assigned_handle = 0; + if (!attrs->handle) + { + /* Allocate handle if not set already */ + + attrs->handle = ++handle; + attrs->_auto_assigned_handle = 1; + } + else if (attrs->handle > handle) + { + /* Use existing handle if valid */ + + handle = attrs->handle; + } + else if (find_attr(attrs->handle)) + { + /* Service has conflicting handles */ + + LOG_ERR("Unable to register handle 0x%04x", attrs->handle); + return -EINVAL; + } + + LOG_DBG("attr %p handle 0x%04x uuid %s perm 0x%02x", attrs, + attrs->handle, + bt_uuid_str(attrs->uuid), attrs->perm); + } + + gatt_insert(svc, last_handle); + + return 0; +} +#endif /* CONFIG_BT_GATT_DYNAMIC_DB */ + +static inline void sc_work_submit(clock_t timeout) +{ +#if defined(CONFIG_BT_GATT_SERVICE_CHANGED) + bt_work_reschedule(&gatt_sc.work, timeout); +#endif +} + +#if defined(CONFIG_BT_GATT_SERVICE_CHANGED) +static void sc_indicate_rsp( + struct bt_conn *conn, struct bt_gatt_indicate_params *params, uint8_t err) +{ +#if defined(CONFIG_BT_GATT_CACHING) + struct gatt_cf_cfg *cfg; +#endif + + LOG_DBG("err 0x%02x", err); + + atomic_clear_bit(gatt_sc.flags, SC_INDICATE_PENDING); + + /* Check if there is new change in the meantime */ + + if (atomic_test_bit(gatt_sc.flags, SC_RANGE_CHANGED)) + { + /* Reschedule without any delay since it is waiting already */ + + sc_work_submit(0); + } + +#if defined(CONFIG_BT_GATT_CACHING) + /* BLUETOOTH CORE SPECIFICATION Version 5.3 | Vol 3, Part G page 1476: + * 2.5.2.1 Robust Caching ... a change-unaware connected client using + * exactly one ATT bearer becomes change-aware when ... The client receives + * and confirms a Handle Value Indication for the Service Changed + * characteristic + */ + + if (bt_att_fixed_chan_only(conn)) + { + cfg = find_cf_cfg(conn); + if (cfg && CF_ROBUST_CACHING(cfg)) + { + set_change_aware(cfg, true); + } + } +#endif /* CONFIG_BT_GATT_CACHING */ +} + +static void sc_process(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct gatt_sc *sc = CONTAINER_OF(dwork, struct gatt_sc, work); + uint16_t sc_range[2]; + + DEBUGASSERT(!atomic_test_bit(sc->flags, SC_INDICATE_PENDING)); + + LOG_DBG("start 0x%04x end 0x%04x", sc->start, sc->end); + + sc_range[0] = sys_cpu_to_le16(sc->start); + sc_range[1] = sys_cpu_to_le16(sc->end); + + atomic_clear_bit(sc->flags, SC_RANGE_CHANGED); + sc->start = 0U; + sc->end = 0U; + + sc->params.attr = &_1_gatt_svc.attrs[2]; + sc->params.func = sc_indicate_rsp; + sc->params.data = &sc_range[0]; + sc->params.len = sizeof(sc_range); +#if defined(CONFIG_BT_EATT) + sc->params.chan_opt = BT_ATT_CHAN_OPT_NONE; +#endif /* CONFIG_BT_EATT */ + + if (bt_gatt_indicate(NULL, &sc->params)) + { + /* No connections to indicate */ + + return; + } + + atomic_set_bit(sc->flags, SC_INDICATE_PENDING); +} +#endif /* defined(CONFIG_BT_GATT_SERVICE_CHANGED) */ + +static void clear_ccc_cfg(struct bt_gatt_ccc_cfg *cfg) +{ + bt_addr_le_copy(&cfg->peer, BT_ADDR_LE_ANY); + cfg->id = 0U; + cfg->value = 0U; +} + +static void gatt_store_ccc_cf(uint8_t id, const bt_addr_le_t *peer_addr); + +struct ds_peer +{ + uint8_t id; + bt_addr_le_t peer; + + atomic_define(flags, DELAYED_STORE_NUM_FLAGS); +}; + +#if defined(CONFIG_BT_SETTINGS_DELAYED_STORE) +static struct gatt_delayed_store +{ + struct ds_peer peer_list[CONFIG_BT_MAX_PAIRED + CONFIG_BT_MAX_CONN]; + struct k_work_delayable work; +} gatt_delayed_store; +#endif + +static struct ds_peer *gatt_delayed_store_find(uint8_t id, + const bt_addr_le_t *peer_addr) +{ + IF_ENABLED(CONFIG_BT_SETTINGS_DELAYED_STORE, ( + { + struct ds_peer *el; + + for (size_t i = 0; i < ARRAY_SIZE(gatt_delayed_store.peer_list); i++) + { + el = &gatt_delayed_store.peer_list[i]; + if (el->id == id && bt_addr_le_eq(peer_addr, &el->peer)) + { + return el; + } + } + } + + )); + + return NULL; +} + +static void gatt_delayed_store_free(struct ds_peer *el) +{ + if (el) + { + el->id = 0; + memset(&el->peer, 0, sizeof(el->peer)); + atomic_set(el->flags, 0); + } +} + +#if defined(CONFIG_BT_SETTINGS_DELAYED_STORE) +static struct ds_peer *gatt_delayed_store_alloc(uint8_t id, + const + bt_addr_le_t *peer_addr) +{ + struct ds_peer *el; + + for (size_t i = 0; i < ARRAY_SIZE(gatt_delayed_store.peer_list); i++) + { + el = &gatt_delayed_store.peer_list[i]; + + /* Checking for the flags is cheaper than a memcmp for the + * address, so we use that to signal that a given slot is free. + */ + + if (atomic_read(el->flags) == 0) + { + bt_addr_le_copy(&el->peer, peer_addr); + el->id = id; + + return el; + } + } + + return NULL; +} + +static void gatt_delayed_store_enqueue( + uint8_t id, const bt_addr_le_t *peer_addr, enum delayed_store_flags flag) +{ + bool bonded = bt_le_bond_exists(id, peer_addr); + struct ds_peer *el = gatt_delayed_store_find(id, peer_addr); + + if (bonded) + { + if (el == NULL) + { + el = gatt_delayed_store_alloc(id, peer_addr); + DEBUGASSERT(el != NULL); + } + + atomic_set_bit(el->flags, flag); + + bt_work_reschedule(&gatt_delayed_store.work, + MSEC2TICK(CONFIG_BT_SETTINGS_DELAYED_STORE_MS)); + } +} + +static void delayed_store(struct k_work *work) +{ + struct ds_peer *el; + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct gatt_delayed_store *store = CONTAINER_OF(dwork, + struct gatt_delayed_store, work); + + for (size_t i = 0; i < ARRAY_SIZE(gatt_delayed_store.peer_list); i++) + { + el = &store->peer_list[i]; + + gatt_store_ccc_cf(el->id, &el->peer); + } +} +#endif /* CONFIG_BT_SETTINGS_DELAYED_STORE */ + +static void gatt_store_ccc_cf(uint8_t id, const bt_addr_le_t *peer_addr) +{ + struct ds_peer *el = gatt_delayed_store_find(id, peer_addr); + + if (bt_le_bond_exists(id, peer_addr)) + { + if (!IS_ENABLED(CONFIG_BT_SETTINGS_CCC_STORE_ON_WRITE) || + (IS_ENABLED(CONFIG_BT_SETTINGS_CCC_STORE_ON_WRITE) && el && + atomic_test_and_clear_bit(el->flags, DELAYED_STORE_CCC))) + { + gatt_store_ccc(id, peer_addr); + } + + if (!IS_ENABLED(CONFIG_BT_SETTINGS_CF_STORE_ON_WRITE) || + (IS_ENABLED(CONFIG_BT_SETTINGS_CF_STORE_ON_WRITE) && el && + atomic_test_and_clear_bit(el->flags, DELAYED_STORE_CF))) + { + bt_gatt_store_cf(id, peer_addr); + } + + if (el && atomic_read(el->flags) == 0) + { + gatt_delayed_store_free(el); + } + } +} + +#if defined(CONFIG_BT_SETTINGS) && defined(CONFIG_BT_SMP) +BT_CONN_CB_DEFINE(gatt_conn_cb) = +{ + /* Also update the address of CCC or CF writes that happened before + * the identity resolution. Note that to increase security in the + * future, we might want to explicitly not do this and treat a + * bonded device as a brand-new peer. + */ + + .identity_resolved = bt_gatt_identity_resolved, +}; +#endif /* CONFIG_BT_SETTINGS && CONFIG_BT_SMP */ + +static void bt_gatt_service_init(void) +{ + if (atomic_test_and_set_bit(gatt_flags, GATT_SERVICE_INITIALIZED)) + { + return; + } + + STRUCT_SECTION_FOREACH(bt_gatt_service_static, svc) + { + last_static_handle += svc->attr_count; + } +} + +static void central_addr_res_pairing_complete(struct bt_conn *conn, + bool bonded); + +void bt_gatt_init(void) +{ + if (atomic_test_and_set_bit(gatt_flags, GATT_INITIALIZED)) + { + return; + } + + for (int i = 0; i < ARRAY_SIZE(subscriptions); i++) + { + list_initialize(&subscriptions[i].list); + } + + bt_gatt_service_init(); + +#if defined(CONFIG_BT_GATT_CACHING) + k_work_init_delayable(&db_hash.work, db_hash_process); + + /* Submit work to Generate initial hash as there could be static + * services already in the database. + */ + + if (IS_ENABLED(CONFIG_BT_LONG_WQ)) + { + bt_long_wq_schedule(&db_hash.work, DB_HASH_TIMEOUT); + } + else + { + bt_work_schedule(&db_hash.work, DB_HASH_TIMEOUT); + } +#endif /* CONFIG_BT_GATT_CACHING */ + +#if defined(CONFIG_BT_GATT_SERVICE_CHANGED) + k_work_init_delayable(&gatt_sc.work, sc_process); + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + /* Make sure to not send SC indications until SC + * settings are loaded + */ + + atomic_set_bit(gatt_sc.flags, SC_INDICATE_PENDING); + } +#endif /* defined(CONFIG_BT_GATT_SERVICE_CHANGED) */ + +#if defined(CONFIG_BT_SETTINGS_DELAYED_STORE) + k_work_init_delayable(&gatt_delayed_store.work, delayed_store); +#endif + +#if defined(CONFIG_BT_SETTINGS) && defined(CONFIG_BT_SMP) + static struct bt_conn_auth_info_cb gatt_conn_auth_info_cb = + { + .pairing_complete = bt_gatt_pairing_complete, + }; + + /* Register the gatt module for authentication info callbacks so it can + * be notified when pairing has completed. This is used to enable CCC and + * CF storage on pairing complete. + */ + + bt_conn_auth_info_cb_register(&gatt_conn_auth_info_cb); +#endif /* CONFIG_BT_SETTINGS && CONFIG_BT_SMP */ + + if (IS_ENABLED(CONFIG_BT_GATT_AUTO_READ_CENTRAL_ADDR_RES)) + { + static struct bt_conn_auth_info_cb central_addr_res_auth_info_cb = + { + .pairing_complete = central_addr_res_pairing_complete, + }; + + int err; + + /* Read the peer's Central Address Resolution support once a + * bond has been created. + */ + + err = bt_conn_auth_info_cb_register(¢ral_addr_res_auth_info_cb); + if (err != 0) + { + LOG_ERR("Unable to register pairing callbacks (err %d)", err); + } + } +} + +static void sc_indicate(uint16_t start, uint16_t end) +{ +#if defined(CONFIG_BT_GATT_DYNAMIC_DB) || \ + (defined(CONFIG_BT_GATT_CACHING) && defined(CONFIG_BT_SETTINGS)) + LOG_DBG("start 0x%04x end 0x%04x", start, end); + + if (!atomic_test_and_set_bit(gatt_sc.flags, SC_RANGE_CHANGED)) + { + gatt_sc.start = start; + gatt_sc.end = end; + goto submit; + } + + if (!update_range(&gatt_sc.start, &gatt_sc.end, start, end)) + { + return; + } + +submit: + if (atomic_test_bit(gatt_sc.flags, SC_INDICATE_PENDING)) + { + LOG_DBG("indicate pending, waiting until complete..."); + return; + } + + /* Reschedule since the range has changed */ + + sc_work_submit(SC_TIMEOUT); +#endif /* BT_GATT_DYNAMIC_DB || (BT_GATT_CACHING && BT_SETTINGS) */ +} + +void bt_gatt_cb_register(struct bt_gatt_cb *cb) +{ + list_add_tail(&callback_list, &cb->node); +} + +int bt_gatt_cb_unregister(struct bt_gatt_cb *cb) +{ + if (cb == NULL) + { + return -EINVAL; + } + + if (!list_find_and_remove(&callback_list, &cb->node)) + { + return -ENOENT; + } + + return 0; +} + +#if defined(CONFIG_BT_GATT_DYNAMIC_DB) +static void db_changed(void) +{ +#if defined(CONFIG_BT_GATT_CACHING) + struct bt_conn *conn; + int i; + + atomic_clear_bit(gatt_sc.flags, DB_HASH_VALID); + + if (IS_ENABLED(CONFIG_BT_LONG_WQ)) + { + bt_long_wq_reschedule(&db_hash.work, DB_HASH_TIMEOUT); + } + else + { + bt_work_reschedule(&db_hash.work, DB_HASH_TIMEOUT); + } + + for (i = 0; i < ARRAY_SIZE(cf_cfg); i++) + { + struct gatt_cf_cfg *cfg = &cf_cfg[i]; + + if (bt_addr_le_eq(&cfg->peer, BT_ADDR_LE_ANY)) + { + continue; + } + + if (CF_ROBUST_CACHING(cfg)) + { + /* Core Spec 5.1 | Vol 3, Part G, 2.5.2.1 Robust Caching + * ... the database changes again before the client + * becomes change-aware in which case the error response shall be + * sent again. + */ + + conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cfg->peer); + if (conn) + { + bt_att_clear_out_of_sync_sent(conn); + bt_conn_unref(conn); + } + + atomic_clear_bit(cfg->flags, CF_DB_HASH_READ); + set_change_aware(cfg, false); + } + } +#endif +} + +static void gatt_unregister_ccc(struct bt_gatt_ccc_managed_user_data *ccc) +{ + ccc->value = 0; + + for (size_t i = 0; i < ARRAY_SIZE(ccc->cfg); i++) + { + struct bt_gatt_ccc_cfg *cfg = &ccc->cfg[i]; + + if (!bt_addr_le_eq(&cfg->peer, BT_ADDR_LE_ANY)) + { + struct bt_conn *conn; + bool store = true; + + conn = bt_conn_lookup_addr_le(cfg->id, &cfg->peer); + if (conn) + { + if (conn->state == BT_CONN_CONNECTED) + { +#if defined(CONFIG_BT_SETTINGS_CCC_STORE_ON_WRITE) + gatt_delayed_store_enqueue(conn->id, &conn->le.dst, + DELAYED_STORE_CCC); +#endif + store = false; + } + + bt_conn_unref(conn); + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS) && store && + bt_le_bond_exists(cfg->id, &cfg->peer)) + { + gatt_store_ccc(cfg->id, &cfg->peer); + } + + clear_ccc_cfg(cfg); + } + } +} + +static int gatt_unregister(struct bt_gatt_service *svc) +{ + if (!list_find_and_remove(&db, &svc->node)) + { + return -ENOENT; + } + + for (uint16_t i = 0; i < svc->attr_count; i++) + { + struct bt_gatt_attr *attr = &svc->attrs[i]; + + if (is_host_managed_ccc(attr)) + { + gatt_unregister_ccc(attr->user_data); + } + + /* The stack should not clear any handles set by the user. */ + + if (attr->_auto_assigned_handle) + { + attr->handle = 0; + attr->_auto_assigned_handle = 0; + } + } + + return 0; +} + +int bt_gatt_service_register(struct bt_gatt_service *svc) +{ + int err; + + DEBUGASSERT(svc); + DEBUGASSERT(svc->attrs); + DEBUGASSERT(svc->attr_count); + + if (IS_ENABLED(CONFIG_BT_SETTINGS) && + atomic_test_bit(gatt_flags, GATT_INITIALIZED) && + !atomic_test_bit(gatt_sc.flags, SC_LOAD)) + { + LOG_DBG("Can't register service after init and before settings are " + "loaded."); + return -EINVAL; + } + + /* Init GATT core services */ + + bt_gatt_service_init(); + + /* Do no allow to register mandatory services twice */ + + if (!bt_uuid_cmp(svc->attrs[0].uuid, BT_UUID_GAP) || + !bt_uuid_cmp(svc->attrs[0].uuid, BT_UUID_GATT)) + { + return -EALREADY; + } + + bt_dev_lock(); + + /* The RX-path readers of the database (the ATT request handlers + * iterating the attributes) do not take the host lock and rely on + * cooperative scheduling: hold the scheduler lock as well so that they + * cannot preempt a preemptible caller mid-update. + */ + + sched_lock(); + + err = gatt_register(svc); + if (err < 0) + { + sched_unlock(); + bt_dev_unlock(); + return err; + } + + /* Don't submit any work until the stack is initialized */ + + if (!atomic_test_bit(gatt_flags, GATT_INITIALIZED)) + { + sched_unlock(); + bt_dev_unlock(); + return 0; + } + + sc_indicate(svc->attrs[0].handle, svc->attrs[svc->attr_count - 1].handle); + + db_changed(); + + sched_unlock(); + bt_dev_unlock(); + + return 0; +} + +int bt_gatt_service_unregister(struct bt_gatt_service *svc) +{ + uint16_t sc_start_handle; + uint16_t sc_end_handle; + int err; + + DEBUGASSERT(svc); + + /* gatt_unregister() clears handles when those were auto-assigned + * by host + */ + + sc_start_handle = svc->attrs[0].handle; + sc_end_handle = svc->attrs[svc->attr_count - 1].handle; + + bt_dev_lock(); + + /* See bt_gatt_service_register() for why the scheduler lock is + * held as well. + */ + + sched_lock(); + + err = gatt_unregister(svc); + if (err) + { + sched_unlock(); + bt_dev_unlock(); + return err; + } + + /* Don't submit any work until the stack is initialized */ + + if (!atomic_test_bit(gatt_flags, GATT_INITIALIZED)) + { + sched_unlock(); + bt_dev_unlock(); + return 0; + } + + sc_indicate(sc_start_handle, sc_end_handle); + + db_changed(); + + sched_unlock(); + bt_dev_unlock(); + + return 0; +} + +bool bt_gatt_service_is_registered(const struct bt_gatt_service *svc) +{ + bool registered = false; + struct list_node *node; + + bt_dev_lock(); + list_for_every(&db, node) + { + if (&svc->node == node) + { + registered = true; + break; + } + } + + bt_dev_unlock(); + + return registered; +} +#endif /* CONFIG_BT_GATT_DYNAMIC_DB */ + +ssize_t bt_gatt_attr_read( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t buf_len, uint16_t offset, const void *value, uint16_t value_len) +{ + uint16_t len; + + if (offset > value_len) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (value_len != 0U && value == NULL) + { + LOG_DBG("value_len of %u provided for NULL value", value_len); + + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + if (value_len == 0U) + { + len = 0U; + } + else + { + len = MIN(buf_len, value_len - offset); + memcpy(buf, (uint8_t *)value + offset, len); + } + + LOG_DBG("handle 0x%04x offset %u length %u", attr->handle, offset, len); + + return len; +} + +ssize_t bt_gatt_attr_read_service( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + const struct bt_uuid *uuid = attr->user_data; + + if (uuid->type == BT_UUID_TYPE_16) + { + uint16_t uuid16 = sys_cpu_to_le16(BT_UUID_16(uuid)->val); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &uuid16, 2); + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + BT_UUID_128(uuid)->val, 16); +} + +struct gatt_incl +{ + uint16_t start_handle; + uint16_t end_handle; + uint16_t uuid16; +} __packed; + +static uint8_t get_service_handles( + const struct bt_gatt_attr *attr, uint16_t handle, void *user_data) +{ + struct gatt_incl *include = user_data; + + /* Stop if attribute is a service */ + + if (!bt_uuid_cmp(attr->uuid, BT_UUID_GATT_PRIMARY) || + !bt_uuid_cmp(attr->uuid, BT_UUID_GATT_SECONDARY)) + { + return BT_GATT_ITER_STOP; + } + + include->end_handle = sys_cpu_to_le16(handle); + + return BT_GATT_ITER_CONTINUE; +} + +uint16_t bt_gatt_attr_get_handle(const struct bt_gatt_attr *attr) +{ + uint16_t handle = 1; + + if (!attr) + { + return 0; + } + + if (attr->handle) + { + return attr->handle; + } + + STRUCT_SECTION_FOREACH(bt_gatt_service_static, static_svc) + { + /* Skip ahead if start is not within service attributes array */ + + if ((attr < &static_svc->attrs[0]) || + (attr > &static_svc->attrs[static_svc->attr_count - 1])) + { + handle += static_svc->attr_count; + continue; + } + + for (size_t i = 0; i < static_svc->attr_count; i++, handle++) + { + if (attr == &static_svc->attrs[i]) + { + return handle; + } + } + } + + return 0; +} + +ssize_t bt_gatt_attr_read_included( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + if ((attr == NULL) || (attr->user_data == NULL)) + { + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + struct bt_gatt_attr *incl = attr->user_data; + uint16_t handle = bt_gatt_attr_get_handle(incl); + struct bt_uuid *uuid = incl->user_data; + struct gatt_incl pdu; + uint8_t value_len; + + /* first attr points to the start handle */ + + pdu.start_handle = sys_cpu_to_le16(handle); + value_len = sizeof(pdu.start_handle) + sizeof(pdu.end_handle); + + /* Core 4.2, Vol 3, Part G, 3.2, + * The Service UUID shall only be present when the UUID is a 16-bit + * Bluetooth UUID. + */ + + if (uuid->type == BT_UUID_TYPE_16) + { + pdu.uuid16 = sys_cpu_to_le16(BT_UUID_16(uuid)->val); + value_len += sizeof(pdu.uuid16); + } + + /* Lookup for service end handle */ + + bt_gatt_foreach_attr(handle + 1, 0xffff, get_service_handles, &pdu); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &pdu, value_len); +} + +struct gatt_chrc +{ + uint8_t properties; + uint16_t value_handle; + union + { + uint16_t uuid16; + uint8_t uuid[16]; + } __packed; +} __packed; + +uint16_t bt_gatt_attr_value_handle(const struct bt_gatt_attr *attr) +{ + uint16_t handle = 0; + + if (attr != NULL && bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CHRC) == 0) + { + struct bt_gatt_chrc *chrc = attr->user_data; + + handle = chrc->value_handle; + if (handle == 0) + { + /* Fall back to Zephyr value handle policy */ + + handle = bt_gatt_attr_get_handle(attr) + 1U; + } + } + + return handle; +} + +ssize_t bt_gatt_attr_read_chrc( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct bt_gatt_chrc *chrc = attr->user_data; + struct gatt_chrc pdu; + uint8_t value_len; + + pdu.properties = chrc->properties; + /* BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part G] page 534: + * 3.3.2 Characteristic Value Declaration The Characteristic Value + * declaration contains the value of the characteristic. It is the first + * Attribute after the characteristic declaration. All characteristic + * definitions shall have a Characteristic Value declaration. + */ + + pdu.value_handle = sys_cpu_to_le16(bt_gatt_attr_value_handle(attr)); + + value_len = sizeof(pdu.properties) + sizeof(pdu.value_handle); + + if (chrc->uuid->type == BT_UUID_TYPE_16) + { + pdu.uuid16 = sys_cpu_to_le16(BT_UUID_16(chrc->uuid)->val); + value_len += 2U; + } + else + { + memcpy(pdu.uuid, BT_UUID_128(chrc->uuid)->val, 16); + value_len += 16U; + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &pdu, value_len); +} + +static uint8_t gatt_foreach_iter( + const struct bt_gatt_attr *attr, uint16_t handle, uint16_t start_handle, + uint16_t end_handle, const struct bt_uuid *uuid, const void *attr_data, + uint16_t *num_matches, bt_gatt_attr_func_t func, void *user_data) +{ + uint8_t result; + + /* Stop if over the requested range */ + + if (handle > end_handle) + { + return BT_GATT_ITER_STOP; + } + + /* Check if attribute handle is within range */ + + if (handle < start_handle) + { + return BT_GATT_ITER_CONTINUE; + } + + /* Match attribute UUID if set */ + + if (uuid && bt_uuid_cmp(uuid, attr->uuid)) + { + return BT_GATT_ITER_CONTINUE; + } + + /* Match attribute user_data if set */ + + if (attr_data && attr_data != attr->user_data) + { + return BT_GATT_ITER_CONTINUE; + } + + *num_matches -= 1; + + result = func(attr, handle, user_data); + + if (!*num_matches) + { + return BT_GATT_ITER_STOP; + } + + return result; +} + +static void foreach_attr_type_dyndb( + uint16_t start_handle, uint16_t end_handle, const struct bt_uuid *uuid, + const void *attr_data, uint16_t num_matches, bt_gatt_attr_func_t func, + void *user_data) +{ +#if defined(CONFIG_BT_GATT_DYNAMIC_DB) + size_t i; + struct bt_gatt_service *svc; + + list_for_every_container(&db, svc, node) + { + struct bt_gatt_service *next; + + next = list_next_container(&db, svc, node); + if (next) + { + /* Skip ahead if start is not within service handles */ + + if (next->attrs[0].handle <= start_handle) + { + continue; + } + } + + for (i = 0; i < svc->attr_count; i++) + { + struct bt_gatt_attr *attr = &svc->attrs[i]; + + if (gatt_foreach_iter(attr, attr->handle, start_handle, end_handle, + uuid, attr_data, + &num_matches, func, user_data) == + BT_GATT_ITER_STOP) + { + return; + } + } + } +#endif /* CONFIG_BT_GATT_DYNAMIC_DB */ +} + +void bt_gatt_foreach_attr_type(uint16_t start_handle, + uint16_t end_handle, + const struct bt_uuid *uuid, + const void *attr_data, + uint16_t num_matches, + bt_gatt_attr_func_t func, + void *user_data) +{ + size_t i; + + if (!num_matches) + { + num_matches = UINT16_MAX; + } + + if (start_handle <= last_static_handle) + { + uint16_t handle = 1; + + STRUCT_SECTION_FOREACH(bt_gatt_service_static, static_svc) + { + /* Skip ahead if start is not within service handles */ + + if (handle + static_svc->attr_count < start_handle) + { + handle += static_svc->attr_count; + continue; + } + + for (i = 0; i < static_svc->attr_count; i++, handle++) + { + if (gatt_foreach_iter(&static_svc->attrs[i], + handle, + start_handle, + + end_handle, uuid, + attr_data, + &num_matches, + func, + user_data) == BT_GATT_ITER_STOP) + { + return; + } + } + } + } + + /* Iterate over dynamic db */ + + foreach_attr_type_dyndb(start_handle, end_handle, uuid, attr_data, + num_matches, func, user_data); +} + +static uint8_t find_next(const struct bt_gatt_attr *attr, + uint16_t handle, void *user_data) +{ + struct bt_gatt_attr **next = user_data; + + *next = (struct bt_gatt_attr *)attr; + + return BT_GATT_ITER_STOP; +} + +struct bt_gatt_attr *bt_gatt_attr_next(const struct bt_gatt_attr *attr) +{ + struct bt_gatt_attr *next = NULL; + uint16_t handle = bt_gatt_attr_get_handle(attr); + + bt_gatt_foreach_attr(handle + 1, handle + 1, find_next, &next); + + return next; +} + +static struct bt_gatt_ccc_cfg *find_ccc_cfg(const struct bt_conn *conn, + struct bt_gatt_ccc_managed_user_data *ccc) +{ + for (size_t i = 0; i < ARRAY_SIZE(ccc->cfg); i++) + { + struct bt_gatt_ccc_cfg *cfg = &ccc->cfg[i]; + + if (conn) + { + if (bt_conn_is_peer_addr_le(conn, cfg->id, &cfg->peer)) + { + return cfg; + } + } + else if (bt_addr_le_eq(&cfg->peer, BT_ADDR_LE_ANY)) + { + return cfg; + } + } + + return NULL; +} + +ssize_t bt_gatt_attr_read_ccc( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct bt_gatt_ccc_managed_user_data *ccc = attr->user_data; + const struct bt_gatt_ccc_cfg *cfg; + uint16_t value; + + cfg = find_ccc_cfg(conn, ccc); + if (cfg) + { + value = sys_cpu_to_le16(cfg->value); + } + else + { + /* Default to disable if there is no cfg for the peer */ + + value = 0x0000; + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &value, + sizeof(value)); +} + +static void gatt_ccc_changed(const struct bt_gatt_attr *attr, + struct bt_gatt_ccc_managed_user_data *ccc) +{ + int i; + uint16_t value = 0x0000; + + for (i = 0; i < ARRAY_SIZE(ccc->cfg); i++) + { + /* `ccc->value` shall be a summary of connected peers' CCC values, but + * `ccc->cfg` can contain entries for bonded but not connected peers. + */ + + struct bt_conn *conn = bt_conn_lookup_addr_le(ccc->cfg[i].id, + &ccc->cfg[i].peer); + + if (conn) + { + /* Leave out Reserved bits, which a receiver ignores: + * callbacks compare the value with BT_GATT_CCC_NOTIFY or + * BT_GATT_CCC_INDICATE. + */ + + uint16_t peer_value = + ccc->cfg[i].value & ( + BT_GATT_CCC_NOTIFY | BT_GATT_CCC_INDICATE); + + if (peer_value > value) + { + value = peer_value; + } + + bt_conn_unref(conn); + } + } + + LOG_DBG("ccc %p value 0x%04x", ccc, value); + + if (value != ccc->value) + { + ccc->value = value; + if (ccc->cfg_changed) + { + ccc->cfg_changed(attr, value); + } + } +} + +/* Look up the properties of the characteristic that the given CCC descriptor + * belongs to, i.e. the nearest preceding characteristic declaration that is + * not separated from it by a service declaration. + * + * Returns false if no such declaration could be found or read, in which case + * the caller must not draw any conclusion from the properties. + */ + +static bool ccc_get_chrc_props(const struct bt_gatt_attr *ccc_attr, + uint8_t *properties) +{ + uint16_t ccc_handle = bt_gatt_attr_get_handle(ccc_attr); + uint16_t handle; + + if (ccc_handle <= BT_ATT_FIRST_ATTRIBUTE_HANDLE) + { + return false; + } + + for (handle = ccc_handle - + 1; handle >= BT_ATT_FIRST_ATTRIBUTE_HANDLE; handle--) + { + const struct bt_gatt_attr *attr = find_attr(handle); + ssize_t len; + + if (attr == NULL) + { + return false; + } + + /* Service and include declarations can only appear before the + * characteristic definitions of a service, so finding one means no + * owning declaration exists. + */ + + if (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_PRIMARY) == 0 || + bt_uuid_cmp(attr->uuid, BT_UUID_GATT_SECONDARY) == 0 || + bt_uuid_cmp(attr->uuid, BT_UUID_GATT_INCLUDE) == 0) + { + LOG_WRN("CCC 0x%04x has no characteristic " + "declaration", ccc_handle); + return false; + } + + if (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CHRC) != 0) + { + continue; + } + + if (attr->read == NULL) + { + LOG_WRN("Characteristic declaration 0x%04x is not readable", + handle); + return false; + } + + /* The characteristic properties are the first octet of the + * declaration value. + */ + + len = attr->read(NULL, attr, properties, sizeof(*properties), 0); + if (len != sizeof(*properties)) + { + LOG_WRN("Reading characteristic declaration 0x%04x failed (%zd != " + "%zu)", + handle, len, + sizeof(*properties)); + return false; + } + + return true; + } + + return false; +} + +ssize_t bt_gatt_attr_write_ccc( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + struct bt_gatt_ccc_managed_user_data *ccc = attr->user_data; + struct bt_gatt_ccc_cfg *cfg; + bool value_changed; + uint16_t value; + + if (offset) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (!len || len > sizeof(uint16_t)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + if (len < sizeof(uint16_t)) + { + value = *(uint8_t *)buf; + } + else + { + value = sys_get_le16(buf); + } + + if ((value & (BT_GATT_CCC_NOTIFY | BT_GATT_CCC_INDICATE)) != 0) + { + uint8_t properties; + + /* Reject configurations the characteristic cannot deliver. If + * the declaration cannot be located the write is let through, so that + * a database built by other means than the BT_GATT_CHARACTERISTIC() + * macros keeps working. + */ + + if (ccc_get_chrc_props(attr, &properties)) + { + if (((value & BT_GATT_CCC_NOTIFY) != 0 && + (properties & BT_GATT_CHRC_NOTIFY) == 0) || + ((value & BT_GATT_CCC_INDICATE) != 0 && + (properties & BT_GATT_CHRC_INDICATE) == 0)) + { + LOG_DBG("CCC 0x%04x value 0x%04x unsupported by properties " + "0x%02x", + bt_gatt_attr_get_handle(attr), value, properties); + return BT_GATT_ERR(BT_ATT_ERR_CCC_IMPROPER_CONF); + } + } + } + + cfg = find_ccc_cfg(conn, ccc); + if (!cfg) + { + /* If there's no existing entry, but the new value is zero, + * we don't need to do anything, since a disabled CCC is behaviorally + * the same as no written CCC. + */ + + if (!value) + { + return len; + } + + cfg = find_ccc_cfg(NULL, ccc); + if (!cfg) + { + LOG_WRN("No space to store CCC cfg"); + return BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES); + } + + bt_addr_le_copy(&cfg->peer, &conn->le.dst); + cfg->id = conn->id; + } + + /* Confirm write if cfg is managed by application */ + + if (ccc->cfg_write) + { + ssize_t write = ccc->cfg_write(conn, attr, value); + + if (write < 0) + { + return write; + } + + /* Accept size=1 for backwards compatibility */ + + if (write != sizeof(value) && write != 1) + { + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + } + + value_changed = cfg->value != value; + cfg->value = value; + + LOG_DBG("handle 0x%04x value %u", attr->handle, cfg->value); + + /* Update cfg if don't match */ + + if (cfg->value != ccc->value) + { + gatt_ccc_changed(attr, ccc); + } + + if (value_changed) + { +#if defined(CONFIG_BT_SETTINGS_CCC_STORE_ON_WRITE) + /* Enqueue CCC store if value has changed for the connection */ + + gatt_delayed_store_enqueue(conn->id, &conn->le.dst, DELAYED_STORE_CCC); +#endif + } + + /* Disabled CCC is the same as no configured CCC, so clear the entry */ + + if (!value) + { + clear_ccc_cfg(cfg); + } + + return len; +} + +ssize_t bt_gatt_attr_read_cep( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + const struct bt_gatt_cep *value = attr->user_data; + uint16_t props = sys_cpu_to_le16(value->properties); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &props, + sizeof(props)); +} + +ssize_t bt_gatt_attr_read_cud( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + const char *value = attr->user_data; + + return bt_gatt_attr_read(conn, attr, buf, len, offset, value, + strlen(value)); +} + +struct gatt_cpf +{ + uint8_t format; + int8_t exponent; + uint16_t unit; + uint8_t name_space; + uint16_t description; +} __packed; + +ssize_t bt_gatt_attr_read_cpf( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + const struct bt_gatt_cpf *cpf = attr->user_data; + struct gatt_cpf value; + + value.format = cpf->format; + value.exponent = cpf->exponent; + value.unit = sys_cpu_to_le16(cpf->unit); + value.name_space = cpf->name_space; + value.description = sys_cpu_to_le16(cpf->description); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &value, + sizeof(value)); +} + +struct notify_data +{ + const struct bt_gatt_attr *attr; + uint16_t handle; + int err; + uint16_t type; + union + { + struct bt_gatt_notify_params *nfy_params; + struct bt_gatt_indicate_params *ind_params; + }; +}; + +#if defined(CONFIG_BT_GATT_NOTIFY_MULTIPLE) + +static struct net_buf *nfy_mult[CONFIG_BT_MAX_CONN]; + +static int gatt_notify_mult_send(struct bt_conn *conn, struct net_buf *buf) +{ + int ret; + uint8_t *pdu = buf->data; + + /* PDU structure is [Opcode (1)] [Handle (2)] [Length (2)] [Value (Length)] + */ + + uint16_t first_attr_len = sys_get_le16(&pdu[3]); + + /* Convert to ATT_HANDLE_VALUE_NTF if containing a single handle. */ + + if (buf->len == (1 + sizeof(struct bt_att_notify_mult) + first_attr_len)) + { + /* Store attr handle */ + + uint16_t handle = sys_get_le16(&pdu[1]); + + /* Remove the ATT_MULTIPLE_HANDLE_VALUE_NTF opcode, + * attribute handle and length + */ + + (void)net_buf_pull(buf, 1 + sizeof(struct bt_att_notify_mult)); + + /* Add back an ATT_HANDLE_VALUE_NTF opcode and attr handle */ + + /* PDU structure is now [Opcode (1)] [Handle (1)] [Value] */ + + net_buf_push_le16(buf, handle); + net_buf_push_u8(buf, BT_ATT_OP_NOTIFY); + LOG_DBG("Converted BT_ATT_OP_NOTIFY_MULT with single attr to " + "BT_ATT_OP_NOTIFY"); + } + + ret = bt_att_send(conn, buf); + if (ret < 0) + { + net_buf_unref(buf); + } + + return ret; +} + +static void notify_mult_process(struct k_work *work) +{ + int i; + + /* Send to any connection with an allocated buffer */ + + for (i = 0; i < ARRAY_SIZE(nfy_mult); i++) + { + struct net_buf **buf = &nfy_mult[i]; + + if (*buf) + { + struct bt_conn *conn = bt_conn_lookup_index(i); + + gatt_notify_mult_send(conn, *buf); + *buf = NULL; + bt_conn_unref(conn); + } + } +} + +K_WORK_DELAYABLE_DEFINE(nfy_mult_work, notify_mult_process); + +static bool gatt_cf_notify_multi(struct bt_conn *conn) +{ + struct gatt_cf_cfg *cfg; + + cfg = find_cf_cfg(conn); + if (!cfg) + { + return false; + } + + return CF_NOTIFY_MULTI(cfg); +} + +static int gatt_notify_flush(struct bt_conn *conn) +{ + int err = 0; + struct net_buf **buf = &nfy_mult[bt_conn_index(conn)]; + + if (*buf) + { + err = gatt_notify_mult_send(conn, *buf); + *buf = NULL; + } + + return err; +} + +static void cleanup_notify(struct bt_conn *conn) +{ + struct net_buf **buf = &nfy_mult[bt_conn_index(conn)]; + + net_buf_drop(buf); +} + +static void gatt_add_nfy_to_buf(struct net_buf *buf, uint16_t handle, + struct bt_gatt_notify_params *params) +{ + struct bt_att_notify_mult *nfy; + + nfy = net_buf_add(buf, sizeof(*nfy) + params->len); + nfy->handle = sys_cpu_to_le16(handle); + nfy->len = sys_cpu_to_le16(params->len); + (void)memcpy(nfy->value, params->data, params->len); +} + +#if (CONFIG_BT_GATT_NOTIFY_MULTIPLE_FLUSH_MS != 0) +static int gatt_notify_mult(struct bt_conn *conn, uint16_t handle, + struct bt_gatt_notify_params *params) +{ + struct net_buf **buf = &nfy_mult[bt_conn_index(conn)]; + + /* Check if we can fit more data into it, in case it doesn't fit send + * the existing buffer and proceed to create a new one + */ + + if (*buf && + ((net_buf_tailroom(*buf) < + sizeof(struct bt_att_notify_mult) + params->len) || + !bt_att_tx_meta_data_match(*buf, params->func, params->user_data, + BT_ATT_CHAN_OPT(params)))) + { + int ret; + + ret = gatt_notify_mult_send(conn, *buf); + *buf = NULL; + if (ret < 0) + { + return ret; + } + } + + if (!*buf) + { + *buf = bt_att_create_pdu(conn, BT_ATT_OP_NOTIFY_MULT, + sizeof(struct bt_att_notify_mult) + + params->len); + if (!*buf) + { + return -ENOMEM; + } + + bt_att_set_tx_meta_data(*buf, params->func, params->user_data, + BT_ATT_CHAN_OPT(params)); + } + else + { + /* Increment the number of handles, ensuring the notify callback + * gets called once for every attribute. + */ + + bt_att_increment_tx_meta_data_attr_count(*buf, 1); + } + + LOG_DBG("handle 0x%04x len %u", handle, params->len); + gatt_add_nfy_to_buf(*buf, handle, params); + + /* Use `bt_work_schedule` to keep the original deadline, instead of + * re-setting the timeout whenever a new notification is appended. + */ + + bt_work_schedule(&nfy_mult_work, + MSEC2TICK(CONFIG_BT_GATT_NOTIFY_MULTIPLE_FLUSH_MS)); + + return 0; +} +#endif /* CONFIG_BT_GATT_NOTIFY_MULTIPLE_FLUSH_MS != 0 */ +#endif /* CONFIG_BT_GATT_NOTIFY_MULTIPLE */ + +static int gatt_notify(struct bt_conn *conn, + uint16_t handle, struct bt_gatt_notify_params *params) +{ + struct net_buf *buf; + struct bt_att_notify *nfy; + +#if defined(CONFIG_BT_GATT_ENFORCE_CHANGE_UNAWARE) + /* BLUETOOTH CORE SPECIFICATION Version 5.3 + * Vol 3, Part G 2.5.3 (page 1479): + * + * Except for a Handle Value indication for the Service Changed + * characteristic, the server shall not send notifications and indications + * to such a client until it becomes change-aware. + */ + + if (!bt_gatt_change_aware(conn, false)) + { + return -EAGAIN; + } +#endif + + /* Confirm that the connection has the correct level of security */ + + if (bt_gatt_check_perm(conn, params->attr, BT_GATT_PERM_READ_ENCRYPT_MASK)) + { + LOG_DBG("Link is not encrypted"); + return -EPERM; + } + + if (IS_ENABLED(CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION)) + { + /* Check if client has subscribed before sending notifications. + * This is not really required in the Bluetooth specification, but + * follows its spirit. + */ + + if (!bt_gatt_is_subscribed(conn, params->attr, BT_GATT_CCC_NOTIFY)) + { + LOG_DBG("Device is not subscribed to characteristic"); + return -EINVAL; + } + } + + if (IS_ENABLED(CONFIG_BT_EATT) && !bt_att_chan_opt_valid(conn, + BT_ATT_CHAN_OPT(params))) + { + return -EINVAL; + } + +#if defined(CONFIG_BT_GATT_NOTIFY_MULTIPLE) && (CONFIG_BT_GATT_NOTIFY_MULTIPLE_FLUSH_MS != 0) + if (gatt_cf_notify_multi(conn)) + { + return gatt_notify_mult(conn, handle, params); + } +#endif /* CONFIG_BT_GATT_NOTIFY_MULTIPLE */ + + buf = bt_att_create_pdu(conn, BT_ATT_OP_NOTIFY, sizeof(*nfy) + + params->len); + if (!buf) + { + LOG_DBG("No buffer available to send notification"); + return -ENOMEM; + } + + LOG_DBG("conn %p handle 0x%04x", conn, handle); + + nfy = net_buf_add(buf, sizeof(*nfy) + params->len); + nfy->handle = sys_cpu_to_le16(handle); + memcpy(nfy->value, params->data, params->len); + + bt_att_set_tx_meta_data(buf, params->func, params->user_data, + BT_ATT_CHAN_OPT(params)); + return bt_att_send(conn, buf); +} + +/* Converts error (negative errno) to ATT Error code */ + +static uint8_t att_err_from_int(int err) +{ + LOG_DBG("%d", err); + + /* ATT error codes are 1 byte values, so any value outside the range is + * unknown + */ + + if (!IN_RANGE(err, 0, UINT8_MAX)) + { + return BT_ATT_ERR_UNLIKELY; + } + + return err; +} + +static void gatt_indicate_rsp( + struct bt_conn *conn, int err, const void *pdu, uint16_t length, + void *user_data) +{ + struct bt_gatt_indicate_params *params = user_data; + + if (params->func) + { + params->func(conn, params, att_err_from_int(err)); + } + + params->_ref--; + if (params->destroy && (params->_ref == 0)) + { + params->destroy(params); + } +} + +static struct bt_att_req *gatt_req_alloc( + bt_att_func_t func, void *params, bt_att_encode_t encode, uint8_t op, + size_t len) +{ + struct bt_att_req *req; + + /* Allocate new request */ + + req = bt_att_req_alloc(BT_ATT_TIMEOUT); + if (!req) + { + return NULL; + } + +#if defined(CONFIG_BT_ATT_RETRY_ON_SEC_ERR) + req->att_op = op; + req->len = len; + req->encode = encode; +#else + ARG_UNUSED(encode); + ARG_UNUSED(op); + ARG_UNUSED(len); +#endif /* CONFIG_BT_ATT_RETRY_ON_SEC_ERR */ + req->func = func; + req->user_data = params; + + return req; +} + +#ifdef CONFIG_BT_GATT_CLIENT +static int gatt_req_send( + struct bt_conn *conn, bt_att_func_t func, void *params, + bt_att_encode_t encode, uint8_t op, size_t len, + enum bt_att_chan_opt chan_opt) +{ + struct bt_att_req *req; + struct net_buf *buf; + int err; + + if (IS_ENABLED(CONFIG_BT_EATT) && !bt_att_chan_opt_valid(conn, chan_opt)) + { + return -EINVAL; + } + + req = gatt_req_alloc(func, params, encode, op, len); + if (!req) + { + return -ENOMEM; + } + + buf = bt_att_create_pdu(conn, op, len); + if (!buf) + { + bt_att_req_free(req); + return -ENOMEM; + } + + bt_att_set_tx_meta_data(buf, NULL, NULL, chan_opt); + + req->buf = buf; + + err = encode(buf, len, params); + if (err) + { + bt_att_req_free(req); + return err; + } + + err = bt_att_req_send(conn, req); + if (err) + { + bt_att_req_free(req); + } + + return err; +} +#endif + +static int gatt_indicate(struct bt_conn *conn, uint16_t handle, + struct bt_gatt_indicate_params *params) +{ + struct net_buf *buf; + struct bt_att_indicate *ind; + struct bt_att_req *req; + size_t len; + int err; + +#if defined(CONFIG_BT_GATT_ENFORCE_CHANGE_UNAWARE) + /* BLUETOOTH CORE SPECIFICATION Version 5.1 | Vol 3, Part G page 2350: + * Except for the Handle Value indication, the server shall not send + * notifications and indications to such a client until it becomes + * change-aware. + */ + + if (!(params->func && (params->func == sc_indicate_rsp || + params->func == sc_restore_rsp)) && + !bt_gatt_change_aware(conn, false)) + { + return -EAGAIN; + } +#endif + + /* Confirm that the connection has the correct level of security */ + + if (bt_gatt_check_perm(conn, params->attr, BT_GATT_PERM_READ_ENCRYPT_MASK)) + { + LOG_DBG("Link is not encrypted"); + return -EPERM; + } + + if (IS_ENABLED(CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION)) + { + /* Check if client has subscribed before sending notifications. + * This is not really required in the Bluetooth specification, but + * follows its spirit. + */ + + if (!bt_gatt_is_subscribed(conn, params->attr, BT_GATT_CCC_INDICATE)) + { + LOG_DBG("Device is not subscribed to characteristic"); + return -EINVAL; + } + } + + if (IS_ENABLED(CONFIG_BT_EATT) && !bt_att_chan_opt_valid(conn, + BT_ATT_CHAN_OPT(params))) + { + return -EINVAL; + } + + len = sizeof(*ind) + params->len; + + req = gatt_req_alloc(gatt_indicate_rsp, params, NULL, BT_ATT_OP_INDICATE, + len); + if (!req) + { + return -ENOMEM; + } + + buf = bt_att_create_pdu(conn, BT_ATT_OP_INDICATE, len); + if (!buf) + { + LOG_DBG("No buffer available to send indication"); + bt_att_req_free(req); + return -ENOMEM; + } + + bt_att_set_tx_meta_data(buf, NULL, NULL, BT_ATT_CHAN_OPT(params)); + + ind = net_buf_add(buf, sizeof(*ind) + params->len); + ind->handle = sys_cpu_to_le16(handle); + memcpy(ind->value, params->data, params->len); + + LOG_DBG("conn %p handle 0x%04x", conn, handle); + + req->buf = buf; + + err = bt_att_req_send(conn, req); + if (err) + { + bt_att_req_free(req); + } + + return err; +} + +static uint8_t notify_cb(const struct bt_gatt_attr *attr, + uint16_t handle, void *user_data) +{ + struct notify_data *data = user_data; + struct bt_gatt_ccc_managed_user_data *ccc; + size_t i; + + if (!is_host_managed_ccc(attr)) + { + return BT_GATT_ITER_CONTINUE; + } + + ccc = attr->user_data; + + /* Save Service Changed data if peer is not connected */ + + if (IS_ENABLED(CONFIG_BT_GATT_SERVICE_CHANGED) && ccc == &sc_ccc) + { + for (i = 0; i < ARRAY_SIZE(sc_cfg); i++) + { + struct gatt_sc_cfg *cfg = &sc_cfg[i]; + struct bt_conn *conn; + + if (bt_addr_le_eq(&cfg->peer, BT_ADDR_LE_ANY)) + { + continue; + } + + conn = bt_conn_lookup_state_le(cfg->id, &cfg->peer, + BT_CONN_CONNECTED); + if (!conn) + { + struct sc_data *sc; + + sc = (struct sc_data *)data->ind_params->data; + sc_save(cfg->id, &cfg->peer, sys_le16_to_cpu(sc->start), + sys_le16_to_cpu(sc->end)); + continue; + } + + bt_conn_unref(conn); + } + } + + /* Notify all peers configured */ + + for (i = 0; i < ARRAY_SIZE(ccc->cfg); i++) + { + struct bt_gatt_ccc_cfg *cfg = &ccc->cfg[i]; + struct bt_conn *conn; + int err; + + /* The consolidated value may be for a different peer, and the + * CCC value is a bit field: a peer may have enabled both notifications + * and indications, and Reserved bits are ignored. + */ + + if ((cfg->value & data->type) == 0U) + { + continue; + } + + conn = bt_conn_lookup_addr_le(cfg->id, &cfg->peer); + if (!conn) + { + continue; + } + + if (conn->state != BT_CONN_CONNECTED) + { + bt_conn_unref(conn); + continue; + } + + /* Confirm match if cfg is managed by application */ + + if (ccc->cfg_match && !ccc->cfg_match(conn, attr)) + { + bt_conn_unref(conn); + continue; + } + + /* Confirm that the connection has the correct level of security */ + + if (bt_gatt_check_perm(conn, attr, BT_GATT_PERM_READ_ENCRYPT_MASK)) + { + LOG_DBG("Link %p is not encrypted", (void *)conn); + bt_conn_unref(conn); + continue; + } + + /* Use the Characteristic Value handle discovered since the + * Client Characteristic Configuration descriptor may occur in any + * position within the characteristic definition after the + * Characteristic Value. Only notify or indicate devices which are + * subscribed. + */ + + if ((data->type == BT_GATT_CCC_INDICATE) && + (cfg->value & BT_GATT_CCC_INDICATE)) + { + err = gatt_indicate(conn, data->handle, data->ind_params); + if (err == 0) + { + data->ind_params->_ref++; + } + } + else if ((data->type == BT_GATT_CCC_NOTIFY) && + (cfg->value & BT_GATT_CCC_NOTIFY)) + { + err = gatt_notify(conn, data->handle, data->nfy_params); + } + else + { + err = 0; + } + + bt_conn_unref(conn); + + data->err = err; + + if (err < 0) + { + return BT_GATT_ITER_STOP; + } + } + + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t match_uuid(const struct bt_gatt_attr *attr, + uint16_t handle, void *user_data) +{ + struct notify_data *data = user_data; + + data->attr = attr; + data->handle = handle; + + return BT_GATT_ITER_STOP; +} + +static bool gatt_find_by_uuid(struct notify_data *found, + const struct bt_uuid *uuid) +{ + found->attr = NULL; + + bt_gatt_foreach_attr_type(found->handle, 0xffff, uuid, NULL, 1, match_uuid, + found); + + return found->attr ? true : false; +} + +struct bt_gatt_attr *bt_gatt_find_by_uuid( + const struct bt_gatt_attr *attr, uint16_t attr_count, + const struct bt_uuid *uuid) +{ + struct bt_gatt_attr *found = NULL; + uint16_t start_handle = bt_gatt_attr_get_handle(attr); + uint16_t end_handle = start_handle && attr_count + ? MIN( + start_handle + attr_count, + BT_ATT_LAST_ATTRIBUTE_HANDLE) : + BT_ATT_LAST_ATTRIBUTE_HANDLE; + + if (attr != NULL && start_handle == 0U) + { + /* If start_handle is 0 then `attr` is not in our database, and should + * not be used as a starting point for the search + */ + + LOG_DBG("Could not find handle of attr %p", attr); + return NULL; + } + + bt_gatt_foreach_attr_type(start_handle, + end_handle, + uuid, + NULL, + 1, + find_next, + &found); + + return found; +} + +/* This function is meant to resolve attr to the characteristic value + * attribute. If attr is a characteristic declaration, returns the next + * attribute (the value). Otherwise returns attr unchanged. + */ + +static const struct bt_gatt_attr *bt_gatt_attr_resolve_value( + const struct bt_gatt_attr *attr) +{ + /* Due to the following requirement in the Core Spec Version 6.3 | Vol 3, + * Part G, + * Section 3.3 we can always assume that the next attribute is the + * Characteristic Value: The Characteristic Value declaration shall exist + * immediately following the characteristic declaration. + */ + + if (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CHRC) == 0) + { + return bt_gatt_attr_next(attr); + } + + return attr; +} + +int bt_gatt_notify_cb(struct bt_conn *conn, + struct bt_gatt_notify_params *params) +{ + struct notify_data data; + + DEBUGASSERT(params); + DEBUGASSERT(params->attr || params->uuid); + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + if (conn && conn->state != BT_CONN_CONNECTED) + { + return -ENOTCONN; + } + + data.attr = params->attr; + data.handle = bt_gatt_attr_get_handle(data.attr); + + /* Lookup UUID if it was given */ + + if (params->uuid) + { + if (!gatt_find_by_uuid(&data, params->uuid)) + { + return -ENOENT; + } + + params->attr = data.attr; + } + else + { + if (!data.handle) + { + return -ENOENT; + } + } + + /* Check if attribute is a characteristic then adjust the handle */ + + if (!bt_uuid_cmp(data.attr->uuid, BT_UUID_GATT_CHRC)) + { + struct bt_gatt_chrc *chrc = data.attr->user_data; + + if (!(chrc->properties & BT_GATT_CHRC_NOTIFY)) + { + return -EINVAL; + } + + data.handle = bt_gatt_attr_value_handle(data.attr); + } + + /* If attribute is a characteristic declaration, resolve to the value + * attribute to ensure the correct permissions are checked + */ + + params->attr = bt_gatt_attr_resolve_value(data.attr); + if (params->attr == NULL) + { + return -EINVAL; + } + + if (conn) + { + return gatt_notify(conn, data.handle, params); + } + + data.err = -ENOTCONN; + data.type = BT_GATT_CCC_NOTIFY; + data.nfy_params = params; + + bt_gatt_foreach_attr_type(data.handle, 0xffff, BT_UUID_GATT_CCC, NULL, 1, + notify_cb, &data); + + return data.err; +} + +#if defined(CONFIG_BT_GATT_NOTIFY_MULTIPLE) +static int gatt_notify_multiple_verify_args(struct bt_conn *conn, + struct bt_gatt_notify_params params[], + uint16_t num_params) +{ + DEBUGASSERT(params); + DEBUGASSERT(params->attr); + + if (num_params < 2) + { + /* Use the standard notification API when sending only one + * notification. + */ + + return -EINVAL; + } + + if (conn == NULL) + { + /* Use the standard notification API to send to all connected + * peers. + */ + + return -EINVAL; + } + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + if (conn->state != BT_CONN_CONNECTED) + { + return -ENOTCONN; + } + +#if defined(CONFIG_BT_GATT_ENFORCE_CHANGE_UNAWARE) + /* BLUETOOTH CORE SPECIFICATION Version 5.3 + * Vol 3, Part G 2.5.3 (page 1479): + * + * Except for a Handle Value indication for the Service Changed + * characteristic, the server shall not send notifications and indications + * to such a client until it becomes change-aware. + */ + + if (!bt_gatt_change_aware(conn, false)) + { + return -EAGAIN; + } +#endif + + /* This API guarantees an ATT_MULTIPLE_HANDLE_VALUE_NTF over the air. */ + + if (!gatt_cf_notify_multi(conn)) + { + return -EOPNOTSUPP; + } + + return 0; +} + +static int gatt_notify_multiple_verify_params(struct bt_conn *conn, + struct bt_gatt_notify_params params[], + uint16_t num_params, + size_t *total_len) +{ + const struct bt_gatt_attr *attr = NULL; + + for (uint16_t i = 0; i < num_params; i++) + { + /* Compute the total data length. */ + + *total_len += params[i].len; + + /* If attribute is a characteristic declaration, resolve to the value + * attribute to ensure the correct permissions are checked + */ + + attr = bt_gatt_attr_resolve_value(params[i].attr); + if (attr == NULL) + { + return -EINVAL; + } + + /* Confirm that the connection has the correct level of security. */ + + if (bt_gatt_check_perm(conn, attr, BT_GATT_PERM_READ_ENCRYPT_MASK)) + { + LOG_DBG("Link %p is not encrypted", (void *)conn); + return -EPERM; + } + + /* The current implementation requires the same callbacks and + * user_data. + */ + + if ((params[0].func != params[i].func) || + (params[0].user_data != params[i].user_data)) + { + return -EINVAL; + } + + /* This API doesn't support passing UUIDs. */ + + if (params[i].uuid) + { + return -EINVAL; + } + + /* Check if the supplied handle is invalid. */ + + if (!bt_gatt_attr_get_handle(params[i].attr)) + { + return -EINVAL; + } + + /* Check if the characteristic is subscribed. */ + + if (IS_ENABLED(CONFIG_BT_GATT_ENFORCE_SUBSCRIPTION) && + !bt_gatt_is_subscribed(conn, params[i].attr, BT_GATT_CCC_NOTIFY)) + { + LOG_WRN("Device is not subscribed to characteristic"); + return -EINVAL; + } + } + + /* PDU length is specified with a 16-bit value. */ + + if (*total_len > UINT16_MAX) + { + return -ERANGE; + } + + /* Check there is a bearer with a high enough MTU. */ + + if (bt_att_get_mtu(conn) < + (sizeof(struct bt_att_notify_mult) + *total_len)) + { + return -ERANGE; + } + + return 0; +} + +int bt_gatt_notify_multiple(struct bt_conn *conn, uint16_t num_params, + struct bt_gatt_notify_params params[]) +{ + int err; + size_t total_len = 0; + struct net_buf *buf; + + /* Validate arguments, connection state and feature support. */ + + err = gatt_notify_multiple_verify_args(conn, params, num_params); + if (err) + { + return err; + } + + /* Validate all the attributes that we want to notify. + * Also gets us the total length of the PDU as a side-effect. + */ + + err = gatt_notify_multiple_verify_params(conn, params, num_params, + &total_len); + if (err) + { + return err; + } + + /* Send any outstanding notifications. + * Frees up buffer space for our PDU. + */ + + gatt_notify_flush(conn); + + /* Build the PDU */ + + buf = bt_att_create_pdu(conn, BT_ATT_OP_NOTIFY_MULT, + sizeof(struct bt_att_notify_mult) + total_len); + if (!buf) + { + return -ENOMEM; + } + + /* Register the callback. It will be called num_params times. */ + + bt_att_set_tx_meta_data(buf, params->func, params->user_data, + BT_ATT_CHAN_OPT(params)); + bt_att_increment_tx_meta_data_attr_count(buf, num_params - 1); + + for (uint16_t i = 0; i < num_params; i++) + { + struct notify_data data; + + data.attr = params[i].attr; + data.handle = bt_gatt_attr_get_handle(data.attr); + + /* Check if attribute is a characteristic then adjust the + * handle + */ + + if (!bt_uuid_cmp(data.attr->uuid, BT_UUID_GATT_CHRC)) + { + data.handle = bt_gatt_attr_value_handle(data.attr); + } + + /* Add handle and data to the command buffer. */ + + gatt_add_nfy_to_buf(buf, data.handle, ¶ms[i]); + } + + /* Send the buffer. */ + + return gatt_notify_mult_send(conn, buf); +} +#endif /* CONFIG_BT_GATT_NOTIFY_MULTIPLE */ + +int bt_gatt_indicate(struct bt_conn *conn, + struct bt_gatt_indicate_params *params) +{ + struct notify_data data; + + DEBUGASSERT(params); + DEBUGASSERT(params->attr || params->uuid); + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + if (conn && conn->state != BT_CONN_CONNECTED) + { + return -ENOTCONN; + } + + data.attr = params->attr; + data.handle = bt_gatt_attr_get_handle(data.attr); + + /* Lookup UUID if it was given */ + + if (params->uuid) + { + if (!gatt_find_by_uuid(&data, params->uuid)) + { + return -ENOENT; + } + + params->attr = data.attr; + } + else + { + if (!data.handle) + { + return -ENOENT; + } + } + + /* Check if attribute is a characteristic then adjust the handle */ + + if (!bt_uuid_cmp(data.attr->uuid, BT_UUID_GATT_CHRC)) + { + struct bt_gatt_chrc *chrc = data.attr->user_data; + + if (!(chrc->properties & BT_GATT_CHRC_INDICATE)) + { + return -EINVAL; + } + + data.handle = bt_gatt_attr_value_handle(data.attr); + } + + /* If attribute is a characteristic declaration, resolve to the value + * attribute to ensure the correct permissions are checked + */ + + params->attr = bt_gatt_attr_resolve_value(data.attr); + if (params->attr == NULL) + { + return -EINVAL; + } + + if (conn) + { + params->_ref = 1; + return gatt_indicate(conn, data.handle, params); + } + + data.err = -ENOTCONN; + data.type = BT_GATT_CCC_INDICATE; + data.ind_params = params; + + params->_ref = 0; + bt_gatt_foreach_attr_type(data.handle, 0xffff, BT_UUID_GATT_CCC, NULL, 1, + notify_cb, &data); + + return data.err; +} + +uint16_t bt_gatt_get_mtu(struct bt_conn *conn) +{ + return bt_att_get_mtu(conn); +} + +uint16_t bt_gatt_get_uatt_mtu(struct bt_conn *conn) +{ + return bt_att_get_uatt_mtu(conn); +} + +uint8_t bt_gatt_check_perm(struct bt_conn *conn, + const struct bt_gatt_attr *attr, uint16_t mask) +{ + if ((mask & BT_GATT_PERM_READ) && + (!(attr->perm & BT_GATT_PERM_READ_MASK) || + !attr->read)) + { + return BT_ATT_ERR_READ_NOT_PERMITTED; + } + + if ((mask & BT_GATT_PERM_WRITE) && + (!(attr->perm & BT_GATT_PERM_WRITE_MASK) || + !attr->write)) + { + return BT_ATT_ERR_WRITE_NOT_PERMITTED; + } + + if (IS_ENABLED(CONFIG_BT_CONN_DISABLE_SECURITY)) + { + return 0; + } + + mask &= attr->perm; + + /* Core Specification 5.4 Vol. 3 Part C 10.3.1 + * + * If neither an LTK nor an STK is available, the service request shall be + * rejected with the error code ā€œInsufficient Authenticationā€. Note: + * When the link is not encrypted, + * the error code ā€œInsufficient Authenticationā€ + * does not indicate that MITM protection is required. + * + * If an LTK or an STK is available and encryption is required (LE security + * mode 1) but encryption is not enabled, the service request shall be + * rejected with the error code ā€œInsufficient Encryptionā€. + */ + + if (mask & (BT_GATT_PERM_ENCRYPT_MASK | BT_GATT_PERM_AUTHEN_MASK | + BT_GATT_PERM_LESC_MASK)) + { +#if defined(CONFIG_BT_SMP) + if (!conn->encrypt) + { + if (bt_conn_ltk_present(conn)) + { + return BT_ATT_ERR_INSUFFICIENT_ENCRYPTION; + } + else + { + return BT_ATT_ERR_AUTHENTICATION; + } + } + + if (mask & BT_GATT_PERM_AUTHEN_MASK) + { + if (bt_conn_get_security(conn) < BT_SECURITY_L3) + { + return BT_ATT_ERR_AUTHENTICATION; + } + } + + if (mask & BT_GATT_PERM_LESC_MASK) + { + const struct bt_keys *keys = conn->le.keys; + + if (!keys || (keys->flags & BT_KEYS_SC) == 0) + { + return BT_ATT_ERR_AUTHENTICATION; + } + } +#else + return BT_ATT_ERR_AUTHENTICATION; +#endif /* CONFIG_BT_SMP */ + } + + return 0; +} + +static void sc_restore_rsp( + struct bt_conn *conn, struct bt_gatt_indicate_params *params, uint8_t err) +{ +#if defined(CONFIG_BT_GATT_CACHING) + struct gatt_cf_cfg *cfg; +#endif + + LOG_DBG("err 0x%02x", err); + +#if defined(CONFIG_BT_GATT_CACHING) + /* BLUETOOTH CORE SPECIFICATION Version 5.3 | Vol 3, Part G page 1476: + * 2.5.2.1 Robust Caching ... a change-unaware connected client using + * exactly one ATT bearer becomes change-aware when ... The client receives + * and confirms a Handle Value Indication for the Service Changed + * characteristic + */ + + if (bt_att_fixed_chan_only(conn)) + { + cfg = find_cf_cfg(conn); + if (cfg && CF_ROBUST_CACHING(cfg)) + { + set_change_aware(cfg, true); + } + } +#endif /* CONFIG_BT_GATT_CACHING */ + + if (!err && IS_ENABLED(CONFIG_BT_GATT_SERVICE_CHANGED)) + { + struct gatt_sc_cfg *gsc_cfg = find_sc_cfg(conn->id, &conn->le.dst); + + if (gsc_cfg) + { + sc_reset(gsc_cfg); + } + } +} + +static struct bt_gatt_indicate_params sc_restore_params[CONFIG_BT_MAX_CONN]; +static uint16_t sc_range[CONFIG_BT_MAX_CONN][2]; + +static void sc_restore(struct bt_conn *conn) +{ + struct gatt_sc_cfg *cfg; + uint8_t index; + + cfg = find_sc_cfg(conn->id, &conn->le.dst); + if (!cfg) + { + LOG_DBG("no SC data found"); + return; + } + + if (!(cfg->data.start || cfg->data.end)) + { + return; + } + + LOG_DBG("peer %s start 0x%04x end 0x%04x", bt_addr_le_str(&cfg->peer), + cfg->data.start, + cfg->data.end); + + index = bt_conn_index(conn); + + sc_range[index][0] = sys_cpu_to_le16(cfg->data.start); + sc_range[index][1] = sys_cpu_to_le16(cfg->data.end); + + sc_restore_params[index].attr = &_1_gatt_svc.attrs[2]; + sc_restore_params[index].func = sc_restore_rsp; + sc_restore_params[index].data = &sc_range[index][0]; + sc_restore_params[index].len = sizeof(sc_range[index]); + + if (bt_gatt_indicate(conn, &sc_restore_params[index])) + { + LOG_ERR("SC restore indication failed"); + } +} + +struct conn_data +{ + struct bt_conn *conn; + bt_security_t sec; +}; + +static uint8_t update_ccc(const struct bt_gatt_attr *attr, + uint16_t handle, void *user_data) +{ + struct conn_data *data = user_data; + struct bt_conn *conn = data->conn; + struct bt_gatt_ccc_managed_user_data *ccc; + size_t i; + uint8_t err; + + if (!is_host_managed_ccc(attr)) + { + return BT_GATT_ITER_CONTINUE; + } + + ccc = attr->user_data; + + for (i = 0; i < ARRAY_SIZE(ccc->cfg); i++) + { + struct bt_gatt_ccc_cfg *cfg = &ccc->cfg[i]; + + /* Ignore configuration for different peer or not active */ + + if (!cfg->value || !bt_conn_is_peer_addr_le(conn, cfg->id, &cfg->peer)) + { + continue; + } + + /* Check if attribute requires encryption/authentication */ + + err = bt_gatt_check_perm(conn, attr, BT_GATT_PERM_WRITE_MASK); + if (err) + { + bt_security_t sec; + + if (err == BT_ATT_ERR_WRITE_NOT_PERMITTED) + { + LOG_WRN("CCC %p not writable", attr); + continue; + } + + sec = BT_SECURITY_L2; + + if (err == BT_ATT_ERR_AUTHENTICATION) + { + sec = BT_SECURITY_L3; + } + + /* Check if current security is enough */ + + if (IS_ENABLED(CONFIG_BT_SMP) && bt_conn_get_security(conn) < sec) + { + if (data->sec < sec) + { + data->sec = sec; + } + + continue; + } + } + + gatt_ccc_changed(attr, ccc); + + if (IS_ENABLED(CONFIG_BT_GATT_SERVICE_CHANGED) && ccc == &sc_ccc) + { + sc_restore(conn); + } + + return BT_GATT_ITER_CONTINUE; + } + + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t disconnected_cb(const struct bt_gatt_attr *attr, + uint16_t handle, void *user_data) +{ + struct bt_conn *conn = user_data; + struct bt_gatt_ccc_managed_user_data *ccc; + bool value_used; + size_t i; + + if (!is_host_managed_ccc(attr)) + { + return BT_GATT_ITER_CONTINUE; + } + + ccc = attr->user_data; + + /* If already disabled skip */ + + if (!ccc->value) + { + return BT_GATT_ITER_CONTINUE; + } + + /* Checking if all values are disabled */ + + value_used = false; + + for (i = 0; i < ARRAY_SIZE(ccc->cfg); i++) + { + struct bt_gatt_ccc_cfg *cfg = &ccc->cfg[i]; + + /* Ignore configurations with disabled value */ + + if (!cfg->value) + { + continue; + } + + if (!bt_conn_is_peer_addr_le(conn, cfg->id, &cfg->peer)) + { + struct bt_conn *tmp; + + /* Skip if there is another peer connected */ + + tmp = bt_conn_lookup_addr_le(cfg->id, &cfg->peer); + if (tmp) + { + if (tmp->state == BT_CONN_CONNECTED) + { + value_used = true; + } + + bt_conn_unref(tmp); + } + } + else + { + /* Clear value if not paired */ + + if (!bt_le_bond_exists(conn->id, &conn->le.dst)) + { + if (ccc == &sc_ccc) + { + sc_clear(conn); + } + + clear_ccc_cfg(cfg); + } + else + { + /* Update address in case it has changed */ + + bt_addr_le_copy(&cfg->peer, &conn->le.dst); + } + } + } + + /* If all values are now disabled, reset value while disconnected */ + + if (!value_used) + { + ccc->value = 0U; + if (ccc->cfg_changed) + { + ccc->cfg_changed(attr, ccc->value); + } + + LOG_DBG("ccc %p reset", ccc); + } + + return BT_GATT_ITER_CONTINUE; +} + +bool bt_gatt_is_subscribed(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + uint16_t ccc_type) +{ + uint16_t ccc_bits; + uint8_t ccc_bits_encoded[sizeof(ccc_bits)]; + ssize_t len; + + DEBUGASSERT(conn); + DEBUGASSERT(attr); + + if (conn->state != BT_CONN_CONNECTED) + { + return false; + } + + /* Check if attribute is a characteristic declaration */ + + if (!bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CHRC)) + { + uint8_t properties; + + if (!attr->read) + { + LOG_ERR("Read method not set"); + return false; + } + + /* The characteristic properties is the first byte of the attribute + * value + */ + + len = attr->read(NULL, attr, &properties, sizeof(properties), 0); + if (len < 0) + { + LOG_ERR("Failed to read attribute %p (err %zd)", attr, len); + return false; + } + else if (len != sizeof(properties)) + { + LOG_ERR("Invalid read length: %zd", len); + return false; + } + + if (!(properties & (BT_GATT_CHRC_NOTIFY | BT_GATT_CHRC_INDICATE))) + { + /* Characteristic doesn't support subscription */ + + return false; + } + + attr = bt_gatt_attr_next(attr); + DEBUGASSERT(attr); + } + + /* Check if attribute is a characteristic value */ + + if (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CCC) != 0) + { + attr = bt_gatt_attr_next(attr); + DEBUGASSERT(attr); + } + + /* Find the CCC Descriptor */ + + while (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CCC) && + + /* Also stop if we leave the current characteristic definition */ + + bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CHRC) && + bt_uuid_cmp(attr->uuid, BT_UUID_GATT_PRIMARY) && + bt_uuid_cmp(attr->uuid, BT_UUID_GATT_SECONDARY)) + { + attr = bt_gatt_attr_next(attr); + if (!attr) + { + return false; + } + } + + if (bt_uuid_cmp(attr->uuid, BT_UUID_GATT_CCC) != 0) + { + return false; + } + + if (!attr->read) + { + LOG_ERR("Read method not set"); + return false; + } + + len = attr->read(conn, attr, ccc_bits_encoded, sizeof(ccc_bits_encoded), + 0); + if (len < 0) + { + LOG_ERR("Failed to read attribute %p (err %zd)", attr, len); + return false; + } + else if (len != sizeof(ccc_bits_encoded)) + { + LOG_ERR("Invalid read length: %zd", len); + return false; + } + + ccc_bits = sys_get_le16(ccc_bits_encoded); + + /* Check if the CCC bits match the subscription type */ + + if (ccc_bits & ccc_type) + { + return true; + } + + return false; +} + +static bool gatt_sub_is_empty(struct gatt_sub *sub) +{ + return list_is_empty(&sub->list); +} + +/** @brief Free sub for reuse. + */ + +static void gatt_sub_free(struct gatt_sub *sub) +{ + DEBUGASSERT(gatt_sub_is_empty(sub)); + bt_addr_le_copy(&sub->peer, BT_ADDR_LE_ANY); +} + +static void gatt_sub_remove( + struct bt_conn *conn, struct gatt_sub *sub, struct list_node *prev, + struct bt_gatt_subscribe_params *params) +{ + if (params) + { + /* Remove subscription from the list */ + + list_remove(&sub->list, prev, ¶ms->node); + + /* Notify removal */ + + params->notify(conn, params, NULL, 0); + } + + if (gatt_sub_is_empty(sub)) + { + gatt_sub_free(sub); + } +} + +#if defined(CONFIG_BT_GATT_CLIENT) +static struct gatt_sub *gatt_sub_find(struct bt_conn *conn) +{ + for (int i = 0; i < ARRAY_SIZE(subscriptions); i++) + { + struct gatt_sub *sub = &subscriptions[i]; + + if (!conn) + { + if (bt_addr_le_eq(&sub->peer, BT_ADDR_LE_ANY)) + { + return sub; + } + } + else if (bt_conn_is_peer_addr_le(conn, sub->id, &sub->peer)) + { + return sub; + } + } + + return NULL; +} + +static struct gatt_sub *gatt_sub_add(struct bt_conn *conn) +{ + struct gatt_sub *sub; + + sub = gatt_sub_find(conn); + if (!sub) + { + sub = gatt_sub_find(NULL); + if (sub) + { + bt_addr_le_copy(&sub->peer, &conn->le.dst); + sub->id = conn->id; + } + } + + return sub; +} + +static struct gatt_sub *gatt_sub_find_by_addr(uint8_t id, + const bt_addr_le_t *addr) +{ + for (int i = 0; i < ARRAY_SIZE(subscriptions); i++) + { + struct gatt_sub *sub = &subscriptions[i]; + + if (id == sub->id && bt_addr_le_eq(&sub->peer, addr)) + { + return sub; + } + } + + return NULL; +} + +static struct gatt_sub *gatt_sub_add_by_addr(uint8_t id, + const bt_addr_le_t *addr) +{ + struct gatt_sub *sub; + + sub = gatt_sub_find_by_addr(id, addr); + if (!sub) + { + sub = gatt_sub_find(NULL); + if (sub) + { + bt_addr_le_copy(&sub->peer, addr); + sub->id = id; + } + } + + return sub; +} + +static bool check_subscribe_security_level(struct bt_conn *conn, + const struct bt_gatt_subscribe_params *params) +{ +#if defined(CONFIG_BT_SMP) + return conn->sec_level >= params->min_security; +#endif + return true; +} + +static void call_notify_cb_and_maybe_unsubscribe( + struct bt_conn *conn, struct gatt_sub *sub, uint16_t handle, + const void *data, uint16_t length) +{ + struct bt_gatt_subscribe_params *params; + struct bt_gatt_subscribe_params *tmp; + int err; + + /* Core Specification Vol 3, Part F, Section 3.2.9: the maximum length + * of an attribute value is 512 octets. + */ + + if (length > BT_ATT_MAX_ATTRIBUTE_LEN) + { + LOG_WRN("Ignoring value with invalid length %u for handle 0x%04x", + length, handle); + return; + } + + list_for_every_container_safe(&sub->list, params, tmp, node) + { + if (handle != params->value_handle) + { + continue; + } + + if (check_subscribe_security_level(conn, params)) + { + if (params->notify(conn, params, data, length) == BT_GATT_ITER_STOP) + { + err = bt_gatt_unsubscribe(conn, params); + if (err != 0) + { + LOG_WRN("Failed to unsubscribe (err %d)", err); + } + } + } + } +} + +void bt_gatt_notification(struct bt_conn *conn, + uint16_t handle, const void *data, uint16_t length) +{ + struct gatt_sub *sub; + + LOG_DBG("handle 0x%04x length %u", handle, length); + + sub = gatt_sub_find(conn); + if (!sub) + { + return; + } + + call_notify_cb_and_maybe_unsubscribe(conn, sub, handle, data, length); +} + +void bt_gatt_mult_notification(struct bt_conn *conn, + const void *data, uint16_t length) +{ + const struct bt_att_notify_mult *nfy; + struct net_buf_simple buf; + struct gatt_sub *sub; + + LOG_DBG("length %u", length); + + sub = gatt_sub_find(conn); + if (!sub) + { + return; + } + + /* This is fine since there no write operation to the buffer. */ + + net_buf_simple_init_with_data(&buf, (void *)data, length); + + while (buf.len > sizeof(*nfy)) + { + uint16_t handle; + uint16_t len; + + nfy = net_buf_simple_pull_mem(&buf, sizeof(*nfy)); + handle = sys_cpu_to_le16(nfy->handle); + len = sys_cpu_to_le16(nfy->len); + + LOG_DBG("handle 0x%02x len %u", handle, len); + + if (len > buf.len) + { + LOG_ERR("Invalid data len %u > %u", len, length); + return; + } + + call_notify_cb_and_maybe_unsubscribe(conn, sub, handle, nfy->value, + len); + + net_buf_simple_pull_mem(&buf, len); + } +} + +static void gatt_sub_update(struct bt_conn *conn, struct gatt_sub *sub) +{ + if (sub->peer.type == BT_ADDR_LE_PUBLIC) + { + return; + } + + /* Update address */ + + bt_addr_le_copy(&sub->peer, &conn->le.dst); +} + +static void remove_subscriptions(struct bt_conn *conn) +{ + struct gatt_sub *sub; + struct bt_gatt_subscribe_params *params; + struct bt_gatt_subscribe_params *tmp; + struct list_node *prev = NULL; + + sub = gatt_sub_find(conn); + if (!sub) + { + return; + } + + /* Lookup existing subscriptions */ + + list_for_every_container_safe(&sub->list, params, tmp, node) + { + atomic_clear_bit(params->flags, BT_GATT_SUBSCRIBE_FLAG_SENT); + + if (!bt_le_bond_exists(conn->id, &conn->le.dst) || + (atomic_test_bit(params->flags, BT_GATT_SUBSCRIBE_FLAG_VOLATILE))) + { + /* Remove subscription */ + + params->value = 0U; + gatt_sub_remove(conn, sub, prev, params); + } + else + { + gatt_sub_update(conn, sub); + prev = ¶ms->node; + } + } +} + +static void gatt_mtu_rsp( + struct bt_conn *conn, int err, const void *pdu, uint16_t length, + void *user_data) +{ + struct bt_gatt_exchange_params *params = user_data; + + params->func(conn, att_err_from_int(err), params); +} + +static int gatt_exchange_mtu_encode(struct net_buf *buf, + size_t len, void *user_data) +{ + struct bt_att_exchange_mtu_req *req; + uint16_t mtu; + + mtu = BT_LOCAL_ATT_MTU_UATT; + + LOG_DBG("Client MTU %u", mtu); + + req = net_buf_add(buf, sizeof(*req)); + req->mtu = sys_cpu_to_le16(mtu); + + return 0; +} + +int bt_gatt_exchange_mtu(struct bt_conn *conn, + struct bt_gatt_exchange_params *params) +{ + int err; + + DEBUGASSERT(conn); + DEBUGASSERT(params && params->func); + + if (conn->state != BT_CONN_CONNECTED) + { + return -ENOTCONN; + } + + /* This request shall only be sent once during a connection by the client. + */ + + if (atomic_test_and_set_bit(conn->flags, BT_CONN_ATT_MTU_EXCHANGED)) + { + return -EALREADY; + } + + err = gatt_req_send(conn, gatt_mtu_rsp, params, gatt_exchange_mtu_encode, + BT_ATT_OP_MTU_REQ, + sizeof(struct bt_att_exchange_mtu_req), + BT_ATT_CHAN_OPT_UNENHANCED_ONLY); + if (err) + { + atomic_clear_bit(conn->flags, BT_CONN_ATT_MTU_EXCHANGED); + } + + return err; +} + +static void gatt_discover_next(struct bt_conn *conn, uint16_t last_handle, + struct bt_gatt_discover_params *params) +{ + /* Skip if last_handle is not set */ + + if (!last_handle) + { + goto discover; + } + + /* Continue from the last found handle */ + + params->start_handle = last_handle; + if (params->start_handle < UINT16_MAX) + { + params->start_handle++; + } + else + { + goto done; + } + + /* Stop if over the range or the requests */ + + if (params->start_handle > params->end_handle) + { + goto done; + } + +discover: + + /* Discover next range */ + + if (!bt_gatt_discover(conn, params)) + { + return; + } + +done: + params->func(conn, NULL, params); +} + +static void gatt_find_type_rsp( + struct bt_conn *conn, int err, const void *pdu, uint16_t length, + void *user_data) +{ + const struct bt_att_handle_group *rsp = pdu; + struct bt_gatt_discover_params *params = user_data; + uint8_t count; + uint16_t end_handle = 0U; + uint16_t start_handle; + + LOG_DBG("err %d", err); + + if (err || (length % sizeof(struct bt_att_handle_group) != 0)) + { + goto done; + } + + count = length / sizeof(struct bt_att_handle_group); + + /* Parse attributes found */ + + for (uint8_t i = 0U; i < count; i++) + { + struct bt_uuid_16 uuid_svc; + struct bt_gatt_attr attr; + struct bt_gatt_service_val value; + + start_handle = sys_le16_to_cpu(rsp[i].start_handle); + end_handle = sys_le16_to_cpu(rsp[i].end_handle); + + LOG_DBG("start_handle 0x%04x end_handle 0x%04x", start_handle, + end_handle); + + uuid_svc.uuid.type = BT_UUID_TYPE_16; + if (params->type == BT_GATT_DISCOVER_PRIMARY) + { + uuid_svc.val = BT_UUID_GATT_PRIMARY_VAL; + } + else + { + uuid_svc.val = BT_UUID_GATT_SECONDARY_VAL; + } + + value.end_handle = end_handle; + value.uuid = params->uuid; + + attr = (struct bt_gatt_attr) + { + .uuid = &uuid_svc.uuid, + .user_data = &value, + .handle = start_handle, + }; + + if (params->func(conn, &attr, params) == BT_GATT_ITER_STOP) + { + return; + } + } + + gatt_discover_next(conn, end_handle, params); + + return; +done: + params->func(conn, NULL, params); +} + +static int gatt_find_type_encode(struct net_buf *buf, + size_t len, void *user_data) +{ + struct bt_gatt_discover_params *params = user_data; + struct bt_att_find_type_req *req; + uint16_t uuid_val; + + req = net_buf_add(buf, sizeof(*req)); + req->start_handle = sys_cpu_to_le16(params->start_handle); + req->end_handle = sys_cpu_to_le16(params->end_handle); + + if (params->type == BT_GATT_DISCOVER_PRIMARY) + { + uuid_val = BT_UUID_GATT_PRIMARY_VAL; + } + else + { + uuid_val = BT_UUID_GATT_SECONDARY_VAL; + } + + req->type = sys_cpu_to_le16(uuid_val); + + LOG_DBG("uuid %s start_handle 0x%04x end_handle 0x%04x", + bt_uuid_str(params->uuid), + params->start_handle, params->end_handle); + + switch (params->uuid->type) + { + case BT_UUID_TYPE_16: + net_buf_add_le16(buf, BT_UUID_16(params->uuid)->val); + break; + case BT_UUID_TYPE_128: + net_buf_add_mem(buf, BT_UUID_128(params->uuid)->val, 16); + break; + } + + return 0; +} + +static int gatt_find_type(struct bt_conn *conn, + struct bt_gatt_discover_params *params) +{ + size_t len; + + len = sizeof(struct bt_att_find_type_req); + + switch (params->uuid->type) + { + case BT_UUID_TYPE_16: + len += BT_UUID_SIZE_16; + break; + case BT_UUID_TYPE_128: + len += BT_UUID_SIZE_128; + break; + default: + LOG_ERR("Unknown UUID type %u", params->uuid->type); + return -EINVAL; + } + + return gatt_req_send(conn, gatt_find_type_rsp, params, + gatt_find_type_encode, + BT_ATT_OP_FIND_TYPE_REQ, len, + BT_ATT_CHAN_OPT(params)); +} + +static void read_included_uuid_cb( + struct bt_conn *conn, int err, const void *pdu, uint16_t length, + void *user_data) +{ + struct bt_gatt_discover_params *params = user_data; + struct bt_gatt_include value; + struct bt_gatt_attr attr; + uint16_t handle; + union + { + struct bt_uuid uuid; + struct bt_uuid_128 u128; + } u; + + if (length != 16U) + { + LOG_ERR("Invalid data len %u", length); + params->func(conn, NULL, params); + return; + } + + handle = params->_included.attr_handle; + value.start_handle = params->_included.start_handle; + value.end_handle = params->_included.end_handle; + value.uuid = &u.uuid; + u.uuid.type = BT_UUID_TYPE_128; + memcpy(u.u128.val, pdu, length); + + LOG_DBG("handle 0x%04x uuid %s start_handle 0x%04x " + "end_handle 0x%04x\n", + params->_included.attr_handle, bt_uuid_str( + &u.uuid), value.start_handle, value.end_handle); + + /* Skip if UUID is set but doesn't match */ + + if (params->uuid && bt_uuid_cmp(&u.uuid, params->uuid)) + { + goto next; + } + + attr = (struct bt_gatt_attr) + { + .uuid = BT_UUID_GATT_INCLUDE, + .user_data = &value, + .handle = handle, + }; + + if (params->func(conn, &attr, params) == BT_GATT_ITER_STOP) + { + return; + } + +next: + gatt_discover_next(conn, params->start_handle, params); + + return; +} + +static int read_included_uuid_encode(struct net_buf *buf, + size_t len, void *user_data) +{ + struct bt_gatt_discover_params *params = user_data; + struct bt_att_read_req *req; + + req = net_buf_add(buf, sizeof(*req)); + req->handle = sys_cpu_to_le16(params->_included.start_handle); + + return 0; +} + +static int read_included_uuid(struct bt_conn *conn, + struct bt_gatt_discover_params *params) +{ + LOG_DBG("handle 0x%04x", params->_included.start_handle); + + return gatt_req_send(conn, read_included_uuid_cb, params, + read_included_uuid_encode, + BT_ATT_OP_READ_REQ, sizeof(struct bt_att_read_req), + BT_ATT_CHAN_OPT(params)); +} + +static uint16_t parse_include(struct bt_conn *conn, + const void *pdu, + struct bt_gatt_discover_params *params, + uint16_t length) +{ + const struct bt_att_read_type_rsp *rsp; + uint16_t handle = 0U; + struct bt_gatt_include value; + union + { + struct bt_uuid uuid; + struct bt_uuid_16 u16; + struct bt_uuid_128 u128; + } u; + + if (length < sizeof(*rsp)) + { + LOG_WRN("Parse err"); + goto done; + } + + rsp = pdu; + + /* Data can be either in UUID16 or UUID128 */ + + switch (rsp->len) + { + case 8: /* UUID16 */ + u.uuid.type = BT_UUID_TYPE_16; + break; + case 6: /* UUID128 */ + + /* BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part G] page 550 + * To get the included service UUID when the included service uses a + * 128-bit UUID, the Read Request is used. + */ + + u.uuid.type = BT_UUID_TYPE_128; + break; + default: + LOG_ERR("Invalid data len %u", rsp->len); + goto done; + } + + /* Parse include found */ + + for (length--, pdu = rsp->data; length >= rsp->len; + length -= rsp->len, pdu = (const uint8_t *)pdu + rsp->len) + { + struct bt_gatt_attr attr; + const struct bt_att_data *data = pdu; + struct gatt_incl *incl = (void *)data->value; + + handle = sys_le16_to_cpu(data->handle); + + /* Handle 0 is invalid */ + + if (!handle) + { + goto done; + } + + /* Convert include data, bt_gatt_incl and gatt_incl + * have different formats so the conversion have to be done field by + * field. + */ + + value.start_handle = sys_le16_to_cpu(incl->start_handle); + value.end_handle = sys_le16_to_cpu(incl->end_handle); + + switch (u.uuid.type) + { + case BT_UUID_TYPE_16: + value.uuid = &u.uuid; + u.u16.val = sys_le16_to_cpu(incl->uuid16); + break; + case BT_UUID_TYPE_128: + params->_included.attr_handle = handle; + params->_included.start_handle = value.start_handle; + params->_included.end_handle = value.end_handle; + + return read_included_uuid(conn, params); + } + + LOG_DBG("handle 0x%04x uuid %s start_handle 0x%04x " + "end_handle 0x%04x\n", + handle, bt_uuid_str( + &u.uuid), value.start_handle, + value.end_handle); + + /* Skip if UUID is set but doesn't match */ + + if (params->uuid && bt_uuid_cmp(&u.uuid, params->uuid)) + { + continue; + } + + attr = (struct bt_gatt_attr) + { + .uuid = BT_UUID_GATT_INCLUDE, + .user_data = &value, + .handle = handle, + }; + + if (params->func(conn, &attr, params) == BT_GATT_ITER_STOP) + { + return 0; + } + } + + /* Whole PDU read without error */ + + if (length == 0U && handle) + { + return handle; + } + +done: + params->func(conn, NULL, params); + return 0; +} + +static uint16_t parse_characteristic(struct bt_conn *conn, + const void *pdu, + struct bt_gatt_discover_params *params, + uint16_t length) +{ + const struct bt_att_read_type_rsp *rsp; + uint16_t handle = 0U; + union + { + struct bt_uuid uuid; + struct bt_uuid_16 u16; + struct bt_uuid_128 u128; + } u; + + if (length < sizeof(*rsp)) + { + LOG_WRN("Parse err"); + goto done; + } + + rsp = pdu; + + /* Data can be either in UUID16 or UUID128 */ + + switch (rsp->len) + { + case 7: /* UUID16 */ + u.uuid.type = BT_UUID_TYPE_16; + break; + case 21: /* UUID128 */ + u.uuid.type = BT_UUID_TYPE_128; + break; + default: + LOG_ERR("Invalid data len %u", rsp->len); + goto done; + } + + /* Parse characteristics found */ + + for (length--, pdu = rsp->data; length >= rsp->len; + length -= rsp->len, pdu = (const uint8_t *)pdu + rsp->len) + { + struct bt_gatt_attr attr; + struct bt_gatt_chrc value; + const struct bt_att_data *data = pdu; + struct gatt_chrc *chrc = (void *)data->value; + + handle = sys_le16_to_cpu(data->handle); + + /* Handle 0 is invalid */ + + if (!handle) + { + goto done; + } + + switch (u.uuid.type) + { + case BT_UUID_TYPE_16: + u.u16.val = sys_le16_to_cpu(chrc->uuid16); + break; + case BT_UUID_TYPE_128: + memcpy(u.u128.val, chrc->uuid, sizeof(chrc->uuid)); + break; + } + + LOG_DBG("handle 0x%04x uuid %s properties 0x%02x", handle, + bt_uuid_str(&u.uuid), + chrc->properties); + + /* Skip if UUID is set but doesn't match */ + + if (params->uuid && bt_uuid_cmp(&u.uuid, params->uuid)) + { + continue; + } + + value = (struct bt_gatt_chrc)BT_GATT_CHRC_INIT(&u.uuid, + sys_le16_to_cpu(chrc->value_handle), + chrc->properties); + + attr = (struct bt_gatt_attr) + { + .uuid = BT_UUID_GATT_CHRC, + .user_data = &value, + .handle = handle, + }; + + if (params->func(conn, &attr, params) == BT_GATT_ITER_STOP) + { + return 0; + } + } + + /* Whole PDU read without error */ + + if (length == 0U && handle) + { + return handle; + } + +done: + params->func(conn, NULL, params); + return 0; +} + +static uint16_t parse_read_std_char_desc( + struct bt_conn *conn, const void *pdu, + struct bt_gatt_discover_params *params, uint16_t length) +{ + const struct bt_att_read_type_rsp *rsp; + const struct bt_att_data *data; + uint16_t handle = 0U; + uint16_t uuid_val; + + if (params->uuid->type != BT_UUID_TYPE_16) + { + goto done; + } + + uuid_val = BT_UUID_16(params->uuid)->val; + + if (length < sizeof(*rsp)) + { + LOG_WRN("Parse err"); + goto done; + } + + rsp = pdu; + + if (rsp->len < sizeof(*data)) + { + LOG_WRN("Invalid data len %u", rsp->len); + goto done; + } + + /* Parse characteristics found */ + + for (length--, pdu = rsp->data; length >= rsp->len; + length -= rsp->len, pdu = (const uint8_t *)pdu + rsp->len) + { + union + { + struct bt_gatt_ccc ccc; + struct bt_gatt_cpf cpf; + struct bt_gatt_cep cep; + struct bt_gatt_scc scc; + } value; + + struct bt_gatt_attr attr; + + if (length < sizeof(*data)) + { + LOG_WRN("Parse err dat"); + goto done; + } + + data = pdu; + + handle = sys_le16_to_cpu(data->handle); + + /* Handle 0 is invalid */ + + if (!handle) + { + goto done; + } + + switch (uuid_val) + { + case BT_UUID_GATT_CEP_VAL: + if (length < sizeof(*data) + sizeof(uint16_t)) + { + LOG_WRN("Parse err cep"); + goto done; + } + + value.cep.properties = sys_get_le16(data->value); + break; + case BT_UUID_GATT_CCC_VAL: + if (length < sizeof(*data) + sizeof(uint16_t)) + { + LOG_WRN("Parse err ccc"); + goto done; + } + + value.ccc.flags = sys_get_le16(data->value); + break; + case BT_UUID_GATT_SCC_VAL: + if (length < sizeof(*data) + sizeof(uint16_t)) + { + LOG_WRN("Parse err scc"); + goto done; + } + + value.scc.flags = sys_get_le16(data->value); + break; + case BT_UUID_GATT_CPF_VAL: + { + struct gatt_cpf *cpf; + + if (length < sizeof(*data) + sizeof(*cpf)) + { + LOG_WRN("Parse err cpf"); + goto done; + } + + cpf = (void *)data->value; + + value.cpf.format = cpf->format; + value.cpf.exponent = cpf->exponent; + value.cpf.unit = sys_le16_to_cpu(cpf->unit); + value.cpf.name_space = cpf->name_space; + value.cpf.description = sys_le16_to_cpu(cpf->description); + break; + } + + default: + goto done; + } + + attr = (struct bt_gatt_attr) + { + .uuid = params->uuid, + .user_data = &value, + .handle = handle, + }; + + if (params->func(conn, &attr, params) == BT_GATT_ITER_STOP) + { + return 0; + } + } + + /* Whole PDU read without error */ + + if (length == 0U && handle) + { + return handle; + } + +done: + params->func(conn, NULL, params); + return 0; +} + +static void gatt_read_type_rsp( + struct bt_conn *conn, int err, const void *pdu, uint16_t length, + void *user_data) +{ + struct bt_gatt_discover_params *params = user_data; + uint16_t handle; + + LOG_DBG("err %d", err); + + if (err) + { + params->func(conn, NULL, params); + return; + } + + if (params->type == BT_GATT_DISCOVER_INCLUDE) + { + handle = parse_include(conn, pdu, params, length); + } + else if (params->type == BT_GATT_DISCOVER_CHARACTERISTIC) + { + handle = parse_characteristic(conn, pdu, params, length); + } + else + { + handle = parse_read_std_char_desc(conn, pdu, params, length); + } + + if (!handle) + { + return; + } + + gatt_discover_next(conn, handle, params); +} + +static int gatt_read_type_encode(struct net_buf *buf, + size_t len, void *user_data) +{ + struct bt_gatt_discover_params *params = user_data; + struct bt_att_read_type_req *req; + + req = net_buf_add(buf, sizeof(*req)); + req->start_handle = sys_cpu_to_le16(params->start_handle); + req->end_handle = sys_cpu_to_le16(params->end_handle); + + switch (params->type) + { + case BT_GATT_DISCOVER_INCLUDE: + net_buf_add_le16(buf, BT_UUID_GATT_INCLUDE_VAL); + break; + case BT_GATT_DISCOVER_CHARACTERISTIC: + net_buf_add_le16(buf, BT_UUID_GATT_CHRC_VAL); + break; + default: + + /* Only 16-bit UUIDs supported */ + + net_buf_add_le16(buf, BT_UUID_16(params->uuid)->val); + break; + } + + return 0; +} + +static int gatt_read_type(struct bt_conn *conn, + struct bt_gatt_discover_params *params) +{ + LOG_DBG("start_handle 0x%04x end_handle 0x%04x", params->start_handle, + params->end_handle); + + return gatt_req_send(conn, gatt_read_type_rsp, params, + gatt_read_type_encode, + BT_ATT_OP_READ_TYPE_REQ, + sizeof(struct bt_att_read_type_req), + BT_ATT_CHAN_OPT(params)); +} + +static uint16_t parse_service(struct bt_conn *conn, + const void *pdu, + struct bt_gatt_discover_params *params, + uint16_t length) +{ + const struct bt_att_read_group_rsp *rsp; + uint16_t start_handle; + uint16_t end_handle = 0U; + union + { + struct bt_uuid uuid; + struct bt_uuid_16 u16; + struct bt_uuid_128 u128; + } u; + + if (length < sizeof(*rsp)) + { + LOG_WRN("Parse err"); + goto done; + } + + rsp = pdu; + + /* Data can be either in UUID16 or UUID128 */ + + switch (rsp->len) + { + case 6: /* UUID16 */ + u.uuid.type = BT_UUID_TYPE_16; + break; + case 20: /* UUID128 */ + u.uuid.type = BT_UUID_TYPE_128; + break; + default: + LOG_ERR("Invalid data len %u", rsp->len); + goto done; + } + + /* Parse services found */ + + for (length--, pdu = rsp->data; length >= rsp->len; + length -= rsp->len, pdu = (const uint8_t *)pdu + rsp->len) + { + struct bt_uuid_16 uuid_svc; + struct bt_gatt_attr attr = + { + }; + + struct bt_gatt_service_val value; + const struct bt_att_group_data *data = pdu; + + start_handle = sys_le16_to_cpu(data->start_handle); + if (!start_handle) + { + goto done; + } + + end_handle = sys_le16_to_cpu(data->end_handle); + if (!end_handle || end_handle < start_handle) + { + goto done; + } + + switch (u.uuid.type) + { + case BT_UUID_TYPE_16: + memcpy(&u.u16.val, data->value, sizeof(u.u16.val)); + u.u16.val = sys_le16_to_cpu(u.u16.val); + break; + case BT_UUID_TYPE_128: + memcpy(u.u128.val, data->value, sizeof(u.u128.val)); + break; + } + + LOG_DBG("start_handle 0x%04x end_handle 0x%04x uuid %s", start_handle, + end_handle, + bt_uuid_str(&u.uuid)); + + uuid_svc.uuid.type = BT_UUID_TYPE_16; + if (params->type == BT_GATT_DISCOVER_PRIMARY) + { + uuid_svc.val = BT_UUID_GATT_PRIMARY_VAL; + } + else + { + uuid_svc.val = BT_UUID_GATT_SECONDARY_VAL; + } + + value.end_handle = end_handle; + value.uuid = &u.uuid; + + attr.uuid = &uuid_svc.uuid; + attr.handle = start_handle; + attr.user_data = &value; + + if (params->func(conn, &attr, params) == BT_GATT_ITER_STOP) + { + return 0; + } + } + + /* Whole PDU read without error */ + + if (length == 0U && end_handle) + { + return end_handle; + } + +done: + params->func(conn, NULL, params); + return 0; +} + +static void gatt_read_group_rsp( + struct bt_conn *conn, int err, const void *pdu, uint16_t length, + void *user_data) +{ + struct bt_gatt_discover_params *params = user_data; + uint16_t handle; + + LOG_DBG("err %d", err); + + if (err) + { + params->func(conn, NULL, params); + return; + } + + handle = parse_service(conn, pdu, params, length); + if (!handle) + { + return; + } + + gatt_discover_next(conn, handle, params); +} + +static int gatt_read_group_encode(struct net_buf *buf, + size_t len, void *user_data) +{ + struct bt_gatt_discover_params *params = user_data; + struct bt_att_read_group_req *req; + + req = net_buf_add(buf, sizeof(*req)); + req->start_handle = sys_cpu_to_le16(params->start_handle); + req->end_handle = sys_cpu_to_le16(params->end_handle); + + if (params->type == BT_GATT_DISCOVER_PRIMARY) + { + net_buf_add_le16(buf, BT_UUID_GATT_PRIMARY_VAL); + } + else + { + net_buf_add_le16(buf, BT_UUID_GATT_SECONDARY_VAL); + } + + return 0; +} + +static int gatt_read_group(struct bt_conn *conn, + struct bt_gatt_discover_params *params) +{ + LOG_DBG("start_handle 0x%04x end_handle 0x%04x", params->start_handle, + params->end_handle); + + return gatt_req_send(conn, gatt_read_group_rsp, params, + gatt_read_group_encode, + BT_ATT_OP_READ_GROUP_REQ, + sizeof(struct bt_att_read_group_req), + BT_ATT_CHAN_OPT(params)); +} + +static void gatt_find_info_rsp( + struct bt_conn *conn, int err, const void *pdu, uint16_t length, + void *user_data) +{ + const struct bt_att_find_info_rsp *rsp; + struct bt_gatt_discover_params *params = user_data; + uint16_t handle = 0U; + uint16_t len; + union + { + const struct bt_att_info_16 *i16; + const struct bt_att_info_128 *i128; + } info; + + union + { + struct bt_uuid uuid; + struct bt_uuid_16 u16; + struct bt_uuid_128 u128; + } u; + + int i; + bool skip = false; + + LOG_DBG("err %d", err); + + if (err) + { + goto done; + } + + if (length < sizeof(*rsp)) + { + LOG_WRN("Parse err"); + goto done; + } + + rsp = pdu; + + /* Data can be either in UUID16 or UUID128 */ + + switch (rsp->format) + { + case BT_ATT_INFO_16: + u.uuid.type = BT_UUID_TYPE_16; + len = sizeof(*info.i16); + break; + case BT_ATT_INFO_128: + u.uuid.type = BT_UUID_TYPE_128; + len = sizeof(*info.i128); + break; + default: + LOG_ERR("Invalid format %u", rsp->format); + goto done; + } + + length--; + + /* Check if there is a least one descriptor in the response */ + + if (length < len) + { + goto done; + } + + /* Parse descriptors found */ + + for (i = length / len, pdu = rsp->info; i != 0; i--, + pdu = (const uint8_t *)pdu + len) + { + struct bt_gatt_attr attr; + + info.i16 = pdu; + handle = sys_le16_to_cpu(info.i16->handle); + + if (skip) + { + skip = false; + continue; + } + + switch (u.uuid.type) + { + case BT_UUID_TYPE_16: + u.u16.val = sys_le16_to_cpu(info.i16->uuid); + break; + case BT_UUID_TYPE_128: + memcpy(u.u128.val, info.i128->uuid, 16); + break; + } + + LOG_DBG("handle 0x%04x uuid %s", handle, bt_uuid_str(&u.uuid)); + + /* Skip if UUID is set but doesn't match */ + + if (params->uuid && bt_uuid_cmp(&u.uuid, params->uuid)) + { + continue; + } + + if (params->type == BT_GATT_DISCOVER_DESCRIPTOR) + { + /* Skip attributes that are not considered + * descriptors. + */ + + if (!bt_uuid_cmp(&u.uuid, BT_UUID_GATT_PRIMARY) || + !bt_uuid_cmp(&u.uuid, BT_UUID_GATT_SECONDARY) || + !bt_uuid_cmp(&u.uuid, BT_UUID_GATT_INCLUDE)) + { + continue; + } + + /* If Characteristic Declaration skip ahead as the next + * entry must be its value. + */ + + if (!bt_uuid_cmp(&u.uuid, BT_UUID_GATT_CHRC)) + { + skip = true; + continue; + } + } + + /* No user_data in this case */ + + attr = (struct bt_gatt_attr) + { + .uuid = &u.uuid, + .handle = handle, + }; + + if (params->func(conn, &attr, params) == BT_GATT_ITER_STOP) + { + return; + } + } + + gatt_discover_next(conn, handle, params); + + return; + +done: + params->func(conn, NULL, params); +} + +static int gatt_find_info_encode(struct net_buf *buf, + size_t len, void *user_data) +{ + struct bt_gatt_discover_params *params = user_data; + struct bt_att_find_info_req *req; + + req = net_buf_add(buf, sizeof(*req)); + req->start_handle = sys_cpu_to_le16(params->start_handle); + req->end_handle = sys_cpu_to_le16(params->end_handle); + + return 0; +} + +static int gatt_find_info(struct bt_conn *conn, + struct bt_gatt_discover_params *params) +{ + LOG_DBG("start_handle 0x%04x end_handle 0x%04x", params->start_handle, + params->end_handle); + + return gatt_req_send(conn, gatt_find_info_rsp, params, + gatt_find_info_encode, + BT_ATT_OP_FIND_INFO_REQ, + sizeof(struct bt_att_find_info_req), + BT_ATT_CHAN_OPT(params)); +} + +int bt_gatt_discover(struct bt_conn *conn, + struct bt_gatt_discover_params *params) +{ + DEBUGASSERT(conn); + DEBUGASSERT(params && params->func); + DEBUGASSERT((params->start_handle && params->end_handle)); + DEBUGASSERT((params->start_handle <= params->end_handle)); + + if (conn->state != BT_CONN_CONNECTED) + { + return -ENOTCONN; + } + + switch (params->type) + { + case BT_GATT_DISCOVER_PRIMARY: + case BT_GATT_DISCOVER_SECONDARY: + if (params->uuid) + { + return gatt_find_type(conn, params); + } + + return gatt_read_group(conn, params); + + case BT_GATT_DISCOVER_STD_CHAR_DESC: + if (!(params->uuid && params->uuid->type == BT_UUID_TYPE_16 && + (!bt_uuid_cmp(params->uuid, BT_UUID_GATT_CEP) || + !bt_uuid_cmp(params->uuid, BT_UUID_GATT_CCC) || + !bt_uuid_cmp(params->uuid, BT_UUID_GATT_SCC) || + !bt_uuid_cmp(params->uuid, BT_UUID_GATT_CPF)))) + { + return -EINVAL; + } + + __fallthrough; + case BT_GATT_DISCOVER_INCLUDE: + case BT_GATT_DISCOVER_CHARACTERISTIC: + return gatt_read_type(conn, params); + case BT_GATT_DISCOVER_DESCRIPTOR: + + /* Only descriptors can be filtered */ + + if (params->uuid && (!bt_uuid_cmp(params->uuid, + BT_UUID_GATT_PRIMARY) || + !bt_uuid_cmp(params->uuid, + BT_UUID_GATT_SECONDARY) || + !bt_uuid_cmp(params->uuid, + BT_UUID_GATT_INCLUDE) || + !bt_uuid_cmp(params->uuid, BT_UUID_GATT_CHRC))) + { + return -EINVAL; + } + + __fallthrough; + case BT_GATT_DISCOVER_ATTRIBUTE: + return gatt_find_info(conn, params); + default: + LOG_DBG("Invalid discovery type: %u", params->type); + } + + return -EINVAL; +} + +static void parse_read_by_uuid( + struct bt_conn *conn, struct bt_gatt_read_params *params, const void *pdu, + uint16_t length) +{ + const struct bt_att_read_type_rsp *rsp = pdu; + + const uint16_t req_start_handle = params->by_uuid.start_handle; + const uint16_t req_end_handle = params->by_uuid.end_handle; + + /* Parse values found */ + + for (length--, pdu = rsp->data; length; length -= rsp->len, + pdu = (const uint8_t *)pdu + rsp->len) + { + const struct bt_att_data *data = pdu; + uint16_t handle; + uint16_t len; + + len = MIN(rsp->len, length); + if (len < sizeof(struct bt_att_data)) + { + LOG_WRN("Bad peer: ATT read-by-uuid rsp: invalid ATTR PDU len %u", + len); + params->func(conn, BT_ATT_ERR_INVALID_PDU, params, NULL, 0); + return; + } + + len -= sizeof(struct bt_att_data); + + handle = sys_le16_to_cpu(data->handle); + + /* Handle 0 is invalid */ + + if (!handle) + { + LOG_ERR("Invalid handle"); + return; + } + + LOG_DBG("handle 0x%04x len %u value %u", handle, rsp->len, len); + + if (!IN_RANGE(handle, req_start_handle, req_end_handle)) + { + LOG_WRN("Bad peer: ATT read-by-uuid rsp: " + "Handle 0x%04x is outside requested range 0x%04x-0x%04x. " + "Aborting read.", + handle, req_start_handle, req_end_handle); + params->func(conn, BT_ATT_ERR_UNLIKELY, params, NULL, 0); + return; + } + + /* Update start_handle */ + + params->by_uuid.start_handle = handle; + + if (params->func(conn, 0, params, data->value, + len) == BT_GATT_ITER_STOP) + { + return; + } + + /* Check if long attribute */ + + if (rsp->len > length) + { + break; + } + + /* Stop if it's the last handle to be read */ + + if (params->by_uuid.start_handle == params->by_uuid.end_handle) + { + params->func(conn, 0, params, NULL, 0); + return; + } + + params->by_uuid.start_handle++; + } + + /* Continue reading the attributes */ + + if (bt_gatt_read(conn, params) < 0) + { + params->func(conn, BT_ATT_ERR_UNLIKELY, params, NULL, 0); + } +} + +static void gatt_read_rsp( + struct bt_conn *conn, int err, const void *pdu, uint16_t length, + void *user_data) +{ + struct bt_gatt_read_params *params = user_data; + + LOG_DBG("err %d", err); + + if (err || !length) + { + params->func(conn, att_err_from_int(err), params, NULL, 0); + return; + } + + if (!params->handle_count) + { + parse_read_by_uuid(conn, params, pdu, length); + return; + } + + if (params->func(conn, 0, params, pdu, length) == BT_GATT_ITER_STOP) + { + return; + } + + /* Core Spec 4.2, Vol. 3, Part G, 4.8.1 + * If the Characteristic Value is greater than (ATT_MTU - 1) octets in + * length, the Read Long Characteristic Value procedure may be used if the + * rest of the Characteristic Value is required. + * + * Note: Both BT_ATT_OP_READ_RSP and BT_ATT_OP_READ_BLOB_RSP have an + * overhead of one octet. + */ + + if (length < (params->_att_mtu - 1)) + { + params->func(conn, 0, params, NULL, 0); + return; + } + + params->single.offset += length; + + /* Continue reading the attribute */ + + if (bt_gatt_read(conn, params) < 0) + { + params->func(conn, BT_ATT_ERR_UNLIKELY, params, NULL, 0); + } +} + +static int gatt_read_blob_encode(struct net_buf *buf, + size_t len, void *user_data) +{ + struct bt_gatt_read_params *params = user_data; + struct bt_att_read_blob_req *req; + + req = net_buf_add(buf, sizeof(*req)); + req->handle = sys_cpu_to_le16(params->single.handle); + req->offset = sys_cpu_to_le16(params->single.offset); + + return 0; +} + +static int gatt_read_blob(struct bt_conn *conn, + struct bt_gatt_read_params *params) +{ + LOG_DBG("handle 0x%04x offset 0x%04x", params->single.handle, + params->single.offset); + + return gatt_req_send(conn, gatt_read_rsp, params, gatt_read_blob_encode, + BT_ATT_OP_READ_BLOB_REQ, + sizeof(struct bt_att_read_blob_req), + BT_ATT_CHAN_OPT(params)); +} + +static int gatt_read_uuid_encode(struct net_buf *buf, + size_t len, void *user_data) +{ + struct bt_gatt_read_params *params = user_data; + struct bt_att_read_type_req *req; + + req = net_buf_add(buf, sizeof(*req)); + req->start_handle = sys_cpu_to_le16(params->by_uuid.start_handle); + req->end_handle = sys_cpu_to_le16(params->by_uuid.end_handle); + + if (params->by_uuid.uuid->type == BT_UUID_TYPE_16) + { + net_buf_add_le16(buf, BT_UUID_16(params->by_uuid.uuid)->val); + } + else + { + net_buf_add_mem(buf, BT_UUID_128(params->by_uuid.uuid)->val, 16); + } + + return 0; +} + +static int gatt_read_uuid(struct bt_conn *conn, + struct bt_gatt_read_params *params) +{ + LOG_DBG("start_handle 0x%04x end_handle 0x%04x uuid %s", + params->by_uuid.start_handle, + params->by_uuid.end_handle, bt_uuid_str(params->by_uuid.uuid)); + + return gatt_req_send(conn, gatt_read_rsp, params, gatt_read_uuid_encode, + BT_ATT_OP_READ_TYPE_REQ, + sizeof(struct bt_att_read_type_req), + BT_ATT_CHAN_OPT(params)); +} + +#if defined(CONFIG_BT_GATT_READ_MULTIPLE) +static void gatt_read_mult_rsp( + struct bt_conn *conn, int err, const void *pdu, uint16_t length, + void *user_data) +{ + struct bt_gatt_read_params *params = user_data; + + LOG_DBG("err %d", err); + + if (err || !length) + { + params->func(conn, att_err_from_int(err), params, NULL, 0); + return; + } + + params->func(conn, 0, params, pdu, length); + + /* mark read as complete since read multiple is single response */ + + params->func(conn, 0, params, NULL, 0); +} + +static int gatt_read_mult_encode(struct net_buf *buf, + size_t len, void *user_data) +{ + struct bt_gatt_read_params *params = user_data; + uint8_t i; + + for (i = 0U; i < params->handle_count; i++) + { + net_buf_add_le16(buf, params->multiple.handles[i]); + } + + return 0; +} + +static int gatt_read_mult(struct bt_conn *conn, + struct bt_gatt_read_params *params) +{ + LOG_DBG("handle_count %zu", params->handle_count); + + return gatt_req_send(conn, gatt_read_mult_rsp, params, + gatt_read_mult_encode, + BT_ATT_OP_READ_MULT_REQ, + params->handle_count * sizeof(uint16_t), + BT_ATT_CHAN_OPT(params)); +} + +#else +static int gatt_read_mult(struct bt_conn *conn, + struct bt_gatt_read_params *params) +{ + return -ENOTSUP; +} +#endif /* CONFIG_BT_GATT_READ_MULTIPLE */ + +#if defined(CONFIG_BT_GATT_READ_MULT_VAR_LEN) +static void gatt_read_mult_vl_rsp( + struct bt_conn *conn, int err, const void *pdu, uint16_t length, + void *user_data) +{ + struct bt_gatt_read_params *params = user_data; + const struct bt_att_read_mult_vl_rsp *rsp; + struct net_buf_simple buf; + + LOG_DBG("err %d", err); + + if (err || !length) + { + params->func(conn, att_err_from_int(err), params, NULL, 0); + return; + } + + net_buf_simple_init_with_data(&buf, (void *)pdu, length); + + while (buf.len >= sizeof(*rsp)) + { + uint16_t len; + + rsp = net_buf_simple_pull_mem(&buf, sizeof(*rsp)); + len = sys_le16_to_cpu(rsp->len); + + /* If a Length Value Tuple is truncated, then the amount of + * Attribute Value will be less than the value of the Value Length + * field. + */ + + if (len > buf.len) + { + len = buf.len; + } + + params->func(conn, 0, params, rsp->value, len); + + net_buf_simple_pull_mem(&buf, len); + } + + /* mark read as complete since read multiple is single response */ + + params->func(conn, 0, params, NULL, 0); +} + +static int gatt_read_mult_vl_encode(struct net_buf *buf, + size_t len, void *user_data) +{ + struct bt_gatt_read_params *params = user_data; + uint8_t i; + + for (i = 0U; i < params->handle_count; i++) + { + net_buf_add_le16(buf, params->multiple.handles[i]); + } + + return 0; +} + +static int gatt_read_mult_vl(struct bt_conn *conn, + struct bt_gatt_read_params *params) +{ + LOG_DBG("handle_count %zu", params->handle_count); + + return gatt_req_send(conn, gatt_read_mult_vl_rsp, params, + gatt_read_mult_vl_encode, + BT_ATT_OP_READ_MULT_VL_REQ, + params->handle_count * sizeof(uint16_t), + BT_ATT_CHAN_OPT(params)); +} + +#else +static int gatt_read_mult_vl(struct bt_conn *conn, + struct bt_gatt_read_params *params) +{ + return -ENOTSUP; +} +#endif /* CONFIG_BT_GATT_READ_MULT_VAR_LEN */ + +static int gatt_read_encode(struct net_buf *buf, size_t len, void *user_data) +{ + struct bt_gatt_read_params *params = user_data; + struct bt_att_read_req *req; + + req = net_buf_add(buf, sizeof(*req)); + req->handle = sys_cpu_to_le16(params->single.handle); + + return 0; +} + +int bt_gatt_read(struct bt_conn *conn, struct bt_gatt_read_params *params) +{ + DEBUGASSERT(conn); + DEBUGASSERT(params && params->func); + + if (conn->state != BT_CONN_CONNECTED) + { + return -ENOTCONN; + } + + if (params->handle_count == 0) + { + return gatt_read_uuid(conn, params); + } + + if (params->handle_count > 1) + { + if (params->multiple.variable) + { + return gatt_read_mult_vl(conn, params); + } + else + { + return gatt_read_mult(conn, params); + } + } + + if (params->single.offset) + { + return gatt_read_blob(conn, params); + } + + LOG_DBG("handle 0x%04x", params->single.handle); + + return gatt_req_send(conn, gatt_read_rsp, params, gatt_read_encode, + BT_ATT_OP_READ_REQ, + sizeof(struct bt_att_read_req), + BT_ATT_CHAN_OPT(params)); +} + +static void gatt_write_rsp( + struct bt_conn *conn, int err, const void *pdu, uint16_t length, + void *user_data) +{ + struct bt_gatt_write_params *params = user_data; + + LOG_DBG("err %d", err); + + params->func(conn, att_err_from_int(err), params); +} + +int bt_gatt_write_without_response_cb( + struct bt_conn *conn, uint16_t handle, const void *data, uint16_t length, + bool sign, bt_gatt_complete_func_t func, void *user_data) +{ + struct net_buf *buf; + struct bt_att_write_cmd *cmd; + __maybe_unused size_t write; + + DEBUGASSERT(conn); + DEBUGASSERT(handle); + + if (conn->state != BT_CONN_CONNECTED) + { + return -ENOTCONN; + } + +#if defined(CONFIG_BT_SMP) + if (conn->encrypt) + { + /* Don't need to sign if already encrypted */ + + sign = false; + } +#endif + + if (sign) + { + buf = bt_att_create_pdu(conn, BT_ATT_OP_SIGNED_WRITE_CMD, + sizeof(*cmd) + length + 12); + } + else + { + buf = bt_att_create_pdu(conn, BT_ATT_OP_WRITE_CMD, + sizeof(*cmd) + length); + } + + if (!buf) + { + return -ENOMEM; + } + + cmd = net_buf_add(buf, sizeof(*cmd)); + cmd->handle = sys_cpu_to_le16(handle); + + write = net_buf_append_bytes(buf, length, data, 0, NULL, NULL); + DEBUGASSERT(write == length); + + LOG_DBG("handle 0x%04x length %u", handle, length); + + bt_att_set_tx_meta_data(buf, func, user_data, BT_ATT_CHAN_OPT_NONE); + + return bt_att_send(conn, buf); +} + +static int gatt_exec_encode(struct net_buf *buf, size_t len, void *user_data) +{ + struct bt_att_exec_write_req *req; + + req = net_buf_add(buf, sizeof(*req)); + req->flags = BT_ATT_FLAG_EXEC; + + return 0; +} + +static int gatt_exec_write(struct bt_conn *conn, + struct bt_gatt_write_params *params) +{ + LOG_DBG(""); + + return gatt_req_send(conn, gatt_write_rsp, params, gatt_exec_encode, + BT_ATT_OP_EXEC_WRITE_REQ, + sizeof(struct bt_att_exec_write_req), + BT_ATT_CHAN_OPT(params)); +} + +static int gatt_cancel_encode(struct net_buf *buf, + size_t len, void *user_data) +{ + struct bt_att_exec_write_req *req; + + req = net_buf_add(buf, sizeof(*req)); + req->flags = BT_ATT_FLAG_CANCEL; + + return 0; +} + +static int gatt_cancel_all_writes(struct bt_conn *conn, + struct bt_gatt_write_params *params) +{ + LOG_DBG(""); + + return gatt_req_send(conn, gatt_write_rsp, params, gatt_cancel_encode, + BT_ATT_OP_EXEC_WRITE_REQ, + sizeof(struct bt_att_exec_write_req), + BT_ATT_CHAN_OPT(params)); +} + +static void gatt_prepare_write_rsp( + struct bt_conn *conn, int err, const void *pdu, uint16_t length, + void *user_data) +{ + struct bt_gatt_write_params *params = user_data; + const struct bt_att_prepare_write_rsp *rsp; + size_t len; + bool data_valid; + + LOG_DBG("err %d", err); + + /* Don't continue in case of error */ + + if (err) + { + params->func(conn, att_err_from_int(err), params); + return; + } + + if (length < sizeof(*rsp)) + { + LOG_WRN("Parse err"); + goto fail; + } + + rsp = pdu; + + len = length - sizeof(*rsp); + if (len > params->length) + { + LOG_ERR("Incorrect length, canceling write"); + if (gatt_cancel_all_writes(conn, params)) + { + goto fail; + } + + return; + } + + data_valid = memcmp(params->data, rsp->value, len) == 0; + if (params->offset != rsp->offset || !data_valid) + { + LOG_ERR("Incorrect offset or data in response, canceling write"); + if (gatt_cancel_all_writes(conn, params)) + { + goto fail; + } + + return; + } + + /* Update params */ + + params->offset += len; + params->data = (const uint8_t *)params->data + len; + params->length -= len; + + /* If there is no more data execute */ + + if (!params->length) + { + if (gatt_exec_write(conn, params)) + { + goto fail; + } + + return; + } + + /* Write next chunk */ + + if (!bt_gatt_write(conn, params)) + { + /* Success */ + + return; + } + +fail: + + /* Notify application that the write operation has failed */ + + params->func(conn, BT_ATT_ERR_UNLIKELY, params); +} + +static int gatt_prepare_write_encode(struct net_buf *buf, + size_t len, void *user_data) +{ + struct bt_gatt_write_params *params = user_data; + struct bt_att_prepare_write_req *req; + size_t write; + + req = net_buf_add(buf, sizeof(*req)); + req->handle = sys_cpu_to_le16(params->handle); + req->offset = sys_cpu_to_le16(params->offset); + + write = net_buf_append_bytes(buf, len - sizeof(*req), + (uint8_t *)params->data, 0, NULL, NULL); + if (write != (len - sizeof(*req))) + { + return -ENOMEM; + } + + return 0; +} + +static int gatt_prepare_write(struct bt_conn *conn, + struct bt_gatt_write_params *params) +{ + uint16_t len; + uint16_t req_len; + uint16_t mtu = bt_att_get_mtu(conn); + + req_len = sizeof(struct bt_att_prepare_write_req); + + /** MTU size is bigger than the ATT_PREPARE_WRITE_REQ header (5 bytes), + * unless there's no connection. + */ + + if (mtu == 0) + { + return -ENOTCONN; + } + + len = mtu - req_len - 1; + len = MIN(params->length, len); + len += req_len; + + return gatt_req_send(conn, gatt_prepare_write_rsp, params, + gatt_prepare_write_encode, + BT_ATT_OP_PREPARE_WRITE_REQ, len, + BT_ATT_CHAN_OPT(params)); +} + +static int gatt_write_encode(struct net_buf *buf, + size_t len, void *user_data) +{ + struct bt_gatt_write_params *params = user_data; + struct bt_att_write_req *req; + size_t write; + + req = net_buf_add(buf, sizeof(*req)); + req->handle = sys_cpu_to_le16(params->handle); + + write = net_buf_append_bytes(buf, params->length, params->data, 0, NULL, + NULL); + if (write != params->length) + { + return -ENOMEM; + } + + return 0; +} + +int bt_gatt_write(struct bt_conn *conn, struct bt_gatt_write_params *params) +{ + size_t len; + + DEBUGASSERT(conn); + DEBUGASSERT(params && params->func); + DEBUGASSERT(params->handle); + + if (conn->state != BT_CONN_CONNECTED) + { + return -ENOTCONN; + } + + len = sizeof(struct bt_att_write_req) + params->length; + + /* Use Prepare Write if offset is set or Long Write is required */ + + if (params->offset || len > (bt_att_get_mtu(conn) - 1)) + { + return gatt_prepare_write(conn, params); + } + + LOG_DBG("handle 0x%04x length %u", params->handle, params->length); + + return gatt_req_send(conn, gatt_write_rsp, params, gatt_write_encode, + BT_ATT_OP_WRITE_REQ, len, + BT_ATT_CHAN_OPT(params)); +} + +static void gatt_write_ccc_rsp( + struct bt_conn *conn, int err, const void *pdu, uint16_t length, + void *user_data) +{ + struct bt_gatt_subscribe_params *params = user_data; + uint8_t att_err; + + LOG_DBG("err %d", err); + + atomic_clear_bit(params->flags, BT_GATT_SUBSCRIBE_FLAG_WRITE_PENDING); + + /* if write to CCC failed we remove subscription and notify app */ + + if (err) + { + struct gatt_sub *sub; + struct list_node *node; + struct list_node *tmp; + struct list_node *prev; + + sub = gatt_sub_find(conn); + if (!sub) + { + return; + } + + att_err = att_err_from_int(err); + + if (params->subscribe) + { + params->subscribe(conn, att_err, params); + } + + prev = NULL; + + list_for_every_safe(&sub->list, node, tmp) + { + if (node == ¶ms->node) + { + gatt_sub_remove(conn, sub, prev, params); + break; + } + + prev = node; + } + } + else + { + if (params->subscribe) + { + params->subscribe(conn, BT_ATT_ERR_SUCCESS, params); + } + + if (!params->value) + { + /* Notify with NULL data to complete unsubscribe */ + + params->notify(conn, params, NULL, 0); + } + } +} + +static int gatt_write_ccc_buf(struct net_buf *buf, + size_t len, void *user_data) +{ + struct bt_gatt_subscribe_params *params = user_data; + struct bt_att_write_req *write_req; + + write_req = net_buf_add(buf, sizeof(*write_req)); + write_req->handle = sys_cpu_to_le16(params->ccc_handle); + net_buf_add_le16(buf, params->value); + + atomic_set_bit(params->flags, BT_GATT_SUBSCRIBE_FLAG_WRITE_PENDING); + + return 0; +} + +static int gatt_write_ccc( + struct bt_conn *conn, struct bt_gatt_subscribe_params *params, + bt_att_func_t rsp) +{ + size_t len = sizeof(struct bt_att_write_req) + sizeof(uint16_t); + + LOG_DBG("handle 0x%04x value 0x%04x", params->ccc_handle, params->value); + + /* The value of the params doesn't matter, this is just so we don't + * repeat CCC writes when the AUTO_RESUBSCRIBE quirk is enabled. + */ + + atomic_set_bit(params->flags, BT_GATT_SUBSCRIBE_FLAG_SENT); + + return gatt_req_send(conn, rsp, params, gatt_write_ccc_buf, + BT_ATT_OP_WRITE_REQ, len, + BT_ATT_CHAN_OPT(params)); +} + +#if defined(CONFIG_BT_GATT_AUTO_DISCOVER_CCC) +static uint8_t gatt_ccc_discover_cb( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + struct bt_gatt_subscribe_params *sub_params = params->sub_params; + + if (!attr) + { + memset(params, 0, sizeof(*params)); + sub_params->notify(conn, sub_params, NULL, 0); + return BT_GATT_ITER_STOP; + } + + if (params->type == BT_GATT_DISCOVER_DESCRIPTOR) + { + memset(params, 0, sizeof(*params)); + sub_params->ccc_handle = attr->handle; + + if (bt_gatt_subscribe(conn, sub_params)) + { + sub_params->notify(conn, sub_params, NULL, 0); + } + + /* else if no error occurred, then `bt_gatt_subscribe` will + * call the notify function once subscribed. + */ + + return BT_GATT_ITER_STOP; + } + + return BT_GATT_ITER_CONTINUE; +} + +static int gatt_ccc_discover(struct bt_conn *conn, + struct bt_gatt_subscribe_params *params) +{ + int err; + static struct bt_uuid_16 ccc_uuid = BT_UUID_INIT_16(0); + + memcpy(&ccc_uuid, BT_UUID_GATT_CCC, sizeof(ccc_uuid)); + memset(params->disc_params, 0, sizeof(*params->disc_params)); + + params->disc_params->sub_params = params; + params->disc_params->uuid = &ccc_uuid.uuid; + params->disc_params->type = BT_GATT_DISCOVER_DESCRIPTOR; + params->disc_params->start_handle = params->value_handle; + params->disc_params->end_handle = params->end_handle; + params->disc_params->func = gatt_ccc_discover_cb; +#if defined(CONFIG_BT_EATT) + params->disc_params->chan_opt = params->chan_opt; +#endif /* CONFIG_BT_EATT */ + + err = bt_gatt_discover(conn, params->disc_params); + if (err) + { + LOG_DBG("CCC Discovery failed (err %d)", err); + return err; + } + + return 0; +} +#endif /* CONFIG_BT_GATT_AUTO_DISCOVER_CCC */ + +int bt_gatt_subscribe(struct bt_conn *conn, + struct bt_gatt_subscribe_params *params) +{ + struct gatt_sub *sub; + struct bt_gatt_subscribe_params *tmp; + bool has_subscription = false; + + DEBUGASSERT(conn); + DEBUGASSERT(params && params->notify); + DEBUGASSERT(params->value); +#if defined(CONFIG_BT_GATT_AUTO_DISCOVER_CCC) + DEBUGASSERT(params->ccc_handle || (params->end_handle && + params->disc_params)); +#else + DEBUGASSERT(params->ccc_handle); +#endif + + if (conn->state != BT_CONN_CONNECTED) + { + return -ENOTCONN; + } + + sub = gatt_sub_add(conn); + if (!sub) + { + return -ENOMEM; + } + +#if defined(CONFIG_BT_GATT_AUTO_DISCOVER_CCC) + if (params->disc_params != NULL && + params->disc_params->func == gatt_ccc_discover_cb) + { + /* Already in progress */ + + return -EBUSY; + } +#endif + + /* Lookup existing subscriptions */ + + list_for_every_container(&sub->list, tmp, node) + { + /* Fail if entry already exists */ + + if (tmp == params) + { + gatt_sub_remove(conn, sub, NULL, NULL); + return -EALREADY; + } + + /* Check if another subscription exists */ + + if (tmp->value_handle == params->value_handle && + tmp->value >= params->value) + { + has_subscription = true; + } + } + + /* Skip write if already subscribed */ + + if (!has_subscription) + { + int err; + +#if defined(CONFIG_BT_GATT_AUTO_DISCOVER_CCC) + if (params->ccc_handle == BT_GATT_AUTO_DISCOVER_CCC_HANDLE) + { + return gatt_ccc_discover(conn, params); + } +#endif + + err = gatt_write_ccc(conn, params, gatt_write_ccc_rsp); + if (err) + { + gatt_sub_remove(conn, sub, NULL, NULL); + return err; + } + } + + /* Add subscription before write complete as some implementation were + * reported to send notification before reply to CCC write. + */ + + list_add_head(&sub->list, ¶ms->node); + + return 0; +} + +int bt_gatt_resubscribe(uint8_t id, const bt_addr_le_t *peer, + struct bt_gatt_subscribe_params *params) +{ + struct gatt_sub *sub; + struct bt_gatt_subscribe_params *tmp; + + DEBUGASSERT(params && params->notify); + DEBUGASSERT(params->value); + DEBUGASSERT(params->ccc_handle); + + sub = gatt_sub_add_by_addr(id, peer); + if (!sub) + { + return -ENOMEM; + } + + /* Lookup existing subscriptions */ + + list_for_every_container(&sub->list, tmp, node) + { + /* Fail if entry already exists */ + + if (tmp == params) + { + gatt_sub_remove(NULL, sub, NULL, NULL); + return -EALREADY; + } + } + + list_add_head(&sub->list, ¶ms->node); + return 0; +} + +static void gatt_cancel(struct bt_conn *conn, + void *params, bool run_completion) +{ + struct bt_att_req *req; + bt_att_func_t func = NULL; + + bt_dev_lock(); + + /* att_handle_rsp() processes the request state on the RX workqueue + * without the host lock, relying on cooperative scheduling: hold the + * scheduler lock as well so that it cannot preempt a preemptible caller + * mid-cancel. + */ + + sched_lock(); + + req = bt_att_find_req_by_user_data(conn, params); + if (req) + { + func = req->func; + bt_att_req_cancel(conn, req); + } + + sched_unlock(); + bt_dev_unlock(); + + if (run_completion && func) + { + func(conn, BT_ATT_ERR_UNLIKELY, NULL, 0, params); + } +} + +int bt_gatt_unsubscribe(struct bt_conn *conn, + struct bt_gatt_subscribe_params *params) +{ + struct gatt_sub *sub; + struct bt_gatt_subscribe_params *tmp; + bool has_subscription = false; + bool found = false; + + DEBUGASSERT(conn); + DEBUGASSERT(params); + + if (conn->state != BT_CONN_CONNECTED) + { + return -ENOTCONN; + } + + sub = gatt_sub_find(conn); + if (!sub) + { + return -EINVAL; + } + + /* Lookup existing subscriptions */ + + list_for_every_container(&sub->list, tmp, node) + { + if (params == tmp) + { + found = true; + continue; + } + + /* Check if there still remains any other subscription */ + + if (tmp->value_handle == params->value_handle) + { + has_subscription = true; + } + } + + if (!found) + { + return -EINVAL; + } + + /* Cancel any in-flight CCC write silently: its completion shares this + * params and must not run once we unsubscribe. gatt_cancel() drops the + * request without invoking it, so clear WRITE_PENDING in its place. + */ + + if (atomic_test_bit(params->flags, BT_GATT_SUBSCRIBE_FLAG_WRITE_PENDING)) + { + gatt_cancel(conn, params, false); + atomic_clear_bit(params->flags, BT_GATT_SUBSCRIBE_FLAG_WRITE_PENDING); + } + + if (!has_subscription) + { + int err; + + params->value = 0x0000; + err = gatt_write_ccc(conn, params, gatt_write_ccc_rsp); + if (err) + { + return err; + } + } + + list_find_and_remove(&sub->list, ¶ms->node); + + if (gatt_sub_is_empty(sub)) + { + gatt_sub_free(sub); + } + + if (has_subscription) + { + /* Notify with NULL data to complete unsubscribe */ + + params->notify(conn, params, NULL, 0); + } + + return 0; +} + +void bt_gatt_cancel(struct bt_conn *conn, void *params) +{ + gatt_cancel(conn, params, true); +} + +#if defined(CONFIG_BT_GATT_AUTO_RESUBSCRIBE) +static void gatt_resub_ccc_rsp( + struct bt_conn *conn, int err, const void *pdu, uint16_t length, + void *user_data) +{ + LOG_DBG("err %d", err); + + if (err == -ECONNRESET) + { + /* The resubscriptions are implicit, thus in the case of ACL + * disconnection during the CCC value ATT Write, there is no need to + * notify the application. + */ + + return; + } + + gatt_write_ccc_rsp(conn, err, pdu, length, user_data); +} + +static int gatt_resub_ccc(struct bt_conn *conn, + struct bt_gatt_subscribe_params *params) +{ + return gatt_write_ccc(conn, params, gatt_resub_ccc_rsp); +} + +static void add_subscriptions(struct bt_conn *conn) +{ + struct gatt_sub *sub; + struct bt_gatt_subscribe_params *params; + + if (!bt_le_bond_exists(conn->id, &conn->le.dst)) + { + return; + } + + sub = gatt_sub_find(conn); + if (!sub) + { + return; + } + + /* Lookup existing subscriptions */ + + list_for_every_container(&sub->list, params, node) + { + if (!atomic_test_bit(params->flags, BT_GATT_SUBSCRIBE_FLAG_SENT) && + !atomic_test_bit(params->flags, BT_GATT_SUBSCRIBE_FLAG_NO_RESUB)) + { + int err; + + /* Force write to CCC to workaround devices that don't + * track it properly. + */ + + err = gatt_resub_ccc(conn, params); + if (err < 0) + { + LOG_WRN("conn %p params %p resub failed (err %d)", (void *)conn, + params, err); + } + } + } +} +#endif /* CONFIG_BT_GATT_AUTO_RESUBSCRIBE */ + +#if defined(CONFIG_BT_GATT_AUTO_UPDATE_MTU) +static void gatt_exchange_mtu_func(struct bt_conn *conn, uint8_t err, + struct bt_gatt_exchange_params *params) +{ + if (err) + { + LOG_WRN("conn %p err 0x%02x", conn, err); + } +} + +static struct bt_gatt_exchange_params gatt_exchange_params = +{ + .func = gatt_exchange_mtu_func, +}; +#endif /* CONFIG_BT_GATT_AUTO_UPDATE_MTU */ +#endif /* CONFIG_BT_GATT_CLIENT */ + +#if defined(CONFIG_BT_SETTINGS_CCC_STORE_MAX) +#define CCC_STORE_MAX CONFIG_BT_SETTINGS_CCC_STORE_MAX +#else /* defined(CONFIG_BT_SETTINGS_CCC_STORE_MAX) */ +#define CCC_STORE_MAX 0 +#endif /* defined(CONFIG_BT_SETTINGS_CCC_STORE_MAX) */ + +static struct bt_gatt_ccc_cfg *ccc_find_cfg( + struct bt_gatt_ccc_managed_user_data *ccc, const bt_addr_le_t *addr, + uint8_t id) +{ + for (size_t i = 0; i < ARRAY_SIZE(ccc->cfg); i++) + { + if (id == ccc->cfg[i].id && bt_addr_le_eq(&ccc->cfg[i].peer, addr)) + { + return &ccc->cfg[i]; + } + } + + return NULL; +} + +struct addr_with_id +{ + const bt_addr_le_t *addr; + uint8_t id; +}; + +struct ccc_load +{ + struct addr_with_id addr_with_id; + struct ccc_store *entry; + size_t count; +}; + +static void ccc_clear( + struct bt_gatt_ccc_managed_user_data *ccc, const bt_addr_le_t *addr, + uint8_t id) +{ + struct bt_gatt_ccc_cfg *cfg; + + cfg = ccc_find_cfg(ccc, addr, id); + if (!cfg) + { + LOG_DBG("Unable to clear CCC: cfg not found"); + return; + } + + clear_ccc_cfg(cfg); +} + +static uint8_t ccc_load(const struct bt_gatt_attr *attr, + uint16_t handle, void *user_data) +{ + struct ccc_load *load = user_data; + struct bt_gatt_ccc_managed_user_data *ccc; + struct bt_gatt_ccc_cfg *cfg; + + if (!is_host_managed_ccc(attr)) + { + return BT_GATT_ITER_CONTINUE; + } + + ccc = attr->user_data; + + /* Clear if value was invalidated */ + + if (!load->entry) + { + ccc_clear(ccc, load->addr_with_id.addr, load->addr_with_id.id); + return BT_GATT_ITER_CONTINUE; + } + else if (!load->count) + { + return BT_GATT_ITER_STOP; + } + + /* Skip if value is not for the given attribute */ + + if (load->entry->handle != handle) + { + /* If attribute handle is bigger then it means + * the attribute no longer exists and cannot be restored. + */ + + if (load->entry->handle < handle) + { + LOG_DBG("Unable to restore CCC: handle 0x%04x cannot be" + " found", + load->entry->handle); + goto next; + } + + return BT_GATT_ITER_CONTINUE; + } + + LOG_DBG("Restoring CCC: handle 0x%04x value 0x%04x", load->entry->handle, + load->entry->value); + + cfg = ccc_find_cfg(ccc, load->addr_with_id.addr, load->addr_with_id.id); + if (!cfg) + { + cfg = ccc_find_cfg(ccc, BT_ADDR_LE_ANY, 0); + if (!cfg) + { + LOG_DBG("Unable to restore CCC: no cfg left"); + goto next; + } + + bt_addr_le_copy(&cfg->peer, load->addr_with_id.addr); + cfg->id = load->addr_with_id.id; + } + + cfg->value = load->entry->value; + +next: + load->entry++; + load->count--; + + return load->count ? BT_GATT_ITER_CONTINUE : BT_GATT_ITER_STOP; +} + +static int ccc_set(const char *name, + size_t len_rd, settings_read_cb read_cb, void *cb_arg) +{ + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + struct ccc_store ccc_store[CCC_STORE_MAX]; + struct ccc_load load; + bt_addr_le_t addr; + ssize_t len; + int err; + const char *next; + + settings_name_next(name, &next); + + if (!name) + { + LOG_ERR("Insufficient number of arguments"); + return -EINVAL; + } + else if (!next) + { + load.addr_with_id.id = BT_ID_DEFAULT; + } + else + { + unsigned long next_id = strtoul(next, NULL, 10); + + if (next_id >= CONFIG_BT_ID_MAX) + { + LOG_ERR("Invalid local identity %lu", next_id); + return -EINVAL; + } + + load.addr_with_id.id = (uint8_t)next_id; + } + + err = bt_settings_decode_key(name, &addr); + if (err) + { + LOG_ERR("Unable to decode address %s", name); + return -EINVAL; + } + + load.addr_with_id.addr = &addr; + + if (len_rd) + { + len = read_cb(cb_arg, ccc_store, sizeof(ccc_store)); + + if (len < 0) + { + LOG_ERR("Failed to decode value (err %zd)", len); + return len; + } + + load.entry = ccc_store; + load.count = len / sizeof(*ccc_store); + + for (size_t i = 0; i < load.count; i++) + { + LOG_DBG("Read CCC: handle 0x%04x value 0x%04x", + ccc_store[i].handle, + ccc_store[i].value); + } + } + else + { + load.entry = NULL; + load.count = 0; + } + + bt_gatt_foreach_attr(0x0001, 0xffff, ccc_load, &load); + + LOG_DBG("Restored CCC for id:%" PRIu8 " addr:%s", load.addr_with_id.id, + bt_addr_le_str(load.addr_with_id.addr)); + } + + return 0; +} + +#ifdef CONFIG_BT_SETTINGS +static int ccc_set_cb(const char *name, + size_t len_rd, settings_read_cb read_cb, void *cb_arg) +{ + if (IS_ENABLED(CONFIG_BT_SETTINGS_CCC_LAZY_LOADING)) + { + /* Only load CCCs on demand */ + + return 0; + } + + return ccc_set(name, len_rd, read_cb, cb_arg); +} + +BT_SETTINGS_DEFINE(ccc, "ccc", ccc_set_cb, NULL); +#endif /* CONFIG_BT_SETTINGS */ + +static int ccc_set_direct( + const char *key, size_t len, settings_read_cb read_cb, void *cb_arg, + void *param) +{ + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + const char *name; + + LOG_DBG("key: %s", (const char *)param); + + /* Only "bt/ccc" settings should ever come here */ + + if (!settings_name_steq((const char *)param, "bt/ccc", &name)) + { + LOG_ERR("Invalid key"); + return -EINVAL; + } + + return ccc_set(name, len, read_cb, cb_arg); + } + + return 0; +} + +void bt_gatt_connected(struct bt_conn *conn) +{ + struct conn_data data; + + LOG_DBG("conn %p", conn); + + data.conn = conn; + data.sec = BT_SECURITY_L1; + + /* Load CCC settings from backend if bonded */ + + if (IS_ENABLED(CONFIG_BT_SETTINGS_CCC_LAZY_LOADING) && + bt_le_bond_exists(conn->id, &conn->le.dst)) + { + char key[BT_SETTINGS_KEY_MAX]; + + if (conn->id) + { + char id_str[4]; + + u8_to_dec(id_str, sizeof(id_str), conn->id); + bt_settings_encode_key(key, sizeof(key), "ccc", &conn->le.dst, + id_str); + } + else + { + bt_settings_encode_key(key, sizeof(key), "ccc", &conn->le.dst, + NULL); + } + + settings_load_subtree_direct(key, ccc_set_direct, (void *)key); + } + + bt_gatt_foreach_attr(0x0001, 0xffff, update_ccc, &data); + + /* BLUETOOTH CORE SPECIFICATION Version 5.1 | Vol 3, Part C page 2192: + * + * 10.3.1.1 Handling of GATT indications and notifications + * + * A client ā€œrequestsā€ a server to send indications and notifications + * by appropriately configuring the server via a Client Characteristic + * Configuration Descriptor. Since the configuration is persistent across a + * disconnection and reconnection, security requirements must be checked + * against the configuration upon a reconnection before sending indications + * or notifications. When a server reconnects to a client to send an + * indication or notification for which security is required, the server + * shall initiate or request encryption with the client prior to sending an + * indication or notification. If the client does not have an LTK + * indicating that the client has lost the bond, enabling encryption will + * fail. + */ + + if (IS_ENABLED(CONFIG_BT_SMP) && + (conn->role == BT_HCI_ROLE_CENTRAL || + IS_ENABLED(CONFIG_BT_GATT_AUTO_SEC_REQ)) && + bt_conn_get_security(conn) < data.sec) + { + int err = bt_conn_set_security(conn, data.sec); + + if (err) + { + LOG_WRN("Failed to set security for bonded peer (%d)", err); + } + } + +#if defined(CONFIG_BT_GATT_AUTO_UPDATE_MTU) + int err; + + err = bt_gatt_exchange_mtu(conn, &gatt_exchange_params); + if (err) + { + LOG_WRN("MTU Exchange failed (err %d)", err); + } +#endif /* CONFIG_BT_GATT_AUTO_UPDATE_MTU */ +} + +void bt_gatt_att_max_mtu_changed(struct bt_conn *conn, + uint16_t tx, uint16_t rx) +{ + struct bt_gatt_cb *cb; + struct bt_gatt_cb *tmp; + + list_for_every_container_safe(&callback_list, cb, tmp, node) + { + if (cb->att_mtu_updated) + { + cb->att_mtu_updated(conn, tx, rx); + } + } +} + +/* Central Address Resolution characteristic value (Core 6.3, Vol 3, Part C, + * 12.4). + */ +#define CENTRAL_ADDR_RES_SUPP 0x01 + +static uint8_t gatt_central_addr_res_rsp(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + enum bt_le_addr_res_support support = BT_LE_ADDR_RES_SUPPORT_UNKNOWN; + struct bt_keys *keys = bt_keys_find_addr(conn->id, &conn->le.dst); + struct bt_conn_auth_info_cb *listener; + struct bt_conn_auth_info_cb *next; + bool supported = false; + bool known = false; + + if (err == 0 && data != NULL && length == sizeof(uint8_t)) + { + known = true; + supported = (((const uint8_t *)data)[0] == CENTRAL_ADDR_RES_SUPP); + } + else if (err == BT_ATT_ERR_ATTRIBUTE_NOT_FOUND) + { + /* An absent characteristic means address resolution is not + * supported (Core 6.3, Vol 3, Part C, 12.4). + */ + + known = true; + } + + /* Any other error, or a malformed value, leaves the support unknown, + * to be read again on the next connection. + */ + + if (known && keys != NULL) + { + keys->flags |= BT_KEYS_CENTRAL_ADDR_RES_KNOWN; + if (supported) + { + keys->flags |= BT_KEYS_CENTRAL_ADDR_RES_SUPPORT; + } + + LOG_DBG("Peer %sable to resolve the target address of directed " + "advertising", + supported ? "" : "un"); + + bt_keys_store(keys); + + support = + supported ? + BT_LE_ADDR_RES_SUPPORT_YES : BT_LE_ADDR_RES_SUPPORT_NO; + } + + params->func = NULL; + + list_for_every_container_safe(&bt_auth_info_cbs, listener, next, node) + { + if (listener->addr_res_support_read != NULL) + { + listener->addr_res_support_read(conn, support); + } + } + + return BT_GATT_ITER_STOP; +} + +static void gatt_read_central_addr_res(struct bt_conn *conn) +{ + static struct bt_gatt_read_params + central_addr_res_params[CONFIG_BT_MAX_CONN]; + struct bt_gatt_read_params *params = + ¢ral_addr_res_params[bt_conn_index(conn)]; + struct bt_keys *keys; + int err; + + if (!IS_ENABLED(CONFIG_BT_GATT_AUTO_READ_CENTRAL_ADDR_RES)) + { + return; + } + + /* The characteristic and the check are LE-specific, and the address + * lookups below use the LE part of the connection object. + */ + + if (conn->type != BT_CONN_TYPE_LE) + { + return; + } + + if (conn->role != BT_HCI_ROLE_PERIPHERAL) + { + return; + } + + if (!bt_le_bond_exists(conn->id, &conn->le.dst)) + { + return; + } + + keys = bt_keys_find_addr(conn->id, &conn->le.dst); + if (keys == NULL || (keys->flags & BT_KEYS_CENTRAL_ADDR_RES_KNOWN) != 0) + { + return; + } + + if (params->func != NULL) + { + /* Read already in progress */ + + return; + } + + *params = (struct bt_gatt_read_params) + { + .func = gatt_central_addr_res_rsp, + .by_uuid.uuid = BT_UUID_CENTRAL_ADDR_RES, + .by_uuid.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE, + .by_uuid.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE, + }; + + err = bt_gatt_read(conn, params); + if (err != 0) + { + LOG_WRN("Unable to read Central Address Resolution (err %d)", err); + params->func = NULL; + } +} + +static void central_addr_res_pairing_complete(struct bt_conn *conn, + bool bonded) +{ + if (bonded) + { + gatt_read_central_addr_res(conn); + } +} + +void bt_gatt_encrypt_change(struct bt_conn *conn) +{ + struct conn_data data; + + LOG_DBG("conn %p", conn); + + data.conn = conn; + data.sec = BT_SECURITY_L1; + +#if defined(CONFIG_BT_GATT_AUTO_RESUBSCRIBE) + add_subscriptions(conn); +#endif /* CONFIG_BT_GATT_AUTO_RESUBSCRIBE */ + + /* Covers bonds that were created before the support was tracked */ + + gatt_read_central_addr_res(conn); + + bt_gatt_foreach_attr(0x0001, 0xffff, update_ccc, &data); + + if (!bt_gatt_change_aware(conn, false)) + { + /* Send a Service Changed indication if the current peer is + * marked as change-unaware. + */ + + sc_indicate(0x0001, 0xffff); + } +} + +bool bt_gatt_change_aware(struct bt_conn *conn, bool req) +{ +#if defined(CONFIG_BT_GATT_CACHING) + struct gatt_cf_cfg *cfg; + + cfg = find_cf_cfg(conn); + if (!cfg || !CF_ROBUST_CACHING(cfg)) + { + return true; + } + + if (atomic_test_bit(cfg->flags, CF_CHANGE_AWARE)) + { + return true; + } + + /* BLUETOOTH CORE SPECIFICATION Version 5.1 | Vol 3, Part G page 2350: + * If a change-unaware client sends an ATT command, the server shall ignore + * it. + */ + + if (!req) + { + return false; + } + + /* BLUETOOTH CORE SPECIFICATION Version 5.3 | Vol 3, Part G page 1475: + * 2.5.2.1 Robust Caching A change-unaware connected client becomes + * change-aware when it reads the Database Hash characteristic and then the + * server receives another ATT request from the client. + */ + + if (atomic_test_and_clear_bit(cfg->flags, CF_DB_HASH_READ)) + { + bt_att_clear_out_of_sync_sent(conn); + set_change_aware(cfg, true); + return true; + } + + /* BLUETOOTH CORE SPECIFICATION Version 5.3 | Vol 3, Part G page 1476: + * 2.5.2.1 Robust Caching ... a change-unaware connected client using + * exactly one ATT bearer becomes change-aware when ... The server sends + * the client a response with the Error Code parameter set to Database Out + * Of Sync (0x12) and then the server receives another ATT request from the + * client. + */ + + if (bt_att_fixed_chan_only(conn) && bt_att_out_of_sync_sent_on_fixed(conn)) + { + atomic_clear_bit(cfg->flags, CF_DB_HASH_READ); + bt_att_clear_out_of_sync_sent(conn); + set_change_aware(cfg, true); + return true; + } + + return false; +#else + return true; +#endif +} + +static struct gatt_cf_cfg *find_cf_cfg_by_addr(uint8_t id, + const bt_addr_le_t *addr) +{ + if (IS_ENABLED(CONFIG_BT_GATT_CACHING)) + { + int i; + + for (i = 0; i < ARRAY_SIZE(cf_cfg); i++) + { + if (id == cf_cfg[i].id && bt_addr_le_eq(addr, &cf_cfg[i].peer)) + { + return &cf_cfg[i]; + } + } + } + + return NULL; +} + +#if defined(CONFIG_BT_SETTINGS) + +struct ccc_save +{ + struct addr_with_id addr_with_id; + struct ccc_store store[CCC_STORE_MAX]; + size_t count; +}; + +static uint8_t ccc_save(const struct bt_gatt_attr *attr, + uint16_t handle, void *user_data) +{ + struct ccc_save *save = user_data; + struct bt_gatt_ccc_managed_user_data *ccc; + struct bt_gatt_ccc_cfg *cfg; + + if (!is_host_managed_ccc(attr)) + { + return BT_GATT_ITER_CONTINUE; + } + + ccc = attr->user_data; + + /* Check if there is a cfg for the peer */ + + cfg = ccc_find_cfg(ccc, save->addr_with_id.addr, save->addr_with_id.id); + if (!cfg) + { + return BT_GATT_ITER_CONTINUE; + } + + LOG_DBG("Storing CCCs handle 0x%04x value 0x%04x", handle, cfg->value); + + if (save->count >= CCC_STORE_MAX) + { + LOG_ERR("Too many Client Characteristic Configuration. " + "See CONFIG_BT_SETTINGS_CCC_STORE_MAX\n"); + return BT_GATT_ITER_STOP; + } + + save->store[save->count].handle = handle; + save->store[save->count].value = cfg->value; + save->count++; + + return BT_GATT_ITER_CONTINUE; +} + +static int gatt_store_ccc(uint8_t id, const bt_addr_le_t *addr) +{ + struct ccc_save save; + size_t len; + char *str; + int err; + + save.addr_with_id.addr = addr; + save.addr_with_id.id = id; + save.count = 0; + + bt_gatt_foreach_attr(0x0001, 0xffff, ccc_save, &save); + + if (save.count) + { + str = (char *)save.store; + len = save.count * sizeof(*save.store); + } + else + { + /* No entries to encode, just clear */ + + str = NULL; + len = 0; + } + + err = bt_settings_store_ccc(id, addr, str, len); + if (err) + { + LOG_ERR("Failed to store CCCs (err %d)", err); + return err; + } + + LOG_DBG("Stored CCCs for %s", bt_addr_le_str(addr)); + if (len) + { + for (size_t i = 0; i < save.count; i++) + { + LOG_DBG(" CCC: handle 0x%04x value 0x%04x", save.store[i].handle, + save.store[i].value); + } + } + else + { + LOG_DBG(" CCC: NULL"); + } + + return 0; +} + +#if defined(CONFIG_BT_GATT_SERVICE_CHANGED) +static int sc_set(const char *name, + size_t len_rd, settings_read_cb read_cb, void *cb_arg) +{ + struct gatt_sc_cfg *cfg; + uint8_t id; + bt_addr_le_t addr; + ssize_t len; + int err; + const char *next; + + if (!name) + { + LOG_ERR("Insufficient number of arguments"); + return -EINVAL; + } + + err = bt_settings_decode_key(name, &addr); + if (err) + { + LOG_ERR("Unable to decode address %s", name); + return -EINVAL; + } + + settings_name_next(name, &next); + + if (!next) + { + id = BT_ID_DEFAULT; + } + else + { + unsigned long next_id = strtoul(next, NULL, 10); + + if (next_id >= CONFIG_BT_ID_MAX) + { + LOG_ERR("Invalid local identity %lu", next_id); + return -EINVAL; + } + + id = (uint8_t)next_id; + } + + cfg = find_sc_cfg(id, &addr); + if (!cfg && len_rd) + { + /* Find and initialize a free sc_cfg entry */ + + cfg = find_sc_cfg(BT_ID_DEFAULT, BT_ADDR_LE_ANY); + if (!cfg) + { + LOG_ERR("Unable to restore SC: no cfg left"); + return -ENOMEM; + } + + cfg->id = id; + bt_addr_le_copy(&cfg->peer, &addr); + } + + if (len_rd) + { + len = read_cb(cb_arg, &cfg->data, sizeof(cfg->data)); + if (len < 0) + { + LOG_ERR("Failed to decode value (err %zd)", len); + return len; + } + + LOG_DBG("Read SC: len %zd", len); + + LOG_DBG("Restored SC for %s", bt_addr_le_str(&addr)); + } + else if (cfg) + { + /* Clear configuration */ + + memset(cfg, 0, sizeof(*cfg)); + + LOG_DBG("Removed SC for %s", bt_addr_le_str(&addr)); + } + + return 0; +} + +static int sc_commit(void) +{ + atomic_set_bit(gatt_sc.flags, SC_LOAD); + atomic_clear_bit(gatt_sc.flags, SC_INDICATE_PENDING); + + if (atomic_test_bit(gatt_sc.flags, SC_RANGE_CHANGED)) + { + /* Schedule SC indication since the range has changed */ + + sc_work_submit(SC_TIMEOUT); + } + + return 0; +} + +BT_SETTINGS_DEFINE(sc, "sc", sc_set, sc_commit); +#endif /* CONFIG_BT_GATT_SERVICE_CHANGED */ + +#if defined(CONFIG_BT_GATT_CACHING) +static int cf_set(const char *name, + size_t len_rd, settings_read_cb read_cb, void *cb_arg) +{ + struct gatt_cf_cfg *cfg; + bt_addr_le_t addr; + const char *next; + ssize_t len; + int err; + uint8_t id; + + if (!name) + { + LOG_ERR("Insufficient number of arguments"); + return -EINVAL; + } + + err = bt_settings_decode_key(name, &addr); + if (err) + { + LOG_ERR("Unable to decode address %s", name); + return -EINVAL; + } + + settings_name_next(name, &next); + + if (!next) + { + id = BT_ID_DEFAULT; + } + else + { + unsigned long next_id = strtoul(next, NULL, 10); + + if (next_id >= CONFIG_BT_ID_MAX) + { + LOG_ERR("Invalid local identity %lu", next_id); + return -EINVAL; + } + + id = (uint8_t)next_id; + } + + cfg = find_cf_cfg_by_addr(id, &addr); + if (!cfg) + { + cfg = find_cf_cfg(NULL); + if (!cfg) + { + LOG_ERR("Unable to restore CF: no cfg left"); + return -ENOMEM; + } + + cfg->id = id; + bt_addr_le_copy(&cfg->peer, &addr); + } + + if (len_rd) + { + char dst[CF_NUM_BYTES + CF_FLAGS_STORE_LEN]; + + len = read_cb(cb_arg, dst, sizeof(dst)); + if (len < 0) + { + LOG_ERR("Failed to decode value (err %zd)", len); + return len; + } + + memcpy(cfg->data, dst, sizeof(cfg->data)); + LOG_DBG("Read CF: len %zd", len); + + if (len != sizeof(dst)) + { + LOG_WRN("Change-aware status not found in settings, " + "defaulting peer status to change-unaware"); + set_change_aware(cfg, false); + } + else + { + /* change-aware byte is present in NVS */ + + uint8_t change_aware = dst[sizeof(cfg->data)]; + + if (change_aware & ~BIT(CF_CHANGE_AWARE)) + { + LOG_WRN("Read back bad change-aware value: 0x%x, " + "defaulting peer status to change-unaware", + change_aware); + set_change_aware(cfg, false); + } + else + { + set_change_aware_no_store(cfg, change_aware); + } + } + } + else + { + clear_cf_cfg(cfg); + } + + LOG_DBG("Restored CF for %s", bt_addr_le_str(&addr)); + + return 0; +} + +BT_SETTINGS_DEFINE(cf, "cf", cf_set, NULL); + +static int db_hash_set(const char *name, + size_t len_rd, settings_read_cb read_cb, void *cb_arg) +{ + ssize_t len; + + len = read_cb(cb_arg, db_hash.stored_hash, sizeof(db_hash.stored_hash)); + if (len < 0) + { + LOG_ERR("Failed to decode value (err %zd)", len); + return len; + } + + LOG_HEXDUMP_DBG(db_hash.stored_hash, sizeof(db_hash.stored_hash), + "Stored Hash: "); + + return 0; +} + +static int db_hash_commit(void) +{ + atomic_set_bit(gatt_sc.flags, DB_HASH_LOAD); + + /* Calculate the hash and compare it against the value loaded from + * flash. Do it from the current context to avoid any potential race + * conditions. + */ + + do_db_hash(); + + return 0; +} + +BT_SETTINGS_DEFINE(hash, "hash", db_hash_set, db_hash_commit); +#endif /* CONFIG_BT_GATT_CACHING */ +#endif /* CONFIG_BT_SETTINGS */ + +static uint8_t remove_peer_from_attr( + const struct bt_gatt_attr *attr, uint16_t handle, void *user_data) +{ + const struct addr_with_id *addr_with_id = user_data; + struct bt_gatt_ccc_managed_user_data *ccc; + struct bt_gatt_ccc_cfg *cfg; + + if (!is_host_managed_ccc(attr)) + { + return BT_GATT_ITER_CONTINUE; + } + + ccc = attr->user_data; + + /* Check if there is a cfg for the peer */ + + cfg = ccc_find_cfg(ccc, addr_with_id->addr, addr_with_id->id); + if (cfg) + { + memset(cfg, 0, sizeof(*cfg)); + } + + return BT_GATT_ITER_CONTINUE; +} + +static int bt_gatt_clear_ccc(uint8_t id, const bt_addr_le_t *addr) +{ + struct addr_with_id addr_with_id = + { + .addr = addr, + .id = id, + }; + + bt_gatt_foreach_attr(0x0001, 0xffff, remove_peer_from_attr, &addr_with_id); + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return bt_settings_delete_ccc(id, addr); + } + + return 0; +} + +static int bt_gatt_clear_cf(uint8_t id, const bt_addr_le_t *addr) +{ + struct gatt_cf_cfg *cfg; + + cfg = find_cf_cfg_by_addr(id, addr); + if (cfg) + { + clear_cf_cfg(cfg); + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return bt_settings_delete_cf(id, addr); + } + + return 0; +} + +static struct gatt_sub *find_gatt_sub(uint8_t id, const bt_addr_le_t *addr) +{ + for (int i = 0; i < ARRAY_SIZE(subscriptions); i++) + { + struct gatt_sub *sub = &subscriptions[i]; + + if (id == sub->id && bt_addr_le_eq(addr, &sub->peer)) + { + return sub; + } + } + + return NULL; +} + +static void bt_gatt_clear_subscriptions(uint8_t id, const bt_addr_le_t *addr) +{ + struct gatt_sub *sub; + struct bt_gatt_subscribe_params *params; + struct bt_gatt_subscribe_params *tmp; + struct list_node *prev = NULL; + + sub = find_gatt_sub(id, addr); + if (!sub) + { + return; + } + + list_for_every_container_safe(&sub->list, params, tmp, node) + { + params->value = 0U; + gatt_sub_remove(NULL, sub, prev, params); + } +} + +int bt_gatt_clear(uint8_t id, const bt_addr_le_t *addr) +{ + int err; + + err = bt_gatt_clear_ccc(id, addr); + if (err < 0) + { + return err; + } + + if (IS_ENABLED(CONFIG_BT_GATT_SERVICE_CHANGED)) + { + err = bt_gatt_clear_sc(id, addr); + if (err < 0) + { + return err; + } + } + + if (IS_ENABLED(CONFIG_BT_GATT_CACHING)) + { + err = bt_gatt_clear_cf(id, addr); + if (err < 0) + { + return err; + } + } + + if (IS_ENABLED(CONFIG_BT_GATT_CLIENT)) + { + bt_gatt_clear_subscriptions(id, addr); + } + + return 0; +} + +void bt_gatt_disconnected(struct bt_conn *conn) +{ + LOG_DBG("conn %p", conn); + bt_gatt_foreach_attr(0x0001, 0xffff, disconnected_cb, conn); + +#if defined(CONFIG_BT_GATT_NOTIFY_MULTIPLE) + /* Clear pending notifications */ + + cleanup_notify(conn); +#endif /* CONFIG_BT_GATT_NOTIFY_MULTIPLE */ + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + gatt_store_ccc_cf(conn->id, &conn->le.dst); + } + + /* Make sure to clear the CCC entry when using lazy loading */ + + if (IS_ENABLED(CONFIG_BT_SETTINGS_CCC_LAZY_LOADING) && + bt_le_bond_exists(conn->id, &conn->le.dst)) + { + struct addr_with_id addr_with_id = + { + .addr = &conn->le.dst, + .id = conn->id, + }; + + bt_gatt_foreach_attr(0x0001, 0xffff, remove_peer_from_attr, + &addr_with_id); + } + +#if defined(CONFIG_BT_GATT_CLIENT) + remove_subscriptions(conn); +#endif /* CONFIG_BT_GATT_CLIENT */ + +#if defined(CONFIG_BT_GATT_CACHING) + remove_cf_cfg(conn); +#endif +} + +void bt_gatt_req_set_mtu(struct bt_att_req *req, uint16_t mtu) +{ +#if defined(CONFIG_BT_GATT_CLIENT) + if (req->func == gatt_read_rsp) + { + struct bt_gatt_read_params *params = req->user_data; + + DEBUGASSERT(params); + params->_att_mtu = mtu; + return; + } +#endif + + /* Otherwise: This request type does not have an `_att_mtu` + * params field or any other method to get this value, so we can just drop + * it here. Feel free to add this capability to other request types if + * needed. + */ +} + +/* Descriptor of application-specific authorization callbacks that are used + * with the CONFIG_BT_GATT_AUTHORIZATION_CUSTOM Kconfig enabled. + */ + +static const struct bt_gatt_authorization_cb *authorization_cb; + +bool bt_gatt_attr_read_authorize(struct bt_conn *conn, + const struct bt_gatt_attr *attr) +{ + if (!IS_ENABLED(CONFIG_BT_GATT_AUTHORIZATION_CUSTOM)) + { + return true; + } + + if (!authorization_cb || !authorization_cb->read_authorize) + { + return true; + } + + return authorization_cb->read_authorize(conn, attr); +} + +bool bt_gatt_attr_write_authorize(struct bt_conn *conn, + const struct bt_gatt_attr *attr) +{ + if (!IS_ENABLED(CONFIG_BT_GATT_AUTHORIZATION_CUSTOM)) + { + return true; + } + + if (!authorization_cb || !authorization_cb->write_authorize) + { + return true; + } + + return authorization_cb->write_authorize(conn, attr); +} + +int bt_gatt_authorization_cb_register( + const struct bt_gatt_authorization_cb *cb) +{ + if (!IS_ENABLED(CONFIG_BT_GATT_AUTHORIZATION_CUSTOM)) + { + return -ENOSYS; + } + + if (!cb) + { + authorization_cb = NULL; + return 0; + } + + if (authorization_cb) + { + return -EALREADY; + } + + authorization_cb = cb; + + return 0; +} diff --git a/libs/zblue/host/gatt_gap_svc_validate.c b/libs/zblue/host/gatt_gap_svc_validate.c new file mode 100644 index 0000000000000..68fd718243c2e --- /dev/null +++ b/libs/zblue/host/gatt_gap_svc_validate.c @@ -0,0 +1,64 @@ +/**************************************************************************** + * libs/zblue/host/gatt_gap_svc_validate.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* gatt_gap_svc_validate.c - GAP service inside GATT validation functions */ + +/* Copyright (c) 2025 Koppel Electronic + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#define LOG_LEVEL CONFIG_BT_GATT_LOG_LEVEL +LOG_MODULE_DECLARE(bt_gatt); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static uint8_t gap_service_check_cb(const struct bt_gatt_attr *attr, + uint16_t handle, + void *user_data) +{ + size_t *cnt = user_data; + const struct bt_uuid *svc_uuid = attr->user_data; + + if (svc_uuid != NULL && bt_uuid_cmp(svc_uuid, BT_UUID_GAP) == 0) + { + (*cnt)++; + LOG_DBG("GAP service found at handle: %u", handle); + } + + return BT_GATT_ITER_CONTINUE; +} + +int gatt_gap_svc_validate(void) +{ + size_t gap_svc_count = 0U; + + bt_gatt_foreach_attr_type(BT_ATT_FIRST_ATTRIBUTE_HANDLE, + BT_ATT_LAST_ATTRIBUTE_HANDLE, + BT_UUID_GATT_PRIMARY, NULL, 0, + gap_service_check_cb, &gap_svc_count); + + if (gap_svc_count != 1U) + { + LOG_DBG("GAP service count invalid: %zu", gap_svc_count); + return -EINVAL; + } + + return 0; +} diff --git a/libs/zblue/host/gatt_gap_svc_validate.h b/libs/zblue/host/gatt_gap_svc_validate.h new file mode 100644 index 0000000000000..723007c94dad2 --- /dev/null +++ b/libs/zblue/host/gatt_gap_svc_validate.h @@ -0,0 +1,33 @@ +/**************************************************************************** + * libs/zblue/host/gatt_gap_svc_validate.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Internal API GATT GAP service validation. + */ + +/* Copyright (c) 2025 Koppel Electronic + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_SUBSYS_BLUETOOTH_HOST_GATT_GAP_SVC_VALIDATE_H +#define ZEPHYR_SUBSYS_BLUETOOTH_HOST_GATT_GAP_SVC_VALIDATE_H + +/** @brief Validate GATT database contains exactly one GAP service. + * + * This function iterates through the GATT attribute database and verifies + * that exactly one Generic Access Profile (GAP) service is present. The + * Bluetooth specification requires that a GATT server expose exactly one GAP + * service. + * + * @return 0 on success (exactly one GAP service found). + * @return -EINVAL if zero or multiple GAP services are found. + */ + +int gatt_gap_svc_validate(void); + +#endif /* ZEPHYR_SUBSYS_BLUETOOTH_HOST_GATT_GAP_SVC_VALIDATE_H */ diff --git a/libs/zblue/host/gatt_internal.h b/libs/zblue/host/gatt_internal.h new file mode 100644 index 0000000000000..c79b655251697 --- /dev/null +++ b/libs/zblue/host/gatt_internal.h @@ -0,0 +1,89 @@ +/**************************************************************************** + * libs/zblue/host/gatt_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Internal API for Generic Attribute Profile handling. + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include + +#define BT_GATT_PERM_READ_MASK \ + (BT_GATT_PERM_READ | BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_READ_AUTHEN | \ + BT_GATT_PERM_READ_LESC) +#define BT_GATT_PERM_WRITE_MASK \ + (BT_GATT_PERM_WRITE | BT_GATT_PERM_WRITE_ENCRYPT | BT_GATT_PERM_WRITE_AUTHEN | \ + BT_GATT_PERM_WRITE_LESC) +#define BT_GATT_PERM_ENCRYPT_MASK (BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT) +#define BT_GATT_PERM_AUTHEN_MASK (BT_GATT_PERM_READ_AUTHEN | BT_GATT_PERM_WRITE_AUTHEN) +#define BT_GATT_PERM_LESC_MASK (BT_GATT_PERM_READ_LESC | BT_GATT_PERM_WRITE_LESC) +#define BT_GATT_PERM_READ_ENCRYPT_MASK \ + (BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_READ_AUTHEN | BT_GATT_PERM_READ_LESC) + +void bt_gatt_init(void); +void bt_gatt_connected(struct bt_conn *conn); +void bt_gatt_att_max_mtu_changed(struct bt_conn *conn, + uint16_t tx, uint16_t rx); +void bt_gatt_encrypt_change(struct bt_conn *conn); +void bt_gatt_disconnected(struct bt_conn *conn); + +bool bt_gatt_change_aware(struct bt_conn *conn, bool req); + +int bt_gatt_clear(uint8_t id, const bt_addr_le_t *addr); + +#if defined(CONFIG_BT_GATT_CLIENT) +void bt_gatt_notification(struct bt_conn *conn, + uint16_t handle, + const void *data, + uint16_t length); + +void bt_gatt_mult_notification(struct bt_conn *conn, + const void *data, uint16_t length); +#else +static inline void bt_gatt_notification( + struct bt_conn *conn, uint16_t handle, const void *data, uint16_t length) +{ +} + +static inline void bt_gatt_mult_notification( + struct bt_conn *conn, const void *data, uint16_t length) +{ +} +#endif /* CONFIG_BT_GATT_CLIENT */ + +struct bt_gatt_attr; + +/* Check attribute permission */ + +uint8_t bt_gatt_check_perm(struct bt_conn *conn, + const struct bt_gatt_attr *attr, uint16_t mask); + +bool bt_gatt_attr_read_authorize(struct bt_conn *conn, + const struct bt_gatt_attr *attr); +bool bt_gatt_attr_write_authorize(struct bt_conn *conn, + const struct bt_gatt_attr *attr); + +/** + * @brief Whether the GATT database hash is currently valid. + * + * When @kconfig{CONFIG_BT_GATT_SERVICE_CHANGED} is disabled this always + * returns false. + */ + +bool bt_gatt_is_db_hash_valid(void); diff --git a/libs/zblue/host/hci_common.c b/libs/zblue/host/hci_common.c new file mode 100644 index 0000000000000..97c99173ddea8 --- /dev/null +++ b/libs/zblue/host/hci_common.c @@ -0,0 +1,98 @@ +/**************************************************************************** + * libs/zblue/host/hci_common.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "common/assert.h" + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +struct net_buf *bt_hci_evt_create(uint8_t evt, uint8_t len) +{ + struct bt_hci_evt_hdr *hdr; + struct net_buf *buf; + + buf = bt_buf_get_evt(evt, false, ((clock_t)-1)); + + BT_ASSERT(buf); + + hdr = net_buf_add(buf, sizeof(*hdr)); + hdr->evt = evt; + hdr->len = len; + + return buf; +} + +struct net_buf *bt_hci_cmd_complete_create(uint16_t op, uint8_t plen) +{ + struct net_buf *buf; + struct bt_hci_evt_cmd_complete *cc; + + buf = bt_hci_evt_create(BT_HCI_EVT_CMD_COMPLETE, sizeof(*cc) + plen); + + cc = net_buf_add(buf, sizeof(*cc)); + + /* The Num_HCI_Command_Packets parameter allows the Controller to + * indicate the number of HCI command packets the Host can send to the + * Controller. If the Controller requires the Host to stop sending + * commands, Num_HCI_Command_Packets will be set to zero. + * + * NOTE: Zephyr Controller (and may be other Controllers) do not support + * higher Number of HCI Command packets than 1. + */ + + cc->ncmd = 1U; + + cc->opcode = sys_cpu_to_le16(op); + + return buf; +} + +struct net_buf *bt_hci_cmd_status_create(uint16_t op, uint8_t status) +{ + struct net_buf *buf; + struct bt_hci_evt_cmd_status *cs; + + buf = bt_hci_evt_create(BT_HCI_EVT_CMD_STATUS, sizeof(*cs)); + + cs = net_buf_add(buf, sizeof(*cs)); + cs->status = status; + + /* The Num_HCI_Command_Packets parameter allows the Controller to + * indicate the number of HCI command packets the Host can send to the + * Controller. If the Controller requires the Host to stop sending + * commands, Num_HCI_Command_Packets will be set to zero. + * + * NOTE: Zephyr Controller (and may be other Controllers) do not support + * higher Number of HCI Command packets than 1. + */ + + cs->ncmd = 1U; + + cs->opcode = sys_cpu_to_le16(op); + + return buf; +} diff --git a/libs/zblue/host/hci_core.c b/libs/zblue/host/hci_core.c new file mode 100644 index 0000000000000..7a36be14dba0c --- /dev/null +++ b/libs/zblue/host/hci_core.c @@ -0,0 +1,6508 @@ +/**************************************************************************** + * libs/zblue/host/hci_core.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* hci_core.c - HCI core Bluetooth handling */ + +/* Copyright (c) 2017-2025 Nordic Semiconductor ASA + * Copyright (c) 2015-2016 Intel Corporation Copyright 2025 NXP Copyright (c) + * 2025 Xiaomi Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "addr_internal.h" +#include "adv.h" +#include "classic/br.h" +#include "common/hci_common_internal.h" +#include "common/bt_str.h" +#include "common/rpa.h" +#include "common/assert.h" +#include "conn_internal.h" +#include "crypto.h" +#include "ecc.h" +#include "gatt_internal.h" +#include "hci_core.h" +#include "id.h" +#include "iso_internal.h" +#include "keys.h" +#include "l2cap_internal.h" +#include "monitor.h" +#include "scan.h" +#include "settings.h" +#include "smp.h" + +#if defined(CONFIG_BT_DF) +#include "direction_internal.h" +#endif /* CONFIG_BT_DF */ + +#define LOG_LEVEL CONFIG_BT_HCI_CORE_LOG_LEVEL +LOG_MODULE_REGISTER(bt_hci_core); + +#define BT_HCI_DEV NULL +#define BT_HCI_BUS 0 +#define BT_HCI_NAME "nuttx_bt_hci" +#define BT_HCI_QUIRKS 0 + +/* These checks are added to warn if the number of ACL or ISO packets in + * Controller is not equal to the number of bt_conn_tx contexts allocated by + * Host. The inequality of these two values can lead to inefficient resources + * usage either on Host's or Controller's side. + */ +#define CHECK_NUM_OF_ACL_PKTS(_num) \ + do \ + { \ + if (CONFIG_BT_BUF_ACL_TX_COUNT != (_num)) \ + { \ + LOG_WRN("Num of Controller's ACL packets != ACL bt_conn_tx contexts" \ + " (%u != %u)", \ + (_num), CONFIG_BT_BUF_ACL_TX_COUNT); \ + } \ + } \ + while (0) + +#define CHECK_NUM_OF_ISO_PKTS(_num) \ + do \ + { \ + if (CONFIG_BT_ISO_TX_BUF_COUNT != (_num)) \ + { \ + LOG_WRN("Num of Controller's ISO packets != ISO bt_conn_tx contexts" \ + "(%u != %u)", \ + (_num), CONFIG_BT_ISO_TX_BUF_COUNT); \ + } \ + } \ + while (0) + +void bt_tx_irq_raise(void); + +#define HCI_CMD_TIMEOUT SEC2TICK(10) + +/* Stacks for the threads */ + +static void rx_work_handler(struct k_work *work); +static K_WORK_DEFINE(rx_work, rx_work_handler); +/* General purpose Bluetooth workqueue. It processes incoming low priority + * HCI packets (high priority events are handled synchronously in the context + * of the bt_recv() caller) as well as the host's internal delayed and + * immediate work items, keeping them off the shared system workqueue. + */ + +static struct k_work_q bt_workq; +static uint64_t rx_thread_stack[CONFIG_BT_RX_STACK_SIZE / sizeof(uint64_t)]; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bt_work_submit(struct k_work *work) +{ + return k_work_submit_to_queue(&bt_workq, work); +} + +int bt_work_schedule(struct k_work_delayable *work, clock_t delay) +{ + return k_work_schedule_for_queue(&bt_workq, work, delay); +} + +int bt_work_reschedule(struct k_work_delayable *work, clock_t delay) +{ + return k_work_reschedule_for_queue(&bt_workq, work, delay); +} + +static int bt_workq_start(void) +{ + static bool bt_workq_started; + int err; + + if (bt_workq_started) + { + return 0; + } + + k_work_queue_init(&bt_workq); + err = k_work_queue_start(&bt_workq, rx_thread_stack, + CONFIG_BT_RX_STACK_SIZE, + K_PRIO_COOP(CONFIG_BT_RX_PRIO), NULL); + if (err) + { + LOG_ERR("Failed to start Bluetooth RX workqueue (err %d)", err); + return err; + } + + bt_workq_started = true; + + return 0; +} + +static void init_work(struct k_work *work); + +struct bt_dev bt_dev = +{ + .init = Z_WORK_INITIALIZER(init_work), + .le.conn_ready = LIST_INITIAL_VALUE(bt_dev.le.conn_ready), + .rx_queue = LIST_INITIAL_VALUE(bt_dev.rx_queue), +#if defined(CONFIG_BT_PRIVACY) + .rpa_timeout = CONFIG_BT_RPA_TIMEOUT, +#endif +#if defined(CONFIG_BT_DEVICE_APPEARANCE_DYNAMIC) + .appearance = CONFIG_BT_DEVICE_APPEARANCE, +#endif + .hci = BT_HCI_DEV, + .lock = NXRMUTEX_INITIALIZER, +}; + +static bt_ready_cb_t ready_cb; + +#if defined(CONFIG_BT_HCI_VS_EVT_USER) +static bt_hci_vnd_evt_cb_t *hci_vnd_evt_cb; +#endif /* CONFIG_BT_HCI_VS_EVT_USER */ + +struct cmd_data +{ + /** HCI status of the command completion */ + + uint8_t status; + + /** The command OpCode that the buffer contains */ + + uint16_t opcode; + + /** The state to update when command completes with success. */ + + struct bt_hci_cmd_state_set *state; + + /** Used by bt_hci_cmd_send_sync. */ + + sem_t *sync; +}; + +static struct cmd_data cmd_data[BT_BUF_CMD_TX_COUNT]; + +#define cmd(buf) (&cmd_data[net_buf_id(buf)]) +#define acl(buf) ((struct bt_conn_rx *)net_buf_user_data(buf)) + +static bool drv_quirk_no_reset(void) +{ + return ((BT_HCI_QUIRKS & BT_HCI_QUIRK_NO_RESET) != 0); +} + +bool bt_drv_quirk_no_auto_dle(void) +{ + return ((BT_HCI_QUIRKS & BT_HCI_QUIRK_NO_AUTO_DLE) != 0); +} + +void bt_hci_cmd_state_set_init( + struct net_buf *buf, struct bt_hci_cmd_state_set *state, atomic_t *target, + int bit, bool val) +{ + state->target = target; + state->bit = bit; + state->val = val; + cmd(buf)->state = state; +} + +/* HCI command buffers. Derive the needed size from both Command and Event + * buffer length since the buffer is also used for the response event i.e + * command complete or command status. + */ +#define CMD_BUF_SIZE MAX(BT_BUF_EVT_RX_SIZE, BT_BUF_CMD_TX_SIZE) +NET_BUF_POOL_FIXED_DEFINE(hci_cmd_pool, + BT_BUF_CMD_TX_COUNT, + CMD_BUF_SIZE, + 0, + NULL); + +struct event_handler +{ + uint8_t event; + uint8_t min_len; + void (*handler)(struct net_buf *buf); +}; + +#define EVENT_HANDLER(_evt, _handler, _min_len) \ + { \ + .event = _evt, \ + .handler = _handler, \ + .min_len = _min_len, \ + } + +static int handle_event_common(uint8_t event, + struct net_buf *buf, + const struct event_handler *handlers, + size_t num_handlers) +{ + size_t i; + + for (i = 0; i < num_handlers; i++) + { + const struct event_handler *handler = &handlers[i]; + + if (handler->event != event) + { + continue; + } + + if (buf->len < handler->min_len) + { + LOG_ERR("Too small (%u bytes) event 0x%02x", buf->len, event); + return -EINVAL; + } + + handler->handler(buf); + return 0; + } + + return -EOPNOTSUPP; +} + +static void handle_event( + uint8_t event, struct net_buf *buf, const struct event_handler *handlers, + size_t num_handlers) +{ + int err; + + err = handle_event_common(event, buf, handlers, num_handlers); + if (err == -EOPNOTSUPP) + { + LOG_WRN("Unhandled event 0x%02x len %u: %s", event, buf->len, + bt_hex(buf->data, buf->len)); + } + + /* Other possible errors are handled by handle_event_common function */ +} + +static void handle_vs_event(uint8_t event, + struct net_buf *buf, + const struct event_handler *handlers, + size_t num_handlers) +{ + int err; + + err = handle_event_common(event, buf, handlers, num_handlers); + if (err == -EOPNOTSUPP) + { + LOG_WRN("Unhandled vendor-specific event 0x%02x len %u: %s", event, + buf->len, + bt_hex(buf->data, buf->len)); + } + + /* Other possible errors are handled by handle_event_common function */ +} + +void bt_send_one_host_num_completed_packets(uint16_t handle) +{ + if (!IS_ENABLED(CONFIG_BT_HCI_ACL_FLOW_CONTROL)) + { + ARG_UNUSED(handle); + return; + } + + struct bt_hci_cp_host_num_completed_packets *cp; + struct bt_hci_handle_count *hc; + struct net_buf *buf; + int err; + + LOG_DBG("Reporting completed packet for handle %u", handle); + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + BT_ASSERT_MSG(buf, "Unable to alloc for Host NCP"); + + cp = net_buf_add(buf, sizeof(*cp)); + cp->num_handles = 1; + + hc = net_buf_add(buf, sizeof(*hc)); + hc->handle = sys_cpu_to_le16(handle); + hc->count = sys_cpu_to_le16(1); + + err = bt_hci_cmd_send(BT_HCI_OP_HOST_NUM_COMPLETED_PACKETS, buf); + BT_ASSERT_MSG(err == 0, "Unable to send Host NCP (err %d)", err); +} + +#if defined(CONFIG_BT_TESTING) +__weak void bt_testing_trace_event_acl_pool_destroy(struct net_buf *buf) +{ +} +#endif + +#if defined(CONFIG_BT_HCI_ACL_FLOW_CONTROL) +static bool drv_quirk_no_flow_control(void) +{ + return ((BT_HCI_QUIRKS & BT_HCI_QUIRK_NO_FLOW_CONTROL) != 0); +} + +void bt_hci_host_num_completed_packets(struct net_buf *buf) +{ + struct bt_hci_cp_host_num_completed_packets *cp; + uint16_t handle = acl(buf)->handle; + struct bt_hci_handle_count *hc; + struct bt_conn *conn; + uint8_t index = acl(buf)->index; + + if (IS_ENABLED(CONFIG_BT_TESTING)) + { + bt_testing_trace_event_acl_pool_destroy(buf); + } + + net_buf_destroy(buf); + + /* Do nothing if controller to host flow control is not supported */ + + if (drv_quirk_no_flow_control() || !BT_CMD_TEST(bt_dev.supported_commands, + 10, 5)) + { + return; + } + + conn = bt_conn_lookup_index(index); + if (!conn) + { + LOG_WRN("Unable to look up conn with index 0x%02x", index); + return; + } + + if (conn->state != BT_CONN_CONNECTED && + conn->state != BT_CONN_DISCONNECTING) + { + LOG_WRN("Not reporting packet for non-connected conn"); + bt_conn_unref(conn); + return; + } + + bt_conn_unref(conn); + + LOG_DBG("Reporting completed packet for handle %u", handle); + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + LOG_ERR("Unable to allocate new HCI command"); + return; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->num_handles = 1; + + hc = net_buf_add(buf, sizeof(*hc)); + hc->handle = sys_cpu_to_le16(handle); + hc->count = sys_cpu_to_le16(1); + + bt_hci_cmd_send(BT_HCI_OP_HOST_NUM_COMPLETED_PACKETS, buf); +} +#endif /* defined(CONFIG_BT_HCI_ACL_FLOW_CONTROL) */ + +struct net_buf *bt_hci_cmd_alloc(clock_t timeout) +{ + struct net_buf *buf; + + buf = net_buf_alloc(&hci_cmd_pool, timeout); + if (!buf) + { + LOG_DBG("Unable to allocate a command buffer"); + return NULL; + } + + LOG_DBG("buf %p", buf); + + /* Reserve room for the packet indicator and HCI command header */ + + net_buf_reserve(buf, BT_HCI_PKT_CMD_HDR_SIZE); + + cmd(buf)->opcode = 0; + cmd(buf)->sync = NULL; + cmd(buf)->state = NULL; + + return buf; +} + +/* Drop every queued command once the HCI transport has been closed, + * completing synchronous senders with an error. + */ + +static void hci_cmd_queue_purge(void) +{ + struct net_buf *buf; + + while (true) + { + buf = k_fifo_get(&bt_dev.cmd_tx_queue, 0); + if (buf == NULL) + { + break; + } + + LOG_WRN("Dropping queued command 0x%04x: HCI transport closed", + cmd(buf)->opcode); + + if (cmd(buf)->sync != NULL) + { + cmd(buf)->status = BT_HCI_ERR_UNSPECIFIED; + nxsem_post(cmd(buf)->sync); + } + + net_buf_unref(buf); + } +} + +int bt_hci_cmd_send(uint16_t opcode, struct net_buf *buf) +{ + int err; + + /* Make sure the HCI transport is open before attempting anything else */ + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_OPEN)) + { + if (buf != NULL) + { + net_buf_unref(buf); + } + + return -EHOSTDOWN; + } + + if (buf == NULL) + { + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + } + + LOG_DBG("opcode 0x%04x param_len %u", opcode, buf->len); + + /* Insufficient headroom or too many parameter bytes can only happen + * with a buffer that was not allocated with bt_hci_cmd_alloc(). Like the + * other failure paths, this one owns the buffer. + */ + + err = bt_hci_pkt_push_cmd_hdr(&buf->b, opcode); + if (err != 0) + { + net_buf_unref(buf); + return err; + } + + cmd(buf)->opcode = opcode; + + /* Host Number of Completed Packets can ignore the ncmd value + * and does not generate any cmd complete/status events. + */ + + if (opcode == BT_HCI_OP_HOST_NUM_COMPLETED_PACKETS) + { + err = bt_send(buf); + if (err) + { + LOG_ERR("Unable to send to driver (err %d)", err); + net_buf_unref(buf); + } + + return err; + } + + k_fifo_put(&bt_dev.cmd_tx_queue, buf); + + /* bt_disable() clears BT_DEV_OPEN before purging the queue, so a + * command queued by a sender that passed the check above just before the + * transport was closed is either found by that purge or seen here: take it + * back and fail the call. If the purge got to it first it completes the + * command like any other queued one. + */ + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_OPEN) && + k_queue_remove(&bt_dev.cmd_tx_queue, buf)) + { + net_buf_unref(buf); + return -EHOSTDOWN; + } + + bt_tx_irq_raise(); + + return 0; +} + +static bool process_pending_cmd(clock_t timeout); +int bt_hci_cmd_send_sync(uint16_t opcode, + struct net_buf *buf, struct net_buf **rsp) +{ + sem_t sync_sem; + uint8_t status; + int err; + + if (!buf) + { + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + } + else + { + /* `cmd(buf)` depends on this */ + + if (buf->pool != &hci_cmd_pool) + { + DEBUGASSERT(false); + return -EINVAL; + } + } + + LOG_DBG("buf %p opcode 0x%04x len %u", buf, opcode, buf->len); + + /* This local sem is just for suspending the current thread until the + * command is processed by the LL. It is given (and we are awaken) by the + * cmd_complete/status handlers. + */ + + nxsem_init(&sync_sem, 0, 0); + cmd(buf)->sync = &sync_sem; + + err = bt_hci_cmd_send(opcode, net_buf_ref(buf)); + if (err) + { + net_buf_unref(buf); + return err; + } + + /* TODO: disallow sending sync commands from syswq altogether */ + + /* If the commands are processed in the syswq and we are on the + * syswq, then we cannot suspend and wait. We have to send the command from + * the current context. + */ + + if (!IS_ENABLED(CONFIG_BT_TX_PROCESSOR_THREAD) && + nxsched_gettid() == k_sys_work_q.thread_id) + { + /* drain the command queue until we get to send the command of + * interest. + */ + + struct net_buf *cmd = NULL; + + do + { + cmd = k_fifo_peek_head(&bt_dev.cmd_tx_queue); + LOG_DBG("process cmd %p want %p", cmd, buf); + + /* Wait for a response from the Bluetooth Controller. + * The Controller may fail to respond if: + * - It was never programmed or connected. + * - There was a fatal error. + * + * See the `BT_HCI_OP_` macros in hci_types.h or Core_v5.4, Vol 4, + * Part E, Section 5.4.1 and Section 7 to map the opcode to the HCI + * command documentation. Example: 0x0c03 represents HCI_Reset + * command. + */ + + __maybe_unused bool success = process_pending_cmd(HCI_CMD_TIMEOUT); + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_OPEN)) + { + /* The transport was closed while draining: the queue + * has been purged, completing this command with an error, so + * there is nothing left to send. + */ + + break; + } + + BT_ASSERT_MSG(success, "command opcode 0x%04x timeout", opcode); + } + while (buf != cmd); + } + + /* Now that we have sent the command, suspend until the LL replies */ + + err = nxsem_tickwait(&sync_sem, HCI_CMD_TIMEOUT); + BT_ASSERT_MSG(err == 0, + "Controller unresponsive, command opcode 0x%04x timeout " + "with err %d", + opcode, err); + + status = cmd(buf)->status; + if (status) + { + LOG_WRN("opcode 0x%04x status 0x%02x %s", opcode, status, + bt_hci_err_to_str(status)); + net_buf_unref(buf); + + switch (status) + { + case BT_HCI_ERR_CONN_LIMIT_EXCEEDED: + return -ECONNREFUSED; + case BT_HCI_ERR_INSUFFICIENT_RESOURCES: + return -ENOMEM; + case BT_HCI_ERR_INVALID_PARAM: + return -EINVAL; + case BT_HCI_ERR_CMD_DISALLOWED: + return -EACCES; + default: + return -EIO; + } + } + + LOG_DBG("rsp %p opcode 0x%04x len %u", buf, opcode, buf->len); + + if (rsp) + { + *rsp = buf; + } + else + { + net_buf_unref(buf); + } + + return 0; +} + +int bt_hci_le_rand(void *buffer, size_t len) +{ + struct bt_hci_rp_le_rand *rp; + struct net_buf *rsp; + size_t count; + int err; + + /* Check first that HCI_LE_Rand is supported */ + + if (!BT_CMD_TEST(bt_dev.supported_commands, 27, 7)) + { + return -ENOTSUP; + } + + while (len > 0) + { + /* Number of bytes to fill on this iteration */ + + count = MIN(len, sizeof(rp->rand)); + + /* Request the next 8 bytes over HCI */ + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_RAND, NULL, &rsp); + if (err) + { + return err; + } + + /* Copy random data into buffer */ + + rp = (void *)rsp->data; + memcpy(buffer, rp->rand, count); + + net_buf_unref(rsp); + buffer = (uint8_t *)buffer + count; + len -= count; + } + + return 0; +} + +int bt_hci_le_read_max_data_len(uint16_t *tx_octets, uint16_t *tx_time) +{ + struct bt_hci_rp_le_read_max_data_len *rp; + struct net_buf *rsp; + int err; + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_READ_MAX_DATA_LEN, NULL, &rsp); + if (err) + { + LOG_ERR("Failed to read DLE max data len"); + return err; + } + + rp = (void *)rsp->data; + *tx_octets = sys_le16_to_cpu(rp->max_tx_octets); + *tx_time = sys_le16_to_cpu(rp->max_tx_time); + net_buf_unref(rsp); + + if (!IN_RANGE(*tx_octets, BT_HCI_LE_MAX_TX_OCTETS_MIN, + BT_HCI_LE_MAX_TX_OCTETS_MAX)) + { + LOG_WRN("tx_octets exceeds the valid range %u", *tx_octets); + } + + if (!IN_RANGE(*tx_time, BT_HCI_LE_MAX_TX_TIME_MIN, + BT_HCI_LE_MAX_TX_TIME_MAX)) + { + LOG_WRN("tx_time exceeds the valid range %u", *tx_time); + } + + return 0; +} + +uint8_t bt_get_phy(uint8_t hci_phy) +{ + switch (hci_phy) + { + case BT_HCI_LE_PHY_1M: + return BT_GAP_LE_PHY_1M; + case BT_HCI_LE_PHY_2M: + return BT_GAP_LE_PHY_2M; + case BT_HCI_LE_PHY_CODED: + return BT_GAP_LE_PHY_CODED; + default: + return 0; + } +} + +int bt_get_df_cte_type(uint8_t hci_cte_type) +{ + switch (hci_cte_type) + { + case BT_HCI_LE_AOA_CTE: + return BT_DF_CTE_TYPE_AOA; + case BT_HCI_LE_AOD_CTE_1US: + return BT_DF_CTE_TYPE_AOD_1US; + case BT_HCI_LE_AOD_CTE_2US: + return BT_DF_CTE_TYPE_AOD_2US; + case BT_HCI_LE_NO_CTE: + return BT_DF_CTE_TYPE_NONE; + default: + return BT_DF_CTE_TYPE_NONE; + } +} + +#if defined(CONFIG_BT_CONN_TX) +static void hci_num_completed_packets(struct net_buf *buf) +{ + struct bt_hci_evt_num_completed_packets *evt = (void *)buf->data; + int i; + + if (sizeof(*evt) + sizeof(evt->h[0]) * evt->num_handles > buf->len) + { + LOG_ERR("evt num_handles (=%u) too large (%zu > %u)", evt->num_handles, + sizeof(*evt) + sizeof(evt->h[0]) * evt->num_handles, buf->len); + return; + } + + LOG_DBG("num_handles %u", evt->num_handles); + + for (i = 0; i < evt->num_handles; i++) + { + uint16_t handle; + uint16_t count; + struct bt_conn *conn; + + handle = sys_le16_to_cpu(evt->h[i].handle); + count = sys_le16_to_cpu(evt->h[i].count); + + LOG_DBG("handle %u count %u", handle, count); + + conn = bt_conn_lookup_handle(handle, BT_CONN_TYPE_ALL); + if (!conn) + { + LOG_ERR("No connection for handle %u", handle); + continue; + } + + while (count--) + { + struct list_node *node; + irqstate_t key; + + /* move the next TX context from the `pending` list to + * the `complete` list. + */ + + node = list_remove_head(&conn->tx_pending); + + if (!node) + { + LOG_ERR("packets count mismatch"); + DEBUGASSERT(0); + break; + } + + nxsem_post(bt_conn_get_pkts(conn)); + + /* The `complete` list is consumed from another context, + * which uses the same lock. + */ + + key = enter_critical_section(); + list_add_tail(&conn->tx_complete, node); + leave_critical_section(key); + + /* align the `pending` value */ + + DEBUGASSERT(atomic_read(&conn->in_ll)); + atomic_sub(&conn->in_ll, 1); + + /* TX context free + callback happens in there */ + + bt_conn_tx_notify(conn, false); + } + + bt_conn_unref(conn); + } +} +#endif /* CONFIG_BT_CONN_TX */ + +#if defined(CONFIG_BT_CONN) +static void hci_acl(struct net_buf *buf) +{ + struct bt_hci_acl_hdr *hdr; + uint16_t handle; + uint16_t len; + struct bt_conn *conn; + uint8_t flags; + + LOG_DBG("buf %p", buf); + if (buf->len < sizeof(*hdr)) + { + LOG_ERR("Invalid HCI ACL packet size (%u)", buf->len); + net_buf_unref(buf); + return; + } + + hdr = net_buf_pull_mem(buf, sizeof(*hdr)); + len = sys_le16_to_cpu(hdr->len); + handle = sys_le16_to_cpu(hdr->handle); + flags = bt_acl_flags(handle); + + acl(buf)->handle = bt_acl_handle(handle); + acl(buf)->index = BT_CONN_INDEX_INVALID; + + LOG_DBG("handle %u len %u flags %u", acl(buf)->handle, len, flags); + + if (buf->len != len) + { + LOG_ERR("ACL data length mismatch (%u != %u)", buf->len, len); + net_buf_unref(buf); + return; + } + + conn = bt_conn_lookup_handle(acl(buf)->handle, BT_CONN_TYPE_ALL); + if (!conn) + { + LOG_ERR("Unable to find conn for handle %u", acl(buf)->handle); + net_buf_unref(buf); + return; + } + + acl(buf)->index = bt_conn_index(conn); + + bt_conn_recv(conn, buf, flags); + bt_conn_unref(conn); +} + +static void hci_data_buf_overflow(struct net_buf *buf) +{ + struct bt_hci_evt_data_buf_overflow *evt = (void *)buf->data; + + LOG_WRN("Data buffer overflow (link type 0x%02x)", evt->link_type); +} + +#if defined(CONFIG_BT_CENTRAL) +static void set_phy_conn_param(const struct bt_conn *conn, + struct bt_hci_ext_conn_phy *phy) +{ + phy->conn_interval_min = sys_cpu_to_le16(conn->le.interval_min); + phy->conn_interval_max = sys_cpu_to_le16(conn->le.interval_max); + phy->conn_latency = sys_cpu_to_le16(conn->le.latency); + phy->supervision_timeout = sys_cpu_to_le16(conn->le.timeout); + + phy->min_ce_len = 0; + phy->max_ce_len = 0; +} + +int bt_le_create_conn_ext(const struct bt_conn *conn) +{ + struct bt_hci_cp_le_ext_create_conn *cp; + struct bt_hci_ext_conn_phy *phy; + struct bt_hci_cmd_state_set state; + bool use_filter = false; + struct net_buf *buf; + uint8_t own_addr_type; + int err; + + if (IS_ENABLED(CONFIG_BT_FILTER_ACCEPT_LIST)) + { + use_filter = atomic_test_bit(conn->flags, BT_CONN_AUTO_CONNECT); + } + + err = bt_id_set_create_conn_own_addr(use_filter, &own_addr_type); + if (err) + { + return err; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + (void)memset(cp, 0, sizeof(*cp)); + + if (use_filter) + { + /* User Initiated procedure use fast scan parameters. */ + + bt_addr_le_copy(&cp->peer_addr, BT_ADDR_LE_ANY); + cp->filter_policy = BT_HCI_LE_CREATE_CONN_FP_FILTER; + } + else + { + const bt_addr_le_t *peer_addr = &conn->le.dst; + +#if defined(CONFIG_BT_SMP) + if (bt_dev.le.rl_entries > bt_dev.le.rl_size) + { + /* Host resolving is used, use the RPA directly. */ + + peer_addr = &conn->le.resp_addr; + } +#endif + + bt_addr_le_copy(&cp->peer_addr, peer_addr); + cp->filter_policy = BT_HCI_LE_CREATE_CONN_FP_NO_FILTER; + } + + cp->own_addr_type = own_addr_type; + cp->phys = 0; + + if (!(bt_dev.create_param.options & BT_CONN_LE_OPT_NO_1M)) + { + cp->phys |= BT_HCI_LE_EXT_SCAN_PHY_1M; + phy = net_buf_add(buf, sizeof(*phy)); + phy->scan_interval = sys_cpu_to_le16(bt_dev.create_param.interval); + phy->scan_window = sys_cpu_to_le16(bt_dev.create_param.window); + set_phy_conn_param(conn, phy); + } + + if (bt_dev.create_param.options & BT_CONN_LE_OPT_CODED) + { + cp->phys |= BT_HCI_LE_EXT_SCAN_PHY_CODED; + phy = net_buf_add(buf, sizeof(*phy)); + phy->scan_interval = + sys_cpu_to_le16(bt_dev.create_param.interval_coded); + phy->scan_window = sys_cpu_to_le16(bt_dev.create_param.window_coded); + set_phy_conn_param(conn, phy); + } + + bt_hci_cmd_state_set_init(buf, &state, bt_dev.flags, BT_DEV_INITIATING, + true); + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_EXT_CREATE_CONN, buf, NULL); +} + +int bt_le_create_conn_synced( + const struct bt_conn *conn, const struct bt_le_ext_adv *adv, + uint8_t subevent) +{ + struct bt_hci_cp_le_ext_create_conn_v2 *cp; + struct bt_hci_ext_conn_phy *phy; + struct bt_hci_cmd_state_set state; + struct net_buf *buf; + uint8_t own_addr_type; + int err; + + err = bt_id_set_create_conn_own_addr(false, &own_addr_type); + if (err) + { + return err; + } + + /* There shall only be one Initiating_PHYs */ + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + (void)memset(cp, 0, sizeof(*cp)); + + cp->subevent = subevent; + cp->adv_handle = adv->handle; + bt_addr_le_copy(&cp->peer_addr, &conn->le.dst); + cp->filter_policy = BT_HCI_LE_CREATE_CONN_FP_NO_FILTER; + cp->own_addr_type = own_addr_type; + + /* The Initiating_PHY is the secondary phy of the corresponding ext adv set + */ + + if (adv->options & BT_LE_ADV_OPT_CODED) + { + cp->phys = BT_HCI_LE_EXT_SCAN_PHY_CODED; + } + else if (adv->options & BT_LE_ADV_OPT_NO_2M) + { + cp->phys = BT_HCI_LE_EXT_SCAN_PHY_1M; + } + else + { + cp->phys = BT_HCI_LE_EXT_SCAN_PHY_2M; + } + + phy = net_buf_add(buf, sizeof(*phy)); + (void)memset(phy, 0, sizeof(*phy)); + set_phy_conn_param(conn, phy); + + bt_hci_cmd_state_set_init(buf, &state, bt_dev.flags, BT_DEV_INITIATING, + true); + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_EXT_CREATE_CONN_V2, buf, NULL); +} + +static int bt_le_create_conn_legacy(const struct bt_conn *conn) +{ + struct bt_hci_cp_le_create_conn *cp; + struct bt_hci_cmd_state_set state; + bool use_filter = false; + struct net_buf *buf; + uint8_t own_addr_type; + int err; + + if (IS_ENABLED(CONFIG_BT_FILTER_ACCEPT_LIST)) + { + use_filter = atomic_test_bit(conn->flags, BT_CONN_AUTO_CONNECT); + } + + err = bt_id_set_create_conn_own_addr(use_filter, &own_addr_type); + if (err) + { + return err; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + memset(cp, 0, sizeof(*cp)); + cp->own_addr_type = own_addr_type; + + if (use_filter) + { + /* User Initiated procedure use fast scan parameters. */ + + bt_addr_le_copy(&cp->peer_addr, BT_ADDR_LE_ANY); + cp->filter_policy = BT_HCI_LE_CREATE_CONN_FP_FILTER; + } + else + { + const bt_addr_le_t *peer_addr = &conn->le.dst; + +#if defined(CONFIG_BT_SMP) + if (bt_dev.le.rl_entries > bt_dev.le.rl_size) + { + /* Host resolving is used, use the RPA directly. */ + + peer_addr = &conn->le.resp_addr; + } +#endif + + bt_addr_le_copy(&cp->peer_addr, peer_addr); + cp->filter_policy = BT_HCI_LE_CREATE_CONN_FP_NO_FILTER; + } + + cp->scan_interval = sys_cpu_to_le16(bt_dev.create_param.interval); + cp->scan_window = sys_cpu_to_le16(bt_dev.create_param.window); + + cp->conn_interval_min = sys_cpu_to_le16(conn->le.interval_min); + cp->conn_interval_max = sys_cpu_to_le16(conn->le.interval_max); + cp->conn_latency = sys_cpu_to_le16(conn->le.latency); + cp->supervision_timeout = sys_cpu_to_le16(conn->le.timeout); + + bt_hci_cmd_state_set_init(buf, &state, bt_dev.flags, BT_DEV_INITIATING, + true); + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CREATE_CONN, buf, NULL); +} + +int bt_le_create_conn(const struct bt_conn *conn) +{ + if (IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features)) + { + return bt_le_create_conn_ext(conn); + } + + return bt_le_create_conn_legacy(conn); +} + +int bt_le_create_conn_cancel(void) +{ + struct net_buf *buf; + struct bt_hci_cmd_state_set state; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + bt_hci_cmd_state_set_init(buf, &state, bt_dev.flags, BT_DEV_INITIATING, + false); + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CREATE_CONN_CANCEL, buf, NULL); +} +#endif /* CONFIG_BT_CENTRAL */ + +int bt_hci_disconnect(uint16_t handle, uint8_t reason) +{ + struct net_buf *buf; + struct bt_hci_cp_disconnect *disconn; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + disconn = net_buf_add(buf, sizeof(*disconn)); + disconn->handle = sys_cpu_to_le16(handle); + disconn->reason = reason; + + return bt_hci_cmd_send_sync(BT_HCI_OP_DISCONNECT, buf, NULL); +} + +static uint16_t disconnected_handles[CONFIG_BT_MAX_CONN]; +static uint8_t disconnected_handles_reason[CONFIG_BT_MAX_CONN]; + +static void disconnected_handles_reset(void) +{ + (void)memset(disconnected_handles, 0, sizeof(disconnected_handles)); +} + +static void conn_handle_disconnected(uint16_t handle, + uint8_t disconnect_reason) +{ + for (int i = 0; i < ARRAY_SIZE(disconnected_handles); i++) + { + if (!disconnected_handles[i]) + { + /* Use invalid connection handle bits so that connection + * handle 0 can be used as a valid non-zero handle. + */ + + disconnected_handles[i] = ~BT_ACL_HANDLE_MASK | handle; + disconnected_handles_reason[i] = disconnect_reason; + + return; + } + } +} + +/** @returns the disconnect reason. */ + +static uint8_t conn_handle_is_disconnected(uint16_t handle) +{ + handle |= ~BT_ACL_HANDLE_MASK; + + for (int i = 0; i < ARRAY_SIZE(disconnected_handles); i++) + { + if (disconnected_handles[i] == handle) + { + disconnected_handles[i] = 0; + return disconnected_handles_reason[i]; + } + } + + return 0; +} + +static void hci_disconn_complete_prio(struct net_buf *buf) +{ + struct bt_hci_evt_disconn_complete *evt = (void *)buf->data; + uint16_t handle = sys_le16_to_cpu(evt->handle); + struct bt_conn *conn; + + LOG_DBG("status 0x%02x %s handle %u reason 0x%02x", evt->status, + bt_hci_err_to_str(evt->status), + handle, evt->reason); + + if (evt->status) + { + return; + } + + conn = bt_conn_lookup_handle(handle, BT_CONN_TYPE_ALL); + if (!conn) + { + /* Priority disconnect complete event received before normal + * connection complete event. + */ + + conn_handle_disconnected(handle, evt->reason); + return; + } + + conn->err = evt->reason; + + bt_conn_set_state(conn, BT_CONN_DISCONNECT_COMPLETE); + bt_conn_unref(conn); +} + +static void hci_disconn_complete(struct net_buf *buf) +{ + struct bt_hci_evt_disconn_complete *evt = (void *)buf->data; + uint16_t handle = sys_le16_to_cpu(evt->handle); + struct bt_conn *conn; + + LOG_DBG("status 0x%02x %s handle %u reason 0x%02x", evt->status, + bt_hci_err_to_str(evt->status), + handle, evt->reason); + + if (evt->status) + { + return; + } + + conn = bt_conn_lookup_handle(handle, BT_CONN_TYPE_ALL); + if (!conn) + { + LOG_ERR("Unable to look up conn with handle %u", handle); + return; + } + + bt_conn_set_state(conn, BT_CONN_DISCONNECTED); + + if (!bt_conn_is_le(conn)) + { +#if defined(CONFIG_BT_CLASSIC) + if (bt_conn_is_sco(conn)) + { + bt_sco_cleanup(conn); + return; + } + + /* If only for one connection session bond was set, clear keys + * database row for this connection. + */ + + if (bt_conn_is_br(conn) && conn->br.link_key != NULL) + { + /* If the connection link is paired but not bond, remove + * the link key upon disconnection. + */ + + if (atomic_test_and_clear_bit(conn->flags, BT_CONN_BR_NOBOND)) + { + bt_keys_link_key_clear(conn->br.link_key); + } + + conn->br.link_key->enc_key_size = 0; + } +#endif + + bt_conn_unref(conn); + return; + } + +#if defined(CONFIG_BT_CENTRAL) && !defined(CONFIG_BT_FILTER_ACCEPT_LIST) + if (atomic_test_bit(conn->flags, BT_CONN_AUTO_CONNECT)) + { + bt_conn_set_state(conn, BT_CONN_SCAN_BEFORE_INITIATING); + + /* Just a best-effort check if the scanner should be started. */ + + int err = bt_le_scan_user_remove(BT_LE_SCAN_USER_NONE); + + if (err) + { + LOG_WRN("Error while updating the scanner (%d)", err); + } + } +#endif /* defined(CONFIG_BT_CENTRAL) && !defined(CONFIG_BT_FILTER_ACCEPT_LIST) */ + + bt_conn_unref(conn); +} + +int bt_hci_le_read_remote_features(struct bt_conn *conn) +{ + struct bt_hci_cp_le_read_remote_features *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + + /* Results in BT_HCI_EVT_LE_REMOTE_FEAT_COMPLETE */ + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_READ_REMOTE_FEATURES, buf, NULL); +} + +int bt_hci_read_remote_version(struct bt_conn *conn) +{ + struct bt_hci_cp_read_remote_version_info *cp; + struct net_buf *buf; + + if (conn->state != BT_CONN_CONNECTED) + { + return -ENOTCONN; + } + + /* Remote version cannot change. */ + + if (atomic_test_bit(conn->flags, BT_CONN_AUTO_VERSION_INFO)) + { + return 0; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + + return bt_hci_cmd_send_sync(BT_HCI_OP_READ_REMOTE_VERSION_INFO, buf, NULL); +} + +/* LE Data Length Change Event is optional so this function just ignore + * error and stack will continue to use default values. + */ + +int bt_le_set_data_len(struct bt_conn *conn, + uint16_t tx_octets, uint16_t tx_time) +{ + struct bt_hci_cp_le_set_data_len *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + cp->tx_octets = sys_cpu_to_le16(tx_octets); + cp->tx_time = sys_cpu_to_le16(tx_time); + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_DATA_LEN, buf, NULL); +} + +#if defined(CONFIG_BT_USER_PHY_UPDATE) +static int hci_le_read_phy(struct bt_conn *conn) +{ + struct bt_hci_cp_le_read_phy *cp; + struct bt_hci_rp_le_read_phy *rp; + struct net_buf *buf; + struct net_buf *rsp; + int err; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_READ_PHY, buf, &rsp); + if (err) + { + return err; + } + + rp = (void *)rsp->data; + conn->le.phy.tx_phy = bt_get_phy(rp->tx_phy); + conn->le.phy.rx_phy = bt_get_phy(rp->rx_phy); + net_buf_unref(rsp); + + return 0; +} +#endif /* defined(CONFIG_BT_USER_PHY_UPDATE) */ + +int bt_le_set_default_phy(uint8_t all_phys, + uint8_t pref_tx_phy, uint8_t pref_rx_phy) +{ + struct bt_hci_cp_le_set_default_phy *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->all_phys = all_phys; + cp->tx_phys = pref_tx_phy; + cp->rx_phys = pref_rx_phy; + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_DEFAULT_PHY, buf, NULL); +} + +int bt_le_set_phy( + struct bt_conn *conn, uint8_t all_phys, uint8_t pref_tx_phy, + uint8_t pref_rx_phy, uint8_t phy_opts) +{ + struct bt_hci_cp_le_set_phy *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(conn->handle); + cp->all_phys = all_phys; + cp->tx_phys = pref_tx_phy; + cp->rx_phys = pref_rx_phy; + cp->phy_opts = phy_opts; + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_PHY, buf, NULL); +} + +static struct bt_conn *find_pending_connect( + uint8_t role, const bt_addr_le_t *peer_addr, + const struct bt_le_ext_adv *ext_adv) +{ + struct bt_conn *conn; + + /* Make lookup to check if there's a connection object in + * CONNECT or CONNECT_AUTO state associated with passed peer LE address. + */ + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && role == BT_HCI_ROLE_CENTRAL) + { + conn = bt_conn_lookup_state_le(BT_ID_DEFAULT, peer_addr, + BT_CONN_INITIATING); + if (IS_ENABLED(CONFIG_BT_FILTER_ACCEPT_LIST) && !conn) + { + conn = bt_conn_lookup_state_le(BT_ID_DEFAULT, BT_ADDR_LE_NONE, + BT_CONN_INITIATING_FILTER_LIST); + } + + return conn; + } + + if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && role == BT_HCI_ROLE_PERIPHERAL) + { + /* Do not fall back between directed and undirected pending connections + * when the terminating advertising set is known. Such a fallback can + * consume a reservation belonging to another active set. + */ + + if (ext_adv != NULL) + { + if (bt_addr_le_eq(&ext_adv->target_addr, BT_ADDR_LE_ANY)) + { + /* Having multiple same-identity undirected reservations and + * finding the first one that might not have been the one that + * was used to initiate the connection is not a problem. + * Undirected reservations have no advertising-set association + * or per-set state, so any matching reservation can be + * consumed; one remains for each other enabled set. + */ + + return bt_conn_lookup_state_le(ext_adv->id, BT_ADDR_LE_NONE, + BT_CONN_ADV_CONNECTABLE); + } + + return bt_conn_lookup_state_le(ext_adv->id, &ext_adv->target_addr, + BT_CONN_ADV_DIR_CONNECTABLE); + } + + /* In case there is no advertising handle, there can be at most one + * relevant peripheral advertiser. This is the case for legacy + * advertising, or when the controller does not support extended + * advertising. In this case, we can fall back to the legacy lookup + * behaviour. + */ + + conn = bt_conn_lookup_state_le(bt_dev.adv_conn_id, peer_addr, + BT_CONN_ADV_DIR_CONNECTABLE); + if (!conn) + { + conn = bt_conn_lookup_state_le(bt_dev.adv_conn_id, BT_ADDR_LE_NONE, + BT_CONN_ADV_CONNECTABLE); + } + + return conn; + } + + return NULL; +} + +static void le_conn_complete_cancel(uint8_t err) +{ + int ret; + struct bt_conn *conn; + + /* Handle create connection cancel. + * + * There is no need to check ID address as only one connection in central + * role can be in pending state. + */ + + conn = find_pending_connect(BT_HCI_ROLE_CENTRAL, NULL, NULL); + if (!conn) + { + LOG_ERR("No pending central connection"); + return; + } + + if (atomic_test_bit(conn->flags, BT_CONN_AUTO_CONNECT)) + { + if (!IS_ENABLED(CONFIG_BT_FILTER_ACCEPT_LIST)) + { + /* Restart passive scanner for device */ + + bt_conn_set_state(conn, BT_CONN_SCAN_BEFORE_INITIATING); + } + else + { + /* Restart FAL initiator after RPA timeout. */ + + ret = bt_le_create_conn(conn); + if (ret) + { + LOG_ERR("Failed to restart initiator"); + } + } + } + else + { + int busy_status = k_work_delayable_busy_get(&conn->deferred_work); + + if (!(busy_status & (K_WORK_QUEUED | K_WORK_DELAYED))) + { + LOG_WRN("Connection creation timeout triggered"); + conn->err = err; + bt_conn_set_state(conn, BT_CONN_DISCONNECTED); + } + else + { + /* Restart initiator after RPA timeout. */ + + ret = bt_le_create_conn(conn); + if (ret) + { + LOG_ERR("Failed to restart initiator"); + } + } + } + + bt_conn_unref(conn); +} + +static void le_conn_complete_adv_timeout(void) +{ + if (!(IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features))) + { + struct bt_le_ext_adv *adv = bt_le_adv_lookup_legacy(); + struct bt_conn *conn; + + /* Handle advertising timeout after high duty cycle directed + * advertising. + */ + + atomic_clear_bit(adv->flags, BT_ADV_ENABLED); + + if (IS_ENABLED(CONFIG_BT_EXT_ADV) && + !BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features)) + { + /* No advertising set terminated event, must be a + * legacy advertiser set. + */ + + bt_le_adv_delete_legacy(); + } + + /* There is no need to check ID address as only one + * connection in peripheral role can be in pending state. + */ + + conn = find_pending_connect(BT_HCI_ROLE_PERIPHERAL, NULL, NULL); + if (!conn) + { + LOG_ERR("No pending peripheral connection"); + return; + } + + conn->err = BT_HCI_ERR_ADV_TIMEOUT; + bt_conn_set_state(conn, BT_CONN_DISCONNECTED); + + bt_conn_unref(conn); + } +} + +static void enh_conn_complete(struct bt_hci_evt_le_enh_conn_complete *evt) +{ +#if defined(CONFIG_BT_CONN) && (CONFIG_BT_EXT_ADV_MAX_ADV_SET > 1) + if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && + evt->role == BT_HCI_ROLE_PERIPHERAL && + evt->status == BT_HCI_ERR_SUCCESS && + (IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_FEAT_LE_EXT_ADV(bt_dev.le.features))) + { + /* Cache the connection complete event. Process it later. + * See bt_dev.cached_conn_complete. + */ + + for (int i = 0; i < ARRAY_SIZE(bt_dev.cached_conn_complete); i++) + { + if (!bt_dev.cached_conn_complete[i].valid) + { + (void)memcpy(&bt_dev.cached_conn_complete[i].evt, evt, + sizeof(struct bt_hci_evt_le_enh_conn_complete)); + bt_dev.cached_conn_complete[i].valid = true; + return; + } + } + + DEBUGASSERT(false); + + return; + } +#endif + + bt_hci_le_enh_conn_complete(evt, NULL); +} + +static void translate_addrs(bt_addr_le_t *peer_addr, + bt_addr_le_t *id_addr, + const struct + bt_hci_evt_le_enh_conn_complete *evt, + uint8_t id) +{ + if (bt_addr_le_is_resolved(&evt->peer_addr)) + { + bt_addr_le_copy_resolved(id_addr, &evt->peer_addr); + bt_addr_le_copy_addr(peer_addr, &evt->peer_rpa, BT_ADDR_LE_RANDOM); + } + else + { + bt_addr_le_copy(id_addr, bt_lookup_id_addr(id, &evt->peer_addr)); + bt_addr_le_copy(peer_addr, &evt->peer_addr); + } +} + +static void update_conn(struct bt_conn *conn, const bt_addr_le_t *id_addr, + const struct bt_hci_evt_le_enh_conn_complete *evt) +{ + conn->handle = sys_le16_to_cpu(evt->handle); + bt_addr_le_copy(&conn->le.dst, id_addr); + conn->le.interval_us = + sys_le16_to_cpu(evt->interval) * BT_HCI_LE_INTERVAL_UNIT_US; + conn->le.latency = sys_le16_to_cpu(evt->latency); + conn->le.timeout = sys_le16_to_cpu(evt->supv_timeout); + conn->role = evt->role; + conn->err = 0U; + +#if defined(CONFIG_BT_USER_DATA_LEN_UPDATE) + conn->le.data_len.tx_max_len = BT_GAP_DATA_LEN_DEFAULT; + conn->le.data_len.tx_max_time = BT_GAP_DATA_TIME_DEFAULT; + conn->le.data_len.rx_max_len = BT_GAP_DATA_LEN_DEFAULT; + conn->le.data_len.rx_max_time = BT_GAP_DATA_TIME_DEFAULT; +#endif +#if defined(CONFIG_BT_SUBRATING) + conn->le.subrate.factor = 1; /* No subrating. */ + conn->le.subrate.continuation_number = 0; +#endif +} + +void bt_hci_le_enh_conn_complete(struct bt_hci_evt_le_enh_conn_complete *evt, + const struct bt_le_ext_adv *ext_adv) +{ + DEBUGASSERT(evt->status == BT_HCI_ERR_SUCCESS); + + uint16_t handle = sys_le16_to_cpu(evt->handle); + uint8_t disconnect_reason = conn_handle_is_disconnected(handle); + bt_addr_le_t peer_addr; + bt_addr_le_t id_addr; + struct bt_conn *conn; + uint8_t id; + + LOG_DBG("status 0x%02x %s handle %u role %u peer %s%s peer RPA %s", + evt->status, + bt_hci_err_to_str(evt->status), handle, evt->role, + bt_addr_le_str(&evt->peer_addr), + bt_addr_le_is_resolved(&evt->peer_addr) ? " (resolved)" : "", + bt_addr_str(&evt->peer_rpa)); + LOG_DBG("local RPA %s", bt_addr_str(&evt->local_rpa)); + +#if defined(CONFIG_BT_SMP) + bt_id_pending_keys_update(); +#endif + + id = BT_ID_DEFAULT; + if (evt->role == BT_HCI_ROLE_PERIPHERAL) + { + id = ext_adv != NULL ? ext_adv->id : bt_dev.adv_conn_id; + } + + translate_addrs(&peer_addr, &id_addr, evt, id); + + conn = find_pending_connect(evt->role, &id_addr, ext_adv); + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && evt->role == BT_HCI_ROLE_CENTRAL) + { + /* Clear initiating even if we are not able to add connection + * object to keep the host in sync with controller state. + */ + + atomic_clear_bit(bt_dev.flags, BT_DEV_INITIATING); + } + + if (!conn) + { + LOG_ERR("No pending conn for peer %s%s", + bt_addr_le_str(&evt->peer_addr), + bt_addr_le_is_resolved(&evt->peer_addr) ? " (resolved)" : ""); + bt_hci_disconnect(handle, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + return; + } + + update_conn(conn, &id_addr, evt); + +#if defined(CONFIG_BT_USER_PHY_UPDATE) + conn->le.phy.tx_phy = BT_GAP_LE_PHY_1M; + conn->le.phy.rx_phy = BT_GAP_LE_PHY_1M; +#endif + /* Use connection address (instead of identity address) as initiator + * or responder address. Only peripheral needs to be updated. For central + * all was set during outgoing connection creation. + */ + + if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && + conn->role == BT_HCI_ROLE_PERIPHERAL) + { + bt_addr_le_copy(&conn->le.init_addr, &peer_addr); + + if (!(IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features))) + { + struct bt_le_ext_adv *adv = bt_le_adv_lookup_legacy(); + + if (IS_ENABLED(CONFIG_BT_PRIVACY) && + !atomic_test_bit(adv->flags, BT_ADV_USE_IDENTITY)) + { + if (!bt_addr_eq(&evt->local_rpa, BT_ADDR_ANY)) + { + bt_addr_le_copy_addr(&conn->le.resp_addr, &evt->local_rpa, + BT_ADDR_LE_RANDOM); + } + else + { + bt_addr_le_copy_addr(&conn->le.resp_addr, + &bt_dev.random_addr, BT_ADDR_LE_RANDOM); + } + } + else + { + bt_addr_le_copy(&conn->le.resp_addr, + &bt_dev.id_addr[conn->id]); + } + } + else + { + /* Copy the local RPA and handle this in advertising set + * terminated event. + */ + + bt_addr_le_copy_addr(&conn->le.resp_addr, &evt->local_rpa, + BT_ADDR_LE_RANDOM); + } + + if (IS_ENABLED(CONFIG_BT_EXT_ADV) && + !BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features)) + { + /* No advertising set terminated event, must be a + * legacy advertiser set. + */ + + bt_le_adv_delete_legacy(); + } + } + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && conn->role == BT_HCI_ROLE_CENTRAL) + { + bt_addr_le_copy(&conn->le.resp_addr, &peer_addr); + + if (IS_ENABLED(CONFIG_BT_PRIVACY)) + { + if (!bt_addr_eq(&evt->local_rpa, BT_ADDR_ANY)) + { + bt_addr_le_copy_addr(&conn->le.init_addr, &evt->local_rpa, + BT_ADDR_LE_RANDOM); + } + else + { + bt_addr_le_copy_addr(&conn->le.init_addr, &bt_dev.random_addr, + BT_ADDR_LE_RANDOM); + } + } + else + { + bt_addr_le_copy(&conn->le.init_addr, &bt_dev.id_addr[conn->id]); + } + } + +#if defined(CONFIG_BT_USER_PHY_UPDATE) + if (IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features)) + { + int err; + + err = hci_le_read_phy(conn); + if (err) + { + LOG_WRN("Failed to read PHY (%d)", err); + } + } +#endif /* defined(CONFIG_BT_USER_PHY_UPDATE) */ + + bt_conn_set_state(conn, BT_CONN_CONNECTED); + + if (disconnect_reason) + { + /* Mark the connection as already disconnected before calling + * the connected callback, so that the application cannot start sending + * packets + */ + + conn->err = disconnect_reason; + bt_conn_set_state(conn, BT_CONN_DISCONNECT_COMPLETE); + } + + bt_conn_connected(conn); + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && conn->role == BT_HCI_ROLE_CENTRAL) + { + int err; + + /* Just a best-effort check if the scanner should be started. */ + + err = bt_le_scan_user_remove(BT_LE_SCAN_USER_NONE); + if (err) + { + LOG_WRN("Error while updating the scanner (%d)", err); + } + } + + bt_conn_unref(conn); +} + +#if defined(CONFIG_BT_PER_ADV_SYNC_RSP) +void bt_hci_le_enh_conn_complete_sync( + struct bt_hci_evt_le_enh_conn_complete_v2 *evt, + struct bt_le_per_adv_sync *sync) +{ + DEBUGASSERT(evt->status == BT_HCI_ERR_SUCCESS); + + uint16_t handle = sys_le16_to_cpu(evt->handle); + uint8_t disconnect_reason = conn_handle_is_disconnected(handle); + bt_addr_le_t peer_addr; + bt_addr_le_t id_addr; + struct bt_conn *conn; + + if (!sync->num_subevents) + { + LOG_ERR("Unexpected connection complete event"); + + return; + } + + conn = bt_conn_add_le(BT_ID_DEFAULT, BT_ADDR_LE_ANY); + if (!conn) + { + LOG_ERR("Unable to allocate connection"); + /* Tell the controller to disconnect to keep it in sync with + * the host state and avoid a "rogue" connection. + */ + + bt_hci_disconnect(handle, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + + return; + } + + LOG_DBG("status 0x%02x %s handle %u role %u peer %s%s peer RPA %s", + evt->status, + bt_hci_err_to_str(evt->status), handle, evt->role, + bt_addr_le_str(&evt->peer_addr), + bt_addr_le_is_resolved(&evt->peer_addr) ? " (resolved)" : "", + bt_addr_str(&evt->peer_rpa)); + LOG_DBG("local RPA %s", bt_addr_str(&evt->local_rpa)); + + if (evt->role != BT_HCI_ROLE_PERIPHERAL) + { + LOG_ERR("PAwR sync always becomes peripheral"); + + return; + } + +#if defined(CONFIG_BT_SMP) + bt_id_pending_keys_update(); +#endif + + translate_addrs(&peer_addr, &id_addr, + (const struct bt_hci_evt_le_enh_conn_complete *)evt, + BT_ID_DEFAULT); + update_conn(conn, + &id_addr, + (const struct bt_hci_evt_le_enh_conn_complete *)evt); + +#if defined(CONFIG_BT_USER_PHY_UPDATE) + /* The connection is always initiated on the same phy as the PAwR + * advertiser + */ + + conn->le.phy.tx_phy = sync->phy; + conn->le.phy.rx_phy = sync->phy; +#endif + + bt_addr_le_copy(&conn->le.init_addr, &peer_addr); + + if (IS_ENABLED(CONFIG_BT_PRIVACY)) + { + bt_addr_le_copy_addr(&conn->le.resp_addr, &evt->local_rpa, + BT_ADDR_LE_RANDOM); + } + else + { + bt_addr_le_copy(&conn->le.resp_addr, &bt_dev.id_addr[conn->id]); + } + + bt_conn_set_state(conn, BT_CONN_CONNECTED); + + if (disconnect_reason) + { + /* Mark the connection as already disconnected before calling + * the connected callback, so that the application cannot start sending + * packets + */ + + conn->err = disconnect_reason; + bt_conn_set_state(conn, BT_CONN_DISCONNECT_COMPLETE); + } + + bt_conn_connected(conn); + + /* Since we don't give the application a reference to manage + * for peripheral connections, we need to release this reference here. + */ + + bt_conn_unref(conn); +} +#endif /* CONFIG_BT_PER_ADV_SYNC_RSP */ + +static void enh_conn_complete_error_handle(uint8_t status) +{ + if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && status == BT_HCI_ERR_ADV_TIMEOUT) + { + le_conn_complete_adv_timeout(); + return; + } + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && status == BT_HCI_ERR_UNKNOWN_CONN_ID) + { + le_conn_complete_cancel(status); + int err = bt_le_scan_user_remove(BT_LE_SCAN_USER_NONE); + + if (err) + { + LOG_WRN("Error while updating the scanner (%d)", err); + } + + return; + } + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && IS_ENABLED(CONFIG_BT_PER_ADV_RSP) && + status == BT_HCI_ERR_CONN_FAIL_TO_ESTAB) + { + le_conn_complete_cancel(status); + + atomic_clear_bit(bt_dev.flags, BT_DEV_INITIATING); + + return; + } + + LOG_WRN("Unexpected status 0x%02x %s", status, bt_hci_err_to_str(status)); +} + +static void le_enh_conn_complete(struct net_buf *buf) +{ + struct bt_hci_evt_le_enh_conn_complete *evt = + (struct bt_hci_evt_le_enh_conn_complete *)buf->data; + + if (evt->status != BT_HCI_ERR_SUCCESS) + { + enh_conn_complete_error_handle(evt->status); + return; + } + + enh_conn_complete(evt); +} + +#if defined(CONFIG_BT_PER_ADV_RSP) || defined(CONFIG_BT_PER_ADV_SYNC_RSP) +static void le_enh_conn_complete_v2(struct net_buf *buf) +{ + struct bt_hci_evt_le_enh_conn_complete_v2 *evt = + (struct bt_hci_evt_le_enh_conn_complete_v2 *)buf->data; + + if (evt->status != BT_HCI_ERR_SUCCESS) + { + enh_conn_complete_error_handle(evt->status); + return; + } + + if (evt->adv_handle == BT_HCI_ADV_HANDLE_INVALID && + evt->sync_handle == BT_HCI_SYNC_HANDLE_INVALID) + { + /* The connection was not created via PAwR, handle the event like v1 */ + + enh_conn_complete((struct bt_hci_evt_le_enh_conn_complete *)evt); + } +#if defined(CONFIG_BT_PER_ADV_RSP) + else if (evt->adv_handle != BT_HCI_ADV_HANDLE_INVALID && + evt->sync_handle == BT_HCI_SYNC_HANDLE_INVALID) + { + /* The connection was created via PAwR advertiser, it can be handled + * like v1 + */ + + enh_conn_complete((struct bt_hci_evt_le_enh_conn_complete *)evt); + } +#endif /* CONFIG_BT_PER_ADV_RSP */ +#if defined(CONFIG_BT_PER_ADV_SYNC_RSP) + else if (evt->adv_handle == BT_HCI_ADV_HANDLE_INVALID && + evt->sync_handle != BT_HCI_SYNC_HANDLE_INVALID) + { + /* Created via PAwR sync, no adv set terminated event, needs separate + * handling + */ + + struct bt_le_per_adv_sync *sync; + + sync = bt_hci_per_adv_sync_lookup_handle(evt->sync_handle); + if (!sync) + { + LOG_ERR("Unknown sync handle %d", evt->sync_handle); + + return; + } + + bt_hci_le_enh_conn_complete_sync(evt, sync); + } +#endif /* CONFIG_BT_PER_ADV_SYNC_RSP */ + else + { + LOG_ERR("Invalid connection complete event"); + } +} +#endif /* CONFIG_BT_PER_ADV_RSP || CONFIG_BT_PER_ADV_SYNC_RSP */ + +static void le_legacy_conn_complete(struct net_buf *buf) +{ + struct bt_hci_evt_le_conn_complete *evt = (void *)buf->data; + struct bt_hci_evt_le_enh_conn_complete enh; + + if (evt->status != BT_HCI_ERR_SUCCESS) + { + enh_conn_complete_error_handle(evt->status); + return; + } + + LOG_DBG("status 0x%02x %s role %u %s", evt->status, + bt_hci_err_to_str(evt->status), evt->role, + bt_addr_le_str(&evt->peer_addr)); + + enh.status = evt->status; + enh.handle = evt->handle; + enh.role = evt->role; + enh.interval = evt->interval; + enh.latency = evt->latency; + enh.supv_timeout = evt->supv_timeout; + enh.clock_accuracy = evt->clock_accuracy; + + bt_addr_le_copy(&enh.peer_addr, &evt->peer_addr); + + if (IS_ENABLED(CONFIG_BT_PRIVACY)) + { + bt_addr_copy(&enh.local_rpa, &bt_dev.random_addr); + } + else + { + bt_addr_copy(&enh.local_rpa, BT_ADDR_ANY); + } + + bt_addr_copy(&enh.peer_rpa, BT_ADDR_ANY); + + enh_conn_complete(&enh); +} + +static void le_remote_feat_complete(struct net_buf *buf) +{ + struct bt_hci_evt_le_remote_feat_complete *evt = (void *)buf->data; + uint16_t handle = sys_le16_to_cpu(evt->handle); + struct bt_conn *conn; + + conn = bt_conn_lookup_handle(handle, BT_CONN_TYPE_LE); + if (!conn) + { + LOG_ERR("Unable to lookup conn for handle %u", handle); + return; + } + + if (!evt->status) + { + memcpy(conn->le.features, evt->features, sizeof(conn->le.features)); + } + + atomic_set_bit(conn->flags, BT_CONN_LE_FEATURES_EXCHANGED); + + if (IS_ENABLED(CONFIG_BT_REMOTE_INFO) && + (!IS_ENABLED(CONFIG_BT_REMOTE_VERSION) || + atomic_test_bit(conn->flags, BT_CONN_AUTO_VERSION_INFO))) + { + bt_conn_notify_remote_info(conn); + } + + bt_conn_unref(conn); +} + +#if defined(CONFIG_BT_LE_EXTENDED_FEAT_SET) +static void le_read_all_remote_feat_complete(struct net_buf *buf) +{ + struct bt_hci_evt_le_read_all_remote_feat_complete + *evt = (void *)buf->data; + struct bt_conn *conn; + struct bt_conn_le_read_all_remote_feat_complete params; + uint16_t handle = sys_le16_to_cpu(evt->handle); + + LOG_DBG("Read all remote feature complete: 0x%02x %s handle %u", + evt->status, + bt_hci_err_to_str(evt->status), handle); + + conn = bt_conn_lookup_handle(handle, BT_CONN_TYPE_LE); + if (conn == NULL) + { + LOG_ERR("Unknown conn handle 0x%04X", handle); + return; + } + + params.status = evt->status; + + if (params.status == BT_HCI_ERR_SUCCESS) + { + params.max_remote_page = evt->max_remote_page; + params.max_valid_page = evt->max_valid_page; + params.features = evt->features; + } + + bt_conn_notify_read_all_remote_feat_complete(conn, ¶ms); + + bt_conn_unref(conn); +} +#endif /* CONFIG_BT_LE_EXTENDED_FEAT_SET */ + +#if defined(CONFIG_BT_FRAME_SPACE_UPDATE) +static void le_frame_space_update_complete(struct net_buf *buf) +{ + struct bt_hci_evt_le_frame_space_update_complete *evt = (void *)buf->data; + struct bt_conn *conn; + struct bt_conn_le_frame_space_updated params; + uint16_t handle = sys_le16_to_cpu(evt->handle); + + conn = bt_conn_lookup_handle(handle, BT_CONN_TYPE_LE); + if (conn == NULL) + { + LOG_ERR("Unknown conn handle 0x%04X", handle); + return; + } + + params.status = evt->status; + + if (params.status == BT_HCI_ERR_SUCCESS) + { + params.phys = evt->phys; + params.spacing_types = evt->spacing_types; + params.frame_space = evt->frame_space; + params.initiator = evt->initiator; + } + + bt_conn_notify_frame_space_update_complete(conn, ¶ms); + + bt_conn_unref(conn); +} +#endif /* CONFIG_BT_FRAME_SPACE_UPDATE */ + +#if defined(CONFIG_BT_DATA_LEN_UPDATE) +static void le_data_len_change(struct net_buf *buf) +{ + struct bt_hci_evt_le_data_len_change *evt = (void *)buf->data; + uint16_t handle = sys_le16_to_cpu(evt->handle); + struct bt_conn *conn; + + conn = bt_conn_lookup_handle(handle, BT_CONN_TYPE_LE); + if (!conn) + { + LOG_ERR("Unable to lookup conn for handle %u", handle); + return; + } + +#if defined(CONFIG_BT_USER_DATA_LEN_UPDATE) + uint16_t max_tx_octets = sys_le16_to_cpu(evt->max_tx_octets); + uint16_t max_rx_octets = sys_le16_to_cpu(evt->max_rx_octets); + uint16_t max_tx_time = sys_le16_to_cpu(evt->max_tx_time); + uint16_t max_rx_time = sys_le16_to_cpu(evt->max_rx_time); + + if (!IN_RANGE(max_tx_octets, BT_HCI_LE_MAX_TX_OCTETS_MIN, + BT_HCI_LE_MAX_TX_OCTETS_MAX)) + { + LOG_WRN("max_tx_octets exceeds the valid range %u", max_tx_octets); + } + + if (!IN_RANGE(max_rx_octets, BT_HCI_LE_MAX_RX_OCTETS_MIN, + BT_HCI_LE_MAX_RX_OCTETS_MAX)) + { + LOG_WRN("max_rx_octets exceeds the valid range %u", max_rx_octets); + } + + if (!IN_RANGE(max_tx_time, BT_HCI_LE_MAX_TX_TIME_MIN, + BT_HCI_LE_MAX_TX_TIME_MAX)) + { + LOG_WRN("max_tx_time exceeds the valid range %u", max_tx_time); + } + + if (!IN_RANGE(max_rx_time, BT_HCI_LE_MAX_RX_TIME_MIN, + BT_HCI_LE_MAX_RX_TIME_MAX)) + { + LOG_WRN("max_rx_time exceeds the valid range %u", max_rx_time); + } + + LOG_DBG("max. tx: %u (%uus), max. rx: %u (%uus)", max_tx_octets, + max_tx_time, + max_rx_octets, + max_rx_time); + + conn->le.data_len.tx_max_len = max_tx_octets; + conn->le.data_len.tx_max_time = max_tx_time; + conn->le.data_len.rx_max_len = max_rx_octets; + conn->le.data_len.rx_max_time = max_rx_time; + bt_conn_notify_le_data_len_updated(conn); +#endif + + bt_conn_unref(conn); +} +#endif /* CONFIG_BT_DATA_LEN_UPDATE */ + +#if defined(CONFIG_BT_PHY_UPDATE) +static void le_phy_update_complete(struct net_buf *buf) +{ + struct bt_hci_evt_le_phy_update_complete *evt = (void *)buf->data; + uint16_t handle = sys_le16_to_cpu(evt->handle); + struct bt_conn *conn; + + conn = bt_conn_lookup_handle(handle, BT_CONN_TYPE_LE); + if (!conn) + { + LOG_ERR("Unable to lookup conn for handle %u", handle); + return; + } + + LOG_DBG("PHY updated: status: 0x%02x %s, tx: %u, rx: %u", evt->status, + bt_hci_err_to_str(evt->status), evt->tx_phy, evt->rx_phy); + +#if defined(CONFIG_BT_USER_PHY_UPDATE) + conn->le.phy.tx_phy = bt_get_phy(evt->tx_phy); + conn->le.phy.rx_phy = bt_get_phy(evt->rx_phy); + bt_conn_notify_le_phy_updated(conn); +#endif + + bt_conn_unref(conn); +} +#endif /* CONFIG_BT_PHY_UPDATE */ + +bool bt_le_conn_params_valid(const struct bt_le_conn_param *param) +{ + if (IS_ENABLED(CONFIG_BT_CONN_PARAM_ANY)) + { + return true; + } + + /* All limits according to BT Core spec 5.0 [Vol 2, Part E, 7.8.12] */ + + if (param->interval_min > param->interval_max || param->interval_min < 6 || + param->interval_max > 3200) + { + return false; + } + + if (param->latency > 499) + { + return false; + } + + if (param->timeout < 10 || param->timeout > 3200 || + ((param->timeout * 4U) <= ((1U + + param->latency) * param->interval_max))) + { + return false; + } + + return true; +} + +static void le_conn_param_neg_reply(uint16_t handle, uint8_t reason) +{ + struct bt_hci_cp_le_conn_param_req_neg_reply *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + LOG_ERR("Unable to allocate buffer"); + return; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(handle); + cp->reason = sys_cpu_to_le16(reason); + + bt_hci_cmd_send(BT_HCI_OP_LE_CONN_PARAM_REQ_NEG_REPLY, buf); +} + +static int le_conn_param_req_reply(uint16_t handle, + const struct bt_le_conn_param *param) +{ + struct bt_hci_cp_le_conn_param_req_reply *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + (void)memset(cp, 0, sizeof(*cp)); + + cp->handle = sys_cpu_to_le16(handle); + cp->interval_min = sys_cpu_to_le16(param->interval_min); + cp->interval_max = sys_cpu_to_le16(param->interval_max); + cp->latency = sys_cpu_to_le16(param->latency); + cp->timeout = sys_cpu_to_le16(param->timeout); + + return bt_hci_cmd_send(BT_HCI_OP_LE_CONN_PARAM_REQ_REPLY, buf); +} + +static void le_conn_param_req(struct net_buf *buf) +{ + struct bt_hci_evt_le_conn_param_req *evt = (void *)buf->data; + struct bt_le_conn_param param; + struct bt_conn *conn; + uint16_t handle; + + handle = sys_le16_to_cpu(evt->handle); + param.interval_min = sys_le16_to_cpu(evt->interval_min); + param.interval_max = sys_le16_to_cpu(evt->interval_max); + param.latency = sys_le16_to_cpu(evt->latency); + param.timeout = sys_le16_to_cpu(evt->timeout); + + conn = bt_conn_lookup_handle(handle, BT_CONN_TYPE_LE); + if (!conn) + { + LOG_ERR("Unable to lookup conn for handle %u", handle); + le_conn_param_neg_reply(handle, BT_HCI_ERR_UNKNOWN_CONN_ID); + return; + } + + if (!bt_conn_le_param_req(conn, ¶m)) + { + le_conn_param_neg_reply(handle, BT_HCI_ERR_INVALID_LL_PARAM); + } + else + { + le_conn_param_req_reply(handle, ¶m); + } + + bt_conn_unref(conn); +} + +static void le_conn_update_complete(struct net_buf *buf) +{ + struct bt_hci_evt_le_conn_update_complete *evt = (void *)buf->data; + struct bt_conn *conn; + uint16_t handle; + + handle = sys_le16_to_cpu(evt->handle); + + LOG_DBG("status 0x%02x %s, handle %u", evt->status, + bt_hci_err_to_str(evt->status), handle); + + conn = bt_conn_lookup_handle(handle, BT_CONN_TYPE_LE); + if (!conn) + { + LOG_ERR("Unable to lookup conn for handle %u", handle); + return; + } + + if (evt->status == BT_HCI_ERR_UNSUPP_REMOTE_FEATURE && + conn->role == BT_HCI_ROLE_PERIPHERAL && + !atomic_test_and_set_bit(conn->flags, BT_CONN_PERIPHERAL_PARAM_L2CAP)) + { + /* CPR not supported, let's try L2CAP CPUP instead */ + + struct bt_le_conn_param param; + + param.interval_min = conn->le.interval_min; + param.interval_max = conn->le.interval_max; + param.latency = conn->le.pending_latency; + param.timeout = conn->le.pending_timeout; + + bt_l2cap_update_conn_param(conn, ¶m); + } + else + { + /* Only application-initiated updates that finally failed (i.e. not + * superseded by a host-initiated retry) are reported as rejected. + */ + + bool notify_rejected = false; + + if (evt->status == BT_HCI_ERR_SUCCESS) + { + conn->le.interval_us = + sys_le16_to_cpu(evt->interval) * + BT_HCI_LE_INTERVAL_UNIT_US; + conn->le.latency = sys_le16_to_cpu(evt->latency); + conn->le.timeout = sys_le16_to_cpu(evt->supv_timeout); + + if (!IS_ENABLED(CONFIG_BT_CONN_PARAM_ANY)) + { + if (!IN_RANGE(conn->le.interval_us / + BT_HCI_LE_INTERVAL_UNIT_US, + BT_HCI_LE_INTERVAL_MIN, BT_HCI_LE_INTERVAL_MAX)) + { + LOG_WRN("interval exceeds the valid range %u us", + conn->le.interval_us); + } + + if (conn->le.latency > BT_HCI_LE_PERIPHERAL_LATENCY_MAX) + { + LOG_WRN("latency exceeds the valid range 0x%04x", + conn->le.latency); + } + + if (!IN_RANGE(conn->le.timeout, + BT_HCI_LE_SUPERVISION_TIMEOUT_MIN, + BT_HCI_LE_SUPERVISION_TIMEOUT_MAX)) + { + LOG_WRN("supv_timeout exceeds the valid range 0x%04x", + conn->le.timeout); + } + } + +#if defined(CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS) + atomic_clear_bit(conn->flags, + BT_CONN_PERIPHERAL_PARAM_AUTO_UPDATE); + } + else if (atomic_test_bit(conn->flags, + BT_CONN_PERIPHERAL_PARAM_AUTO_UPDATE) && + evt->status == BT_HCI_ERR_UNSUPP_LL_PARAM_VAL && + conn->le.conn_param_retry_countdown) + { + conn->le.conn_param_retry_countdown--; + bt_work_schedule(&conn->deferred_work, + MSEC2TICK(CONFIG_BT_CONN_PARAM_RETRY_TIMEOUT)); + } + else + { + if (IS_ENABLED(CONFIG_BT_USER_CONN_PARAM_REJECTED)) + { + /* A host-initiated (auto) update is only reported as a + * rejection while the AUTO_UPDATE flag is still set. It is + * cleared right after, so capture the decision now. + */ + + notify_rejected = !atomic_test_bit(conn->flags, + BT_CONN_PERIPHERAL_PARAM_AUTO_UPDATE); + } + + atomic_clear_bit(conn->flags, + BT_CONN_PERIPHERAL_PARAM_AUTO_UPDATE); +#endif /* CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS */ + } + + if (IS_ENABLED(CONFIG_BT_USER_CONN_PARAM_REJECTED) && + !IS_ENABLED(CONFIG_BT_GAP_AUTO_UPDATE_CONN_PARAMS) && + evt->status != BT_HCI_ERR_SUCCESS) + { + notify_rejected = true; + } + + if (evt->status == BT_HCI_ERR_SUCCESS) + { + bt_conn_notify_le_param_updated(conn); + } + else if (IS_ENABLED(CONFIG_BT_USER_CONN_PARAM_REJECTED) && + notify_rejected) + { + LOG_DBG("LE conn param update handle %u status 0x%02x %s", handle, + evt->status, + bt_hci_err_to_str(evt->status)); + bt_conn_notify_le_param_rejected(conn, evt->status); + } + else + { + /* No action required for failed update. */ + } + } + + bt_conn_unref(conn); +} + +#if defined(CONFIG_BT_HCI_ACL_FLOW_CONTROL) +static int set_flow_control(void) +{ + struct bt_hci_cp_host_buffer_size *hbs; + struct net_buf *buf; + int err; + + if (drv_quirk_no_flow_control()) + { + LOG_WRN("Controller to host flow control disabled by quirk"); + return 0; + } + + /* Check if host flow control is actually supported */ + + if (!BT_CMD_TEST(bt_dev.supported_commands, 10, 5)) + { + LOG_WRN("Controller to host flow control not supported"); + return 0; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + hbs = net_buf_add(buf, sizeof(*hbs)); + (void)memset(hbs, 0, sizeof(*hbs)); + hbs->acl_mtu = sys_cpu_to_le16(CONFIG_BT_BUF_ACL_RX_SIZE); + hbs->acl_pkts = sys_cpu_to_le16(BT_BUF_HCI_ACL_RX_COUNT); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_HOST_BUFFER_SIZE, buf, NULL); + if (err) + { + return err; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + net_buf_add_u8(buf, BT_HCI_CTL_TO_HOST_FLOW_ENABLE); + return bt_hci_cmd_send_sync(BT_HCI_OP_SET_CTL_TO_HOST_FLOW, buf, NULL); +} +#endif /* CONFIG_BT_HCI_ACL_FLOW_CONTROL */ + +static void unpair(uint8_t id, const bt_addr_le_t *addr) +{ + struct bt_keys *keys = NULL; + struct bt_conn *conn = bt_conn_lookup_addr_le(id, addr); + bool was_bonded = false; + + if (conn) + { + /* Clear the conn->le.keys pointer since we'll invalidate it, + * and don't want any subsequent code (like disconnected callbacks) + * accessing it. + */ + + if (bt_conn_is_le(conn)) + { + keys = conn->le.keys; + conn->le.keys = NULL; + } + + bt_conn_disconnect(conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + bt_conn_unref(conn); + } + + if (IS_ENABLED(CONFIG_BT_SMP)) + { + if (!keys) + { + keys = bt_keys_find_addr(id, addr); + } + + if (keys) + { + /* A bond existed if any keys were present. */ + + if (keys->keys != 0) + { + was_bonded = true; + } + + bt_keys_clear(keys); + } + } + + bt_gatt_clear(id, addr); + + if (IS_ENABLED(CONFIG_BT_SMP) && was_bonded) + { + struct bt_conn_auth_info_cb *listener; + struct bt_conn_auth_info_cb *next; + + list_for_every_container_safe(&bt_auth_info_cbs, listener, next, node) + { + if (listener->bond_deleted) + { + listener->bond_deleted(id, addr); + } + } + } +} + +static void unpair_remote(const struct bt_bond_info *info, void *data) +{ + uint8_t *id = (uint8_t *)data; + + unpair(*id, &info->addr); +} + +int bt_unpair(uint8_t id, const bt_addr_le_t *addr) +{ + if (id >= CONFIG_BT_ID_MAX) + { + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_SMP)) + { + if (!addr || bt_addr_le_eq(addr, BT_ADDR_LE_ANY)) + { + bt_foreach_bond(id, unpair_remote, &id); + } + else + { + unpair(id, addr); + } + } + else + { + if (addr == NULL) + { + LOG_DBG("addr is NULL"); + return -EINVAL; + } + + unpair(id, addr); + } + + return 0; +} + +#endif /* CONFIG_BT_CONN */ + +#if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_CLASSIC) +enum bt_security_err bt_security_err_get(uint8_t hci_err) +{ + switch (hci_err) + { + case BT_HCI_ERR_SUCCESS: + return BT_SECURITY_ERR_SUCCESS; + case BT_HCI_ERR_AUTH_FAIL: + return BT_SECURITY_ERR_AUTH_FAIL; + case BT_HCI_ERR_PIN_OR_KEY_MISSING: + return BT_SECURITY_ERR_PIN_OR_KEY_MISSING; + case BT_HCI_ERR_PAIRING_NOT_SUPPORTED: + return BT_SECURITY_ERR_PAIR_NOT_SUPPORTED; + case BT_HCI_ERR_PAIRING_NOT_ALLOWED: + return BT_SECURITY_ERR_PAIR_NOT_ALLOWED; + case BT_HCI_ERR_INVALID_PARAM: + return BT_SECURITY_ERR_INVALID_PARAM; + default: + return BT_SECURITY_ERR_UNSPECIFIED; + } +} +#endif /* defined(CONFIG_BT_SMP) || defined(CONFIG_BT_CLASSIC) */ + +#if defined(CONFIG_BT_SMP) +static bool update_sec_level(struct bt_conn *conn) +{ + if (conn->le.keys && (conn->le.keys->flags & BT_KEYS_AUTHENTICATED)) + { + if (conn->le.keys->flags & BT_KEYS_SC && + conn->le.keys->enc_size == BT_SMP_MAX_ENC_KEY_SIZE) + { + conn->sec_level = BT_SECURITY_L4; + } + else + { + conn->sec_level = BT_SECURITY_L3; + } + } + else + { + conn->sec_level = BT_SECURITY_L2; + } + + return !(conn->required_sec_level > conn->sec_level); +} +#endif /* CONFIG_BT_SMP */ + +#if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_CLASSIC) +static void hci_encrypt_change(struct net_buf *buf) +{ + struct bt_hci_evt_encrypt_change *evt = (void *)buf->data; + uint16_t handle = sys_le16_to_cpu(evt->handle); + uint8_t status = evt->status; + struct bt_conn *conn; + + LOG_DBG("status 0x%02x %s handle %u encrypt 0x%02x", evt->status, + bt_hci_err_to_str(evt->status), + handle, evt->encrypt); + + conn = bt_conn_lookup_handle(handle, BT_CONN_TYPE_ALL); + if (!conn) + { + LOG_ERR("Unable to look up conn with handle %u", handle); + return; + } + + if (status) + { + bt_conn_security_changed(conn, status, bt_security_err_get(status)); + bt_conn_unref(conn); + return; + } + + if (conn->encrypt == evt->encrypt) + { + LOG_WRN("No change to encryption state (encrypt " + "0x%02x)", evt->encrypt); + bt_conn_unref(conn); + return; + } + + conn->encrypt = evt->encrypt; + +#if defined(CONFIG_BT_SMP) + if (bt_conn_is_le(conn)) + { + /* we update keys properties only on successful encryption to + * avoid losing valid keys if encryption was not successful. + * + * Update keys with last pairing info for proper sec level update. This + * is done only for LE transport, for BR/EDR keys are updated on HCI + * 'Link Key Notification Event' + */ + + if (conn->encrypt) + { + bt_smp_update_keys(conn); + } + + if (!update_sec_level(conn)) + { + status = BT_HCI_ERR_AUTH_FAIL; + } + } +#endif /* CONFIG_BT_SMP */ +#if defined(CONFIG_BT_CLASSIC) + + if (bt_conn_is_br(conn)) + { + if (!bt_br_update_sec_level(conn)) + { + bt_conn_unref(conn); + return; + } + + if (IS_ENABLED(CONFIG_BT_SMP)) + { + /* Start SMP over BR/EDR if we are pairing and are + * central on the link + */ + + if (atomic_test_bit(conn->flags, BT_CONN_BR_PAIRED) && + conn->role == BT_CONN_ROLE_CENTRAL) + { + bt_smp_br_send_pairing_req(conn); + } + } + } +#endif /* CONFIG_BT_CLASSIC */ + + bt_conn_security_changed(conn, status, bt_security_err_get(status)); + + if (status) + { + LOG_ERR("Failed to set required security level"); + bt_conn_disconnect(conn, status); + } + + bt_conn_unref(conn); +} + +static void hci_encrypt_key_refresh_complete(struct net_buf *buf) +{ + struct bt_hci_evt_encrypt_key_refresh_complete *evt = (void *)buf->data; + uint8_t status = evt->status; + struct bt_conn *conn; + uint16_t handle; + + handle = sys_le16_to_cpu(evt->handle); + + LOG_DBG("status 0x%02x %s handle %u", evt->status, + bt_hci_err_to_str(evt->status), handle); + + conn = bt_conn_lookup_handle(handle, BT_CONN_TYPE_ALL); + if (!conn) + { + LOG_ERR("Unable to look up conn with handle %u", handle); + return; + } + + if (status) + { + bt_conn_security_changed(conn, status, bt_security_err_get(status)); + bt_conn_unref(conn); + return; + } + + /* Update keys with last pairing info for proper sec level update. + * This is done only for LE transport. For BR/EDR transport keys are + * updated on HCI 'Link Key Notification Event', therefore update here + * only security level based on available keys and encryption state. + */ +#if defined(CONFIG_BT_SMP) + if (bt_conn_is_le(conn)) + { + bt_smp_update_keys(conn); + + if (!update_sec_level(conn)) + { + status = BT_HCI_ERR_AUTH_FAIL; + } + } +#endif /* CONFIG_BT_SMP */ +#if defined(CONFIG_BT_CLASSIC) + + if (bt_conn_is_br(conn)) + { + if (!bt_br_update_sec_level(conn)) + { + bt_conn_unref(conn); + return; + } + + if (IS_ENABLED(CONFIG_BT_SMP)) + { + /* Start SMP over BR/EDR if we are pairing and are + * central on the link + */ + + if (atomic_test_bit(conn->flags, BT_CONN_BR_PAIRED) && + conn->role == BT_CONN_ROLE_CENTRAL) + { + bt_smp_br_send_pairing_req(conn); + } + } + } +#endif /* CONFIG_BT_CLASSIC */ + + bt_conn_security_changed(conn, status, bt_security_err_get(status)); + if (status) + { + LOG_ERR("Failed to set required security level"); + bt_conn_disconnect(conn, status); + } + + bt_conn_unref(conn); +} +#endif /* CONFIG_BT_SMP || CONFIG_BT_CLASSIC */ + +#if defined(CONFIG_BT_REMOTE_VERSION) +static void bt_hci_evt_read_remote_version_complete(struct net_buf *buf) +{ + struct bt_hci_evt_remote_version_info *evt; + struct bt_conn *conn; + uint16_t handle; + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + handle = sys_le16_to_cpu(evt->handle); + conn = bt_conn_lookup_handle(handle, BT_CONN_TYPE_ALL); + if (!conn) + { + LOG_ERR("No connection for handle %u", handle); + return; + } + + if (!evt->status) + { + conn->rv.version = evt->version; + conn->rv.manufacturer = sys_le16_to_cpu(evt->manufacturer); + conn->rv.subversion = sys_le16_to_cpu(evt->subversion); + } + + atomic_set_bit(conn->flags, BT_CONN_AUTO_VERSION_INFO); + + if (IS_ENABLED(CONFIG_BT_REMOTE_INFO) && + atomic_test_bit(conn->flags, BT_CONN_LE_FEATURES_EXCHANGED)) + { + /* Remote features is already present */ + + bt_conn_notify_remote_info(conn); + } + + bt_conn_unref(conn); +} +#endif /* CONFIG_BT_REMOTE_VERSION */ + +static void hci_hardware_error(struct net_buf *buf) +{ + struct bt_hci_evt_hardware_error *evt; + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + + LOG_ERR("Hardware error, hardware code: %d", evt->hardware_code); +} + +#if defined(CONFIG_BT_SMP) +static void le_ltk_neg_reply(uint16_t handle) +{ + struct bt_hci_cp_le_ltk_req_neg_reply *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + LOG_ERR("Out of command buffers"); + + return; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(handle); + + bt_hci_cmd_send(BT_HCI_OP_LE_LTK_REQ_NEG_REPLY, buf); +} + +static void le_ltk_reply(uint16_t handle, uint8_t *ltk) +{ + struct bt_hci_cp_le_ltk_req_reply *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + LOG_ERR("Out of command buffers"); + return; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(handle); + memcpy(cp->ltk, ltk, sizeof(cp->ltk)); + + bt_hci_cmd_send(BT_HCI_OP_LE_LTK_REQ_REPLY, buf); +} + +static void le_ltk_request(struct net_buf *buf) +{ + struct bt_hci_evt_le_ltk_request *evt = (void *)buf->data; + struct bt_conn *conn; + uint16_t handle; + uint8_t ltk[16]; + uint64_t rand_value; + uint16_t ediv; + + handle = sys_le16_to_cpu(evt->handle); + + LOG_DBG("handle %u", handle); + + conn = bt_conn_lookup_handle(handle, BT_CONN_TYPE_LE); + if (!conn) + { + LOG_ERR("Unable to lookup conn for handle %u", handle); + return; + } + + (void)memcpy(&rand_value, evt->rand, sizeof(rand_value)); + (void)memcpy(&ediv, evt->ediv, sizeof(ediv)); + + if (bt_smp_request_ltk(conn, rand_value, ediv, ltk)) + { + le_ltk_reply(handle, ltk); + } + else + { + le_ltk_neg_reply(handle); + } + + bt_conn_unref(conn); +} +#endif /* CONFIG_BT_SMP */ + +static void hci_reset_complete(void) +{ + atomic_t flags; + + if (IS_ENABLED(CONFIG_BT_OBSERVER)) + { + bt_scan_reset(); + } + +#if defined(CONFIG_BT_CLASSIC) + bt_br_discovery_reset(); +#endif /* CONFIG_BT_CLASSIC */ + + flags = (atomic_read(bt_dev.flags) & BT_DEV_PERSISTENT_FLAGS); + atomic_set(bt_dev.flags, flags); +} + +static void hci_cmd_done(uint16_t opcode, + uint8_t status, struct net_buf *evt_buf) +{ + /* Original command buffer. */ + + struct net_buf *buf = NULL; + + LOG_DBG("opcode 0x%04x status 0x%02x %s buf %p", opcode, status, + bt_hci_err_to_str(status), + evt_buf); + + /* Unsolicited cmd complete. This does not complete a command. + * The controller can send these for effect of the `ncmd` field. + */ + + if (opcode == 0) + { + goto exit; + } + + /* Take the original command buffer reference. */ + + buf = net_buf_take(&bt_dev.sent_cmd); + + if (!buf) + { + LOG_ERR("No command sent for cmd complete 0x%04x", opcode); + goto exit; + } + + if (cmd(buf)->opcode != opcode) + { + LOG_ERR("OpCode 0x%04x completed instead of expected 0x%04x", opcode, + cmd(buf)->opcode); + buf = atomic_ptr_xchg((void **)&bt_dev.sent_cmd, buf); + DEBUGASSERT(!buf); + goto exit; + } + + /* Response data is to be delivered in the original command + * buffer. + */ + + if (evt_buf != buf) + { + net_buf_reset(buf); + net_buf_add_mem(buf, evt_buf->data, evt_buf->len); + } + + if (cmd(buf)->state && !status) + { + struct bt_hci_cmd_state_set *update = cmd(buf)->state; + + atomic_set_bit_to(update->target, update->bit, update->val); + } + + /* If the command was synchronous wake up bt_hci_cmd_send_sync() */ + + if (cmd(buf)->sync) + { + LOG_DBG("sync cmd released"); + cmd(buf)->status = status; + nxsem_post(cmd(buf)->sync); + } + +exit: + if (buf) + { + net_buf_unref(buf); + } +} + +static void hci_cmd_complete(struct net_buf *buf) +{ + struct bt_hci_pkt_cmd_rsp rsp; + int err; + + err = bt_hci_pkt_pull_cmd_complete(&buf->b, &rsp); + if (err == -ENODATA) + { + /* The return parameters lack the status they start with, so + * rsp carries BT_HCI_ERR_UNSPECIFIED: the command fails below instead + * of leaving its sender waiting for the command timeout, and the + * command flow control keeps going. + */ + + LOG_WRN("Command Complete for opcode 0x%04x without status", + rsp.opcode); + } + else if (err != 0) + { + LOG_WRN("Malformed Command Complete event (err %d)", err); + return; + } + + LOG_DBG("opcode 0x%04x", rsp.opcode); + + /* HOST_NUM_COMPLETED_PACKETS should not generate a response under normal + * operation. The generation of this command ignores `ncmd_sem`, so should + * not be given here. + */ + + if (rsp.opcode == BT_HCI_OP_HOST_NUM_COMPLETED_PACKETS) + { + LOG_WRN("Unexpected HOST_NUM_COMPLETED_PACKETS, status 0x%02x %s", + rsp.status, + bt_hci_err_to_str(rsp.status)); + return; + } + + hci_cmd_done(rsp.opcode, rsp.status, buf); + + /* Allow next command to be sent */ + + if (rsp.ncmd != 0) + { + nxsem_post(&bt_dev.ncmd_sem); + bt_tx_irq_raise(); + } +} + +static void hci_cmd_status(struct net_buf *buf) +{ + struct bt_hci_pkt_cmd_rsp rsp; + int err; + + err = bt_hci_pkt_pull_cmd_status(&buf->b, &rsp); + if (err != 0) + { + /* Not reachable: the event dispatch guarantees the parameters + * a Command Status event consists of, so there is nothing to recover + * here. + */ + + LOG_WRN("Malformed Command Status event (err %d)", err); + return; + } + + LOG_DBG("opcode 0x%04x", rsp.opcode); + + hci_cmd_done(rsp.opcode, rsp.status, buf); + + /* Allow next command to be sent */ + + if (rsp.ncmd != 0) + { + nxsem_post(&bt_dev.ncmd_sem); + bt_tx_irq_raise(); + } +} + +int bt_hci_get_conn_handle(const struct bt_conn *conn, uint16_t *conn_handle) +{ + if (conn->state != BT_CONN_CONNECTED) + { + return -ENOTCONN; + } + + *conn_handle = conn->handle; + return 0; +} + +#if defined(CONFIG_BT_EXT_ADV) +int bt_hci_get_adv_handle(const struct bt_le_ext_adv *adv, + uint8_t *adv_handle) +{ + if (!atomic_test_bit(adv->flags, BT_ADV_CREATED)) + { + return -EINVAL; + } + + *adv_handle = adv->handle; + return 0; +} +#endif /* CONFIG_BT_EXT_ADV */ + +#if defined(CONFIG_BT_PER_ADV_SYNC) +int bt_hci_get_adv_sync_handle(const struct bt_le_per_adv_sync *sync, + uint16_t *sync_handle) +{ + if (!atomic_test_bit(sync->flags, BT_PER_ADV_SYNC_CREATED)) + { + return -EINVAL; + } + + *sync_handle = sync->handle; + + return 0; +} +#endif + +#if defined(CONFIG_BT_HCI_VS_EVT_USER) +int bt_hci_register_vnd_evt_cb(bt_hci_vnd_evt_cb_t cb) +{ + hci_vnd_evt_cb = cb; + return 0; +} +#endif /* CONFIG_BT_HCI_VS_EVT_USER */ + +#if defined(CONFIG_BT_TRANSMIT_POWER_CONTROL) +void bt_hci_le_transmit_power_report(struct net_buf *buf) +{ + struct bt_hci_evt_le_transmit_power_report *evt; + struct bt_conn_le_tx_power_report report; + struct bt_conn *conn; + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + conn = bt_conn_lookup_handle(sys_le16_to_cpu(evt->handle), + BT_CONN_TYPE_LE); + if (!conn) + { + LOG_ERR("Unknown conn handle 0x%04X for transmit power report", + sys_le16_to_cpu(evt->handle)); + return; + } + + report.reason = evt->reason; + report.phy = evt->phy; + report.tx_power_level = evt->tx_power_level; + report.tx_power_level_flag = evt->tx_power_level_flag; + report.delta = evt->delta; + + bt_conn_notify_tx_power_report(conn, report); + + bt_conn_unref(conn); +} +#endif /* CONFIG_BT_TRANSMIT_POWER_CONTROL */ + +#if defined(CONFIG_BT_PATH_LOSS_MONITORING) +void bt_hci_le_path_loss_threshold_event(struct net_buf *buf) +{ + struct bt_hci_evt_le_path_loss_threshold *evt; + struct bt_conn_le_path_loss_threshold_report report; + struct bt_conn *conn; + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + + if (evt->zone_entered > BT_CONN_LE_PATH_LOSS_ZONE_ENTERED_HIGH) + { + LOG_ERR("Invalid zone %u in bt_hci_evt_le_path_loss_threshold", + evt->zone_entered); + return; + } + + conn = bt_conn_lookup_handle(sys_le16_to_cpu(evt->handle), + BT_CONN_TYPE_LE); + if (!conn) + { + LOG_ERR("Unknown conn handle 0x%04X for path loss threshold report", + sys_le16_to_cpu(evt->handle)); + return; + } + + if (evt->current_path_loss == BT_HCI_LE_PATH_LOSS_UNAVAILABLE) + { + report.zone = BT_CONN_LE_PATH_LOSS_ZONE_UNAVAILABLE; + report.path_loss = BT_HCI_LE_PATH_LOSS_UNAVAILABLE; + } + else + { + report.zone = evt->zone_entered; + report.path_loss = evt->current_path_loss; + } + + bt_conn_notify_path_loss_threshold_report(conn, report); + + bt_conn_unref(conn); +} +#endif /* CONFIG_BT_PATH_LOSS_MONITORING */ + +#if defined(CONFIG_BT_SUBRATING) +void bt_hci_le_subrate_change_event(struct net_buf *buf) +{ + struct bt_hci_evt_le_subrate_change *evt; + struct bt_conn_le_subrate_changed params; + struct bt_conn *conn; + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + + conn = bt_conn_lookup_handle(sys_le16_to_cpu(evt->handle), + BT_CONN_TYPE_LE); + if (!conn) + { + LOG_ERR("Unknown conn handle 0x%04X for subrating event", + sys_le16_to_cpu(evt->handle)); + return; + } + + if (evt->status == BT_HCI_ERR_SUCCESS) + { + conn->le.subrate.factor = sys_le16_to_cpu(evt->subrate_factor); + conn->le.subrate.continuation_number = + sys_le16_to_cpu(evt->continuation_number); + conn->le.latency = sys_le16_to_cpu(evt->peripheral_latency); + conn->le.timeout = sys_le16_to_cpu(evt->supervision_timeout); + + if (!IS_ENABLED(CONFIG_BT_CONN_PARAM_ANY)) + { + if (!IN_RANGE(conn->le.subrate.factor, + BT_HCI_LE_SUBRATE_FACTOR_MIN, + BT_HCI_LE_SUBRATE_FACTOR_MAX)) + { + LOG_WRN("subrate_factor exceeds the valid range %d", + conn->le.subrate.factor); + } + + if (conn->le.latency > BT_HCI_LE_PERIPHERAL_LATENCY_MAX) + { + LOG_WRN("peripheral_latency exceeds the valid range 0x%04x", + conn->le.latency); + } + + if (conn->le.subrate.continuation_number > + BT_HCI_LE_CONTINUATION_NUM_MAX) + { + LOG_WRN("continuation_number exceeds the valid range %d", + conn->le.subrate.continuation_number); + } + + if (!IN_RANGE(conn->le.timeout, BT_HCI_LE_SUPERVISION_TIMEOUT_MIN, + BT_HCI_LE_SUPERVISION_TIMEOUT_MAX)) + { + LOG_WRN("supervision_timeout exceeds the valid range 0x%04x", + conn->le.timeout); + } + } + } + + params.status = evt->status; + params.factor = conn->le.subrate.factor; + params.continuation_number = conn->le.subrate.continuation_number; + params.peripheral_latency = conn->le.latency; + params.supervision_timeout = conn->le.timeout; + + bt_conn_notify_subrate_change(conn, params); + + bt_conn_unref(conn); +} +#endif /* CONFIG_BT_SUBRATING */ + +#if defined(CONFIG_BT_SHORTER_CONNECTION_INTERVALS) +void bt_hci_le_conn_rate_change_event(struct net_buf *buf) +{ + struct bt_hci_evt_le_conn_rate_change *evt; + struct bt_conn_le_conn_rate_changed params; + struct bt_conn *conn; + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + + conn = bt_conn_lookup_handle(sys_le16_to_cpu(evt->handle), + BT_CONN_TYPE_LE); + if (conn == NULL) + { + LOG_ERR("Unknown conn handle 0x%04X for connection rate event", + sys_le16_to_cpu(evt->handle)); + return; + } + + if (evt->status == BT_HCI_ERR_SUCCESS) + { + conn->le.interval_us = + BT_CONN_SCI_INTERVAL_TO_US( + sys_le16_to_cpu(evt->conn_interval)); + conn->le.subrate.factor = sys_le16_to_cpu(evt->subrate_factor); + conn->le.subrate.continuation_number = + sys_le16_to_cpu(evt->continuation_number); + conn->le.latency = sys_le16_to_cpu(evt->peripheral_latency); + conn->le.timeout = sys_le16_to_cpu(evt->supervision_timeout); + + if (!IS_ENABLED(CONFIG_BT_CONN_PARAM_ANY)) + { + if (!IN_RANGE(conn->le.interval_us / + BT_HCI_LE_SCI_INTERVAL_UNIT_US, + BT_HCI_LE_SCI_INTERVAL_MIN_125US, + BT_HCI_LE_SCI_INTERVAL_MAX_125US)) + { + LOG_WRN("interval_us exceeds the valid range %u us", + conn->le.interval_us); + } + + if (!IN_RANGE(conn->le.subrate.factor, + BT_HCI_LE_SUBRATE_FACTOR_MIN, + BT_HCI_LE_SUBRATE_FACTOR_MAX)) + { + LOG_WRN("subrate_factor exceeds the valid range %d", + conn->le.subrate.factor); + } + + if (conn->le.latency > BT_HCI_LE_PERIPHERAL_LATENCY_MAX) + { + LOG_WRN("peripheral_latency exceeds the valid range 0x%04x", + conn->le.latency); + } + + if (conn->le.subrate.continuation_number > + BT_HCI_LE_CONTINUATION_NUM_MAX) + { + LOG_WRN("continuation_number exceeds the valid range %d", + conn->le.subrate.continuation_number); + } + + if (!IN_RANGE(conn->le.timeout, BT_HCI_LE_SUPERVISION_TIMEOUT_MIN, + BT_HCI_LE_SUPERVISION_TIMEOUT_MAX)) + { + LOG_WRN("supervision_timeout exceeds the valid range 0x%04x", + conn->le.timeout); + } + } + + params.interval_us = conn->le.interval_us; + params.subrate_factor = conn->le.subrate.factor; + params.continuation_number = conn->le.subrate.continuation_number; + params.peripheral_latency = conn->le.latency; + params.supervision_timeout_10ms = conn->le.timeout; + + bt_conn_notify_conn_rate_change(conn, evt->status, ¶ms); + } + else + { + bt_conn_notify_conn_rate_change(conn, evt->status, NULL); + } + + bt_conn_unref(conn); +} +#endif /* CONFIG_BT_SHORTER_CONNECTION_INTERVALS */ + +static const struct event_handler vs_events[] = +{ +#if defined(CONFIG_BT_DF_VS_CL_IQ_REPORT_16_BITS_IQ_SAMPLES) + EVENT_HANDLER(BT_HCI_EVT_VS_LE_CONNECTIONLESS_IQ_REPORT, + bt_hci_le_vs_df_connectionless_iq_report, + sizeof( + struct bt_hci_evt_vs_le_connectionless_iq_report)), +#endif /* CONFIG_BT_DF_VS_CL_IQ_REPORT_16_BITS_IQ_SAMPLES */ +#if defined(CONFIG_BT_DF_VS_CONN_IQ_REPORT_16_BITS_IQ_SAMPLES) + EVENT_HANDLER(BT_HCI_EVT_VS_LE_CONNECTION_IQ_REPORT, + bt_hci_le_vs_df_connection_iq_report, + + sizeof(struct bt_hci_evt_vs_le_connection_iq_report)), +#endif /* CONFIG_BT_DF_VS_CONN_IQ_REPORT_16_BITS_IQ_SAMPLES */ +}; + +static void hci_vendor_event(struct net_buf *buf) +{ + bool handled = false; + +#if defined(CONFIG_BT_HCI_VS_EVT_USER) + if (hci_vnd_evt_cb) + { + struct net_buf_simple_state state; + + net_buf_simple_save(&buf->b, &state); + + handled = hci_vnd_evt_cb(&buf->b); + + net_buf_simple_restore(&buf->b, &state); + } +#endif /* CONFIG_BT_HCI_VS_EVT_USER */ + + if (IS_ENABLED(CONFIG_BT_HCI_VS) && !handled) + { + struct bt_hci_evt_vs *evt; + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + + LOG_DBG("subevent 0x%02x", evt->subevent); + + handle_vs_event(evt->subevent, buf, vs_events, ARRAY_SIZE(vs_events)); + } +} + +static const struct event_handler meta_events[] = +{ +#if defined(CONFIG_BT_OBSERVER) + EVENT_HANDLER(BT_HCI_EVT_LE_ADVERTISING_REPORT, bt_hci_le_adv_report, + sizeof(struct bt_hci_evt_le_advertising_report)), +#if defined(CONFIG_BT_SCAN_EXT_FILTER_POLICY) + EVENT_HANDLER(BT_HCI_EVT_LE_DIRECT_ADV_REPORT, + bt_hci_le_direct_adv_report, + + sizeof(struct bt_hci_evt_le_direct_adv_report)), +#endif /* CONFIG_BT_SCAN_EXT_FILTER_POLICY */ +#endif /* CONFIG_BT_OBSERVER */ +#if defined(CONFIG_BT_CONN) + EVENT_HANDLER(BT_HCI_EVT_LE_CONN_COMPLETE, le_legacy_conn_complete, + sizeof(struct bt_hci_evt_le_conn_complete)), + EVENT_HANDLER(BT_HCI_EVT_LE_ENH_CONN_COMPLETE, le_enh_conn_complete, + sizeof(struct bt_hci_evt_le_enh_conn_complete)), + EVENT_HANDLER(BT_HCI_EVT_LE_CONN_UPDATE_COMPLETE, + le_conn_update_complete, + + sizeof(struct bt_hci_evt_le_conn_update_complete)), + EVENT_HANDLER(BT_HCI_EVT_LE_REMOTE_FEAT_COMPLETE, + le_remote_feat_complete, + + sizeof(struct bt_hci_evt_le_remote_feat_complete)), + EVENT_HANDLER(BT_HCI_EVT_LE_CONN_PARAM_REQ, le_conn_param_req, + sizeof(struct bt_hci_evt_le_conn_param_req)), +#if defined(CONFIG_BT_DATA_LEN_UPDATE) + EVENT_HANDLER(BT_HCI_EVT_LE_DATA_LEN_CHANGE, le_data_len_change, + sizeof(struct bt_hci_evt_le_data_len_change)), +#endif /* CONFIG_BT_DATA_LEN_UPDATE */ +#if defined(CONFIG_BT_PHY_UPDATE) + EVENT_HANDLER(BT_HCI_EVT_LE_PHY_UPDATE_COMPLETE, + le_phy_update_complete, + + sizeof(struct bt_hci_evt_le_phy_update_complete)), +#endif /* CONFIG_BT_PHY_UPDATE */ +#endif /* CONFIG_BT_CONN */ +#if defined(CONFIG_BT_SMP) + EVENT_HANDLER(BT_HCI_EVT_LE_LTK_REQUEST, le_ltk_request, + sizeof(struct bt_hci_evt_le_ltk_request)), +#endif /* CONFIG_BT_SMP */ +#if defined(CONFIG_BT_EXT_ADV) +#if defined(CONFIG_BT_BROADCASTER) + EVENT_HANDLER(BT_HCI_EVT_LE_ADV_SET_TERMINATED, + bt_hci_le_adv_set_terminated, + + sizeof(struct bt_hci_evt_le_adv_set_terminated)), + EVENT_HANDLER(BT_HCI_EVT_LE_SCAN_REQ_RECEIVED, + bt_hci_le_scan_req_received, + + sizeof(struct bt_hci_evt_le_scan_req_received)), +#endif +#if defined(CONFIG_BT_OBSERVER) + EVENT_HANDLER(BT_HCI_EVT_LE_SCAN_TIMEOUT, bt_hci_le_scan_timeout, 0), + EVENT_HANDLER(BT_HCI_EVT_LE_EXT_ADVERTISING_REPORT, + bt_hci_le_adv_ext_report, + + sizeof(struct bt_hci_evt_le_ext_advertising_report)), +#endif /* defined(CONFIG_BT_OBSERVER) */ +#if defined(CONFIG_BT_PER_ADV_SYNC) + EVENT_HANDLER(BT_HCI_EVT_LE_PER_ADV_SYNC_ESTABLISHED, + bt_hci_le_per_adv_sync_established, + + sizeof(struct bt_hci_evt_le_per_adv_sync_established)), + EVENT_HANDLER(BT_HCI_EVT_LE_PER_ADVERTISING_REPORT, + bt_hci_le_per_adv_report, + + sizeof(struct bt_hci_evt_le_per_advertising_report)), + EVENT_HANDLER(BT_HCI_EVT_LE_PER_ADV_SYNC_LOST, + bt_hci_le_per_adv_sync_lost, + + sizeof(struct bt_hci_evt_le_per_adv_sync_lost)), +#if defined(CONFIG_BT_PER_ADV_SYNC_TRANSFER_RECEIVER) + EVENT_HANDLER(BT_HCI_EVT_LE_PAST_RECEIVED, bt_hci_le_past_received, + sizeof(struct bt_hci_evt_le_past_received)), +#endif /* CONFIG_BT_PER_ADV_SYNC_TRANSFER_RECEIVER */ +#endif /* defined(CONFIG_BT_PER_ADV_SYNC) */ +#endif /* defined(CONFIG_BT_EXT_ADV) */ +#if defined(CONFIG_BT_ISO_UNICAST) + EVENT_HANDLER(BT_HCI_EVT_LE_CIS_ESTABLISHED, hci_le_cis_established, + sizeof(struct bt_hci_evt_le_cis_established)), + EVENT_HANDLER(BT_HCI_EVT_LE_CIS_ESTABLISHED_V2, + hci_le_cis_established_v2, + + sizeof(struct bt_hci_evt_le_cis_established_v2)), +#if defined(CONFIG_BT_ISO_PERIPHERAL) + EVENT_HANDLER(BT_HCI_EVT_LE_CIS_REQ, + hci_le_cis_req, + sizeof(struct bt_hci_evt_le_cis_req)), +#endif /* (CONFIG_BT_ISO_PERIPHERAL) */ +#endif /* (CONFIG_BT_ISO_UNICAST) */ +#if defined(CONFIG_BT_ISO_BROADCASTER) + EVENT_HANDLER(BT_HCI_EVT_LE_BIG_COMPLETE, hci_le_big_complete, + sizeof(struct bt_hci_evt_le_big_complete)), + EVENT_HANDLER(BT_HCI_EVT_LE_BIG_TERMINATE, hci_le_big_terminate, + sizeof(struct bt_hci_evt_le_big_terminate)), +#endif /* CONFIG_BT_ISO_BROADCASTER */ +#if defined(CONFIG_BT_ISO_SYNC_RECEIVER) + EVENT_HANDLER(BT_HCI_EVT_LE_BIG_SYNC_ESTABLISHED, + hci_le_big_sync_established, + + sizeof(struct bt_hci_evt_le_big_sync_established)), + EVENT_HANDLER(BT_HCI_EVT_LE_BIG_SYNC_LOST, hci_le_big_sync_lost, + sizeof(struct bt_hci_evt_le_big_sync_lost)), + EVENT_HANDLER(BT_HCI_EVT_LE_BIGINFO_ADV_REPORT, + bt_hci_le_biginfo_adv_report, + + sizeof(struct bt_hci_evt_le_biginfo_adv_report)), +#endif /* CONFIG_BT_ISO_SYNC_RECEIVER */ +#if defined(CONFIG_BT_DF_CONNECTIONLESS_CTE_RX) + EVENT_HANDLER(BT_HCI_EVT_LE_CONNECTIONLESS_IQ_REPORT, + bt_hci_le_df_connectionless_iq_report, + + sizeof(struct bt_hci_evt_le_connectionless_iq_report)), +#endif /* CONFIG_BT_DF_CONNECTIONLESS_CTE_RX */ +#if defined(CONFIG_BT_DF_CONNECTION_CTE_RX) + EVENT_HANDLER(BT_HCI_EVT_LE_CONNECTION_IQ_REPORT, + bt_hci_le_df_connection_iq_report, + + sizeof(struct bt_hci_evt_le_connection_iq_report)), +#endif /* CONFIG_BT_DF_CONNECTION_CTE_RX */ +#if defined(CONFIG_BT_DF_CONNECTION_CTE_REQ) + EVENT_HANDLER(BT_HCI_EVT_LE_CTE_REQUEST_FAILED, + bt_hci_le_df_cte_req_failed, + + sizeof(struct bt_hci_evt_le_cte_req_failed)), +#endif /* CONFIG_BT_DF_CONNECTION_CTE_REQ */ +#if defined(CONFIG_BT_TRANSMIT_POWER_CONTROL) + EVENT_HANDLER(BT_HCI_EVT_LE_TRANSMIT_POWER_REPORT, + bt_hci_le_transmit_power_report, + + sizeof(struct bt_hci_evt_le_transmit_power_report)), +#endif /* CONFIG_BT_TRANSMIT_POWER_CONTROL */ +#if defined(CONFIG_BT_PATH_LOSS_MONITORING) + EVENT_HANDLER(BT_HCI_EVT_LE_PATH_LOSS_THRESHOLD, + bt_hci_le_path_loss_threshold_event, + + sizeof(struct bt_hci_evt_le_path_loss_threshold)), +#endif /* CONFIG_BT_PATH_LOSS_MONITORING */ +#if defined(CONFIG_BT_SUBRATING) + EVENT_HANDLER(BT_HCI_EVT_LE_SUBRATE_CHANGE, + bt_hci_le_subrate_change_event, + + sizeof(struct bt_hci_evt_le_subrate_change)), +#endif /* CONFIG_BT_SUBRATING */ +#if defined(CONFIG_BT_SHORTER_CONNECTION_INTERVALS) + EVENT_HANDLER(BT_HCI_EVT_LE_CONN_RATE_CHANGE, + bt_hci_le_conn_rate_change_event, + + sizeof(struct bt_hci_evt_le_conn_rate_change)), +#endif /* CONFIG_BT_SHORTER_CONNECTION_INTERVALS */ +#if defined(CONFIG_BT_PER_ADV_SYNC_RSP) + EVENT_HANDLER(BT_HCI_EVT_LE_PER_ADVERTISING_REPORT_V2, + bt_hci_le_per_adv_report_v2, + + sizeof(struct bt_hci_evt_le_per_advertising_report_v2)), +#if defined(CONFIG_BT_PER_ADV_SYNC_TRANSFER_RECEIVER) + EVENT_HANDLER(BT_HCI_EVT_LE_PAST_RECEIVED_V2, + bt_hci_le_past_received_v2, + + sizeof(struct bt_hci_evt_le_past_received_v2)), +#endif /* CONFIG_BT_PER_ADV_SYNC_TRANSFER_RECEIVER */ + EVENT_HANDLER(BT_HCI_EVT_LE_PER_ADV_SYNC_ESTABLISHED_V2, + bt_hci_le_per_adv_sync_established_v2, + sizeof( + struct bt_hci_evt_le_per_adv_sync_established_v2)), +#endif /* CONFIG_BT_PER_ADV_SYNC_RSP */ +#if defined(CONFIG_BT_PER_ADV_RSP) + EVENT_HANDLER(BT_HCI_EVT_LE_PER_ADV_SUBEVENT_DATA_REQUEST, + bt_hci_le_per_adv_subevent_data_request, + sizeof( + struct bt_hci_evt_le_per_adv_subevent_data_request)), + EVENT_HANDLER(BT_HCI_EVT_LE_PER_ADV_RESPONSE_REPORT, + bt_hci_le_per_adv_response_report, + + sizeof(struct bt_hci_evt_le_per_adv_response_report)), +#endif /* CONFIG_BT_PER_ADV_RSP */ +#if defined(CONFIG_BT_CONN) +#if defined(CONFIG_BT_PER_ADV_RSP) || defined(CONFIG_BT_PER_ADV_SYNC_RSP) + EVENT_HANDLER(BT_HCI_EVT_LE_ENH_CONN_COMPLETE_V2, + le_enh_conn_complete_v2, + + sizeof(struct bt_hci_evt_le_enh_conn_complete_v2)), +#endif /* CONFIG_BT_PER_ADV_RSP || CONFIG_BT_PER_ADV_SYNC_RSP */ +#if defined(CONFIG_BT_LE_EXTENDED_FEAT_SET) + EVENT_HANDLER(BT_HCI_EVT_LE_READ_ALL_REMOTE_FEAT_COMPLETE, + le_read_all_remote_feat_complete, + + sizeof( + struct bt_hci_evt_le_read_all_remote_feat_complete)), +#endif /* CONFIG_BT_LE_EXTENDED_FEAT_SET */ +#if defined(CONFIG_BT_FRAME_SPACE_UPDATE) + EVENT_HANDLER(BT_HCI_EVT_LE_FRAME_SPACE_UPDATE_COMPLETE, + le_frame_space_update_complete, + + sizeof( + struct bt_hci_evt_le_frame_space_update_complete)), +#endif /* CONFIG_BT_FRAME_SPACE_UPDATE */ +#endif /* CONFIG_BT_CONN */ +#if defined(CONFIG_BT_CHANNEL_SOUNDING) + EVENT_HANDLER( + BT_HCI_EVT_LE_CS_READ_REMOTE_SUPPORTED_CAPABILITIES_COMPLETE, + bt_hci_le_cs_read_remote_supported_capabilities_complete, + sizeof( + struct bt_hci_evt_le_cs_read_remote_supported_capabilities_complete)), + EVENT_HANDLER( + BT_HCI_EVT_LE_CS_READ_REMOTE_SUPPORTED_CAPABILITIES_COMPLETE_V2, + bt_hci_le_cs_read_remote_supported_capabilities_complete_v2, + sizeof( + struct bt_hci_evt_le_cs_read_remote_supported_capabilities_complete_v2)), + EVENT_HANDLER(BT_HCI_EVT_LE_CS_READ_REMOTE_FAE_TABLE_COMPLETE, + bt_hci_le_cs_read_remote_fae_table_complete, + sizeof( + struct bt_hci_evt_le_cs_read_remote_fae_table_complete)), + EVENT_HANDLER(BT_HCI_EVT_LE_CS_CONFIG_COMPLETE, + bt_hci_le_cs_config_complete_event, + + sizeof(struct bt_hci_evt_le_cs_config_complete)), + EVENT_HANDLER(BT_HCI_EVT_LE_CS_SECURITY_ENABLE_COMPLETE, + bt_hci_le_cs_security_enable_complete, + sizeof( + struct bt_hci_evt_le_cs_security_enable_complete)), + EVENT_HANDLER(BT_HCI_EVT_LE_CS_PROCEDURE_ENABLE_COMPLETE, + bt_hci_le_cs_procedure_enable_complete, + sizeof( + struct bt_hci_evt_le_cs_procedure_enable_complete)), + EVENT_HANDLER(BT_HCI_EVT_LE_CS_SUBEVENT_RESULT, + bt_hci_le_cs_subevent_result, + + sizeof(struct bt_hci_evt_le_cs_subevent_result)), + EVENT_HANDLER(BT_HCI_EVT_LE_CS_SUBEVENT_RESULT_CONTINUE, + bt_hci_le_cs_subevent_result_continue, + sizeof( + struct bt_hci_evt_le_cs_subevent_result_continue)), +#if defined(CONFIG_BT_CHANNEL_SOUNDING_TEST) + EVENT_HANDLER(BT_HCI_EVT_LE_CS_TEST_END_COMPLETE, + bt_hci_le_cs_test_end_complete, + + sizeof(struct bt_hci_evt_le_cs_test_end_complete)), +#endif /* CONFIG_BT_CHANNEL_SOUNDING_TEST */ +#endif /* CONFIG_BT_CHANNEL_SOUNDING */ +}; + +static void hci_le_meta_event(struct net_buf *buf) +{ + struct bt_hci_evt_le_meta_event *evt; + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + + LOG_DBG("subevent 0x%02x", evt->subevent); + + handle_event(evt->subevent, buf, meta_events, ARRAY_SIZE(meta_events)); +} + +static const struct event_handler normal_events[] = +{ + EVENT_HANDLER(BT_HCI_EVT_VENDOR, + hci_vendor_event, + sizeof(struct bt_hci_evt_vs)), + EVENT_HANDLER(BT_HCI_EVT_LE_META_EVENT, hci_le_meta_event, + sizeof(struct bt_hci_evt_le_meta_event)), +#if defined(CONFIG_BT_CLASSIC) + EVENT_HANDLER(BT_HCI_EVT_CONN_REQUEST, bt_hci_conn_req, + sizeof(struct bt_hci_evt_conn_request)), + EVENT_HANDLER(BT_HCI_EVT_CONN_COMPLETE, bt_hci_conn_complete, + sizeof(struct bt_hci_evt_conn_complete)), + EVENT_HANDLER(BT_HCI_EVT_PIN_CODE_REQ, bt_hci_pin_code_req, + sizeof(struct bt_hci_evt_pin_code_req)), + EVENT_HANDLER(BT_HCI_EVT_LINK_KEY_NOTIFY, bt_hci_link_key_notify, + sizeof(struct bt_hci_evt_link_key_notify)), + EVENT_HANDLER(BT_HCI_EVT_LINK_KEY_REQ, bt_hci_link_key_req, + sizeof(struct bt_hci_evt_link_key_req)), + EVENT_HANDLER(BT_HCI_EVT_IO_CAPA_RESP, bt_hci_io_capa_resp, + sizeof(struct bt_hci_evt_io_capa_resp)), + EVENT_HANDLER(BT_HCI_EVT_IO_CAPA_REQ, bt_hci_io_capa_req, + sizeof(struct bt_hci_evt_io_capa_req)), + EVENT_HANDLER(BT_HCI_EVT_SSP_COMPLETE, bt_hci_ssp_complete, + sizeof(struct bt_hci_evt_ssp_complete)), + EVENT_HANDLER(BT_HCI_EVT_USER_CONFIRM_REQ, bt_hci_user_confirm_req, + sizeof(struct bt_hci_evt_user_confirm_req)), + EVENT_HANDLER(BT_HCI_EVT_USER_PASSKEY_NOTIFY, + bt_hci_user_passkey_notify, + + sizeof(struct bt_hci_evt_user_passkey_notify)), + EVENT_HANDLER(BT_HCI_EVT_USER_PASSKEY_REQ, bt_hci_user_passkey_req, + sizeof(struct bt_hci_evt_user_passkey_req)), + EVENT_HANDLER(BT_HCI_EVT_INQUIRY_COMPLETE, bt_hci_inquiry_complete, + sizeof(struct bt_hci_evt_inquiry_complete)), + EVENT_HANDLER(BT_HCI_EVT_INQUIRY_RESULT_WITH_RSSI, + bt_hci_inquiry_result_with_rssi, + + sizeof(struct bt_hci_evt_inquiry_result_with_rssi)), + EVENT_HANDLER(BT_HCI_EVT_EXTENDED_INQUIRY_RESULT, + bt_hci_extended_inquiry_result, + + sizeof(struct bt_hci_evt_extended_inquiry_result)), + EVENT_HANDLER(BT_HCI_EVT_REMOTE_NAME_REQ_COMPLETE, + bt_hci_remote_name_request_complete, + + sizeof(struct bt_hci_evt_remote_name_req_complete)), + EVENT_HANDLER(BT_HCI_EVT_AUTH_COMPLETE, bt_hci_auth_complete, + sizeof(struct bt_hci_evt_auth_complete)), + EVENT_HANDLER(BT_HCI_EVT_REMOTE_FEATURES, + bt_hci_read_remote_features_complete, + + sizeof(struct bt_hci_evt_remote_features)), + EVENT_HANDLER(BT_HCI_EVT_REMOTE_EXT_FEATURES, + bt_hci_read_remote_ext_features_complete, + + sizeof(struct bt_hci_evt_remote_ext_features)), + EVENT_HANDLER(BT_HCI_EVT_ROLE_CHANGE, bt_hci_role_change, + sizeof(struct bt_hci_evt_role_change)), + EVENT_HANDLER(BT_HCI_EVT_CONN_PKT_TYPE_CHANGED, + bt_hci_conn_pkt_type_changed, + + sizeof(struct bt_hci_evt_conn_pkt_type_changed)), +#if defined(CONFIG_BT_POWER_MODE_CONTROL) + EVENT_HANDLER(BT_HCI_EVT_MODE_CHANGE, bt_hci_link_mode_change, + sizeof(struct bt_hci_evt_mode_change)), +#endif /* CONFIG_BT_POWER_MODE_CONTROL */ + EVENT_HANDLER(BT_HCI_EVT_SYNC_CONN_COMPLETE, + bt_hci_synchronous_conn_complete, + + sizeof(struct bt_hci_evt_sync_conn_complete)), +#endif /* CONFIG_BT_CLASSIC */ +#if defined(CONFIG_BT_CONN) + EVENT_HANDLER(BT_HCI_EVT_DISCONN_COMPLETE, hci_disconn_complete, + sizeof(struct bt_hci_evt_disconn_complete)), +#endif /* CONFIG_BT_CONN */ +#if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_CLASSIC) + EVENT_HANDLER(BT_HCI_EVT_ENCRYPT_CHANGE, hci_encrypt_change, + sizeof(struct bt_hci_evt_encrypt_change)), + EVENT_HANDLER(BT_HCI_EVT_ENCRYPT_KEY_REFRESH_COMPLETE, + hci_encrypt_key_refresh_complete, + + sizeof(struct bt_hci_evt_encrypt_key_refresh_complete)), +#endif /* CONFIG_BT_SMP || CONFIG_BT_CLASSIC */ +#if defined(CONFIG_BT_REMOTE_VERSION) + EVENT_HANDLER(BT_HCI_EVT_REMOTE_VERSION_INFO, + bt_hci_evt_read_remote_version_complete, + + sizeof(struct bt_hci_evt_remote_version_info)), +#endif /* CONFIG_BT_REMOTE_VERSION */ + EVENT_HANDLER(BT_HCI_EVT_HARDWARE_ERROR, hci_hardware_error, + sizeof(struct bt_hci_evt_hardware_error)), +}; + +#define BT_HCI_EVT_FLAG_RECV_PRIO BIT(0) +#define BT_HCI_EVT_FLAG_RECV BIT(1) + +/** @brief Get HCI event flags. + * + * Helper for the HCI driver to get HCI event flags that describes rules + * that. must be followed. + * + * @param evt HCI event code. + * + * @return HCI event flags for the specified event. + */ + +static inline uint8_t bt_hci_evt_get_flags(uint8_t evt) +{ + switch (evt) + { + case BT_HCI_EVT_DISCONN_COMPLETE: + return BT_HCI_EVT_FLAG_RECV | BT_HCI_EVT_FLAG_RECV_PRIO; + + /* fallthrough */ +#if defined(CONFIG_BT_CONN) || defined(CONFIG_BT_ISO) + case BT_HCI_EVT_NUM_COMPLETED_PACKETS: +#if defined(CONFIG_BT_CONN) + case BT_HCI_EVT_DATA_BUF_OVERFLOW: + __fallthrough; +#endif /* defined(CONFIG_BT_CONN) */ +#endif /* CONFIG_BT_CONN || CONFIG_BT_ISO */ + case BT_HCI_EVT_CMD_COMPLETE: + case BT_HCI_EVT_CMD_STATUS: + return BT_HCI_EVT_FLAG_RECV_PRIO; + default: + return BT_HCI_EVT_FLAG_RECV; + } +} + +static inline uint8_t bt_hci_le_subevent_get_flags(uint8_t subevent) +{ + switch (subevent) + { + case BT_HCI_EVT_LE_ENH_CONN_COMPLETE: + case BT_HCI_EVT_LE_CONN_COMPLETE: + return BT_HCI_EVT_FLAG_RECV | BT_HCI_EVT_FLAG_RECV_PRIO; + default: + return BT_HCI_EVT_FLAG_RECV; + } +} + +static void hci_event(struct net_buf *buf) +{ + struct bt_hci_evt_hdr *hdr; + + hdr = net_buf_pull_mem(buf, sizeof(*hdr)); + LOG_DBG("event 0x%02x", hdr->evt); + + if (hdr->evt == BT_HCI_EVT_LE_META_EVENT) + { + struct bt_hci_evt_le_meta_event *le_evt; + + le_evt = (struct bt_hci_evt_le_meta_event *)buf->data; + + BT_ASSERT(bt_hci_le_subevent_get_flags(le_evt->subevent) & + BT_HCI_EVT_FLAG_RECV); + } + else + { + BT_ASSERT(bt_hci_evt_get_flags(hdr->evt) & BT_HCI_EVT_FLAG_RECV); + } + + handle_event(hdr->evt, buf, normal_events, ARRAY_SIZE(normal_events)); + + net_buf_unref(buf); +} + +static void hci_core_send_cmd(void) +{ + struct net_buf *buf; + int err; + + /* Get next command */ + + LOG_DBG("fetch cmd"); + buf = k_fifo_get(&bt_dev.cmd_tx_queue, 0); + BT_ASSERT(buf); + + /* Clear out any existing sent command */ + + if (bt_dev.sent_cmd) + { + LOG_ERR("Uncleared pending sent_cmd"); + net_buf_drop(&bt_dev.sent_cmd); + } + + bt_dev.sent_cmd = net_buf_ref(buf); + + LOG_DBG("Sending command 0x%04x (buf %p) to " + "driver", cmd(buf)->opcode, buf); + + err = bt_send(buf); + if (err) + { + LOG_ERR("Unable to send to driver (err %d)", err); + nxsem_post(&bt_dev.ncmd_sem); + hci_cmd_done(cmd(buf)->opcode, BT_HCI_ERR_UNSPECIFIED, buf); + net_buf_unref(buf); + bt_tx_irq_raise(); + } +} + +#if defined(CONFIG_BT_CONN) +#if defined(CONFIG_BT_ISO) +/* command FIFO + conn_change signal + MAX_CONN + ISO_MAX_CHAN */ +#define EV_COUNT (2 + CONFIG_BT_MAX_CONN + CONFIG_BT_ISO_MAX_CHAN) +#else +/* command FIFO + conn_change signal + MAX_CONN */ +#define EV_COUNT (2 + CONFIG_BT_MAX_CONN) +#endif /* CONFIG_BT_ISO */ +#else +#if defined(CONFIG_BT_ISO) +/* command FIFO + conn_change signal + ISO_MAX_CHAN */ +#define EV_COUNT (2 + CONFIG_BT_ISO_MAX_CHAN) +#else +/* command FIFO */ +#define EV_COUNT 1 +#endif /* CONFIG_BT_ISO */ +#endif /* CONFIG_BT_CONN */ + +static void read_local_ver_complete(struct net_buf *buf) +{ + struct bt_hci_rp_read_local_version_info *rp = (void *)buf->data; + + LOG_DBG("status 0x%02x %s", rp->status, bt_hci_err_to_str(rp->status)); + + bt_dev.hci_version = rp->hci_version; + bt_dev.hci_revision = sys_le16_to_cpu(rp->hci_revision); + bt_dev.lmp_version = rp->lmp_version; + bt_dev.lmp_subversion = sys_le16_to_cpu(rp->lmp_subversion); + bt_dev.manufacturer = sys_le16_to_cpu(rp->manufacturer); +} + +static void read_le_features_complete(struct net_buf *buf) +{ + struct bt_hci_rp_le_read_local_features *rp = (void *)buf->data; + + LOG_DBG("status 0x%02x %s", rp->status, bt_hci_err_to_str(rp->status)); + + memcpy(bt_dev.le.features, rp->features, sizeof(rp->features)); +} + +static void read_le_all_supported_features_complete(struct net_buf *buf) +{ + struct bt_hci_rp_le_read_all_local_supported_features *rp = + (void *)buf->data; + + LOG_DBG("status 0x%02x %s", rp->status, bt_hci_err_to_str(rp->status)); + + memcpy(bt_dev.le.features, rp->features, sizeof(bt_dev.le.features)); +} + +static int read_le_local_supported_features(void) +{ + struct net_buf *rsp; + int err; + + /* Read Low Energy Supported Features */ + + if (IS_ENABLED(CONFIG_BT_LE_EXTENDED_FEAT_SET) && + BT_READ_ALL_LOCAL_FEATURES_SUPPORTED(bt_dev.supported_commands)) + { + err = + bt_hci_cmd_send_sync( + BT_HCI_OP_LE_READ_ALL_LOCAL_SUPPORTED_FEATURES, + NULL, &rsp); + if (err != 0) + { + return err; + } + + read_le_all_supported_features_complete(rsp); + } + else + { + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_READ_LOCAL_FEATURES, NULL, + &rsp); + if (err != 0) + { + return err; + } + + read_le_features_complete(rsp); + } + + net_buf_unref(rsp); + return 0; +} + +#if defined(CONFIG_BT_CONN) +#if !defined(CONFIG_BT_CLASSIC) +static void read_buffer_size_complete(struct net_buf *buf) +{ + struct bt_hci_rp_read_buffer_size *rp = (void *)buf->data; + uint16_t pkts; + + LOG_DBG("status 0x%02x %s", rp->status, bt_hci_err_to_str(rp->status)); + + /* If LE-side has buffers we can ignore the BR/EDR values */ + + if (bt_dev.le.acl_mtu) + { + return; + } + + bt_dev.le.acl_mtu = sys_le16_to_cpu(rp->acl_max_len); + pkts = sys_le16_to_cpu(rp->acl_max_num); + + LOG_DBG("ACL BR/EDR buffers: pkts %u mtu %u", pkts, bt_dev.le.acl_mtu); + + nxsem_init(&bt_dev.le.acl_pkts, 0, pkts); +} +#endif /* !defined(CONFIG_BT_CLASSIC) */ +#endif /* CONFIG_BT_CONN */ + +static void le_read_buffer_size_complete(struct net_buf *buf) +{ + struct bt_hci_rp_le_read_buffer_size *rp = (void *)buf->data; + + LOG_DBG("status 0x%02x %s", rp->status, bt_hci_err_to_str(rp->status)); + +#if defined(CONFIG_BT_CONN) + uint16_t acl_mtu = sys_le16_to_cpu(rp->le_max_len); + + if (!acl_mtu || !rp->le_max_num) + { + return; + } + + bt_dev.le.acl_mtu = acl_mtu; + + LOG_DBG("ACL LE buffers: pkts %u mtu %u", rp->le_max_num, + bt_dev.le.acl_mtu); + + CHECK_NUM_OF_ACL_PKTS(rp->le_max_num); + + nxsem_init(&bt_dev.le.acl_pkts, 0, rp->le_max_num); +#endif /* CONFIG_BT_CONN */ +} + +static void read_buffer_size_v2_complete(struct net_buf *buf) +{ +#if defined(CONFIG_BT_ISO) + struct bt_hci_rp_le_read_buffer_size_v2 *rp = (void *)buf->data; + + LOG_DBG("status %u %s", rp->status, bt_hci_err_to_str(rp->status)); + +#if defined(CONFIG_BT_CONN) + uint16_t acl_mtu = sys_le16_to_cpu(rp->acl_max_len); + + if (acl_mtu && rp->acl_max_num) + { + bt_dev.le.acl_mtu = acl_mtu; + LOG_DBG("ACL LE buffers: pkts %u mtu %u", rp->acl_max_num, + bt_dev.le.acl_mtu); + + nxsem_init(&bt_dev.le.acl_pkts, 0, rp->acl_max_num); + + CHECK_NUM_OF_ACL_PKTS(rp->acl_max_num); + } +#endif /* CONFIG_BT_CONN */ + + uint16_t iso_mtu = sys_le16_to_cpu(rp->iso_max_len); + + if (!iso_mtu || !rp->iso_max_num) + { + LOG_ERR("ISO buffer size not set"); + return; + } + + bt_dev.le.iso_mtu = iso_mtu; + + LOG_DBG("ISO buffers: pkts %u mtu %u", rp->iso_max_num, bt_dev.le.iso_mtu); + + nxsem_init(&bt_dev.le.iso_pkts, 0, rp->iso_max_num); + bt_dev.le.iso_limit = rp->iso_max_num; + + CHECK_NUM_OF_ISO_PKTS(rp->iso_max_num); +#endif /* CONFIG_BT_ISO */ +} + +static int le_set_host_feature(uint8_t bit_number, uint8_t bit_value) +{ + struct bt_hci_cp_le_set_host_feature *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->bit_number = bit_number; + cp->bit_value = bit_value; + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_HOST_FEATURE, buf, NULL); +} + +static void read_supported_commands_complete(struct net_buf *buf) +{ + struct bt_hci_rp_read_supported_commands *rp = (void *)buf->data; + + LOG_DBG("status 0x%02x %s", rp->status, bt_hci_err_to_str(rp->status)); + + memcpy(bt_dev.supported_commands, rp->commands, + sizeof(bt_dev.supported_commands)); +} + +static void read_local_features_complete(struct net_buf *buf) +{ + struct bt_hci_rp_read_local_features *rp = (void *)buf->data; + + LOG_DBG("status 0x%02x %s", rp->status, bt_hci_err_to_str(rp->status)); + + memcpy(bt_dev.features[0], rp->features, sizeof(bt_dev.features[0])); +} + +static void le_read_supp_states_complete(struct net_buf *buf) +{ + struct bt_hci_rp_le_read_supp_states *rp = (void *)buf->data; + + LOG_DBG("status 0x%02x %s", rp->status, bt_hci_err_to_str(rp->status)); + + bt_dev.le.states = sys_get_le64(rp->le_states); +} + +#if defined(CONFIG_BT_BROADCASTER) +static void le_read_maximum_adv_data_len_complete(struct net_buf *buf) +{ + struct bt_hci_rp_le_read_max_adv_data_len *rp = (void *)buf->data; + + LOG_DBG("status 0x%02x %s", rp->status, bt_hci_err_to_str(rp->status)); + + bt_dev.le.max_adv_data_len = sys_le16_to_cpu(rp->max_adv_data_len); +} +#endif /* CONFIG_BT_BROADCASTER */ + +#if defined(CONFIG_BT_SMP) +static void le_read_resolving_list_size_complete(struct net_buf *buf) +{ + struct bt_hci_rp_le_read_rl_size *rp = (void *)buf->data; + + LOG_DBG("Resolving List size %u", rp->rl_size); + + bt_dev.le.rl_size = rp->rl_size; +} +#endif /* defined(CONFIG_BT_SMP) */ + +static int common_init(void) +{ + struct net_buf *rsp; + int err; + + if (!drv_quirk_no_reset()) + { + /* Send HCI_RESET */ + + err = bt_hci_cmd_send_sync(BT_HCI_OP_RESET, NULL, NULL); + if (err) + { + return err; + } + + hci_reset_complete(); + } + + /* Read Local Supported Features */ + + err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_LOCAL_FEATURES, NULL, &rsp); + if (err) + { + return err; + } + + read_local_features_complete(rsp); + net_buf_unref(rsp); + + /* Read Local Version Information */ + + err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_LOCAL_VERSION_INFO, NULL, &rsp); + if (err) + { + return err; + } + + read_local_ver_complete(rsp); + net_buf_unref(rsp); + + /* Read Local Supported Commands */ + + err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_SUPPORTED_COMMANDS, NULL, &rsp); + if (err) + { + return err; + } + + read_supported_commands_complete(rsp); + net_buf_unref(rsp); + + if (IS_ENABLED(CONFIG_BT_HOST_CRYPTO)) + { + /* Initialize crypto for host */ + + err = bt_crypto_init(); + if (err) + { + return err; + } + } + +#if defined(CONFIG_BT_HCI_ACL_FLOW_CONTROL) + err = set_flow_control(); + if (err) + { + return err; + } +#endif /* CONFIG_BT_HCI_ACL_FLOW_CONTROL */ + + return 0; +} + +static int le_set_event_mask(void) +{ + struct bt_hci_cp_le_set_event_mask *cp_mask; + struct net_buf *buf; + uint64_t mask = 0U; + + /* Set LE event mask */ + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp_mask = net_buf_add(buf, sizeof(*cp_mask)); + + mask |= BT_EVT_MASK_LE_ADVERTISING_REPORT; + + if (IS_ENABLED(CONFIG_BT_SCAN_EXT_FILTER_POLICY) && + BT_FEAT_LE_EXT_SCAN(bt_dev.le.features)) + { + /* Only generated by a Controller that supports the Extended Scanner + * Filter Policies, and only while scanning with one of them. + */ + + mask |= BT_EVT_MASK_LE_DIRECT_ADV_REPORT; + } + + if (IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features)) + { + mask |= BT_EVT_MASK_LE_ADV_SET_TERMINATED; + mask |= BT_EVT_MASK_LE_SCAN_REQ_RECEIVED; + mask |= BT_EVT_MASK_LE_EXT_ADVERTISING_REPORT; + mask |= BT_EVT_MASK_LE_SCAN_TIMEOUT; + if (IS_ENABLED(CONFIG_BT_PER_ADV_SYNC)) + { + mask |= BT_EVT_MASK_LE_PER_ADV_SYNC_ESTABLISHED; + mask |= BT_EVT_MASK_LE_PER_ADVERTISING_REPORT; + mask |= BT_EVT_MASK_LE_PER_ADV_SYNC_LOST; + mask |= BT_EVT_MASK_LE_PAST_RECEIVED; + } + } + + if (IS_ENABLED(CONFIG_BT_CONN)) + { + if ((IS_ENABLED(CONFIG_BT_SMP) && + BT_FEAT_LE_PRIVACY(bt_dev.le.features)) || + (IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features))) + { + /* C24: + * Mandatory if the LE Controller supports Connection State and + * either LE Feature (LL Privacy) or LE Feature (Extended + * Advertising) is supported, ... + */ + + mask |= BT_EVT_MASK_LE_ENH_CONN_COMPLETE; + } + else + { + mask |= BT_EVT_MASK_LE_CONN_COMPLETE; + } + + mask |= BT_EVT_MASK_LE_CONN_UPDATE_COMPLETE; + mask |= BT_EVT_MASK_LE_REMOTE_FEAT_COMPLETE; + + if (BT_FEAT_LE_CONN_PARAM_REQ_PROC(bt_dev.le.features)) + { + mask |= BT_EVT_MASK_LE_CONN_PARAM_REQ; + } + + if (IS_ENABLED(CONFIG_BT_DATA_LEN_UPDATE) && + BT_FEAT_LE_DLE(bt_dev.le.features)) + { + mask |= BT_EVT_MASK_LE_DATA_LEN_CHANGE; + } + + if (IS_ENABLED(CONFIG_BT_PHY_UPDATE) && + (BT_FEAT_LE_PHY_2M(bt_dev.le.features) || + BT_FEAT_LE_PHY_CODED(bt_dev.le.features))) + { + mask |= BT_EVT_MASK_LE_PHY_UPDATE_COMPLETE; + } + + if (IS_ENABLED(CONFIG_BT_TRANSMIT_POWER_CONTROL)) + { + mask |= BT_EVT_MASK_LE_TRANSMIT_POWER_REPORTING; + } + + if (IS_ENABLED(CONFIG_BT_PATH_LOSS_MONITORING)) + { + mask |= BT_EVT_MASK_LE_PATH_LOSS_THRESHOLD; + } + + if (IS_ENABLED(CONFIG_BT_SUBRATING) && + BT_FEAT_LE_CONN_SUBRATING(bt_dev.le.features)) + { + mask |= BT_EVT_MASK_LE_SUBRATE_CHANGE; + } + + if (IS_ENABLED(CONFIG_BT_SHORTER_CONNECTION_INTERVALS) && + BT_FEAT_LE_SHORTER_CONN_INTERVALS(bt_dev.le.features)) + { + mask |= BT_EVT_MASK_LE_CONN_RATE_CHANGE; + } + + if (IS_ENABLED(CONFIG_BT_LE_EXTENDED_FEAT_SET) && + BT_FEAT_LE_EXTENDED_FEAT_SET(bt_dev.le.features)) + { + mask |= BT_EVT_MASK_LE_READ_ALL_REMOTE_FEAT_COMPLETE; + } + + if (IS_ENABLED(CONFIG_BT_FRAME_SPACE_UPDATE) && + BT_FEAT_LE_FRAME_SPACE_UPDATE_SET(bt_dev.le.features)) + { + mask |= BT_EVT_MASK_LE_FRAME_SPACE_UPDATE_COMPLETE; + } + } + + if (IS_ENABLED(CONFIG_BT_SMP) && BT_FEAT_LE_ENCR(bt_dev.le.features)) + { + mask |= BT_EVT_MASK_LE_LTK_REQUEST; + } + + /* Enable CIS events only if ISO connections are enabled and controller + * support them. + */ + + if (IS_ENABLED(CONFIG_BT_ISO) && BT_FEAT_LE_CIS(bt_dev.le.features)) + { + mask |= BT_EVT_MASK_LE_CIS_ESTABLISHED; + mask |= BT_EVT_MASK_LE_CIS_ESTABLISHED_V2; + if (BT_FEAT_LE_CIS_PERIPHERAL(bt_dev.le.features)) + { + mask |= BT_EVT_MASK_LE_CIS_REQ; + } + } + + /* Enable BIS events for broadcaster and/or receiver */ + + if (IS_ENABLED(CONFIG_BT_ISO) && BT_FEAT_LE_BIS(bt_dev.le.features)) + { + if (IS_ENABLED(CONFIG_BT_ISO_BROADCASTER) && + BT_FEAT_LE_ISO_BROADCASTER(bt_dev.le.features)) + { + mask |= BT_EVT_MASK_LE_BIG_COMPLETE; + mask |= BT_EVT_MASK_LE_BIG_TERMINATED; + } + + if (IS_ENABLED(CONFIG_BT_ISO_SYNC_RECEIVER) && + BT_FEAT_LE_SYNC_RECEIVER(bt_dev.le.features)) + { + mask |= BT_EVT_MASK_LE_BIG_SYNC_ESTABLISHED; + mask |= BT_EVT_MASK_LE_BIG_SYNC_LOST; + mask |= BT_EVT_MASK_LE_BIGINFO_ADV_REPORT; + } + } + + /* Enable IQ samples report events receiver */ + + if (IS_ENABLED(CONFIG_BT_DF_CONNECTIONLESS_CTE_RX)) + { + mask |= BT_EVT_MASK_LE_CONNECTIONLESS_IQ_REPORT; + } + + if (IS_ENABLED(CONFIG_BT_DF_CONNECTION_CTE_RX)) + { + mask |= BT_EVT_MASK_LE_CONNECTION_IQ_REPORT; + mask |= BT_EVT_MASK_LE_CTE_REQUEST_FAILED; + } + + if (IS_ENABLED(CONFIG_BT_PER_ADV_RSP)) + { + mask |= BT_EVT_MASK_LE_PER_ADV_SUBEVENT_DATA_REQ; + mask |= BT_EVT_MASK_LE_PER_ADV_RESPONSE_REPORT; + } + + if (IS_ENABLED(CONFIG_BT_PER_ADV_SYNC_RSP)) + { + mask |= BT_EVT_MASK_LE_PER_ADVERTISING_REPORT_V2; + mask |= BT_EVT_MASK_LE_PER_ADV_SYNC_ESTABLISHED_V2; + mask |= BT_EVT_MASK_LE_PAST_RECEIVED_V2; + } + + if (IS_ENABLED(CONFIG_BT_CONN) && + (IS_ENABLED(CONFIG_BT_PER_ADV_RSP) || + IS_ENABLED(CONFIG_BT_PER_ADV_SYNC_RSP))) + { + mask |= BT_EVT_MASK_LE_ENH_CONN_COMPLETE_V2; + } + + if (IS_ENABLED(CONFIG_BT_CHANNEL_SOUNDING) && + BT_FEAT_LE_CHANNEL_SOUNDING(bt_dev.le.features)) + { + mask |= BT_EVT_MASK_LE_CS_READ_REMOTE_SUPPORTED_CAPABILITIES_COMPLETE; + /* Only set v2 event mask if controller supports it; v1-only CS + * controllers may reject the Set Event Mask command if an unknown bit + * is set. + */ + + if (BT_LE_CS_READ_LOCAL_SUPPORTED_CAPABILITIES_V2_SUPPORTED( + bt_dev.supported_commands)) + { + const uint64_t cs_cap_v2 = BT_EVT_BIT(55); + + mask |= cs_cap_v2; + } + + mask |= BT_EVT_MASK_LE_CS_READ_REMOTE_FAE_TABLE_COMPLETE; + mask |= BT_EVT_MASK_LE_CS_CONFIG_COMPLETE; + mask |= BT_EVT_MASK_LE_CS_SECURITY_ENABLE_COMPLETE; + mask |= BT_EVT_MASK_LE_CS_PROCEDURE_ENABLE_COMPLETE; + mask |= BT_EVT_MASK_LE_CS_SUBEVENT_RESULT; + mask |= BT_EVT_MASK_LE_CS_SUBEVENT_RESULT_CONTINUE; + mask |= BT_EVT_MASK_LE_CS_TEST_END_COMPLETE; + } + + sys_put_le64(mask, cp_mask->events); + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_EVENT_MASK, buf, NULL); +} + +static int le_init_iso(void) +{ + int err; + struct net_buf *rsp; + + if (IS_ENABLED(CONFIG_BT_ISO_UNICAST)) + { + /* Set Connected Isochronous Streams - Host support */ + + err = le_set_host_feature(BT_LE_FEAT_BIT_ISO_CHANNELS, 1); + if (err) + { + return err; + } + } + + /* Octet 41, bit 5 is read buffer size V2 */ + + if (BT_CMD_TEST(bt_dev.supported_commands, 41, 5)) + { + /* Read ISO Buffer Size V2 */ + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_READ_BUFFER_SIZE_V2, NULL, + &rsp); + if (err) + { + return err; + } + + read_buffer_size_v2_complete(rsp); + + net_buf_unref(rsp); + } + else if (IS_ENABLED(CONFIG_BT_CONN_TX)) + { + if (IS_ENABLED(CONFIG_BT_ISO_TX)) + { + LOG_WRN("Read Buffer Size V2 command is not supported. " + "No ISO TX buffers will be available"); + } + + /* Read LE Buffer Size in the case that we support ACL without TX ISO + * (e.g. if we only support ISO sync receiver). + */ + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_READ_BUFFER_SIZE, NULL, &rsp); + if (err) + { + return err; + } + + le_read_buffer_size_complete(rsp); + + net_buf_unref(rsp); + } + + return 0; +} + +static int le_init(void) +{ + struct bt_hci_cp_write_le_host_supp *cp_le; + struct net_buf *buf; + struct net_buf *rsp; + int err; + + /* For now we only support LE capable controllers */ + + if (!BT_FEAT_LE(bt_dev.features)) + { + LOG_ERR("Non-LE capable controller detected!"); + return -ENODEV; + } + + err = read_le_local_supported_features(); + if (err) + { + return err; + } + + if (IS_ENABLED(CONFIG_BT_ISO) && BT_FEAT_LE_ISO(bt_dev.le.features)) + { + err = le_init_iso(); + if (err) + { + return err; + } + } + else if (IS_ENABLED(CONFIG_BT_CONN)) + { + /* Read LE Buffer Size */ + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_READ_BUFFER_SIZE, NULL, &rsp); + if (err) + { + return err; + } + + le_read_buffer_size_complete(rsp); + + net_buf_unref(rsp); + } + +#if defined(CONFIG_BT_BROADCASTER) + if (IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features)) + { + /* Read LE Max Adv Data Len */ + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_READ_MAX_ADV_DATA_LEN, NULL, + &rsp); + if (err == 0) + { + le_read_maximum_adv_data_len_complete(rsp); + net_buf_unref(rsp); + } + else if (err == -EIO) + { + LOG_WRN("Controller does not support 'LE_READ_MAX_ADV_DATA_LEN'. " + "Assuming maximum length is 31 bytes."); + bt_dev.le.max_adv_data_len = 31; + } + else + { + return err; + } + } + else + { + bt_dev.le.max_adv_data_len = 31; + } +#endif /* CONFIG_BT_BROADCASTER */ + + if (BT_FEAT_BREDR(bt_dev.features)) + { + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp_le = net_buf_add(buf, sizeof(*cp_le)); + + /* Explicitly enable LE for dual-mode controllers */ + + cp_le->le = 0x01; + cp_le->simul = 0x00; + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_WRITE_LE_HOST_SUPP, buf, NULL); + if (err) + { + return err; + } + } + + /* Read LE Supported States */ + + if (BT_CMD_LE_STATES(bt_dev.supported_commands)) + { + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_READ_SUPP_STATES, NULL, &rsp); + if (err) + { + return err; + } + + le_read_supp_states_complete(rsp); + net_buf_unref(rsp); + } + + if (IS_ENABLED(CONFIG_BT_CONN) && IS_ENABLED(CONFIG_BT_DATA_LEN_UPDATE) && + IS_ENABLED(CONFIG_BT_AUTO_DATA_LEN_UPDATE) && + BT_FEAT_LE_DLE(bt_dev.le.features)) + { + struct bt_hci_cp_le_write_default_data_len *cp; + uint16_t tx_octets; + uint16_t tx_time; + + err = bt_hci_le_read_max_data_len(&tx_octets, &tx_time); + if (err) + { + return err; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->max_tx_octets = sys_cpu_to_le16(tx_octets); + cp->max_tx_time = sys_cpu_to_le16(tx_time); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_WRITE_DEFAULT_DATA_LEN, buf, + NULL); + if (err) + { + return err; + } + } + +#if defined(CONFIG_BT_SMP) + if (BT_FEAT_LE_PRIVACY(bt_dev.le.features)) + { +#if defined(CONFIG_BT_PRIVACY) + struct bt_hci_cp_le_set_rpa_timeout *cp; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->rpa_timeout = sys_cpu_to_le16(bt_dev.rpa_timeout); + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_RPA_TIMEOUT, buf, NULL); + if (err) + { + return err; + } +#endif /* defined(CONFIG_BT_PRIVACY) */ + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_READ_RL_SIZE, NULL, &rsp); + if (err) + { + return err; + } + + le_read_resolving_list_size_complete(rsp); + net_buf_unref(rsp); + } +#endif + +#if defined(CONFIG_BT_DF) + if (BT_FEAT_LE_CONNECTIONLESS_CTE_TX(bt_dev.le.features) || + BT_FEAT_LE_CONNECTIONLESS_CTE_RX(bt_dev.le.features) || + BT_FEAT_LE_RX_CTE(bt_dev.le.features)) + { + err = le_df_init(); + if (err) + { + return err; + } + } +#endif /* CONFIG_BT_DF */ + + if (IS_ENABLED(CONFIG_BT_SUBRATING) && + BT_FEAT_LE_CONN_SUBRATING(bt_dev.le.features)) + { + /* Connection Subrating (Host Support) */ + + err = le_set_host_feature(BT_LE_FEAT_BIT_CONN_SUBRATING_HOST_SUPP, 1); + if (err) + { + return err; + } + } + + if (IS_ENABLED(CONFIG_BT_CHANNEL_SOUNDING) && + BT_FEAT_LE_CHANNEL_SOUNDING(bt_dev.le.features)) + { + err = le_set_host_feature(BT_LE_FEAT_BIT_CHANNEL_SOUNDING_HOST, 1); + if (err) + { + return err; + } + } + + if (IS_ENABLED(CONFIG_BT_EXT_ADV_CODING_SELECTION) && + IS_ENABLED(CONFIG_BT_OBSERVER) && + BT_FEAT_LE_ADV_CODING_SEL(bt_dev.le.features)) + { + err = le_set_host_feature(BT_LE_FEAT_BIT_ADV_CODING_SEL_HOST, 1); + if (err) + { + return err; + } + } + + if (IS_ENABLED(CONFIG_BT_SHORTER_CONNECTION_INTERVALS) && + BT_FEAT_LE_SHORTER_CONN_INTERVALS(bt_dev.le.features)) + { + err = + le_set_host_feature( + BT_LE_FEAT_BIT_SHORTER_CONN_INTERVALS_HOST_SUPP, + 1); + if (err != 0) + { + return err; + } + } + + return le_set_event_mask(); +} + +static int hci_read_buffer_size(void) +{ +#if !defined(CONFIG_BT_CLASSIC) && defined(CONFIG_BT_CONN) + struct net_buf *rsp; + int err; + + if (bt_dev.le.acl_mtu) + { + return 0; + } + + /* Use BR/EDR buffer size if LE reports zero buffers */ + + err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_BUFFER_SIZE, NULL, &rsp); + if (err) + { + return err; + } + + read_buffer_size_complete(rsp); + net_buf_unref(rsp); +#endif /* !CONFIG_BT_CLASSIC */ + + return 0; +} + +static int set_event_mask(void) +{ + struct bt_hci_cp_set_event_mask *ev; + struct net_buf *buf; + uint64_t mask = 0U; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + ev = net_buf_add(buf, sizeof(*ev)); + + if (IS_ENABLED(CONFIG_BT_CLASSIC)) + { + /* Since we require LE support, we can count on a + * Bluetooth 4.0 feature set + */ + + mask |= BT_EVT_MASK_INQUIRY_COMPLETE; + mask |= BT_EVT_MASK_CONN_COMPLETE; + mask |= BT_EVT_MASK_CONN_REQUEST; + mask |= BT_EVT_MASK_AUTH_COMPLETE; + mask |= BT_EVT_MASK_REMOTE_NAME_REQ_COMPLETE; + mask |= BT_EVT_MASK_REMOTE_FEATURES; + mask |= BT_EVT_MASK_ROLE_CHANGE; + mask |= BT_EVT_MASK_CONN_PKT_TYPE_CHANGED; +#ifdef CONFIG_BT_POWER_MODE_CONTROL + mask |= BT_EVT_MASK_MODE_CHANGE; +#endif /* CONFIG_BT_POWER_MODE_CONTROL */ + mask |= BT_EVT_MASK_PIN_CODE_REQ; + mask |= BT_EVT_MASK_LINK_KEY_REQ; + mask |= BT_EVT_MASK_LINK_KEY_NOTIFY; + mask |= BT_EVT_MASK_INQUIRY_RESULT_WITH_RSSI; + mask |= BT_EVT_MASK_REMOTE_EXT_FEATURES; + mask |= BT_EVT_MASK_SYNC_CONN_COMPLETE; + mask |= BT_EVT_MASK_EXTENDED_INQUIRY_RESULT; + mask |= BT_EVT_MASK_IO_CAPA_REQ; + mask |= BT_EVT_MASK_IO_CAPA_RESP; + mask |= BT_EVT_MASK_USER_CONFIRM_REQ; + mask |= BT_EVT_MASK_USER_PASSKEY_REQ; + mask |= BT_EVT_MASK_SSP_COMPLETE; + mask |= BT_EVT_MASK_USER_PASSKEY_NOTIFY; + } + + mask |= BT_EVT_MASK_HARDWARE_ERROR; + mask |= BT_EVT_MASK_DATA_BUFFER_OVERFLOW; + mask |= BT_EVT_MASK_LE_META_EVENT; + + if (IS_ENABLED(CONFIG_BT_CONN)) + { + mask |= BT_EVT_MASK_DISCONN_COMPLETE; + mask |= BT_EVT_MASK_REMOTE_VERSION_INFO; + } + + if (IS_ENABLED(CONFIG_BT_SMP) && BT_FEAT_LE_ENCR(bt_dev.le.features)) + { + mask |= BT_EVT_MASK_ENCRYPT_CHANGE; + mask |= BT_EVT_MASK_ENCRYPT_KEY_REFRESH_COMPLETE; + } + + sys_put_le64(mask, ev->events); + return bt_hci_cmd_send_sync(BT_HCI_OP_SET_EVENT_MASK, buf, NULL); +} + +const char *bt_hci_get_ver_str(uint8_t core_version) +{ + static const char *const str[] = + { + "1.0b", "1.1", "1.2", "2.0", "2.1", "3.0", "4.0", "4.1", "4.2", + "5.0", "5.1", "5.2", "5.3", "5.4", + "6.0", "6.1", "6.2", "6.3" + }; + + if (core_version < ARRAY_SIZE(str)) + { + return str[core_version]; + } + + return "unknown"; +} + +static void bt_dev_show_info(void) +{ + int i; + + LOG_INF("HCI transport: %s", BT_HCI_NAME); + LOG_INF("Identity%s: %s", bt_dev.id_count > 1 ? "[0]" : "", + bt_addr_le_str(&bt_dev.id_addr[0])); + + if (IS_ENABLED(CONFIG_BT_LOG_SNIFFER_INFO)) + { +#if defined(CONFIG_BT_PRIVACY) + uint8_t irk[16]; + + sys_memcpy_swap(irk, bt_dev.irk[0], 16); + LOG_INF("IRK%s: 0x%s", bt_dev.id_count > 1 ? "[0]" : "", bt_hex(irk, + 16)); +#endif + } + + for (i = 1; i < bt_dev.id_count; i++) + { + LOG_INF("Identity[%d]: %s", i, bt_addr_le_str(&bt_dev.id_addr[i])); + + if (IS_ENABLED(CONFIG_BT_LOG_SNIFFER_INFO)) + { +#if defined(CONFIG_BT_PRIVACY) + uint8_t irk[16]; + + sys_memcpy_swap(irk, bt_dev.irk[i], 16); + LOG_INF("IRK[%d]: 0x%s", i, bt_hex(irk, 16)); +#endif + } + } + + if (IS_ENABLED(CONFIG_BT_SMP) && IS_ENABLED(CONFIG_BT_LOG_SNIFFER_INFO)) + { + bt_keys_foreach_type(BT_KEYS_ALL, bt_keys_show_sniffer_info, NULL); + } + + LOG_INF("HCI: version %s (0x%02x) revision 0x%04x, manufacturer 0x%04x", + bt_hci_get_ver_str(bt_dev.hci_version), bt_dev.hci_version, + bt_dev.hci_revision, + bt_dev.manufacturer); + LOG_INF("LMP: version %s (0x%02x) subver 0x%04x", + bt_hci_get_ver_str(bt_dev.lmp_version), + bt_dev.lmp_version, bt_dev.lmp_subversion); +} + +#if defined(CONFIG_BT_HCI_VS) +static const char *vs_hw_platform(uint16_t platform) +{ + static const char *const plat_str[] = + { + "reserved", "Intel Corporation", "Nordic Semiconductor", + "NXP Semiconductors", + "Espressif Systems" + }; + + if (platform < ARRAY_SIZE(plat_str)) + { + return plat_str[platform]; + } + + return "unknown"; +} + +static const char *vs_hw_variant(uint16_t platform, uint16_t variant) +{ +#if defined(CONFIG_SOC_FAMILY_NORDIC_NRF) + static const char *const nordic_str[] = + { + "reserved", "nRF51x", "nRF52x", + "nRF53x", "nRF54Hx", "nRF54Lx" + }; + + if (platform == BT_HCI_VS_HW_PLAT_NORDIC && + variant < ARRAY_SIZE(nordic_str)) + { + return nordic_str[variant]; + } +#endif +#if defined(CONFIG_SOC_FAMILY_ESPRESSIF_ESP32) + + static const char *const esp32_str[] = + { + "reserved", "ESP32", "ESP32-S3", + "ESP32-C2", "ESP32-C3", "ESP32-C6", + "ESP32-H2", "ESP32-C5", + "ESP32-C61" + }; + + if (platform == BT_HCI_VS_HW_PLAT_ESPRESSIF && + variant < ARRAY_SIZE(esp32_str)) + { + return esp32_str[variant]; + } +#endif + + ARG_UNUSED(platform); + ARG_UNUSED(variant); + + return "unknown"; +} + +static const char *vs_fw_variant(uint8_t variant) +{ + static const char *const var_str[] = + { + "Standard Bluetooth controller", + "Vendor specific Bluetooth Controller", + "Firmware loader", + "Rescue image", + }; + + /* The Zephyr controller responds with the standard variant and The + * Linux Foundation company identifier. + */ + + if (variant == BT_HCI_VS_FW_VAR_STANDARD_CTLR && + bt_dev.manufacturer == BT_COMP_ID_LF) + { + return "NuttX Bluetooth Controller"; + } + + if (variant < ARRAY_SIZE(var_str)) + { + return var_str[variant]; + } + + return "unknown"; +} + +static void hci_vs_init(void) +{ + union + { + struct bt_hci_rp_vs_read_version_info *info; + struct bt_hci_rp_vs_read_supported_commands *cmds; + struct bt_hci_rp_vs_read_supported_features *feat; + } rp; + + struct net_buf *rsp; + int err; + + /* If heuristics is enabled, try to guess HCI VS support by looking + * at the HCI version and identity address. We haven't set any addresses at + * this point. So we need to read the public address. + */ + + if (IS_ENABLED(CONFIG_BT_HCI_VS_EXT_DETECT)) + { + bt_addr_le_t addr; + + if ((bt_dev.hci_version < BT_HCI_VERSION_5_0) || + bt_id_read_public_addr(&addr)) + { + LOG_WRN("Controller doesn't seem to support " + "NuttX vendor HCI"); + return; + } + } + + err = bt_hci_cmd_send_sync(BT_HCI_OP_VS_READ_VERSION_INFO, NULL, &rsp); + if (err) + { + LOG_WRN("Vendor HCI extensions not available"); + return; + } + + if (IS_ENABLED(CONFIG_BT_HCI_VS_EXT_DETECT) && + rsp->len != sizeof(struct bt_hci_rp_vs_read_version_info)) + { + LOG_WRN("Invalid Vendor HCI extensions"); + net_buf_unref(rsp); + return; + } + + rp.info = (void *)rsp->data; + LOG_INF("HW Platform: %s (0x%04x)", + vs_hw_platform(sys_le16_to_cpu(rp.info->hw_platform)), + sys_le16_to_cpu(rp.info->hw_platform)); + LOG_INF("HW Variant: %s (0x%04x)", + vs_hw_variant(sys_le16_to_cpu(rp.info->hw_platform), + sys_le16_to_cpu(rp.info->hw_variant)), + sys_le16_to_cpu(rp.info->hw_variant)); + LOG_INF("Controller: %s (" + "0x%02x) manufacturer 0x%04x Version %u.%u Build %u", + vs_fw_variant(rp.info->fw_variant), rp.info->fw_variant, + bt_dev.manufacturer, + rp.info->fw_version, sys_le16_to_cpu(rp.info->fw_revision), + sys_le32_to_cpu(rp.info->fw_build)); + + net_buf_unref(rsp); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_VS_READ_SUPPORTED_COMMANDS, NULL, + &rsp); + if (err) + { + LOG_WRN("Failed to read supported vendor commands"); + return; + } + + if (IS_ENABLED(CONFIG_BT_HCI_VS_EXT_DETECT) && + rsp->len != sizeof(struct bt_hci_rp_vs_read_supported_commands)) + { + LOG_WRN("Invalid Vendor HCI extensions"); + net_buf_unref(rsp); + return; + } + + rp.cmds = (void *)rsp->data; + memcpy(bt_dev.vs_commands, rp.cmds->commands, BT_DEV_VS_CMDS_MAX); + net_buf_unref(rsp); +} + +static int hci_vs_write_bd_addr(bt_addr_t *bdaddr) +{ + struct bt_hci_cp_vs_write_bd_addr *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (buf == NULL) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + bt_addr_copy(&cp->bdaddr, bdaddr); + + return bt_hci_cmd_send_sync(BT_HCI_OP_VS_WRITE_BD_ADDR, buf, NULL); +} +#endif /* CONFIG_BT_HCI_VS */ + +static int hci_init(void) +{ + int err; + +#if defined(CONFIG_BT_HCI_SETUP) + struct bt_hci_setup_params setup_params = + { + 0 + }; + + bt_addr_copy(&setup_params.public_addr, BT_ADDR_ANY); +#if defined(CONFIG_BT_HCI_SET_PUBLIC_ADDR) + if (bt_dev.id_count > 0 && + bt_dev.id_addr[BT_ID_DEFAULT].type == BT_ADDR_LE_PUBLIC) + { + bt_addr_copy(&setup_params.public_addr, + &bt_dev.id_addr[BT_ID_DEFAULT].a); + } +#endif /* defined(CONFIG_BT_HCI_SET_PUBLIC_ADDR) */ + + err = bt_hci_setup(bt_dev.hci, &setup_params); + if (err && err != -ENOSYS) + { + return err; + } +#endif /* defined(CONFIG_BT_HCI_SETUP) */ + + err = common_init(); + if (err) + { + return err; + } + + err = le_init(); + if (err) + { + return err; + } + + if (BT_FEAT_BREDR(bt_dev.features)) + { + if (IS_ENABLED(CONFIG_BT_CLASSIC)) + { + err = bt_br_init(); + } + else if (IS_ENABLED(CONFIG_BT_CONN)) + { + err = hci_read_buffer_size(); + } + + if (err) + { + return err; + } + } + else if (IS_ENABLED(CONFIG_BT_CLASSIC)) + { + LOG_ERR("Non-BR/EDR controller detected"); + return -EIO; + } +#if defined(CONFIG_BT_CONN) + else if (!bt_dev.le.acl_mtu) + { + LOG_ERR("ACL BR/EDR buffers not initialized"); + return -EIO; + } +#endif + + err = set_event_mask(); + if (err) + { + return err; + } + +#if defined(CONFIG_BT_HCI_VS) + hci_vs_init(); + + if (bt_dev.id_count > 0U && + bt_dev.id_addr[BT_ID_DEFAULT].type == BT_ADDR_LE_PUBLIC) + { + if (BT_VS_CMD_WRITE_BD_ADDR(bt_dev.vs_commands)) + { + err = hci_vs_write_bd_addr(&bt_dev.id_addr[BT_ID_DEFAULT].a); + if (err != 0) + { + return err; + } + } + else if (!IS_ENABLED(CONFIG_BT_HCI_SET_PUBLIC_ADDR)) + { + return -ENOTSUP; + } + } +#endif + + err = bt_id_init(); + if (err) + { + return err; + } + + return 0; +} + +int bt_send(struct net_buf *buf) +{ + /* Only peek at the type, since it needs to be retained for bt_hci_send() */ + + uint8_t type = buf->data[0]; + + LOG_DBG("buf %p len %u type %u", buf, buf->len, type); + + bt_monitor_send(bt_monitor_opcode(type, BT_MONITOR_TX), buf->data + 1, + buf->len - 1); + + return bt_hci_send(bt_dev.hci, buf); +} + +#if defined(CONFIG_BT_CONN) +static void le_conn_complete_prio(uint8_t role) +{ + if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && role == BT_HCI_ROLE_PERIPHERAL && + !(IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features))) + { + struct bt_le_ext_adv *adv = bt_le_adv_lookup_legacy(); + /* Clear advertising even if we are not able to add connection + * object to keep host in sync with controller state. + */ + + atomic_clear_bit(adv->flags, BT_ADV_ENABLED); + (void)bt_le_lim_adv_cancel_timeout(adv); + } +} + +static void le_legacy_conn_complete_prio(struct net_buf *buf) +{ + struct bt_hci_evt_le_conn_complete *evt = (void *)buf->data; + + le_conn_complete_prio(evt->role); +} + +static void le_enh_conn_complete_prio(struct net_buf *buf) +{ + struct bt_hci_evt_le_enh_conn_complete *evt = + (struct bt_hci_evt_le_enh_conn_complete *)buf->data; + + le_conn_complete_prio(evt->role); +} +#endif + +static const struct event_handler meta_prio_events[] = +{ +#if defined(CONFIG_BT_CONN) + EVENT_HANDLER(BT_HCI_EVT_LE_CONN_COMPLETE, + le_legacy_conn_complete_prio, + + sizeof(struct bt_hci_evt_le_conn_complete)), + EVENT_HANDLER(BT_HCI_EVT_LE_ENH_CONN_COMPLETE, + le_enh_conn_complete_prio, + + sizeof(struct bt_hci_evt_le_enh_conn_complete)), +#endif /* CONFIG_BT_CONN */ +}; + +static void hci_le_meta_prio_event(struct net_buf *buf) +{ + struct bt_hci_evt_le_meta_event *evt; + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + + LOG_DBG("subevent 0x%02x", evt->subevent); + + handle_event(evt->subevent, buf, meta_prio_events, + ARRAY_SIZE(meta_prio_events)); +} + +static const struct event_handler prio_events[] = +{ + EVENT_HANDLER(BT_HCI_EVT_CMD_COMPLETE, hci_cmd_complete, + sizeof(struct bt_hci_evt_cmd_complete)), + EVENT_HANDLER(BT_HCI_EVT_CMD_STATUS, + hci_cmd_status, + sizeof(struct bt_hci_evt_cmd_status)), +#if defined(CONFIG_BT_CONN) + EVENT_HANDLER(BT_HCI_EVT_DATA_BUF_OVERFLOW, hci_data_buf_overflow, + sizeof(struct bt_hci_evt_data_buf_overflow)), + EVENT_HANDLER(BT_HCI_EVT_DISCONN_COMPLETE, hci_disconn_complete_prio, + sizeof(struct bt_hci_evt_disconn_complete)), +#endif /* CONFIG_BT_CONN */ +#if defined(CONFIG_BT_CONN_TX) + EVENT_HANDLER(BT_HCI_EVT_NUM_COMPLETED_PACKETS, + hci_num_completed_packets, + + sizeof(struct bt_hci_evt_num_completed_packets)), +#endif /* CONFIG_BT_CONN_TX */ + EVENT_HANDLER(BT_HCI_EVT_LE_META_EVENT, hci_le_meta_prio_event, + sizeof(struct bt_hci_evt_le_meta_event)), +}; + +static void hci_event_prio(struct net_buf *buf) +{ + struct net_buf_simple_state state; + struct bt_hci_evt_hdr *hdr; + uint8_t evt_flags; + + net_buf_simple_save(&buf->b, &state); + + /* Remove buffer type byte */ + + net_buf_pull(buf, 1); + + hdr = net_buf_pull_mem(buf, sizeof(*hdr)); + + if (hdr->evt == BT_HCI_EVT_LE_META_EVENT) + { + struct bt_hci_evt_le_meta_event *le_evt; + + le_evt = (struct bt_hci_evt_le_meta_event *)buf->data; + + evt_flags = bt_hci_le_subevent_get_flags(le_evt->subevent); + } + else + { + evt_flags = bt_hci_evt_get_flags(hdr->evt); + } + + BT_ASSERT(evt_flags & BT_HCI_EVT_FLAG_RECV_PRIO); + + handle_event(hdr->evt, buf, prio_events, ARRAY_SIZE(prio_events)); + + if (evt_flags & BT_HCI_EVT_FLAG_RECV) + { + net_buf_simple_restore(&buf->b, &state); + } + else + { + net_buf_unref(buf); + } +} + +/* Whether bt_disable() is tearing down low-priority RX processing, meaning + * that RX packets must no longer be queued or dispatched. Not true after a + * failed disable, which clears BT_DEV_DISABLING again. + */ + +static bool rx_teardown_active(void) +{ + return atomic_test_bit(bt_dev.flags, BT_DEV_DISABLING) && + !atomic_test_bit(bt_dev.flags, BT_DEV_READY); +} + +static void rx_queue_put(struct net_buf *buf) +{ + if (rx_teardown_active()) + { + net_buf_unref(buf); + return; + } + + net_buf_slist_put(&bt_dev.rx_queue, buf); + + const int err = bt_work_submit(&rx_work); + + if (err < 0) + { + LOG_ERR("Could not submit rx_work: %d", err); + } +} + +static int bt_recv_unsafe(struct net_buf *buf) +{ + /* Don't pull the type, since we still need it in the rx queue */ + + uint8_t type = buf->data[0]; + + bt_monitor_send(bt_monitor_opcode(type, BT_MONITOR_RX), buf->data + 1, + buf->len - 1); + + LOG_DBG("buf %p len %u", buf, buf->len); + + switch (type) + { +#if defined(CONFIG_BT_CONN) + case BT_HCI_H4_ACL: + rx_queue_put(buf); + return 0; +#endif /* BT_CONN */ + case BT_HCI_H4_EVT: + { + struct bt_hci_evt_hdr *hdr; + uint8_t evt_flags; + + if (buf->len < sizeof(*hdr) + 1) + { + LOG_ERR("Invalid HCI event size (%u)", buf->len); + net_buf_unref(buf); + return 0; + } + + hdr = (void *)(buf->data + 1); + + if (hdr->evt == BT_HCI_EVT_LE_META_EVENT) + { + struct bt_hci_evt_le_meta_event *le_evt; + + if (buf->len < sizeof(*hdr) + 1 + sizeof(*le_evt)) + { + LOG_ERR("Invalid LE meta event size (%u)", buf->len); + net_buf_unref(buf); + return 0; + } + + le_evt = + (struct bt_hci_evt_le_meta_event *)&buf->data[sizeof( + *hdr) + 1]; + evt_flags = bt_hci_le_subevent_get_flags(le_evt->subevent); + } + else + { + evt_flags = bt_hci_evt_get_flags(hdr->evt); + } + + if (evt_flags & BT_HCI_EVT_FLAG_RECV_PRIO) + { + hci_event_prio(buf); + } + + if (evt_flags & BT_HCI_EVT_FLAG_RECV) + { + rx_queue_put(buf); + } + + return 0; + } +#if defined(CONFIG_BT_ISO) + + case BT_HCI_H4_ISO: + rx_queue_put(buf); + return 0; +#endif /* CONFIG_BT_ISO */ + default: + LOG_ERR("Invalid buf type %u", type); + return -EINVAL; + } +} + +static int bt_recv(const struct device *dev, struct net_buf *buf) +{ + ARG_UNUSED(dev); + int err; + + sched_lock(); + err = bt_recv_unsafe(buf); + sched_unlock(); + + return err; +} + +/* Complete the enable transition started by bt_enable(), whatever its + * outcome: BT_DEV_ENABLING is cleared in every case, so that bt_enable() and + * bt_disable() can be called again, while the stack is marked ready only + * when the initialization succeeded. Called once per transition, either from + * bt_init() or, when the identity comes from settings, from the settings + * commit handler. + */ + +void bt_finalize_init(int err) +{ + if (!atomic_test_bit(bt_dev.flags, BT_DEV_ENABLING)) + { + return; + } + + if (err == 0) + { + if (IS_ENABLED(CONFIG_BT_OBSERVER)) + { + bt_scan_reset(); + } + + bt_dev_show_info(); + + /* Publish BT_DEV_READY before clearing BT_DEV_ENABLING, so that + * bt_disable() always finds the stack either still enabling or ready, + * never in between. + */ + + atomic_set_bit(bt_dev.flags, BT_DEV_READY); + } + + atomic_clear_bit(bt_dev.flags, BT_DEV_ENABLING); +} + +static int bt_init(void) +{ + int err; + + err = hci_init(); + if (err) + { + goto done; + } + + if (IS_ENABLED(CONFIG_BT_CONN)) + { + err = bt_conn_init(); + if (err) + { + goto done; + } + } + + if (IS_ENABLED(CONFIG_BT_ISO)) + { + err = bt_conn_iso_init(); + if (err) + { + goto done; + } + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + if (!bt_dev.id_count) + { + LOG_INF("No ID address. App must call settings_load()"); + return 0; + } + + atomic_set_bit(bt_dev.flags, BT_DEV_PRESET_ID); + } + +done: + bt_finalize_init(err); + return err; +} + +static void init_work(struct k_work *work) +{ + int err; + + err = bt_init(); + if (ready_cb) + { + ready_cb(err); + } +} + +static void rx_work_handler(struct k_work *work) +{ + uint8_t type; + int err; + + struct net_buf *buf; + + LOG_DBG("Getting net_buf from queue"); + buf = net_buf_slist_get(&bt_dev.rx_queue); + if (!buf) + { + return; + } + + if (rx_teardown_active()) + { + /* Drop the packet rather than dispatch it towards a transport + * that is being torn down. No need to resubmit the work: bt_disable() + * purges whatever remains on the queue. + */ + + net_buf_unref(buf); + return; + } + + type = net_buf_pull_u8(buf); + + LOG_DBG("buf %p type %u len %u", buf, type, buf->len); + + switch (type) + { +#if defined(CONFIG_BT_CONN) + case BT_HCI_H4_ACL: + hci_acl(buf); + break; +#endif /* CONFIG_BT_CONN */ +#if defined(CONFIG_BT_ISO) + case BT_HCI_H4_ISO: + hci_iso(buf); + break; +#endif /* CONFIG_BT_ISO */ + case BT_HCI_H4_EVT: + hci_event(buf); + break; + default: + LOG_ERR("Unknown buf type %u", type); + net_buf_unref(buf); + break; + } + + /* Schedule the work handler to be executed again if there are + * additional items in the queue. This allows for other users of the work + * queue to get a chance at running, which wouldn't be possible if we used + * a while() loop with a sched_yield() statement. + */ + + if (!list_is_empty(&bt_dev.rx_queue)) + { + err = bt_work_submit(&rx_work); + if (err < 0) + { + LOG_ERR("Could not submit rx_work: %d", err); + } + } +} + +#if defined(CONFIG_BT_TESTING) && defined(CONFIG_BT_ISO) +void bt_testing_set_iso_mtu(uint16_t mtu) +{ + bt_dev.le.iso_mtu = mtu; +} +#endif /* CONFIG_BT_TESTING && CONFIG_BT_ISO */ + +int bt_enable(bt_ready_cb_t cb) +{ + int err; + + if (IS_ENABLED(CONFIG_ZTEST) && bt_dev.hci == NULL) + { + LOG_ERR("No DT chosen property for HCI"); + return -ENODEV; + } + + if (atomic_test_and_set_bit(bt_dev.flags, BT_DEV_ENABLING)) + { + return -EALREADY; + } + + if (atomic_test_bit(bt_dev.flags, BT_DEV_DISABLING)) + { + err = -EAGAIN; + goto failed; + } + + if (!device_is_ready(bt_dev.hci)) + { + LOG_ERR("HCI driver is not ready"); + err = -ENODEV; + goto failed; + } + + bt_monitor_new_index(BT_MONITOR_TYPE_PRIMARY, BT_HCI_BUS, BT_ADDR_ANY, + BT_HCI_NAME); + + if (atomic_test_bit(bt_dev.flags, BT_DEV_OPEN)) + { + err = -EALREADY; + goto failed; + } + + /* Keep the queue alive across enable/disable cycles because delayable + * host work may outlive an individual cycle. + */ + + err = bt_workq_start(); + if (err) + { + goto failed; + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + err = bt_settings_init(); + if (err) + { + goto failed; + } + } + else if (IS_ENABLED(CONFIG_BT_DEVICE_NAME_DYNAMIC)) + { + err = bt_set_name(CONFIG_BT_DEVICE_NAME); + if (err) + { + LOG_WRN("Failed to set device name (%d)", err); + + /* Not a critical error, so continue with initialization. */ + + err = 0; + } + } + + ready_cb = cb; + + /* Give cmd_sem allowing to send first HCI_Reset cmd, the only + * exception is if the controller requests to wait for an initial Command + * Complete for NOP. + */ + + if (!IS_ENABLED(CONFIG_BT_WAIT_NOP)) + { + nxsem_init(&bt_dev.ncmd_sem, 0, 1); + } + else + { + nxsem_init(&bt_dev.ncmd_sem, 0, 0); + } + + k_fifo_init(&bt_dev.cmd_tx_queue); + + if (IS_ENABLED(CONFIG_BT_HCI_SET_PUBLIC_ADDR)) + { + /* Set on every enable, and cleared when there is no public + * identity, so that an address an earlier enable left behind is not + * applied to the controller by this one. + */ + + if (bt_dev.id_count > 0 && + bt_dev.id_addr[BT_ID_DEFAULT].type == BT_ADDR_LE_PUBLIC) + { + bt_hci_set_public_addr(bt_dev.hci, + &bt_dev.id_addr[BT_ID_DEFAULT].a); + } + else + { + bt_hci_set_public_addr(bt_dev.hci, BT_ADDR_ANY); + } + } + + err = bt_hci_open(bt_dev.hci, bt_recv); + if (err) + { + LOG_ERR("HCI driver open failed (%d)", err); + goto failed; + } + + atomic_set_bit(bt_dev.flags, BT_DEV_OPEN); + + bt_monitor_send(BT_MONITOR_OPEN_INDEX, NULL, 0); + + if (!cb) + { + return bt_init(); + } + + k_work_submit(&bt_dev.init); + return 0; + +failed: + atomic_clear_bit(bt_dev.flags, BT_DEV_ENABLING); + return err; +} + +int bt_disable(void) +{ + struct net_buf *buf; + bool was_ready; + int err; + + if (atomic_test_and_set_bit(bt_dev.flags, BT_DEV_DISABLING)) + { + return -EALREADY; + } + + if (atomic_test_bit(bt_dev.flags, BT_DEV_ENABLING)) + { + atomic_clear_bit(bt_dev.flags, BT_DEV_DISABLING); + return -EAGAIN; + } + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_OPEN)) + { + atomic_clear_bit(bt_dev.flags, BT_DEV_DISABLING); + return -EALREADY; + } + + /* A driver without a close() op cannot be disabled. Find that out + * before anything is torn down, none of which can be undone. + */ + + if (!bt_hci_can_close(bt_dev.hci)) + { + atomic_clear_bit(bt_dev.flags, BT_DEV_DISABLING); + return -ENOSYS; + } + + /* Clear BT_DEV_READY before disabling HCI link. It is not set if + * bt_enable() failed after opening the transport, in which case a failed + * disable must not set it either. + */ + + was_ready = atomic_test_and_clear_bit(bt_dev.flags, BT_DEV_READY); + +#if defined(CONFIG_BT_BROADCASTER) + bt_adv_reset_adv_pool(); +#endif /* CONFIG_BT_BROADCASTER */ + +#if defined(CONFIG_BT_PRIVACY) + k_work_cancel_delayable(&bt_dev.rpa_update); +#endif /* CONFIG_BT_PRIVACY */ + +#if defined(CONFIG_BT_PER_ADV_SYNC) + bt_periodic_sync_disable(); +#endif /* CONFIG_BT_PER_ADV_SYNC */ + + /* Stop low-priority RX processing before resetting and closing the + * transport: new packets are no longer queued (see rx_teardown_active()), + * already-queued ones are discarded here, and an in-flight RX work item is + * waited for while the transport is still able to serve any HCI commands + * it may issue. High-priority (RECV_PRIO) events are unaffected, as the + * HCI Reset below relies on them. The workqueue itself is kept running, + * since delayable host work may remain scheduled across an enable/disable + * cycle. + */ + + buf = net_buf_slist_get(&bt_dev.rx_queue); + while (buf != NULL) + { + net_buf_unref(buf); + buf = net_buf_slist_get(&bt_dev.rx_queue); + } + + if (nxsched_gettid() == bt_workq.thread_id) + { + (void)k_work_cancel(&rx_work); + } + else + { + struct k_work_sync sync; + + (void)k_work_cancel_sync(&rx_work, &sync); + } + + /* Reset the Controller */ + + if (!drv_quirk_no_reset()) + { + err = bt_hci_cmd_send_sync(BT_HCI_OP_RESET, NULL, NULL); + if (err) + { + LOG_ERR("Failed to reset BLE controller"); + if (was_ready) + { + atomic_set_bit(bt_dev.flags, BT_DEV_READY); + } + + atomic_clear_bit(bt_dev.flags, BT_DEV_DISABLING); + return err; + } + + hci_reset_complete(); + } + + /* Tear down the connections only after the controller has been reset: + * until then it still owns the packets in flight and reports their + * completion, which must find the TX bookkeeping intact. What is left + * afterwards is completed by the host with an error. Neither the ISO nor + * the connection cleanup sends HCI commands. + */ + + if (IS_ENABLED(CONFIG_BT_ISO)) + { + bt_iso_reset(); + } + +#if defined(CONFIG_BT_CONN) + if (IS_ENABLED(CONFIG_BT_SMP)) + { + bt_pub_key_hci_disrupted(); + } + + bt_conn_cleanup_all(); + disconnected_handles_reset(); +#endif /* CONFIG_BT_CONN */ + + /* Mark the transport closed before purging the command queue: a + * command queued after this point is taken back by its sender (see + * bt_hci_cmd_send()), so nothing is left behind for the next enable. + */ + + atomic_clear_bit(bt_dev.flags, BT_DEV_OPEN); + hci_cmd_queue_purge(); + + err = bt_hci_close(bt_dev.hci); + if (err) + { + /* The transport is still open, but what has been torn down + * cannot be brought back: the connections are gone, and the controller + * has been reset unless the driver has the no-reset quirk. So the + * stack is left not ready, where all that can be done is to try the + * disable again. + */ + + atomic_set_bit(bt_dev.flags, BT_DEV_OPEN); + atomic_clear_bit(bt_dev.flags, BT_DEV_DISABLING); + return err; + } + + /* Some functions rely on checking this bitfield */ + + memset(bt_dev.supported_commands, 0x00, sizeof(bt_dev.supported_commands)); + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bt_settings_flush(); + } + + /* Reset IDs and corresponding keys. */ + + bt_dev.id_count = 0; +#if defined(CONFIG_BT_SMP) + bt_dev.le.rl_entries = 0; + bt_keys_reset(); +#endif + + /* If random address was set up - clear it */ + + bt_addr_copy(&bt_dev.random_addr, BT_ADDR_ANY); + + bt_monitor_send(BT_MONITOR_CLOSE_INDEX, NULL, 0); + + atomic_clear_bit(bt_dev.flags, BT_DEV_DISABLING); + + return 0; +} + +bool bt_is_ready(void) +{ + return atomic_test_bit(bt_dev.flags, BT_DEV_READY); +} + +#define DEVICE_NAME_LEN (sizeof(CONFIG_BT_DEVICE_NAME) - 1) +#if defined(CONFIG_BT_DEVICE_NAME_DYNAMIC) +BUILD_ASSERT(DEVICE_NAME_LEN < CONFIG_BT_DEVICE_NAME_MAX); +#else +BUILD_ASSERT(DEVICE_NAME_LEN < 248); +#endif + +int bt_set_name(const char *name) +{ +#if defined(CONFIG_BT_DEVICE_NAME_DYNAMIC) + size_t len = strlen(name); + int err; + + if (len > CONFIG_BT_DEVICE_NAME_MAX) + { + return -ENOMEM; + } + + if (!strcmp(bt_dev.name, name)) + { + return 0; + } + + if (IS_ENABLED(CONFIG_BT_CLASSIC) && + atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + err = bt_br_write_local_name(name); + if (err != 0) + { + LOG_ERR("Unable to set local name %d", err); + return err; + } + } + + memcpy(bt_dev.name, name, len); + bt_dev.name[len] = '\0'; + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + err = bt_settings_store_name(bt_dev.name, len); + if (err) + { + LOG_WRN("Unable to store name"); + } + } + + return 0; +#else + return -ENOMEM; +#endif +} + +const char *bt_get_name(void) +{ +#if defined(CONFIG_BT_DEVICE_NAME_DYNAMIC) + return bt_dev.name; +#else + return CONFIG_BT_DEVICE_NAME; +#endif +} + +uint16_t bt_get_appearance(void) +{ +#if defined(CONFIG_BT_DEVICE_APPEARANCE_DYNAMIC) + return bt_dev.appearance; +#else + return CONFIG_BT_DEVICE_APPEARANCE; +#endif +} + +#if defined(CONFIG_BT_DEVICE_APPEARANCE_DYNAMIC) +int bt_set_appearance(uint16_t appearance) +{ + if (bt_dev.appearance != appearance) + { + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + int err = bt_settings_store_appearance(&appearance, + sizeof(appearance)); + + if (err) + { + LOG_ERR("Unable to save setting 'bt/appearance' (err %d).", + err); + return err; + } + } + + bt_dev.appearance = appearance; + } + + return 0; +} +#endif + +int bt_le_get_local_features(struct bt_le_local_features *remote_info) +{ + if (remote_info == NULL) + { + return -EINVAL; + } + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + memcpy(remote_info->features, bt_dev.le.features, + sizeof(remote_info->features)); + remote_info->states = bt_dev.le.states; + remote_info->acl_mtu = COND_CODE_1(CONFIG_BT_CONN, (bt_dev.le.acl_mtu), + (0)); + remote_info->acl_pkts = COND_CODE_1(CONFIG_BT_CONN, + (CONFIG_BT_BUF_ACL_TX_COUNT), (0)); + remote_info->iso_mtu = COND_CODE_1(CONFIG_BT_ISO, (bt_dev.le.iso_mtu), + (0)); + remote_info->iso_pkts = COND_CODE_1(CONFIG_BT_ISO, (bt_dev.le.iso_limit), + (0)); + remote_info->rl_size = COND_CODE_1(CONFIG_BT_SMP, (bt_dev.le.rl_size), + (0)); + remote_info->max_adv_data_len = + COND_CODE_1(CONFIG_BT_BROADCASTER, (bt_dev.le.max_adv_data_len), + (0)); + + return 0; +} + +bool bt_le_bond_exists(uint8_t id, const bt_addr_le_t *addr) +{ + if (IS_ENABLED(CONFIG_BT_SMP)) + { + struct bt_keys *keys = bt_keys_find_addr(id, addr); + + /* if there are any keys stored then device is bonded */ + + return keys && keys->keys; + } + else + { + return false; + } +} + +#if defined(CONFIG_BT_FILTER_ACCEPT_LIST) +int bt_le_filter_accept_list_add(const bt_addr_le_t *addr) +{ + struct bt_hci_cp_le_add_dev_to_fal *cp; + struct net_buf *buf; + int err; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + bt_addr_le_copy(&cp->addr, addr); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_ADD_DEV_TO_FAL, buf, NULL); + if (err) + { + LOG_ERR("Failed to add device to filter accept list"); + + return err; + } + + return 0; +} + +int bt_le_filter_accept_list_remove(const bt_addr_le_t *addr) +{ + struct bt_hci_cp_le_rem_dev_from_fal *cp; + struct net_buf *buf; + int err; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + bt_addr_le_copy(&cp->addr, addr); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_REM_DEV_FROM_FAL, buf, NULL); + if (err) + { + LOG_ERR("Failed to remove device from filter accept list"); + return err; + } + + return 0; +} + +int bt_le_filter_accept_list_clear(void) +{ + int err; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_CLEAR_FAL, NULL, NULL); + if (err) + { + LOG_ERR("Failed to clear filter accept list"); + return err; + } + + return 0; +} +#endif /* defined(CONFIG_BT_FILTER_ACCEPT_LIST) */ + +int bt_le_set_chan_map(uint8_t chan_map[5]) +{ + struct bt_hci_cp_le_set_host_chan_classif *cp; + struct net_buf *buf; + + if (!(IS_ENABLED(CONFIG_BT_CENTRAL) || IS_ENABLED(CONFIG_BT_BROADCASTER))) + { + return -ENOTSUP; + } + + if (!BT_CMD_TEST(bt_dev.supported_commands, 27, 3)) + { + LOG_WRN("Set Host Channel Classification command is " + "not supported"); + return -ENOTSUP; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + + memcpy(&cp->ch_map[0], &chan_map[0], 4); + cp->ch_map[4] = chan_map[4] & BIT_MASK(5); + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_HOST_CHAN_CLASSIF, buf, NULL); +} + +#if defined(CONFIG_BT_RPA_TIMEOUT_DYNAMIC) +int bt_le_set_rpa_timeout(uint16_t new_rpa_timeout) +{ + if ((new_rpa_timeout == 0) || (new_rpa_timeout > 3600)) + { + return -EINVAL; + } + + if (new_rpa_timeout == bt_dev.rpa_timeout) + { + return 0; + } + + bt_dev.rpa_timeout = new_rpa_timeout; + atomic_set_bit(bt_dev.flags, BT_DEV_RPA_TIMEOUT_CHANGED); + + return 0; +} +#endif + +int bt_configure_data_path(uint8_t dir, + uint8_t id, uint8_t vs_config_len, + const uint8_t *vs_config) +{ + struct bt_hci_rp_configure_data_path *rp; + struct bt_hci_cp_configure_data_path *cp; + struct net_buf *rsp; + struct net_buf *buf; + int err; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->data_path_dir = dir; + cp->data_path_id = id; + cp->vs_config_len = vs_config_len; + if (vs_config_len) + { + (void)memcpy(cp->vs_config, vs_config, vs_config_len); + } + + err = bt_hci_cmd_send_sync(BT_HCI_OP_CONFIGURE_DATA_PATH, buf, &rsp); + if (err) + { + return err; + } + + rp = (void *)rsp->data; + if (rp->status) + { + err = -EIO; + } + + net_buf_unref(rsp); + + return err; +} + +/* Return `true` if a command was processed/sent */ + +static bool process_pending_cmd(clock_t timeout) +{ + if (!atomic_test_bit(bt_dev.flags, BT_DEV_OPEN)) + { + hci_cmd_queue_purge(); + return false; + } + + if (!k_fifo_is_empty(&bt_dev.cmd_tx_queue)) + { + if (nxsem_tickwait(&bt_dev.ncmd_sem, timeout) == 0) + { + hci_core_send_cmd(); + return true; + } + } + + return false; +} + +static void tx_processor(struct k_work *item) +{ + LOG_DBG("TX process start"); + + /* The whole TX processing pass (command and data processors) runs + * under the host lock, which replaces the historical reliance on + * cooperative scheduling (sched_lock). This serializes it against + * bt_conn_data_ready()/l2cap raise/cancel_data_ready() and the other + * host-lock sections. + * + * Ordering rule: no code may wait, while holding the host lock, on + * anything produced by the HCI prio path (ncmd_sem, command completion). + * All resource acquisitions on this path are 0. Note that the HCI driver's + * send() is called with the lock held; drivers that deliver events + * synchronously from send() (e.g. the native controller) re-enter + * bt_recv() on this thread, which is fine since the lock is recursive. + */ + + bt_dev_lock(); + + if (process_pending_cmd(0)) + { + /* If we processed a command, let the scheduler run before + * processing another command (or data). + */ + + bt_tx_irq_raise(); + goto exit; + } + + /* Hand over control to conn to process pending data */ + + if (IS_ENABLED(CONFIG_BT_CONN_TX)) + { + bt_conn_tx_processor(); + } + +exit: + bt_dev_unlock(); +} + +/* This work item shall never be cancelled. + */ + +static K_WORK_DEFINE(tx_work, tx_processor); + +#if defined(CONFIG_BT_TX_PROCESSOR_THREAD) +static uint64_t + bt_tx_processor_stack[CONFIG_BT_TX_PROCESSOR_STACK_SIZE / sizeof( + uint64_t)]; + +/* This work queue shall never be stopped, drained or plugged. + */ + +static struct k_work_q bt_tx_processor_workq; + +static int bt_tx_processor_init(void) +{ + struct k_work_queue_config cfg = + { + }; + + if (IS_ENABLED(CONFIG_THREAD_NAME)) + { + cfg.name = "bt_tx_processor"; + } + + k_work_queue_start(&bt_tx_processor_workq, bt_tx_processor_stack, + sizeof(bt_tx_processor_stack), + CONFIG_BT_TX_PROCESSOR_THREAD_PRIO, &cfg); + + return 0; +} + +/* Priority 999 is the last to run in POST_KERNEL. We don't actually + * care when it runs, so long as it's before APPLICATION, when `bt_enable()` + * can be called. Running it last will allow more urgent initializations + * competing for CPU time to complete first. + */ + +SYS_INIT(bt_tx_processor_init, POST_KERNEL, 999); +#endif /* CONFIG_BT_TX_PROCESSOR_THREAD */ + +/* This function shall not be called before init level APPLICATION. + */ + +void bt_tx_irq_raise(void) +{ + int __maybe_unused err; + + LOG_DBG("kick TX"); +#if defined(CONFIG_BT_TX_PROCESSOR_THREAD) + err = k_work_submit_to_queue(&bt_tx_processor_workq, &tx_work); + DEBUGASSERT(err >= 0); + /* Assertions: + * + * EBUSY shall not occur because `bt_tx_processor_workq` shall never be + * draining or plugged, and `tx_work` shall never be cancelled. + * + * EINVAL is not possible because taking address of variable cannot result + * in the null pointer. + * + * ENODEV shall not occur because `bt_tx_processor_workq` shall never be + * stopped, is started before init level APPLICATION, and this function + * shall not be called before init level APPLICATION. + * + * The above is an exhaustive list of the API errors. + * + * Defensive coding: If any error occurs and asserts are disabled, the + * program will recover if bt_tx_irq_raise is called again and is + * successful. No cleanup is needed. + */ +#else + k_work_submit(&tx_work); +#endif +} diff --git a/libs/zblue/host/hci_core.h b/libs/zblue/host/hci_core.h new file mode 100644 index 0000000000000..d68f69c05582e --- /dev/null +++ b/libs/zblue/host/hci_core.h @@ -0,0 +1,795 @@ +/**************************************************************************** + * libs/zblue/host/hci_core.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* hci_core.h - Bluetooth HCI core access */ + +/* Copyright (c) 2021-2025 Nordic Semiconductor ASA + * Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/* LL connection parameters */ +#define LE_CONN_LATENCY 0x0000 +#define LE_CONN_TIMEOUT 0x002a + +#if defined(CONFIG_BT_CLASSIC) +#define LMP_FEAT_PAGES_COUNT 3 +#else +#define LMP_FEAT_PAGES_COUNT 1 +#endif + +/* SCO settings */ +#define BT_VOICE_CVSD_16BIT 0x0060 + +/* bt_dev flags: the flags defined here represent BT controller state */ + +enum +{ + BT_DEV_ENABLING, /* Host stack is being enabled */ + BT_DEV_DISABLING, /* Host stack is being disabled */ + BT_DEV_OPEN, /* HCI transport is open */ + BT_DEV_READY, /* Host stack has completed init */ + BT_DEV_PRESET_ID, + BT_DEV_HAS_PUB_KEY, + + /** The application either explicitly or implicitly instructed the stack to + * scan for advertisers. + * + * Examples of such cases + * - Explicit scanning, @ref BT_LE_SCAN_USER_EXPLICIT_SCAN. + * - The application instructed the stack to automatically connect if a + * given device is detected. + * - The application wants to connect to a peer device using private + * addresses, but the controller resolving list is too small. The host + * will fallback to using host-based privacy and first scan for the device + * before it initiates a connection. + * - The application wants to synchronize to a periodic advertiser. + * The host will implicitly start scanning if it is not already doing so. + * + * The host needs to keep track of this state to ensure it can restart + * scanning when a connection is established/lost, explicit scanning is + * started or stopped etc. Also, when the scanner and advertiser share the + * same identity, the scanner may need to be restarted upon RPA refresh. + */ + + BT_DEV_SCANNING, + +/* Scanner is configured with a timeout. + */ + + BT_DEV_SCAN_LIMITED, + + BT_DEV_INITIATING, + + BT_DEV_RPA_VALID, + BT_DEV_RPA_TIMEOUT_CHANGED, + + BT_DEV_ID_PENDING, + BT_DEV_STORE_ID, + +#if defined(CONFIG_BT_CLASSIC) + BT_DEV_ISCAN, + BT_DEV_PSCAN, + BT_DEV_INQUIRY, + BT_DEV_LIMITED_DISCOVERABLE_MODE, +#endif /* CONFIG_BT_CLASSIC */ + + /* Total number of flags - must be at the end of the enum */ + + BT_DEV_NUM_FLAGS, +}; + +/* Flags which should not be cleared upon HCI_Reset */ +#define BT_DEV_PERSISTENT_FLAGS \ + (BIT(BT_DEV_ENABLING) | BIT(BT_DEV_DISABLING) | BIT(BT_DEV_OPEN) | BIT(BT_DEV_PRESET_ID)) + +#if defined(CONFIG_BT_EXT_ADV_LEGACY_SUPPORT) +/* Check the feature bit for extended or legacy advertising commands */ +#define BT_DEV_FEAT_LE_EXT_ADV(feat) BT_FEAT_LE_EXT_ADV(feat) +#else +/* Always use extended advertising commands. */ +#define BT_DEV_FEAT_LE_EXT_ADV(feat) 1 +#endif + +enum +{ + /* Advertising set has been created in the host. */ + + BT_ADV_CREATED, + /* Advertising parameters has been set in the controller. + * This implies that the advertising set has been created in the + * controller. + */ + + BT_ADV_PARAMS_SET, + + /* Advertising data has been set in the controller. */ + + BT_ADV_DATA_SET, + /* Advertising random address has been updated in the controller before + * enabling advertising. + */ + + BT_ADV_RANDOM_ADDR_UPDATED, + + /* Advertising random address pending to be set in the controller. */ + + BT_ADV_RANDOM_ADDR_PENDING, + /* The private random address of the advertiser is valid for this cycle + * of the RPA timeout. + */ + + BT_ADV_RPA_VALID, + + /* The private random address of the advertiser is being updated. */ + + BT_ADV_RPA_UPDATE, + /* The advertiser set is limited by a timeout, or number of advertising + * events, or both. + */ + + BT_ADV_LIMITED, + + /* Advertiser set is currently advertising in the controller. */ + + BT_ADV_ENABLED, + + /* Advertiser set is connectable */ + + BT_ADV_CONNECTABLE, + + /* Advertiser set is scannable */ + + BT_ADV_SCANNABLE, + + /* Advertiser set is using extended advertising */ + + BT_ADV_EXT_ADV, + /* Advertiser set has disabled the use of private addresses and is using + * the identity address instead. + */ + + BT_ADV_USE_IDENTITY, + + /* Advertiser has been temporarily disabled. */ + + BT_ADV_PAUSED, + + /* Periodic Advertising has been enabled in the controller. */ + + BT_PER_ADV_ENABLED, + + /* Periodic Advertising parameters has been set in the controller. */ + + BT_PER_ADV_PARAMS_SET, + + /* Periodic Advertising to include AdvDataInfo (ADI) */ + + BT_PER_ADV_INCLUDE_ADI, + /* Constant Tone Extension parameters for Periodic Advertising + * has been set in the controller. + */ + + BT_PER_ADV_CTE_PARAMS_SET, + /* Constant Tone Extension for Periodic Advertising has been enabled + * in the controller. + */ + + BT_PER_ADV_CTE_ENABLED, + + BT_ADV_NUM_FLAGS, +}; + +#if defined(CONFIG_BT_PER_ADV_RSP_REASSEMBLY) +/* Maximum size of a reassembled PAwR response for a single response slot. + * An AUX_SYNC_SUBEVENT_RSP PDU cannot carry an AuxPtr (Core 6.3 Vol 6 Part + * B, Table 2.9), so a response is at most 254 octets, which matches the + * 0-254 range of Response_Data_Length in LE Set Periodic Advertising + * Response Data (Core 6.3 Vol 4 Part E, 7.8.126). + */ +#define BT_PER_ADV_RSP_REASSEMBLY_BUF_SIZE 254 + +/* Reassembly state for fragmented periodic advertising response reports */ + +struct pawr_rsp_reassembly +{ + /* Buffer used to reassemble the fragmented response data */ + + struct net_buf_simple buf; + + /* Backing storage for the reassembly buffer */ + + uint8_t reassembly_data[BT_PER_ADV_RSP_REASSEMBLY_BUF_SIZE]; + + /* Subevent of the response being reassembled */ + + uint8_t subevent; + + /* Response slot of the response being reassembled */ + + uint8_t response_slot; + + /* True if the current response chain overflowed the reassembly buffer. + * The remaining fragments, up to and including the terminating COMPLETE + * fragment, are dropped instead of being reported to the application. + */ + + bool report_truncated; +}; +#endif /* CONFIG_BT_PER_ADV_RSP_REASSEMBLY */ + +struct bt_le_ext_adv +{ + /* ID Address used for advertising */ + + uint8_t id; + + /* Advertising handle */ + + uint8_t handle; + +#if defined(CONFIG_BT_EXT_ADV) + /* TX Power in use by the controller */ + + int8_t tx_power; + + /* Advertising Set ID */ + + uint8_t sid; + + /* Callbacks for the advertising set */ + + const struct bt_le_ext_adv_cb *cb; +#endif /* defined(CONFIG_BT_EXT_ADV) */ + +#if defined(CONFIG_BT_PER_ADV_RSP_REASSEMBLY) + /* Reassembly state for fragmented periodic advertising response reports */ + + struct pawr_rsp_reassembly pawr_rsp_reassembly; +#endif /* CONFIG_BT_PER_ADV_RSP_REASSEMBLY */ + + /* Address this set advertises with. Updated by + * bt_id_set_adv_random_addr() when a per-set random address is programmed, + * and by bt_id_save_adv_addr() for the address types that are not + * programmed per-set. bt_le_ext_adv_get_info() hands out a pointer to it. + */ + + bt_addr_le_t adv_addr; + + /* Current target address */ + + bt_addr_le_t target_addr; + + atomic_define(flags, BT_ADV_NUM_FLAGS); + + struct k_work_delayable lim_adv_timeout_work; + + /** The options used to set the parameters for this advertising set @ref + * bt_le_adv_param + */ + + uint32_t options; +}; + +enum +{ + /** Periodic Advertising Sync has been created in the host. */ + + BT_PER_ADV_SYNC_CREATED, + + /** Periodic Advertising Sync is established and can be terminated */ + + BT_PER_ADV_SYNC_SYNCED, + + /** Periodic Advertising Sync is attempting to create sync */ + + BT_PER_ADV_SYNC_SYNCING, + + /** Periodic Advertising Sync is attempting to create sync using Advertiser + * List + */ + + BT_PER_ADV_SYNC_SYNCING_USE_LIST, + + /** Periodic Advertising Sync established with reporting disabled */ + + BT_PER_ADV_SYNC_RECV_DISABLED, + + /** Constant Tone Extension for Periodic Advertising has been enabled in + * the Controller. + */ + + BT_PER_ADV_SYNC_CTE_ENABLED, + + BT_PER_ADV_SYNC_NUM_FLAGS, +}; + +struct bt_le_per_adv_sync +{ + /** Periodic Advertiser Address */ + + bt_addr_le_t addr; + + /** Advertiser SID */ + + uint8_t sid; + + /** Sync handle */ + + uint16_t handle; + + /** Periodic advertising interval (N * 1.25 ms) */ + + uint16_t interval; + + /** Periodic advertising advertiser clock accuracy (ppm) */ + + uint16_t clock_accuracy; + + /** Advertiser PHY */ + + uint8_t phy; + +#if defined(CONFIG_BT_DF_CONNECTIONLESS_CTE_RX) + /* @brief Bitfield with allowed CTE types. + * + * Allowed values are defined by @ref bt_df_cte_type, except + * BT_DF_CTE_TYPE_NONE. + */ + + uint8_t cte_types; +#endif /* CONFIG_BT_DF_CONNECTIONLESS_CTE_RX */ + +#if CONFIG_BT_PER_ADV_SYNC_BUF_SIZE > 0 + /** Reassembly buffer for advertising reports */ + + struct net_buf_simple reassembly; + + /** Storage for the reassembly buffer */ + + uint8_t reassembly_data[CONFIG_BT_PER_ADV_SYNC_BUF_SIZE]; +#endif /* CONFIG_BT_PER_ADV_SYNC_BUF_SIZE > 0 */ + + /** True if the following periodic adv reports up to and including the next + * complete one should be dropped + */ + + bool report_truncated; + + /** Flags */ + + atomic_define(flags, BT_PER_ADV_SYNC_NUM_FLAGS); + +#if defined(CONFIG_BT_PER_ADV_SYNC_RSP) + /** Number of subevents */ + + uint8_t num_subevents; + + /** Subevent interval (N * 1.25ms) */ + + uint8_t subevent_interval; + + /** Response slot delay (N * 1.25ms) */ + + uint8_t response_slot_delay; + + /** Response slot spacing (N * 1.25ms) */ + + uint8_t response_slot_spacing; +#endif /* CONFIG_BT_PER_ADV_SYNC_RSP */ +}; + +struct bt_dev_le +{ + /* LE features */ + + uint8_t features[BT_LE_LOCAL_SUPPORTED_FEATURES_SIZE]; + + /* LE states */ + + uint64_t states; + +#if defined(CONFIG_BT_CONN) + /* Controller buffer information */ + + uint16_t acl_mtu; + sem_t acl_pkts; +#endif /* CONFIG_BT_CONN */ +#if defined(CONFIG_BT_ISO) + uint16_t iso_mtu; + uint8_t iso_limit; + sem_t iso_pkts; +#endif /* CONFIG_BT_ISO */ +#if defined(CONFIG_BT_BROADCASTER) + uint16_t max_adv_data_len; +#endif /* CONFIG_BT_BROADCASTER */ + +#if defined(CONFIG_BT_SMP) + /* Size of the controller resolving list */ + + uint8_t rl_size; + /* Number of entries in the resolving list. rl_entries > rl_size + * means that host-side resolving is used. + */ + + uint8_t rl_entries; +#endif /* CONFIG_BT_SMP */ + /* List of `struct bt_conn` that have either pending data to send, or + * something to process (e.g. a disconnection event). + * + * Each element in this list contains a reference to its `conn` object. + */ + struct list_node conn_ready; +}; + +struct bt_dev_br +{ + /* Max controller's acceptable ACL packet length */ + + uint16_t mtu; + sem_t pkts; + uint16_t esco_pkt_type; +}; + +/* The theoretical max for these is 8 and 64, but there's no point + * in allocating the full memory if we only support a small subset. These + * values must be updated whenever the host implementation is extended beyond + * the current values. + */ +#define BT_DEV_VS_FEAT_MAX 1 +#define BT_DEV_VS_CMDS_MAX 2 + +/* State tracking for the local Bluetooth controller */ + +struct bt_dev +{ + /* Local Identity Address(es) */ + + bt_addr_le_t id_addr[CONFIG_BT_ID_MAX]; + uint8_t id_count; + + struct bt_conn_le_create_param create_param; + +#if !defined(CONFIG_BT_EXT_ADV) + /* Legacy advertiser */ + + struct bt_le_ext_adv adv; +#else + /* Pointer to reserved advertising set */ + + struct bt_le_ext_adv *adv; +#if defined(CONFIG_BT_CONN) && (CONFIG_BT_EXT_ADV_MAX_ADV_SET > 1) + /* When supporting multiple concurrent connectable advertising sets, + * we need to know the identity of the terminating advertising set to + * identify the connection object. If multiple sets share an identity, we + * also need to know whether the terminating set is directed or undirected + * to select the corresponding connection reservation. The advertising set + * is identified by its advertising handle, which is part of the LE + * Advertising Set Terminated event which is always sent _after_ the LE + * Enhanced Connection Complete event. Therefore we need to cache this + * event until its advertising set is known. + */ + + struct + { + bool valid; + struct bt_hci_evt_le_enh_conn_complete evt; + } + cached_conn_complete[MIN(CONFIG_BT_MAX_CONN, + CONFIG_BT_EXT_ADV_MAX_ADV_SET)]; +#endif +#endif + /* Current local Random Address */ + + bt_addr_t random_addr; + uint8_t adv_conn_id; + + /* Controller version & manufacturer information */ + + uint8_t hci_version; + uint8_t lmp_version; + uint16_t hci_revision; + uint16_t lmp_subversion; + uint16_t manufacturer; + + /* LMP features (pages 0, 1, 2) */ + + uint8_t features[LMP_FEAT_PAGES_COUNT][8]; + + /* Supported commands */ + + uint8_t supported_commands[64]; + +#if defined(CONFIG_BT_HCI_VS) + /* Vendor HCI support */ + + uint8_t vs_commands[BT_DEV_VS_CMDS_MAX]; +#endif + + struct k_work init; + + atomic_define(flags, BT_DEV_NUM_FLAGS); + + /* LE controller specific features */ + + struct bt_dev_le le; + +#if defined(CONFIG_BT_CLASSIC) + /* BR/EDR controller specific features */ + + struct bt_dev_br br; +#endif + + /* Number of commands controller can accept */ + + sem_t ncmd_sem; + + /* Last sent HCI command */ + + struct net_buf *sent_cmd; + + /* Queue for incoming HCI events & ACL data */ + + struct list_node rx_queue; + + /* Queue for outgoing HCI commands */ + + struct k_fifo cmd_tx_queue; + + const struct device *hci; + +#if defined(CONFIG_BT_PRIVACY) + /* Local Identity Resolving Key */ + + uint8_t irk[CONFIG_BT_ID_MAX][BT_IRK_SIZE]; + +#if defined(CONFIG_BT_RPA_SHARING) + /* Only 1 RPA per identity */ + + bt_addr_t rpa[CONFIG_BT_ID_MAX]; +#endif + + /* Work used for RPA rotation */ + + struct k_work_delayable rpa_update; + + /* The RPA timeout value. */ + + uint16_t rpa_timeout; +#endif + + /* Local Name */ +#if defined(CONFIG_BT_DEVICE_NAME_DYNAMIC) + char name[CONFIG_BT_DEVICE_NAME_MAX + 1]; +#endif +#if defined(CONFIG_BT_DEVICE_APPEARANCE_DYNAMIC) + /* Appearance Value */ + + uint16_t appearance; +#endif + + /* The host lock: serializes access to the per-controller host state + * between the contexts that mutate it. Statically initialized so that it + * is usable before bt_enable(), e.g. from bt_gatt_service_register(). + */ + + rmutex_t lock; +}; + +extern struct bt_dev bt_dev; + +/* Lock/unlock the host lock. The recursive mutex allows nested lock/unlock + * pairs on the same thread (needed e.g. for the bt_att_req_send() -> + * bt_att_chan_req_send() nesting). + */ + +static inline void bt_dev_lock(void) +{ + __maybe_unused int err = nxrmutex_lock(&bt_dev.lock); + + DEBUGASSERT(err == 0); +} + +static inline void bt_dev_unlock(void) +{ + __maybe_unused int err = nxrmutex_unlock(&bt_dev.lock); + + DEBUGASSERT(err == 0); +} + +/* Assert that the current thread holds the host lock. */ +#define BT_DEV_LOCK_ASSERT() DEBUGASSERT(nxrmutex_is_hold(&bt_dev.lock)) +extern const struct bt_conn_auth_cb *bt_auth; +extern struct list_node bt_auth_info_cbs; +enum bt_security_err bt_security_err_get(uint8_t hci_err); + +/* Submit work to the general purpose Bluetooth workqueue. + * + * This is used for the host's internal work items to avoid the shared system + * workqueue. The work runs in the RX thread context. + */ + +int bt_work_submit(struct k_work *work); +int bt_work_schedule(struct k_work_delayable *work, clock_t delay); +int bt_work_reschedule(struct k_work_delayable *work, clock_t delay); + +/* Data type to store state related with command to be updated + * when command completes successfully. + */ + +struct bt_hci_cmd_state_set +{ + /* Target memory to be updated */ + + atomic_t *target; + + /* Bit number to be updated in target memory */ + + int bit; + + /* Value to determine if enable or disable bit */ + + bool val; +}; + +/* Set command state related with the command buffer */ + +void bt_hci_cmd_state_set_init( + struct net_buf *buf, struct bt_hci_cmd_state_set *state, atomic_t *target, + int bit, bool val); + +int bt_hci_disconnect(uint16_t handle, uint8_t reason); + +bool bt_le_conn_params_valid(const struct bt_le_conn_param *param); +int bt_le_set_data_len(struct bt_conn *conn, + uint16_t tx_octets, uint16_t tx_time); +int bt_le_set_default_phy(uint8_t all_phys, + uint8_t pref_tx_phy, uint8_t pref_rx_phy); +int bt_le_set_phy( + struct bt_conn *conn, uint8_t all_phys, uint8_t pref_tx_phy, + uint8_t pref_rx_phy, uint8_t phy_opts); +uint8_t bt_get_phy(uint8_t hci_phy); +/** + * @brief Convert CTE type value from HCI format to @ref bt_df_cte_type + * format. + * + * @param hci_cte_type CTE type in an HCI format. + * + * @return CTE type (@ref bt_df_cte_type). + */ + +int bt_get_df_cte_type(uint8_t hci_cte_type); + +int bt_le_create_conn(const struct bt_conn *conn); +int bt_le_create_conn_cancel(void); +int bt_le_create_conn_synced( + const struct bt_conn *conn, const struct bt_le_ext_adv *adv, + uint8_t subevent); + +const bt_addr_le_t *bt_lookup_id_addr(uint8_t id, const bt_addr_le_t *addr); + +int bt_send(struct net_buf *buf); + +/* Don't require everyone to include keys.h */ + +struct bt_keys; +void bt_id_add(struct bt_keys *keys); +void bt_id_del(struct bt_keys *keys); + +struct bt_keys *bt_id_find_conflict(struct bt_keys *candidate); + +int bt_setup_random_id_addr(void); +int bt_setup_public_id_addr(void); + +void bt_finalize_init(int err); + +void bt_hci_host_num_completed_packets(struct net_buf *buf); + +/* HCI event handlers */ + +void bt_hci_pin_code_req(struct net_buf *buf); +void bt_hci_link_key_notify(struct net_buf *buf); +void bt_hci_link_key_req(struct net_buf *buf); +void bt_hci_io_capa_resp(struct net_buf *buf); +void bt_hci_io_capa_req(struct net_buf *buf); +void bt_hci_ssp_complete(struct net_buf *buf); +void bt_hci_user_confirm_req(struct net_buf *buf); +void bt_hci_user_passkey_notify(struct net_buf *buf); +void bt_hci_user_passkey_req(struct net_buf *buf); +void bt_hci_auth_complete(struct net_buf *buf); + +/* Common HCI event handlers */ + +void bt_hci_le_enh_conn_complete(struct bt_hci_evt_le_enh_conn_complete *evt, + const struct bt_le_ext_adv *ext_adv); + +/* Scan HCI event handlers */ + +void bt_hci_le_adv_report(struct net_buf *buf); +void bt_hci_le_direct_adv_report(struct net_buf *buf); +void bt_hci_le_scan_timeout(struct net_buf *buf); +void bt_hci_le_adv_ext_report(struct net_buf *buf); +void bt_hci_le_per_adv_sync_established(struct net_buf *buf); +void bt_hci_le_per_adv_sync_established_v2(struct net_buf *buf); +void bt_hci_le_per_adv_report(struct net_buf *buf); +void bt_hci_le_per_adv_report_v2(struct net_buf *buf); +void bt_hci_le_per_adv_sync_lost(struct net_buf *buf); +void bt_hci_le_biginfo_adv_report(struct net_buf *buf); +void bt_hci_le_df_connectionless_iq_report(struct net_buf *buf); +void bt_hci_le_vs_df_connectionless_iq_report(struct net_buf *buf); +void bt_hci_le_past_received(struct net_buf *buf); +void bt_hci_le_past_received_v2(struct net_buf *buf); + +/* CS HCI event handlers */ + +void bt_hci_le_cs_read_remote_supported_capabilities_complete( + struct net_buf *buf); +void bt_hci_le_cs_read_remote_supported_capabilities_complete_v2( + struct net_buf *buf); +void bt_hci_le_cs_read_remote_fae_table_complete(struct net_buf *buf); +void bt_hci_le_cs_config_complete_event(struct net_buf *buf); +void bt_hci_le_cs_security_enable_complete(struct net_buf *buf); +void bt_hci_le_cs_procedure_enable_complete(struct net_buf *buf); +void bt_hci_le_cs_subevent_result(struct net_buf *buf); +void bt_hci_le_cs_subevent_result_continue(struct net_buf *buf); +void bt_hci_le_cs_test_end_complete(struct net_buf *buf); + +/* Adv HCI event handlers */ + +void bt_hci_le_adv_set_terminated(struct net_buf *buf); +void bt_hci_le_scan_req_received(struct net_buf *buf); + +/* BR/EDR HCI event handlers */ + +void bt_hci_conn_req(struct net_buf *buf); +void bt_hci_conn_complete(struct net_buf *buf); + +void bt_hci_inquiry_complete(struct net_buf *buf); +void bt_hci_inquiry_result_with_rssi(struct net_buf *buf); +void bt_hci_extended_inquiry_result(struct net_buf *buf); +void bt_hci_remote_name_request_complete(struct net_buf *buf); + +void bt_hci_read_remote_features_complete(struct net_buf *buf); +void bt_hci_read_remote_ext_features_complete(struct net_buf *buf); +void bt_hci_role_change(struct net_buf *buf); +void bt_hci_conn_pkt_type_changed(struct net_buf *buf); +#if defined(CONFIG_BT_POWER_MODE_CONTROL) +void bt_hci_link_mode_change(struct net_buf *buf); +#endif /* CONFIG_BT_POWER_MODE_CONTROL */ +void bt_hci_synchronous_conn_complete(struct net_buf *buf); + +void bt_hci_le_df_connection_iq_report(struct net_buf *buf); +void bt_hci_le_vs_df_connection_iq_report(struct net_buf *buf); +void bt_hci_le_df_cte_req_failed(struct net_buf *buf); + +void bt_hci_le_per_adv_subevent_data_request(struct net_buf *buf); +void bt_hci_le_per_adv_response_report(struct net_buf *buf); + +int bt_hci_read_remote_version(struct bt_conn *conn); +int bt_hci_le_read_remote_features(struct bt_conn *conn); +int bt_hci_le_read_max_data_len(uint16_t *tx_octets, uint16_t *tx_time); + +bool bt_drv_quirk_no_auto_dle(void); + +void bt_tx_irq_raise(void); diff --git a/libs/zblue/host/hci_raw.c b/libs/zblue/host/hci_raw.c new file mode 100644 index 0000000000000..2736939f8f3fb --- /dev/null +++ b/libs/zblue/host/hci_raw.c @@ -0,0 +1,240 @@ +/**************************************************************************** + * libs/zblue/host/hci_raw.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* hci_userchan.c - HCI user channel Bluetooth handling */ + +/* Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/hci_common_internal.h" +#include "hci_raw_internal.h" +#include "monitor.h" + +#define LOG_LEVEL CONFIG_BT_HCI_CORE_LOG_LEVEL +LOG_MODULE_REGISTER(bt_hci_raw); + +static struct k_fifo *raw_rx; + +static bt_buf_rx_freed_cb_t buf_rx_freed_cb; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void hci_rx_buf_destroy(struct net_buf *buf) +{ + net_buf_destroy(buf); + + if (buf_rx_freed_cb) + { + /* bt_buf_get_rx is used for all types of RX buffers */ + + buf_rx_freed_cb(BT_BUF_EVT | BT_BUF_ACL_IN | BT_BUF_ISO_IN); + } +} + +NET_BUF_POOL_FIXED_DEFINE(hci_rx_pool, + BT_BUF_RX_COUNT, + BT_BUF_RX_SIZE, + 0, + hci_rx_buf_destroy); +NET_BUF_POOL_FIXED_DEFINE(hci_cmd_pool, BT_BUF_CMD_TX_COUNT, + BT_BUF_CMD_SIZE(CONFIG_BT_BUF_CMD_TX_SIZE), + 0, + NULL); +NET_BUF_POOL_FIXED_DEFINE(hci_acl_pool, CONFIG_BT_BUF_ACL_TX_COUNT, + BT_BUF_ACL_SIZE(CONFIG_BT_BUF_ACL_TX_SIZE), + 0, + NULL); +#if defined(CONFIG_BT_ISO) +NET_BUF_POOL_FIXED_DEFINE(hci_iso_pool, CONFIG_BT_ISO_TX_BUF_COUNT, + BT_ISO_SDU_BUF_SIZE(CONFIG_BT_ISO_TX_MTU), + 0, + NULL); +#endif /* CONFIG_BT_ISO */ + +#define BT_HCI_DEV NULL +#define BT_HCI_BUS 0 +#define BT_HCI_NAME "nuttx_bt_hci" + +struct bt_dev_raw bt_dev = +{ + .hci = BT_HCI_DEV, +}; + +struct net_buf *bt_buf_get_rx(enum bt_buf_type type, clock_t timeout) +{ + struct net_buf *buf; + + switch (type) + { + case BT_BUF_EVT: + case BT_BUF_ACL_IN: + case BT_BUF_ISO_IN: + break; + default: + LOG_ERR("Invalid rx type: %u", type); + return NULL; + } + + buf = net_buf_alloc(&hci_rx_pool, timeout); + if (!buf) + { + return buf; + } + + net_buf_add_u8(buf, bt_buf_type_to_h4(type)); + + return buf; +} + +void bt_buf_rx_freed_cb_set(bt_buf_rx_freed_cb_t cb) +{ + buf_rx_freed_cb = cb; +} + +struct net_buf *bt_buf_get_tx(enum bt_buf_type type, + clock_t timeout, const void *data, size_t size) +{ + struct net_buf_pool *pool; + struct net_buf *buf; + + switch (type) + { + case BT_BUF_CMD: + pool = &hci_cmd_pool; + break; + case BT_BUF_ACL_OUT: + pool = &hci_acl_pool; + break; +#if defined(CONFIG_BT_ISO) + case BT_BUF_ISO_OUT: + pool = &hci_iso_pool; + break; +#endif /* CONFIG_BT_ISO */ + default: + LOG_ERR("Invalid tx type: %u", type); + return NULL; + } + + buf = net_buf_alloc(pool, timeout); + if (!buf) + { + return buf; + } + + net_buf_add_u8(buf, bt_buf_type_to_h4(type)); + + if (data && size) + { + if (net_buf_tailroom(buf) < size) + { + net_buf_unref(buf); + return NULL; + } + + net_buf_add_mem(buf, data, size); + } + + return buf; +} + +struct net_buf *bt_buf_get_evt(uint8_t evt, + bool discardable, clock_t timeout) +{ + ARG_UNUSED(evt); + ARG_UNUSED(discardable); + + return bt_buf_get_rx(BT_BUF_EVT, timeout); +} + +static int bt_recv(const struct device *dev, struct net_buf *buf) +{ + uint8_t type = buf->data[0]; + + ARG_UNUSED(dev); + + LOG_DBG("buf %p len %u", buf, buf->len); + + bt_monitor_send(bt_monitor_opcode(type, BT_MONITOR_RX), buf->data + 1, + buf->len - 1); + + k_fifo_put(raw_rx, buf); + + return 0; +} + +int bt_send(struct net_buf *buf) +{ + if (buf->len == 0) + { + return BT_HCI_ERR_INVALID_PARAM; + } + + LOG_DBG("buf %p type %u len %u", buf, buf->data[0], buf->len); + + bt_monitor_send(bt_monitor_opcode(buf->data[0], BT_MONITOR_TX), + buf->data + 1, buf->len - 1); + + return bt_hci_send(bt_dev.hci, buf); +} + +int bt_enable_raw(struct k_fifo *rx_queue) +{ + int err; + + LOG_DBG(""); + + raw_rx = rx_queue; + + if (!device_is_ready(bt_dev.hci)) + { + LOG_ERR("HCI driver is not ready"); + return -ENODEV; + } + + bt_monitor_new_index(BT_MONITOR_TYPE_PRIMARY, BT_HCI_BUS, BT_ADDR_ANY, + BT_HCI_NAME); + + err = bt_hci_open(bt_dev.hci, bt_recv); + if (err) + { + LOG_ERR("HCI driver open failed (%d)", err); + return err; + } + + LOG_INF("Lower HCI transport: %s", BT_HCI_NAME); + LOG_INF("Bluetooth enabled in RAW mode"); + + return 0; +} diff --git a/libs/zblue/host/hci_raw_internal.h b/libs/zblue/host/hci_raw_internal.h new file mode 100644 index 0000000000000..6be5f954b8054 --- /dev/null +++ b/libs/zblue/host/hci_raw_internal.h @@ -0,0 +1,38 @@ +/**************************************************************************** + * libs/zblue/host/hci_raw_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef __BT_HCI_RAW_INTERNAL_H +#define __BT_HCI_RAW_INTERNAL_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct bt_dev_raw +{ + const struct device *hci; +}; + +extern struct bt_dev_raw bt_dev; + +#ifdef __cplusplus +} +#endif + +#endif /* __BT_HCI_RAW_INTERNAL_H */ diff --git a/libs/zblue/host/id.c b/libs/zblue/host/id.c new file mode 100644 index 0000000000000..945991428dd46 --- /dev/null +++ b/libs/zblue/host/id.c @@ -0,0 +1,2998 @@ +/**************************************************************************** + * libs/zblue/host/id.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017-2025 Nordic Semiconductor ASA + * Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "adv.h" +#include "common/bt_str.h" +#include "common/rpa.h" +#include "conn_internal.h" +#include "hci_core.h" +#include "id.h" +#include "keys.h" +#include "scan.h" +#include "settings.h" +#include "smp.h" + +#define LOG_LEVEL CONFIG_BT_HCI_CORE_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_id); + +struct bt_adv_id_check_data +{ + uint8_t id; + bool adv_enabled; +}; + +#if defined(CONFIG_BT_OBSERVER) || defined(CONFIG_BT_BROADCASTER) + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +const bt_addr_le_t *bt_lookup_id_addr(uint8_t id, const bt_addr_le_t *addr) +{ + if (id >= CONFIG_BT_ID_MAX || addr == NULL) + { + return NULL; + } + + if (IS_ENABLED(CONFIG_BT_SMP)) + { + struct bt_keys *keys; + + keys = bt_keys_find_irk(id, addr); + if (keys) + { + LOG_DBG("Identity %s matched RPA %s", bt_addr_le_str(&keys->addr), + bt_addr_le_str(addr)); + return &keys->addr; + } + } + + return addr; +} +#endif /* CONFIG_BT_OBSERVER || CONFIG_BT_CONN */ + +static void adv_id_check_func(struct bt_le_ext_adv *adv, void *data) +{ + struct bt_adv_id_check_data *check_data = data; + + if (IS_ENABLED(CONFIG_BT_EXT_ADV)) + { + /* Only check if the ID is in use, as the advertiser can be + * started and stopped without reconfiguring parameters. + */ + + if (check_data->id == adv->id) + { + check_data->adv_enabled = true; + } + } + else + { + if (check_data->id == adv->id && + atomic_test_bit(adv->flags, BT_ADV_ENABLED)) + { + check_data->adv_enabled = true; + } + } +} + +static void adv_is_private_enabled(struct bt_le_ext_adv *adv, void *data) +{ + bool *adv_enabled = data; + + if (atomic_test_bit(adv->flags, BT_ADV_ENABLED) && + !atomic_test_bit(adv->flags, BT_ADV_USE_IDENTITY)) + { + *adv_enabled = true; + } +} + +#if defined(CONFIG_BT_SMP) +static void adv_is_limited_enabled(struct bt_le_ext_adv *adv, void *data) +{ + bool *adv_enabled = data; + + if (atomic_test_bit(adv->flags, BT_ADV_ENABLED) && + atomic_test_bit(adv->flags, BT_ADV_LIMITED)) + { + *adv_enabled = true; + } +} + +static void adv_pause_enabled(struct bt_le_ext_adv *adv, void *data) +{ + if (atomic_test_bit(adv->flags, BT_ADV_ENABLED)) + { + atomic_set_bit(adv->flags, BT_ADV_PAUSED); + bt_le_adv_set_enable(adv, false); + } +} + +static void adv_unpause_enabled(struct bt_le_ext_adv *adv, void *data) +{ + if (atomic_test_and_clear_bit(adv->flags, BT_ADV_PAUSED)) + { + bt_le_adv_set_enable(adv, true); + } +} +#endif /* defined(CONFIG_BT_SMP) */ + +static int set_random_address(const bt_addr_t *addr) +{ + struct net_buf *buf; + int err; + + LOG_DBG("%s", bt_addr_str(addr)); + + /* Do nothing if we already have the right address */ + + if (bt_addr_eq(addr, &bt_dev.random_addr)) + { + return 0; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + net_buf_add_mem(buf, addr, sizeof(*addr)); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_RANDOM_ADDRESS, buf, NULL); + if (err) + { + if (err == -EACCES) + { + /* If we are here we probably tried to set a random + * address while a legacy advertising, scanning or initiating is + * enabled, this is illegal. + * + * See Core Spec @ Vol 4, Part E 7.8.4 + */ + + LOG_WRN("cmd disallowed"); + } + + return err; + } + + bt_addr_copy(&bt_dev.random_addr, addr); + return 0; +} + +int bt_id_set_adv_random_addr(struct bt_le_ext_adv *adv, + const bt_addr_t *addr) +{ + struct bt_hci_cp_le_set_adv_set_random_addr *cp; + struct net_buf *buf; + int err; + + if (adv == NULL || addr == NULL) + { + return -EINVAL; + } + + if (!(IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features))) + { + err = set_random_address(addr); + if (err != 0) + { + return err; + } + + bt_id_save_adv_addr(adv, BT_HCI_OWN_ADDR_RANDOM); + + return 0; + } + + LOG_DBG("%s", bt_addr_str(addr)); + + if (!atomic_test_bit(adv->flags, BT_ADV_PARAMS_SET)) + { + bt_addr_le_copy_addr(&adv->adv_addr, addr, BT_ADDR_LE_RANDOM); + atomic_set_bit(adv->flags, BT_ADV_RANDOM_ADDR_PENDING); + return 0; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + + cp->handle = adv->handle; + bt_addr_copy(&cp->bdaddr, addr); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_ADV_SET_RANDOM_ADDR, buf, + NULL); + if (err) + { + return err; + } + + if (&adv->adv_addr.a != addr) + { + bt_addr_le_copy_addr(&adv->adv_addr, addr, BT_ADDR_LE_RANDOM); + } + + return 0; +} + +/* If rpa sharing is enabled, then rpa expired cb of adv-sets belonging + * to same id is verified to return true. If not, adv-sets will continue + * with old rpa through out the rpa rotations. + */ + +static void adv_rpa_expired(struct bt_le_ext_adv *adv, void *data) +{ + bool rpa_invalid = true; +#if defined(CONFIG_BT_EXT_ADV) && defined(CONFIG_BT_PRIVACY) + /* Notify the user about the RPA timeout and set the RPA validity. */ + + if (atomic_test_bit(adv->flags, BT_ADV_RPA_VALID) && adv->cb && + adv->cb->rpa_expired) + { + rpa_invalid = adv->cb->rpa_expired(adv); + } +#endif + + if (IS_ENABLED(CONFIG_BT_RPA_SHARING)) + { + if (adv->id >= bt_dev.id_count) + { + return; + } + + bool *rpa_invalid_set_ptr = data; + + if (!rpa_invalid) + { + rpa_invalid_set_ptr[adv->id] = false; + } + } + else + { + if (rpa_invalid) + { + atomic_clear_bit(adv->flags, BT_ADV_RPA_VALID); + } + } +} + +static void adv_rpa_invalidate(struct bt_le_ext_adv *adv, void *data) +{ + /* RPA of Advertisers limited by timeout or number of packets only expire + * when they are stopped. + */ + + if (!atomic_test_bit(adv->flags, BT_ADV_LIMITED) && + !atomic_test_bit(adv->flags, BT_ADV_USE_IDENTITY)) + { + adv_rpa_expired(adv, data); + } +} + +#if defined(CONFIG_BT_RPA_SHARING) +static void adv_rpa_clear_data(struct bt_le_ext_adv *adv, void *data) +{ + if (adv->id >= bt_dev.id_count) + { + return; + } + + bool *rpa_invalid_set_ptr = data; + + if (rpa_invalid_set_ptr[adv->id]) + { + atomic_clear_bit(adv->flags, BT_ADV_RPA_VALID); + bt_addr_copy(&bt_dev.rpa[adv->id], BT_ADDR_NONE); + } + else + { + LOG_WRN("Adv sets rpa expired cb with id %d returns false", adv->id); + } +} +#endif + +static void le_rpa_invalidate(void) +{ + /* Invalidate RPA */ + + if (!(IS_ENABLED(CONFIG_BT_EXT_ADV) && + atomic_test_bit(bt_dev.flags, BT_DEV_SCAN_LIMITED))) + { + atomic_clear_bit(bt_dev.flags, BT_DEV_RPA_VALID); + } + + if (IS_ENABLED(CONFIG_BT_BROADCASTER)) + { + if (bt_dev.id_count == 0) + { + return; + } + + bool rpa_expired_data[bt_dev.id_count]; + + for (uint8_t i = 0; i < bt_dev.id_count; i++) + { + rpa_expired_data[i] = true; + } + + bt_le_ext_adv_foreach(adv_rpa_invalidate, &rpa_expired_data); +#if defined(CONFIG_BT_RPA_SHARING) + /* rpa_expired data collected. now clear data based on data collected. + */ + + bt_le_ext_adv_foreach(adv_rpa_clear_data, &rpa_expired_data); +#endif + } +} + +#if defined(CONFIG_BT_PRIVACY) + +#if defined(CONFIG_BT_RPA_TIMEOUT_DYNAMIC) +static void le_rpa_timeout_update(void) +{ + int err = 0; + + if (atomic_test_and_clear_bit(bt_dev.flags, BT_DEV_RPA_TIMEOUT_CHANGED)) + { + struct net_buf *buf; + struct bt_hci_cp_le_set_rpa_timeout *cp; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + LOG_ERR("Failed to create HCI RPA timeout command"); + err = -ENOBUFS; + goto submit; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->rpa_timeout = sys_cpu_to_le16(bt_dev.rpa_timeout); + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_RPA_TIMEOUT, buf, NULL); + if (err) + { + LOG_ERR("Failed to send HCI RPA timeout command"); + goto submit; + } + } + +submit: + if (err) + { + atomic_set_bit(bt_dev.flags, BT_DEV_RPA_TIMEOUT_CHANGED); + } +} +#endif + +static void le_rpa_timeout_submit(void) +{ +#if defined(CONFIG_BT_RPA_TIMEOUT_DYNAMIC) + le_rpa_timeout_update(); +#endif + + (void)bt_work_schedule(&bt_dev.rpa_update, SEC2TICK(bt_dev.rpa_timeout)); +} + +/* this function sets new RPA only if current one is no longer valid */ + +int bt_id_set_private_addr(uint8_t id) +{ + bt_addr_t rpa; + int err; + + if (id >= CONFIG_BT_ID_MAX) + { + return -EINVAL; + } + + /* check if RPA is valid */ + + if (atomic_test_bit(bt_dev.flags, BT_DEV_RPA_VALID)) + { + return 0; + } + + err = bt_rpa_create(bt_dev.irk[id], &rpa); + if (!err) + { + err = set_random_address(&rpa); + if (!err) + { + atomic_set_bit(bt_dev.flags, BT_DEV_RPA_VALID); + } + } + + le_rpa_timeout_submit(); + + if (err) + { + return err; + } + + if (IS_ENABLED(CONFIG_BT_LOG_SNIFFER_INFO)) + { + LOG_INF("RPA: %s", bt_addr_str(&rpa)); + } + + return 0; +} + +#if defined(CONFIG_BT_RPA_SHARING) +static int adv_rpa_get(struct bt_le_ext_adv *adv, bt_addr_t *rpa) +{ + int err; + + if (bt_addr_eq(&bt_dev.rpa[adv->id], BT_ADDR_NONE)) + { + err = bt_rpa_create(bt_dev.irk[adv->id], &bt_dev.rpa[adv->id]); + if (err) + { + return err; + } + } + + bt_addr_copy(rpa, &bt_dev.rpa[adv->id]); + + return 0; +} +#else +static int adv_rpa_get(struct bt_le_ext_adv *adv, bt_addr_t *rpa) +{ + int err; + + err = bt_rpa_create(bt_dev.irk[adv->id], rpa); + if (err) + { + return err; + } + + return 0; +} +#endif /* defined(CONFIG_BT_RPA_SHARING) */ + +int bt_id_set_adv_private_addr(struct bt_le_ext_adv *adv) +{ + bt_addr_t rpa; + int err; + + if (adv == NULL) + { + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_PRIVACY) && + (adv->options & BT_LE_ADV_OPT_USE_NRPA)) + { + bt_addr_le_t addr; + + err = bt_addr_le_create_nrpa(&addr); + if (err != 0) + { + return err; + } + + err = bt_id_set_adv_random_addr(adv, &addr.a); + if (err == 0 && !atomic_test_bit(adv->flags, BT_ADV_LIMITED)) + { + le_rpa_timeout_submit(); + } + + return err; + } + + if (!(IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features))) + { + return bt_id_set_private_addr(adv->id); + } + + /* check if RPA is valid */ + + if (atomic_test_bit(adv->flags, BT_ADV_RPA_VALID)) + { + /* Schedule the RPA timer if it is not running. + * The RPA may be valid without the timer running. + */ + + if (!atomic_test_bit(adv->flags, BT_ADV_LIMITED)) + { + le_rpa_timeout_submit(); + } + + return 0; + } + + if (adv == bt_le_adv_lookup_legacy() && adv->id == BT_ID_DEFAULT) + { + /* Make sure that a Legacy advertiser using default ID has same + * RPA address as scanner roles. + */ + + err = bt_id_set_private_addr(BT_ID_DEFAULT); + if (err) + { + return err; + } + + err = bt_id_set_adv_random_addr(adv, &bt_dev.random_addr); + if (!err) + { + atomic_set_bit(adv->flags, BT_ADV_RPA_VALID); + } + + return 0; + } + + err = adv_rpa_get(adv, &rpa); + if (!err) + { + err = bt_id_set_adv_random_addr(adv, &rpa); + if (!err) + { + atomic_set_bit(adv->flags, BT_ADV_RPA_VALID); + } + } + + if (!atomic_test_bit(adv->flags, BT_ADV_LIMITED)) + { + le_rpa_timeout_submit(); + } + + if (err) + { + return err; + } + + if (IS_ENABLED(CONFIG_BT_LOG_SNIFFER_INFO)) + { + LOG_INF("RPA: %s", bt_addr_str(&rpa)); + } + + return 0; +} +#else +int bt_id_set_private_addr(uint8_t id) +{ + bt_addr_t nrpa; + int err; + + if (id >= CONFIG_BT_ID_MAX) + { + return -EINVAL; + } + + err = bt_rand(nrpa.val, sizeof(nrpa.val)); + if (err) + { + return err; + } + + BT_ADDR_SET_NRPA(&nrpa); + + err = set_random_address(&nrpa); + if (err) + { + return err; + } + + if (IS_ENABLED(CONFIG_BT_LOG_SNIFFER_INFO)) + { + LOG_INF("NRPA: %s", bt_addr_str(&nrpa)); + } + + return 0; +} + +int bt_id_set_adv_private_addr(struct bt_le_ext_adv *adv) +{ + bt_addr_t nrpa; + int err; + + if (adv == NULL) + { + return -EINVAL; + } + + err = bt_rand(nrpa.val, sizeof(nrpa.val)); + if (err) + { + return err; + } + + BT_ADDR_SET_NRPA(&nrpa); + + err = bt_id_set_adv_random_addr(adv, &nrpa); + if (err) + { + return err; + } + + if (IS_ENABLED(CONFIG_BT_LOG_SNIFFER_INFO)) + { + LOG_INF("NRPA: %s", bt_addr_str(&nrpa)); + } + + return 0; +} +#endif /* defined(CONFIG_BT_PRIVACY) */ + +static void adv_pause_rpa(struct bt_le_ext_adv *adv, void *data) +{ + bool *adv_enabled = data; + + /* Disable advertising sets to prepare them for RPA update. */ + + if (atomic_test_bit(adv->flags, BT_ADV_ENABLED) && + !atomic_test_bit(adv->flags, BT_ADV_LIMITED) && + !atomic_test_bit(adv->flags, BT_ADV_USE_IDENTITY)) + { + int err; + + err = bt_le_adv_set_enable_ext(adv, false, NULL); + if (err) + { + LOG_ERR("Failed to disable advertising (err %d)", err); + } + + atomic_set_bit(adv->flags, BT_ADV_RPA_UPDATE); + *adv_enabled = true; + } +} + +static bool le_adv_rpa_timeout(void) +{ + bool adv_enabled = false; + + if (IS_ENABLED(CONFIG_BT_BROADCASTER)) + { + if (IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features)) + { + /* Pause all advertising sets using RPAs */ + + bt_le_ext_adv_foreach(adv_pause_rpa, &adv_enabled); + } + else + { + /* Check if advertising set is enabled */ + + bt_le_ext_adv_foreach(adv_is_private_enabled, &adv_enabled); + } + } + + return adv_enabled; +} + +static void adv_enable_rpa(struct bt_le_ext_adv *adv, void *data) +{ + if (atomic_test_and_clear_bit(adv->flags, BT_ADV_RPA_UPDATE)) + { + int err; + + err = bt_id_set_adv_private_addr(adv); + if (err) + { + LOG_WRN("Failed to update advertiser RPA address (%d)", err); + } + + err = bt_le_adv_set_enable_ext(adv, true, NULL); + if (err) + { + LOG_ERR("Failed to enable advertising (err %d)", err); + } + } +} + +static void le_update_private_addr(void) +{ + struct bt_le_ext_adv *adv = NULL; + bool adv_enabled = false; + uint8_t id = BT_ID_DEFAULT; + int err; + +#if defined(CONFIG_BT_OBSERVER) + bool scan_enabled = false; + + if (atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING) && + !(IS_ENABLED(CONFIG_BT_EXT_ADV) && atomic_test_bit(bt_dev.flags, + BT_DEV_SCAN_LIMITED))) + { + bt_le_scan_set_enable(BT_HCI_LE_SCAN_DISABLE); + scan_enabled = true; + } +#endif + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && + atomic_test_bit(bt_dev.flags, BT_DEV_INITIATING)) + { + /* Canceled initiating procedure will be restarted by + * connection complete event. + */ + + bt_le_create_conn_cancel(); + } + + if (IS_ENABLED(CONFIG_BT_BROADCASTER) && + !(IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features))) + { + adv = bt_le_adv_lookup_legacy(); + + if (adv && atomic_test_bit(adv->flags, BT_ADV_ENABLED) && + !atomic_test_bit(adv->flags, BT_ADV_USE_IDENTITY)) + { + adv_enabled = true; + id = adv->id; + bt_le_adv_set_enable_legacy(adv, false); + } + } + + /* If both advertiser and scanner is running then the advertiser + * ID must be BT_ID_DEFAULT, this will update the RPA address for both + * roles. + */ + + err = bt_id_set_private_addr(id); + if (err) + { + LOG_WRN("Failed to update RPA address (%d)", err); + return; + } + + if (IS_ENABLED(CONFIG_BT_BROADCASTER) && adv != NULL && + !atomic_test_bit(adv->flags, BT_ADV_USE_IDENTITY)) + { + /* The legacy advertiser advertises with the device-wide + * random address that was just refreshed. + */ + + bt_id_save_adv_addr(adv, BT_HCI_OWN_ADDR_RANDOM); + } + + if (IS_ENABLED(CONFIG_BT_BROADCASTER) && IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features)) + { + bt_le_ext_adv_foreach(adv_enable_rpa, NULL); + } + + if (IS_ENABLED(CONFIG_BT_BROADCASTER) && adv && adv_enabled) + { + bt_le_adv_set_enable_legacy(adv, true); + } + +#if defined(CONFIG_BT_OBSERVER) + if (scan_enabled) + { + bt_le_scan_set_enable(BT_HCI_LE_SCAN_ENABLE); + } +#endif +} + +static void le_force_rpa_timeout(void) +{ +#if defined(CONFIG_BT_PRIVACY) + struct k_work_sync sync; + + k_work_cancel_delayable_sync(&bt_dev.rpa_update, &sync); +#endif + (void)le_adv_rpa_timeout(); + le_rpa_invalidate(); + le_update_private_addr(); +} + +#if defined(CONFIG_BT_PRIVACY) +/* Generate a random IRK for the given identity using bt_rand(). */ + +static int bt_gen_irk(uint8_t id) +{ + return bt_rand(&bt_dev.irk[id], sizeof(bt_dev.irk[id])); +} + +static void rpa_timeout(struct k_work *work) +{ + bool adv_enabled; + + LOG_DBG(""); + + if (IS_ENABLED(CONFIG_BT_CENTRAL)) + { + struct bt_conn *conn = + bt_conn_lookup_state_le(BT_ID_DEFAULT, NULL, + BT_CONN_SCAN_BEFORE_INITIATING); + + if (conn) + { + bt_conn_unref(conn); + bt_le_create_conn_cancel(); + } + } + + adv_enabled = le_adv_rpa_timeout(); + le_rpa_invalidate(); + + /* IF no roles using the RPA is running we can stop the RPA timer */ + + if (IS_ENABLED(CONFIG_BT_CENTRAL)) + { + if (!(adv_enabled || atomic_test_bit(bt_dev.flags, + BT_DEV_INITIATING) || + bt_le_scan_active_scanner_running())) + { + return; + } + } + + le_update_private_addr(); +} +#endif /* CONFIG_BT_PRIVACY */ + +bool bt_id_scan_random_addr_check(void) +{ + struct bt_le_ext_adv *adv; + + if (!IS_ENABLED(CONFIG_BT_BROADCASTER) || + (IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features))) + { + /* Advertiser is not enabled or advertiser and scanner are using + * a different random address. + */ + + return true; + } + + adv = bt_le_adv_lookup_legacy(); + if (!adv) + { + return true; + } + + /* If the advertiser is not active there is no issue */ + + if (!atomic_test_bit(adv->flags, BT_ADV_ENABLED)) + { + return true; + } + + /* When privacy is enabled the random address will not be set + * immediately before starting the role, because the RPA might still be + * valid and only updated on RPA timeout. + */ + + if (IS_ENABLED(CONFIG_BT_PRIVACY)) + { + /* Cannot start scanner or initiator if the random address is + * used by the advertiser for an RPA with a different identity or for a + * random static identity address. + */ + + if ((atomic_test_bit(adv->flags, BT_ADV_USE_IDENTITY) && + bt_dev.id_addr[adv->id].type == BT_ADDR_LE_RANDOM) || + adv->id != BT_ID_DEFAULT) + { + return false; + } + } + + /* If privacy is not enabled then the random address will be attempted + * to be set before enabling the role. If another role is already using the + * random address then this command will fail, and should return the error + * code to the application. + */ + + return true; +} + +bool bt_id_adv_random_addr_check(const struct bt_le_adv_param *param) +{ + if (param == NULL) + { + return false; + } + + if (!IS_ENABLED(CONFIG_BT_OBSERVER) || + (IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features))) + { + /* If scanner roles are not enabled or advertiser and scanner + * are using a different random address. + */ + + return true; + } + + /* If scanner roles are not active there is no issue. */ + + if (!(atomic_test_bit(bt_dev.flags, BT_DEV_INITIATING) || + atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING))) + { + return true; + } + + /* When privacy is enabled the random address will not be set + * immediately before starting the role, because the RPA might still be + * valid and only updated on RPA timeout. + */ + + if (IS_ENABLED(CONFIG_BT_PRIVACY)) + { + /* Cannot start an advertiser with random static identity or + * using an RPA generated for a different identity than scanner roles. + */ + + if (((param->options & BT_LE_ADV_OPT_USE_IDENTITY) && + bt_dev.id_addr[param->id].type == BT_ADDR_LE_RANDOM) || + param->id != BT_ID_DEFAULT) + { + return false; + } + } + else if (IS_ENABLED(CONFIG_BT_SCAN_WITH_IDENTITY) && + atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING) && + bt_dev.id_addr[BT_ID_DEFAULT].type == BT_ADDR_LE_RANDOM) + { + /* Scanning with random static identity. Stop the advertiser + * from overwriting the passive scanner identity address. In this case + * the LE Set Random Address command does not protect us in the case of + * a passive scanner. Explicitly stop it here. + */ + + if (!(param->options & BT_LE_ADV_OPT_CONN) && + (param->options & BT_LE_ADV_OPT_USE_IDENTITY)) + { + /* Attempt to set non-connectable NRPA */ + + return false; + } + else if (bt_dev.id_addr[param->id].type == BT_ADDR_LE_RANDOM && + param->id != BT_ID_DEFAULT) + { + /* Attempt to set connectable, or non-connectable with + * identity different than scanner. + */ + + return false; + } + } + + /* If privacy is not enabled then the random address will be attempted + * to be set before enabling the role. If another role is already using the + * random address then this command will fail, and should return the error + * code to the application. + */ + + return true; +} + +void bt_id_adv_limited_stopped(struct bt_le_ext_adv *adv) +{ + adv_rpa_expired(adv, NULL); +} + +#if defined(CONFIG_BT_SMP) +static int le_set_privacy_mode(const bt_addr_le_t *addr, uint8_t mode) +{ + struct bt_hci_cp_le_set_privacy_mode cp; + struct net_buf *buf; + int err; + + /* Check if set privacy mode command is supported */ + + if (!BT_CMD_TEST(bt_dev.supported_commands, 39, 2)) + { + LOG_WRN("Set privacy mode command is not supported"); + return 0; + } + + LOG_DBG("addr %s mode 0x%02x", bt_addr_le_str(addr), mode); + + bt_addr_le_copy(&cp.id_addr, addr); + cp.mode = mode; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + net_buf_add_mem(buf, &cp, sizeof(cp)); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_PRIVACY_MODE, buf, NULL); + if (err) + { + return err; + } + + return 0; +} + +static int addr_res_enable(uint8_t enable) +{ + struct net_buf *buf; + + LOG_DBG("%s", enable ? "enabled" : "disabled"); + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + net_buf_add_u8(buf, enable); + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_ADDR_RES_ENABLE, buf, NULL); +} + +static int hci_id_add(uint8_t id, + const bt_addr_le_t *addr, uint8_t peer_irk[16]) +{ + struct bt_hci_cp_le_add_dev_to_rl *cp; + struct net_buf *buf; + + if (id >= CONFIG_BT_ID_MAX) + { + return -EINVAL; + } + + LOG_DBG("addr %s", bt_addr_le_str(addr)); + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + bt_addr_le_copy(&cp->peer_id_addr, addr); + memcpy(cp->peer_irk, peer_irk, 16); + +#if defined(CONFIG_BT_PRIVACY) + (void)memcpy(cp->local_irk, &bt_dev.irk[id], 16); +#else + (void)memset(cp->local_irk, 0, 16); +#endif + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_ADD_DEV_TO_RL, buf, NULL); +} + +/* Peer identities whose resolving list removal was deferred */ + +static bt_addr_le_t pending_id_del_addrs[CONFIG_BT_MAX_PAIRED]; +static size_t pending_id_del_count; + +static bool id_del(const bt_addr_le_t *addr, struct bt_keys *keys); + +static void pending_id_update(struct bt_keys *keys, void *data) +{ + if (keys->state & BT_KEYS_ID_PENDING_ADD) + { + keys->state &= ~BT_KEYS_ID_PENDING_ADD; + bt_id_add(keys); + } +} + +static void pending_id_del_update(void) +{ + bt_addr_le_t addr; + + while (pending_id_del_count > 0U) + { + pending_id_del_count--; + bt_addr_le_copy(&addr, &pending_id_del_addrs[pending_id_del_count]); + + if (!id_del(&addr, NULL)) + { + break; + } + } +} + +void bt_id_pending_keys_update_set(struct bt_keys *keys, uint8_t flag) +{ + atomic_set_bit(bt_dev.flags, BT_DEV_ID_PENDING); + keys->state |= flag; +} + +void bt_id_pending_keys_update(void) +{ + if (atomic_test_and_clear_bit(bt_dev.flags, BT_DEV_ID_PENDING)) + { + pending_id_del_update(); + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && IS_ENABLED(CONFIG_BT_PRIVACY)) + { + bt_keys_foreach_type(BT_KEYS_ALL, pending_id_update, NULL); + } + else + { + bt_keys_foreach_type(BT_KEYS_IRK, pending_id_update, NULL); + } + } +} + +struct bt_id_conflict +{ + struct bt_keys *candidate; + struct bt_keys *found; +}; + +/* The Controller Resolve List is constrained by 7.8.38 "LE Add Device To + * Resolving List command". The Host is designed with the assumption that all + * local bonds can be put in the resolve list if there is room. Therefore we + * must refuse bonds that conflict in the resolve list. Notably, this + * prevents multiple local identities to bond with the same remote identity. + */ + +static void find_rl_conflict(struct bt_keys *resident, void *user_data) +{ + struct bt_id_conflict *conflict = user_data; + bool addr_conflict; + bool irk_conflict; + + DEBUGASSERT(conflict != NULL); + DEBUGASSERT(conflict->candidate != NULL); + DEBUGASSERT(resident != NULL); + + /* Only uncommitted bonds can be in conflict with committed bonds. */ + + DEBUGASSERT((conflict->candidate->state & BT_KEYS_ID_ADDED) == 0); + + if (conflict->found) + { + return; + } + + /* Test against committed bonds only. */ + + if ((resident->state & BT_KEYS_ID_ADDED) == 0) + { + return; + } + + addr_conflict = bt_addr_le_eq(&conflict->candidate->addr, &resident->addr); + + /* All-zero IRK is "no IRK", and does not conflict with other Zero-IRKs. */ + + irk_conflict = (!bt_irk_eq(&conflict->candidate->irk, &(struct bt_irk) + { + } + + ) && + bt_irk_eq(&conflict->candidate->irk, &resident->irk)); + + if (addr_conflict || irk_conflict) + { + LOG_DBG("Resident : addr %s and IRK %s, id: %d", + bt_addr_le_str(&resident->addr), + bt_hex(resident->irk.val, + sizeof(resident->irk.val)), resident->id); + LOG_DBG("Candidate: addr %s and IRK %s, id: %d", + bt_addr_le_str(&conflict->candidate->addr), + bt_hex(conflict->candidate->irk.val, + sizeof(conflict->candidate->irk.val)), + conflict->candidate->id); + + conflict->found = resident; + } +} + +struct bt_keys *bt_id_find_conflict(struct bt_keys *candidate) +{ + struct bt_id_conflict conflict = + { + .candidate = candidate, + }; + + bt_keys_foreach_type(BT_KEYS_IRK, find_rl_conflict, &conflict); + + return conflict.found; +} + +void bt_id_add(struct bt_keys *keys) +{ + if (keys == NULL) + { + return; + } + + struct bt_conn *conn; + int err; + bool enable_controller_res = true; + + LOG_DBG("addr %s", bt_addr_le_str(&keys->addr)); + + DEBUGASSERT(keys != NULL); + + /* We assume (and could assert) !bt_id_find_conflict(keys) here. */ + + /* Nothing to be done if host-side resolving is used */ + + if (!bt_dev.le.rl_size || bt_dev.le.rl_entries > bt_dev.le.rl_size) + { + bt_dev.le.rl_entries++; + keys->state |= BT_KEYS_ID_ADDED; + return; + } + + conn = bt_conn_lookup_state_le(BT_ID_DEFAULT, NULL, BT_CONN_INITIATING); + if (conn) + { + bt_id_pending_keys_update_set(keys, BT_KEYS_ID_PENDING_ADD); + bt_conn_unref(conn); + return; + } + + if (IS_ENABLED(CONFIG_BT_BROADCASTER) && IS_ENABLED(CONFIG_BT_EXT_ADV)) + { + bool adv_enabled = false; + + bt_le_ext_adv_foreach(adv_is_limited_enabled, &adv_enabled); + if (adv_enabled) + { + bt_id_pending_keys_update_set(keys, BT_KEYS_ID_PENDING_ADD); + return; + } + } + +#if defined(CONFIG_BT_OBSERVER) + bool scan_enabled = atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING); + + if (IS_ENABLED(CONFIG_BT_EXT_ADV) && scan_enabled && + atomic_test_bit(bt_dev.flags, BT_DEV_SCAN_LIMITED)) + { + bt_id_pending_keys_update_set(keys, BT_KEYS_ID_PENDING_ADD); + } +#endif + + pending_id_del_update(); + + if (IS_ENABLED(CONFIG_BT_BROADCASTER)) + { + bt_le_ext_adv_foreach(adv_pause_enabled, NULL); + } + +#if defined(CONFIG_BT_OBSERVER) + if (scan_enabled) + { + bt_le_scan_set_enable(BT_HCI_LE_SCAN_DISABLE); + } +#endif /* CONFIG_BT_OBSERVER */ + + /* If there are any existing entries address resolution will be on */ + + if (bt_dev.le.rl_entries) + { + err = addr_res_enable(BT_HCI_ADDR_RES_DISABLE); + if (err) + { + LOG_WRN("Failed to disable address resolution"); + /* If it fails to disable, it should already be enabled, + * don't need to enable again. + */ + + enable_controller_res = false; + goto done; + } + } + + if (bt_dev.le.rl_entries == bt_dev.le.rl_size) + { + LOG_WRN("Resolving list size exceeded. Switching to host."); + + /* Since the controller resolving list is cleared, + * don't need to enable the address resolution. + */ + + enable_controller_res = false; + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_CLEAR_RL, NULL, NULL); + if (err) + { + LOG_ERR("Failed to clear resolution list"); + goto done; + } + + bt_dev.le.rl_entries++; + keys->state |= BT_KEYS_ID_ADDED; + + goto done; + } + + err = hci_id_add(keys->id, &keys->addr, keys->irk.val); + if (err) + { + LOG_ERR("Failed to add IRK to controller"); + goto done; + } + + bt_dev.le.rl_entries++; + keys->state |= BT_KEYS_ID_ADDED; + + /* According to Core Spec. 5.0 Vol 1, Part A 5.4.5 Privacy Feature + * + * By default, network privacy mode is used when private addresses are + * resolved and generated by the Controller, so advertising packets from + * peer devices that contain private addresses will only be accepted. By + * changing to the device privacy mode device is only concerned about its + * privacy and will accept advertising packets from peer devices that + * contain their identity address as well as ones that contain a private + * address, even if the peer device has distributed its IRK in the past. + */ + + err = le_set_privacy_mode(&keys->addr, BT_HCI_LE_PRIVACY_MODE_DEVICE); + if (err) + { + LOG_ERR("Failed to set privacy mode"); + goto done; + } + +done: + if (enable_controller_res) + { + addr_res_enable(BT_HCI_ADDR_RES_ENABLE); + } + +#if defined(CONFIG_BT_OBSERVER) + if (scan_enabled) + { + bt_le_scan_set_enable(BT_HCI_LE_SCAN_ENABLE); + } +#endif /* CONFIG_BT_OBSERVER */ + + if (IS_ENABLED(CONFIG_BT_BROADCASTER)) + { + bt_le_ext_adv_foreach(adv_unpause_enabled, NULL); + } +} + +static void keys_add_id(struct bt_keys *keys, void *data) +{ + if (keys->state & BT_KEYS_ID_ADDED) + { + hci_id_add(keys->id, &keys->addr, keys->irk.val); + } +} + +static int hci_id_del(const bt_addr_le_t *addr) +{ + struct bt_hci_cp_le_rem_dev_from_rl *cp; + struct net_buf *buf; + + LOG_DBG("addr %s", bt_addr_le_str(addr)); + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + bt_addr_le_copy(&cp->peer_id_addr, addr); + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_REM_DEV_FROM_RL, buf, NULL); +} + +static void pending_id_del_set(const bt_addr_le_t *addr, + struct bt_keys *keys) +{ + DEBUGASSERT(pending_id_del_count < ARRAY_SIZE(pending_id_del_addrs)); + + if (pending_id_del_count < ARRAY_SIZE(pending_id_del_addrs)) + { + bt_addr_le_copy(&pending_id_del_addrs[pending_id_del_count], addr); + pending_id_del_count++; + } + else + { + LOG_ERR("Unable to defer resolving list removal of %s", + bt_addr_le_str(addr)); + } + + if (keys != NULL) + { + keys->state &= ~BT_KEYS_ID_ADDED; + } + + atomic_set_bit(bt_dev.flags, BT_DEV_ID_PENDING); +} + +static bool id_del(const bt_addr_le_t *addr, struct bt_keys *keys) +{ + struct bt_conn *conn; + int err; + + LOG_DBG("addr %s", bt_addr_le_str(addr)); + + if (!bt_dev.le.rl_size || bt_dev.le.rl_entries > bt_dev.le.rl_size + 1) + { + DEBUGASSERT(bt_dev.le.rl_entries > 0); + if (bt_dev.le.rl_entries > 0) + { + bt_dev.le.rl_entries--; + } + + if (keys != NULL) + { + keys->state &= ~BT_KEYS_ID_ADDED; + } + + return true; + } + + conn = bt_conn_lookup_state_le(BT_ID_DEFAULT, NULL, BT_CONN_INITIATING); + if (conn) + { + pending_id_del_set(addr, keys); + bt_conn_unref(conn); + return false; + } + + if (IS_ENABLED(CONFIG_BT_BROADCASTER) && IS_ENABLED(CONFIG_BT_EXT_ADV)) + { + bool adv_enabled = false; + + bt_le_ext_adv_foreach(adv_is_limited_enabled, &adv_enabled); + if (adv_enabled) + { + pending_id_del_set(addr, keys); + return false; + } + } + +#if defined(CONFIG_BT_OBSERVER) + bool scan_enabled = atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING); +#endif /* CONFIG_BT_OBSERVER */ + + if (IS_ENABLED(CONFIG_BT_BROADCASTER)) + { + bt_le_ext_adv_foreach(adv_pause_enabled, NULL); + } + +#if defined(CONFIG_BT_OBSERVER) + if (scan_enabled) + { + bt_le_scan_set_enable(BT_HCI_LE_SCAN_DISABLE); + } +#endif /* CONFIG_BT_OBSERVER */ + + err = addr_res_enable(BT_HCI_ADDR_RES_DISABLE); + if (err) + { + LOG_ERR("Disabling address resolution failed (err %d)", err); + goto done; + } + + /* We checked size + 1 earlier, so here we know we can fit again */ + + if (bt_dev.le.rl_entries > bt_dev.le.rl_size) + { + bt_dev.le.rl_entries--; + if (keys != NULL) + { + keys->state &= ~BT_KEYS_ID_ADDED; + } + + /* The rebuilt list holds none of the queued removals */ + + bt_dev.le.rl_entries -= (uint8_t)pending_id_del_count; + pending_id_del_count = 0U; + if (IS_ENABLED(CONFIG_BT_CENTRAL) && IS_ENABLED(CONFIG_BT_PRIVACY)) + { + bt_keys_foreach_type(BT_KEYS_ALL, keys_add_id, NULL); + } + else + { + bt_keys_foreach_type(BT_KEYS_IRK, keys_add_id, NULL); + } + + goto done; + } + + err = hci_id_del(addr); + if (err) + { + LOG_ERR("Failed to remove IRK from controller"); + goto done; + } + + bt_dev.le.rl_entries--; + if (keys != NULL) + { + keys->state &= ~BT_KEYS_ID_ADDED; + } + +done: + + /* Only re-enable if there are entries to do resolving with */ + + if (bt_dev.le.rl_entries) + { + addr_res_enable(BT_HCI_ADDR_RES_ENABLE); + } + +#if defined(CONFIG_BT_OBSERVER) + if (scan_enabled) + { + bt_le_scan_set_enable(BT_HCI_LE_SCAN_ENABLE); + } +#endif /* CONFIG_BT_OBSERVER */ + + if (IS_ENABLED(CONFIG_BT_BROADCASTER)) + { + bt_le_ext_adv_foreach(adv_unpause_enabled, NULL); + } + + return true; +} + +void bt_id_del(struct bt_keys *keys) +{ + if (keys == NULL) + { + return; + } + + (void)id_del(&keys->addr, keys); +} +#endif /* defined(CONFIG_BT_SMP) */ + +void bt_id_get(bt_addr_le_t *addrs, size_t *count) +{ + if (addrs) + { + size_t to_copy = MIN(*count, bt_dev.id_count); + + memcpy(addrs, bt_dev.id_addr, to_copy * sizeof(bt_addr_le_t)); + *count = to_copy; + } + else + { + *count = bt_dev.id_count; + } +} + +static int id_find(const bt_addr_le_t *addr) +{ + uint8_t id; + + for (id = 0U; id < bt_dev.id_count; id++) + { + if (bt_addr_le_eq(addr, &bt_dev.id_addr[id])) + { + return id; + } + } + + return -ENOENT; +} + +static int id_create(uint8_t id, bt_addr_le_t *addr, uint8_t *irk) +{ + if (addr && !bt_addr_le_eq(addr, BT_ADDR_LE_ANY)) + { + bt_addr_le_copy(&bt_dev.id_addr[id], addr); + } + else + { + bt_addr_le_t new_addr; + + do + { + int err; + + err = bt_addr_le_create_static(&new_addr); + if (err) + { + return err; + } + + /* Make sure we didn't generate a duplicate */ + } + while (id_find(&new_addr) >= 0); + + bt_addr_le_copy(&bt_dev.id_addr[id], &new_addr); + + if (addr) + { + bt_addr_le_copy(addr, &bt_dev.id_addr[id]); + } + } + +#if defined(CONFIG_BT_PRIVACY) + { + uint8_t zero_irk[16] = + { + 0 + }; + + if (irk && memcmp(irk, zero_irk, 16)) + { + memcpy(&bt_dev.irk[id], irk, 16); + } + else + { + int err; + + err = bt_gen_irk(id); + if (err) + { + return err; + } + + if (irk) + { + memcpy(irk, &bt_dev.irk[id], 16); + } + } + +#if defined(CONFIG_BT_RPA_SHARING) + bt_addr_copy(&bt_dev.rpa[id], BT_ADDR_NONE); +#endif + } +#endif + + /* Only store if stack was already initialized. Before initialization + * we don't know the flash content, so it's potentially harmful to try to + * write anything there. + */ + + if (IS_ENABLED(CONFIG_BT_SETTINGS) && + atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + (void)bt_settings_store_id(); + (void)bt_settings_store_irk(); + } + + return 0; +} + +int bt_id_create(bt_addr_le_t *addr, uint8_t *irk) +{ + int new_id; + int err; + + if (!IS_ENABLED(CONFIG_BT_PRIVACY) && irk) + { + return -EINVAL; + } + + if (addr && !bt_addr_le_eq(addr, BT_ADDR_LE_ANY)) + { + if (id_find(addr) >= 0) + { + return -EALREADY; + } + + if (addr->type == BT_ADDR_LE_PUBLIC && + (IS_ENABLED(CONFIG_BT_HCI_SET_PUBLIC_ADDR) || + IS_ENABLED(CONFIG_BT_HCI_VS))) + { + /* set the single public address */ + + if (bt_dev.id_count != 0) + { + return -EALREADY; + } + + bt_addr_le_copy(&bt_dev.id_addr[BT_ID_DEFAULT], addr); + bt_dev.id_count++; + return BT_ID_DEFAULT; + } + else if (addr->type != BT_ADDR_LE_RANDOM || + !BT_ADDR_IS_STATIC(&addr->a)) + { + LOG_ERR("Only random static identity address supported"); + return -EINVAL; + } + } + + if (bt_dev.id_count == ARRAY_SIZE(bt_dev.id_addr)) + { + return -ENOMEM; + } + + /* bt_rand is not available before Bluetooth enable has been called */ + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_OPEN)) + { + uint8_t zero_irk[16] = + { + 0 + }; + + if (!(addr && !bt_addr_le_eq(addr, BT_ADDR_LE_ANY))) + { + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_PRIVACY) && !(irk && + memcmp(irk, zero_irk, 16))) + { + return -EINVAL; + } + } + + new_id = bt_dev.id_count++; + err = id_create(new_id, addr, irk); + if (err) + { + bt_dev.id_count--; + return err; + } + + return new_id; +} + +int bt_id_reset(uint8_t id, bt_addr_le_t *addr, uint8_t *irk) +{ + int err; + + if (addr && !bt_addr_le_eq(addr, BT_ADDR_LE_ANY)) + { + if (addr->type != BT_ADDR_LE_RANDOM || !BT_ADDR_IS_STATIC(&addr->a)) + { + LOG_ERR("Only static random identity address supported"); + return -EINVAL; + } + + if (id_find(addr) >= 0) + { + return -EALREADY; + } + } + + if (!IS_ENABLED(CONFIG_BT_PRIVACY) && irk) + { + return -EINVAL; + } + + if (id == BT_ID_DEFAULT || id >= bt_dev.id_count) + { + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_BROADCASTER)) + { + struct bt_adv_id_check_data check_data = + { + .id = id, + .adv_enabled = false, + }; + + bt_le_ext_adv_foreach(adv_id_check_func, &check_data); + if (check_data.adv_enabled) + { + return -EBUSY; + } + } + + if (IS_ENABLED(CONFIG_BT_SMP) && !bt_addr_le_eq(&bt_dev.id_addr[id], + BT_ADDR_LE_ANY)) + { + err = bt_unpair(id, NULL); + if (err) + { + return err; + } + } + + err = id_create(id, addr, irk); + if (err) + { + return err; + } + + return id; +} + +int bt_id_delete(uint8_t id) +{ + if (id == BT_ID_DEFAULT || id >= bt_dev.id_count) + { + return -EINVAL; + } + + if (bt_addr_le_eq(&bt_dev.id_addr[id], BT_ADDR_LE_ANY)) + { + return -EALREADY; + } + + if (IS_ENABLED(CONFIG_BT_BROADCASTER)) + { + struct bt_adv_id_check_data check_data = + { + .id = id, + .adv_enabled = false, + }; + + bt_le_ext_adv_foreach(adv_id_check_func, &check_data); + if (check_data.adv_enabled) + { + return -EBUSY; + } + } + + if (IS_ENABLED(CONFIG_BT_SMP)) + { + int err; + + err = bt_unpair(id, NULL); + if (err) + { + return err; + } + } + +#if defined(CONFIG_BT_PRIVACY) + (void)memset(bt_dev.irk[id], 0, 16); +#endif + bt_addr_le_copy(&bt_dev.id_addr[id], BT_ADDR_LE_ANY); + + if (id == bt_dev.id_count - 1) + { + bt_dev.id_count--; + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS) && + atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + (void)bt_settings_store_id(); + (void)bt_settings_store_irk(); + } + + return 0; +} + +#if defined(CONFIG_BT_PRIVACY) +static void adv_is_enabled_cb(struct bt_le_ext_adv *adv, void *data) +{ + if (atomic_test_bit(adv->flags, BT_ADV_ENABLED)) + { + bool *adv_enabled = data; + + *adv_enabled = true; + } +} + +int bt_id_reset_irk(uint8_t id, uint8_t *irk) +{ + uint8_t new_irk[sizeof(bt_dev.irk[id])]; + uint8_t zero_irk[sizeof(new_irk)] = + { + 0 + }; + + bool generate_irk = true; + int err; + + if (id >= bt_dev.id_count) + { + return -EINVAL; + } + + if (bt_addr_le_eq(&bt_dev.id_addr[id], BT_ADDR_LE_ANY)) + { + return -EALREADY; + } + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + if (IS_ENABLED(CONFIG_BT_BROADCASTER)) + { + struct bt_adv_id_check_data check_data = + { + .id = id, + .adv_enabled = false, + }; + + bt_le_ext_adv_foreach(adv_id_check_func, &check_data); + if (check_data.adv_enabled) + { + return -EBUSY; + } + + bool adv_enabled = false; + + bt_le_ext_adv_foreach(adv_is_enabled_cb, &adv_enabled); + if (adv_enabled) + { + return -EBUSY; + } + } + + if ((id == BT_ID_DEFAULT) && (atomic_test_bit(bt_dev.flags, + BT_DEV_SCANNING) || + atomic_test_bit(bt_dev.flags, + BT_DEV_INITIATING))) + { + return -EBUSY; + } + + if (bt_keys_has_bond(id)) + { + return -ENOTEMPTY; + } + + if (irk != NULL) + { + generate_irk = util_memeq(irk, zero_irk, sizeof(new_irk)); + } + + if (!generate_irk) + { + (void)memcpy(new_irk, irk, sizeof(new_irk)); + } + else + { + err = bt_rand(new_irk, sizeof(new_irk)); + if (err != 0) + { + return err == -ENOTSUP ? -ENOTSUP : -EIO; + } + } + + (void)memcpy(bt_dev.irk[id], new_irk, sizeof(new_irk)); + if (irk != NULL) + { + (void)memcpy(irk, new_irk, sizeof(new_irk)); + } + + atomic_clear_bit(bt_dev.flags, BT_DEV_RPA_VALID); + +#if defined(CONFIG_BT_RPA_SHARING) + bt_addr_copy(&bt_dev.rpa[id], BT_ADDR_NONE); +#endif + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + (void)bt_settings_store_irk(); + } + + return 0; +} + +static void bt_read_identity_root(uint8_t *ir) +{ + /* Invalid IR */ + + memset(ir, 0, 16); + +#if defined(CONFIG_BT_HCI_VS) + struct bt_hci_rp_vs_read_key_hierarchy_roots *rp; + struct net_buf *rsp; + int err; + + if (!BT_VS_CMD_READ_KEY_ROOTS(bt_dev.vs_commands)) + { + return; + } + + err = bt_hci_cmd_send_sync(BT_HCI_OP_VS_READ_KEY_HIERARCHY_ROOTS, NULL, + &rsp); + if (err) + { + LOG_WRN("Failed to read identity root"); + return; + } + + if (IS_ENABLED(CONFIG_BT_HCI_VS_EXT_DETECT) && + rsp->len != sizeof(struct bt_hci_rp_vs_read_key_hierarchy_roots)) + { + LOG_WRN("Invalid Vendor HCI extensions"); + net_buf_unref(rsp); + return; + } + + rp = (void *)rsp->data; + memcpy(ir, rp->ir, 16); + + net_buf_unref(rsp); +#endif /* defined(CONFIG_BT_HCI_VS) */ +} +#endif /* defined(CONFIG_BT_PRIVACY) */ + +uint8_t bt_id_read_public_addr(bt_addr_le_t *addr) +{ + struct bt_hci_rp_read_bd_addr *rp; + struct net_buf *rsp; + int err; + + if (addr == NULL) + { + LOG_WRN("Invalid input parameters"); + return 0U; + } + + /* Read Bluetooth Address */ + + err = bt_hci_cmd_send_sync(BT_HCI_OP_READ_BD_ADDR, NULL, &rsp); + if (err) + { + LOG_WRN("Failed to read public address"); + return 0U; + } + + rp = (void *)rsp->data; + + if (bt_addr_eq(&rp->bdaddr, BT_ADDR_ANY) || + bt_addr_eq(&rp->bdaddr, BT_ADDR_NONE)) + { + LOG_DBG("Controller has no public address"); + net_buf_unref(rsp); + return 0U; + } + + bt_addr_le_copy_addr(addr, &rp->bdaddr, BT_ADDR_LE_PUBLIC); + + net_buf_unref(rsp); + return 1U; +} + +int bt_setup_public_id_addr(void) +{ + bt_addr_le_t addr; + uint8_t *irk = NULL; + + bt_dev.id_count = bt_id_read_public_addr(&addr); + + if (!bt_dev.id_count) + { + return 0; + } + +#if defined(CONFIG_BT_PRIVACY) + uint8_t ir_irk[16]; + uint8_t ir[16]; + + bt_read_identity_root(ir); + + if (!IS_ENABLED(CONFIG_BT_PRIVACY_RANDOMIZE_IR)) + { + if (!bt_smp_irk_get(ir, ir_irk)) + { + irk = ir_irk; + } + } +#endif /* defined(CONFIG_BT_PRIVACY) */ + + /* If true, `id_create` will randomize the IRK. */ + + if (!irk && IS_ENABLED(CONFIG_BT_PRIVACY)) + { + /* `id_create` will not store the id when called before BT_DEV_READY. + * But since part of the id will be randomized, it needs to be stored. + */ + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + atomic_set_bit(bt_dev.flags, BT_DEV_STORE_ID); + } + } + + return id_create(BT_ID_DEFAULT, &addr, irk); +} + +static uint8_t vs_read_static_addr(struct bt_hci_vs_static_addr addrs[], + uint8_t size) +{ +#if defined(CONFIG_BT_HCI_VS) + struct bt_hci_rp_vs_read_static_addrs *rp; + struct net_buf *rsp; + int err; + int i; + uint8_t cnt; + + if (!BT_VS_CMD_READ_STATIC_ADDRS(bt_dev.vs_commands)) + { + LOG_WRN("Read Static Addresses command not available"); + return 0; + } + + err = bt_hci_cmd_send_sync(BT_HCI_OP_VS_READ_STATIC_ADDRS, NULL, &rsp); + if (err) + { + LOG_WRN("Failed to read static addresses"); + return 0; + } + + if (IS_ENABLED(CONFIG_BT_HCI_VS_EXT_DETECT) && + rsp->len < sizeof(struct bt_hci_rp_vs_read_static_addrs)) + { + LOG_WRN("Invalid Vendor HCI extensions"); + net_buf_unref(rsp); + return 0; + } + + rp = (void *)rsp->data; + cnt = MIN(rp->num_addrs, size); + + if (IS_ENABLED(CONFIG_BT_HCI_VS_EXT_DETECT) && + rsp->len != (sizeof(struct bt_hci_rp_vs_read_static_addrs) + + rp->num_addrs * sizeof(struct bt_hci_vs_static_addr))) + { + LOG_WRN("Invalid Vendor HCI extensions"); + net_buf_unref(rsp); + return 0; + } + + for (i = 0; i < cnt; i++) + { + memcpy(&addrs[i], &rp->a[i], sizeof(struct bt_hci_vs_static_addr)); + } + + net_buf_unref(rsp); + if (!cnt) + { + LOG_WRN("No static addresses stored in controller"); + } + + return cnt; +#else + return 0; +#endif +} + +int bt_setup_random_id_addr(void) +{ + /* Only read the addresses if the user has not already configured one or + * more identities (!bt_dev.id_count). + */ + + if (IS_ENABLED(CONFIG_BT_HCI_VS) && !bt_dev.id_count) + { + struct bt_hci_vs_static_addr addrs[CONFIG_BT_ID_MAX]; + + bt_dev.id_count = vs_read_static_addr(addrs, CONFIG_BT_ID_MAX); + + for (uint8_t i = 0; i < bt_dev.id_count; i++) + { + int err; + bt_addr_le_t addr; + uint8_t *irk = NULL; + uint8_t ir_irk[16]; + + if (IS_ENABLED(CONFIG_BT_PRIVACY) && + !IS_ENABLED(CONFIG_BT_PRIVACY_RANDOMIZE_IR)) + { + if (!bt_smp_irk_get(addrs[i].ir, ir_irk)) + { + irk = ir_irk; + } + } + + /* If true, `id_create` will randomize the IRK. */ + + if (!irk && IS_ENABLED(CONFIG_BT_PRIVACY)) + { + /* `id_create` will not store the id when called before + * BT_DEV_READY. But since part of the id will be randomized, + * it needs to be stored. + */ + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + atomic_set_bit(bt_dev.flags, BT_DEV_STORE_ID); + } + } + + bt_addr_le_copy_addr(&addr, &addrs[i].bdaddr, BT_ADDR_LE_RANDOM); + + err = id_create(i, &addr, irk); + if (err) + { + return err; + } + } + + if (bt_dev.id_count > 0) + { + return 0; + } + } + + if (IS_ENABLED(CONFIG_BT_PRIVACY) && IS_ENABLED(CONFIG_BT_SETTINGS)) + { + atomic_set_bit(bt_dev.flags, BT_DEV_STORE_ID); + } + + return bt_id_create(NULL, NULL); +} + +#if defined(CONFIG_BT_CENTRAL) +static inline bool rpa_timeout_valid_check(void) +{ +#if defined(CONFIG_BT_PRIVACY) + uint32_t remaining_ms = + TICK2MSEC(k_work_delayable_remaining_get(&bt_dev.rpa_update)); + + /* Check if create conn timeout will happen before RPA timeout. */ + + return remaining_ms > (10 * bt_dev.create_param.timeout); +#else + return true; +#endif +} + +int bt_id_set_create_conn_own_addr(bool use_filter, uint8_t *own_addr_type) +{ + int err; + + if (own_addr_type == NULL) + { + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_PRIVACY)) + { + if (use_filter || rpa_timeout_valid_check()) + { + err = bt_id_set_private_addr(BT_ID_DEFAULT); + if (err) + { + return err; + } + } + else + { + /* Force new RPA timeout so that RPA timeout is not + * triggered while direct initiator is active. + */ + + le_force_rpa_timeout(); + } + + if (BT_FEAT_LE_PRIVACY(bt_dev.le.features)) + { + *own_addr_type = BT_HCI_OWN_ADDR_RPA_OR_RANDOM; + } + else + { + *own_addr_type = BT_HCI_OWN_ADDR_RANDOM; + } + } + else + { + const bt_addr_le_t *addr = &bt_dev.id_addr[BT_ID_DEFAULT]; + + /* If Static Random address is used as Identity address we + * need to restore it before creating connection. Otherwise NRPA used + * for active scan could be used for connection. + */ + + if (addr->type == BT_ADDR_LE_RANDOM) + { + err = set_random_address(&addr->a); + if (err) + { + return err; + } + + *own_addr_type = BT_HCI_OWN_ADDR_RANDOM; + } + else + { + /* If address type is not random, it's public. If it's public then + * we assume it's the Controller's public address. + */ + + *own_addr_type = BT_HCI_OWN_ADDR_PUBLIC; + } + } + + return 0; +} +#endif /* defined(CONFIG_BT_CENTRAL) */ + +#if defined(CONFIG_BT_OBSERVER) +static bool is_legacy_adv_enabled(void) +{ + struct bt_le_ext_adv *adv; + + if (!IS_ENABLED(CONFIG_BT_BROADCASTER) || + (IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features))) + { + /* When advertising is not enabled or is using extended + * advertising HCI commands then only the scanner uses the set random + * address command. + */ + + return false; + } + + adv = bt_le_adv_lookup_legacy(); + + return adv != NULL && atomic_test_bit(adv->flags, BT_ADV_ENABLED); +} + +static bool is_legacy_adv_using_id_addr(void) +{ + struct bt_le_ext_adv *adv; + + if (!IS_ENABLED(CONFIG_BT_BROADCASTER) || + (IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features))) + { + /* When advertising is not enabled or is using extended + * advertising HCI commands then only the scanner uses the set random + * address command. + */ + + return false; + } + + adv = bt_le_adv_lookup_legacy(); + + return adv != NULL && atomic_test_bit(adv->flags, BT_ADV_ENABLED) && + atomic_test_bit(adv->flags, BT_ADV_USE_IDENTITY); +} + +int bt_id_set_scan_own_addr(bool active_scan, uint8_t *own_addr_type) +{ + int err; + + if (own_addr_type == NULL) + { + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_PRIVACY)) + { + if (BT_FEAT_LE_PRIVACY(bt_dev.le.features)) + { + *own_addr_type = BT_HCI_OWN_ADDR_RPA_OR_RANDOM; + } + else + { + *own_addr_type = BT_HCI_OWN_ADDR_RANDOM; + } + + err = bt_id_set_private_addr(BT_ID_DEFAULT); + if (err == -EACCES && (atomic_test_bit(bt_dev.flags, + BT_DEV_SCANNING) || + atomic_test_bit(bt_dev.flags, + BT_DEV_INITIATING))) + { + LOG_WRN("Set random addr failure ignored in scan/init state"); + + return 0; + } + else if (err) + { + return err; + } + } + else + { + /* Use NRPA unless identity has been explicitly requested + * (through Kconfig). Use same RPA as legacy advertiser if advertising. + */ + + if (!IS_ENABLED(CONFIG_BT_SCAN_WITH_IDENTITY)) + { + /* When using legacy advertising commands, the scanner and + * advertiser share the same address, so we cannot change it. When + * using extended advertising commands, however, the advertising + * sets have their own addresses, so we can always change the + * scanner address here. + */ + + if (is_legacy_adv_using_id_addr()) + { + if (bt_dev.id_addr[BT_ID_DEFAULT].type == BT_ADDR_LE_RANDOM) + { + *own_addr_type = BT_HCI_OWN_ADDR_RANDOM; + } + else + { + *own_addr_type = BT_HCI_OWN_ADDR_PUBLIC; + } + } + else if (is_legacy_adv_enabled()) + { + *own_addr_type = BT_HCI_OWN_ADDR_RANDOM; + } + else + { + err = bt_id_set_private_addr(BT_ID_DEFAULT); + if (err) + { + return err; + } + + *own_addr_type = BT_HCI_OWN_ADDR_RANDOM; + } + } + else + { + if (bt_dev.id_addr[BT_ID_DEFAULT].type == BT_ADDR_LE_RANDOM) + { + /* If scanning with Identity Address we must set the + * random identity address for both active and passive scanner + * in order to receive adv reports that are directed towards + * this identity. + */ + + err = set_random_address(&bt_dev.id_addr[BT_ID_DEFAULT].a); + if (err) + { + return err; + } + + *own_addr_type = BT_HCI_OWN_ADDR_RANDOM; + } + else if (bt_dev.id_addr[BT_ID_DEFAULT].type == BT_ADDR_LE_PUBLIC) + { + *own_addr_type = BT_HCI_OWN_ADDR_PUBLIC; + } + } + } + + return 0; +} +#endif /* defined(CONFIG_BT_OBSERVER) */ + +int bt_id_set_adv_own_addr( + struct bt_le_ext_adv *adv, uint32_t options, bool dir_adv, + uint8_t *own_addr_type) +{ + const bt_addr_le_t *id_addr; + int err = 0; + + if (adv == NULL || own_addr_type == NULL) + { + return -EINVAL; + } + + /* Set which local identity address we're advertising with */ + + id_addr = &bt_dev.id_addr[adv->id]; + + /* Short-circuit to force NRPA usage */ + + if (options & BT_LE_ADV_OPT_USE_NRPA) + { + if (options & BT_LE_ADV_OPT_USE_IDENTITY) + { + LOG_ERR("Can't set both IDENTITY & NRPA"); + + return -EINVAL; + } + + err = bt_id_set_adv_private_addr(adv); + if (err) + { + return err; + } + + *own_addr_type = BT_HCI_OWN_ADDR_RANDOM; + + return 0; + } + + if (options & BT_LE_ADV_OPT_CONN) + { + if (dir_adv && (options & BT_LE_ADV_OPT_DIR_ADDR_RPA) && + !BT_FEAT_LE_PRIVACY(bt_dev.le.features)) + { + return -ENOTSUP; + } + + if (IS_ENABLED(CONFIG_BT_PRIVACY) && + !(options & BT_LE_ADV_OPT_USE_IDENTITY)) + { + err = bt_id_set_adv_private_addr(adv); + if (err) + { + return err; + } + + if (dir_adv && (options & BT_LE_ADV_OPT_DIR_ADDR_RPA)) + { + *own_addr_type = BT_HCI_OWN_ADDR_RPA_OR_RANDOM; + } + else + { + *own_addr_type = BT_HCI_OWN_ADDR_RANDOM; + } + } + else + { + /* If Static Random address is used as Identity + * address we need to restore it before advertising is enabled. + * Otherwise NRPA used for active scan could be used for + * advertising. + */ + + if (id_addr->type == BT_ADDR_LE_RANDOM) + { + err = bt_id_set_adv_random_addr(adv, &id_addr->a); + if (err) + { + return err; + } + + *own_addr_type = BT_HCI_OWN_ADDR_RANDOM; + } + else if (id_addr->type == BT_ADDR_LE_PUBLIC) + { + *own_addr_type = BT_HCI_OWN_ADDR_PUBLIC; + } + + if (dir_adv && (options & BT_LE_ADV_OPT_DIR_ADDR_RPA)) + { + *own_addr_type |= BT_HCI_OWN_ADDR_RPA_MASK; + } + } + } + else + { + if (options & BT_LE_ADV_OPT_USE_IDENTITY) + { + if (id_addr->type == BT_ADDR_LE_RANDOM) + { + err = bt_id_set_adv_random_addr(adv, &id_addr->a); + if (err) + { + return err; + } + + *own_addr_type = BT_HCI_OWN_ADDR_RANDOM; + } + else if (id_addr->type == BT_ADDR_LE_PUBLIC) + { + *own_addr_type = BT_HCI_OWN_ADDR_PUBLIC; + } + + if (options & BT_LE_ADV_OPT_DIR_ADDR_RPA) + { + *own_addr_type |= BT_HCI_OWN_ADDR_RPA_MASK; + } + } + else if (!(IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features))) + { + /* In case advertising set random address is not + * available we must handle the shared random address problem. + */ +#if defined(CONFIG_BT_OBSERVER) + bool scan_disabled = false; + bool dev_scanning = atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING); + + /* If active scan with NRPA is ongoing refresh NRPA */ + + if (!IS_ENABLED(CONFIG_BT_PRIVACY) && + !IS_ENABLED(CONFIG_BT_SCAN_WITH_IDENTITY) && + dev_scanning) + { + err = bt_le_scan_set_enable(BT_HCI_LE_SCAN_DISABLE); + scan_disabled = err == 0; + } + + /* If we are scanning with the identity address, it does + * not make sense to set an NRPA. + */ + + if (!IS_ENABLED(CONFIG_BT_SCAN_WITH_IDENTITY) || !dev_scanning) + { + err = bt_id_set_adv_private_addr(adv); + *own_addr_type = BT_HCI_OWN_ADDR_RANDOM; + } + else + { + if (id_addr->type == BT_ADDR_LE_RANDOM) + { + *own_addr_type = BT_HCI_OWN_ADDR_RANDOM; + } + else if (id_addr->type == BT_ADDR_LE_PUBLIC) + { + *own_addr_type = BT_HCI_OWN_ADDR_PUBLIC; + } + } + + if (scan_disabled) + { + bt_le_scan_set_enable(BT_HCI_LE_SCAN_ENABLE); + } +#else + err = bt_id_set_adv_private_addr(adv); + *own_addr_type = BT_HCI_OWN_ADDR_RANDOM; +#endif /* defined(CONFIG_BT_OBSERVER) */ + } + else + { + err = bt_id_set_adv_private_addr(adv); + *own_addr_type = BT_HCI_OWN_ADDR_RANDOM; + } + + if (err) + { + return err; + } + } + + return 0; +} + +void bt_id_save_adv_addr(struct bt_le_ext_adv *adv, uint8_t own_addr_type) +{ + switch (own_addr_type) + { + case BT_HCI_OWN_ADDR_PUBLIC: + case BT_HCI_OWN_ADDR_RPA_OR_PUBLIC: + /* The identity address is not programmed into the controller, + * so it has to be recorded here. + * + * For BT_HCI_OWN_ADDR_RPA_OR_PUBLIC the controller substitutes a + * locally generated RPA whenever the peer is in the resolving list, + * which the host cannot observe, so the configured fallback address + * is + * recorded instead. + */ + + bt_addr_le_copy(&adv->adv_addr, &bt_dev.id_addr[adv->id]); + break; + case BT_HCI_OWN_ADDR_RANDOM: + case BT_HCI_OWN_ADDR_RPA_OR_RANDOM: + if (!(IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features))) + { + /* With extended advertising the per-set random address + * is always programmed through bt_id_set_adv_random_addr(), + * which + * saves it, so there is nothing to do here. Without it the set + * advertises with the device-wide random address, which is not + * always set through that function: with privacy it comes from + * bt_id_set_private_addr(), and when scanning with a static + * random + * identity it is already in place. + */ + + bt_addr_le_copy_addr(&adv->adv_addr, &bt_dev.random_addr, + BT_ADDR_LE_RANDOM); + } + break; + default: + break; + } +} + +#if defined(CONFIG_BT_CLASSIC) +int bt_br_oob_get_local(struct bt_br_oob *oob) +{ + if (oob == NULL) + { + return -EINVAL; + } + + bt_addr_copy(&oob->addr, &bt_dev.id_addr[0].a); + + return 0; +} +#endif /* CONFIG_BT_CLASSIC */ + +int bt_le_oob_get_local(uint8_t id, struct bt_le_oob *oob) +{ + struct bt_le_ext_adv *adv = NULL; + int err; + + if (oob == NULL) + { + return -EINVAL; + } + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + if (id >= bt_dev.id_count || bt_addr_le_eq(&bt_dev.id_addr[id], + BT_ADDR_LE_ANY)) + { + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_BROADCASTER)) + { + adv = bt_le_adv_lookup_legacy(); + } + + if (IS_ENABLED(CONFIG_BT_PRIVACY) && + !(adv && adv->id == id && atomic_test_bit(adv->flags, + BT_ADV_ENABLED) && + atomic_test_bit(adv->flags, BT_ADV_USE_IDENTITY) && + bt_dev.id_addr[id].type == BT_ADDR_LE_RANDOM)) + { + if (IS_ENABLED(CONFIG_BT_CENTRAL) && + atomic_test_bit(bt_dev.flags, BT_DEV_INITIATING)) + { + struct bt_conn *conn; + + conn = bt_conn_lookup_state_le(BT_ID_DEFAULT, NULL, + BT_CONN_SCAN_BEFORE_INITIATING); + if (conn) + { + /* Cannot set new RPA while creating + * connections. + */ + + bt_conn_unref(conn); + return -EINVAL; + } + } + + if (adv && atomic_test_bit(adv->flags, BT_ADV_ENABLED) && + atomic_test_bit(adv->flags, BT_ADV_USE_IDENTITY) && + (bt_dev.id_addr[id].type == BT_ADDR_LE_RANDOM)) + { + /* Cannot set a new RPA address while advertising with + * random static identity address for a different identity. + */ + + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_OBSERVER) && CONFIG_BT_ID_MAX > 1 && + id != BT_ID_DEFAULT && + (atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING) || + atomic_test_bit(bt_dev.flags, BT_DEV_INITIATING))) + { + /* Cannot switch identity of scanner or initiator */ + + return -EINVAL; + } + + le_force_rpa_timeout(); + + bt_addr_le_copy_addr(&oob->addr, &bt_dev.random_addr, + BT_ADDR_LE_RANDOM); + } + else + { + bt_addr_le_copy(&oob->addr, &bt_dev.id_addr[id]); + } + + if (IS_ENABLED(CONFIG_BT_SMP)) + { + err = bt_smp_le_oob_generate_sc_data(&oob->le_sc_data); + if (err && err != -ENOTSUP) + { + return err; + } + } + + return 0; +} + +#if defined(CONFIG_BT_EXT_ADV) +int bt_le_ext_adv_oob_get_local(struct bt_le_ext_adv *adv, + struct bt_le_oob *oob) +{ + int err; + + if (adv == NULL || oob == NULL) + { + return -EINVAL; + } + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + if (IS_ENABLED(CONFIG_BT_PRIVACY) && + !atomic_test_bit(adv->flags, BT_ADV_USE_IDENTITY)) + { + /* Don't refresh RPA addresses if the RPA is new. + * This allows back to back calls to this function or + * bt_le_oob_get_local to not invalidate the previously set RPAs. + */ + + if (!atomic_test_bit(adv->flags, BT_ADV_LIMITED) && + !bt_id_rpa_is_new()) + { + if (IS_ENABLED(CONFIG_BT_CENTRAL) && + atomic_test_bit(bt_dev.flags, BT_DEV_INITIATING)) + { + struct bt_conn *conn; + + conn = bt_conn_lookup_state_le(BT_ID_DEFAULT, NULL, + BT_CONN_SCAN_BEFORE_INITIATING); + + if (conn) + { + /* Cannot set new RPA while creating + * connections. + */ + + bt_conn_unref(conn); + return -EINVAL; + } + } + + le_force_rpa_timeout(); + } + + bt_addr_le_copy(&oob->addr, &adv->adv_addr); + } + else + { + bt_addr_le_copy(&oob->addr, &bt_dev.id_addr[adv->id]); + } + + if (IS_ENABLED(CONFIG_BT_SMP)) + { + err = bt_smp_le_oob_generate_sc_data(&oob->le_sc_data); + if (err && err != -ENOTSUP) + { + return err; + } + } + + return 0; +} +#endif /* defined(CONFIG_BT_EXT_ADV) */ + +#if defined(CONFIG_BT_SMP) +#if !defined(CONFIG_BT_SMP_SC_PAIR_ONLY) +int bt_le_oob_set_legacy_tk(struct bt_conn *conn, const uint8_t *tk) +{ + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + if (tk == NULL) + { + return -EINVAL; + } + + return bt_smp_le_oob_set_tk(conn, tk); +} +#endif /* !defined(CONFIG_BT_SMP_SC_PAIR_ONLY) */ + +#if !defined(CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY) +int bt_le_oob_set_sc_data( + struct bt_conn *conn, const struct bt_le_oob_sc_data *oobd_local, + const struct bt_le_oob_sc_data *oobd_remote) +{ + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + return bt_smp_le_oob_set_sc_data(conn, oobd_local, oobd_remote); +} + +int bt_le_oob_get_sc_data( + struct bt_conn *conn, const struct bt_le_oob_sc_data **oobd_local, + const struct bt_le_oob_sc_data **oobd_remote) +{ + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + LOG_ERR("Invalid connection: %p", conn); + LOG_ERR("Invalid connection type: %u for %p", conn->handle, conn); + return -EINVAL; + } + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + return bt_smp_le_oob_get_sc_data(conn, oobd_local, oobd_remote); +} +#endif /* !defined(CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY) */ +#endif /* defined(CONFIG_BT_SMP) */ + +int bt_id_init(void) +{ + int err; + +#if defined(CONFIG_BT_SMP) + pending_id_del_count = 0U; +#endif + +#if defined(CONFIG_BT_PRIVACY) + k_work_init_delayable(&bt_dev.rpa_update, rpa_timeout); +#if defined(CONFIG_BT_RPA_SHARING) + for (uint8_t id = 0U; id < ARRAY_SIZE(bt_dev.rpa); id++) + { + bt_addr_copy(&bt_dev.rpa[id], BT_ADDR_NONE); + } +#endif +#endif + + if (!IS_ENABLED(CONFIG_BT_SETTINGS) && !bt_dev.id_count) + { + LOG_DBG("No user identity. Trying to set public."); + + err = bt_setup_public_id_addr(); + if (err) + { + LOG_ERR("Unable to set identity address"); + return err; + } + } + + if (!IS_ENABLED(CONFIG_BT_SETTINGS) && !bt_dev.id_count) + { + LOG_DBG("No public address. Trying to set static random."); + + err = bt_setup_random_id_addr(); + if (err) + { + LOG_ERR("Unable to set identity address"); + return err; + } + + /* The passive scanner just sends a dummy address type in the + * command. If the first activity does this, and the dummy type is a + * random address, it needs a valid value, even though it's not + * actually used. + */ + + err = set_random_address(&bt_dev.id_addr[0].a); + if (err) + { + LOG_ERR("Unable to set random address"); + return err; + } + } + + return 0; +} diff --git a/libs/zblue/host/id.h b/libs/zblue/host/id.h new file mode 100644 index 0000000000000..538fff7554445 --- /dev/null +++ b/libs/zblue/host/id.h @@ -0,0 +1,86 @@ +/**************************************************************************** + * libs/zblue/host/id.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017-2021 Nordic Semiconductor ASA + * Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include + +#include "keys.h" + +#define RPA_TIMEOUT_MS(_rpa_timeout) (_rpa_timeout * MSEC_PER_SEC) + +static inline bool bt_id_rpa_is_new(void) +{ +#if defined(CONFIG_BT_PRIVACY) + /* TODO: To get bt_dev we should include "hci_core.h" but that gives + * redefinitions Should we have an API to get the rpa_update value? + */ + + uint32_t remaining_ms = + TICK2MSEC(k_work_delayable_remaining_get(&bt_dev.rpa_update)); + /* RPA is considered new if there is less than half a second since the + * timeout was started. + */ + + return remaining_ms > (RPA_TIMEOUT_MS(bt_dev.rpa_timeout) - 500); +#else + return false; +#endif +} + +int bt_id_init(void); + +uint8_t bt_id_read_public_addr(bt_addr_le_t *addr); + +int bt_id_set_create_conn_own_addr(bool use_filter, uint8_t *own_addr_type); + +int bt_id_set_scan_own_addr(bool active_scan, uint8_t *own_addr_type); + +int bt_id_set_adv_own_addr( + struct bt_le_ext_adv *adv, uint32_t options, bool dir_adv, + uint8_t *own_addr_type); + +/* Set adv->adv_addr, the address the set advertises with, for the own + * address types whose address is not programmed per-set through + * bt_id_set_adv_random_addr(): identity addresses, and the device-wide + * random address on controllers without the extended advertising feature. In + * the advertising parameter setting path this must be called only after the + * parameters have been accepted by the controller, so that a failed + * parameter command does not change the reported address. + */ + +void bt_id_save_adv_addr(struct bt_le_ext_adv *adv, uint8_t own_addr_type); + +bool bt_id_adv_random_addr_check(const struct bt_le_adv_param *param); + +bool bt_id_scan_random_addr_check(void); + +int bt_id_set_adv_random_addr(struct bt_le_ext_adv *adv, + const bt_addr_t *addr); +int bt_id_set_adv_private_addr(struct bt_le_ext_adv *adv); + +int bt_id_set_private_addr(uint8_t id); + +void bt_id_pending_keys_update(void); + +void bt_id_pending_keys_update_set(struct bt_keys *keys, uint8_t flag); + +void bt_id_adv_limited_stopped(struct bt_le_ext_adv *adv); diff --git a/libs/zblue/host/iso.c b/libs/zblue/host/iso.c new file mode 100644 index 0000000000000..7a292f0a82514 --- /dev/null +++ b/libs/zblue/host/iso.c @@ -0,0 +1,4249 @@ +/**************************************************************************** + * libs/zblue/host/iso.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Bluetooth ISO */ + +/* Copyright (c) 2020 Intel Corporation + * Copyright (c) 2021-2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/assert.h" +#include "host/buf_view.h" +#include "host/hci_core.h" +#include "host/conn_internal.h" +#include "iso_internal.h" + +LOG_MODULE_REGISTER(bt_iso, CONFIG_BT_ISO_LOG_LEVEL); + +#if defined(CONFIG_BT_DEBUG_ISO_DATA) +#define BT_ISO_DATA_DBG(fmt, ...) LOG_DBG(fmt, ##__VA_ARGS__) +#else +#define BT_ISO_DATA_DBG(fmt, ...) +#endif /* CONFIG_BT_DEBUG_ISO_DATA */ + +#define iso_chan(_iso) ((_iso)->iso.chan); + +#if defined(CONFIG_BT_ISO_RX) +static void *buf_rx_freed_cb; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void iso_rx_buf_destroy(struct net_buf *buf) +{ + bt_iso_buf_rx_freed_cb_t cb; + + cb = (bt_iso_buf_rx_freed_cb_t)atomic_ptr_read(&buf_rx_freed_cb); + + net_buf_destroy(buf); + + if (cb != NULL) + { + cb(); + } +} + +NET_BUF_POOL_FIXED_DEFINE(iso_rx_pool, CONFIG_BT_ISO_RX_BUF_COUNT, + BT_ISO_SDU_BUF_SIZE(CONFIG_BT_ISO_RX_MTU), + sizeof(struct bt_conn_rx), + iso_rx_buf_destroy); + +static struct bt_iso_recv_info iso_info_data[CONFIG_BT_ISO_RX_BUF_COUNT]; +#define iso_info(buf) (&iso_info_data[net_buf_id(buf)]) +#endif /* CONFIG_BT_ISO_RX */ + +struct bt_conn iso_conns[CONFIG_BT_ISO_MAX_CHAN]; + +/* TODO: Allow more than one server? */ +#if defined(CONFIG_BT_ISO_CENTRAL) +struct bt_iso_cig cigs[CONFIG_BT_ISO_MAX_CIG]; + +static struct bt_iso_cig *get_cig(const struct bt_iso_chan *iso_chan); +static int hci_le_create_cis(const struct bt_iso_connect_param *param, + size_t count); + +#endif /* CONFIG_BT_ISO_CENTRAL */ + +#if defined(CONFIG_BT_ISO_PERIPHERAL) +static struct bt_iso_server *iso_server; + +static struct bt_conn *bt_conn_add_iso(struct bt_conn *acl); +#endif /* CONFIG_BT_ISO_PERIPHERAL */ + +#if defined(CONFIG_BT_ISO_BROADCAST) +struct bt_iso_big bigs[CONFIG_BT_ISO_MAX_BIG]; + +static struct bt_iso_big *lookup_big_by_handle(uint8_t big_handle); +#endif /* CONFIG_BT_ISO_BROADCAST */ + +struct bt_iso_chan *bt_iso_get_chan_by_conn(const struct bt_conn *iso) +{ + if (!IS_ARRAY_ELEMENT(iso_conns, iso)) + { + LOG_DBG("Invalid iso %p pointer", iso); + return NULL; + } + + if (iso->type != BT_CONN_TYPE_ISO) + { + LOG_DBG("iso %p not initialized", iso); + return NULL; + } + + return iso->iso.chan; +} + +static void bt_iso_sent_cb(struct bt_conn *iso, void *user_data, int err) +{ +#if defined(CONFIG_BT_ISO_TX) + struct bt_iso_chan *chan = iso->iso.chan; + struct bt_iso_chan_ops *ops; + + DEBUGASSERT(chan != NULL); + + ops = chan->ops; + + if (ops != NULL) + { + if (err == 0 && ops->sent != NULL) + { + ops->sent(chan); + } + else if (err != 0 && ops->send_failed != NULL) + { + ops->send_failed(chan, err); + } + else + { + /* no op */ + } + } +#endif /* CONFIG_BT_ISO_TX */ +} + +void hci_iso(struct net_buf *buf) +{ + struct bt_hci_iso_hdr *hdr; + uint16_t handle; + uint16_t len; + struct bt_conn *iso; + uint8_t flags; + + BT_ISO_DATA_DBG("buf %p", buf); + + if (buf->len < sizeof(*hdr)) + { + LOG_ERR("Invalid HCI ISO packet size (%u)", buf->len); + net_buf_unref(buf); + return; + } + + hdr = net_buf_pull_mem(buf, sizeof(*hdr)); + len = bt_iso_hdr_len(sys_le16_to_cpu(hdr->len)); + handle = sys_le16_to_cpu(hdr->handle); + flags = bt_iso_flags(handle); + + iso(buf)->handle = bt_iso_handle(handle); + iso(buf)->index = BT_CONN_INDEX_INVALID; + + BT_ISO_DATA_DBG("handle %u len %u flags %u", iso(buf)->handle, len, flags); + + if (buf->len != len) + { + LOG_ERR("ISO data length mismatch (%u != %u)", buf->len, len); + net_buf_unref(buf); + return; + } + + iso = bt_conn_lookup_handle(iso(buf)->handle, BT_CONN_TYPE_ISO); + if (iso == NULL) + { + LOG_ERR("Unable to find conn for handle %u", iso(buf)->handle); + net_buf_unref(buf); + return; + } + + iso(buf)->index = bt_conn_index(iso); + + bt_conn_recv(iso, buf, flags); + bt_conn_unref(iso); +} + +/* Pull data from the ISO layer */ + +static struct net_buf *iso_data_pull(struct bt_conn *conn, + size_t amount, size_t *length); + +/* Returns true if the ISO layer has data to send on this conn */ + +static bool iso_has_data(struct bt_conn *conn); + +static void iso_get_and_clear_cb( + struct bt_conn *conn, struct net_buf *buf, bt_conn_tx_cb_t *cb, void **ud) +{ + if (IS_ENABLED(CONFIG_BT_ISO_TX)) + { + *cb = bt_iso_sent_cb; + } + else + { + *cb = NULL; + } + + *ud = NULL; +} + +static struct bt_conn *iso_new(void) +{ + struct bt_conn *iso = bt_conn_new(iso_conns, ARRAY_SIZE(iso_conns)); + + if (iso) + { + iso->type = BT_CONN_TYPE_ISO; + iso->tx_data_pull = iso_data_pull; + iso->get_and_clear_cb = iso_get_and_clear_cb; + iso->has_data = iso_has_data; + } + else + { + LOG_DBG("Could not create new ISO"); + } + + return iso; +} + +static int hci_le_setup_iso_data_path(const struct bt_conn *iso, uint8_t dir, + const struct bt_iso_chan_path *path) +{ + struct bt_hci_cp_le_setup_iso_path *cp; + struct bt_hci_rp_le_setup_iso_path *rp; + struct net_buf *buf; + struct net_buf *rsp; + uint8_t *cc; + int err; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(iso->handle); + cp->path_dir = dir; + cp->path_id = path->pid; + cp->codec_id.coding_format = path->format; + cp->codec_id.company_id = sys_cpu_to_le16(path->cid); + cp->codec_id.vs_codec_id = sys_cpu_to_le16(path->vid); + sys_put_le24(path->delay, cp->controller_delay); + cp->codec_config_len = path->cc_len; + cc = net_buf_add(buf, path->cc_len); + if (path->cc_len) + { + memcpy(cc, path->cc, path->cc_len); + } + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SETUP_ISO_PATH, buf, &rsp); + if (err) + { + return err; + } + + rp = (void *)rsp->data; + if (rp->status || (sys_le16_to_cpu(rp->handle) != iso->handle)) + { + err = -EIO; + } + + net_buf_unref(rsp); + + return err; +} + +static void bt_iso_chan_add(struct bt_conn *iso, struct bt_iso_chan *chan) +{ + /* Attach ISO channel to the connection */ + + chan->iso = iso; + iso->iso.chan = chan; +#if defined(CONFIG_BT_ISO_TX) + k_fifo_init(&iso->iso.txq); +#endif /* CONFIG_BT_ISO_TX */ + + LOG_DBG("iso %p chan %p", iso, chan); +} + +static int validate_iso_setup_data_path_parms( + const struct bt_iso_chan *chan, uint8_t dir, + const struct bt_iso_chan_path *path) +{ + struct bt_conn *iso; + + if (chan == NULL) + { + LOG_DBG("chan is NULL"); + + return -EINVAL; + } + + if (path == NULL) + { + LOG_DBG("path is NULL"); + + return -EINVAL; + } + + if (dir != BT_HCI_DATAPATH_DIR_HOST_TO_CTLR && + dir != BT_HCI_DATAPATH_DIR_CTLR_TO_HOST) + { + LOG_DBG("Invalid dir: %u", dir); + + return -EINVAL; + } + + iso = chan->iso; + if (iso == NULL) + { + LOG_DBG("chan %p not associated with a CIS/BIS handle", chan); + + return -ENODEV; + } + + if (!iso->iso.info.can_recv && dir == BT_HCI_DATAPATH_DIR_CTLR_TO_HOST) + { + LOG_DBG("Invalid dir %u for chan %p that cannot receive data", dir, + chan); + + return -EINVAL; + } + + if (!iso->iso.info.can_send && dir == BT_HCI_DATAPATH_DIR_HOST_TO_CTLR) + { + LOG_DBG("Invalid dir %u for chan %p that cannot send data", dir, chan); + + return -EINVAL; + } + + if (path->pid != BT_ISO_DATA_PATH_HCI && + !IN_RANGE(path->pid, BT_ISO_DATA_PATH_VS_ID_MIN, + BT_ISO_DATA_PATH_VS_ID_MAX)) + { + LOG_DBG("Invalid pid %u", path->pid); + + return -EINVAL; + } + + if (path->format > BT_HCI_CODING_FORMAT_G729A && + path->format != BT_HCI_CODING_FORMAT_VS) + { + LOG_DBG("Invalid format %u", path->format); + + return -EINVAL; + } + + if (path->delay > BT_ISO_CONTROLLER_DELAY_MAX) + { + LOG_DBG("Invalid delay: %u", path->delay); + + return -EINVAL; + } + + if (path->cc_len > 0U && path->cc == NULL) + { + LOG_DBG("No CC provided for CC length %u", path->cc_len); + + return -EINVAL; + } + + return 0; +} + +int bt_iso_setup_data_path(const struct bt_iso_chan *chan, uint8_t dir, + const struct bt_iso_chan_path *path) +{ + int err; + + err = validate_iso_setup_data_path_parms(chan, dir, path); + if (err != 0) + { + return err; + } + + err = hci_le_setup_iso_data_path(chan->iso, dir, path); + if (err != 0) + { + LOG_DBG("Failed to set data path: %d", err); + + /* Return known possible errors */ + + if (err == -ENOBUFS || err == -EIO || err == -EACCES) + { + return err; + } + + LOG_DBG("Unknown error from hci_le_setup_iso_data_path: %d", err); + + return -ENOEXEC; + } + + return 0; +} + +static int hci_le_remove_iso_data_path(struct bt_conn *iso, uint8_t dir) +{ + struct bt_hci_cp_le_remove_iso_path *cp; + struct bt_hci_rp_le_remove_iso_path *rp; + struct net_buf *buf; + struct net_buf *rsp; + int err; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(iso->handle); + /* The path_dir is a bitfield and it's technically possible to do BIT(0) | + * BIT(1) but for + * simplicity our API only supports removing a single ISO data path at a + * time. We can convert from BT_HCI_DATAPATH_DIR_HOST_TO_CTLR and + * BT_HCI_DATAPATH_DIR_CTLR_TO_HOST to the proper values just by using + * `BIT` + */ + + cp->path_dir = BIT(dir); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_REMOVE_ISO_PATH, buf, &rsp); + if (err) + { + return err; + } + + rp = (void *)rsp->data; + if (rp->status || (sys_le16_to_cpu(rp->handle) != iso->handle)) + { + err = -EIO; + } + + net_buf_unref(rsp); + + return err; +} + +static int validate_iso_remove_data_path(const struct bt_iso_chan *chan, + uint8_t dir) +{ + struct bt_conn *iso; + + if (chan == NULL) + { + LOG_DBG("chan is NULL"); + + return -EINVAL; + } + + if (dir != BT_HCI_DATAPATH_DIR_HOST_TO_CTLR && + dir != BT_HCI_DATAPATH_DIR_CTLR_TO_HOST) + { + LOG_DBG("Invalid dir: %u", dir); + + return -EINVAL; + } + + iso = chan->iso; + if (iso == NULL) + { + LOG_DBG("chan %p not associated with a CIS/BIS handle", chan); + + return -ENODEV; + } + + return 0; +} + +int bt_iso_remove_data_path(const struct bt_iso_chan *chan, uint8_t dir) +{ + int err; + + err = validate_iso_remove_data_path(chan, dir); + if (err != 0) + { + return err; + } + + err = hci_le_remove_iso_data_path(chan->iso, dir); + if (err != 0) + { + LOG_DBG("Failed to remove data path: %d", err); + + /* Return known possible errors */ + + if (err == -ENOBUFS || err == -EIO || err == -EACCES) + { + return err; + } + + LOG_DBG("Unknown error from hci_le_remove_iso_data_path: %d", err); + + return -ENOEXEC; + } + + return 0; +} + +void bt_iso_connected(struct bt_conn *iso) +{ + struct bt_iso_chan *chan; + + if (iso == NULL || iso->type != BT_CONN_TYPE_ISO) + { + LOG_DBG("Invalid parameters: iso %p iso->type %u", iso, + iso ? iso->type : 0); + return; + } + + LOG_DBG("%p", iso); + + chan = iso_chan(iso); + if (chan == NULL) + { + LOG_ERR("Could not lookup chan from connected ISO"); + return; + } + + bt_iso_chan_set_state(chan, BT_ISO_STATE_CONNECTED); + + if (chan->ops->connected) + { + chan->ops->connected(chan); + } +} + +static void bt_iso_chan_disconnected(struct bt_iso_chan *chan, + uint8_t reason) +{ + uint8_t conn_type; + + LOG_DBG("%p, reason 0x%02x", chan, reason); + + DEBUGASSERT(chan->iso != NULL); + +#if defined(CONFIG_BT_ISO_TX) + struct net_buf *buf; + + /* release buffers from tx_queue */ + + while ((buf = k_fifo_get(&chan->iso->iso.txq, 0))) + { + DEBUGASSERT(!bt_buf_has_view(buf)); + net_buf_unref(buf); + } +#endif /* CONFIG_BT_ISO_TX */ + + bt_iso_chan_set_state(chan, BT_ISO_STATE_DISCONNECTED); + bt_conn_set_state(chan->iso, BT_CONN_DISCONNECT_COMPLETE); + + /* Calling disconnected before final cleanup allows users to use + * bt_iso_chan_get_info in the callback and to be more similar to the ACL + * disconnected callback. This also means that the channel cannot be reused + * or memset in the callback + */ + + if (chan->ops->disconnected) + { + chan->ops->disconnected(chan, reason); + } + + conn_type = chan->iso->iso.info.type; + + /* The peripheral does not have the concept of a CIG, so once a CIS + * disconnects it is completely freed by unref'ing it + */ + + if (IS_ENABLED(CONFIG_BT_ISO_UNICAST) && + (conn_type == BT_ISO_CHAN_TYPE_CENTRAL || + conn_type == BT_ISO_CHAN_TYPE_PERIPHERAL)) + { + bt_iso_cleanup_acl(chan->iso); + + if (conn_type == BT_ISO_CHAN_TYPE_PERIPHERAL) + { + bt_conn_drop(&chan->iso); +#if defined(CONFIG_BT_ISO_CENTRAL) + } + else + { + bool is_chan_connected; + struct bt_iso_cig *cig; + struct bt_iso_chan *cis_chan; + + /* Update CIG state */ + + cig = get_cig(chan); + DEBUGASSERT(cig != NULL); + + is_chan_connected = false; + list_for_every_container(&cig->cis_channels, cis_chan, node) + { + if (cis_chan->state != BT_ISO_STATE_DISCONNECTED) + { + is_chan_connected = true; + break; + } + } + + if (!is_chan_connected) + { + cig->state = BT_ISO_CIG_STATE_INACTIVE; + } +#endif /* CONFIG_BT_ISO_CENTRAL */ + } + } + else if (IS_ENABLED(CONFIG_BT_ISO_BROADCASTER) && + conn_type == BT_ISO_CHAN_TYPE_BROADCASTER) + { + /* BIS do not get a HCI Disconnected event and will not handle cleanup + * of pending TX complete in the same way as ACL and CIS do. Call + * bt_conn_tx_notify directly here to flush the chan->iso->tx_complete + * for each disconnected BIS + */ + + bt_conn_tx_notify(chan->iso, true); + } + else + { + /* No special handling for BT_ISO_CHAN_TYPE_SYNC_RECEIVER */ + } +} + +void bt_iso_disconnected(struct bt_conn *iso) +{ + struct bt_iso_chan *chan; + + if (iso == NULL || iso->type != BT_CONN_TYPE_ISO) + { + LOG_DBG("Invalid parameters: iso %p iso->type %u", iso, + iso ? iso->type : 0); + return; + } + + LOG_DBG("%p", iso); + + chan = iso_chan(iso); + if (chan == NULL) + { + LOG_ERR("Could not lookup chan from disconnected ISO"); + return; + } + + bt_iso_chan_disconnected(chan, iso->err); +} + +const char *bt_iso_chan_state_str(enum bt_iso_state state) +{ + switch (state) + { + case BT_ISO_STATE_DISCONNECTED: + return "disconnected"; + case BT_ISO_STATE_CONNECTING: + return "connecting"; + case BT_ISO_STATE_CONNECTED: + return "connected"; + case BT_ISO_STATE_DISCONNECTING: + return "disconnecting"; + default: + return "unknown"; + } +} + +#if defined(CONFIG_BT_ISO_LOG_LEVEL_DBG) + +void bt_iso_chan_set_state_debug( + struct bt_iso_chan *chan, enum bt_iso_state state, const char *func, + int line) +{ + LOG_DBG("chan %p iso %p %s -> %s", chan, chan->iso, + bt_iso_chan_state_str(chan->state), + bt_iso_chan_state_str(state)); + + /* check transitions validness */ + + switch (state) + { + case BT_ISO_STATE_DISCONNECTED: + + /* regardless of old state always allows this states */ + + break; + case BT_ISO_STATE_CONNECTING: + if (chan->state != BT_ISO_STATE_DISCONNECTED) + { + LOG_WRN("%s()%d: invalid transition", func, line); + } + break; + case BT_ISO_STATE_CONNECTED: + if (chan->state != BT_ISO_STATE_CONNECTING) + { + LOG_WRN("%s()%d: invalid transition", func, line); + } + break; + case BT_ISO_STATE_DISCONNECTING: + if (chan->state != BT_ISO_STATE_CONNECTING && + chan->state != BT_ISO_STATE_CONNECTED) + { + LOG_WRN("%s()%d: invalid transition", func, line); + } + break; + default: + LOG_ERR("%s()%d: unknown (%u) state was set", func, line, state); + return; + } + + chan->state = state; +} +#else +void bt_iso_chan_set_state(struct bt_iso_chan *chan, enum bt_iso_state state) +{ + chan->state = state; +} +#endif /* CONFIG_BT_ISO_LOG_LEVEL_DBG */ + +int bt_iso_chan_get_info(const struct bt_iso_chan *chan, + struct bt_iso_info *info) +{ + if (chan == NULL) + { + LOG_DBG("chan is NULL"); + return -EINVAL; + } + + if (chan->iso == NULL) + { + LOG_DBG("chan->iso is NULL"); + return -EINVAL; + } + + if (info == NULL) + { + LOG_DBG("info is NULL"); + return -EINVAL; + } + + (void)memcpy(info, &chan->iso->iso.info, sizeof(*info)); + + return 0; +} + +#if defined(CONFIG_BT_ISO_RX) +struct net_buf *bt_iso_get_rx(clock_t timeout) +{ + struct net_buf *buf = net_buf_alloc( + &iso_rx_pool, ((timeout) == + (((clock_t)-1))) ? -1 : (int32_t)TICK2MSEC(timeout)); + + if (buf) + { + net_buf_add_u8(buf, BT_HCI_H4_ISO); + } + + return buf; +} + +void bt_iso_buf_rx_freed_cb_set(bt_iso_buf_rx_freed_cb_t cb) +{ + atomic_ptr_xchg(&buf_rx_freed_cb, (void *)cb); +} + +void bt_iso_recv(struct bt_conn *iso, struct net_buf *buf, uint8_t flags) +{ + struct bt_hci_iso_sdu_hdr *hdr; + struct bt_iso_chan *chan; + uint8_t pb; + uint8_t ts; + uint16_t len; + uint16_t pkt_seq_no; + + pb = bt_iso_flags_pb(flags); + ts = bt_iso_flags_ts(flags); + + BT_ISO_DATA_DBG("handle %u len %u flags 0x%02x pb 0x%02x ts 0x%02x", + iso->handle, buf->len, flags, + pb, ts); + + /* When the PB_Flag does not equal BT_ISO_START or BT_ISO_SINGLE, + * the fields Time_Stamp, Packet_Sequence_Number, Packet_Status_Flag and + * ISO_SDU_Length are omitted from the HCI ISO Data packet. + */ + + switch (pb) + { + case BT_ISO_START: + case BT_ISO_SINGLE: + iso_info(buf)->flags = 0; + + /* The ISO_Data_Load field contains either the first fragment + * of an SDU or a complete SDU. + */ + + if (ts) + { + struct bt_hci_iso_sdu_ts_hdr *ts_hdr; + + if (buf->len < sizeof(*ts_hdr)) + { + LOG_ERR("Unexpected ISO buffer size %u (< %zu)", buf->len, + sizeof(*ts_hdr)); + net_buf_unref(buf); + return; + } + + ts_hdr = net_buf_pull_mem(buf, sizeof(*ts_hdr)); + iso_info(buf)->ts = sys_le32_to_cpu(ts_hdr->ts); + + hdr = &ts_hdr->sdu; + iso_info(buf)->flags |= BT_ISO_FLAGS_TS; + } + else + { + if (buf->len < sizeof(*hdr)) + { + LOG_ERR("Unexpected ISO buffer size %u (< %zu)", buf->len, + sizeof(*hdr)); + net_buf_unref(buf); + return; + } + + hdr = net_buf_pull_mem(buf, sizeof(*hdr)); + + /* TODO: Generate a timestamp? */ + + iso_info(buf)->ts = 0x00000000; + } + + len = sys_le16_to_cpu(hdr->slen); + flags = bt_iso_pkt_flags(len); + len = bt_iso_pkt_len(len); + pkt_seq_no = sys_le16_to_cpu(hdr->sn); + iso_info(buf)->seq_num = pkt_seq_no; + if (flags == BT_ISO_DATA_VALID) + { + iso_info(buf)->flags |= BT_ISO_FLAGS_VALID; + } + else if (flags == BT_ISO_DATA_INVALID) + { + iso_info(buf)->flags |= BT_ISO_FLAGS_ERROR; + } + else if (flags == BT_ISO_DATA_NOP) + { + iso_info(buf)->flags |= BT_ISO_FLAGS_LOST; + } + else + { + LOG_WRN("Invalid ISO packet status flag: %u", flags); + iso_info(buf)->flags = 0; + } + + BT_ISO_DATA_DBG("%s, len %u total %u flags 0x%02x timestamp %u", + pb == BT_ISO_START ? "Start" : "Single", buf->len, + len, + flags, + iso_info(buf)->ts); + + if (iso->rx) + { + LOG_ERR("Unexpected ISO %s fragment", + pb == BT_ISO_START ? "Start" : "Single"); + bt_conn_reset_rx_state(iso); + } + + iso->rx = buf; + iso->iso.sdu_len = len; + + if (pb == BT_ISO_SINGLE) + { + /* For single fragments the packet length shall match the + * `buf->len` + */ + + if (len != buf->len) + { + LOG_ERR("Unexpected ISO single fragment length %u != %u", + len, + buf->len); + bt_conn_reset_rx_state(iso); + return; + } + + break; + } + else if (pb == BT_ISO_START) + { + /* For start fragments the packet length shall be larger than + * `buf->len` + */ + + if (len <= buf->len) + { + LOG_ERR("Unexpected ISO start fragment length %u <= %u", len, + buf->len); + bt_conn_reset_rx_state(iso); + } + + return; + } + else + { + CODE_UNREACHABLE; + } + + case BT_ISO_CONT: + /* The ISO_Data_Load field contains a continuation fragment of + * an SDU. + */ + + if (!iso->rx) + { + LOG_ERR("Unexpected ISO continuation fragment"); + net_buf_unref(buf); + return; + } + + BT_ISO_DATA_DBG("Cont, len %u sdu_len %u", buf->len, + iso->iso.sdu_len); + + if (buf->len > net_buf_tailroom(iso->rx)) + { + LOG_ERR("Not enough buffer space for ISO data"); + bt_conn_reset_rx_state(iso); + net_buf_unref(buf); + return; + } + + net_buf_add_mem(iso->rx, buf->data, buf->len); + net_buf_unref(buf); + return; + + case BT_ISO_END: + /* The ISO_Data_Load field contains the last fragment of an + * SDU. + */ + + BT_ISO_DATA_DBG("End, len %u sdu_len %u", buf->len, + iso->iso.sdu_len); + + if (iso->rx == NULL) + { + LOG_ERR("Unexpected ISO end fragment"); + net_buf_unref(buf); + return; + } + + if (buf->len > net_buf_tailroom(iso->rx)) + { + LOG_ERR("Not enough buffer space for ISO data"); + bt_conn_reset_rx_state(iso); + net_buf_unref(buf); + return; + } + + (void)net_buf_add_mem(iso->rx, buf->data, buf->len); + net_buf_unref(buf); + + break; + default: + LOG_ERR("Unexpected ISO pb flags (0x%02x)", pb); + bt_conn_reset_rx_state(iso); + net_buf_unref(buf); + return; + } + + if (iso->rx->len != iso->iso.sdu_len) + { + LOG_ERR("ISO SDU len mismatch (%u != %u)", iso->rx->len, + iso->iso.sdu_len); + bt_conn_reset_rx_state(iso); + return; + } + + chan = iso_chan(iso); + if (chan == NULL) + { + LOG_ERR("Could not lookup chan from receiving ISO"); + } + else if (chan->ops->recv != NULL) + { + chan->ops->recv(chan, iso_info(iso->rx), iso->rx); + } + + bt_conn_reset_rx_state(iso); +} +#endif /* CONFIG_BT_ISO_RX */ + +static bool iso_has_data(struct bt_conn *conn) +{ +#if defined(CONFIG_BT_ISO_TX) + return ((conn->iso.chan->state == BT_ISO_STATE_CONNECTED) && + !k_fifo_is_empty(&conn->iso.txq)); +#else /* !CONFIG_BT_ISO_TX */ + return false; +#endif /* CONFIG_BT_ISO_TX */ +} + +static struct net_buf *iso_data_pull(struct bt_conn *conn, + size_t amount, size_t *length) +{ +#if defined(CONFIG_BT_ISO_TX) + BT_ISO_DATA_DBG("conn %p amount %d", conn, amount); + + /* Leave the PDU buffer in the queue until we have sent all its + * fragments. + */ + + struct net_buf *q_frag = k_fifo_peek_head(&conn->iso.txq); + + if (!q_frag) + { + BT_ISO_DATA_DBG("signaled ready but no frag available"); + + /* Service other connections */ + + bt_tx_irq_raise(); + + return NULL; + } + + DEBUGASSERT(conn->state == BT_CONN_CONNECTED); + + if (conn->iso.chan->state != BT_ISO_STATE_CONNECTED) + { + LOG_DBG("channel has been disconnected"); + + /* Service other connections */ + + bt_tx_irq_raise(); + + return NULL; + } + + if (bt_buf_has_view(q_frag)) + { + /* This should not happen. conn.c should wait until the view is + * destroyed before requesting more data. + */ + + LOG_DBG("already have view"); + return NULL; + } + + struct net_buf *frag = net_buf_ref(q_frag); + bool last_frag = amount >= frag->len; + + if (last_frag) + { + q_frag = k_fifo_get(&conn->iso.txq, 0); + + BT_ISO_DATA_DBG("last frag, pop buf"); + DEBUGASSERT(q_frag == frag); + + net_buf_unref(q_frag); + } + + *length = frag->len; + + return frag; +#else + return NULL; +#endif +} + +#if defined(CONFIG_BT_ISO_TX) +static uint16_t iso_chan_max_data_len(const struct bt_iso_chan *chan) +{ + size_t max_controller_data_len; + uint16_t max_data_len; + + if (chan->qos->tx == NULL) + { + return 0; + } + + max_data_len = chan->qos->tx->sdu; + + /* Ensure that the SDU fits when using all the buffers */ + + max_controller_data_len = bt_dev.le.iso_mtu * bt_dev.le.iso_limit; + + /* Update the max_data_len to take the max_controller_data_len into account + */ + + max_data_len = MIN(max_data_len, max_controller_data_len); + + return max_data_len; +} + +int conn_iso_send(struct bt_conn *conn, + struct net_buf *buf, enum bt_iso_timestamp has_ts) +{ + if (buf->user_data_size < CONFIG_BT_CONN_TX_USER_DATA_SIZE) + { + LOG_ERR("not enough room in user_data %d < %d pool %u", + buf->user_data_size, + CONFIG_BT_CONN_TX_USER_DATA_SIZE, net_buf_id(buf)); + return -EINVAL; + } + + k_fifo_put(&conn->iso.txq, buf); + BT_ISO_DATA_DBG("%p put on list", buf); + + /* only one ISO channel per conn-object */ + + bt_conn_data_ready(conn); + + return 0; +} + +static int validate_send( + const struct bt_iso_chan *chan, const struct net_buf *buf, + uint8_t hdr_size) +{ + const struct bt_conn *iso_conn; + uint16_t max_data_len; + + if (!chan || !buf) + { + LOG_DBG("Invalid parameters: chan %p buf %p", chan, buf); + return -EINVAL; + } + + BT_ISO_DATA_DBG("chan %p len %zu", chan, net_buf_frags_len(buf)); + + iso_conn = chan->iso; + if (iso_conn == NULL || iso_conn->state != BT_CONN_CONNECTED || + chan->state != BT_ISO_STATE_CONNECTED) + { + LOG_DBG("Channel %p not connected", chan); + return -ENOTCONN; + } + + if (!iso_conn->iso.info.can_send) + { + LOG_DBG("Channel %p not able to send", chan); + return -EINVAL; + } + + if (net_buf_headroom(buf) != BT_BUF_ISO_SIZE(0)) + { + LOG_DBG("Buffer headroom (%d) != BT_BUF_ISO_SIZE(0) (%d) bytes", + net_buf_headroom(buf), + BT_BUF_ISO_SIZE(0)); + + /* DO NOT remove this check. We rely on precise headroom further + * below in the stack to determine if `buf` contains a timestamp field + * or not. See conn.c:contains_iso_timestamp. + */ + + return -EMSGSIZE; + } + + if (buf->size < hdr_size) + { + LOG_DBG("Channel %p cannot send ISO packet with buffer size %u", chan, + buf->size); + + return -EMSGSIZE; + } + + max_data_len = iso_chan_max_data_len(chan); + if (buf->len > max_data_len) + { + LOG_DBG("Channel %p cannot send %u octets, maximum %u", chan, buf->len, + max_data_len); + return -EMSGSIZE; + } + + return 0; +} + +int bt_iso_chan_send(struct bt_iso_chan *chan, + struct net_buf *buf, uint16_t seq_num) +{ + struct bt_hci_iso_sdu_hdr *hdr; + struct bt_conn *iso_conn; + int err; + + err = validate_send(chan, buf, BT_HCI_ISO_SDU_HDR_SIZE); + if (err != 0) + { + return err; + } + + BT_ISO_DATA_DBG("chan %p len %zu", chan, net_buf_frags_len(buf)); + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->sn = sys_cpu_to_le16(seq_num); + hdr->slen = sys_cpu_to_le16( + bt_iso_pkt_len_pack(net_buf_frags_len(buf) - sizeof(*hdr), + BT_ISO_DATA_VALID)); + + iso_conn = chan->iso; + + return conn_iso_send(iso_conn, buf, BT_ISO_TS_ABSENT); +} + +int bt_iso_chan_send_ts( + struct bt_iso_chan *chan, struct net_buf *buf, uint16_t seq_num, + uint32_t ts) +{ + struct bt_hci_iso_sdu_ts_hdr *hdr; + struct bt_conn *iso_conn; + int err; + + err = validate_send(chan, buf, BT_HCI_ISO_SDU_TS_HDR_SIZE); + if (err != 0) + { + return err; + } + + BT_ISO_DATA_DBG("chan %p len %zu ts %u", chan, net_buf_frags_len(buf), ts); + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->ts = sys_cpu_to_le32(ts); + hdr->sdu.sn = sys_cpu_to_le16(seq_num); + hdr->sdu.slen = sys_cpu_to_le16( + bt_iso_pkt_len_pack(net_buf_frags_len(buf) - sizeof(*hdr), + BT_ISO_DATA_VALID)); + + iso_conn = chan->iso; + + return conn_iso_send(iso_conn, buf, BT_ISO_TS_PRESENT); +} + +#if defined(CONFIG_BT_ISO_CENTRAL) || defined(CONFIG_BT_ISO_BROADCASTER) +static bool valid_chan_io_qos( + const struct bt_iso_chan_io_qos *io_qos, bool is_tx, bool is_broadcast, + bool advanced) +{ + if (!IN_RANGE(io_qos->phy, BT_GAP_LE_PHY_1M, BT_GAP_LE_PHY_CODED)) + { + LOG_DBG("Invalid PHY %u", io_qos->phy); + + return false; + } + + if (IS_ENABLED(CONFIG_BT_ISO_BROADCASTER) && is_broadcast && + io_qos->rtn > BT_ISO_BROADCAST_RTN_MAX) + { + LOG_DBG("Invalid RTN %u", io_qos->phy); + + return false; + } + +#if defined(CONFIG_BT_ISO_TEST_PARAMS) + if (advanced) + { + if (IS_ENABLED(CONFIG_BT_ISO_BROADCASTER) && is_broadcast) + { + if (!IN_RANGE(io_qos->max_pdu, BT_ISO_BROADCAST_PDU_MIN, + BT_ISO_PDU_MAX)) + { + LOG_DBG("Invalid broadcast PDU %u", io_qos->max_pdu); + + return false; + } + } + else if (IS_ENABLED(CONFIG_BT_ISO_CENTRAL)) + { + if (!IN_RANGE(io_qos->max_pdu, BT_ISO_CONNECTED_PDU_MIN, + BT_ISO_PDU_MAX)) + { + LOG_DBG("Invalid unicast PDU %u", io_qos->max_pdu); + + return false; + } + } + + if (!IN_RANGE(io_qos->burst_number, BT_ISO_BN_MIN, BT_ISO_BN_MAX)) + { + LOG_DBG("Invalid BN %u", io_qos->burst_number); + + return false; + } + } +#else + ARG_UNUSED(advanced); +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ + + return true; +} +#endif /* CONFIG_BT_ISO_CENTRAL || CONFIG_BT_ISO_BROADCASTER */ + +int bt_iso_chan_get_tx_sync(const struct bt_iso_chan *chan, + struct bt_iso_tx_info *info) +{ + struct bt_hci_cp_le_read_iso_tx_sync *cp; + struct bt_hci_rp_le_read_iso_tx_sync *rp; + struct net_buf *buf; + struct net_buf *rsp = NULL; + int err; + + if (chan == NULL) + { + LOG_DBG("chan is NULL"); + return -EINVAL; + } + + if (chan->iso == NULL) + { + LOG_DBG("chan->iso is NULL"); + return -EINVAL; + } + + if (info == NULL) + { + LOG_DBG("info is NULL"); + return -EINVAL; + } + + if (chan->state != BT_ISO_STATE_CONNECTED) + { + return -ENOTCONN; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOMEM; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(chan->iso->handle); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_READ_ISO_TX_SYNC, buf, &rsp); + if (err) + { + return err; + } + + if (rsp) + { + rp = (struct bt_hci_rp_le_read_iso_tx_sync *)rsp->data; + + info->ts = sys_le32_to_cpu(rp->timestamp); + info->seq_num = sys_le16_to_cpu(rp->seq); + info->offset = sys_get_le24(rp->offset); + + net_buf_unref(rsp); + } + else + { + return -ENOTSUP; + } + + return 0; +} +#endif /* CONFIG_BT_ISO_TX */ + +#if defined(CONFIG_BT_ISO_UNICAST) +int bt_iso_chan_disconnect(struct bt_iso_chan *chan) +{ + int err; + + if (!chan) + { + LOG_DBG("Invalid parameter: chan %p", chan); + return -EINVAL; + } + + if (chan->iso == NULL) + { + LOG_DBG("Channel has not been initialized in a CIG"); + return -EINVAL; + } + + if (chan->iso->iso.acl == NULL || chan->state == BT_ISO_STATE_DISCONNECTED) + { + LOG_DBG("Channel is not connected"); + return -ENOTCONN; + } + + if (chan->state == BT_ISO_STATE_DISCONNECTING) + { + LOG_DBG("Already disconnecting"); + + return -EALREADY; + } + + if (IS_ENABLED(CONFIG_BT_ISO_PERIPHERAL) && + chan->iso->role == BT_HCI_ROLE_PERIPHERAL && + chan->state == BT_ISO_STATE_CONNECTING) + { + /* A CIS peripheral is not allowed to disconnect a CIS in the + * connecting state - It has to wait for a CIS Established event + */ + + return -EAGAIN; + } + + err = bt_conn_disconnect(chan->iso, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + if (err == 0) + { + bt_iso_chan_set_state(chan, BT_ISO_STATE_DISCONNECTING); + } + + return err; +} + +static bool bt_iso_acl_has_cis(const struct bt_conn *acl) +{ + ARRAY_FOR_EACH_PTR(iso_conns, iso_conn) + { + struct bt_conn *iso = bt_conn_ref(iso_conn); + + if (iso == NULL) + { + continue; + } + + if (iso->iso.acl == acl) + { + bt_conn_unref(iso); + + return true; + } + + bt_conn_unref(iso); + } + + return false; +} + +void bt_iso_cleanup_acl(struct bt_conn *iso) +{ + struct bt_conn *acl = bt_conn_take(&iso->iso.acl); + + LOG_DBG("%p", iso); + + if (acl != NULL) + { + /* If we have removed the last ACL reference, trigger the deferred work + * to finalize the ACL disconnection + */ + + if (acl->state == BT_CONN_DISCONNECTED && !bt_iso_acl_has_cis(acl)) + { + LOG_DBG("Trigger disconnect work for ACL %p", acl); + + __maybe_unused const int err = + bt_work_schedule(&acl->deferred_work, + 0); + + DEBUGASSERT(err >= 0); + } + + bt_conn_unref(acl); + } +} + +static void log_cis_info(const struct bt_conn_iso *iso_conn) +{ + const struct bt_iso_info *info = &iso_conn->info; + const struct bt_iso_unicast_info *unicast_info = &info->unicast; + const struct bt_iso_unicast_tx_info + *peripheral = &unicast_info->peripheral; + const struct bt_iso_unicast_tx_info *central = &unicast_info->central; + const struct bt_iso_chan *chan = iso_conn->chan; + + DEBUGASSERT(chan != NULL); + + const struct bt_iso_chan_io_qos *tx = chan->qos == NULL ? + NULL : chan->qos->tx; + const struct bt_iso_chan_io_qos *rx = chan->qos == NULL ? + NULL : chan->qos->rx; + + LOG_DBG("iso_chan %p established: tx %p, rx %p, iso_interval 0x%04X, " + "max_subevent 0x%02X, " + "cig_id 0x%02X, cis_id 0x%02X, cig_sync_delay 0x%06X, " + "cis_sync_delay 0x%06X, " + "subinterval 0x%08X%s", + chan, tx, rx, info->iso_interval, info->max_subevent, + unicast_info->cig_id, + unicast_info->cis_id, unicast_info->cig_sync_delay, + unicast_info->cis_sync_delay, + unicast_info->subinterval, + unicast_info->subinterval == BT_ISO_SUBINTERVAL_UNKNOWN ? + " (unknown)" : ""); + + LOG_DBG("\tC to P: latency 0x%06X, flush_timeout 0x%06X, " + "max_pdu 0x%04X, phy 0x%02X, " + "bn 0x%02X, max_sdu 0x%04X, sdu_interval 0x%06X%s", + central->latency, central->flush_timeout, central->max_pdu, + central->phy, central->bn, + central->max_sdu, central->sdu_interval, + central->sdu_interval == BT_ISO_SDU_INTERVAL_UNKNOWN ? + " (unknown)" : ""); + + LOG_DBG("\tP to C: latency 0x%06X, flush_timeout 0x%06X, " + "max_pdu 0x%04X, phy 0x%02X, " + "bn 0x%02X, max_sdu 0x%04X, sdu_interval 0x%06X%s", + peripheral->latency, peripheral->flush_timeout, + peripheral->max_pdu, peripheral->phy, + peripheral->bn, peripheral->max_sdu, peripheral->sdu_interval, + peripheral->sdu_interval == BT_ISO_SDU_INTERVAL_UNKNOWN ? + " (unknown)" : ""); +} + +static void store_cis_info(const struct bt_hci_evt_le_cis_established *evt, + struct bt_conn *iso) +{ + struct bt_conn_iso *iso_conn = &iso->iso; + struct bt_iso_info *info = &iso_conn->info; + struct bt_iso_unicast_info *unicast_info = &info->unicast; + struct bt_iso_unicast_tx_info *peripheral = &unicast_info->peripheral; + struct bt_iso_unicast_tx_info *central = &unicast_info->central; + const uint8_t c_phy = bt_get_phy(evt->c_phy); + const uint8_t p_phy = bt_get_phy(evt->p_phy); + struct bt_iso_chan_io_qos *tx; + struct bt_iso_chan_io_qos *rx; + struct bt_iso_chan *chan; + + iso_conn = &iso->iso; + chan = iso_conn->chan; + rx = chan->qos->rx; + tx = chan->qos->tx; + + if (iso->role == BT_HCI_ROLE_PERIPHERAL) + { + /* As of BT Core 6.0, we can only get the SDU size if the controller + * supports bt_hci_evt_le_cis_established_v2. Since this is not + * guaranteeds, we fallback to using the PDU size as the SDU size. + */ + + if (rx != NULL) + { + rx->phy = c_phy; + rx->sdu = sys_le16_to_cpu(evt->c_max_pdu); + } + + if (tx != NULL) + { + tx->phy = p_phy; + tx->sdu = sys_le16_to_cpu(evt->p_max_pdu); + } + + iso_conn->info.type = BT_ISO_CHAN_TYPE_PERIPHERAL; + } + else + { + /* values are already set for central - Verify */ + + if (tx != NULL && tx->phy != c_phy) + { + LOG_WRN("Unexpected C to P PHY: %u != %u", c_phy, tx->phy); + /* We assume that tx->phy has become invalid, and will use the + * event from the controller as the truth + */ + + tx->phy = c_phy; + } + + if (rx != NULL && rx->phy != p_phy) + { + LOG_WRN("Unexpected P to C max SDU: %u != %u", p_phy, rx->phy); + /* We assume that rx->phy has become invalid, and will use the + * event from the controller as the truth + */ + + rx->phy = p_phy; + } + } + + /* Verify if device can send */ + + iso_conn->info.can_send = false; + if (tx != NULL) + { + if (iso->role == BT_HCI_ROLE_PERIPHERAL && evt->p_bn > 0) + { + iso_conn->info.can_send = true; + } + else if (iso->role == BT_HCI_ROLE_CENTRAL && evt->c_bn > 0) + { + iso_conn->info.can_send = true; + } + } + + /* Verify if device can recv */ + + iso_conn->info.can_recv = false; + if (rx != NULL) + { + if (iso->role == BT_HCI_ROLE_PERIPHERAL && evt->c_bn > 0) + { + iso_conn->info.can_recv = true; + } + else if (iso->role == BT_HCI_ROLE_CENTRAL && evt->p_bn > 0) + { + iso_conn->info.can_recv = true; + } + } + + info->iso_interval = sys_le16_to_cpu(evt->interval); + info->max_subevent = evt->nse; + + unicast_info->cig_sync_delay = sys_get_le24(evt->cig_sync_delay); + unicast_info->cis_sync_delay = sys_get_le24(evt->cis_sync_delay); + unicast_info->cig_id = iso_conn->info.unicast.cig_id; + unicast_info->cis_id = iso_conn->info.unicast.cis_id; + + central->bn = evt->c_bn; + central->phy = bt_get_phy(evt->c_phy); + central->latency = sys_get_le24(evt->c_latency); + central->max_pdu = sys_le16_to_cpu(evt->c_max_pdu); + + /* Transform to n * 1.25ms */ + + central->flush_timeout = info->iso_interval * evt->c_ft; + + peripheral->bn = evt->p_bn; + peripheral->phy = bt_get_phy(evt->p_phy); + peripheral->latency = sys_get_le24(evt->p_latency); + peripheral->max_pdu = sys_le16_to_cpu(evt->p_max_pdu); + + /* Transform to n * 1.25ms */ + + peripheral->flush_timeout = info->iso_interval * evt->p_ft; + + /* The following values are only available with + * bt_hci_evt_le_cis_established_v2 so initialize them to the "unknown" + * values + */ + + unicast_info->subinterval = BT_ISO_SUBINTERVAL_UNKNOWN; + + if (iso->role == BT_HCI_ROLE_PERIPHERAL) + { + central->max_sdu = central->max_pdu; + central->sdu_interval = BT_ISO_SDU_INTERVAL_UNKNOWN; + + peripheral->max_sdu = peripheral->max_pdu; + peripheral->sdu_interval = BT_ISO_SDU_INTERVAL_UNKNOWN; + } + else + { + central->max_sdu = tx == NULL ? 0 : tx->sdu; + central->sdu_interval = BT_ISO_SDU_INTERVAL_UNKNOWN; + + peripheral->max_sdu = rx == NULL ? 0 : rx->sdu; + peripheral->sdu_interval = BT_ISO_SDU_INTERVAL_UNKNOWN; + } +} + +/** Only store information that is not stored by store_cis_info Assumes that + * store_cis_info has been called first + */ + +static void store_cis_info_v2( + const struct bt_hci_evt_le_cis_established_v2 *evt, struct bt_conn *iso) +{ + struct bt_conn_iso *iso_conn = &iso->iso; + struct bt_iso_info *info = &iso_conn->info; + struct bt_iso_unicast_info *unicast_info = &info->unicast; + struct bt_iso_unicast_tx_info *peripheral = &unicast_info->peripheral; + struct bt_iso_unicast_tx_info *central = &unicast_info->central; + const uint16_t c_max_sdu = sys_le16_to_cpu(evt->c_max_sdu); + const uint16_t p_max_sdu = sys_le16_to_cpu(evt->p_max_sdu); + struct bt_iso_chan_io_qos *tx; + struct bt_iso_chan_io_qos *rx; + struct bt_iso_chan *chan; + + /* The v1 version of the event is a subset of the v2 version - We can thus + * use the store_cis_info function for the majority of the info + */ + + store_cis_info((const struct bt_hci_evt_le_cis_established *)evt, iso); + + chan = iso_conn->chan; + rx = chan->qos->rx; + tx = chan->qos->tx; + + if (iso->role == BT_HCI_ROLE_PERIPHERAL) + { + /* Update the SDU sizes in the IO QoS fields stored by store_cis_info */ + + if (rx != NULL) + { + rx->sdu = c_max_sdu; + } + + if (tx != NULL) + { + tx->sdu = p_max_sdu; + } + } + else + { + /* values are already set for central - Verify */ + + if (tx != NULL && tx->sdu != c_max_sdu) + { + LOG_WRN("Unexpected C to P max SDU: %u != %u", c_max_sdu, tx->sdu); + /* We assume that tx->sdu has become invalid, and will use the + * event from the controller as the truth + */ + + tx->sdu = c_max_sdu; + } + + if (rx != NULL && rx->sdu != p_max_sdu) + { + LOG_WRN("Unexpected P to C max SDU: %u != %u", p_max_sdu, rx->sdu); + /* We assume that rx->sdu has become invalid, and will use the + * event from the controller as the truth + */ + + rx->sdu = p_max_sdu; + } + } + + unicast_info->subinterval = sys_get_le24(evt->sub_interval); + + central->max_sdu = sys_le16_to_cpu(evt->c_max_sdu); + central->sdu_interval = sys_get_le24(evt->c_sdu_interval); + + peripheral->max_sdu = sys_le16_to_cpu(evt->p_max_sdu); + peripheral->sdu_interval = sys_get_le24(evt->p_sdu_interval); +} + +void hci_le_cis_established(struct net_buf *buf) +{ + struct bt_hci_evt_le_cis_established *evt = (void *)buf->data; + uint16_t handle = sys_le16_to_cpu(evt->conn_handle); + struct bt_conn *iso; + + LOG_DBG("status 0x%02x %s handle %u", evt->status, + bt_hci_err_to_str(evt->status), handle); + + /* ISO connection handles are already assigned at this point */ + + iso = bt_conn_lookup_handle(handle, BT_CONN_TYPE_ISO); + if (!iso) + { + /* If it is a local disconnect, then we may have received the + * disconnect complete event before this event, and in which case we do + * not expect to find the CIS object + */ + + if (evt->status != BT_HCI_ERR_OP_CANCELLED_BY_HOST) + { + LOG_ERR("No connection found for handle %u", handle); + } + + return; + } + + if (evt->status == BT_HCI_ERR_SUCCESS) + { + store_cis_info(evt, iso); + log_cis_info(&iso->iso); + bt_conn_set_state(iso, BT_CONN_CONNECTED); + } + else if (iso->role == BT_HCI_ROLE_PERIPHERAL || + evt->status != BT_HCI_ERR_OP_CANCELLED_BY_HOST) + { + iso->err = evt->status; + bt_iso_disconnected(iso); + } + + /* else we wait for disconnect event */ + + bt_conn_unref(iso); +} + +void hci_le_cis_established_v2(struct net_buf *buf) +{ + struct bt_hci_evt_le_cis_established_v2 *evt = (void *)buf->data; + uint16_t handle = sys_le16_to_cpu(evt->conn_handle); + struct bt_conn *iso; + + LOG_DBG("status 0x%02x %s handle %u", evt->status, + bt_hci_err_to_str(evt->status), handle); + + /* ISO connection handles are already assigned at this point */ + + iso = bt_conn_lookup_handle(handle, BT_CONN_TYPE_ISO); + if (!iso) + { + /* If it is a local disconnect, then we may have received the + * disconnect complete event before this event, and in which case we do + * not expect to find the CIS object + */ + + if (evt->status != BT_HCI_ERR_OP_CANCELLED_BY_HOST) + { + LOG_ERR("No connection found for handle %u", handle); + } + + return; + } + + if (evt->status == BT_HCI_ERR_SUCCESS) + { + store_cis_info_v2(evt, iso); + log_cis_info(&iso->iso); + bt_conn_set_state(iso, BT_CONN_CONNECTED); + } + else if (iso->role == BT_HCI_ROLE_PERIPHERAL || + evt->status != BT_HCI_ERR_OP_CANCELLED_BY_HOST) + { + iso->err = evt->status; + bt_iso_disconnected(iso); + } + + /* else we wait for disconnect event */ + + bt_conn_unref(iso); +} + +#if defined(CONFIG_BT_ISO_PERIPHERAL) +int bt_iso_server_register(struct bt_iso_server *server) +{ + if (!server) + { + LOG_DBG("Invalid parameter: server %p", server); + return -EINVAL; + } + + /* Check if controller is ISO capable */ + + if (!BT_FEAT_LE_CIS_PERIPHERAL(bt_dev.le.features)) + { + return -ENOTSUP; + } + + if (iso_server) + { + return -EADDRINUSE; + } + + if (!server->accept) + { + return -EINVAL; + } + + LOG_DBG("%p", server); + + iso_server = server; + + return 0; +} + +int bt_iso_server_unregister(struct bt_iso_server *server) +{ + if (!server) + { + LOG_DBG("Invalid parameter: server %p", server); + return -EINVAL; + } + + if (iso_server != server) + { + return -EINVAL; + } + + iso_server = NULL; + + return 0; +} + +static int iso_accept(struct bt_conn *acl, struct bt_conn *iso) +{ + struct bt_iso_accept_info accept_info; + struct bt_iso_chan *chan; + int err; + + if (!iso || iso->type != BT_CONN_TYPE_ISO) + { + LOG_DBG("Invalid parameters: iso %p iso->type %u", iso, + iso ? iso->type : 0); + return -EINVAL; + } + + LOG_DBG("%p", iso); + + accept_info.acl = acl; + accept_info.cig_id = iso->iso.info.unicast.cig_id; + accept_info.cis_id = iso->iso.info.unicast.cis_id; + + err = iso_server->accept(&accept_info, &chan); + if (err < 0) + { + LOG_ERR("Server failed to accept: %d", err); + return err; + } + + bt_iso_chan_add(iso, chan); + bt_iso_chan_set_state(chan, BT_ISO_STATE_CONNECTING); + + return 0; +} + +static int hci_le_reject_cis(uint16_t handle, uint8_t reason) +{ + struct bt_hci_cp_le_reject_cis *cp; + struct net_buf *buf; + int err; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(handle); + cp->reason = reason; + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_REJECT_CIS, buf, NULL); + if (err) + { + return err; + } + + return 0; +} + +static int hci_le_accept_cis(uint16_t handle) +{ + struct bt_hci_cp_le_accept_cis *cp; + struct net_buf *buf; + int err; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + cp->handle = sys_cpu_to_le16(handle); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_ACCEPT_CIS, buf, NULL); + if (err) + { + return err; + } + + return 0; +} + +void hci_le_cis_req(struct net_buf *buf) +{ + struct bt_hci_evt_le_cis_req *evt = (void *)buf->data; + uint16_t acl_handle = sys_le16_to_cpu(evt->acl_handle); + uint16_t cis_handle = sys_le16_to_cpu(evt->cis_handle); + struct bt_conn *acl; + struct bt_conn *iso; + int err; + + LOG_DBG("acl_handle %u cis_handle %u cig_id %u cis %u", acl_handle, + cis_handle, evt->cig_id, + evt->cis_id); + + if (iso_server == NULL) + { + LOG_DBG("No ISO server registered"); + hci_le_reject_cis(cis_handle, BT_HCI_ERR_UNSPECIFIED); + return; + } + + /* Lookup existing connection with same handle */ + + iso = bt_conn_lookup_handle(cis_handle, BT_CONN_TYPE_ISO); + if (iso) + { + LOG_ERR("Invalid ISO handle %u", cis_handle); + hci_le_reject_cis(cis_handle, BT_HCI_ERR_CONN_LIMIT_EXCEEDED); + bt_conn_unref(iso); + return; + } + + /* Lookup ACL connection to attach */ + + acl = bt_conn_lookup_handle(acl_handle, BT_CONN_TYPE_LE); + if (!acl) + { + LOG_ERR("Invalid ACL handle %u", acl_handle); + hci_le_reject_cis(cis_handle, BT_HCI_ERR_UNKNOWN_CONN_ID); + return; + } + + /* Add ISO connection */ + + iso = bt_conn_add_iso(acl); + + bt_conn_unref(acl); + + if (!iso) + { + LOG_ERR("Could not create and add ISO to ACL %u", acl_handle); + hci_le_reject_cis(cis_handle, BT_HCI_ERR_INSUFFICIENT_RESOURCES); + return; + } + + iso->iso.info.type = BT_ISO_CHAN_TYPE_PERIPHERAL; + iso->iso.info.unicast.cig_id = evt->cig_id; + iso->iso.info.unicast.cis_id = evt->cis_id; + + /* Request application to accept */ + + err = iso_accept(acl, iso); + if (err) + { + LOG_DBG("App rejected ISO %d", err); + bt_iso_cleanup_acl(iso); + bt_conn_unref(iso); + hci_le_reject_cis(cis_handle, BT_HCI_ERR_INSUFFICIENT_RESOURCES); + return; + } + + iso->handle = cis_handle; + iso->role = BT_HCI_ROLE_PERIPHERAL; + bt_conn_set_state(iso, BT_CONN_INITIATING); + + err = hci_le_accept_cis(cis_handle); + if (err) + { + bt_iso_cleanup_acl(iso); + bt_conn_unref(iso); + hci_le_reject_cis(cis_handle, BT_HCI_ERR_INSUFFICIENT_RESOURCES); + return; + } +} + +static struct bt_conn *bt_conn_add_iso(struct bt_conn *acl) +{ + struct bt_conn *iso = iso_new(); + + if (iso == NULL) + { + LOG_ERR("Unable to allocate ISO connection"); + return NULL; + } + + iso->iso.acl = bt_conn_ref(acl); + + return iso; +} +#endif /* CONFIG_BT_ISO_PERIPHERAL */ + +#if defined(CONFIG_BT_ISO_CENTRAL) +static bool valid_chan_qos(const struct bt_iso_chan_qos *qos, bool advanced) +{ +#if defined(CONFIG_BT_ISO_TEST_PARAMS) + if (advanced && !IN_RANGE(qos->num_subevents, BT_ISO_NSE_MIN, + BT_ISO_NSE_MAX)) + { + LOG_DBG("Invalid NSE: %u", qos->num_subevents); + + return false; + } +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ + + if (qos->rx == NULL && qos->tx == NULL) + { + LOG_DBG("Both rx and tx qos are NULL"); + return false; + } + + if (qos->rx != NULL && !valid_chan_io_qos(qos->rx, false, false, advanced)) + { + LOG_DBG("Invalid rx qos"); + return false; + } + + if (qos->tx != NULL && !valid_chan_io_qos(qos->tx, true, false, advanced)) + { + LOG_DBG("Invalid tx qos"); + return false; + } + + return true; +} + +static int hci_le_remove_cig(uint8_t cig_id) +{ + struct bt_hci_cp_le_remove_cig *req; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + req = net_buf_add(buf, sizeof(*req)); + + memset(req, 0, sizeof(*req)); + + req->cig_id = cig_id; + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_REMOVE_CIG, buf, NULL); +} + +static struct net_buf *hci_le_set_cig_params(const struct bt_iso_cig *cig, + const struct bt_iso_cig_param *param) +{ + struct bt_hci_cp_le_set_cig_params *req; + struct bt_hci_cis_params *cis_param; + struct net_buf *buf; + struct net_buf *rsp; + int i; + int err; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return NULL; + } + + req = net_buf_add(buf, sizeof(*req)); + + memset(req, 0, sizeof(*req)); + + req->cig_id = cig->id; + req->c_latency = sys_cpu_to_le16(param->c_to_p_latency); + req->p_latency = sys_cpu_to_le16(param->p_to_c_latency); + sys_put_le24(param->c_to_p_interval, req->c_interval); + sys_put_le24(param->p_to_c_interval, req->p_interval); + + req->sca = param->sca; + req->packing = param->packing; + req->framing = param->framing; + req->num_cis = param->num_cis; + + LOG_DBG("id %u, latency C to P %u, latency P to C %u, " + "interval C to P %u, interval P to C %u, " + "sca %u, packing %u, framing %u, num_cis %u", + cig->id, param->c_to_p_latency, param->p_to_c_latency, + param->c_to_p_interval, + param->p_to_c_interval, + param->sca, + param->packing, + param->framing, + param->num_cis); + + /* Program the cis parameters */ + + for (i = 0; i < param->num_cis; i++) + { + struct bt_iso_chan *cis = param->cis_channels[i]; + struct bt_iso_chan_qos *qos = cis->qos; + + cis_param = net_buf_add(buf, sizeof(*cis_param)); + + memset(cis_param, 0, sizeof(*cis_param)); + + cis_param->cis_id = cis->iso->iso.info.unicast.cis_id; + + if (!qos->tx && !qos->rx) + { + LOG_ERR("Both TX and RX QoS are disabled"); + net_buf_unref(buf); + return NULL; + } + + if (!qos->tx) + { + /* Use RX PHY if TX is not set (disabled) + * to avoid setting invalid values + */ + + cis_param->c_phy = qos->rx->phy; + } + else + { + cis_param->c_sdu = sys_cpu_to_le16(qos->tx->sdu); + cis_param->c_phy = qos->tx->phy; + cis_param->c_rtn = qos->tx->rtn; + } + + if (!qos->rx) + { + /* Use TX PHY if RX is not set (disabled) + * to avoid setting invalid values + */ + + cis_param->p_phy = qos->tx->phy; + } + else + { + cis_param->p_sdu = sys_cpu_to_le16(qos->rx->sdu); + cis_param->p_phy = qos->rx->phy; + cis_param->p_rtn = qos->rx->rtn; + } + + LOG_DBG("[%d]: id %u, c_phy %u, c_sdu %u, c_rtn %u, p_phy %u, p_sdu " + "%u, p_rtn %u", + i, + cis_param->cis_id, cis_param->c_phy, cis_param->c_sdu, + cis_param->c_rtn, + cis_param->p_phy, cis_param->p_sdu, cis_param->p_rtn); + } + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_CIG_PARAMS, buf, &rsp); + if (err) + { + return NULL; + } + + return rsp; +} + +#if defined(CONFIG_BT_ISO_TEST_PARAMS) +static struct net_buf *hci_le_set_cig_test_params( + const struct bt_iso_cig *cig, const struct bt_iso_cig_param *param) +{ + struct bt_hci_cp_le_set_cig_params_test *req; + struct bt_hci_cis_params_test *cis_param; + struct net_buf *buf; + struct net_buf *rsp; + int err; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return NULL; + } + + req = net_buf_add(buf, sizeof(*req)); + + memset(req, 0, sizeof(*req)); + + req->cig_id = cig->id; + sys_put_le24(param->c_to_p_interval, req->c_interval); + sys_put_le24(param->p_to_c_interval, req->p_interval); + + req->c_ft = param->c_to_p_ft; + req->p_ft = param->p_to_c_ft; + req->iso_interval = sys_cpu_to_le16(param->iso_interval); + req->sca = param->sca; + req->packing = param->packing; + req->framing = param->framing; + req->num_cis = param->num_cis; + + LOG_DBG("id %u, SDU interval C to P %u, SDU interval P to C %u, c_ft %u, " + "p_ft %u, " + "iso_interval %u, sca %u, packing %u, framing %u, num_cis %u", + cig->id, param->c_to_p_interval, param->p_to_c_interval, + param->c_to_p_ft, + param->p_to_c_ft, param->iso_interval, param->sca, param->packing, + param->framing, + param->num_cis); + + /* Program the cis parameters */ + + for (uint8_t i = 0U; i < param->num_cis; i++) + { + const struct bt_iso_chan *cis = param->cis_channels[i]; + const struct bt_iso_chan_qos *qos = cis->qos; + + cis_param = net_buf_add(buf, sizeof(*cis_param)); + + memset(cis_param, 0, sizeof(*cis_param)); + + cis_param->cis_id = cis->iso->iso.info.unicast.cis_id; + cis_param->nse = qos->num_subevents; + + if (!qos->tx && !qos->rx) + { + LOG_ERR("Both TX and RX QoS are disabled"); + net_buf_unref(buf); + return NULL; + } + + if (!qos->tx) + { + /* Use RX PHY if TX is not set (disabled) + * to avoid setting invalid values + */ + + cis_param->c_phy = qos->rx->phy; + } + else + { + cis_param->c_sdu = sys_cpu_to_le16(qos->tx->sdu); + cis_param->c_pdu = sys_cpu_to_le16(qos->tx->max_pdu); + cis_param->c_phy = qos->tx->phy; + cis_param->c_bn = qos->tx->burst_number; + } + + if (!qos->rx) + { + /* Use TX PHY if RX is not set (disabled) + * to avoid setting invalid values + */ + + cis_param->p_phy = qos->tx->phy; + } + else + { + cis_param->p_sdu = sys_cpu_to_le16(qos->rx->sdu); + cis_param->p_pdu = sys_cpu_to_le16(qos->rx->max_pdu); + cis_param->p_phy = qos->rx->phy; + cis_param->p_bn = qos->rx->burst_number; + } + + LOG_DBG("[%d]: id %u, nse %u c_sdu %u, p_sdu %u, c_pdu %u, " + "p_pdu %u, c_phy %u, p_phy %u, c_bn %u, p_bn %u", + i, cis_param->cis_id, cis_param->nse, cis_param->c_sdu, + cis_param->p_sdu, + cis_param->c_pdu, cis_param->p_pdu, cis_param->c_phy, + cis_param->p_phy, + cis_param->c_bn, cis_param->p_bn); + } + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_CIG_PARAMS_TEST, buf, &rsp); + if (err) + { + return NULL; + } + + return rsp; +} + +static bool is_advanced_cig_param(const struct bt_iso_cig_param *param) +{ + if (param->c_to_p_ft != 0U || param->p_to_c_ft != 0U || + param->iso_interval != 0U) + { + return true; + } + + /* Check if any of the CIS contain any test-param-only values */ + + for (uint8_t i = 0U; i < param->num_cis; i++) + { + const struct bt_iso_chan *cis = param->cis_channels[i]; + const struct bt_iso_chan_qos *qos = cis->qos; + + if (qos->num_subevents > 0U) + { + return true; + } + + if (qos->tx != NULL) + { + if (qos->tx->max_pdu > 0U || qos->tx->burst_number > 0U) + { + return true; + } + } + + if (qos->rx != NULL) + { + if (qos->rx->max_pdu > 0U || qos->rx->burst_number > 0U) + { + return true; + } + } + } + + return false; +} +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ + +static struct bt_iso_cig *get_cig(const struct bt_iso_chan *iso_chan) +{ + if (iso_chan == NULL || iso_chan->iso == NULL) + { + return NULL; + } + + DEBUGASSERT(iso_chan->iso->iso.info.unicast.cig_id < ARRAY_SIZE(cigs)); + + return &cigs[iso_chan->iso->iso.info.unicast.cig_id]; +} + +static struct bt_iso_cig *get_free_cig(void) +{ + /* We can use the index in the `cigs` array as CIG ID */ + + for (size_t i = 0; i < ARRAY_SIZE(cigs); i++) + { + if (cigs[i].state == BT_ISO_CIG_STATE_IDLE) + { + cigs[i].state = BT_ISO_CIG_STATE_CONFIGURED; + cigs[i].id = i; + list_initialize(&cigs[i].cis_channels); + return &cigs[i]; + } + } + + LOG_DBG("Could not allocate any more CIGs"); + + return NULL; +} + +static bool cis_is_in_cig(const struct bt_iso_cig *cig, + const struct bt_iso_chan *cis) +{ + if (cig == NULL || cis == NULL || cis->iso == NULL) + { + return false; + } + + return cig->id == cis->iso->iso.info.unicast.cig_id; +} + +static int cig_init_cis(struct bt_iso_cig *cig, + const struct bt_iso_cig_param *param) +{ + for (uint8_t i = 0; i < param->num_cis; i++) + { + struct bt_iso_chan *cis = param->cis_channels[i]; + + if (cis->iso == NULL) + { + struct bt_conn_iso *iso_conn; + + cis->iso = iso_new(); + if (cis->iso == NULL) + { + LOG_ERR("Unable to allocate CIS connection"); + return -ENOMEM; + } + + iso_conn = &cis->iso->iso; + + iso_conn->info.unicast.cig_id = cig->id; + iso_conn->info.type = BT_ISO_CHAN_TYPE_CENTRAL; + iso_conn->info.unicast.cis_id = cig->num_cis++; + + bt_iso_chan_add(cis->iso, cis); + + list_add_tail(&cig->cis_channels, &cis->node); + } + + /* else already initialized */ + } + + return 0; +} + +static void cleanup_cig(struct bt_iso_cig *cig) +{ + struct bt_iso_chan *cis; + struct bt_iso_chan *tmp; + + list_for_every_container_safe(&cig->cis_channels, cis, tmp, node) + { + bt_conn_drop(&cis->iso); + + list_remove(&cig->cis_channels, NULL, &cis->node); + } + + memset(cig, 0, sizeof(*cig)); +} + +static bool valid_cig_param( + const struct bt_iso_cig_param *param, bool advanced, + const struct bt_iso_cig *cig) +{ + bool is_c_to_p = false; + bool is_p_to_c = false; + + if (param == NULL) + { + return false; + } + + for (uint8_t i = 0; i < param->num_cis; i++) + { + struct bt_iso_chan *cis = param->cis_channels[i]; + + if (cis == NULL) + { + LOG_DBG("cis_channels[%u]: NULL channel", i); + return false; + } + + if (cis->qos == NULL) + { + LOG_DBG("cis_channels[%u]: NULL QoS", i); + return false; + } + + if (cis->iso != NULL && !cis_is_in_cig(cig, cis)) + { + LOG_DBG("cis_channels[%u]: already allocated to CIG %p", i, + get_cig(cis)); + return false; + } + + if (!valid_chan_qos(cis->qos, advanced)) + { + LOG_DBG("cis_channels[%u]: Invalid QOS", i); + return false; + } + + for (uint8_t j = 0; j < i; j++) + { + if (cis == param->cis_channels[j]) + { + LOG_DBG("ISO %p duplicated at index %u and %u", cis, i, j); + return false; + } + } + + if (cis->qos->rx != NULL && cis->qos->rx->sdu != 0U) + { + is_p_to_c = true; + } + + if (cis->qos->tx != NULL && cis->qos->tx->sdu != 0U) + { + is_c_to_p = true; + } + + if (!is_p_to_c && !is_c_to_p) + { + LOG_DBG("cis_channels[%u]: Neither C to P nor P to C can be " + "configured", + i); + return false; + } + } + + if (param->framing != BT_ISO_FRAMING_UNFRAMED && + param->framing != BT_ISO_FRAMING_FRAMED) + { + LOG_DBG("Invalid framing parameter: %u", param->framing); + return false; + } + + if (param->packing != BT_ISO_PACKING_SEQUENTIAL && + param->packing != BT_ISO_PACKING_INTERLEAVED) + { + LOG_DBG("Invalid packing parameter: %u", param->packing); + return false; + } + + if (param->num_cis > BT_ISO_MAX_GROUP_ISO_COUNT || + param->num_cis > CONFIG_BT_ISO_MAX_CHAN) + { + LOG_DBG("num_cis (%u) shall be lower than: %u", param->num_cis, + MAX(CONFIG_BT_ISO_MAX_CHAN, BT_ISO_MAX_GROUP_ISO_COUNT)); + return false; + } + + if (is_c_to_p && + !IN_RANGE(param->c_to_p_interval, BT_ISO_SDU_INTERVAL_MIN, + BT_ISO_SDU_INTERVAL_MAX)) + { + LOG_DBG("Invalid C to P interval: %u", param->c_to_p_interval); + return false; + } + + if (is_p_to_c && + !IN_RANGE(param->p_to_c_interval, BT_ISO_SDU_INTERVAL_MIN, + BT_ISO_SDU_INTERVAL_MAX)) + { + LOG_DBG("Invalid P to C interval: %u", param->p_to_c_interval); + return false; + } + + if (!advanced) + { + if (is_c_to_p && !IN_RANGE(param->c_to_p_latency, BT_ISO_LATENCY_MIN, + BT_ISO_LATENCY_MAX)) + { + LOG_DBG("Invalid C to P latency: %u", param->c_to_p_latency); + return false; + } + + if (is_p_to_c && !IN_RANGE(param->p_to_c_latency, BT_ISO_LATENCY_MIN, + BT_ISO_LATENCY_MAX)) + { + LOG_DBG("Invalid P to C latency: %u", param->p_to_c_latency); + return false; + } + } + +#if defined(CONFIG_BT_ISO_TEST_PARAMS) + if (advanced) + { + if (!IN_RANGE(param->c_to_p_ft, BT_ISO_FT_MIN, BT_ISO_FT_MAX)) + { + LOG_DBG("Invalid Central to Peripheral FT %u", param->c_to_p_ft); + + return false; + } + + if (!IN_RANGE(param->p_to_c_ft, BT_ISO_FT_MIN, BT_ISO_FT_MAX)) + { + LOG_DBG("Invalid Peripheral to Central FT %u", param->p_to_c_ft); + + return false; + } + + if (!IN_RANGE(param->iso_interval, BT_ISO_ISO_INTERVAL_MIN, + BT_ISO_ISO_INTERVAL_MAX)) + { + LOG_DBG("Invalid ISO interval %u", param->iso_interval); + + return false; + } + } +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ + + return true; +} + +int bt_iso_cig_create(const struct bt_iso_cig_param *param, + struct bt_iso_cig **out_cig) +{ + int err; + struct net_buf *rsp = NULL; + struct bt_iso_cig *cig; + struct bt_hci_rp_le_set_cig_params *cig_rsp; + struct bt_iso_chan *cis; + bool advanced = false; + int i; + + if (param == NULL) + { + LOG_DBG("param is NULL"); + return -EINVAL; + } + + if (out_cig == NULL) + { + LOG_DBG("out_cig is NULL"); + return -EINVAL; + } + + *out_cig = NULL; + + /* Check if controller is ISO capable as a central */ + + if (!BT_FEAT_LE_CIS_CENTRAL(bt_dev.le.features)) + { + return -ENOTSUP; + } + + /* TBD: Should we allow creating empty CIGs? */ + + if (param->cis_channels == NULL) + { + LOG_DBG("NULL CIS channels"); + return -EINVAL; + } + + if (param->num_cis == 0) + { + LOG_DBG("Invalid number of CIS %u", param->num_cis); + return -EINVAL; + } + +#if defined(CONFIG_BT_ISO_TEST_PARAMS) + advanced = is_advanced_cig_param(param); +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ + + if (!valid_cig_param(param, advanced, NULL)) + { + LOG_DBG("Invalid CIG params"); + return -EINVAL; + } + + cig = get_free_cig(); + + if (!cig) + { + return -ENOMEM; + } + + err = cig_init_cis(cig, param); + if (err) + { + LOG_DBG("Could not init CIS %d", err); + cleanup_cig(cig); + return err; + } + + if (!advanced) + { + rsp = hci_le_set_cig_params(cig, param); +#if defined(CONFIG_BT_ISO_TEST_PARAMS) + } + else + { + rsp = hci_le_set_cig_test_params(cig, param); +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ + } + + if (rsp == NULL) + { + LOG_WRN("Unexpected response to hci_le_set_cig_params"); + err = -EIO; + cleanup_cig(cig); + return err; + } + + cig_rsp = (void *)rsp->data; + + if (rsp->len < sizeof(*cig_rsp) || cig_rsp->num_handles != param->num_cis) + { + LOG_WRN("Unexpected response to hci_le_set_cig_params"); + err = -EIO; + net_buf_unref(rsp); + cleanup_cig(cig); + return err; + } + + i = 0; + list_for_every_container(&cig->cis_channels, cis, node) + { + const uint16_t handle = cig_rsp->handle[i++]; + + /* Assign the connection handle */ + + cis->iso->handle = sys_le16_to_cpu(handle); + } + + net_buf_unref(rsp); + + *out_cig = cig; + + return err; +} + +static void restore_cig(struct bt_iso_cig *cig, uint8_t existing_num_cis) +{ + struct bt_iso_chan *cis; + struct bt_iso_chan *tmp; + + list_for_every_container_safe(&cig->cis_channels, cis, tmp, node) + { + /* Remove all newly added by comparing the cis_id to the number + * of CIS that was previously added before bt_iso_cig_reconfigure was + * called + */ + + if (cis->iso != NULL && + cis->iso->iso.info.unicast.cis_id >= existing_num_cis) + { + bt_conn_drop(&cis->iso); + + list_remove(&cig->cis_channels, NULL, &cis->node); + cig->num_cis--; + } + } +} + +int bt_iso_cig_reconfigure(struct bt_iso_cig *cig, + const struct bt_iso_cig_param *param) +{ + struct bt_hci_rp_le_set_cig_params *cig_rsp; + uint8_t existing_num_cis; + bool advanced = false; + struct net_buf *rsp = NULL; + int err; + + if (cig == NULL) + { + LOG_DBG("cig is NULL"); + return -EINVAL; + } + + if (param == NULL) + { + LOG_DBG("param is NULL"); + return -EINVAL; + } + + if (cig->state != BT_ISO_CIG_STATE_CONFIGURED) + { + LOG_DBG("Invalid CIG state: %u", cig->state); + return -EINVAL; + } + +#if defined(CONFIG_BT_ISO_TEST_PARAMS) + advanced = is_advanced_cig_param(param); +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ + + if (!valid_cig_param(param, advanced, cig)) + { + LOG_DBG("Invalid CIG params"); + return -EINVAL; + } + + /* Used to restore CIG in case of error */ + + existing_num_cis = cig->num_cis; + + err = cig_init_cis(cig, param); + if (err != 0) + { + LOG_DBG("Could not init CIS %d", err); + restore_cig(cig, existing_num_cis); + return err; + } + + if (!advanced) + { + rsp = hci_le_set_cig_params(cig, param); +#if defined(CONFIG_BT_ISO_TEST_PARAMS) + } + else + { + rsp = hci_le_set_cig_test_params(cig, param); +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ + } + + if (rsp == NULL) + { + LOG_WRN("Unexpected response to hci_le_set_cig_params"); + err = -EIO; + restore_cig(cig, existing_num_cis); + return err; + } + + cig_rsp = (void *)rsp->data; + + if (rsp->len < sizeof(*cig_rsp)) + { + LOG_WRN("Unexpected response len to hci_le_set_cig_params %u != %zu", + rsp->len, + sizeof(*cig_rsp)); + err = -EIO; + net_buf_unref(rsp); + restore_cig(cig, existing_num_cis); + return err; + } + + if (cig_rsp->num_handles != param->num_cis) + { + LOG_WRN("Unexpected response num_handles to hci_le_set_cig_params %u " + "!= " + "%u", + cig_rsp->num_handles, param->num_cis); + err = -EIO; + net_buf_unref(rsp); + restore_cig(cig, existing_num_cis); + return err; + } + + for (uint8_t i = 0u; i < param->num_cis; i++) + { + const uint16_t handle = cig_rsp->handle[i]; + struct bt_iso_chan *cis = param->cis_channels[i]; + + /* Assign the connection handle */ + + cis->iso->handle = sys_le16_to_cpu(handle); + } + + net_buf_unref(rsp); + + return err; +} + +int bt_iso_cig_terminate(struct bt_iso_cig *cig) +{ + int err; + + if (cig == NULL) + { + LOG_DBG("cig is NULL"); + return -EINVAL; + } + + if (cig->state != BT_ISO_CIG_STATE_INACTIVE && + cig->state != BT_ISO_CIG_STATE_CONFIGURED) + { + LOG_DBG("Invalid CIG state: %u", cig->state); + return -EINVAL; + } + + err = hci_le_remove_cig(cig->id); + if (err != 0) + { + LOG_DBG("Failed to terminate CIG: %d", err); + return err; + } + + cleanup_cig(cig); + + return 0; +} + +static int hci_le_create_cis(const struct bt_iso_connect_param *param, + size_t count) +{ + struct bt_hci_cis *cis; + struct bt_hci_cp_le_create_cis *req; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + req = net_buf_add(buf, sizeof(*req)); + + memset(req, 0, sizeof(*req)); + + /* Program the cis parameters */ + + for (size_t i = 0; i < count; i++) + { + cis = net_buf_add(buf, sizeof(*cis)); + + memset(cis, 0, sizeof(*cis)); + + cis->cis_handle = sys_cpu_to_le16(param[i].iso_chan->iso->handle); + cis->acl_handle = sys_cpu_to_le16(param[i].acl->handle); + req->num_cis++; + } + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_CREATE_CIS, buf, NULL); +} + +static bool iso_chans_connecting(void) +{ + for (size_t i = 0U; i < ARRAY_SIZE(iso_conns); i++) + { + const struct bt_conn *iso = &iso_conns[i]; + const struct bt_iso_chan *iso_chan; + + if (iso == NULL || !(iso->iso.info.type == BT_ISO_CHAN_TYPE_CENTRAL || + iso->iso.info.type == + BT_ISO_CHAN_TYPE_PERIPHERAL)) + { + continue; + } + + iso_chan = iso_chan(iso); + if (iso_chan->state == BT_ISO_STATE_CONNECTING) + { + return true; + } + } + + return false; +} + +int bt_iso_chan_connect(const struct bt_iso_connect_param *param, + size_t count) +{ + int err; + + if (param == NULL) + { + LOG_DBG("param is NULL"); + return -EINVAL; + } + + if (count == 0) + { + LOG_DBG("Invalid count %zu", count); + return -EINVAL; + } + + if (count > CONFIG_BT_ISO_MAX_CHAN) + { + return -EINVAL; + } + + /* Validate input */ + + for (size_t i = 0; i < count; i++) + { + if (param[i].iso_chan == NULL) + { + LOG_DBG("[%zu]: Invalid iso (%p)", i, param[i].iso_chan); + return -EINVAL; + } + + if (param[i].acl == NULL) + { + LOG_DBG("[%zu]: Invalid acl (%p)", i, param[i].acl); + return -EINVAL; + } + + if ((param[i].acl->type & BT_CONN_TYPE_LE) == 0) + { + LOG_DBG("[%zu]: acl type (%u) shall be an LE connection", i, + param[i].acl->type); + return -EINVAL; + } + + if (param[i].iso_chan->iso == NULL) + { + LOG_DBG("[%zu]: ISO has not been initialized in a CIG", i); + return -EINVAL; + } + + if (param[i].iso_chan->state != BT_ISO_STATE_DISCONNECTED) + { + LOG_DBG("[%zu]: ISO is not in the BT_ISO_STATE_DISCONNECTED " + "state: " + "%u", + i, + param[i].iso_chan->state); + return -EINVAL; + } + } + + if (iso_chans_connecting()) + { + LOG_DBG("There are pending ISO connections"); + return -EBUSY; + } + + err = hci_le_create_cis(param, count); + if (err != 0) + { + LOG_DBG("Failed to connect CISes: %d", err); + + return err; + } + + /* Set connection states */ + + for (size_t i = 0; i < count; i++) + { + struct bt_iso_chan *iso_chan = param[i].iso_chan; + struct bt_iso_cig *cig; + + iso_chan->iso->iso.acl = bt_conn_ref(param[i].acl); + bt_conn_set_state(iso_chan->iso, BT_CONN_INITIATING); + bt_iso_chan_set_state(iso_chan, BT_ISO_STATE_CONNECTING); + + cig = get_cig(iso_chan); + DEBUGASSERT(cig != NULL); + cig->state = BT_ISO_CIG_STATE_ACTIVE; + } + + return 0; +} +#endif /* CONFIG_BT_ISO_CENTRAL */ +#endif /* CONFIG_BT_ISO_UNICAST */ + +#if defined(CONFIG_BT_ISO_BROADCAST) +static struct list_node iso_big_cbs = LIST_INITIAL_VALUE(iso_big_cbs); + +static struct bt_iso_big *lookup_big_by_handle(uint8_t big_handle) +{ + return &bigs[big_handle]; +} + +static struct bt_iso_big *get_free_big(void) +{ + /* We can use the index in the `bigs` array as BIG handles, for both + * broadcaster and receiver (even if the device is both!) + */ + + for (size_t i = 0; i < ARRAY_SIZE(bigs); i++) + { + if (!atomic_test_and_set_bit(bigs[i].flags, BT_BIG_INITIALIZED)) + { + bigs[i].handle = i; + list_initialize(&bigs[i].bis_channels); + return &bigs[i]; + } + } + + LOG_DBG("Could not allocate any more BIGs"); + + return NULL; +} + +static struct bt_iso_big *big_lookup_flag(int bit) +{ + for (size_t i = 0; i < ARRAY_SIZE(bigs); i++) + { + if (atomic_test_bit(bigs[i].flags, bit)) + { + return &bigs[i]; + } + } + + LOG_DBG("No BIG with flag bit %d set", bit); + + return NULL; +} + +static void cleanup_big(struct bt_iso_big *big) +{ + struct bt_iso_chan *bis; + struct bt_iso_chan *tmp; + + list_for_every_container_safe(&big->bis_channels, bis, tmp, node) + { + bt_conn_drop(&bis->iso); + + list_remove(&big->bis_channels, NULL, &bis->node); + } + + memset(big, 0, sizeof(*big)); +} + +static void big_disconnect(struct bt_iso_big *big, uint8_t reason) +{ + struct bt_iso_chan *bis; + + atomic_set_bit(big->flags, BT_BIG_BUSY); + + list_for_every_container(&big->bis_channels, bis, node) + { + bis->iso->err = reason; + + bt_iso_chan_disconnected(bis, reason); + } + + /* Cleanup the BIG before calling the `stopped` so that the `big` pointer + * and the ISO channels in the `big` can be reused in the callback + */ + + cleanup_big(big); + + if (!list_is_empty(&iso_big_cbs)) + { + struct bt_iso_big_cb *listener; + + list_for_every_container(&iso_big_cbs, listener, _node) + { + if (listener->stopped != NULL) + { + listener->stopped(big, reason); + } + } + } +} + +static int big_init_bis( + struct bt_iso_big *big, struct bt_iso_chan *bis, uint8_t bis_number, + bool broadcaster) +{ + struct bt_conn_iso *iso_conn; + + bis->iso = iso_new(); + if (bis->iso == NULL) + { + LOG_ERR("Unable to allocate BIS connection"); + return -ENOMEM; + } + + iso_conn = &bis->iso->iso; + +#if defined(CONFIG_BT_ISO_BROADCASTER) + if (broadcaster) + { + iso_conn->info.type = BT_ISO_CHAN_TYPE_BROADCASTER; + iso_conn->info.broadcaster.big_handle = big->handle; + iso_conn->info.broadcaster.bis_number = bis_number; + } +#endif /* CONFIG_BT_ISO_BROADCASTER */ +#if defined(CONFIG_BT_ISO_SYNC_RECEIVER) + + if (!broadcaster) + { + iso_conn->info.type = BT_ISO_CHAN_TYPE_SYNC_RECEIVER; + iso_conn->info.sync_receiver.big_handle = big->handle; + iso_conn->info.sync_receiver.bis_number = bis_number; + } +#endif /* CONFIG_BT_ISO_SYNC_RECEIVER */ + + bt_iso_chan_add(bis->iso, bis); + + list_add_tail(&big->bis_channels, &bis->node); + + return 0; +} + +int bt_iso_big_register_cb(struct bt_iso_big_cb *cb) +{ + if (cb == NULL) + { + LOG_DBG("cb is NULL"); + + return -EINVAL; + } + + if (list_find(&iso_big_cbs, &cb->_node, NULL)) + { + LOG_DBG("cb %p is already registered", cb); + + return -EEXIST; + } + + list_add_tail(&iso_big_cbs, &cb->_node); + + return 0; +} + +#if defined(CONFIG_BT_ISO_BROADCASTER) +static int hci_le_create_big( + struct bt_le_ext_adv *padv, struct bt_iso_big *big, + struct bt_iso_big_create_param *param) +{ + const struct bt_iso_chan_io_qos *qos; + struct bt_hci_cp_le_create_big *req; + struct bt_hci_cmd_state_set state; + struct net_buf *buf; + int err; + struct bt_iso_chan *bis; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + + if (!buf) + { + return -ENOBUFS; + } + + bis = list_peek_head_container(&big->bis_channels, bis, node); + DEBUGASSERT(bis != NULL); + + /* All BIS will share the same QOS */ + + qos = bis->qos->tx; + + req = net_buf_add(buf, sizeof(*req)); + req->big_handle = big->handle; + req->adv_handle = padv->handle; + req->num_bis = big->num_bis; + sys_put_le24(param->interval, req->sdu_interval); + req->max_sdu = sys_cpu_to_le16(qos->sdu); + req->max_latency = sys_cpu_to_le16(param->latency); + req->rtn = qos->rtn; + req->phy = qos->phy; + req->packing = param->packing; + req->framing = param->framing; + req->encryption = param->encryption; + if (req->encryption) + { + memcpy(req->bcode, param->bcode, sizeof(req->bcode)); + } + else + { + memset(req->bcode, 0, sizeof(req->bcode)); + } + + LOG_DBG("BIG handle 0x%02x, adv_handle 0x%02x, num_bis %u, sdu_interval " + "%u, max_sdu %u, " + "max_latency %u, rtn %u, phy %u, packing %u, framing %u, " + "encryption %u", + big->handle, padv->handle, big->num_bis, param->interval, + qos->sdu, param->latency, + qos->rtn, + qos->phy, + param->packing, + param->framing, + param->encryption); + + bt_hci_cmd_state_set_init(buf, &state, big->flags, BT_BIG_PENDING, true); + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_CREATE_BIG, buf, NULL); + + if (err) + { + return err; + } + + list_for_every_container(&big->bis_channels, bis, node) + { + bt_iso_chan_set_state(bis, BT_ISO_STATE_CONNECTING); + } + + return err; +} + +#if defined(CONFIG_BT_ISO_TEST_PARAMS) +static int hci_le_create_big_test( + const struct bt_le_ext_adv *padv, struct bt_iso_big *big, + const struct bt_iso_big_create_param *param) +{ + struct bt_hci_cp_le_create_big_test *req; + struct bt_hci_cmd_state_set state; + const struct bt_iso_chan_qos *qos; + struct bt_iso_chan *bis; + struct net_buf *buf; + int err; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + + if (!buf) + { + return -ENOBUFS; + } + + bis = list_peek_head_container(&big->bis_channels, bis, node); + DEBUGASSERT(bis != NULL); + + /* All BIS will share the same QOS */ + + qos = bis->qos; + + req = net_buf_add(buf, sizeof(*req)); + req->big_handle = big->handle; + req->adv_handle = padv->handle; + req->num_bis = big->num_bis; + sys_put_le24(param->interval, req->sdu_interval); + req->iso_interval = sys_cpu_to_le16(param->iso_interval); + req->nse = qos->num_subevents; + req->max_sdu = sys_cpu_to_le16(qos->tx->sdu); + req->max_pdu = sys_cpu_to_le16(qos->tx->max_pdu); + req->phy = qos->tx->phy; + req->packing = param->packing; + req->framing = param->framing; + req->bn = qos->tx->burst_number; + req->irc = param->irc; + req->pto = param->pto; + req->encryption = param->encryption; + if (req->encryption) + { + memcpy(req->bcode, param->bcode, sizeof(req->bcode)); + } + else + { + memset(req->bcode, 0, sizeof(req->bcode)); + } + + LOG_DBG("BIG handle %u, adv handle %u, num_bis %u, SDU interval %u, " + "ISO interval %u, NSE %u, SDU %u, PDU %u, PHY %u, packing %u, " + "framing %u, BN %u, IRC %u, PTO %u, encryption %u", + req->big_handle, req->adv_handle, req->num_bis, param->interval, + param->iso_interval, + req->nse, req->max_sdu, req->max_pdu, req->phy, req->packing, + req->framing, req->bn, + req->irc, req->pto, req->encryption); + + bt_hci_cmd_state_set_init(buf, &state, big->flags, BT_BIG_PENDING, true); + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_CREATE_BIG_TEST, buf, NULL); + if (err) + { + return err; + } + + list_for_every_container(&big->bis_channels, bis, node) + { + bt_iso_chan_set_state(bis, BT_ISO_STATE_CONNECTING); + } + + return err; +} + +static bool +is_advanced_big_param(const struct bt_iso_big_create_param *param) +{ + if (param->irc != 0U || param->iso_interval != 0U) + { + return true; + } + + /* Check if any of the CIS contain any test-param-only values */ + + for (uint8_t i = 0U; i < param->num_bis; i++) + { + const struct bt_iso_chan *bis = param->bis_channels[i]; + const struct bt_iso_chan_qos *qos = bis->qos; + + if (qos->num_subevents > 0U) + { + return true; + } + + DEBUGASSERT(qos->tx != NULL); + + if (qos->tx->max_pdu > 0U || qos->tx->burst_number > 0U) + { + return true; + } + } + + return false; +} +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ + +static bool valid_big_param(const struct bt_iso_big_create_param *param, + bool advanced) +{ + if (!param->bis_channels) + { + LOG_DBG("NULL BIS channels"); + + return false; + } + + if (!param->num_bis) + { + LOG_DBG("Invalid number of BIS %u", param->num_bis); + + return false; + } + + for (uint8_t i = 0; i < param->num_bis; i++) + { + struct bt_iso_chan *bis = param->bis_channels[i]; + + if (bis == NULL) + { + LOG_DBG("bis_channels[%u]: NULL channel", i); + + return false; + } + + if (bis->iso) + { + LOG_DBG("bis_channels[%u]: already allocated", i); + + return false; + } + + if (bis->qos == NULL) + { + LOG_DBG("bis_channels[%u]: qos is NULL", i); + + return false; + } + + if (bis->qos->tx == NULL || !valid_chan_io_qos(bis->qos->tx, true, + true, + advanced)) + { + LOG_DBG("bis_channels[%u]: Invalid QOS", i); + + return false; + } + } + + if (param->framing != BT_ISO_FRAMING_UNFRAMED && + param->framing != BT_ISO_FRAMING_FRAMED) + { + LOG_DBG("Invalid framing parameter: %u", param->framing); + + return false; + } + + if (param->packing != BT_ISO_PACKING_SEQUENTIAL && + param->packing != BT_ISO_PACKING_INTERLEAVED) + { + LOG_DBG("Invalid packing parameter: %u", param->packing); + + return false; + } + + if (param->num_bis > BT_ISO_MAX_GROUP_ISO_COUNT || + param->num_bis > CONFIG_BT_ISO_MAX_CHAN) + { + LOG_DBG("num_bis (%u) shall be lower than: %u", param->num_bis, + MAX(CONFIG_BT_ISO_MAX_CHAN, BT_ISO_MAX_GROUP_ISO_COUNT)); + + return false; + } + + if (!IN_RANGE(param->interval, BT_ISO_SDU_INTERVAL_MIN, + BT_ISO_SDU_INTERVAL_MAX)) + { + LOG_DBG("Invalid interval: %u", param->interval); + + return false; + } + + if (!advanced && !IN_RANGE(param->latency, BT_ISO_LATENCY_MIN, + BT_ISO_LATENCY_MAX)) + { + LOG_DBG("Invalid latency: %u", param->latency); + + return false; + } + +#if defined(CONFIG_BT_ISO_TEST_PARAMS) + if (advanced) + { + if (!IN_RANGE(param->irc, BT_ISO_IRC_MIN, BT_ISO_IRC_MAX)) + { + LOG_DBG("Invalid IRC %u", param->irc); + + return false; + } + + if (!IN_RANGE(param->pto, BT_ISO_PTO_MIN, BT_ISO_PTO_MAX)) + { + LOG_DBG("Invalid PTO %u", param->pto); + + return false; + } + + if (!IN_RANGE(param->iso_interval, BT_ISO_ISO_INTERVAL_MIN, + BT_ISO_ISO_INTERVAL_MAX)) + { + LOG_DBG("Invalid ISO interval %u", param->iso_interval); + + return false; + } + } +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ + + return true; +} + +int bt_iso_big_create( + struct bt_le_ext_adv *padv, struct bt_iso_big_create_param *param, + struct bt_iso_big **out_big) +{ + struct bt_iso_chan **bis_channels; + int err; + struct bt_iso_big *big; + bool advanced = false; + + if (padv == NULL) + { + LOG_DBG("padv is NULL"); + return -EINVAL; + } + + if (param == NULL) + { + LOG_DBG("param is NULL"); + return -EINVAL; + } + + if (out_big == NULL) + { + LOG_DBG("out_big is NULL"); + return -EINVAL; + } + + if (!atomic_test_bit(padv->flags, BT_PER_ADV_PARAMS_SET)) + { + LOG_DBG("PA params not set; invalid adv object"); + return -EINVAL; + } + +#if defined(CONFIG_BT_ISO_TEST_PARAMS) + advanced = is_advanced_big_param(param); +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ + + if (!valid_big_param(param, advanced)) + { + LOG_DBG("Invalid BIG parameters"); + + return -EINVAL; + } + + big = get_free_big(); + + if (!big) + { + return -ENOMEM; + } + + bis_channels = param->bis_channels; + for (uint8_t i = 0; i < param->num_bis; i++) + { + /* BIS numbers start from 1, so the first BIS will get BIS Number = 1 */ + + const uint8_t bis_number = i + 1U; + + err = big_init_bis(big, bis_channels[i], bis_number, true); + if (err != 0) + { + LOG_DBG("Could not init BIS[%u]: %d", i, err); + cleanup_big(big); + + return err; + } + } + + big->num_bis = param->num_bis; + + if (!advanced) + { + err = hci_le_create_big(padv, big, param); +#if defined(CONFIG_BT_ISO_TEST_PARAMS) + } + else + { + err = hci_le_create_big_test(padv, big, param); +#endif /* CONFIG_BT_ISO_TEST_PARAMS */ + } + + if (err) + { + LOG_DBG("Could not create BIG %d", err); + cleanup_big(big); + return err; + } + + *out_big = big; + + return err; +} + +static void store_bis_broadcaster_info( + const struct bt_hci_evt_le_big_complete *evt, struct bt_conn *iso) +{ + struct bt_conn_iso *iso_conn = &iso->iso; + struct bt_iso_info *info = &iso_conn->info; + struct bt_iso_broadcaster_info *broadcaster_info = &info->broadcaster; + const struct bt_iso_chan *chan = iso_conn->chan; + const struct bt_iso_chan_io_qos *tx = chan->qos->tx; + + info->iso_interval = sys_le16_to_cpu(evt->iso_interval); + info->max_subevent = evt->nse; + + broadcaster_info->sync_delay = sys_get_le24(evt->sync_delay); + broadcaster_info->latency = sys_get_le24(evt->latency); + broadcaster_info->phy = bt_get_phy(evt->phy); + broadcaster_info->bn = evt->bn; + broadcaster_info->irc = evt->irc; + + /* Transform to n * 1.25ms */ + + broadcaster_info->pto = info->iso_interval * evt->pto; + broadcaster_info->max_pdu = sys_le16_to_cpu(evt->max_pdu); + broadcaster_info->big_handle = iso_conn->info.broadcaster.big_handle; + broadcaster_info->bis_number = iso_conn->info.broadcaster.bis_number; + + info->can_send = true; + info->can_recv = false; + + LOG_DBG("iso_chan %p established: tx %p, big_handle 0x%02X, bis_number " + "0x%02X, " + "iso_interval 0x%04X, max_subevent 0x%02X, sync_delay 0x%06X, " + "latency 0x%06X, " + "phy 0x%02X, bn 0x%02X, irc 0x%02X, pto 0x%06X, max_pdu 0x%04X", + chan, tx, broadcaster_info->big_handle, + broadcaster_info->bis_number, info->iso_interval, + info->max_subevent, broadcaster_info->sync_delay, + broadcaster_info->latency, + broadcaster_info->phy, broadcaster_info->bn, + broadcaster_info->irc, broadcaster_info->pto, + broadcaster_info->max_pdu); +} + +void hci_le_big_complete(struct net_buf *buf) +{ + struct bt_hci_evt_le_big_complete *evt = (void *)buf->data; + struct bt_iso_chan *bis; + struct bt_iso_big *big; + int i; + + if (evt->big_handle >= ARRAY_SIZE(bigs)) + { + LOG_WRN("Invalid BIG handle"); + + big = big_lookup_flag(BT_BIG_PENDING); + if (big) + { + big_disconnect(big, + evt->status ? evt->status : BT_HCI_ERR_UNSPECIFIED); + } + + return; + } + + big = lookup_big_by_handle(evt->big_handle); + atomic_clear_bit(big->flags, BT_BIG_PENDING); + + LOG_DBG("BIG[%u] %p completed, status 0x%02x %s", big->handle, big, + evt->status, + bt_hci_err_to_str(evt->status)); + + if (evt->status || evt->num_bis != big->num_bis) + { + if (evt->status == BT_HCI_ERR_SUCCESS && evt->num_bis != big->num_bis) + { + LOG_ERR("Invalid number of BIS created, was %u expected %u", + evt->num_bis, big->num_bis); + } + + big_disconnect(big, evt->status ? evt->status : + BT_HCI_ERR_UNSPECIFIED); + return; + } + + atomic_set_bit(big->flags, BT_BIG_BUSY); + i = 0; + list_for_every_container(&big->bis_channels, bis, node) + { + const uint16_t handle = evt->handle[i++]; + struct bt_conn *iso_conn = bis->iso; + + iso_conn->handle = sys_le16_to_cpu(handle); + store_bis_broadcaster_info(evt, iso_conn); + bt_conn_set_state(iso_conn, BT_CONN_CONNECTED); + } + + atomic_clear_bit(big->flags, BT_BIG_BUSY); + + if (!list_is_empty(&iso_big_cbs)) + { + struct bt_iso_big_cb *listener; + + list_for_every_container(&iso_big_cbs, listener, _node) + { + if (listener->started != NULL) + { + listener->started(big); + } + } + } +} + +void hci_le_big_terminate(struct net_buf *buf) +{ + struct bt_hci_evt_le_big_terminate *evt = (void *)buf->data; + struct bt_iso_big *big; + + if (evt->big_handle >= ARRAY_SIZE(bigs)) + { + LOG_WRN("Invalid BIG handle"); + return; + } + + big = lookup_big_by_handle(evt->big_handle); + + LOG_DBG("BIG[%u] %p terminated", big->handle, big); + + big_disconnect(big, evt->reason); +} +#endif /* CONFIG_BT_ISO_BROADCASTER */ + +static int hci_le_terminate_big(struct bt_iso_big *big) +{ + struct bt_hci_cp_le_terminate_big *req; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + req = net_buf_add(buf, sizeof(*req)); + req->big_handle = big->handle; + req->reason = BT_HCI_ERR_REMOTE_USER_TERM_CONN; + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_TERMINATE_BIG, buf, NULL); +} + +static int hci_le_big_sync_term(struct bt_iso_big *big) +{ + struct bt_hci_cp_le_big_terminate_sync *req; + struct bt_hci_rp_le_big_terminate_sync *evt; + struct net_buf *buf; + struct net_buf *rsp; + int err; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + req = net_buf_add(buf, sizeof(*req)); + req->big_handle = big->handle; + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_BIG_TERMINATE_SYNC, buf, &rsp); + if (err) + { + return err; + } + + evt = (struct bt_hci_rp_le_big_terminate_sync *)rsp->data; + if (evt->status || (evt->big_handle != big->handle)) + { + err = -EIO; + } + + net_buf_unref(rsp); + + return err; +} + +int bt_iso_big_terminate(struct bt_iso_big *big) +{ + struct bt_iso_chan *bis; + int err; + + if (big == NULL) + { + LOG_DBG("big is NULL"); + return -EINVAL; + } + + if (!atomic_test_bit(big->flags, BT_BIG_INITIALIZED) || !big->num_bis) + { + LOG_DBG("BIG not initialized"); + return -EINVAL; + } + + if (atomic_test_bit(big->flags, BT_BIG_BUSY)) + { + LOG_DBG("BIG %p is busy", big); + return -EBUSY; + } + + bis = list_peek_head_container(&big->bis_channels, bis, node); + DEBUGASSERT(bis != NULL); + + if (IS_ENABLED(CONFIG_BT_ISO_BROADCASTER) && + bis->iso->iso.info.type == BT_ISO_CHAN_TYPE_BROADCASTER) + { + err = hci_le_terminate_big(big); + + /* Wait for BT_HCI_EVT_LE_BIG_TERMINATE before cleaning up + * the BIG in hci_le_big_terminate + */ + + if (!err) + { + list_for_every_container(&big->bis_channels, bis, node) + { + bt_iso_chan_set_state(bis, BT_ISO_STATE_DISCONNECTING); + } + } + } + else if (IS_ENABLED(CONFIG_BT_ISO_SYNC_RECEIVER) && + bis->iso->iso.info.type == BT_ISO_CHAN_TYPE_SYNC_RECEIVER) + { + err = hci_le_big_sync_term(big); + + if (!err) + { + big_disconnect(big, BT_HCI_ERR_LOCALHOST_TERM_CONN); + } + } + else + { + err = -EINVAL; + } + + if (err) + { + LOG_DBG("Could not terminate BIG %d", err); + } + + return err; +} + +#if defined(CONFIG_BT_ISO_SYNC_RECEIVER) +static void store_bis_sync_receiver_info( + const struct bt_hci_evt_le_big_sync_established *evt, struct bt_conn *iso) +{ + struct bt_conn_iso *iso_conn = &iso->iso; + struct bt_iso_info *info = &iso_conn->info; + struct bt_iso_sync_receiver_info *receiver_info = &info->sync_receiver; + const struct bt_iso_chan *chan = iso_conn->chan; + const struct bt_iso_chan_io_qos *rx = chan->qos->rx; + + info->max_subevent = evt->nse; + info->iso_interval = sys_le16_to_cpu(evt->iso_interval); + + receiver_info->latency = sys_get_le24(evt->latency); + receiver_info->bn = evt->bn; + receiver_info->irc = evt->irc; + + /* Transform to n * 1.25ms */ + + receiver_info->pto = info->iso_interval * evt->pto; + receiver_info->max_pdu = sys_le16_to_cpu(evt->max_pdu); + receiver_info->big_handle = iso_conn->info.sync_receiver.big_handle; + receiver_info->bis_number = iso_conn->info.sync_receiver.bis_number; + + info->can_send = false; + info->can_recv = true; + + LOG_DBG("iso_chan %p established: rx %p, big_handle 0x%02X, bis_number " + "0x%02X, " + "iso_interval 0x%04X, max_subevent 0x%02X, latency 0x%06X, bn " + "0x%02X, irc 0x%02X, " + "pto 0x%06X, max_pdu 0x%04X", + chan, rx, receiver_info->big_handle, receiver_info->bis_number, + info->iso_interval, + info->max_subevent, receiver_info->latency, receiver_info->bn, + receiver_info->irc, + receiver_info->pto, receiver_info->max_pdu); +} + +void hci_le_big_sync_established(struct net_buf *buf) +{ + struct bt_hci_evt_le_big_sync_established *evt = (void *)buf->data; + struct bt_iso_chan *bis; + struct bt_iso_big *big; + int i; + + if (evt->big_handle >= ARRAY_SIZE(bigs)) + { + LOG_WRN("Invalid BIG handle"); + big = big_lookup_flag(BT_BIG_SYNCING); + if (big) + { + big_disconnect(big, + evt->status ? evt->status : BT_HCI_ERR_UNSPECIFIED); + } + + return; + } + + big = lookup_big_by_handle(evt->big_handle); + atomic_clear_bit(big->flags, BT_BIG_SYNCING); + + LOG_DBG("BIG[%u] %p sync established, status 0x%02x %s", big->handle, big, + evt->status, + bt_hci_err_to_str(evt->status)); + + if (evt->status || evt->num_bis != big->num_bis) + { + if (evt->status == BT_HCI_ERR_SUCCESS && evt->num_bis != big->num_bis) + { + LOG_ERR("Invalid number of BIS synced, was %u expected %u", + evt->num_bis, big->num_bis); + } + + big_disconnect(big, evt->status ? evt->status : + BT_HCI_ERR_UNSPECIFIED); + return; + } + + atomic_set_bit(big->flags, BT_BIG_BUSY); + i = 0; + list_for_every_container(&big->bis_channels, bis, node) + { + const uint16_t handle = evt->handle[i++]; + struct bt_conn *iso_conn = bis->iso; + + iso_conn->handle = sys_le16_to_cpu(handle); + store_bis_sync_receiver_info(evt, iso_conn); + bt_conn_set_state(iso_conn, BT_CONN_CONNECTED); + } + + atomic_clear_bit(big->flags, BT_BIG_BUSY); + + if (!list_is_empty(&iso_big_cbs)) + { + struct bt_iso_big_cb *listener; + + list_for_every_container(&iso_big_cbs, listener, _node) + { + if (listener->started != NULL) + { + listener->started(big); + } + } + } +} + +void hci_le_big_sync_lost(struct net_buf *buf) +{ + struct bt_hci_evt_le_big_sync_lost *evt = (void *)buf->data; + struct bt_iso_big *big; + + if (evt->big_handle >= ARRAY_SIZE(bigs)) + { + LOG_WRN("Invalid BIG handle"); + return; + } + + big = lookup_big_by_handle(evt->big_handle); + + LOG_DBG("BIG[%u] %p sync lost", big->handle, big); + + big_disconnect(big, evt->reason); +} + +static int hci_le_big_create_sync( + const struct bt_le_per_adv_sync *sync, struct bt_iso_big *big, + const struct bt_iso_big_sync_param *param) +{ + struct bt_hci_cp_le_big_create_sync *req; + struct bt_hci_cmd_state_set state; + struct net_buf *buf; + int err; + uint8_t bit_idx = 0; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + req = net_buf_add(buf, sizeof(*req) + big->num_bis); + req->big_handle = big->handle; + req->sync_handle = sys_cpu_to_le16(sync->handle); + req->encryption = param->encryption; + if (req->encryption) + { + memcpy(req->bcode, param->bcode, sizeof(req->bcode)); + } + else + { + memset(req->bcode, 0, sizeof(req->bcode)); + } + + req->mse = param->mse; + req->sync_timeout = sys_cpu_to_le16(param->sync_timeout); + req->num_bis = big->num_bis; + + /* Transform from bitfield to array */ + + for (int i = 1; i <= BT_ISO_MAX_GROUP_ISO_COUNT; i++) + { + if (param->bis_bitfield & BT_ISO_BIS_INDEX_BIT(i)) + { + if (bit_idx == big->num_bis) + { + LOG_DBG("BIG cannot contain %u BISes", bit_idx + 1); + return -EINVAL; + } + + req->bis[bit_idx++] = i; + } + } + + if (bit_idx != big->num_bis) + { + LOG_DBG("Number of bits in bis_bitfield (%u) doesn't match num_bis " + "(%u)", + bit_idx, + big->num_bis); + return -EINVAL; + } + + bt_hci_cmd_state_set_init(buf, &state, big->flags, BT_BIG_SYNCING, true); + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_BIG_CREATE_SYNC, buf, NULL); + + return err; +} + +int bt_iso_big_sync( + struct bt_le_per_adv_sync *sync, struct bt_iso_big_sync_param *param, + struct bt_iso_big **out_big) +{ + struct bt_iso_chan **bis_channels; + int err; + struct bt_iso_chan *bis; + struct bt_iso_big *big; + uint32_t bitfield_copy; + + if (sync == NULL) + { + LOG_DBG("sync is NULL"); + return -EINVAL; + } + + if (param == NULL) + { + LOG_DBG("param is NULL"); + return -EINVAL; + } + + if (out_big == NULL) + { + LOG_DBG("out_big is NULL"); + return -EINVAL; + } + + if (!atomic_test_bit(sync->flags, BT_PER_ADV_SYNC_SYNCED)) + { + LOG_DBG("PA sync not synced"); + return -EINVAL; + } + + if (param->mse > BT_ISO_SYNC_MSE_MAX) + { + LOG_DBG("Invalid MSE 0x%02x", param->mse); + return -EINVAL; + } + + if (param->sync_timeout < BT_ISO_SYNC_TIMEOUT_MIN || + param->sync_timeout > BT_ISO_SYNC_TIMEOUT_MAX) + { + LOG_DBG("Invalid sync timeout 0x%04x", param->sync_timeout); + return -EINVAL; + } + + if (!BT_ISO_VALID_BIS_BITFIELD(param->bis_bitfield)) + { + LOG_DBG("Invalid BIS bitfield 0x%08x", param->bis_bitfield); + return -EINVAL; + } + + if (!param->bis_channels) + { + LOG_DBG("NULL BIS channels"); + return -EINVAL; + } + + if (!param->num_bis) + { + LOG_DBG("Invalid number of BIS %u", param->num_bis); + return -EINVAL; + } + + for (uint8_t i = 0; i < param->num_bis; i++) + { + struct bt_iso_chan *param_bis = param->bis_channels[i]; + + if (param_bis == NULL) + { + LOG_DBG("bis_channels[%u]: NULL channel", i); + return -EINVAL; + } + + if (param_bis->iso) + { + LOG_DBG("bis_channels[%u]: already allocated", i); + return -EALREADY; + } + + if (param_bis->qos == NULL) + { + LOG_DBG("bis_channels[%u]: qos is NULL", i); + return -EINVAL; + } + + if (param_bis->qos->rx == NULL) + { + LOG_DBG("bis_channels[%u]: qos->rx is NULL", i); + return -EINVAL; + } + } + + big = get_free_big(); + + if (!big) + { + return -ENOMEM; + } + + bitfield_copy = param->bis_bitfield; + bis_channels = param->bis_channels; + for (uint8_t i = 0; i < param->num_bis; i++) + { + const uint8_t bis_number = ffs(bitfield_copy); + + /* pop the least significant bit from the bitfield */ + + bitfield_copy &= ~BIT(bis_number - 1U); + + /* The least significant bit will be the bis_number, so that if the + * bitfield is 0b0101 then the first pop will be 1 and the + * bitfield_copy will be 0b0100 and then the second pop will be 3 and + * the bitfield_copy will be 0b0000 Since ffs returns the index + * starting from 1 we can use the result directly as the bis_numbers + * also start from 1. + */ + + err = big_init_bis(big, bis_channels[i], bis_number, false); + if (err != 0) + { + LOG_DBG("Could not init BIS[%u]: %d", i, err); + cleanup_big(big); + + return err; + } + } + + big->num_bis = param->num_bis; + + err = hci_le_big_create_sync(sync, big, param); + if (err) + { + LOG_DBG("Could not create BIG sync %d", err); + cleanup_big(big); + return err; + } + + list_for_every_container(&big->bis_channels, bis, node) + { + bt_iso_chan_set_state(bis, BT_ISO_STATE_CONNECTING); + } + + *out_big = big; + + return 0; +} +#endif /* CONFIG_BT_ISO_SYNC_RECEIVER */ +#endif /* CONFIG_BT_ISO_BROADCAST */ + +void bt_iso_reset(void) +{ +#if defined(CONFIG_BT_ISO_CENTRAL) + for (size_t i = 0U; i < ARRAY_SIZE(cigs); i++) + { + struct bt_iso_cig *cig = &cigs[i]; + struct bt_iso_chan *cis; + + /* Disconnect any connected CIS and call the callback + * We cannot use bt_iso_chan_disconnected directly here, as that + * attempts to also remove the ISO data path, which we shouldn't + * attempt to during the reset, as that sends HCI commands. + */ + + list_for_every_container(&cig->cis_channels, cis, node) + { + if (cis->state != BT_ISO_STATE_DISCONNECTED) + { + bt_iso_chan_set_state(cis, BT_ISO_STATE_DISCONNECTED); + bt_iso_cleanup_acl(cis->iso); + if (cis->ops != NULL && cis->ops->disconnected != NULL) + { + cis->ops->disconnected(cis, BT_HCI_ERR_UNSPECIFIED); + } + } + } + + cleanup_cig(cig); + } +#endif /* CONFIG_BT_ISO_CENTRAL */ + +#if defined(CONFIG_BT_ISO_BROADCAST) + for (size_t i = 0U; i < ARRAY_SIZE(bigs); i++) + { + struct bt_iso_big *big = &bigs[i]; + + big_disconnect(big, BT_HCI_ERR_UNSPECIFIED); + } +#endif /* CONFIG_BT_ISO_BROADCAST */ +} diff --git a/libs/zblue/host/iso_internal.h b/libs/zblue/host/iso_internal.h new file mode 100644 index 0000000000000..c25a2683ac0e4 --- /dev/null +++ b/libs/zblue/host/iso_internal.h @@ -0,0 +1,179 @@ +/**************************************************************************** + * libs/zblue/host/iso_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Internal APIs for Bluetooth ISO handling. + */ + +/* Copyright (c) 2020 Intel Corporation + * Copyright (c) 2021-2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +enum bt_iso_cig_state +{ + BT_ISO_CIG_STATE_IDLE, + BT_ISO_CIG_STATE_CONFIGURED, + BT_ISO_CIG_STATE_ACTIVE, + BT_ISO_CIG_STATE_INACTIVE +}; + +struct bt_iso_cig +{ + /** List of ISO channels to setup as CIS (the CIG). */ + + struct list_node cis_channels; + + /** Total number of CISes in the CIG. */ + + uint8_t num_cis; + + /** The CIG ID */ + + uint8_t id; + + /** The CIG state + * + * Refer to BT Core Spec 5.3, Vol 6, Part 6, Figure 4.63 + */ + + enum bt_iso_cig_state state; +}; + +enum +{ + BT_BIG_INITIALIZED, + + /* Creating a BIG as a broadcaster */ + + BT_BIG_PENDING, + + /* Creating a BIG as a receiver */ + + BT_BIG_SYNCING, + /* BIG is busy handling an HCI event. + * + * Use this to prevent API calls from modifying the BIG while the event is + * being processed. + */ + + BT_BIG_BUSY, + + BT_BIG_NUM_FLAGS, +}; + +struct bt_iso_big +{ + /** List of ISO channels to setup as BIS (the BIG). */ + + struct list_node bis_channels; + + /** Total number of BISes in the BIG. */ + + uint8_t num_bis; + + /** The BIG handle */ + + uint8_t handle; + + atomic_define(flags, BT_BIG_NUM_FLAGS); +}; + +#define iso(buf) ((struct bt_conn_rx *)net_buf_user_data(buf)) + +/* Process ISO buffer */ + +void hci_iso(struct net_buf *buf); + +/* Allocates RX buffer */ + +struct net_buf *bt_iso_get_rx(clock_t timeout); + +/** A callback used to notify about freed buffer in the iso rx pool. */ + +typedef void (*bt_iso_buf_rx_freed_cb_t)(void); + +/** Set a callback to notify about freed buffer in the iso rx pool. + * + * @param cb Callback to notify about freed buffer in the iso rx pool. If + * NULL, the callback is disabled. + */ + +void bt_iso_buf_rx_freed_cb_set(bt_iso_buf_rx_freed_cb_t cb); + +/* Process CIS Established event */ + +void hci_le_cis_established(struct net_buf *buf); +void hci_le_cis_established_v2(struct net_buf *buf); + +/* Process CIS Request event */ + +void hci_le_cis_req(struct net_buf *buf); + +/** Process BIG complete event */ + +void hci_le_big_complete(struct net_buf *buf); + +/** Process BIG terminate event */ + +void hci_le_big_terminate(struct net_buf *buf); + +/** Process BIG sync established event */ + +void hci_le_big_sync_established(struct net_buf *buf); + +/** Process BIG sync lost event */ + +void hci_le_big_sync_lost(struct net_buf *buf); + +/* Notify ISO channels of a new connection */ + +void bt_iso_connected(struct bt_conn *iso); + +/* Notify ISO channels of a disconnect event */ + +void bt_iso_disconnected(struct bt_conn *iso); + +#if defined(CONFIG_BT_ISO_LOG_LEVEL_DBG) +void bt_iso_chan_set_state_debug( + struct bt_iso_chan *chan, enum bt_iso_state state, const char *func, + int line); +#define bt_iso_chan_set_state(_chan, _state) \ + bt_iso_chan_set_state_debug(_chan, _state, __func__, __LINE__) +#else +void bt_iso_chan_set_state(struct bt_iso_chan *chan, + enum bt_iso_state state); +#endif /* CONFIG_BT_ISO_LOG_LEVEL_DBG */ + +/* Process incoming data for a connection */ + +void bt_iso_recv(struct bt_conn *iso, struct net_buf *buf, uint8_t flags); + +/* Whether the HCI ISO data packet contains a timestamp or not. + * Per spec, the TS flag can only be set for the first fragment. + */ + +enum bt_iso_timestamp +{ + BT_ISO_TS_ABSENT = 0, + BT_ISO_TS_PRESENT, +}; diff --git a/libs/zblue/host/keys.c b/libs/zblue/host/keys.c new file mode 100644 index 0000000000000..5ffe6f6badafd --- /dev/null +++ b/libs/zblue/host/keys.c @@ -0,0 +1,794 @@ +/**************************************************************************** + * libs/zblue/host/keys.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* keys.c - Bluetooth key handling */ + +/* Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/bt_str.h" +#include "common/rpa.h" +#include "conn_internal.h" +#include "gatt_internal.h" +#include "hci_core.h" +#include "id.h" +#include "keys.h" +#include "settings.h" +#include "smp.h" +#include "sys/types.h" + +#define LOG_LEVEL CONFIG_BT_KEYS_LOG_LEVEL +LOG_MODULE_REGISTER(bt_keys); + +static struct bt_keys key_pool[CONFIG_BT_MAX_PAIRED]; + +#define BT_KEYS_STORAGE_LEN_COMPAT (BT_KEYS_STORAGE_LEN - sizeof(uint32_t)) + +/* Configuration version used to detect if the device has configuration flags + * present in the stored keys. Shall be higher than the maximum value of the + * `enc_size` field (16). + */ +#define STORAGE_CFG_VERSION_MIN 17U +#define STORAGE_CFG_VERSION 17U +BUILD_ASSERT(STORAGE_CFG_VERSION >= STORAGE_CFG_VERSION_MIN, + "STORAGE_CFG_VERSION is too small"); +BUILD_ASSERT(STORAGE_CFG_VERSION <= UINT8_MAX, + "STORAGE_CFG_VERSION is too large"); + +/* Configuration flags for storage. Based on the bt_keys_cfg_flags enum. */ +#define STORAGE_CFG_FLAGS \ + ((IS_ENABLED(CONFIG_BT_SIGNING) ? BT_KEYS_CFG_SIGNING : 0) | \ + (IS_ENABLED(CONFIG_BT_SMP_SC_PAIR_ONLY) ? BT_KEYS_CFG_SC_PAIR_ONLY : 0) | \ + (IS_ENABLED(CONFIG_BT_KEYS_OVERWRITE_OLDEST) ? BT_KEYS_CFG_OVERWRITE_OLDEST : 0)) +BUILD_ASSERT(STORAGE_CFG_FLAGS < BIT(24), "STORAGE_CFG_FLAGS is too large"); + +#if defined(CONFIG_BT_KEYS_OVERWRITE_OLDEST) +static uint32_t aging_counter_val; +static struct bt_keys *last_keys_updated; + +struct key_data +{ + bool in_use; + uint8_t id; +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void find_key_in_use(struct bt_conn *conn, void *data) +{ + struct key_data *kdata = data; + struct bt_keys *key; + + DEBUGASSERT(conn != NULL); + DEBUGASSERT(data != NULL); + + if (conn->state == BT_CONN_CONNECTED) + { + key = bt_keys_find_addr(conn->id, bt_conn_get_dst(conn)); + if (key == NULL) + { + return; + } + + /* Ensure that the reference returned matches the current pool item */ + + if (key == &key_pool[kdata->id]) + { + kdata->in_use = true; + LOG_DBG("Connected device %s is using key_pool[%d]", + bt_conn_dst_str(conn), kdata->id); + } + } +} + +static bool key_is_in_use(uint8_t id) +{ + struct key_data kdata = + { + false, id + }; + + bt_conn_foreach(BT_CONN_TYPE_LE, find_key_in_use, &kdata); + + return kdata.in_use; +} +#endif /* CONFIG_BT_KEYS_OVERWRITE_OLDEST */ + +void bt_keys_reset(void) +{ + memset(key_pool, 0, sizeof(key_pool)); +} + +bool bt_keys_has_bond(uint8_t id) +{ + for (size_t i = 0U; i < ARRAY_SIZE(key_pool); i++) + { + if ((key_pool[i].keys != 0U) && (key_pool[i].id == id)) + { + return true; + } + } + + return false; +} + +struct bt_keys *bt_keys_get_addr(uint8_t id, const bt_addr_le_t *addr) +{ + struct bt_keys *keys; + int i; + size_t first_free_slot = ARRAY_SIZE(key_pool); + + DEBUGASSERT(addr != NULL); + + LOG_DBG("%s", bt_addr_le_str(addr)); + + for (i = 0; i < ARRAY_SIZE(key_pool); i++) + { + keys = &key_pool[i]; + + if (keys->id == id && bt_addr_le_eq(&keys->addr, addr)) + { + return keys; + } + + if (first_free_slot == + ARRAY_SIZE(key_pool) && bt_addr_le_eq(&keys->addr, + BT_ADDR_LE_ANY)) + { + first_free_slot = i; + } + } + +#if defined(CONFIG_BT_KEYS_OVERWRITE_OLDEST) + if (first_free_slot == ARRAY_SIZE(key_pool)) + { + struct bt_keys *oldest = NULL; + bt_addr_le_t oldest_addr; + + for (i = 0; i < ARRAY_SIZE(key_pool); i++) + { + struct bt_keys *current = &key_pool[i]; + bool key_in_use = key_is_in_use(i); + + if (key_in_use) + { + continue; + } + + if ((oldest == NULL) + || (current->aging_counter < oldest->aging_counter)) + { + oldest = current; + } + } + + if (oldest == NULL) + { + LOG_DBG("unable to create keys for %s", bt_addr_le_str(addr)); + return NULL; + } + + /* Use a copy as bt_unpair will clear the oldest key. */ + + bt_addr_le_copy(&oldest_addr, &oldest->addr); + bt_unpair(oldest->id, &oldest_addr); + if (bt_addr_le_eq(&oldest->addr, BT_ADDR_LE_ANY)) + { + first_free_slot = oldest - &key_pool[0]; + } + } + +#endif /* CONFIG_BT_KEYS_OVERWRITE_OLDEST */ + if (first_free_slot < ARRAY_SIZE(key_pool)) + { + keys = &key_pool[first_free_slot]; + keys->id = id; + bt_addr_le_copy(&keys->addr, addr); + keys->cfg_version = STORAGE_CFG_VERSION; + sys_put_le24(STORAGE_CFG_FLAGS, keys->cfg_flags); +#if defined(CONFIG_BT_KEYS_OVERWRITE_OLDEST) + keys->aging_counter = ++aging_counter_val; + last_keys_updated = keys; +#endif /* CONFIG_BT_KEYS_OVERWRITE_OLDEST */ + LOG_DBG("created %p for %s", keys, bt_addr_le_str(addr)); + return keys; + } + + LOG_DBG("unable to create keys for %s", bt_addr_le_str(addr)); + + return NULL; +} + +void bt_foreach_bond( + uint8_t id, void (*func)(const struct bt_bond_info *info, void *user_data), + void *user_data) +{ + int i; + + DEBUGASSERT(func != NULL); + + for (i = 0; i < ARRAY_SIZE(key_pool); i++) + { + struct bt_keys *keys = &key_pool[i]; + + if (keys->keys && keys->id == id) + { + struct bt_bond_info info; + + bt_addr_le_copy(&info.addr, &keys->addr); + func(&info, user_data); + } + } +} + +enum bt_le_addr_res_support bt_le_bond_addr_res_support(uint8_t id, + const bt_addr_le_t *peer) +{ + struct bt_keys *keys; + + DEBUGASSERT(peer != NULL); + + if (!IS_ENABLED(CONFIG_BT_GATT_AUTO_READ_CENTRAL_ADDR_RES)) + { + /* Also ignores an answer stored by a previous firmware that + * had the option enabled. + */ + + return BT_LE_ADDR_RES_SUPPORT_UNKNOWN; + } + + keys = bt_keys_find_addr(id, peer); + if (keys == NULL || (keys->flags & BT_KEYS_CENTRAL_ADDR_RES_KNOWN) == 0) + { + return BT_LE_ADDR_RES_SUPPORT_UNKNOWN; + } + + if ((keys->flags & BT_KEYS_CENTRAL_ADDR_RES_SUPPORT) != 0) + { + return BT_LE_ADDR_RES_SUPPORT_YES; + } + + return BT_LE_ADDR_RES_SUPPORT_NO; +} + +void bt_keys_foreach_type( + enum bt_keys_type type, void (*func)(struct bt_keys *keys, void *data), + void *data) +{ + int i; + + DEBUGASSERT(func != NULL); + + for (i = 0; i < ARRAY_SIZE(key_pool); i++) + { + if ((key_pool[i].keys & type)) + { + func(&key_pool[i], data); + } + } +} + +struct bt_keys *bt_keys_find(enum bt_keys_type type, + uint8_t id, const bt_addr_le_t *addr) +{ + int i; + + DEBUGASSERT(addr != NULL); + + LOG_DBG("type %d %s", type, bt_addr_le_str(addr)); + + for (i = 0; i < ARRAY_SIZE(key_pool); i++) + { + if ((key_pool[i].keys & type) && key_pool[i].id == id && + bt_addr_le_eq(&key_pool[i].addr, addr)) + { + return &key_pool[i]; + } + } + + return NULL; +} + +struct bt_keys *bt_keys_get_type(enum bt_keys_type type, + uint8_t id, const bt_addr_le_t *addr) +{ + struct bt_keys *keys; + + DEBUGASSERT(addr != NULL); + + LOG_DBG("type %d %s", type, bt_addr_le_str(addr)); + + keys = bt_keys_find(type, id, addr); + if (keys) + { + return keys; + } + + keys = bt_keys_get_addr(id, addr); + if (!keys) + { + return NULL; + } + + bt_keys_add_type(keys, type); + + return keys; +} + +struct bt_keys *bt_keys_find_irk(uint8_t id, const bt_addr_le_t *addr) +{ + int i; + + DEBUGASSERT(addr != NULL); + + LOG_DBG("%s", bt_addr_le_str(addr)); + + if (!bt_addr_le_is_rpa(addr)) + { + return NULL; + } + + for (i = 0; i < ARRAY_SIZE(key_pool); i++) + { + if (!(key_pool[i].keys & BT_KEYS_IRK)) + { + continue; + } + + if (key_pool[i].id == id && bt_addr_eq(&addr->a, &key_pool[i].irk.rpa)) + { + LOG_DBG("cached RPA %s for %s", bt_addr_str(&key_pool[i].irk.rpa), + bt_addr_le_str(&key_pool[i].addr)); + return &key_pool[i]; + } + } + + for (i = 0; i < ARRAY_SIZE(key_pool); i++) + { + if (!(key_pool[i].keys & BT_KEYS_IRK)) + { + continue; + } + + if (key_pool[i].id != id) + { + continue; + } + + if (bt_rpa_irk_matches(key_pool[i].irk.val, &addr->a)) + { + LOG_DBG("RPA %s matches %s", bt_addr_str(&key_pool[i].irk.rpa), + bt_addr_le_str(&key_pool[i].addr)); + + bt_addr_copy(&key_pool[i].irk.rpa, &addr->a); + + return &key_pool[i]; + } + } + + LOG_DBG("No IRK for %s", bt_addr_le_str(addr)); + + return NULL; +} + +struct bt_keys *bt_keys_find_addr(uint8_t id, const bt_addr_le_t *addr) +{ + int i; + + DEBUGASSERT(addr != NULL); + + LOG_DBG("%s", bt_addr_le_str(addr)); + + for (i = 0; i < ARRAY_SIZE(key_pool); i++) + { + if (key_pool[i].id == id && bt_addr_le_eq(&key_pool[i].addr, addr)) + { + return &key_pool[i]; + } + } + + return NULL; +} + +void bt_keys_add_type(struct bt_keys *keys, enum bt_keys_type type) +{ + DEBUGASSERT(keys != NULL); + + keys->keys |= type; +} + +void bt_keys_clear(struct bt_keys *keys) +{ + DEBUGASSERT(keys != NULL); + + LOG_DBG("%s (keys 0x%04x)", bt_addr_le_str(&keys->addr), keys->keys); + + if (keys->state & BT_KEYS_ID_ADDED) + { + bt_id_del(keys); + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + /* Delete stored keys from flash */ + + bt_settings_delete_keys(keys->id, &keys->addr); + } + + (void)memset(keys, 0, sizeof(*keys)); +} + +#if defined(CONFIG_BT_SETTINGS) +int bt_keys_store(struct bt_keys *keys) +{ + int err; + + DEBUGASSERT(keys != NULL); + + err = bt_settings_store_keys(keys->id, &keys->addr, keys->storage_start, + BT_KEYS_STORAGE_LEN); + if (err) + { + LOG_ERR("Failed to save keys (err %d)", err); + return err; + } + + LOG_DBG("Stored keys for %s", bt_addr_le_str(&keys->addr)); + + return 0; +} + +static int keys_set(const char *name, + size_t len_rd, settings_read_cb read_cb, void *cb_arg) +{ + struct bt_keys *keys; + bt_addr_le_t addr; + uint8_t id; + ssize_t len; + int err; + char val[BT_KEYS_STORAGE_LEN]; + const char *next; + + if (!name) + { + LOG_ERR("Insufficient number of arguments"); + return -EINVAL; + } + + len = read_cb(cb_arg, val, sizeof(val)); + if (len < 0) + { + LOG_ERR("Failed to read value (err %zd)", len); + return -EINVAL; + } + + LOG_DBG("name %s val %s", name, (len) ? bt_hex(val, + sizeof(val)) : "(null)"); + + err = bt_settings_decode_key(name, &addr); + if (err) + { + LOG_ERR("Unable to decode address %s", name); + return -EINVAL; + } + + settings_name_next(name, &next); + + if (!next) + { + id = BT_ID_DEFAULT; + } + else + { + unsigned long next_id = strtoul(next, NULL, 10); + + if (next_id >= CONFIG_BT_ID_MAX) + { + LOG_ERR("Invalid local identity %lu", next_id); + return -EINVAL; + } + + id = (uint8_t)next_id; + } + + if (!len) + { + keys = bt_keys_find(BT_KEYS_ALL, id, &addr); + if (keys) + { + (void)memset(keys, 0, sizeof(*keys)); + LOG_DBG("Cleared keys for %s", bt_addr_le_str(&addr)); + } + else + { + LOG_WRN("Unable to find deleted keys for %s", + bt_addr_le_str(&addr)); + } + + return 0; + } + + keys = bt_keys_get_addr(id, &addr); + if (!keys) + { + LOG_ERR("Failed to allocate keys for %s", bt_addr_le_str(&addr)); + return -ENOMEM; + } + + if (len != BT_KEYS_STORAGE_LEN) + { + if ((uint8_t)val[0] < (uint8_t)STORAGE_CFG_VERSION_MIN && + len == BT_KEYS_STORAGE_LEN_COMPAT) + { + /* This check migrates keys without configuration flags to the new + * format granted only the configuration version and flags are + * missing. Older keys are recognized by the first octet being the + * enc_size field. + */ + + LOG_DBG("Keys for %s do not have configuration flags, adding " + "automatically", + bt_addr_le_str(&addr)); + keys->cfg_version = STORAGE_CFG_VERSION; + sys_put_le24(STORAGE_CFG_FLAGS, keys->cfg_flags); + memcpy((char *)keys + offsetof(struct bt_keys, enc_size), val, + len); + } + else if (IS_ENABLED(CONFIG_BT_KEYS_OVERWRITE_OLDEST) && + len == BT_KEYS_STORAGE_LEN_COMPAT) + { + /* Load shorter structure for compatibility with old + * records format with no counter. + */ + + LOG_WRN("Keys for %s have no aging " + "counter", bt_addr_le_str(&addr)); + memcpy(keys->storage_start, val, len); + } + else + { + LOG_ERR("Invalid key length %zd != %zu", len, BT_KEYS_STORAGE_LEN); + bt_keys_clear(keys); + + return -EINVAL; + } + } + else if (!IS_ENABLED(CONFIG_BT_SIGNING) && + IS_ENABLED(CONFIG_BT_SMP_SC_PAIR_ONLY) && + (uint8_t)val[0] < (uint8_t)STORAGE_CFG_VERSION_MIN) + { + /* Migrate `bt_keys` stored with extra data to the new format in the + * most common scenario where both legacy pairing and signing support + * are disabled. This allows to still properly load and use the keys + * after firmware upgrade. + */ + + size_t load_size = BT_KEYS_STORAGE_LEN_COMPAT; + + if (IS_ENABLED(CONFIG_BT_KEYS_OVERWRITE_OLDEST)) + { + /* Restoring aging counter is not implemented for this use-case. */ + + LOG_WRN("Skip aging counter - keys for %s", bt_addr_le_str(&addr)); + + /* `aging_counter` is an `uint32_t` */ + + load_size -= sizeof(uint32_t); + } + + if (len >= load_size) + { + LOG_DBG("Adding configuration flags to keys for %s", + bt_addr_le_str(&addr)); + keys->cfg_version = STORAGE_CFG_VERSION; + sys_put_le24(STORAGE_CFG_FLAGS, keys->cfg_flags); + (void)memcpy((char *)keys + offsetof(struct bt_keys, enc_size), + val, + load_size); + + /* Ensure no unsupported key types are set. */ + + keys->keys &= (BT_KEYS_IRK | BT_KEYS_LTK_P256); + if ((keys->keys & BT_KEYS_LTK_P256) == 0U) + { + LOG_WRN("Dropping keys for %s, no SC LTK", + bt_addr_le_str(&addr)); + bt_keys_clear(keys); + return -EINVAL; + } + } + } + else + { + memcpy(keys->storage_start, val, len); + } + + /* Some Kconfig options can change the size of the keys structure. This + * check will clear the stored keys if the config flags are not matching + * between firmware updates. + */ + + if ((keys->cfg_version != STORAGE_CFG_VERSION) || + (sys_get_le24(keys->cfg_flags) != STORAGE_CFG_FLAGS)) + { + LOG_ERR("Stored keys for %s do not match current config flags or " + "version", + bt_addr_le_str(&addr)); + bt_keys_clear(keys); + return -EINVAL; + } + + /* As of Core v6.2, authenticated keys are only valid for OOB or LE SC + * pairing methods. This check ensures that keys are valid if a device is + * updated from a previous version that did not enforce this requirement. + */ + + if ((keys->flags & BT_KEYS_AUTHENTICATED) && + !(keys->flags & (BT_KEYS_OOB | BT_KEYS_SC))) + { + LOG_WRN("The keys for %s are downgraded to unauthenticated as they no " + "longer meet " + "authentication requirements", + bt_addr_le_str(&addr)); + keys->flags &= ~BT_KEYS_AUTHENTICATED; + } + + LOG_DBG("Successfully restored keys for %s", bt_addr_le_str(&addr)); +#if defined(CONFIG_BT_KEYS_OVERWRITE_OLDEST) + if (aging_counter_val < keys->aging_counter) + { + aging_counter_val = keys->aging_counter; + } +#endif /* CONFIG_BT_KEYS_OVERWRITE_OLDEST */ + + return 0; +} + +static void id_add(struct bt_keys *keys, void *user_data) +{ + DEBUGASSERT(keys != NULL); + + if ((keys->state & BT_KEYS_ID_ADDED) != 0U) + { + return; + } + + /* Only mark as pending here. Keys are added to the controller RL on + * demand when advertising or scanning starts, avoiding bt_id_add() + * blocking settings_load() on HCI. + */ + + bt_id_pending_keys_update_set(keys, BT_KEYS_ID_PENDING_ADD); +} + +static int keys_commit(void) +{ + /* We do this in commit() rather than add() since add() may get + * called multiple times for the same address, especially if the keys were + * already removed. + */ + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && IS_ENABLED(CONFIG_BT_PRIVACY)) + { + bt_keys_foreach_type(BT_KEYS_ALL, id_add, NULL); + } + else + { + bt_keys_foreach_type(BT_KEYS_IRK, id_add, NULL); + } + + return 0; +} + +BT_SETTINGS_DEFINE(keys, "keys", keys_set, keys_commit); + +#endif /* CONFIG_BT_SETTINGS */ + +#if defined(CONFIG_BT_KEYS_OVERWRITE_OLDEST) +void bt_keys_update_usage(uint8_t id, const bt_addr_le_t *addr) +{ + DEBUGASSERT(addr != NULL); + + struct bt_keys *keys = bt_keys_find_addr(id, addr); + + if (!keys) + { + return; + } + + if (last_keys_updated == keys) + { + return; + } + + keys->aging_counter = ++aging_counter_val; + last_keys_updated = keys; + + LOG_DBG("Aging counter for %s is set to %u", bt_addr_le_str(addr), + keys->aging_counter); + + if (IS_ENABLED(CONFIG_BT_KEYS_SAVE_AGING_COUNTER_ON_PAIRING)) + { + bt_keys_store(keys); + } +} + +#endif /* CONFIG_BT_KEYS_OVERWRITE_OLDEST */ + +#if defined(CONFIG_BT_LOG_SNIFFER_INFO) +void bt_keys_show_sniffer_info(struct bt_keys *keys, void *data) +{ + uint8_t ltk[16]; + + DEBUGASSERT(keys != NULL); + + if (keys->keys & BT_KEYS_LTK_P256) + { + sys_memcpy_swap(ltk, keys->ltk.val, keys->enc_size); + LOG_INF("SC LTK: 0x%s", bt_hex(ltk, keys->enc_size)); + } + +#if !defined(CONFIG_BT_SMP_SC_PAIR_ONLY) + if (keys->keys & BT_KEYS_PERIPH_LTK) + { + sys_memcpy_swap(ltk, keys->periph_ltk.val, keys->enc_size); + LOG_INF("Legacy LTK: 0x%s (peripheral)", bt_hex(ltk, keys->enc_size)); + } +#endif /* !CONFIG_BT_SMP_SC_PAIR_ONLY */ + + if (keys->keys & BT_KEYS_LTK) + { + sys_memcpy_swap(ltk, keys->ltk.val, keys->enc_size); + LOG_INF("Legacy LTK: 0x%s (central)", bt_hex(ltk, keys->enc_size)); + } +} +#endif /* defined(CONFIG_BT_LOG_SNIFFER_INFO) */ + +#ifdef ZTEST_UNITTEST +struct bt_keys *bt_keys_get_key_pool(void) +{ + return key_pool; +} + +#if defined(CONFIG_BT_KEYS_OVERWRITE_OLDEST) +uint32_t bt_keys_get_aging_counter_val(void) +{ + return aging_counter_val; +} + +struct bt_keys *bt_keys_get_last_keys_updated(void) +{ + return last_keys_updated; +} +#endif /* CONFIG_BT_KEYS_OVERWRITE_OLDEST */ +#endif /* ZTEST_UNITTEST */ diff --git a/libs/zblue/host/keys.h b/libs/zblue/host/keys.h new file mode 100644 index 0000000000000..4617182f1e894 --- /dev/null +++ b/libs/zblue/host/keys.h @@ -0,0 +1,314 @@ +/**************************************************************************** + * libs/zblue/host/keys.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* keys.h - Bluetooth key handling */ + +/* Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_SUBSYS_BLUETOOTH_HOST_KEYS_H_ +#define ZEPHYR_SUBSYS_BLUETOOTH_HOST_KEYS_H_ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include + +/** @cond INTERNAL_HIDDEN */ + +enum bt_keys_type +{ + BT_KEYS_PERIPH_LTK = BIT(0), + BT_KEYS_IRK = BIT(1), + BT_KEYS_LTK = BIT(2), + BT_KEYS_LOCAL_CSRK = BIT(3), + BT_KEYS_REMOTE_CSRK = BIT(4), + BT_KEYS_LTK_P256 = BIT(5), + + BT_KEYS_ALL = (BT_KEYS_PERIPH_LTK | + BT_KEYS_IRK | + BT_KEYS_LTK | + BT_KEYS_LOCAL_CSRK | + BT_KEYS_REMOTE_CSRK | + BT_KEYS_LTK_P256), +}; + +enum +{ + BT_KEYS_ID_PENDING_ADD = BIT(0), + BT_KEYS_ID_ADDED = BIT(2), +}; + +enum +{ + BT_KEYS_AUTHENTICATED = BIT(0), + BT_KEYS_DEBUG = BIT(1), + + /* Bit 2 and 3 might accidentally exist in old stored keys */ + + BT_KEYS_SC = BIT(4), + BT_KEYS_OOB = BIT(5), + /* Whether the value of the peer's Central Address Resolution + * characteristic is known, and if so, whether address resolution is + * supported. Keys stored before these flags were introduced load with both + * bits cleared, i.e. as unknown. + */ + + BT_KEYS_CENTRAL_ADDR_RES_KNOWN = BIT(6), + BT_KEYS_CENTRAL_ADDR_RES_SUPPORT = BIT(7), +}; + +enum bt_keys_cfg_flags +{ + BT_KEYS_CFG_SIGNING = BIT(0), + BT_KEYS_CFG_SC_PAIR_ONLY = BIT(1), + BT_KEYS_CFG_OVERWRITE_OLDEST = BIT(2), + + /* BIT(24) and higher: Invalid. See STORAGE_CFG_FLAGS. */ +}; + +struct bt_ltk +{ + uint8_t rand[8]; + uint8_t ediv[2]; + uint8_t val[16]; +}; + +struct bt_irk +{ + uint8_t val[16]; + + /* Cache for `bt_keys_find_irk`. Not reliable as "current RPA"! */ + + bt_addr_t rpa; +}; + +static inline bool bt_irk_eq(struct bt_irk const *a, struct bt_irk const *b) +{ + return util_memeq(a->val, b->val, sizeof(a->val)); +} + +struct bt_csrk +{ + uint8_t val[16]; + uint32_t cnt; +}; + +struct bt_keys +{ + uint8_t id; + bt_addr_le_t addr; + uint8_t state; + uint8_t storage_start[0] __aligned(sizeof(void *)); + + /* cfg_version and cfg_flags total 4 octets to maintain struct alignment */ + + uint8_t cfg_version; + uint8_t cfg_flags[3]; + uint8_t enc_size; + uint8_t flags; + uint16_t keys; + struct bt_ltk ltk; + struct bt_irk irk; +#if defined(CONFIG_BT_SIGNING) + struct bt_csrk local_csrk; + struct bt_csrk remote_csrk; +#endif /* BT_SIGNING */ +#if !defined(CONFIG_BT_SMP_SC_PAIR_ONLY) + struct bt_ltk periph_ltk; +#endif /* CONFIG_BT_SMP_SC_PAIR_ONLY */ +#if (defined(CONFIG_BT_KEYS_OVERWRITE_OLDEST)) + uint32_t aging_counter; +#endif /* CONFIG_BT_KEYS_OVERWRITE_OLDEST */ +}; + +#define BT_KEYS_STORAGE_LEN (sizeof(struct bt_keys) - offsetof(struct bt_keys, storage_start)) + +/** Clears all keys. + * + * Keys stored in settings are not cleared. + */ + +void bt_keys_reset(void); + +bool bt_keys_has_bond(uint8_t id); + +/** + * @brief Get a call through the callback for each key with the same type + * + * @param type Key type. + * @param func Callback function to be called when a matched record is found. + * @param data User data to be passed to the callback function. + */ + +void bt_keys_foreach_type( + enum bt_keys_type type, void (*func)(struct bt_keys *keys, void *data), + void *data); + +/** + * @brief Get the key slot reference for an ID and address pair. + * + * If the pair already exists in the keys pool, no new reference is reserved + * and the same reference is returned directly. Otherwise try to reserve one + * for the new ID and address pair if there is a room for the new pair. + * + * @note If 'CONFIG_BT_KEYS_OVERWRITE_OLDEST' is defined and the keys pool is + * full, the function will try to find the oldest key that isn't in use with + * a connection. If a key with matched criteria is found, it will be + * overwritten with the new one. + * + * @param id Key identifier. + * @param addr Destination address. + * + * @return A valid reference pointer to the key slot if process succeeded. + * Otherwise, a NULL value is returned. + */ + +struct bt_keys *bt_keys_get_addr(uint8_t id, const bt_addr_le_t *addr); + +/** + * @brief Get the key slot reference for an ID and address pair with a + * certain type. + * + * If the pair already exists in the keys pool, no new reference is reserved + * and the same reference is returned directly. Otherwise try to reserve one + * for the new ID and address pair if there is a room for the new pair and + * set the type. + * + * @param type Key type. + * @param id Key identifier. + * @param addr Destination address. + * + * @return A valid reference pointer to the key slot if process succeeded. + * Otherwise, a NULL value is returned. + */ + +struct bt_keys *bt_keys_get_type(enum bt_keys_type type, + uint8_t id, const bt_addr_le_t *addr); + +/** + * @brief Find key identified by type, ID and address + * + * @param type Key type. + * @param id Key identifier. + * @param addr Destination address. + * + * @return A valid reference pointer to the key slot if it exists. + * Otherwise, a NULL value is returned. + */ + +struct bt_keys *bt_keys_find(enum bt_keys_type type, + uint8_t id, const bt_addr_le_t *addr); + +/** + * @brief Find key reference by trying to resolve an RPA using IRK + * + * @param id Key identifier. + * @param addr Destination address. + * @return A valid reference pointer to the key slot on success. + * Otherwise, a NULL value is returned. + */ + +struct bt_keys *bt_keys_find_irk(uint8_t id, const bt_addr_le_t *addr); + +/** + * @brief Find a key by ID and address + * + * @param id Key identifier. + * @param addr Destination address. + * @return A valid reference pointer to the key slot if it exists. + * Otherwise, a NULL value is returned. + */ + +struct bt_keys *bt_keys_find_addr(uint8_t id, const bt_addr_le_t *addr); + +/** + * @brief Add a type to a key + * + * @param keys Key reference. + * @param type Key type to be added. + */ + +void bt_keys_add_type(struct bt_keys *keys, enum bt_keys_type type); + +/** + * @brief Clear a key contents + * + * @param keys Key reference to be cleared. + */ + +void bt_keys_clear(struct bt_keys *keys); + +#if defined(CONFIG_BT_SETTINGS) + +/** + * @brief Store key to persistent memory + * + * @param keys Key reference. + * @return 0 on success, non-zero error code otherwise + */ + +int bt_keys_store(struct bt_keys *keys); +#else +static inline int bt_keys_store(struct bt_keys *keys) +{ + return 0; +} +#endif + +enum +{ + BT_LINK_KEY_AUTHENTICATED = BIT(0), + BT_LINK_KEY_DEBUG = BIT(1), + BT_LINK_KEY_SC = BIT(2), +}; + +struct bt_keys_link_key +{ + bt_addr_t addr; + uint8_t enc_key_size; + uint8_t storage_start[0] __aligned(sizeof(void *)); + uint8_t flags; + uint8_t val[16]; +#if (defined(CONFIG_BT_KEYS_OVERWRITE_OLDEST)) + uint32_t aging_counter; +#endif /* CONFIG_BT_KEYS_OVERWRITE_OLDEST */ +}; +#define BT_KEYS_LINK_KEY_STORAGE_LEN \ + (sizeof(struct bt_keys_link_key) - offsetof(struct bt_keys_link_key, storage_start)) + +struct bt_keys_link_key *bt_keys_get_link_key(const bt_addr_t *addr); +struct bt_keys_link_key *bt_keys_find_link_key(const bt_addr_t *addr); +void bt_keys_link_key_clear(struct bt_keys_link_key *link_key); +void bt_keys_link_key_clear_addr(const bt_addr_t *addr); +void bt_keys_link_key_store(struct bt_keys_link_key *link_key); + +/* This function is used to signal that the key has been used for paring */ + +/* It updates the aging counter and saves it to flash if configuration option + */ + +/* BT_KEYS_SAVE_AGING_COUNTER_ON_PAIRING is enabled */ + +void bt_keys_update_usage(uint8_t id, const bt_addr_le_t *addr); +void bt_keys_link_key_update_usage(const bt_addr_t *addr); + +void bt_keys_show_sniffer_info(struct bt_keys *keys, void *data); + +/** @endcond */ + +#endif /* ZEPHYR_SUBSYS_BLUETOOTH_HOST_KEYS_H_ */ diff --git a/libs/zblue/host/l2cap.c b/libs/zblue/host/l2cap.c new file mode 100644 index 0000000000000..8b3b3fef54331 --- /dev/null +++ b/libs/zblue/host/l2cap.c @@ -0,0 +1,4098 @@ +/**************************************************************************** + * libs/zblue/host/l2cap.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* l2cap.c - L2CAP handling */ + +/* Copyright (c) 2015-2016 Intel Corporation + * Copyright (c) 2023 Nordic Semiconductor Copyright (c) 2025 Xiaomi + * Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "buf_view.h" +#include "hci_core.h" +#include "conn_internal.h" +#include "l2cap_internal.h" + +#define LOG_DBG_ENABLED IS_ENABLED(CONFIG_BT_L2CAP_LOG_LEVEL_DBG) +LOG_MODULE_REGISTER(bt_l2cap, CONFIG_BT_L2CAP_LOG_LEVEL); + +#define LE_CHAN_RTX(_w) \ + CONTAINER_OF(k_work_delayable_from_work(_w), struct bt_l2cap_le_chan, rtx_work) +#define CHAN_RX(_w) CONTAINER_OF(_w, struct bt_l2cap_le_chan, rx_work) + +#define L2CAP_LE_MIN_MTU 23 +#define L2CAP_LE_MIN_MPS 23 + +#define L2CAP_LE_MAX_CREDITS (BT_BUF_ACL_RX_COUNT - 1) + +#define L2CAP_LE_CID_FIXED_START 0x0001 +#define L2CAP_LE_CID_FIXED_END 0x003f +#define L2CAP_LE_CID_IS_FIXED(_cid) \ + (_cid >= L2CAP_LE_CID_FIXED_START && _cid <= L2CAP_LE_CID_FIXED_END) + +#define L2CAP_LE_CID_DYN_START 0x0040 +#define L2CAP_LE_CID_DYN_END 0x007f +#define L2CAP_LE_CID_IS_DYN(_cid) (_cid >= L2CAP_LE_CID_DYN_START && _cid <= L2CAP_LE_CID_DYN_END) + +#define L2CAP_LE_PSM_FIXED_START 0x0001 +#define L2CAP_LE_PSM_FIXED_END 0x007f +#define L2CAP_LE_PSM_DYN_START 0x0080 +#define L2CAP_LE_PSM_DYN_END 0x00ff +#define L2CAP_LE_PSM_IS_DYN(_psm) (_psm >= L2CAP_LE_PSM_DYN_START && _psm <= L2CAP_LE_PSM_DYN_END) + +#define L2CAP_CONN_TIMEOUT SEC2TICK(CONFIG_BT_L2CAP_CONN_RTX_TIMEOUT) +#define L2CAP_DISC_TIMEOUT SEC2TICK(2) +/** @brief Local L2CAP RTX (Response Timeout eXpired) + * + * Specification-allowed range for the value of RTX is 1 to 60 seconds. + */ +#define L2CAP_RTX_TIMEOUT SEC2TICK(2) + +#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) +/* Dedicated pool for disconnect buffers so they are guaranteed to be send + * even in case of data congestion due to flooding. + */ + +NET_BUF_POOL_FIXED_DEFINE(disc_pool, 1, + BT_L2CAP_BUF_SIZE(sizeof(struct bt_l2cap_sig_hdr) + + sizeof( + struct bt_l2cap_disconn_req)), + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + +#define ANY_OPCODE 0x100 +#define l2cap_lookup_ident(conn, ident, req_opcode) \ + __l2cap_lookup_ident(conn, ident, req_opcode, false) +#define l2cap_remove_ident(conn, ident, req_opcode) \ + __l2cap_lookup_ident(conn, ident, req_opcode, true) + +static struct list_node servers = LIST_INITIAL_VALUE(servers); +#endif /* CONFIG_BT_L2CAP_DYNAMIC_CHANNEL */ + +/* L2CAP signalling channel specific context */ + +struct bt_l2cap +{ + /* The channel this context is associated with */ + + struct bt_l2cap_le_chan chan; +}; + +static const struct bt_l2cap_ecred_cb *ecred_cb; +static struct bt_l2cap bt_l2cap_pool[CONFIG_BT_MAX_CONN]; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_l2cap_register_ecred_cb(const struct bt_l2cap_ecred_cb *cb) +{ + ecred_cb = cb; +} + +static uint8_t get_ident(void) +{ + static uint8_t ident; + + ident++; + + /* handle integer overflow (0 is not valid) */ + + if (!ident) + { + ident++; + } + + return ident; +} + +#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) +static struct bt_l2cap_le_chan *l2cap_chan_alloc_cid(struct bt_conn *conn, + struct bt_l2cap_chan *chan) +{ + struct bt_l2cap_le_chan *le_chan = BT_L2CAP_LE_CHAN(chan); + uint16_t cid; + + /* No action needed if there's already a CID allocated, e.g. in + * the case of a fixed channel. + */ + + if (le_chan->rx.cid > 0) + { + return le_chan; + } + + for (cid = L2CAP_LE_CID_DYN_START; cid <= L2CAP_LE_CID_DYN_END; cid++) + { + if (!bt_l2cap_le_lookup_rx_cid(conn, cid)) + { + le_chan->rx.cid = cid; + return le_chan; + } + } + + return NULL; +} + +static struct bt_l2cap_le_chan *__l2cap_lookup_ident( + struct bt_conn *conn, uint16_t ident, uint16_t req_opcode, bool remove) +{ + struct bt_l2cap_chan *chan; + struct list_node *prev = NULL; + + list_for_every_container(&conn->channels, chan, node) + { + if ((BT_L2CAP_LE_CHAN(chan)->ident == ident) && + ((BT_L2CAP_LE_CHAN(chan)->pending_req == req_opcode) || + (req_opcode == ANY_OPCODE))) + { + if (remove) + { + list_remove(&conn->channels, prev, &chan->node); + } + + return BT_L2CAP_LE_CHAN(chan); + } + + prev = &chan->node; + } + + return NULL; +} +#endif /* CONFIG_BT_L2CAP_DYNAMIC_CHANNEL */ + +void bt_l2cap_chan_remove(struct bt_conn *conn, struct bt_l2cap_chan *ch) +{ + struct bt_l2cap_chan *chan; + struct list_node *prev = NULL; + + list_for_every_container(&conn->channels, chan, node) + { + if (chan == ch) + { + list_remove(&conn->channels, prev, &chan->node); + return; + } + + prev = &chan->node; + } +} + +const char *bt_l2cap_chan_state_str(bt_l2cap_chan_state_t state) +{ + switch (state) + { + case BT_L2CAP_DISCONNECTED: + return "disconnected"; + case BT_L2CAP_CONNECTING: + return "connecting"; + case BT_L2CAP_CONFIG: + return "config"; + case BT_L2CAP_CONNECTED: + return "connected"; + case BT_L2CAP_DISCONNECTING: + return "disconnecting"; + default: + return "unknown"; + } +} + +#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) +#if defined(CONFIG_BT_L2CAP_LOG_LEVEL_DBG) +#define l2cap_chan_set_state(_chan, _state) \ + l2cap_chan_set_state_debug(_chan, _state, __func__, __LINE__) + +static void l2cap_chan_set_state_debug( + struct bt_l2cap_chan *chan, bt_l2cap_chan_state_t state, const char *func, + int line) +{ + struct bt_l2cap_le_chan *le_chan = BT_L2CAP_LE_CHAN(chan); + + LOG_DBG("chan %p psm 0x%04x %s -> %s", chan, le_chan->psm, + bt_l2cap_chan_state_str(le_chan->state), + bt_l2cap_chan_state_str(state)); + + /* check transitions validness */ + + switch (state) + { + case BT_L2CAP_DISCONNECTED: + + /* regardless of old state always allows this state */ + + break; + case BT_L2CAP_CONNECTING: + if (le_chan->state != BT_L2CAP_DISCONNECTED) + { + LOG_WRN("%s()%d: invalid transition", func, line); + } + break; + case BT_L2CAP_CONFIG: + if (le_chan->state != BT_L2CAP_CONNECTING) + { + LOG_WRN("%s()%d: invalid transition", func, line); + } + break; + case BT_L2CAP_CONNECTED: + if (le_chan->state != BT_L2CAP_CONFIG && + le_chan->state != BT_L2CAP_CONNECTING) + { + LOG_WRN("%s()%d: invalid transition", func, line); + } + break; + case BT_L2CAP_DISCONNECTING: + if (le_chan->state != BT_L2CAP_CONFIG && + le_chan->state != BT_L2CAP_CONNECTED) + { + LOG_WRN("%s()%d: invalid transition", func, line); + } + break; + default: + LOG_ERR("%s()%d: unknown (%u) state was set", func, line, state); + return; + } + + le_chan->state = state; +} +#else +static void l2cap_chan_set_state(struct bt_l2cap_chan *chan, + bt_l2cap_chan_state_t state) +{ + BT_L2CAP_LE_CHAN(chan)->state = state; +} +#endif /* CONFIG_BT_L2CAP_LOG_LEVEL_DBG */ +#endif /* CONFIG_BT_L2CAP_DYNAMIC_CHANNEL */ + +static void cancel_data_ready(struct bt_l2cap_le_chan *lechan); +static bool chan_has_data(struct bt_l2cap_le_chan *lechan); +static void l2cap_chan_del(struct bt_l2cap_chan *chan) +{ + const struct bt_l2cap_chan_ops *ops = chan->ops; + struct bt_l2cap_le_chan *le_chan = BT_L2CAP_LE_CHAN(chan); + struct net_buf *buf; + + LOG_DBG("conn %p chan %p", chan->conn, chan); + + if (!chan->conn) + { + goto destroy; + } + + cancel_data_ready(le_chan); + + /* Remove buffers on the PDU TX queue. We can't do that in + * `l2cap_chan_destroy()` as it is not called for fixed channels. + */ + + while ((buf = k_fifo_get(&le_chan->tx_queue, 0))) + { + bt_conn_tx_cb_t cb = closure_cb(buf->user_data); + + if (cb != NULL) + { + void *user_data = closure_data(buf->user_data); + + cb(chan->conn, user_data, -ESHUTDOWN); + } + + net_buf_unref(buf); + } + + if (ops->disconnected) + { + ops->disconnected(chan); + } + + chan->conn = NULL; + +destroy: +#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) + /* Reset internal members of common channel */ + + l2cap_chan_set_state(chan, BT_L2CAP_DISCONNECTED); + BT_L2CAP_LE_CHAN(chan)->psm = 0U; +#endif + if (chan->destroy) + { + chan->destroy(chan); + } + + if (ops->released) + { + ops->released(chan); + } +} + +#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) +static void l2cap_rtx_timeout(struct k_work *work) +{ + struct bt_l2cap_le_chan *chan = LE_CHAN_RTX(work); + struct bt_conn *conn = chan->chan.conn; + + LOG_ERR("chan %p timeout", chan); + + bt_l2cap_chan_remove(conn, &chan->chan); + l2cap_chan_del(&chan->chan); + + /* Remove other channels if pending on the same ident */ + + while ((chan = l2cap_remove_ident(conn, chan->ident, chan->pending_req))) + { + l2cap_chan_del(&chan->chan); + } +} + +static void l2cap_chan_le_recv(struct bt_l2cap_le_chan *chan, + struct net_buf *buf); + +static void l2cap_rx_process(struct k_work *work) +{ + struct bt_l2cap_le_chan *ch = CHAN_RX(work); + struct net_buf *buf; + + while ((buf = k_fifo_get(&ch->rx_queue, 0))) + { + LOG_DBG("ch %p buf %p", ch, buf); + l2cap_chan_le_recv(ch, buf); + net_buf_unref(buf); + } +} +#endif /* CONFIG_BT_L2CAP_DYNAMIC_CHANNEL */ + +void bt_l2cap_chan_add(struct bt_conn *conn, struct bt_l2cap_chan *chan, + bt_l2cap_chan_destroy_t destroy) +{ + /* Attach channel to the connection */ + + list_add_tail(&conn->channels, &chan->node); + chan->conn = conn; + chan->destroy = destroy; + + LOG_DBG("conn %p chan %p", conn, chan); +} + +static void init_le_chan_private(struct bt_l2cap_le_chan *le_chan) +{ + /* Initialize private members of the struct. We can't "just memset" as + * some members are used as application parameters. + */ +#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) + le_chan->_sdu = NULL; + le_chan->_sdu_len = 0; +#if defined(CONFIG_BT_L2CAP_SEG_RECV) + le_chan->_sdu_len_done = 0; +#endif /* CONFIG_BT_L2CAP_SEG_RECV */ +#endif /* CONFIG_BT_L2CAP_DYNAMIC_CHANNEL */ + memset(&le_chan->_pdu_ready, 0, sizeof(le_chan->_pdu_ready)); + le_chan->_pdu_remaining = 0; +} + +static bool l2cap_chan_add(struct bt_conn *conn, struct bt_l2cap_chan *chan, + bt_l2cap_chan_destroy_t destroy) +{ + struct bt_l2cap_le_chan *le_chan; + +#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) + le_chan = l2cap_chan_alloc_cid(conn, chan); +#else + le_chan = BT_L2CAP_LE_CHAN(chan); +#endif + + if (!le_chan) + { + LOG_ERR("Unable to allocate L2CAP channel ID"); + return false; + } + + atomic_set(chan->status, 0); + init_le_chan_private(le_chan); + + bt_l2cap_chan_add(conn, chan, destroy); + +#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) + /* All dynamic channels have the destroy handler which makes sure that + * the RTX work structure is properly released with a cancel sync. The + * fixed signal channel is only removed when disconnected and the + * disconnected handler is always called from the Bluetooth workqueue, + * which the RTX work also runs on, so canceling from there should always + * succeed. + */ + + k_work_init_delayable(&le_chan->rtx_work, l2cap_rtx_timeout); + + if (L2CAP_LE_CID_IS_DYN(le_chan->rx.cid)) + { + k_work_init(&le_chan->rx_work, l2cap_rx_process); + k_fifo_init(&le_chan->rx_queue); + l2cap_chan_set_state(chan, BT_L2CAP_CONNECTING); + } +#endif /* CONFIG_BT_L2CAP_DYNAMIC_CHANNEL */ + + return true; +} + +void bt_l2cap_connected(struct bt_conn *conn) +{ + struct bt_l2cap_chan *chan; + + if (bt_conn_is_br(conn)) + { + bt_l2cap_br_connected(conn); + return; + } + + STRUCT_SECTION_FOREACH(bt_l2cap_fixed_chan, fchan) + { + struct bt_l2cap_le_chan *le_chan; + + DEBUGASSERT(L2CAP_LE_CID_IS_FIXED(fchan->cid)); + + chan = bt_l2cap_le_lookup_tx_cid(conn, fchan->cid); + DEBUGASSERT(chan == NULL); + + if (fchan->accept(conn, &chan) < 0) + { + continue; + } + + le_chan = BT_L2CAP_LE_CHAN(chan); + +/* Fill up remaining fixed channel context attached in + * fchan->accept() + */ + + le_chan->rx.cid = fchan->cid; + le_chan->tx.cid = fchan->cid; + + if (!l2cap_chan_add(conn, chan, NULL)) + { + return; + } + + k_fifo_init(&le_chan->tx_queue); + + if (chan->ops->connected) + { + chan->ops->connected(chan); + } + + /* Always set output status to fixed channels */ + + atomic_set_bit(chan->status, BT_L2CAP_STATUS_OUT); + + if (chan->ops->status) + { + chan->ops->status(chan, chan->status); + } + } +} + +void bt_l2cap_disconnected(struct bt_conn *conn) +{ + struct bt_l2cap_chan *chan; + struct bt_l2cap_chan *next; + + if (bt_conn_is_br(conn)) + { + bt_l2cap_br_disconnected(conn); + return; + } + + list_for_every_container_safe(&conn->channels, chan, next, node) + { + bt_l2cap_chan_remove(conn, chan); + l2cap_chan_del(chan); + } +} + +static struct net_buf *l2cap_create_le_sig_pdu(uint8_t code, + uint8_t ident, uint16_t len) +{ + struct bt_l2cap_sig_hdr *hdr; + struct net_buf_pool *pool = NULL; + struct net_buf *buf; + +#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) + if (code == BT_L2CAP_DISCONN_REQ) + { + pool = &disc_pool; + } +#endif + + /* Don't wait more than the minimum RTX timeout of 2 seconds */ + + buf = bt_l2cap_create_pdu_timeout(pool, 0, L2CAP_RTX_TIMEOUT); + if (!buf) + { + /* If it was not possible to allocate a buffer within the + * timeout return NULL. + */ + + LOG_ERR("Unable to allocate buffer for op 0x%02x", code); + return NULL; + } + + hdr = net_buf_add(buf, sizeof(*hdr)); + hdr->code = code; + hdr->ident = ident; + hdr->len = sys_cpu_to_le16(len); + + return buf; +} + +/* Send the buffer over the signalling channel. Release it in case of + * failure. Any other cleanup in failure to send should be handled by the + * disconnected handler. + */ + +static int l2cap_send_sig(struct bt_conn *conn, struct net_buf *buf) +{ + struct bt_l2cap_chan *ch = bt_l2cap_le_lookup_tx_cid(conn, + BT_L2CAP_CID_LE_SIG); + struct bt_l2cap_le_chan *chan = BT_L2CAP_LE_CHAN(ch); + + int err = bt_l2cap_send_pdu(chan, buf, NULL, NULL); + + if (err) + { + net_buf_unref(buf); + } + + return err; +} + +#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) +static void l2cap_chan_send_req(struct bt_l2cap_chan *chan, + struct net_buf *buf, clock_t timeout) +{ + if (l2cap_send_sig(chan->conn, buf)) + { + return; + } + + /* BLUETOOTH SPECIFICATION Version 4.2 [Vol 3, Part A] page 126: + * + * The value of this timer is implementation-dependent but the minimum + * initial value is 1 second and the maximum initial value is 60 seconds. + * One RTX timer shall exist for each outstanding signaling request, + * including each Echo Request. The timer disappears on the final + * expiration, when the response is received, or the physical link is lost. + */ + + bt_work_reschedule(&(BT_L2CAP_LE_CHAN(chan)->rtx_work), timeout); +} + +static int l2cap_le_conn_req(struct bt_l2cap_le_chan *ch) +{ + struct net_buf *buf; + struct bt_l2cap_le_conn_req *req; + uint8_t ident; + + ident = get_ident(); + + buf = l2cap_create_le_sig_pdu(BT_L2CAP_LE_CONN_REQ, ident, sizeof(*req)); + if (!buf) + { + return -ENOMEM; + } + + /* TODO Ident handling/setting should ideally be done in + * l2cap_chan_send_req after the request is successfully sent on the + * channel but will require special considerations for functions such as + * l2cap_ecred_conn_req and bt_l2cap_ecred_chan_reconfigure where the ident + * is set for multiple channels, but the request is only sent on one. + */ + + ch->ident = ident; + ch->pending_req = BT_L2CAP_LE_CONN_REQ; + + req = net_buf_add(buf, sizeof(*req)); + req->psm = sys_cpu_to_le16(ch->psm); + req->scid = sys_cpu_to_le16(ch->rx.cid); + req->mtu = sys_cpu_to_le16(ch->rx.mtu); + req->mps = sys_cpu_to_le16(ch->rx.mps); + req->credits = sys_cpu_to_le16(ch->rx.credits); + + l2cap_chan_send_req(&ch->chan, buf, L2CAP_CONN_TIMEOUT); + + return 0; +} + +#if defined(CONFIG_BT_L2CAP_ECRED) +static int l2cap_ecred_conn_req(struct bt_l2cap_chan **chan, int channels) +{ + struct net_buf *buf; + struct bt_l2cap_ecred_conn_req *req; + struct bt_l2cap_le_chan *ch; + int i; + uint8_t ident; + __maybe_unused uint16_t req_psm; + __maybe_unused uint16_t req_mtu; + + if (!chan || !channels) + { + return -EINVAL; + } + + ident = get_ident(); + + buf = l2cap_create_le_sig_pdu(BT_L2CAP_ECRED_CONN_REQ, ident, + sizeof(*req) + + (channels * sizeof(uint16_t))); + + if (!buf) + { + return -ENOMEM; + } + + req = net_buf_add(buf, sizeof(*req)); + + ch = BT_L2CAP_LE_CHAN(chan[0]); + + /* Init common parameters */ + + req->psm = sys_cpu_to_le16(ch->psm); + req->mtu = sys_cpu_to_le16(ch->rx.mtu); + req->mps = sys_cpu_to_le16(ch->rx.mps); + req->credits = sys_cpu_to_le16(ch->rx.credits); + req_psm = ch->psm; + req_mtu = ch->tx.mtu; + + for (i = 0; i < channels; i++) + { + ch = BT_L2CAP_LE_CHAN(chan[i]); + + DEBUGASSERT(ch->psm == req_psm); + DEBUGASSERT(ch->tx.mtu == req_mtu); + + ch->ident = ident; + ch->pending_req = BT_L2CAP_ECRED_CONN_REQ; + + net_buf_add_le16(buf, ch->rx.cid); + } + + l2cap_chan_send_req(*chan, buf, L2CAP_CONN_TIMEOUT); + + return 0; +} +#endif /* defined(CONFIG_BT_L2CAP_ECRED) */ + +static void l2cap_le_encrypt_change(struct bt_l2cap_chan *chan, + uint8_t status) +{ + int err; + struct bt_l2cap_le_chan *le = BT_L2CAP_LE_CHAN(chan); + + /* Skip channels that are not pending waiting for encryption */ + + if (!atomic_test_and_clear_bit(chan->status, + BT_L2CAP_STATUS_ENCRYPT_PENDING)) + { + return; + } + + if (status) + { + goto fail; + } + +#if defined(CONFIG_BT_L2CAP_ECRED) + if (le->ident) + { + struct bt_l2cap_chan *echan[BT_L2CAP_ECRED_CHAN_MAX_PER_REQ]; + struct bt_l2cap_chan *ch; + int i = 0; + + list_for_every_container(&chan->conn->channels, ch, node) + { + if (le->ident == BT_L2CAP_LE_CHAN(ch)->ident) + { + DEBUGASSERT(i < BT_L2CAP_ECRED_CHAN_MAX_PER_REQ); + atomic_clear_bit(ch->status, BT_L2CAP_STATUS_ENCRYPT_PENDING); + echan[i++] = ch; + } + } + + /* Retry ecred connect */ + + l2cap_ecred_conn_req(echan, i); + return; + } +#endif /* defined(CONFIG_BT_L2CAP_ECRED) */ + + /* Retry to connect */ + + err = l2cap_le_conn_req(le); + if (err) + { + goto fail; + } + + return; +fail: + bt_l2cap_chan_remove(chan->conn, chan); + l2cap_chan_del(chan); +} +#endif /* CONFIG_BT_L2CAP_DYNAMIC_CHANNEL */ + +void bt_l2cap_security_changed(struct bt_conn *conn, uint8_t hci_status) +{ + struct bt_l2cap_chan *chan; + struct bt_l2cap_chan *next; + + if (bt_conn_is_br(conn)) + { + l2cap_br_encrypt_change(conn, hci_status); + return; + } + + list_for_every_container_safe(&conn->channels, chan, next, node) + { +#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) + l2cap_le_encrypt_change(chan, hci_status); +#endif + + if (chan->ops->encrypt_change) + { + chan->ops->encrypt_change(chan, hci_status); + } + } +} + +struct net_buf *bt_l2cap_create_pdu_timeout( + struct net_buf_pool *pool, size_t reserve, clock_t timeout) +{ + if (!((timeout) == (0)) + && nxsched_gettid() == k_work_queue_thread_get(&k_sys_work_q)) + { + timeout = 0; + } + + return bt_conn_create_pdu_timeout(pool, + sizeof(struct bt_l2cap_hdr) + reserve, + timeout); +} + +static void raise_data_ready(struct bt_l2cap_le_chan *le_chan) +{ + __maybe_unused bool added; + + /* The l2cap_data_ready list is only ever modified under the host + * lock: here (append, any thread context), in cancel_data_ready() (remove, + * any thread context) and in lower_data_ready() (remove, TX processor + * context, which holds the lock across the whole pass). + */ + + bt_dev_lock(); + + if (!list_find(&le_chan->chan.conn->l2cap_data_ready, &le_chan->_pdu_ready, + NULL)) + { + list_add_tail(&le_chan->chan.conn->l2cap_data_ready, + &le_chan->_pdu_ready); + + added = true; + } + else + { + added = false; + } + + bt_dev_unlock(); + + LOG_DBG("L2CAP channel %p data ready %s", le_chan, + added ? "added" : "already added"); + + bt_conn_data_ready(le_chan->chan.conn); +} + +static void lower_data_ready(struct bt_l2cap_le_chan *le_chan) +{ + struct bt_conn *conn = le_chan->chan.conn; + __maybe_unused struct list_node *s; + + /* Only called from the TX processor, which holds the host lock + * across the whole processing pass. + */ + + BT_DEV_LOCK_ASSERT(); + + s = list_remove_head(&conn->l2cap_data_ready); + + LOG_DBG("%p", le_chan); + + DEBUGASSERT(s == &le_chan->_pdu_ready); +} + +static void cancel_data_ready(struct bt_l2cap_le_chan *le_chan) +{ + struct bt_conn *conn = le_chan->chan.conn; + + LOG_DBG("%p", le_chan); + + /* Take the host lock as this function can be called from any + * thread context and the data ready list must not be modified while we are + * removing the channel from it (see raise_data_ready()). + */ + + bt_dev_lock(); + + list_find_and_remove(&conn->l2cap_data_ready, &le_chan->_pdu_ready); + + bt_dev_unlock(); +} + +int bt_l2cap_send_pdu( + struct bt_l2cap_le_chan *le_chan, struct net_buf *pdu, bt_conn_tx_cb_t cb, + void *user_data) +{ + if (!le_chan->chan.conn || le_chan->chan.conn->state != BT_CONN_CONNECTED) + { + return -ENOTCONN; + } + + if (pdu->ref != 1) + { + /* The host may alter the buf contents when fragmenting. Higher + * layers cannot expect the buf contents to stay intact. Extra refs + * suggests a silent data corruption would occur if not for this error. + */ + + LOG_ERR("Expecting 1 ref, got %d", pdu->ref); + return -EINVAL; + } + + if (pdu->user_data_size < sizeof(struct closure)) + { + LOG_DBG("not enough room in user_data %d < %d pool %p", + pdu->user_data_size, + CONFIG_BT_CONN_TX_USER_DATA_SIZE, pdu->pool); + return -EINVAL; + } + + make_closure(pdu->user_data, cb, user_data); + LOG_DBG("push: pdu %p len %d cb %p userdata %p", pdu, pdu->len, cb, + user_data); + + k_fifo_put(&le_chan->tx_queue, pdu); + + raise_data_ready(le_chan); /* tis just a flag */ + + return 0; /* look ma, no failures */ +} + +/* L2CAP channel wants to send a PDU */ + +static bool chan_has_data(struct bt_l2cap_le_chan *lechan) +{ + return !k_fifo_is_empty(&lechan->tx_queue); +} + +#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) +static bool test_and_dec(atomic_t *target) +{ + atomic_t old_value; + atomic_t new_value; + + do + { + old_value = atomic_read(target); + if (!old_value) + { + return false; + } + + new_value = old_value - 1; + } + while (atomic_cmpxchg_value(target, old_value, new_value) == 0); + + return true; +} +#endif + +/* Just like in group projects :p */ + +static void chan_take_credit(struct bt_l2cap_le_chan *lechan) +{ +#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) + if (!L2CAP_LE_CID_IS_DYN(lechan->tx.cid)) + { + return; + } + + if (!test_and_dec(&lechan->tx.credits)) + { + /* Always ensure you have credits before calling this fn */ + + DEBUGASSERT(0); + } + + /* Notify channel user that it can't send anymore on this channel. */ + + if (!atomic_read(&lechan->tx.credits)) + { + LOG_DBG("chan %p paused", lechan); + atomic_clear_bit(lechan->chan.status, BT_L2CAP_STATUS_OUT); + + if (lechan->chan.ops->status) + { + lechan->chan.ops->status(&lechan->chan, lechan->chan.status); + } + } +#endif +} + +static struct bt_l2cap_le_chan *get_ready_chan(struct bt_conn *conn) +{ + struct bt_l2cap_le_chan *lechan; + + struct list_node *pdu_ready = list_peek_head(&conn->l2cap_data_ready); + + if (!pdu_ready) + { + LOG_DBG("nothing to send on this conn"); + return NULL; + } + + list_for_every_container(&conn->l2cap_data_ready, lechan, _pdu_ready) + { + if (chan_has_data(lechan)) + { + LOG_DBG("sending from chan %p (%s) data %d", lechan, + L2CAP_LE_CID_IS_DYN(lechan->tx.cid) ? "dynamic" : "static", + chan_has_data(lechan)); + return lechan; + } + + LOG_DBG("chan %p has no data", lechan); + lower_data_ready(lechan); + } + + return NULL; +} + +static void l2cap_chan_sdu_sent(struct bt_conn *conn, + void *user_data, int err) +{ + struct bt_l2cap_chan *chan; + uint16_t cid = POINTER_TO_UINT(user_data); + + LOG_DBG("conn %p CID 0x%04x err %d", conn, cid, err); + + if (err) + { + LOG_DBG("error %d when sending SDU", err); + + return; + } + + chan = bt_l2cap_le_lookup_tx_cid(conn, cid); + if (!chan) + { + LOG_DBG("got SDU sent cb for disconnected chan (CID %u)", cid); + + return; + } + + if (chan->ops->sent) + { + chan->ops->sent(chan); + } +} + +static uint16_t get_pdu_len(struct bt_l2cap_le_chan *lechan, + struct net_buf *buf) +{ + if (!L2CAP_LE_CID_IS_DYN(lechan->tx.cid)) + { + /* No segmentation shenanigans on static channels */ + + return buf->len; + } + + return MIN(buf->len, lechan->tx.mps); +} + +static bool chan_has_credits(struct bt_l2cap_le_chan *lechan) +{ +#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) + if (!L2CAP_LE_CID_IS_DYN(lechan->tx.cid)) + { + return true; + } + + LOG_DBG("chan %p credits %ld", lechan, atomic_read(&lechan->tx.credits)); + + return atomic_read(&lechan->tx.credits) >= 1; +#else + return true; +#endif +} + +__weak void bt_test_l2cap_data_pull_spy( + struct bt_conn *conn, struct bt_l2cap_le_chan *lechan, size_t amount, + size_t *length) +{ +} + +struct net_buf *l2cap_data_pull(struct bt_conn *conn, + size_t amount, size_t *length) +{ + struct bt_l2cap_le_chan *lechan = get_ready_chan(conn); + + if (IS_ENABLED(CONFIG_BT_TESTING)) + { + /* Allow tests to snoop in */ + + bt_test_l2cap_data_pull_spy(conn, lechan, amount, length); + } + + if (!lechan) + { + LOG_DBG("no channel conn %p", conn); + bt_tx_irq_raise(); + return NULL; + } + + /* Leave the PDU buffer in the queue until we have sent all its + * fragments. + * + * For SDUs we do the same, we keep it in the queue until all the segments + * have been sent, adding the PDU headers just-in-time. + */ + + struct net_buf *fifo_pdu = k_fifo_peek_head(&lechan->tx_queue); + + /* We don't have anything to send for the current channel. We could + * however have something to send on another channel that is attached to + * the same ACL connection. Re-trigger the TX processor: it will call us + * again and this time we will select another channel to pull data from. + */ + + if (!fifo_pdu) + { + bt_tx_irq_raise(); + return NULL; + } + + DEBUGASSERT(conn->state == BT_CONN_CONNECTED); + + if (bt_buf_has_view(fifo_pdu)) + { + LOG_ERR("already have view on %p", fifo_pdu); + return NULL; + } + + if (lechan->_pdu_remaining == 0 && !chan_has_credits(lechan)) + { + /* We don't have credits to send a new K-frame PDU. Remove the + * channel from the ready-list, it will be added back later when we get + * more credits. + */ + + LOG_DBG("no credits for new K-frame on %p", lechan); + lower_data_ready(lechan); + return NULL; + } + + struct net_buf *pdu = net_buf_ref(fifo_pdu); + + /* Add PDU header */ + + if (lechan->_pdu_remaining == 0) + { + struct bt_l2cap_hdr *hdr; + uint16_t pdu_len = get_pdu_len(lechan, pdu); + + LOG_DBG("Adding L2CAP PDU header: buf %p chan %p len %u / " + "%u", pdu, lechan, pdu_len, + pdu->len); + + LOG_HEXDUMP_DBG(pdu->data, pdu->len, "PDU payload"); + + hdr = net_buf_push(pdu, sizeof(*hdr)); + hdr->len = sys_cpu_to_le16(pdu_len); + hdr->cid = sys_cpu_to_le16(lechan->tx.cid); + + lechan->_pdu_remaining = pdu_len + sizeof(*hdr); + chan_take_credit(lechan); + } + + /* Whether the data to be pulled is the last ACL fragment */ + + bool last_frag = amount >= lechan->_pdu_remaining; + + /* Whether the data to be pulled is part of the last L2CAP segment. For + * static channels, this variable will always be true, even though static + * channels don't have the concept of L2CAP segments. + */ + + bool last_seg = lechan->_pdu_remaining == pdu->len; + + if (last_frag && last_seg) + { + LOG_DBG("last frag of last seg, dequeuing %p", pdu); + fifo_pdu = k_fifo_get(&lechan->tx_queue, 0); + + DEBUGASSERT(fifo_pdu == pdu); + + net_buf_unref(fifo_pdu); + } + + if (last_frag && L2CAP_LE_CID_IS_DYN(lechan->tx.cid)) + { + bool sdu_end = last_frag && last_seg; + + LOG_DBG("adding %s callback", sdu_end ? "`sdu_sent`" : "NULL"); + + /* No user callbacks for SDUs */ + + make_closure(pdu->user_data, sdu_end ? l2cap_chan_sdu_sent : NULL, + sdu_end ? UINT_TO_POINTER(lechan->tx.cid) : NULL); + } + + if (last_frag) + { + LOG_DBG("done sending PDU"); + + /* Lowering the "request to send" and raising it again allows + * fair scheduling of channels on an ACL link: the channel is marked as + * "ready to send" by adding a reference to it on a FIFO on `conn`. + * Adding it again will send it to the back of the queue. + * + * TODO: add a user-controlled QoS function. + */ + + LOG_DBG("chan %p done", lechan); + lower_data_ready(lechan); + + /* Append channel to list if it still has data */ + + if (chan_has_data(lechan)) + { + LOG_DBG("chan %p ready", lechan); + raise_data_ready(lechan); + } + } + + /* This is used by `conn.c` to figure out if the PDU is done sending. */ + + *length = lechan->_pdu_remaining; + + if (lechan->_pdu_remaining > amount) + { + lechan->_pdu_remaining -= amount; + } + else + { + lechan->_pdu_remaining = 0; + } + + return pdu; +} + +static void l2cap_send_reject( + struct bt_conn *conn, uint8_t ident, uint16_t reason, void *data, + uint8_t data_len) +{ + struct bt_l2cap_cmd_reject *rej; + struct net_buf *buf; + + buf = l2cap_create_le_sig_pdu(BT_L2CAP_CMD_REJECT, ident, + sizeof(*rej) + data_len); + if (!buf) + { + return; + } + + rej = net_buf_add(buf, sizeof(*rej)); + rej->reason = sys_cpu_to_le16(reason); + + if (data) + { + net_buf_add_mem(buf, data, data_len); + } + + l2cap_send_sig(conn, buf); +} + +static void le_conn_param_rsp(struct bt_l2cap *l2cap, struct net_buf *buf) +{ + struct bt_l2cap_conn_param_rsp *rsp = (void *)buf->data; + + if (buf->len != sizeof(*rsp)) + { + LOG_ERR("Invalid LE conn param rsp size (%u != %zu)", buf->len, + sizeof(*rsp)); + return; + } + + __maybe_unused uint16_t result = sys_le16_to_cpu(rsp->result); + + LOG_DBG("L2CAP conn param rsp result %u", result); + + if (IS_ENABLED(CONFIG_BT_USER_CONN_PARAM_REJECTED) && + result == BT_L2CAP_CONN_PARAM_REJECTED) + { + struct bt_conn *conn = l2cap->chan.chan.conn; + + /* Mirror le_conn_update_complete(): only notify for + * application-initiated updates, not host-initiated (auto) ones. + */ + + if (!atomic_test_bit(conn->flags, + BT_CONN_PERIPHERAL_PARAM_AUTO_UPDATE)) + { + bt_conn_notify_le_param_rejected(conn, + BT_CONN_PARAM_REJECT_ERR_L2CAP_CPUP); + } + } +} + +static void le_conn_param_update_req(struct bt_l2cap *l2cap, + uint8_t ident, struct net_buf *buf) +{ + struct bt_conn *conn = l2cap->chan.chan.conn; + struct bt_le_conn_param param; + struct bt_l2cap_conn_param_rsp *rsp; + struct bt_l2cap_conn_param_req *req = (void *)buf->data; + bool accepted; + + if (buf->len != sizeof(*req)) + { + LOG_ERR("Invalid LE conn update param req size (%u != %zu)", buf->len, + sizeof(*req)); + return; + } + + if (conn->state != BT_CONN_CONNECTED) + { + LOG_WRN("Not connected"); + return; + } + + if (conn->role != BT_HCI_ROLE_CENTRAL) + { + l2cap_send_reject(conn, ident, BT_L2CAP_REJ_NOT_UNDERSTOOD, NULL, 0); + return; + } + + param.interval_min = sys_le16_to_cpu(req->min_interval); + param.interval_max = sys_le16_to_cpu(req->max_interval); + param.latency = sys_le16_to_cpu(req->latency); + param.timeout = sys_le16_to_cpu(req->timeout); + + LOG_DBG("min 0x%04x max 0x%04x latency: 0x%04x timeout: 0x%04x", + param.interval_min, + param.interval_max, param.latency, param.timeout); + + buf = l2cap_create_le_sig_pdu(BT_L2CAP_CONN_PARAM_RSP, ident, + sizeof(*rsp)); + if (!buf) + { + return; + } + + accepted = bt_conn_le_param_req(conn, ¶m); + + rsp = net_buf_add(buf, sizeof(*rsp)); + if (accepted) + { + rsp->result = sys_cpu_to_le16(BT_L2CAP_CONN_PARAM_ACCEPTED); + } + else + { + rsp->result = sys_cpu_to_le16(BT_L2CAP_CONN_PARAM_REJECTED); + } + + l2cap_send_sig(conn, buf); + + if (accepted) + { + bt_conn_le_conn_update(conn, ¶m); + } +} + +struct bt_l2cap_chan *bt_l2cap_le_lookup_tx_cid(struct bt_conn *conn, + uint16_t cid) +{ + struct bt_l2cap_chan *chan; + + list_for_every_container(&conn->channels, chan, node) + { + if (BT_L2CAP_LE_CHAN(chan)->tx.cid == cid) + { + return chan; + } + } + + return NULL; +} + +struct bt_l2cap_chan *bt_l2cap_le_lookup_rx_cid(struct bt_conn *conn, + uint16_t cid) +{ + struct bt_l2cap_chan *chan; + + list_for_every_container(&conn->channels, chan, node) + { + if (BT_L2CAP_LE_CHAN(chan)->rx.cid == cid) + { + return chan; + } + } + + return NULL; +} + +#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) +struct bt_l2cap_server *bt_l2cap_server_lookup_psm(uint16_t psm) +{ + struct bt_l2cap_server *server; + + list_for_every_container(&servers, server, node) + { + if (server->psm == psm) + { + return server; + } + } + + return NULL; +} + +int bt_l2cap_server_register(struct bt_l2cap_server *server) +{ + if (!server->accept) + { + return -EINVAL; + } + + if (server->psm) + { + if (server->psm < L2CAP_LE_PSM_FIXED_START || + server->psm > L2CAP_LE_PSM_DYN_END) + { + return -EINVAL; + } + + /* Check if given PSM is already in use */ + + if (bt_l2cap_server_lookup_psm(server->psm)) + { + LOG_DBG("PSM already registered"); + return -EADDRINUSE; + } + } + else + { + uint16_t psm; + + for (psm = L2CAP_LE_PSM_DYN_START; psm <= L2CAP_LE_PSM_DYN_END; psm++) + { + if (!bt_l2cap_server_lookup_psm(psm)) + { + break; + } + } + + if (psm > L2CAP_LE_PSM_DYN_END) + { + LOG_WRN("No free dynamic PSMs available"); + return -EADDRNOTAVAIL; + } + + LOG_DBG("Allocated PSM 0x%04x for new server", psm); + server->psm = psm; + } + + if (server->sec_level > BT_SECURITY_L4) + { + return -EINVAL; + } + else if (server->sec_level < BT_SECURITY_L1) + { + /* Level 0 is only applicable for BR/EDR */ + + server->sec_level = BT_SECURITY_L1; + } + + LOG_DBG("PSM 0x%04x", server->psm); + + list_add_tail(&servers, &server->node); + + return 0; +} + +#if defined(CONFIG_BT_L2CAP_SEG_RECV) +static void l2cap_chan_seg_recv_rx_init(struct bt_l2cap_le_chan *chan) +{ + if (chan->rx.mps > BT_L2CAP_RX_MTU) + { + LOG_ERR("Limiting RX MPS by stack buffer size."); + chan->rx.mps = BT_L2CAP_RX_MTU; + } + + chan->_sdu_len = 0; + chan->_sdu_len_done = 0; +} +#endif /* CONFIG_BT_L2CAP_SEG_RECV */ + +static void l2cap_chan_rx_init(struct bt_l2cap_le_chan *chan) +{ + LOG_DBG("chan %p", chan); + + chan->rx.cid = 0U; + + /* Redirect to experimental API. */ + + IF_ENABLED(CONFIG_BT_L2CAP_SEG_RECV, ( + { + if (chan->chan.ops->seg_recv) + { + l2cap_chan_seg_recv_rx_init(chan); + return; + } + } + + )) + + /* Use existing MTU if defined */ + + if (!chan->rx.mtu) + { + /* If application has not provide the incoming L2CAP SDU MTU use + * an MTU that does not require segmentation. + */ + + chan->rx.mtu = BT_L2CAP_SDU_RX_MTU; + } + + /* MPS shall not be bigger than MTU + BT_L2CAP_SDU_HDR_SIZE as the + * remaining bytes cannot be used. + */ + + chan->rx.mps = MIN(chan->rx.mtu + BT_L2CAP_SDU_HDR_SIZE, BT_L2CAP_RX_MTU); + + /* Truncate MTU if channel have disabled segmentation but still have + * set an MTU which requires it. + */ + + if (!chan->chan.ops->alloc_buf && + (chan->rx.mps < chan->rx.mtu + BT_L2CAP_SDU_HDR_SIZE)) + { + LOG_WRN("Segmentation disabled but MTU > MPS, truncating MTU"); + chan->rx.mtu = chan->rx.mps - BT_L2CAP_SDU_HDR_SIZE; + } + + atomic_set(&chan->rx.credits, 1); +} + +/** @brief Get @c chan->state. + * + * This field does not exist when @kconfig{CONFIG_BT_L2CAP_DYNAMIC_CHANNEL} + * is disabled. In that case, this function returns @ref BT_L2CAP_CONNECTED + * since the struct can only represent static channels in that case and + * static channels are always connected. + */ + +static bt_l2cap_chan_state_t bt_l2cap_chan_get_state( + struct bt_l2cap_chan *chan) +{ +#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) + return BT_L2CAP_LE_CHAN(chan)->state; +#else + return BT_L2CAP_CONNECTED; +#endif +} + +static void l2cap_chan_tx_init(struct bt_l2cap_le_chan *chan) +{ + LOG_DBG("chan %p", chan); + + (void)memset(&chan->tx, 0, sizeof(chan->tx)); + atomic_set(&chan->tx.credits, 0); + k_fifo_init(&chan->tx_queue); +} + +static void l2cap_chan_tx_give_credits(struct bt_l2cap_le_chan *chan, + uint16_t credits) +{ + LOG_DBG("chan %p credits %u", chan, credits); + + atomic_add(&chan->tx.credits, credits); + + if (!atomic_test_and_set_bit(chan->chan.status, BT_L2CAP_STATUS_OUT)) + { + LOG_DBG("chan %p unpaused", chan); + if (chan->chan.ops->status) + { + chan->chan.ops->status(&chan->chan, chan->chan.status); + } + + if (chan_has_data(chan)) + { + raise_data_ready(chan); + } + } +} + +static void l2cap_chan_destroy(struct bt_l2cap_chan *chan) +{ + struct bt_l2cap_le_chan *le_chan = BT_L2CAP_LE_CHAN(chan); + struct net_buf *buf; + + LOG_DBG("chan %p cid 0x%04x", le_chan, le_chan->rx.cid); + + /* Cancel ongoing work. Since the channel can be reused after this + * we need to sync to make sure that the kernel does not have it in its + * queue anymore. + * + * In the case where we are in the context of executing the rtx_work item, + * we don't sync as it will deadlock the workqueue. + */ + + struct k_work_q *rtx_work_queue = le_chan->rtx_work.queue; + + if (rtx_work_queue == NULL || nxsched_gettid() != + rtx_work_queue->thread_id) + { + k_work_cancel_delayable_sync(&le_chan->rtx_work, &le_chan->rtx_sync); + } + else + { + k_work_cancel_delayable(&le_chan->rtx_work); + } + + /* Make sure the RX work is not left queued with a reference to a + * channel object that may be freed or reused after this call. + */ + + (void)k_work_cancel(&le_chan->rx_work); + + /* Remove buffers on the SDU RX queue */ + + while ((buf = k_fifo_get(&le_chan->rx_queue, 0))) + { + net_buf_unref(buf); + } + + /* Destroy segmented SDU if it exists */ + + if (le_chan->_sdu) + { + net_buf_drop(&le_chan->_sdu); + le_chan->_sdu_len = 0U; + } +} + +static uint16_t le_err_to_result(int err) +{ + switch (err) + { + case -ENOMEM: + return BT_L2CAP_LE_ERR_NO_RESOURCES; + case -EACCES: + return BT_L2CAP_LE_ERR_AUTHORIZATION; + case -EPERM: + return BT_L2CAP_LE_ERR_KEY_SIZE; + case -ENOTSUP: + /* This handle the cases where a fixed channel is registered but + * for some reason (e.g. controller not supporting a feature) cannot + * be + * used. + */ + + return BT_L2CAP_LE_ERR_PSM_NOT_SUPP; + default: + return BT_L2CAP_LE_ERR_UNACCEPT_PARAMS; + } +} + +static uint16_t l2cap_chan_accept( + struct bt_conn *conn, struct bt_l2cap_server *server, uint16_t scid, + uint16_t mtu, uint16_t mps, uint16_t credits, struct bt_l2cap_chan **chan) +{ + struct bt_l2cap_le_chan *le_chan; + int err; + + LOG_DBG("conn %p scid 0x%04x chan %p", conn, scid, chan); + + if (!L2CAP_LE_CID_IS_DYN(scid)) + { + return BT_L2CAP_LE_ERR_INVALID_SCID; + } + + *chan = bt_l2cap_le_lookup_tx_cid(conn, scid); + if (*chan) + { + return BT_L2CAP_LE_ERR_SCID_IN_USE; + } + + /* Request server to accept the new connection and allocate the + * channel. + */ + + err = server->accept(conn, server, chan); + if (err < 0) + { + return le_err_to_result(err); + } + +#if defined(CONFIG_BT_L2CAP_SEG_RECV) + if (!(*chan)->ops->recv == !(*chan)->ops->seg_recv) + { + LOG_ERR("Exactly one of 'recv' or 'seg_recv' must be set"); + return BT_L2CAP_LE_ERR_UNACCEPT_PARAMS; + } +#else + if (!(*chan)->ops->recv) + { + LOG_ERR("Mandatory callback 'recv' missing"); + return BT_L2CAP_LE_ERR_UNACCEPT_PARAMS; + } +#endif + + le_chan = BT_L2CAP_LE_CHAN(*chan); + + le_chan->required_sec_level = server->sec_level; + + /* Init TX parameters */ + + l2cap_chan_tx_init(le_chan); + le_chan->tx.cid = scid; + le_chan->tx.mps = mps; + le_chan->tx.mtu = mtu; + + /* Init RX parameters */ + + l2cap_chan_rx_init(le_chan); + + if (!l2cap_chan_add(conn, *chan, l2cap_chan_destroy)) + { + return BT_L2CAP_LE_ERR_NO_RESOURCES; + } + + l2cap_chan_tx_give_credits(le_chan, credits); + + /* Set channel PSM */ + + le_chan->psm = server->psm; + + /* Update state */ + + l2cap_chan_set_state(*chan, BT_L2CAP_CONNECTED); + + return BT_L2CAP_LE_SUCCESS; +} + +static uint16_t l2cap_check_security(struct bt_conn *conn, + struct bt_l2cap_server *server) +{ + if (IS_ENABLED(CONFIG_BT_CONN_DISABLE_SECURITY)) + { + return BT_L2CAP_LE_SUCCESS; + } + + if (conn->sec_level >= server->sec_level) + { + return BT_L2CAP_LE_SUCCESS; + } + + if (conn->sec_level > BT_SECURITY_L1) + { + return BT_L2CAP_LE_ERR_AUTHENTICATION; + } + + /* If an LTK or an STK is available and encryption is required + * (LE security mode 1) but encryption is not enabled, the service request + * shall be rejected with the error code "Insufficient Encryption". + */ + + if (bt_conn_ltk_present(conn)) + { + return BT_L2CAP_LE_ERR_ENCRYPTION; + } + + return BT_L2CAP_LE_ERR_AUTHENTICATION; +} + +static void le_conn_req(struct bt_l2cap *l2cap, + uint8_t ident, struct net_buf *buf) +{ + struct bt_conn *conn = l2cap->chan.chan.conn; + struct bt_l2cap_chan *chan; + struct bt_l2cap_le_chan *le_chan; + struct bt_l2cap_server *server; + struct bt_l2cap_le_conn_req *req = (void *)buf->data; + struct bt_l2cap_le_conn_rsp *rsp; + uint16_t psm; + uint16_t scid; + uint16_t mtu; + uint16_t mps; + uint16_t credits; + uint16_t result; + + if (buf->len != sizeof(*req)) + { + LOG_ERR("Invalid LE conn req packet size (%u != %zu)", buf->len, + sizeof(*req)); + return; + } + + psm = sys_le16_to_cpu(req->psm); + scid = sys_le16_to_cpu(req->scid); + mtu = sys_le16_to_cpu(req->mtu); + mps = sys_le16_to_cpu(req->mps); + credits = sys_le16_to_cpu(req->credits); + + LOG_DBG("psm 0x%02x scid 0x%04x mtu %u mps %u credits %u", psm, scid, mtu, + mps, credits); + + buf = l2cap_create_le_sig_pdu(BT_L2CAP_LE_CONN_RSP, ident, sizeof(*rsp)); + if (!buf) + { + return; + } + + rsp = net_buf_add(buf, sizeof(*rsp)); + (void)memset(rsp, 0, sizeof(*rsp)); + + /* Validate parameters. Requirements are from Core Spec v6.0, Vol 3.A.4.22. + * Valid credit range is from 0 to UINT16_MAX, thus no credit validation is + * needed. + */ + + if (!IN_RANGE(mtu, L2CAP_LE_MIN_MTU, BT_L2CAP_MAX_MTU) || + !IN_RANGE(mps, L2CAP_LE_MIN_MPS, BT_L2CAP_MAX_MPS)) + { + LOG_ERR("Invalid le conn req params: mtu %u mps %u", mtu, mps); + result = BT_L2CAP_LE_ERR_UNACCEPT_PARAMS; + goto rsp; + } + + /* Check if there is a server registered */ + + server = bt_l2cap_server_lookup_psm(psm); + if (!server) + { + result = BT_L2CAP_LE_ERR_PSM_NOT_SUPP; + goto rsp; + } + + /* Check if connection has minimum required security level */ + + result = l2cap_check_security(conn, server); + if (result != BT_L2CAP_LE_SUCCESS) + { + goto rsp; + } + + result = l2cap_chan_accept(conn, server, scid, mtu, mps, credits, &chan); + if (result != BT_L2CAP_LE_SUCCESS) + { + goto rsp; + } + + le_chan = BT_L2CAP_LE_CHAN(chan); + + /* Prepare response protocol data */ + + rsp->dcid = sys_cpu_to_le16(le_chan->rx.cid); + rsp->mps = sys_cpu_to_le16(le_chan->rx.mps); + rsp->mtu = sys_cpu_to_le16(le_chan->rx.mtu); + rsp->credits = sys_cpu_to_le16(le_chan->rx.credits); + + result = BT_L2CAP_LE_SUCCESS; + +rsp: + rsp->result = sys_cpu_to_le16(result); + + if (l2cap_send_sig(conn, buf)) + { + return; + } + + /* Raise connected callback on success */ + + if ((result == BT_L2CAP_LE_SUCCESS) && (chan->ops->connected != NULL)) + { + chan->ops->connected(chan); + } +} + +#if defined(CONFIG_BT_L2CAP_ECRED) +static void le_ecred_conn_req(struct bt_l2cap *l2cap, + uint8_t ident, struct net_buf *buf) +{ + struct bt_conn *conn = l2cap->chan.chan.conn; + struct bt_l2cap_chan *chan[BT_L2CAP_ECRED_CHAN_MAX_PER_REQ]; + struct bt_l2cap_le_chan *ch = NULL; + struct bt_l2cap_server *server; + struct bt_l2cap_ecred_conn_req *req; + struct bt_l2cap_ecred_conn_rsp *rsp; + uint16_t mtu; + uint16_t mps; + uint16_t credits; + uint16_t result = BT_L2CAP_LE_SUCCESS; + uint16_t psm = 0x0000; + uint16_t scid; + uint16_t dcid[BT_L2CAP_ECRED_CHAN_MAX_PER_REQ]; + int i = 0; + uint8_t req_cid_count; + bool rsp_queued = false; + + /* set dcid to zeros here, in case of all connections refused error */ + + memset(dcid, 0, sizeof(dcid)); + if (buf->len < sizeof(*req)) + { + LOG_ERR("Too small LE conn req packet size"); + result = BT_L2CAP_LE_ERR_INVALID_PARAMS; + req_cid_count = 0; + goto response; + } + + req = net_buf_pull_mem(buf, sizeof(*req)); + req_cid_count = buf->len / sizeof(scid); + + if (buf->len > sizeof(dcid)) + { + LOG_ERR("Too large LE conn req packet size"); + req_cid_count = BT_L2CAP_ECRED_CHAN_MAX_PER_REQ; + result = BT_L2CAP_LE_ERR_INVALID_PARAMS; + goto response; + } + + psm = sys_le16_to_cpu(req->psm); + mtu = sys_le16_to_cpu(req->mtu); + mps = sys_le16_to_cpu(req->mps); + credits = sys_le16_to_cpu(req->credits); + + LOG_DBG("psm 0x%02x mtu %u mps %u credits %u", psm, mtu, mps, credits); + + /* Validate parameters. Requirements are from Core Spec v6.0, Vol 3.A.4.25. + */ + + if (!IN_RANGE(mtu, BT_L2CAP_ECRED_MIN_MTU, BT_L2CAP_MAX_MTU) || + !IN_RANGE(mps, BT_L2CAP_ECRED_MIN_MPS, BT_L2CAP_MAX_MPS) || + !IN_RANGE(credits, BT_L2CAP_ECRED_CREDITS_MIN, + BT_L2CAP_ECRED_CREDITS_MAX)) + { + LOG_ERR("Invalid le ecred conn req params: mtu %u mps %u credits %u", + mtu, mps, credits); + result = BT_L2CAP_LE_ERR_INVALID_PARAMS; + goto response; + } + + /* Check if there is a server registered */ + + server = bt_l2cap_server_lookup_psm(psm); + if (!server) + { + result = BT_L2CAP_LE_ERR_PSM_NOT_SUPP; + goto response; + } + + /* Check if connection has minimum required security level */ + + result = l2cap_check_security(conn, server); + if (result != BT_L2CAP_LE_SUCCESS) + { + goto response; + } + + while (buf->len >= sizeof(scid)) + { + uint16_t rc; + + scid = net_buf_pull_le16(buf); + + rc = l2cap_chan_accept(conn, server, scid, mtu, mps, credits, &chan[ + i]); + if (rc != BT_L2CAP_LE_SUCCESS) + { + result = rc; + } + + switch (rc) + { + case BT_L2CAP_LE_SUCCESS: + ch = BT_L2CAP_LE_CHAN(chan[i]); + dcid[i++] = sys_cpu_to_le16(ch->rx.cid); + continue; + + /* Some connections refused – invalid Source CID */ + + /* Some connections refused – Source CID already allocated */ + + /* Some connections refused – not enough resources + * available. + */ + + default: + /* If a Destination CID is 0x0000, the channel was not + * established. + */ + + dcid[i++] = 0x0000; + continue; + } + } + +response: + buf = l2cap_create_le_sig_pdu(BT_L2CAP_ECRED_CONN_RSP, ident, + sizeof(*rsp) + + (sizeof(scid) * req_cid_count)); + if (!buf) + { + goto callback; + } + + rsp = net_buf_add(buf, sizeof(*rsp)); + (void)memset(rsp, 0, sizeof(*rsp)); + if (ch) + { + rsp->mps = sys_cpu_to_le16(ch->rx.mps); + rsp->mtu = sys_cpu_to_le16(ch->rx.mtu); + rsp->credits = sys_cpu_to_le16(ch->rx.credits); + } + + rsp->result = sys_cpu_to_le16(result); + + net_buf_add_mem(buf, dcid, sizeof(scid) * req_cid_count); + + if (l2cap_send_sig(conn, buf)) + { + goto callback; + } + + rsp_queued = true; + +callback: + if (ecred_cb && ecred_cb->ecred_conn_req) + { + ecred_cb->ecred_conn_req(conn, result, psm); + } + + if (rsp_queued) + { + for (i = 0; i < req_cid_count; i++) + { + /* Raise connected callback for established channels */ + + if ((dcid[i] != 0x00) && (chan[i]->ops->connected != NULL)) + { + chan[i]->ops->connected(chan[i]); + } + } + } +} + +static void le_ecred_reconf_req(struct bt_l2cap *l2cap, + uint8_t ident, struct net_buf *buf) +{ + struct bt_conn *conn = l2cap->chan.chan.conn; + struct bt_l2cap_chan *chans[BT_L2CAP_ECRED_CHAN_MAX_PER_REQ]; + struct bt_l2cap_ecred_reconf_req *req; + struct bt_l2cap_ecred_reconf_rsp *rsp; + uint16_t mtu; + uint16_t mps; + uint16_t scid; + uint16_t result = BT_L2CAP_RECONF_SUCCESS; + int chan_count = 0; + bool mps_reduced = false; + + if (buf->len < sizeof(*req)) + { + LOG_ERR("Too small ecred reconf req packet size"); + return; + } + + req = net_buf_pull_mem(buf, sizeof(*req)); + + mtu = sys_le16_to_cpu(req->mtu); + mps = sys_le16_to_cpu(req->mps); + + if (mps < BT_L2CAP_ECRED_MIN_MPS) + { + result = BT_L2CAP_RECONF_OTHER_UNACCEPT; + goto response; + } + + if (mtu < BT_L2CAP_ECRED_MIN_MTU) + { + result = BT_L2CAP_RECONF_INVALID_MTU; + goto response; + } + + /* The specification only allows up to 5 CIDs in this packet */ + + if (buf->len > (BT_L2CAP_ECRED_CHAN_MAX_PER_REQ * sizeof(scid))) + { + result = BT_L2CAP_RECONF_OTHER_UNACCEPT; + goto response; + } + + while (buf->len >= sizeof(scid)) + { + struct bt_l2cap_chan *chan; + + scid = net_buf_pull_le16(buf); + if (!L2CAP_LE_CID_IS_DYN(scid)) + { + result = BT_L2CAP_RECONF_INVALID_CID; + goto response; + } + + chan = bt_l2cap_le_lookup_tx_cid(conn, scid); + if (!chan) + { + result = BT_L2CAP_RECONF_INVALID_CID; + goto response; + } + + if (BT_L2CAP_LE_CHAN(chan)->tx.mtu > mtu) + { + LOG_ERR("chan %p decreased MTU %u -> %u", chan, + BT_L2CAP_LE_CHAN(chan)->tx.mtu, mtu); + result = BT_L2CAP_RECONF_INVALID_MTU; + goto response; + } + + if (BT_L2CAP_LE_CHAN(chan)->tx.mps > mps) + { + mps_reduced = true; + } + + chans[chan_count] = chan; + chan_count++; + } + + /* As per BT Core Spec V5.2 Vol. 3, Part A, section 7.11 + * The request (...) shall not decrease the MPS of a channel if more than + * one channel is specified. + */ + + if (mps_reduced && chan_count > 1) + { + result = BT_L2CAP_RECONF_INVALID_MPS; + goto response; + } + + for (int i = 0; i < chan_count; i++) + { + BT_L2CAP_LE_CHAN(chans[i])->tx.mtu = mtu; + BT_L2CAP_LE_CHAN(chans[i])->tx.mps = mps; + + if (chans[i]->ops->reconfigured) + { + chans[i]->ops->reconfigured(chans[i]); + } + } + + LOG_DBG("mtu %u mps %u", mtu, mps); + +response: + buf = l2cap_create_le_sig_pdu(BT_L2CAP_ECRED_RECONF_RSP, ident, + sizeof(*rsp)); + if (!buf) + { + return; + } + + rsp = net_buf_add(buf, sizeof(*rsp)); + rsp->result = sys_cpu_to_le16(result); + + l2cap_send_sig(conn, buf); +} + +static void le_ecred_reconf_rsp(struct bt_l2cap *l2cap, + uint8_t ident, struct net_buf *buf) +{ + struct bt_conn *conn = l2cap->chan.chan.conn; + struct bt_l2cap_ecred_reconf_rsp *rsp; + struct bt_l2cap_le_chan *ch; + uint16_t result; + + if (buf->len != sizeof(*rsp)) + { + LOG_ERR("Invalid ecred reconf rsp packet size (%u != %zu)", buf->len, + sizeof(*rsp)); + return; + } + + rsp = net_buf_pull_mem(buf, sizeof(*rsp)); + result = sys_le16_to_cpu(rsp->result); + + while ((ch = l2cap_lookup_ident(conn, ident, BT_L2CAP_ECRED_RECONF_REQ))) + { + /* Stop timer started on REQ send. The timer is only set on one + * of the channels, but we don't want to make assumptions on which one + * it is. + */ + + k_work_cancel_delayable(&ch->rtx_work); + + if (result == BT_L2CAP_LE_SUCCESS) + { + ch->rx.mtu = ch->pending_rx_mtu; + } + + ch->pending_rx_mtu = 0; + ch->ident = 0U; + ch->pending_req = 0U; + + if (ch->chan.ops->reconfigured) + { + ch->chan.ops->reconfigured(&ch->chan); + } + } +} +#endif /* defined(CONFIG_BT_L2CAP_ECRED) */ + +static struct bt_l2cap_le_chan *l2cap_remove_rx_cid(struct bt_conn *conn, + uint16_t cid) +{ + struct bt_l2cap_chan *chan; + struct list_node *prev = NULL; + + /* Protect fixed channels against accidental removal */ + + if (!L2CAP_LE_CID_IS_DYN(cid)) + { + return NULL; + } + + list_for_every_container(&conn->channels, chan, node) + { + if (BT_L2CAP_LE_CHAN(chan)->rx.cid == cid) + { + list_remove(&conn->channels, prev, &chan->node); + return BT_L2CAP_LE_CHAN(chan); + } + + prev = &chan->node; + } + + return NULL; +} + +static void le_disconn_req(struct bt_l2cap *l2cap, + uint8_t ident, struct net_buf *buf) +{ + struct bt_conn *conn = l2cap->chan.chan.conn; + struct bt_l2cap_le_chan *chan; + struct bt_l2cap_disconn_req *req = (void *)buf->data; + struct bt_l2cap_disconn_rsp *rsp; + uint16_t dcid; + + if (buf->len != sizeof(*req)) + { + LOG_ERR("Invalid LE conn req packet size (%u != %zu)", buf->len, + sizeof(*req)); + return; + } + + dcid = sys_le16_to_cpu(req->dcid); + + LOG_DBG("dcid 0x%04x scid 0x%04x", dcid, sys_le16_to_cpu(req->scid)); + + chan = l2cap_remove_rx_cid(conn, dcid); + if (!chan) + { + struct bt_l2cap_cmd_reject_cid_data data; + + data.scid = req->scid; + data.dcid = req->dcid; + + l2cap_send_reject(conn, ident, BT_L2CAP_REJ_INVALID_CID, &data, + sizeof(data)); + return; + } + + buf = l2cap_create_le_sig_pdu(BT_L2CAP_DISCONN_RSP, ident, sizeof(*rsp)); + if (!buf) + { + return; + } + + rsp = net_buf_add(buf, sizeof(*rsp)); + rsp->dcid = sys_cpu_to_le16(chan->rx.cid); + rsp->scid = sys_cpu_to_le16(chan->tx.cid); + + l2cap_chan_del(&chan->chan); + + l2cap_send_sig(conn, buf); +} + +static int l2cap_change_security(struct bt_l2cap_le_chan *chan, uint16_t err) +{ + struct bt_conn *conn = chan->chan.conn; + bt_security_t sec; + int ret; + + if (atomic_test_bit(chan->chan.status, BT_L2CAP_STATUS_ENCRYPT_PENDING)) + { + return -EINPROGRESS; + } + + switch (err) + { + case BT_L2CAP_LE_ERR_ENCRYPTION: + if (conn->sec_level >= BT_SECURITY_L2) + { + return -EALREADY; + } + + sec = BT_SECURITY_L2; + break; + case BT_L2CAP_LE_ERR_AUTHENTICATION: + if (conn->sec_level < BT_SECURITY_L2) + { + sec = BT_SECURITY_L2; + } + else if (conn->sec_level < BT_SECURITY_L3) + { + sec = BT_SECURITY_L3; + } + else if (conn->sec_level < BT_SECURITY_L4) + { + sec = BT_SECURITY_L4; + } + else + { + return -EALREADY; + } + break; + default: + return -EINVAL; + } + + ret = bt_conn_set_security(chan->chan.conn, sec); + if (ret < 0) + { + return ret; + } + + atomic_set_bit(chan->chan.status, BT_L2CAP_STATUS_ENCRYPT_PENDING); + + return 0; +} + +#if defined(CONFIG_BT_L2CAP_ECRED) +static void le_ecred_conn_rsp(struct bt_l2cap *l2cap, + uint8_t ident, struct net_buf *buf) +{ + struct bt_conn *conn = l2cap->chan.chan.conn; + struct bt_l2cap_le_chan *chan; + struct bt_l2cap_ecred_conn_rsp *rsp; + uint16_t dcid; + uint16_t mtu; + uint16_t mps; + uint16_t credits; + uint16_t result; + uint16_t psm; + uint8_t attempted = 0; + uint8_t succeeded = 0; + + if (buf->len < sizeof(*rsp)) + { + LOG_ERR("Too small ecred conn rsp packet size"); + return; + } + + rsp = net_buf_pull_mem(buf, sizeof(*rsp)); + mtu = sys_le16_to_cpu(rsp->mtu); + mps = sys_le16_to_cpu(rsp->mps); + credits = sys_le16_to_cpu(rsp->credits); + result = sys_le16_to_cpu(rsp->result); + + LOG_DBG("mtu 0x%04x mps 0x%04x credits 0x%04x result " + "%u", mtu, mps, credits, + result); + + chan = l2cap_lookup_ident(conn, ident, BT_L2CAP_ECRED_CONN_REQ); + if (chan) + { + psm = chan->psm; + } + else + { + psm = 0x0000; + } + + switch (result) + { + case BT_L2CAP_LE_ERR_AUTHENTICATION: + case BT_L2CAP_LE_ERR_ENCRYPTION: + while ((chan = l2cap_lookup_ident(conn, ident, + BT_L2CAP_ECRED_CONN_REQ))) + { + /* Cancel RTX work */ + + k_work_cancel_delayable(&chan->rtx_work); + + /* If security needs changing wait it to be completed */ + + if (!l2cap_change_security(chan, result)) + { + return; + } + + bt_l2cap_chan_remove(conn, &chan->chan); + l2cap_chan_del(&chan->chan); + } + break; + case BT_L2CAP_LE_SUCCESS: + + /* Some connections refused – invalid Source CID */ + + case BT_L2CAP_LE_ERR_INVALID_SCID: + + /* Some connections refused – Source CID already allocated */ + + case BT_L2CAP_LE_ERR_SCID_IN_USE: + + /* Some connections refused – not enough resources available */ + + case BT_L2CAP_LE_ERR_NO_RESOURCES: + while ((chan = l2cap_lookup_ident(conn, ident, + BT_L2CAP_ECRED_CONN_REQ))) + { + struct bt_l2cap_chan *c; + + /* Cancel RTX work */ + + k_work_cancel_delayable(&chan->rtx_work); + + if (buf->len < sizeof(dcid)) + { + LOG_ERR("Fewer dcid values than expected"); + bt_l2cap_chan_remove(conn, &chan->chan); + l2cap_chan_del(&chan->chan); + continue; + } + + dcid = net_buf_pull_le16(buf); + attempted++; + + LOG_DBG("dcid 0x%04x", dcid); + + /* Validate parameters before assignment. Requirements are from + * Core Spec v6.0, Vol 3.A.4.26. If a Destination CID is 0x0000, + * the channel was not established. + */ + + if (dcid == 0U) + { + bt_l2cap_chan_remove(conn, &chan->chan); + l2cap_chan_del(&chan->chan); + continue; + } + else if (!L2CAP_LE_CID_IS_DYN(dcid) || + !IN_RANGE(mtu, BT_L2CAP_ECRED_MIN_MTU, + BT_L2CAP_MAX_MTU) || + !IN_RANGE(mps, BT_L2CAP_ECRED_MIN_MPS, + BT_L2CAP_MAX_MPS) || + !IN_RANGE(credits, BT_L2CAP_ECRED_CREDITS_MIN, + BT_L2CAP_ECRED_CREDITS_MAX)) + { + LOG_WRN("Invalid ecred conn rsp params: dcid 0x%04x mtu %u " + "mps " + "%u " + "credits %u. Disconnecting.", + dcid, mtu, mps, credits); + bt_conn_disconnect(conn, BT_HCI_ERR_UNACCEPT_CONN_PARAM); + return; + } + + c = bt_l2cap_le_lookup_tx_cid(conn, dcid); + if (c) + { + /* If a device receives a + * L2CAP_CREDIT_BASED_CONNECTION_RSP packet with an already + * assigned Destination CID, then both the original channel + * and + * the new channel shall be immediately discarded and not + * used. + */ + + bt_l2cap_chan_remove(conn, &chan->chan); + l2cap_chan_del(&chan->chan); + bt_l2cap_chan_disconnect(c); + continue; + } + + chan->tx.cid = dcid; + + chan->ident = 0U; + chan->pending_req = 0U; + + chan->tx.mtu = mtu; + chan->tx.mps = mps; + + /* Update state */ + + l2cap_chan_set_state(&chan->chan, BT_L2CAP_CONNECTED); + + if (chan->chan.ops->connected) + { + chan->chan.ops->connected(&chan->chan); + } + + /* Give credits */ + + l2cap_chan_tx_give_credits(chan, credits); + + succeeded++; + } + break; + case BT_L2CAP_LE_ERR_PSM_NOT_SUPP: + default: + while ((chan = l2cap_remove_ident(conn, ident, + BT_L2CAP_ECRED_CONN_REQ))) + { + l2cap_chan_del(&chan->chan); + } + break; + } + + if (ecred_cb && ecred_cb->ecred_conn_rsp) + { + ecred_cb->ecred_conn_rsp(conn, result, attempted, succeeded, psm); + } +} +#endif /* CONFIG_BT_L2CAP_ECRED */ + +static void le_conn_rsp(struct bt_l2cap *l2cap, + uint8_t ident, struct net_buf *buf) +{ + struct bt_conn *conn = l2cap->chan.chan.conn; + struct bt_l2cap_le_chan *chan; + struct bt_l2cap_le_conn_rsp *rsp = (void *)buf->data; + uint16_t dcid; + uint16_t mtu; + uint16_t mps; + uint16_t credits; + uint16_t result; + + if (buf->len != sizeof(*rsp)) + { + LOG_ERR("Invalid LE conn rsp packet size (%u != %zu)", buf->len, + sizeof(*rsp)); + return; + } + + dcid = sys_le16_to_cpu(rsp->dcid); + mtu = sys_le16_to_cpu(rsp->mtu); + mps = sys_le16_to_cpu(rsp->mps); + credits = sys_le16_to_cpu(rsp->credits); + result = sys_le16_to_cpu(rsp->result); + + LOG_DBG("dcid 0x%04x mtu %u mps %u credits %u result 0x%04x", dcid, mtu, + mps, + credits, result); + + /* Keep the channel in case of security errors */ + + if (result == BT_L2CAP_LE_SUCCESS || + result == BT_L2CAP_LE_ERR_AUTHENTICATION || + result == BT_L2CAP_LE_ERR_ENCRYPTION) + { + chan = l2cap_lookup_ident(conn, ident, BT_L2CAP_LE_CONN_REQ); + } + else + { + chan = l2cap_remove_ident(conn, ident, BT_L2CAP_LE_CONN_REQ); + } + + if (!chan) + { + LOG_ERR("Cannot find channel for ident %u", ident); + return; + } + + /* Cancel RTX work */ + + k_work_cancel_delayable(&chan->rtx_work); + + /* Reset ident since it got a response */ + + chan->ident = 0U; + chan->pending_req = 0U; + + switch (result) + { + case BT_L2CAP_LE_SUCCESS: + /* Validate parameters on successful connection. Requirements are + * from + * Core Spec v6.0, Vol 3.A.4.23. Valid credit range is from 0 to + * UINT16_MAX, thus no credit validation is needed. + */ + + if ((!L2CAP_LE_CID_IS_DYN(dcid) || + !IN_RANGE(mtu, L2CAP_LE_MIN_MTU, BT_L2CAP_MAX_MTU) || + !IN_RANGE(mps, L2CAP_LE_MIN_MPS, BT_L2CAP_MAX_MPS))) + { + LOG_WRN("Invalid conn rsp params: dcid 0x%04x mtu %u mps %u. " + "Disconnecting.", + dcid, mtu, mps); + bt_conn_disconnect(conn, BT_HCI_ERR_UNACCEPT_CONN_PARAM); + return; + } + + chan->tx.cid = dcid; + chan->tx.mtu = mtu; + chan->tx.mps = mps; + + /* Update state */ + + l2cap_chan_set_state(&chan->chan, BT_L2CAP_CONNECTED); + + if (chan->chan.ops->connected) + { + chan->chan.ops->connected(&chan->chan); + } + + /* Give credits */ + + l2cap_chan_tx_give_credits(chan, credits); + + break; + case BT_L2CAP_LE_ERR_AUTHENTICATION: + case BT_L2CAP_LE_ERR_ENCRYPTION: + + /* If security needs changing wait it to be completed */ + + if (l2cap_change_security(chan, result) == 0) + { + return; + } + + bt_l2cap_chan_remove(conn, &chan->chan); + __fallthrough; + default: + l2cap_chan_del(&chan->chan); + } +} + +static void le_disconn_rsp(struct bt_l2cap *l2cap, + uint8_t ident, struct net_buf *buf) +{ + struct bt_conn *conn = l2cap->chan.chan.conn; + struct bt_l2cap_le_chan *chan; + struct bt_l2cap_disconn_rsp *rsp = (void *)buf->data; + uint16_t scid; + + if (buf->len != sizeof(*rsp)) + { + LOG_ERR("Invalid LE disconn rsp packet size (%u != %zu)", buf->len, + sizeof(*rsp)); + return; + } + + chan = l2cap_lookup_ident(conn, ident, BT_L2CAP_DISCONN_REQ); + if (!chan) + { + LOG_ERR("Cannot find channel for ident %u", ident); + return; + } + + scid = sys_le16_to_cpu(rsp->scid); + if (scid != chan->rx.cid) + { + LOG_ERR("Invalid scid 0x%04x", scid); + return; + } + + LOG_DBG("dcid 0x%04x scid 0x%04x", sys_le16_to_cpu(rsp->dcid), scid); + + chan = l2cap_remove_rx_cid(conn, scid); + if (!chan) + { + return; + } + + l2cap_chan_del(&chan->chan); +} + +static void le_credits(struct bt_l2cap *l2cap, + uint8_t ident, struct net_buf *buf) +{ + struct bt_conn *conn = l2cap->chan.chan.conn; + struct bt_l2cap_chan *chan; + struct bt_l2cap_le_credits *ev = (void *)buf->data; + struct bt_l2cap_le_chan *le_chan; + uint16_t credits; + uint16_t cid; + + if (buf->len != sizeof(*ev)) + { + LOG_ERR("Invalid LE Credits packet size (%u != %zu)", buf->len, + sizeof(*ev)); + return; + } + + cid = sys_le16_to_cpu(ev->cid); + credits = sys_le16_to_cpu(ev->credits); + + if (!L2CAP_LE_CID_IS_DYN(cid)) + { + LOG_WRN("Can't add credits to non-dynamic channel %p (cid 0x%04x)", + &l2cap->chan, cid); + return; + } + + if (credits == 0U) + { + LOG_WRN("Ignoring zero credit packet"); + return; + } + + LOG_DBG("cid 0x%04x credits %u", cid, credits); + + chan = bt_l2cap_le_lookup_tx_cid(conn, cid); + if (!chan) + { + LOG_ERR("Unable to find channel of LE Credits packet"); + return; + } + + le_chan = BT_L2CAP_LE_CHAN(chan); + + if (atomic_read(&le_chan->tx.credits) + credits > UINT16_MAX) + { + LOG_ERR("Credits overflow"); + bt_l2cap_chan_disconnect(chan); + return; + } + + l2cap_chan_tx_give_credits(le_chan, credits); + + LOG_DBG("chan %p total credits %lu", le_chan, + atomic_read(&le_chan->tx.credits)); +} + +static void reject_cmd(struct bt_l2cap *l2cap, + uint8_t ident, struct net_buf *buf) +{ + struct bt_conn *conn = l2cap->chan.chan.conn; + struct bt_l2cap_le_chan *chan; + + while ((chan = l2cap_remove_ident(conn, ident, ANY_OPCODE))) + { + l2cap_chan_del(&chan->chan); + } +} +#endif /* CONFIG_BT_L2CAP_DYNAMIC_CHANNEL */ + +static int l2cap_recv(struct bt_l2cap_chan *chan, struct net_buf *buf) +{ + struct bt_l2cap_le_chan *l2chan = CONTAINER_OF(chan, + struct bt_l2cap_le_chan, + chan); + struct bt_l2cap *l2cap = CONTAINER_OF(l2chan, struct bt_l2cap, chan); + struct bt_l2cap_sig_hdr *hdr; + uint16_t len; + + if (buf->len < sizeof(*hdr)) + { + LOG_ERR("Too small L2CAP signaling PDU"); + return 0; + } + + hdr = net_buf_pull_mem(buf, sizeof(*hdr)); + len = sys_le16_to_cpu(hdr->len); + + LOG_DBG("Signaling code 0x%02x ident %u len %u", hdr->code, hdr->ident, + len); + + if (buf->len != len) + { + LOG_ERR("L2CAP length mismatch (%u != %u)", buf->len, len); + return 0; + } + + if (!hdr->ident) + { + LOG_ERR("Invalid ident value in L2CAP PDU"); + return 0; + } + + switch (hdr->code) + { + case BT_L2CAP_CONN_PARAM_RSP: + le_conn_param_rsp(l2cap, buf); + break; +#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) + case BT_L2CAP_LE_CONN_REQ: + le_conn_req(l2cap, hdr->ident, buf); + break; + case BT_L2CAP_LE_CONN_RSP: + le_conn_rsp(l2cap, hdr->ident, buf); + break; + case BT_L2CAP_DISCONN_REQ: + le_disconn_req(l2cap, hdr->ident, buf); + break; + case BT_L2CAP_DISCONN_RSP: + le_disconn_rsp(l2cap, hdr->ident, buf); + break; + case BT_L2CAP_LE_CREDITS: + le_credits(l2cap, hdr->ident, buf); + break; + case BT_L2CAP_CMD_REJECT: + reject_cmd(l2cap, hdr->ident, buf); + break; +#if defined(CONFIG_BT_L2CAP_ECRED) + case BT_L2CAP_ECRED_CONN_REQ: + le_ecred_conn_req(l2cap, hdr->ident, buf); + break; + case BT_L2CAP_ECRED_CONN_RSP: + le_ecred_conn_rsp(l2cap, hdr->ident, buf); + break; + case BT_L2CAP_ECRED_RECONF_REQ: + le_ecred_reconf_req(l2cap, hdr->ident, buf); + break; + case BT_L2CAP_ECRED_RECONF_RSP: + le_ecred_reconf_rsp(l2cap, hdr->ident, buf); + break; +#endif /* defined(CONFIG_BT_L2CAP_ECRED) */ +#else + case BT_L2CAP_CMD_REJECT: + + /* Ignored */ + + break; +#endif /* CONFIG_BT_L2CAP_DYNAMIC_CHANNEL */ + case BT_L2CAP_CONN_PARAM_REQ: + if (IS_ENABLED(CONFIG_BT_CENTRAL)) + { + le_conn_param_update_req(l2cap, hdr->ident, buf); + break; + } + + __fallthrough; + default: + LOG_WRN("Rejecting unknown L2CAP PDU code 0x%02x", hdr->code); + l2cap_send_reject(chan->conn, hdr->ident, + BT_L2CAP_REJ_NOT_UNDERSTOOD, + NULL, 0); + break; + } + + return 0; +} + +#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) +static void l2cap_chan_shutdown(struct bt_l2cap_chan *chan) +{ + struct bt_l2cap_le_chan *le_chan = BT_L2CAP_LE_CHAN(chan); + struct net_buf *buf; + + LOG_DBG("chan %p", chan); + + atomic_set_bit(chan->status, BT_L2CAP_STATUS_SHUTDOWN); + + /* Destroy segmented SDU if it exists */ + + if (le_chan->_sdu) + { + net_buf_drop(&le_chan->_sdu); + le_chan->_sdu_len = 0U; + } + + /* Remove buffers on the TX queue */ + + while ((buf = k_fifo_get(&le_chan->tx_queue, 0))) + { + net_buf_unref(buf); + } + + /* Remove buffers on the RX queue */ + + while ((buf = k_fifo_get(&le_chan->rx_queue, 0))) + { + net_buf_unref(buf); + } + + /* Update status */ + + if (chan->ops->status) + { + chan->ops->status(chan, chan->status); + } +} + +static void l2cap_chan_send_credits(struct bt_l2cap_le_chan *chan, + uint16_t credits) +{ + struct bt_l2cap_le_credits *ev; + struct net_buf *buf; + + DEBUGASSERT(bt_l2cap_chan_get_state(&chan->chan) == BT_L2CAP_CONNECTED); + + buf = l2cap_create_le_sig_pdu(BT_L2CAP_LE_CREDITS, get_ident(), + sizeof(*ev)); + if (!buf) + { + LOG_ERR("Unable to send credits update"); + /* Disconnect would probably not work either so the only + * option left is to shutdown the channel. + */ + + l2cap_chan_shutdown(&chan->chan); + return; + } + + DEBUGASSERT(atomic_read(&chan->rx.credits) == 0); + atomic_set(&chan->rx.credits, credits); + + ev = net_buf_add(buf, sizeof(*ev)); + ev->cid = sys_cpu_to_le16(chan->rx.cid); + ev->credits = sys_cpu_to_le16(credits); + + l2cap_send_sig(chan->chan.conn, buf); + + LOG_DBG("chan %p credits %lu", chan, atomic_read(&chan->rx.credits)); +} + +#if defined(CONFIG_BT_L2CAP_SEG_RECV) +static int l2cap_chan_send_credits_pdu(struct bt_conn *conn, + uint16_t cid, uint16_t credits) +{ + struct net_buf *buf; + struct bt_l2cap_le_credits *ev; + + buf = l2cap_create_le_sig_pdu(BT_L2CAP_LE_CREDITS, get_ident(), + sizeof(*ev)); + if (!buf) + { + return -ENOBUFS; + } + + ev = net_buf_add(buf, sizeof(*ev)); + *ev = (struct bt_l2cap_le_credits) + { + .cid = sys_cpu_to_le16(cid), + .credits = sys_cpu_to_le16(credits), + }; + + return l2cap_send_sig(conn, buf); +} + +/* Combination of @ref atomic_add and uint16_t overflow checking. Leaves @p + * target unchanged if an overflow would occur. Assumes the current value of + * @p target is representable by uint16_t. + */ + +static bool atomic_add_safe_u16(atomic_t *target, uint16_t addition) +{ + uint16_t target_old; + uint16_t target_new; + + do + { + target_old = atomic_read(target); + if (target_old > UINT16_MAX - addition) + { + return true; + } + + target_new = target_old + addition; + } + while (!atomic_cmpxchg_value(target, target_old, target_new)); + + return false; +} + +int bt_l2cap_chan_give_credits(struct bt_l2cap_chan *chan, + uint16_t additional_credits) +{ + struct bt_l2cap_le_chan *le_chan = BT_L2CAP_LE_CHAN(chan); + + if (!chan || !chan->ops) + { + LOG_ERR("%s: Invalid chan object.", __func__); + return -EINVAL; + } + + if (!chan->ops->seg_recv) + { + LOG_ERR("%s: Available only with seg_recv.", __func__); + return -EINVAL; + } + + if (additional_credits == 0) + { + LOG_ERR("%s: Refusing to give 0.", __func__); + return -EINVAL; + } + + if (bt_l2cap_chan_get_state(chan) == BT_L2CAP_CONNECTING) + { + LOG_ERR("%s: Cannot give credits while connecting.", __func__); + return -EBUSY; + } + + if (atomic_add_safe_u16(&le_chan->rx.credits, additional_credits)) + { + LOG_ERR("%s: Overflow.", __func__); + return -EOVERFLOW; + } + + if (bt_l2cap_chan_get_state(chan) == BT_L2CAP_CONNECTED) + { + int err; + + err = l2cap_chan_send_credits_pdu(chan->conn, le_chan->rx.cid, + additional_credits); + if (err) + { + LOG_ERR("%s: PDU failed %d.", __func__, err); + return err; + } + } + + return 0; +} +#endif /* CONFIG_BT_L2CAP_SEG_RECV */ + +int bt_l2cap_chan_recv_complete(struct bt_l2cap_chan *chan, + struct net_buf *buf) +{ + struct bt_l2cap_le_chan *le_chan = BT_L2CAP_LE_CHAN(chan); + struct bt_conn *conn = chan->conn; + + DEBUGASSERT(chan); + DEBUGASSERT(buf); + + net_buf_unref(buf); + + if (!conn) + { + return -ENOTCONN; + } + + if (bt_conn_is_br(conn)) + { + return bt_l2cap_br_chan_recv_complete(chan); + } + + if (!bt_conn_is_le(conn)) + { + return -ENOTSUP; + } + + LOG_DBG("chan %p buf %p", chan, buf); + + if (bt_l2cap_chan_get_state(&le_chan->chan) == BT_L2CAP_CONNECTED) + { + l2cap_chan_send_credits(le_chan, 1); + } + + return 0; +} + +static struct net_buf *l2cap_alloc_frag(int32_t timeout, void *user_data) +{ + struct bt_l2cap_le_chan *chan = user_data; + struct net_buf *frag = NULL; + + frag = chan->chan.ops->alloc_buf(&chan->chan); + if (!frag) + { + return NULL; + } + + LOG_DBG("frag %p tailroom %zu", frag, net_buf_tailroom(frag)); + + return frag; +} + +static void l2cap_chan_le_recv_sdu(struct bt_l2cap_le_chan *chan, + struct net_buf *buf, uint16_t seg) +{ + int err; + + LOG_DBG("chan %p len %u", chan, buf->len); + + DEBUGASSERT(bt_l2cap_chan_get_state(&chan->chan) == BT_L2CAP_CONNECTED); + DEBUGASSERT(atomic_read(&chan->rx.credits) == 0); + + /* Receiving complete SDU, notify channel and reset SDU buf */ + + err = chan->chan.ops->recv(&chan->chan, buf); + if (err < 0) + { + if (err != -EINPROGRESS) + { + LOG_ERR("err %d", err); + bt_l2cap_chan_disconnect(&chan->chan); + net_buf_unref(buf); + } + + return; + } + else if (bt_l2cap_chan_get_state(&chan->chan) == BT_L2CAP_CONNECTED) + { + l2cap_chan_send_credits(chan, 1); + } + + net_buf_unref(buf); +} + +static void l2cap_chan_le_recv_seg(struct bt_l2cap_le_chan *chan, + struct net_buf *buf) +{ + uint16_t len; + uint16_t seg = 0U; + + len = chan->_sdu->len; + if (len) + { + memcpy(&seg, net_buf_user_data(chan->_sdu), sizeof(seg)); + } + + if (len + buf->len > chan->_sdu_len) + { + LOG_ERR("SDU length mismatch"); + bt_l2cap_chan_disconnect(&chan->chan); + return; + } + + seg++; + + /* Store received segments in user_data */ + + memcpy(net_buf_user_data(chan->_sdu), &seg, sizeof(seg)); + + LOG_DBG("chan %p seg %d len %u", chan, seg, buf->len); + + /* Append received segment to SDU */ + + len = net_buf_append_bytes(chan->_sdu, buf->len, buf->data, 0, + l2cap_alloc_frag, chan); + if (len != buf->len) + { + LOG_ERR("Unable to store SDU"); + bt_l2cap_chan_disconnect(&chan->chan); + return; + } + + if (chan->_sdu->len < chan->_sdu_len) + { + /* Give more credits if remote has run out of them, this + * should only happen if the remote cannot fully utilize the MPS for + * some reason. + * + * We can't send more than one credit, because if the remote decides to + * start fully utilizing the MPS for the remainder of the SDU, then the + * remote will end up with more credits than the app has buffers. + */ + + if (atomic_read(&chan->rx.credits) == 0) + { + LOG_DBG("remote is not fully utilizing MPS"); + l2cap_chan_send_credits(chan, 1); + } + + return; + } + + buf = net_buf_take(&chan->_sdu); + chan->_sdu_len = 0U; + + l2cap_chan_le_recv_sdu(chan, buf, seg); +} + +#if defined(CONFIG_BT_L2CAP_SEG_RECV) +static void l2cap_chan_le_recv_seg_direct(struct bt_l2cap_le_chan *chan, + struct net_buf *seg) +{ + uint16_t seg_offset; + uint16_t sdu_remaining; + + if (chan->_sdu_len_done == chan->_sdu_len) + { + /* This is the first PDU in a SDU. */ + + if (seg->len < 2) + { + LOG_WRN("Missing SDU header"); + bt_l2cap_chan_disconnect(&chan->chan); + return; + } + + /* Pop off the "SDU header". */ + + chan->_sdu_len = net_buf_pull_le16(seg); + chan->_sdu_len_done = 0; + + if (chan->_sdu_len > chan->rx.mtu) + { + LOG_WRN("SDU exceeds MTU"); + bt_l2cap_chan_disconnect(&chan->chan); + return; + } + } + + seg_offset = chan->_sdu_len_done; + sdu_remaining = chan->_sdu_len - chan->_sdu_len_done; + + if (seg->len > sdu_remaining) + { + LOG_WRN("L2CAP RX PDU total exceeds SDU"); + bt_l2cap_chan_disconnect(&chan->chan); + return; + } + + /* Commit receive. */ + + chan->_sdu_len_done += seg->len; + + /* Tail call. */ + + chan->chan.ops->seg_recv(&chan->chan, chan->_sdu_len, seg_offset, &seg->b); +} +#endif /* CONFIG_BT_L2CAP_SEG_RECV */ + +static void l2cap_chan_le_recv(struct bt_l2cap_le_chan *chan, + struct net_buf *buf) +{ + struct net_buf *owned_ref; + uint16_t sdu_len; + int err; + + if (!test_and_dec(&chan->rx.credits)) + { + LOG_ERR("No credits to receive packet"); + bt_l2cap_chan_disconnect(&chan->chan); + return; + } + + if (buf->len > chan->rx.mps) + { + LOG_WRN("PDU size > MPS (%u > %u)", buf->len, chan->rx.mps); + bt_l2cap_chan_disconnect(&chan->chan); + return; + } + + /* Redirect to experimental API. */ + + IF_ENABLED(CONFIG_BT_L2CAP_SEG_RECV, (if (chan->chan.ops->seg_recv) + { + l2cap_chan_le_recv_seg_direct(chan, buf); + return; + } + + )) + + /* Check if segments already exist */ + + if (chan->_sdu) + { + l2cap_chan_le_recv_seg(chan, buf); + return; + } + + if (buf->len < 2) + { + LOG_WRN("Too short data packet"); + bt_l2cap_chan_disconnect(&chan->chan); + return; + } + + sdu_len = net_buf_pull_le16(buf); + + LOG_DBG("chan %p len %u sdu_len %u", chan, buf->len, sdu_len); + + if (sdu_len > chan->rx.mtu) + { + LOG_ERR("Invalid SDU length"); + bt_l2cap_chan_disconnect(&chan->chan); + return; + } + + /* Always allocate buffer from the channel if supported. */ + + if (chan->chan.ops->alloc_buf) + { + chan->_sdu = chan->chan.ops->alloc_buf(&chan->chan); + if (!chan->_sdu) + { + LOG_ERR("Unable to allocate buffer for SDU"); + bt_l2cap_chan_disconnect(&chan->chan); + return; + } + + if (chan->_sdu->user_data_size < sizeof(uint16_t)) + { + LOG_ERR("SDU buffer user_data_size %u is too small", + chan->_sdu->user_data_size); + net_buf_drop(&chan->_sdu); + bt_l2cap_chan_disconnect(&chan->chan); + return; + } + + chan->_sdu_len = sdu_len; + + /* Send sdu_len/mps worth of credits */ + + uint16_t credits = + DIV_ROUND_UP(MIN(sdu_len - buf->len, + net_buf_tailroom(chan->_sdu)), chan->rx.mps); + + if (credits) + { + LOG_DBG("sending %d extra credits (sdu_len %d buf_len %d mps %d)", + credits, sdu_len, + buf->len, chan->rx.mps); + l2cap_chan_send_credits(chan, credits); + } + + l2cap_chan_le_recv_seg(chan, buf); + return; + } + + owned_ref = net_buf_ref(buf); + err = chan->chan.ops->recv(&chan->chan, owned_ref); + if (err != -EINPROGRESS) + { + net_buf_drop(&owned_ref); + } + + if (err < 0) + { + if (err != -EINPROGRESS) + { + LOG_ERR("err %d", err); + bt_l2cap_chan_disconnect(&chan->chan); + } + + return; + } + + /* Only attempt to send credits if the channel wasn't disconnected + * in the recv() callback above + */ + + if (bt_l2cap_chan_get_state(&chan->chan) == BT_L2CAP_CONNECTED) + { + l2cap_chan_send_credits(chan, 1); + } +} + +static void l2cap_chan_recv_queue(struct bt_l2cap_le_chan *chan, + struct net_buf *buf) +{ + if (chan->state == BT_L2CAP_DISCONNECTING) + { + LOG_WRN("Ignoring data received while disconnecting"); + net_buf_unref(buf); + return; + } + + if (atomic_test_bit(chan->chan.status, BT_L2CAP_STATUS_SHUTDOWN)) + { + LOG_WRN("Ignoring data received while channel has shutdown"); + net_buf_unref(buf); + return; + } + + if (!L2CAP_LE_PSM_IS_DYN(chan->psm)) + { + l2cap_chan_le_recv(chan, buf); + net_buf_unref(buf); + return; + } + + k_fifo_put(&chan->rx_queue, buf); + bt_work_submit(&chan->rx_work); +} +#endif /* CONFIG_BT_L2CAP_DYNAMIC_CHANNEL */ + +static void l2cap_chan_recv(struct bt_l2cap_chan *chan, + struct net_buf *buf, bool complete) +{ +#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) + struct bt_l2cap_le_chan *le_chan = BT_L2CAP_LE_CHAN(chan); + + if (L2CAP_LE_CID_IS_DYN(le_chan->rx.cid)) + { + if (complete) + { + l2cap_chan_recv_queue(le_chan, buf); + } + else + { + /* if packet was not complete this means peer device + * overflowed our RX and channel shall be disconnected + */ + + bt_l2cap_chan_disconnect(chan); + net_buf_unref(buf); + } + + return; + } +#endif /* CONFIG_BT_L2CAP_DYNAMIC_CHANNEL */ + + LOG_DBG("chan %p len %u", chan, buf->len); + + chan->ops->recv(chan, buf); + net_buf_unref(buf); +} + +void bt_l2cap_recv(struct bt_conn *conn, struct net_buf *buf, bool complete) +{ + struct bt_l2cap_hdr *hdr; + struct bt_l2cap_chan *chan; + uint16_t cid; + + if (bt_conn_is_br(conn)) + { + bt_l2cap_br_recv(conn, buf); + return; + } + + if (buf->len < sizeof(*hdr)) + { + LOG_ERR("Too small L2CAP PDU received"); + net_buf_unref(buf); + return; + } + + hdr = net_buf_pull_mem(buf, sizeof(*hdr)); + cid = sys_le16_to_cpu(hdr->cid); + + LOG_DBG("Packet for CID %u len %u", cid, buf->len); + + chan = bt_l2cap_le_lookup_rx_cid(conn, cid); + if (!chan) + { + LOG_WRN("Ignoring data for unknown channel ID 0x%04x", cid); + net_buf_unref(buf); + return; + } + + l2cap_chan_recv(chan, buf, complete); +} + +int bt_l2cap_update_conn_param(struct bt_conn *conn, + const struct bt_le_conn_param *param) +{ + struct bt_l2cap_conn_param_req *req; + struct net_buf *buf; + + buf = l2cap_create_le_sig_pdu(BT_L2CAP_CONN_PARAM_REQ, get_ident(), + sizeof(*req)); + if (!buf) + { + return -ENOMEM; + } + + req = net_buf_add(buf, sizeof(*req)); + req->min_interval = sys_cpu_to_le16(param->interval_min); + req->max_interval = sys_cpu_to_le16(param->interval_max); + req->latency = sys_cpu_to_le16(param->latency); + req->timeout = sys_cpu_to_le16(param->timeout); + + return l2cap_send_sig(conn, buf); +} + +static void l2cap_connected(struct bt_l2cap_chan *chan) +{ + LOG_DBG("ch %p cid 0x%04x", BT_L2CAP_LE_CHAN(chan), + BT_L2CAP_LE_CHAN(chan)->rx.cid); +} + +static void l2cap_disconnected(struct bt_l2cap_chan *chan) +{ + struct bt_l2cap_le_chan *le_chan = BT_L2CAP_LE_CHAN(chan); + + LOG_DBG("ch %p cid 0x%04x", le_chan, le_chan->rx.cid); + +#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) + /* Cancel RTX work on signal channel. + * Disconnected callback is always called from the Bluetooth workqueue, + * which the RTX work also runs on, so this should always succeed. + */ + + (void)k_work_cancel_delayable(&le_chan->rtx_work); +#endif /* CONFIG_BT_L2CAP_DYNAMIC_CHANNEL */ +} + +static int l2cap_accept(struct bt_conn *conn, struct bt_l2cap_chan **chan) +{ + int i; + static const struct bt_l2cap_chan_ops ops = + { + .connected = l2cap_connected, + .disconnected = l2cap_disconnected, + .recv = l2cap_recv, + }; + + LOG_DBG("conn %p handle %u", conn, conn->handle); + + for (i = 0; i < ARRAY_SIZE(bt_l2cap_pool); i++) + { + struct bt_l2cap *l2cap = &bt_l2cap_pool[i]; + + if (l2cap->chan.chan.conn) + { + continue; + } + + l2cap->chan.chan.ops = &ops; + *chan = &l2cap->chan.chan; + + return 0; + } + + LOG_ERR("No available L2CAP context for conn %p", conn); + + return -ENOMEM; +} + +BT_L2CAP_FIXED_CHANNEL_DEFINE(le_fixed_chan) = +{ + .cid = BT_L2CAP_CID_LE_SIG, + .accept = l2cap_accept, +}; + +void bt_l2cap_init(void) +{ + if (IS_ENABLED(CONFIG_BT_CLASSIC)) + { + bt_l2cap_br_init(); + } +} + +#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) +static int l2cap_le_connect(struct bt_conn *conn, + struct bt_l2cap_le_chan *ch, uint16_t psm) +{ + int err; + + if (psm < L2CAP_LE_PSM_FIXED_START || psm > L2CAP_LE_PSM_DYN_END) + { + return -EINVAL; + } + + l2cap_chan_tx_init(ch); + l2cap_chan_rx_init(ch); + + if (!l2cap_chan_add(conn, &ch->chan, l2cap_chan_destroy)) + { + return -ENOMEM; + } + + ch->psm = psm; + + if (conn->sec_level < ch->required_sec_level) + { + err = bt_conn_set_security(conn, ch->required_sec_level); + if (err) + { + goto fail; + } + + atomic_set_bit(ch->chan.status, BT_L2CAP_STATUS_ENCRYPT_PENDING); + + return 0; + } + + err = l2cap_le_conn_req(ch); + if (err) + { + goto fail; + } + + return 0; + +fail: + bt_l2cap_chan_remove(conn, &ch->chan); + l2cap_chan_del(&ch->chan); + return err; +} + +#if defined(CONFIG_BT_L2CAP_ECRED) +static int l2cap_ecred_init(struct bt_conn *conn, + struct bt_l2cap_le_chan *ch, uint16_t psm) +{ + if (psm < L2CAP_LE_PSM_FIXED_START || psm > L2CAP_LE_PSM_DYN_END) + { + return -EINVAL; + } + + l2cap_chan_tx_init(ch); + l2cap_chan_rx_init(ch); + + if (!l2cap_chan_add(conn, &ch->chan, l2cap_chan_destroy)) + { + return -ENOMEM; + } + + ch->psm = psm; + + LOG_DBG("ch %p psm 0x%02x mtu %u mps %u credits " + "1", ch, ch->psm, ch->rx.mtu, + ch->rx.mps); + + return 0; +} + +int bt_l2cap_ecred_chan_connect(struct bt_conn *conn, + struct bt_l2cap_chan **chan, uint16_t psm) +{ + int i; + int err; + + LOG_DBG("conn %p chan %p psm 0x%04x", conn, chan, psm); + + if (!conn || !chan) + { + return -EINVAL; + } + + /* Init non-null channels */ + + for (i = 0; i < BT_L2CAP_ECRED_CHAN_MAX_PER_REQ; i++) + { + if (!chan[i]) + { + break; + } + + err = l2cap_ecred_init(conn, BT_L2CAP_LE_CHAN(chan[i]), psm); + if (err < 0) + { + i--; + goto fail; + } + } + + return l2cap_ecred_conn_req(chan, i); +fail: + + /* Remove channels added */ + + for (; i >= 0; i--) + { + if (!chan[i]) + { + continue; + } + + bt_l2cap_chan_remove(conn, chan[i]); + } + + return err; +} + +static struct bt_l2cap_le_chan *l2cap_find_pending_reconf( + struct bt_conn *conn) +{ + struct bt_l2cap_chan *chan; + + list_for_every_container(&conn->channels, chan, node) + { + if (BT_L2CAP_LE_CHAN(chan)->pending_rx_mtu) + { + return BT_L2CAP_LE_CHAN(chan); + } + } + + return NULL; +} + +int bt_l2cap_ecred_chan_reconfigure(struct bt_l2cap_chan **chans, + uint16_t mtu) +{ + struct bt_l2cap_ecred_reconf_req *req; + struct bt_conn *conn = NULL; + struct bt_l2cap_le_chan *ch; + struct net_buf *buf; + uint8_t ident; + int i; + + LOG_DBG("chans %p mtu 0x%04x", chans, mtu); + + if (!chans) + { + return -EINVAL; + } + + for (i = 0; i < BT_L2CAP_ECRED_CHAN_MAX_PER_REQ; i++) + { + if (!chans[i]) + { + break; + } + + /* validate that all channels are from same connection */ + + if (conn) + { + if (conn != chans[i]->conn) + { + return -EINVAL; + } + } + else + { + conn = chans[i]->conn; + } + + /* validate MTU is not decreased */ + + if (mtu < BT_L2CAP_LE_CHAN(chans[i])->rx.mtu) + { + return -EINVAL; + } + } + + if (i == 0) + { + return -EINVAL; + } + + if (!conn) + { + return -ENOTCONN; + } + + if (!bt_conn_is_le(conn)) + { + return -EINVAL; + } + + /* allow only 1 request at time */ + + if (l2cap_find_pending_reconf(conn)) + { + return -EBUSY; + } + + ident = get_ident(); + + buf = l2cap_create_le_sig_pdu(BT_L2CAP_ECRED_RECONF_REQ, ident, + sizeof(*req) + (i * sizeof(uint16_t))); + if (!buf) + { + return -ENOMEM; + } + + req = net_buf_add(buf, sizeof(*req)); + req->mtu = sys_cpu_to_le16(mtu); + + /* MPS shall not be bigger than MTU + BT_L2CAP_SDU_HDR_SIZE + * as the remaining bytes cannot be used. + */ + + req->mps = sys_cpu_to_le16(MIN(mtu + BT_L2CAP_SDU_HDR_SIZE, + BT_L2CAP_RX_MTU)); + + for (int j = 0; j < i; j++) + { + ch = BT_L2CAP_LE_CHAN(chans[j]); + + ch->ident = ident; + ch->pending_req = BT_L2CAP_ECRED_RECONF_REQ; + ch->pending_rx_mtu = mtu; + + net_buf_add_le16(buf, ch->rx.cid); + }; + + /* We set the RTX timer on one of the supplied channels, but when the + * request resolves or times out we will act on all the channels in the + * supplied array, using the ident field to find them. + */ + + l2cap_chan_send_req(chans[0], buf, L2CAP_CONN_TIMEOUT); + + return 0; +} + +#if defined(CONFIG_BT_L2CAP_RECONFIGURE_EXPLICIT) +int bt_l2cap_ecred_chan_reconfigure_explicit( + struct bt_l2cap_chan **chans, size_t chan_count, uint16_t mtu, + uint16_t mps) +{ + struct bt_l2cap_ecred_reconf_req *req; + struct bt_conn *conn = NULL; + struct net_buf *buf; + uint8_t ident; + + LOG_DBG("chans %p chan_count %u mtu 0x%04x mps 0x%04x", chans, chan_count, + mtu, mps); + + if (!chans || !IN_RANGE(chan_count, 1, BT_L2CAP_ECRED_CHAN_MAX_PER_REQ)) + { + return -EINVAL; + } + + if (!IN_RANGE(mps, BT_L2CAP_ECRED_MIN_MPS, BT_L2CAP_RX_MTU)) + { + return -EINVAL; + } + + for (size_t i = 0; i < chan_count; i++) + { + /* validate that all channels are from same connection */ + + if (conn) + { + if (conn != chans[i]->conn) + { + return -EINVAL; + } + } + else + { + conn = chans[i]->conn; + } + + /* validate MTU is not decreased */ + + if (mtu < BT_L2CAP_LE_CHAN(chans[i])->rx.mtu) + { + return -EINVAL; + } + + /* MPS is not allowed to decrease when reconfiguring multiple channels. + * Core Specification 3.A.4.27 v6.0 + */ + + if (chan_count > 1 && mps < BT_L2CAP_LE_CHAN(chans[i])->rx.mps) + { + return -EINVAL; + } + } + + if (!conn) + { + return -ENOTCONN; + } + + if (!bt_conn_is_le(conn)) + { + return -EINVAL; + } + + /* allow only 1 request at time */ + + if (l2cap_find_pending_reconf(conn)) + { + return -EBUSY; + } + + ident = get_ident(); + + buf = l2cap_create_le_sig_pdu(BT_L2CAP_ECRED_RECONF_REQ, ident, + sizeof(*req) + + (chan_count * sizeof(uint16_t))); + if (!buf) + { + return -ENOMEM; + } + + req = net_buf_add(buf, sizeof(*req)); + req->mtu = sys_cpu_to_le16(mtu); + req->mps = sys_cpu_to_le16(mps); + + for (size_t i = 0; i < chan_count; i++) + { + struct bt_l2cap_le_chan *ch; + + ch = BT_L2CAP_LE_CHAN(chans[i]); + + ch->ident = ident; + ch->pending_req = BT_L2CAP_ECRED_RECONF_REQ; + ch->pending_rx_mtu = mtu; + + net_buf_add_le16(buf, ch->rx.cid); + }; + + /* We set the RTX timer on one of the supplied channels, but when the + * request resolves or times out we will act on all the channels in the + * supplied array, using the ident field to find them. + */ + + l2cap_chan_send_req(chans[0], buf, L2CAP_CONN_TIMEOUT); + + return 0; +} +#endif /* defined(CONFIG_BT_L2CAP_RECONFIGURE_EXPLICIT) */ + +#endif /* defined(CONFIG_BT_L2CAP_ECRED) */ + +int bt_l2cap_chan_connect(struct bt_conn *conn, + struct bt_l2cap_chan *chan, uint16_t psm) +{ + struct bt_l2cap_le_chan *le_chan = BT_L2CAP_LE_CHAN(chan); + + LOG_DBG("conn %p chan %p psm 0x%04x", conn, chan, psm); + + if (!conn || conn->state != BT_CONN_CONNECTED) + { + return -ENOTCONN; + } + + if (!chan) + { + return -EINVAL; + } + + if (bt_conn_is_br(conn)) + { + return bt_l2cap_br_chan_connect(conn, chan, psm); + } + + if (le_chan->required_sec_level > BT_SECURITY_L4) + { + return -EINVAL; + } + else if (le_chan->required_sec_level == BT_SECURITY_L0) + { + le_chan->required_sec_level = BT_SECURITY_L1; + } + + return l2cap_le_connect(conn, le_chan, psm); +} + +int bt_l2cap_chan_disconnect(struct bt_l2cap_chan *chan) +{ + struct bt_conn *conn = chan->conn; + struct net_buf *buf; + struct bt_l2cap_disconn_req *req; + struct bt_l2cap_le_chan *le_chan; + uint8_t ident; + + if (!conn) + { + return -ENOTCONN; + } + + if (bt_conn_is_br(conn)) + { + return bt_l2cap_br_chan_disconnect(chan); + } + + le_chan = BT_L2CAP_LE_CHAN(chan); + + LOG_DBG("chan %p scid 0x%04x dcid 0x%04x", chan, le_chan->rx.cid, + le_chan->tx.cid); + + ident = get_ident(); + + buf = l2cap_create_le_sig_pdu(BT_L2CAP_DISCONN_REQ, ident, sizeof(*req)); + if (!buf) + { + return -ENOMEM; + } + + le_chan->ident = ident; + le_chan->pending_req = BT_L2CAP_DISCONN_REQ; + + req = net_buf_add(buf, sizeof(*req)); + req->dcid = sys_cpu_to_le16(le_chan->tx.cid); + req->scid = sys_cpu_to_le16(le_chan->rx.cid); + + l2cap_chan_send_req(chan, buf, L2CAP_DISC_TIMEOUT); + l2cap_chan_set_state(chan, BT_L2CAP_DISCONNECTING); + + return 0; +} + +__maybe_unused static bool user_data_not_empty(const struct net_buf *buf) +{ + size_t ud_len = sizeof(struct closure); + const uint8_t *ud = net_buf_user_data(buf); + + for (size_t i = 0; i < ud_len; i++) + { + if (ud[i] != 0) + { + return true; + } + } + + return false; +} + +static int bt_l2cap_dyn_chan_send(struct bt_l2cap_le_chan *le_chan, + struct net_buf *buf) +{ + uint16_t sdu_len = buf->len; + + LOG_DBG("chan %p buf %p", le_chan, buf); + + /* Frags are not supported. */ + + DEBUGASSERT(buf->frags == NULL); + + if (sdu_len > le_chan->tx.mtu) + { + LOG_ERR("attempt to send %u bytes on %u MTU chan", sdu_len, + le_chan->tx.mtu); + return -EMSGSIZE; + } + + if (buf->ref != 1) + { + /* The host may alter the buf contents when segmenting. Higher + * layers cannot expect the buf contents to stay intact. Extra refs + * suggests a silent data corruption would occur if not for this error. + */ + + LOG_ERR("buf given to l2cap has other refs"); + return -EINVAL; + } + + if (net_buf_headroom(buf) < BT_L2CAP_SDU_CHAN_SEND_RESERVE) + { + /* Call `net_buf_reserve(buf, BT_L2CAP_SDU_CHAN_SEND_RESERVE)` + * when allocating buffers intended for bt_l2cap_chan_send(). + */ + + LOG_ERR("Not enough headroom in buf %p", buf); + return -EINVAL; + } + + if (user_data_not_empty(buf)) + { + /* There may be issues if user_data is not empty. */ + + LOG_WRN("user_data is not empty"); + } + + /* Prepend SDU length. + * + * L2CAP LE CoC SDUs are segmented and put into K-frames PDUs which have + * their own L2CAP header (i.e. PDU length, channel id). + * + * The SDU length is right before the data that will be segmented and is + * only present in the first PDU. Here's an example: + * + * Sent data payload of 50 bytes over channel 0x4040 with MPS of 30 bytes: + * First PDU (K-frame): + * | L2CAP K-frame header | K-frame payload | + * | PDU length | Channel ID | SDU length | SDU payload | + * | 0x001e | 0x4040 | 0x0032 | 28 bytes of data | + * + * Second and last PDU (K-frame): + * | L2CAP K-frame header | K-frame payload | + * | PDU length | Channel ID | rest of SDU payload | + * | 0x0016 | 0x4040 | 22 bytes of data | + */ + + net_buf_push_le16(buf, sdu_len); + + /* Put buffer on TX queue */ + + k_fifo_put(&le_chan->tx_queue, buf); + + /* Always process the queue in the same context */ + + raise_data_ready(le_chan); + + return 0; +} +#endif /* CONFIG_BT_L2CAP_DYNAMIC_CHANNEL */ + +static void l2cap_fixed_chan_sent_cb(struct bt_conn *conn, + void *user_data, int err) +{ + struct bt_l2cap_chan *chan = user_data; + + if (chan->ops->sent) + { + chan->ops->sent(chan); + } +} + +int bt_l2cap_chan_send(struct bt_l2cap_chan *chan, struct net_buf *buf) +{ + if (!buf || !chan) + { + return -EINVAL; + } + + LOG_DBG("chan %p buf %p len %u", chan, buf, buf->len); + + if (buf->ref != 1) + { + LOG_WRN("Expecting 1 ref, got %d", buf->ref); + return -EINVAL; + } + + if (!chan->conn || chan->conn->state != BT_CONN_CONNECTED) + { + return -ENOTCONN; + } + + if (atomic_test_bit(chan->status, BT_L2CAP_STATUS_SHUTDOWN)) + { + return -ESHUTDOWN; + } + + if (bt_conn_is_br(chan->conn)) + { + return bt_l2cap_br_chan_send_cb(chan, buf, NULL, NULL); + } + + struct bt_l2cap_le_chan *le_chan = BT_L2CAP_LE_CHAN(chan); + + DEBUGASSERT(le_chan); + +#if defined(CONFIG_BT_L2CAP_DYNAMIC_CHANNEL) + if (L2CAP_LE_CID_IS_DYN(le_chan->tx.cid)) + { + return bt_l2cap_dyn_chan_send(le_chan, buf); + } +#endif + + if (L2CAP_LE_CID_IS_FIXED(le_chan->tx.cid)) + { + return bt_l2cap_send_pdu(le_chan, buf, l2cap_fixed_chan_sent_cb, + (void *)chan); + } + + LOG_DBG("Invalid channel type (chan %p)", chan); + + return -EINVAL; +} diff --git a/libs/zblue/host/l2cap_internal.h b/libs/zblue/host/l2cap_internal.h new file mode 100644 index 0000000000000..91a22acf4e1e8 --- /dev/null +++ b/libs/zblue/host/l2cap_internal.h @@ -0,0 +1,283 @@ +/**************************************************************************** + * libs/zblue/host/l2cap_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Internal APIs for Bluetooth L2CAP handling. + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "host/classic/l2cap_br_interface.h" +/* TODO: we should include conn_internal.h for bt_conn_tx_cb_t but that + * causes redefinitions + */ + +enum l2cap_conn_list_action +{ + BT_L2CAP_CHAN_LOOKUP, + BT_L2CAP_CHAN_DETACH, +}; + +#define BT_L2CAP_PSM_RFCOMM 0x0003 + +struct bt_l2cap_hdr +{ + uint16_t len; + uint16_t cid; +} __packed; + +struct bt_l2cap_sig_hdr +{ + uint8_t code; + uint8_t ident; + uint16_t len; +} __packed; + +#define BT_L2CAP_REJ_NOT_UNDERSTOOD 0x0000 +#define BT_L2CAP_REJ_MTU_EXCEEDED 0x0001 +#define BT_L2CAP_REJ_INVALID_CID 0x0002 + +#define BT_L2CAP_CMD_REJECT 0x01 +struct bt_l2cap_cmd_reject +{ + uint16_t reason; + uint8_t data[]; +} __packed; + +struct bt_l2cap_cmd_reject_cid_data +{ + uint16_t scid; + uint16_t dcid; +} __packed; + +#define BT_L2CAP_DISCONN_REQ 0x06 +struct bt_l2cap_disconn_req +{ + uint16_t dcid; + uint16_t scid; +} __packed; + +#define BT_L2CAP_DISCONN_RSP 0x07 +struct bt_l2cap_disconn_rsp +{ + uint16_t dcid; + uint16_t scid; +} __packed; + +#define BT_L2CAP_CONN_PARAM_REQ 0x12 +struct bt_l2cap_conn_param_req +{ + uint16_t min_interval; + uint16_t max_interval; + uint16_t latency; + uint16_t timeout; +} __packed; + +#define BT_L2CAP_CONN_PARAM_ACCEPTED 0x0000 +#define BT_L2CAP_CONN_PARAM_REJECTED 0x0001 + +#define BT_L2CAP_CONN_PARAM_RSP 0x13 +struct bt_l2cap_conn_param_rsp +{ + uint16_t result; +} __packed; + +#define BT_L2CAP_LE_CONN_REQ 0x14 +struct bt_l2cap_le_conn_req +{ + uint16_t psm; + uint16_t scid; + uint16_t mtu; + uint16_t mps; + uint16_t credits; +} __packed; + +/* valid results in conn response on LE */ +#define BT_L2CAP_LE_SUCCESS 0x0000 +#define BT_L2CAP_LE_ERR_PSM_NOT_SUPP 0x0002 +#define BT_L2CAP_LE_ERR_NO_RESOURCES 0x0004 +#define BT_L2CAP_LE_ERR_AUTHENTICATION 0x0005 +#define BT_L2CAP_LE_ERR_AUTHORIZATION 0x0006 +#define BT_L2CAP_LE_ERR_KEY_SIZE 0x0007 +#define BT_L2CAP_LE_ERR_ENCRYPTION 0x0008 +#define BT_L2CAP_LE_ERR_INVALID_SCID 0x0009 +#define BT_L2CAP_LE_ERR_SCID_IN_USE 0x000A +#define BT_L2CAP_LE_ERR_UNACCEPT_PARAMS 0x000B +#define BT_L2CAP_LE_ERR_INVALID_PARAMS 0x000C + +#define BT_L2CAP_LE_CONN_RSP 0x15 +struct bt_l2cap_le_conn_rsp +{ + uint16_t dcid; + uint16_t mtu; + uint16_t mps; + uint16_t credits; + uint16_t result; +} __packed; + +#define BT_L2CAP_LE_CREDITS 0x16 +struct bt_l2cap_le_credits +{ + uint16_t cid; + uint16_t credits; +} __packed; + +#define BT_L2CAP_ECRED_CREDITS_MIN 1 +#define BT_L2CAP_ECRED_CREDITS_MAX UINT16_MAX + +#define BT_L2CAP_ECRED_CONN_REQ 0x17 +struct bt_l2cap_ecred_conn_req +{ + uint16_t psm; + uint16_t mtu; + uint16_t mps; + uint16_t credits; + uint16_t scid[]; +} __packed; + +#define BT_L2CAP_ECRED_CONN_RSP 0x18 +struct bt_l2cap_ecred_conn_rsp +{ + uint16_t mtu; + uint16_t mps; + uint16_t credits; + uint16_t result; + uint16_t dcid[]; +} __packed; + +#define BT_L2CAP_ECRED_RECONF_REQ 0x19 +struct bt_l2cap_ecred_reconf_req +{ + uint16_t mtu; + uint16_t mps; + uint16_t scid[]; +} __packed; + +#define BT_L2CAP_RECONF_SUCCESS 0x0000 +#define BT_L2CAP_RECONF_INVALID_MTU 0x0001 +#define BT_L2CAP_RECONF_INVALID_MPS 0x0002 +#define BT_L2CAP_RECONF_INVALID_CID 0x0003 +#define BT_L2CAP_RECONF_OTHER_UNACCEPT 0x0004 + +#define BT_L2CAP_ECRED_RECONF_RSP 0x1a +struct bt_l2cap_ecred_reconf_rsp +{ + uint16_t result; +} __packed; + +/* Notify L2CAP channels of a new connection */ + +void bt_l2cap_connected(struct bt_conn *conn); + +/* Notify L2CAP channels of a disconnect event */ + +void bt_l2cap_disconnected(struct bt_conn *conn); + +/* Add channel to the connection */ + +void bt_l2cap_chan_add(struct bt_conn *conn, struct bt_l2cap_chan *chan, + bt_l2cap_chan_destroy_t destroy); + +/* Remove channel from the connection */ + +void bt_l2cap_chan_remove(struct bt_conn *conn, struct bt_l2cap_chan *chan); + +const char *bt_l2cap_chan_state_str(bt_l2cap_chan_state_t state); + +/* Notify L2CAP channels of a change in encryption state passing additionally + * HCI status of performed security procedure. + */ + +void bt_l2cap_security_changed(struct bt_conn *conn, uint8_t hci_status); + +/* Prepare an L2CAP PDU to be sent over a connection */ + +struct net_buf *bt_l2cap_create_pdu_timeout( + struct net_buf_pool *pool, size_t reserve, clock_t timeout); + +#define bt_l2cap_create_pdu(_pool, _reserve) \ + bt_l2cap_create_pdu_timeout(_pool, _reserve, ((clock_t)-1)) + +/* Send L2CAP PDU over a connection + * + * Buffer ownership is transferred to stack in case of success. + * + * The callback will always be invoked exactly once: After the Controller + * gives a Number of Completed Packets Event for the last L2CAP PDU of the + * buffer or after the channel is disconnected and the buffer may or may not + * have been sent in full, in which case the error code will be -ESHUTDOWN. + */ + +int bt_l2cap_send_pdu( + struct bt_l2cap_le_chan *le_chan, struct net_buf *pdu, bt_conn_tx_cb_t cb, + void *user_data); + +/* Receive a new L2CAP PDU from a connection */ + +void bt_l2cap_recv(struct bt_conn *conn, struct net_buf *buf, bool complete); + +/* Perform connection parameter update request */ + +int bt_l2cap_update_conn_param(struct bt_conn *conn, + const struct bt_le_conn_param *param); + +/* Initialize L2CAP and supported channels */ + +void bt_l2cap_init(void); + +/* Lookup channel by Transmission CID */ + +struct bt_l2cap_chan *bt_l2cap_le_lookup_tx_cid(struct bt_conn *conn, + uint16_t cid); + +/* Lookup channel by Receiver CID */ + +struct bt_l2cap_chan *bt_l2cap_le_lookup_rx_cid(struct bt_conn *conn, + uint16_t cid); + +struct bt_l2cap_ecred_cb +{ + void (*ecred_conn_rsp)(struct bt_conn *conn, + uint16_t result, + uint8_t attempted, + uint8_t succeeded, + uint16_t psm); + void (*ecred_conn_req)(struct bt_conn *conn, + uint16_t result, + uint16_t psm); +}; + +/* Register callbacks for Enhanced Credit based Flow Control */ + +void bt_l2cap_register_ecred_cb(const struct bt_l2cap_ecred_cb *cb); + +/* Returns a server if it exists for given psm. */ + +struct bt_l2cap_server *bt_l2cap_server_lookup_psm(uint16_t psm); + +/* Pull data from the L2CAP layer */ + +struct net_buf *l2cap_data_pull(struct bt_conn *conn, + size_t amount, size_t *length); diff --git a/libs/zblue/host/long_wq.c b/libs/zblue/host/long_wq.c new file mode 100644 index 0000000000000..6098b08211877 --- /dev/null +++ b/libs/zblue/host/long_wq.c @@ -0,0 +1,63 @@ +/**************************************************************************** + * libs/zblue/host/long_wq.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* long_work.c - Workqueue intended for long-running operations. */ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +static uint64_t bt_lw_stack_area[CONFIG_BT_LONG_WQ_STACK_SIZE / + sizeof(uint64_t)]; +static struct k_work_q bt_long_wq; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bt_long_wq_schedule(struct k_work_delayable *dwork, clock_t timeout) +{ + return k_work_schedule_for_queue(&bt_long_wq, dwork, timeout); +} + +int bt_long_wq_reschedule(struct k_work_delayable *dwork, clock_t timeout) +{ + return k_work_reschedule_for_queue(&bt_long_wq, dwork, timeout); +} + +int bt_long_wq_submit(struct k_work *work) +{ + return k_work_submit_to_queue(&bt_long_wq, work); +} + +static int long_wq_init(void) +{ + const struct k_work_queue_config cfg = + { + .name = "BT LW WQ" + }; + + k_work_queue_init(&bt_long_wq); + + k_work_queue_start(&bt_long_wq, bt_lw_stack_area, sizeof(bt_lw_stack_area), + CONFIG_BT_LONG_WQ_PRIO, &cfg); + + return 0; +} + +SYS_INIT(long_wq_init, POST_KERNEL, CONFIG_BT_LONG_WQ_INIT_PRIO); diff --git a/libs/zblue/host/long_wq.h b/libs/zblue/host/long_wq.h new file mode 100644 index 0000000000000..0f91654e27782 --- /dev/null +++ b/libs/zblue/host/long_wq.h @@ -0,0 +1,24 @@ +/**************************************************************************** + * libs/zblue/host/long_wq.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* long_wq.h - Workqueue API intended for long-running operations. */ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +int bt_long_wq_schedule(struct k_work_delayable *dwork, clock_t timeout); +int bt_long_wq_reschedule(struct k_work_delayable *dwork, clock_t timeout); +int bt_long_wq_submit(struct k_work *dwork); diff --git a/libs/zblue/host/monitor.c b/libs/zblue/host/monitor.c new file mode 100644 index 0000000000000..7a12f13eaf689 --- /dev/null +++ b/libs/zblue/host/monitor.c @@ -0,0 +1,299 @@ +/**************************************************************************** + * libs/zblue/host/monitor.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Custom logging over UART + */ + +/* Copyright (c) 2016 Intel Corporation + * Copyright (c) 2026 Silicon Laboratories Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "monitor.h" + +/* TS resolution is 1/10th of a millisecond */ +#define MONITOR_TS_FREQ 10000 + +/* This is the same default priority as for other console handlers, + * except that we're not exporting it as a Kconfig variable until a clear + * need arises. + */ +#define MONITOR_INIT_PRIORITY 60 + +struct bt_monitor_data +{ + const void *data; + size_t len; +}; + +enum +{ + BT_LOG_BUSY, +}; + +static atomic_t flags; + +static struct +{ + atomic_t cmd; + atomic_t evt; + atomic_t acl_tx; + atomic_t acl_rx; +#if defined(CONFIG_BT_CLASSIC) + atomic_t sco_tx; + atomic_t sco_rx; +#endif + atomic_t other; +} drops; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void drop_add(uint16_t opcode) +{ + switch (opcode) + { + case BT_MONITOR_COMMAND_PKT: + atomic_add(&drops.cmd, 1); + break; + case BT_MONITOR_EVENT_PKT: + atomic_add(&drops.evt, 1); + break; + case BT_MONITOR_ACL_TX_PKT: + atomic_add(&drops.acl_tx, 1); + break; + case BT_MONITOR_ACL_RX_PKT: + atomic_add(&drops.acl_rx, 1); + break; +#if defined(CONFIG_BT_CLASSIC) + case BT_MONITOR_SCO_TX_PKT: + atomic_add(&drops.sco_tx, 1); + break; + case BT_MONITOR_SCO_RX_PKT: + atomic_add(&drops.sco_rx, 1); + break; +#endif + default: + atomic_add(&drops.other, 1); + break; + } +} + +static bool monitor_send(const struct bt_monitor_data *frags, size_t count) +{ + ARG_UNUSED(frags); + ARG_UNUSED(count); + + return false; +} + +static void encode_drops(struct bt_monitor_hdr *hdr, + uint8_t type, atomic_t *val) +{ + int32_t count; + + count = atomic_xchg(val, 0); + if (count) + { + hdr->ext[hdr->hdr_len++] = type; + hdr->ext[hdr->hdr_len++] = MIN(count, 255); + if (count > 255) + { + /* Keep the surplus for the next record */ + + atomic_add(val, count - 255); + } + } +} + +static atomic_t *drop_counter(uint8_t type) +{ + switch (type) + { + case BT_MONITOR_COMMAND_DROPS: + return &drops.cmd; + case BT_MONITOR_EVENT_DROPS: + return &drops.evt; + case BT_MONITOR_ACL_TX_DROPS: + return &drops.acl_tx; + case BT_MONITOR_ACL_RX_DROPS: + return &drops.acl_rx; +#if defined(CONFIG_BT_CLASSIC) + case BT_MONITOR_SCO_TX_DROPS: + return &drops.sco_tx; + case BT_MONITOR_SCO_RX_DROPS: + return &drops.sco_rx; +#endif + case BT_MONITOR_OTHER_DROPS: + return &drops.other; + default: + return NULL; + } +} + +/* Restore drop counts that encode_hdr() consumed into a record which then + * failed to be sent: without this, every record dropped back-to-back would + * destroy the counts accumulated by its predecessors, understating drops + * under sustained overload. + */ + +static void restore_drops(const struct bt_monitor_hdr *hdr) +{ + /* The timestamp is always encoded first, drop pairs follow */ + + for (uint8_t i = sizeof(struct bt_monitor_ts32); i + + 1U < hdr->hdr_len; i += 2U) + { + atomic_t *counter = drop_counter(hdr->ext[i]); + + if (counter != NULL) + { + atomic_add(counter, hdr->ext[i + 1]); + } + } +} + +/* Convert microseconds to the monitor timestamp resolution */ + +static uint64_t monitor_ts_from_us(uint64_t us) +{ + return us / (USEC_PER_SEC / MONITOR_TS_FREQ); +} + +/* Timestamp for HCI packets: the real-time clock when configured, + * otherwise the system timer. Conversions go via microseconds rather than + * dividing by hw_cycles / MONITOR_TS_FREQ: the latter truncates badly for + * cycle rates that are not a multiple of MONITOR_TS_FREQ (e.g. 32768 Hz + * yields a divisor of 3 instead of 3.2768, making the timestamps run 9.2% + * fast). + */ + +static uint64_t monitor_ts_get(void) +{ + struct timespec ts; + uint64_t us; + + if (IS_ENABLED(CONFIG_LOG_TIMESTAMP_USE_REALTIME) && + clock_gettime(CLOCK_REALTIME, &ts) == 0) + { + us = + ((uint64_t)ts.tv_sec * USEC_PER_SEC) + + (ts.tv_nsec / NSEC_PER_USEC); + } + else + { + us = clock_systime_nsec() / NSEC_PER_USEC; + } + + return monitor_ts_from_us(us); +} + +static inline void encode_hdr( + struct bt_monitor_hdr *hdr, uint64_t timestamp, uint16_t opcode, + uint16_t len) +{ + struct bt_monitor_ts32 *ts; + + hdr->opcode = sys_cpu_to_le16(opcode); + hdr->flags = 0U; + + ts = (void *)hdr->ext; + ts->type = BT_MONITOR_TS32; + /* The btsnoop protocol only supports 32-bit timestamps (in 1/10th ms). + * This overflows after 4.97 days, which is acceptable as it only affects + * rendering in btmon/wireshark. Recordings are usually not that long + * anyway, and packet ordering is still preserved. + */ + + ts->ts32 = (uint32_t)timestamp; + hdr->hdr_len = sizeof(*ts); + + encode_drops(hdr, BT_MONITOR_COMMAND_DROPS, &drops.cmd); + encode_drops(hdr, BT_MONITOR_EVENT_DROPS, &drops.evt); + encode_drops(hdr, BT_MONITOR_ACL_TX_DROPS, &drops.acl_tx); + encode_drops(hdr, BT_MONITOR_ACL_RX_DROPS, &drops.acl_rx); +#if defined(CONFIG_BT_CLASSIC) + encode_drops(hdr, BT_MONITOR_SCO_TX_DROPS, &drops.sco_tx); + encode_drops(hdr, BT_MONITOR_SCO_RX_DROPS, &drops.sco_rx); +#endif + encode_drops(hdr, BT_MONITOR_OTHER_DROPS, &drops.other); + + hdr->data_len = sys_cpu_to_le16(4 + hdr->hdr_len + len); +} + +void bt_monitor_send(uint16_t opcode, const void *data, size_t len) +{ + struct bt_monitor_hdr hdr; + struct bt_monitor_data frags[] = + { + {&hdr, 0}, /* Length known only after encode_hdr() */ + { + data, + len + }, + }; + + if (atomic_test_and_set_bit(&flags, BT_LOG_BUSY)) + { + drop_add(opcode); + return; + } + + encode_hdr(&hdr, monitor_ts_get(), opcode, len); + frags[0].len = BT_MONITOR_BASE_HDR_LEN + hdr.hdr_len; + + if (!monitor_send(frags, ARRAY_SIZE(frags))) + { + restore_drops(&hdr); + drop_add(opcode); + } + + atomic_clear_bit(&flags, BT_LOG_BUSY); +} + +void bt_monitor_new_index(uint8_t type, + uint8_t bus, const bt_addr_t *addr, + const char *name) +{ + struct bt_monitor_new_index pkt; + + pkt.type = type; + pkt.bus = bus; + memcpy(pkt.bdaddr, addr, 6); + strncpy(pkt.name, name, sizeof(pkt.name) - 1); + pkt.name[sizeof(pkt.name) - 1] = '\0'; + + bt_monitor_send(BT_MONITOR_NEW_INDEX, &pkt, sizeof(pkt)); +} + +static int bt_monitor_init(void) +{ + return 0; +} + +SYS_INIT(bt_monitor_init, PRE_KERNEL_1, MONITOR_INIT_PRIORITY); diff --git a/libs/zblue/host/monitor.h b/libs/zblue/host/monitor.h new file mode 100644 index 0000000000000..8d124c73e66a1 --- /dev/null +++ b/libs/zblue/host/monitor.h @@ -0,0 +1,163 @@ +/**************************************************************************** + * libs/zblue/host/monitor.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Custom monitor protocol logging over UART + */ + +/* Copyright (c) 2016 Intel Corporation + * Copyright (c) 2025 Silicon Laboratories Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#define BT_MONITOR_NEW_INDEX 0 +#define BT_MONITOR_DEL_INDEX 1 +#define BT_MONITOR_COMMAND_PKT 2 +#define BT_MONITOR_EVENT_PKT 3 +#define BT_MONITOR_ACL_TX_PKT 4 +#define BT_MONITOR_ACL_RX_PKT 5 +#define BT_MONITOR_SCO_TX_PKT 6 +#define BT_MONITOR_SCO_RX_PKT 7 +#define BT_MONITOR_OPEN_INDEX 8 +#define BT_MONITOR_CLOSE_INDEX 9 +#define BT_MONITOR_INDEX_INFO 10 +#define BT_MONITOR_VENDOR_DIAG 11 +#define BT_MONITOR_SYSTEM_NOTE 12 +#define BT_MONITOR_USER_LOGGING 13 +#define BT_MONITOR_ISO_TX_PKT 18 +#define BT_MONITOR_ISO_RX_PKT 19 +#define BT_MONITOR_NOP 255 + +#define BT_MONITOR_TYPE_PRIMARY 0 +#define BT_MONITOR_TYPE_AMP 1 + +/* Extended header types */ +#define BT_MONITOR_COMMAND_DROPS 1 +#define BT_MONITOR_EVENT_DROPS 2 +#define BT_MONITOR_ACL_RX_DROPS 3 +#define BT_MONITOR_ACL_TX_DROPS 4 +#define BT_MONITOR_SCO_RX_DROPS 5 +#define BT_MONITOR_SCO_TX_DROPS 6 +#define BT_MONITOR_OTHER_DROPS 7 +#define BT_MONITOR_TS32 8 + +#define BT_MONITOR_BASE_HDR_LEN 6 + +#if defined(CONFIG_BT_CLASSIC) +#define BT_MONITOR_EXT_HDR_MAX 19 +#else +#define BT_MONITOR_EXT_HDR_MAX 15 +#endif + +struct bt_monitor_hdr +{ + uint16_t data_len; + uint16_t opcode; + uint8_t flags; + uint8_t hdr_len; + + uint8_t ext[BT_MONITOR_EXT_HDR_MAX]; +} __packed; + +struct bt_monitor_ts32 +{ + uint8_t type; + uint32_t ts32; +} __packed; + +struct bt_monitor_new_index +{ + uint8_t type; + uint8_t bus; + uint8_t bdaddr[6]; + char name[8]; +} __packed; + +struct bt_monitor_user_logging +{ + uint8_t priority; + uint8_t ident_len; +} __packed; + +enum bt_monitor_dir +{ + BT_MONITOR_TX, + BT_MONITOR_RX, +}; + +static inline uint8_t bt_monitor_opcode(uint8_t type, + enum bt_monitor_dir dir) +{ + switch (type) + { + case BT_HCI_H4_CMD: + return BT_MONITOR_COMMAND_PKT; + case BT_HCI_H4_EVT: + return BT_MONITOR_EVENT_PKT; + case BT_HCI_H4_ACL: + if (dir == BT_MONITOR_TX) + { + return BT_MONITOR_ACL_TX_PKT; + } + else + { + return BT_MONITOR_ACL_RX_PKT; + } + case BT_HCI_H4_ISO: + if (dir == BT_MONITOR_TX) + { + return BT_MONITOR_ISO_TX_PKT; + } + else + { + return BT_MONITOR_ISO_RX_PKT; + } + default: + return BT_MONITOR_NOP; + } +} + +/* TODO: Remove guard and replace with IS_ENABLED(CONFIG_BT_MONITOR) */ +#if defined(CONFIG_BT_MONITOR) + +void bt_monitor_send(uint16_t opcode, const void *data, size_t len); + +void bt_monitor_new_index(uint8_t type, + uint8_t bus, const bt_addr_t *addr, + const char *name); + +#else /* !CONFIG_BT_MONITOR */ + +static inline void bt_monitor_send(uint16_t opcode, + const void *data, size_t len) +{ + ARG_UNUSED(opcode); + ARG_UNUSED(data); + ARG_UNUSED(len); +} + +static inline void bt_monitor_new_index( + uint8_t type, uint8_t bus, const bt_addr_t *addr, const char *name) +{ + ARG_UNUSED(type); + ARG_UNUSED(bus); + ARG_UNUSED(addr); + ARG_UNUSED(name); +} + +#endif diff --git a/libs/zblue/host/monitor_buffer.h b/libs/zblue/host/monitor_buffer.h new file mode 100644 index 0000000000000..76ad5ad539237 --- /dev/null +++ b/libs/zblue/host/monitor_buffer.h @@ -0,0 +1,136 @@ +/**************************************************************************** + * libs/zblue/host/monitor_buffer.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2026 Silicon Laboratories Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_SUBSYS_BLUETOOTH_HOST_MONITOR_BUFFER_H_ +#define ZEPHYR_SUBSYS_BLUETOOTH_HOST_MONITOR_BUFFER_H_ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include + +struct bt_monitor_data +{ + const void *data; + size_t len; +}; + +/* Header-only so that the unit test in tests/bluetooth/host/monitor_buffer + * can build this function without pulling in monitor.c and its dependencies. + * monitor.c is the only in-tree includer, so the generated code is the same + * as for a file-local static function. + */ + +static inline bool bt_monitor_ring_buf_put(struct circbuf_s *buf, + const struct bt_monitor_data *frags, + size_t count) +{ + size_t space = circbuf_space(buf); + size_t total = 0; + + /* total only grows after passing the check, so total <= space is + * invariant: the subtraction cannot underflow, the sum cannot overflow, + * and offset/remaining in the copy loop below stay bounded by the buffer + * size. + */ + + for (size_t i = 0; i < count; i++) + { + if (frags[i].len > space - total) + { + return false; + } + + total += frags[i].len; + } + + /* Pairs with bt_monitor_ring_buf_consume(): space observed above may + * have been freed by a consumer on another CPU, whose data reads must + * complete before the writes below reuse it. + */ + + SMP_MB(); + + /* The space check guarantees that the writes below always fit, and + * the single commit makes the complete record visible to the consumer at + * once. + */ + + for (size_t i = 0, offset = 0; i < count; i++) + { + const uint8_t *src = frags[i].data; + size_t remaining = frags[i].len; + + while (remaining > 0) + { + uint8_t *dst; + size_t len; + size_t off; + + off = (buf->head + offset) % buf->size; + dst = (uint8_t *)buf->base + off; + len = MIN(remaining, buf->size - off); + + (void)memcpy(dst, src, len); + offset += len; + src += len; + remaining -= len; + } + } + + /* Make the record bytes visible before the commit publishes them + * (pairs with bt_monitor_ring_buf_get_ptr()). + */ + + SMP_MB(); + circbuf_writecommit(buf, total); + + return true; +} + +/* Consumer-side wrappers adding the memory ordering that the ring buffer's + * lock-free SPSC contract leaves to its users on SMP systems. The producer + * side is handled in bt_monitor_ring_buf_put(). + */ + +static inline uint32_t bt_monitor_ring_buf_get_ptr(struct circbuf_s *buf, + uint8_t **data) +{ + size_t len; + + *data = circbuf_get_readptr(buf, &len); + + /* Order the index load against the data reads that follow */ + + SMP_MB(); + + return len; +} + +static inline void bt_monitor_ring_buf_consume(struct circbuf_s *buf, + uint32_t size) +{ + /* Order the data reads against the space becoming reusable */ + + SMP_MB(); + circbuf_readcommit(buf, size); +} + +#endif /* ZEPHYR_SUBSYS_BLUETOOTH_HOST_MONITOR_BUFFER_H_ */ diff --git a/libs/zblue/host/scan.c b/libs/zblue/host/scan.c new file mode 100644 index 0000000000000..08ea0713d7e95 --- /dev/null +++ b/libs/zblue/host/scan.c @@ -0,0 +1,3272 @@ +/**************************************************************************** + * libs/zblue/host/scan.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017-2021 Nordic Semiconductor ASA + * Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "addr_internal.h" +#include "common/bt_str.h" +#include "conn_internal.h" +#include "direction_internal.h" +#include "hci_core.h" +#include "id.h" +#include "scan.h" + +#define LOG_LEVEL CONFIG_BT_HCI_CORE_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_scan); + +struct scanner_state +{ + atomic_define(scan_flags, BT_LE_SCAN_USER_NUM_FLAGS); + struct bt_le_scan_param explicit_scan_param; + struct bt_le_scan_param used_scan_param; + rmutex_t scan_update_mutex; + rmutex_t scan_explicit_params_mutex; +}; + +enum scan_action +{ + SCAN_ACTION_NONE, + SCAN_ACTION_START, + SCAN_ACTION_STOP, + SCAN_ACTION_UPDATE, +}; + +static bt_le_scan_cb_t *scan_dev_found_cb; +static struct list_node scan_cbs = LIST_INITIAL_VALUE(scan_cbs); + +static struct scanner_state scan_state; + +#if defined(CONFIG_BT_EXT_ADV) +/* A buffer used to reassemble advertisement data from the controller. */ + +NET_BUF_SIMPLE_DEFINE(ext_scan_buf, CONFIG_BT_EXT_SCAN_BUF_SIZE); +#define REASSEMBLY_TIMEOUT MSEC2TICK(CONFIG_BT_EXT_ADV_REASSEMBLY_TIMEOUT) + +#if defined(CONFIG_BT_TESTING) + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +__weak void bt_testing_trace_ext_adv_reassembly_timeout(void) +{ +} + +__weak void bt_testing_trace_ext_adv_reassembly_complete(void) +{ +} +#endif /* defined(CONFIG_BT_TESTING) */ + +struct fragmented_advertiser +{ + bt_addr_le_t addr; + uint8_t sid; + enum + { + FRAG_ADV_INACTIVE, + FRAG_ADV_REASSEMBLING, + FRAG_ADV_DISCARDING, + } state; +}; + +static struct fragmented_advertiser reassembling_advertiser; + +/* The timeout handler runs on the Bluetooth workqueue and is thereby + * serialized with the extended advertising report processing, which also + * accesses reassembling_advertiser without locking. + */ + +static void reassembly_timeout_work_handler(struct k_work *work) +{ + if (reassembling_advertiser.state == FRAG_ADV_REASSEMBLING) + { + LOG_DBG("Ext adv reassembly timeout, discarding incomplete chain"); + reassembling_advertiser.state = FRAG_ADV_DISCARDING; + + if (IS_ENABLED(CONFIG_BT_TESTING)) + { + bt_testing_trace_ext_adv_reassembly_timeout(); + } + } +} + +K_WORK_DELAYABLE_DEFINE(reassembly_timeout_work, + reassembly_timeout_work_handler); + +static void reassembly_timeout_work_reschedule(void) +{ + int err = bt_work_reschedule(&reassembly_timeout_work, REASSEMBLY_TIMEOUT); + + if (err < 0) + { + LOG_ERR("Failed to reschedule reassembly timeout work: %d", err); + } +} + +static bool fragmented_advertisers_equal( + const struct fragmented_advertiser *a, const bt_addr_le_t *addr, + uint8_t sid) +{ + /* Two advertisers are equal if they are the same adv set from the same + * device + */ + + return a->sid == sid && bt_addr_le_eq(&a->addr, addr); +} + +/* Sets the address and sid of the advertiser to be reassembled. */ + +static void init_reassembling_advertiser(const bt_addr_le_t *addr, + uint8_t sid) +{ + bt_addr_le_copy(&reassembling_advertiser.addr, addr); + reassembling_advertiser.sid = sid; + reassembling_advertiser.state = FRAG_ADV_REASSEMBLING; + reassembly_timeout_work_reschedule(); +} + +static void reset_reassembling_advertiser(void) +{ + if (k_work_delayable_is_pending(&reassembly_timeout_work)) + { + int err = k_work_cancel_delayable(&reassembly_timeout_work); + + if (err < 0) + { + LOG_ERR("Failed to cancel reassembly timeout work: %d", err); + } + } + + net_buf_simple_reset(&ext_scan_buf); + reassembling_advertiser.state = FRAG_ADV_INACTIVE; +} + +#if defined(CONFIG_BT_PER_ADV_SYNC) +static struct bt_le_per_adv_sync + per_adv_sync_pool[CONFIG_BT_PER_ADV_SYNC_MAX]; +static struct list_node pa_sync_cbs = LIST_INITIAL_VALUE(pa_sync_cbs); +#endif /* defined(CONFIG_BT_PER_ADV_SYNC) */ +#endif /* defined(CONFIG_BT_EXT_ADV) */ + +void bt_scan_softreset(void) +{ + scan_dev_found_cb = NULL; +#if defined(CONFIG_BT_EXT_ADV) + reset_reassembling_advertiser(); +#endif +} + +void bt_scan_reset(void) +{ + memset(&scan_state, 0x0, sizeof(scan_state)); + nxrmutex_init(&scan_state.scan_update_mutex); + nxrmutex_init(&scan_state.scan_explicit_params_mutex); + bt_scan_softreset(); +} + +static int cmd_le_set_ext_scan_enable(bool enable, + bool filter_duplicates, + uint16_t duration) +{ + struct bt_hci_cp_le_set_ext_scan_enable *cp; + struct bt_hci_cmd_state_set state; + struct net_buf *buf; + int err; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + + cp->filter_dup = filter_duplicates; + cp->enable = enable; + cp->duration = sys_cpu_to_le16(duration); + cp->period = 0; + + bt_hci_cmd_state_set_init(buf, &state, bt_dev.flags, BT_DEV_SCANNING, + enable == BT_HCI_LE_SCAN_ENABLE); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_EXT_SCAN_ENABLE, buf, NULL); + if (err) + { + return err; + } + + return 0; +} + +static int cmd_le_set_scan_enable_legacy(bool enable, bool filter_duplicates) +{ + struct bt_hci_cp_le_set_scan_enable *cp; + struct bt_hci_cmd_state_set state; + struct net_buf *buf; + int err; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + + cp->filter_dup = filter_duplicates; + cp->enable = enable; + + bt_hci_cmd_state_set_init(buf, &state, bt_dev.flags, BT_DEV_SCANNING, + enable == BT_HCI_LE_SCAN_ENABLE); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_SCAN_ENABLE, buf, NULL); + if (err) + { + return err; + } + + return 0; +} + +static int cmd_le_set_scan_enable(bool enable, bool filter_duplicates) +{ + if (IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features)) + { + return cmd_le_set_ext_scan_enable(enable, filter_duplicates, 0); + } + + return cmd_le_set_scan_enable_legacy(enable, filter_duplicates); +} + +int bt_le_scan_set_enable(uint8_t enable) +{ + return cmd_le_set_scan_enable(enable, scan_state.used_scan_param.options & + BT_LE_SCAN_OPT_FILTER_DUPLICATE); +} + +/* Select the scanning filter policy to request from the Controller. */ + +static uint8_t get_scan_filter_policy(uint8_t options) +{ + bool filter_accept_list = IS_ENABLED(CONFIG_BT_FILTER_ACCEPT_LIST) && + ((options & BT_LE_SCAN_OPT_FILTER_ACCEPT_LIST) != 0U); + + if (IS_ENABLED(CONFIG_BT_SCAN_EXT_FILTER_POLICY) && + ((options & BT_LE_SCAN_OPT_EXT_FILTER_POLICY) != 0U)) + { + return filter_accept_list ? + BT_HCI_LE_SCAN_FP_EXT_FILTER : BT_HCI_LE_SCAN_FP_EXT_NO_FILTER; + } + + return filter_accept_list ? + BT_HCI_LE_SCAN_FP_BASIC_FILTER : BT_HCI_LE_SCAN_FP_BASIC_NO_FILTER; +} + +static int start_le_scan_ext(struct bt_le_scan_param *scan_param) +{ + struct bt_hci_ext_scan_phy param_1m; + struct bt_hci_ext_scan_phy param_coded; + + struct bt_hci_ext_scan_phy *phy_1m = NULL; + struct bt_hci_ext_scan_phy *phy_coded = NULL; + + if (!(scan_param->options & BT_LE_SCAN_OPT_NO_1M)) + { + param_1m.type = scan_param->type; + param_1m.interval = sys_cpu_to_le16(scan_param->interval); + param_1m.window = sys_cpu_to_le16(scan_param->window); + + phy_1m = ¶m_1m; + } + + if (scan_param->options & BT_LE_SCAN_OPT_CODED) + { + uint16_t interval = + scan_param->interval_coded ? + scan_param->interval_coded : scan_param->interval; + uint16_t window = + scan_param->window_coded ? + scan_param->window_coded : scan_param->window; + + param_coded.type = scan_param->type; + param_coded.interval = sys_cpu_to_le16(interval); + param_coded.window = sys_cpu_to_le16(window); + phy_coded = ¶m_coded; + } + + struct bt_hci_cp_le_set_ext_scan_param *set_param; + struct net_buf *buf; + uint8_t own_addr_type; + bool active_scan; + int err; + + active_scan = (phy_1m && phy_1m->type == BT_HCI_LE_SCAN_ACTIVE) || + (phy_coded && phy_coded->type == BT_HCI_LE_SCAN_ACTIVE); + + if (scan_param->timeout > 0) + { + atomic_set_bit(bt_dev.flags, BT_DEV_SCAN_LIMITED); + + /* Allow bt_le_oob_get_local to be called directly before + * starting a scan limited by timeout. + */ + + if (IS_ENABLED(CONFIG_BT_PRIVACY) && !bt_id_rpa_is_new()) + { + atomic_clear_bit(bt_dev.flags, BT_DEV_RPA_VALID); + } + } + + err = bt_id_set_scan_own_addr(active_scan, &own_addr_type); + if (err) + { + return err; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + set_param = net_buf_add(buf, sizeof(*set_param)); + set_param->own_addr_type = own_addr_type; + set_param->phys = 0; + set_param->filter_policy = get_scan_filter_policy(scan_param->options); + + if (phy_1m) + { + set_param->phys |= BT_HCI_LE_EXT_SCAN_PHY_1M; + net_buf_add_mem(buf, phy_1m, sizeof(*phy_1m)); + } + + if (phy_coded) + { + set_param->phys |= BT_HCI_LE_EXT_SCAN_PHY_CODED; + net_buf_add_mem(buf, phy_coded, sizeof(*phy_coded)); + } + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_EXT_SCAN_PARAM, buf, NULL); + if (err) + { + return err; + } + + err = cmd_le_set_ext_scan_enable(BT_HCI_LE_SCAN_ENABLE, + scan_param->options & + BT_LE_SCAN_OPT_FILTER_DUPLICATE, + scan_param->timeout); + if (err) + { + return err; + } + + return 0; +} + +static int start_le_scan_legacy(struct bt_le_scan_param *param) +{ + struct bt_hci_cp_le_set_scan_param set_param; + struct net_buf *buf; + int err; + bool active_scan; + + (void)memset(&set_param, 0, sizeof(set_param)); + + set_param.scan_type = param->type; + + /* for the rest parameters apply default values according to + * spec 4.2, vol2, part E, 7.8.10 + */ + + set_param.interval = sys_cpu_to_le16(param->interval); + set_param.window = sys_cpu_to_le16(param->window); + + set_param.filter_policy = get_scan_filter_policy(param->options); + + active_scan = param->type == BT_HCI_LE_SCAN_ACTIVE; + err = bt_id_set_scan_own_addr(active_scan, &set_param.addr_type); + if (err) + { + return err; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + net_buf_add_mem(buf, &set_param, sizeof(set_param)); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_SCAN_PARAM, buf, NULL); + if (err) + { + return err; + } + + err = cmd_le_set_scan_enable(BT_HCI_LE_SCAN_ENABLE, + param->options & + BT_LE_SCAN_OPT_FILTER_DUPLICATE); + if (err) + { + return err; + } + + return 0; +} + +bool bt_le_scan_active_scanner_running(void) +{ + return atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING) && + scan_state.used_scan_param.type == BT_LE_SCAN_TYPE_ACTIVE; +} + +static void select_scan_params(struct bt_le_scan_param *scan_param) +{ + /* From high priority to low priority: select parameters */ + + /* 1. Priority: explicitly chosen parameters */ + + if (atomic_test_bit(scan_state.scan_flags, BT_LE_SCAN_USER_EXPLICIT_SCAN)) + { + memcpy(scan_param, &scan_state.explicit_scan_param, + sizeof(*scan_param)); + } + + /* Below this, the scanner module chooses the parameters. */ + + /* 2. Priority: reuse parameters from initiator */ + + else if (atomic_test_bit(bt_dev.flags, BT_DEV_INITIATING)) + { + *scan_param = (struct bt_le_scan_param) + { + .type = BT_LE_SCAN_TYPE_PASSIVE, + .options = BT_LE_SCAN_OPT_FILTER_DUPLICATE, + .interval = bt_dev.create_param.interval, + .window = bt_dev.create_param.window, + .timeout = 0, + .interval_coded = bt_dev.create_param.interval_coded, + .window_coded = bt_dev.create_param.window_coded, + }; + } + + /* 3. Priority: choose custom parameters */ + + else + { + *scan_param = (struct bt_le_scan_param) + { + .type = BT_LE_SCAN_TYPE_PASSIVE, + .options = BT_LE_SCAN_OPT_FILTER_DUPLICATE, + .interval = CONFIG_BT_BACKGROUND_SCAN_INTERVAL, + .window = CONFIG_BT_BACKGROUND_SCAN_WINDOW, + .timeout = 0, + .interval_coded = 0, + .window_coded = 0, + }; + + if (BT_FEAT_LE_PHY_CODED(bt_dev.le.features)) + { + scan_param->options |= BT_LE_SCAN_OPT_CODED; + } + + if (atomic_test_bit(scan_state.scan_flags, BT_LE_SCAN_USER_PER_SYNC) || + atomic_test_bit(scan_state.scan_flags, BT_LE_SCAN_USER_CONN)) + { + scan_param->window = BT_GAP_SCAN_FAST_WINDOW; + scan_param->interval = BT_GAP_SCAN_FAST_INTERVAL; + } + } +} + +static int start_scan(struct bt_le_scan_param *scan_param) +{ + if (IS_ENABLED(CONFIG_BT_SMP) && atomic_test_bit(bt_dev.flags, + BT_DEV_ID_PENDING)) + { + bt_id_pending_keys_update(); + } + + if (IS_ENABLED(CONFIG_BT_EXT_ADV) && + BT_DEV_FEAT_LE_EXT_ADV(bt_dev.le.features)) + { + return start_le_scan_ext(scan_param); + } + + return start_le_scan_legacy(scan_param); +} + +static bool is_already_using_same_params(struct bt_le_scan_param *scan_param) +{ + return !memcmp(scan_param, &scan_state.used_scan_param, + sizeof(*scan_param)); +} + +static enum scan_action get_scan_action(struct bt_le_scan_param *scan_param) +{ + bool is_scanning = atomic_test_bit(bt_dev.flags, BT_DEV_SCANNING); + + /* Check if there is reason to have the scanner running */ + + if (atomic_read(scan_state.scan_flags) != 0) + { + if (is_scanning) + { + if (is_already_using_same_params(scan_param)) + { + /* Already scanning with the desired parameters */ + + return SCAN_ACTION_NONE; + } + else + { + return SCAN_ACTION_UPDATE; + } + } + else + { + return SCAN_ACTION_START; + } + } + else + { + /* Scanner should not run */ + + if (is_scanning) + { + return SCAN_ACTION_STOP; + } + else + { + return SCAN_ACTION_NONE; + } + } +} + +static int scan_update(void) +{ + int32_t err; + + struct bt_le_scan_param scan_param; + + /* Prevent partial updates of the scanner state. */ + + err = nxrmutex_trylock(&scan_state.scan_update_mutex); + + if (err) + { + return -EBUSY; + } + + select_scan_params(&scan_param); + + enum scan_action action = get_scan_action(&scan_param); + + /* start/stop/update if required and allowed */ + + switch (action) + { + case SCAN_ACTION_NONE: + break; + case SCAN_ACTION_STOP: + err = cmd_le_set_scan_enable(BT_HCI_LE_SCAN_DISABLE, + BT_HCI_LE_SCAN_FILTER_DUP_DISABLE); + if (err) + { + LOG_DBG("Could not stop scanner: %d", err); + break; + } + + memset(&scan_state.used_scan_param, 0x0, + sizeof(scan_state.used_scan_param)); + break; + case SCAN_ACTION_UPDATE: + err = cmd_le_set_scan_enable(BT_HCI_LE_SCAN_DISABLE, + BT_HCI_LE_SCAN_FILTER_DUP_DISABLE); + if (err) + { + LOG_DBG("Could not stop scanner to update: %d", err); + break; + } + + __fallthrough; + case SCAN_ACTION_START: + err = start_scan(&scan_param); + if (err) + { + LOG_DBG("Could not start scanner: %d", err); + break; + } + + memcpy(&scan_state.used_scan_param, &scan_param, sizeof(scan_param)); + break; + } + + nxrmutex_unlock(&scan_state.scan_update_mutex); + + return err; +} + +static int scan_check_if_state_allowed(enum bt_le_scan_user flag) +{ + /* check if state is already set */ + + if (atomic_test_bit(scan_state.scan_flags, flag)) + { + return -EALREADY; + } + + if (flag == BT_LE_SCAN_USER_EXPLICIT_SCAN && + !BT_LE_STATES_SCAN_INIT(bt_dev.le.states) && + atomic_test_bit(bt_dev.flags, BT_DEV_INITIATING)) + { + return -EPERM; + } + + return 0; +} + +int bt_le_scan_user_add(enum bt_le_scan_user flag) +{ + uint32_t err; + + if (flag == BT_LE_SCAN_USER_NONE) + { + /* Only check if the scanner parameters should be updated / the scanner + * should be started. This is mainly triggered once connections are + * established. + */ + + return scan_update(); + } + + err = scan_check_if_state_allowed(flag); + if (err) + { + return err; + } + + atomic_set_bit(scan_state.scan_flags, flag); + + err = scan_update(); + if (err) + { + atomic_clear_bit(scan_state.scan_flags, flag); + } + + return err; +} + +int bt_le_scan_user_remove(enum bt_le_scan_user flag) +{ + if (flag == BT_LE_SCAN_USER_NONE) + { + /* Only check if the scanner parameters should be updated / the scanner + * should be started. This is mainly triggered once connections are + * established. + */ + } + else + { + atomic_clear_bit(scan_state.scan_flags, flag); + } + + return scan_update(); +} + +static void check_pending_conn( + const bt_addr_le_t *id_addr, const bt_addr_le_t *addr, uint8_t adv_props) +{ + struct bt_conn *conn; + int err; + + /* No connections are allowed during explicit scanning + * when the controller does not support concurrent scanning and initiating. + */ + + if (!BT_LE_STATES_SCAN_INIT(bt_dev.le.states) && + atomic_test_bit(scan_state.scan_flags, BT_LE_SCAN_USER_EXPLICIT_SCAN)) + { + return; + } + + /* Return if event is not connectable */ + + if (!(adv_props & BT_HCI_LE_ADV_EVT_TYPE_CONN)) + { + return; + } + + conn = bt_conn_lookup_state_le(BT_ID_DEFAULT, id_addr, + BT_CONN_SCAN_BEFORE_INITIATING); + if (!conn) + { + return; + } + + /* Stop the scanner if there is no other reason to have it running. + * Ignore possible failures here, since the user is guaranteed to be + * removed and the scanner state is updated once the initiator starts / + * stops. + */ + + err = bt_le_scan_user_remove(BT_LE_SCAN_USER_CONN); + if (err) + { + LOG_DBG("Error while removing conn user from scanner (%d)", err); + } + + bt_addr_le_copy(&conn->le.resp_addr, addr); + if (bt_le_create_conn(conn)) + { + goto failed; + } + + bt_conn_set_state(conn, BT_CONN_INITIATING); + bt_conn_unref(conn); + return; + +failed: + conn->err = BT_HCI_ERR_UNSPECIFIED; + bt_conn_set_state(conn, BT_CONN_DISCONNECTED); + bt_conn_unref(conn); + + /* Just a best-effort check if the scanner should be started. */ + + err = bt_le_scan_user_remove(BT_LE_SCAN_USER_NONE); + + if (err) + { + LOG_WRN("Error while updating the scanner (%d)", err); + } +} + +/* Convert Legacy adv report evt_type field to adv props */ + +static uint8_t get_adv_props_legacy(uint8_t evt_type) +{ + switch (evt_type) + { + case BT_GAP_ADV_TYPE_ADV_IND: + return BT_GAP_ADV_PROP_CONNECTABLE | BT_GAP_ADV_PROP_SCANNABLE; + + case BT_GAP_ADV_TYPE_ADV_DIRECT_IND: + return BT_GAP_ADV_PROP_CONNECTABLE | BT_GAP_ADV_PROP_DIRECTED; + + case BT_GAP_ADV_TYPE_ADV_SCAN_IND: + return BT_GAP_ADV_PROP_SCANNABLE; + + case BT_GAP_ADV_TYPE_ADV_NONCONN_IND: + return 0; + + /* In legacy advertising report, we don't know if the scan + * response come from a connectable advertiser, so don't set + * connectable property bit. + */ + + case BT_GAP_ADV_TYPE_SCAN_RSP: + return BT_GAP_ADV_PROP_SCAN_RESPONSE | BT_GAP_ADV_PROP_SCANNABLE; + + default: + return 0; + } +} + +#if defined(CONFIG_BT_EXT_ADV) || defined(CONFIG_BT_SCAN_EXT_FILTER_POLICY) +/* Copy the target address of a directed advertisement out of an HCI event, + * converting an identity address to a regular one. + * + * BT_ADDR_LE_UNRESOLVED has the identity address bit set, so it has to be + * excluded before calling bt_addr_le_is_resolved(), which only inspects that + * bit. + */ + +static void copy_hci_target_addr(bt_addr_le_t *dst, + const bt_addr_le_t *hci_addr) +{ + if (hci_addr->type != BT_ADDR_LE_UNRESOLVED && + bt_addr_le_is_resolved(hci_addr)) + { + bt_addr_le_copy_resolved(dst, hci_addr); + } + else + { + bt_addr_le_copy(dst, hci_addr); + } +} +#endif /* CONFIG_BT_EXT_ADV || CONFIG_BT_SCAN_EXT_FILTER_POLICY */ + +/* Fill in the fields shared by the reports of the legacy scanning commands. + * Reports carrying a target address set it after this. + */ + +static void create_legacy_adv_info(uint8_t evt_type, + int8_t rssi, + struct bt_le_scan_recv_info *const scan_info) +{ + scan_info->primary_phy = BT_GAP_LE_PHY_1M; + scan_info->secondary_phy = 0; + scan_info->tx_power = BT_GAP_TX_POWER_INVALID; + scan_info->rssi = rssi; + scan_info->sid = BT_GAP_SID_INVALID; + scan_info->interval = 0U; + scan_info->adv_type = evt_type; + scan_info->adv_props = get_adv_props_legacy(evt_type); + scan_info->direct_addr = NULL; +} + +static void le_adv_recv( + bt_addr_le_t *addr, struct bt_le_scan_recv_info *info, + struct net_buf_simple *buf, uint16_t len) +{ + struct bt_le_scan_cb *listener; + struct bt_le_scan_cb *next; + struct net_buf_simple_state state; + bt_addr_le_t id_addr; + bool explicit_scan = atomic_test_bit(scan_state.scan_flags, + BT_LE_SCAN_USER_EXPLICIT_SCAN); + bool conn_scan = atomic_test_bit(scan_state.scan_flags, + BT_LE_SCAN_USER_CONN); + /* A directed advertisement whose target address the Controller could not + * resolve is by construction not addressed to the local identity, so it + * cannot disclose it. It is only reported when the application asked for + * it with BT_LE_SCAN_OPT_EXT_FILTER_POLICY. + */ + + bool unresolved_directed = + info->direct_addr != NULL && + info->direct_addr->type == BT_ADDR_LE_UNRESOLVED; + + LOG_DBG("%s%s event %u, len %u, rssi %d dBm", bt_addr_le_str(addr), + bt_addr_le_is_resolved(addr) ? " (resolved)" : "", + info->adv_type, + len, + info->rssi); + + if (!IS_ENABLED(CONFIG_BT_PRIVACY) && + !IS_ENABLED(CONFIG_BT_SCAN_WITH_IDENTITY) && + explicit_scan && (info->adv_props & BT_GAP_ADV_PROP_DIRECTED) != 0U && + !unresolved_directed) + { + LOG_DBG("Dropped direct adv report"); + return; + } + + if (bt_addr_le_is_resolved(addr)) + { + bt_addr_le_copy_resolved(&id_addr, addr); + } + else if (addr->type == BT_HCI_PEER_ADDR_ANONYMOUS) + { + bt_addr_le_copy(&id_addr, BT_ADDR_LE_ANY); + } + else + { + bt_addr_le_copy(&id_addr, bt_lookup_id_addr(BT_ID_DEFAULT, addr)); + } + + /* For connection-purpose scanning, + * skip app callbacks but allow pending-conn check logic. + */ + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && !explicit_scan && conn_scan) + { + goto check_pending_conn; + } + + if (scan_dev_found_cb) + { + net_buf_simple_save(buf, &state); + + buf->len = len; + scan_dev_found_cb(&id_addr, info->rssi, info->adv_type, buf); + + net_buf_simple_restore(buf, &state); + } + + info->addr = &id_addr; + + list_for_every_container_safe(&scan_cbs, listener, next, node) + { + if (listener->recv) + { + net_buf_simple_save(buf, &state); + + buf->len = len; + listener->recv(info, buf); + + net_buf_simple_restore(buf, &state); + } + } + + /* Clear pointer to this stack frame before returning to calling function */ + + info->addr = NULL; + +check_pending_conn: + if (IS_ENABLED(CONFIG_BT_CENTRAL)) + { + check_pending_conn(&id_addr, addr, info->adv_props); + } +} + +#if defined(CONFIG_BT_EXT_ADV) +void bt_hci_le_scan_timeout(struct net_buf *buf) +{ + struct bt_le_scan_cb *listener; + struct bt_le_scan_cb *next; + + int err = bt_le_scan_user_remove(BT_LE_SCAN_USER_EXPLICIT_SCAN); + + if (err) + { + sched_yield(); + err = bt_le_scan_user_remove(BT_LE_SCAN_USER_EXPLICIT_SCAN); + } + + if (err) + { + LOG_WRN("Could not stop the explicit scanner (%d)", err); + } + + atomic_clear_bit(bt_dev.flags, BT_DEV_SCAN_LIMITED); + atomic_clear_bit(bt_dev.flags, BT_DEV_RPA_VALID); + +#if defined(CONFIG_BT_SMP) + bt_id_pending_keys_update(); +#endif + + list_for_every_container_safe(&scan_cbs, listener, next, node) + { + if (listener->timeout) + { + listener->timeout(); + } + } +} + +/* Convert Extended adv report evt_type field into adv type */ + +static uint8_t get_adv_type(uint8_t evt_type) +{ + switch (evt_type) + { + case (BT_HCI_LE_ADV_EVT_TYPE_CONN | BT_HCI_LE_ADV_EVT_TYPE_SCAN | + BT_HCI_LE_ADV_EVT_TYPE_LEGACY): + return BT_GAP_ADV_TYPE_ADV_IND; + + case (BT_HCI_LE_ADV_EVT_TYPE_CONN | BT_HCI_LE_ADV_EVT_TYPE_DIRECT | + BT_HCI_LE_ADV_EVT_TYPE_LEGACY): + return BT_GAP_ADV_TYPE_ADV_DIRECT_IND; + + case (BT_HCI_LE_ADV_EVT_TYPE_SCAN | BT_HCI_LE_ADV_EVT_TYPE_LEGACY): + return BT_GAP_ADV_TYPE_ADV_SCAN_IND; + + case BT_HCI_LE_ADV_EVT_TYPE_LEGACY: + return BT_GAP_ADV_TYPE_ADV_NONCONN_IND; + + case (BT_HCI_LE_ADV_EVT_TYPE_SCAN_RSP | BT_HCI_LE_ADV_EVT_TYPE_CONN | + BT_HCI_LE_ADV_EVT_TYPE_SCAN | BT_HCI_LE_ADV_EVT_TYPE_LEGACY): + case (BT_HCI_LE_ADV_EVT_TYPE_SCAN_RSP | BT_HCI_LE_ADV_EVT_TYPE_SCAN | + BT_HCI_LE_ADV_EVT_TYPE_LEGACY): + + /* Scan response from connectable or non-connectable advertiser. */ + + return BT_GAP_ADV_TYPE_SCAN_RSP; + + default: + return BT_GAP_ADV_TYPE_EXT_ADV; + } +} + +/* Convert Extended adv report PHY to GAP PHY */ + +static uint8_t get_ext_adv_coding_sel_phy(uint8_t hci_phy) +{ + /* Converts from Extended adv report PHY to BT_GAP_LE_PHY_* + * When Advertising Coding Selection (Host Support) is enabled the + * controller will return the advertising coding scheme which can be S=2 or + * S=8 data coding. + */ + + switch (hci_phy) + { + case BT_HCI_LE_ADV_EVT_PHY_1M: + return BT_GAP_LE_PHY_1M; + case BT_HCI_LE_ADV_EVT_PHY_2M: + return BT_GAP_LE_PHY_2M; + case BT_HCI_LE_ADV_EVT_PHY_CODED_S8: + return BT_GAP_LE_PHY_CODED_S8; + case BT_HCI_LE_ADV_EVT_PHY_CODED_S2: + return BT_GAP_LE_PHY_CODED_S2; + default: + return 0; + } +} + +/* Convert extended adv report evt_type field to adv props */ + +static uint16_t get_adv_props_extended(uint16_t evt_type) +{ + /* Converts from BT_HCI_LE_ADV_EVT_TYPE_* to BT_GAP_ADV_PROP_* + * The first 4 bits are the same (conn, scan, direct, scan_rsp). Bit 4 must + * be flipped as the meaning of 1 is opposite (legacy -> extended) The rest + * of the bits are zeroed out. + */ + + return (evt_type ^ BT_HCI_LE_ADV_EVT_TYPE_LEGACY) & BIT_MASK(5); +} + +static void create_ext_adv_info( + struct bt_hci_evt_le_ext_advertising_info const *const evt, + struct bt_le_scan_recv_info *const scan_info, + bt_addr_le_t *const direct_addr) +{ + uint16_t evt_type = sys_le16_to_cpu(evt->evt_type); + + if (IS_ENABLED(CONFIG_BT_EXT_ADV_CODING_SELECTION) && + BT_FEAT_LE_ADV_CODING_SEL(bt_dev.le.features)) + { + scan_info->primary_phy = get_ext_adv_coding_sel_phy(evt->prim_phy); + scan_info->secondary_phy = get_ext_adv_coding_sel_phy(evt->sec_phy); + } + else + { + scan_info->primary_phy = bt_get_phy(evt->prim_phy); + scan_info->secondary_phy = bt_get_phy(evt->sec_phy); + } + + scan_info->tx_power = evt->tx_power; + scan_info->rssi = evt->rssi; + scan_info->sid = evt->sid; + scan_info->interval = sys_le16_to_cpu(evt->interval); + scan_info->adv_type = get_adv_type(evt_type); + scan_info->adv_props = get_adv_props_extended(evt_type); + + if ((evt_type & BT_HCI_LE_ADV_EVT_TYPE_DIRECT) != 0U) + { + copy_hci_target_addr(direct_addr, &evt->direct_addr); + scan_info->direct_addr = direct_addr; + } + else + { + scan_info->direct_addr = NULL; + } +} + +void bt_hci_le_adv_ext_report(struct net_buf *buf) +{ + uint8_t num_reports = net_buf_pull_u8(buf); + bool explicit_scan = atomic_test_bit(scan_state.scan_flags, + BT_LE_SCAN_USER_EXPLICIT_SCAN); + bool conn_scan = atomic_test_bit(scan_state.scan_flags, + BT_LE_SCAN_USER_CONN); + + LOG_DBG("Adv number of reports %u", num_reports); + + while (num_reports--) + { + struct bt_hci_evt_le_ext_advertising_info *evt; + struct bt_le_scan_recv_info scan_info; + bt_addr_le_t direct_addr; + uint16_t data_status; + uint16_t evt_type; + bool is_report_complete; + bool more_to_come; + bool is_new_advertiser; + + if (!explicit_scan) + { + /* The application has not requested explicit scan, so it is not + * expecting advertising reports. Discard, and reset the + * reassembler if not inactive This is done in the loop as this + * flag can change between each iteration, and it is not uncommon + * that scanning is disabled in the callback called from + * le_adv_recv. + * + * However, if scanning is running for connection purposes, the + * report shall still be processed to allow pending connections. + */ + + if (reassembling_advertiser.state != FRAG_ADV_INACTIVE) + { + reset_reassembling_advertiser(); + } + + if (!conn_scan) + { + break; + } + } + + if (buf->len < sizeof(*evt)) + { + LOG_ERR("Unexpected end of buffer"); + break; + } + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + evt_type = sys_le16_to_cpu(evt->evt_type); + data_status = BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS(evt_type); + is_report_complete = + data_status == BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS_COMPLETE; + more_to_come = + data_status == BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS_PARTIAL; + + if (evt->length > buf->len) + { + LOG_WRN("Adv report corrupted (wants %u out of %u)", evt->length, + buf->len); + + net_buf_reset(buf); + + if (evt_type & BT_HCI_LE_ADV_EVT_TYPE_LEGACY) + { + return; + } + + /* Start discarding irrespective of the `more_to_come` flag. We + * assume we may have lost a partial adv report in the truncated + * data. + */ + + reassembling_advertiser.state = FRAG_ADV_DISCARDING; + + return; + } + + if (evt_type & BT_HCI_LE_ADV_EVT_TYPE_LEGACY) + { + /* Legacy advertising reports are complete. + * Create event immediately. + */ + + create_ext_adv_info(evt, &scan_info, &direct_addr); + le_adv_recv(&evt->addr, &scan_info, &buf->b, evt->length); + goto cont; + } + + is_new_advertiser = + reassembling_advertiser.state == FRAG_ADV_INACTIVE || + !fragmented_advertisers_equal(&reassembling_advertiser, + &evt->addr, evt->sid); + + if (is_new_advertiser && is_report_complete) + { + /* Only advertising report from this advertiser. + * Create event immediately. + */ + + create_ext_adv_info(evt, &scan_info, &direct_addr); + le_adv_recv(&evt->addr, &scan_info, &buf->b, evt->length); + goto cont; + } + + if (is_new_advertiser && + reassembling_advertiser.state == FRAG_ADV_REASSEMBLING) + { + LOG_WRN("Received an incomplete advertising report while " + "reassembling " + "advertising reports from a different advertiser. The " + "advertising " + "report is discarded and future scan results may be " + "incomplete. " + "Interleaving of fragmented advertising reports from " + "different " + "advertisers is not yet supported."); + goto cont; + } + + if (data_status == BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS_INCOMPLETE) + { + /* Got HCI_LE_Extended_Advertising_Report: Incomplete, data + * truncated, no more to come. This means the Controller is + * aborting the reassembly. We discard the partially received + * report, and the application is not notified. + * + * See the Controller's documentation for possible reasons for + * aborting. Hint: CONFIG_BT_CTLR_SCAN_DATA_LEN_MAX. + */ + + LOG_DBG("Discarding incomplete advertisement."); + reset_reassembling_advertiser(); + goto cont; + } + + if (is_new_advertiser) + { + /* We are not reassembling reports from an advertiser and + * this is the first report from the new advertiser. Initialize the + * new advertiser. + */ + + if (reassembling_advertiser.state == FRAG_ADV_DISCARDING) + { + /* Previous chain was abandoned (reassembly timeout). */ + + reset_reassembling_advertiser(); + } + + DEBUGASSERT(reassembling_advertiser.state == FRAG_ADV_INACTIVE); + init_reassembling_advertiser(&evt->addr, evt->sid); + } + + if (evt->length + ext_scan_buf.len > ext_scan_buf.size) + { + /* The report does not fit in the reassembly buffer + * Discard this and future reports from the advertiser. + */ + + reassembling_advertiser.state = FRAG_ADV_DISCARDING; + } + + if (reassembling_advertiser.state == FRAG_ADV_DISCARDING) + { + if (!more_to_come) + { + /* We do no longer need to keep track of this advertiser as + * all the expected data is received. + */ + + reset_reassembling_advertiser(); + } + + goto cont; + } + + net_buf_simple_add_mem(&ext_scan_buf, buf->data, evt->length); + if (more_to_come) + { + /* The controller will send additional reports to be reassembled */ + + reassembly_timeout_work_reschedule(); + continue; + } + + /* No more data coming from the controller. + * Create event. + */ + + DEBUGASSERT(is_report_complete); + create_ext_adv_info(evt, &scan_info, &direct_addr); + le_adv_recv(&evt->addr, &scan_info, &ext_scan_buf, ext_scan_buf.len); + + if (IS_ENABLED(CONFIG_BT_TESTING)) + { + bt_testing_trace_ext_adv_reassembly_complete(); + } + + /* We do no longer need to keep track of this advertiser. */ + + reset_reassembling_advertiser(); + +cont: + net_buf_pull(buf, evt->length); + } +} + +#if defined(CONFIG_BT_PER_ADV_SYNC) +static void per_adv_sync_delete(struct bt_le_per_adv_sync *per_adv_sync) +{ + atomic_set(per_adv_sync->flags, 0); +} + +static struct bt_le_per_adv_sync *per_adv_sync_new(void) +{ + struct bt_le_per_adv_sync *per_adv_sync = NULL; + + for (int i = 0; i < ARRAY_SIZE(per_adv_sync_pool); i++) + { + if (!atomic_test_bit(per_adv_sync_pool[i].flags, + BT_PER_ADV_SYNC_CREATED)) + { + per_adv_sync = &per_adv_sync_pool[i]; + break; + } + } + + if (!per_adv_sync) + { + return NULL; + } + + (void)memset(per_adv_sync, 0, sizeof(*per_adv_sync)); + atomic_set_bit(per_adv_sync->flags, BT_PER_ADV_SYNC_CREATED); + +#if CONFIG_BT_PER_ADV_SYNC_BUF_SIZE > 0 + net_buf_simple_init_with_data(&per_adv_sync->reassembly, + per_adv_sync->reassembly_data, + CONFIG_BT_PER_ADV_SYNC_BUF_SIZE); + net_buf_simple_reset(&per_adv_sync->reassembly); +#endif /* CONFIG_BT_PER_ADV_SYNC_BUF_SIZE > 0 */ + + return per_adv_sync; +} + +static struct bt_le_per_adv_sync *get_pending_per_adv_sync(void) +{ + for (int i = 0; i < ARRAY_SIZE(per_adv_sync_pool); i++) + { + if (atomic_test_bit(per_adv_sync_pool[i].flags, + BT_PER_ADV_SYNC_SYNCING)) + { + return &per_adv_sync_pool[i]; + } + } + + return NULL; +} + +void bt_periodic_sync_disable(void) +{ + for (size_t i = 0; i < ARRAY_SIZE(per_adv_sync_pool); i++) + { + per_adv_sync_delete(&per_adv_sync_pool[i]); + } +} + +struct bt_le_per_adv_sync *bt_hci_per_adv_sync_lookup_handle(uint16_t handle) +{ + for (int i = 0; i < ARRAY_SIZE(per_adv_sync_pool); i++) + { + if (per_adv_sync_pool[i].handle == handle && + atomic_test_bit(per_adv_sync_pool[i].flags, + BT_PER_ADV_SYNC_SYNCED)) + { + return &per_adv_sync_pool[i]; + } + } + + return NULL; +} + +void bt_hci_le_per_adv_report_recv(struct bt_le_per_adv_sync *per_adv_sync, + struct net_buf_simple *buf, + const struct bt_le_per_adv_sync_recv_info *info) +{ + struct net_buf_simple_state state; + struct bt_le_per_adv_sync_cb *listener; + struct bt_le_per_adv_sync_cb *tmp; + + list_for_every_container_safe(&pa_sync_cbs, listener, tmp, node) + { + if (listener->recv) + { + net_buf_simple_save(buf, &state); + listener->recv(per_adv_sync, info, buf); + net_buf_simple_restore(buf, &state); + } + } +} + +#if defined(CONFIG_BT_PER_ADV_SYNC_RSP) && (CONFIG_BT_PER_ADV_SYNC_BUF_SIZE > 0) +static void bt_hci_le_per_adv_report_recv_failure( + struct bt_le_per_adv_sync *per_adv_sync, + const struct bt_le_per_adv_sync_recv_info *info) +{ + struct bt_le_per_adv_sync_cb *listener; + struct bt_le_per_adv_sync_cb *tmp; + + list_for_every_container_safe(&pa_sync_cbs, listener, tmp, node) + { + if (listener->recv) + { + listener->recv(per_adv_sync, info, NULL); + } + } +} +#endif /* defined(CONFIG_BT_PER_ADV_SYNC_RSP) && (CONFIG_BT_PER_ADV_SYNC_BUF_SIZE > 0) */ + +static void bt_hci_le_per_adv_report_common(struct net_buf *buf) +{ +#if defined(CONFIG_BT_PER_ADV_SYNC_RSP) + struct bt_hci_evt_le_per_advertising_report_v2 *evt; +#else + struct bt_hci_evt_le_per_advertising_report *evt; +#endif /* defined(CONFIG_BT_PER_ADV_SYNC_RSP) */ + + struct bt_le_per_adv_sync *per_adv_sync; + struct bt_le_per_adv_sync_recv_info info; + + if (buf->len < sizeof(*evt)) + { + LOG_ERR("Unexpected end of buffer"); + return; + } + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + + per_adv_sync = + bt_hci_per_adv_sync_lookup_handle(sys_le16_to_cpu(evt->handle)); + + if (!per_adv_sync) + { + LOG_ERR("Unknown handle 0x%04X for periodic advertising report", + sys_le16_to_cpu(evt->handle)); + return; + } + + if (atomic_test_bit(per_adv_sync->flags, BT_PER_ADV_SYNC_RECV_DISABLED)) + { + LOG_ERR("Received PA adv report when receive disabled"); + return; + } + + info.tx_power = evt->tx_power; + info.rssi = evt->rssi; + info.cte_type = bt_get_df_cte_type(evt->cte_type); + info.addr = &per_adv_sync->addr; + info.sid = per_adv_sync->sid; + +#if defined(CONFIG_BT_PER_ADV_SYNC_RSP) + info.periodic_event_counter = sys_le16_to_cpu(evt->periodic_event_counter); + info.subevent = evt->subevent; +#endif /* CONFIG_BT_PER_ADV_SYNC_RSP */ + + if (!per_adv_sync->report_truncated) + { +#if CONFIG_BT_PER_ADV_SYNC_BUF_SIZE > 0 + if (evt->length > buf->len) + { + /* The event does not carry the data it claims. Drop the report */ + + LOG_WRN("Periodic adv report corrupted (wants %u out of %u)", + evt->length, buf->len); + + per_adv_sync->report_truncated = true; + net_buf_simple_reset(&per_adv_sync->reassembly); + return; + } + + if (net_buf_simple_tailroom(&per_adv_sync->reassembly) < evt->length) + { + /* The buffer is too small for the entire report. Drop it */ + + LOG_WRN("Buffer is too small to reassemble the report. " + "Use CONFIG_BT_PER_ADV_SYNC_BUF_SIZE to change " + "the buffer size."); + + per_adv_sync->report_truncated = true; + net_buf_simple_reset(&per_adv_sync->reassembly); + return; + } + + if (evt->data_status == BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS_COMPLETE) + { + if (per_adv_sync->reassembly.len == 0) + { + /* We have not received any partial data before. + * This buffer can be forwarded without an extra copy. + */ + + bt_hci_le_per_adv_report_recv(per_adv_sync, &buf->b, &info); + } + else + { + net_buf_simple_add_mem(&per_adv_sync->reassembly, buf->data, + evt->length); + bt_hci_le_per_adv_report_recv(per_adv_sync, + &per_adv_sync->reassembly, &info); + net_buf_simple_reset(&per_adv_sync->reassembly); + } + } + else if (evt->data_status == + BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS_INCOMPLETE) + { + LOG_DBG("Received incomplete advertising data. " + "Advertising report dropped."); + + net_buf_simple_reset(&per_adv_sync->reassembly); + } + else if (evt->data_status == + BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS_PARTIAL) + { + net_buf_simple_add_mem(&per_adv_sync->reassembly, buf->data, + evt->length); +#if defined(CONFIG_BT_PER_ADV_SYNC_RSP) + } + else if (evt->data_status == + BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS_RX_FAILED && + per_adv_sync->num_subevents) + { + bt_hci_le_per_adv_report_recv_failure(per_adv_sync, &info); +#endif /* CONFIG_BT_PER_ADV_SYNC_RSP */ + } + else + { + DEBUGASSERT(false); + } +#else /* CONFIG_BT_PER_ADV_SYNC_BUF_SIZE > 0 */ + if (evt->data_status == BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS_COMPLETE) + { + bt_hci_le_per_adv_report_recv(per_adv_sync, &buf->b, &info); + } + else + { + per_adv_sync->report_truncated = true; + } +#endif /* CONFIG_BT_PER_ADV_SYNC_BUF_SIZE > 0 */ + } + else if (evt->data_status == BT_HCI_LE_ADV_EVT_TYPE_DATA_STATUS_COMPLETE) + { + per_adv_sync->report_truncated = false; + } +} + +void bt_hci_le_per_adv_report(struct net_buf *buf) +{ + if (IS_ENABLED(CONFIG_BT_PER_ADV_SYNC_RSP)) + { + LOG_ERR("The controller shall raise the latest unmasked version of " + "the " + "event"); + + return; + } + + bt_hci_le_per_adv_report_common(buf); +} + +static int per_adv_sync_terminate(uint16_t handle) +{ + struct bt_hci_cp_le_per_adv_terminate_sync *cp; + struct net_buf *buf; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + (void)memset(cp, 0, sizeof(*cp)); + + cp->handle = sys_cpu_to_le16(handle); + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_PER_ADV_TERMINATE_SYNC, buf, + NULL); +} + +static void per_adv_sync_terminated(struct bt_le_per_adv_sync *per_adv_sync, + uint8_t reason) +{ + /* Terminate the PA sync and notify app */ + + const struct bt_le_per_adv_sync_term_info term_info = + { + .addr = &per_adv_sync->addr, + .sid = per_adv_sync->sid, + .reason = reason, + }; + + struct bt_le_per_adv_sync_cb *listener; + struct bt_le_per_adv_sync_cb *tmp; + + /* Deleting before callback, so the caller will be able + * to restart sync in the callback. + */ + + per_adv_sync_delete(per_adv_sync); + + list_for_every_container_safe(&pa_sync_cbs, listener, tmp, node) + { + if (listener->term) + { + listener->term(per_adv_sync, &term_info); + } + } +} + +static void bt_hci_le_per_adv_sync_established_common(struct net_buf *buf) +{ +#if defined(CONFIG_BT_PER_ADV_SYNC_RSP) + struct bt_hci_evt_le_per_adv_sync_established_v2 *evt = + (struct bt_hci_evt_le_per_adv_sync_established_v2 *)buf->data; +#else + struct bt_hci_evt_le_per_adv_sync_established *evt = + (struct bt_hci_evt_le_per_adv_sync_established *)buf->data; +#endif /* defined(CONFIG_BT_PER_ADV_SYNC_RSP) */ + + struct bt_le_per_adv_sync_synced_info sync_info; + struct bt_le_per_adv_sync *pending_per_adv_sync; + struct bt_le_per_adv_sync_cb *listener; + struct bt_le_per_adv_sync_cb *tmp; + bt_addr_le_t pending_id_addr; + bt_addr_le_t id_addr; + bool unexpected_evt; + int err; + + pending_per_adv_sync = get_pending_per_adv_sync(); + + if (pending_per_adv_sync) + { + atomic_clear_bit(pending_per_adv_sync->flags, BT_PER_ADV_SYNC_SYNCING); + err = bt_le_scan_user_remove(BT_LE_SCAN_USER_PER_SYNC); + + if (err) + { + LOG_ERR("Could not update scan (%d)", err); + } + } + + if (evt->status == BT_HCI_ERR_OP_CANCELLED_BY_HOST) + { + /* Cancelled locally, don't call CB */ + + if (pending_per_adv_sync) + { + per_adv_sync_delete(pending_per_adv_sync); + } + else + { + LOG_ERR("Unexpected per adv sync cancelled event"); + } + + return; + } + + if (bt_addr_le_is_resolved(&evt->adv_addr)) + { + bt_addr_le_copy_resolved(&id_addr, &evt->adv_addr); + } + else + { + bt_addr_le_copy(&id_addr, bt_lookup_id_addr(BT_ID_DEFAULT, + &evt->adv_addr)); + } + + if (pending_per_adv_sync != NULL) + { + bt_addr_le_t *addr_to_check = &pending_per_adv_sync->addr; + + if (bt_addr_le_is_resolved(addr_to_check)) + { + bt_addr_le_copy_resolved(&pending_id_addr, addr_to_check); + } + else + { + bt_addr_le_copy(&pending_id_addr, bt_lookup_id_addr(BT_ID_DEFAULT, + addr_to_check)); + } + } + + if (!pending_per_adv_sync || + (!atomic_test_bit(pending_per_adv_sync->flags, + BT_PER_ADV_SYNC_SYNCING_USE_LIST) && + ((pending_per_adv_sync->sid != evt->sid) || + !bt_addr_le_eq(&pending_id_addr, &id_addr)))) + { + LOG_ERR("Unexpected per adv sync established event"); + + /* Request terminate of pending periodic advertising in controller */ + + per_adv_sync_terminate(sys_le16_to_cpu(evt->handle)); + + unexpected_evt = true; + } + else + { + unexpected_evt = false; + } + + if (unexpected_evt || evt->status != BT_HCI_ERR_SUCCESS) + { + if (pending_per_adv_sync) + { + const uint8_t reason = + unexpected_evt ? BT_HCI_ERR_UNSPECIFIED : evt->status; + + if (atomic_test_bit(pending_per_adv_sync->flags, + BT_PER_ADV_SYNC_SYNCING_USE_LIST)) + { + /* Update the addr and sid for the callback + * Already set if not using the sync list + */ + + bt_addr_le_copy(&pending_per_adv_sync->addr, &id_addr); + pending_per_adv_sync->sid = evt->sid; + } + + per_adv_sync_terminated(pending_per_adv_sync, reason); + } + + return; + } + + pending_per_adv_sync->report_truncated = false; + + atomic_set_bit(pending_per_adv_sync->flags, BT_PER_ADV_SYNC_SYNCED); + + pending_per_adv_sync->handle = sys_le16_to_cpu(evt->handle); + pending_per_adv_sync->interval = sys_le16_to_cpu(evt->interval); + pending_per_adv_sync->clock_accuracy = + sys_le16_to_cpu(evt->clock_accuracy); + pending_per_adv_sync->phy = bt_get_phy(evt->phy); + + memset(&sync_info, 0, sizeof(sync_info)); + sync_info.interval = pending_per_adv_sync->interval; + sync_info.phy = pending_per_adv_sync->phy; + + if (atomic_test_bit(pending_per_adv_sync->flags, + BT_PER_ADV_SYNC_SYNCING_USE_LIST)) + { + /* Now we know which address and SID we synchronized to. */ + + pending_per_adv_sync->sid = evt->sid; + + if (bt_addr_le_is_resolved(&pending_per_adv_sync->addr)) + { + bt_addr_le_copy_resolved(&pending_per_adv_sync->addr, &id_addr); + } + else + { + bt_addr_le_copy(&pending_per_adv_sync->addr, &id_addr); + } + } + + sync_info.addr = &pending_per_adv_sync->addr; + sync_info.sid = pending_per_adv_sync->sid; +#if defined(CONFIG_BT_PER_ADV_SYNC_RSP) + sync_info.num_subevents = evt->num_subevents; + sync_info.subevent_interval = evt->subevent_interval; + sync_info.response_slot_delay = evt->response_slot_delay; + sync_info.response_slot_spacing = evt->response_slot_spacing; + + pending_per_adv_sync->num_subevents = evt->num_subevents; + pending_per_adv_sync->subevent_interval = evt->subevent_interval; + pending_per_adv_sync->response_slot_delay = evt->response_slot_delay; + pending_per_adv_sync->response_slot_spacing = evt->response_slot_spacing; +#endif /* CONFIG_BT_PER_ADV_SYNC_RSP */ + + sync_info.recv_enabled = + !atomic_test_bit(pending_per_adv_sync->flags, + BT_PER_ADV_SYNC_RECV_DISABLED); + + list_for_every_container_safe(&pa_sync_cbs, listener, tmp, node) + { + if (listener->synced) + { + listener->synced(pending_per_adv_sync, &sync_info); + } + } +} + +void bt_hci_le_per_adv_sync_established(struct net_buf *buf) +{ + if (IS_ENABLED(CONFIG_BT_PER_ADV_SYNC_RSP)) + { + LOG_ERR("The controller shall raise the latest unmasked version of " + "the " + "event"); + + return; + } + + bt_hci_le_per_adv_sync_established_common(buf); +} + +#if defined(CONFIG_BT_PER_ADV_SYNC_RSP) +int bt_le_per_adv_sync_subevent(struct bt_le_per_adv_sync *per_adv_sync, + struct bt_le_per_adv_sync_subevent_params *params) +{ + struct bt_hci_cp_le_set_pawr_sync_subevent *cp; + struct net_buf *buf; + + if (!IS_ARRAY_ELEMENT(per_adv_sync_pool, per_adv_sync)) + { + LOG_DBG("Invalid per_adv_sync pointer %p", per_adv_sync); + return -EINVAL; + } + + if (params->num_subevents > BT_HCI_PAWR_SUBEVENT_MAX) + { + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + (void)memset(cp, 0, sizeof(*cp)); + cp->sync_handle = sys_cpu_to_le16(per_adv_sync->handle); + cp->periodic_adv_properties = sys_cpu_to_le16(params->properties); + cp->num_subevents = params->num_subevents; + net_buf_add_mem(buf, params->subevents, cp->num_subevents); + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_PER_ADV_SYNC_SUBEVENT, buf, + NULL); +} + +int bt_le_per_adv_set_response_data(struct bt_le_per_adv_sync *per_adv_sync, + const struct bt_le_per_adv_response_params *param, + const struct net_buf_simple *data) +{ + struct bt_hci_cp_le_set_pawr_response_data *cp; + struct net_buf *buf; + + if (!IS_ARRAY_ELEMENT(per_adv_sync_pool, per_adv_sync)) + { + LOG_DBG("Invalid per_adv_sync pointer %p", per_adv_sync); + return -EINVAL; + } + + if (per_adv_sync->num_subevents == 0) + { + return -EINVAL; + } + + if (param->request_subevent >= per_adv_sync->num_subevents) + { + return -EINVAL; + } + + if (param->response_subevent >= per_adv_sync->num_subevents) + { + return -EINVAL; + } + + if (data->len > 247) + { + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + (void)memset(cp, 0, sizeof(*cp)); + cp->sync_handle = sys_cpu_to_le16(per_adv_sync->handle); + cp->request_event = sys_cpu_to_le16(param->request_event); + cp->request_subevent = param->request_subevent; + cp->response_subevent = param->response_subevent; + cp->response_slot = param->response_slot; + cp->response_data_length = data->len; + + net_buf_add_mem(buf, data->data, cp->response_data_length); + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_PER_ADV_RESPONSE_DATA, buf, + NULL); +} +#endif /* CONFIG_BT_PER_ADV_SYNC_RSP */ + +void bt_hci_le_per_adv_sync_lost(struct net_buf *buf) +{ + struct bt_hci_evt_le_per_adv_sync_lost *evt = + (struct bt_hci_evt_le_per_adv_sync_lost *)buf->data; + struct bt_le_per_adv_sync *per_adv_sync; + + per_adv_sync = + bt_hci_per_adv_sync_lookup_handle(sys_le16_to_cpu(evt->handle)); + + if (!per_adv_sync) + { + LOG_ERR("Unknown handle 0x%04X for periodic adv sync lost", + sys_le16_to_cpu(evt->handle)); + return; + } + + /* There is no status in the per. adv. sync lost event */ + + per_adv_sync_terminated(per_adv_sync, BT_HCI_ERR_UNSPECIFIED); +} + +#if defined(CONFIG_BT_PER_ADV_SYNC_TRANSFER_RECEIVER) +static uint8_t conn_past_modes[CONFIG_BT_MAX_CONN]; +static uint8_t default_past_mode; + +static void past_disconnected_cb(struct bt_conn *conn, uint8_t reason) +{ + /* The core spec does not explicit state that the mode of a connection + * handle is cleared on disconnect, but let's assume it is. + */ + + conn_past_modes[bt_conn_index(conn)] = BT_HCI_LE_PAST_MODE_NO_SYNC; +} + +BT_CONN_CB_DEFINE(past_conn_callbacks) = +{ + .disconnected = past_disconnected_cb, +}; + +static void bt_hci_le_past_received_common(struct net_buf *buf) +{ +#if defined(CONFIG_BT_PER_ADV_SYNC_RSP) + struct bt_hci_evt_le_past_received_v2 *evt = + (struct bt_hci_evt_le_past_received_v2 *)buf->data; +#else + struct bt_hci_evt_le_past_received *evt = + (struct bt_hci_evt_le_past_received *)buf->data; +#endif /* defined(CONFIG_BT_PER_ADV_SYNC_RSP) */ + + struct bt_le_per_adv_sync_synced_info sync_info; + struct bt_le_per_adv_sync_cb *listener; + struct bt_le_per_adv_sync_cb *tmp; + struct bt_le_per_adv_sync *per_adv_sync; + bt_addr_le_t id_addr; + + if (evt->status) + { + /* No sync created, don't notify app */ + + LOG_DBG("PAST receive failed with status 0x%02X %s", evt->status, + bt_hci_err_to_str(evt->status)); + return; + } + + sync_info.conn = bt_conn_lookup_handle(sys_le16_to_cpu(evt->conn_handle), + BT_CONN_TYPE_LE); + + if (!sync_info.conn) + { + LOG_ERR("Could not lookup connection handle from PAST"); + per_adv_sync_terminate(sys_le16_to_cpu(evt->sync_handle)); + return; + } + + per_adv_sync = per_adv_sync_new(); + if (!per_adv_sync) + { + LOG_WRN("Could not allocate new PA sync from PAST"); + per_adv_sync_terminate(sys_le16_to_cpu(evt->sync_handle)); + bt_conn_unref(sync_info.conn); + return; + } + + atomic_set_bit(per_adv_sync->flags, BT_PER_ADV_SYNC_SYNCED); + + if (bt_addr_le_is_resolved(&evt->addr)) + { + bt_addr_le_copy_resolved(&id_addr, &evt->addr); + } + else + { + bt_addr_le_copy(&id_addr, bt_lookup_id_addr(BT_ID_DEFAULT, + &evt->addr)); + } + + per_adv_sync->handle = sys_le16_to_cpu(evt->sync_handle); + per_adv_sync->interval = sys_le16_to_cpu(evt->interval); + per_adv_sync->clock_accuracy = sys_le16_to_cpu(evt->clock_accuracy); + per_adv_sync->phy = bt_get_phy(evt->phy); + bt_addr_le_copy(&per_adv_sync->addr, &id_addr); + per_adv_sync->sid = evt->adv_sid; + +#if defined(CONFIG_BT_PER_ADV_SYNC_RSP) + per_adv_sync->num_subevents = evt->num_subevents; + per_adv_sync->subevent_interval = evt->subevent_interval; + per_adv_sync->response_slot_delay = evt->response_slot_delay; + per_adv_sync->response_slot_spacing = evt->response_slot_spacing; +#endif /* defined(CONFIG_BT_PER_ADV_SYNC_RSP) */ + + sync_info.interval = per_adv_sync->interval; + sync_info.phy = per_adv_sync->phy; + sync_info.addr = &per_adv_sync->addr; + sync_info.sid = per_adv_sync->sid; + sync_info.service_data = sys_le16_to_cpu(evt->service_data); + + const uint8_t mode = conn_past_modes[bt_conn_index(sync_info.conn)]; + + if (mode == BT_HCI_LE_PAST_MODE_NO_SYNC) + { + /* Use the default parameter mode as the conn specific mode is not set + */ + + sync_info.recv_enabled = + default_past_mode == BT_HCI_LE_PAST_MODE_SYNC || + default_past_mode == BT_HCI_LE_PAST_MODE_SYNC_FILTER_DUPLICATES; + } + else + { + sync_info.recv_enabled = mode == BT_HCI_LE_PAST_MODE_SYNC || + mode == BT_HCI_LE_PAST_MODE_SYNC_FILTER_DUPLICATES; + } + +#if defined(CONFIG_BT_PER_ADV_SYNC_RSP) + sync_info.num_subevents = per_adv_sync->num_subevents; + sync_info.subevent_interval = per_adv_sync->subevent_interval; + sync_info.response_slot_delay = per_adv_sync->response_slot_delay; + sync_info.response_slot_spacing = per_adv_sync->response_slot_spacing; +#endif /* defined(CONFIG_BT_PER_ADV_SYNC_RSP) */ + + list_for_every_container_safe(&pa_sync_cbs, listener, tmp, node) + { + if (listener->synced) + { + listener->synced(per_adv_sync, &sync_info); + } + } + + bt_conn_unref(sync_info.conn); +} + +void bt_hci_le_past_received(struct net_buf *buf) +{ + if (IS_ENABLED(CONFIG_BT_PER_ADV_SYNC_RSP)) + { + LOG_ERR("The controller shall raise the latest unmasked version of " + "the " + "event"); + + return; + } + + bt_hci_le_past_received_common(buf); +} + +#if defined(CONFIG_BT_PER_ADV_SYNC_RSP) +void bt_hci_le_past_received_v2(struct net_buf *buf) +{ + bt_hci_le_past_received_common(buf); +} +#endif /* CONFIG_BT_PER_ADV_SYNC_RSP */ +#endif /* CONFIG_BT_PER_ADV_SYNC_TRANSFER_RECEIVER */ + +#if defined(CONFIG_BT_PER_ADV_SYNC_RSP) +void bt_hci_le_per_adv_sync_established_v2(struct net_buf *buf) +{ + bt_hci_le_per_adv_sync_established_common(buf); +} + +void bt_hci_le_per_adv_report_v2(struct net_buf *buf) +{ + bt_hci_le_per_adv_report_common(buf); +} +#endif /* CONFIG_BT_PER_ADV_SYNC_RSP */ + +#if defined(CONFIG_BT_ISO_BROADCAST) +void bt_hci_le_biginfo_adv_report(struct net_buf *buf) +{ + struct bt_hci_evt_le_biginfo_adv_report *evt; + struct bt_le_per_adv_sync *per_adv_sync; + struct bt_le_per_adv_sync_cb *listener; + struct bt_le_per_adv_sync_cb *tmp; + struct bt_iso_biginfo biginfo; + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + + per_adv_sync = + bt_hci_per_adv_sync_lookup_handle(sys_le16_to_cpu( + evt->sync_handle)); + + if (!per_adv_sync) + { + LOG_ERR("Unknown handle 0x%04X for periodic advertising report", + sys_le16_to_cpu(evt->sync_handle)); + return; + } + + biginfo.addr = &per_adv_sync->addr; + biginfo.sid = per_adv_sync->sid; + biginfo.num_bis = evt->num_bis; + biginfo.sub_evt_count = evt->nse; + biginfo.iso_interval = sys_le16_to_cpu(evt->iso_interval); + biginfo.burst_number = evt->bn; + biginfo.offset = evt->pto; + biginfo.rep_count = evt->irc; + biginfo.max_pdu = sys_le16_to_cpu(evt->max_pdu); + biginfo.sdu_interval = sys_get_le24(evt->sdu_interval); + biginfo.max_sdu = sys_le16_to_cpu(evt->max_sdu); + biginfo.phy = bt_get_phy(evt->phy); + biginfo.framing = evt->framing; + biginfo.encryption = evt->encryption ? true : false; + + list_for_every_container_safe(&pa_sync_cbs, listener, tmp, node) + { + if (listener->biginfo) + { + listener->biginfo(per_adv_sync, &biginfo); + } + } +} +#endif /* CONFIG_BT_ISO_BROADCAST */ +#if defined(CONFIG_BT_DF_CONNECTIONLESS_CTE_RX) + +static void bt_hci_le_df_connectionless_iq_report_common(uint8_t event, + struct net_buf *buf) +{ + int err; + + struct bt_df_per_adv_sync_iq_samples_report cte_report; + struct bt_le_per_adv_sync *per_adv_sync; + struct bt_le_per_adv_sync_cb *listener; + struct bt_le_per_adv_sync_cb *tmp; + + if (event == BT_HCI_EVT_LE_CONNECTIONLESS_IQ_REPORT) + { + err = hci_df_prepare_connectionless_iq_report(buf, &cte_report, + &per_adv_sync); + if (err) + { + LOG_ERR("Prepare CTE conn IQ report failed %d", err); + return; + } + } + else if (IS_ENABLED(CONFIG_BT_DF_VS_CL_IQ_REPORT_16_BITS_IQ_SAMPLES) && + event == BT_HCI_EVT_VS_LE_CONNECTIONLESS_IQ_REPORT) + { + err = hci_df_vs_prepare_connectionless_iq_report(buf, &cte_report, + &per_adv_sync); + if (err) + { + LOG_ERR("Prepare CTE conn IQ report failed %d", err); + return; + } + } + else + { + LOG_ERR("Unhandled VS connectionless IQ report"); + return; + } + + list_for_every_container_safe(&pa_sync_cbs, listener, tmp, node) + { + if (listener->cte_report_cb) + { + listener->cte_report_cb(per_adv_sync, &cte_report); + } + } +} + +void bt_hci_le_df_connectionless_iq_report(struct net_buf *buf) +{ + bt_hci_le_df_connectionless_iq_report_common( + BT_HCI_EVT_LE_CONNECTIONLESS_IQ_REPORT, + buf); +} + +#if defined(CONFIG_BT_DF_VS_CL_IQ_REPORT_16_BITS_IQ_SAMPLES) +void bt_hci_le_vs_df_connectionless_iq_report(struct net_buf *buf) +{ + bt_hci_le_df_connectionless_iq_report_common( + BT_HCI_EVT_VS_LE_CONNECTIONLESS_IQ_REPORT, + buf); +} +#endif /* CONFIG_BT_DF_VS_CL_IQ_REPORT_16_BITS_IQ_SAMPLES */ +#endif /* CONFIG_BT_DF_CONNECTIONLESS_CTE_RX */ +#endif /* defined(CONFIG_BT_PER_ADV_SYNC) */ +#endif /* defined(CONFIG_BT_EXT_ADV) */ + +void bt_hci_le_adv_report(struct net_buf *buf) +{ + uint8_t num_reports = net_buf_pull_u8(buf); + struct bt_hci_evt_le_advertising_info *evt; + bool explicit_scan = atomic_test_bit(scan_state.scan_flags, + BT_LE_SCAN_USER_EXPLICIT_SCAN); + bool conn_scan = atomic_test_bit(scan_state.scan_flags, + BT_LE_SCAN_USER_CONN); + + LOG_DBG("Adv number of reports %u", num_reports); + + while (num_reports--) + { + struct bt_le_scan_recv_info adv_info; + + if (!explicit_scan && !conn_scan) + { + /* The application has not requested explicit scan, so it is not + * expecting advertising reports. Discard. This is done in the loop + * as this flag can change between each iteration, and it is not + * uncommon that scanning is disabled in the callback called from + * le_adv_recv. + * + * However, if scanning is running for connection purposes, the + * report shall still be processed to allow pending connections. + */ + + break; + } + + if (buf->len < sizeof(*evt)) + { + LOG_ERR("Unexpected end of buffer"); + break; + } + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + + if (buf->len < evt->length + sizeof(adv_info.rssi)) + { + LOG_ERR("Unexpected end of buffer"); + break; + } + + /* This event carries no target address, not even for a directed + * advertisement, so direct_addr stays NULL. + */ + + create_legacy_adv_info(evt->evt_type, evt->data[evt->length], + &adv_info); + + le_adv_recv(&evt->addr, &adv_info, &buf->b, evt->length); + + net_buf_pull(buf, evt->length + sizeof(adv_info.rssi)); + } +} + +#if defined(CONFIG_BT_SCAN_EXT_FILTER_POLICY) +void bt_hci_le_direct_adv_report(struct net_buf *buf) +{ + uint8_t num_reports = net_buf_pull_u8(buf); + struct bt_hci_evt_le_direct_adv_info *evt; + bool explicit_scan = atomic_test_bit(scan_state.scan_flags, + BT_LE_SCAN_USER_EXPLICIT_SCAN); + bool conn_scan = atomic_test_bit(scan_state.scan_flags, + BT_LE_SCAN_USER_CONN); + + LOG_DBG("Direct adv number of reports %u", num_reports); + + if (!explicit_scan && !conn_scan) + { + /* The application has not requested explicit scan, so it is not + * expecting advertising reports. Discard. + * + * However, if scanning is running for connection purposes, the report + * shall still be processed to allow pending connections. + */ + + return; + } + + for (uint8_t i = 0U; i < num_reports; i++) + { + struct bt_le_scan_recv_info adv_info; + bt_addr_le_t direct_addr; + + if (buf->len < sizeof(*evt)) + { + LOG_ERR("Unexpected end of buffer"); + break; + } + + evt = net_buf_pull_mem(buf, sizeof(*evt)); + + create_legacy_adv_info(evt->evt_type, evt->rssi, &adv_info); + + copy_hci_target_addr(&direct_addr, &evt->dir_addr); + + if (!bt_addr_le_is_resolved(&evt->dir_addr)) + { + /* This event only reports a target address that the Controller was + * unable to resolve, and the address type it carries is always + * BT_ADDR_LE_RANDOM. Normalize it so that the application sees the + * same representation as in an extended advertising report. + */ + + direct_addr.type = BT_ADDR_LE_UNRESOLVED; + } + + adv_info.direct_addr = &direct_addr; + + /* A directed advertisement carries no advertising data. */ + + le_adv_recv(&evt->addr, &adv_info, &buf->b, 0); + } +} +#endif /* CONFIG_BT_SCAN_EXT_FILTER_POLICY */ + +static bool valid_le_scan_param(const struct bt_le_scan_param *param) +{ + const uint8_t supported_options = + BT_LE_SCAN_OPT_FILTER_DUPLICATE | + BT_LE_SCAN_OPT_FILTER_ACCEPT_LIST | + BT_LE_SCAN_OPT_CODED | BT_LE_SCAN_OPT_NO_1M | + (IS_ENABLED(CONFIG_BT_SCAN_EXT_FILTER_POLICY) ? + BT_LE_SCAN_OPT_EXT_FILTER_POLICY : 0); + + if (IS_ENABLED(CONFIG_BT_PRIVACY) && + param->type == BT_LE_SCAN_TYPE_ACTIVE && + param->timeout != 0) + { + /* This is marked as not supported as a stopgap until the (scan, + * adv, init) roles are reworked into proper state machines. + * + * Having proper state machines is necessary to be able to suspend all + * roles that use the (resolvable) private address, update the RPA and + * resume them again with the right parameters. + * + * Else we lower the privacy of the device as either the RPA update + * will fail or the scanner will not use the newly generated RPA. + */ + + return false; + } + + if (param->type != BT_LE_SCAN_TYPE_PASSIVE && + param->type != BT_LE_SCAN_TYPE_ACTIVE) + { + return false; + } + + if ((param->options & ~supported_options) != 0U) + { + return false; + } + + if (param->interval < 0x0004 || param->interval > 0x4000) + { + return false; + } + + if (param->window < 0x0004 || param->window > 0x4000) + { + return false; + } + + if (param->window > param->interval) + { + return false; + } + + return true; +} + +int bt_le_scan_start(const struct bt_le_scan_param *param, + bt_le_scan_cb_t cb) +{ + int err; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + /* Check that the parameters have valid values */ + + if (!valid_le_scan_param(param)) + { + return -EINVAL; + } + + /* The Controller only accepts the extended scanning filter policies if its + * Link Layer supports them. Reject the request here to avoid an opaque HCI + * error later on. + */ + + if (IS_ENABLED(CONFIG_BT_SCAN_EXT_FILTER_POLICY) && + ((param->options & BT_LE_SCAN_OPT_EXT_FILTER_POLICY) != 0U) && + !BT_FEAT_LE_EXT_SCAN(bt_dev.le.features)) + { + LOG_WRN("Extended scanner filter policies not supported by the " + "Controller"); + return -ENOTSUP; + } + + if (param->type && !bt_id_scan_random_addr_check()) + { + return -EINVAL; + } + + /* Prevent multiple threads to try to enable explicit scanning at the same + * time. That could lead to unwanted overwriting of + * scan_state.explicit_scan_param. + */ + + err = nxrmutex_trylock(&scan_state.scan_explicit_params_mutex); + + if (err) + { + return -EBUSY; + } + + err = scan_check_if_state_allowed(BT_LE_SCAN_USER_EXPLICIT_SCAN); + + if (err) + { + nxrmutex_unlock(&scan_state.scan_explicit_params_mutex); + return err; + } + + /* store the parameters that were used to start the scanner */ + + memcpy(&scan_state.explicit_scan_param, param, + sizeof(scan_state.explicit_scan_param)); + + scan_dev_found_cb = cb; + err = bt_le_scan_user_add(BT_LE_SCAN_USER_EXPLICIT_SCAN); + nxrmutex_unlock(&scan_state.scan_explicit_params_mutex); + + return err; +} + +int bt_le_scan_stop(void) +{ + __maybe_unused int unlock_err; + int err; + + /* Take the same lock as bt_le_scan_start(), so that the state it sets + * up is not cleared while it is still being set up. + */ + + err = nxrmutex_trylock(&scan_state.scan_explicit_params_mutex); + if (err != 0) + { + return -EBUSY; + } + + bt_scan_softreset(); + scan_dev_found_cb = NULL; + + if (IS_ENABLED(CONFIG_BT_EXT_ADV) && + atomic_test_and_clear_bit(bt_dev.flags, BT_DEV_SCAN_LIMITED)) + { + atomic_clear_bit(bt_dev.flags, BT_DEV_RPA_VALID); + +#if defined(CONFIG_BT_SMP) + bt_id_pending_keys_update(); +#endif + } + + err = bt_le_scan_user_remove(BT_LE_SCAN_USER_EXPLICIT_SCAN); + + unlock_err = nxrmutex_unlock(&scan_state.scan_explicit_params_mutex); + DEBUGASSERT(unlock_err == 0); + + return err; +} + +int bt_le_scan_cb_register(struct bt_le_scan_cb *cb) +{ + if (list_find(&scan_cbs, &cb->node, NULL)) + { + return -EEXIST; + } + + list_add_tail(&scan_cbs, &cb->node); + + return 0; +} + +void bt_le_scan_cb_unregister(struct bt_le_scan_cb *cb) +{ + list_find_and_remove(&scan_cbs, &cb->node); +} + +#if defined(CONFIG_BT_PER_ADV_SYNC) +uint8_t bt_le_per_adv_sync_get_index(struct bt_le_per_adv_sync *per_adv_sync) +{ + DEBUGASSERT(IS_ARRAY_ELEMENT(per_adv_sync_pool, per_adv_sync)); + + return (uint8_t)ARRAY_INDEX(per_adv_sync_pool, per_adv_sync); +} + +struct bt_le_per_adv_sync *bt_le_per_adv_sync_lookup_index(uint8_t index) +{ + if (index >= ARRAY_SIZE(per_adv_sync_pool)) + { + return NULL; + } + + return &per_adv_sync_pool[index]; +} + +int bt_le_per_adv_sync_get_info(struct bt_le_per_adv_sync *per_adv_sync, + struct bt_le_per_adv_sync_info *info) +{ + if (!IS_ARRAY_ELEMENT(per_adv_sync_pool, per_adv_sync)) + { + LOG_DBG("Invalid per_adv_sync pointer %p", per_adv_sync); + return -EINVAL; + } + + if (info == NULL) + { + LOG_DBG("info is NULL"); + return -EINVAL; + } + + if (!atomic_test_bit(per_adv_sync->flags, BT_PER_ADV_SYNC_CREATED)) + { + LOG_DBG("per_adv_sync %p is not created", per_adv_sync); + return -EINVAL; + } + + bt_addr_le_copy(&info->addr, &per_adv_sync->addr); + info->sid = per_adv_sync->sid; + info->phy = per_adv_sync->phy; + info->interval = per_adv_sync->interval; + + return 0; +} + +struct bt_le_per_adv_sync *bt_le_per_adv_sync_lookup_addr( + const bt_addr_le_t *adv_addr, uint8_t sid) +{ + for (int i = 0; i < ARRAY_SIZE(per_adv_sync_pool); i++) + { + struct bt_le_per_adv_sync *sync = &per_adv_sync_pool[i]; + + if (!atomic_test_bit(per_adv_sync_pool[i].flags, + BT_PER_ADV_SYNC_CREATED)) + { + continue; + } + + if (bt_addr_le_eq(&sync->addr, adv_addr) && sync->sid == sid) + { + return sync; + } + } + + return NULL; +} + +int bt_le_per_adv_sync_create(const struct bt_le_per_adv_sync_param *param, + struct bt_le_per_adv_sync **out_sync) +{ + struct bt_hci_cp_le_per_adv_create_sync *cp; + struct net_buf *buf; + struct bt_le_per_adv_sync *per_adv_sync; + int err; + + if (!BT_FEAT_LE_EXT_PER_ADV(bt_dev.le.features)) + { + return -ENOTSUP; + } + + if (get_pending_per_adv_sync()) + { + return -EBUSY; + } + + if (param->sid > BT_GAP_SID_MAX || param->skip > BT_GAP_PER_ADV_MAX_SKIP || + param->timeout > BT_GAP_PER_ADV_MAX_TIMEOUT + || param->timeout < BT_GAP_PER_ADV_MIN_TIMEOUT) + { + return -EINVAL; + } + + if ((param->options & BT_LE_PER_ADV_SYNC_OPT_FILTER_DUPLICATE) != 0 && + BT_FEAT_LE_PER_ADV_ADI_SUPP(bt_dev.le.features) == 0) + { + return -ENOTSUP; + } + + per_adv_sync = per_adv_sync_new(); + if (!per_adv_sync) + { + return -ENOMEM; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + per_adv_sync_delete(per_adv_sync); + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + (void)memset(cp, 0, sizeof(*cp)); + + if (param->options & BT_LE_PER_ADV_SYNC_OPT_USE_PER_ADV_LIST) + { + atomic_set_bit(per_adv_sync->flags, BT_PER_ADV_SYNC_SYNCING_USE_LIST); + + cp->options |= BT_HCI_LE_PER_ADV_CREATE_SYNC_FP_USE_LIST; + } + else + { + /* If BT_LE_PER_ADV_SYNC_OPT_USE_PER_ADV_LIST is set, then the + * address and SID are ignored by the controller, so we only + * copy/assign them in case that the periodic advertising list is not + * used. + */ + + bt_addr_le_copy(&cp->addr, ¶m->addr); + cp->sid = param->sid; + } + + if (param->options & BT_LE_PER_ADV_SYNC_OPT_REPORTING_INITIALLY_DISABLED) + { + cp->options |= BT_HCI_LE_PER_ADV_CREATE_SYNC_FP_REPORTS_DISABLED; + + atomic_set_bit(per_adv_sync->flags, BT_PER_ADV_SYNC_RECV_DISABLED); + } + + if (param->options & BT_LE_PER_ADV_SYNC_OPT_FILTER_DUPLICATE) + { + cp->options |= BT_HCI_LE_PER_ADV_CREATE_SYNC_FP_FILTER_DUPLICATE; + } + + if (param->options & BT_LE_PER_ADV_SYNC_OPT_DONT_SYNC_AOA) + { + cp->cte_type |= BT_HCI_LE_PER_ADV_CREATE_SYNC_CTE_TYPE_NO_AOA; + } + + if (param->options & BT_LE_PER_ADV_SYNC_OPT_DONT_SYNC_AOD_1US) + { + cp->cte_type |= BT_HCI_LE_PER_ADV_CREATE_SYNC_CTE_TYPE_NO_AOD_1US; + } + + if (param->options & BT_LE_PER_ADV_SYNC_OPT_DONT_SYNC_AOD_2US) + { + cp->cte_type |= BT_HCI_LE_PER_ADV_CREATE_SYNC_CTE_TYPE_NO_AOD_2US; + } + + if (param->options & BT_LE_PER_ADV_SYNC_OPT_SYNC_ONLY_CONST_TONE_EXT) + { + cp->cte_type |= BT_HCI_LE_PER_ADV_CREATE_SYNC_CTE_TYPE_ONLY_CTE; + } + + cp->skip = sys_cpu_to_le16(param->skip); + cp->sync_timeout = sys_cpu_to_le16(param->timeout); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_PER_ADV_CREATE_SYNC, buf, NULL); + if (err) + { + per_adv_sync_delete(per_adv_sync); + return err; + } + + atomic_set_bit(per_adv_sync->flags, BT_PER_ADV_SYNC_SYNCING); + + /* Syncing requires that scan is enabled. If the caller doesn't enable + * scan first, we enable it here, and disable it once the sync has been + * established. We don't need to use any callbacks since we rely on the + * advertiser address in the sync params. + */ + + err = bt_le_scan_user_add(BT_LE_SCAN_USER_PER_SYNC); + if (err) + { + int per_sync_remove_err = + bt_le_scan_user_remove(BT_LE_SCAN_USER_PER_SYNC); + + if (per_sync_remove_err) + { + LOG_WRN("Error while updating the scanner (%d)", + per_sync_remove_err); + } + + bt_le_per_adv_sync_delete(per_adv_sync); + return err; + } + + *out_sync = per_adv_sync; + bt_addr_le_copy(&per_adv_sync->addr, ¶m->addr); + per_adv_sync->sid = param->sid; + + return 0; +} + +static int bt_le_per_adv_sync_create_cancel( + struct bt_le_per_adv_sync *per_adv_sync) +{ + struct net_buf *buf; + int err; + + if (get_pending_per_adv_sync() != per_adv_sync) + { + return -EINVAL; + } + + err = bt_le_scan_user_remove(BT_LE_SCAN_USER_PER_SYNC); + + if (err) + { + return err; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_PER_ADV_CREATE_SYNC_CANCEL, buf, + NULL); + if (err) + { + return err; + } + + return 0; +} + +static int bt_le_per_adv_sync_terminate( + struct bt_le_per_adv_sync *per_adv_sync) +{ + int err; + + if (!IS_ARRAY_ELEMENT(per_adv_sync_pool, per_adv_sync)) + { + LOG_DBG("Invalid per_adv_sync pointer %p", per_adv_sync); + return -EINVAL; + } + + if (!atomic_test_bit(per_adv_sync->flags, BT_PER_ADV_SYNC_SYNCED)) + { + return -EINVAL; + } + + err = per_adv_sync_terminate(per_adv_sync->handle); + + if (err) + { + return err; + } + + return 0; +} + +int bt_le_per_adv_sync_delete(struct bt_le_per_adv_sync *per_adv_sync) +{ + int err = 0; + + if (!BT_FEAT_LE_EXT_PER_ADV(bt_dev.le.features)) + { + return -ENOTSUP; + } + + if (!IS_ARRAY_ELEMENT(per_adv_sync_pool, per_adv_sync)) + { + LOG_DBG("Invalid per_adv_sync pointer %p", per_adv_sync); + return -EINVAL; + } + + if (atomic_test_bit(per_adv_sync->flags, BT_PER_ADV_SYNC_SYNCED)) + { + err = bt_le_per_adv_sync_terminate(per_adv_sync); + + if (!err) + { + per_adv_sync_terminated(per_adv_sync, + BT_HCI_ERR_LOCALHOST_TERM_CONN); + } + } + else if (get_pending_per_adv_sync() == per_adv_sync) + { + err = bt_le_per_adv_sync_create_cancel(per_adv_sync); + /* Delete of the per_adv_sync will be done in the event + * handler when cancelling. + */ + } + + return err; +} + +int bt_le_per_adv_sync_cb_register(struct bt_le_per_adv_sync_cb *cb) +{ + if (list_find(&pa_sync_cbs, &cb->node, NULL)) + { + return -EEXIST; + } + + list_add_tail(&pa_sync_cbs, &cb->node); + + return 0; +} + +int bt_le_per_adv_sync_cb_unregister(struct bt_le_per_adv_sync_cb *cb) +{ + if (cb == NULL) + { + return -EINVAL; + } + + if (!list_find_and_remove(&pa_sync_cbs, &cb->node)) + { + return -ENOENT; + } + + return 0; +} + +static int bt_le_set_per_adv_recv_enable( + struct bt_le_per_adv_sync *per_adv_sync, bool enable) +{ + struct bt_hci_cp_le_set_per_adv_recv_enable *cp; + struct bt_le_per_adv_sync_cb *listener; + struct bt_le_per_adv_sync_cb *tmp; + struct bt_le_per_adv_sync_state_info info; + struct net_buf *buf; + struct bt_hci_cmd_state_set state; + int err; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + if (!BT_FEAT_LE_EXT_PER_ADV(bt_dev.le.features)) + { + return -ENOTSUP; + } + + if (!IS_ARRAY_ELEMENT(per_adv_sync_pool, per_adv_sync)) + { + LOG_DBG("Invalid per_adv_sync pointer %p", per_adv_sync); + return -EINVAL; + } + + if (!atomic_test_bit(per_adv_sync->flags, BT_PER_ADV_SYNC_SYNCED)) + { + return -EINVAL; + } + + if ((enable && !atomic_test_bit(per_adv_sync->flags, + BT_PER_ADV_SYNC_RECV_DISABLED)) || + (!enable && atomic_test_bit(per_adv_sync->flags, + BT_PER_ADV_SYNC_RECV_DISABLED))) + { + return -EALREADY; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + (void)memset(cp, 0, sizeof(*cp)); + + cp->handle = sys_cpu_to_le16(per_adv_sync->handle); + cp->enable = enable ? 1 : 0; + + bt_hci_cmd_state_set_init(buf, &state, per_adv_sync->flags, + BT_PER_ADV_SYNC_RECV_DISABLED, + !enable); + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_SET_PER_ADV_RECV_ENABLE, buf, + NULL); + + if (err) + { + return err; + } + + info.recv_enabled = !atomic_test_bit(per_adv_sync->flags, + BT_PER_ADV_SYNC_RECV_DISABLED); + + list_for_every_container_safe(&pa_sync_cbs, listener, tmp, node) + { + if (listener->state_changed) + { + listener->state_changed(per_adv_sync, &info); + } + } + + return 0; +} + +int bt_le_per_adv_sync_recv_enable(struct bt_le_per_adv_sync *per_adv_sync) +{ + return bt_le_set_per_adv_recv_enable(per_adv_sync, true); +} + +int bt_le_per_adv_sync_recv_disable(struct bt_le_per_adv_sync *per_adv_sync) +{ + return bt_le_set_per_adv_recv_enable(per_adv_sync, false); +} + +#if defined(CONFIG_BT_PER_ADV_SYNC_TRANSFER_SENDER) +int bt_le_per_adv_sync_transfer( + const struct bt_le_per_adv_sync *per_adv_sync, const struct bt_conn *conn, + uint16_t service_data) +{ + struct bt_hci_cp_le_per_adv_sync_transfer *cp; + struct net_buf *buf; + + if (!BT_FEAT_LE_EXT_PER_ADV(bt_dev.le.features)) + { + return -ENOTSUP; + } + else if (!BT_FEAT_LE_PAST_SEND(bt_dev.le.features)) + { + return -ENOTSUP; + } + + if (!IS_ARRAY_ELEMENT(per_adv_sync_pool, per_adv_sync)) + { + LOG_DBG("Invalid per_adv_sync pointer %p", per_adv_sync); + return -EINVAL; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + (void)memset(cp, 0, sizeof(*cp)); + + cp->conn_handle = sys_cpu_to_le16(conn->handle); + cp->sync_handle = sys_cpu_to_le16(per_adv_sync->handle); + cp->service_data = sys_cpu_to_le16(service_data); + + return bt_hci_cmd_send_sync(BT_HCI_OP_LE_PER_ADV_SYNC_TRANSFER, buf, NULL); +} +#endif /* CONFIG_BT_PER_ADV_SYNC_TRANSFER_SENDER */ + +#if defined(CONFIG_BT_PER_ADV_SYNC_TRANSFER_RECEIVER) +static bool valid_past_param( + const struct bt_le_per_adv_sync_transfer_param *param) +{ + if (param->skip > 0x01f3 || param->timeout < 0x000a || + param->timeout > 0x4000) + { + return false; + } + + if ((param->options & + BT_LE_PER_ADV_SYNC_TRANSFER_OPT_REPORTING_INITIALLY_DISABLED) && + (param->options & BT_LE_PER_ADV_SYNC_TRANSFER_OPT_FILTER_DUPLICATES)) + { + return false; + } + + return true; +} + +static int past_param_set( + const struct bt_conn *conn, uint8_t mode, uint16_t skip, uint16_t timeout, + uint8_t cte_type) +{ + struct bt_hci_cp_le_past_param *cp; + struct net_buf *buf; + int err; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + (void)memset(cp, 0, sizeof(*cp)); + + cp->conn_handle = sys_cpu_to_le16(conn->handle); + cp->mode = mode; + cp->skip = sys_cpu_to_le16(skip); + cp->timeout = sys_cpu_to_le16(timeout); + cp->cte_type = cte_type; + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_PAST_PARAM, buf, NULL); + + return err; +} + +static int default_past_param_set(uint8_t mode, + uint16_t skip, uint16_t timeout, + uint8_t cte_type) +{ + struct bt_hci_cp_le_default_past_param *cp; + struct net_buf *buf; + int err; + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + (void)memset(cp, 0, sizeof(*cp)); + + cp->mode = mode; + cp->skip = sys_cpu_to_le16(skip); + cp->timeout = sys_cpu_to_le16(timeout); + cp->cte_type = cte_type; + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_DEFAULT_PAST_PARAM, buf, NULL); + + return err; +} + +int bt_le_per_adv_sync_transfer_subscribe(const struct bt_conn *conn, + const struct bt_le_per_adv_sync_transfer_param *param) +{ + uint8_t cte_type = 0; + uint8_t mode = BT_HCI_LE_PAST_MODE_SYNC; + int err; + + if (!BT_FEAT_LE_EXT_PER_ADV(bt_dev.le.features)) + { + return -ENOTSUP; + } + else if (!BT_FEAT_LE_PAST_RECV(bt_dev.le.features)) + { + return -ENOTSUP; + } + + if (!valid_past_param(param)) + { + return -EINVAL; + } + + if (param->options & BT_LE_PER_ADV_SYNC_TRANSFER_OPT_SYNC_NO_AOA) + { + cte_type |= BT_HCI_LE_PAST_CTE_TYPE_NO_AOA; + } + + if (param->options & BT_LE_PER_ADV_SYNC_TRANSFER_OPT_SYNC_NO_AOD_1US) + { + cte_type |= BT_HCI_LE_PAST_CTE_TYPE_NO_AOD_1US; + } + + if (param->options & BT_LE_PER_ADV_SYNC_TRANSFER_OPT_SYNC_NO_AOD_2US) + { + cte_type |= BT_HCI_LE_PAST_CTE_TYPE_NO_AOD_2US; + } + + if (param->options & BT_LE_PER_ADV_SYNC_TRANSFER_OPT_SYNC_ONLY_CTE) + { + cte_type |= BT_HCI_LE_PAST_CTE_TYPE_ONLY_CTE; + } + + if ( + param->options & + BT_LE_PER_ADV_SYNC_TRANSFER_OPT_REPORTING_INITIALLY_DISABLED) + { + mode = BT_HCI_LE_PAST_MODE_NO_REPORTS; + } + else + if (param->options & BT_LE_PER_ADV_SYNC_TRANSFER_OPT_FILTER_DUPLICATES) + { + mode = BT_HCI_LE_PAST_MODE_SYNC_FILTER_DUPLICATES; + } + + if (conn) + { + const uint8_t conn_idx = bt_conn_index(conn); + const uint8_t old_mode = conn_past_modes[conn_idx]; + + conn_past_modes[conn_idx] = mode; + + err = past_param_set(conn, + mode, + param->skip, + param->timeout, + cte_type); + if (err != 0) + { + /* Restore old mode */ + + conn_past_modes[conn_idx] = old_mode; + } + } + else + { + const uint8_t old_mode = default_past_mode; + + default_past_mode = mode; + + err = default_past_param_set(mode, param->skip, param->timeout, + cte_type); + if (err != 0) + { + /* Restore old mode */ + + default_past_mode = old_mode; + } + } + + return err; +} + +int bt_le_per_adv_sync_transfer_unsubscribe(const struct bt_conn *conn) +{ + int err; + + if (!BT_FEAT_LE_EXT_PER_ADV(bt_dev.le.features)) + { + return -ENOTSUP; + } + else if (!BT_FEAT_LE_PAST_RECV(bt_dev.le.features)) + { + return -ENOTSUP; + } + + if (conn) + { + const uint8_t conn_idx = bt_conn_index(conn); + const uint8_t old_mode = conn_past_modes[conn_idx]; + + conn_past_modes[conn_idx] = BT_HCI_LE_PAST_MODE_NO_SYNC; + + err = past_param_set(conn, BT_HCI_LE_PAST_MODE_NO_SYNC, 0, 0x0a, 0); + if (err != 0) + { + /* Restore old mode */ + + conn_past_modes[conn_idx] = old_mode; + } + } + else + { + const uint8_t old_mode = default_past_mode; + + default_past_mode = BT_HCI_LE_PAST_MODE_NO_SYNC; + err = default_past_param_set(BT_HCI_LE_PAST_MODE_NO_SYNC, 0, 0x0a, 0); + if (err != 0) + { + /* Restore old mode */ + + default_past_mode = old_mode; + } + } + + return err; +} +#endif /* CONFIG_BT_PER_ADV_SYNC_TRANSFER_RECEIVER */ + +int bt_le_per_adv_list_add(const bt_addr_le_t *addr, uint8_t sid) +{ + struct bt_hci_cp_le_add_dev_to_per_adv_list *cp; + struct net_buf *buf; + int err; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + bt_addr_le_copy(&cp->addr, addr); + cp->sid = sid; + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_ADD_DEV_TO_PER_ADV_LIST, buf, + NULL); + if (err) + { + LOG_ERR("Failed to add device to periodic advertiser list"); + } + + return err; +} + +int bt_le_per_adv_list_remove(const bt_addr_le_t *addr, uint8_t sid) +{ + struct bt_hci_cp_le_rem_dev_from_per_adv_list *cp; + struct net_buf *buf; + int err; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + buf = bt_hci_cmd_alloc(((clock_t)-1)); + if (!buf) + { + return -ENOBUFS; + } + + cp = net_buf_add(buf, sizeof(*cp)); + bt_addr_le_copy(&cp->addr, addr); + cp->sid = sid; + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_REM_DEV_FROM_PER_ADV_LIST, buf, + NULL); + if (err) + { + LOG_ERR("Failed to remove device from periodic advertiser list"); + return err; + } + + return 0; +} + +int bt_le_per_adv_list_clear(void) +{ + int err; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_READY)) + { + return -EAGAIN; + } + + err = bt_hci_cmd_send_sync(BT_HCI_OP_LE_CLEAR_PER_ADV_LIST, NULL, NULL); + if (err) + { + LOG_ERR("Failed to clear periodic advertiser list"); + return err; + } + + return 0; +} +#endif /* defined(CONFIG_BT_PER_ADV_SYNC) */ + +bool bt_le_explicit_scanner_running(void) +{ + return atomic_test_bit(scan_state.scan_flags, + BT_LE_SCAN_USER_EXPLICIT_SCAN); +} + +bool bt_le_explicit_scanner_uses_same_params( + const struct bt_conn_le_create_param *create_param) +{ + if (scan_state.explicit_scan_param.window != create_param->window || + scan_state.explicit_scan_param.interval != create_param->interval) + { + return false; + } + + if (scan_state.explicit_scan_param.options & BT_LE_SCAN_OPT_CODED) + { + if (scan_state.explicit_scan_param.window_coded != + create_param->window_coded || + scan_state.explicit_scan_param.interval_coded != + create_param->interval_coded) + { + return false; + } + } + + return true; +} diff --git a/libs/zblue/host/scan.h b/libs/zblue/host/scan.h new file mode 100644 index 0000000000000..165a99fffa9ad --- /dev/null +++ b/libs/zblue/host/scan.h @@ -0,0 +1,155 @@ +/**************************************************************************** + * libs/zblue/host/scan.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017-2021 Nordic Semiconductor ASA + * Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef SUBSYS_BLUETOOTH_HOST_SCAN_H_ +#define SUBSYS_BLUETOOTH_HOST_SCAN_H_ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include + +/** + * Reasons why a scanner can be running. Used as input to @ref + * bt_le_scan_user_add and @ref bt_le_scan_user_remove. + */ + +enum bt_le_scan_user +{ + /** The application explicitly instructed the stack to scan for advertisers + * using the API @ref bt_le_scan_start. Users of the scanner module may not + * use this flag as input to @ref bt_le_scan_user_add and @ref + * bt_le_scan_user_remove. Use Ā®ref bt_le_scan_start and @ref + * bt_le_scan_stop instead. + */ + + BT_LE_SCAN_USER_EXPLICIT_SCAN, + + /** + * Periodic sync syncing to a periodic advertiser. + */ + + BT_LE_SCAN_USER_PER_SYNC, + + /** + * Scanning to find devices to connect to. + */ + + BT_LE_SCAN_USER_CONN, + + /** + * Special state for a NOP for @ref bt_le_scan_user_add and @ref + * bt_le_scan_user_remove to not add/remove any user. + */ + + BT_LE_SCAN_USER_NONE, + BT_LE_SCAN_USER_NUM_FLAGS, +}; + +void bt_scan_reset(void); + +bool bt_id_scan_random_addr_check(void); +bool bt_le_scan_active_scanner_running(void); + +int bt_le_scan_set_enable(uint8_t enable); + +void bt_periodic_sync_disable(void); + +/** + * Start / update the scanner. + * + * This API updates the users of the scanner. Multiple users can be enabled + * at the same time. Depending on all the users, scan parameters are selected + * and the scanner is started or updated, if needed. This API may update the + * scan parameters, for example if the scanner is already running when + * another user that demands higher duty-cycle is being added. It is not + * allowed to add a user that was already added. + * + * Every SW module that informs the scanner that it should run, needs to + * eventually remove the flag again using @ref bt_le_scan_user_remove once it + * does not require the scanner to run, anymore. + * + * If flag is set to @ref BT_LE_SCAN_USER_NONE, no user is being added. + * Instead, the existing users are checked and the scanner is started, + * stopped or updated. For platforms where scanning and initiating at the + * same time is not supported, this allows the background scanner to be + * started or stopped once the device starts to initiate a connection. + * + * @param flag user requesting the scanner + * + * @retval 0 in case of success + * @retval -EALREADY if the user is already enabled + * @retval -EPERM if the explicit scanner is being enabled while the + * initiator is running and the device does not support this configuration. + * @retval -ENOBUFS if no hci command buffer could be allocated + * @retval -EBUSY if the scanner is updated in a different thread. The user + * was added but the scanner was not started/stopped/updated. + * @returns negative error codes for errors in @ref bt_hci_cmd_send_sync + */ + +int bt_le_scan_user_add(enum bt_le_scan_user flag); + +/** + * Stop / update the scanner. + * + * This API updates the users of the scanner. Depending on all enabled users, + * scan parameters are selected and the scanner is stopped or updated, if + * needed. This API may update the scan parameters, for example if the + * scanner is already running when a user that demands higher duty-cycle is + * being removed. Removing a user that was not added does not result in an + * error. + * + * This API allows removing the user why the scanner is running. If all users + * for the scanner to run are removed, this API will stop the scanner. + * + * If flag is set to @ref BT_LE_SCAN_USER_NONE, no user is being added. + * Instead, the existing users are checked and the scanner is started, + * stopped or updated. For platforms where scanning and initiating at the + * same time is not supported, this allows the background scanner to be + * started or stopped once the device starts to initiate a connection. + * + * @param flag user releasing the scanner + * + * @retval 0 in case of success + * @retval -ENOBUFS if no hci command buffer could be allocated + * @retval -EBUSY if the scanner is updated in a different thread. The user + * was removed but the scanner was not started/stopped/updated. + * @returns negative error codes for errors in @ref bt_hci_cmd_send_sync + */ + +int bt_le_scan_user_remove(enum bt_le_scan_user flag); + +/** + * Check if the explicit scanner was enabled. + */ + +bool bt_le_explicit_scanner_running(void); + +/** + * Check if an explicit scanner uses the same parameters + * + * @param create_param Parameters used for connection establishment. + * + * @return true If explicit scanner uses the same parameters + * @return false If explicit scanner uses different parameters + */ + +bool bt_le_explicit_scanner_uses_same_params( + const struct bt_conn_le_create_param *create_param); +#endif /* defined SUBSYS_BLUETOOTH_HOST_SCAN_H_ */ diff --git a/libs/zblue/host/settings.c b/libs/zblue/host/settings.c new file mode 100644 index 0000000000000..dc531c24e0dd0 --- /dev/null +++ b/libs/zblue/host/settings.c @@ -0,0 +1,752 @@ +/**************************************************************************** + * libs/zblue/host/settings.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2018 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/bt_settings_commit.h" +#include "common/bt_str.h" +#include "classic/br.h" +#include "hci_core.h" +#include "settings.h" +#include "sys/types.h" + +#define LOG_LEVEL CONFIG_BT_SETTINGS_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_settings); + +#if defined(CONFIG_BT_SETTINGS_USE_PRINTK) + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bt_settings_encode_key(char *path, size_t path_size, const char *subsys, + const bt_addr_le_t *addr, const char *key) +{ + int err; + + if (key) + { + err = snprintf(path, path_size, "bt/%s/%02x%02x%02x%02x%02x%02x%u/%s", + subsys, addr->a.val[5], + addr->a.val[4], addr->a.val[3], addr->a.val[2], + addr->a.val[1], addr->a.val[0], + addr->type, key); + } + else + { + err = snprintf(path, path_size, "bt/%s/%02x%02x%02x%02x%02x%02x%u", + subsys, addr->a.val[5], + addr->a.val[4], addr->a.val[3], addr->a.val[2], + addr->a.val[1], addr->a.val[0], + addr->type); + } + + if ((err < 0) || (err >= path_size)) + { + /* Error or output was truncated */ + + return -EINVAL; + } + + LOG_DBG("Encoded path %s", path); + + return 0; +} + +static int bt_settings_encode_key_no_addr(char *path, + size_t path_size, const char *key) +{ + int err; + + err = snprintf(path, path_size, "bt/%s", key); + if ((err < 0) || (err >= path_size)) + { + /* Error or output was truncated */ + + return -EINVAL; + } + + return 0; +} + +#else /* !CONFIG_BT_SETTINGS_USE_PRINTK */ +int bt_settings_encode_key(char *path, size_t path_size, const char *subsys, + const bt_addr_le_t *addr, const char *key) +{ + /* Key format: + * "bt///", "/" is optional + */ + + const char delimiter = '/'; + const char null_term = '\0'; + const size_t prefix_len = sizeof("bt") - 1U; + const size_t subsys_len = strlen(subsys); + const size_t addr_str_len = sizeof("554433221100t") - 1U; + const size_t key_len = (key == NULL) ? 0U : strlen(key); + const size_t total_len = + prefix_len + sizeof(delimiter) + subsys_len + sizeof(delimiter) + + addr_str_len + ((key == + NULL) ? 0U : (sizeof(delimiter) + key_len)) + + sizeof(null_term); + size_t offset = 0U; + int err; + + if (path_size < total_len) + { + return -EINVAL; + } + + memcpy(path, "bt", prefix_len); + offset += prefix_len; + + path[offset] = delimiter; + offset++; + memcpy(&path[offset], subsys, subsys_len); + offset += subsys_len; + + path[offset] = delimiter; + offset++; + for (int8_t i = 5; i >= 0; i--) + { + /* We supply valid 0-15 as input */ + + err = hex2char(addr->a.val[i] >> 4, &path[offset]); + DEBUGASSERT(err == 0); + offset++; + + /* We supply valid 0-15 as input */ + + err = hex2char(addr->a.val[i] & 0xf, &path[offset]); + DEBUGASSERT(err == 0); + offset++; + } + + /* We are not checking hex2char return value as we supply valid 0-1 as + * input + */ + + err = hex2char(addr->type, &path[offset]); + DEBUGASSERT(err == 0); + offset++; + + if (key != NULL) + { + path[offset] = delimiter; + offset++; + memcpy(&path[offset], key, key_len); + offset += key_len; + } + + path[offset] = null_term; + + LOG_DBG("Encoded path %s", path); + + return 0; +} + +static int bt_settings_encode_key_no_addr(char *path, + size_t path_size, const char *key) +{ + /* Key format: + * "bt/" + */ + + const char delimiter = '/'; + const char null_term = '\0'; + const size_t prefix_len = sizeof("bt") - 1U; + const size_t key_len = strlen(key); + const size_t total_len = + prefix_len + sizeof(delimiter) + key_len + sizeof(null_term); + size_t offset = 0U; + + if (path_size < total_len) + { + return -EINVAL; + } + + memcpy(path, "bt", prefix_len); + offset += prefix_len; + + path[offset] = delimiter; + offset++; + memcpy(&path[offset], key, key_len); + offset += key_len; + + path[offset] = null_term; + + LOG_DBG("Encoded path %s", path); + + return 0; +} +#endif /* !CONFIG_BT_SETTINGS_USE_PRINTK */ + +int bt_settings_decode_key(const char *key, bt_addr_le_t *addr) +{ + if (settings_name_next(key, NULL) != 13) + { + return -EINVAL; + } + + if (key[12] == '0') + { + addr->type = BT_ADDR_LE_PUBLIC; + } + else if (key[12] == '1') + { + addr->type = BT_ADDR_LE_RANDOM; + } + else + { + return -EINVAL; + } + + for (uint8_t i = 0; i < 6; i++) + { + hex2bin(&key[i * 2], 2, &addr->a.val[5 - i], 1); + } + + LOG_DBG("Decoded %s as %s", key, bt_addr_le_str(addr)); + + return 0; +} + +static int set_setting(const char *name, + size_t len_rd, settings_read_cb read_cb, void *cb_arg) +{ + ssize_t len; + const char *next; + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_OPEN)) + { + /* The Bluetooth settings loader needs to communicate with the + * Bluetooth controller to setup identities. This will not work before + * bt_enable(). The doc on @ref bt_enable requires the "bt/" settings + * tree to be loaded after @ref bt_enable is completed, so this handler + * will be called again later. + */ + + return 0; + } + + if (!name) + { + LOG_ERR("Insufficient number of arguments"); + return -ENOENT; + } + + len = settings_name_next(name, &next); + + if ((len == 2) && (memcmp(name, "id", len) == 0)) + { + /* Any previously provided identities supersede flash */ + + if (atomic_test_bit(bt_dev.flags, BT_DEV_PRESET_ID)) + { + LOG_WRN("Ignoring identities stored in flash"); + return 0; + } + + len = read_cb(cb_arg, &bt_dev.id_addr, sizeof(bt_dev.id_addr)); + if (len < sizeof(bt_dev.id_addr[0])) + { + if (len < 0) + { + LOG_ERR("Failed to read ID address from storage" + " (err %zd)", + len); + } + else + { + LOG_ERR("Invalid length ID address in storage"); + LOG_HEXDUMP_DBG(&bt_dev.id_addr, len, "data read"); + } + + (void)memset(bt_dev.id_addr, 0, sizeof(bt_dev.id_addr)); + bt_dev.id_count = 0U; + } + else + { + int i; + + bt_dev.id_count = len / sizeof(bt_dev.id_addr[0]); + for (i = 0; i < bt_dev.id_count; i++) + { + LOG_DBG("ID[%d] %s", i, bt_addr_le_str(&bt_dev.id_addr[i])); + } + } + + return 0; + } + +#if defined(CONFIG_BT_DEVICE_NAME_DYNAMIC) + if ((len == 4) && (memcmp(name, "name", len) == 0)) + { + len = read_cb(cb_arg, &bt_dev.name, sizeof(bt_dev.name) - 1); + if (len < 0) + { + LOG_ERR("Failed to read device name from storage" + " (err %zd)", + len); + } + else + { + bt_dev.name[len] = '\0'; + + LOG_DBG("Name set to %s", bt_dev.name); + } + + return 0; + } +#endif + +#if defined(CONFIG_BT_DEVICE_APPEARANCE_DYNAMIC) + if ((len == 10) && (memcmp(name, "appearance", len) == 0)) + { + if (len_rd != sizeof(bt_dev.appearance)) + { + LOG_ERR("Ignoring settings entry 'bt/appearance'. Wrong length."); + return -EINVAL; + } + + len = read_cb(cb_arg, &bt_dev.appearance, sizeof(bt_dev.appearance)); + if (len < 0) + { + return len; + } + + return 0; + } +#endif + +#if defined(CONFIG_BT_PRIVACY) + if ((len == 3) && (memcmp(name, "irk", len) == 0)) + { + len = read_cb(cb_arg, bt_dev.irk, sizeof(bt_dev.irk)); + if (len < sizeof(bt_dev.irk[0])) + { + if (len < 0) + { + LOG_ERR("Failed to read IRK from storage" + " (err %zd)", + len); + } + else + { + LOG_ERR("Invalid length IRK in storage"); + (void)memset(bt_dev.irk, 0, sizeof(bt_dev.irk)); + } + } + else + { + int i; + int count; + + count = len / sizeof(bt_dev.irk[0]); + for (i = 0; i < count; i++) + { + LOG_DBG("IRK[%d] %s", i, bt_hex(bt_dev.irk[i], 16)); + } + } + + return 0; + } +#endif /* CONFIG_BT_PRIVACY */ + + return -ENOENT; +} + +static int commit_settings(void) +{ + int err; + + LOG_DBG(""); + + if (!atomic_test_bit(bt_dev.flags, BT_DEV_OPEN)) + { + /* The Bluetooth settings loader needs to communicate with the + * Bluetooth controller to setup identities. This will not work before + * bt_enable(). The doc on @ref bt_enable requires the "bt/" settings + * tree to be loaded after @ref bt_enable is completed, so this handler + * will be called again later. + */ + + return 0; + } + +#if defined(CONFIG_BT_DEVICE_NAME_DYNAMIC) + if (bt_dev.name[0] == '\0') + { + /* No name in flash — populate bt_dev.name with the default. + * Skip bt_set_name() to avoid an unnecessary flash write. + */ + + strncpy(bt_dev.name, CONFIG_BT_DEVICE_NAME, CONFIG_BT_DEVICE_NAME_MAX); + bt_dev.name[CONFIG_BT_DEVICE_NAME_MAX] = '\0'; + } + + /* Push the name (restored or default) to all transports. + * For BLE the GAP device name is already handled by the advertising / + * scan-response path; for BR/EDR we must issue the HCI Write Local Name + * command explicitly. + */ + + if (IS_ENABLED(CONFIG_BT_CLASSIC)) + { + err = bt_br_write_local_name(bt_dev.name); + if (err != 0) + { + LOG_ERR("Unable to set BR/EDR local name (err %d)", err); + goto finalize; + } + } +#endif + + if (!bt_dev.id_count) + { + err = bt_setup_public_id_addr(); + if (err) + { + LOG_ERR("Unable to setup an identity address"); + goto finalize; + } + } + + if (!bt_dev.id_count) + { + err = bt_setup_random_id_addr(); + if (err) + { + LOG_ERR("Unable to setup an identity address"); + goto finalize; + } + } + + /* If any part of the Identity Information of the device has been + * generated this Identity needs to be saved persistently. + */ + + if (atomic_test_and_clear_bit(bt_dev.flags, BT_DEV_STORE_ID)) + { + LOG_DBG("Storing Identity Information"); + bt_settings_store_id(); + bt_settings_store_irk(); + } + + err = 0; + +finalize: + bt_finalize_init(err); + + return err; +} + +SETTINGS_STATIC_HANDLER_DEFINE_WITH_CPRIO(bt, + "bt", + NULL, + set_setting, + commit_settings, + NULL, + + BT_SETTINGS_CPRIO_0); + +int bt_settings_init(void) +{ + int err; + + LOG_DBG(""); + + err = settings_subsys_init(); + if (err) + { + LOG_ERR("settings_subsys_init failed (err %d)", err); + return err; + } + + return 0; +} + +__weak void bt_testing_settings_store_hook(const char *key, + const void *value, size_t val_len) +{ + ARG_UNUSED(key); + ARG_UNUSED(value); + ARG_UNUSED(val_len); +} + +__weak void bt_testing_settings_delete_hook(const char *key) +{ + ARG_UNUSED(key); +} + +int bt_settings_store( + const char *key, uint8_t id, const bt_addr_le_t *addr, const void *value, + size_t val_len) +{ + char key_str[BT_SETTINGS_KEY_MAX]; + int err; + + if (addr) + { + char id_str[4]; + + if (id) + { + u8_to_dec(id_str, sizeof(id_str), id); + } + + err = bt_settings_encode_key(key_str, sizeof(key_str), key, addr, + (id ? id_str : NULL)); + } + else + { + err = bt_settings_encode_key_no_addr(key_str, sizeof(key_str), key); + } + + if (err != 0) + { + return err; + } + + if (IS_ENABLED(CONFIG_BT_TESTING)) + { + bt_testing_settings_store_hook(key_str, value, val_len); + } + + return settings_save_one(key_str, value, val_len); +} + +int bt_settings_delete(const char *key, uint8_t id, const bt_addr_le_t *addr) +{ + char key_str[BT_SETTINGS_KEY_MAX]; + int err; + + if (addr) + { + char id_str[4]; + + if (id) + { + u8_to_dec(id_str, sizeof(id_str), id); + } + + err = bt_settings_encode_key(key_str, sizeof(key_str), key, addr, + (id ? id_str : NULL)); + } + else + { + err = bt_settings_encode_key_no_addr(key_str, sizeof(key_str), key); + } + + if (err != 0) + { + return err; + } + + if (IS_ENABLED(CONFIG_BT_TESTING)) + { + bt_testing_settings_delete_hook(key_str); + } + + return settings_delete(key_str); +} + +int bt_settings_store_sc(uint8_t id, + const bt_addr_le_t *addr, const void *value, + size_t val_len) +{ + return bt_settings_store("sc", id, addr, value, val_len); +} + +int bt_settings_delete_sc(uint8_t id, const bt_addr_le_t *addr) +{ + return bt_settings_delete("sc", id, addr); +} + +int bt_settings_store_cf(uint8_t id, + const bt_addr_le_t *addr, const void *value, + size_t val_len) +{ + return bt_settings_store("cf", id, addr, value, val_len); +} + +int bt_settings_delete_cf(uint8_t id, const bt_addr_le_t *addr) +{ + return bt_settings_delete("cf", id, addr); +} + +int bt_settings_store_ccc(uint8_t id, + const bt_addr_le_t *addr, const void *value, + size_t val_len) +{ + return bt_settings_store("ccc", id, addr, value, val_len); +} + +int bt_settings_delete_ccc(uint8_t id, const bt_addr_le_t *addr) +{ + return bt_settings_delete("ccc", id, addr); +} + +int bt_settings_store_hash(const void *value, size_t val_len) +{ + return bt_settings_store("hash", 0, NULL, value, val_len); +} + +int bt_settings_delete_hash(void) +{ + return bt_settings_delete("hash", 0, NULL); +} + +int bt_settings_store_name(const void *value, size_t val_len) +{ + return bt_settings_store("name", 0, NULL, value, val_len); +} + +int bt_settings_delete_name(void) +{ + return bt_settings_delete("name", 0, NULL); +} + +int bt_settings_store_appearance(const void *value, size_t val_len) +{ + return bt_settings_store("appearance", 0, NULL, value, val_len); +} + +int bt_settings_delete_appearance(void) +{ + return bt_settings_delete("appearance", 0, NULL); +} + +static void do_store_id(struct k_work *work) +{ + int err = bt_settings_store("id", 0, NULL, &bt_dev.id_addr, + ID_DATA_LEN(bt_dev.id_addr)); + + if (err) + { + LOG_ERR("Failed to save ID (err %d)", err); + } +} + +K_WORK_DEFINE(store_id_work, do_store_id); + +int bt_settings_store_id(void) +{ + bt_work_submit(&store_id_work); + + return 0; +} + +int bt_settings_delete_id(void) +{ + return bt_settings_delete("id", 0, NULL); +} + +bool settings_name_steq(const char *name, const char *key, const char **next) +{ + size_t key_len = strlen(key); + + if (strncmp(name, key, key_len) != 0) + { + return false; + } + + if (next) + { + *next = &name[key_len]; + } + + return true; +} + +static void do_store_irk(struct k_work *work) +{ +#if defined(CONFIG_BT_PRIVACY) + int err = bt_settings_store("irk", 0, NULL, bt_dev.irk, + ID_DATA_LEN(bt_dev.irk)); + + if (err) + { + LOG_ERR("Failed to save IRK (err %d)", err); + } +#endif +} + +K_WORK_DEFINE(store_irk_work, do_store_irk); + +int bt_settings_store_irk(void) +{ +#if defined(CONFIG_BT_PRIVACY) + bt_work_submit(&store_irk_work); +#endif /* defined(CONFIG_BT_PRIVACY) */ + return 0; +} + +int bt_settings_delete_irk(void) +{ + return bt_settings_delete("irk", 0, NULL); +} + +void bt_settings_flush(void) +{ + struct k_work_sync sync; + + /* The store handlers read the identity state that bt_disable() is + * about to reset. Running after the reset they would persist empty or torn + * records, while canceling them would silently lose the latest identity + * update. Complete any pending stores now, while the state is still valid. + */ + + (void)k_work_flush(&store_id_work, &sync); + (void)k_work_flush(&store_irk_work, &sync); +} + +int bt_settings_store_link_key(const bt_addr_le_t *addr, + const void *value, size_t val_len) +{ + return bt_settings_store("link_key", 0, addr, value, val_len); +} + +int bt_settings_delete_link_key(const bt_addr_le_t *addr) +{ + return bt_settings_delete("link_key", 0, addr); +} + +int bt_settings_store_keys(uint8_t id, + const bt_addr_le_t *addr, const void *value, + size_t val_len) +{ + return bt_settings_store("keys", id, addr, value, val_len); +} + +int bt_settings_delete_keys(uint8_t id, const bt_addr_le_t *addr) +{ + return bt_settings_delete("keys", id, addr); +} diff --git a/libs/zblue/host/settings.h b/libs/zblue/host/settings.h new file mode 100644 index 0000000000000..b071f1950e8be --- /dev/null +++ b/libs/zblue/host/settings.h @@ -0,0 +1,95 @@ +/**************************************************************************** + * libs/zblue/host/settings.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2018 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include "common/bt_settings_commit.h" + +/* Max settings key length (with all components) */ +#define BT_SETTINGS_KEY_MAX 36 + +/* Base64-encoded string buffer size of in_size bytes */ +#define BT_SETTINGS_SIZE(in_size) ((((((in_size) - 1) / 3) * 4) + 4) + 1) + +#define BT_SETTINGS_DEFINE(_hname, _subtree, _set, _commit) \ + SETTINGS_STATIC_HANDLER_DEFINE_WITH_CPRIO(bt_##_hname, "bt/" _subtree, NULL, _set, _commit, \ + NULL, BT_SETTINGS_CPRIO_1) + +#define ID_DATA_LEN(array) (bt_dev.id_count * sizeof(array[0])) + +int bt_settings_store( + const char *key, uint8_t id, const bt_addr_le_t *addr, const void *value, + size_t val_len); +int bt_settings_delete(const char *key, + uint8_t id, const bt_addr_le_t *addr); + +void bt_testing_settings_store_hook(const char *key, + const void *value, size_t val_len); +void bt_testing_settings_delete_hook(const char *key); + +/* Helpers for keys containing a bdaddr */ + +int bt_settings_encode_key(char *path, size_t path_size, const char *subsys, + const bt_addr_le_t *addr, const char *key); +int bt_settings_decode_key(const char *key, bt_addr_le_t *addr); + +void bt_settings_save_id(void); + +int bt_settings_init(void); + +void bt_settings_flush(void); + +int bt_settings_store_sc(uint8_t id, + const bt_addr_le_t *addr, const void *value, + size_t val_len); +int bt_settings_delete_sc(uint8_t id, const bt_addr_le_t *addr); + +int bt_settings_store_cf(uint8_t id, + const bt_addr_le_t *addr, const void *value, + size_t val_len); +int bt_settings_delete_cf(uint8_t id, const bt_addr_le_t *addr); + +int bt_settings_store_ccc(uint8_t id, + const bt_addr_le_t *addr, const void *value, + size_t val_len); +int bt_settings_delete_ccc(uint8_t id, const bt_addr_le_t *addr); + +int bt_settings_store_hash(const void *value, size_t val_len); +int bt_settings_delete_hash(void); + +int bt_settings_store_name(const void *value, size_t val_len); +int bt_settings_delete_name(void); + +int bt_settings_store_appearance(const void *value, size_t val_len); +int bt_settings_delete_appearance(void); + +int bt_settings_store_id(void); +int bt_settings_delete_id(void); + +int bt_settings_store_irk(void); +int bt_settings_delete_irk(void); + +int bt_settings_store_link_key(const bt_addr_le_t *addr, + const void *value, size_t val_len); +int bt_settings_delete_link_key(const bt_addr_le_t *addr); + +int bt_settings_store_keys(uint8_t id, + const bt_addr_le_t *addr, const void *value, + size_t val_len); +int bt_settings_delete_keys(uint8_t id, const bt_addr_le_t *addr); diff --git a/libs/zblue/host/smp.c b/libs/zblue/host/smp.c new file mode 100644 index 0000000000000..742a279219a00 --- /dev/null +++ b/libs/zblue/host/smp.c @@ -0,0 +1,7855 @@ +/**************************************************************************** + * libs/zblue/host/smp.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file smp.c Security Manager Protocol implementation + */ + +/* Copyright (c) 2025 Xiaomi Corporation + * Copyright (c) 2017-2025 Nordic Semiconductor ASA Copyright (c) 2015-2016 + * Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "conn_internal.h" +#include "common/bt_str.h" +#include "common/rpa.h" +#include "crypto/bt_crypto.h" +#include "ecc.h" +#include "hci_core.h" +#include "keys.h" +#include "l2cap_internal.h" +#include "smp.h" + +#define LOG_LEVEL CONFIG_BT_SMP_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_smp); + +#define SMP_TIMEOUT SEC2TICK(30) + +/* Number of confirm/random rounds in the LE Secure Connections Passkey Entry + * protocol. The passkey is a 6-digit decimal number, which is carried one + * bit per round, least significant bit first. Core Spec 6.3, Vol 3, Part H, + * 2.3.5.6.3. + */ +#define SMP_PASSKEY_ROUNDS 20U + +#if defined(CONFIG_BT_SIGNING) +#define SIGN_DIST BT_SMP_DIST_SIGN +#else +#define SIGN_DIST 0 +#endif + +#if defined(CONFIG_BT_PRIVACY) +#define ID_DIST BT_SMP_DIST_ID_KEY +#else +#define ID_DIST 0 +#endif + +#if defined(CONFIG_BT_SMP_DERIVE_LK) +#define LINK_DIST BT_SMP_DIST_LINK_KEY +#else +#define LINK_DIST 0 +#endif + +#define RECV_KEYS (BT_SMP_DIST_ENC_KEY | BT_SMP_DIST_ID_KEY | SIGN_DIST | LINK_DIST) +#define SEND_KEYS (BT_SMP_DIST_ENC_KEY | ID_DIST | SIGN_DIST | LINK_DIST) + +#define RECV_KEYS_SC (RECV_KEYS & ~(BT_SMP_DIST_ENC_KEY)) +#define SEND_KEYS_SC (SEND_KEYS & ~(BT_SMP_DIST_ENC_KEY)) + +#define BR_RECV_KEYS_SC (RECV_KEYS & ~(LINK_DIST)) +#define BR_SEND_KEYS_SC (SEND_KEYS & ~(LINK_DIST)) + +#define BT_SMP_AUTH_MASK 0x07 + +#if defined(CONFIG_BT_BONDABLE) +#define BT_SMP_AUTH_BONDING_FLAGS BT_SMP_AUTH_BONDING +#else +#define BT_SMP_AUTH_BONDING_FLAGS 0 +#endif /* CONFIG_BT_BONDABLE */ + +#if defined(CONFIG_BT_CLASSIC) + +#define BT_SMP_AUTH_MASK_SC 0x2f +#if defined(CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY) || defined(CONFIG_BT_SMP_LEGACY_PAIR_ONLY) +#define BT_SMP_AUTH_DEFAULT (BT_SMP_AUTH_BONDING_FLAGS | BT_SMP_AUTH_CT2) +#else +#define BT_SMP_AUTH_DEFAULT (BT_SMP_AUTH_BONDING_FLAGS | BT_SMP_AUTH_CT2 | BT_SMP_AUTH_SC) +#endif /* CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY || CONFIG_BT_SMP_LEGACY_PAIR_ONLY */ + +#else + +#define BT_SMP_AUTH_MASK_SC 0x0f +#if defined(CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY) || defined(CONFIG_BT_SMP_LEGACY_PAIR_ONLY) +#define BT_SMP_AUTH_DEFAULT (BT_SMP_AUTH_BONDING_FLAGS) +#else +#define BT_SMP_AUTH_DEFAULT (BT_SMP_AUTH_BONDING_FLAGS | BT_SMP_AUTH_SC) +#endif /* CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY || CONFIG_BT_SMP_LEGACY_PAIR_ONLY */ + +#endif /* CONFIG_BT_CLASSIC */ + +enum pairing_method +{ + JUST_WORKS, /* JustWorks pairing */ + PASSKEY_INPUT, /* Passkey Entry input */ + PASSKEY_DISPLAY, /* Passkey Entry display */ + PASSKEY_CONFIRM, /* Passkey confirm */ + PASSKEY_ROLE, /* Passkey Entry depends on role */ + LE_SC_OOB, /* LESC Out of Band */ + LEGACY_OOB, /* Legacy Out of Band */ +}; + +enum +{ + SMP_FLAG_CFM_DELAYED, /* if confirm should be send when TK is valid */ + SMP_FLAG_ENC_PENDING, /* if waiting for an encryption change event */ + SMP_FLAG_KEYS_DISTR, /* if keys distribution phase is in progress */ + SMP_FLAG_PAIRING, /* if pairing is in progress */ + SMP_FLAG_TIMEOUT, /* if SMP timeout occurred */ + SMP_FLAG_SC, /* if LE Secure Connections is used */ + SMP_FLAG_PKEY_SEND, /* if should send Public Key when available */ + SMP_FLAG_DHKEY_PENDING, /* if waiting for local DHKey */ + SMP_FLAG_DHKEY_GEN, /* if generating DHKey */ + SMP_FLAG_DHKEY_SEND, /* if should generate and send DHKey Check */ + SMP_FLAG_USER, /* if waiting for user input */ + SMP_FLAG_DISPLAY, /* if display_passkey() callback was called */ + SMP_FLAG_OOB_PENDING, /* if waiting for OOB data */ + SMP_FLAG_BOND, /* if bonding */ + SMP_FLAG_SC_DEBUG_KEY, /* if Secure Connection are using debug key */ + SMP_FLAG_SEC_REQ, /* if Security Request was sent/received */ + SMP_FLAG_DHCHECK_WAIT, /* if waiting for remote DHCheck (as periph) */ + SMP_FLAG_DERIVE_LK, /* if Link Key should be derived */ + SMP_FLAG_BR_CONNECTED, /* if BR/EDR channel is connected */ + SMP_FLAG_BR_PAIR, /* if should start BR/EDR pairing */ + SMP_FLAG_CT2, /* if should use H7 for keys derivation */ + + /* Total number of flags - must be at the end */ + + SMP_NUM_FLAGS, +}; + +/* SMP channel specific context */ + +struct bt_smp +{ + /* Commands that remote is allowed to send */ + + atomic_define(allowed_cmds, BT_SMP_NUM_CMDS); + + /* Flags for SMP state machine */ + + atomic_define(flags, SMP_NUM_FLAGS); + + /* Type of method used for pairing */ + + uint8_t method; + + /* Pairing Request PDU */ + + uint8_t preq[7]; + + /* Pairing Response PDU */ + + uint8_t prsp[7]; + + /* Pairing Confirm PDU */ + + uint8_t pcnf[16]; + + /* Local random number */ + + uint8_t prnd[16]; + + /* Remote random number */ + + uint8_t rrnd[16]; + + /* Temporary key */ + + uint8_t tk[16]; + + /* Remote Public Key for LE SC */ + + uint8_t pkey[BT_PUB_KEY_LEN]; + + /* DHKey */ + + uint8_t dhkey[BT_DH_KEY_LEN]; + + /* Remote DHKey check */ + + uint8_t e[16]; + + /* MacKey */ + + uint8_t mackey[16]; + + /* LE SC passkey */ + + uint32_t passkey; + + /* LE SC passkey round */ + + uint8_t passkey_round; + + /* LE SC local OOB data */ + + const struct bt_le_oob_sc_data *oobd_local; + + /* LE SC remote OOB data */ + + const struct bt_le_oob_sc_data *oobd_remote; + + /* Local key distribution */ + + uint8_t local_dist; + + /* Remote key distribution */ + + uint8_t remote_dist; + + /* The channel this context is associated with. + * This marks the beginning of the part of the structure that will not be + * memset to zero in init. + */ + + struct bt_l2cap_le_chan chan; + + /* Delayed work for timeout handling */ + + struct k_work_delayable work; + + /* Used Bluetooth authentication callbacks. */ + + void *auth_cb; + + /* Bondable flag */ + + atomic_t bondable; +}; + +#if !defined(CONFIG_BT_SMP_SC_PAIR_ONLY) +/* based on table 2.8 Core Spec 2.3.5.1 Vol. 3 Part H */ + +static const uint8_t gen_method_legacy[5 /* remote */][5 /* local */] = +{ + { + JUST_WORKS, + JUST_WORKS, + PASSKEY_INPUT, + JUST_WORKS, + PASSKEY_INPUT + }, + { + JUST_WORKS, + JUST_WORKS, + PASSKEY_INPUT, + JUST_WORKS, + PASSKEY_INPUT + }, + { + PASSKEY_DISPLAY, + PASSKEY_DISPLAY, + PASSKEY_INPUT, + JUST_WORKS, + PASSKEY_DISPLAY + }, + { + JUST_WORKS, + JUST_WORKS, + JUST_WORKS, + JUST_WORKS, + JUST_WORKS + }, + { + PASSKEY_DISPLAY, + PASSKEY_DISPLAY, + PASSKEY_INPUT, + JUST_WORKS, + PASSKEY_ROLE + }, +}; +#endif /* CONFIG_BT_SMP_SC_PAIR_ONLY */ + +#if !defined(CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY) +/* based on table 2.8 Core Spec 2.3.5.1 Vol. 3 Part H */ + +static const uint8_t gen_method_sc[5 /* remote */][5 /* local */] = +{ + { + JUST_WORKS, + JUST_WORKS, + PASSKEY_INPUT, + JUST_WORKS, + PASSKEY_INPUT + }, + { + JUST_WORKS, + PASSKEY_CONFIRM, + PASSKEY_INPUT, + JUST_WORKS, + PASSKEY_CONFIRM + }, + { + PASSKEY_DISPLAY, + PASSKEY_DISPLAY, + PASSKEY_INPUT, + JUST_WORKS, + PASSKEY_DISPLAY + }, + { + JUST_WORKS, + JUST_WORKS, + JUST_WORKS, + JUST_WORKS, + JUST_WORKS + }, + { + PASSKEY_DISPLAY, + PASSKEY_CONFIRM, + PASSKEY_INPUT, + JUST_WORKS, + PASSKEY_CONFIRM + }, +}; +#endif /* !CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY */ + +#if defined(CONFIG_BT_CLASSIC) +/* SMP over BR/EDR channel specific context */ + +struct bt_smp_br +{ + /* Commands that remote is allowed to send */ + + atomic_define(allowed_cmds, BT_SMP_NUM_CMDS); + + /* Flags for SMP state machine */ + + atomic_define(flags, SMP_NUM_FLAGS); + + /* Local key distribution */ + + uint8_t local_dist; + + /* Remote key distribution */ + + uint8_t remote_dist; + + /* Local keys have been distributed */ + + uint8_t local_distributed; + + /* Encryption Key Size used for connection */ + + uint8_t enc_key_size; + + /* The channel this context is associated with. + * This marks the beginning of the part of the structure that will not be + * memset to zero in init. + */ + + struct bt_l2cap_br_chan chan; + + /* Delayed work for timeout handling */ + + struct k_work_delayable work; +}; + +static struct bt_smp_br bt_smp_br_pool[CONFIG_BT_MAX_CONN]; +#endif /* CONFIG_BT_CLASSIC */ + +static struct bt_smp bt_smp_pool[CONFIG_BT_MAX_CONN]; +static bool bondable = IS_ENABLED(CONFIG_BT_BONDABLE); +static bool sc_oobd_present; +static bool legacy_oobd_present; +static bool sc_supported; +static const uint8_t *sc_public_key; + +static void bt_smp_pkey_ready(const uint8_t *pkey); +static struct +{ + rmutex_t lock; + struct k_condvar condvar; + struct bt_pub_key_cb cb; +} + +pub_key_gen = +{ + .lock = NXRMUTEX_INITIALIZER, + .condvar = Z_CONDVAR_INITIALIZER(pub_key_gen.condvar), + .cb = + { + .func = bt_smp_pkey_ready, + }, +}; + +/* Pointer to internal data is used to mark that callbacks of given SMP + * channel are not initialized. Value of NULL represents no authentication + * capabilities and cannot be used for that purpose. + */ +#define BT_SMP_AUTH_CB_UNINITIALIZED ((void *)bt_smp_pool) + +/* Value used to mark that per-connection bondable flag is not initialized. + * Value false/true represent if flag is cleared or set and cannot be used + * for that purpose. + */ +#define BT_SMP_BONDABLE_UNINITIALIZED ((int32_t)-1) + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static bool le_sc_supported(void) +{ + if (IS_ENABLED(CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY) || + IS_ENABLED(CONFIG_BT_SMP_LEGACY_PAIR_ONLY)) + { + return false; + } + + return IS_ENABLED(CONFIG_BT_ECC); +} + +static const struct bt_conn_auth_cb *latch_auth_cb(struct bt_smp *smp) +{ + (void)atomic_ptr_cmpxchg(&smp->auth_cb, BT_SMP_AUTH_CB_UNINITIALIZED, + (void *)bt_auth); + + return atomic_ptr_read(&smp->auth_cb); +} + +static bool latch_bondable(struct bt_smp *smp) +{ + (void)atomic_cmpxchg_value(&smp->bondable, BT_SMP_BONDABLE_UNINITIALIZED, + (int32_t)bondable); + + return atomic_read(&smp->bondable); +} + +static uint8_t get_io_capa(struct bt_smp *smp) +{ + const struct bt_conn_auth_cb *smp_auth_cb = latch_auth_cb(smp); + + if (!smp_auth_cb) + { + goto no_callbacks; + } + + /* Passkey Confirmation is valid only for LE SC */ + + if (smp_auth_cb->passkey_display && smp_auth_cb->passkey_entry && + (smp_auth_cb->passkey_confirm || !sc_supported)) + { + return BT_SMP_IO_KEYBOARD_DISPLAY; + } + + /* DisplayYesNo is useful only for LE SC */ + + if (sc_supported && smp_auth_cb->passkey_display && + smp_auth_cb->passkey_confirm) + { + return BT_SMP_IO_DISPLAY_YESNO; + } + +#if defined(CONFIG_BT_APP_PASSKEY) + /* Implementation of the app_passkey cb implies that the application can + * "know" the passkey without actually having a display, thus earning the + * "display" capability. + */ + + if (smp_auth_cb->app_passkey) + { + if (smp_auth_cb->passkey_entry) + { + return BT_SMP_IO_KEYBOARD_DISPLAY; + } + + return BT_SMP_IO_DISPLAY_ONLY; + } +#endif /* CONFIG_BT_APP_PASSKEY */ + + if (smp_auth_cb->passkey_entry) + { + return BT_SMP_IO_KEYBOARD_ONLY; + } + + if (smp_auth_cb->passkey_display) + { + return BT_SMP_IO_DISPLAY_ONLY; + } + +no_callbacks: + return BT_SMP_IO_NO_INPUT_OUTPUT; +} + +#if !defined(CONFIG_BT_SMP_SC_PAIR_ONLY) +static uint8_t legacy_get_pair_method(struct bt_smp *smp, uint8_t remote_io); +#endif + +static bool smp_keys_check(struct bt_conn *conn) +{ + if (atomic_test_bit(conn->flags, BT_CONN_FORCE_PAIR)) + { + return false; + } + + if (!conn->le.keys) + { + conn->le.keys = bt_keys_find(BT_KEYS_LTK_P256, conn->id, + &conn->le.dst); + if (!conn->le.keys) + { + conn->le.keys = bt_keys_find(BT_KEYS_LTK, conn->id, &conn->le.dst); + } + } + + if (!conn->le.keys || + !(conn->le.keys->keys & (BT_KEYS_LTK | BT_KEYS_LTK_P256))) + { + return false; + } + + if (conn->required_sec_level >= BT_SECURITY_L3 && + !(conn->le.keys->flags & BT_KEYS_AUTHENTICATED)) + { + return false; + } + + if (conn->required_sec_level >= BT_SECURITY_L4 && + !((conn->le.keys->flags & BT_KEYS_AUTHENTICATED) && + (conn->le.keys->keys & BT_KEYS_LTK_P256) && + (conn->le.keys->enc_size == BT_SMP_MAX_ENC_KEY_SIZE))) + { + return false; + } + + return true; +} + +static uint8_t get_pair_method(struct bt_smp *smp, uint8_t remote_io) +{ +#if !defined(CONFIG_BT_SMP_SC_PAIR_ONLY) + if (!atomic_test_bit(smp->flags, SMP_FLAG_SC)) + { + return legacy_get_pair_method(smp, remote_io); + } +#endif + +#if !defined(CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY) + struct bt_smp_pairing *req; + struct bt_smp_pairing *rsp; + + req = (struct bt_smp_pairing *)&smp->preq[1]; + rsp = (struct bt_smp_pairing *)&smp->prsp[1]; + + if ((req->auth_req & rsp->auth_req) & BT_SMP_AUTH_SC) + { + /* if one side has OOB data use OOB */ + + if ((req->oob_flag | rsp->oob_flag) & BT_SMP_OOB_DATA_MASK) + { + return LE_SC_OOB; + } + } + + if (remote_io > BT_SMP_IO_KEYBOARD_DISPLAY) + { + return JUST_WORKS; + } + + /* if none side requires MITM use JustWorks */ + + if (!((req->auth_req | rsp->auth_req) & BT_SMP_AUTH_MITM)) + { + return JUST_WORKS; + } + + return gen_method_sc[remote_io][get_io_capa(smp)]; +#else + return JUST_WORKS; +#endif +} + +static enum bt_security_err security_err_get(uint8_t smp_err) +{ + switch (smp_err) + { + case BT_SMP_ERR_PASSKEY_ENTRY_FAILED: + case BT_SMP_ERR_DHKEY_CHECK_FAILED: + case BT_SMP_ERR_NUMERIC_COMP_FAILED: + case BT_SMP_ERR_CONFIRM_FAILED: + return BT_SECURITY_ERR_AUTH_FAIL; + case BT_SMP_ERR_OOB_NOT_AVAIL: + return BT_SECURITY_ERR_OOB_NOT_AVAILABLE; + case BT_SMP_ERR_AUTH_REQUIREMENTS: + case BT_SMP_ERR_ENC_KEY_SIZE: + return BT_SECURITY_ERR_AUTH_REQUIREMENT; + case BT_SMP_ERR_PAIRING_NOTSUPP: + case BT_SMP_ERR_CMD_NOTSUPP: + return BT_SECURITY_ERR_PAIR_NOT_SUPPORTED; + case BT_SMP_ERR_REPEATED_ATTEMPTS: + case BT_SMP_ERR_BREDR_PAIRING_IN_PROGRESS: + case BT_SMP_ERR_CROSS_TRANSP_NOT_ALLOWED: + return BT_SECURITY_ERR_PAIR_NOT_ALLOWED; + case BT_SMP_ERR_INVALID_PARAMS: + return BT_SECURITY_ERR_INVALID_PARAM; + case BT_SMP_ERR_KEY_REJECTED: + return BT_SECURITY_ERR_KEY_REJECTED; + case BT_SMP_ERR_UNSPECIFIED: + default: + return BT_SECURITY_ERR_UNSPECIFIED; + } +} + +#if defined(CONFIG_BT_SECURITY_ERR_TO_STR) +const char *bt_security_err_to_str(enum bt_security_err err) +{ +#define SEC_ERR(err) [err] = #err + + const char *const mapping_table[] = + { + SEC_ERR(BT_SECURITY_ERR_SUCCESS), + SEC_ERR(BT_SECURITY_ERR_AUTH_FAIL), + SEC_ERR(BT_SECURITY_ERR_PIN_OR_KEY_MISSING), + SEC_ERR(BT_SECURITY_ERR_OOB_NOT_AVAILABLE), + SEC_ERR(BT_SECURITY_ERR_AUTH_REQUIREMENT), + SEC_ERR(BT_SECURITY_ERR_PAIR_NOT_SUPPORTED), + SEC_ERR(BT_SECURITY_ERR_PAIR_NOT_ALLOWED), + SEC_ERR(BT_SECURITY_ERR_INVALID_PARAM), + SEC_ERR(BT_SECURITY_ERR_KEY_REJECTED), + SEC_ERR(BT_SECURITY_ERR_UNSPECIFIED), + }; + + if (err < ARRAY_SIZE(mapping_table) && mapping_table[err]) + { + return mapping_table[err]; + } + else + { + return "(unknown)"; + } + +#undef SEC_ERR +} +#endif /* CONFIG_BT_SECURITY_ERR_TO_STR */ + +static uint8_t smp_err_get(enum bt_security_err auth_err) +{ + switch (auth_err) + { + case BT_SECURITY_ERR_OOB_NOT_AVAILABLE: + return BT_SMP_ERR_OOB_NOT_AVAIL; + + case BT_SECURITY_ERR_AUTH_FAIL: + case BT_SECURITY_ERR_AUTH_REQUIREMENT: + return BT_SMP_ERR_AUTH_REQUIREMENTS; + + case BT_SECURITY_ERR_PAIR_NOT_SUPPORTED: + return BT_SMP_ERR_PAIRING_NOTSUPP; + + case BT_SECURITY_ERR_INVALID_PARAM: + return BT_SMP_ERR_INVALID_PARAMS; + + case BT_SECURITY_ERR_PIN_OR_KEY_MISSING: + case BT_SECURITY_ERR_PAIR_NOT_ALLOWED: + case BT_SECURITY_ERR_UNSPECIFIED: + return BT_SMP_ERR_UNSPECIFIED; + default: + return 0; + } +} + +#if defined(CONFIG_BT_SMP_ERR_TO_STR) +const char *bt_smp_err_to_str(uint8_t smp_err) +{ +#define SMP_ERR(err) [err] = #err + + const char *const mapping_table[] = + { + SMP_ERR(BT_SMP_ERR_SUCCESS), + SMP_ERR(BT_SMP_ERR_PASSKEY_ENTRY_FAILED), + SMP_ERR(BT_SMP_ERR_OOB_NOT_AVAIL), + SMP_ERR(BT_SMP_ERR_AUTH_REQUIREMENTS), + SMP_ERR(BT_SMP_ERR_CONFIRM_FAILED), + SMP_ERR(BT_SMP_ERR_PAIRING_NOTSUPP), + SMP_ERR(BT_SMP_ERR_ENC_KEY_SIZE), + SMP_ERR(BT_SMP_ERR_CMD_NOTSUPP), + SMP_ERR(BT_SMP_ERR_UNSPECIFIED), + SMP_ERR(BT_SMP_ERR_REPEATED_ATTEMPTS), + SMP_ERR(BT_SMP_ERR_INVALID_PARAMS), + SMP_ERR(BT_SMP_ERR_DHKEY_CHECK_FAILED), + SMP_ERR(BT_SMP_ERR_NUMERIC_COMP_FAILED), + SMP_ERR(BT_SMP_ERR_BREDR_PAIRING_IN_PROGRESS), + SMP_ERR(BT_SMP_ERR_CROSS_TRANSP_NOT_ALLOWED), + SMP_ERR(BT_SMP_ERR_KEY_REJECTED), + }; + + if (smp_err < ARRAY_SIZE(mapping_table) && mapping_table[smp_err]) + { + return mapping_table[smp_err]; + } + else + { + return "(unknown)"; + } + +#undef SMP_ERR +} +#endif /* CONFIG_BT_SMP_ERR_TO_STR */ + +static struct net_buf *smp_create_pdu(struct bt_smp *smp, + uint8_t op, size_t len) +{ + struct bt_smp_hdr *hdr; + struct net_buf *buf; + clock_t timeout; + + /* Don't if session had already timed out */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_TIMEOUT)) + { + timeout = 0; + } + else + { + timeout = SMP_TIMEOUT; + } + + /* Use smaller timeout if returning an error since that could be + * caused by lack of buffers. + */ + + buf = bt_l2cap_create_pdu_timeout(NULL, 0, timeout); + if (!buf) + { + /* If it was not possible to allocate a buffer within the + * timeout marked it as timed out. + */ + + atomic_set_bit(smp->flags, SMP_FLAG_TIMEOUT); + return NULL; + } + + hdr = net_buf_add(buf, sizeof(*hdr)); + hdr->code = op; + + return buf; +} + +static uint8_t get_encryption_key_size(struct bt_smp *smp) +{ + struct bt_smp_pairing *req; + struct bt_smp_pairing *rsp; + + req = (struct bt_smp_pairing *)&smp->preq[1]; + rsp = (struct bt_smp_pairing *)&smp->prsp[1]; + + /* The smaller value of the initiating and responding devices maximum + * encryption key length parameters shall be used as the encryption key + * size. + */ + + return MIN(req->max_key_size, rsp->max_key_size); +} + +/* Check that if a new pairing procedure with an existing bond will not lower + * the established security level of the bond. + */ + +static bool update_keys_check(struct bt_smp *smp, struct bt_keys *keys) +{ + if (IS_ENABLED(CONFIG_BT_SMP_DISABLE_LEGACY_JW_PASSKEY) && + !atomic_test_bit(smp->flags, SMP_FLAG_SC) && smp->method != LEGACY_OOB) + { + return false; + } + + if (IS_ENABLED(CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY) && + smp->method != LEGACY_OOB) + { + return false; + } + + if (!keys || !(keys->keys & (BT_KEYS_LTK_P256 | BT_KEYS_LTK))) + { + return true; + } + + if (keys->enc_size > get_encryption_key_size(smp)) + { + return false; + } + + if ((keys->keys & BT_KEYS_LTK_P256) && + !atomic_test_bit(smp->flags, SMP_FLAG_SC)) + { + return false; + } + + if ((keys->flags & BT_KEYS_AUTHENTICATED) && + ((smp->method == JUST_WORKS) || + (!atomic_test_bit(smp->flags, SMP_FLAG_SC) && + (smp->method == PASSKEY_DISPLAY || smp->method == PASSKEY_INPUT)))) + { + return false; + } + + if (!IS_ENABLED(CONFIG_BT_SMP_ALLOW_UNAUTH_OVERWRITE) && + (!(keys->flags & BT_KEYS_AUTHENTICATED) && smp->method == JUST_WORKS)) + { + if (!IS_ENABLED(CONFIG_BT_ID_ALLOW_UNAUTH_OVERWRITE) || + (keys->id == smp->chan.chan.conn->id)) + { + return false; + } + } + + return true; +} + +#ifndef CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY +static bool update_debug_keys_check(struct bt_smp *smp) +{ + struct bt_conn *conn = smp->chan.chan.conn; + + if (!conn->le.keys) + { + conn->le.keys = bt_keys_get_addr(conn->id, &conn->le.dst); + } + + if (!conn->le.keys || + !(conn->le.keys->keys & (BT_KEYS_LTK_P256 | BT_KEYS_LTK))) + { + return true; + } + + if (conn->le.keys->flags & BT_KEYS_DEBUG) + { + return true; + } + + return false; +} +#endif /* CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY */ + +#if defined(CONFIG_BT_PRIVACY) || defined(CONFIG_BT_SIGNING) || !defined(CONFIG_BT_SMP_SC_PAIR_ONLY) +/* For TX callbacks */ + +static void smp_pairing_complete(struct bt_smp *smp, uint8_t status); +#if defined(CONFIG_BT_CLASSIC) +static void smp_pairing_br_complete(struct bt_smp_br *smp, uint8_t status); +#endif + +static void smp_check_complete(struct bt_conn *conn, uint8_t dist_complete) +{ + struct bt_l2cap_chan *chan; + + if (bt_conn_is_le(conn)) + { + struct bt_smp *smp; + + chan = bt_l2cap_le_lookup_tx_cid(conn, BT_L2CAP_CID_SMP); + DEBUGASSERT(chan); + + smp = CONTAINER_OF(chan, struct bt_smp, chan.chan); + smp->local_dist &= ~dist_complete; + + /* if all keys were distributed, pairing is done */ + + if (!smp->local_dist && !smp->remote_dist) + { + smp_pairing_complete(smp, 0); + } + + return; + } + +#if defined(CONFIG_BT_CLASSIC) + if (bt_conn_is_br(conn)) + { + struct bt_smp_br *smp; + + chan = bt_l2cap_br_lookup_tx_cid(conn, BT_L2CAP_CID_BR_SMP); + DEBUGASSERT(chan); + + smp = CONTAINER_OF(chan, struct bt_smp_br, chan.chan); + smp->local_dist &= ~dist_complete; + + /* if all keys were distributed, pairing is done */ + + if (!smp->local_dist && !smp->remote_dist) + { + smp_pairing_br_complete(smp, 0); + } + } +#endif +} +#endif + +#if defined(CONFIG_BT_PRIVACY) +static void smp_id_sent(struct bt_conn *conn, void *user_data, int err) +{ + if (!err) + { + smp_check_complete(conn, BT_SMP_DIST_ID_KEY); + } +} +#endif /* CONFIG_BT_PRIVACY */ + +#if defined(CONFIG_BT_SIGNING) +static void smp_sign_info_sent(struct bt_conn *conn, + void *user_data, int err) +{ + if (!err) + { + smp_check_complete(conn, BT_SMP_DIST_SIGN); + } +} +#endif /* CONFIG_BT_SIGNING */ + +#if defined(CONFIG_BT_CLASSIC) +static bool ltk_derive_link_key_allowed(struct bt_smp *smp) +{ + struct bt_conn *conn; + struct bt_keys_link_key *link_key; + struct bt_keys *keys; + + if (!IS_ENABLED(CONFIG_BT_SMP_DERIVE_LK)) + { + return false; + } + + if (!smp->chan.chan.conn) + { + return false; + } + + conn = smp->chan.chan.conn; + keys = conn->le.keys; + if (keys == NULL) + { + return false; + } + + /* Check whether it is has been bonded */ + + link_key = bt_keys_find_link_key(&conn->le.dst.a); + if (link_key == NULL) + { + return true; + } + + if (link_key->flags & BT_LINK_KEY_DEBUG) + { + LOG_DBG("Debug LK can be overwrote"); + return true; + } + + if ((link_key->flags & BT_LINK_KEY_AUTHENTICATED) && + ((keys->flags & BT_KEYS_AUTHENTICATED) == 0)) + { + LOG_DBG("Stronger LK (MITM) cannot be overwrote by weaker LTK"); + return false; + } + + if ((link_key->flags & BT_LINK_KEY_SC) && ((keys->flags & BT_KEYS_SC) == + 0)) + { + LOG_DBG("Stronger LK (SC) cannot be overwrote by weaker LTK"); + return false; + } + + return true; +} + +static void sc_derive_link_key(struct bt_smp *smp) +{ + /* constants as specified in Core Spec Vol.3 Part H 2.4.2.4 */ + + static const uint8_t lebr[4] = + { + 0x72, 0x62, 0x65, 0x6c + }; + + struct bt_conn *conn = smp->chan.chan.conn; + struct bt_keys_link_key *link_key; + uint8_t ilk[16]; + + LOG_DBG(""); + + /* TODO handle errors? */ + + if (!ltk_derive_link_key_allowed(smp)) + { + LOG_DBG("LK cannot be derived by LTK"); + return; + } + + /* Remove the bonding information */ + + link_key = bt_keys_find_link_key(&conn->le.dst.a); + if (link_key != NULL) + { + bt_keys_link_key_clear(link_key); + } + + /* At this point remote device identity is known so we can use + * destination address here + */ + + link_key = bt_keys_get_link_key(&conn->le.dst.a); + if (!link_key) + { + return; + } + + if (atomic_test_bit(smp->flags, SMP_FLAG_CT2)) + { + /* constants as specified in Core Spec Vol.3 Part H 2.4.2.4 */ + + static const uint8_t salt[16] = + { + 0x31, 0x70, 0x6d, 0x74, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + if (bt_crypto_h7(salt, conn->le.keys->ltk.val, ilk)) + { + bt_keys_link_key_clear(link_key); + return; + } + } + else + { + /* constants as specified in Core Spec Vol.3 Part H 2.4.2.4 */ + + static const uint8_t tmp1[4] = + { + 0x31, 0x70, 0x6d, 0x74 + }; + + if (bt_crypto_h6(conn->le.keys->ltk.val, tmp1, ilk)) + { + bt_keys_link_key_clear(link_key); + return; + } + } + + if (bt_crypto_h6(ilk, lebr, link_key->val)) + { + bt_keys_link_key_clear(link_key); + } + + if (conn->le.keys->flags & BT_KEYS_SC) + { + link_key->flags |= BT_LINK_KEY_SC; + } + else + { + link_key->flags &= ~BT_LINK_KEY_SC; + } + + if (conn->le.keys->flags & BT_KEYS_AUTHENTICATED) + { + link_key->flags |= BT_LINK_KEY_AUTHENTICATED; + } + else + { + link_key->flags &= ~BT_LINK_KEY_AUTHENTICATED; + } + + if (atomic_test_bit(smp->flags, SMP_FLAG_BOND)) + { + /* Store the link key */ + + bt_keys_link_key_store(link_key); + } +} + +static void smp_br_reset(struct bt_smp_br *smp) +{ + bool br_smp_connected; + + br_smp_connected = atomic_test_bit(smp->flags, SMP_FLAG_BR_CONNECTED); + + /* Clear flags first in case canceling of timeout fails. The SMP context + * shall be marked as timed out in that case. + */ + + atomic_set(smp->flags, 0); + + /* Set back the status of the flag SMP_FLAG_BR_CONNECTED. */ + + atomic_set_bit_to(smp->flags, SMP_FLAG_BR_CONNECTED, br_smp_connected); + + /* If canceling fails the timeout handler will set the timeout flag and + * mark the it as timed out. No new pairing procedures shall be started on + * this connection if that happens. + */ + + (void)k_work_cancel_delayable(&smp->work); + + atomic_set(smp->allowed_cmds, 0); + + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_REQ); +} + +static void smp_br_id_add_replace(struct bt_keys *keys) +{ + struct bt_keys *conflict; + + /* Check whether key has been added to resolving list. */ + + if (keys->state & BT_KEYS_ID_ADDED) + { + bt_id_del(keys); + } + + conflict = bt_id_find_conflict(keys); + if (conflict != NULL) + { + __maybe_unused int err; + + LOG_DBG("Un-pairing old conflicting bond and finalizing new."); + + err = bt_unpair(conflict->id, &conflict->addr); + DEBUGASSERT(!err); + } + + DEBUGASSERT(!bt_id_find_conflict(keys)); + bt_id_add(keys); +} + +static void smp_pairing_br_complete(struct bt_smp_br *smp, uint8_t status) +{ + struct bt_conn *conn = smp->chan.chan.conn; + struct bt_keys *keys; + bt_addr_le_t addr; + + LOG_DBG("status 0x%x", status); + + /* For dualmode devices LE address is same as BR/EDR address + * and is of public type. + */ + + bt_addr_le_copy_addr(&addr, &conn->br.dst, BT_ADDR_LE_PUBLIC); + keys = bt_keys_find_addr(conn->id, &addr); + + if (status) + { + struct bt_conn_auth_info_cb *listener; + struct bt_conn_auth_info_cb *next; + + if (keys) + { + bt_keys_clear(keys); + } + + list_for_every_container_safe(&bt_auth_info_cbs, listener, next, node) + { + if (listener->pairing_failed) + { + listener->pairing_failed(smp->chan.chan.conn, + security_err_get(status)); + } + } + } + else + { + bool bond_flag = !atomic_test_bit(conn->flags, BT_CONN_BR_NOBOND); + struct bt_conn_auth_info_cb *listener; + struct bt_conn_auth_info_cb *next; + + if (keys) + { + smp_br_id_add_replace(keys); + } + + if (bond_flag && keys) + { + bt_keys_store(keys); + } + + list_for_every_container_safe(&bt_auth_info_cbs, listener, next, node) + { + if (listener->pairing_complete) + { + listener->pairing_complete(smp->chan.chan.conn, bond_flag); + } + } + } + + smp_br_reset(smp); +} + +static void smp_br_timeout(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_smp_br *smp = CONTAINER_OF(dwork, struct bt_smp_br, work); + + LOG_ERR("SMP Timeout"); + + smp_pairing_br_complete(smp, BT_SMP_ERR_UNSPECIFIED); + atomic_set_bit(smp->flags, SMP_FLAG_TIMEOUT); +} + +static void smp_br_send(struct bt_smp_br *smp, + struct net_buf *buf, bt_conn_tx_cb_t cb) +{ + int err = bt_l2cap_br_send_cb(smp->chan.chan.conn, BT_L2CAP_CID_BR_SMP, + buf, + cb, NULL); + + if (err) + { + if (err == -ENOBUFS) + { + LOG_ERR("Ran out of TX buffers or contexts."); + } + + net_buf_unref(buf); + return; + } + + bt_work_reschedule(&smp->work, SMP_TIMEOUT); +} + +static void bt_smp_br_connected(struct bt_l2cap_chan *chan) +{ + struct bt_smp_br *smp = CONTAINER_OF(chan, struct bt_smp_br, chan.chan); + + LOG_DBG("chan %p cid 0x%04x", chan, CONTAINER_OF(chan, + struct bt_l2cap_br_chan, chan)->tx.cid); + + atomic_set_bit(smp->flags, SMP_FLAG_BR_CONNECTED); + + /* if this flag is set it means pairing was requested before channel + * was connected + */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_BR_PAIR)) + { + bt_smp_br_send_pairing_req(chan->conn); + } +} + +static void bt_smp_br_disconnected(struct bt_l2cap_chan *chan) +{ + struct bt_smp_br *smp = CONTAINER_OF(chan, struct bt_smp_br, chan.chan); + + LOG_DBG("chan %p cid 0x%04x", chan, CONTAINER_OF(chan, + struct bt_l2cap_br_chan, chan)->tx.cid); + + /* The channel disconnected callback and timeout work both run on the + * Bluetooth workqueue, so canceling the timeout work should always + * succeed. + */ + + (void)k_work_cancel_delayable(&smp->work); + + (void)memset(smp, 0, sizeof(*smp)); +} + +static void smp_br_init(struct bt_smp_br *smp) +{ + bool br_smp_connected; + + br_smp_connected = atomic_test_bit(smp->flags, SMP_FLAG_BR_CONNECTED); + + /* Initialize SMP context excluding L2CAP channel context and anything + * else declared after. + */ + + (void)memset(smp, 0, offsetof(struct bt_smp_br, chan)); + + /* Set back the status of the flag SMP_FLAG_BR_CONNECTED. */ + + atomic_set_bit_to(smp->flags, SMP_FLAG_BR_CONNECTED, br_smp_connected); + + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_FAIL); +} + +static void smp_br_derive_ltk(struct bt_smp_br *smp) +{ + /* constants as specified in Core Spec Vol.3 Part H 2.4.2.5 */ + + static const uint8_t brle[4] = + { + 0x65, 0x6c, 0x72, 0x62 + }; + + struct bt_conn *conn = smp->chan.chan.conn; + struct bt_keys_link_key *link_key = conn->br.link_key; + struct bt_keys *keys; + bt_addr_le_t addr; + uint8_t ilk[16]; + + LOG_DBG(""); + + if (!link_key) + { + return; + } + + if (IS_ENABLED(CONFIG_BT_SMP_FORCE_BREDR) && + conn->encrypt != BT_HCI_ENCRYPTION_ON_BR_AES_CCM) + { + LOG_WRN("Using P192 Link Key for P256 LTK derivation"); + } + + /* For dualmode devices LE address is same as BR/EDR address and is of + * public type. + */ + + bt_addr_le_copy_addr(&addr, &conn->br.dst, BT_ADDR_LE_PUBLIC); + + keys = bt_keys_find_addr(conn->id, &addr); + if (keys != NULL) + { + LOG_DBG("Clear the current keys for %s", bt_addr_le_str(&addr)); + bt_keys_clear(keys); + } + + keys = bt_keys_get_type(BT_KEYS_LTK_P256, conn->id, &addr); + if (!keys) + { + LOG_ERR("Unable to get keys for %s", bt_addr_le_str(&addr)); + return; + } + + if (atomic_test_bit(smp->flags, SMP_FLAG_CT2)) + { + /* constants as specified in Core Spec Vol.3 Part H 2.4.2.5 */ + + static const uint8_t salt[16] = + { + 0x32, 0x70, 0x6d, 0x74, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + if (bt_crypto_h7(salt, link_key->val, ilk)) + { + bt_keys_link_key_clear(link_key); + return; + } + } + else + { + /* constants as specified in Core Spec Vol.3 Part H 2.4.2.5 */ + + static const uint8_t tmp2[4] = + { + 0x32, 0x70, 0x6d, 0x74 + }; + + if (bt_crypto_h6(link_key->val, tmp2, ilk)) + { + bt_keys_clear(keys); + return; + } + } + + if (bt_crypto_h6(ilk, brle, keys->ltk.val)) + { + bt_keys_clear(keys); + return; + } + + (void)memset(keys->ltk.ediv, 0, sizeof(keys->ltk.ediv)); + (void)memset(keys->ltk.rand, 0, sizeof(keys->ltk.rand)); + keys->enc_size = smp->enc_key_size; + + if (link_key->flags & BT_LINK_KEY_AUTHENTICATED) + { + keys->flags |= BT_KEYS_AUTHENTICATED; + } + else + { + keys->flags &= ~BT_KEYS_AUTHENTICATED; + } + + if (conn->encrypt == BT_HCI_ENCRYPTION_ON_BR_AES_CCM) + { + keys->flags |= BT_KEYS_SC; + } + else + { + keys->flags &= ~BT_KEYS_SC; + } + + LOG_DBG("LTK derived from LinkKey"); +} + +static struct net_buf *smp_br_create_pdu(struct bt_smp_br *smp, + uint8_t op, size_t len) +{ + struct bt_smp_hdr *hdr; + struct net_buf *buf; + clock_t timeout; + + /* Don't if session had already timed out */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_TIMEOUT)) + { + timeout = 0; + } + else + { + timeout = SMP_TIMEOUT; + } + + /* Use smaller timeout if returning an error since that could be + * caused by lack of buffers. + */ + + buf = bt_l2cap_create_pdu_timeout(NULL, 0, timeout); + if (!buf) + { + /* If it was not possible to allocate a buffer within the + * timeout marked it as timed out. + */ + + atomic_set_bit(smp->flags, SMP_FLAG_TIMEOUT); + return NULL; + } + + hdr = net_buf_add(buf, sizeof(*hdr)); + hdr->code = op; + + return buf; +} + +static void smp_br_distribute_keys(struct bt_smp_br *smp) +{ + struct bt_conn *conn = smp->chan.chan.conn; + struct bt_keys *keys; + bt_addr_le_t addr; + + /* For dualmode devices LE address is same as BR/EDR address and is of + * public type. + */ + + bt_addr_le_copy_addr(&addr, &conn->br.dst, BT_ADDR_LE_PUBLIC); + + keys = bt_keys_get_addr(conn->id, &addr); + if (!keys) + { + LOG_ERR("No keys space for %s", bt_addr_le_str(&addr)); + return; + } + +#if defined(CONFIG_BT_PRIVACY) + if ((smp->local_dist & BT_SMP_DIST_ID_KEY) && + ((smp->local_distributed & BT_SMP_DIST_ID_KEY) == 0)) + { + struct bt_smp_ident_info *id_info; + struct bt_smp_ident_addr_info *id_addr_info; + struct net_buf *buf; + + smp->local_distributed |= BT_SMP_DIST_ID_KEY; + + buf = smp_br_create_pdu(smp, BT_SMP_CMD_IDENT_INFO, sizeof(*id_info)); + if (!buf) + { + LOG_ERR("Unable to allocate Ident Info buffer"); + return; + } + + id_info = net_buf_add(buf, sizeof(*id_info)); + memcpy(id_info->irk, bt_dev.irk[conn->id], 16); + + smp_br_send(smp, buf, NULL); + + buf = smp_br_create_pdu(smp, BT_SMP_CMD_IDENT_ADDR_INFO, + sizeof(*id_addr_info)); + if (!buf) + { + LOG_ERR("Unable to allocate Ident Addr Info buffer"); + return; + } + + id_addr_info = net_buf_add(buf, sizeof(*id_addr_info)); + bt_addr_le_copy(&id_addr_info->addr, &bt_dev.id_addr[conn->id]); + + smp_br_send(smp, buf, smp_id_sent); + } +#endif /* CONFIG_BT_PRIVACY */ + +#if defined(CONFIG_BT_SIGNING) + if ((smp->local_dist & BT_SMP_DIST_SIGN) && + ((smp->local_distributed & BT_SMP_DIST_SIGN) == 0)) + { + struct bt_smp_signing_info *info; + struct net_buf *buf; + + smp->local_distributed |= BT_SMP_DIST_SIGN; + + buf = smp_br_create_pdu(smp, BT_SMP_CMD_SIGNING_INFO, sizeof(*info)); + if (!buf) + { + LOG_ERR("Unable to allocate Signing Info buffer"); + return; + } + + info = net_buf_add(buf, sizeof(*info)); + + if (bt_rand(info->csrk, sizeof(info->csrk))) + { + LOG_ERR("Unable to get random bytes"); + return; + } + + if (atomic_test_bit(smp->flags, SMP_FLAG_BOND)) + { + bt_keys_add_type(keys, BT_KEYS_LOCAL_CSRK); + memcpy(keys->local_csrk.val, info->csrk, 16); + keys->local_csrk.cnt = 0U; + } + + smp_br_send(smp, buf, smp_sign_info_sent); + } +#endif /* CONFIG_BT_SIGNING */ +} + +static bool smp_br_pairing_allowed(struct bt_smp_br *smp) +{ + bt_addr_le_t addr; + struct bt_conn *conn; + struct bt_keys_link_key *key; + struct bt_keys *le_keys; + + if (!IS_ENABLED(CONFIG_BT_SMP_DERIVE_LTK)) + { + return false; + } + + if (!smp->chan.chan.conn) + { + return false; + } + + conn = smp->chan.chan.conn; + + bt_addr_le_copy_addr(&addr, &conn->br.dst, BT_ADDR_LE_PUBLIC); + le_keys = bt_keys_find_addr(BT_ID_DEFAULT, &addr); + + key = bt_keys_find_link_key(&conn->br.dst); + if (!key) + { + return false; + } + +/* Core v6.0, Vol 3, Part C, 14.1 Cross-transport key derivation + * + * If an LE LTK already exists and the BR/EDR link key is weaker in either + * strength or MITM protection, then neither device shall generate an LE + * LTK using cross-transport key derivation from a BR/EDR link key. + */ + + if ((le_keys != NULL) && ((le_keys->flags & BT_KEYS_AUTHENTICATED) != 0) && + ((key->flags & BT_LINK_KEY_AUTHENTICATED) == 0)) + { + LOG_WRN("Stronger LTK (MITM) cannot be overwrote by weaker LK"); + return false; + } + + if (conn->encrypt == BT_HCI_ENCRYPTION_ON_BR_AES_CCM) + { + return true; + } + + if (IS_ENABLED(CONFIG_BT_SMP_FORCE_BREDR) && + conn->encrypt == BT_HCI_ENCRYPTION_ON_BR_E0) + { + LOG_WRN("Allowing BR/EDR SMP with P-192 key"); + return true; + } + + return false; +} + +static uint8_t smp_br_pairing_req(struct bt_smp_br *smp, struct net_buf *buf) +{ + struct bt_smp_pairing *req = (void *)buf->data; + struct bt_conn *conn = smp->chan.chan.conn; + struct bt_smp_pairing *rsp; + struct net_buf *rsp_buf; + uint8_t max_key_size; + + LOG_DBG("req: io_capability 0x%02X, oob_flag 0x%02X, auth_req 0x%02X, " + "max_key_size 0x%02X, init_key_dist 0x%02X, resp_key_dist 0x%02X", + req->io_capability, req->oob_flag, req->auth_req, + req->max_key_size, req->init_key_dist, + req->resp_key_dist); + + /* If a Pairing Request is received over the BR/EDR transport when + * either cross-transport key derivation/generation is not supported or the + * BR/EDR transport is not encrypted using a Link Key generated using P256, + * a Pairing Failed shall be sent with the error code "Cross-transport Key + * Derivation/Generation not allowed" (0x0E)." + */ + + if (!smp_br_pairing_allowed(smp)) + { + return BT_SMP_ERR_CROSS_TRANSP_NOT_ALLOWED; + } + + max_key_size = bt_conn_enc_key_size(conn); + if (!max_key_size) + { + LOG_DBG("Invalid encryption key size"); + return BT_SMP_ERR_UNSPECIFIED; + } + + if (req->max_key_size != max_key_size) + { + return BT_SMP_ERR_ENC_KEY_SIZE; + } + + rsp_buf = smp_br_create_pdu(smp, BT_SMP_CMD_PAIRING_RSP, sizeof(*rsp)); + if (!rsp_buf) + { + return BT_SMP_ERR_UNSPECIFIED; + } + + smp_br_init(smp); + smp->enc_key_size = max_key_size; + + /* If Secure Connections pairing has been initiated over BR/EDR, the IO + * Capability, OOB data flag and Auth Req fields of the SM Pairing + * Request/Response PDU shall be set to zero on transmission, and ignored + * on reception. + */ + + rsp = net_buf_add(rsp_buf, sizeof(*rsp)); + + rsp->auth_req = BT_SMP_AUTH_CT2; + rsp->io_capability = 0x00; + rsp->oob_flag = 0x00; + rsp->max_key_size = max_key_size; + rsp->init_key_dist = (req->init_key_dist & BR_RECV_KEYS_SC); + rsp->resp_key_dist = (req->resp_key_dist & BR_SEND_KEYS_SC); + + smp->local_dist = rsp->resp_key_dist; + smp->remote_dist = rsp->init_key_dist; + + LOG_DBG("rsp: io_capability 0x%02X, oob_flag 0x%02X, auth_req 0x%02X, " + "max_key_size 0x%02X, init_key_dist 0x%02X, resp_key_dist 0x%02X", + rsp->io_capability, rsp->oob_flag, rsp->auth_req, + rsp->max_key_size, rsp->init_key_dist, + rsp->resp_key_dist); + + smp_br_send(smp, rsp_buf, NULL); + + atomic_set_bit(smp->flags, SMP_FLAG_PAIRING); + + /* If CT2 bit is set both side, set CT2 flag */ + + if ((rsp->auth_req & BT_SMP_AUTH_CT2) && (req->auth_req & BT_SMP_AUTH_CT2)) + { + atomic_set_bit(smp->flags, SMP_FLAG_CT2); + } + + /* derive LTK if requested and clear distribution bits */ + + if ((smp->local_dist & BT_SMP_DIST_ENC_KEY) && + (smp->remote_dist & BT_SMP_DIST_ENC_KEY)) + { + smp_br_derive_ltk(smp); + } + + smp->local_dist &= ~BT_SMP_DIST_ENC_KEY; + smp->remote_dist &= ~BT_SMP_DIST_ENC_KEY; + + /* BR/EDR acceptor is like LE Peripheral and distributes keys first */ + + smp_br_distribute_keys(smp); + + if (smp->remote_dist & BT_SMP_DIST_ID_KEY) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_IDENT_INFO); + } + else if (smp->remote_dist & BT_SMP_DIST_SIGN) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_SIGNING_INFO); + } + + /* if all keys were distributed, pairing is done */ + + if (!smp->local_dist && !smp->remote_dist) + { + smp_pairing_br_complete(smp, 0); + } + + return 0; +} + +static uint8_t smp_br_pairing_rsp(struct bt_smp_br *smp, struct net_buf *buf) +{ + struct bt_smp_pairing *rsp = (void *)buf->data; + struct bt_conn *conn = smp->chan.chan.conn; + uint8_t max_key_size; + + LOG_DBG("rsp: io_capability 0x%02X, oob_flag 0x%02X, auth_req 0x%02X, " + "max_key_size 0x%02X, init_key_dist 0x%02X, resp_key_dist 0x%02X", + rsp->io_capability, rsp->oob_flag, rsp->auth_req, + rsp->max_key_size, rsp->init_key_dist, + rsp->resp_key_dist); + + max_key_size = bt_conn_enc_key_size(conn); + if (!max_key_size) + { + LOG_DBG("Invalid encryption key size"); + return BT_SMP_ERR_UNSPECIFIED; + } + + if (rsp->max_key_size != max_key_size) + { + return BT_SMP_ERR_ENC_KEY_SIZE; + } + + smp->local_dist &= rsp->init_key_dist; + smp->remote_dist &= rsp->resp_key_dist; + + smp->local_dist &= BR_SEND_KEYS_SC; + smp->remote_dist &= BR_RECV_KEYS_SC; + + /* Peripheral distributes its keys first */ + + if (smp->remote_dist & BT_SMP_DIST_ID_KEY) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_IDENT_INFO); + } + else if (smp->remote_dist & BT_SMP_DIST_SIGN) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_SIGNING_INFO); + } + + /* CT2 flag is set in pairing req by bt_smp_br_send_pairing_req. + * If CT2 bit is set both side, set CT2 flag. + */ + + if (rsp->auth_req & BT_SMP_AUTH_CT2) + { + atomic_set_bit(smp->flags, SMP_FLAG_CT2); + } + + /* derive LTK if requested and clear distribution bits */ + + if ((smp->local_dist & BT_SMP_DIST_ENC_KEY) && + (smp->remote_dist & BT_SMP_DIST_ENC_KEY)) + { + smp_br_derive_ltk(smp); + } + + smp->local_dist &= ~BT_SMP_DIST_ENC_KEY; + smp->remote_dist &= ~BT_SMP_DIST_ENC_KEY; + + /* Pairing acceptor distributes it's keys first */ + + if (smp->remote_dist) + { + return 0; + } + + smp_br_distribute_keys(smp); + + /* if all keys were distributed, pairing is done */ + + if (!smp->local_dist && !smp->remote_dist) + { + smp_pairing_br_complete(smp, 0); + } + + return 0; +} + +static uint8_t smp_br_pairing_failed(struct bt_smp_br *smp, + struct net_buf *buf) +{ + struct bt_smp_pairing_fail *req = (void *)buf->data; + + LOG_ERR("pairing failed (peer reason 0x%x)", req->reason); + + smp_pairing_br_complete(smp, req->reason); + smp_br_reset(smp); + + /* return no error to avoid sending Pairing Failed in response */ + + return 0; +} + +static uint8_t smp_br_ident_info(struct bt_smp_br *smp, struct net_buf *buf) +{ + struct bt_smp_ident_info *req = (void *)buf->data; + struct bt_conn *conn = smp->chan.chan.conn; + struct bt_keys *keys; + bt_addr_le_t addr; + + LOG_DBG(""); + + /* TODO should we resolve LE address if matching RPA is connected? */ + + /* For dualmode devices LE address is same as BR/EDR address and is of + * public type. + */ + + bt_addr_le_copy_addr(&addr, &conn->br.dst, BT_ADDR_LE_PUBLIC); + + keys = bt_keys_get_type(BT_KEYS_IRK, conn->id, &addr); + if (!keys) + { + LOG_ERR("Unable to get keys for %s", bt_addr_le_str(&addr)); + return BT_SMP_ERR_UNSPECIFIED; + } + + memcpy(keys->irk.val, req->irk, sizeof(keys->irk.val)); + + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_IDENT_ADDR_INFO); + + return 0; +} + +static void convert_to_id_on_irk_match(struct bt_conn *conn, void *data) +{ + struct bt_keys *keys = data; + + if (!bt_addr_le_is_rpa(&conn->le.dst)) + { + return; + } + + if (bt_rpa_irk_matches(keys->irk.val, &conn->le.dst.a)) + { + if (conn->le.keys != NULL && conn->le.keys != keys) + { + bt_keys_clear(conn->le.keys); + } + + conn->le.keys = keys; + + /* always update last use RPA */ + + bt_addr_copy(&keys->irk.rpa, &conn->le.dst.a); + bt_addr_le_copy(&conn->le.dst, &keys->addr); + } +} + +static uint8_t smp_br_ident_addr_info(struct bt_smp_br *smp, + struct net_buf *buf) +{ + struct bt_conn *conn = smp->chan.chan.conn; + struct bt_smp_ident_addr_info *req = (void *)buf->data; + bt_addr_le_t addr; + struct bt_keys *keys; + + LOG_DBG("identity %s", bt_addr_le_str(&req->addr)); + + /* For dual mode device identity address must be same as BR/EDR address + * and be of public type. So if received one doesn't match BR/EDR address + * we fail. + */ + + bt_addr_le_copy_addr(&addr, &conn->br.dst, BT_ADDR_LE_PUBLIC); + + if (!bt_addr_le_eq(&addr, &req->addr)) + { + return BT_SMP_ERR_UNSPECIFIED; + } + + smp->remote_dist &= ~BT_SMP_DIST_ID_KEY; + + if (smp->remote_dist & BT_SMP_DIST_SIGN) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_SIGNING_INFO); + } + + /* Check the BLE connections that has RPA matched with this IRK */ + + keys = bt_keys_get_type(BT_KEYS_IRK, conn->id, &addr); + if (keys) + { + bt_conn_foreach(BT_CONN_TYPE_LE, convert_to_id_on_irk_match, keys); + } + else + { + LOG_ERR("Unable to get keys for %s", bt_addr_le_str(&addr)); + } + + if (conn->role == BT_CONN_ROLE_CENTRAL && !smp->remote_dist) + { + smp_br_distribute_keys(smp); + } + + /* if all keys were distributed, pairing is done */ + + if (!smp->local_dist && !smp->remote_dist) + { + /* TODO: consider the follow cases (BLE to BR derivation need to + * consider them too): (1) the ble connection is already encrypted, + * then the new LTK is derived. (2) the ble connection is not + * encrypted, the LTK is derived, how to trigger the BLE encryption. + */ + + smp_pairing_br_complete(smp, 0); + } + + return 0; +} + +#if defined(CONFIG_BT_SIGNING) +static uint8_t smp_br_signing_info(struct bt_smp_br *smp, + struct net_buf *buf) +{ + struct bt_smp_signing_info *req = (void *)buf->data; + struct bt_conn *conn = smp->chan.chan.conn; + struct bt_keys *keys; + bt_addr_le_t addr; + + LOG_DBG(""); + + /* For dualmode devices LE address is same as BR/EDR address and is of + * public type. + */ + + bt_addr_le_copy_addr(&addr, &conn->br.dst, BT_ADDR_LE_PUBLIC); + + keys = bt_keys_get_type(BT_KEYS_REMOTE_CSRK, conn->id, &addr); + if (!keys) + { + LOG_ERR("Unable to get keys for %s", bt_addr_le_str(&addr)); + return BT_SMP_ERR_UNSPECIFIED; + } + + memcpy(keys->remote_csrk.val, req->csrk, sizeof(keys->remote_csrk.val)); + + smp->remote_dist &= ~BT_SMP_DIST_SIGN; + + if (conn->role == BT_CONN_ROLE_CENTRAL && !smp->remote_dist) + { + smp_br_distribute_keys(smp); + } + + /* if all keys were distributed, pairing is done */ + + if (!smp->local_dist && !smp->remote_dist) + { + smp_pairing_br_complete(smp, 0); + } + + return 0; +} +#else +static uint8_t smp_br_signing_info(struct bt_smp_br *smp, + struct net_buf *buf) +{ + return BT_SMP_ERR_CMD_NOTSUPP; +} +#endif /* CONFIG_BT_SIGNING */ + +static const struct +{ + uint8_t (*func)(struct bt_smp_br *smp, struct net_buf *buf); + uint8_t expect_len; +} + +br_handlers[] = +{ + { + }, + + /* No op-code defined for 0x00 */ + + { + smp_br_pairing_req, sizeof(struct bt_smp_pairing) + }, + + { + smp_br_pairing_rsp, sizeof(struct bt_smp_pairing) + }, + + { + }, + + /* pairing confirm not used over BR/EDR */ + + { + }, + + /* pairing random not used over BR/EDR */ + + { + smp_br_pairing_failed, sizeof(struct bt_smp_pairing_fail) + }, + + { + }, + + /* encrypt info not used over BR/EDR */ + + { + }, + + /* central ident not used over BR/EDR */ + + { + smp_br_ident_info, sizeof(struct bt_smp_ident_info) + }, + + { + smp_br_ident_addr_info, sizeof(struct bt_smp_ident_addr_info) + }, + + { + smp_br_signing_info, sizeof(struct bt_smp_signing_info) + }, + + /* security request not used over BR/EDR */ + + /* public key not used over BR/EDR */ + + /* DHKey check not used over BR/EDR */ +}; + +static int smp_br_error(struct bt_smp_br *smp, uint8_t reason) +{ + struct bt_smp_pairing_fail *rsp; + struct net_buf *buf; + + /* reset context and report */ + + smp_br_reset(smp); + + buf = smp_br_create_pdu(smp, BT_SMP_CMD_PAIRING_FAIL, sizeof(*rsp)); + if (!buf) + { + return -ENOBUFS; + } + + rsp = net_buf_add(buf, sizeof(*rsp)); + rsp->reason = reason; + + /* SMP timer is not restarted for PairingFailed so don't use + * smp_br_send + */ + + if (bt_l2cap_br_send_cb(smp->chan.chan.conn, BT_L2CAP_CID_BR_SMP, buf, + NULL, + NULL)) + { + net_buf_unref(buf); + } + + return 0; +} + +static int bt_smp_br_recv(struct bt_l2cap_chan *chan, struct net_buf *buf) +{ + struct bt_smp_br *smp = CONTAINER_OF(chan, struct bt_smp_br, chan.chan); + struct bt_smp_hdr *hdr; + uint8_t err; + + if (buf->len < sizeof(*hdr)) + { + LOG_ERR("Too small SMP PDU received"); + return 0; + } + + hdr = net_buf_pull_mem(buf, sizeof(*hdr)); + LOG_DBG("Received SMP code 0x%02x len %u", hdr->code, buf->len); + + /* If SMP timeout occurred "no further SMP commands shall be sent over + * the L2CAP Security Manager Channel. A new SM procedure shall only be + * performed when a new physical link has been established." + */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_TIMEOUT)) + { + LOG_WRN("SMP command (code 0x%02x) received after timeout", hdr->code); + return 0; + } + + if (hdr->code >= ARRAY_SIZE(br_handlers) || !br_handlers[hdr->code].func) + { + LOG_WRN("Unhandled SMP code 0x%02x", hdr->code); + smp_br_error(smp, BT_SMP_ERR_CMD_NOTSUPP); + return 0; + } + + if (!atomic_test_and_clear_bit(smp->allowed_cmds, hdr->code)) + { + LOG_WRN("Unexpected SMP code 0x%02x", hdr->code); + smp_br_error(smp, BT_SMP_ERR_UNSPECIFIED); + return 0; + } + + if (buf->len != br_handlers[hdr->code].expect_len) + { + LOG_ERR("Invalid len %u for code 0x%02x", buf->len, hdr->code); + smp_br_error(smp, BT_SMP_ERR_INVALID_PARAMS); + return 0; + } + + err = br_handlers[hdr->code].func(smp, buf); + if (err) + { + smp_br_error(smp, err); + } + + return 0; +} + +static bool br_sc_supported(void) +{ + if (IS_ENABLED(CONFIG_BT_SMP_FORCE_BREDR)) + { + LOG_WRN("Enabling BR/EDR SMP without BR/EDR SC support"); + return true; + } + + return BT_FEAT_SC(bt_dev.features); +} + +static int bt_smp_br_accept(struct bt_conn *conn, + struct bt_l2cap_chan **chan) +{ + struct bt_smp_br *smp; + uint8_t index; + static const struct bt_l2cap_chan_ops ops = + { + .connected = bt_smp_br_connected, + .disconnected = bt_smp_br_disconnected, + .recv = bt_smp_br_recv, + }; + + /* Check BR/EDR SC is supported */ + + if (!br_sc_supported()) + { + return -ENOTSUP; + } + + index = bt_conn_index(conn); + DEBUGASSERT(index < ARRAY_SIZE(bt_smp_br_pool)); + + LOG_DBG("conn %p handle %u", conn, conn->handle); + + smp = &bt_smp_br_pool[index]; + + if (smp->chan.state != BT_L2CAP_DISCONNECTED) + { + LOG_ERR("SMP BR chan %p is not idle (state %u)", &smp->chan, + smp->chan.state); + return -EBUSY; + } + + smp->chan.chan.ops = &ops; + + *chan = &smp->chan.chan; + + k_work_init_delayable(&smp->work, smp_br_timeout); + smp_br_reset(smp); + + return 0; +} + +static struct bt_smp_br *smp_br_chan_get(struct bt_conn *conn) +{ + struct bt_l2cap_chan *chan; + + chan = bt_l2cap_br_lookup_rx_cid(conn, BT_L2CAP_CID_BR_SMP); + if (!chan) + { + LOG_ERR("Unable to find SMP channel"); + return NULL; + } + + return CONTAINER_OF(chan, struct bt_smp_br, chan.chan); +} + +int bt_smp_br_send_pairing_req(struct bt_conn *conn) +{ + struct bt_smp_pairing *req; + struct net_buf *req_buf; + uint8_t max_key_size; + struct bt_smp_br *smp; + uint8_t remote_fixed_chan; + + remote_fixed_chan = bt_l2cap_br_get_remote_fixed_chan(conn); + if (!(remote_fixed_chan & BIT(BT_L2CAP_CID_BR_SMP))) + { + return -ENOTSUP; + } + + smp = smp_br_chan_get(conn); + if (!smp) + { + return -ENOTCONN; + } + + /* SMP Timeout */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_TIMEOUT)) + { + return -EIO; + } + + /* pairing is in progress */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_PAIRING)) + { + return -EBUSY; + } + + /* check if we are allowed to start SMP over BR/EDR */ + + if (!smp_br_pairing_allowed(smp)) + { + return -ENOTSUP; + } + + /* Channel not yet connected, will start pairing once connected */ + + if (!atomic_test_bit(smp->flags, SMP_FLAG_BR_CONNECTED)) + { + atomic_set_bit(smp->flags, SMP_FLAG_BR_PAIR); + return 0; + } + + max_key_size = bt_conn_enc_key_size(conn); + if (!max_key_size) + { + LOG_DBG("Invalid encryption key size"); + return -EIO; + } + + smp_br_init(smp); + smp->enc_key_size = max_key_size; + + req_buf = smp_br_create_pdu(smp, BT_SMP_CMD_PAIRING_REQ, sizeof(*req)); + if (!req_buf) + { + return -ENOBUFS; + } + + req = net_buf_add(req_buf, sizeof(*req)); + + /* If Secure Connections pairing has been initiated over BR/EDR, the IO + * Capability, OOB data flag and Auth Req fields of the SM Pairing + * Request/Response PDU shall be set to zero on transmission, and ignored + * on reception. + */ + + req->auth_req = BT_SMP_AUTH_CT2; + req->io_capability = 0x00; + req->oob_flag = 0x00; + req->max_key_size = max_key_size; + req->init_key_dist = BR_SEND_KEYS_SC; + req->resp_key_dist = BR_RECV_KEYS_SC; + + smp_br_send(smp, req_buf, NULL); + + smp->local_dist = BR_SEND_KEYS_SC; + smp->remote_dist = BR_RECV_KEYS_SC; + + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_RSP); + + atomic_set_bit(smp->flags, SMP_FLAG_PAIRING); + + return 0; +} +#endif /* CONFIG_BT_CLASSIC */ + +static void smp_reset(struct bt_smp *smp) +{ + struct bt_conn *conn = smp->chan.chan.conn; + + /* Clear flags first in case canceling of timeout fails. The SMP context + * shall be marked as timed out in that case. + */ + + atomic_set(smp->flags, 0); + + /* If canceling fails the timeout handler will set the timeout flag and + * mark the it as timed out. No new pairing procedures shall be started on + * this connection if that happens. + */ + + (void)k_work_cancel_delayable(&smp->work); + + smp->method = JUST_WORKS; + atomic_set(smp->allowed_cmds, 0); + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && conn->role == BT_HCI_ROLE_CENTRAL) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_SECURITY_REQUEST); + return; + } + + if (IS_ENABLED(CONFIG_BT_PERIPHERAL)) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_REQ); + } +} + +static uint8_t hci_err_get(enum bt_security_err err) +{ + switch (err) + { + case BT_SECURITY_ERR_SUCCESS: + return BT_HCI_ERR_SUCCESS; + case BT_SECURITY_ERR_AUTH_FAIL: + return BT_HCI_ERR_AUTH_FAIL; + case BT_SECURITY_ERR_PIN_OR_KEY_MISSING: + return BT_HCI_ERR_PIN_OR_KEY_MISSING; + case BT_SECURITY_ERR_PAIR_NOT_SUPPORTED: + return BT_HCI_ERR_PAIRING_NOT_SUPPORTED; + case BT_SECURITY_ERR_PAIR_NOT_ALLOWED: + return BT_HCI_ERR_PAIRING_NOT_ALLOWED; + case BT_SECURITY_ERR_INVALID_PARAM: + return BT_HCI_ERR_INVALID_PARAM; + default: + return BT_HCI_ERR_UNSPECIFIED; + } +} + +/* Note: This function not only does set the status but also calls smp_reset + * at the end which clears any flags previously set. + */ + +static void smp_pairing_complete(struct bt_smp *smp, uint8_t status) +{ + struct bt_conn *conn = smp->chan.chan.conn; + + LOG_DBG("got status 0x%x", status); + + if (conn->le.keys == NULL) + { + /* We can get here if the application calls `bt_unpair` in the + * `security_changed` callback. + */ + + LOG_WRN("The in-progress pairing has been deleted!"); + status = BT_SMP_ERR_UNSPECIFIED; + } + + if (!status) + { +#if defined(CONFIG_BT_CLASSIC) + /* Don't derive if Debug Keys are used. + * TODO should we allow this if BR/EDR is already connected? + */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_DERIVE_LK) && + (!atomic_test_bit(smp->flags, SMP_FLAG_SC_DEBUG_KEY) || + IS_ENABLED(CONFIG_BT_STORE_DEBUG_KEYS))) + { + sc_derive_link_key(smp); + } +#endif /* CONFIG_BT_CLASSIC */ + + bool bond_flag = atomic_test_bit(smp->flags, SMP_FLAG_BOND); + struct bt_conn_auth_info_cb *listener; + struct bt_conn_auth_info_cb *next; + + if (IS_ENABLED(CONFIG_BT_LOG_SNIFFER_INFO)) + { + bt_keys_show_sniffer_info(conn->le.keys, NULL); + } + + if (bond_flag && conn->le.keys) + { + bt_keys_store(conn->le.keys); + } + + list_for_every_container_safe(&bt_auth_info_cbs, listener, next, node) + { + if (listener->pairing_complete) + { + listener->pairing_complete(conn, bond_flag); + } + } + } + else + { + enum bt_security_err security_err = security_err_get(status); + + /* Clear the key pool entry in case of pairing failure if the + * keys already existed before the pairing procedure or the pairing + * failed during key distribution. + */ + + if (conn->le.keys && + (!conn->le.keys->enc_size || atomic_test_bit(smp->flags, + SMP_FLAG_KEYS_DISTR))) + { + bt_keys_clear(conn->le.keys); + conn->le.keys = NULL; + } + + if (!atomic_test_bit(smp->flags, SMP_FLAG_KEYS_DISTR)) + { + bt_conn_security_changed(conn, hci_err_get(security_err), + security_err); + } + + /* Check SMP_FLAG_PAIRING as bt_conn_security_changed may + * have called the pairing_failed callback already. + */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_PAIRING)) + { + struct bt_conn_auth_info_cb *listener; + struct bt_conn_auth_info_cb *next; + + list_for_every_container_safe(&bt_auth_info_cbs, listener, next, + node) + { + if (listener->pairing_failed) + { + listener->pairing_failed(conn, security_err); + } + } + } + } + + smp_reset(smp); + + if (conn->state == BT_CONN_CONNECTED && + conn->sec_level != conn->required_sec_level) + { + bt_smp_start_security(conn); + } +} + +static void smp_timeout(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_smp *smp = CONTAINER_OF(dwork, struct bt_smp, work); + + /* if number of flags or supported commands exceed capacity of one + * atomic variable following error log shall be extended + */ + + BUILD_ASSERT(atomic_bitmap_size(SMP_NUM_FLAGS) == 1); + BUILD_ASSERT(atomic_bitmap_size(BT_SMP_NUM_CMDS) == 1); + + LOG_ERR("SMP Timeout (flags:0x%08x allowed_cmds:0x%08x)", + (unsigned int)atomic_read(&smp->flags[0]), + (unsigned int)atomic_read(&smp->allowed_cmds[0])); + + smp_pairing_complete(smp, BT_SMP_ERR_UNSPECIFIED); + + /* smp_pairing_complete clears flags so setting timeout flag must come + * after it. + */ + + atomic_set_bit(smp->flags, SMP_FLAG_TIMEOUT); +} + +static void smp_send(struct bt_smp *smp, + struct net_buf *buf, + bt_conn_tx_cb_t cb, + void *user_data) +{ + DEBUGASSERT(user_data == NULL); + + int err = bt_l2cap_send_pdu(&smp->chan, buf, cb, NULL); + + if (err) + { + if (err == -ENOBUFS) + { + LOG_ERR("Ran out of TX buffers or contexts."); + } + + net_buf_unref(buf); + return; + } + + bt_work_reschedule(&smp->work, SMP_TIMEOUT); +} + +static int smp_error(struct bt_smp *smp, uint8_t reason) +{ + struct bt_smp_pairing_fail *rsp; + struct net_buf *buf; + bool remote_already_completed; + + /* By spec, SMP "pairing process" completes successfully when the last + * key to distribute is acknowledged at link-layer. + */ + + remote_already_completed = (atomic_test_bit(smp->flags, + SMP_FLAG_KEYS_DISTR) && + !smp->local_dist && !smp->remote_dist); + + if (atomic_test_bit(smp->flags, SMP_FLAG_PAIRING) || + atomic_test_bit(smp->flags, SMP_FLAG_ENC_PENDING) || + atomic_test_bit(smp->flags, SMP_FLAG_SEC_REQ)) + { + /* reset context and report */ + + smp_pairing_complete(smp, reason); + } + + if (remote_already_completed) + { + LOG_WRN("SMP does not allow a pairing failure at this point. Known " + "issue. " + "Disconnecting instead."); + /* We are probably here because we are, as a peripheral, rejecting a + * pairing based on the central's identity address information, but + * that was the last key to be transmitted. In that case, the pairing + * process is already completed. The SMP protocol states that the + * pairing process is completed the moment the peripheral link-layer + * confirmed the reception of the PDU with the last key. + */ + + bt_conn_disconnect(smp->chan.chan.conn, BT_HCI_ERR_AUTH_FAIL); + return 0; + } + + buf = smp_create_pdu(smp, BT_SMP_CMD_PAIRING_FAIL, sizeof(*rsp)); + if (!buf) + { + return -ENOBUFS; + } + + rsp = net_buf_add(buf, sizeof(*rsp)); + rsp->reason = reason; + + /* SMP timer is not restarted for PairingFailed so don't use smp_send */ + + if (bt_l2cap_send_pdu(&smp->chan, buf, NULL, NULL)) + { + net_buf_unref(buf); + } + + return 0; +} + +static uint8_t smp_send_pairing_random(struct bt_smp *smp) +{ + struct bt_smp_pairing_random *req; + struct net_buf *rsp_buf; + + rsp_buf = smp_create_pdu(smp, BT_SMP_CMD_PAIRING_RANDOM, sizeof(*req)); + if (!rsp_buf) + { + return BT_SMP_ERR_UNSPECIFIED; + } + + req = net_buf_add(rsp_buf, sizeof(*req)); + memcpy(req->val, smp->prnd, sizeof(req->val)); + + smp_send(smp, rsp_buf, NULL, NULL); + + return 0; +} + +#if !defined(CONFIG_BT_SMP_SC_PAIR_ONLY) +static int smp_c1( + const uint8_t k[16], const uint8_t r[16], const uint8_t preq[7], + const uint8_t pres[7], const bt_addr_le_t *ia, const bt_addr_le_t *ra, + uint8_t enc_data[16]) +{ + uint8_t p1[16]; + uint8_t p2[16]; + int err; + + LOG_DBG("k %s", bt_hex(k, 16)); + LOG_DBG("r %s", bt_hex(r, 16)); + LOG_DBG("ia %s", bt_addr_le_str(ia)); + LOG_DBG("ra %s", bt_addr_le_str(ra)); + LOG_DBG("preq %s", bt_hex(preq, 7)); + LOG_DBG("pres %s", bt_hex(pres, 7)); + + /* pres, preq, rat and iat are concatenated to generate p1 */ + + p1[0] = ia->type; + p1[1] = ra->type; + memcpy(p1 + 2, preq, 7); + memcpy(p1 + 9, pres, 7); + + LOG_DBG("p1 %s", bt_hex(p1, 16)); + + /* c1 = e(k, e(k, r XOR p1) XOR p2) */ + + /* Using enc_data as temporary output buffer */ + + mem_xor_128(enc_data, r, p1); + + err = bt_encrypt_le(k, enc_data, enc_data); + if (err) + { + return err; + } + + /* ra is concatenated with ia and padding to generate p2 */ + + memcpy(p2, ra->a.val, 6); + memcpy(p2 + 6, ia->a.val, 6); + (void)memset(p2 + 12, 0, 4); + + LOG_DBG("p2 %s", bt_hex(p2, 16)); + + mem_xor_128(enc_data, p2, enc_data); + + return bt_encrypt_le(k, enc_data, enc_data); +} +#endif /* !CONFIG_BT_SMP_SC_PAIR_ONLY */ + +static uint8_t smp_send_pairing_confirm(struct bt_smp *smp) +{ + struct bt_smp_pairing_confirm *req; + struct net_buf *buf; + uint8_t r; + + switch (smp->method) + { + case PASSKEY_CONFIRM: + case JUST_WORKS: + r = 0U; + break; + case PASSKEY_DISPLAY: + case PASSKEY_INPUT: + /* In the Passkey Entry protocol, the most significant + * bit of Z is set equal to one and the least significant bit is made + * up from one bit of the passkey e.g. if the passkey bit is 1, then + * Z + * = 0x81 and if the passkey bit is 0, then Z = 0x80. + */ + + r = (smp->passkey >> smp->passkey_round) & 0x01; + r |= 0x80; + break; + default: + LOG_ERR("Unknown pairing method (%u)", smp->method); + return BT_SMP_ERR_UNSPECIFIED; + } + + buf = smp_create_pdu(smp, BT_SMP_CMD_PAIRING_CONFIRM, sizeof(*req)); + if (!buf) + { + return BT_SMP_ERR_UNSPECIFIED; + } + + req = net_buf_add(buf, sizeof(*req)); + + if (bt_crypto_f4(sc_public_key, smp->pkey, smp->prnd, r, req->val)) + { + net_buf_unref(buf); + return BT_SMP_ERR_UNSPECIFIED; + } + + smp_send(smp, buf, NULL, NULL); + + atomic_clear_bit(smp->flags, SMP_FLAG_CFM_DELAYED); + + return 0; +} + +#if !defined(CONFIG_BT_SMP_SC_PAIR_ONLY) +static void smp_ident_sent(struct bt_conn *conn, void *user_data, int err) +{ + if (!err) + { + smp_check_complete(conn, BT_SMP_DIST_ENC_KEY); + } +} + +static void legacy_distribute_keys(struct bt_smp *smp) +{ + struct bt_conn *conn = smp->chan.chan.conn; + struct bt_keys *keys = conn->le.keys; + + if (smp->local_dist & BT_SMP_DIST_ENC_KEY) + { + struct bt_smp_encrypt_info *info; + struct bt_smp_central_ident *ident; + struct net_buf *buf; + + /* Use struct to get randomness in single call to bt_rand */ + + struct + { + uint8_t key[16]; + uint8_t rand[8]; + uint8_t ediv[2]; + } rand; + + if (bt_rand((void *)&rand, sizeof(rand))) + { + LOG_ERR("Unable to get random bytes"); + return; + } + + buf = smp_create_pdu(smp, BT_SMP_CMD_ENCRYPT_INFO, sizeof(*info)); + if (!buf) + { + LOG_ERR("Unable to allocate Encrypt Info buffer"); + return; + } + + info = net_buf_add(buf, sizeof(*info)); + + /* distributed only enc_size bytes of key */ + + memcpy(info->ltk, rand.key, keys->enc_size); + if (keys->enc_size < sizeof(info->ltk)) + { + (void)memset(info->ltk + keys->enc_size, 0, + sizeof(info->ltk) - keys->enc_size); + } + + smp_send(smp, buf, NULL, NULL); + + buf = smp_create_pdu(smp, BT_SMP_CMD_CENTRAL_IDENT, sizeof(*ident)); + if (!buf) + { + LOG_ERR("Unable to allocate Central Ident buffer"); + return; + } + + ident = net_buf_add(buf, sizeof(*ident)); + memcpy(ident->rand, rand.rand, sizeof(ident->rand)); + memcpy(ident->ediv, rand.ediv, sizeof(ident->ediv)); + + smp_send(smp, buf, smp_ident_sent, NULL); + + if (atomic_test_bit(smp->flags, SMP_FLAG_BOND)) + { + bt_keys_add_type(keys, BT_KEYS_PERIPH_LTK); + + memcpy(keys->periph_ltk.val, rand.key, + sizeof(keys->periph_ltk.val)); + memcpy(keys->periph_ltk.rand, rand.rand, + sizeof(keys->periph_ltk.rand)); + memcpy(keys->periph_ltk.ediv, rand.ediv, + sizeof(keys->periph_ltk.ediv)); + } + } +} +#endif /* !CONFIG_BT_SMP_SC_PAIR_ONLY */ + +static uint8_t bt_smp_distribute_keys(struct bt_smp *smp) +{ + struct bt_conn *conn = smp->chan.chan.conn; + struct bt_keys *keys = conn->le.keys; + + if (!keys) + { + LOG_ERR("No keys space for %s", bt_addr_le_str(&conn->le.dst)); + return BT_SMP_ERR_UNSPECIFIED; + } + +#if !defined(CONFIG_BT_SMP_SC_PAIR_ONLY) + /* Distribute legacy pairing specific keys */ + + if (!atomic_test_bit(smp->flags, SMP_FLAG_SC)) + { + legacy_distribute_keys(smp); + } +#endif /* !CONFIG_BT_SMP_SC_PAIR_ONLY */ + +#if defined(CONFIG_BT_PRIVACY) + if (smp->local_dist & BT_SMP_DIST_ID_KEY) + { + struct bt_smp_ident_info *id_info; + struct bt_smp_ident_addr_info *id_addr_info; + struct net_buf *buf; + + buf = smp_create_pdu(smp, BT_SMP_CMD_IDENT_INFO, sizeof(*id_info)); + if (!buf) + { + LOG_ERR("Unable to allocate Ident Info buffer"); + return BT_SMP_ERR_UNSPECIFIED; + } + + id_info = net_buf_add(buf, sizeof(*id_info)); + memcpy(id_info->irk, bt_dev.irk[conn->id], 16); + + smp_send(smp, buf, NULL, NULL); + + buf = smp_create_pdu(smp, BT_SMP_CMD_IDENT_ADDR_INFO, + sizeof(*id_addr_info)); + if (!buf) + { + LOG_ERR("Unable to allocate Ident Addr Info buffer"); + return BT_SMP_ERR_UNSPECIFIED; + } + + id_addr_info = net_buf_add(buf, sizeof(*id_addr_info)); + bt_addr_le_copy(&id_addr_info->addr, &bt_dev.id_addr[conn->id]); + + smp_send(smp, buf, smp_id_sent, NULL); + } +#endif /* CONFIG_BT_PRIVACY */ + +#if defined(CONFIG_BT_SIGNING) + if (smp->local_dist & BT_SMP_DIST_SIGN) + { + struct bt_smp_signing_info *info; + struct net_buf *buf; + + buf = smp_create_pdu(smp, BT_SMP_CMD_SIGNING_INFO, sizeof(*info)); + if (!buf) + { + LOG_ERR("Unable to allocate Signing Info buffer"); + return BT_SMP_ERR_UNSPECIFIED; + } + + info = net_buf_add(buf, sizeof(*info)); + + if (bt_rand(info->csrk, sizeof(info->csrk))) + { + return BT_SMP_ERR_UNSPECIFIED; + } + + if (atomic_test_bit(smp->flags, SMP_FLAG_BOND)) + { + bt_keys_add_type(keys, BT_KEYS_LOCAL_CSRK); + memcpy(keys->local_csrk.val, info->csrk, 16); + keys->local_csrk.cnt = 0U; + } + + smp_send(smp, buf, smp_sign_info_sent, NULL); + } +#endif /* CONFIG_BT_SIGNING */ + + return 0; +} + +#if defined(CONFIG_BT_PERIPHERAL) +static uint8_t send_pairing_rsp(struct bt_smp *smp) +{ + struct bt_smp_pairing *rsp; + struct net_buf *rsp_buf; + + rsp_buf = smp_create_pdu(smp, BT_SMP_CMD_PAIRING_RSP, sizeof(*rsp)); + if (!rsp_buf) + { + return BT_SMP_ERR_UNSPECIFIED; + } + + rsp = net_buf_add(rsp_buf, sizeof(*rsp)); + memcpy(rsp, smp->prsp + 1, sizeof(*rsp)); + + smp_send(smp, rsp_buf, NULL, NULL); + + return 0; +} +#endif /* CONFIG_BT_PERIPHERAL */ + +static uint8_t smp_pairing_accept_query(struct bt_smp *smp, + struct bt_smp_pairing *pairing) +{ +#if defined(CONFIG_BT_SMP_APP_PAIRING_ACCEPT) + const struct bt_conn_auth_cb *smp_auth_cb = latch_auth_cb(smp); + struct bt_conn *conn = smp->chan.chan.conn; + + if (smp_auth_cb && smp_auth_cb->pairing_accept) + { + const struct bt_conn_pairing_feat feat = + { + .io_capability = pairing->io_capability, + .oob_data_flag = pairing->oob_flag, + .auth_req = pairing->auth_req, + .max_enc_key_size = pairing->max_key_size, + .init_key_dist = pairing->init_key_dist, + .resp_key_dist = pairing->resp_key_dist + }; + + return smp_err_get(smp_auth_cb->pairing_accept(conn, &feat)); + } +#endif /* CONFIG_BT_SMP_APP_PAIRING_ACCEPT */ + + return 0; +} + +#if !defined(CONFIG_BT_SMP_SC_PAIR_ONLY) +static int smp_s1(const uint8_t k[16], + const uint8_t r1[16], + const uint8_t r2[16], + uint8_t out[16]) +{ + /* The most significant 64-bits of r1 are discarded to generate + * r1' and the most significant 64-bits of r2 are discarded to generate + * r2'. r1' is concatenated with r2' to generate r' which is used as the + * 128-bit input parameter plaintextData to security function e: + * + * r' = r1' || r2' + */ + + memcpy(out, r2, 8); + memcpy(out + 8, r1, 8); + + /* s1(k, r1 , r2) = e(k, r') */ + + return bt_encrypt_le(k, out, out); +} + +static uint8_t legacy_get_pair_method(struct bt_smp *smp, uint8_t remote_io) +{ + struct bt_smp_pairing *req; + struct bt_smp_pairing *rsp; + uint8_t method; + + if (remote_io > BT_SMP_IO_KEYBOARD_DISPLAY) + { + return JUST_WORKS; + } + + req = (struct bt_smp_pairing *)&smp->preq[1]; + rsp = (struct bt_smp_pairing *)&smp->prsp[1]; + + /* if both sides have OOB data use OOB */ + + if ((req->oob_flag & rsp->oob_flag) & BT_SMP_OOB_DATA_MASK) + { + return LEGACY_OOB; + } + + /* if none side requires MITM use JustWorks */ + + if (!((req->auth_req | rsp->auth_req) & BT_SMP_AUTH_MITM)) + { + return JUST_WORKS; + } + + method = gen_method_legacy[remote_io][get_io_capa(smp)]; + + /* if both sides have KeyboardDisplay capabilities, initiator displays + * and responder inputs + */ + + if (method == PASSKEY_ROLE) + { + if (smp->chan.chan.conn->role == BT_HCI_ROLE_CENTRAL) + { + method = PASSKEY_DISPLAY; + } + else + { + method = PASSKEY_INPUT; + } + } + + return method; +} + +static uint8_t legacy_request_tk(struct bt_smp *smp) +{ + struct bt_conn *conn = smp->chan.chan.conn; + const struct bt_conn_auth_cb *smp_auth_cb = latch_auth_cb(smp); + struct bt_keys *keys; + + /* Fail if we have keys that are stronger than keys that will be + * distributed in new pairing. This is to avoid replacing authenticated + * keys with unauthenticated ones. + */ + + keys = bt_keys_find_addr(conn->id, &conn->le.dst); + if (keys && (keys->flags & BT_KEYS_AUTHENTICATED) && + (smp->method == JUST_WORKS || smp->method == PASSKEY_DISPLAY || + smp->method == PASSKEY_INPUT)) + { + LOG_ERR("Pairing failed, authenticated keys present"); + return BT_SMP_ERR_UNSPECIFIED; + } + + switch (smp->method) + { + case LEGACY_OOB: + if (smp_auth_cb && smp_auth_cb->oob_data_request) + { + struct bt_conn_oob_info info = + { + .type = BT_CONN_OOB_LE_LEGACY, + }; + + atomic_set_bit(smp->flags, SMP_FLAG_USER); + smp_auth_cb->oob_data_request(smp->chan.chan.conn, &info); + } + else + { + return BT_SMP_ERR_OOB_NOT_AVAIL; + } + + break; + case PASSKEY_DISPLAY: + { + uint32_t passkey = BT_PASSKEY_RAND; + +#if defined(CONFIG_BT_APP_PASSKEY) + if (smp_auth_cb && smp_auth_cb->app_passkey) + { + passkey = smp_auth_cb->app_passkey(conn); + + if (passkey != BT_PASSKEY_RAND && passkey > 999999) + { + LOG_WRN("App-provided passkey is out of valid range: %u", + passkey); + return BT_SMP_ERR_UNSPECIFIED; + } + } +#endif /* CONFIG_BT_APP_PASSKEY */ + + if (passkey == BT_PASSKEY_RAND) + { + if (bt_rand(&passkey, sizeof(passkey))) + { + return BT_SMP_ERR_UNSPECIFIED; + } + + passkey %= 1000000; + } + + if (IS_ENABLED(CONFIG_BT_LOG_SNIFFER_INFO)) + { + LOG_INF("Legacy passkey %u", passkey); + } + + if (smp_auth_cb && smp_auth_cb->passkey_display) + { + atomic_set_bit(smp->flags, SMP_FLAG_DISPLAY); + smp_auth_cb->passkey_display(conn, passkey); + } + + sys_put_le32(passkey, smp->tk); + + break; + } + + case PASSKEY_INPUT: + atomic_set_bit(smp->flags, SMP_FLAG_USER); + smp_auth_cb->passkey_entry(conn); + break; + case JUST_WORKS: + break; + default: + LOG_ERR("Unknown pairing method (%u)", smp->method); + return BT_SMP_ERR_UNSPECIFIED; + } + + return 0; +} + +static uint8_t legacy_send_pairing_confirm(struct bt_smp *smp) +{ + struct bt_conn *conn = smp->chan.chan.conn; + struct bt_smp_pairing_confirm *req; + struct net_buf *buf; + + buf = smp_create_pdu(smp, BT_SMP_CMD_PAIRING_CONFIRM, sizeof(*req)); + if (!buf) + { + return BT_SMP_ERR_UNSPECIFIED; + } + + req = net_buf_add(buf, sizeof(*req)); + + if (smp_c1(smp->tk, smp->prnd, smp->preq, smp->prsp, &conn->le.init_addr, + &conn->le.resp_addr, + req->val)) + { + net_buf_unref(buf); + return BT_SMP_ERR_UNSPECIFIED; + } + + smp_send(smp, buf, NULL, NULL); + + atomic_clear_bit(smp->flags, SMP_FLAG_CFM_DELAYED); + + return 0; +} + +#if defined(CONFIG_BT_PERIPHERAL) +static uint8_t legacy_pairing_req(struct bt_smp *smp) +{ + const struct bt_conn_auth_cb *smp_auth_cb = latch_auth_cb(smp); + uint8_t ret; + + LOG_DBG(""); + + ret = legacy_request_tk(smp); + if (ret) + { + return ret; + } + + /* ask for consent if pairing is not due to sending SecReq */ + + if (smp->method == JUST_WORKS && !atomic_test_bit(smp->flags, + SMP_FLAG_SEC_REQ) && + smp_auth_cb && + smp_auth_cb->pairing_confirm) + { + atomic_set_bit(smp->flags, SMP_FLAG_USER); + smp_auth_cb->pairing_confirm(smp->chan.chan.conn); + return 0; + } + + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_CONFIRM); + atomic_set_bit(smp->allowed_cmds, BT_SMP_KEYPRESS_NOTIFICATION); + return send_pairing_rsp(smp); +} +#endif /* CONFIG_BT_PERIPHERAL */ + +static uint8_t legacy_pairing_random(struct bt_smp *smp) +{ + struct bt_conn *conn = smp->chan.chan.conn; + uint8_t tmp[16]; + uint8_t cfm_i[16]; + int err; + + LOG_DBG(""); + + /* calculate LP_CONFIRM_R */ + + err = smp_c1(smp->tk, smp->rrnd, smp->preq, smp->prsp, &conn->le.init_addr, + &conn->le.resp_addr, + tmp); + if (err) + { + return BT_SMP_ERR_UNSPECIFIED; + } + + /* calculate LP_CONFIRM_I */ + + err = smp_c1(smp->tk, smp->prnd, smp->preq, smp->prsp, &conn->le.init_addr, + &conn->le.resp_addr, + cfm_i); + if (err) + { + return BT_SMP_ERR_UNSPECIFIED; + } + + LOG_DBG("pcnf %s", bt_hex(smp->pcnf, 16)); + LOG_DBG("cfm (remote) %s", bt_hex(tmp, 16)); + LOG_DBG("cfm (local) %s", bt_hex(cfm_i, 16)); + + /* Core Specification, Vol 3, Part H, section 2.3.5.5 (Errata ES-24491): If + * the computed LP_CONFIRM_R value is not equal to the received + * LP_CONFIRM_R value, or the received LP_CONFIRM_R value is equal to the + * LP_CONFIRM_I value, fail pairing. + */ + + if (memcmp(smp->pcnf, tmp, sizeof(smp->pcnf)) || + !memcmp(smp->pcnf, cfm_i, sizeof(smp->pcnf))) + { + return BT_SMP_ERR_CONFIRM_FAILED; + } + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && conn->role == BT_HCI_ROLE_CENTRAL) + { + uint8_t ediv[2]; + uint8_t rand[8]; + + /* No need to store central STK */ + + err = smp_s1(smp->tk, smp->rrnd, smp->prnd, tmp); + if (err) + { + return BT_SMP_ERR_UNSPECIFIED; + } + + /* Rand and EDiv are 0 for the STK */ + + (void)memset(ediv, 0, sizeof(ediv)); + (void)memset(rand, 0, sizeof(rand)); + if (bt_conn_le_start_encryption(conn, rand, ediv, tmp, + get_encryption_key_size(smp))) + { + LOG_ERR("Failed to start encryption"); + return BT_SMP_ERR_UNSPECIFIED; + } + + atomic_set_bit(smp->flags, SMP_FLAG_ENC_PENDING); + + if (IS_ENABLED(CONFIG_BT_SMP_USB_HCI_CTLR_WORKAROUND)) + { + if (smp->remote_dist & BT_SMP_DIST_ENC_KEY) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_ENCRYPT_INFO); + } + else if (smp->remote_dist & BT_SMP_DIST_ID_KEY) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_IDENT_INFO); + } + else if (smp->remote_dist & BT_SMP_DIST_SIGN) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_SIGNING_INFO); + } + } + + return 0; + } + + if (IS_ENABLED(CONFIG_BT_PERIPHERAL)) + { + err = smp_s1(smp->tk, smp->prnd, smp->rrnd, tmp); + if (err) + { + LOG_ERR("Calculate STK failed"); + return BT_SMP_ERR_UNSPECIFIED; + } + + memcpy(smp->tk, tmp, sizeof(smp->tk)); + LOG_DBG("generated STK %s", bt_hex(smp->tk, 16)); + + atomic_set_bit(smp->flags, SMP_FLAG_ENC_PENDING); + + return smp_send_pairing_random(smp); + } + + return 0; +} + +static uint8_t legacy_pairing_confirm(struct bt_smp *smp) +{ + LOG_DBG(""); + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && + smp->chan.chan.conn->role == BT_HCI_ROLE_CENTRAL) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_CONFIRM); + return legacy_send_pairing_confirm(smp); + } + + if (IS_ENABLED(CONFIG_BT_PERIPHERAL)) + { + if (!atomic_test_bit(smp->flags, SMP_FLAG_USER)) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_RANDOM); + return legacy_send_pairing_confirm(smp); + } + + atomic_set_bit(smp->flags, SMP_FLAG_CFM_DELAYED); + } + + return 0; +} + +static void legacy_user_tk_entry(struct bt_smp *smp) +{ + if (!atomic_test_and_clear_bit(smp->flags, SMP_FLAG_CFM_DELAYED)) + { + return; + } + + /* if confirm failed ie. due to invalid passkey, cancel pairing */ + + if (legacy_pairing_confirm(smp)) + { + smp_error(smp, BT_SMP_ERR_PASSKEY_ENTRY_FAILED); + return; + } + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && + smp->chan.chan.conn->role == BT_HCI_ROLE_CENTRAL) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_CONFIRM); + return; + } + + if (IS_ENABLED(CONFIG_BT_PERIPHERAL)) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_RANDOM); + } +} + +static void legacy_passkey_entry(struct bt_smp *smp, unsigned int passkey) +{ + passkey = sys_cpu_to_le32(passkey); + memcpy(smp->tk, &passkey, sizeof(passkey)); + + legacy_user_tk_entry(smp); +} + +static uint8_t smp_encrypt_info(struct bt_smp *smp, struct net_buf *buf) +{ + LOG_DBG(""); + + if (atomic_test_bit(smp->flags, SMP_FLAG_BOND)) + { + struct bt_smp_encrypt_info *req = (void *)buf->data; + struct bt_conn *conn = smp->chan.chan.conn; + struct bt_keys *keys; + + keys = bt_keys_get_type(BT_KEYS_LTK, conn->id, &conn->le.dst); + if (!keys) + { + LOG_ERR("Unable to get keys for " + "%s", bt_addr_le_str(&conn->le.dst)); + return BT_SMP_ERR_UNSPECIFIED; + } + + memcpy(keys->ltk.val, req->ltk, 16); + } + + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_CENTRAL_IDENT); + + return 0; +} + +static uint8_t smp_central_ident(struct bt_smp *smp, struct net_buf *buf) +{ + struct bt_conn *conn = smp->chan.chan.conn; + uint8_t err; + + LOG_DBG(""); + + if (atomic_test_bit(smp->flags, SMP_FLAG_BOND)) + { + struct bt_smp_central_ident *req = (void *)buf->data; + struct bt_keys *keys; + + keys = bt_keys_get_type(BT_KEYS_LTK, conn->id, &conn->le.dst); + if (!keys) + { + LOG_ERR("Unable to get keys for " + "%s", bt_addr_le_str(&conn->le.dst)); + return BT_SMP_ERR_UNSPECIFIED; + } + + memcpy(keys->ltk.ediv, req->ediv, sizeof(keys->ltk.ediv)); + memcpy(keys->ltk.rand, req->rand, sizeof(req->rand)); + } + + smp->remote_dist &= ~BT_SMP_DIST_ENC_KEY; + + if (smp->remote_dist & BT_SMP_DIST_ID_KEY) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_IDENT_INFO); + } + else if (smp->remote_dist & BT_SMP_DIST_SIGN) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_SIGNING_INFO); + } + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && conn->role == BT_HCI_ROLE_CENTRAL && + !smp->remote_dist) + { + err = bt_smp_distribute_keys(smp); + if (err) + { + return err; + } + } + + /* if all keys were distributed, pairing is done */ + + if (!smp->local_dist && !smp->remote_dist) + { + smp_pairing_complete(smp, 0); + } + + return 0; +} + +#if defined(CONFIG_BT_CENTRAL) +static uint8_t legacy_pairing_rsp(struct bt_smp *smp) +{ + const struct bt_conn_auth_cb *smp_auth_cb = latch_auth_cb(smp); + uint8_t ret; + + LOG_DBG(""); + + ret = legacy_request_tk(smp); + if (ret) + { + return ret; + } + + /* ask for consent if this is due to received SecReq */ + + if (smp->method == JUST_WORKS && atomic_test_bit(smp->flags, + SMP_FLAG_SEC_REQ) && + smp_auth_cb && + smp_auth_cb->pairing_confirm) + { + atomic_set_bit(smp->flags, SMP_FLAG_USER); + smp_auth_cb->pairing_confirm(smp->chan.chan.conn); + return 0; + } + + if (!atomic_test_bit(smp->flags, SMP_FLAG_USER)) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_CONFIRM); + atomic_set_bit(smp->allowed_cmds, BT_SMP_KEYPRESS_NOTIFICATION); + return legacy_send_pairing_confirm(smp); + } + + atomic_set_bit(smp->flags, SMP_FLAG_CFM_DELAYED); + + return 0; +} +#endif /* CONFIG_BT_CENTRAL */ +#else +static uint8_t smp_encrypt_info(struct bt_smp *smp, struct net_buf *buf) +{ + return BT_SMP_ERR_CMD_NOTSUPP; +} + +static uint8_t smp_central_ident(struct bt_smp *smp, struct net_buf *buf) +{ + return BT_SMP_ERR_CMD_NOTSUPP; +} +#endif /* !CONFIG_BT_SMP_SC_PAIR_ONLY */ + +static int smp_init(struct bt_smp *smp) +{ + /* SMP_FLAG_SEC_REQ is claimed before this runs and has to stay set for + * the whole procedure, so the flags are cleared without ever dropping it. + */ + + (void)atomic_and(smp->flags, BIT(SMP_FLAG_SEC_REQ)); + + /* Initialize SMP context excluding L2CAP channel context and anything + * else declared after. + */ + + (void)memset(smp, 0, offsetof(struct bt_smp, flags)); + (void)memset(&smp->method, 0, offsetof(struct bt_smp, + chan) - offsetof(struct bt_smp, method)); + + /* Generate local random number */ + + if (bt_rand(smp->prnd, 16)) + { + return BT_SMP_ERR_UNSPECIFIED; + } + + LOG_DBG("prnd %s", bt_hex(smp->prnd, 16)); + + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_FAIL); + +#if !defined(CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY) + sc_public_key = bt_pub_key_get(); +#endif + + return 0; +} + +void bt_set_bondable(bool enable) +{ + bondable = enable; +} + +bool bt_get_bondable(void) +{ + return bondable; +} + +void bt_le_oob_set_sc_flag(bool enable) +{ + sc_oobd_present = enable; +} + +void bt_le_oob_set_legacy_flag(bool enable) +{ + legacy_oobd_present = enable; +} + +static uint8_t get_auth(struct bt_smp *smp, uint8_t auth) +{ + struct bt_conn *conn = smp->chan.chan.conn; + + if (sc_supported) + { + auth &= BT_SMP_AUTH_MASK_SC; + } + else + { + auth &= BT_SMP_AUTH_MASK; + } + + if ((get_io_capa(smp) == BT_SMP_IO_NO_INPUT_OUTPUT) || + (!IS_ENABLED(CONFIG_BT_SMP_ENFORCE_MITM) && + (conn->required_sec_level < BT_SECURITY_L3))) + { + auth &= ~(BT_SMP_AUTH_MITM); + } + else + { + auth |= BT_SMP_AUTH_MITM; + } + + if (latch_bondable(smp)) + { + auth |= BT_SMP_AUTH_BONDING; + } + else + { + auth &= ~BT_SMP_AUTH_BONDING; + } + + if (IS_ENABLED(CONFIG_BT_PASSKEY_KEYPRESS)) + { + auth |= BT_SMP_AUTH_KEYPRESS; + } + else + { + auth &= ~BT_SMP_AUTH_KEYPRESS; + } + +#if defined(CONFIG_BT_CLASSIC) + auth |= BT_SMP_AUTH_CT2; +#endif /* CONFIG_BT_CLASSIC */ + + return auth; +} + +static uint8_t remote_sec_level_reachable(struct bt_smp *smp) +{ + bt_security_t sec = smp->chan.chan.conn->required_sec_level; + + if (IS_ENABLED(CONFIG_BT_SMP_SC_ONLY)) + { + sec = BT_SECURITY_L4; + } + + if (IS_ENABLED(CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY)) + { + sec = BT_SECURITY_L3; + } + + switch (sec) + { + case BT_SECURITY_L1: + case BT_SECURITY_L2: + return 0; + + case BT_SECURITY_L4: + if (get_encryption_key_size(smp) != BT_SMP_MAX_ENC_KEY_SIZE) + { + return BT_SMP_ERR_ENC_KEY_SIZE; + } + + if (!atomic_test_bit(smp->flags, SMP_FLAG_SC)) + { + return BT_SMP_ERR_AUTH_REQUIREMENTS; + } + + __fallthrough; + case BT_SECURITY_L3: + if (smp->method == JUST_WORKS || + (!atomic_test_bit(smp->flags, SMP_FLAG_SC) && + (smp->method == PASSKEY_DISPLAY || + smp->method == PASSKEY_INPUT))) + { + return BT_SMP_ERR_AUTH_REQUIREMENTS; + } + + return 0; + default: + return BT_SMP_ERR_UNSPECIFIED; + } +} + +static bool sec_level_reachable(struct bt_smp *smp) +{ + const struct bt_conn_auth_cb *smp_auth_cb = latch_auth_cb(smp); + + switch (smp->chan.chan.conn->required_sec_level) + { + case BT_SECURITY_L1: + case BT_SECURITY_L2: + return true; + case BT_SECURITY_L3: + return get_io_capa(smp) != BT_SMP_IO_NO_INPUT_OUTPUT || + (smp_auth_cb && smp_auth_cb->oob_data_request); + case BT_SECURITY_L4: + return (get_io_capa(smp) != BT_SMP_IO_NO_INPUT_OUTPUT || + (smp_auth_cb && smp_auth_cb->oob_data_request)) && + sc_supported; + default: + return false; + } +} + +static struct bt_smp *smp_chan_get(struct bt_conn *conn) +{ + struct bt_l2cap_chan *chan; + + chan = bt_l2cap_le_lookup_rx_cid(conn, BT_L2CAP_CID_SMP); + if (!chan) + { + LOG_ERR("Unable to find SMP channel"); + return NULL; + } + + return CONTAINER_OF(chan, struct bt_smp, chan.chan); +} + +bool bt_smp_request_ltk(struct bt_conn *conn, + uint64_t rand, uint16_t ediv, uint8_t *ltk) +{ + struct bt_smp *smp; + uint8_t enc_size; + + smp = smp_chan_get(conn); + if (!smp) + { + return false; + } + + /* Both legacy STK and LE SC LTK have rand and ediv equal to zero. + * If pairing is in progress use the TK for encryption. + */ + + if (ediv == 0U && rand == 0U && atomic_test_bit(smp->flags, + SMP_FLAG_PAIRING) && + atomic_test_bit(smp->flags, SMP_FLAG_ENC_PENDING)) + { + enc_size = get_encryption_key_size(smp); + + /* We keep both legacy STK and LE SC LTK in TK. + * Also use only enc_size bytes of key for encryption. + */ + + memcpy(ltk, smp->tk, enc_size); + if (enc_size < BT_SMP_MAX_ENC_KEY_SIZE) + { + (void)memset(ltk + enc_size, 0, BT_SMP_MAX_ENC_KEY_SIZE - + enc_size); + } + + atomic_set_bit(smp->flags, SMP_FLAG_ENC_PENDING); + return true; + } + + if (!conn->le.keys) + { + conn->le.keys = bt_keys_find(BT_KEYS_LTK_P256, conn->id, + &conn->le.dst); + if (!conn->le.keys) + { + conn->le.keys = bt_keys_find(BT_KEYS_PERIPH_LTK, conn->id, + &conn->le.dst); + } + } + + if (ediv == 0U && rand == 0U && conn->le.keys && + (conn->le.keys->keys & BT_KEYS_LTK_P256)) + { + enc_size = conn->le.keys->enc_size; + + memcpy(ltk, conn->le.keys->ltk.val, enc_size); + if (enc_size < BT_SMP_MAX_ENC_KEY_SIZE) + { + (void)memset(ltk + enc_size, 0, BT_SMP_MAX_ENC_KEY_SIZE - + enc_size); + } + + atomic_set_bit(smp->flags, SMP_FLAG_ENC_PENDING); + return true; + } + +#if !defined(CONFIG_BT_SMP_SC_PAIR_ONLY) + if (conn->le.keys && (conn->le.keys->keys & BT_KEYS_PERIPH_LTK) && + !memcmp(conn->le.keys->periph_ltk.rand, &rand, 8) && + !memcmp(conn->le.keys->periph_ltk.ediv, &ediv, 2)) + { + enc_size = conn->le.keys->enc_size; + + memcpy(ltk, conn->le.keys->periph_ltk.val, enc_size); + if (enc_size < BT_SMP_MAX_ENC_KEY_SIZE) + { + (void)memset(ltk + enc_size, 0, BT_SMP_MAX_ENC_KEY_SIZE - + enc_size); + } + + atomic_set_bit(smp->flags, SMP_FLAG_ENC_PENDING); + return true; + } +#endif /* !CONFIG_BT_SMP_SC_PAIR_ONLY */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_SEC_REQ)) + { + /* Notify higher level that security failed if security was + * initiated by peripheral. + */ + + bt_conn_security_changed(conn, BT_HCI_ERR_PIN_OR_KEY_MISSING, + BT_SECURITY_ERR_PIN_OR_KEY_MISSING); + } + + smp_reset(smp); + return false; +} + +#if defined(CONFIG_BT_PERIPHERAL) +static int smp_send_security_req(struct bt_conn *conn) +{ + struct bt_smp *smp; + struct bt_smp_security_request *req; + struct net_buf *req_buf; + int err; + + LOG_DBG(""); + smp = smp_chan_get(conn); + if (!smp) + { + return -ENOTCONN; + } + + /* SMP Timeout */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_TIMEOUT)) + { + return -EIO; + } + + /* pairing is in progress */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_PAIRING)) + { + return -EBUSY; + } + + if (atomic_test_bit(smp->flags, SMP_FLAG_ENC_PENDING)) + { + return -EBUSY; + } + + /* early verify if required sec level if reachable */ + + if (!(sec_level_reachable(smp) || smp_keys_check(conn))) + { + return -EINVAL; + } + + if (!conn->le.keys) + { + conn->le.keys = bt_keys_get_addr(conn->id, &conn->le.dst); + if (!conn->le.keys) + { + return -ENOMEM; + } + } + + if (atomic_test_and_set_bit(smp->flags, SMP_FLAG_SEC_REQ)) + { + return -EALREADY; + } + + if (smp_init(smp) != 0) + { + atomic_clear_bit(smp->flags, SMP_FLAG_SEC_REQ); + return -ENOBUFS; + } + + req_buf = smp_create_pdu(smp, BT_SMP_CMD_SECURITY_REQUEST, sizeof(*req)); + if (!req_buf) + { + atomic_clear_bit(smp->flags, SMP_FLAG_SEC_REQ); + return -ENOBUFS; + } + + req = net_buf_add(req_buf, sizeof(*req)); + req->auth_req = get_auth(smp, BT_SMP_AUTH_DEFAULT); + + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_REQ); + + /* SMP timer is not restarted for SecRequest so don't use smp_send */ + + err = bt_l2cap_send_pdu(&smp->chan, req_buf, NULL, NULL); + if (err) + { + net_buf_unref(req_buf); + atomic_clear_bit(smp->flags, SMP_FLAG_SEC_REQ); + atomic_clear_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_REQ); + return err; + } + + return 0; +} + +static uint8_t smp_pairing_req(struct bt_smp *smp, struct net_buf *buf) +{ + struct bt_conn *conn = smp->chan.chan.conn; + const struct bt_conn_auth_cb *smp_auth_cb = latch_auth_cb(smp); + + struct bt_smp_pairing *req = (void *)buf->data; + struct bt_smp_pairing *rsp; + uint8_t err; + + LOG_DBG("req: io_capability 0x%02X, oob_flag 0x%02X, auth_req 0x%02X, " + "max_key_size 0x%02X, init_key_dist 0x%02X, resp_key_dist 0x%02X", + req->io_capability, req->oob_flag, req->auth_req, + req->max_key_size, req->init_key_dist, + req->resp_key_dist); + + if ((req->max_key_size > BT_SMP_MAX_ENC_KEY_SIZE) || + (req->max_key_size < BT_SMP_MIN_ENC_KEY_SIZE)) + { + return BT_SMP_ERR_ENC_KEY_SIZE; + } + + if (!conn->le.keys) + { + conn->le.keys = bt_keys_get_addr(conn->id, &conn->le.dst); + if (!conn->le.keys) + { + LOG_DBG("Unable to get keys for " + "%s", bt_addr_le_str(&conn->le.dst)); + return BT_SMP_ERR_UNSPECIFIED; + } + } + + /* If we already sent a security request then the SMP context + * is already initialized. + */ + + if (!atomic_test_bit(smp->flags, SMP_FLAG_SEC_REQ)) + { + int ret = smp_init(smp); + + if (ret) + { + return ret; + } + } + + /* Store req for later use */ + + smp->preq[0] = BT_SMP_CMD_PAIRING_REQ; + memcpy(smp->preq + 1, req, sizeof(*req)); + + /* create rsp, it will be used later on */ + + smp->prsp[0] = BT_SMP_CMD_PAIRING_RSP; + rsp = (struct bt_smp_pairing *)&smp->prsp[1]; + + rsp->auth_req = get_auth(smp, req->auth_req); + rsp->io_capability = get_io_capa(smp); + rsp->max_key_size = BT_SMP_MAX_ENC_KEY_SIZE; + rsp->init_key_dist = (req->init_key_dist & RECV_KEYS); + rsp->resp_key_dist = (req->resp_key_dist & SEND_KEYS); + + if ((rsp->auth_req & BT_SMP_AUTH_SC) && (req->auth_req & BT_SMP_AUTH_SC)) + { + atomic_set_bit(smp->flags, SMP_FLAG_SC); + + rsp->init_key_dist &= RECV_KEYS_SC; + rsp->resp_key_dist &= SEND_KEYS_SC; + } + + if (atomic_test_bit(smp->flags, SMP_FLAG_SC)) + { + rsp->oob_flag = + sc_oobd_present ? BT_SMP_OOB_PRESENT : BT_SMP_OOB_NOT_PRESENT; + } + else + { + rsp->oob_flag = + legacy_oobd_present ? + BT_SMP_OOB_PRESENT : BT_SMP_OOB_NOT_PRESENT; + } + + if ((rsp->auth_req & BT_SMP_AUTH_CT2) && (req->auth_req & BT_SMP_AUTH_CT2)) + { + atomic_set_bit(smp->flags, SMP_FLAG_CT2); + } + + if ((rsp->auth_req & BT_SMP_AUTH_BONDING) && + (req->auth_req & BT_SMP_AUTH_BONDING)) + { + atomic_set_bit(smp->flags, SMP_FLAG_BOND); + } + else if (IS_ENABLED(CONFIG_BT_BONDING_REQUIRED)) + { + /* Reject pairing req if not both intend to bond */ + + LOG_DBG("Bonding required"); + return BT_SMP_ERR_UNSPECIFIED; + } + else + { + rsp->init_key_dist = 0; + rsp->resp_key_dist = 0; + } + + smp->local_dist = rsp->resp_key_dist; + smp->remote_dist = rsp->init_key_dist; + + atomic_set_bit(smp->flags, SMP_FLAG_PAIRING); + + smp->method = get_pair_method(smp, req->io_capability); + + if (!update_keys_check(smp, conn->le.keys)) + { + return BT_SMP_ERR_AUTH_REQUIREMENTS; + } + + err = remote_sec_level_reachable(smp); + if (err) + { + return err; + } + + if (!atomic_test_bit(smp->flags, SMP_FLAG_SC)) + { +#if defined(CONFIG_BT_SMP_SC_PAIR_ONLY) + return BT_SMP_ERR_AUTH_REQUIREMENTS; +#else + if (IS_ENABLED(CONFIG_BT_SMP_APP_PAIRING_ACCEPT)) + { + err = smp_pairing_accept_query(smp, req); + if (err) + { + return err; + } + } + + return legacy_pairing_req(smp); +#endif /* CONFIG_BT_SMP_SC_PAIR_ONLY */ + } + + if (IS_ENABLED(CONFIG_BT_SMP_APP_PAIRING_ACCEPT)) + { + err = smp_pairing_accept_query(smp, req); + if (err) + { + return err; + } + } + + if (!IS_ENABLED(CONFIG_BT_SMP_SC_PAIR_ONLY) && smp->method == JUST_WORKS && + !atomic_test_bit(smp->flags, + SMP_FLAG_SEC_REQ) && smp_auth_cb && + smp_auth_cb->pairing_confirm) + { + atomic_set_bit(smp->flags, SMP_FLAG_USER); + smp_auth_cb->pairing_confirm(conn); + return 0; + } + + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PUBLIC_KEY); + + LOG_DBG("rsp: io_capability 0x%02X, oob_flag 0x%02X, auth_req 0x%02X, " + "max_key_size 0x%02X, init_key_dist 0x%02X, resp_key_dist 0x%02X", + rsp->io_capability, rsp->oob_flag, rsp->auth_req, + rsp->max_key_size, rsp->init_key_dist, + rsp->resp_key_dist); + + return send_pairing_rsp(smp); +} +#else +static uint8_t smp_pairing_req(struct bt_smp *smp, struct net_buf *buf) +{ + return BT_SMP_ERR_CMD_NOTSUPP; +} +#endif /* CONFIG_BT_PERIPHERAL */ + +static uint8_t sc_send_public_key(struct bt_smp *smp) +{ + struct bt_smp_public_key *req; + struct net_buf *req_buf; + + req_buf = smp_create_pdu(smp, BT_SMP_CMD_PUBLIC_KEY, sizeof(*req)); + if (!req_buf) + { + return BT_SMP_ERR_UNSPECIFIED; + } + + req = net_buf_add(req_buf, sizeof(*req)); + + memcpy(req->x, sc_public_key, sizeof(req->x)); + memcpy(req->y, &sc_public_key[32], sizeof(req->y)); + + smp_send(smp, req_buf, NULL, NULL); + + if (IS_ENABLED(CONFIG_BT_USE_DEBUG_KEYS)) + { + atomic_set_bit(smp->flags, SMP_FLAG_SC_DEBUG_KEY); + } + + return 0; +} + +#if defined(CONFIG_BT_CENTRAL) +static int smp_send_pairing_req(struct bt_conn *conn) +{ + struct bt_smp *smp; + struct bt_smp_pairing *req; + struct net_buf *req_buf; + + LOG_DBG(""); + + smp = smp_chan_get(conn); + if (!smp) + { + return -ENOTCONN; + } + + /* SMP Timeout */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_TIMEOUT)) + { + return -EIO; + } + + /* A higher security level is requested during the key distribution + * phase, once pairing is complete a new pairing procedure will start. + */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_KEYS_DISTR)) + { + return 0; + } + + /* pairing is in progress */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_PAIRING)) + { + return -EBUSY; + } + + /* Encryption is in progress */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_ENC_PENDING)) + { + return -EBUSY; + } + + /* early verify if required sec level if reachable */ + + if (!sec_level_reachable(smp)) + { + return -EINVAL; + } + + if (!conn->le.keys) + { + conn->le.keys = bt_keys_get_addr(conn->id, &conn->le.dst); + if (!conn->le.keys) + { + return -ENOMEM; + } + } + + if (smp_init(smp)) + { + return -ENOBUFS; + } + + req_buf = smp_create_pdu(smp, BT_SMP_CMD_PAIRING_REQ, sizeof(*req)); + if (!req_buf) + { + return -ENOBUFS; + } + + req = net_buf_add(req_buf, sizeof(*req)); + + req->auth_req = get_auth(smp, BT_SMP_AUTH_DEFAULT); + req->io_capability = get_io_capa(smp); + + /* At this point is it unknown if pairing will be legacy or LE SC so + * set OOB flag if any OOB data is present and assume to peer device + * provides OOB data that will match it's pairing type. + */ + + req->oob_flag = + (legacy_oobd_present || + sc_oobd_present) ? BT_SMP_OOB_PRESENT : BT_SMP_OOB_NOT_PRESENT; + + req->max_key_size = BT_SMP_MAX_ENC_KEY_SIZE; + + if (req->auth_req & BT_SMP_AUTH_BONDING) + { + req->init_key_dist = SEND_KEYS; + req->resp_key_dist = RECV_KEYS; + } + else + { + req->init_key_dist = 0; + req->resp_key_dist = 0; + } + + smp->local_dist = req->init_key_dist; + smp->remote_dist = req->resp_key_dist; + + /* Store req for later use */ + + smp->preq[0] = BT_SMP_CMD_PAIRING_REQ; + memcpy(smp->preq + 1, req, sizeof(*req)); + + LOG_DBG("req: io_capability 0x%02X, oob_flag 0x%02X, auth_req 0x%02X, " + "max_key_size 0x%02X, init_key_dist 0x%02X, resp_key_dist 0x%02X", + req->io_capability, req->oob_flag, req->auth_req, + req->max_key_size, req->init_key_dist, + req->resp_key_dist); + + smp_send(smp, req_buf, NULL, NULL); + + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_RSP); + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_SECURITY_REQUEST); + atomic_set_bit(smp->flags, SMP_FLAG_PAIRING); + + return 0; +} + +static uint8_t smp_pairing_rsp(struct bt_smp *smp, struct net_buf *buf) +{ + struct bt_conn *conn = smp->chan.chan.conn; + struct bt_smp_pairing *rsp = (void *)buf->data; + struct bt_smp_pairing *req = (struct bt_smp_pairing *)&smp->preq[1]; + uint8_t err; + + LOG_DBG("rsp: io_capability 0x%02X, oob_flag 0x%02X, auth_req 0x%02X, " + "max_key_size 0x%02X, init_key_dist 0x%02X, resp_key_dist 0x%02X", + rsp->io_capability, rsp->oob_flag, rsp->auth_req, + rsp->max_key_size, rsp->init_key_dist, + rsp->resp_key_dist); + + if ((rsp->max_key_size > BT_SMP_MAX_ENC_KEY_SIZE) || + (rsp->max_key_size < BT_SMP_MIN_ENC_KEY_SIZE)) + { + return BT_SMP_ERR_ENC_KEY_SIZE; + } + + smp->local_dist &= rsp->init_key_dist; + smp->remote_dist &= rsp->resp_key_dist; + + /* Store rsp for later use */ + + smp->prsp[0] = BT_SMP_CMD_PAIRING_RSP; + memcpy(smp->prsp + 1, rsp, sizeof(*rsp)); + + if ((rsp->auth_req & BT_SMP_AUTH_SC) && (req->auth_req & BT_SMP_AUTH_SC)) + { + atomic_set_bit(smp->flags, SMP_FLAG_SC); + } + + if ((rsp->auth_req & BT_SMP_AUTH_CT2) && (req->auth_req & BT_SMP_AUTH_CT2)) + { + atomic_set_bit(smp->flags, SMP_FLAG_CT2); + } + + if ((rsp->auth_req & BT_SMP_AUTH_BONDING) && + (req->auth_req & BT_SMP_AUTH_BONDING)) + { + atomic_set_bit(smp->flags, SMP_FLAG_BOND); + } + else if (IS_ENABLED(CONFIG_BT_BONDING_REQUIRED)) + { + /* Reject pairing req if not both intend to bond */ + + LOG_DBG("Bonding required"); + return BT_SMP_ERR_UNSPECIFIED; + } + else + { + smp->local_dist = 0; + smp->remote_dist = 0; + } + + smp->method = get_pair_method(smp, rsp->io_capability); + + if (!update_keys_check(smp, conn->le.keys)) + { + return BT_SMP_ERR_AUTH_REQUIREMENTS; + } + + err = remote_sec_level_reachable(smp); + if (err) + { + return err; + } + + /* the OR operation evaluated by "if" statement below seems redundant + * when CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY is enabled, because in that case + * the SMP_FLAG_SC will always be set to false. But it's needed in order to + * inform the compiler that the inside of the "if" is the return point for + * CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY enabled builds. This avoids compiler + * warnings regarding the code after the "if" statement, that would happen + * for builds with CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY enabled + */ + + if (IS_ENABLED(CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY) || + !atomic_test_bit(smp->flags, SMP_FLAG_SC)) + { +#if defined(CONFIG_BT_SMP_SC_PAIR_ONLY) + return BT_SMP_ERR_AUTH_REQUIREMENTS; +#else + if (IS_ENABLED(CONFIG_BT_SMP_APP_PAIRING_ACCEPT)) + { + err = smp_pairing_accept_query(smp, rsp); + if (err) + { + return err; + } + } + + return legacy_pairing_rsp(smp); +#endif /* CONFIG_BT_SMP_SC_PAIR_ONLY */ + } + + const struct bt_conn_auth_cb *smp_auth_cb = latch_auth_cb(smp); + + smp->local_dist &= SEND_KEYS_SC; + smp->remote_dist &= RECV_KEYS_SC; + + if (IS_ENABLED(CONFIG_BT_SMP_APP_PAIRING_ACCEPT)) + { + err = smp_pairing_accept_query(smp, rsp); + if (err) + { + return err; + } + } + + if (!IS_ENABLED(CONFIG_BT_SMP_SC_PAIR_ONLY) && smp->method == JUST_WORKS && + atomic_test_bit(smp->flags, + SMP_FLAG_SEC_REQ) && smp_auth_cb && + smp_auth_cb->pairing_confirm) + { + atomic_set_bit(smp->flags, SMP_FLAG_USER); + smp_auth_cb->pairing_confirm(conn); + return 0; + } + + if (!sc_public_key) + { + atomic_set_bit(smp->flags, SMP_FLAG_PKEY_SEND); + return 0; + } + + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PUBLIC_KEY); + atomic_clear_bit(smp->allowed_cmds, BT_SMP_CMD_SECURITY_REQUEST); + + return sc_send_public_key(smp); +} +#else +static uint8_t smp_pairing_rsp(struct bt_smp *smp, struct net_buf *buf) +{ + return BT_SMP_ERR_CMD_NOTSUPP; +} +#endif /* CONFIG_BT_CENTRAL */ + +static uint8_t smp_pairing_confirm(struct bt_smp *smp, struct net_buf *buf) +{ + struct bt_smp_pairing_confirm *req = (void *)buf->data; + + LOG_DBG(""); + + atomic_clear_bit(smp->flags, SMP_FLAG_DISPLAY); + + memcpy(smp->pcnf, req->val, sizeof(smp->pcnf)); + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && + smp->chan.chan.conn->role == BT_HCI_ROLE_CENTRAL) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_RANDOM); + return smp_send_pairing_random(smp); + } + + if (!IS_ENABLED(CONFIG_BT_PERIPHERAL)) + { + return 0; + } + +#if !defined(CONFIG_BT_SMP_SC_PAIR_ONLY) + if (!atomic_test_bit(smp->flags, SMP_FLAG_SC)) + { + return legacy_pairing_confirm(smp); + } +#endif /* !CONFIG_BT_SMP_SC_PAIR_ONLY */ + + switch (smp->method) + { + case PASSKEY_DISPLAY: + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_RANDOM); + return smp_send_pairing_confirm(smp); + case PASSKEY_INPUT: + if (atomic_test_bit(smp->flags, SMP_FLAG_USER)) + { + atomic_set_bit(smp->flags, SMP_FLAG_CFM_DELAYED); + return 0; + } + + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_RANDOM); + return smp_send_pairing_confirm(smp); + case JUST_WORKS: + case PASSKEY_CONFIRM: + default: + LOG_ERR("Unknown pairing method (%u)", smp->method); + return BT_SMP_ERR_UNSPECIFIED; + } +} + +static uint8_t sc_smp_send_dhkey_check(struct bt_smp *smp, const uint8_t *e) +{ + struct bt_smp_dhkey_check *req; + struct net_buf *buf; + + LOG_DBG(""); + + buf = smp_create_pdu(smp, BT_SMP_DHKEY_CHECK, sizeof(*req)); + if (!buf) + { + return BT_SMP_ERR_UNSPECIFIED; + } + + req = net_buf_add(buf, sizeof(*req)); + memcpy(req->e, e, sizeof(req->e)); + + smp_send(smp, buf, NULL, NULL); + + return 0; +} + +#if defined(CONFIG_BT_CENTRAL) +static uint8_t compute_and_send_central_dhcheck(struct bt_smp *smp) +{ + uint8_t e[16]; + uint8_t r[16]; + + (void)memset(r, 0, sizeof(r)); + + switch (smp->method) + { + case JUST_WORKS: + case PASSKEY_CONFIRM: + break; + case PASSKEY_DISPLAY: + case PASSKEY_INPUT: + memcpy(r, &smp->passkey, sizeof(smp->passkey)); + break; + case LE_SC_OOB: + if (smp->oobd_remote) + { + memcpy(r, smp->oobd_remote->r, sizeof(r)); + } + break; + default: + LOG_ERR("Unknown pairing method (%u)", smp->method); + return BT_SMP_ERR_UNSPECIFIED; + } + + /* calculate LTK and mackey */ + + if (bt_crypto_f5(smp->dhkey, smp->prnd, smp->rrnd, + &smp->chan.chan.conn->le.init_addr, + &smp->chan.chan.conn->le.resp_addr, smp->mackey, smp->tk)) + { + LOG_ERR("Calculate LTK failed"); + return BT_SMP_ERR_UNSPECIFIED; + } + + /* calculate local DHKey check */ + + if (bt_crypto_f6(smp->mackey, smp->prnd, smp->rrnd, r, &smp->preq[1], + &smp->chan.chan.conn->le.init_addr, + &smp->chan.chan.conn->le.resp_addr, e)) + { + LOG_ERR("Calculate local DHKey check failed"); + return BT_SMP_ERR_UNSPECIFIED; + } + + atomic_set_bit(smp->allowed_cmds, BT_SMP_DHKEY_CHECK); + return sc_smp_send_dhkey_check(smp, e); +} +#endif /* CONFIG_BT_CENTRAL */ + +#if defined(CONFIG_BT_PERIPHERAL) +static uint8_t compute_and_check_and_send_periph_dhcheck(struct bt_smp *smp) +{ + uint8_t re[16]; + uint8_t e[16]; + uint8_t r[16]; + uint8_t err; + + (void)memset(r, 0, sizeof(r)); + + switch (smp->method) + { + case JUST_WORKS: + case PASSKEY_CONFIRM: + break; + case PASSKEY_DISPLAY: + case PASSKEY_INPUT: + memcpy(r, &smp->passkey, sizeof(smp->passkey)); + break; + case LE_SC_OOB: + if (smp->oobd_remote) + { + memcpy(r, smp->oobd_remote->r, sizeof(r)); + } + break; + default: + LOG_ERR("Unknown pairing method (%u)", smp->method); + return BT_SMP_ERR_UNSPECIFIED; + } + + /* calculate LTK and mackey */ + + if (bt_crypto_f5(smp->dhkey, smp->rrnd, smp->prnd, + &smp->chan.chan.conn->le.init_addr, + &smp->chan.chan.conn->le.resp_addr, smp->mackey, smp->tk)) + { + LOG_ERR("Calculate LTK failed"); + return BT_SMP_ERR_UNSPECIFIED; + } + + /* calculate local DHKey check */ + + if (bt_crypto_f6(smp->mackey, smp->prnd, smp->rrnd, r, &smp->prsp[1], + &smp->chan.chan.conn->le.resp_addr, + &smp->chan.chan.conn->le.init_addr, e)) + { + LOG_ERR("Calculate local DHKey check failed"); + return BT_SMP_ERR_UNSPECIFIED; + } + + if (smp->method == LE_SC_OOB) + { + if (smp->oobd_local) + { + memcpy(r, smp->oobd_local->r, sizeof(r)); + } + else + { + memset(r, 0, sizeof(r)); + } + } + + /* calculate remote DHKey check */ + + if (bt_crypto_f6(smp->mackey, smp->rrnd, smp->prnd, r, &smp->preq[1], + &smp->chan.chan.conn->le.init_addr, + &smp->chan.chan.conn->le.resp_addr, re)) + { + LOG_ERR("Calculate remote DHKey check failed"); + return BT_SMP_ERR_UNSPECIFIED; + } + + /* compare received E with calculated remote */ + + if (memcmp(smp->e, re, 16)) + { + return BT_SMP_ERR_DHKEY_CHECK_FAILED; + } + + /* send local e */ + + err = sc_smp_send_dhkey_check(smp, e); + if (err) + { + return err; + } + + atomic_set_bit(smp->flags, SMP_FLAG_ENC_PENDING); + return 0; +} +#endif /* CONFIG_BT_PERIPHERAL */ + +static void bt_smp_dhkey_ready(const uint8_t *dhkey); +static uint8_t smp_dhkey_generate(struct bt_smp *smp) +{ + int err; + + atomic_set_bit(smp->flags, SMP_FLAG_DHKEY_GEN); + err = bt_dh_key_gen(smp->pkey, bt_smp_dhkey_ready); + if (err) + { + atomic_clear_bit(smp->flags, SMP_FLAG_DHKEY_GEN); + + LOG_ERR("Failed to generate DHKey"); + return BT_SMP_ERR_UNSPECIFIED; + } + + return 0; +} + +static uint8_t smp_dhkey_ready(struct bt_smp *smp, const uint8_t *dhkey) +{ + if (!dhkey) + { + return BT_SMP_ERR_DHKEY_CHECK_FAILED; + } + + atomic_clear_bit(smp->flags, SMP_FLAG_DHKEY_PENDING); + memcpy(smp->dhkey, dhkey, BT_DH_KEY_LEN); + + /* wait for user passkey confirmation */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_USER)) + { + atomic_set_bit(smp->flags, SMP_FLAG_DHKEY_SEND); + return 0; + } + + /* wait for remote DHKey Check */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_DHCHECK_WAIT)) + { + atomic_set_bit(smp->flags, SMP_FLAG_DHKEY_SEND); + return 0; + } + + if (atomic_test_bit(smp->flags, SMP_FLAG_DHKEY_SEND)) + { +#if defined(CONFIG_BT_CENTRAL) + if (smp->chan.chan.conn->role == BT_HCI_ROLE_CENTRAL) + { + return compute_and_send_central_dhcheck(smp); + } + +#endif /* CONFIG_BT_CENTRAL */ + +#if defined(CONFIG_BT_PERIPHERAL) + return compute_and_check_and_send_periph_dhcheck(smp); +#endif /* CONFIG_BT_PERIPHERAL */ + } + + return 0; +} + +static struct bt_smp *smp_find(int flag) +{ + for (int i = 0; i < ARRAY_SIZE(bt_smp_pool); i++) + { + if (atomic_test_bit(bt_smp_pool[i].flags, flag)) + { + return &bt_smp_pool[i]; + } + } + + return NULL; +} + +static void bt_smp_dhkey_ready(const uint8_t *dhkey) +{ + LOG_DBG("%p", (void *)dhkey); + int err; + + struct bt_smp *smp = smp_find(SMP_FLAG_DHKEY_GEN); + + if (smp) + { + atomic_clear_bit(smp->flags, SMP_FLAG_DHKEY_GEN); + err = smp_dhkey_ready(smp, dhkey); + if (err) + { + smp_error(smp, err); + } + } + + err = 0; + do + { + smp = smp_find(SMP_FLAG_DHKEY_PENDING); + if (smp) + { + err = smp_dhkey_generate(smp); + if (err) + { + smp_error(smp, err); + } + } + } + while (smp && err); +} + +static uint8_t sc_smp_check_confirm(struct bt_smp *smp) +{ + uint8_t cfm[16]; + uint8_t r; + + switch (smp->method) + { + case LE_SC_OOB: + return 0; + case PASSKEY_CONFIRM: + case JUST_WORKS: + r = 0U; + break; + case PASSKEY_DISPLAY: + case PASSKEY_INPUT: + /* In the Passkey Entry protocol, the most significant + * bit of Z is set equal to one and the least significant bit is made + * up from one bit of the passkey e.g. if the passkey bit is 1, then + * Z + * = 0x81 and if the passkey bit is 0, then Z = 0x80. + */ + + r = (smp->passkey >> smp->passkey_round) & 0x01; + r |= 0x80; + break; + default: + LOG_ERR("Unknown pairing method (%u)", smp->method); + return BT_SMP_ERR_UNSPECIFIED; + } + + if (bt_crypto_f4(smp->pkey, sc_public_key, smp->rrnd, r, cfm)) + { + LOG_ERR("Calculate confirm failed"); + return BT_SMP_ERR_UNSPECIFIED; + } + + LOG_DBG("pcnf %s", bt_hex(smp->pcnf, 16)); + LOG_DBG("cfm %s", bt_hex(cfm, 16)); + + if (memcmp(smp->pcnf, cfm, 16)) + { + return BT_SMP_ERR_CONFIRM_FAILED; + } + + return 0; +} + +static bool le_sc_oob_data_req_check(struct bt_smp *smp) +{ + struct bt_smp_pairing *req = (struct bt_smp_pairing *)&smp->preq[1]; + + return ((req->oob_flag & BT_SMP_OOB_DATA_MASK) == BT_SMP_OOB_PRESENT); +} + +static bool le_sc_oob_data_rsp_check(struct bt_smp *smp) +{ + struct bt_smp_pairing *rsp = (struct bt_smp_pairing *)&smp->prsp[1]; + + return ((rsp->oob_flag & BT_SMP_OOB_DATA_MASK) == BT_SMP_OOB_PRESENT); +} + +__maybe_unused static void le_sc_oob_config_set(struct bt_smp *smp, + struct + bt_conn_oob_info *info) +{ + bool req_oob_present = le_sc_oob_data_req_check(smp); + bool rsp_oob_present = le_sc_oob_data_rsp_check(smp); + int oob_config = BT_CONN_OOB_NO_DATA; + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && + smp->chan.chan.conn->role == BT_HCI_ROLE_CENTRAL) + { + oob_config = + req_oob_present ? BT_CONN_OOB_REMOTE_ONLY : + BT_CONN_OOB_NO_DATA; + + if (rsp_oob_present) + { + oob_config = (oob_config == BT_CONN_OOB_REMOTE_ONLY) ? + BT_CONN_OOB_BOTH_PEERS + : BT_CONN_OOB_LOCAL_ONLY; + } + } + else if (IS_ENABLED(CONFIG_BT_PERIPHERAL)) + { + oob_config = + req_oob_present ? BT_CONN_OOB_LOCAL_ONLY : BT_CONN_OOB_NO_DATA; + + if (rsp_oob_present) + { + oob_config = (oob_config == BT_CONN_OOB_LOCAL_ONLY) ? + BT_CONN_OOB_BOTH_PEERS + : BT_CONN_OOB_REMOTE_ONLY; + } + } + + info->lesc.oob_config = oob_config; +} + +static uint8_t smp_pairing_random(struct bt_smp *smp, struct net_buf *buf) +{ + const struct bt_conn_auth_cb *smp_auth_cb = latch_auth_cb(smp); + struct bt_smp_pairing_random *req = (void *)buf->data; + uint32_t passkey; + uint8_t err; + + LOG_DBG(""); + + memcpy(smp->rrnd, req->val, sizeof(smp->rrnd)); + +#if !defined(CONFIG_BT_SMP_SC_PAIR_ONLY) + if (!atomic_test_bit(smp->flags, SMP_FLAG_SC)) + { + return legacy_pairing_random(smp); + } +#endif /* !CONFIG_BT_SMP_SC_PAIR_ONLY */ + +#if defined(CONFIG_BT_CENTRAL) + if (smp->chan.chan.conn->role == BT_HCI_ROLE_CENTRAL) + { + err = sc_smp_check_confirm(smp); + if (err) + { + return err; + } + + switch (smp->method) + { + case PASSKEY_CONFIRM: + + /* compare passkey before calculating LTK */ + + if (bt_crypto_g2(sc_public_key, smp->pkey, smp->prnd, smp->rrnd, + &passkey)) + { + return BT_SMP_ERR_UNSPECIFIED; + } + + atomic_set_bit(smp->flags, SMP_FLAG_USER); + atomic_set_bit(smp->flags, SMP_FLAG_DHKEY_SEND); + smp_auth_cb->passkey_confirm(smp->chan.chan.conn, passkey); + return 0; + case JUST_WORKS: + break; + case LE_SC_OOB: + break; + case PASSKEY_DISPLAY: + case PASSKEY_INPUT: + if (smp->passkey_round >= SMP_PASSKEY_ROUNDS) + { + LOG_WRN("Passkey round %u out of range", smp->passkey_round); + return BT_SMP_ERR_UNSPECIFIED; + } + + smp->passkey_round++; + if (smp->passkey_round == SMP_PASSKEY_ROUNDS) + { + break; + } + + if (bt_rand(smp->prnd, 16)) + { + return BT_SMP_ERR_UNSPECIFIED; + } + + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_CONFIRM); + return smp_send_pairing_confirm(smp); + default: + LOG_ERR("Unknown pairing method (%u)", smp->method); + return BT_SMP_ERR_UNSPECIFIED; + } + + /* wait for DHKey being generated */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_DHKEY_PENDING)) + { + atomic_set_bit(smp->flags, SMP_FLAG_DHKEY_SEND); + return 0; + } + + return compute_and_send_central_dhcheck(smp); + } +#endif /* CONFIG_BT_CENTRAL */ + +#if defined(CONFIG_BT_PERIPHERAL) + switch (smp->method) + { + case PASSKEY_CONFIRM: + if (bt_crypto_g2(smp->pkey, sc_public_key, smp->rrnd, smp->prnd, + &passkey)) + { + return BT_SMP_ERR_UNSPECIFIED; + } + + atomic_set_bit(smp->flags, SMP_FLAG_USER); + smp_auth_cb->passkey_confirm(smp->chan.chan.conn, passkey); + break; + case JUST_WORKS: + break; + case PASSKEY_DISPLAY: + case PASSKEY_INPUT: + if (smp->passkey_round >= SMP_PASSKEY_ROUNDS) + { + LOG_WRN("Passkey round %u out of range", smp->passkey_round); + return BT_SMP_ERR_UNSPECIFIED; + } + + err = sc_smp_check_confirm(smp); + if (err) + { + return err; + } + + err = smp_send_pairing_random(smp); + if (err) + { + return err; + } + + smp->passkey_round++; + if (smp->passkey_round == SMP_PASSKEY_ROUNDS) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_DHKEY_CHECK); + atomic_set_bit(smp->flags, SMP_FLAG_DHCHECK_WAIT); + return 0; + } + + /* Only accept another confirm if a round is actually left, + * otherwise a peer can keep the protocol going past the last passkey + * bit. + */ + + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_CONFIRM); + + if (bt_rand(smp->prnd, 16)) + { + return BT_SMP_ERR_UNSPECIFIED; + } + + return 0; + case LE_SC_OOB: + + /* Step 6: Select random N */ + + if (bt_rand(smp->prnd, 16)) + { + return BT_SMP_ERR_UNSPECIFIED; + } + + if (!IS_ENABLED(CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY) && smp_auth_cb && + smp_auth_cb->oob_data_request) + { + struct bt_conn_oob_info info = + { + .type = BT_CONN_OOB_LE_SC, + .lesc.oob_config = BT_CONN_OOB_NO_DATA, + }; + + le_sc_oob_config_set(smp, &info); + + smp->oobd_local = NULL; + smp->oobd_remote = NULL; + + atomic_set_bit(smp->flags, SMP_FLAG_OOB_PENDING); + smp_auth_cb->oob_data_request(smp->chan.chan.conn, &info); + + return 0; + } + else + { + return BT_SMP_ERR_OOB_NOT_AVAIL; + } + + default: + LOG_ERR("Unknown pairing method (%u)", smp->method); + return BT_SMP_ERR_UNSPECIFIED; + } + + atomic_set_bit(smp->allowed_cmds, BT_SMP_DHKEY_CHECK); + atomic_set_bit(smp->flags, SMP_FLAG_DHCHECK_WAIT); + return smp_send_pairing_random(smp); +#else + return BT_SMP_ERR_PAIRING_NOTSUPP; +#endif /* CONFIG_BT_PERIPHERAL */ +} + +static uint8_t smp_pairing_failed(struct bt_smp *smp, struct net_buf *buf) +{ + struct bt_conn *conn = smp->chan.chan.conn; + const struct bt_conn_auth_cb *smp_auth_cb = latch_auth_cb(smp); + struct bt_smp_pairing_fail *req = (void *)buf->data; + + LOG_ERR("pairing failed (peer reason 0x%x)", req->reason); + + if (atomic_test_and_clear_bit(smp->flags, SMP_FLAG_USER) || + atomic_test_and_clear_bit(smp->flags, SMP_FLAG_DISPLAY)) + { + if (smp_auth_cb && smp_auth_cb->cancel) + { + smp_auth_cb->cancel(conn); + } + } + + smp_pairing_complete(smp, req->reason); + + /* return no error to avoid sending Pairing Failed in response */ + + return 0; +} + +static uint8_t smp_ident_info(struct bt_smp *smp, struct net_buf *buf) +{ + LOG_DBG(""); + + if (atomic_test_bit(smp->flags, SMP_FLAG_BOND)) + { + struct bt_smp_ident_info *req = (void *)buf->data; + struct bt_conn *conn = smp->chan.chan.conn; + struct bt_keys *keys; + + keys = bt_keys_get_type(BT_KEYS_IRK, conn->id, &conn->le.dst); + if (!keys) + { + LOG_ERR("Unable to get keys for " + "%s", bt_addr_le_str(&conn->le.dst)); + return BT_SMP_ERR_UNSPECIFIED; + } + + memcpy(keys->irk.val, req->irk, 16); + } + + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_IDENT_ADDR_INFO); + + return 0; +} + +static uint8_t smp_id_add_replace(struct bt_smp *smp, + struct bt_keys *new_bond) +{ + struct bt_keys *conflict; + + /* Sanity check: It does not make sense to finalize a bond before we + * have the remote identity. + */ + + DEBUGASSERT(!(smp->remote_dist & BT_SMP_DIST_ID_KEY)); + + conflict = bt_id_find_conflict(new_bond); + if (conflict) + { + LOG_DBG("New bond conflicts with a bond on id %d.", conflict->id); + } + + if (conflict && !IS_ENABLED(CONFIG_BT_ID_UNPAIR_MATCHING_BONDS)) + { + LOG_WRN("Refusing new pairing. The old bond must be unpaired first."); + return BT_SMP_ERR_AUTH_REQUIREMENTS; + } + + if (conflict && IS_ENABLED(CONFIG_BT_ID_UNPAIR_MATCHING_BONDS)) + { + bool trust_ok; + __maybe_unused int unpair_err; + + trust_ok = update_keys_check(smp, conflict); + if (!trust_ok) + { + LOG_WRN("Refusing new pairing. The old bond has more trust."); + return BT_SMP_ERR_AUTH_REQUIREMENTS; + } + + LOG_DBG("Un-pairing old conflicting bond and finalizing new."); + + unpair_err = bt_unpair(conflict->id, &conflict->addr); + DEBUGASSERT(!unpair_err); + } + + DEBUGASSERT(!bt_id_find_conflict(new_bond)); + bt_id_add(new_bond); + return 0; +} + +struct addr_match +{ + const bt_addr_le_t *rpa; + const bt_addr_le_t *id_addr; + struct bt_keys *keys; +}; + +static void convert_to_id_on_match(struct bt_conn *conn, void *data) +{ + struct addr_match *addr_match = data; + + if (bt_addr_le_eq(&conn->le.dst, addr_match->rpa)) + { + bt_addr_le_copy(&conn->le.dst, addr_match->id_addr); + + if (conn->le.keys && conn->le.keys != addr_match->keys) + { + bt_addr_le_copy(&conn->le.keys->addr, addr_match->id_addr); + } + } +} + +static uint8_t smp_ident_addr_info(struct bt_smp *smp, struct net_buf *buf) +{ + struct bt_conn *conn = smp->chan.chan.conn; + struct bt_smp_ident_addr_info *req = (void *)buf->data; + uint8_t err; + + LOG_DBG("identity %s", bt_addr_le_str(&req->addr)); + + smp->remote_dist &= ~BT_SMP_DIST_ID_KEY; + + if (!bt_addr_le_is_identity(&req->addr)) + { + LOG_ERR("Invalid identity %s", bt_addr_le_str(&req->addr)); + LOG_ERR(" for %s", bt_addr_le_str(&conn->le.dst)); + return BT_SMP_ERR_INVALID_PARAMS; + } + + if (!bt_addr_le_eq(&conn->le.dst, &req->addr)) + { + struct bt_keys *keys = bt_keys_find_addr(conn->id, &req->addr); + + if (keys) + { + if (!update_keys_check(smp, keys)) + { + return BT_SMP_ERR_UNSPECIFIED; + } + + bt_keys_clear(keys); + } + } + + if (atomic_test_bit(smp->flags, SMP_FLAG_BOND)) + { + const bt_addr_le_t *dst; + struct bt_keys *keys; + + keys = bt_keys_get_type(BT_KEYS_IRK, conn->id, &conn->le.dst); + if (!keys) + { + LOG_ERR("Unable to get keys for " + "%s", bt_addr_le_str(&conn->le.dst)); + return BT_SMP_ERR_UNSPECIFIED; + } + + /* We can't use conn->dst here as this might already contain + * identity address known from previous pairing. Since all keys are + * cleared on re-pairing we wouldn't store IRK distributed in new + * pairing. + */ + + if (conn->role == BT_HCI_ROLE_CENTRAL) + { + dst = &conn->le.resp_addr; + } + else + { + dst = &conn->le.init_addr; + } + + if (bt_addr_le_is_rpa(dst)) + { + /* always update last use RPA */ + + bt_addr_copy(&keys->irk.rpa, &dst->a); + + /* Update connection address and notify about identity + * resolved only if connection wasn't already reported with + * identity address. This may happen if IRK was present before ie. + * due to re-pairing. + */ + + if (!bt_addr_le_is_identity(&conn->le.dst)) + { + struct addr_match addr_match = + { + .rpa = &conn->le.dst, + .id_addr = &req->addr, + .keys = keys, + }; + + bt_conn_foreach(BT_CONN_TYPE_LE, convert_to_id_on_match, + &addr_match); + bt_addr_le_copy(&keys->addr, &req->addr); + + bt_conn_identity_resolved(conn); + } + } + + err = smp_id_add_replace(smp, keys); + if (err) + { + return err; + } + } + + if (smp->remote_dist & BT_SMP_DIST_SIGN) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_SIGNING_INFO); + } + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && conn->role == BT_HCI_ROLE_CENTRAL && + !smp->remote_dist) + { + err = bt_smp_distribute_keys(smp); + if (err) + { + return err; + } + } + + /* if all keys were distributed, pairing is done */ + + if (!smp->local_dist && !smp->remote_dist) + { + smp_pairing_complete(smp, 0); + } + + return 0; +} + +#if defined(CONFIG_BT_SIGNING) +static uint8_t smp_signing_info(struct bt_smp *smp, struct net_buf *buf) +{ + struct bt_conn *conn = smp->chan.chan.conn; + uint8_t err; + + LOG_DBG(""); + + if (atomic_test_bit(smp->flags, SMP_FLAG_BOND)) + { + struct bt_smp_signing_info *req = (void *)buf->data; + struct bt_keys *keys; + + keys = bt_keys_get_type(BT_KEYS_REMOTE_CSRK, conn->id, &conn->le.dst); + if (!keys) + { + LOG_ERR("Unable to get keys for " + "%s", bt_addr_le_str(&conn->le.dst)); + return BT_SMP_ERR_UNSPECIFIED; + } + + memcpy(keys->remote_csrk.val, req->csrk, + sizeof(keys->remote_csrk.val)); + } + + smp->remote_dist &= ~BT_SMP_DIST_SIGN; + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && conn->role == BT_HCI_ROLE_CENTRAL && + !smp->remote_dist) + { + err = bt_smp_distribute_keys(smp); + if (err) + { + return err; + } + } + + /* if all keys were distributed, pairing is done */ + + if (!smp->local_dist && !smp->remote_dist) + { + smp_pairing_complete(smp, 0); + } + + return 0; +} +#else +static uint8_t smp_signing_info(struct bt_smp *smp, struct net_buf *buf) +{ + return BT_SMP_ERR_CMD_NOTSUPP; +} +#endif /* CONFIG_BT_SIGNING */ + +#if defined(CONFIG_BT_CENTRAL) +static uint8_t smp_security_request(struct bt_smp *smp, struct net_buf *buf) +{ + struct bt_conn *conn = smp->chan.chan.conn; + struct bt_smp_security_request *req = (void *)buf->data; + uint8_t auth; + + LOG_DBG(""); + + /* A higher security level is requested during the key distribution + * phase, once pairing is complete a new pairing procedure will start. + */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_KEYS_DISTR)) + { + return 0; + } + + if (atomic_test_bit(smp->flags, SMP_FLAG_PAIRING)) + { + /* We have already started pairing process */ + + return 0; + } + + if (atomic_test_bit(smp->flags, SMP_FLAG_ENC_PENDING)) + { + /* We have already started encryption procedure */ + + return 0; + } + + if (sc_supported) + { + auth = req->auth_req & BT_SMP_AUTH_MASK_SC; + } + else + { + auth = req->auth_req & BT_SMP_AUTH_MASK; + } + + if (IS_ENABLED(CONFIG_BT_SMP_SC_PAIR_ONLY) && !(auth & BT_SMP_AUTH_SC)) + { + return BT_SMP_ERR_AUTH_REQUIREMENTS; + } + + if (IS_ENABLED(CONFIG_BT_BONDING_REQUIRED) && + !(latch_bondable(smp) && (auth & BT_SMP_AUTH_BONDING))) + { + /* Reject security req if not both intend to bond */ + + LOG_DBG("Bonding required"); + return BT_SMP_ERR_UNSPECIFIED; + } + + if (conn->le.keys) + { + /* Make sure we have an LTK to encrypt with */ + + if (!(conn->le.keys->keys & (BT_KEYS_LTK_P256 | BT_KEYS_LTK))) + { + goto pair; + } + } + else + { + conn->le.keys = bt_keys_find(BT_KEYS_LTK_P256, conn->id, + &conn->le.dst); + if (!conn->le.keys) + { + conn->le.keys = bt_keys_find(BT_KEYS_LTK, conn->id, &conn->le.dst); + } + } + + if (!conn->le.keys) + { + goto pair; + } + + /* if MITM required key must be authenticated */ + + if ((auth & BT_SMP_AUTH_MITM) && + !(conn->le.keys->flags & BT_KEYS_AUTHENTICATED)) + { + if (get_io_capa(smp) != BT_SMP_IO_NO_INPUT_OUTPUT) + { + LOG_INF("New auth requirements: 0x%x, repairing", auth); + goto pair; + } + + LOG_WRN("Unsupported auth requirements: 0x%x, repairing", auth); + goto pair; + } + + /* if LE SC required and no p256 key present repair */ + + if ((auth & BT_SMP_AUTH_SC) && !(conn->le.keys->keys & BT_KEYS_LTK_P256)) + { + LOG_INF("New auth requirements: 0x%x, repairing", auth); + goto pair; + } + + if (bt_conn_le_start_encryption(conn, conn->le.keys->ltk.rand, + conn->le.keys->ltk.ediv, + conn->le.keys->ltk.val, + conn->le.keys->enc_size) < 0) + { + LOG_ERR("Failed to start encryption"); + return BT_SMP_ERR_UNSPECIFIED; + } + + atomic_set_bit(smp->flags, SMP_FLAG_ENC_PENDING); + + return 0; +pair: + if (smp_send_pairing_req(conn) < 0) + { + return BT_SMP_ERR_UNSPECIFIED; + } + + atomic_set_bit(smp->flags, SMP_FLAG_SEC_REQ); + + return 0; +} +#else +static uint8_t smp_security_request(struct bt_smp *smp, struct net_buf *buf) +{ + return BT_SMP_ERR_CMD_NOTSUPP; +} +#endif /* CONFIG_BT_CENTRAL */ + +__maybe_unused static uint8_t generate_dhkey(struct bt_smp *smp) +{ + if (IS_ENABLED(CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY)) + { + return BT_SMP_ERR_UNSPECIFIED; + } + + atomic_set_bit(smp->flags, SMP_FLAG_DHKEY_PENDING); + if (!smp_find(SMP_FLAG_DHKEY_GEN)) + { + return smp_dhkey_generate(smp); + } + + return 0; +} + +__maybe_unused static uint8_t display_passkey(struct bt_smp *smp) +{ + struct bt_conn *conn = smp->chan.chan.conn; + const struct bt_conn_auth_cb *smp_auth_cb = latch_auth_cb(smp); + uint32_t passkey = BT_PASSKEY_RAND; + +#if defined(CONFIG_BT_APP_PASSKEY) + if (smp_auth_cb && smp_auth_cb->app_passkey) + { + passkey = smp_auth_cb->app_passkey(conn); + + if (passkey != BT_PASSKEY_RAND && passkey > 999999) + { + LOG_WRN("App-provided passkey is out of valid range: %u", passkey); + return BT_SMP_ERR_UNSPECIFIED; + } + } +#endif /* CONFIG_BT_APP_PASSKEY */ + + if (passkey == BT_PASSKEY_RAND) + { + if (bt_rand(&passkey, sizeof(passkey))) + { + return BT_SMP_ERR_UNSPECIFIED; + } + + passkey %= 1000000; + } + + smp->passkey = passkey; + smp->passkey_round = 0U; + + if (smp_auth_cb && smp_auth_cb->passkey_display) + { + atomic_set_bit(smp->flags, SMP_FLAG_DISPLAY); + smp_auth_cb->passkey_display(conn, smp->passkey); + } + + smp->passkey = sys_cpu_to_le32(smp->passkey); + + return 0; +} + +#if defined(CONFIG_BT_PERIPHERAL) +static uint8_t smp_public_key_periph(struct bt_smp *smp) +{ + const struct bt_conn_auth_cb *smp_auth_cb = latch_auth_cb(smp); + uint8_t err; + + if (!atomic_test_bit(smp->flags, SMP_FLAG_SC_DEBUG_KEY) && + memcmp(smp->pkey, sc_public_key, BT_PUB_KEY_COORD_LEN) == 0) + { + /* Deny public key with identical X coordinate unless it is the + * debug public key. + */ + + LOG_WRN("Remote public key rejected"); + return BT_SMP_ERR_UNSPECIFIED; + } + + err = sc_send_public_key(smp); + if (err) + { + return err; + } + + switch (smp->method) + { + case PASSKEY_CONFIRM: + case JUST_WORKS: + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_RANDOM); + + err = smp_send_pairing_confirm(smp); + if (err) + { + return err; + } + break; + case PASSKEY_DISPLAY: + err = display_passkey(smp); + if (err) + { + return err; + } + + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_CONFIRM); + atomic_set_bit(smp->allowed_cmds, BT_SMP_KEYPRESS_NOTIFICATION); + break; + case PASSKEY_INPUT: + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_CONFIRM); + atomic_set_bit(smp->allowed_cmds, BT_SMP_KEYPRESS_NOTIFICATION); + atomic_set_bit(smp->flags, SMP_FLAG_USER); + smp_auth_cb->passkey_entry(smp->chan.chan.conn); + break; + case LE_SC_OOB: + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_RANDOM); + break; + default: + LOG_ERR("Unknown pairing method (%u)", smp->method); + return BT_SMP_ERR_UNSPECIFIED; + } + + return generate_dhkey(smp); +} +#endif /* CONFIG_BT_PERIPHERAL */ + +#ifdef CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY +static uint8_t smp_public_key(struct bt_smp *smp, struct net_buf *buf) +{ + return BT_SMP_ERR_AUTH_REQUIREMENTS; +} +#else +static uint8_t smp_public_key(struct bt_smp *smp, struct net_buf *buf) +{ + const struct bt_conn_auth_cb *smp_auth_cb = latch_auth_cb(smp); + struct bt_smp_public_key *req = (void *)buf->data; + uint8_t err; + + LOG_DBG(""); + + memcpy(smp->pkey, req->x, BT_PUB_KEY_COORD_LEN); + memcpy(&smp->pkey[BT_PUB_KEY_COORD_LEN], req->y, BT_PUB_KEY_COORD_LEN); + + /* mark key as debug if remote is using it */ + + if (bt_pub_key_is_debug(smp->pkey)) + { + LOG_INF("Remote is using Debug Public key"); + atomic_set_bit(smp->flags, SMP_FLAG_SC_DEBUG_KEY); + + /* Don't allow a bond established without debug key to be + * updated using LTK generated from debug key. + */ + + if (!update_debug_keys_check(smp)) + { + return BT_SMP_ERR_AUTH_REQUIREMENTS; + } + } + else if (!bt_pub_key_is_valid(smp->pkey)) + { + LOG_WRN("Received invalid public key"); + return BT_SMP_ERR_DHKEY_CHECK_FAILED; + } + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && + smp->chan.chan.conn->role == BT_HCI_ROLE_CENTRAL) + { + if (!atomic_test_bit(smp->flags, SMP_FLAG_SC_DEBUG_KEY) && + memcmp(smp->pkey, sc_public_key, BT_PUB_KEY_COORD_LEN) == 0) + { + /* Deny public key with identical X coordinate unless + * it is the debug public key. + */ + + LOG_WRN("Remote public key rejected"); + return BT_SMP_ERR_UNSPECIFIED; + } + + switch (smp->method) + { + case PASSKEY_CONFIRM: + case JUST_WORKS: + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_CONFIRM); + break; + case PASSKEY_DISPLAY: + err = display_passkey(smp); + if (err) + { + return err; + } + + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_CONFIRM); + + atomic_set_bit(smp->allowed_cmds, BT_SMP_KEYPRESS_NOTIFICATION); + + err = smp_send_pairing_confirm(smp); + if (err) + { + return err; + } + break; + case PASSKEY_INPUT: + atomic_set_bit(smp->flags, SMP_FLAG_USER); + smp_auth_cb->passkey_entry(smp->chan.chan.conn); + + atomic_set_bit(smp->allowed_cmds, BT_SMP_KEYPRESS_NOTIFICATION); + + break; + case LE_SC_OOB: + + /* Step 6: Select random N */ + + if (bt_rand(smp->prnd, 16)) + { + return BT_SMP_ERR_UNSPECIFIED; + } + + if (smp_auth_cb && smp_auth_cb->oob_data_request) + { + struct bt_conn_oob_info info = + { + .type = BT_CONN_OOB_LE_SC, + .lesc.oob_config = BT_CONN_OOB_NO_DATA, + }; + + le_sc_oob_config_set(smp, &info); + + smp->oobd_local = NULL; + smp->oobd_remote = NULL; + + atomic_set_bit(smp->flags, SMP_FLAG_OOB_PENDING); + smp_auth_cb->oob_data_request(smp->chan.chan.conn, &info); + } + else + { + return BT_SMP_ERR_OOB_NOT_AVAIL; + } + break; + default: + LOG_ERR("Unknown pairing method (%u)", smp->method); + return BT_SMP_ERR_UNSPECIFIED; + } + + return generate_dhkey(smp); + } + +#if defined(CONFIG_BT_PERIPHERAL) + if (!sc_public_key) + { + atomic_set_bit(smp->flags, SMP_FLAG_PKEY_SEND); + return 0; + } + + err = smp_public_key_periph(smp); + if (err) + { + return err; + } +#endif /* CONFIG_BT_PERIPHERAL */ + + return 0; +} +#endif /* CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY */ + +static uint8_t smp_dhkey_check(struct bt_smp *smp, struct net_buf *buf) +{ + struct bt_smp_dhkey_check *req = (void *)buf->data; + + LOG_DBG(""); + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && + smp->chan.chan.conn->role == BT_HCI_ROLE_CENTRAL) + { + uint8_t e[16]; + uint8_t r[16]; + uint8_t enc_size; + uint8_t ediv[2]; + uint8_t rand[8]; + + (void)memset(r, 0, sizeof(r)); + + switch (smp->method) + { + case JUST_WORKS: + case PASSKEY_CONFIRM: + break; + case PASSKEY_DISPLAY: + case PASSKEY_INPUT: + memcpy(r, &smp->passkey, sizeof(smp->passkey)); + break; + case LE_SC_OOB: + if (smp->oobd_local) + { + memcpy(r, smp->oobd_local->r, sizeof(r)); + } + break; + default: + LOG_ERR("Unknown pairing method (%u)", smp->method); + return BT_SMP_ERR_UNSPECIFIED; + } + + /* calculate remote DHKey check for comparison */ + + if (bt_crypto_f6(smp->mackey, smp->rrnd, smp->prnd, r, &smp->prsp[1], + &smp->chan.chan.conn->le.resp_addr, + &smp->chan.chan.conn->le.init_addr, e)) + { + return BT_SMP_ERR_UNSPECIFIED; + } + + if (memcmp(e, req->e, 16)) + { + return BT_SMP_ERR_DHKEY_CHECK_FAILED; + } + + enc_size = get_encryption_key_size(smp); + + /* Rand and EDiv are 0 */ + + (void)memset(ediv, 0, sizeof(ediv)); + (void)memset(rand, 0, sizeof(rand)); + if (bt_conn_le_start_encryption(smp->chan.chan.conn, rand, ediv, + smp->tk, + enc_size) < 0) + { + LOG_ERR("Failed to start encryption"); + return BT_SMP_ERR_UNSPECIFIED; + } + + atomic_set_bit(smp->flags, SMP_FLAG_ENC_PENDING); + + if (IS_ENABLED(CONFIG_BT_SMP_USB_HCI_CTLR_WORKAROUND)) + { + if (smp->remote_dist & BT_SMP_DIST_ID_KEY) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_IDENT_INFO); + } + else if (smp->remote_dist & BT_SMP_DIST_SIGN) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_SIGNING_INFO); + } + } + + return 0; + } + +#if defined(CONFIG_BT_PERIPHERAL) + if (smp->chan.chan.conn->role == BT_HCI_ROLE_PERIPHERAL) + { + atomic_clear_bit(smp->flags, SMP_FLAG_DHCHECK_WAIT); + memcpy(smp->e, req->e, sizeof(smp->e)); + + /* wait for DHKey being generated */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_DHKEY_PENDING)) + { + atomic_set_bit(smp->flags, SMP_FLAG_DHKEY_SEND); + return 0; + } + + /* waiting for user to confirm passkey */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_USER)) + { + atomic_set_bit(smp->flags, SMP_FLAG_DHKEY_SEND); + return 0; + } + + return compute_and_check_and_send_periph_dhcheck(smp); + } +#endif /* CONFIG_BT_PERIPHERAL */ + + return 0; +} + +#if defined(CONFIG_BT_PASSKEY_KEYPRESS) +static uint8_t smp_keypress_notif(struct bt_smp *smp, struct net_buf *buf) +{ + const struct bt_conn_auth_cb *smp_auth_cb = latch_auth_cb(smp); + struct bt_conn *conn = smp->chan.chan.conn; + struct bt_smp_keypress_notif *notif = (void *)buf->data; + enum bt_conn_auth_keypress type = notif->type; + + LOG_DBG("Keypress from conn %u, type %u", bt_conn_index(conn), type); + + /* For now, keypress notifications are always accepted. In the future we + * should be smarter about this. We might also want to enforce something + * about the 'start' and 'end' messages. + */ + + atomic_set_bit(smp->allowed_cmds, BT_SMP_KEYPRESS_NOTIFICATION); + + if (!IN_RANGE(type, BT_CONN_AUTH_KEYPRESS_ENTRY_STARTED, + BT_CONN_AUTH_KEYPRESS_ENTRY_COMPLETED)) + { + LOG_WRN("Received unknown keypress event type %u. Discarding.", type); + return BT_SMP_ERR_INVALID_PARAMS; + } + + /* Reset SMP timeout, like the spec says. */ + + bt_work_reschedule(&smp->work, SMP_TIMEOUT); + + if (smp_auth_cb->passkey_display_keypress) + { + smp_auth_cb->passkey_display_keypress(conn, type); + } + + return 0; +} +#else +static uint8_t smp_keypress_notif(struct bt_smp *smp, struct net_buf *buf) +{ + ARG_UNUSED(smp); + ARG_UNUSED(buf); + + LOG_DBG(""); + + /* Ignore packets until keypress notifications are fully supported. */ + + atomic_set_bit(smp->allowed_cmds, BT_SMP_KEYPRESS_NOTIFICATION); + return 0; +} +#endif + +static const struct +{ + uint8_t (*func)(struct bt_smp *smp, struct net_buf *buf); + uint8_t expect_len; +} + +handlers[] = +{ + { + }, + + /* No op-code defined for 0x00 */ + + { + smp_pairing_req, sizeof(struct bt_smp_pairing) + }, + + { + smp_pairing_rsp, sizeof(struct bt_smp_pairing) + }, + + { + smp_pairing_confirm, sizeof(struct bt_smp_pairing_confirm) + }, + + { + smp_pairing_random, sizeof(struct bt_smp_pairing_random) + }, + + { + smp_pairing_failed, sizeof(struct bt_smp_pairing_fail) + }, + + { + smp_encrypt_info, sizeof(struct bt_smp_encrypt_info) + }, + + { + smp_central_ident, sizeof(struct bt_smp_central_ident) + }, + + { + smp_ident_info, sizeof(struct bt_smp_ident_info) + }, + + { + smp_ident_addr_info, sizeof(struct bt_smp_ident_addr_info) + }, + + { + smp_signing_info, sizeof(struct bt_smp_signing_info) + }, + + { + smp_security_request, sizeof(struct bt_smp_security_request) + }, + + { + smp_public_key, sizeof(struct bt_smp_public_key) + }, + + { + smp_dhkey_check, sizeof(struct bt_smp_dhkey_check) + }, + + { + smp_keypress_notif, sizeof(struct bt_smp_keypress_notif) + }, +}; + +static bool is_in_pairing_procedure(struct bt_smp *smp) +{ + return atomic_test_bit(smp->flags, SMP_FLAG_PAIRING); +} + +static int bt_smp_recv(struct bt_l2cap_chan *chan, struct net_buf *buf) +{ + struct bt_smp *smp = CONTAINER_OF(chan, struct bt_smp, chan.chan); + struct bt_smp_hdr *hdr; + uint8_t err; + + if (buf->len < sizeof(*hdr)) + { + LOG_ERR("Too small SMP PDU received"); + return 0; + } + + hdr = net_buf_pull_mem(buf, sizeof(*hdr)); + LOG_DBG("Received SMP code 0x%02x len %u", hdr->code, buf->len); + + /* If SMP timeout occurred "no further SMP commands shall be sent over + * the L2CAP Security Manager Channel. A new SM procedure shall only be + * performed when a new physical link has been established." + */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_TIMEOUT)) + { + LOG_WRN("SMP command (code 0x%02x) received after timeout", hdr->code); + return 0; + } + + /* Bluetooth Core Specification Version 5.2, Vol 3, Part H, page 1667: + * If a packet is received with a Code that is reserved for future use it + * shall be ignored. + */ + + if (hdr->code >= ARRAY_SIZE(handlers)) + { + LOG_WRN("Received reserved SMP code 0x%02x", hdr->code); + return 0; + } + + if (!handlers[hdr->code].func) + { + LOG_WRN("Unhandled SMP code 0x%02x", hdr->code); + smp_error(smp, BT_SMP_ERR_CMD_NOTSUPP); + return 0; + } + + if (!atomic_test_and_clear_bit(smp->allowed_cmds, hdr->code)) + { + LOG_WRN("Unexpected SMP code 0x%02x", hdr->code); + + /* Do not send errors outside of pairing procedure. */ + + if (is_in_pairing_procedure(smp)) + { + smp_error(smp, BT_SMP_ERR_UNSPECIFIED); + } + + return 0; + } + + if (buf->len != handlers[hdr->code].expect_len) + { + LOG_ERR("Invalid len %u for code 0x%02x", buf->len, hdr->code); + smp_error(smp, BT_SMP_ERR_INVALID_PARAMS); + return 0; + } + + err = handlers[hdr->code].func(smp, buf); + if (err) + { + smp_error(smp, err); + } + + return 0; +} + +static void pub_key_ready_notify(void) +{ + int err; + + ARG_UNUSED(err); + + err = nxrmutex_lock(&pub_key_gen.lock); + DEBUGASSERT(err == 0); + + (void)k_condvar_broadcast(&pub_key_gen.condvar); + + err = nxrmutex_unlock(&pub_key_gen.lock); + DEBUGASSERT(err == 0); +} + +static void bt_smp_pkey_ready(const uint8_t *pkey) +{ + int i; + + LOG_DBG(""); + + sc_public_key = pkey; + + pub_key_ready_notify(); + + if (!pkey) + { + LOG_WRN("Public key not available"); + return; + } + + for (i = 0; i < ARRAY_SIZE(bt_smp_pool); i++) + { + struct bt_smp *smp = &bt_smp_pool[i]; + uint8_t err; + + if (!atomic_test_bit(smp->flags, SMP_FLAG_PKEY_SEND)) + { + continue; + } + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && + smp->chan.chan.conn->role == BT_HCI_ROLE_CENTRAL) + { + err = sc_send_public_key(smp); + if (err) + { + smp_error(smp, err); + } + + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PUBLIC_KEY); + continue; + } + +#if defined(CONFIG_BT_PERIPHERAL) + err = smp_public_key_periph(smp); + if (err) + { + smp_error(smp, err); + } +#endif /* CONFIG_BT_PERIPHERAL */ + } +} + +static void bt_smp_connected(struct bt_l2cap_chan *chan) +{ + struct bt_smp *smp = CONTAINER_OF(chan, struct bt_smp, chan.chan); + + LOG_DBG("chan %p cid 0x%04x", chan, CONTAINER_OF(chan, + struct bt_l2cap_le_chan, chan)->tx.cid); + + k_work_init_delayable(&smp->work, smp_timeout); + smp_reset(smp); + + atomic_ptr_xchg(&smp->auth_cb, BT_SMP_AUTH_CB_UNINITIALIZED); + atomic_set(&smp->bondable, BT_SMP_BONDABLE_UNINITIALIZED); +} + +static void bt_smp_disconnected(struct bt_l2cap_chan *chan) +{ + struct bt_smp *smp = CONTAINER_OF(chan, struct bt_smp, chan.chan); + struct bt_keys *keys = chan->conn->le.keys; + + LOG_DBG("chan %p cid 0x%04x", chan, CONTAINER_OF(chan, + struct bt_l2cap_le_chan, chan)->tx.cid); + + /* The channel disconnected callback and timeout work both run on the + * Bluetooth workqueue, so canceling the timeout work should always + * succeed. + */ + + (void)k_work_cancel_delayable(&smp->work); + + if (atomic_test_bit(smp->flags, SMP_FLAG_PAIRING) || + atomic_test_bit(smp->flags, SMP_FLAG_ENC_PENDING) || + atomic_test_bit(smp->flags, SMP_FLAG_SEC_REQ)) + { + /* reset context and report */ + + smp_pairing_complete(smp, BT_SMP_ERR_UNSPECIFIED); + } + + if (keys) + { + /* If debug keys were used for pairing remove them. + * No keys indicate no bonding so free keys storage. + */ + + if (!keys->keys || (!IS_ENABLED(CONFIG_BT_STORE_DEBUG_KEYS) && + (keys->flags & BT_KEYS_DEBUG))) + { + bt_keys_clear(keys); + } + } + + (void)memset(smp, 0, sizeof(*smp)); +} + +static void bt_smp_encrypt_change(struct bt_l2cap_chan *chan, + uint8_t hci_status) +{ + struct bt_smp *smp = CONTAINER_OF(chan, struct bt_smp, chan.chan); + struct bt_conn *conn = chan->conn; + + LOG_DBG("chan %p conn %p handle %u encrypt 0x%02x hci status 0x%02x %s", + chan, conn, conn->handle, + conn->encrypt, hci_status, bt_hci_err_to_str(hci_status)); + + if (!atomic_test_and_clear_bit(smp->flags, SMP_FLAG_ENC_PENDING)) + { + /* We where not waiting for encryption procedure. + * This happens when encrypt change is called to notify that security + * has failed before starting encryption. + */ + + return; + } + + if (hci_status) + { + if (atomic_test_bit(smp->flags, SMP_FLAG_PAIRING)) + { + uint8_t smp_err = smp_err_get(bt_security_err_get(hci_status)); + + /* Fail as if it happened during key distribution */ + + atomic_set_bit(smp->flags, SMP_FLAG_KEYS_DISTR); + smp_pairing_complete(smp, smp_err); + } + + return; + } + + if (!conn->encrypt) + { + return; + } + + /* We were waiting for encryption but with no pairing in progress. + * This can happen if paired peripheral sent Security Request and we + * enabled encryption. + */ + + if (!atomic_test_bit(smp->flags, SMP_FLAG_PAIRING)) + { + smp_reset(smp); + return; + } + + /* derive BR/EDR LinkKey if supported by both sides */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_SC)) + { + if ((smp->local_dist & BT_SMP_DIST_LINK_KEY) && + (smp->remote_dist & BT_SMP_DIST_LINK_KEY)) + { + /* Link Key will be derived after key distribution to + * make sure remote device identity is known + */ + + atomic_set_bit(smp->flags, SMP_FLAG_DERIVE_LK); + } + + /* Those are used as pairing finished indicator so generated + * but not distributed keys must be cleared here. + */ + + smp->local_dist &= ~BT_SMP_DIST_LINK_KEY; + smp->remote_dist &= ~BT_SMP_DIST_LINK_KEY; + } + + if (smp->remote_dist & BT_SMP_DIST_ENC_KEY) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_ENCRYPT_INFO); + } + else if (smp->remote_dist & BT_SMP_DIST_ID_KEY) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_IDENT_INFO); + } + else if (smp->remote_dist & BT_SMP_DIST_SIGN) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_SIGNING_INFO); + } + + /* This is the last point that is common for all code paths in the + * pairing process (during which we still have the option to send Pairing + * Failed). That makes it convenient to update the RL here. We want to + * update the RL during the pairing process so that we can fail it in case + * there is a conflict with an existing bond. + * + * We can do the update here only if the peer does not intend to send us + * any identity information. In this case we already have everything that + * goes into the RL. + * + * We need an entry in the RL despite the remote not using privacy. This is + * because we are using privacy locally and need to associate correct local + * IRK with the peer. + * + * If the peer does intend to send us identity information, we must wait + * for that information to enter it in the RL. In that case, we call + * `smp_id_add_replace` not here, but later. If neither we nor the peer are + * using privacy, there is no need for an entry in the RL. + */ + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && IS_ENABLED(CONFIG_BT_PRIVACY) && + conn->role == BT_HCI_ROLE_CENTRAL && + !(smp->remote_dist & BT_SMP_DIST_ID_KEY)) + { + uint8_t smp_err; + + smp_err = smp_id_add_replace(smp, conn->le.keys); + if (smp_err) + { + smp_pairing_complete(smp, smp_err); + } + } + + atomic_set_bit(smp->flags, SMP_FLAG_KEYS_DISTR); + + /* Peripheral distributes it's keys first */ + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && conn->role == BT_HCI_ROLE_CENTRAL && + smp->remote_dist) + { + return; + } + + if (IS_ENABLED(CONFIG_BT_TESTING)) + { + /* Avoid the HCI-USB race condition where HCI data and + * HCI events can be re-ordered, and pairing information appears to be + * sent unencrypted. + */ + + nxsched_msleep(100); + } + + if (bt_smp_distribute_keys(smp)) + { + return; + } + + /* if all keys were distributed, pairing is done */ + + if (!smp->local_dist && !smp->remote_dist) + { + smp_pairing_complete(smp, 0); + } +} + +#if defined(CONFIG_BT_SIGNING) || defined(CONFIG_BT_SMP_SELFTEST) +/* Sign message using msg as a buffer, len is a size of the message, + * msg buffer contains message itself, 32 bit count and signature, so total + * buffer size is len + 4 + 8 octets. API is Little Endian to make it + * suitable for Bluetooth. + */ + +static int smp_sign_buf(const uint8_t *key, uint8_t *msg, uint16_t len) +{ + uint8_t *m = msg; + uint32_t cnt = UNALIGNED_GET((uint32_t *)&msg[len]); + uint8_t *sig = msg + len; + uint8_t key_s[16]; + uint8_t tmp[16]; + int err; + + LOG_DBG("Signing msg %s len %u key %s", bt_hex(msg, len), len, bt_hex(key, + 16)); + + sys_mem_swap(m, len + sizeof(cnt)); + sys_memcpy_swap(key_s, key, 16); + + err = bt_crypto_aes_cmac(key_s, m, len + sizeof(cnt), tmp); + if (err) + { + LOG_ERR("Data signing failed"); + return err; + } + + sys_mem_swap(tmp, sizeof(tmp)); + memcpy(tmp + 4, &cnt, sizeof(cnt)); + + /* Swap original message back */ + + sys_mem_swap(m, len + sizeof(cnt)); + + memcpy(sig, tmp + 4, 12); + + LOG_DBG("sig %s", bt_hex(sig, 12)); + + return 0; +} +#endif + +#if defined(CONFIG_BT_SIGNING) +int bt_smp_sign_verify(struct bt_conn *conn, struct net_buf *buf) +{ + struct bt_keys *keys; + uint8_t sig[12]; + uint32_t cnt; + int err; + + /* Store signature incl. count */ + + memcpy(sig, net_buf_tail(buf) - sizeof(sig), sizeof(sig)); + + keys = bt_keys_find(BT_KEYS_REMOTE_CSRK, conn->id, &conn->le.dst); + if (!keys) + { + LOG_ERR("Unable to find Remote CSRK for %s", + bt_addr_le_str(&conn->le.dst)); + return -ENOENT; + } + + cnt = sys_get_le32(sig); + + /* The Signing Algorithm is performed with the received counter + * value, and replay protection is done by rejecting counter values that + * have already been used (Core Spec v6.2, Vol 3, Part C, Section 10.4.2, + * the last version to specify data signing before its removal in v6.3). + * Values greater than the expected next one are accepted, since the peer + * may consume counter values for signed PDUs that never end up being + * received, e.g. when it retries a failed send with a fresh signature or + * disconnects with signed PDUs still queued. + */ + + if (cnt < keys->remote_csrk.cnt) + { + LOG_WRN("Rejecting already used sign counter %u from %s", cnt, + bt_conn_dst_str(conn)); + return -EBADMSG; + } + + /* Fail closed when the received counter is the final possible + * value: accepting it would wrap the next expected value around to zero, + * re-opening the replay window for every previously used counter value. + * The counter space of the CSRK is then exhausted, and signing can only + * resume once a new pairing distributes a fresh CSRK. + */ + + if (cnt == UINT32_MAX) + { + LOG_WRN("Sign counter of remote CSRK for %s exhausted", + bt_conn_dst_str(conn)); + return -EOVERFLOW; + } + + LOG_DBG("Sign data len %zu key %s count %u", buf->len - sizeof(sig), + bt_hex(keys->remote_csrk.val, 16), cnt); + + err = smp_sign_buf(keys->remote_csrk.val, buf->data, + buf->len - sizeof(sig)); + if (err) + { + LOG_ERR("Unable to create signature for %s", + bt_addr_le_str(&conn->le.dst)); + return -EIO; + } + + if (memcmp(sig, net_buf_tail(buf) - sizeof(sig), sizeof(sig))) + { + LOG_ERR("Unable to verify signature for %s", + bt_addr_le_str(&conn->le.dst)); + return -EBADMSG; + } + + keys->remote_csrk.cnt = cnt + 1U; + + /* The sign counter is bound to the lifetime of the CSRK, not to a + * single power cycle. Without persisting each accepted value, a reboot + * would restore the counter stored at pairing time, making previously + * captured Signed Write Commands verify (replay) again. + * + * Fail closed if the new value cannot be persisted, so that no command + * gets executed unless it is also protected from replay. The incremented + * in-memory value is kept, since the peer has already consumed this + * counter value and expects the next one. + */ + + err = bt_keys_store(keys); + if (err != 0) + { + LOG_ERR("Unable to store updated sign counter for %s", + bt_conn_dst_str(conn)); + return err; + } + + return 0; +} + +int bt_smp_sign(struct bt_conn *conn, struct net_buf *buf) +{ + struct bt_keys *keys; + uint32_t cnt; + int err; + + keys = bt_keys_find(BT_KEYS_LOCAL_CSRK, conn->id, &conn->le.dst); + if (!keys) + { + LOG_ERR("Unable to find local CSRK for %s", + bt_addr_le_str(&conn->le.dst)); + return -ENOENT; + } + + /* Fail closed when the counter space is exhausted, instead of + * wrapping around and reusing counter values, which the peer would reject + * as replays. The final possible value is never used for signing, since a + * receiver cannot accept it without its own next expected value wrapping + * (see bt_smp_sign_verify()). A new pairing needs to distribute a fresh + * CSRK to resume signing. + */ + + if (keys->local_csrk.cnt == UINT32_MAX) + { + LOG_ERR("Sign counter of local CSRK for %s exhausted", + bt_conn_dst_str(conn)); + return -EOVERFLOW; + } + + /* Reserve space for data signature */ + + net_buf_add(buf, 12); + + /* Copy signing count */ + + cnt = sys_cpu_to_le32(keys->local_csrk.cnt); + memcpy(net_buf_tail(buf) - 12, &cnt, sizeof(cnt)); + + LOG_DBG("Sign data len %u key %s count %u", buf->len, + bt_hex(keys->local_csrk.val, 16), + keys->local_csrk.cnt); + + err = smp_sign_buf(keys->local_csrk.val, buf->data, buf->len - 12); + if (err) + { + LOG_ERR("Unable to create signature for %s", + bt_addr_le_str(&conn->le.dst)); + return -EIO; + } + + keys->local_csrk.cnt++; + + /* Persist the new counter value so that a reboot does not lead to + * reuse of an already used value, which the peer would reject. + * + * On failure the increment is reverted: the PDU is not sent in that case, + * so the counter value has not been consumed and must be used for the next + * attempt to stay in sync with the peer. + */ + + err = bt_keys_store(keys); + if (err != 0) + { + LOG_ERR("Unable to store updated sign counter for %s", + bt_conn_dst_str(conn)); + keys->local_csrk.cnt--; + return err; + } + + return 0; +} +#else +int bt_smp_sign_verify(struct bt_conn *conn, struct net_buf *buf) +{ + return -ENOTSUP; +} + +int bt_smp_sign(struct bt_conn *conn, struct net_buf *buf) +{ + return -ENOTSUP; +} +#endif /* CONFIG_BT_SIGNING */ + +static int smp_d1(const uint8_t *key, + uint16_t d, uint16_t r, uint8_t res[16]) +{ + int err; + + LOG_DBG("key %s d %u r %u", bt_hex(key, 16), d, r); + + sys_put_le16(d, &res[0]); + sys_put_le16(r, &res[2]); + memset(&res[4], 0, 16 - 4); + + err = bt_encrypt_le(key, res, res); + if (err) + { + return err; + } + + LOG_DBG("res %s", bt_hex(res, 16)); + return 0; +} + +int bt_smp_irk_get(uint8_t *ir, uint8_t *irk) +{ + uint8_t invalid_ir[16] = + { + 0 + }; + + if (!memcmp(ir, invalid_ir, 16)) + { + return -EINVAL; + } + + return smp_d1(ir, 1, 0, irk); +} + +#if defined(CONFIG_BT_SMP_SELFTEST) +/* Test vectors are taken from RFC 4493 + * https://tools.ietf.org/html/rfc4493 + * Same mentioned in the Bluetooth Spec. + */ + +static const uint8_t key[] = +{ + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, + 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c +}; + +static const uint8_t M[] = +{ + 0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, + 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93, 0x17, 0x2a, + 0xae, 0x2d, 0x8a, 0x57, 0x1e, 0x03, 0xac, 0x9c, + 0x9e, 0xb7, 0x6f, 0xac, 0x45, 0xaf, 0x8e, 0x51, + 0x30, 0xc8, 0x1c, 0x46, 0xa3, 0x5c, 0xe4, 0x11, + 0xe5, 0xfb, 0xc1, 0x19, 0x1a, 0x0a, 0x52, 0xef, + 0xf6, 0x9f, 0x24, 0x45, 0xdf, 0x4f, 0x9b, 0x17, + 0xad, 0x2b, 0x41, 0x7b, 0xe6, 0x6c, 0x37, 0x10 +}; + +static int aes_test( + const char *prefix, const uint8_t *in_key, const uint8_t *m, uint16_t len, + const uint8_t *mac) +{ + uint8_t out[16]; + + LOG_DBG("%s: AES CMAC of message with len %u", prefix, len); + + bt_crypto_aes_cmac(in_key, m, len, out); + if (!memcmp(out, mac, 16)) + { + LOG_DBG("%s: Success", prefix); + } + else + { + LOG_ERR("%s: Failed", prefix); + return -1; + } + + return 0; +} + +static int smp_aes_cmac_test(void) +{ + uint8_t mac1[] = + { + 0xbb, 0x1d, 0x69, 0x29, 0xe9, 0x59, 0x37, 0x28, + 0x7f, 0xa3, 0x7d, 0x12, 0x9b, 0x75, 0x67, 0x46 + }; + + uint8_t mac2[] = + { + 0x07, 0x0a, 0x16, 0xb4, 0x6b, 0x4d, 0x41, 0x44, + 0xf7, 0x9b, 0xdd, 0x9d, 0xd0, 0x4a, 0x28, 0x7c + }; + + uint8_t mac3[] = + { + 0xdf, 0xa6, 0x67, 0x47, 0xde, 0x9a, 0xe6, 0x30, + 0x30, 0xca, 0x32, 0x61, 0x14, 0x97, 0xc8, 0x27 + }; + + uint8_t mac4[] = + { + 0x51, 0xf0, 0xbe, 0xbf, 0x7e, 0x3b, 0x9d, 0x92, + 0xfc, 0x49, 0x74, 0x17, 0x79, 0x36, 0x3c, 0xfe + }; + + int err; + + err = aes_test("Test aes-cmac0", key, M, 0, mac1); + if (err) + { + return err; + } + + err = aes_test("Test aes-cmac16", key, M, 16, mac2); + if (err) + { + return err; + } + + err = aes_test("Test aes-cmac40", key, M, 40, mac3); + if (err) + { + return err; + } + + err = aes_test("Test aes-cmac64", key, M, 64, mac4); + if (err) + { + return err; + } + + return 0; +} + +static int sign_test( + const char *prefix, const uint8_t *sign_key, const uint8_t *m, + uint16_t len, const uint8_t *sig) +{ + uint8_t msg[len + sizeof(uint32_t) + 8]; + uint8_t orig[len + sizeof(uint32_t) + 8]; + uint8_t *out = msg + len; + int err; + + LOG_DBG("%s: Sign message with len %u", prefix, len); + + (void)memset(msg, 0, sizeof(msg)); + memcpy(msg, m, len); + (void)memset(msg + len, 0, sizeof(uint32_t)); + + memcpy(orig, msg, sizeof(msg)); + + err = smp_sign_buf(sign_key, msg, len); + if (err) + { + return err; + } + + /* Check original message */ + + if (!memcmp(msg, orig, len + sizeof(uint32_t))) + { + LOG_DBG("%s: Original message intact", prefix); + } + else + { + LOG_ERR("%s: Original message modified", prefix); + LOG_DBG("%s: orig %s", prefix, bt_hex(orig, sizeof(orig))); + LOG_DBG("%s: msg %s", prefix, bt_hex(msg, sizeof(msg))); + return -1; + } + + if (!memcmp(out, sig, 12)) + { + LOG_DBG("%s: Success", prefix); + } + else + { + LOG_ERR("%s: Failed", prefix); + return -1; + } + + return 0; +} + +static int smp_sign_test(void) +{ + const uint8_t sig1[] = + { + 0x00, 0x00, 0x00, 0x00, 0xb3, 0xa8, 0x59, 0x41, + 0x27, 0xeb, 0xc2, 0xc0 + }; + + const uint8_t sig2[] = + { + 0x00, 0x00, 0x00, 0x00, 0x27, 0x39, 0x74, 0xf4, + 0x39, 0x2a, 0x23, 0x2a + }; + + const uint8_t sig3[] = + { + 0x00, 0x00, 0x00, 0x00, 0xb7, 0xca, 0x94, 0xab, + 0x87, 0xc7, 0x82, 0x18 + }; + + const uint8_t sig4[] = + { + 0x00, 0x00, 0x00, 0x00, 0x44, 0xe1, 0xe6, 0xce, + 0x1d, 0xf5, 0x13, 0x68 + }; + + uint8_t key_s[16]; + int err; + + /* Use the same key as aes-cmac but swap bytes */ + + sys_memcpy_swap(key_s, key, 16); + + err = sign_test("Test sign0", key_s, M, 0, sig1); + if (err) + { + return err; + } + + err = sign_test("Test sign16", key_s, M, 16, sig2); + if (err) + { + return err; + } + + err = sign_test("Test sign40", key_s, M, 40, sig3); + if (err) + { + return err; + } + + err = sign_test("Test sign64", key_s, M, 64, sig4); + if (err) + { + return err; + } + + return 0; +} + +static int smp_f4_test(void) +{ + uint8_t u[32] = + { + 0xe6, 0x9d, 0x35, 0x0e, 0x48, 0x01, 0x03, 0xcc, + 0xdb, 0xfd, 0xf4, 0xac, 0x11, 0x91, 0xf4, 0xef, + 0xb9, 0xa5, 0xf9, 0xe9, 0xa7, 0x83, 0x2c, 0x5e, + 0x2c, 0xbe, 0x97, 0xf2, 0xd2, 0x03, 0xb0, 0x20 + }; + + uint8_t v[32] = + { + 0xfd, 0xc5, 0x7f, 0xf4, 0x49, 0xdd, 0x4f, 0x6b, + 0xfb, 0x7c, 0x9d, 0xf1, 0xc2, 0x9a, 0xcb, 0x59, + 0x2a, 0xe7, 0xd4, 0xee, 0xfb, 0xfc, 0x0a, 0x90, + 0x9a, 0xbb, 0xf6, 0x32, 0x3d, 0x8b, 0x18, 0x55 + }; + + uint8_t x[16] = + { + 0xab, 0xae, 0x2b, 0x71, 0xec, 0xb2, 0xff, 0xff, + 0x3e, 0x73, 0x77, 0xd1, 0x54, 0x84, 0xcb, 0xd5 + }; + + uint8_t z = 0x00; + uint8_t exp[16] = + { + 0x2d, 0x87, 0x74, 0xa9, 0xbe, 0xa1, 0xed, 0xf1, + 0x1c, 0xbd, 0xa9, 0x07, 0xf1, 0x16, 0xc9, 0xf2 + }; + + uint8_t res[16]; + int err; + + err = bt_crypto_f4(u, v, x, z, res); + if (err) + { + return err; + } + + if (memcmp(res, exp, 16)) + { + return -EINVAL; + } + + return 0; +} + +static int smp_f5_test(void) +{ + uint8_t w[32] = + { + 0x98, 0xa6, 0xbf, 0x73, 0xf3, 0x34, 0x8d, 0x86, + 0xf1, 0x66, 0xf8, 0xb4, 0x13, 0x6b, 0x79, 0x99, + 0x9b, 0x7d, 0x39, 0x0a, 0xa6, 0x10, 0x10, 0x34, + 0x05, 0xad, 0xc8, 0x57, 0xa3, 0x34, 0x02, 0xec + }; + + uint8_t n1[16] = + { + 0xab, 0xae, 0x2b, 0x71, 0xec, 0xb2, 0xff, 0xff, + 0x3e, 0x73, 0x77, 0xd1, 0x54, 0x84, 0xcb, 0xd5 + }; + + uint8_t n2[16] = + { + 0xcf, 0xc4, 0x3d, 0xff, 0xf7, 0x83, 0x65, 0x21, + 0x6e, 0x5f, 0xa7, 0x25, 0xcc, 0xe7, 0xe8, 0xa6 + }; + + bt_addr_le_t a1 = + { + .type = 0x00, .a.val = + { + 0xce, 0xbf, 0x37, 0x37, 0x12, 0x56 + } + }; + + bt_addr_le_t a2 = + { + .type = 0x00, .a.val = + { + 0xc1, 0xcf, 0x2d, 0x70, 0x13, 0xa7 + } + }; + + uint8_t exp_ltk[16] = + { + 0x38, 0x0a, 0x75, 0x94, 0xb5, 0x22, 0x05, 0x98, + 0x23, 0xcd, 0xd7, 0x69, 0x11, 0x79, 0x86, 0x69 + }; + + uint8_t exp_mackey[16] = + { + 0x20, 0x6e, 0x63, 0xce, 0x20, 0x6a, 0x3f, 0xfd, + 0x02, 0x4a, 0x08, 0xa1, 0x76, 0xf1, 0x65, 0x29 + }; + + uint8_t mackey[16]; + uint8_t ltk[16]; + int err; + + err = bt_crypto_f5(w, n1, n2, &a1, &a2, mackey, ltk); + if (err) + { + return err; + } + + if (memcmp(mackey, exp_mackey, 16) || memcmp(ltk, exp_ltk, 16)) + { + return -EINVAL; + } + + return 0; +} + +static int smp_f6_test(void) +{ + uint8_t w[16] = + { + 0x20, 0x6e, 0x63, 0xce, 0x20, 0x6a, 0x3f, 0xfd, + 0x02, 0x4a, 0x08, 0xa1, 0x76, 0xf1, 0x65, 0x29 + }; + + uint8_t n1[16] = + { + 0xab, 0xae, 0x2b, 0x71, 0xec, 0xb2, 0xff, 0xff, + 0x3e, 0x73, 0x77, 0xd1, 0x54, 0x84, 0xcb, 0xd5 + }; + + uint8_t n2[16] = + { + 0xcf, 0xc4, 0x3d, 0xff, 0xf7, 0x83, 0x65, 0x21, + 0x6e, 0x5f, 0xa7, 0x25, 0xcc, 0xe7, 0xe8, 0xa6 + }; + + uint8_t r[16] = + { + 0xc8, 0x0f, 0x2d, 0x0c, 0xd2, 0x42, 0xda, 0x08, + 0x54, 0xbb, 0x53, 0xb4, 0x3b, 0x34, 0xa3, 0x12 + }; + + uint8_t io_cap[3] = + { + 0x02, 0x01, 0x01 + }; + + bt_addr_le_t a1 = + { + .type = 0x00, .a.val = + { + 0xce, 0xbf, 0x37, 0x37, 0x12, 0x56 + } + }; + + bt_addr_le_t a2 = + { + .type = 0x00, .a.val = + { + 0xc1, 0xcf, 0x2d, 0x70, 0x13, 0xa7 + } + }; + + uint8_t exp[16] = + { + 0x61, 0x8f, 0x95, 0xda, 0x09, 0x0b, 0x6c, 0xd2, + 0xc5, 0xe8, 0xd0, 0x9c, 0x98, 0x73, 0xc4, 0xe3 + }; + + uint8_t res[16]; + int err; + + err = bt_crypto_f6(w, n1, n2, r, io_cap, &a1, &a2, res); + if (err) + { + return err; + } + + if (memcmp(res, exp, 16)) + { + return -EINVAL; + } + + return 0; +} + +static int smp_g2_test(void) +{ + uint8_t u[32] = + { + 0xe6, 0x9d, 0x35, 0x0e, 0x48, 0x01, 0x03, 0xcc, + 0xdb, 0xfd, 0xf4, 0xac, 0x11, 0x91, 0xf4, 0xef, + 0xb9, 0xa5, 0xf9, 0xe9, 0xa7, 0x83, 0x2c, 0x5e, + 0x2c, 0xbe, 0x97, 0xf2, 0xd2, 0x03, 0xb0, 0x20 + }; + + uint8_t v[32] = + { + 0xfd, 0xc5, 0x7f, 0xf4, 0x49, 0xdd, 0x4f, 0x6b, + 0xfb, 0x7c, 0x9d, 0xf1, 0xc2, 0x9a, 0xcb, 0x59, + 0x2a, 0xe7, 0xd4, 0xee, 0xfb, 0xfc, 0x0a, 0x90, + 0x9a, 0xbb, 0xf6, 0x32, 0x3d, 0x8b, 0x18, 0x55 + }; + + uint8_t x[16] = + { + 0xab, 0xae, 0x2b, 0x71, 0xec, 0xb2, 0xff, 0xff, + 0x3e, 0x73, 0x77, 0xd1, 0x54, 0x84, 0xcb, 0xd5 + }; + + uint8_t y[16] = + { + 0xcf, 0xc4, 0x3d, 0xff, 0xf7, 0x83, 0x65, 0x21, + 0x6e, 0x5f, 0xa7, 0x25, 0xcc, 0xe7, 0xe8, 0xa6 + }; + + uint32_t exp_val = 0x2f9ed5ba % 1000000; + uint32_t val; + int err; + + err = bt_crypto_g2(u, v, x, y, &val); + if (err) + { + return err; + } + + if (val != exp_val) + { + return -EINVAL; + } + + return 0; +} + +#if defined(CONFIG_BT_CLASSIC) +static int smp_h6_test(void) +{ + uint8_t w[16] = + { + 0x9b, 0x7d, 0x39, 0x0a, 0xa6, 0x10, 0x10, 0x34, + 0x05, 0xad, 0xc8, 0x57, 0xa3, 0x34, 0x02, 0xec + }; + + uint8_t key_id[4] = + { + 0x72, 0x62, 0x65, 0x6c + }; + + uint8_t exp_res[16] = + { + 0x99, 0x63, 0xb1, 0x80, 0xe2, 0xa9, 0xd3, 0xe8, + 0x1c, 0xc9, 0x6d, 0xe7, 0x02, 0xe1, 0x9a, 0x2d + }; + + uint8_t res[16]; + int err; + + err = bt_crypto_h6(w, key_id, res); + if (err) + { + return err; + } + + if (memcmp(res, exp_res, 16)) + { + return -EINVAL; + } + + return 0; +} + +static int smp_h7_test(void) +{ + uint8_t salt[16] = + { + 0x31, 0x70, 0x6d, 0x74, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + uint8_t w[16] = + { + 0x9b, 0x7d, 0x39, 0x0a, 0xa6, 0x10, 0x10, 0x34, + 0x05, 0xad, 0xc8, 0x57, 0xa3, 0x34, 0x02, 0xec + }; + + uint8_t exp_res[16] = + { + 0x11, 0x70, 0xa5, 0x75, 0x2a, 0x8c, 0x99, 0xd2, + 0xec, 0xc0, 0xa3, 0xc6, 0x97, 0x35, 0x17, 0xfb + }; + + uint8_t res[16]; + int err; + + err = bt_crypto_h7(salt, w, res); + if (err) + { + return err; + } + + if (memcmp(res, exp_res, 16)) + { + return -EINVAL; + } + + return 0; +} +#endif /* CONFIG_BT_CLASSIC */ + +static int smp_h8_test(void) +{ + uint8_t k[16] = + { + 0xec, 0x02, 0x34, 0xa3, 0x57, 0xc8, 0xad, 0x05, + 0x34, 0x10, 0x10, 0xa6, 0x0a, 0x39, 0x7d, 0x9b + }; + + uint8_t s[16] = + { + 0x15, 0x36, 0xd1, 0x8d, 0xe3, 0xd2, 0x0d, 0xf9, + 0x9b, 0x70, 0x44, 0xc1, 0x2f, 0x9e, 0xd5, 0xba + }; + + uint8_t key_id[4] = + { + 0xcc, 0x03, 0x01, 0x48 + }; + + uint8_t exp_res[16] = + { + 0xe5, 0xe5, 0xbe, 0xba, 0xae, 0x72, 0x28, 0xe7, + 0x22, 0xa3, 0x89, 0x04, 0xed, 0x35, 0x0f, 0x6d + }; + + uint8_t res[16]; + int err; + + err = bt_crypto_h8(k, s, key_id, res); + if (err) + { + return err; + } + + if (memcmp(res, exp_res, 16)) + { + return -EINVAL; + } + + return 0; +} + +static int smp_self_test(void) +{ + int err; + + err = smp_aes_cmac_test(); + if (err) + { + LOG_ERR("SMP AES-CMAC self tests failed"); + return err; + } + + err = smp_sign_test(); + if (err) + { + LOG_ERR("SMP signing self tests failed"); + return err; + } + + err = smp_f4_test(); + if (err) + { + LOG_ERR("SMP f4 self test failed"); + return err; + } + + err = smp_f5_test(); + if (err) + { + LOG_ERR("SMP f5 self test failed"); + return err; + } + + err = smp_f6_test(); + if (err) + { + LOG_ERR("SMP f6 self test failed"); + return err; + } + + err = smp_g2_test(); + if (err) + { + LOG_ERR("SMP g2 self test failed"); + return err; + } + +#if defined(CONFIG_BT_CLASSIC) + err = smp_h6_test(); + if (err) + { + LOG_ERR("SMP h6 self test failed"); + return err; + } + + err = smp_h7_test(); + if (err) + { + LOG_ERR("SMP h7 self test failed"); + return err; + } +#endif /* CONFIG_BT_CLASSIC */ + + err = smp_h8_test(); + if (err) + { + LOG_ERR("SMP h8 self test failed"); + return err; + } + + return 0; +} +#else +static inline int smp_self_test(void) +{ + return 0; +} +#endif + +#if defined(CONFIG_BT_BONDABLE_PER_CONNECTION) +int bt_conn_set_bondable(struct bt_conn *conn, bool enable) +{ + struct bt_smp *smp; + + if (!bt_conn_is_br(conn) && !bt_conn_is_le(conn)) + { + LOG_DBG("Invalid connection type: %u for %p", conn->type, conn); + return -EINVAL; + } + + if (bt_conn_is_br(conn)) + { + if (enable && atomic_test_and_set_bit(conn->flags, + BT_CONN_BR_BONDABLE)) + { + return -EALREADY; + } + + if (!enable && !atomic_test_and_clear_bit(conn->flags, + BT_CONN_BR_BONDABLE)) + { + return -EALREADY; + } + + return 0; + } + + smp = smp_chan_get(conn); + if (!smp) + { + return -EINVAL; + } + + if (atomic_cmpxchg_value(&smp->bondable, BT_SMP_BONDABLE_UNINITIALIZED, + (int32_t)enable)) + { + return 0; + } + else + { + return -EALREADY; + } +} +#endif + +int bt_smp_auth_cb_overlay(struct bt_conn *conn, + const struct bt_conn_auth_cb *cb) +{ + struct bt_smp *smp; + + smp = smp_chan_get(conn); + if (!smp) + { + return -EINVAL; + } + + if (atomic_ptr_cmpxchg(&smp->auth_cb, BT_SMP_AUTH_CB_UNINITIALIZED, + (void *)cb)) + { + return 0; + } + else + { + return -EALREADY; + } +} + +#if defined(CONFIG_BT_PASSKEY_KEYPRESS) +static int smp_send_keypress_notif(struct bt_smp *smp, uint8_t type) +{ + struct bt_smp_keypress_notif *req; + struct net_buf *buf; + + buf = smp_create_pdu(smp, BT_SMP_KEYPRESS_NOTIFICATION, sizeof(*req)); + if (!buf) + { + return -ENOMEM; + } + + req = net_buf_add(buf, sizeof(*req)); + req->type = type; + + smp_send(smp, buf, NULL, NULL); + + return 0; +} +#endif + +#if defined(CONFIG_BT_PASSKEY_KEYPRESS) +int bt_smp_auth_keypress_notify(struct bt_conn *conn, + enum bt_conn_auth_keypress type) +{ + struct bt_smp *smp; + + smp = smp_chan_get(conn); + if (!smp) + { + return -EINVAL; + } + + if (!IN_RANGE(type, BT_CONN_AUTH_KEYPRESS_ENTRY_STARTED, + BT_CONN_AUTH_KEYPRESS_ENTRY_COMPLETED)) + { + LOG_ERR("Refusing to send unknown event type %u", type); + return -EINVAL; + } + + if (smp->method != PASSKEY_INPUT || !atomic_test_bit(smp->flags, + SMP_FLAG_USER)) + { + LOG_ERR("Refusing to send keypress: Not waiting for passkey input."); + return -EINVAL; + } + + return smp_send_keypress_notif(smp, type); +} +#endif + +int bt_smp_auth_passkey_entry(struct bt_conn *conn, unsigned int passkey) +{ + struct bt_smp *smp; + uint8_t err; + + smp = smp_chan_get(conn); + if (!smp) + { + return -EINVAL; + } + + if (!atomic_test_and_clear_bit(smp->flags, SMP_FLAG_USER)) + { + return -EINVAL; + } + +#if !defined(CONFIG_BT_SMP_SC_PAIR_ONLY) + if (!atomic_test_bit(smp->flags, SMP_FLAG_SC)) + { + legacy_passkey_entry(smp, passkey); + return 0; + } +#endif /* !CONFIG_BT_SMP_SC_PAIR_ONLY */ + + smp->passkey = sys_cpu_to_le32(passkey); + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && + smp->chan.chan.conn->role == BT_HCI_ROLE_CENTRAL) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_CONFIRM); + err = smp_send_pairing_confirm(smp); + if (err) + { + smp_error(smp, BT_SMP_ERR_PASSKEY_ENTRY_FAILED); + return 0; + } + + return 0; + } + + if (IS_ENABLED(CONFIG_BT_PERIPHERAL) && + atomic_test_bit(smp->flags, SMP_FLAG_CFM_DELAYED)) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_RANDOM); + err = smp_send_pairing_confirm(smp); + if (err) + { + smp_error(smp, BT_SMP_ERR_PASSKEY_ENTRY_FAILED); + return 0; + } + } + + return 0; +} + +int bt_smp_auth_passkey_confirm(struct bt_conn *conn) +{ + struct bt_smp *smp; + + smp = smp_chan_get(conn); + if (!smp) + { + return -EINVAL; + } + + if (!atomic_test_and_clear_bit(smp->flags, SMP_FLAG_USER)) + { + return -EINVAL; + } + + /* wait for DHKey being generated */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_DHKEY_PENDING)) + { + atomic_set_bit(smp->flags, SMP_FLAG_DHKEY_SEND); + return 0; + } + + /* wait for remote DHKey Check */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_DHCHECK_WAIT)) + { + atomic_set_bit(smp->flags, SMP_FLAG_DHKEY_SEND); + return 0; + } + + if (atomic_test_bit(smp->flags, SMP_FLAG_DHKEY_SEND)) + { + uint8_t err; + +#if defined(CONFIG_BT_CENTRAL) + if (smp->chan.chan.conn->role == BT_HCI_ROLE_CENTRAL) + { + err = compute_and_send_central_dhcheck(smp); + if (err) + { + smp_error(smp, err); + } + + return 0; + } +#endif /* CONFIG_BT_CENTRAL */ + +#if defined(CONFIG_BT_PERIPHERAL) + err = compute_and_check_and_send_periph_dhcheck(smp); + if (err) + { + smp_error(smp, err); + } +#endif /* CONFIG_BT_PERIPHERAL */ + } + + return 0; +} + +#if !defined(CONFIG_BT_SMP_SC_PAIR_ONLY) +int bt_smp_le_oob_set_tk(struct bt_conn *conn, const uint8_t *tk) +{ + struct bt_smp *smp; + + smp = smp_chan_get(conn); + if (!smp || !tk) + { + return -EINVAL; + } + + LOG_DBG("%s", bt_hex(tk, 16)); + + if (!atomic_test_and_clear_bit(smp->flags, SMP_FLAG_USER)) + { + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_LOG_SNIFFER_INFO)) + { + uint8_t oob[16]; + + sys_memcpy_swap(oob, tk, 16); + LOG_INF("Legacy OOB data 0x%s", bt_hex(oob, 16)); + } + + memcpy(smp->tk, tk, 16 * sizeof(uint8_t)); + + legacy_user_tk_entry(smp); + + return 0; +} +#endif /* !defined(CONFIG_BT_SMP_SC_PAIR_ONLY) */ + +int bt_smp_le_oob_generate_sc_data(struct bt_le_oob_sc_data *le_sc_oob) +{ + int err; + + if (!le_sc_supported()) + { + return -ENOTSUP; + } + + if (!sc_public_key) + { + /* Public key request has not been finished yet, or finished, but + * generation failed. Retrying. + */ + + err = nxrmutex_lock(&pub_key_gen.lock); + DEBUGASSERT(err == 0); + + err = bt_pub_key_gen(&pub_key_gen.cb); + if (err && err != -EALREADY) + { + LOG_WRN("Public key re-generation request failed (%d)", err); + + __maybe_unused int mutex_err; + + mutex_err = nxrmutex_unlock(&pub_key_gen.lock); + DEBUGASSERT(mutex_err == 0); + + return err; + } + + /* 30 seconds is an arbitrary number. Increase if fails earlier on + * certain platforms. + */ + + err = k_condvar_wait(&pub_key_gen.condvar, &pub_key_gen.lock, + SEC2TICK(30)); + if (err != 0) + { + LOG_WRN("Public key generation timeout"); + + __maybe_unused int mutex_err; + + mutex_err = nxrmutex_unlock(&pub_key_gen.lock); + DEBUGASSERT(mutex_err == 0); + + return err; + } + + err = nxrmutex_unlock(&pub_key_gen.lock); + DEBUGASSERT(err == 0); + + /* Public key has been re-requested but generation failed. */ + + if (!sc_public_key) + { + return -EAGAIN; + } + } + + if (IS_ENABLED(CONFIG_BT_OOB_DATA_FIXED)) + { + uint8_t rand_num[] = + { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 + }; + + memcpy(le_sc_oob->r, rand_num, sizeof(le_sc_oob->r)); + } + else + { + err = bt_rand(le_sc_oob->r, 16); + if (err) + { + return err; + } + } + + err = bt_crypto_f4(sc_public_key, sc_public_key, le_sc_oob->r, 0, + le_sc_oob->c); + if (err) + { + return err; + } + + return 0; +} + +#if !defined(CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY) +static bool le_sc_oob_data_check(struct bt_smp *smp, bool oobd_local_present, + bool oobd_remote_present) +{ + bool req_oob_present = le_sc_oob_data_req_check(smp); + bool rsp_oob_present = le_sc_oob_data_rsp_check(smp); + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && + smp->chan.chan.conn->role == BT_HCI_ROLE_CENTRAL) + { + if ((req_oob_present != oobd_remote_present) && + (rsp_oob_present != oobd_local_present)) + { + return false; + } + } + else if (IS_ENABLED(CONFIG_BT_PERIPHERAL)) + { + if ((req_oob_present != oobd_local_present) && + (rsp_oob_present != oobd_remote_present)) + { + return false; + } + } + + return true; +} + +static int le_sc_oob_pairing_continue(struct bt_smp *smp) +{ + if (smp->oobd_remote) + { + int err; + uint8_t c[16]; + + err = bt_crypto_f4(smp->pkey, smp->pkey, smp->oobd_remote->r, 0, c); + if (err) + { + return err; + } + + bool match = (memcmp(c, smp->oobd_remote->c, sizeof(c)) == 0); + + if (!match) + { + smp_error(smp, BT_SMP_ERR_CONFIRM_FAILED); + return 0; + } + } + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && + smp->chan.chan.conn->role == BT_HCI_ROLE_CENTRAL) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_RANDOM); + } + else if (IS_ENABLED(CONFIG_BT_PERIPHERAL)) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_DHKEY_CHECK); + atomic_set_bit(smp->flags, SMP_FLAG_DHCHECK_WAIT); + } + + return smp_send_pairing_random(smp); +} + +int bt_smp_le_oob_set_sc_data( + struct bt_conn *conn, const struct bt_le_oob_sc_data *oobd_local, + const struct bt_le_oob_sc_data *oobd_remote) +{ + struct bt_smp *smp; + + smp = smp_chan_get(conn); + if (!smp) + { + return -EINVAL; + } + + if (!le_sc_oob_data_check(smp, + (oobd_local != NULL), + (oobd_remote != NULL))) + { + return -EINVAL; + } + + if (!atomic_test_and_clear_bit(smp->flags, SMP_FLAG_OOB_PENDING)) + { + return -EINVAL; + } + + smp->oobd_local = oobd_local; + smp->oobd_remote = oobd_remote; + + return le_sc_oob_pairing_continue(smp); +} + +int bt_smp_le_oob_get_sc_data( + struct bt_conn *conn, const struct bt_le_oob_sc_data **oobd_local, + const struct bt_le_oob_sc_data **oobd_remote) +{ + struct bt_smp *smp; + + smp = smp_chan_get(conn); + if (!smp) + { + return -EINVAL; + } + + if (!smp->oobd_local && !smp->oobd_remote) + { + return -ESRCH; + } + + if (oobd_local) + { + *oobd_local = smp->oobd_local; + } + + if (oobd_remote) + { + *oobd_remote = smp->oobd_remote; + } + + return 0; +} +#endif /* !CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY */ + +int bt_smp_auth_cancel(struct bt_conn *conn) +{ + struct bt_smp *smp; + + smp = smp_chan_get(conn); + if (!smp) + { + return -EINVAL; + } + + if (!atomic_test_and_clear_bit(smp->flags, SMP_FLAG_USER)) + { + return -EINVAL; + } + + LOG_DBG(""); + + switch (smp->method) + { + case PASSKEY_INPUT: + case PASSKEY_DISPLAY: + return smp_error(smp, BT_SMP_ERR_PASSKEY_ENTRY_FAILED); + case PASSKEY_CONFIRM: + return smp_error(smp, BT_SMP_ERR_CONFIRM_FAILED); + case LE_SC_OOB: + case LEGACY_OOB: + return smp_error(smp, BT_SMP_ERR_OOB_NOT_AVAIL); + case JUST_WORKS: + return smp_error(smp, BT_SMP_ERR_UNSPECIFIED); + default: + LOG_ERR("Unknown pairing method (%u)", smp->method); + return 0; + } +} + +#if !defined(CONFIG_BT_SMP_SC_PAIR_ONLY) +int bt_smp_auth_pairing_confirm(struct bt_conn *conn) +{ + struct bt_smp *smp; + + smp = smp_chan_get(conn); + if (!smp) + { + return -EINVAL; + } + + if (!atomic_test_and_clear_bit(smp->flags, SMP_FLAG_USER)) + { + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_CENTRAL) && conn->role == BT_CONN_ROLE_CENTRAL) + { + if (!atomic_test_bit(smp->flags, SMP_FLAG_SC)) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_CONFIRM); + return legacy_send_pairing_confirm(smp); + } + + if (!sc_public_key) + { + atomic_set_bit(smp->flags, SMP_FLAG_PKEY_SEND); + return 0; + } + + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PUBLIC_KEY); + return sc_send_public_key(smp); + } + +#if defined(CONFIG_BT_PERIPHERAL) + if (!atomic_test_bit(smp->flags, SMP_FLAG_SC)) + { + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PAIRING_CONFIRM); + return send_pairing_rsp(smp); + } + + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_PUBLIC_KEY); + if (send_pairing_rsp(smp)) + { + return -EIO; + } +#endif /* CONFIG_BT_PERIPHERAL */ + + return 0; +} +#else +int bt_smp_auth_pairing_confirm(struct bt_conn *conn) +{ + /* confirm_pairing will never be called in LE SC only mode */ + + return -EINVAL; +} +#endif /* !CONFIG_BT_SMP_SC_PAIR_ONLY */ + +int bt_smp_start_security(struct bt_conn *conn) +{ + switch (conn->role) + { +#if defined(CONFIG_BT_CENTRAL) + case BT_HCI_ROLE_CENTRAL: + { + int err; + struct bt_smp *smp; + + smp = smp_chan_get(conn); + if (!smp) + { + return -ENOTCONN; + } + + /* pairing is in progress */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_PAIRING)) + { + return -EBUSY; + } + + /* Encryption is in progress */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_ENC_PENDING)) + { + return -EBUSY; + } + + if (!smp_keys_check(conn)) + { + return smp_send_pairing_req(conn); + } + + /* LE SC LTK and legacy central LTK are stored in same place */ + + err = bt_conn_le_start_encryption(conn, conn->le.keys->ltk.rand, + conn->le.keys->ltk.ediv, + conn->le.keys->ltk.val, + conn->le.keys->enc_size); + if (err) + { + return err; + } + + atomic_set_bit(smp->allowed_cmds, BT_SMP_CMD_SECURITY_REQUEST); + atomic_set_bit(smp->flags, SMP_FLAG_ENC_PENDING); + return 0; + } +#endif /* CONFIG_BT_CENTRAL && CONFIG_BT_SMP */ +#if defined(CONFIG_BT_PERIPHERAL) + + case BT_HCI_ROLE_PERIPHERAL: + return smp_send_security_req(conn); +#endif /* CONFIG_BT_PERIPHERAL && CONFIG_BT_SMP */ + default: + return -EINVAL; + } +} + +void bt_smp_update_keys(struct bt_conn *conn) +{ + struct bt_smp *smp; + + smp = smp_chan_get(conn); + if (!smp) + { + return; + } + + if (!atomic_test_bit(smp->flags, SMP_FLAG_PAIRING)) + { + return; + } + + /* If link was successfully encrypted cleanup old keys as from now on + * only keys distributed in this pairing or LTK from LE SC will be used. + */ + + if (conn->le.keys) + { + bt_keys_clear(conn->le.keys); + } + + conn->le.keys = bt_keys_get_addr(conn->id, &conn->le.dst); + if (!conn->le.keys) + { + LOG_ERR("Unable to get keys for %s", bt_addr_le_str(&conn->le.dst)); + smp_error(smp, BT_SMP_ERR_UNSPECIFIED); + return; + } + + /* mark keys as debug */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_SC_DEBUG_KEY)) + { + conn->le.keys->flags |= BT_KEYS_DEBUG; + } + + /* store key type deducted from pairing method used + * it is important to store it since type is used to determine security + * level upon encryption + */ + + switch (smp->method) + { + case LE_SC_OOB: + case LEGACY_OOB: + conn->le.keys->flags |= BT_KEYS_OOB; + conn->le.keys->flags |= BT_KEYS_AUTHENTICATED; + break; + case PASSKEY_DISPLAY: + case PASSKEY_INPUT: + case PASSKEY_CONFIRM: + if (atomic_test_bit(smp->flags, SMP_FLAG_SC)) + { + conn->le.keys->flags |= BT_KEYS_AUTHENTICATED; + break; + } + + /* fallthrough */ + + case JUST_WORKS: + default: + + /* unauthenticated key, clear it */ + + conn->le.keys->flags &= ~BT_KEYS_OOB; + conn->le.keys->flags &= ~BT_KEYS_AUTHENTICATED; + break; + } + + conn->le.keys->enc_size = get_encryption_key_size(smp); + + /* Store LTK if LE SC is used, this is safe since LE SC is mutually + * exclusive with legacy pairing. Other keys are added on keys + * distribution. + */ + + if (atomic_test_bit(smp->flags, SMP_FLAG_SC)) + { + conn->le.keys->flags |= BT_KEYS_SC; + + if (atomic_test_bit(smp->flags, SMP_FLAG_BOND)) + { + bt_keys_add_type(conn->le.keys, BT_KEYS_LTK_P256); + memcpy(conn->le.keys->ltk.val, smp->tk, + sizeof(conn->le.keys->ltk.val)); + (void)memset(conn->le.keys->ltk.rand, 0, + sizeof(conn->le.keys->ltk.rand)); + (void)memset(conn->le.keys->ltk.ediv, 0, + sizeof(conn->le.keys->ltk.ediv)); + } + else if (IS_ENABLED(CONFIG_BT_LOG_SNIFFER_INFO)) + { + uint8_t ltk[16]; + + sys_memcpy_swap(ltk, smp->tk, conn->le.keys->enc_size); + LOG_INF("SC LTK: 0x%s (No bonding)", bt_hex(ltk, + conn->le.keys->enc_size)); + } + } + else + { + conn->le.keys->flags &= ~BT_KEYS_SC; + } +} + +static int bt_smp_accept(struct bt_conn *conn, struct bt_l2cap_chan **chan) +{ + int i; + static const struct bt_l2cap_chan_ops ops = + { + .connected = bt_smp_connected, + .disconnected = bt_smp_disconnected, + .encrypt_change = bt_smp_encrypt_change, + .recv = bt_smp_recv, + }; + + LOG_DBG("conn %p handle %u", conn, conn->handle); + + for (i = 0; i < ARRAY_SIZE(bt_smp_pool); i++) + { + struct bt_smp *smp = &bt_smp_pool[i]; + + if (smp->chan.chan.conn) + { + continue; + } + + smp->chan.chan.ops = &ops; + + *chan = &smp->chan.chan; + + return 0; + } + + LOG_ERR("No available SMP context for conn %p", conn); + + return -ENOMEM; +} + +BT_L2CAP_FIXED_CHANNEL_DEFINE(smp_fixed_chan) = +{ + .cid = BT_L2CAP_CID_SMP, + .accept = bt_smp_accept, +}; +#if defined(CONFIG_BT_CLASSIC) + +BT_L2CAP_BR_CHANNEL_DEFINE(smp_br_fixed_chan, + BT_L2CAP_CID_BR_SMP, + bt_smp_br_accept); +#endif /* CONFIG_BT_CLASSIC */ + +int bt_smp_init(void) +{ + sc_supported = le_sc_supported(); + if (IS_ENABLED(CONFIG_BT_SMP_SC_PAIR_ONLY) && !sc_supported) + { + LOG_ERR("SC Pair Only Mode selected but LE SC not supported"); + return -ENOENT; + } + + if (IS_ENABLED(CONFIG_BT_SMP_USB_HCI_CTLR_WORKAROUND)) + { + LOG_WRN("BT_SMP_USB_HCI_CTLR_WORKAROUND is enabled, which " + "exposes a security vulnerability!"); + } + + LOG_DBG("LE SC %s", sc_supported ? "enabled" : "disabled"); + + if (!IS_ENABLED(CONFIG_BT_SMP_OOB_LEGACY_PAIR_ONLY)) + { + bt_pub_key_gen(&pub_key_gen.cb); + } + + return smp_self_test(); +} diff --git a/libs/zblue/host/smp.h b/libs/zblue/host/smp.h new file mode 100644 index 0000000000000..01b824c5d2219 --- /dev/null +++ b/libs/zblue/host/smp.h @@ -0,0 +1,241 @@ +/**************************************************************************** + * libs/zblue/host/smp.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file smp.h Security Manager Protocol implementation header + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include + +struct bt_smp_hdr +{ + uint8_t code; +} __packed; + +#define BT_SMP_ERR_SUCCESS 0x00 +#define BT_SMP_ERR_PASSKEY_ENTRY_FAILED 0x01 +#define BT_SMP_ERR_OOB_NOT_AVAIL 0x02 +#define BT_SMP_ERR_AUTH_REQUIREMENTS 0x03 +#define BT_SMP_ERR_CONFIRM_FAILED 0x04 +#define BT_SMP_ERR_PAIRING_NOTSUPP 0x05 +#define BT_SMP_ERR_ENC_KEY_SIZE 0x06 +#define BT_SMP_ERR_CMD_NOTSUPP 0x07 +#define BT_SMP_ERR_UNSPECIFIED 0x08 +#define BT_SMP_ERR_REPEATED_ATTEMPTS 0x09 +#define BT_SMP_ERR_INVALID_PARAMS 0x0a +#define BT_SMP_ERR_DHKEY_CHECK_FAILED 0x0b +#define BT_SMP_ERR_NUMERIC_COMP_FAILED 0x0c +#define BT_SMP_ERR_BREDR_PAIRING_IN_PROGRESS 0x0d +#define BT_SMP_ERR_CROSS_TRANSP_NOT_ALLOWED 0x0e +#define BT_SMP_ERR_KEY_REJECTED 0x0f + +#define BT_SMP_IO_DISPLAY_ONLY 0x00 +#define BT_SMP_IO_DISPLAY_YESNO 0x01 +#define BT_SMP_IO_KEYBOARD_ONLY 0x02 +#define BT_SMP_IO_NO_INPUT_OUTPUT 0x03 +#define BT_SMP_IO_KEYBOARD_DISPLAY 0x04 + +#define BT_SMP_OOB_DATA_MASK 0x01 +#define BT_SMP_OOB_NOT_PRESENT 0x00 +#define BT_SMP_OOB_PRESENT 0x01 + +#define BT_SMP_MIN_ENC_KEY_SIZE CONFIG_BT_SMP_MIN_ENC_KEY_SIZE +#define BT_SMP_MAX_ENC_KEY_SIZE 16 + +#define BT_SMP_DIST_ENC_KEY 0x01 +#define BT_SMP_DIST_ID_KEY 0x02 +#define BT_SMP_DIST_SIGN 0x04 +#define BT_SMP_DIST_LINK_KEY 0x08 + +#define BT_SMP_DIST_MASK 0x0f + +#define BT_SMP_AUTH_NONE 0x00 +#define BT_SMP_AUTH_BONDING 0x01 +#define BT_SMP_AUTH_MITM 0x04 +#define BT_SMP_AUTH_SC 0x08 +#define BT_SMP_AUTH_KEYPRESS 0x10 +#define BT_SMP_AUTH_CT2 0x20 + +#define BT_SMP_CMD_PAIRING_REQ 0x01 +#define BT_SMP_CMD_PAIRING_RSP 0x02 +struct bt_smp_pairing +{ + uint8_t io_capability; + uint8_t oob_flag; + uint8_t auth_req; + uint8_t max_key_size; + uint8_t init_key_dist; + uint8_t resp_key_dist; +} __packed; + +#define BT_SMP_CMD_PAIRING_CONFIRM 0x03 +struct bt_smp_pairing_confirm +{ + uint8_t val[16]; +} __packed; + +#define BT_SMP_CMD_PAIRING_RANDOM 0x04 +struct bt_smp_pairing_random +{ + uint8_t val[16]; +} __packed; + +#define BT_SMP_CMD_PAIRING_FAIL 0x05 +struct bt_smp_pairing_fail +{ + uint8_t reason; +} __packed; + +#define BT_SMP_CMD_ENCRYPT_INFO 0x06 +struct bt_smp_encrypt_info +{ + uint8_t ltk[16]; +} __packed; + +#define BT_SMP_CMD_CENTRAL_IDENT 0x07 +struct bt_smp_central_ident +{ + uint8_t ediv[2]; + uint8_t rand[8]; +} __packed; + +#define BT_SMP_CMD_IDENT_INFO 0x08 +struct bt_smp_ident_info +{ + uint8_t irk[16]; +} __packed; + +#define BT_SMP_CMD_IDENT_ADDR_INFO 0x09 +struct bt_smp_ident_addr_info +{ + bt_addr_le_t addr; +} __packed; + +#define BT_SMP_CMD_SIGNING_INFO 0x0a +struct bt_smp_signing_info +{ + uint8_t csrk[16]; +} __packed; + +#define BT_SMP_CMD_SECURITY_REQUEST 0x0b +struct bt_smp_security_request +{ + uint8_t auth_req; +} __packed; + +#define BT_SMP_CMD_PUBLIC_KEY 0x0c +struct bt_smp_public_key +{ + uint8_t x[32]; + uint8_t y[32]; +} __packed; + +#define BT_SMP_DHKEY_CHECK 0x0d +struct bt_smp_dhkey_check +{ + uint8_t e[16]; +} __packed; + +#define BT_SMP_KEYPRESS_NOTIFICATION 0x0e +struct bt_smp_keypress_notif +{ + uint8_t type; +} __packed; + +#define BT_SMP_NUM_CMDS 0x0f + +int bt_smp_start_security(struct bt_conn *conn); +bool bt_smp_request_ltk(struct bt_conn *conn, + uint64_t rand, uint16_t ediv, uint8_t *ltk); + +void bt_smp_update_keys(struct bt_conn *conn); + +int bt_smp_br_send_pairing_req(struct bt_conn *conn); + +int bt_smp_init(void); + +int bt_smp_auth_cb_overlay(struct bt_conn *conn, + const struct bt_conn_auth_cb *cb); +int bt_smp_auth_keypress_notify(struct bt_conn *conn, + enum bt_conn_auth_keypress type); +int bt_smp_auth_passkey_entry(struct bt_conn *conn, unsigned int passkey); +int bt_smp_auth_passkey_confirm(struct bt_conn *conn); +int bt_smp_auth_pairing_confirm(struct bt_conn *conn); +int bt_smp_auth_cancel(struct bt_conn *conn); + +int bt_smp_le_oob_set_tk(struct bt_conn *conn, const uint8_t *tk); +int bt_smp_le_oob_generate_sc_data(struct bt_le_oob_sc_data *le_sc_oob); +int bt_smp_le_oob_set_sc_data( + struct bt_conn *conn, const struct bt_le_oob_sc_data *oobd_local, + const struct bt_le_oob_sc_data *oobd_remote); +int bt_smp_le_oob_get_sc_data( + struct bt_conn *conn, const struct bt_le_oob_sc_data **oobd_local, + const struct bt_le_oob_sc_data **oobd_remote); + +/** brief Verify signed message + * + * @param conn Bluetooth connection + * @param buf received packet buffer with message and signature + * + * @return 0 in success, error code otherwise + */ + +int bt_smp_sign_verify(struct bt_conn *conn, struct net_buf *buf); + +/** brief Sign message + * + * @param conn Bluetooth connection + * @param buf message buffer + * + * @return 0 in success, error code otherwise + */ + +int bt_smp_sign(struct bt_conn *conn, struct net_buf *buf); + +/** Generate IRK from Identity Root (IR) */ + +int bt_smp_irk_get(uint8_t *ir, uint8_t *irk); + +/** Converts a SMP error to string. + * + * The error codes are described in the Bluetooth Core specification, Vol 3, + * Part H, Section 3.5.5. + * + * The Security Manager Protocol documentation found in Vol 4, Part H, + * describes when the different error codes are used. + * + * See also the defined BT_SMP_ERR_* macros. + * + * @return The string representation of the SMP error code. + */ +#if defined(CONFIG_BT_SMP_ERR_TO_STR) +const char *bt_smp_err_to_str(uint8_t smp_err); +#else +static inline const char *bt_smp_err_to_str(uint8_t smp_err) +{ + ARG_UNUSED(smp_err); + + return ""; +} +#endif diff --git a/libs/zblue/host/smp_null.c b/libs/zblue/host/smp_null.c new file mode 100644 index 0000000000000..8428475c02eda --- /dev/null +++ b/libs/zblue/host/smp_null.c @@ -0,0 +1,130 @@ +/**************************************************************************** + * libs/zblue/host/smp_null.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file smp_null.c Security Manager Protocol stub + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "conn_internal.h" +#include "hci_core.h" +#include "l2cap_internal.h" +#include "smp.h" + +#define LOG_LEVEL CONFIG_BT_HCI_CORE_LOG_LEVEL +LOG_MODULE_REGISTER(bt_smp); + +static struct bt_l2cap_le_chan bt_smp_pool[CONFIG_BT_MAX_CONN]; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bt_smp_sign_verify(struct bt_conn *conn, struct net_buf *buf) +{ + return -ENOTSUP; +} + +int bt_smp_sign(struct bt_conn *conn, struct net_buf *buf) +{ + return -ENOTSUP; +} + +static int bt_smp_recv(struct bt_l2cap_chan *chan, struct net_buf *req_buf) +{ + struct bt_l2cap_le_chan *le_chan = BT_L2CAP_LE_CHAN(chan); + struct bt_smp_pairing_fail *rsp; + struct bt_smp_hdr *hdr; + struct net_buf *buf; + + ARG_UNUSED(req_buf); + + /* If a device does not support pairing then it shall respond with + * a Pairing Failed command with the reason set to "Pairing Not Supported" + * when any command is received. Core Specification Vol. 3, Part H, 3.3 + */ + + buf = bt_l2cap_create_pdu(NULL, 0); + + /* NULL is not a possible return due to ((clock_t)-1) */ + + hdr = net_buf_add(buf, sizeof(*hdr)); + hdr->code = BT_SMP_CMD_PAIRING_FAIL; + + rsp = net_buf_add(buf, sizeof(*rsp)); + rsp->reason = BT_SMP_ERR_PAIRING_NOTSUPP; + + if (bt_l2cap_send_pdu(le_chan, buf, NULL, NULL)) + { + net_buf_unref(buf); + } + + return 0; +} + +static int bt_smp_accept(struct bt_conn *conn, struct bt_l2cap_chan **chan) +{ + int i; + static const struct bt_l2cap_chan_ops ops = + { + .recv = bt_smp_recv, + }; + + LOG_DBG("conn %p handle %u", conn, conn->handle); + + for (i = 0; i < ARRAY_SIZE(bt_smp_pool); i++) + { + struct bt_l2cap_le_chan *smp = &bt_smp_pool[i]; + + if (smp->chan.conn) + { + continue; + } + + smp->chan.ops = &ops; + + *chan = &smp->chan; + + return 0; + } + + LOG_ERR("No available SMP context for conn %p", conn); + + return -ENOMEM; +} + +BT_L2CAP_FIXED_CHANNEL_DEFINE(smp_fixed_chan) = +{ + .cid = BT_L2CAP_CID_SMP, + .accept = bt_smp_accept, +}; + +int bt_smp_init(void) +{ + return 0; +} diff --git a/libs/zblue/host/uuid.c b/libs/zblue/host/uuid.c new file mode 100644 index 0000000000000..3f06d05644e35 --- /dev/null +++ b/libs/zblue/host/uuid.c @@ -0,0 +1,356 @@ +/**************************************************************************** + * libs/zblue/host/uuid.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* uuid.c - Bluetooth UUID handling */ + +/* Copyright (c) 2025 Xiaomi Corporation + * Copyright (c) 2015-2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define BT_UUID_STR_LEN_16 4U +#define BT_UUID_STR_LEN_32 8U +#define BT_UUID_STR_LEN_128 UUID_STR_LEN +#define BT_UUID_SIZE 16U + +/* Bluetooth Base UUID (big-endian / RFC 9562): + * 00000000-0000-1000-8000-00805F9B34FB + */ + +static const uint8_t bt_uuid_base_be[BT_UUID_SIZE] = +{ + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x10u, 0x00u, 0x80u, + 0x00u, 0x00u, + 0x80u, + 0x5fu, + 0x9bu, + 0x34u, + 0xfbu +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/** + * @brief Expand a Bluetooth UUID to the generic UUID representation. + * + * 16-bit and 32-bit short UUIDs are expanded using the Bluetooth Base UUID. + * 128-bit UUIDs are byte-swapped from BT wire order (LE) to RFC 9562 order + * (BE). + */ + +static void bt_uuid_to_uuid(const struct bt_uuid *src, uuid_t *dst) +{ + uint8_t uuid_be[BT_UUID_SIZE]; + + switch (src->type) + { + case BT_UUID_TYPE_16: + memcpy(uuid_be, bt_uuid_base_be, sizeof(uuid_be)); + sys_put_be16(BT_UUID_16(src)->val, &uuid_be[2]); + uuid_dec_be(uuid_be, dst); + return; + case BT_UUID_TYPE_32: + memcpy(uuid_be, bt_uuid_base_be, sizeof(uuid_be)); + sys_put_be32(BT_UUID_32(src)->val, uuid_be); + uuid_dec_be(uuid_be, dst); + return; + case BT_UUID_TYPE_128: + sys_memcpy_swap(uuid_be, BT_UUID_128(src)->val, sizeof(uuid_be)); + uuid_dec_be(uuid_be, dst); + return; + default: + DEBUGASSERT(false); + } +} + +/** + * @brief Offset of the Bluetooth Base UUID tail (bytes [4..15]). + */ +#define BT_UUID_BASE_TAIL_OFFSET 4U + +/** + * @brief Check whether a generic UUID matches the Bluetooth Base UUID. + * + * Compares bytes [4..15] against the Bluetooth Base UUID tail. + * + * @return true if the UUID matches the Bluetooth Base UUID pattern. + */ + +static bool is_bt_base_uuid(const uuid_t *u) +{ + uint8_t uuid_be[BT_UUID_SIZE]; + + uuid_enc_be(uuid_be, u); + return memcmp(&uuid_be[BT_UUID_BASE_TAIL_OFFSET], + &bt_uuid_base_be[BT_UUID_BASE_TAIL_OFFSET], + sizeof(uuid_be) - BT_UUID_BASE_TAIL_OFFSET) == 0; +} + +/** + * @brief Determine the compact Bluetooth UUID type for a base-matching UUID. + * + * @pre is_bt_base_uuid(u) must be true. + * + * @return BT_UUID_TYPE_16 or BT_UUID_TYPE_32. + */ + +static uint8_t bt_base_uuid_type(const uuid_t *u) +{ + uint8_t uuid_be[BT_UUID_SIZE]; + + uuid_enc_be(uuid_be, u); + if (uuid_be[0] == 0 && uuid_be[1] == 0) + { + return BT_UUID_TYPE_16; + } + + return BT_UUID_TYPE_32; +} + +int bt_uuid_cmp(const struct bt_uuid *u1, const struct bt_uuid *u2) +{ + if (u1->type != u2->type) + { + uuid_t uu1; + uuid_t uu2; + uint8_t uu1_be[BT_UUID_SIZE]; + uint8_t uu2_be[BT_UUID_SIZE]; + + bt_uuid_to_uuid(u1, &uu1); + bt_uuid_to_uuid(u2, &uu2); + uuid_enc_be(uu1_be, &uu1); + uuid_enc_be(uu2_be, &uu2); + return memcmp(uu1_be, uu2_be, sizeof(uu1_be)); + } + + switch (u1->type) + { + case BT_UUID_TYPE_16: + return (int)BT_UUID_16(u1)->val - (int)BT_UUID_16(u2)->val; + case BT_UUID_TYPE_32: + return (int)BT_UUID_32(u1)->val - (int)BT_UUID_32(u2)->val; + case BT_UUID_TYPE_128: + return memcmp(BT_UUID_128(u1)->val, BT_UUID_128(u2)->val, 16); + } + + return -EINVAL; +} + +bool bt_uuid_create(struct bt_uuid *uuid, + const uint8_t *data, uint8_t data_len) +{ + /* Copy UUID from packet data/internal variable to internal bt_uuid */ + + switch (data_len) + { + case BT_UUID_SIZE_16: + uuid->type = BT_UUID_TYPE_16; + BT_UUID_16(uuid)->val = sys_get_le16(data); + break; + case BT_UUID_SIZE_32: + uuid->type = BT_UUID_TYPE_32; + BT_UUID_32(uuid)->val = sys_get_le32(data); + break; + case BT_UUID_SIZE_128: + uuid->type = BT_UUID_TYPE_128; + memcpy(&BT_UUID_128(uuid)->val, data, 16); + break; + default: + return false; + } + + return true; +} + +void bt_uuid_to_str(const struct bt_uuid *uuid, char *str, size_t len) +{ + __maybe_unused int err; + + switch (uuid->type) + { + case BT_UUID_TYPE_16: + err = snprintf(str, len, "%04x", BT_UUID_16(uuid)->val); + DEBUGASSERT(err == BT_UUID_STR_LEN_16); + break; + case BT_UUID_TYPE_32: + err = snprintf(str, len, "%08x", BT_UUID_32(uuid)->val); + DEBUGASSERT(err == BT_UUID_STR_LEN_32); + break; + case BT_UUID_TYPE_128: + { + uuid_t gen_uuid; + uint32_t status; + char *full = NULL; + + bt_uuid_to_uuid(uuid, &gen_uuid); + uuid_to_string(&gen_uuid, &full, &status); + if (status != uuid_s_ok || full == NULL) + { + (void)memset(str, 0, len); + return; + } + + err = snprintf(str, len, "%s", full); + free(full); + DEBUGASSERT(err == BT_UUID_STR_LEN_128); + break; + } + + default: + (void)memset(str, 0, len); + return; + } +} + +int bt_uuid_from_str(const char *str, struct bt_uuid_any *uuid) +{ + uuid_t gen_uuid; + uint8_t uuid_be[BT_UUID_SIZE]; + uint32_t status; + + if (str == NULL || uuid == NULL) + { + return -EINVAL; + } + + switch (strlen(str)) + { + case BT_UUID_STR_LEN_16: + { + char full[UUID_STR_LEN + 1U]; + + snprintf(full, sizeof(full), + "00000000-0000-1000-8000-00805f9b34fb"); + if (strlen(full) != UUID_STR_LEN) + { + return -EINVAL; + } + + (void)memcpy(&full[4], str, BT_UUID_STR_LEN_16); + + uuid_from_string(full, &gen_uuid, &status); + if (status != uuid_s_ok) + { + return -EINVAL; + } + + /* Enforce 16-bit UUID type for 4-hex-digit input strings. */ + + uuid->uuid.type = BT_UUID_TYPE_16; + uuid_enc_be(uuid_be, &gen_uuid); + BT_UUID_16(&uuid->uuid)->val = sys_get_be16(&uuid_be[2]); + return 0; + } + + case BT_UUID_STR_LEN_32: + { + char full[UUID_STR_LEN + 1U]; + + snprintf(full, sizeof(full), + "00000000-0000-1000-8000-00805f9b34fb"); + if (strlen(full) != UUID_STR_LEN) + { + return -EINVAL; + } + + (void)memcpy(full, str, BT_UUID_STR_LEN_32); + uuid_from_string(full, &gen_uuid, &status); + if (status != uuid_s_ok) + { + return -EINVAL; + } + + /* Enforce 32-bit UUID type for 8-hex-digit input strings. */ + + uuid->uuid.type = BT_UUID_TYPE_32; + uuid_enc_be(uuid_be, &gen_uuid); + BT_UUID_32(&uuid->uuid)->val = sys_get_be32(uuid_be); + return 0; + } + + case BT_UUID_STR_LEN_128: /* full string -> always 128-bit */ + uuid_from_string(str, &gen_uuid, &status); + if (status != uuid_s_ok) + { + return -EINVAL; + } + + uuid->uuid.type = BT_UUID_TYPE_128; + uuid_enc_be(uuid_be, &gen_uuid); + sys_memcpy_swap(BT_UUID_128(&uuid->uuid)->val, uuid_be, + sizeof(uuid_be)); + return 0; + default: + return -EINVAL; + } +} + +int bt_uuid_compress(const struct bt_uuid *src, struct bt_uuid_any *dst) +{ + uuid_t u; + uint8_t uuid_be[BT_UUID_SIZE]; + + if (src == NULL || dst == NULL) + { + return -EINVAL; + } + + switch (src->type) + { + case BT_UUID_TYPE_16: + dst->uuid.type = BT_UUID_TYPE_16; + BT_UUID_16(&dst->uuid)->val = BT_UUID_16(src)->val; + return 0; + case BT_UUID_TYPE_32: + dst->uuid.type = BT_UUID_TYPE_32; + BT_UUID_32(&dst->uuid)->val = BT_UUID_32(src)->val; + return 0; + case BT_UUID_TYPE_128: + bt_uuid_to_uuid(src, &u); + + if (!is_bt_base_uuid(&u)) + { + return -ENOTSUP; + } + + dst->uuid.type = bt_base_uuid_type(&u); + uuid_enc_be(uuid_be, &u); + + if (dst->uuid.type == BT_UUID_TYPE_16) + { + BT_UUID_16(&dst->uuid)->val = sys_get_be16(&uuid_be[2]); + } + else + { + BT_UUID_32(&dst->uuid)->val = sys_get_be32(uuid_be); + } + + return 0; + default: + return -EINVAL; + } +} diff --git a/libs/zblue/lib/CMakeLists.txt b/libs/zblue/lib/CMakeLists.txt new file mode 100644 index 0000000000000..9465e0f48f9cf --- /dev/null +++ b/libs/zblue/lib/CMakeLists.txt @@ -0,0 +1,41 @@ +# ############################################################################## +# libs/zblue/lib/CMakeLists.txt +# +# Author: chao an +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +if(CONFIG_BT) + set(BT_LIB_SRCS) + + if(CONFIG_BT_EAD) + list(APPEND BT_LIB_SRCS ${CMAKE_CURRENT_LIST_DIR}/ead.c) + endif() + + if(CONFIG_LIBSBC) + list(APPEND BT_LIB_SRCS ${CMAKE_CURRENT_LIST_DIR}/sbc.c) + endif() + + if(BT_LIB_SRCS) + target_sources(zblue PRIVATE ${BT_LIB_SRCS}) + endif() + + target_include_directories(zblue PRIVATE ${CMAKE_CURRENT_LIST_DIR}) +endif() diff --git a/libs/zblue/lib/Kconfig b/libs/zblue/lib/Kconfig new file mode 100644 index 0000000000000..e19f670c7ed4e --- /dev/null +++ b/libs/zblue/lib/Kconfig @@ -0,0 +1,22 @@ +############################################################################ +# libs/zblue/lib/Kconfig +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Copyright (c) 2023 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config BT_EAD + bool "Encrypted Advertising Data" + select BT_HOST_CCM + help + Enable the Encrypted Advertising Data library + +config LIBSBC + bool "SBC codec support" + default n + help + Enable the SBC codec helper used by Bluetooth Classic A2DP. diff --git a/libs/zblue/lib/Make.defs b/libs/zblue/lib/Make.defs new file mode 100644 index 0000000000000..839671234c8c0 --- /dev/null +++ b/libs/zblue/lib/Make.defs @@ -0,0 +1,39 @@ +############################################################################ +# libs/zblue/lib/Make.defs +# +# Author: chao an +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +ifeq ($(CONFIG_BT),y) + +ifeq ($(CONFIG_BT_EAD),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/lib/ead.c +endif + +ifeq ($(CONFIG_LIBSBC),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/lib/sbc.c +endif + +VPATH += $(TOPDIR)/libs/zblue/lib +DEPPATH += --dep-path $(TOPDIR)/libs/zblue/lib +CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)/libs/zblue/lib + +endif diff --git a/libs/zblue/lib/ead.c b/libs/zblue/lib/ead.c new file mode 100644 index 0000000000000..8ec4959559cfd --- /dev/null +++ b/libs/zblue/lib/ead.c @@ -0,0 +1,308 @@ +/**************************************************************************** + * libs/zblue/lib/ead.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2023 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include +#include + +#include + +/** nonce size in bytes */ +#define BT_EAD_NONCE_SIZE 13 + +/* This value is used to set the directionBit of the CCM nonce to the MSB of + * the Randomizer field (see Supplement to the Bluetooth Core Specification + * v11, Part A 1.23.3) + */ +#define BT_EAD_RANDOMIZER_DIRECTION_BIT 7 + +/** Additional Authenticated Data size in bytes */ +#define BT_EAD_AAD_SIZE 1 + +/* Fixed value used for the Additional Authenticated Data (see Supplement to + * the Bluetooth Core Specification v11, Part A 1.23.3) + */ + +static uint8_t bt_ead_aad[] = +{ + 0xea +}; +BUILD_ASSERT(sizeof(bt_ead_aad) == BT_EAD_AAD_SIZE); + +LOG_MODULE_REGISTER(bt_encrypted_ad_data, CONFIG_BT_EAD_LOG_LEVEL); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int bt_ead_generate_randomizer( + uint8_t randomizer[BT_EAD_RANDOMIZER_SIZE]) +{ + int err; + + err = bt_rand(randomizer, BT_EAD_RANDOMIZER_SIZE); + + if (err != 0) + { + return -ECANCELED; + } + + /* From Supplement to the Bluetooth Core Specification v11, Part A 1.23.3: + * The directionBit of the CCM nonce shall be set to the most significant + * bit of the Randomizer field. + */ + + randomizer[4] |= 1 << BT_EAD_RANDOMIZER_DIRECTION_BIT; + + return 0; +} + +static int bt_ead_generate_nonce(const uint8_t iv[BT_EAD_IV_SIZE], + const uint8_t randomizer[BT_EAD_RANDOMIZER_SIZE], + uint8_t *nonce) +{ + uint8_t new_randomizer[BT_EAD_RANDOMIZER_SIZE]; + + if (randomizer == NULL) + { + int err; + + err = bt_ead_generate_randomizer(new_randomizer); + + if (err != 0) + { + LOG_DBG("Failed to generate Randomizer"); + return -ECANCELED; + } + + randomizer = new_randomizer; + } + + memcpy(&nonce[0], randomizer, BT_EAD_RANDOMIZER_SIZE); + memcpy(&nonce[BT_EAD_RANDOMIZER_SIZE], iv, BT_EAD_IV_SIZE); + + return 0; +} + +static int ead_encrypt(const uint8_t session_key[BT_EAD_KEY_SIZE], + const uint8_t iv[BT_EAD_IV_SIZE], + const uint8_t randomizer[BT_EAD_RANDOMIZER_SIZE], + const uint8_t *payload, size_t payload_size, + uint8_t *encrypted_payload) +{ + int err; + uint8_t nonce[BT_EAD_NONCE_SIZE]; + size_t ead_size = BT_EAD_RANDOMIZER_SIZE + payload_size + BT_EAD_MIC_SIZE; + + err = bt_ead_generate_nonce(iv, randomizer, nonce); + if (err != 0) + { + return -ECANCELED; + } + + memcpy(encrypted_payload, nonce, BT_EAD_RANDOMIZER_SIZE); + + err = bt_ccm_encrypt(session_key, nonce, payload, payload_size, bt_ead_aad, + BT_EAD_AAD_SIZE, + &encrypted_payload[BT_EAD_RANDOMIZER_SIZE], + BT_EAD_MIC_SIZE); + if (err != 0) + { + LOG_DBG("Failed to encrypt the payload (bt_ccm_encrypt err %d)", err); + return -EIO; + } + + LOG_HEXDUMP_DBG(encrypted_payload, ead_size, "Encrypted Data: "); + + return 0; +} + +int bt_ead_encrypt(const uint8_t session_key[BT_EAD_KEY_SIZE], + const uint8_t iv[BT_EAD_IV_SIZE], const uint8_t *payload, + size_t payload_size, uint8_t *encrypted_payload) +{ + if (session_key == NULL) + { + LOG_DBG("session_key is NULL"); + return -EINVAL; + } + + if (iv == NULL) + { + LOG_DBG("iv is NULL"); + return -EINVAL; + } + + if (payload == NULL) + { + LOG_DBG("payload is NULL"); + return -EINVAL; + } + + if (encrypted_payload == NULL) + { + LOG_DBG("encrypted_payload is NULL"); + return -EINVAL; + } + + if (payload_size == 0) + { + LOG_WRN("payload_size is set to 0. The encrypted result will only " + "contain the " + "Randomizer and the MIC."); + } + + return ead_encrypt(session_key, iv, NULL, payload, payload_size, + encrypted_payload); +} + +#if defined(CONFIG_BT_TESTING) + +int bt_test_ead_encrypt(const uint8_t session_key[BT_EAD_KEY_SIZE], + const uint8_t iv[BT_EAD_IV_SIZE], + const uint8_t randomizer[BT_EAD_RANDOMIZER_SIZE], + const uint8_t *payload, + size_t payload_size, + uint8_t *encrypted_payload) +{ + if (session_key == NULL) + { + LOG_DBG("session_key is NULL"); + return -EINVAL; + } + + if (iv == NULL) + { + LOG_DBG("iv is NULL"); + return -EINVAL; + } + + if (randomizer == NULL) + { + LOG_DBG("randomizer is NULL"); + return -EINVAL; + } + + if (payload == NULL) + { + LOG_DBG("payload is NULL"); + return -EINVAL; + } + + if (encrypted_payload == NULL) + { + LOG_DBG("encrypted_payload is NULL"); + return -EINVAL; + } + + if (payload_size == 0) + { + LOG_WRN("payload_size is set to 0. The encrypted result will be " + "filled " + "with only " + "the Randomizer and the MIC."); + } + + return ead_encrypt(session_key, iv, randomizer, payload, payload_size, + encrypted_payload); +} + +#endif /* CONFIG_BT_TESTING */ + +static int ead_decrypt(const uint8_t session_key[BT_EAD_KEY_SIZE], + const uint8_t iv[BT_EAD_IV_SIZE], + const uint8_t *encrypted_payload, + size_t encrypted_payload_size, uint8_t *payload) +{ + int err; + uint8_t nonce[BT_EAD_NONCE_SIZE]; + const uint8_t *encrypted_ad_data = + &encrypted_payload[BT_EAD_RANDOMIZER_SIZE]; + size_t encrypted_ad_data_size = + encrypted_payload_size - BT_EAD_RANDOMIZER_SIZE; + size_t payload_size = encrypted_ad_data_size - BT_EAD_MIC_SIZE; + + const uint8_t *randomizer = encrypted_payload; + + err = bt_ead_generate_nonce(iv, randomizer, nonce); + if (err != 0) + { + return -EIO; + } + + LOG_HEXDUMP_DBG(encrypted_ad_data, encrypted_ad_data_size, + "Encrypted Data: "); + + err = bt_ccm_decrypt(session_key, nonce, encrypted_ad_data, payload_size, + bt_ead_aad, + BT_EAD_AAD_SIZE, payload, BT_EAD_MIC_SIZE); + LOG_HEXDUMP_DBG(payload, payload_size, "Decrypted Data: "); + if (err != 0) + { + LOG_DBG("Failed to decrypt the data (bt_ccm_decrypt err %d)", err); + return -EIO; + } + + return 0; +} + +int bt_ead_decrypt(const uint8_t session_key[BT_EAD_KEY_SIZE], + const uint8_t iv[BT_EAD_IV_SIZE], + const uint8_t *encrypted_payload, + size_t encrypted_payload_size, uint8_t *payload) +{ + if (session_key == NULL) + { + LOG_DBG("session_key is NULL"); + return -EINVAL; + } + + if (iv == NULL) + { + LOG_DBG("iv is NULL"); + return -EINVAL; + } + + if (encrypted_payload == NULL) + { + LOG_DBG("encrypted_payload is NULL"); + return -EINVAL; + } + + if (payload == NULL) + { + LOG_DBG("payload is NULL"); + return -EINVAL; + } + + if (encrypted_payload_size < BT_EAD_RANDOMIZER_SIZE + BT_EAD_MIC_SIZE) + { + LOG_DBG("encrypted_payload_size is not large enough."); + return -EINVAL; + } + else + if (encrypted_payload_size == BT_EAD_RANDOMIZER_SIZE + BT_EAD_MIC_SIZE) + { + LOG_WRN("encrypted_payload_size not large enough to contain encrypted " + "data."); + } + + return ead_decrypt(session_key, iv, encrypted_payload, + encrypted_payload_size, payload); +} diff --git a/libs/zblue/lib/sbc.c b/libs/zblue/lib/sbc.c new file mode 100644 index 0000000000000..908f0bd3ab5d6 --- /dev/null +++ b/libs/zblue/lib/sbc.c @@ -0,0 +1,238 @@ +/**************************************************************************** + * libs/zblue/lib/sbc.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#if defined(CONFIG_LIBSBC) + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int sbc_setup_encoder(struct sbc_encoder *encoder, + struct sbc_encoder_init_param *param) +{ + SBC_ENC_PARAMS *encoder_params; + + if (encoder == NULL) + { + return -EINVAL; + } + + memset(encoder, 0, sizeof(struct sbc_encoder)); + + encoder_params = &encoder->sbc_encoder_params; + + encoder_params->s16ChannelMode = (int16_t)param->ch_mode; + encoder_params->s16NumOfSubBands = (int16_t)param->subband; + if (!encoder_params->s16NumOfSubBands) + { + return -EINVAL; + } + + encoder_params->s16NumOfBlocks = (int16_t)param->blk_len; + if (!encoder_params->s16NumOfBlocks) + { + return -EINVAL; + } + + encoder_params->s16AllocationMethod = (int16_t)param->alloc_mthd; + encoder_params->s16NumOfChannels = param->ch_num; + if (!encoder_params->s16NumOfChannels) + { + return -EINVAL; + } + + switch (param->samp_freq) + { + case 16000u: + encoder_params->s16SamplingFreq = 0; + break; + case 32000u: + encoder_params->s16SamplingFreq = 1; + break; + case 44100u: + encoder_params->s16SamplingFreq = 2; + break; + case 48000u: + encoder_params->s16SamplingFreq = 3; + break; + default: + return -EINVAL; + } + + encoder_params->u16BitRate = param->bit_rate; + + SBC_Encoder_Init(encoder_params); + + if (encoder_params->s16BitPool < param->min_bitpool) + { + /* need to increase the `param->bit_rate` */ + + return -EINVAL; + } + else if (encoder_params->s16BitPool > param->max_bitpool) + { + /* need to decrease the `param->bit_rate` */ + + return -EOVERFLOW; + } + + return 0; +} + +/** + * Encode a SBC frame + */ + +uint32_t sbc_encode(struct sbc_encoder *encoder, + const void *in_data, void *out_data) +{ + uint32_t ret; + + if ((encoder == NULL) || (in_data == NULL) || (out_data == NULL)) + { + return 0; + } + + ret = SBC_Encode(&encoder->sbc_encoder_params, (int16_t *)in_data, + out_data); + + return ret; +} + +int sbc_frame_samples(struct sbc_encoder *encoder) +{ + if (encoder == NULL) + { + return -EINVAL; + } + + return encoder->sbc_encoder_params.s16NumOfSubBands * + encoder->sbc_encoder_params.s16NumOfBlocks; +} + +int sbc_frame_bytes(struct sbc_encoder *encoder) +{ + if (encoder == NULL) + { + return -EINVAL; + } + + return sbc_frame_samples(encoder) * 2 * + (encoder->sbc_encoder_params.s16ChannelMode == SBC_CH_MODE_MONO ? + 1 : 2); +} + +int sbc_frame_encoded_bytes(struct sbc_encoder *encoder) +{ + int size = 4; + int channel_num = 2; + SBC_ENC_PARAMS *encoder_params; + + if (encoder == NULL) + { + return -EINVAL; + } + + encoder_params = &encoder->sbc_encoder_params; + + if (encoder_params->s16ChannelMode == SBC_CH_MODE_MONO) + { + channel_num = 1; + } + + size += (4 * encoder_params->s16NumOfSubBands * channel_num) / 8; + if ((encoder_params->s16ChannelMode == SBC_CH_MODE_MONO) || + (encoder_params->s16ChannelMode == SBC_CH_MODE_DUAL_CHANNEL)) + { + size += + ((encoder_params->s16NumOfBlocks * channel_num * + encoder_params->s16BitPool + 7) / 8); + } + else if (encoder_params->s16ChannelMode == SBC_CH_MODE_STEREO) + { + size += + ((encoder_params->s16NumOfBlocks * + encoder_params->s16BitPool + 7) / 8); + } + else + { + size += ((encoder_params->s16NumOfSubBands + + encoder_params->s16NumOfBlocks * encoder_params->s16BitPool + 7) / + 8); + } + + return size; +} + +/** + * Setup decoder + */ + +int sbc_setup_decoder(struct sbc_decoder *decoder) +{ + OI_STATUS status; + + if (decoder == NULL) + { + return -EINVAL; + } + + memset(decoder, 0, sizeof(struct sbc_decoder)); + + status = OI_CODEC_SBC_DecoderReset(&decoder->context, + &decoder->context_data[0], + sizeof(decoder->context_data), 2, 2, + FALSE); + if (!OI_SUCCESS(status)) + { + return -EIO; + } + + return 0; +} + +/** + * Decode a frame + */ + +int sbc_decode( + struct sbc_decoder *decoder, const void **in_data, uint32_t *in_size, + void *out_data, uint32_t *out_size) +{ + OI_STATUS status; + + if (decoder == NULL || in_data == NULL || in_size == NULL || + out_data == NULL || out_size == NULL) + { + return -EINVAL; + } + + status = OI_CODEC_SBC_DecodeFrame(&decoder->context, + (const OI_BYTE **)in_data, in_size, out_data, + out_size); + if (!OI_SUCCESS(status)) + { + return -EIO; + } + else + { + return 0; + } +} +#endif diff --git a/libs/zblue/mesh/CMakeLists.txt b/libs/zblue/mesh/CMakeLists.txt new file mode 100644 index 0000000000000..3206e0f3f120c --- /dev/null +++ b/libs/zblue/mesh/CMakeLists.txt @@ -0,0 +1,199 @@ +# ############################################################################## +# libs/zblue/mesh/CMakeLists.txt +# +# Author: chao an +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +if(CONFIG_BT_MESH) + set(BT_MESH_SRCS + ${CMAKE_CURRENT_LIST_DIR}/main.c + ${CMAKE_CURRENT_LIST_DIR}/cfg.c + ${CMAKE_CURRENT_LIST_DIR}/adv.c + ${CMAKE_CURRENT_LIST_DIR}/beacon.c + ${CMAKE_CURRENT_LIST_DIR}/net.c + ${CMAKE_CURRENT_LIST_DIR}/subnet.c + ${CMAKE_CURRENT_LIST_DIR}/app_keys.c + ${CMAKE_CURRENT_LIST_DIR}/heartbeat.c + ${CMAKE_CURRENT_LIST_DIR}/crypto.c + ${CMAKE_CURRENT_LIST_DIR}/crypto_psa.c + ${CMAKE_CURRENT_LIST_DIR}/access.c + ${CMAKE_CURRENT_LIST_DIR}/msg.c + ${CMAKE_CURRENT_LIST_DIR}/cfg_srv.c + ${CMAKE_CURRENT_LIST_DIR}/health_srv.c + ${CMAKE_CURRENT_LIST_DIR}/va.c + ${CMAKE_CURRENT_LIST_DIR}/transport.c) + + if(CONFIG_BT_MESH_ADV_LEGACY) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/adv_legacy.c) + endif() + if(CONFIG_BT_MESH_ADV_EXT) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/adv_ext.c) + endif() + if(CONFIG_BT_SETTINGS) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/settings.c) + endif() + if(CONFIG_BT_MESH_RPL_STORAGE_MODE_SETTINGS) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/rpl.c) + endif() + if(CONFIG_BT_MESH_LOW_POWER) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/lpn.c) + endif() + if(CONFIG_BT_MESH_FRIEND) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/friend.c) + endif() + if(CONFIG_BT_MESH_PROV) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/prov.c) + endif() + if(CONFIG_BT_MESH_PROVISIONEE) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/provisionee.c) + endif() + if(CONFIG_BT_MESH_PROVISIONER) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/provisioner.c) + endif() + if(CONFIG_BT_MESH_PB_ADV) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/pb_adv.c) + endif() + if(CONFIG_BT_MESH_PB_GATT_COMMON) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/pb_gatt.c) + endif() + if(CONFIG_BT_MESH_PB_GATT) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/pb_gatt_srv.c) + endif() + if(CONFIG_BT_MESH_PB_GATT_CLIENT) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/pb_gatt_cli.c) + endif() + if(CONFIG_BT_MESH_GATT_CLIENT) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/gatt_cli.c) + endif() + if(CONFIG_BT_MESH_PROXY_CLIENT) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/proxy_cli.c) + endif() + if(CONFIG_BT_MESH_GATT_PROXY) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/proxy_srv.c) + endif() + if(CONFIG_BT_MESH_GATT) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/proxy_msg.c) + endif() + if(CONFIG_BT_MESH_CFG_CLI) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/cfg_cli.c) + endif() + if(CONFIG_BT_MESH_HEALTH_CLI) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/health_cli.c) + endif() + if(CONFIG_BT_MESH_SAR_CFG_CLI) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/sar_cfg_cli.c) + endif() + if(CONFIG_BT_MESH_SAR_CFG_SRV) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/sar_cfg_srv.c) + endif() + if(CONFIG_BT_MESH_SAR_CFG) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/sar_cfg.c) + endif() + if(CONFIG_BT_MESH_OP_AGG_SRV) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/op_agg_srv.c) + endif() + if(CONFIG_BT_MESH_OP_AGG_CLI) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/op_agg_cli.c) + endif() + if(CONFIG_BT_MESH_OP_AGG) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/op_agg.c) + endif() + if(CONFIG_BT_MESH_LARGE_COMP_DATA_SRV) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/large_comp_data_srv.c) + endif() + if(CONFIG_BT_MESH_LARGE_COMP_DATA_CLI) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/large_comp_data_cli.c) + endif() + if(CONFIG_BT_MESH_PRIV_BEACON_SRV) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/priv_beacon_srv.c) + endif() + if(CONFIG_BT_MESH_PRIV_BEACON_CLI) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/priv_beacon_cli.c) + endif() + if(CONFIG_BT_MESH_SELF_TEST) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/test.c) + endif() + if(CONFIG_BT_MESH_CDB) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/cdb.c) + endif() + if(CONFIG_BT_MESH_BLOB_SRV) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/blob_srv.c) + endif() + if(CONFIG_BT_MESH_BLOB_CLI) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/blob_cli.c) + endif() + if(CONFIG_BT_MESH_BLOB_IO_FLASH) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/blob_io_flash.c) + endif() + if(CONFIG_BT_MESH_DFU_CLI) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/dfu_cli.c) + endif() + if(CONFIG_BT_MESH_DFU_SRV) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/dfu_srv.c) + endif() + if(CONFIG_BT_MESH_DFD_SRV) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/dfd_srv.c) + endif() + if(CONFIG_BT_MESH_DFU_SLOTS) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/dfu_slot.c) + endif() + if(CONFIG_BT_MESH_DFU_METADATA) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/dfu_metadata.c) + endif() + if(CONFIG_BT_MESH_RPR_CLI) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/rpr_cli.c) + endif() + if(CONFIG_BT_MESH_RPR_SRV) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/rpr_srv.c) + endif() + if(CONFIG_BT_MESH_OD_PRIV_PROXY_CLI) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/od_priv_proxy_cli.c) + endif() + if(CONFIG_BT_MESH_OD_PRIV_PROXY_SRV) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/od_priv_proxy_srv.c + ${CMAKE_CURRENT_LIST_DIR}/sol_pdu_rpl_srv.c) + endif() + if(CONFIG_BT_MESH_SOL_PDU_RPL_CLI) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/sol_pdu_rpl_cli.c) + endif() + if(CONFIG_BT_MESH_BRG_CFG_CLI) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/brg_cfg_cli.c) + endif() + if(CONFIG_BT_MESH_BRG_CFG_SRV) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/brg_cfg_srv.c + ${CMAKE_CURRENT_LIST_DIR}/brg_cfg.c) + endif() + if(CONFIG_BT_MESH_SOLICITATION) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/solicitation.c) + endif() + if(CONFIG_BT_MESH_STATISTIC) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/statistic.c) + endif() + if(CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/delayable_msg.c) + endif() + if(CONFIG_BT_TESTING) + list(APPEND BT_MESH_SRCS ${CMAKE_CURRENT_LIST_DIR}/testing.c) + endif() + + target_sources(zblue PRIVATE ${BT_MESH_SRCS}) + target_include_directories(zblue PRIVATE ${CMAKE_CURRENT_LIST_DIR}) +endif() diff --git a/libs/zblue/mesh/Kconfig b/libs/zblue/mesh/Kconfig new file mode 100644 index 0000000000000..48f35bd7754a9 --- /dev/null +++ b/libs/zblue/mesh/Kconfig @@ -0,0 +1,1947 @@ +############################################################################ +# libs/zblue/mesh/Kconfig +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth Mesh configuration options + +# Copyright (c) 2017 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +menuconfig BT_MESH + bool "Bluetooth Mesh support" + depends on BT_OBSERVER && BT_BROADCASTER + select POLL + help + This option enables Bluetooth Mesh support. The specific + features that are available may depend on other features + that have been enabled in the stack, such as GATT support. + +if BT_MESH + +################################################################################################### +# Invisible options +################################################################################################### + +# Virtual option enabled whenever Proxy Server or Client is needed +config BT_MESH_PROXY + bool + +config BT_MESH_GATT + bool + +config BT_MESH_PROXY_MSG_LEN + int + default 66 if BT_MESH_PB_GATT_COMMON + default 33 if BT_MESH_GATT_PROXY + depends on BT_MESH_GATT + +config BT_MESH_GATT_CLIENT + bool + select BT_MESH_GATT + +config BT_MESH_GATT_SERVER + bool + select BT_MESH_GATT + select BT_GATT_DYNAMIC_DB + +config BT_MESH_PB_GATT_COMMON + bool + +# Virtual option enabled whenever Generic Provisioning layer is needed +config BT_MESH_PROV + bool + select BT_LONG_WQ + +################################################################################################### +# Visible options +################################################################################################### + +menu "Advertiser" + +choice BT_MESH_ADV + prompt "Advertiser mode" + default BT_MESH_ADV_EXT if BT_EXT_ADV + default BT_MESH_ADV_LEGACY + +menuconfig BT_MESH_ADV_LEGACY + bool "Legacy advertising" + help + Use legacy advertising commands for mesh sending. Legacy + advertising is significantly slower than the extended advertising, but + is supported by all controllers. + + WARNING: The legacy advertiser can occasionally do more message + retransmissions than requested because of limitations of HCI + interface API. + +if BT_MESH_ADV_LEGACY + +config BT_MESH_ADV_STACK_SIZE + int "Mesh advertiser thread stack size" + default 1800 if BT_MESH_PROVISIONER + default 1536 if BT_MESH_PROXY + default 776 if BT_MESH_PRIV_BEACONS + default 768 + help + Size of bt mesh adv thread stack. + + NOTE: This is an advanced setting and should not be changed unless + absolutely necessary + +config BT_MESH_ADV_PRIO + int "Mesh advertiser thread priority" + default 7 + help + Priority of bt mesh adv thread. + + NOTE: This is an advanced setting and should not be changed unless + absolutely necessary + +endif # BT_MESH_ADV_LEGACY + +menuconfig BT_MESH_ADV_EXT + bool "Extended advertising" + depends on BT_EXT_ADV + help + Use extended advertising commands for operating the advertiser. + Extended advertising is faster and uses less memory than legacy + advertising, but isn't supported by all controllers. + +if BT_MESH_ADV_EXT + +choice BT_MESH_WORKQ_CONTEXT + prompt "Advertising thread selection" + default BT_MESH_WORKQ_MESH + help + Defines a context for mesh messages transmission. + +config BT_MESH_WORKQ_MESH + bool "Mesh-specific workqueue" + help + When this option is selected, the mesh sends messages from the + mesh-specific workqueue. This will ensure that messages are always sent. + The application needs to ensure the mesh-specific workqueue size is large + enough. Refer to BT_MESH_ADV_STACK_SIZE for the recommended minimum. + +config BT_MESH_WORKQ_SYS + bool "System workqueue" + help + When this option is selected, the mesh sends messages from + the system work queue. The application needs to ensure the system + workqueue stack size (SYSTEM_WORKQUEUE_STACK_SIZE) is large enough, + refer to BT_MESH_ADV_STACK_SIZE for the recommended minimum. + + When this option is enabled and the mesh tries to send a message, + and the host ran out of the HCI command buffers controlled by + CONFIG_BT_BUF_CMD_TX_COUNT, the system work queue will be blocked + until an HCI command buffer has been freed. This may cause a deadlock + where the host cannot use the system workqueue to free the buffer + that the mesh is waiting for. To mitigate this issue, make sure to + have a sufficient number of HCI command buffers. + When this option is enabled, the latency of sending mesh messages + will be affected by other users on the system work queue, resulting in + reduced reliability for sending mesh messages. + +endchoice + +if BT_MESH_WORKQ_MESH + +config BT_MESH_ADV_STACK_SIZE + int "Mesh extended advertiser thread stack size" + default 2048 if BT_MESH_PROVISIONER + default 1536 if BT_MESH_PROXY + default 776 if BT_MESH_PRIV_BEACONS + default 768 + help + Size of bt mesh adv thread stack. + + NOTE: This is an advanced setting and should not be changed unless + absolutely necessary + +config BT_MESH_ADV_PRIO + int "Mesh advertiser thread priority" + default 7 + help + Priority of bt mesh adv thread. + + NOTE: This is an advanced setting and should not be changed unless + absolutely necessary + +endif # BT_MESH_WORKQ_MESH + +config BT_MESH_RELAY_ADV_SETS + int "Maximum of simultaneous relay message support" + default 0 + range 0 16 + depends on BT_MESH_RELAY || BT_MESH_BRG_CFG_SRV + help + Maximum of simultaneous relay message support. Requires controller support + multiple advertising sets. + + Note that: The Subnet Bridge feature uses the relay advertising sets. + +config BT_MESH_ADV_EXT_RELAY_USING_MAIN_ADV_SET + bool "Use the main advertising set to relay messages" + depends on BT_MESH_RELAY_ADV_SETS > 0 + help + When this option is enabled, there is a message that needs to be + relayed, all relay advertising sets defined by + CONFIG_BT_MESH_RELAY_ADV_SETS are busy with relaying messages + and the main advertising set is not busy with sending local + messages, the stack will use the main advertising set to relay + the message. This maximizes the utilization efficiency of + advertising sets, which is helpful for the sending of dense + relays. With CONFIG_BT_MESH_RELAY_RETRANSMIT_COUNT value + greater than zero, this can noticeably delay transmission of + local messages. When Friend feature is enabled and the node is + in a friendship, this option can delay transmission of local + messages thus requiring bigger CONFIG_BT_MESH_FRIEND_RECV_WIN + value. This in turn will result in increase of the power + consumption of the Low Power node. + +config BT_MESH_ADV_EXT_GATT_SEPARATE + bool "Use a separate extended advertising set for GATT Server Advertising" + depends on BT_MESH_GATT_SERVER + help + Use a separate extended advertising set for GATT Server Advertising, + otherwise a shared advertising set will be used. + +config BT_MESH_ADV_EXT_FRIEND_SEPARATE + bool "Use a separate extended advertising set for Friend advertising" + depends on BT_MESH_FRIEND + help + Use a separate extended advertising set for Friend advertising, + otherwise a shared advertising set will be used. Using the separate + extended advertising set makes the Friend node send friendship + messages as close to the start of the ReceiveWindow as possible, + thus reducing the scanning time on the Low Power node. + +endif # BT_MESH_ADV_EXT + +endchoice + +config BT_MESH_ADV_BUF_COUNT + int "Number of advertising buffers for local messages" + default 6 + range 1 256 + help + Number of advertising buffers available for sending local messages. + This should be chosen based on the number of local messages that the node + can send simultaneously. + +config BT_MESH_DEBUG_USE_ID_ADDR + bool "Use identity address for all advertising" + help + This option forces the usage of the local identity address for + all advertising. This can be a help for debugging (analyzing + traces), however it should never be enabled for a production + build as it compromises the privacy of the device. + +endmenu # Advertiser + +menu "Provisioning" + +menuconfig BT_MESH_PB_ADV + bool "PB-ADV support" + select BT_MESH_PROV + default y + help + Enable this option to allow the device to be provisioned over + the advertising bearer. + +config BT_MESH_UNPROV_BEACON_INT + int + prompt "The interval (in seconds) to send the unprovisioned beacon" if BT_MESH_PB_ADV + default 5 + range 1 10 + help + This option specifies the interval (in seconds) at which the + device sends unprovisioned beacon. + +if BT_MESH_PB_ADV + +config BT_MESH_PB_ADV_USE_RELAY_SETS + bool "Use relay advertising sets to send provisioning PDUs" + depends on BT_MESH_RELAY_ADV_SETS > 0 + help + Use relay advertising sets to send provisioning PDUs + +config BT_MESH_PB_ADV_TRANS_PDU_RETRANSMIT_COUNT + int "Link Open and Transaction PDU retransmit count" + default 7 if BT_MESH_PB_ADV_USE_RELAY_SETS + default 0 + range 0 7 + help + Controls the number of retransmissions of original Link Open and Transaction PDU, + in addition to the first transmission. + +config BT_MESH_PB_ADV_TRANS_ACK_RETRANSMIT_COUNT + int "Link Ack and Transaction Ack retransmit count" + default 2 + range 0 7 + help + Controls the number of retransmissions of original Link Ack and Transaction Acknowledgment PDU, + in addition to the first transmission. + +config BT_MESH_PB_ADV_LINK_CLOSE_RETRANSMIT_COUNT + int "Link Close retransmit count" + default 7 if BT_MESH_PB_ADV_USE_RELAY_SETS + default 2 + range 0 7 + help + Controls the number of retransmissions of original Link Close, + in addition to the first transmission. + +config BT_MESH_PB_ADV_RETRANS_TIMEOUT + int "Timeout value of retransmit provisioning PDUs" + default 500 + range 100 800 + help + Timeout value of retransmit provisioning PDUs. + +endif # BT_MESH_PB_ADV + +if BT_CONN + +config BT_MESH_PB_GATT + bool "PB-GATT Server support" + select BT_MESH_GATT_SERVER + select BT_MESH_PROV + select BT_MESH_PB_GATT_COMMON + select BT_MESH_PROVISIONEE + help + Enable this option to allow the device to be provisioned over + GATT. + +config BT_MESH_PB_GATT_USE_DEVICE_NAME + bool "Include Bluetooth device name in scan response" + depends on BT_MESH_PB_GATT + default y + help + This option includes GAP device name in scan response when + the PB-GATT is enabled. + +config BT_MESH_PB_GATT_CLIENT + bool "PB-GATT Client support" + select BT_MESH_PROV + select BT_GATT_CLIENT + select BT_MESH_GATT_CLIENT + select BT_MESH_PB_GATT_COMMON + select BT_MESH_PROVISIONER + depends on BT_CENTRAL + help + Enable this option to allow the provisioner provisioning the + device over GATT. + +endif # BT_CONN + +config BT_MESH_PROVISIONEE + bool "Provisionee role support" + depends on BT_MESH_PB_ADV || BT_MESH_PB_GATT + default y + help + Enable this option to allow the device to be provisioned into a mesh network. + +config BT_MESH_PROV_OOB_PUBLIC_KEY + bool "OOB Public key support" + depends on BT_MESH_PROVISIONEE + help + Enable this option if public key is to be exchanged via Out of Band (OOB) technology. + +config BT_MESH_ECDH_P256_CMAC_AES128_AES_CCM + bool "Support CMAC AES128 for OOB authentication" + depends on BT_MESH_PROV + default y + help + Enable this option to support CMAC AES128 for OOB authentication. + +config BT_MESH_ECDH_P256_HMAC_SHA256_AES_CCM + bool "Support HMAC SHA256 for OOB authentication" + depends on BT_MESH_PROV + default y + help + Enable this option to support HMAC SHA256 for OOB authentication. + +config BT_MESH_OOB_AUTH_REQUIRED + bool "OOB authentication mandates to use HMAC SHA256" + depends on BT_MESH_ECDH_P256_HMAC_SHA256_AES_CCM + +config BT_MESH_PROV_OOB_API_LEGACY + bool "Reduced maximum OOB authentication size support [DEPRECATED]" + depends on BT_MESH_PROV + help + By default, the maximum OOB authentication size is 32 bytes. + Enable this option to use the legacy API which is only compatible with + specification v1.1 and earlier. This limits OOB authentication to 8 + bytes maximum. + This option is intended for backward compatibility. + Not recommended for new designs due to reduced security. + +config BT_MESH_PROVISIONER + bool "Provisioner support" + depends on BT_MESH_CDB + depends on BT_MESH_PROV + depends on BT_MESH_PB_ADV || BT_MESH_PB_GATT_CLIENT + help + Enable this option to have support for provisioning remote devices. + +menuconfig BT_MESH_CDB + bool "Mesh Configuration Database" + +if BT_MESH_CDB + +config BT_MESH_CDB_NODE_COUNT + int "Maximum number of nodes in the database" + default 8 + range 1 4096 + help + This option specifies how many nodes each network can at most + save in the configuration database. + +config BT_MESH_CDB_SUBNET_COUNT + int "Maximum number of subnets in the database" + default 1 + range 1 4096 + help + This option specifies how many subnets that can at most be + saved in the configuration database. + +config BT_MESH_CDB_APP_KEY_COUNT + int "Maximum number of application keys in the database" + default 1 + range 1 4096 + help + This option specifies how many application keys that can at most + be saved in the configuration database. + +config BT_MESH_CDB_KEY_SYNC + bool "Keys synchronization support" + default y + help + Enable this option to have support for synchronizing keys between + mesh stack and cdb. + When this option is enabled, key operations (add, remove, update) + performed on the mesh stack through Configuration Server messages + are automatically mirrored to the CDB. + +endif # BT_MESH_CDB + +endmenu # Provisioning + +menu "Network layer" + +config BT_MESH_LOOPBACK_BUFS + int "Number of loopback buffers" + default 3 + help + The number of buffers allocated for the network loopback mechanism. + Loopback is used when the device sends messages to itself. + +config BT_MESH_NETWORK_TRANSMIT_COUNT + int "Network Transmit Count" + default 2 + range 0 7 + help + Controls the initial number of retransmissions of original messages, + in addition to the first transmission. Can be changed through runtime + configuration. + +config BT_MESH_NETWORK_TRANSMIT_INTERVAL + int "Network Transmit Interval" + default 20 + range 10 330 + help + Controls the initial interval between retransmissions of original + messages, in milliseconds. Can be changed through runtime + configuration. + +config BT_MESH_MSG_CACHE_SIZE + int "Network message cache size" + default 32 + range 2 65535 + help + Number of messages that are cached by the node to avoid acting on the + recently seen duplicate messages. This option is similar to + the replay protection list, but has a different purpose. Network message + cache helps prevent unnecessary decryption operations. This also prevents + unnecessary relaying and helps in getting rid of relay loops. Setting + this value to a very low number can cause unnecessary network traffic. + Setting this value to a very large number can impact the processing time + for each received network PDU and increases RAM footprint proportionately. + +menuconfig BT_MESH_RELAY + bool "Relay support" + help + Support for acting as a Mesh Relay Node. + +if BT_MESH_RELAY + +config BT_MESH_RELAY_ENABLED + bool "Relay feature enabled by default" + default y + help + Controls whether the Relay feature is enabled by default when the + device boots up for the first time or unprovisioned. Can be changed + at runtime using bt_mesh_relay_set() function. + +config BT_MESH_RELAY_RETRANSMIT_COUNT + int "Relay Retransmit Count" + default 2 + range 0 7 + help + Controls the initial number of retransmissions of relayed messages, in + addition to the first transmission. Can be changed through runtime + configuration. + +config BT_MESH_RELAY_RETRANSMIT_INTERVAL + int "Relay Retransmit Interval" + default 20 + range 10 330 + help + Controls the initial interval between retransmissions of relayed + messages, in milliseconds. Can be changed through runtime + configuration. + +endif # BT_MESH_RELAY + +config BT_MESH_RELAY_BUF_COUNT + int "Number of advertising buffers for relayed messages" + depends on BT_MESH_RELAY || BT_MESH_BRG_CFG_SRV + default 32 + range 1 256 + help + Number of advertising buffers available for messages to be relayed. + High number of advertising buffers increases the reliability of the + mesh network. Low number of advertising buffers reduces the message + latency on the Relay Node, but at the same time increases the amount + of packet drops. When considering the message latency, also consider + the values of BT_MESH_RELAY_RETRANSMIT_COUNT and + BT_MESH_RELAY_RETRANSMIT_INTERVAL. A higher number of + BT_MESH_RELAY_ADV_SETS allows the increase in the number of buffers + while maintaining the latency. + + Note that: The Subnet Bridge feature uses the relay advertising buffers. If both the Relay + feature and the Subnet Bridge feature is enabled, the added load should be taken into + account. + +endmenu # Network layer + +menu "Transport layer" + +menu "Transport SAR configuration" + +config BT_MESH_TX_SEG_MSG_COUNT + int "Maximum number of simultaneous outgoing segmented messages" + default 1 + range 0 255 + help + Maximum number of simultaneous outgoing multi-segment and/or + reliable messages. + + Note that: Since Mesh Segmentation/reassembling is a mandatory + feature of specification, set to zero will not allow send any + Mesh Segment message. + +config BT_MESH_RX_SEG_MSG_COUNT + int "Maximum number of simultaneous incoming segmented messages" + default 1 + range 0 255 + help + Maximum number of simultaneous incoming multi-segment and/or + reliable messages. + + Note that: Since Mesh Segmentation/reassemblying is a mandatory + feature of specification, set to zero will not allow receive any + Mesh Segment message. + +config BT_MESH_SEG_BUFS + int "Number of segment buffers available" + default 64 + range BT_MESH_RX_SEG_MAX 16384 if BT_MESH_RX_SEG_MAX > \ + BT_MESH_TX_SEG_MAX + range BT_MESH_TX_SEG_MAX 16384 + help + The incoming and outgoing segmented messages allocate their + segments from the same pool. Each segment is a 12 byte block, + and may only be used by one message at the time. + + Outgoing messages will allocate their segments at the start of the + transmission, and release them one by one as soon as they have been + acknowledged by the receiver. Incoming messages allocate all their + segments at the start of the transaction, and won't release them until + the message is fully received. + +config BT_MESH_RX_SEG_MAX + int "Maximum number of segments in incoming messages" + default 6 if BT_MESH_BLOB_SRV || BT_MESH_BLOB_CLI || \ + BT_MESH_RPR_SRV || BT_MESH_RPR_CLI + default 3 + range 1 32 + depends on BT_MESH_RX_SEG_MSG_COUNT > 0 + help + Maximum number of segments supported for incoming messages. + This value should typically be fine-tuned based on what + models the local node supports, i.e. what's the largest + message payload that the node needs to be able to receive. + This value affects memory and call stack consumption, which + is why the default is lower than the maximum that the + specification would allow (32 segments). + + The maximum incoming SDU size is 12 times this number (out of + which 4 or 8 bytes is used for the Transport Layer MIC). For + example, 5 segments means the maximum SDU size is 60 bytes, + which leaves 56 bytes for application layer data using a + 4-byte MIC and 52 bytes using an 8-byte MIC. + +config BT_MESH_TX_SEG_MAX + int "Maximum number of segments in outgoing messages" + default 6 if BT_MESH_BLOB_SRV || BT_MESH_BLOB_CLI || \ + BT_MESH_RPR_SRV || BT_MESH_RPR_CLI + default 3 + range 1 32 + depends on BT_MESH_TX_SEG_MSG_COUNT > 0 + help + Maximum number of segments supported for outgoing messages. + This value should typically be fine-tuned based on what + models the local node supports, i.e. what's the largest + message payload that the node needs to be able to send. + This value affects memory consumption, which is why the + default is lower than the maximum that the specification + would allow (32 segments). + + The maximum outgoing SDU size is 12 times this number (out of + which 4 or 8 bytes is used for the Transport Layer MIC). For + example, 5 segments means the maximum SDU size is 60 bytes, + which leaves 56 bytes for application layer data using a + 4-byte MIC and 52 bytes using an 8-byte MIC. + +config BT_MESH_SAR_TX_SEG_INT_STEP + hex "Interval between sending two consecutive segments" + range 0x00 0x0F + default 0x05 + help + This value controls the interval between sending two consecutive + segments in a segmented message. The interval is measured in + milliseconds and calculated using the following formula: + (CONFIG_BT_MESH_SAR_TX_SEG_INT_STEP + 1) * 10 ms. + +config BT_MESH_SAR_TX_UNICAST_RETRANS_COUNT + hex "Maximum number of retransmissions to unicast address" + range 0x00 0x0F + default 0x02 + help + This value controls the maximum number of retransmissions of a + segmented message to a unicast address before giving up the transfer. + +config BT_MESH_SAR_TX_UNICAST_RETRANS_WITHOUT_PROG_COUNT + hex "Maximum number of retransmissions without progress to a unicast address" + range 0x00 0x0F + default 0x02 + help + This value defines the maximum number of retransmissions of a + segmented message to a unicast address that the stack will send if no + acknowledgment was received during timeout, or if an + acknowledgment with already confirmed segments was received. + +config BT_MESH_SAR_TX_UNICAST_RETRANS_INT_STEP + hex "Retransmissions interval step of missing segments to unicast address" + range 0x00 0x0F + default 0x07 + help + This value controls the interval step used for delaying the + retransmissions of unacknowledged segments of a segmented message to + a unicast address. The interval step is measured in milliseconds and + calculated using the following formula: + (CONFIG_BT_MESH_SAR_TX_UNICAST_RETRANS_INT_STEP + 1) * 25 ms. + +config BT_MESH_SAR_TX_UNICAST_RETRANS_INT_INC + hex "Retransmissions interval increment of missing segments to unicast address" + range 0x00 0x0F + default 0x01 + help + This value controls the interval increment used for delaying the + retransmissions of unacknowledged segments of a segmented message to + a unicast address. The increment is measured in milliseconds and + calculated using the following formula: + (CONFIG_BT_MESH_SAR_TX_UNICAST_RETRANS_INT_INC + 1) * 25 ms. + +config BT_MESH_SAR_TX_MULTICAST_RETRANS_COUNT + hex "Total number of retransmissions to multicast address" + range 0x00 0x0F + default 0x02 + help + This value controls the total number of retransmissions of a segmented + message to a multicast address. + +config BT_MESH_SAR_TX_MULTICAST_RETRANS_INT + hex "Interval between retransmissions to multicast address" + range 0x00 0x0F + default 0x09 + help + This value controls the interval between retransmissions of all + segments in a segmented message to a multicast address. The + interval is measured in milliseconds and calculated using the + following formula: + (CONFIG_BT_MESH_SAR_TX_MULTICAST_RETRANS_INT + 1) * 25 ms. + +config BT_MESH_SAR_RX_SEG_THRESHOLD + hex "Acknowledgments retransmission threshold" + range 0x00 0x1F + default 0x03 + help + This value defines a threshold in number of segments of a segmented + message for acknowledgment retransmissions. When the number of + segments of a segmented message is above this threshold, the stack + will additionally retransmit every acknowledgment message the + number of times given by value of CONFIG_BT_MESH_SAR_RX_ACK_RETRANS_COUNT. + +config BT_MESH_SAR_RX_ACK_DELAY_INC + hex "Acknowledgment delay increment" + range 0x00 0x07 + default 0x01 + help + This value controls the delay increment of an interval used for + delaying the transmission of an acknowledgment message after + receiving a new segment. The increment is measured in segments + and calculated using the following formula: + CONFIG_BT_MESH_SAR_RX_ACK_DELAY_INC + 1.5. + +config BT_MESH_SAR_RX_SEG_INT_STEP + hex "Segments reception interval step" + range 0x00 0x0F + default 0x05 + help + This value defines the segments reception interval step used for + delaying the transmission of an acknowledgment message after + receiving a new segment. The interval is measured in milliseconds + and calculated using the following formula: + (CONFIG_BT_MESH_SAR_RX_SEG_INT_STEP + 1) * 10 ms + +config BT_MESH_SAR_RX_DISCARD_TIMEOUT + hex "Discard timeout for reception of a segmented message" + range 0x00 0x0F + default 0x01 + help + This value defines the time since the last successfully received + segment before giving up the reception of a segmented message. + +config BT_MESH_SAR_RX_ACK_RETRANS_COUNT + hex "Total number of acknowledgment message retransmission" + range 0x00 0x03 + default 0x00 + help + This value defines the total number of retranmissions of an + acknowledgment message that the stack will additionally send when the + size of segments in a segmented message is above the + CONFIG_BT_MESH_SAR_RX_SEG_THRESHOLD value. + +endmenu # Transport SAR configuration + +config BT_MESH_DEFAULT_TTL + int "Default TTL value" + default 7 + range 0 128 + help + Controls the default TTL value for outgoing messages. Can be changed + through runtime configuration. + +menu "Replay Protection List" + +config BT_MESH_CRPL + int "Maximum capacity of the replay protection list" + default 26 if BT_MESH_BRG_CFG_SRV + default 10 + range 2 65535 + help + This option specifies the maximum capacity of the replay + protection list. This option is similar to the network message + cache size, but has a purpose of preventing replay attacks. + + Note that: To ensure sufficient space in CRPL for normal node + operations and as specified by other Bluetooth specification + requirements, when subnet bridge functionality is enabled on a node, + you should increase the CRPL capacity in your project configuration + file with the number of bridging table entries + (BT_MESH_BRG_TABLE_ITEMS_MAX) specified for the project as a minimum. + +choice BT_MESH_RPL_STORAGE_MODE + prompt "Replay protection list storage mode" + default BT_MESH_RPL_STORAGE_MODE_SETTINGS + +config BT_MESH_RPL_STORAGE_MODE_SETTINGS + bool "Persistent storage of RPL in Settings" + help + Persistent storage of RPL in Settings. If BT_SETTINGS is not + enabled this choice will provide a non-persistent implementation + variant of the RPL list. + +endchoice + +endmenu # Replay Protection List + +endmenu # Transport layer + +menu "Access layer" + +config BT_MESH_ACCESS_LAYER_MSG + bool "Direct Bluetooth message access layer messages" + help + This option allows the application to directly access + Bluetooth access layer messages without the need to + instantiate Bluetooth Mesh models. + + Please note that Bluetooth Mesh stack stores authentication sequence from + any message that passed over network cache if this option is enabled. + It happens despite the device does not have necessary application keys or + there are unknown model operation codes. It causes the situation when device + will update the replay protection cache for messages those cannot be handled + in the stack. For example, spamming broadcast messages those stack cannot handle + might wear out persistent memory. + +config BT_MESH_MODEL_VND_MSG_CID_FORCE + bool "Force vendor model to use opcodes with matching CID [DEPRECATED]" + help + This option forces vendor models to only use opcodes with a CID field + matching the models CID. It is deprecated and should no longer be used. + +config BT_MESH_MODEL_EXTENSIONS + bool "Support for Model extensions" + help + Enable support for the model extension concept, allowing the Access + layer to know about mesh model relationships. + +config BT_MESH_COMP_PAGE_1 + bool "Support for Composition Data Page 1" + default y + help + Enable support for Composition Data Page 1. + +config BT_MESH_MODEL_EXTENSION_LIST_SIZE + int "Model extensions list size" + depends on BT_MESH_COMP_PAGE_1 + range 0 255 + default 0 if !BT_MESH_MODEL_EXTENSIONS + default 10 + help + This option specifies how many models relations can be saved. + Equals to the number of `bt_mesh_model_extend` and `bt_mesh_model_correspond` calls. + This information is used to construct Composition Data Page 1. + +config BT_MESH_COMP_PAGE_2 + bool "Support for Composition Data Page 2" + help + Enable support for Composition Data Page 2. + +config BT_MESH_COMP_PST_BUF_SIZE + int "Composition Data Page persistence buffer size" + default 100 + help + Stack allocated buffer used to temporarily hold Composition + Data Pages during flash operations. Should reflect the size + of the largest Composition Data Page present in the application. + Note that this buffer should still be large enough to restore previously stored + pages after a performed device firmware update. + +menuconfig BT_MESH_ACCESS_DELAYABLE_MSG + bool "Access layer tx delayable message" + default y + help + Enable following of the message transmitting recommendations, the Access layer + specification. The recommendations are optional. + However, they are strictly recommended if the device participates in the network with + intensive communication where the device receives a lot of requests that require responses. + +if BT_MESH_ACCESS_DELAYABLE_MSG + +config BT_MESH_ACCESS_DELAYABLE_MSG_CTX_ENABLED + bool "The delayable message in the notification message context" + default y + help + Controls whether the delayable message feature is enabled by default in + the message context of the opcode notifications. This allows the server part of any + model to not bother about additional context configuration to enable the delayable message. + Note that if this is disabled then all foundation models stop using the delayable message + functionality. + +config BT_MESH_ACCESS_DELAYABLE_MSG_COUNT + int "Number of simultaneously delayed messages" + default 4 + help + The maximum number of messages the Access layer can manage to delay + at the same time. The number of messages can be handled only if the Access layer + has a sufficient amount of memory to store the model payload for them. + +config BT_MESH_ACCESS_DELAYABLE_MSG_CHUNK_SIZE + int "Maximum delayable message storage chunk" + default 10 + help + Size of memory that Access layer uses to split model message to. It allocates + a sufficient number of these chunks from the pool to store the full model payload. + +config BT_MESH_ACCESS_DELAYABLE_MSG_CHUNK_COUNT + int "Maximum number of available chunks" + default 40 + help + The maximum number of available chunks the Access layer allocates to store model payload. + It is recommended to keep chunk size equal to the reasonable small value to prevent + unnecessary memory wasting. + +endif # BT_MESH_ACCESS_DELAYABLE_MSG + +config BT_MESH_DELAYABLE_PUBLICATION + bool "Delayable publication" + default y + help + When enabled, the periodic publications are randomly delayed by 20 to 50ms. Publications + triggered at the start of the stack or by the bt_mesh_model_publish() call are delayed by + 20 to 500ms. This option reduces the probability of collisions when multiple nodes publish + at the same time. + +endmenu # Access layer + +menu "Models" + +config BT_MESH_CFG_CLI + bool "Support for Configuration Client Model" + help + Enable support for the configuration client model. + +if BT_MESH_CFG_CLI + +config BT_MESH_CFG_CLI_TIMEOUT + int "Config Client model timeout in milliseconds" + default 5000 + help + This timeout controls how long config client waits for a response + message to arrive. This value can be changed at runtime using + @ref bt_mesh_cfg_cli_timeout_set. + +endif # BT_MESH_CFG_CLI + +config BT_MESH_HEALTH_CLI + bool "Support for Health Client Model" + help + Enable support for the health client model. + +if BT_MESH_HEALTH_CLI + +config BT_MESH_HEALTH_CLI_TIMEOUT + int "Health Client model timeout in milliseconds" + default 2000 + help + This timeout controls how long health client waits for a response + message to arrive. This value can be changed at runtime using + @ref bt_mesh_health_cli_timeout_set. + +endif # BT_MESH_HEALTH_CLI + +menuconfig BT_MESH_BLOB_SRV + bool "Support for BLOB Transfer Server model" + help + Enable the Binary Large Object (BLOB) Transfer Server model. + +if BT_MESH_BLOB_SRV + +config BT_MESH_BLOB_SRV_PULL_REQ_COUNT + int "Number of chunks to request for each pull" + default 4 + range 1 16 + help + In Pull mode (Pull BLOB Transfer Mode), the BLOB Transfer Server + requests a fixed number of chunks from the Client. Use this option to + control the chunk count in the request. If the BLOB Transfer Server + is instantiated on a Low Power node, the pull request count will be + trimmed to not overflow the Friend queue. + +config BT_MESH_BLOB_SIZE_MAX + int "Largest BLOB size in bytes" + default 524288 + range 1 3257617792 + help + The maximum object size a BLOB Transfer Server can receive. + +config BT_MESH_BLOB_BLOCK_SIZE_MIN + int "Minimum block size" + default 4096 + range 64 1048576 # 2^6 - 2^20 + help + Minimum acceptable block size in a BLOB transfer. The transfer block + size will be some number that is a power of two, and is between block + size min and block size max. If no such number exists, a compile + time warning will be issued. + +config BT_MESH_BLOB_BLOCK_SIZE_MAX + int "Maximum block size" + default 4096 + range BT_MESH_BLOB_BLOCK_SIZE_MIN 1048576 + help + Maximum acceptable block size in a BLOB transfer. The transfer block + size will be some number that is a power of two, and is between block + size min and block size max. If no such number exists, a compile + time warning will be issued. + +config BT_MESH_BLOB_REPORT_TIMEOUT + int "Partial Block Report interval in Pull mode" + default 10 + range 1 31 + help + The timer value that Pull BLOB Transfer Server uses to report missed chunks. + +config BT_MESH_RX_BLOB_CHUNK_SIZE + depends on !BT_MESH_ALIGN_CHUNK_SIZE_TO_MAX_SEGMENT + int "BLOB Server chunk size" + default 8 + range 8 377 + help + Set the chunk size for the BLOB Server. + The actual maximum chunk size depends on how many segments are + possible and will be clamped to the max possible if set above. + see also: BT_MESH_RX_SEG_MAX, + and the maximum SDU a node can receive. + +endif # BT_MESH_BLOB_SRV + +menuconfig BT_MESH_BLOB_CLI + bool "Support for BLOB Transfer Client model" + help + Enable the Binary Large Object (BLOB) Transfer Client model. + +if BT_MESH_BLOB_CLI + +config BT_MESH_BLOB_CLI_BLOCK_RETRIES + int "Number of retries per block" + default 5 + help + Controls the number of times the client will attempt to resend missing + chunks to the BLOB receivers for every block. + +config BT_MESH_TX_BLOB_CHUNK_SIZE + depends on !BT_MESH_ALIGN_CHUNK_SIZE_TO_MAX_SEGMENT + int "BLOB Client chunk size" + default 8 + range 1 377 + help + Set the chunk size for the BLOB Client. + The actual maximum chunk size depends on how many segments are + possible and will be clamped to the max possible if set above. + see also: BT_MESH_TX_SEG_MAX, + and the maximum SDU a node can receive. + +config BT_MESH_TX_BLOB_CHUNK_SEND_INTERVAL + int "BLOB Client chunk send interval" + default 0 + range 0 2147483647 + help + Set the interval in milliseconds in which chunks are sent during the BLOB transfer. + Note: Without a delay between each sent chunk, the network might become too busy with the + BLOB transfer for other communications to succeed. + Note: Timing restrictions, like the timeout base, should be considered or changed + accordingly when setting this interval. Otherwise, the interval might be too big for the + timeout settings and cause timeouts. + +endif # BT_MESH_BLOB_CLI + +menu "BLOB models common configuration" + visible if BT_MESH_BLOB_SRV || BT_MESH_BLOB_CLI + +config BT_MESH_ALIGN_CHUNK_SIZE_TO_MAX_SEGMENT + bool "Align chunk size to max segmented message size" + default y + +config BT_MESH_BLOB_CHUNK_COUNT_MAX + int "Maximum chunk count per block" + default 256 + range 1 2992 + help + A BLOB transfer contains several blocks. Each block is made up of + several chunks. This option controls the maximum chunk count per + block. + +endmenu + +config BT_MESH_BLOB_IO_FLASH + bool "BLOB flash stream" + default y + depends on BT_MESH_BLOB_SRV || BT_MESH_BLOB_CLI + help + Enable the BLOB flash stream API stub. The NuttX Bluetooth port does + not provide a Zephyr flash map backend. + +config BT_MESH_DFU_SRV + bool "Support for Firmware Update Server model" + depends on BT_MESH_MODEL_EXTENSIONS + depends on BT_MESH_BLOB_SRV + help + Enable the Firmware Update Server model. + +config BT_MESH_DFU_CLI + bool "Support for Firmware Update Client model" + depends on BT_MESH_MODEL_EXTENSIONS + depends on BT_MESH_BLOB_CLI + help + Enable the Firmware Update Client model. + +menu "Firmware Update model configuration" + visible if BT_MESH_DFU_SRV || BT_MESH_DFU_CLI + +config BT_MESH_DFU_FWID_MAXLEN + int "DFU FWID max length" + default 106 + range 0 106 + help + This value defines the maximum length of the vendor-specific version information + that the mesh stack can manage, describing the image. Please note that + only 106 octets size allows the device to be compliant with + the Bluetooth Mesh DFU specification. If smaller than 106 octets are set, + then the device will fail with managing firmware images that have + a longer vendor-specific version information. + +config BT_MESH_DFU_METADATA_MAXLEN + int "DFU metadata max length" + default 32 + range 18 255 if BT_MESH_DFU_METADATA + range 0 255 + help + This value defines the maximum length of an image's metadata. + +config BT_MESH_DFU_METADATA + bool "Support for the default metadata format" + help + This option adds a set of functions that can be used to encode and decode a firmware + metadata using the format defined in the Bluetooth Mesh DFU subsystem. + +config BT_MESH_DFU_URI_MAXLEN + int "DFU URI max length" + default 32 + range 0 255 + help + This value defines the maximum length of an image's URI, not including + a string terminator. + +endmenu # Firmware Update model configuration + +menuconfig BT_MESH_DFU_SLOTS + bool "Firmware image slot manager" + default y if BT_MESH_DFU_CLI + help + Enable the DFU image slot manager, for managing firmware distribution slots + for the Firmware Update Client model. + +if BT_MESH_DFU_SLOTS + +config BT_MESH_DFU_SLOT_CNT + int "Number of firmware image slots" + default 1 + range 1 32767 + help + This value defines the number of firmware slots the DFU image slot manager + can keep simultaneously. + +endif + +menuconfig BT_MESH_DFD_SRV + bool "Support for Firmware Distribution Server model" + depends on BT_MESH_BLOB_SRV + depends on BT_MESH_DFU_CLI + help + Enable the Firmware Distribution Server model. + +if BT_MESH_DFD_SRV + +config BT_MESH_DFD_SRV_SLOT_MAX_SIZE + int "Largest DFU image that can be stored" + default BT_MESH_BLOB_SIZE_MAX + range 0 BT_MESH_BLOB_SIZE_MAX + help + This value defines the largest DFU image a single slot can store. + +config BT_MESH_DFD_SRV_SLOT_SPACE + int "Total DFU image storage space" + default BT_MESH_DFD_SRV_SLOT_MAX_SIZE + range 0 4294967295 + help + This value defines the total storage space dedicated to storing DFU + images on the Firmware Distribution Server. + +config BT_MESH_DFD_SRV_TARGETS_MAX + int "Maximum Target node count" + default 8 + range 1 65535 + help + This value defines the maximum number of Target nodes the Firmware + Distribution Server can target simultaneously. + +config BT_MESH_DFD_SRV_OOB_UPLOAD + bool "Support for DFU image OOB upload" + help + This enables support for OOB upload of firmware images for + distribution. This makes several callbacks and use of the init + macro BT_MESH_DFD_SRV_INIT_OOB mandatory. See the API documentation + for bt_mesh_dfd_srv_cb for details about the mandatory callbacks. + +endif + +config BT_MESH_RPR_SRV + bool "Support for Remote Provisioning Server model" + help + The Remote Provisioning Server is the proxy for a provisioning + process, allowing provisioners to tunnel their provisioning + messages through the mesh to the Remote Provisioning Server, which + communicates directly with the unprovisioned node. + +config BT_MESH_RPR_CLI + bool "Support for Remote Provisioning Client model" + depends on BT_MESH_PROVISIONER + help + The Remote Provisioning Client is instantiated on the provisioner + node, and allows provisioning of new devices through the mesh network + by tunnelling provisioning messages to a Remote Provisioning Server. + +menu "Remote Provisioning configuration" + visible if BT_MESH_RPR_SRV || BT_MESH_RPR_CLI + +config BT_MESH_RPR_AD_TYPES_MAX + int "Max AD types in extended scanning" + default 1 + range 1 16 + help + During extended scanning, the Remote Provisioning Server can include + a set of AD types in the scan reports, collected from the + unprovisioned device's advertisement data. This option controls + the highest number of AD types a single server can scan for, and a + Client can request. + +config BT_MESH_RPR_SRV_SCANNED_ITEMS_MAX + int "Max scannable unprovisioned devices for Remote Provisioning Server" + default 4 + range 4 255 + help + Max number of unique unprovisioned devices a single Remote + Provisioning Server can hold. + +config BT_MESH_RPR_SRV_AD_DATA_MAX + int "Max additional advertisement data to report" + default 31 + range 3 255 + help + Buffer size for the additional advertisement data reported during + extended scanning. + +endmenu # Remote Provisioning configuration + +config BT_MESH_SAR_CFG + bool + +config BT_MESH_SAR_CFG_SRV + bool "Support for SAR Configuration Server model" + select BT_MESH_SAR_CFG + help + Enable support for the SAR configuration server model. + +config BT_MESH_SAR_CFG_CLI + bool "Support for SAR Configuration Client Model" + select BT_MESH_SAR_CFG + help + Enable support for the SAR configuration client model. + +config BT_MESH_OP_AGG + bool + +config BT_MESH_OP_AGG_SRV + bool "Support for Opcode Aggregator Server Model" + select BT_MESH_OP_AGG + help + Enable support for the Opcode Aggregator Server model. + +config BT_MESH_OP_AGG_CLI + bool "Support for Opcode Aggregator Client Model" + select BT_MESH_OP_AGG + help + Enable support for the Opcode Aggregator Client model. + +if BT_MESH_OP_AGG_CLI + +config BT_MESH_OP_AGG_CLI_TIMEOUT + int "Opcodes Aggregator Client model timeout in milliseconds" + default 10000 + help + This timeout controls how long Opcodes Aggregator Client waits + for a response message to arrive. This value can be changed at + runtime using @ref bt_mesh_op_agg_cli_timeout_set. + +endif # BT_MESH_OP_AGG_CLI + +config BT_MESH_LARGE_COMP_DATA_SRV + bool "Support for Large Composition Data Server Model" + depends on BT_MESH_MODEL_EXTENSIONS + help + Enable support for the Large Composition Data Server model. + +if BT_MESH_LARGE_COMP_DATA_SRV + +config BT_MESH_MODELS_METADATA_PAGE_LEN + int "Maximum length of the Models Metadata Page" + default 150 + help + This value is the combined total metadata length for + all models on the device. + +endif # BT_MESH_LARGE_COMP_DATA_SRV + +config BT_MESH_LARGE_COMP_DATA_CLI + bool "Support for Large Composition Data Client model" + help + Enable support for the Large Composition Data Client model. + +if BT_MESH_PRIV_BEACONS + +config BT_MESH_PRIV_BEACON_SRV + bool "Support for Private Beacon Server Model" + depends on BT_MESH_MODEL_EXTENSIONS + help + Enable support for the Private Beacon Server model. + +config BT_MESH_PRIV_BEACON_CLI + bool "Support for Private Beacon Client Model" + help + Enable support for the Private Beacon Client model. + +endif # BT_MESH_PRIV_BEACONS + +config BT_MESH_OD_PRIV_PROXY_CLI + bool "Support for On-Demand Private Proxy Client model" + help + On-Demand Private Proxy Client allows to configure and check the state + of On-Demand Private Proxy Servers. The state determines if the peers will + advertise the Private Network Identity type after receiving a Solicitation PDU. + + +config BT_MESH_OD_PRIV_PROXY_CLI_TIMEOUT + int "Solicitation PDU RPL Configuration Client model timeout in milliseconds" + default 5000 + depends on BT_MESH_OD_PRIV_PROXY_CLI + help + This timeout controls how long the On-Demand Private Proxy Client waits + for a response message to arrive. This value can be changed at runtime + using @ref bt_mesh_od_priv_proxy_cli_timeout_set. + +config BT_MESH_OD_PRIV_PROXY_SRV + bool "Support for On-Demand Private Proxy Server model" + depends on BT_MESH_PRIV_BEACON_SRV + select BT_MESH_SOLICITATION + help + The On-Demand Private Proxy Server is used to support configuration of + advertising with Private Network Identity type of a node. + When enabled, the Solicitation PDU RPL Configuration Server model is also enabled. + +config BT_MESH_PROXY_SRPL_SIZE + int "Size of solicitation replay protection list (SRPL)" + depends on BT_MESH_OD_PRIV_PROXY_SRV + default 10 + range 1 255 + help + Size of SRPL. The list is used to determine if a received Solicitation PDU + is valid. It is valid when the SSRC field value of the received Solicitation PDU + is stored in the SRPL and the SSEQ field value is bigger than the corresponding + stored SSEQ value, or if the SSRC was not stored in the RPL and the SRPL still has + space for new entries. + +config BT_MESH_SOL_PDU_RPL_CLI + bool "Support for Solicitation PDU RPL Configuration Client model" + help + The Solicitation PDU RPL Configuration Client is used to support the + functionality of removing addresses from the solicitation replay + protection list (SRPL) of a node that supports the Solicitation + PDU RPL Configuration Server model. + +config BT_MESH_SOL_PDU_RPL_CLI_TIMEOUT + int "Solicitation PDU RPL Configuration Client model timeout in milliseconds" + default 5000 + depends on BT_MESH_SOL_PDU_RPL_CLI + help + This timeout controls how long Solicitation PDU RPL Configuration Client waits + for a response message to arrive. This value can be changed at runtime + using @ref bt_mesh_sol_pdu_rpl_cli_timeout_set. + +config BT_MESH_BRG_CFG_SRV + bool "Support for Bridge Configuration Server model" + depends on BT_MESH_MODEL_EXTENSIONS + help + The Bridge Configuration Server model is used to support the configuration + of the subnet bridge functionality of a node. + +menu "Subnet Bridge configuration" + visible if BT_MESH_BRG_CFG_SRV + +config BT_MESH_BRG_TABLE_ITEMS_MAX + int "Maximum number of entries in the bridging table" + default 16 + range 16 255 + help + The maximum number of entries in the bridging table. + +endmenu + +config BT_MESH_BRG_CFG_CLI + bool "Support for Bridge Configuration Client model" + help + The Bridge Configuration Client is used to support the functionality of a + node that can configure the subnet bridge functionality of another node. + +if BT_MESH_BRG_CFG_CLI + +config BT_MESH_BRG_CFG_CLI_TIMEOUT + int "Bridge Configuration Client model timeout in milliseconds" + default 5000 + help + This timeout controls how long the bridge configuration client waits for a + response message to arrive. This value can be changed at runtime using + @ref bt_mesh_brg_cfg_cli_timeout_set. + +endif # BT_MESH_BRG_CFG_CLI + +endmenu # Models + +menu "Proxy" + visible if BT_CONN + +menuconfig BT_MESH_GATT_PROXY + bool "GATT Proxy Service support" + select BT_MESH_GATT_SERVER + select BT_MESH_PROXY + help + This option enables support for the Mesh GATT Proxy Service, + i.e. the ability to act as a proxy between a Mesh GATT Client + and a Mesh network. + +if BT_MESH_GATT_PROXY + +config BT_MESH_GATT_PROXY_ENABLED + bool "GATT Proxy enabled" + depends on BT_MESH_GATT_PROXY + default y + help + Controls whether the GATT Proxy feature is enabled by default. + Can be changed through runtime configuration. + +config BT_MESH_NODE_ID_TIMEOUT + int "Node Identity advertising timeout" + range 1 60 + default 60 + help + This option determines for how long the local node advertises + using Node Identity. The given value is in seconds. The + specification limits this to 60 seconds, and implies that to + be the appropriate value as well, so just leaving this as the + default is the safest option. + +config BT_MESH_PROXY_USE_DEVICE_NAME + bool "Include Bluetooth device name in scan response" + help + This option includes GAP device name in scan response when + the GATT Proxy feature is enabled. + +config BT_MESH_PROXY_FILTER_SIZE + int "Maximum number of filter entries per Proxy Client" + default 16 + range 1 32767 + help + This option specifies how many Proxy Filter entries the local + node supports. This helps in reducing unwanted traffic getting sent to + the proxy client. This value is application specific and should be large + enough so that proxy client can communicate with several devices through + this proxy server node using the default accept list filter type. + +endif # BT_MESH_GATT_PROXY + +config BT_MESH_PROXY_CLIENT + bool "Proxy client support" + select BT_GATT_CLIENT + select BT_MESH_GATT_CLIENT + depends on BT_CENTRAL + help + This option enables support for the Mesh GATT Proxy Client, + i.e. the ability to act as a proxy between a Mesh GATT Service + and a Mesh network. + +config BT_MESH_SOLICITATION + bool + +config BT_MESH_PROXY_SOLICITATION + bool "Proxy solicitation feature" + select BT_MESH_SOLICITATION + help + This option enables support for sending Solicitation PDUs. + +config BT_MESH_SOL_ADV_XMIT + int "Solicitation PDU retransmission count" + depends on BT_MESH_PROXY_SOLICITATION + range 0 10 + default 2 + help + How many times Solicitation PDU advertisements will be repeated. 0 means that there will be + 1 transmission without retransmissions. + +endmenu # Proxy + +config BT_MESH_SECURE_STORAGE + bool + depends on SECURE_STORAGE + +config BT_MESH_CRYPTO_LIB + bool + default y + select PSA_CRYPTO + select PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_IMPORT + select PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_EXPORT + select PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_GENERATE + select PSA_WANT_KEY_TYPE_ECC_KEY_PAIR_DERIVE + select PSA_WANT_ALG_CMAC + select PSA_WANT_ALG_ECB_NO_PADDING + select PSA_WANT_KEY_TYPE_AES + select PSA_WANT_ALG_CCM + select PSA_WANT_KEY_TYPE_HMAC + select PSA_WANT_ALG_HMAC + select PSA_WANT_ALG_SHA_256 + select PSA_WANT_ALG_ECDH + select PSA_WANT_ECC_SECP_R1_256 + select BT_MESH_SECURE_STORAGE if BT_SETTINGS && PSA_CRYPTO_PROVIDER_MBEDTLS + imply MBEDTLS_AES_ROM_TABLES if PSA_CRYPTO_PROVIDER_MBEDTLS + help + Crypto library support for mesh security. + +menu "Beacons" + +config BT_MESH_BEACON_ENABLED + bool "Secure network beacon enabled" + default y + help + Controls whether the Secure network beacon feature is enabled by + default. Can be changed through runtime configuration. + +config BT_MESH_PRIV_BEACONS + bool "Support for private beacons" + default y + help + Enable support for private beacons. + +endmenu # Beacons + +menu "IV Index & Sequence number" + +config BT_MESH_IV_UPDATE_TEST + bool "Test the IV Update Procedure" + help + This option removes the 96 hour limit of the IV Update + Procedure and lets the state be changed at any time. + +config BT_MESH_IV_UPDATE_SEQ_LIMIT + hex "Sequence number limit to start iv update" + default 0x800000 + range 0x000001 0xFFFFFE + help + This option specifies the sequence number value to start iv update. + +config BT_MESH_IVU_DIVIDER + int "Divider for IV Update state refresh timer" + default 4 + range 2 96 + help + When the IV Update state enters Normal operation or IV Update + in Progress, we need to keep track of how many hours has passed + in the state, since the specification requires us to remain in + the state at least for 96 hours (Update in Progress has an + additional upper limit of 144 hours). + + In order to fulfill the above requirement, even if the node might + be powered off once in a while, we need to store persistently + how many hours the node has been in the state. This doesn't + necessarily need to happen every hour (thanks to the flexible + duration range). The exact cadence will depend a lot on the + ways that the node will be used and what kind of power source it + has. + + Since there is no single optimal answer, this configuration + option allows specifying a divider, i.e. how many intervals + the 96 hour minimum gets split into. After each interval the + duration that the node has been in the current state gets + stored to flash. E.g. the default value of 4 means that the + state is saved every 24 hours (96 / 4). + +endmenu # IV Index & Sequence number + +menuconfig BT_MESH_LOW_POWER + bool "Support for Low Power features" + help + Enable this option to be able to act as a Low Power Node. + +if BT_MESH_LOW_POWER + +config BT_MESH_LPN_ESTABLISHMENT + bool "Perform Friendship establishment using low power" + default y + help + Perform the Friendship establishment using low power, with + the help of a reduced scan duty cycle. The downside of this + is that the node may miss out on messages intended for it + until it has successfully set up Friendship with a Friend + node. + +config BT_MESH_LPN_OFFER_WAIT_TIMEOUT + int "Time to wait for Friend Offers after a Friend Request" + default 1000 + range 100 10000 + help + Time in milliseconds that the Low Power node waits for Friend Offer + messages, starting 100 ms after the Friend Request was sent. The + Bluetooth Mesh Protocol specification recommends listening for up to + 1 second. When BT_MESH_LPN_ESTABLISHMENT is enabled, this is also the + duration for which the scanner is kept enabled. If no acceptable + Friend Offer is received, the next Friend Request is sent after + BT_MESH_LPN_RETRY_TIMEOUT. + +config BT_MESH_LPN_AUTO + bool "Automatically start looking for Friend nodes once provisioned" + default y + help + Automatically enable LPN functionality once provisioned and start + looking for Friend nodes. If this option is disabled LPN mode + needs to be manually enabled by calling bt_mesh_lpn_set(true). + +config BT_MESH_LPN_AUTO_TIMEOUT + int "Time from last received message before going to LPN mode" + default 15 + range 0 3600 + depends on BT_MESH_LPN_AUTO + help + Time in seconds from the last received message, that the node + will wait before starting to look for Friend nodes. + +config BT_MESH_LPN_RETRY_TIMEOUT + int "Retry timeout for Friend requests" + default 8 + range 1 3600 + help + Time in seconds between Friend Requests, if a previous Friend + Request did not receive any acceptable Friend Offers. + +config BT_MESH_LPN_RSSI_FACTOR + int "RSSIFactor, used in the Friend Offer Delay calculation" + range 0 3 + default 0 + help + The contribution of the RSSI measured by the Friend node used + in Friend Offer Delay calculations. 0 = 1, 1 = 1.5, 2 = 2, 3 = 2.5. + +config BT_MESH_LPN_RECV_WIN_FACTOR + int "ReceiveWindowFactor, used in the Friend Offer Delay calculation" + range 0 3 + default 0 + help + The contribution of the supported Receive Window used in + Friend Offer Delay calculations. 0 = 1, 1 = 1.5, 2 = 2, 3 = 2.5. + +config BT_MESH_LPN_MIN_QUEUE_SIZE + int "Minimum size of acceptable friend queue (MinQueueSizeLog)" + range 1 7 + default 1 + help + The MinQueueSizeLog field is defined as log_2(N), where N is + the minimum number of maximum size Lower Transport PDUs that + the Friend node can store in its Friend Queue. As an example, + MinQueueSizeLog value 1 gives N = 2, and value 7 gives N = 128. + +config BT_MESH_LPN_RECV_DELAY + int "Receive delay requested by the local node" + range 50 255 if BT_MESH_ADV_LEGACY + range 10 255 + default 100 + help + The ReceiveDelay is the time between the Low Power node + sending a request and listening for a response. This delay + allows the Friend node time to prepare the response. The value + is in units of milliseconds. When BT_MESH_ADV_LEGACY is used, + the minimal value for the delay can not be less than 50ms due + to a limitation in the legacy advertiser implementation. + +config BT_MESH_LPN_POLL_TIMEOUT + int "The value of the PollTimeout timer" + range 10 244735 + default 300 + help + PollTimeout timer is used to measure time between two + consecutive requests sent by the Low Power node. If no + requests are received by the Friend node before the + PollTimeout timer expires, then the friendship is considered + terminated. The value is in units of 100 milliseconds, so e.g. + a value of 300 means 30 seconds. + +config BT_MESH_LPN_INIT_POLL_TIMEOUT + int "The starting value of the PollTimeout timer" + range 10 BT_MESH_LPN_POLL_TIMEOUT + default BT_MESH_LPN_POLL_TIMEOUT + help + The initial value of the PollTimeout timer when Friendship + gets established for the first time. After this the timeout + will gradually grow toward the actual PollTimeout, doubling + in value for each iteration. The value is in units of 100 + milliseconds, so e.g. a value of 300 means 30 seconds. + +config BT_MESH_LPN_SCAN_LATENCY + int "Latency for enabling scanning" + range 0 50 + default 15 + help + Latency in milliseconds that it takes to enable scanning. This + is in practice how much time in advance before the Receive Window + that scanning is requested to be enabled. + +config BT_MESH_LPN_GROUPS + int "Number of groups the LPN can subscribe to" + range 0 16384 + default 8 + help + Maximum number of groups that the LPN can subscribe to. + +config BT_MESH_LPN_SUB_ALL_NODES_ADDR + bool "Automatically subscribe all nodes address" + help + Automatically subscribe all nodes address when friendship + established. + +endif # BT_MESH_LOW_POWER + +menuconfig BT_MESH_FRIEND + bool "Support for acting as a Friend Node" + help + Enable this option to be able to act as a Friend Node. + +if BT_MESH_FRIEND + +config BT_MESH_FRIEND_ENABLED + bool "Friend feature enabled by default" + default y + help + Controls whether the Friend feature is enabled by default when the + device boots up for the first time or unprovisioned. Can be changed + at runtime using bt_mesh_friend_set() function. + +config BT_MESH_FRIEND_RECV_WIN + int "Friend Receive Window" + range 1 255 + default 255 + help + Receive Window in milliseconds supported by the Friend node. + +config BT_MESH_FRIEND_QUEUE_SIZE + int "Minimum number of buffers supported per Friend Queue" + range 2 65536 + default 16 + help + Minimum number of buffers available to be stored for each + local Friend Queue. + +config BT_MESH_FRIEND_SUB_LIST_SIZE + int "Friend Subscription List Size" + range 0 1023 + default 3 + help + Size of the Subscription List that can be supported by a + Friend node for a Low Power node. + +config BT_MESH_FRIEND_LPN_COUNT + int "Number of supported LPN nodes" + range 1 1000 + default 2 + help + Number of Low Power Nodes the Friend can have a Friendship + with simultaneously. + +config BT_MESH_FRIEND_SEG_RX + int "Number of incomplete segment lists per LPN" + range 1 1000 + default 1 + help + Number of incomplete segment lists that we track for each LPN + that we are Friends for. In other words, this determines how + many elements we can simultaneously be receiving segmented + messages from when the messages are going into the Friend queue. + +config BT_MESH_FRIEND_ADV_LATENCY + int "Latency for enabling advertising" + range 0 10 + default 0 + help + Latency in milliseconds between request for and start of Friend + advertising. Used to tune the ReceiveDelay, making Friend + start sending a message earlier thus compensating for the time between + pushing the message to the Bluetooth host and the actual advertising + start. + +endif # BT_MESH_FRIEND + +menu "Capabilities" + +config BT_MESH_SUBNET_COUNT + int "Maximum number of mesh subnets per network" + default 1 + range 1 4096 + help + This option specifies how many subnets a Mesh network can + participate in at the same time. + +config BT_MESH_APP_KEY_COUNT + int "Maximum number of application keys per network" + default 1 + range 1 4096 + help + This option specifies how many application keys the device can + store per network. + +config BT_MESH_MODEL_KEY_COUNT + int "Maximum number of application keys per model" + default 1 + range 1 4096 + help + This option specifies how many application keys each model can + at most be bound to. + +config BT_MESH_MODEL_GROUP_COUNT + int "Maximum number of group address subscriptions per model" + default 1 + range 1 4096 + help + This option specifies how many group addresses each model can + at most be subscribed to. + +config BT_MESH_LABEL_COUNT + int "Maximum number of Label UUIDs used for Virtual Addresses" + default 1 + range 0 4096 + help + This option specifies how many Label UUIDs can be stored. + +endmenu # Capabilities + +menu "Persistent storage" + visible if BT_SETTINGS + +config BT_MESH_STORE_TIMEOUT + int "Delay (in seconds) before storing anything persistently" + range 0 1000000 + default 2 + help + This value defines in seconds how soon any pending changes + are actually written into persistent storage (flash) after + a change occurs. + +config BT_MESH_SEQ_STORE_RATE + int "How often the sequence number gets updated in storage" + range 0 1000000 + default 128 + default 1 if !BT_SETTINGS # To keep compiling the code + help + This value defines how often the local sequence number gets + updated in persistent storage (i.e. flash). E.g. a value of 100 + means that the sequence number will be stored to flash on every + 100th increment. If the node sends messages very frequently a + higher value makes more sense, whereas if the node sends + infrequently a value as low as 0 (update storage for every + increment) can make sense. When the stack gets initialized it + will add this number to the last stored one, so that it starts + off with a value that's guaranteed to be larger than the last + one used before power off. + +if BT_MESH_RPL_STORAGE_MODE_SETTINGS && BT_SETTINGS + +config BT_MESH_RPL_STORE_TIMEOUT + int "Minimum interval after which unsaved RPL and SRPL entries are updated in the settings subsystem" + range -1 1000000 + default 5 + help + This value defines time in seconds until unsaved RPL and SRPL entries + are written to the persistent storage. Setting this value + to a large number may lead to security vulnerabilities if a node + gets powered off before the timer is fired. When flash is used + as the persistent storage setting this value to a low number + may wear out flash sooner or later. However, if the RPL gets + updated infrequently a value as low as 0 (write immediately) + may make sense. Setting this value to -1 will disable this timer. + In this case, a user is responsible to store pending RPL entries + using @ref bt_mesh_rpl_pending_store. In the mean time, when + IV Index is updated, the outdated RPL entries will still be + stored by @ref BT_MESH_STORE_TIMEOUT. Finding the right balance + between this timeout and calling @ref bt_mesh_rpl_pending_store + may reduce a risk of security vulnerability and flash wear out. + Failure to store the RPL and becoming vulnerable after reboot + will cause the device to not perform the replay protection + required by the spec. + +endif # BT_MESH_RPL_STORAGE_MODE_SETTINGS && BT_SETTINGS + +config BT_MESH_SETTINGS_WORKQ + bool "Store the Bluetooth Mesh settings in a separate work queue" + default y + help + This option enables a separate cooperative thread which is used to + store Bluetooth Mesh configuration. When this option is disabled, + the stack's configuration is stored in the system workqueue. This + means that the system workqueue will be blocked for the time needed + to store the pending data. This time may significantly increase if + the flash driver does the erase operation. Enabling this option + allows Bluetooth Mesh not to block the system workqueue, and thus + process the incoming and outgoing messages while the flash driver + waits for the controller to allocate the time needed to write the + data and/or erase the required flash pages. + +if BT_MESH_SETTINGS_WORKQ + +config BT_MESH_SETTINGS_WORKQ_PRIO + int + default 1 + +config BT_MESH_SETTINGS_WORKQ_STACK_SIZE + int "Stack size of the settings workq" + default 1200 if BT_MESH_RPR_SRV + default 900 + help + Size of the settings workqueue stack. + +endif # BT_MESH_SETTINGS_WORKQ + +endmenu # Persistent storage + +if BT_CONN + +config BT_MESH_MAX_CONN + int "Maximum number of simultaneous Bluetooth connections" + default BT_MAX_CONN + range 1 BT_MAX_CONN + help + Maximum number of simultaneous Bluetooth connections that the Bluetooth + mesh stack can use. + +endif # BT_CONN + +config BT_MESH_SELF_TEST + bool "Perform self-tests" + help + This option adds extra self-tests which are run every time + mesh networking is initialized. + +config BT_MESH_STATISTIC + bool "Mesh communication statistics [EXPERIMENTAL]" + help + The module gathers statistics of received, relayed, and transmitted + frames. This helps to estimate the quality of the communication and + the sufficiency of configured advertiser instances. + + When LPN functionality is enabled, the module also measures + LPN friendship timing parameters (ReceiveDelay, ReceiveWindow) by + timestamping protocol events during each poll cycle. + +endif # BT_MESH diff --git a/libs/zblue/mesh/Make.defs b/libs/zblue/mesh/Make.defs new file mode 100644 index 0000000000000..7fbdee29ba306 --- /dev/null +++ b/libs/zblue/mesh/Make.defs @@ -0,0 +1,197 @@ +############################################################################ +# libs/zblue/mesh/Make.defs +# +# Author: chao an +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +ifeq ($(CONFIG_BT_MESH),y) + +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/main.c $(TOPDIR)/libs/zblue/mesh/cfg.c +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/adv.c $(TOPDIR)/libs/zblue/mesh/beacon.c +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/net.c $(TOPDIR)/libs/zblue/mesh/subnet.c +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/app_keys.c +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/heartbeat.c +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/crypto.c +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/crypto_psa.c +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/access.c $(TOPDIR)/libs/zblue/mesh/msg.c +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/cfg_srv.c +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/health_srv.c +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/va.c +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/transport.c + +ifeq ($(CONFIG_BT_MESH_ADV_LEGACY),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/adv_legacy.c +endif +ifeq ($(CONFIG_BT_MESH_ADV_EXT),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/adv_ext.c +endif +ifeq ($(CONFIG_BT_SETTINGS),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/settings.c +endif +ifeq ($(CONFIG_BT_MESH_RPL_STORAGE_MODE_SETTINGS),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/rpl.c +endif +ifeq ($(CONFIG_BT_MESH_LOW_POWER),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/lpn.c +endif +ifeq ($(CONFIG_BT_MESH_FRIEND),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/friend.c +endif +ifeq ($(CONFIG_BT_MESH_PROV),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/prov.c +endif +ifeq ($(CONFIG_BT_MESH_PROVISIONEE),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/provisionee.c +endif +ifeq ($(CONFIG_BT_MESH_PROVISIONER),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/provisioner.c +endif +ifeq ($(CONFIG_BT_MESH_PB_ADV),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/pb_adv.c +endif +ifeq ($(CONFIG_BT_MESH_PB_GATT_COMMON),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/pb_gatt.c +endif +ifeq ($(CONFIG_BT_MESH_PB_GATT),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/pb_gatt_srv.c +endif +ifeq ($(CONFIG_BT_MESH_PB_GATT_CLIENT),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/pb_gatt_cli.c +endif +ifeq ($(CONFIG_BT_MESH_GATT_CLIENT),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/gatt_cli.c +endif +ifeq ($(CONFIG_BT_MESH_PROXY_CLIENT),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/proxy_cli.c +endif +ifeq ($(CONFIG_BT_MESH_GATT_PROXY),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/proxy_srv.c +endif +ifeq ($(CONFIG_BT_MESH_GATT),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/proxy_msg.c +endif +ifeq ($(CONFIG_BT_MESH_CFG_CLI),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/cfg_cli.c +endif +ifeq ($(CONFIG_BT_MESH_HEALTH_CLI),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/health_cli.c +endif +ifeq ($(CONFIG_BT_MESH_SAR_CFG_CLI),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/sar_cfg_cli.c +endif +ifeq ($(CONFIG_BT_MESH_SAR_CFG_SRV),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/sar_cfg_srv.c +endif +ifeq ($(CONFIG_BT_MESH_SAR_CFG),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/sar_cfg.c +endif +ifeq ($(CONFIG_BT_MESH_OP_AGG_SRV),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/op_agg_srv.c +endif +ifeq ($(CONFIG_BT_MESH_OP_AGG_CLI),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/op_agg_cli.c +endif +ifeq ($(CONFIG_BT_MESH_OP_AGG),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/op_agg.c +endif +ifeq ($(CONFIG_BT_MESH_LARGE_COMP_DATA_SRV),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/large_comp_data_srv.c +endif +ifeq ($(CONFIG_BT_MESH_LARGE_COMP_DATA_CLI),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/large_comp_data_cli.c +endif +ifeq ($(CONFIG_BT_MESH_PRIV_BEACON_SRV),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/priv_beacon_srv.c +endif +ifeq ($(CONFIG_BT_MESH_PRIV_BEACON_CLI),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/priv_beacon_cli.c +endif +ifeq ($(CONFIG_BT_MESH_SELF_TEST),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/test.c +endif +ifeq ($(CONFIG_BT_MESH_CDB),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/cdb.c +endif +ifeq ($(CONFIG_BT_MESH_BLOB_SRV),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/blob_srv.c +endif +ifeq ($(CONFIG_BT_MESH_BLOB_CLI),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/blob_cli.c +endif +ifeq ($(CONFIG_BT_MESH_BLOB_IO_FLASH),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/blob_io_flash.c +endif +ifeq ($(CONFIG_BT_MESH_DFU_CLI),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/dfu_cli.c +endif +ifeq ($(CONFIG_BT_MESH_DFU_SRV),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/dfu_srv.c +endif +ifeq ($(CONFIG_BT_MESH_DFD_SRV),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/dfd_srv.c +endif +ifeq ($(CONFIG_BT_MESH_DFU_SLOTS),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/dfu_slot.c +endif +ifeq ($(CONFIG_BT_MESH_DFU_METADATA),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/dfu_metadata.c +endif +ifeq ($(CONFIG_BT_MESH_RPR_CLI),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/rpr_cli.c +endif +ifeq ($(CONFIG_BT_MESH_RPR_SRV),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/rpr_srv.c +endif +ifeq ($(CONFIG_BT_MESH_OD_PRIV_PROXY_CLI),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/od_priv_proxy_cli.c +endif +ifeq ($(CONFIG_BT_MESH_OD_PRIV_PROXY_SRV),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/od_priv_proxy_srv.c +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/sol_pdu_rpl_srv.c +endif +ifeq ($(CONFIG_BT_MESH_SOL_PDU_RPL_CLI),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/sol_pdu_rpl_cli.c +endif +ifeq ($(CONFIG_BT_MESH_BRG_CFG_CLI),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/brg_cfg_cli.c +endif +ifeq ($(CONFIG_BT_MESH_BRG_CFG_SRV),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/brg_cfg_srv.c +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/brg_cfg.c +endif +ifeq ($(CONFIG_BT_MESH_SOLICITATION),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/solicitation.c +endif +ifeq ($(CONFIG_BT_MESH_STATISTIC),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/statistic.c +endif +ifeq ($(CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/delayable_msg.c +endif +ifeq ($(CONFIG_BT_TESTING),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/mesh/testing.c +endif + +VPATH += $(TOPDIR)/libs/zblue/mesh +DEPPATH += --dep-path $(TOPDIR)/libs/zblue/mesh +CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)/libs/zblue/mesh + +endif diff --git a/libs/zblue/mesh/access.c b/libs/zblue/mesh/access.c new file mode 100644 index 0000000000000..c8a6c373e0413 --- /dev/null +++ b/libs/zblue/mesh/access.c @@ -0,0 +1,3280 @@ +/**************************************************************************** + * libs/zblue/mesh/access.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "common/bt_str.h" + +#include "testing.h" + +#include "mesh.h" +#include "net.h" +#include "lpn.h" +#include "transport.h" +#include "access.h" +#include "foundation.h" +#include "op_agg.h" +#include "settings.h" +#include "va.h" +#include "delayable_msg.h" + +#define LOG_LEVEL CONFIG_BT_MESH_ACCESS_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_access); + +/* 20 - 50ms */ +#define RANDOM_DELAY_SHORT 30 +/* 20 - 500ms */ +#define RANDOM_DELAY_LONG 480 + +/* Model publication information for persistent storage. */ + +struct mod_pub_val +{ + struct + { + uint16_t addr; + uint16_t key; + uint8_t ttl; + uint8_t retransmit; + uint8_t period; + uint8_t period_div : 4; + uint8_t cred : 1; + } base; + uint16_t uuidx; +}; + +struct comp_foreach_model_arg +{ + struct net_buf_simple *buf; + size_t *offset; +}; + +static const struct bt_mesh_comp *dev_comp; +static const struct bt_mesh_comp2 *dev_comp2; +static uint16_t dev_primary_addr; +static void (*msg_cb)(uint32_t opcode, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf); + +/* Structure containing information about model extension */ + +struct mod_relation +{ + /** Element that composition data base model belongs to. */ + + uint8_t elem_base; + + /** Index of composition data base model in its element. */ + + uint8_t idx_base; + + /** Element that composition data extension model belongs to. */ + + uint8_t elem_ext; + + /** Index of composition data extension model in its element. */ + + uint8_t idx_ext; + /** Type of relation; value in range 0x00-0xFE marks correspondence and + * equals to Correspondence ID; value 0xFF marks extension + */ + + uint8_t type; +}; + +#ifdef CONFIG_BT_MESH_MODEL_EXTENSION_LIST_SIZE +#define MOD_REL_LIST_SIZE CONFIG_BT_MESH_MODEL_EXTENSION_LIST_SIZE +#else +#define MOD_REL_LIST_SIZE 0 +#endif + +/* List of all existing extension relations between models */ + +static struct mod_relation mod_rel_list[MOD_REL_LIST_SIZE]; + +#define MOD_REL_LIST_FOR_EACH(idx) \ + for ((idx) = 0; (idx) < ARRAY_SIZE(mod_rel_list) && \ + !(mod_rel_list[(idx)].elem_base == 0 && mod_rel_list[(idx)].idx_base == 0 && \ + mod_rel_list[(idx)].elem_ext == 0 && mod_rel_list[(idx)].idx_ext == 0); \ + (idx)++) + +#define IS_MOD_BASE(mod, idx, offset) \ + (mod_rel_list[(idx)].elem_base == mod->rt->elem_idx && \ + mod_rel_list[(idx)].idx_base == mod->rt->mod_idx + (offset)) + +#define IS_MOD_EXTENSION(mod, idx, offset) \ + (mod_rel_list[(idx)].elem_ext == mod->rt->elem_idx && \ + mod_rel_list[(idx)].idx_ext == mod->rt->mod_idx + (offset)) + +#define RELATION_TYPE_EXT 0xFF + +static const struct +{ + const char *path; + uint8_t page; +} + +comp_data_pages[] = +{ + { + "bt/mesh/cmp/0", + 0, + }, +#if defined(CONFIG_BT_MESH_COMP_PAGE_1) + { + "bt/mesh/cmp/1", + 1, + }, +#endif +#if defined(CONFIG_BT_MESH_COMP_PAGE_2) + { + "bt/mesh/cmp/2", + 2, + }, +#endif +}; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_mesh_model_foreach(void (*func)(const struct bt_mesh_model *mod, + const struct bt_mesh_elem *elem, + bool vnd, + bool primary, + void *user_data), + void *user_data) +{ + int i; + int j; + + for (i = 0; i < dev_comp->elem_count; i++) + { + const struct bt_mesh_elem *elem = &dev_comp->elem[i]; + + for (j = 0; j < elem->model_count; j++) + { + const struct bt_mesh_model *model = &elem->models[j]; + + func(model, elem, false, i == 0, user_data); + } + + for (j = 0; j < elem->vnd_model_count; j++) + { + const struct bt_mesh_model *model = &elem->vnd_models[j]; + + func(model, elem, true, i == 0, user_data); + } + } +} + +static size_t bt_mesh_comp_elem_size(const struct bt_mesh_elem *elem) +{ + return (4 + (elem->model_count * 2U) + (elem->vnd_model_count * 4U)); +} + +static uint8_t *data_buf_add_u8_offset(struct net_buf_simple *buf, + uint8_t val, size_t *offset) +{ + if (*offset >= 1) + { + *offset -= 1; + return NULL; + } + + return net_buf_simple_add_u8(buf, val); +} + +static void data_buf_add_le16_offset(struct net_buf_simple *buf, + uint16_t val, size_t *offset) +{ + if (*offset >= 2) + { + *offset -= 2; + return; + } + else if (*offset == 1) + { + *offset -= 1; + net_buf_simple_add_u8(buf, (val >> 8)); + } + else + { + net_buf_simple_add_le16(buf, val); + } +} + +static void data_buf_add_mem_offset( + struct net_buf_simple *buf, const uint8_t *data, size_t len, + size_t *offset) +{ + if (*offset >= len) + { + *offset -= len; + return; + } + + net_buf_simple_add_mem(buf, data + *offset, len - *offset); + *offset = 0; +} + +static void comp_add_model( + const struct bt_mesh_model *mod, const struct bt_mesh_elem *elem, bool vnd, + void *user_data) +{ + struct comp_foreach_model_arg *arg = user_data; + + if (vnd) + { + data_buf_add_le16_offset(arg->buf, mod->vnd.company, arg->offset); + data_buf_add_le16_offset(arg->buf, mod->vnd.id, arg->offset); + } + else + { + data_buf_add_le16_offset(arg->buf, mod->id, arg->offset); + } +} + +#if defined(CONFIG_BT_MESH_LARGE_COMP_DATA_SRV) + +static size_t metadata_model_size( + const struct bt_mesh_model *mod, const struct bt_mesh_elem *elem, bool vnd) +{ + const struct bt_mesh_models_metadata_entry *entry; + size_t size = 0; + + if (!mod->metadata) + { + return size; + } + + if (vnd) + { + size += sizeof(mod->vnd.company); + size += sizeof(mod->vnd.id); + } + else + { + size += sizeof(mod->id); + } + + size += sizeof(uint8_t); + + for (entry = mod->metadata; entry && entry->len; ++entry) + { + size += sizeof(entry->len) + sizeof(entry->id) + entry->len; + } + + return size; +} + +size_t bt_mesh_metadata_page_0_size(void) +{ + const struct bt_mesh_comp *comp; + size_t size = 0; + int i; + int j; + + comp = bt_mesh_comp_get(); + + for (i = 0; i < dev_comp->elem_count; i++) + { + const struct bt_mesh_elem *elem = &dev_comp->elem[i]; + + size += sizeof(elem->model_count) + sizeof(elem->vnd_model_count); + + for (j = 0; j < elem->model_count; j++) + { + const struct bt_mesh_model *model = &elem->models[j]; + + size += metadata_model_size(model, elem, false); + } + + for (j = 0; j < elem->vnd_model_count; j++) + { + const struct bt_mesh_model *model = &elem->vnd_models[j]; + + size += metadata_model_size(model, elem, true); + } + } + + return size; +} + +static int metadata_add_model( + const struct bt_mesh_model *mod, const struct bt_mesh_elem *elem, bool vnd, + void *user_data) +{ + const struct bt_mesh_models_metadata_entry *entry; + struct comp_foreach_model_arg *arg = user_data; + struct net_buf_simple *buf = arg->buf; + size_t *offset = arg->offset; + size_t model_size; + uint8_t count = 0; + uint8_t *count_ptr; + + model_size = metadata_model_size(mod, elem, vnd); + + if (*offset >= model_size) + { + *offset -= model_size; + return 0; + } + + if (net_buf_simple_tailroom(buf) < (model_size + BT_MESH_MIC_SHORT)) + { + LOG_DBG("Model metadata didn't fit in the buffer"); + return -E2BIG; + } + + comp_add_model(mod, elem, vnd, user_data); + + count_ptr = data_buf_add_u8_offset(buf, 0, offset); + + if (mod->metadata) + { + for (entry = mod->metadata; entry && entry->data != NULL; ++entry) + { + data_buf_add_le16_offset(buf, entry->len, offset); + data_buf_add_le16_offset(buf, entry->id, offset); + data_buf_add_mem_offset(buf, entry->data, entry->len, offset); + count++; + } + } + + if (count_ptr) + { + *count_ptr = count; + } + + return 0; +} + +int bt_mesh_metadata_get_page_0(struct net_buf_simple *buf, size_t offset) +{ + const struct bt_mesh_comp *comp; + struct comp_foreach_model_arg arg = + { + .buf = buf, + .offset = &offset, + }; + + uint8_t *mod_count_ptr; + uint8_t *vnd_count_ptr; + int i; + int j; + int err; + + comp = bt_mesh_comp_get(); + + for (i = 0; i < comp->elem_count; i++) + { + const struct bt_mesh_elem *elem = &dev_comp->elem[i]; + + /* Check that the buffer has available tailroom for metadata item + * counts + */ + + if (net_buf_simple_tailroom(buf) < (((offset == 0) ? 2 + : (offset == 1) ? 1 + : 0) + + BT_MESH_MIC_SHORT)) + { + LOG_DBG("Model metadata didn't fit in the buffer"); + return -E2BIG; + } + + mod_count_ptr = data_buf_add_u8_offset(buf, 0, &offset); + vnd_count_ptr = data_buf_add_u8_offset(buf, 0, &offset); + + for (j = 0; j < elem->model_count; j++) + { + const struct bt_mesh_model *model = &elem->models[j]; + + if (!model->metadata) + { + continue; + } + + err = metadata_add_model(model, elem, false, &arg); + if (err) + { + return err; + } + + if (mod_count_ptr) + { + (*mod_count_ptr) += 1; + } + } + + for (j = 0; j < elem->vnd_model_count; j++) + { + const struct bt_mesh_model *model = &elem->vnd_models[j]; + + if (!model->metadata) + { + continue; + } + + err = metadata_add_model(model, elem, true, &arg); + if (err) + { + return err; + } + + if (vnd_count_ptr) + { + (*vnd_count_ptr) += 1; + } + } + } + + return 0; +} +#endif + +static int comp_add_elem( + struct net_buf_simple *buf, const struct bt_mesh_elem *elem, + size_t *offset) +{ + struct comp_foreach_model_arg arg = + { + .buf = buf, + .offset = offset, + }; + + const size_t elem_size = bt_mesh_comp_elem_size(elem); + int i; + + if (*offset >= elem_size) + { + *offset -= elem_size; + return 0; + } + + if (net_buf_simple_tailroom(buf) < + ((elem_size - *offset) + BT_MESH_MIC_SHORT)) + { + if (IS_ENABLED(CONFIG_BT_MESH_LARGE_COMP_DATA_SRV)) + { + /* MshPRTv1.1: 4.4.1.2.2: + * If the complete list of models does not fit in the Data field, + * the element shall not be reported. + */ + + LOG_DBG("Element 0x%04x didn't fit in the Data field", + elem->rt->addr); + return 0; + } + + LOG_ERR("Too large device composition"); + return -E2BIG; + } + + data_buf_add_le16_offset(buf, elem->loc, offset); + + data_buf_add_u8_offset(buf, elem->model_count, offset); + data_buf_add_u8_offset(buf, elem->vnd_model_count, offset); + + for (i = 0; i < elem->model_count; i++) + { + const struct bt_mesh_model *model = &elem->models[i]; + + comp_add_model(model, elem, false, &arg); + } + + for (i = 0; i < elem->vnd_model_count; i++) + { + const struct bt_mesh_model *model = &elem->vnd_models[i]; + + comp_add_model(model, elem, true, &arg); + } + + return 0; +} + +int bt_mesh_comp_data_get_page_0(struct net_buf_simple *buf, size_t offset) +{ + uint16_t feat = 0U; + const struct bt_mesh_comp *comp; + int i; + + comp = bt_mesh_comp_get(); + + if (IS_ENABLED(CONFIG_BT_MESH_RELAY)) + { + feat |= BT_MESH_FEAT_RELAY; + } + + if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY)) + { + feat |= BT_MESH_FEAT_PROXY; + } + + if (IS_ENABLED(CONFIG_BT_MESH_FRIEND)) + { + feat |= BT_MESH_FEAT_FRIEND; + } + + if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER)) + { + feat |= BT_MESH_FEAT_LOW_POWER; + } + + data_buf_add_le16_offset(buf, comp->cid, &offset); + data_buf_add_le16_offset(buf, comp->pid, &offset); + data_buf_add_le16_offset(buf, comp->vid, &offset); + data_buf_add_le16_offset(buf, CONFIG_BT_MESH_CRPL, &offset); + data_buf_add_le16_offset(buf, feat, &offset); + + for (i = 0; i < comp->elem_count; i++) + { + int err; + + err = comp_add_elem(buf, &comp->elem[i], &offset); + if (err) + { + return err; + } + } + + return 0; +} + +static uint8_t count_mod_ext( + const struct bt_mesh_model *mod, int8_t *max_offset, uint8_t sig_offset) +{ + int i; + uint8_t extensions = 0; + int8_t offset; + int8_t offset_record = 0; + + MOD_REL_LIST_FOR_EACH(i) + { + if (IS_MOD_EXTENSION(mod, i, sig_offset) && + mod_rel_list[i].type == RELATION_TYPE_EXT) + { + extensions++; + offset = mod_rel_list[i].elem_ext - mod_rel_list[i].elem_base; + if (abs(offset) > abs(offset_record)) + { + offset_record = offset; + } + } + } + + if (max_offset) + { + memcpy(max_offset, &offset_record, sizeof(uint8_t)); + } + + return extensions; +} + +static bool is_cor_present(const struct bt_mesh_model *mod, + uint8_t *cor_id, uint8_t sig_offset) +{ + int i; + + MOD_REL_LIST_FOR_EACH(i) + { + if ((IS_MOD_BASE(mod, i, sig_offset) || + IS_MOD_EXTENSION(mod, i, sig_offset)) && + mod_rel_list[i].type < RELATION_TYPE_EXT) + { + if (cor_id) + { + memcpy(cor_id, &mod_rel_list[i].type, sizeof(uint8_t)); + } + + return true; + } + } + + return false; +} + +static void prep_model_item_header( + const struct bt_mesh_model *mod, uint8_t *cor_id, uint8_t *mod_cnt, + struct net_buf_simple *buf, size_t *offset, uint8_t sig_offset) +{ + uint8_t ext_mod_cnt; + bool cor_present; + uint8_t mod_elem_info = 0; + int8_t max_offset; + + ext_mod_cnt = count_mod_ext(mod, &max_offset, sig_offset); + cor_present = is_cor_present(mod, cor_id, sig_offset); + + mod_elem_info = ext_mod_cnt << 2; + if (ext_mod_cnt > 31 || max_offset > 3 || max_offset < -4) + { + mod_elem_info |= BIT(1); + } + + if (cor_present) + { + mod_elem_info |= BIT(0); + } + + data_buf_add_u8_offset(buf, mod_elem_info, offset); + + if (cor_present) + { + data_buf_add_u8_offset(buf, *cor_id, offset); + } + + memset(mod_cnt, ext_mod_cnt, sizeof(uint8_t)); +} + +static void add_items_to_page( + struct net_buf_simple *buf, const struct bt_mesh_model *mod, + uint8_t ext_mod_cnt, size_t *offset, uint8_t sig_offset) +{ + int i; + int elem_offset; + uint8_t mod_idx; + + MOD_REL_LIST_FOR_EACH(i) + { + if (IS_MOD_EXTENSION(mod, i, sig_offset) && + mod_rel_list[i].type == RELATION_TYPE_EXT) + { + elem_offset = mod->rt->elem_idx - mod_rel_list[i].elem_base; + mod_idx = mod_rel_list[i].idx_base; + if (ext_mod_cnt < 32 && elem_offset < 4 && elem_offset > -5) + { + /* short format */ + + if (elem_offset < 0) + { + elem_offset += 8; + } + + elem_offset |= mod_idx << 3; + data_buf_add_u8_offset(buf, elem_offset, offset); + } + else + { + /* long format */ + + if (elem_offset < 0) + { + elem_offset += 256; + } + + data_buf_add_u8_offset(buf, elem_offset, offset); + data_buf_add_u8_offset(buf, mod_idx, offset); + } + } + } +} + +static size_t mod_items_size(const struct bt_mesh_model *mod, + uint8_t sig_offset) +{ + int i; + int offset; + size_t temp_size = 0; + int ext_mod_cnt = count_mod_ext(mod, NULL, sig_offset); + + if (!ext_mod_cnt) + { + return 0; + } + + MOD_REL_LIST_FOR_EACH(i) + { + if (IS_MOD_EXTENSION(mod, i, sig_offset)) + { + offset = mod->rt->elem_idx - mod_rel_list[i].elem_base; + temp_size += (ext_mod_cnt < 32 && offset < 4 && offset > -5) ? 1 : 2; + } + } + + return temp_size; +} + +static size_t page1_elem_size(const struct bt_mesh_elem *elem) +{ + size_t temp_size = 2; + + for (int i = 0; i < elem->model_count; i++) + { + temp_size += is_cor_present(&elem->models[i], NULL, 0) ? 2 : 1; + temp_size += mod_items_size(&elem->models[i], 0); + } + + for (int i = 0; i < elem->vnd_model_count; i++) + { + temp_size += is_cor_present(&elem->vnd_models[i], NULL, + elem->model_count) ? 2 : 1; + temp_size += mod_items_size(&elem->vnd_models[i], elem->model_count); + } + + return temp_size; +} + +static int bt_mesh_comp_data_get_page_1(struct net_buf_simple *buf, + size_t offset) +{ + const struct bt_mesh_comp *comp; + uint8_t cor_id = 0; + uint8_t ext_mod_cnt = 0; + int i; + int j; + + comp = bt_mesh_comp_get(); + + for (i = 0; i < comp->elem_count; i++) + { + size_t elem_size = page1_elem_size(&comp->elem[i]); + + if (offset >= elem_size) + { + offset -= elem_size; + continue; + } + + if (net_buf_simple_tailroom(buf) < ((elem_size - offset) + + BT_MESH_MIC_SHORT)) + { + if (IS_ENABLED(CONFIG_BT_MESH_LARGE_COMP_DATA_SRV)) + { + /* MshPRTv1.1: 4.4.1.2.2: + * If the complete list of models does not fit in the Data + * field, the element shall not be reported. + */ + + LOG_DBG("Element 0x%04x didn't fit in the Data field", + comp->elem[i].rt->addr); + return 0; + } + + LOG_ERR("Too large device composition"); + return -E2BIG; + } + + data_buf_add_u8_offset(buf, comp->elem[i].model_count, &offset); + data_buf_add_u8_offset(buf, comp->elem[i].vnd_model_count, &offset); + for (j = 0; j < comp->elem[i].model_count; j++) + { + prep_model_item_header(&comp->elem[i].models[j], &cor_id, + &ext_mod_cnt, buf, &offset, 0); + if (ext_mod_cnt != 0) + { + add_items_to_page(buf, &comp->elem[i].models[j], ext_mod_cnt, + &offset, 0); + } + } + + for (j = 0; j < comp->elem[i].vnd_model_count; j++) + { + prep_model_item_header(&comp->elem[i].vnd_models[j], &cor_id, + &ext_mod_cnt, buf, &offset, + comp->elem[i].model_count); + if (ext_mod_cnt != 0) + { + add_items_to_page(buf, &comp->elem[i].vnd_models[j], + ext_mod_cnt, + &offset, + comp->elem[i].model_count); + } + } + } + + return 0; +} + +static int bt_mesh_comp_data_get_page_2(struct net_buf_simple *buf, + size_t offset) +{ + if (!dev_comp2) + { + LOG_ERR("Composition data P2 not registered"); + return -ENODEV; + } + + size_t elem_size; + + for (int i = 0; i < dev_comp2->record_cnt; i++) + { + elem_size = + 8 + dev_comp2->record[i].elem_offset_cnt + + dev_comp2->record[i].data_len; + if (offset >= elem_size) + { + offset -= elem_size; + continue; + } + + if (net_buf_simple_tailroom(buf) < ((elem_size - offset) + + BT_MESH_MIC_SHORT)) + { + if (IS_ENABLED(CONFIG_BT_MESH_LARGE_COMP_DATA_SRV)) + { + /* MshPRTv1.1: 4.4.1.2.2: + * If the complete list of models does not fit in the Data + * field, the element shall not be reported. + */ + + LOG_DBG("Record 0x%04x didn't fit in the Data field", i); + return 0; + } + + LOG_ERR("Too large device composition"); + return -E2BIG; + } + + data_buf_add_le16_offset(buf, dev_comp2->record[i].id, &offset); + data_buf_add_u8_offset(buf, dev_comp2->record[i].version.x, &offset); + data_buf_add_u8_offset(buf, dev_comp2->record[i].version.y, &offset); + data_buf_add_u8_offset(buf, dev_comp2->record[i].version.z, &offset); + data_buf_add_u8_offset(buf, dev_comp2->record[i].elem_offset_cnt, + &offset); + if (dev_comp2->record[i].elem_offset_cnt) + { + data_buf_add_mem_offset(buf, dev_comp2->record[i].elem_offset, + dev_comp2->record[i].elem_offset_cnt, + &offset); + } + + data_buf_add_le16_offset(buf, dev_comp2->record[i].data_len, &offset); + if (dev_comp2->record[i].data_len) + { + data_buf_add_mem_offset(buf, dev_comp2->record[i].data, + dev_comp2->record[i].data_len, + &offset); + } + } + + return 0; +} + +int32_t bt_mesh_model_pub_period_get(const struct bt_mesh_model *mod) +{ + int32_t period; + + if (!mod->pub) + { + return 0; + } + + switch (mod->pub->period >> 6) + { + case 0x00: + + /* 1 step is 100 ms */ + + period = (mod->pub->period & BIT_MASK(6)) * 100U; + break; + case 0x01: + + /* 1 step is 1 second */ + + period = (mod->pub->period & BIT_MASK(6)) * MSEC_PER_SEC; + break; + case 0x02: + + /* 1 step is 10 seconds */ + + period = (mod->pub->period & BIT_MASK(6)) * 10U * MSEC_PER_SEC; + break; + case 0x03: + + /* 1 step is 10 minutes */ + + period = (mod->pub->period & BIT_MASK(6)) * 600U * MSEC_PER_SEC; + break; + default: + CODE_UNREACHABLE; + } + + if (mod->pub->fast_period) + { + if (!period) + { + return 0; + } + + return MAX(period >> mod->pub->period_div, 100); + } + else + { + return period; + } +} + +static int32_t next_period(const struct bt_mesh_model *mod) +{ + struct bt_mesh_model_pub *pub = mod->pub; + uint32_t period = 0; + uint32_t elapsed; + + elapsed = (uint32_t)TICK2MSEC(clock_systime_ticks()) - pub->period_start; + LOG_DBG("Publishing took %ums", elapsed); + + if (mod->pub->count) + { + /* If a message is to be retransmitted, period should include time + * since the first publication until the last publication. + */ + + period = BT_MESH_PUB_TRANSMIT_INT(mod->pub->retransmit); + period *= BT_MESH_PUB_MSG_NUM(mod->pub); + + if (period && elapsed >= period) + { + LOG_WRN("Retransmission interval is too short"); + + if (!!pub->delayable) + { + LOG_WRN("Publication period is too short for" + " retransmissions"); + } + + /* Keep retransmitting the message with the interval sacrificing + * the next publication period start. + */ + + return BT_MESH_PUB_TRANSMIT_INT(mod->pub->retransmit); + } + } + + if (!period) + { + period = bt_mesh_model_pub_period_get(mod); + if (!period) + { + return 0; + } + } + + if (elapsed >= period) + { + LOG_WRN("Publication sending took longer than the period"); + + if (!!pub->delayable) + { + LOG_WRN("Publication period is too short to be delayable"); + } + + /* Return smallest positive number since 0 means disabled */ + + return 1; + } + + return period - elapsed; +} + +static void publish_sent(int err, void *user_data) +{ + const struct bt_mesh_model *mod = user_data; + int32_t delay; + + LOG_DBG("err %d, time " + "%u", err, (uint32_t)TICK2MSEC(clock_systime_ticks())); + + delay = next_period(mod); + + if (delay) + { + LOG_DBG("Publishing next time in %dms", delay); + /* Using schedule() in case the application has already called + * bt_mesh_publish, and a publication is pending. + */ + + k_work_schedule(&mod->pub->timer, MSEC2TICK(delay)); + } +} + +static void publish_start(uint16_t duration, int err, void *user_data) +{ + if (err) + { + LOG_ERR("Failed to publish: err %d", err); + publish_sent(err, user_data); + return; + } +} + +static const struct bt_mesh_send_cb pub_sent_cb = +{ + .start = publish_start, + .end = publish_sent, +}; + +static int publish_transmit(const struct bt_mesh_model *mod) +{ + NET_BUF_SIMPLE_DEFINE(sdu, BT_MESH_TX_SDU_MAX); + struct bt_mesh_model_pub *pub = mod->pub; + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_PUB(pub); + struct bt_mesh_net_tx tx = + { + .ctx = &ctx, + .src = bt_mesh_model_elem(mod)->rt->addr, + .friend_cred = pub->cred, + }; + + net_buf_simple_add_mem(&sdu, pub->msg->data, pub->msg->len); + + return bt_mesh_trans_send(&tx, &sdu, &pub_sent_cb, (void *)mod); +} + +static int pub_period_start(struct bt_mesh_model_pub *pub) +{ + int err; + + pub->count = BT_MESH_PUB_TRANSMIT_COUNT(pub->retransmit); + + if (!pub->update) + { + return 0; + } + + err = pub->update(pub->mod); + + pub->period_start = (uint32_t)TICK2MSEC(clock_systime_ticks()); + + if (err) + { + /* Skip this publish attempt. */ + + LOG_DBG("Update failed, skipping publish (err: %d)", err); + pub->count = 0; + publish_sent(err, (void *)pub->mod); + return err; + } + + return 0; +} + +static uint16_t pub_delay_get(int random_delay_window) +{ + if (!IS_ENABLED(CONFIG_BT_MESH_DELAYABLE_PUBLICATION)) + { + return 0; + } + + uint16_t num = 0; + + (void)bt_rand(&num, sizeof(num)); + + return 20 + (num % random_delay_window); +} + +static int pub_delay_schedule(struct bt_mesh_model_pub *pub, int delay) +{ + uint16_t random; + int err; + + if (!IS_ENABLED(CONFIG_BT_MESH_DELAYABLE_PUBLICATION)) + { + return -ENOTSUP; + } + + random = pub_delay_get(delay); + err = k_work_reschedule(&pub->timer, MSEC2TICK(random)); + if (err < 0) + { + LOG_ERR("Unable to delay publication (err %d)", err); + return err; + } + + LOG_DBG("Publication delayed by %dms", random); + return 0; +} + +static void mod_publish(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_mesh_model_pub *pub = CONTAINER_OF(dwork, + struct bt_mesh_model_pub, + timer); + int err; + + if (pub->addr == BT_MESH_ADDR_UNASSIGNED || + atomic_test_bit(bt_mesh.flags, BT_MESH_SUSPENDED)) + { + /* Publication is no longer active, but the cancellation of the + * delayed work failed. Abandon recurring timer. + */ + + return; + } + + LOG_DBG("timestamp: %u", (uint32_t)TICK2MSEC(clock_systime_ticks())); + + if (pub->count) + { + pub->count--; + + if (pub->retr_update && pub->update && + bt_mesh_model_pub_is_retransmission(pub->mod)) + { + err = pub->update(pub->mod); + if (err) + { + publish_sent(err, (void *)pub->mod); + return; + } + } + } + else + { + /* First publication in this period */ + + err = pub_period_start(pub); + if (err) + { + return; + } + + /* Delay the first publication in a period. */ + + if (!!pub->delayable && !pub_delay_schedule(pub, RANDOM_DELAY_SHORT)) + { + /* Increment count as it would do BT_MESH_PUB_MSG_TOTAL */ + + pub->count++; + return; + } + } + + err = publish_transmit(pub->mod); + if (err) + { + LOG_ERR("Failed to publish (err %d)", err); + publish_sent(err, (void *)pub->mod); + } +} + +const struct bt_mesh_elem * +bt_mesh_model_elem(const struct bt_mesh_model *mod) +{ + return &dev_comp->elem[mod->rt->elem_idx]; +} + +const struct bt_mesh_model *bt_mesh_model_get(bool vnd, + uint8_t elem_idx, + uint8_t mod_idx) +{ + const struct bt_mesh_elem *elem; + + if (elem_idx >= dev_comp->elem_count) + { + LOG_ERR("Invalid element index %u", elem_idx); + return NULL; + } + + elem = &dev_comp->elem[elem_idx]; + + if (vnd) + { + if (mod_idx >= elem->vnd_model_count) + { + LOG_ERR("Invalid vendor model index %u", mod_idx); + return NULL; + } + + return &elem->vnd_models[mod_idx]; + } + else + { + if (mod_idx >= elem->model_count) + { + LOG_ERR("Invalid SIG model index %u", mod_idx); + return NULL; + } + + return &elem->models[mod_idx]; + } +} + +static int bt_mesh_vnd_mod_msg_cid_check(const struct bt_mesh_model *mod) +{ + uint16_t cid; + const struct bt_mesh_model_op *op; + + for (op = mod->op; op->func; op++) + { + cid = (uint16_t)(op->opcode & 0xffff); + + if (BT_MESH_MODEL_OP_LEN(op->opcode) == 3 && cid == mod->vnd.company) + { + continue; + } + + LOG_ERR("Invalid vendor model(company:0x%04x" + " id:0x%04x) message opcode 0x%08x", + mod->vnd.company, mod->vnd.id, op->opcode); + + return -EINVAL; + } + + return 0; +} + +static void mod_init( + const struct bt_mesh_model *mod, const struct bt_mesh_elem *elem, bool vnd, + bool primary, void *user_data) +{ + int i; + int *err = user_data; + + if (*err) + { + return; + } + + if (mod->pub) + { + mod->pub->mod = mod; + k_work_init_delayable(&mod->pub->timer, mod_publish); + } + + for (i = 0; i < mod->keys_cnt; i++) + { + mod->keys[i] = BT_MESH_KEY_UNUSED; + } + + mod->rt->elem_idx = elem - dev_comp->elem; + if (vnd) + { + mod->rt->mod_idx = mod - elem->vnd_models; + + if (IS_ENABLED(CONFIG_BT_MESH_MODEL_VND_MSG_CID_FORCE)) + { + *err = bt_mesh_vnd_mod_msg_cid_check(mod); + if (*err) + { + return; + } + } + } + else + { + mod->rt->mod_idx = mod - elem->models; + } + + if (mod->cb && mod->cb->init) + { + *err = mod->cb->init(mod); + } +} + +int bt_mesh_comp_register(const struct bt_mesh_comp *comp) +{ + int err; + + /* There must be at least one element */ + + if (!comp || !comp->elem_count) + { + return -EINVAL; + } + + dev_comp = comp; + + err = 0; + + if (MOD_REL_LIST_SIZE > 0) + { + memset(mod_rel_list, 0, sizeof(mod_rel_list)); + } + + bt_mesh_model_foreach(mod_init, &err); + + if (MOD_REL_LIST_SIZE > 0) + { + int i; + + MOD_REL_LIST_FOR_EACH(i) + { + LOG_DBG("registered %s", + mod_rel_list[i].type < RELATION_TYPE_EXT ? + "correspondence" : "extension"); + LOG_DBG("\tbase: elem %u idx %u", mod_rel_list[i].elem_base, + mod_rel_list[i].idx_base); + LOG_DBG("\text: elem %u idx %u", mod_rel_list[i].elem_ext, + mod_rel_list[i].idx_ext); + } + + if (i < MOD_REL_LIST_SIZE) + { + LOG_WRN("Unused space in relation list: " + "%d", MOD_REL_LIST_SIZE - i); + } + } + + return err; +} + +int bt_mesh_comp2_register(const struct bt_mesh_comp2 *comp2) +{ + if (!IS_ENABLED(CONFIG_BT_MESH_COMP_PAGE_2)) + { + return -EINVAL; + } + + dev_comp2 = comp2; + + return 0; +} + +void bt_mesh_comp_provision(uint16_t addr) +{ + int i; + + dev_primary_addr = addr; + + LOG_DBG("addr 0x%04x elem_count %zu", addr, dev_comp->elem_count); + + for (i = 0; i < dev_comp->elem_count; i++) + { + const struct bt_mesh_elem *elem = &dev_comp->elem[i]; + + elem->rt->addr = addr++; + + LOG_DBG("addr 0x%04x mod_count %u vnd_mod_count %u", elem->rt->addr, + elem->model_count, + elem->vnd_model_count); + } +} + +void bt_mesh_comp_unprovision(void) +{ + LOG_DBG(""); + + dev_primary_addr = BT_MESH_ADDR_UNASSIGNED; + + for (int i = 0; i < dev_comp->elem_count; i++) + { + const struct bt_mesh_elem *elem = &dev_comp->elem[i]; + + elem->rt->addr = BT_MESH_ADDR_UNASSIGNED; + } +} + +uint16_t bt_mesh_primary_addr(void) +{ + return dev_primary_addr; +} + +static uint16_t *model_group_get(const struct bt_mesh_model *mod, + uint16_t addr) +{ + int i; + + for (i = 0; i < mod->groups_cnt; i++) + { + if (mod->groups[i] == addr) + { + return &mod->groups[i]; + } + } + + return NULL; +} + +struct find_group_visitor_ctx +{ + uint16_t *entry; + const struct bt_mesh_model *mod; + uint16_t addr; +}; + +static enum bt_mesh_walk find_group_mod_visitor( + const struct bt_mesh_model *mod, void *user_data) +{ + struct find_group_visitor_ctx *ctx = user_data; + + if (mod->rt->elem_idx != ctx->mod->rt->elem_idx) + { + return BT_MESH_WALK_CONTINUE; + } + + ctx->entry = model_group_get(mod, ctx->addr); + if (ctx->entry) + { + ctx->mod = mod; + return BT_MESH_WALK_STOP; + } + + return BT_MESH_WALK_CONTINUE; +} + +uint16_t *bt_mesh_model_find_group(const struct bt_mesh_model **mod, + uint16_t addr) +{ + struct find_group_visitor_ctx ctx = + { + .mod = *mod, + .entry = NULL, + .addr = addr, + }; + + bt_mesh_model_extensions_walk(*mod, find_group_mod_visitor, &ctx); + + *mod = ctx.mod; + return ctx.entry; +} + +#if CONFIG_BT_MESH_LABEL_COUNT > 0 +static const uint8_t **model_uuid_get(const struct bt_mesh_model *mod, + const uint8_t *uuid) +{ + int i; + + for (i = 0; i < CONFIG_BT_MESH_LABEL_COUNT; i++) + { + if (mod->uuids[i] == uuid) + { + /* If we are looking for a new entry, ensure that we find a model + * where there is empty entry in both, uuids and groups list. + */ + + if (uuid == NULL && !model_group_get(mod, BT_MESH_ADDR_UNASSIGNED)) + { + continue; + } + + return &mod->uuids[i]; + } + } + + return NULL; +} + +struct find_uuid_visitor_ctx +{ + const uint8_t **entry; + const struct bt_mesh_model *mod; + const uint8_t *uuid; +}; + +static enum bt_mesh_walk find_uuid_mod_visitor( + const struct bt_mesh_model *mod, void *user_data) +{ + struct find_uuid_visitor_ctx *ctx = user_data; + + if (mod->rt->elem_idx != ctx->mod->rt->elem_idx) + { + return BT_MESH_WALK_CONTINUE; + } + + ctx->entry = model_uuid_get(mod, ctx->uuid); + if (ctx->entry) + { + ctx->mod = mod; + return BT_MESH_WALK_STOP; + } + + return BT_MESH_WALK_CONTINUE; +} +#endif /* CONFIG_BT_MESH_LABEL_COUNT > 0 */ + +const uint8_t **bt_mesh_model_find_uuid(const struct bt_mesh_model **mod, + const uint8_t *uuid) +{ +#if CONFIG_BT_MESH_LABEL_COUNT > 0 + struct find_uuid_visitor_ctx ctx = + { + .mod = *mod, + .entry = NULL, + .uuid = uuid, + }; + + bt_mesh_model_extensions_walk(*mod, find_uuid_mod_visitor, &ctx); + + *mod = ctx.mod; + return ctx.entry; +#else + return NULL; +#endif +} + +static const struct bt_mesh_model *bt_mesh_elem_find_group( + const struct bt_mesh_elem *elem, uint16_t group_addr) +{ + const struct bt_mesh_model *model; + uint16_t *match; + int i; + + for (i = 0; i < elem->model_count; i++) + { + model = &elem->models[i]; + + match = model_group_get(model, group_addr); + if (match) + { + return model; + } + } + + for (i = 0; i < elem->vnd_model_count; i++) + { + model = &elem->vnd_models[i]; + + match = model_group_get(model, group_addr); + if (match) + { + return model; + } + } + + return NULL; +} + +const struct bt_mesh_elem *bt_mesh_elem_find(uint16_t addr) +{ + uint16_t index; + + if (!BT_MESH_ADDR_IS_UNICAST(addr)) + { + return NULL; + } + + index = addr - dev_comp->elem[0].rt->addr; + if (index >= dev_comp->elem_count) + { + return NULL; + } + + return &dev_comp->elem[index]; +} + +bool bt_mesh_has_addr(uint16_t addr) +{ + uint16_t index; + + if (BT_MESH_ADDR_IS_UNICAST(addr)) + { + return bt_mesh_elem_find(addr) != NULL; + } + + if (IS_ENABLED(CONFIG_BT_MESH_ACCESS_LAYER_MSG) && msg_cb) + { + return true; + } + + for (index = 0; index < dev_comp->elem_count; index++) + { + const struct bt_mesh_elem *elem = &dev_comp->elem[index]; + + if (bt_mesh_elem_find_group(elem, addr)) + { + return true; + } + } + + return false; +} + +#if defined(CONFIG_BT_MESH_ACCESS_LAYER_MSG) +void bt_mesh_msg_cb_set( + void (*cb)(uint32_t opcode, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf)) +{ + msg_cb = cb; +} +#endif + +int bt_mesh_access_send( + struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf, uint16_t src_addr, + const struct bt_mesh_send_cb *cb, void *cb_data) +{ + struct bt_mesh_net_tx tx = + { + .ctx = ctx, + .src = src_addr, + }; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x dst 0x%04x", tx.ctx->net_idx, + tx.ctx->app_idx, + tx.ctx->addr); + LOG_DBG("len %u: %s", buf->len, bt_hex(buf->data, buf->len)); + + if (!bt_mesh_is_provisioned()) + { + LOG_ERR("Local node is not yet provisioned"); + return -EAGAIN; + } + + return bt_mesh_trans_send(&tx, buf, cb, cb_data); +} + +uint8_t bt_mesh_elem_count(void) +{ + return dev_comp->elem_count; +} + +bool bt_mesh_model_has_key(const struct bt_mesh_model *mod, uint16_t key) +{ + int i; + + for (i = 0; i < mod->keys_cnt; i++) + { + if (mod->keys[i] == key || (mod->keys[i] == BT_MESH_KEY_DEV_ANY && + BT_MESH_IS_DEV_KEY(key))) + { + return true; + } + } + + return false; +} + +static bool model_has_dst(const struct bt_mesh_model *mod, + uint16_t dst, const uint8_t *uuid) +{ + if (BT_MESH_ADDR_IS_UNICAST(dst)) + { + return (dev_comp->elem[mod->rt->elem_idx].rt->addr == dst); + } + else if (BT_MESH_ADDR_IS_VIRTUAL(dst)) + { + return !!bt_mesh_model_find_uuid(&mod, uuid); + } + else if (BT_MESH_ADDR_IS_GROUP(dst) || + (BT_MESH_ADDR_IS_FIXED_GROUP(dst) && mod->rt->elem_idx != 0)) + { + return !!bt_mesh_model_find_group(&mod, dst); + } + + /* If a message with a fixed group address is sent to the access layer, + * the lower layers have already confirmed that we are subscribing to it. + * All models on the primary element should receive the message. + */ + + return mod->rt->elem_idx == 0; +} + +static const struct bt_mesh_model_op *find_op_in_list( + const struct bt_mesh_model *models, uint8_t model_count, uint32_t opcode, + const struct bt_mesh_model **model) +{ + for (uint8_t i = 0U; i < model_count; i++) + { + const struct bt_mesh_model_op *op; + + *model = &models[i]; + + for (op = (*model)->op; op->func; op++) + { + if (op->opcode == opcode) + { + return op; + } + } + } + + *model = NULL; + return NULL; +} + +static const struct bt_mesh_model_op *find_op( + const struct bt_mesh_elem *elem, uint32_t opcode, + const struct bt_mesh_model **model) +{ + const struct bt_mesh_model_op *op; + + op = find_op_in_list(elem->vnd_models, elem->vnd_model_count, opcode, + model); + + if (op != NULL || BT_MESH_MODEL_OP_LEN(opcode) == 3) + { + /* Return early if LEN == 3, since SIG models cannot contain 3-byte + * opcodes. + */ + + return op; + } + + return find_op_in_list(elem->models, elem->model_count, opcode, model); +} + +static int get_opcode(struct net_buf_simple *buf, uint32_t *opcode) +{ + switch (buf->data[0] >> 6) + { + case 0x00: + case 0x01: + if (buf->data[0] == 0x7f) + { + LOG_ERR("Ignoring RFU OpCode"); + return -EINVAL; + } + + *opcode = net_buf_simple_pull_u8(buf); + return 0; + case 0x02: + if (buf->len < 2) + { + LOG_ERR("Too short payload for 2-octet OpCode"); + return -EINVAL; + } + + *opcode = net_buf_simple_pull_be16(buf); + return 0; + case 0x03: + if (buf->len < 3) + { + LOG_ERR("Too short payload for 3-octet OpCode"); + return -EINVAL; + } + + *opcode = net_buf_simple_pull_u8(buf) << 16; + /* Using LE for the CID since the model layer is defined as + * little-endian in the mesh spec and using BT_MESH_MODEL_OP_3 will + * declare the opcode in this way. + */ + + *opcode |= net_buf_simple_pull_le16(buf); + return 0; + } + + CODE_UNREACHABLE; +} + +static int element_model_recv( + struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf, + const struct bt_mesh_elem *elem, uint32_t opcode) +{ + const struct bt_mesh_model_op *op; + const struct bt_mesh_model *model; + struct net_buf_simple_state state; + int err; + + op = find_op(elem, opcode, &model); + if (!op) + { + LOG_DBG("No OpCode 0x%08x for elem 0x%02x", opcode, elem->rt->addr); + return ACCESS_STATUS_WRONG_OPCODE; + } + + if (!bt_mesh_model_has_key(model, ctx->app_idx)) + { + LOG_DBG("Model at 0x%04x is not bound to app idx %d", elem->rt->addr, + ctx->app_idx); + return ACCESS_STATUS_WRONG_KEY; + } + + if (!model_has_dst(model, ctx->recv_dst, ctx->uuid)) + { + LOG_DBG("Dst addr 0x%02x is invalid for model at " + "0x%04x", ctx->recv_dst, + elem->rt->addr); + return ACCESS_STATUS_INVALID_ADDRESS; + } + + if ((op->len >= 0) && (buf->len < (size_t)op->len)) + { + LOG_ERR("Too short message for OpCode 0x%08x", opcode); + return ACCESS_STATUS_MESSAGE_NOT_UNDERSTOOD; + } + else if ((op->len < 0) && (buf->len != (size_t)(-op->len))) + { + LOG_ERR("Invalid message size for OpCode 0x%08x", opcode); + return ACCESS_STATUS_MESSAGE_NOT_UNDERSTOOD; + } + + if (IS_ENABLED(CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG_CTX_ENABLED)) + { + ctx->rnd_delay = true; + } + + net_buf_simple_save(buf, &state); + err = op->func(model, ctx, buf); + net_buf_simple_restore(buf, &state); + + if (err) + { + return ACCESS_STATUS_MESSAGE_NOT_UNDERSTOOD; + } + + return ACCESS_STATUS_SUCCESS; +} + +int bt_mesh_model_recv(struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + int err = ACCESS_STATUS_SUCCESS; + uint32_t opcode; + uint16_t index; + + LOG_DBG("app_idx 0x%04x src 0x%04x dst 0x%04x", ctx->app_idx, ctx->addr, + ctx->recv_dst); + LOG_DBG("len %u: %s", buf->len, bt_hex(buf->data, buf->len)); + + if (IS_ENABLED(CONFIG_BT_TESTING)) + { + bt_mesh_test_model_recv(ctx->addr, ctx->recv_dst, buf->data, buf->len); + } + + if (get_opcode(buf, &opcode) < 0) + { + LOG_WRN("Unable to decode OpCode"); + return ACCESS_STATUS_WRONG_OPCODE; + } + + LOG_DBG("OpCode 0x%08x", opcode); + + if (BT_MESH_ADDR_IS_UNICAST(ctx->recv_dst)) + { + index = ctx->recv_dst - dev_comp->elem[0].rt->addr; + + if (index >= dev_comp->elem_count) + { + LOG_ERR("Invalid address 0x%02x", ctx->recv_dst); + return ACCESS_STATUS_INVALID_ADDRESS; + } + else + { + const struct bt_mesh_elem *elem = &dev_comp->elem[index]; + + err = element_model_recv(ctx, buf, elem, opcode); + } + } + else + { + err = ACCESS_STATUS_MESSAGE_NOT_UNDERSTOOD; + for (index = 0; index < dev_comp->elem_count; index++) + { + const struct bt_mesh_elem *elem = &dev_comp->elem[index]; + int err_elem; + + err_elem = element_model_recv(ctx, buf, elem, opcode); + err = err_elem == ACCESS_STATUS_SUCCESS ? err_elem : err; + } + } + + if (IS_ENABLED(CONFIG_BT_MESH_ACCESS_LAYER_MSG) && msg_cb) + { + msg_cb(opcode, ctx, buf); + } + + return err; +} + +int bt_mesh_access_recv(struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + int err; + + err = bt_mesh_model_recv(ctx, buf); + + if (IS_ENABLED(CONFIG_BT_MESH_ACCESS_LAYER_MSG) && msg_cb) + { + /* Mesh assumes that the application has processed the message. + * Access layer returns success to trigger RPL update and prevent + * replay attack over application. + */ + + err = 0; + } + + return err; +} + +int bt_mesh_model_send( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *msg, const struct bt_mesh_send_cb *cb, + void *cb_data) +{ + if (IS_ENABLED(CONFIG_BT_MESH_OP_AGG_SRV) && + bt_mesh_op_agg_srv_accept(ctx, msg)) + { + return bt_mesh_op_agg_srv_send(model, msg); + } + else if (IS_ENABLED(CONFIG_BT_MESH_OP_AGG_CLI) && + bt_mesh_op_agg_cli_accept(ctx, msg)) + { + return bt_mesh_op_agg_cli_send(model, msg); + } + + if (!bt_mesh_model_has_key(model, ctx->app_idx)) + { + LOG_ERR("Model not bound to AppKey 0x%04x", ctx->app_idx); + return -EINVAL; + } + +#if defined CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG + /* No sense to use delayable message for unicast loopback. */ + + if (ctx->rnd_delay && !(bt_mesh_has_addr(ctx->addr) && + BT_MESH_ADDR_IS_UNICAST(ctx->addr))) + { + return bt_mesh_delayable_msg_manage(ctx, msg, + bt_mesh_model_elem(model)->rt->addr, cb, + cb_data); + } +#endif + + return bt_mesh_access_send(ctx, msg, bt_mesh_model_elem(model)->rt->addr, + cb, + cb_data); +} + +int bt_mesh_model_publish(const struct bt_mesh_model *model) +{ + struct bt_mesh_model_pub *pub = model->pub; + + if (!pub) + { + return -ENOTSUP; + } + + LOG_DBG(""); + + if (pub->addr == BT_MESH_ADDR_UNASSIGNED) + { + return -EADDRNOTAVAIL; + } + + if (!pub->msg || !pub->msg->len) + { + LOG_ERR("No publication message"); + return -EINVAL; + } + + if (pub->msg->len + BT_MESH_MIC_SHORT > BT_MESH_TX_SDU_MAX) + { + LOG_ERR("Message does not fit maximum SDU size"); + return -EMSGSIZE; + } + + if (pub->count) + { + LOG_WRN("Clearing publish retransmit timer"); + } + + /* Account for initial transmission */ + + pub->count = BT_MESH_PUB_MSG_TOTAL(pub); + pub->period_start = (uint32_t)TICK2MSEC(clock_systime_ticks()); + + LOG_DBG("Publish Retransmit Count %u Interval %ums", pub->count, + BT_MESH_PUB_TRANSMIT_INT(pub->retransmit)); + + /* Delay the publication for longer time when the publication is triggered + * manually (section 3.7.3.1): + * + * When the publication of a message is the result of a power-up, a state + * transition progress update, or completion of a state transition, + * multiple nodes may be reporting the state change at the same time. To + * reduce the probability of a message collision, these messages should be + * sent with a random delay between 20 and 500 milliseconds. + */ + + if (!!pub->delayable && !pub_delay_schedule(pub, RANDOM_DELAY_LONG)) + { + return 0; + } + + k_work_reschedule(&pub->timer, 0); + + return 0; +} + +const struct bt_mesh_model *bt_mesh_model_find_vnd( + const struct bt_mesh_elem *elem, uint16_t company, uint16_t id) +{ + uint8_t i; + + for (i = 0U; i < elem->vnd_model_count; i++) + { + if (elem->vnd_models[i].vnd.company == company && + elem->vnd_models[i].vnd.id == id) + { + return &elem->vnd_models[i]; + } + } + + return NULL; +} + +const struct bt_mesh_model *bt_mesh_model_find( + const struct bt_mesh_elem *elem, uint16_t id) +{ + uint8_t i; + + for (i = 0U; i < elem->model_count; i++) + { + if (elem->models[i].id == id) + { + return &elem->models[i]; + } + } + + return NULL; +} + +const struct bt_mesh_comp *bt_mesh_comp_get(void) +{ + return dev_comp; +} + +void bt_mesh_model_extensions_walk(const struct bt_mesh_model *model, + enum bt_mesh_walk (*cb)( + const struct bt_mesh_model *mod, + void *user_data), + void *user_data) +{ +#ifndef CONFIG_BT_MESH_MODEL_EXTENSIONS + (void)cb(model, user_data); + return; +#else + const struct bt_mesh_model *it; + + if (cb(model, user_data) == BT_MESH_WALK_STOP || !model->rt->next) + { + return; + } + + /* List is circular. Step through all models until we reach the start: */ + + for (it = model->rt->next; it != model; it = it->rt->next) + { + if (cb(it, user_data) == BT_MESH_WALK_STOP) + { + return; + } + } +#endif +} + +#ifdef CONFIG_BT_MESH_MODEL_EXTENSIONS +/* For vendor models, determine the offset within the model relation list + * by counting the number of standard SIG models in the associated element. + */ + +static uint8_t get_sig_offset(const struct bt_mesh_model *mod) +{ + const struct bt_mesh_elem *elem = bt_mesh_model_elem(mod); + uint8_t i; + + for (i = 0U; i < elem->vnd_model_count; i++) + { + if (&elem->vnd_models[i] == mod) + { + return elem->model_count; + } + } + + return 0; +} + +static int mod_rel_register( + const struct bt_mesh_model *base, const struct bt_mesh_model *ext, + uint8_t type) +{ + LOG_DBG(""); + struct mod_relation extension = + { + base->rt->elem_idx, + base->rt->mod_idx + get_sig_offset(base), + ext->rt->elem_idx, + ext->rt->mod_idx + get_sig_offset(ext), + type, + }; + + int i; + + for (i = 0; i < ARRAY_SIZE(mod_rel_list); i++) + { + if (mod_rel_list[i].elem_base == 0 && mod_rel_list[i].idx_base == 0 && + mod_rel_list[i].elem_ext == 0 && mod_rel_list[i].idx_ext == 0) + { + memcpy(&mod_rel_list[i], &extension, sizeof(extension)); + return 0; + } + } + + LOG_ERR("CONFIG_BT_MESH_MODEL_EXTENSION_LIST_SIZE is too small"); + return -ENOMEM; +} + +int bt_mesh_model_extend(const struct bt_mesh_model *extending_mod, + const struct bt_mesh_model *base_mod) +{ + const struct bt_mesh_model *a = extending_mod; + const struct bt_mesh_model *b = base_mod; + const struct bt_mesh_model *a_next = a->rt->next; + const struct bt_mesh_model *b_next = b->rt->next; + const struct bt_mesh_model *it; + + base_mod->rt->flags |= BT_MESH_MOD_EXTENDED; + + if (a == b) + { + return 0; + } + + /* Check if a's list contains b */ + + for (it = a; (it != NULL) && (it->rt->next != a); it = it->rt->next) + { + if (it == b) + { + goto register_extension; + } + } + + /* Merge lists */ + + if (a_next) + { + b->rt->next = a_next; + } + else + { + b->rt->next = a; + } + + if (b_next) + { + a->rt->next = b_next; + } + else + { + a->rt->next = b; + } + +register_extension: + if (MOD_REL_LIST_SIZE > 0) + { + return mod_rel_register(base_mod, extending_mod, RELATION_TYPE_EXT); + } + else if (IS_ENABLED(CONFIG_BT_MESH_COMP_PAGE_1)) + { + LOG_ERR("CONFIG_BT_MESH_MODEL_EXTENSION_LIST_SIZE is too small"); + return -ENOMEM; + } + + return 0; +} + +int bt_mesh_model_correspond(const struct bt_mesh_model *corresponding_mod, + const struct bt_mesh_model *base_mod) +{ + int i; + int err; + uint8_t cor_id = 0; + + if (MOD_REL_LIST_SIZE == 0) + { + return -ENOTSUP; + } + + uint8_t base_offset = get_sig_offset(base_mod); + uint8_t corresponding_offset = get_sig_offset(corresponding_mod); + + MOD_REL_LIST_FOR_EACH(i) + { + if (mod_rel_list[i].type < RELATION_TYPE_EXT && + mod_rel_list[i].type >= cor_id) + { + cor_id = mod_rel_list[i].type + 1; + } + + if ((IS_MOD_BASE(base_mod, i, base_offset) || + IS_MOD_EXTENSION(base_mod, i, base_offset) || + IS_MOD_BASE(corresponding_mod, i, corresponding_offset) || + IS_MOD_EXTENSION(corresponding_mod, i, corresponding_offset)) && + mod_rel_list[i].type < RELATION_TYPE_EXT) + { + return mod_rel_register(base_mod, corresponding_mod, + mod_rel_list[i].type); + } + } + + err = mod_rel_register(base_mod, corresponding_mod, cor_id); + if (err) + { + return err; + } + + return 0; +} +#endif /* CONFIG_BT_MESH_MODEL_EXTENSIONS */ + +bool bt_mesh_model_is_extended(const struct bt_mesh_model *model) +{ + return model->rt->flags & BT_MESH_MOD_EXTENDED; +} + +static int mod_set_bind( + const struct bt_mesh_model *mod, size_t len_rd, settings_read_cb read_cb, + void *cb_arg) +{ + ssize_t len; + int i; + + /* Start with empty array regardless of cleared or set value */ + + for (i = 0; i < mod->keys_cnt; i++) + { + mod->keys[i] = BT_MESH_KEY_UNUSED; + } + + if (len_rd == 0) + { + LOG_DBG("Cleared bindings for model"); + return 0; + } + + len = read_cb(cb_arg, mod->keys, mod->keys_cnt * sizeof(mod->keys[0])); + if (len < 0) + { + LOG_ERR("Failed to read value (err %zd)", len); + return len; + } + + LOG_HEXDUMP_DBG(mod->keys, len, "val"); + + LOG_DBG("Decoded %zu bound keys for model", len / sizeof(mod->keys[0])); + return 0; +} + +static int mod_set_sub( + const struct bt_mesh_model *mod, size_t len_rd, settings_read_cb read_cb, + void *cb_arg) +{ + size_t size = mod->groups_cnt * sizeof(mod->groups[0]); + ssize_t len; + + /* Start with empty array regardless of cleared or set value */ + + (void)memset(mod->groups, 0, size); + + if (len_rd == 0) + { + LOG_DBG("Cleared subscriptions for model"); + return 0; + } + + len = read_cb(cb_arg, mod->groups, size); + if (len < 0) + { + LOG_ERR("Failed to read value (err %zd)", len); + return len; + } + + LOG_HEXDUMP_DBG(mod->groups, len, "val"); + + LOG_DBG("Decoded %zu subscribed group addresses for model", + len / sizeof(mod->groups[0])); + + return 0; +} + +static int mod_set_sub_va( + const struct bt_mesh_model *mod, size_t len_rd, settings_read_cb read_cb, + void *cb_arg) +{ +#if CONFIG_BT_MESH_LABEL_COUNT > 0 + uint16_t uuidxs[CONFIG_BT_MESH_LABEL_COUNT]; + ssize_t len; + int i; + int count; + + /* Start with empty array regardless of cleared or set value */ + + (void)memset(mod->uuids, 0, + CONFIG_BT_MESH_LABEL_COUNT * sizeof(mod->uuids[0])); + + if (len_rd == 0) + { + LOG_DBG("Cleared subscriptions for model"); + return 0; + } + + len = read_cb(cb_arg, uuidxs, sizeof(uuidxs)); + if (len < 0) + { + LOG_ERR("Failed to read value (err %zd)", len); + return len; + } + + LOG_HEXDUMP_DBG(uuidxs, len, "val"); + + for (i = 0, count = 0; i < len / sizeof(uint16_t); i++) + { + mod->uuids[count] = bt_mesh_va_get_uuid_by_idx(uuidxs[i]); + if (mod->uuids[count] != NULL) + { + count++; + } + } + + LOG_DBG("Decoded %zu subscribed virtual addresses for model", count); +#endif /* CONFIG_BT_MESH_LABEL_COUNT > 0 */ + return 0; +} + +static int mod_set_pub( + const struct bt_mesh_model *mod, size_t len_rd, settings_read_cb read_cb, + void *cb_arg) +{ + struct mod_pub_val pub; + int err; + + if (!mod->pub) + { + LOG_WRN("Model has no publication context!"); + return -EINVAL; + } + + if (len_rd == 0) + { + mod->pub->addr = BT_MESH_ADDR_UNASSIGNED; + mod->pub->key = 0U; + mod->pub->cred = 0U; + mod->pub->ttl = 0U; + mod->pub->period = 0U; + mod->pub->retransmit = 0U; + mod->pub->count = 0U; + mod->pub->uuid = NULL; + + LOG_DBG("Cleared publication for model"); + return 0; + } + + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return 0; + } + + err = bt_mesh_settings_set(read_cb, cb_arg, &pub, sizeof(pub)); + if (err) + { + LOG_ERR("Failed to set \'model-pub\'"); + return err; + } + + if (BT_MESH_ADDR_IS_VIRTUAL(pub.base.addr)) + { + mod->pub->uuid = bt_mesh_va_get_uuid_by_idx(pub.uuidx); + } + + mod->pub->addr = pub.base.addr; + mod->pub->key = pub.base.key; + mod->pub->cred = pub.base.cred; + mod->pub->ttl = pub.base.ttl; + mod->pub->period = pub.base.period; + mod->pub->retransmit = pub.base.retransmit; + mod->pub->period_div = pub.base.period_div; + mod->pub->count = 0U; + + LOG_DBG("Restored model publication, dst 0x%04x app_idx 0x%03x", + pub.base.addr, pub.base.key); + + return 0; +} + +static int mod_data_set( + const struct bt_mesh_model *mod, const char *name, size_t len_rd, + settings_read_cb read_cb, void *cb_arg) +{ + const char *next; + + settings_name_next(name, &next); + + if (mod->cb && mod->cb->settings_set) + { + return mod->cb->settings_set(mod, next, len_rd, read_cb, cb_arg); + } + + return 0; +} + +static int mod_set( + bool vnd, const char *name, size_t len_rd, settings_read_cb read_cb, + void *cb_arg) +{ + const struct bt_mesh_model *mod; + uint8_t elem_idx; + uint8_t mod_idx; + uint16_t mod_key; + const char *next; + + if (!name) + { + LOG_ERR("Insufficient number of arguments"); + return -ENOENT; + } + + mod_key = strtol(name, NULL, 16); + elem_idx = mod_key >> 8; + mod_idx = mod_key; + + LOG_DBG("Decoded mod_key 0x%04x as elem_idx %u mod_idx %u", mod_key, + elem_idx, mod_idx); + + mod = bt_mesh_model_get(vnd, elem_idx, mod_idx); + if (!mod) + { + LOG_ERR("Failed to get model for elem_idx %u mod_idx %u", elem_idx, + mod_idx); + return -ENOENT; + } + + settings_name_next(name, &next); + if (!next) + { + LOG_ERR("Insufficient number of arguments"); + return -ENOENT; + } + + /* Call settings_name_next() again to get length of `next`. */ + + switch (settings_name_next(next, NULL)) + { + case 4: + if (!strncmp(next, "bind", 4)) + { + return mod_set_bind(mod, len_rd, read_cb, cb_arg); + } + else if (!strncmp(next, "subv", 4)) + { + return mod_set_sub_va(mod, len_rd, read_cb, cb_arg); + } + else if (!strncmp(next, "data", 4)) + { + return mod_data_set(mod, next, len_rd, read_cb, cb_arg); + } + + break; + case 3: + if (!strncmp(next, "sub", 3)) + { + return mod_set_sub(mod, len_rd, read_cb, cb_arg); + } + else if (!strncmp(next, "pub", 3)) + { + return mod_set_pub(mod, len_rd, read_cb, cb_arg); + } + + break; + default: + break; + } + + LOG_WRN("Unknown module key %s", next); + return -ENOENT; +} + +static int sig_mod_set(const char *name, + size_t len_rd, settings_read_cb read_cb, void *cb_arg) +{ + return mod_set(false, name, len_rd, read_cb, cb_arg); +} + +BT_MESH_SETTINGS_DEFINE(sig_mod, "s", sig_mod_set); + +static int vnd_mod_set(const char *name, + size_t len_rd, settings_read_cb read_cb, void *cb_arg) +{ + return mod_set(true, name, len_rd, read_cb, cb_arg); +} + +BT_MESH_SETTINGS_DEFINE(vnd_mod, "v", vnd_mod_set); + +static int comp_set(const char *name, + size_t len_rd, settings_read_cb read_cb, void *cb_arg) +{ + /* Only need to know that the entry exists. Will load the contents on + * demand. + */ + + if (len_rd > 0) + { + atomic_set_bit(bt_mesh.flags, BT_MESH_COMP_DIRTY); + } + + return 0; +} + +BT_MESH_SETTINGS_DEFINE(comp, "cmp", comp_set); + +static void encode_mod_path( + const struct bt_mesh_model *mod, bool vnd, const char *key, char *path, + size_t path_len) +{ + uint16_t mod_key = (((uint16_t)mod->rt->elem_idx << 8) | mod->rt->mod_idx); + + if (vnd) + { + snprintf(path, path_len, "bt/mesh/v/%x/%s", mod_key, key); + } + else + { + snprintf(path, path_len, "bt/mesh/s/%x/%s", mod_key, key); + } +} + +static void store_pending_mod_bind(const struct bt_mesh_model *mod, bool vnd) +{ + uint16_t keys[CONFIG_BT_MESH_MODEL_KEY_COUNT]; + char path[20]; + int i; + int count; + int err; + + for (i = 0, count = 0; i < mod->keys_cnt; i++) + { + if (mod->keys[i] != BT_MESH_KEY_UNUSED) + { + keys[count++] = mod->keys[i]; + LOG_DBG("model key 0x%04x", mod->keys[i]); + } + } + + encode_mod_path(mod, vnd, "bind", path, sizeof(path)); + + if (count) + { + err = settings_save_one(path, keys, count * sizeof(keys[0])); + } + else + { + err = settings_delete(path); + } + + if (err) + { + LOG_ERR("Failed to store %s value", path); + } + else + { + LOG_DBG("Stored %s value", path); + } +} + +static void store_pending_mod_sub(const struct bt_mesh_model *mod, bool vnd) +{ + uint16_t groups[CONFIG_BT_MESH_MODEL_GROUP_COUNT]; + char path[20]; + int i; + int count; + int err; + + for (i = 0, count = 0; i < mod->groups_cnt; i++) + { + if (mod->groups[i] != BT_MESH_ADDR_UNASSIGNED) + { + groups[count++] = mod->groups[i]; + } + } + + encode_mod_path(mod, vnd, "sub", path, sizeof(path)); + + if (count) + { + err = settings_save_one(path, groups, count * sizeof(groups[0])); + } + else + { + err = settings_delete(path); + } + + if (err) + { + LOG_ERR("Failed to store %s value", path); + } + else + { + LOG_DBG("Stored %s value", path); + } +} + +static void store_pending_mod_sub_va(const struct bt_mesh_model *mod, + bool vnd) +{ +#if CONFIG_BT_MESH_LABEL_COUNT > 0 + uint16_t uuidxs[CONFIG_BT_MESH_LABEL_COUNT]; + char path[20]; + int i; + int count; + int err; + + for (i = 0, count = 0; i < CONFIG_BT_MESH_LABEL_COUNT; i++) + { + if (mod->uuids[i] != NULL) + { + err = bt_mesh_va_get_idx_by_uuid(mod->uuids[i], &uuidxs[count]); + if (!err) + { + count++; + } + } + } + + encode_mod_path(mod, vnd, "subv", path, sizeof(path)); + + if (count) + { + err = settings_save_one(path, uuidxs, count * sizeof(uuidxs[0])); + } + else + { + err = settings_delete(path); + } + + if (err) + { + LOG_ERR("Failed to store %s value", path); + } + else + { + LOG_DBG("Stored %s value", path); + } +#endif /* CONFIG_BT_MESH_LABEL_COUNT > 0 */ +} + +static void store_pending_mod_pub(const struct bt_mesh_model *mod, bool vnd) +{ + struct mod_pub_val pub = + { + 0 + }; + + char path[20]; + int err; + + encode_mod_path(mod, vnd, "pub", path, sizeof(path)); + + if (!mod->pub || mod->pub->addr == BT_MESH_ADDR_UNASSIGNED) + { + err = settings_delete(path); + } + else + { + pub.base.addr = mod->pub->addr; + pub.base.key = mod->pub->key; + pub.base.ttl = mod->pub->ttl; + pub.base.retransmit = mod->pub->retransmit; + pub.base.period = mod->pub->period; + pub.base.period_div = mod->pub->period_div; + pub.base.cred = mod->pub->cred; + + if (BT_MESH_ADDR_IS_VIRTUAL(mod->pub->addr)) + { + (void)bt_mesh_va_get_idx_by_uuid(mod->pub->uuid, &pub.uuidx); + } + + err = settings_save_one(path, &pub, sizeof(pub)); + } + + if (err) + { + LOG_ERR("Failed to store %s value", path); + } + else + { + LOG_DBG("Stored %s value", path); + } +} + +static void store_pending_mod( + const struct bt_mesh_model *mod, const struct bt_mesh_elem *elem, bool vnd, + bool primary, void *user_data) +{ + if (!mod->rt->flags) + { + return; + } + + if (mod->rt->flags & BT_MESH_MOD_BIND_PENDING) + { + mod->rt->flags &= ~BT_MESH_MOD_BIND_PENDING; + store_pending_mod_bind(mod, vnd); + } + + if (mod->rt->flags & BT_MESH_MOD_SUB_PENDING) + { + mod->rt->flags &= ~BT_MESH_MOD_SUB_PENDING; + store_pending_mod_sub(mod, vnd); + store_pending_mod_sub_va(mod, vnd); + } + + if (mod->rt->flags & BT_MESH_MOD_PUB_PENDING) + { + mod->rt->flags &= ~BT_MESH_MOD_PUB_PENDING; + store_pending_mod_pub(mod, vnd); + } + + if (mod->rt->flags & BT_MESH_MOD_DATA_PENDING) + { + mod->rt->flags &= ~BT_MESH_MOD_DATA_PENDING; + mod->cb->pending_store(mod); + } +} + +void bt_mesh_model_pending_store(void) +{ + bt_mesh_model_foreach(store_pending_mod, NULL); +} + +void bt_mesh_model_bind_store(const struct bt_mesh_model *mod) +{ + mod->rt->flags |= BT_MESH_MOD_BIND_PENDING; + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_MOD_PENDING); +} + +void bt_mesh_model_sub_store(const struct bt_mesh_model *mod) +{ + mod->rt->flags |= BT_MESH_MOD_SUB_PENDING; + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_MOD_PENDING); +} + +void bt_mesh_model_pub_store(const struct bt_mesh_model *mod) +{ + mod->rt->flags |= BT_MESH_MOD_PUB_PENDING; + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_MOD_PENDING); +} + +int bt_mesh_comp_data_get_page(struct net_buf_simple *buf, + size_t page, size_t offset) +{ + if (page == 0 || page == 128) + { + return bt_mesh_comp_data_get_page_0(buf, offset); + } + else if (IS_ENABLED(CONFIG_BT_MESH_COMP_PAGE_1) && (page == 1 || + page == 129)) + { + return bt_mesh_comp_data_get_page_1(buf, offset); + } + else if (IS_ENABLED(CONFIG_BT_MESH_COMP_PAGE_2) && (page == 2 || + page == 130)) + { + return bt_mesh_comp_data_get_page_2(buf, offset); + } + + return -EINVAL; +} + +size_t comp_page_0_size(void) +{ + const struct bt_mesh_comp *comp; + const struct bt_mesh_elem *elem; + size_t size = 10; /* Non-variable length params of comp page 0. */ + + comp = bt_mesh_comp_get(); + + for (int i = 0; i < comp->elem_count; i++) + { + elem = &comp->elem[i]; + size += bt_mesh_comp_elem_size(elem); + } + + return size; +} + +size_t comp_page_1_size(void) +{ + const struct bt_mesh_comp *comp; + size_t size = 0; + + comp = bt_mesh_comp_get(); + + for (int i = 0; i < comp->elem_count; i++) + { + size += page1_elem_size(&comp->elem[i]); + } + + return size; +} + +size_t comp_page_2_size(void) +{ + size_t size = 0; + + if (!dev_comp2) + { + LOG_ERR("Composition data P2 not registered"); + return size; + } + + for (int i = 0; i < dev_comp2->record_cnt; i++) + { + size += + 8 + dev_comp2->record[i].elem_offset_cnt + + dev_comp2->record[i].data_len; + } + + return size; +} + +size_t bt_mesh_comp_page_size(uint8_t page) +{ + if (page == 0 || page == 128) + { + return comp_page_0_size(); + } + else if (IS_ENABLED(CONFIG_BT_MESH_COMP_PAGE_1) && (page == 1 || + page == 129)) + { + return comp_page_1_size(); + } + else if (IS_ENABLED(CONFIG_BT_MESH_COMP_PAGE_2) && (page == 2 || + page == 130)) + { + return comp_page_2_size(); + } + + return 0; +} + +int bt_mesh_comp_store(void) +{ + NET_BUF_SIMPLE_DEFINE(buf, CONFIG_BT_MESH_COMP_PST_BUF_SIZE); + int err; + + for (int i = 0; i < ARRAY_SIZE(comp_data_pages); i++) + { + size_t page_size = bt_mesh_comp_page_size(i); + + if (page_size > CONFIG_BT_MESH_COMP_PST_BUF_SIZE) + { + LOG_WRN("CDP%d is larger than the CDP persistence buffer. " + "Please increase the CDP persistence buffer size " + "to the required size (%d bytes)", + i, page_size); + } + + net_buf_simple_reset(&buf); + + err = bt_mesh_comp_data_get_page(&buf, comp_data_pages[i].page, 0); + if (err) + { + LOG_ERR("Failed to read CDP%d: %d", comp_data_pages[i].page, err); + return err; + } + + err = settings_save_one(comp_data_pages[i].path, buf.data, buf.len); + if (err) + { + LOG_ERR("Failed to store CDP%d: %d", comp_data_pages[i].page, err); + return err; + } + + LOG_DBG("Stored CDP%d", comp_data_pages[i].page); + } + + return 0; +} + +int bt_mesh_comp_change_prepare(void) +{ + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return -ENOTSUP; + } + + return bt_mesh_comp_store(); +} + +static void comp_data_clear(void) +{ + int err; + + for (int i = 0; i < ARRAY_SIZE(comp_data_pages); i++) + { + err = settings_delete(comp_data_pages[i].path); + if (err) + { + LOG_ERR("Failed to clear CDP%d: %d", comp_data_pages[i].page, err); + } + } + + atomic_clear_bit(bt_mesh.flags, BT_MESH_COMP_DIRTY); +} + +static int read_comp_cb( + const char *key, size_t len, settings_read_cb read_cb, void *cb_arg, + void *param) +{ + struct net_buf_simple *buf = param; + + if (len > net_buf_simple_tailroom(buf)) + { + return -ENOBUFS; + } + + len = read_cb(cb_arg, net_buf_simple_tail(buf), len); + if (len > 0) + { + net_buf_simple_add(buf, len); + } + + return -EALREADY; +} + +int bt_mesh_comp_read(struct net_buf_simple *buf, uint8_t page) +{ + size_t original_len = buf->len; + int i; + int err; + + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return -ENOTSUP; + } + + for (i = 0; i < ARRAY_SIZE(comp_data_pages); i++) + { + if (comp_data_pages[i].page == page) + { + break; + } + } + + if (i == ARRAY_SIZE(comp_data_pages)) + { + return -ENOENT; + } + + err = settings_load_subtree_direct(comp_data_pages[i].path, read_comp_cb, + buf); + + if (err) + { + LOG_ERR("Failed reading composition data: %d", err); + return err; + } + + if (buf->len == original_len) + { + return -ENOENT; + } + + return 0; +} + +int bt_mesh_model_data_store( + const struct bt_mesh_model *mod, bool vnd, const char *name, + const void *data, size_t data_len) +{ + char path[30]; + int err; + + encode_mod_path(mod, vnd, "data", path, sizeof(path)); + if (name) + { + strcat(path, "/"); + strncat(path, name, SETTINGS_MAX_DIR_DEPTH); + } + + if (data_len) + { + err = settings_save_one(path, data, data_len); + } + else + { + err = settings_delete(path); + } + + if (err) + { + LOG_ERR("Failed to store %s value", path); + } + else + { + LOG_DBG("Stored %s value", path); + } + + return err; +} + +#if defined(CONFIG_BT_MESH_LARGE_COMP_DATA_SRV) +static int metadata_set(const char *name, + size_t len_rd, + settings_read_cb read_cb, + void *cb_arg) +{ + /* Only need to know that the entry exists. Will load the contents on + * demand. + */ + + if (len_rd > 0) + { + atomic_set_bit(bt_mesh.flags, BT_MESH_METADATA_DIRTY); + } + + return 0; +} + +BT_MESH_SETTINGS_DEFINE(metadata, "metadata", metadata_set); + +int bt_mesh_models_metadata_store(void) +{ + NET_BUF_SIMPLE_DEFINE(buf, CONFIG_BT_MESH_MODELS_METADATA_PAGE_LEN); + size_t total_size; + int err; + + total_size = bt_mesh_metadata_page_0_size(); + LOG_DBG("bt/mesh/metadata total %d", total_size); + + net_buf_simple_init(&buf, 0); + net_buf_simple_add_le16(&buf, total_size); + + err = bt_mesh_metadata_get_page_0(&buf, 0); + if (err == -E2BIG) + { + LOG_ERR("Metadata too large"); + return err; + } + + if (err) + { + LOG_ERR("Failed to read models metadata: %d", err); + return err; + } + + LOG_DBG("bt/mesh/metadata len %d", buf.len); + + err = settings_save_one("bt/mesh/metadata", buf.data, buf.len); + if (err) + { + LOG_ERR("Failed to store models metadata: %d", err); + } + else + { + LOG_DBG("Stored models metadata"); + } + + return err; +} + +int bt_mesh_models_metadata_read(struct net_buf_simple *buf, size_t offset) +{ + NET_BUF_SIMPLE_DEFINE(stored_buf, CONFIG_BT_MESH_MODELS_METADATA_PAGE_LEN); + size_t original_len = buf->len; + int err; + + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return -ENOTSUP; + } + + net_buf_simple_init(&stored_buf, 0); + + err = settings_load_subtree_direct("bt/mesh/metadata", read_comp_cb, + &stored_buf); + if (err) + { + LOG_ERR("Failed reading models metadata: %d", err); + return err; + } + + /* First two bytes are total length */ + + offset += 2; + + net_buf_simple_add_mem(buf, &stored_buf.data, + MIN(net_buf_simple_tailroom(buf), 2)); + + if (offset >= stored_buf.len) + { + return 0; + } + + net_buf_simple_add_mem(buf, &stored_buf.data[offset], + MIN(net_buf_simple_tailroom(buf), + stored_buf.len - offset)); + + LOG_DBG("metadata read %d", buf->len); + + if (buf->len == original_len) + { + return -ENOENT; + } + + return 0; +} +#endif + +static void models_metadata_clear(void) +{ + int err; + + err = settings_delete("bt/mesh/metadata"); + if (err) + { + LOG_ERR("Failed to clear models metadata: %d", err); + } + else + { + LOG_DBG("Cleared models metadata"); + } + + atomic_clear_bit(bt_mesh.flags, BT_MESH_METADATA_DIRTY); +} + +void bt_mesh_comp_data_pending_clear(void) +{ + comp_data_clear(); + models_metadata_clear(); +} + +void bt_mesh_comp_data_clear(void) +{ + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_COMP_PENDING); +} + +int bt_mesh_models_metadata_change_prepare(void) +{ +#if defined(CONFIG_BT_MESH_LARGE_COMP_DATA_SRV) + return bt_mesh_models_metadata_store(); +#else + return -ENOTSUP; +#endif +} + +static void commit_mod( + const struct bt_mesh_model *mod, const struct bt_mesh_elem *elem, bool vnd, + bool primary, void *user_data) +{ + if (mod->pub + && mod->pub->update + && mod->pub->addr != BT_MESH_ADDR_UNASSIGNED) + { + int32_t ms = bt_mesh_model_pub_period_get(mod); + + if (ms > 0) + { + /* Delay the first publication after power-up for longer time + * (section 3.7.3.1): + * + * When the publication of a message is the result of a power-up, a + * state transition progress update, or completion of a state + * transition, multiple nodes may be reporting the state change at + * the same time. To reduce the probability of a message collision, + * these messages should be sent with a random delay between 20 and + * 500 milliseconds. + */ + + uint16_t random; + + random = + !!mod->pub->delayable ? + pub_delay_get(RANDOM_DELAY_LONG) : 0; + + LOG_DBG("Starting publish timer (period %u ms, delay %u ms)", ms, + random); + k_work_schedule(&mod->pub->timer, MSEC2TICK(ms + random)); + } + } + + if (!IS_ENABLED(CONFIG_BT_MESH_LOW_POWER)) + { + return; + } + + for (int i = 0; i < mod->groups_cnt; i++) + { + if (mod->groups[i] != BT_MESH_ADDR_UNASSIGNED) + { + bt_mesh_lpn_group_add(mod->groups[i]); + } + } +} + +void bt_mesh_model_settings_commit(void) +{ + bt_mesh_model_foreach(commit_mod, NULL); +} + +void bt_mesh_model_data_store_schedule(const struct bt_mesh_model *mod) +{ + mod->rt->flags |= BT_MESH_MOD_DATA_PENDING; + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_MOD_PENDING); +} + +uint8_t bt_mesh_comp_parse_page(struct net_buf_simple *buf) +{ + uint8_t page = net_buf_simple_pull_u8(buf); + + if (page >= 130U && IS_ENABLED(CONFIG_BT_MESH_COMP_PAGE_2) && + (atomic_test_bit(bt_mesh.flags, + BT_MESH_COMP_DIRTY) || IS_ENABLED(CONFIG_BT_MESH_RPR_SRV))) + { + page = 130U; + } + else if (page >= 129U && IS_ENABLED(CONFIG_BT_MESH_COMP_PAGE_1) && + (atomic_test_bit(bt_mesh.flags, BT_MESH_COMP_DIRTY) || + IS_ENABLED(CONFIG_BT_MESH_RPR_SRV))) + { + page = 129U; + } + else if (page >= 128U && (atomic_test_bit(bt_mesh.flags, + BT_MESH_COMP_DIRTY) || + IS_ENABLED(CONFIG_BT_MESH_RPR_SRV))) + { + page = 128U; + } + else if (page >= 2U && IS_ENABLED(CONFIG_BT_MESH_COMP_PAGE_2)) + { + page = 2U; + } + else if (page >= 1U && IS_ENABLED(CONFIG_BT_MESH_COMP_PAGE_1)) + { + page = 1U; + } + else if (page != 0U) + { + LOG_DBG("Composition page %u not available", page); + page = 0U; + } + + return page; +} + +void bt_mesh_access_init(void) +{ +#if defined CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG + bt_mesh_delayable_msg_init(); +#endif +} + +void bt_mesh_access_suspend(void) +{ +#if defined CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG + bt_mesh_delayable_msg_stop(); +#endif +} + +void bt_mesh_access_reset(void) +{ +#if defined CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG + bt_mesh_delayable_msg_stop(); +#endif +} diff --git a/libs/zblue/mesh/access.h b/libs/zblue/mesh/access.h new file mode 100644 index 0000000000000..f53c756ed2cb9 --- /dev/null +++ b/libs/zblue/mesh/access.h @@ -0,0 +1,150 @@ +/**************************************************************************** + * libs/zblue/mesh/access.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Tree walk return codes */ + +enum bt_mesh_walk +{ + BT_MESH_WALK_STOP, + BT_MESH_WALK_CONTINUE, +}; + +/* bt_mesh_model.flags */ + +enum +{ + BT_MESH_MOD_BIND_PENDING = BIT(0), + BT_MESH_MOD_SUB_PENDING = BIT(1), + BT_MESH_MOD_PUB_PENDING = BIT(2), + BT_MESH_MOD_EXTENDED = BIT(3), + BT_MESH_MOD_DEVKEY_ONLY = BIT(4), + BT_MESH_MOD_DATA_PENDING = BIT(5), +}; + +void bt_mesh_elem_register(const struct bt_mesh_elem *elem, uint8_t count); + +uint8_t bt_mesh_elem_count(void); +size_t bt_mesh_comp_page_size(uint8_t page); +int bt_mesh_comp_data_get_page_0(struct net_buf_simple *buf, size_t offset); +size_t bt_mesh_metadata_page_0_size(void); +int bt_mesh_metadata_get_page_0(struct net_buf_simple *buf, size_t offset); + +/* Find local element based on unicast address */ + +const struct bt_mesh_elem *bt_mesh_elem_find(uint16_t addr); + +bool bt_mesh_has_addr(uint16_t addr); +bool bt_mesh_model_has_key(const struct bt_mesh_model *mod, uint16_t key); + +void bt_mesh_model_extensions_walk(const struct bt_mesh_model *root, + enum bt_mesh_walk (*cb)( + const struct bt_mesh_model *mod, + void *user_data), + void *user_data); + +uint16_t *bt_mesh_model_find_group(const struct bt_mesh_model **mod, + uint16_t addr); +const uint8_t **bt_mesh_model_find_uuid(const struct bt_mesh_model **mod, + const uint8_t *uuid); + +void bt_mesh_model_foreach(void (*func)(const struct bt_mesh_model *mod, + const struct bt_mesh_elem *elem, + bool vnd, + bool primary, + void *user_data), + void *user_data); + +int32_t bt_mesh_model_pub_period_get(const struct bt_mesh_model *mod); + +void bt_mesh_comp_provision(uint16_t addr); +void bt_mesh_comp_unprovision(void); + +uint16_t bt_mesh_primary_addr(void); + +const struct bt_mesh_comp *bt_mesh_comp_get(void); + +const struct bt_mesh_model *bt_mesh_model_get(bool vnd, + uint8_t elem_idx, + uint8_t mod_idx); + +int bt_mesh_access_recv(struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf); +int bt_mesh_model_recv(struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf); + +int bt_mesh_comp_register(const struct bt_mesh_comp *comp); +int bt_mesh_comp_store(void); +int bt_mesh_comp_read(struct net_buf_simple *buf, uint8_t page); +uint8_t bt_mesh_comp_parse_page(struct net_buf_simple *buf); + +int bt_mesh_models_metadata_store(void); +int bt_mesh_models_metadata_read(struct net_buf_simple *buf, size_t offset); + +void bt_mesh_comp_data_pending_clear(void); +void bt_mesh_comp_data_clear(void); +int bt_mesh_comp_data_get_page(struct net_buf_simple *buf, + size_t page, size_t offset); + +void bt_mesh_model_pending_store(void); +void bt_mesh_model_bind_store(const struct bt_mesh_model *mod); +void bt_mesh_model_sub_store(const struct bt_mesh_model *mod); +void bt_mesh_model_pub_store(const struct bt_mesh_model *mod); +void bt_mesh_model_settings_commit(void); + +/** @brief Register a callback function hook for mesh model messages. + * + * Register a callback function to act as a hook for receiving mesh model + * layer messages directly to the application without having instantiated the + * relevant models. + * + * @param cb A pointer to the callback function. + */ + +void bt_mesh_msg_cb_set( +void (*cb)(uint32_t opcode, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf)); + +/** @brief Send a mesh model message. + * + * Send a mesh model layer message out into the mesh network without having + * instantiated the relevant mesh models. + * + * @param ctx The Bluetooth Mesh message context. + * @param buf The message payload. + * @param src_addr The source address of model + * @param cb Callback function. + * @param cb_data Callback data. + * + * @return 0 on success or negative error code on failure. + */ + +int bt_mesh_access_send( + struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf, uint16_t src_addr, + const struct bt_mesh_send_cb *cb, void *cb_data); + +/** @brief Initialize the Access layer. + * + * Initialize the delayable message mechanism if it has been enabled. + */ + +void bt_mesh_access_init(void); + +/** @brief Suspend the Access layer. + */ + +void bt_mesh_access_suspend(void); + +/** @brief Reset the Access layer. + */ + +void bt_mesh_access_reset(void); diff --git a/libs/zblue/mesh/adv.c b/libs/zblue/mesh/adv.c new file mode 100644 index 0000000000000..97ad1bce217de --- /dev/null +++ b/libs/zblue/mesh/adv.c @@ -0,0 +1,489 @@ +/**************************************************************************** + * libs/zblue/mesh/adv.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2018 Nordic Semiconductor ASA + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include + +#include "common/bt_str.h" + +#include "net.h" +#include "foundation.h" +#include "beacon.h" +#include "prov.h" +#include "proxy.h" +#include "pb_gatt_srv.h" +#include "solicitation.h" +#include "statistic.h" + +#define LOG_LEVEL CONFIG_BT_MESH_ADV_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_adv); + +/* Window and Interval are equal for continuous scanning */ +#define MESH_SCAN_INTERVAL BT_MESH_ADV_SCAN_UNIT(BT_MESH_SCAN_INTERVAL_MS) +#define MESH_SCAN_WINDOW BT_MESH_ADV_SCAN_UNIT(BT_MESH_SCAN_WINDOW_MS) + +const uint8_t bt_mesh_adv_type[BT_MESH_ADV_TYPES] = +{ + [BT_MESH_ADV_PROV] = BT_DATA_MESH_PROV, + [BT_MESH_ADV_DATA] = BT_DATA_MESH_MESSAGE, + [BT_MESH_ADV_BEACON] = BT_DATA_MESH_BEACON, + [BT_MESH_ADV_URI] = BT_DATA_URI, +}; + +static bool active_scanning; +static K_FIFO_DEFINE(bt_mesh_adv_queue); +static K_FIFO_DEFINE(bt_mesh_relay_queue); +static K_FIFO_DEFINE(bt_mesh_friend_queue); + +K_MEM_SLAB_DEFINE_STATIC_TYPE(local_adv_pool, + struct bt_mesh_adv, + CONFIG_BT_MESH_ADV_BUF_COUNT); + +#if defined(CONFIG_BT_MESH_RELAY_BUF_COUNT) +K_MEM_SLAB_DEFINE_STATIC_TYPE(relay_adv_pool, + struct bt_mesh_adv, + CONFIG_BT_MESH_RELAY_BUF_COUNT); +#endif + +#if defined(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE) +K_MEM_SLAB_DEFINE_STATIC_TYPE(friend_adv_pool, + struct bt_mesh_adv, + CONFIG_BT_MESH_FRIEND_LPN_COUNT); +#endif + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_mesh_adv_send_start(uint16_t duration, + int err, struct bt_mesh_adv_ctx *ctx) +{ + if (!ctx->started) + { + ctx->started = 1; + + if (ctx->cb && ctx->cb->start) + { + ctx->cb->start(duration, err, ctx->cb_data); + } + + if (err) + { + ctx->cb = NULL; + } + else if (IS_ENABLED(CONFIG_BT_MESH_STATISTIC)) + { + bt_mesh_stat_succeeded_count(ctx); + } + } +} + +void bt_mesh_adv_send_end(int err, struct bt_mesh_adv_ctx const *ctx) +{ + if (ctx->started && ctx->cb && ctx->cb->end) + { + ctx->cb->end(err, ctx->cb_data); + } +} + +static struct bt_mesh_adv *adv_create_from_pool(struct k_mem_slab *buf_pool, + enum bt_mesh_adv_type type, + enum bt_mesh_adv_tag tag, + uint8_t xmit, + clock_t timeout) +{ + struct bt_mesh_adv_ctx *ctx; + struct bt_mesh_adv *adv; + int err; + + if (atomic_test_bit(bt_mesh.flags, BT_MESH_SUSPENDED)) + { + LOG_WRN("Refusing to allocate buffer while suspended"); + return NULL; + } + + err = k_mem_slab_alloc(buf_pool, (void **)&adv, timeout); + if (err) + { + return NULL; + } + + adv->__ref = 1; + + net_buf_simple_init_with_data(&adv->b, adv->__bufs, BT_MESH_ADV_DATA_SIZE); + net_buf_simple_reset(&adv->b); + + ctx = &adv->ctx; + + (void)memset(ctx, 0, sizeof(*ctx)); + + ctx->type = type; + ctx->tag = tag; + ctx->xmit = xmit; + + return adv; +} + +struct bt_mesh_adv *bt_mesh_adv_ref(struct bt_mesh_adv *adv) +{ + DEBUGASSERT(adv->__ref < UINT8_MAX); + + adv->__ref++; + + return adv; +} + +void bt_mesh_adv_unref(struct bt_mesh_adv *adv) +{ + DEBUGASSERT(adv->__ref > 0); + + if (--adv->__ref > 0) + { + return; + } + + struct k_mem_slab *slab = &local_adv_pool; + +#if (defined(CONFIG_BT_MESH_RELAY) || defined(CONFIG_BT_MESH_BRG_CFG_SRV)) + if (adv->ctx.tag == BT_MESH_ADV_TAG_RELAY) + { + slab = &relay_adv_pool; + } +#endif + +#if defined(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE) + if (adv->ctx.tag == BT_MESH_ADV_TAG_FRIEND) + { + slab = &friend_adv_pool; + } +#endif + + k_mem_slab_free(slab, (void *)adv); +} + +struct bt_mesh_adv *bt_mesh_adv_create( + enum bt_mesh_adv_type type, enum bt_mesh_adv_tag tag, uint8_t xmit, + clock_t timeout) +{ +#if (defined(CONFIG_BT_MESH_RELAY) || defined(CONFIG_BT_MESH_BRG_CFG_SRV)) + if (tag == BT_MESH_ADV_TAG_RELAY) + { + return adv_create_from_pool(&relay_adv_pool, type, tag, xmit, timeout); + } +#endif + +#if defined(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE) + if (tag == BT_MESH_ADV_TAG_FRIEND) + { + return adv_create_from_pool(&friend_adv_pool, + type, + tag, + xmit, + timeout); + } +#endif + + return adv_create_from_pool(&local_adv_pool, type, tag, xmit, timeout); +} + +static struct bt_mesh_adv *process_events(struct k_poll_event *ev, int count) +{ + for (; count; ev++, count--) + { + LOG_DBG("ev->state %u", ev->state); + + switch (ev->state) + { + case K_POLL_STATE_FIFO_DATA_AVAILABLE: + return k_fifo_get(ev->fifo, 0); + case K_POLL_STATE_NOT_READY: + case K_POLL_STATE_CANCELLED: + break; + default: + LOG_WRN("Unexpected k_poll event state %u", ev->state); + break; + } + } + + return NULL; +} + +struct bt_mesh_adv *bt_mesh_adv_get(clock_t timeout) +{ + int err; + struct k_poll_event events[] = + { + K_POLL_EVENT_STATIC_INITIALIZER(K_POLL_TYPE_FIFO_DATA_AVAILABLE, + K_POLL_MODE_NOTIFY_ONLY, + + &bt_mesh_adv_queue, 0), +#if (defined(CONFIG_BT_MESH_RELAY) || defined(CONFIG_BT_MESH_BRG_CFG_SRV)) && \ + (defined(CONFIG_BT_MESH_ADV_LEGACY) || \ + defined(CONFIG_BT_MESH_ADV_EXT_RELAY_USING_MAIN_ADV_SET) || \ + !(CONFIG_BT_MESH_RELAY_ADV_SETS)) + K_POLL_EVENT_STATIC_INITIALIZER(K_POLL_TYPE_FIFO_DATA_AVAILABLE, + K_POLL_MODE_NOTIFY_ONLY, + + &bt_mesh_relay_queue, 0), +#endif + }; + + err = k_poll(events, ARRAY_SIZE(events), timeout); + if (err) + { + return NULL; + } + + return process_events(events, ARRAY_SIZE(events)); +} + +struct bt_mesh_adv *bt_mesh_adv_get_by_tag(enum bt_mesh_adv_tag_bit tags, + clock_t timeout) +{ + if (IS_ENABLED(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE) + && tags & BT_MESH_ADV_TAG_BIT_FRIEND) + { + return k_fifo_get(&bt_mesh_friend_queue, timeout); + } + + if ((IS_ENABLED(CONFIG_BT_MESH_RELAY) + || IS_ENABLED(CONFIG_BT_MESH_BRG_CFG_SRV)) && + !(tags & BT_MESH_ADV_TAG_BIT_LOCAL)) + { + return k_fifo_get(&bt_mesh_relay_queue, timeout); + } + + if (IS_ENABLED(CONFIG_BT_MESH_ADV_EXT_GATT_SEPARATE) + && tags & BT_MESH_ADV_TAG_BIT_PROXY) + { + return NULL; + } + + return bt_mesh_adv_get(timeout); +} + +void bt_mesh_adv_get_cancel(void) +{ + LOG_DBG(""); + + k_fifo_cancel_wait(&bt_mesh_adv_queue); + + if ((IS_ENABLED(CONFIG_BT_MESH_RELAY) || + IS_ENABLED(CONFIG_BT_MESH_BRG_CFG_SRV))) + { + k_fifo_cancel_wait(&bt_mesh_relay_queue); + } + + if (IS_ENABLED(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE)) + { + k_fifo_cancel_wait(&bt_mesh_friend_queue); + } +} + +void bt_mesh_adv_send(struct bt_mesh_adv *adv, + const struct bt_mesh_send_cb *cb, void *cb_data) +{ + LOG_DBG("type 0x%02x len %u: %s", adv->ctx.type, adv->b.len, + bt_hex(adv->b.data, adv->b.len)); + + if (atomic_test_bit(bt_mesh.flags, BT_MESH_SUSPENDED)) + { + LOG_WRN("Sending advertisement while suspended"); + } + + adv->ctx.cb = cb; + adv->ctx.cb_data = cb_data; + adv->ctx.busy = 1U; + + if (IS_ENABLED(CONFIG_BT_MESH_STATISTIC)) + { + bt_mesh_stat_planned_count(&adv->ctx); + } + + if (IS_ENABLED(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE) && + adv->ctx.tag == BT_MESH_ADV_TAG_FRIEND) + { + k_fifo_put(&bt_mesh_friend_queue, bt_mesh_adv_ref(adv)); + bt_mesh_adv_friend_ready(); + return; + } + + if (((IS_ENABLED(CONFIG_BT_MESH_RELAY) || + IS_ENABLED(CONFIG_BT_MESH_BRG_CFG_SRV)) && + adv->ctx.tag == BT_MESH_ADV_TAG_RELAY) || + (IS_ENABLED(CONFIG_BT_MESH_PB_ADV_USE_RELAY_SETS) && + adv->ctx.tag == BT_MESH_ADV_TAG_PROV)) + { + k_fifo_put(&bt_mesh_relay_queue, bt_mesh_adv_ref(adv)); + bt_mesh_adv_relay_ready(); + return; + } + + k_fifo_put(&bt_mesh_adv_queue, bt_mesh_adv_ref(adv)); + bt_mesh_adv_local_ready(); +} + +int bt_mesh_adv_gatt_send(void) +{ + if (bt_mesh_is_provisioned()) + { + if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY)) + { + LOG_DBG("Proxy Advertising"); + return bt_mesh_proxy_adv_start(); + } + } + else if (IS_ENABLED(CONFIG_BT_MESH_PB_GATT)) + { + LOG_DBG("PB-GATT Advertising"); + return bt_mesh_pb_gatt_srv_adv_start(); + } + + return -ENOTSUP; +} + +static void bt_mesh_scan_cb( + const bt_addr_le_t *addr, int8_t rssi, uint8_t adv_type, + struct net_buf_simple *buf) +{ + if (adv_type != BT_GAP_ADV_TYPE_ADV_NONCONN_IND) + { + return; + } + + LOG_DBG("len %u: %s", buf->len, bt_hex(buf->data, buf->len)); + + while (buf->len > 1) + { + struct net_buf_simple_state state; + uint8_t len; + uint8_t type; + + len = net_buf_simple_pull_u8(buf); + + /* Check for early termination */ + + if (len == 0U) + { + return; + } + + if (len > buf->len) + { + LOG_WRN("AD malformed"); + return; + } + + net_buf_simple_save(buf, &state); + + type = net_buf_simple_pull_u8(buf); + + buf->len = len - 1; + + switch (type) + { + case BT_DATA_MESH_MESSAGE: + bt_mesh_net_recv(buf, rssi, BT_MESH_NET_IF_ADV); + break; +#if defined(CONFIG_BT_MESH_PB_ADV) + case BT_DATA_MESH_PROV: + bt_mesh_pb_adv_recv(buf); + break; +#endif + case BT_DATA_MESH_BEACON: + bt_mesh_beacon_recv(buf); + break; + case BT_DATA_UUID16_SOME: + + /* Fall through */ + + case BT_DATA_UUID16_ALL: + if (IS_ENABLED(CONFIG_BT_MESH_OD_PRIV_PROXY_SRV)) + { + /* Restore buffer with Solicitation PDU */ + + net_buf_simple_restore(buf, &state); + bt_mesh_sol_recv(buf, len - 1); + } + break; + default: + break; + } + + net_buf_simple_restore(buf, &state); + net_buf_simple_pull(buf, len); + } +} + +int bt_mesh_scan_active_set(bool active) +{ + if (active_scanning == active) + { + return 0; + } + + active_scanning = active; + bt_mesh_scan_disable(); + return bt_mesh_scan_enable(); +} + +int bt_mesh_scan_enable(void) +{ + struct bt_le_scan_param scan_param = + { + .type = active_scanning ? BT_LE_SCAN_TYPE_ACTIVE + : BT_LE_SCAN_TYPE_PASSIVE, + .interval = MESH_SCAN_INTERVAL, + .window = MESH_SCAN_WINDOW + }; + + int err; + + LOG_DBG(""); + + err = bt_le_scan_start(&scan_param, bt_mesh_scan_cb); + if (err && err != -EALREADY) + { + LOG_ERR("starting scan failed (err %d)", err); + return err; + } + + return 0; +} + +int bt_mesh_scan_disable(void) +{ + int err; + + LOG_DBG(""); + + err = bt_le_scan_stop(); + if (err && err != -EALREADY) + { + LOG_ERR("stopping scan failed (err %d)", err); + return err; + } + + return 0; +} diff --git a/libs/zblue/mesh/adv.h b/libs/zblue/mesh/adv.h new file mode 100644 index 0000000000000..d70d7a2b91f35 --- /dev/null +++ b/libs/zblue/mesh/adv.h @@ -0,0 +1,152 @@ +/**************************************************************************** + * libs/zblue/mesh/adv.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#ifndef ZEPHYR_SUBSYS_BLUETOOTH_MESH_ADV_H_ +#define ZEPHYR_SUBSYS_BLUETOOTH_MESH_ADV_H_ + +/* Maximum advertising data payload for a single data type */ +#define BT_MESH_ADV_DATA_SIZE 29 + +#define BT_MESH_ADV_SCAN_UNIT(_ms) ((_ms) * 8 / 5) +#define BT_MESH_SCAN_INTERVAL_MS 30 +#define BT_MESH_SCAN_WINDOW_MS 30 + +enum bt_mesh_adv_type +{ + BT_MESH_ADV_PROV, + BT_MESH_ADV_DATA, + BT_MESH_ADV_BEACON, + BT_MESH_ADV_URI, + + BT_MESH_ADV_TYPES, +}; + +enum bt_mesh_adv_tag +{ + BT_MESH_ADV_TAG_LOCAL, + BT_MESH_ADV_TAG_RELAY, + BT_MESH_ADV_TAG_PROXY, + BT_MESH_ADV_TAG_FRIEND, + BT_MESH_ADV_TAG_PROV, +}; + +enum bt_mesh_adv_tag_bit +{ + BT_MESH_ADV_TAG_BIT_LOCAL = BIT(BT_MESH_ADV_TAG_LOCAL), + BT_MESH_ADV_TAG_BIT_RELAY = BIT(BT_MESH_ADV_TAG_RELAY), + BT_MESH_ADV_TAG_BIT_PROXY = BIT(BT_MESH_ADV_TAG_PROXY), + BT_MESH_ADV_TAG_BIT_FRIEND = BIT(BT_MESH_ADV_TAG_FRIEND), + BT_MESH_ADV_TAG_BIT_PROV = BIT(BT_MESH_ADV_TAG_PROV), +}; + +struct bt_mesh_adv_ctx +{ + const struct bt_mesh_send_cb *cb; + void *cb_data; + + uint8_t type : 2; + uint8_t started : 1; + uint8_t busy : 1; + uint8_t tag : 4; + + uint8_t xmit; +}; + +struct bt_mesh_adv +{ + void *adv_bearer; + +#if defined(CONFIG_BT_MESH_GATT) + void *gatt_bearer[CONFIG_BT_MAX_CONN]; +#endif + + struct bt_mesh_adv_ctx ctx; + + struct net_buf_simple b; + + uint8_t __ref; + + uint8_t __bufs[BT_MESH_ADV_DATA_SIZE]; +}; + +/* Lookup table for Advertising data types for bt_mesh_adv_type: */ + +extern const uint8_t bt_mesh_adv_type[BT_MESH_ADV_TYPES]; + +struct bt_mesh_adv *bt_mesh_adv_ref(struct bt_mesh_adv *adv); +void bt_mesh_adv_unref(struct bt_mesh_adv *adv); + +/* xmit_count: Number of retransmissions, i.e. 0 == 1 transmission */ + +struct bt_mesh_adv *bt_mesh_adv_create( + enum bt_mesh_adv_type type, enum bt_mesh_adv_tag tag, uint8_t xmit, + clock_t timeout); + +void bt_mesh_adv_send(struct bt_mesh_adv *adv, + const struct bt_mesh_send_cb *cb, void *cb_data); +void bt_mesh_adv_send_end(int err, struct bt_mesh_adv_ctx const *ctx); + +struct bt_mesh_adv *bt_mesh_adv_get(clock_t timeout); + +struct bt_mesh_adv *bt_mesh_adv_get_by_tag(enum bt_mesh_adv_tag_bit tags, + clock_t timeout); + +void bt_mesh_adv_gatt_update(void); + +void bt_mesh_adv_get_cancel(void); + +void bt_mesh_adv_init(void); + +int bt_mesh_scan_enable(void); + +int bt_mesh_scan_disable(void); + +int bt_mesh_adv_enable(void); + +int bt_mesh_adv_disable(void); + +void bt_mesh_adv_local_ready(void); + +void bt_mesh_adv_relay_ready(void); + +int bt_mesh_adv_terminate(struct bt_mesh_adv *adv); + +void bt_mesh_adv_friend_ready(void); + +int bt_mesh_adv_gatt_send(void); + +int bt_mesh_adv_gatt_start( + const struct bt_le_adv_param *param, int32_t duration, + const struct bt_data *ad, size_t ad_len, const struct bt_data *sd, + size_t sd_len); + +void bt_mesh_adv_send_start(uint16_t duration, + int err, struct bt_mesh_adv_ctx *ctx); + +int bt_mesh_scan_active_set(bool active); + +int bt_mesh_adv_bt_data_send( + uint8_t num_events, uint16_t adv_interval, const struct bt_data *ad, + size_t ad_len); + +int bt_mesh_wq_submit(struct k_work *work); + +#endif /* ZEPHYR_SUBSYS_BLUETOOTH_MESH_ADV_H_ */ diff --git a/libs/zblue/mesh/adv_ext.c b/libs/zblue/mesh/adv_ext.c new file mode 100644 index 0000000000000..8d97fadcdf8d1 --- /dev/null +++ b/libs/zblue/mesh/adv_ext.c @@ -0,0 +1,747 @@ +/**************************************************************************** + * libs/zblue/mesh/adv_ext.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2021 Xiaomi Corporation + * Copyright (c) 2018 Nordic Semiconductor ASA Copyright (c) 2017 Intel + * Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include + +#include "common/bt_str.h" + +#include "host/hci_core.h" + +#include "net.h" +#include "proxy.h" +#include "solicitation.h" + +#define LOG_LEVEL CONFIG_BT_MESH_ADV_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_adv_ext); + +/* Convert from ms to 0.625ms units */ +#define ADV_INT_FAST_MS 20 + +#ifndef CONFIG_BT_MESH_RELAY_ADV_SETS +#define CONFIG_BT_MESH_RELAY_ADV_SETS 0 +#endif + +#ifdef CONFIG_BT_MESH_ADV_STACK_SIZE +#define MESH_WORKQ_PRIORITY CONFIG_BT_MESH_ADV_PRIO +#define MESH_WORKQ_STACK_SIZE CONFIG_BT_MESH_ADV_STACK_SIZE +#else +#define MESH_WORKQ_PRIORITY 0 +#define MESH_WORKQ_STACK_SIZE 0 +#endif + +enum +{ + /** Controller is currently advertising */ + + ADV_FLAG_ACTIVE, + + /** Advertising sending completed */ + + ADV_FLAG_SENT, + + /** Currently performing proxy advertising */ + + ADV_FLAG_PROXY, + /** Custom adv params have been set, we need to update the parameters on + * the next send. + */ + + ADV_FLAG_UPDATE_PARAMS, + + /** The advertiser is suspending. */ + + ADV_FLAG_SUSPENDING, + + /* Number of adv flags. */ + + ADV_FLAGS_NUM +}; + +struct bt_mesh_ext_adv +{ + const enum bt_mesh_adv_tag_bit tags; + atomic_define(flags, ADV_FLAGS_NUM); + struct bt_le_ext_adv *instance; + struct bt_mesh_adv *adv; + uint32_t timestamp; + struct k_work work; + struct bt_le_adv_param adv_param; +}; + +static void send_pending_adv(struct k_work *work); +static bool schedule_send(struct bt_mesh_ext_adv *ext_adv); + +static struct k_work_q bt_mesh_workq; +static uint64_t thread_stack[MESH_WORKQ_STACK_SIZE / sizeof(uint64_t)]; + +#if defined(CONFIG_BT_MESH_WORKQ_MESH) +#define MESH_WORKQ &bt_mesh_workq +#else /* CONFIG_BT_MESH_WORKQ_SYS */ +#define MESH_WORKQ &k_sys_work_q +#endif /* CONFIG_BT_MESH_WORKQ_MESH */ + +static struct bt_mesh_ext_adv advs[] = +{ + [0] = + { + .tags = ( +#if !defined(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE) + BT_MESH_ADV_TAG_BIT_FRIEND | +#endif +#if !defined(CONFIG_BT_MESH_ADV_EXT_GATT_SEPARATE) + BT_MESH_ADV_TAG_BIT_PROXY | +#endif /* !CONFIG_BT_MESH_ADV_EXT_GATT_SEPARATE */ +#if defined(CONFIG_BT_MESH_ADV_EXT_RELAY_USING_MAIN_ADV_SET) + BT_MESH_ADV_TAG_BIT_RELAY | +#endif /* CONFIG_BT_MESH_ADV_EXT_RELAY_USING_MAIN_ADV_SET */ +#if defined(CONFIG_BT_MESH_PB_ADV) + BT_MESH_ADV_TAG_BIT_PROV | +#endif /* CONFIG_BT_MESH_PB_ADV */ + BT_MESH_ADV_TAG_BIT_LOCAL), + .work = Z_WORK_INITIALIZER(send_pending_adv), + }, +#if CONFIG_BT_MESH_RELAY_ADV_SETS + [1 ... CONFIG_BT_MESH_RELAY_ADV_SETS] = + { + .tags = ( +#if (defined(CONFIG_BT_MESH_RELAY) || defined(CONFIG_BT_MESH_BRG_CFG_SRV)) + BT_MESH_ADV_TAG_BIT_RELAY | +#endif /* CONFIG_BT_MESH_RELAY || CONFIG_BT_MESH_BRG_CFG_SRV */ +#if defined(CONFIG_BT_MESH_PB_ADV_USE_RELAY_SETS) + BT_MESH_ADV_TAG_BIT_PROV | +#endif /* CONFIG_BT_MESH_PB_ADV_USE_RELAY_SETS */ + 0), + .work = Z_WORK_INITIALIZER(send_pending_adv), + }, +#endif /* CONFIG_BT_MESH_RELAY_ADV_SETS */ +#if defined(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE) + { + .tags = BT_MESH_ADV_TAG_BIT_FRIEND, + .work = Z_WORK_INITIALIZER(send_pending_adv), + }, +#endif /* CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE */ +#if defined(CONFIG_BT_MESH_ADV_EXT_GATT_SEPARATE) + { + .tags = BT_MESH_ADV_TAG_BIT_PROXY, + .work = Z_WORK_INITIALIZER(send_pending_adv), + }, +#endif /* CONFIG_BT_MESH_ADV_EXT_GATT_SEPARATE */ +}; + +BUILD_ASSERT(ARRAY_SIZE(advs) <= CONFIG_BT_EXT_ADV_MAX_ADV_SET, + "Insufficient adv instances"); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static inline struct bt_mesh_ext_adv *relay_adv_get(void) +{ + if (!!(CONFIG_BT_MESH_RELAY_ADV_SETS)) + { + return &advs[1]; + } + else + { + return &advs[0]; + } +} + +static inline struct bt_mesh_ext_adv *gatt_adv_get(void) +{ + if (IS_ENABLED(CONFIG_BT_MESH_ADV_EXT_GATT_SEPARATE)) + { + return &advs[ARRAY_SIZE(advs) - 1]; + } + else + { + return &advs[0]; + } +} + +static int adv_start( + struct bt_mesh_ext_adv *ext_adv, const struct bt_le_adv_param *param, + struct bt_le_ext_adv_start_param *start, const struct bt_data *ad, + size_t ad_len, const struct bt_data *sd, size_t sd_len) +{ + int err; + + if (!ext_adv->instance) + { + LOG_ERR("Mesh advertiser not enabled"); + return -ENODEV; + } + + if (atomic_test_and_set_bit(ext_adv->flags, ADV_FLAG_ACTIVE)) + { + LOG_ERR("Advertiser is busy"); + return -EBUSY; + } + + if (atomic_test_bit(ext_adv->flags, ADV_FLAG_UPDATE_PARAMS)) + { + err = bt_le_ext_adv_update_param(ext_adv->instance, param); + if (err) + { + LOG_ERR("Failed updating adv params: %d", err); + atomic_clear_bit(ext_adv->flags, ADV_FLAG_ACTIVE); + return err; + } + + atomic_set_bit_to(ext_adv->flags, ADV_FLAG_UPDATE_PARAMS, + param != &ext_adv->adv_param); + } + + err = bt_le_ext_adv_set_data(ext_adv->instance, ad, ad_len, sd, sd_len); + if (err) + { + LOG_ERR("Failed setting adv data: %d", err); + atomic_clear_bit(ext_adv->flags, ADV_FLAG_ACTIVE); + return err; + } + + ext_adv->timestamp = (uint32_t)TICK2MSEC(clock_systime_ticks()); + + err = bt_le_ext_adv_start(ext_adv->instance, start); + if (err) + { + LOG_ERR("Advertising failed: err %d", err); + atomic_clear_bit(ext_adv->flags, ADV_FLAG_ACTIVE); + } + + return err; +} + +static int bt_data_send( + struct bt_mesh_ext_adv *ext_adv, uint8_t num_events, uint16_t adv_interval, + const struct bt_data *ad, size_t ad_len) +{ + struct bt_le_ext_adv_start_param start = + { + .num_events = num_events, + }; + + adv_interval = MAX(ADV_INT_FAST_MS, adv_interval); + + /* Only update advertising parameters if they're different */ + + if (ext_adv->adv_param.interval_min != BT_MESH_ADV_SCAN_UNIT(adv_interval)) + { + ext_adv->adv_param.interval_min = BT_MESH_ADV_SCAN_UNIT(adv_interval); + ext_adv->adv_param.interval_max = ext_adv->adv_param.interval_min; + atomic_set_bit(ext_adv->flags, ADV_FLAG_UPDATE_PARAMS); + } + + return adv_start(ext_adv, + &ext_adv->adv_param, + &start, + ad, + ad_len, + NULL, + 0); +} + +static int adv_send(struct bt_mesh_ext_adv *ext_adv, struct bt_mesh_adv *adv) +{ + uint8_t num_events = BT_MESH_TRANSMIT_COUNT(adv->ctx.xmit) + 1; + uint16_t duration; + uint16_t adv_int; + struct bt_data ad; + int err; + + adv_int = BT_MESH_TRANSMIT_INT(adv->ctx.xmit); + + /* Upper boundary estimate: */ + + duration = num_events * (adv_int + 10); + + LOG_DBG("type %u len %u: " + "%s", adv->ctx.type, adv->b.len, bt_hex(adv->b.data, + adv->b.len)); + LOG_DBG("count %u interval %ums duration %ums", num_events, adv_int, + duration); + + ad.type = bt_mesh_adv_type[adv->ctx.type]; + ad.data_len = adv->b.len; + ad.data = adv->b.data; + + err = bt_data_send(ext_adv, num_events, adv_int, &ad, 1); + if (!err) + { + ext_adv->adv = bt_mesh_adv_ref(adv); + } + + bt_mesh_adv_send_start(duration, err, &adv->ctx); + + return err; +} + +static int stop_proxy_adv(struct bt_mesh_ext_adv *ext_adv) +{ + int err; + + if (atomic_test_bit(ext_adv->flags, ADV_FLAG_PROXY)) + { + err = bt_le_ext_adv_stop(ext_adv->instance); + if (err) + { + LOG_ERR("Failed to stop proxy advertising: %d", err); + return err; + } + + atomic_clear_bit(ext_adv->flags, ADV_FLAG_PROXY); + atomic_clear_bit(ext_adv->flags, ADV_FLAG_ACTIVE); + } + + return 0; +} + +static int adv_queue_send_process(struct bt_mesh_ext_adv *ext_adv) +{ + struct bt_mesh_adv *adv; + int err = -ENOENT; + + while ((adv = bt_mesh_adv_get_by_tag(ext_adv->tags, 0))) + { + /* busy == 0 means this was canceled */ + + if (!adv->ctx.busy) + { + bt_mesh_adv_unref(adv); + continue; + } + + if (stop_proxy_adv(ext_adv)) + { + LOG_WRN("Advertising %p canceled due to proxy adv failed to stop", + adv); + bt_mesh_adv_send_start(0, -ECANCELED, &adv->ctx); + bt_mesh_adv_unref(adv); + continue; + } + + adv->ctx.busy = 0U; + err = adv_send(ext_adv, adv); + + bt_mesh_adv_unref(adv); + + if (!err) + { + return 0; /* Wait for advertising to finish */ + } + } + + return err; +} + +static void start_proxy_sol_or_proxy_adv(struct bt_mesh_ext_adv *ext_adv) +{ + if (ext_adv->instance == NULL) + { + LOG_DBG("Advertiser is suspended or deleted"); + return; + } + + if (!(ext_adv->tags & BT_MESH_ADV_TAG_BIT_PROXY)) + { + return; + } + + if (IS_ENABLED(CONFIG_BT_MESH_PROXY_SOLICITATION)) + { + if (stop_proxy_adv(ext_adv)) + { + return; + } + + if (!bt_mesh_sol_send()) + { + return; + } + } + + if (IS_ENABLED(CONFIG_BT_MESH_GATT_SERVER)) + { + if (stop_proxy_adv(ext_adv)) + { + return; + } + + if (!atomic_test_and_set_bit(ext_adv->flags, ADV_FLAG_PROXY)) + { + if (bt_mesh_adv_gatt_send()) + { + atomic_clear_bit(ext_adv->flags, ADV_FLAG_PROXY); + return; + } + } + } +} + +static void send_pending_adv(struct k_work *work) +{ + static const char *const adv_tag_to_str[] = + { + [BT_MESH_ADV_TAG_LOCAL] = + "local", [BT_MESH_ADV_TAG_RELAY] = "relay", + [BT_MESH_ADV_TAG_PROXY] = + "proxy", [BT_MESH_ADV_TAG_FRIEND] = "friend", + [BT_MESH_ADV_TAG_PROV] = "prov", + }; + + struct bt_mesh_ext_adv *ext_adv; + int err; + + ext_adv = CONTAINER_OF(work, struct bt_mesh_ext_adv, work); + + if (atomic_test_bit(ext_adv->flags, ADV_FLAG_SUSPENDING)) + { + LOG_DBG("Advertiser is suspending"); + return; + } + + if (atomic_test_and_clear_bit(ext_adv->flags, ADV_FLAG_SENT)) + { + LOG_DBG("Advertising stopped after %u ms for %s adv", + (uint32_t)TICK2MSEC(clock_systime_ticks()) - + ext_adv->timestamp, + ext_adv->adv ? adv_tag_to_str[ext_adv->adv->ctx.tag] + : adv_tag_to_str[BT_MESH_ADV_TAG_PROXY]); + + atomic_clear_bit(ext_adv->flags, ADV_FLAG_ACTIVE); + atomic_clear_bit(ext_adv->flags, ADV_FLAG_PROXY); + + if (ext_adv->adv) + { + struct bt_mesh_adv_ctx ctx = ext_adv->adv->ctx; + + ext_adv->adv->ctx.started = 0; + bt_mesh_adv_unref(ext_adv->adv); + bt_mesh_adv_send_end(0, &ctx); + + ext_adv->adv = NULL; + } + } + + err = adv_queue_send_process(ext_adv); + if (!err) + { + return; + } + + start_proxy_sol_or_proxy_adv(ext_adv); +} + +static bool schedule_send(struct bt_mesh_ext_adv *ext_adv) +{ + if (atomic_test_bit(ext_adv->flags, ADV_FLAG_ACTIVE)) + { + /* We don't need to resubmit the `send_pending_adv` work if the mesh + * advertiser is sending a mesh packet. The `send_pending_adv` work is + * resubmitted when the current advertising is finished, which is done + * through the `adv_sent` callback. + * + * The proxy advertisement in turns doesn't timeout or stop quickly and + * has less priority than regular mesh messages, thus needs to be + * stopped immediately. + */ + + if (!atomic_test_bit(ext_adv->flags, ADV_FLAG_PROXY)) + { + return false; + } + } + else if (k_work_is_pending(&ext_adv->work) && + !atomic_test_bit(ext_adv->flags, ADV_FLAG_PROXY)) + { + /* This can happen if we try to schedule a send while a previous send + * is still pending in the work queue. There is nothing wrong with + * resubmitting the same work to the work queue, but in this case won't + * have a change to try a work from another advertising set which can + * be ready for sending. + * + * If, however, ADV_FLAG_PROXY is set, we want to stop the proxy + * advertising. + */ + + return false; + } + + bt_mesh_wq_submit(&ext_adv->work); + + return true; +} + +void bt_mesh_adv_gatt_update(void) +{ + (void)schedule_send(gatt_adv_get()); +} + +void bt_mesh_adv_local_ready(void) +{ + (void)schedule_send(advs); +} + +void bt_mesh_adv_relay_ready(void) +{ + struct bt_mesh_ext_adv *ext_adv = relay_adv_get(); + + for (int i = 0; i < CONFIG_BT_MESH_RELAY_ADV_SETS; i++) + { + if (schedule_send(&ext_adv[i])) + { + return; + } + } + + /* Use the main adv set for the sending of relay messages. */ + + if (IS_ENABLED(CONFIG_BT_MESH_ADV_EXT_RELAY_USING_MAIN_ADV_SET) || + CONFIG_BT_MESH_RELAY_ADV_SETS == 0) + { + (void)schedule_send(advs); + } +} + +void bt_mesh_adv_friend_ready(void) +{ + if (IS_ENABLED(CONFIG_BT_MESH_ADV_EXT_FRIEND_SEPARATE)) + { + schedule_send(&advs[1 + CONFIG_BT_MESH_RELAY_ADV_SETS]); + } + else + { + schedule_send(&advs[0]); + } +} + +static void adv_sent(struct bt_mesh_ext_adv *ext_adv) +{ + atomic_set_bit(ext_adv->flags, ADV_FLAG_SENT); + + bt_mesh_wq_submit(&ext_adv->work); +} + +int bt_mesh_adv_terminate(struct bt_mesh_adv *adv) +{ + int err; + + for (int i = 0; i < ARRAY_SIZE(advs); i++) + { + struct bt_mesh_ext_adv *ext_adv = &advs[i]; + + if (ext_adv->adv != adv) + { + continue; + } + + if (!atomic_test_bit(ext_adv->flags, ADV_FLAG_ACTIVE)) + { + return 0; + } + + err = bt_le_ext_adv_stop(ext_adv->instance); + if (err) + { + LOG_ERR("Failed to stop adv %d", err); + return err; + } + + /* Do not call `cb:end`, since this user action */ + + adv->ctx.cb = NULL; + + adv_sent(ext_adv); + + return 0; + } + + return -EINVAL; +} + +void bt_mesh_adv_init(void) +{ + struct bt_le_adv_param adv_param = + { + .id = BT_ID_DEFAULT, + .interval_min = BT_MESH_ADV_SCAN_UNIT(ADV_INT_FAST_MS), + .interval_max = BT_MESH_ADV_SCAN_UNIT(ADV_INT_FAST_MS), +#if defined(CONFIG_BT_MESH_DEBUG_USE_ID_ADDR) + .options = BT_LE_ADV_OPT_USE_IDENTITY, +#endif + }; + + for (int i = 0; i < ARRAY_SIZE(advs); i++) + { + (void)memcpy(&advs[i].adv_param, &adv_param, sizeof(adv_param)); + } + + if (IS_ENABLED(CONFIG_BT_MESH_WORKQ_MESH)) + { + k_work_queue_init(&bt_mesh_workq); + k_work_queue_start(&bt_mesh_workq, thread_stack, MESH_WORKQ_STACK_SIZE, + K_PRIO_COOP(MESH_WORKQ_PRIORITY), NULL); + } +} + +static struct bt_mesh_ext_adv *adv_instance_find( + struct bt_le_ext_adv *instance) +{ + for (int i = 0; i < ARRAY_SIZE(advs); i++) + { + if (advs[i].instance == instance) + { + return &advs[i]; + } + } + + return NULL; +} + +static void ext_adv_set_sent(struct bt_le_ext_adv *instance, + struct bt_le_ext_adv_sent_info *info) +{ + struct bt_mesh_ext_adv *ext_adv = adv_instance_find(instance); + + if (!ext_adv) + { + LOG_WRN("Unexpected adv instance"); + return; + } + + if (!atomic_test_bit(ext_adv->flags, ADV_FLAG_ACTIVE)) + { + LOG_DBG("Advertiser %p ADV_FLAG_ACTIVE not set", ext_adv); + return; + } + + adv_sent(ext_adv); +} + +int bt_mesh_adv_enable(void) +{ + int err; + + static const struct bt_le_ext_adv_cb adv_cb = + { + .sent = ext_adv_set_sent, + }; + + if (advs[0].instance) + { + /* Already initialized */ + + return 0; + } + + for (int i = 0; i < ARRAY_SIZE(advs); i++) + { + err = bt_le_ext_adv_create(&advs[i].adv_param, &adv_cb, + &advs[i].instance); + if (err) + { + return err; + } + } + + return 0; +} + +int bt_mesh_adv_disable(void) +{ + struct k_work_sync sync; + int err; + + for (int i = 0; i < ARRAY_SIZE(advs); i++) + { + atomic_set_bit(advs[i].flags, ADV_FLAG_SUSPENDING); + + if (nxsched_gettid() != k_work_queue_thread_get(MESH_WORKQ) || + (k_work_busy_get(&advs[i].work) & K_WORK_RUNNING) == 0) + { + k_work_flush(&advs[i].work, &sync); + } + + err = bt_le_ext_adv_stop(advs[i].instance); + if (err) + { + LOG_ERR("Failed to stop adv %d", err); + return err; + } + + err = bt_le_ext_adv_delete(advs[i].instance); + if (err) + { + LOG_ERR("Failed to delete adv %d", err); + return err; + } + + advs[i].instance = NULL; + + atomic_clear_bit(advs[i].flags, ADV_FLAG_SUSPENDING); + + /* `adv_sent` is called to finish transmission of an adv buffer that + * was pushed to the host before the advertiser was stopped, but did + * not finish. + */ + + adv_sent(&advs[i]); + } + + return 0; +} + +int bt_mesh_adv_gatt_start( + const struct bt_le_adv_param *param, int32_t duration, + const struct bt_data *ad, size_t ad_len, const struct bt_data *sd, + size_t sd_len) +{ + struct bt_mesh_ext_adv *ext_adv = gatt_adv_get(); + struct bt_le_ext_adv_start_param start = + { + /* Timeout is set in 10 ms steps, with 0 indicating "forever" */ + + .timeout = (duration == -1) ? 0 : MAX(1, duration / 10), + }; + + LOG_DBG("Start advertising %d ms", duration); + + atomic_set_bit(ext_adv->flags, ADV_FLAG_UPDATE_PARAMS); + + return adv_start(ext_adv, param, &start, ad, ad_len, sd, sd_len); +} + +int bt_mesh_adv_bt_data_send( + uint8_t num_events, uint16_t adv_interval, const struct bt_data *ad, + size_t ad_len) +{ + return bt_data_send(advs, num_events, adv_interval, ad, ad_len); +} + +int bt_mesh_wq_submit(struct k_work *work) +{ + return k_work_submit_to_queue(MESH_WORKQ, work); +} diff --git a/libs/zblue/mesh/adv_legacy.c b/libs/zblue/mesh/adv_legacy.c new file mode 100644 index 0000000000000..265050b88b372 --- /dev/null +++ b/libs/zblue/mesh/adv_legacy.c @@ -0,0 +1,362 @@ +/**************************************************************************** + * libs/zblue/mesh/adv_legacy.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2018 Nordic Semiconductor ASA + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include "common/bt_str.h" + +#include "host/hci_core.h" + +#include "net.h" +#include "foundation.h" +#include "beacon.h" +#include "prov.h" +#include "solicitation.h" + +#define LOG_LEVEL CONFIG_BT_MESH_ADV_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_adv_legacy); + +/* Pre-5.0 controllers enforce a minimum interval of 100ms + * whereas 5.0+ controllers can go down to 20ms. + */ +#define ADV_INT_DEFAULT_MS 100 +#define ADV_INT_FAST_MS 20 + +static pid_t adv_thread_pid = INVALID_PROCESS_ID; +static uint64_t adv_thread_stack[CONFIG_BT_MESH_ADV_STACK_SIZE / + sizeof(uint64_t)]; +static int32_t adv_timeout; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static bool is_mesh_suspended(void) +{ + return atomic_test_bit(bt_mesh.flags, BT_MESH_SUSPENDED); +} + +static int bt_data_send(uint8_t num_events, + uint16_t adv_int, + const struct bt_data *ad, + size_t ad_len, + struct bt_mesh_adv_ctx *ctx) +{ + struct bt_le_adv_param param = + { + }; + + uint64_t uptime = TICK2MSEC(clock_systime_ticks()); + uint16_t duration; + int err; + const int32_t adv_int_min = + ((bt_dev.hci_version >= + BT_HCI_VERSION_5_0) ? ADV_INT_FAST_MS : ADV_INT_DEFAULT_MS); + + adv_int = MAX(adv_int_min, adv_int); + + ARG_UNUSED(uptime); + + /* NuttX Bluetooth Low Energy Controller for mesh stack uses + * pre-emptible continuous scanning, allowing advertising events to be + * transmitted without delay when advertising is enabled. No need to + * compensate with scan window duration. An advertising event could be + * delayed by upto one interval when advertising is stopped and started in + * quick succession, hence add advertising interval to the total + * advertising duration. + */ + + duration = adv_int + num_events * (adv_int + 10); + + /* NuttX Bluetooth Low Energy Controller built for nRF51x SoCs use + * CONFIG_BT_CTLR_LOW_LAT=y, and continuous scanning cannot be preempted, + * hence, scanning will block advertising events from being transmitted. + * Increase the advertising duration by the amount of scan window duration + * to compensate for the blocked advertising events. + */ + + if (IS_ENABLED(CONFIG_BT_CTLR_LOW_LAT)) + { + duration += BT_MESH_SCAN_WINDOW_MS; + } + + LOG_DBG("count %u interval %ums duration %ums", num_events, adv_int, + duration); + + if (IS_ENABLED(CONFIG_BT_MESH_DEBUG_USE_ID_ADDR)) + { + param.options = BT_LE_ADV_OPT_USE_IDENTITY; + } + else + { + param.options = 0U; + } + + param.id = BT_ID_DEFAULT; + param.interval_min = BT_MESH_ADV_SCAN_UNIT(adv_int); + param.interval_max = param.interval_min; + + err = bt_le_adv_start(¶m, ad, ad_len, NULL, 0); + + if (err) + { + LOG_ERR("Advertising failed: err %d", err); + return err; + } + + LOG_DBG("Advertising started. Sleeping %u ms", duration); + + if (ctx) + { + bt_mesh_adv_send_start(duration, err, ctx); + } + + if (!is_mesh_suspended()) + { + nxsched_msleep(duration); + } + + err = bt_le_adv_stop(); + if (err) + { + LOG_ERR("Stopping advertising failed: err %d", err); + return err; + } + + LOG_DBG("Advertising stopped (%u ms)", + (uint32_t)(TICK2MSEC(clock_systime_ticks()) - uptime)); + + return 0; +} + +int bt_mesh_adv_bt_data_send(uint8_t num_events, + uint16_t adv_int, + const struct bt_data *ad, + size_t ad_len) +{ + return bt_data_send(num_events, adv_int, ad, ad_len, NULL); +} + +static inline void adv_send(struct bt_mesh_adv *adv) +{ + uint16_t num_events = BT_MESH_TRANSMIT_COUNT(adv->ctx.xmit) + 1; + uint16_t adv_int; + struct bt_data ad; + + adv_int = BT_MESH_TRANSMIT_INT(adv->ctx.xmit); + + LOG_DBG("type %u len %u: " + "%s", adv->ctx.type, adv->b.len, bt_hex(adv->b.data, + adv->b.len)); + + ad.type = bt_mesh_adv_type[adv->ctx.type]; + ad.data_len = adv->b.len; + ad.data = adv->b.data; + + bt_data_send(num_events, adv_int, &ad, 1, &adv->ctx); +} + +static void adv_pool_drain(void) +{ + struct bt_mesh_adv *adv; + + /* Empty the advertising pool when advertising is disabled */ + + while ((adv = bt_mesh_adv_get(0))) + { + bt_mesh_adv_send_start(0, -ENODEV, &adv->ctx); + bt_mesh_adv_unref(adv); + } +} + +static int adv_thread(int argc, char *argv[]) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + LOG_DBG("started"); + struct bt_mesh_adv *adv; + + while (true) + { + if (is_mesh_suspended()) + { + adv_pool_drain(); + nxsched_msleep(10); + continue; + } + + if (IS_ENABLED(CONFIG_BT_MESH_GATT_SERVER)) + { + adv = bt_mesh_adv_get(0); + if (IS_ENABLED(CONFIG_BT_MESH_PROXY_SOLICITATION) && !adv) + { + (void)bt_mesh_sol_send(); + } + + while (!adv && !is_mesh_suspended()) + { + /* Adv timeout may be set by a call from proxy + * to bt_mesh_adv_gatt_start: + */ + + adv_timeout = -1; + (void)bt_mesh_adv_gatt_send(); + + adv = bt_mesh_adv_get(adv_timeout == -1 ? + (clock_t)-1 : MSEC2TICK(adv_timeout)); + bt_le_adv_stop(); + + if (IS_ENABLED(CONFIG_BT_MESH_PROXY_SOLICITATION) && !adv) + { + (void)bt_mesh_sol_send(); + } + } + + if (is_mesh_suspended()) + { + continue; + } + } + else + { + adv = bt_mesh_adv_get(((clock_t)-1)); + } + + if (!adv) + { + continue; + } + + /* busy == 0 means this was canceled */ + + if (adv->ctx.busy) + { + adv->ctx.busy = 0U; + adv_send(adv); + } + + struct bt_mesh_adv_ctx ctx = adv->ctx; + + adv->ctx.started = 0; + bt_mesh_adv_unref(adv); + bt_mesh_adv_send_end(0, &ctx); + + /* Give other threads a chance to run */ + + sched_yield(); + } + + return 0; +} + +void bt_mesh_adv_local_ready(void) +{ + /* Will be handled automatically */ +} + +void bt_mesh_adv_relay_ready(void) +{ + /* Will be handled automatically */ +} + +void bt_mesh_adv_gatt_update(void) +{ + bt_mesh_adv_get_cancel(); +} + +int bt_mesh_adv_terminate(struct bt_mesh_adv *adv) +{ + ARG_UNUSED(adv); + + return 0; +} + +void bt_mesh_adv_init(void) +{ + if (adv_thread_pid != INVALID_PROCESS_ID) + { + return; + } + + adv_thread_pid = + kthread_create_with_stack("BT Mesh adv", + K_PRIO_COOP(CONFIG_BT_MESH_ADV_PRIO), + adv_thread_stack, + sizeof(adv_thread_stack), adv_thread, + NULL); + DEBUGASSERT(adv_thread_pid > 0); +} + +int bt_mesh_adv_enable(void) +{ + /* The advertiser thread relies on BT_MESH_SUSPENDED flag. No point in + * starting the advertiser thread if the flag is not set. + */ + + if (is_mesh_suspended()) + { + return -EINVAL; + } + + return 0; +} + +int bt_mesh_adv_disable(void) +{ + /* The advertiser thread is kept alive permanently. The flag is set by + * the higher layer function, and the thread will observe it and drain any + * pending advertising buffers without being joined or recreated. + */ + + if (!is_mesh_suspended()) + { + return -EINVAL; + } + + LOG_DBG("Advertising disabled"); + + return 0; +} + +int bt_mesh_adv_gatt_start(const struct bt_le_adv_param *param, + int32_t duration, + const struct bt_data *ad, + size_t ad_len, + const struct bt_data *sd, + size_t sd_len) +{ + adv_timeout = duration; + return bt_le_adv_start(param, ad, ad_len, sd, sd_len); +} + +int bt_mesh_wq_submit(struct k_work *work) +{ + return k_work_submit(work); +} diff --git a/libs/zblue/mesh/app_keys.c b/libs/zblue/mesh/app_keys.c new file mode 100644 index 0000000000000..0db169081c0f3 --- /dev/null +++ b/libs/zblue/mesh/app_keys.c @@ -0,0 +1,858 @@ +/**************************************************************************** + * libs/zblue/mesh/app_keys.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include "mesh.h" +#include "net.h" +#include "app_keys.h" +#include "rpl.h" +#include "settings.h" +#include "crypto.h" +#include "proxy.h" +#include "friend.h" +#include "foundation.h" +#include "access.h" + +#include "common/bt_str.h" + +#define LOG_LEVEL CONFIG_BT_MESH_KEYS_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_app_keys); + +/* Tracking of what storage changes are pending for App Keys. We track this + * in a separate array here instead of within the respective bt_mesh_app_key + * struct itself, since once a key gets deleted its struct becomes invalid + * and may be reused for other keys. + */ + +struct app_key_update +{ + uint16_t key_idx : 12; /* AppKey Index */ + uint16_t valid : 1; /* 1 if this entry is valid, 0 if not */ + uint16_t clear : 1; /* 1 if key needs clearing, 0 if storing */ +}; + +/* AppKey information for persistent storage. */ + +struct app_key_val +{ + uint16_t net_idx; + bool updated; + struct bt_mesh_key val[2]; +} __packed; + +static struct app_key_update app_key_updates[CONFIG_BT_MESH_APP_KEY_COUNT]; + +static struct bt_mesh_app_key apps[CONFIG_BT_MESH_APP_KEY_COUNT] = +{ + [0 ...(CONFIG_BT_MESH_APP_KEY_COUNT - 1)] = + { + .app_idx = BT_MESH_KEY_UNUSED, + .net_idx = BT_MESH_KEY_UNUSED, + } +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static struct bt_mesh_app_key *app_get(uint16_t app_idx) +{ + for (int i = 0; i < ARRAY_SIZE(apps); i++) + { + if (apps[i].app_idx == app_idx) + { + return &apps[i]; + } + } + + return NULL; +} + +static void clear_app_key(uint16_t app_idx) +{ + char path[20]; + int err; + + snprintf(path, sizeof(path), "bt/mesh/AppKey/%x", app_idx); + err = settings_delete(path); + if (err) + { + LOG_ERR("Failed to clear AppKeyIndex 0x%03x", app_idx); + } + else + { + LOG_DBG("Cleared AppKeyIndex 0x%03x", app_idx); + } +} + +static void store_app_key(uint16_t app_idx) +{ + const struct bt_mesh_app_key *app; + struct app_key_val key; + char path[20]; + int err; + + snprintf(path, sizeof(path), "bt/mesh/AppKey/%x", app_idx); + + app = app_get(app_idx); + if (!app) + { + LOG_WRN("ApKeyIndex 0x%03x not found", app_idx); + return; + } + + key.net_idx = app->net_idx, key.updated = app->updated, + + memcpy(&key.val[0], &app->keys[0].val, sizeof(struct bt_mesh_key)); + memcpy(&key.val[1], &app->keys[1].val, sizeof(struct bt_mesh_key)); + + err = settings_save_one(path, &key, sizeof(key)); + if (err) + { + LOG_ERR("Failed to store AppKey %s value", path); + } + else + { + LOG_DBG("Stored AppKey %s value", path); + } +} + +static struct app_key_update *app_key_update_find(uint16_t key_idx, + struct app_key_update **free_slot) +{ + struct app_key_update *match; + int i; + + match = NULL; + *free_slot = NULL; + + for (i = 0; i < ARRAY_SIZE(app_key_updates); i++) + { + struct app_key_update *update = &app_key_updates[i]; + + if (!update->valid) + { + *free_slot = update; + continue; + } + + if (update->key_idx == key_idx) + { + match = update; + } + } + + return match; +} + +static void update_app_key_settings(uint16_t app_idx, bool store) +{ + struct app_key_update *update; + struct app_key_update *free_slot; + uint8_t clear = store ? 0U : 1U; + + LOG_DBG("AppKeyIndex 0x%03x", app_idx); + + update = app_key_update_find(app_idx, &free_slot); + if (update) + { + update->clear = clear; + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_APP_KEYS_PENDING); + return; + } + + if (!free_slot) + { + if (store) + { + store_app_key(app_idx); + } + else + { + clear_app_key(app_idx); + } + + return; + } + + free_slot->valid = 1U; + free_slot->key_idx = app_idx; + free_slot->clear = clear; + + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_APP_KEYS_PENDING); +} + +static void app_key_evt(struct bt_mesh_app_key *app, + enum bt_mesh_key_evt evt) +{ + STRUCT_SECTION_FOREACH(bt_mesh_app_key_cb, cb) + { + cb->evt_handler(app, evt); + } +} + +static struct bt_mesh_app_key *app_key_alloc(uint16_t app_idx) +{ + struct bt_mesh_app_key *app = NULL; + + for (int i = 0; i < ARRAY_SIZE(apps); i++) + { + /* Check for already existing app_key */ + + if (apps[i].app_idx == app_idx) + { + return &apps[i]; + } + + if (!app && apps[i].app_idx == BT_MESH_KEY_UNUSED) + { + app = &apps[i]; + } + } + + return app; +} + +static void app_key_del(struct bt_mesh_app_key *app) +{ + LOG_DBG("AppIdx 0x%03x", app->app_idx); + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + update_app_key_settings(app->app_idx, false); + } + + app_key_evt(app, BT_MESH_KEY_DELETED); + + app->net_idx = BT_MESH_KEY_UNUSED; + app->app_idx = BT_MESH_KEY_UNUSED; + bt_mesh_key_destroy(&app->keys[0].val); + bt_mesh_key_destroy(&app->keys[1].val); + memset(app->keys, 0, sizeof(app->keys)); +} + +static void app_key_revoke(struct bt_mesh_app_key *app) +{ + if (!app->updated) + { + return; + } + + bt_mesh_key_destroy(&app->keys[0].val); + memcpy(&app->keys[0], &app->keys[1], sizeof(app->keys[0])); + memset(&app->keys[1], 0, sizeof(app->keys[1])); + app->updated = false; + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + update_app_key_settings(app->app_idx, true); + } + + app_key_evt(app, BT_MESH_KEY_REVOKED); +} + +uint8_t bt_mesh_app_key_add(uint16_t app_idx, + uint16_t net_idx, const uint8_t key[16]) +{ + struct bt_mesh_app_key *app; + + LOG_DBG("net_idx 0x%04x app_idx %04x val %s", net_idx, app_idx, bt_hex(key, + 16)); + + if (!bt_mesh_subnet_get(net_idx)) + { + return STATUS_INVALID_NETKEY; + } + + app = app_key_alloc(app_idx); + if (!app) + { + return STATUS_INSUFF_RESOURCES; + } + + if (app->app_idx == app_idx) + { + if (app->net_idx != net_idx) + { + return STATUS_INVALID_NETKEY; + } + + if (bt_mesh_key_compare(key, &app->keys[0].val)) + { + return STATUS_IDX_ALREADY_STORED; + } + + return STATUS_SUCCESS; + } + + if (bt_mesh_app_id(key, &app->keys[0].id)) + { + return STATUS_CANNOT_SET; + } + + LOG_DBG("AppIdx 0x%04x AID 0x%02x", app_idx, app->keys[0].id); + + app->net_idx = net_idx; + app->app_idx = app_idx; + app->updated = false; + if (bt_mesh_key_import(BT_MESH_KEY_TYPE_APP, key, &app->keys[0].val)) + { + LOG_ERR("Unable to import application key"); + return STATUS_CANNOT_SET; + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + LOG_DBG("Storing AppKey persistently"); + update_app_key_settings(app->app_idx, true); + } + + app_key_evt(app, BT_MESH_KEY_ADDED); + + return STATUS_SUCCESS; +} + +uint8_t bt_mesh_app_key_update(uint16_t app_idx, + uint16_t net_idx, const uint8_t key[16]) +{ + struct bt_mesh_app_key *app; + struct bt_mesh_subnet *sub; + + LOG_DBG("net_idx 0x%04x app_idx %04x val %s", net_idx, app_idx, bt_hex(key, + 16)); + + app = app_get(app_idx); + if (!app) + { + return STATUS_INVALID_APPKEY; + } + + if (net_idx != BT_MESH_KEY_UNUSED && app->net_idx != net_idx) + { + return STATUS_INVALID_BINDING; + } + + sub = bt_mesh_subnet_get(app->net_idx); + if (!sub) + { + return STATUS_INVALID_NETKEY; + } + + /* The AppKey Update message shall generate an error when node + * is in normal operation, Phase 2, or Phase 3 or in Phase 1 when the + * AppKey Update message on a valid AppKeyIndex when the AppKey value is + * different. + */ + + if (sub->kr_phase != BT_MESH_KR_PHASE_1) + { + return STATUS_CANNOT_UPDATE; + } + + if (app->updated) + { + if (bt_mesh_key_compare(key, &app->keys[1].val)) + { + return STATUS_IDX_ALREADY_STORED; + } + + return STATUS_SUCCESS; + } + + if (bt_mesh_app_id(key, &app->keys[1].id)) + { + return STATUS_CANNOT_UPDATE; + } + + LOG_DBG("app_idx 0x%04x AID 0x%02x", app_idx, app->keys[1].id); + + app->updated = true; + if (bt_mesh_key_import(BT_MESH_KEY_TYPE_APP, key, &app->keys[1].val)) + { + LOG_ERR("Unable to import application key"); + return STATUS_CANNOT_UPDATE; + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + LOG_DBG("Storing AppKey persistently"); + update_app_key_settings(app->app_idx, true); + } + + app_key_evt(app, BT_MESH_KEY_UPDATED); + + return STATUS_SUCCESS; +} + +uint8_t bt_mesh_app_key_del(uint16_t app_idx, uint16_t net_idx) +{ + struct bt_mesh_app_key *app; + + LOG_DBG("AppIdx 0x%03x", app_idx); + + if (net_idx != BT_MESH_KEY_UNUSED && !bt_mesh_subnet_get(net_idx)) + { + return STATUS_INVALID_NETKEY; + } + + app = app_get(app_idx); + if (!app) + { + /* This could be a retry of a previous attempt that had its + * response lost, so pretend that it was a success. + */ + + return STATUS_SUCCESS; + } + + if (net_idx != BT_MESH_KEY_UNUSED && net_idx != app->net_idx) + { + return STATUS_INVALID_BINDING; + } + + app_key_del(app); + + return STATUS_SUCCESS; +} + +static int app_id_set(struct bt_mesh_app_key *app, + int key_idx, const struct bt_mesh_key *key) +{ + uint8_t raw_key[16]; + int err; + + err = bt_mesh_key_export(raw_key, key); + if (err) + { + return err; + } + + err = bt_mesh_app_id(raw_key, &app->keys[key_idx].id); + if (err) + { + return err; + } + + bt_mesh_key_assign(&app->keys[key_idx].val, key); + + return 0; +} + +int bt_mesh_app_key_set( + uint16_t app_idx, uint16_t net_idx, const struct bt_mesh_key *old_key, + const struct bt_mesh_key *new_key) +{ + struct bt_mesh_app_key *app; + + app = app_key_alloc(app_idx); + if (!app) + { + return -ENOMEM; + } + + if (app->app_idx == app_idx) + { + return 0; + } + + LOG_DBG("AppIdx 0x%04x AID 0x%02x", app_idx, app->keys[0].id); + + if (app_id_set(app, 0, old_key)) + { + return -EIO; + } + + if (new_key != NULL && app_id_set(app, 1, new_key)) + { + return -EIO; + } + + app->net_idx = net_idx; + app->app_idx = app_idx; + app->updated = !!new_key; + + return 0; +} + +bool bt_mesh_app_key_exists(uint16_t app_idx) +{ + for (int i = 0; i < ARRAY_SIZE(apps); i++) + { + if (apps[i].app_idx == app_idx) + { + return true; + } + } + + return false; +} + +ssize_t bt_mesh_app_keys_get(uint16_t net_idx, + uint16_t app_idxs[], size_t max, off_t skip) +{ + size_t count = 0; + + for (int i = 0; i < ARRAY_SIZE(apps); i++) + { + struct bt_mesh_app_key *app = &apps[i]; + + if (app->app_idx == BT_MESH_KEY_UNUSED) + { + continue; + } + + if (net_idx != BT_MESH_KEY_ANY && app->net_idx != net_idx) + { + continue; + } + + if (skip) + { + skip--; + continue; + } + + if (count >= max) + { + return -ENOMEM; + } + + app_idxs[count++] = app->app_idx; + } + + return count; +} + +int bt_mesh_keys_resolve( + struct bt_mesh_msg_ctx *ctx, struct bt_mesh_subnet **sub, + const struct bt_mesh_key **app_key, uint8_t *aid) +{ + struct bt_mesh_app_key *app = NULL; + + if (BT_MESH_IS_DEV_KEY(ctx->app_idx)) + { + /* With device keys, the application has to decide which subnet + * to send on. + */ + + *sub = bt_mesh_subnet_get(ctx->net_idx); + if (!*sub) + { + LOG_WRN("Unknown NetKey 0x%03x", ctx->net_idx); + return -EINVAL; + } + + if (ctx->app_idx == BT_MESH_KEY_DEV_REMOTE && + !bt_mesh_has_addr(ctx->addr)) + { + struct bt_mesh_cdb_node *node; + + if (!IS_ENABLED(CONFIG_BT_MESH_CDB)) + { + LOG_WRN("No DevKey for 0x%04x", ctx->addr); + return -EINVAL; + } + + node = bt_mesh_cdb_node_get(ctx->addr); + if (!node) + { + LOG_WRN("No DevKey for 0x%04x", ctx->addr); + return -EINVAL; + } + + *app_key = &node->dev_key; + } + else + { + *app_key = &bt_mesh.dev_key; + } + + *aid = 0; + return 0; + } + + app = app_get(ctx->app_idx); + if (!app) + { + LOG_WRN("Unknown AppKey 0x%03x", ctx->app_idx); + return -EINVAL; + } + + *sub = bt_mesh_subnet_get(app->net_idx); + if (!*sub) + { + LOG_WRN("Unknown NetKey 0x%03x", app->net_idx); + return -EINVAL; + } + + if ((*sub)->kr_phase == BT_MESH_KR_PHASE_2 && app->updated) + { + *aid = app->keys[1].id; + *app_key = &app->keys[1].val; + } + else + { + *aid = app->keys[0].id; + *app_key = &app->keys[0].val; + } + + return 0; +} + +uint16_t bt_mesh_app_key_find( + bool dev_key, uint8_t aid, struct bt_mesh_net_rx *rx, + int (*cb)(struct bt_mesh_net_rx *rx, + const struct bt_mesh_key *key, + void *cb_data), + void *cb_data) +{ + int err; + int i; + + if (dev_key) + { + /* Attempt remote dev key first, as that is only available for + * provisioner devices, which normally don't interact with nodes that + * know their local dev key. + */ + + if (IS_ENABLED(CONFIG_BT_MESH_CDB) && + rx->net_if != BT_MESH_NET_IF_LOCAL) + { + struct bt_mesh_cdb_node *node; + + node = bt_mesh_cdb_node_get(rx->ctx.addr); + if (node && !cb(rx, &node->dev_key, cb_data)) + { + return BT_MESH_KEY_DEV_REMOTE; + } + } + + /** MshPRTv1.1: 3.4.3: The Device key is only valid for unicast + * addresses. + */ + + if (BT_MESH_ADDR_IS_UNICAST(rx->ctx.recv_dst)) + { + err = cb(rx, &bt_mesh.dev_key, cb_data); + if (!err) + { + return BT_MESH_KEY_DEV_LOCAL; + } + +#if defined(CONFIG_BT_MESH_RPR_SRV) + if (atomic_test_bit(bt_mesh.flags, BT_MESH_DEVKEY_CAND)) + { + err = cb(rx, &bt_mesh.dev_key_cand, cb_data); + if (!err) + { + /* MshPRTv1.1: 3.6.4.2: + * If a message is successfully decrypted using the device + * key candidate, the device key candidate should + * permanently replace the original devkey. + */ + + bt_mesh_dev_key_cand_activate(); + return BT_MESH_KEY_DEV_LOCAL; + } + } +#endif + } + + return BT_MESH_KEY_UNUSED; + } + + for (i = 0; i < ARRAY_SIZE(apps); i++) + { + const struct bt_mesh_app_key *app = &apps[i]; + const struct bt_mesh_app_cred *cred; + + if (app->app_idx == BT_MESH_KEY_UNUSED) + { + continue; + } + + if (app->net_idx != rx->sub->net_idx) + { + continue; + } + + if (rx->new_key && app->updated) + { + cred = &app->keys[1]; + } + else + { + cred = &app->keys[0]; + } + + if (cred->id != aid) + { + continue; + } + + err = cb(rx, &cred->val, cb_data); + if (err) + { + continue; + } + + return app->app_idx; + } + + return BT_MESH_KEY_UNUSED; +} + +static void subnet_evt(struct bt_mesh_subnet *sub, enum bt_mesh_key_evt evt) +{ + if (evt == BT_MESH_KEY_UPDATED || evt == BT_MESH_KEY_ADDED) + { + return; + } + + for (int i = 0; i < ARRAY_SIZE(apps); i++) + { + struct bt_mesh_app_key *app = &apps[i]; + + if (app->app_idx == BT_MESH_KEY_UNUSED) + { + continue; + } + + if (app->net_idx != sub->net_idx) + { + continue; + } + + if (evt == BT_MESH_KEY_DELETED) + { + app_key_del(app); + } + else if (evt == BT_MESH_KEY_REVOKED) + { + app_key_revoke(app); + } + else if (evt == BT_MESH_KEY_SWAPPED && app->updated) + { + app_key_evt(app, BT_MESH_KEY_SWAPPED); + } + } +} + +BT_MESH_SUBNET_CB_DEFINE(app_keys) = +{ + .evt_handler = subnet_evt, +}; + +void bt_mesh_app_keys_reset(void) +{ + for (int i = 0; i < ARRAY_SIZE(apps); i++) + { + struct bt_mesh_app_key *app = &apps[i]; + + if (app->app_idx != BT_MESH_KEY_UNUSED) + { + app_key_del(app); + } + } +} + +static int app_key_set(const char *name, + size_t len_rd, settings_read_cb read_cb, void *cb_arg) +{ + struct app_key_val key; + struct bt_mesh_key val[2]; + uint16_t app_idx; + int err; + + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return 0; + } + + if (!name) + { + LOG_ERR("Insufficient number of arguments"); + return -ENOENT; + } + + app_idx = strtol(name, NULL, 16); + + if (!len_rd) + { + return 0; + } + + err = bt_mesh_settings_set(read_cb, cb_arg, &key, sizeof(key)); + if (err < 0) + { + return -EINVAL; + } + + /* One extra copying since key.val array is from packed structure + * and might be unaligned. + */ + + memcpy(val, key.val, sizeof(key.val)); + + err = bt_mesh_app_key_set(app_idx, key.net_idx, &val[0], + key.updated ? &val[1] : NULL); + if (err) + { + LOG_ERR("Failed to set \'app-key\'"); + return err; + } + + LOG_DBG("AppKeyIndex 0x%03x recovered from storage", app_idx); + + return 0; +} + +BT_MESH_SETTINGS_DEFINE(app, "AppKey", app_key_set); + +void bt_mesh_app_key_pending_store(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(app_key_updates); i++) + { + struct app_key_update *update = &app_key_updates[i]; + + if (!update->valid) + { + continue; + } + + update->valid = 0U; + + if (update->clear) + { + clear_app_key(update->key_idx); + } + else + { + store_app_key(update->key_idx); + } + } +} diff --git a/libs/zblue/mesh/app_keys.h b/libs/zblue/mesh/app_keys.h new file mode 100644 index 0000000000000..1bb9789ea959e --- /dev/null +++ b/libs/zblue/mesh/app_keys.h @@ -0,0 +1,135 @@ +/**************************************************************************** + * libs/zblue/mesh/app_keys.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_SUBSYS_BLUETOOTH_MESH_APP_KEYS_H_ +#define ZEPHYR_SUBSYS_BLUETOOTH_MESH_APP_KEYS_H_ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include "subnet.h" + +/** Mesh Application Key. */ + +struct bt_mesh_app_key +{ + /** NetKey Index the AppKey is bound to. */ + + uint16_t net_idx; + + /** Application Key Index. */ + + uint16_t app_idx; + + /** True if the AppKey has a second key for KR. */ + + bool updated; + struct bt_mesh_app_cred + { + /** Application ID. */ + + uint8_t id; + + /** Application Key value. */ + + struct bt_mesh_key val; + } + keys[2]; +}; + +/** Appkey callback. Instantiate with @ref BT_MESH_APP_KEY_CB_DEFINE */ + +struct bt_mesh_app_key_cb +{ + void (*evt_handler)(struct bt_mesh_app_key *app, enum bt_mesh_key_evt evt); +}; + +/** + * @brief Register an AppKey event callback. + * + * @param _handler Handler function, see @ref + * bt_mesh_app_key_cb::evt_handler. + */ +#define BT_MESH_APP_KEY_CB_DEFINE(_handler) \ + static const STRUCT_SECTION_ITERABLE(bt_mesh_app_key_cb, \ + _CONCAT(bt_mesh_app_key_cb_, _handler)) = { \ + .evt_handler = (_handler), \ + } + +/** @brief Reset the app keys module. */ + +void bt_mesh_app_keys_reset(void); + +/** @brief Initialize a new application key with the given parameters. + * + * @param app_idx AppIndex. + * @param net_idx NetIndex the application is bound to. + * @param old_key Current application key. + * @param new_key Updated application key, or NULL if not known. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_app_key_set(uint16_t app_idx, + uint16_t net_idx, + const struct bt_mesh_key *old_key, + const struct bt_mesh_key *new_key); + +/** @brief Resolve the message encryption keys, given a message context. + * + * Will use the @c ctx::app_idx and @c ctx::net_idx fields to find a pair of + * message encryption keys. If @c ctx::app_idx represents a device key, + * the @c ctx::net_idx will be used to determine the net key. Otherwise, + * the @c ctx::net_idx parameter will be ignored. + * + * @param ctx Message context. + * @param sub Subnet return parameter. + * @param app_key Application return parameter. + * @param aid Application ID return parameter. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_keys_resolve(struct bt_mesh_msg_ctx *ctx, + struct bt_mesh_subnet **sub, + const struct bt_mesh_key **app_key, + uint8_t *aid); + +/** @brief Iterate through all matching application keys and call @c cb on + * each. + * + * @param dev_key Whether to return device keys. + * @param aid 7 bit application ID to match. + * @param rx RX structure to match against. + * @param cb Callback to call for every valid app key. + * @param cb_data Callback data to pass to the callback. + * + * @return The AppIdx that yielded a 0-return from the callback. + */ + +uint16_t bt_mesh_app_key_find(bool dev_key, + uint8_t aid, + struct bt_mesh_net_rx *rx, + +int (*cb)(struct bt_mesh_net_rx *rx, + const struct bt_mesh_key *key, + void *cb_data), + void *cb_data); + +/** @brief Store pending application keys in persistent storage. */ + +void bt_mesh_app_key_pending_store(void); + +#endif /* ZEPHYR_SUBSYS_BLUETOOTH_MESH_APP_KEYS_H_ */ diff --git a/libs/zblue/mesh/beacon.c b/libs/zblue/mesh/beacon.c new file mode 100644 index 0000000000000..8fcaea4ad2416 --- /dev/null +++ b/libs/zblue/mesh/beacon.c @@ -0,0 +1,954 @@ +/**************************************************************************** + * libs/zblue/mesh/beacon.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/bt_str.h" + +#include "net.h" +#include "prov.h" +#include "crypto.h" +#include "beacon.h" +#include "cfg.h" + +#define LOG_LEVEL CONFIG_BT_MESH_BEACON_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_beacon); + +#define PROVISIONED_INTERVAL SEC2TICK(10) + +#define BEACON_TYPE_UNPROVISIONED 0x00 +#define BEACON_TYPE_SECURE 0x01 +#define BEACON_TYPE_PRIVATE 0x02 + +/* 3 transmissions, 20ms interval */ +#define UNPROV_XMIT BT_MESH_TRANSMIT(2, 20) + +/* 1 transmission, 20ms interval */ +#define PROV_XMIT BT_MESH_TRANSMIT(0, 20) + +static struct k_work_delayable beacon_timer; +static struct bt_mesh_subnet *beacon_send_sub_curr; + +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) +static struct +{ +/* Identifier for the current Private beacon random-value. Each time we + * regenerate the random-value, we'll update this idx. Whenever it's time + * for a subnet to create a beacon, it'll compare the subnet's beacon idx + * to determine whether the random value has changed since the last beacon + * was sent. If this is the case, we'll regenerate the beacon based on the + * new random value. + */ + + uint16_t idx; + uint8_t val[13]; + uint64_t timestamp; +} priv_random; +#endif + +struct beacon_params +{ + bool private; + union + { + const uint8_t *net_id; + struct + { + const uint8_t *data; + const uint8_t *random; + }; + }; + const uint8_t *auth; + uint32_t iv_index; + uint8_t flags; + + bool new_key; +}; + +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) +static int private_beacon_create(struct bt_mesh_subnet *sub, + struct net_buf_simple *buf); +static int private_beacon_update(struct bt_mesh_subnet *sub); +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static struct bt_mesh_beacon *subnet_beacon_get_by_type( + struct bt_mesh_subnet *sub, bool priv) +{ +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) + return priv ? &sub->priv_beacon : &sub->secure_beacon; +#else + return &sub->secure_beacon; +#endif +} + +static bool beacon_cache_match(struct bt_mesh_subnet *sub, void *data) +{ + struct beacon_params *params; + struct bt_mesh_beacon *beacon; + + params = data; + beacon = subnet_beacon_get_by_type(sub, params->private); + + return !memcmp(beacon->cache, params->auth, sizeof(beacon->cache)); +} + +static void cache_add(const uint8_t auth[8], struct bt_mesh_beacon *beacon) +{ + memcpy(beacon->cache, auth, sizeof(beacon->cache)); +} + +void bt_mesh_beacon_cache_clear(struct bt_mesh_subnet *sub) +{ + (void)memset(sub->secure_beacon.cache, 0, + sizeof(sub->secure_beacon.cache)); +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) + (void)memset(sub->priv_beacon.cache, 0, sizeof(sub->priv_beacon.cache)); +#endif +} + +static void beacon_start(uint16_t duration, int err, void *user_data) +{ + if (err) + { + LOG_ERR("Failed to send beacon: err %d", err); + if (beacon_send_sub_curr) + { + k_work_reschedule(&beacon_timer, 0); + } + } +} + +static void beacon_complete(int err, void *user_data) +{ + struct bt_mesh_beacon *beacon = user_data; + + LOG_DBG("err %d", err); + beacon->sent = (uint32_t)TICK2MSEC(clock_systime_ticks()); + + if (beacon_send_sub_curr) + { + k_work_reschedule(&beacon_timer, MSEC2TICK(20)); + } +} + +static int secure_beacon_create(struct bt_mesh_subnet *sub, + struct net_buf_simple *buf) +{ + uint8_t flags = bt_mesh_net_flags(sub); + struct bt_mesh_subnet_keys *keys; + + net_buf_simple_add_u8(buf, BEACON_TYPE_SECURE); + + keys = &sub->keys[SUBNET_KEY_TX_IDX(sub)]; + + net_buf_simple_add_u8(buf, flags); + + /* Network ID */ + + net_buf_simple_add_mem(buf, keys->net_id, 8); + + /* IV Index */ + + net_buf_simple_add_be32(buf, bt_mesh.iv_index); + + net_buf_simple_add_mem(buf, sub->secure_beacon.auth, 8); + + LOG_DBG("net_idx 0x%04x flags 0x%02x NetID %s", sub->net_idx, flags, + bt_hex(keys->net_id, 8)); + LOG_DBG("IV Index 0x%08x Auth %s", bt_mesh.iv_index, + bt_hex(sub->secure_beacon.auth, 8)); + + return 0; +} + +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) +static int private_random_update(void) +{ + uint8_t interval = bt_mesh_priv_beacon_update_interval_get(); + uint64_t uptime = TICK2MSEC(clock_systime_ticks()); + int err; + + /* The Private beacon random value should change every N seconds to + * maintain privacy. N = (10 * interval) seconds, or on every beacon + * creation, if the interval is 0. + */ + + if (bt_mesh_priv_beacon_get() == BT_MESH_FEATURE_ENABLED && interval && + uptime - priv_random.timestamp < (10 * interval * MSEC_PER_SEC) && + priv_random.timestamp != 0) + { + /* Not time yet */ + + return 0; + } + + err = bt_rand(priv_random.val, sizeof(priv_random.val)); + if (err) + { + return err; + } + + /* Update the index to indicate to all subnets that the private beacon must + * be regenerated. Each subnet maintains the random index their private + * beacon data was generated with. + */ + + priv_random.idx++; + priv_random.timestamp = uptime; + + return 0; +} + +static int private_beacon_update(struct bt_mesh_subnet *sub) +{ + struct bt_mesh_subnet_keys *keys = &sub->keys[SUBNET_KEY_TX_IDX(sub)]; + uint8_t flags = bt_mesh_net_flags(sub); + int err; + + err = bt_mesh_beacon_encrypt(&keys->priv_beacon, flags, bt_mesh.iv_index, + priv_random.val, + sub->priv_beacon_ctx.data, + sub->priv_beacon.auth); + if (err) + { + LOG_ERR("Can't encrypt private beacon"); + return err; + } + + sub->priv_beacon_ctx.idx = priv_random.idx; + return 0; +} + +static int private_beacon_create(struct bt_mesh_subnet *sub, + struct net_buf_simple *buf) +{ + int err; + + /* Refresh beacon data */ + + err = private_random_update(); + if (err) + { + return err; + } + + if (sub->priv_beacon_ctx.idx != priv_random.idx) + { + err = private_beacon_update(sub); + if (err) + { + return err; + } + } + + net_buf_simple_add_u8(buf, BEACON_TYPE_PRIVATE); + net_buf_simple_add_mem(buf, priv_random.val, 13); + net_buf_simple_add_mem(buf, sub->priv_beacon_ctx.data, 5); + net_buf_simple_add_mem(buf, sub->priv_beacon.auth, 8); + + LOG_DBG("0x%03x", sub->net_idx); + return 0; +} +#endif + +int bt_mesh_beacon_create(struct bt_mesh_subnet *sub, + struct net_buf_simple *buf, bool priv) +{ +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) + if (priv) + { + return private_beacon_create(sub, buf); + } +#endif + + secure_beacon_create(sub, buf); + return 0; +} + +/* If the interval has passed or is within 5 seconds from now send a beacon */ +#define BEACON_THRESHOLD(beacon) ((10 * ((beacon)->last + 1)) * MSEC_PER_SEC - (5 * MSEC_PER_SEC)) + +static bool secure_beacon_is_running(void) +{ + return bt_mesh_beacon_enabled() || atomic_test_bit(bt_mesh.flags, + BT_MESH_IVU_INITIATOR); +} + +static int net_beacon_send( + struct bt_mesh_subnet *sub, struct bt_mesh_beacon *beacon, + int (*beacon_create)(struct bt_mesh_subnet *sub, + struct net_buf_simple *buf)) +{ + static const struct bt_mesh_send_cb send_cb = + { + .start = beacon_start, + .end = beacon_complete, + }; + + uint32_t now = (uint32_t)TICK2MSEC(clock_systime_ticks()); + struct bt_mesh_adv *adv; + uint32_t time_diff; + uint32_t time_since_last_recv; + int err; + + LOG_DBG(""); + + time_diff = now - beacon->sent; + time_since_last_recv = now - beacon->recv; + if (time_diff < (600 * MSEC_PER_SEC) && + (time_diff < BEACON_THRESHOLD(beacon) + || time_since_last_recv < (10 * MSEC_PER_SEC))) + { + return -ENOMSG; + } + + adv = bt_mesh_adv_create(BT_MESH_ADV_BEACON, BT_MESH_ADV_TAG_LOCAL, + PROV_XMIT, 0); + if (!adv) + { + LOG_ERR("Unable to allocate beacon adv"); + return -ENOMEM; /* Bail out */ + } + + err = beacon_create(sub, &adv->b); + if (!err) + { + bt_mesh_adv_send(adv, &send_cb, beacon); + } + + bt_mesh_adv_unref(adv); + + return err; +} + +static int net_beacon_for_subnet_send(struct bt_mesh_subnet *sub) +{ + int err = -ENOMSG; + + struct + { + struct bt_mesh_beacon *beacon; + bool enabled; + int (*create_fn)(struct bt_mesh_subnet *sub, struct net_buf_simple *buf); + } + + beacons[] = +{ + [0] = +{ + .beacon = &sub->secure_beacon, + .enabled = secure_beacon_is_running(), + .create_fn = secure_beacon_create, +}, +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) + + [1] = +{ + .beacon = &sub->priv_beacon, + .enabled = bt_mesh_priv_beacon_get() == BT_MESH_FEATURE_ENABLED, + .create_fn = private_beacon_create, +}, +#endif +}; + + for (int i = 0; i < ARRAY_SIZE(beacons); i++) + { + if (!beacons[i].enabled) + { + continue; + } + + err = net_beacon_send(sub, beacons[i].beacon, beacons[i].create_fn); + if (err < 0) + { + /* Bail out */ + + break; + } + } + + return err; +} + +static int unprovisioned_beacon_send(void) +{ + const struct bt_mesh_prov *prov; + uint8_t uri_hash[16] = + { + 0 + }; + + struct bt_mesh_adv *adv; + uint16_t oob_info; + + LOG_DBG(""); + + adv = bt_mesh_adv_create(BT_MESH_ADV_BEACON, BT_MESH_ADV_TAG_LOCAL, + UNPROV_XMIT, 0); + if (!adv) + { + LOG_ERR("Unable to allocate beacon adv"); + return -ENOBUFS; + } + + prov = bt_mesh_prov_get(); + + net_buf_simple_add_u8(&adv->b, BEACON_TYPE_UNPROVISIONED); + net_buf_simple_add_mem(&adv->b, prov->uuid, 16); + + if (prov->uri && bt_mesh_s1_str(prov->uri, uri_hash) == 0) + { + oob_info = prov->oob_info | BT_MESH_PROV_OOB_URI; + } + else + { + oob_info = prov->oob_info; + } + + net_buf_simple_add_be16(&adv->b, oob_info); + net_buf_simple_add_mem(&adv->b, uri_hash, 4); + + bt_mesh_adv_send(adv, NULL, NULL); + bt_mesh_adv_unref(adv); + + if (prov->uri) + { + size_t len; + + adv = bt_mesh_adv_create(BT_MESH_ADV_URI, BT_MESH_ADV_TAG_LOCAL, + UNPROV_XMIT, 0); + if (!adv) + { + LOG_ERR("Unable to allocate URI adv"); + return -ENOBUFS; + } + + len = strlen(prov->uri); + if (net_buf_simple_tailroom(&adv->b) < len) + { + LOG_WRN("Too long URI to fit advertising data"); + } + else + { + net_buf_simple_add_mem(&adv->b, prov->uri, len); + bt_mesh_adv_send(adv, NULL, NULL); + } + + bt_mesh_adv_unref(adv); + } + + return 0; +} + +static void unprovisioned_beacon_recv(struct net_buf_simple *buf) +{ + const struct bt_mesh_prov *prov; + uint8_t *uuid; + uint16_t oob_info; + uint32_t uri_hash_val; + uint32_t *uri_hash = NULL; + + prov = bt_mesh_prov_get(); + + if (!prov->unprovisioned_beacon) + { + return; + } + + if (buf->len != 18 && buf->len != 22) + { + LOG_ERR("Invalid unprovisioned beacon length (%u)", buf->len); + return; + } + + uuid = net_buf_simple_pull_mem(buf, 16); + oob_info = net_buf_simple_pull_be16(buf); + + if (buf->len == 4) + { + uri_hash_val = net_buf_simple_pull_be32(buf); + uri_hash = &uri_hash_val; + } + + LOG_DBG("uuid %s", bt_hex(uuid, 16)); + + prov->unprovisioned_beacon(uuid, (bt_mesh_prov_oob_info_t)oob_info, + uri_hash); +} + +static void sub_update_beacon_observation(struct bt_mesh_subnet *sub) +{ + sub->secure_beacon.last = sub->secure_beacon.cur; + sub->secure_beacon.cur = 0U; + +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) + sub->priv_beacon.last = sub->priv_beacon.cur; + sub->priv_beacon.cur = 0U; +#endif +} + +static void update_beacon_observation(void) +{ + static bool first_half; + + /* Observation period is 20 seconds, whereas the beacon timer + * runs every 10 seconds. We process what's happened during the window only + * after the second half. + */ + + first_half = !first_half; + if (first_half) + { + return; + } + + bt_mesh_subnet_foreach(sub_update_beacon_observation); +} + +static bool net_beacon_is_running(void) +{ + return secure_beacon_is_running() || + (bt_mesh_priv_beacon_get() == BT_MESH_FEATURE_ENABLED); +} + +static bool beacons_send_next(void) +{ + int err; + struct bt_mesh_subnet *sub_first = bt_mesh_subnet_next(NULL); + struct bt_mesh_subnet *sub_next; + + do + { + sub_next = bt_mesh_subnet_next(beacon_send_sub_curr); + if (sub_next == sub_first && beacon_send_sub_curr != NULL) + { + beacon_send_sub_curr = NULL; + return false; + } + + beacon_send_sub_curr = sub_next; + err = net_beacon_for_subnet_send(beacon_send_sub_curr); + if (err < 0 && (err != -ENOMSG)) + { + LOG_ERR("Failed to advertise subnet %d: err %d", + beacon_send_sub_curr->net_idx, err); + } + } + while (err); + + return true; +} + +static void beacon_send(struct k_work *work) +{ + LOG_DBG(""); + + if (bt_mesh_is_provisioned()) + { + if (!net_beacon_is_running()) + { + return; + } + + if (!beacon_send_sub_curr) + { + update_beacon_observation(); + } + + if (!beacons_send_next()) + { + k_work_schedule(&beacon_timer, PROVISIONED_INTERVAL); + } + + return; + } + + if (IS_ENABLED(CONFIG_BT_MESH_PB_ADV)) + { + /* Don't send anything if we have an active provisioning link */ + + if (!bt_mesh_prov_active()) + { + unprovisioned_beacon_send(); + } + + k_work_schedule(&beacon_timer, + SEC2TICK(CONFIG_BT_MESH_UNPROV_BEACON_INT)); + } +} + +static bool auth_match(struct bt_mesh_subnet_keys *keys, + const struct beacon_params *params) +{ + uint8_t net_auth[8]; + + if (memcmp(params->net_id, keys->net_id, 8)) + { + return false; + } + + if (bt_mesh_beacon_auth(&keys->beacon, params->flags, keys->net_id, + params->iv_index, net_auth)) + { + return false; + } + + if (memcmp(params->auth, net_auth, 8)) + { + LOG_WRN("Invalid auth value. Received auth: %s", bt_hex(params->auth, + 8)); + LOG_WRN("Calculated auth: %s", bt_hex(net_auth, 8)); + return false; + } + + return true; +} + +static bool secure_beacon_authenticate(struct bt_mesh_subnet *sub, + void *cb_data) +{ + struct beacon_params *params = cb_data; + + for (int i = 0; i < ARRAY_SIZE(sub->keys); i++) + { + if (sub->keys[i].valid && auth_match(&sub->keys[i], params)) + { + params->new_key = (i > 0); +#if defined(CONFIG_BT_TESTING) + struct bt_mesh_snb beacon_info; + + beacon_info.flags = params->flags; + memcpy(&beacon_info.net_id, params->net_id, 8); + beacon_info.iv_idx = params->iv_index; + memcpy(&beacon_info.auth_val, params->auth, 8); + + STRUCT_SECTION_FOREACH(bt_mesh_beacon_cb, cb) + { + if (cb->snb_received) + { + cb->snb_received(&beacon_info); + } + } +#endif + + return true; + } + } + + return false; +} + +static bool priv_beacon_decrypt(struct bt_mesh_subnet *sub, void *cb_data) +{ + struct beacon_params *params = cb_data; + uint8_t out[5]; + int err; + + for (int i = 0; i < ARRAY_SIZE(sub->keys); i++) + { + if (!sub->keys[i].valid) + { + continue; + } + + err = bt_mesh_beacon_decrypt(&sub->keys[i].priv_beacon, params->random, + params->data, + params->auth, out); + if (!err) + { + params->new_key = (i > 0); + params->flags = out[0]; + params->iv_index = sys_get_be32(&out[1]); + +#if defined(CONFIG_BT_TESTING) + struct bt_mesh_prb beacon_info; + + memcpy(beacon_info.random, params->random, 13); + beacon_info.flags = params->flags; + beacon_info.iv_idx = params->iv_index; + memcpy(&beacon_info.auth_tag, params->auth, 8); + + STRUCT_SECTION_FOREACH(bt_mesh_beacon_cb, cb) + { + if (cb->priv_received) + { + cb->priv_received(&beacon_info); + } + } +#endif + + return true; + } + } + + return false; +} + +static void net_beacon_register(struct bt_mesh_beacon *beacon, bool priv) +{ + if (((priv && + bt_mesh_priv_beacon_get() == BT_MESH_PRIV_GATT_PROXY_ENABLED) || + bt_mesh_beacon_enabled()) && + beacon->cur < 0xff) + { + beacon->cur++; + beacon->recv = (uint32_t)TICK2MSEC(clock_systime_ticks()); + } +} + +static void net_beacon_recv(struct bt_mesh_subnet *sub, + const struct beacon_params *params) +{ + bt_mesh_kr_update(sub, BT_MESH_KEY_REFRESH(params->flags), + params->new_key); + + /* If we have NetKey0 accept IV index initiation only from it */ + + if (bt_mesh_subnet_get(BT_MESH_KEY_PRIMARY) && + sub->net_idx != BT_MESH_KEY_PRIMARY) + { + LOG_WRN("Ignoring secure beacon on non-primary subnet"); + return; + } + + LOG_DBG("net_idx 0x%04x flags %u iv_index 0x%08x, " + "current iv_index 0x%08x", + sub->net_idx, params->flags, params->iv_index, bt_mesh.iv_index); + + if (atomic_test_bit(bt_mesh.flags, BT_MESH_IVU_INITIATOR) && + (atomic_test_bit(bt_mesh.flags, + BT_MESH_IVU_IN_PROGRESS) == BT_MESH_IV_UPDATE(params->flags))) + { + bt_mesh_beacon_ivu_initiator(false); + } + + bt_mesh_net_iv_update(params->iv_index, BT_MESH_IV_UPDATE(params->flags)); +} + +static void net_beacon_resolve(struct beacon_params *params, bool( + *matcher)(struct bt_mesh_subnet *sub, void *cb_data)) +{ + struct bt_mesh_subnet *sub; + struct bt_mesh_beacon *beacon; + + sub = bt_mesh_subnet_find(beacon_cache_match, (void *)params); + if (sub) + { + beacon = subnet_beacon_get_by_type(sub, params->private); + + /* We've seen this beacon before - just update the stats */ + + net_beacon_register(beacon, params->private); + return; + } + + sub = bt_mesh_subnet_find(matcher, params); + if (!sub) + { + LOG_DBG("No subnet that matched beacon"); + return; + } + + if (sub->kr_phase == BT_MESH_KR_PHASE_2 && !params->new_key) + { + LOG_WRN("Ignoring Phase 2 KR Update secured using old key"); + return; + } + + beacon = subnet_beacon_get_by_type(sub, params->private); + + cache_add(params->auth, beacon); + + net_beacon_recv(sub, params); + net_beacon_register(beacon, params->private); +} + +static void secure_beacon_recv(struct net_buf_simple *buf) +{ + struct beacon_params params; + + if (buf->len < 21) + { + LOG_ERR("Too short secure beacon (len %u)", buf->len); + return; + } + + params.private = false; + params.flags = net_buf_simple_pull_u8(buf); + params.net_id = net_buf_simple_pull_mem(buf, 8); + params.iv_index = net_buf_simple_pull_be32(buf); + params.auth = buf->data; + + net_beacon_resolve(¶ms, secure_beacon_authenticate); +} + +static void private_beacon_recv(struct net_buf_simple *buf) +{ + struct beacon_params params; + + if (buf->len < 26) + { + LOG_ERR("Too short private beacon (len %u)", buf->len); + return; + } + + params.private = true; + params.random = net_buf_simple_pull_mem(buf, 13); + params.data = net_buf_simple_pull_mem(buf, 5); + params.auth = buf->data; + + net_beacon_resolve(¶ms, priv_beacon_decrypt); +} + +void bt_mesh_beacon_recv(struct net_buf_simple *buf) +{ + uint8_t type; + + LOG_DBG("%u bytes: %s", buf->len, bt_hex(buf->data, buf->len)); + + if (buf->len < 1) + { + LOG_ERR("Too short beacon"); + return; + } + + type = net_buf_simple_pull_u8(buf); + switch (type) + { + case BEACON_TYPE_UNPROVISIONED: + if (IS_ENABLED(CONFIG_BT_MESH_PB_ADV)) + { + unprovisioned_beacon_recv(buf); + } + break; + case BEACON_TYPE_SECURE: + secure_beacon_recv(buf); + break; + case BEACON_TYPE_PRIVATE: + private_beacon_recv(buf); + break; + default: + LOG_WRN("Unknown beacon type 0x%02x", type); + break; + } +} + +void bt_mesh_beacon_update(struct bt_mesh_subnet *sub) +{ + uint8_t flags = bt_mesh_net_flags(sub); + struct bt_mesh_subnet_keys *keys; + + keys = &sub->keys[SUBNET_KEY_TX_IDX(sub)]; + + LOG_DBG("NetIndex 0x%03x Using %s key", sub->net_idx, + SUBNET_KEY_TX_IDX(sub) ? "new" : "current"); + LOG_DBG("flags 0x%02x, IVI 0x%08x", flags, bt_mesh.iv_index); + +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) + /* Invalidate private beacon to force regeneration: */ + + sub->priv_beacon_ctx.idx = priv_random.idx - 1; + priv_random.timestamp = 0; +#endif + + bt_mesh_beacon_auth(&keys->beacon, flags, keys->net_id, bt_mesh.iv_index, + sub->secure_beacon.auth); +} + +static void subnet_evt(struct bt_mesh_subnet *sub, enum bt_mesh_key_evt evt) +{ + if (evt != BT_MESH_KEY_DELETED) + { + bt_mesh_beacon_update(sub); + } +} + +BT_MESH_SUBNET_CB_DEFINE(beacon) = +{ + .evt_handler = subnet_evt, +}; + +void bt_mesh_beacon_init(void) +{ + k_work_init_delayable(&beacon_timer, beacon_send); + +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) + private_random_update(); +#endif +} + +void bt_mesh_beacon_ivu_initiator(bool enable) +{ + atomic_set_bit_to(bt_mesh.flags, BT_MESH_IVU_INITIATOR, enable); + + /* Fire the beacon handler straight away if it's not already pending - + * in which case we'll fire according to the ongoing periodic sending. If + * beacons are disabled, the handler will exit early. + * + * An alternative solution would be to check whether beacons are enabled + * here, and cancel if not. As the cancel operation may fail, we would + * still have to implement an early exit mechanism, so we might as well + * just use this every time. + */ + + beacon_send_sub_curr = NULL; + k_work_schedule(&beacon_timer, 0); +} + +static void subnet_beacon_enable(struct bt_mesh_subnet *sub) +{ + sub->secure_beacon.last = 0U; + sub->secure_beacon.cur = 0U; + +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) + sub->priv_beacon.last = 0U; + sub->priv_beacon.cur = 0U; +#endif + + bt_mesh_beacon_update(sub); +} + +void bt_mesh_beacon_enable(void) +{ + if (bt_mesh_is_provisioned()) + { + bt_mesh_subnet_foreach(subnet_beacon_enable); + } + + beacon_send_sub_curr = NULL; + k_work_reschedule(&beacon_timer, 0); +} + +void bt_mesh_beacon_disable(void) +{ + /* If this fails, we'll do an early exit in the work handler. */ + + beacon_send_sub_curr = NULL; + (void)k_work_cancel_delayable(&beacon_timer); +} diff --git a/libs/zblue/mesh/beacon.h b/libs/zblue/mesh/beacon.h new file mode 100644 index 0000000000000..526646904dc88 --- /dev/null +++ b/libs/zblue/mesh/beacon.h @@ -0,0 +1,24 @@ +/**************************************************************************** + * libs/zblue/mesh/beacon.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +void bt_mesh_beacon_enable(void); +void bt_mesh_beacon_disable(void); +void bt_mesh_beacon_cache_clear(struct bt_mesh_subnet *sub); +void bt_mesh_beacon_ivu_initiator(bool enable); + +void bt_mesh_beacon_recv(struct net_buf_simple *buf); + +int bt_mesh_beacon_create(struct bt_mesh_subnet *sub, + struct net_buf_simple *buf, bool priv); + +void bt_mesh_beacon_init(void); +void bt_mesh_beacon_update(struct bt_mesh_subnet *sub); diff --git a/libs/zblue/mesh/blob.h b/libs/zblue/mesh/blob.h new file mode 100644 index 0000000000000..c571bb3814310 --- /dev/null +++ b/libs/zblue/mesh/blob.h @@ -0,0 +1,175 @@ +/**************************************************************************** + * libs/zblue/mesh/blob.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#define BT_MESH_BLOB_OP_XFER_GET BT_MESH_MODEL_OP_2(0x83, 0x00) +#define BT_MESH_BLOB_OP_XFER_START BT_MESH_MODEL_OP_2(0x83, 0x01) +#define BT_MESH_BLOB_OP_XFER_CANCEL BT_MESH_MODEL_OP_2(0x83, 0x02) +#define BT_MESH_BLOB_OP_XFER_STATUS BT_MESH_MODEL_OP_2(0x83, 0x03) +#define BT_MESH_BLOB_OP_BLOCK_GET BT_MESH_MODEL_OP_2(0x83, 0x05) +#define BT_MESH_BLOB_OP_BLOCK_START BT_MESH_MODEL_OP_2(0x83, 0x04) +#define BT_MESH_BLOB_OP_CHUNK BT_MESH_MODEL_OP_1(0x66) +#define BT_MESH_BLOB_OP_BLOCK_STATUS BT_MESH_MODEL_OP_1(0x67) +#define BT_MESH_BLOB_OP_BLOCK_REPORT BT_MESH_MODEL_OP_1(0x68) +#define BT_MESH_BLOB_OP_INFO_GET BT_MESH_MODEL_OP_2(0x83, 0x06) +#define BT_MESH_BLOB_OP_INFO_STATUS BT_MESH_MODEL_OP_2(0x83, 0x07) + +#define BLOB_BLOCK_NOT_SET 0xffff + +#define BLOB_CHUNK_SDU_OVERHEAD \ + (BT_MESH_MODEL_OP_LEN(BT_MESH_BLOB_OP_CHUNK) + 2 + BT_MESH_MIC_SHORT) + +#define BLOB_CHUNK_SIZE_MAX(sdu_max) ((sdu_max) - BLOB_CHUNK_SDU_OVERHEAD) +#define BLOB_CHUNK_SDU_LEN(chunk_size) (BLOB_CHUNK_SDU_OVERHEAD + (chunk_size)) + +#if CONFIG_BT_MESH_ALIGN_CHUNK_SIZE_TO_MAX_SEGMENT || \ + CONFIG_BT_MESH_RX_BLOB_CHUNK_SIZE > BLOB_CHUNK_SIZE_MAX(BT_MESH_RX_SDU_MAX) +#define BLOB_RX_CHUNK_SIZE BLOB_CHUNK_SIZE_MAX(BT_MESH_RX_SDU_MAX) +#else +#define BLOB_RX_CHUNK_SIZE CONFIG_BT_MESH_RX_BLOB_CHUNK_SIZE +#endif + +#if CONFIG_BT_MESH_ALIGN_CHUNK_SIZE_TO_MAX_SEGMENT || \ + CONFIG_BT_MESH_TX_BLOB_CHUNK_SIZE > BLOB_CHUNK_SIZE_MAX(BT_MESH_TX_SDU_MAX) +#define BLOB_TX_CHUNK_SIZE BLOB_CHUNK_SIZE_MAX(BT_MESH_TX_SDU_MAX) +#else +#define BLOB_TX_CHUNK_SIZE CONFIG_BT_MESH_TX_BLOB_CHUNK_SIZE +#endif + +/* Utility macros for calculating log2 of a number at compile time. + * Used to determine the log2 representation of the block size, which is + * configured as a raw number, but encoded as log2. + * + * The macros expand to a series of ternary expressions, effectively + * searching through power of twos until a match is found. According to + * MshMBTv1.0, the block size cannot be larger than 2^20, so we'll stop the + * search at 20. + */ +#define _BLOB_LOG_2_CEIL(l, x) ((x) <= (1U << l)) ? l: +#define _BLOB_LOG_2_FLOOR(l, x) ((x) < (1U << (l + 1))) ? l: + +#define BLOB_BLOCK_SIZE_LOG_CEIL(x) (LISTIFY(20, _BLOB_LOG_2_CEIL, (), x) 20) +#define BLOB_BLOCK_SIZE_LOG_FLOOR(x) (LISTIFY(20, _BLOB_LOG_2_FLOOR, (), x) 20) + +/* Log2 representation of the minimum block size */ +#define BLOB_BLOCK_SIZE_LOG_MIN BLOB_BLOCK_SIZE_LOG_CEIL(CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MIN) +/* Log2 representation of the maximum block size */ +#define BLOB_BLOCK_SIZE_LOG_MAX BLOB_BLOCK_SIZE_LOG_FLOOR(CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX) + +#if defined(CONFIG_BT_MESH_BLOB_SRV) +#define BLOB_BLOCK_REPORT_STATUS_MSG_MAXLEN \ + (MAX(sizeof(((struct bt_mesh_blob_block *)0)->missing), \ + CONFIG_BT_MESH_BLOB_SRV_PULL_REQ_COUNT * 3)) +#define BLOB_BLOCK_STATUS_MSG_MAXLEN \ + (5 + MAX(sizeof(((struct bt_mesh_blob_block *)0)->missing), \ + CONFIG_BT_MESH_BLOB_SRV_PULL_REQ_COUNT * 3)) +#else +#define BLOB_BLOCK_REPORT_STATUS_MSG_MAXLEN sizeof(((struct bt_mesh_blob_srv *)0)->block.missing) +#define BLOB_BLOCK_STATUS_MSG_MAXLEN (5 + sizeof(((struct bt_mesh_blob_srv *)0)->block.missing)) +#endif + +#define BLOB_XFER_STATUS_MSG_MAXLEN (17 + sizeof(((struct bt_mesh_blob_srv *)0)->state.blocks)) + +enum bt_mesh_blob_chunks_missing +{ + BT_MESH_BLOB_CHUNKS_MISSING_ALL, + BT_MESH_BLOB_CHUNKS_MISSING_NONE, + BT_MESH_BLOB_CHUNKS_MISSING_SOME, + BT_MESH_BLOB_CHUNKS_MISSING_ENCODED, +}; + +static inline size_t blob_block_size(size_t xfer_size, + uint8_t block_size_log, uint32_t idx) +{ + if (((idx + 1U) << block_size_log) <= xfer_size) + { + return (1U << block_size_log); + } + + return xfer_size & BIT_MASK(block_size_log); +} + +static inline void blob_chunk_missing_set(uint8_t *missing_chunks, + int idx, bool missing) +{ + WRITE_BIT(missing_chunks[idx / 8], idx % 8, missing); +} + +static inline bool blob_chunk_missing_get(const uint8_t *missing_chunks, + int idx) +{ + return !!(missing_chunks[idx / 8] & BIT(idx % 8)); +} + +static inline void blob_chunk_missing_set_all( + struct bt_mesh_blob_block *block) +{ + size_t bytes = block->chunk_count / 8; + + memset(block->missing, 0xff, bytes); + if (block->chunk_count % 8) + { + block->missing[bytes] = BIT_MASK(block->chunk_count % 8); + } +} + +static inline void blob_chunk_missing_set_none( + struct bt_mesh_blob_block *block) +{ + memset(block->missing, 0, sizeof(block->missing)); +} + +/** @brief Perform a message broadcast to all BLOB Transfer Client Target + * nodes. + * + * Will send to a group or each Target node individually, repeating until + * all Target nodes have responded or the retry time has run out. + * + * @param cli BLOB Transfer Client instance + * @param ctx Broadcast context + */ + +void blob_cli_broadcast(struct bt_mesh_blob_cli *cli, + const struct blob_cli_broadcast_ctx *ctx); + +/** @brief Register that a Target node responded to a broadcast. + * + * @param cli BLOB Transfer Client instance + * @param target Target node that responded. + */ + +void blob_cli_broadcast_rsp(struct bt_mesh_blob_cli *cli, + struct bt_mesh_blob_target *target); + +/** @brief Notify the BLOB Transfer Client that the requested transmission is + * complete. + * + * Should be called once for each call to the @ref + * blob_cli_broadcast_ctx.send callback. + * + * @param cli BLOB Transfer Client instance. + */ + +void blob_cli_broadcast_tx_complete(struct bt_mesh_blob_cli *cli); + +/** @brief Aborts any ongoing BLOB Transfer Client operations. + * + * @param cli BLOB Transfer Client instance. + */ + +void blob_cli_broadcast_abort(struct bt_mesh_blob_cli *cli); diff --git a/libs/zblue/mesh/blob_cli.c b/libs/zblue/mesh/blob_cli.c new file mode 100644 index 0000000000000..cdaf9584e5c25 --- /dev/null +++ b/libs/zblue/mesh/blob_cli.c @@ -0,0 +1,2004 @@ +/**************************************************************************** + * libs/zblue/mesh/blob_cli.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include "mesh.h" +#include "blob.h" +#include "net.h" +#include "transport.h" + +#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_blob_cli); + +#define TARGETS_FOR_EACH(cli, target) \ + list_for_every_container((struct list_node *)&(cli)->inputs->targets, target, n) + +/* The Maximum BLOB Poll Interval - T_MBPI */ +#define BLOB_POLL_TIME_MAX_SECS 30 + +#define CLIENT_TIMEOUT_MSEC(cli) \ + (10 * MSEC_PER_SEC * (cli->inputs->timeout_base + 2) + 100 * cli->inputs->ttl) +#define BLOCK_REPORT_TIME_MSEC ((BLOB_POLL_TIME_MAX_SECS * 2 + 7) * 1000) + +/* BLOB Client is running Send Data State Machine from section 6.2.4.2. */ +#define SENDING_CHUNKS_IN_PULL_MODE(cli) \ + ((cli)->state == BT_MESH_BLOB_CLI_STATE_BLOCK_SEND && \ + (cli)->xfer->mode == BT_MESH_BLOB_XFER_MODE_PULL) +#define UNICAST_MODE(cli) \ + ((cli)->inputs->group == BT_MESH_ADDR_UNASSIGNED || (cli)->tx.ctx.force_unicast) + +BUILD_ASSERT((BLOB_XFER_STATUS_MSG_MAXLEN + + BT_MESH_MODEL_OP_LEN(BT_MESH_BLOB_OP_XFER_STATUS) + + BT_MESH_MIC_SHORT) <= BT_MESH_RX_SDU_MAX, + "The BLOB Transfer Status message does not fit into the " + "maximum incoming SDU size."); + +BUILD_ASSERT((BLOB_BLOCK_REPORT_STATUS_MSG_MAXLEN + + BT_MESH_MODEL_OP_LEN(BT_MESH_BLOB_OP_BLOCK_REPORT) + + BT_MESH_MIC_SHORT) <= BT_MESH_RX_SDU_MAX, + "The BLOB Partial Block Report message does not fit into " + "the maximum incoming SDU size."); + +BUILD_ASSERT((BLOB_BLOCK_STATUS_MSG_MAXLEN + + BT_MESH_MODEL_OP_LEN(BT_MESH_BLOB_OP_BLOCK_STATUS) + + BT_MESH_MIC_SHORT) <= BT_MESH_RX_SDU_MAX, + "The BLOB Block Status message does not fit into the " + "maximum incoming SDU size."); + +struct block_status +{ + enum bt_mesh_blob_status status; + enum bt_mesh_blob_chunks_missing missing; + struct bt_mesh_blob_block block; +}; + +static struct bt_mesh_blob_target *next_target(struct bt_mesh_blob_cli *cli, + struct bt_mesh_blob_target **current); +static void transfer_cancel(struct bt_mesh_blob_cli *cli); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void start_retry_timer(struct bt_mesh_blob_cli *cli) +{ + clock_t next_timeout; + + if (SENDING_CHUNKS_IN_PULL_MODE(cli)) + { + int64_t next_timeout_ms = cli->tx.cli_timestamp; + struct bt_mesh_blob_target *target = NULL; + + TARGETS_FOR_EACH(cli, target) + { + if (!target->procedure_complete && + target->status == BT_MESH_BLOB_SUCCESS && + target->pull->block_report_timestamp < next_timeout_ms) + { + next_timeout_ms = target->pull->block_report_timestamp; + } + } + + /* cli_timestamp and block_report_timestamp represent absolute time, + * while k_work_* functions use relative time. + */ + + next_timeout_ms -= TICK2MSEC(clock_systime_ticks()); + next_timeout = next_timeout_ms <= 0 ? 0 : MSEC2TICK(next_timeout_ms); + } + else + { + next_timeout = + MSEC2TICK(CLIENT_TIMEOUT_MSEC( + cli) / CONFIG_BT_MESH_BLOB_CLI_BLOCK_RETRIES); + } + + (void)k_work_reschedule(&cli->tx.retry, next_timeout); +} + +static void cli_state_reset(struct bt_mesh_blob_cli *cli) +{ + k_work_cancel_delayable(&cli->tx.retry); + cli->xfer = NULL; + cli->state = BT_MESH_BLOB_CLI_STATE_NONE; + cli->tx.ctx.is_inited = 0; + cli->tx.cli_timestamp = 0ll; + cli->tx.sending = 0; +} + +static struct bt_mesh_blob_target *target_get(struct bt_mesh_blob_cli *cli, + uint16_t addr) +{ + struct bt_mesh_blob_target *target; + + TARGETS_FOR_EACH(cli, target) + { + if (target->addr == addr) + { + return target; + } + } + + LOG_ERR("Unknown target 0x%04x", addr); + return NULL; +} + +static void target_drop( + struct bt_mesh_blob_cli *cli, struct bt_mesh_blob_target *target, + enum bt_mesh_blob_status reason) +{ + LOG_WRN("Dropping 0x%04x: %u", target->addr, reason); + + target->status = reason; + if (cli->cb && cli->cb->lost_target) + { + cli->cb->lost_target(cli, target, reason); + } +} + +static uint32_t targets_reset(struct bt_mesh_blob_cli *cli) +{ + struct bt_mesh_blob_target *target; + uint32_t count = 0; + + TARGETS_FOR_EACH(cli, target) + { + if (target->status == BT_MESH_BLOB_SUCCESS) + { + target->acked = 0U; + count++; + } + } + + return count; +} + +static bool targets_active(struct bt_mesh_blob_cli *cli) +{ + struct bt_mesh_blob_target *target; + + TARGETS_FOR_EACH(cli, target) + { + if (target->status == BT_MESH_BLOB_SUCCESS) + { + return true; + } + } + + return false; +} + +static bool targets_timedout(struct bt_mesh_blob_cli *cli) +{ + struct bt_mesh_blob_target *target; + + TARGETS_FOR_EACH(cli, target) + { + if (!!target->timedout) + { + return true; + } + } + + return false; +} + +static int io_open(struct bt_mesh_blob_cli *cli) +{ + if (!cli->io->open) + { + return 0; + } + + return cli->io->open(cli->io, cli->xfer, BT_MESH_BLOB_READ); +} + +static void io_close(struct bt_mesh_blob_cli *cli) +{ + if (!cli->io->close) + { + return; + } + + cli->io->close(cli->io, cli->xfer); +} + +static uint16_t next_missing_chunk( + struct bt_mesh_blob_cli *cli, const uint8_t *missing_chunks, uint16_t idx) +{ + do + { + if (blob_chunk_missing_get(missing_chunks, idx)) + { + break; + } + } + while (++idx < cli->block.chunk_count); + + return idx; +} + +/* Used in Pull mode to collect all missing chunks from each target in + * cli->block.missing. + */ + +static void update_missing_chunks(struct bt_mesh_blob_cli *cli) +{ + struct bt_mesh_blob_target *target; + + memset(cli->block.missing, 0, sizeof(cli->block.missing)); + + TARGETS_FOR_EACH(cli, target) + { + if (target->procedure_complete || target->timedout) + { + continue; + } + + for (size_t idx = 0; idx < cli->block.chunk_count; idx++) + { + bool missing = blob_chunk_missing_get(cli->block.missing, idx) || + blob_chunk_missing_get(target->pull->missing, idx); + + blob_chunk_missing_set(cli->block.missing, idx, missing); + } + } +} + +static inline size_t chunk_size(const struct bt_mesh_blob_xfer *xfer, + const struct bt_mesh_blob_block *block, + uint16_t chunk_idx) +{ + if ((chunk_idx == block->chunk_count - 1) && + (block->size % xfer->chunk_size)) + { + return block->size % xfer->chunk_size; + } + + return xfer->chunk_size; +} + +static int chunk_idx_decode(struct net_buf_simple *buf) +{ + uint16_t data; + uint8_t byte; + + if (buf->len == 0) + { + return -EINVAL; + } + + byte = net_buf_simple_pull_u8(buf); + + /* utf-8 decoding */ + + if ((byte & 0xf0) == 0xe0) + { + /* 0x800 - 0xffff */ + + if (buf->len < 2) + { + return -EINVAL; + } + + data = (byte & 0x0f) << 12; + data |= (net_buf_simple_pull_u8(buf) & 0x3f) << 6; + data |= (net_buf_simple_pull_u8(buf) & 0x3f); + } + else if ((byte & 0xe0) == 0xc0) + { + /* 0x80 - 0x7ff */ + + if (buf->len < 1) + { + return -EINVAL; + } + + data = (byte & 0x1f) << 6; + data |= (net_buf_simple_pull_u8(buf) & 0x3f); + } + else + { + /* 0x00 - 0x7f */ + + data = byte & 0x7f; + } + + return data; +} + +static void block_set(struct bt_mesh_blob_cli *cli, uint16_t block_idx) +{ + cli->block.number = block_idx; + cli->block.offset = block_idx * (1UL << cli->xfer->block_size_log); + cli->block.size = blob_block_size(cli->xfer->size, + cli->xfer->block_size_log, + block_idx); + cli->block.chunk_count = DIV_ROUND_UP(cli->block.size, + cli->xfer->chunk_size); + + if (cli->xfer->mode == BT_MESH_BLOB_XFER_MODE_PUSH) + { + blob_chunk_missing_set_all(&cli->block); + } + else + { + struct bt_mesh_blob_target *target; + + /* In pull mode, the server will tell us which blocks are missing. */ + + memset(cli->block.missing, 0, sizeof(cli->block.missing)); + + TARGETS_FOR_EACH(cli, target) + { + memset(target->pull->missing, 0, sizeof(target->pull->missing)); + } + } + + LOG_DBG("%u size: %u chunks: %u", block_idx, cli->block.size, + cli->block.chunk_count); +} + +static void suspend(struct bt_mesh_blob_cli *cli) +{ + cli->state = BT_MESH_BLOB_CLI_STATE_SUSPENDED; + + if (cli->cb && cli->cb->suspended) + { + cli->cb->suspended(cli); + } +} + +static void end(struct bt_mesh_blob_cli *cli, bool success) +{ + const struct bt_mesh_blob_xfer *xfer = cli->xfer; + + LOG_DBG("%u", success); + + io_close(cli); + cli_state_reset(cli); + if (cli->cb && cli->cb->end) + { + cli->cb->end(cli, xfer, success); + } +} + +static enum bt_mesh_blob_status caps_adjust(struct bt_mesh_blob_cli *cli, + const struct bt_mesh_blob_cli_caps *in) +{ + if (!(in->modes & cli->caps.modes)) + { + return BT_MESH_BLOB_ERR_UNSUPPORTED_MODE; + } + + if ((in->min_block_size_log > cli->caps.max_block_size_log) || + (in->max_block_size_log < cli->caps.min_block_size_log)) + { + return BT_MESH_BLOB_ERR_INVALID_BLOCK_SIZE; + } + + cli->caps.min_block_size_log = MAX(cli->caps.min_block_size_log, + in->min_block_size_log); + cli->caps.max_block_size_log = MIN(cli->caps.max_block_size_log, + in->max_block_size_log); + cli->caps.max_chunks = MIN(cli->caps.max_chunks, in->max_chunks); + cli->caps.mtu_size = MIN(cli->caps.mtu_size, in->mtu_size); + cli->caps.max_chunk_size = MIN(cli->caps.max_chunk_size, + in->max_chunk_size); + cli->caps.modes &= in->modes; + cli->caps.max_size = MIN(cli->caps.max_size, in->max_size); + + return BT_MESH_BLOB_SUCCESS; +} + +/**************************************************************************** + * TX State machine + * + * All messages in the transfer are going out to all the targets, either + * through group messaging or directly to each. The TX state machine + * implements this pattern for the transfer state machine to use. It will + * send the messages to all devices (through the group or directly), + * repeating until it receives a response from each device, or the attempts + * run out. Messages may also be marked as unacked if they require no + * response. + ****************************************************************************/ + +static struct bt_mesh_blob_target *next_target(struct bt_mesh_blob_cli *cli, + struct bt_mesh_blob_target **current) +{ + if (*current) + { + *current = list_next_container((struct list_node + **)&cli->inputs->targets, *current, n); + } + else + { + *current = list_peek_head_container((struct list_node + **)&cli->inputs->targets, *current, n); + } + + while (*current) + { + if ((*current)->acked || (*current)->procedure_complete || + (*current)->status != BT_MESH_BLOB_SUCCESS || + (*current)->timedout || + (*current)->skip) + { + goto next; + } + + if (SENDING_CHUNKS_IN_PULL_MODE(cli) && + (TICK2MSEC(clock_systime_ticks()) < + (*current)->pull->block_report_timestamp || + !blob_chunk_missing_get((*current)->pull->missing, + cli->chunk_idx))) + { + /* Skip targets that didn't time out or timed out, but confirmed + * the currently transmitted chunk (cli->chunk_idx). + */ + + goto next; + } + + break; + +next: + *current = list_next_container((struct list_node + **)&cli->inputs->targets, *current, n); + } + + return *current; +} + +static void send(struct bt_mesh_blob_cli *cli) +{ + cli->tx.sending = 1U; + if (UNICAST_MODE(cli)) + { + cli->tx.ctx.send(cli, cli->tx.target->addr); + } + else + { + cli->tx.ctx.send(cli, cli->inputs->group); + } +} + +static void broadcast_complete(struct bt_mesh_blob_cli *cli) +{ + LOG_DBG("%s", cli->tx.cancelled ? "cancelling" : "continuing"); + + cli->tx.ctx.is_inited = 0; + k_work_cancel_delayable(&cli->tx.retry); + + if (cli->tx.cancelled) + { + transfer_cancel(cli); + } + else + { + DEBUGASSERT(cli->tx.ctx.next); + cli->tx.ctx.next(cli); + } +} + +static void tx_complete(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_mesh_blob_cli *cli = CONTAINER_OF(dwork, struct bt_mesh_blob_cli, + tx.complete); + + if (!cli->tx.ctx.is_inited || !cli->tx.sending) + { + return; + } + + cli->tx.sending = 0U; + + if (cli->tx.cancelled) + { + broadcast_complete(cli); + return; + } + + if (cli->tx.ctx.send_complete) + { + cli->tx.ctx.send_complete(cli, cli->tx.target->addr); + } + + if (UNICAST_MODE(cli) && next_target(cli, &cli->tx.target)) + { + send(cli); + return; + } + + if (cli->tx.ctx.acked && cli->tx.pending) + { + start_retry_timer(cli); + return; + } + + broadcast_complete(cli); +} + +static void drop_remaining_targets(struct bt_mesh_blob_cli *cli) +{ + struct bt_mesh_blob_target *target; + + LOG_DBG(""); + + cli->tx.pending = 0; + + TARGETS_FOR_EACH(cli, target) + { + if (!target->acked && !target->timedout && !target->procedure_complete && + !target->skip) + { + target->timedout = 1U; + target_drop(cli, target, BT_MESH_BLOB_ERR_INTERNAL); + } + } + + /* Update missing chunks to exclude chunks from dropped targets. */ + + if (SENDING_CHUNKS_IN_PULL_MODE(cli)) + { + update_missing_chunks(cli); + } +} + +static void retry_timeout(struct k_work *work) +{ + struct bt_mesh_blob_cli *cli = CONTAINER_OF(work, struct bt_mesh_blob_cli, + tx.retry.work); + + /* When sending chunks in Pull mode, timeout is handled differently. Client + * will drop all non-responsive servers by cli_timestamp. By calling + * broadcast_complete(), client will either retransmit the missing chunks + * (if any), or proceed to the next block, or suspend the transfer if all + * targets timed out. All this is handled in block_check_end(). Retry logic + * for all other procedures in Pull mode is handled as in Push mode. + */ + + if (SENDING_CHUNKS_IN_PULL_MODE(cli)) + { + if (TICK2MSEC(clock_systime_ticks()) >= cli->tx.cli_timestamp) + { + LOG_DBG("Transfer timed out."); + + if (!cli->tx.ctx.optional) + { + drop_remaining_targets(cli); + } + } + + broadcast_complete(cli); + return; + } + + LOG_DBG("%u", cli->tx.retries); + + cli->tx.retries--; + cli->tx.target = NULL; + + DEBUGASSERT(!cli->tx.sending); + DEBUGASSERT(cli->tx.ctx.is_inited); + + if (!cli->tx.retries) + { + LOG_DBG("Transfer timed out."); + + if (!cli->tx.ctx.optional) + { + drop_remaining_targets(cli); + } + + broadcast_complete(cli); + return; + } + + if (!cli->tx.ctx.acked || !next_target(cli, &cli->tx.target) || + cli->tx.cancelled) + { + broadcast_complete(cli); + return; + } + + send(cli); +} + +void blob_cli_broadcast(struct bt_mesh_blob_cli *cli, + const struct blob_cli_broadcast_ctx *ctx) +{ + if (cli->tx.ctx.is_inited || cli->tx.sending) + { + LOG_ERR("BLOB cli busy"); + return; + } + + cli->tx.cancelled = 0U; + cli->tx.retries = CONFIG_BT_MESH_BLOB_CLI_BLOCK_RETRIES; + cli->tx.ctx = *ctx; + cli->tx.ctx.is_inited = 1U; + + cli->tx.pending = targets_reset(cli); + + LOG_DBG("%u targets", cli->tx.pending); + + cli->tx.target = NULL; + if (!next_target(cli, &cli->tx.target)) + { + LOG_DBG("No active targets"); + broadcast_complete(cli); + return; + } + + send(cli); +} + +void blob_cli_broadcast_tx_complete(struct bt_mesh_blob_cli *cli) +{ + k_work_schedule(&cli->tx.complete, + MSEC2TICK(cli->tx.ctx.post_send_delay_ms)); +} + +void blob_cli_broadcast_rsp(struct bt_mesh_blob_cli *cli, + struct bt_mesh_blob_target *target) +{ + if (target->acked) + { + return; + } + + LOG_DBG("0x%04x, pending: %d", target->addr, cli->tx.pending); + + target->acked = 1U; + + if (!--cli->tx.pending && !cli->tx.sending) + { + broadcast_complete(cli); + } +} + +void blob_cli_broadcast_abort(struct bt_mesh_blob_cli *cli) +{ + if (!cli->tx.ctx.is_inited) + { + return; + } + + if ((cli)->state >= BT_MESH_BLOB_CLI_STATE_START) + { + io_close(cli); + } + + cli_state_reset(cli); +} + +static void send_start(uint16_t duration, int err, void *cb_data); +static void send_end(int err, void *user_data); + +static int tx(struct bt_mesh_blob_cli *cli, + uint16_t addr, struct net_buf_simple *buf) +{ + static const struct bt_mesh_send_cb end_cb = + { + .start = send_start, + .end = send_end, + }; + + struct bt_mesh_msg_ctx ctx = + { + .app_idx = cli->inputs->app_idx, + .addr = addr, + .send_ttl = cli->inputs->ttl, + }; + + int err; + + err = bt_mesh_model_send(cli->mod, &ctx, buf, &end_cb, cli); + if (err) + { + LOG_ERR("Send err: %d", err); + send_end(err, cli); + return err; + } + + return 0; +} + +static void send_start(uint16_t duration, int err, void *cb_data) +{ + if (err) + { + LOG_ERR("TX Start failed: %d", err); + send_end(err, cb_data); + } +} + +static void send_end(int err, void *user_data) +{ + struct bt_mesh_blob_cli *cli = user_data; + + if (!cli->tx.ctx.is_inited) + { + return; + } + + blob_cli_broadcast_tx_complete(cli); +} + +/**************************************************************************** + * TX + ****************************************************************************/ + +static void info_get_tx(struct bt_mesh_blob_cli *cli, uint16_t dst) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_BLOB_OP_INFO_GET, 0); + bt_mesh_model_msg_init(&buf, BT_MESH_BLOB_OP_INFO_GET); + + tx(cli, dst, &buf); +} + +static void xfer_start_tx(struct bt_mesh_blob_cli *cli, uint16_t dst) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_BLOB_OP_XFER_START, 16); + bt_mesh_model_msg_init(&buf, BT_MESH_BLOB_OP_XFER_START); + net_buf_simple_add_u8(&buf, cli->xfer->mode << 6); + net_buf_simple_add_le64(&buf, cli->xfer->id); + net_buf_simple_add_le32(&buf, cli->xfer->size); + net_buf_simple_add_u8(&buf, cli->xfer->block_size_log); + net_buf_simple_add_le16(&buf, BT_MESH_TX_SDU_MAX); + + tx(cli, dst, &buf); +} + +static void xfer_get_tx(struct bt_mesh_blob_cli *cli, uint16_t dst) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_BLOB_OP_XFER_GET, 0); + bt_mesh_model_msg_init(&buf, BT_MESH_BLOB_OP_XFER_GET); + + tx(cli, dst, &buf); +} + +static void xfer_cancel_tx(struct bt_mesh_blob_cli *cli, uint16_t dst) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_BLOB_OP_XFER_CANCEL, 8); + bt_mesh_model_msg_init(&buf, BT_MESH_BLOB_OP_XFER_CANCEL); + net_buf_simple_add_le64(&buf, cli->xfer->id); + + tx(cli, dst, &buf); +} + +static void block_start_tx(struct bt_mesh_blob_cli *cli, uint16_t dst) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_BLOB_OP_BLOCK_START, 4); + bt_mesh_model_msg_init(&buf, BT_MESH_BLOB_OP_BLOCK_START); + net_buf_simple_add_le16(&buf, cli->block.number); + net_buf_simple_add_le16(&buf, cli->xfer->chunk_size); + + tx(cli, dst, &buf); +} + +static void chunk_tx(struct bt_mesh_blob_cli *cli, uint16_t dst) +{ + NET_BUF_SIMPLE_DEFINE(buf, BT_MESH_TX_SDU_MAX); + struct bt_mesh_blob_chunk chunk; + int err; + + bt_mesh_model_msg_init(&buf, BT_MESH_BLOB_OP_CHUNK); + net_buf_simple_add_le16(&buf, cli->chunk_idx); + + chunk.size = chunk_size(cli->xfer, &cli->block, cli->chunk_idx); + chunk.offset = cli->xfer->chunk_size * cli->chunk_idx; + chunk.data = net_buf_simple_add(&buf, chunk.size); + + err = cli->io->rd(cli->io, cli->xfer, &cli->block, &chunk); + if (err || cli->state == BT_MESH_BLOB_CLI_STATE_NONE) + { + bt_mesh_blob_cli_cancel(cli); + return; + } + + tx(cli, dst, &buf); +} + +static void block_get_tx(struct bt_mesh_blob_cli *cli, uint16_t dst) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_BLOB_OP_BLOCK_GET, 0); + bt_mesh_model_msg_init(&buf, BT_MESH_BLOB_OP_BLOCK_GET); + + tx(cli, dst, &buf); +} + +/**************************************************************************** + * State machine + * + * The BLOB Client state machine walks through the steps in the BLOB transfer + * in the following fashion: + * + * .---------------------------------------. + * xfer_start | | + * | V | + * ->block_set -> block_start | + * A | | + * | ->chunk_send -> chunk_send_end | + * | | | + * | V | + * | [more missing chunks?]-----[Yes]-----+ + * | | | + * | [No] | + * | | | + * | V | + * | [mode?] | + * | .---[Push]---' '---[Pull]---. | + * | | | | + * | V V | + * | block_check block_report_wait | + * | | | | + * | '-----------. .-------------' | + * | | | | + * | V V | + * | block_check_end | + * | | | + * | V | + * | [block completed?]------[No]------' + * | | + * | [Yes] + * | | + * | V + * '----------[No]---------[last block sent?] + * | + * [Yes] + * | + * V + * confirm_transfer + * | + * V + * transfer_complete + * + * In each state, the Client transmits a message to all target nodes. In each + * state, except when sending chunks (chunk_send), the Client expects a + * response from all target nodes, before proceeding to the next state. + * + * When a target node responds, the Client calls @ref blob_cli_broadcast_rsp + * for the corresponding target. Once all target nodes has responded, the + * Client proceeds to the next state. + * + * When sending chunks in Push mode, the Client will proceed to the next + * state (block_check) after transmitting all missing chunks. In the + * block_check state, the Client will request a block status from all target + * nodes. If any targets have missing chunks, the Client will resend them. + * + * When sending chunks in Pull mode, the Client addresses each target node + * individually using + * @ref bt_mesh_blob_target_pull structure. The Client uses @ref + * bt_mesh_blob_cli::block::missing to keep all missing chunks for the + * current block. Missing chunks for an individual target is kept in @ref + * bt_mesh_blob_target_pull::missing. The Client uses @ref + * bt_mesh_blob_target_pull::block_report_timeout to decide if it can send a + * chunk to this target. + * + * After sending all reported missing chunks to each target, the Client + * updates + * @ref bt_mesh_blob_target_pull::block_report_timestamp value for every + * target individually in chunk_tx_complete. The Client then proceeds to + * block_report_wait state and uses the earliest of all + * block_report_timestamp and cli_timestamp to schedule the retry timer. When + * the retry timer expires, the Client proceeds to the block_check_end state. + * + * In Pull mode, target nodes send a Partial Block Report message to the + * Client to inform about missing chunks. The Client doesn't control when + * these messages are sent by target nodes, and therefore it can't use @ref + * blob_cli_broadcast_rsp when it receives them. When the Client receives the + * Partial Block Report message, it updates missing chunks, resets + * block_report_timestamp, and explicitly calls @ref broadcast_complete to + * proceed to block_check_end state. + * + ****************************************************************************/ + +static void caps_collected(struct bt_mesh_blob_cli *cli); +static void block_start(struct bt_mesh_blob_cli *cli); +static void chunk_send(struct bt_mesh_blob_cli *cli); +static void block_check(struct bt_mesh_blob_cli *cli); +static void block_check_end(struct bt_mesh_blob_cli *cli); +static void block_report_wait(struct bt_mesh_blob_cli *cli); +static void chunk_send_end(struct bt_mesh_blob_cli *cli); +static void confirm_transfer(struct bt_mesh_blob_cli *cli); +static void transfer_complete(struct bt_mesh_blob_cli *cli); + +static void caps_get(struct bt_mesh_blob_cli *cli) +{ + const struct blob_cli_broadcast_ctx ctx = + { + .send = info_get_tx, + .next = caps_collected, + .acked = true, + }; + + cli->state = BT_MESH_BLOB_CLI_STATE_CAPS_GET; + blob_cli_broadcast(cli, &ctx); +} + +static void caps_collected(struct bt_mesh_blob_cli *cli) +{ + struct bt_mesh_blob_target *target; + bool success = false; + + cli->state = BT_MESH_BLOB_CLI_STATE_NONE; + + cli_state_reset(cli); + + TARGETS_FOR_EACH(cli, target) + { + if (target->status == BT_MESH_BLOB_SUCCESS) + { + success = true; + break; + } + } + + while (success && + (1UL << cli->caps.max_block_size_log) > + ( + cli->caps.max_chunk_size * cli->caps.max_chunks)) + { + cli->caps.max_block_size_log--; + } + + if (cli->cb && cli->cb->caps) + { + cli->cb->caps(cli, success ? &cli->caps : NULL); + } +} + +static int xfer_start(struct bt_mesh_blob_cli *cli) +{ + const struct blob_cli_broadcast_ctx ctx = + { + .send = xfer_start_tx, + .next = block_start, + .acked = true, + }; + + int err; + + err = io_open(cli); + if (err) + { + return -EIO; + } + + cli->state = BT_MESH_BLOB_CLI_STATE_START; + + blob_cli_broadcast(cli, &ctx); + return 0; +} + +static void block_start(struct bt_mesh_blob_cli *cli) +{ + const struct blob_cli_broadcast_ctx ctx = + { + .send = block_start_tx, + .next = chunk_send, + .acked = true, + }; + + struct bt_mesh_blob_target *target; + + if (!targets_active(cli)) + { + if (targets_timedout(cli)) + { + suspend(cli); + return; + } + + end(cli, false); + return; + } + + LOG_DBG("%u (%u chunks, %u/%u)", cli->block.number, cli->block.chunk_count, + cli->block.number + 1, + cli->block_count); + + cli->chunk_idx = 0; + cli->state = BT_MESH_BLOB_CLI_STATE_BLOCK_START; + /* Client Timeout Timer in Send Data State Machine is initialized initially + * after transmitting the first bunch of chunks (see block_report_wait()). + * Next time it will be updated after every Partial Block Report message. + */ + + cli->tx.cli_timestamp = 0ll; + + TARGETS_FOR_EACH(cli, target) + { + target->procedure_complete = 0U; + + if (cli->xfer->mode == BT_MESH_BLOB_XFER_MODE_PULL) + { + target->pull->block_report_timestamp = 0ll; + } + } + + if (cli->io->block_start) + { + cli->io->block_start(cli->io, cli->xfer, &cli->block); + if (cli->state == BT_MESH_BLOB_CLI_STATE_NONE) + { + return; + } + } + + blob_cli_broadcast(cli, &ctx); +} + +static void chunk_tx_complete(struct bt_mesh_blob_cli *cli, uint16_t dst) +{ + if (cli->xfer->mode != BT_MESH_BLOB_XFER_MODE_PULL) + { + return; + } + + /* Update Block Report Timer individually for each target after sending out + * the last chunk in current iteration. + */ + + uint16_t chunk_idx = next_missing_chunk(cli, cli->tx.target->pull->missing, + cli->chunk_idx + 1); + + if (chunk_idx < cli->block.chunk_count) + { + /* Will send more chunks to this target in this iteration. */ + + return; + } + + /* This was the last chunk sent for this target. Now start the Block Report + * Timeout Timer. + */ + + struct bt_mesh_blob_target *target; + int64_t timestamp = + TICK2MSEC(clock_systime_ticks()) + BLOCK_REPORT_TIME_MSEC; + + if (!UNICAST_MODE(cli)) + { + /* If using group addressing, reset timestamp for all targets after all + * chunks are sent to the group address + */ + + TARGETS_FOR_EACH(cli, target) + { + target->pull->block_report_timestamp = timestamp; + } + + return; + } + + cli->tx.target->pull->block_report_timestamp = timestamp; +} + +static void chunk_send(struct bt_mesh_blob_cli *cli) +{ + struct blob_cli_broadcast_ctx ctx = + { + .send = chunk_tx, + .next = chunk_send_end, + .acked = false, + .post_send_delay_ms = cli->chunk_interval_ms, + }; + + if (cli->xfer->mode == BT_MESH_BLOB_XFER_MODE_PULL) + { + ctx.send_complete = chunk_tx_complete; + } + + if (!targets_active(cli)) + { + if (targets_timedout(cli)) + { + suspend(cli); + return; + } + + end(cli, false); + return; + } + + LOG_DBG("%u / %u size: %u", cli->chunk_idx + 1, cli->block.chunk_count, + chunk_size(cli->xfer, &cli->block, cli->chunk_idx)); + + cli->state = BT_MESH_BLOB_CLI_STATE_BLOCK_SEND; + blob_cli_broadcast(cli, &ctx); +} + +static void chunk_send_end(struct bt_mesh_blob_cli *cli) +{ + /* In pull mode, the partial block reports are used to confirm which + * chunks have been received, while in push mode, we just assume that a + * sent chunk has been received. + */ + + if (cli->xfer->mode == BT_MESH_BLOB_XFER_MODE_PUSH) + { + blob_chunk_missing_set(cli->block.missing, cli->chunk_idx, false); + } + + cli->chunk_idx = next_missing_chunk(cli, cli->block.missing, + cli->chunk_idx + 1); + if (cli->chunk_idx < cli->block.chunk_count) + { + chunk_send(cli); + return; + } + + if (cli->xfer->mode == BT_MESH_BLOB_XFER_MODE_PUSH) + { + block_check(cli); + } + else + { + block_report_wait(cli); + } +} + +/* The block checking pair(block_check - block_check_end) + * is relevant only for Push mode. + */ + +static void block_check(struct bt_mesh_blob_cli *cli) +{ + const struct blob_cli_broadcast_ctx ctx = + { + .send = block_get_tx, + .next = block_check_end, + .acked = true, + }; + + cli->state = BT_MESH_BLOB_CLI_STATE_BLOCK_CHECK; + + LOG_DBG(""); + + blob_cli_broadcast(cli, &ctx); +} + +static void block_report_wait(struct bt_mesh_blob_cli *cli) +{ + const struct blob_cli_broadcast_ctx ctx = + { + .next = block_check_end, + .acked = false, + }; + + /* Check if all servers already confirmed all chunks during the + * transmission. + */ + + if (next_missing_chunk(cli, cli->block.missing, + 0) >= cli->block.chunk_count) + { + block_check_end(cli); + return; + } + + LOG_DBG("Waiting for partial block report..."); + cli->tx.ctx = ctx; + + /* Start Client Timeout Timer in Send Data sub-procedure for the first + * time. + */ + + if (!cli->tx.cli_timestamp) + { + cli->tx.cli_timestamp = + TICK2MSEC(clock_systime_ticks()) + CLIENT_TIMEOUT_MSEC(cli); + } + + start_retry_timer(cli); +} + +static void block_check_end(struct bt_mesh_blob_cli *cli) +{ + LOG_DBG(""); + + if (!targets_active(cli)) + { + if (targets_timedout(cli)) + { + suspend(cli); + return; + } + + end(cli, false); + return; + } + + cli->chunk_idx = next_missing_chunk(cli, cli->block.missing, 0); + if (cli->chunk_idx < cli->block.chunk_count) + { + chunk_send(cli); + return; + } + + LOG_DBG("No more missing chunks for block %u", cli->block.number); + + if (cli->io->block_end) + { + cli->io->block_end(cli->io, cli->xfer, &cli->block); + if (cli->state == BT_MESH_BLOB_CLI_STATE_NONE) + { + return; + } + } + + if (cli->block.number == cli->block_count - 1) + { + struct bt_mesh_blob_target *target; + + TARGETS_FOR_EACH(cli, target) + { + target->procedure_complete = 0U; + } + + confirm_transfer(cli); + return; + } + + block_set(cli, cli->block.number + 1); + block_start(cli); +} + +static void confirm_transfer(struct bt_mesh_blob_cli *cli) +{ + const struct blob_cli_broadcast_ctx ctx = + { + .send = xfer_get_tx, + .next = transfer_complete, + .acked = true, + }; + + LOG_DBG(""); + + cli->state = BT_MESH_BLOB_CLI_STATE_XFER_CHECK; + + blob_cli_broadcast(cli, &ctx); +} + +static void progress_checked(struct bt_mesh_blob_cli *cli) +{ + LOG_DBG(""); + + cli->state = BT_MESH_BLOB_CLI_STATE_NONE; + + if (cli->cb && cli->cb->xfer_progress_complete) + { + cli->cb->xfer_progress_complete(cli); + } +} + +static void check_transfer(struct bt_mesh_blob_cli *cli) +{ + const struct blob_cli_broadcast_ctx ctx = + { + .send = xfer_get_tx, + .next = progress_checked, + .acked = true, + }; + + LOG_DBG(""); + + cli->state = BT_MESH_BLOB_CLI_STATE_XFER_PROGRESS_GET; + + blob_cli_broadcast(cli, &ctx); +} + +static void transfer_cancel(struct bt_mesh_blob_cli *cli) +{ + const struct blob_cli_broadcast_ctx ctx = + { + .send = xfer_cancel_tx, + .next = transfer_complete, + .acked = true, + }; + + LOG_DBG(""); + + cli->state = BT_MESH_BLOB_CLI_STATE_CANCEL; + + blob_cli_broadcast(cli, &ctx); +} + +static void transfer_complete(struct bt_mesh_blob_cli *cli) +{ + bool success = targets_active(cli) && + cli->state == BT_MESH_BLOB_CLI_STATE_XFER_CHECK; + + end(cli, success); +} + +/**************************************************************************** + * RX + ****************************************************************************/ + +static void rx_block_status( + struct bt_mesh_blob_cli *cli, struct bt_mesh_blob_target *target, + struct block_status *block) +{ + if (cli->state != BT_MESH_BLOB_CLI_STATE_BLOCK_START && + cli->state != BT_MESH_BLOB_CLI_STATE_BLOCK_SEND && + cli->state != BT_MESH_BLOB_CLI_STATE_BLOCK_CHECK) + { + LOG_WRN("Invalid state %u", cli->state); + return; + } + + LOG_DBG("0x%04x: block: %u status: %u", target->addr, block->block.number, + block->status); + + if (block->status != BT_MESH_BLOB_SUCCESS) + { + target_drop(cli, target, block->status); + blob_cli_broadcast_rsp(cli, target); + return; + } + + if (block->block.number != cli->block.number) + { + LOG_DBG("Invalid block num (expected %u)", cli->block.number); + return; + } + + if (block->missing == BT_MESH_BLOB_CHUNKS_MISSING_NONE) + { + target->procedure_complete = 1U; + + if (cli->xfer->mode == BT_MESH_BLOB_XFER_MODE_PULL) + { + memset(target->pull->missing, 0, sizeof(target->pull->missing)); + update_missing_chunks(cli); + } + + LOG_DBG("Target 0x%04x received all chunks", target->addr); + } + else if (block->missing == BT_MESH_BLOB_CHUNKS_MISSING_ALL) + { + blob_chunk_missing_set_all(&cli->block); + } + else if (cli->xfer->mode == BT_MESH_BLOB_XFER_MODE_PULL) + { + memcpy(target->pull->missing, block->block.missing, + sizeof(block->block.missing)); + + LOG_DBG("Missing: %s", bt_hex(target->pull->missing, + cli->block.chunk_count)); + + update_missing_chunks(cli); + + /* Target has responded. Reset the timestamp so that client can start + * transmitting missing chunks to it. + */ + + target->pull->block_report_timestamp = 0ll; + } + else + { + for (int i = 0; i < ARRAY_SIZE(block->block.missing); ++i) + { + cli->block.missing[i] |= block->block.missing[i]; + } + } + + if (SENDING_CHUNKS_IN_PULL_MODE(cli)) + { + if (!cli->tx.sending) + { + /* If not sending, then the retry timer is running. Call + * broadcast_complete() to proceed to block_check_end() and start + * transmitting missing chunks. + */ + + broadcast_complete(cli); + } + + /* When sending chunks in Pull mode, we don't confirm transaction when + * receiving Partial Block Report message. + */ + + return; + } + + blob_cli_broadcast_rsp(cli, target); +} + +static int handle_xfer_status( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_blob_cli *cli = mod->rt->user_data; + enum bt_mesh_blob_xfer_phase expected_phase; + struct bt_mesh_blob_target *target; + struct bt_mesh_blob_xfer_info info = + { + 0 + }; + + uint8_t status_and_mode; + + status_and_mode = net_buf_simple_pull_u8(buf); + info.status = status_and_mode & BIT_MASK(4); + info.mode = status_and_mode >> 6; + info.phase = net_buf_simple_pull_u8(buf); + + if (buf->len) + { + info.id = net_buf_simple_pull_le64(buf); + } + + if (buf->len >= 7) + { + info.size = net_buf_simple_pull_le32(buf); + info.block_size_log = net_buf_simple_pull_u8(buf); + info.mtu_size = net_buf_simple_pull_le16(buf); + info.missing_blocks = net_buf_simple_pull(buf, buf->len); + } + + LOG_DBG("status: %u %s phase: %u %s", info.status, + info.mode == + BT_MESH_BLOB_XFER_MODE_PUSH ? "push" : "pull", info.phase, + bt_hex(&info.id, 8)); + + if (cli->state != BT_MESH_BLOB_CLI_STATE_START && + cli->state != BT_MESH_BLOB_CLI_STATE_XFER_CHECK && + cli->state != BT_MESH_BLOB_CLI_STATE_CANCEL && + cli->state != BT_MESH_BLOB_CLI_STATE_XFER_PROGRESS_GET) + { + LOG_WRN("Wrong state: %d", cli->state); + return -EBUSY; + } + + target = target_get(cli, ctx->addr); + if (!target) + { + return -ENOENT; + } + + if (cli->state == BT_MESH_BLOB_CLI_STATE_START) + { + expected_phase = BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK; + /* If a transfer is resumed quickly (before the server's rx_timeout + * fires), the server is still in WAITING_FOR_CHUNK. The server's + * idempotency logic replies Success without changing its phase. Accept + * WAITING_FOR_CHUNK here so the client can proceed to BLOCK_START, + * which will reset the server's timeout timer. + */ + + if (info.status == BT_MESH_BLOB_SUCCESS && + info.phase == BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_CHUNK) + { + expected_phase = info.phase; + } + } + else if (cli->state == BT_MESH_BLOB_CLI_STATE_XFER_CHECK) + { + expected_phase = BT_MESH_BLOB_XFER_PHASE_COMPLETE; + } + else if (cli->state != BT_MESH_BLOB_CLI_STATE_XFER_PROGRESS_GET) + { + expected_phase = BT_MESH_BLOB_XFER_PHASE_INACTIVE; + } + else + { + /* cli->state == BT_MESH_BLOB_CLI_STATE_XFER_PROGRESS_GET */ + + blob_cli_broadcast_rsp(cli, target); + if (cli->cb && cli->cb->xfer_progress) + { + cli->cb->xfer_progress(cli, target, &info); + } + + return 0; + } + + if (info.status != BT_MESH_BLOB_SUCCESS) + { + target_drop(cli, target, info.status); + } + else if (info.phase != expected_phase) + { + LOG_WRN("Wrong phase: %u != %u", expected_phase, info.phase); + return -EINVAL; + } + else if (info.phase != BT_MESH_BLOB_XFER_PHASE_INACTIVE && + info.id != cli->xfer->id) + { + target_drop(cli, target, BT_MESH_BLOB_ERR_WRONG_BLOB_ID); + } + + blob_cli_broadcast_rsp(cli, target); + + return 0; +} + +static int handle_block_report( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_blob_cli *cli = mod->rt->user_data; + struct block_status status = + { + .status = BT_MESH_BLOB_SUCCESS, + .block.number = cli->block.number, + .missing = (buf->len ? BT_MESH_BLOB_CHUNKS_MISSING_ENCODED + : BT_MESH_BLOB_CHUNKS_MISSING_NONE), + }; + + struct bt_mesh_blob_target *target; + + if (!cli->xfer) + { + return -EINVAL; + } + + if (cli->xfer->mode == BT_MESH_BLOB_XFER_MODE_PUSH) + { + LOG_WRN("Unexpected encoded block report in push mode"); + return -EINVAL; + } + + LOG_DBG(""); + + target = target_get(cli, ctx->addr); + if (!target) + { + return -ENOENT; + } + + while (buf->len) + { + int idx; + + idx = chunk_idx_decode(buf); + if (idx < 0) + { + return idx; + } + + blob_chunk_missing_set(status.block.missing, idx, true); + } + + /* If all chunks were already confirmed by this target, Send Data State + * Machine is in Final state for this target. Therefore, the message should + * be ignored. + */ + + if (next_missing_chunk(cli, target->pull->missing, + 0) >= cli->block.chunk_count) + { + LOG_DBG("All chunks already confirmed"); + return 0; + } + + cli->tx.cli_timestamp = + TICK2MSEC(clock_systime_ticks()) + CLIENT_TIMEOUT_MSEC(cli); + + rx_block_status(cli, target, &status); + + return 0; +} + +static int handle_block_status( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_blob_cli *cli = mod->rt->user_data; + struct bt_mesh_blob_target *target; + struct block_status status = + { + 0 + }; + + uint8_t status_and_format; + uint16_t chunk_size; + size_t len; + int idx; + + target = target_get(cli, ctx->addr); + if (!target) + { + return -ENOENT; + } + + status_and_format = net_buf_simple_pull_u8(buf); + status.status = status_and_format & BIT_MASK(4); + status.missing = status_and_format >> 6; + status.block.number = net_buf_simple_pull_le16(buf); + chunk_size = net_buf_simple_pull_le16(buf); + status.block.chunk_count = DIV_ROUND_UP(cli->block.size, chunk_size); + + LOG_DBG("status: %u block: %u encoding: %u", status.status, + status.block.number, status.missing); + + switch (status.missing) + { + case BT_MESH_BLOB_CHUNKS_MISSING_ALL: + blob_chunk_missing_set_all(&status.block); + break; + case BT_MESH_BLOB_CHUNKS_MISSING_NONE: + break; + case BT_MESH_BLOB_CHUNKS_MISSING_SOME: + if (buf->len > sizeof(status.block.missing)) + { + return -EINVAL; + } + + len = buf->len; + memcpy(status.block.missing, net_buf_simple_pull_mem(buf, len), len); + + LOG_DBG("Missing: %s", bt_hex(status.block.missing, len)); + break; + case BT_MESH_BLOB_CHUNKS_MISSING_ENCODED: + /** MshMBTv1.0: 5.3.8: An empty Missing Chunks field entails that + * there + * are no missing chunks for this block. + */ + + if (!buf->len) + { + status.missing = BT_MESH_BLOB_CHUNKS_MISSING_NONE; + } + + while (buf->len) + { + idx = chunk_idx_decode(buf); + if (idx < 0 || idx >= status.block.chunk_count) + { + LOG_ERR("Invalid encoding"); + return -EINVAL; + } + + LOG_DBG("Missing %d", idx); + + blob_chunk_missing_set(status.block.missing, idx, true); + } + break; + } + + rx_block_status(cli, target, &status); + + return 0; +} + +static int handle_info_status( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_blob_cli *cli = mod->rt->user_data; + struct bt_mesh_blob_cli_caps caps; + enum bt_mesh_blob_status status; + struct bt_mesh_blob_target *target; + + if (cli->state != BT_MESH_BLOB_CLI_STATE_CAPS_GET) + { + return -EBUSY; + } + + caps.min_block_size_log = net_buf_simple_pull_u8(buf); + caps.max_block_size_log = net_buf_simple_pull_u8(buf); + caps.max_chunks = net_buf_simple_pull_le16(buf); + caps.max_chunk_size = net_buf_simple_pull_le16(buf); + caps.max_size = net_buf_simple_pull_le32(buf); + caps.mtu_size = net_buf_simple_pull_le16(buf); + caps.modes = net_buf_simple_pull_u8(buf); + + if (caps.min_block_size_log < 0x06 || caps.max_block_size_log > 0x20 || + caps.max_block_size_log < caps.min_block_size_log || + caps.max_chunks == 0 || + caps.max_chunk_size < 8 || caps.max_size == 0 || caps.mtu_size < 0x14) + { + return -EINVAL; + } + + LOG_DBG("0x%04x\n\tblock size: %u - %u\n\tchunks: %u\n\tchunk size: %u\n" + "\tblob size: %u\n\tmtu size: %u\n\tmodes: %x", + ctx->addr, caps.min_block_size_log, caps.max_block_size_log, + caps.max_chunks, + caps.max_chunk_size, caps.max_size, caps.mtu_size, caps.modes); + + target = target_get(cli, ctx->addr); + if (!target) + { + return -ENOENT; + } + + status = caps_adjust(cli, &caps); + if (status != BT_MESH_BLOB_SUCCESS) + { + target_drop(cli, target, status); + } + + blob_cli_broadcast_rsp(cli, target); + + return 0; +} + +const struct bt_mesh_model_op _bt_mesh_blob_cli_op[] = +{ + { + BT_MESH_BLOB_OP_XFER_STATUS, + BT_MESH_LEN_MIN(2), + handle_xfer_status + }, + + { + BT_MESH_BLOB_OP_BLOCK_REPORT, + BT_MESH_LEN_MIN(0), + handle_block_report + }, + + { + BT_MESH_BLOB_OP_BLOCK_STATUS, + BT_MESH_LEN_MIN(5), + handle_block_status + }, + + { + BT_MESH_BLOB_OP_INFO_STATUS, + BT_MESH_LEN_EXACT(13), + handle_info_status + }, + + BT_MESH_MODEL_OP_END, +}; + +static int blob_cli_init(const struct bt_mesh_model *mod) +{ + struct bt_mesh_blob_cli *cli = mod->rt->user_data; + + cli->mod = mod; + + bt_mesh_blob_cli_set_chunk_interval_ms(cli, + CONFIG_BT_MESH_TX_BLOB_CHUNK_SEND_INTERVAL); + cli->tx.cli_timestamp = 0ll; + k_work_init_delayable(&cli->tx.retry, retry_timeout); + k_work_init_delayable(&cli->tx.complete, tx_complete); + + return 0; +} + +static void blob_cli_reset(const struct bt_mesh_model *mod) +{ + struct bt_mesh_blob_cli *cli = mod->rt->user_data; + + cli_state_reset(cli); +} + +const struct bt_mesh_model_cb _bt_mesh_blob_cli_cb = +{ + .init = blob_cli_init, + .reset = blob_cli_reset, +}; + +int bt_mesh_blob_cli_caps_get(struct bt_mesh_blob_cli *cli, + const struct bt_mesh_blob_cli_inputs *inputs) +{ + if (bt_mesh_blob_cli_is_busy(cli)) + { + return -EBUSY; + } + + cli->inputs = inputs; + + cli->caps.min_block_size_log = 0x06; + cli->caps.max_block_size_log = 0x20; + cli->caps.max_chunks = CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX; + cli->caps.max_chunk_size = BLOB_TX_CHUNK_SIZE; + cli->caps.max_size = 0xffffffff; + cli->caps.mtu_size = 0xffff; + cli->caps.modes = BT_MESH_BLOB_XFER_MODE_ALL; + + if (!targets_reset(cli)) + { + LOG_ERR("No valid targets"); + return -ENODEV; + } + + caps_get(cli); + + return 0; +} + +int bt_mesh_blob_cli_send(struct bt_mesh_blob_cli *cli, + const struct bt_mesh_blob_cli_inputs *inputs, + const struct bt_mesh_blob_xfer *xfer, + const struct bt_mesh_blob_io *io) +{ + if (bt_mesh_blob_cli_is_busy(cli)) + { + LOG_ERR("BLOB Client is busy"); + return -EBUSY; + } + + if (!(xfer->mode & BT_MESH_BLOB_XFER_MODE_ALL) || + xfer->block_size_log < 0x06 || + xfer->block_size_log > 0x20 + || xfer->chunk_size < 8 + || xfer->chunk_size > BLOB_TX_CHUNK_SIZE) + { + LOG_ERR("Incompatible transfer parameters"); + return -EINVAL; + } + + cli->xfer = xfer; + cli->inputs = inputs; + cli->io = io; + + if (cli->xfer->block_size_log == 0x20) + { + cli->block_count = 1; + } + else + { + cli->block_count = DIV_ROUND_UP(cli->xfer->size, + (1U << cli->xfer->block_size_log)); + } + + block_set(cli, 0); + + if (cli->block.chunk_count > CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX) + { + LOG_ERR("Too many chunks"); + return -EINVAL; + } + + if (!targets_reset(cli)) + { + LOG_ERR("No valid targets"); + return -ENODEV; + } + + LOG_DBG("\n\tblock size log: %u\n\tchunk size: %u\n" + "\tblob size: %u\n\tmode: %x", + cli->xfer->block_size_log, + cli->xfer->chunk_size, + cli->xfer->size, + cli->xfer->mode); + + return xfer_start(cli); +} + +int bt_mesh_blob_cli_suspend(struct bt_mesh_blob_cli *cli) +{ + if (cli->state == BT_MESH_BLOB_CLI_STATE_SUSPENDED) + { + return 0; + } + + if (cli->state != BT_MESH_BLOB_CLI_STATE_BLOCK_START && + cli->state != BT_MESH_BLOB_CLI_STATE_BLOCK_SEND && + cli->state != BT_MESH_BLOB_CLI_STATE_BLOCK_CHECK) + { + LOG_WRN("BLOB xfer not started: %d", cli->state); + return -EINVAL; + } + + cli->state = BT_MESH_BLOB_CLI_STATE_SUSPENDED; + (void)k_work_cancel_delayable(&cli->tx.retry); + cli->tx.ctx.is_inited = 0; + cli->tx.sending = 0; + cli->tx.cli_timestamp = 0ll; + return 0; +} + +int bt_mesh_blob_cli_resume(struct bt_mesh_blob_cli *cli) +{ + struct bt_mesh_blob_target *target; + + if (cli->state != BT_MESH_BLOB_CLI_STATE_SUSPENDED) + { + LOG_WRN("Not suspended"); + return -EINVAL; + } + + /* Restore timed out targets. */ + + TARGETS_FOR_EACH(cli, target) + { + if (!!target->timedout) + { + target->status = BT_MESH_BLOB_SUCCESS; + target->timedout = 0U; + } + } + + if (!targets_reset(cli)) + { + LOG_ERR("No valid targets"); + return -ENODEV; + } + + /* Resume from the current block, not block 0. If the server hasn't + * timed out yet (still in WAITING_FOR_CHUNK), it will reject a BLOCK_START + * for a different block number with WRONG_PHASE (MshMBTv1.0 5.2.2.3). + * Starting from the same block the client was on when it suspended avoids + * this mismatch. Earlier blocks are already completed by all active + * targets. + */ + + block_set(cli, cli->block.number); + return xfer_start(cli); +} + +void bt_mesh_blob_cli_cancel(struct bt_mesh_blob_cli *cli) +{ + if (!bt_mesh_blob_cli_is_busy(cli)) + { + LOG_WRN("BLOB xfer already cancelled"); + return; + } + + LOG_DBG(""); + + if (cli->state == BT_MESH_BLOB_CLI_STATE_CAPS_GET || + cli->state == BT_MESH_BLOB_CLI_STATE_SUSPENDED) + { + cli_state_reset(cli); + return; + } + + cli->tx.cancelled = 1U; + cli->state = BT_MESH_BLOB_CLI_STATE_CANCEL; +} + +int bt_mesh_blob_cli_xfer_progress_get(struct bt_mesh_blob_cli *cli, + const struct bt_mesh_blob_cli_inputs *inputs) +{ + if (bt_mesh_blob_cli_is_busy(cli)) + { + return -EBUSY; + } + + cli->inputs = inputs; + + check_transfer(cli); + + return 0; +} + +uint8_t bt_mesh_blob_cli_xfer_progress_active_get( + struct bt_mesh_blob_cli *cli) +{ + if (cli->state < BT_MESH_BLOB_CLI_STATE_START) + { + return 0; + } + + return (100U * cli->block.number) / cli->block_count; +} + +bool bt_mesh_blob_cli_is_busy(struct bt_mesh_blob_cli *cli) +{ + return cli->state != BT_MESH_BLOB_CLI_STATE_NONE; +} + +void bt_mesh_blob_cli_set_chunk_interval_ms(struct bt_mesh_blob_cli *cli, + uint32_t interval_ms) +{ + cli->chunk_interval_ms = interval_ms; +} diff --git a/libs/zblue/mesh/blob_io_flash.c b/libs/zblue/mesh/blob_io_flash.c new file mode 100644 index 0000000000000..3c7b2e8a692ef --- /dev/null +++ b/libs/zblue/mesh/blob_io_flash.c @@ -0,0 +1,93 @@ +/**************************************************************************** + * libs/zblue/mesh/blob_io_flash.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int io_open( + const struct bt_mesh_blob_io *io, const struct bt_mesh_blob_xfer *xfer, + enum bt_mesh_blob_io_mode mode) +{ + (void)io; + (void)xfer; + (void)mode; + + return -ENOTSUP; +} + +static void io_close(const struct bt_mesh_blob_io *io, + const struct bt_mesh_blob_xfer *xfer) +{ + (void)io; + (void)xfer; +} + +static int block_start( + const struct bt_mesh_blob_io *io, const struct bt_mesh_blob_xfer *xfer, + const struct bt_mesh_blob_block *block) +{ + (void)io; + (void)xfer; + (void)block; + + return -ENOTSUP; +} + +static int rd_chunk( + const struct bt_mesh_blob_io *io, const struct bt_mesh_blob_xfer *xfer, + const struct bt_mesh_blob_block *block, + const struct bt_mesh_blob_chunk *chunk) +{ + (void)io; + (void)xfer; + (void)block; + (void)chunk; + + return -ENOTSUP; +} + +static int wr_chunk( + const struct bt_mesh_blob_io *io, const struct bt_mesh_blob_xfer *xfer, + const struct bt_mesh_blob_block *block, + const struct bt_mesh_blob_chunk *chunk) +{ + (void)io; + (void)xfer; + (void)block; + (void)chunk; + + return -ENOTSUP; +} + +int bt_mesh_blob_io_flash_init(struct bt_mesh_blob_io_flash *flash, + uint8_t area_id, off_t offset) +{ + flash->area_id = area_id; + flash->offset = offset; + flash->io.open = io_open; + flash->io.close = io_close; + flash->io.block_start = block_start; + flash->io.block_end = NULL; + flash->io.rd = rd_chunk; + flash->io.wr = wr_chunk; + + return 0; +} diff --git a/libs/zblue/mesh/blob_srv.c b/libs/zblue/mesh/blob_srv.c new file mode 100644 index 0000000000000..ef4748ecc1c10 --- /dev/null +++ b/libs/zblue/mesh/blob_srv.c @@ -0,0 +1,1240 @@ +/**************************************************************************** + * libs/zblue/mesh/blob_srv.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include "net.h" +#include "access.h" +#include "transport.h" +#include "lpn.h" +#include "blob.h" + +#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_blob_srv); + +#define MTU_SIZE_MAX (BT_MESH_RX_SDU_MAX - BT_MESH_MIC_SHORT) + +/* The Receive BLOB Timeout Timer */ +#define SERVER_TIMEOUT_SECS(srv) (10 * (1 + (srv)->state.timeout_base)) +/* The initial timer value used by an instance of the Pull BLOB State machine + * - T_BPI + */ +#define REPORT_TIMER_TIMEOUT SEC2TICK(CONFIG_BT_MESH_BLOB_REPORT_TIMEOUT) + +BUILD_ASSERT(BLOB_BLOCK_SIZE_LOG_MIN <= BLOB_BLOCK_SIZE_LOG_MAX, + "The must be at least one number between the min and " + "max block size that is the power of two."); + +BUILD_ASSERT((BLOB_XFER_STATUS_MSG_MAXLEN + + BT_MESH_MODEL_OP_LEN(BT_MESH_BLOB_OP_XFER_STATUS) + + BT_MESH_MIC_SHORT) <= BT_MESH_TX_SDU_MAX, + "The BLOB Transfer Status message does not fit into the " + "maximum outgoing SDU size."); + +BUILD_ASSERT((BLOB_BLOCK_REPORT_STATUS_MSG_MAXLEN + + BT_MESH_MODEL_OP_LEN(BT_MESH_BLOB_OP_BLOCK_REPORT) + + BT_MESH_MIC_SHORT) <= BT_MESH_TX_SDU_MAX, + "The BLOB Partial Block Report message does not fit into " + "the maximum outgoing SDU size."); + +BUILD_ASSERT((BLOB_BLOCK_STATUS_MSG_MAXLEN + + BT_MESH_MODEL_OP_LEN(BT_MESH_BLOB_OP_BLOCK_STATUS) + + BT_MESH_MIC_SHORT) <= BT_MESH_TX_SDU_MAX, + "The BLOB Block Status message does not fit into the " + "maximum outgoing SDU size."); + +static void cancel(struct bt_mesh_blob_srv *srv); +static void suspend(struct bt_mesh_blob_srv *srv); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static inline uint32_t block_count_get(const struct bt_mesh_blob_srv *srv) +{ + return DIV_ROUND_UP(srv->state.xfer.size, + (1U << srv->state.xfer.block_size_log)); +} + +static inline uint32_t max_chunk_size(const struct bt_mesh_blob_srv *srv) +{ + return MIN((srv->state.mtu_size - 2 - + BT_MESH_MODEL_OP_LEN(BT_MESH_BLOB_OP_CHUNK)), + BLOB_RX_CHUNK_SIZE); +} + +static inline uint32_t max_chunk_count(const struct bt_mesh_blob_srv *srv) +{ + return MIN(8 * (srv->state.mtu_size - 6), + CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX); +} + +static inline uint32_t missing_chunks(const struct bt_mesh_blob_block *block) +{ + int i; + uint32_t count = 0; + + for (i = 0; i < ARRAY_SIZE(block->missing); ++i) + { + count += POPCOUNT(block->missing[i]); + } + + return count; +} + +static void store_state(const struct bt_mesh_blob_srv *srv) +{ + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return; + } + + /* Convert bit count to byte count: */ + + uint32_t block_len = DIV_ROUND_UP(block_count_get(srv), 8); + + bt_mesh_model_data_store(srv->mod, false, NULL, &srv->state, + offsetof(struct bt_mesh_blob_srv_state, + blocks) + block_len); +} + +static void erase_state(struct bt_mesh_blob_srv *srv) +{ + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return; + } + + bt_mesh_model_data_store(srv->mod, false, NULL, NULL, 0); +} + +static int io_open(struct bt_mesh_blob_srv *srv) +{ + if (!srv->io->open) + { + return 0; + } + + return srv->io->open(srv->io, &srv->state.xfer, BT_MESH_BLOB_WRITE); +} + +static void io_close(struct bt_mesh_blob_srv *srv) +{ + if (!srv->io->close) + { + return; + } + + srv->io->close(srv->io, &srv->state.xfer); +} + +static void reset_timer(struct bt_mesh_blob_srv *srv) +{ + uint32_t timeout_secs = + srv->state.xfer.mode == BT_MESH_BLOB_XFER_MODE_PULL + ? MAX(SERVER_TIMEOUT_SECS(srv), + CONFIG_BT_MESH_BLOB_REPORT_TIMEOUT + 1) + : SERVER_TIMEOUT_SECS(srv); + + k_work_reschedule(&srv->rx_timeout, SEC2TICK(timeout_secs)); +} + +static void buf_chunk_index_add(struct net_buf_simple *buf, uint16_t chunk) +{ + /* utf-8 encoded: */ + + if (chunk < 0x80) + { + net_buf_simple_add_u8(buf, chunk); + } + else if (chunk < 0x8000) + { + net_buf_simple_add_u8(buf, 0xc0 | chunk >> 6); + net_buf_simple_add_u8(buf, 0x80 | (chunk & BIT_MASK(6))); + } + else + { + net_buf_simple_add_u8(buf, 0xe0 | chunk >> 12); + net_buf_simple_add_u8(buf, 0x80 | ((chunk >> 6) & BIT_MASK(6))); + net_buf_simple_add_u8(buf, 0x80 | (chunk & BIT_MASK(6))); + } +} + +static int pull_req_max(const struct bt_mesh_blob_srv *srv) +{ + int count = CONFIG_BT_MESH_BLOB_SRV_PULL_REQ_COUNT; + +#if defined(CONFIG_BT_MESH_LOW_POWER) + /* No point in requesting more than the friend node can hold: */ + + if (bt_mesh_lpn_established()) + { + uint32_t segments_per_chunk = + DIV_ROUND_UP(BLOB_CHUNK_SDU_LEN(srv->state.xfer.chunk_size), + BT_MESH_APP_SEG_SDU_MAX); + + /* It is possible that the friend node cannot hold all segments per + * chunk. In this case, we should request at least 1 chunk. As + * requesting `0` would be invalid. + */ + + count = MAX(1, MIN(CONFIG_BT_MESH_BLOB_SRV_PULL_REQ_COUNT, + bt_mesh.lpn.queue_size / segments_per_chunk)); + } +#endif + + return MIN(count, missing_chunks(&srv->block)); +} + +static void report_sent(int err, void *cb_data) +{ + struct bt_mesh_blob_srv *srv = cb_data; + + LOG_DBG(""); + + if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER) && bt_mesh_lpn_established()) + { + bt_mesh_lpn_poll(); + } + + if (k_work_delayable_is_pending(&srv->rx_timeout)) + { + k_work_reschedule(&srv->pull.report, REPORT_TIMER_TIMEOUT); + } +} + +static void block_report(struct bt_mesh_blob_srv *srv) +{ + static const struct bt_mesh_send_cb report_cb = + { + .end = report_sent + }; + + struct bt_mesh_msg_ctx ctx = + { + .app_idx = srv->state.app_idx, + .send_ttl = srv->state.ttl, + .addr = srv->state.cli, + }; + + int count; + int i; + + LOG_DBG("rx BLOB Timeout Timer: %i", + k_work_delayable_is_pending(&srv->rx_timeout)); + + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_BLOB_OP_BLOCK_REPORT, + BLOB_BLOCK_REPORT_STATUS_MSG_MAXLEN); + bt_mesh_model_msg_init(&buf, BT_MESH_BLOB_OP_BLOCK_REPORT); + + count = pull_req_max(srv); + + for (i = 0; i < srv->block.chunk_count && count; ++i) + { + if (blob_chunk_missing_get(srv->block.missing, i)) + { + buf_chunk_index_add(&buf, i); + count--; + } + } + + (void)bt_mesh_model_send(srv->mod, &ctx, &buf, &report_cb, srv); +} + +static void phase_set(struct bt_mesh_blob_srv *srv, + enum bt_mesh_blob_xfer_phase phase) +{ + srv->phase = phase; + LOG_DBG("Phase: %u", phase); +} + +static void cancel(struct bt_mesh_blob_srv *srv) +{ + /* TODO: Could this state be preserved instead somehow? Wiping the + * entire transfer state is a bit overkill + */ + + phase_set(srv, BT_MESH_BLOB_XFER_PHASE_INACTIVE); + srv->state.xfer.mode = BT_MESH_BLOB_XFER_MODE_NONE; + srv->state.ttl = BT_MESH_TTL_DEFAULT; + srv->block.number = 0xffff; + memset(srv->block.missing, 0, sizeof(srv->block.missing)); + srv->state.xfer.chunk_size = 0xffff; + k_work_cancel_delayable(&srv->rx_timeout); + k_work_cancel_delayable(&srv->pull.report); + io_close(srv); + erase_state(srv); + + if (srv->cb && srv->cb->end) + { + srv->cb->end(srv, srv->state.xfer.id, false); + } +} + +static void suspend(struct bt_mesh_blob_srv *srv) +{ + LOG_DBG(""); + k_work_cancel_delayable(&srv->rx_timeout); + k_work_cancel_delayable(&srv->pull.report); + phase_set(srv, BT_MESH_BLOB_XFER_PHASE_SUSPENDED); + if (srv->cb && srv->cb->suspended) + { + srv->cb->suspended(srv); + } +} + +static void resume(struct bt_mesh_blob_srv *srv) +{ + LOG_DBG("Resuming"); + + phase_set(srv, BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK); + reset_timer(srv); +} + +static void end(struct bt_mesh_blob_srv *srv) +{ + phase_set(srv, BT_MESH_BLOB_XFER_PHASE_COMPLETE); + k_work_cancel_delayable(&srv->rx_timeout); + k_work_cancel_delayable(&srv->pull.report); + io_close(srv); + erase_state(srv); + + if (srv->cb && srv->cb->end) + { + srv->cb->end(srv, srv->state.xfer.id, true); + } +} + +static bool all_blocks_received(struct bt_mesh_blob_srv *srv) +{ + for (int i = 0; i < ARRAY_SIZE(srv->state.blocks); ++i) + { + if (srv->state.blocks[i]) + { + return false; + } + } + + return true; +} + +static bool pull_mode_xfer_complete(struct bt_mesh_blob_srv *srv) +{ + return srv->state.xfer.mode == BT_MESH_BLOB_XFER_MODE_PULL && + srv->phase == BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_CHUNK && + all_blocks_received(srv); +} + +static void timeout(struct k_work *work) +{ + struct bt_mesh_blob_srv *srv = CONTAINER_OF(work, struct bt_mesh_blob_srv, + rx_timeout.work); + + LOG_DBG(""); + + if (srv->phase == BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START) + { + cancel(srv); + } + else if (pull_mode_xfer_complete(srv)) + { + end(srv); + } + else + { + suspend(srv); + } +} + +static void report_timeout(struct k_work *work) +{ + struct bt_mesh_blob_srv *srv = CONTAINER_OF(work, struct bt_mesh_blob_srv, + pull.report.work); + + LOG_DBG(""); + + if (srv->phase != BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK && + srv->phase != BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_CHUNK) + { + return; + } + + block_report(srv); +} + +/* Message handling + */ + +static void xfer_status_rsp( + struct bt_mesh_blob_srv *srv, struct bt_mesh_msg_ctx *ctx, + enum bt_mesh_blob_status status) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, + BT_MESH_BLOB_OP_XFER_STATUS, + BLOB_XFER_STATUS_MSG_MAXLEN); + bt_mesh_model_msg_init(&buf, BT_MESH_BLOB_OP_XFER_STATUS); + + net_buf_simple_add_u8(&buf, + ((status & BIT_MASK(4)) | (srv->state.xfer.mode << 6))); + net_buf_simple_add_u8(&buf, srv->phase); + + if (srv->phase == BT_MESH_BLOB_XFER_PHASE_INACTIVE) + { + goto send; + } + + net_buf_simple_add_le64(&buf, srv->state.xfer.id); + + if (srv->phase == BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START) + { + goto send; + } + + net_buf_simple_add_le32(&buf, srv->state.xfer.size); + net_buf_simple_add_u8(&buf, srv->state.xfer.block_size_log); + net_buf_simple_add_le16(&buf, srv->state.mtu_size); + net_buf_simple_add_mem(&buf, srv->state.blocks, + DIV_ROUND_UP(block_count_get(srv), 8)); + +send: + ctx->send_ttl = srv->state.ttl; + (void)bt_mesh_model_send(srv->mod, ctx, &buf, NULL, NULL); +} + +static void block_status_rsp( + struct bt_mesh_blob_srv *srv, struct bt_mesh_msg_ctx *ctx, + enum bt_mesh_blob_status status) +{ + enum bt_mesh_blob_chunks_missing format; + uint32_t missing; + int i; + + BT_MESH_MODEL_BUF_DEFINE(buf, + BT_MESH_BLOB_OP_BLOCK_STATUS, + BLOB_BLOCK_STATUS_MSG_MAXLEN); + bt_mesh_model_msg_init(&buf, BT_MESH_BLOB_OP_BLOCK_STATUS); + + if (srv->phase == BT_MESH_BLOB_XFER_PHASE_INACTIVE || + srv->phase == BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START) + { + missing = srv->block.chunk_count; + } + else if (srv->phase == BT_MESH_BLOB_XFER_PHASE_COMPLETE) + { + missing = 0U; + } + else + { + missing = missing_chunks(&srv->block); + } + + if (srv->state.xfer.mode == BT_MESH_BLOB_XFER_MODE_PULL) + { + format = BT_MESH_BLOB_CHUNKS_MISSING_ENCODED; + } + else if (missing == srv->block.chunk_count) + { + format = BT_MESH_BLOB_CHUNKS_MISSING_ALL; + } + else if (missing == 0) + { + format = BT_MESH_BLOB_CHUNKS_MISSING_NONE; + } + else + { + format = BT_MESH_BLOB_CHUNKS_MISSING_SOME; + } + + LOG_DBG("Status: %u, missing: %u/%u", status, missing, + srv->block.chunk_count); + + net_buf_simple_add_u8(&buf, (status & BIT_MASK(4)) | (format << 6)); + net_buf_simple_add_le16(&buf, srv->block.number); + net_buf_simple_add_le16(&buf, srv->state.xfer.chunk_size); + + if (format == BT_MESH_BLOB_CHUNKS_MISSING_SOME) + { + net_buf_simple_add_mem(&buf, srv->block.missing, + DIV_ROUND_UP(srv->block.chunk_count, 8)); + + LOG_DBG("Bits: %s", bt_hex(srv->block.missing, + DIV_ROUND_UP(srv->block.chunk_count, 8))); + } + else if (format == BT_MESH_BLOB_CHUNKS_MISSING_ENCODED) + { + int count = pull_req_max(srv); + + for (i = 0; (i < srv->block.chunk_count) && count; ++i) + { + if (blob_chunk_missing_get(srv->block.missing, i)) + { + LOG_DBG("Missing %u", i); + buf_chunk_index_add(&buf, i); + count--; + } + } + } + + if (srv->phase != BT_MESH_BLOB_XFER_PHASE_INACTIVE) + { + ctx->send_ttl = srv->state.ttl; + } + + (void)bt_mesh_model_send(srv->mod, ctx, &buf, NULL, NULL); +} + +static int handle_xfer_get( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_blob_srv *srv = mod->rt->user_data; + + LOG_DBG(""); + + if (pull_mode_xfer_complete(srv)) + { + /* The client requested transfer. If we are in Pull mode and all blocks + * were received, we should change the Transfer state here to Complete + * so that the client receives the correct state. + */ + + end(srv); + } + + xfer_status_rsp(srv, ctx, BT_MESH_BLOB_SUCCESS); + + return 0; +} + +static int handle_xfer_start( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_blob_srv *srv = mod->rt->user_data; + enum bt_mesh_blob_status status; + enum bt_mesh_blob_xfer_mode mode; + uint64_t id; + size_t size; + uint8_t block_size_log; + uint32_t block_count; + uint16_t mtu_size; + int err; + + mode = (net_buf_simple_pull_u8(buf) >> 6); + id = net_buf_simple_pull_le64(buf); + size = net_buf_simple_pull_le32(buf); + block_size_log = net_buf_simple_pull_u8(buf); + mtu_size = net_buf_simple_pull_le16(buf); + + LOG_DBG("\n\tsize: %u block size: %u\n\tmtu_size: %u\n\tmode: %s", size, + (1U << block_size_log), + mtu_size, mode == BT_MESH_BLOB_XFER_MODE_PUSH ? "push" : "pull"); + + if (mode != BT_MESH_BLOB_XFER_MODE_PULL && + mode != BT_MESH_BLOB_XFER_MODE_PUSH) + { + LOG_WRN("Invalid mode 0x%x", mode); + return -EINVAL; + } + + if (srv->phase == BT_MESH_BLOB_XFER_PHASE_INACTIVE) + { + status = BT_MESH_BLOB_ERR_WRONG_PHASE; + LOG_WRN("Uninitialized"); + goto rsp; + } + + if (srv->state.xfer.id != id) + { + status = BT_MESH_BLOB_ERR_WRONG_BLOB_ID; + /* bt_hex uses static array for the resulting hex string. + * Not possible to use bt_hex in the same logging function twice. + */ + + LOG_WRN("Invalid ID: %s", bt_hex(&id, sizeof(uint64_t))); + LOG_WRN("Expected ID: %s", bt_hex(&srv->state.xfer.id, + sizeof(uint64_t))); + goto rsp; + } + + if (srv->phase != BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START) + { + if (srv->state.xfer.mode != mode || srv->state.xfer.size != size || + srv->state.xfer.block_size_log != block_size_log || + srv->state.mtu_size > mtu_size) + { + status = BT_MESH_BLOB_ERR_WRONG_PHASE; + LOG_WRN("Busy"); + goto rsp; + } + + if (srv->phase == BT_MESH_BLOB_XFER_PHASE_SUSPENDED) + { + resume(srv); + store_state(srv); + } + else + { + LOG_DBG("Duplicate"); + } + + status = BT_MESH_BLOB_SUCCESS; + goto rsp; + } + + if (size > CONFIG_BT_MESH_BLOB_SIZE_MAX) + { + LOG_WRN("Too large"); + status = BT_MESH_BLOB_ERR_BLOB_TOO_LARGE; + goto rsp; + } + + if (((1U << block_size_log) < CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MIN) || + ((1U << block_size_log) > CONFIG_BT_MESH_BLOB_BLOCK_SIZE_MAX)) + { + LOG_WRN("Invalid block size: %u", block_size_log); + status = BT_MESH_BLOB_ERR_INVALID_BLOCK_SIZE; + goto rsp; + } + + srv->state.cli = ctx->addr; + srv->state.app_idx = ctx->app_idx; + srv->state.mtu_size = MIN(mtu_size, MTU_SIZE_MAX); + srv->state.xfer.id = id; + srv->state.xfer.size = size; + srv->state.xfer.mode = mode; + srv->state.xfer.block_size_log = block_size_log; + srv->state.xfer.chunk_size = 0xffff; + srv->block.number = 0xffff; + + block_count = block_count_get(srv); + if (block_count > BT_MESH_BLOB_BLOCKS_MAX) + { + LOG_WRN("Invalid block count (%u)", block_count); + status = BT_MESH_BLOB_ERR_INVALID_PARAM; + cancel(srv); + goto rsp; + } + + memset(srv->state.blocks, 0, sizeof(srv->state.blocks)); + for (int i = 0; i < block_count; i++) + { + atomic_set_bit(srv->state.blocks, i); + } + + err = io_open(srv); + if (err) + { + LOG_ERR("Couldn't open stream (err: %d)", err); + status = BT_MESH_BLOB_ERR_INTERNAL; + cancel(srv); + goto rsp; + } + + if (srv->cb && srv->cb->start) + { + err = srv->cb->start(srv, ctx, &srv->state.xfer); + if (err) + { + LOG_ERR("Couldn't start transfer (err: %d)", err); + status = BT_MESH_BLOB_ERR_INTERNAL; + cancel(srv); + goto rsp; + } + } + + reset_timer(srv); + phase_set(srv, BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK); + store_state(srv); + status = BT_MESH_BLOB_SUCCESS; + +rsp: + xfer_status_rsp(srv, ctx, status); + + return 0; +} + +static int handle_xfer_cancel( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + enum bt_mesh_blob_status status = BT_MESH_BLOB_SUCCESS; + struct bt_mesh_blob_srv *srv = mod->rt->user_data; + uint64_t id; + + id = net_buf_simple_pull_le64(buf); + + LOG_DBG("%u", (uint32_t)id); + + if (srv->phase == BT_MESH_BLOB_XFER_PHASE_INACTIVE) + { + goto rsp; + } + + if (srv->state.xfer.id != id) + { + status = BT_MESH_BLOB_ERR_WRONG_BLOB_ID; + goto rsp; + } + + cancel(srv); + +rsp: + xfer_status_rsp(srv, ctx, status); + + return 0; +} + +static int handle_block_get( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + enum bt_mesh_blob_status status; + struct bt_mesh_blob_srv *srv = mod->rt->user_data; + + switch (srv->phase) + { + case BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK: + case BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_CHUNK: + case BT_MESH_BLOB_XFER_PHASE_COMPLETE: + status = BT_MESH_BLOB_SUCCESS; + break; + case BT_MESH_BLOB_XFER_PHASE_SUSPENDED: + status = BT_MESH_BLOB_ERR_INFO_UNAVAILABLE; + break; + case BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START: + case BT_MESH_BLOB_XFER_PHASE_INACTIVE: + status = BT_MESH_BLOB_ERR_WRONG_PHASE; + break; + default: + status = BT_MESH_BLOB_ERR_INTERNAL; + break; + } + + LOG_DBG(""); + + block_status_rsp(srv, ctx, status); + + return 0; +} + +static int handle_block_start( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_blob_srv *srv = mod->rt->user_data; + enum bt_mesh_blob_status status; + uint16_t block_number; + uint16_t chunk_size; + int err; + + block_number = net_buf_simple_pull_le16(buf); + chunk_size = net_buf_simple_pull_le16(buf); + + if (srv->phase == BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START || + srv->phase == BT_MESH_BLOB_XFER_PHASE_INACTIVE) + { + status = BT_MESH_BLOB_ERR_WRONG_PHASE; + goto rsp; + } + + reset_timer(srv); + + if (srv->phase == BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_CHUNK) + { + if (block_number != srv->block.number || + chunk_size != srv->state.xfer.chunk_size) + { + status = BT_MESH_BLOB_ERR_WRONG_PHASE; + } + else + { + status = BT_MESH_BLOB_SUCCESS; + } + + goto rsp; + } + + if (block_number >= block_count_get(srv)) + { + status = BT_MESH_BLOB_ERR_INVALID_BLOCK_NUM; + goto rsp; + } + + if (!chunk_size || chunk_size > max_chunk_size(srv) || + (DIV_ROUND_UP((1 << srv->state.xfer.block_size_log), + chunk_size) > max_chunk_count(srv))) + { + LOG_WRN("Invalid chunk size: (chunk size: %u, max: %u, block log: %u, " + "count: %u)", + chunk_size, + max_chunk_size(srv), + srv->state.xfer.block_size_log, + max_chunk_count(srv)); + status = BT_MESH_BLOB_ERR_INVALID_CHUNK_SIZE; + goto rsp; + } + + srv->block.size = + blob_block_size(srv->state.xfer.size, + srv->state.xfer.block_size_log, + block_number); + srv->block.number = block_number; + srv->block.chunk_count = DIV_ROUND_UP(srv->block.size, chunk_size); + srv->state.xfer.chunk_size = chunk_size; + srv->block.offset = block_number * (1UL << srv->state.xfer.block_size_log); + + if (srv->phase == BT_MESH_BLOB_XFER_PHASE_COMPLETE || + !atomic_test_bit(srv->state.blocks, block_number)) + { + memset(srv->block.missing, 0, sizeof(srv->block.missing)); + status = BT_MESH_BLOB_SUCCESS; + goto rsp; + } + + if (srv->phase == BT_MESH_BLOB_XFER_PHASE_SUSPENDED && srv->cb && + srv->cb->resume) + { + srv->cb->resume(srv); + } + + phase_set(srv, BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_CHUNK); + blob_chunk_missing_set_all(&srv->block); + + LOG_DBG("%u: (%u/%u)\n\tsize: %u\n\tchunk size: %u\n\tchunk count: %u", + srv->block.number, + srv->block.number + 1, block_count_get( + srv), srv->block.size, chunk_size, srv->block.chunk_count); + + if (srv->io->block_start) + { + err = srv->io->block_start(srv->io, &srv->state.xfer, &srv->block); + if (err) + { + cancel(srv); + status = BT_MESH_BLOB_ERR_INTERNAL; + goto rsp; + } + } + + if (srv->state.xfer.mode == BT_MESH_BLOB_XFER_MODE_PULL) + { + /* Wait for the client to send the first chunk */ + + k_work_reschedule(&srv->pull.report, REPORT_TIMER_TIMEOUT); + } + + status = BT_MESH_BLOB_SUCCESS; + +rsp: + block_status_rsp(srv, ctx, status); + + return 0; +} + +static int handle_chunk( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_blob_srv *srv = mod->rt->user_data; + struct bt_mesh_blob_chunk chunk; + size_t expected_size = 0; + uint16_t idx; + int err; + + idx = net_buf_simple_pull_le16(buf); + chunk.size = buf->len; + chunk.data = net_buf_simple_pull_mem(buf, chunk.size); + chunk.offset = idx * srv->state.xfer.chunk_size; + + if (srv->phase != BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_CHUNK || + idx >= srv->block.chunk_count) + { + LOG_ERR("Invalid phase or index (%u %u)", srv->phase, idx); + return -EINVAL; + } + + if (idx == srv->block.chunk_count - 1) + { + expected_size = srv->block.size % srv->state.xfer.chunk_size; + } + + if (expected_size == 0) + { + expected_size = srv->state.xfer.chunk_size; + } + + if (chunk.size != expected_size) + { + LOG_ERR("Unexpected size: %u != %u", expected_size, chunk.size); + return -EINVAL; + } + + LOG_DBG("%u/%u (%u bytes)", idx + 1, srv->block.chunk_count, chunk.size); + + reset_timer(srv); + if (srv->state.xfer.mode == BT_MESH_BLOB_XFER_MODE_PULL) + { + k_work_reschedule(&srv->pull.report, REPORT_TIMER_TIMEOUT); + } + + if (!blob_chunk_missing_get(srv->block.missing, idx)) + { + LOG_DBG("Duplicate chunk %u", idx); + return -EALREADY; + } + + err = srv->io->wr(srv->io, &srv->state.xfer, &srv->block, &chunk); + if (err) + { + return err; + } + + blob_chunk_missing_set(srv->block.missing, idx, false); + if (missing_chunks(&srv->block)) + { + return 0; + } + + if (srv->state.xfer.mode == BT_MESH_BLOB_XFER_MODE_PULL) + { + block_report(srv); + } + + if (srv->io->block_end) + { + srv->io->block_end(srv->io, &srv->state.xfer, &srv->block); + } + + atomic_clear_bit(srv->state.blocks, srv->block.number); + + if (!all_blocks_received(srv)) + { + phase_set(srv, BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_BLOCK); + store_state(srv); + return 0; + } + + if (srv->state.xfer.mode == BT_MESH_BLOB_XFER_MODE_PULL) + { + /* By spec (section 5.2.4), the BLOB Server stops sending BLOB Partial + * Block Report messages "If the current block is the last block, then + * the server determines that the client knows the transfer is + * complete. For example, a higher-layer model may indicate that the + * client considers the transfer complete." + * + * We don't have any way for higher-layer model to indicate that the + * transfer is complete. Therefore we need to keep sending Partial + * Block Report messages until the client sends BLOB Transfer Get + * message or the Block Timer expires. + */ + + return 0; + } + + end(srv); + return 0; +} + +static int handle_info_get( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_blob_srv *srv = mod->rt->user_data; + + LOG_DBG(""); + + BT_MESH_MODEL_BUF_DEFINE(rsp, BT_MESH_BLOB_OP_INFO_STATUS, 15); + bt_mesh_model_msg_init(&rsp, BT_MESH_BLOB_OP_INFO_STATUS); + net_buf_simple_add_u8(&rsp, BLOB_BLOCK_SIZE_LOG_MIN); + net_buf_simple_add_u8(&rsp, BLOB_BLOCK_SIZE_LOG_MAX); + net_buf_simple_add_le16(&rsp, CONFIG_BT_MESH_BLOB_CHUNK_COUNT_MAX); + +#if defined(CONFIG_BT_MESH_LOW_POWER) + /* If friendship is established, then chunk size is according to friend's + * queue size. Chunk size = (Queue size * Segment size) - (Opcode (1) - + * Chunk Number (2) + * - 8 byte MIC (max)) + */ + + if (bt_mesh_lpn_established() && bt_mesh.lpn.queue_size > 0) + { + uint16_t chunk_size = (bt_mesh.lpn.queue_size * 12) - 11; + + chunk_size = MIN(chunk_size, BLOB_RX_CHUNK_SIZE); + net_buf_simple_add_le16(&rsp, chunk_size); + if (bt_mesh.lpn.queue_size <= 2) + { + LOG_WRN("FndQ too small %u", bt_mesh.lpn.queue_size); + } + } + else + { + net_buf_simple_add_le16(&rsp, BLOB_RX_CHUNK_SIZE); + } +#else + net_buf_simple_add_le16(&rsp, BLOB_RX_CHUNK_SIZE); +#endif + + net_buf_simple_add_le32(&rsp, CONFIG_BT_MESH_BLOB_SIZE_MAX); + net_buf_simple_add_le16(&rsp, MTU_SIZE_MAX); + net_buf_simple_add_u8(&rsp, BT_MESH_BLOB_XFER_MODE_ALL); + + if (srv->phase != BT_MESH_BLOB_XFER_PHASE_INACTIVE) + { + ctx->send_ttl = srv->state.ttl; + } + + (void)bt_mesh_model_send(srv->mod, ctx, &rsp, NULL, NULL); + + return 0; +} + +const struct bt_mesh_model_op _bt_mesh_blob_srv_op[] = +{ + { + BT_MESH_BLOB_OP_XFER_GET, + BT_MESH_LEN_EXACT(0), + handle_xfer_get + }, + + { + BT_MESH_BLOB_OP_XFER_START, + BT_MESH_LEN_EXACT(16), + handle_xfer_start + }, + + { + BT_MESH_BLOB_OP_XFER_CANCEL, + BT_MESH_LEN_EXACT(8), + handle_xfer_cancel + }, + + { + BT_MESH_BLOB_OP_BLOCK_GET, + BT_MESH_LEN_EXACT(0), + handle_block_get + }, + + { + BT_MESH_BLOB_OP_BLOCK_START, + BT_MESH_LEN_EXACT(4), + handle_block_start + }, + + { + BT_MESH_BLOB_OP_CHUNK, + BT_MESH_LEN_MIN(2), + handle_chunk + }, + + { + BT_MESH_BLOB_OP_INFO_GET, + BT_MESH_LEN_EXACT(0), + handle_info_get + }, + + BT_MESH_MODEL_OP_END, +}; + +static int blob_srv_init(const struct bt_mesh_model *mod) +{ + struct bt_mesh_blob_srv *srv = mod->rt->user_data; + + srv->mod = mod; + srv->state.ttl = BT_MESH_TTL_DEFAULT; + srv->block.number = 0xffff; + srv->state.xfer.chunk_size = 0xffff; + k_work_init_delayable(&srv->rx_timeout, timeout); + k_work_init_delayable(&srv->pull.report, report_timeout); + + return 0; +} + +static int blob_srv_settings_set( + const struct bt_mesh_model *mod, const char *name, size_t len_rd, + settings_read_cb read_cb, void *cb_arg) +{ + struct bt_mesh_blob_srv *srv = mod->rt->user_data; + ssize_t len; + + if (len_rd < offsetof(struct bt_mesh_blob_srv_state, blocks)) + { + return -EINVAL; + } + + len = read_cb(cb_arg, &srv->state, sizeof(srv->state)); + if (len < 0) + { + return len; + } + + srv->block.number = 0xffff; + srv->state.xfer.chunk_size = 0xffff; + + if (block_count_get(srv) > BT_MESH_BLOB_BLOCKS_MAX) + { + LOG_WRN("Loaded block count too high (%u, max: %u)", + block_count_get(srv), + BT_MESH_BLOB_BLOCKS_MAX); + return 0; + } + + /* If device restarted before it handled `XFER_START` server we restore + * state into BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START phase, so + * `XFER_START` can be accepted as it would before reboot + */ + + if (srv->state.cli == BT_MESH_ADDR_UNASSIGNED) + { + LOG_DBG("Transfer (id=%llu) waiting for start", srv->state.xfer.id); + phase_set(srv, BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START); + } + else + { + phase_set(srv, BT_MESH_BLOB_XFER_PHASE_SUSPENDED); + + LOG_DBG("Recovered transfer from 0x%04x (%llu)", srv->state.cli, + srv->state.xfer.id); + } + + return 0; +} + +static int blob_srv_start(const struct bt_mesh_model *mod) +{ + struct bt_mesh_blob_srv *srv = mod->rt->user_data; + int err = -ENOTSUP; + + if (srv->phase == BT_MESH_BLOB_XFER_PHASE_INACTIVE) + { + return 0; + } + + if (srv->cb && srv->cb->recover) + { + srv->io = NULL; + err = srv->cb->recover(srv, &srv->state.xfer, &srv->io); + if (!err && srv->io) + { + err = io_open(srv); + } + } + + if (err || !srv->io) + { + LOG_WRN("Abandoning transfer."); + phase_set(srv, BT_MESH_BLOB_XFER_PHASE_INACTIVE); + srv->state.xfer.mode = BT_MESH_BLOB_XFER_MODE_NONE; + srv->state.ttl = BT_MESH_TTL_DEFAULT; + erase_state(srv); + } + + return 0; +} + +static void blob_srv_reset(const struct bt_mesh_model *mod) +{ + struct bt_mesh_blob_srv *srv = mod->rt->user_data; + + phase_set(srv, BT_MESH_BLOB_XFER_PHASE_INACTIVE); + srv->state.xfer.mode = BT_MESH_BLOB_XFER_MODE_NONE; + k_work_cancel_delayable(&srv->rx_timeout); + k_work_cancel_delayable(&srv->pull.report); + erase_state(srv); +} + +const struct bt_mesh_model_cb _bt_mesh_blob_srv_cb = +{ + .init = blob_srv_init, + .settings_set = blob_srv_settings_set, + .start = blob_srv_start, + .reset = blob_srv_reset, +}; + +int bt_mesh_blob_srv_recv(struct bt_mesh_blob_srv *srv, + uint64_t id, + const struct bt_mesh_blob_io *io, + uint8_t ttl, + uint16_t timeout_base) +{ + if (bt_mesh_blob_srv_is_busy(srv)) + { + return -EBUSY; + } + + if (!io || !io->wr) + { + return -EINVAL; + } + + srv->state.xfer.id = id; + srv->state.ttl = ttl; + srv->state.timeout_base = timeout_base; + srv->io = io; + srv->block.number = 0xffff; + srv->state.xfer.chunk_size = 0xffff; + phase_set(srv, BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START); + store_state(srv); + + return 0; +} + +int bt_mesh_blob_srv_cancel(struct bt_mesh_blob_srv *srv) +{ + if (!bt_mesh_blob_srv_is_busy(srv)) + { + return -EALREADY; + } + + cancel(srv); + + return 0; +} + +bool bt_mesh_blob_srv_is_busy(const struct bt_mesh_blob_srv *srv) +{ + return srv->phase != BT_MESH_BLOB_XFER_PHASE_INACTIVE && + srv->phase != BT_MESH_BLOB_XFER_PHASE_SUSPENDED && + srv->phase != BT_MESH_BLOB_XFER_PHASE_COMPLETE; +} + +uint8_t bt_mesh_blob_srv_progress(const struct bt_mesh_blob_srv *srv) +{ + uint32_t total; + uint32_t received; + + if (srv->phase == BT_MESH_BLOB_XFER_PHASE_INACTIVE || + srv->phase == BT_MESH_BLOB_XFER_PHASE_WAITING_FOR_START) + { + return 0; + } + + total = block_count_get(srv); + + received = 0; + for (int i = 0; i < total; ++i) + { + if (!atomic_test_bit(srv->state.blocks, i)) + { + received++; + } + } + + return (100U * received) / total; +} diff --git a/libs/zblue/mesh/brg_cfg.c b/libs/zblue/mesh/brg_cfg.c new file mode 100644 index 0000000000000..0576b108aeae9 --- /dev/null +++ b/libs/zblue/mesh/brg_cfg.c @@ -0,0 +1,497 @@ +/**************************************************************************** + * libs/zblue/mesh/brg_cfg.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Implementation for states of Subnet Bridge feature in Bluetooth Mesh + * Protocol v1.1 specification + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include "keys.h" +#include "net.h" +#include "settings.h" +#include "brg_cfg.h" +#include "foundation.h" + +#define LOG_LEVEL CONFIG_BT_MESH_BRG_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_brg_cfg); + +/* Bridging table state and counter */ + +static struct bt_mesh_brg_cfg_row + brg_tbl[CONFIG_BT_MESH_BRG_TABLE_ITEMS_MAX]; +static uint32_t bt_mesh_brg_cfg_row_cnt; + +/* Bridging enabled state */ + +static bool brg_enabled; + +enum +{ + STATE_UPDATED, + TABLE_UPDATED, + BRG_CFG_FLAGS_COUNT, +}; +static atomic_define(brg_cfg_flags, BRG_CFG_FLAGS_COUNT); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/* Compact the bridge table for all removed entries, input is first removed + * entry + */ + +static void brg_tbl_compact(int j) +{ + for (int k = j; k < bt_mesh_brg_cfg_row_cnt; k++) + { + if (brg_tbl[k].direction != 0) + { + brg_tbl[j] = brg_tbl[k]; + j++; + } + } + + memset(&brg_tbl[j], 0, sizeof(brg_tbl[j]) * (bt_mesh_brg_cfg_row_cnt - j)); + bt_mesh_brg_cfg_row_cnt = j; +} + +/* Set function for initializing bridging enable state from value stored in + * settings. + */ + +static int brg_en_set(const char *name, + size_t len_rd, settings_read_cb read_cb, void *cb_arg) +{ + int err; + + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return 0; + } + + if (len_rd == 0) + { + brg_enabled = false; + LOG_DBG("Cleared bridge enable state"); + return 0; + } + + err = bt_mesh_settings_set(read_cb, cb_arg, &brg_enabled, + sizeof(brg_enabled)); + if (err) + { + LOG_ERR("Failed to set bridge enable state"); + return err; + } + + LOG_DBG("Restored bridge enable state"); + + return 0; +} + +/* Define a setting for storing enable state */ + +BT_MESH_SETTINGS_DEFINE(brg_en, "brg_en", brg_en_set); + +/* Set function for initializing bridging table rows from values stored in + * settings. + */ + +static int brg_tbl_set(const char *name, + size_t len_rd, settings_read_cb read_cb, void *cb_arg) +{ + ssize_t len; + + if (len_rd == 0) + { + memset(brg_tbl, 0, sizeof(brg_tbl)); + bt_mesh_brg_cfg_row_cnt = 0; + LOG_DBG("Cleared bridging table entries"); + return 0; + } + + if (len_rd % sizeof(brg_tbl[0])) + { + LOG_ERR("Invalid data size"); + return -EINVAL; + } + + if (len_rd > sizeof(brg_tbl)) + { + LOG_ERR("Too many entries to fit in bridging table"); + return -ENOMEM; + } + + len = read_cb(cb_arg, brg_tbl, sizeof(brg_tbl)); + if (len < 0 || len % sizeof(brg_tbl[0])) + { + LOG_ERR("Failed to read bridging table entries (err %zd)", len); + return len < 0 ? len : -EINVAL; + } + + bt_mesh_brg_cfg_row_cnt = len / sizeof(brg_tbl[0]); + LOG_DBG("Restored %d entries in bridging table", bt_mesh_brg_cfg_row_cnt); + + return 0; +} + +/* Define a setting for storing briging table rows */ + +BT_MESH_SETTINGS_DEFINE(brg_tbl, "brg_tbl", brg_tbl_set); + +bool bt_mesh_brg_cfg_enable_get(void) +{ + return brg_enabled; +} + +int bt_mesh_brg_cfg_enable_set(bool enable) +{ + if (brg_enabled == enable) + { + return 0; + } + + brg_enabled = enable; + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + atomic_set_bit(brg_cfg_flags, STATE_UPDATED); + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_BRG_PENDING); + } + + return 0; +} + +void bt_mesh_brg_cfg_pending_store(void) +{ + char *path_en = "bt/mesh/brg_en"; + char *path_tbl = "bt/mesh/brg_tbl"; + int err; + + if (atomic_test_and_clear_bit(brg_cfg_flags, STATE_UPDATED)) + { + if (brg_enabled) + { + err = settings_save_one(path_en, &brg_enabled, + sizeof(brg_enabled)); + } + else + { + err = settings_delete(path_en); + } + + if (err) + { + LOG_ERR("Failed to store %s value", path_en); + } + } + + if (atomic_test_and_clear_bit(brg_cfg_flags, TABLE_UPDATED)) + { + if (bt_mesh_brg_cfg_row_cnt) + { + err = settings_save_one(path_tbl, &brg_tbl, + bt_mesh_brg_cfg_row_cnt * sizeof(brg_tbl[0])); + } + else + { + err = settings_delete(path_tbl); + } + + if (err) + { + LOG_ERR("Failed to store %s value", path_tbl); + } + } +} + +/* Remove the entry from the bridging table that corresponds with the NetKey + * Index of the removed subnet. + */ + +static void brg_tbl_netkey_removed_evt(struct bt_mesh_subnet *sub, + enum bt_mesh_key_evt evt) +{ + int first_removed = -1; + + if (evt != BT_MESH_KEY_DELETED) + { + return; + } + + for (int i = 0; i < bt_mesh_brg_cfg_row_cnt; i++) + { + if (brg_tbl[i].net_idx1 == sub->net_idx || + brg_tbl[i].net_idx2 == sub->net_idx) + { + /* Setting direction to 0, entry will be cleared in + * brg_tbl_compact. + */ + + brg_tbl[i].direction = 0; + if (first_removed == -1) + { + first_removed = i; + } + } + } + + if (first_removed != -1) + { + /* Compact when all rows have been deleted. */ + + brg_tbl_compact(first_removed); + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + atomic_set_bit(brg_cfg_flags, TABLE_UPDATED); + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_BRG_PENDING); + } + } +} + +/* Add event hook for key deletion event */ + +BT_MESH_SUBNET_CB_DEFINE(sbr) = +{ + .evt_handler = brg_tbl_netkey_removed_evt, +}; + +int bt_mesh_brg_cfg_tbl_reset(void) +{ + int err = 0; + + brg_enabled = false; + bt_mesh_brg_cfg_row_cnt = 0; + memset(brg_tbl, 0, sizeof(brg_tbl)); + atomic_set(brg_cfg_flags, 0); + + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return 0; + } + + err = settings_delete("bt/mesh/brg_en"); + + if (err) + { + return err; + } + + err = settings_delete("bt/mesh/brg_tbl"); + return err; +} + +int bt_mesh_brg_cfg_tbl_get(const struct bt_mesh_brg_cfg_row **rows) +{ + *rows = brg_tbl; + return bt_mesh_brg_cfg_row_cnt; +} + +static bool netkey_check(uint16_t net_idx1, uint16_t net_idx2) +{ + return bt_mesh_subnet_get(net_idx1) && bt_mesh_subnet_get(net_idx2); +} + +int bt_mesh_brg_cfg_tbl_add( + uint8_t direction, uint16_t net_idx1, uint16_t net_idx2, uint16_t addr1, + uint16_t addr2, uint8_t *status) +{ + /* Sanity checks */ + + if (!BT_MESH_ADDR_IS_UNICAST(addr1) || net_idx1 == net_idx2 || + addr1 == addr2 || + net_idx1 > BT_MESH_BRG_CFG_KEY_INDEX_MAX + || net_idx2 > BT_MESH_BRG_CFG_KEY_INDEX_MAX) + { + return -EINVAL; + } + + if (direction != BT_MESH_BRG_CFG_DIR_ONEWAY && + direction != BT_MESH_BRG_CFG_DIR_TWOWAY) + { + return -EINVAL; + } + + if ((direction == BT_MESH_BRG_CFG_DIR_ONEWAY && + (addr2 == BT_MESH_ADDR_UNASSIGNED || + addr2 == BT_MESH_ADDR_ALL_NODES)) || + (direction == BT_MESH_BRG_CFG_DIR_TWOWAY && + !BT_MESH_ADDR_IS_UNICAST(addr2))) + { + return -EINVAL; + } + + if (!netkey_check(net_idx1, net_idx2)) + { + *status = STATUS_INVALID_NETKEY; + return 0; + } + + /* Check if entry already exists, if yes, then, update the direction field + * and it is a success. "If a Bridging Table state entry corresponding to + * the received message exists, the element shall set the Directions field + * in the entry to the value of the Directions field in the received + * message." + */ + + for (int i = 0; i < bt_mesh_brg_cfg_row_cnt; i++) + { + if (brg_tbl[i].net_idx1 == net_idx1 && + brg_tbl[i].net_idx2 == net_idx2 && + brg_tbl[i].addr1 == addr1 && brg_tbl[i].addr2 == addr2) + { + brg_tbl[i].direction = direction; + goto store; + } + } + + /* Empty element, is the current table row counter */ + + if (bt_mesh_brg_cfg_row_cnt >= CONFIG_BT_MESH_BRG_TABLE_ITEMS_MAX) + { + *status = STATUS_INSUFF_RESOURCES; + return 0; + } + + /* Update the row */ + + brg_tbl[bt_mesh_brg_cfg_row_cnt].direction = direction; + brg_tbl[bt_mesh_brg_cfg_row_cnt].net_idx1 = net_idx1; + brg_tbl[bt_mesh_brg_cfg_row_cnt].net_idx2 = net_idx2; + brg_tbl[bt_mesh_brg_cfg_row_cnt].addr1 = addr1; + brg_tbl[bt_mesh_brg_cfg_row_cnt].addr2 = addr2; + bt_mesh_brg_cfg_row_cnt++; + +store: + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + atomic_set_bit(brg_cfg_flags, TABLE_UPDATED); + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_BRG_PENDING); + } + + *status = STATUS_SUCCESS; + return 0; +} + +void bt_mesh_brg_cfg_tbl_foreach_subnet( + uint16_t src, uint16_t dst, uint16_t net_idx, bt_mesh_brg_cfg_cb_t cb, + void *user_data) +{ + for (int i = 0; i < bt_mesh_brg_cfg_row_cnt; i++) + { + if ((brg_tbl[i].direction == BT_MESH_BRG_CFG_DIR_ONEWAY || + brg_tbl[i].direction == BT_MESH_BRG_CFG_DIR_TWOWAY) && + brg_tbl[i].net_idx1 == net_idx && brg_tbl[i].addr1 == src && + brg_tbl[i].addr2 == dst) + { + cb(brg_tbl[i].net_idx2, user_data); + } + else if ((brg_tbl[i].direction == BT_MESH_BRG_CFG_DIR_TWOWAY && + brg_tbl[i].net_idx2 == net_idx && brg_tbl[i].addr2 == src && + brg_tbl[i].addr1 == dst)) + { + cb(brg_tbl[i].net_idx1, user_data); + } + } +} + +int bt_mesh_brg_cfg_tbl_remove( + uint16_t net_idx1, uint16_t net_idx2, uint16_t addr1, uint16_t addr2, + uint8_t *status) +{ + int first_removed = -1; + + /* Sanity checks */ + + if ((!BT_MESH_ADDR_IS_UNICAST(addr1) + && addr1 != BT_MESH_ADDR_UNASSIGNED) || + (BT_MESH_ADDR_IS_UNICAST(addr1) && addr1 == addr2) || + addr2 == BT_MESH_ADDR_ALL_NODES) + { + return -EINVAL; + } + + if (net_idx1 == net_idx2 || net_idx1 > BT_MESH_BRG_CFG_KEY_INDEX_MAX || + net_idx2 > BT_MESH_BRG_CFG_KEY_INDEX_MAX) + { + return -EINVAL; + } + + if (!netkey_check(net_idx1, net_idx2)) + { + *status = STATUS_INVALID_NETKEY; + return 0; + } + + /* Iterate over items and set matching row to 0, if nothing exist, or + * nothing matches, then it is success (similar to add) + */ + + if (bt_mesh_brg_cfg_row_cnt == 0) + { + *status = STATUS_SUCCESS; + return 0; + } + + for (int i = 0; i < bt_mesh_brg_cfg_row_cnt; i++) + { + /* Match according to remove behavior in Section 4.4.9.2.2 of + * MshPRT_v1.1 + */ + + if (!(brg_tbl[i].net_idx1 == net_idx1 && + brg_tbl[i].net_idx2 == net_idx2)) + { + continue; + } + + if ((brg_tbl[i].addr1 == addr1 && brg_tbl[i].addr2 == addr2) || + (addr2 == BT_MESH_ADDR_UNASSIGNED && brg_tbl[i].addr1 == addr1) || + (addr1 == BT_MESH_ADDR_UNASSIGNED && brg_tbl[i].addr2 == addr2)) + { + /* Setting direction to 0, entry will be cleared in + * brg_tbl_compact. + */ + + brg_tbl[i].direction = 0; + if (first_removed == -1) + { + first_removed = i; + } + } + } + + if (first_removed != -1) + { + /* Compact when all rows have been deleted. */ + + brg_tbl_compact(first_removed); + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + atomic_set_bit(brg_cfg_flags, TABLE_UPDATED); + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_BRG_PENDING); + } + } + + *status = STATUS_SUCCESS; + return 0; +} diff --git a/libs/zblue/mesh/brg_cfg.h b/libs/zblue/mesh/brg_cfg.h new file mode 100644 index 0000000000000..b96b8b38ad2fc --- /dev/null +++ b/libs/zblue/mesh/brg_cfg.h @@ -0,0 +1,89 @@ +/**************************************************************************** + * libs/zblue/mesh/brg_cfg.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_SUBSYS_BLUETOOTH_MESH_BRG_CFG_H_ +#define ZEPHYR_SUBSYS_BLUETOOTH_MESH_BRG_CFG_H_ + +/** These are internal APIs. They do not sanitize input params. */ + +#define BT_MESH_BRG_CFG_KEY_INDEX_MAX 0x0FFF + +#define BT_MESH_BRG_CFG_NKEY_PRHB_FLT_MASK 0x000C + +#define BT_MESH_BRG_CFG_NETIDX_NOMATCH 0xFFFF + +/* One row of the bridging table */ + +struct bt_mesh_brg_cfg_row +{ + /* Direction of the entry in the bridging table + * 0 - no entry, 1 - bridge messages with src as addr1 and dst as addr2 2 - + * bridge messages with src as addr1 and dst as addr2 and vice-versa + */ + + uint32_t direction : 8; + uint32_t net_idx1 : 12; + uint32_t net_idx2 : 12; + uint16_t addr1; + uint16_t addr2; +}; + +bool bt_mesh_brg_cfg_enable_get(void); + +int bt_mesh_brg_cfg_enable_set(bool enable); + +void bt_mesh_brg_cfg_pending_store(void); + +int bt_mesh_brg_cfg_tbl_reset(void); + +int bt_mesh_brg_cfg_tbl_get(const struct bt_mesh_brg_cfg_row **rows); + +int bt_mesh_brg_cfg_tbl_add(uint8_t direction, + uint16_t net_idx1, + uint16_t net_idx2, + uint16_t addr1, + uint16_t addr2, + uint8_t *status); + +int bt_mesh_brg_cfg_tbl_remove(uint16_t net_idx1, + uint16_t net_idx2, + uint16_t addr1, + uint16_t addr2, + uint8_t *status); + +typedef void (*bt_mesh_brg_cfg_cb_t)(uint16_t new_netidx, void *user_data); + +/** + * @brief Iterate over the bridging table to find a matching entry for the + * given SRC, DST, and NetKey Index. + * + * This function iterates over the bridging table and checks if there is a + * match for the provided parameters. If a match is found, the callback + * function specified by the 'cb' parameter is invoked with the NetKey Index + * of each matching entry (there can be several). Relaying operation can then + * happen inside this callback. + * + * @param src The source address to match. + * @param dst The destination address to match. + * @param net_idx The NetKey Index to match. + * @param cb The callback function to be invoked for each matching + * entry. + * @param user_data User data to be passed to the callback function. + */ + +void bt_mesh_brg_cfg_tbl_foreach_subnet(uint16_t src, + uint16_t dst, + uint16_t net_idx, + bt_mesh_brg_cfg_cb_t cb, + void *user_data); + +#endif /* ZEPHYR_SUBSYS_BLUETOOTH_MESH_BRG_CFG_H_ */ diff --git a/libs/zblue/mesh/brg_cfg_cli.c b/libs/zblue/mesh/brg_cfg_cli.c new file mode 100644 index 0000000000000..89c59310dc45d --- /dev/null +++ b/libs/zblue/mesh/brg_cfg_cli.c @@ -0,0 +1,483 @@ +/**************************************************************************** + * libs/zblue/mesh/brg_cfg_cli.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include "access.h" +#include "foundation.h" +#include "msg.h" + +#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_brg_cfg_cli); + +static int32_t msg_timeout; + +static struct bt_mesh_brg_cfg_cli *cli; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int bridge_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + enum bt_mesh_brg_cfg_state status = + (enum bt_mesh_brg_cfg_state)net_buf_simple_pull_u8(buf); + enum bt_mesh_brg_cfg_state *rsp; + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_SUBNET_BRIDGE_STATUS, + ctx->addr, (void **)&rsp)) + { + *rsp = status; + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + + if (cli->cb && cli->cb->bridge_status) + { + cli->cb->bridge_status(cli, ctx->addr, status); + } + + return 0; +} + +static int table_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_brg_cfg_table_status table_status; + struct bt_mesh_brg_cfg_table_status *rsp; + + table_status.status = net_buf_simple_pull_u8(buf); + table_status.entry.directions = net_buf_simple_pull_u8(buf); + key_idx_unpack_pair(buf, &table_status.entry.net_idx1, + &table_status.entry.net_idx2); + table_status.entry.addr1 = net_buf_simple_pull_le16(buf); + table_status.entry.addr2 = net_buf_simple_pull_le16(buf); + + if (!(table_status.entry.addr1 == BT_MESH_ADDR_UNASSIGNED || + BT_MESH_ADDR_IS_UNICAST(table_status.entry.addr1))) + { + LOG_ERR("addr1 shall be a unicast address or unassigned."); + return -EINVAL; + } + else if (table_status.entry.addr2 == BT_MESH_ADDR_ALL_NODES) + { + LOG_ERR("addr2 shall not be the all-nodes fixed group address."); + return -EINVAL; + } + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_BRIDGING_TABLE_STATUS, + ctx->addr, (void **)&rsp)) + { + *rsp = table_status; + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + + if (cli->cb && cli->cb->table_status) + { + cli->cb->table_status(cli, ctx->addr, &table_status); + } + + return 0; +} + +static int subnets_list( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_brg_cfg_subnets_list subnets_list; + struct bt_mesh_brg_cfg_subnets_list *rsp; + uint16_t net_idx_filter; + + net_idx_filter = net_buf_simple_pull_le16(buf); + subnets_list.net_idx_filter.filter = net_idx_filter & BIT_MASK(2); + subnets_list.net_idx_filter.prohibited = + (net_idx_filter >> 2) & BIT_MASK(2); + subnets_list.net_idx_filter.net_idx = (net_idx_filter >> 4) & BIT_MASK(12); + subnets_list.start_idx = net_buf_simple_pull_u8(buf); + + if (buf->len && !(buf->len % 3)) + { + subnets_list.list = buf; + } + else + { + subnets_list.list = NULL; + } + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_BRIDGED_SUBNETS_LIST, + ctx->addr, (void **)&rsp)) + { + rsp->net_idx_filter = subnets_list.net_idx_filter; + rsp->start_idx = subnets_list.start_idx; + + if (rsp->list) + { + size_t to_copy; + + to_copy = MIN(net_buf_simple_tailroom(rsp->list), buf->len); + net_buf_simple_add_mem(rsp->list, buf->data, to_copy); + } + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + + if (cli->cb && cli->cb->subnets_list) + { + cli->cb->subnets_list(cli, ctx->addr, &subnets_list); + } + + return 0; +} + +static int table_list( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_brg_cfg_table_list table_list; + struct bt_mesh_brg_cfg_table_list *rsp; + + table_list.status = net_buf_simple_pull_u8(buf); + key_idx_unpack_pair(buf, &table_list.net_idx1, &table_list.net_idx2); + table_list.start_idx = net_buf_simple_pull_le16(buf); + + if ((table_list.status == STATUS_SUCCESS) && buf->len && !(buf->len % 5)) + { + table_list.list = buf; + } + else + { + table_list.list = NULL; + } + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_BRIDGING_TABLE_LIST, + ctx->addr, (void **)&rsp)) + { + rsp->status = table_list.status; + rsp->net_idx1 = table_list.net_idx1; + rsp->net_idx2 = table_list.net_idx2; + rsp->start_idx = table_list.start_idx; + + if (rsp->list) + { + size_t to_copy; + + to_copy = MIN(net_buf_simple_tailroom(rsp->list), + (buf->len / 5) * 5); + net_buf_simple_add_mem(rsp->list, buf->data, to_copy); + } + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + + if (cli->cb && cli->cb->table_list) + { + cli->cb->table_list(cli, ctx->addr, &table_list); + } + + return 0; +} + +static int table_size_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint16_t size = net_buf_simple_pull_le16(buf); + uint16_t *rsp; + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_BRIDGING_TABLE_SIZE_STATUS, + ctx->addr, + (void **)&rsp)) + { + *rsp = size; + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + + if (cli->cb && cli->cb->table_size_status) + { + cli->cb->table_size_status(cli, ctx->addr, size); + } + + return 0; +} + +const struct bt_mesh_model_op _bt_mesh_brg_cfg_cli_op[] = +{ + { + OP_SUBNET_BRIDGE_STATUS, + BT_MESH_LEN_EXACT(1), + bridge_status + }, + + { + OP_BRIDGING_TABLE_STATUS, + BT_MESH_LEN_EXACT(9), + table_status + }, + + { + OP_BRIDGED_SUBNETS_LIST, + BT_MESH_LEN_MIN(3), + subnets_list + }, + + { + OP_BRIDGING_TABLE_LIST, + BT_MESH_LEN_MIN(6), + table_list + }, + + { + OP_BRIDGING_TABLE_SIZE_STATUS, + BT_MESH_LEN_EXACT(2), + table_size_status + }, + + BT_MESH_MODEL_OP_END, +}; + +static int brg_cfg_cli_init(const struct bt_mesh_model *model) +{ + if (!bt_mesh_model_in_primary(model)) + { + LOG_ERR("Bridge Configuration Client only allowed in primary element"); + return -EINVAL; + } + + if (!model->rt->user_data) + { + LOG_ERR("No Bridge Configuration Client context provided"); + return -EINVAL; + } + + cli = model->rt->user_data; + cli->model = model; + msg_timeout = CONFIG_BT_MESH_BRG_CFG_CLI_TIMEOUT; + + model->keys[0] = BT_MESH_KEY_DEV_ANY; + model->rt->flags |= BT_MESH_MOD_DEVKEY_ONLY; + + bt_mesh_msg_ack_ctx_init(&cli->ack_ctx); + + return 0; +} + +const struct bt_mesh_model_cb _bt_mesh_brg_cfg_cli_cb = +{ + .init = brg_cfg_cli_init, +}; + +int bt_mesh_brg_cfg_cli_get(uint16_t net_idx, + uint16_t addr, + enum bt_mesh_brg_cfg_state *status) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_SUBNET_BRIDGE_GET, 0); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + const struct bt_mesh_msg_rsp_ctx rsp_ctx = + { + .ack = &cli->ack_ctx, + .op = OP_SUBNET_BRIDGE_STATUS, + .user_data = status, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_SUBNET_BRIDGE_GET); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, + !status ? NULL : &rsp_ctx); +} + +int bt_mesh_brg_cfg_cli_set( + uint16_t net_idx, uint16_t addr, enum bt_mesh_brg_cfg_state val, + enum bt_mesh_brg_cfg_state *status) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_SUBNET_BRIDGE_SET, 1); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + const struct bt_mesh_msg_rsp_ctx rsp_ctx = + { + .ack = &cli->ack_ctx, + .op = OP_SUBNET_BRIDGE_STATUS, + .user_data = status, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_SUBNET_BRIDGE_SET); + net_buf_simple_add_u8(&msg, (uint8_t)val); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, + !status ? NULL : &rsp_ctx); +} + +int bt_mesh_brg_cfg_cli_table_size_get(uint16_t net_idx, + uint16_t addr, uint16_t *size) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_BRIDGING_TABLE_SIZE_GET, 0); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + const struct bt_mesh_msg_rsp_ctx rsp_ctx = + { + .ack = &cli->ack_ctx, + .op = OP_BRIDGING_TABLE_SIZE_STATUS, + .user_data = size, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_BRIDGING_TABLE_SIZE_GET); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, + !size ? NULL : &rsp_ctx); +} + +int bt_mesh_brg_cfg_cli_table_add(uint16_t net_idx, uint16_t addr, + struct bt_mesh_brg_cfg_table_entry *entry, + struct bt_mesh_brg_cfg_table_status *rsp) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_BRIDGING_TABLE_ADD, 8); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + const struct bt_mesh_msg_rsp_ctx rsp_ctx = + { + .ack = &cli->ack_ctx, + .op = OP_BRIDGING_TABLE_STATUS, + .user_data = rsp, + .timeout = msg_timeout, + }; + + if (entry->addr1 == entry->addr2) + { + LOG_ERR("addr1 and addr2 shall have different values."); + return -EINVAL; + } + else if (!BT_MESH_ADDR_IS_UNICAST(entry->addr1)) + { + LOG_ERR("addr1 shall be a unicast address."); + return -EINVAL; + } + else if (entry->directions == 0x01 && + (entry->addr2 == BT_MESH_ADDR_UNASSIGNED || + entry->addr2 == BT_MESH_ADDR_ALL_NODES)) + { + LOG_ERR("For direction 0x01: addr2 shall not be unassigned or the " + "all-nodes fixed " + "group address."); + return -EINVAL; + } + else if (entry->directions == 0x02 && + !BT_MESH_ADDR_IS_UNICAST(entry->addr2)) + { + LOG_ERR("For direction 0x02: addr2 shall be a unicast address."); + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_BRIDGING_TABLE_ADD); + net_buf_simple_add_u8(&msg, entry->directions); + key_idx_pack_pair(&msg, entry->net_idx1, entry->net_idx2); + net_buf_simple_add_le16(&msg, entry->addr1); + net_buf_simple_add_le16(&msg, entry->addr2); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, + !rsp ? NULL : &rsp_ctx); +} + +int bt_mesh_brg_cfg_cli_table_remove( + uint16_t net_idx, uint16_t addr, uint16_t net_idx1, uint16_t net_idx2, + uint16_t addr1, uint16_t addr2, struct bt_mesh_brg_cfg_table_status *rsp) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_BRIDGING_TABLE_REMOVE, 7); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + const struct bt_mesh_msg_rsp_ctx rsp_ctx = + { + .ack = &cli->ack_ctx, + .op = OP_BRIDGING_TABLE_STATUS, + .user_data = rsp, + .timeout = msg_timeout, + }; + + if (!(addr1 == BT_MESH_ADDR_UNASSIGNED || BT_MESH_ADDR_IS_UNICAST(addr1))) + { + LOG_ERR("addr1 shall be a unicast address or unassigned."); + return -EINVAL; + } + else if (addr2 == BT_MESH_ADDR_ALL_NODES) + { + LOG_ERR("addr2 shall not be the all-nodes fixed group address."); + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_BRIDGING_TABLE_REMOVE); + key_idx_pack_pair(&msg, net_idx1, net_idx2); + net_buf_simple_add_le16(&msg, addr1); + net_buf_simple_add_le16(&msg, addr2); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, + !rsp ? NULL : &rsp_ctx); +} + +int bt_mesh_brg_cfg_cli_subnets_get(uint16_t net_idx, + uint16_t addr, + struct bt_mesh_brg_cfg_filter_netkey filter_net_idx, + uint8_t start_idx, + struct bt_mesh_brg_cfg_subnets_list *rsp) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_BRIDGED_SUBNETS_GET, 3); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + const struct bt_mesh_msg_rsp_ctx rsp_ctx = + { + .ack = &cli->ack_ctx, + .op = OP_BRIDGED_SUBNETS_LIST, + .user_data = rsp, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_BRIDGED_SUBNETS_GET); + net_buf_simple_add_le16(&msg, + (filter_net_idx.filter | filter_net_idx.net_idx << 4)); + net_buf_simple_add_u8(&msg, start_idx); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, + !rsp ? NULL : &rsp_ctx); +} + +int bt_mesh_brg_cfg_cli_table_get( + uint16_t net_idx, uint16_t addr, uint16_t net_idx1, uint16_t net_idx2, + uint16_t start_idx, struct bt_mesh_brg_cfg_table_list *rsp) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_BRIDGING_TABLE_GET, 5); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + const struct bt_mesh_msg_rsp_ctx rsp_ctx = + { + .ack = &cli->ack_ctx, + .op = OP_BRIDGING_TABLE_LIST, + .user_data = rsp, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_BRIDGING_TABLE_GET); + key_idx_pack_pair(&msg, net_idx1, net_idx2); + net_buf_simple_add_le16(&msg, start_idx); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, + !rsp ? NULL : &rsp_ctx); +} + +int32_t bt_mesh_brg_cfg_cli_timeout_get(void) +{ + return msg_timeout; +} + +void bt_mesh_brg_cfg_cli_timeout_set(int32_t timeout) +{ + msg_timeout = timeout; +} diff --git a/libs/zblue/mesh/brg_cfg_srv.c b/libs/zblue/mesh/brg_cfg_srv.c new file mode 100644 index 0000000000000..79af1bd260c86 --- /dev/null +++ b/libs/zblue/mesh/brg_cfg_srv.c @@ -0,0 +1,391 @@ +/**************************************************************************** + * libs/zblue/mesh/brg_cfg_srv.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include "access.h" +#include "brg_cfg.h" +#include "foundation.h" +#include "subnet.h" + +#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_brg_cfg_srv); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void bridge_status_send(const struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_SUBNET_BRIDGE_STATUS, 1); + + bt_mesh_model_msg_init(&msg, OP_SUBNET_BRIDGE_STATUS); + net_buf_simple_add_u8(&msg, bt_mesh_brg_cfg_enable_get() ? 1 : 0); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Brg Status send failed"); + } +} + +static int subnet_bridge_get( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + bridge_status_send(model, ctx); + + return 0; +} + +static int subnet_bridge_set( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint8_t enable = net_buf_simple_pull_u8(buf); + + if (enable > BT_MESH_BRG_CFG_ENABLED) + { + return -EINVAL; + } + + bt_mesh_brg_cfg_enable_set(enable); + bridge_status_send(model, ctx); + + return 0; +} + +static void bridging_table_status_send(const struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + uint8_t status, + struct bt_mesh_brg_cfg_table_entry *entry) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_BRIDGING_TABLE_STATUS, 9); + + bt_mesh_model_msg_init(&msg, OP_BRIDGING_TABLE_STATUS); + net_buf_simple_add_u8(&msg, status); + net_buf_simple_add_u8(&msg, entry->directions); + key_idx_pack_pair(&msg, entry->net_idx1, entry->net_idx2); + net_buf_simple_add_le16(&msg, entry->addr1); + net_buf_simple_add_le16(&msg, entry->addr2); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Brg Tbl Status send failed"); + } +} + +static bool netkey_check(uint16_t net_idx1, uint16_t net_idx2) +{ + return bt_mesh_subnet_get(net_idx1) && bt_mesh_subnet_get(net_idx2); +} + +static int bridging_table_add( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_brg_cfg_table_entry entry; + uint8_t status = STATUS_SUCCESS; + int err; + + entry.directions = net_buf_simple_pull_u8(buf); + key_idx_unpack_pair(buf, &entry.net_idx1, &entry.net_idx2); + entry.addr1 = net_buf_simple_pull_le16(buf); + entry.addr2 = net_buf_simple_pull_le16(buf); + + err = bt_mesh_brg_cfg_tbl_add(entry.directions, entry.net_idx1, + entry.net_idx2, entry.addr1, + entry.addr2, &status); + if (err) + { + return err; + } + + bridging_table_status_send(model, ctx, status, &entry); + + return 0; +} + +static int bridging_table_remove( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_brg_cfg_table_entry entry; + uint8_t status = STATUS_SUCCESS; + int err; + + entry.directions = 0; + key_idx_unpack_pair(buf, &entry.net_idx1, &entry.net_idx2); + entry.addr1 = net_buf_simple_pull_le16(buf); + entry.addr2 = net_buf_simple_pull_le16(buf); + + err = bt_mesh_brg_cfg_tbl_remove(entry.net_idx1, entry.net_idx2, + entry.addr1, + entry.addr2, + &status); + if (err) + { + return err; + } + + bridging_table_status_send(model, ctx, status, &entry); + + return 0; +} + +static int bridged_subnets_get( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_BRIDGED_SUBNETS_LIST, + BT_MESH_TX_SDU_MAX - BT_MESH_MODEL_OP_LEN( + OP_BRIDGED_SUBNETS_LIST)); + bt_mesh_model_msg_init(&msg, OP_BRIDGED_SUBNETS_LIST); + + const struct bt_mesh_brg_cfg_row *brg_tbl; + int rows = bt_mesh_brg_cfg_tbl_get(&brg_tbl); + int16_t net_idx_filter = net_buf_simple_pull_le16(buf); + + if (net_idx_filter & BT_MESH_BRG_CFG_NKEY_PRHB_FLT_MASK) + { + return -EINVAL; + } + + struct bt_mesh_brg_cfg_filter_netkey filter_net_idx; + + filter_net_idx.filter = net_idx_filter & BIT_MASK(2); + filter_net_idx.net_idx = (net_idx_filter >> 4) & BIT_MASK(12); + + uint8_t start_id = net_buf_simple_pull_u8(buf); + + net_buf_simple_add_le16(&msg, net_idx_filter); + net_buf_simple_add_u8(&msg, start_id); + + for (int i = 0; i < rows; i++) + { + uint16_t net_idx1 = brg_tbl[i].net_idx1; + uint16_t net_idx2 = brg_tbl[i].net_idx2; + bool is_first_instance; + + if (net_buf_simple_tailroom(&msg) < 3 + BT_MESH_MIC_SHORT) + { + break; + } + + is_first_instance = true; + for (int j = 0; j < i; j++) + { + if (net_idx1 == brg_tbl[j].net_idx1 && + net_idx2 == brg_tbl[j].net_idx2) + { + is_first_instance = false; + break; + } + } + + if (is_first_instance && + (filter_net_idx.filter == 0 || + (filter_net_idx.filter == 1 && + net_idx1 == filter_net_idx.net_idx) || + (filter_net_idx.filter == 2 && + net_idx2 == filter_net_idx.net_idx) || + (filter_net_idx.filter == 3 && + (net_idx1 == filter_net_idx.net_idx || + net_idx2 == filter_net_idx.net_idx)))) + { + if (start_id > 0) + { + start_id--; + } + else + { + key_idx_pack_pair(&msg, net_idx1, net_idx2); + } + } + } + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Brg Subnet List send failed"); + } + + return 0; +} + +static int bridging_table_get( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_BRIDGING_TABLE_LIST, + BT_MESH_TX_SDU_MAX - BT_MESH_MODEL_OP_LEN( + OP_BRIDGING_TABLE_LIST)); + uint8_t status = STATUS_SUCCESS; + uint16_t net_idx1; + uint16_t net_idx2; + + bt_mesh_model_msg_init(&msg, OP_BRIDGING_TABLE_LIST); + + key_idx_unpack_pair(buf, &net_idx1, &net_idx2); + + uint16_t start_id = net_buf_simple_pull_le16(buf); + + if (!netkey_check(net_idx1, net_idx2)) + { + status = STATUS_INVALID_NETKEY; + } + + net_buf_simple_add_u8(&msg, status); + key_idx_pack_pair(&msg, net_idx1, net_idx2); + net_buf_simple_add_le16(&msg, start_id); + + if (status != STATUS_SUCCESS) + { + goto tbl_get_respond; + } + + int cnt = 0; + const struct bt_mesh_brg_cfg_row *brg_tbl; + int rows = bt_mesh_brg_cfg_tbl_get(&brg_tbl); + + for (int i = 0; i < rows; i++) + { + if (brg_tbl[i].net_idx1 == net_idx1 && brg_tbl[i].net_idx2 == net_idx2) + { + if (cnt >= start_id) + { + if (net_buf_simple_tailroom(&msg) < 5 + BT_MESH_MIC_SHORT) + { + LOG_WRN("Bridging Table List message too large"); + break; + } + + net_buf_simple_add_le16(&msg, brg_tbl[i].addr1); + net_buf_simple_add_le16(&msg, brg_tbl[i].addr2); + net_buf_simple_add_u8(&msg, brg_tbl[i].direction); + } + + cnt++; + } + } + +tbl_get_respond: + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Brg Tbl List send failed"); + } + + return 0; +} + +static int bridging_table_size_get( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_BRIDGING_TABLE_SIZE_STATUS, 2); + bt_mesh_model_msg_init(&msg, OP_BRIDGING_TABLE_SIZE_STATUS); + + net_buf_simple_add_le16(&msg, CONFIG_BT_MESH_BRG_TABLE_ITEMS_MAX); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Brg Tbl Size Status send failed"); + } + + return 0; +} + +const struct bt_mesh_model_op _bt_mesh_brg_cfg_srv_op[] = +{ + { + OP_SUBNET_BRIDGE_GET, + BT_MESH_LEN_EXACT(0), + subnet_bridge_get + }, + + { + OP_SUBNET_BRIDGE_SET, + BT_MESH_LEN_EXACT(1), + subnet_bridge_set + }, + + { + OP_BRIDGING_TABLE_ADD, + BT_MESH_LEN_EXACT(8), + bridging_table_add + }, + + { + OP_BRIDGING_TABLE_REMOVE, + BT_MESH_LEN_EXACT(7), + bridging_table_remove + }, + + { + OP_BRIDGED_SUBNETS_GET, + BT_MESH_LEN_EXACT(3), + bridged_subnets_get + }, + + { + OP_BRIDGING_TABLE_GET, + BT_MESH_LEN_EXACT(5), + bridging_table_get + }, + + { + OP_BRIDGING_TABLE_SIZE_GET, + BT_MESH_LEN_EXACT(0), + bridging_table_size_get + }, + + BT_MESH_MODEL_OP_END, +}; + +static int brg_cfg_srv_init(const struct bt_mesh_model *model) +{ + const struct bt_mesh_model *config_srv = + bt_mesh_model_find(bt_mesh_model_elem(model), + BT_MESH_MODEL_ID_CFG_SRV); + + if (config_srv == NULL) + { + LOG_ERR("Not on primary element"); + return -EINVAL; + } + + /* Bridge Configuration Server model security is device key based and only + * the local device key is allowed to access this model. + */ + + model->keys[0] = BT_MESH_KEY_DEV_LOCAL; + model->rt->flags |= BT_MESH_MOD_DEVKEY_ONLY; + + bt_mesh_model_extend(model, config_srv); + + return 0; +} + +void brg_cfg_srv_reset(const struct bt_mesh_model *model) +{ + bt_mesh_brg_cfg_tbl_reset(); +} + +const struct bt_mesh_model_cb _bt_mesh_brg_cfg_srv_cb = +{ + .init = brg_cfg_srv_init, + .reset = brg_cfg_srv_reset, +}; diff --git a/libs/zblue/mesh/cdb.c b/libs/zblue/mesh/cdb.c new file mode 100644 index 0000000000000..5b9c0ff5317d2 --- /dev/null +++ b/libs/zblue/mesh/cdb.c @@ -0,0 +1,1693 @@ +/**************************************************************************** + * libs/zblue/mesh/cdb.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2019 Tobias Svehagen + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include + +#include + +#include "common/bt_str.h" + +#include "cdb.h" +#include "net.h" +#include "app_keys.h" +#include "rpl.h" +#include "settings.h" +#include "keys.h" + +#define LOG_LEVEL CONFIG_BT_MESH_CDB_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_cdb); + +/* Tracking of what storage changes are pending for App and Net Keys. We + * track this in a separate array here instead of within the respective + * bt_mesh_app_key and bt_mesh_subnet structs themselves, since once a key + * gets deleted its struct becomes invalid and may be reused for other keys. + */ + +struct key_update +{ + uint16_t key_idx : 12; /* AppKey or NetKey Index */ + uint16_t valid : 1; /* 1 if this entry is valid, 0 if not */ + uint16_t app_key : 1; /* 1 if this is an AppKey, 0 if a NetKey */ + uint16_t clear : 1; /* 1 if key needs clearing, 0 if storing */ +}; + +/* Tracking of what storage changes are pending for node settings. */ + +struct node_update +{ + uint16_t addr; + bool clear; +}; + +/* Node information for persistent storage. */ + +struct node_val +{ + uint16_t net_idx; + uint8_t num_elem; + uint8_t flags; +#define F_NODE_CONFIGURED 0x01 + uint8_t uuid[16]; + struct bt_mesh_key dev_key; +} __packed; + +/* NetKey storage information */ + +struct net_key_val +{ + uint8_t unused : 1; + uint8_t kr_phase : 7; + struct bt_mesh_key val[2]; +} __packed; + +/* AppKey information for persistent storage. */ + +struct app_key_val +{ + uint16_t net_idx; + bool updated; + struct bt_mesh_key val[2]; +} __packed; + +/* Network information for persistent storage. */ + +struct net_val +{ + struct __packed + { + uint32_t index; + bool update; + } iv; + uint16_t lowest_avail_addr; +} __packed; + +/* One more entry for the node's address update. */ + +static struct node_update + cdb_node_updates[CONFIG_BT_MESH_CDB_NODE_COUNT + 1]; +static struct key_update + cdb_key_updates[CONFIG_BT_MESH_CDB_SUBNET_COUNT + + CONFIG_BT_MESH_CDB_APP_KEY_COUNT]; + +struct bt_mesh_cdb bt_mesh_cdb = +{ + .nodes = + { + [0 ...(CONFIG_BT_MESH_CDB_NODE_COUNT - 1)] = + { + .addr = BT_MESH_ADDR_UNASSIGNED, + } + }, + .subnets = + { + [0 ...(CONFIG_BT_MESH_CDB_SUBNET_COUNT - 1)] = + { + .net_idx = BT_MESH_KEY_UNUSED, + } + }, + .app_keys = + { + [0 ...(CONFIG_BT_MESH_CDB_APP_KEY_COUNT - 1)] = + { + .app_idx = BT_MESH_KEY_UNUSED, + .net_idx = BT_MESH_KEY_UNUSED, + } + }, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/* Check if an address range from addr_start for addr_start + num_elem - 1 is + * free for use. When a conflict is found, next will be set to the next + * address available after the conflicting range and -EAGAIN will be + * returned. + */ + +static int addr_is_free(uint16_t addr_start, + uint8_t num_elem, uint16_t *next) +{ + uint16_t addr_end = addr_start + num_elem - 1; + uint16_t other_start; + uint16_t other_end; + int i; + + if (!BT_MESH_ADDR_IS_UNICAST(addr_start) || + !BT_MESH_ADDR_IS_UNICAST(addr_end) || num_elem == 0) + { + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(bt_mesh_cdb.nodes); i++) + { + struct bt_mesh_cdb_node *node = &bt_mesh_cdb.nodes[i]; + + if (node->addr == BT_MESH_ADDR_UNASSIGNED) + { + continue; + } + + other_start = node->addr; + other_end = other_start + node->num_elem - 1; + + if (!(addr_end < other_start || addr_start > other_end)) + { + if (next) + { + *next = other_end + 1; + } + + return -EAGAIN; + } + } + + return 0; +} + +/* Find the lowest possible starting address that can fit num_elem elements. + * If a free address range cannot be found, BT_MESH_ADDR_UNASSIGNED will be + * returned. Otherwise the first address in the range is returned. + * + * NOTE: This is quite an ineffective algorithm as it might need to look + * through the array of nodes N+2 times. A more effective algorithm could be + * used if the nodes were stored in a sorted list. + */ + +static uint16_t find_lowest_free_addr(uint8_t num_elem) +{ + uint16_t addr = bt_mesh_cdb.lowest_avail_addr; + uint16_t next; + int err; + int i; + + /* It takes a maximum of node count + 2 to find a free address if there + * is any. +1 for our own address and +1 for making sure that the address + * range is valid. + */ + + for (i = 0; i < ARRAY_SIZE(bt_mesh_cdb.nodes) + 2; ++i) + { + err = addr_is_free(addr, num_elem, &next); + if (err == 0) + { + break; + } + else if (err != -EAGAIN) + { + addr = BT_MESH_ADDR_UNASSIGNED; + break; + } + + addr = next; + } + + return addr; +} + +static int cdb_net_set(const char *name, + size_t len_rd, settings_read_cb read_cb, void *cb_arg) +{ + struct net_val net; + int err; + + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return 0; + } + + if (len_rd == 0) + { + LOG_DBG("val (null)"); + return 0; + } + + err = bt_mesh_settings_set(read_cb, cb_arg, &net, sizeof(net)); + if (err) + { + /* Try to recover previous version of the network settings without + * address. + */ + + err = bt_mesh_settings_set(read_cb, cb_arg, &net, sizeof(net.iv)); + if (err) + { + LOG_ERR("Failed to set \'cdb_net\'"); + return err; + } + + net.lowest_avail_addr = 1; + } + + bt_mesh_cdb.iv_index = net.iv.index; + + if (net.iv.update) + { + atomic_set_bit(bt_mesh_cdb.flags, BT_MESH_CDB_IVU_IN_PROGRESS); + } + + bt_mesh_cdb.lowest_avail_addr = net.lowest_avail_addr; + + atomic_set_bit(bt_mesh_cdb.flags, BT_MESH_CDB_VALID); + + return 0; +} + +static int cdb_node_set(const char *name, + size_t len_rd, + settings_read_cb read_cb, + void *cb_arg) +{ + struct bt_mesh_cdb_node *node; + struct node_val val; + struct bt_mesh_key tmp; + uint16_t addr; + int err; + + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return 0; + } + + if (!name) + { + LOG_ERR("Insufficient number of arguments"); + return -ENOENT; + } + + addr = strtol(name, NULL, 16); + + if (len_rd == 0) + { + LOG_DBG("val (null)"); + LOG_DBG("Deleting node 0x%04x", addr); + + node = bt_mesh_cdb_node_get(addr); + if (node) + { + bt_mesh_cdb_node_del(node, false); + } + + return 0; + } + + err = bt_mesh_settings_set(read_cb, cb_arg, &val, sizeof(val)); + if (err) + { + LOG_ERR("Failed to set \'node\'"); + return err; + } + + node = bt_mesh_cdb_node_get(addr); + if (!node) + { + node = bt_mesh_cdb_node_alloc(val.uuid, addr, val.num_elem, + val.net_idx); + } + + if (!node) + { + LOG_ERR("No space for a new node"); + return -ENOMEM; + } + + if (val.flags & F_NODE_CONFIGURED) + { + atomic_set_bit(node->flags, BT_MESH_CDB_NODE_CONFIGURED); + } + + memcpy(node->uuid, val.uuid, 16); + + /* One extra copying since val.dev_key is from packed structure + * and might be unaligned. + */ + + memcpy(&tmp, &val.dev_key, sizeof(struct bt_mesh_key)); + bt_mesh_key_assign(&node->dev_key, &tmp); + + LOG_DBG("Node 0x%04x recovered from storage", addr); + + return 0; +} + +static int cdb_subnet_set(const char *name, + size_t len_rd, settings_read_cb read_cb, + void *cb_arg) +{ + struct bt_mesh_cdb_subnet *sub; + struct net_key_val key; + struct bt_mesh_key tmp[2]; + uint16_t net_idx; + int err; + + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return 0; + } + + if (!name) + { + LOG_ERR("Insufficient number of arguments"); + return -ENOENT; + } + + net_idx = strtol(name, NULL, 16); + sub = bt_mesh_cdb_subnet_get(net_idx); + + if (len_rd == 0) + { + LOG_DBG("val (null)"); + if (!sub) + { + LOG_ERR("No subnet with NetKeyIndex 0x%03x", net_idx); + return -ENOENT; + } + + LOG_DBG("Deleting NetKeyIndex 0x%03x", net_idx); + bt_mesh_cdb_subnet_del(sub, false); + return 0; + } + + err = bt_mesh_settings_set(read_cb, cb_arg, &key, sizeof(key)); + if (err) + { + LOG_ERR("Failed to set \'net-key\'"); + return err; + } + + /* One extra copying since key.val[] is from packed structure + * and might be unaligned. + */ + + memcpy(&tmp[0], &key.val[0], sizeof(struct bt_mesh_key)); + memcpy(&tmp[1], &key.val[1], sizeof(struct bt_mesh_key)); + + if (sub) + { + LOG_DBG("Updating existing NetKeyIndex 0x%03x", net_idx); + + sub->kr_phase = key.kr_phase; + bt_mesh_key_assign(&sub->keys[0].net_key, &tmp[0]); + bt_mesh_key_assign(&sub->keys[1].net_key, &tmp[1]); + + return 0; + } + + sub = bt_mesh_cdb_subnet_alloc(net_idx); + if (!sub) + { + LOG_ERR("No space to allocate a new subnet"); + return -ENOMEM; + } + + sub->kr_phase = key.kr_phase; + bt_mesh_key_assign(&sub->keys[0].net_key, &tmp[0]); + bt_mesh_key_assign(&sub->keys[1].net_key, &tmp[1]); + + LOG_DBG("NetKeyIndex 0x%03x recovered from storage", net_idx); + + return 0; +} + +static int cdb_app_key_set(const char *name, + size_t len_rd, settings_read_cb read_cb, + void *cb_arg) +{ + struct bt_mesh_cdb_app_key *app; + struct app_key_val key; + struct bt_mesh_key tmp[2]; + uint16_t app_idx; + int err; + + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return 0; + } + + if (!name) + { + LOG_ERR("Insufficient number of arguments"); + return -ENOENT; + } + + app_idx = strtol(name, NULL, 16); + + if (len_rd == 0) + { + LOG_DBG("val (null)"); + LOG_DBG("Deleting AppKeyIndex 0x%03x", app_idx); + + app = bt_mesh_cdb_app_key_get(app_idx); + if (app) + { + bt_mesh_cdb_app_key_del(app, false); + } + + return 0; + } + + err = bt_mesh_settings_set(read_cb, cb_arg, &key, sizeof(key)); + if (err) + { + LOG_ERR("Failed to set \'app-key\'"); + return err; + } + + /* One extra copying since key.val[] is from packed structure + * and might be unaligned. + */ + + memcpy(&tmp[0], &key.val[0], sizeof(struct bt_mesh_key)); + memcpy(&tmp[1], &key.val[1], sizeof(struct bt_mesh_key)); + + app = bt_mesh_cdb_app_key_get(app_idx); + if (!app) + { + app = bt_mesh_cdb_app_key_alloc(key.net_idx, app_idx); + } + + if (!app) + { + LOG_ERR("No space for a new app key"); + return -ENOMEM; + } + + bt_mesh_key_assign(&app->keys[0].app_key, &tmp[0]); + bt_mesh_key_assign(&app->keys[1].app_key, &tmp[1]); + + LOG_DBG("AppKeyIndex 0x%03x recovered from storage", app_idx); + + return 0; +} + +static int cdb_set(const char *name, + size_t len_rd, settings_read_cb read_cb, void *cb_arg) +{ + int len; + const char *next; + + if (!name) + { + LOG_ERR("Insufficient number of arguments"); + return -ENOENT; + } + + if (!strcmp(name, "Net")) + { + return cdb_net_set(name, len_rd, read_cb, cb_arg); + } + + len = settings_name_next(name, &next); + + if (!next) + { + LOG_ERR("Insufficient number of arguments"); + return -ENOENT; + } + + if (!strncmp(name, "Node", len)) + { + return cdb_node_set(next, len_rd, read_cb, cb_arg); + } + + if (!strncmp(name, "Subnet", len)) + { + return cdb_subnet_set(next, len_rd, read_cb, cb_arg); + } + + if (!strncmp(name, "AppKey", len)) + { + return cdb_app_key_set(next, len_rd, read_cb, cb_arg); + } + + LOG_WRN("Unknown module key %s", name); + return -ENOENT; +} + +BT_MESH_SETTINGS_DEFINE(cdb, "cdb", cdb_set); + +static void store_cdb_node(const struct bt_mesh_cdb_node *node) +{ + struct node_val val; + char path[30]; + int err; + + val.net_idx = node->net_idx; + val.num_elem = node->num_elem; + val.flags = 0; + + if (atomic_test_bit(node->flags, BT_MESH_CDB_NODE_CONFIGURED)) + { + val.flags |= F_NODE_CONFIGURED; + } + + memcpy(val.uuid, node->uuid, 16); + memcpy(&val.dev_key, &node->dev_key, sizeof(struct bt_mesh_key)); + + snprintf(path, sizeof(path), "bt/mesh/cdb/Node/%x", node->addr); + + err = settings_save_one(path, &val, sizeof(val)); + if (err) + { + LOG_ERR("Failed to store Node %s", path); + } + else + { + LOG_DBG("Stored Node %s", path); + } +} + +static void clear_cdb_node(uint16_t addr) +{ + char path[30]; + int err; + + LOG_DBG("Node 0x%04x", addr); + + snprintf(path, sizeof(path), "bt/mesh/cdb/Node/%x", addr); + err = settings_delete(path); + if (err) + { + LOG_ERR("Failed to clear Node 0x%04x", addr); + } + else + { + LOG_DBG("Cleared Node 0x%04x", addr); + } +} + +static void store_cdb_subnet(const struct bt_mesh_cdb_subnet *sub) +{ + struct net_key_val key; + char path[30]; + int err; + + LOG_DBG("NetKeyIndex 0x%03x NetKey %s", sub->net_idx, + bt_hex(&sub->keys[0].net_key, sizeof(struct bt_mesh_key))); + + memcpy(&key.val[0], &sub->keys[0].net_key, sizeof(struct bt_mesh_key)); + memcpy(&key.val[1], &sub->keys[1].net_key, sizeof(struct bt_mesh_key)); + key.unused = 0U; + key.kr_phase = sub->kr_phase; + + snprintf(path, sizeof(path), "bt/mesh/cdb/Subnet/%x", sub->net_idx); + + err = settings_save_one(path, &key, sizeof(key)); + if (err) + { + LOG_ERR("Failed to store Subnet %s", path); + } + else + { + LOG_DBG("Stored Subnet %s", path); + } +} + +static void clear_cdb_subnet(uint16_t net_idx) +{ + char path[30]; + int err; + + LOG_DBG("NetKeyIndex 0x%03x", net_idx); + + snprintf(path, sizeof(path), "bt/mesh/cdb/Subnet/%x", net_idx); + err = settings_delete(path); + if (err) + { + LOG_ERR("Failed to clear NetKeyIndex 0x%03x", net_idx); + } + else + { + LOG_DBG("Cleared NetKeyIndex 0x%03x", net_idx); + } +} + +static void store_cdb_app_key(const struct bt_mesh_cdb_app_key *app) +{ + struct app_key_val key; + char path[30]; + int err; + + key.net_idx = app->net_idx; + key.updated = false; + memcpy(&key.val[0], &app->keys[0].app_key, sizeof(struct bt_mesh_key)); + memcpy(&key.val[1], &app->keys[1].app_key, sizeof(struct bt_mesh_key)); + + snprintf(path, sizeof(path), "bt/mesh/cdb/AppKey/%x", app->app_idx); + + err = settings_save_one(path, &key, sizeof(key)); + if (err) + { + LOG_ERR("Failed to store AppKey %s", path); + } + else + { + LOG_DBG("Stored AppKey %s", path); + } +} + +static void clear_cdb_app_key(uint16_t app_idx) +{ + char path[30]; + int err; + + snprintf(path, sizeof(path), "bt/mesh/cdb/AppKey/%x", app_idx); + err = settings_delete(path); + if (err) + { + LOG_ERR("Failed to clear AppKeyIndex 0x%03x", app_idx); + } + else + { + LOG_DBG("Cleared AppKeyIndex 0x%03x", app_idx); + } +} + +static void schedule_cdb_store(int flag) +{ + atomic_set_bit(bt_mesh_cdb.flags, flag); + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_CDB_PENDING); +} + +static void update_cdb_net_settings(void) +{ + schedule_cdb_store(BT_MESH_CDB_SUBNET_PENDING); +} + +static struct node_update *cdb_node_update_find(uint16_t addr, + struct node_update **free_slot) +{ + struct node_update *match; + int i; + + match = NULL; + *free_slot = NULL; + + for (i = 0; i < ARRAY_SIZE(cdb_node_updates); i++) + { + struct node_update *update = &cdb_node_updates[i]; + + if (update->addr == BT_MESH_ADDR_UNASSIGNED) + { + *free_slot = update; + continue; + } + + if (update->addr == addr) + { + match = update; + } + } + + return match; +} + +static void update_cdb_node_settings(const struct bt_mesh_cdb_node *node, + bool store) +{ + struct node_update *update; + struct node_update *free_slot; + + LOG_DBG("Node 0x%04x", node->addr); + + update = cdb_node_update_find(node->addr, &free_slot); + if (update) + { + update->clear = !store; + schedule_cdb_store(BT_MESH_CDB_NODES_PENDING); + return; + } + + if (!free_slot) + { + if (store) + { + store_cdb_node(node); + } + else + { + clear_cdb_node(node->addr); + } + + return; + } + + free_slot->addr = node->addr; + free_slot->clear = !store; + + schedule_cdb_store(BT_MESH_CDB_NODES_PENDING); +} + +static struct key_update *cdb_key_update_find(bool app_key, uint16_t key_idx, + struct key_update **free_slot) +{ + struct key_update *match; + int i; + + match = NULL; + *free_slot = NULL; + + for (i = 0; i < ARRAY_SIZE(cdb_key_updates); i++) + { + struct key_update *update = &cdb_key_updates[i]; + + if (!update->valid) + { + *free_slot = update; + continue; + } + + if (update->app_key != app_key) + { + continue; + } + + if (update->key_idx == key_idx) + { + match = update; + } + } + + return match; +} + +static void update_cdb_subnet_settings(const struct bt_mesh_cdb_subnet *sub, + bool store) +{ + struct key_update *update; + struct key_update *free_slot; + uint8_t clear = store ? 0U : 1U; + + LOG_DBG("NetKeyIndex 0x%03x", sub->net_idx); + + update = cdb_key_update_find(false, sub->net_idx, &free_slot); + if (update) + { + update->clear = clear; + schedule_cdb_store(BT_MESH_CDB_KEYS_PENDING); + return; + } + + if (!free_slot) + { + if (store) + { + store_cdb_subnet(sub); + } + else + { + clear_cdb_subnet(sub->net_idx); + } + + return; + } + + free_slot->valid = 1U; + free_slot->key_idx = sub->net_idx; + free_slot->app_key = 0U; + free_slot->clear = clear; + + schedule_cdb_store(BT_MESH_CDB_KEYS_PENDING); +} + +static void update_cdb_app_key_settings( + const struct bt_mesh_cdb_app_key *key, bool store) +{ + struct key_update *update; + struct key_update *free_slot; + uint8_t clear = store ? 0U : 1U; + + LOG_DBG("AppKeyIndex 0x%03x", key->app_idx); + + update = cdb_key_update_find(true, key->app_idx, &free_slot); + if (update) + { + update->clear = clear; + schedule_cdb_store(BT_MESH_CDB_KEYS_PENDING); + return; + } + + if (!free_slot) + { + if (store) + { + store_cdb_app_key(key); + } + else + { + clear_cdb_app_key(key->app_idx); + } + + return; + } + + free_slot->valid = 1U; + free_slot->key_idx = key->app_idx; + free_slot->app_key = 1U; + free_slot->clear = clear; + + schedule_cdb_store(BT_MESH_CDB_KEYS_PENDING); +} + +static uint16_t addr_assign(uint16_t addr, uint8_t num_elem) +{ + if (addr == BT_MESH_ADDR_UNASSIGNED) + { + addr = find_lowest_free_addr(num_elem); + } + else if (addr < bt_mesh_cdb.lowest_avail_addr) + { + return BT_MESH_ADDR_UNASSIGNED; + } + else if (addr_is_free(addr, num_elem, NULL) < 0) + { + LOG_DBG("Address range 0x%04x-0x%04x is not free", addr, + addr + num_elem - 1); + return BT_MESH_ADDR_UNASSIGNED; + } + + return addr; +} + +int bt_mesh_cdb_create(const uint8_t key[16]) +{ + struct bt_mesh_cdb_subnet *sub; + int err; + + if (atomic_test_and_set_bit(bt_mesh_cdb.flags, BT_MESH_CDB_VALID)) + { + return -EALREADY; + } + + sub = bt_mesh_cdb_subnet_alloc(BT_MESH_KEY_PRIMARY); + if (sub == NULL) + { + return -ENOMEM; + } + + err = bt_mesh_key_import(BT_MESH_KEY_TYPE_NET, key, &sub->keys[0].net_key); + if (err) + { + return err; + } + + bt_mesh_cdb.iv_index = 0; + bt_mesh_cdb.lowest_avail_addr = 1; + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + update_cdb_net_settings(); + update_cdb_subnet_settings(sub, true); + } + + return 0; +} + +void bt_mesh_cdb_clear(void) +{ + int i; + + atomic_clear_bit(bt_mesh_cdb.flags, BT_MESH_CDB_VALID); + + for (i = 0; i < ARRAY_SIZE(bt_mesh_cdb.nodes); ++i) + { + if (bt_mesh_cdb.nodes[i].addr != BT_MESH_ADDR_UNASSIGNED) + { + bt_mesh_cdb_node_del(&bt_mesh_cdb.nodes[i], true); + } + } + + for (i = 0; i < ARRAY_SIZE(bt_mesh_cdb.subnets); ++i) + { + if (bt_mesh_cdb.subnets[i].net_idx != BT_MESH_KEY_UNUSED) + { + bt_mesh_cdb_subnet_del(&bt_mesh_cdb.subnets[i], true); + } + } + + for (i = 0; i < ARRAY_SIZE(bt_mesh_cdb.app_keys); ++i) + { + if (bt_mesh_cdb.app_keys[i].net_idx != BT_MESH_KEY_UNUSED) + { + bt_mesh_cdb_app_key_del(&bt_mesh_cdb.app_keys[i], true); + } + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + update_cdb_net_settings(); + bt_mesh_settings_store_pending(); + } +} + +void bt_mesh_cdb_iv_update(uint32_t iv_index, bool iv_update) +{ + LOG_DBG("Updating IV index to %d\n", iv_index); + + /* Reset the last deleted addr when IV Index is updated or recovered. */ + + if (!iv_update || iv_index > bt_mesh_cdb.iv_index + 1) + { + bt_mesh_cdb.lowest_avail_addr = 1; + } + + bt_mesh_cdb.iv_index = iv_index; + + atomic_set_bit_to(bt_mesh_cdb.flags, BT_MESH_CDB_IVU_IN_PROGRESS, + iv_update); + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + update_cdb_net_settings(); + } +} + +struct bt_mesh_cdb_subnet *bt_mesh_cdb_subnet_alloc(uint16_t net_idx) +{ + struct bt_mesh_cdb_subnet *sub; + int i; + + if (bt_mesh_cdb_subnet_get(net_idx) != NULL) + { + return NULL; + } + + for (i = 0; i < ARRAY_SIZE(bt_mesh_cdb.subnets); ++i) + { + sub = &bt_mesh_cdb.subnets[i]; + + if (sub->net_idx != BT_MESH_KEY_UNUSED) + { + continue; + } + + sub->net_idx = net_idx; + + return sub; + } + + return NULL; +} + +void bt_mesh_cdb_subnet_del(struct bt_mesh_cdb_subnet *sub, bool store) +{ + LOG_DBG("NetIdx 0x%03x store %u", sub->net_idx, store); + + if (IS_ENABLED(CONFIG_BT_SETTINGS) && store) + { + update_cdb_subnet_settings(sub, false); + } + + sub->net_idx = BT_MESH_KEY_UNUSED; + bt_mesh_key_destroy(&sub->keys[0].net_key); + bt_mesh_key_destroy(&sub->keys[1].net_key); + memset(sub->keys, 0, sizeof(sub->keys)); +} + +struct bt_mesh_cdb_subnet *bt_mesh_cdb_subnet_get(uint16_t net_idx) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh_cdb.subnets); ++i) + { + if (bt_mesh_cdb.subnets[i].net_idx == net_idx) + { + return &bt_mesh_cdb.subnets[i]; + } + } + + return NULL; +} + +void bt_mesh_cdb_subnet_store(const struct bt_mesh_cdb_subnet *sub) +{ + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + update_cdb_subnet_settings(sub, true); + } +} + +uint8_t bt_mesh_cdb_subnet_flags(const struct bt_mesh_cdb_subnet *sub) +{ + uint8_t flags = 0x00; + + if (sub && SUBNET_KEY_TX_IDX(sub)) + { + flags |= BT_MESH_NET_FLAG_KR; + } + + if (atomic_test_bit(bt_mesh_cdb.flags, BT_MESH_CDB_IVU_IN_PROGRESS)) + { + flags |= BT_MESH_NET_FLAG_IVU; + } + + return flags; +} + +int bt_mesh_cdb_subnet_key_import(struct bt_mesh_cdb_subnet *sub, + int key_idx, const uint8_t in[16]) +{ + if (!bt_mesh_key_compare(in, &sub->keys[key_idx].net_key)) + { + return 0; + } + + bt_mesh_key_destroy(&sub->keys[key_idx].net_key); + + return bt_mesh_key_import(BT_MESH_KEY_TYPE_NET, in, + &sub->keys[key_idx].net_key); +} + +int bt_mesh_cdb_subnet_key_export( + const struct bt_mesh_cdb_subnet *sub, int key_idx, uint8_t out[16]) +{ + return bt_mesh_key_export(out, &sub->keys[key_idx].net_key); +} + +#if defined CONFIG_BT_MESH_CDB_KEY_SYNC +static void subnet_evt(struct bt_mesh_subnet *sub, enum bt_mesh_key_evt evt) +{ + struct bt_mesh_cdb_subnet *sub_cdb = NULL; + uint8_t tmp[16]; + int err; + + LOG_DBG("Subnet %#x evt %d", sub->net_idx, evt); + + for (int i = 0; i < ARRAY_SIZE(bt_mesh_cdb.subnets); ++i) + { + if (bt_mesh_cdb.subnets[i].net_idx == sub->net_idx) + { + sub_cdb = &bt_mesh_cdb.subnets[i]; + break; + } + } + + if (sub_cdb == NULL && evt != BT_MESH_KEY_ADDED) + { + LOG_WRN("Subnet %#x not found in CDB", sub->net_idx); + return; + } + else if (sub_cdb != NULL && evt == BT_MESH_KEY_ADDED) + { + LOG_WRN("Subnet %#x already exists in CDB. Skipping adding", + sub->net_idx); + return; + } + + switch (evt) + { + case BT_MESH_KEY_ADDED: + sub_cdb = bt_mesh_cdb_subnet_alloc(sub->net_idx); + if (sub_cdb == NULL) + { + LOG_WRN("No space to allocate new subnet in CDB"); + return; + } + + sub_cdb->kr_phase = sub->kr_phase; + err = bt_mesh_key_export(tmp, &sub->keys[0].net); + if (err) + { + LOG_ERR( + "Failed to export NetKey from stack for subnet: %#x, err: " + "%d", sub->net_idx, err); + return; + } + + err = bt_mesh_cdb_subnet_key_import(sub_cdb, 0, tmp); + if (err) + { + LOG_ERR( + "Failed to import NetKey in CDB for subnet: %#x, err: %d", + sub->net_idx, err); + return; + } + break; + case BT_MESH_KEY_DELETED: + bt_mesh_cdb_subnet_del(sub_cdb, true); + break; + case BT_MESH_KEY_UPDATED: + sub_cdb->kr_phase = sub->kr_phase; + err = bt_mesh_key_export(tmp, &sub->keys[1].net); + if (err) + { + LOG_ERR( + "Failed to export NetKey from stack for subnet: %#x, err: " + "%d", sub->net_idx, err); + return; + } + + err = bt_mesh_cdb_subnet_key_import(sub_cdb, 1, tmp); + if (err) + { + LOG_ERR( + "Failed to import NetKey in CDB for subnet: %#x, err: %d", + sub->net_idx, err); + return; + } + break; + case BT_MESH_KEY_SWAPPED: + sub_cdb->kr_phase = sub->kr_phase; + break; + case BT_MESH_KEY_REVOKED: + sub_cdb->kr_phase = sub->kr_phase; + bt_mesh_key_destroy(&sub_cdb->keys[0].net_key); + memcpy(&sub_cdb->keys[0], &sub_cdb->keys[1], + sizeof(sub_cdb->keys[0])); + memset(&sub_cdb->keys[1], 0, sizeof(sub_cdb->keys[1])); + break; + default: + CODE_UNREACHABLE; + } + + if (evt != BT_MESH_KEY_DELETED) + { + bt_mesh_cdb_subnet_store(sub_cdb); + } +} + +BT_MESH_SUBNET_CB_DEFINE(cdb) = +{ + .evt_handler = subnet_evt, +}; +#endif /* CONFIG_BT_MESH_CDB_KEY_SYNC */ + +struct bt_mesh_cdb_node *bt_mesh_cdb_node_alloc( + const uint8_t uuid[16], uint16_t addr, uint8_t num_elem, uint16_t net_idx) +{ + int i; + + addr = addr_assign(addr, num_elem); + if (addr == BT_MESH_ADDR_UNASSIGNED) + { + return NULL; + } + + for (i = 0; i < ARRAY_SIZE(bt_mesh_cdb.nodes); i++) + { + struct bt_mesh_cdb_node *node = &bt_mesh_cdb.nodes[i]; + + if (node->addr == BT_MESH_ADDR_UNASSIGNED) + { + memcpy(node->uuid, uuid, 16); + node->addr = addr; + node->num_elem = num_elem; + node->net_idx = net_idx; + atomic_set(node->flags, 0); + return node; + } + } + + return NULL; +} + +uint16_t bt_mesh_cdb_free_addr_get(uint8_t num_elem) +{ + return find_lowest_free_addr(num_elem); +} + +void bt_mesh_cdb_node_del(struct bt_mesh_cdb_node *node, bool store) +{ + LOG_DBG("Node addr 0x%04x store %u", node->addr, store); + + if (IS_ENABLED(CONFIG_BT_SETTINGS) && store) + { + update_cdb_node_settings(node, false); + } + + if (store && node->addr + node->num_elem > bt_mesh_cdb.lowest_avail_addr) + { + bt_mesh_cdb.lowest_avail_addr = node->addr + node->num_elem; + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + update_cdb_net_settings(); + } + } + + node->addr = BT_MESH_ADDR_UNASSIGNED; + bt_mesh_key_destroy(&node->dev_key); + memset(&node->dev_key, 0, sizeof(node->dev_key)); +} + +void bt_mesh_cdb_node_update(struct bt_mesh_cdb_node *node, + uint16_t addr, uint8_t num_elem) +{ + /* Address is used as a key to the nodes array. Remove the current entry + * first, then store new address. + */ + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + update_cdb_node_settings(node, false); + } + + node->addr = addr; + node->num_elem = num_elem; + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + update_cdb_node_settings(node, true); + } +} + +struct bt_mesh_cdb_node *bt_mesh_cdb_node_get(uint16_t addr) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh_cdb.nodes); i++) + { + struct bt_mesh_cdb_node *node = &bt_mesh_cdb.nodes[i]; + + if (addr >= node->addr && addr <= node->addr + node->num_elem - 1) + { + return node; + } + } + + return NULL; +} + +void bt_mesh_cdb_node_store(const struct bt_mesh_cdb_node *node) +{ + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + update_cdb_node_settings(node, true); + } +} + +void bt_mesh_cdb_node_foreach(bt_mesh_cdb_node_func_t func, void *user_data) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh_cdb.nodes); ++i) + { + if (bt_mesh_cdb.nodes[i].addr == BT_MESH_ADDR_UNASSIGNED) + { + continue; + } + + if (func(&bt_mesh_cdb.nodes[i], user_data) == BT_MESH_CDB_ITER_STOP) + { + break; + } + } +} + +int bt_mesh_cdb_node_key_import(struct bt_mesh_cdb_node *node, + const uint8_t in[16]) +{ + if (!bt_mesh_key_compare(in, &node->dev_key)) + { + return 0; + } + + bt_mesh_key_destroy(&node->dev_key); + + return bt_mesh_key_import(BT_MESH_KEY_TYPE_DEV, in, &node->dev_key); +} + +int bt_mesh_cdb_node_key_export(const struct bt_mesh_cdb_node *node, + uint8_t out[16]) +{ + return bt_mesh_key_export(out, &node->dev_key); +} + +struct bt_mesh_cdb_app_key *bt_mesh_cdb_app_key_alloc(uint16_t net_idx, + uint16_t app_idx) +{ + struct bt_mesh_cdb_app_key *key; + struct bt_mesh_cdb_app_key *vacant_key = NULL; + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh_cdb.app_keys); ++i) + { + key = &bt_mesh_cdb.app_keys[i]; + + if (key->app_idx == app_idx) + { + return NULL; + } + + if (key->net_idx != BT_MESH_KEY_UNUSED || vacant_key) + { + continue; + } + + vacant_key = key; + } + + if (vacant_key) + { + vacant_key->net_idx = net_idx; + vacant_key->app_idx = app_idx; + } + + return vacant_key; +} + +void bt_mesh_cdb_app_key_del(struct bt_mesh_cdb_app_key *key, bool store) +{ + LOG_DBG("AppIdx 0x%03x store %u", key->app_idx, store); + + if (IS_ENABLED(CONFIG_BT_SETTINGS) && store) + { + update_cdb_app_key_settings(key, false); + } + + key->net_idx = BT_MESH_KEY_UNUSED; + key->app_idx = BT_MESH_KEY_UNUSED; + bt_mesh_key_destroy(&key->keys[0].app_key); + bt_mesh_key_destroy(&key->keys[1].app_key); + memset(key->keys, 0, sizeof(key->keys)); +} + +struct bt_mesh_cdb_app_key *bt_mesh_cdb_app_key_get(uint16_t app_idx) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh_cdb.app_keys); i++) + { + struct bt_mesh_cdb_app_key *key = &bt_mesh_cdb.app_keys[i]; + + if (key->net_idx != BT_MESH_KEY_UNUSED && key->app_idx == app_idx) + { + return key; + } + } + + return NULL; +} + +void bt_mesh_cdb_app_key_store(const struct bt_mesh_cdb_app_key *key) +{ + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + update_cdb_app_key_settings(key, true); + } +} + +int bt_mesh_cdb_app_key_import(struct bt_mesh_cdb_app_key *key, + int key_idx, const uint8_t in[16]) +{ + if (!bt_mesh_key_compare(in, &key->keys[key_idx].app_key)) + { + return 0; + } + + bt_mesh_key_destroy(&key->keys[key_idx].app_key); + + return bt_mesh_key_import(BT_MESH_KEY_TYPE_APP, in, + &key->keys[key_idx].app_key); +} + +int bt_mesh_cdb_app_key_export(const struct bt_mesh_cdb_app_key *key, + int key_idx, uint8_t out[16]) +{ + return bt_mesh_key_export(out, &key->keys[key_idx].app_key); +} + +#if defined CONFIG_BT_MESH_CDB_KEY_SYNC +static void app_key_evt(struct bt_mesh_app_key *app, + enum bt_mesh_key_evt evt) +{ + struct bt_mesh_cdb_app_key *app_cdb = NULL; + uint8_t tmp[16]; + int err; + + LOG_DBG("AppKey %#x NetIdx %#x evt %d", app->app_idx, app->net_idx, evt); + + for (int i = 0; i < ARRAY_SIZE(bt_mesh_cdb.app_keys); ++i) + { + if (bt_mesh_cdb.app_keys[i].net_idx == app->net_idx && + bt_mesh_cdb.app_keys[i].app_idx == app->app_idx) + { + app_cdb = &bt_mesh_cdb.app_keys[i]; + break; + } + } + + if (app_cdb == NULL && evt != BT_MESH_KEY_ADDED) + { + LOG_WRN("AppKey %#x not found in CDB", app->app_idx); + return; + } + else if (app_cdb != NULL && evt == BT_MESH_KEY_ADDED) + { + LOG_WRN("AppKey %#x already exists in CDB. Skipping adding", + app->app_idx); + return; + } + + switch (evt) + { + case BT_MESH_KEY_ADDED: + app_cdb = bt_mesh_cdb_app_key_alloc(app->net_idx, app->app_idx); + if (app_cdb == NULL) + { + LOG_WRN("No space to allocate new app key in CDB"); + return; + } + + err = bt_mesh_key_export(tmp, &app->keys[0].val); + if (err) + { + LOG_ERR("Failed to export AppKey: %#x, err: " + "%d", app->app_idx, err); + return; + } + + app_cdb->net_idx = app->net_idx; + app_cdb->app_idx = app->app_idx; + err = bt_mesh_cdb_app_key_import(app_cdb, 0, tmp); + if (err) + { + LOG_ERR("Failed to import AppKey: %#x, err: " + "%d", app->app_idx, err); + return; + } + break; + case BT_MESH_KEY_DELETED: + bt_mesh_cdb_app_key_del(app_cdb, true); + break; + case BT_MESH_KEY_UPDATED: + err = bt_mesh_key_export(tmp, &app->keys[1].val); + if (err) + { + LOG_ERR("Failed to export AppKey: %#x, err: " + "%d", app->app_idx, err); + return; + } + + err = bt_mesh_cdb_app_key_import(app_cdb, 1, tmp); + if (err) + { + LOG_ERR("Failed to import AppKey: %#x, err: " + "%d", app->app_idx, err); + return; + } + break; + case BT_MESH_KEY_SWAPPED: + break; + case BT_MESH_KEY_REVOKED: + bt_mesh_key_destroy(&app_cdb->keys[0].app_key); + memcpy(&app_cdb->keys[0], &app_cdb->keys[1], + sizeof(app_cdb->keys[0])); + memset(&app_cdb->keys[1], 0, sizeof(app_cdb->keys[1])); + break; + default: + CODE_UNREACHABLE; + } + + if (evt != BT_MESH_KEY_DELETED && evt != BT_MESH_KEY_SWAPPED) + { + bt_mesh_cdb_app_key_store(app_cdb); + } +} + +BT_MESH_APP_KEY_CB_DEFINE(app_key_evt); +#endif /* CONFIG_BT_MESH_CDB_KEY_SYNC */ + +static void clear_cdb_net(void) +{ + int err; + + err = settings_delete("bt/mesh/cdb/Net"); + if (err) + { + LOG_ERR("Failed to clear Network"); + } + else + { + LOG_DBG("Cleared Network"); + } +} + +static void store_cdb_pending_net(void) +{ + struct net_val net; + int err; + + LOG_DBG(""); + + net.iv.index = bt_mesh_cdb.iv_index; + net.iv.update = atomic_test_bit(bt_mesh_cdb.flags, + BT_MESH_CDB_IVU_IN_PROGRESS); + net.lowest_avail_addr = bt_mesh_cdb.lowest_avail_addr; + + err = settings_save_one("bt/mesh/cdb/Net", &net, sizeof(net)); + if (err) + { + LOG_ERR("Failed to store Network value"); + } + else + { + LOG_DBG("Stored Network value"); + } +} + +static void store_cdb_pending_nodes(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cdb_node_updates); ++i) + { + struct node_update *update = &cdb_node_updates[i]; + uint16_t addr; + + if (update->addr == BT_MESH_ADDR_UNASSIGNED) + { + continue; + } + + addr = update->addr; + update->addr = BT_MESH_ADDR_UNASSIGNED; + + LOG_DBG("addr: 0x%04x, clear: %d", addr, update->clear); + + if (update->clear) + { + clear_cdb_node(addr); + } + else + { + struct bt_mesh_cdb_node *node; + + node = bt_mesh_cdb_node_get(addr); + if (node) + { + store_cdb_node(node); + } + else + { + LOG_WRN("Node 0x%04x not found", addr); + } + } + } +} + +static void store_cdb_pending_keys(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(cdb_key_updates); i++) + { + struct key_update *update = &cdb_key_updates[i]; + + if (!update->valid) + { + continue; + } + + update->valid = 0U; + + if (update->clear) + { + if (update->app_key) + { + clear_cdb_app_key(update->key_idx); + } + else + { + clear_cdb_subnet(update->key_idx); + } + } + else + { + if (update->app_key) + { + struct bt_mesh_cdb_app_key *key; + + key = bt_mesh_cdb_app_key_get(update->key_idx); + if (key) + { + store_cdb_app_key(key); + } + else + { + LOG_WRN("AppKeyIndex 0x%03x not found", update->key_idx); + } + } + else + { + struct bt_mesh_cdb_subnet *sub; + + sub = bt_mesh_cdb_subnet_get(update->key_idx); + if (sub) + { + store_cdb_subnet(sub); + } + else + { + LOG_WRN("NetKeyIndex 0x%03x not found", update->key_idx); + } + } + } + } +} + +void bt_mesh_cdb_pending_store(void) +{ + if (atomic_test_and_clear_bit(bt_mesh_cdb.flags, + BT_MESH_CDB_SUBNET_PENDING)) + { + if (atomic_test_bit(bt_mesh_cdb.flags, BT_MESH_CDB_VALID)) + { + store_cdb_pending_net(); + } + else + { + clear_cdb_net(); + } + } + + if (atomic_test_and_clear_bit(bt_mesh_cdb.flags, + BT_MESH_CDB_NODES_PENDING)) + { + store_cdb_pending_nodes(); + } + + if (atomic_test_and_clear_bit(bt_mesh_cdb.flags, BT_MESH_CDB_KEYS_PENDING)) + { + store_cdb_pending_keys(); + } +} diff --git a/libs/zblue/mesh/cdb.h b/libs/zblue/mesh/cdb.h new file mode 100644 index 0000000000000..78aeafb1a43bd --- /dev/null +++ b/libs/zblue/mesh/cdb.h @@ -0,0 +1,14 @@ +/**************************************************************************** + * libs/zblue/mesh/cdb.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +void bt_mesh_cdb_node_store(const struct bt_mesh_cdb_node *node); +void bt_mesh_cdb_pending_store(void); diff --git a/libs/zblue/mesh/cfg.c b/libs/zblue/mesh/cfg.c new file mode 100644 index 0000000000000..eded73c757d1e --- /dev/null +++ b/libs/zblue/mesh/cfg.c @@ -0,0 +1,588 @@ +/**************************************************************************** + * libs/zblue/mesh/cfg.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include "mesh.h" +#include "net.h" +#include "rpl.h" +#include "beacon.h" +#include "settings.h" +#include "heartbeat.h" +#include "friend.h" +#include "cfg.h" +#include "od_priv_proxy.h" +#include "priv_beacon.h" + +#define LOG_LEVEL CONFIG_BT_MESH_CFG_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_cfg); + +/* Miscellaneous configuration server model states */ + +struct cfg_val +{ + uint8_t net_transmit; + uint8_t relay; + uint8_t relay_retransmit; + uint8_t beacon; + uint8_t gatt_proxy; + uint8_t frnd; + uint8_t default_ttl; +}; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_mesh_beacon_set(bool beacon) +{ + if (atomic_test_bit(bt_mesh.flags, BT_MESH_BEACON) == beacon) + { + return; + } + + atomic_set_bit_to(bt_mesh.flags, BT_MESH_BEACON, beacon); + + if (beacon) + { + bt_mesh_beacon_enable(); + } + else + { + /* Beacon timer will stop automatically when all beacons are disabled. + */ + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS) && + atomic_test_bit(bt_mesh.flags, BT_MESH_VALID)) + { + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_CFG_PENDING); + } +} + +bool bt_mesh_beacon_enabled(void) +{ + return atomic_test_bit(bt_mesh.flags, BT_MESH_BEACON); +} + +static int feature_set(int feature_flag, enum bt_mesh_feat_state state) +{ + if (state != BT_MESH_FEATURE_DISABLED && state != BT_MESH_FEATURE_ENABLED) + { + return -EINVAL; + } + + if (atomic_test_bit(bt_mesh.flags, + feature_flag) == (state == BT_MESH_FEATURE_ENABLED)) + { + return -EALREADY; + } + + atomic_set_bit_to(bt_mesh.flags, feature_flag, + (state == BT_MESH_FEATURE_ENABLED)); + + return 0; +} + +static enum bt_mesh_feat_state feature_get(int feature_flag) +{ + return atomic_test_bit(bt_mesh.flags, + feature_flag) ? BT_MESH_FEATURE_ENABLED + : BT_MESH_FEATURE_DISABLED; +} + +int bt_mesh_priv_beacon_set(enum bt_mesh_feat_state priv_beacon) +{ + int err; + + if (!IS_ENABLED(CONFIG_BT_MESH_PRIV_BEACONS)) + { + return -ENOTSUP; + } + + err = feature_set(BT_MESH_PRIV_BEACON, priv_beacon); + if (err) + { + return err; + } + + if (priv_beacon == BT_MESH_FEATURE_ENABLED) + { + bt_mesh_beacon_enable(); + } + else + { + /* Beacon timer will stop automatically when all beacons are disabled. + */ + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS) && + IS_ENABLED(CONFIG_BT_MESH_PRIV_BEACON_SRV) && + atomic_test_bit(bt_mesh.flags, BT_MESH_VALID)) + { + bt_mesh_priv_beacon_srv_store_schedule(); + } + + return 0; +} + +enum bt_mesh_feat_state bt_mesh_priv_beacon_get(void) +{ + if (!IS_ENABLED(CONFIG_BT_MESH_PRIV_BEACONS)) + { + return BT_MESH_FEATURE_NOT_SUPPORTED; + } + + return feature_get(BT_MESH_PRIV_BEACON); +} + +void bt_mesh_priv_beacon_update_interval_set(uint8_t interval) +{ +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) + bt_mesh.priv_beacon_int = interval; +#endif +} + +uint8_t bt_mesh_priv_beacon_update_interval_get(void) +{ +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) + return bt_mesh.priv_beacon_int; +#else + return 0; +#endif +} + +int bt_mesh_od_priv_proxy_get(void) +{ +#if defined(CONFIG_BT_MESH_OD_PRIV_PROXY_SRV) + return bt_mesh.on_demand_state; +#else + return -ENOTSUP; +#endif +} + +int bt_mesh_od_priv_proxy_set(uint8_t on_demand_proxy) +{ +#if !defined(CONFIG_BT_MESH_OD_PRIV_PROXY_SRV) + return -ENOTSUP; +#else + + if (bt_mesh_priv_gatt_proxy_get() != BT_MESH_FEATURE_NOT_SUPPORTED) + { + bt_mesh.on_demand_state = on_demand_proxy; + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS) && + IS_ENABLED(CONFIG_BT_MESH_OD_PRIV_PROXY_SRV) && + atomic_test_bit(bt_mesh.flags, BT_MESH_VALID)) + { + bt_mesh_od_priv_proxy_srv_store_schedule(); + } + + return 0; +#endif +} + +static bool node_id_is_running(struct bt_mesh_subnet *sub, void *cb_data) +{ + return sub->node_id == BT_MESH_NODE_IDENTITY_RUNNING; +} + +int bt_mesh_gatt_proxy_set(enum bt_mesh_feat_state gatt_proxy) +{ + int err; + + if (!IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY)) + { + return -ENOTSUP; + } + + err = feature_set(BT_MESH_GATT_PROXY, gatt_proxy); + if (err) + { + return err; + } + + /* The binding from section 4.2.45.1 disables Private GATT Proxy state when + * non-private state is enabled. + */ + + if (gatt_proxy == BT_MESH_FEATURE_ENABLED) + { + feature_set(BT_MESH_PRIV_GATT_PROXY, BT_MESH_FEATURE_DISABLED); + } + + if ((gatt_proxy == BT_MESH_FEATURE_ENABLED) || + (gatt_proxy == BT_MESH_FEATURE_DISABLED && + !bt_mesh_subnet_find(node_id_is_running, NULL))) + { + bt_mesh_adv_gatt_update(); + } + + bt_mesh_hb_feature_changed(BT_MESH_FEAT_PROXY); + + if (IS_ENABLED(CONFIG_BT_SETTINGS) && + atomic_test_bit(bt_mesh.flags, BT_MESH_VALID)) + { + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_CFG_PENDING); + } + + return 0; +} + +enum bt_mesh_feat_state bt_mesh_gatt_proxy_get(void) +{ + if (!IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY)) + { + return BT_MESH_FEATURE_NOT_SUPPORTED; + } + + return feature_get(BT_MESH_GATT_PROXY); +} + +int bt_mesh_priv_gatt_proxy_set(enum bt_mesh_feat_state priv_gatt_proxy) +{ + int err; + + if (!IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY) || + !IS_ENABLED(CONFIG_BT_MESH_PRIV_BEACONS)) + { + return BT_MESH_FEATURE_NOT_SUPPORTED; + } + + /* Reverse binding from section 4.2.45.1 doesn't allow to enable private + * state if non-private state is enabled. + */ + + if (bt_mesh_gatt_proxy_get() == BT_MESH_FEATURE_ENABLED) + { + return BT_MESH_FEATURE_DISABLED; + } + + err = feature_set(BT_MESH_PRIV_GATT_PROXY, priv_gatt_proxy); + if (err) + { + return err; + } + + if (priv_gatt_proxy == BT_MESH_FEATURE_ENABLED) + { + /* Re-generate proxy beacon */ + + bt_mesh_adv_gatt_update(); + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS) && + IS_ENABLED(CONFIG_BT_MESH_PRIV_BEACON_SRV) && + atomic_test_bit(bt_mesh.flags, BT_MESH_VALID)) + { + bt_mesh_priv_beacon_srv_store_schedule(); + } + + return 0; +} + +enum bt_mesh_feat_state bt_mesh_priv_gatt_proxy_get(void) +{ + if (!IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY) + || !IS_ENABLED(CONFIG_BT_MESH_PRIV_BEACONS)) + { + return BT_MESH_FEATURE_NOT_SUPPORTED; + } + + return feature_get(BT_MESH_PRIV_GATT_PROXY); +} + +int bt_mesh_default_ttl_set(uint8_t default_ttl) +{ + if (default_ttl == 1 || default_ttl > BT_MESH_TTL_MAX) + { + return -EINVAL; + } + + if (default_ttl == bt_mesh.default_ttl) + { + return 0; + } + + bt_mesh.default_ttl = default_ttl; + + if (IS_ENABLED(CONFIG_BT_SETTINGS) && + atomic_test_bit(bt_mesh.flags, BT_MESH_VALID)) + { + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_CFG_PENDING); + } + + return 0; +} + +uint8_t bt_mesh_default_ttl_get(void) +{ + return bt_mesh.default_ttl; +} + +int bt_mesh_friend_set(enum bt_mesh_feat_state friendship) +{ + int err; + + if (!IS_ENABLED(CONFIG_BT_MESH_FRIEND)) + { + return -ENOTSUP; + } + + err = feature_set(BT_MESH_FRIEND, friendship); + if (err) + { + return err; + } + + bt_mesh_hb_feature_changed(BT_MESH_FEAT_FRIEND); + + if (IS_ENABLED(CONFIG_BT_SETTINGS) && + atomic_test_bit(bt_mesh.flags, BT_MESH_VALID)) + { + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_CFG_PENDING); + } + + if (friendship == BT_MESH_FEATURE_DISABLED) + { + bt_mesh_friends_clear(); + } + + return 0; +} + +enum bt_mesh_feat_state bt_mesh_friend_get(void) +{ + if (!IS_ENABLED(CONFIG_BT_MESH_FRIEND)) + { + return BT_MESH_FEATURE_NOT_SUPPORTED; + } + + return feature_get(BT_MESH_FRIEND); +} + +void bt_mesh_net_transmit_set(uint8_t xmit) +{ + if (bt_mesh.net_xmit == xmit) + { + return; + } + + bt_mesh.net_xmit = xmit; + + if (IS_ENABLED(CONFIG_BT_SETTINGS) && + atomic_test_bit(bt_mesh.flags, BT_MESH_VALID)) + { + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_CFG_PENDING); + } +} + +uint8_t bt_mesh_net_transmit_get(void) +{ + return bt_mesh.net_xmit; +} + +int bt_mesh_relay_set(enum bt_mesh_feat_state relay, uint8_t xmit) +{ + int err; + + if (!IS_ENABLED(CONFIG_BT_MESH_RELAY)) + { + return -ENOTSUP; + } + + err = feature_set(BT_MESH_RELAY, relay); + if (err == -EINVAL) + { + return err; + } + + if (err == -EALREADY && bt_mesh.relay_xmit == xmit) + { + return -EALREADY; + } + + bt_mesh.relay_xmit = xmit; + bt_mesh_hb_feature_changed(BT_MESH_FEAT_RELAY); + + if (IS_ENABLED(CONFIG_BT_SETTINGS) && + atomic_test_bit(bt_mesh.flags, BT_MESH_VALID)) + { + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_CFG_PENDING); + } + + return 0; +} + +enum bt_mesh_feat_state bt_mesh_relay_get(void) +{ + return feature_get(BT_MESH_RELAY); +} + +uint8_t bt_mesh_relay_retransmit_get(void) +{ + if (!IS_ENABLED(CONFIG_BT_MESH_RELAY)) + { + return 0; + } + + return bt_mesh.relay_xmit; +} + +bool bt_mesh_fixed_group_match(uint16_t addr) +{ + /* Check for fixed group addresses */ + + switch (addr) + { + case BT_MESH_ADDR_ALL_NODES: + return true; + case BT_MESH_ADDR_PROXIES: + return (bt_mesh_gatt_proxy_get() == BT_MESH_FEATURE_ENABLED); + case BT_MESH_ADDR_FRIENDS: + return (bt_mesh_friend_get() == BT_MESH_FEATURE_ENABLED); + case BT_MESH_ADDR_RELAYS: + return (bt_mesh_relay_get() == BT_MESH_FEATURE_ENABLED); + default: + return false; + } +} + +void bt_mesh_cfg_default_set(void) +{ + bt_mesh.default_ttl = CONFIG_BT_MESH_DEFAULT_TTL; + bt_mesh.net_xmit = BT_MESH_TRANSMIT(CONFIG_BT_MESH_NETWORK_TRANSMIT_COUNT, + CONFIG_BT_MESH_NETWORK_TRANSMIT_INTERVAL); + +#if defined(CONFIG_BT_MESH_RELAY) + bt_mesh.relay_xmit = + BT_MESH_TRANSMIT(CONFIG_BT_MESH_RELAY_RETRANSMIT_COUNT, + CONFIG_BT_MESH_RELAY_RETRANSMIT_INTERVAL); +#endif + + if (IS_ENABLED(CONFIG_BT_MESH_RELAY_ENABLED)) + { + atomic_set_bit(bt_mesh.flags, BT_MESH_RELAY); + } + + if (IS_ENABLED(CONFIG_BT_MESH_BEACON_ENABLED)) + { + atomic_set_bit(bt_mesh.flags, BT_MESH_BEACON); + } + + if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY_ENABLED)) + { + atomic_set_bit(bt_mesh.flags, BT_MESH_GATT_PROXY); + } + + if (IS_ENABLED(CONFIG_BT_MESH_FRIEND_ENABLED)) + { + atomic_set_bit(bt_mesh.flags, BT_MESH_FRIEND); + } +} + +static int cfg_set(const char *name, + size_t len_rd, settings_read_cb read_cb, void *cb_arg) +{ + struct cfg_val cfg; + int err; + + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return 0; + } + + if (len_rd == 0) + { + LOG_DBG("Cleared configuration state"); + return 0; + } + + err = bt_mesh_settings_set(read_cb, cb_arg, &cfg, sizeof(cfg)); + if (err) + { + LOG_ERR("Failed to set \'cfg\'"); + return err; + } + + bt_mesh_net_transmit_set(cfg.net_transmit); + bt_mesh_relay_set(cfg.relay, cfg.relay_retransmit); + bt_mesh_beacon_set(cfg.beacon); + bt_mesh_gatt_proxy_set(cfg.gatt_proxy); + bt_mesh_friend_set(cfg.frnd); + bt_mesh_default_ttl_set(cfg.default_ttl); + + LOG_DBG("Restored configuration state"); + + return 0; +} + +BT_MESH_SETTINGS_DEFINE(cfg, "Cfg", cfg_set); + +static void clear_cfg(void) +{ + int err; + + err = settings_delete("bt/mesh/Cfg"); + if (err) + { + LOG_ERR("Failed to clear configuration (err: %d)", err); + } + else + { + LOG_DBG("Cleared configuration"); + } +} + +static void store_pending_cfg(void) +{ + struct cfg_val val; + int err; + + val.net_transmit = bt_mesh_net_transmit_get(); + val.relay = bt_mesh_relay_get(); + val.relay_retransmit = bt_mesh_relay_retransmit_get(); + val.beacon = bt_mesh_beacon_enabled(); + val.gatt_proxy = bt_mesh_gatt_proxy_get(); + val.frnd = bt_mesh_friend_get(); + val.default_ttl = bt_mesh_default_ttl_get(); + + err = settings_save_one("bt/mesh/Cfg", &val, sizeof(val)); + if (err) + { + LOG_ERR("Failed to store configuration value"); + } + else + { + LOG_DBG("Stored configuration value"); + LOG_HEXDUMP_DBG(&val, sizeof(val), "raw value"); + } +} + +void bt_mesh_cfg_pending_store(void) +{ + if (atomic_test_bit(bt_mesh.flags, BT_MESH_VALID)) + { + store_pending_cfg(); + } + else + { + clear_cfg(); + } +} diff --git a/libs/zblue/mesh/cfg.h b/libs/zblue/mesh/cfg.h new file mode 100644 index 0000000000000..0bf291309ca3b --- /dev/null +++ b/libs/zblue/mesh/cfg.h @@ -0,0 +1,16 @@ +/**************************************************************************** + * libs/zblue/mesh/cfg.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +void bt_mesh_cfg_default_set(void); +void bt_mesh_cfg_pending_store(void); + +bool bt_mesh_fixed_group_match(uint16_t addr); diff --git a/libs/zblue/mesh/cfg_cli.c b/libs/zblue/mesh/cfg_cli.c new file mode 100644 index 0000000000000..dd265bd066fce --- /dev/null +++ b/libs/zblue/mesh/cfg_cli.c @@ -0,0 +1,3158 @@ +/**************************************************************************** + * libs/zblue/mesh/cfg_cli.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "common/bt_str.h" + +#include "access.h" +#include "net.h" +#include "foundation.h" +#include "msg.h" + +#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_cfg_cli); + +#define CID_NVAL 0xffff + +/* 2 byte dummy opcode for getting compile time buffer sizes. */ +#define DUMMY_2_BYTE_OP BT_MESH_MODEL_OP_2(0xff, 0xff) + +#define COR_PRESENT(hdr) ((hdr) & BIT(0)) +#define FMT(hdr) ((hdr) & BIT(1)) +#define EXT_ITEM_CNT(hdr) ((hdr) >> 2) +#define OFFSET(item) (item & (uint8_t)BIT_MASK(5)) +#define IDX(item) (item >> 3) + +struct comp_data +{ + uint8_t *page; + struct net_buf_simple *comp; +}; + +static int32_t msg_timeout; + +static struct bt_mesh_cfg_cli *cli; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int comp_data_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct net_buf_simple_state state; + struct comp_data *param; + size_t to_copy; + uint8_t page; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + page = net_buf_simple_pull_u8(buf); + + net_buf_simple_save(buf, &state); + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_DEV_COMP_DATA_STATUS, + ctx->addr, (void **)¶m)) + { + if (param->page) + { + *(param->page) = page; + } + + if (param->comp) + { + to_copy = MIN(net_buf_simple_tailroom(param->comp), buf->len); + net_buf_simple_add_mem(param->comp, buf->data, to_copy); + } + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + + net_buf_simple_restore(buf, &state); + if (cli->cb && cli->cb->comp_data) + { + cli->cb->comp_data(cli, ctx->addr, page, buf); + } + + return 0; +} + +static uint8_t state_status_u8( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf, uint32_t expect_status) +{ + uint8_t *param; + uint8_t status; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + status = net_buf_simple_pull_u8(buf); + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, expect_status, ctx->addr, + (void **)¶m)) + { + if (param) + { + *param = status; + } + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + + return status; +} + +static int beacon_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint8_t status; + + status = state_status_u8(model, ctx, buf, OP_BEACON_STATUS); + + if (cli->cb && cli->cb->beacon_status) + { + cli->cb->beacon_status(cli, ctx->addr, status); + } + + return 0; +} + +static int ttl_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint8_t status; + + status = state_status_u8(model, ctx, buf, OP_DEFAULT_TTL_STATUS); + + if (cli->cb && cli->cb->ttl_status) + { + cli->cb->ttl_status(cli, ctx->addr, status); + } + + return 0; +} + +static int friend_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint8_t status; + + status = state_status_u8(model, ctx, buf, OP_FRIEND_STATUS); + + if (cli->cb && cli->cb->friend_status) + { + cli->cb->friend_status(cli, ctx->addr, status); + } + + return 0; +} + +static int gatt_proxy_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint8_t status; + + status = state_status_u8(model, ctx, buf, OP_GATT_PROXY_STATUS); + + if (cli->cb && cli->cb->gatt_proxy_status) + { + cli->cb->gatt_proxy_status(cli, ctx->addr, status); + } + + return 0; +} + +struct krp_param +{ + uint8_t *status; + uint16_t net_idx; + uint8_t *phase; +}; + +static int krp_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + int err = 0; + struct krp_param *param; + uint16_t net_idx; + uint8_t status; + uint8_t phase; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + status = net_buf_simple_pull_u8(buf); + net_idx = net_buf_simple_pull_le16(buf) & 0xfff; + phase = net_buf_simple_pull_u8(buf); + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_KRP_STATUS, ctx->addr, + (void **)¶m)) + { + if (param->net_idx != net_idx) + { + err = -ENOENT; + goto done; + } + + if (param->status) + { + *param->status = status; + } + + if (param->phase) + { + *param->phase = phase; + } + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + +done: + if (cli->cb && cli->cb->krp_status) + { + cli->cb->krp_status(cli, ctx->addr, status, net_idx, phase); + } + + return err; +} + +struct relay_param +{ + uint8_t *status; + uint8_t *transmit; +}; + +static int relay_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct relay_param *param; + uint8_t status; + uint8_t transmit; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + status = net_buf_simple_pull_u8(buf); + transmit = net_buf_simple_pull_u8(buf); + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_RELAY_STATUS, ctx->addr, + (void **)¶m)) + { + if (param->status) + { + *param->status = status; + } + + if (param->transmit) + { + *param->transmit = transmit; + } + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + + if (cli->cb && cli->cb->relay_status) + { + cli->cb->relay_status(cli, ctx->addr, status, transmit); + } + + return 0; +} + +static int net_transmit_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint8_t status; + + status = state_status_u8(model, ctx, buf, OP_NET_TRANSMIT_STATUS); + + if (cli->cb && cli->cb->network_transmit_status) + { + cli->cb->network_transmit_status(cli, ctx->addr, status); + } + + return 0; +} + +struct net_key_param +{ + uint8_t *status; + uint16_t net_idx; +}; + +static int net_key_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct net_key_param *param; + uint16_t net_idx; + uint8_t status; + int err; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + status = net_buf_simple_pull_u8(buf); + net_idx = net_buf_simple_pull_le16(buf) & 0xfff; + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_NET_KEY_STATUS, ctx->addr, + (void **)¶m)) + { + if (param->net_idx != net_idx) + { + LOG_WRN("Net Key Status key index does not match"); + err = -ENOENT; + goto done; + } + + if (param->status) + { + *param->status = status; + } + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + + err = 0; + +done: + if (cli->cb && cli->cb->net_key_status) + { + cli->cb->net_key_status(cli, ctx->addr, status, net_idx); + } + + return err; +} + +struct net_key_list_param +{ + uint16_t *keys; + size_t *key_cnt; +}; + +int bt_mesh_key_idx_unpack_list(struct net_buf_simple *buf, + uint16_t *dst_arr, size_t *dst_cnt) +{ + size_t i; + + if (!dst_cnt) + { + return 0; + } + + for (i = 0; (i + 1) < *dst_cnt && buf->len >= 3; i += 2) + { + key_idx_unpack_pair(buf, &dst_arr[i], &dst_arr[i + 1]); + } + + if (i < *dst_cnt && buf->len >= 2) + { + dst_arr[i++] = net_buf_simple_pull_le16(buf) & 0xfff; + } + + *dst_cnt = i; + + return buf->len > 0 ? -EMSGSIZE : 0; +} + +static int net_key_list( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + int err = 0; + struct net_key_list_param *param; + struct net_buf_simple_state state; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + net_buf_simple_save(buf, &state); + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_NET_KEY_LIST, ctx->addr, + (void **)¶m)) + { + if (param->keys && param->key_cnt) + { + err = bt_mesh_key_idx_unpack_list(buf, + param->keys, + param->key_cnt); + + if (err) + { + LOG_ERR("The message size for the application opcode is " + "incorrect."); + goto done; + } + } + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + +done: + net_buf_simple_restore(buf, &state); + if (cli->cb && cli->cb->net_key_list) + { + cli->cb->net_key_list(cli, ctx->addr, buf); + } + + return err; +} + +static int node_reset_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + bool *param = NULL; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x", ctx->net_idx, + ctx->app_idx, ctx->addr); + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_NODE_RESET_STATUS, + ctx->addr, + (void **)¶m)) + { + if (param) + { + *param = true; + } + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + + if (cli->cb && cli->cb->node_reset_status) + { + cli->cb->node_reset_status(cli, ctx->addr); + } + + return 0; +} + +struct app_key_param +{ + uint8_t *status; + uint16_t net_idx; + uint16_t app_idx; +}; + +static int app_key_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct app_key_param *param; + uint16_t net_idx; + uint16_t app_idx; + uint8_t status; + int err; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + status = net_buf_simple_pull_u8(buf); + key_idx_unpack_pair(buf, &net_idx, &app_idx); + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_APP_KEY_STATUS, ctx->addr, + (void **)¶m)) + { + if (param->net_idx != net_idx || param->app_idx != app_idx) + { + LOG_WRN("App Key Status key indices did not match"); + err = -ENOENT; + goto done; + } + + if (param->status) + { + *param->status = status; + } + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + + err = 0; + +done: + if (cli->cb && cli->cb->app_key_status) + { + cli->cb->app_key_status(cli, ctx->addr, status, net_idx, app_idx); + } + + return err; +} + +struct app_key_list_param +{ + uint16_t net_idx; + uint8_t *status; + uint16_t *keys; + size_t *key_cnt; +}; + +static int app_key_list( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + int err = 0; + struct net_buf_simple_state state; + struct app_key_list_param *param; + uint16_t net_idx; + uint8_t status; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + status = net_buf_simple_pull_u8(buf); + net_idx = net_buf_simple_pull_le16(buf) & 0xfff; + net_buf_simple_save(buf, &state); + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_APP_KEY_LIST, ctx->addr, + (void **)¶m)) + { + if (param->net_idx != net_idx) + { + LOG_WRN("App Key List Net Key index did not match"); + return -ENOENT; + } + + if (param->keys && param->key_cnt) + { + err = bt_mesh_key_idx_unpack_list(buf, + param->keys, + param->key_cnt); + + if (err) + { + LOG_ERR("The message size for the application opcode is " + "incorrect."); + goto done; + } + } + + if (param->status) + { + *param->status = status; + } + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + +done: + net_buf_simple_restore(buf, &state); + if (cli->cb && cli->cb->app_key_list) + { + cli->cb->app_key_list(cli, ctx->addr, status, net_idx, buf); + } + + return err; +} + +struct mod_app_param +{ + uint8_t *status; + uint16_t elem_addr; + uint16_t mod_app_idx; + uint16_t mod_id; + uint16_t cid; +}; + +static int mod_app_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct mod_app_param *param; + uint16_t elem_addr; + uint16_t mod_app_idx; + uint16_t mod_id; + uint16_t cid; + uint8_t status; + int err; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + if ((buf->len != 7U) && (buf->len != 9U)) + { + LOG_ERR("The message size for the application opcode is incorrect."); + return -EMSGSIZE; + } + + status = net_buf_simple_pull_u8(buf); + elem_addr = net_buf_simple_pull_le16(buf); + mod_app_idx = net_buf_simple_pull_le16(buf); + + if (buf->len >= 4U) + { + cid = net_buf_simple_pull_le16(buf); + } + else + { + cid = CID_NVAL; + } + + mod_id = net_buf_simple_pull_le16(buf); + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_MOD_APP_STATUS, ctx->addr, + (void **)¶m)) + { + if (param->elem_addr != elem_addr || + param->mod_app_idx != mod_app_idx || + param->mod_id != mod_id || param->cid != cid) + { + LOG_WRN("Model App Status parameters did not match"); + err = -ENOENT; + goto done; + } + + if (param->status) + { + *param->status = status; + } + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + + err = 0; + +done: + if (cli->cb && cli->cb->mod_app_status) + { + cli->cb->mod_app_status(cli, ctx->addr, status, elem_addr, mod_app_idx, + (cid << 16) | mod_id); + } + + return err; +} + +struct mod_member_list_param +{ + uint8_t *status; + uint16_t elem_addr; + uint16_t mod_id; + uint16_t cid; + uint16_t *members; + size_t *member_cnt; +}; + +static int mod_sub_list_handle( + struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf, uint16_t op, + bool vnd) +{ + int err = 0; + struct mod_member_list_param *param; + struct net_buf_simple_state state; + uint16_t elem_addr; + uint16_t mod_id; + uint16_t cid; + uint8_t status; + + status = net_buf_simple_pull_u8(buf); + elem_addr = net_buf_simple_pull_le16(buf); + cid = vnd ? net_buf_simple_pull_le16(buf) : CID_NVAL; + + mod_id = net_buf_simple_pull_le16(buf); + if (buf->len % 2) + { + LOG_WRN("Model Member List invalid length"); + return -EMSGSIZE; + } + + net_buf_simple_save(buf, &state); + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, op, ctx->addr, + (void **)¶m)) + { + if (param->elem_addr != elem_addr || param->mod_id != mod_id || + (vnd && param->cid != cid)) + { + LOG_WRN("Model Member List parameters did not match"); + err = -ENOENT; + goto done; + } + + if (param->member_cnt && param->members) + { + int i; + + for (i = 0; i < *param->member_cnt && buf->len; i++) + { + param->members[i] = net_buf_simple_pull_le16(buf); + } + + *param->member_cnt = i; + } + + if (param->status) + { + *param->status = status; + } + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + +done: + net_buf_simple_restore(buf, &state); + if (cli->cb && cli->cb->mod_sub_list) + { + cli->cb->mod_sub_list(cli, ctx->addr, status, elem_addr, mod_id, cid, + buf); + } + + return err; +} + +static int mod_app_list_handle( + struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf, uint16_t op, + bool vnd) +{ + int err = 0; + struct net_buf_simple_state state; + struct mod_member_list_param *param; + uint16_t elem_addr; + uint16_t mod_id; + uint16_t cid; + uint8_t status; + + status = net_buf_simple_pull_u8(buf); + elem_addr = net_buf_simple_pull_le16(buf); + cid = vnd ? net_buf_simple_pull_le16(buf) : CID_NVAL; + + mod_id = net_buf_simple_pull_le16(buf); + + net_buf_simple_save(buf, &state); + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, op, ctx->addr, + (void **)¶m)) + { + if (param->elem_addr != elem_addr || param->mod_id != mod_id || + (vnd && param->cid != cid)) + { + LOG_WRN("Model Member List parameters did not match"); + return -ENOENT; + } + + if (param->member_cnt && param->members) + { + err = bt_mesh_key_idx_unpack_list(buf, param->members, + param->member_cnt); + + if (err) + { + LOG_ERR("The message size for the application opcode is " + "incorrect."); + goto done; + } + } + + if (param->status) + { + *param->status = status; + } + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + +done: + net_buf_simple_restore(buf, &state); + if (cli->cb && cli->cb->mod_app_list) + { + cli->cb->mod_app_list(cli, ctx->addr, status, elem_addr, mod_id, cid, + buf); + } + + return err; +} + +static int mod_app_list( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + return mod_app_list_handle(ctx, buf, OP_SIG_MOD_APP_LIST, false); +} + +static int mod_app_list_vnd( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + return mod_app_list_handle(ctx, buf, OP_VND_MOD_APP_LIST, true); +} + +struct mod_pub_param +{ + uint16_t mod_id; + uint16_t cid; + uint16_t elem_addr; + uint8_t *status; + struct bt_mesh_cfg_cli_mod_pub *pub; +}; + +static int mod_pub_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + int err = 0; + struct mod_pub_param *param; + uint16_t mod_id; + uint16_t cid; + uint16_t elem_addr; + struct bt_mesh_cfg_cli_mod_pub pub; + uint8_t status; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + if ((buf->len != 12U) && (buf->len != 14U)) + { + LOG_ERR("The message size for the application opcode is incorrect."); + return -EINVAL; + } + + status = net_buf_simple_pull_u8(buf); + + elem_addr = net_buf_simple_pull_le16(buf); + + pub.addr = net_buf_simple_pull_le16(buf); + pub.app_idx = net_buf_simple_pull_le16(buf); + pub.cred_flag = (pub.app_idx & BIT(12)); + pub.app_idx &= BIT_MASK(12); + pub.ttl = net_buf_simple_pull_u8(buf); + pub.period = net_buf_simple_pull_u8(buf); + pub.transmit = net_buf_simple_pull_u8(buf); + + if (buf->len == 4U) + { + cid = net_buf_simple_pull_le16(buf); + mod_id = net_buf_simple_pull_le16(buf); + } + else + { + cid = CID_NVAL; + mod_id = net_buf_simple_pull_le16(buf); + } + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_MOD_PUB_STATUS, ctx->addr, + (void **)¶m)) + { + if (mod_id != param->mod_id || cid != param->cid) + { + LOG_WRN("Mod Pub Model ID or Company ID mismatch"); + err = -ENOENT; + goto done; + } + + if (elem_addr != param->elem_addr) + { + LOG_WRN("Model Pub Status for unexpected element (0x%04x)", + elem_addr); + err = -ENOENT; + goto done; + } + + if (param->status) + { + *param->status = status; + } + + if (param->pub) + { + param->pub->addr = pub.addr; + param->pub->app_idx = pub.app_idx; + param->pub->cred_flag = pub.cred_flag; + param->pub->ttl = pub.ttl; + param->pub->period = pub.period; + param->pub->transmit = pub.transmit; + } + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + +done: + if (cli->cb && cli->cb->mod_pub_status) + { + cli->cb->mod_pub_status(cli, ctx->addr, status, elem_addr, mod_id, cid, + &pub); + } + + return err; +} + +struct mod_sub_param +{ + uint8_t *status; + uint16_t elem_addr; + uint16_t *sub_addr; + uint16_t *expect_sub; + uint16_t mod_id; + uint16_t cid; +}; + +static int mod_sub_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct mod_sub_param *param; + uint16_t elem_addr; + uint16_t sub_addr; + uint16_t mod_id; + uint16_t cid; + uint8_t status; + int err = 0; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + if ((buf->len != 7U) && (buf->len != 9U)) + { + LOG_ERR("The message size for the application opcode is incorrect."); + return -EINVAL; + } + + status = net_buf_simple_pull_u8(buf); + elem_addr = net_buf_simple_pull_le16(buf); + sub_addr = net_buf_simple_pull_le16(buf); + + if (buf->len >= 4U) + { + cid = net_buf_simple_pull_le16(buf); + } + else + { + cid = CID_NVAL; + } + + mod_id = net_buf_simple_pull_le16(buf); + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_MOD_SUB_STATUS, ctx->addr, + (void **)¶m)) + { + if (param->elem_addr != elem_addr || param->mod_id != mod_id || + (param->expect_sub && *param->expect_sub != sub_addr) || + param->cid != cid) + { + LOG_WRN("Model Subscription Status parameters did not match"); + err = -ENOENT; + goto done; + } + + if (param->sub_addr) + { + *param->sub_addr = sub_addr; + } + + if (param->status) + { + *param->status = status; + } + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + +done: + if (cli->cb && cli->cb->mod_sub_status) + { + cli->cb->mod_sub_status(cli, ctx->addr, status, elem_addr, sub_addr, + (cid << 16) | mod_id); + } + + return err; +} + +static int mod_sub_list( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + return mod_sub_list_handle(ctx, buf, OP_MOD_SUB_LIST, false); +} + +static int mod_sub_list_vnd( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + return mod_sub_list_handle(ctx, buf, OP_MOD_SUB_LIST_VND, true); +} + +struct hb_sub_param +{ + uint8_t *status; + struct bt_mesh_cfg_cli_hb_sub *sub; +}; + +static int hb_sub_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct hb_sub_param *param; + struct bt_mesh_cfg_cli_hb_sub sub; + uint8_t status; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + status = net_buf_simple_pull_u8(buf); + sub.src = net_buf_simple_pull_le16(buf); + sub.dst = net_buf_simple_pull_le16(buf); + sub.period = net_buf_simple_pull_u8(buf); + sub.count = net_buf_simple_pull_u8(buf); + sub.min = net_buf_simple_pull_u8(buf); + sub.max = net_buf_simple_pull_u8(buf); + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_HEARTBEAT_SUB_STATUS, + ctx->addr, (void **)¶m)) + { + if (param->status) + { + *param->status = status; + } + + if (param->sub) + { + *param->sub = sub; + } + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + + if (cli->cb && cli->cb->hb_sub_status) + { + cli->cb->hb_sub_status(cli, ctx->addr, status, &sub); + } + + return 0; +} + +struct hb_pub_param +{ + uint8_t *status; + struct bt_mesh_cfg_cli_hb_pub *pub; +}; + +static int hb_pub_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct hb_pub_param *param; + uint8_t status; + struct bt_mesh_cfg_cli_hb_pub pub; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + status = net_buf_simple_pull_u8(buf); + pub.dst = net_buf_simple_pull_le16(buf); + pub.count = net_buf_simple_pull_u8(buf); + pub.period = net_buf_simple_pull_u8(buf); + pub.ttl = net_buf_simple_pull_u8(buf); + pub.feat = net_buf_simple_pull_le16(buf); + pub.net_idx = net_buf_simple_pull_le16(buf) & 0xfff; + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_HEARTBEAT_PUB_STATUS, + ctx->addr, (void **)¶m)) + { + if (param->status) + { + *param->status = status; + } + + if (param->pub) + { + *param->pub = pub; + } + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + + if (cli->cb && cli->cb->hb_pub_status) + { + cli->cb->hb_pub_status(cli, ctx->addr, status, &pub); + } + + return 0; +} + +struct node_idt_param +{ + uint8_t *status; + uint16_t net_idx; + uint8_t *identity; +}; + +static int node_identity_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct node_idt_param *param; + uint16_t net_idx; + uint16_t identity; + uint8_t status; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + status = net_buf_simple_pull_u8(buf); + net_idx = net_buf_simple_pull_le16(buf) & 0xfff; + identity = net_buf_simple_pull_u8(buf); + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_NODE_IDENTITY_STATUS, + ctx->addr, (void **)¶m)) + { + if (param && param->status) + { + *param->status = status; + } + + if (param && param->identity) + { + *param->identity = identity; + } + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + + if (cli->cb && cli->cb->node_identity_status) + { + cli->cb->node_identity_status(cli, ctx->addr, status, net_idx, + identity); + } + + return 0; +} + +struct lpn_timeout_param +{ + uint16_t unicast_addr; + int32_t *polltimeout; +}; + +static int lpn_timeout_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct lpn_timeout_param *param; + uint16_t unicast_addr; + int32_t polltimeout; + int err; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + unicast_addr = net_buf_simple_pull_le16(buf); + polltimeout = net_buf_simple_pull_le24(buf); + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_LPN_TIMEOUT_STATUS, + ctx->addr, (void **)¶m)) + { + if (param->unicast_addr != unicast_addr) + { + err = -ENOENT; + goto done; + } + + if (param->polltimeout) + { + *param->polltimeout = polltimeout; + } + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + + err = 0; + +done: + if (cli->cb && cli->cb->lpn_timeout_status) + { + cli->cb->lpn_timeout_status(cli, ctx->addr, unicast_addr, polltimeout); + } + + return err; +} + +const struct bt_mesh_model_op bt_mesh_cfg_cli_op[] = +{ + { + OP_DEV_COMP_DATA_STATUS, + BT_MESH_LEN_MIN(5), + comp_data_status + }, + + { + OP_BEACON_STATUS, + BT_MESH_LEN_EXACT(1), + beacon_status + }, + + { + OP_DEFAULT_TTL_STATUS, + BT_MESH_LEN_EXACT(1), + ttl_status + }, + + { + OP_FRIEND_STATUS, + BT_MESH_LEN_EXACT(1), + friend_status + }, + + { + OP_GATT_PROXY_STATUS, + BT_MESH_LEN_EXACT(1), + gatt_proxy_status + }, + + { + OP_RELAY_STATUS, + BT_MESH_LEN_EXACT(2), + relay_status + }, + + { + OP_NET_TRANSMIT_STATUS, + BT_MESH_LEN_EXACT(1), + net_transmit_status + }, + + { + OP_NET_KEY_STATUS, + BT_MESH_LEN_EXACT(3), + net_key_status + }, + + { + OP_NET_KEY_LIST, + BT_MESH_LEN_MIN(0), + net_key_list + }, + + { + OP_APP_KEY_STATUS, + BT_MESH_LEN_EXACT(4), + app_key_status + }, + + { + OP_APP_KEY_LIST, + BT_MESH_LEN_MIN(3), + app_key_list + }, + + { + OP_MOD_APP_STATUS, + BT_MESH_LEN_MIN(7), + mod_app_status + }, + + { + OP_SIG_MOD_APP_LIST, + BT_MESH_LEN_MIN(5), + mod_app_list + }, + + { + OP_VND_MOD_APP_LIST, + BT_MESH_LEN_MIN(7), + mod_app_list_vnd + }, + + { + OP_MOD_PUB_STATUS, + BT_MESH_LEN_MIN(12), + mod_pub_status + }, + + { + OP_MOD_SUB_STATUS, + BT_MESH_LEN_MIN(7), + mod_sub_status + }, + + { + OP_MOD_SUB_LIST, + BT_MESH_LEN_MIN(5), + mod_sub_list + }, + + { + OP_MOD_SUB_LIST_VND, + BT_MESH_LEN_MIN(7), + mod_sub_list_vnd + }, + + { + OP_HEARTBEAT_SUB_STATUS, + BT_MESH_LEN_EXACT(9), + hb_sub_status + }, + + { + OP_HEARTBEAT_PUB_STATUS, + BT_MESH_LEN_EXACT(10), + hb_pub_status + }, + + { + OP_NODE_RESET_STATUS, + BT_MESH_LEN_EXACT(0), + node_reset_status + }, + + { + OP_NODE_IDENTITY_STATUS, + BT_MESH_LEN_EXACT(4), + node_identity_status + }, + + { + OP_LPN_TIMEOUT_STATUS, + BT_MESH_LEN_EXACT(5), + lpn_timeout_status + }, + + { + OP_KRP_STATUS, + BT_MESH_LEN_EXACT(4), + krp_status + }, + + BT_MESH_MODEL_OP_END, +}; + +static int cfg_cli_init(const struct bt_mesh_model *model) +{ + if (!bt_mesh_model_in_primary(model)) + { + LOG_ERR("Configuration Client only allowed in primary element"); + return -EINVAL; + } + + if (!model->rt->user_data) + { + LOG_ERR("No Configuration Client context provided"); + return -EINVAL; + } + + cli = model->rt->user_data; + cli->model = model; + msg_timeout = CONFIG_BT_MESH_CFG_CLI_TIMEOUT; + + /* Configuration Model security is device-key based and both the local + * and remote keys are allowed to access this model. + */ + + model->keys[0] = BT_MESH_KEY_DEV_ANY; + model->rt->flags |= BT_MESH_MOD_DEVKEY_ONLY; + + bt_mesh_msg_ack_ctx_init(&cli->ack_ctx); + + return 0; +} + +const struct bt_mesh_model_cb bt_mesh_cfg_cli_cb = +{ + .init = cfg_cli_init, +}; + +int bt_mesh_cfg_cli_comp_data_get( + uint16_t net_idx, uint16_t addr, uint8_t page, uint8_t *rsp, + struct net_buf_simple *comp) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_DEV_COMP_DATA_GET, 1); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct comp_data param = + { + .page = rsp, + .comp = comp, + }; + + const struct bt_mesh_msg_rsp_ctx rsp_ctx = + { + .ack = &cli->ack_ctx, + .op = OP_DEV_COMP_DATA_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_DEV_COMP_DATA_GET); + net_buf_simple_add_u8(&msg, page); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, !rsp && + !comp ? NULL : &rsp_ctx); +} + +static int get_state_u8(uint16_t net_idx, + uint16_t addr, uint32_t op, uint32_t rsp, + uint8_t *val) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, DUMMY_2_BYTE_OP, 0); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + const struct bt_mesh_msg_rsp_ctx rsp_ctx = + { + .ack = &cli->ack_ctx, + .op = rsp, + .user_data = val, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, op); + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, + !val ? NULL : &rsp_ctx); +} + +static int set_state_u8( + uint16_t net_idx, uint16_t addr, uint32_t op, uint32_t rsp, + uint8_t new_val, uint8_t *val) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, DUMMY_2_BYTE_OP, 1); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + const struct bt_mesh_msg_rsp_ctx rsp_ctx = + { + .ack = &cli->ack_ctx, + .op = rsp, + .user_data = val, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, op); + net_buf_simple_add_u8(&msg, new_val); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, + !val ? NULL : &rsp_ctx); +} + +int bt_mesh_cfg_cli_beacon_get(uint16_t net_idx, + uint16_t addr, uint8_t *status) +{ + return get_state_u8(net_idx, addr, OP_BEACON_GET, OP_BEACON_STATUS, + status); +} + +int bt_mesh_cfg_cli_krp_get( + uint16_t net_idx, uint16_t addr, uint16_t key_net_idx, uint8_t *status, + uint8_t *phase) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_KRP_GET, 2); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct krp_param param = + { + .status = status, + .phase = phase, + .net_idx = key_net_idx, + }; + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_KRP_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_KRP_GET); + net_buf_simple_add_le16(&msg, key_net_idx); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, !status && + !phase ? NULL : &rsp); +} + +int bt_mesh_cfg_cli_krp_set( + uint16_t net_idx, uint16_t addr, uint16_t key_net_idx, uint8_t transition, + uint8_t *status, uint8_t *phase) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_KRP_SET, 3); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct krp_param param = + { + .status = status, + .phase = phase, + .net_idx = key_net_idx, + }; + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_KRP_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_KRP_SET); + net_buf_simple_add_le16(&msg, key_net_idx); + net_buf_simple_add_u8(&msg, transition); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, !status && + !phase ? NULL : &rsp); +} + +int bt_mesh_cfg_cli_beacon_set(uint16_t net_idx, + uint16_t addr, uint8_t val, uint8_t *status) +{ + return set_state_u8(net_idx, addr, OP_BEACON_SET, OP_BEACON_STATUS, val, + status); +} + +int bt_mesh_cfg_cli_ttl_get(uint16_t net_idx, uint16_t addr, uint8_t *ttl) +{ + return get_state_u8(net_idx, addr, OP_DEFAULT_TTL_GET, + OP_DEFAULT_TTL_STATUS, + ttl); +} + +int bt_mesh_cfg_cli_ttl_set(uint16_t net_idx, + uint16_t addr, uint8_t val, uint8_t *ttl) +{ + return set_state_u8(net_idx, addr, OP_DEFAULT_TTL_SET, + OP_DEFAULT_TTL_STATUS, + val, ttl); +} + +int bt_mesh_cfg_cli_friend_get(uint16_t net_idx, + uint16_t addr, uint8_t *status) +{ + return get_state_u8(net_idx, addr, OP_FRIEND_GET, OP_FRIEND_STATUS, + status); +} + +int bt_mesh_cfg_cli_friend_set(uint16_t net_idx, + uint16_t addr, uint8_t val, uint8_t *status) +{ + return set_state_u8(net_idx, addr, OP_FRIEND_SET, OP_FRIEND_STATUS, val, + status); +} + +int bt_mesh_cfg_cli_gatt_proxy_get(uint16_t net_idx, + uint16_t addr, uint8_t *status) +{ + return get_state_u8(net_idx, addr, OP_GATT_PROXY_GET, OP_GATT_PROXY_STATUS, + status); +} + +int bt_mesh_cfg_cli_gatt_proxy_set(uint16_t net_idx, + uint16_t addr, uint8_t val, + uint8_t *status) +{ + return set_state_u8(net_idx, addr, OP_GATT_PROXY_SET, OP_GATT_PROXY_STATUS, + val, status); +} + +int bt_mesh_cfg_cli_net_transmit_set( + uint16_t net_idx, uint16_t addr, uint8_t val, uint8_t *transmit) +{ + return set_state_u8(net_idx, addr, OP_NET_TRANSMIT_SET, + OP_NET_TRANSMIT_STATUS, val, transmit); +} + +int bt_mesh_cfg_cli_net_transmit_get(uint16_t net_idx, + uint16_t addr, uint8_t *transmit) +{ + return get_state_u8(net_idx, addr, OP_NET_TRANSMIT_GET, + OP_NET_TRANSMIT_STATUS, transmit); +} + +int bt_mesh_cfg_cli_relay_get(uint16_t net_idx, + uint16_t addr, uint8_t *status, + uint8_t *transmit) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_RELAY_GET, 0); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct relay_param param = + { + .status = status, + .transmit = transmit, + }; + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_RELAY_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_RELAY_GET); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, !status && + !transmit ? NULL : &rsp); +} + +int bt_mesh_cfg_cli_relay_set( + uint16_t net_idx, uint16_t addr, uint8_t new_relay, uint8_t new_transmit, + uint8_t *status, uint8_t *transmit) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_RELAY_SET, 2); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct relay_param param = + { + .status = status, + .transmit = transmit, + }; + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_RELAY_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_RELAY_SET); + net_buf_simple_add_u8(&msg, new_relay); + net_buf_simple_add_u8(&msg, new_transmit); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, !status && + !transmit ? NULL : &rsp); +} + +int bt_mesh_cfg_cli_net_key_add( + uint16_t net_idx, uint16_t addr, uint16_t key_net_idx, + const uint8_t net_key[16], uint8_t *status) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_NET_KEY_ADD, 18); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct net_key_param param = + { + .status = status, + .net_idx = key_net_idx, + }; + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_NET_KEY_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_NET_KEY_ADD); + net_buf_simple_add_le16(&msg, key_net_idx); + net_buf_simple_add_mem(&msg, net_key, 16); + + return bt_mesh_msg_ackd_send(cli->model, + &ctx, + &msg, + !status ? NULL : &rsp); +} + +int bt_mesh_cfg_cli_net_key_update( + uint16_t net_idx, uint16_t addr, uint16_t key_net_idx, + const uint8_t net_key[16], uint8_t *status) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_NET_KEY_UPDATE, 18); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct net_key_param param = + { + .status = status, + .net_idx = key_net_idx, + }; + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_NET_KEY_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_NET_KEY_UPDATE); + net_buf_simple_add_le16(&msg, key_net_idx); + net_buf_simple_add_mem(&msg, net_key, 16); + + return bt_mesh_msg_ackd_send(cli->model, + &ctx, + &msg, + !status ? NULL : &rsp); +} + +int bt_mesh_cfg_cli_net_key_get(uint16_t net_idx, + uint16_t addr, uint16_t *keys, + size_t *key_cnt) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_NET_KEY_GET, 0); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct net_key_list_param param = + { + .keys = keys, + .key_cnt = key_cnt, + }; + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_NET_KEY_LIST, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_NET_KEY_GET); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, !keys && + !key_cnt ? NULL : &rsp); +} + +int bt_mesh_cfg_cli_net_key_del( + uint16_t net_idx, uint16_t addr, uint16_t key_net_idx, uint8_t *status) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_NET_KEY_DEL, 2); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct net_key_param param = + { + .status = status, + .net_idx = key_net_idx, + }; + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_NET_KEY_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_NET_KEY_DEL); + net_buf_simple_add_le16(&msg, key_net_idx); + + return bt_mesh_msg_ackd_send(cli->model, + &ctx, + &msg, + !status ? NULL : &rsp); +} + +int bt_mesh_cfg_cli_app_key_add( + uint16_t net_idx, uint16_t addr, uint16_t key_net_idx, + uint16_t key_app_idx, const uint8_t app_key[16], uint8_t *status) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_APP_KEY_ADD, 19); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct app_key_param param = + { + .status = status, + .net_idx = key_net_idx, + .app_idx = key_app_idx, + }; + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_APP_KEY_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_APP_KEY_ADD); + key_idx_pack_pair(&msg, key_net_idx, key_app_idx); + net_buf_simple_add_mem(&msg, app_key, 16); + + return bt_mesh_msg_ackd_send(cli->model, + &ctx, + &msg, + !status ? NULL : &rsp); +} + +int bt_mesh_cfg_cli_app_key_update( + uint16_t net_idx, uint16_t addr, uint16_t key_net_idx, + uint16_t key_app_idx, const uint8_t app_key[16], uint8_t *status) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_APP_KEY_UPDATE, 19); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct app_key_param param = + { + .status = status, + .net_idx = key_net_idx, + .app_idx = key_app_idx, + }; + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_APP_KEY_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_APP_KEY_UPDATE); + key_idx_pack_pair(&msg, key_net_idx, key_app_idx); + net_buf_simple_add_mem(&msg, app_key, 16); + + return bt_mesh_msg_ackd_send(cli->model, + &ctx, + &msg, + !status ? NULL : &rsp); +} + +int bt_mesh_cfg_cli_node_reset(uint16_t net_idx, uint16_t addr, bool *status) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_NODE_RESET, 0); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + + if (status) + { + *status = false; + } + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_NODE_RESET_STATUS, + .user_data = status, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_NODE_RESET); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, !status ? NULL : + &rsp); +} + +int bt_mesh_cfg_cli_app_key_get( + uint16_t net_idx, uint16_t addr, uint16_t key_net_idx, uint8_t *status, + uint16_t *keys, size_t *key_cnt) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_APP_KEY_GET, 2); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct app_key_list_param param = + { + .net_idx = key_net_idx, + .status = status, + .keys = keys, + .key_cnt = key_cnt, + }; + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_APP_KEY_LIST, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_APP_KEY_GET); + net_buf_simple_add_le16(&msg, key_net_idx); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, + !status && (!keys || !key_cnt) ? NULL : &rsp); +} + +int bt_mesh_cfg_cli_app_key_del( + uint16_t net_idx, uint16_t addr, uint16_t key_net_idx, + uint16_t key_app_idx, uint8_t *status) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_APP_KEY_DEL, 3); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct app_key_param param = + { + .status = status, + .net_idx = key_net_idx, + .app_idx = key_app_idx, + }; + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_APP_KEY_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_APP_KEY_DEL); + key_idx_pack_pair(&msg, key_net_idx, key_app_idx); + + return bt_mesh_msg_ackd_send(cli->model, + &ctx, + &msg, + !status ? NULL : &rsp); +} + +static int mod_app_bind( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_app_idx, + uint16_t mod_id, uint16_t cid, uint8_t *status) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_MOD_APP_BIND, 8); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct mod_app_param param = + { + .status = status, + .elem_addr = elem_addr, + .mod_app_idx = mod_app_idx, + .mod_id = mod_id, + .cid = cid, + }; + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_MOD_APP_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_MOD_APP_BIND); + net_buf_simple_add_le16(&msg, elem_addr); + net_buf_simple_add_le16(&msg, mod_app_idx); + + if (cid != CID_NVAL) + { + net_buf_simple_add_le16(&msg, cid); + } + + net_buf_simple_add_le16(&msg, mod_id); + + return bt_mesh_msg_ackd_send(cli->model, + &ctx, + &msg, + !status ? NULL : &rsp); +} + +int bt_mesh_cfg_cli_mod_app_bind( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_app_idx, + uint16_t mod_id, uint8_t *status) +{ + return mod_app_bind(net_idx, + addr, + elem_addr, + mod_app_idx, + mod_id, + CID_NVAL, + + status); +} + +int bt_mesh_cfg_cli_mod_app_bind_vnd( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_app_idx, + uint16_t mod_id, uint16_t cid, uint8_t *status) +{ + if (cid == CID_NVAL) + { + return -EINVAL; + } + + return mod_app_bind(net_idx, addr, elem_addr, mod_app_idx, mod_id, cid, + status); +} + +static int mod_app_unbind( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_app_idx, + uint16_t mod_id, uint16_t cid, uint8_t *status) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_MOD_APP_UNBIND, 8); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct mod_app_param param = + { + .status = status, + .elem_addr = elem_addr, + .mod_app_idx = mod_app_idx, + .mod_id = mod_id, + .cid = cid, + }; + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_MOD_APP_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_MOD_APP_UNBIND); + net_buf_simple_add_le16(&msg, elem_addr); + net_buf_simple_add_le16(&msg, mod_app_idx); + + if (cid != CID_NVAL) + { + net_buf_simple_add_le16(&msg, cid); + } + + net_buf_simple_add_le16(&msg, mod_id); + + return bt_mesh_msg_ackd_send(cli->model, + &ctx, + &msg, + !status ? NULL : &rsp); +} + +int bt_mesh_cfg_cli_mod_app_unbind( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_app_idx, + uint16_t mod_id, uint8_t *status) +{ + return mod_app_unbind(net_idx, addr, elem_addr, mod_app_idx, mod_id, + CID_NVAL, status); +} + +int bt_mesh_cfg_cli_mod_app_unbind_vnd( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_app_idx, + uint16_t mod_id, uint16_t cid, uint8_t *status) +{ + if (cid == CID_NVAL) + { + return -EINVAL; + } + + return mod_app_unbind(net_idx, addr, elem_addr, mod_app_idx, mod_id, cid, + status); +} + +static int mod_member_list_get( + uint32_t op, uint32_t expect_op, uint16_t net_idx, uint16_t addr, + uint16_t elem_addr, uint16_t mod_id, uint16_t cid, uint8_t *status, + uint16_t *apps, size_t *app_cnt) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, DUMMY_2_BYTE_OP, 6); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct mod_member_list_param param = + { + .status = status, + .elem_addr = elem_addr, + .mod_id = mod_id, + .cid = cid, + .members = apps, + .member_cnt = app_cnt, + }; + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = expect_op, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + LOG_DBG("net_idx 0x%04x addr 0x%04x elem_addr 0x%04x", net_idx, addr, + elem_addr); + LOG_DBG("mod_id 0x%04x cid 0x%04x op: %x", mod_id, cid, op); + + bt_mesh_model_msg_init(&msg, op); + net_buf_simple_add_le16(&msg, elem_addr); + + if (cid != CID_NVAL) + { + net_buf_simple_add_le16(&msg, cid); + } + + net_buf_simple_add_le16(&msg, mod_id); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, + !status && (!apps || !app_cnt) ? NULL : &rsp); +} + +int bt_mesh_cfg_cli_mod_app_get( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_id, + uint8_t *status, uint16_t *apps, size_t *app_cnt) +{ + return mod_member_list_get(OP_SIG_MOD_APP_GET, + OP_SIG_MOD_APP_LIST, + net_idx, + + addr, elem_addr, + mod_id, CID_NVAL, status, apps, app_cnt); +} + +int bt_mesh_cfg_cli_mod_app_get_vnd( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_id, + uint16_t cid, uint8_t *status, uint16_t *apps, size_t *app_cnt) +{ + if (cid == CID_NVAL) + { + return -EINVAL; + } + + return mod_member_list_get(OP_VND_MOD_APP_GET, + OP_VND_MOD_APP_LIST, + net_idx, + + addr, elem_addr, + mod_id, cid, status, apps, app_cnt); +} + +static int mod_sub( + uint32_t op, uint16_t net_idx, uint16_t addr, uint16_t elem_addr, + uint16_t sub_addr, uint16_t mod_id, uint16_t cid, uint8_t *status) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, DUMMY_2_BYTE_OP, 8); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct mod_sub_param param = + { + .status = status, + .elem_addr = elem_addr, + .expect_sub = &sub_addr, + .mod_id = mod_id, + .cid = cid, + }; + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_MOD_SUB_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, op); + net_buf_simple_add_le16(&msg, elem_addr); + + if (sub_addr != BT_MESH_ADDR_UNASSIGNED) + { + net_buf_simple_add_le16(&msg, sub_addr); + } + + if (cid != CID_NVAL) + { + net_buf_simple_add_le16(&msg, cid); + } + + net_buf_simple_add_le16(&msg, mod_id); + + return bt_mesh_msg_ackd_send(cli->model, + &ctx, + &msg, + !status ? NULL : &rsp); +} + +int bt_mesh_cfg_cli_mod_sub_add( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t sub_addr, + uint16_t mod_id, uint8_t *status) +{ + if (!BT_MESH_ADDR_IS_GROUP(sub_addr) && + !BT_MESH_ADDR_IS_FIXED_GROUP(sub_addr)) + { + return -EINVAL; + } + + return mod_sub(OP_MOD_SUB_ADD, net_idx, addr, elem_addr, sub_addr, mod_id, + CID_NVAL, status); +} + +int bt_mesh_cfg_cli_mod_sub_add_vnd( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t sub_addr, + uint16_t mod_id, uint16_t cid, uint8_t *status) +{ + if ((!BT_MESH_ADDR_IS_GROUP(sub_addr) && + !BT_MESH_ADDR_IS_FIXED_GROUP(sub_addr)) || + cid == CID_NVAL) + { + return -EINVAL; + } + + return mod_sub(OP_MOD_SUB_ADD, net_idx, addr, elem_addr, sub_addr, mod_id, + cid, status); +} + +int bt_mesh_cfg_cli_mod_sub_del( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t sub_addr, + uint16_t mod_id, uint8_t *status) +{ + if (!BT_MESH_ADDR_IS_GROUP(sub_addr) && + !BT_MESH_ADDR_IS_FIXED_GROUP(sub_addr)) + { + return -EINVAL; + } + + return mod_sub(OP_MOD_SUB_DEL, net_idx, addr, elem_addr, sub_addr, mod_id, + CID_NVAL, status); +} + +int bt_mesh_cfg_cli_mod_sub_del_all( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_id, + uint8_t *status) +{ + return mod_sub(OP_MOD_SUB_DEL_ALL, net_idx, addr, elem_addr, + BT_MESH_ADDR_UNASSIGNED, mod_id, + CID_NVAL, status); +} + +int bt_mesh_cfg_cli_mod_sub_del_vnd( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t sub_addr, + uint16_t mod_id, uint16_t cid, uint8_t *status) +{ + if ((!BT_MESH_ADDR_IS_GROUP(sub_addr) && + !BT_MESH_ADDR_IS_FIXED_GROUP(sub_addr)) || + cid == CID_NVAL) + { + return -EINVAL; + } + + return mod_sub(OP_MOD_SUB_DEL, net_idx, addr, elem_addr, sub_addr, mod_id, + cid, status); +} + +int bt_mesh_cfg_cli_mod_sub_del_all_vnd( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_id, + uint16_t cid, uint8_t *status) +{ + if (cid == CID_NVAL) + { + return -EINVAL; + } + + return mod_sub(OP_MOD_SUB_DEL_ALL, net_idx, addr, elem_addr, + BT_MESH_ADDR_UNASSIGNED, mod_id, cid, + status); +} + +int bt_mesh_cfg_cli_mod_sub_overwrite( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t sub_addr, + uint16_t mod_id, uint8_t *status) +{ + if (!BT_MESH_ADDR_IS_GROUP(sub_addr) && + !BT_MESH_ADDR_IS_FIXED_GROUP(sub_addr)) + { + return -EINVAL; + } + + return mod_sub(OP_MOD_SUB_OVERWRITE, net_idx, addr, elem_addr, sub_addr, + mod_id, CID_NVAL, + status); +} + +int bt_mesh_cfg_cli_mod_sub_overwrite_vnd( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t sub_addr, + uint16_t mod_id, uint16_t cid, uint8_t *status) +{ + if ((!BT_MESH_ADDR_IS_GROUP(sub_addr) && + !BT_MESH_ADDR_IS_FIXED_GROUP(sub_addr)) || + cid == CID_NVAL) + { + return -EINVAL; + } + + return mod_sub(OP_MOD_SUB_OVERWRITE, net_idx, addr, elem_addr, sub_addr, + mod_id, cid, status); +} + +static int mod_sub_va( + uint32_t op, uint16_t net_idx, uint16_t addr, uint16_t elem_addr, + const uint8_t label[16], uint16_t mod_id, uint16_t cid, + uint16_t *virt_addr, uint8_t *status) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, DUMMY_2_BYTE_OP, 22); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct mod_sub_param param = + { + .status = status, + .elem_addr = elem_addr, + .sub_addr = virt_addr, + .mod_id = mod_id, + .cid = cid, + }; + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_MOD_SUB_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + LOG_DBG("net_idx 0x%04x addr 0x%04x elem_addr 0x%04x label %s", net_idx, + addr, elem_addr, + bt_hex(label, 16)); + LOG_DBG("mod_id 0x%04x cid 0x%04x", mod_id, cid); + + bt_mesh_model_msg_init(&msg, op); + net_buf_simple_add_le16(&msg, elem_addr); + net_buf_simple_add_mem(&msg, label, 16); + + if (cid != CID_NVAL) + { + net_buf_simple_add_le16(&msg, cid); + } + + net_buf_simple_add_le16(&msg, mod_id); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, !status && + !virt_addr ? NULL : &rsp); +} + +int bt_mesh_cfg_cli_mod_sub_va_add( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, + const uint8_t label[16], uint16_t mod_id, uint16_t *virt_addr, + uint8_t *status) +{ + return mod_sub_va(OP_MOD_SUB_VA_ADD, net_idx, addr, elem_addr, label, + mod_id, + CID_NVAL, virt_addr, + status); +} + +int bt_mesh_cfg_cli_mod_sub_va_add_vnd( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, + const uint8_t label[16], uint16_t mod_id, uint16_t cid, + uint16_t *virt_addr, uint8_t *status) +{ + if (cid == CID_NVAL) + { + return -EINVAL; + } + + return mod_sub_va(OP_MOD_SUB_VA_ADD, net_idx, addr, elem_addr, label, + mod_id, + cid, virt_addr, + status); +} + +int bt_mesh_cfg_cli_mod_sub_va_del( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, + const uint8_t label[16], uint16_t mod_id, uint16_t *virt_addr, + uint8_t *status) +{ + return mod_sub_va(OP_MOD_SUB_VA_DEL, net_idx, addr, elem_addr, label, + mod_id, + CID_NVAL, virt_addr, + status); +} + +int bt_mesh_cfg_cli_mod_sub_va_del_vnd( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, + const uint8_t label[16], uint16_t mod_id, uint16_t cid, + uint16_t *virt_addr, uint8_t *status) +{ + if (cid == CID_NVAL) + { + return -EINVAL; + } + + return mod_sub_va(OP_MOD_SUB_VA_DEL, net_idx, addr, elem_addr, label, + mod_id, + cid, virt_addr, + status); +} + +int bt_mesh_cfg_cli_mod_sub_va_overwrite( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, + const uint8_t label[16], uint16_t mod_id, uint16_t *virt_addr, + uint8_t *status) +{ + return mod_sub_va(OP_MOD_SUB_VA_OVERWRITE, net_idx, addr, elem_addr, label, + mod_id, CID_NVAL, + virt_addr, status); +} + +int bt_mesh_cfg_cli_mod_sub_va_overwrite_vnd( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, + const uint8_t label[16], uint16_t mod_id, uint16_t cid, + uint16_t *virt_addr, uint8_t *status) +{ + if (cid == CID_NVAL) + { + return -EINVAL; + } + + return mod_sub_va(OP_MOD_SUB_VA_OVERWRITE, net_idx, addr, elem_addr, label, + mod_id, cid, + virt_addr, status); +} + +int bt_mesh_cfg_cli_mod_sub_get( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_id, + uint8_t *status, uint16_t *subs, size_t *sub_cnt) +{ + return mod_member_list_get(OP_MOD_SUB_GET, OP_MOD_SUB_LIST, net_idx, addr, + elem_addr, mod_id, + CID_NVAL, status, subs, sub_cnt); +} + +int bt_mesh_cfg_cli_mod_sub_get_vnd( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_id, + uint16_t cid, uint8_t *status, uint16_t *subs, size_t *sub_cnt) +{ + if (cid == CID_NVAL) + { + return -EINVAL; + } + + return mod_member_list_get(OP_MOD_SUB_GET_VND, + OP_MOD_SUB_LIST_VND, + net_idx, + + addr, elem_addr, + mod_id, cid, status, subs, sub_cnt); +} + +static int mod_pub_get( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_id, + uint16_t cid, struct bt_mesh_cfg_cli_mod_pub *pub, uint8_t *status) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_MOD_PUB_GET, 6); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct mod_pub_param param = + { + .mod_id = mod_id, + .cid = cid, + .elem_addr = elem_addr, + .status = status, + .pub = pub, + }; + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_MOD_PUB_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_MOD_PUB_GET); + + net_buf_simple_add_le16(&msg, elem_addr); + + if (cid != CID_NVAL) + { + net_buf_simple_add_le16(&msg, cid); + } + + net_buf_simple_add_le16(&msg, mod_id); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, !status && + !pub ? NULL : &rsp); +} + +int bt_mesh_cfg_cli_mod_pub_get( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_id, + struct bt_mesh_cfg_cli_mod_pub *pub, uint8_t *status) +{ + return mod_pub_get(net_idx, + addr, + elem_addr, + mod_id, + CID_NVAL, + pub, + status); +} + +int bt_mesh_cfg_cli_mod_pub_get_vnd( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_id, + uint16_t cid, struct bt_mesh_cfg_cli_mod_pub *pub, uint8_t *status) +{ + if (cid == CID_NVAL) + { + return -EINVAL; + } + + return mod_pub_get(net_idx, addr, elem_addr, mod_id, cid, pub, status); +} + +static int mod_pub_set( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_id, + uint16_t cid, struct bt_mesh_cfg_cli_mod_pub *pub, uint8_t *status) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_MOD_PUB_SET, 13); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct mod_pub_param param = + { + .mod_id = mod_id, + .cid = cid, + .elem_addr = elem_addr, + .status = status, + .pub = pub, + }; + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_MOD_PUB_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_MOD_PUB_SET); + + net_buf_simple_add_le16(&msg, elem_addr); + net_buf_simple_add_le16(&msg, pub->addr); + net_buf_simple_add_le16(&msg, (pub->app_idx | (pub->cred_flag << 12))); + net_buf_simple_add_u8(&msg, pub->ttl); + net_buf_simple_add_u8(&msg, pub->period); + net_buf_simple_add_u8(&msg, pub->transmit); + + if (cid != CID_NVAL) + { + net_buf_simple_add_le16(&msg, cid); + } + + net_buf_simple_add_le16(&msg, mod_id); + + return bt_mesh_msg_ackd_send(cli->model, + &ctx, + &msg, + !status ? NULL : &rsp); +} + +static int mod_pub_va_set( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_id, + uint16_t cid, struct bt_mesh_cfg_cli_mod_pub *pub, uint8_t *status) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_MOD_PUB_VA_SET, 27); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct mod_pub_param param = + { + .mod_id = mod_id, + .cid = cid, + .elem_addr = elem_addr, + .status = status, + .pub = pub, + }; + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_MOD_PUB_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + LOG_DBG("app_idx 0x%04x", pub->app_idx); + bt_mesh_model_msg_init(&msg, OP_MOD_PUB_VA_SET); + + net_buf_simple_add_le16(&msg, elem_addr); + net_buf_simple_add_mem(&msg, pub->uuid, 16); + net_buf_simple_add_le16(&msg, (pub->app_idx | (pub->cred_flag << 12))); + net_buf_simple_add_u8(&msg, pub->ttl); + net_buf_simple_add_u8(&msg, pub->period); + net_buf_simple_add_u8(&msg, pub->transmit); + + if (cid != CID_NVAL) + { + net_buf_simple_add_le16(&msg, cid); + } + + net_buf_simple_add_le16(&msg, mod_id); + + return bt_mesh_msg_ackd_send(cli->model, + &ctx, + &msg, + !status ? NULL : &rsp); +} + +int bt_mesh_cfg_cli_mod_pub_set( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_id, + struct bt_mesh_cfg_cli_mod_pub *pub, uint8_t *status) +{ + if (!pub) + { + return -EINVAL; + } + + if (pub->uuid) + { + return mod_pub_va_set(net_idx, addr, elem_addr, mod_id, CID_NVAL, pub, + status); + } + else + { + return mod_pub_set(net_idx, addr, elem_addr, mod_id, CID_NVAL, pub, + status); + } +} + +int bt_mesh_cfg_cli_mod_pub_set_vnd( + uint16_t net_idx, uint16_t addr, uint16_t elem_addr, uint16_t mod_id, + uint16_t cid, struct bt_mesh_cfg_cli_mod_pub *pub, uint8_t *status) +{ + if (!pub) + { + return -EINVAL; + } + + if (cid == CID_NVAL) + { + return -EINVAL; + } + + if (pub->uuid) + { + return mod_pub_va_set(net_idx, addr, elem_addr, mod_id, cid, pub, + status); + } + else + { + return mod_pub_set(net_idx, addr, elem_addr, mod_id, cid, pub, status); + } +} + +int bt_mesh_cfg_cli_hb_sub_set( + uint16_t net_idx, uint16_t addr, struct bt_mesh_cfg_cli_hb_sub *sub, + uint8_t *status) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_HEARTBEAT_SUB_SET, 5); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct hb_sub_param param = + { + .status = status, + .sub = sub, + }; + + if (!sub) + { + return -EINVAL; + } + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_HEARTBEAT_SUB_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_HEARTBEAT_SUB_SET); + net_buf_simple_add_le16(&msg, sub->src); + net_buf_simple_add_le16(&msg, sub->dst); + net_buf_simple_add_u8(&msg, sub->period); + + return bt_mesh_msg_ackd_send(cli->model, + &ctx, + &msg, + !status ? NULL : &rsp); +} + +int bt_mesh_cfg_cli_hb_sub_get( + uint16_t net_idx, uint16_t addr, struct bt_mesh_cfg_cli_hb_sub *sub, + uint8_t *status) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_HEARTBEAT_SUB_GET, 0); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct hb_sub_param param = + { + .status = status, + .sub = sub, + }; + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_HEARTBEAT_SUB_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_HEARTBEAT_SUB_GET); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, !status && + !sub ? NULL : &rsp); +} + +int bt_mesh_cfg_cli_hb_pub_set(uint16_t net_idx, + uint16_t addr, + const struct bt_mesh_cfg_cli_hb_pub *pub, + uint8_t *status) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_HEARTBEAT_PUB_SET, 9); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct hb_pub_param param = + { + .status = status, + }; + + if (!pub) + { + return -EINVAL; + } + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_HEARTBEAT_PUB_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_HEARTBEAT_PUB_SET); + net_buf_simple_add_le16(&msg, pub->dst); + net_buf_simple_add_u8(&msg, pub->count); + net_buf_simple_add_u8(&msg, pub->period); + net_buf_simple_add_u8(&msg, pub->ttl); + net_buf_simple_add_le16(&msg, pub->feat); + net_buf_simple_add_le16(&msg, pub->net_idx); + + return bt_mesh_msg_ackd_send(cli->model, + &ctx, + &msg, + !status ? NULL : &rsp); +} + +int bt_mesh_cfg_cli_hb_pub_get( + uint16_t net_idx, uint16_t addr, struct bt_mesh_cfg_cli_hb_pub *pub, + uint8_t *status) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_HEARTBEAT_PUB_GET, 0); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct hb_pub_param param = + { + .status = status, + .pub = pub, + }; + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_HEARTBEAT_PUB_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_HEARTBEAT_PUB_GET); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, !status && + !pub ? NULL : &rsp); +} + +int bt_mesh_cfg_cli_node_identity_set( + uint16_t net_idx, uint16_t addr, uint16_t key_net_idx, + uint8_t new_identity, uint8_t *status, uint8_t *identity) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_NODE_IDENTITY_SET, 4); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct node_idt_param param = + { + .status = status, + .net_idx = key_net_idx, + .identity = identity, + }; + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_NODE_IDENTITY_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_NODE_IDENTITY_SET); + net_buf_simple_add_le16(&msg, key_net_idx); + net_buf_simple_add_u8(&msg, new_identity); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, !status && + !identity ? NULL : &rsp); +} + +int bt_mesh_cfg_cli_node_identity_get( + uint16_t net_idx, uint16_t addr, uint16_t key_net_idx, uint8_t *status, + uint8_t *identity) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_NODE_IDENTITY_GET, 2); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct node_idt_param param = + { + .status = status, + .net_idx = key_net_idx, + .identity = identity, + }; + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_NODE_IDENTITY_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_NODE_IDENTITY_GET); + net_buf_simple_add_le16(&msg, key_net_idx); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, !status && + !identity ? NULL : &rsp); +} + +int bt_mesh_cfg_cli_lpn_timeout_get( + uint16_t net_idx, uint16_t addr, uint16_t unicast_addr, + int32_t *polltimeout) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_LPN_TIMEOUT_GET, 2); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + struct lpn_timeout_param param = + { + .unicast_addr = unicast_addr, + .polltimeout = polltimeout, + }; + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_LPN_TIMEOUT_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, OP_LPN_TIMEOUT_GET); + net_buf_simple_add_le16(&msg, unicast_addr); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, + !polltimeout ? NULL : &rsp); +} + +int32_t bt_mesh_cfg_cli_timeout_get(void) +{ + return msg_timeout; +} + +void bt_mesh_cfg_cli_timeout_set(int32_t timeout) +{ + msg_timeout = timeout; +} + +int bt_mesh_comp_p0_get(struct bt_mesh_comp_p0 *page, + struct net_buf_simple *buf) +{ + if (buf->len < 10) + { + return -EINVAL; + } + + page->cid = net_buf_simple_pull_le16(buf); + page->pid = net_buf_simple_pull_le16(buf); + page->vid = net_buf_simple_pull_le16(buf); + page->crpl = net_buf_simple_pull_le16(buf); + page->feat = net_buf_simple_pull_le16(buf); + page->_buf = buf; + + return 0; +} + +struct bt_mesh_comp_p0_elem *bt_mesh_comp_p0_elem_pull( + const struct bt_mesh_comp_p0 *page, struct bt_mesh_comp_p0_elem *elem) +{ + size_t modlist_size; + + if (page->_buf->len < 4) + { + LOG_DBG("Buffer is too short"); + return NULL; + } + + elem->loc = net_buf_simple_pull_le16(page->_buf); + elem->nsig = net_buf_simple_pull_u8(page->_buf); + elem->nvnd = net_buf_simple_pull_u8(page->_buf); + + modlist_size = elem->nsig * 2 + elem->nvnd * 4; + + if (page->_buf->len < modlist_size) + { + LOG_DBG("Buffer is shorter than number of claimed models"); + return NULL; + } + + elem->_buf = net_buf_simple_pull_mem(page->_buf, modlist_size); + + return elem; +} + +uint16_t bt_mesh_comp_p0_elem_mod(struct bt_mesh_comp_p0_elem *elem, int idx) +{ + if (idx >= elem->nsig) + { + return 0xffff; + } + + return sys_get_le16(&elem->_buf[idx * 2]); +} + +struct bt_mesh_mod_id_vnd bt_mesh_comp_p0_elem_mod_vnd( + struct bt_mesh_comp_p0_elem *elem, int idx) +{ + if (idx >= elem->nvnd) + { + return (struct bt_mesh_mod_id_vnd) + { + 0xffff, 0xffff + }; + } + + size_t offset = elem->nsig * 2 + idx * 4; + struct bt_mesh_mod_id_vnd mod = + { + .company = sys_get_le16(&elem->_buf[offset]), + .id = sys_get_le16(&elem->_buf[offset + 2]), + }; + + return mod; +} + +struct bt_mesh_comp_p1_elem *bt_mesh_comp_p1_elem_pull( + struct net_buf_simple *buf, struct bt_mesh_comp_p1_elem *elem) +{ + if (buf->len < 4) + { + LOG_DBG("Buffer is too short"); + return NULL; + } + + size_t elem_size = 0; + uint8_t header; + uint8_t ext_item_cnt; + bool fmt; + bool cor_present; + int i; + + elem->nsig = net_buf_simple_pull_u8(buf); + elem->nvnd = net_buf_simple_pull_u8(buf); + for (i = 0; i < elem->nsig + elem->nvnd; i++) + { + if (buf->len < elem_size + 1) + { + LOG_DBG("Buffer is shorter than number of claimed models"); + return NULL; + } + + header = buf->data[elem_size]; + cor_present = COR_PRESENT(header); + fmt = FMT(header); + ext_item_cnt = EXT_ITEM_CNT(header); + + LOG_DBG("header %d, cor_present %d, fmt %d, ext_item_cnt " + "%d", header, cor_present, fmt, + ext_item_cnt); + /* Size of element equals 1 octet (header) + optional 1 octet + * (Correspondence ID, if applies) + size of Extended Model Items (each + * 1 or 2 octet long, depending on format) + */ + + elem_size += (1 + cor_present) + (fmt + 1) * ext_item_cnt; + } + + if (buf->len < elem_size) + { + LOG_DBG("No more elements to pull or missing data"); + return NULL; + } + + net_buf_simple_init_with_data(elem->_buf, + net_buf_simple_pull_mem(buf, elem_size), + elem_size); + return elem; +} + +struct bt_mesh_comp_p1_model_item * +bt_mesh_comp_p1_item_pull(struct bt_mesh_comp_p1_elem *elem, + struct bt_mesh_comp_p1_model_item *item) +{ + if (elem->_buf->len < 1) + { + LOG_ERR("Empty buffer"); + return NULL; + } + + LOG_DBG("N_SIG %d, N_VND %d, buf len=%d:0x%s", elem->nsig, elem->nvnd, + elem->_buf->len, + bt_hex(elem->_buf->data, elem->_buf->len)); + + size_t item_size; + uint8_t header; + + header = net_buf_simple_pull_u8(elem->_buf); + item->cor_present = COR_PRESENT(header); + item->format = FMT(header); + item->ext_item_cnt = EXT_ITEM_CNT(header); + item_size = item->ext_item_cnt * (item->format + 1); + if (item->cor_present) + { + if (elem->_buf->len < 1) + { + LOG_DBG("Coresponding_Present field is claimed but not present"); + return NULL; + } + + item->cor_id = net_buf_simple_pull_u8(elem->_buf); + } + + if (elem->_buf->len < item_size) + { + LOG_DBG("No more elements to pull or missing data"); + return NULL; + } + + net_buf_simple_init_with_data(item->_buf, + net_buf_simple_pull_mem(elem->_buf, + item_size), + item_size); + return item; +} + +static struct bt_mesh_comp_p1_item_short * +comp_p1_pull_item_short(struct bt_mesh_comp_p1_model_item *item, + struct bt_mesh_comp_p1_item_short *ext_item) +{ + if (item->_buf->len < 1) + { + LOG_ERR("Empty buffer"); + return NULL; + } + + LOG_DBG("Correspondence ID %s, format %s, extended items count=%d", + item->cor_present ? "present" : "not present", + item->format ? "long" : "short", + item->ext_item_cnt); + if (item->format == 1 || item->_buf->len != 1) + { + return NULL; + } + + uint8_t item_data = net_buf_simple_pull_u8(item->_buf); + + ext_item->elem_offset = OFFSET(item_data); + ext_item->mod_item_idx = IDX(item_data); + return ext_item; +} + +static struct bt_mesh_comp_p1_item_long * +comp_p1_pull_item_long(struct bt_mesh_comp_p1_model_item *item, + struct bt_mesh_comp_p1_item_long *ext_item) +{ + if (item->_buf->len < 2) + { + LOG_ERR("Missing data, buf len=%d", item->_buf->len); + return NULL; + } + + LOG_DBG("Correspondence ID %s, format %s, extended items count=%d", + item->cor_present ? "present" : "not present", + item->format ? "long" : "short", + item->ext_item_cnt); + if (item->format == 0 || item->_buf->len != 2) + { + return NULL; + } + + ext_item->elem_offset = net_buf_simple_pull_u8(item->_buf); + ext_item->mod_item_idx = net_buf_simple_pull_u8(item->_buf); + + return ext_item; +} + +struct bt_mesh_comp_p1_ext_item * +bt_mesh_comp_p1_pull_ext_item(struct bt_mesh_comp_p1_model_item *item, + struct bt_mesh_comp_p1_ext_item *ext_item) +{ + if (item->_buf->len < 1) + { + LOG_ERR("Empty buffer"); + return NULL; + } + else if (item->_buf->len < 2) + { + LOG_DBG("Item in short format"); + ext_item->type = SHORT; + comp_p1_pull_item_short(item, &ext_item->short_item); + } + else + { + LOG_DBG("Item in long format"); + ext_item->type = LONG; + comp_p1_pull_item_long(item, &ext_item->long_item); + } + + return ext_item; +} + +struct bt_mesh_comp_p2_record *bt_mesh_comp_p2_record_pull( + struct net_buf_simple *buf, struct bt_mesh_comp_p2_record *record) +{ + if (buf->len < 8) + { + LOG_DBG("No more elements to pull or missing data"); + return NULL; + } + + uint8_t elem_offset_cnt; + uint16_t data_len; + + record->id = net_buf_simple_pull_le16(buf); + record->version.x = net_buf_simple_pull_u8(buf); + record->version.y = net_buf_simple_pull_u8(buf); + record->version.z = net_buf_simple_pull_u8(buf); + elem_offset_cnt = net_buf_simple_pull_u8(buf); + if (buf->len < elem_offset_cnt + 2) + { + LOG_WRN("Invalid composition data offset count"); + return NULL; + } + + net_buf_simple_init_with_data(record->elem_buf, + net_buf_simple_pull_mem(buf, elem_offset_cnt), + elem_offset_cnt); + data_len = net_buf_simple_pull_le16(buf); + if (buf->len < data_len) + { + LOG_WRN("Invalid composition data additional data length"); + return NULL; + } + + net_buf_simple_init_with_data(record->data_buf, + net_buf_simple_pull_mem(buf, data_len), + data_len); + return record; +} diff --git a/libs/zblue/mesh/cfg_srv.c b/libs/zblue/mesh/cfg_srv.c new file mode 100644 index 0000000000000..9963946ada2b2 --- /dev/null +++ b/libs/zblue/mesh/cfg_srv.c @@ -0,0 +1,3192 @@ +/**************************************************************************** + * libs/zblue/mesh/cfg_srv.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "common/bt_str.h" + +#include "testing.h" + +#include "mesh.h" +#include "net.h" +#include "app_keys.h" +#include "rpl.h" +#include "lpn.h" +#include "transport.h" +#include "heartbeat.h" +#include "crypto.h" +#include "access.h" +#include "beacon.h" +#include "proxy.h" +#include "foundation.h" +#include "friend.h" +#include "settings.h" +#include "cfg.h" +#include "va.h" + +#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_cfg_srv); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void node_reset_pending_handler(struct k_work *work) +{ + bt_mesh_reset(); +} + +static K_WORK_DEFINE(node_reset_pending, node_reset_pending_handler); + +static int dev_comp_data_get( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + NET_BUF_SIMPLE_DEFINE(sdu, BT_MESH_TX_SDU_MAX); + uint8_t page; + int err; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + page = bt_mesh_comp_parse_page(buf); + LOG_DBG("Preparing Composition data page %d", page); + + bt_mesh_model_msg_init(&sdu, OP_DEV_COMP_DATA_STATUS); + + net_buf_simple_add_u8(&sdu, page); + + if (atomic_test_bit(bt_mesh.flags, BT_MESH_COMP_DIRTY) && page < 128) + { + sdu.size -= BT_MESH_MIC_SHORT; + err = bt_mesh_comp_read(&sdu, page); + sdu.size += BT_MESH_MIC_SHORT; + } + else + { + err = bt_mesh_comp_data_get_page(&sdu, page, 0); + } + + if (err) + { + LOG_ERR("Failed to get CDP%d, err:%d", page, err); + return err; + } + + if (bt_mesh_model_send(model, ctx, &sdu, NULL, NULL)) + { + LOG_ERR("Unable to send Device Composition Status response"); + } + + return err; +} + +static const struct bt_mesh_model *get_model(const struct bt_mesh_elem *elem, + struct net_buf_simple *buf, + bool *vnd) +{ + if (buf->len < 4) + { + uint16_t id; + + id = net_buf_simple_pull_le16(buf); + + LOG_DBG("ID 0x%04x addr 0x%04x", id, elem->rt->addr); + + *vnd = false; + + return bt_mesh_model_find(elem, id); + } + else + { + uint16_t company; + uint16_t id; + + company = net_buf_simple_pull_le16(buf); + id = net_buf_simple_pull_le16(buf); + + LOG_DBG("Company 0x%04x ID 0x%04x addr 0x%04x", company, id, + elem->rt->addr); + + *vnd = true; + + return bt_mesh_model_find_vnd(elem, company, id); + } +} + +static uint8_t _mod_pub_set( + const struct bt_mesh_model *model, uint16_t pub_addr, const uint8_t *uuid, + uint16_t app_idx, uint8_t cred_flag, uint8_t ttl, uint8_t period, + uint8_t retransmit, bool store) +{ + if (!model->pub) + { + return STATUS_NVAL_PUB_PARAM; + } + + if (!IS_ENABLED(CONFIG_BT_MESH_LOW_POWER) && cred_flag) + { + return STATUS_FEAT_NOT_SUPP; + } + + if (!model->pub->update && period) + { + return STATUS_NVAL_PUB_PARAM; + } + + if (pub_addr == BT_MESH_ADDR_UNASSIGNED) + { + if (model->pub->addr == BT_MESH_ADDR_UNASSIGNED) + { + return STATUS_SUCCESS; + } + + model->pub->addr = BT_MESH_ADDR_UNASSIGNED; + model->pub->key = 0U; + model->pub->cred = 0U; + model->pub->ttl = 0U; + model->pub->period = 0U; + model->pub->retransmit = 0U; + model->pub->count = 0U; + model->pub->uuid = NULL; + + if (model->pub->update) + { + /* If this fails, the timer will check pub->addr and + * exit without transmitting. + */ + + (void)k_work_cancel_delayable(&model->pub->timer); + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS) && store) + { + bt_mesh_model_pub_store(model); + } + + return STATUS_SUCCESS; + } + + if (!bt_mesh_app_key_exists(app_idx) || + !bt_mesh_model_has_key(model, app_idx)) + { + return STATUS_INVALID_APPKEY; + } + +#if CONFIG_BT_MESH_LABEL_COUNT > 0 + if (BT_MESH_ADDR_IS_VIRTUAL(model->pub->addr)) + { + (void)bt_mesh_va_del(model->pub->uuid); + } +#endif + + model->pub->addr = pub_addr; + model->pub->key = app_idx; + model->pub->cred = cred_flag; + model->pub->ttl = ttl; + model->pub->period = period; + model->pub->retransmit = retransmit; + model->pub->uuid = uuid; + + if (model->pub->update) + { + int32_t period_ms; + + period_ms = bt_mesh_model_pub_period_get(model); + LOG_DBG("period %u ms", period_ms); + + if (period_ms > 0) + { + k_work_reschedule(&model->pub->timer, MSEC2TICK(period_ms)); + } + else + { + /* If this fails, publication will stop after the + * ongoing set of retransmits. + */ + + (void)k_work_cancel_delayable(&model->pub->timer); + } + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS) && store) + { + bt_mesh_model_pub_store(model); + } + + return STATUS_SUCCESS; +} + +static uint8_t mod_bind(const struct bt_mesh_model *model, uint16_t key_idx) +{ + int i; + + LOG_DBG("model %p key_idx 0x%03x", model, key_idx); + + if (!bt_mesh_app_key_exists(key_idx)) + { + return STATUS_INVALID_APPKEY; + } + + for (i = 0; i < model->keys_cnt; i++) + { + LOG_DBG("model %p id 0x%04x i %d key 0x%03x", model, model->id, i, + model->keys[i]); + + /* Treat existing binding as success */ + + if (model->keys[i] == key_idx) + { + return STATUS_SUCCESS; + } + } + + for (i = 0; i < model->keys_cnt; i++) + { + if (model->keys[i] == BT_MESH_KEY_UNUSED) + { + model->keys[i] = key_idx; + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bt_mesh_model_bind_store(model); + } + + return STATUS_SUCCESS; + } + } + + return STATUS_INSUFF_RESOURCES; +} + +static uint8_t mod_unbind(const struct bt_mesh_model *model, + uint16_t key_idx, bool store) +{ + int i; + + LOG_DBG("model %p key_idx 0x%03x store %u", model, key_idx, store); + + if (!bt_mesh_app_key_exists(key_idx)) + { + return STATUS_INVALID_APPKEY; + } + + for (i = 0; i < model->keys_cnt; i++) + { + if (model->keys[i] != key_idx) + { + continue; + } + + model->keys[i] = BT_MESH_KEY_UNUSED; + + if (IS_ENABLED(CONFIG_BT_SETTINGS) && store) + { + bt_mesh_model_bind_store(model); + } + + if (model->pub && model->pub->key == key_idx) + { + _mod_pub_set(model, BT_MESH_ADDR_UNASSIGNED, NULL, 0, 0, 0, 0, 0, + store); + } + } + + return STATUS_SUCCESS; +} + +static void key_idx_pack_list(struct net_buf_simple *buf, + uint16_t *arr, size_t cnt) +{ + uint16_t *idx = NULL; + + for (int i = 0; i < cnt; i++) + { + if (arr[i] != BT_MESH_KEY_UNUSED) + { + if (!idx) + { + idx = &arr[i]; + continue; + } + + key_idx_pack_pair(buf, *idx, arr[i]); + idx = NULL; + } + } + + if (idx) + { + net_buf_simple_add_le16(buf, *idx); + } +} + +static int send_app_key_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + uint8_t status, uint16_t app_idx, uint16_t net_idx) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_APP_KEY_STATUS, 4); + + bt_mesh_model_msg_init(&msg, OP_APP_KEY_STATUS); + net_buf_simple_add_u8(&msg, status); + key_idx_pack_pair(&msg, net_idx, app_idx); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send App Key Status response"); + } + + return 0; +} + +static int app_key_add( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint16_t key_net_idx; + uint16_t key_app_idx; + uint8_t status; + + key_idx_unpack_pair(buf, &key_net_idx, &key_app_idx); + + LOG_DBG("AppIdx 0x%04x NetIdx 0x%04x", key_app_idx, key_net_idx); + + status = bt_mesh_app_key_add(key_app_idx, key_net_idx, buf->data); + + return send_app_key_status(model, ctx, status, key_app_idx, key_net_idx); +} + +static int app_key_update( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint16_t key_net_idx; + uint16_t key_app_idx; + uint8_t status; + + key_idx_unpack_pair(buf, &key_net_idx, &key_app_idx); + + LOG_DBG("AppIdx 0x%04x NetIdx 0x%04x", key_app_idx, key_net_idx); + + status = bt_mesh_app_key_update(key_app_idx, key_net_idx, buf->data); + LOG_DBG("status 0x%02x", status); + + return send_app_key_status(model, ctx, status, key_app_idx, key_net_idx); +} + +static void mod_app_key_del( + const struct bt_mesh_model *mod, const struct bt_mesh_elem *elem, bool vnd, + bool primary, void *user_data) +{ + uint16_t *app_idx = user_data; + + mod_unbind(mod, *app_idx, true); +} + +static void app_key_evt(struct bt_mesh_app_key *app, + enum bt_mesh_key_evt evt) +{ + if (evt == BT_MESH_KEY_DELETED) + { + bt_mesh_model_foreach(&mod_app_key_del, &app->app_idx); + } +} + +BT_MESH_APP_KEY_CB_DEFINE(app_key_evt); + +static int app_key_del( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint16_t key_net_idx; + uint16_t key_app_idx; + uint8_t status; + + key_idx_unpack_pair(buf, &key_net_idx, &key_app_idx); + + LOG_DBG("AppIdx 0x%04x NetIdx 0x%04x", key_app_idx, key_net_idx); + + status = bt_mesh_app_key_del(key_app_idx, key_net_idx); + + return send_app_key_status(model, ctx, status, key_app_idx, key_net_idx); +} + +/* Index list length: 3 bytes for every pair and 2 bytes for an odd idx */ +#define IDX_LEN(num) (((num) / 2) * 3 + ((num) % 2) * 2) + +static int app_key_get( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, + OP_APP_KEY_LIST, + 3 + IDX_LEN(CONFIG_BT_MESH_APP_KEY_COUNT)); + uint16_t app_idx[CONFIG_BT_MESH_APP_KEY_COUNT]; + uint16_t get_idx; + uint8_t status; + ssize_t count; + + get_idx = net_buf_simple_pull_le16(buf); + if (get_idx > 0xfff) + { + LOG_ERR("Invalid NetKeyIndex 0x%04x", get_idx); + return -EINVAL; + } + + LOG_DBG("idx 0x%04x", get_idx); + + bt_mesh_model_msg_init(&msg, OP_APP_KEY_LIST); + + if (!bt_mesh_subnet_exists(get_idx)) + { + status = STATUS_INVALID_NETKEY; + } + else + { + status = STATUS_SUCCESS; + } + + net_buf_simple_add_u8(&msg, status); + net_buf_simple_add_le16(&msg, get_idx); + + if (status != STATUS_SUCCESS) + { + goto send_status; + } + + count = bt_mesh_app_keys_get(get_idx, app_idx, ARRAY_SIZE(app_idx), 0); + if (count < 0 || count > ARRAY_SIZE(app_idx)) + { + count = ARRAY_SIZE(app_idx); + } + + key_idx_pack_list(&msg, app_idx, count); + +send_status: + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send AppKey List"); + } + + return 0; +} + +static int beacon_get( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_BEACON_STATUS, 1); + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + bt_mesh_model_msg_init(&msg, OP_BEACON_STATUS); + net_buf_simple_add_u8(&msg, bt_mesh_beacon_enabled()); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send Config Beacon Status response"); + } + + return 0; +} + +static int beacon_set( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_BEACON_STATUS, 1); + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + if (buf->data[0] != 0x00 && buf->data[0] != 0x01) + { + LOG_WRN("Invalid Config Beacon value 0x%02x", buf->data[0]); + return -EINVAL; + } + + bt_mesh_beacon_set(buf->data[0]); + + bt_mesh_model_msg_init(&msg, OP_BEACON_STATUS); + net_buf_simple_add_u8(&msg, buf->data[0]); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send Config Beacon Status response"); + } + + return 0; +} + +static int default_ttl_get( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_DEFAULT_TTL_STATUS, 1); + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + bt_mesh_model_msg_init(&msg, OP_DEFAULT_TTL_STATUS); + net_buf_simple_add_u8(&msg, bt_mesh_default_ttl_get()); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send Default TTL Status response"); + } + + return 0; +} + +static int default_ttl_set( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_DEFAULT_TTL_STATUS, 1); + int err; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + err = bt_mesh_default_ttl_set(buf->data[0]); + if (err) + { + LOG_WRN("Prohibited Default TTL value 0x%02x", buf->data[0]); + return err; + } + + bt_mesh_model_msg_init(&msg, OP_DEFAULT_TTL_STATUS); + net_buf_simple_add_u8(&msg, buf->data[0]); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send Default TTL Status response"); + } + + return 0; +} + +static int send_gatt_proxy_status(const struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_GATT_PROXY_STATUS, 1); + + bt_mesh_model_msg_init(&msg, OP_GATT_PROXY_STATUS); + net_buf_simple_add_u8(&msg, bt_mesh_gatt_proxy_get()); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send GATT Proxy Status"); + } + + return 0; +} + +static int gatt_proxy_get( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + return send_gatt_proxy_status(model, ctx); +} + +static int gatt_proxy_set( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + if (buf->data[0] != 0x00 && buf->data[0] != 0x01) + { + LOG_WRN("Invalid GATT Proxy value 0x%02x", buf->data[0]); + return -EINVAL; + } + + (void)bt_mesh_gatt_proxy_set(buf->data[0]); + + /** 4.2.46.1: If the value of the Node Identity state of the node for any + * subnet is 0x01, then the value of the Private Node Identity state shall + * be Disable (0x00). + */ + + if (IS_ENABLED(CONFIG_BT_MESH_PRIV_BEACONS) && buf->data[0]) + { + (void)bt_mesh_priv_gatt_proxy_set(BT_MESH_FEATURE_DISABLED); + } + + return send_gatt_proxy_status(model, ctx); +} + +static int net_transmit_get( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_NET_TRANSMIT_STATUS, 1); + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + bt_mesh_model_msg_init(&msg, OP_NET_TRANSMIT_STATUS); + net_buf_simple_add_u8(&msg, bt_mesh_net_transmit_get()); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send Config Network Transmit Status"); + } + + return 0; +} + +static int net_transmit_set( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_NET_TRANSMIT_STATUS, 1); + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + LOG_DBG("Transmit 0x%02x (count %u interval %ums)", buf->data[0], + BT_MESH_TRANSMIT_COUNT(buf->data[0]), + BT_MESH_TRANSMIT_INT(buf->data[0])); + + bt_mesh_net_transmit_set(buf->data[0]); + + bt_mesh_model_msg_init(&msg, OP_NET_TRANSMIT_STATUS); + net_buf_simple_add_u8(&msg, buf->data[0]); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send Network Transmit Status"); + } + + return 0; +} + +static int relay_get( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_RELAY_STATUS, 2); + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + bt_mesh_model_msg_init(&msg, OP_RELAY_STATUS); + net_buf_simple_add_u8(&msg, bt_mesh_relay_get()); + net_buf_simple_add_u8(&msg, bt_mesh_relay_retransmit_get()); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send Config Relay Status response"); + } + + return 0; +} + +static int relay_set( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_RELAY_STATUS, 2); + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + if (buf->data[0] != 0x00 && buf->data[0] != 0x01) + { + LOG_WRN("Invalid Relay value 0x%02x", buf->data[0]); + return -EINVAL; + } + + (void)bt_mesh_relay_set(buf->data[0], buf->data[1]); + + bt_mesh_model_msg_init(&msg, OP_RELAY_STATUS); + net_buf_simple_add_u8(&msg, bt_mesh_relay_get()); + net_buf_simple_add_u8(&msg, bt_mesh_relay_retransmit_get()); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send Relay Status response"); + } + + return 0; +} + +static int send_mod_pub_status( + const struct bt_mesh_model *cfg_mod, struct bt_mesh_msg_ctx *ctx, + uint16_t elem_addr, uint16_t pub_addr, bool vnd, + const struct bt_mesh_model *mod, uint8_t status, uint8_t *mod_id) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_MOD_PUB_STATUS, 14); + + bt_mesh_model_msg_init(&msg, OP_MOD_PUB_STATUS); + + net_buf_simple_add_u8(&msg, status); + net_buf_simple_add_le16(&msg, elem_addr); + + if (status != STATUS_SUCCESS) + { + (void)memset(net_buf_simple_add(&msg, 7), 0, 7); + } + else + { + uint16_t idx_cred; + + net_buf_simple_add_le16(&msg, pub_addr); + + idx_cred = mod->pub->key | (uint16_t)mod->pub->cred << 12; + net_buf_simple_add_le16(&msg, idx_cred); + net_buf_simple_add_u8(&msg, mod->pub->ttl); + net_buf_simple_add_u8(&msg, mod->pub->period); + net_buf_simple_add_u8(&msg, mod->pub->retransmit); + } + + if (vnd) + { + memcpy(net_buf_simple_add(&msg, 4), mod_id, 4); + } + else + { + memcpy(net_buf_simple_add(&msg, 2), mod_id, 2); + } + + if (bt_mesh_model_send(cfg_mod, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send Model Publication Status"); + } + + return 0; +} + +static int mod_pub_get( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint16_t elem_addr; + uint16_t pub_addr = 0U; + const struct bt_mesh_model *mod; + const struct bt_mesh_elem *elem; + uint8_t *mod_id; + uint8_t status; + bool vnd; + + if ((buf->len != 4U) && (buf->len != 6U)) + { + LOG_ERR("The message size for the application opcode is incorrect."); + return -EMSGSIZE; + } + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) + { + LOG_WRN("Prohibited element address"); + return -EINVAL; + } + + mod_id = buf->data; + + LOG_DBG("elem_addr 0x%04x", elem_addr); + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) + { + mod = NULL; + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) + { + status = STATUS_INVALID_MODEL; + goto send_status; + } + + if (!mod->pub) + { + status = STATUS_NVAL_PUB_PARAM; + goto send_status; + } + + pub_addr = mod->pub->addr; + status = STATUS_SUCCESS; + +send_status: + return send_mod_pub_status(model, ctx, elem_addr, pub_addr, vnd, mod, + status, + mod_id); +} + +static int mod_pub_set( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint8_t retransmit; + uint8_t status; + uint8_t pub_ttl; + uint8_t pub_period; + uint8_t cred_flag; + uint16_t elem_addr; + uint16_t pub_addr; + uint16_t pub_app_idx; + const struct bt_mesh_model *mod; + const struct bt_mesh_elem *elem; + uint8_t *mod_id; + bool vnd; + + if ((buf->len != 11U) && (buf->len != 13U)) + { + LOG_ERR("The message size for the application opcode is incorrect."); + return -EMSGSIZE; + } + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) + { + LOG_WRN("Prohibited element address"); + return -EINVAL; + } + + pub_addr = net_buf_simple_pull_le16(buf); + pub_app_idx = net_buf_simple_pull_le16(buf); + cred_flag = ((pub_app_idx >> 12) & BIT_MASK(1)); + pub_app_idx &= BIT_MASK(12); + + pub_ttl = net_buf_simple_pull_u8(buf); + if (pub_ttl > BT_MESH_TTL_MAX && pub_ttl != BT_MESH_TTL_DEFAULT) + { + LOG_ERR("Invalid TTL value 0x%02x", pub_ttl); + return -EINVAL; + } + + pub_period = net_buf_simple_pull_u8(buf); + retransmit = net_buf_simple_pull_u8(buf); + mod_id = buf->data; + + LOG_DBG("elem_addr 0x%04x pub_addr 0x%04x cred_flag %u", elem_addr, + pub_addr, + cred_flag); + LOG_DBG("pub_app_idx 0x%03x, pub_ttl %u pub_period 0x%02x", pub_app_idx, + pub_ttl, pub_period); + LOG_DBG("retransmit 0x%02x (count %u interval %ums)", retransmit, + BT_MESH_PUB_TRANSMIT_COUNT(retransmit), + BT_MESH_PUB_TRANSMIT_INT(retransmit)); + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) + { + mod = NULL; + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) + { + status = STATUS_INVALID_MODEL; + goto send_status; + } + + status = _mod_pub_set(mod, pub_addr, NULL, pub_app_idx, cred_flag, pub_ttl, + pub_period, + retransmit, true); + +send_status: + return send_mod_pub_status(model, ctx, elem_addr, pub_addr, vnd, mod, + status, + mod_id); +} + +static size_t mod_sub_list_clear(const struct bt_mesh_model *mod) +{ + size_t clear_count; + int i; + + for (i = 0, clear_count = 0; i < mod->groups_cnt; i++) + { + /* mod->groups contains both, group and virtual addrs. Virtual addrs + * deletion will be handled separately. + */ + + if (mod->groups[i] != BT_MESH_ADDR_UNASSIGNED) + { + mod->groups[i] = BT_MESH_ADDR_UNASSIGNED; + clear_count++; + } + } + +#if CONFIG_BT_MESH_LABEL_COUNT > 0 + /* Unref stored labels related to this model */ + + for (i = 0; i < CONFIG_BT_MESH_LABEL_COUNT; i++) + { + if (mod->uuids[i] == NULL) + { + continue; + } + + (void)bt_mesh_va_del(mod->uuids[i]); + mod->uuids[i] = NULL; + + /* No need to increment `clear_count` as `groups` contains virtual + * addresses. + */ + } +#endif + + return clear_count; +} + +static int mod_pub_va_set( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + const struct bt_mesh_va *va; + uint8_t retransmit; + uint8_t status; + uint8_t pub_ttl; + uint8_t pub_period; + uint8_t cred_flag; + uint16_t elem_addr; + uint16_t pub_app_idx; + uint16_t pub_addr = 0U; + const struct bt_mesh_model *mod; + const struct bt_mesh_elem *elem; + const uint8_t *uuid; + uint8_t *mod_id; + bool vnd; + + if ((buf->len != 25U) && (buf->len != 27U)) + { + LOG_ERR("The message size for the application opcode is incorrect."); + return -EMSGSIZE; + } + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) + { + LOG_WRN("Prohibited element address"); + return -EINVAL; + } + + uuid = net_buf_simple_pull_mem(buf, 16); + pub_app_idx = net_buf_simple_pull_le16(buf); + cred_flag = ((pub_app_idx >> 12) & BIT_MASK(1)); + pub_app_idx &= BIT_MASK(12); + pub_ttl = net_buf_simple_pull_u8(buf); + if (pub_ttl > BT_MESH_TTL_MAX && pub_ttl != BT_MESH_TTL_DEFAULT) + { + LOG_ERR("Invalid TTL value 0x%02x", pub_ttl); + return -EINVAL; + } + + pub_period = net_buf_simple_pull_u8(buf); + retransmit = net_buf_simple_pull_u8(buf); + mod_id = buf->data; + + LOG_DBG("elem_addr 0x%04x cred_flag %u", elem_addr, cred_flag); + LOG_DBG("pub_app_idx 0x%03x, pub_ttl %u pub_period 0x%02x", pub_app_idx, + pub_ttl, pub_period); + LOG_DBG("retransmit 0x%02x (count %u interval %ums)", retransmit, + BT_MESH_PUB_TRANSMIT_COUNT(retransmit), + BT_MESH_PUB_TRANSMIT_INT(retransmit)); + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) + { + mod = NULL; + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) + { + status = STATUS_INVALID_MODEL; + goto send_status; + } + + status = bt_mesh_va_add(uuid, &va); + if (status != STATUS_SUCCESS) + { + goto send_status; + } + + pub_addr = va->addr; + + status = _mod_pub_set(mod, pub_addr, va->uuid, pub_app_idx, cred_flag, + pub_ttl, pub_period, + retransmit, true); + if (status != STATUS_SUCCESS) + { + (void)bt_mesh_va_del(va->uuid); + } + +send_status: + return send_mod_pub_status(model, ctx, elem_addr, pub_addr, vnd, mod, + status, + mod_id); +} + +static int send_mod_sub_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + uint8_t status, uint16_t elem_addr, uint16_t sub_addr, uint8_t *mod_id, + bool vnd) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_MOD_SUB_STATUS, 9); + + LOG_DBG("status 0x%02x elem_addr 0x%04x sub_addr " + "0x%04x", status, elem_addr, + sub_addr); + + bt_mesh_model_msg_init(&msg, OP_MOD_SUB_STATUS); + + net_buf_simple_add_u8(&msg, status); + net_buf_simple_add_le16(&msg, elem_addr); + net_buf_simple_add_le16(&msg, sub_addr); + + if (vnd) + { + memcpy(net_buf_simple_add(&msg, 4), mod_id, 4); + } + else + { + memcpy(net_buf_simple_add(&msg, 2), mod_id, 2); + } + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send Model Subscription Status"); + } + + return 0; +} + +static int mod_sub_add( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint16_t elem_addr; + uint16_t sub_addr; + const struct bt_mesh_model *mod; + const struct bt_mesh_elem *elem; + uint8_t *mod_id; + uint8_t status; + uint16_t *entry; + bool vnd; + + if ((buf->len != 6U) && (buf->len != 8U)) + { + LOG_ERR("The message size for the application opcode is incorrect."); + return -EMSGSIZE; + } + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) + { + LOG_WRN("Prohibited element address"); + return -EINVAL; + } + + sub_addr = net_buf_simple_pull_le16(buf); + + LOG_DBG("elem_addr 0x%04x, sub_addr 0x%04x", elem_addr, sub_addr); + + mod_id = buf->data; + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) + { + mod = NULL; + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) + { + status = STATUS_INVALID_MODEL; + goto send_status; + } + + if (!BT_MESH_ADDR_IS_GROUP(sub_addr) && + !BT_MESH_ADDR_IS_FIXED_GROUP(sub_addr)) + { + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + if (bt_mesh_model_find_group(&mod, sub_addr)) + { + /* Tried to add existing subscription */ + + LOG_DBG("found existing subscription"); + status = STATUS_SUCCESS; + goto send_status; + } + + entry = bt_mesh_model_find_group(&mod, BT_MESH_ADDR_UNASSIGNED); + if (!entry) + { + status = STATUS_INSUFF_RESOURCES; + goto send_status; + } + + *entry = sub_addr; + status = STATUS_SUCCESS; + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bt_mesh_model_sub_store(mod); + } + + if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER)) + { + bt_mesh_lpn_group_add(sub_addr); + } + +send_status: + return send_mod_sub_status(model, ctx, status, elem_addr, sub_addr, mod_id, + vnd); +} + +static int mod_sub_del( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint16_t elem_addr; + uint16_t sub_addr; + const struct bt_mesh_model *mod; + const struct bt_mesh_elem *elem; + uint8_t *mod_id; + uint16_t *match; + uint8_t status; + bool vnd; + + if ((buf->len != 6U) && (buf->len != 8U)) + { + LOG_ERR("The message size for the application opcode is incorrect."); + return -EMSGSIZE; + } + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) + { + LOG_WRN("Prohibited element address"); + return -EINVAL; + } + + sub_addr = net_buf_simple_pull_le16(buf); + + LOG_DBG("elem_addr 0x%04x sub_addr 0x%04x", elem_addr, sub_addr); + + mod_id = buf->data; + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) + { + mod = NULL; + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) + { + status = STATUS_INVALID_MODEL; + goto send_status; + } + + if (!BT_MESH_ADDR_IS_GROUP(sub_addr) && + !BT_MESH_ADDR_IS_FIXED_GROUP(sub_addr)) + { + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + /* An attempt to remove a non-existing address shall be treated + * as a success. + */ + + status = STATUS_SUCCESS; + + if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER)) + { + bt_mesh_lpn_group_del(&sub_addr, 1); + } + + match = bt_mesh_model_find_group(&mod, sub_addr); + if (match) + { + *match = BT_MESH_ADDR_UNASSIGNED; + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bt_mesh_model_sub_store(mod); + } + } + +send_status: + return send_mod_sub_status(model, ctx, status, elem_addr, sub_addr, mod_id, + vnd); +} + +static enum bt_mesh_walk mod_sub_clear_visitor( + const struct bt_mesh_model *mod, void *user_data) +{ + if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER)) + { + bt_mesh_lpn_group_del(mod->groups, mod->groups_cnt); + } + + mod_sub_list_clear(mod); + + return BT_MESH_WALK_CONTINUE; +} + +static int mod_sub_overwrite( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint16_t elem_addr; + uint16_t sub_addr; + const struct bt_mesh_model *mod; + const struct bt_mesh_elem *elem; + uint8_t *mod_id; + uint8_t status; + bool vnd; + + if ((buf->len != 6U) && (buf->len != 8U)) + { + LOG_ERR("The message size for the application opcode is incorrect."); + return -EMSGSIZE; + } + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) + { + LOG_WRN("Prohibited element address"); + return -EINVAL; + } + + sub_addr = net_buf_simple_pull_le16(buf); + + LOG_DBG("elem_addr 0x%04x sub_addr 0x%04x", elem_addr, sub_addr); + + mod_id = buf->data; + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) + { + mod = NULL; + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) + { + status = STATUS_INVALID_MODEL; + goto send_status; + } + + if (!BT_MESH_ADDR_IS_GROUP(sub_addr) && + !BT_MESH_ADDR_IS_FIXED_GROUP(sub_addr)) + { + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + if (mod->groups_cnt > 0) + { + bt_mesh_model_extensions_walk(mod, mod_sub_clear_visitor, NULL); + + mod->groups[0] = sub_addr; + status = STATUS_SUCCESS; + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bt_mesh_model_sub_store(mod); + } + + if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER)) + { + bt_mesh_lpn_group_add(sub_addr); + } + } + else + { + status = STATUS_INSUFF_RESOURCES; + } + +send_status: + return send_mod_sub_status(model, ctx, status, elem_addr, sub_addr, mod_id, + vnd); +} + +static int mod_sub_del_all( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + const struct bt_mesh_model *mod; + const struct bt_mesh_elem *elem; + uint16_t elem_addr; + uint8_t *mod_id; + uint8_t status; + bool vnd; + + if ((buf->len != 4U) && (buf->len != 6U)) + { + LOG_ERR("The message size for the application opcode is incorrect."); + return -EMSGSIZE; + } + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) + { + LOG_WRN("Prohibited element address"); + return -EINVAL; + } + + LOG_DBG("elem_addr 0x%04x", elem_addr); + + mod_id = buf->data; + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) + { + mod = NULL; + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) + { + status = STATUS_INVALID_MODEL; + goto send_status; + } + + bt_mesh_model_extensions_walk(mod, mod_sub_clear_visitor, NULL); + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bt_mesh_model_sub_store(mod); + } + + status = STATUS_SUCCESS; + +send_status: + return send_mod_sub_status(model, ctx, status, elem_addr, + BT_MESH_ADDR_UNASSIGNED, mod_id, vnd); +} + +struct mod_sub_list_ctx +{ + uint16_t elem_idx; + struct net_buf_simple *msg; +}; + +static enum bt_mesh_walk mod_sub_list_visitor( + const struct bt_mesh_model *mod, void *ctx) +{ + struct mod_sub_list_ctx *visit = ctx; + int count = 0; + int i; + + if (mod->rt->elem_idx != visit->elem_idx) + { + return BT_MESH_WALK_CONTINUE; + } + + for (i = 0; i < mod->groups_cnt; i++) + { + if (mod->groups[i] == BT_MESH_ADDR_UNASSIGNED) + { + continue; + } + + if (net_buf_simple_tailroom(visit->msg) < 2 + BT_MESH_MIC_SHORT) + { + LOG_WRN("No room for all groups"); + return BT_MESH_WALK_STOP; + } + + net_buf_simple_add_le16(visit->msg, mod->groups[i]); + count++; + } + + LOG_DBG("sublist: model %u:%x: %u groups", mod->rt->elem_idx, mod->id, + count); + + return BT_MESH_WALK_CONTINUE; +} + +static int mod_sub_get( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + NET_BUF_SIMPLE_DEFINE(msg, BT_MESH_TX_SDU_MAX); + struct mod_sub_list_ctx visit_ctx; + const struct bt_mesh_model *mod; + const struct bt_mesh_elem *elem; + uint16_t addr; + uint16_t id; + + addr = net_buf_simple_pull_le16(buf); + if (!BT_MESH_ADDR_IS_UNICAST(addr)) + { + LOG_WRN("Prohibited element address"); + return -EINVAL; + } + + id = net_buf_simple_pull_le16(buf); + + LOG_DBG("addr 0x%04x id 0x%04x", addr, id); + + bt_mesh_model_msg_init(&msg, OP_MOD_SUB_LIST); + + elem = bt_mesh_elem_find(addr); + if (!elem) + { + net_buf_simple_add_u8(&msg, STATUS_INVALID_ADDRESS); + net_buf_simple_add_le16(&msg, addr); + net_buf_simple_add_le16(&msg, id); + goto send_list; + } + + mod = bt_mesh_model_find(elem, id); + if (!mod) + { + net_buf_simple_add_u8(&msg, STATUS_INVALID_MODEL); + net_buf_simple_add_le16(&msg, addr); + net_buf_simple_add_le16(&msg, id); + goto send_list; + } + + net_buf_simple_add_u8(&msg, STATUS_SUCCESS); + + net_buf_simple_add_le16(&msg, addr); + net_buf_simple_add_le16(&msg, id); + + visit_ctx.msg = &msg; + visit_ctx.elem_idx = mod->rt->elem_idx; + bt_mesh_model_extensions_walk(mod, mod_sub_list_visitor, &visit_ctx); + +send_list: + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send Model Subscription List"); + } + + return 0; +} + +static int mod_sub_get_vnd( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + NET_BUF_SIMPLE_DEFINE(msg, BT_MESH_TX_SDU_MAX); + struct mod_sub_list_ctx visit_ctx; + const struct bt_mesh_model *mod; + const struct bt_mesh_elem *elem; + uint16_t company; + uint16_t addr; + uint16_t id; + + addr = net_buf_simple_pull_le16(buf); + if (!BT_MESH_ADDR_IS_UNICAST(addr)) + { + LOG_WRN("Prohibited element address"); + return -EINVAL; + } + + company = net_buf_simple_pull_le16(buf); + id = net_buf_simple_pull_le16(buf); + + LOG_DBG("addr 0x%04x company 0x%04x id 0x%04x", addr, company, id); + + bt_mesh_model_msg_init(&msg, OP_MOD_SUB_LIST_VND); + + elem = bt_mesh_elem_find(addr); + if (!elem) + { + net_buf_simple_add_u8(&msg, STATUS_INVALID_ADDRESS); + net_buf_simple_add_le16(&msg, addr); + net_buf_simple_add_le16(&msg, company); + net_buf_simple_add_le16(&msg, id); + goto send_list; + } + + mod = bt_mesh_model_find_vnd(elem, company, id); + if (!mod) + { + net_buf_simple_add_u8(&msg, STATUS_INVALID_MODEL); + net_buf_simple_add_le16(&msg, addr); + net_buf_simple_add_le16(&msg, company); + net_buf_simple_add_le16(&msg, id); + goto send_list; + } + + net_buf_simple_add_u8(&msg, STATUS_SUCCESS); + + net_buf_simple_add_le16(&msg, addr); + net_buf_simple_add_le16(&msg, company); + net_buf_simple_add_le16(&msg, id); + + visit_ctx.msg = &msg; + visit_ctx.elem_idx = mod->rt->elem_idx; + bt_mesh_model_extensions_walk(mod, mod_sub_list_visitor, &visit_ctx); + +send_list: + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send Vendor Model Subscription List"); + } + + return 0; +} + +static int mod_sub_va_add( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + const struct bt_mesh_va *va; + uint16_t elem_addr; + uint16_t sub_addr = BT_MESH_ADDR_UNASSIGNED; + const struct bt_mesh_model *mod; + const struct bt_mesh_elem *elem; + const uint8_t *uuid; + uint8_t *mod_id; + uint16_t *group_entry; + const uint8_t **label_entry; + uint8_t status; + bool vnd; + + if ((buf->len != 20U) && (buf->len != 22U)) + { + LOG_ERR("The message size for the application opcode is incorrect."); + return -EMSGSIZE; + } + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) + { + LOG_WRN("Prohibited element address"); + return -EINVAL; + } + + uuid = net_buf_simple_pull_mem(buf, 16); + + LOG_DBG("elem_addr 0x%04x", elem_addr); + + mod_id = buf->data; + elem = bt_mesh_elem_find(elem_addr); + if (!elem) + { + mod = NULL; + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) + { + status = STATUS_INVALID_MODEL; + goto send_status; + } + + status = bt_mesh_va_add(uuid, &va); + if (status != STATUS_SUCCESS) + { + goto send_status; + } + + if (bt_mesh_model_find_uuid(&mod, va->uuid)) + { + /* Tried to add existing subscription */ + + status = STATUS_SUCCESS; + (void)bt_mesh_va_del(va->uuid); + sub_addr = va->addr; + goto send_status; + } + + label_entry = bt_mesh_model_find_uuid(&mod, NULL); + if (!label_entry) + { + status = STATUS_INSUFF_RESOURCES; + (void)bt_mesh_va_del(va->uuid); + goto send_status; + } + + group_entry = NULL; + + for (int i = 0; i < mod->groups_cnt; i++) + { + if (mod->groups[i] == BT_MESH_ADDR_UNASSIGNED) + { + group_entry = &mod->groups[i]; + break; + } + } + + /* bt_mesh_model_find_uuid() should find a model where both, uuids and + * groups lists have empty entry. + */ + + if (!group_entry) + { + status = STATUS_INSUFF_RESOURCES; + (void)bt_mesh_va_del(va->uuid); + goto send_status; + } + + *group_entry = va->addr; + *label_entry = va->uuid; + + if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER) && va->ref == 1 && + !bt_mesh_va_collision_check(va->addr)) + { + bt_mesh_lpn_group_add(va->addr); + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bt_mesh_model_sub_store(mod); + } + + status = STATUS_SUCCESS; + sub_addr = va->addr; + +send_status: + return send_mod_sub_status(model, ctx, status, elem_addr, sub_addr, mod_id, + vnd); +} + +static int mod_sub_va_del( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + const struct bt_mesh_va *va; + uint16_t elem_addr; + uint16_t sub_addr = BT_MESH_ADDR_UNASSIGNED; + const struct bt_mesh_model *mod; + const struct bt_mesh_elem *elem; + const uint8_t *uuid; + uint8_t *mod_id; + const uint8_t **label_match; + uint8_t status; + bool vnd; + + if ((buf->len != 20U) && (buf->len != 22U)) + { + LOG_ERR("The message size for the application opcode is incorrect."); + return -EMSGSIZE; + } + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) + { + LOG_WRN("Prohibited element address"); + return -EINVAL; + } + + uuid = net_buf_simple_pull_mem(buf, 16); + + LOG_DBG("elem_addr 0x%04x", elem_addr); + + mod_id = buf->data; + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) + { + mod = NULL; + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) + { + status = STATUS_INVALID_MODEL; + goto send_status; + } + + va = bt_mesh_va_find(uuid); + if (!va) + { + status = STATUS_CANNOT_REMOVE; + goto send_status; + } + + if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER) && va->ref == 1 && + !bt_mesh_va_collision_check(va->addr)) + { + bt_mesh_lpn_group_del(&va->addr, 1); + } + + label_match = bt_mesh_model_find_uuid(&mod, va->uuid); + if (!label_match) + { + status = STATUS_CANNOT_REMOVE; + goto send_status; + } + + for (int i = 0; i < mod->groups_cnt; i++) + { + if (mod->groups[i] == va->addr) + { + mod->groups[i] = BT_MESH_ADDR_UNASSIGNED; + break; + } + } + + *label_match = NULL; + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bt_mesh_model_sub_store(mod); + } + + sub_addr = va->addr; + (void)bt_mesh_va_del(va->uuid); + status = STATUS_SUCCESS; + +send_status: + return send_mod_sub_status(model, ctx, status, elem_addr, sub_addr, mod_id, + vnd); +} + +static int mod_sub_va_overwrite( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + const struct bt_mesh_va *va; + uint16_t elem_addr; + uint16_t sub_addr = BT_MESH_ADDR_UNASSIGNED; + const struct bt_mesh_model *mod; + const struct bt_mesh_elem *elem; + const uint8_t *uuid; + uint8_t *mod_id; + uint8_t status; + bool vnd; + + if ((buf->len != 20U) && (buf->len != 22U)) + { + LOG_ERR("The message size for the application opcode is incorrect."); + return -EMSGSIZE; + } + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) + { + LOG_WRN("Prohibited element address"); + return -EINVAL; + } + + uuid = net_buf_simple_pull_mem(buf, 16); + + LOG_DBG("elem_addr 0x%04x", elem_addr); + + mod_id = buf->data; + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) + { + mod = NULL; + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) + { + status = STATUS_INVALID_MODEL; + goto send_status; + } + + if (CONFIG_BT_MESH_LABEL_COUNT == 0 || mod->groups_cnt == 0) + { + (void)va; + status = STATUS_INSUFF_RESOURCES; + goto send_status; + } + +#if CONFIG_BT_MESH_LABEL_COUNT > 0 + status = bt_mesh_va_add(uuid, &va); + if (status != STATUS_SUCCESS) + { + goto send_status; + } + + bt_mesh_model_extensions_walk(mod, mod_sub_clear_visitor, NULL); + mod->groups[0] = va->addr; + mod->uuids[0] = va->uuid; + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bt_mesh_model_sub_store(mod); + } + + if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER) && va->ref == 1 && + !bt_mesh_va_collision_check(va->addr)) + { + bt_mesh_lpn_group_add(va->addr); + } + + sub_addr = va->addr; +#endif +send_status: + return send_mod_sub_status(model, ctx, status, elem_addr, sub_addr, mod_id, + vnd); +} + +static int send_net_key_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + uint16_t idx, uint8_t status) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_NET_KEY_STATUS, 3); + + bt_mesh_model_msg_init(&msg, OP_NET_KEY_STATUS); + + net_buf_simple_add_u8(&msg, status); + net_buf_simple_add_le16(&msg, idx); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send NetKey Status"); + } + + return 0; +} + +static int net_key_add( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint8_t status; + uint16_t idx; + + idx = net_buf_simple_pull_le16(buf); + if (idx > 0xfff) + { + LOG_ERR("Invalid NetKeyIndex 0x%04x", idx); + return -EINVAL; + } + + LOG_DBG("idx 0x%04x", idx); + + status = bt_mesh_subnet_add(idx, buf->data); + + return send_net_key_status(model, ctx, idx, status); +} + +static int net_key_update( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint8_t status; + uint16_t idx; + + idx = net_buf_simple_pull_le16(buf); + if (idx > 0xfff) + { + LOG_ERR("Invalid NetKeyIndex 0x%04x", idx); + return -EINVAL; + } + + status = bt_mesh_subnet_update(idx, buf->data); + + return send_net_key_status(model, ctx, idx, status); +} + +static int net_key_del( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint16_t del_idx; + + del_idx = net_buf_simple_pull_le16(buf); + if (del_idx > 0xfff) + { + LOG_ERR("Invalid NetKeyIndex 0x%04x", del_idx); + return -EINVAL; + } + + LOG_DBG("idx 0x%04x", del_idx); + + /* The key that the message was encrypted with cannot be removed. + * The NetKey List must contain a minimum of one NetKey. + */ + + if (ctx->net_idx == del_idx) + { + return send_net_key_status(model, ctx, del_idx, STATUS_CANNOT_REMOVE); + } + + (void)bt_mesh_subnet_del(del_idx); + + return send_net_key_status(model, ctx, del_idx, STATUS_SUCCESS); +} + +static int net_key_get( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, + OP_NET_KEY_LIST, + IDX_LEN(CONFIG_BT_MESH_SUBNET_COUNT)); + uint16_t net_idx[CONFIG_BT_MESH_SUBNET_COUNT]; + ssize_t count; + + bt_mesh_model_msg_init(&msg, OP_NET_KEY_LIST); + + count = bt_mesh_subnets_get(net_idx, ARRAY_SIZE(net_idx), 0); + if (count < 0 || count > ARRAY_SIZE(net_idx)) + { + count = ARRAY_SIZE(net_idx); + } + + key_idx_pack_list(&msg, net_idx, count); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send NetKey List"); + } + + return 0; +} + +static int send_node_id_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + uint8_t status, uint16_t net_idx, uint8_t node_id) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_NODE_IDENTITY_STATUS, 4); + + bt_mesh_model_msg_init(&msg, OP_NODE_IDENTITY_STATUS); + net_buf_simple_add_u8(&msg, status); + net_buf_simple_add_le16(&msg, net_idx); + net_buf_simple_add_u8(&msg, node_id); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send Node Identity Status"); + } + + return 0; +} + +static int node_identity_get( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + enum bt_mesh_feat_state node_id; + uint8_t status; + uint16_t idx; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + idx = net_buf_simple_pull_le16(buf); + if (idx > 0xfff) + { + LOG_ERR("Invalid NetKeyIndex 0x%04x", idx); + return -EINVAL; + } + + status = bt_mesh_subnet_node_id_get(idx, &node_id); + + return send_node_id_status(model, ctx, status, idx, node_id); +} + +static int node_identity_set( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint8_t node_id; + uint8_t status; + uint16_t idx; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + idx = net_buf_simple_pull_le16(buf); + if (idx > 0xfff) + { + LOG_WRN("Invalid NetKeyIndex 0x%04x", idx); + return -EINVAL; + } + + node_id = net_buf_simple_pull_u8(buf); + if (node_id != 0x00 && node_id != 0x01) + { + LOG_WRN("Invalid Node ID value 0x%02x", node_id); + return -EINVAL; + } + + status = bt_mesh_subnet_node_id_set(idx, node_id); + if (status == STATUS_INVALID_NETKEY) + { + return send_node_id_status(model, ctx, status, idx, + BT_MESH_NODE_IDENTITY_STOPPED); + } + + if (status == STATUS_FEAT_NOT_SUPP) + { + /* Should return success, even if feature isn't supported: */ + + return send_node_id_status(model, ctx, STATUS_SUCCESS, idx, + BT_MESH_NODE_IDENTITY_NOT_SUPPORTED); + } + + return send_node_id_status(model, ctx, status, idx, node_id); +} + +static void create_mod_app_status( + struct net_buf_simple *msg, const struct bt_mesh_model *mod, bool vnd, + uint16_t elem_addr, uint16_t app_idx, uint8_t status, uint8_t *mod_id) +{ + bt_mesh_model_msg_init(msg, OP_MOD_APP_STATUS); + + net_buf_simple_add_u8(msg, status); + net_buf_simple_add_le16(msg, elem_addr); + net_buf_simple_add_le16(msg, app_idx); + + if (vnd) + { + memcpy(net_buf_simple_add(msg, 4), mod_id, 4); + } + else + { + memcpy(net_buf_simple_add(msg, 2), mod_id, 2); + } +} + +static int mod_app_bind( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_MOD_APP_STATUS, 9); + uint16_t elem_addr; + uint16_t key_app_idx; + const struct bt_mesh_model *mod; + const struct bt_mesh_elem *elem; + uint8_t *mod_id; + uint8_t status; + bool vnd; + + if ((buf->len != 6U) && (buf->len != 8U)) + { + LOG_ERR("The message size for the application opcode is incorrect."); + return -EMSGSIZE; + } + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) + { + LOG_WRN("Prohibited element address"); + return -EINVAL; + } + + key_app_idx = net_buf_simple_pull_le16(buf); + mod_id = buf->data; + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) + { + mod = NULL; + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) + { + status = STATUS_INVALID_MODEL; + goto send_status; + } + + /* Some models only allow device key based access */ + + if (mod->rt->flags & BT_MESH_MOD_DEVKEY_ONLY) + { + LOG_ERR("Client tried to bind AppKey to DevKey based model"); + status = STATUS_CANNOT_BIND; + goto send_status; + } + + status = mod_bind(mod, key_app_idx); + + if (IS_ENABLED(CONFIG_BT_TESTING) && status == STATUS_SUCCESS) + { + bt_mesh_test_model_bound(ctx->addr, mod, key_app_idx); + } + +send_status: + LOG_DBG("status 0x%02x", status); + create_mod_app_status(&msg, mod, vnd, elem_addr, key_app_idx, status, + mod_id); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send Model App Bind Status response"); + } + + return 0; +} + +static int mod_app_unbind( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_MOD_APP_STATUS, 9); + uint16_t elem_addr; + uint16_t key_app_idx; + const struct bt_mesh_model *mod; + const struct bt_mesh_elem *elem; + uint8_t *mod_id; + uint8_t status; + bool vnd; + + if ((buf->len != 6U) && (buf->len != 8U)) + { + LOG_ERR("The message size for the application opcode is incorrect."); + return -EMSGSIZE; + } + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) + { + LOG_WRN("Prohibited element address"); + return -EINVAL; + } + + key_app_idx = net_buf_simple_pull_le16(buf); + mod_id = buf->data; + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) + { + mod = NULL; + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_status; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) + { + status = STATUS_INVALID_MODEL; + goto send_status; + } + + status = mod_unbind(mod, key_app_idx, true); + + if (IS_ENABLED(CONFIG_BT_TESTING) && status == STATUS_SUCCESS) + { + bt_mesh_test_model_unbound(ctx->addr, mod, key_app_idx); + } + +send_status: + LOG_DBG("status 0x%02x", status); + create_mod_app_status(&msg, mod, vnd, elem_addr, key_app_idx, status, + mod_id); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send Model App Unbind Status response"); + } + + return 0; +} + +#define KEY_LIST_LEN (CONFIG_BT_MESH_MODEL_KEY_COUNT * 2) + +static int mod_app_get( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + NET_BUF_SIMPLE_DEFINE(msg, + MAX(BT_MESH_MODEL_BUF_LEN(OP_VND_MOD_APP_LIST, 9 + KEY_LIST_LEN), + BT_MESH_MODEL_BUF_LEN(OP_SIG_MOD_APP_LIST, + 9 + KEY_LIST_LEN))); + const struct bt_mesh_model *mod; + const struct bt_mesh_elem *elem; + uint8_t *mod_id; + uint8_t status; + uint16_t elem_addr; + bool vnd; + + if ((buf->len != 4U) && (buf->len != 6U)) + { + LOG_ERR("The message size for the application opcode is incorrect."); + return -EMSGSIZE; + } + + elem_addr = net_buf_simple_pull_le16(buf); + if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) + { + LOG_WRN("Prohibited element address"); + return -EINVAL; + } + + mod_id = buf->data; + + LOG_DBG("elem_addr 0x%04x", elem_addr); + + elem = bt_mesh_elem_find(elem_addr); + if (!elem) + { + mod = NULL; + vnd = (buf->len == 4U); + status = STATUS_INVALID_ADDRESS; + goto send_list; + } + + mod = get_model(elem, buf, &vnd); + if (!mod) + { + status = STATUS_INVALID_MODEL; + goto send_list; + } + + status = STATUS_SUCCESS; + +send_list: + if (vnd) + { + bt_mesh_model_msg_init(&msg, OP_VND_MOD_APP_LIST); + } + else + { + bt_mesh_model_msg_init(&msg, OP_SIG_MOD_APP_LIST); + } + + net_buf_simple_add_u8(&msg, status); + net_buf_simple_add_le16(&msg, elem_addr); + + if (vnd) + { + net_buf_simple_add_mem(&msg, mod_id, 4); + } + else + { + net_buf_simple_add_mem(&msg, mod_id, 2); + } + + if (mod) + { + key_idx_pack_list(&msg, mod->keys, mod->keys_cnt); + } + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send Model Application List message"); + } + + return 0; +} + +static void reset_send_start(uint16_t duration, int err, void *cb_data) +{ + if (err) + { + LOG_ERR("Sending Node Reset Status failed (err %d)", err); + k_work_submit(&node_reset_pending); + } +} + +static void reset_send_end(int err, void *cb_data) +{ + k_work_submit(&node_reset_pending); +} + +static int node_reset( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + static const struct bt_mesh_send_cb reset_cb = + { + .start = reset_send_start, + .end = reset_send_end, + }; + + BT_MESH_MODEL_BUF_DEFINE(msg, OP_NODE_RESET_STATUS, 0); + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + bt_mesh_model_msg_init(&msg, OP_NODE_RESET_STATUS); + + if (bt_mesh_model_send(model, ctx, &msg, &reset_cb, NULL)) + { + LOG_ERR("Unable to send Node Reset Status"); + } + + return 0; +} + +static int send_friend_status(const struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_FRIEND_STATUS, 1); + + bt_mesh_model_msg_init(&msg, OP_FRIEND_STATUS); + net_buf_simple_add_u8(&msg, bt_mesh_friend_get()); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send Friend Status"); + } + + return 0; +} + +static int friend_get( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + return send_friend_status(model, ctx); +} + +static int friend_set( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + if (buf->data[0] != 0x00 && buf->data[0] != 0x01) + { + LOG_WRN("Invalid Friend value 0x%02x", buf->data[0]); + return -EINVAL; + } + + (void)bt_mesh_friend_set(buf->data[0]); + + return send_friend_status(model, ctx); +} + +static int lpn_timeout_get( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_LPN_TIMEOUT_STATUS, 5); + struct bt_mesh_friend *frnd; + int32_t timeout_steps; + uint16_t lpn_addr; + + lpn_addr = net_buf_simple_pull_le16(buf); + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x lpn_addr 0x%02x", + ctx->net_idx, ctx->app_idx, + ctx->addr, lpn_addr); + + if (!BT_MESH_ADDR_IS_UNICAST(lpn_addr)) + { + LOG_WRN("Invalid LPNAddress; ignoring msg"); + return -EINVAL; + } + + bt_mesh_model_msg_init(&msg, OP_LPN_TIMEOUT_STATUS); + net_buf_simple_add_le16(&msg, lpn_addr); + + if (!IS_ENABLED(CONFIG_BT_MESH_FRIEND)) + { + timeout_steps = 0; + goto send_rsp; + } + + frnd = bt_mesh_friend_find(BT_MESH_KEY_ANY, lpn_addr, true, true); + if (!frnd) + { + timeout_steps = 0; + goto send_rsp; + } + + /* PollTimeout should be reported in steps of 100ms. */ + + timeout_steps = frnd->poll_to / 100; + +send_rsp: + net_buf_simple_add_le24(&msg, timeout_steps); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send LPN PollTimeout Status"); + } + + return 0; +} + +static int send_krp_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + uint16_t idx, uint8_t phase, uint8_t status) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_KRP_STATUS, 4); + + bt_mesh_model_msg_init(&msg, OP_KRP_STATUS); + + net_buf_simple_add_u8(&msg, status); + net_buf_simple_add_le16(&msg, idx); + net_buf_simple_add_u8(&msg, phase); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send Key Refresh State Status"); + } + + return 0; +} + +static int krp_get( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint8_t kr_phase; + uint8_t status; + uint16_t idx; + + idx = net_buf_simple_pull_le16(buf); + if (idx > 0xfff) + { + LOG_ERR("Invalid NetKeyIndex 0x%04x", idx); + return -EINVAL; + } + + LOG_DBG("idx 0x%04x", idx); + + status = bt_mesh_subnet_kr_phase_get(idx, &kr_phase); + + return send_krp_status(model, ctx, idx, kr_phase, status); +} + +static int krp_set( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint8_t phase; + uint8_t status; + uint16_t idx; + + idx = net_buf_simple_pull_le16(buf); + phase = net_buf_simple_pull_u8(buf); + + if (idx > 0xfff) + { + LOG_ERR("Invalid NetKeyIndex 0x%04x", idx); + return -EINVAL; + } + + status = bt_mesh_subnet_kr_phase_set(idx, &phase); + if (status == STATUS_CANNOT_UPDATE) + { + LOG_ERR("Invalid kr phase transition 0x%02x", phase); + return -EINVAL; + } + + return send_krp_status(model, ctx, idx, phase, status); +} + +static uint8_t hb_sub_count_log(uint32_t val) +{ + if (val == 0xffff) + { + return 0xff; + } + else + { + return bt_mesh_hb_log(val); + } +} + +static uint8_t hb_pub_count_log(uint16_t val) +{ + if (!val) + { + return 0x00; + } + else if (val == 0x01) + { + return 0x01; + } + else if (val == 0xfffe) + { + return 0x11; + } + else if (val == 0xffff) + { + return 0xff; + } + else + { + return 32 - __builtin_clz(val - 1) + 1; + } +} + +struct hb_pub_param +{ + uint16_t dst; + uint8_t count_log; + uint8_t period_log; + uint8_t ttl; + uint16_t feat; + uint16_t net_idx; +} __packed; + +static int hb_pub_send_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + uint8_t status, const struct bt_mesh_hb_pub *pub) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_HEARTBEAT_PUB_STATUS, 10); + + LOG_DBG("src 0x%04x status 0x%02x", ctx->addr, status); + + bt_mesh_model_msg_init(&msg, OP_HEARTBEAT_PUB_STATUS); + + net_buf_simple_add_u8(&msg, status); + + net_buf_simple_add_le16(&msg, pub->dst); + if (pub->dst == BT_MESH_ADDR_UNASSIGNED) + { + (void)memset(net_buf_simple_add(&msg, 7), 0, 7); + } + else + { + net_buf_simple_add_u8(&msg, hb_pub_count_log(pub->count)); + net_buf_simple_add_u8(&msg, bt_mesh_hb_log(pub->period)); + net_buf_simple_add_u8(&msg, pub->ttl); + net_buf_simple_add_le16(&msg, pub->feat); + net_buf_simple_add_le16(&msg, pub->net_idx & 0xfff); + } + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send Heartbeat Publication Status"); + } + + return 0; +} + +static int heartbeat_pub_get( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_hb_pub pub; + + LOG_DBG("src 0x%04x", ctx->addr); + + bt_mesh_hb_pub_get(&pub); + + return hb_pub_send_status(model, ctx, STATUS_SUCCESS, &pub); +} + +static int heartbeat_pub_set( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct hb_pub_param *param = (void *)buf->data; + struct bt_mesh_hb_pub pub; + uint8_t status; + + LOG_DBG("src 0x%04x", ctx->addr); + + pub.dst = sys_le16_to_cpu(param->dst); + if (param->count_log == 0x11) + { + /* Special case defined in MshPRFv1.1 Errata 11737 */ + + pub.count = 0xfffe; + } + else + { + pub.count = bt_mesh_hb_pwr2(param->count_log); + } + + if (param->period_log == 0x11) + { + pub.period = 0x10000; + } + else + { + pub.period = bt_mesh_hb_pwr2(param->period_log); + } + + pub.ttl = param->ttl; + pub.feat = sys_le16_to_cpu(param->feat); + pub.net_idx = sys_le16_to_cpu(param->net_idx); + + /* All other address types but virtual are valid */ + + if (BT_MESH_ADDR_IS_VIRTUAL(pub.dst)) + { + status = STATUS_INVALID_ADDRESS; + goto rsp; + } + + if (param->count_log > 0x11 && param->count_log != 0xff) + { + status = STATUS_CANNOT_SET; + goto rsp; + } + + if (param->period_log > 0x11) + { + status = STATUS_CANNOT_SET; + goto rsp; + } + + if (param->ttl > BT_MESH_TTL_MAX && param->ttl != BT_MESH_TTL_DEFAULT) + { + LOG_ERR("Invalid TTL value 0x%02x", param->ttl); + return -EINVAL; + } + + if (pub.net_idx > 0xfff) + { + LOG_ERR("Invalid NetKeyIndex 0x%04x", pub.net_idx); + return -EINVAL; + } + + status = bt_mesh_hb_pub_set(&pub); + +rsp: + return hb_pub_send_status(model, ctx, status, &pub); +} + +static int hb_sub_send_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + const struct bt_mesh_hb_sub *sub) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_HEARTBEAT_SUB_STATUS, 9); + + LOG_DBG("src 0x%04x ", ctx->addr); + + bt_mesh_model_msg_init(&msg, OP_HEARTBEAT_SUB_STATUS); + + net_buf_simple_add_u8(&msg, STATUS_SUCCESS); + net_buf_simple_add_le16(&msg, sub->src); + net_buf_simple_add_le16(&msg, sub->dst); + net_buf_simple_add_u8(&msg, bt_mesh_hb_log(sub->remaining)); + net_buf_simple_add_u8(&msg, hb_sub_count_log(sub->count)); + net_buf_simple_add_u8(&msg, sub->min_hops); + net_buf_simple_add_u8(&msg, sub->max_hops); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send Heartbeat Subscription Status"); + } + + return 0; +} + +static int heartbeat_sub_get( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_hb_sub sub; + + LOG_DBG("src 0x%04x", ctx->addr); + + bt_mesh_hb_sub_get(&sub); + + return hb_sub_send_status(model, ctx, &sub); +} + +static int heartbeat_sub_set( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint8_t period_log; + uint8_t status; + struct bt_mesh_hb_sub sub; + uint16_t sub_src; + uint16_t sub_dst; + uint32_t period; + int err; + + LOG_DBG("src 0x%04x", ctx->addr); + + sub_src = net_buf_simple_pull_le16(buf); + sub_dst = net_buf_simple_pull_le16(buf); + period_log = net_buf_simple_pull_u8(buf); + + LOG_DBG("sub_src 0x%04x sub_dst 0x%04x period 0x%02x", sub_src, sub_dst, + period_log); + + if (period_log > 0x11) + { + LOG_WRN("Prohibited subscription period 0x%02x", period_log); + return -EINVAL; + } + + if (period_log == 0x11) + { + period = 0x10000; + } + else + { + period = bt_mesh_hb_pwr2(period_log); + } + + status = bt_mesh_hb_sub_set(sub_src, sub_dst, period); + if (status != STATUS_SUCCESS) + { + /* All errors are caused by invalid packets, which should be + * ignored. + */ + + return -EINVAL; + } + + bt_mesh_hb_sub_get(&sub); + + /* MESH/NODE/CFG/HBS/BV-01-C expects the MinHops to be 0x7f after + * disabling subscription, but 0x00 for subsequent Get requests. + */ + + if (sub.src == BT_MESH_ADDR_UNASSIGNED || !period_log) + { + sub.min_hops = BT_MESH_TTL_MAX; + } + + err = hb_sub_send_status(model, ctx, &sub); + if (err) + { + return err; + } + + /* MESH/NODE/CFG/HBS/BV-02-C expects us to return previous + * count value and then reset it to 0. + */ + + if (sub.src != BT_MESH_ADDR_UNASSIGNED && + sub.dst != BT_MESH_ADDR_UNASSIGNED && + !period) + { + bt_mesh_hb_sub_reset_count(); + } + + return 0; +} + +const struct bt_mesh_model_op bt_mesh_cfg_srv_op[] = +{ + { + OP_DEV_COMP_DATA_GET, + BT_MESH_LEN_EXACT(1), + dev_comp_data_get + }, + + { + OP_APP_KEY_ADD, + BT_MESH_LEN_EXACT(19), + app_key_add + }, + + { + OP_APP_KEY_UPDATE, + BT_MESH_LEN_EXACT(19), + app_key_update + }, + + { + OP_APP_KEY_DEL, + BT_MESH_LEN_EXACT(3), + app_key_del + }, + + { + OP_APP_KEY_GET, + BT_MESH_LEN_EXACT(2), + app_key_get + }, + + { + OP_BEACON_GET, + BT_MESH_LEN_EXACT(0), + beacon_get + }, + + { + OP_BEACON_SET, + BT_MESH_LEN_EXACT(1), + beacon_set + }, + + { + OP_DEFAULT_TTL_GET, + BT_MESH_LEN_EXACT(0), + default_ttl_get + }, + + { + OP_DEFAULT_TTL_SET, + BT_MESH_LEN_EXACT(1), + default_ttl_set + }, + + { + OP_GATT_PROXY_GET, + BT_MESH_LEN_EXACT(0), + gatt_proxy_get + }, + + { + OP_GATT_PROXY_SET, + BT_MESH_LEN_EXACT(1), + gatt_proxy_set + }, + + { + OP_NET_TRANSMIT_GET, + BT_MESH_LEN_EXACT(0), + net_transmit_get + }, + + { + OP_NET_TRANSMIT_SET, + BT_MESH_LEN_EXACT(1), + net_transmit_set + }, + + { + OP_RELAY_GET, + BT_MESH_LEN_EXACT(0), + relay_get + }, + + { + OP_RELAY_SET, + BT_MESH_LEN_EXACT(2), + relay_set + }, + + { + OP_MOD_PUB_GET, + BT_MESH_LEN_MIN(4), + mod_pub_get + }, + + { + OP_MOD_PUB_SET, + BT_MESH_LEN_MIN(11), + mod_pub_set + }, + + { + OP_MOD_PUB_VA_SET, + BT_MESH_LEN_MIN(25), + mod_pub_va_set + }, + + { + OP_MOD_SUB_ADD, + BT_MESH_LEN_MIN(6), + mod_sub_add + }, + + { + OP_MOD_SUB_VA_ADD, + BT_MESH_LEN_MIN(20), + mod_sub_va_add + }, + + { + OP_MOD_SUB_DEL, + BT_MESH_LEN_MIN(6), + mod_sub_del + }, + + { + OP_MOD_SUB_VA_DEL, + BT_MESH_LEN_MIN(20), + mod_sub_va_del + }, + + { + OP_MOD_SUB_OVERWRITE, + BT_MESH_LEN_MIN(6), + mod_sub_overwrite + }, + + { + OP_MOD_SUB_VA_OVERWRITE, + BT_MESH_LEN_MIN(20), + mod_sub_va_overwrite + }, + + { + OP_MOD_SUB_DEL_ALL, + BT_MESH_LEN_MIN(4), + mod_sub_del_all + }, + + { + OP_MOD_SUB_GET, + BT_MESH_LEN_EXACT(4), + mod_sub_get + }, + + { + OP_MOD_SUB_GET_VND, + BT_MESH_LEN_EXACT(6), + mod_sub_get_vnd + }, + + { + OP_NET_KEY_ADD, + BT_MESH_LEN_EXACT(18), + net_key_add + }, + + { + OP_NET_KEY_UPDATE, + BT_MESH_LEN_EXACT(18), + net_key_update + }, + + { + OP_NET_KEY_DEL, + BT_MESH_LEN_EXACT(2), + net_key_del + }, + + { + OP_NET_KEY_GET, + BT_MESH_LEN_EXACT(0), + net_key_get + }, + + { + OP_NODE_IDENTITY_GET, + BT_MESH_LEN_EXACT(2), + node_identity_get + }, + + { + OP_NODE_IDENTITY_SET, + BT_MESH_LEN_EXACT(3), + node_identity_set + }, + + { + OP_MOD_APP_BIND, + BT_MESH_LEN_MIN(6), + mod_app_bind + }, + + { + OP_MOD_APP_UNBIND, + BT_MESH_LEN_MIN(6), + mod_app_unbind + }, + + { + OP_SIG_MOD_APP_GET, + BT_MESH_LEN_MIN(4), + mod_app_get + }, + + { + OP_VND_MOD_APP_GET, + BT_MESH_LEN_MIN(6), + mod_app_get + }, + + { + OP_NODE_RESET, + BT_MESH_LEN_EXACT(0), + node_reset + }, + + { + OP_FRIEND_GET, + BT_MESH_LEN_EXACT(0), + friend_get + }, + + { + OP_FRIEND_SET, + BT_MESH_LEN_EXACT(1), + friend_set + }, + + { + OP_LPN_TIMEOUT_GET, + BT_MESH_LEN_EXACT(2), + lpn_timeout_get + }, + + { + OP_KRP_GET, + BT_MESH_LEN_EXACT(2), + krp_get + }, + + { + OP_KRP_SET, + BT_MESH_LEN_EXACT(3), + krp_set + }, + + { + OP_HEARTBEAT_PUB_GET, + BT_MESH_LEN_EXACT(0), + heartbeat_pub_get + }, + + { + OP_HEARTBEAT_PUB_SET, + BT_MESH_LEN_EXACT(9), + heartbeat_pub_set + }, + + { + OP_HEARTBEAT_SUB_GET, + BT_MESH_LEN_EXACT(0), + heartbeat_sub_get + }, + + { + OP_HEARTBEAT_SUB_SET, + BT_MESH_LEN_EXACT(5), + heartbeat_sub_set + }, + + BT_MESH_MODEL_OP_END, +}; + +static int cfg_srv_init(const struct bt_mesh_model *model) +{ + if (!bt_mesh_model_in_primary(model)) + { + LOG_ERR("Configuration Server only allowed in primary element"); + return -EINVAL; + } + + /* Configuration Model security is device-key based and only the local + * device-key is allowed to access this model. + */ + + model->keys[0] = BT_MESH_KEY_DEV_LOCAL; + model->rt->flags |= BT_MESH_MOD_DEVKEY_ONLY; + + return 0; +} + +const struct bt_mesh_model_cb bt_mesh_cfg_srv_cb = +{ + .init = cfg_srv_init, +}; + +static void mod_reset( + const struct bt_mesh_model *mod, const struct bt_mesh_elem *elem, bool vnd, + bool primary, void *user_data) +{ + size_t clear_count; + + /* Clear model state that isn't otherwise cleared. E.g. AppKey + * binding and model publication is cleared as a consequence of removing + * all app keys, however model subscription and user data clearing must be + * taken care of here. + */ + + clear_count = mod_sub_list_clear(mod); + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + if (clear_count) + { + bt_mesh_model_sub_store(mod); + } + } + + if (mod->cb && mod->cb->reset) + { + mod->cb->reset(mod); + } +} + +void bt_mesh_model_reset(void) +{ + bt_mesh_model_foreach(mod_reset, NULL); +} diff --git a/libs/zblue/mesh/crypto.c b/libs/zblue/mesh/crypto.c new file mode 100644 index 0000000000000..19d08fa156d32 --- /dev/null +++ b/libs/zblue/mesh/crypto.c @@ -0,0 +1,1007 @@ +/**************************************************************************** + * libs/zblue/mesh/crypto.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include "common/bt_str.h" + +#include "mesh.h" +#include "crypto.h" + +#define LOG_LEVEL CONFIG_BT_MESH_CRYPTO_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_crypto); + +#define NET_MIC_LEN(pdu) (((pdu)[1] & 0x80) ? 8 : 4) +#define APP_MIC_LEN(aszmic) ((aszmic) ? 8 : 4) + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int bt_mesh_aes_cmac_one_raw_key( + const uint8_t key[16], const void *m, size_t len, uint8_t mac[16]) +{ + struct bt_mesh_sg sg = + { + .data = m, .len = len + }; + + return bt_mesh_aes_cmac_raw_key(key, &sg, 1, mac); +} + +static int bt_mesh_aes_cmac_one_mesh_key( + const struct bt_mesh_key *key, const void *m, size_t len, uint8_t mac[16]) +{ + struct bt_mesh_sg sg = + { + .data = m, .len = len + }; + + return bt_mesh_aes_cmac_mesh_key(key, &sg, 1, mac); +} + +static int bt_mesh_sha256_hmac_one_raw_key( + const uint8_t key[32], const void *m, size_t len, uint8_t mac[32]) +{ + struct bt_mesh_sg sg = + { + .data = m, .len = len + }; + + return bt_mesh_sha256_hmac_raw_key(key, &sg, 1, mac); +} + +int bt_mesh_s1(const void *m, size_t m_len, uint8_t salt[16]) +{ + const uint8_t zero[16] = + { + 0 + }; + + return bt_mesh_aes_cmac_one_raw_key(zero, m, m_len, salt); +} + +int bt_mesh_s2(const void *m, size_t m_len, uint8_t salt[32]) +{ + const uint8_t zero[32] = + { + 0 + }; + + return bt_mesh_sha256_hmac_one_raw_key(zero, m, m_len, salt); +} + +int bt_mesh_k1(const uint8_t *ikm, + size_t ikm_len, const uint8_t salt[16], const char *info, + uint8_t okm[16]) +{ + int err; + + err = bt_mesh_aes_cmac_one_raw_key(salt, ikm, ikm_len, okm); + if (err < 0) + { + return err; + } + + return bt_mesh_aes_cmac_one_raw_key(okm, info, strlen(info), okm); +} + +int bt_mesh_k2( + const uint8_t n[16], const uint8_t *p, size_t p_len, uint8_t net_id[1], + struct bt_mesh_key *enc_key, struct bt_mesh_key *priv_key) +{ + struct bt_mesh_sg sg[3]; + uint8_t salt[16]; + uint8_t out[16]; + uint8_t t[16]; + uint8_t pad; + int err; + + LOG_DBG("n %s", bt_hex(n, 16)); + LOG_DBG("p %s", bt_hex(p, p_len)); + + err = bt_mesh_s1_str("smk2", salt); + if (err) + { + return err; + } + + err = bt_mesh_aes_cmac_one_raw_key(salt, n, 16, t); + if (err) + { + return err; + } + + pad = 0x01; + + sg[0].data = NULL; + sg[0].len = 0; + sg[1].data = p; + sg[1].len = p_len; + sg[2].data = &pad; + sg[2].len = sizeof(pad); + + err = bt_mesh_aes_cmac_raw_key(t, sg, ARRAY_SIZE(sg), out); + if (err) + { + return err; + } + + net_id[0] = out[15] & 0x7f; + + sg[0].data = out; + sg[0].len = sizeof(out); + pad = 0x02; + + err = bt_mesh_aes_cmac_raw_key(t, sg, ARRAY_SIZE(sg), out); + if (err) + { + return err; + } + + err = bt_mesh_key_import(BT_MESH_KEY_TYPE_CCM, out, enc_key); + if (err) + { + return err; + } + + pad = 0x03; + + err = bt_mesh_aes_cmac_raw_key(t, sg, ARRAY_SIZE(sg), out); + if (err) + { + return err; + } + + err = bt_mesh_key_import(BT_MESH_KEY_TYPE_ECB, out, priv_key); + if (err) + { + return err; + } + + LOG_DBG("NID 0x%02x enc_key %s", net_id[0], bt_hex(enc_key, + sizeof(struct bt_mesh_key))); + LOG_DBG("priv_key %s", bt_hex(priv_key, sizeof(struct bt_mesh_key))); + + return 0; +} + +int bt_mesh_k3(const uint8_t n[16], uint8_t out[8]) +{ + uint8_t id64[] = + { + 'i', 'd', '6', '4', 0x01 + }; + + uint8_t tmp[16]; + uint8_t t[16]; + int err; + + err = bt_mesh_s1_str("smk3", tmp); + if (err) + { + return err; + } + + err = bt_mesh_aes_cmac_one_raw_key(tmp, n, 16, t); + if (err) + { + return err; + } + + err = bt_mesh_aes_cmac_one_raw_key(t, id64, sizeof(id64), tmp); + if (err) + { + return err; + } + + memcpy(out, tmp + 8, 8); + + return 0; +} + +int bt_mesh_k4(const uint8_t n[16], uint8_t out[1]) +{ + uint8_t id6[] = + { + 'i', 'd', '6', 0x01 + }; + + uint8_t tmp[16]; + uint8_t t[16]; + int err; + + err = bt_mesh_s1_str("smk4", tmp); + if (err) + { + return err; + } + + err = bt_mesh_aes_cmac_one_raw_key(tmp, n, 16, t); + if (err) + { + return err; + } + + err = bt_mesh_aes_cmac_one_raw_key(t, id6, sizeof(id6), tmp); + if (err) + { + return err; + } + + out[0] = tmp[15] & BIT_MASK(6); + + return 0; +} + +int bt_mesh_k5( + const uint8_t *n, size_t n_len, const uint8_t salt[32], const char *p, + uint8_t out[32]) +{ + uint8_t t[32]; + int err; + + err = bt_mesh_sha256_hmac_one_raw_key(salt, n, n_len, t); + if (err) + { + return err; + } + + err = bt_mesh_sha256_hmac_one_raw_key(t, p, strlen(p), out); + if (err) + { + return err; + } + + return 0; +} + +int bt_mesh_id128( + const uint8_t n[16], const char *s, enum bt_mesh_key_type type, + struct bt_mesh_key *out) +{ + const char *id128 = "id128\x01"; + uint8_t salt[16]; + uint8_t k1_out[16]; + int err; + + err = bt_mesh_s1_str(s, salt); + if (err) + { + return err; + } + + err = bt_mesh_k1(n, 16, salt, id128, k1_out); + if (err) + { + return err; + } + + err = bt_mesh_key_import(type, k1_out, out); + if (err) + { + return err; + } + + return 0; +} + +int bt_mesh_prov_nonce(const uint8_t dhkey[32], + const uint8_t prov_salt[16], uint8_t nonce[13]) +{ + uint8_t tmp[16]; + int err; + + err = bt_mesh_k1(dhkey, 32, prov_salt, "prsn", tmp); + if (!err) + { + memcpy(nonce, tmp + 3, 13); + } + + return err; +} + +int bt_mesh_session_key(const uint8_t dhkey[32], const uint8_t prov_salt[16], + struct bt_mesh_key *session_key) +{ + uint8_t raw_key[16]; + int err; + + err = bt_mesh_k1(dhkey, 32, prov_salt, "prsk", raw_key); + + if (!err) + { + LOG_DBG("SessionKey: %s", bt_hex(raw_key, 16)); + err = bt_mesh_key_import(BT_MESH_KEY_TYPE_CCM, raw_key, session_key); + } + + return err; +} + +int bt_mesh_dev_key(const uint8_t dhkey[32], + const uint8_t prov_salt[16], uint8_t dev_key[16]) +{ + int err; + + err = bt_mesh_k1(dhkey, 32, prov_salt, "prdk", dev_key); + + if (!err) + { + LOG_DBG("DevKey: %s", bt_hex(dev_key, 16)); + } + + return err; +} + +static void create_proxy_nonce(uint8_t nonce[13], + const uint8_t *pdu, uint32_t iv_index) +{ + memset(nonce, 0, 13); + + /* Nonce Type */ + + nonce[0] = 0x03; + + /* Sequence Number */ + + nonce[2] = pdu[2]; + nonce[3] = pdu[3]; + nonce[4] = pdu[4]; + + /* Source Address */ + + nonce[5] = pdu[5]; + nonce[6] = pdu[6]; + + /* IV Index */ + + sys_put_be32(iv_index, &nonce[9]); +} + +static void create_proxy_sol_nonce(uint8_t nonce[13], const uint8_t *pdu) +{ + memset(nonce, 0, 13); + + /* Nonce Type */ + + nonce[0] = 0x04; + + /* Sequence Number */ + + nonce[2] = pdu[2]; + nonce[3] = pdu[3]; + nonce[4] = pdu[4]; + + /* Source Address */ + + nonce[5] = pdu[5]; + nonce[6] = pdu[6]; +} + +static void create_net_nonce(uint8_t nonce[13], + const uint8_t *pdu, uint32_t iv_index) +{ + /* Nonce Type */ + + nonce[0] = 0x00; + + /* FRND + TTL */ + + nonce[1] = pdu[1]; + + /* Sequence Number */ + + nonce[2] = pdu[2]; + nonce[3] = pdu[3]; + nonce[4] = pdu[4]; + + /* Source Address */ + + nonce[5] = pdu[5]; + nonce[6] = pdu[6]; + + /* Pad */ + + nonce[7] = 0U; + nonce[8] = 0U; + + /* IV Index */ + + sys_put_be32(iv_index, &nonce[9]); +} + +int bt_mesh_net_obfuscate(uint8_t *pdu, + uint32_t iv_index, + const struct bt_mesh_key *privacy_key) +{ + uint8_t priv_rand[16] = + { + 0x00, 0x00, 0x00, 0x00, 0x00, + }; + + uint8_t tmp[16]; + int err; + int i; + + LOG_DBG("IVIndex %u, PrivacyKey %s", iv_index, bt_hex(privacy_key, + sizeof(struct bt_mesh_key))); + + sys_put_be32(iv_index, &priv_rand[5]); + memcpy(&priv_rand[9], &pdu[7], 7); + + LOG_DBG("PrivacyRandom %s", bt_hex(priv_rand, 16)); + + err = bt_mesh_encrypt(privacy_key, priv_rand, tmp); + if (err) + { + return err; + } + + for (i = 0; i < 6; i++) + { + pdu[1 + i] ^= tmp[i]; + } + + return 0; +} + +int bt_mesh_net_encrypt( + const struct bt_mesh_key *key, struct net_buf_simple *buf, + uint32_t iv_index, enum bt_mesh_nonce_type type) +{ + uint8_t mic_len = NET_MIC_LEN(buf->data); + uint8_t nonce[13]; + int err; + + LOG_DBG("IVIndex %u EncKey %s mic_len %u", iv_index, bt_hex(key, + sizeof(struct bt_mesh_key)), + mic_len); + LOG_DBG("PDU (len %u) %s", buf->len, bt_hex(buf->data, buf->len)); + + if (IS_ENABLED(CONFIG_BT_MESH_PROXY) && type == BT_MESH_NONCE_PROXY) + { + create_proxy_nonce(nonce, buf->data, iv_index); + } + else if (IS_ENABLED(CONFIG_BT_MESH_OD_PRIV_PROXY_SRV) && + type == BT_MESH_NONCE_SOLICITATION) + { + create_proxy_sol_nonce(nonce, buf->data); + } + else + { + create_net_nonce(nonce, buf->data, iv_index); + } + + LOG_DBG("Nonce %s", bt_hex(nonce, 13)); + + err = bt_mesh_ccm_encrypt(key, nonce, &buf->data[7], buf->len - 7, NULL, 0, + &buf->data[7], + mic_len); + if (!err) + { + net_buf_simple_add(buf, mic_len); + } + + return err; +} + +int bt_mesh_net_decrypt( + const struct bt_mesh_key *key, struct net_buf_simple *buf, + uint32_t iv_index, enum bt_mesh_nonce_type type) +{ + uint8_t mic_len = NET_MIC_LEN(buf->data); + uint8_t nonce[13]; + + LOG_DBG("PDU (%u bytes) %s", buf->len, bt_hex(buf->data, buf->len)); + LOG_DBG("iv_index %u, key %s mic_len %u", iv_index, bt_hex(key, + sizeof(struct bt_mesh_key)), + mic_len); + + if (IS_ENABLED(CONFIG_BT_MESH_PROXY) && type == BT_MESH_NONCE_PROXY) + { + create_proxy_nonce(nonce, buf->data, iv_index); + } + else if (IS_ENABLED(CONFIG_BT_MESH_SOLICITATION) && + type == BT_MESH_NONCE_SOLICITATION) + { + create_proxy_sol_nonce(nonce, buf->data); + } + else + { + create_net_nonce(nonce, buf->data, iv_index); + } + + LOG_DBG("Nonce %s", bt_hex(nonce, 13)); + + buf->len -= mic_len; + + return bt_mesh_ccm_decrypt(key, + nonce, + &buf->data[7], + buf->len - 7, + NULL, + 0, + + &buf->data[7], + mic_len); +} + +static void create_app_nonce(uint8_t nonce[13], + const struct bt_mesh_app_crypto_ctx *ctx) +{ + if (ctx->dev_key) + { + nonce[0] = 0x02; + } + else + { + nonce[0] = 0x01; + } + + sys_put_be32((ctx->seq_num | ((uint32_t)ctx->aszmic << 31)), &nonce[1]); + + sys_put_be16(ctx->src, &nonce[5]); + sys_put_be16(ctx->dst, &nonce[7]); + + sys_put_be32(ctx->iv_index, &nonce[9]); +} + +int bt_mesh_app_encrypt( + const struct bt_mesh_key *key, const struct bt_mesh_app_crypto_ctx *ctx, + struct net_buf_simple *buf) +{ + uint8_t nonce[13]; + int err; + + LOG_DBG("AppKey %s", bt_hex(key, sizeof(struct bt_mesh_key))); + LOG_DBG("dev_key %u src 0x%04x dst 0x%04x", ctx->dev_key, ctx->src, + ctx->dst); + LOG_DBG("seq_num 0x%08x iv_index 0x%08x", ctx->seq_num, ctx->iv_index); + LOG_DBG("Clear: %s", bt_hex(buf->data, buf->len)); + + create_app_nonce(nonce, ctx); + + LOG_DBG("Nonce %s", bt_hex(nonce, 13)); + + err = bt_mesh_ccm_encrypt(key, nonce, buf->data, buf->len, ctx->ad, + ctx->ad ? 16 : 0, buf->data, + APP_MIC_LEN(ctx->aszmic)); + if (!err) + { + net_buf_simple_add(buf, APP_MIC_LEN(ctx->aszmic)); + LOG_DBG("Encr: %s", bt_hex(buf->data, buf->len)); + } + + return err; +} + +int bt_mesh_app_decrypt( + const struct bt_mesh_key *key, const struct bt_mesh_app_crypto_ctx *ctx, + struct net_buf_simple *buf, struct net_buf_simple *out) +{ + uint8_t nonce[13]; + int err; + + LOG_DBG("EncData (len %u) %s", buf->len, bt_hex(buf->data, buf->len)); + + create_app_nonce(nonce, ctx); + + LOG_DBG("AppKey %s", bt_hex(key, sizeof(struct bt_mesh_key))); + LOG_DBG("Nonce %s", bt_hex(nonce, 13)); + + err = bt_mesh_ccm_decrypt(key, nonce, buf->data, buf->len, ctx->ad, + ctx->ad ? 16 : 0, out->data, + APP_MIC_LEN(ctx->aszmic)); + if (!err) + { + net_buf_simple_add(out, buf->len); + } + + return err; +} + +/* reversed, 8-bit, poly=0x07 */ + +static const uint8_t crc_table[256] = +{ + 0x00, 0x91, 0xe3, 0x72, 0x07, 0x96, 0xe4, 0x75, 0x0e, 0x9f, 0xed, + 0x7c, 0x09, 0x98, 0xea, 0x7b, 0x1c, 0x8d, 0xff, 0x6e, 0x1b, 0x8a, + 0xf8, 0x69, 0x12, 0x83, 0xf1, 0x60, 0x15, 0x84, 0xf6, 0x67, + + 0x38, 0xa9, 0xdb, 0x4a, 0x3f, 0xae, 0xdc, 0x4d, 0x36, 0xa7, 0xd5, + 0x44, 0x31, 0xa0, 0xd2, 0x43, 0x24, 0xb5, 0xc7, 0x56, 0x23, 0xb2, + 0xc0, 0x51, 0x2a, 0xbb, 0xc9, 0x58, 0x2d, 0xbc, 0xce, 0x5f, + + 0x70, 0xe1, 0x93, 0x02, 0x77, 0xe6, 0x94, 0x05, 0x7e, 0xef, 0x9d, + 0x0c, 0x79, 0xe8, 0x9a, 0x0b, 0x6c, 0xfd, 0x8f, 0x1e, 0x6b, 0xfa, + 0x88, 0x19, 0x62, 0xf3, 0x81, 0x10, 0x65, 0xf4, 0x86, 0x17, + + 0x48, 0xd9, 0xab, 0x3a, 0x4f, 0xde, 0xac, 0x3d, 0x46, 0xd7, 0xa5, + 0x34, 0x41, 0xd0, 0xa2, 0x33, 0x54, 0xc5, 0xb7, 0x26, 0x53, 0xc2, + 0xb0, 0x21, 0x5a, 0xcb, 0xb9, 0x28, 0x5d, 0xcc, 0xbe, 0x2f, + + 0xe0, 0x71, 0x03, 0x92, 0xe7, 0x76, 0x04, 0x95, 0xee, 0x7f, 0x0d, + 0x9c, 0xe9, 0x78, 0x0a, 0x9b, 0xfc, 0x6d, 0x1f, 0x8e, 0xfb, 0x6a, + 0x18, 0x89, 0xf2, 0x63, 0x11, 0x80, 0xf5, 0x64, 0x16, 0x87, + + 0xd8, 0x49, 0x3b, 0xaa, 0xdf, 0x4e, 0x3c, 0xad, 0xd6, 0x47, 0x35, + 0xa4, 0xd1, 0x40, 0x32, 0xa3, 0xc4, 0x55, 0x27, 0xb6, 0xc3, 0x52, + 0x20, 0xb1, 0xca, 0x5b, 0x29, 0xb8, 0xcd, 0x5c, 0x2e, 0xbf, + + 0x90, 0x01, 0x73, 0xe2, 0x97, 0x06, 0x74, 0xe5, 0x9e, 0x0f, 0x7d, + 0xec, 0x99, 0x08, 0x7a, 0xeb, 0x8c, 0x1d, 0x6f, 0xfe, 0x8b, 0x1a, + 0x68, 0xf9, 0x82, 0x13, 0x61, 0xf0, 0x85, 0x14, 0x66, 0xf7, + + 0xa8, 0x39, 0x4b, 0xda, 0xaf, 0x3e, 0x4c, 0xdd, 0xa6, 0x37, 0x45, + 0xd4, 0xa1, 0x30, 0x42, 0xd3, 0xb4, 0x25, 0x57, 0xc6, 0xb3, 0x22, + 0x50, 0xc1, 0xba, 0x2b, 0x59, 0xc8, 0xbd, 0x2c, 0x5e, 0xcf +}; + +uint8_t bt_mesh_fcs_calc(const uint8_t *data, uint8_t data_len) +{ + uint8_t fcs = 0xff; + + while (data_len--) + { + fcs = crc_table[fcs ^ *data++]; + } + + LOG_DBG("fcs 0x%02x", 0xff - fcs); + + return 0xff - fcs; +} + +bool bt_mesh_fcs_check(struct net_buf_simple *buf, uint8_t received_fcs) +{ + const uint8_t *data = buf->data; + uint16_t data_len = buf->len; + uint8_t fcs = 0xff; + + while (data_len--) + { + fcs = crc_table[fcs ^ *data++]; + } + + return crc_table[fcs ^ received_fcs] == 0xcf; +} + +int bt_mesh_virtual_addr(const uint8_t virtual_label[16], uint16_t *addr) +{ + uint8_t salt[16]; + uint8_t tmp[16]; + int err; + + err = bt_mesh_s1_str("vtad", salt); + if (err) + { + return err; + } + + err = bt_mesh_aes_cmac_one_raw_key(salt, virtual_label, 16, tmp); + if (err) + { + return err; + } + + *addr = (sys_get_be16(&tmp[14]) & 0x3fff) | 0x8000; + + return 0; +} + +int bt_mesh_prov_salt( + uint8_t algorithm, const uint8_t *conf_salt, const uint8_t *prov_rand, + const uint8_t *dev_rand, uint8_t *prov_salt) +{ + uint8_t size = algorithm ? 32 : 16; + const uint8_t prov_salt_key[16] = + { + 0 + }; + + struct bt_mesh_sg sg[] = + { + { + conf_salt, + size + }, + { + prov_rand, + size + }, + { + dev_rand, + size + }, + }; + + return bt_mesh_aes_cmac_raw_key(prov_salt_key, sg, ARRAY_SIZE(sg), + prov_salt); +} + +int bt_mesh_prov_conf_salt(uint8_t algorithm, + const uint8_t conf_inputs[145], uint8_t *salt) +{ + if (algorithm == BT_MESH_PROV_AUTH_HMAC_SHA256_AES_CCM && + IS_ENABLED(CONFIG_BT_MESH_ECDH_P256_HMAC_SHA256_AES_CCM)) + { + return bt_mesh_s2(conf_inputs, 145, salt); + } + + if (algorithm == BT_MESH_PROV_AUTH_CMAC_AES128_AES_CCM && + IS_ENABLED(CONFIG_BT_MESH_ECDH_P256_CMAC_AES128_AES_CCM)) + { + return bt_mesh_s1(conf_inputs, 145, salt); + } + + return -EINVAL; +} + +int bt_mesh_prov_conf_key( + uint8_t algorithm, const uint8_t *k_input, const uint8_t *conf_salt, + uint8_t *conf_key) +{ + if (algorithm == BT_MESH_PROV_AUTH_HMAC_SHA256_AES_CCM && + IS_ENABLED(CONFIG_BT_MESH_ECDH_P256_HMAC_SHA256_AES_CCM)) + { + return bt_mesh_k5(k_input, 64, conf_salt, "prck256", conf_key); + } + + if (algorithm == BT_MESH_PROV_AUTH_CMAC_AES128_AES_CCM && + IS_ENABLED(CONFIG_BT_MESH_ECDH_P256_CMAC_AES128_AES_CCM)) + { + return bt_mesh_k1(k_input, 32, conf_salt, "prck", conf_key); + } + + return -EINVAL; +} + +int bt_mesh_prov_conf( + uint8_t algorithm, const uint8_t *conf_key, const uint8_t *prov_rand, + const uint8_t *auth, uint8_t *conf) +{ + uint8_t auth_size = algorithm ? 32 : 16; + + LOG_DBG("ConfirmationKey %s", bt_hex(conf_key, auth_size)); + LOG_DBG("RandomDevice %s", bt_hex(prov_rand, auth_size)); + LOG_DBG("AuthValue %s", bt_hex(auth, auth_size)); + + if (algorithm == BT_MESH_PROV_AUTH_HMAC_SHA256_AES_CCM && + IS_ENABLED(CONFIG_BT_MESH_ECDH_P256_HMAC_SHA256_AES_CCM)) + { + return bt_mesh_sha256_hmac_one_raw_key(conf_key, prov_rand, 32, conf); + } + + if (algorithm == BT_MESH_PROV_AUTH_CMAC_AES128_AES_CCM && + IS_ENABLED(CONFIG_BT_MESH_ECDH_P256_CMAC_AES128_AES_CCM)) + { + struct bt_mesh_sg sg[] = + { + { + prov_rand, 16 + }, + { + auth, 16 + } + }; + + return bt_mesh_aes_cmac_raw_key(conf_key, sg, ARRAY_SIZE(sg), conf); + } + + return -EINVAL; +} + +int bt_mesh_prov_decrypt( + struct bt_mesh_key *key, uint8_t nonce[13], const uint8_t data[25 + 8], + uint8_t out[25]) +{ + int err; + + err = bt_mesh_ccm_decrypt(key, nonce, data, 25, NULL, 0, out, 8); + + return err; +} + +int bt_mesh_prov_encrypt( + struct bt_mesh_key *key, uint8_t nonce[13], const uint8_t data[25], + uint8_t out[25 + 8]) +{ + int err; + + err = bt_mesh_ccm_encrypt(key, nonce, data, 25, NULL, 0, out, 8); + + return err; +} + +int bt_mesh_beacon_auth(const struct bt_mesh_key *beacon_key, + uint8_t flags, + const uint8_t net_id[8], + uint32_t iv_index, + uint8_t auth[8]) +{ + uint8_t msg[13]; + uint8_t tmp[16]; + int err; + + LOG_DBG("BeaconKey %s", bt_hex(beacon_key, sizeof(struct bt_mesh_key))); + LOG_DBG("NetId %s", bt_hex(net_id, 8)); + LOG_DBG("IV Index 0x%08x", iv_index); + + msg[0] = flags; + memcpy(&msg[1], net_id, 8); + sys_put_be32(iv_index, &msg[9]); + + LOG_DBG("BeaconMsg %s", bt_hex(msg, sizeof(msg))); + + err = bt_mesh_aes_cmac_one_mesh_key(beacon_key, msg, sizeof(msg), tmp); + if (!err) + { + memcpy(auth, tmp, 8); + } + + return err; +} + +static int private_beacon_obf( + const struct bt_mesh_key *pbk, const uint8_t data[5], + const uint8_t random[13], uint8_t out[5]) +{ + uint8_t salt[16]; + int i; + int err; + + /* C1 = 0x01 | random | 0x0001 */ + + salt[0] = 0x01; + memcpy(&salt[1], random, 13); + sys_put_be16(0x0001, &salt[14]); + + /* ObfData = e(pbk, C1) ^ (flags | iv_index) */ + + err = bt_mesh_encrypt(pbk, salt, salt); + if (err) + { + return err; + } + + for (i = 0; i < 5; i++) + { + out[i] = data[i] ^ salt[i]; + } + + return 0; +} + +static int private_beacon_auth( + const struct bt_mesh_key *pbk, const uint8_t beacon_data[5], + const uint8_t random[13], uint8_t auth[8]) +{ + uint8_t salt[16]; + uint8_t tmp[16]; + int i; + int err; + + /* B0 = 0x19 | random | 0x0005 */ + + salt[0] = 0x19; + memcpy(&salt[1], random, 13); + sys_put_be16(0x0005, &salt[14]); + + /* T0 = e(PBK, b0) */ + + err = bt_mesh_encrypt(pbk, salt, tmp); + if (err) + { + return err; + } + + /* P = flags | iv_index | 000 */ + + /* T1 = e(PBK, P ^ T0) */ + + for (i = 0; i < 5; i++) + { + tmp[i] ^= beacon_data[i]; + } + + err = bt_mesh_encrypt(pbk, tmp, tmp); + if (err) + { + return err; + } + + /* C0 = 0x01 | random | 0x0000 */ + + salt[0] = 0x01; + sys_put_be16(0x0000, &salt[14]); + + /* T2 = T1 ^ e(PBK, C0) */ + + memcpy(auth, tmp, 8); + + err = bt_mesh_encrypt(pbk, salt, tmp); + if (err) + { + return err; + } + + /* Auth = T2[0..7] */ + + for (i = 0; i < 8; i++) + { + auth[i] ^= tmp[i]; + } + + return 0; +} + +int bt_mesh_beacon_decrypt( + const struct bt_mesh_key *pbk, const uint8_t random[13], + const uint8_t data[5], const uint8_t expected_auth[8], uint8_t out[5]) +{ + uint8_t auth[8]; + int err; + + LOG_DBG(""); + + err = private_beacon_obf(pbk, data, random, out); + if (err) + { + return err; + } + + err = private_beacon_auth(pbk, out, random, auth); + if (err) + { + return err; + } + + LOG_DBG("0x%02x, 0x%08x", out[0], sys_get_be32(&out[1])); + + if (memcmp(auth, expected_auth, 8)) + { + LOG_DBG("Invalid auth rx: %s", bt_hex(auth, 8)); + LOG_DBG("Expected auth: %s", bt_hex(expected_auth, 8)); + return -EBADMSG; + } + + return 0; +} + +int bt_mesh_beacon_encrypt( + const struct bt_mesh_key *pbk, uint8_t flags, uint32_t iv_index, + const uint8_t random[13], uint8_t data[5], uint8_t auth[8]) +{ + int err; + + LOG_WRN("Enc beacon: 0x%02x, 0x%08x", flags, iv_index); + + data[0] = flags; + sys_put_be32(iv_index, &data[1]); + + err = private_beacon_auth(pbk, data, random, auth); + if (err) + { + return err; + } + + return private_beacon_obf(pbk, data, random, data); +} diff --git a/libs/zblue/mesh/crypto.h b/libs/zblue/mesh/crypto.h new file mode 100644 index 0000000000000..0f1d90f0d1726 --- /dev/null +++ b/libs/zblue/mesh/crypto.h @@ -0,0 +1,201 @@ +/**************************************************************************** + * libs/zblue/mesh/crypto.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include "keys.h" + +enum bt_mesh_nonce_type +{ + BT_MESH_NONCE_NETWORK, + BT_MESH_NONCE_PROXY, + BT_MESH_NONCE_SOLICITATION, +}; + +struct bt_mesh_sg +{ + const void *data; + size_t len; +}; + +int bt_mesh_crypto_init(void); + +int bt_mesh_encrypt( + const struct bt_mesh_key *key, const uint8_t plaintext[16], + uint8_t enc_data[16]); + +int bt_mesh_ccm_encrypt( + const struct bt_mesh_key *key, uint8_t nonce[13], const uint8_t *plaintext, + size_t len, const uint8_t *aad, size_t aad_len, uint8_t *enc_data, + size_t mic_size); + +int bt_mesh_ccm_decrypt( + const struct bt_mesh_key *key, uint8_t nonce[13], const uint8_t *enc_data, + size_t len, const uint8_t *aad, size_t aad_len, uint8_t *plaintext, + size_t mic_size); + +int bt_mesh_aes_cmac_mesh_key( + const struct bt_mesh_key *key, struct bt_mesh_sg *sg, size_t sg_len, + uint8_t mac[16]); + +int bt_mesh_aes_cmac_raw_key( + const uint8_t key[16], struct bt_mesh_sg *sg, size_t sg_len, + uint8_t mac[16]); + +int bt_mesh_sha256_hmac_raw_key( + const uint8_t key[32], struct bt_mesh_sg *sg, size_t sg_len, + uint8_t mac[32]); + +int bt_mesh_s1(const void *m, size_t m_len, uint8_t salt[16]); + +static inline int bt_mesh_s1_str(const char *m, uint8_t salt[16]) +{ + return bt_mesh_s1(m, strlen(m), salt); +} + +int bt_mesh_s2(const void *m, size_t m_len, uint8_t salt[32]); + +int bt_mesh_k1(const uint8_t *ikm, + size_t ikm_len, const uint8_t salt[16], const char *info, + uint8_t okm[16]); + +int bt_mesh_k2( + const uint8_t n[16], const uint8_t *p, size_t p_len, uint8_t net_id[1], + struct bt_mesh_key *enc_key, struct bt_mesh_key *priv_key); + +int bt_mesh_k3(const uint8_t n[16], uint8_t out[8]); + +int bt_mesh_k4(const uint8_t n[16], uint8_t out[1]); + +int bt_mesh_k5( + const uint8_t *n, size_t n_len, const uint8_t salt[32], const char *p, + uint8_t out[32]); + +int bt_mesh_id128( + const uint8_t n[16], const char *s, enum bt_mesh_key_type type, + struct bt_mesh_key *out); + +static inline int bt_mesh_identity_key(const uint8_t net_key[16], + struct bt_mesh_key *identity_key) +{ + return bt_mesh_id128(net_key, "nkik", BT_MESH_KEY_TYPE_ECB, identity_key); +} + +static inline int bt_mesh_beacon_key(const uint8_t net_key[16], + struct bt_mesh_key *beacon_key) +{ + return bt_mesh_id128(net_key, "nkbk", BT_MESH_KEY_TYPE_CMAC, beacon_key); +} + +static inline int bt_mesh_private_beacon_key(const uint8_t net_key[16], + struct bt_mesh_key *private_beacon_key) +{ + return bt_mesh_id128(net_key, "nkpk", BT_MESH_KEY_TYPE_ECB, + private_beacon_key); +} + +int bt_mesh_beacon_auth(const struct bt_mesh_key *beacon_key, uint8_t flags, + const uint8_t net_id[8], uint32_t iv_index, + uint8_t auth[8]); + +static inline int bt_mesh_app_id(const uint8_t app_key[16], + uint8_t app_id[1]) +{ + return bt_mesh_k4(app_key, app_id); +} + +int bt_mesh_session_key(const uint8_t dhkey[32], const uint8_t prov_salt[16], + struct bt_mesh_key *session_key); + +int bt_mesh_prov_nonce(const uint8_t dhkey[32], + const uint8_t prov_salt[16], uint8_t nonce[13]); + +int bt_mesh_dev_key(const uint8_t dhkey[32], + const uint8_t prov_salt[16], uint8_t dev_key[16]); + +int bt_mesh_prov_salt( + uint8_t algorithm, const uint8_t *conf_salt, const uint8_t *prov_rand, + const uint8_t *dev_rand, uint8_t *prov_salt); + +int bt_mesh_net_obfuscate(uint8_t *pdu, + uint32_t iv_index, + const struct bt_mesh_key *privacy_key); + +int bt_mesh_net_encrypt( + const struct bt_mesh_key *key, struct net_buf_simple *buf, + uint32_t iv_index, enum bt_mesh_nonce_type type); + +int bt_mesh_net_decrypt( + const struct bt_mesh_key *key, struct net_buf_simple *buf, + uint32_t iv_index, enum bt_mesh_nonce_type type); + +struct bt_mesh_app_crypto_ctx +{ + bool dev_key; + uint8_t aszmic; + uint16_t src; + uint16_t dst; + uint32_t seq_num; + uint32_t iv_index; + const uint8_t *ad; +}; + +int bt_mesh_app_encrypt( + const struct bt_mesh_key *key, const struct bt_mesh_app_crypto_ctx *ctx, + struct net_buf_simple *buf); + +int bt_mesh_app_decrypt( + const struct bt_mesh_key *key, const struct bt_mesh_app_crypto_ctx *ctx, + struct net_buf_simple *buf, struct net_buf_simple *out); + +uint8_t bt_mesh_fcs_calc(const uint8_t *data, uint8_t data_len); + +bool bt_mesh_fcs_check(struct net_buf_simple *buf, uint8_t received_fcs); + +int bt_mesh_virtual_addr(const uint8_t virtual_label[16], uint16_t *addr); + +int bt_mesh_prov_conf_salt(uint8_t algorithm, + const uint8_t conf_inputs[145], uint8_t *salt); + +int bt_mesh_prov_conf_key( + uint8_t algorithm, const uint8_t *k_input, const uint8_t *conf_salt, + uint8_t *conf_key); + +int bt_mesh_prov_conf( + uint8_t algorithm, const uint8_t *conf_key, const uint8_t *prov_rand, + const uint8_t *auth, uint8_t *conf); + +int bt_mesh_prov_decrypt( + struct bt_mesh_key *key, uint8_t nonce[13], const uint8_t data[25 + 8], + uint8_t out[25]); + +int bt_mesh_prov_encrypt( + struct bt_mesh_key *key, uint8_t nonce[13], const uint8_t data[25], + uint8_t out[25 + 8]); + +int bt_mesh_pub_key_gen(void); + +const uint8_t *bt_mesh_pub_key_get(void); + +int bt_mesh_dhkey_gen(const uint8_t *pub_key, + const uint8_t *priv_key, uint8_t *dhkey); + +int bt_mesh_beacon_decrypt( + const struct bt_mesh_key *pbk, const uint8_t random[13], + const uint8_t data[5], const uint8_t expected_auth[8], uint8_t out[5]); + +int bt_mesh_beacon_encrypt( + const struct bt_mesh_key *pbk, uint8_t flags, uint32_t iv_index, + const uint8_t random[13], uint8_t data[5], uint8_t auth[8]); diff --git a/libs/zblue/mesh/crypto_psa.c b/libs/zblue/mesh/crypto_psa.c new file mode 100644 index 0000000000000..777bcda34bc96 --- /dev/null +++ b/libs/zblue/mesh/crypto_psa.c @@ -0,0 +1,620 @@ +/**************************************************************************** + * libs/zblue/mesh/crypto_psa.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#define LOG_LEVEL CONFIG_BT_MESH_CRYPTO_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_crypto_psa); + +#include "mesh.h" +#include "crypto.h" +#include "prov.h" + +/* Mesh requires to keep in persistent memory network keys (2 keys per + * subnetwork), application keys (2 real keys per 1 configured) and device + * key + device key candidate. + */ +#if defined CONFIG_BT_MESH_CDB +#define BT_MESH_CDB_KEY_ID_RANGE_SIZE (2 * SUBNET_COUNT + 2 * APP_KEY_COUNT + NODE_COUNT) +#else +#define BT_MESH_CDB_KEY_ID_RANGE_SIZE 0 +#endif + +#define BT_MESH_PSA_KEY_ID_MIN ZEPHYR_PSA_BT_MESH_KEY_ID_RANGE_BEGIN + +#define BT_MESH_PSA_KEY_ID_RANGE_SIZE \ + (2 * CONFIG_BT_MESH_SUBNET_COUNT + 2 * CONFIG_BT_MESH_APP_KEY_COUNT + 2 + \ + BT_MESH_CDB_KEY_ID_RANGE_SIZE) +BUILD_ASSERT(BT_MESH_PSA_KEY_ID_RANGE_SIZE <= + ZEPHYR_PSA_BT_MESH_KEY_ID_RANGE_SIZE, + "PSA key ID range exceeds officially allocated range."); + +BUILD_ASSERT(PSA_MAC_LENGTH(PSA_KEY_TYPE_AES, 128, PSA_ALG_CMAC) == 16, + "MAC length should be 16 bytes for 128-bits key for CMAC-AES"); + +BUILD_ASSERT(PSA_MAC_LENGTH(PSA_KEY_TYPE_HMAC, 256, + PSA_ALG_HMAC(PSA_ALG_SHA_256)) == 32, + "MAC length should be 32 bytes for 256-bits key for HMAC-SHA"); + +static struct +{ + bool is_ready; + psa_key_id_t priv_key_id; + uint8_t public_key_be[PUB_KEY_SIZE + 1]; +} dh_pair; + +static atomic_define(pst_keys, BT_MESH_PSA_KEY_ID_RANGE_SIZE); + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bt_mesh_crypto_init(void) +{ + if (psa_crypto_init() != PSA_SUCCESS) + { + return -EIO; + } + + return 0; +} + +int bt_mesh_encrypt( + const struct bt_mesh_key *key, const uint8_t plaintext[16], + uint8_t enc_data[16]) +{ + size_t output_len; + psa_status_t status; + int err = 0; + + status = + psa_cipher_encrypt(key->key, PSA_ALG_ECB_NO_PADDING, plaintext, 16, + enc_data, 16, + &output_len); + + if (status != PSA_SUCCESS || output_len != 16) + { + err = -EIO; + } + + return err; +} + +int bt_mesh_ccm_encrypt( + const struct bt_mesh_key *key, uint8_t nonce[13], const uint8_t *plaintext, + size_t len, const uint8_t *aad, size_t aad_len, uint8_t *enc_data, + size_t mic_size) +{ + size_t output_len; + psa_status_t status; + int err = 0; + psa_algorithm_t alg = PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_CCM, + mic_size); + + status = + psa_aead_encrypt(key->key, alg, nonce, 13, aad, aad_len, plaintext, + len, enc_data, + len + mic_size, &output_len); + + if (status != PSA_SUCCESS || output_len != len + mic_size) + { + err = -EIO; + } + + return err; +} + +int bt_mesh_ccm_decrypt( + const struct bt_mesh_key *key, uint8_t nonce[13], const uint8_t *enc_data, + size_t len, const uint8_t *aad, size_t aad_len, uint8_t *plaintext, + size_t mic_size) +{ + size_t output_len; + psa_status_t status; + int err = 0; + psa_algorithm_t alg = PSA_ALG_AEAD_WITH_SHORTENED_TAG(PSA_ALG_CCM, + mic_size); + + status = psa_aead_decrypt(key->key, alg, nonce, 13, aad, aad_len, enc_data, + len + mic_size, + plaintext, len, &output_len); + + if (status != PSA_SUCCESS || output_len != len) + { + err = -EIO; + } + + return err; +} + +int bt_mesh_aes_cmac_mesh_key( + const struct bt_mesh_key *key, struct bt_mesh_sg *sg, size_t sg_len, + uint8_t mac[16]) +{ + psa_mac_operation_t operation = PSA_MAC_OPERATION_INIT; + psa_algorithm_t alg = PSA_ALG_CMAC; + psa_status_t status; + + status = psa_mac_sign_setup(&operation, key->key, alg); + if (status != PSA_SUCCESS) + { + return -EIO; + } + + for (; sg_len; sg_len--, sg++) + { + status = psa_mac_update(&operation, sg->data, sg->len); + if (status != PSA_SUCCESS) + { + psa_mac_abort(&operation); + return -EIO; + } + } + + size_t mac_len; + + status = psa_mac_sign_finish(&operation, mac, 16, &mac_len); + if (status != PSA_SUCCESS) + { + return -EIO; + } + + if (mac_len != 16) + { + return -ERANGE; + } + + return 0; +} + +int bt_mesh_aes_cmac_raw_key( + const uint8_t key[16], struct bt_mesh_sg *sg, size_t sg_len, + uint8_t mac[16]) +{ + struct bt_mesh_key key_id; + int err; + + err = bt_mesh_key_import(BT_MESH_KEY_TYPE_CMAC, key, &key_id); + if (err) + { + return err; + } + + err = bt_mesh_aes_cmac_mesh_key(&key_id, sg, sg_len, mac); + + psa_destroy_key(key_id.key); + + return err; +} + +int bt_mesh_sha256_hmac_raw_key( + const uint8_t key[32], struct bt_mesh_sg *sg, size_t sg_len, + uint8_t mac[32]) +{ + psa_mac_operation_t operation = PSA_MAC_OPERATION_INIT; + psa_algorithm_t alg = PSA_ALG_HMAC(PSA_ALG_SHA_256); + + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + psa_key_id_t key_id; + + psa_status_t status; + int err = 0; + + /* Import a key */ + + psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_SIGN_MESSAGE); + psa_set_key_lifetime(&attributes, PSA_KEY_LIFETIME_VOLATILE); + psa_set_key_algorithm(&attributes, PSA_ALG_HMAC(PSA_ALG_SHA_256)); + psa_set_key_type(&attributes, PSA_KEY_TYPE_HMAC); + psa_set_key_bits(&attributes, 256); + + status = psa_import_key(&attributes, key, 32, &key_id); + if (status != PSA_SUCCESS) + { + err = -EIO; + goto end; + } + + psa_reset_key_attributes(&attributes); + + status = psa_mac_sign_setup(&operation, key_id, alg); + if (status != PSA_SUCCESS) + { + err = -EIO; + goto end; + } + + for (; sg_len; sg_len--, sg++) + { + status = psa_mac_update(&operation, sg->data, sg->len); + if (status != PSA_SUCCESS) + { + psa_mac_abort(&operation); + err = -EIO; + goto end; + } + } + + size_t mac_len; + + status = psa_mac_sign_finish(&operation, mac, 32, &mac_len); + if (status != PSA_SUCCESS) + { + err = -EIO; + goto end; + } + + if (mac_len != 32) + { + err = -ERANGE; + } + +end: + + /* Destroy the key */ + + psa_destroy_key(key_id); + + return err; +} + +int bt_mesh_pub_key_gen(void) +{ + psa_key_attributes_t key_attributes = PSA_KEY_ATTRIBUTES_INIT; + psa_status_t status; + int err = 0; + size_t key_len; + + psa_destroy_key(dh_pair.priv_key_id); + dh_pair.is_ready = false; + + /* Crypto settings for ECDH using the SHA256 hashing algorithm, + * the secp256r1 curve + */ + + psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_DERIVE); + psa_set_key_lifetime(&key_attributes, PSA_KEY_LIFETIME_VOLATILE); + psa_set_key_algorithm(&key_attributes, PSA_ALG_ECDH); + psa_set_key_type(&key_attributes, + PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); + psa_set_key_bits(&key_attributes, 256); + + /* Generate a key pair */ + + status = psa_generate_key(&key_attributes, &dh_pair.priv_key_id); + if (status != PSA_SUCCESS) + { + err = -EIO; + goto end; + } + + status = psa_export_public_key(dh_pair.priv_key_id, dh_pair.public_key_be, + sizeof(dh_pair.public_key_be), &key_len); + if (status != PSA_SUCCESS) + { + err = -EIO; + goto end; + } + + if (key_len != PUB_KEY_SIZE + 1) + { + err = -ERANGE; + goto end; + } + + dh_pair.is_ready = true; + +end: + psa_reset_key_attributes(&key_attributes); + + return err; +} + +const uint8_t *bt_mesh_pub_key_get(void) +{ + return dh_pair.is_ready ? dh_pair.public_key_be + 1 : NULL; +} + +BUILD_ASSERT(PSA_RAW_KEY_AGREEMENT_OUTPUT_SIZE( + PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1), 256) == + DH_KEY_SIZE, + "Diffie-Hellman shared secret size should be the same in PSA " + "and Bluetooth Mesh"); + +BUILD_ASSERT(PSA_KEY_EXPORT_ECC_PUBLIC_KEY_MAX_SIZE(256) == PUB_KEY_SIZE + 1, + "Exported PSA public key should be 1 byte larger than " + "Bluetooth Mesh public key"); + +int bt_mesh_dhkey_gen(const uint8_t *pub_key, + const uint8_t *priv_key, uint8_t *dhkey) +{ + int err = 0; + psa_key_id_t priv_key_id = PSA_KEY_ID_NULL; + uint8_t public_key_repr[PUB_KEY_SIZE + 1]; + psa_status_t status; + size_t dh_key_len; + + if (priv_key) + { + psa_key_attributes_t attributes = PSA_KEY_ATTRIBUTES_INIT; + + /* Import a custom private key */ + + psa_set_key_usage_flags(&attributes, PSA_KEY_USAGE_DERIVE); + psa_set_key_lifetime(&attributes, PSA_KEY_LIFETIME_VOLATILE); + psa_set_key_algorithm(&attributes, PSA_ALG_ECDH); + psa_set_key_type(&attributes, + PSA_KEY_TYPE_ECC_KEY_PAIR(PSA_ECC_FAMILY_SECP_R1)); + psa_set_key_bits(&attributes, 256); + + status = psa_import_key(&attributes, priv_key, PRIV_KEY_SIZE, + &priv_key_id); + if (status != PSA_SUCCESS) + { + err = -EIO; + goto end; + } + + psa_reset_key_attributes(&attributes); + } + else + { + priv_key_id = dh_pair.priv_key_id; + } + + /* For elliptic curve key pairs for Weierstrass curve families + * (PSA_ECC_FAMILY_SECP_R1) the representations of public key is: + * - The byte 0x04; + * - x_P as a ceiling(m/8)-byte string, big-endian; + * - y_P as a ceiling(m/8)-byte string, big-endian. + */ + + public_key_repr[0] = 0x04; + memcpy(public_key_repr + 1, pub_key, PUB_KEY_SIZE); + + /* Calculate the secret */ + + status = psa_raw_key_agreement(PSA_ALG_ECDH, priv_key_id, public_key_repr, + PUB_KEY_SIZE + 1, + dhkey, DH_KEY_SIZE, &dh_key_len); + if (status != PSA_SUCCESS) + { + err = -EIO; + goto end; + } + + if (dh_key_len != DH_KEY_SIZE) + { + err = -ERANGE; + } + +end: + + if (priv_key) + { + psa_destroy_key(priv_key_id); + } + + return err; +} + +static psa_key_id_t keyid_alloc(void) +{ + for (int i = 0; i < BT_MESH_PSA_KEY_ID_RANGE_SIZE; i++) + { + if (!atomic_test_bit(pst_keys, i)) + { + atomic_set_bit(pst_keys, i); + return BT_MESH_PSA_KEY_ID_MIN + i; + } + } + + return PSA_KEY_ID_NULL; +} + +static int keyid_free(psa_key_id_t key_id) +{ + if (IN_RANGE(key_id, BT_MESH_PSA_KEY_ID_MIN, + BT_MESH_PSA_KEY_ID_MIN + BT_MESH_PSA_KEY_ID_RANGE_SIZE - 1)) + { + atomic_clear_bit(pst_keys, key_id - BT_MESH_PSA_KEY_ID_MIN); + return 0; + } + + return -EIO; +} + +static void keyid_assign(psa_key_id_t key_id) +{ + if (IN_RANGE(key_id, BT_MESH_PSA_KEY_ID_MIN, + BT_MESH_PSA_KEY_ID_MIN + BT_MESH_PSA_KEY_ID_RANGE_SIZE - 1)) + { + atomic_set_bit(pst_keys, key_id - BT_MESH_PSA_KEY_ID_MIN); + } +} + +int bt_mesh_key_import(enum bt_mesh_key_type type, + const uint8_t in[16], struct bt_mesh_key *out) +{ + psa_key_attributes_t key_attributes = PSA_KEY_ATTRIBUTES_INIT; + psa_status_t status; + psa_key_id_t key_id = PSA_KEY_ID_NULL; + int err = 0; + + switch (type) + { + case BT_MESH_KEY_TYPE_ECB: + psa_set_key_lifetime(&key_attributes, PSA_KEY_LIFETIME_VOLATILE); + psa_set_key_usage_flags(&key_attributes, + PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT); + psa_set_key_algorithm(&key_attributes, PSA_ALG_ECB_NO_PADDING); + break; + case BT_MESH_KEY_TYPE_CCM: + psa_set_key_lifetime(&key_attributes, PSA_KEY_LIFETIME_VOLATILE); + psa_set_key_usage_flags(&key_attributes, + PSA_KEY_USAGE_ENCRYPT | PSA_KEY_USAGE_DECRYPT); + psa_set_key_algorithm(&key_attributes, + PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG( + PSA_ALG_CCM, 4)); + break; + case BT_MESH_KEY_TYPE_CMAC: + psa_set_key_lifetime(&key_attributes, PSA_KEY_LIFETIME_VOLATILE); + psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_SIGN_MESSAGE); + psa_set_key_algorithm(&key_attributes, PSA_ALG_CMAC); + break; + case BT_MESH_KEY_TYPE_NET: + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + key_id = keyid_alloc(); + + if (key_id == PSA_KEY_ID_NULL) + { + return -ENOMEM; + } + + psa_set_key_lifetime(&key_attributes, + PSA_KEY_LIFETIME_PERSISTENT); + psa_set_key_id(&key_attributes, key_id); + } + else + { + psa_set_key_lifetime(&key_attributes, PSA_KEY_LIFETIME_VOLATILE); + } + + psa_set_key_usage_flags(&key_attributes, PSA_KEY_USAGE_EXPORT); + break; + case BT_MESH_KEY_TYPE_APP: + case BT_MESH_KEY_TYPE_DEV: + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + key_id = keyid_alloc(); + + if (key_id == PSA_KEY_ID_NULL) + { + return -ENOMEM; + } + + psa_set_key_lifetime(&key_attributes, + PSA_KEY_LIFETIME_PERSISTENT); + psa_set_key_id(&key_attributes, key_id); + } + else + { + psa_set_key_lifetime(&key_attributes, PSA_KEY_LIFETIME_VOLATILE); + } + + psa_set_key_usage_flags(&key_attributes, + PSA_KEY_USAGE_ENCRYPT | + PSA_KEY_USAGE_DECRYPT | + PSA_KEY_USAGE_EXPORT); + psa_set_key_algorithm(&key_attributes, + PSA_ALG_AEAD_WITH_AT_LEAST_THIS_LENGTH_TAG( + PSA_ALG_CCM, 4)); + break; + default: + return -EIO; + } + + psa_set_key_type(&key_attributes, PSA_KEY_TYPE_AES); + psa_set_key_bits(&key_attributes, 128); + + status = psa_import_key(&key_attributes, in, 16, &out->key); + if (status == PSA_ERROR_ALREADY_EXISTS) + { + LOG_WRN("Key with ID 0x%4x already exists (desync between mesh and " + "PSA " + "ITS)", + key_id); + (void)psa_destroy_key(key_id); + status = psa_import_key(&key_attributes, in, 16, &out->key); + } + + err = status == PSA_SUCCESS ? 0 : -EIO; + if (err && key_id != PSA_KEY_ID_NULL) + { + keyid_free(key_id); + } + + psa_reset_key_attributes(&key_attributes); + + return err; +} + +int bt_mesh_key_export(uint8_t out[16], const struct bt_mesh_key *in) +{ + size_t data_length; + + if (psa_export_key(in->key, out, 16, &data_length) != PSA_SUCCESS) + { + return -EIO; + } + + if (data_length != 16) + { + return -EIO; + } + + return 0; +} + +void bt_mesh_key_assign(struct bt_mesh_key *dst, + const struct bt_mesh_key *src) +{ + memcpy(dst, src, sizeof(struct bt_mesh_key)); + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + keyid_assign(dst->key); + } +} + +int bt_mesh_key_destroy(const struct bt_mesh_key *key) +{ + if (psa_destroy_key(key->key) != PSA_SUCCESS) + { + return -EIO; + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return keyid_free(key->key); + } + + return 0; +} + +int bt_mesh_key_compare(const uint8_t raw_key[16], + const struct bt_mesh_key *key) +{ + uint8_t out[16]; + int err; + + err = bt_mesh_key_export(out, key); + if (err) + { + return err; + } + + return memcmp(out, raw_key, 16); +} + +__weak int bt_rand(void *buf, size_t len) +{ + return psa_generate_random(buf, len) == PSA_SUCCESS ? 0 : -EIO; +} diff --git a/libs/zblue/mesh/delayable_msg.c b/libs/zblue/mesh/delayable_msg.c new file mode 100644 index 0000000000000..853382881f9fc --- /dev/null +++ b/libs/zblue/mesh/delayable_msg.c @@ -0,0 +1,557 @@ +/**************************************************************************** + * libs/zblue/mesh/delayable_msg.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include + +#include + +#include +#include + +#include "msg.h" +#include "access.h" +#include "net.h" + +#define LOG_LEVEL CONFIG_BT_MESH_ACCESS_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_delayable_msg); + +static void delayable_msg_handler(struct k_work *w); +static bool push_msg_from_delayable_msgs(void); + +static struct delayable_msg_chunk +{ + struct list_node node; + uint8_t data[CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG_CHUNK_SIZE]; +} + +delayable_msg_chunks[CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG_CHUNK_COUNT]; + +static struct delayable_msg_ctx +{ + struct list_node node; + struct list_node chunks; + struct bt_mesh_msg_ctx ctx; + uint16_t src_addr; + const struct bt_mesh_send_cb *cb; + void *cb_data; + uint32_t fired_time; + uint16_t len; +} + +delayable_msgs_ctx[CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG_COUNT]; + +static struct +{ + struct list_node busy_ctx; + struct list_node free_ctx; + struct list_node free_chunks; + struct k_work_delayable random_delay; +} + +access_delayable_msg = +{ + .random_delay = Z_WORK_DELAYABLE_INITIALIZER(delayable_msg_handler) +}; + +/* Serializes access to busy_ctx, free_ctx, free_chunks, and the fired_time + * field of any ctx currently on busy_ctx. Must never be held across a call + * that can block (bt_mesh_access_send(), bt_rand(), user callbacks). + */ + +static spinlock_t lock; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/* Caller must hold `lock`. */ + +static void put_ctx_to_busy_list(struct delayable_msg_ctx *ctx) +{ + struct delayable_msg_ctx *curr_ctx; + struct list_node *list = &access_delayable_msg.busy_ctx; + struct list_node *head = list_peek_head(list); + struct list_node *curr = head; + struct list_node *prev = curr; + + if (!head) + { + list_add_tail(list, &ctx->node); + return; + } + + do + { + curr_ctx = CONTAINER_OF(curr, struct delayable_msg_ctx, node); + if (ctx->fired_time < curr_ctx->fired_time) + { + if (curr == head) + { + list_add_head(list, &ctx->node); + } + else + { + list_insert_after(list, prev, &ctx->node); + } + + return; + } + + prev = curr; + } + while ((curr = list_next(list, curr))); + + list_add_tail(list, &ctx->node); +} + +static struct delayable_msg_ctx *peek_pending_msg(void) +{ + struct delayable_msg_ctx *pending_msg = NULL; + struct list_node *node = list_peek_head(&access_delayable_msg.busy_ctx); + + if (node) + { + pending_msg = CONTAINER_OF(node, struct delayable_msg_ctx, node); + } + + return pending_msg; +} + +static void reschedule_delayable_msg(struct delayable_msg_ctx *msg) +{ + int32_t remaining; + clock_t delay = 0; + struct delayable_msg_ctx *pending_msg; + irqstate_t key = spin_lock_irqsave(&lock); + + if (msg) + { + put_ctx_to_busy_list(msg); + } + + pending_msg = peek_pending_msg(); + + if (!pending_msg) + { + spin_unlock_irqrestore(&lock, key); + return; + } + + remaining = + (int32_t)(pending_msg->fired_time - + (uint32_t)TICK2MSEC(clock_systime_ticks())); + if (remaining > 0) + { + delay = MSEC2TICK(remaining); + } + + /* k_work_reschedule() is safe to call under our spinlock: it uses its own + * internal spinlock and does not block. Holding the lock here makes the + * "select head + arm timer" step atomic w.r.t. concurrent insertions. + */ + + k_work_reschedule(&access_delayable_msg.random_delay, delay); + spin_unlock_irqrestore(&lock, key); +} + +static int allocate_delayable_msg_chunks(struct delayable_msg_ctx *msg, + int number) +{ + struct list_node *node; + irqstate_t key = spin_lock_irqsave(&lock); + + for (int i = 0; i < number; i++) + { + node = list_remove_head(&access_delayable_msg.free_chunks); + if (!node) + { + spin_unlock_irqrestore(&lock, key); + LOG_WRN("Unable allocate %u chunks, allocated %u", number, i); + return i; + } + + list_add_tail(&msg->chunks, node); + } + + spin_unlock_irqrestore(&lock, key); + return number; +} + +static void release_delayable_msg_chunks(struct delayable_msg_ctx *msg) +{ + struct list_node *node; + irqstate_t key = spin_lock_irqsave(&lock); + + while ((node = list_remove_head(&msg->chunks))) + { + list_add_tail(&access_delayable_msg.free_chunks, node); + } + + spin_unlock_irqrestore(&lock, key); +} + +static struct delayable_msg_ctx *allocate_delayable_msg_ctx(void) +{ + struct delayable_msg_ctx *msg; + struct list_node *node; + irqstate_t key; + + key = spin_lock_irqsave(&lock); + node = list_remove_head(&access_delayable_msg.free_ctx); + spin_unlock_irqrestore(&lock, key); + + if (!node) + { + LOG_WRN("Purge pending delayable message."); + + /* May block, so it must not be called with the lock held. */ + + if (!push_msg_from_delayable_msgs()) + { + return NULL; + } + + key = spin_lock_irqsave(&lock); + node = list_remove_head(&access_delayable_msg.free_ctx); + spin_unlock_irqrestore(&lock, key); + + /* Another context may have taken the purged ctx before we retried. */ + + if (!node) + { + return NULL; + } + } + + msg = CONTAINER_OF(node, struct delayable_msg_ctx, node); + + /* msg is off every list here, so it is private to this caller. */ + + list_initialize(&msg->chunks); + + return msg; +} + +static void release_delayable_msg_ctx(struct delayable_msg_ctx *ctx) +{ + irqstate_t key = spin_lock_irqsave(&lock); + + /* Not on busy_ctx when called from the manage() error path. */ + + (void)list_find_and_remove(&access_delayable_msg.busy_ctx, &ctx->node); + list_add_tail(&access_delayable_msg.free_ctx, &ctx->node); + spin_unlock_irqrestore(&lock, key); +} + +/* Releases `msg` and reports `err` to its sender, which is skipped for a + * successful send because the transport owns the callback from then on. Must + * not be called with the lock held. + */ + +static void complete_delayable_msg(struct delayable_msg_ctx *msg, int err) +{ + /* Capture the callback before releasing the ctx; another caller may take + * it immediately and overwrite msg->cb / msg->cb_data. + */ + + const struct bt_mesh_send_cb *cb = msg->cb; + void *cb_data = msg->cb_data; + + release_delayable_msg_chunks(msg); + release_delayable_msg_ctx(msg); + + if (err && cb && cb->start) + { + cb->start(0, err, cb_data); + } +} + +/* Signed delta keeps this correct across the 32-bit uptime wrap. */ + +static bool msg_expired(const struct delayable_msg_ctx *msg) +{ + return (int32_t)((uint32_t)TICK2MSEC(clock_systime_ticks()) - + msg->fired_time) >= 0; +} + +/* Takes the head off busy_ctx only if it is due. k_work_reschedule() cannot + * withdraw a submission that already happened, so the handler can run for an + * expiry another context has consumed; this leaves such an invocation with + * nothing to send. + */ + +static struct delayable_msg_ctx *take_expired_msg(void) +{ + struct delayable_msg_ctx *msg; + irqstate_t key = spin_lock_irqsave(&lock); + + msg = peek_pending_msg(); + if (msg && !msg_expired(msg)) + { + msg = NULL; + } + else if (msg) + { + (void)list_remove_head(&access_delayable_msg.busy_ctx); + } + + spin_unlock_irqrestore(&lock, key); + + return msg; +} + +/* Takes the head whatever its fired_time, so the purge path can force out + * the earliest msg. + */ + +static struct delayable_msg_ctx *take_earliest_msg(void) +{ + struct list_node *node; + irqstate_t key = spin_lock_irqsave(&lock); + + /* Dequeueing under the lock stops concurrent callers from sending and + * releasing the same msg twice. + */ + + node = list_remove_head(&access_delayable_msg.busy_ctx); + spin_unlock_irqrestore(&lock, key); + + return node ? CONTAINER_OF(node, struct delayable_msg_ctx, node) : NULL; +} + +/* Sends `msg` and releases it. Returns false if a transient failure put it + * back on busy_ctx. + */ + +static bool send_delayable_msg(struct delayable_msg_ctx *msg) +{ + struct list_node *node; + struct delayable_msg_chunk *chunk; + uint16_t len = msg->len; + int err; + irqstate_t key; + + /* bt_mesh_suspend() sets BT_MESH_SUSPENDED and bt_mesh_reset() clears + * BT_MESH_VALID before the queue is drained, so either one means the stack + * is down. Never hand a message to the transport in that state, however it + * ended up on busy_ctx. + */ + + if (atomic_test_bit(bt_mesh.flags, BT_MESH_SUSPENDED) || + !atomic_test_bit(bt_mesh.flags, BT_MESH_VALID)) + { + complete_delayable_msg(msg, -ENODEV); + return true; + } + + NET_BUF_SIMPLE_DEFINE(buf, BT_MESH_TX_SDU_MAX); + + /* msg is off busy_ctx, so it is unreachable from other contexts and its + * chunks can be walked unlocked. + */ + + list_for_every(&msg->chunks, node) + { + uint16_t tmp = + MIN((uint16_t)CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG_CHUNK_SIZE, + len); + + chunk = CONTAINER_OF(node, struct delayable_msg_chunk, node); + memcpy(net_buf_simple_add(&buf, tmp), chunk->data, tmp); + len -= tmp; + } + + msg->ctx.rnd_delay = false; + + /* Blocking call: must NOT hold the spinlock. */ + + err = bt_mesh_access_send(&msg->ctx, &buf, msg->src_addr, msg->cb, + msg->cb_data); + msg->ctx.rnd_delay = true; + + if (err == -EBUSY || err == -ENOBUFS) + { + /* Transient failure: retry the msg 10 ms later, re-sorted into + * busy_ctx, and re-arm the timer for whatever is now the head. + */ + + key = spin_lock_irqsave(&lock); + msg->fired_time += 10; + put_ctx_to_busy_list(msg); + spin_unlock_irqrestore(&lock, key); + + reschedule_delayable_msg(NULL); + return false; + } + + /* User callback: must NOT hold the spinlock. */ + + complete_delayable_msg(msg, err); + + return true; +} + +static bool push_msg_from_delayable_msgs(void) +{ + struct delayable_msg_ctx *msg = take_earliest_msg(); + + if (!msg) + { + return false; + } + + return send_delayable_msg(msg); +} + +static void delayable_msg_handler(struct k_work *w) +{ + struct delayable_msg_ctx *msg = take_expired_msg(); + + if (msg) + { + (void)send_delayable_msg(msg); + } + + /* Re-arms for the next msg, immediately if one is already due. */ + + reschedule_delayable_msg(NULL); +} + +int bt_mesh_delayable_msg_manage( + struct bt_mesh_msg_ctx *ctx, struct net_buf_simple *buf, uint16_t src_addr, + const struct bt_mesh_send_cb *cb, void *cb_data) +{ + struct list_node *node; + struct delayable_msg_ctx *msg; + uint16_t random_delay; + int total_number = DIV_ROUND_UP(buf->len, + CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG_CHUNK_SIZE); + int allocated_number = 0; + uint16_t len = buf->len; + + if (atomic_test_bit(bt_mesh.flags, BT_MESH_SUSPENDED)) + { + LOG_WRN("Refusing to allocate message context while suspended"); + return -ENODEV; + } + + if (total_number > CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG_CHUNK_COUNT) + { + return -EINVAL; + } + + msg = allocate_delayable_msg_ctx(); + if (!msg) + { + LOG_WRN("No available free delayable message context."); + return -ENOMEM; + } + + do + { + allocated_number += allocate_delayable_msg_chunks(msg, + total_number - allocated_number); + + if (total_number > allocated_number) + { + LOG_DBG("Unable allocate %u chunks, allocated %u", total_number, + allocated_number); + if (!push_msg_from_delayable_msgs()) + { + LOG_WRN("No available chunk memory."); + release_delayable_msg_chunks(msg); + release_delayable_msg_ctx(msg); + return -ENOMEM; + } + } + } + while (total_number > allocated_number); + + list_for_every(&msg->chunks, node) + { + uint16_t tmp = MIN(CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG_CHUNK_SIZE, + buf->len); + + struct delayable_msg_chunk *chunk = CONTAINER_OF(node, + struct delayable_msg_chunk, node); + + memcpy(chunk->data, net_buf_simple_pull_mem(buf, tmp), tmp); + } + + bt_rand(&random_delay, sizeof(uint16_t)); + random_delay = + 20 + + random_delay % (BT_MESH_ADDR_IS_UNICAST(ctx->recv_dst) ? + 30 : 480); + msg->fired_time = + (uint32_t)TICK2MSEC(clock_systime_ticks()) + random_delay; + msg->ctx = *ctx; + msg->src_addr = src_addr; + msg->cb = cb; + msg->cb_data = cb_data; + msg->len = len; + + reschedule_delayable_msg(msg); + + return 0; +} + +void bt_mesh_delayable_msg_init(void) +{ + list_initialize(&access_delayable_msg.busy_ctx); + list_initialize(&access_delayable_msg.free_ctx); + list_initialize(&access_delayable_msg.free_chunks); + + for (int i = 0; i < CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG_COUNT; i++) + { + list_add_tail(&access_delayable_msg.free_ctx, + &delayable_msgs_ctx[i].node); + } + + for (int i = 0; i < CONFIG_BT_MESH_ACCESS_DELAYABLE_MSG_CHUNK_COUNT; i++) + { + list_add_tail(&access_delayable_msg.free_chunks, + &delayable_msg_chunks[i].node); + } +} + +void bt_mesh_delayable_msg_stop(void) +{ + struct list_node *node; + irqstate_t key; + + k_work_cancel_delayable(&access_delayable_msg.random_delay); + + for (; ; ) + { + key = spin_lock_irqsave(&lock); + node = list_remove_head(&access_delayable_msg.busy_ctx); + spin_unlock_irqrestore(&lock, key); + + if (!node) + { + break; + } + + complete_delayable_msg(CONTAINER_OF(node, struct delayable_msg_ctx, + node), -ENODEV); + } +} diff --git a/libs/zblue/mesh/delayable_msg.h b/libs/zblue/mesh/delayable_msg.h new file mode 100644 index 0000000000000..7eff88faf1b07 --- /dev/null +++ b/libs/zblue/mesh/delayable_msg.h @@ -0,0 +1,24 @@ +/**************************************************************************** + * libs/zblue/mesh/delayable_msg.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_DELAYABLE_MSG_H__ +#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_DELAYABLE_MSG_H__ + +int bt_mesh_delayable_msg_manage(struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf, + uint16_t src_addr, + const struct bt_mesh_send_cb *cb, + void *cb_data); +void bt_mesh_delayable_msg_init(void); +void bt_mesh_delayable_msg_stop(void); + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_DELAYABLE_MSG_H__ */ diff --git a/libs/zblue/mesh/dfd.h b/libs/zblue/mesh/dfd.h new file mode 100644 index 0000000000000..79c510e02854f --- /dev/null +++ b/libs/zblue/mesh/dfd.h @@ -0,0 +1,35 @@ +/**************************************************************************** + * libs/zblue/mesh/dfd.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define BT_MESH_DFD_OP_RECEIVERS_ADD BT_MESH_MODEL_OP_2(0x83, 0x11) +#define BT_MESH_DFD_OP_RECEIVERS_DELETE_ALL BT_MESH_MODEL_OP_2(0x83, 0x12) +#define BT_MESH_DFD_OP_RECEIVERS_STATUS BT_MESH_MODEL_OP_2(0x83, 0x13) +#define BT_MESH_DFD_OP_RECEIVERS_GET BT_MESH_MODEL_OP_2(0x83, 0x14) +#define BT_MESH_DFD_OP_RECEIVERS_LIST BT_MESH_MODEL_OP_2(0x83, 0x15) +#define BT_MESH_DFD_OP_CAPABILITIES_GET BT_MESH_MODEL_OP_2(0x83, 0x16) +#define BT_MESH_DFD_OP_CAPABILITIES_STATUS BT_MESH_MODEL_OP_2(0x83, 0x17) +#define BT_MESH_DFD_OP_GET BT_MESH_MODEL_OP_2(0x83, 0x18) +#define BT_MESH_DFD_OP_START BT_MESH_MODEL_OP_2(0x83, 0x19) +#define BT_MESH_DFD_OP_SUSPEND BT_MESH_MODEL_OP_2(0x83, 0x1a) +#define BT_MESH_DFD_OP_CANCEL BT_MESH_MODEL_OP_2(0x83, 0x1b) +#define BT_MESH_DFD_OP_APPLY BT_MESH_MODEL_OP_2(0x83, 0x1c) +#define BT_MESH_DFD_OP_STATUS BT_MESH_MODEL_OP_2(0x83, 0x1d) +#define BT_MESH_DFD_OP_UPLOAD_GET BT_MESH_MODEL_OP_2(0x83, 0x1e) +#define BT_MESH_DFD_OP_UPLOAD_START BT_MESH_MODEL_OP_2(0x83, 0x1f) +#define BT_MESH_DFD_OP_UPLOAD_START_OOB BT_MESH_MODEL_OP_2(0x83, 0x20) +#define BT_MESH_DFD_OP_UPLOAD_CANCEL BT_MESH_MODEL_OP_2(0x83, 0x21) +#define BT_MESH_DFD_OP_UPLOAD_STATUS BT_MESH_MODEL_OP_2(0x83, 0x22) +#define BT_MESH_DFD_OP_FW_GET BT_MESH_MODEL_OP_2(0x83, 0x23) +#define BT_MESH_DFD_OP_FW_GET_BY_INDEX BT_MESH_MODEL_OP_2(0x83, 0x24) +#define BT_MESH_DFD_OP_FW_DELETE BT_MESH_MODEL_OP_2(0x83, 0x25) +#define BT_MESH_DFD_OP_FW_DELETE_ALL BT_MESH_MODEL_OP_2(0x83, 0x26) +#define BT_MESH_DFD_OP_FW_STATUS BT_MESH_MODEL_OP_2(0x83, 0x27) diff --git a/libs/zblue/mesh/dfd_srv.c b/libs/zblue/mesh/dfd_srv.c new file mode 100644 index 0000000000000..91a15cd4acf6c --- /dev/null +++ b/libs/zblue/mesh/dfd_srv.c @@ -0,0 +1,2001 @@ +/**************************************************************************** + * libs/zblue/mesh/dfd_srv.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include "dfu_slot.h" +#include "dfd.h" +#include "dfu.h" +#include "dfd_srv_internal.h" +#include "net.h" +#include "transport.h" +#include "access.h" + +#define LOG_LEVEL CONFIG_BT_MESH_DFU_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_dfd_srv); + +#define DFD_UPLOAD_STATUS_MSG_MAXLEN (5 + CONFIG_BT_MESH_DFU_FWID_MAXLEN) + +BUILD_ASSERT((DFD_UPLOAD_STATUS_MSG_MAXLEN + + BT_MESH_MODEL_OP_LEN(BT_MESH_DFD_OP_UPLOAD_STATUS) + + BT_MESH_MIC_SHORT) <= BT_MESH_TX_SDU_MAX, + "The Firmware Distribution Upload Status message does not " + "fit into the maximum outgoing SDU size."); + +#define DFD_UPLOAD_START_MSG_MAXLEN \ + (16 + CONFIG_BT_MESH_DFU_FWID_MAXLEN + CONFIG_BT_MESH_DFU_METADATA_MAXLEN) + +BUILD_ASSERT((DFD_UPLOAD_START_MSG_MAXLEN + + BT_MESH_MODEL_OP_LEN(BT_MESH_DFD_OP_UPLOAD_START) + + BT_MESH_MIC_SHORT) <= BT_MESH_RX_SDU_MAX, + "The Firmware Distribution Upload Start message does not " + "fit into the maximum incoming SDU size."); + +#define DFD_RECEIVERS_LIST_MSG_MAXLEN \ + (BT_MESH_TX_SDU_MAX - BT_MESH_MIC_SHORT - BT_MESH_MODEL_OP_LEN(BT_MESH_DFD_OP_RECEIVERS_LIST)) + +#define DFD_RECEIVERS_ADD_MSG_MAXLEN (CONFIG_BT_MESH_DFD_SRV_TARGETS_MAX * 3) + +BUILD_ASSERT((DFD_RECEIVERS_ADD_MSG_MAXLEN + + BT_MESH_MODEL_OP_LEN(BT_MESH_DFD_OP_RECEIVERS_ADD) + + BT_MESH_MIC_SHORT) <= BT_MESH_RX_SDU_MAX, + "The Firmware Distribution Receivers Add message does not " + "fit into the maximum incoming SDU size."); + +struct slot_search_ctx +{ + off_t offset; + size_t size; + bool failed; +}; + +struct dfd_srv_persistent_target +{ + uint16_t addr; + uint8_t img_idx; + uint8_t phase; + uint8_t status; + uint8_t effect; +} __packed; + +struct dfd_srv_persistent_state +{ + uint16_t target_cnt; + uint16_t slot_idx; + uint16_t app_idx; + uint16_t group; + uint16_t timeout_base; + uint8_t phase; + uint8_t apply; + uint8_t ttl; + uint8_t xfer_mode; + struct dfd_srv_persistent_target + targets[CONFIG_BT_MESH_DFD_SRV_TARGETS_MAX]; +} __packed; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int store_state(struct bt_mesh_dfd_srv *srv) +{ + struct dfd_srv_persistent_state state = + { + .phase = srv->phase, + .apply = srv->apply, + .target_cnt = srv->target_cnt, + .slot_idx = srv->slot_idx, + .app_idx = srv->inputs.app_idx, + .group = srv->inputs.group, + .ttl = srv->inputs.ttl, + .timeout_base = srv->inputs.timeout_base, + .xfer_mode = srv->dfu.xfer.blob.mode, + }; + + uint16_t cnt = MIN(srv->target_cnt, ARRAY_SIZE(srv->targets)); + + for (uint16_t i = 0; i < srv->target_cnt && + i < ARRAY_SIZE(srv->targets); i++) + { + state.targets[i].addr = srv->targets[i].blob.addr; + state.targets[i].img_idx = srv->targets[i].img_idx; + state.targets[i].phase = srv->targets[i].phase; + state.targets[i].status = srv->targets[i].status; + state.targets[i].effect = srv->targets[i].effect; + } + + return bt_mesh_model_data_store(srv->mod, false, NULL, &state, + offsetof(struct dfd_srv_persistent_state, + targets) + + cnt * sizeof( + struct + dfd_srv_persistent_target)); +} + +static void erase_state(struct bt_mesh_dfd_srv *srv) +{ + bt_mesh_model_data_store(srv->mod, false, NULL, NULL, 0); +} + +static void dfd_phase_set(struct bt_mesh_dfd_srv *srv, + enum bt_mesh_dfd_phase new_phase) +{ + srv->phase = new_phase; + + /* Store at Applying Update for self-update resume, erase at + * terminal states to prevent re-triggering after reboot. + */ + + switch (new_phase) + { + case BT_MESH_DFD_PHASE_IDLE: + case BT_MESH_DFD_PHASE_COMPLETED: + case BT_MESH_DFD_PHASE_FAILED: + erase_state(srv); + break; + case BT_MESH_DFD_PHASE_APPLYING_UPDATE: + if (store_state(srv)) + { + /* Without persisted state the self-apply reboot would be + * unrecoverable, so fail the distribution instead. + */ + + LOG_ERR("Failed to store DFD state, failing distribution"); + srv->phase = BT_MESH_DFD_PHASE_FAILED; + erase_state(srv); + } + break; + default: + break; + } + + if (srv->cb && srv->cb->phase) + { + srv->cb->phase(srv, srv->phase); + } +} + +static struct bt_mesh_dfu_target *target_get(struct bt_mesh_dfd_srv *srv, + uint16_t addr) +{ + for (int i = 0; i < srv->target_cnt; ++i) + { + if (addr == srv->targets[i].blob.addr) + { + return &srv->targets[i]; + } + } + + return NULL; +} + +static bool is_busy(const struct bt_mesh_dfd_srv *srv) +{ + return srv->phase == BT_MESH_DFD_PHASE_TRANSFER_ACTIVE || + srv->phase == BT_MESH_DFD_PHASE_TRANSFER_SUCCESS || + srv->phase == BT_MESH_DFD_PHASE_APPLYING_UPDATE; +} + +static bool upload_is_busy(const struct bt_mesh_dfd_srv *srv) +{ + return bt_mesh_blob_srv_is_busy(&srv->upload.blob) || + srv->upload.phase == BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_ACTIVE; +} + +static int slot_del(struct bt_mesh_dfd_srv *srv, + const struct bt_mesh_dfu_slot *slot) +{ + if (srv->cb && srv->cb->del) + { + srv->cb->del(srv, slot); + } + + return bt_mesh_dfu_slot_del(slot); +} + +static void receivers_status_rsp( + struct bt_mesh_dfd_srv *srv, struct bt_mesh_msg_ctx *ctx, + enum bt_mesh_dfd_status status) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_DFD_OP_RECEIVERS_STATUS, 3); + bt_mesh_model_msg_init(&buf, BT_MESH_DFD_OP_RECEIVERS_STATUS); + + net_buf_simple_add_u8(&buf, status); + net_buf_simple_add_le16(&buf, srv->target_cnt); + + bt_mesh_model_send(srv->mod, ctx, &buf, NULL, NULL); +} + +static int handle_receivers_add( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + enum bt_mesh_dfd_status status = BT_MESH_DFD_SUCCESS; + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + + if (buf->len % 3) + { + return -EINVAL; + } + + if (bt_mesh_dfu_cli_is_busy(&srv->dfu)) + { + receivers_status_rsp(srv, ctx, BT_MESH_DFD_ERR_BUSY_WITH_DISTRIBUTION); + return 0; + } + + while (buf->len >= 3 && status == BT_MESH_DFD_SUCCESS) + { + uint8_t img_idx; + uint16_t addr; + + addr = net_buf_simple_pull_le16(buf); + img_idx = net_buf_simple_pull_u8(buf); + + status = bt_mesh_dfd_srv_receiver_add(srv, addr, img_idx); + } + + receivers_status_rsp(srv, ctx, status); + + return 0; +} + +static int handle_receivers_delete_all( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + + receivers_status_rsp(srv, ctx, bt_mesh_dfd_srv_receivers_delete_all(srv)); + + return 0; +} + +static int handle_receivers_get( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + uint16_t first; + uint16_t cnt; + uint8_t progress; + int i; + + first = net_buf_simple_pull_le16(buf); + cnt = net_buf_simple_pull_le16(buf); + if (cnt == 0) + { + return -EINVAL; + } + + /* Create a buffer that can fit the full target list, maxing out at + * TX_SDU_MAX: + */ + + NET_BUF_SIMPLE_DEFINE( + rsp, BT_MESH_MODEL_BUF_LEN(BT_MESH_DFD_OP_RECEIVERS_LIST, + DFD_RECEIVERS_LIST_MSG_MAXLEN)); + bt_mesh_model_msg_init(&rsp, BT_MESH_DFD_OP_RECEIVERS_LIST); + + net_buf_simple_add_le16(&rsp, srv->target_cnt); + net_buf_simple_add_le16(&rsp, first); + + /* The spec mandates a response with an omitted list when First Index is + * out of range. + */ + + if (first >= srv->target_cnt) + { + cnt = 0; + } + else + { + cnt = MIN(cnt, (uint16_t)(srv->target_cnt - first)); + } + + progress = bt_mesh_dfu_cli_progress(&srv->dfu) / 2; + + for (i = 0; i < cnt + && net_buf_simple_tailroom(&rsp) >= 5 + BT_MESH_MIC_SHORT; i++) + { + const struct bt_mesh_dfu_target *t = &srv->targets[i + first]; + + net_buf_simple_add_le32(&rsp, (t->blob.addr & BIT_MASK(15)) | + ((t->phase & BIT_MASK(4)) << 15U) | + ((t->status & BIT_MASK(3)) << 19U) | + ((t->blob.status & BIT_MASK(4)) << 22U) | + ((progress & BIT_MASK(6)) << 26U)); + net_buf_simple_add_u8(&rsp, t->img_idx); + } + + bt_mesh_model_send(srv->mod, ctx, &rsp, NULL, NULL); + + return 0; +} + +static enum bt_mesh_dfu_iter slot_space_cb( + const struct bt_mesh_dfu_slot *slot, void *user_data) +{ + size_t *total = user_data; + + *total += slot->size; + + return BT_MESH_DFU_ITER_CONTINUE; +} + +static int handle_capabilities_get( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + size_t size = 0; + + BT_MESH_MODEL_BUF_DEFINE(rsp, BT_MESH_DFD_OP_CAPABILITIES_STATUS, 17); + bt_mesh_model_msg_init(&rsp, BT_MESH_DFD_OP_CAPABILITIES_STATUS); + + net_buf_simple_add_le16(&rsp, CONFIG_BT_MESH_DFD_SRV_TARGETS_MAX); + net_buf_simple_add_le16(&rsp, CONFIG_BT_MESH_DFU_SLOT_CNT); + net_buf_simple_add_le32(&rsp, CONFIG_BT_MESH_DFD_SRV_SLOT_MAX_SIZE); + net_buf_simple_add_le32(&rsp, CONFIG_BT_MESH_DFD_SRV_SLOT_SPACE); + + /* Remaining size */ + + (void)bt_mesh_dfu_slot_foreach(slot_space_cb, &size); + size = MIN(size, CONFIG_BT_MESH_DFD_SRV_SLOT_SPACE); + + net_buf_simple_add_le32(&rsp, CONFIG_BT_MESH_DFD_SRV_SLOT_SPACE - size); + +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + + if (srv->oob_schemes.count > 0) + { + net_buf_simple_add_u8(&rsp, 1); + net_buf_simple_add_mem(&rsp, srv->oob_schemes.schemes, + srv->oob_schemes.count); + } + else +#endif + { + net_buf_simple_add_u8(&rsp, 0); + } + + bt_mesh_model_send(mod, ctx, &rsp, NULL, NULL); + + return 0; +} + +static void status_rsp( + struct bt_mesh_dfd_srv *srv, struct bt_mesh_msg_ctx *ctx, + enum bt_mesh_dfd_status status) +{ + BT_MESH_MODEL_BUF_DEFINE(rsp, BT_MESH_DFD_OP_STATUS, 12); + bt_mesh_model_msg_init(&rsp, BT_MESH_DFD_OP_STATUS); + + net_buf_simple_add_u8(&rsp, status); + net_buf_simple_add_u8(&rsp, srv->phase); + + if (srv->phase == BT_MESH_DFD_PHASE_IDLE || !srv->dfu.xfer.slot) + { + bt_mesh_model_send(srv->mod, ctx, &rsp, NULL, NULL); + return; + } + + net_buf_simple_add_le16(&rsp, srv->inputs.group); + net_buf_simple_add_le16(&rsp, srv->inputs.app_idx); + net_buf_simple_add_u8(&rsp, srv->inputs.ttl); + net_buf_simple_add_le16(&rsp, srv->inputs.timeout_base); + net_buf_simple_add_u8(&rsp, + (srv->dfu.xfer.blob.mode & BIT_MASK(2)) | + ((srv->apply & BIT_MASK(1)) << 2)); + net_buf_simple_add_le16(&rsp, srv->slot_idx); + + bt_mesh_model_send(srv->mod, ctx, &rsp, NULL, NULL); +} + +static int handle_get( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + + status_rsp(srv, ctx, BT_MESH_DFD_SUCCESS); + + return 0; +} + +static int handle_start( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + struct bt_mesh_dfd_start_params params; + uint8_t byte; + + params.app_idx = net_buf_simple_pull_le16(buf); + params.ttl = net_buf_simple_pull_u8(buf); + params.timeout_base = net_buf_simple_pull_le16(buf); + byte = net_buf_simple_pull_u8(buf); + params.xfer_mode = byte & BIT_MASK(2); + params.apply = (byte >> 2U) & BIT_MASK(1); + params.slot_idx = net_buf_simple_pull_le16(buf); + + if (buf->len == 16) + { + /* TODO: Virtual addresses not supported. */ + + status_rsp(srv, ctx, BT_MESH_DFD_ERR_INTERNAL); + return 0; + } + + if (buf->len != 2) + { + return -EINVAL; + } + + params.group = net_buf_simple_pull_le16(buf); + + status_rsp(srv, ctx, bt_mesh_dfd_srv_start(srv, ¶ms)); + + return 0; +} + +static int handle_suspend( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + + status_rsp(srv, ctx, bt_mesh_dfd_srv_suspend(srv)); + + return 0; +} + +static int handle_cancel( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + + bt_mesh_dfd_srv_cancel(srv, ctx); + + return 0; +} + +static int handle_apply( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + + status_rsp(srv, ctx, bt_mesh_dfd_srv_apply(srv)); + + return 0; +} + +static void upload_status_rsp_with_progress(struct bt_mesh_dfd_srv *srv, + struct bt_mesh_msg_ctx *ctx, + enum bt_mesh_dfd_status status, + uint8_t progress) +{ + BT_MESH_MODEL_BUF_DEFINE(rsp, + BT_MESH_DFD_OP_UPLOAD_STATUS, + DFD_UPLOAD_STATUS_MSG_MAXLEN); + bt_mesh_model_msg_init(&rsp, BT_MESH_DFD_OP_UPLOAD_STATUS); + + net_buf_simple_add_u8(&rsp, status); + net_buf_simple_add_u8(&rsp, srv->upload.phase); + + if (srv->upload.phase == BT_MESH_DFD_UPLOAD_PHASE_IDLE + || !srv->upload.slot) + { + bt_mesh_model_send(srv->mod, ctx, &rsp, NULL, NULL); + return; + } + +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD + if (srv->upload.is_oob) + { + net_buf_simple_add_u8(&rsp, progress | BIT(7)); + net_buf_simple_add_mem(&rsp, srv->upload.oob.current_fwid, + srv->upload.oob.current_fwid_len); + } + else +#endif + { + net_buf_simple_add_u8(&rsp, progress); + net_buf_simple_add_mem(&rsp, srv->upload.slot->fwid, + srv->upload.slot->fwid_len); + } + + bt_mesh_model_send(srv->mod, ctx, &rsp, NULL, NULL); +} + +static void upload_status_rsp( + struct bt_mesh_dfd_srv *srv, struct bt_mesh_msg_ctx *ctx, + enum bt_mesh_dfd_status status) +{ + uint8_t progress; + +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD + if (srv->upload.is_oob) + { + progress = srv->cb->oob_progress_get(srv, srv->upload.slot); + } + else +#endif + { + progress = bt_mesh_blob_srv_progress(&srv->upload.blob); + } + + upload_status_rsp_with_progress(srv, ctx, status, progress); +} + +static int handle_upload_get( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + + upload_status_rsp(srv, ctx, BT_MESH_DFD_SUCCESS); + + return 0; +} + +static inline int set_upload_fwid( + struct bt_mesh_dfd_srv *srv, struct bt_mesh_msg_ctx *ctx, + const uint8_t *fwid, size_t fwid_len) +{ + int err = bt_mesh_dfu_slot_fwid_set(srv->upload.slot, fwid, fwid_len); + + switch (err) + { + case -EFBIG: /* Fwid too long */ + case -EALREADY: /* Other server is in progress with this fwid */ + bt_mesh_dfu_slot_release(srv->upload.slot); + srv->upload.slot = NULL; + upload_status_rsp(srv, ctx, BT_MESH_DFD_ERR_INTERNAL); + break; + case -EEXIST: /* Img with this fwid already is in list */ + srv->upload.phase = BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_SUCCESS; + bt_mesh_dfu_slot_release(srv->upload.slot); + + err = bt_mesh_dfu_slot_get(fwid, fwid_len, &srv->upload.slot); + if (!err) + { + upload_status_rsp_with_progress(srv, + ctx, + BT_MESH_DFD_SUCCESS, + 100); + } + else + { + srv->upload.slot = NULL; + upload_status_rsp(srv, ctx, BT_MESH_DFD_ERR_INTERNAL); + } + break; + case 0: + srv->upload.phase = BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_ACTIVE; + break; + case -EINVAL: /* Slot in wrong state. */ + default: + break; + } + + return err; +} + +static int handle_upload_start( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + size_t meta_len; + size_t fwid_len; + size_t size; + const uint8_t *meta; + const uint8_t *fwid; + uint16_t timeout_base; + uint64_t blob_id; + int err; + uint8_t ttl; + + ttl = net_buf_simple_pull_u8(buf); + timeout_base = net_buf_simple_pull_le16(buf); + blob_id = net_buf_simple_pull_le64(buf); + size = net_buf_simple_pull_le32(buf); + meta_len = net_buf_simple_pull_u8(buf); + if (buf->len < meta_len) + { + return -EINVAL; + } + + meta = net_buf_simple_pull_mem(buf, meta_len); + fwid_len = buf->len; + fwid = net_buf_simple_pull_mem(buf, fwid_len); + + LOG_DBG("Upload Start: size: %d, fwid: %s, metadata: " + "%s", size, bt_hex(fwid, + fwid_len), + bt_hex(meta, meta_len)); + + if (size > CONFIG_BT_MESH_DFD_SRV_SLOT_MAX_SIZE) + { + upload_status_rsp(srv, ctx, BT_MESH_DFD_ERR_INSUFFICIENT_RESOURCES); + return 0; + } + + if (upload_is_busy(srv)) + { + if (!srv->upload.slot) + { + LOG_WRN("Busy with no upload slot"); + upload_status_rsp(srv, ctx, BT_MESH_DFD_ERR_INTERNAL); + return 0; + } + + if (srv->upload.slot->fwid_len == fwid_len && + !memcmp(srv->upload.slot->fwid, fwid, fwid_len) && + srv->upload.slot->metadata_len == meta_len && + !memcmp(srv->upload.slot->metadata, meta, meta_len) && + srv->upload.blob.state.xfer.id == blob_id && + srv->upload.blob.state.ttl == ttl && + srv->upload.blob.state.timeout_base == timeout_base +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD + && !srv->upload.is_oob +#endif + ) + { + LOG_DBG("Duplicate upload start"); + upload_status_rsp(srv, ctx, BT_MESH_DFD_SUCCESS); + return 0; + } + + LOG_WRN("Upload already in progress"); + upload_status_rsp(srv, ctx, BT_MESH_DFD_ERR_BUSY_WITH_UPLOAD); + return 0; + } + + /* This will be a no-op if the slot state isn't RESERVED, which is + * what we want. + */ + + if (srv->upload.slot) + { + bt_mesh_dfu_slot_release(srv->upload.slot); + } + +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD + srv->upload.is_oob = false; +#endif + srv->upload.slot = bt_mesh_dfu_slot_reserve(); + + if (!srv->upload.slot) + { + LOG_WRN("No space for slot"); + upload_status_rsp(srv, ctx, BT_MESH_DFD_ERR_INSUFFICIENT_RESOURCES); + return 0; + } + + err = set_upload_fwid(srv, ctx, fwid, fwid_len); + if (err) + { + return err; + } + + err = bt_mesh_dfu_slot_info_set(srv->upload.slot, size, meta, meta_len); + switch (err) + { + case -EFBIG: + upload_status_rsp(srv, ctx, BT_MESH_DFD_ERR_INTERNAL); + break; + case 0: + break; + default: + return err; + } + + srv->io = NULL; + err = srv->cb->recv(srv, srv->upload.slot, &srv->io); + if (err || !srv->io) + { + LOG_ERR("App rejected upload. err: %d io: %p", err, srv->io); + bt_mesh_dfu_slot_release(srv->upload.slot); + upload_status_rsp(srv, ctx, BT_MESH_DFD_ERR_INTERNAL); + return 0; + } + + err = bt_mesh_blob_srv_recv(&srv->upload.blob, blob_id, srv->io, ttl, + timeout_base); + if (err) + { + LOG_ERR("BLOB Server rejected upload (err: %d)", err); + bt_mesh_dfu_slot_release(srv->upload.slot); + upload_status_rsp(srv, ctx, BT_MESH_DFD_ERR_INTERNAL); + return 0; + } + + LOG_DBG("%s", bt_hex(fwid, fwid_len)); + + srv->upload.phase = BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_ACTIVE; + upload_status_rsp(srv, ctx, BT_MESH_DFD_SUCCESS); + + return 0; +} + +static int handle_upload_start_oob( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + uint8_t uri_len; + uint8_t *uri; + uint16_t fwid_len; + uint8_t *fwid; + + uri_len = net_buf_simple_pull_u8(buf); + + if (uri_len > buf->len) + { + return -EINVAL; + } + + uri = net_buf_simple_pull_mem(buf, uri_len); + fwid_len = buf->len; + fwid = net_buf_simple_pull_mem(buf, fwid_len); + + LOG_DBG("Upload OOB Start"); + LOG_HEXDUMP_DBG(uri, uri_len, "URI"); + LOG_HEXDUMP_DBG(fwid, fwid_len, "FWID"); + + if (upload_is_busy(srv)) + { +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD + if (srv->upload.is_oob && uri_len == srv->upload.oob.uri_len && + fwid_len == srv->upload.oob.current_fwid_len && + !memcmp(uri, srv->upload.oob.uri, uri_len) && + !memcmp(fwid, srv->upload.oob.current_fwid, fwid_len)) + { + /* Same image, return SUCCESS for idempotency */ + + upload_status_rsp(srv, ctx, BT_MESH_DFD_SUCCESS); + return 0; + } +#endif + + upload_status_rsp(srv, ctx, BT_MESH_DFD_ERR_BUSY_WITH_UPLOAD); + return 0; +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD + } + else if (srv->upload.is_oob && srv->upload.is_pending_oob_check) + { + /* Ignore the request if we didn't confirm the previous one. */ + + return 0; +#endif + } + +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD + if (uri_len > CONFIG_BT_MESH_DFU_URI_MAXLEN || + fwid_len > CONFIG_BT_MESH_DFU_FWID_MAXLEN) + { + upload_status_rsp(srv, ctx, BT_MESH_DFD_ERR_INTERNAL); + return 0; + } + + struct bt_mesh_dfu_slot *slot = bt_mesh_dfu_slot_reserve(); + + if (slot == NULL) + { + upload_status_rsp(srv, ctx, BT_MESH_DFD_ERR_INSUFFICIENT_RESOURCES); + return 0; + } + + /* This will be a no-op if the slot state isn't RESERVED, which is + * what we want. + */ + + if (srv->upload.slot) + { + bt_mesh_dfu_slot_release(srv->upload.slot); + } + + srv->upload.is_oob = true; + srv->upload.slot = slot; + memcpy(srv->upload.oob.uri, uri, uri_len); + srv->upload.oob.uri_len = uri_len; + memcpy(srv->upload.oob.current_fwid, fwid, fwid_len); + srv->upload.oob.current_fwid_len = fwid_len; + memcpy(&srv->upload.oob.ctx, ctx, sizeof(struct bt_mesh_msg_ctx)); + + int status = srv->cb->start_oob_upload(srv, srv->upload.slot, + srv->upload.oob.uri, + srv->upload.oob.uri_len, + srv->upload.oob.current_fwid, + srv->upload.oob.current_fwid_len); + + if (status != BT_MESH_DFD_SUCCESS) + { + upload_status_rsp(srv, ctx, status); + bt_mesh_dfu_slot_release(srv->upload.slot); + } + else + { + srv->upload.is_pending_oob_check = true; + } +#else + upload_status_rsp(srv, ctx, BT_MESH_DFD_ERR_URI_NOT_SUPPORTED); +#endif /* CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD */ + + return 0; +} + +static int handle_upload_cancel( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + + srv->upload.phase = BT_MESH_DFD_UPLOAD_PHASE_IDLE; +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD + if (srv->upload.is_oob) + { + srv->cb->cancel_oob_upload(srv, srv->upload.slot); + } + else +#endif + { + (void)bt_mesh_blob_srv_cancel(&srv->upload.blob); + } + + upload_status_rsp(srv, ctx, BT_MESH_DFD_SUCCESS); + + return 0; +} + +static void fw_status_rsp( + struct bt_mesh_dfd_srv *srv, struct bt_mesh_msg_ctx *ctx, + enum bt_mesh_dfd_status status, uint16_t idx, const uint8_t *fwid, + size_t fwid_len) +{ + BT_MESH_MODEL_BUF_DEFINE(rsp, + BT_MESH_DFD_OP_FW_STATUS, + 7 + CONFIG_BT_MESH_DFU_FWID_MAXLEN); + bt_mesh_model_msg_init(&rsp, BT_MESH_DFD_OP_FW_STATUS); + + net_buf_simple_add_u8(&rsp, status); + net_buf_simple_add_le16(&rsp, bt_mesh_dfu_slot_count()); + + net_buf_simple_add_le16(&rsp, idx); + if (fwid) + { + net_buf_simple_add_mem(&rsp, fwid, fwid_len); + } + + bt_mesh_model_send(srv->mod, ctx, &rsp, NULL, NULL); +} + +static int handle_fw_get( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + struct bt_mesh_dfu_slot *slot; + const uint8_t *fwid; + size_t fwid_len; + int idx; + + fwid_len = buf->len; + fwid = net_buf_simple_pull_mem(buf, fwid_len); + + idx = bt_mesh_dfu_slot_get(fwid, fwid_len, &slot); + if (idx >= 0) + { + fw_status_rsp(srv, ctx, BT_MESH_DFD_SUCCESS, idx, fwid, fwid_len); + } + else + { + fw_status_rsp(srv, ctx, BT_MESH_DFD_ERR_FW_NOT_FOUND, 0xffff, fwid, + fwid_len); + } + + return 0; +} + +static int handle_fw_get_by_index( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + const struct bt_mesh_dfu_slot *slot; + uint16_t idx; + + idx = net_buf_simple_pull_le16(buf); + + slot = bt_mesh_dfu_slot_at(idx); + if (slot) + { + fw_status_rsp(srv, ctx, BT_MESH_DFD_SUCCESS, idx, slot->fwid, + slot->fwid_len); + } + else + { + fw_status_rsp(srv, ctx, BT_MESH_DFD_ERR_FW_NOT_FOUND, idx, NULL, 0); + } + + return 0; +} + +static int handle_fw_delete( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + const uint8_t *fwid; + size_t fwid_len; + + fwid_len = buf->len; + fwid = net_buf_simple_pull_mem(buf, fwid_len); + + enum bt_mesh_dfd_status status = bt_mesh_dfd_srv_fw_delete(srv, &fwid_len, + &fwid); + + fw_status_rsp(srv, ctx, status, 0xffff, fwid, fwid_len); + + return 0; +} + +static enum bt_mesh_dfu_iter slot_del_cb(const struct bt_mesh_dfu_slot *slot, + void *user_data) +{ + struct bt_mesh_dfd_srv *srv = user_data; + + if (srv->cb && srv->cb->del) + { + srv->cb->del(srv, slot); + } + + return BT_MESH_DFU_ITER_CONTINUE; +} + +static int handle_fw_delete_all( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + + fw_status_rsp(srv, ctx, bt_mesh_dfd_srv_fw_delete_all(srv), 0xffff, NULL, + 0); + + return 0; +} + +const struct bt_mesh_model_op _bt_mesh_dfd_srv_op[] = +{ + { + BT_MESH_DFD_OP_RECEIVERS_ADD, + BT_MESH_LEN_MIN(3), + handle_receivers_add + }, + + { + BT_MESH_DFD_OP_RECEIVERS_DELETE_ALL, + BT_MESH_LEN_EXACT(0), + handle_receivers_delete_all + }, + + { + BT_MESH_DFD_OP_RECEIVERS_GET, + BT_MESH_LEN_EXACT(4), + handle_receivers_get + }, + + { + BT_MESH_DFD_OP_CAPABILITIES_GET, + BT_MESH_LEN_EXACT(0), + handle_capabilities_get + }, + + { + BT_MESH_DFD_OP_GET, + BT_MESH_LEN_EXACT(0), + handle_get + }, + + { + BT_MESH_DFD_OP_START, + BT_MESH_LEN_MIN(10), + handle_start + }, + + { + BT_MESH_DFD_OP_SUSPEND, + BT_MESH_LEN_EXACT(0), + handle_suspend + }, + + { + BT_MESH_DFD_OP_CANCEL, + BT_MESH_LEN_EXACT(0), + handle_cancel + }, + + { + BT_MESH_DFD_OP_APPLY, + BT_MESH_LEN_EXACT(0), + handle_apply + }, + + { + BT_MESH_DFD_OP_UPLOAD_GET, + BT_MESH_LEN_EXACT(0), + handle_upload_get + }, + + { + BT_MESH_DFD_OP_UPLOAD_START, + BT_MESH_LEN_MIN(16), + handle_upload_start + }, + + { + BT_MESH_DFD_OP_UPLOAD_START_OOB, + BT_MESH_LEN_MIN(2), + handle_upload_start_oob + }, + + { + BT_MESH_DFD_OP_UPLOAD_CANCEL, + BT_MESH_LEN_EXACT(0), + handle_upload_cancel + }, + + { + BT_MESH_DFD_OP_FW_GET, + BT_MESH_LEN_MIN(0), + handle_fw_get + }, + + { + BT_MESH_DFD_OP_FW_GET_BY_INDEX, + BT_MESH_LEN_EXACT(2), + handle_fw_get_by_index + }, + + { + BT_MESH_DFD_OP_FW_DELETE, + BT_MESH_LEN_MIN(0), + handle_fw_delete + }, + + { + BT_MESH_DFD_OP_FW_DELETE_ALL, + BT_MESH_LEN_EXACT(0), + handle_fw_delete_all + }, + + BT_MESH_MODEL_OP_END +}; + +static void dfu_suspended(struct bt_mesh_dfu_cli *cli) +{ + struct bt_mesh_dfd_srv *srv = CONTAINER_OF(cli, struct bt_mesh_dfd_srv, + dfu); + + dfd_phase_set(srv, BT_MESH_DFD_PHASE_TRANSFER_SUSPENDED); +} + +static struct bt_mesh_dfu_srv *self_target_dfu_srv( + struct bt_mesh_dfd_srv *srv) +{ + /* The spec requires the DFU Server (Target role element Y) to be on + * a different element from the DFD Server (Distributor role element X). + * Find the local DFU Server by looking up the element that owns the + * self-target unicast address stored in the receivers list. + */ + + for (uint16_t i = 0; i < srv->target_cnt && + i < ARRAY_SIZE(srv->targets); i++) + { + if (bt_mesh_has_addr(srv->targets[i].blob.addr)) + { + const struct bt_mesh_elem *elem = + bt_mesh_elem_find(srv->targets[i].blob.addr); + const struct bt_mesh_model *mod = bt_mesh_model_find(elem, + BT_MESH_MODEL_ID_DFU_SRV); + + /* A local element without a DFU Server is not the Target + * element; it times out as an ordinary target. + */ + + if (mod) + { + return mod->rt->user_data; + } + } + } + + return NULL; +} + +static void dfd_srv_find_cb( + const struct bt_mesh_model *mod, const struct bt_mesh_elem *elem, bool vnd, + bool primary, void *user_data) +{ + struct bt_mesh_dfd_srv **srv = user_data; + + if (!vnd && mod->id == BT_MESH_MODEL_ID_DFD_SRV) + { + *srv = mod->rt->user_data; + } +} + +void bt_mesh_dfd_srv_self_applied(void) +{ + struct bt_mesh_dfd_srv *srv = NULL; + + bt_mesh_model_foreach(dfd_srv_find_cb, &srv); + if (!srv || srv->phase != BT_MESH_DFD_PHASE_APPLYING_UPDATE) + { + return; + } + + dfd_phase_set(srv, BT_MESH_DFD_PHASE_COMPLETED); +} + +static int trigger_self_apply(struct bt_mesh_dfd_srv *srv) +{ + struct bt_mesh_dfu_srv *dfu_srv; + + if (!IS_ENABLED(CONFIG_BT_MESH_DFU_SRV)) + { + return 0; + } + + /* Trigger self-target apply after distribution completes or fails. + * The DFU Server deferred its apply callback to let the Distributor finish + * the confirm step first. + */ + + dfu_srv = self_target_dfu_srv(srv); + if (!dfu_srv) + { + return 0; + } + + return bt_mesh_dfu_srv_apply_deferred(dfu_srv); +} + +static void cancel_self_apply(struct bt_mesh_dfd_srv *srv) +{ + struct bt_mesh_dfu_srv *dfu_srv; + + if (!IS_ENABLED(CONFIG_BT_MESH_DFU_SRV)) + { + return; + } + + dfu_srv = self_target_dfu_srv(srv); + if (!dfu_srv) + { + return; + } + + bt_mesh_dfu_srv_apply_cancel(dfu_srv); +} + +static void dfu_ended(struct bt_mesh_dfu_cli *cli, + enum bt_mesh_dfu_status reason) +{ + struct bt_mesh_dfd_srv *srv = CONTAINER_OF(cli, struct bt_mesh_dfd_srv, + dfu); + int err; + + LOG_DBG("reason: %u, phase: %u, apply: " + "%u", reason, srv->phase, srv->apply); + + if (srv->phase == BT_MESH_DFD_PHASE_IDLE) + { + return; + } + + if (srv->phase == BT_MESH_DFD_PHASE_CANCELING_UPDATE) + { + dfd_phase_set(srv, BT_MESH_DFD_PHASE_IDLE); + return; + } + + if (reason != BT_MESH_DFU_SUCCESS) + { + dfd_phase_set(srv, BT_MESH_DFD_PHASE_FAILED); + /* Remote targets failed to confirm (e.g. no target + * reported new FWID). Distribution is over, safe to apply deferred + * self-update. + */ + + (void)trigger_self_apply(srv); + return; + } + + if (!srv->apply) + { + dfd_phase_set(srv, BT_MESH_DFD_PHASE_TRANSFER_SUCCESS); + return; + } + + /* dfd_phase_set() downgrades to FAILED if the state cannot be persisted. */ + + dfd_phase_set(srv, BT_MESH_DFD_PHASE_APPLYING_UPDATE); + if (srv->phase != BT_MESH_DFD_PHASE_APPLYING_UPDATE) + { + return; + } + + err = bt_mesh_dfu_cli_apply(cli); + if (err) + { + LOG_ERR("Apply failed: %d", err); + dfd_phase_set(srv, BT_MESH_DFD_PHASE_FAILED); + } +} + +static void dfu_applied(struct bt_mesh_dfu_cli *cli) +{ + struct bt_mesh_dfd_srv *srv = CONTAINER_OF(cli, struct bt_mesh_dfd_srv, + dfu); + int err; + + if (srv->phase == BT_MESH_DFD_PHASE_CANCELING_UPDATE) + { + dfd_phase_set(srv, BT_MESH_DFD_PHASE_FAILED); + return; + } + + if (srv->phase != BT_MESH_DFD_PHASE_APPLYING_UPDATE) + { + return; + } + + err = bt_mesh_dfu_cli_confirm(cli); + if (err) + { + LOG_ERR("Confirm failed: %d", err); + dfd_phase_set(srv, BT_MESH_DFD_PHASE_FAILED); + } +} + +static void dfu_confirmed(struct bt_mesh_dfu_cli *cli) +{ + struct bt_mesh_dfd_srv *srv = CONTAINER_OF(cli, struct bt_mesh_dfd_srv, + dfu); + struct bt_mesh_dfu_srv *dfu_srv; + + if (srv->phase != BT_MESH_DFD_PHASE_APPLYING_UPDATE && + srv->phase != BT_MESH_DFD_PHASE_CANCELING_UPDATE) + { + return; + } + + /* MshDFUv1.0 Section 6.2.2.4 Confirm step requires the DFD Server to + * wait until the Target nodes have applied the new firmware before setting + * Distribution Phase to Completed. In a self-update, the distributor is + * one of those Target nodes, so trigger the deferred self-apply first. If + * the apply callback reboots the device (real firmware), execution does + * not return: the DFD phase stays in APPLYING_UPDATE with state persisted, + * and dfd_srv_model_start() finishes the Confirm step on the next boot. If + * the self-target is still in APPLYING after the callback (async apply or + * pending reboot), keep DFD in APPLYING_UPDATE so state remains persisted + * for resume; an async apply completes it from + * bt_mesh_dfd_srv_self_applied(). + */ + + if (trigger_self_apply(srv)) + { + dfd_phase_set(srv, BT_MESH_DFD_PHASE_FAILED); + return; + } + + dfu_srv = self_target_dfu_srv(srv); + if (dfu_srv && dfu_srv->update.phase == BT_MESH_DFU_PHASE_APPLYING) + { + return; + } + + /* An apply that completed inside the callback has already notified us. */ + + if (srv->phase == BT_MESH_DFD_PHASE_COMPLETED) + { + return; + } + + dfd_phase_set(srv, BT_MESH_DFD_PHASE_COMPLETED); +} + +const struct bt_mesh_dfu_cli_cb _bt_mesh_dfd_srv_dfu_cb = +{ + .suspended = dfu_suspended, + .ended = dfu_ended, + .applied = dfu_applied, + .confirmed = dfu_confirmed, +}; + +static int upload_start( + struct bt_mesh_blob_srv *b, struct bt_mesh_msg_ctx *ctx, + struct bt_mesh_blob_xfer *xfer) +{ + LOG_DBG(""); + return 0; +} + +static void upload_end(struct bt_mesh_blob_srv *b, uint64_t id, bool success) +{ + struct bt_mesh_dfd_srv *srv = CONTAINER_OF(b, struct bt_mesh_dfd_srv, + upload.blob); + + if (srv->upload.phase != BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_ACTIVE) + { + return; + } + + LOG_DBG("%u", success); + + if (success && (bt_mesh_dfu_slot_commit(srv->upload.slot) == 0)) + { + srv->upload.phase = BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_SUCCESS; + return; + } + + /* Will delete slot when we start a new upload */ + + srv->upload.phase = BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_ERROR; +} + +static void upload_timeout(struct bt_mesh_blob_srv *b) +{ + LOG_DBG(""); + + upload_end(b, b->state.xfer.id, false); +} + +const struct bt_mesh_blob_srv_cb _bt_mesh_dfd_srv_blob_cb = +{ + .start = upload_start, + .end = upload_end, + .suspended = upload_timeout, +}; + +static int dfd_srv_init(const struct bt_mesh_model *mod) +{ + int err; + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + + srv->mod = mod; + + const struct bt_mesh_model *blob_srv = + bt_mesh_model_find(bt_mesh_model_elem(mod), + BT_MESH_MODEL_ID_BLOB_SRV); + + if (blob_srv == NULL) + { + LOG_ERR("Missing BLOB Srv."); + return -EINVAL; + } + + /** BLOB client also shall be present on the same element, but it is + * already checked by initiation of dfu client which we check here. + */ + + const struct bt_mesh_model *dfu_cli = + bt_mesh_model_find(bt_mesh_model_elem(mod), + BT_MESH_MODEL_ID_DFU_CLI); + + if (dfu_cli == NULL) + { + LOG_ERR("Missing FU Cli."); + return -EINVAL; + } + + err = bt_mesh_model_extend(mod, srv->upload.blob.mod); + + if (err) + { + return err; + } + + return 0; +} + +static void dfd_srv_reset(const struct bt_mesh_model *mod) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + + dfd_phase_set(srv, BT_MESH_DFD_PHASE_IDLE); + srv->upload.phase = BT_MESH_DFD_UPLOAD_PHASE_IDLE; + + list_initialize(&srv->inputs.targets); + srv->target_cnt = 0; + + bt_mesh_dfu_slot_foreach(slot_del_cb, srv); + bt_mesh_dfu_slot_del_all(); +} + +static int dfd_srv_model_start(const struct bt_mesh_model *mod) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + + if (srv->phase != BT_MESH_DFD_PHASE_APPLYING_UPDATE) + { + return 0; + } + + /* Resolve the slot pointer deferred from settings_set(). All + * settings are loaded by the time .start fires. + */ + + srv->dfu.xfer.slot = bt_mesh_dfu_slot_at(srv->slot_idx); + if (!srv->dfu.xfer.slot) + { + LOG_WRN("Slot %u lost, failing distribution", srv->slot_idx); + dfd_phase_set(srv, BT_MESH_DFD_PHASE_FAILED); + return 0; + } + + /* Self-update resume: if the local DFU Server is still in APPLYING, + * the reboot means apply succeeded. Transition it to IDLE so it responds + * to Firmware Update Information Get with the new FWID. + */ + + if (IS_ENABLED(CONFIG_BT_MESH_DFU_SRV)) + { + struct bt_mesh_dfu_srv *dfu_srv = self_target_dfu_srv(srv); + + if (dfu_srv && dfu_srv->update.phase == BT_MESH_DFU_PHASE_APPLYING) + { + bt_mesh_dfu_srv_applied(dfu_srv); + } + } + + /* Resume confirm step to verify targets report new FWID. */ + + (void)bt_mesh_dfu_cli_confirm(&srv->dfu); + + return 0; +} + +static int dfd_srv_settings_set( + const struct bt_mesh_model *mod, const char *name, size_t len_rd, + settings_read_cb read_cb, void *cb_arg) +{ + struct bt_mesh_dfd_srv *srv = mod->rt->user_data; + struct dfd_srv_persistent_state state; + ssize_t len; + + if (len_rd < offsetof(struct dfd_srv_persistent_state, targets)) + { + return -EINVAL; + } + + len = read_cb(cb_arg, &state, sizeof(state)); + if (len < 0 || len < offsetof(struct dfd_srv_persistent_state, targets)) + { + return -EINVAL; + } + + if (state.target_cnt > ARRAY_SIZE(srv->targets)) + { + LOG_WRN("Stored target count %u exceeds max %u", state.target_cnt, + (uint8_t)ARRAY_SIZE(srv->targets)); + return -EINVAL; + } + + if (len < offsetof(struct dfd_srv_persistent_state, targets) + + state.target_cnt * + sizeof(struct dfd_srv_persistent_target)) + { + LOG_WRN("Stored data too short for %u targets", state.target_cnt); + return -EINVAL; + } + + if (state.phase != BT_MESH_DFD_PHASE_APPLYING_UPDATE) + { + LOG_ERR("Unexpected persisted phase %u", state.phase); + return -EINVAL; + } + + srv->phase = state.phase; + + srv->apply = state.apply; + srv->slot_idx = state.slot_idx; + srv->inputs.app_idx = state.app_idx; + srv->inputs.group = state.group; + srv->inputs.ttl = state.ttl; + srv->inputs.timeout_base = state.timeout_base; + srv->dfu.xfer.blob.mode = state.xfer_mode; + + srv->target_cnt = state.target_cnt; + list_initialize(&srv->inputs.targets); + for (int i = 0; i < srv->target_cnt; i++) + { + memset(&srv->targets[i], 0, sizeof(struct bt_mesh_dfu_target)); + memset(&srv->pull_ctxs[i], 0, sizeof(struct bt_mesh_blob_target_pull)); + srv->targets[i].blob.addr = state.targets[i].addr; + srv->targets[i].blob.pull = &srv->pull_ctxs[i]; + srv->targets[i].img_idx = state.targets[i].img_idx; + srv->targets[i].phase = state.targets[i].phase; + srv->targets[i].status = state.targets[i].status; + srv->targets[i].effect = state.targets[i].effect; + list_add_tail(&srv->inputs.targets, &srv->targets[i].blob.n); + } + + bt_mesh_dfu_cli_restore(&srv->dfu, &srv->inputs); + + /* Slot pointer is resolved in dfd_srv_model_start() since the slot + * settings subtree may not be loaded yet at this point. + */ + + LOG_DBG("Recovered distribution (phase: %u, targets: %u, slot: %u)", + srv->phase, srv->target_cnt, + srv->slot_idx); + + return 0; +} + +const struct bt_mesh_model_cb _bt_mesh_dfd_srv_cb = +{ + .init = dfd_srv_init, + .start = dfd_srv_model_start, + .settings_set = dfd_srv_settings_set, + .reset = dfd_srv_reset, +}; + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_receiver_add( + struct bt_mesh_dfd_srv *srv, uint16_t addr, uint8_t img_idx) +{ + struct bt_mesh_dfu_target *t; + struct bt_mesh_blob_target_pull *p; + + if (!BT_MESH_ADDR_IS_UNICAST(addr)) + { + return BT_MESH_DFD_SUCCESS; + } + + t = target_get(srv, addr); + if (t) + { + t->img_idx = img_idx; + return BT_MESH_DFD_SUCCESS; + } + + /* New target node, add it to the list */ + + if (srv->target_cnt == ARRAY_SIZE(srv->targets)) + { + return BT_MESH_DFD_ERR_INSUFFICIENT_RESOURCES; + } + + t = &srv->targets[srv->target_cnt]; + p = &srv->pull_ctxs[srv->target_cnt]; + srv->target_cnt++; + + memset(t, 0, sizeof(*t)); + memset(p, 0, sizeof(*p)); + t->blob.addr = addr; + t->blob.pull = p; + t->img_idx = img_idx; + + LOG_DBG("Added receiver 0x%04x img: %u", t->blob.addr, t->img_idx); + + return BT_MESH_DFD_SUCCESS; +} + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_receivers_delete_all( + struct bt_mesh_dfd_srv *srv) +{ + if (bt_mesh_dfu_cli_is_busy(&srv->dfu)) + { + return BT_MESH_DFD_ERR_BUSY_WITH_DISTRIBUTION; + } + + list_initialize(&srv->inputs.targets); + srv->target_cnt = 0; + + return BT_MESH_DFD_SUCCESS; +} + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_start(struct bt_mesh_dfd_srv *srv, + struct bt_mesh_dfd_start_params *params) +{ + int err; + int i; + struct bt_mesh_dfu_cli_xfer xfer = + { + 0 + }; + + if (!srv->target_cnt) + { + return BT_MESH_DFD_ERR_RECEIVERS_LIST_EMPTY; + } + + if (!bt_mesh_app_key_exists(params->app_idx)) + { + return BT_MESH_DFD_ERR_INVALID_APPKEY_INDEX; + } + + xfer.mode = params->xfer_mode; + xfer.slot = bt_mesh_dfu_slot_at(params->slot_idx); + if (!xfer.slot) + { + return BT_MESH_DFD_ERR_FW_NOT_FOUND; + } + + if (srv->inputs.app_idx == params->app_idx + && srv->inputs.timeout_base == params->timeout_base && + srv->inputs.group == params->group && srv->inputs.ttl == params->ttl && + srv->dfu.xfer.blob.mode == xfer.mode && srv->apply == params->apply && + srv->slot_idx == params->slot_idx) + { + if (is_busy(srv) || srv->phase == BT_MESH_DFD_PHASE_COMPLETED) + { + LOG_WRN("Already completed or in progress"); + return BT_MESH_DFD_SUCCESS; + } + else if (srv->phase == BT_MESH_DFD_PHASE_TRANSFER_SUSPENDED) + { + bt_mesh_dfu_cli_resume(&srv->dfu); + dfd_phase_set(srv, BT_MESH_DFD_PHASE_TRANSFER_ACTIVE); + return BT_MESH_DFD_SUCCESS; + } + } + else if (is_busy(srv) + || srv->phase == BT_MESH_DFD_PHASE_TRANSFER_SUSPENDED) + { + LOG_WRN("Busy with distribution"); + return BT_MESH_DFD_ERR_BUSY_WITH_DISTRIBUTION; + } + + if (srv->phase == BT_MESH_DFD_PHASE_CANCELING_UPDATE) + { + LOG_WRN("Canceling distribution"); + return BT_MESH_DFD_ERR_BUSY_WITH_DISTRIBUTION; + } + + srv->io = NULL; + err = srv->cb->send(srv, xfer.slot, &srv->io); + if (err || !srv->io) + { + return BT_MESH_DFD_ERR_INTERNAL; + } + + list_initialize(&srv->inputs.targets); + for (i = 0; i < srv->target_cnt; i++) + { + uint16_t addr = srv->targets[i].blob.addr; + + memset(&srv->targets[i].blob, 0, sizeof(struct bt_mesh_blob_target)); + memset(&srv->pull_ctxs[i], 0, sizeof(struct bt_mesh_blob_target_pull)); + srv->targets[i].blob.addr = addr; + srv->targets[i].blob.pull = &srv->pull_ctxs[i]; + + list_add_tail(&srv->inputs.targets, &srv->targets[i].blob.n); + } + + srv->slot_idx = params->slot_idx; + srv->inputs.app_idx = params->app_idx; + srv->inputs.timeout_base = params->timeout_base; + srv->inputs.group = params->group; + srv->inputs.ttl = params->ttl; + srv->apply = params->apply; + + LOG_DBG("Distribution Start: slot: %d, appidx: %d, tb: %d, addr: %04X, " + "ttl: %d, apply: %d", + params->slot_idx, params->app_idx, params->timeout_base, + params->group, params->ttl, + params->apply); + + /* DFD Server will always retrieve targets' capabilities before + * distributing a firmware. + */ + + xfer.blob_params = NULL; + + dfd_phase_set(srv, BT_MESH_DFD_PHASE_TRANSFER_ACTIVE); + err = bt_mesh_dfu_cli_send(&srv->dfu, &srv->inputs, srv->io, &xfer); + if (err) + { + dfd_phase_set(srv, BT_MESH_DFD_PHASE_IDLE); + return BT_MESH_DFD_ERR_INTERNAL; + } + + return BT_MESH_DFD_SUCCESS; +} + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_suspend(struct bt_mesh_dfd_srv *srv) +{ + int err; + + if (srv->phase == BT_MESH_DFD_PHASE_TRANSFER_SUSPENDED) + { + return BT_MESH_DFD_SUCCESS; + } + + if (srv->phase != BT_MESH_DFD_PHASE_TRANSFER_ACTIVE) + { + return BT_MESH_DFD_ERR_WRONG_PHASE; + } + + err = bt_mesh_dfu_cli_suspend(&srv->dfu); + if (err) + { + return BT_MESH_DFD_ERR_SUSPEND_FAILED; + } + + srv->phase = BT_MESH_DFD_PHASE_TRANSFER_SUSPENDED; + return BT_MESH_DFD_SUCCESS; +} + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_cancel(struct bt_mesh_dfd_srv *srv, + struct bt_mesh_msg_ctx *ctx) +{ + enum bt_mesh_dfd_phase prev_phase; + int err; + + if (srv->phase == BT_MESH_DFD_PHASE_CANCELING_UPDATE + || srv->phase == BT_MESH_DFD_PHASE_IDLE) + { + if (ctx != NULL) + { + status_rsp(srv, ctx, BT_MESH_DFD_SUCCESS); + } + + return BT_MESH_DFD_SUCCESS; + } + + if (srv->phase == BT_MESH_DFD_PHASE_COMPLETED + || srv->phase == BT_MESH_DFD_PHASE_FAILED) + { + dfd_phase_set(srv, BT_MESH_DFD_PHASE_IDLE); + if (ctx != NULL) + { + status_rsp(srv, ctx, BT_MESH_DFD_SUCCESS); + } + + return BT_MESH_DFD_SUCCESS; + } + + /* Phase TRANSFER_ACTIVE, TRANSFER_SUSPENDED, TRANSFER_SUCCESS, + * APPLYING_UPDATE: + */ + + prev_phase = srv->phase; + dfd_phase_set(srv, BT_MESH_DFD_PHASE_CANCELING_UPDATE); + + if (prev_phase == BT_MESH_DFD_PHASE_APPLYING_UPDATE) + { + cancel_self_apply(srv); + } + + err = bt_mesh_dfu_cli_cancel(&srv->dfu, NULL); + /* -EALREADY means the DFU Client has nothing left to cancel, which is + * the case while waiting for a deferred self-apply. + */ + + if (err && err != -EALREADY) + { + if (ctx != NULL) + { + status_rsp(srv, ctx, BT_MESH_DFD_ERR_INTERNAL); + } + + return BT_MESH_DFD_ERR_INTERNAL; + } + + if (prev_phase == BT_MESH_DFD_PHASE_APPLYING_UPDATE && ctx) + { + /* Disable randomization for the Firmware Distribution State message to + * avoid reordering when Firmware Distribution Server sends 2 messages + * in a row when cancelling the update (see section 6.2.3.10 of + * MshDFUv1.0). + */ + + ctx->rnd_delay = false; + } + + if (ctx != NULL) + { + status_rsp(srv, ctx, BT_MESH_DFD_SUCCESS); + } + + if (prev_phase == BT_MESH_DFD_PHASE_APPLYING_UPDATE) + { + dfd_phase_set(srv, BT_MESH_DFD_PHASE_IDLE); + if (ctx != NULL) + { + status_rsp(srv, ctx, BT_MESH_DFD_SUCCESS); + } + } + + return BT_MESH_DFD_SUCCESS; +} + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_apply(struct bt_mesh_dfd_srv *srv) +{ + int err; + + if (srv->phase == BT_MESH_DFD_PHASE_IDLE + || srv->phase == BT_MESH_DFD_PHASE_CANCELING_UPDATE || + srv->phase == BT_MESH_DFD_PHASE_TRANSFER_ACTIVE || + srv->phase == BT_MESH_DFD_PHASE_TRANSFER_SUSPENDED + || srv->phase == BT_MESH_DFD_PHASE_FAILED) + { + return BT_MESH_DFD_ERR_WRONG_PHASE; + } + + if (srv->phase == BT_MESH_DFD_PHASE_APPLYING_UPDATE + || srv->phase == BT_MESH_DFD_PHASE_COMPLETED) + { + return BT_MESH_DFD_SUCCESS; + } + + err = bt_mesh_dfu_cli_apply(&srv->dfu); + if (err) + { + return BT_MESH_DFD_ERR_INTERNAL; + } + + /* dfd_phase_set() downgrades to FAILED if the state cannot be persisted. */ + + dfd_phase_set(srv, BT_MESH_DFD_PHASE_APPLYING_UPDATE); + if (srv->phase != BT_MESH_DFD_PHASE_APPLYING_UPDATE) + { + return BT_MESH_DFD_ERR_INTERNAL; + } + + return BT_MESH_DFD_SUCCESS; +} + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_fw_delete( + struct bt_mesh_dfd_srv *srv, size_t *fwid_len, const uint8_t **fwid) +{ + struct bt_mesh_dfu_slot *slot; + int idx; + int err; + + if (srv->phase != BT_MESH_DFD_PHASE_IDLE) + { + *fwid = NULL; + *fwid_len = 0; + return BT_MESH_DFD_ERR_BUSY_WITH_DISTRIBUTION; + } + + idx = bt_mesh_dfu_slot_get(*fwid, *fwid_len, &slot); + if (idx < 0) + { + return BT_MESH_DFD_SUCCESS; + } + + err = slot_del(srv, slot); + if (err) + { + *fwid = NULL; + *fwid_len = 0; + return BT_MESH_DFD_ERR_INTERNAL; + } + else + { + return BT_MESH_DFD_SUCCESS; + } +} + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_fw_delete_all( + struct bt_mesh_dfd_srv *srv) +{ + if (srv->phase != BT_MESH_DFD_PHASE_IDLE) + { + return BT_MESH_DFD_ERR_BUSY_WITH_DISTRIBUTION; + } + + bt_mesh_dfu_slot_foreach(slot_del_cb, srv); + + bt_mesh_dfu_slot_del_all(); + + return BT_MESH_DFD_SUCCESS; +} + +#ifdef CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD +int bt_mesh_dfd_srv_oob_check_complete(struct bt_mesh_dfd_srv *srv, + const struct bt_mesh_dfu_slot *slot, + int status, + uint8_t *fwid, + size_t fwid_len) +{ + int err; + + if (slot != srv->upload.slot || !srv->upload.is_oob || + srv->upload.phase == BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_ACTIVE || + !srv->upload.is_pending_oob_check) + { + /* This should not happen, unless the application calls the function + * with a "wrong" pointer or at a wrong time. + */ + + return -EINVAL; + } + + srv->upload.is_pending_oob_check = false; + + if (status != BT_MESH_DFD_SUCCESS) + { + bt_mesh_dfu_slot_release(srv->upload.slot); + upload_status_rsp(srv, &srv->upload.oob.ctx, status); + return -ECANCELED; + } + + err = set_upload_fwid(srv, &srv->upload.oob.ctx, fwid, fwid_len); + + if (err) + { + return err; + } + + upload_status_rsp(srv, &srv->upload.oob.ctx, BT_MESH_DFD_SUCCESS); + return 0; +} + +int bt_mesh_dfd_srv_oob_store_complete(struct bt_mesh_dfd_srv *srv, + const struct bt_mesh_dfu_slot *slot, + bool success, + size_t size, + const uint8_t *metadata, + size_t metadata_len) +{ + int err = 0; + + if (srv->upload.phase != BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_ACTIVE || + srv->upload.slot != slot || + !srv->upload.is_oob) + { + return -EINVAL; + } + + if (!success) + { + goto error; + } + + err = bt_mesh_dfu_slot_info_set(srv->upload.slot, size, metadata, + metadata_len); + if (err) + { + goto error; + } + + err = bt_mesh_dfu_slot_commit(srv->upload.slot); + if (err) + { + goto error; + } + + srv->upload.phase = BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_SUCCESS; + return 0; + +error: + srv->upload.phase = BT_MESH_DFD_UPLOAD_PHASE_TRANSFER_ERROR; + bt_mesh_dfu_slot_release(srv->upload.slot); + return err; +} +#endif /* CONFIG_BT_MESH_DFD_SRV_OOB_UPLOAD */ diff --git a/libs/zblue/mesh/dfd_srv_internal.h b/libs/zblue/mesh/dfd_srv_internal.h new file mode 100644 index 0000000000000..16536612d421b --- /dev/null +++ b/libs/zblue/mesh/dfd_srv_internal.h @@ -0,0 +1,70 @@ +/**************************************************************************** + * libs/zblue/mesh/dfd_srv_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFD_SRV_INTERNAL_H__ +#define ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFD_SRV_INTERNAL_H__ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +struct bt_mesh_dfd_start_params +{ + uint16_t app_idx; + uint16_t timeout_base; + uint16_t slot_idx; + uint16_t group; + enum bt_mesh_blob_xfer_mode xfer_mode; + uint8_t ttl; + bool apply; +}; + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_receiver_add( + struct bt_mesh_dfd_srv *srv, uint16_t addr, uint8_t img_idx); + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_receivers_delete_all( + struct bt_mesh_dfd_srv *srv); + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_start(struct bt_mesh_dfd_srv *srv, + struct bt_mesh_dfd_start_params *params); + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_suspend(struct bt_mesh_dfd_srv *srv); + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_cancel(struct bt_mesh_dfd_srv *srv, + struct bt_mesh_msg_ctx *ctx); + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_apply(struct bt_mesh_dfd_srv *srv); + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_fw_delete( + struct bt_mesh_dfd_srv *srv, size_t *fwid_len, const uint8_t **fwid); + +enum bt_mesh_dfd_status bt_mesh_dfd_srv_fw_delete_all( + struct bt_mesh_dfd_srv *srv); + +/** Notify a co-located Firmware Distribution Server that the local Firmware + * Update Server completed a deferred self-apply. + */ + +void bt_mesh_dfd_srv_self_applied(void); + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_MESH_DFD_SRV_INTERNAL_H__ */ diff --git a/libs/zblue/mesh/dfu.h b/libs/zblue/mesh/dfu.h new file mode 100644 index 0000000000000..c64a210f12e3b --- /dev/null +++ b/libs/zblue/mesh/dfu.h @@ -0,0 +1,61 @@ +/**************************************************************************** + * libs/zblue/mesh/dfu.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define BT_MESH_DFU_OP_UPDATE_INFO_GET BT_MESH_MODEL_OP_2(0x83, 0x08) +#define BT_MESH_DFU_OP_UPDATE_INFO_STATUS BT_MESH_MODEL_OP_2(0x83, 0x09) + +#define BT_MESH_DFU_OP_UPDATE_METADATA_CHECK BT_MESH_MODEL_OP_2(0x83, 0x0a) +#define BT_MESH_DFU_OP_UPDATE_METADATA_STATUS BT_MESH_MODEL_OP_2(0x83, 0x0b) + +#define BT_MESH_DFU_OP_UPDATE_GET BT_MESH_MODEL_OP_2(0x83, 0x0c) +#define BT_MESH_DFU_OP_UPDATE_START BT_MESH_MODEL_OP_2(0x83, 0x0d) +#define BT_MESH_DFU_OP_UPDATE_CANCEL BT_MESH_MODEL_OP_2(0x83, 0x0e) +#define BT_MESH_DFU_OP_UPDATE_APPLY BT_MESH_MODEL_OP_2(0x83, 0x0f) +#define BT_MESH_DFU_OP_UPDATE_STATUS BT_MESH_MODEL_OP_2(0x83, 0x10) + +#define DFU_UPDATE_INFO_STATUS_MSG_MINLEN \ + (4 + CONFIG_BT_MESH_DFU_FWID_MAXLEN + CONFIG_BT_MESH_DFU_URI_MAXLEN) +#define DFU_UPDATE_START_MSG_MAXLEN (12 + CONFIG_BT_MESH_DFU_METADATA_MAXLEN) + +void bt_mesh_dfu_cli_restore(struct bt_mesh_dfu_cli *cli, + const struct bt_mesh_blob_cli_inputs *inputs); + +int bt_mesh_dfu_srv_apply_deferred(struct bt_mesh_dfu_srv *srv); + +void bt_mesh_dfu_srv_apply_cancel(struct bt_mesh_dfu_srv *srv); + +static inline uint16_t dfu_metadata_checksum(struct net_buf_simple *buf) +{ + /* Simple Fletcher-16 checksum to ensure duplicate start messages don't + * have different metadata. + */ + + struct net_buf_simple_state state; + uint8_t sum[2] = + { + 0, 0 + }; + + net_buf_simple_save(buf, &state); + + while (buf->len) + { + uint8_t byte = net_buf_simple_pull_u8(buf); + + sum[0] += byte; + sum[1] += sum[0]; + } + + net_buf_simple_restore(buf, &state); + + return (sum[0] << 8U) | sum[1]; +} diff --git a/libs/zblue/mesh/dfu_cli.c b/libs/zblue/mesh/dfu_cli.c new file mode 100644 index 0000000000000..2496584e3fb62 --- /dev/null +++ b/libs/zblue/mesh/dfu_cli.c @@ -0,0 +1,1527 @@ +/**************************************************************************** + * libs/zblue/mesh/dfu_cli.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include "access.h" +#include "dfu.h" +#include "blob.h" +#include + +#define LOG_LEVEL CONFIG_BT_MESH_DFU_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_dfu_cli); + +#define TARGETS_FOR_EACH(cli, target) \ + list_for_every_container((struct list_node *)&((cli)->blob.inputs)->targets, target, blob.n) + +#define MSG_CTX(cli, dst) \ + { \ + .app_idx = (cli)->blob.inputs->app_idx, \ + .addr = dst, \ + .send_ttl = (cli)->blob.inputs->ttl, \ + } + +#define DFU_CLI(blob_cli) CONTAINER_OF(blob_cli, struct bt_mesh_dfu_cli, blob) + +BUILD_ASSERT((DFU_UPDATE_START_MSG_MAXLEN + + BT_MESH_MODEL_OP_LEN(BT_MESH_DFU_OP_UPDATE_START) + + BT_MESH_MIC_SHORT) <= BT_MESH_TX_SDU_MAX, + "The Firmware Update Start message does not fit into the " + "maximum outgoing SDU size."); + +BUILD_ASSERT((DFU_UPDATE_INFO_STATUS_MSG_MINLEN + + BT_MESH_MODEL_OP_LEN(BT_MESH_DFU_OP_UPDATE_INFO_STATUS) + + BT_MESH_MIC_SHORT) <= BT_MESH_RX_SDU_MAX, + "The Firmware Update Info Status message does not fit into " + "the maximum incoming SDU size."); + +enum req +{ + REQ_NONE, + REQ_METADATA, + REQ_IMG, + REQ_STATUS, +}; + +enum +{ + FLAG_FAILED = BIT(0), + FLAG_CANCELLED = BIT(1), + FLAG_SKIP_CAPS_GET = BIT(2), + FLAG_RESUME = BIT(3), + FLAG_COMPLETED = BIT(4), +}; + +enum +{ + STATE_IDLE, + STATE_TRANSFER, + STATE_REFRESH, + STATE_VERIFIED, + STATE_APPLY, + STATE_APPLIED, + STATE_CONFIRM, + STATE_CANCEL, + STATE_SUSPENDED, +}; + +static int32_t dfu_cli_timeout = (10 * MSEC_PER_SEC); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static struct bt_mesh_dfu_target *target_get(struct bt_mesh_dfu_cli *cli, + uint16_t addr) +{ + struct bt_mesh_dfu_target *target; + + TARGETS_FOR_EACH(cli, target) + { + if (addr == target->blob.addr) + { + return target; + } + } + + return NULL; +} + +static void target_failed( + struct bt_mesh_dfu_cli *cli, struct bt_mesh_dfu_target *target, + enum bt_mesh_dfu_status status) +{ + target->status = status; + + LOG_ERR("Target 0x%04x failed: %u", target->blob.addr, status); + + /* Invalidate blob status to prevent the target from being included in + * future sending: + */ + + if (target->blob.status == BT_MESH_BLOB_SUCCESS) + { + target->blob.status = BT_MESH_BLOB_ERR_INTERNAL; + } + + if (cli->cb && cli->cb->lost_target) + { + cli->cb->lost_target(cli, target); + } +} + +static void dfu_complete(struct bt_mesh_dfu_cli *cli) +{ + LOG_DBG(""); + + if (cli->cb && cli->cb->ended) + { + cli->cb->ended(cli, BT_MESH_DFU_SUCCESS); + } +} + +static void dfu_applied(struct bt_mesh_dfu_cli *cli) +{ + LOG_DBG(""); + + cli->xfer.state = STATE_APPLIED; + + if (cli->cb && cli->cb->applied) + { + cli->cb->applied(cli); + } +} + +static void dfu_failed(struct bt_mesh_dfu_cli *cli, + enum bt_mesh_dfu_status reason) +{ + LOG_DBG("%u", reason); + + cli->xfer.flags |= FLAG_FAILED; + + if (cli->cb && cli->cb->ended) + { + cli->cb->ended(cli, reason); + } +} + +static int req_setup(struct bt_mesh_dfu_cli *cli, + enum req type, uint16_t addr, void *params) +{ + if (cli->req.type != REQ_NONE) + { + return -EBUSY; + } + + cli->req.addr = addr; + cli->req.params = params; + cli->req.type = type; + + return 0; +} + +static int req_wait(struct bt_mesh_dfu_cli *cli, clock_t timeout) +{ + int err; + + err = nxsem_tickwait(&cli->req.sem, timeout); + cli->req.type = REQ_NONE; + + return err; +} + +static bool targets_active(struct bt_mesh_dfu_cli *cli) +{ + struct bt_mesh_dfu_target *target; + + TARGETS_FOR_EACH(cli, target) + { + if (target->status == BT_MESH_DFU_SUCCESS) + { + return true; + } + } + + return false; +} + +/* Blob client + */ + +static void refresh(struct bt_mesh_dfu_cli *cli); + +static void blob_caps(struct bt_mesh_blob_cli *b, + const struct bt_mesh_blob_cli_caps *caps) +{ + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + int err; + + if (!caps) + { + dfu_failed(cli, BT_MESH_DFU_ERR_RESOURCES); + return; + } + + cli->xfer.blob.block_size_log = caps->max_block_size_log; + cli->xfer.blob.chunk_size = caps->max_chunk_size; + + /* If mode is not already set and server reported it supports all modes + * default to PUSH, otherwise set value reported by server. If mode was set + * and server supports all modes, keep old value; set reported value + * otherwise. + */ + + if (!(cli->xfer.blob.mode & BT_MESH_BLOB_XFER_MODE_ALL)) + { + cli->xfer.blob.mode = + caps->modes == BT_MESH_BLOB_XFER_MODE_ALL ? + BT_MESH_BLOB_XFER_MODE_PUSH : caps->modes; + } + else + { + cli->xfer.blob.mode = + caps->modes == BT_MESH_BLOB_XFER_MODE_ALL ? + cli->xfer.blob.mode : caps->modes; + } + + err = bt_mesh_blob_cli_send(b, b->inputs, &cli->xfer.blob, cli->xfer.io); + if (err) + { + LOG_ERR("Starting BLOB xfer failed: %d", err); + dfu_failed(cli, BT_MESH_DFU_ERR_BLOB_XFER_BUSY); + } +} + +static void blob_lost_target( + struct bt_mesh_blob_cli *b, struct bt_mesh_blob_target *blobt, + enum bt_mesh_blob_status reason) +{ + struct bt_mesh_dfu_target *target = CONTAINER_OF(blobt, + struct bt_mesh_dfu_target, blob); + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + + if ((cli->xfer.state == STATE_CONFIRM || cli->xfer.state == STATE_APPLY) && + target->effect == BT_MESH_DFU_EFFECT_UNPROV) + { + /* Reset status for such targets to use them in consequent procedures. + * See sections 7.1.2.6 and 7.1.2.9 of the MeshDFU. + */ + + target->blob.status = BT_MESH_BLOB_SUCCESS; + return; + } + + target_failed(cli, target, BT_MESH_DFU_ERR_INTERNAL); +} + +static void blob_suspended(struct bt_mesh_blob_cli *b) +{ + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + + LOG_DBG("BLOB transfer suspended"); + + cli->xfer.state = STATE_SUSPENDED; + + if (cli->cb && cli->cb->suspended) + { + cli->cb->suspended(cli); + } +} + +static void blob_end(struct bt_mesh_blob_cli *b, + const struct bt_mesh_blob_xfer *xfer, bool success) +{ + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + + cli->req.img_cb = NULL; + + if (success) + { + refresh(cli); + return; + } + + if (cli->xfer.state == STATE_CANCEL) + { + /* The user cancelled the transfer, DFU will end when all + * targets have been notified. + */ + + return; + } + + if (cli->xfer.state != STATE_TRANSFER) + { + LOG_ERR("Blob failed in invalid state %u", cli->xfer.state); + return; + } + + dfu_failed(cli, BT_MESH_DFU_ERR_INTERNAL); +} + +const struct bt_mesh_blob_cli_cb _bt_mesh_dfu_cli_blob_handlers = +{ + .caps = blob_caps, + .lost_target = blob_lost_target, + .suspended = blob_suspended, + .end = blob_end, +}; + +/* Message sending + */ + +static void tx_start(uint16_t dur, int err, void *cb_data); +static void tx_end(int err, void *cb_data); + +static const struct bt_mesh_send_cb send_cb = +{ + .start = tx_start, + .end = tx_end, +}; + +static void tx_start(uint16_t dur, int err, void *cb_data) +{ + if (err) + { + tx_end(err, cb_data); + } +} + +static void tx_end(int err, void *cb_data) +{ + struct bt_mesh_dfu_cli *cli = cb_data; + + blob_cli_broadcast_tx_complete(&cli->blob); +} + +static int tx(const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf, const struct bt_mesh_send_cb *cb, + struct bt_mesh_dfu_cli *cli) +{ + int err; + + err = bt_mesh_model_send(mod, ctx, buf, cb, cli); + if (err) + { + LOG_ERR("Send err: %d", err); + if (cb) + { + cb->end(err, cli); + } + + return err; + } + + return 0; +} + +static int info_get( + struct bt_mesh_dfu_cli *cli, struct bt_mesh_msg_ctx *ctx, uint8_t idx, + uint8_t max_count, const struct bt_mesh_send_cb *cb) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_DFU_OP_UPDATE_INFO_GET, 2); + bt_mesh_model_msg_init(&buf, BT_MESH_DFU_OP_UPDATE_INFO_GET); + net_buf_simple_add_u8(&buf, idx); + net_buf_simple_add_u8(&buf, max_count); + + return tx(cli->mod, ctx, &buf, cb, cli); +} + +static void send_info_get(struct bt_mesh_blob_cli *b, uint16_t dst) +{ + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + struct bt_mesh_msg_ctx ctx = MSG_CTX(cli, dst); + + if (bt_mesh_has_addr(dst)) + { + const struct bt_mesh_elem *elem = bt_mesh_elem_find(dst); + const struct bt_mesh_model *mod = + elem ? bt_mesh_model_find(elem, + BT_MESH_MODEL_ID_DFU_SRV) : NULL; + struct bt_mesh_dfu_srv *dfu_srv = mod ? mod->rt->user_data : NULL; + + if (dfu_srv && dfu_srv->update.phase == BT_MESH_DFU_PHASE_APPLYING) + { + struct bt_mesh_dfu_target *target = target_get(cli, dst); + + /* The local DFU Server defers its apply callback until + * the Confirm step completes (see dfu_confirmed() in dfd_srv.c), + * so it will reject Firmware Update Information Get with EBUSY per + * MshDFUv1.0 Section 7.2. Retrying only delays confirmed() from + * triggering the deferred apply. Ack the self-target immediately + * and drive the broadcast state machine forward. + */ + + if (target) + { + blob_cli_broadcast_rsp(&cli->blob, &target->blob); + } + + blob_cli_broadcast_tx_complete(&cli->blob); + return; + } + } + + cli->req.img_cnt = 0xff; + + info_get(cli, &ctx, 0, cli->req.img_cnt, &send_cb); +} + +static void send_update_start(struct bt_mesh_blob_cli *b, uint16_t dst) +{ + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + struct bt_mesh_msg_ctx ctx = MSG_CTX(cli, dst); + struct bt_mesh_dfu_target *target; + + if (b->tx.ctx.force_unicast) + { + target = target_get(cli, dst); + } + else + { + target = + list_peek_head_container((struct list_node *)&(( + cli)->blob.inputs)->targets, target, blob.n); + } + + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_DFU_OP_UPDATE_START, + DFU_UPDATE_START_MSG_MAXLEN); + bt_mesh_model_msg_init(&buf, BT_MESH_DFU_OP_UPDATE_START); + + net_buf_simple_add_u8(&buf, cli->blob.inputs->ttl); + net_buf_simple_add_le16(&buf, cli->blob.inputs->timeout_base); + net_buf_simple_add_le64(&buf, cli->xfer.blob.id); + net_buf_simple_add_u8(&buf, target->img_idx); + net_buf_simple_add_mem(&buf, cli->xfer.slot->metadata, + cli->xfer.slot->metadata_len); + + (void)tx(cli->mod, &ctx, &buf, &send_cb, cli); +} + +static void send_update_get(struct bt_mesh_blob_cli *b, uint16_t dst) +{ + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + struct bt_mesh_msg_ctx ctx = MSG_CTX(cli, dst); + + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_DFU_OP_UPDATE_GET, 0); + bt_mesh_model_msg_init(&buf, BT_MESH_DFU_OP_UPDATE_GET); + + (void)tx(cli->mod, &ctx, &buf, &send_cb, cli); +} + +static void send_update_cancel(struct bt_mesh_blob_cli *b, uint16_t dst) +{ + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + struct bt_mesh_msg_ctx ctx = MSG_CTX(cli, dst); + + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_DFU_OP_UPDATE_CANCEL, 0); + bt_mesh_model_msg_init(&buf, BT_MESH_DFU_OP_UPDATE_CANCEL); + + (void)tx(cli->mod, &ctx, &buf, &send_cb, cli); +} + +static void send_update_apply(struct bt_mesh_blob_cli *b, uint16_t dst) +{ + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + struct bt_mesh_msg_ctx ctx = MSG_CTX(cli, dst); + + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_DFU_OP_UPDATE_APPLY, 0); + bt_mesh_model_msg_init(&buf, BT_MESH_DFU_OP_UPDATE_APPLY); + + (void)tx(cli->mod, &ctx, &buf, &send_cb, cli); +} + +/* Distribution procedure + */ + +static void transfer(struct bt_mesh_blob_cli *b); +static void apply(struct bt_mesh_dfu_cli *cli); +static void applied(struct bt_mesh_blob_cli *b); +static void confirmed(struct bt_mesh_blob_cli *b); +static void cancelled(struct bt_mesh_blob_cli *b); + +static void initiate(struct bt_mesh_dfu_cli *cli) +{ + struct blob_cli_broadcast_ctx tx = + { + .send = send_update_start, + .next = transfer, + .acked = true, + }; + + struct bt_mesh_dfu_target *target; + int img_idx = -1; + + /** If firmware img index is the same for all targets, we can send Firmware + * Update Start message using multicast address. Otherwise, it has to be + * send in a unicast way. + */ + + TARGETS_FOR_EACH(cli, target) + { + if (img_idx == -1) + { + img_idx = target->img_idx; + } + else if (target->img_idx != img_idx) + { + tx.force_unicast = true; + break; + } + } + + LOG_DBG(""); + + cli->op = BT_MESH_DFU_OP_UPDATE_STATUS; + cli->xfer.state = STATE_TRANSFER; + + blob_cli_broadcast(&cli->blob, &tx); +} + +static void skip_targets_from_broadcast(struct bt_mesh_dfu_cli *cli, + bool skip) +{ + struct bt_mesh_dfu_target *target; + + TARGETS_FOR_EACH(cli, target) + { + /* If distributor is in the targets list, or target is in Verify phase, + * disable it until Retrieve Capabilities and BLOB Transfer procedures + * are completed. + */ + + if (bt_mesh_has_addr(target->blob.addr) || + target->phase == BT_MESH_DFU_PHASE_VERIFY) + { + target->blob.skip = skip; + break; + } + } +} + +static bool transfer_skip(struct bt_mesh_dfu_cli *cli) +{ + struct bt_mesh_dfu_target *target; + + TARGETS_FOR_EACH(cli, target) + { + if (!bt_mesh_has_addr(target->blob.addr) || !target->blob.skip) + { + return false; + } + } + + return true; +} + +static void transfer(struct bt_mesh_blob_cli *b) +{ + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + int err; + + LOG_DBG(""); + + if (!targets_active(cli)) + { + dfu_failed(cli, BT_MESH_DFU_ERR_INTERNAL); + return; + } + + skip_targets_from_broadcast(cli, true); + + if (transfer_skip(cli)) + { + /* If distributor only updates itself, or all targets are in Verify + * phase, proceed to the refresh step immediately. + */ + + refresh(cli); + return; + } + + if (cli->xfer.flags & FLAG_RESUME) + { + cli->xfer.flags ^= FLAG_RESUME; + err = bt_mesh_blob_cli_resume(b); + if (err) + { + LOG_ERR("Resuming BLOB xfer failed: %d", err); + dfu_failed(cli, BT_MESH_DFU_ERR_BLOB_XFER_BUSY); + } + } + else if (cli->xfer.flags & FLAG_SKIP_CAPS_GET) + { + cli->xfer.flags ^= FLAG_SKIP_CAPS_GET; + err = bt_mesh_blob_cli_send(b, b->inputs, &cli->xfer.blob, + cli->xfer.io); + if (err) + { + LOG_ERR("Starting BLOB xfer failed: %d", err); + dfu_failed(cli, BT_MESH_DFU_ERR_BLOB_XFER_BUSY); + } + } + else + { + err = bt_mesh_blob_cli_caps_get(&cli->blob, cli->blob.inputs); + if (err) + { + LOG_ERR("Failed starting blob xfer: %d", err); + dfu_failed(cli, BT_MESH_DFU_ERR_BLOB_XFER_BUSY); + } + } +} + +static void refreshed(struct bt_mesh_blob_cli *b) +{ + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + + if (!targets_active(cli)) + { + dfu_failed(cli, BT_MESH_DFU_ERR_INTERNAL); + return; + } + + cli->xfer.state = STATE_VERIFIED; + dfu_complete(cli); +} + +static void refresh(struct bt_mesh_dfu_cli *cli) +{ + const struct blob_cli_broadcast_ctx tx = + { + .send = send_update_get, .next = refreshed, .acked = true + }; + + LOG_DBG(""); + + cli->xfer.state = STATE_REFRESH; + cli->op = BT_MESH_DFU_OP_UPDATE_STATUS; + + /* If distributor is in the targets list, enable it again so it + * participates in Distribute Firmware procedure. + */ + + skip_targets_from_broadcast(cli, false); + + blob_cli_broadcast(&cli->blob, &tx); +} + +static void apply(struct bt_mesh_dfu_cli *cli) +{ + const struct blob_cli_broadcast_ctx tx = + { + .send = send_update_apply, .next = applied, .acked = true + }; + + LOG_DBG(""); + + cli->xfer.state = STATE_APPLY; + cli->op = BT_MESH_DFU_OP_UPDATE_STATUS; + + blob_cli_broadcast(&cli->blob, &tx); +} + +static void applied(struct bt_mesh_blob_cli *b) +{ + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + + if (!targets_active(cli)) + { + dfu_failed(cli, BT_MESH_DFU_ERR_INTERNAL); + return; + } + + dfu_applied(cli); +} + +static enum bt_mesh_dfu_iter target_img_cb( + struct bt_mesh_dfu_cli *cli, struct bt_mesh_msg_ctx *ctx, uint8_t idx, + uint8_t cnt, const struct bt_mesh_dfu_img *img, void *cb_data) +{ + struct bt_mesh_dfu_target *target; + + if ((img->fwid_len != cli->xfer.slot->fwid_len) || + memcmp(cli->xfer.slot->fwid, img->fwid, img->fwid_len)) + { + return BT_MESH_DFU_ITER_CONTINUE; + } + + target = target_get(cli, ctx->addr); + if (target) + { + LOG_DBG("SUCCESS: 0x%04x applied dfu (as image %u)", ctx->addr, idx); + target->phase = BT_MESH_DFU_PHASE_APPLY_SUCCESS; + blob_cli_broadcast_rsp(&cli->blob, &target->blob); + } + else + { + LOG_WRN("Target 0x%04x not found", ctx->addr); + } + + return BT_MESH_DFU_ITER_STOP; +} + +static void confirm(struct bt_mesh_dfu_cli *cli) +{ + const struct blob_cli_broadcast_ctx tx = + { + .send = send_info_get, + .next = confirmed, + .acked = true, + .optional = true, + }; + + LOG_DBG(""); + + cli->op = BT_MESH_DFU_OP_UPDATE_INFO_STATUS; + cli->req.img_cb = target_img_cb; + cli->req.ttl = cli->blob.inputs->ttl; + + blob_cli_broadcast(&cli->blob, &tx); +} + +static void confirmed(struct bt_mesh_blob_cli *b) +{ + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + struct bt_mesh_dfu_target *target; + bool success = false; + + cli->req.img_cb = NULL; + + TARGETS_FOR_EACH(cli, target) + { + if (target->status != BT_MESH_DFU_SUCCESS) + { + /* Target either failed at earlier stage or during confirmation. In + * any case, the app is already notified. Don't consider the target + * here. + */ + + continue; + } + + if (target->effect == BT_MESH_DFU_EFFECT_UNPROV) + { + if (!target->blob.acked) + { + success = true; + continue; + } + + LOG_DBG("Target 0x%04x still provisioned", target->blob.addr); + target->phase = BT_MESH_DFU_PHASE_APPLY_FAIL; + target_failed(cli, target, BT_MESH_DFU_ERR_INTERNAL); + } + else if (!target->blob.acked) + { + if (bt_mesh_has_addr(target->blob.addr)) + { + /* Self-target deferred apply until distribution + * completes, so it can't confirm yet. + */ + + success = true; + continue; + } + + LOG_DBG("Target 0x%04x failed to respond", target->blob.addr); + target->phase = BT_MESH_DFU_PHASE_APPLY_FAIL; + target_failed(cli, target, BT_MESH_DFU_ERR_INTERNAL); + } + else if (target->status == BT_MESH_DFU_SUCCESS) + { + success = true; + } + } + + if (success) + { + cli->xfer.state = STATE_IDLE; + cli->xfer.flags = FLAG_COMPLETED; + + if (cli->cb && cli->cb->confirmed) + { + cli->cb->confirmed(cli); + } + } + else + { + dfu_failed(cli, BT_MESH_DFU_ERR_INTERNAL); + } +} + +static void cancel(struct bt_mesh_dfu_cli *cli) +{ + const struct blob_cli_broadcast_ctx tx = + { + .send = send_update_cancel, .next = cancelled, .acked = true + }; + + LOG_DBG(""); + + cli->op = BT_MESH_DFU_OP_UPDATE_STATUS; + + blob_cli_broadcast(&cli->blob, &tx); +} + +static void cancelled(struct bt_mesh_blob_cli *b) +{ + struct bt_mesh_dfu_cli *cli = DFU_CLI(b); + + cli->xfer.flags |= FLAG_CANCELLED; + dfu_failed(cli, BT_MESH_DFU_ERR_INTERNAL); +} + +/* Message handlers + */ + +static int handle_status( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfu_cli *cli = mod->rt->user_data; + enum bt_mesh_dfu_status status; + enum bt_mesh_dfu_phase phase; + struct bt_mesh_dfu_target *target; + uint8_t byte; + + byte = net_buf_simple_pull_u8(buf); + status = byte & BIT_MASK(3); + phase = byte >> 5; + + if (cli->req.type == REQ_STATUS && cli->req.addr == ctx->addr) + { + if (cli->req.params) + { + struct bt_mesh_dfu_target_status *rsp = cli->req.params; + + rsp->status = status; + rsp->phase = phase; + if (buf->len == 13) + { + rsp->ttl = net_buf_simple_pull_u8(buf); + rsp->effect = net_buf_simple_pull_u8(buf) & BIT_MASK(5); + rsp->timeout_base = net_buf_simple_pull_le16(buf); + rsp->blob_id = net_buf_simple_pull_le64(buf); + rsp->img_idx = net_buf_simple_pull_u8(buf); + } + else if (buf->len) + { + return -EINVAL; + } + + rsp->ttl = 0U; + rsp->effect = BT_MESH_DFU_EFFECT_NONE; + rsp->timeout_base = 0U; + rsp->blob_id = 0U; + rsp->img_idx = 0U; + } + + nxsem_post(&cli->req.sem); + } + + if (cli->op != BT_MESH_DFU_OP_UPDATE_STATUS) + { + return 0; + } + + target = target_get(cli, ctx->addr); + if (!target) + { + LOG_WRN("Unknown target 0x%04x", ctx->addr); + return -ENOENT; + } + + LOG_DBG("%u phase: %u, cur state: %u", status, phase, cli->xfer.state); + + target->phase = phase; + + if (cli->xfer.state == STATE_APPLY && phase == BT_MESH_DFU_PHASE_IDLE && + status == BT_MESH_DFU_ERR_WRONG_PHASE) + { + LOG_DBG("Response received with Idle phase"); + blob_cli_broadcast_rsp(&cli->blob, &target->blob); + return 0; + } + + if (status != BT_MESH_DFU_SUCCESS) + { + target_failed(cli, target, status); + blob_cli_broadcast_rsp(&cli->blob, &target->blob); + return 0; + } + + if (buf->len == 13) + { + net_buf_simple_pull_u8(buf); /* ttl */ + target->effect = net_buf_simple_pull_u8(buf) & BIT_MASK(5); + net_buf_simple_pull_le16(buf); /* timeout */ + + if (net_buf_simple_pull_le64(buf) != cli->xfer.blob.id) + { + LOG_WRN("Invalid BLOB ID"); + target_failed(cli, target, BT_MESH_DFU_ERR_BLOB_XFER_BUSY); + blob_cli_broadcast_rsp(&cli->blob, &target->blob); + return 0; + } + + target->img_idx = net_buf_simple_pull_u8(buf); + + LOG_DBG("Target 0x%04x receiving transfer", ctx->addr); + } + else if (buf->len) + { + return -EINVAL; + } + + if (cli->xfer.state == STATE_REFRESH) + { + if (phase == BT_MESH_DFU_PHASE_VERIFY) + { + LOG_DBG("Still pending..."); + return 0; + } + else if (phase == BT_MESH_DFU_PHASE_VERIFY_FAIL) + { + LOG_WRN("Verification failed on target 0x%04x", target->blob.addr); + target_failed(cli, target, BT_MESH_DFU_ERR_WRONG_PHASE); + } + } + else if (cli->xfer.state == STATE_APPLY) + { + if (phase != BT_MESH_DFU_PHASE_APPLYING && + (target->effect == BT_MESH_DFU_EFFECT_UNPROV || + phase != BT_MESH_DFU_PHASE_IDLE)) + { + LOG_WRN("Target 0x%04x in phase %u after apply", target->blob.addr, + phase); + target_failed(cli, target, BT_MESH_DFU_ERR_WRONG_PHASE); + blob_cli_broadcast_rsp(&cli->blob, &target->blob); + return 0; + } + + if (phase == BT_MESH_DFU_PHASE_APPLYING && + !bt_mesh_has_addr(target->blob.addr)) + { + /* MshDFUv1.0 Section 6.2.2.4 requires the Confirm step to + * wait until the targets have applied. Keep repeating Firmware + * Update Apply (Section 7.1.2.6) until the target leaves Applying + * Update, otherwise the Confirm step reads the old Firmware ID. + */ + + LOG_DBG("Target 0x%04x still applying", target->blob.addr); + return 0; + } + + /* The self-target defers its apply until the Confirm step + * completes, so it never leaves Applying Update on its own. + */ + + LOG_DBG("Target 0x%04x accepted apply (phase %u)", target->blob.addr, + phase); + blob_cli_broadcast_rsp(&cli->blob, &target->blob); + return 0; + } + else if (cli->xfer.state == STATE_CONFIRM) + { + if (phase == BT_MESH_DFU_PHASE_APPLYING) + { + LOG_DBG("Still pending..."); + return 0; + } + + if (phase != BT_MESH_DFU_PHASE_IDLE) + { + LOG_WRN("Target 0x%04x in phase %u after apply", target->blob.addr, + phase); + target->phase = BT_MESH_DFU_PHASE_APPLY_FAIL; + target_failed(cli, target, BT_MESH_DFU_ERR_WRONG_PHASE); + blob_cli_broadcast_rsp(&cli->blob, &target->blob); + return 0; + } + } + else if (cli->xfer.state == STATE_CANCEL) + { + target->phase = BT_MESH_DFU_PHASE_TRANSFER_CANCELED; + } + + blob_cli_broadcast_rsp(&cli->blob, &target->blob); + + return 0; +} + +static int handle_info_status( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfu_cli *cli = mod->rt->user_data; + struct bt_mesh_dfu_target *target; + enum bt_mesh_dfu_iter it = BT_MESH_DFU_ITER_CONTINUE; + uint8_t img_cnt; + uint8_t idx; + + if (!cli->req.img_cb || (cli->req.type == REQ_IMG && + cli->req.addr != ctx->addr)) + { + LOG_WRN("Unexpected info status from 0x%04x", ctx->addr); + return 0; + } + + img_cnt = net_buf_simple_pull_u8(buf); + if (img_cnt < cli->req.img_cnt) + { + cli->req.img_cnt = img_cnt; + } + + idx = net_buf_simple_pull_u8(buf); + if (idx >= img_cnt) + { + LOG_WRN("Invalid idx %u", idx); + return -ENOENT; + } + + LOG_DBG("Image list from 0x%04x from index %u", ctx->addr, idx); + + while (buf->len && cli->req.img_cb && idx < cli->req.img_cnt) + { + char uri_buf[CONFIG_BT_MESH_DFU_URI_MAXLEN + 1]; + struct bt_mesh_dfu_img img; + size_t uri_len; + + img.fwid_len = net_buf_simple_pull_u8(buf); + if (buf->len < img.fwid_len + 1) + { + LOG_WRN("Invalid format: fwid"); + return -EINVAL; + } + + img.fwid = net_buf_simple_pull_mem(buf, img.fwid_len); + + uri_len = net_buf_simple_pull_u8(buf); + if (buf->len < uri_len) + { + LOG_WRN("Invalid format: uri"); + return -EINVAL; + } + + LOG_DBG("\tImage %u\n\r\tfwid: %s", idx, bt_hex(img.fwid, + img.fwid_len)); + + if (uri_len) + { + size_t uri_buf_len = MIN(CONFIG_BT_MESH_DFU_URI_MAXLEN, uri_len); + + memcpy(uri_buf, + net_buf_simple_pull_mem(buf, uri_len), + uri_buf_len); + uri_buf[uri_buf_len] = '\0'; + img.uri = uri_buf; + } + else + { + img.uri = NULL; + } + + it = cli->req.img_cb(cli, ctx, idx, img_cnt, &img, cli->req.params); + if (it != BT_MESH_DFU_ITER_CONTINUE) + { + if (cli->req.type == REQ_IMG) + { + nxsem_post(&cli->req.sem); + } + + return 0; + } + + idx++; + } + + if (idx < cli->req.img_cnt) + { + LOG_DBG("Fetching more images (%u/%u)", idx, cli->req.img_cnt); + ctx->send_ttl = cli->req.ttl; + info_get(cli, ctx, idx, cli->req.img_cnt - idx, + (cli->req.type == REQ_IMG) ? NULL : &send_cb); + return 0; + } + + if (cli->req.type == REQ_IMG) + { + nxsem_post(&cli->req.sem); + return 0; + } + + /* Confirm-procedure termination: */ + + target = target_get(cli, ctx->addr); + if (target) + { + LOG_WRN("Target 0x%04x failed to apply image: %s", ctx->addr, + bt_hex(cli->xfer.slot->fwid, cli->xfer.slot->fwid_len)); + target->phase = BT_MESH_DFU_PHASE_APPLY_FAIL; + target_failed(cli, target, BT_MESH_DFU_ERR_INTERNAL); + blob_cli_broadcast_rsp(&cli->blob, &target->blob); + } + + return 0; +} + +static int handle_metadata_status( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfu_cli *cli = mod->rt->user_data; + struct bt_mesh_dfu_metadata_status *rsp = cli->req.params; + uint8_t hdr; + uint8_t idx; + + hdr = net_buf_simple_pull_u8(buf); + idx = net_buf_simple_pull_u8(buf); + + if (cli->req.type != REQ_METADATA || ctx->addr != cli->req.addr || + idx != rsp->idx) + { + LOG_WRN("Unexpected metadata status from 0x%04x img %u", ctx->addr, + idx); + if (cli->req.type != REQ_METADATA) + { + LOG_WRN("Expected %u", cli->req.type); + } + else + { + LOG_WRN("Expected 0x%04x img %u", cli->req.addr, idx); + } + + return 0; + } + + rsp->status = hdr & BIT_MASK(3); + rsp->effect = (hdr >> 3); + nxsem_post(&cli->req.sem); + + return 0; +} + +const struct bt_mesh_model_op _bt_mesh_dfu_cli_op[] = +{ + { + BT_MESH_DFU_OP_UPDATE_STATUS, BT_MESH_LEN_MIN(1), handle_status + }, + + { + BT_MESH_DFU_OP_UPDATE_INFO_STATUS, BT_MESH_LEN_MIN( + 2), handle_info_status + }, + + { + BT_MESH_DFU_OP_UPDATE_METADATA_STATUS, + BT_MESH_LEN_EXACT(2), + handle_metadata_status + }, + + BT_MESH_MODEL_OP_END, +}; + +static int dfu_cli_init(const struct bt_mesh_model *mod) +{ + int err; + struct bt_mesh_dfu_cli *cli = mod->rt->user_data; + + cli->mod = mod; + + const struct bt_mesh_model *blob_cli = + bt_mesh_model_find(bt_mesh_model_elem(mod), + BT_MESH_MODEL_ID_BLOB_CLI); + + if (blob_cli == NULL) + { + LOG_ERR("Missing BLOB Cli."); + return -EINVAL; + } + + err = bt_mesh_model_extend(mod, cli->blob.mod); + + if (err) + { + return err; + } + + nxsem_init(&cli->req.sem, 0, 0); + + return 0; +} + +static void dfu_cli_reset(const struct bt_mesh_model *mod) +{ + struct bt_mesh_dfu_cli *cli = mod->rt->user_data; + + cli->req.type = REQ_NONE; + cli->req.addr = BT_MESH_ADDR_UNASSIGNED; + cli->req.img_cnt = 0; + cli->req.img_cb = NULL; + cli->xfer.state = STATE_IDLE; + cli->xfer.flags = 0; +} + +const struct bt_mesh_model_cb _bt_mesh_dfu_cli_cb = +{ + .init = dfu_cli_init, + .reset = dfu_cli_reset, +}; + +/* Public API + */ + +int bt_mesh_dfu_cli_send( + struct bt_mesh_dfu_cli *cli, const struct bt_mesh_blob_cli_inputs *inputs, + const struct bt_mesh_blob_io *io, const struct bt_mesh_dfu_cli_xfer *xfer) +{ + struct bt_mesh_dfu_target *target; + + if (bt_mesh_dfu_cli_is_busy(cli)) + { + return -EBUSY; + } + + cli->xfer.blob.mode = xfer->mode; + cli->xfer.blob.size = xfer->slot->size; + + if (xfer->blob_id == 0) + { + int err = bt_rand(&cli->xfer.blob.id, sizeof(cli->xfer.blob.id)); + + if (err) + { + return err; + } + } + else + { + cli->xfer.blob.id = xfer->blob_id; + } + + cli->xfer.io = io; + cli->blob.inputs = inputs; + cli->xfer.slot = xfer->slot; + cli->xfer.flags = 0U; + + if (xfer->blob_params) + { + cli->xfer.flags |= FLAG_SKIP_CAPS_GET; + cli->xfer.blob.block_size_log = xfer->blob_params->block_size_log; + cli->xfer.blob.chunk_size = xfer->blob_params->chunk_size; + } + + /* Phase will be set based on target status messages: */ + + TARGETS_FOR_EACH(cli, target) + { + target->status = BT_MESH_DFU_SUCCESS; + target->phase = BT_MESH_DFU_PHASE_UNKNOWN; + } + + initiate(cli); + return 0; +} + +int bt_mesh_dfu_cli_suspend(struct bt_mesh_dfu_cli *cli) +{ + int err; + + err = bt_mesh_blob_cli_suspend(&cli->blob); + if (!err) + { + cli->xfer.state = STATE_SUSPENDED; + } + + return err; +} + +int bt_mesh_dfu_cli_resume(struct bt_mesh_dfu_cli *cli) +{ + struct bt_mesh_dfu_target *target; + + if (cli->xfer.state != STATE_SUSPENDED) + { + return -EINVAL; + } + + cli->xfer.flags = FLAG_RESUME; + + /* Restore timed out targets. */ + + TARGETS_FOR_EACH(cli, target) + { + if (!!target->blob.timedout) + { + target->status = BT_MESH_DFU_SUCCESS; + target->phase = BT_MESH_DFU_PHASE_UNKNOWN; + } + } + + initiate(cli); + return 0; +} + +int bt_mesh_dfu_cli_cancel(struct bt_mesh_dfu_cli *cli, + struct bt_mesh_msg_ctx *ctx) +{ + if (ctx) + { + int err; + + err = req_setup(cli, REQ_STATUS, ctx->addr, NULL); + if (err) + { + return err; + } + + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_DFU_OP_UPDATE_CANCEL, 0); + bt_mesh_model_msg_init(&buf, BT_MESH_DFU_OP_UPDATE_CANCEL); + + err = bt_mesh_model_send(cli->mod, ctx, &buf, NULL, NULL); + if (err) + { + cli->req.type = REQ_NONE; + return err; + } + + return req_wait(cli, MSEC2TICK(dfu_cli_timeout)); + } + + if (cli->xfer.state == STATE_IDLE) + { + return -EALREADY; + } + + cli->xfer.state = STATE_CANCEL; + blob_cli_broadcast_abort(&cli->blob); + cancel(cli); + return 0; +} + +int bt_mesh_dfu_cli_apply(struct bt_mesh_dfu_cli *cli) +{ + if (cli->xfer.state != STATE_VERIFIED) + { + return -EBUSY; + } + + apply(cli); + + return 0; +} + +int bt_mesh_dfu_cli_confirm(struct bt_mesh_dfu_cli *cli) +{ + if (cli->xfer.state != STATE_APPLIED) + { + return -EBUSY; + } + + cli->xfer.state = STATE_CONFIRM; + confirm(cli); + + return 0; +} + +uint8_t bt_mesh_dfu_cli_progress(struct bt_mesh_dfu_cli *cli) +{ + if (cli->xfer.state == STATE_TRANSFER) + { + return bt_mesh_blob_cli_xfer_progress_active_get(&cli->blob); + } + + if (cli->xfer.state == STATE_IDLE) + { + if (cli->xfer.flags & FLAG_COMPLETED) + { + return 100U; + } + + return 0U; + } + + return 100U; +} + +bool bt_mesh_dfu_cli_is_busy(struct bt_mesh_dfu_cli *cli) +{ + return (cli->xfer.state == STATE_TRANSFER || + cli->xfer.state == STATE_REFRESH || + cli->xfer.state == STATE_APPLY || + cli->xfer.state == STATE_CONFIRM) && + !(cli->xfer.flags & FLAG_FAILED); +} + +int bt_mesh_dfu_cli_imgs_get( + struct bt_mesh_dfu_cli *cli, struct bt_mesh_msg_ctx *ctx, + bt_mesh_dfu_img_cb_t cb, void *cb_data, uint8_t max_count) +{ + int err; + + if (cli->req.img_cb) + { + return -EBUSY; + } + + err = req_setup(cli, REQ_IMG, ctx->addr, NULL); + if (err) + { + return err; + } + + cli->req.img_cb = cb; + cli->req.params = cb_data; + cli->req.ttl = ctx->send_ttl; + cli->req.img_cnt = max_count; + + err = info_get(cli, ctx, 0, cli->req.img_cnt, NULL); + if (err) + { + cli->req.img_cb = NULL; + cli->req.type = REQ_NONE; + return err; + } + + err = req_wait(cli, MSEC2TICK(dfu_cli_timeout)); + + cli->req.img_cb = NULL; + + return err; +} + +int bt_mesh_dfu_cli_metadata_check( + struct bt_mesh_dfu_cli *cli, struct bt_mesh_msg_ctx *ctx, uint8_t img_idx, + const struct bt_mesh_dfu_slot *slot, + struct bt_mesh_dfu_metadata_status *rsp) +{ + int err; + + err = req_setup(cli, REQ_METADATA, ctx->addr, rsp); + if (err) + { + return err; + } + + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_DFU_OP_UPDATE_METADATA_CHECK, + 1 + CONFIG_BT_MESH_DFU_METADATA_MAXLEN); + bt_mesh_model_msg_init(&buf, BT_MESH_DFU_OP_UPDATE_METADATA_CHECK); + + net_buf_simple_add_u8(&buf, img_idx); + + if (slot->metadata_len) + { + net_buf_simple_add_mem(&buf, slot->metadata, slot->metadata_len); + } + + rsp->idx = img_idx; + + err = bt_mesh_model_send(cli->mod, ctx, &buf, NULL, NULL); + if (err) + { + cli->req.type = REQ_NONE; + return err; + } + + return req_wait(cli, MSEC2TICK(dfu_cli_timeout)); +} + +int bt_mesh_dfu_cli_status_get( + struct bt_mesh_dfu_cli *cli, struct bt_mesh_msg_ctx *ctx, + struct bt_mesh_dfu_target_status *rsp) +{ + int err; + + err = req_setup(cli, REQ_STATUS, ctx->addr, rsp); + if (err) + { + return err; + } + + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_DFU_OP_UPDATE_GET, 0); + bt_mesh_model_msg_init(&buf, BT_MESH_DFU_OP_UPDATE_GET); + + err = bt_mesh_model_send(cli->mod, ctx, &buf, NULL, NULL); + if (err) + { + cli->req.type = REQ_NONE; + return err; + } + + return req_wait(cli, MSEC2TICK(dfu_cli_timeout)); +} + +int32_t bt_mesh_dfu_cli_timeout_get(void) +{ + return dfu_cli_timeout; +} + +void bt_mesh_dfu_cli_timeout_set(int32_t t) +{ + dfu_cli_timeout = t; +} + +void bt_mesh_dfu_cli_restore(struct bt_mesh_dfu_cli *cli, + const struct bt_mesh_blob_cli_inputs *inputs) +{ + cli->xfer.state = STATE_APPLIED; + cli->blob.inputs = inputs; +} diff --git a/libs/zblue/mesh/dfu_metadata.c b/libs/zblue/mesh/dfu_metadata.c new file mode 100644 index 0000000000000..6b82ef8d79cb6 --- /dev/null +++ b/libs/zblue/mesh/dfu_metadata.c @@ -0,0 +1,128 @@ +/**************************************************************************** + * libs/zblue/mesh/dfu_metadata.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include "crypto.h" +#include "access.h" + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bt_mesh_dfu_metadata_decode(struct net_buf_simple *buf, + struct bt_mesh_dfu_metadata *metadata) +{ + if (buf->len < 12) + { + return -EMSGSIZE; + } + + metadata->fw_ver.major = net_buf_simple_pull_u8(buf); + metadata->fw_ver.minor = net_buf_simple_pull_u8(buf); + metadata->fw_ver.revision = net_buf_simple_pull_le16(buf); + metadata->fw_ver.build_num = net_buf_simple_pull_le32(buf); + metadata->fw_size = net_buf_simple_pull_le24(buf); + metadata->fw_core_type = net_buf_simple_pull_u8(buf); + + if (metadata->fw_core_type & BT_MESH_DFU_FW_CORE_TYPE_APP) + { + if (buf->len < 6) + { + return -EMSGSIZE; + } + + metadata->comp_hash = net_buf_simple_pull_le32(buf); + metadata->elems = net_buf_simple_pull_le16(buf); + } + + metadata->user_data = buf->len > 0 ? buf->data : NULL; + metadata->user_data_len = buf->len; + + return 0; +} + +int bt_mesh_dfu_metadata_encode(const struct bt_mesh_dfu_metadata *metadata, + struct net_buf_simple *buf) +{ + size_t md_len_min = 12 + metadata->user_data_len; + + if (metadata->fw_core_type & BT_MESH_DFU_FW_CORE_TYPE_APP) + { + md_len_min += 6; + } + + if (net_buf_simple_tailroom(buf) < md_len_min) + { + return -EMSGSIZE; + } + + net_buf_simple_add_u8(buf, metadata->fw_ver.major); + net_buf_simple_add_u8(buf, metadata->fw_ver.minor); + net_buf_simple_add_le16(buf, metadata->fw_ver.revision); + net_buf_simple_add_le32(buf, metadata->fw_ver.build_num); + net_buf_simple_add_le24(buf, metadata->fw_size); + net_buf_simple_add_u8(buf, metadata->fw_core_type); + net_buf_simple_add_le32(buf, metadata->comp_hash); + net_buf_simple_add_le16(buf, metadata->elems); + + if (metadata->user_data_len > 0) + { + net_buf_simple_add_mem(buf, metadata->user_data, + metadata->user_data_len); + } + + return 0; +} + +int bt_mesh_dfu_metadata_comp_hash_get(struct net_buf_simple *buf, + uint8_t *key, uint32_t *hash) +{ + uint8_t mac[16]; + int err; + struct bt_mesh_sg sg = + { + .data = buf->data, .len = buf->len + }; + + err = bt_mesh_aes_cmac_raw_key(key, &sg, 1, mac); + if (err) + { + return err; + } + + *hash = sys_get_le32(mac); + + return 0; +} + +int bt_mesh_dfu_metadata_comp_hash_local_get(uint8_t *key, uint32_t *hash) +{ + NET_BUF_SIMPLE_DEFINE(buf, BT_MESH_TX_SDU_MAX); + int err; + + err = bt_mesh_comp_data_get_page_0(&buf, 0); + if (err) + { + return err; + } + + err = bt_mesh_dfu_metadata_comp_hash_get(&buf, key, hash); + return err; +} diff --git a/libs/zblue/mesh/dfu_slot.c b/libs/zblue/mesh/dfu_slot.c new file mode 100644 index 0000000000000..0cac7a5bfdd68 --- /dev/null +++ b/libs/zblue/mesh/dfu_slot.c @@ -0,0 +1,472 @@ +/**************************************************************************** + * libs/zblue/mesh/dfu_slot.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include "dfu_slot.h" +#include +#include +#include +#include +#include + +#define LOG_LEVEL CONFIG_BT_MESH_DFU_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_dfu_slot); + +#define SLOT_ENTRY_BUFLEN 25 + +#define DFU_SLOT_SETTINGS_PATH "bt/mesh-dfu/slot" + +#define HEADER_SIZE offsetof(struct slot, slot.fwid) + +#define PROP_HEADER "h" +#define PROP_FWID "id" +#define PROP_METADATA "m" + +static struct list_node list = LIST_INITIAL_VALUE(list); + +static struct slot +{ + uint32_t idx; + struct bt_mesh_dfu_slot slot; + struct list_node n; +} + +slots[CONFIG_BT_MESH_DFU_SLOT_CNT]; + +static uint32_t slot_index; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static char *slot_entry_encode(uint16_t idx, + char buf[SLOT_ENTRY_BUFLEN], + const char *property) +{ + snprintf(buf, SLOT_ENTRY_BUFLEN, DFU_SLOT_SETTINGS_PATH "/%x/%s", idx, + property); + + return buf; +} + +static bool slot_eq(const struct bt_mesh_dfu_slot *slot, + const uint8_t *fwid, size_t fwid_len) +{ + return (slot->fwid_len == fwid_len) && !memcmp(fwid, slot->fwid, fwid_len); +} + +static bool is_slot_committed(struct slot *slot_to_check) +{ + struct slot *s; + + list_for_every_container(&list, s, n) + { + if (s == slot_to_check) + { + return true; + } + } + + return false; +} + +static int slot_store(const struct slot *slot_to_store) +{ + uint16_t idx = ARRAY_INDEX(slots, slot_to_store); + char buf[SLOT_ENTRY_BUFLEN]; + int err; + + err = settings_save_one(slot_entry_encode(idx, buf, PROP_HEADER), + slot_to_store, HEADER_SIZE); + if (err) + { + return err; + } + + err = settings_save_one(slot_entry_encode(idx, buf, PROP_FWID), + slot_to_store->slot.fwid, + slot_to_store->slot.fwid_len); + if (err) + { + return err; + } + + err = settings_save_one(slot_entry_encode(idx, buf, PROP_METADATA), + slot_to_store->slot.metadata, + slot_to_store->slot.metadata_len); + + return err; +} + +static void slot_erase(struct slot *slot_to_erase) +{ + uint16_t idx = ARRAY_INDEX(slots, slot_to_erase); + char buf[SLOT_ENTRY_BUFLEN]; + + (void)settings_delete(slot_entry_encode(idx, buf, PROP_HEADER)); + (void)settings_delete(slot_entry_encode(idx, buf, PROP_FWID)); + (void)settings_delete(slot_entry_encode(idx, buf, PROP_METADATA)); +} + +static void slot_index_defrag(void) +{ + slot_index = 0; + struct slot *s; + + list_for_every_container(&list, s, n) + { + s->idx = ++slot_index; + slot_store(s); + } +} + +int bt_mesh_dfu_slot_count(void) +{ + int cnt = 0; + struct list_node *n; + + list_for_every(&list, n) + { + cnt++; + } + + return cnt; +} + +struct bt_mesh_dfu_slot *bt_mesh_dfu_slot_reserve(void) +{ + struct slot *slot = NULL; + + for (int i = 0; i < ARRAY_SIZE(slots); ++i) + { + if (slots[i].idx == 0) + { + slot = &slots[i]; + break; + } + } + + if (!slot) + { + LOG_WRN("No space"); + return NULL; + } + + if (slot_index == UINT32_MAX) + { + slot_index_defrag(); + } + + slot->slot.fwid_len = 0; + slot->slot.metadata_len = 0; + slot->slot.size = 0; + slot->idx = ++slot_index; + + LOG_DBG("Reserved slot #%u", slot - &slots[0]); + + return &slot->slot; +} + +int bt_mesh_dfu_slot_info_set(struct bt_mesh_dfu_slot *dfu_slot, size_t size, + const uint8_t *metadata, size_t metadata_len) +{ + struct slot *slot = CONTAINER_OF(dfu_slot, struct slot, slot); + + if (metadata_len > CONFIG_BT_MESH_DFU_METADATA_MAXLEN) + { + return -EFBIG; + } + + if (slot->idx == 0 || is_slot_committed(slot)) + { + return -EINVAL; + } + + slot->slot.size = size; + slot->slot.metadata_len = metadata_len; + memcpy(slot->slot.metadata, metadata, metadata_len); + return 0; +} + +int bt_mesh_dfu_slot_fwid_set( + struct bt_mesh_dfu_slot *dfu_slot, const uint8_t *fwid, size_t fwid_len) +{ + struct slot *slot = CONTAINER_OF(dfu_slot, struct slot, slot); + + if (fwid_len > CONFIG_BT_MESH_DFU_FWID_MAXLEN) + { + return -EFBIG; + } + + if (slot->idx == 0 || is_slot_committed(slot)) + { + return -EINVAL; + } + + for (int i = 0; i < ARRAY_SIZE(slots); i++) + { + if (slots[i].idx != 0 && slot_eq(&slots[i].slot, fwid, fwid_len)) + { + return is_slot_committed(&slots[i]) ? -EEXIST : -EALREADY; + } + } + + slot->slot.fwid_len = fwid_len; + memcpy(slot->slot.fwid, fwid, fwid_len); + return 0; +} + +int bt_mesh_dfu_slot_commit(struct bt_mesh_dfu_slot *dfu_slot) +{ + int err; + struct slot *slot = CONTAINER_OF(dfu_slot, struct slot, slot); + + if (slot->idx == 0 || slot->slot.fwid_len == 0 || slot->slot.size == 0 || + is_slot_committed(slot)) + { + return -EINVAL; + } + + err = slot_store(slot); + if (err) + { + LOG_WRN("Store failed (err: %d)", err); + return err; + } + + list_add_tail(&list, &slot->n); + + LOG_DBG("Stored slot #%u: %s", ARRAY_INDEX(slots, slot), + bt_hex(slot->slot.fwid, slot->slot.fwid_len)); + return 0; +} + +void bt_mesh_dfu_slot_release(const struct bt_mesh_dfu_slot *dfu_slot) +{ + struct slot *slot = CONTAINER_OF(dfu_slot, struct slot, slot); + + if (is_slot_committed(slot)) + { + return; + } + + slot->idx = 0; +} + +int bt_mesh_dfu_slot_del(const struct bt_mesh_dfu_slot *dfu_slot) +{ + struct slot *slot = CONTAINER_OF(dfu_slot, struct slot, slot); + + if (!list_find_and_remove(&list, &slot->n)) + { + return -EINVAL; + } + + int idx = ARRAY_INDEX(slots, slot); + + LOG_DBG("%u", idx); + + slot_erase(slot); + slot->idx = 0; + + return 0; +} + +void bt_mesh_dfu_slot_del_all(void) +{ + struct slot *s; + + list_for_every_container(&list, s, n) + { + slot_erase(s); + s->idx = 0; + } + + list_initialize(&list); +} + +const struct bt_mesh_dfu_slot *bt_mesh_dfu_slot_at(uint16_t img_idx) +{ + struct slot *s; + + list_for_every_container(&list, s, n) + { + if (!img_idx--) + { + return &s->slot; + } + } + + return NULL; +} + +int bt_mesh_dfu_slot_get(const uint8_t *fwid, + size_t fwid_len, struct bt_mesh_dfu_slot **slot) +{ + struct slot *s; + int idx = 0; + + list_for_every_container(&list, s, n) + { + if (slot_eq(&s->slot, fwid, fwid_len)) + { + if (slot) + { + *slot = &s->slot; + } + + return idx; + } + + idx++; + } + + return -ENOENT; +} + +int bt_mesh_dfu_slot_img_idx_get(const struct bt_mesh_dfu_slot *dfu_slot) +{ + struct slot *s; + int idx = 0; + + list_for_every_container(&list, s, n) + { + if (&s->slot == dfu_slot) + { + return idx; + } + + idx++; + } + + return -ENOENT; +} + +size_t bt_mesh_dfu_slot_foreach(bt_mesh_dfu_slot_cb_t cb, void *user_data) +{ + enum bt_mesh_dfu_iter iter; + size_t cnt = 0; + struct slot *s; + + list_for_every_container(&list, s, n) + { + cnt++; + + if (!cb) + { + continue; + } + + iter = cb(&s->slot, user_data); + if (iter != BT_MESH_DFU_ITER_CONTINUE) + { + break; + } + } + + return cnt; +} + +static int slot_data_load(const char *key, + size_t len_rd, settings_read_cb read_cb, + void *cb_arg) +{ + const char *prop; + size_t len; + uint16_t idx; + + idx = strtol(key, NULL, 16); + + if (idx >= ARRAY_SIZE(slots)) + { + return 0; + } + + len = settings_name_next(key, &prop); + + if (!strncmp(prop, PROP_HEADER, len)) + { + if (read_cb(cb_arg, &slots[idx], HEADER_SIZE) > 0) + { + struct slot *s; + struct slot *prev = NULL; + + list_for_every_container(&list, s, n) + { + if (s->idx > slots[idx].idx) + { + break; + } + + prev = s; + } + + if (prev == NULL) + { + list_add_head(&list, &slots[idx].n); + } + else + { + list_insert_after(&list, &prev->n, &slots[idx].n); + } + + if (slots[idx].idx >= slot_index) + { + slot_index = slots[idx].idx + 1; + } + } + + return 0; + } + + if (!strncmp(prop, PROP_FWID, len)) + { + if (read_cb(cb_arg, &slots[idx].slot.fwid, + sizeof(slots[idx].slot.fwid)) < 0) + { + slots[idx].idx = 0; + list_find_and_remove(&list, &slots[idx].n); + return 0; + } + + slots[idx].slot.fwid_len = len_rd; + return 0; + } + + if (!strncmp(prop, PROP_METADATA, len)) + { + if (read_cb(cb_arg, &slots[idx].slot.metadata, + sizeof(slots[idx].slot.metadata)) < 0) + { + slots[idx].idx = 0; + list_find_and_remove(&list, &slots[idx].n); + return 0; + } + + slots[idx].slot.metadata_len = len_rd; + return 0; + } + + return 0; +} + +SETTINGS_STATIC_HANDLER_DEFINE(bt_mesh_dfu_slots, + DFU_SLOT_SETTINGS_PATH, + NULL, + slot_data_load, + + NULL, NULL); diff --git a/libs/zblue/mesh/dfu_slot.h b/libs/zblue/mesh/dfu_slot.h new file mode 100644 index 0000000000000..966f2841776dc --- /dev/null +++ b/libs/zblue/mesh/dfu_slot.h @@ -0,0 +1,161 @@ +/**************************************************************************** + * libs/zblue/mesh/dfu_slot.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/** @brief Slot iteration callback. + * + * @param slot A valid DFU image slot. + * @param user_data User data passed to @ref bt_mesh_dfu_slot_foreach. + * + * @return Iteration action determining next step. + */ + +typedef enum bt_mesh_dfu_iter( + *bt_mesh_dfu_slot_cb_t)(const struct bt_mesh_dfu_slot *slot, void + **user_data); + +/** @brief Get the number of slots committed to the firmware list. + * + * @return Number of committed slots. + */ + +int bt_mesh_dfu_slot_count(void); + +/** @brief Reserve a new DFU image slot for a distributable image. + * + * A DFU image slot represents a single distributable DFU image with all its + * metadata. The slot data must be set using @ref bt_mesh_dfu_slot_info_set + * and + * @ref bt_mesh_dfu_slot_fwid_set, and the slot committed using + * @ref bt_mesh_dfu_slot_commit for the slot to be considered part of the + * slot list. + * + * @return A pointer to the reserved slot, or NULL if allocation failed. + */ + +struct bt_mesh_dfu_slot *bt_mesh_dfu_slot_reserve(void); + +/** @brief Set the size and metadata for a reserved slot. + * + * @param dfu_slot Pointer to the reserved slot for which to set the + * metadata. + * @param size The size of the image. + * @param metadata Metadata or NULL. + * @param metadata_len Length of the metadata, at most @c + * CONFIG_BT_MESH_DFU_METADATA_MAXLEN. + * + * @return 0 on success, (negative) error code otherwise. + */ + +int bt_mesh_dfu_slot_info_set(struct bt_mesh_dfu_slot *dfu_slot, size_t size, + const uint8_t *metadata, size_t metadata_len); + +/** @brief Set the new fwid for the incoming image for a reserved slot. + * + * @param dfu_slot Pointer to the reserved slot for which to set the fwid. + * @param fwid Fwid to set. + * @param fwid_len Length of the fwid, at most @c + * CONFIG_BT_MESH_DFU_FWID_MAXLEN. + * + * @return 0 on success, (negative) error code otherwise. + */ + +int bt_mesh_dfu_slot_fwid_set( + struct bt_mesh_dfu_slot *dfu_slot, const uint8_t *fwid, size_t fwid_len); + +/** @brief Commit the reserved slot to the list of slots, and store it + * persistently. + * + * If the commit fails for any reason, the slot will still be in the + * reserved state after this call. + * + * @param dfu_slot Pointer to the reserved slot. + * + * @return 0 on success, (negative) error code otherwise. + */ + +int bt_mesh_dfu_slot_commit(struct bt_mesh_dfu_slot *dfu_slot); + +/** @brief Release a reserved slot so that it can be reserved again. + * + * @param dfu_slot Pointer to the reserved slot. + */ + +void bt_mesh_dfu_slot_release(const struct bt_mesh_dfu_slot *dfu_slot); + +/** @brief Delete a committed DFU image slot. + * + * @param slot Slot to delete. Must be a valid pointer acquired from this + * module. + * + * @return 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_dfu_slot_del(const struct bt_mesh_dfu_slot *slot); + +/** @brief Delete all DFU image slots. + * + * @return 0 on success, or (negative) error code on failure. + */ + +void bt_mesh_dfu_slot_del_all(void); + +/** @brief Get the DFU image slot at the given firmware image list index. + * + * @param idx DFU image slot index. + * + * @return The DFU image slot at the given index, or NULL if no slot exists + * with the given index. + */ + +const struct bt_mesh_dfu_slot *bt_mesh_dfu_slot_at(uint16_t img_idx); + +/** @brief Get the committed DFU image slot for the image with the given + * firmware ID. + * + * @param fwid Firmware ID. + * @param fwid_len Firmware ID length. + * @param slot Slot pointer to fill. + * + * @return Slot index on success, or negative error code on failure. + */ + +int bt_mesh_dfu_slot_get(const uint8_t *fwid, + size_t fwid_len, struct bt_mesh_dfu_slot **slot); + +/** @brief Get the index in the firmware image list for the given slot. + * + * @param slot Slot to find. + * + * @return Slot index on success, or negative error code on failure. + */ + +int bt_mesh_dfu_slot_img_idx_get(const struct bt_mesh_dfu_slot *slot); + +/** @brief Iterate through all DFU image slots. + * + * Calls the callback for every DFU image slot or until the callback returns + * something other than @ref BT_MESH_DFU_ITER_CONTINUE. + * + * @param cb Callback to call for each slot, or NULL to just count + * the number of slots. + * @param user_data User data to pass to the callback. + * + * @return The number of slots iterated over. + */ + +size_t bt_mesh_dfu_slot_foreach(bt_mesh_dfu_slot_cb_t cb, void *user_data); diff --git a/libs/zblue/mesh/dfu_srv.c b/libs/zblue/mesh/dfu_srv.c new file mode 100644 index 0000000000000..3c4d5bbc360f2 --- /dev/null +++ b/libs/zblue/mesh/dfu_srv.c @@ -0,0 +1,839 @@ +/**************************************************************************** + * libs/zblue/mesh/dfu_srv.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include "dfu.h" +#include "blob.h" +#include "access.h" +#include "dfd_srv_internal.h" + +#define LOG_LEVEL CONFIG_BT_MESH_DFU_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_dfu_srv); + +#define UPDATE_IDX_NONE 0xff + +BUILD_ASSERT((DFU_UPDATE_START_MSG_MAXLEN + + BT_MESH_MODEL_OP_LEN(BT_MESH_DFU_OP_UPDATE_START) + + BT_MESH_MIC_SHORT) <= BT_MESH_RX_SDU_MAX, + "The Firmware Update Start message does not fit into the " + "maximum incoming SDU size."); + +BUILD_ASSERT((DFU_UPDATE_INFO_STATUS_MSG_MINLEN + + BT_MESH_MODEL_OP_LEN(BT_MESH_DFU_OP_UPDATE_INFO_STATUS) + + BT_MESH_MIC_SHORT) <= BT_MESH_TX_SDU_MAX, + "The Firmware Update Info Status message does not fit into " + "the maximum outgoing SDU size."); + +struct dfu_srv_persistent_state +{ + uint8_t effect; + uint8_t phase; + uint8_t ttl; + uint8_t idx; + uint16_t timeout_base; + uint16_t meta; +} __packed; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void store_state(struct bt_mesh_dfu_srv *srv) +{ + struct dfu_srv_persistent_state state = + { + .effect = srv->update.effect, + .phase = srv->update.phase, + .ttl = srv->update.ttl, + .idx = srv->update.idx, + .timeout_base = srv->update.timeout_base, + .meta = srv->update.meta, + }; + + bt_mesh_model_data_store(srv->mod, false, NULL, &state, sizeof(state)); +} + +static void erase_state(struct bt_mesh_dfu_srv *srv) +{ + bt_mesh_model_data_store(srv->mod, false, NULL, NULL, 0); +} + +static void xfer_failed(struct bt_mesh_dfu_srv *srv) +{ + if (!bt_mesh_dfu_srv_is_busy(srv) || srv->update.idx >= srv->img_count) + { + return; + } + + erase_state(srv); + + if (srv->cb->end) + { + srv->cb->end(srv, &srv->imgs[srv->update.idx], false); + } +} + +static enum bt_mesh_dfu_status metadata_check(struct bt_mesh_dfu_srv *srv, + uint8_t idx, + struct net_buf_simple *buf, + enum + bt_mesh_dfu_effect *effect) +{ + *effect = BT_MESH_DFU_EFFECT_NONE; + + if (idx >= srv->img_count) + { + return BT_MESH_DFU_ERR_FW_IDX; + } + + if (!srv->cb->check) + { + return BT_MESH_DFU_SUCCESS; + } + + if (srv->cb->check(srv, &srv->imgs[idx], buf, effect)) + { + *effect = BT_MESH_DFU_EFFECT_NONE; + return BT_MESH_DFU_ERR_METADATA; + } + + return BT_MESH_DFU_SUCCESS; +} + +static void apply_rsp_sent(int err, void *cb_params) +{ + struct bt_mesh_dfu_srv *srv = cb_params; + + if (err) + { + /* return phase back to give client one more chance. */ + + srv->update.phase = BT_MESH_DFU_PHASE_VERIFY_OK; + LOG_WRN("Apply response failed, wait for retry (err %d)", err); + return; + } + + LOG_DBG(""); + + if (!srv->cb->apply || srv->update.idx == UPDATE_IDX_NONE) + { + srv->update.phase = BT_MESH_DFU_PHASE_IDLE; + erase_state(srv); + LOG_DBG("Prerequisites for apply callback are wrong"); + return; + } + + store_state(srv); + + if (srv->update.self_update) + { + LOG_DBG("Self-update: deferring apply"); + return; + } + + err = srv->cb->apply(srv, &srv->imgs[srv->update.idx]); + if (err) + { + srv->update.phase = BT_MESH_DFU_PHASE_IDLE; + erase_state(srv); + LOG_DBG("Application apply callback failed (err %d)", err); + } +} + +static void apply_rsp_sending(uint16_t duration, int err, void *cb_params) +{ + if (err) + { + apply_rsp_sent(err, cb_params); + } +} + +static void verify(struct bt_mesh_dfu_srv *srv) +{ + srv->update.phase = BT_MESH_DFU_PHASE_VERIFY; + + if (srv->update.idx >= srv->img_count) + { + bt_mesh_dfu_srv_rejected(srv); + return; + } + + if (!srv->cb->end) + { + bt_mesh_dfu_srv_verified(srv); + return; + } + + srv->cb->end(srv, &srv->imgs[srv->update.idx], true); + if (srv->update.phase == BT_MESH_DFU_PHASE_VERIFY) + { + store_state(srv); + } +} + +static int handle_info_get(const struct bt_mesh_model *mod, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfu_srv *srv = mod->rt->user_data; + uint8_t idx; + uint8_t limit; + + if (srv->update.phase == BT_MESH_DFU_PHASE_APPLYING) + { + LOG_INF("Still applying, not responding"); + return -EBUSY; + } + + idx = net_buf_simple_pull_u8(buf); + limit = net_buf_simple_pull_u8(buf); + + LOG_DBG("from %u (limit: %u)", idx, limit); + + NET_BUF_SIMPLE_DEFINE(rsp, BT_MESH_TX_SDU_MAX); + bt_mesh_model_msg_init(&rsp, BT_MESH_DFU_OP_UPDATE_INFO_STATUS); + net_buf_simple_add_u8(&rsp, srv->img_count); + net_buf_simple_add_u8(&rsp, idx); + + for (; idx < srv->img_count && limit > 0; ++idx) + { + uint32_t entry_len; + + if (!srv->imgs[idx].fwid) + { + continue; + } + + entry_len = 2 + srv->imgs[idx].fwid_len; + if (srv->imgs[idx].uri) + { + entry_len += strlen(srv->imgs[idx].uri); + } + + if (net_buf_simple_tailroom(&rsp) + BT_MESH_MIC_SHORT < entry_len) + { + break; + } + + net_buf_simple_add_u8(&rsp, srv->imgs[idx].fwid_len); + net_buf_simple_add_mem(&rsp, srv->imgs[idx].fwid, + srv->imgs[idx].fwid_len); + + if (srv->imgs[idx].uri) + { + size_t len = strlen(srv->imgs[idx].uri); + + net_buf_simple_add_u8(&rsp, len); + net_buf_simple_add_mem(&rsp, srv->imgs[idx].uri, len); + } + else + { + net_buf_simple_add_u8(&rsp, 0); + } + + limit--; + } + + if (srv->update.phase != BT_MESH_DFU_PHASE_IDLE) + { + ctx->send_ttl = srv->update.ttl; + } + + bt_mesh_model_send(mod, ctx, &rsp, NULL, NULL); + + return 0; +} + +static int handle_metadata_check(const struct bt_mesh_model *mod, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfu_srv *srv = mod->rt->user_data; + enum bt_mesh_dfu_status status; + enum bt_mesh_dfu_effect effect; + uint8_t idx; + + BT_MESH_MODEL_BUF_DEFINE(rsp, BT_MESH_DFU_OP_UPDATE_METADATA_STATUS, 2); + bt_mesh_model_msg_init(&rsp, BT_MESH_DFU_OP_UPDATE_METADATA_STATUS); + + idx = net_buf_simple_pull_u8(buf); + status = metadata_check(srv, idx, buf, &effect); + + LOG_DBG("%u", idx); + + net_buf_simple_add_u8(&rsp, (status & BIT_MASK(3)) | (effect << 3)); + net_buf_simple_add_u8(&rsp, idx); + + if (srv->update.phase != BT_MESH_DFU_PHASE_IDLE) + { + ctx->send_ttl = srv->update.ttl; + } + + bt_mesh_model_send(mod, ctx, &rsp, NULL, NULL); + + return 0; +} + +static void update_status_rsp(struct bt_mesh_dfu_srv *srv, + struct bt_mesh_msg_ctx *ctx, + enum bt_mesh_dfu_status status, + const struct bt_mesh_send_cb *send_cb) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, BT_MESH_DFU_OP_UPDATE_STATUS, 14); + bt_mesh_model_msg_init(&buf, BT_MESH_DFU_OP_UPDATE_STATUS); + + net_buf_simple_add_u8(&buf, + ((status & BIT_MASK(3)) | (srv->update.phase << 5))); + + if (srv->update.phase != BT_MESH_DFU_PHASE_IDLE) + { + net_buf_simple_add_u8(&buf, srv->update.ttl); + net_buf_simple_add_u8(&buf, srv->update.effect); + net_buf_simple_add_le16(&buf, srv->update.timeout_base); + net_buf_simple_add_le64(&buf, srv->blob.state.xfer.id); + net_buf_simple_add_u8(&buf, srv->update.idx); + + ctx->send_ttl = srv->update.ttl; + } + + bt_mesh_model_send(srv->mod, ctx, &buf, send_cb, srv); +} + +static int handle_get(const struct bt_mesh_model *mod, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfu_srv *srv = mod->rt->user_data; + + LOG_DBG(""); + + update_status_rsp(srv, ctx, BT_MESH_DFU_SUCCESS, NULL); + + return 0; +} + +static inline bool is_active_update(struct bt_mesh_dfu_srv *srv, + uint8_t idx, + uint16_t timeout_base, + const uint64_t *blob_id, + uint8_t ttl, + uint16_t meta_checksum) +{ + return (srv->update.idx != idx || srv->blob.state.xfer.id != *blob_id || + srv->update.ttl != ttl || + srv->update.timeout_base != timeout_base || + srv->update.meta != meta_checksum); +} + +static int handle_start(const struct bt_mesh_model *mod, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfu_srv *srv = mod->rt->user_data; + const struct bt_mesh_blob_io *io; + uint16_t timeout_base; + uint16_t meta_checksum; + enum bt_mesh_dfu_status status; + uint8_t ttl; + uint8_t idx; + uint64_t blob_id; + int err; + struct net_buf_simple_state buf_state; + + ttl = net_buf_simple_pull_u8(buf); + timeout_base = net_buf_simple_pull_le16(buf); + blob_id = net_buf_simple_pull_le64(buf); + idx = net_buf_simple_pull_u8(buf); + meta_checksum = dfu_metadata_checksum(buf); + + LOG_DBG("%u ttl: %u extra time: %u", idx, ttl, timeout_base); + + if ((!buf->len || meta_checksum == srv->update.meta) && + srv->update.phase == BT_MESH_DFU_PHASE_TRANSFER_ERR && + srv->update.ttl == ttl && + srv->update.timeout_base == timeout_base && srv->update.idx == idx && + srv->blob.state.xfer.id == blob_id) + { + srv->update.phase = BT_MESH_DFU_PHASE_TRANSFER_ACTIVE; + status = BT_MESH_DFU_SUCCESS; + store_state(srv); + + /* blob srv will resume the transfer. */ + + LOG_DBG("Resuming transfer"); + goto rsp; + } + + if (bt_mesh_dfu_srv_is_busy(srv)) + { + if (is_active_update(srv, idx, timeout_base, &blob_id, ttl, + meta_checksum)) + { + status = BT_MESH_DFU_ERR_WRONG_PHASE; + } + else + { + status = BT_MESH_DFU_SUCCESS; + srv->update.ttl = ttl; + srv->blob.state.xfer.id = blob_id; + } + + LOG_WRN("Busy. Phase: %u", srv->update.phase); + goto rsp; + } + + net_buf_simple_save(buf, &buf_state); + status = metadata_check(srv, idx, buf, + (enum bt_mesh_dfu_effect *)&srv->update.effect); + net_buf_simple_restore(buf, &buf_state); + if (status != BT_MESH_DFU_SUCCESS) + { + goto rsp; + } + + srv->update.ttl = ttl; + srv->update.timeout_base = timeout_base; + srv->update.meta = meta_checksum; + srv->update.self_update = bt_mesh_has_addr(ctx->addr); + + io = NULL; + err = srv->cb->start(srv, &srv->imgs[idx], buf, &io); + if (err == -EALREADY || (!err && bt_mesh_has_addr(ctx->addr))) + { + /* This image has already been received or this is a + * self-update. Skip the transfer phase and proceed to verifying + * update. + */ + + status = BT_MESH_DFU_SUCCESS; + srv->update.idx = idx; + srv->blob.state.xfer.id = blob_id; + srv->update.phase = BT_MESH_DFU_PHASE_VERIFY; + update_status_rsp(srv, ctx, status, NULL); + verify(srv); + return 0; + } + + if (err == -ENOMEM) + { + status = BT_MESH_DFU_ERR_RESOURCES; + goto rsp; + } + + if (err == -EBUSY) + { + status = BT_MESH_DFU_ERR_TEMPORARILY_UNAVAILABLE; + goto rsp; + } + + if (err || !io || !io->wr) + { + status = BT_MESH_DFU_ERR_INTERNAL; + goto rsp; + } + + err = bt_mesh_blob_srv_recv(&srv->blob, blob_id, io, ttl, timeout_base); + if (err) + { + status = BT_MESH_DFU_ERR_BLOB_XFER_BUSY; + goto rsp; + } + + srv->update.idx = idx; + srv->update.phase = BT_MESH_DFU_PHASE_TRANSFER_ACTIVE; + status = BT_MESH_DFU_SUCCESS; + store_state(srv); + +rsp: + update_status_rsp(srv, ctx, status, NULL); + + return 0; +} + +static int handle_cancel(const struct bt_mesh_model *mod, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfu_srv *srv = mod->rt->user_data; + + if (srv->update.idx == UPDATE_IDX_NONE) + { + goto rsp; + } + + LOG_DBG(""); + + bt_mesh_blob_srv_cancel(&srv->blob); + srv->update.phase = BT_MESH_DFU_PHASE_IDLE; + erase_state(srv); + xfer_failed(srv); + +rsp: + update_status_rsp(srv, ctx, BT_MESH_DFU_SUCCESS, NULL); + + return 0; +} + +static int handle_apply(const struct bt_mesh_model *mod, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_dfu_srv *srv = mod->rt->user_data; + static const struct bt_mesh_send_cb send_cb = + { + .start = apply_rsp_sending, + .end = apply_rsp_sent, + }; + + LOG_DBG(""); + + if (srv->update.phase == BT_MESH_DFU_PHASE_APPLYING) + { + update_status_rsp(srv, ctx, BT_MESH_DFU_SUCCESS, NULL); + return 0; + } + + if (srv->update.phase != BT_MESH_DFU_PHASE_VERIFY_OK) + { + LOG_WRN("Apply: Invalid phase %u", srv->update.phase); + update_status_rsp(srv, ctx, BT_MESH_DFU_ERR_WRONG_PHASE, NULL); + return 0; + } + + /* Postponing the apply callback until the response has been sent, in + * case it triggers a reboot: + */ + + srv->update.phase = BT_MESH_DFU_PHASE_APPLYING; + update_status_rsp(srv, ctx, BT_MESH_DFU_SUCCESS, &send_cb); + + return 0; +} + +const struct bt_mesh_model_op _bt_mesh_dfu_srv_op[] = +{ + { + BT_MESH_DFU_OP_UPDATE_INFO_GET, + BT_MESH_LEN_EXACT(2), + handle_info_get + }, + + { + BT_MESH_DFU_OP_UPDATE_METADATA_CHECK, + BT_MESH_LEN_MIN(1), + handle_metadata_check + }, + + { + BT_MESH_DFU_OP_UPDATE_GET, + BT_MESH_LEN_EXACT(0), + handle_get + }, + + { + BT_MESH_DFU_OP_UPDATE_START, + BT_MESH_LEN_MIN(12), + handle_start + }, + + { + BT_MESH_DFU_OP_UPDATE_CANCEL, + BT_MESH_LEN_EXACT(0), + handle_cancel + }, + + { + BT_MESH_DFU_OP_UPDATE_APPLY, + BT_MESH_LEN_EXACT(0), + handle_apply + }, + + BT_MESH_MODEL_OP_END, +}; + +static int dfu_srv_init(const struct bt_mesh_model *mod) +{ + int err; + struct bt_mesh_dfu_srv *srv = mod->rt->user_data; + + srv->mod = mod; + srv->update.idx = UPDATE_IDX_NONE; + + if (!srv->cb || !srv->cb->start || !srv->imgs || srv->img_count == 0 || + srv->img_count == UPDATE_IDX_NONE) + { + LOG_ERR("Invalid DFU Server initialization"); + return -EINVAL; + } + + const struct bt_mesh_model *blob_srv = + bt_mesh_model_find(bt_mesh_model_elem(mod), + BT_MESH_MODEL_ID_BLOB_SRV); + + if (blob_srv == NULL) + { + LOG_ERR("Missing BLOB Srv."); + return -EINVAL; + } + + err = bt_mesh_model_extend(mod, srv->blob.mod); + + if (err) + { + return err; + } + + return 0; +} + +static int dfu_srv_settings_set(const struct bt_mesh_model *mod, + const char *name, + size_t len_rd, + settings_read_cb read_cb, + void *cb_arg) +{ + struct bt_mesh_dfu_srv *srv = mod->rt->user_data; + struct dfu_srv_persistent_state state; + ssize_t len; + + if (len_rd < sizeof(state)) + { + return -EINVAL; + } + + len = read_cb(cb_arg, &state, sizeof(state)); + if (len < 0) + { + return len; + } + + srv->update.effect = state.effect; + srv->update.phase = state.phase; + srv->update.ttl = state.ttl; + srv->update.idx = state.idx; + srv->update.timeout_base = state.timeout_base; + srv->update.meta = state.meta; + + LOG_DBG("Recovered transfer (phase: %u, idx: %u)", srv->update.phase, + srv->update.idx); + if (srv->update.phase == BT_MESH_DFU_PHASE_TRANSFER_ACTIVE) + { + LOG_DBG("Settings recovered mid-transfer, setting transfer error"); + srv->update.phase = BT_MESH_DFU_PHASE_TRANSFER_ERR; + } + else if (srv->update.phase == BT_MESH_DFU_PHASE_VERIFY_OK) + { + LOG_DBG("Settings recovered before application, setting verification " + "fail"); + srv->update.phase = BT_MESH_DFU_PHASE_VERIFY_FAIL; + } + + return 0; +} + +static void dfu_srv_reset(const struct bt_mesh_model *mod) +{ + struct bt_mesh_dfu_srv *srv = mod->rt->user_data; + + srv->update.phase = BT_MESH_DFU_PHASE_IDLE; + erase_state(srv); +} + +const struct bt_mesh_model_cb _bt_mesh_dfu_srv_cb = +{ + .init = dfu_srv_init, + .settings_set = dfu_srv_settings_set, + .reset = dfu_srv_reset, +}; + +static void blob_suspended(struct bt_mesh_blob_srv *b) +{ + struct bt_mesh_dfu_srv + *srv = CONTAINER_OF(b, struct bt_mesh_dfu_srv, blob); + + srv->update.phase = BT_MESH_DFU_PHASE_TRANSFER_ERR; + store_state(srv); +} + +static void blob_end(struct bt_mesh_blob_srv *b, uint64_t id, bool success) +{ + struct bt_mesh_dfu_srv + *srv = CONTAINER_OF(b, struct bt_mesh_dfu_srv, blob); + + LOG_DBG("success: %u", success); + + if (!success) + { + srv->update.phase = BT_MESH_DFU_PHASE_TRANSFER_ERR; + xfer_failed(srv); + return; + } + + verify(srv); +} + +static int blob_recover(struct bt_mesh_blob_srv *b, + struct bt_mesh_blob_xfer *xfer, + const struct bt_mesh_blob_io **io) +{ + struct bt_mesh_dfu_srv *srv = CONTAINER_OF(b, + struct bt_mesh_dfu_srv, + blob); + + if (!srv->cb->recover + || srv->update.phase != BT_MESH_DFU_PHASE_TRANSFER_ERR || + srv->update.idx >= srv->img_count) + { + return -ENOTSUP; + } + + return srv->cb->recover(srv, &srv->imgs[srv->update.idx], io); +} + +const struct bt_mesh_blob_srv_cb _bt_mesh_dfu_srv_blob_cb = +{ + .suspended = blob_suspended, + .end = blob_end, + .recover = blob_recover, +}; + +void bt_mesh_dfu_srv_verified(struct bt_mesh_dfu_srv *srv) +{ + if (srv->update.phase != BT_MESH_DFU_PHASE_VERIFY) + { + LOG_DBG("Wrong state"); + return; + } + + LOG_DBG(""); + + srv->update.phase = BT_MESH_DFU_PHASE_VERIFY_OK; + store_state(srv); +} + +void bt_mesh_dfu_srv_rejected(struct bt_mesh_dfu_srv *srv) +{ + if (srv->update.phase != BT_MESH_DFU_PHASE_VERIFY) + { + LOG_DBG("Wrong state"); + return; + } + + LOG_DBG(""); + + srv->update.phase = BT_MESH_DFU_PHASE_VERIFY_FAIL; + store_state(srv); +} + +void bt_mesh_dfu_srv_cancel(struct bt_mesh_dfu_srv *srv) +{ + if (srv->update.phase == BT_MESH_DFU_PHASE_IDLE) + { + LOG_DBG("Wrong state"); + return; + } + + (void)bt_mesh_blob_srv_cancel(&srv->blob); +} + +void bt_mesh_dfu_srv_applied(struct bt_mesh_dfu_srv *srv) +{ + if (srv->update.phase != BT_MESH_DFU_PHASE_APPLYING) + { + LOG_DBG("Wrong state"); + return; + } + + LOG_DBG(""); + + srv->update.phase = BT_MESH_DFU_PHASE_IDLE; + store_state(srv); + + /* Not set after a reboot, so the Distribution Server's own resume path + * does not complete the distribution before the Confirm step re-runs. + */ + + if (IS_ENABLED(CONFIG_BT_MESH_DFD_SRV) && srv->update.self_update) + { + bt_mesh_dfd_srv_self_applied(); + } +} + +bool bt_mesh_dfu_srv_is_busy(const struct bt_mesh_dfu_srv *srv) +{ + return srv->update.phase != BT_MESH_DFU_PHASE_IDLE && + srv->update.phase != BT_MESH_DFU_PHASE_TRANSFER_ERR && + srv->update.phase != BT_MESH_DFU_PHASE_VERIFY_FAIL; +} + +int bt_mesh_dfu_srv_apply_deferred(struct bt_mesh_dfu_srv *srv) +{ + int err; + + if (srv->update.phase != BT_MESH_DFU_PHASE_APPLYING) + { + return 0; + } + + if (!srv->cb->apply || srv->update.idx == UPDATE_IDX_NONE) + { + srv->update.phase = BT_MESH_DFU_PHASE_IDLE; + erase_state(srv); + return -EINVAL; + } + + err = srv->cb->apply(srv, &srv->imgs[srv->update.idx]); + if (err) + { + srv->update.phase = BT_MESH_DFU_PHASE_IDLE; + erase_state(srv); + } + + return err; +} + +void bt_mesh_dfu_srv_apply_cancel(struct bt_mesh_dfu_srv *srv) +{ + if (srv->update.phase != BT_MESH_DFU_PHASE_APPLYING) + { + return; + } + + LOG_DBG(""); + + /* Equivalent of receiving Firmware Update Cancel, which a self-target + * cannot receive over the mesh from its own Distributor. + */ + + srv->update.phase = BT_MESH_DFU_PHASE_IDLE; + erase_state(srv); +} + +uint8_t bt_mesh_dfu_srv_progress(const struct bt_mesh_dfu_srv *srv) +{ + if (!bt_mesh_dfu_srv_is_busy(srv)) + { + return 0U; + } + + if (srv->update.phase == BT_MESH_DFU_PHASE_TRANSFER_ACTIVE) + { + return bt_mesh_blob_srv_progress(&srv->blob); + } + + return 100U; +} diff --git a/libs/zblue/mesh/foundation.h b/libs/zblue/mesh/foundation.h new file mode 100644 index 0000000000000..65459103d2edd --- /dev/null +++ b/libs/zblue/mesh/foundation.h @@ -0,0 +1,193 @@ +/**************************************************************************** + * libs/zblue/mesh/foundation.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define OP_APP_KEY_ADD BT_MESH_MODEL_OP_1(0x00) +#define OP_APP_KEY_UPDATE BT_MESH_MODEL_OP_1(0x01) +#define OP_DEV_COMP_DATA_STATUS BT_MESH_MODEL_OP_1(0x02) +#define OP_MOD_PUB_SET BT_MESH_MODEL_OP_1(0x03) +#define OP_HEALTH_CURRENT_STATUS BT_MESH_MODEL_OP_1(0x04) +#define OP_HEALTH_FAULT_STATUS BT_MESH_MODEL_OP_1(0x05) +#define OP_HEARTBEAT_PUB_STATUS BT_MESH_MODEL_OP_1(0x06) +#define OP_APP_KEY_DEL BT_MESH_MODEL_OP_2(0x80, 0x00) +#define OP_APP_KEY_GET BT_MESH_MODEL_OP_2(0x80, 0x01) +#define OP_APP_KEY_LIST BT_MESH_MODEL_OP_2(0x80, 0x02) +#define OP_APP_KEY_STATUS BT_MESH_MODEL_OP_2(0x80, 0x03) +#define OP_ATTENTION_GET BT_MESH_MODEL_OP_2(0x80, 0x04) +#define OP_ATTENTION_SET BT_MESH_MODEL_OP_2(0x80, 0x05) +#define OP_ATTENTION_SET_UNREL BT_MESH_MODEL_OP_2(0x80, 0x06) +#define OP_ATTENTION_STATUS BT_MESH_MODEL_OP_2(0x80, 0x07) +#define OP_DEV_COMP_DATA_GET BT_MESH_MODEL_OP_2(0x80, 0x08) +#define OP_BEACON_GET BT_MESH_MODEL_OP_2(0x80, 0x09) +#define OP_BEACON_SET BT_MESH_MODEL_OP_2(0x80, 0x0a) +#define OP_BEACON_STATUS BT_MESH_MODEL_OP_2(0x80, 0x0b) +#define OP_DEFAULT_TTL_GET BT_MESH_MODEL_OP_2(0x80, 0x0c) +#define OP_DEFAULT_TTL_SET BT_MESH_MODEL_OP_2(0x80, 0x0d) +#define OP_DEFAULT_TTL_STATUS BT_MESH_MODEL_OP_2(0x80, 0x0e) +#define OP_FRIEND_GET BT_MESH_MODEL_OP_2(0x80, 0x0f) +#define OP_FRIEND_SET BT_MESH_MODEL_OP_2(0x80, 0x10) +#define OP_FRIEND_STATUS BT_MESH_MODEL_OP_2(0x80, 0x11) +#define OP_GATT_PROXY_GET BT_MESH_MODEL_OP_2(0x80, 0x12) +#define OP_GATT_PROXY_SET BT_MESH_MODEL_OP_2(0x80, 0x13) +#define OP_GATT_PROXY_STATUS BT_MESH_MODEL_OP_2(0x80, 0x14) +#define OP_KRP_GET BT_MESH_MODEL_OP_2(0x80, 0x15) +#define OP_KRP_SET BT_MESH_MODEL_OP_2(0x80, 0x16) +#define OP_KRP_STATUS BT_MESH_MODEL_OP_2(0x80, 0x17) +#define OP_MOD_PUB_GET BT_MESH_MODEL_OP_2(0x80, 0x18) +#define OP_MOD_PUB_STATUS BT_MESH_MODEL_OP_2(0x80, 0x19) +#define OP_MOD_PUB_VA_SET BT_MESH_MODEL_OP_2(0x80, 0x1a) +#define OP_MOD_SUB_ADD BT_MESH_MODEL_OP_2(0x80, 0x1b) +#define OP_MOD_SUB_DEL BT_MESH_MODEL_OP_2(0x80, 0x1c) +#define OP_MOD_SUB_DEL_ALL BT_MESH_MODEL_OP_2(0x80, 0x1d) +#define OP_MOD_SUB_OVERWRITE BT_MESH_MODEL_OP_2(0x80, 0x1e) +#define OP_MOD_SUB_STATUS BT_MESH_MODEL_OP_2(0x80, 0x1f) +#define OP_MOD_SUB_VA_ADD BT_MESH_MODEL_OP_2(0x80, 0x20) +#define OP_MOD_SUB_VA_DEL BT_MESH_MODEL_OP_2(0x80, 0x21) +#define OP_MOD_SUB_VA_OVERWRITE BT_MESH_MODEL_OP_2(0x80, 0x22) +#define OP_NET_TRANSMIT_GET BT_MESH_MODEL_OP_2(0x80, 0x23) +#define OP_NET_TRANSMIT_SET BT_MESH_MODEL_OP_2(0x80, 0x24) +#define OP_NET_TRANSMIT_STATUS BT_MESH_MODEL_OP_2(0x80, 0x25) +#define OP_RELAY_GET BT_MESH_MODEL_OP_2(0x80, 0x26) +#define OP_RELAY_SET BT_MESH_MODEL_OP_2(0x80, 0x27) +#define OP_RELAY_STATUS BT_MESH_MODEL_OP_2(0x80, 0x28) +#define OP_MOD_SUB_GET BT_MESH_MODEL_OP_2(0x80, 0x29) +#define OP_MOD_SUB_LIST BT_MESH_MODEL_OP_2(0x80, 0x2a) +#define OP_MOD_SUB_GET_VND BT_MESH_MODEL_OP_2(0x80, 0x2b) +#define OP_MOD_SUB_LIST_VND BT_MESH_MODEL_OP_2(0x80, 0x2c) +#define OP_LPN_TIMEOUT_GET BT_MESH_MODEL_OP_2(0x80, 0x2d) +#define OP_LPN_TIMEOUT_STATUS BT_MESH_MODEL_OP_2(0x80, 0x2e) +#define OP_HEALTH_FAULT_CLEAR BT_MESH_MODEL_OP_2(0x80, 0x2f) +#define OP_HEALTH_FAULT_CLEAR_UNREL BT_MESH_MODEL_OP_2(0x80, 0x30) +#define OP_HEALTH_FAULT_GET BT_MESH_MODEL_OP_2(0x80, 0x31) +#define OP_HEALTH_FAULT_TEST BT_MESH_MODEL_OP_2(0x80, 0x32) +#define OP_HEALTH_FAULT_TEST_UNREL BT_MESH_MODEL_OP_2(0x80, 0x33) +#define OP_HEALTH_PERIOD_GET BT_MESH_MODEL_OP_2(0x80, 0x34) +#define OP_HEALTH_PERIOD_SET BT_MESH_MODEL_OP_2(0x80, 0x35) +#define OP_HEALTH_PERIOD_SET_UNREL BT_MESH_MODEL_OP_2(0x80, 0x36) +#define OP_HEALTH_PERIOD_STATUS BT_MESH_MODEL_OP_2(0x80, 0x37) +#define OP_HEARTBEAT_PUB_GET BT_MESH_MODEL_OP_2(0x80, 0x38) +#define OP_HEARTBEAT_PUB_SET BT_MESH_MODEL_OP_2(0x80, 0x39) +#define OP_HEARTBEAT_SUB_GET BT_MESH_MODEL_OP_2(0x80, 0x3a) +#define OP_HEARTBEAT_SUB_SET BT_MESH_MODEL_OP_2(0x80, 0x3b) +#define OP_HEARTBEAT_SUB_STATUS BT_MESH_MODEL_OP_2(0x80, 0x3c) +#define OP_MOD_APP_BIND BT_MESH_MODEL_OP_2(0x80, 0x3d) +#define OP_MOD_APP_STATUS BT_MESH_MODEL_OP_2(0x80, 0x3e) +#define OP_MOD_APP_UNBIND BT_MESH_MODEL_OP_2(0x80, 0x3f) +#define OP_NET_KEY_ADD BT_MESH_MODEL_OP_2(0x80, 0x40) +#define OP_NET_KEY_DEL BT_MESH_MODEL_OP_2(0x80, 0x41) +#define OP_NET_KEY_GET BT_MESH_MODEL_OP_2(0x80, 0x42) +#define OP_NET_KEY_LIST BT_MESH_MODEL_OP_2(0x80, 0x43) +#define OP_NET_KEY_STATUS BT_MESH_MODEL_OP_2(0x80, 0x44) +#define OP_NET_KEY_UPDATE BT_MESH_MODEL_OP_2(0x80, 0x45) +#define OP_NODE_IDENTITY_GET BT_MESH_MODEL_OP_2(0x80, 0x46) +#define OP_NODE_IDENTITY_SET BT_MESH_MODEL_OP_2(0x80, 0x47) +#define OP_NODE_IDENTITY_STATUS BT_MESH_MODEL_OP_2(0x80, 0x48) +#define OP_NODE_RESET BT_MESH_MODEL_OP_2(0x80, 0x49) +#define OP_NODE_RESET_STATUS BT_MESH_MODEL_OP_2(0x80, 0x4a) +#define OP_SIG_MOD_APP_GET BT_MESH_MODEL_OP_2(0x80, 0x4b) +#define OP_SIG_MOD_APP_LIST BT_MESH_MODEL_OP_2(0x80, 0x4c) +#define OP_VND_MOD_APP_GET BT_MESH_MODEL_OP_2(0x80, 0x4d) +#define OP_VND_MOD_APP_LIST BT_MESH_MODEL_OP_2(0x80, 0x4e) + +#define OP_SAR_CFG_TX_GET BT_MESH_MODEL_OP_2(0x80, 0x6c) +#define OP_SAR_CFG_TX_SET BT_MESH_MODEL_OP_2(0x80, 0x6d) +#define OP_SAR_CFG_TX_STATUS BT_MESH_MODEL_OP_2(0x80, 0x6e) +#define OP_SAR_CFG_RX_GET BT_MESH_MODEL_OP_2(0x80, 0x6f) +#define OP_SAR_CFG_RX_SET BT_MESH_MODEL_OP_2(0x80, 0x70) +#define OP_SAR_CFG_RX_STATUS BT_MESH_MODEL_OP_2(0x80, 0x71) +#define OP_OPCODES_AGGREGATOR_SEQUENCE BT_MESH_MODEL_OP_2(0x80, 0x72) +#define OP_OPCODES_AGGREGATOR_STATUS BT_MESH_MODEL_OP_2(0x80, 0x73) +#define OP_LARGE_COMP_DATA_GET BT_MESH_MODEL_OP_2(0x80, 0x74) +#define OP_LARGE_COMP_DATA_STATUS BT_MESH_MODEL_OP_2(0x80, 0x75) +#define OP_MODELS_METADATA_GET BT_MESH_MODEL_OP_2(0x80, 0x76) +#define OP_MODELS_METADATA_STATUS BT_MESH_MODEL_OP_2(0x80, 0x77) + +#define OP_PRIV_BEACON_GET BT_MESH_MODEL_OP_2(0x80, 0x60) +#define OP_PRIV_BEACON_SET BT_MESH_MODEL_OP_2(0x80, 0x61) +#define OP_PRIV_BEACON_STATUS BT_MESH_MODEL_OP_2(0x80, 0x62) +#define OP_PRIV_GATT_PROXY_GET BT_MESH_MODEL_OP_2(0x80, 0x63) +#define OP_PRIV_GATT_PROXY_SET BT_MESH_MODEL_OP_2(0x80, 0x64) +#define OP_PRIV_GATT_PROXY_STATUS BT_MESH_MODEL_OP_2(0x80, 0x65) +#define OP_PRIV_NODE_ID_GET BT_MESH_MODEL_OP_2(0x80, 0x66) +#define OP_PRIV_NODE_ID_SET BT_MESH_MODEL_OP_2(0x80, 0x67) +#define OP_PRIV_NODE_ID_STATUS BT_MESH_MODEL_OP_2(0x80, 0x68) + +#define OP_OD_PRIV_PROXY_GET BT_MESH_MODEL_OP_2(0x80, 0x69) +#define OP_OD_PRIV_PROXY_SET BT_MESH_MODEL_OP_2(0x80, 0x6a) +#define OP_OD_PRIV_PROXY_STATUS BT_MESH_MODEL_OP_2(0x80, 0x6b) + +#define OP_SOL_PDU_RPL_ITEM_CLEAR BT_MESH_MODEL_OP_2(0x80, 0x78) +#define OP_SOL_PDU_RPL_ITEM_CLEAR_UNACKED BT_MESH_MODEL_OP_2(0x80, 0x79) +#define OP_SOL_PDU_RPL_ITEM_STATUS BT_MESH_MODEL_OP_2(0x80, 0x7a) + +#define OP_SUBNET_BRIDGE_GET BT_MESH_MODEL_OP_2(0x80, 0xb1) +#define OP_SUBNET_BRIDGE_SET BT_MESH_MODEL_OP_2(0x80, 0xb2) +#define OP_SUBNET_BRIDGE_STATUS BT_MESH_MODEL_OP_2(0x80, 0xb3) +#define OP_BRIDGING_TABLE_ADD BT_MESH_MODEL_OP_2(0x80, 0xb4) +#define OP_BRIDGING_TABLE_REMOVE BT_MESH_MODEL_OP_2(0x80, 0xb5) +#define OP_BRIDGING_TABLE_STATUS BT_MESH_MODEL_OP_2(0x80, 0xb6) +#define OP_BRIDGED_SUBNETS_GET BT_MESH_MODEL_OP_2(0x80, 0xb7) +#define OP_BRIDGED_SUBNETS_LIST BT_MESH_MODEL_OP_2(0x80, 0xb8) +#define OP_BRIDGING_TABLE_GET BT_MESH_MODEL_OP_2(0x80, 0xb9) +#define OP_BRIDGING_TABLE_LIST BT_MESH_MODEL_OP_2(0x80, 0xba) +#define OP_BRIDGING_TABLE_SIZE_GET BT_MESH_MODEL_OP_2(0x80, 0xbb) +#define OP_BRIDGING_TABLE_SIZE_STATUS BT_MESH_MODEL_OP_2(0x80, 0xbc) + +#define STATUS_SUCCESS 0x00 +#define STATUS_INVALID_ADDRESS 0x01 +#define STATUS_INVALID_MODEL 0x02 +#define STATUS_INVALID_APPKEY 0x03 +#define STATUS_INVALID_NETKEY 0x04 +#define STATUS_INSUFF_RESOURCES 0x05 +#define STATUS_IDX_ALREADY_STORED 0x06 +#define STATUS_NVAL_PUB_PARAM 0x07 +#define STATUS_NOT_SUB_MOD 0x08 +#define STATUS_STORAGE_FAIL 0x09 +#define STATUS_FEAT_NOT_SUPP 0x0a +#define STATUS_CANNOT_UPDATE 0x0b +#define STATUS_CANNOT_REMOVE 0x0c +#define STATUS_CANNOT_BIND 0x0d +#define STATUS_TEMP_STATE_CHG_FAIL 0x0e +#define STATUS_CANNOT_SET 0x0f +#define STATUS_UNSPECIFIED 0x10 +#define STATUS_INVALID_BINDING 0x11 + +#define ACCESS_STATUS_SUCCESS 0x00 +#define ACCESS_STATUS_INVALID_ADDRESS 0x01 +#define ACCESS_STATUS_WRONG_KEY 0x02 +#define ACCESS_STATUS_WRONG_OPCODE 0x03 +#define ACCESS_STATUS_MESSAGE_NOT_UNDERSTOOD 0x04 +#define ACCESS_STATUS_RESPONSE_OVERFLOW 0x05 + +void bt_mesh_model_reset(void); + +void bt_mesh_attention(const struct bt_mesh_model *model, uint8_t time); + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +static inline void key_idx_pack_pair(struct net_buf_simple *buf, + uint16_t idx1, uint16_t idx2) +{ + net_buf_simple_add_le16(buf, idx1 | ((idx2 & 0x00f) << 12)); + net_buf_simple_add_u8(buf, idx2 >> 4); +} + +static inline void key_idx_unpack_pair(struct net_buf_simple *buf, + uint16_t *idx1, uint16_t *idx2) +{ + *idx1 = sys_get_le16(&buf->data[0]) & 0xfff; + *idx2 = sys_get_le16(&buf->data[1]) >> 4; + net_buf_simple_pull(buf, 3); +} diff --git a/libs/zblue/mesh/friend.c b/libs/zblue/mesh/friend.c new file mode 100644 index 0000000000000..ad74609c8927a --- /dev/null +++ b/libs/zblue/mesh/friend.c @@ -0,0 +1,2139 @@ +/**************************************************************************** + * libs/zblue/mesh/friend.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include "crypto.h" +#include "mesh.h" +#include "net.h" +#include "app_keys.h" +#include "transport.h" +#include "access.h" +#include "foundation.h" +#include "friend.h" +#include "va.h" + +#define LOG_LEVEL CONFIG_BT_MESH_FRIEND_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_friend); + +/* Log modes other than the deferred may cause unintended delays during + * processing of log messages. This in turns will affect scheduling of the + * receive delay and receive window. + */ +#if !defined(CONFIG_TEST) && !defined(CONFIG_ARCH_POSIX) && defined(CONFIG_LOG) && \ + !defined(CONFIG_LOG_MODE_DEFERRED) && (LOG_LEVEL >= LOG_LEVEL_INF) +#warning Frienship feature may work unstable when non-deferred log mode is selected. Use the \ + CONFIG_LOG_MODE_DEFERRED Kconfig option when Friend feature is enabled. +#endif + +/* We reserve one extra buffer for each friendship, since we need to be able + * to resend the last sent PDU, which sits separately outside of the queue. + */ +#define FRIEND_BUF_COUNT ((CONFIG_BT_MESH_FRIEND_QUEUE_SIZE + 1) * CONFIG_BT_MESH_FRIEND_LPN_COUNT) + +/* PDUs from Friend to the LPN should only be transmitted once with the + * smallest possible interval (20ms). + */ +#define FRIEND_XMIT BT_MESH_TRANSMIT(0, 20) + +struct friend_pdu_info +{ + uint16_t src; + uint16_t dst; + + uint8_t seq[3]; + + uint8_t ttl : 7; + uint8_t ctl : 1; + + uint32_t iv_index; +}; + +BUILD_ASSERT(CONFIG_BT_MESH_LABEL_COUNT <= 0xfffu, + "Friend doesn't support more than 4096 labels."); + +struct friend_adv +{ + uint16_t app_idx; + struct + { + /* CONFIG_BT_MESH_LABEL_COUNT max value is 4096. */ + + uint16_t uuidx : 15; + uint16_t seg : 1; + }; +}; + +NET_BUF_POOL_FIXED_DEFINE(friend_buf_pool, + FRIEND_BUF_COUNT, + BT_MESH_ADV_DATA_SIZE, + + sizeof(struct friend_adv), NULL); + +static struct friend_adv adv_pool[FRIEND_BUF_COUNT]; + +#define FRIEND_ADV(buf) (*(struct friend_adv **)net_buf_user_data(buf)) + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static struct friend_adv *adv_alloc(int id) +{ + adv_pool[id].app_idx = BT_MESH_KEY_UNUSED; + adv_pool[id].seg = false; + return &adv_pool[id]; +} + +static bool friend_is_allocated(const struct bt_mesh_friend *frnd) +{ + return frnd->subnet != NULL; +} + +static bool is_lpn_unicast(struct bt_mesh_friend *frnd, uint16_t addr) +{ + if (frnd->lpn == BT_MESH_ADDR_UNASSIGNED) + { + return false; + } + + return (addr >= frnd->lpn && addr < (frnd->lpn + frnd->num_elem)); +} + +struct bt_mesh_friend *bt_mesh_friend_find( + uint16_t net_idx, uint16_t lpn_addr, bool valid, bool established) +{ + int i; + + LOG_DBG("net_idx 0x%04x lpn_addr 0x%04x", net_idx, lpn_addr); + + for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) + { + struct bt_mesh_friend *frnd = &bt_mesh.frnd[i]; + + if (valid && !friend_is_allocated(frnd)) + { + continue; + } + + if (established && !frnd->established) + { + continue; + } + + if (net_idx != BT_MESH_KEY_ANY + && (!frnd->subnet || frnd->subnet->net_idx != net_idx)) + { + continue; + } + + if (is_lpn_unicast(frnd, lpn_addr)) + { + return frnd; + } + } + + return NULL; +} + +static int friend_cred_create(struct bt_mesh_friend *frnd, uint8_t idx) +{ + return bt_mesh_friend_cred_create(&frnd->cred[idx], frnd->lpn, + bt_mesh_primary_addr(), + frnd->lpn_counter, frnd->counter, + &frnd->subnet->keys[idx].net); +} + +static void purge_buffers(struct list_node *list) +{ + struct net_buf *buf; + + while ((buf = (void *)net_buf_slist_get(list))) + { + net_buf_unref(buf); + } +} + +/* Intentionally start a little bit late into the ReceiveWindow when + * it's large enough. This may improve reliability with some platforms, like + * the PTS, where the receiver might not have sufficiently compensated for + * internal latencies required to start scanning. + */ + +static int32_t recv_delay(struct bt_mesh_friend *frnd) +{ +#if CONFIG_BT_MESH_FRIEND_RECV_WIN > 50 + return (int32_t)frnd->recv_delay + (CONFIG_BT_MESH_FRIEND_RECV_WIN / 5); +#else + return frnd->recv_delay; +#endif +} + +static void friend_clear(struct bt_mesh_friend *frnd) +{ + int i; + + LOG_DBG("LPN 0x%04x", frnd->lpn); + + /* If cancelling the timer fails, we'll exit early in the work handler. */ + + (void)k_work_cancel_delayable(&frnd->timer); + + for (i = 0; i < ARRAY_SIZE(frnd->cred); i++) + { + if (frnd->subnet->keys[i].valid) + { + bt_mesh_friend_cred_destroy(&frnd->cred[i]); + } + } + + memset(frnd->cred, 0, sizeof(frnd->cred)); + + net_buf_drop(&frnd->last); + + purge_buffers(&frnd->queue); + + for (i = 0; i < ARRAY_SIZE(frnd->seg); i++) + { + struct bt_mesh_friend_seg *seg = &frnd->seg[i]; + + purge_buffers(&seg->queue); + seg->seg_count = 0U; + } + + STRUCT_SECTION_FOREACH(bt_mesh_friend_cb, cb) + { + if (frnd->established && cb->terminated) + { + cb->terminated(frnd->subnet->net_idx, frnd->lpn); + } + } + + frnd->counter++; + frnd->subnet = NULL; + frnd->established = 0U; + frnd->pending_buf = 0U; + frnd->fsn = 0U; + frnd->queue_size = 0U; + frnd->pending_req = 0U; + (void)memset(frnd->sub_list, 0, sizeof(frnd->sub_list)); +} + +void bt_mesh_friends_clear(void) +{ + int i; + + LOG_DBG(""); + + for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) + { + struct bt_mesh_friend *frnd = &bt_mesh.frnd[i]; + + if (!friend_is_allocated(frnd)) + { + continue; + } + + friend_clear(frnd); + } +} + +static void enqueue_update(struct bt_mesh_friend *frnd, uint8_t md); + +void bt_mesh_friend_sec_update(uint16_t net_idx) +{ + int i; + + LOG_DBG("net_idx 0x%04x", net_idx); + + for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) + { + struct bt_mesh_friend *frnd = &bt_mesh.frnd[i]; + + if (!friend_is_allocated(frnd)) + { + continue; + } + + if (net_idx == BT_MESH_KEY_ANY || frnd->subnet->net_idx == net_idx) + { + enqueue_update(frnd, 0x00); + } + } +} + +int bt_mesh_friend_clear(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + struct bt_mesh_ctl_friend_clear *msg = (void *)buf->data; + struct bt_mesh_friend *frnd; + uint16_t lpn_addr; + uint16_t lpn_counter; + uint8_t xmit = bt_mesh_net_transmit_get(); + int32_t delay; + + if (buf->len < sizeof(*msg)) + { + LOG_WRN("Too short Friend Clear"); + return -EBADMSG; + } + + lpn_addr = sys_be16_to_cpu(msg->lpn_addr); + lpn_counter = sys_be16_to_cpu(msg->lpn_counter); + + LOG_DBG("LPN addr 0x%04x counter 0x%04x", lpn_addr, lpn_counter); + + frnd = bt_mesh_friend_find(rx->sub->net_idx, lpn_addr, false, false); + if (!frnd) + { + LOG_WRN("No matching LPN addr 0x%04x", lpn_addr); + return 0; + } + + /* A Friend Clear message is considered valid if the result of the + * subtraction of the value of the LPNCounter field of the Friend Request + * message (the one that initiated the friendship) from the value of the + * LPNCounter field of the Friend Clear message, modulo 65536, is in the + * range 0 to 255 inclusive. + */ + + if (lpn_counter - frnd->lpn_counter > 255) + { + LOG_WRN("LPN Counter out of range (old %u new %u)", frnd->lpn_counter, + lpn_counter); + return 0; + } + + /* Store context for the delayed Confirm before clearing the slot. */ + + frnd->clear.cfm.net_idx = rx->sub->net_idx; + frnd->clear.cfm.dst = rx->ctx.addr; + frnd->clear.cfm.lpn_addr = msg->lpn_addr; + frnd->clear.cfm.lpn_counter = msg->lpn_counter; + frnd->clear.cfm.ttl = rx->ctx.recv_ttl == 0 ? 0 : BT_MESH_TTL_MAX; + + friend_clear(frnd); + + /* Estimate how long the LPN advertises before it starts scanning. + * The Friend does not know the LPN's actual Network Transmit state, so we + * use our own as a best-effort approximation (nodes in the same network + * typically share the same transmit configuration). + * + * The LPN retransmits its Friend Clear as (COUNT + 1) advertising events. + * Between each pair of events the Mesh spec allows a random delay of 0-10 + * ms on top of the configured interval, so the worst-case gap is (interval + * + 10) ms. The total advertising duration is therefore at most (COUNT + + * 1) * (interval + 10) ms. + * + * Example with COUNT=2, interval=20 ms (3 events total): + * + * t=0 ms event 1 t<=30 ms event 2 t<=60 ms event 3 (last) t~=70 ms LPN + * finishes, starts scanning t=delay Timer fires, Friend sends Confirm + * + * The formula intentionally overestimates to provide margin for scheduling + * jitter and scanner startup time. + */ + + delay = + (BT_MESH_TRANSMIT_COUNT(xmit) + 1) * (BT_MESH_TRANSMIT_INT(xmit) + + 10); + + frnd->pending_cfm = 1U; + k_work_reschedule(&frnd->clear.timer, MSEC2TICK(delay)); + + return 0; +} + +static void friend_sub_add(struct bt_mesh_friend *frnd, uint16_t addr) +{ + int empty_idx = INT_MAX; + + for (int i = 0; i < ARRAY_SIZE(frnd->sub_list); i++) + { + if (frnd->sub_list[i] == addr) + { + return; + } + + if (frnd->sub_list[i] == BT_MESH_ADDR_UNASSIGNED) + { + empty_idx = i; + } + } + + if (empty_idx != INT_MAX) + { + frnd->sub_list[empty_idx] = addr; + LOG_DBG("%04x added %04x to subscription list", frnd->lpn, addr); + } + else + { + LOG_WRN("No space in friend subscription list"); + } +} + +static void friend_sub_rem(struct bt_mesh_friend *frnd, uint16_t addr) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(frnd->sub_list); i++) + { + if (frnd->sub_list[i] == addr) + { + LOG_DBG("%04x removed %04x from subscription list", frnd->lpn, + addr); + frnd->sub_list[i] = BT_MESH_ADDR_UNASSIGNED; + return; + } + } +} + +static struct net_buf *create_friend_pdu( + struct bt_mesh_friend *frnd, struct friend_pdu_info *info, + struct net_buf_simple *sdu) +{ + struct net_buf *buf; + + buf = net_buf_alloc(&friend_buf_pool, 0); + if (!buf) + { + return NULL; + } + + FRIEND_ADV(buf) = adv_alloc(net_buf_id(buf)); + + net_buf_add_u8(buf, (info->iv_index & 1) << 7); /* Will be reset in encryption */ + + if (info->ctl) + { + net_buf_add_u8(buf, info->ttl | 0x80); + } + else + { + net_buf_add_u8(buf, info->ttl); + } + + net_buf_add_mem(buf, info->seq, sizeof(info->seq)); + + net_buf_add_be16(buf, info->src); + net_buf_add_be16(buf, info->dst); + + net_buf_add_mem(buf, sdu->data, sdu->len); + + return buf; +} + +struct unseg_app_sdu_meta +{ + struct bt_mesh_app_crypto_ctx crypto; + const struct bt_mesh_key *key; + struct bt_mesh_subnet *subnet; + uint8_t aid; +}; + +static int unseg_app_sdu_unpack( + struct bt_mesh_friend *frnd, struct net_buf *buf, + struct unseg_app_sdu_meta *meta) +{ + uint16_t app_idx = FRIEND_ADV(buf)->app_idx; + uint16_t uuidx = FRIEND_ADV(buf)->uuidx; + struct bt_mesh_net_rx net = + { + .ctx = + { + .app_idx = app_idx, + .net_idx = frnd->subnet->net_idx, + }, + }; + + int err; + + meta->subnet = frnd->subnet; + bt_mesh_net_header_parse(&buf->b, &net); + err = bt_mesh_keys_resolve(&net.ctx, &net.sub, &meta->key, &meta->aid); + if (err) + { + return err; + } + + meta->crypto.src = net.ctx.addr; + meta->crypto.dst = net.ctx.recv_dst; + meta->crypto.iv_index = BT_MESH_NET_IVI_TX; + meta->crypto.dev_key = BT_MESH_IS_DEV_KEY(app_idx); + meta->crypto.seq_num = net.seq; + meta->crypto.aszmic = 0; + + if (BT_MESH_ADDR_IS_VIRTUAL(meta->crypto.dst)) + { + meta->crypto.ad = bt_mesh_va_get_uuid_by_idx(uuidx); + } + else + { + meta->crypto.ad = NULL; + } + + return 0; +} + +static int unseg_app_sdu_decrypt( + struct bt_mesh_friend *frnd, struct net_buf *buf, + const struct unseg_app_sdu_meta *meta) +{ + struct net_buf_simple in; + struct net_buf_simple out; + + /* Direct the input buffer at the Upper Transport Access PDU, accounting + * for the network header and the 1 byte lower transport header + */ + + net_buf_simple_clone(&buf->b, &in); + net_buf_simple_pull(&in, BT_MESH_NET_HDR_LEN); + net_buf_simple_pull(&in, 1); + in.len -= BT_MESH_MIC_SHORT; + + net_buf_simple_clone(&in, &out); + out.len = 0; /* length will be set by decrypt */ + + /* Decrypt in place, as we only need to test one key: */ + + return bt_mesh_app_decrypt(meta->key, &meta->crypto, &in, &out); +} + +static int unseg_app_sdu_encrypt( + struct bt_mesh_friend *frnd, struct net_buf *buf, + const struct unseg_app_sdu_meta *meta) +{ + struct net_buf_simple sdu; + + net_buf_simple_clone(&buf->b, &sdu); + net_buf_simple_pull(&sdu, BT_MESH_NET_HDR_LEN); + net_buf_simple_pull(&sdu, 1); + sdu.len -= BT_MESH_MIC_SHORT; + + return bt_mesh_app_encrypt(meta->key, &meta->crypto, &sdu); +} + +static int unseg_app_sdu_prepare(struct bt_mesh_friend *frnd, + struct net_buf *buf) +{ + struct unseg_app_sdu_meta meta; + int err; + + if (FRIEND_ADV(buf)->app_idx == BT_MESH_KEY_UNUSED) + { + return 0; + } + + err = unseg_app_sdu_unpack(frnd, buf, &meta); + if (err) + { + return err; + } + + /* No need to reencrypt the message if the sequence number is + * unchanged. + */ + + if (meta.crypto.seq_num == bt_mesh.seq) + { + return 0; + } + + LOG_DBG("Re-encrypting friend pdu (SeqNum %06x -> %06x)", + meta.crypto.seq_num, bt_mesh.seq); + + err = unseg_app_sdu_decrypt(frnd, buf, &meta); + if (err) + { + LOG_WRN("Decryption failed! %d", err); + return err; + } + + meta.crypto.seq_num = bt_mesh.seq; + + err = unseg_app_sdu_encrypt(frnd, buf, &meta); + if (err) + { + LOG_WRN("Re-encryption failed! %d", err); + } + + return err; +} + +static int encrypt_friend_pdu(struct bt_mesh_friend *frnd, + struct net_buf *buf, bool flooding_cred) +{ + const struct bt_mesh_net_cred *cred; + uint32_t iv_index; + uint16_t src; + int err; + + if (flooding_cred) + { + cred = &frnd->subnet->keys[SUBNET_KEY_TX_IDX(frnd->subnet)].msg; + } + else + { + cred = &frnd->cred[SUBNET_KEY_TX_IDX(frnd->subnet)]; + } + + src = sys_get_be16(&buf->data[5]); + + if (bt_mesh_has_addr(src)) + { + uint32_t seq; + + if (FRIEND_ADV(buf)->app_idx != BT_MESH_KEY_UNUSED) + { + err = unseg_app_sdu_prepare(frnd, buf); + if (err) + { + return err; + } + } + + seq = bt_mesh_next_seq(); + sys_put_be24(seq, &buf->data[2]); + + iv_index = BT_MESH_NET_IVI_TX; + FRIEND_ADV(buf)->app_idx = BT_MESH_KEY_UNUSED; + } + else + { + uint8_t ivi = (buf->data[0] >> 7); + + iv_index = (bt_mesh.iv_index - ((bt_mesh.iv_index & 1) != ivi)); + } + + buf->data[0] = (cred->nid | (iv_index & 1) << 7); + + if (bt_mesh_net_encrypt(&cred->enc, &buf->b, iv_index, + BT_MESH_NONCE_NETWORK)) + { + LOG_ERR("Encrypting failed"); + return -EINVAL; + } + + if (bt_mesh_net_obfuscate(buf->data, iv_index, &cred->privacy)) + { + LOG_ERR("Obfuscating failed"); + return -EINVAL; + } + + return 0; +} + +static struct net_buf *encode_friend_ctl( + struct bt_mesh_friend *frnd, uint8_t ctl_op, struct net_buf_simple *sdu) +{ + struct friend_pdu_info info; + + LOG_DBG("LPN 0x%04x", frnd->lpn); + + net_buf_simple_push_u8(sdu, TRANS_CTL_HDR(ctl_op, 0)); + + info.src = bt_mesh_primary_addr(); + info.dst = frnd->lpn; + + info.ctl = 1U; + info.ttl = 0U; + + memset(info.seq, 0, sizeof(info.seq)); + + info.iv_index = BT_MESH_NET_IVI_TX; + + return create_friend_pdu(frnd, &info, sdu); +} + +static struct net_buf *encode_update(struct bt_mesh_friend *frnd, uint8_t md) +{ + struct bt_mesh_ctl_friend_update *upd; + + NET_BUF_SIMPLE_DEFINE(sdu, 1 + sizeof(*upd)); + + DEBUGASSERT(friend_is_allocated(frnd)); + + LOG_DBG("lpn 0x%04x md 0x%02x", frnd->lpn, md); + + net_buf_simple_reserve(&sdu, 1); + + upd = net_buf_simple_add(&sdu, sizeof(*upd)); + upd->flags = bt_mesh_net_flags(frnd->subnet); + upd->iv_index = sys_cpu_to_be32(bt_mesh.iv_index); + upd->md = md; + + return encode_friend_ctl(frnd, TRANS_CTL_OP_FRIEND_UPDATE, &sdu); +} + +static void enqueue_sub_cfm(struct bt_mesh_friend *frnd, uint8_t xact) +{ + struct bt_mesh_ctl_friend_sub_confirm *cfm; + + NET_BUF_SIMPLE_DEFINE(sdu, 1 + sizeof(*cfm)); + struct net_buf *buf; + + LOG_DBG("lpn 0x%04x xact 0x%02x", frnd->lpn, xact); + + net_buf_simple_reserve(&sdu, 1); + + cfm = net_buf_simple_add(&sdu, sizeof(*cfm)); + cfm->xact = xact; + + buf = encode_friend_ctl(frnd, TRANS_CTL_OP_FRIEND_SUB_CFM, &sdu); + if (!buf) + { + LOG_ERR("Unable to encode Subscription List Confirmation"); + return; + } + + if (encrypt_friend_pdu(frnd, buf, false)) + { + return; + } + + if (frnd->last) + { + LOG_DBG("Discarding last PDU"); + net_buf_unref(frnd->last); + } + + frnd->last = buf; + frnd->send_last = 1U; +} + +static void friend_recv_delay(struct bt_mesh_friend *frnd) +{ + int32_t delay = recv_delay(frnd); + + frnd->pending_req = 1U; + k_work_reschedule(&frnd->timer, MSEC2TICK(delay)); + LOG_DBG("Waiting RecvDelay of %d ms", delay); +} + +int bt_mesh_friend_sub_add(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + struct bt_mesh_friend *frnd; + uint8_t xact; + + if (buf->len < BT_MESH_FRIEND_SUB_MIN_LEN) + { + LOG_WRN("Too short Friend Subscription Add"); + return -EBADMSG; + } + + frnd = bt_mesh_friend_find(rx->sub->net_idx, rx->ctx.addr, true, true); + if (!frnd) + { + LOG_WRN("No matching LPN addr 0x%04x", rx->ctx.addr); + return 0; + } + + if (frnd->pending_buf) + { + LOG_WRN("Previous buffer not yet sent!"); + return 0; + } + + friend_recv_delay(frnd); + + xact = net_buf_simple_pull_u8(buf); + + while (buf->len >= 2U) + { + friend_sub_add(frnd, net_buf_simple_pull_be16(buf)); + } + + enqueue_sub_cfm(frnd, xact); + + return 0; +} + +int bt_mesh_friend_sub_rem(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + struct bt_mesh_friend *frnd; + uint8_t xact; + + if (buf->len < BT_MESH_FRIEND_SUB_MIN_LEN) + { + LOG_WRN("Too short Friend Subscription Remove"); + return -EBADMSG; + } + + frnd = bt_mesh_friend_find(rx->sub->net_idx, rx->ctx.addr, true, true); + if (!frnd) + { + LOG_WRN("No matching LPN addr 0x%04x", rx->ctx.addr); + return 0; + } + + if (frnd->pending_buf) + { + LOG_WRN("Previous buffer not yet sent!"); + return 0; + } + + friend_recv_delay(frnd); + + xact = net_buf_simple_pull_u8(buf); + + while (buf->len >= 2U) + { + friend_sub_rem(frnd, net_buf_simple_pull_be16(buf)); + } + + enqueue_sub_cfm(frnd, xact); + + return 0; +} + +static void enqueue_buf(struct bt_mesh_friend *frnd, struct net_buf *buf) +{ + net_buf_slist_put(&frnd->queue, buf); + frnd->queue_size++; +} + +static void enqueue_update(struct bt_mesh_friend *frnd, uint8_t md) +{ + struct net_buf *buf; + + buf = encode_update(frnd, md); + if (!buf) + { + LOG_ERR("Unable to encode Friend Update"); + return; + } + + enqueue_buf(frnd, buf); +} + +int bt_mesh_friend_poll(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + struct bt_mesh_ctl_friend_poll *msg = (void *)buf->data; + struct bt_mesh_friend *frnd; + + if (buf->len < sizeof(*msg)) + { + LOG_WRN("Too short Friend Poll"); + return -EBADMSG; + } + + frnd = bt_mesh_friend_find(rx->sub->net_idx, rx->ctx.addr, true, false); + if (!frnd) + { + LOG_WRN("No matching LPN addr 0x%04x", rx->ctx.addr); + return 0; + } + + if (msg->fsn & ~1) + { + LOG_WRN("Prohibited (non-zero) padding bits"); + return -EBADMSG; + } + + if (frnd->pending_buf) + { + LOG_WRN("Previous buffer not yet sent"); + return 0; + } + + LOG_DBG("msg->fsn %u frnd->fsn %u", (msg->fsn & 1), frnd->fsn); + + friend_recv_delay(frnd); + + if (msg->fsn == frnd->fsn && frnd->last) + { + LOG_DBG("Re-sending last PDU"); + frnd->send_last = 1U; + } + else + { + net_buf_drop(&frnd->last); + + frnd->fsn = msg->fsn; + + if (list_is_empty(&frnd->queue)) + { + enqueue_update(frnd, 0); + LOG_DBG("Enqueued Friend Update to empty queue"); + } + } + + STRUCT_SECTION_FOREACH(bt_mesh_friend_cb, cb) + { + if (cb->polled) + { + cb->polled(frnd->subnet->net_idx, frnd->lpn); + } + } + + if (!frnd->established) + { + LOG_DBG("Friendship established with 0x%04x", frnd->lpn); + frnd->established = 1U; + + STRUCT_SECTION_FOREACH(bt_mesh_friend_cb, cb) + { + if (cb->established) + { + cb->established(frnd->subnet->net_idx, frnd->lpn, + frnd->recv_delay, + frnd->poll_to); + } + } + } + + return 0; +} + +static struct bt_mesh_friend *find_clear(uint16_t prev_friend) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) + { + struct bt_mesh_friend *frnd = &bt_mesh.frnd[i]; + + if (frnd->clear.frnd == prev_friend) + { + return frnd; + } + } + + return NULL; +} + +static void friend_clear_sent(int err, void *user_data) +{ + struct bt_mesh_friend *frnd = user_data; + + k_work_reschedule(&frnd->clear.timer, SEC2TICK(frnd->clear.repeat_sec)); + frnd->clear.repeat_sec *= 2U; +} + +static const struct bt_mesh_send_cb clear_sent_cb = +{ + .end = friend_clear_sent, +}; + +static void send_friend_clear(struct bt_mesh_friend *frnd) +{ + struct bt_mesh_msg_ctx ctx = + { + .net_idx = frnd->subnet->net_idx, + .app_idx = BT_MESH_KEY_UNUSED, + .addr = frnd->clear.frnd, + .send_ttl = BT_MESH_TTL_MAX, + }; + + struct bt_mesh_net_tx tx = + { + .sub = frnd->subnet, + .ctx = &ctx, + .src = bt_mesh_primary_addr(), + .xmit = bt_mesh_net_transmit_get(), + }; + + struct bt_mesh_ctl_friend_clear req = + { + .lpn_addr = sys_cpu_to_be16(frnd->lpn), + .lpn_counter = sys_cpu_to_be16(frnd->lpn_counter), + }; + + LOG_DBG(""); + + bt_mesh_ctl_send(&tx, TRANS_CTL_OP_FRIEND_CLEAR, &req, sizeof(req), + &clear_sent_cb, frnd); +} + +static void clear_timeout(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_mesh_friend *frnd = CONTAINER_OF(dwork, struct bt_mesh_friend, + clear.timer); + uint32_t duration; + + if (frnd->pending_cfm) + { + struct bt_mesh_subnet *sub; + struct bt_mesh_msg_ctx ctx = + { + .net_idx = frnd->clear.cfm.net_idx, + .app_idx = BT_MESH_KEY_UNUSED, + .addr = frnd->clear.cfm.dst, + .send_ttl = frnd->clear.cfm.ttl, + }; + + struct bt_mesh_net_tx tx = + { + .ctx = &ctx, + .src = bt_mesh_primary_addr(), + .xmit = bt_mesh_net_transmit_get(), + }; + + struct bt_mesh_ctl_friend_clear_confirm cfm = + { + .lpn_addr = frnd->clear.cfm.lpn_addr, + .lpn_counter = frnd->clear.cfm.lpn_counter, + }; + + frnd->pending_cfm = 0U; + + sub = bt_mesh_subnet_get(frnd->clear.cfm.net_idx); + if (!sub) + { + LOG_DBG("No subnet for Friend Clear Confirm"); + return; + } + + tx.sub = sub; + bt_mesh_ctl_send(&tx, TRANS_CTL_OP_FRIEND_CLEAR_CFM, &cfm, sizeof(cfm), + NULL, NULL); + return; + } + + if (frnd->clear.frnd == BT_MESH_ADDR_UNASSIGNED) + { + /* Failed cancelling timer, return early. */ + + return; + } + + LOG_DBG("LPN 0x%04x (old) Friend 0x%04x", frnd->lpn, frnd->clear.frnd); + + duration = (uint32_t)TICK2MSEC(clock_systime_ticks()) - frnd->clear.start; + if (duration > 2 * frnd->poll_to) + { + LOG_DBG("Clear Procedure timer expired"); + frnd->clear.frnd = BT_MESH_ADDR_UNASSIGNED; + return; + } + + send_friend_clear(frnd); +} + +static void clear_procedure_start(struct bt_mesh_friend *frnd) +{ + LOG_DBG("LPN 0x%04x (old) Friend 0x%04x", frnd->lpn, frnd->clear.frnd); + + frnd->clear.start = (uint32_t)TICK2MSEC(clock_systime_ticks()); + frnd->clear.repeat_sec = 1U; + + send_friend_clear(frnd); +} + +int bt_mesh_friend_clear_cfm(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + struct bt_mesh_ctl_friend_clear_confirm *msg = (void *)buf->data; + struct bt_mesh_friend *frnd; + uint16_t lpn_addr; + uint16_t lpn_counter; + + LOG_DBG(""); + + if (buf->len < sizeof(*msg)) + { + LOG_WRN("Too short Friend Clear Confirm"); + return -EBADMSG; + } + + frnd = find_clear(rx->ctx.addr); + if (!frnd) + { + LOG_WRN("No pending clear procedure for 0x%02x", rx->ctx.addr); + return 0; + } + + lpn_addr = sys_be16_to_cpu(msg->lpn_addr); + if (lpn_addr != frnd->lpn) + { + LOG_WRN("LPN address mismatch (0x%04x != " + "0x%04x)", lpn_addr, frnd->lpn); + return 0; + } + + lpn_counter = sys_be16_to_cpu(msg->lpn_counter); + if (lpn_counter != frnd->lpn_counter) + { + LOG_WRN("LPN counter mismatch (0x%04x != 0x%04x)", lpn_counter, + frnd->lpn_counter); + return 0; + } + + /* If this fails, the unassigned check will make the handler return early. + */ + + (void)k_work_cancel_delayable(&frnd->clear.timer); + frnd->clear.frnd = BT_MESH_ADDR_UNASSIGNED; + + return 0; +} + +static void enqueue_offer(struct bt_mesh_friend *frnd, int8_t rssi) +{ + struct bt_mesh_ctl_friend_offer *off; + + NET_BUF_SIMPLE_DEFINE(sdu, 1 + sizeof(*off)); + struct net_buf *buf; + + LOG_DBG(""); + + net_buf_simple_reserve(&sdu, 1); + + off = net_buf_simple_add(&sdu, sizeof(*off)); + + off->recv_win = CONFIG_BT_MESH_FRIEND_RECV_WIN, + off->queue_size = CONFIG_BT_MESH_FRIEND_QUEUE_SIZE, + off->sub_list_size = ARRAY_SIZE(frnd->sub_list), off->rssi = rssi, + + /* The Counter may be used in the later key update procedure. Therefore + * we should postpone the update of counter until we terminated friendship. + */ + + off->frnd_counter = sys_cpu_to_be16(frnd->counter); + + buf = encode_friend_ctl(frnd, TRANS_CTL_OP_FRIEND_OFFER, &sdu); + if (!buf) + { + LOG_ERR("Unable to encode Friend Offer"); + return; + } + + if (encrypt_friend_pdu(frnd, buf, true)) + { + return; + } + + if (frnd->last) + { + net_buf_unref(frnd->last); + } + + frnd->last = buf; + frnd->send_last = 1U; +} + +#define RECV_WIN CONFIG_BT_MESH_FRIEND_RECV_WIN +#define RSSI_FACT(crit) (((crit) >> 5) & (uint8_t)BIT_MASK(2)) +#define RECV_WIN_FACT(crit) (((crit) >> 3) & (uint8_t)BIT_MASK(2)) +#define MIN_QUEUE_SIZE_LOG(crit) ((crit) & (uint8_t)BIT_MASK(3)) +#define MIN_QUEUE_SIZE(crit) ((uint32_t)BIT(MIN_QUEUE_SIZE_LOG(crit))) + +static int32_t offer_delay(struct bt_mesh_friend *frnd, + int8_t rssi, uint8_t crit) +{ + /* Scaling factors. The actual values are 1, 1.5, 2 & 2.5, but we + * want to avoid floating-point arithmetic. + */ + + static const uint8_t fact[] = + { + 10, 15, 20, 25 + }; + + int32_t delay; + + LOG_DBG("ReceiveWindowFactor %u ReceiveWindow %u RSSIFactor %u RSSI %d", + fact[RECV_WIN_FACT(crit)], RECV_WIN, fact[RSSI_FACT(crit)], rssi); + + /* Delay = ReceiveWindowFactor * ReceiveWindow - RSSIFactor * RSSI */ + + delay = (int32_t)fact[RECV_WIN_FACT(crit)] * RECV_WIN; + delay -= (int32_t)fact[RSSI_FACT(crit)] * rssi; + delay /= 10; + + LOG_DBG("Local Delay calculated as %d ms", delay); + + return MAX(delay, 100); +} + +int bt_mesh_friend_req(struct bt_mesh_net_rx *rx, struct net_buf_simple *buf) +{ + struct bt_mesh_ctl_friend_req *msg = (void *)buf->data; + struct bt_mesh_friend *frnd = NULL; + uint32_t poll_to; + int32_t delay; + int i; + int err; + + if (rx->net_if == BT_MESH_NET_IF_LOCAL) + { + LOG_DBG("Ignoring Friend request from local interface"); + return 0; + } + + if (buf->len < sizeof(*msg)) + { + LOG_WRN("Too short Friend Request"); + return -EBADMSG; + } + + if (msg->recv_delay <= 0x09) + { + LOG_WRN("Prohibited ReceiveDelay (0x%02x)", msg->recv_delay); + return -EBADMSG; + } + + poll_to = sys_get_be24(msg->poll_to); + + if (poll_to <= 0x000009 || poll_to >= 0x34bc00) + { + LOG_WRN("Prohibited PollTimeout (0x%06x)", poll_to); + return -EBADMSG; + } + + if (msg->num_elem == 0x00) + { + LOG_WRN("Prohibited NumElements value (0x00)"); + return -EBADMSG; + } + + if (!BT_MESH_ADDR_IS_UNICAST(rx->ctx.addr + msg->num_elem - 1)) + { + LOG_WRN("LPN elements stretch outside of unicast range"); + return -EBADMSG; + } + + if (!MIN_QUEUE_SIZE_LOG(msg->criteria)) + { + LOG_WRN("Prohibited Minimum Queue Size in Friend Request"); + return -EBADMSG; + } + + if (CONFIG_BT_MESH_FRIEND_QUEUE_SIZE < MIN_QUEUE_SIZE(msg->criteria)) + { + LOG_WRN("We have a too small Friend Queue size (%u < %u)", + CONFIG_BT_MESH_FRIEND_QUEUE_SIZE, + MIN_QUEUE_SIZE(msg->criteria)); + return 0; + } + + frnd = bt_mesh_friend_find(rx->sub->net_idx, rx->ctx.addr, true, false); + if (frnd) + { + LOG_WRN("Existing LPN re-requesting Friendship"); + friend_clear(frnd); + goto init_friend; + } + + for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) + { + if (!bt_mesh.frnd[i].subnet && !bt_mesh.frnd[i].pending_cfm) + { + frnd = &bt_mesh.frnd[i]; + break; + } + } + + if (!frnd) + { + LOG_WRN("No free Friend contexts for new LPN"); + return -ENOMEM; + } + +init_friend: + frnd->lpn = rx->ctx.addr; + frnd->num_elem = msg->num_elem; + frnd->subnet = rx->sub; + frnd->recv_delay = msg->recv_delay - CONFIG_BT_MESH_FRIEND_ADV_LATENCY; + frnd->poll_to = poll_to * 100U; + frnd->lpn_counter = sys_be16_to_cpu(msg->lpn_counter); + frnd->clear.frnd = sys_be16_to_cpu(msg->prev_addr); + + err = friend_cred_create(frnd, SUBNET_KEY_TX_IDX(frnd->subnet)); + if (err) + { + LOG_ERR("Failed to create friend credentials"); + friend_clear(frnd); + return -EIO; + } + + LOG_DBG("LPN 0x%04x rssi %d recv_delay %u poll_to %ums", frnd->lpn, + rx->ctx.recv_rssi, + frnd->recv_delay, frnd->poll_to); + + if (BT_MESH_ADDR_IS_UNICAST(frnd->clear.frnd) && + !bt_mesh_has_addr(frnd->clear.frnd)) + { + clear_procedure_start(frnd); + } + + delay = offer_delay(frnd, rx->ctx.recv_rssi, msg->criteria); + k_work_reschedule(&frnd->timer, MSEC2TICK(delay)); + + enqueue_offer(frnd, rx->ctx.recv_rssi); + + return 0; +} + +static bool is_seg(struct bt_mesh_friend_seg *seg, + uint16_t src, uint16_t seq_zero) +{ + struct net_buf *buf = (void *)list_peek_head(&seg->queue); + struct net_buf_simple_state state; + uint16_t buf_seq_zero; + uint16_t buf_src; + + if (!buf) + { + return false; + } + + net_buf_simple_save(&buf->b, &state); + net_buf_skip(buf, 5); /* skip IVI, NID, CTL, TTL, SEQ */ + buf_src = net_buf_pull_be16(buf); + net_buf_skip(buf, 3); /* skip DST, OP/AID */ + buf_seq_zero = ((net_buf_pull_be16(buf) >> 2) & TRANS_SEQ_ZERO_MASK); + net_buf_simple_restore(&buf->b, &state); + + return ((src == buf_src) && (seq_zero == buf_seq_zero)); +} + +static struct bt_mesh_friend_seg *get_seg( + struct bt_mesh_friend *frnd, uint16_t src, uint16_t seq_zero, + uint8_t seg_count) +{ + struct bt_mesh_friend_seg *unassigned = NULL; + int i; + + for (i = 0; i < ARRAY_SIZE(frnd->seg); i++) + { + struct bt_mesh_friend_seg *seg = &frnd->seg[i]; + + if (is_seg(seg, src, seq_zero)) + { + return seg; + } + + if (!unassigned && !list_peek_head(&seg->queue)) + { + unassigned = seg; + } + } + + if (unassigned) + { + unassigned->seg_count = seg_count; + } + + return unassigned; +} + +static void enqueue_friend_pdu( + struct bt_mesh_friend *frnd, enum bt_mesh_friend_pdu_type type, + uint16_t src, uint8_t seg_count, struct net_buf *buf) +{ + struct bt_mesh_friend_seg *seg; + + LOG_DBG("type %u", type); + + if (type == BT_MESH_FRIEND_PDU_SINGLE) + { + enqueue_buf(frnd, buf); + return; + } + + uint16_t seq_zero = + ( + ((buf->data + [10] << 8 | buf->data[11]) >> 2) & TRANS_SEQ_ZERO_MASK); + + seg = get_seg(frnd, src, seq_zero, seg_count); + if (!seg) + { + LOG_ERR("No free friend segment RX contexts for 0x%04x", src); + net_buf_unref(buf); + return; + } + + net_buf_slist_put(&seg->queue, buf); + + if (type == BT_MESH_FRIEND_PDU_COMPLETE) + { + list_merge(&frnd->queue, &seg->queue); + + frnd->queue_size += seg->seg_count; + seg->seg_count = 0U; + } + else + { + FRIEND_ADV(buf)->seg = true; + } +} + +static void buf_send_start(uint16_t duration, int err, void *user_data) +{ + struct bt_mesh_friend *frnd = user_data; + + LOG_DBG("err %d", err); + + if (!frnd->pending_buf) + { + LOG_WRN("Attempt of sending to removed friend"); + return; + } + + frnd->pending_buf = 0U; + + /* Friend Offer doesn't follow the re-sending semantics */ + + if (!frnd->established) + { + net_buf_drop(&frnd->last); + } +} + +static void buf_send_end(int err, void *user_data) +{ + struct bt_mesh_friend *frnd = user_data; + + LOG_DBG("err %d", err); + + if (frnd->pending_req || frnd->pending_buf) + { + LOG_WRN("Another request before previous completed sending"); + return; + } + + if (frnd->established) + { + /* Always restart poll timeout timer after sending */ + + k_work_reschedule(&frnd->timer, MSEC2TICK(frnd->poll_to)); + LOG_DBG("Waiting %u ms for next poll", frnd->poll_to); + } + else + { + /* Friend offer timeout is 1 second */ + + k_work_reschedule(&frnd->timer, SEC2TICK(1)); + LOG_DBG("Waiting for first poll"); + } +} + +static void update_overwrite(struct net_buf *buf, uint8_t md) +{ + struct net_buf_simple_state state; + struct bt_mesh_ctl_friend_update *upd; + + if (buf->len != 16) + { + return; + } + + net_buf_simple_save(&buf->b, &state); + + net_buf_skip(buf, 1); /* skip IVI, NID */ + + if (!(net_buf_pull_u8(buf) >> 7)) + { + goto end; + } + + net_buf_skip(buf, 7); /* skip seqnum src dec */ + + if (TRANS_CTL_OP((uint8_t *)net_buf_pull_mem(buf, + 1)) != TRANS_CTL_OP_FRIEND_UPDATE) + { + goto end; + } + + upd = net_buf_pull_mem(buf, sizeof(*upd)); + LOG_DBG("Update Previous Friend Update MD 0x%02x -> 0x%02x", upd->md, md); + upd->md = md; + +end: + net_buf_simple_restore(&buf->b, &state); +} + +static void friend_timeout(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_mesh_friend *frnd = CONTAINER_OF(dwork, struct bt_mesh_friend, + timer); + static const struct bt_mesh_send_cb buf_sent_cb = + { + .start = buf_send_start, + .end = buf_send_end, + }; + + struct bt_mesh_adv *adv; + uint8_t md; + + if (!friend_is_allocated(frnd)) + { + return; + } + + DEBUGASSERT(frnd->pending_buf == 0U); + + LOG_DBG("lpn 0x%04x send_last %u last %p", frnd->lpn, frnd->send_last, + frnd->last); + + if (frnd->send_last && frnd->last) + { + LOG_DBG("Sending frnd->last %p", frnd->last); + frnd->send_last = 0U; + goto send_last; + } + + if (frnd->established && !frnd->pending_req) + { + LOG_WRN("Friendship lost with 0x%04x", frnd->lpn); + friend_clear(frnd); + return; + } + + frnd->last = (void *)net_buf_slist_get(&frnd->queue); + if (!frnd->last) + { + LOG_WRN("Friendship not established with 0x%04x", frnd->lpn); + friend_clear(frnd); + return; + } + + md = (uint8_t)(list_peek_head(&frnd->queue) != NULL); + + update_overwrite(frnd->last, md); + + if (encrypt_friend_pdu(frnd, frnd->last, false)) + { + return; + } + + LOG_DBG("Sending buf %p from Friend Queue of LPN 0x%04x", frnd->last, + frnd->lpn); + frnd->queue_size--; + +send_last: + adv = bt_mesh_adv_create(BT_MESH_ADV_DATA, BT_MESH_ADV_TAG_FRIEND, + FRIEND_XMIT, 0); + if (!adv) + { + LOG_ERR("Unable to allocate friend adv"); + return; + } + + net_buf_simple_add_mem(&adv->b, frnd->last->data, frnd->last->len); + + frnd->pending_req = 0U; + frnd->pending_buf = 1U; + bt_mesh_adv_send(adv, &buf_sent_cb, frnd); + bt_mesh_adv_unref(adv); +} + +static void subnet_evt(struct bt_mesh_subnet *sub, enum bt_mesh_key_evt evt) +{ + int i; + int err; + + if (evt == BT_MESH_KEY_ADDED) + { + return; + } + + for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) + { + struct bt_mesh_friend *frnd = &bt_mesh.frnd[i]; + + if (frnd->subnet != sub) + { + continue; + } + + switch (evt) + { + case BT_MESH_KEY_DELETED: + LOG_DBG("Cleared network for 0x%04x", frnd->lpn); + friend_clear(frnd); + break; + case BT_MESH_KEY_UPDATED: + LOG_DBG("Generating new keys for 0x%04x", frnd->lpn); + err = friend_cred_create(frnd, 1); + if (err) + { + LOG_ERR("Failed updating friend cred for 0x%04x", frnd->lpn); + friend_clear(frnd); + } + break; + case BT_MESH_KEY_SWAPPED: + enqueue_update(frnd, 0); + break; + case BT_MESH_KEY_REVOKED: + LOG_DBG("Revoking old keys for 0x%04x", frnd->lpn); + bt_mesh_friend_cred_destroy(&frnd->cred[0]); + memcpy(&frnd->cred[0], &frnd->cred[1], sizeof(frnd->cred[0])); + memset(&frnd->cred[1], 0, sizeof(frnd->cred[1])); + enqueue_update(frnd, 0); + break; + default: + break; + } + } +} + +BT_MESH_SUBNET_CB_DEFINE(friend) = +{ + .evt_handler = subnet_evt, +}; + +int bt_mesh_friend_init(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) + { + struct bt_mesh_friend *frnd = &bt_mesh.frnd[i]; + int j; + + list_initialize(&frnd->queue); + + k_work_init_delayable(&frnd->timer, friend_timeout); + k_work_init_delayable(&frnd->clear.timer, clear_timeout); + + for (j = 0; j < ARRAY_SIZE(frnd->seg); j++) + { + list_initialize(&frnd->seg[j].queue); + } + } + + return 0; +} + +static bool is_segack(struct net_buf *buf, + const uint64_t *seqauth, uint16_t src) +{ + struct net_buf_simple_state state; + bool found = false; + + if (buf->len != 16) + { + return false; + } + + net_buf_simple_save(&buf->b, &state); + + net_buf_skip(buf, 1); /* skip IVI, NID */ + + if (!(net_buf_pull_u8(buf) >> 7)) + { + goto end; + } + + net_buf_pull(buf, 3); /* skip SEQNUM */ + + if (src != net_buf_pull_be16(buf)) + { + goto end; + } + + net_buf_skip(buf, 2); /* skip dst */ + + if (TRANS_CTL_OP((uint8_t *)net_buf_pull_mem(buf, 1)) != TRANS_CTL_OP_ACK) + { + goto end; + } + + found = ((net_buf_pull_be16(buf) >> 2) & + TRANS_SEQ_ZERO_MASK) == (*seqauth & TRANS_SEQ_ZERO_MASK); +end: + net_buf_simple_restore(&buf->b, &state); + return found; +} + +static void friend_purge_old_ack( + struct bt_mesh_friend *frnd, const uint64_t *seq_auth, uint16_t src) +{ + struct list_node *cur; + struct list_node *prev = NULL; + + LOG_DBG("SeqAuth %llx src 0x%04x", *seq_auth, src); + + for (cur = list_peek_head(&frnd->queue); cur != NULL; + prev = cur, cur = list_next(&frnd->queue, cur)) + { + struct net_buf *buf = (void *)cur; + + if (is_segack(buf, seq_auth, src)) + { + LOG_DBG("Removing old ack from Friend Queue"); + + list_remove(&frnd->queue, prev, cur); + frnd->queue_size--; + + net_buf_unref(buf); + break; + } + } +} + +static void friend_lpn_enqueue_rx( + struct bt_mesh_friend *frnd, struct bt_mesh_net_rx *rx, + enum bt_mesh_friend_pdu_type type, const uint64_t *seq_auth, + uint8_t seg_count, struct net_buf_simple *sbuf) +{ + struct friend_pdu_info info; + struct net_buf *buf; + + /* Because of network loopback, tx packets will also be passed into + * this rx function. These packets have already been added to the queue, + * and should be ignored. + */ + + if (bt_mesh_has_addr(rx->ctx.addr)) + { + return; + } + + LOG_DBG("LPN 0x%04x queue_size %u", frnd->lpn, frnd->queue_size); + + if (type == BT_MESH_FRIEND_PDU_SINGLE && seq_auth) + { + friend_purge_old_ack(frnd, seq_auth, rx->ctx.addr); + } + + info.src = rx->ctx.addr; + info.dst = rx->ctx.recv_dst; + + if (rx->net_if == BT_MESH_NET_IF_LOCAL) + { + info.ttl = rx->ctx.recv_ttl; + } + else + { + info.ttl = rx->ctx.recv_ttl - 1U; + } + + info.ctl = rx->ctl; + + sys_put_be24(rx->seq, info.seq); + + info.iv_index = BT_MESH_NET_IVI_RX(rx); + + buf = create_friend_pdu(frnd, &info, sbuf); + if (!buf) + { + LOG_ERR("Failed to encode Friend buffer"); + return; + } + + enqueue_friend_pdu(frnd, type, info.src, seg_count, buf); + + LOG_DBG("Queued message for LPN 0x%04x, queue_size %u", frnd->lpn, + frnd->queue_size); +} + +static void friend_lpn_enqueue_tx( + struct bt_mesh_friend *frnd, struct bt_mesh_net_tx *tx, + enum bt_mesh_friend_pdu_type type, const uint64_t *seq_auth, + uint8_t seg_count, struct net_buf_simple *sbuf) +{ + struct friend_pdu_info info; + struct net_buf *buf; + + LOG_DBG("LPN 0x%04x", frnd->lpn); + + if (type == BT_MESH_FRIEND_PDU_SINGLE && seq_auth) + { + friend_purge_old_ack(frnd, seq_auth, tx->src); + } + + info.src = tx->src; + info.dst = tx->ctx->addr; + + info.ttl = tx->ctx->send_ttl; + info.ctl = (tx->ctx->app_idx == BT_MESH_KEY_UNUSED); + + sys_put_be24(bt_mesh.seq, info.seq); + + info.iv_index = BT_MESH_NET_IVI_TX; + + buf = create_friend_pdu(frnd, &info, sbuf); + if (!buf) + { + LOG_ERR("Failed to encode Friend buffer"); + return; + } + + if (type == BT_MESH_FRIEND_PDU_SINGLE && !info.ctl) + { + /* Unsegmented application packets may be reencrypted later, + * as they depend on the sequence number being the same when encrypting + * in transport and network. + */ + + FRIEND_ADV(buf)->app_idx = tx->ctx->app_idx; + + /* When reencrypting a virtual address message, we need to know uuid as + * well. + */ + + if (BT_MESH_ADDR_IS_VIRTUAL(tx->ctx->addr)) + { + uint16_t uuidx; + int err; + + err = bt_mesh_va_get_idx_by_uuid(tx->ctx->uuid, &uuidx); + if (err) + { + net_buf_unref(buf); + return; + } + + FRIEND_ADV(buf)->uuidx = uuidx; + } + } + + enqueue_friend_pdu(frnd, type, info.src, seg_count, buf); + + LOG_DBG("Queued message for LPN 0x%04x, dst: %04x, uuid: %p", frnd->lpn, + tx->ctx->addr, + tx->ctx->uuid); +} + +static bool friend_lpn_matches(struct bt_mesh_friend *frnd, + uint16_t net_idx, uint16_t addr) +{ + int i; + + if (!frnd->established) + { + return false; + } + + if (net_idx != frnd->subnet->net_idx) + { + return false; + } + + if (BT_MESH_ADDR_IS_UNICAST(addr)) + { + return is_lpn_unicast(frnd, addr); + } + + for (i = 0; i < ARRAY_SIZE(frnd->sub_list); i++) + { + if (frnd->sub_list[i] == addr) + { + return true; + } + } + + return false; +} + +bool bt_mesh_friend_match(uint16_t net_idx, uint16_t addr) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) + { + struct bt_mesh_friend *frnd = &bt_mesh.frnd[i]; + + if (friend_lpn_matches(frnd, net_idx, addr)) + { + LOG_DBG("LPN 0x%04x matched address 0x%04x", frnd->lpn, addr); + return true; + } + } + + LOG_DBG("No matching LPN for address 0x%04x", addr); + + return false; +} + +static bool friend_queue_has_space( + struct bt_mesh_friend *frnd, uint16_t addr, const uint64_t *seq_auth, + uint8_t seg_count) +{ + uint32_t total = 0; + int i; + + if (seg_count > CONFIG_BT_MESH_FRIEND_QUEUE_SIZE) + { + return false; + } + + for (i = 0; i < ARRAY_SIZE(frnd->seg); i++) + { + struct bt_mesh_friend_seg *seg = &frnd->seg[i]; + + if (seq_auth && is_seg(seg, addr, *seq_auth & TRANS_SEQ_ZERO_MASK)) + { + /* If there's a segment queue for this message then the + * space verification has already happened. + */ + + return true; + } + + total += seg->seg_count; + } + + /* If currently pending segments combined with this segmented message + * are more than the Friend Queue Size, then there's no space. This is + * because we don't have a mechanism of aborting already pending segmented + * messages to free up buffers. + */ + + return (CONFIG_BT_MESH_FRIEND_QUEUE_SIZE - total) > seg_count; +} + +bool bt_mesh_friend_queue_has_space( + uint16_t net_idx, uint16_t src, uint16_t dst, uint64_t *seq_auth, + uint8_t seg_count) +{ + bool someone_has_space = false; + bool friend_match = false; + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) + { + struct bt_mesh_friend *frnd = &bt_mesh.frnd[i]; + + if (!friend_lpn_matches(frnd, net_idx, dst)) + { + continue; + } + + friend_match = true; + + if (friend_queue_has_space(frnd, src, seq_auth, seg_count)) + { + someone_has_space = true; + } + } + + /* If there were no matched LPNs treat this as success, so the + * transport layer can continue its work. + */ + + if (!friend_match) + { + return true; + } + + /* From the transport layers perspective it's good enough that at + * least one Friend Queue has space. If there were multiple Friend matches + * then the destination must be a group address, in which case e.g. segment + * acks are not sent. + */ + + return someone_has_space; +} + +static bool friend_queue_check_dup(struct bt_mesh_friend *frnd, + uint32_t seq, uint16_t src) +{ + struct bt_mesh_net_rx rx; + struct list_node *cur; + + for (cur = list_peek_head(&frnd->queue); cur != NULL; cur = + list_next(&frnd->queue, + cur)) + { + struct net_buf *buf = (void *)cur; + + bt_mesh_net_header_parse(&buf->b, &rx); + + if ((src == rx.ctx.addr) && (seq == rx.seq)) + { + return true; + } + } + + return false; +} + +static bool friend_queue_prepare_space( + struct bt_mesh_friend *frnd, uint16_t addr, const uint64_t *seq_auth, + uint8_t seg_count) +{ + bool pending_segments; + uint8_t avail_space; + + if (!friend_queue_has_space(frnd, addr, seq_auth, seg_count)) + { + return false; + } + + avail_space = CONFIG_BT_MESH_FRIEND_QUEUE_SIZE - frnd->queue_size; + pending_segments = false; + + while (pending_segments || avail_space < seg_count) + { + struct net_buf *buf = (void *)net_buf_slist_get(&frnd->queue); + + if (!buf) + { + LOG_ERR("Unable to free up enough buffers"); + return false; + } + + frnd->queue_size--; + avail_space++; + + pending_segments = FRIEND_ADV(buf)->seg; + + net_buf_unref(buf); + } + + return true; +} + +void bt_mesh_friend_enqueue_rx( + struct bt_mesh_net_rx *rx, enum bt_mesh_friend_pdu_type type, + const uint64_t *seq_auth, uint8_t seg_count, struct net_buf_simple *sbuf) +{ + int i; + + if (!rx->friend_match || (rx->ctx.recv_ttl <= 1U && + rx->net_if != BT_MESH_NET_IF_LOCAL) || + bt_mesh_friend_get() != BT_MESH_FRIEND_ENABLED) + { + return; + } + + LOG_DBG("recv_ttl %u net_idx 0x%04x src 0x%04x dst 0x%04x", + rx->ctx.recv_ttl, + rx->sub->net_idx, + rx->ctx.addr, rx->ctx.recv_dst); + + for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) + { + struct bt_mesh_friend *frnd = &bt_mesh.frnd[i]; + + if (!friend_lpn_matches(frnd, rx->sub->net_idx, rx->ctx.recv_dst)) + { + continue; + } + + if (friend_lpn_matches(frnd, rx->sub->net_idx, rx->ctx.addr)) + { + continue; + } + + if (friend_queue_check_dup(frnd, rx->seq, rx->ctx.addr)) + { + continue; + } + + if (!friend_queue_prepare_space(frnd, rx->ctx.addr, seq_auth, + seg_count)) + { + continue; + } + + friend_lpn_enqueue_rx(frnd, rx, type, seq_auth, seg_count, sbuf); + } +} + +bool bt_mesh_friend_enqueue_tx( + struct bt_mesh_net_tx *tx, enum bt_mesh_friend_pdu_type type, + const uint64_t *seq_auth, uint8_t seg_count, struct net_buf_simple *sbuf) +{ + bool matched = false; + int i; + + if (!bt_mesh_friend_match(tx->sub->net_idx, tx->ctx->addr) || + bt_mesh_friend_get() != BT_MESH_FRIEND_ENABLED) + { + return matched; + } + + LOG_DBG("net_idx 0x%04x dst 0x%04x src 0x%04x", tx->sub->net_idx, + tx->ctx->addr, tx->src); + + for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) + { + struct bt_mesh_friend *frnd = &bt_mesh.frnd[i]; + + if (!friend_lpn_matches(frnd, tx->sub->net_idx, tx->ctx->addr)) + { + continue; + } + + if (!friend_queue_prepare_space(frnd, tx->src, seq_auth, seg_count)) + { + continue; + } + + friend_lpn_enqueue_tx(frnd, tx, type, seq_auth, seg_count, sbuf); + matched = true; + } + + return matched; +} + +int bt_mesh_friend_terminate(uint16_t lpn_addr) +{ + struct bt_mesh_friend *frnd; + + frnd = bt_mesh_friend_find(BT_MESH_KEY_ANY, lpn_addr, false, false); + if (!frnd) + { + return -ENOENT; + } + + friend_clear(frnd); + + return 0; +} + +void bt_mesh_friend_clear_incomplete( + struct bt_mesh_subnet *sub, uint16_t src, uint16_t dst, uint64_t *seq_auth) +{ + int i; + + LOG_DBG(""); + + for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) + { + struct bt_mesh_friend *frnd = &bt_mesh.frnd[i]; + int j; + + if (!friend_lpn_matches(frnd, sub->net_idx, dst)) + { + continue; + } + + for (j = 0; j < ARRAY_SIZE(frnd->seg); j++) + { + struct bt_mesh_friend_seg *seg = &frnd->seg[j]; + + if (!is_seg(seg, src, *seq_auth & TRANS_SEQ_ZERO_MASK)) + { + continue; + } + + LOG_WRN("Clearing incomplete segments for 0x%04x", src); + + purge_buffers(&seg->queue); + seg->seg_count = 0U; + break; + } + } +} diff --git a/libs/zblue/mesh/friend.h b/libs/zblue/mesh/friend.h new file mode 100644 index 0000000000000..7fcb5a08a4d4c --- /dev/null +++ b/libs/zblue/mesh/friend.h @@ -0,0 +1,57 @@ +/**************************************************************************** + * libs/zblue/mesh/friend.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +enum bt_mesh_friend_pdu_type +{ + BT_MESH_FRIEND_PDU_SINGLE, + BT_MESH_FRIEND_PDU_PARTIAL, + BT_MESH_FRIEND_PDU_COMPLETE, +}; + +bool bt_mesh_friend_match(uint16_t net_idx, uint16_t addr); + +struct bt_mesh_friend *bt_mesh_friend_find( + uint16_t net_idx, uint16_t lpn_addr, bool valid, bool established); + +bool bt_mesh_friend_queue_has_space( + uint16_t net_idx, uint16_t src, uint16_t dst, uint64_t *seq_auth, + uint8_t seg_count); + +void bt_mesh_friend_enqueue_rx( + struct bt_mesh_net_rx *rx, enum bt_mesh_friend_pdu_type type, + const uint64_t *seq_auth, uint8_t seg_count, struct net_buf_simple *sbuf); +bool bt_mesh_friend_enqueue_tx( + struct bt_mesh_net_tx *tx, enum bt_mesh_friend_pdu_type type, + const uint64_t *seq_auth, uint8_t seg_count, struct net_buf_simple *sbuf); + +void bt_mesh_friend_clear_incomplete( + struct bt_mesh_subnet *sub, uint16_t src, uint16_t dst, + uint64_t *seq_auth); + +void bt_mesh_friend_sec_update(uint16_t net_idx); + +void bt_mesh_friends_clear(void); + +int bt_mesh_friend_poll(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf); +int bt_mesh_friend_req(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf); +int bt_mesh_friend_clear(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf); +int bt_mesh_friend_clear_cfm(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf); +int bt_mesh_friend_sub_add(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf); +int bt_mesh_friend_sub_rem(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf); + +int bt_mesh_friend_init(void); diff --git a/libs/zblue/mesh/gatt.h b/libs/zblue/mesh/gatt.h new file mode 100644 index 0000000000000..c7c4cf6c21121 --- /dev/null +++ b/libs/zblue/mesh/gatt.h @@ -0,0 +1,24 @@ +/**************************************************************************** + * libs/zblue/mesh/gatt.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define ADV_SLOW_INT \ + .interval_min = BT_GAP_ADV_SLOW_INT_MIN, .interval_max = BT_GAP_ADV_SLOW_INT_MAX + +#define ADV_FAST_INT \ + .interval_min = BT_GAP_ADV_FAST_INT_MIN_2, .interval_max = BT_GAP_ADV_FAST_INT_MAX_2 + +#define BT_DEVICE_NAME \ + (IS_ENABLED(CONFIG_BT_DEVICE_NAME_DYNAMIC) ? (const uint8_t *)bt_get_name() \ + : (const uint8_t *)CONFIG_BT_DEVICE_NAME) +#define BT_DEVICE_NAME_LEN \ + (IS_ENABLED(CONFIG_BT_DEVICE_NAME_DYNAMIC) ? strlen(bt_get_name()) \ + : (sizeof(CONFIG_BT_DEVICE_NAME) - 1)) diff --git a/libs/zblue/mesh/gatt_cli.c b/libs/zblue/mesh/gatt_cli.c new file mode 100644 index 0000000000000..163bb40776494 --- /dev/null +++ b/libs/zblue/mesh/gatt_cli.c @@ -0,0 +1,415 @@ +/**************************************************************************** + * libs/zblue/mesh/gatt_cli.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2021 Xiaomi Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "common/bt_str.h" + +#include "mesh.h" +#include "net.h" +#include "rpl.h" +#include "transport.h" +#include "prov.h" +#include "beacon.h" +#include "foundation.h" +#include "access.h" +#include "proxy.h" +#include "proxy_msg.h" +#include "proxy_cli.h" +#include "gatt_cli.h" +#include "pb_gatt_cli.h" + +#define LOG_LEVEL CONFIG_BT_MESH_PROXY_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_gatt_client); + +static struct bt_mesh_gatt_server +{ + struct bt_conn *conn; + uint16_t svc_start_handle; + uint16_t data_in_handle; + const struct bt_mesh_gatt_cli *gatt; + + union + { + void *user_data; + struct bt_gatt_discover_params discover; + struct bt_gatt_subscribe_params subscribe; + }; +} + +servers[CONFIG_BT_MAX_CONN]; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static struct bt_mesh_gatt_server *get_server(struct bt_conn *conn) +{ + return &servers[bt_conn_index(conn)]; +} + +static uint8_t notify_func( + struct bt_conn *conn, struct bt_gatt_subscribe_params *params, + const void *data, uint16_t length) +{ + const uint8_t *val = data; + + if (!data) + { + LOG_WRN("[UNSUBSCRIBED]"); + params->value_handle = 0U; + return BT_GATT_ITER_STOP; + } + + if (length < 1) + { + LOG_WRN("Too small Proxy PDU"); + return BT_GATT_ITER_STOP; + } + + (void)bt_mesh_proxy_msg_recv(conn, val, length); + + return BT_GATT_ITER_CONTINUE; +} + +static void notify_enabled(struct bt_conn *conn, uint8_t err, + struct bt_gatt_subscribe_params *params) +{ + struct bt_mesh_gatt_server *server = get_server(conn); + + if (err != 0) + { + LOG_WRN("Enable notify failed(err:%d)", err); + return; + } + + LOG_DBG("[SUBSCRIBED]"); + + server->gatt->link_open(conn); +} + +static uint8_t discover_func( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *params) +{ + int err; + struct bt_mesh_gatt_server *server = get_server(conn); + + if (!attr) + { + LOG_DBG("GATT Services Discover complete"); + (void)memset(params, 0, sizeof(*params)); + return BT_GATT_ITER_STOP; + } + + LOG_DBG("[ATTRIBUTE UUID 0x%04x] handle %u", + BT_UUID_16(server->discover.uuid)->val, + attr->handle); + + if (!bt_uuid_cmp(server->discover.uuid, &server->gatt->srv_uuid.uuid)) + { + server->svc_start_handle = attr->handle; + + server->discover.uuid = &server->gatt->data_in_uuid.uuid; + server->discover.start_handle = attr->handle + 1; + server->discover.type = BT_GATT_DISCOVER_CHARACTERISTIC; + + err = bt_gatt_discover(conn, &server->discover); + if (err) + { + LOG_DBG("Discover GATT data in char failed (err %d)", err); + } + } + else if (!bt_uuid_cmp(server->discover.uuid, + &server->gatt->data_in_uuid.uuid)) + { + server->data_in_handle = attr->handle + 1; + + server->discover.uuid = &server->gatt->data_out_uuid.uuid; + server->discover.start_handle = server->svc_start_handle + 1; + server->discover.type = BT_GATT_DISCOVER_CHARACTERISTIC; + + err = bt_gatt_discover(conn, &server->discover); + if (err) + { + LOG_DBG("Discover GATT data out char failed (err %d)", err); + } + } + else if (!bt_uuid_cmp(server->discover.uuid, + &server->gatt->data_out_uuid.uuid)) + { + server->discover.uuid = &server->gatt->data_out_cccd_uuid.uuid; + server->discover.start_handle = attr->handle + 2; + server->discover.type = BT_GATT_DISCOVER_DESCRIPTOR; + + err = bt_gatt_discover(conn, &server->discover); + if (err) + { + LOG_DBG("Discover GATT CCCD failed (err %d)", err); + } + } + else + { + (void)memset(&server->subscribe, 0, sizeof(server->subscribe)); + + server->subscribe.notify = notify_func; + server->subscribe.subscribe = notify_enabled; + server->subscribe.value = BT_GATT_CCC_NOTIFY; + server->subscribe.ccc_handle = attr->handle; + server->subscribe.value_handle = attr->handle - 1; + + err = bt_gatt_subscribe(conn, &server->subscribe); + if (err && err != -EALREADY) + { + LOG_DBG("Subscribe failed (err %d)", err); + } + } + + return BT_GATT_ITER_STOP; +} + +int bt_mesh_gatt_send(struct bt_conn *conn, const void *data, uint16_t len, + bt_gatt_complete_func_t end, void *user_data) +{ + struct bt_mesh_gatt_server *server = get_server(conn); + + LOG_DBG("%u bytes: %s", len, bt_hex(data, len)); + + return bt_gatt_write_without_response_cb(conn, + server->data_in_handle, + data, + + len, false, end, + user_data); +} + +static void gatt_connected(struct bt_conn *conn, uint8_t conn_err) +{ + struct bt_mesh_gatt_server *server = get_server(conn); + struct bt_conn_info info; + int err; + + err = bt_conn_get_info(conn, &info); + if (err || info.role != BT_CONN_ROLE_CENTRAL || !server->gatt) + { + return; + } + + if (conn_err) + { + LOG_ERR("Failed to connect GATT Services(%u)", conn_err); + + bt_conn_drop(&server->conn); + + (void)bt_mesh_scan_enable(); + + return; + } + + LOG_DBG("conn %p err 0x%02x", (void *)conn, conn_err); + + server->gatt->connected(conn, server->user_data); + + (void)bt_mesh_scan_enable(); + + server->discover.uuid = &server->gatt->srv_uuid.uuid; + server->discover.func = discover_func; + server->discover.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; + server->discover.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + server->discover.type = BT_GATT_DISCOVER_PRIMARY; + err = bt_gatt_discover(conn, &server->discover); + if (err) + { + LOG_ERR("Unable discover GATT Services (err %d)", err); + } +} + +static void gatt_disconnected(struct bt_conn *conn, uint8_t reason) +{ + struct bt_conn_info info; + struct bt_mesh_gatt_server *server = get_server(conn); + int err; + + err = bt_conn_get_info(conn, &info); + if (err || info.role != BT_CONN_ROLE_CENTRAL || !server->gatt) + { + return; + } + + server->gatt->disconnected(conn); + + bt_conn_unref(server->conn); + + (void)memset(server, 0, sizeof(struct bt_mesh_gatt_server)); +} + +int bt_mesh_gatt_cli_connect( + const bt_addr_le_t *addr, const struct bt_mesh_gatt_cli *gatt, + void *user_data) +{ + int err; + struct bt_conn *conn; + struct bt_mesh_gatt_server *server; + + /* Avoid interconnection between proxy client and server */ + + conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, addr); + if (conn) + { + bt_conn_unref(conn); + return -EALREADY; + } + + err = bt_mesh_scan_disable(); + if (err) + { + return err; + } + + LOG_DBG("Try to connect services"); + + err = bt_conn_le_create(addr, BT_CONN_LE_CREATE_CONN, + BT_LE_CONN_PARAM_DEFAULT, &conn); + if (err) + { + LOG_ERR("Connection failed (err:%d)", err); + + (void)bt_mesh_scan_enable(); + + return err; + } + + server = get_server(conn); + server->conn = conn; + server->gatt = gatt; + server->user_data = user_data; + + return 0; +} + +static void gatt_advertising_recv(const struct bt_le_scan_recv_info *info, + struct net_buf_simple *buf) +{ + uint16_t uuid; + + if (buf->len < 3) + { + return; + } + + uuid = net_buf_simple_pull_le16(buf); + switch (uuid) + { +#if defined(CONFIG_BT_MESH_PROXY_CLIENT) + case BT_UUID_MESH_PROXY_VAL: + bt_mesh_proxy_cli_adv_recv(info, buf); + break; +#endif +#if defined(CONFIG_BT_MESH_PB_GATT_CLIENT) + case BT_UUID_MESH_PROV_VAL: + bt_mesh_pb_gatt_cli_adv_recv(info, buf); + break; +#endif + + default: + break; + } +} + +static void scan_recv(const struct bt_le_scan_recv_info *info, + struct net_buf_simple *buf) +{ + if (info->adv_type != BT_GAP_ADV_TYPE_ADV_IND) + { + return; + } + + if (!bt_mesh_proxy_has_avail_conn()) + { + return; + } + + while (buf->len > 1) + { + struct net_buf_simple_state state; + uint8_t len; + uint8_t type; + + len = net_buf_simple_pull_u8(buf); + + /* Check for early termination */ + + if (len == 0U) + { + return; + } + + if (len > buf->len) + { + LOG_WRN("AD malformed"); + return; + } + + net_buf_simple_save(buf, &state); + + type = net_buf_simple_pull_u8(buf); + + buf->len = len - 1; + + switch (type) + { + case BT_DATA_SVC_DATA16: + gatt_advertising_recv(info, buf); + break; + default: + break; + } + + net_buf_simple_restore(buf, &state); + net_buf_simple_pull(buf, len); + } +} + +static struct bt_le_scan_cb scan_cb = +{ + .recv = scan_recv, +}; + +void bt_mesh_gatt_client_init(void) +{ + bt_le_scan_cb_register(&scan_cb); +} + +void bt_mesh_gatt_client_deinit(void) +{ + bt_le_scan_cb_unregister(&scan_cb); +} + +BT_CONN_CB_DEFINE(conn_callbacks) = +{ + .connected = gatt_connected, + .disconnected = gatt_disconnected, +}; diff --git a/libs/zblue/mesh/gatt_cli.h b/libs/zblue/mesh/gatt_cli.h new file mode 100644 index 0000000000000..8d8fb639aa31d --- /dev/null +++ b/libs/zblue/mesh/gatt_cli.h @@ -0,0 +1,34 @@ +/**************************************************************************** + * libs/zblue/mesh/gatt_cli.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2021 Xiaomi Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +struct bt_mesh_gatt_cli +{ + struct bt_uuid_16 srv_uuid; + struct bt_uuid_16 data_in_uuid; + struct bt_uuid_16 data_out_uuid; + struct bt_uuid_16 data_out_cccd_uuid; + + void (*connected)(struct bt_conn *conn, void *user_data); + void (*link_open)(struct bt_conn *conn); + void (*disconnected)(struct bt_conn *conn); +}; + +int bt_mesh_gatt_cli_connect(const bt_addr_le_t *addr, + const struct bt_mesh_gatt_cli *gatt, + void *user_data); + +int bt_mesh_gatt_send(struct bt_conn *conn, const void *data, uint16_t len, + bt_gatt_complete_func_t end, void *user_data); + +void bt_mesh_gatt_client_init(void); + +void bt_mesh_gatt_client_deinit(void); diff --git a/libs/zblue/mesh/health_cli.c b/libs/zblue/mesh/health_cli.c new file mode 100644 index 0000000000000..cadd4161f78ac --- /dev/null +++ b/libs/zblue/mesh/health_cli.c @@ -0,0 +1,555 @@ +/**************************************************************************** + * libs/zblue/mesh/health_cli.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "common/bt_str.h" + +#include "net.h" +#include "foundation.h" +#include "msg.h" + +#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_health_cli); + +static int32_t msg_timeout; + +struct health_fault_param +{ + uint16_t cid; + uint8_t *expect_test_id; + uint8_t *test_id; + uint8_t *faults; + size_t *fault_count; +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int health_fault_status(const struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_health_cli *cli = model->rt->user_data; + struct health_fault_param *param; + uint8_t test_id; + uint16_t cid; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + test_id = net_buf_simple_pull_u8(buf); + cid = net_buf_simple_pull_le16(buf); + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_HEALTH_FAULT_STATUS, + ctx->addr, (void **)¶m)) + { + if (param->expect_test_id && (test_id != *param->expect_test_id)) + { + goto done; + } + + if (cid != param->cid) + { + goto done; + } + + if (param->test_id) + { + *param->test_id = test_id; + } + + if (param->faults && param->fault_count) + { + if (buf->len > *param->fault_count) + { + LOG_WRN("Got more faults than there's space for"); + } + else + { + *param->fault_count = buf->len; + } + + memcpy(param->faults, buf->data, *param->fault_count); + } + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + +done: + if (cli->fault_status) + { + cli->fault_status(cli, ctx->addr, test_id, cid, buf->data, buf->len); + } + + return 0; +} + +static int health_current_status(const struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_health_cli *cli = model->rt->user_data; + uint8_t test_id; + uint16_t cid; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + test_id = net_buf_simple_pull_u8(buf); + cid = net_buf_simple_pull_le16(buf); + + LOG_DBG("Test ID 0x%02x Company ID 0x%04x Fault Count %u", test_id, cid, + buf->len); + + if (cli->current_status) + { + cli->current_status(cli, ctx->addr, test_id, cid, buf->data, buf->len); + } + + return 0; +} + +struct health_period_param +{ + uint8_t *divisor; +}; + +static int health_period_status(const struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_health_cli *cli = model->rt->user_data; + struct health_period_param *param; + uint8_t divisor; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + divisor = net_buf_simple_pull_u8(buf); + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_HEALTH_PERIOD_STATUS, + ctx->addr, (void **)¶m)) + { + if (param->divisor) + { + *param->divisor = divisor; + } + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + + if (cli->period_status) + { + cli->period_status(cli, ctx->addr, divisor); + } + + return 0; +} + +struct health_attention_param +{ + uint8_t *attention; +}; + +static int health_attention_status(const struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_health_cli *cli = model->rt->user_data; + struct health_attention_param *param; + uint8_t attention; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + attention = net_buf_simple_pull_u8(buf); + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, + OP_ATTENTION_STATUS, + ctx->addr, + + (void **)¶m)) + { + if (param->attention) + { + *param->attention = attention; + } + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + + if (cli->attention_status) + { + cli->attention_status(cli, ctx->addr, attention); + } + + return 0; +} + +const struct bt_mesh_model_op bt_mesh_health_cli_op[] = +{ + { + OP_HEALTH_FAULT_STATUS, + BT_MESH_LEN_MIN(3), + health_fault_status + }, + + { + OP_HEALTH_CURRENT_STATUS, + BT_MESH_LEN_MIN(3), + health_current_status + }, + + { + OP_HEALTH_PERIOD_STATUS, + BT_MESH_LEN_EXACT(1), + health_period_status + }, + + { + OP_ATTENTION_STATUS, + BT_MESH_LEN_EXACT(1), + health_attention_status + }, + + BT_MESH_MODEL_OP_END, +}; + +int bt_mesh_health_cli_attention_get(struct bt_mesh_health_cli *cli, + struct bt_mesh_msg_ctx *ctx, + uint8_t *attention) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_ATTENTION_GET, 0); + struct health_attention_param param = + { + .attention = attention, + }; + + bt_mesh_model_msg_init(&msg, OP_ATTENTION_GET); + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_ATTENTION_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + return bt_mesh_msg_ackd_send(cli->model, ctx, &msg, + attention ? &rsp : NULL); +} + +int bt_mesh_health_cli_attention_set(struct bt_mesh_health_cli *cli, + struct bt_mesh_msg_ctx *ctx, + uint8_t attention, + uint8_t *updated_attention) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_ATTENTION_SET, 1); + struct health_attention_param param = + { + .attention = updated_attention, + }; + + bt_mesh_model_msg_init(&msg, OP_ATTENTION_SET); + net_buf_simple_add_u8(&msg, attention); + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_ATTENTION_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + return bt_mesh_msg_ackd_send(cli->model, ctx, &msg, + updated_attention ? &rsp : NULL); +} + +int bt_mesh_health_cli_attention_set_unack(struct bt_mesh_health_cli *cli, + struct bt_mesh_msg_ctx *ctx, + uint8_t attention) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_ATTENTION_SET_UNREL, 1); + + bt_mesh_model_msg_init(&msg, OP_ATTENTION_SET_UNREL); + net_buf_simple_add_u8(&msg, attention); + + return bt_mesh_msg_send(cli->model, ctx, &msg); +} + +int bt_mesh_health_cli_period_get(struct bt_mesh_health_cli *cli, + struct bt_mesh_msg_ctx *ctx, + uint8_t *divisor) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_HEALTH_PERIOD_GET, 0); + struct health_period_param param = + { + .divisor = divisor, + }; + + bt_mesh_model_msg_init(&msg, OP_HEALTH_PERIOD_GET); + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_HEALTH_PERIOD_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + return bt_mesh_msg_ackd_send(cli->model, ctx, &msg, divisor ? &rsp : NULL); +} + +int bt_mesh_health_cli_period_set(struct bt_mesh_health_cli *cli, + struct bt_mesh_msg_ctx *ctx, + uint8_t divisor, + uint8_t *updated_divisor) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_HEALTH_PERIOD_SET, 1); + struct health_period_param param = + { + .divisor = updated_divisor, + }; + + bt_mesh_model_msg_init(&msg, OP_HEALTH_PERIOD_SET); + net_buf_simple_add_u8(&msg, divisor); + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_HEALTH_PERIOD_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + return bt_mesh_msg_ackd_send(cli->model, ctx, &msg, + updated_divisor ? &rsp : NULL); +} + +int bt_mesh_health_cli_period_set_unack(struct bt_mesh_health_cli *cli, + struct bt_mesh_msg_ctx *ctx, + uint8_t divisor) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_HEALTH_PERIOD_SET_UNREL, 1); + + bt_mesh_model_msg_init(&msg, OP_HEALTH_PERIOD_SET_UNREL); + net_buf_simple_add_u8(&msg, divisor); + + return bt_mesh_msg_send(cli->model, ctx, &msg); +} + +int bt_mesh_health_cli_fault_test(struct bt_mesh_health_cli *cli, + struct bt_mesh_msg_ctx *ctx, + uint16_t cid, + uint8_t test_id, + uint8_t *faults, + size_t *fault_count) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_HEALTH_FAULT_TEST, 3); + struct health_fault_param param = + { + .cid = cid, + .expect_test_id = &test_id, + .faults = faults, + .fault_count = fault_count, + }; + + bt_mesh_model_msg_init(&msg, OP_HEALTH_FAULT_TEST); + net_buf_simple_add_u8(&msg, test_id); + net_buf_simple_add_le16(&msg, cid); + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_HEALTH_FAULT_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + return bt_mesh_msg_ackd_send(cli->model, ctx, &msg, &rsp); +} + +int bt_mesh_health_cli_fault_test_unack(struct bt_mesh_health_cli *cli, + struct bt_mesh_msg_ctx *ctx, + uint16_t cid, + uint8_t test_id) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_HEALTH_FAULT_TEST_UNREL, 3); + + bt_mesh_model_msg_init(&msg, OP_HEALTH_FAULT_TEST_UNREL); + net_buf_simple_add_u8(&msg, test_id); + net_buf_simple_add_le16(&msg, cid); + + return bt_mesh_msg_send(cli->model, ctx, &msg); +} + +int bt_mesh_health_cli_fault_clear(struct bt_mesh_health_cli *cli, + struct bt_mesh_msg_ctx *ctx, + uint16_t cid, + uint8_t *test_id, + uint8_t *faults, + size_t *fault_count) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_HEALTH_FAULT_CLEAR, 2); + struct health_fault_param param = + { + .cid = cid, + .test_id = test_id, + .faults = faults, + .fault_count = fault_count, + }; + + bt_mesh_model_msg_init(&msg, OP_HEALTH_FAULT_CLEAR); + net_buf_simple_add_le16(&msg, cid); + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_HEALTH_FAULT_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + return bt_mesh_msg_ackd_send(cli->model, ctx, &msg, + (!test_id && (!faults + || !fault_count)) ? NULL : &rsp); +} + +int bt_mesh_health_cli_fault_clear_unack(struct bt_mesh_health_cli *cli, + struct bt_mesh_msg_ctx *ctx, + uint16_t cid) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_HEALTH_FAULT_CLEAR_UNREL, 2); + + bt_mesh_model_msg_init(&msg, OP_HEALTH_FAULT_CLEAR_UNREL); + net_buf_simple_add_le16(&msg, cid); + + return bt_mesh_msg_send(cli->model, ctx, &msg); +} + +int bt_mesh_health_cli_fault_get(struct bt_mesh_health_cli *cli, + struct bt_mesh_msg_ctx *ctx, + uint16_t cid, + uint8_t *test_id, + uint8_t *faults, + size_t *fault_count) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_HEALTH_FAULT_GET, 2); + struct health_fault_param param = + { + .cid = cid, + .test_id = test_id, + .faults = faults, + .fault_count = fault_count, + }; + + bt_mesh_model_msg_init(&msg, OP_HEALTH_FAULT_GET); + net_buf_simple_add_le16(&msg, cid); + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_HEALTH_FAULT_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + return bt_mesh_msg_ackd_send(cli->model, ctx, &msg, + (!test_id && (!faults + || !fault_count)) ? NULL : &rsp); +} + +int32_t bt_mesh_health_cli_timeout_get(void) +{ + return msg_timeout; +} + +void bt_mesh_health_cli_timeout_set(int32_t timeout) +{ + msg_timeout = timeout; +} + +static int update_callback(const struct bt_mesh_model *model) +{ + struct bt_mesh_health_cli *cli = model->rt->user_data; + + if (cli->update) + { + return cli->update(cli, &cli->pub_buf); + } + + return -EINVAL; +} + +static int health_cli_init(const struct bt_mesh_model *model) +{ + struct bt_mesh_health_cli *cli = model->rt->user_data; + + LOG_DBG("primary %u", bt_mesh_model_in_primary(model)); + + if (!cli) + { + LOG_ERR("No Health Client context provided"); + return -EINVAL; + } + + cli->model = model; + msg_timeout = CONFIG_BT_MESH_HEALTH_CLI_TIMEOUT; + + cli->pub.msg = &cli->pub_buf; + cli->pub.update = update_callback; + net_buf_simple_init_with_data(&cli->pub_buf, cli->pub_data, + sizeof(cli->pub_data)); + + bt_mesh_msg_ack_ctx_init(&cli->ack_ctx); + return 0; +} + +static void health_cli_reset(const struct bt_mesh_model *model) +{ + struct bt_mesh_health_cli *cli = model->rt->user_data; + + net_buf_simple_reset(cli->pub.msg); +} + +const struct bt_mesh_model_cb bt_mesh_health_cli_cb = +{ + .init = health_cli_init, + .reset = health_cli_reset, +}; diff --git a/libs/zblue/mesh/health_srv.c b/libs/zblue/mesh/health_srv.c new file mode 100644 index 0000000000000..c4da7f66eca83 --- /dev/null +++ b/libs/zblue/mesh/health_srv.c @@ -0,0 +1,589 @@ +/**************************************************************************** + * libs/zblue/mesh/health_srv.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "mesh.h" +#include "net.h" +#include "transport.h" +#include "access.h" +#include "foundation.h" + +#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_health_srv); + +#define HEALTH_TEST_STANDARD 0x00 + +/* Health Server context of the primary element */ + +struct bt_mesh_health_srv *health_srv; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void health_get_registered( + const struct bt_mesh_model *mod, uint16_t company_id, + struct net_buf_simple *msg) +{ + struct bt_mesh_health_srv *srv = mod->rt->user_data; + uint8_t *test_id; + + LOG_DBG("Company ID 0x%04x", company_id); + + bt_mesh_model_msg_init(msg, OP_HEALTH_FAULT_STATUS); + + test_id = net_buf_simple_add(msg, 1); + net_buf_simple_add_le16(msg, company_id); + + if (srv->cb && srv->cb->fault_get_reg) + { + uint8_t fault_count = net_buf_simple_tailroom(msg) - 4; + int err; + + err = srv->cb->fault_get_reg(mod, company_id, test_id, + net_buf_simple_tail(msg), + &fault_count); + if (err) + { + LOG_ERR("Failed to get faults (err %d)", err); + *test_id = HEALTH_TEST_STANDARD; + } + else + { + net_buf_simple_add(msg, fault_count); + } + } + else + { + LOG_WRN("No callback for getting faults"); + *test_id = HEALTH_TEST_STANDARD; + } +} + +static size_t health_get_current(const struct bt_mesh_model *mod, + struct net_buf_simple *msg) +{ + struct bt_mesh_health_srv *srv = mod->rt->user_data; + const struct bt_mesh_comp *comp; + uint8_t *test_id; + uint8_t *company_ptr; + uint16_t company_id; + uint8_t fault_count; + + bt_mesh_model_msg_init(msg, OP_HEALTH_CURRENT_STATUS); + + test_id = net_buf_simple_add(msg, 1); + company_ptr = net_buf_simple_add(msg, sizeof(company_id)); + comp = bt_mesh_comp_get(); + + if (srv->cb && srv->cb->fault_get_cur) + { + fault_count = net_buf_simple_tailroom(msg); + int err; + + err = srv->cb->fault_get_cur(mod, test_id, &company_id, + net_buf_simple_tail(msg), + &fault_count); + if (err) + { + LOG_ERR("Failed to get faults (err %d)", err); + sys_put_le16(comp->cid, company_ptr); + *test_id = HEALTH_TEST_STANDARD; + fault_count = 0U; + } + else + { + sys_put_le16(company_id, company_ptr); + net_buf_simple_add(msg, fault_count); + } + } + else + { + LOG_WRN("No callback for getting faults"); + sys_put_le16(comp->cid, company_ptr); + *test_id = HEALTH_TEST_STANDARD; + fault_count = 0U; + } + + return fault_count; +} + +static int health_fault_get( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + NET_BUF_SIMPLE_DEFINE(sdu, BT_MESH_TX_SDU_MAX); + uint16_t company_id; + + company_id = net_buf_simple_pull_le16(buf); + + LOG_DBG("company_id 0x%04x", company_id); + + health_get_registered(model, company_id, &sdu); + + if (bt_mesh_model_send(model, ctx, &sdu, NULL, NULL)) + { + LOG_ERR("Unable to send Health Current Status response"); + } + + return 0; +} + +static int health_fault_clear_unrel( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_health_srv *srv = model->rt->user_data; + uint16_t company_id; + + company_id = net_buf_simple_pull_le16(buf); + + LOG_DBG("company_id 0x%04x", company_id); + + if (srv->cb && srv->cb->fault_clear) + { + return srv->cb->fault_clear(model, company_id); + } + + return 0; +} + +static int health_fault_clear( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + NET_BUF_SIMPLE_DEFINE(sdu, BT_MESH_TX_SDU_MAX); + struct bt_mesh_health_srv *srv = model->rt->user_data; + uint16_t company_id; + + company_id = net_buf_simple_pull_le16(buf); + + LOG_DBG("company_id 0x%04x", company_id); + + if (srv->cb && srv->cb->fault_clear) + { + int err; + + err = srv->cb->fault_clear(model, company_id); + if (err) + { + return err; + } + } + + health_get_registered(model, company_id, &sdu); + + if (bt_mesh_model_send(model, ctx, &sdu, NULL, NULL)) + { + LOG_ERR("Unable to send Health Current Status response"); + } + + return 0; +} + +static int health_fault_test_unrel( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_health_srv *srv = model->rt->user_data; + uint16_t company_id; + uint8_t test_id; + + test_id = net_buf_simple_pull_u8(buf); + company_id = net_buf_simple_pull_le16(buf); + + LOG_DBG("test 0x%02x company 0x%04x", test_id, company_id); + + if (srv->cb && srv->cb->fault_test) + { + return srv->cb->fault_test(model, test_id, company_id); + } + + return 0; +} + +static int health_fault_test( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + NET_BUF_SIMPLE_DEFINE(sdu, BT_MESH_TX_SDU_MAX); + struct bt_mesh_health_srv *srv = model->rt->user_data; + uint16_t company_id; + uint8_t test_id; + + LOG_DBG(""); + + test_id = net_buf_simple_pull_u8(buf); + company_id = net_buf_simple_pull_le16(buf); + + LOG_DBG("test 0x%02x company 0x%04x", test_id, company_id); + + if (srv->cb && srv->cb->fault_test) + { + int err; + + err = srv->cb->fault_test(model, test_id, company_id); + if (err) + { + LOG_WRN("Running fault test failed with err %d", err); + return err; + } + } + + health_get_registered(model, company_id, &sdu); + + if (bt_mesh_model_send(model, ctx, &sdu, NULL, NULL)) + { + LOG_ERR("Unable to send Health Current Status response"); + } + + return 0; +} + +static int send_attention_status(const struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx) +{ + /* Needed size: opcode (2 bytes) + msg + MIC */ + + BT_MESH_MODEL_BUF_DEFINE(msg, OP_ATTENTION_STATUS, 1); + struct bt_mesh_health_srv *srv = model->rt->user_data; + uint8_t time; + + time = TICK2MSEC(k_work_delayable_remaining_get(&srv->attn_timer)) / 1000U; + LOG_DBG("%u second%s", time, (time == 1U) ? "" : "s"); + + bt_mesh_model_msg_init(&msg, OP_ATTENTION_STATUS); + + net_buf_simple_add_u8(&msg, time); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send Attention Status"); + } + + return 0; +} + +static int attention_get( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + LOG_DBG(""); + + return send_attention_status(model, ctx); +} + +static int attention_set_unrel( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint8_t time; + + time = net_buf_simple_pull_u8(buf); + + LOG_DBG("%u second%s", time, (time == 1U) ? "" : "s"); + + bt_mesh_attention(model, time); + + return 0; +} + +static int attention_set( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + int err; + + LOG_DBG(""); + + err = attention_set_unrel(model, ctx, buf); + if (err) + { + return err; + } + + return send_attention_status(model, ctx); +} + +static int send_health_period_status(const struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx) +{ + /* Needed size: opcode (2 bytes) + msg + MIC */ + + BT_MESH_MODEL_BUF_DEFINE(msg, OP_HEALTH_PERIOD_STATUS, 1); + + bt_mesh_model_msg_init(&msg, OP_HEALTH_PERIOD_STATUS); + + net_buf_simple_add_u8(&msg, model->pub->period_div); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send Health Period Status"); + } + + return 0; +} + +static int health_period_get( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + LOG_DBG(""); + + return send_health_period_status(model, ctx); +} + +static int health_period_set_unrel( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint8_t period; + + period = net_buf_simple_pull_u8(buf); + if (period > 15) + { + LOG_WRN("Prohibited period value %u", period); + return -EINVAL; + } + + LOG_DBG("period %u", period); + + model->pub->period_div = period; + + return 0; +} + +static int health_period_set( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + int err; + + LOG_DBG(""); + + err = health_period_set_unrel(model, ctx, buf); + if (err) + { + return err; + } + + return send_health_period_status(model, ctx); +} + +const struct bt_mesh_model_op bt_mesh_health_srv_op[] = +{ + { + OP_HEALTH_FAULT_GET, + BT_MESH_LEN_EXACT(2), + health_fault_get + }, + + { + OP_HEALTH_FAULT_CLEAR, + BT_MESH_LEN_EXACT(2), + health_fault_clear + }, + + { + OP_HEALTH_FAULT_CLEAR_UNREL, + BT_MESH_LEN_EXACT(2), + health_fault_clear_unrel + }, + + { + OP_HEALTH_FAULT_TEST, + BT_MESH_LEN_EXACT(3), + health_fault_test + }, + + { + OP_HEALTH_FAULT_TEST_UNREL, + BT_MESH_LEN_EXACT(3), + health_fault_test_unrel + }, + + { + OP_HEALTH_PERIOD_GET, + BT_MESH_LEN_EXACT(0), + health_period_get + }, + + { + OP_HEALTH_PERIOD_SET, + BT_MESH_LEN_EXACT(1), + health_period_set + }, + + { + OP_HEALTH_PERIOD_SET_UNREL, + BT_MESH_LEN_EXACT(1), + health_period_set_unrel + }, + + { + OP_ATTENTION_GET, + BT_MESH_LEN_EXACT(0), + attention_get + }, + + { + OP_ATTENTION_SET, + BT_MESH_LEN_EXACT(1), + attention_set + }, + + { + OP_ATTENTION_SET_UNREL, + BT_MESH_LEN_EXACT(1), + attention_set_unrel + }, + + BT_MESH_MODEL_OP_END, +}; + +static int health_pub_update(const struct bt_mesh_model *mod) +{ + struct bt_mesh_model_pub *pub = mod->pub; + size_t count; + + LOG_DBG(""); + + count = health_get_current(mod, pub->msg); + if (count) + { + pub->fast_period = 1U; + } + else + { + pub->fast_period = 0U; + } + + return 0; +} + +int bt_mesh_health_srv_fault_update(const struct bt_mesh_elem *elem) +{ + const struct bt_mesh_model *mod; + + mod = bt_mesh_model_find(elem, BT_MESH_MODEL_ID_HEALTH_SRV); + if (!mod) + { + return -EINVAL; + } + + /* Let periodic publishing, if enabled, take care of sending the + * Health Current Status. + */ + + if (bt_mesh_model_pub_period_get(mod) > 0) + { + return 0; + } + + health_pub_update(mod); + + return bt_mesh_model_publish(mod); +} + +static void attention_off(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_mesh_health_srv *srv = CONTAINER_OF(dwork, + struct bt_mesh_health_srv, attn_timer); + + LOG_DBG(""); + + if (srv->cb && srv->cb->attn_off) + { + srv->cb->attn_off(srv->model); + } +} + +static int health_srv_init(const struct bt_mesh_model *model) +{ + struct bt_mesh_health_srv *srv = model->rt->user_data; + + if (!srv) + { + LOG_ERR("No Health Server context provided"); + return -EINVAL; + } + + if (!model->pub) + { + LOG_ERR("Health Server has no publication support"); + return -EINVAL; + } + + model->pub->update = health_pub_update; + + k_work_init_delayable(&srv->attn_timer, attention_off); + + srv->model = model; + + if (bt_mesh_model_in_primary(model)) + { + health_srv = srv; + } + + return 0; +} + +const struct bt_mesh_model_cb bt_mesh_health_srv_cb = +{ + .init = health_srv_init, +}; + +void bt_mesh_attention(const struct bt_mesh_model *model, uint8_t time) +{ + struct bt_mesh_health_srv *srv; + + if (!model) + { + srv = health_srv; + if (!srv) + { + LOG_WRN("No Health Server available"); + return; + } + + model = srv->model; + } + else + { + srv = model->rt->user_data; + } + + if ((time > 0) && srv->cb && srv->cb->attn_on) + { + srv->cb->attn_on(model); + } + + k_work_reschedule(&srv->attn_timer, SEC2TICK(time)); +} diff --git a/libs/zblue/mesh/heartbeat.c b/libs/zblue/mesh/heartbeat.c new file mode 100644 index 0000000000000..eede81ef2fc82 --- /dev/null +++ b/libs/zblue/mesh/heartbeat.c @@ -0,0 +1,558 @@ +/**************************************************************************** + * libs/zblue/mesh/heartbeat.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include "net.h" +#include "rpl.h" +#include "access.h" +#include "lpn.h" +#include "settings.h" +#include "mesh.h" +#include "transport.h" +#include "heartbeat.h" +#include "foundation.h" + +#define LOG_LEVEL CONFIG_BT_MESH_TRANS_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_hb); + +/* Heartbeat Publication information for persistent storage. */ + +struct hb_pub_val +{ + uint16_t dst; + uint8_t period; + uint8_t ttl; + uint16_t feat; + uint16_t net_idx : 12; + uint16_t indefinite : 1; +}; + +static struct bt_mesh_hb_pub pub; +static struct bt_mesh_hb_sub sub; +static struct k_work_delayable sub_timer; +static struct k_work_delayable pub_timer; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void notify_pub_sent(void) +{ + STRUCT_SECTION_FOREACH(bt_mesh_hb_cb, cb) + { + if (cb->pub_sent) + { + cb->pub_sent(&pub); + } + } +} + +static int64_t sub_remaining(void) +{ + if (sub.dst == BT_MESH_ADDR_UNASSIGNED) + { + return 0U; + } + + uint32_t rem_ms = TICK2MSEC(k_work_delayable_remaining_get(&sub_timer)); + + return rem_ms / MSEC_PER_SEC; +} + +static void hb_publish_end_cb(int err, void *cb_data) +{ + if (pub.period && pub.count > 1) + { + k_work_reschedule(&pub_timer, SEC2TICK(pub.period)); + } + + if (pub.count != 0xffff) + { + pub.count--; + } + + if (!err) + { + notify_pub_sent(); + } +} + +static void notify_recv(uint8_t hops, uint16_t feat) +{ + sub.remaining = sub_remaining(); + + STRUCT_SECTION_FOREACH(bt_mesh_hb_cb, cb) + { + if (cb->recv) + { + cb->recv(&sub, hops, feat); + } + } +} + +static void notify_sub_end(void) +{ + sub.remaining = 0; + + STRUCT_SECTION_FOREACH(bt_mesh_hb_cb, cb) + { + if (cb->sub_end) + { + cb->sub_end(&sub); + } + } +} + +static void sub_end(struct k_work *work) +{ + notify_sub_end(); +} + +static int heartbeat_send(const struct bt_mesh_send_cb *cb, void *cb_data) +{ + uint16_t feat = 0U; + struct __packed + { + uint8_t init_ttl; + uint16_t feat; + } hb; + + struct bt_mesh_msg_ctx ctx = + { + .net_idx = pub.net_idx, + .app_idx = BT_MESH_KEY_UNUSED, + .addr = pub.dst, + .send_ttl = pub.ttl, + }; + + struct bt_mesh_net_tx tx = + { + .sub = bt_mesh_subnet_get(pub.net_idx), + .ctx = &ctx, + .src = bt_mesh_primary_addr(), + .xmit = bt_mesh_net_transmit_get(), + }; + + /* Do nothing if heartbeat publication is not enabled or the subnet is + * removed. + */ + + if (!tx.sub || pub.dst == BT_MESH_ADDR_UNASSIGNED) + { + return 0; + } + + hb.init_ttl = pub.ttl; + + if (bt_mesh_relay_get() == BT_MESH_RELAY_ENABLED) + { + feat |= BT_MESH_FEAT_RELAY; + } + + if (bt_mesh_gatt_proxy_get() == BT_MESH_GATT_PROXY_ENABLED) + { + feat |= BT_MESH_FEAT_PROXY; + } + + if (bt_mesh_friend_get() == BT_MESH_FRIEND_ENABLED) + { + feat |= BT_MESH_FEAT_FRIEND; + } + + if (bt_mesh_lpn_established()) + { + feat |= BT_MESH_FEAT_LOW_POWER; + } + + hb.feat = sys_cpu_to_be16(feat); + + LOG_DBG("InitTTL %u feat 0x%04x", pub.ttl, feat); + + return bt_mesh_ctl_send(&tx, TRANS_CTL_OP_HEARTBEAT, &hb, sizeof(hb), cb, + cb_data); +} + +static void hb_publish_start_cb(uint16_t duration, int err, void *cb_data) +{ + if (err) + { + hb_publish_end_cb(err, cb_data); + } +} + +static void hb_publish(struct k_work *work) +{ + static const struct bt_mesh_send_cb publish_cb = + { + .start = hb_publish_start_cb, + .end = hb_publish_end_cb, + }; + + struct bt_mesh_subnet *subnet; + int err; + + LOG_DBG("hb_pub.count: %u", pub.count); + + /* Fast exit if disabled or expired */ + + if (pub.period == 0U || pub.count == 0U) + { + return; + } + + subnet = bt_mesh_subnet_get(pub.net_idx); + if (!subnet) + { + LOG_ERR("No matching subnet for idx 0x%02x", pub.net_idx); + pub.dst = BT_MESH_ADDR_UNASSIGNED; + return; + } + + err = heartbeat_send(&publish_cb, NULL); + if (err) + { + hb_publish_end_cb(err, NULL); + } +} + +int bt_mesh_hb_recv(struct bt_mesh_net_rx *rx, struct net_buf_simple *buf) +{ + uint8_t init_ttl; + uint8_t hops; + uint16_t feat; + + if (buf->len < 3) + { + LOG_ERR("Too short heartbeat message"); + return -EINVAL; + } + + init_ttl = (net_buf_simple_pull_u8(buf) & 0x7f); + feat = net_buf_simple_pull_be16(buf); + + hops = (init_ttl - rx->ctx.recv_ttl + 1); + + if (rx->ctx.addr != sub.src || rx->ctx.recv_dst != sub.dst) + { + LOG_DBG("No subscription for received heartbeat"); + return 0; + } + + if (!k_work_delayable_is_pending(&sub_timer)) + { + LOG_DBG("Heartbeat subscription inactive"); + return 0; + } + + sub.min_hops = MIN(sub.min_hops, hops); + sub.max_hops = MAX(sub.max_hops, hops); + + if (sub.count < 0xffff) + { + sub.count++; + } + + LOG_DBG("src 0x%04x TTL %u InitTTL %u (%u hop%s) feat " + "0x%04x", rx->ctx.addr, + rx->ctx.recv_ttl, + init_ttl, hops, (hops == 1U) ? "" : "s", feat); + + notify_recv(hops, feat); + + return 0; +} + +static void pub_disable(void) +{ + LOG_DBG(""); + + pub.dst = BT_MESH_ADDR_UNASSIGNED; + pub.count = 0U; + pub.period = 0U; + pub.ttl = 0U; + pub.feat = 0U; + pub.net_idx = 0U; + + /* Try to cancel, but it's OK if this still runs (or is + * running) as the handler will be a no-op if it hasn't already checked + * period for being non-zero. + */ + + (void)k_work_cancel_delayable(&pub_timer); +} + +uint8_t bt_mesh_hb_pub_set(struct bt_mesh_hb_pub *new_pub) +{ + if (!new_pub || new_pub->dst == BT_MESH_ADDR_UNASSIGNED) + { + pub_disable(); + + if (IS_ENABLED(CONFIG_BT_SETTINGS) && bt_mesh_is_provisioned()) + { + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_HB_PUB_PENDING); + } + + return STATUS_SUCCESS; + } + + if (!bt_mesh_subnet_get(new_pub->net_idx)) + { + LOG_ERR("Unknown NetKey 0x%04x", new_pub->net_idx); + return STATUS_INVALID_NETKEY; + } + + new_pub->feat &= BT_MESH_FEAT_SUPPORTED; + pub = *new_pub; + + if (!bt_mesh_is_provisioned()) + { + return STATUS_SUCCESS; + } + + /* The first Heartbeat message shall be published as soon as possible + * after the Heartbeat Publication Period state has been configured for + * periodic publishing. + * + * If the new configuration disables publishing this flushes the work item. + */ + + k_work_reschedule(&pub_timer, 0); + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_HB_PUB_PENDING); + } + + return STATUS_SUCCESS; +} + +void bt_mesh_hb_pub_get(struct bt_mesh_hb_pub *get) +{ + *get = pub; +} + +uint8_t bt_mesh_hb_sub_set(uint16_t src, uint16_t dst, uint32_t period) +{ + if (src != BT_MESH_ADDR_UNASSIGNED && !BT_MESH_ADDR_IS_UNICAST(src)) + { + LOG_WRN("Prohibited source address"); + return STATUS_INVALID_ADDRESS; + } + + if (BT_MESH_ADDR_IS_VIRTUAL(dst) || BT_MESH_ADDR_IS_RFU(dst) || + (BT_MESH_ADDR_IS_UNICAST(dst) && dst != bt_mesh_primary_addr())) + { + LOG_WRN("Prohibited destination address"); + return STATUS_INVALID_ADDRESS; + } + + if (period > (1U << 16)) + { + LOG_WRN("Prohibited subscription period %u s", period); + return STATUS_CANNOT_SET; + } + + /* Only an explicit address change to unassigned should trigger clearing + * of the values according to MESH/NODE/CFG/HBS/BV-02-C. + */ + + if (src == BT_MESH_ADDR_UNASSIGNED || dst == BT_MESH_ADDR_UNASSIGNED) + { + sub.src = BT_MESH_ADDR_UNASSIGNED; + sub.dst = BT_MESH_ADDR_UNASSIGNED; + sub.min_hops = 0U; + sub.max_hops = 0U; + sub.count = 0U; + sub.period = 0U; + } + else if (period) + { + sub.src = src; + sub.dst = dst; + sub.min_hops = BT_MESH_TTL_MAX; + sub.max_hops = 0U; + sub.count = 0U; + sub.period = period; + } + else + { + /* Clearing the period should stop heartbeat subscription + * without clearing the parameters, so we can still read them. + */ + + sub.period = 0U; + } + + /* Start the timer, which notifies immediately if the new + * configuration disables the subscription. + */ + + k_work_reschedule(&sub_timer, SEC2TICK(sub.period)); + + return STATUS_SUCCESS; +} + +void bt_mesh_hb_sub_reset_count(void) +{ + sub.count = 0; +} + +void bt_mesh_hb_sub_get(struct bt_mesh_hb_sub *get) +{ + *get = sub; + get->remaining = sub_remaining(); +} + +static void hb_unsolicited_pub_end_cb(int err, void *cb_data) +{ + if (!err) + { + notify_pub_sent(); + } +} + +void bt_mesh_hb_feature_changed(uint16_t features) +{ + static const struct bt_mesh_send_cb pub_cb = + { + .end = hb_unsolicited_pub_end_cb, + }; + + if (pub.dst == BT_MESH_ADDR_UNASSIGNED) + { + return; + } + + if (!(pub.feat & features)) + { + return; + } + + heartbeat_send(&pub_cb, NULL); +} + +void bt_mesh_hb_init(void) +{ + pub.net_idx = BT_MESH_KEY_UNUSED; + k_work_init_delayable(&pub_timer, hb_publish); + k_work_init_delayable(&sub_timer, sub_end); +} + +void bt_mesh_hb_start(void) +{ + if (pub.count && pub.period) + { + LOG_DBG("Starting heartbeat publication"); + k_work_reschedule(&pub_timer, 0); + } +} + +void bt_mesh_hb_suspend(void) +{ + /* Best-effort suspend. This cannot guarantee that an + * in-progress publish will not complete. + */ + + (void)k_work_cancel_delayable(&pub_timer); +} + +void bt_mesh_hb_resume(void) +{ + if (pub.period && pub.count) + { + LOG_DBG("Starting heartbeat publication"); + k_work_reschedule(&pub_timer, 0); + } +} + +static int hb_pub_set(const char *name, + size_t len_rd, settings_read_cb read_cb, void *cb_arg) +{ + struct bt_mesh_hb_pub hb_pub; + struct hb_pub_val hb_val; + int err; + + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return 0; + } + + err = bt_mesh_settings_set(read_cb, cb_arg, &hb_val, sizeof(hb_val)); + if (err) + { + LOG_ERR("Failed to set \'hb_val\'"); + return err; + } + + hb_pub.dst = hb_val.dst; + hb_pub.period = bt_mesh_hb_pwr2(hb_val.period); + hb_pub.ttl = hb_val.ttl; + hb_pub.feat = hb_val.feat; + hb_pub.net_idx = hb_val.net_idx; + + if (hb_val.indefinite) + { + hb_pub.count = 0xffff; + } + else + { + hb_pub.count = 0U; + } + + (void)bt_mesh_hb_pub_set(&hb_pub); + + LOG_DBG("Restored heartbeat publication"); + + return 0; +} + +BT_MESH_SETTINGS_DEFINE(pub, "HBPub", hb_pub_set); + +void bt_mesh_hb_pub_pending_store(void) +{ + struct bt_mesh_hb_pub hb_pub; + struct hb_pub_val val; + int err; + + bt_mesh_hb_pub_get(&hb_pub); + if (hb_pub.dst == BT_MESH_ADDR_UNASSIGNED) + { + err = settings_delete("bt/mesh/HBPub"); + } + else + { + val.indefinite = (hb_pub.count == 0xffff); + val.dst = hb_pub.dst; + val.period = bt_mesh_hb_log(hb_pub.period); + val.ttl = hb_pub.ttl; + val.feat = hb_pub.feat; + val.net_idx = hb_pub.net_idx; + + err = settings_save_one("bt/mesh/HBPub", &val, sizeof(val)); + } + + if (err) + { + LOG_ERR("Failed to store Heartbeat Publication"); + } + else + { + LOG_DBG("Stored Heartbeat Publication"); + } +} diff --git a/libs/zblue/mesh/heartbeat.h b/libs/zblue/mesh/heartbeat.h new file mode 100644 index 0000000000000..e023e52ba5a40 --- /dev/null +++ b/libs/zblue/mesh/heartbeat.h @@ -0,0 +1,52 @@ +/**************************************************************************** + * libs/zblue/mesh/heartbeat.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +static inline uint16_t bt_mesh_hb_pwr2(uint8_t val) +{ + if (!val) + { + return 0x0000; + } + else if (val == 0xff) + { + return 0xffff; + } + else + { + return (1 << (val - 1)); + } +} + +static inline uint8_t bt_mesh_hb_log(uint32_t val) +{ + if (!val) + { + return 0x00; + } + else + { + return 32 - __builtin_clz(val); + } +} + +void bt_mesh_hb_init(void); +void bt_mesh_hb_start(void); +void bt_mesh_hb_suspend(void); +void bt_mesh_hb_resume(void); + +int bt_mesh_hb_recv(struct bt_mesh_net_rx *rx, struct net_buf_simple *buf); +void bt_mesh_hb_feature_changed(uint16_t features); + +uint8_t bt_mesh_hb_pub_set(struct bt_mesh_hb_pub *hb_pub); +uint8_t bt_mesh_hb_sub_set(uint16_t src, uint16_t dst, uint32_t period); +void bt_mesh_hb_sub_reset_count(void); +void bt_mesh_hb_pub_pending_store(void); diff --git a/libs/zblue/mesh/keys.h b/libs/zblue/mesh/keys.h new file mode 100644 index 0000000000000..f8f34e6379b25 --- /dev/null +++ b/libs/zblue/mesh/keys.h @@ -0,0 +1,41 @@ +/**************************************************************************** + * libs/zblue/mesh/keys.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define BT_MESH_KEY_PRIMARY 0x0000 + +enum bt_mesh_key_evt +{ + BT_MESH_KEY_ADDED, /* New key added */ + BT_MESH_KEY_DELETED, /* Existing key deleted */ + BT_MESH_KEY_UPDATED, /* KR phase 1, second key added */ + BT_MESH_KEY_SWAPPED, /* KR phase 2, now sending on second key */ + BT_MESH_KEY_REVOKED, /* KR phase 3, old key removed */ +}; + +enum bt_mesh_key_type +{ + BT_MESH_KEY_TYPE_ECB, + BT_MESH_KEY_TYPE_CCM, + BT_MESH_KEY_TYPE_CMAC, + BT_MESH_KEY_TYPE_NET, + BT_MESH_KEY_TYPE_APP, + BT_MESH_KEY_TYPE_DEV +}; + +int bt_mesh_key_import(enum bt_mesh_key_type type, + const uint8_t in[16], struct bt_mesh_key *out); +int bt_mesh_key_export(uint8_t out[16], const struct bt_mesh_key *in); +void bt_mesh_key_assign(struct bt_mesh_key *dst, + const struct bt_mesh_key *src); +int bt_mesh_key_destroy(const struct bt_mesh_key *key); +int bt_mesh_key_compare(const uint8_t raw_key[16], + const struct bt_mesh_key *mesh_key); diff --git a/libs/zblue/mesh/large_comp_data_cli.c b/libs/zblue/mesh/large_comp_data_cli.c new file mode 100644 index 0000000000000..24dbb3629acec --- /dev/null +++ b/libs/zblue/mesh/large_comp_data_cli.c @@ -0,0 +1,218 @@ +/**************************************************************************** + * libs/zblue/mesh/large_comp_data_cli.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Bluetooth Mesh */ + +/* Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "msg.h" + +#include + +#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_large_comp_data_cli); + +#include "net.h" +#include "access.h" +#include "foundation.h" + +static struct bt_mesh_large_comp_data_cli *cli; +static int32_t msg_timeout; + +static int data_status(const struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + + struct net_buf_simple *buf, uint32_t op, + void (*cb)(struct bt_mesh_large_comp_data_cli *cli, + uint16_t addr, + struct bt_mesh_large_comp_data_rsp *rsp)) +{ + struct bt_mesh_large_comp_data_rsp *rsp; + uint8_t page; + uint16_t offset; + uint16_t total_size; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + page = net_buf_simple_pull_u8(buf); + offset = net_buf_simple_pull_le16(buf); + total_size = net_buf_simple_pull_le16(buf); + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, op, ctx->addr, (void **)&rsp)) + { + rsp->page = page; + rsp->offset = offset; + rsp->total_size = total_size; + + if (rsp->data) + { + net_buf_simple_add_mem(rsp->data, buf->data, + MIN(net_buf_simple_tailroom(rsp->data), + buf->len)); + } + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + + if (cb) + { + struct bt_mesh_large_comp_data_rsp status_rsp = + { + .page = page, + .offset = offset, + .total_size = total_size, + .data = buf, + }; + + cb(cli, ctx->addr, &status_rsp); + } + + return 0; +} + +static int large_comp_data_status(const struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + return data_status( + model, ctx, buf, OP_LARGE_COMP_DATA_STATUS, + (cli->cb && + cli->cb->large_comp_data_status ? + cli->cb->large_comp_data_status : NULL)); +} + +static int models_metadata_status(const struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + return data_status( + model, ctx, buf, OP_MODELS_METADATA_STATUS, + (cli->cb && + cli->cb->models_metadata_status ? + cli->cb->models_metadata_status : NULL)); +} + +const struct bt_mesh_model_op _bt_mesh_large_comp_data_cli_op[] = +{ + { + OP_LARGE_COMP_DATA_STATUS, + BT_MESH_LEN_MIN(5), + large_comp_data_status + }, + + { + OP_MODELS_METADATA_STATUS, + BT_MESH_LEN_MIN(5), + models_metadata_status + }, + + BT_MESH_MODEL_OP_END, +}; + +static int large_comp_data_cli_init(const struct bt_mesh_model *model) +{ + if (!bt_mesh_model_in_primary(model)) + { + LOG_ERR("Large Comp Data Client only allowed in primary element"); + return -EINVAL; + } + + model->keys[0] = BT_MESH_KEY_DEV_ANY; + model->rt->flags |= BT_MESH_MOD_DEVKEY_ONLY; + + cli = model->rt->user_data; + cli->model = model; + + msg_timeout = 5000; + bt_mesh_msg_ack_ctx_init(&cli->ack_ctx); + + return 0; +} + +const struct bt_mesh_model_cb _bt_mesh_large_comp_data_cli_cb = +{ + .init = large_comp_data_cli_init, +}; + +static int data_get(uint16_t net_idx, + uint16_t addr, + uint32_t op, + uint32_t status_op, + uint8_t page, + size_t offset, + struct bt_mesh_large_comp_data_rsp *rsp) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, op, 3); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + const struct bt_mesh_msg_rsp_ctx rsp_ctx = + { + .ack = &cli->ack_ctx, + .op = status_op, + .user_data = rsp, + .timeout = msg_timeout, + }; + + bt_mesh_model_msg_init(&msg, op); + net_buf_simple_add_u8(&msg, page); + net_buf_simple_add_le16(&msg, offset); + + return bt_mesh_msg_ackd_send(cli->model, + &ctx, + &msg, + rsp ? &rsp_ctx : NULL); +} + +int bt_mesh_large_comp_data_get(uint16_t net_idx, + uint16_t addr, + uint8_t page, + size_t offset, + struct bt_mesh_large_comp_data_rsp *rsp) +{ + return data_get(net_idx, addr, OP_LARGE_COMP_DATA_GET, + OP_LARGE_COMP_DATA_STATUS, page, offset, + rsp); +} + +int bt_mesh_models_metadata_get(uint16_t net_idx, + uint16_t addr, + uint8_t page, + size_t offset, + struct bt_mesh_large_comp_data_rsp *rsp) +{ + return data_get(net_idx, addr, OP_MODELS_METADATA_GET, + OP_MODELS_METADATA_STATUS, page, offset, + rsp); +} diff --git a/libs/zblue/mesh/large_comp_data_srv.c b/libs/zblue/mesh/large_comp_data_srv.c new file mode 100644 index 0000000000000..f3bcdbb44c43b --- /dev/null +++ b/libs/zblue/mesh/large_comp_data_srv.c @@ -0,0 +1,254 @@ +/**************************************************************************** + * libs/zblue/mesh/large_comp_data_srv.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_large_comp_data_srv); + +#include "access.h" +#include "cfg.h" +#include "foundation.h" +#include "net.h" +#include "mesh.h" +#include "transport.h" + +#define DUMMY_2_BYTE_OP BT_MESH_MODEL_OP_2(0xff, 0xff) +#define BT_MESH_MODEL_PAYLOAD_MAX \ + (BT_MESH_TX_SDU_MAX - BT_MESH_MODEL_OP_LEN(DUMMY_2_BYTE_OP) - BT_MESH_MIC_SHORT) + +/** Mesh Large Composition Data Server Model Context */ + +static struct bt_mesh_large_comp_data_srv +{ + /** Composition data model entry pointer. */ + + const struct bt_mesh_model *model; +} srv; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int handle_large_comp_data_get(const struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_MESH_MODEL_BUF_DEFINE(rsp, + OP_LARGE_COMP_DATA_STATUS, + BT_MESH_MODEL_PAYLOAD_MAX); + uint8_t page; + size_t offset; + size_t total_size; + int err; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + page = bt_mesh_comp_parse_page(buf); + offset = net_buf_simple_pull_le16(buf); + + LOG_DBG("page %u offset %u", page, offset); + + bt_mesh_model_msg_init(&rsp, OP_LARGE_COMP_DATA_STATUS); + net_buf_simple_add_u8(&rsp, page); + net_buf_simple_add_le16(&rsp, offset); + + if (atomic_test_bit(bt_mesh.flags, BT_MESH_COMP_DIRTY) && page < 128) + { + size_t msg_space; + + NET_BUF_SIMPLE_DEFINE(temp_buf, CONFIG_BT_MESH_COMP_PST_BUF_SIZE); + err = bt_mesh_comp_read(&temp_buf, page); + if (err) + { + LOG_ERR("Could not read comp data p%d, err: %d", page, err); + return err; + } + + net_buf_simple_add_le16(&rsp, temp_buf.len); + if (offset > temp_buf.len) + { + return 0; + } + + msg_space = net_buf_simple_tailroom(&rsp) - BT_MESH_MIC_SHORT; + net_buf_simple_add_mem(&rsp, temp_buf.data + offset, + (msg_space < + (temp_buf.len - offset)) ? msg_space + : + temp_buf.len - offset); + } + else + { + total_size = bt_mesh_comp_page_size(page); + net_buf_simple_add_le16(&rsp, total_size); + + if (offset < total_size) + { + err = bt_mesh_comp_data_get_page(&rsp, page, offset); + if (err && err != -E2BIG) + { + LOG_ERR("Could not read comp data p%d, err: %d", page, err); + return err; + } + } + } + + if (bt_mesh_model_send(model, ctx, &rsp, NULL, NULL)) + { + LOG_ERR("Unable to send Large Composition Data Status"); + } + + return 0; +} + +static int handle_models_metadata_get(const struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_MESH_MODEL_BUF_DEFINE(rsp, + OP_MODELS_METADATA_STATUS, + BT_MESH_MODEL_PAYLOAD_MAX); + size_t offset; + size_t total_size; + uint8_t page; + int err; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + page = net_buf_simple_pull_u8(buf); + offset = net_buf_simple_pull_le16(buf); + + LOG_DBG("page %u offset %u", page, offset); + + if (page >= 128U && atomic_test_bit(bt_mesh.flags, BT_MESH_METADATA_DIRTY)) + { + LOG_DBG("Models Metadata Page 128"); + page = 128U; + } + else if (page != 0U) + { + LOG_DBG("Models Metadata Page %u not available", page); + page = 0U; + } + + bt_mesh_model_msg_init(&rsp, OP_MODELS_METADATA_STATUS); + net_buf_simple_add_u8(&rsp, page); + net_buf_simple_add_le16(&rsp, offset); + + if (atomic_test_bit(bt_mesh.flags, BT_MESH_METADATA_DIRTY) == (page == 0U)) + { + rsp.size -= BT_MESH_MIC_SHORT; + err = bt_mesh_models_metadata_read(&rsp, offset); + if (err) + { + LOG_ERR("Unable to get stored models metadata"); + return err; + } + + rsp.size += BT_MESH_MIC_SHORT; + } + else + { + total_size = bt_mesh_metadata_page_0_size(); + net_buf_simple_add_le16(&rsp, total_size); + + if (offset < total_size) + { + err = bt_mesh_metadata_get_page_0(&rsp, offset); + if (err && err != -E2BIG) + { + LOG_ERR("Failed to get Models Metadata Page 0: %d", err); + return err; + } + } + } + + if (bt_mesh_model_send(model, ctx, &rsp, NULL, NULL)) + { + LOG_ERR("Unable to send Models Metadata Status"); + } + + return 0; +} + +const struct bt_mesh_model_op _bt_mesh_large_comp_data_srv_op[] = +{ + { + OP_LARGE_COMP_DATA_GET, + BT_MESH_LEN_EXACT(3), + handle_large_comp_data_get + }, + + { + OP_MODELS_METADATA_GET, + BT_MESH_LEN_EXACT(3), + handle_models_metadata_get + }, + + BT_MESH_MODEL_OP_END, +}; + +static int large_comp_data_srv_init(const struct bt_mesh_model *model) +{ + const struct bt_mesh_model *config_srv = + bt_mesh_model_find(bt_mesh_model_elem(model), + BT_MESH_MODEL_ID_CFG_SRV); + + if (config_srv == NULL) + { + LOG_ERR("Large Composition Data Server cannot extend Configuration " + "server"); + return -EINVAL; + } + + /* Large Composition Data Server model shall use the device key */ + + model->keys[0] = BT_MESH_KEY_DEV; + model->rt->flags |= BT_MESH_MOD_DEVKEY_ONLY; + + srv.model = model; + + bt_mesh_model_extend(model, config_srv); + + return 0; +} + +const struct bt_mesh_model_cb _bt_mesh_large_comp_data_srv_cb = +{ + .init = large_comp_data_srv_init, +}; diff --git a/libs/zblue/mesh/lpn.c b/libs/zblue/mesh/lpn.c new file mode 100644 index 0000000000000..5b3488ef13609 --- /dev/null +++ b/libs/zblue/mesh/lpn.c @@ -0,0 +1,1424 @@ +/**************************************************************************** + * libs/zblue/mesh/lpn.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include "crypto.h" +#include "net.h" +#include "transport.h" +#include "heartbeat.h" +#include "access.h" +#include "beacon.h" +#include "foundation.h" +#include "lpn.h" +#include "statistic.h" + +#define LOG_LEVEL CONFIG_BT_MESH_LOW_POWER_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_lpn); + +/** + * Log modes other than the deferred may cause unintended delays during + * processing of log messages. This in turns will affect scheduling of the + * receive delay and receive window. + */ +#if !defined(CONFIG_TEST) && !defined(CONFIG_ARCH_POSIX) && defined(CONFIG_LOG) && \ + !defined(CONFIG_LOG_MODE_DEFERRED) && (LOG_LEVEL >= LOG_LEVEL_INF) +#warning Frienship feature may work unstable when non-deferred log mode is selected. Use the \ + CONFIG_LOG_MODE_DEFERRED Kconfig option when Low Power node feature is enabled. +#endif + +#if defined(CONFIG_BT_MESH_ADV_LEGACY) +#define RX_DELAY_CORRECTION(lpn) ((lpn)->adv_duration) +#else +#define RX_DELAY_CORRECTION(lpn) 0 +#endif + +#if defined(CONFIG_BT_MESH_LPN_AUTO) +#define LPN_AUTO_TIMEOUT (CONFIG_BT_MESH_LPN_AUTO_TIMEOUT * MSEC_PER_SEC) +#else +#define LPN_AUTO_TIMEOUT 0 +#endif + +#define LPN_RECV_DELAY CONFIG_BT_MESH_LPN_RECV_DELAY +#define SCAN_LATENCY MIN(CONFIG_BT_MESH_LPN_SCAN_LATENCY, LPN_RECV_DELAY) + +#define FRIEND_REQ_RETRY_TIMEOUT SEC2TICK(CONFIG_BT_MESH_LPN_RETRY_TIMEOUT) + +#define FRIEND_REQ_WAIT 100 +#define FRIEND_REQ_SCAN CONFIG_BT_MESH_LPN_OFFER_WAIT_TIMEOUT +#define FRIEND_REQ_TIMEOUT (FRIEND_REQ_WAIT + FRIEND_REQ_SCAN) + +#define POLL_RETRY_TIMEOUT 100 + +#define REQ_RETRY_DURATION(lpn) \ + (LPN_RECV_DELAY + (lpn)->adv_duration + (lpn)->recv_win + POLL_RETRY_TIMEOUT) + +#define POLL_TIMEOUT_INIT (CONFIG_BT_MESH_LPN_INIT_POLL_TIMEOUT * 100) + +#define POLL_TIMEOUT (CONFIG_BT_MESH_LPN_POLL_TIMEOUT * 100) + +#define REQ_ATTEMPTS_MAX 6 +#define REQ_ATTEMPTS(lpn) MIN(REQ_ATTEMPTS_MAX, POLL_TIMEOUT / REQ_RETRY_DURATION(lpn)) + +#define POLL_TIMEOUT_MAX(lpn) (POLL_TIMEOUT - (REQ_ATTEMPTS(lpn) * REQ_RETRY_DURATION(lpn))) + +#define CLEAR_ATTEMPTS 3 + +/* The specification defines no Friend Clear Confirm timeout for the Low + * Power node. + */ +#define FRIEND_CLEAR_TIMEOUT (1 * MSEC_PER_SEC) + +#define LPN_CRITERIA \ + ((CONFIG_BT_MESH_LPN_MIN_QUEUE_SIZE) | (CONFIG_BT_MESH_LPN_RSSI_FACTOR << 3) | \ + (CONFIG_BT_MESH_LPN_RECV_WIN_FACTOR << 5)) + +#define POLL_TO(to) {(uint8_t)((to) >> 16), (uint8_t)((to) >> 8), (uint8_t)(to)} +#define LPN_POLL_TO POLL_TO(CONFIG_BT_MESH_LPN_POLL_TIMEOUT) + +/* 1 transmission, 20ms interval */ +#define POLL_XMIT BT_MESH_TRANSMIT(0, 20) + +#if defined(CONFIG_BT_MESH_LOW_POWER_LOG_LEVEL_DBG) + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static const char *state2str(int state) +{ + switch (state) + { + case BT_MESH_LPN_DISABLED: + return "disabled"; + case BT_MESH_LPN_CLEAR: + return "clear"; + case BT_MESH_LPN_TIMER: + return "timer"; + case BT_MESH_LPN_ENABLED: + return "enabled"; + case BT_MESH_LPN_REQ_WAIT: + return "req wait"; + case BT_MESH_LPN_WAIT_OFFER: + return "wait offer"; + case BT_MESH_LPN_ESTABLISHED: + return "established"; + case BT_MESH_LPN_RECV_DELAY: + return "recv delay"; + case BT_MESH_LPN_WAIT_UPDATE: + return "wait update"; + default: + return "(unknown)"; + } +} +#endif /* CONFIG_BT_MESH_LOW_POWER_LOG_LEVEL_DBG */ + +static int32_t poll_timeout(struct bt_mesh_lpn *lpn) +{ + /* If we're waiting for segment acks keep polling at high freq */ + + if (bt_mesh_tx_in_progress()) + { + LOG_DBG("Tx is in progress. Keep polling"); + return MIN(POLL_TIMEOUT_MAX(lpn), 1 * MSEC_PER_SEC); + } + + if (lpn->poll_timeout < POLL_TIMEOUT_MAX(lpn)) + { + lpn->poll_timeout *= 2; + lpn->poll_timeout = MIN(lpn->poll_timeout, POLL_TIMEOUT_MAX(lpn)); + } + + LOG_DBG("Poll Timeout is %ums", lpn->poll_timeout); + + return lpn->poll_timeout; +} + +static inline void lpn_set_state(int state) +{ +#if defined(CONFIG_BT_MESH_LOW_POWER_LOG_LEVEL_DBG) + LOG_DBG("%s -> %s", state2str(bt_mesh.lpn.state), state2str(state)); +#endif + bt_mesh.lpn.state = state; +} + +static inline void group_zero(atomic_t *target) +{ +#if CONFIG_BT_MESH_LPN_GROUPS > 32 + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.lpn.added); i++) + { + atomic_set(&target[i], 0); + } +#else + atomic_set(target, 0); +#endif +} + +static inline void group_set(atomic_t *target, atomic_t *source) +{ +#if CONFIG_BT_MESH_LPN_GROUPS > 32 + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.lpn.added); i++) + { + (void)atomic_or(&target[i], atomic_read(&source[i])); + } +#else + (void)atomic_or(target, atomic_read(source)); +#endif +} + +static inline void group_clear(atomic_t *target, atomic_t *source) +{ +#if CONFIG_BT_MESH_LPN_GROUPS > 32 + int i; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.lpn.added); i++) + { + (void)atomic_and(&target[i], ~atomic_read(&source[i])); + } +#else + (void)atomic_and(target, ~atomic_read(source)); +#endif +} + +static void clear_friendship(bool force, bool disable); + +static void friend_clear_sent(int err, void *user_data) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + + bt_mesh_scan_enable(); + + lpn->req_attempts++; + + if (err) + { + LOG_ERR("Sending Friend Request failed (err %d)", err); + lpn_set_state(BT_MESH_LPN_ENABLED); + clear_friendship(false, lpn->disable); + return; + } + + lpn_set_state(BT_MESH_LPN_CLEAR); + k_work_reschedule(&lpn->timer, MSEC2TICK(FRIEND_CLEAR_TIMEOUT)); +} + +static const struct bt_mesh_send_cb clear_sent_cb = +{ + .end = friend_clear_sent, +}; + +static int send_friend_clear(void) +{ + struct bt_mesh_msg_ctx ctx = + { + .net_idx = bt_mesh.lpn.sub->net_idx, + .app_idx = BT_MESH_KEY_UNUSED, + .addr = bt_mesh.lpn.frnd, + .send_ttl = 0, + }; + + struct bt_mesh_net_tx tx = + { + .sub = bt_mesh.lpn.sub, + .ctx = &ctx, + .src = bt_mesh_primary_addr(), + .xmit = bt_mesh_net_transmit_get(), + }; + + struct bt_mesh_ctl_friend_clear req = + { + .lpn_addr = sys_cpu_to_be16(tx.src), + .lpn_counter = sys_cpu_to_be16(bt_mesh.lpn.lpn_counter), + }; + + LOG_DBG(""); + + return bt_mesh_ctl_send(&tx, TRANS_CTL_OP_FRIEND_CLEAR, &req, sizeof(req), + &clear_sent_cb, NULL); +} + +static void clear_friendship(bool force, bool disable) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + bool was_established = lpn->established; + uint16_t frnd = lpn->frnd; + uint16_t net_idx = lpn->sub->net_idx; + + LOG_DBG("force %u disable %u", force, disable); + + if (!force && lpn->established && !lpn->clear_success && + lpn->req_attempts < CLEAR_ATTEMPTS) + { + send_friend_clear(); + lpn->disable = disable; + return; + } + + bt_mesh_rx_reset(); + + /* Disable LPN while clearing, in case the work handler gets a chance to + * fire. + */ + + lpn_set_state(BT_MESH_LPN_DISABLED); + + /* The timer handler returns without any actions if this fails. */ + + (void)k_work_cancel_delayable(&lpn->timer); + + if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) + { + bt_mesh_scan_disable(); + } + + if (lpn->clear_success) + { + lpn->old_friend = BT_MESH_ADDR_UNASSIGNED; + } + else + { + lpn->old_friend = lpn->frnd; + } + + for (int i = 0; i < ARRAY_SIZE(lpn->cred); i++) + { + if (lpn->sub->keys[i].valid) + { + bt_mesh_friend_cred_destroy(&lpn->cred[i]); + } + } + + lpn->frnd = BT_MESH_ADDR_UNASSIGNED; + lpn->fsn = 0U; + lpn->req_attempts = 0U; + lpn->recv_win = 0U; + lpn->queue_size = 0U; + lpn->disable = 0U; + lpn->sent_req = 0U; + lpn->established = 0U; + lpn->clear_success = 0U; + lpn->sub = NULL; + + group_zero(lpn->added); + group_zero(lpn->pending); + group_zero(lpn->to_remove); + + /* Set this to 1 to force group subscription when the next + * Friendship is created, in case lpn->groups doesn't get modified + * meanwhile. + */ + + lpn->groups_changed = 1U; + + bt_mesh_hb_feature_changed(BT_MESH_FEAT_LOW_POWER); + + if (!disable) + { + lpn_set_state(BT_MESH_LPN_ENABLED); + + k_work_reschedule(&lpn->timer, FRIEND_REQ_RETRY_TIMEOUT); + + if (!IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) + { + bt_mesh_scan_enable(); + } + } + + if (was_established) + { + STRUCT_SECTION_FOREACH(bt_mesh_lpn_cb, cb) + { + if (cb->terminated) + { + cb->terminated(net_idx, frnd); + } + } + } +} + +static void friend_req_send_end(int err, void *user_data) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + + if (lpn->state != BT_MESH_LPN_ENABLED) + { + return; + } + + if (err) + { + LOG_ERR("Sending Friend Request failed (err %d)", err); + return; + } + + lpn->adv_duration = + (uint32_t)TICK2MSEC(clock_systime_ticks()) - lpn->adv_start_time; + + if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) + { + k_work_reschedule(&lpn->timer, + MSEC2TICK(FRIEND_REQ_WAIT - (int32_t)lpn->adv_duration)); + lpn_set_state(BT_MESH_LPN_REQ_WAIT); + } + else + { + k_work_reschedule(&lpn->timer, MSEC2TICK(FRIEND_REQ_TIMEOUT)); + lpn_set_state(BT_MESH_LPN_WAIT_OFFER); + } +} + +static void friend_req_send_start(uint16_t duration, + int err, void *user_data) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + + lpn->adv_start_time = (uint32_t)TICK2MSEC(clock_systime_ticks()); + + if (err) + { + friend_req_send_end(err, user_data); + } +} + +static const struct bt_mesh_send_cb friend_req_send_cb = +{ + .start = friend_req_send_start, + .end = friend_req_send_end, +}; + +static int send_friend_req(struct bt_mesh_lpn *lpn) +{ + const struct bt_mesh_comp *comp = bt_mesh_comp_get(); + struct bt_mesh_msg_ctx ctx = + { + .app_idx = BT_MESH_KEY_UNUSED, + .addr = BT_MESH_ADDR_FRIENDS, + .send_ttl = 0, + }; + + struct bt_mesh_net_tx tx = + { + .ctx = &ctx, + .src = bt_mesh_primary_addr(), + .xmit = POLL_XMIT, + }; + + lpn->lpn_counter++; + + struct bt_mesh_ctl_friend_req req = + { + .criteria = LPN_CRITERIA, + .recv_delay = LPN_RECV_DELAY, + .poll_to = LPN_POLL_TO, + .prev_addr = sys_cpu_to_be16(lpn->old_friend), + .num_elem = comp->elem_count, + .lpn_counter = sys_cpu_to_be16(lpn->lpn_counter), + }; + + LOG_DBG(""); + + lpn->sub = bt_mesh_subnet_next(NULL); + if (!lpn->sub) + { + LOG_ERR("No subnets, can't start LPN mode"); + return -ENOENT; + } + + ctx.net_idx = lpn->sub->net_idx; + tx.sub = lpn->sub; + + return bt_mesh_ctl_send(&tx, TRANS_CTL_OP_FRIEND_REQ, &req, sizeof(req), + &friend_req_send_cb, + NULL); +} + +static void req_send_end(int err, void *user_data) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + bool retry; + + if (lpn->state == BT_MESH_LPN_DISABLED) + { + return; + } + + lpn->adv_duration = + (uint32_t)TICK2MSEC(clock_systime_ticks()) - lpn->adv_start_time; + +#if defined(CONFIG_BT_MESH_LOW_POWER_LOG_LEVEL_DBG) + LOG_DBG("req 0x%02x duration %u err %d state %s", lpn->sent_req, + lpn->adv_duration, err, + state2str(lpn->state)); +#endif + + if (err) + { + LOG_ERR("Sending request failed (err %d)", err); + lpn->sent_req = 0U; + group_zero(lpn->pending); + return; + } + + retry = (lpn->req_attempts > 0); + + lpn->req_attempts++; + + if (IS_ENABLED(CONFIG_BT_MESH_STATISTIC)) + { + bt_mesh_stat_lpn_timing_update_poll_tx(); + } + + if (lpn->established || IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) + { + lpn_set_state(BT_MESH_LPN_RECV_DELAY); + /* We start scanning a bit early to eliminate risk of missing + * response data due to HCI and other latencies. + */ + + k_work_reschedule(&lpn->timer, + MSEC2TICK(LPN_RECV_DELAY - SCAN_LATENCY - + RX_DELAY_CORRECTION(lpn))); + } + else + { + lpn_set_state(BT_MESH_LPN_WAIT_UPDATE); + k_work_reschedule(&lpn->timer, + MSEC2TICK(LPN_RECV_DELAY + lpn->recv_win)); + } + + STRUCT_SECTION_FOREACH(bt_mesh_lpn_cb, cb) + { + if (cb->polled) + { + cb->polled(lpn->sub->net_idx, lpn->frnd, retry); + } + } +} + +static void req_send_start(uint16_t duration, int err, void *user_data) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + + lpn->adv_start_time = (uint32_t)TICK2MSEC(clock_systime_ticks()); + + if (err) + { + req_send_end(err, user_data); + } +} + +static const struct bt_mesh_send_cb req_send_cb = +{ + .start = req_send_start, + .end = req_send_end, +}; + +static int send_friend_poll(void) +{ + struct bt_mesh_msg_ctx ctx = + { + .net_idx = bt_mesh.lpn.sub->net_idx, + .app_idx = BT_MESH_KEY_UNUSED, + .addr = bt_mesh.lpn.frnd, + .send_ttl = 0, + }; + + struct bt_mesh_net_tx tx = + { + .sub = bt_mesh.lpn.sub, + .ctx = &ctx, + .src = bt_mesh_primary_addr(), + .xmit = POLL_XMIT, + .friend_cred = true, + }; + + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + uint8_t fsn = lpn->fsn; + int err; + + LOG_DBG("lpn->sent_req 0x%02x", lpn->sent_req); + + if (lpn->sent_req) + { + if (lpn->sent_req != TRANS_CTL_OP_FRIEND_POLL) + { + lpn->pending_poll = 1U; + } + + return 0; + } + + err = bt_mesh_ctl_send(&tx, TRANS_CTL_OP_FRIEND_POLL, &fsn, 1, + &req_send_cb, + NULL); + if (err == 0) + { + lpn->pending_poll = 0U; + lpn->sent_req = TRANS_CTL_OP_FRIEND_POLL; + } + + return err; +} + +void bt_mesh_lpn_disable(bool force) +{ + if (bt_mesh.lpn.state == BT_MESH_LPN_DISABLED) + { + return; + } + + clear_friendship(force, true); +} + +int bt_mesh_lpn_set(bool enable) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + + if (enable) + { + if (lpn->state != BT_MESH_LPN_DISABLED) + { + return 0; + } + } + else + { + if (lpn->state == BT_MESH_LPN_DISABLED) + { + return 0; + } + } + + if (!bt_mesh_is_provisioned()) + { + if (enable) + { + lpn_set_state(BT_MESH_LPN_ENABLED); + } + else + { + lpn_set_state(BT_MESH_LPN_DISABLED); + } + + return 0; + } + + if (enable) + { + lpn_set_state(BT_MESH_LPN_ENABLED); + + if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) + { + bt_mesh_scan_disable(); + } + + send_friend_req(lpn); + } + else + { + if (IS_ENABLED(CONFIG_BT_MESH_LPN_AUTO) && + lpn->state == BT_MESH_LPN_TIMER) + { + /* If this fails, the work handler will just exit on the + * next timeout. + */ + + (void)k_work_cancel_delayable(&lpn->timer); + lpn_set_state(BT_MESH_LPN_DISABLED); + } + else + { + bt_mesh_lpn_disable(false); + } + } + + return 0; +} + +void bt_mesh_lpn_friendship_end(void) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + + if (!lpn->established) + { + return; + } + + clear_friendship(true, false); +} + +static void friend_response_received(struct bt_mesh_lpn *lpn) +{ + LOG_DBG("lpn->sent_req 0x%02x", lpn->sent_req); + + if (lpn->sent_req == TRANS_CTL_OP_FRIEND_POLL) + { + lpn->fsn++; + } + + lpn_set_state(BT_MESH_LPN_ESTABLISHED); + lpn->req_attempts = 0U; + lpn->sent_req = 0U; + + /* Schedule the next poll. This may be overridden by additional + * processing of the received response. + */ + + int32_t timeout = poll_timeout(lpn); + + k_work_reschedule(&lpn->timer, MSEC2TICK(timeout)); + bt_mesh_scan_disable(); + if (IS_ENABLED(CONFIG_BT_MESH_STATISTIC)) + { + bt_mesh_stat_lpn_timing_update_response_rx(); + } +} + +void bt_mesh_lpn_msg_received(struct bt_mesh_net_rx *rx) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + + if (lpn->state == BT_MESH_LPN_TIMER) + { + LOG_DBG("Restarting establishment timer"); + k_work_reschedule(&lpn->timer, MSEC2TICK(LPN_AUTO_TIMEOUT)); + return; + } + + /* If the message was a Friend control message, it's possible that a + * Poll was already queued for sending. In this case, we're already in a + * different state. + */ + + if (lpn->state != BT_MESH_LPN_WAIT_UPDATE) + { + return; + } + + if (lpn->sent_req != TRANS_CTL_OP_FRIEND_POLL) + { + LOG_WRN("Unexpected message without a preceding Poll"); + return; + } + + friend_response_received(lpn); + + LOG_DBG("Requesting more messages from Friend"); + + send_friend_poll(); +} + +static int friend_cred_create(struct bt_mesh_net_cred *cred, + const struct bt_mesh_key *key) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + + return bt_mesh_friend_cred_create(cred, bt_mesh_primary_addr(), lpn->frnd, + lpn->lpn_counter, + lpn->frnd_counter, key); +} + +int bt_mesh_lpn_friend_offer(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + struct bt_mesh_ctl_friend_offer *msg = (void *)buf->data; + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + uint16_t frnd_counter; + int err; + + if (buf->len < sizeof(*msg)) + { + LOG_WRN("Too short Friend Offer"); + return -EBADMSG; + } + + if (lpn->state != BT_MESH_LPN_WAIT_OFFER) + { + LOG_WRN("Ignoring unexpected Friend Offer"); + return 0; + } + + if (!msg->recv_win) + { + LOG_WRN("Prohibited ReceiveWindow value"); + return -EBADMSG; + } + + frnd_counter = sys_be16_to_cpu(msg->frnd_counter); + + LOG_DBG("recv_win %u queue_size %u sub_list_size %u rssi %d counter %u", + msg->recv_win, + msg->queue_size, msg->sub_list_size, msg->rssi, frnd_counter); + + lpn->frnd_counter = frnd_counter; + lpn->frnd = rx->ctx.addr; + + /* Create friend credentials for each of the valid keys in the + * friendship subnet: + */ + + for (int i = 0; i < ARRAY_SIZE(lpn->cred); i++) + { + if (!lpn->sub->keys[i].valid) + { + continue; + } + + err = friend_cred_create(&lpn->cred[i], &lpn->sub->keys[i].net); + if (err) + { + lpn->frnd = BT_MESH_ADDR_UNASSIGNED; + return err; + } + } + + /* TODO: Add offer acceptance criteria check */ + + lpn->recv_win = msg->recv_win; + lpn->queue_size = msg->queue_size; + + err = send_friend_poll(); + if (err) + { + LOG_WRN("LPN didn't succeed poll sending (err %d)", err); + for (int i = 0; i < ARRAY_SIZE(lpn->cred); i++) + { + if (lpn->sub->keys[i].valid) + { + bt_mesh_friend_cred_destroy(&lpn->cred[i]); + } + } + + lpn->sub = NULL; + lpn->frnd = BT_MESH_ADDR_UNASSIGNED; + lpn->recv_win = 0U; + lpn->queue_size = 0U; + } + + return 0; +} + +int bt_mesh_lpn_friend_clear_cfm(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + struct bt_mesh_ctl_friend_clear_confirm *msg = (void *)buf->data; + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + uint16_t addr; + uint16_t counter; + + if (buf->len < sizeof(*msg)) + { + LOG_WRN("Too short Friend Clear Confirm"); + return -EBADMSG; + } + + if (lpn->state != BT_MESH_LPN_CLEAR) + { + LOG_WRN("Ignoring unexpected Friend Clear Confirm"); + return 0; + } + + addr = sys_be16_to_cpu(msg->lpn_addr); + counter = sys_be16_to_cpu(msg->lpn_counter); + + LOG_DBG("LPNAddress 0x%04x LPNCounter 0x%04x", addr, counter); + + if (addr != bt_mesh_primary_addr() || counter != lpn->lpn_counter) + { + LOG_WRN("Invalid parameters in Friend Clear Confirm"); + return 0; + } + + lpn->clear_success = 1U; + clear_friendship(false, lpn->disable); + + return 0; +} + +static void lpn_group_add(uint16_t group) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + uint16_t *free_slot = NULL; + int i; + + for (i = 0; i < ARRAY_SIZE(lpn->groups); i++) + { + if (lpn->groups[i] == group) + { + atomic_clear_bit(lpn->to_remove, i); + return; + } + + if (!free_slot && lpn->groups[i] == BT_MESH_ADDR_UNASSIGNED) + { + free_slot = &lpn->groups[i]; + } + } + + if (!free_slot) + { + LOG_WRN("Friend Subscription List exceeded!"); + return; + } + + *free_slot = group; + lpn->groups_changed = 1U; +} + +static void lpn_group_del(uint16_t group) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + int i; + + for (i = 0; i < ARRAY_SIZE(lpn->groups); i++) + { + if (lpn->groups[i] == group) + { + if (atomic_test_bit(lpn->added, i) || + atomic_test_bit(lpn->pending, i)) + { + atomic_set_bit(lpn->to_remove, i); + lpn->groups_changed = 1U; + } + else + { + lpn->groups[i] = BT_MESH_ADDR_UNASSIGNED; + } + } + } +} + +static inline int group_popcount(atomic_t *target) +{ +#if CONFIG_BT_MESH_LPN_GROUPS > 32 + int i; + int count = 0; + + for (i = 0; i < ARRAY_SIZE(bt_mesh.lpn.added); i++) + { + count += POPCOUNT(atomic_read(&target[i])); + } +#else + return POPCOUNT(atomic_read(target)); +#endif +} + +static bool sub_update(uint8_t op) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + int added_count = group_popcount(lpn->added); + struct bt_mesh_msg_ctx ctx = + { + .net_idx = lpn->sub->net_idx, + .app_idx = BT_MESH_KEY_UNUSED, + .addr = lpn->frnd, + .send_ttl = 0, + }; + + struct bt_mesh_net_tx tx = + { + .sub = lpn->sub, + .ctx = &ctx, + .src = bt_mesh_primary_addr(), + .xmit = POLL_XMIT, + .friend_cred = true, + }; + + struct bt_mesh_ctl_friend_sub req; + size_t i; + size_t g; + + LOG_DBG("op 0x%02x sent_req 0x%02x", op, lpn->sent_req); + + if (lpn->sent_req) + { + return false; + } + + for (i = 0, g = 0; i < ARRAY_SIZE(lpn->groups); i++) + { + if (lpn->groups[i] == BT_MESH_ADDR_UNASSIGNED) + { + continue; + } + + if (op == TRANS_CTL_OP_FRIEND_SUB_ADD) + { + if (atomic_test_bit(lpn->added, i)) + { + continue; + } + } + else + { + if (!atomic_test_bit(lpn->to_remove, i)) + { + continue; + } + } + + if (added_count + g >= lpn->queue_size) + { + LOG_WRN("Friend Queue Size exceeded"); + break; + } + + req.addr_list[g++] = sys_cpu_to_be16(lpn->groups[i]); + atomic_set_bit(lpn->pending, i); + + if (g == ARRAY_SIZE(req.addr_list)) + { + break; + } + } + + if (g == 0) + { + group_zero(lpn->pending); + return false; + } + + req.xact = lpn->xact_next++; + + if (bt_mesh_ctl_send(&tx, op, &req, 1 + g * 2, &req_send_cb, NULL) < 0) + { + group_zero(lpn->pending); + return false; + } + + lpn->xact_pending = req.xact; + lpn->sent_req = op; + return true; +} + +static void update_timeout(struct bt_mesh_lpn *lpn) +{ + if (lpn->established) + { + LOG_WRN("No response from Friend during ReceiveWindow"); + lpn_set_state(BT_MESH_LPN_ESTABLISHED); + k_work_reschedule(&lpn->timer, MSEC2TICK(POLL_RETRY_TIMEOUT)); + bt_mesh_scan_disable(); + if (IS_ENABLED(CONFIG_BT_MESH_STATISTIC)) + { + bt_mesh_stat_lpn_timing_update_win_expired(); + } + } + else + { + if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) + { + bt_mesh_scan_disable(); + } + + if (lpn->req_attempts < REQ_ATTEMPTS(lpn)) + { + LOG_WRN("Retrying first Friend Poll"); + lpn->sent_req = 0U; + if (send_friend_poll() == 0) + { + return; + } + } + + LOG_ERR("Timed out waiting for first Friend Update"); + clear_friendship(false, false); + } +} + +static void lpn_timeout(struct k_work *work) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + +#if defined(CONFIG_BT_MESH_LOW_POWER_LOG_LEVEL_DBG) + LOG_DBG("state: %s", state2str(lpn->state)); +#endif + + switch (lpn->state) + { + case BT_MESH_LPN_DISABLED: + break; + case BT_MESH_LPN_CLEAR: + clear_friendship(false, bt_mesh.lpn.disable); + break; + case BT_MESH_LPN_TIMER: + LOG_DBG("Starting to look for Friend nodes"); + lpn_set_state(BT_MESH_LPN_ENABLED); + if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) + { + bt_mesh_scan_disable(); + } + + __fallthrough; + case BT_MESH_LPN_ENABLED: + send_friend_req(lpn); + break; + case BT_MESH_LPN_REQ_WAIT: + k_work_reschedule(&lpn->timer, + MSEC2TICK(lpn->adv_duration + FRIEND_REQ_SCAN)); + lpn_set_state(BT_MESH_LPN_WAIT_OFFER); + bt_mesh_scan_enable(); + break; + case BT_MESH_LPN_WAIT_OFFER: + LOG_WRN("No acceptable Friend Offers received"); + lpn_set_state(BT_MESH_LPN_ENABLED); + lpn->sent_req = 0U; + k_work_reschedule(&lpn->timer, FRIEND_REQ_RETRY_TIMEOUT); + + if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) + { + bt_mesh_scan_disable(); + } + break; + case BT_MESH_LPN_ESTABLISHED: + if (lpn->req_attempts < REQ_ATTEMPTS(lpn)) + { + uint8_t req = lpn->sent_req; + + lpn->sent_req = 0U; + + if (!req || req == TRANS_CTL_OP_FRIEND_POLL) + { + send_friend_poll(); + } + else + { + sub_update(req); + } + + break; + } + + LOG_ERR("No response from Friend after %u retries", + lpn->req_attempts); + lpn->req_attempts = 0U; + clear_friendship(true, false); + break; + case BT_MESH_LPN_RECV_DELAY: + k_work_reschedule(&lpn->timer, + MSEC2TICK(SCAN_LATENCY + lpn->recv_win + + RX_DELAY_CORRECTION(lpn))); + lpn_set_state(BT_MESH_LPN_WAIT_UPDATE); + bt_mesh_scan_enable(); + if (IS_ENABLED(CONFIG_BT_MESH_STATISTIC)) + { + bt_mesh_stat_lpn_timing_update_scan_start(); + } + break; + case BT_MESH_LPN_WAIT_UPDATE: + update_timeout(lpn); + break; + default: + DEBUGASSERT(0); + break; + } +} + +void bt_mesh_lpn_group_add(uint16_t group) +{ + LOG_DBG("group 0x%04x", group); + + lpn_group_add(group); + + if (!bt_mesh_lpn_established() || bt_mesh.lpn.sent_req) + { + return; + } + + sub_update(TRANS_CTL_OP_FRIEND_SUB_ADD); +} + +void bt_mesh_lpn_group_del(const uint16_t *groups, size_t group_count) +{ + int i; + + for (i = 0; i < group_count; i++) + { + if (groups[i] != BT_MESH_ADDR_UNASSIGNED) + { + LOG_DBG("group 0x%04x", groups[i]); + lpn_group_del(groups[i]); + } + } + + if (!bt_mesh_lpn_established() || bt_mesh.lpn.sent_req) + { + return; + } + + sub_update(TRANS_CTL_OP_FRIEND_SUB_REM); +} + +int bt_mesh_lpn_friend_sub_cfm(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + struct bt_mesh_ctl_friend_sub_confirm *msg = (void *)buf->data; + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + + if (buf->len < sizeof(*msg)) + { + LOG_WRN("Too short Friend Subscription Confirm"); + return -EBADMSG; + } + + LOG_DBG("xact 0x%02x", msg->xact); + + if (!lpn->sent_req) + { + LOG_WRN("No pending subscription list message"); + return 0; + } + + if (msg->xact != lpn->xact_pending) + { + LOG_WRN("Transaction mismatch (0x%02x != 0x%02x)", msg->xact, + lpn->xact_pending); + return 0; + } + + if (lpn->sent_req == TRANS_CTL_OP_FRIEND_SUB_ADD) + { + group_set(lpn->added, lpn->pending); + group_zero(lpn->pending); + } + else if (lpn->sent_req == TRANS_CTL_OP_FRIEND_SUB_REM) + { + int i; + + group_clear(lpn->added, lpn->pending); + + for (i = 0; i < ARRAY_SIZE(lpn->groups); i++) + { + if (atomic_test_and_clear_bit(lpn->pending, i) && + atomic_test_and_clear_bit(lpn->to_remove, i)) + { + lpn->groups[i] = BT_MESH_ADDR_UNASSIGNED; + } + } + } + else + { + LOG_WRN("Unexpected Friend Subscription Confirm"); + return 0; + } + + friend_response_received(lpn); + + if (lpn->groups_changed) + { + sub_update(TRANS_CTL_OP_FRIEND_SUB_ADD); + sub_update(TRANS_CTL_OP_FRIEND_SUB_REM); + + if (!lpn->sent_req) + { + lpn->groups_changed = 0U; + } + } + + if (lpn->pending_poll) + { + send_friend_poll(); + } + + return 0; +} + +int bt_mesh_lpn_friend_update(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + struct bt_mesh_ctl_friend_update *msg = (void *)buf->data; + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + struct bt_mesh_subnet *sub = rx->sub; + uint32_t iv_index; + bool established = false; + + if (buf->len < sizeof(*msg)) + { + LOG_WRN("Too short Friend Update"); + return -EBADMSG; + } + + if (lpn->sent_req != TRANS_CTL_OP_FRIEND_POLL) + { + LOG_WRN("Unexpected friend update"); + return 0; + } + + if (sub->kr_phase == BT_MESH_KR_PHASE_2 && !rx->new_key) + { + LOG_WRN("Ignoring Phase 2 KR Update secured using old key"); + return 0; + } + + if (atomic_test_bit(bt_mesh.flags, BT_MESH_IVU_INITIATOR) && + (atomic_test_bit(bt_mesh.flags, + BT_MESH_IVU_IN_PROGRESS) == BT_MESH_IV_UPDATE(msg->flags))) + { + bt_mesh_beacon_ivu_initiator(false); + } + + if (!lpn->established) + { + /* This is normally checked on the transport layer, however + * in this state we're also still accepting flooding credentials so we + * need to ensure the right ones (Friend Credentials) were used for + * this message. + */ + + if (!rx->friend_cred) + { + LOG_WRN("Friend Update with wrong credentials"); + return -EINVAL; + } + + lpn->established = 1U; + + LOG_INF("Friendship established with 0x%04x", lpn->frnd); + + bt_mesh_hb_feature_changed(BT_MESH_FEAT_LOW_POWER); + + /* Set initial poll timeout */ + + lpn->poll_timeout = MIN(POLL_TIMEOUT_MAX(lpn), POLL_TIMEOUT_INIT); + + established = true; + } + + friend_response_received(lpn); + + iv_index = sys_be32_to_cpu(msg->iv_index); + + LOG_DBG("flags 0x%02x iv_index 0x%08x md %u", msg->flags, iv_index, + msg->md); + + bt_mesh_kr_update(sub, BT_MESH_KEY_REFRESH(msg->flags), rx->new_key); + bt_mesh_net_iv_update(iv_index, BT_MESH_IV_UPDATE(msg->flags)); + + if (lpn->groups_changed) + { + sub_update(TRANS_CTL_OP_FRIEND_SUB_ADD); + sub_update(TRANS_CTL_OP_FRIEND_SUB_REM); + + if (!lpn->sent_req) + { + lpn->groups_changed = 0U; + } + } + + if (msg->md) + { + LOG_DBG("Requesting for more messages"); + send_friend_poll(); + } + + if (established) + { + STRUCT_SECTION_FOREACH(bt_mesh_lpn_cb, cb) + { + if (cb->established) + { + cb->established(lpn->sub->net_idx, lpn->frnd, lpn->queue_size, + lpn->recv_win); + } + } + } + + return 0; +} + +int bt_mesh_lpn_poll(void) +{ + if (!bt_mesh.lpn.established) + { + return -EAGAIN; + } + + LOG_DBG("Requesting more messages"); + + return send_friend_poll(); +} + +static void subnet_evt(struct bt_mesh_subnet *sub, enum bt_mesh_key_evt evt) +{ + switch (evt) + { + case BT_MESH_KEY_DELETED: + if (sub == bt_mesh.lpn.sub) + { + LOG_DBG("NetKey deleted"); + clear_friendship(true, false); + } + break; + case BT_MESH_KEY_UPDATED: + LOG_DBG("NetKey updated"); + friend_cred_create(&bt_mesh.lpn.cred[1], &sub->keys[1].net); + break; + default: + break; + } +} + +BT_MESH_SUBNET_CB_DEFINE(lpn) = +{ + .evt_handler = subnet_evt, +}; + +int bt_mesh_lpn_init(void) +{ + struct bt_mesh_lpn *lpn = &bt_mesh.lpn; + + LOG_DBG(""); + + k_work_init_delayable(&lpn->timer, lpn_timeout); + + if (lpn->state == BT_MESH_LPN_ENABLED) + { + if (IS_ENABLED(CONFIG_BT_MESH_LPN_ESTABLISHMENT)) + { + bt_mesh_scan_disable(); + } + else + { + bt_mesh_scan_enable(); + } + + send_friend_req(lpn); + } + else + { + bt_mesh_scan_enable(); + + if (IS_ENABLED(CONFIG_BT_MESH_LPN_AUTO)) + { + LOG_DBG("Waiting %u ms for messages", LPN_AUTO_TIMEOUT); + lpn_set_state(BT_MESH_LPN_TIMER); + k_work_reschedule(&lpn->timer, MSEC2TICK(LPN_AUTO_TIMEOUT)); + } + } + + return 0; +} diff --git a/libs/zblue/mesh/lpn.h b/libs/zblue/mesh/lpn.h new file mode 100644 index 0000000000000..7ccf947909536 --- /dev/null +++ b/libs/zblue/mesh/lpn.h @@ -0,0 +1,68 @@ +/**************************************************************************** + * libs/zblue/mesh/lpn.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +int bt_mesh_lpn_friend_update(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf); +int bt_mesh_lpn_friend_offer(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf); +int bt_mesh_lpn_friend_clear_cfm(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf); +int bt_mesh_lpn_friend_sub_cfm(struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf); + +static inline bool bt_mesh_lpn_established(void) +{ +#if defined(CONFIG_BT_MESH_LOW_POWER) + return bt_mesh.lpn.established; +#else + return false; +#endif +} + +static inline bool bt_mesh_lpn_match(uint16_t addr) +{ +#if defined(CONFIG_BT_MESH_LOW_POWER) + if (bt_mesh_lpn_established()) + { + return (addr == bt_mesh.lpn.frnd); + } +#endif + return false; +} + +static inline bool bt_mesh_lpn_waiting_update(void) +{ +#if defined(CONFIG_BT_MESH_LOW_POWER) + return (bt_mesh.lpn.state == BT_MESH_LPN_WAIT_UPDATE); +#else + return false; +#endif +} + +static inline bool bt_mesh_lpn_clearing(void) +{ +#if defined(CONFIG_BT_MESH_LOW_POWER) + return (bt_mesh.lpn.state == BT_MESH_LPN_CLEAR); +#else + return false; +#endif +} + +void bt_mesh_lpn_msg_received(struct bt_mesh_net_rx *rx); + +void bt_mesh_lpn_group_add(uint16_t group); +void bt_mesh_lpn_group_del(const uint16_t *groups, size_t group_count); + +void bt_mesh_lpn_disable(bool force); +void bt_mesh_lpn_friendship_end(void); + +int bt_mesh_lpn_init(void); diff --git a/libs/zblue/mesh/main.c b/libs/zblue/mesh/main.c new file mode 100644 index 0000000000000..fb0d292ee5aa1 --- /dev/null +++ b/libs/zblue/mesh/main.c @@ -0,0 +1,797 @@ +/**************************************************************************** + * libs/zblue/mesh/main.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "test.h" +#include "prov.h" +#include "provisioner.h" +#include "net.h" +#include "subnet.h" +#include "app_keys.h" +#include "rpl.h" +#include "cfg.h" +#include "beacon.h" +#include "lpn.h" +#include "friend.h" +#include "transport.h" +#include "heartbeat.h" +#include "access.h" +#include "foundation.h" +#include "proxy.h" +#include "pb_gatt_srv.h" +#include "settings.h" +#include "mesh.h" +#include "solicitation.h" +#include "gatt_cli.h" +#include "crypto.h" + +LOG_MODULE_REGISTER(bt_mesh_main, CONFIG_BT_MESH_LOG_LEVEL); + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bt_mesh_provision( + const uint8_t net_key[16], uint16_t net_idx, uint8_t flags, + uint32_t iv_index, uint16_t addr, const uint8_t dev_key[16]) +{ + struct bt_mesh_key mesh_dev_key; + struct bt_mesh_key mesh_net_key; + bool is_net_key_valid = false; + bool is_dev_key_valid = false; + int err = 0; + + if (!atomic_test_bit(bt_mesh.flags, BT_MESH_INIT)) + { + return -ENODEV; + } + + struct bt_mesh_cdb_subnet *subnet = NULL; + struct bt_mesh_cdb_node *node = NULL; + + LOG_INF("Primary Element: 0x%04x", addr); + LOG_DBG("net_idx 0x%04x flags 0x%02x iv_index 0x%04x", net_idx, flags, + iv_index); + + if (atomic_test_bit(bt_mesh.flags, BT_MESH_VALID)) + { + return -EALREADY; + } + + if (IS_ENABLED(CONFIG_BT_MESH_CDB) && + atomic_test_bit(bt_mesh_cdb.flags, BT_MESH_CDB_VALID)) + { + const struct bt_mesh_comp *comp; + const struct bt_mesh_prov *prov; + + comp = bt_mesh_comp_get(); + if (comp == NULL) + { + LOG_ERR("Failed to get node composition"); + return -EINVAL; + } + + subnet = bt_mesh_cdb_subnet_get(net_idx); + if (!subnet) + { + LOG_ERR("No subnet with idx %d", net_idx); + return -ENOENT; + } + + prov = bt_mesh_prov_get(); + node = bt_mesh_cdb_node_alloc(prov->uuid, addr, comp->elem_count, + net_idx); + if (node == NULL) + { + LOG_ERR("Failed to allocate database node"); + return -ENOMEM; + } + + if (BT_MESH_KEY_REFRESH(flags)) + { + subnet->kr_phase = BT_MESH_KR_PHASE_2; + } + else + { + subnet->kr_phase = BT_MESH_KR_NORMAL; + } + + /* The primary network key has been imported during cdb creation. + * Importing here leaves it 'as is' if the key is the same. Otherwise, + * cdb replaces the old one with the new one. + */ + + err = bt_mesh_cdb_subnet_key_import(subnet, + BT_MESH_KEY_REFRESH(flags) ? 1 : 0, net_key); + if (err) + { + LOG_ERR("Failed to import cdb network key"); + goto error_exit; + } + + addr = node->addr; + bt_mesh_cdb_iv_update(iv_index, BT_MESH_IV_UPDATE(flags)); + + err = bt_mesh_cdb_node_key_import(node, dev_key); + if (err) + { + LOG_ERR("Failed to import cdb device key"); + goto error_exit; + } + } + + err = bt_mesh_key_import(BT_MESH_KEY_TYPE_DEV, dev_key, &mesh_dev_key); + if (err) + { + LOG_ERR("Failed to import device key"); + goto error_exit; + } + + is_dev_key_valid = true; + + err = bt_mesh_key_import(BT_MESH_KEY_TYPE_NET, net_key, &mesh_net_key); + if (err) + { + LOG_ERR("Failed to import network key"); + goto error_exit; + } + + is_net_key_valid = true; + + err = bt_mesh_net_create(net_idx, flags, &mesh_net_key, iv_index); + if (err) + { + LOG_ERR("Failed to create network"); + goto error_exit; + } + + if (IS_ENABLED(CONFIG_BT_MESH_CDB) && + atomic_test_bit(bt_mesh_cdb.flags, BT_MESH_CDB_VALID)) + { + bt_mesh_cdb_subnet_store(subnet); + bt_mesh_cdb_node_store(node); + } + + bt_mesh_net_settings_commit(); + + bt_mesh.seq = 0U; + + bt_mesh_comp_provision(addr); + + memcpy(&bt_mesh.dev_key, &mesh_dev_key, sizeof(struct bt_mesh_key)); + + if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER) && + IS_ENABLED(CONFIG_BT_MESH_LPN_SUB_ALL_NODES_ADDR)) + { + bt_mesh_lpn_group_add(BT_MESH_ADDR_ALL_NODES); + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bt_mesh_net_store(); + } + + atomic_set_bit(bt_mesh.flags, BT_MESH_VALID); + bt_mesh_start(); + + return 0; + +error_exit: + if (node != NULL && IS_ENABLED(CONFIG_BT_MESH_CDB)) + { + bt_mesh_cdb_node_del(node, false); + } + + if (is_dev_key_valid) + { + bt_mesh_key_destroy(&mesh_dev_key); + } + + if (is_net_key_valid) + { + bt_mesh_key_destroy(&mesh_net_key); + } + + return err; +} + +#if defined(CONFIG_BT_MESH_RPR_SRV) +void bt_mesh_reprovision(uint16_t addr) +{ + LOG_DBG("0x%04x devkey: %s", addr, bt_hex(&bt_mesh.dev_key_cand, + sizeof(struct bt_mesh_key))); + + if (addr != bt_mesh_primary_addr()) + { + bt_mesh.seq = 0U; + + bt_mesh_comp_provision(addr); + bt_mesh_trans_reset(); + + if (IS_ENABLED(CONFIG_BT_MESH_FRIEND)) + { + bt_mesh_friends_clear(); + } + + if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER)) + { + bt_mesh_lpn_friendship_end(); + } + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + LOG_DBG("Storing network information persistently"); + bt_mesh_net_store(); + bt_mesh_net_seq_store(true); + bt_mesh_comp_data_clear(); + } +} + +void bt_mesh_dev_key_cand(const uint8_t *key) +{ + int err; + + LOG_DBG("%s", bt_hex(key, 16)); + + err = bt_mesh_key_import(BT_MESH_KEY_TYPE_DEV, key, &bt_mesh.dev_key_cand); + if (err) + { + LOG_ERR("Failed to import device key candidate"); + return; + } + + atomic_set_bit(bt_mesh.flags, BT_MESH_DEVKEY_CAND); + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bt_mesh_net_dev_key_cand_store(); + } +} + +void bt_mesh_dev_key_cand_remove(void) +{ + if (!atomic_test_and_clear_bit(bt_mesh.flags, BT_MESH_DEVKEY_CAND)) + { + return; + } + + LOG_DBG(""); + bt_mesh_key_destroy(&bt_mesh.dev_key_cand); + memset(&bt_mesh.dev_key_cand, 0, sizeof(struct bt_mesh_key)); + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bt_mesh_net_dev_key_cand_store(); + } +} + +void bt_mesh_dev_key_cand_activate(void) +{ + if (!atomic_test_and_clear_bit(bt_mesh.flags, BT_MESH_DEVKEY_CAND)) + { + return; + } + + bt_mesh_key_destroy(&bt_mesh.dev_key); + memcpy(&bt_mesh.dev_key, &bt_mesh.dev_key_cand, + sizeof(struct bt_mesh_key)); + memset(&bt_mesh.dev_key_cand, 0, sizeof(struct bt_mesh_key)); + + LOG_DBG(""); + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bt_mesh_net_pending_net_store(); + bt_mesh_net_dev_key_cand_store(); + } +} +#endif + +int bt_mesh_provision_adv( + const uint8_t uuid[16], uint16_t net_idx, uint16_t addr, + uint8_t attention_duration) +{ + if (!atomic_test_bit(bt_mesh.flags, BT_MESH_VALID)) + { + return -EINVAL; + } + + if (bt_mesh_subnet_get(net_idx) == NULL) + { + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_MESH_PROVISIONER) && + IS_ENABLED(CONFIG_BT_MESH_PB_ADV)) + { + return bt_mesh_pb_adv_open(uuid, net_idx, addr, attention_duration); + } + + return -ENOTSUP; +} + +int bt_mesh_provision_gatt( + const uint8_t uuid[16], uint16_t net_idx, uint16_t addr, + uint8_t attention_duration) +{ + if (!atomic_test_bit(bt_mesh.flags, BT_MESH_VALID)) + { + return -EINVAL; + } + + if (bt_mesh_subnet_get(net_idx) == NULL) + { + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_MESH_PB_GATT_CLIENT)) + { + return bt_mesh_pb_gatt_open(uuid, net_idx, addr, attention_duration); + } + + return -ENOTSUP; +} + +int bt_mesh_provision_remote( + struct bt_mesh_rpr_cli *cli, const struct bt_mesh_rpr_node *srv, + const uint8_t uuid[16], uint16_t net_idx, uint16_t addr) +{ + if (!atomic_test_bit(bt_mesh.flags, BT_MESH_VALID)) + { + return -EINVAL; + } + + if (bt_mesh_subnet_get(net_idx) == NULL) + { + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_MESH_PROVISIONER) && + IS_ENABLED(CONFIG_BT_MESH_RPR_CLI)) + { + return bt_mesh_pb_remote_open(cli, srv, uuid, net_idx, addr); + } + + return -ENOTSUP; +} + +int bt_mesh_reprovision_remote( + struct bt_mesh_rpr_cli *cli, struct bt_mesh_rpr_node *srv, uint16_t addr, + bool comp_change) +{ + if (!atomic_test_bit(bt_mesh.flags, BT_MESH_VALID)) + { + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_MESH_PROVISIONER) && + IS_ENABLED(CONFIG_BT_MESH_RPR_CLI)) + { + return bt_mesh_pb_remote_open_node(cli, srv, addr, comp_change); + } + + return -ENOTSUP; +} + +void bt_mesh_reset(void) +{ + if (!atomic_test_bit(bt_mesh.flags, BT_MESH_VALID) || + !atomic_test_bit(bt_mesh.flags, BT_MESH_INIT)) + { + return; + } + + bt_mesh.iv_index = 0U; + bt_mesh.ivu_duration = 0; + bt_mesh.seq = 0U; + + memset(bt_mesh.flags, 0, sizeof(bt_mesh.flags)); + atomic_set_bit(bt_mesh.flags, BT_MESH_INIT); + + bt_mesh_scan_disable(); + + /* If this fails, the work handler will return early on the next + * execution, as the device is not provisioned. If the device is + * reprovisioned, the timer is always restarted. + */ + + (void)k_work_cancel_delayable(&bt_mesh.ivu_timer); + + bt_mesh_access_reset(); + bt_mesh_model_reset(); + bt_mesh_cfg_default_set(); + bt_mesh_trans_reset(); + bt_mesh_app_keys_reset(); + bt_mesh_net_keys_reset(); + + bt_mesh_net_loopback_clear(BT_MESH_KEY_ANY); + + if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER)) + { + if (IS_ENABLED(CONFIG_BT_MESH_LPN_SUB_ALL_NODES_ADDR)) + { + uint16_t group = BT_MESH_ADDR_ALL_NODES; + + bt_mesh_lpn_group_del(&group, 1); + } + + bt_mesh_lpn_disable(true); + } + + if (IS_ENABLED(CONFIG_BT_MESH_FRIEND)) + { + bt_mesh_friends_clear(); + } + + if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY)) + { + (void)bt_mesh_proxy_gatt_disable(); + } + + if (IS_ENABLED(CONFIG_BT_MESH_GATT_CLIENT)) + { + bt_mesh_gatt_client_deinit(); + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bt_mesh_net_clear(); + } + + bt_mesh_key_destroy(&bt_mesh.dev_key); + memset(&bt_mesh.dev_key, 0, sizeof(bt_mesh.dev_key)); + + if (IS_ENABLED(CONFIG_BT_MESH_RPR_SRV)) + { + bt_mesh_dev_key_cand_remove(); + } + + bt_mesh_beacon_disable(); + + bt_mesh_comp_unprovision(); + + if (IS_ENABLED(CONFIG_BT_MESH_PROXY_SOLICITATION)) + { + bt_mesh_sol_reset(); + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bt_mesh_settings_store_pending(); + } + + if (IS_ENABLED(CONFIG_BT_MESH_PROV)) + { + bt_mesh_prov_reset(); + } +} + +bool bt_mesh_is_provisioned(void) +{ + return atomic_test_bit(bt_mesh.flags, BT_MESH_VALID); +} + +static void model_suspend( + const struct bt_mesh_model *mod, const struct bt_mesh_elem *elem, bool vnd, + bool primary, void *user_data) +{ + if (mod->pub && mod->pub->update) + { + mod->pub->count = 0U; + /* If this fails, the work handler will check the suspend call + * and exit without transmitting. + */ + + (void)k_work_cancel_delayable(&mod->pub->timer); + } +} + +int bt_mesh_suspend(void) +{ + int err; + + if (!atomic_test_bit(bt_mesh.flags, BT_MESH_VALID)) + { + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_MESH_PROV) && bt_mesh_prov_active()) + { + return -EBUSY; + } + + if (atomic_test_and_set_bit(bt_mesh.flags, BT_MESH_SUSPENDED)) + { + return -EALREADY; + } + + err = bt_mesh_scan_disable(); + if (err) + { + atomic_clear_bit(bt_mesh.flags, BT_MESH_SUSPENDED); + LOG_WRN("Disabling scanning failed (err %d)", err); + return err; + } + + if (IS_ENABLED(CONFIG_BT_MESH_GATT_CLIENT)) + { + bt_mesh_proxy_disconnect(BT_MESH_KEY_ANY); + } + + bt_mesh_hb_suspend(); + + bt_mesh_beacon_disable(); + + bt_mesh_model_foreach(model_suspend, NULL); + + bt_mesh_access_suspend(); + +#if defined(CONFIG_BT_MESH_PROVISIONEE) + err = bt_mesh_provisionee_suspend(); + if (err && err != -EALREADY) + { + LOG_WRN("Suspending provisioning bearers failed (err %d)", err); + atomic_clear_bit(bt_mesh.flags, BT_MESH_SUSPENDED); + return err; + } +#endif + + if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY)) + { + err = bt_mesh_proxy_gatt_disable(); + if (err && err != -EALREADY) + { + LOG_WRN("Disabling GATT proxy failed (err %d)", err); + return err; + } + } + + err = bt_mesh_adv_disable(); + if (err) + { + atomic_clear_bit(bt_mesh.flags, BT_MESH_SUSPENDED); + LOG_WRN("Disabling advertisers failed (err %d)", err); + return err; + } + + return 0; +} + +static void model_resume( + const struct bt_mesh_model *mod, const struct bt_mesh_elem *elem, bool vnd, + bool primary, void *user_data) +{ + if (mod->pub && mod->pub->update) + { + int32_t period_ms = bt_mesh_model_pub_period_get(mod); + + if (period_ms) + { + k_work_reschedule(&mod->pub->timer, MSEC2TICK(period_ms)); + } + } +} + +int bt_mesh_resume(void) +{ + int err; + + if (!atomic_test_bit(bt_mesh.flags, BT_MESH_VALID)) + { + return -EINVAL; + } + + if (!atomic_test_and_clear_bit(bt_mesh.flags, BT_MESH_SUSPENDED)) + { + return -EALREADY; + } + + if (!IS_ENABLED(CONFIG_BT_EXT_ADV)) + { + bt_mesh_adv_init(); + } + + err = bt_mesh_adv_enable(); + if (err) + { + atomic_set_bit(bt_mesh.flags, BT_MESH_SUSPENDED); + LOG_WRN("Re-enabling advertisers failed (err %d)", err); + return err; + } + + err = bt_mesh_scan_enable(); + if (err) + { + LOG_WRN("Re-enabling scanning failed (err %d)", err); + atomic_set_bit(bt_mesh.flags, BT_MESH_SUSPENDED); + return err; + } + + if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY) && bt_mesh_is_provisioned()) + { + err = bt_mesh_proxy_gatt_enable(); + if (err) + { + LOG_WRN("Re-enabling GATT proxy failed (err %d)", err); + return err; + } + } + +#if defined(CONFIG_BT_MESH_PROVISIONEE) + err = bt_mesh_provisionee_resume(); + if (err) + { + LOG_WRN("Resuming provisioning bearers failed (err %d)", err); + atomic_set_bit(bt_mesh.flags, BT_MESH_SUSPENDED); + return err; + } +#endif + + bt_mesh_hb_resume(); + + if (bt_mesh_beacon_enabled() || + bt_mesh_priv_beacon_get() == BT_MESH_PRIV_BEACON_ENABLED) + { + bt_mesh_beacon_enable(); + } + + bt_mesh_model_foreach(model_resume, NULL); + + bt_mesh_adv_gatt_update(); + return 0; +} + +int bt_mesh_init(const struct bt_mesh_prov *prov, + const struct bt_mesh_comp *comp) +{ + int err; + + if (atomic_test_and_set_bit(bt_mesh.flags, BT_MESH_INIT)) + { + return -EALREADY; + } + + err = bt_mesh_test(); + if (err) + { + return err; + } + + err = bt_mesh_crypto_init(); + if (err) + { + return err; + } + + err = bt_mesh_comp_register(comp); + if (err) + { + return err; + } + + if (IS_ENABLED(CONFIG_BT_MESH_PROV)) + { + err = bt_mesh_prov_init(prov); + if (err) + { + return err; + } + } + + bt_mesh_cfg_default_set(); + bt_mesh_net_init(); + bt_mesh_trans_init(); + bt_mesh_access_init(); + bt_mesh_hb_init(); + bt_mesh_beacon_init(); + bt_mesh_adv_init(); + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bt_mesh_settings_init(); + } + + return 0; +} + +static void model_start( + const struct bt_mesh_model *mod, const struct bt_mesh_elem *elem, bool vnd, + bool primary, void *user_data) +{ + if (mod->cb && mod->cb->start) + { + mod->cb->start(mod); + } +} + +int bt_mesh_start(void) +{ + int err; + + err = bt_mesh_adv_enable(); + if (err) + { + LOG_ERR("Failed enabling advertiser"); + return err; + } + + if (bt_mesh_beacon_enabled() || + bt_mesh_priv_beacon_get() == BT_MESH_PRIV_BEACON_ENABLED) + { + bt_mesh_beacon_enable(); + } + + if (!IS_ENABLED(CONFIG_BT_MESH_LOW_POWER)) + { + bt_mesh_scan_enable(); + } + + if (!IS_ENABLED(CONFIG_BT_MESH_PROV) || !bt_mesh_prov_active() || + bt_mesh_prov_link.bearer->type == BT_MESH_PROV_ADV) + { + if (IS_ENABLED(CONFIG_BT_MESH_PB_GATT)) + { + (void)bt_mesh_pb_gatt_srv_disable(); + } + + if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY)) + { + (void)bt_mesh_proxy_gatt_enable(); + } + } + + if (IS_ENABLED(CONFIG_BT_MESH_GATT_CLIENT)) + { + bt_mesh_gatt_client_init(); + } + + if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER)) + { + bt_mesh_lpn_init(); + } + + if (IS_ENABLED(CONFIG_BT_MESH_FRIEND)) + { + bt_mesh_friend_init(); + } + + if (IS_ENABLED(CONFIG_BT_MESH_PROV)) + { + struct bt_mesh_subnet *sub = bt_mesh_subnet_next(NULL); + uint16_t addr = bt_mesh_primary_addr(); + + bt_mesh_prov_complete(sub->net_idx, addr); + } + + bt_mesh_hb_start(); + + bt_mesh_model_foreach(model_start, NULL); + + return 0; +} diff --git a/libs/zblue/mesh/mesh.h b/libs/zblue/mesh/mesh.h new file mode 100644 index 0000000000000..5198ffde737ff --- /dev/null +++ b/libs/zblue/mesh/mesh.h @@ -0,0 +1,19 @@ +/**************************************************************************** + * libs/zblue/mesh/mesh.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +struct bt_mesh_net; + +int bt_mesh_start(void); +void bt_mesh_reprovision(uint16_t addr); +void bt_mesh_dev_key_cand(const uint8_t *key); +void bt_mesh_dev_key_cand_remove(void); +void bt_mesh_dev_key_cand_activate(void); diff --git a/libs/zblue/mesh/msg.c b/libs/zblue/mesh/msg.c new file mode 100644 index 0000000000000..67879ea17f290 --- /dev/null +++ b/libs/zblue/mesh/msg.c @@ -0,0 +1,166 @@ +/**************************************************************************** + * libs/zblue/mesh/msg.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include "msg.h" + +#define LOG_LEVEL CONFIG_BT_MESH_ACCESS_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_msg); + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_mesh_model_msg_init(struct net_buf_simple *msg, uint32_t opcode) +{ + net_buf_simple_init(msg, 0); + + switch (BT_MESH_MODEL_OP_LEN(opcode)) + { + case 1: + net_buf_simple_add_u8(msg, opcode); + break; + case 2: + net_buf_simple_add_be16(msg, opcode); + break; + case 3: + net_buf_simple_add_u8(msg, ((opcode >> 16) & 0xff)); + /* Using LE for the CID since the model layer is defined as + * little-endian in the mesh spec and using BT_MESH_MODEL_OP_3 will + * declare the opcode in this way. + */ + + net_buf_simple_add_le16(msg, opcode & 0xffff); + break; + default: + LOG_WRN("Unknown opcode format"); + break; + } +} + +void bt_mesh_msg_ack_ctx_clear(struct bt_mesh_msg_ack_ctx *ack) +{ + ack->op = 0U; + ack->user_data = NULL; + ack->dst = BT_MESH_ADDR_UNASSIGNED; +} + +int bt_mesh_msg_ack_ctx_prepare( + struct bt_mesh_msg_ack_ctx *ack, uint32_t op, uint16_t dst, + void *user_data) +{ + if (ack->op) + { + LOG_WRN("Another synchronous operation pending"); + return -EBUSY; + } + + ack->op = op; + ack->user_data = user_data; + ack->dst = dst; + + return 0; +} + +int bt_mesh_msg_ack_ctx_wait(struct bt_mesh_msg_ack_ctx *ack, + clock_t timeout) +{ + int err; + + err = nxsem_tickwait(&ack->sem, timeout); + bt_mesh_msg_ack_ctx_clear(ack); + + if (err == -EAGAIN) + { + return -ETIMEDOUT; + } + + return err; +} + +bool bt_mesh_msg_ack_ctx_match( + const struct bt_mesh_msg_ack_ctx *ack, uint32_t op, uint16_t addr, + void **user_data) +{ + if (ack->op != op || (BT_MESH_ADDR_IS_UNICAST(ack->dst) && + ack->dst != addr)) + { + return false; + } + + if (user_data != NULL) + { + *user_data = ack->user_data; + } + + return true; +} + +int bt_mesh_msg_send( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + if (!ctx && !model->pub) + { + return -ENOTSUP; + } + + if (ctx) + { + return bt_mesh_model_send(model, ctx, buf, NULL, 0); + } + + net_buf_simple_reset(model->pub->msg); + net_buf_simple_add_mem(model->pub->msg, buf->data, buf->len); + + return bt_mesh_model_publish(model); +} + +int bt_mesh_msg_ackd_send( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf, const struct bt_mesh_msg_rsp_ctx *rsp) +{ + int err; + + if (rsp) + { + err = bt_mesh_msg_ack_ctx_prepare(rsp->ack, rsp->op, + ctx ? ctx->addr : model->pub->addr, + rsp->user_data); + if (err) + { + return err; + } + } + + err = bt_mesh_msg_send(model, ctx, buf); + + if (!rsp) + { + return err; + } + + if (!err) + { + return bt_mesh_msg_ack_ctx_wait(rsp->ack, MSEC2TICK(rsp->timeout)); + } + + bt_mesh_msg_ack_ctx_clear(rsp->ack); + + return err; +} diff --git a/libs/zblue/mesh/msg.h b/libs/zblue/mesh/msg.h new file mode 100644 index 0000000000000..70045276aef5e --- /dev/null +++ b/libs/zblue/mesh/msg.h @@ -0,0 +1,76 @@ +/**************************************************************************** + * libs/zblue/mesh/msg.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** @brief Send a model message. + * + * Sends a model message with the given context. If the message context is + * NULL, this updates the publish message, and publishes with the configured + * publication parameters. + * + * @param model Model to send the message on. + * @param ctx Message context, or NULL to send with the configured publish + * parameters. + * @param buf Message to send. + * + * @retval 0 The message was sent successfully. + * @retval -ENOTSUP A message context was not provided and publishing is not + * supported. + * @retval -EADDRNOTAVAIL A message context was not provided and publishing + * is not configured. + * @retval -EAGAIN The device has not been provisioned. + */ + +int bt_mesh_msg_send(const struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf); + +/** + * Message response context. + */ + +struct bt_mesh_msg_rsp_ctx +{ + struct bt_mesh_msg_ack_ctx *ack; /**< Acknowledged message context. */ + uint32_t op; /**< Opcode we're waiting for. */ + void *user_data; /**< User specific parameter. */ + int32_t timeout; /**< Response timeout in milliseconds. */ +}; + +/** @brief Send an acknowledged model message. + * + * Sends a model message with the given context. If the message context is + * NULL, this updates the publish message, and publishes with the configured + * publication parameters. + * + * If a response context is provided, the call blocks for the time specified + * in the response context, or until @ref bt_mesh_msg_ack_ctx_rx is called. + * + * @param model Model to send the message on. + * @param ctx Message context, or NULL to send with the configured publish + * parameters. + * @param buf Message to send. + * @param rsp Message response context, or NULL if no response is expected. + * + * @retval 0 The message was sent successfully. + * @retval -EBUSY A blocking request is already in progress. + * @retval -ENOTSUP A message context was not provided and publishing is not + * supported. + * @retval -EADDRNOTAVAIL A message context was not provided and publishing + * is not configured. + * @retval -EAGAIN The device has not been provisioned. + * @retval -ETIMEDOUT The request timed out without a response. + */ + +int bt_mesh_msg_ackd_send(const struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf, + const struct bt_mesh_msg_rsp_ctx *rsp); diff --git a/libs/zblue/mesh/net.c b/libs/zblue/mesh/net.c new file mode 100644 index 0000000000000..71aacad3c207d --- /dev/null +++ b/libs/zblue/mesh/net.c @@ -0,0 +1,1597 @@ +/**************************************************************************** + * libs/zblue/mesh/net.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "common/bt_str.h" + +#include "crypto.h" +#include "mesh.h" +#include "net.h" +#include "rpl.h" +#include "lpn.h" +#include "friend.h" +#include "proxy.h" +#include "proxy_cli.h" +#include "transport.h" +#include "access.h" +#include "foundation.h" +#include "beacon.h" +#include "settings.h" +#include "prov.h" +#include "cfg.h" +#include "statistic.h" +#include "sar_cfg_internal.h" +#include "brg_cfg.h" + +#define LOG_LEVEL CONFIG_BT_MESH_NET_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_net); + +#define LOOPBACK_MAX_PDU_LEN (BT_MESH_NET_HDR_LEN + 16) + +/* Seq limit after IV Update is triggered */ +#define IV_UPDATE_SEQ_LIMIT CONFIG_BT_MESH_IV_UPDATE_SEQ_LIMIT + +/* Information needed for bridging the network PDUs */ + +struct pdu_ctx +{ + struct net_buf_simple *sbuf; + struct net_buf_simple_state *state; + struct bt_mesh_net_rx *rx; +}; + +/* Mesh network information for persistent storage. */ + +struct net_val +{ + uint16_t primary_addr; + struct bt_mesh_key dev_key; +} __packed; + +/* Sequence number information for persistent storage. */ + +struct seq_val +{ + uint8_t val[3]; +} __packed; + +/* IV Index & IV Update information for persistent storage. */ + +struct iv_val +{ + uint32_t iv_index; + uint8_t iv_update : 1; + uint8_t iv_duration : 7; +} __packed; + +static struct +{ + uint32_t src : 15; /* MSb of source is always 0 */ + uint32_t seq : 17; + uint16_t net_idx; +} + +msg_cache[CONFIG_BT_MESH_MSG_CACHE_SIZE]; +static uint16_t msg_cache_next; + +/* Singleton network context (the implementation only supports one) */ + +struct bt_mesh_net bt_mesh = +{ + .local_queue = LIST_INITIAL_VALUE(bt_mesh.local_queue), + .sar_tx = BT_MESH_SAR_TX_INIT, + .sar_rx = BT_MESH_SAR_RX_INIT, + +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) + .priv_beacon_int = 0x3c, +#endif +}; + +/* MshPRTv1.1: 3.11.5: + * "A node shall not start an IV Update procedure more often than once every + * 192 hours." + * + * Mark that the IV Index Recovery has been done to prevent two recoveries to + * be done before a normal IV Index update has been completed within 96h+96h. + */ + +static bool ivi_was_recovered; + +struct loopback_buf +{ + struct list_node node; + struct bt_mesh_subnet *sub; + uint8_t len; + uint8_t data[LOOPBACK_MAX_PDU_LEN]; +}; + +K_MEM_SLAB_DEFINE_TYPE(loopback_buf_pool, + struct loopback_buf, + CONFIG_BT_MESH_LOOPBACK_BUFS); + +static uint32_t dup_cache[CONFIG_BT_MESH_MSG_CACHE_SIZE]; +static int dup_cache_next; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static uint8_t pdu_ivi(const uint8_t *pdu) +{ + return (pdu[0] >> 7); +} + +static uint8_t pdu_nid(const uint8_t *pdu) +{ + return (pdu[0] & 0x7f); +} + +static uint8_t pdu_ctl(const uint8_t *pdu) +{ + return (pdu[1] >> 7); +} + +static uint8_t pdu_ttl(const uint8_t *pdu) +{ + return (pdu[1] & 0x7f); +} + +static uint32_t pdu_seq(const uint8_t *pdu) +{ + return sys_get_be24(&pdu[2]); +} + +static uint16_t pdu_src(const uint8_t *pdu) +{ + return sys_get_be16(&pdu[5]); +} + +static uint16_t pdu_dst(const uint8_t *pdu) +{ + return sys_get_be16(&pdu[7]); +} + +static bool check_dup(struct net_buf_simple *data) +{ + const uint8_t *tail = net_buf_simple_tail(data); + uint32_t val; + int i; + + val = sys_get_be32(tail - 4) ^ sys_get_be32(tail - 8); + + for (i = dup_cache_next; i > 0; ) + { + if (dup_cache[--i] == val) + { + return true; + } + } + + for (i = ARRAY_SIZE(dup_cache); i > dup_cache_next; ) + { + if (dup_cache[--i] == val) + { + return true; + } + } + + dup_cache_next %= ARRAY_SIZE(dup_cache); + dup_cache[dup_cache_next++] = val; + + return false; +} + +static bool msg_cache_match(struct net_buf_simple *pdu, uint16_t net_idx) +{ + uint16_t i; + + for (i = msg_cache_next; i > 0U; ) + { + if (msg_cache[--i].src == pdu_src(pdu->data) && + msg_cache[i].seq == (pdu_seq(pdu->data) & BIT_MASK(17)) && + msg_cache[i].net_idx == net_idx) + { + return true; + } + } + + for (i = ARRAY_SIZE(msg_cache); i > msg_cache_next; ) + { + if (msg_cache[--i].src == pdu_src(pdu->data) && + msg_cache[i].seq == (pdu_seq(pdu->data) & BIT_MASK(17)) && + msg_cache[i].net_idx == net_idx) + { + return true; + } + } + + return false; +} + +static void msg_cache_add(struct bt_mesh_net_rx *rx) +{ + msg_cache_next %= ARRAY_SIZE(msg_cache); + msg_cache[msg_cache_next].src = rx->ctx.addr; + msg_cache[msg_cache_next].seq = rx->seq; + msg_cache[msg_cache_next].net_idx = rx->sub->net_idx; + msg_cache_next++; +} + +static void store_iv(bool only_duration) +{ + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_IV_PENDING); + + if (!only_duration) + { + /* Always update Seq whenever IV changes */ + + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_SEQ_PENDING); + } +} + +void bt_mesh_net_seq_store(bool force) +{ + if (!force && CONFIG_BT_MESH_SEQ_STORE_RATE > 1 && + (bt_mesh.seq % CONFIG_BT_MESH_SEQ_STORE_RATE)) + { + return; + } + + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_SEQ_PENDING); +} + +int bt_mesh_net_create( + uint16_t idx, uint8_t flags, const struct bt_mesh_key *key, + uint32_t iv_index) +{ + int err; + + LOG_DBG("idx %u flags 0x%02x iv_index %u", idx, flags, iv_index); + + LOG_DBG("NetKey %s", bt_hex(key, sizeof(struct bt_mesh_key))); + + if (BT_MESH_KEY_REFRESH(flags)) + { + err = bt_mesh_subnet_set(idx, BT_MESH_KR_PHASE_2, NULL, key); + } + else + { + err = bt_mesh_subnet_set(idx, BT_MESH_KR_NORMAL, key, NULL); + } + + if (err) + { + LOG_ERR("Failed creating subnet"); + return err; + } + + (void)memset(msg_cache, 0, sizeof(msg_cache)); + msg_cache_next = 0U; + + bt_mesh.iv_index = iv_index; + atomic_set_bit_to(bt_mesh.flags, BT_MESH_IVU_IN_PROGRESS, + BT_MESH_IV_UPDATE(flags)); + + /* If the node is added to a network when the network is in Normal + * operation, then it shall operate in Normal operation for at least 96 + * hours. If a node is added to a network while the network is in the IV + * Update in Progress state, then the node shall be given the new IV Index + * value and operate in IV Update in Progress operation without the + * restriction of being in this state for at least 96 hours. + */ + + if (BT_MESH_IV_UPDATE(flags)) + { + bt_mesh.ivu_duration = BT_MESH_IVU_MIN_HOURS; + } + else + { + bt_mesh.ivu_duration = 0U; + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + LOG_DBG("Storing network information persistently"); + bt_mesh_subnet_store(idx); + store_iv(false); + } + + return 0; +} + +#if defined(CONFIG_BT_MESH_IV_UPDATE_TEST) +void bt_mesh_iv_update_test(bool enable) +{ + atomic_set_bit_to(bt_mesh.flags, BT_MESH_IVU_TEST, enable); + + /* Reset the duration variable - needed for some PTS tests */ + + bt_mesh.ivu_duration = 0U; + /* ivu_refresh() drops the beacon cache when the 96-hour limit expires. + * Test mode lifts that limit without the timer running, so drop it here + * too, or a beacon cached while the limit was in force would keep being + * filtered as a duplicate. + */ + + bt_mesh_subnet_foreach(bt_mesh_beacon_cache_clear); +} + +bool bt_mesh_iv_update(void) +{ + if (!bt_mesh_is_provisioned()) + { + LOG_ERR("Not yet provisioned"); + return false; + } + + if (atomic_test_bit(bt_mesh.flags, BT_MESH_IVU_IN_PROGRESS)) + { + bt_mesh_net_iv_update(bt_mesh.iv_index, false); + } + else + { + bt_mesh_net_iv_update(bt_mesh.iv_index + 1, true); + } + + return atomic_test_bit(bt_mesh.flags, BT_MESH_IVU_IN_PROGRESS); +} +#endif /* CONFIG_BT_MESH_IV_UPDATE_TEST */ + +bool bt_mesh_net_iv_update(uint32_t iv_index, bool iv_update) +{ + /* Check if IV index should to be recovered. */ + + if (iv_index < bt_mesh.iv_index || iv_index > bt_mesh.iv_index + 42) + { + LOG_ERR("IV Index out of sync: 0x%08x != 0x%08x", iv_index, + bt_mesh.iv_index); + return false; + } + + /* Discard [iv, false] --> [iv, true] */ + + if (iv_index == bt_mesh.iv_index && iv_update) + { + LOG_DBG("Ignore previous IV update procedure"); + return false; + } + + if ((iv_index > bt_mesh.iv_index + 1) || + (iv_index == bt_mesh.iv_index + 1 && + (atomic_test_bit(bt_mesh.flags, + BT_MESH_IVU_IN_PROGRESS) || !iv_update))) + { + if (ivi_was_recovered && + (bt_mesh.ivu_duration < (2 * BT_MESH_IVU_MIN_HOURS))) + { + LOG_ERR("IV Index Recovery before minimum delay"); + return false; + } + + /* MshPRTv1.1 allows to initiate an + * IV Index Recovery procedure if previous IV update has been missed. + * This allows the node to remain functional. + * + * Upon receiving and successfully authenticating a Secure Network + * beacon for a primary subnet whose IV Index is 1 or more higher than + * the current known IV Index, the node shall set its current IV Index + * and its current IV Update procedure state from the values in this + * Secure Network beacon. + */ + + LOG_WRN("Performing IV Index Recovery"); + ivi_was_recovered = true; + bt_mesh_rpl_clear(); + bt_mesh.iv_index = iv_index; + bt_mesh.seq = 0U; + + goto do_update; + } + + if (atomic_test_bit(bt_mesh.flags, BT_MESH_IVU_IN_PROGRESS) == iv_update) + { + LOG_DBG("No change for IV Update procedure"); + return false; + } + + if (!(IS_ENABLED(CONFIG_BT_MESH_IV_UPDATE_TEST) && + atomic_test_bit(bt_mesh.flags, BT_MESH_IVU_TEST))) + { + if (bt_mesh.ivu_duration < BT_MESH_IVU_MIN_HOURS) + { + LOG_WRN("IV Update before minimum duration"); + return false; + } + } + + /* Defer change to Normal Operation if there are pending acks */ + + if (!iv_update && bt_mesh_tx_in_progress()) + { + LOG_WRN("IV Update deferred because of pending transfer"); + atomic_set_bit(bt_mesh.flags, BT_MESH_IVU_PENDING); + return false; + } + + if (iv_update) + { + bt_mesh.iv_index = iv_index; + LOG_DBG("IV Update state entered. New index 0x%08x", bt_mesh.iv_index); + + bt_mesh_rpl_reset(); + ivi_was_recovered = false; + } + else + { + LOG_DBG("Normal mode entered"); + bt_mesh.seq = 0U; + } + +do_update: + atomic_set_bit_to(bt_mesh.flags, BT_MESH_IVU_IN_PROGRESS, iv_update); + bt_mesh.ivu_duration = 0U; + + k_work_reschedule(&bt_mesh.ivu_timer, BT_MESH_IVU_TIMEOUT); + + /* Notify other modules */ + + if (IS_ENABLED(CONFIG_BT_MESH_FRIEND)) + { + bt_mesh_friend_sec_update(BT_MESH_KEY_ANY); + } + + bt_mesh_subnet_foreach(bt_mesh_beacon_update); + + if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY) && + (bt_mesh_gatt_proxy_get() == BT_MESH_GATT_PROXY_ENABLED || + bt_mesh_priv_gatt_proxy_get() == BT_MESH_PRIV_GATT_PROXY_ENABLED)) + { + bt_mesh_proxy_beacon_send(NULL); + } + + if (IS_ENABLED(CONFIG_BT_MESH_CDB)) + { + bt_mesh_cdb_iv_update(iv_index, iv_update); + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + store_iv(false); + } + + return true; +} + +uint32_t bt_mesh_next_seq(void) +{ + uint32_t seq = bt_mesh.seq++; + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bt_mesh_net_seq_store(false); + } + + if (!atomic_test_bit(bt_mesh.flags, BT_MESH_IVU_IN_PROGRESS) && + bt_mesh.seq > IV_UPDATE_SEQ_LIMIT && + bt_mesh_subnet_get(BT_MESH_KEY_PRIMARY)) + { + bt_mesh_beacon_ivu_initiator(true); + bt_mesh_net_iv_update(bt_mesh.iv_index + 1, true); + } + + return seq; +} + +static void bt_mesh_net_local(struct k_work *work) +{ + struct net_buf_simple sbuf; + struct list_node *node; + + while ((node = list_remove_head(&bt_mesh.local_queue))) + { + struct loopback_buf *buf = CONTAINER_OF(node, struct loopback_buf, + node); + struct bt_mesh_net_rx rx = + { + .ctx = + { + .net_idx = buf->sub->net_idx, + + /* Initialize AppIdx to a sane value */ + + .app_idx = BT_MESH_KEY_UNUSED, + .recv_ttl = pdu_ttl(buf->data), + + /* TTL=1 only goes to local IF */ + + .send_ttl = 1U, + .addr = pdu_src(buf->data), + .recv_dst = pdu_dst(buf->data), + .recv_rssi = 0, + }, + .net_if = BT_MESH_NET_IF_LOCAL, + .sub = buf->sub, + .old_iv = (pdu_ivi(buf->data) != (bt_mesh.iv_index & 0x01)), + .ctl = pdu_ctl(buf->data), + .seq = pdu_seq(buf->data), + .new_key = SUBNET_KEY_TX_IDX(buf->sub), + .local_match = 1U, + .friend_match = 0U, + }; + + LOG_DBG("src: 0x%04x dst: 0x%04x seq 0x%06x sub %p", rx.ctx.addr, + rx.ctx.addr, rx.seq, + buf->sub); + + net_buf_simple_init_with_data(&sbuf, buf->data, buf->len); + (void)bt_mesh_trans_recv(&sbuf, &rx); + k_mem_slab_free(&loopback_buf_pool, (void *)buf); + } +} + +static const struct bt_mesh_net_cred *net_tx_cred_get( + struct bt_mesh_net_tx *tx) +{ +#if defined(CONFIG_BT_MESH_LOW_POWER) + if (tx->friend_cred && bt_mesh.lpn.frnd) + { + return &bt_mesh.lpn.cred[SUBNET_KEY_TX_IDX(tx->sub)]; + } +#endif + + tx->friend_cred = 0U; + return &tx->sub->keys[SUBNET_KEY_TX_IDX(tx->sub)].msg; +} + +static int net_header_encode(struct bt_mesh_net_tx *tx, + uint8_t nid, struct net_buf_simple *buf) +{ + const bool ctl = (tx->ctx->app_idx == BT_MESH_KEY_UNUSED); + + if (ctl && net_buf_simple_tailroom(buf) < 8) + { + LOG_ERR("Insufficient MIC space for CTL PDU"); + return -EINVAL; + } + else if (net_buf_simple_tailroom(buf) < 4) + { + LOG_ERR("Insufficient MIC space for PDU"); + return -EINVAL; + } + + LOG_DBG("src 0x%04x dst 0x%04x ctl %u seq 0x%06x", tx->src, tx->ctx->addr, + ctl, bt_mesh.seq); + + net_buf_simple_push_be16(buf, tx->ctx->addr); + net_buf_simple_push_be16(buf, tx->src); + net_buf_simple_push_be24(buf, bt_mesh_next_seq()); + + if (ctl) + { + net_buf_simple_push_u8(buf, tx->ctx->send_ttl | 0x80); + } + else + { + net_buf_simple_push_u8(buf, tx->ctx->send_ttl); + } + + net_buf_simple_push_u8(buf, (nid | (BT_MESH_NET_IVI_TX & 1) << 7)); + + return 0; +} + +static int net_encrypt( + struct net_buf_simple *buf, const struct bt_mesh_net_cred *cred, + uint32_t iv_index, enum bt_mesh_nonce_type proxy) +{ + int err; + + err = bt_mesh_net_encrypt(&cred->enc, buf, iv_index, proxy); + if (err) + { + return err; + } + + return bt_mesh_net_obfuscate(buf->data, iv_index, &cred->privacy); +} + +int bt_mesh_net_encode(struct bt_mesh_net_tx *tx, struct net_buf_simple *buf, + enum bt_mesh_nonce_type type) +{ + const struct bt_mesh_net_cred *cred; + int err; + + cred = net_tx_cred_get(tx); + err = net_header_encode(tx, cred->nid, buf); + if (err) + { + return err; + } + + return net_encrypt(buf, cred, BT_MESH_NET_IVI_TX, type); +} + +static int net_loopback(const struct bt_mesh_net_tx *tx, + const uint8_t *data, size_t len) +{ + int err; + struct loopback_buf *buf; + + err = k_mem_slab_alloc(&loopback_buf_pool, (void **)&buf, 0); + if (err) + { + LOG_WRN("Unable to allocate loopback"); + return -ENOMEM; + } + + buf->sub = tx->sub; + + (void)memcpy(buf->data, data, len); + buf->len = len; + + list_add_tail(&bt_mesh.local_queue, &buf->node); + + k_work_submit(&bt_mesh.local_work); + + return 0; +} + +int bt_mesh_net_send(struct bt_mesh_net_tx *tx, struct bt_mesh_adv *adv, + const struct bt_mesh_send_cb *cb, void *cb_data) +{ + const struct bt_mesh_net_cred *cred; + int err; + + LOG_DBG("src 0x%04x dst 0x%04x len %u headroom %zu tailroom %zu", tx->src, + tx->ctx->addr, + adv->b.len, net_buf_simple_headroom( + &adv->b), + net_buf_simple_tailroom(&adv->b)); + LOG_DBG("Payload len %u: %s", adv->b.len, + bt_hex(adv->b.data, adv->b.len)); + LOG_DBG("Seq 0x%06x", bt_mesh.seq); + + cred = net_tx_cred_get(tx); + err = net_header_encode(tx, cred->nid, &adv->b); + if (err) + { + goto done; + } + + /* Deliver to local network interface if necessary */ + + if (bt_mesh_fixed_group_match(tx->ctx->addr) || + bt_mesh_has_addr(tx->ctx->addr)) + { + err = net_loopback(tx, adv->b.data, adv->b.len); + + /* Local unicast messages should not go out to network */ + + if (BT_MESH_ADDR_IS_UNICAST(tx->ctx->addr) || tx->ctx->send_ttl == 1U) + { + if (!err) + { + send_cb_finalize(cb, cb_data); + } + + goto done; + } + } + + /* MshPRTv1.1: 3.4.5.2: "The output filter of the interface connected to + * advertising or GATT bearers shall drop all messages with TTL value set + * to 1." If a TTL=1 packet wasn't for a local interface, it is invalid. + */ + + if (tx->ctx->send_ttl == 1U) + { + err = -EINVAL; + goto done; + } + + err = net_encrypt(&adv->b, + cred, + BT_MESH_NET_IVI_TX, + BT_MESH_NONCE_NETWORK); + if (err) + { + goto done; + } + + adv->ctx.cb = cb; + adv->ctx.cb_data = cb_data; + + /* Deliver to GATT Proxy Clients if necessary. */ + + if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY)) + { + (void)bt_mesh_proxy_relay(adv, tx->ctx->addr); + } + + /* Deliver to GATT Proxy Servers if necessary. */ + + if (IS_ENABLED(CONFIG_BT_MESH_PROXY_CLIENT)) + { + (void)bt_mesh_proxy_cli_relay(adv); + } + + bt_mesh_adv_send(adv, cb, cb_data); + +done: + bt_mesh_adv_unref(adv); + return err; +} + +void bt_mesh_net_loopback_clear(uint16_t net_idx) +{ + struct list_node new_list; + struct list_node *node; + + LOG_DBG("0x%04x", net_idx); + + list_initialize(&new_list); + + while ((node = list_remove_head(&bt_mesh.local_queue))) + { + struct loopback_buf *buf = CONTAINER_OF(node, struct loopback_buf, + node); + + if (net_idx == BT_MESH_KEY_ANY || net_idx == buf->sub->net_idx) + { + LOG_DBG("Dropped 0x%06x", pdu_seq(buf->data)); + k_mem_slab_free(&loopback_buf_pool, (void *)buf); + } + else + { + list_add_tail(&new_list, &buf->node); + } + } + + bt_mesh.local_queue = new_list; +} + +static bool net_decrypt(struct bt_mesh_net_rx *rx, + struct net_buf_simple *in, + struct net_buf_simple *out, + const struct bt_mesh_net_cred *cred) +{ + bool proxy = (rx->net_if == BT_MESH_NET_IF_PROXY_CFG); + + if (pdu_nid(in->data) != cred->nid) + { + return false; + } + + LOG_DBG("NID 0x%02x", pdu_nid(in->data)); + LOG_DBG("IVI %u net->iv_index 0x%08x", + pdu_ivi(in->data), + bt_mesh.iv_index); + + rx->old_iv = (pdu_ivi(in->data) != (bt_mesh.iv_index & 0x01)); + + net_buf_simple_reset(out); + net_buf_simple_add_mem(out, in->data, in->len); + + if (bt_mesh_net_obfuscate(out->data, BT_MESH_NET_IVI_RX(rx), + &cred->privacy)) + { + return false; + } + + rx->ctx.addr = pdu_src(out->data); + if (!BT_MESH_ADDR_IS_UNICAST(rx->ctx.addr)) + { + LOG_DBG("Ignoring non-unicast src addr 0x%04x", rx->ctx.addr); + return false; + } + + if (bt_mesh_has_addr(rx->ctx.addr)) + { + LOG_DBG("Dropping locally originated packet"); + return false; + } + + if (rx->net_if == BT_MESH_NET_IF_ADV && + msg_cache_match(out, rx->sub->net_idx)) + { + LOG_DBG("Duplicate found in Network Message Cache"); + return false; + } + + LOG_DBG("src 0x%04x", rx->ctx.addr); + + return bt_mesh_net_decrypt(&cred->enc, out, BT_MESH_NET_IVI_RX(rx), + proxy) == 0; +} + +/* Relaying from advertising to the advertising bearer should only happen + * if the Relay state is set to enabled. Locally originated packets always + * get sent to the advertising bearer. If the packet came in through GATT, + * then we should only relay it if the GATT Proxy state is enabled. + */ + +static bool relay_to_adv(enum bt_mesh_net_if net_if) +{ + switch (net_if) + { + case BT_MESH_NET_IF_ADV: + return (bt_mesh_relay_get() == BT_MESH_RELAY_ENABLED); + case BT_MESH_NET_IF_PROXY: + return (bt_mesh_gatt_proxy_get() == BT_MESH_GATT_PROXY_ENABLED) || + (bt_mesh_priv_gatt_proxy_get( + ) == BT_MESH_PRIV_GATT_PROXY_ENABLED); + default: + return false; + } +} + +static void bt_mesh_net_relay(struct net_buf_simple *sbuf, + struct bt_mesh_net_rx *rx, bool bridge) +{ + const struct bt_mesh_net_cred *cred; + struct bt_mesh_adv *adv; + uint8_t transmit; + + if (rx->ctx.recv_ttl <= 1U) + { + return; + } + + if (rx->net_if == BT_MESH_NET_IF_ADV && !rx->friend_cred && !bridge && + bt_mesh_relay_get() != BT_MESH_RELAY_ENABLED && + bt_mesh_gatt_proxy_get() != BT_MESH_GATT_PROXY_ENABLED && + bt_mesh_priv_gatt_proxy_get() != BT_MESH_PRIV_GATT_PROXY_ENABLED) + { + return; + } + + LOG_DBG("TTL %u CTL %u dst 0x%04x", rx->ctx.recv_ttl, rx->ctl, + rx->ctx.recv_dst); + + /* The Relay Retransmit state is only applied to adv-adv relaying. + * Anything else (like GATT to adv, or locally originated packets) use the + * Network Transmit state. + */ + + if (rx->net_if == BT_MESH_NET_IF_ADV && !rx->friend_cred && !bridge) + { + transmit = bt_mesh_relay_retransmit_get(); + } + else + { + transmit = bt_mesh_net_transmit_get(); + } + + adv = bt_mesh_adv_create(BT_MESH_ADV_DATA, BT_MESH_ADV_TAG_RELAY, transmit, + 0); + if (!adv) + { + LOG_DBG("Out of relay advs"); + return; + } + + /* Leave CTL bit intact */ + + sbuf->data[1] &= 0x80; + sbuf->data[1] |= rx->ctx.recv_ttl - 1U; + + net_buf_simple_add_mem(&adv->b, sbuf->data, sbuf->len); + + cred = &rx->sub->keys[SUBNET_KEY_TX_IDX(rx->sub)].msg; + + LOG_DBG("Relaying packet. TTL is now %u", pdu_ttl(adv->b.data)); + + /* Update NID if RX, RX was with friend credentials or when bridging the + * message + */ + + if (rx->friend_cred || bridge) + { + adv->b.data[0] &= 0x80; /* Clear everything except IVI */ + adv->b.data[0] |= cred->nid; + } + + /* We re-encrypt and obfuscate using the received IVI rather than + * the normal TX IVI (which may be different) since the transport layer + * nonce includes the IVI. + */ + + if (net_encrypt(&adv->b, cred, BT_MESH_NET_IVI_RX(rx), + BT_MESH_NONCE_NETWORK)) + { + LOG_ERR("Re-encrypting failed"); + goto done; + } + + /* When the Friend node relays message for lpn, the message will be + * retransmitted using the managed flooding security credentials and the + * Network PDU shall be retransmitted to all network interfaces. + */ + + if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY) && + (rx->friend_cred || + bt_mesh_gatt_proxy_get() == BT_MESH_GATT_PROXY_ENABLED || + bt_mesh_priv_gatt_proxy_get() == BT_MESH_PRIV_GATT_PROXY_ENABLED)) + { + bt_mesh_proxy_relay(adv, rx->ctx.recv_dst); + } + + if (relay_to_adv(rx->net_if) || rx->friend_cred || bridge) + { + bt_mesh_adv_send(adv, NULL, NULL); + } + +done: + bt_mesh_adv_unref(adv); +} + +#if IS_ENABLED(CONFIG_BT_MESH_BRG_CFG_SRV) +static bool find_subnet_cb(struct bt_mesh_subnet *sub, void *cb_data) +{ + uint16_t *net_idx = cb_data; + + return sub->net_idx == *net_idx; +} + +static void bt_mesh_sbr_check_cb(uint16_t new_net_idx, void *user_data) +{ + struct pdu_ctx *ctx = (struct pdu_ctx *)user_data; + + if (new_net_idx < BT_MESH_BRG_CFG_NETIDX_NOMATCH) + { + struct bt_mesh_subnet *subnet = bt_mesh_subnet_find(find_subnet_cb, + &new_net_idx); + + if (!subnet) + { + LOG_ERR("Failed to find subnet 0x%04x", new_net_idx); + return; + } + + ctx->rx->sub = subnet; + ctx->rx->ctx.net_idx = new_net_idx; + + net_buf_simple_restore(ctx->sbuf, ctx->state); + bt_mesh_net_relay(ctx->sbuf, ctx->rx, true); + } +} +#endif + +void bt_mesh_net_header_parse(struct net_buf_simple *buf, + struct bt_mesh_net_rx *rx) +{ + rx->old_iv = (pdu_ivi(buf->data) != (bt_mesh.iv_index & 0x01)); + rx->ctl = pdu_ctl(buf->data); + rx->ctx.recv_ttl = pdu_ttl(buf->data); + rx->seq = pdu_seq(buf->data); + rx->ctx.addr = pdu_src(buf->data); + rx->ctx.recv_dst = pdu_dst(buf->data); +} + +int bt_mesh_net_decode(struct net_buf_simple *in, enum bt_mesh_net_if net_if, + struct bt_mesh_net_rx *rx, struct net_buf_simple *out) +{ + if (in->len < BT_MESH_NET_MIN_PDU_LEN) + { + LOG_WRN("Dropping too short mesh packet (len %u)", in->len); + LOG_WRN("%s", bt_hex(in->data, in->len)); + return -EINVAL; + } + + if (in->len > BT_MESH_NET_MAX_PDU_LEN) + { + LOG_WRN("Dropping too long mesh packet (len %u)", in->len); + return -EINVAL; + } + + if (net_if == BT_MESH_NET_IF_ADV && check_dup(in)) + { + return -EINVAL; + } + + LOG_DBG("%u bytes: %s", in->len, bt_hex(in->data, in->len)); + + rx->net_if = net_if; + + if (!bt_mesh_net_cred_find(rx, in, out, net_decrypt)) + { + LOG_DBG("Unable to find matching net for packet"); + return -ENOENT; + } + + /* Initialize AppIdx to a sane value */ + + rx->ctx.app_idx = BT_MESH_KEY_UNUSED; + + rx->ctx.recv_ttl = pdu_ttl(out->data); + + /* Default to responding with TTL 0 for non-routed messages */ + + if (rx->ctx.recv_ttl == 0U) + { + rx->ctx.send_ttl = 0U; + } + else + { + rx->ctx.send_ttl = BT_MESH_TTL_DEFAULT; + } + + rx->ctl = pdu_ctl(out->data); + rx->seq = pdu_seq(out->data); + rx->ctx.recv_dst = pdu_dst(out->data); + + LOG_DBG("Decryption successful. Payload len %u", out->len); + + if (net_if != BT_MESH_NET_IF_PROXY_CFG && + rx->ctx.recv_dst == BT_MESH_ADDR_UNASSIGNED) + { + LOG_ERR("Destination address is unassigned; dropping packet"); + return -EBADMSG; + } + + LOG_DBG("src 0x%04x dst 0x%04x ttl %u", rx->ctx.addr, rx->ctx.recv_dst, + rx->ctx.recv_ttl); + LOG_DBG("PDU: %s", bt_hex(out->data, out->len)); + + msg_cache_add(rx); + + return 0; +} + +void bt_mesh_net_recv(struct net_buf_simple *data, + int8_t rssi, enum bt_mesh_net_if net_if) +{ + NET_BUF_SIMPLE_DEFINE(buf, BT_MESH_NET_MAX_PDU_LEN); + struct bt_mesh_net_rx rx = + { + .ctx.recv_rssi = rssi + }; + + struct net_buf_simple_state state; + int err; + + LOG_DBG("rssi %d net_if %u", rssi, net_if); + + if (!bt_mesh_is_provisioned()) + { + return; + } + + if (bt_mesh_net_decode(data, net_if, &rx, &buf)) + { + return; + } + + if (IS_ENABLED(CONFIG_BT_MESH_STATISTIC)) + { + bt_mesh_stat_rx(net_if); + } + + /* Save the state so the buffer can later be relayed */ + + net_buf_simple_save(&buf, &state); + + rx.local_match = + (bt_mesh_fixed_group_match(rx.ctx.recv_dst) || + bt_mesh_has_addr(rx.ctx.recv_dst)); + + if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY) && net_if == + BT_MESH_NET_IF_PROXY) + { + bt_mesh_proxy_addr_add(data, rx.ctx.addr); + + if (bt_mesh_gatt_proxy_get() == BT_MESH_GATT_PROXY_DISABLED && + bt_mesh_priv_gatt_proxy_get() == + BT_MESH_PRIV_GATT_PROXY_DISABLED && + !rx.local_match) + { + LOG_INF("Proxy is disabled; ignoring message"); + return; + } + } + + err = bt_mesh_trans_recv(&buf, &rx); + if (err == -EAGAIN) + { + /* The transport layer has indicated that it has rejected the message, + * but would like to see it again if it is received in the future. This + * can happen if a message is received when the device is in Low Power + * mode, but the message was not encrypted with the friend credentials. + * Remove it from the message cache so that we accept it again in the + * future. + */ + + LOG_WRN("Removing rejected message from Network Message Cache"); + + /* Rewind the next index now that we're not using this entry */ + + msg_cache[--msg_cache_next].src = BT_MESH_ADDR_UNASSIGNED; + dup_cache[--dup_cache_next] = 0; + return; + } + else if (err == -EBADMSG) + { + LOG_DBG("Not relaying message rejected by the Transport layer"); + return; + } + + /* Relay if this was a group/virtual address, or if the destination + * was neither a local element nor an LPN we're Friends for, and device is + * not in Friendship as the Low Power node or the message is not encrypted + * using friendship security credentials. + */ + + if ((!BT_MESH_ADDR_IS_UNICAST(rx.ctx.recv_dst) || + (!rx.local_match && !rx.friend_match)) && + (!bt_mesh_lpn_established() || !rx.friend_cred)) + { + net_buf_simple_restore(&buf, &state); + bt_mesh_net_relay(&buf, &rx, false); + } + +#if IS_ENABLED(CONFIG_BT_MESH_BRG_CFG_SRV) + struct pdu_ctx tx_ctx = + { + .sbuf = &buf, + .state = &state, + .rx = &rx, + }; + + /* Bridge the traffic if enabled */ + + if (!bt_mesh_brg_cfg_enable_get()) + { + return; + } + + if (bt_mesh_rpl_check(&rx, NULL, true)) + { + return; + } + + bt_mesh_brg_cfg_tbl_foreach_subnet(rx.ctx.addr, rx.ctx.recv_dst, + rx.ctx.net_idx, + bt_mesh_sbr_check_cb, &tx_ctx); +#endif +} + +static void ivu_refresh(struct k_work *work) +{ + uint8_t prev_duration; + + if (!bt_mesh_is_provisioned()) + { + return; + } + + prev_duration = bt_mesh.ivu_duration; + bt_mesh.ivu_duration = MIN(UINT8_MAX, + bt_mesh.ivu_duration + BT_MESH_IVU_HOURS); + + LOG_DBG("%s for %u hour%s", + atomic_test_bit(bt_mesh.flags, + BT_MESH_IVU_IN_PROGRESS) ? "IVU in Progress" + : "IVU Normal mode", + bt_mesh.ivu_duration, bt_mesh.ivu_duration == 1U ? "" : "s"); + + if (bt_mesh.ivu_duration < BT_MESH_IVU_MIN_HOURS) + { + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + store_iv(true); + } + + goto end; + } + + /* Because the beacon may be cached, iv update or iv recovery + * cannot be performed after 96 hours or 192 hours. So we need clear beacon + * cache. BT_MESH_IVU_HOURS need not divide the limit, so test for crossing + * it. + */ + + if (prev_duration / BT_MESH_IVU_MIN_HOURS != + bt_mesh.ivu_duration / BT_MESH_IVU_MIN_HOURS) + { + bt_mesh_subnet_foreach(bt_mesh_beacon_cache_clear); + } + + if (atomic_test_bit(bt_mesh.flags, BT_MESH_IVU_IN_PROGRESS)) + { + bt_mesh_beacon_ivu_initiator(true); + bt_mesh_net_iv_update(bt_mesh.iv_index, false); + } + else if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + store_iv(true); + } + +end: + k_work_reschedule(&bt_mesh.ivu_timer, BT_MESH_IVU_TIMEOUT); +} + +void bt_mesh_net_init(void) +{ + k_work_init_delayable(&bt_mesh.ivu_timer, ivu_refresh); + + k_work_init(&bt_mesh.local_work, bt_mesh_net_local); +} + +static int net_set(const char *name, + size_t len_rd, settings_read_cb read_cb, void *cb_arg) +{ + struct net_val net; + struct bt_mesh_key key; + int err; + + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return 0; + } + + if (len_rd == 0) + { + LOG_DBG("val (null)"); + + bt_mesh_comp_unprovision(); + bt_mesh_key_destroy(&bt_mesh.dev_key); + memset(&bt_mesh.dev_key, 0, sizeof(struct bt_mesh_key)); + return 0; + } + + err = bt_mesh_settings_set(read_cb, cb_arg, &net, sizeof(net)); + if (err) + { + LOG_ERR("Failed to set \'net\'"); + return err; + } + + /* One extra copying since net.dev_key is from packed structure + * and might be unaligned. + */ + + memcpy(&key, &net.dev_key, sizeof(struct bt_mesh_key)); + + bt_mesh_key_assign(&bt_mesh.dev_key, &key); + bt_mesh_comp_provision(net.primary_addr); + + LOG_DBG("Provisioned with primary address 0x%04x", net.primary_addr); + LOG_DBG("Recovered DevKey %s", bt_hex(&bt_mesh.dev_key, + sizeof(struct bt_mesh_key))); + + return 0; +} + +BT_MESH_SETTINGS_DEFINE(net, "Net", net_set); + +static int iv_set(const char *name, + size_t len_rd, settings_read_cb read_cb, void *cb_arg) +{ + struct iv_val iv; + int err; + + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return 0; + } + + if (len_rd == 0) + { + LOG_DBG("IV deleted"); + + bt_mesh.iv_index = 0U; + atomic_clear_bit(bt_mesh.flags, BT_MESH_IVU_IN_PROGRESS); + return 0; + } + + err = bt_mesh_settings_set(read_cb, cb_arg, &iv, sizeof(iv)); + if (err) + { + LOG_ERR("Failed to set \'iv\'"); + return err; + } + + bt_mesh.iv_index = iv.iv_index; + atomic_set_bit_to(bt_mesh.flags, BT_MESH_IVU_IN_PROGRESS, iv.iv_update); + bt_mesh.ivu_duration = iv.iv_duration; + + LOG_DBG("IV Index 0x%04x (IV Update Flag %u) duration %u hours", + iv.iv_index, + iv.iv_update, + iv.iv_duration); + + return 0; +} + +BT_MESH_SETTINGS_DEFINE(iv, "IV", iv_set); + +static int seq_set(const char *name, + size_t len_rd, settings_read_cb read_cb, void *cb_arg) +{ + struct seq_val seq; + int err; + + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return 0; + } + + if (len_rd == 0) + { + LOG_DBG("val (null)"); + + bt_mesh.seq = 0U; + return 0; + } + + err = bt_mesh_settings_set(read_cb, cb_arg, &seq, sizeof(seq)); + if (err) + { + LOG_ERR("Failed to set \'seq\'"); + return err; + } + + bt_mesh.seq = sys_get_le24(seq.val); + + if (CONFIG_BT_MESH_SEQ_STORE_RATE > 0) + { + /* Make sure we have a large enough sequence number. We + * subtract 1 so that the first transmission causes a write to the + * settings storage. + */ + + bt_mesh.seq += + (CONFIG_BT_MESH_SEQ_STORE_RATE - + (bt_mesh.seq % CONFIG_BT_MESH_SEQ_STORE_RATE)); + bt_mesh.seq--; + } + + LOG_DBG("Sequence Number 0x%06x", bt_mesh.seq); + + return 0; +} + +BT_MESH_SETTINGS_DEFINE(seq, "Seq", seq_set); + +#if defined(CONFIG_BT_MESH_RPR_SRV) +static int dev_key_cand_set(const char *name, + size_t len_rd, settings_read_cb read_cb, + void *cb_arg) +{ + int err; + struct bt_mesh_key key; + + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return 0; + } + + if (len_rd == 0) + { + LOG_DBG("val (null)"); + + bt_mesh_key_destroy(&bt_mesh.dev_key_cand); + memset(&bt_mesh.dev_key_cand, 0, sizeof(struct bt_mesh_key)); + return 0; + } + + err = bt_mesh_settings_set(read_cb, cb_arg, &key, + sizeof(struct bt_mesh_key)); + if (!err) + { + LOG_DBG("DevKey candidate recovered from storage"); + atomic_set_bit(bt_mesh.flags, BT_MESH_DEVKEY_CAND); + bt_mesh_key_assign(&bt_mesh.dev_key_cand, &key); + } + + return err; +} + +BT_MESH_SETTINGS_DEFINE(dev_key, "DevKeyC", dev_key_cand_set); +#endif + +void bt_mesh_net_pending_dev_key_cand_store(void) +{ +#if defined(CONFIG_BT_MESH_RPR_SRV) + int err; + + if (atomic_test_bit(bt_mesh.flags, BT_MESH_DEVKEY_CAND)) + { + err = settings_save_one("bt/mesh/DevKeyC", &bt_mesh.dev_key_cand, + sizeof(struct bt_mesh_key)); + } + else + { + err = settings_delete("bt/mesh/DevKeyC"); + } + + if (err) + { + LOG_ERR("Failed to update DevKey candidate value"); + } + else + { + LOG_DBG("Stored DevKey candidate value"); + } +#endif +} + +void bt_mesh_net_dev_key_cand_store(void) +{ + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_DEV_KEY_CAND_PENDING); +} + +static void clear_iv(void) +{ + int err; + + err = settings_delete("bt/mesh/IV"); + if (err) + { + LOG_ERR("Failed to clear IV"); + } + else + { + LOG_DBG("Cleared IV"); + } +} + +static void store_pending_iv(void) +{ + struct iv_val iv; + int err; + + iv.iv_index = bt_mesh.iv_index; + iv.iv_update = atomic_test_bit(bt_mesh.flags, BT_MESH_IVU_IN_PROGRESS); + iv.iv_duration = bt_mesh.ivu_duration; + + err = settings_save_one("bt/mesh/IV", &iv, sizeof(iv)); + if (err) + { + LOG_ERR("Failed to store IV value"); + } + else + { + LOG_DBG("Stored IV value"); + } +} + +void bt_mesh_net_pending_iv_store(void) +{ + if (atomic_test_bit(bt_mesh.flags, BT_MESH_VALID)) + { + store_pending_iv(); + } + else + { + clear_iv(); + } +} + +static void clear_net(void) +{ + int err; + + err = settings_delete("bt/mesh/Net"); + if (err) + { + LOG_ERR("Failed to clear Network"); + } + else + { + LOG_DBG("Cleared Network"); + } +} + +static void store_pending_net(void) +{ + struct net_val net; + int err; + + LOG_DBG("addr 0x%04x DevKey %s", bt_mesh_primary_addr(), + bt_hex(&bt_mesh.dev_key, sizeof(struct bt_mesh_key))); + + net.primary_addr = bt_mesh_primary_addr(); + memcpy(&net.dev_key, &bt_mesh.dev_key, sizeof(struct bt_mesh_key)); + + err = settings_save_one("bt/mesh/Net", &net, sizeof(net)); + if (err) + { + LOG_ERR("Failed to store Network value"); + } + else + { + LOG_DBG("Stored Network value"); + } +} + +void bt_mesh_net_pending_net_store(void) +{ + if (atomic_test_bit(bt_mesh.flags, BT_MESH_VALID)) + { + store_pending_net(); + } + else + { + clear_net(); + } +} + +void bt_mesh_net_pending_seq_store(void) +{ + struct seq_val seq; + int err; + + if (atomic_test_bit(bt_mesh.flags, BT_MESH_VALID)) + { + sys_put_le24(bt_mesh.seq, seq.val); + + err = settings_save_one("bt/mesh/Seq", &seq, sizeof(seq)); + if (err) + { + LOG_ERR("Failed to stor Seq value"); + } + else + { + LOG_DBG("Stored Seq value"); + } + } + else + { + err = settings_delete("bt/mesh/Seq"); + if (err) + { + LOG_ERR("Failed to clear Seq value"); + } + else + { + LOG_DBG("Cleared Seq value"); + } + } +} + +void bt_mesh_net_store(void) +{ + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_NET_PENDING); +} + +void bt_mesh_net_clear(void) +{ + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_NET_PENDING); + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_IV_PENDING); + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_CFG_PENDING); + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_SEQ_PENDING); +} + +void bt_mesh_net_settings_commit(void) +{ + if (bt_mesh.ivu_duration < BT_MESH_IVU_MIN_HOURS) + { + k_work_reschedule(&bt_mesh.ivu_timer, BT_MESH_IVU_TIMEOUT); + } +} diff --git a/libs/zblue/mesh/net.h b/libs/zblue/mesh/net.h new file mode 100644 index 0000000000000..23ab3933c3ddb --- /dev/null +++ b/libs/zblue/mesh/net.h @@ -0,0 +1,417 @@ +/**************************************************************************** + * libs/zblue/mesh/net.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include "adv.h" +#include "subnet.h" +#include +#include +#include +#include +#include + +#define BT_MESH_IV_UPDATE(flags) ((flags >> 1) & 0x01) +#define BT_MESH_KEY_REFRESH(flags) (flags & 0x01) + +/* How many hours in between updating IVU duration */ +#define BT_MESH_IVU_MIN_HOURS 96 +#define BT_MESH_IVU_HOURS (BT_MESH_IVU_MIN_HOURS / CONFIG_BT_MESH_IVU_DIVIDER) +#define BT_MESH_IVU_TIMEOUT SEC2TICK((BT_MESH_IVU_HOURS) * SEC_PER_HOUR) + +/* Minimum valid Mesh Network PDU length. The Network headers + * themselves take up 9 bytes. After that there is a minimum of 1 byte + * payload for both CTL=1 and CTL=0 PDUs (smallest OpCode is 1 byte). CTL=1 + * PDUs must use a 64-bit (8 byte) NetMIC, whereas CTL=0 PDUs have at least a + * 32-bit (4 byte) NetMIC and AppMIC giving again a total of 8 bytes. + */ +#define BT_MESH_NET_MIN_PDU_LEN (BT_MESH_NET_HDR_LEN + 1 + 8) +/* Maximum valid Mesh Network PDU length. The longest packet can either be a + * transport control message (CTL=1) of 12 bytes + 8 bytes of NetMIC, or an + * access message (CTL=0) of 16 bytes + 4 bytes of NetMIC. + */ +#define BT_MESH_NET_MAX_PDU_LEN (BT_MESH_NET_HDR_LEN + 16 + 4) + +struct bt_mesh_net_cred; +enum bt_mesh_nonce_type; + +struct bt_mesh_node +{ + uint16_t addr; + uint16_t net_idx; + struct bt_mesh_key dev_key; + uint8_t num_elem; +}; + +#if defined(CONFIG_BT_MESH_FRIEND) +#define FRIEND_SEG_RX CONFIG_BT_MESH_FRIEND_SEG_RX +#define FRIEND_SUB_LIST_SIZE CONFIG_BT_MESH_FRIEND_SUB_LIST_SIZE +#else +#define FRIEND_SEG_RX 0 +#define FRIEND_SUB_LIST_SIZE 0 +#endif + +struct bt_mesh_friend +{ + uint16_t lpn; + uint8_t recv_delay; + uint8_t fsn : 1; + uint8_t send_last : 1; + uint8_t pending_req : 1; + uint8_t pending_buf : 1; + uint8_t established : 1; + uint8_t pending_cfm : 1; + int32_t poll_to; + uint8_t num_elem; + uint16_t lpn_counter; + uint16_t counter; + + struct bt_mesh_subnet *subnet; + + struct bt_mesh_net_cred cred[2]; + + uint16_t sub_list[FRIEND_SUB_LIST_SIZE]; + + struct k_work_delayable timer; + + struct bt_mesh_friend_seg + { + struct list_node queue; + + /* The target number of segments, i.e. not necessarily + * the current number of segments, in the queue. This is used for + * Friend Queue free space calculations. + */ + + uint8_t seg_count; + } + seg[FRIEND_SEG_RX]; + + struct net_buf *last; + + struct list_node queue; + uint32_t queue_size; + + /* Friend Clear Procedure / Friend Clear Confirm */ + + struct + { + struct k_work_delayable timer; + union + { + /* Friend Clear Procedure context */ + + struct + { + uint32_t start; /* Procedure start */ + uint16_t frnd; /* Previous Friend's address */ + uint16_t repeat_sec; /* Repeat timeout in seconds */ + }; + /* Pending Friend Clear Confirm context. + * Delayed to allow the LPN to finish advertising its Friend Clear and + * start scanning. Scheduling this timer also cancels any ongoing + * Friend Clear Procedure, which is no longer needed since the + * friendship has been terminated. + */ + + struct + { + uint16_t net_idx; + uint16_t dst; + uint16_t lpn_addr; + uint16_t lpn_counter; + uint8_t ttl; + } cfm; + }; + } clear; +}; + +#if defined(CONFIG_BT_MESH_LOW_POWER) +#define LPN_GROUPS CONFIG_BT_MESH_LPN_GROUPS +#else +#define LPN_GROUPS 0 +#endif + +/* Low Power Node state */ + +struct bt_mesh_lpn +{ + enum __packed + { + BT_MESH_LPN_DISABLED, /* LPN feature is disabled */ + BT_MESH_LPN_CLEAR, /* Clear in progress */ + BT_MESH_LPN_TIMER, /* Waiting for auto timer expiry */ + BT_MESH_LPN_ENABLED, /* LPN enabled, but no Friend */ + BT_MESH_LPN_REQ_WAIT, /* Wait before scanning for offers */ + BT_MESH_LPN_WAIT_OFFER, /* Friend Req sent */ + BT_MESH_LPN_ESTABLISHED, /* Friendship established */ + BT_MESH_LPN_RECV_DELAY, /* Poll sent, waiting ReceiveDelay */ + BT_MESH_LPN_WAIT_UPDATE, /* Waiting for Update or message */ + } state; + + /* Transaction Number (used for subscription list) */ + + uint8_t xact_next; + uint8_t xact_pending; + uint8_t sent_req; + + /* Address of our Friend when we're a LPN. Unassigned if we don't + * have a friend yet. + */ + + uint16_t frnd; + + /* Value from the friend offer */ + + uint8_t recv_win; + + uint8_t req_attempts; /* Number of Request attempts */ + + int32_t poll_timeout; + + uint8_t groups_changed : 1; /* Friend Subscription List needs updating */ + uint8_t pending_poll : 1; /* Poll to be sent after subscription */ + uint8_t disable : 1; /* Disable LPN after clearing */ + uint8_t fsn : 1; /* Friend Sequence Number */ + uint8_t established : 1; /* Friendship established */ + uint8_t clear_success : 1; /* Friend Clear Confirm received */ + + /* Friend Queue Size */ + + uint8_t queue_size; + + /* FriendCounter */ + + uint16_t frnd_counter; + + /* LPNCounter */ + + uint16_t lpn_counter; + + /* Previous Friend of this LPN */ + + uint16_t old_friend; + + /* Duration reported for last advertising packet */ + + uint16_t adv_duration; + + /* Advertising start time. */ + + uint32_t adv_start_time; + + /* Next LPN related action timer */ + + struct k_work_delayable timer; + + /* Subscribed groups */ + + uint16_t groups[LPN_GROUPS]; + + struct bt_mesh_subnet *sub; + + struct bt_mesh_net_cred cred[2]; + + /* Bit fields for tracking which groups the Friend knows about */ + + atomic_define(added, LPN_GROUPS); + atomic_define(pending, LPN_GROUPS); + atomic_define(to_remove, LPN_GROUPS); +}; + +/* bt_mesh_net.flags */ + +enum +{ + BT_MESH_INIT, /* We have been initialized */ + BT_MESH_VALID, /* We have been provisioned */ + BT_MESH_SUSPENDED, /* Network is temporarily suspended */ + BT_MESH_IVU_IN_PROGRESS, /* IV Update in Progress */ + BT_MESH_IVU_INITIATOR, /* IV Update initiated by us */ + BT_MESH_IVU_TEST, /* IV Update test mode */ + BT_MESH_IVU_PENDING, /* Update blocked by SDU in progress */ + BT_MESH_COMP_DIRTY, /* Composition data is dirty */ + BT_MESH_DEVKEY_CAND, /* Has device key candidate */ + BT_MESH_METADATA_DIRTY, /* Models metadata is dirty */ + + /* Feature flags */ + + BT_MESH_RELAY, + BT_MESH_BEACON, + BT_MESH_GATT_PROXY, + BT_MESH_FRIEND, + BT_MESH_PRIV_BEACON, + BT_MESH_PRIV_GATT_PROXY, + BT_MESH_OD_PRIV_PROXY, + + /* Don't touch - intentionally last */ + + BT_MESH_FLAG_COUNT, +}; + +struct bt_mesh_net +{ + uint32_t iv_index; /* Current IV Index */ + uint32_t seq; /* Next outgoing sequence number (24 bits) */ + + atomic_define(flags, BT_MESH_FLAG_COUNT); + + /* Local network interface */ + + struct k_work local_work; + struct list_node local_queue; + +#if defined(CONFIG_BT_MESH_FRIEND) + /* Friend state, unique for each LPN that we're Friends for */ + + struct bt_mesh_friend frnd[CONFIG_BT_MESH_FRIEND_LPN_COUNT]; +#endif + +#if defined(CONFIG_BT_MESH_LOW_POWER) + struct bt_mesh_lpn lpn; /* Low Power Node state */ +#endif + + /* Number of hours in current IV Update state */ + + uint8_t ivu_duration; + + uint8_t net_xmit; + uint8_t relay_xmit; + uint8_t default_ttl; + +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) + uint8_t priv_beacon_int; +#endif + + /* Timer to track duration in current IV Update state */ + + struct k_work_delayable ivu_timer; + + struct bt_mesh_key dev_key; + +#if defined(CONFIG_BT_MESH_RPR_SRV) + struct bt_mesh_key dev_key_cand; +#endif +#if defined(CONFIG_BT_MESH_OD_PRIV_PROXY_SRV) + uint8_t on_demand_state; +#endif + struct bt_mesh_sar_tx sar_tx; /* Transport SAR Transmitter configuration */ + struct bt_mesh_sar_rx sar_rx; /* Transport SAR Receiver configuration */ +}; + +/* Network interface */ + +enum bt_mesh_net_if +{ + BT_MESH_NET_IF_ADV, + BT_MESH_NET_IF_LOCAL, + BT_MESH_NET_IF_PROXY, + BT_MESH_NET_IF_PROXY_CFG, +}; + +/* Decoding context for Network/Transport data */ + +struct bt_mesh_net_rx +{ + struct bt_mesh_subnet *sub; + struct bt_mesh_msg_ctx ctx; + uint32_t seq; /* Sequence Number */ + uint8_t old_iv : 1; /* iv_index - 1 was used */ + uint8_t new_key : 1; /* Data was encrypted with updated key */ + uint8_t friend_cred : 1; /* Data was encrypted with friend cred */ + uint8_t ctl : 1; /* Network Control */ + uint8_t net_if : 2; /* Network interface */ + uint8_t local_match : 1; /* Matched a local element */ + uint8_t friend_match : 1; /* Matched an LPN we're friends for */ +}; + +/* Encoding context for Network/Transport data */ + +struct bt_mesh_net_tx +{ + struct bt_mesh_subnet *sub; + struct bt_mesh_msg_ctx *ctx; + uint16_t src; + uint8_t xmit; + uint8_t friend_cred : 1; + uint8_t aszmic : 1; + uint8_t aid : 6; +}; + +extern struct bt_mesh_net bt_mesh; + +#define BT_MESH_NET_IVI_TX \ + (bt_mesh.iv_index - atomic_test_bit(bt_mesh.flags, BT_MESH_IVU_IN_PROGRESS)) +#define BT_MESH_NET_IVI_RX(rx) (bt_mesh.iv_index - (rx)->old_iv) + +#define BT_MESH_NET_HDR_LEN 9 + +int bt_mesh_net_create( + uint16_t idx, uint8_t flags, const struct bt_mesh_key *key, + uint32_t iv_index); + +bool bt_mesh_net_iv_update(uint32_t iv_index, bool iv_update); + +int bt_mesh_net_encode(struct bt_mesh_net_tx *tx, struct net_buf_simple *buf, + enum bt_mesh_nonce_type type); + +int bt_mesh_net_send(struct bt_mesh_net_tx *tx, struct bt_mesh_adv *adv, + const struct bt_mesh_send_cb *cb, void *cb_data); + +int bt_mesh_net_decode(struct net_buf_simple *in, enum bt_mesh_net_if net_if, + struct bt_mesh_net_rx *rx, struct net_buf_simple + *out); + +void bt_mesh_net_recv(struct net_buf_simple *data, + int8_t rssi, enum bt_mesh_net_if net_if); + +void bt_mesh_net_loopback_clear(uint16_t net_idx); + +uint32_t bt_mesh_next_seq(void); +void bt_mesh_net_seq_store(bool force); + +void bt_mesh_net_init(void); +void bt_mesh_net_header_parse(struct net_buf_simple *buf, + struct bt_mesh_net_rx *rx); +void bt_mesh_net_pending_net_store(void); +void bt_mesh_net_pending_iv_store(void); +void bt_mesh_net_pending_seq_store(void); + +void bt_mesh_net_pending_dev_key_cand_store(void); +void bt_mesh_net_dev_key_cand_store(void); + +void bt_mesh_net_store(void); +void bt_mesh_net_clear(void); +void bt_mesh_net_settings_commit(void); + +static inline void send_cb_finalize(const struct bt_mesh_send_cb *cb, + void *cb_data) +{ + if (!cb) + { + return; + } + + if (cb->start) + { + cb->start(0, 0, cb_data); + } + + if (cb->end) + { + cb->end(0, cb_data); + } +} diff --git a/libs/zblue/mesh/od_priv_proxy.h b/libs/zblue/mesh/od_priv_proxy.h new file mode 100644 index 0000000000000..e8bab90cb867d --- /dev/null +++ b/libs/zblue/mesh/od_priv_proxy.h @@ -0,0 +1,13 @@ +/**************************************************************************** + * libs/zblue/mesh/od_priv_proxy.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +void bt_mesh_od_priv_proxy_srv_store_schedule(void); diff --git a/libs/zblue/mesh/od_priv_proxy_cli.c b/libs/zblue/mesh/od_priv_proxy_cli.c new file mode 100644 index 0000000000000..12c403d230b32 --- /dev/null +++ b/libs/zblue/mesh/od_priv_proxy_cli.c @@ -0,0 +1,146 @@ +/**************************************************************************** + * libs/zblue/mesh/od_priv_proxy_cli.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include "cfg.h" +#include "access.h" +#include "foundation.h" +#include "msg.h" + +#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_od_priv_proxy_cli); + +/** On-Demand Private Proxy Client Model Context */ + +static struct bt_mesh_od_priv_proxy_cli *cli; + +static int32_t msg_timeout; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int handle_proxy_status( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint8_t *state; + uint8_t state_rsp; + + state_rsp = net_buf_simple_pull_u8(buf); + + LOG_DBG("On-Demand Private Proxy status received: state: %u", state_rsp); + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_OD_PRIV_PROXY_STATUS, + ctx->addr, (void **)&state)) + { + if (state) + { + *state = state_rsp; + } + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + + if (cli->od_status) + { + cli->od_status(cli, ctx->addr, state_rsp); + } + + return 0; +} + +const struct bt_mesh_model_op _bt_mesh_od_priv_proxy_cli_op[] = +{ + { + OP_OD_PRIV_PROXY_STATUS, + BT_MESH_LEN_EXACT(1), + handle_proxy_status + }, + + BT_MESH_MODEL_OP_END +}; + +int bt_mesh_od_priv_proxy_cli_get(uint16_t net_idx, + uint16_t addr, uint8_t *val_rsp) +{ + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_OD_PRIV_PROXY_STATUS, + .user_data = val_rsp, + .timeout = msg_timeout, + }; + + BT_MESH_MODEL_BUF_DEFINE(msg, OP_OD_PRIV_PROXY_GET, 0); + bt_mesh_model_msg_init(&msg, OP_OD_PRIV_PROXY_GET); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, val_rsp ? &rsp : + NULL); +} + +int bt_mesh_od_priv_proxy_cli_set(uint16_t net_idx, + uint16_t addr, uint8_t val, + uint8_t *val_rsp) +{ + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_OD_PRIV_PROXY_STATUS, + .user_data = val_rsp, + .timeout = msg_timeout, + }; + + BT_MESH_MODEL_BUF_DEFINE(msg, OP_OD_PRIV_PROXY_SET, 1); + bt_mesh_model_msg_init(&msg, OP_OD_PRIV_PROXY_SET); + + net_buf_simple_add_u8(&msg, val); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &msg, val_rsp ? &rsp : + NULL); +} + +void bt_mesh_od_priv_proxy_cli_timeout_set(int32_t timeout) +{ + msg_timeout = timeout; +} + +static int on_demand_proxy_cli_init(const struct bt_mesh_model *mod) +{ + if (!bt_mesh_model_in_primary(mod)) + { + LOG_ERR("On-Demand Private Proxy client not in primary element"); + return -EINVAL; + } + + cli = mod->rt->user_data; + cli->model = mod; + mod->keys[0] = BT_MESH_KEY_DEV_ANY; + mod->rt->flags |= BT_MESH_MOD_DEVKEY_ONLY; + msg_timeout = CONFIG_BT_MESH_OD_PRIV_PROXY_CLI_TIMEOUT; + + bt_mesh_msg_ack_ctx_init(&cli->ack_ctx); + return 0; +} + +const struct bt_mesh_model_cb _bt_mesh_od_priv_proxy_cli_cb = +{ + .init = on_demand_proxy_cli_init, +}; diff --git a/libs/zblue/mesh/od_priv_proxy_srv.c b/libs/zblue/mesh/od_priv_proxy_srv.c new file mode 100644 index 0000000000000..2dae9ec168a63 --- /dev/null +++ b/libs/zblue/mesh/od_priv_proxy_srv.c @@ -0,0 +1,198 @@ +/**************************************************************************** + * libs/zblue/mesh/od_priv_proxy_srv.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include "access.h" +#include "cfg.h" +#include "foundation.h" +#include "settings.h" + +#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_od_priv_proxy_srv); + +static const struct bt_mesh_model *od_priv_proxy_srv; +static uint8_t on_demand_state; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int od_priv_proxy_store(bool delete) +{ + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return 0; + } + + const void *data = delete ? NULL : &on_demand_state; + size_t len = delete ? 0 : sizeof(uint8_t); + + return bt_mesh_model_data_store(od_priv_proxy_srv, false, "pp", data, len); +} + +static int proxy_status_rsp(const struct bt_mesh_model *mod, + struct bt_mesh_msg_ctx *ctx) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, OP_OD_PRIV_PROXY_STATUS, 1); + bt_mesh_model_msg_init(&buf, OP_OD_PRIV_PROXY_STATUS); + + net_buf_simple_add_u8(&buf, bt_mesh_od_priv_proxy_get()); + + bt_mesh_model_send(mod, ctx, &buf, NULL, NULL); + + return 0; +} + +static int handle_proxy_get( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + LOG_DBG(""); + + proxy_status_rsp(mod, ctx); + + return 0; +} + +static int handle_proxy_set( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint8_t state; + + LOG_DBG(""); + + state = net_buf_simple_pull_u8(buf); + LOG_DBG("state %d", state); + + bt_mesh_od_priv_proxy_set(state); + proxy_status_rsp(mod, ctx); + + return 0; +} + +const struct bt_mesh_model_op _bt_mesh_od_priv_proxy_srv_op[] = +{ + { + OP_OD_PRIV_PROXY_GET, + BT_MESH_LEN_EXACT(0), + handle_proxy_get + }, + + { + OP_OD_PRIV_PROXY_SET, + BT_MESH_LEN_EXACT(1), + handle_proxy_set + }, + + BT_MESH_MODEL_OP_END +}; + +static int od_priv_proxy_srv_init(const struct bt_mesh_model *mod) +{ + od_priv_proxy_srv = mod; + + const struct bt_mesh_model *priv_beacon_srv = + bt_mesh_model_find(bt_mesh_model_elem(mod), + BT_MESH_MODEL_ID_PRIV_BEACON_SRV); + const struct bt_mesh_model *sol_pdu_rpl_srv = + bt_mesh_model_find(bt_mesh_model_elem(mod), + BT_MESH_MODEL_ID_SOL_PDU_RPL_SRV); + + if (priv_beacon_srv == NULL) + { + LOG_ERR("On-Demand Private Proxy server cannot extend Private Beacon " + "server"); + return -EINVAL; + } + + mod->keys[0] = BT_MESH_KEY_DEV_LOCAL; + mod->rt->flags |= BT_MESH_MOD_DEVKEY_ONLY; + + bt_mesh_model_extend(mod, priv_beacon_srv); + + if (sol_pdu_rpl_srv != NULL) + { + bt_mesh_model_correspond(mod, sol_pdu_rpl_srv); + } + else + { + LOG_WRN("On-Demand Private Proxy server cannot be corresponded by " + "Solicitation PDU " + "RPL Configuration server"); + } + + return 0; +} + +static void od_priv_proxy_srv_reset(const struct bt_mesh_model *model) +{ + on_demand_state = 0; + od_priv_proxy_store(true); +} + +#ifdef CONFIG_BT_SETTINGS +static int od_priv_proxy_srv_settings_set( + const struct bt_mesh_model *model, const char *name, size_t len_rd, + settings_read_cb read_cb, void *cb_data) +{ + int err; + + if (len_rd == 0) + { + LOG_DBG("Cleared configuration state"); + return 0; + } + + err = bt_mesh_settings_set(read_cb, cb_data, &on_demand_state, + sizeof(uint8_t)); + if (err) + { + LOG_ERR("Failed to set OD private proxy state"); + return err; + } + + bt_mesh_od_priv_proxy_set(on_demand_state); + return 0; +} + +static void +od_priv_proxy_srv_pending_store(const struct bt_mesh_model *model) +{ + on_demand_state = bt_mesh_od_priv_proxy_get(); + od_priv_proxy_store(false); +} +#endif + +const struct bt_mesh_model_cb _bt_mesh_od_priv_proxy_srv_cb = +{ + .init = od_priv_proxy_srv_init, + .reset = od_priv_proxy_srv_reset, +#ifdef CONFIG_BT_SETTINGS + .settings_set = od_priv_proxy_srv_settings_set, + .pending_store = od_priv_proxy_srv_pending_store, +#endif +}; + +void bt_mesh_od_priv_proxy_srv_store_schedule(void) +{ + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bt_mesh_model_data_store_schedule(od_priv_proxy_srv); + } +} diff --git a/libs/zblue/mesh/op_agg.c b/libs/zblue/mesh/op_agg.c new file mode 100644 index 0000000000000..f1171ae2f6d8b --- /dev/null +++ b/libs/zblue/mesh/op_agg.c @@ -0,0 +1,113 @@ +/**************************************************************************** + * libs/zblue/mesh/op_agg.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include "foundation.h" +#include "op_agg.h" + +#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_op_agg); + +#define IS_LENGTH_LONG(buf) ((buf)->data[0] & 1) +#define LENGTH_SHORT_MAX BIT_MASK(7) + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bt_mesh_op_agg_encode_msg(struct net_buf_simple *msg, + struct net_buf_simple *buf) +{ + if (msg->len > LENGTH_SHORT_MAX) + { + if (net_buf_simple_tailroom(buf) < (msg->len + 2)) + { + return -ENOMEM; + } + + net_buf_simple_add_le16(buf, (msg->len << 1) | 1); + } + else + { + if (net_buf_simple_tailroom(buf) < (msg->len + 1)) + { + return -ENOMEM; + } + + net_buf_simple_add_u8(buf, msg->len << 1); + } + + net_buf_simple_add_mem(buf, msg->data, msg->len); + + return 0; +} + +int bt_mesh_op_agg_decode_msg(struct net_buf_simple *msg, + struct net_buf_simple *buf) +{ + uint16_t len; + + if (IS_LENGTH_LONG(buf)) + { + if (buf->len < 2) + { + return -EINVAL; + } + + len = net_buf_simple_pull_le16(buf) >> 1; + } + else + { + if (buf->len < 1) + { + return -EINVAL; + } + + len = net_buf_simple_pull_u8(buf) >> 1; + } + + if (buf->len < len) + { + return -EINVAL; + } + + net_buf_simple_init_with_data(msg, net_buf_simple_pull_mem(buf, len), len); + + return 0; +} + +bool bt_mesh_op_agg_is_op_agg_msg(struct net_buf_simple *buf) +{ + if (buf->len >= 2 && (buf->data[0] >> 6) == 2) + { + uint16_t opcode; + struct net_buf_simple_state state; + + net_buf_simple_save(buf, &state); + opcode = net_buf_simple_pull_be16(buf); + net_buf_simple_restore(buf, &state); + + if ((opcode == OP_OPCODES_AGGREGATOR_STATUS) || + (opcode == OP_OPCODES_AGGREGATOR_SEQUENCE)) + { + return true; + } + } + + return false; +} diff --git a/libs/zblue/mesh/op_agg.h b/libs/zblue/mesh/op_agg.h new file mode 100644 index 0000000000000..3111c542046f0 --- /dev/null +++ b/libs/zblue/mesh/op_agg.h @@ -0,0 +1,48 @@ +/**************************************************************************** + * libs/zblue/mesh/op_agg.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +struct op_agg_ctx +{ + /** Context is initialized. */ + + bool initialized; + + /** NetKey Index of the subnet to send the message on. */ + + uint16_t net_idx; + + /** AppKey Index to encrypt the message with. */ + + uint16_t app_idx; + + /** Remote element address. */ + + uint16_t addr; + + /** Aggregated message buffer. */ + + struct net_buf_simple *sdu; +}; + +int bt_mesh_op_agg_encode_msg(struct net_buf_simple *msg, + struct net_buf_simple *buf); +int bt_mesh_op_agg_decode_msg(struct net_buf_simple *msg, + struct net_buf_simple *buf); +int bt_mesh_op_agg_cli_send(const struct bt_mesh_model *model, + struct net_buf_simple *msg); +int bt_mesh_op_agg_cli_accept(struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf); +int bt_mesh_op_agg_srv_send(const struct bt_mesh_model *model, + struct net_buf_simple *msg); +int bt_mesh_op_agg_srv_accept(struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf); +bool bt_mesh_op_agg_is_op_agg_msg(struct net_buf_simple *buf); diff --git a/libs/zblue/mesh/op_agg_cli.c b/libs/zblue/mesh/op_agg_cli.c new file mode 100644 index 0000000000000..5d7d7b0364964 --- /dev/null +++ b/libs/zblue/mesh/op_agg_cli.c @@ -0,0 +1,282 @@ +/**************************************************************************** + * libs/zblue/mesh/op_agg_cli.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include + +#include "access.h" +#include "foundation.h" +#include "net.h" +#include "mesh.h" +#include "op_agg.h" + +#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_op_agg_cli); + +NET_BUF_SIMPLE_DEFINE_STATIC(srcs, BT_MESH_TX_SDU_MAX); +NET_BUF_SIMPLE_DEFINE_STATIC(sdu, BT_MESH_TX_SDU_MAX); + +/** Mesh Opcodes Aggregator Client Model Context */ + +static struct bt_mesh_op_agg_cli +{ + /** Composition data model entry pointer. */ + + const struct bt_mesh_model *model; + + /** Acknowledge context. */ + + struct bt_mesh_msg_ack_ctx ack_ctx; + /** List of source element addresses. + * Used by Client to match aggregated responses with local source client + * models. + */ + + struct net_buf_simple *srcs; + + /** Aggregator context. */ + + struct op_agg_ctx ctx; +} + +cli = +{ + .srcs = &srcs, .ctx.sdu = &sdu +}; + +static int32_t msg_timeout; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int handle_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct net_buf_simple msg; + uint8_t status; + uint16_t elem_addr; + uint16_t addr; + int err; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + if (!bt_mesh_msg_ack_ctx_match(&cli.ack_ctx, OP_OPCODES_AGGREGATOR_STATUS, + ctx->addr, NULL)) + { + LOG_WRN("Unexpected Opcodes Aggregator Status"); + return -ENOENT; + } + + status = net_buf_simple_pull_u8(buf); + elem_addr = net_buf_simple_pull_le16(buf); + + while (buf->len > 0) + { + err = bt_mesh_op_agg_decode_msg(&msg, buf); + if (err) + { + LOG_ERR("Cannot decode aggregated message %d", err); + cli.ctx.initialized = true; + return -EINVAL; + } + + if (cli.srcs->len < 2) + { + LOG_ERR("Mismatch in sources address buffer"); + cli.ctx.initialized = true; + return -ENOENT; + } + + addr = net_buf_simple_pull_le16(cli.srcs); + + /* Empty item means unacked msg. */ + + if (!msg.len) + { + continue; + } + + ctx->recv_dst = addr; + err = bt_mesh_model_recv(ctx, &msg); + if (err) + { + LOG_ERR("Opcodes Aggregator receive error %d", err); + cli.ctx.initialized = true; + return err; + } + } + + bt_mesh_msg_ack_ctx_rx(&cli.ack_ctx); + + return 0; +} + +const struct bt_mesh_model_op _bt_mesh_op_agg_cli_op[] = +{ + { + OP_OPCODES_AGGREGATOR_STATUS, + BT_MESH_LEN_MIN(3), + handle_status + }, + + BT_MESH_MODEL_OP_END, +}; + +static int op_agg_cli_init(const struct bt_mesh_model *model) +{ + if (!bt_mesh_model_in_primary(model)) + { + LOG_ERR("Opcodes Aggregator Client only allowed in primary element"); + return -EINVAL; + } + + /* Opcodes Aggregator Client model shall use the device key and + * application keys. + */ + + model->keys[0] = BT_MESH_KEY_DEV_ANY; + + msg_timeout = CONFIG_BT_MESH_OP_AGG_CLI_TIMEOUT; + cli.model = model; + bt_mesh_msg_ack_ctx_init(&cli.ack_ctx); + + return 0; +} + +int bt_mesh_op_agg_cli_seq_start( + uint16_t net_idx, uint16_t app_idx, uint16_t dst, uint16_t elem_addr) +{ + if (!BT_MESH_ADDR_IS_UNICAST(elem_addr)) + { + LOG_ERR("Element address shall be a unicast address"); + return -EINVAL; + } + + if (cli.ctx.initialized) + { + LOG_ERR("Opcodes Aggregator is already configured"); + return -EALREADY; + } + + cli.ctx.net_idx = net_idx; + cli.ctx.app_idx = app_idx; + cli.ctx.addr = dst; + cli.ctx.initialized = true; + + net_buf_simple_init(cli.srcs, 0); + bt_mesh_model_msg_init(cli.ctx.sdu, OP_OPCODES_AGGREGATOR_SEQUENCE); + net_buf_simple_add_le16(cli.ctx.sdu, elem_addr); + + return 0; +} + +int bt_mesh_op_agg_cli_seq_send(void) +{ + struct bt_mesh_msg_ctx ctx = + { + .net_idx = cli.ctx.net_idx, + .app_idx = cli.ctx.app_idx, + .addr = cli.ctx.addr, + }; + + int err; + + if (!cli.ctx.initialized) + { + LOG_ERR("Opcodes Aggregator not initialized"); + return -EINVAL; + } + + err = bt_mesh_msg_ack_ctx_prepare(&cli.ack_ctx, + OP_OPCODES_AGGREGATOR_STATUS, + cli.ctx.addr, NULL); + if (err) + { + return err; + } + + cli.ctx.initialized = false; + + err = bt_mesh_model_send(cli.model, &ctx, cli.ctx.sdu, NULL, NULL); + if (err) + { + LOG_ERR("model_send() failed (err %d)", err); + bt_mesh_msg_ack_ctx_clear(&cli.ack_ctx); + return err; + } + + return bt_mesh_msg_ack_ctx_wait(&cli.ack_ctx, MSEC2TICK(msg_timeout)); +} + +void bt_mesh_op_agg_cli_seq_abort(void) +{ + cli.ctx.initialized = false; +} + +bool bt_mesh_op_agg_cli_seq_is_started(void) +{ + return cli.ctx.initialized; +} + +size_t bt_mesh_op_agg_cli_seq_tailroom(void) +{ + return net_buf_simple_tailroom(cli.ctx.sdu); +} + +int32_t bt_mesh_op_agg_cli_timeout_get(void) +{ + return msg_timeout; +} + +void bt_mesh_op_agg_cli_timeout_set(int32_t timeout) +{ + msg_timeout = timeout; +} + +int bt_mesh_op_agg_cli_send(const struct bt_mesh_model *model, + struct net_buf_simple *msg) +{ + uint16_t src = bt_mesh_model_elem(model)->rt->addr; + + if (net_buf_simple_tailroom(&srcs) < 2) + { + return -ENOMEM; + } + + net_buf_simple_add_le16(&srcs, src); + return bt_mesh_op_agg_encode_msg(msg, cli.ctx.sdu); +} + +int bt_mesh_op_agg_cli_accept(struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + return cli.ctx.initialized && (ctx->net_idx == cli.ctx.net_idx) && + (ctx->addr == cli.ctx.addr) && + (ctx->app_idx == cli.ctx.app_idx) && + !bt_mesh_op_agg_is_op_agg_msg(buf); +} + +const struct bt_mesh_model_cb _bt_mesh_op_agg_cli_cb = +{ + .init = op_agg_cli_init, +}; diff --git a/libs/zblue/mesh/op_agg_srv.c b/libs/zblue/mesh/op_agg_srv.c new file mode 100644 index 0000000000000..baa82ea38c1b4 --- /dev/null +++ b/libs/zblue/mesh/op_agg_srv.c @@ -0,0 +1,210 @@ +/**************************************************************************** + * libs/zblue/mesh/op_agg_srv.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include "access.h" +#include "foundation.h" +#include "net.h" +#include "mesh.h" +#include "transport.h" +#include "op_agg.h" + +#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_op_agg_srv); + +NET_BUF_SIMPLE_DEFINE_STATIC(sdu, BT_MESH_TX_SDU_MAX); + +/** Mesh Opcodes Aggregator Server Model Context */ + +static struct bt_mesh_op_agg_srv +{ + /** Composition data model entry pointer. */ + + const struct bt_mesh_model *model; + + /** Response error code. */ + + int rsp_err; + /** Indicates that the received aggregated message was acknowledged by + * local server model. + */ + + bool ack; + + /** Aggregator context. */ + + struct op_agg_ctx ctx; +} + +srv = +{ + .ctx.sdu = &sdu +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int handle_sequence( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct net_buf_simple_state state; + struct net_buf_simple msg; + uint16_t elem; + uint8_t *status; + int err; + + elem = net_buf_simple_pull_le16(buf); + ctx->recv_dst = elem; + + bt_mesh_model_msg_init(srv.ctx.sdu, OP_OPCODES_AGGREGATOR_STATUS); + status = net_buf_simple_add_u8(srv.ctx.sdu, 0); + net_buf_simple_add_le16(srv.ctx.sdu, elem); + + srv.ctx.net_idx = ctx->net_idx; + srv.ctx.app_idx = ctx->app_idx; + srv.ctx.addr = ctx->addr; + srv.ctx.initialized = true; + + if (!BT_MESH_ADDR_IS_UNICAST(elem)) + { + LOG_WRN("Address is not unicast, ignoring."); + return -EINVAL; + } + + net_buf_simple_save(buf, &state); + while (buf->len > 0) + { + err = bt_mesh_op_agg_decode_msg(&msg, buf); + if (err) + { + LOG_ERR("Unable to parse Opcodes Aggregator Sequence message (err " + "%d)", + err); + return err; + } + } + + net_buf_simple_restore(buf, &state); + + if (!bt_mesh_elem_find(elem)) + { + *status = ACCESS_STATUS_INVALID_ADDRESS; + goto send; + } + + while (buf->len > 0) + { + (void)bt_mesh_op_agg_decode_msg(&msg, buf); + + srv.ack = false; + srv.rsp_err = 0; + err = bt_mesh_model_recv(ctx, &msg); + + if (srv.rsp_err) + { + *status = srv.rsp_err; + break; + } + + if (err) + { + *status = err; + break; + } + + if (!srv.ack) + { + net_buf_simple_add_u8(srv.ctx.sdu, 0); + } + } + +send: + srv.ctx.initialized = false; + err = bt_mesh_model_send(model, ctx, srv.ctx.sdu, NULL, NULL); + if (err) + { + LOG_ERR("Unable to send Opcodes Aggregator Status"); + return err; + } + + return 0; +} + +const struct bt_mesh_model_op _bt_mesh_op_agg_srv_op[] = +{ + { + OP_OPCODES_AGGREGATOR_SEQUENCE, + BT_MESH_LEN_MIN(2), + handle_sequence + }, + + BT_MESH_MODEL_OP_END, +}; + +static int op_agg_srv_init(const struct bt_mesh_model *model) +{ + if (!bt_mesh_model_in_primary(model)) + { + LOG_ERR("Opcodes Aggregator Server only allowed in primary element"); + return -EINVAL; + } + + /* Opcodes Aggregator Server model shall use the device key and + * application keys. + */ + + model->keys[0] = BT_MESH_KEY_DEV_ANY; + + srv.model = model; + + return 0; +} + +int bt_mesh_op_agg_srv_send(const struct bt_mesh_model *model, + struct net_buf_simple *msg) +{ + int err; + + /* Model responded so mark this message as acknowledged */ + + srv.ack = true; + + err = bt_mesh_op_agg_encode_msg(msg, srv.ctx.sdu); + if (err) + { + srv.rsp_err = ACCESS_STATUS_RESPONSE_OVERFLOW; + } + + return err; +} + +int bt_mesh_op_agg_srv_accept(struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + return srv.ctx.initialized && (ctx->net_idx == srv.ctx.net_idx) && + (ctx->addr == srv.ctx.addr) && + (ctx->app_idx == srv.ctx.app_idx) && + !bt_mesh_op_agg_is_op_agg_msg(buf); +} + +const struct bt_mesh_model_cb _bt_mesh_op_agg_srv_cb = +{ + .init = op_agg_srv_init, +}; diff --git a/libs/zblue/mesh/pb_adv.c b/libs/zblue/mesh/pb_adv.c new file mode 100644 index 0000000000000..f9299c023d19d --- /dev/null +++ b/libs/zblue/mesh/pb_adv.c @@ -0,0 +1,1341 @@ +/**************************************************************************** + * libs/zblue/mesh/pb_adv.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include "testing.h" +#include "net.h" +#include "crypto.h" +#include "beacon.h" +#include "prov.h" + +#include "common/bt_str.h" + +#define LOG_LEVEL CONFIG_BT_MESH_PROV_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_pb_adv); + +#define GPCF(gpc) (gpc & 0x03) +#define GPC_START(last_seg) (((last_seg) << 2) | 0x00) +#define GPC_ACK 0x01 +#define GPC_CONT(seg_id) (((seg_id) << 2) | 0x02) +#define GPC_CTL(op) (((op) << 2) | 0x03) + +#define START_PAYLOAD_MAX 20 +#define CONT_PAYLOAD_MAX 23 +#define RX_BUFFER_MAX 65 + +#define START_LAST_SEG(gpc) (gpc >> 2) +#define CONT_SEG_INDEX(gpc) (gpc >> 2) + +#define BEARER_CTL(gpc) (gpc >> 2) +#define LINK_OPEN 0x00 +#define LINK_ACK 0x01 +#define LINK_CLOSE 0x02 + +#define XACT_SEG_OFFSET(_seg) (20 + ((_seg - 1) * 23)) +#define XACT_SEG_DATA(_seg) (&pb_adv_link.rx.buf->data[XACT_SEG_OFFSET(_seg)]) +#define XACT_SEG_RECV(_seg) (pb_adv_link.rx.seg &= ~(1 << (_seg))) + +#define XACT_ID_MAX 0x7f +#define XACT_ID_NVAL 0xff +#define SEG_NVAL 0xff + +#define RETRANSMIT_TIMEOUT MSEC2TICK(CONFIG_BT_MESH_PB_ADV_RETRANS_TIMEOUT) +#define BUF_TIMEOUT MSEC2TICK(400) +#define CLOSING_TIMEOUT 3 +#define TRANSACTION_TIMEOUT 30 + +/* Acked messages, will do retransmissions manually, taking acks into + * account: + */ +#define RETRANSMITS_RELIABLE CONFIG_BT_MESH_PB_ADV_TRANS_PDU_RETRANSMIT_COUNT +/* PDU acks: */ +#define RETRANSMITS_ACK CONFIG_BT_MESH_PB_ADV_TRANS_ACK_RETRANSMIT_COUNT +/* Link close retransmits: */ +#define RETRANSMITS_LINK_CLOSE CONFIG_BT_MESH_PB_ADV_LINK_CLOSE_RETRANSMIT_COUNT + +enum +{ + ADV_LINK_ACTIVE, /* Link has been opened */ + ADV_LINK_ACK_RECVD, /* Ack for link has been received */ + ADV_LINK_CLOSING, /* Link is closing down */ + ADV_LINK_INVALID, /* Error occurred during provisioning */ + ADV_ACK_PENDING, /* An acknowledgment is being sent */ + ADV_PROVISIONER, /* The link was opened as provisioner */ + ADV_LINK_ACK_SENDING, /* Link Ack tx was scheduled but not finished. */ + ADV_SENDING, /* Sending any PDU */ + + ADV_NUM_FLAGS, +}; + +struct pb_adv +{ + uint32_t id; /* Link ID */ + + atomic_define(flags, ADV_NUM_FLAGS); + + const struct prov_bearer_cb *cb; + void *cb_data; + + struct + { + uint8_t id; /* Most recent transaction ID */ + uint8_t seg; /* Bit-field of unreceived segments */ + uint8_t last_seg; /* Last segment (to check length) */ + uint8_t fcs; /* Expected FCS value */ + struct net_buf_simple *buf; + } rx; + + struct + { + /* Start timestamp of the transaction */ + + int64_t start; + + /* Transaction id */ + + uint8_t id; + + /* Current ack id */ + + uint8_t pending_ack; + + /* Transaction timeout in seconds */ + + uint8_t timeout; + + /* Pending Link Open, Gen Trans Start and Gen Trans Cont advs */ + + struct bt_mesh_adv *adv[3]; + + /* Index of the next adv to be sent */ + + int next; + + prov_bearer_send_complete_t cb; + + void *cb_data; + + /* Retransmit timer */ + + struct k_work_delayable retransmit; + + /* Unacked adv buffers (Link Ack, Link Close and Gen Trans Ack). Array + * size is hardcoded to 2 allowing to send Gen Trans Ack and Link Close + * at the same time. + */ + + struct unacked_adv_ctx + { + struct bt_mesh_adv *adv; + prov_bearer_send_complete_t cb; + void *cb_data; + } + + unacked[2]; + + /* Last sent unacked[] buffer */ + + int last_unacked; + } tx; + + /* Protocol timeout */ + + struct k_work_delayable prot_timer; +}; + +struct prov_rx +{ + uint32_t link_id; + uint8_t xact_id; + uint8_t gpc; +}; + +NET_BUF_SIMPLE_DEFINE_STATIC(rx_buf, RX_BUFFER_MAX); + +static struct pb_adv pb_adv_link = +{ + .rx = + { + .buf = &rx_buf + } +}; + +static void gen_prov_ack_send(uint8_t xact_id); +static void link_open(struct prov_rx *rx, struct net_buf_simple *buf); +static void link_ack(struct prov_rx *rx, struct net_buf_simple *buf); +static void link_close(struct prov_rx *rx, struct net_buf_simple *buf); +static void prov_link_close(enum prov_bearer_link_status status); +static void close_link(enum prov_bearer_link_status status); + +static void tx_work_handler(struct k_work *work); +static K_WORK_DELAYABLE_DEFINE(tx_work, tx_work_handler); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void tx_schedule(void) +{ + uint16_t random_delay; + + if (atomic_test_bit(pb_adv_link.flags, ADV_SENDING)) + { + LOG_DBG("Another tx is in progress"); + return; + } + + (void)bt_rand(&random_delay, sizeof(random_delay)); + random_delay = 20 + (random_delay % 30); + + LOG_DBG("Next PDU delayed by %ums", random_delay); + + (void)k_work_schedule(&tx_work, MSEC2TICK(random_delay)); +} + +static int send_unacked(struct bt_mesh_adv *adv, + prov_bearer_send_complete_t cb, void *cb_data) +{ + for (int i = 0; i < ARRAY_SIZE(pb_adv_link.tx.unacked); i++) + { + if (pb_adv_link.tx.unacked[i].adv != NULL) + { + continue; + } + + pb_adv_link.tx.unacked[i].adv = adv; + pb_adv_link.tx.unacked[i].cb = cb; + pb_adv_link.tx.unacked[i].cb_data = cb_data; + + tx_schedule(); + + return 0; + } + + LOG_WRN("No memory to send unacked PDU: %s", bt_hex(adv->b.data, + adv->b.len)); + return -ENOMEM; +} + +static void send_reliable(void) +{ + /* Dropping next tx adv index to start transmission from the first adv + * buffer. + */ + + pb_adv_link.tx.next = 0; + + tx_schedule(); +} + +static void delayed_adv_send_end(int err, void *user_data) +{ + bool unacked = (bool)user_data; + struct unacked_adv_ctx *unacked_adv = + &pb_adv_link.tx.unacked[pb_adv_link.tx.last_unacked]; + + if (unacked && unacked_adv->adv != NULL) + { + if (unacked_adv->cb) + { + unacked_adv->cb(err, unacked_adv->cb_data); + } + + bt_mesh_adv_unref(unacked_adv->adv); + unacked_adv->adv = NULL; + } + + atomic_clear_bit(pb_adv_link.flags, ADV_SENDING); + tx_schedule(); +} + +static void delayed_adv_send_start(uint16_t duration, + int err, void *user_data) +{ + if (err) + { + delayed_adv_send_end(err, user_data); + } +} + +static const struct bt_mesh_send_cb delayed_adv_send_cb = +{ + .start = delayed_adv_send_start, + .end = delayed_adv_send_end, +}; + +static void tx_work_handler(struct k_work *work) +{ + int i; + + /* Send Link Ack, Link Close and Gen Trans Ack first. */ + + for (i = 0; i < ARRAY_SIZE(pb_adv_link.tx.unacked); i++) + { + int idx = + (i + pb_adv_link.tx.last_unacked) % ARRAY_SIZE( + pb_adv_link.tx.unacked); + struct unacked_adv_ctx *unacked = &pb_adv_link.tx.unacked[idx]; + + if (!unacked->adv) + { + continue; + } + + atomic_set_bit(pb_adv_link.flags, ADV_SENDING); + bt_mesh_adv_send(unacked->adv, &delayed_adv_send_cb, (void *)true); + + pb_adv_link.tx.last_unacked = idx; + + return; + } + + /* Send Trans Start, Trans Cont and Link Open */ + + if (pb_adv_link.tx.next >= ARRAY_SIZE(pb_adv_link.tx.adv) || + pb_adv_link.tx.adv[pb_adv_link.tx.next] == NULL) + { + LOG_DBG("All PDUs were sent"); + return; + } + + atomic_set_bit(pb_adv_link.flags, ADV_SENDING); + bt_mesh_adv_send(pb_adv_link.tx.adv[pb_adv_link.tx.next], + &delayed_adv_send_cb, (void *)false); + + pb_adv_link.tx.next++; + + if (pb_adv_link.tx.next == ARRAY_SIZE(pb_adv_link.tx.adv) || + pb_adv_link.tx.adv[pb_adv_link.tx.next] == NULL) + { + /* All ack-able PDUs are sent. Now we can run the retransmit timer. */ + + LOG_DBG("Starting retransmit timer"); + k_work_reschedule(&pb_adv_link.tx.retransmit, RETRANSMIT_TIMEOUT); + } +} + +static uint8_t last_seg(uint16_t len) +{ + if (len <= START_PAYLOAD_MAX) + { + return 0; + } + + len -= START_PAYLOAD_MAX; + + return 1 + (len / CONT_PAYLOAD_MAX); +} + +static void free_segments(void) +{ + int i; + bool canceled = false; + + for (i = 0; i < ARRAY_SIZE(pb_adv_link.tx.adv); i++) + { + struct bt_mesh_adv *adv = pb_adv_link.tx.adv[i]; + int err; + + if (!adv) + { + break; + } + + pb_adv_link.tx.adv[i] = NULL; + + /* Terminate active adv */ + + if (adv->ctx.busy == 0U) + { + err = bt_mesh_adv_terminate(adv); + if (err == 0) + { + canceled = true; + } + } + else + { + /* Mark as canceled */ + + adv->ctx.busy = 0U; + canceled = true; + } + + bt_mesh_adv_unref(adv); + } + + if (canceled) + { + atomic_clear_bit(pb_adv_link.flags, ADV_SENDING); + tx_schedule(); + } +} + +static uint8_t next_transaction_id(uint8_t id) +{ + return (((id + 1) & XACT_ID_MAX) | (id & (XACT_ID_MAX + 1))); +} + +static void prov_clear_tx(void) +{ + LOG_DBG(""); + + /* If this fails, the work handler will not find any advs to send, + * and return without rescheduling. The work handler also checks the + * LINK_ACTIVE flag, so if this call is part of reset_adv_link, it'll exit + * early. + */ + + (void)k_work_cancel_delayable(&pb_adv_link.tx.retransmit); + + free_segments(); +} + +static void reset_adv_link(void) +{ + LOG_DBG(""); + prov_clear_tx(); + + /* If this fails, the work handler will exit early on the LINK_ACTIVE + * check. + */ + + (void)k_work_cancel_delayable(&pb_adv_link.prot_timer); + + if (atomic_test_bit(pb_adv_link.flags, ADV_PROVISIONER)) + { + /* Clear everything except the retransmit and protocol timer + * delayed work objects. + */ + + (void)memset(&pb_adv_link, 0, offsetof(struct pb_adv, tx.retransmit)); + pb_adv_link.rx.id = XACT_ID_NVAL; + } + else + { + /* If provisioned, reset the link callback to stop receiving + * provisioning advs, otherwise keep the callback to accept another + * provisioning attempt. + */ + + if (bt_mesh_is_provisioned()) + { + pb_adv_link.cb = NULL; + } + + pb_adv_link.id = 0; + atomic_set(pb_adv_link.flags, 0); + pb_adv_link.rx.id = XACT_ID_MAX; + pb_adv_link.tx.id = XACT_ID_NVAL; + } + + pb_adv_link.tx.pending_ack = XACT_ID_NVAL; + pb_adv_link.rx.buf = &rx_buf; + net_buf_simple_reset(pb_adv_link.rx.buf); +} + +static void close_link(enum prov_bearer_link_status reason) +{ + const struct prov_bearer_cb *cb = pb_adv_link.cb; + void *cb_data = pb_adv_link.cb_data; + + reset_adv_link(); + cb->link_closed(&bt_mesh_pb_adv, cb_data, reason); +} + +static struct bt_mesh_adv *adv_create(uint8_t retransmits) +{ + struct bt_mesh_adv *adv; + + adv = bt_mesh_adv_create(BT_MESH_ADV_PROV, BT_MESH_ADV_TAG_PROV, + BT_MESH_TRANSMIT(retransmits, 20), BUF_TIMEOUT); + if (!adv) + { + LOG_ERR("Out of provisioning advs"); + return NULL; + } + + return adv; +} + +static void ack_complete(int err, void *user_data) +{ + LOG_DBG("xact 0x%x complete", (uint8_t)pb_adv_link.tx.pending_ack); + atomic_clear_bit(pb_adv_link.flags, ADV_ACK_PENDING); +} + +static bool ack_pending(void) +{ + return atomic_test_bit(pb_adv_link.flags, ADV_ACK_PENDING); +} + +static void prov_failed(uint8_t err) +{ + LOG_DBG("%u", err); + pb_adv_link.cb->error(&bt_mesh_pb_adv, pb_adv_link.cb_data, err); + atomic_set_bit(pb_adv_link.flags, ADV_LINK_INVALID); +} + +static void prov_msg_recv(void) +{ + if (!bt_mesh_fcs_check(pb_adv_link.rx.buf, pb_adv_link.rx.fcs)) + { + LOG_ERR("Incorrect FCS"); + return; + } + + gen_prov_ack_send(pb_adv_link.rx.id); + + if (atomic_test_bit(pb_adv_link.flags, ADV_LINK_INVALID)) + { + LOG_WRN("Unexpected msg 0x%02x on invalidated link", + pb_adv_link.rx.buf->data[0]); + prov_failed(PROV_ERR_UNEXP_PDU); + return; + } + + k_work_reschedule(&pb_adv_link.prot_timer, + bt_mesh_prov_protocol_timeout_get()); + pb_adv_link.cb->recv(&bt_mesh_pb_adv, pb_adv_link.cb_data, + pb_adv_link.rx.buf); +} + +static void protocol_timeout(struct k_work *work) +{ + if (!atomic_test_bit(pb_adv_link.flags, ADV_LINK_ACTIVE)) + { + return; + } + + LOG_DBG(""); + + pb_adv_link.rx.seg = 0U; + prov_link_close(PROV_BEARER_LINK_STATUS_TIMEOUT); +} + +/* Generic provisioning + */ + +static void gen_prov_ack_send(uint8_t xact_id) +{ + struct bt_mesh_adv *adv; + bool pending = atomic_test_and_set_bit(pb_adv_link.flags, ADV_ACK_PENDING); + int err; + + LOG_DBG("xact_id 0x%x", xact_id); + + if (pending && pb_adv_link.tx.pending_ack == xact_id) + { + LOG_DBG("Not sending duplicate ack"); + return; + } + + adv = adv_create(RETRANSMITS_ACK); + if (!adv) + { + atomic_clear_bit(pb_adv_link.flags, ADV_ACK_PENDING); + return; + } + + if (!pending) + { + pb_adv_link.tx.pending_ack = xact_id; + } + + net_buf_simple_add_be32(&adv->b, pb_adv_link.id); + net_buf_simple_add_u8(&adv->b, xact_id); + net_buf_simple_add_u8(&adv->b, GPC_ACK); + + err = send_unacked(adv, pending ? NULL : ack_complete, NULL); + if (err) + { + atomic_clear_bit(pb_adv_link.flags, ADV_ACK_PENDING); + } +} + +static void gen_prov_cont(struct prov_rx *rx, struct net_buf_simple *buf) +{ + uint8_t seg = CONT_SEG_INDEX(rx->gpc); + + LOG_DBG("len %u, seg_index %u", buf->len, seg); + + /* When pb_adv_link.rx.seg is zero for a valid pb_adv_link.rx.id, this + * means that transaction has already been received. The other device + * probably missed the Transaction Acknowledgment PDU, so we need to resend + * it regardless of the active transmission. + */ + + if (!pb_adv_link.rx.seg && pb_adv_link.rx.id == rx->xact_id) + { + /* Send ack if another ack is NOT pending for transmission. Otherwise, + * skip sending this ack for now. + */ + + if (!ack_pending()) + { + LOG_DBG("Resending ack"); + gen_prov_ack_send(rx->xact_id); + } + + return; + } + + if (pb_adv_link.tx.adv[0]) + { + LOG_DBG("Ongoing tx transaction has not been completed yet"); + return; + } + + if (!pb_adv_link.rx.seg && + next_transaction_id(pb_adv_link.rx.id) == rx->xact_id) + { + LOG_DBG("Start segment lost"); + + pb_adv_link.rx.id = rx->xact_id; + + net_buf_simple_reset(pb_adv_link.rx.buf); + + pb_adv_link.rx.seg = SEG_NVAL; + pb_adv_link.rx.last_seg = SEG_NVAL; + + prov_clear_tx(); + } + else if (rx->xact_id != pb_adv_link.rx.id) + { + LOG_WRN("Data for unknown transaction (0x%x != 0x%x)", rx->xact_id, + pb_adv_link.rx.id); + return; + } + + if (seg > pb_adv_link.rx.last_seg || seg == 0) + { + LOG_ERR("Invalid segment index %u", seg); + prov_failed(PROV_ERR_NVAL_FMT); + return; + } + + if (!(pb_adv_link.rx.seg & BIT(seg))) + { + LOG_DBG("Ignoring already received segment"); + return; + } + + if (XACT_SEG_OFFSET(seg) + buf->len > RX_BUFFER_MAX) + { + LOG_WRN("Rx buffer overflow. Malformed generic prov frame?"); + return; + } + + memcpy(XACT_SEG_DATA(seg), buf->data, buf->len); + XACT_SEG_RECV(seg); + + if (seg == pb_adv_link.rx.last_seg && !(pb_adv_link.rx.seg & BIT(0))) + { + uint8_t expect_len; + + expect_len = + (pb_adv_link.rx.buf->len - 20U - ((pb_adv_link.rx.last_seg - + 1) * 23U)); + if (expect_len != buf->len) + { + LOG_ERR("Incorrect last seg len: %u != %u", expect_len, buf->len); + prov_failed(PROV_ERR_NVAL_FMT); + return; + } + } + + if (!pb_adv_link.rx.seg) + { + prov_msg_recv(); + } +} + +static void gen_prov_ack(struct prov_rx *rx, struct net_buf_simple *buf) +{ + LOG_DBG("len %u", buf->len); + + if (!pb_adv_link.tx.adv[0]) + { + return; + } + + if (rx->xact_id == pb_adv_link.tx.id) + { + /* Don't clear resending of link_close messages */ + + if (!atomic_test_bit(pb_adv_link.flags, ADV_LINK_CLOSING)) + { + prov_clear_tx(); + } + + if (pb_adv_link.tx.cb) + { + pb_adv_link.tx.cb(0, pb_adv_link.tx.cb_data); + } + } +} + +static void gen_prov_start(struct prov_rx *rx, struct net_buf_simple *buf) +{ + uint8_t seg = SEG_NVAL; + + /* When pb_adv_link.rx.seg is zero for a valid pb_adv_link.rx.id, this + * means that transaction has already been received. The other device + * probably missed the Transaction Acknowledgment PDU, so we need to resend + * it regardless of the active transmission. + */ + + if (rx->xact_id == pb_adv_link.rx.id && !pb_adv_link.rx.seg) + { + /* Send ack if another ack is NOT pending for transmission. Otherwise, + * skip sending this ack for now. + */ + + if (!ack_pending()) + { + LOG_DBG("Resending ack"); + gen_prov_ack_send(rx->xact_id); + } + + return; + } + + if (pb_adv_link.tx.adv[0]) + { + LOG_DBG("Ongoing tx transaction has not been completed yet"); + return; + } + + if (rx->xact_id == pb_adv_link.rx.id) + { + if (!(pb_adv_link.rx.seg & BIT(0))) + { + LOG_DBG("Ignoring duplicate segment"); + return; + } + } + else if (rx->xact_id != next_transaction_id(pb_adv_link.rx.id)) + { + LOG_WRN("Unexpected xact 0x%x, expected 0x%x", rx->xact_id, + next_transaction_id(pb_adv_link.rx.id)); + return; + } + + net_buf_simple_reset(pb_adv_link.rx.buf); + pb_adv_link.rx.buf->len = net_buf_simple_pull_be16(buf); + pb_adv_link.rx.id = rx->xact_id; + pb_adv_link.rx.fcs = net_buf_simple_pull_u8(buf); + + LOG_DBG("len %u last_seg %u total_len %u fcs 0x%02x", buf->len, + START_LAST_SEG(rx->gpc), + pb_adv_link.rx.buf->len, pb_adv_link.rx.fcs); + + if (pb_adv_link.rx.buf->len < 1) + { + LOG_ERR("Ignoring zero-length provisioning PDU"); + prov_failed(PROV_ERR_NVAL_FMT); + return; + } + + if (pb_adv_link.rx.buf->len > pb_adv_link.rx.buf->size) + { + LOG_ERR("Too large provisioning PDU (%u bytes)", + pb_adv_link.rx.buf->len); + prov_failed(PROV_ERR_NVAL_FMT); + return; + } + + if (pb_adv_link.rx.buf->len < buf->len) + { + LOG_ERR("Invalid declared provisionig PDU length (%u > %u)", buf->len, + pb_adv_link.rx.buf->len); + prov_failed(PROV_ERR_NVAL_FMT); + return; + } + + if (START_LAST_SEG(rx->gpc) > 0 && pb_adv_link.rx.buf->len <= 20U) + { + LOG_ERR("Too small total length for multi-segment PDU"); + prov_failed(PROV_ERR_NVAL_FMT); + return; + } + + if (START_LAST_SEG(rx->gpc) != last_seg(pb_adv_link.rx.buf->len)) + { + LOG_ERR("Invalid SegN (%u, calculated %u)", START_LAST_SEG(rx->gpc), + last_seg(pb_adv_link.rx.buf->len)); + prov_failed(PROV_ERR_NVAL_FMT); + return; + } + + prov_clear_tx(); + + pb_adv_link.rx.last_seg = START_LAST_SEG(rx->gpc); + + /* This (BIT(0) is set) can happen if we received a Transaction + * Continuation PDU, before receiving a Transaction Start PDU (see + * `gen_prov_cont`). Now we received the Transaction Start PDU and we can + * extract the last segment number. Knowing this, we check if previously + * received segment exceeds the last segment number. If so, we reject the + * Transaction Start PDU. + */ + + if ((pb_adv_link.rx.seg & BIT(0)) && + ((pb_adv_link.rx.seg & SEG_NVAL) != SEG_NVAL) && + (fls((~pb_adv_link.rx.seg) & SEG_NVAL) - 1 > pb_adv_link.rx.last_seg)) + { + LOG_ERR("Invalid segment index %u", seg); + prov_failed(PROV_ERR_NVAL_FMT); + return; + } + + if (pb_adv_link.rx.seg) + { + seg = pb_adv_link.rx.seg; + } + + pb_adv_link.rx.seg = seg & ((1 << (START_LAST_SEG(rx->gpc) + 1)) - 1); + memcpy(pb_adv_link.rx.buf->data, buf->data, buf->len); + XACT_SEG_RECV(0); + + if (!pb_adv_link.rx.seg) + { + prov_msg_recv(); + } +} + +static void gen_prov_ctl(struct prov_rx *rx, struct net_buf_simple *buf) +{ + LOG_DBG("op 0x%02x len %u", BEARER_CTL(rx->gpc), buf->len); + + switch (BEARER_CTL(rx->gpc)) + { + case LINK_OPEN: + link_open(rx, buf); + break; + case LINK_ACK: + if (!atomic_test_bit(pb_adv_link.flags, ADV_LINK_ACTIVE)) + { + return; + } + + link_ack(rx, buf); + break; + case LINK_CLOSE: + if (!atomic_test_bit(pb_adv_link.flags, ADV_LINK_ACTIVE)) + { + return; + } + + link_close(rx, buf); + break; + default: + LOG_ERR("Unknown bearer opcode: 0x%02x", BEARER_CTL(rx->gpc)); + + if (IS_ENABLED(CONFIG_BT_TESTING)) + { + bt_mesh_test_prov_invalid_bearer(BEARER_CTL(rx->gpc)); + } + + return; + } +} + +static const struct +{ + void (*func)(struct prov_rx *rx, struct net_buf_simple *buf); + bool require_link; + uint8_t min_len; +} + +gen_prov[] = +{ + { + gen_prov_start, true, 3 + }, + + { + gen_prov_ack, true, 0 + }, + + { + gen_prov_cont, true, 0 + }, + + { + gen_prov_ctl, false, 0 + }, +}; + +static void gen_prov_recv(struct prov_rx *rx, struct net_buf_simple *buf) +{ + if (buf->len < gen_prov[GPCF(rx->gpc)].min_len) + { + LOG_ERR("Too short GPC message type %u", GPCF(rx->gpc)); + return; + } + + if (!atomic_test_bit(pb_adv_link.flags, ADV_LINK_ACTIVE) && + gen_prov[GPCF(rx->gpc)].require_link) + { + LOG_DBG("Ignoring message that requires active link"); + return; + } + + gen_prov[GPCF(rx->gpc)].func(rx, buf); +} + +/* TX + */ + +static void prov_retransmit(struct k_work *work) +{ + LOG_DBG(""); + + if (!atomic_test_bit(pb_adv_link.flags, ADV_LINK_ACTIVE)) + { + LOG_WRN("Link not active"); + return; + } + + if (TICK2MSEC(clock_systime_ticks()) - pb_adv_link.tx.start > + pb_adv_link.tx.timeout * MSEC_PER_SEC) + { + LOG_WRN("Giving up transaction"); + prov_link_close(PROV_BEARER_LINK_STATUS_TIMEOUT); + return; + } + + send_reliable(); +} + +static struct bt_mesh_adv *ctl_adv_create( + uint8_t op, const void *data, uint8_t data_len, uint8_t retransmits) +{ + struct bt_mesh_adv *adv; + + LOG_DBG("op 0x%02x data_len %u", op, data_len); + + adv = adv_create(retransmits); + if (!adv) + { + return NULL; + } + + net_buf_simple_add_be32(&adv->b, pb_adv_link.id); + + /* Transaction ID, always 0 for Bearer messages */ + + net_buf_simple_add_u8(&adv->b, 0x00); + net_buf_simple_add_u8(&adv->b, GPC_CTL(op)); + net_buf_simple_add_mem(&adv->b, data, data_len); + + return adv; +} + +static int bearer_ctl_send(struct bt_mesh_adv *adv) +{ + if (!adv) + { + return -ENOMEM; + } + + prov_clear_tx(); + k_work_reschedule(&pb_adv_link.prot_timer, + bt_mesh_prov_protocol_timeout_get()); + + pb_adv_link.tx.start = TICK2MSEC(clock_systime_ticks()); + pb_adv_link.tx.adv[0] = adv; + send_reliable(); + + return 0; +} + +static void buf_sent(int err, void *user_data) +{ + enum prov_bearer_link_status reason = + (enum prov_bearer_link_status)(intptr_t)user_data; + + atomic_clear_bit(pb_adv_link.flags, ADV_LINK_ACK_SENDING); + + if (atomic_test_and_clear_bit(pb_adv_link.flags, ADV_LINK_CLOSING)) + { + close_link(reason); + return; + } +} + +static int bearer_ctl_send_unacked(struct bt_mesh_adv *adv, void *user_data) +{ + int err; + + if (!adv) + { + return -ENOMEM; + } + + prov_clear_tx(); + k_work_reschedule(&pb_adv_link.prot_timer, + bt_mesh_prov_protocol_timeout_get()); + + err = send_unacked(adv, &buf_sent, user_data); + if (err) + { + bt_mesh_adv_unref(adv); + } + + return err; +} + +static int prov_send_adv(struct net_buf_simple *msg, + prov_bearer_send_complete_t cb, void *cb_data) +{ + struct bt_mesh_adv *start; + struct bt_mesh_adv *adv; + uint8_t seg_len; + uint8_t seg_id; + + prov_clear_tx(); + k_work_reschedule(&pb_adv_link.prot_timer, + bt_mesh_prov_protocol_timeout_get()); + + start = adv_create(RETRANSMITS_RELIABLE); + if (!start) + { + return -ENOBUFS; + } + + pb_adv_link.tx.id = next_transaction_id(pb_adv_link.tx.id); + net_buf_simple_add_be32(&start->b, pb_adv_link.id); + net_buf_simple_add_u8(&start->b, pb_adv_link.tx.id); + + net_buf_simple_add_u8(&start->b, GPC_START(last_seg(msg->len))); + net_buf_simple_add_be16(&start->b, msg->len); + net_buf_simple_add_u8(&start->b, bt_mesh_fcs_calc(msg->data, msg->len)); + + pb_adv_link.tx.adv[0] = start; + pb_adv_link.tx.cb = cb; + pb_adv_link.tx.cb_data = cb_data; + pb_adv_link.tx.start = TICK2MSEC(clock_systime_ticks()); + + LOG_DBG("xact_id: 0x%x len: %u", pb_adv_link.tx.id, msg->len); + + seg_len = MIN(msg->len, START_PAYLOAD_MAX); + LOG_DBG("seg 0 len %u: %s", seg_len, bt_hex(msg->data, seg_len)); + net_buf_simple_add_mem(&start->b, msg->data, seg_len); + net_buf_simple_pull(msg, seg_len); + + adv = start; + for (seg_id = 1U; msg->len > 0; seg_id++) + { + if (seg_id >= ARRAY_SIZE(pb_adv_link.tx.adv)) + { + LOG_ERR("Too big message"); + free_segments(); + return -E2BIG; + } + + adv = adv_create(RETRANSMITS_RELIABLE); + if (!adv) + { + free_segments(); + return -ENOBUFS; + } + + pb_adv_link.tx.adv[seg_id] = adv; + + seg_len = MIN(msg->len, CONT_PAYLOAD_MAX); + + LOG_DBG("seg %u len %u: %s", seg_id, seg_len, bt_hex(msg->data, + seg_len)); + + net_buf_simple_add_be32(&adv->b, pb_adv_link.id); + net_buf_simple_add_u8(&adv->b, pb_adv_link.tx.id); + net_buf_simple_add_u8(&adv->b, GPC_CONT(seg_id)); + net_buf_simple_add_mem(&adv->b, msg->data, seg_len); + net_buf_simple_pull(msg, seg_len); + } + + send_reliable(); + + return 0; +} + +/* Link management rx + */ + +static void link_open(struct prov_rx *rx, struct net_buf_simple *buf) +{ + int err; + + LOG_DBG("len %u", buf->len); + + if (buf->len < 16) + { + LOG_ERR("Too short bearer open message (len %u)", buf->len); + return; + } + + if (atomic_test_bit(pb_adv_link.flags, ADV_LINK_ACTIVE)) + { + /* Send another link ack if the provisioner missed the last */ + + if (pb_adv_link.id != rx->link_id) + { + LOG_DBG("Ignoring bearer open: link already active"); + return; + } + + if (atomic_test_bit(pb_adv_link.flags, ADV_LINK_ACK_SENDING)) + { + LOG_DBG("Still sending Link Ack"); + return; + } + + /* Ignore errors, message will be attempted again if we keep receiving + * link open: + */ + + atomic_set_bit(pb_adv_link.flags, ADV_LINK_ACK_SENDING); + (void)bearer_ctl_send_unacked(ctl_adv_create(LINK_ACK, NULL, 0, + RETRANSMITS_ACK), + (void *)PROV_BEARER_LINK_STATUS_SUCCESS); + return; + } + + if (memcmp(buf->data, bt_mesh_prov_get()->uuid, 16)) + { + LOG_DBG("Bearer open message not for us"); + return; + } + + pb_adv_link.id = rx->link_id; + atomic_set_bit(pb_adv_link.flags, ADV_LINK_ACTIVE); + net_buf_simple_reset(pb_adv_link.rx.buf); + + atomic_set_bit(pb_adv_link.flags, ADV_LINK_ACK_SENDING); + err = bearer_ctl_send_unacked(ctl_adv_create(LINK_ACK, NULL, 0, + RETRANSMITS_ACK), + (void *)PROV_BEARER_LINK_STATUS_SUCCESS); + if (err) + { + reset_adv_link(); + return; + } + + pb_adv_link.cb->link_opened(&bt_mesh_pb_adv, pb_adv_link.cb_data); +} + +static void link_ack(struct prov_rx *rx, struct net_buf_simple *buf) +{ + LOG_DBG("len %u", buf->len); + + if (atomic_test_bit(pb_adv_link.flags, ADV_PROVISIONER)) + { + if (atomic_test_and_set_bit(pb_adv_link.flags, ADV_LINK_ACK_RECVD)) + { + return; + } + + prov_clear_tx(); + + pb_adv_link.tx.timeout = TRANSACTION_TIMEOUT; + + pb_adv_link.cb->link_opened(&bt_mesh_pb_adv, pb_adv_link.cb_data); + } +} + +static void link_close(struct prov_rx *rx, struct net_buf_simple *buf) +{ + LOG_DBG("len %u", buf->len); + + if (buf->len != 1) + { + return; + } + + close_link(net_buf_simple_pull_u8(buf)); +} + +/* Higher level functionality + */ + +void bt_mesh_pb_adv_recv(struct net_buf_simple *buf) +{ + struct prov_rx rx; + + if (!pb_adv_link.cb) + { + return; + } + + if (buf->len < 6) + { + LOG_WRN("Too short provisioning packet (len %u)", buf->len); + return; + } + + rx.link_id = net_buf_simple_pull_be32(buf); + rx.xact_id = net_buf_simple_pull_u8(buf); + rx.gpc = net_buf_simple_pull_u8(buf); + + if (atomic_test_bit(pb_adv_link.flags, ADV_LINK_ACTIVE) && + pb_adv_link.id != rx.link_id) + { + return; + } + + LOG_DBG("link_id 0x%08x xact_id 0x%x", rx.link_id, rx.xact_id); + + gen_prov_recv(&rx, buf); +} + +static int prov_link_open( + const uint8_t uuid[16], uint8_t timeout, const struct prov_bearer_cb *cb, + void *cb_data) +{ + int err; + + LOG_DBG("uuid %s", bt_hex(uuid, 16)); + + err = bt_mesh_adv_enable(); + if (err) + { + LOG_ERR("Failed enabling advertiser"); + return err; + } + + if (atomic_test_and_set_bit(pb_adv_link.flags, ADV_LINK_ACTIVE)) + { + return -EBUSY; + } + + atomic_set_bit(pb_adv_link.flags, ADV_PROVISIONER); + + bt_rand(&pb_adv_link.id, sizeof(pb_adv_link.id)); + pb_adv_link.tx.id = XACT_ID_MAX; + pb_adv_link.rx.id = XACT_ID_NVAL; + pb_adv_link.cb = cb; + pb_adv_link.cb_data = cb_data; + + /* The link open time is configurable, but this will be changed to + * TRANSACTION_TIMEOUT once the link is established. + */ + + pb_adv_link.tx.timeout = timeout; + + net_buf_simple_reset(pb_adv_link.rx.buf); + + return bearer_ctl_send(ctl_adv_create(LINK_OPEN, uuid, 16, + RETRANSMITS_RELIABLE)); +} + +static int prov_link_accept(const struct prov_bearer_cb *cb, void *cb_data) +{ + int err; + + err = bt_mesh_adv_enable(); + if (err) + { + LOG_ERR("Failed enabling advertiser"); + return err; + } + + if (atomic_test_bit(pb_adv_link.flags, ADV_LINK_ACTIVE)) + { + return -EBUSY; + } + + pb_adv_link.rx.id = XACT_ID_MAX; + pb_adv_link.tx.id = XACT_ID_NVAL; + pb_adv_link.cb = cb; + pb_adv_link.cb_data = cb_data; + pb_adv_link.tx.timeout = TRANSACTION_TIMEOUT; + + /* Make sure we're scanning for provisioning invitations */ + + bt_mesh_scan_enable(); + + /* Enable unprovisioned beacon sending */ + + bt_mesh_beacon_enable(); + + return 0; +} + +static void prov_link_cancel(void) +{ + bt_mesh_beacon_disable(); + bt_mesh_scan_disable(); +} + +static void prov_link_close(enum prov_bearer_link_status status) +{ + int err; + + if (atomic_test_and_set_bit(pb_adv_link.flags, ADV_LINK_CLOSING)) + { + return; + } + + /* According to MshPRTv1.1: 5.3.1.4.3, the close message should + * be restransmitted at least three times. Retransmit the LINK_CLOSE + * message until CLOSING_TIMEOUT has elapsed. + */ + + pb_adv_link.tx.timeout = CLOSING_TIMEOUT; + + /* Ignore errors, the link will time out eventually if this doesn't get + * sent + */ + + err = bearer_ctl_send_unacked(ctl_adv_create(LINK_CLOSE, &status, 1, + RETRANSMITS_LINK_CLOSE), + (void *)status); + if (err) + { + close_link(status); + } +} + +void bt_mesh_pb_adv_init(void) +{ + k_work_init_delayable(&pb_adv_link.prot_timer, protocol_timeout); + k_work_init_delayable(&pb_adv_link.tx.retransmit, prov_retransmit); +} + +void bt_mesh_pb_adv_reset(void) +{ + reset_adv_link(); +} + +const struct prov_bearer bt_mesh_pb_adv = +{ + .type = BT_MESH_PROV_ADV, + .link_open = prov_link_open, + .link_accept = prov_link_accept, + .link_cancel = prov_link_cancel, + .link_close = prov_link_close, + .send = prov_send_adv, + .clear_tx = prov_clear_tx, +}; diff --git a/libs/zblue/mesh/pb_gatt.c b/libs/zblue/mesh/pb_gatt.c new file mode 100644 index 0000000000000..00cf4f85455b8 --- /dev/null +++ b/libs/zblue/mesh/pb_gatt.c @@ -0,0 +1,292 @@ +/**************************************************************************** + * libs/zblue/mesh/pb_gatt.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include "net.h" +#include "proxy.h" +#include "prov.h" +#include "pb_gatt.h" +#include "proxy_msg.h" +#include "pb_gatt_srv.h" +#include "pb_gatt_cli.h" + +#include + +#include "common/bt_str.h" + +#define LOG_LEVEL CONFIG_BT_MESH_PROV_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_pb_gatt); + +#define link pb_gatt_link + +struct prov_bearer_send_cb +{ + prov_bearer_send_complete_t cb; + void *cb_data; +}; + +struct prov_link +{ + struct bt_conn *conn; + const struct prov_bearer_cb *cb; + void *cb_data; + struct prov_bearer_send_cb comp; + struct k_work_delayable prot_timer; +}; + +static struct prov_link link; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void reset_state(void) +{ + bt_conn_drop(&link.conn); + + /* If this fails, the protocol timeout handler will exit early. */ + + (void)k_work_cancel_delayable(&link.prot_timer); +} + +static void link_closed(enum prov_bearer_link_status status) +{ + const struct prov_bearer_cb *cb = link.cb; + void *cb_data = link.cb_data; + + reset_state(); + + cb->link_closed(&bt_mesh_pb_gatt, cb_data, status); +} + +static void protocol_timeout(struct k_work *work) +{ + if (!atomic_test_bit(bt_mesh_prov_link.flags, LINK_ACTIVE)) + { + return; + } + + /* If connection failed or timeout, not allow establish connection */ + + if (IS_ENABLED(CONFIG_BT_MESH_PB_GATT_CLIENT) && + atomic_test_bit(bt_mesh_prov_link.flags, PROVISIONER)) + { + if (link.conn) + { + (void)bt_conn_disconnect(link.conn, + BT_HCI_ERR_REMOTE_USER_TERM_CONN); + } + else + { + (void)bt_mesh_pb_gatt_cli_setup(NULL); + } + } + + LOG_DBG("Protocol timeout"); + + link_closed(PROV_BEARER_LINK_STATUS_TIMEOUT); +} + +int bt_mesh_pb_gatt_recv(struct bt_conn *conn, struct net_buf_simple *buf) +{ + LOG_DBG("%u bytes: %s", buf->len, bt_hex(buf->data, buf->len)); + + if (link.conn != conn || !link.cb) + { + LOG_WRN("Data for unexpected connection"); + return -ENOTCONN; + } + + if (buf->len < 1) + { + LOG_WRN("Too short provisioning packet (len %u)", buf->len); + return -EINVAL; + } + + k_work_reschedule(&link.prot_timer, bt_mesh_prov_protocol_timeout_get()); + + link.cb->recv(&bt_mesh_pb_gatt, link.cb_data, buf); + + return 0; +} + +int bt_mesh_pb_gatt_start(struct bt_conn *conn) +{ + LOG_DBG("conn %p", (void *)conn); + + if (link.conn) + { + return -EBUSY; + } + + link.conn = bt_conn_ref(conn); + k_work_reschedule(&link.prot_timer, bt_mesh_prov_protocol_timeout_get()); + + link.cb->link_opened(&bt_mesh_pb_gatt, link.cb_data); + + return 0; +} + +int bt_mesh_pb_gatt_close(struct bt_conn *conn) +{ + LOG_DBG("conn %p", (void *)conn); + + if (link.conn != conn) + { + LOG_DBG("Not connected"); + return -ENOTCONN; + } + + link_closed(PROV_BEARER_LINK_STATUS_SUCCESS); + + return 0; +} + +#if defined(CONFIG_BT_MESH_PB_GATT_CLIENT) +int bt_mesh_pb_gatt_cli_start(struct bt_conn *conn) +{ + LOG_DBG("conn %p", (void *)conn); + + if (link.conn) + { + return -EBUSY; + } + + link.conn = bt_conn_ref(conn); + k_work_reschedule(&link.prot_timer, bt_mesh_prov_protocol_timeout_get()); + + return 0; +} + +int bt_mesh_pb_gatt_cli_open(struct bt_conn *conn) +{ + LOG_DBG("conn %p", (void *)conn); + + if (link.conn != conn) + { + LOG_DBG("Not connected"); + return -ENOTCONN; + } + + link.cb->link_opened(&bt_mesh_pb_gatt, link.cb_data); + + return 0; +} + +static int prov_link_open( + const uint8_t uuid[16], uint8_t timeout, const struct prov_bearer_cb *cb, + void *cb_data) +{ + LOG_DBG("uuid %s", bt_hex(uuid, 16)); + + link.cb = cb; + link.cb_data = cb_data; + + k_work_reschedule(&link.prot_timer, SEC2TICK(timeout)); + + return bt_mesh_pb_gatt_cli_setup(uuid); +} + +static void prov_link_close(enum prov_bearer_link_status status) +{ + (void)bt_conn_disconnect(link.conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); +} +#endif + +#if defined(CONFIG_BT_MESH_PB_GATT) +static int link_accept(const struct prov_bearer_cb *cb, void *cb_data) +{ + int err; + + err = bt_mesh_adv_enable(); + if (err) + { + LOG_ERR("Failed enabling advertiser"); + return err; + } + + (void)bt_mesh_pb_gatt_srv_enable(); + bt_mesh_adv_gatt_update(); + + link.cb = cb; + link.cb_data = cb_data; + + return 0; +} + +static void link_cancel(void) +{ + (void)bt_mesh_pb_gatt_srv_disable(); +} +#endif + +static void buf_send_end(struct bt_conn *conn, void *user_data) +{ + if (link.comp.cb) + { + link.comp.cb(0, link.comp.cb_data); + } +} + +static int buf_send(struct net_buf_simple *buf, + prov_bearer_send_complete_t cb, void *cb_data) +{ + if (!link.conn) + { + return -ENOTCONN; + } + + link.comp.cb = cb; + link.comp.cb_data = cb_data; + + k_work_reschedule(&link.prot_timer, bt_mesh_prov_protocol_timeout_get()); + + return bt_mesh_proxy_msg_send(link.conn, BT_MESH_PROXY_PROV, buf, + buf_send_end, NULL); +} + +static void clear_tx(void) +{ + /* No action */ +} + +void bt_mesh_pb_gatt_init(void) +{ + k_work_init_delayable(&link.prot_timer, protocol_timeout); +} + +void bt_mesh_pb_gatt_reset(void) +{ + reset_state(); +} + +const struct prov_bearer bt_mesh_pb_gatt = +{ + .type = BT_MESH_PROV_GATT, +#if defined(CONFIG_BT_MESH_PB_GATT_CLIENT) + .link_open = prov_link_open, + .link_close = prov_link_close, +#endif +#if defined(CONFIG_BT_MESH_PB_GATT) + .link_accept = link_accept, + .link_cancel = link_cancel, +#endif + .send = buf_send, + .clear_tx = clear_tx, +}; diff --git a/libs/zblue/mesh/pb_gatt.h b/libs/zblue/mesh/pb_gatt.h new file mode 100644 index 0000000000000..4307c8851b92f --- /dev/null +++ b/libs/zblue/mesh/pb_gatt.h @@ -0,0 +1,19 @@ +/**************************************************************************** + * libs/zblue/mesh/pb_gatt.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation Copyright (c) 2021 Nordic + * Semiconductor ASA Copyright (c) 2022 Xiaomi Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +int bt_mesh_pb_gatt_start(struct bt_conn *conn); +int bt_mesh_pb_gatt_close(struct bt_conn *conn); +int bt_mesh_pb_gatt_recv(struct bt_conn *conn, struct net_buf_simple *buf); + +int bt_mesh_pb_gatt_cli_open(struct bt_conn *conn); +int bt_mesh_pb_gatt_cli_start(struct bt_conn *conn); diff --git a/libs/zblue/mesh/pb_gatt_cli.c b/libs/zblue/mesh/pb_gatt_cli.c new file mode 100644 index 0000000000000..17f379be6fcd4 --- /dev/null +++ b/libs/zblue/mesh/pb_gatt_cli.c @@ -0,0 +1,150 @@ +/**************************************************************************** + * libs/zblue/mesh/pb_gatt_cli.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2021 Xiaomi Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "mesh.h" +#include "net.h" +#include "rpl.h" +#include "transport.h" +#include "prov.h" +#include "pb_gatt.h" +#include "beacon.h" +#include "foundation.h" +#include "access.h" +#include "proxy.h" +#include "gatt_cli.h" +#include "proxy_msg.h" + +#define LOG_LEVEL CONFIG_BT_MESH_PROV_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_pb_gatt_client); + +static struct +{ + bool target_set; + uint8_t target_uuid[16]; + struct bt_mesh_proxy_role *srv; +} server; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void pb_gatt_msg_recv(struct bt_mesh_proxy_role *role) +{ + switch (role->msg_type) + { + case BT_MESH_PROXY_PROV: + LOG_DBG("Mesh Provisioning PDU"); + bt_mesh_pb_gatt_recv(role->conn, &role->buf); + break; + default: + LOG_WRN("Unhandled Message Type 0x%02x", role->msg_type); + break; + } +} + +static void pb_gatt_connected(struct bt_conn *conn, void *user_data) +{ + server.srv = bt_mesh_proxy_role_setup(conn, bt_mesh_gatt_send, + pb_gatt_msg_recv); + + server.target_set = false; + + bt_mesh_pb_gatt_cli_start(conn); +} + +static void pb_gatt_link_open(struct bt_conn *conn) +{ + bt_mesh_pb_gatt_cli_open(conn); +} + +static void pb_gatt_disconnected(struct bt_conn *conn) +{ + bt_mesh_pb_gatt_close(conn); + + bt_mesh_proxy_role_cleanup(server.srv); + + server.srv = NULL; +} + +static const struct bt_mesh_gatt_cli pbgatt = +{ + .srv_uuid = BT_UUID_INIT_16(BT_UUID_MESH_PROV_VAL), + .data_in_uuid = BT_UUID_INIT_16(BT_UUID_MESH_PROV_DATA_IN_VAL), + .data_out_uuid = BT_UUID_INIT_16(BT_UUID_MESH_PROV_DATA_OUT_VAL), + .data_out_cccd_uuid = BT_UUID_INIT_16(BT_UUID_GATT_CCC_VAL), + + .connected = pb_gatt_connected, + .link_open = pb_gatt_link_open, + .disconnected = pb_gatt_disconnected +}; + +int bt_mesh_pb_gatt_cli_setup(const uint8_t uuid[16]) +{ + if (server.srv) + { + return -EBUSY; + } + + memcpy(server.target_uuid, uuid, 16); + server.target_set = true; + return 0; +} + +void bt_mesh_pb_gatt_cli_adv_recv(const struct bt_le_scan_recv_info *info, + struct net_buf_simple *buf) +{ + uint8_t *uuid; + bt_mesh_prov_oob_info_t oob_info; + + if (server.srv) + { + return; + } + + if (buf->len != 18) + { + return; + } + + uuid = net_buf_simple_pull_mem(buf, 16); + + if (server.target_set && !memcmp(server.target_uuid, uuid, 16)) + { + (void)bt_mesh_gatt_cli_connect(info->addr, &pbgatt, NULL); + return; + } + + if (!bt_mesh_prov->unprovisioned_beacon_gatt) + { + return; + } + + oob_info = (bt_mesh_prov_oob_info_t)net_buf_simple_pull_le16(buf); + + bt_mesh_prov->unprovisioned_beacon_gatt(uuid, oob_info); +} diff --git a/libs/zblue/mesh/pb_gatt_cli.h b/libs/zblue/mesh/pb_gatt_cli.h new file mode 100644 index 0000000000000..b7853dd08c3b5 --- /dev/null +++ b/libs/zblue/mesh/pb_gatt_cli.h @@ -0,0 +1,16 @@ +/**************************************************************************** + * libs/zblue/mesh/pb_gatt_cli.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2021 Xiaomi Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +int bt_mesh_pb_gatt_cli_setup(const uint8_t uuid[16]); + +void bt_mesh_pb_gatt_cli_adv_recv(const struct bt_le_scan_recv_info *info, + struct net_buf_simple *buf); diff --git a/libs/zblue/mesh/pb_gatt_srv.c b/libs/zblue/mesh/pb_gatt_srv.c new file mode 100644 index 0000000000000..dc7bad50b9dfe --- /dev/null +++ b/libs/zblue/mesh/pb_gatt_srv.c @@ -0,0 +1,390 @@ +/**************************************************************************** + * libs/zblue/mesh/pb_gatt_srv.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * Copyright (c) 2021 Lingao Meng + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include + +#include "common/bt_str.h" + +#include "mesh.h" +#include "net.h" +#include "rpl.h" +#include "transport.h" +#include "prov.h" +#include "pb_gatt.h" +#include "beacon.h" +#include "foundation.h" +#include "access.h" +#include "proxy.h" +#include "gatt.h" +#include "proxy_msg.h" +#include "pb_gatt_srv.h" + +#define LOG_LEVEL CONFIG_BT_MESH_PROV_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_pb_gatt_srv); + +#define ADV_OPT_PROV (BT_LE_ADV_OPT_CONN | BT_LE_ADV_OPT_SCANNABLE | ADV_OPT_USE_IDENTITY) + +#define FAST_ADV_TIME (60LL * MSEC_PER_SEC) + +static int64_t fast_adv_timestamp; + +static int gatt_send(struct bt_conn *conn, const void *data, uint16_t len, + bt_gatt_complete_func_t end, void *user_data); + +static struct bt_mesh_proxy_role *cli; +static bool service_registered; +static bool adv_restart_pending; + +static int prov_gatt_service_unregister(void); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void proxy_msg_recv(struct bt_mesh_proxy_role *role) +{ + switch (role->msg_type) + { + case BT_MESH_PROXY_PROV: + LOG_DBG("Mesh Provisioning PDU"); + bt_mesh_pb_gatt_recv(role->conn, &role->buf); + break; + + default: + LOG_WRN("Unhandled Message Type 0x%02x", role->msg_type); + break; + } +} + +static ssize_t gatt_recv( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + const uint8_t *data = buf; + + if (cli->conn != conn) + { + LOG_ERR("No PB-GATT Client found"); + return -ENOTCONN; + } + + if (len < 1) + { + LOG_WRN("Too small Proxy PDU"); + return -EINVAL; + } + + if (PDU_TYPE(data) != BT_MESH_PROXY_PROV) + { + LOG_WRN("Proxy PDU type doesn't match GATT service"); + return -EINVAL; + } + + return bt_mesh_proxy_msg_recv(conn, buf, len); +} + +static void gatt_connected(struct bt_conn *conn, uint8_t conn_err) +{ + struct bt_conn_info info; + int err; + + err = bt_conn_get_info(conn, &info); + if (err || info.role != BT_CONN_ROLE_PERIPHERAL || !service_registered || + bt_mesh_is_provisioned() || info.id != BT_ID_DEFAULT || cli) + { + return; + } + + cli = bt_mesh_proxy_role_setup(conn, gatt_send, proxy_msg_recv); + + LOG_DBG("conn %p err 0x%02x", (void *)conn, conn_err); +} + +static void gatt_disconnected(struct bt_conn *conn, uint8_t reason) +{ + struct bt_conn_info info; + int err; + + err = bt_conn_get_info(conn, &info); + if (err || info.role != BT_CONN_ROLE_PERIPHERAL || !service_registered || + info.id != BT_ID_DEFAULT || !cli || cli->conn != conn) + { + return; + } + + bt_mesh_proxy_role_cleanup(cli); + cli = NULL; + + bt_mesh_pb_gatt_close(conn); + + if (bt_mesh_is_provisioned()) + { + (void)prov_gatt_service_unregister(); + } + + adv_restart_pending = true; +} + +static void prov_ccc_changed(const struct bt_gatt_attr *attr, uint16_t value) +{ + LOG_DBG("value 0x%04x", value); +} + +static ssize_t prov_ccc_write(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + uint16_t value) +{ + if (cli->conn != conn) + { + LOG_ERR("No PB-GATT Client found"); + return -ENOTCONN; + } + + LOG_DBG("value 0x%04x", value); + + if (value != BT_GATT_CCC_NOTIFY) + { + LOG_WRN("Client wrote 0x%04x instead enabling notify", value); + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + + bt_mesh_pb_gatt_start(conn); + + return sizeof(value); +} + +/* Mesh Provisioning Service Declaration */ + +static struct bt_gatt_ccc_managed_user_data prov_ccc = + BT_GATT_CCC_MANAGED_USER_DATA_INIT(prov_ccc_changed, + prov_ccc_write, + NULL); + +static struct bt_gatt_attr prov_attrs[] = +{ + BT_GATT_PRIMARY_SERVICE(BT_UUID_MESH_PROV), + + BT_GATT_CHARACTERISTIC(BT_UUID_MESH_PROV_DATA_IN, + BT_GATT_CHRC_WRITE_WITHOUT_RESP, + + BT_GATT_PERM_WRITE, NULL, gatt_recv, NULL), + + BT_GATT_CHARACTERISTIC(BT_UUID_MESH_PROV_DATA_OUT, + BT_GATT_CHRC_NOTIFY, + BT_GATT_PERM_NONE, + + NULL, NULL, NULL), + BT_GATT_CCC_MANAGED(&prov_ccc, + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), +}; + +static struct bt_gatt_service prov_svc = BT_GATT_SERVICE(prov_attrs); + +static int prov_gatt_service_unregister(void) +{ + if (!service_registered) + { + return -EALREADY; + } + + (void)bt_gatt_service_unregister(&prov_svc); + service_registered = false; + + return 0; +} + +int bt_mesh_pb_gatt_srv_enable(void) +{ + LOG_DBG(""); + + if (bt_mesh_is_provisioned()) + { + return -ENOTSUP; + } + + if (service_registered) + { + return -EBUSY; + } + + (void)bt_gatt_service_register(&prov_svc); + service_registered = true; + fast_adv_timestamp = TICK2MSEC(clock_systime_ticks()); + + return 0; +} + +int bt_mesh_pb_gatt_srv_disable(void) +{ + int err; + + LOG_DBG(""); + + err = prov_gatt_service_unregister(); + if (err) + { + return err; + } + + bt_mesh_adv_gatt_update(); + + return 0; +} + +static uint8_t prov_svc_data[20] = +{ + BT_UUID_16_ENCODE(BT_UUID_MESH_PROV_VAL), +}; + +static const struct bt_data prov_ad[] = +{ + BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), + BT_DATA_BYTES(BT_DATA_UUID16_ALL, + BT_UUID_16_ENCODE(BT_UUID_MESH_PROV_VAL)), + BT_DATA(BT_DATA_SVC_DATA16, prov_svc_data, sizeof(prov_svc_data)), +}; + +static size_t gatt_prov_adv_create(struct bt_data prov_sd[2]) +{ + size_t prov_sd_len = 0; + + const struct bt_mesh_prov *prov = bt_mesh_prov_get(); + size_t uri_len; + + memcpy(prov_svc_data + 2, prov->uuid, 16); + sys_put_be16(prov->oob_info, prov_svc_data + 18); + + if (!prov->uri) + { + goto dev_name; + } + + uri_len = strlen(prov->uri); + if (uri_len > 29) + { + /* There's no way to shorten an URI */ + + LOG_WRN("Too long URI to fit advertising packet"); + goto dev_name; + } + + prov_sd[prov_sd_len].type = BT_DATA_URI; + prov_sd[prov_sd_len].data_len = uri_len; + prov_sd[prov_sd_len].data = (const uint8_t *)prov->uri; + + prov_sd_len += 1; + +dev_name: + if (IS_ENABLED(CONFIG_BT_MESH_PB_GATT_USE_DEVICE_NAME)) + { + prov_sd[prov_sd_len].type = BT_DATA_NAME_COMPLETE; + prov_sd[prov_sd_len].data_len = BT_DEVICE_NAME_LEN; + prov_sd[prov_sd_len].data = BT_DEVICE_NAME; + prov_sd_len += 1; + } + + return prov_sd_len; +} + +static int gatt_send(struct bt_conn *conn, const void *data, uint16_t len, + bt_gatt_complete_func_t end, void *user_data) +{ + LOG_DBG("%u bytes: %s", len, bt_hex(data, len)); + + struct bt_gatt_notify_params params = + { + .data = data, + .len = len, + .attr = &prov_attrs[3], + .user_data = user_data, + .func = end, + }; + + return bt_gatt_notify_cb(conn, ¶ms); +} + +int bt_mesh_pb_gatt_srv_adv_start(void) +{ + LOG_DBG(""); + + if (!service_registered || bt_mesh_is_provisioned() || + !bt_mesh_proxy_has_avail_conn()) + { + return -ENOTSUP; + } + + struct bt_le_adv_param fast_adv_param = + { + .id = BT_ID_DEFAULT, + .options = ADV_OPT_PROV, + ADV_FAST_INT, + }; + + struct bt_data prov_sd[2]; + size_t prov_sd_len; + int64_t elapsed_time = + TICK2MSEC(clock_systime_ticks()) - fast_adv_timestamp; + + prov_sd_len = gatt_prov_adv_create(prov_sd); + + if (elapsed_time > FAST_ADV_TIME) + { + struct bt_le_adv_param slow_adv_param = + { + .id = BT_ID_DEFAULT, + .options = ADV_OPT_PROV, + ADV_SLOW_INT, + }; + + return bt_mesh_adv_gatt_start(&slow_adv_param, -1, prov_ad, + ARRAY_SIZE(prov_ad), prov_sd, + prov_sd_len); + } + + LOG_DBG("remaining fast adv time (%lld ms)", + (FAST_ADV_TIME - elapsed_time)); + + /* Advertise 60 seconds using fast interval */ + + return bt_mesh_adv_gatt_start(&fast_adv_param, + (FAST_ADV_TIME - elapsed_time), prov_ad, + ARRAY_SIZE(prov_ad), prov_sd, prov_sd_len); +} + +static void conn_recycled(void) +{ + if (adv_restart_pending) + { + adv_restart_pending = false; + bt_mesh_adv_gatt_update(); + } +} + +BT_CONN_CB_DEFINE(conn_callbacks) = +{ + .connected = gatt_connected, + .disconnected = gatt_disconnected, + .recycled = conn_recycled, +}; diff --git a/libs/zblue/mesh/pb_gatt_srv.h b/libs/zblue/mesh/pb_gatt_srv.h new file mode 100644 index 0000000000000..ffd3e831e701f --- /dev/null +++ b/libs/zblue/mesh/pb_gatt_srv.h @@ -0,0 +1,27 @@ +/**************************************************************************** + * libs/zblue/mesh/pb_gatt_srv.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation Copyright (c) 2021 Lingao Meng + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_SUBSYS_BLUETOOTH_MESH_PB_GATT_SRV_H_ +#define ZEPHYR_SUBSYS_BLUETOOTH_MESH_PB_GATT_SRV_H_ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +int bt_mesh_pb_gatt_srv_enable(void); +int bt_mesh_pb_gatt_srv_disable(void); + +int bt_mesh_pb_gatt_srv_adv_start(void); + +#endif /* ZEPHYR_SUBSYS_BLUETOOTH_MESH_PB_GATT_SRV_H_ */ diff --git a/libs/zblue/mesh/priv_beacon.h b/libs/zblue/mesh/priv_beacon.h new file mode 100644 index 0000000000000..52d7d88baa80b --- /dev/null +++ b/libs/zblue/mesh/priv_beacon.h @@ -0,0 +1,13 @@ +/**************************************************************************** + * libs/zblue/mesh/priv_beacon.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +void bt_mesh_priv_beacon_srv_store_schedule(void); diff --git a/libs/zblue/mesh/priv_beacon_cli.c b/libs/zblue/mesh/priv_beacon_cli.c new file mode 100644 index 0000000000000..4c93ada74ccb6 --- /dev/null +++ b/libs/zblue/mesh/priv_beacon_cli.c @@ -0,0 +1,353 @@ +/**************************************************************************** + * libs/zblue/mesh/priv_beacon_cli.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include "net.h" +#include "foundation.h" +#include "access.h" +#include "msg.h" + +#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_priv_beacon_cli); + +static struct bt_mesh_priv_beacon_cli *cli; + +static int32_t msg_timeout; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int handle_beacon_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_priv_beacon *rsp; + uint8_t beacon; + uint8_t rand_int; + + beacon = net_buf_simple_pull_u8(buf); + rand_int = net_buf_simple_pull_u8(buf); + + if (beacon != BT_MESH_BEACON_DISABLED && beacon != BT_MESH_BEACON_ENABLED) + { + LOG_WRN("Invalid beacon value 0x%02x", beacon); + return -EINVAL; + } + + LOG_DBG("0x%02x (%u s)", beacon, 10U * rand_int); + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_PRIV_BEACON_STATUS, + ctx->addr, (void **)&rsp)) + { + rsp->enabled = beacon; + rsp->rand_interval = rand_int; + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + + if (cli->cb && cli->cb->priv_beacon_status) + { + struct bt_mesh_priv_beacon state = + { + .enabled = beacon, + .rand_interval = rand_int, + }; + + cli->cb->priv_beacon_status(cli, ctx->addr, &state); + } + + return 0; +} + +static int handle_gatt_proxy_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint8_t *rsp; + uint8_t proxy; + + proxy = net_buf_simple_pull_u8(buf); + + if (proxy != BT_MESH_GATT_PROXY_DISABLED && + proxy != BT_MESH_GATT_PROXY_ENABLED && + proxy != BT_MESH_GATT_PROXY_NOT_SUPPORTED) + { + LOG_WRN("Invalid GATT proxy value 0x%02x", proxy); + return -EINVAL; + } + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_PRIV_GATT_PROXY_STATUS, + ctx->addr, (void **)&rsp)) + { + *rsp = proxy; + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + + if (cli->cb && cli->cb->priv_gatt_proxy_status) + { + cli->cb->priv_gatt_proxy_status(cli, ctx->addr, proxy); + } + + return 0; +} + +static int handle_node_id_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_priv_node_id *rsp; + uint8_t status; + uint8_t node_id; + uint16_t net_idx; + + status = net_buf_simple_pull_u8(buf); + net_idx = net_buf_simple_pull_le16(buf); + node_id = net_buf_simple_pull_u8(buf); + + if (node_id != BT_MESH_NODE_IDENTITY_STOPPED && + node_id != BT_MESH_NODE_IDENTITY_RUNNING && + node_id != BT_MESH_NODE_IDENTITY_NOT_SUPPORTED) + { + LOG_WRN("Invalid node ID value 0x%02x", node_id); + return -EINVAL; + } + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_PRIV_NODE_ID_STATUS, + ctx->addr, (void **)&rsp)) + { + rsp->net_idx = net_idx; + rsp->status = status; + rsp->state = node_id; + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + + if (cli->cb && cli->cb->priv_node_id_status) + { + struct bt_mesh_priv_node_id state = + { + .net_idx = net_idx, + .status = status, + .state = node_id, + }; + + cli->cb->priv_node_id_status(cli, ctx->addr, &state); + } + + return 0; +} + +const struct bt_mesh_model_op bt_mesh_priv_beacon_cli_op[] = +{ + { + OP_PRIV_BEACON_STATUS, + BT_MESH_LEN_EXACT(2), + handle_beacon_status + }, + + { + OP_PRIV_GATT_PROXY_STATUS, + BT_MESH_LEN_EXACT(1), + handle_gatt_proxy_status + }, + + { + OP_PRIV_NODE_ID_STATUS, + BT_MESH_LEN_EXACT(4), + handle_node_id_status + }, + + BT_MESH_MODEL_OP_END, +}; + +static int priv_beacon_cli_init(const struct bt_mesh_model *model) +{ + if (!bt_mesh_model_in_primary(model)) + { + LOG_ERR("Private Beacon Client only allowed in primary element"); + return -EINVAL; + } + + cli = model->rt->user_data; + cli->model = model; + msg_timeout = 2 * MSEC_PER_SEC; + model->keys[0] = BT_MESH_KEY_DEV_ANY; + model->rt->flags |= BT_MESH_MOD_DEVKEY_ONLY; + + bt_mesh_msg_ack_ctx_init(&cli->ack_ctx); + + return 0; +} + +const struct bt_mesh_model_cb bt_mesh_priv_beacon_cli_cb = +{ + .init = priv_beacon_cli_init, +}; + +int bt_mesh_priv_beacon_cli_set( + uint16_t net_idx, uint16_t addr, struct bt_mesh_priv_beacon *val, + struct bt_mesh_priv_beacon *rsp) +{ + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + const struct bt_mesh_msg_rsp_ctx rsp_ctx = + { + .ack = &cli->ack_ctx, + .op = OP_PRIV_BEACON_STATUS, + .user_data = rsp, + .timeout = msg_timeout, + }; + + BT_MESH_MODEL_BUF_DEFINE(buf, OP_PRIV_BEACON_SET, 2); + bt_mesh_model_msg_init(&buf, OP_PRIV_BEACON_SET); + + net_buf_simple_add_u8(&buf, val->enabled); + if (val->rand_interval) + { + net_buf_simple_add_u8(&buf, val->rand_interval); + } + + return bt_mesh_msg_ackd_send(cli->model, + &ctx, + &buf, + rsp ? &rsp_ctx : NULL); +} + +int bt_mesh_priv_beacon_cli_get(uint16_t net_idx, + uint16_t addr, + struct bt_mesh_priv_beacon *val) +{ + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + const struct bt_mesh_msg_rsp_ctx rsp_ctx = + { + .ack = &cli->ack_ctx, + .op = OP_PRIV_BEACON_STATUS, + .user_data = val, + .timeout = msg_timeout, + }; + + BT_MESH_MODEL_BUF_DEFINE(buf, OP_PRIV_BEACON_GET, 0); + bt_mesh_model_msg_init(&buf, OP_PRIV_BEACON_GET); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &buf, val ? &rsp_ctx : + NULL); +} + +int bt_mesh_priv_beacon_cli_gatt_proxy_set( + uint16_t net_idx, uint16_t addr, uint8_t val, uint8_t *rsp) +{ + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + const struct bt_mesh_msg_rsp_ctx rsp_ctx = + { + .ack = &cli->ack_ctx, + .op = OP_PRIV_GATT_PROXY_STATUS, + .user_data = rsp, + .timeout = msg_timeout, + }; + + if ((val != BT_MESH_GATT_PROXY_DISABLED && + val != BT_MESH_GATT_PROXY_ENABLED)) + { + return -EINVAL; + } + + BT_MESH_MODEL_BUF_DEFINE(buf, OP_PRIV_GATT_PROXY_SET, 1); + bt_mesh_model_msg_init(&buf, OP_PRIV_GATT_PROXY_SET); + + net_buf_simple_add_u8(&buf, val); + + return bt_mesh_msg_ackd_send(cli->model, + &ctx, + &buf, + rsp ? &rsp_ctx : NULL); +} + +int bt_mesh_priv_beacon_cli_gatt_proxy_get(uint16_t net_idx, + uint16_t addr, uint8_t *val) +{ + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + const struct bt_mesh_msg_rsp_ctx rsp_ctx = + { + .ack = &cli->ack_ctx, + .op = OP_PRIV_GATT_PROXY_STATUS, + .user_data = val, + .timeout = msg_timeout, + }; + + BT_MESH_MODEL_BUF_DEFINE(buf, OP_PRIV_GATT_PROXY_GET, 0); + bt_mesh_model_msg_init(&buf, OP_PRIV_GATT_PROXY_GET); + + return bt_mesh_msg_ackd_send(cli->model, &ctx, &buf, val ? &rsp_ctx : + NULL); +} + +int bt_mesh_priv_beacon_cli_node_id_set(uint16_t net_idx, uint16_t addr, + struct bt_mesh_priv_node_id *val, + struct bt_mesh_priv_node_id *rsp) +{ + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + const struct bt_mesh_msg_rsp_ctx rsp_ctx = + { + .ack = &cli->ack_ctx, + .op = OP_PRIV_NODE_ID_STATUS, + .user_data = rsp, + .timeout = msg_timeout, + }; + + if (val->net_idx > 0xfff || + (val->state != BT_MESH_NODE_IDENTITY_STOPPED && + val->state != BT_MESH_NODE_IDENTITY_RUNNING)) + { + return -EINVAL; + } + + BT_MESH_MODEL_BUF_DEFINE(buf, OP_PRIV_NODE_ID_SET, 3); + bt_mesh_model_msg_init(&buf, OP_PRIV_NODE_ID_SET); + + net_buf_simple_add_le16(&buf, val->net_idx); + net_buf_simple_add_u8(&buf, val->state); + + return bt_mesh_msg_ackd_send(cli->model, + &ctx, + &buf, + rsp ? &rsp_ctx : NULL); +} + +int bt_mesh_priv_beacon_cli_node_id_get( + uint16_t net_idx, uint16_t addr, uint16_t key_net_idx, + struct bt_mesh_priv_node_id *val) +{ + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + const struct bt_mesh_msg_rsp_ctx rsp_ctx = + { + .ack = &cli->ack_ctx, + .op = OP_PRIV_NODE_ID_STATUS, + .user_data = val, + .timeout = msg_timeout, + }; + + BT_MESH_MODEL_BUF_DEFINE(buf, OP_PRIV_NODE_ID_GET, 2); + bt_mesh_model_msg_init(&buf, OP_PRIV_NODE_ID_GET); + + net_buf_simple_add_le16(&buf, key_net_idx); + + return bt_mesh_msg_ackd_send(cli->model, + &ctx, + &buf, + val ? &rsp_ctx : NULL); +} diff --git a/libs/zblue/mesh/priv_beacon_srv.c b/libs/zblue/mesh/priv_beacon_srv.c new file mode 100644 index 0000000000000..6d1b8e7147c93 --- /dev/null +++ b/libs/zblue/mesh/priv_beacon_srv.c @@ -0,0 +1,336 @@ +/**************************************************************************** + * libs/zblue/mesh/priv_beacon_srv.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include "net.h" +#include +#include "proxy.h" +#include "foundation.h" +#include "beacon.h" +#include "cfg.h" +#include "settings.h" + +#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_priv_beacon_srv); + +static const struct bt_mesh_model *priv_beacon_srv; + +/* Private Beacon configuration server model states */ + +struct +{ + uint8_t state; + uint8_t interval; + uint8_t proxy_state; +} priv_beacon_state; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int priv_beacon_store(bool delete) +{ + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return 0; + } + + const void *data = delete ? NULL : &priv_beacon_state; + size_t len = delete ? 0 : sizeof(priv_beacon_state); + + return bt_mesh_model_data_store(priv_beacon_srv, false, "pb", data, len); +} + +static int beacon_status_rsp(const struct bt_mesh_model *mod, + struct bt_mesh_msg_ctx *ctx) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, OP_PRIV_BEACON_STATUS, 2); + bt_mesh_model_msg_init(&buf, OP_PRIV_BEACON_STATUS); + + net_buf_simple_add_u8(&buf, bt_mesh_priv_beacon_get()); + net_buf_simple_add_u8(&buf, bt_mesh_priv_beacon_update_interval_get()); + + bt_mesh_model_send(mod, ctx, &buf, NULL, NULL); + + return 0; +} + +static int handle_beacon_get( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + LOG_DBG(""); + + beacon_status_rsp(mod, ctx); + + return 0; +} + +static int handle_beacon_set( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint8_t beacon; + + if (buf->len > 2U) + { + return -EMSGSIZE; + } + + beacon = net_buf_simple_pull_u8(buf); + if (beacon != BT_MESH_BEACON_DISABLED && beacon != BT_MESH_BEACON_ENABLED) + { + LOG_WRN("Invalid beacon value %u", beacon); + return -EINVAL; + } + + if (buf->len == 1U) + { + bt_mesh_priv_beacon_update_interval_set(net_buf_simple_pull_u8(buf)); + } + + (void)bt_mesh_priv_beacon_set(beacon); + beacon_status_rsp(mod, ctx); + + return 0; +} + +static void gatt_proxy_status_rsp(const struct bt_mesh_model *mod, + struct bt_mesh_msg_ctx *ctx) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, OP_PRIV_GATT_PROXY_STATUS, 1); + bt_mesh_model_msg_init(&buf, OP_PRIV_GATT_PROXY_STATUS); + + net_buf_simple_add_u8(&buf, bt_mesh_priv_gatt_proxy_get()); + + bt_mesh_model_send(mod, ctx, &buf, NULL, NULL); +} + +static int handle_gatt_proxy_get( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + LOG_DBG(""); + + gatt_proxy_status_rsp(mod, ctx); + + return 0; +} + +static int handle_gatt_proxy_set( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint8_t gatt_proxy; + + gatt_proxy = net_buf_simple_pull_u8(buf); + if (gatt_proxy != BT_MESH_GATT_PROXY_DISABLED && + gatt_proxy != BT_MESH_GATT_PROXY_ENABLED) + { + LOG_WRN("Invalid GATT proxy value %u", gatt_proxy); + return -EINVAL; + } + + LOG_DBG("%u", gatt_proxy); + + bt_mesh_priv_gatt_proxy_set(gatt_proxy); + + gatt_proxy_status_rsp(mod, ctx); + + return 0; +} + +static void node_id_status_rsp( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + uint8_t status, uint16_t net_idx, uint8_t node_id) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, OP_PRIV_NODE_ID_STATUS, 4); + bt_mesh_model_msg_init(&buf, OP_PRIV_NODE_ID_STATUS); + + net_buf_simple_add_u8(&buf, status); + net_buf_simple_add_le16(&buf, net_idx); + net_buf_simple_add_u8(&buf, node_id); + + bt_mesh_model_send(mod, ctx, &buf, NULL, NULL); +} + +static int handle_node_id_get( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + enum bt_mesh_feat_state node_id; + uint16_t net_idx; + uint8_t status; + + net_idx = net_buf_simple_pull_le16(buf) & 0xfff; + + status = bt_mesh_subnet_priv_node_id_get(net_idx, &node_id); + node_id_status_rsp(mod, ctx, status, net_idx, (uint8_t)node_id); + + return 0; +} + +static int handle_node_id_set( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + uint8_t node_id; + uint8_t status; + uint16_t net_idx; + + net_idx = net_buf_simple_pull_le16(buf) & 0xfff; + node_id = net_buf_simple_pull_u8(buf); + if (node_id != BT_MESH_NODE_IDENTITY_RUNNING && + node_id != BT_MESH_NODE_IDENTITY_STOPPED) + { + LOG_ERR("Invalid node ID value 0x%02x", node_id); + return -EINVAL; + } + + status = bt_mesh_subnet_priv_node_id_set(net_idx, node_id); + node_id_status_rsp(mod, ctx, status, net_idx, node_id); + + return 0; +} + +const struct bt_mesh_model_op bt_mesh_priv_beacon_srv_op[] = +{ + { + OP_PRIV_BEACON_GET, + BT_MESH_LEN_EXACT(0), + handle_beacon_get + }, + + { + OP_PRIV_BEACON_SET, + BT_MESH_LEN_MIN(1), + handle_beacon_set + }, + + { + OP_PRIV_GATT_PROXY_GET, + BT_MESH_LEN_EXACT(0), + handle_gatt_proxy_get + }, + + { + OP_PRIV_GATT_PROXY_SET, + BT_MESH_LEN_EXACT(1), + handle_gatt_proxy_set + }, + + { + OP_PRIV_NODE_ID_GET, + BT_MESH_LEN_EXACT(2), + handle_node_id_get + }, + + { + OP_PRIV_NODE_ID_SET, + BT_MESH_LEN_EXACT(3), + handle_node_id_set + }, + + BT_MESH_MODEL_OP_END +}; + +static int priv_beacon_srv_init(const struct bt_mesh_model *mod) +{ + int err; + const struct bt_mesh_model *config_srv = + bt_mesh_model_find(bt_mesh_model_elem(mod), + BT_MESH_MODEL_ID_CFG_SRV); + + if (config_srv == NULL) + { + LOG_ERR("Private Beacon server cannot extend Configuration server"); + return -EINVAL; + } + + priv_beacon_srv = mod; + mod->keys[0] = BT_MESH_KEY_DEV_LOCAL; + + err = bt_mesh_model_extend(mod, config_srv); + + if (err) + { + return err; + } + + return 0; +} + +static void priv_beacon_srv_reset(const struct bt_mesh_model *model) +{ + (void)memset(&priv_beacon_state, 0, sizeof(priv_beacon_state)); + priv_beacon_store(true); +} + +#ifdef CONFIG_BT_SETTINGS +static int priv_beacon_srv_settings_set( + const struct bt_mesh_model *model, const char *name, size_t len_rd, + settings_read_cb read_cb, void *cb_data) +{ + int err; + + if (len_rd == 0) + { + LOG_DBG("Cleared configuration state"); + return 0; + } + + err = bt_mesh_settings_set(read_cb, cb_data, &priv_beacon_state, + sizeof(priv_beacon_state)); + if (err) + { + LOG_ERR("Failed to set Private Beacon state"); + return err; + } + + bt_mesh_priv_beacon_set(priv_beacon_state.state); + bt_mesh_priv_beacon_update_interval_set(priv_beacon_state.interval); + bt_mesh_priv_gatt_proxy_set(priv_beacon_state.proxy_state); + return 0; +} + +static void priv_beacon_srv_pending_store(const struct bt_mesh_model *model) +{ + priv_beacon_state.state = bt_mesh_priv_beacon_get(); + priv_beacon_state.interval = bt_mesh_priv_beacon_update_interval_get(); + priv_beacon_state.proxy_state = bt_mesh_priv_gatt_proxy_get(); + + priv_beacon_store(false); +} +#endif + +const struct bt_mesh_model_cb bt_mesh_priv_beacon_srv_cb = +{ + .init = priv_beacon_srv_init, + .reset = priv_beacon_srv_reset, +#ifdef CONFIG_BT_SETTINGS + .settings_set = priv_beacon_srv_settings_set, + .pending_store = priv_beacon_srv_pending_store, +#endif +}; + +void bt_mesh_priv_beacon_srv_store_schedule(void) +{ + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bt_mesh_model_data_store_schedule(priv_beacon_srv); + } +} diff --git a/libs/zblue/mesh/prov.c b/libs/zblue/mesh/prov.c new file mode 100644 index 0000000000000..a7ae4895689a0 --- /dev/null +++ b/libs/zblue/mesh/prov.c @@ -0,0 +1,654 @@ +/**************************************************************************** + * libs/zblue/mesh/prov.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * Copyright (c) 2020 Lingao Meng + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "crypto.h" +#include "mesh.h" +#include "net.h" +#include "access.h" +#include "foundation.h" +#include "prov.h" + +#define LOG_LEVEL CONFIG_BT_MESH_PROV_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_prov); + +/* 10 power 32 represents 14 bytes maximum in little-endian format */ +#define MAX_NUMERIC_OOB_BYTES 14 + +struct bt_mesh_prov_link bt_mesh_prov_link; +const struct bt_mesh_prov *bt_mesh_prov; + +/* Verify specification defined length: */ + +BUILD_ASSERT(sizeof(bt_mesh_prov_link.conf_inputs) == 145, + "Confirmation inputs shall be 145 bytes"); + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bt_mesh_prov_reset_state(void) +{ + int err; + const size_t offset = offsetof(struct bt_mesh_prov_link, addr); + + atomic_set(bt_mesh_prov_link.flags, 0); + (void)memset((uint8_t *)&bt_mesh_prov_link + offset, 0, + sizeof(bt_mesh_prov_link) - offset); + + err = bt_mesh_pub_key_gen(); + if (err) + { + LOG_ERR("Failed to generate public key (%d)", err); + return err; + } + + return 0; +} + +static bt_mesh_output_action_t output_action(uint8_t action) +{ + switch (action) + { + case OUTPUT_OOB_BLINK: + return BT_MESH_BLINK; + case OUTPUT_OOB_BEEP: + return BT_MESH_BEEP; + case OUTPUT_OOB_VIBRATE: + return BT_MESH_VIBRATE; + case OUTPUT_OOB_NUMBER: + return BT_MESH_DISPLAY_NUMBER; + case OUTPUT_OOB_STRING: + return BT_MESH_DISPLAY_STRING; + default: + return BT_MESH_NO_OUTPUT; + } +} + +static bt_mesh_input_action_t input_action(uint8_t action) +{ + switch (action) + { + case INPUT_OOB_PUSH: + return BT_MESH_PUSH; + case INPUT_OOB_TWIST: + return BT_MESH_TWIST; + case INPUT_OOB_NUMBER: + return BT_MESH_ENTER_NUMBER; + case INPUT_OOB_STRING: + return BT_MESH_ENTER_STRING; + default: + return BT_MESH_NO_INPUT; + } +} + +static int check_output_auth(bt_mesh_output_action_t output, uint8_t size) +{ + if (!output) + { + return -EINVAL; + } + + if (!(bt_mesh_prov->output_actions & output)) + { + return -EINVAL; + } + + if (size > bt_mesh_prov->output_size || size == 0) + { + return -EINVAL; + } + + return 0; +} + +static int check_input_auth(bt_mesh_input_action_t input, uint8_t size) +{ + if (!input) + { + return -EINVAL; + } + + if (!(bt_mesh_prov->input_actions & input)) + { + return -EINVAL; + } + + if (size > bt_mesh_prov->input_size || size == 0) + { + return -EINVAL; + } + + return 0; +} + +static void get_auth_string(uint8_t *str, uint8_t size) +{ + static const char characters[36] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + bt_rand(str, size); + + for (int i = 0; i < size; i++) + { + str[i] = characters[(uint8_t)str[i] % (uint8_t)36]; + } + + memcpy(bt_mesh_prov_link.auth, str, size); + memset(bt_mesh_prov_link.auth + size, 0, + sizeof(bt_mesh_prov_link.auth) - size); +} + +/* Computes 10^n - 1 in little endian array */ + +static void compute_pow10_minus1(uint8_t n, uint8_t *out, size_t *out_len) +{ + out[0] = 1; /* Start with value 1 at LSB */ + size_t len = 1; + + /* Compute 10^n */ + + for (uint8_t i = 0; i < n; ++i) + { + uint16_t carry = 0; + + for (size_t j = 0; j < len; ++j) + { + uint16_t prod = out[j] * 10 + carry; + + out[j] = prod & 0xff; + carry = prod >> 8; + } + + if (carry > 0 && len < MAX_NUMERIC_OOB_BYTES) + { + out[len] = carry; + len++; + } + } + + /* Subtract 1 from the result (10^n - 1) */ + + size_t j = 0; + + while (j < len) + { + if (out[j] > 0) + { + out[j] -= 1; + break; + } + + out[j] = 0xff; + j++; + } + + /* Adjust length if highest byte becomes 0 */ + + while (len > 1 && out[len - 1] == 0) + { + len--; + } + + *out_len = len; +} + +static void random_byte_shift(uint8_t max_inclusive, uint8_t *out) +{ + if (max_inclusive == 0) + { + *out = 0; + return; + } + + while (*out > max_inclusive) + { + *out >>= 1; + } +} + +static void generate_random_below_pow10(uint8_t n, + uint8_t *output, size_t *out_len) +{ + uint8_t max_val[MAX_NUMERIC_OOB_BYTES] = + { + 0 + }; + + size_t max_len = 0; + + compute_pow10_minus1(n, max_val, &max_len); + bt_rand(output, max_len); + + /* Generate random number less than max_val, starting from MSB in + * little-endian + */ + + for (int i = max_len - 1; i >= 0; --i) + { + random_byte_shift(max_val[i], &output[i]); + if (output[i] < max_val[i]) + { + break; + } + } + + /* Ensure the result is not all zero */ + + int all_zero = 1; + + for (size_t i = 0; i < max_len; ++i) + { + if (output[i] != 0) + { + all_zero = 0; + break; + } + } + + if (all_zero) + { + output[0] = 1; + } + + *out_len = max_len; +} + +static void get_auth_number(uint8_t *rand_bytes, size_t *size) +{ + uint8_t auth_size = bt_mesh_prov_auth_size_get(); + + generate_random_below_pow10(*size, rand_bytes, size); + sys_memcpy_swap(bt_mesh_prov_link.auth + (auth_size - *size), rand_bytes, + *size); + memset(bt_mesh_prov_link.auth, 0, auth_size - *size); +} + +int bt_mesh_prov_auth(bool is_provisioner, + uint8_t method, uint8_t action, size_t size) +{ + bt_mesh_output_action_t output; + bt_mesh_input_action_t input; + uint8_t rand_bytes[PROV_IO_OOB_SIZE_MAX + 1] = + { + 0 + }; + + uint8_t auth_size = bt_mesh_prov_auth_size_get(); + int err; + + size = MIN(size, PROV_IO_OOB_SIZE_MAX); + + switch (method) + { + case AUTH_METHOD_NO_OOB: + if (action || size) + { + return -EINVAL; + } + + (void)memset(bt_mesh_prov_link.auth, 0, auth_size); + return 0; + case AUTH_METHOD_STATIC: + if (action || size) + { + return -EINVAL; + } + + atomic_set_bit(bt_mesh_prov_link.flags, OOB_STATIC_KEY); + + return 0; + + case AUTH_METHOD_OUTPUT: + output = output_action(action); + err = check_output_auth(output, size); + if (err) + { + return err; + } + + if (is_provisioner) + { + if (output == BT_MESH_DISPLAY_STRING) + { + input = BT_MESH_ENTER_STRING; + atomic_set_bit(bt_mesh_prov_link.flags, WAIT_STRING); + } + else + { + input = BT_MESH_ENTER_NUMBER; + atomic_set_bit(bt_mesh_prov_link.flags, WAIT_NUMBER); + } + + return bt_mesh_prov->input(input, size); + } + + atomic_set_bit(bt_mesh_prov_link.flags, NOTIFY_INPUT_COMPLETE); + + if (output == BT_MESH_DISPLAY_STRING) + { + get_auth_string(rand_bytes, MIN(size, auth_size)); + return bt_mesh_prov->output_string((const char *)rand_bytes); + } + + get_auth_number(rand_bytes, &size); +#if defined CONFIG_BT_MESH_PROV_OOB_API_LEGACY + uint32_t output_num; + + memcpy(&output_num, rand_bytes, sizeof(output_num)); + return bt_mesh_prov->output_number(output, output_num); +#else + return bt_mesh_prov->output_numeric(output, rand_bytes, size); +#endif + + case AUTH_METHOD_INPUT: + input = input_action(action); + err = check_input_auth(input, size); + if (err) + { + return err; + } + + if (!is_provisioner) + { + if (input == BT_MESH_ENTER_STRING) + { + atomic_set_bit(bt_mesh_prov_link.flags, WAIT_STRING); + } + else + { + atomic_set_bit(bt_mesh_prov_link.flags, WAIT_NUMBER); + } + + return bt_mesh_prov->input(input, size); + } + + atomic_set_bit(bt_mesh_prov_link.flags, NOTIFY_INPUT_COMPLETE); + + if (input == BT_MESH_ENTER_STRING) + { + get_auth_string(rand_bytes, MIN(size, auth_size)); + return bt_mesh_prov->output_string((const char *)rand_bytes); + } + + get_auth_number(rand_bytes, &size); + output = BT_MESH_DISPLAY_NUMBER; +#if defined CONFIG_BT_MESH_PROV_OOB_API_LEGACY + uint32_t input_num; + + memcpy(&input_num, rand_bytes, sizeof(input_num)); + return bt_mesh_prov->output_number(output, input_num); +#else + return bt_mesh_prov->output_numeric(output, rand_bytes, size); +#endif + + default: + return -EINVAL; + } +} + +#if defined CONFIG_BT_MESH_PROV_OOB_API_LEGACY +int bt_mesh_input_number(uint32_t num) +{ + uint8_t auth_size = bt_mesh_prov_auth_size_get(); + + LOG_DBG("%u", num); + + if (!atomic_test_and_clear_bit(bt_mesh_prov_link.flags, WAIT_NUMBER)) + { + return -EINVAL; + } + + sys_put_be32(num, &bt_mesh_prov_link.auth[auth_size - sizeof(num)]); + + bt_mesh_prov_link.role->input_complete(); + + return 0; +} +#endif + +int bt_mesh_input_numeric(uint8_t *numeric, size_t size) +{ + uint8_t auth_size = bt_mesh_prov_auth_size_get(); + + LOG_HEXDUMP_DBG(numeric, size, ""); + + if (size > MAX_NUMERIC_OOB_BYTES) + { + return -ENOTSUP; + } + + if (!atomic_test_and_clear_bit(bt_mesh_prov_link.flags, WAIT_NUMBER)) + { + return -EINVAL; + } + + sys_memcpy_swap(bt_mesh_prov_link.auth + (auth_size - size), numeric, + size); + memset(bt_mesh_prov_link.auth, 0, auth_size - size); + + bt_mesh_prov_link.role->input_complete(); + + return 0; +} + +int bt_mesh_input_string(const char *str) +{ + size_t size = strlen(str); + + LOG_DBG("%s", str); + + if (size > PROV_IO_OOB_SIZE_MAX || size > bt_mesh_prov_link.oob_size) + { + return -ENOTSUP; + } + + if (!atomic_test_and_clear_bit(bt_mesh_prov_link.flags, WAIT_STRING)) + { + return -EINVAL; + } + + memcpy(bt_mesh_prov_link.auth, str, size); + memset(bt_mesh_prov_link.auth + size, 0, + sizeof(bt_mesh_prov_link.auth) - size); + + bt_mesh_prov_link.role->input_complete(); + + return 0; +} + +const struct bt_mesh_prov *bt_mesh_prov_get(void) +{ + return bt_mesh_prov; +} + +bool bt_mesh_prov_active(void) +{ + return atomic_test_bit(bt_mesh_prov_link.flags, LINK_ACTIVE); +} + +static void prov_recv(const struct prov_bearer *bearer, + void *cb_data, struct net_buf_simple *buf) +{ + static const uint8_t op_len[10] = + { + [PROV_INVITE] = PDU_LEN_INVITE, + [PROV_CAPABILITIES] = PDU_LEN_CAPABILITIES, + [PROV_START] = PDU_LEN_START, + [PROV_PUB_KEY] = PDU_LEN_PUB_KEY, + [PROV_INPUT_COMPLETE] = PDU_LEN_INPUT_COMPLETE, + [PROV_CONFIRM] = PDU_LEN_CONFIRM, + [PROV_RANDOM] = PDU_LEN_RANDOM, + [PROV_DATA] = PDU_LEN_DATA, + [PROV_COMPLETE] = PDU_LEN_COMPLETE, + [PROV_FAILED] = PDU_LEN_FAILED, + }; + + uint8_t type = buf->data[0]; + + LOG_DBG("type 0x%02x len %u", type, buf->len); + + if (type >= ARRAY_SIZE(bt_mesh_prov_link.role->op)) + { + LOG_ERR("Unknown provisioning PDU type 0x%02x", type); + bt_mesh_prov_link.role->error(PROV_ERR_NVAL_PDU); + return; + } + + if ((type != PROV_FAILED && type != bt_mesh_prov_link.expect) || + !bt_mesh_prov_link.role->op[type]) + { + LOG_WRN("Unexpected msg 0x%02x != 0x%02x", type, + bt_mesh_prov_link.expect); + bt_mesh_prov_link.role->error(PROV_ERR_UNEXP_PDU); + return; + } + + uint8_t expected = 1 + op_len[type]; + + if (type == PROV_CONFIRM || type == PROV_RANDOM) + { + /* Expected length depends on Auth size */ + + expected = 1 + bt_mesh_prov_auth_size_get(); + } + + if (buf->len != expected) + { + LOG_ERR("Invalid length %u for type 0x%02x", buf->len, type); + bt_mesh_prov_link.role->error(PROV_ERR_NVAL_FMT); + return; + } + + bt_mesh_prov_link.role->op[type](&buf->data[1]); +} + +static void prov_link_opened(const struct prov_bearer *bearer, void *cb_data) +{ + atomic_set_bit(bt_mesh_prov_link.flags, LINK_ACTIVE); + + if (bt_mesh_prov->link_open) + { + bt_mesh_prov->link_open(bearer->type); + } + + bt_mesh_prov_link.bearer = bearer; + + if (bt_mesh_prov_link.role->link_opened) + { + bt_mesh_prov_link.role->link_opened(); + } +} + +static void prov_link_closed(const struct prov_bearer *bearer, void *cb_data, + enum prov_bearer_link_status reason) +{ + LOG_DBG("%u", reason); + + if (bt_mesh_prov_link.role->link_closed) + { + bt_mesh_prov_link.role->link_closed(reason); + } + + if (bt_mesh_prov->link_close) + { + bt_mesh_prov->link_close(bearer->type); + } +} + +static void prov_bearer_error(const struct prov_bearer *bearer, + void *cb_data, uint8_t err) +{ + if (bt_mesh_prov_link.role->error) + { + bt_mesh_prov_link.role->error(err); + } +} + +static const struct prov_bearer_cb prov_bearer_cb = +{ + .link_opened = prov_link_opened, + .link_closed = prov_link_closed, + .error = prov_bearer_error, + .recv = prov_recv, +}; + +const struct prov_bearer_cb *bt_mesh_prov_bearer_cb_get(void) +{ + return &prov_bearer_cb; +} + +void bt_mesh_prov_complete(uint16_t net_idx, uint16_t addr) +{ + if (bt_mesh_prov->complete) + { + bt_mesh_prov->complete(net_idx, addr); + } +} + +void bt_mesh_prov_reset(void) +{ + if (IS_ENABLED(CONFIG_BT_MESH_PB_ADV)) + { + bt_mesh_pb_adv_reset(); + } + + if (IS_ENABLED(CONFIG_BT_MESH_PB_GATT)) + { + bt_mesh_pb_gatt_reset(); + } + + bt_mesh_prov_reset_state(); + + if (bt_mesh_prov->reset) + { + bt_mesh_prov->reset(); + } +} + +int bt_mesh_prov_init(const struct bt_mesh_prov *prov_info) +{ + if (!prov_info) + { + LOG_ERR("No provisioning context provided"); + return -EINVAL; + } + + bt_mesh_prov = prov_info; + + if (IS_ENABLED(CONFIG_BT_MESH_PB_ADV)) + { + bt_mesh_pb_adv_init(); + } + + if (IS_ENABLED(CONFIG_BT_MESH_PB_GATT)) + { + bt_mesh_pb_gatt_init(); + } + + return bt_mesh_prov_reset_state(); +} diff --git a/libs/zblue/mesh/prov.h b/libs/zblue/mesh/prov.h new file mode 100644 index 0000000000000..f2a40916cfa60 --- /dev/null +++ b/libs/zblue/mesh/prov.h @@ -0,0 +1,239 @@ +/**************************************************************************** + * libs/zblue/mesh/prov.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * Copyright (c) 2020 Lingao Meng + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_SUBSYS_BLUETOOTH_MESH_PROV_H_ +#define ZEPHYR_SUBSYS_BLUETOOTH_MESH_PROV_H_ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include "prov_bearer.h" + +#define PROV_ERR_NONE 0x00 +#define PROV_ERR_NVAL_PDU 0x01 +#define PROV_ERR_NVAL_FMT 0x02 +#define PROV_ERR_UNEXP_PDU 0x03 +#define PROV_ERR_CFM_FAILED 0x04 +#define PROV_ERR_RESOURCES 0x05 +#define PROV_ERR_DECRYPT 0x06 +#define PROV_ERR_UNEXP_ERR 0x07 +#define PROV_ERR_ADDR 0x08 +#define PROV_ERR_INVALID_DATA 0x09 + +#define AUTH_METHOD_NO_OOB 0x00 +#define AUTH_METHOD_STATIC 0x01 +#define AUTH_METHOD_OUTPUT 0x02 +#define AUTH_METHOD_INPUT 0x03 + +#define OUTPUT_OOB_BLINK 0x00 +#define OUTPUT_OOB_BEEP 0x01 +#define OUTPUT_OOB_VIBRATE 0x02 +#define OUTPUT_OOB_NUMBER 0x03 +#define OUTPUT_OOB_STRING 0x04 + +#define INPUT_OOB_PUSH 0x00 +#define INPUT_OOB_TWIST 0x01 +#define INPUT_OOB_NUMBER 0x02 +#define INPUT_OOB_STRING 0x03 + +#define PUB_KEY_NO_OOB 0x00 +#define PUB_KEY_OOB 0x01 + +#define PROV_INVITE 0x00 +#define PROV_CAPABILITIES 0x01 +#define PROV_START 0x02 +#define PROV_PUB_KEY 0x03 +#define PROV_INPUT_COMPLETE 0x04 +#define PROV_CONFIRM 0x05 +#define PROV_RANDOM 0x06 +#define PROV_DATA 0x07 +#define PROV_COMPLETE 0x08 +#define PROV_FAILED 0x09 + +#define PROV_NO_PDU 0xff + +#define PDU_LEN_INVITE 1 +#define PDU_LEN_CAPABILITIES 11 +#define PDU_LEN_START 5 +#define PDU_LEN_PUB_KEY 64 +#define PDU_LEN_INPUT_COMPLETE 0 +#define PDU_LEN_CONFIRM 32 /* Max size */ +#define PDU_LEN_RANDOM 32 /* Max size */ +#define PDU_LEN_DATA 33 +#define PDU_LEN_COMPLETE 0 +#define PDU_LEN_FAILED 1 + +#define PDU_OP_LEN 1 + +#define PROV_ALG_P256 0x00 + +#if defined CONFIG_BT_MESH_PROV_OOB_API_LEGACY +#define PROV_IO_OOB_SIZE_MAX 8 /* in bytes */ +#else +#define PROV_IO_OOB_SIZE_MAX 32 /* in bytes */ +#endif + +#define PRIV_KEY_SIZE 32 +#define PUB_KEY_SIZE PDU_LEN_PUB_KEY +#define DH_KEY_SIZE 32 + +#define PROV_BUF(name, len) \ + NET_BUF_SIMPLE_DEFINE(name, \ + PROV_BEARER_BUF_HEADROOM + PDU_OP_LEN + len + PROV_BEARER_BUF_TAILROOM) + +#if defined(CONFIG_BT_MESH_ECDH_P256_HMAC_SHA256_AES_CCM) +#define PROV_AUTH_MAX_LEN 32 +#else +#define PROV_AUTH_MAX_LEN 16 +#endif + +enum +{ + LINK_ACTIVE, /* Link has been opened */ + WAIT_NUMBER, /* Waiting for number input from user */ + WAIT_STRING, /* Waiting for string input from user */ + NOTIFY_INPUT_COMPLETE, /* Notify that input has been completed. */ + PROVISIONER, /* The link was opened as provisioner */ + OOB_PUB_KEY, /* OOB Public key used */ + PUB_KEY_SENT, /* Public key has been sent */ + REMOTE_PUB_KEY, /* Remote key has been received */ + INPUT_COMPLETE, /* Device input completed */ + WAIT_CONFIRM, /* Wait for send confirm */ + WAIT_AUTH, /* Wait for auth response */ + OOB_STATIC_KEY, /* OOB Static Authentication */ + REPROVISION, /* The link was opened as a reprovision target */ + COMPLETE, /* The provisioning process completed. */ + + NUM_FLAGS, +}; + +/** Provisioning role */ + +struct bt_mesh_prov_role +{ + void (*link_opened)(void); + + void (*link_closed)(enum prov_bearer_link_status status); + + void (*error)(uint8_t reason); + + void (*input_complete)(void); + + void (*op[10])(const uint8_t *data); +}; + +struct bt_mesh_prov_link +{ + atomic_define(flags, NUM_FLAGS); + + const struct prov_bearer *bearer; + const struct bt_mesh_prov_role *role; + + uint16_t addr; /* Assigned address */ + + uint8_t algorithm; /* Authen algorithm */ + uint8_t oob_method; /* Authen method */ + uint8_t oob_action; /* Authen action */ + uint8_t oob_size; /* Authen size */ + uint8_t auth[PROV_AUTH_MAX_LEN]; /* Authen value */ + + uint8_t dhkey[DH_KEY_SIZE]; /* Calculated DHKey */ + uint8_t expect; /* Next expected PDU */ + + uint8_t conf[PROV_AUTH_MAX_LEN]; /* Local/Remote Confirmation */ + uint8_t rand[PROV_AUTH_MAX_LEN]; /* Local Random */ + + uint8_t conf_salt[PROV_AUTH_MAX_LEN]; /* ConfirmationSalt */ + uint8_t conf_key[PROV_AUTH_MAX_LEN]; /* ConfirmationKey */ + + /* ConfirmationInput fields: */ + + struct + { + uint8_t invite[PDU_LEN_INVITE]; + uint8_t capabilities[PDU_LEN_CAPABILITIES]; + uint8_t start[PDU_LEN_START]; + uint8_t pub_key_provisioner[PDU_LEN_PUB_KEY]; /* big-endian */ + uint8_t pub_key_device[PDU_LEN_PUB_KEY]; /* big-endian */ + } conf_inputs; + uint8_t prov_salt[16]; /* Provisioning Salt */ +}; + +extern struct bt_mesh_prov_link bt_mesh_prov_link; +extern const struct bt_mesh_prov *bt_mesh_prov; + +static inline int bt_mesh_prov_send(struct net_buf_simple *buf, + prov_bearer_send_complete_t cb) +{ + return bt_mesh_prov_link.bearer->send(buf, cb, NULL); +} + +static inline void bt_mesh_prov_buf_init(struct net_buf_simple *buf, + uint8_t type) +{ + net_buf_simple_reserve(buf, PROV_BEARER_BUF_HEADROOM); + net_buf_simple_add_u8(buf, type); +} + +static inline uint8_t bt_mesh_prov_auth_size_get(void) +{ + if (IS_ENABLED(CONFIG_BT_MESH_ECDH_P256_HMAC_SHA256_AES_CCM)) + { + return bt_mesh_prov_link.algorithm == + BT_MESH_PROV_AUTH_CMAC_AES128_AES_CCM ? 16 : 32; + } + else + { + return 16; + } +} + +static inline clock_t bt_mesh_prov_protocol_timeout_get(void) +{ + return (bt_mesh_prov_link.oob_method == AUTH_METHOD_INPUT || + bt_mesh_prov_link.oob_method == AUTH_METHOD_OUTPUT) + ? PROTOCOL_TIMEOUT_EXT + : PROTOCOL_TIMEOUT; +} + +int bt_mesh_prov_reset_state(void); + +bool bt_mesh_prov_active(void); + +int bt_mesh_prov_auth(bool is_provisioner, + uint8_t method, uint8_t action, size_t size); + +int bt_mesh_provisionee_suspend(void); +int bt_mesh_provisionee_resume(void); + +int bt_mesh_pb_remote_open( + struct bt_mesh_rpr_cli *cli, const struct bt_mesh_rpr_node *srv, + const uint8_t uuid[16], uint16_t net_idx, uint16_t addr); + +int bt_mesh_pb_remote_open_node( + struct bt_mesh_rpr_cli *cli, struct bt_mesh_rpr_node *srv, uint16_t addr, + bool composition_change); + +const struct bt_mesh_prov *bt_mesh_prov_get(void); + +void bt_mesh_prov_complete(uint16_t net_idx, uint16_t addr); +void bt_mesh_prov_reset(void); + +const struct prov_bearer_cb *bt_mesh_prov_bearer_cb_get(void); + +void bt_mesh_pb_adv_recv(struct net_buf_simple *buf); + +int bt_mesh_prov_init(const struct bt_mesh_prov *prov); + +#endif /* ZEPHYR_SUBSYS_BLUETOOTH_MESH_PROV_H_ */ diff --git a/libs/zblue/mesh/prov_bearer.h b/libs/zblue/mesh/prov_bearer.h new file mode 100644 index 0000000000000..31fe7469b17d2 --- /dev/null +++ b/libs/zblue/mesh/prov_bearer.h @@ -0,0 +1,166 @@ +/**************************************************************************** + * libs/zblue/mesh/prov_bearer.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** Provisioning protocol timeout in seconds. */ +#define PROTOCOL_TIMEOUT_SEC 60 +#define PROTOCOL_TIMEOUT_EXT_SEC 120 + +/** Provisioning protocol timeout. */ +#define PROTOCOL_TIMEOUT SEC2TICK(PROTOCOL_TIMEOUT_SEC) +#define PROTOCOL_TIMEOUT_EXT SEC2TICK(PROTOCOL_TIMEOUT_EXT_SEC) + +/** @def PROV_BEARER_BUF_HEADROOM + * + * @brief Required headroom for the bearer packet buffers. + */ +#if defined(CONFIG_BT_MESH_PB_GATT_COMMON) +#define PROV_BEARER_BUF_HEADROOM 5 +#elif defined(CONFIG_BT_MESH_RPR_CLI) || defined(CONFIG_BT_MESH_RPR_SRV) +#define PROV_BEARER_BUF_HEADROOM 3 +#else +#define PROV_BEARER_BUF_HEADROOM 0 +#endif + +/* @brief Required tailroom for the bearer packet buffers. + */ +#if defined(CONFIG_BT_MESH_RPR_CLI) || defined(CONFIG_BT_MESH_RPR_SRV) +#define PROV_BEARER_BUF_TAILROOM 4 +#else +#define PROV_BEARER_BUF_TAILROOM 0 +#endif + +enum prov_bearer_link_status +{ + PROV_BEARER_LINK_STATUS_SUCCESS, + PROV_BEARER_LINK_STATUS_TIMEOUT, + PROV_BEARER_LINK_STATUS_FAIL, +}; + +struct prov_bearer; + +/** Callbacks from bearer to host */ + +struct prov_bearer_cb +{ + void (*link_opened)(const struct prov_bearer *bearer, void *cb_data); + + void (*link_closed)(const struct prov_bearer *bearer, void *cb_data, + enum prov_bearer_link_status reason); + + void (*error)(const struct prov_bearer *bearer, + void *cb_data, + uint8_t err); + + void (*recv)(const struct prov_bearer *bearer, void *cb_data, + struct net_buf_simple *buf); +}; + +typedef void (*prov_bearer_send_complete_t)(int err, void *cb_data); + +/** Provisioning bearer API */ + +struct prov_bearer +{ + /** Provisioning bearer type. */ + + bt_mesh_prov_bearer_t type; + + /** @brief Enable link establishment as a provisionee. + * + * Prompts the bearer to make itself visible to provisioners, and start + * accepting link open messages. + * + * @param cb Bearer event callbacks used for the duration of the + * link. + * @param cb_data Context parameter to pass to the bearer callbacks. + * + * @return Zero on success, or (negative) error code otherwise. + */ + + int (*link_accept)(const struct prov_bearer_cb *cb, void *cb_data); + + /** @brief Disable link establishment as a provisionee. + * + * Stops accepting link open messages and sending unprovisioned beacons. + */ + + void (*link_cancel)(void); + + /** @brief Send a packet on an established link. + * + * @param buf Payload buffer. Requires @ref PROV_BEARER_BUF_HEADROOM + * bytes of headroom. + * @param cb Callback to call when sending is complete. + * @param cb_data Callback data. + * + * @return Zero on success, or (negative) error code otherwise. + */ + + int (*send)(struct net_buf_simple *buf, prov_bearer_send_complete_t cb, + void *cb_data); + + /** @brief Clear any ongoing transmissions, if possible. + * + * Bearers that don't support tx clearing must implement this callback and + * leave it empty. + */ + + void (*clear_tx)(void); + + /* Only available in provisioners: */ + + /** @brief Open a new link as a provisioner. + * + * Only available in provisioners. Bearers that don't support the + * provisioner role should leave this as NULL. + * + * @param uuid UUID of the node to establish a link to. + * @param timeout Link open timeout in seconds. + * @param cb Bearer event callbacks used for the duration of the + * link. + * @param cb_data Context parameter to pass to the bearer callbacks. + * + * @return Zero on success, or (negative) error code otherwise. + */ + + int (*link_open)(const uint8_t uuid[16], uint8_t timeout, + const struct prov_bearer_cb *cb, + void *cb_data); + + /** @brief Close the current link. + * + * Only available in provisioners. Bearers that don't support the + * provisioner role should leave this as NULL. + * + * @param status Link status for the link close message. + */ + + void (*link_close)(enum prov_bearer_link_status status); +}; + +struct pb_remote_ctx +{ + struct bt_mesh_rpr_cli *cli; + const struct bt_mesh_rpr_node *srv; + enum bt_mesh_rpr_node_refresh refresh; +}; + +extern const struct prov_bearer bt_mesh_pb_adv; +extern const struct prov_bearer bt_mesh_pb_gatt; +extern const struct prov_bearer pb_remote_cli; +extern const struct prov_bearer pb_remote_srv; + +void bt_mesh_pb_adv_init(void); +void bt_mesh_pb_gatt_init(void); + +void bt_mesh_pb_adv_reset(void); +void bt_mesh_pb_gatt_reset(void); diff --git a/libs/zblue/mesh/provisionee.c b/libs/zblue/mesh/provisionee.c new file mode 100644 index 0000000000000..f07b0f0ef23d2 --- /dev/null +++ b/libs/zblue/mesh/provisionee.c @@ -0,0 +1,1049 @@ +/**************************************************************************** + * libs/zblue/mesh/provisionee.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * Copyright (c) 2020 Lingao Meng + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "common/bt_str.h" +#include "../host/long_wq.h" + +#include "crypto.h" +#include "mesh.h" +#include "net.h" +#include "rpl.h" +#include "beacon.h" +#include "access.h" +#include "foundation.h" +#include "proxy.h" +#include "pb_gatt_srv.h" +#include "prov.h" +#include "settings.h" +#include "rpr.h" + +#define LOG_LEVEL CONFIG_BT_MESH_PROVISIONEE_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_provisionee); + +static void reprovision_fail(void); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void prov_send_fail_msg(uint8_t err) +{ + PROV_BUF(buf, PDU_LEN_FAILED); + + LOG_DBG("%u", err); + + bt_mesh_prov_link.expect = PROV_NO_PDU; + + bt_mesh_prov_buf_init(&buf, PROV_FAILED); + net_buf_simple_add_u8(&buf, err); + + if (bt_mesh_prov_send(&buf, NULL)) + { + LOG_ERR("Failed to send Provisioning Failed message"); + } +} + +static void prov_fail(uint8_t reason) +{ + /* According to MshPRTv1.1: 5.4.4, the provisioner just closes the link + * when something fails, while the provisionee sends the fail message, and + * waits for the provisioner to close the link. + */ + + prov_send_fail_msg(reason); + + if (IS_ENABLED(CONFIG_BT_MESH_RPR_SRV) && + atomic_test_bit(bt_mesh_prov_link.flags, REPROVISION)) + { + reprovision_fail(); + } +} + +static void prov_invite(const uint8_t *data) +{ + PROV_BUF(buf, PDU_LEN_CAPABILITIES); + + LOG_DBG("Attention Duration: %u seconds", data[0]); + + if (data[0]) + { + bt_mesh_attention(NULL, data[0]); + } + + memcpy(bt_mesh_prov_link.conf_inputs.invite, data, PDU_LEN_INVITE); + + bt_mesh_prov_buf_init(&buf, PROV_CAPABILITIES); + + /* Number of Elements supported */ + + net_buf_simple_add_u8(&buf, bt_mesh_elem_count()); + + uint16_t algorithm_bm = 0; + uint8_t oob_type = + bt_mesh_prov->static_val ? BT_MESH_STATIC_OOB_AVAILABLE : 0; + bool oob_availability = + bt_mesh_prov->output_size > 0 || bt_mesh_prov->input_size > 0 || + bt_mesh_prov->static_val; + + if (IS_ENABLED(CONFIG_BT_MESH_ECDH_P256_HMAC_SHA256_AES_CCM)) + { + WRITE_BIT(algorithm_bm, BT_MESH_PROV_AUTH_HMAC_SHA256_AES_CCM, 1); + } + + if (IS_ENABLED(CONFIG_BT_MESH_ECDH_P256_CMAC_AES128_AES_CCM)) + { + WRITE_BIT(algorithm_bm, BT_MESH_PROV_AUTH_CMAC_AES128_AES_CCM, 1); + } + + if (oob_availability && IS_ENABLED(CONFIG_BT_MESH_OOB_AUTH_REQUIRED)) + { + oob_type |= BT_MESH_OOB_AUTH_REQUIRED; + WRITE_BIT(algorithm_bm, BT_MESH_PROV_AUTH_CMAC_AES128_AES_CCM, 0); + } + + /* Supported algorithms */ + + net_buf_simple_add_be16(&buf, algorithm_bm); + + /* Public Key Type */ + + net_buf_simple_add_u8(&buf, + bt_mesh_prov->public_key_be == NULL ? PUB_KEY_NO_OOB : + PUB_KEY_OOB); + + /* Static OOB Type */ + + net_buf_simple_add_u8(&buf, oob_type); + + /* Output OOB Size */ + + net_buf_simple_add_u8(&buf, bt_mesh_prov->output_size); + + /* Output OOB Action */ + + net_buf_simple_add_be16(&buf, bt_mesh_prov->output_actions); + + /* Input OOB Size */ + + net_buf_simple_add_u8(&buf, bt_mesh_prov->input_size); + + /* Input OOB Action */ + + net_buf_simple_add_be16(&buf, bt_mesh_prov->input_actions); + + memcpy(bt_mesh_prov_link.conf_inputs.capabilities, &buf.data[1], + PDU_LEN_CAPABILITIES); + + if (bt_mesh_prov_send(&buf, NULL)) + { + LOG_ERR("Failed to send capabilities"); + return; + } + + bt_mesh_prov_link.expect = PROV_START; +} + +static void prov_start(const uint8_t *data) +{ + LOG_DBG("Algorithm: 0x%02x", data[0]); + LOG_DBG("Public Key: 0x%02x", data[1]); + LOG_DBG("Auth Method: 0x%02x", data[2]); + LOG_DBG("Auth Action: 0x%02x", data[3]); + LOG_DBG("Auth Size: 0x%02x", data[4]); + + if (IS_ENABLED(CONFIG_BT_MESH_ECDH_P256_HMAC_SHA256_AES_CCM) && + data[0] == BT_MESH_PROV_AUTH_HMAC_SHA256_AES_CCM) + { + bt_mesh_prov_link.algorithm = data[0]; + } + else if (IS_ENABLED(CONFIG_BT_MESH_ECDH_P256_CMAC_AES128_AES_CCM) && + data[0] == BT_MESH_PROV_AUTH_CMAC_AES128_AES_CCM) + { + bt_mesh_prov_link.algorithm = data[0]; + } + else + { + LOG_ERR("Unknown algorithm 0x%02x", data[0]); + prov_fail(PROV_ERR_NVAL_FMT); + return; + } + + uint8_t auth_size = bt_mesh_prov_auth_size_get(); + + if (data[1] > PUB_KEY_OOB || + (data[1] == PUB_KEY_OOB && + (!IS_ENABLED(CONFIG_BT_MESH_PROV_OOB_PUBLIC_KEY) || + !bt_mesh_prov->public_key_be))) + { + LOG_ERR("Invalid public key type: 0x%02x", data[1]); + prov_fail(PROV_ERR_NVAL_FMT); + return; + } + + atomic_set_bit_to(bt_mesh_prov_link.flags, OOB_PUB_KEY, + data[1] == PUB_KEY_OOB); + + memcpy(bt_mesh_prov_link.conf_inputs.start, data, PDU_LEN_START); + + bt_mesh_prov_link.expect = PROV_PUB_KEY; + bt_mesh_prov_link.oob_method = data[2]; + bt_mesh_prov_link.oob_action = data[3]; + bt_mesh_prov_link.oob_size = data[4]; + + if (IS_ENABLED(CONFIG_BT_MESH_OOB_AUTH_REQUIRED) && + (bt_mesh_prov_link.oob_method == AUTH_METHOD_NO_OOB || + bt_mesh_prov_link.algorithm == BT_MESH_PROV_AUTH_CMAC_AES128_AES_CCM)) + { + prov_fail(PROV_ERR_NVAL_FMT); + return; + } + + if (bt_mesh_prov_auth(false, data[2], data[3], data[4]) < 0) + { + LOG_ERR("Invalid authentication method: 0x%02x; " + "action: 0x%02x; size: 0x%02x", + data[2], data[3], data[4]); + prov_fail(PROV_ERR_NVAL_FMT); + return; + } + + if (atomic_test_bit(bt_mesh_prov_link.flags, OOB_STATIC_KEY)) + { + /* Trim the Auth if it is longer than required length */ + + memcpy(bt_mesh_prov_link.auth, bt_mesh_prov->static_val, + bt_mesh_prov->static_val_len > auth_size ? + auth_size : bt_mesh_prov->static_val_len); + + /* Pad with zeros if the Auth is shorter the required length */ + + if (bt_mesh_prov->static_val_len < auth_size) + { + memset(bt_mesh_prov_link.auth + bt_mesh_prov->static_val_len, 0, + auth_size - bt_mesh_prov->static_val_len); + } + } +} + +static void send_confirm(void) +{ + PROV_BUF(cfm, PDU_LEN_CONFIRM); + uint8_t auth_size = bt_mesh_prov_auth_size_get(); + uint8_t *inputs = (uint8_t *)&bt_mesh_prov_link.conf_inputs; + uint8_t conf_key_input[64]; + + LOG_DBG("ConfInputs[0] %s", bt_hex(inputs, 32)); + LOG_DBG("ConfInputs[32] %s", bt_hex(&inputs[32], 32)); + LOG_DBG("ConfInputs[64] %s", bt_hex(&inputs[64], 32)); + LOG_DBG("ConfInputs[96] %s", bt_hex(&inputs[96], 32)); + LOG_DBG("ConfInputs[128] %s", bt_hex(&inputs[128], 17)); + + if (bt_mesh_prov_conf_salt(bt_mesh_prov_link.algorithm, inputs, + bt_mesh_prov_link.conf_salt)) + { + LOG_ERR("Unable to generate confirmation salt"); + prov_fail(PROV_ERR_UNEXP_ERR); + return; + } + + LOG_DBG("ConfirmationSalt: %s", bt_hex(bt_mesh_prov_link.conf_salt, + auth_size)); + + memcpy(conf_key_input, bt_mesh_prov_link.dhkey, 32); + + if (IS_ENABLED(CONFIG_BT_MESH_ECDH_P256_HMAC_SHA256_AES_CCM) && + bt_mesh_prov_link.algorithm == BT_MESH_PROV_AUTH_HMAC_SHA256_AES_CCM) + { + memcpy(&conf_key_input[32], bt_mesh_prov_link.auth, 32); + LOG_DBG("AuthValue %s", bt_hex(bt_mesh_prov_link.auth, 32)); + } + + if (bt_mesh_prov_conf_key(bt_mesh_prov_link.algorithm, conf_key_input, + bt_mesh_prov_link.conf_salt, + bt_mesh_prov_link.conf_key)) + { + LOG_ERR("Unable to generate confirmation key"); + prov_fail(PROV_ERR_UNEXP_ERR); + return; + } + + LOG_DBG("ConfirmationKey: %s", bt_hex(bt_mesh_prov_link.conf_key, + auth_size)); + + if (bt_rand(bt_mesh_prov_link.rand, auth_size)) + { + LOG_ERR("Unable to generate random number"); + prov_fail(PROV_ERR_UNEXP_ERR); + return; + } + + LOG_DBG("LocalRandom: %s", bt_hex(bt_mesh_prov_link.rand, auth_size)); + + bt_mesh_prov_buf_init(&cfm, PROV_CONFIRM); + + if (bt_mesh_prov_conf(bt_mesh_prov_link.algorithm, + bt_mesh_prov_link.conf_key, + bt_mesh_prov_link.rand, bt_mesh_prov_link.auth, + net_buf_simple_add(&cfm, auth_size))) + { + LOG_ERR("Unable to generate confirmation value"); + prov_fail(PROV_ERR_UNEXP_ERR); + return; + } + + if (bt_mesh_prov_send(&cfm, NULL)) + { + LOG_ERR("Failed to send Provisioning Confirm"); + return; + } + + bt_mesh_prov_link.expect = PROV_RANDOM; +} + +static void send_input_complete(void) +{ + PROV_BUF(buf, PDU_LEN_INPUT_COMPLETE); + + bt_mesh_prov_buf_init(&buf, PROV_INPUT_COMPLETE); + if (bt_mesh_prov_send(&buf, NULL)) + { + LOG_ERR("Failed to send Provisioning Input Complete"); + } + + bt_mesh_prov_link.expect = PROV_CONFIRM; +} + +static void public_key_sent(int err, void *cb_data) +{ + atomic_set_bit(bt_mesh_prov_link.flags, PUB_KEY_SENT); + + if (atomic_test_bit(bt_mesh_prov_link.flags, INPUT_COMPLETE)) + { + send_input_complete(); + return; + } +} + +static void start_auth(void) +{ + if (atomic_test_bit(bt_mesh_prov_link.flags, WAIT_NUMBER) || + atomic_test_bit(bt_mesh_prov_link.flags, WAIT_STRING)) + { + bt_mesh_prov_link.expect = PROV_NO_PDU; /* Wait for input */ + } + else + { + bt_mesh_prov_link.expect = PROV_CONFIRM; + } +} + +static void send_pub_key(void) +{ + PROV_BUF(buf, PDU_LEN_PUB_KEY); + const uint8_t *key; + + key = bt_mesh_pub_key_get(); + if (!key) + { + LOG_ERR("No public key available"); + prov_fail(PROV_ERR_UNEXP_ERR); + return; + } + + bt_mesh_prov_buf_init(&buf, PROV_PUB_KEY); + net_buf_simple_add_mem(&buf, key, PUB_KEY_SIZE); + LOG_DBG("Local Public Key: %s", bt_hex(buf.data + 1, PUB_KEY_SIZE)); + + /* PublicKeyDevice */ + + memcpy(bt_mesh_prov_link.conf_inputs.pub_key_device, &buf.data[1], + PDU_LEN_PUB_KEY); + + if (bt_mesh_prov_send(&buf, public_key_sent)) + { + LOG_ERR("Failed to send Public Key"); + return; + } + + start_auth(); +} + +static void prov_dh_key_gen(void) +{ + const uint8_t *remote_pub_key; + const uint8_t *remote_priv_key; + + remote_pub_key = bt_mesh_prov_link.conf_inputs.pub_key_provisioner; + + if (IS_ENABLED(CONFIG_BT_MESH_PROV_OOB_PUBLIC_KEY) && + atomic_test_bit(bt_mesh_prov_link.flags, OOB_PUB_KEY)) + { + remote_priv_key = bt_mesh_prov->private_key_be; + } + else + { + remote_priv_key = NULL; + } + + if (bt_mesh_dhkey_gen(remote_pub_key, remote_priv_key, + bt_mesh_prov_link.dhkey)) + { + LOG_ERR("Failed to generate DHKey"); + prov_fail(PROV_ERR_UNEXP_ERR); + return; + } + + LOG_DBG("DHkey: %s", bt_hex(bt_mesh_prov_link.dhkey, DH_KEY_SIZE)); + + if (atomic_test_bit(bt_mesh_prov_link.flags, OOB_PUB_KEY)) + { + start_auth(); + } + else + { + send_pub_key(); + } +} + +static void prov_dh_key_gen_handler(struct k_work *work) +{ + prov_dh_key_gen(); +} + +static K_WORK_DEFINE(dh_gen_work, prov_dh_key_gen_handler); + +static void prov_pub_key(const uint8_t *data) +{ + LOG_DBG("Remote Public Key: %s", bt_hex(data, PUB_KEY_SIZE)); + + /* PublicKeyProvisioner */ + + memcpy(bt_mesh_prov_link.conf_inputs.pub_key_provisioner, data, + PDU_LEN_PUB_KEY); + + if (IS_ENABLED(CONFIG_BT_MESH_PROV_OOB_PUBLIC_KEY) && + atomic_test_bit(bt_mesh_prov_link.flags, OOB_PUB_KEY)) + { + if (!bt_mesh_prov->public_key_be || !bt_mesh_prov->private_key_be) + { + LOG_ERR("Public or private key is not ready"); + prov_fail(PROV_ERR_UNEXP_ERR); + return; + } + + if (!memcmp(bt_mesh_prov->public_key_be, + bt_mesh_prov_link.conf_inputs.pub_key_provisioner, + PDU_LEN_PUB_KEY)) + { + LOG_ERR("Public keys are identical"); + prov_fail(PROV_ERR_NVAL_FMT); + return; + } + + /* No swap needed since user provides public key in big-endian */ + + memcpy(bt_mesh_prov_link.conf_inputs.pub_key_device, + bt_mesh_prov->public_key_be, + PDU_LEN_PUB_KEY); + } + + bt_long_wq_submit(&dh_gen_work); +} + +static void notify_input_complete(void) +{ + if (atomic_test_and_clear_bit(bt_mesh_prov_link.flags, + NOTIFY_INPUT_COMPLETE) && + bt_mesh_prov->input_complete) + { + bt_mesh_prov->input_complete(); + } +} + +static void send_random(void) +{ + PROV_BUF(rnd, PDU_LEN_RANDOM); + + bt_mesh_prov_buf_init(&rnd, PROV_RANDOM); + net_buf_simple_add_mem(&rnd, bt_mesh_prov_link.rand, + bt_mesh_prov_auth_size_get()); + + if (bt_mesh_prov_send(&rnd, NULL)) + { + LOG_ERR("Failed to send Provisioning Random"); + return; + } + + bt_mesh_prov_link.expect = PROV_DATA; +} + +static void prov_random(const uint8_t *data) +{ + uint8_t rand_size = bt_mesh_prov_auth_size_get(); + uint8_t conf_verify[PROV_AUTH_MAX_LEN]; + + LOG_DBG("Remote Random: %s", bt_hex(data, rand_size)); + if (!memcmp(data, bt_mesh_prov_link.rand, rand_size)) + { + LOG_ERR("Random value is identical to ours, rejecting."); + prov_fail(PROV_ERR_CFM_FAILED); + return; + } + + if (bt_mesh_prov_conf(bt_mesh_prov_link.algorithm, + bt_mesh_prov_link.conf_key, data, + bt_mesh_prov_link.auth, conf_verify)) + { + LOG_ERR("Unable to calculate confirmation verification"); + prov_fail(PROV_ERR_UNEXP_ERR); + return; + } + + if (memcmp(conf_verify, bt_mesh_prov_link.conf, rand_size)) + { + LOG_ERR("Invalid confirmation value"); + LOG_DBG("Received: %s", bt_hex(bt_mesh_prov_link.conf, rand_size)); + LOG_DBG("Calculated: %s", bt_hex(conf_verify, rand_size)); + prov_fail(PROV_ERR_CFM_FAILED); + return; + } + + if (bt_mesh_prov_salt(bt_mesh_prov_link.algorithm, + bt_mesh_prov_link.conf_salt, data, + bt_mesh_prov_link.rand, bt_mesh_prov_link.prov_salt)) + { + LOG_ERR("Failed to generate provisioning salt"); + prov_fail(PROV_ERR_UNEXP_ERR); + return; + } + + LOG_DBG("ProvisioningSalt: %s", bt_hex(bt_mesh_prov_link.prov_salt, 16)); + + send_random(); +} + +static void prov_confirm(const uint8_t *data) +{ + uint8_t conf_size = bt_mesh_prov_auth_size_get(); + + LOG_DBG("Remote Confirm: %s", bt_hex(data, conf_size)); + + memcpy(bt_mesh_prov_link.conf, data, conf_size); + notify_input_complete(); + + send_confirm(); +} + +static bool is_pb_gatt(void) +{ + return bt_mesh_prov_link.bearer && + bt_mesh_prov_link.bearer->type == BT_MESH_PROV_GATT; +} + +static bool is_pb_adv(void) +{ + return bt_mesh_prov_link.bearer && + bt_mesh_prov_link.bearer->type == BT_MESH_PROV_ADV; +} + +static bool is_pb_remote(void) +{ + return bt_mesh_prov_link.bearer && + bt_mesh_prov_link.bearer->type == BT_MESH_PROV_REMOTE; +} + +static bool refresh_is_valid(const uint8_t *netkey, + uint16_t net_idx, uint32_t iv_index) +{ + enum bt_mesh_rpr_node_refresh proc = bt_mesh_node_refresh_get(); + struct bt_mesh_subnet *sub = bt_mesh_subnet_get(net_idx); + uint16_t old_addr = bt_mesh_primary_addr(); + bool valid_addr; + + if (iv_index != bt_mesh.iv_index) + { + LOG_ERR("Invalid IV index"); + return false; + } + + if (!sub || bt_mesh_key_compare(netkey, + &sub->keys[SUBNET_KEY_TX_IDX(sub)].net)) + { + LOG_ERR("Invalid netkey"); + return false; + } + + if (proc == BT_MESH_RPR_NODE_REFRESH_ADDR) + { + valid_addr = bt_mesh_prov_link.addr < old_addr || + bt_mesh_prov_link.addr >= old_addr + + bt_mesh_comp_get()->elem_count; + } + else + { + valid_addr = bt_mesh_prov_link.addr == bt_mesh_primary_addr(); + } + + if (!valid_addr) + { + LOG_ERR("Invalid address"); + } + + return valid_addr; +} + +static void prov_data(const uint8_t *data) +{ + PROV_BUF(msg, PDU_LEN_COMPLETE); + struct bt_mesh_key session_key; + uint8_t nonce[13]; + uint8_t dev_key[16]; + uint8_t pdu[25]; + uint8_t flags; + uint32_t iv_index; + uint16_t net_idx; + int err; + bool identity_enable; + + LOG_DBG(""); + + err = bt_mesh_session_key(bt_mesh_prov_link.dhkey, + bt_mesh_prov_link.prov_salt, &session_key); + if (err) + { + LOG_ERR("Unable to generate session key"); + prov_fail(PROV_ERR_UNEXP_ERR); + return; + } + + err = bt_mesh_prov_nonce(bt_mesh_prov_link.dhkey, + bt_mesh_prov_link.prov_salt, nonce); + if (err) + { + LOG_ERR("Unable to generate session nonce"); + prov_fail(PROV_ERR_UNEXP_ERR); + goto session_key_destructor; + } + + LOG_DBG("Nonce: %s", bt_hex(nonce, 13)); + + err = bt_mesh_prov_decrypt(&session_key, nonce, data, pdu); + if (err) + { + LOG_ERR("Unable to decrypt provisioning data"); + prov_fail(PROV_ERR_DECRYPT); + goto session_key_destructor; + } + + err = bt_mesh_dev_key(bt_mesh_prov_link.dhkey, bt_mesh_prov_link.prov_salt, + dev_key); + if (err) + { + LOG_ERR("Unable to generate device key"); + prov_fail(PROV_ERR_UNEXP_ERR); + goto session_key_destructor; + } + + net_idx = sys_get_be16(&pdu[16]); + flags = pdu[18]; + iv_index = sys_get_be32(&pdu[19]); + bt_mesh_prov_link.addr = sys_get_be16(&pdu[23]); + + if (IS_ENABLED(CONFIG_BT_MESH_RPR_SRV) && + atomic_test_bit(bt_mesh_prov_link.flags, REPROVISION) && + !refresh_is_valid(pdu, net_idx, iv_index)) + { + prov_send_fail_msg(PROV_ERR_INVALID_DATA); + goto session_key_destructor; + } + + LOG_DBG("net_idx %u iv_index 0x%08x, addr 0x%04x", net_idx, iv_index, + bt_mesh_prov_link.addr); + + bt_mesh_prov_buf_init(&msg, PROV_COMPLETE); + if (bt_mesh_prov_send(&msg, NULL)) + { + LOG_ERR("Failed to send Provisioning Complete"); + goto session_key_destructor; + } + + /* Ignore any further PDUs on this link */ + + bt_mesh_prov_link.expect = PROV_NO_PDU; + atomic_set_bit(bt_mesh_prov_link.flags, COMPLETE); + + if (IS_ENABLED(CONFIG_BT_MESH_RPR_SRV) && + atomic_test_bit(bt_mesh_prov_link.flags, REPROVISION)) + { + bt_mesh_dev_key_cand(dev_key); + goto session_key_destructor; + } + + /* Store info, since bt_mesh_provision() will end up clearing it */ + + if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY)) + { + identity_enable = is_pb_gatt(); + } + else + { + identity_enable = false; + } + + err = bt_mesh_provision(pdu, net_idx, flags, iv_index, + bt_mesh_prov_link.addr, dev_key); + if (err) + { + LOG_ERR("Failed to provision (err %d)", err); + goto session_key_destructor; + } + + /* After PB-GATT provisioning we should start advertising + * using Node Identity. + */ + + if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY) && identity_enable) + { + bt_mesh_proxy_identity_enable(); + } + +session_key_destructor: + bt_mesh_key_destroy(&session_key); +} + +static void reprovision_complete(void) +{ + bt_mesh_reprovision(bt_mesh_prov_link.addr); + + /* When performing the refresh composition procedure, + * the device key will be activated after the first successful decryption + * with the new key. + */ + + if (bt_mesh_node_refresh_get() == BT_MESH_RPR_NODE_REFRESH_ADDR) + { + bt_mesh_dev_key_cand_activate(); + } + + if (bt_mesh_prov->reprovisioned) + { + bt_mesh_prov->reprovisioned(bt_mesh_primary_addr()); + } +} + +static void reprovision_fail(void) +{ + bt_mesh_dev_key_cand_remove(); +} + +static void local_input_complete(void) +{ + if (atomic_test_bit(bt_mesh_prov_link.flags, PUB_KEY_SENT) || + atomic_test_bit(bt_mesh_prov_link.flags, OOB_PUB_KEY)) + { + send_input_complete(); + } + else + { + atomic_set_bit(bt_mesh_prov_link.flags, INPUT_COMPLETE); + } +} + +static bt_mesh_prov_bearer_t active_bearers; + +static void prov_link_closed(enum prov_bearer_link_status status) +{ + if (IS_ENABLED(CONFIG_BT_MESH_RPR_SRV) && + atomic_test_bit(bt_mesh_prov_link.flags, REPROVISION)) + { + if (atomic_test_bit(bt_mesh_prov_link.flags, COMPLETE) && + status == PROV_BEARER_LINK_STATUS_SUCCESS) + { + reprovision_complete(); + } + else + { + reprovision_fail(); + } + } + else if (bt_mesh_prov_link.conf_inputs.invite[0]) + { + /* Disable Attention Timer if it was set */ + + bt_mesh_attention(NULL, 0); + } + + bt_mesh_prov_reset_state(); + + /* Restore provisioning bearers stopped in prov_link_opened. If the device + * was provisioned, only re-enable the PB-Remote bearer. + */ + + if (IS_ENABLED(CONFIG_BT_MESH_RPR_SRV) && !is_pb_remote() && + (active_bearers & BT_MESH_PROV_REMOTE)) + { + pb_remote_srv.link_accept(bt_mesh_prov_bearer_cb_get(), NULL); + } + + if (bt_mesh_is_provisioned()) + { + return; + } + + if (IS_ENABLED(CONFIG_BT_MESH_PB_GATT) && !is_pb_gatt() && + (active_bearers & BT_MESH_PROV_GATT)) + { + bt_mesh_pb_gatt.link_accept(bt_mesh_prov_bearer_cb_get(), NULL); + } + + if (IS_ENABLED(CONFIG_BT_MESH_PB_ADV) && !is_pb_adv() && + (active_bearers & BT_MESH_PROV_ADV)) + { + bt_mesh_pb_adv.link_accept(bt_mesh_prov_bearer_cb_get(), NULL); + } +} + +static void prov_link_opened(void) +{ + bt_mesh_prov_link.expect = PROV_INVITE; + if (IS_ENABLED(CONFIG_BT_MESH_RPR_SRV) && bt_mesh_is_provisioned()) + { + atomic_set_bit(bt_mesh_prov_link.flags, REPROVISION); + } + + /* Stop other provisioning bearers for the duration of the prov link. */ + + if (IS_ENABLED(CONFIG_BT_MESH_PB_GATT) && is_pb_adv() && + (active_bearers & BT_MESH_PROV_GATT)) + { + bt_mesh_pb_gatt.link_cancel(); + } + + if (IS_ENABLED(CONFIG_BT_MESH_PB_ADV) && is_pb_gatt() && + (active_bearers & BT_MESH_PROV_ADV)) + { + bt_mesh_pb_adv.link_cancel(); + } + + if (IS_ENABLED(CONFIG_BT_MESH_RPR_SRV) && !is_pb_remote() && + (active_bearers & BT_MESH_PROV_REMOTE)) + { + pb_remote_srv.link_cancel(); + } +} + +static const struct bt_mesh_prov_role role_device = +{ + .input_complete = local_input_complete, + .link_opened = prov_link_opened, + .link_closed = prov_link_closed, + .error = prov_fail, + .op = + { + [PROV_INVITE] = prov_invite, + [PROV_START] = prov_start, + [PROV_PUB_KEY] = prov_pub_key, + [PROV_CONFIRM] = prov_confirm, + [PROV_RANDOM] = prov_random, + [PROV_DATA] = prov_data, + }, +}; + +int bt_mesh_prov_enable(bt_mesh_prov_bearer_t bearers) +{ + if (IS_ENABLED(CONFIG_BT_MESH_RPR_SRV) && (bearers & BT_MESH_PROV_REMOTE)) + { + pb_remote_srv.link_accept(bt_mesh_prov_bearer_cb_get(), NULL); + + /* Only PB-Remote supports reprovisioning */ + + if (bt_mesh_is_provisioned()) + { + goto role_init; + } + } + else if (bt_mesh_is_provisioned()) + { + return -EALREADY; + } + +#if defined(CONFIG_BT_MESH_PROVISIONEE_LOG_LEVEL) + if (CONFIG_BT_MESH_PROVISIONEE_LOG_LEVEL > 2) + { + struct bt_uuid_128 uuid = + { + .uuid = + { + BT_UUID_TYPE_128 + } + }; + + sys_memcpy_swap(uuid.val, bt_mesh_prov->uuid, 16); + LOG_INF("Device UUID: %s", bt_uuid_str(&uuid.uuid)); + } +#endif + + if (IS_ENABLED(CONFIG_BT_MESH_PB_ADV) && (bearers & BT_MESH_PROV_ADV)) + { + bt_mesh_pb_adv.link_accept(bt_mesh_prov_bearer_cb_get(), NULL); + } + + if (IS_ENABLED(CONFIG_BT_MESH_PB_GATT) && (bearers & BT_MESH_PROV_GATT)) + { + bt_mesh_pb_gatt.link_accept(bt_mesh_prov_bearer_cb_get(), NULL); + } + +role_init: + bt_mesh_prov_link.role = &role_device; + active_bearers |= bearers; + + return 0; +} + +int bt_mesh_prov_disable(bt_mesh_prov_bearer_t bearers) +{ + if (bt_mesh_is_provisioned()) + { + return -EALREADY; + } + + if (bt_mesh_prov_active()) + { + return -EBUSY; + } + + if (IS_ENABLED(CONFIG_BT_MESH_PB_ADV) && (bearers & BT_MESH_PROV_ADV)) + { + bt_mesh_pb_adv.link_cancel(); + } + + if (IS_ENABLED(CONFIG_BT_MESH_PB_GATT) && (bearers & BT_MESH_PROV_GATT)) + { + bt_mesh_pb_gatt.link_cancel(); + } + + if (IS_ENABLED(CONFIG_BT_MESH_RPR_SRV) && (bearers & BT_MESH_PROV_REMOTE)) + { + pb_remote_srv.link_cancel(); + } + + active_bearers &= ~bearers; + + return 0; +} + +int bt_mesh_provisionee_suspend(void) +{ + int err; + + if (bt_mesh_is_provisioned()) + { + /* When provisioned, only PB-Remote may be active (reprovisioning). */ + + if (IS_ENABLED(CONFIG_BT_MESH_RPR_SRV) && + (active_bearers & BT_MESH_PROV_REMOTE)) + { + pb_remote_srv.link_cancel(); + } + + return 0; + } + + /* Suspend only previously requested bearers (link_cancel / disable). */ + + if (IS_ENABLED(CONFIG_BT_MESH_PB_GATT) && + (active_bearers & BT_MESH_PROV_GATT)) + { + err = bt_mesh_pb_gatt_srv_disable(); + if (err && err != -EALREADY) + { + LOG_WRN("Disabling PB-GATT failed (err %d)", err); + return err; + } + } + + if (IS_ENABLED(CONFIG_BT_MESH_PB_ADV) && + (active_bearers & BT_MESH_PROV_ADV)) + { + bt_mesh_pb_adv.link_cancel(); + } + + if (IS_ENABLED(CONFIG_BT_MESH_RPR_SRV) && + (active_bearers & BT_MESH_PROV_REMOTE)) + { + pb_remote_srv.link_cancel(); + } + + return 0; +} + +int bt_mesh_provisionee_resume(void) +{ + int err; + + if (bt_mesh_is_provisioned()) + { + /* When provisioned, only re-enable PB-Remote if it was active. */ + + if (IS_ENABLED(CONFIG_BT_MESH_RPR_SRV) && + (active_bearers & BT_MESH_PROV_REMOTE)) + { + pb_remote_srv.link_accept(bt_mesh_prov_bearer_cb_get(), NULL); + } + + return 0; + } + + /* Re-enable only previously requested bearers (link_accept / enable). */ + + if (IS_ENABLED(CONFIG_BT_MESH_PB_GATT) && + (active_bearers & BT_MESH_PROV_GATT)) + { + err = bt_mesh_pb_gatt_srv_enable(); + if (err) + { + LOG_WRN("Re-enabling PB-GATT failed (err %d)", err); + return err; + } + } + + if (IS_ENABLED(CONFIG_BT_MESH_PB_ADV) && + (active_bearers & BT_MESH_PROV_ADV)) + { + bt_mesh_pb_adv.link_accept(bt_mesh_prov_bearer_cb_get(), NULL); + } + + if (IS_ENABLED(CONFIG_BT_MESH_RPR_SRV) && + (active_bearers & BT_MESH_PROV_REMOTE)) + { + pb_remote_srv.link_accept(bt_mesh_prov_bearer_cb_get(), NULL); + } + + return 0; +} diff --git a/libs/zblue/mesh/provisioner.c b/libs/zblue/mesh/provisioner.c new file mode 100644 index 0000000000000..339ec534975bf --- /dev/null +++ b/libs/zblue/mesh/provisioner.c @@ -0,0 +1,1130 @@ +/**************************************************************************** + * libs/zblue/mesh/provisioner.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * Copyright (c) 2020 Lingao Meng Copyright (c) 2021 Manulytica Limited + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "common/bt_str.h" +#include "../host/long_wq.h" + +#include "crypto.h" +#include "mesh.h" +#include "net.h" +#include "rpl.h" +#include "beacon.h" +#include "access.h" +#include "foundation.h" +#include "proxy.h" +#include "prov.h" +#include "settings.h" +#include "provisioner.h" + +/* Timeout for receiving the link open response */ +#define LINK_ESTABLISHMENT_TIMEOUT 60 + +#define LOG_LEVEL CONFIG_BT_MESH_PROVISIONER_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_provisioner); + +static struct +{ + struct bt_mesh_cdb_node *node; + uint16_t net_idx; + uint8_t elem_count; + uint8_t attention_duration; + uint8_t uuid[16]; + uint8_t new_dev_key[16]; +} provisionee; + +static void send_pub_key(void); +static void prov_dh_key_gen(void); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int reset_state(void) +{ + if (!atomic_test_bit(bt_mesh_prov_link.flags, REPROVISION) && + provisionee.node != NULL) + { + bt_mesh_cdb_node_del(provisionee.node, false); + } + + return bt_mesh_prov_reset_state(); +} + +static void prov_link_close(enum prov_bearer_link_status status) +{ + LOG_DBG("%u", status); + bt_mesh_prov_link.expect = PROV_NO_PDU; + + bt_mesh_prov_link.bearer->link_close(status); +} + +static void prov_fail(uint8_t reason) +{ + /* According to MshPRTv1.1: 5.4.4, the + * provisioner just closes the link when something fails, while the + * provisionee sends the fail message, and waits for the provisioner to + * close the link. + */ + + prov_link_close(PROV_BEARER_LINK_STATUS_FAIL); +} + +static void send_invite(void) +{ + PROV_BUF(inv, PDU_LEN_INVITE); + + LOG_DBG(""); + + bt_mesh_prov_buf_init(&inv, PROV_INVITE); + net_buf_simple_add_u8(&inv, provisionee.attention_duration); + + memcpy(bt_mesh_prov_link.conf_inputs.invite, + &provisionee.attention_duration, + PDU_LEN_INVITE); + + if (bt_mesh_prov_send(&inv, NULL)) + { + LOG_ERR("Failed to send invite"); + return; + } + + bt_mesh_prov_link.expect = PROV_CAPABILITIES; +} + +static void start_sent(int err, void *cb_data) +{ + send_pub_key(); +} + +static void send_start(void) +{ + LOG_DBG(""); + + PROV_BUF(start, PDU_LEN_START); + + bool oob_pub_key = + bt_mesh_prov_link.conf_inputs.capabilities[3] == PUB_KEY_OOB; + + bt_mesh_prov_buf_init(&start, PROV_START); + net_buf_simple_add_u8(&start, bt_mesh_prov_link.algorithm); + + if (atomic_test_bit(bt_mesh_prov_link.flags, REMOTE_PUB_KEY) && + oob_pub_key) + { + net_buf_simple_add_u8(&start, PUB_KEY_OOB); + atomic_set_bit(bt_mesh_prov_link.flags, OOB_PUB_KEY); + } + else + { + net_buf_simple_add_u8(&start, PUB_KEY_NO_OOB); + } + + net_buf_simple_add_u8(&start, bt_mesh_prov_link.oob_method); + + net_buf_simple_add_u8(&start, bt_mesh_prov_link.oob_action); + + net_buf_simple_add_u8(&start, bt_mesh_prov_link.oob_size); + + memcpy(bt_mesh_prov_link.conf_inputs.start, &start.data[1], PDU_LEN_START); + + if (bt_mesh_prov_auth(true, bt_mesh_prov_link.oob_method, + bt_mesh_prov_link.oob_action, + bt_mesh_prov_link.oob_size) < 0) + { + LOG_ERR("Invalid authentication method: 0x%02x; " + "action: 0x%02x; size: 0x%02x", + bt_mesh_prov_link.oob_method, bt_mesh_prov_link.oob_action, + bt_mesh_prov_link.oob_size); + return; + } + + if (bt_mesh_prov_send(&start, start_sent)) + { + LOG_ERR("Failed to send Provisioning Start"); + return; + } +} + +static bool prov_check_method(struct bt_mesh_dev_capabilities *caps) +{ + if (bt_mesh_prov_link.oob_method == AUTH_METHOD_STATIC) + { + if (!caps->oob_type) + { + LOG_WRN("Device not support OOB static authentication " + "provisioning"); + return false; + } + } + else if (bt_mesh_prov_link.oob_method == AUTH_METHOD_INPUT) + { + if (bt_mesh_prov_link.oob_size > caps->input_size) + { + LOG_WRN("The required input length (0x%02x) " + "exceeds the device capacity (0x%02x)", + bt_mesh_prov_link.oob_size, caps->input_size); + return false; + } + + if (!(BIT(bt_mesh_prov_link.oob_action) & caps->input_actions)) + { + LOG_WRN("The required input action (0x%04x) " + "not supported by the device (0x%02x)", + (uint16_t)BIT(bt_mesh_prov_link.oob_action), + caps->input_actions); + return false; + } + + if (bt_mesh_prov_link.oob_action == INPUT_OOB_STRING) + { + if (!bt_mesh_prov->output_string) + { + LOG_WRN("Not support output string"); + return false; + } + } + else + { +#if defined CONFIG_BT_MESH_PROV_OOB_API_LEGACY + bool no_output = !bt_mesh_prov->output_number; +#else + bool no_output = !bt_mesh_prov->output_numeric; +#endif + + if (no_output) + { + LOG_WRN("Not support output numeric"); + return false; + } + } + } + else if (bt_mesh_prov_link.oob_method == AUTH_METHOD_OUTPUT) + { + if (bt_mesh_prov_link.oob_size > caps->output_size) + { + LOG_WRN("The required output length (0x%02x) " + "exceeds the device capacity (0x%02x)", + bt_mesh_prov_link.oob_size, caps->output_size); + return false; + } + + if (!(BIT(bt_mesh_prov_link.oob_action) & caps->output_actions)) + { + LOG_WRN("The required output action (0x%04x) " + "not supported by the device (0x%02x)", + (uint16_t)BIT(bt_mesh_prov_link.oob_action), + caps->output_actions); + return false; + } + + if (!bt_mesh_prov->input) + { + LOG_WRN("Not support input"); + return false; + } + } + + return true; +} + +static void prov_capabilities(const uint8_t *data) +{ + struct bt_mesh_dev_capabilities caps; + + caps.elem_count = data[0]; + LOG_DBG("Elements: %u", caps.elem_count); + + caps.algorithms = sys_get_be16(&data[1]); + LOG_DBG("Algorithms: 0x%02x", caps.algorithms); + + bool is_aes128 = false; + bool is_sha256 = false; + + if ((caps.algorithms & BIT(BT_MESH_PROV_AUTH_CMAC_AES128_AES_CCM)) && + IS_ENABLED(CONFIG_BT_MESH_ECDH_P256_CMAC_AES128_AES_CCM)) + { + is_aes128 = true; + } + + if ((caps.algorithms & BIT(BT_MESH_PROV_AUTH_HMAC_SHA256_AES_CCM)) && + IS_ENABLED(CONFIG_BT_MESH_ECDH_P256_HMAC_SHA256_AES_CCM)) + { + is_sha256 = true; + } + + if (!(is_sha256 || is_aes128)) + { + LOG_ERR("Invalid encryption algorithm"); + prov_fail(PROV_ERR_NVAL_FMT); + return; + } + + caps.pub_key_type = data[3]; + caps.oob_type = data[4]; + caps.output_size = data[5]; + LOG_DBG("Public Key Type: 0x%02x", caps.pub_key_type); + LOG_DBG("Static OOB Type: 0x%02x", caps.oob_type); + LOG_DBG("Output OOB Size: %u", caps.output_size); + + caps.output_actions = (bt_mesh_output_action_t)(sys_get_be16(&data[6])); + caps.input_size = data[8]; + caps.input_actions = (bt_mesh_input_action_t)(sys_get_be16(&data[9])); + LOG_DBG("Output OOB Action: 0x%04x", caps.output_actions); + LOG_DBG("Input OOB Size: %u", caps.input_size); + LOG_DBG("Input OOB Action: 0x%04x", caps.input_actions); + + provisionee.elem_count = caps.elem_count; + if (provisionee.elem_count == 0) + { + LOG_ERR("Invalid number of elements"); + prov_fail(PROV_ERR_NVAL_FMT); + return; + } + + if (caps.oob_type & BT_MESH_OOB_AUTH_REQUIRED) + { + bool oob_availability = caps.output_size > 0 || caps.input_size > 0 || + (caps.oob_type & BT_MESH_STATIC_OOB_AVAILABLE); + + if (!oob_availability && !is_sha256) + { + LOG_ERR("Invalid capabilities for OOB authentication"); + prov_fail(PROV_ERR_NVAL_FMT); + return; + } + } + + bt_mesh_prov_link.algorithm = + is_sha256 ? BT_MESH_PROV_AUTH_HMAC_SHA256_AES_CCM : + BT_MESH_PROV_AUTH_CMAC_AES128_AES_CCM; + + if (atomic_test_bit(bt_mesh_prov_link.flags, REPROVISION)) + { + if (!bt_mesh_prov_link.addr) + { + bt_mesh_prov_link.addr = + bt_mesh_cdb_free_addr_get(provisionee.elem_count); + if (!bt_mesh_prov_link.addr) + { + LOG_ERR("Failed allocating address for node"); + prov_fail(PROV_ERR_ADDR); + return; + } + } + } + else + { + provisionee.node = bt_mesh_cdb_node_alloc(provisionee.uuid, + bt_mesh_prov_link.addr, + provisionee.elem_count, + provisionee.net_idx); + if (provisionee.node == NULL) + { + LOG_ERR("Failed allocating node 0x%04x", bt_mesh_prov_link.addr); + prov_fail(PROV_ERR_RESOURCES); + return; + } + + /* Address might change in the alloc call */ + + bt_mesh_prov_link.addr = provisionee.node->addr; + } + + memcpy(bt_mesh_prov_link.conf_inputs.capabilities, data, + PDU_LEN_CAPABILITIES); + + if (bt_mesh_prov->capabilities) + { + bt_mesh_prov->capabilities(&caps); + } + + if (!prov_check_method(&caps)) + { + prov_fail(PROV_ERR_UNEXP_ERR); + return; + } + + send_start(); +} + +static void send_confirm(void) +{ + PROV_BUF(cfm, PDU_LEN_CONFIRM); + uint8_t auth_size = bt_mesh_prov_auth_size_get(); + uint8_t *inputs = (uint8_t *)&bt_mesh_prov_link.conf_inputs; + uint8_t conf_key_input[64]; + + LOG_DBG("ConfInputs[0] %s", bt_hex(inputs, 32)); + LOG_DBG("ConfInputs[32] %s", bt_hex(&inputs[32], 32)); + LOG_DBG("ConfInputs[64] %s", bt_hex(&inputs[64], 32)); + LOG_DBG("ConfInputs[96] %s", bt_hex(&inputs[96], 32)); + LOG_DBG("ConfInputs[128] %s", bt_hex(&inputs[128], 17)); + + if (bt_mesh_prov_conf_salt(bt_mesh_prov_link.algorithm, inputs, + bt_mesh_prov_link.conf_salt)) + { + LOG_ERR("Unable to generate confirmation salt"); + prov_fail(PROV_ERR_UNEXP_ERR); + return; + } + + LOG_DBG("ConfirmationSalt: %s", bt_hex(bt_mesh_prov_link.conf_salt, + auth_size)); + + memcpy(conf_key_input, bt_mesh_prov_link.dhkey, 32); + + if (bt_mesh_prov_link.algorithm == BT_MESH_PROV_AUTH_HMAC_SHA256_AES_CCM && + IS_ENABLED(CONFIG_BT_MESH_ECDH_P256_HMAC_SHA256_AES_CCM)) + { + memcpy(&conf_key_input[32], bt_mesh_prov_link.auth, 32); + LOG_DBG("AuthValue %s", bt_hex(bt_mesh_prov_link.auth, 32)); + } + + if (bt_mesh_prov_conf_key(bt_mesh_prov_link.algorithm, conf_key_input, + bt_mesh_prov_link.conf_salt, + bt_mesh_prov_link.conf_key)) + { + LOG_ERR("Unable to generate confirmation key"); + prov_fail(PROV_ERR_UNEXP_ERR); + return; + } + + LOG_DBG("ConfirmationKey: %s", bt_hex(bt_mesh_prov_link.conf_key, + auth_size)); + + if (bt_rand(bt_mesh_prov_link.rand, auth_size)) + { + LOG_ERR("Unable to generate random number"); + prov_fail(PROV_ERR_UNEXP_ERR); + return; + } + + LOG_DBG("LocalRandom: %s", bt_hex(bt_mesh_prov_link.rand, auth_size)); + + bt_mesh_prov_buf_init(&cfm, PROV_CONFIRM); + + if (bt_mesh_prov_conf(bt_mesh_prov_link.algorithm, + bt_mesh_prov_link.conf_key, + bt_mesh_prov_link.rand, bt_mesh_prov_link.auth, + bt_mesh_prov_link.conf)) + { + LOG_ERR("Unable to generate confirmation value"); + prov_fail(PROV_ERR_UNEXP_ERR); + return; + } + + net_buf_simple_add_mem(&cfm, bt_mesh_prov_link.conf, auth_size); + + if (bt_mesh_prov_send(&cfm, NULL)) + { + LOG_ERR("Failed to send Provisioning Confirm"); + return; + } + + bt_mesh_prov_link.expect = PROV_CONFIRM; +} + +static void public_key_sent(int err, void *cb_data) +{ + atomic_set_bit(bt_mesh_prov_link.flags, PUB_KEY_SENT); + + if (atomic_test_bit(bt_mesh_prov_link.flags, OOB_PUB_KEY) && + atomic_test_bit(bt_mesh_prov_link.flags, REMOTE_PUB_KEY)) + { + prov_dh_key_gen(); + return; + } +} + +static void send_pub_key(void) +{ + PROV_BUF(buf, PDU_LEN_PUB_KEY); + const uint8_t *key; + + key = bt_mesh_pub_key_get(); + if (!key) + { + LOG_ERR("No public key available"); + prov_fail(PROV_ERR_UNEXP_ERR); + return; + } + + bt_mesh_prov_buf_init(&buf, PROV_PUB_KEY); + net_buf_simple_add_mem(&buf, key, PUB_KEY_SIZE); + LOG_DBG("Local Public Key: %s", bt_hex(buf.data + 1, PUB_KEY_SIZE)); + + /* PublicKeyProvisioner */ + + memcpy(bt_mesh_prov_link.conf_inputs.pub_key_provisioner, &buf.data[1], + PDU_LEN_PUB_KEY); + + if (bt_mesh_prov_send(&buf, public_key_sent)) + { + LOG_ERR("Failed to send Public Key"); + return; + } + + bt_mesh_prov_link.expect = PROV_PUB_KEY; +} + +static void prov_dh_key_gen(void) +{ + const uint8_t *remote_pk; + const uint8_t *local_pk; + + local_pk = bt_mesh_prov_link.conf_inputs.pub_key_provisioner; + remote_pk = bt_mesh_prov_link.conf_inputs.pub_key_device; + + if (!memcmp(local_pk, remote_pk, PUB_KEY_SIZE)) + { + LOG_ERR("Public keys are identical"); + prov_fail(PROV_ERR_NVAL_FMT); + return; + } + + if (bt_mesh_dhkey_gen(remote_pk, NULL, bt_mesh_prov_link.dhkey)) + { + LOG_ERR("Failed to generate DHKey"); + prov_fail(PROV_ERR_UNEXP_ERR); + } + + LOG_DBG("DHkey: %s", bt_hex(bt_mesh_prov_link.dhkey, DH_KEY_SIZE)); + + if (atomic_test_bit(bt_mesh_prov_link.flags, NOTIFY_INPUT_COMPLETE)) + { + bt_mesh_prov_link.expect = PROV_INPUT_COMPLETE; + } + + if (atomic_test_bit(bt_mesh_prov_link.flags, WAIT_STRING) || + atomic_test_bit(bt_mesh_prov_link.flags, WAIT_NUMBER) || + atomic_test_bit(bt_mesh_prov_link.flags, NOTIFY_INPUT_COMPLETE)) + { + atomic_set_bit(bt_mesh_prov_link.flags, WAIT_CONFIRM); + return; + } + + send_confirm(); +} + +static void prov_dh_key_gen_handler(struct k_work *work) +{ + prov_dh_key_gen(); +} + +static K_WORK_DEFINE(dh_gen_work, prov_dh_key_gen_handler); + +static void prov_pub_key(const uint8_t *data) +{ + LOG_DBG("Remote Public Key: %s", bt_hex(data, PUB_KEY_SIZE)); + + atomic_set_bit(bt_mesh_prov_link.flags, REMOTE_PUB_KEY); + + /* PublicKeyDevice */ + + memcpy(bt_mesh_prov_link.conf_inputs.pub_key_device, data, PUB_KEY_SIZE); + bt_mesh_prov_link.bearer->clear_tx(); + + bt_long_wq_submit(&dh_gen_work); +} + +static void notify_input_complete(void) +{ + if (atomic_test_and_clear_bit(bt_mesh_prov_link.flags, + NOTIFY_INPUT_COMPLETE) && + bt_mesh_prov->input_complete) + { + bt_mesh_prov->input_complete(); + } +} + +static void prov_input_complete(const uint8_t *data) +{ + LOG_DBG(""); + + notify_input_complete(); + + if (atomic_test_and_clear_bit(bt_mesh_prov_link.flags, WAIT_CONFIRM)) + { + send_confirm(); + } +} + +static void send_prov_data(void) +{ + PROV_BUF(pdu, PDU_LEN_DATA); + struct bt_mesh_cdb_subnet *sub; + uint8_t net_key[16]; + struct bt_mesh_key session_key; + uint8_t nonce[13]; + int err; + + err = bt_mesh_session_key(bt_mesh_prov_link.dhkey, + bt_mesh_prov_link.prov_salt, &session_key); + if (err) + { + LOG_ERR("Unable to generate session key"); + prov_fail(PROV_ERR_UNEXP_ERR); + return; + } + + err = bt_mesh_prov_nonce(bt_mesh_prov_link.dhkey, + bt_mesh_prov_link.prov_salt, nonce); + if (err) + { + LOG_ERR("Unable to generate session nonce"); + prov_fail(PROV_ERR_UNEXP_ERR); + goto session_key_destructor; + } + + LOG_DBG("Nonce: %s", bt_hex(nonce, 13)); + + err = bt_mesh_dev_key(bt_mesh_prov_link.dhkey, bt_mesh_prov_link.prov_salt, + provisionee.new_dev_key); + if (err) + { + LOG_ERR("Unable to generate device key"); + prov_fail(PROV_ERR_UNEXP_ERR); + goto session_key_destructor; + } + + sub = bt_mesh_cdb_subnet_get(provisionee.node->net_idx); + if (sub == NULL) + { + LOG_ERR("No subnet with net_idx %u", provisionee.node->net_idx); + prov_fail(PROV_ERR_UNEXP_ERR); + goto session_key_destructor; + } + + err = + bt_mesh_key_export(net_key, + &sub->keys[SUBNET_KEY_TX_IDX(sub)].net_key); + if (err) + { + LOG_ERR("Unable to export network key"); + prov_fail(PROV_ERR_UNEXP_ERR); + goto session_key_destructor; + } + + bt_mesh_prov_buf_init(&pdu, PROV_DATA); + net_buf_simple_add_mem(&pdu, net_key, sizeof(net_key)); + net_buf_simple_add_be16(&pdu, provisionee.node->net_idx); + net_buf_simple_add_u8(&pdu, bt_mesh_cdb_subnet_flags(sub)); + net_buf_simple_add_be32(&pdu, bt_mesh_cdb.iv_index); + net_buf_simple_add_be16(&pdu, bt_mesh_prov_link.addr); + net_buf_simple_add(&pdu, 8); /* For MIC */ + + LOG_DBG("net_idx %u, iv_index 0x%08x, addr 0x%04x", + provisionee.node->net_idx, bt_mesh.iv_index, + bt_mesh_prov_link.addr); + + err = bt_mesh_prov_encrypt(&session_key, nonce, &pdu.data[1], &pdu.data[ + 1]); + if (err) + { + LOG_ERR("Unable to encrypt provisioning data"); + prov_fail(PROV_ERR_DECRYPT); + goto session_key_destructor; + } + + if (bt_mesh_prov_send(&pdu, NULL)) + { + LOG_ERR("Failed to send Provisioning Data"); + goto session_key_destructor; + } + + bt_mesh_prov_link.expect = PROV_COMPLETE; + +session_key_destructor: + bt_mesh_key_destroy(&session_key); +} + +static void prov_complete(const uint8_t *data) +{ + struct bt_mesh_cdb_node *node = provisionee.node; + + LOG_DBG("key %s, net_idx %u, num_elem %u, addr 0x%04x", + bt_hex(&provisionee.new_dev_key, 16), + node->net_idx, node->num_elem, node->addr); + + atomic_set_bit(bt_mesh_prov_link.flags, COMPLETE); + + prov_link_close(PROV_BEARER_LINK_STATUS_SUCCESS); +} + +static void prov_node_add(void) +{ + LOG_DBG(""); + struct bt_mesh_cdb_node *node = provisionee.node; + int err; + + err = bt_mesh_cdb_node_key_import(node, provisionee.new_dev_key); + if (err) + { + LOG_ERR("Failed to import node device key"); + return; + } + + if (atomic_test_bit(bt_mesh_prov_link.flags, REPROVISION)) + { + bt_mesh_cdb_node_update(node, bt_mesh_prov_link.addr, + provisionee.elem_count); + } + else if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bt_mesh_cdb_node_store(node); + } + + provisionee.node = NULL; + + if (bt_mesh_prov->node_added) + { + bt_mesh_prov->node_added(node->net_idx, node->uuid, node->addr, + node->num_elem); + } +} + +static void send_random(void) +{ + PROV_BUF(rnd, PDU_LEN_RANDOM); + uint8_t rand_size = bt_mesh_prov_auth_size_get(); + + bt_mesh_prov_buf_init(&rnd, PROV_RANDOM); + net_buf_simple_add_mem(&rnd, bt_mesh_prov_link.rand, rand_size); + + if (bt_mesh_prov_send(&rnd, NULL)) + { + LOG_ERR("Failed to send Provisioning Random"); + return; + } + + bt_mesh_prov_link.expect = PROV_RANDOM; +} + +static void prov_random(const uint8_t *data) +{ + uint8_t rand_size = bt_mesh_prov_auth_size_get(); + uint8_t conf_verify[PROV_AUTH_MAX_LEN]; + + LOG_DBG("Remote Random: %s", bt_hex(data, rand_size)); + if (!memcmp(data, bt_mesh_prov_link.rand, rand_size)) + { + LOG_ERR("Random value is identical to ours, rejecting."); + prov_fail(PROV_ERR_CFM_FAILED); + return; + } + + if (bt_mesh_prov_conf(bt_mesh_prov_link.algorithm, + bt_mesh_prov_link.conf_key, data, + bt_mesh_prov_link.auth, conf_verify)) + { + LOG_ERR("Unable to calculate confirmation verification"); + prov_fail(PROV_ERR_UNEXP_ERR); + return; + } + + if (memcmp(conf_verify, bt_mesh_prov_link.conf, rand_size)) + { + LOG_ERR("Invalid confirmation value"); + LOG_DBG("Received: %s", bt_hex(bt_mesh_prov_link.conf, rand_size)); + LOG_DBG("Calculated: %s", bt_hex(conf_verify, rand_size)); + prov_fail(PROV_ERR_CFM_FAILED); + return; + } + + if (bt_mesh_prov_salt(bt_mesh_prov_link.algorithm, + bt_mesh_prov_link.conf_salt, + bt_mesh_prov_link.rand, data, + bt_mesh_prov_link.prov_salt)) + { + LOG_ERR("Failed to generate provisioning salt"); + prov_fail(PROV_ERR_UNEXP_ERR); + return; + } + + LOG_DBG("ProvisioningSalt: %s", bt_hex(bt_mesh_prov_link.prov_salt, 16)); + + send_prov_data(); +} + +static void prov_confirm(const uint8_t *data) +{ + uint8_t conf_size = bt_mesh_prov_auth_size_get(); + + LOG_DBG("Remote Confirm: %s", bt_hex(data, conf_size)); + + if (!memcmp(data, bt_mesh_prov_link.conf, conf_size)) + { + LOG_ERR("Confirm value is identical to ours, rejecting."); + prov_fail(PROV_ERR_CFM_FAILED); + return; + } + + memcpy(bt_mesh_prov_link.conf, data, conf_size); + + send_random(); +} + +static void prov_failed(const uint8_t *data) +{ + LOG_WRN("Error: 0x%02x", data[0]); + prov_link_close(PROV_BEARER_LINK_STATUS_FAIL); +} + +static void local_input_complete(void) +{ + if (atomic_test_and_clear_bit(bt_mesh_prov_link.flags, WAIT_CONFIRM)) + { + send_confirm(); + } +} + +static void prov_link_closed(enum prov_bearer_link_status status) +{ + LOG_DBG(""); + if (atomic_test_bit(bt_mesh_prov_link.flags, COMPLETE)) + { + prov_node_add(); + } + + reset_state(); +} + +static void prov_link_opened(void) +{ + send_invite(); +} + +static const struct bt_mesh_prov_role role_provisioner = +{ + .input_complete = local_input_complete, + .link_opened = prov_link_opened, + .link_closed = prov_link_closed, + .error = prov_fail, + .op = + { + [PROV_CAPABILITIES] = prov_capabilities, + [PROV_PUB_KEY] = prov_pub_key, + [PROV_INPUT_COMPLETE] = prov_input_complete, + [PROV_CONFIRM] = prov_confirm, + [PROV_RANDOM] = prov_random, + [PROV_COMPLETE] = prov_complete, + [PROV_FAILED] = prov_failed, + }, +}; + +static void prov_set_method(uint8_t method, uint8_t action, uint8_t size) +{ + bt_mesh_prov_link.oob_method = method; + bt_mesh_prov_link.oob_action = action; + bt_mesh_prov_link.oob_size = size; +} + +int bt_mesh_auth_method_set_input(bt_mesh_input_action_t action, + uint8_t size) +{ + if (!action || !size || size > PROV_IO_OOB_SIZE_MAX) + { + return -EINVAL; + } + + prov_set_method(AUTH_METHOD_INPUT, fls(action) - 1, size); + return 0; +} + +int bt_mesh_auth_method_set_output(bt_mesh_output_action_t action, + uint8_t size) +{ + if (!action || !size || size > PROV_IO_OOB_SIZE_MAX) + { + return -EINVAL; + } + + prov_set_method(AUTH_METHOD_OUTPUT, fls(action) - 1, size); + return 0; +} + +int bt_mesh_auth_method_set_static(const uint8_t *static_val, uint8_t size) +{ + if (!size || !static_val) + { + return -EINVAL; + } + + prov_set_method(AUTH_METHOD_STATIC, 0, 0); + + /* Trim the Auth if it is longer than required length */ + + memcpy(bt_mesh_prov_link.auth, static_val, + size > PROV_AUTH_MAX_LEN ? PROV_AUTH_MAX_LEN : size); + + /* Pad with zeros if the Auth is shorter the required length */ + + if (size < PROV_AUTH_MAX_LEN) + { + memset(bt_mesh_prov_link.auth + size, 0, PROV_AUTH_MAX_LEN - size); + } + + return 0; +} + +int bt_mesh_auth_method_set_none(void) +{ + prov_set_method(AUTH_METHOD_NO_OOB, 0, 0); + return 0; +} + +int bt_mesh_prov_remote_pub_key_set(const uint8_t public_key[PUB_KEY_SIZE]) +{ + if (public_key == NULL) + { + return -EINVAL; + } + + if (atomic_test_and_set_bit(bt_mesh_prov_link.flags, REMOTE_PUB_KEY)) + { + return -EALREADY; + } + + memcpy(bt_mesh_prov_link.conf_inputs.pub_key_device, public_key, + PDU_LEN_PUB_KEY); + + return 0; +} + +static int link_open(const uint8_t *uuid, const struct prov_bearer *bearer, + uint16_t net_idx, + uint16_t addr, uint8_t attention_duration, + void *bearer_cb_data, + uint8_t timeout) +{ + int err; + + if (atomic_test_and_set_bit(bt_mesh_prov_link.flags, LINK_ACTIVE)) + { + return -EBUSY; + } + + if (uuid) + { + memcpy(provisionee.uuid, uuid, 16); + + struct bt_uuid_128 uuid_repr = + { + .uuid = + { + BT_UUID_TYPE_128 + } + }; + + memcpy(uuid_repr.val, uuid, 16); + LOG_DBG("Provisioning %s", bt_uuid_str(&uuid_repr.uuid)); + } + else + { + atomic_set_bit(bt_mesh_prov_link.flags, REPROVISION); + LOG_DBG("Reprovisioning"); + } + + atomic_set_bit(bt_mesh_prov_link.flags, PROVISIONER); + bt_mesh_prov_link.addr = addr; + bt_mesh_prov_link.bearer = bearer; + bt_mesh_prov_link.role = &role_provisioner; + provisionee.net_idx = net_idx; + provisionee.attention_duration = attention_duration; + + err = bt_mesh_prov_link.bearer->link_open(uuid, timeout, + bt_mesh_prov_bearer_cb_get(), + bearer_cb_data); + if (err) + { + atomic_clear_bit(bt_mesh_prov_link.flags, LINK_ACTIVE); + } + + return err; +} + +#if defined(CONFIG_BT_MESH_PB_ADV) +int bt_mesh_pb_adv_open(const uint8_t uuid[16], uint16_t net_idx, + uint16_t addr, + uint8_t attention_duration) +{ + return link_open(uuid, &bt_mesh_pb_adv, net_idx, addr, attention_duration, + NULL, + LINK_ESTABLISHMENT_TIMEOUT); +} +#endif + +#if defined(CONFIG_BT_MESH_PB_GATT_CLIENT) +int bt_mesh_pb_gatt_open(const uint8_t uuid[16], uint16_t net_idx, + uint16_t addr, + uint8_t attention_duration) +{ + return link_open(uuid, &bt_mesh_pb_gatt, net_idx, addr, attention_duration, + NULL, + LINK_ESTABLISHMENT_TIMEOUT); +} +#endif + +#if defined(CONFIG_BT_MESH_RPR_CLI) +int bt_mesh_pb_remote_open(struct bt_mesh_rpr_cli *cli, + const struct bt_mesh_rpr_node *srv, + const uint8_t uuid[16], uint16_t net_idx, + uint16_t addr) +{ + struct pb_remote_ctx ctx = + { + cli, srv + }; + + return link_open(uuid, &pb_remote_cli, net_idx, addr, 0, &ctx, 0); +} + +/* Remote Provision done where client and server is on same node, skip open + * link and sending of reprovision message, just execute reprovisioning on it + * self. + */ + +static int reprovision_local_client_server(uint16_t addr) +{ + int err; + const uint8_t *pub_key; + const uint8_t *priv_key = NULL; + + if (atomic_test_and_set_bit(bt_mesh_prov_link.flags, LINK_ACTIVE)) + { + return -EBUSY; + } + + LOG_DBG("net_idx %u iv_index 0x%08x, addr 0x%04x", + provisionee.node->net_idx, + bt_mesh_cdb.iv_index, addr); + + atomic_set_bit(bt_mesh_prov_link.flags, REPROVISION); + atomic_set_bit(bt_mesh_prov_link.flags, PROVISIONER); + bt_mesh_prov_link.addr = addr; + bt_mesh_prov_link.bearer = &pb_remote_cli; + bt_mesh_prov_link.role = &role_provisioner; + provisionee.net_idx = provisionee.node->net_idx; + provisionee.attention_duration = 0; + + if (IS_ENABLED(CONFIG_BT_MESH_PROV_OOB_PUBLIC_KEY) && + bt_mesh_prov->public_key_be && + bt_mesh_prov->private_key_be) + { + LOG_DBG("Use OOB Public and Private key"); + pub_key = bt_mesh_prov->public_key_be; + priv_key = bt_mesh_prov->private_key_be; + } + else + { + pub_key = bt_mesh_pub_key_get(); + } + + if (!pub_key) + { + LOG_ERR("No public key available"); + return -ENOEXEC; + } + + if (bt_mesh_dhkey_gen(pub_key, priv_key, bt_mesh_prov_link.dhkey)) + { + LOG_ERR("Failed to generate DHKey"); + return -ENOEXEC; + } + + LOG_DBG("DHkey: %s", bt_hex(bt_mesh_prov_link.dhkey, DH_KEY_SIZE)); + + err = bt_mesh_dev_key(bt_mesh_prov_link.dhkey, bt_mesh_prov_link.prov_salt, + provisionee.new_dev_key); + if (err) + { + LOG_ERR("Unable to generate device key"); + return err; + } + + bt_mesh_dev_key_cand(provisionee.new_dev_key); + + /* Mark the link that was never opened as closed. */ + + atomic_set_bit(bt_mesh_prov_link.flags, COMPLETE); + bt_mesh_reprovision(addr); + bt_mesh_dev_key_cand_activate(); + + if (bt_mesh_prov->reprovisioned) + { + LOG_DBG("Application reprovisioned callback 0x%04x", + bt_mesh_primary_addr()); + bt_mesh_prov->reprovisioned(bt_mesh_primary_addr()); + } + + prov_link_closed(PROV_BEARER_LINK_STATUS_SUCCESS); + return 0; +} + +int bt_mesh_pb_remote_open_node(struct bt_mesh_rpr_cli *cli, + struct bt_mesh_rpr_node *srv, + uint16_t addr, bool composition_change) +{ + struct pb_remote_ctx ctx = + { + cli, srv + }; + + if (srv->addr != addr) + { + ctx.refresh = BT_MESH_RPR_NODE_REFRESH_ADDR; + } + else if (composition_change) + { + ctx.refresh = BT_MESH_RPR_NODE_REFRESH_COMPOSITION; + } + else + { + ctx.refresh = BT_MESH_RPR_NODE_REFRESH_DEVKEY; + } + + provisionee.node = bt_mesh_cdb_node_get(srv->addr); + if (!provisionee.node) + { + LOG_ERR("No CDB node for 0x%04x", srv->addr); + return -ENOENT; + } + + /* Check if server is on same device as client */ + + if (IS_ENABLED(CONFIG_BT_MESH_RPR_SRV) && bt_mesh_has_addr(srv->addr)) + { + return reprovision_local_client_server(addr); + } + + return link_open(NULL, &pb_remote_cli, provisionee.node->net_idx, addr, 0, + &ctx, 0); +} +#endif diff --git a/libs/zblue/mesh/provisioner.h b/libs/zblue/mesh/provisioner.h new file mode 100644 index 0000000000000..f185958949e9f --- /dev/null +++ b/libs/zblue/mesh/provisioner.h @@ -0,0 +1,22 @@ +/**************************************************************************** + * libs/zblue/mesh/provisioner.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * Copyright (c) 2020 Lingao Meng + * + * SPDX-License-Identifier: Apache-2.0 + */ + +int bt_mesh_pb_adv_open(const uint8_t uuid[16], + uint16_t net_idx, + uint16_t addr, + uint8_t attention_duration); + +int bt_mesh_pb_gatt_open(const uint8_t uuid[16], + uint16_t net_idx, + uint16_t addr, + uint8_t attention_duration); diff --git a/libs/zblue/mesh/proxy.h b/libs/zblue/mesh/proxy.h new file mode 100644 index 0000000000000..9ddc9ab0e7dc8 --- /dev/null +++ b/libs/zblue/mesh/proxy.h @@ -0,0 +1,37 @@ +/**************************************************************************** + * libs/zblue/mesh/proxy.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#if defined(CONFIG_BT_MESH_DEBUG_USE_ID_ADDR) +#define ADV_OPT_USE_IDENTITY BT_LE_ADV_OPT_USE_IDENTITY +#else +#define ADV_OPT_USE_IDENTITY 0 +#endif + +#define BT_MESH_ID_TYPE_NET 0x00 +#define BT_MESH_ID_TYPE_NODE 0x01 +#define BT_MESH_ID_TYPE_PRIV_NET 0x02 +#define BT_MESH_ID_TYPE_PRIV_NODE 0x03 + +int bt_mesh_proxy_gatt_enable(void); +int bt_mesh_proxy_gatt_disable(void); +void bt_mesh_proxy_gatt_disconnect(void); + +void bt_mesh_proxy_beacon_send(struct bt_mesh_subnet *sub); + +int bt_mesh_proxy_adv_start(void); + +void bt_mesh_proxy_identity_start(struct bt_mesh_subnet *sub, bool private); +void bt_mesh_proxy_identity_stop(struct bt_mesh_subnet *sub); + +bool bt_mesh_proxy_relay(struct bt_mesh_adv *adv, uint16_t dst); +void bt_mesh_proxy_addr_add(struct net_buf_simple *buf, uint16_t addr); +uint8_t bt_mesh_proxy_srv_connected_cnt(void); diff --git a/libs/zblue/mesh/proxy_cli.c b/libs/zblue/mesh/proxy_cli.c new file mode 100644 index 0000000000000..842d2c4582b53 --- /dev/null +++ b/libs/zblue/mesh/proxy_cli.c @@ -0,0 +1,488 @@ +/**************************************************************************** + * libs/zblue/mesh/proxy_cli.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2021 Xiaomi Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "net.h" +#include "rpl.h" +#include "transport.h" +#include "prov.h" +#include "beacon.h" +#include "foundation.h" +#include "access.h" +#include "proxy.h" +#include "gatt_cli.h" +#include "proxy_msg.h" +#include "crypto.h" + +#define LOG_LEVEL CONFIG_BT_MESH_PROXY_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_proxy_client); + +static struct bt_mesh_proxy_server +{ + struct bt_mesh_proxy_role *role; + bool link_opened; + uint16_t net_idx; +} + +servers[CONFIG_BT_MAX_CONN] = +{ + [0 ...(CONFIG_BT_MAX_CONN - 1)] = + { + .net_idx = BT_MESH_KEY_UNUSED, + }, +}; + +static bool allow_all_subnet; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static struct bt_mesh_proxy_server *find_proxy_srv(uint16_t net_idx, + bool conn, bool disconn) +{ + for (int i = 0; i < ARRAY_SIZE(servers); i++) + { + if (!servers[i].role) + { + if (!disconn) + { + continue; + } + } + else if (!conn) + { + continue; + } + + if (servers[i].net_idx == net_idx) + { + return &servers[i]; + } + } + + return NULL; +} + +static struct bt_mesh_proxy_server *find_proxy_srv_by_conn( + struct bt_conn *conn) +{ + for (int i = 0; i < ARRAY_SIZE(servers); i++) + { + if (!servers[i].role || servers[i].role->conn != conn) + { + continue; + } + + return &servers[i]; + } + + return NULL; +} + +bool bt_mesh_proxy_cli_relay(struct bt_mesh_adv *adv) +{ + bool relayed = false; + int i; + + for (i = 0; i < ARRAY_SIZE(servers); i++) + { + struct bt_mesh_proxy_server *server = &servers[i]; + + if (!server->link_opened) + { + continue; + } + + if (bt_mesh_proxy_relay_send(server->role->conn, adv)) + { + continue; + } + + relayed = true; + } + + return relayed; +} + +static void proxy_msg_recv(struct bt_mesh_proxy_role *role) +{ + switch (role->msg_type) + { + case BT_MESH_PROXY_NET_PDU: + LOG_DBG("Mesh Network PDU"); + bt_mesh_net_recv(&role->buf, 0, BT_MESH_NET_IF_PROXY); + break; + case BT_MESH_PROXY_BEACON: + LOG_DBG("Mesh Beacon PDU"); + bt_mesh_beacon_recv(&role->buf); + break; + case BT_MESH_PROXY_CONFIG: + LOG_DBG("Mesh Configuration PDU"); + + /* TODO */ + + break; + default: + LOG_WRN("Unhandled Message Type 0x%02x", role->msg_type); + break; + } +} + +static void proxy_connected(struct bt_conn *conn, void *user_data) +{ + struct bt_mesh_proxy_server *srv = user_data; + + srv->role = bt_mesh_proxy_role_setup(conn, bt_mesh_gatt_send, + proxy_msg_recv); +} + +static void proxy_link_open(struct bt_conn *conn) +{ + struct bt_mesh_proxy_server *srv = find_proxy_srv_by_conn(conn); + + srv->link_opened = true; +} + +static void proxy_disconnected(struct bt_conn *conn) +{ + struct bt_mesh_proxy_server *srv = find_proxy_srv_by_conn(conn); + + bt_mesh_proxy_role_cleanup(srv->role); + + srv->role = NULL; + srv->link_opened = false; +} + +static const struct bt_mesh_gatt_cli proxy = +{ + .srv_uuid = BT_UUID_INIT_16(BT_UUID_MESH_PROXY_VAL), + .data_in_uuid = BT_UUID_INIT_16(BT_UUID_MESH_PROXY_DATA_IN_VAL), + .data_out_uuid = BT_UUID_INIT_16(BT_UUID_MESH_PROXY_DATA_OUT_VAL), + .data_out_cccd_uuid = BT_UUID_INIT_16(BT_UUID_GATT_CCC_VAL), + + .connected = proxy_connected, + .link_open = proxy_link_open, + .disconnected = proxy_disconnected +}; + +static bool proxy_srv_check_and_get( + struct bt_mesh_subnet *sub, const uint8_t *net_id, + struct bt_mesh_proxy_server **p_srv) +{ + struct bt_mesh_proxy_server *srv; + + srv = find_proxy_srv(sub->net_idx, true, true); + if (srv) + { + if (srv->role) + { + return true; + } + } + else if (!allow_all_subnet) + { + return false; + } + + if (!srv) + { + srv = find_proxy_srv(BT_MESH_KEY_UNUSED, false, true); + if (!srv) + { + return true; + } + } + + /* If net_id is NULL we already know that the networks match */ + + if (!net_id || !memcmp(sub->keys[0].net_id, net_id, 8) || + (bt_mesh_subnet_has_new_key(sub) + && !memcmp(sub->keys[1].net_id, net_id, + 8))) + { + *p_srv = srv; + return true; + } + + return false; +} + +struct find_net_id +{ + uint8_t type; + + union + { + const uint8_t *net_id; + struct + { + const uint8_t *hash; + const uint8_t *rand; + } priv; + } data; + + struct bt_mesh_proxy_server *srv; +}; + +static bool is_hash_equal(struct bt_mesh_subnet *sub, + struct find_net_id *res, uint8_t idx) +{ + int err; + uint8_t in[16]; + uint8_t out[16]; + + memcpy(&in[0], sub->keys[idx].net_id, 8); + memcpy(&in[8], res->data.priv.rand, 8); + err = bt_mesh_encrypt(&sub->keys[idx].identity, in, out); + if (err) + { + LOG_ERR("Failed to generate hash (err: %d)", err); + return false; + } + + if (memcmp(&out[8], res->data.priv.hash, 8)) + { + return false; + } + + return true; +} + +static bool has_net_id(struct bt_mesh_subnet *sub, void *user_data) +{ + struct find_net_id *res = user_data; + uint8_t *net_id = NULL; + + if (res->type == BT_MESH_ID_TYPE_NET) + { + net_id = (uint8_t *)res->data.net_id; + goto end; + } + + /* Additional handling for BT_MESH_ID_TYPE_PRIV_NET msg type */ + + if (!(is_hash_equal(sub, res, 0) || + (bt_mesh_subnet_has_new_key(sub) && is_hash_equal(sub, res, 1)))) + { + return false; + } + +end: + return proxy_srv_check_and_get(sub, net_id, &res->srv); +} + +static void handle_net_id( + uint8_t type, const struct bt_le_scan_recv_info *info, + struct net_buf_simple *buf) +{ + int err; + struct find_net_id res; + struct bt_mesh_subnet *sub; + + res.type = type; + res.srv = NULL; + + if (type == BT_MESH_ID_TYPE_NET) + { + if (buf->len != 8) + { + return; + } + + res.data.net_id = net_buf_simple_pull_mem(buf, 8); + } + else + { + if (buf->len != 16) + { + return; + } + + res.data.priv.hash = net_buf_simple_pull_mem(buf, 8); + res.data.priv.rand = net_buf_simple_pull_mem(buf, 8); + } + + sub = bt_mesh_subnet_find(has_net_id, (void *)&res); + if (sub && res.srv) + { + err = bt_mesh_gatt_cli_connect(info->addr, &proxy, res.srv); + if (err) + { + LOG_DBG("Failed to connect over GATT (err:%d)", err); + } + } +} + +void bt_mesh_proxy_cli_adv_recv(const struct bt_le_scan_recv_info *info, + struct net_buf_simple *buf) +{ + uint8_t type; + + type = net_buf_simple_pull_u8(buf); + switch (type) + { + case BT_MESH_ID_TYPE_NET: + + /* Fallthrough */ + + case BT_MESH_ID_TYPE_PRIV_NET: + { + handle_net_id(type, info, buf); + break; + } + + case BT_MESH_ID_TYPE_NODE: + { + /* TODO */ + + break; + } + + case BT_MESH_ID_TYPE_PRIV_NODE: + { + /* TODO */ + + break; + } + + default: + return; + } +} + +int bt_mesh_proxy_connect(uint16_t net_idx) +{ + struct bt_mesh_proxy_server *srv; + + if (net_idx == BT_MESH_KEY_ANY) + { + if (allow_all_subnet) + { + return -EALREADY; + } + + allow_all_subnet = true; + + return 0; + } + + srv = find_proxy_srv(net_idx, true, true); + if (srv) + { + return -EALREADY; + } + + srv = find_proxy_srv(BT_MESH_KEY_UNUSED, false, true); + if (!srv) + { + return -ENOMEM; + } + + srv->net_idx = net_idx; + + return 0; +} + +int bt_mesh_proxy_disconnect(uint16_t net_idx) +{ + int err; + struct bt_mesh_proxy_server *srv; + + if (net_idx != BT_MESH_KEY_ANY) + { + srv = find_proxy_srv(net_idx, true, true); + if (!srv) + { + return -EALREADY; + } + + srv->net_idx = BT_MESH_KEY_UNUSED; + + if (!srv->role) + { + return 0; + } + + return bt_conn_disconnect(srv->role->conn, + BT_HCI_ERR_REMOTE_USER_TERM_CONN); + } + + if (!allow_all_subnet) + { + return -EALREADY; + } + + allow_all_subnet = false; + + for (int i = 0; i < ARRAY_SIZE(servers); i++) + { + servers[i].net_idx = BT_MESH_KEY_UNUSED; + + if (!servers[i].role) + { + continue; + } + + err = bt_conn_disconnect(servers[i].role->conn, + BT_HCI_ERR_REMOTE_USER_TERM_CONN); + if (err) + { + return err; + } + } + + return 0; +} + +static void subnet_evt(struct bt_mesh_subnet *sub, enum bt_mesh_key_evt evt) +{ + switch (evt) + { + case BT_MESH_KEY_DELETED: + (void)bt_mesh_proxy_disconnect(sub->net_idx); + break; + + default: + break; + } +} + +BT_MESH_SUBNET_CB_DEFINE(proxy_cli) = +{ + .evt_handler = subnet_evt, +}; + +bool bt_mesh_proxy_cli_is_connected(uint16_t net_idx) +{ + if (find_proxy_srv(net_idx, true, false)) + { + return true; + } + + return false; +} diff --git a/libs/zblue/mesh/proxy_cli.h b/libs/zblue/mesh/proxy_cli.h new file mode 100644 index 0000000000000..6cd0a2a994169 --- /dev/null +++ b/libs/zblue/mesh/proxy_cli.h @@ -0,0 +1,18 @@ +/**************************************************************************** + * libs/zblue/mesh/proxy_cli.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2021 Xiaomi Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +void bt_mesh_proxy_cli_adv_recv(const struct bt_le_scan_recv_info *info, + struct net_buf_simple *buf); + +bool bt_mesh_proxy_cli_relay(struct bt_mesh_adv *adv); + +bool bt_mesh_proxy_cli_is_connected(uint16_t net_idx); diff --git a/libs/zblue/mesh/proxy_msg.c b/libs/zblue/mesh/proxy_msg.c new file mode 100644 index 0000000000000..1630f52ae8504 --- /dev/null +++ b/libs/zblue/mesh/proxy_msg.c @@ -0,0 +1,399 @@ +/**************************************************************************** + * libs/zblue/mesh/proxy_msg.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Bluetooth Mesh */ + +/* Copyright (c) 2017 Intel Corporation + * Copyright (c) 2021 Lingao Meng + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "common/bt_str.h" + +#include "mesh.h" +#include "net.h" +#include "rpl.h" +#include "transport.h" +#include "prov.h" +#include "beacon.h" +#include "foundation.h" +#include "access.h" +#include "proxy.h" +#include "proxy_msg.h" + +#define LOG_LEVEL CONFIG_BT_MESH_PROXY_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_proxy); + +#define PDU_SAR(data) (data[0] >> 6) + +/* MshPRTv1.1: 6.3.2.2: + * "The timeout for the SAR transfer is 20 seconds. When the timeout expires, + * the Proxy Server shall disconnect." + */ +#define PROXY_SAR_TIMEOUT SEC2TICK(20) + +#define SAR_COMPLETE 0x00 +#define SAR_FIRST 0x01 +#define SAR_CONT 0x02 +#define SAR_LAST 0x03 + +#define PDU_HDR(sar, type) (sar << 6 | (type & BIT_MASK(6))) + +static uint8_t bufs[CONFIG_BT_MAX_CONN * CONFIG_BT_MESH_PROXY_MSG_LEN]; + +static struct bt_mesh_proxy_role roles[CONFIG_BT_MAX_CONN]; + +static int conn_count; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void proxy_queue_put(struct bt_mesh_proxy_role *role, + struct bt_mesh_adv *adv) +{ + k_fifo_put(&role->pending, &(adv->gatt_bearer[bt_conn_index(role->conn)])); +} + +static struct bt_mesh_adv *proxy_queue_get(struct bt_mesh_proxy_role *role) +{ + void *gatt_bearer; + + gatt_bearer = k_fifo_get(&role->pending, 0); + if (!gatt_bearer) + { + return NULL; + } + + return CONTAINER_OF(gatt_bearer, struct bt_mesh_adv, + gatt_bearer[bt_conn_index(role->conn)]); +} + +static void proxy_sar_timeout(struct k_work *work) +{ + struct bt_mesh_proxy_role *role; + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + + LOG_WRN("Proxy SAR timeout"); + + role = CONTAINER_OF(dwork, struct bt_mesh_proxy_role, sar_timer); + + while (!k_fifo_is_empty(&role->pending)) + { + struct bt_mesh_adv *adv = proxy_queue_get(role); + + DEBUGASSERT(adv); + + bt_mesh_adv_unref(adv); + } + + if (role->conn) + { + bt_conn_disconnect(role->conn, BT_HCI_ERR_REMOTE_USER_TERM_CONN); + } +} + +ssize_t bt_mesh_proxy_msg_recv(struct bt_conn *conn, + const void *buf, uint16_t len) +{ + const uint8_t *data = buf; + struct bt_mesh_proxy_role *role = &roles[bt_conn_index(conn)]; + + if (net_buf_simple_tailroom(&role->buf) < len - 1) + { + LOG_WRN("Proxy role buffer overflow"); + return -EINVAL; + } + + switch (PDU_SAR(data)) + { + case SAR_COMPLETE: + if (role->buf.len) + { + LOG_WRN("Complete PDU while a pending incomplete one"); + return -EINVAL; + } + + role->msg_type = PDU_TYPE(data); + net_buf_simple_add_mem(&role->buf, data + 1, len - 1); + role->cb.recv(role); + net_buf_simple_reset(&role->buf); + break; + + case SAR_FIRST: + if (role->buf.len) + { + LOG_WRN("First PDU while a pending incomplete one"); + return -EINVAL; + } + + k_work_reschedule(&role->sar_timer, PROXY_SAR_TIMEOUT); + role->msg_type = PDU_TYPE(data); + net_buf_simple_add_mem(&role->buf, data + 1, len - 1); + break; + + case SAR_CONT: + if (!role->buf.len) + { + LOG_WRN("Continuation with no prior data"); + return -EINVAL; + } + + if (role->msg_type != PDU_TYPE(data)) + { + LOG_WRN("Unexpected message type in continuation"); + return -EINVAL; + } + + k_work_reschedule(&role->sar_timer, PROXY_SAR_TIMEOUT); + net_buf_simple_add_mem(&role->buf, data + 1, len - 1); + break; + + case SAR_LAST: + if (!role->buf.len) + { + LOG_WRN("Last SAR PDU with no prior data"); + return -EINVAL; + } + + if (role->msg_type != PDU_TYPE(data)) + { + LOG_WRN("Unexpected message type in last SAR PDU"); + return -EINVAL; + } + + /* If this fails, the work handler exits early, as there's no + * active SAR buffer. + */ + + (void)k_work_cancel_delayable(&role->sar_timer); + net_buf_simple_add_mem(&role->buf, data + 1, len - 1); + role->cb.recv(role); + net_buf_simple_reset(&role->buf); + break; + } + + return len; +} + +int bt_mesh_proxy_msg_send( + struct bt_conn *conn, uint8_t type, struct net_buf_simple *msg, + bt_gatt_complete_func_t end, void *user_data) +{ + int err; + uint16_t att_mtu = bt_gatt_get_mtu(conn); + uint16_t mtu; + struct bt_mesh_proxy_role *role = &roles[bt_conn_index(conn)]; + + LOG_DBG("conn %p type 0x%02x len %u: %s", (void *)conn, type, msg->len, + bt_hex(msg->data, msg->len)); + + /* ATT_MTU - OpCode (1 byte) - Handle (2 bytes) */ + + if (att_mtu < 3) + { + LOG_WRN("Invalid ATT MTU: %d", att_mtu); + return -EINVAL; + } + + mtu = att_mtu - 3; + if (mtu > msg->len) + { + net_buf_simple_push_u8(msg, PDU_HDR(SAR_COMPLETE, type)); + return role->cb.send(conn, msg->data, msg->len, end, user_data); + } + + net_buf_simple_push_u8(msg, PDU_HDR(SAR_FIRST, type)); + err = role->cb.send(conn, msg->data, mtu, NULL, NULL); + if (err) + { + return err; + } + + net_buf_simple_pull(msg, mtu); + + while (msg->len) + { + if (msg->len + 1 <= mtu) + { + net_buf_simple_push_u8(msg, PDU_HDR(SAR_LAST, type)); + err = role->cb.send(conn, msg->data, msg->len, end, user_data); + if (err) + { + return err; + } + + break; + } + + net_buf_simple_push_u8(msg, PDU_HDR(SAR_CONT, type)); + err = role->cb.send(conn, msg->data, mtu, NULL, NULL); + if (err) + { + return err; + } + + net_buf_simple_pull(msg, mtu); + } + + return 0; +} + +static void buf_send_end(struct bt_conn *conn, void *user_data) +{ + struct bt_mesh_adv *adv = user_data; + + bt_mesh_adv_unref(adv); +} + +static int proxy_relay_send(struct bt_conn *conn, struct bt_mesh_adv *adv) +{ + int err; + + NET_BUF_SIMPLE_DEFINE(msg, 1 + BT_MESH_NET_MAX_PDU_LEN); + + /* Proxy PDU sending modifies the original buffer, + * so we need to make a copy. + */ + + net_buf_simple_reserve(&msg, 1); + net_buf_simple_add_mem(&msg, adv->b.data, adv->b.len); + + err = bt_mesh_proxy_msg_send(conn, BT_MESH_PROXY_NET_PDU, &msg, + buf_send_end, + bt_mesh_adv_ref(adv)); + + bt_mesh_adv_send_start(0, err, &adv->ctx); + if (err) + { + LOG_ERR("Failed to send proxy message (err %d)", err); + + /* If segment_and_send() fails the buf_send_end() callback will + * not be called, so we need to clear the user data (net_buf, which is + * just opaque data to segment_and send) reference given to + * segment_and_send() here. + */ + + bt_mesh_adv_unref(adv); + } + + return err; +} + +int bt_mesh_proxy_relay_send(struct bt_conn *conn, struct bt_mesh_adv *adv) +{ + struct bt_mesh_proxy_role *role = &roles[bt_conn_index(conn)]; + + proxy_queue_put(role, bt_mesh_adv_ref(adv)); + + bt_mesh_wq_submit(&role->work); + + return 0; +} + +static void proxy_msg_send_pending(struct k_work *work) +{ + struct bt_mesh_proxy_role *role = CONTAINER_OF(work, + struct bt_mesh_proxy_role, work); + struct bt_mesh_adv *adv; + + if (!role->conn) + { + return; + } + + adv = proxy_queue_get(role); + if (!adv) + { + return; + } + + (void)proxy_relay_send(role->conn, adv); + bt_mesh_adv_unref(adv); + + if (!k_fifo_is_empty(&role->pending)) + { + bt_mesh_wq_submit(&role->work); + } +} + +static void proxy_msg_init(struct bt_mesh_proxy_role *role) +{ + /* Check if buf has been allocated, in this way, we no longer need + * to repeat the operation. + */ + + if (role->buf.__buf) + { + net_buf_simple_reset(&role->buf); + return; + } + + net_buf_simple_init_with_data(&role->buf, + &bufs[bt_conn_index( + role->conn) * CONFIG_BT_MESH_PROXY_MSG_LEN], + CONFIG_BT_MESH_PROXY_MSG_LEN); + + net_buf_simple_reset(&role->buf); + + k_fifo_init(&role->pending); + k_work_init(&role->work, proxy_msg_send_pending); + + k_work_init_delayable(&role->sar_timer, proxy_sar_timeout); +} + +struct bt_mesh_proxy_role *bt_mesh_proxy_role_setup( + struct bt_conn *conn, proxy_send_cb_t send, proxy_recv_cb_t recv) +{ + struct bt_mesh_proxy_role *role; + + conn_count++; + + role = &roles[bt_conn_index(conn)]; + + role->conn = bt_conn_ref(conn); + proxy_msg_init(role); + + role->cb.recv = recv; + role->cb.send = send; + + return role; +} + +void bt_mesh_proxy_role_cleanup(struct bt_mesh_proxy_role *role) +{ + /* If this fails, the work handler exits early, as + * there's no active connection. + */ + + (void)k_work_cancel_delayable(&role->sar_timer); + bt_conn_drop(&role->conn); + + conn_count--; +} + +bool bt_mesh_proxy_has_avail_conn(void) +{ + return conn_count < CONFIG_BT_MESH_MAX_CONN; +} diff --git a/libs/zblue/mesh/proxy_msg.h b/libs/zblue/mesh/proxy_msg.h new file mode 100644 index 0000000000000..fa1df302e9e3c --- /dev/null +++ b/libs/zblue/mesh/proxy_msg.h @@ -0,0 +1,76 @@ +/**************************************************************************** + * libs/zblue/mesh/proxy_msg.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Bluetooth Mesh */ + +/* Copyright (c) 2017 Intel Corporation + * Copyright (c) 2021 Lingao Meng + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_SUBSYS_BLUETOOTH_MESH_PROXY_MSG_H_ +#define ZEPHYR_SUBSYS_BLUETOOTH_MESH_PROXY_MSG_H_ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#define PDU_TYPE(data) (data[0] & BIT_MASK(6)) +#define CFG_FILTER_SET 0x00 +#define CFG_FILTER_ADD 0x01 +#define CFG_FILTER_REMOVE 0x02 +#define CFG_FILTER_STATUS 0x03 + +#define BT_MESH_PROXY_NET_PDU 0x00 +#define BT_MESH_PROXY_BEACON 0x01 +#define BT_MESH_PROXY_CONFIG 0x02 +#define BT_MESH_PROXY_PROV 0x03 + +#define PDU_HDR(sar, type) (sar << 6 | (type & BIT_MASK(6))) + +struct bt_mesh_proxy_role; + +typedef int (*proxy_send_cb_t)(struct bt_conn *conn, const void *data, + uint16_t len, bt_gatt_complete_func_t end, + void *user_data); + +typedef void (*proxy_recv_cb_t)(struct bt_mesh_proxy_role *role); + +struct bt_mesh_proxy_role +{ + struct bt_conn *conn; + uint8_t msg_type; + + struct k_fifo pending; + struct k_work work; + + struct + { + proxy_send_cb_t send; + proxy_recv_cb_t recv; + } cb; + + struct k_work_delayable sar_timer; + struct net_buf_simple buf; +}; + +ssize_t bt_mesh_proxy_msg_recv(struct bt_conn *conn, + const void *buf, uint16_t len); +int bt_mesh_proxy_msg_send( + struct bt_conn *conn, uint8_t type, struct net_buf_simple *msg, + bt_gatt_complete_func_t end, void *user_data); +int bt_mesh_proxy_relay_send(struct bt_conn *conn, struct bt_mesh_adv *adv); +struct bt_mesh_proxy_role *bt_mesh_proxy_role_setup( + struct bt_conn *conn, proxy_send_cb_t send, proxy_recv_cb_t recv); +void bt_mesh_proxy_role_cleanup(struct bt_mesh_proxy_role *role); + +bool bt_mesh_proxy_has_avail_conn(void); + +#endif /* ZEPHYR_SUBSYS_BLUETOOTH_MESH_PROXY_MSG_H_ */ diff --git a/libs/zblue/mesh/proxy_srv.c b/libs/zblue/mesh/proxy_srv.c new file mode 100644 index 0000000000000..f0994a53e94d5 --- /dev/null +++ b/libs/zblue/mesh/proxy_srv.c @@ -0,0 +1,1457 @@ +/**************************************************************************** + * libs/zblue/mesh/proxy_srv.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * Copyright (c) 2021 Lingao Meng + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "common/bt_str.h" + +#include "net.h" +#include "rpl.h" +#include "transport.h" +#include "prov.h" +#include "beacon.h" +#include "foundation.h" +#include "access.h" +#include "proxy.h" +#include "gatt.h" +#include "proxy_msg.h" +#include "crypto.h" + +#define LOG_LEVEL CONFIG_BT_MESH_PROXY_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_gatt); + +/* Interval to update random value in (10 minutes). + * + * Defined in the Bluetooth Mesh Specification v1.1, Section 7.2.2.2.4. + */ +#define PROXY_RANDOM_UPDATE_INTERVAL (10 * 60 * MSEC_PER_SEC) + +#define ADV_OPT_ADDR(private) \ + (IS_ENABLED(CONFIG_BT_MESH_DEBUG_USE_ID_ADDR) ? BT_LE_ADV_OPT_USE_IDENTITY \ + : (private) ? BT_LE_ADV_OPT_USE_NRPA \ + : 0) + +#define ADV_OPT_PROXY(private) \ + (BT_LE_ADV_OPT_CONN | BT_LE_ADV_OPT_SCANNABLE | ADV_OPT_ADDR(private)) + +static void proxy_send_beacons(struct k_work *work); +static int proxy_send(struct bt_conn *conn, const void *data, uint16_t len, + bt_gatt_complete_func_t end, void *user_data); + +static struct bt_mesh_proxy_client +{ + struct bt_mesh_proxy_role *cli; + uint16_t filter[CONFIG_BT_MESH_PROXY_FILTER_SIZE]; + enum __packed + { + NONE, + ACCEPT, + REJECT, + } filter_type; + struct k_work send_beacons; +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) + bool privacy; +#endif +} + +clients[CONFIG_BT_MAX_CONN] = +{ + [0 ...(CONFIG_BT_MAX_CONN - 1)] = + { + .send_beacons = + Z_WORK_INITIALIZER(proxy_send_beacons), + }, +}; + +static bool service_registered; +static bool adv_restart_pending; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static struct bt_mesh_proxy_client *find_client(struct bt_conn *conn) +{ + return &clients[bt_conn_index(conn)]; +} + +static ssize_t gatt_recv( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + const uint8_t *data = buf; + + if (len < 1) + { + LOG_WRN("Too small Proxy PDU"); + return -EINVAL; + } + + if (PDU_TYPE(data) == BT_MESH_PROXY_PROV) + { + LOG_WRN("Proxy PDU type doesn't match GATT service"); + return -EINVAL; + } + + return bt_mesh_proxy_msg_recv(conn, buf, len); +} + +/* Next subnet in queue to be advertised */ + +static struct bt_mesh_subnet *beacon_sub; + +static int filter_set(struct bt_mesh_proxy_client *client, + struct net_buf_simple *buf) +{ + uint8_t type; + + if (buf->len < 1) + { + LOG_WRN("Too short Filter Set message"); + return -EINVAL; + } + + type = net_buf_simple_pull_u8(buf); + LOG_DBG("type 0x%02x", type); + + switch (type) + { + case 0x00: + (void)memset(client->filter, 0, sizeof(client->filter)); + client->filter_type = ACCEPT; + break; + case 0x01: + (void)memset(client->filter, 0, sizeof(client->filter)); + client->filter_type = REJECT; + break; + default: + LOG_WRN("Prohibited Filter Type 0x%02x", type); + return -EINVAL; + } + + return 0; +} + +static void filter_add(struct bt_mesh_proxy_client *client, uint16_t addr) +{ + int i; + + LOG_DBG("addr 0x%04x", addr); + + if (addr == BT_MESH_ADDR_UNASSIGNED) + { + return; + } + + for (i = 0; i < ARRAY_SIZE(client->filter); i++) + { + if (client->filter[i] == addr) + { + return; + } + } + + for (i = 0; i < ARRAY_SIZE(client->filter); i++) + { + if (client->filter[i] == BT_MESH_ADDR_UNASSIGNED) + { + client->filter[i] = addr; + return; + } + } +} + +static void filter_remove(struct bt_mesh_proxy_client *client, uint16_t addr) +{ + int i; + + LOG_DBG("addr 0x%04x", addr); + + if (addr == BT_MESH_ADDR_UNASSIGNED) + { + return; + } + + for (i = 0; i < ARRAY_SIZE(client->filter); i++) + { + if (client->filter[i] == addr) + { + client->filter[i] = BT_MESH_ADDR_UNASSIGNED; + return; + } + } +} + +static void send_filter_status( + struct bt_mesh_proxy_client *client, struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + struct bt_mesh_net_tx tx = + { + .sub = rx->sub, + .ctx = &rx->ctx, + .src = bt_mesh_primary_addr(), + }; + + uint16_t filter_size; + int i; + int err; + + /* Configuration messages always have dst unassigned */ + + tx.ctx->addr = BT_MESH_ADDR_UNASSIGNED; + + net_buf_simple_reset(buf); + net_buf_simple_reserve(buf, 10); + + net_buf_simple_add_u8(buf, CFG_FILTER_STATUS); + + if (client->filter_type == ACCEPT) + { + net_buf_simple_add_u8(buf, 0x00); + } + else + { + net_buf_simple_add_u8(buf, 0x01); + } + + for (filter_size = 0U, i = 0; i < ARRAY_SIZE(client->filter); i++) + { + if (client->filter[i] != BT_MESH_ADDR_UNASSIGNED) + { + filter_size++; + } + } + + net_buf_simple_add_be16(buf, filter_size); + + LOG_DBG("%u bytes: %s", buf->len, bt_hex(buf->data, buf->len)); + + err = bt_mesh_net_encode(&tx, buf, BT_MESH_NONCE_PROXY); + if (err) + { + LOG_ERR("Encoding Proxy cfg message failed (err %d)", err); + return; + } + + err = bt_mesh_proxy_msg_send(client->cli->conn, BT_MESH_PROXY_CONFIG, buf, + NULL, NULL); + if (err) + { + LOG_ERR("Failed to send proxy cfg message (err %d)", err); + } +} + +static void proxy_filter_recv( + struct bt_conn *conn, struct bt_mesh_net_rx *rx, + struct net_buf_simple *buf) +{ + struct bt_mesh_proxy_client *client; + uint8_t opcode; + + client = find_client(conn); + + opcode = net_buf_simple_pull_u8(buf); + switch (opcode) + { + case CFG_FILTER_SET: + filter_set(client, buf); + send_filter_status(client, rx, buf); + break; + case CFG_FILTER_ADD: + while (buf->len >= 2) + { + uint16_t addr; + + addr = net_buf_simple_pull_be16(buf); + filter_add(client, addr); + } + + send_filter_status(client, rx, buf); + break; + case CFG_FILTER_REMOVE: + while (buf->len >= 2) + { + uint16_t addr; + + addr = net_buf_simple_pull_be16(buf); + filter_remove(client, addr); + } + + send_filter_status(client, rx, buf); + break; + default: + LOG_WRN("Unhandled configuration OpCode 0x%02x", opcode); + break; + } +} + +static void proxy_cfg(struct bt_mesh_proxy_role *role) +{ + NET_BUF_SIMPLE_DEFINE(buf, BT_MESH_NET_MAX_PDU_LEN); + struct bt_mesh_net_rx rx; + int err; + + err = bt_mesh_net_decode(&role->buf, BT_MESH_NET_IF_PROXY_CFG, &rx, &buf); + if (err) + { + LOG_ERR("Failed to decode Proxy Configuration (err %d)", err); + return; + } + + rx.local_match = 1U; + + if (bt_mesh_rpl_check(&rx, NULL, false)) + { + LOG_WRN("Replay: src 0x%04x dst 0x%04x seq 0x%06x", rx.ctx.addr, + rx.ctx.recv_dst, rx.seq); + return; + } + + /* Remove network headers */ + + net_buf_simple_pull(&buf, BT_MESH_NET_HDR_LEN); + + LOG_DBG("%u bytes: %s", buf.len, bt_hex(buf.data, buf.len)); + + if (buf.len < 1) + { + LOG_WRN("Too short proxy configuration PDU"); + return; + } + + proxy_filter_recv(role->conn, &rx, &buf); +} + +static void proxy_msg_recv(struct bt_mesh_proxy_role *role) +{ + switch (role->msg_type) + { + case BT_MESH_PROXY_NET_PDU: + LOG_DBG("Mesh Network PDU"); + bt_mesh_net_recv(&role->buf, 0, BT_MESH_NET_IF_PROXY); + break; + case BT_MESH_PROXY_BEACON: + LOG_DBG("Mesh Beacon PDU"); + bt_mesh_beacon_recv(&role->buf); + break; + case BT_MESH_PROXY_CONFIG: + LOG_DBG("Mesh Configuration PDU"); + proxy_cfg(role); + break; + default: + LOG_WRN("Unhandled Message Type 0x%02x", role->msg_type); + break; + } +} + +static int beacon_send(struct bt_mesh_proxy_client *client, + struct bt_mesh_subnet *sub) +{ + int err; + + NET_BUF_SIMPLE_DEFINE(buf, 28); + + net_buf_simple_reserve(&buf, 1); + +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) + err = bt_mesh_beacon_create(sub, &buf, client->privacy); +#else + err = bt_mesh_beacon_create(sub, &buf, false); +#endif + if (err) + { + return err; + } + + return bt_mesh_proxy_msg_send(client->cli->conn, BT_MESH_PROXY_BEACON, + &buf, + NULL, NULL); +} + +static bool send_beacon_cb(struct bt_mesh_subnet *sub, void *cb_data) +{ + struct bt_mesh_proxy_client *client = cb_data; + + return beacon_send(client, sub) != 0; +} + +static void proxy_send_beacons(struct k_work *work) +{ + struct bt_mesh_proxy_client *client; + + client = CONTAINER_OF(work, struct bt_mesh_proxy_client, send_beacons); + + (void)bt_mesh_subnet_find(send_beacon_cb, client); +} + +void bt_mesh_proxy_beacon_send(struct bt_mesh_subnet *sub) +{ + int i; + + if (!sub) + { + /* NULL means we send on all subnets */ + + bt_mesh_subnet_foreach(bt_mesh_proxy_beacon_send); + return; + } + + for (i = 0; i < ARRAY_SIZE(clients); i++) + { + if (clients[i].cli) + { + beacon_send(&clients[i], sub); + } + } +} + +static void identity_enabled(struct bt_mesh_subnet *sub) +{ + sub->node_id = BT_MESH_NODE_IDENTITY_RUNNING; + sub->node_id_start = (uint32_t)TICK2MSEC(clock_systime_ticks()); + + STRUCT_SECTION_FOREACH(bt_mesh_proxy_cb, cb) + { + if (cb->identity_enabled) + { + cb->identity_enabled(sub->net_idx); + } + } +} + +static void node_id_start(struct bt_mesh_subnet *sub) +{ +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) + sub->priv_beacon_ctx.node_id = false; +#endif + + identity_enabled(sub); +} + +static void private_node_id_start(struct bt_mesh_subnet *sub) +{ +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) + sub->priv_beacon_ctx.node_id = true; +#endif + + identity_enabled(sub); +} + +void bt_mesh_proxy_identity_start(struct bt_mesh_subnet *sub, bool private) +{ + if (private) + { + private_node_id_start(sub); + } + else + { + node_id_start(sub); + } + + /* Prioritize the recently enabled subnet */ + + beacon_sub = sub; +} + +void bt_mesh_proxy_identity_stop(struct bt_mesh_subnet *sub) +{ + sub->node_id = BT_MESH_NODE_IDENTITY_STOPPED; + sub->node_id_start = 0U; + + STRUCT_SECTION_FOREACH(bt_mesh_proxy_cb, cb) + { + if (cb->identity_disabled) + { + cb->identity_disabled(sub->net_idx); + } + } +} + +int bt_mesh_proxy_identity_enable(void) +{ + LOG_DBG(""); + + if (!bt_mesh_is_provisioned()) + { + return -EAGAIN; + } + + if (bt_mesh_subnet_foreach(node_id_start)) + { + bt_mesh_adv_gatt_update(); + } + + return 0; +} + +int bt_mesh_proxy_private_identity_enable(void) +{ + LOG_DBG(""); + + if (!IS_ENABLED(CONFIG_BT_MESH_PRIV_BEACONS)) + { + return -ENOTSUP; + } + + if (!bt_mesh_is_provisioned()) + { + return -EAGAIN; + } + + if (bt_mesh_subnet_foreach(private_node_id_start)) + { + bt_mesh_adv_gatt_update(); + } + + return 0; +} + +#define ENC_ID_LEN 19 +#define NET_ID_LEN 11 + +#define NODE_ID_TIMEOUT (CONFIG_BT_MESH_NODE_ID_TIMEOUT * MSEC_PER_SEC) + +static uint8_t proxy_svc_data[ENC_ID_LEN] = +{ + BT_UUID_16_ENCODE(BT_UUID_MESH_PROXY_VAL), +}; + +static const struct bt_data enc_id_ad[] = +{ + BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), + BT_DATA_BYTES(BT_DATA_UUID16_ALL, + BT_UUID_16_ENCODE(BT_UUID_MESH_PROXY_VAL)), + BT_DATA(BT_DATA_SVC_DATA16, proxy_svc_data, ENC_ID_LEN), +}; + +static const struct bt_data net_id_ad[] = +{ + BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), + BT_DATA_BYTES(BT_DATA_UUID16_ALL, + BT_UUID_16_ENCODE(BT_UUID_MESH_PROXY_VAL)), + BT_DATA(BT_DATA_SVC_DATA16, proxy_svc_data, NET_ID_LEN), +}; + +static int randomize_bt_addr(void) +{ + /* TODO: There appears to be no way to force an RPA/NRPA refresh. */ + + return 0; +} + +static int enc_id_adv(struct bt_mesh_subnet *sub, + uint8_t type, uint8_t hash[16], int32_t duration) +{ + struct bt_le_adv_param slow_adv_param = + { + .id = BT_ID_DEFAULT, + .options = ADV_OPT_PROXY(type == BT_MESH_ID_TYPE_PRIV_NET || + type == BT_MESH_ID_TYPE_PRIV_NODE), + ADV_SLOW_INT, + }; + + struct bt_le_adv_param fast_adv_param = + { + .id = BT_ID_DEFAULT, + .options = ADV_OPT_PROXY(type == BT_MESH_ID_TYPE_PRIV_NET || + type == BT_MESH_ID_TYPE_PRIV_NODE), + ADV_FAST_INT, + }; + + struct bt_data sd[1]; + int err; + + err = bt_mesh_encrypt(&sub->keys[SUBNET_KEY_TX_IDX(sub)].identity, hash, + hash); + if (err) + { + return err; + } + + /* MshPRTv1.1: 7.2.2.2.4: The AdvA field shall be regenerated whenever the + * Random field is regenerated. + */ + + err = randomize_bt_addr(); + if (err) + { + LOG_ERR("AdvA refresh failed: %d", err); + return err; + } + + proxy_svc_data[2] = type; + memcpy(&proxy_svc_data[3], &hash[8], 8); + + if (IS_ENABLED(CONFIG_BT_MESH_PROXY_USE_DEVICE_NAME)) + { + sd[0].type = BT_DATA_NAME_COMPLETE; + sd[0].data_len = BT_DEVICE_NAME_LEN; + sd[0].data = BT_DEVICE_NAME; + } + + err = bt_mesh_adv_gatt_start( + type == BT_MESH_ID_TYPE_PRIV_NET ? + &slow_adv_param : &fast_adv_param, duration, enc_id_ad, + ARRAY_SIZE(enc_id_ad), + IS_ENABLED(CONFIG_BT_MESH_PROXY_USE_DEVICE_NAME) ? sd : NULL, + IS_ENABLED(CONFIG_BT_MESH_PROXY_USE_DEVICE_NAME) ? + ARRAY_SIZE(sd) : 0); + if (err) + { + LOG_WRN("Failed to advertise using type 0x%02x (err %d)", type, err); + return err; + } + + return 0; +} + +static int node_id_adv(struct bt_mesh_subnet *sub, int32_t duration) +{ + uint8_t *random = &proxy_svc_data[11]; + uint8_t tmp[16]; + int err; + + LOG_DBG("0x%03x", sub->net_idx); + + err = bt_rand(random, 8); + if (err) + { + return err; + } + + memset(&tmp[0], 0x00, 6); + memcpy(&tmp[6], random, 8); + sys_put_be16(bt_mesh_primary_addr(), &tmp[14]); + + return enc_id_adv(sub, BT_MESH_ID_TYPE_NODE, tmp, duration); +} + +static int priv_node_id_adv(struct bt_mesh_subnet *sub, int32_t duration) +{ + uint8_t *random = &proxy_svc_data[11]; + uint8_t tmp[16]; + int err; + + LOG_DBG("0x%03x", sub->net_idx); + + err = bt_rand(random, 8); + if (err) + { + return err; + } + + memset(&tmp[0], 0x00, 5); + tmp[5] = 0x03; + memcpy(&tmp[6], random, 8); + sys_put_be16(bt_mesh_primary_addr(), &tmp[14]); + + return enc_id_adv(sub, BT_MESH_ID_TYPE_PRIV_NODE, tmp, duration); +} + +static int priv_net_id_adv(struct bt_mesh_subnet *sub, int32_t duration) +{ + uint8_t *random = &proxy_svc_data[11]; + uint8_t tmp[16]; + int err; + + LOG_DBG("0x%03x", sub->net_idx); + + err = bt_rand(random, 8); + if (err) + { + return err; + } + + memcpy(&tmp[0], sub->keys[SUBNET_KEY_TX_IDX(sub)].net_id, 8); + memcpy(&tmp[8], random, 8); + + return enc_id_adv(sub, BT_MESH_ID_TYPE_PRIV_NET, tmp, duration); +} + +static int net_id_adv(struct bt_mesh_subnet *sub, int32_t duration) +{ + struct bt_le_adv_param slow_adv_param = + { + .id = BT_ID_DEFAULT, + .options = ADV_OPT_PROXY(false), + ADV_SLOW_INT, + }; + + struct bt_data sd[1]; + int err; + + proxy_svc_data[2] = BT_MESH_ID_TYPE_NET; + + LOG_DBG("Advertising with NetId %s", + bt_hex(sub->keys[SUBNET_KEY_TX_IDX(sub)].net_id, 8)); + + memcpy(proxy_svc_data + 3, sub->keys[SUBNET_KEY_TX_IDX(sub)].net_id, 8); + + if (IS_ENABLED(CONFIG_BT_MESH_PROXY_USE_DEVICE_NAME)) + { + sd[0].type = BT_DATA_NAME_COMPLETE; + sd[0].data_len = BT_DEVICE_NAME_LEN; + sd[0].data = BT_DEVICE_NAME; + } + + err = bt_mesh_adv_gatt_start(&slow_adv_param, duration, net_id_ad, + ARRAY_SIZE(net_id_ad), + IS_ENABLED(CONFIG_BT_MESH_PROXY_USE_DEVICE_NAME) ? sd : NULL, + IS_ENABLED(CONFIG_BT_MESH_PROXY_USE_DEVICE_NAME) ? ARRAY_SIZE(sd) + : 0); + if (err) + { + LOG_WRN("Failed to advertise using Network ID (err %d)", err); + return err; + } + + return 0; +} + +static bool is_sub_proxy_active(struct bt_mesh_subnet *sub) +{ + if (sub->net_idx == BT_MESH_KEY_UNUSED) + { + return false; + } + + return (sub->node_id == BT_MESH_NODE_IDENTITY_RUNNING || +#if defined(CONFIG_BT_MESH_OD_PRIV_PROXY_SRV) + (bt_mesh_od_priv_proxy_get() > 0 && sub->solicited) || +#endif + bt_mesh_gatt_proxy_get() == BT_MESH_GATT_PROXY_ENABLED || + bt_mesh_priv_gatt_proxy_get() == BT_MESH_GATT_PROXY_ENABLED); +} + +static bool active_proxy_sub_cnt_cb(struct bt_mesh_subnet *sub, + void *cb_data) +{ + int *cnt = cb_data; + + if (is_sub_proxy_active(sub)) + { + (*cnt)++; + } + + /* Don't stop until we've visited all subnets. + * We're only using the "find" variant of the subnet iteration to get a + * context parameter. + */ + + return false; +} + +static int active_proxy_sub_cnt_get(void) +{ + int cnt = 0; + + (void)bt_mesh_subnet_find(active_proxy_sub_cnt_cb, &cnt); + + return cnt; +} + +static void proxy_adv_timeout_eval(struct bt_mesh_subnet *sub) +{ + int32_t time_passed; + + if (sub->node_id == BT_MESH_NODE_IDENTITY_RUNNING) + { + time_passed = + (uint32_t)TICK2MSEC(clock_systime_ticks()) - + sub->node_id_start; + if (time_passed > (NODE_ID_TIMEOUT - MSEC_PER_SEC)) + { + bt_mesh_proxy_identity_stop(sub); + LOG_DBG("Node ID stopped for subnet %d after %dms", sub->net_idx, + time_passed); + } + } + +#if defined(CONFIG_BT_MESH_OD_PRIV_PROXY_SRV) + if (bt_mesh_od_priv_proxy_get() > 0 && sub->solicited && + sub->priv_net_id_sent) + { + time_passed = + (uint32_t)TICK2MSEC(clock_systime_ticks()) - + sub->priv_net_id_sent; + if (time_passed > ((MSEC_PER_SEC * bt_mesh_od_priv_proxy_get()) - + MSEC_PER_SEC)) + { + sub->priv_net_id_sent = 0; + sub->solicited = false; + LOG_DBG("Private Network ID stopped for subnet %d after %dms on " + "solicitation", + sub->net_idx, time_passed); + } + } +#endif +} + +enum proxy_adv_evt +{ + NET_ID, + PRIV_NET_ID, + NODE_ID, + PRIV_NODE_ID, + OD_PRIV_NET_ID, +}; + +struct proxy_adv_request +{ + int32_t duration; + enum proxy_adv_evt evt; +}; + +static bool proxy_adv_request_get(struct bt_mesh_subnet *sub, + struct proxy_adv_request *request) +{ + if (!sub) + { + return false; + } + + if (sub->net_idx == BT_MESH_KEY_UNUSED) + { + return false; + } + +/** The priority for proxy adv is first solicitation, then Node + * Identity, and lastly Network ID. Network ID is prioritized last + * since, in many cases, another device can fulfill the same demand. + * Solicitation is prioritized first since legacy devices are dependent + * on this to connect to the network. + */ + +#if defined(CONFIG_BT_MESH_OD_PRIV_PROXY_SRV) + if (bt_mesh_od_priv_proxy_get() > 0 && sub->solicited) + { + int32_t timeout = MSEC_PER_SEC * (int32_t)bt_mesh_od_priv_proxy_get(); + + request->evt = OD_PRIV_NET_ID; + request->duration = !sub->priv_net_id_sent + ? timeout + : timeout - ((uint32_t)TICK2MSEC( + clock_systime_ticks( + )) - sub->priv_net_id_sent); + return true; + } +#endif + + if (sub->node_id == BT_MESH_NODE_IDENTITY_RUNNING) + { + request->duration = + NODE_ID_TIMEOUT - + ((uint32_t)TICK2MSEC(clock_systime_ticks()) - + sub->node_id_start); + request->evt = +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) + sub->priv_beacon_ctx.node_id ? PRIV_NODE_ID : +#endif + NODE_ID; + + return true; + } + + if (bt_mesh_priv_gatt_proxy_get() == BT_MESH_FEATURE_ENABLED) + { + request->evt = PRIV_NET_ID; + request->duration = PROXY_RANDOM_UPDATE_INTERVAL; + return true; + } + + if (bt_mesh_gatt_proxy_get() == BT_MESH_FEATURE_ENABLED) + { + request->evt = NET_ID; + request->duration = -1; + return true; + } + + return false; +} + +static struct bt_mesh_subnet *adv_sub_get_next( + struct bt_mesh_subnet *sub_start, struct proxy_adv_request *request) +{ + struct bt_mesh_subnet *sub_temp = bt_mesh_subnet_next(sub_start); + + do + { + if (proxy_adv_request_get(sub_temp, request)) + { + return sub_temp; + } + + sub_temp = bt_mesh_subnet_next(sub_temp); + } + while (sub_temp != sub_start); + + return NULL; +} + +static struct +{ + int32_t start; + struct bt_mesh_subnet *sub; + struct proxy_adv_request request; +} sub_adv; + +static int gatt_proxy_advertise(void) +{ + int err; + + int32_t max_adv_duration = 0; + int cnt; + struct bt_mesh_subnet *sub; + struct proxy_adv_request request; + + LOG_DBG(""); + + /* Close proxy activity that has timed out on all subnets */ + + bt_mesh_subnet_foreach(proxy_adv_timeout_eval); + + if (!bt_mesh_proxy_has_avail_conn()) + { + LOG_DBG("Connectable advertising deferred (max connections)"); + return -ENOMEM; + } + + cnt = active_proxy_sub_cnt_get(); + if (!cnt) + { + LOG_DBG("No subnets to advertise proxy on"); + return -ENOENT; + } + else if (cnt > 1) + { + /** There is more than one subnet that requires proxy adv, and the adv + * resources must be shared. + */ + + /* We use NODE_ID_TIMEOUT as a starting point since it may + * be less than 60 seconds. Divide this period into at least 6 slices, + * but make sure that a slice is more than one second long (to avoid + * excessive rotation). + */ + + max_adv_duration = NODE_ID_TIMEOUT / MAX(cnt, 6); + max_adv_duration = MAX(max_adv_duration, MSEC_PER_SEC + 20); + + /* Check if the previous subnet finished its allocated timeslot */ + + if ((sub_adv.request.duration != -1) && + proxy_adv_request_get(sub_adv.sub, &request) && + (sub_adv.request.evt == request.evt)) + { + int32_t time_passed = + (uint32_t)TICK2MSEC(clock_systime_ticks()) - sub_adv.start; + + if (time_passed < sub_adv.request.duration && + ((sub_adv.request.duration - time_passed) >= MSEC_PER_SEC)) + { + sub = sub_adv.sub; + request.duration = sub_adv.request.duration - time_passed; + goto end; + } + } + } + + sub = adv_sub_get_next(sub_adv.sub, &request); + if (!sub) + { + LOG_ERR("Could not find subnet to advertise"); + return -ENOENT; + } + +end: + if (cnt > 1) + { + request.duration = + (request.duration == -1) ? + max_adv_duration : MIN(request.duration, + max_adv_duration); + } + + /* Save current state for next iteration */ + + sub_adv.start = (uint32_t)TICK2MSEC(clock_systime_ticks()); + sub_adv.sub = sub; + sub_adv.request = request; + + switch (request.evt) + { + case NET_ID: + err = net_id_adv(sub, request.duration); + break; +#if defined(CONFIG_BT_MESH_OD_PRIV_PROXY_SRV) + case OD_PRIV_NET_ID: + if (!sub->priv_net_id_sent) + { + sub->priv_net_id_sent = TICK2MSEC(clock_systime_ticks()); + } + + /* Fall through */ +#endif + case PRIV_NET_ID: + err = priv_net_id_adv(sub, request.duration); + break; + case NODE_ID: + err = node_id_adv(sub, request.duration); + break; + case PRIV_NODE_ID: + err = priv_node_id_adv(sub, request.duration); + break; + default: + LOG_ERR("Unexpected proxy adv evt: %d", request.evt); + return -ENODEV; + } + + if (err) + { + LOG_ERR("Advertising proxy failed (err: %d)", err); + return err; + } + + LOG_DBG("Advertising %d ms for net_idx 0x%04x", request.duration, + sub->net_idx); + return err; +} + +static void subnet_evt(struct bt_mesh_subnet *sub, enum bt_mesh_key_evt evt) +{ + if (evt == BT_MESH_KEY_DELETED) + { + if (sub == beacon_sub) + { + beacon_sub = NULL; + } + + bt_mesh_proxy_identity_stop(sub); + } + else + { + bt_mesh_proxy_beacon_send(sub); + } + + bt_mesh_adv_gatt_update(); +} + +BT_MESH_SUBNET_CB_DEFINE(gatt_services) = +{ + .evt_handler = subnet_evt, +}; + +static void proxy_ccc_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + LOG_DBG("value 0x%04x", value); +} + +static ssize_t proxy_ccc_write( + struct bt_conn *conn, const struct bt_gatt_attr *attr, uint16_t value) +{ + struct bt_mesh_proxy_client *client; + + LOG_DBG("value: 0x%04x", value); + + if (value != BT_GATT_CCC_NOTIFY) + { + LOG_WRN("Client wrote 0x%04x instead enabling notify", value); + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + + client = find_client(conn); + if (client->filter_type == NONE) + { + client->filter_type = ACCEPT; + bt_mesh_wq_submit(&client->send_beacons); + } + + return sizeof(value); +} + +/* Mesh Proxy Service Declaration */ + +static struct bt_gatt_ccc_managed_user_data proxy_ccc = + BT_GATT_CCC_MANAGED_USER_DATA_INIT(proxy_ccc_changed, + proxy_ccc_write, + NULL); + +static struct bt_gatt_attr proxy_attrs[] = +{ + BT_GATT_PRIMARY_SERVICE(BT_UUID_MESH_PROXY), + + BT_GATT_CHARACTERISTIC(BT_UUID_MESH_PROXY_DATA_IN, + BT_GATT_CHRC_WRITE_WITHOUT_RESP, + + BT_GATT_PERM_WRITE, NULL, gatt_recv, NULL), + + BT_GATT_CHARACTERISTIC(BT_UUID_MESH_PROXY_DATA_OUT, + BT_GATT_CHRC_NOTIFY, + BT_GATT_PERM_NONE, + + NULL, NULL, NULL), + BT_GATT_CCC_MANAGED(&proxy_ccc, + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), +}; + +static struct bt_gatt_service proxy_svc = BT_GATT_SERVICE(proxy_attrs); + +static int proxy_gatt_service_register(void) +{ + int err; + + if (!bt_mesh_is_provisioned()) + { + return -ENOTSUP; + } + + if (service_registered) + { + return -EBUSY; + } + + err = bt_gatt_service_register(&proxy_svc); + if (err) + { + LOG_ERR("Unable to register Mesh Proxy Service (err %d)", err); + return err; + } + + service_registered = true; + + for (int i = 0; i < ARRAY_SIZE(clients); i++) + { + if (clients[i].cli) + { + clients[i].filter_type = ACCEPT; + } + } + + return 0; +} + +int bt_mesh_proxy_gatt_enable(void) +{ + int err; + + LOG_DBG(""); + + err = proxy_gatt_service_register(); + if (err) + { + return err; + } + + bt_mesh_adv_gatt_update(); + + return 0; +} + +void bt_mesh_proxy_gatt_disconnect(void) +{ + int i; + + LOG_DBG(""); + + for (i = 0; i < ARRAY_SIZE(clients); i++) + { + struct bt_mesh_proxy_client *client = &clients[i]; + + if (client->cli && (client->filter_type == ACCEPT || + client->filter_type == REJECT)) + { + client->filter_type = NONE; + bt_conn_disconnect(client->cli->conn, + BT_HCI_ERR_REMOTE_USER_TERM_CONN); + } + } +} + +int bt_mesh_proxy_gatt_disable(void) +{ + LOG_DBG(""); + + if (!service_registered) + { + return -EALREADY; + } + + bt_mesh_proxy_gatt_disconnect(); + + bt_gatt_service_unregister(&proxy_svc); + service_registered = false; + + return 0; +} + +void bt_mesh_proxy_addr_add(struct net_buf_simple *buf, uint16_t addr) +{ + struct bt_mesh_proxy_client *client; + struct bt_mesh_proxy_role *cli = CONTAINER_OF(buf, + struct bt_mesh_proxy_role, + buf); + + client = find_client(cli->conn); + + LOG_DBG("filter_type %u addr 0x%04x", client->filter_type, addr); + + if (client->filter_type == ACCEPT) + { + filter_add(client, addr); + } + else if (client->filter_type == REJECT) + { + filter_remove(client, addr); + } +} + +static bool client_filter_match(struct bt_mesh_proxy_client *client, + uint16_t addr) +{ + int i; + + LOG_DBG("filter_type %u addr 0x%04x", client->filter_type, addr); + + if (client->filter_type == REJECT) + { + for (i = 0; i < ARRAY_SIZE(client->filter); i++) + { + if (client->filter[i] == addr) + { + return false; + } + } + + return true; + } + + if (addr == BT_MESH_ADDR_ALL_NODES) + { + return true; + } + + if (client->filter_type == ACCEPT) + { + for (i = 0; i < ARRAY_SIZE(client->filter); i++) + { + if (client->filter[i] == addr) + { + return true; + } + } + } + + return false; +} + +bool bt_mesh_proxy_relay(struct bt_mesh_adv *adv, uint16_t dst) +{ + bool relayed = false; + int i; + + LOG_DBG("%u bytes to dst 0x%04x", adv->b.len, dst); + + for (i = 0; i < ARRAY_SIZE(clients); i++) + { + struct bt_mesh_proxy_client *client = &clients[i]; + + if (!client->cli) + { + continue; + } + + if (!client_filter_match(client, dst)) + { + continue; + } + + if (bt_mesh_proxy_relay_send(client->cli->conn, adv)) + { + continue; + } + + relayed = true; + } + + return relayed; +} + +static void solicitation_reset(struct bt_mesh_subnet *sub) +{ +#if defined(CONFIG_BT_MESH_OD_PRIV_PROXY_SRV) + sub->solicited = false; + sub->priv_net_id_sent = 0; +#endif +} + +static void gatt_connected(struct bt_conn *conn, uint8_t conn_err) +{ + struct bt_mesh_proxy_client *client; + struct bt_conn_info info; + int err; + + err = bt_conn_get_info(conn, &info); + if (err || info.role != BT_CONN_ROLE_PERIPHERAL || !service_registered || + info.id != BT_ID_DEFAULT) + { + return; + } + + LOG_DBG("conn %p err 0x%02x", (void *)conn, conn_err); + + client = find_client(conn); + + client->filter_type = NONE; + (void)memset(client->filter, 0, sizeof(client->filter)); + client->cli = bt_mesh_proxy_role_setup(conn, proxy_send, proxy_msg_recv); + +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) + /* Binding from MshPRTv1.1: 7.2.2.2.6. */ + + enum bt_mesh_subnets_node_id_state cur_node_id = + bt_mesh_subnets_node_id_state_get(); + + if (bt_mesh_gatt_proxy_get() == BT_MESH_FEATURE_ENABLED || + cur_node_id == BT_MESH_SUBNETS_NODE_ID_STATE_ENABLED) + { + client->privacy = false; + } + else + { + client->privacy = + (bt_mesh_priv_gatt_proxy_get() == BT_MESH_FEATURE_ENABLED) || + (cur_node_id == BT_MESH_SUBNETS_NODE_ID_STATE_ENABLED_PRIVATE); + } + + LOG_DBG("privacy: %d", client->privacy); +#endif + + /* If connection was formed after Proxy Solicitation we need to stop future + * Private Network ID advertisements + */ + + bt_mesh_subnet_foreach(solicitation_reset); + + /* Try to re-enable advertising in case it's possible */ + + if (bt_mesh_proxy_has_avail_conn()) + { + bt_mesh_adv_gatt_update(); + } +} + +static void gatt_disconnected(struct bt_conn *conn, uint8_t reason) +{ + struct bt_conn_info info; + struct bt_mesh_proxy_client *client; + int err; + + err = bt_conn_get_info(conn, &info); + if (err || info.role != BT_CONN_ROLE_PERIPHERAL || info.id != + BT_ID_DEFAULT) + { + return; + } + + if (!service_registered && bt_mesh_is_provisioned()) + { + (void)proxy_gatt_service_register(); + adv_restart_pending = true; + return; + } + + client = find_client(conn); + if (client->cli) + { + bt_mesh_proxy_role_cleanup(client->cli); + client->cli = NULL; + adv_restart_pending = true; + } +} + +static int proxy_send(struct bt_conn *conn, const void *data, uint16_t len, + bt_gatt_complete_func_t end, void *user_data) +{ + LOG_DBG("%u bytes: %s", len, bt_hex(data, len)); + + struct bt_gatt_notify_params params = + { + .data = data, + .len = len, + .attr = &proxy_attrs[3], + .user_data = user_data, + .func = end, + }; + + return bt_gatt_notify_cb(conn, ¶ms); +} + +int bt_mesh_proxy_adv_start(void) +{ + LOG_DBG(""); + + if (!service_registered || !bt_mesh_is_provisioned()) + { + return -ENOTSUP; + } + + return gatt_proxy_advertise(); +} + +static void conn_recycled(void) +{ + if (adv_restart_pending) + { + adv_restart_pending = false; + bt_mesh_adv_gatt_update(); + } +} + +BT_CONN_CB_DEFINE(conn_callbacks) = +{ + .connected = gatt_connected, + .disconnected = gatt_disconnected, + .recycled = conn_recycled, +}; + +uint8_t bt_mesh_proxy_srv_connected_cnt(void) +{ + uint8_t cnt = 0; + + for (int i = 0; i < ARRAY_SIZE(clients); i++) + { + if (clients[i].cli) + { + cnt++; + } + } + + return cnt; +} diff --git a/libs/zblue/mesh/rpl.c b/libs/zblue/mesh/rpl.c new file mode 100644 index 0000000000000..4bc8d2209a238 --- /dev/null +++ b/libs/zblue/mesh/rpl.c @@ -0,0 +1,516 @@ +/**************************************************************************** + * libs/zblue/mesh/rpl.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * Copyright (c) 2020 Lingao Meng + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "mesh.h" +#include "net.h" +#include "rpl.h" +#include "settings.h" + +#define LOG_LEVEL CONFIG_BT_MESH_RPL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_rpl); + +/* Replay Protection List information for persistent storage. */ + +struct rpl_val +{ + uint32_t seq : 24; + uint32_t old_iv : 1; +}; + +static struct bt_mesh_rpl replay_list[CONFIG_BT_MESH_CRPL]; +static atomic_define(store, CONFIG_BT_MESH_CRPL); + +enum +{ + PENDING_CLEAR, + PENDING_RESET, + RPL_FLAGS_COUNT, +}; +static atomic_define(rpl_flags, RPL_FLAGS_COUNT); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static inline int rpl_idx(const struct bt_mesh_rpl *rpl) +{ + return rpl - &replay_list[0]; +} + +static void clear_rpl(struct bt_mesh_rpl *rpl) +{ + int err; + char path[18]; + + if (!rpl->src) + { + return; + } + + atomic_clear_bit(store, rpl_idx(rpl)); + + snprintf(path, sizeof(path), "bt/mesh/RPL/%x", rpl->src); + err = settings_delete(path); + if (err) + { + LOG_ERR("Failed to clear RPL"); + } + else + { + LOG_DBG("Cleared RPL"); + } +} + +static void schedule_rpl_store(struct bt_mesh_rpl *entry, bool force) +{ + atomic_set_bit(store, rpl_idx(entry)); + + if (force +#ifdef CONFIG_BT_MESH_RPL_STORE_TIMEOUT + || CONFIG_BT_MESH_RPL_STORE_TIMEOUT >= 0 +#endif + ) + { + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_RPL_PENDING); + } +} + +void bt_mesh_rpl_update(struct bt_mesh_rpl *rpl, struct bt_mesh_net_rx *rx) +{ + /* If this is the first message on the new IV index, we should reset it + * to zero to avoid invalid combinations of IV index and seg. + */ + + if (rpl->old_iv && !rx->old_iv) + { + rpl->seg = 0; + } + + rpl->src = rx->ctx.addr; + rpl->seq = rx->seq; + rpl->old_iv = rx->old_iv; + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + schedule_rpl_store(rpl, false); + } +} + +/* Check the Replay Protection List for a replay attempt. If non-NULL match + * parameter is given the RPL slot is returned, but it is not immediately + * updated. This is used to prevent storing data in RPL that has been + * rejected by upper logic (access, transport commands) and for receiving the + * segmented messages. If a NULL match is given the RPL is immediately + * updated (used for proxy configuration). + */ + +bool bt_mesh_rpl_check(struct bt_mesh_net_rx *rx, + struct bt_mesh_rpl **match, bool bridge) +{ + struct bt_mesh_rpl *rpl; + int i; + + /* Don't bother checking messages from ourselves */ + + if (rx->net_if == BT_MESH_NET_IF_LOCAL) + { + return false; + } + + /* The RPL is used only for the local node or Subnet Bridge. */ + + if (!rx->local_match && !bridge) + { + return false; + } + + for (i = 0; i < ARRAY_SIZE(replay_list); i++) + { + rpl = &replay_list[i]; + + /* Empty slot */ + + if (!rpl->src) + { + goto match; + } + + /* Existing slot for given address */ + + if (rpl->src == rx->ctx.addr) + { + if (!rpl->old_iv && atomic_test_bit(rpl_flags, PENDING_RESET) && + !atomic_test_bit(store, i)) + { + /* Until rpl reset is finished, entry with old_iv == false and + * without "store" bit set will be removed, therefore it can be + * reused. If such entry is reused, "store" bit will be set and + * the entry won't be removed. + */ + + goto match; + } + + if (rx->old_iv && !rpl->old_iv) + { + return true; + } + + if ((!rx->old_iv && rpl->old_iv) || rpl->seq < rx->seq) + { + goto match; + } + else + { + return true; + } + } + } + + LOG_ERR("RPL is full!"); + return true; + +match: + if (match) + { + *match = rpl; + } + else + { + bt_mesh_rpl_update(rpl, rx); + } + + return false; +} + +void bt_mesh_rpl_clear(void) +{ + LOG_DBG(""); + + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + (void)memset(replay_list, 0, sizeof(replay_list)); + return; + } + + atomic_set_bit(rpl_flags, PENDING_CLEAR); + + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_RPL_RESET_PENDING); +} + +static struct bt_mesh_rpl *bt_mesh_rpl_find(uint16_t src) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(replay_list); i++) + { + if (replay_list[i].src == src) + { + return &replay_list[i]; + } + } + + return NULL; +} + +static struct bt_mesh_rpl *bt_mesh_rpl_alloc(uint16_t src) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(replay_list); i++) + { + if (!replay_list[i].src) + { + replay_list[i].src = src; + return &replay_list[i]; + } + } + + return NULL; +} + +void bt_mesh_rpl_reset(void) +{ + /* Discard "old old" IV Index entries from RPL and flag + * any other ones (which are valid) as old. + */ + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bool dirty = false; + + for (int i = 0; i < ARRAY_SIZE(replay_list); i++) + { + struct bt_mesh_rpl *rpl = &replay_list[i]; + + if (!rpl->src) + { + continue; + } + + /* Entries with "store" bit set will be stored, other entries will + * be removed. + */ + + atomic_set_bit_to(store, i, !rpl->old_iv); + rpl->old_iv = !rpl->old_iv; + dirty = true; + } + + if (dirty) + { + atomic_set_bit(rpl_flags, PENDING_RESET); + bt_mesh_settings_store_schedule( + BT_MESH_SETTINGS_RPL_RESET_PENDING); + } + } + else + { + int shift = 0; + int last = 0; + + for (int i = 0; i < ARRAY_SIZE(replay_list); i++) + { + struct bt_mesh_rpl *rpl = &replay_list[i]; + + if (rpl->src) + { + if (rpl->old_iv) + { + (void)memset(rpl, 0, sizeof(*rpl)); + + shift++; + } + else + { + rpl->old_iv = true; + + if (shift > 0) + { + replay_list[i - shift] = *rpl; + } + } + + last = i; + } + } + + (void)memset(&replay_list[last - shift + 1], 0, + sizeof(struct bt_mesh_rpl) * shift); + } +} + +static int rpl_set(const char *name, + size_t len_rd, settings_read_cb read_cb, void *cb_arg) +{ + struct bt_mesh_rpl *entry; + struct rpl_val rpl; + int err; + uint16_t src; + + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return 0; + } + + if (!name) + { + LOG_ERR("Insufficient number of arguments"); + return -ENOENT; + } + + src = strtol(name, NULL, 16); + entry = bt_mesh_rpl_find(src); + + if (len_rd == 0) + { + LOG_DBG("val (null)"); + if (entry) + { + (void)memset(entry, 0, sizeof(*entry)); + } + else + { + LOG_WRN("Unable to find RPL entry for 0x%04x", src); + } + + return 0; + } + + if (!entry) + { + entry = bt_mesh_rpl_alloc(src); + if (!entry) + { + LOG_ERR("Unable to allocate RPL entry for 0x%04x", src); + return -ENOMEM; + } + } + + err = bt_mesh_settings_set(read_cb, cb_arg, &rpl, sizeof(rpl)); + if (err) + { + LOG_ERR("Failed to set `net`"); + return err; + } + + entry->seq = rpl.seq; + entry->old_iv = rpl.old_iv; + + LOG_DBG("RPL entry for 0x%04x: Seq 0x%06x old_iv %u", entry->src, + entry->seq, + entry->old_iv); + + return 0; +} + +BT_MESH_SETTINGS_DEFINE(rpl, "RPL", rpl_set); + +static void store_rpl(struct bt_mesh_rpl *entry) +{ + struct rpl_val rpl = + { + 0 + }; + + char path[18]; + int err; + + if (!entry->src) + { + return; + } + + LOG_DBG("src 0x%04x seq 0x%06x old_iv %u", entry->src, entry->seq, + entry->old_iv); + + rpl.seq = entry->seq; + rpl.old_iv = entry->old_iv; + + snprintf(path, sizeof(path), "bt/mesh/RPL/%x", entry->src); + + err = settings_save_one(path, &rpl, sizeof(rpl)); + if (err) + { + LOG_ERR("Failed to store RPL %s value", path); + } + else + { + LOG_DBG("Stored RPL %s value", path); + } +} + +void bt_mesh_rpl_pending_store(uint16_t addr) +{ + int shift = 0; + int last = 0; + bool clr; + bool rst; + + if (!IS_ENABLED(CONFIG_BT_SETTINGS) || + (!BT_MESH_ADDR_IS_UNICAST(addr) && addr != BT_MESH_ADDR_ALL_NODES)) + { + return; + } + + if (addr == BT_MESH_ADDR_ALL_NODES) + { + bt_mesh_settings_store_cancel(BT_MESH_SETTINGS_RPL_PENDING); + bt_mesh_settings_store_cancel(BT_MESH_SETTINGS_RPL_RESET_PENDING); + } + + clr = atomic_test_and_clear_bit(rpl_flags, PENDING_CLEAR); + rst = atomic_test_bit(rpl_flags, PENDING_RESET); + + for (int i = 0; i < ARRAY_SIZE(replay_list); i++) + { + struct bt_mesh_rpl *rpl = &replay_list[i]; + + if (addr != BT_MESH_ADDR_ALL_NODES && addr != rpl->src) + { + continue; + } + + if (clr) + { + clear_rpl(rpl); + shift++; + } + else if (atomic_test_and_clear_bit(store, i)) + { + if (shift > 0) + { + replay_list[i - shift] = *rpl; + } + + store_rpl(&replay_list[i - shift]); + } + else if (rst) + { + clear_rpl(rpl); + + /* Check if this entry was reused during removal. If so, shift it + * as well. Otherwise, increment shift counter. + */ + + if (atomic_test_and_clear_bit(store, i)) + { + replay_list[i - shift] = *rpl; + atomic_set_bit(store, i - shift); + } + else + { + shift++; + } + } + + last = i; + + if (addr != BT_MESH_ADDR_ALL_NODES) + { + break; + } + } + + atomic_clear_bit(rpl_flags, PENDING_RESET); + + if (addr == BT_MESH_ADDR_ALL_NODES) + { + (void)memset(&replay_list[last - shift + 1], 0, + sizeof(struct bt_mesh_rpl) * shift); + } +} + +void bt_mesh_rpl_pending_store_all_nodes(void) +{ + bt_mesh_rpl_pending_store(BT_MESH_ADDR_ALL_NODES); +} diff --git a/libs/zblue/mesh/rpl.h b/libs/zblue/mesh/rpl.h new file mode 100644 index 0000000000000..153ac8138045f --- /dev/null +++ b/libs/zblue/mesh/rpl.h @@ -0,0 +1,37 @@ +/**************************************************************************** + * libs/zblue/mesh/rpl.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * Copyright (c) 2020 Lingao Meng + * + * SPDX-License-Identifier: Apache-2.0 + */ + +struct bt_mesh_rpl +{ + uint64_t src : 15; + uint64_t old_iv : 1; + uint64_t seq : 24; + + /** Sequence authentication value for the previous segmented + * message received from this address. + * + * This value is used to manage the parallel RPL of the SeqAuth values in + * transport. + */ + + uint64_t seg : 24; +}; + +typedef void (*bt_mesh_rpl_func_t)(struct bt_mesh_rpl *rpl, void *user_data); + +void bt_mesh_rpl_reset(void); +bool bt_mesh_rpl_check(struct bt_mesh_net_rx *rx, + struct bt_mesh_rpl **match, bool bridge); +void bt_mesh_rpl_clear(void); +void bt_mesh_rpl_update(struct bt_mesh_rpl *rpl, struct bt_mesh_net_rx *rx); +void bt_mesh_rpl_pending_store_all_nodes(void); diff --git a/libs/zblue/mesh/rpr.h b/libs/zblue/mesh/rpr.h new file mode 100644 index 0000000000000..0f6f2c7260475 --- /dev/null +++ b/libs/zblue/mesh/rpr.h @@ -0,0 +1,39 @@ +/**************************************************************************** + * libs/zblue/mesh/rpr.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define RPR_OP_EXTENDED_SCAN_REPORT BT_MESH_MODEL_OP_2(0x80, 0x57) +#define RPR_OP_EXTENDED_SCAN_START BT_MESH_MODEL_OP_2(0x80, 0x56) +#define RPR_OP_LINK_CLOSE BT_MESH_MODEL_OP_2(0x80, 0x5A) +#define RPR_OP_LINK_GET BT_MESH_MODEL_OP_2(0x80, 0x58) +#define RPR_OP_LINK_OPEN BT_MESH_MODEL_OP_2(0x80, 0x59) +#define RPR_OP_LINK_REPORT BT_MESH_MODEL_OP_2(0x80, 0x5C) +#define RPR_OP_LINK_STATUS BT_MESH_MODEL_OP_2(0x80, 0x5B) +#define RPR_OP_PDU_OUTBOUND_REPORT BT_MESH_MODEL_OP_2(0x80, 0x5E) +#define RPR_OP_PDU_REPORT BT_MESH_MODEL_OP_2(0x80, 0x5F) +#define RPR_OP_PDU_SEND BT_MESH_MODEL_OP_2(0x80, 0x5D) +#define RPR_OP_SCAN_CAPS_GET BT_MESH_MODEL_OP_2(0x80, 0x4F) +#define RPR_OP_SCAN_CAPS_STATUS BT_MESH_MODEL_OP_2(0x80, 0x50) +#define RPR_OP_SCAN_GET BT_MESH_MODEL_OP_2(0x80, 0x51) +#define RPR_OP_SCAN_REPORT BT_MESH_MODEL_OP_2(0x80, 0x55) +#define RPR_OP_SCAN_START BT_MESH_MODEL_OP_2(0x80, 0x52) +#define RPR_OP_SCAN_STATUS BT_MESH_MODEL_OP_2(0x80, 0x54) +#define RPR_OP_SCAN_STOP BT_MESH_MODEL_OP_2(0x80, 0x53) + +#define RPR_NODE(ctx) {.addr = (ctx)->addr, .net_idx = (ctx)->net_idx, .ttl = BT_MESH_TTL_DEFAULT} + +static inline bool rpr_node_equal(const struct bt_mesh_rpr_node *a, + const struct bt_mesh_rpr_node *b) +{ + return (a->addr == b->addr) && (a->net_idx == b->net_idx); +} + +enum bt_mesh_rpr_node_refresh bt_mesh_node_refresh_get(void); diff --git a/libs/zblue/mesh/rpr_cli.c b/libs/zblue/mesh/rpr_cli.c new file mode 100644 index 0000000000000..77b588d13cede --- /dev/null +++ b/libs/zblue/mesh/rpr_cli.c @@ -0,0 +1,923 @@ +/**************************************************************************** + * libs/zblue/mesh/rpr_cli.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include "access.h" +#include "prov.h" +#include "rpr.h" + +#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_rpr_cli); + +#define LINK_TIMEOUT_SECONDS_DEFAULT 10 + +BUILD_ASSERT(BT_MESH_MODEL_OP_LEN(RPR_OP_PDU_SEND) == 2, + "Assumes PDU send is a 2 byte opcode"); + +#define LINK_CTX(_srv, _send_rel) \ + { \ + .net_idx = (_srv)->net_idx, \ + .app_idx = BT_MESH_KEY_DEV_REMOTE, \ + .addr = (_srv)->addr, \ + .send_ttl = (_srv)->ttl, \ + .send_rel = (_send_rel), \ + } + +enum +{ + BEARER_LINK_IDLE, + BEARER_LINK_OPENING, + BEARER_LINK_OPENED, +}; + +static struct +{ + int link; + const struct prov_bearer_cb *cb; + struct bt_mesh_rpr_cli *cli; + struct + { + prov_bearer_send_complete_t cb; + } tx; +} bearer; + +static int32_t tx_timeout = (2 * MSEC_PER_SEC); + +static void link_reset(struct bt_mesh_rpr_cli *cli); +static void link_closed(struct bt_mesh_rpr_cli *cli, + enum bt_mesh_rpr_status status); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void link_report( + struct bt_mesh_rpr_cli *cli, struct bt_mesh_rpr_node *srv, + struct bt_mesh_rpr_link *link) +{ + struct pb_remote_ctx ctx = + { + cli, srv + }; + + if (link->state == BT_MESH_RPR_LINK_ACTIVE && + bearer.link == BEARER_LINK_OPENING) + { + bearer.link = BEARER_LINK_OPENED; + LOG_DBG("Opened"); + bearer.cb->link_opened(&pb_remote_cli, &ctx); + + /* PB-Remote Open Link procedure timeout is configurable, but the + * provisioning protocol timeout is not. Use default provisioning + * protocol timeout. + */ + + cli->link.time = PROTOCOL_TIMEOUT_SEC; + return; + } + + if (link->state == BT_MESH_RPR_LINK_IDLE + && bearer.link != BEARER_LINK_IDLE) + { + bearer.link = BEARER_LINK_IDLE; + + LOG_DBG("Closed (%u)", link->status); + bearer.cb->link_closed(&pb_remote_cli, &ctx, + ((link->status == BT_MESH_RPR_SUCCESS) + ? PROV_BEARER_LINK_STATUS_SUCCESS + : PROV_BEARER_LINK_STATUS_FAIL)); + } +} + +static void tx_complete(struct bt_mesh_rpr_cli *cli, int err, void *cb_data) +{ + LOG_DBG("%d", err); + + cli->link.tx_pdu++; + bt_mesh_msg_ack_ctx_clear(&cli->prov_ack_ctx); + + if (bearer.tx.cb) + { + bearer.tx.cb(err, cb_data); + } +} + +static int handle_extended_scan_report( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_rpr_node srv = RPR_NODE(ctx); + struct bt_mesh_rpr_cli *cli = mod->rt->user_data; + struct bt_mesh_rpr_unprov dev = + { + 0 + }; + + enum bt_mesh_rpr_status status; + bool found_dev = false; + + status = net_buf_simple_pull_u8(buf); + if (status != BT_MESH_RPR_SUCCESS) + { + LOG_WRN("scan report fail (%u)", status); + return 0; + } + + memcpy(dev.uuid, net_buf_simple_pull_mem(buf, 16), 16); + if (buf->len >= 2) + { + dev.oob = net_buf_simple_pull_le16(buf); + found_dev = true; + LOG_DBG("0x%04x: %s oob: 0x%04x adv data: %s", srv.addr, + bt_hex(dev.uuid, + 16), dev.oob, + bt_hex(buf->data, buf->len)); + } + else + { + LOG_DBG("0x%04x: %s not found.", srv.addr, bt_hex(dev.uuid, 16)); + } + + if (cli->scan_report && found_dev) + { + cli->scan_report(cli, &srv, &dev, buf); + } + + return 0; +} + +static int handle_link_report( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_rpr_node srv = RPR_NODE(ctx); + struct bt_mesh_rpr_cli *cli = mod->rt->user_data; + struct bt_mesh_rpr_link link; + uint8_t reason = PROV_BEARER_LINK_STATUS_SUCCESS; + + link.status = net_buf_simple_pull_u8(buf); + link.state = net_buf_simple_pull_u8(buf); + if (buf->len == 1) + { + reason = net_buf_simple_pull_u8(buf); + } + else if (buf->len) + { + LOG_WRN("Invalid link report len"); + return -EINVAL; + } + + if (cli->link.srv.addr != srv.addr) + { + LOG_DBG("Link report from unknown server 0x%04x", srv.addr); + return 0; + } + + k_work_reschedule(&cli->link.timeout, SEC2TICK(cli->link.time)); + + cli->link.state = link.state; + + LOG_DBG("0x%04x: status: %u state: %u reason: %u", srv.addr, link.status, + link.state, reason); + + if (link.state == BT_MESH_RPR_LINK_IDLE) + { + link_reset(cli); + } + + link_report(cli, &cli->link.srv, &link); + + return 0; +} + +static int handle_link_status( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_rpr_cli *cli = mod->rt->user_data; + struct bt_mesh_rpr_node srv = RPR_NODE(ctx); + struct bt_mesh_rpr_link *rsp; + struct bt_mesh_rpr_link link; + + link.status = net_buf_simple_pull_u8(buf); + link.state = net_buf_simple_pull_u8(buf); + + LOG_DBG("0x%04x: status: %u state: %u", srv.addr, link.status, link.state); + + if (bt_mesh_msg_ack_ctx_match(&cli->prov_ack_ctx, RPR_OP_LINK_STATUS, + srv.addr, (void **)&rsp)) + { + *rsp = link; + bt_mesh_msg_ack_ctx_rx(&cli->prov_ack_ctx); + } + + if (cli->link.srv.addr == srv.addr) + { + k_work_reschedule(&cli->link.timeout, SEC2TICK(cli->link.time)); + + cli->link.state = link.state; + if (link.state == BT_MESH_RPR_LINK_IDLE) + { + cli->link.srv.addr = BT_MESH_ADDR_UNASSIGNED; + } + + link_report(cli, &cli->link.srv, &link); + } + + return 0; +} + +static int handle_pdu_outbound_report( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_rpr_cli *cli = mod->rt->user_data; + struct bt_mesh_rpr_node srv = RPR_NODE(ctx); + void *cb_data; + uint8_t num; + + if (srv.addr != cli->link.srv.addr) + { + LOG_WRN("Outbound report from unknown server 0x%04x", srv.addr); + return 0; + } + + num = net_buf_simple_pull_u8(buf); + + LOG_DBG("0x%04x: %u", srv.addr, num); + + k_work_reschedule(&cli->link.timeout, SEC2TICK(cli->link.time)); + + if (!bt_mesh_msg_ack_ctx_match(&cli->prov_ack_ctx, + RPR_OP_PDU_OUTBOUND_REPORT, srv.addr, + &cb_data) || + num != cli->link.tx_pdu) + { + LOG_WRN("Non-matching PDU report (%u)", num); + return 0; + } + + tx_complete(cli, 0, cb_data); + + return 0; +} + +static int handle_pdu_report( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_rpr_cli *cli = mod->rt->user_data; + struct bt_mesh_rpr_node srv = RPR_NODE(ctx); + struct pb_remote_ctx cb_ctx = + { + cli, + &cli->link.srv, + }; + + uint8_t pdu; + + if (cli->link.srv.addr != srv.addr) + { + LOG_WRN("PDU report from unknown server 0x%04x", srv.addr); + return 0; + } + + k_work_reschedule(&cli->link.timeout, SEC2TICK(cli->link.time)); + + pdu = net_buf_simple_pull_u8(buf); + if (pdu <= cli->link.rx_pdu) + { + LOG_WRN("Duplicate rx %u", pdu); + return 0; + } + + LOG_DBG("0x%04x: %u (%u bytes)", srv.addr, pdu, buf->len); + + bearer.cb->recv(&pb_remote_cli, &cb_ctx, buf); + + return 0; +} + +static int handle_scan_caps_status( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_rpr_cli *cli = mod->rt->user_data; + struct bt_mesh_rpr_node srv = RPR_NODE(ctx); + struct bt_mesh_rpr_caps *caps; + + if (!bt_mesh_msg_ack_ctx_match(&cli->scan_ack_ctx, RPR_OP_SCAN_CAPS_STATUS, + srv.addr, + (void **)&caps)) + { + LOG_WRN("Unexpected scan caps rsp from 0x%04x", srv.addr); + return 0; + } + + caps->max_devs = net_buf_simple_pull_u8(buf); + caps->active_scan = net_buf_simple_pull_u8(buf); + + LOG_DBG("max devs: %u active scan: %u", caps->max_devs, caps->active_scan); + + bt_mesh_msg_ack_ctx_rx(&cli->scan_ack_ctx); + + return 0; +} + +static int handle_scan_report( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_rpr_cli *cli = mod->rt->user_data; + struct bt_mesh_rpr_node srv = RPR_NODE(ctx); + struct bt_mesh_rpr_unprov dev = + { + 0 + }; + + dev.rssi = net_buf_simple_pull_u8(buf); + memcpy(dev.uuid, net_buf_simple_pull_mem(buf, 16), 16); + dev.oob = net_buf_simple_pull_le16(buf); + if (buf->len == 4) + { + memcpy(&dev.hash, net_buf_simple_pull_mem(buf, 4), 4); + dev.flags = BT_MESH_RPR_UNPROV_HASH; + } + else if (buf->len) + { + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_MESH_MODEL_LOG_LEVEL_DBG)) + { + struct bt_uuid_128 uuid_repr = + { + .uuid = + { + BT_UUID_TYPE_128 + } + }; + + memcpy(uuid_repr.val, dev.uuid, 16); + LOG_DBG("0x%04x: %s oob: 0x%04x %ddBm", srv.addr, + bt_uuid_str(&uuid_repr.uuid), dev.oob, + dev.rssi); + } + + if (cli->scan_report) + { + cli->scan_report(cli, &srv, &dev, NULL); + } + + return 0; +} + +static int handle_scan_status( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_rpr_cli *cli = mod->rt->user_data; + struct bt_mesh_rpr_scan_status *status; + struct bt_mesh_rpr_node srv = RPR_NODE(ctx); + + if (!bt_mesh_msg_ack_ctx_match(&cli->scan_ack_ctx, RPR_OP_SCAN_STATUS, + srv.addr, + (void **)&status)) + { + LOG_WRN("Unexpected scan status from 0x%04x", srv.addr); + return 0; + } + + status->status = net_buf_simple_pull_u8(buf); + status->scan = net_buf_simple_pull_u8(buf); + status->max_devs = net_buf_simple_pull_u8(buf); + status->timeout = net_buf_simple_pull_u8(buf); + + LOG_DBG("status: %u state: %u max devs: %u timeout: %u seconds", + status->status, status->scan, + status->max_devs, status->timeout); + bt_mesh_msg_ack_ctx_rx(&cli->scan_ack_ctx); + + return 0; +} + +const struct bt_mesh_model_op _bt_mesh_rpr_cli_op[] = +{ + { + RPR_OP_EXTENDED_SCAN_REPORT, + BT_MESH_LEN_MIN(17), + handle_extended_scan_report + }, + + { + RPR_OP_LINK_REPORT, + BT_MESH_LEN_MIN(2), + handle_link_report + }, + + { + RPR_OP_LINK_STATUS, + BT_MESH_LEN_EXACT(2), + handle_link_status + }, + + { + RPR_OP_PDU_OUTBOUND_REPORT, + BT_MESH_LEN_EXACT(1), + handle_pdu_outbound_report + }, + + { + RPR_OP_PDU_REPORT, + BT_MESH_LEN_MIN(2), + handle_pdu_report + }, + + { + RPR_OP_SCAN_CAPS_STATUS, + BT_MESH_LEN_EXACT(2), + handle_scan_caps_status + }, + + { + RPR_OP_SCAN_REPORT, + BT_MESH_LEN_MIN(19), + handle_scan_report + }, + + { + RPR_OP_SCAN_STATUS, + BT_MESH_LEN_EXACT(4), + handle_scan_status + }, + + BT_MESH_MODEL_OP_END, +}; + +static void link_timeout(struct k_work *work) +{ + struct bt_mesh_rpr_cli *cli = + CONTAINER_OF(k_work_delayable_from_work(work), + struct bt_mesh_rpr_cli, link.timeout); + + if (bearer.link != BEARER_LINK_IDLE) + { + LOG_DBG(""); + link_closed(cli, BT_MESH_RPR_ERR_LINK_CLOSED_BY_CLIENT); + } +} + +static int rpr_cli_init(const struct bt_mesh_model *mod) +{ + if (mod->rt->elem_idx) + { + LOG_ERR("Remote provisioning client must be initialized " + "on first element"); + return -EINVAL; + } + + struct bt_mesh_rpr_cli *cli = mod->rt->user_data; + + cli->mod = mod; + cli->link.time = LINK_TIMEOUT_SECONDS_DEFAULT; + + bt_mesh_msg_ack_ctx_init(&cli->scan_ack_ctx); + bt_mesh_msg_ack_ctx_init(&cli->prov_ack_ctx); + k_work_init_delayable(&cli->link.timeout, link_timeout); + mod->keys[0] = BT_MESH_KEY_DEV_ANY; + mod->rt->flags |= BT_MESH_MOD_DEVKEY_ONLY; + + return 0; +} + +const struct bt_mesh_model_cb _bt_mesh_rpr_cli_cb = +{ + .init = rpr_cli_init, +}; + +static void pdu_send_start(uint16_t duration, int err, void *cb_data) +{ + struct bt_mesh_rpr_cli *cli = cb_data; + + if (err) + { + LOG_ERR("PDU Send failed: %d", err); + + link_closed(cli, BT_MESH_RPR_ERR_LINK_CLOSED_AS_CANNOT_SEND_PDU); + } +} + +static void pdu_send_end(int err, void *cb_data) +{ + struct bt_mesh_rpr_cli *cli = cb_data; + + if (err) + { + LOG_ERR("PDU Send failed: %d", err); + + link_closed(cli, BT_MESH_RPR_ERR_LINK_CLOSED_AS_CANNOT_SEND_PDU); + return; + } + + k_work_reschedule(&cli->link.timeout, SEC2TICK(cli->link.time)); +} + +static const struct bt_mesh_send_cb pdu_send_cb = +{ + .start = pdu_send_start, + .end = pdu_send_end, +}; + +static int tx_wait( + struct bt_mesh_rpr_cli *cli, struct bt_mesh_msg_ack_ctx *ack_ctx, + const struct bt_mesh_rpr_node *srv, struct net_buf_simple *buf, + uint32_t rsp, void *rsp_ctx) +{ + struct bt_mesh_msg_ctx ctx = LINK_CTX(srv, false); + int err; + + err = bt_mesh_msg_ack_ctx_prepare(ack_ctx, rsp, srv->addr, rsp_ctx); + if (err) + { + return err; + } + + err = bt_mesh_model_send(cli->mod, &ctx, buf, NULL, NULL); + if (err) + { + bt_mesh_msg_ack_ctx_clear(ack_ctx); + LOG_WRN("TX fail"); + return err; + } + + err = bt_mesh_msg_ack_ctx_wait(ack_ctx, MSEC2TICK(tx_timeout)); + + bt_mesh_msg_ack_ctx_clear(ack_ctx); + return err; +} + +static void link_init(struct bt_mesh_rpr_cli *cli, + const struct bt_mesh_rpr_node *srv) +{ + cli->link.srv = *srv; + cli->link.state = BT_MESH_RPR_LINK_IDLE; + cli->link.rx_pdu = 0; + cli->link.tx_pdu = 1; + k_work_reschedule(&cli->link.timeout, SEC2TICK(cli->link.time)); +} + +static void link_reset(struct bt_mesh_rpr_cli *cli) +{ + k_work_cancel_delayable(&cli->link.timeout); + cli->link.srv.addr = BT_MESH_ADDR_UNASSIGNED; + cli->link.state = BT_MESH_RPR_LINK_IDLE; + bt_mesh_msg_ack_ctx_clear(&cli->prov_ack_ctx); +} + +static void link_closed(struct bt_mesh_rpr_cli *cli, + enum bt_mesh_rpr_status status) +{ + struct bt_mesh_rpr_node srv = cli->link.srv; + struct bt_mesh_rpr_link link = + { + .status = status, + .state = BT_MESH_RPR_LINK_IDLE, + }; + + LOG_DBG("0x%04x: status: %u state: %u rx: %u tx: " + "%u", srv.addr, link.status, + cli->link.state, + cli->link.rx_pdu, cli->link.tx_pdu); + + link_reset(cli); + + link_report(cli, &srv, &link); +} + +int bt_mesh_rpr_scan_caps_get( + struct bt_mesh_rpr_cli *cli, const struct bt_mesh_rpr_node *srv, + struct bt_mesh_rpr_caps *caps) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_SCAN_CAPS_GET, 0); + bt_mesh_model_msg_init(&buf, RPR_OP_SCAN_CAPS_GET); + + return tx_wait(cli, &cli->scan_ack_ctx, srv, &buf, RPR_OP_SCAN_CAPS_STATUS, + caps); +} + +int bt_mesh_rpr_scan_get( + struct bt_mesh_rpr_cli *cli, const struct bt_mesh_rpr_node *srv, + struct bt_mesh_rpr_scan_status *status) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_SCAN_GET, 0); + bt_mesh_model_msg_init(&buf, RPR_OP_SCAN_GET); + + return tx_wait(cli, &cli->scan_ack_ctx, srv, &buf, RPR_OP_SCAN_STATUS, + status); +} + +int bt_mesh_rpr_scan_start( + struct bt_mesh_rpr_cli *cli, const struct bt_mesh_rpr_node *srv, + const uint8_t uuid[16], uint8_t timeout, uint8_t max_devs, + struct bt_mesh_rpr_scan_status *status) +{ + if (!timeout) + { + return -EINVAL; + } + + BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_SCAN_START, 18); + bt_mesh_model_msg_init(&buf, RPR_OP_SCAN_START); + + net_buf_simple_add_u8(&buf, max_devs); + net_buf_simple_add_u8(&buf, timeout); + + if (uuid) + { + net_buf_simple_add_mem(&buf, uuid, 16); + } + + return tx_wait(cli, &cli->scan_ack_ctx, srv, &buf, RPR_OP_SCAN_STATUS, + status); +} + +int bt_mesh_rpr_scan_start_ext( + struct bt_mesh_rpr_cli *cli, const struct bt_mesh_rpr_node *srv, + const uint8_t uuid[16], uint8_t timeout, const uint8_t *ad_types, + size_t ad_count) +{ + struct bt_mesh_msg_ctx ctx = LINK_CTX(srv, false); + + if ((uuid && + (timeout < BT_MESH_RPR_EXT_SCAN_TIME_MIN + || timeout > BT_MESH_RPR_EXT_SCAN_TIME_MAX)) || + !ad_count || ad_count > CONFIG_BT_MESH_RPR_AD_TYPES_MAX) + { + return -EINVAL; + } + + BT_MESH_MODEL_BUF_DEFINE(buf, + RPR_OP_EXTENDED_SCAN_START, + 18 + CONFIG_BT_MESH_RPR_AD_TYPES_MAX); + bt_mesh_model_msg_init(&buf, RPR_OP_EXTENDED_SCAN_START); + + net_buf_simple_add_u8(&buf, ad_count); + net_buf_simple_add_mem(&buf, ad_types, ad_count); + if (uuid) + { + net_buf_simple_add_mem(&buf, uuid, 16); + net_buf_simple_add_u8(&buf, timeout); + } + + return bt_mesh_model_send(cli->mod, &ctx, &buf, NULL, NULL); +} + +int bt_mesh_rpr_scan_stop( + struct bt_mesh_rpr_cli *cli, const struct bt_mesh_rpr_node *srv, + struct bt_mesh_rpr_scan_status *status) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_SCAN_STOP, 0); + bt_mesh_model_msg_init(&buf, RPR_OP_SCAN_STOP); + + return tx_wait(cli, &cli->scan_ack_ctx, srv, &buf, RPR_OP_SCAN_STATUS, + status); +} + +int bt_mesh_rpr_link_get( + struct bt_mesh_rpr_cli *cli, const struct bt_mesh_rpr_node *srv, + struct bt_mesh_rpr_link *rsp) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_LINK_GET, 0); + bt_mesh_model_msg_init(&buf, RPR_OP_LINK_GET); + + return tx_wait(cli, + &cli->prov_ack_ctx, + srv, + &buf, + RPR_OP_LINK_STATUS, + rsp); +} + +int bt_mesh_rpr_link_close( + struct bt_mesh_rpr_cli *cli, const struct bt_mesh_rpr_node *srv, + struct bt_mesh_rpr_link *rsp) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_LINK_CLOSE, 1); + bt_mesh_model_msg_init(&buf, RPR_OP_LINK_CLOSE); + net_buf_simple_add_u8(&buf, PROV_BEARER_LINK_STATUS_FAIL); + + return tx_wait(cli, + &cli->prov_ack_ctx, + srv, + &buf, + RPR_OP_LINK_STATUS, + rsp); +} + +static int link_open_prov( + struct bt_mesh_rpr_cli *cli, const struct bt_mesh_rpr_node *srv, + const uint8_t uuid[16], uint8_t timeout) +{ + struct bt_mesh_msg_ctx ctx = LINK_CTX(srv, false); + + BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_LINK_OPEN, 17); + bt_mesh_model_msg_init(&buf, RPR_OP_LINK_OPEN); + + net_buf_simple_add_mem(&buf, uuid, 16); + + if (cli->link.time != LINK_TIMEOUT_SECONDS_DEFAULT) + { + net_buf_simple_add_u8(&buf, cli->link.time); + } + + return bt_mesh_model_send(cli->mod, &ctx, &buf, NULL, NULL); +} + +static int link_open_node( + struct bt_mesh_rpr_cli *cli, const struct bt_mesh_rpr_node *srv, + enum bt_mesh_rpr_node_refresh type) +{ + struct bt_mesh_msg_ctx ctx = LINK_CTX(srv, false); + + BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_LINK_OPEN, 1); + bt_mesh_model_msg_init(&buf, RPR_OP_LINK_OPEN); + + net_buf_simple_add_u8(&buf, type); + + return bt_mesh_model_send(cli->mod, &ctx, &buf, NULL, NULL); +} + +static int link_close(struct bt_mesh_rpr_cli *cli, + enum prov_bearer_link_status status) +{ + struct bt_mesh_msg_ctx ctx = LINK_CTX(&cli->link.srv, false); + int err; + + if (cli->link.srv.addr == BT_MESH_ADDR_UNASSIGNED) + { + return -EALREADY; + } + + BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_LINK_CLOSE, 1); + bt_mesh_model_msg_init(&buf, RPR_OP_LINK_CLOSE); + + net_buf_simple_add_u8(&buf, status); + + err = bt_mesh_model_send(cli->mod, &ctx, &buf, NULL, NULL); + if (err) + { + link_reset(cli); + } + + k_work_reschedule(&cli->link.timeout, SEC2TICK(cli->link.time)); + return err; +} + +static int send(struct bt_mesh_rpr_cli *cli, + struct net_buf_simple *buf, void *cb_data) +{ + struct bt_mesh_msg_ctx ctx = LINK_CTX(&cli->link.srv, true); + int err; + + if (cli->link.srv.addr == BT_MESH_ADDR_UNASSIGNED) + { + LOG_ERR("No server"); + return -ESHUTDOWN; + } + + if (net_buf_simple_headroom(buf) < 3) + { + LOG_ERR("Invalid buffer"); + return -EINVAL; + } + + err = bt_mesh_msg_ack_ctx_prepare(&cli->prov_ack_ctx, + RPR_OP_PDU_OUTBOUND_REPORT, + cli->link.srv.addr, cb_data); + if (err) + { + LOG_ERR("Busy"); + return err; + } + + LOG_DBG("0x%02x", buf->data[0]); + + net_buf_simple_push_u8(buf, cli->link.tx_pdu); + + /* Assumes opcode is 2 bytes. Build assert at top of file ensures this. */ + + net_buf_simple_push_be16(buf, RPR_OP_PDU_SEND); + + err = bt_mesh_model_send(cli->mod, &ctx, buf, &pdu_send_cb, cli); + if (err) + { + link_closed(cli, BT_MESH_RPR_ERR_LINK_CLOSED_AS_CANNOT_SEND_PDU); + } + + return err; +} + +int32_t bt_mesh_rpr_cli_timeout_get(void) +{ + return tx_timeout; +} + +void bt_mesh_rpr_cli_timeout_set(int32_t timeout) +{ + tx_timeout = timeout; +} + +/* Prov bearer interface + */ + +static int pb_send(struct net_buf_simple *buf, + prov_bearer_send_complete_t cb, void *cb_data) +{ + bearer.tx.cb = cb; + return send(bearer.cli, buf, cb_data); +} + +static void pb_clear_tx(void) +{ + /* Nothing can be done */ +} + +static int pb_link_open( + const uint8_t uuid[16], uint8_t timeout, const struct prov_bearer_cb *cb, + void *cb_data) +{ + struct pb_remote_ctx *ctx = cb_data; + struct bt_mesh_rpr_cli *cli = ctx->cli; + const struct bt_mesh_rpr_node *srv = ctx->srv; + int err; + + if (cli->link.srv.addr != BT_MESH_ADDR_UNASSIGNED) + { + return -EBUSY; + } + + bearer.cli = ctx->cli; + bearer.cb = cb; + + cli->link.time = timeout ? timeout : LINK_TIMEOUT_SECONDS_DEFAULT; + + LOG_DBG("timeout: %d", cli->link.time); + + link_init(cli, srv); + + if (uuid) + { + err = link_open_prov(cli, srv, uuid, timeout); + } + else + { + err = link_open_node(cli, srv, ctx->refresh); + } + + if (err) + { + link_reset(cli); + return err; + } + + bearer.link = BEARER_LINK_OPENING; + + return 0; +} + +static void pb_link_close(enum prov_bearer_link_status status) +{ + int err; + + err = link_close(bearer.cli, status); + if (err) + { + LOG_ERR("Link close failed (%d)", err); + } +} + +const struct prov_bearer pb_remote_cli = +{ + .type = BT_MESH_PROV_REMOTE, + .send = pb_send, + .clear_tx = pb_clear_tx, + .link_open = pb_link_open, + .link_close = pb_link_close, +}; diff --git a/libs/zblue/mesh/rpr_srv.c b/libs/zblue/mesh/rpr_srv.c new file mode 100644 index 0000000000000..8d09cafeba9a1 --- /dev/null +++ b/libs/zblue/mesh/rpr_srv.c @@ -0,0 +1,1706 @@ +/**************************************************************************** + * libs/zblue/mesh/rpr_srv.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "access.h" +#include "prov.h" +#include "crypto.h" +#include "rpr.h" +#include "net.h" + +#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_rpr_srv); + +#define LINK_OPEN_TIMEOUT_DEFAULT 10 + +#define LINK_CTX(_cli, _send_rel) \ + {.net_idx = (_cli)->net_idx, \ + .app_idx = BT_MESH_KEY_DEV_LOCAL, \ + .addr = (_cli)->addr, \ + .send_ttl = (_cli)->ttl, \ + .send_rel = (_send_rel)} + +enum +{ + SCANNING, + SCAN_REPORT_PENDING, + SCAN_EXT_HAS_ADDR, + NODE_REFRESH, + URI_MATCHED, + URI_REQUESTED, + + RPR_SRV_NUM_FLAGS, +}; + +/** Remote provisioning server instance. */ + +static struct +{ + const struct bt_mesh_model *mod; + + atomic_define(flags, RPR_SRV_NUM_FLAGS); + + struct + { + struct bt_mesh_rpr_unprov devs[CONFIG_BT_MESH_RPR_SRV_SCANNED_ITEMS_MAX]; + uint8_t max_devs; + enum bt_mesh_rpr_scan state; + struct k_work_delayable report; + struct k_work_delayable timeout; + + /* Extended scanning */ + + bt_addr_le_t addr; + uint8_t ad[CONFIG_BT_MESH_RPR_AD_TYPES_MAX]; + uint8_t ad_count; + + /* Time to do regular scanning after extended scanning ends: */ + + uint32_t additional_time; + struct net_buf_simple *adv_data; + struct bt_mesh_rpr_node cli; + struct bt_mesh_rpr_unprov *dev; + } scan; + struct + { + struct k_work report; + enum bt_mesh_rpr_link_state state; + enum bt_mesh_rpr_status status; + uint8_t close_reason; + uint8_t tx_pdu; + uint8_t rx_pdu; + struct bt_mesh_rpr_node cli; + struct bt_mesh_rpr_unprov *dev; + } link; + struct + { + const struct prov_bearer_cb *cb; + enum bt_mesh_rpr_node_refresh procedure; + void *cb_data; + struct + { + prov_bearer_send_complete_t cb; + void *cb_data; + } tx; + } refresh; +} +srv = +{ + .scan = +{ + .adv_data = NET_BUF_SIMPLE(CONFIG_BT_MESH_RPR_SRV_AD_DATA_MAX) +} +}; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +enum bt_mesh_rpr_node_refresh bt_mesh_node_refresh_get(void) +{ + return srv.refresh.procedure; +} + +static struct bt_mesh_rpr_unprov *unprov_get(const uint8_t uuid[16]) +{ + int i; + + for (i = 0; i < srv.scan.max_devs; ++i) + { + if (uuid) + { + if ((srv.scan.devs[i].flags & BT_MESH_RPR_UNPROV_ACTIVE) && + !memcmp(srv.scan.devs[i].uuid, uuid, 16)) + { + return &srv.scan.devs[i]; + } + } + else if (!(srv.scan.devs[i].flags & BT_MESH_RPR_UNPROV_ACTIVE)) + { + return &srv.scan.devs[i]; + } + } + + return NULL; +} + +static uint8_t *get_ad_type(uint8_t *list, size_t count, uint8_t ad) +{ + int i; + + for (i = 0; i < count; ++i) + { + if (ad == list[i] || (ad == BT_DATA_NAME_SHORTENED && + list[i] == BT_DATA_NAME_COMPLETE)) + { + return &list[i]; + } + } + + return NULL; +} + +static void cli_scan_clear(void) +{ + srv.scan.cli.addr = BT_MESH_ADDR_UNASSIGNED; + srv.scan.cli.net_idx = BT_MESH_KEY_UNUSED; +} + +static void cli_link_clear(void) +{ + srv.link.cli.addr = BT_MESH_ADDR_UNASSIGNED; + srv.link.cli.net_idx = BT_MESH_KEY_UNUSED; +} + +static void scan_status_send(struct bt_mesh_msg_ctx *ctx, + enum bt_mesh_rpr_status status) +{ + uint8_t timeout = 0; + + if (atomic_test_bit(srv.flags, SCANNING)) + { + timeout = + TICK2MSEC(k_work_delayable_remaining_get( + &srv.scan.timeout)) / MSEC_PER_SEC; + } + + BT_MESH_MODEL_BUF_DEFINE(rsp, RPR_OP_SCAN_STATUS, 4); + bt_mesh_model_msg_init(&rsp, RPR_OP_SCAN_STATUS); + net_buf_simple_add_u8(&rsp, status); + net_buf_simple_add_u8(&rsp, srv.scan.state); + net_buf_simple_add_u8(&rsp, srv.scan.max_devs); + net_buf_simple_add_u8(&rsp, timeout); + + bt_mesh_model_send(srv.mod, ctx, &rsp, NULL, NULL); +} + +static void link_status_send(struct bt_mesh_msg_ctx *ctx, + enum bt_mesh_rpr_status status) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_LINK_STATUS, 2); + bt_mesh_model_msg_init(&buf, RPR_OP_LINK_STATUS); + net_buf_simple_add_u8(&buf, status); + net_buf_simple_add_u8(&buf, srv.link.state); + + bt_mesh_model_send(srv.mod, ctx, &buf, NULL, NULL); +} + +static void link_report_send(void) +{ + struct bt_mesh_msg_ctx ctx = LINK_CTX(&srv.link.cli, true); + + BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_LINK_REPORT, 3); + bt_mesh_model_msg_init(&buf, RPR_OP_LINK_REPORT); + net_buf_simple_add_u8(&buf, srv.link.status); + net_buf_simple_add_u8(&buf, srv.link.state); + if (srv.link.status == BT_MESH_RPR_ERR_LINK_CLOSED_BY_SERVER || + srv.link.status == BT_MESH_RPR_ERR_LINK_CLOSED_BY_DEVICE) + { + net_buf_simple_add_u8(&buf, srv.link.close_reason); + } + + LOG_DBG("%u %u", srv.link.status, srv.link.state); + + bt_mesh_model_send(srv.mod, &ctx, &buf, NULL, NULL); +} + +static void scan_report_schedule(void) +{ + uint32_t delay = 0; + + if (k_work_delayable_remaining_get(&srv.scan.report) || + atomic_test_bit(srv.flags, SCAN_REPORT_PENDING)) + { + return; + } + + (void)bt_rand(&delay, sizeof(uint32_t)); + delay = (delay % 480) + 20; + + k_work_reschedule(&srv.scan.report, MSEC2TICK(delay)); +} + +static void scan_report_sent(int err, void *cb_data) +{ + atomic_clear_bit(srv.flags, SCAN_REPORT_PENDING); + k_work_reschedule(&srv.scan.report, 0); +} + +static const struct bt_mesh_send_cb report_cb = +{ + .end = scan_report_sent, +}; + +static void scan_report_send(void) +{ + struct bt_mesh_msg_ctx ctx = LINK_CTX(&srv.scan.cli, true); + int i; + int err; + + if (atomic_test_bit(srv.flags, SCAN_REPORT_PENDING)) + { + return; + } + + for (i = 0; i < srv.scan.max_devs; ++i) + { + struct bt_mesh_rpr_unprov *dev = &srv.scan.devs[i]; + + if (!(dev->flags & BT_MESH_RPR_UNPROV_FOUND) || + (dev->flags & BT_MESH_RPR_UNPROV_REPORTED)) + { + continue; + } + + BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_SCAN_REPORT, 23); + bt_mesh_model_msg_init(&buf, RPR_OP_SCAN_REPORT); + net_buf_simple_add_u8(&buf, dev->rssi); + net_buf_simple_add_mem(&buf, dev->uuid, 16); + net_buf_simple_add_le16(&buf, dev->oob); + if (dev->flags & BT_MESH_RPR_UNPROV_HASH) + { + net_buf_simple_add_mem(&buf, &dev->hash, 4); + } + + atomic_set_bit(srv.flags, SCAN_REPORT_PENDING); + + err = bt_mesh_model_send(srv.mod, &ctx, &buf, &report_cb, NULL); + if (err) + { + atomic_clear_bit(srv.flags, SCAN_REPORT_PENDING); + LOG_DBG("tx failed: %d", err); + break; + } + + LOG_DBG("Reported unprov #%u", i); + dev->flags |= BT_MESH_RPR_UNPROV_REPORTED; + break; + } +} + +static void scan_ext_report_send(void) +{ + struct bt_mesh_msg_ctx ctx = LINK_CTX(&srv.scan.cli, true); + int err; + + BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_EXTENDED_SCAN_REPORT, + 19 + CONFIG_BT_MESH_RPR_SRV_AD_DATA_MAX); + bt_mesh_model_msg_init(&buf, RPR_OP_EXTENDED_SCAN_REPORT); + net_buf_simple_add_u8(&buf, BT_MESH_RPR_SUCCESS); + net_buf_simple_add_mem(&buf, srv.scan.dev->uuid, 16); + + if (srv.scan.dev->flags & BT_MESH_RPR_UNPROV_FOUND) + { + net_buf_simple_add_le16(&buf, srv.scan.dev->oob); + } + else + { + LOG_DBG("not found"); + goto send; + } + + if (srv.scan.dev->flags & BT_MESH_RPR_UNPROV_EXT_ADV_RXD) + { + net_buf_simple_add_mem(&buf, srv.scan.adv_data->data, + srv.scan.adv_data->len); + LOG_DBG("adv data: %s", bt_hex(srv.scan.adv_data->data, + srv.scan.adv_data->len)); + } + + srv.scan.dev->flags &= ~BT_MESH_RPR_UNPROV_EXT_ADV_RXD; +send: + err = bt_mesh_model_send(srv.mod, &ctx, &buf, NULL, NULL); + if (!err) + { + srv.scan.dev->flags |= BT_MESH_RPR_UNPROV_REPORTED; + } +} + +static void scan_stop(void) +{ + LOG_DBG(""); + + k_work_cancel_delayable(&srv.scan.report); + k_work_cancel_delayable(&srv.scan.timeout); + srv.scan.state = BT_MESH_RPR_SCAN_IDLE; + cli_scan_clear(); + atomic_clear_bit(srv.flags, SCANNING); +} + +static void scan_report_timeout(struct k_work *work) +{ + scan_report_send(); +} + +static void scan_ext_stop(uint32_t remaining_time) +{ + atomic_clear_bit(srv.flags, URI_MATCHED); + atomic_clear_bit(srv.flags, URI_REQUESTED); + + if ((remaining_time + srv.scan.additional_time) && + srv.scan.state != BT_MESH_RPR_SCAN_IDLE) + { + k_work_reschedule(&srv.scan.timeout, + MSEC2TICK(remaining_time + srv.scan.additional_time)); + } + else if (srv.scan.state == BT_MESH_RPR_SCAN_MULTI) + { + /* Extended scan might have finished early */ + + scan_ext_report_send(); + } + else if (srv.scan.state != BT_MESH_RPR_SCAN_IDLE) + { + scan_report_send(); + scan_stop(); + } + else + { + atomic_clear_bit(srv.flags, SCANNING); + } + + if (!(srv.scan.dev->flags & BT_MESH_RPR_UNPROV_REPORTED)) + { + scan_ext_report_send(); + } + + bt_mesh_scan_active_set(false); + srv.scan.dev = NULL; +} + +static void adv_handle_ext_scan(const struct bt_le_scan_recv_info *info, + struct net_buf_simple *buf); + +static void scan_timeout(struct k_work *work) +{ + LOG_DBG("%s", (srv.scan.dev ? "Extended scanning" : "Normal scanning")); + + if (srv.scan.dev) + { + scan_ext_stop(0); + } + else + { + scan_report_send(); + scan_stop(); + } +} + +static void link_close(enum bt_mesh_rpr_status status, + enum prov_bearer_link_status reason) +{ + srv.link.status = status; + srv.link.close_reason = reason; + srv.link.state = BT_MESH_RPR_LINK_CLOSING; + + LOG_DBG("status: %u reason: %u", status, reason); + + if (atomic_test_and_clear_bit(srv.flags, NODE_REFRESH)) + { + /* Link closing is an atomic operation: */ + + srv.link.state = BT_MESH_RPR_LINK_IDLE; + link_report_send(); + srv.refresh.cb->link_closed(&pb_remote_srv, srv.refresh.cb_data, + srv.link.close_reason); + + cli_link_clear(); + } + else + { + bt_mesh_pb_adv.link_close(reason); + } +} + +static void outbound_pdu_report_send(void) +{ + struct bt_mesh_msg_ctx ctx = LINK_CTX(&srv.link.cli, true); + + BT_MESH_MODEL_BUF_DEFINE(buf, RPR_OP_PDU_OUTBOUND_REPORT, 1); + bt_mesh_model_msg_init(&buf, RPR_OP_PDU_OUTBOUND_REPORT); + net_buf_simple_add_u8(&buf, srv.link.tx_pdu); + + LOG_DBG("%u", srv.link.tx_pdu); + + bt_mesh_model_send(srv.mod, &ctx, &buf, NULL, NULL); +} + +static void pdu_send_complete(int err, void *cb_data) +{ + if (err) + { + link_close(BT_MESH_RPR_ERR_LINK_CLOSED_AS_CANNOT_SEND_PDU, + PROV_BEARER_LINK_STATUS_FAIL); + } + else if (srv.link.state == BT_MESH_RPR_LINK_SENDING) + { + srv.link.state = BT_MESH_RPR_LINK_ACTIVE; + srv.link.tx_pdu++; + outbound_pdu_report_send(); + } +} + +static int inbound_pdu_send(struct net_buf_simple *buf, + const struct bt_mesh_send_cb *cb) +{ + struct bt_mesh_msg_ctx ctx = LINK_CTX(&srv.link.cli, true); + + BT_MESH_MODEL_BUF_DEFINE(msg, RPR_OP_PDU_REPORT, 66); + bt_mesh_model_msg_init(&msg, RPR_OP_PDU_REPORT); + net_buf_simple_add_u8(&msg, srv.link.rx_pdu); + net_buf_simple_add_mem(&msg, buf->data, buf->len); + + return bt_mesh_model_send(srv.mod, &ctx, &msg, cb, NULL); +} + +static void subnet_evt_handler(struct bt_mesh_subnet *subnet, + enum bt_mesh_key_evt evt) +{ + if (!srv.mod || evt != BT_MESH_KEY_DELETED) + { + return; + } + + LOG_DBG("Subnet deleted"); + + if (srv.link.state != BT_MESH_RPR_LINK_IDLE && + subnet->net_idx == srv.link.cli.net_idx) + { + link_close(BT_MESH_RPR_ERR_LINK_CLOSED_BY_SERVER, + PROV_BEARER_LINK_STATUS_FAIL); + /* Skip the link closing stage, as specified in the Bluetooth + * MshPRTv1.1: 4.4.5.4. + */ + + srv.link.state = BT_MESH_RPR_LINK_IDLE; + } + else if (atomic_test_bit(srv.flags, SCANNING) && + subnet->net_idx == srv.scan.cli.net_idx) + { + scan_stop(); + } +} + +BT_MESH_SUBNET_CB_DEFINE(rpr_srv) = +{ + .evt_handler = subnet_evt_handler +}; + +/* Prov bearer interface + */ + +static void pb_link_opened(const struct prov_bearer *bearer, void *cb_data) +{ + LOG_DBG(""); + + srv.link.state = BT_MESH_RPR_LINK_ACTIVE; + srv.link.status = BT_MESH_RPR_SUCCESS; + link_report_send(); +} + +static void link_report_send_and_clear(struct k_work *work) +{ + link_report_send(); + cli_link_clear(); +} + +static void pb_link_closed(const struct prov_bearer *bearer, void *cb_data, + enum prov_bearer_link_status reason) +{ + if (srv.link.state == BT_MESH_RPR_LINK_IDLE) + { + return; + } + + LOG_DBG("%u", reason); + + if (srv.link.state == BT_MESH_RPR_LINK_OPENING) + { + srv.link.status = BT_MESH_RPR_ERR_LINK_OPEN_FAILED; + } + else if (reason == PROV_BEARER_LINK_STATUS_TIMEOUT) + { + if (srv.link.state == BT_MESH_RPR_LINK_SENDING) + { + srv.link.status = BT_MESH_RPR_ERR_LINK_CLOSED_AS_CANNOT_SEND_PDU; + } + else + { + srv.link.status = BT_MESH_RPR_ERR_LINK_CLOSED_BY_SERVER; + } + } + else if (reason == PROV_BEARER_LINK_STATUS_FAIL && + srv.link.status != BT_MESH_RPR_ERR_LINK_CLOSED_BY_CLIENT && + srv.link.status != BT_MESH_RPR_ERR_LINK_CLOSED_BY_SERVER) + { + srv.link.status = BT_MESH_RPR_ERR_LINK_CLOSED_BY_DEVICE; + } + + if (reason == PROV_BEARER_LINK_STATUS_SUCCESS) + { + srv.link.close_reason = PROV_BEARER_LINK_STATUS_SUCCESS; + } + else + { + srv.link.close_reason = PROV_BEARER_LINK_STATUS_FAIL; + } + + srv.link.state = BT_MESH_RPR_LINK_IDLE; + k_work_submit(&srv.link.report); +} + +static void pb_error(const struct prov_bearer *bearer, + void *cb_data, uint8_t err) +{ + if (srv.link.state == BT_MESH_RPR_LINK_IDLE) + { + return; + } + + LOG_DBG("%d", err); + srv.link.close_reason = err; + srv.link.state = BT_MESH_RPR_LINK_IDLE; + srv.link.status = BT_MESH_RPR_ERR_LINK_CLOSED_AS_CANNOT_RECEIVE_PDU; + link_report_send(); + cli_link_clear(); +} + +static void pb_rx(const struct prov_bearer *bearer, + void *cb_data, struct net_buf_simple *buf) +{ + int err; + + if (srv.link.state != BT_MESH_RPR_LINK_ACTIVE && + srv.link.state != BT_MESH_RPR_LINK_SENDING) + { + return; + } + + srv.link.rx_pdu++; + LOG_DBG(""); + + err = inbound_pdu_send(buf, NULL); + if (err) + { + LOG_ERR("PDU send fail: %d", err); + link_close(BT_MESH_RPR_ERR_LINK_CLOSED_AS_CANNOT_SEND_PDU, + PROV_BEARER_LINK_STATUS_FAIL); + bt_mesh_pb_adv.link_close(PROV_ERR_RESOURCES); + } +} + +static const struct prov_bearer_cb prov_bearer_cb = +{ + .link_opened = pb_link_opened, + .link_closed = pb_link_closed, + .error = pb_error, + .recv = pb_rx, +}; + +/* Message handlers + */ + +static int handle_scan_caps_get( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_MESH_MODEL_BUF_DEFINE(rsp, RPR_OP_SCAN_CAPS_STATUS, 2); + bt_mesh_model_msg_init(&rsp, RPR_OP_SCAN_CAPS_STATUS); + net_buf_simple_add_u8(&rsp, CONFIG_BT_MESH_RPR_SRV_SCANNED_ITEMS_MAX); + net_buf_simple_add_u8(&rsp, true); + + bt_mesh_model_send(srv.mod, ctx, &rsp, NULL, NULL); + + return 0; +} + +static int handle_scan_get( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + scan_status_send(ctx, BT_MESH_RPR_SUCCESS); + + return 0; +} + +static int handle_scan_start( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_rpr_node cli = RPR_NODE(ctx); + enum bt_mesh_rpr_status status; + const uint8_t *uuid = NULL; + uint8_t max_devs; + uint8_t timeout; + int i; + + max_devs = net_buf_simple_pull_u8(buf); + timeout = net_buf_simple_pull_u8(buf); + if (!timeout) + { + return -EINVAL; + } + + if (buf->len == 16) + { + uuid = net_buf_simple_pull_mem(buf, 16); + } + else if (buf->len) + { + return -EINVAL; + } + + LOG_DBG("max %u devs, %u s %s", max_devs, timeout, uuid ? bt_hex(uuid, + 16) : ""); + + if (max_devs > CONFIG_BT_MESH_RPR_SRV_SCANNED_ITEMS_MAX) + { + status = BT_MESH_RPR_ERR_SCANNING_CANNOT_START; + goto rsp; + } + + if (srv.scan.state != BT_MESH_RPR_SCAN_IDLE && + !rpr_node_equal(&cli, &srv.scan.cli)) + { + status = BT_MESH_RPR_ERR_INVALID_STATE; + goto rsp; + } + + for (i = 0; i < ARRAY_SIZE(srv.scan.devs); ++i) + { + srv.scan.devs[i].flags = 0; + } + + if (uuid) + { + srv.scan.state = BT_MESH_RPR_SCAN_SINGLE; + + srv.scan.devs[0].flags = BT_MESH_RPR_UNPROV_ACTIVE; + memcpy(srv.scan.devs[0].uuid, uuid, 16); + } + else + { + srv.scan.state = BT_MESH_RPR_SCAN_MULTI; + } + + srv.scan.max_devs = + (max_devs ? max_devs : CONFIG_BT_MESH_RPR_SRV_SCANNED_ITEMS_MAX); + srv.scan.cli = cli; + status = BT_MESH_RPR_SUCCESS; + + atomic_set_bit(srv.flags, SCANNING); + k_work_reschedule(&srv.scan.timeout, SEC2TICK(timeout)); + +rsp: + scan_status_send(ctx, status); + + return 0; +} + +static int handle_extended_scan_start( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + BT_MESH_MODEL_BUF_DEFINE(rsp, RPR_OP_EXTENDED_SCAN_REPORT, + 19 + CONFIG_BT_MESH_RPR_SRV_AD_DATA_MAX); + struct bt_mesh_rpr_node cli = RPR_NODE(ctx); + enum bt_mesh_rpr_status status; + const uint8_t *uuid; + uint8_t *ad = NULL; + uint8_t ad_count; + uint8_t timeout; + int i; + + /* According to MshPRTv1.1: 4.4.5.5.1.7, scan reports shall be + * sent as segmented messages. + */ + + ctx->send_rel = true; + + ad_count = net_buf_simple_pull_u8(buf); + if (buf->len < ad_count || ad_count == 0 || ad_count > 0x10) + { + /* Prohibited */ + + return -EINVAL; + } + + ad = net_buf_simple_pull_mem(buf, ad_count); + for (i = 0; i < ad_count; ++i) + { + if (ad[i] == BT_DATA_NAME_SHORTENED || ad[i] == BT_DATA_UUID16_SOME || + ad[i] == BT_DATA_UUID32_SOME || ad[i] == BT_DATA_UUID128_SOME) + { + return -EINVAL; + } + + for (int j = 0; j < i; j++) + { + if (ad[i] == ad[j]) + { + /* Duplicate entry */ + + return -EINVAL; + } + } + } + + ad_count = MIN(ad_count, CONFIG_BT_MESH_RPR_AD_TYPES_MAX); + + if (!buf->len) + { + const struct bt_mesh_prov *prov = bt_mesh_prov_get(); + + LOG_DBG("Self scan"); + + /* Want our local info. Could also include additional adv data, + * but there's no functionality for this in the mesh stack at the + * moment, so we'll only include the URI (if requested) + */ + + bt_mesh_model_msg_init(&rsp, RPR_OP_EXTENDED_SCAN_REPORT); + + net_buf_simple_add_u8(&rsp, BT_MESH_RPR_SUCCESS); + net_buf_simple_add_mem(&rsp, prov->uuid, 16); + net_buf_simple_add_le16(&rsp, prov->oob_info); + + if (prov->uri && get_ad_type(ad, ad_count, BT_DATA_URI)) + { + uint8_t uri_len = strlen(prov->uri); + + if (uri_len < CONFIG_BT_MESH_RPR_SRV_AD_DATA_MAX - 2) + { + net_buf_simple_add_u8(&rsp, uri_len + 1); + net_buf_simple_add_u8(&rsp, BT_DATA_URI); + net_buf_simple_add_mem(&rsp, prov->uri, uri_len); + LOG_DBG("URI added: %s", prov->uri); + } + else + { + LOG_WRN("URI data won't fit in scan report"); + } + } + + bt_mesh_model_send(mod, ctx, &rsp, NULL, NULL); + return 0; + } + + if (buf->len != 17) + { + return -EINVAL; + } + + uuid = net_buf_simple_pull_mem(buf, 16); + timeout = net_buf_simple_pull_u8(buf); + + if (IS_ENABLED(CONFIG_BT_MESH_MODEL_LOG_LEVEL_DBG)) + { + struct bt_uuid_128 uuid_repr = + { + .uuid = + { + BT_UUID_TYPE_128 + } + }; + + memcpy(uuid_repr.val, uuid, 16); + LOG_DBG("%s AD types: %s", bt_uuid_str(&uuid_repr.uuid), bt_hex(ad, + ad_count)); + } + + if (timeout < BT_MESH_RPR_EXT_SCAN_TIME_MIN || + timeout > BT_MESH_RPR_EXT_SCAN_TIME_MAX) + { + LOG_ERR("Invalid extended scan timeout %u", timeout); + return -EINVAL; + } + + if (srv.link.state != BT_MESH_RPR_LINK_IDLE) + { + status = BT_MESH_RPR_ERR_LIMITED_RESOURCES; + goto rsp; + } + + if (srv.scan.dev && + (memcmp(srv.scan.dev->uuid, uuid, 16) || !rpr_node_equal(&srv.scan.cli, + &cli))) + { + LOG_WRN("Extended scan fail: Busy"); + status = BT_MESH_RPR_ERR_LIMITED_RESOURCES; + goto rsp; + } + + if (srv.scan.state == BT_MESH_RPR_SCAN_IDLE) + { + srv.scan.max_devs = 1; + srv.scan.devs[0].flags = 0; + } + + srv.scan.dev = unprov_get(uuid); + if (!srv.scan.dev) + { + srv.scan.dev = unprov_get(NULL); + if (!srv.scan.dev) + { + LOG_WRN("Extended scan fail: No memory"); + status = BT_MESH_RPR_ERR_LIMITED_RESOURCES; + goto rsp; + } + + memcpy(srv.scan.dev->uuid, uuid, 16); + srv.scan.dev->oob = 0; + srv.scan.dev->flags = 0; + } + + memcpy(srv.scan.ad, ad, ad_count); + srv.scan.ad_count = ad_count; + net_buf_simple_reset(srv.scan.adv_data); + + atomic_set_bit(srv.flags, SCANNING); + atomic_clear_bit(srv.flags, SCAN_EXT_HAS_ADDR); + srv.scan.dev->flags &= ~BT_MESH_RPR_UNPROV_REPORTED; + srv.scan.dev->flags |= BT_MESH_RPR_UNPROV_ACTIVE | BT_MESH_RPR_UNPROV_EXT; + + if (srv.scan.state == BT_MESH_RPR_SCAN_IDLE) + { + srv.scan.additional_time = 0; + srv.scan.cli = cli; + } + else if (TICK2MSEC(k_work_delayable_remaining_get(&srv.scan.timeout)) < + (timeout * MSEC_PER_SEC)) + { + srv.scan.additional_time = 0; + } + else + { + srv.scan.additional_time = + TICK2MSEC(k_work_delayable_remaining_get(&srv.scan.timeout)) - + (timeout * MSEC_PER_SEC); + } + + bt_mesh_scan_active_set(true); + k_work_reschedule(&srv.scan.timeout, SEC2TICK(timeout)); + return 0; + +rsp: + bt_mesh_model_msg_init(&rsp, RPR_OP_EXTENDED_SCAN_REPORT); + net_buf_simple_add_u8(&rsp, status); + net_buf_simple_add_mem(&rsp, uuid, 16); + bt_mesh_model_send(mod, ctx, &rsp, NULL, NULL); + + return 0; +} + +static int handle_scan_stop( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + if (atomic_test_bit(srv.flags, SCANNING)) + { + scan_report_send(); + scan_stop(); + } + + scan_status_send(ctx, BT_MESH_RPR_SUCCESS); + + return 0; +} + +static int handle_link_get( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + LOG_DBG(""); + + link_status_send(ctx, BT_MESH_RPR_SUCCESS); + + return 0; +} + +static int handle_link_open( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + bool is_refresh_procedure = (buf->len == 1); + struct bt_mesh_rpr_node cli = RPR_NODE(ctx); + int8_t timeout = LINK_OPEN_TIMEOUT_DEFAULT; + enum bt_mesh_rpr_status status; + const uint8_t *uuid; + uint8_t refresh; + int err; + + if (buf->len != 1 && buf->len != 16 && buf->len != 17) + { + return -EINVAL; + } + + if (srv.link.state == BT_MESH_RPR_LINK_CLOSING || + srv.link.state == BT_MESH_RPR_LINK_SENDING) + { + status = BT_MESH_RPR_ERR_INVALID_STATE; + LOG_ERR("Invalid state: %u", srv.link.state); + goto rsp; + } + + if (srv.link.state == BT_MESH_RPR_LINK_OPENING || + srv.link.state == BT_MESH_RPR_LINK_ACTIVE) + { + if (!rpr_node_equal(&cli, &srv.link.cli)) + { + status = BT_MESH_RPR_ERR_LINK_CANNOT_OPEN; + goto rsp; + } + + if (is_refresh_procedure) + { + refresh = net_buf_simple_pull_u8(buf); + if (!atomic_test_bit(srv.flags, NODE_REFRESH) || + srv.refresh.procedure != refresh) + { + status = BT_MESH_RPR_ERR_LINK_CANNOT_OPEN; + } + else + { + status = BT_MESH_RPR_SUCCESS; + } + + goto rsp; + } + + if (atomic_test_bit(srv.flags, NODE_REFRESH)) + { + status = BT_MESH_RPR_ERR_LINK_CANNOT_OPEN; + goto rsp; + } + + uuid = net_buf_simple_pull_mem(buf, 16); + + if (memcmp(uuid, srv.link.dev->uuid, 16)) + { + status = BT_MESH_RPR_ERR_LINK_CANNOT_OPEN; + } + else + { + status = BT_MESH_RPR_SUCCESS; + } + + goto rsp; + } + + /* Link state is IDLE */ + + if (is_refresh_procedure) + { + refresh = net_buf_simple_pull_u8(buf); + if (refresh > BT_MESH_RPR_NODE_REFRESH_COMPOSITION) + { + LOG_ERR("Invalid refresh: %u", refresh); + return -EINVAL; + } + + if (refresh == BT_MESH_RPR_NODE_REFRESH_COMPOSITION && + !atomic_test_bit(bt_mesh.flags, BT_MESH_COMP_DIRTY)) + { + LOG_WRN("Composition data page 128 is equal to page 0"); + status = BT_MESH_RPR_ERR_LINK_CANNOT_OPEN; + goto rsp; + } + + if (srv.refresh.cb == NULL) + { + LOG_WRN("RPR provisioning client bearer not available"); + status = BT_MESH_RPR_ERR_LINK_CANNOT_OPEN; + goto rsp; + } + + LOG_DBG("Node Refresh: %u", refresh); + + atomic_set_bit(srv.flags, NODE_REFRESH); + srv.refresh.procedure = refresh; + srv.link.cli = cli; + srv.link.rx_pdu = 0; + srv.link.tx_pdu = 0; + srv.link.state = BT_MESH_RPR_LINK_ACTIVE; + srv.link.status = BT_MESH_RPR_SUCCESS; + srv.refresh.cb->link_opened(&pb_remote_srv, &srv); + status = BT_MESH_RPR_SUCCESS; + link_report_send(); + goto rsp; + } + + uuid = net_buf_simple_pull_mem(buf, 16); + if (buf->len) + { + timeout = net_buf_simple_pull_u8(buf); + if (!timeout || timeout > 0x3c) + { + LOG_ERR("Invalid timeout: %u", timeout); + return -EINVAL; + } + } + + LOG_DBG("0x%04x: %s", cli.addr, bt_hex(uuid, 16)); + + /* Attempt to reuse the scanned unprovisioned device, to preserve as + * much information as possible, but fall back to hijacking the first slot + * if none was found. + */ + + srv.link.dev = unprov_get(uuid); + if (!srv.link.dev) + { + srv.link.dev = &srv.scan.devs[0]; + memcpy(srv.link.dev->uuid, uuid, 16); + srv.link.dev->flags = 0; + } + + err = bt_mesh_pb_adv.link_open(uuid, timeout, &prov_bearer_cb, &srv); + if (err) + { + status = BT_MESH_RPR_ERR_LINK_CANNOT_OPEN; + goto rsp; + } + + srv.link.cli = cli; + srv.link.rx_pdu = 0; + srv.link.tx_pdu = 0; + srv.link.state = BT_MESH_RPR_LINK_OPENING; + srv.link.status = BT_MESH_RPR_SUCCESS; + srv.link.dev->flags |= BT_MESH_RPR_UNPROV_HAS_LINK; + status = BT_MESH_RPR_SUCCESS; + +rsp: + link_status_send(ctx, status); + + return 0; +} + +static int handle_link_close( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_rpr_node cli = RPR_NODE(ctx); + enum prov_bearer_link_status reason; + + reason = net_buf_simple_pull_u8(buf); + if (reason != PROV_BEARER_LINK_STATUS_SUCCESS && + reason != PROV_BEARER_LINK_STATUS_FAIL) + { + return -EINVAL; + } + + LOG_DBG(""); + + if (srv.link.state == BT_MESH_RPR_LINK_IDLE || + srv.link.state == BT_MESH_RPR_LINK_CLOSING) + { + link_status_send(ctx, BT_MESH_RPR_SUCCESS); + return 0; + } + + if (!rpr_node_equal(&cli, &srv.link.cli)) + { + link_status_send(ctx, BT_MESH_RPR_ERR_INVALID_STATE); + return 0; + } + + srv.link.state = BT_MESH_RPR_LINK_CLOSING; + + /* Note: The response status isn't the same as the link status state, + * which will be used in the link report when the link is fully closed. + */ + + /* Disable randomization for the Remote Provisioning Link Status message to + * avoid reordering of it with the Remote Provisioning Link Report message + * that shall be sent in a sequence when closing an active link (see + * section 4.4.5.5.3.3 of MshPRTv1.1). + */ + + ctx->rnd_delay = false; + + link_status_send(ctx, BT_MESH_RPR_SUCCESS); + link_close(BT_MESH_RPR_ERR_LINK_CLOSED_BY_CLIENT, reason); + + return 0; +} + +static int handle_pdu_send( + const struct bt_mesh_model *mod, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_rpr_node cli = RPR_NODE(ctx); + uint8_t pdu_num; + int err; + + pdu_num = net_buf_simple_pull_u8(buf); + + if (srv.link.state != BT_MESH_RPR_LINK_ACTIVE) + { + LOG_WRN("Sending PDU while busy (state %u)", srv.link.state); + return 0; + } + + if (!rpr_node_equal(&cli, &srv.link.cli)) + { + LOG_WRN("Unknown client 0x%04x", cli.addr); + return 0; + } + + if (pdu_num != srv.link.tx_pdu + 1) + { + LOG_WRN("Invalid pdu number: %u, expected %u", pdu_num, + srv.link.tx_pdu + 1); + outbound_pdu_report_send(); + return 0; + } + + LOG_DBG("0x%02x", buf->data[0]); + + if (atomic_test_bit(srv.flags, NODE_REFRESH)) + { + srv.link.tx_pdu++; + outbound_pdu_report_send(); + srv.refresh.cb->recv(&pb_remote_srv, srv.refresh.cb_data, buf); + } + else + { + srv.link.state = BT_MESH_RPR_LINK_SENDING; + err = bt_mesh_pb_adv.send(buf, pdu_send_complete, &srv); + if (err) + { + link_close(BT_MESH_RPR_ERR_LINK_CLOSED_AS_CANNOT_SEND_PDU, + PROV_BEARER_LINK_STATUS_FAIL); + } + } + + return 0; +} + +const struct bt_mesh_model_op _bt_mesh_rpr_srv_op[] = +{ + { + RPR_OP_SCAN_CAPS_GET, + BT_MESH_LEN_EXACT(0), + handle_scan_caps_get + }, + + { + RPR_OP_SCAN_GET, + BT_MESH_LEN_EXACT(0), + handle_scan_get + }, + + { + RPR_OP_SCAN_START, + BT_MESH_LEN_MIN(2), + handle_scan_start + }, + + { + RPR_OP_EXTENDED_SCAN_START, + BT_MESH_LEN_MIN(1), + handle_extended_scan_start + }, + + { + RPR_OP_SCAN_STOP, + BT_MESH_LEN_EXACT(0), + handle_scan_stop + }, + + { + RPR_OP_LINK_GET, + BT_MESH_LEN_EXACT(0), + handle_link_get + }, + + { + RPR_OP_LINK_OPEN, + BT_MESH_LEN_MIN(1), + handle_link_open + }, + + { + RPR_OP_LINK_CLOSE, + BT_MESH_LEN_EXACT(1), + handle_link_close + }, + + { + RPR_OP_PDU_SEND, + BT_MESH_LEN_MIN(1), + handle_pdu_send + }, + + BT_MESH_MODEL_OP_END, +}; + +static struct bt_mesh_rpr_unprov *adv_handle_beacon( + const struct bt_le_scan_recv_info *info, struct bt_data *ad) +{ + struct bt_uuid_128 uuid_repr = + { + .uuid = + { + BT_UUID_TYPE_128 + } + }; + + struct bt_mesh_rpr_unprov *dev = NULL; + const uint8_t *uuid; + + if (ad->data[0] != 0x00 || (ad->data_len != 19 && ad->data_len != 23)) + { + return NULL; + } + + uuid = &ad->data[1]; + + dev = unprov_get(uuid); + if (!dev) + { + if (srv.scan.state != BT_MESH_RPR_SCAN_MULTI) + { + return NULL; + } + + dev = unprov_get(NULL); + if (!dev) + { + return NULL; + } + + memcpy(dev->uuid, uuid, 16); + dev->flags = BT_MESH_RPR_UNPROV_ACTIVE; + } + else if (dev->flags & BT_MESH_RPR_UNPROV_FOUND) + { + return dev; + } + + dev->oob = sys_get_be16(&ad->data[17]); + dev->rssi = info->rssi; + + if (ad->data_len == 23) + { + memcpy(&dev->hash, &ad->data[19], 4); + dev->flags |= BT_MESH_RPR_UNPROV_HASH; + } + + dev->flags |= BT_MESH_RPR_UNPROV_FOUND; + memcpy(uuid_repr.val, uuid, 16); + + LOG_DBG("Unprov #%u: %s OOB: 0x%04x %s", dev - &srv.scan.devs[0], + bt_uuid_str(&uuid_repr.uuid), + dev->oob, + (dev->flags & BT_MESH_RPR_UNPROV_HASH) ? bt_hex(&dev->hash, + 4) : "(no hash)"); + + if (dev != srv.scan.dev && !(dev->flags & BT_MESH_RPR_UNPROV_REPORTED)) + { + scan_report_schedule(); + } + + return dev; +} + +static bool pull_ad_data(struct net_buf_simple *buf, struct bt_data *ad) +{ + uint8_t len; + + if (!buf->len) + { + return false; + } + + len = net_buf_simple_pull_u8(buf); + if (!len || len > buf->len) + { + return false; + } + + ad->type = net_buf_simple_pull_u8(buf); + ad->data_len = len - sizeof(ad->type); + ad->data = net_buf_simple_pull_mem(buf, ad->data_len); + return true; +} + +static void adv_handle_ext_scan(const struct bt_le_scan_recv_info *info, + struct net_buf_simple *buf) +{ + struct bt_mesh_rpr_unprov *dev = NULL; + struct net_buf_simple_state initial; + struct bt_data ad; + bool uri_match = false; + bool uri_present = false; + bool is_beacon = false; + + if (atomic_test_bit(srv.flags, SCAN_EXT_HAS_ADDR) && + !bt_addr_le_cmp(&srv.scan.addr, info->addr)) + { + dev = srv.scan.dev; + } + + /* Do AD data walk in two rounds: First to figure out which + * unprovisioned device this is (if any), and the second to copy out + * relevant AD data to the extended scan report. + */ + + net_buf_simple_save(buf, &initial); + while (pull_ad_data(buf, &ad)) + { + if (ad.type == BT_DATA_URI) + { + uri_present = true; + } + + if (ad.type == BT_DATA_MESH_BEACON && !dev) + { + dev = adv_handle_beacon(info, &ad); + is_beacon = true; + } + else if (ad.type == BT_DATA_URI && + (srv.scan.dev->flags & BT_MESH_RPR_UNPROV_HASH)) + { + uint8_t hash[16]; + + if (bt_mesh_s1(ad.data, ad.data_len, hash) || + memcmp(hash, &srv.scan.dev->hash, 4)) + { + continue; + } + + LOG_DBG("Found matching URI"); + uri_match = true; + dev = srv.scan.dev; + srv.scan.dev->flags |= BT_MESH_RPR_UNPROV_EXT_ADV_RXD; + } + } + + if (uri_match) + { + atomic_set_bit(srv.flags, URI_MATCHED); + } + + if (!dev) + { + return; + } + + /* Do not process advertisement if it was not identified by URI hash from + * beacon + */ + + if (!(dev->flags & BT_MESH_RPR_UNPROV_EXT_ADV_RXD)) + { + return; + } + + srv.scan.addr = *info->addr; + atomic_set_bit(srv.flags, SCAN_EXT_HAS_ADDR); + + if (IS_ENABLED(CONFIG_BT_MESH_MODEL_LOG_LEVEL_DBG)) + { + struct bt_uuid_128 uuid_repr = + { + .uuid = + { + BT_UUID_TYPE_128 + } + }; + + memcpy(uuid_repr.val, dev->uuid, 16); + LOG_DBG("Is %s", bt_uuid_str(&uuid_repr.uuid)); + } + + net_buf_simple_restore(buf, &initial); + + /* The ADTypeFilter field of the Remote Provisioning Extended Scan Start + * message contains only the URI AD Type, and the URI Hash is not available + * for the device with the Device UUID that was requested in the Remote + * Provisioning Extended Scan Start message. + */ + + if (srv.scan.ad_count == 1 && get_ad_type(srv.scan.ad, 1, BT_DATA_URI) && + !uri_match) + { + goto complete; + } + + while (srv.scan.ad_count && pull_ad_data(buf, &ad)) + { + uint8_t *ad_entry; + + ad_entry = get_ad_type(srv.scan.ad, srv.scan.ad_count, ad.type); + if (!ad_entry || (ad.type == BT_DATA_URI && !uri_match)) + { + continue; + } + + LOG_DBG("AD type 0x%02x", ad.type); + + if (ad.type == BT_DATA_URI) + { + atomic_set_bit(srv.flags, URI_REQUESTED); + } + + if (ad.data_len + 2 > net_buf_simple_tailroom(srv.scan.adv_data)) + { + LOG_WRN("Can't fit AD 0x%02x in scan report", ad.type); + continue; + } + + net_buf_simple_add_u8(srv.scan.adv_data, ad.data_len + 1); + net_buf_simple_add_u8(srv.scan.adv_data, ad.type); + net_buf_simple_add_mem(srv.scan.adv_data, ad.data, ad.data_len); + + *ad_entry = srv.scan.ad[--srv.scan.ad_count]; + } + + /* The Remote Provisioning Server collects AD structures corresponding to + * all AD Types specified in the ADTypeFilter field of the Remote + * Provisioning Extended Scan Start message. The timeout specified in the + * Timeout field of the Remote Provisioning Extended Scan Start message + * expires. OR The ADTypeFilter field of the Remote Provisioning Extended + * Scan Start message contains only the URI AD Type, and the Remote + * Provisioning Server has received an advertising report or scan response + * with the URI corresponding to the URI Hash of the device with the Device + * UUID that was requested in the Remote Provisioning Extended Scan Start + * message. + */ + + if (!srv.scan.ad_count) + { + goto complete; + } + + /* The ADTypeFilter field of the Remote Provisioning Extended Scan Start + * message does not contain the URI AD Type, and the Remote Provisioning + * Server receives and processes the scan response data from the device + * with Device UUID requested in the Remote Provisioning Extended Scan + * Start message. + */ + + if (!is_beacon && !uri_present && + info->adv_type == BT_GAP_ADV_TYPE_SCAN_RSP) + { + goto complete; + } + + /* The ADTypeFilter field of the Remote Provisioning Extended Scan Start + * message contains the URI AD Type and at least one different AD Type in + * the ADTypeFilter field, and the Remote Provisioning Server has received + * an advertising report or scan response with the URI corresponding to the + * URI Hash of the device with the Device UUID that was requested in the + * Remote Provisioning Extended Scan Start message, and the Remote + * Provisioning Server received the scan response from the same device. OR + * The ADTypeFilter field of the Remote Provisioning Extended Scan Start + * message contains the URI AD Type and at least one different AD Type in + * the ADTypeFilter field, and the URI Hash is not available for the device + * with the Device UUID that was requested in the Remote Provisioning + * Extended Scan Start message, and the Remote Provisioning Server received + * the scan response from the same device. + */ + + if (atomic_read(srv.flags) & URI_REQUESTED && + (atomic_read(srv.flags) & URI_MATCHED || + (dev->flags & ~BT_MESH_RPR_UNPROV_HASH)) && + info->adv_type == BT_GAP_ADV_TYPE_SCAN_RSP) + { + goto complete; + } + + return; +complete: + srv.scan.additional_time = 0; + if (srv.scan.state != BT_MESH_RPR_SCAN_MULTI) + { + k_work_cancel_delayable(&srv.scan.timeout); + } + + scan_ext_stop(0); +} + +static void adv_handle_scan(const struct bt_le_scan_recv_info *info, + struct net_buf_simple *buf) +{ + struct bt_data ad; + + if (info->adv_type != BT_HCI_ADV_NONCONN_IND) + { + return; + } + + while (pull_ad_data(buf, &ad)) + { + if (ad.type == BT_DATA_MESH_BEACON) + { + adv_handle_beacon(info, &ad); + return; + } + } +} + +static void scan_packet_recv(const struct bt_le_scan_recv_info *info, + struct net_buf_simple *buf) +{ + if (!atomic_test_bit(srv.flags, SCANNING)) + { + return; + } + + if (srv.scan.dev) + { + adv_handle_ext_scan(info, buf); + } + else + { + adv_handle_scan(info, buf); + } +} + +static struct bt_le_scan_cb scan_cb = +{ + .recv = scan_packet_recv, +}; + +static int rpr_srv_init(const struct bt_mesh_model *mod) +{ + if (mod->rt->elem_idx || srv.mod) + { + LOG_ERR("Remote provisioning server must be initialized " + "on first element"); + return -EINVAL; + } + + srv.mod = mod; + + net_buf_simple_init(srv.scan.adv_data, 0); + + k_work_init_delayable(&srv.scan.timeout, scan_timeout); + k_work_init_delayable(&srv.scan.report, scan_report_timeout); + k_work_init(&srv.link.report, link_report_send_and_clear); + bt_le_scan_cb_register(&scan_cb); + mod->keys[0] = BT_MESH_KEY_DEV_LOCAL; + mod->rt->flags |= BT_MESH_MOD_DEVKEY_ONLY; + + return 0; +} + +static void rpr_srv_reset(const struct bt_mesh_model *mod) +{ + cli_link_clear(); + cli_scan_clear(); + srv.scan.state = BT_MESH_RPR_SCAN_IDLE; + srv.link.state = BT_MESH_RPR_LINK_IDLE; + k_work_cancel_delayable(&srv.scan.timeout); + k_work_cancel_delayable(&srv.scan.report); + net_buf_simple_init(srv.scan.adv_data, 0); + atomic_set(srv.flags, 0); + srv.link.dev = NULL; + srv.scan.dev = NULL; +} + +const struct bt_mesh_model_cb _bt_mesh_rpr_srv_cb = +{ + .init = rpr_srv_init, + .reset = rpr_srv_reset, +}; + +static int node_refresh_link_accept(const struct prov_bearer_cb *cb, + void *cb_data) +{ + srv.refresh.cb = cb; + srv.refresh.cb_data = cb_data; + + return 0; +} + +static void node_refresh_link_cancel(void) +{ + srv.refresh.cb = NULL; + srv.refresh.cb_data = NULL; +} + +static void node_refresh_tx_complete(int err, void *cb_data) +{ + if (err) + { + link_close(BT_MESH_RPR_ERR_LINK_CLOSED_AS_CANNOT_SEND_PDU, + PROV_BEARER_LINK_STATUS_FAIL); + return; + } + + if (srv.refresh.tx.cb) + { + srv.refresh.tx.cb(err, srv.refresh.tx.cb_data); + } +} + +static int node_refresh_buf_send( + struct net_buf_simple *buf, prov_bearer_send_complete_t cb, void *cb_data) +{ + static const struct bt_mesh_send_cb send_cb = + { + .end = node_refresh_tx_complete, + }; + + int err; + + if (!atomic_test_bit(srv.flags, NODE_REFRESH)) + { + return -EBUSY; + } + + srv.refresh.tx.cb = cb; + srv.refresh.tx.cb_data = cb_data; + srv.link.rx_pdu++; + + LOG_DBG("%u", srv.link.rx_pdu); + + err = inbound_pdu_send(buf, &send_cb); + if (err) + { + link_close(BT_MESH_RPR_ERR_LINK_CLOSED_BY_SERVER, + PROV_BEARER_LINK_STATUS_FAIL); + } + + return err; +} + +static void node_refresh_clear_tx(void) +{ + /* Nothing can be done */ +} + +const struct prov_bearer pb_remote_srv = +{ + .type = BT_MESH_PROV_REMOTE, + + .link_accept = node_refresh_link_accept, + .link_cancel = node_refresh_link_cancel, + .send = node_refresh_buf_send, + .clear_tx = node_refresh_clear_tx, +}; diff --git a/libs/zblue/mesh/sar_cfg.c b/libs/zblue/mesh/sar_cfg.c new file mode 100644 index 0000000000000..cea31d5f74de2 --- /dev/null +++ b/libs/zblue/mesh/sar_cfg.c @@ -0,0 +1,88 @@ +/**************************************************************************** + * libs/zblue/mesh/sar_cfg.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "net.h" +#include "sar_cfg_internal.h" + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_mesh_sar_tx_encode(struct net_buf_simple *buf, + const struct bt_mesh_sar_tx *tx) +{ + net_buf_simple_add_u8(buf, (tx->seg_int_step & 0xf) | + (tx->unicast_retrans_count << 4)); + net_buf_simple_add_u8(buf, (tx->unicast_retrans_without_prog_count & 0xf) | + (tx->unicast_retrans_int_step << 4)); + net_buf_simple_add_u8(buf, + (tx->unicast_retrans_int_inc & 0xf) | + (tx->multicast_retrans_count << 4)); + net_buf_simple_add_u8(buf, tx->multicast_retrans_int & 0xf); +} + +void bt_mesh_sar_rx_encode(struct net_buf_simple *buf, + const struct bt_mesh_sar_rx *rx) +{ + net_buf_simple_add_u8(buf, (rx->seg_thresh & 0x1f) | + (rx->ack_delay_inc << 5)); + net_buf_simple_add_u8(buf, (rx->discard_timeout & 0xf) | + ((rx->rx_seg_int_step & 0xf) << 4)); + net_buf_simple_add_u8(buf, (rx->ack_retrans_count & 0x3)); +} + +void bt_mesh_sar_tx_decode(struct net_buf_simple *buf, + struct bt_mesh_sar_tx *tx) +{ + uint8_t val; + + val = net_buf_simple_pull_u8(buf); + tx->seg_int_step = (val & 0xf); + tx->unicast_retrans_count = (val >> 4); + val = net_buf_simple_pull_u8(buf); + tx->unicast_retrans_without_prog_count = (val & 0xf); + tx->unicast_retrans_int_step = (val >> 4); + val = net_buf_simple_pull_u8(buf); + tx->unicast_retrans_int_inc = (val & 0xf); + tx->multicast_retrans_count = (val >> 4); + val = net_buf_simple_pull_u8(buf); + tx->multicast_retrans_int = (val & 0xf); +} + +void bt_mesh_sar_rx_decode(struct net_buf_simple *buf, + struct bt_mesh_sar_rx *rx) +{ + uint8_t val; + + val = net_buf_simple_pull_u8(buf); + rx->seg_thresh = (val & 0x1f); + rx->ack_delay_inc = (val >> 5); + val = net_buf_simple_pull_u8(buf); + rx->discard_timeout = (val & 0xf); + rx->rx_seg_int_step = (val >> 4); + val = net_buf_simple_pull_u8(buf); + rx->ack_retrans_count = (val & 0x3); +} diff --git a/libs/zblue/mesh/sar_cfg_cli.c b/libs/zblue/mesh/sar_cfg_cli.c new file mode 100644 index 0000000000000..fa19cc1ae78d1 --- /dev/null +++ b/libs/zblue/mesh/sar_cfg_cli.c @@ -0,0 +1,280 @@ +/**************************************************************************** + * libs/zblue/mesh/sar_cfg_cli.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include + +#include "net.h" +#include "access.h" +#include "foundation.h" +#include "mesh.h" +#include "sar_cfg_internal.h" + +#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_sar_cfg_cli); + +static struct bt_mesh_sar_cfg_cli *cli; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int transmitter_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_sar_tx *rsp; + + if (!bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_SAR_CFG_TX_STATUS, + ctx->addr, (void **)&rsp)) + { + return 0; + } + + bt_mesh_sar_tx_decode(buf, rsp); + + LOG_DBG("SAR TX {0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x}", + rsp->seg_int_step, + rsp->unicast_retrans_count, + rsp->unicast_retrans_without_prog_count, + rsp->unicast_retrans_int_step, rsp->unicast_retrans_int_inc, + rsp->multicast_retrans_count, + rsp->multicast_retrans_int); + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + + return 0; +} + +static int receiver_status( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_sar_rx *rsp; + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x src 0x%04x len %u: " + "%s", ctx->net_idx, + ctx->app_idx, + ctx->addr, buf->len, bt_hex(buf->data, buf->len)); + + if (!bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_SAR_CFG_RX_STATUS, + ctx->addr, (void **)&rsp)) + { + return 0; + } + + bt_mesh_sar_rx_decode(buf, rsp); + + LOG_DBG("SAR RX {0x%02x 0x%02x 0x%02x 0x%02x 0x%02x}", rsp->seg_thresh, + rsp->ack_delay_inc, + rsp->discard_timeout, + rsp->rx_seg_int_step, + rsp->ack_retrans_count); + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + + return 0; +} + +const struct bt_mesh_model_op _bt_mesh_sar_cfg_cli_op[] = +{ + { + OP_SAR_CFG_TX_STATUS, + BT_MESH_LEN_EXACT(BT_MESH_SAR_TX_LEN), + transmitter_status + }, + + { + OP_SAR_CFG_RX_STATUS, + BT_MESH_LEN_EXACT(BT_MESH_SAR_RX_LEN), + receiver_status + }, + + BT_MESH_MODEL_OP_END, +}; + +int32_t bt_mesh_sar_cfg_cli_timeout_get(void) +{ + return cli->timeout; +} + +void bt_mesh_sar_cfg_cli_timeout_set(int32_t timeout) +{ + cli->timeout = timeout; +} + +static int bt_mesh_sar_cfg_cli_init(const struct bt_mesh_model *model) +{ + if (!bt_mesh_model_in_primary(model)) + { + LOG_ERR("SAR Configuration Client only allowed in primary element"); + return -EINVAL; + } + + if (!model->rt->user_data) + { + LOG_ERR("No SAR Configuration Client context provided"); + return -EINVAL; + } + + cli = model->rt->user_data; + cli->model = model; + cli->timeout = 2 * MSEC_PER_SEC; + + model->keys[0] = BT_MESH_KEY_DEV_ANY; + model->rt->flags |= BT_MESH_MOD_DEVKEY_ONLY; + + bt_mesh_msg_ack_ctx_init(&cli->ack_ctx); + + return 0; +} + +static void bt_mesh_sar_cfg_cli_reset(const struct bt_mesh_model *model) +{ + struct bt_mesh_sar_cfg_cli *model_cli; + + model_cli = model->rt->user_data; + + bt_mesh_msg_ack_ctx_clear(&model_cli->ack_ctx); +} + +const struct bt_mesh_model_cb _bt_mesh_sar_cfg_cli_cb = +{ + .init = bt_mesh_sar_cfg_cli_init, + .reset = bt_mesh_sar_cfg_cli_reset, +}; + +int bt_mesh_sar_cfg_cli_transmitter_get(uint16_t net_idx, + uint16_t addr, + struct bt_mesh_sar_tx *rsp) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_SAR_CFG_TX_GET, 0); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + int err; + + err = bt_mesh_msg_ack_ctx_prepare(&cli->ack_ctx, OP_SAR_CFG_TX_STATUS, + addr, + rsp); + if (err) + { + return err; + } + + bt_mesh_model_msg_init(&msg, OP_SAR_CFG_TX_GET); + + err = bt_mesh_model_send(cli->model, &ctx, &msg, NULL, NULL); + if (err) + { + LOG_ERR("model_send() failed (err %d)", err); + bt_mesh_msg_ack_ctx_clear(&cli->ack_ctx); + return err; + } + + return bt_mesh_msg_ack_ctx_wait(&cli->ack_ctx, MSEC2TICK(cli->timeout)); +} + +int bt_mesh_sar_cfg_cli_transmitter_set(uint16_t net_idx, uint16_t addr, + const struct bt_mesh_sar_tx *set, + struct bt_mesh_sar_tx *rsp) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_SAR_CFG_TX_SET, BT_MESH_SAR_TX_LEN); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + int err; + + err = + bt_mesh_msg_ack_ctx_prepare(&cli->ack_ctx, OP_SAR_CFG_TX_STATUS, addr, + rsp); + if (err) + { + return err; + } + + bt_mesh_model_msg_init(&msg, OP_SAR_CFG_TX_SET); + bt_mesh_sar_tx_encode(&msg, set); + + err = bt_mesh_model_send(cli->model, &ctx, &msg, NULL, NULL); + if (err) + { + LOG_ERR("model_send() failed (err %d)", err); + bt_mesh_msg_ack_ctx_clear(&cli->ack_ctx); + return err; + } + + return bt_mesh_msg_ack_ctx_wait(&cli->ack_ctx, MSEC2TICK(cli->timeout)); +} + +int bt_mesh_sar_cfg_cli_receiver_get(uint16_t net_idx, + uint16_t addr, + struct bt_mesh_sar_rx *rsp) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_SAR_CFG_RX_GET, 0); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + int err; + + err = bt_mesh_msg_ack_ctx_prepare(&cli->ack_ctx, OP_SAR_CFG_RX_STATUS, + addr, + rsp); + if (err) + { + return err; + } + + bt_mesh_model_msg_init(&msg, OP_SAR_CFG_RX_GET); + + err = bt_mesh_model_send(cli->model, &ctx, &msg, NULL, NULL); + if (err) + { + LOG_ERR("model_send() failed (err %d)", err); + bt_mesh_msg_ack_ctx_clear(&cli->ack_ctx); + return err; + } + + return bt_mesh_msg_ack_ctx_wait(&cli->ack_ctx, MSEC2TICK(cli->timeout)); +} + +int bt_mesh_sar_cfg_cli_receiver_set(uint16_t net_idx, + uint16_t addr, + const struct bt_mesh_sar_rx *set, + struct bt_mesh_sar_rx *rsp) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_SAR_CFG_RX_SET, BT_MESH_SAR_RX_LEN); + struct bt_mesh_msg_ctx ctx = BT_MESH_MSG_CTX_INIT_DEV(net_idx, addr); + int err; + + err = + bt_mesh_msg_ack_ctx_prepare(&cli->ack_ctx, OP_SAR_CFG_RX_STATUS, addr, + rsp); + if (err) + { + return err; + } + + bt_mesh_model_msg_init(&msg, OP_SAR_CFG_RX_SET); + bt_mesh_sar_rx_encode(&msg, set); + + err = bt_mesh_model_send(cli->model, &ctx, &msg, NULL, NULL); + if (err) + { + LOG_ERR("model_send() failed (err %d)", err); + bt_mesh_msg_ack_ctx_clear(&cli->ack_ctx); + return err; + } + + return bt_mesh_msg_ack_ctx_wait(&cli->ack_ctx, MSEC2TICK(cli->timeout)); +} diff --git a/libs/zblue/mesh/sar_cfg_internal.h b/libs/zblue/mesh/sar_cfg_internal.h new file mode 100644 index 0000000000000..2511162a314d1 --- /dev/null +++ b/libs/zblue/mesh/sar_cfg_internal.h @@ -0,0 +1,80 @@ +/**************************************************************************** + * libs/zblue/mesh/sar_cfg_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Internal Transport SAR Configuration API + */ + +#ifndef ZEPHYR_SUBSYS_BLUETOOTH_MESH_SAR_CFG_INTERNAL_H__ +#define ZEPHYR_SUBSYS_BLUETOOTH_MESH_SAR_CFG_INTERNAL_H__ + +/** SAR Transmitter state encoded length */ +#define BT_MESH_SAR_TX_LEN 4 +/** SAR Receiver state encoded length */ +#define BT_MESH_SAR_RX_LEN 3 + +/** SAR Transmitter Configuration state initializer */ +#define BT_MESH_SAR_TX_INIT \ + { \ + CONFIG_BT_MESH_SAR_TX_SEG_INT_STEP, \ + CONFIG_BT_MESH_SAR_TX_UNICAST_RETRANS_COUNT, \ + CONFIG_BT_MESH_SAR_TX_UNICAST_RETRANS_WITHOUT_PROG_COUNT, \ + CONFIG_BT_MESH_SAR_TX_UNICAST_RETRANS_INT_STEP, \ + CONFIG_BT_MESH_SAR_TX_UNICAST_RETRANS_INT_INC, \ + CONFIG_BT_MESH_SAR_TX_MULTICAST_RETRANS_COUNT, \ + CONFIG_BT_MESH_SAR_TX_MULTICAST_RETRANS_INT, \ + } + +/** SAR Receiver Configuration state initializer */ +#define BT_MESH_SAR_RX_INIT \ + { \ + CONFIG_BT_MESH_SAR_RX_SEG_THRESHOLD, CONFIG_BT_MESH_SAR_RX_ACK_DELAY_INC, \ + CONFIG_BT_MESH_SAR_RX_DISCARD_TIMEOUT, CONFIG_BT_MESH_SAR_RX_SEG_INT_STEP, \ + CONFIG_BT_MESH_SAR_RX_ACK_RETRANS_COUNT, \ + } + +#define BT_MESH_SAR_TX_SEG_INT_MS ((bt_mesh.sar_tx.seg_int_step + 1) * 10) +#define BT_MESH_SAR_TX_RETRANS_COUNT(addr) \ + (BT_MESH_ADDR_IS_UNICAST(addr) ? (bt_mesh.sar_tx.unicast_retrans_count + 1) \ + : (bt_mesh.sar_tx.multicast_retrans_count + 1)) +#define BT_MESH_SAR_TX_RETRANS_NO_PROGRESS (bt_mesh.sar_tx.unicast_retrans_without_prog_count + 1) +#define BT_MESH_SAR_TX_UNICAST_RETRANS_INT_STEP_MS \ + ((bt_mesh.sar_tx.unicast_retrans_int_step + 1) * 25) +#define BT_MESH_SAR_TX_UNICAST_RETRANS_INT_INC_MS \ + ((bt_mesh.sar_tx.unicast_retrans_int_inc + 1) * 25) +#define BT_MESH_SAR_TX_UNICAST_RETRANS_TIMEOUT_MS(ttl) \ + ((ttl > 0) ? (BT_MESH_SAR_TX_UNICAST_RETRANS_INT_STEP_MS + \ + BT_MESH_SAR_TX_UNICAST_RETRANS_INT_INC_MS * (ttl - 1)) \ + : BT_MESH_SAR_TX_UNICAST_RETRANS_INT_STEP_MS) +#define BT_MESH_SAR_TX_MULTICAST_RETRANS_TIMEOUT_MS \ + ((bt_mesh.sar_tx.multicast_retrans_int + 1) * 25) +#define BT_MESH_SAR_TX_RETRANS_TIMEOUT_MS(addr, ttl) \ + (BT_MESH_ADDR_IS_UNICAST(addr) ? BT_MESH_SAR_TX_UNICAST_RETRANS_TIMEOUT_MS(ttl) \ + : BT_MESH_SAR_TX_MULTICAST_RETRANS_TIMEOUT_MS) + +#define BT_MESH_SAR_RX_SEG_THRESHOLD (bt_mesh.sar_rx.seg_thresh) +#define BT_MESH_SAR_RX_ACK_DELAY_INC_X2 (bt_mesh.sar_rx.ack_delay_inc * 2 + 3) +#define BT_MESH_SAR_RX_ACK_RETRANS_COUNT (bt_mesh.sar_rx.ack_retrans_count + 1) +#define BT_MESH_SAR_RX_SEG_INT_MS ((bt_mesh.sar_rx.rx_seg_int_step + 1) * 10) +#define BT_MESH_SAR_RX_DISCARD_TIMEOUT_MS ((bt_mesh.sar_rx.discard_timeout + 1) * 5 * MSEC_PER_SEC) + +void bt_mesh_sar_tx_encode(struct net_buf_simple *buf, + const struct bt_mesh_sar_tx *tx); +void bt_mesh_sar_rx_encode(struct net_buf_simple *buf, + const struct bt_mesh_sar_rx *rx); +void bt_mesh_sar_tx_decode(struct net_buf_simple *buf, + struct bt_mesh_sar_tx *tx); +void bt_mesh_sar_rx_decode(struct net_buf_simple *buf, + struct bt_mesh_sar_rx *rx); + +#endif /* ZEPHYR_SUBSYS_BLUETOOTH_MESH_SAR_CFG_INTERNAL_H__ */ diff --git a/libs/zblue/mesh/sar_cfg_srv.c b/libs/zblue/mesh/sar_cfg_srv.c new file mode 100644 index 0000000000000..b015d52a99871 --- /dev/null +++ b/libs/zblue/mesh/sar_cfg_srv.c @@ -0,0 +1,255 @@ +/**************************************************************************** + * libs/zblue/mesh/sar_cfg_srv.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "net.h" +#include "access.h" +#include "foundation.h" +#include "mesh.h" +#include "sar_cfg_internal.h" +#include "settings.h" + +#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_sar_cfg_srv); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int sar_rx_store(const struct bt_mesh_model *model, bool delete) +{ + const void *data = delete ? NULL : &bt_mesh.sar_rx; + size_t len = delete ? 0 : sizeof(struct bt_mesh_sar_rx); + + return bt_mesh_model_data_store(model, false, "sar_rx", data, len); +} + +static int sar_tx_store(const struct bt_mesh_model *model, bool delete) +{ + const void *data = delete ? NULL : &bt_mesh.sar_tx; + size_t len = delete ? 0 : sizeof(struct bt_mesh_sar_tx); + + return bt_mesh_model_data_store(model, false, "sar_tx", data, len); +} + +static void transmitter_status(const struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_SAR_CFG_TX_STATUS, BT_MESH_SAR_TX_LEN); + const struct bt_mesh_sar_tx *tx = &bt_mesh.sar_tx; + + LOG_DBG("SAR TX {0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x 0x%02x}", + tx->seg_int_step, + tx->unicast_retrans_count, tx->unicast_retrans_without_prog_count, + tx->unicast_retrans_int_step, tx->unicast_retrans_int_inc, + tx->multicast_retrans_count, + tx->multicast_retrans_int); + + bt_mesh_model_msg_init(&msg, OP_SAR_CFG_TX_STATUS); + bt_mesh_sar_tx_encode(&msg, tx); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send Transmitter Status"); + } +} + +static void receiver_status(const struct bt_mesh_model *model, + struct bt_mesh_msg_ctx *ctx) +{ + BT_MESH_MODEL_BUF_DEFINE(msg, OP_SAR_CFG_RX_STATUS, BT_MESH_SAR_RX_LEN); + const struct bt_mesh_sar_rx *rx = &bt_mesh.sar_rx; + + LOG_DBG("SAR RX {0x%02x 0x%02x 0x%02x 0x%02x 0x%02x}", rx->seg_thresh, + rx->ack_delay_inc, + rx->discard_timeout, rx->rx_seg_int_step, rx->ack_retrans_count); + + bt_mesh_model_msg_init(&msg, OP_SAR_CFG_RX_STATUS); + bt_mesh_sar_rx_encode(&msg, rx); + + if (bt_mesh_model_send(model, ctx, &msg, NULL, NULL)) + { + LOG_ERR("Unable to send Receiver Status"); + } +} + +static int transmitter_get( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + LOG_DBG("src 0x%04x", ctx->addr); + + transmitter_status(model, ctx); + + return 0; +} + +static int transmitter_set( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_sar_tx *tx = &bt_mesh.sar_tx; + + LOG_DBG("src 0x%04x", ctx->addr); + + bt_mesh_sar_tx_decode(buf, tx); + transmitter_status(model, ctx); + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + sar_tx_store(model, false); + } + + return 0; +} + +static int receiver_get( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + LOG_DBG("src 0x%04x", ctx->addr); + + receiver_status(model, ctx); + + return 0; +} + +static int receiver_set( + const struct bt_mesh_model *model, struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct bt_mesh_sar_rx *rx = &bt_mesh.sar_rx; + + LOG_DBG("src 0x%04x", ctx->addr); + + bt_mesh_sar_rx_decode(buf, rx); + receiver_status(model, ctx); + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + sar_rx_store(model, false); + } + + return 0; +} + +const struct bt_mesh_model_op bt_mesh_sar_cfg_srv_op[] = +{ + { + OP_SAR_CFG_TX_GET, + BT_MESH_LEN_EXACT(0), + transmitter_get + }, + + { + OP_SAR_CFG_TX_SET, + BT_MESH_LEN_EXACT(BT_MESH_SAR_TX_LEN), + transmitter_set + }, + + { + OP_SAR_CFG_RX_GET, + BT_MESH_LEN_EXACT(0), + receiver_get + }, + + { + OP_SAR_CFG_RX_SET, + BT_MESH_LEN_EXACT(BT_MESH_SAR_RX_LEN), + receiver_set + }, + + BT_MESH_MODEL_OP_END, +}; + +static int sar_cfg_srv_init(const struct bt_mesh_model *model) +{ + if (!bt_mesh_model_in_primary(model)) + { + LOG_ERR("Configuration Server only allowed in primary element"); + return -EINVAL; + } + + /* SAR Configuration Model security is device-key based and only the local + * device-key is allowed to access this model. + */ + + model->keys[0] = BT_MESH_KEY_DEV_LOCAL; + model->rt->flags |= BT_MESH_MOD_DEVKEY_ONLY; + + return 0; +} + +static void sar_cfg_srv_reset(const struct bt_mesh_model *model) +{ + struct bt_mesh_sar_tx sar_tx = BT_MESH_SAR_TX_INIT; + struct bt_mesh_sar_rx sar_rx = BT_MESH_SAR_RX_INIT; + + bt_mesh.sar_tx = sar_tx; + bt_mesh.sar_rx = sar_rx; + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + sar_rx_store(model, true); + sar_tx_store(model, true); + } +} + +#ifdef CONFIG_BT_SETTINGS +static int sar_cfg_srv_settings_set( + const struct bt_mesh_model *model, const char *name, size_t len_rd, + settings_read_cb read_cb, void *cb_data) +{ + if (!strncmp(name, "sar_rx", 5)) + { + return bt_mesh_settings_set(read_cb, cb_data, &bt_mesh.sar_rx, + sizeof(bt_mesh.sar_rx)); + } + + if (!strncmp(name, "sar_tx", 5)) + { + return bt_mesh_settings_set(read_cb, cb_data, &bt_mesh.sar_tx, + sizeof(bt_mesh.sar_tx)); + } + + return 0; +} +#endif + +const struct bt_mesh_model_cb bt_mesh_sar_cfg_srv_cb = +{ + .init = sar_cfg_srv_init, + .reset = + sar_cfg_srv_reset, +#ifdef CONFIG_BT_SETTINGS + .settings_set = + sar_cfg_srv_settings_set +#endif +}; diff --git a/libs/zblue/mesh/settings.c b/libs/zblue/mesh/settings.c new file mode 100644 index 0000000000000..9596d35b37c90 --- /dev/null +++ b/libs/zblue/mesh/settings.c @@ -0,0 +1,383 @@ +/**************************************************************************** + * libs/zblue/mesh/settings.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2018 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "mesh.h" +#include "subnet.h" +#include "app_keys.h" +#include "net.h" +#include "cdb.h" +#include "crypto.h" +#include "rpl.h" +#include "transport.h" +#include "heartbeat.h" +#include "access.h" +#include "proxy.h" +#include "pb_gatt_srv.h" +#include "settings.h" +#include "cfg.h" +#include "brg_cfg.h" +#include "solicitation.h" +#include "va.h" + +#define LOG_LEVEL CONFIG_BT_MESH_SETTINGS_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_settings); + +#ifdef CONFIG_BT_MESH_RPL_STORE_TIMEOUT +#define RPL_STORE_TIMEOUT CONFIG_BT_MESH_RPL_STORE_TIMEOUT +#else +#define RPL_STORE_TIMEOUT (-1) +#endif + +#ifdef CONFIG_BT_MESH_SETTINGS_WORKQ_PRIO +#define SETTINGS_WORKQ_PRIO CONFIG_BT_MESH_SETTINGS_WORKQ_PRIO +#else +#define SETTINGS_WORKQ_PRIO 1 +#endif + +#ifdef CONFIG_BT_MESH_SETTINGS_WORKQ_STACK_SIZE +#define SETTINGS_WORKQ_STACK_SIZE CONFIG_BT_MESH_SETTINGS_WORKQ_STACK_SIZE +#else +#define SETTINGS_WORKQ_STACK_SIZE 0 +#endif + +/* When true, the replay lists are stored on a deadline of their own. + * Otherwise they are stored together with the generic pending flags. + */ +#define RPL_OWN_TIMEOUT \ + (IS_ENABLED(CONFIG_BT_MESH_RPL_STORAGE_MODE_SETTINGS) && RPL_STORE_TIMEOUT >= 0) + +static struct k_work_q settings_work_q; +static uint64_t settings_work_stack[SETTINGS_WORKQ_STACK_SIZE / + sizeof(uint64_t)]; + +static struct k_work_delayable pending_store; +static struct k_work_delayable rpl_pending_store; +static atomic_define(pending_flags, BT_MESH_SETTINGS_FLAG_COUNT); + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bt_mesh_settings_set(settings_read_cb read_cb, + void *cb_arg, void *out, size_t read_len) +{ + ssize_t len; + + len = read_cb(cb_arg, out, read_len); + if (len < 0) + { + LOG_ERR("Failed to read value (err %zd)", len); + return len; + } + + LOG_HEXDUMP_DBG(out, len, "val"); + + if (len != read_len) + { + LOG_ERR("Unexpected value length (%zd != %zu)", len, read_len); + return -EINVAL; + } + + return 0; +} + +static int mesh_commit(void) +{ + if (!atomic_test_bit(bt_mesh.flags, BT_MESH_INIT)) + { + return 0; + } + + if (!bt_is_ready()) + { + /* The Bluetooth Mesh settings loader calls bt_mesh_start() immediately + * after loading the settings. This is not intended to work before + * bt_enable(). The doc on @ref bt_enable requires the "bt/" settings + * tree to be loaded after @ref bt_enable is completed, so this handler + * will be called again later. + */ + + return 0; + } + + if (!bt_mesh_subnet_next(NULL)) + { + /* Nothing to do since we're not yet provisioned */ + + return 0; + } + + bt_mesh_net_settings_commit(); + bt_mesh_model_settings_commit(); + + atomic_set_bit(bt_mesh.flags, BT_MESH_VALID); + + bt_mesh_start(); + + return 0; +} + +SETTINGS_STATIC_HANDLER_DEFINE_WITH_CPRIO(bt_mesh, + "bt/mesh", + NULL, + NULL, + mesh_commit, + NULL, + + BT_SETTINGS_CPRIO_2); + +/* Pending flags that use 0 as the storage timeout */ +#define NO_WAIT_PENDING_BITS \ + (BIT(BT_MESH_SETTINGS_NET_PENDING) | BIT(BT_MESH_SETTINGS_IV_PENDING) | \ + BIT(BT_MESH_SETTINGS_SEQ_PENDING) | BIT(BT_MESH_SETTINGS_CDB_PENDING)) + +/* Pending flags that use CONFIG_BT_MESH_RPL_STORE_TIMEOUT. Both replay lists + * share the deadline, and each is stored only if its own flag is set. + */ +#define RPL_PENDING_BITS (BIT(BT_MESH_SETTINGS_RPL_PENDING) | BIT(BT_MESH_SETTINGS_SRPL_PENDING)) + +static void schedule_store(struct k_work_delayable *dwork, + uint32_t timeout_ms) +{ + uint32_t remaining_ms; + + remaining_ms = TICK2MSEC(k_work_delayable_remaining_get(dwork)); + LOG_DBG("Waiting %u ms vs rem %u ms", timeout_ms, remaining_ms); + + /* If the new deadline is sooner, override any existing + * deadline; otherwise schedule without changing any existing deadline. + */ + + if (timeout_ms < remaining_ms) + { + if (IS_ENABLED(CONFIG_BT_MESH_SETTINGS_WORKQ)) + { + k_work_reschedule_for_queue(&settings_work_q, dwork, + MSEC2TICK(timeout_ms)); + } + else + { + k_work_reschedule(dwork, MSEC2TICK(timeout_ms)); + } + } + else + { + if (IS_ENABLED(CONFIG_BT_MESH_SETTINGS_WORKQ)) + { + k_work_schedule_for_queue(&settings_work_q, dwork, + MSEC2TICK(timeout_ms)); + } + else + { + k_work_schedule(dwork, MSEC2TICK(timeout_ms)); + } + } +} + +void bt_mesh_settings_store_schedule(enum bt_mesh_settings_flag flag) +{ + atomic_set_bit(pending_flags, flag); + + if (RPL_OWN_TIMEOUT && (BIT(flag) & RPL_PENDING_BITS) != 0) + { + schedule_store(&rpl_pending_store, RPL_STORE_TIMEOUT * MSEC_PER_SEC); + return; + } + + if ((atomic_read(pending_flags) & NO_WAIT_PENDING_BITS) != 0) + { + schedule_store(&pending_store, 0); + } + else + { + schedule_store(&pending_store, + CONFIG_BT_MESH_STORE_TIMEOUT * MSEC_PER_SEC); + } +} + +void bt_mesh_settings_store_cancel(enum bt_mesh_settings_flag flag) +{ + atomic_clear_bit(pending_flags, flag); +} + +static void store_pending_flags(uint32_t mask) +{ + LOG_DBG(""); + + static const struct + { + void (*handler)(void); + } + + handlers[BT_MESH_SETTINGS_FLAG_COUNT] = +{ + [BT_MESH_SETTINGS_RPL_PENDING] = +{ + bt_mesh_rpl_pending_store_all_nodes +}, + + [BT_MESH_SETTINGS_RPL_RESET_PENDING] = +{ + bt_mesh_rpl_pending_store_all_nodes +}, + + [BT_MESH_SETTINGS_NET_KEYS_PENDING] = +{ + bt_mesh_subnet_pending_store +}, + + [BT_MESH_SETTINGS_APP_KEYS_PENDING] = +{ + bt_mesh_app_key_pending_store +}, + + [BT_MESH_SETTINGS_NET_PENDING] = +{ + bt_mesh_net_pending_net_store +}, + + [BT_MESH_SETTINGS_IV_PENDING] = +{ + bt_mesh_net_pending_iv_store +}, + + [BT_MESH_SETTINGS_SEQ_PENDING] = +{ + bt_mesh_net_pending_seq_store +}, + + [BT_MESH_SETTINGS_DEV_KEY_CAND_PENDING] = +{ + bt_mesh_net_pending_dev_key_cand_store +}, + + [BT_MESH_SETTINGS_HB_PUB_PENDING] = +{ + bt_mesh_hb_pub_pending_store +}, + + [BT_MESH_SETTINGS_CFG_PENDING] = +{ + bt_mesh_cfg_pending_store +}, + + [BT_MESH_SETTINGS_COMP_PENDING] = +{ + bt_mesh_comp_data_pending_clear +}, + + [BT_MESH_SETTINGS_MOD_PENDING] = +{ + bt_mesh_model_pending_store +}, + + [BT_MESH_SETTINGS_VA_PENDING] = +{ + bt_mesh_va_pending_store +}, + + [BT_MESH_SETTINGS_CDB_PENDING] = +{ + IS_ENABLED(CONFIG_BT_MESH_CDB) ? bt_mesh_cdb_pending_store : NULL +}, + + [BT_MESH_SETTINGS_SRPL_PENDING] = +{ + IS_ENABLED(CONFIG_BT_MESH_OD_PRIV_PROXY_SRV) ? + bt_mesh_srpl_pending_store : NULL +}, + + [BT_MESH_SETTINGS_SSEQ_PENDING] = +{ + IS_ENABLED(CONFIG_BT_MESH_PROXY_SOLICITATION) ? + bt_mesh_sseq_pending_store : NULL +}, + + [BT_MESH_SETTINGS_BRG_PENDING] = +{ + IS_ENABLED(CONFIG_BT_MESH_BRG_CFG_SRV) ? + bt_mesh_brg_cfg_pending_store : NULL +}, +}; + + for (int i = 0; i < ARRAY_SIZE(handlers); i++) + { + if (handlers[i].handler == NULL) + { + continue; + } + + if ((mask & BIT(i)) == 0) + { + continue; + } + + if (atomic_test_and_clear_bit(pending_flags, i)) + { + handlers[i].handler(); + } + } +} + +static void store_pending(struct k_work *work) +{ + store_pending_flags(RPL_OWN_TIMEOUT ? ~RPL_PENDING_BITS : ~0U); +} + +static void rpl_store_pending(struct k_work *work) +{ + store_pending_flags(RPL_PENDING_BITS); +} + +void bt_mesh_settings_init(void) +{ + if (IS_ENABLED(CONFIG_BT_MESH_SETTINGS_WORKQ)) + { + k_work_queue_start(&settings_work_q, settings_work_stack, + sizeof(settings_work_stack), + K_PRIO_COOP(SETTINGS_WORKQ_PRIO), NULL); + } + + k_work_init_delayable(&pending_store, store_pending); + if (RPL_OWN_TIMEOUT) + { + k_work_init_delayable(&rpl_pending_store, rpl_store_pending); + } +} + +void bt_mesh_settings_store_pending(void) +{ + (void)k_work_cancel_delayable(&pending_store); + if (RPL_OWN_TIMEOUT) + { + (void)k_work_cancel_delayable(&rpl_pending_store); + } + + store_pending_flags(~0U); +} diff --git a/libs/zblue/mesh/settings.h b/libs/zblue/mesh/settings.h new file mode 100644 index 0000000000000..0d493c75e7e6e --- /dev/null +++ b/libs/zblue/mesh/settings.h @@ -0,0 +1,73 @@ +/**************************************************************************** + * libs/zblue/mesh/settings.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2018 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Pending storage actions. */ + +enum bt_mesh_settings_flag +{ + BT_MESH_SETTINGS_RPL_PENDING, + /* Replay list invalidated by an IV Index update, or IV index recovery. + * Stored on CONFIG_BT_MESH_STORE_TIMEOUT rather than the replay list + * timeout. + */ + + BT_MESH_SETTINGS_RPL_RESET_PENDING, + BT_MESH_SETTINGS_NET_KEYS_PENDING, + BT_MESH_SETTINGS_APP_KEYS_PENDING, + BT_MESH_SETTINGS_NET_PENDING, + BT_MESH_SETTINGS_IV_PENDING, + BT_MESH_SETTINGS_SEQ_PENDING, + BT_MESH_SETTINGS_HB_PUB_PENDING, + BT_MESH_SETTINGS_CFG_PENDING, + BT_MESH_SETTINGS_MOD_PENDING, + BT_MESH_SETTINGS_VA_PENDING, + BT_MESH_SETTINGS_CDB_PENDING, + BT_MESH_SETTINGS_SRPL_PENDING, + BT_MESH_SETTINGS_SSEQ_PENDING, + BT_MESH_SETTINGS_COMP_PENDING, + BT_MESH_SETTINGS_DEV_KEY_CAND_PENDING, + BT_MESH_SETTINGS_BRG_PENDING, + + BT_MESH_SETTINGS_FLAG_COUNT, +}; + +#ifdef CONFIG_BT_SETTINGS +#define BT_MESH_SETTINGS_DEFINE(_hname, _subtree, _set) \ + static int pre_##_set(const char *name, size_t len_rd, \ + settings_read_cb read_cb, void *cb_arg) \ + { \ + if (!atomic_test_bit(bt_mesh.flags, BT_MESH_INIT)) \ + { \ + return 0; \ + } \ + \ + return _set(name, len_rd, read_cb, cb_arg); \ + } \ + SETTINGS_STATIC_HANDLER_DEFINE(bt_mesh_##_hname, "bt/mesh/" _subtree, \ + NULL, pre_##_set, NULL, NULL) +#else +/* Declaring non static settings handler helps avoid unnecessary ifdefs + * as well as unused function warning. Since the declared handler structure + * is unused, linker will discard it. + */ +#define BT_MESH_SETTINGS_DEFINE(_hname, _subtree, _set) \ + const struct settings_handler settings_handler_bt_mesh_##_hname = { \ + .h_set = _set, \ + } +#endif + +void bt_mesh_settings_init(void); +void bt_mesh_settings_store_schedule(enum bt_mesh_settings_flag flag); +void bt_mesh_settings_store_cancel(enum bt_mesh_settings_flag flag); +void bt_mesh_settings_store_pending(void); +int bt_mesh_settings_set(settings_read_cb read_cb, + void *cb_arg, void *out, size_t read_len); diff --git a/libs/zblue/mesh/sol_pdu_rpl_cli.c b/libs/zblue/mesh/sol_pdu_rpl_cli.c new file mode 100644 index 0000000000000..c48a38d573660 --- /dev/null +++ b/libs/zblue/mesh/sol_pdu_rpl_cli.c @@ -0,0 +1,229 @@ +/**************************************************************************** + * libs/zblue/mesh/sol_pdu_rpl_cli.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include "foundation.h" +#include "msg.h" + +#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_sol_pdu_rpl_cli); + +static struct bt_mesh_sol_pdu_rpl_cli *cli; + +static int32_t msg_timeout; + +struct sol_rpl_param +{ + uint16_t *start; + uint8_t *len; +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int handle_status(const struct bt_mesh_model *mod, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + struct sol_rpl_param *param; + uint16_t primary; + uint16_t range; + uint8_t len = 0; + + LOG_DBG(""); + + if (buf->len > 3) + { + return -EMSGSIZE; + } + + range = net_buf_simple_pull_le16(buf); + primary = range >> 1; + if (primary == 0) + { + return -EINVAL; + } + + if (range & BIT(0)) + { + if (buf->len == 0) + { + return -EMSGSIZE; + } + + len = net_buf_simple_pull_u8(buf); + + if (len < 2) + { + return -EINVAL; + } + } + + LOG_DBG("SRPL clear status received: range start: %u, range len: %u", + primary, len); + + if (bt_mesh_msg_ack_ctx_match(&cli->ack_ctx, OP_SOL_PDU_RPL_ITEM_STATUS, + ctx->addr, + (void **)¶m)) + { + if (param->start) + { + *param->start = primary; + } + + if (param->len) + { + *param->len = len; + } + + bt_mesh_msg_ack_ctx_rx(&cli->ack_ctx); + } + + if (cli->srpl_status) + { + cli->srpl_status(cli, ctx->addr, primary, len); + } + + return 0; +} + +static void sol_pdu_rpl_clear_pdu_create(uint16_t range_start, + uint8_t range_len, + struct net_buf_simple *msg) +{ + uint16_t range; + + range = range_start << 1 | (range_len >= 2 ? 1U : 0); + net_buf_simple_add_le16(msg, range); + if (range_len >= 2) + { + net_buf_simple_add_u8(msg, range_len); + } +} + +int bt_mesh_sol_pdu_rpl_clear(struct bt_mesh_msg_ctx *ctx, + uint16_t range_start, + uint8_t range_len, + uint16_t *start_rsp, + uint8_t *len_rsp) +{ + struct sol_rpl_param param = + { + .start = start_rsp, + .len = len_rsp, + }; + + const struct bt_mesh_msg_rsp_ctx rsp = + { + .ack = &cli->ack_ctx, + .op = OP_SOL_PDU_RPL_ITEM_STATUS, + .user_data = ¶m, + .timeout = msg_timeout, + }; + + if (range_len == 1) + { + LOG_ERR("Invalid range length"); + return -EINVAL; + } + + if ((range_start + (range_len > 1 ? range_len : 0)) > 0x8000 || + range_start == 0) + { + LOG_ERR("Range outside unicast address range"); + return -EINVAL; + } + + BT_MESH_MODEL_BUF_DEFINE(msg, + OP_SOL_PDU_RPL_ITEM_CLEAR, + range_len >= 2 ? 3 : 2); + + bt_mesh_model_msg_init(&msg, OP_SOL_PDU_RPL_ITEM_CLEAR); + + sol_pdu_rpl_clear_pdu_create(range_start, range_len, &msg); + + return bt_mesh_msg_ackd_send(cli->model, ctx, &msg, (start_rsp && + len_rsp) ? &rsp : NULL); +} + +int bt_mesh_sol_pdu_rpl_clear_unack(struct bt_mesh_msg_ctx *ctx, + uint16_t range_start, + uint8_t range_len) +{ + if (range_len == 1) + { + LOG_ERR("Invalid range length"); + return -EINVAL; + } + + if ((range_start + (range_len > 1 ? range_len : 0)) > 0x8000 || + range_start == 0) + { + LOG_ERR("Range outside unicast address range"); + return -EINVAL; + } + + BT_MESH_MODEL_BUF_DEFINE(msg, + OP_SOL_PDU_RPL_ITEM_CLEAR, + range_len >= 2 ? 3 : 2); + + bt_mesh_model_msg_init(&msg, OP_SOL_PDU_RPL_ITEM_CLEAR_UNACKED); + + sol_pdu_rpl_clear_pdu_create(range_start, range_len, &msg); + + return bt_mesh_msg_send(cli->model, ctx, &msg); +} + +const struct bt_mesh_model_op _bt_mesh_sol_pdu_rpl_cli_op[] = +{ + { + OP_SOL_PDU_RPL_ITEM_STATUS, + BT_MESH_LEN_MIN(2), + handle_status + }, + + BT_MESH_MODEL_OP_END +}; + +void bt_mesh_sol_pdu_rpl_cli_timeout_set(int32_t timeout) +{ + msg_timeout = timeout; +} + +static int sol_pdu_rpl_cli_init(const struct bt_mesh_model *mod) +{ + if (!bt_mesh_model_in_primary(mod)) + { + LOG_ERR("Solicitation PDU RPL Configuration client not in primary " + "element"); + return -EINVAL; + } + + msg_timeout = CONFIG_BT_MESH_SOL_PDU_RPL_CLI_TIMEOUT; + + cli = mod->rt->user_data; + cli->model = mod; + bt_mesh_msg_ack_ctx_init(&cli->ack_ctx); + return 0; +} + +const struct bt_mesh_model_cb _bt_mesh_sol_pdu_rpl_cli_cb = +{ + .init = sol_pdu_rpl_cli_init, +}; diff --git a/libs/zblue/mesh/sol_pdu_rpl_srv.c b/libs/zblue/mesh/sol_pdu_rpl_srv.c new file mode 100644 index 0000000000000..f3d08919a7341 --- /dev/null +++ b/libs/zblue/mesh/sol_pdu_rpl_srv.c @@ -0,0 +1,152 @@ +/**************************************************************************** + * libs/zblue/mesh/sol_pdu_rpl_srv.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include "foundation.h" +#include "subnet.h" +#include "solicitation.h" + +#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_sol_pdu_rpl_srv); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void sol_rpl_status_rsp(const struct bt_mesh_model *mod, + struct bt_mesh_msg_ctx *ctx, + uint16_t range, + uint8_t len) +{ + BT_MESH_MODEL_BUF_DEFINE(buf, + OP_SOL_PDU_RPL_ITEM_STATUS, + 2 + (len < 2 ? 0 : 1)); + + bt_mesh_model_msg_init(&buf, OP_SOL_PDU_RPL_ITEM_STATUS); + + net_buf_simple_add_le16(&buf, range); + + if (len >= 2) + { + net_buf_simple_add_u8(&buf, len); + } + + bt_mesh_model_send(mod, ctx, &buf, NULL, NULL); +} + +static int item_clear(const struct bt_mesh_model *mod, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf, + bool acked) +{ + uint16_t primary; + uint16_t range; + uint8_t len = 0; + + LOG_DBG(""); + + if (buf->len > 3) + { + return -EMSGSIZE; + } + + range = net_buf_simple_pull_le16(buf); + primary = range >> 1; + + LOG_DBG("Start address: 0x%02x, %d", primary, buf->len); + if (range & BIT(0)) + { + if (buf->len == 0) + { + return -EMSGSIZE; + } + + len = net_buf_simple_pull_u8(buf); + + if (len < 2) + { + return -EINVAL; + } + } + + if ((primary + len) > 0x8000 || primary == 0) + { + LOG_WRN("Range outside unicast address range or equal to 0"); + return -EINVAL; + } + + for (int i = 0; i < len || i == 0; i++) + { + bt_mesh_srpl_entry_clear(primary + i); + } + + if (acked) + { + sol_rpl_status_rsp(mod, ctx, range, len); + } + + return 0; +} + +static int handle_item_clear(const struct bt_mesh_model *mod, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + return item_clear(mod, ctx, buf, true); +} + +static int handle_item_clear_unacked(const struct bt_mesh_model *mod, + struct bt_mesh_msg_ctx *ctx, + struct net_buf_simple *buf) +{ + return item_clear(mod, ctx, buf, false); +} + +const struct bt_mesh_model_op _bt_mesh_sol_pdu_rpl_srv_op[] = +{ + { + OP_SOL_PDU_RPL_ITEM_CLEAR, + BT_MESH_LEN_MIN(2), + handle_item_clear + }, + + { + OP_SOL_PDU_RPL_ITEM_CLEAR_UNACKED, + BT_MESH_LEN_MIN(2), + handle_item_clear_unacked + }, + + BT_MESH_MODEL_OP_END +}; + +static int sol_pdu_rpl_srv_init(const struct bt_mesh_model *mod) +{ + if (!bt_mesh_model_in_primary(mod)) + { + LOG_ERR("Solicitation PDU RPL Configuration server not in primary " + "element"); + return -EINVAL; + } + + return 0; +} + +const struct bt_mesh_model_cb _bt_mesh_sol_pdu_rpl_srv_cb = +{ + .init = sol_pdu_rpl_srv_init, +}; diff --git a/libs/zblue/mesh/solicitation.c b/libs/zblue/mesh/solicitation.c new file mode 100644 index 0000000000000..a2d0cef06c1fb --- /dev/null +++ b/libs/zblue/mesh/solicitation.c @@ -0,0 +1,671 @@ +/**************************************************************************** + * libs/zblue/mesh/solicitation.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include "access.h" +#include "cfg.h" +#include "crypto.h" +#include "mesh.h" +#include "net.h" +#include "proxy.h" +#include "settings.h" + +#include "common/bt_str.h" + +#include "host/hci_core.h" + +#define LOG_LEVEL CONFIG_BT_MESH_MODEL_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_solicitation); + +#if CONFIG_BT_MESH_OD_PRIV_PROXY_SRV +static struct srpl_entry +{ + uint32_t sseq; + uint16_t ssrc; +} + +sol_pdu_rpl[CONFIG_BT_MESH_PROXY_SRPL_SIZE]; + +static atomic_define(store, CONFIG_BT_MESH_PROXY_SRPL_SIZE); +static atomic_t clear; +#endif +#if CONFIG_BT_MESH_PROXY_SOLICITATION +static uint32_t sseq_out; +#endif + +#if CONFIG_BT_MESH_OD_PRIV_PROXY_SRV + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static struct srpl_entry *srpl_find_by_addr(uint16_t ssrc) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(sol_pdu_rpl); i++) + { + if (sol_pdu_rpl[i].ssrc == ssrc) + { + return &sol_pdu_rpl[i]; + } + } + + return NULL; +} + +static int srpl_entry_save(struct bt_mesh_subnet *sub, + uint32_t sseq, uint16_t ssrc) +{ + struct srpl_entry *entry; + + if (!BT_MESH_ADDR_IS_UNICAST(ssrc)) + { + LOG_DBG("Addr not in unicast range"); + return -EINVAL; + } + + entry = srpl_find_by_addr(ssrc); + if (entry) + { + if (entry->sseq >= sseq) + { + LOG_WRN("Higher or equal SSEQ already saved for this SSRC"); + return -EALREADY; + } + } + else + { + entry = srpl_find_by_addr(BT_MESH_ADDR_UNASSIGNED); + if (!entry) + { + /* No space to save new PDU in RPL for this SSRC + * and this PDU is first for this SSRC + */ + + return -ENOMEM; + } + } + + entry->sseq = sseq; + entry->ssrc = ssrc; + + LOG_DBG("Added: SSRC %d SSEQ %d to SRPL", entry->ssrc, entry->sseq); + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + atomic_set_bit(store, entry - &sol_pdu_rpl[0]); + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_SRPL_PENDING); + } + + return 0; +} +#endif + +void bt_mesh_sseq_pending_store(void) +{ +#if CONFIG_BT_MESH_PROXY_SOLICITATION + char *path = "bt/mesh/SSeq"; + int err; + + if (sseq_out) + { + err = settings_save_one(path, &sseq_out, sizeof(sseq_out)); + } + else + { + err = settings_delete(path); + } + + if (err) + { + LOG_ERR("Failed to %s SSeq %s value", + (sseq_out == 0 ? "delete" : "store"), path); + } + else + { + LOG_DBG("%s %s value", (sseq_out == 0 ? "Deleted" : "Stored"), path); + } +#endif +} + +#if CONFIG_BT_MESH_PROXY_SOLICITATION +static int sseq_set(const char *name, + size_t len_rd, settings_read_cb read_cb, void *cb_arg) +{ + int err; + + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return 0; + } + + err = bt_mesh_settings_set(read_cb, cb_arg, &sseq_out, sizeof(sseq_out)); + if (err) + { + LOG_ERR("Failed to set \'sseq\'"); + return err; + } + + LOG_DBG("Restored SSeq value 0x%06x", sseq_out); + + return 0; +} + +BT_MESH_SETTINGS_DEFINE(sseq, "SSeq", sseq_set); +#endif + +#if CONFIG_BT_MESH_OD_PRIV_PROXY_SRV +static bool sol_pdu_decrypt(struct bt_mesh_subnet *sub, void *data) +{ + struct net_buf_simple *in = data; + struct net_buf_simple *out = NET_BUF_SIMPLE(17); + int err; + int i; + uint32_t sseq; + uint16_t ssrc; + + for (i = 0; i < ARRAY_SIZE(sub->keys); i++) + { + if (!sub->keys[i].valid) + { + LOG_ERR("invalid keys %d", i); + continue; + } + + net_buf_simple_init(out, 0); + net_buf_simple_add_mem(out, in->data, in->len); + + err = bt_mesh_net_obfuscate(out->data, 0, &sub->keys[i].msg.privacy); + if (err) + { + LOG_DBG("obfuscation err %d", err); + continue; + } + + err = bt_mesh_net_decrypt(&sub->keys[i].msg.enc, out, 0, + BT_MESH_NONCE_SOLICITATION); + if (!err) + { + LOG_DBG("Decrypted PDU %s", bt_hex(out->data, out->len)); + memcpy(&sseq, &out->data[2], 3); + memcpy(&ssrc, &out->data[5], 2); + err = srpl_entry_save(sub, sys_be24_to_cpu(sseq), + sys_be16_to_cpu(ssrc)); + return err ? false : true; + } + + LOG_DBG("decrypt err %d", err); + } + + return false; +} +#endif + +void bt_mesh_sol_recv(struct net_buf_simple *buf, uint8_t uuid_list_len) +{ +#if CONFIG_BT_MESH_OD_PRIV_PROXY_SRV + uint8_t type; + struct bt_mesh_subnet *sub; + uint16_t uuid; + uint8_t reported_len; + uint8_t svc_data_type; + bool sol_uuid_found = false; + bool svc_data_found = false; + + if (bt_mesh_gatt_proxy_get() == BT_MESH_GATT_PROXY_ENABLED || + bt_mesh_priv_gatt_proxy_get() == BT_MESH_GATT_PROXY_ENABLED || + bt_mesh_od_priv_proxy_get() == 0) + { + LOG_DBG("Not soliciting"); + return; + } + + /* Get rid of ad_type that was checked in bt_mesh_scan_cb */ + + type = net_buf_simple_pull_u8(buf); + if (type != BT_DATA_UUID16_SOME && type != BT_DATA_UUID16_ALL) + { + LOG_DBG("Invalid type 0x%x, expected 0x%x or 0x%x", type, + BT_DATA_UUID16_SOME, + BT_DATA_UUID16_ALL); + return; + } + + if (buf->len < 24) + { + LOG_DBG("Invalid length (%u) Solicitation PDU", buf->len); + return; + } + + while (uuid_list_len >= 2) + { + uuid = net_buf_simple_pull_le16(buf); + if (uuid == BT_UUID_MESH_PROXY_SOLICITATION_VAL) + { + sol_uuid_found = true; + } + + uuid_list_len -= 2; + } + + if (!sol_uuid_found) + { + LOG_DBG("No solicitation UUID found"); + return; + } + + while (buf->len >= 22) + { + reported_len = net_buf_simple_pull_u8(buf); + svc_data_type = net_buf_simple_pull_u8(buf); + uuid = net_buf_simple_pull_le16(buf); + + if (reported_len == 21 && svc_data_type == BT_DATA_SVC_DATA16 && + uuid == BT_UUID_MESH_PROXY_SOLICITATION_VAL) + { + svc_data_found = true; + break; + } + + if (reported_len < 3 || buf->len <= reported_len - 3) + { + LOG_DBG("Invalid length: buf->len=%u reported_len=%u Solicitation " + "PDU", + buf->len, + reported_len); + return; + } + + net_buf_simple_pull_mem(buf, reported_len - 3); + } + + if (!svc_data_found) + { + LOG_DBG("No solicitation service data found"); + return; + } + + type = net_buf_simple_pull_u8(buf); + if (type != 0) + { + LOG_DBG("Invalid type %d, expected 0x00", type); + return; + } + + /* Trim any trailing AD structures left attached by the scan callback: + * the encrypted Solicitation PDU has a fixed length of 17 octets. + */ + + if (buf->len < 17) + { + LOG_DBG("Truncated Solicitation PDU (len %u)", buf->len); + return; + } + + buf->len = 17; + + sub = bt_mesh_subnet_find(sol_pdu_decrypt, (void *)buf); + if (!sub) + { + LOG_DBG("Unable to find subnetwork for received solicitation PDU"); + return; + } + + LOG_DBG("Decrypted solicitation PDU for existing subnet"); + + sub->solicited = true; + bt_mesh_adv_gatt_update(); +#endif +} + +int bt_mesh_proxy_solicit(uint16_t net_idx) +{ +#if CONFIG_BT_MESH_PROXY_SOLICITATION + struct bt_mesh_subnet *sub; + + sub = bt_mesh_subnet_get(net_idx); + if (!sub) + { + LOG_ERR("No subnet with net_idx %d", net_idx); + return -EINVAL; + } + + if (sub->sol_tx == true) + { + LOG_ERR("Solicitation already scheduled for this subnet"); + return -EALREADY; + } + + /* SSeq reached its maximum value */ + + if (sseq_out > 0xffffff) + { + LOG_ERR("SSeq out of range"); + return -EOVERFLOW; + } + + sub->sol_tx = true; + + bt_mesh_adv_gatt_update(); + return 0; +#else + return -ENOTSUP; +#endif +} + +#if CONFIG_BT_MESH_PROXY_SOLICITATION +static int sol_pdu_create(struct bt_mesh_subnet *sub, + struct net_buf_simple *pdu) +{ + int err; + + net_buf_simple_add_u8(pdu, sub->keys[SUBNET_KEY_TX_IDX(sub)].msg.nid); + + /* CTL = 1, TTL = 0 */ + + net_buf_simple_add_u8(pdu, 0x80); + net_buf_simple_add_le24(pdu, sys_cpu_to_be24(sseq_out)); + net_buf_simple_add_le16(pdu, sys_cpu_to_be16(bt_mesh_primary_addr())); + + /* DST = 0x0000 */ + + net_buf_simple_add_le16(pdu, 0x0000); + + err = bt_mesh_net_encrypt(&sub->keys[SUBNET_KEY_TX_IDX(sub)].msg.enc, pdu, + 0, + BT_MESH_NONCE_SOLICITATION); + + if (err) + { + LOG_ERR("Encryption failed, err=%d", err); + return err; + } + + err = bt_mesh_net_obfuscate(pdu->data, 0, + &sub->keys[SUBNET_KEY_TX_IDX(sub)].msg.privacy); + if (err) + { + LOG_ERR("Obfuscation failed, err=%d", err); + return err; + } + + net_buf_simple_push_u8(pdu, 0); + net_buf_simple_push_le16(pdu, BT_UUID_MESH_PROXY_SOLICITATION_VAL); + + return 0; +} +#endif + +#if CONFIG_BT_MESH_OD_PRIV_PROXY_SRV +static int srpl_set(const char *name, + size_t len_rd, settings_read_cb read_cb, void *cb_arg) +{ + struct srpl_entry *entry; + int err; + uint16_t ssrc; + uint32_t sseq; + + if (!name) + { + LOG_ERR("Insufficient number of arguments"); + return -ENOENT; + } + + ssrc = strtol(name, NULL, 16); + entry = srpl_find_by_addr(ssrc); + + if (len_rd == 0) + { + LOG_DBG("val (null)"); + if (entry) + { + (void)memset(entry, 0, sizeof(*entry)); + } + else + { + LOG_WRN("Unable to find RPL entry for 0x%04x", ssrc); + } + + return 0; + } + + if (!entry) + { + entry = srpl_find_by_addr(BT_MESH_ADDR_UNASSIGNED); + if (!entry) + { + LOG_ERR("Unable to allocate SRPL entry for 0x%04x", ssrc); + return -ENOMEM; + } + } + + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return 0; + } + + err = bt_mesh_settings_set(read_cb, cb_arg, &sseq, sizeof(sseq)); + if (err) + { + LOG_ERR("Failed to set \'sseq\'"); + return err; + } + + entry->ssrc = ssrc; + entry->sseq = sseq; + + LOG_DBG("SRPL entry for 0x%04x: Seq 0x%06x", entry->ssrc, entry->sseq); + + return 0; +} + +BT_MESH_SETTINGS_DEFINE(srpl, "SRPL", srpl_set); +#endif + +#if CONFIG_BT_MESH_OD_PRIV_PROXY_SRV +static void srpl_entry_clear(int i) +{ + uint16_t addr = sol_pdu_rpl[i].ssrc; + + LOG_DBG("Removing entry SSRC: %d, SSEQ: %d from RPL", sol_pdu_rpl[i].ssrc, + sol_pdu_rpl[i].sseq); + sol_pdu_rpl[i].ssrc = 0; + sol_pdu_rpl[i].sseq = 0; + + atomic_clear_bit(store, i); + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + char path[18]; + + snprintf(path, sizeof(path), "bt/mesh/SRPL/%x", addr); + + settings_delete(path); + } +} + +static void srpl_store(struct srpl_entry *entry) +{ + char path[18]; + int err; + + LOG_DBG("src 0x%04x seq 0x%06x", entry->ssrc, entry->sseq); + + snprintf(path, sizeof(path), "bt/mesh/SRPL/%x", entry->ssrc); + + err = settings_save_one(path, &entry->sseq, sizeof(entry->sseq)); + if (err) + { + LOG_ERR("Failed to store RPL %s value", path); + } + else + { + LOG_DBG("Stored RPL %s value", path); + } +} +#endif + +void bt_mesh_srpl_pending_store(void) +{ +#if CONFIG_BT_MESH_OD_PRIV_PROXY_SRV + bool clr; + + clr = atomic_cmpxchg_value(&clear, 1, 0); + + for (int i = 0; i < ARRAY_SIZE(sol_pdu_rpl); i++) + { + LOG_DBG("src 0x%04x seq 0x%06x", sol_pdu_rpl[i].ssrc, + sol_pdu_rpl[i].sseq); + + if (clr) + { + srpl_entry_clear(i); + } + else if (atomic_test_and_clear_bit(store, i)) + { + srpl_store(&sol_pdu_rpl[i]); + } + } +#endif +} + +void bt_mesh_srpl_entry_clear(uint16_t addr) +{ +#if CONFIG_BT_MESH_OD_PRIV_PROXY_SRV + struct srpl_entry *entry; + + if (!BT_MESH_ADDR_IS_UNICAST(addr)) + { + LOG_DBG("Addr not in unicast range"); + return; + } + + entry = srpl_find_by_addr(addr); + if (!entry) + { + return; + } + + srpl_entry_clear(entry - &sol_pdu_rpl[0]); +#endif +} + +void bt_mesh_sol_reset(void) +{ +#if CONFIG_BT_MESH_PROXY_SOLICITATION + sseq_out = 0; + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_SSEQ_PENDING); + } +#endif + +#if CONFIG_BT_MESH_OD_PRIV_PROXY_SRV + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + (void)atomic_cmpxchg_value(&clear, 0, 1); + + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_SRPL_PENDING); + } +#endif +} + +#if CONFIG_BT_MESH_PROXY_SOLICITATION +static bool sol_subnet_find(struct bt_mesh_subnet *sub, void *cb_data) +{ + return sub->sol_tx; +} +#endif + +int bt_mesh_sol_send(void) +{ +#if CONFIG_BT_MESH_PROXY_SOLICITATION + uint16_t adv_int; + struct bt_mesh_subnet *sub; + int err; + + NET_BUF_SIMPLE_DEFINE(pdu, 20); + + sub = bt_mesh_subnet_find(sol_subnet_find, NULL); + if (!sub) + { + return -ENOENT; + } + + /* SSeq reached its maximum value */ + + if (sseq_out > 0xffffff) + { + LOG_ERR("SSeq out of range"); + sub->sol_tx = false; + return -EOVERFLOW; + } + + net_buf_simple_init(&pdu, 3); + + adv_int = BT_MESH_TRANSMIT_INT(CONFIG_BT_MESH_SOL_ADV_XMIT); + + err = sol_pdu_create(sub, &pdu); + if (err) + { + LOG_ERR("Failed to create Solicitation PDU, err=%d", err); + return err; + } + + struct bt_data ad[] = + { + BT_DATA_BYTES(BT_DATA_FLAGS, + (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)), + BT_DATA_BYTES(BT_DATA_UUID16_ALL, + BT_UUID_16_ENCODE(BT_UUID_MESH_PROXY_SOLICITATION_VAL)), + BT_DATA(BT_DATA_SVC_DATA16, pdu.data, pdu.size), + }; + + err = bt_mesh_adv_bt_data_send(CONFIG_BT_MESH_SOL_ADV_XMIT, adv_int, ad, + 3); + if (err) + { + LOG_ERR("Failed to advertise Solicitation PDU, err=%d", err); + + sub->sol_tx = false; + + return err; + } + + sub->sol_tx = false; + + sseq_out++; + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_SSEQ_PENDING); + } + + return 0; +#else + return -ENOTSUP; +#endif +} diff --git a/libs/zblue/mesh/solicitation.h b/libs/zblue/mesh/solicitation.h new file mode 100644 index 0000000000000..174b3a2b6e684 --- /dev/null +++ b/libs/zblue/mesh/solicitation.h @@ -0,0 +1,23 @@ +/**************************************************************************** + * libs/zblue/mesh/solicitation.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2021 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +void bt_mesh_sol_reset(void); + +void bt_mesh_sol_recv(struct net_buf_simple *buf, uint8_t uuid_list_len); + +void bt_mesh_srpl_entry_clear(uint16_t addr); + +void bt_mesh_srpl_pending_store(void); + +void bt_mesh_sseq_pending_store(void); + +int bt_mesh_sol_send(void); diff --git a/libs/zblue/mesh/statistic.c b/libs/zblue/mesh/statistic.c new file mode 100644 index 0000000000000..2037c7433d39a --- /dev/null +++ b/libs/zblue/mesh/statistic.c @@ -0,0 +1,213 @@ +/**************************************************************************** + * libs/zblue/mesh/statistic.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include "net.h" +#include "statistic.h" + +static struct bt_mesh_statistic stat; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_mesh_stat_get(struct bt_mesh_statistic *st) +{ + memcpy(st, &stat, sizeof(struct bt_mesh_statistic)); +} + +void bt_mesh_stat_reset(void) +{ + memset(&stat, 0, sizeof(struct bt_mesh_statistic)); +} + +void bt_mesh_stat_planned_count(struct bt_mesh_adv_ctx *ctx) +{ + if (ctx->tag == BT_MESH_ADV_TAG_LOCAL) + { + stat.tx_local_planned++; + } + else if (ctx->tag == BT_MESH_ADV_TAG_RELAY) + { + stat.tx_adv_relay_planned++; + } + else if (ctx->tag == BT_MESH_ADV_TAG_FRIEND) + { + stat.tx_friend_planned++; + } +} + +void bt_mesh_stat_succeeded_count(struct bt_mesh_adv_ctx *ctx) +{ + if (ctx->tag == BT_MESH_ADV_TAG_LOCAL) + { + stat.tx_local_succeeded++; + } + else if (ctx->tag == BT_MESH_ADV_TAG_RELAY) + { + stat.tx_adv_relay_succeeded++; + } + else if (ctx->tag == BT_MESH_ADV_TAG_FRIEND) + { + stat.tx_friend_succeeded++; + } +} + +void bt_mesh_stat_rx(enum bt_mesh_net_if net_if) +{ + switch (net_if) + { + case BT_MESH_NET_IF_ADV: + stat.rx_adv++; + break; + case BT_MESH_NET_IF_LOCAL: + stat.rx_loopback++; + break; + case BT_MESH_NET_IF_PROXY: + case BT_MESH_NET_IF_PROXY_CFG: + stat.rx_proxy++; + break; + default: + stat.rx_uknown++; + break; + } +} + +#if defined(CONFIG_BT_MESH_LOW_POWER) + +static struct bt_mesh_lpn_timing lpn_timing; + +static int64_t poll_tx_end_ts; +static int64_t scan_start_ts; + +void bt_mesh_stat_lpn_timing_update_poll_tx(void) +{ + poll_tx_end_ts = clock_systime_ticks(); +} + +void bt_mesh_stat_lpn_timing_update_scan_start(void) +{ + int64_t now = clock_systime_ticks(); + uint32_t delay; + + scan_start_ts = now; + + if (poll_tx_end_ts == 0) + { + return; + } + + delay = TICK2USEC(now - poll_tx_end_ts); + lpn_timing.recv_delay_us = delay; + + if (lpn_timing.cnt == 0 && lpn_timing.recv_delay_min_us == 0) + { + lpn_timing.recv_delay_min_us = delay; + lpn_timing.recv_delay_max_us = delay; + } + else + { + if (delay < lpn_timing.recv_delay_min_us) + { + lpn_timing.recv_delay_min_us = delay; + } + + if (delay > lpn_timing.recv_delay_max_us) + { + lpn_timing.recv_delay_max_us = delay; + } + } +} + +void bt_mesh_stat_lpn_timing_update_response_rx(void) +{ + int64_t now = clock_systime_ticks(); + uint32_t win; + + if (scan_start_ts == 0) + { + return; + } + + win = TICK2USEC(now - scan_start_ts); + lpn_timing.recv_win_us = win; + + if (lpn_timing.cnt == 0) + { + lpn_timing.recv_win_min_us = win; + lpn_timing.recv_win_max_us = win; + } + else + { + if (win < lpn_timing.recv_win_min_us) + { + lpn_timing.recv_win_min_us = win; + } + + if (win > lpn_timing.recv_win_max_us) + { + lpn_timing.recv_win_max_us = win; + } + } + + lpn_timing.cnt++; +} + +void bt_mesh_stat_lpn_timing_update_win_expired(void) +{ + int64_t now = clock_systime_ticks(); + + if (scan_start_ts == 0) + { + return; + } + + lpn_timing.recv_win_expected_us = TICK2USEC(now - scan_start_ts); + lpn_timing.cnt_failed++; +} + +int bt_mesh_stat_lpn_timing_get(struct bt_mesh_lpn_timing *timing) +{ + if (timing == NULL) + { + return -EINVAL; + } + + memcpy(timing, &lpn_timing, sizeof(*timing)); + return 0; +} + +void bt_mesh_stat_lpn_timing_reset(void) +{ + memset(&lpn_timing, 0, sizeof(lpn_timing)); + poll_tx_end_ts = 0; + scan_start_ts = 0; +} + +#else /* !CONFIG_BT_MESH_LOW_POWER */ + +int bt_mesh_stat_lpn_timing_get(struct bt_mesh_lpn_timing *timing) +{ + ARG_UNUSED(timing); + return -ENOTSUP; +} + +void bt_mesh_stat_lpn_timing_reset(void) +{ +} + +#endif /* CONFIG_BT_MESH_LOW_POWER */ diff --git a/libs/zblue/mesh/statistic.h b/libs/zblue/mesh/statistic.h new file mode 100644 index 0000000000000..0495eacef1d53 --- /dev/null +++ b/libs/zblue/mesh/statistic.h @@ -0,0 +1,27 @@ +/**************************************************************************** + * libs/zblue/mesh/statistic.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_SUBSYS_BLUETOOTH_MESH_STATISTIC_H_ +#define ZEPHYR_SUBSYS_BLUETOOTH_MESH_STATISTIC_H_ + +void bt_mesh_stat_planned_count(struct bt_mesh_adv_ctx *ctx); +void bt_mesh_stat_succeeded_count(struct bt_mesh_adv_ctx *ctx); +void bt_mesh_stat_rx(enum bt_mesh_net_if net_if); + +#if defined(CONFIG_BT_MESH_LOW_POWER) +void bt_mesh_stat_lpn_timing_update_poll_tx(void); +void bt_mesh_stat_lpn_timing_update_scan_start(void); +void bt_mesh_stat_lpn_timing_update_response_rx(void); +void bt_mesh_stat_lpn_timing_update_win_expired(void); +#endif + +#endif /* ZEPHYR_SUBSYS_BLUETOOTH_MESH_STATISTIC_H_ */ diff --git a/libs/zblue/mesh/subnet.c b/libs/zblue/mesh/subnet.c new file mode 100644 index 0000000000000..175a952790908 --- /dev/null +++ b/libs/zblue/mesh/subnet.c @@ -0,0 +1,1212 @@ +/**************************************************************************** + * libs/zblue/mesh/subnet.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common/bt_str.h" + +#include "crypto.h" +#include "net.h" +#include "lpn.h" +#include "friend.h" +#include "proxy.h" +#include "transport.h" +#include "access.h" +#include "foundation.h" +#include "beacon.h" +#include "rpl.h" +#include "settings.h" +#include "prov.h" + +#define LOG_LEVEL CONFIG_BT_MESH_KEYS_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_net_keys); + +/* Tracking of what storage changes are pending for Net Keys. We track this + * in a separate array here instead of within the respective bt_mesh_subnet + * struct itself, since once a key gets deleted its struct becomes invalid + * and may be reused for other keys. + */ + +struct net_key_update +{ + uint16_t key_idx : 12; /* NetKey Index */ + uint16_t valid : 1; /* 1 if this entry is valid, 0 if not */ + uint16_t clear : 1; /* 1 if key needs clearing, 0 if storing */ +}; + +/* NetKey storage information */ + +struct net_key_val +{ + uint8_t unused : 1; + uint8_t kr_phase : 7; + struct bt_mesh_key val[2]; +} __packed; + +static struct net_key_update net_key_updates[CONFIG_BT_MESH_SUBNET_COUNT]; + +static struct bt_mesh_subnet subnets[CONFIG_BT_MESH_SUBNET_COUNT] = +{ + [0 ...(CONFIG_BT_MESH_SUBNET_COUNT - 1)] = + { + .net_idx = BT_MESH_KEY_UNUSED, + }, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void subnet_evt(struct bt_mesh_subnet *sub, enum bt_mesh_key_evt evt) +{ + STRUCT_SECTION_FOREACH(bt_mesh_subnet_cb, cb) + { + cb->evt_handler(sub, evt); + } +} + +static void clear_net_key(uint16_t net_idx) +{ + char path[20]; + int err; + + LOG_DBG("NetKeyIndex 0x%03x", net_idx); + + snprintf(path, sizeof(path), "bt/mesh/NetKey/%x", net_idx); + err = settings_delete(path); + if (err) + { + LOG_ERR("Failed to clear NetKeyIndex 0x%03x", net_idx); + } + else + { + LOG_DBG("Cleared NetKeyIndex 0x%03x", net_idx); + } +} + +static void store_subnet(uint16_t net_idx) +{ + const struct bt_mesh_subnet *sub; + struct net_key_val key; + char path[20]; + int err; + + sub = bt_mesh_subnet_get(net_idx); + if (!sub) + { + LOG_WRN("NetKeyIndex 0x%03x not found", net_idx); + return; + } + + LOG_DBG("NetKeyIndex 0x%03x", net_idx); + + snprintf(path, sizeof(path), "bt/mesh/NetKey/%x", net_idx); + + memcpy(&key.val[0], &sub->keys[0].net, sizeof(struct bt_mesh_key)); + memcpy(&key.val[1], &sub->keys[1].net, sizeof(struct bt_mesh_key)); + key.unused = 0U; + key.kr_phase = sub->kr_phase; + + err = settings_save_one(path, &key, sizeof(key)); + if (err) + { + LOG_ERR("Failed to store NetKey value"); + } + else + { + LOG_DBG("Stored NetKey value"); + } +} + +static struct net_key_update *net_key_update_find(uint16_t key_idx, + struct net_key_update **free_slot) +{ + struct net_key_update *match; + int i; + + match = NULL; + *free_slot = NULL; + + for (i = 0; i < ARRAY_SIZE(net_key_updates); i++) + { + struct net_key_update *update = &net_key_updates[i]; + + if (!update->valid) + { + *free_slot = update; + continue; + } + + if (update->key_idx == key_idx) + { + match = update; + } + } + + return match; +} + +uint8_t bt_mesh_net_flags(struct bt_mesh_subnet *sub) +{ + uint8_t flags = 0x00; + + if (sub && (sub->kr_phase == BT_MESH_KR_PHASE_2)) + { + flags |= BT_MESH_NET_FLAG_KR; + } + + if (atomic_test_bit(bt_mesh.flags, BT_MESH_IVU_IN_PROGRESS)) + { + flags |= BT_MESH_NET_FLAG_IVU; + } + + return flags; +} + +static void update_subnet_settings(uint16_t net_idx, bool store) +{ + struct net_key_update *update; + struct net_key_update *free_slot; + uint8_t clear = store ? 0U : 1U; + + LOG_DBG("NetKeyIndex 0x%03x", net_idx); + + update = net_key_update_find(net_idx, &free_slot); + if (update) + { + update->clear = clear; + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_NET_KEYS_PENDING); + return; + } + + if (!free_slot) + { + if (store) + { + store_subnet(net_idx); + } + else + { + clear_net_key(net_idx); + } + + return; + } + + free_slot->valid = 1U; + free_slot->key_idx = net_idx; + free_slot->clear = clear; + + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_NET_KEYS_PENDING); +} + +void bt_mesh_subnet_store(uint16_t net_idx) +{ + update_subnet_settings(net_idx, true); +} + +static void subnet_keys_destroy(struct bt_mesh_subnet_keys *key) +{ + bt_mesh_key_destroy(&key->net); + bt_mesh_key_destroy(&key->msg.enc); + bt_mesh_key_destroy(&key->msg.privacy); + bt_mesh_key_destroy(&key->beacon); +#if defined(CONFIG_BT_MESH_GATT_PROXY) + bt_mesh_key_destroy(&key->identity); +#endif +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) + bt_mesh_key_destroy(&key->priv_beacon); +#endif +} + +static void key_refresh(struct bt_mesh_subnet *sub, uint8_t new_phase) +{ + LOG_DBG("Phase 0x%02x -> 0x%02x", sub->kr_phase, new_phase); + + switch (new_phase) + { + /* Added second set of keys */ + + case BT_MESH_KR_PHASE_1: + sub->kr_phase = new_phase; + subnet_evt(sub, BT_MESH_KEY_UPDATED); + break; + + /* Now using new keys for TX */ + + case BT_MESH_KR_PHASE_2: + sub->kr_phase = new_phase; + subnet_evt(sub, BT_MESH_KEY_SWAPPED); + break; + + /* Revoking keys */ + + case BT_MESH_KR_PHASE_3: + if (sub->kr_phase == BT_MESH_KR_NORMAL) + { + return; + } + + __fallthrough; + case BT_MESH_KR_NORMAL: + sub->kr_phase = BT_MESH_KR_NORMAL; + subnet_keys_destroy(&sub->keys[0]); + memcpy(&sub->keys[0], &sub->keys[1], sizeof(sub->keys[0])); + sub->keys[1].valid = 0U; + subnet_evt(sub, BT_MESH_KEY_REVOKED); + break; + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + LOG_DBG("Storing Updated NetKey persistently"); + bt_mesh_subnet_store(sub->net_idx); + } +} + +void bt_mesh_kr_update(struct bt_mesh_subnet *sub, + bool kr_flag, bool new_key) +{ + if (!new_key) + { + return; + } + + if (sub->kr_phase == BT_MESH_KR_PHASE_1) + { + /* MshPRTv1.1: 3.11.4.1: + * Can skip phase 2 if we get KR=0 on new key. + */ + + key_refresh(sub, (kr_flag ? BT_MESH_KR_PHASE_2 : BT_MESH_KR_PHASE_3)); + } + else if (sub->kr_phase == BT_MESH_KR_PHASE_2 && !kr_flag) + { + key_refresh(sub, BT_MESH_KR_PHASE_3); + } +} + +static struct bt_mesh_subnet *subnet_alloc(uint16_t net_idx) +{ + struct bt_mesh_subnet *sub = NULL; + + for (int i = 0; i < ARRAY_SIZE(subnets); i++) + { + /* Check for already existing subnet */ + + if (subnets[i].net_idx == net_idx) + { + return &subnets[i]; + } + + if (!sub && subnets[i].net_idx == BT_MESH_KEY_UNUSED) + { + sub = &subnets[i]; + } + } + + return sub; +} + +static void subnet_del(struct bt_mesh_subnet *sub) +{ + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + update_subnet_settings(sub->net_idx, false); + } + + for (int i = 0; i < ARRAY_SIZE(sub->keys); i++) + { + if (sub->keys[i].valid) + { + subnet_keys_destroy(&sub->keys[i]); + } + } + + bt_mesh_net_loopback_clear(sub->net_idx); + + subnet_evt(sub, BT_MESH_KEY_DELETED); + (void)memset(sub, 0, sizeof(*sub)); + sub->net_idx = BT_MESH_KEY_UNUSED; +} + +static int msg_cred_create( + struct bt_mesh_net_cred *cred, const uint8_t *p, size_t p_len, + const uint8_t key[16]) +{ + return bt_mesh_k2(key, p, p_len, &cred->nid, &cred->enc, &cred->privacy); +} + +static int net_keys_create(struct bt_mesh_subnet_keys *keys, + bool import, const uint8_t key[16]) +{ + uint8_t p = 0; + int err; + + err = msg_cred_create(&keys->msg, &p, 1, key); + if (err) + { + LOG_ERR("Unable to generate NID, EncKey & PrivacyKey"); + return err; + } + + if (import) + { + err = bt_mesh_key_import(BT_MESH_KEY_TYPE_NET, key, &keys->net); + if (err) + { + LOG_ERR("Unable to import network key"); + return err; + } + } + + LOG_DBG("NID 0x%02x EncKey %s", keys->msg.nid, + bt_hex(&keys->msg.enc, sizeof(struct bt_mesh_key))); + LOG_DBG("PrivacyKey %s", bt_hex(&keys->msg.privacy, + sizeof(struct bt_mesh_key))); + + err = bt_mesh_k3(key, keys->net_id); + if (err) + { + LOG_ERR("Unable to generate Net ID"); + return err; + } + + LOG_DBG("NetID %s", bt_hex(keys->net_id, 8)); + +#if defined(CONFIG_BT_MESH_GATT_PROXY) + err = bt_mesh_identity_key(key, &keys->identity); + if (err) + { + LOG_ERR("Unable to generate IdentityKey"); + return err; + } + + LOG_DBG("IdentityKey %s", bt_hex(&keys->identity, + sizeof(struct bt_mesh_key))); +#endif /* GATT_PROXY */ + + err = bt_mesh_beacon_key(key, &keys->beacon); + if (err) + { + LOG_ERR("Unable to generate beacon key"); + return err; + } + + LOG_DBG("BeaconKey %s", bt_hex(&keys->beacon, sizeof(struct bt_mesh_key))); + +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) + err = bt_mesh_private_beacon_key(key, &keys->priv_beacon); + if (err) + { + LOG_ERR("Unable to generate private beacon key"); + return err; + } + + LOG_DBG("PrivateBeaconKey %s", bt_hex(&keys->priv_beacon, + sizeof(struct bt_mesh_key))); +#endif + + keys->valid = 1U; + + return 0; +} + +uint8_t bt_mesh_subnet_add(uint16_t net_idx, const uint8_t key[16]) +{ + struct bt_mesh_subnet *sub = NULL; + int err; + + LOG_DBG("0x%03x", net_idx); + + sub = subnet_alloc(net_idx); + if (!sub) + { + return STATUS_INSUFF_RESOURCES; + } + + if (sub->net_idx == net_idx) + { + if (bt_mesh_key_compare(key, &sub->keys[0].net)) + { + return STATUS_IDX_ALREADY_STORED; + } + + return STATUS_SUCCESS; + } + + err = net_keys_create(&sub->keys[0], true, key); + if (err) + { + return STATUS_UNSPECIFIED; + } + + sub->net_idx = net_idx; + sub->kr_phase = BT_MESH_KR_NORMAL; + + if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY)) + { + sub->node_id = BT_MESH_NODE_IDENTITY_STOPPED; + } + else + { + sub->node_id = BT_MESH_NODE_IDENTITY_NOT_SUPPORTED; + } + + subnet_evt(sub, BT_MESH_KEY_ADDED); + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + LOG_DBG("Storing NetKey persistently"); + bt_mesh_subnet_store(sub->net_idx); + } + + return STATUS_SUCCESS; +} + +bool bt_mesh_subnet_exists(uint16_t net_idx) +{ + return !!bt_mesh_subnet_get(net_idx); +} + +uint8_t bt_mesh_subnet_update(uint16_t net_idx, const uint8_t key[16]) +{ + struct bt_mesh_subnet *sub; + int err; + + LOG_DBG("0x%03x", net_idx); + + sub = bt_mesh_subnet_get(net_idx); + if (!sub) + { + return STATUS_INVALID_NETKEY; + } + + /* The node shall successfully process a NetKey Update message on a + * valid NetKeyIndex when the NetKey value is different and the Key Refresh + * procedure has not been started, or when the NetKey value is the same in + * Phase 1. The NetKey Update message shall generate an error when the node + * is in Phase 2, or Phase 3. + */ + + switch (sub->kr_phase) + { + case BT_MESH_KR_NORMAL: + if (!bt_mesh_key_compare(key, &sub->keys[0].net)) + { + return STATUS_IDX_ALREADY_STORED; + } + break; + case BT_MESH_KR_PHASE_1: + if (!bt_mesh_key_compare(key, &sub->keys[1].net)) + { + return STATUS_SUCCESS; + } + + __fallthrough; + case BT_MESH_KR_PHASE_2: + case BT_MESH_KR_PHASE_3: + return STATUS_CANNOT_UPDATE; + } + + err = net_keys_create(&sub->keys[1], true, key); + if (err) + { + return STATUS_CANNOT_UPDATE; + } + + key_refresh(sub, BT_MESH_KR_PHASE_1); + + return STATUS_SUCCESS; +} + +uint8_t bt_mesh_subnet_del(uint16_t net_idx) +{ + struct bt_mesh_subnet *sub; + + LOG_DBG("0x%03x", net_idx); + + sub = bt_mesh_subnet_get(net_idx); + if (!sub) + { + /* This could be a retry of a previous attempt that had its + * response lost, so pretend that it was a success. + */ + + return STATUS_INVALID_NETKEY; + } + + subnet_del(sub); + + return STATUS_SUCCESS; +} + +int bt_mesh_friend_cred_create( + struct bt_mesh_net_cred *cred, uint16_t lpn_addr, uint16_t frnd_addr, + uint16_t lpn_counter, uint16_t frnd_counter, const struct bt_mesh_key *key) +{ + uint8_t p[9]; + uint8_t raw_key[16]; + int err; + + p[0] = 0x01; + sys_put_be16(lpn_addr, p + 1); + sys_put_be16(frnd_addr, p + 3); + sys_put_be16(lpn_counter, p + 5); + sys_put_be16(frnd_counter, p + 7); + + err = bt_mesh_key_export(raw_key, key); + if (err) + { + return err; + } + + return msg_cred_create(cred, p, sizeof(p), raw_key); +} + +void bt_mesh_friend_cred_destroy(struct bt_mesh_net_cred *cred) +{ + bt_mesh_key_destroy(&cred->enc); + bt_mesh_key_destroy(&cred->privacy); +} + +uint8_t bt_mesh_subnet_kr_phase_set(uint16_t net_idx, uint8_t *phase) +{ + /* Table in MshPRTv1.1: 4.2.15: */ + + const uint8_t valid_transitions[] = + { + /* Normal phase: KR is started by key update */ + + BIT(BT_MESH_KR_PHASE_3), + BIT(BT_MESH_KR_PHASE_2) | BIT(BT_MESH_KR_PHASE_3), /* Phase 1 */ + BIT(BT_MESH_KR_PHASE_3), /* Phase 2 */ + + /* Subnet is never in Phase 3 */ + }; + + struct bt_mesh_subnet *sub; + + LOG_DBG("0x%03x", net_idx); + + sub = bt_mesh_subnet_get(net_idx); + if (!sub) + { + *phase = 0x00; + return STATUS_INVALID_NETKEY; + } + + if (*phase == sub->kr_phase) + { + return STATUS_SUCCESS; + } + + if (sub->kr_phase < ARRAY_SIZE(valid_transitions) && + valid_transitions[sub->kr_phase] & BIT(*phase)) + { + key_refresh(sub, *phase); + + *phase = sub->kr_phase; + + return STATUS_SUCCESS; + } + + LOG_WRN("Invalid KR transition: 0x%02x -> 0x%02x", sub->kr_phase, *phase); + + *phase = sub->kr_phase; + + return STATUS_CANNOT_UPDATE; +} + +uint8_t bt_mesh_subnet_kr_phase_get(uint16_t net_idx, uint8_t *phase) +{ + struct bt_mesh_subnet *sub; + + sub = bt_mesh_subnet_get(net_idx); + if (!sub) + { + *phase = BT_MESH_KR_NORMAL; + return STATUS_INVALID_NETKEY; + } + + *phase = sub->kr_phase; + + return STATUS_SUCCESS; +} + +uint8_t bt_mesh_subnet_node_id_set(uint16_t net_idx, + enum bt_mesh_feat_state node_id) +{ + struct bt_mesh_subnet *sub; + + if (node_id == BT_MESH_FEATURE_NOT_SUPPORTED) + { + return STATUS_CANNOT_SET; + } + + sub = bt_mesh_subnet_get(net_idx); + if (!sub) + { + return STATUS_INVALID_NETKEY; + } + + if (!IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY)) + { + return STATUS_FEAT_NOT_SUPP; + } + +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) + /* Implements binding from MshPRTv1.1: 4.2.46.1. When enabling non-private + * node identity state, disable its private counterpart. + */ + + for (int i = 0; i < ARRAY_SIZE(subnets); i++) + { + if (subnets[i].net_idx != BT_MESH_KEY_UNUSED && + subnets[i].node_id == BT_MESH_FEATURE_ENABLED && + subnets[i].priv_beacon_ctx.node_id) + { + bt_mesh_proxy_identity_stop(&subnets[i]); + } + } +#endif + + if (node_id) + { + bt_mesh_proxy_identity_start(sub, false); + } + else + { + bt_mesh_proxy_identity_stop(sub); + } + + bt_mesh_adv_gatt_update(); + + return STATUS_SUCCESS; +} + +uint8_t bt_mesh_subnet_node_id_get(uint16_t net_idx, + enum bt_mesh_feat_state *node_id) +{ + struct bt_mesh_subnet *sub; + + sub = bt_mesh_subnet_get(net_idx); + if (!sub) + { + *node_id = 0x00; + return STATUS_INVALID_NETKEY; + } + + *node_id = sub->node_id; +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) + *node_id &= !sub->priv_beacon_ctx.node_id; +#endif + + return STATUS_SUCCESS; +} + +uint8_t bt_mesh_subnet_priv_node_id_set(uint16_t net_idx, + enum bt_mesh_feat_state priv_node_id) +{ + struct bt_mesh_subnet *sub; + + if (priv_node_id == BT_MESH_FEATURE_NOT_SUPPORTED) + { + return STATUS_CANNOT_SET; + } + + sub = bt_mesh_subnet_get(net_idx); + if (!sub) + { + return STATUS_INVALID_NETKEY; + } + + if (!IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY) || + !IS_ENABLED(CONFIG_BT_MESH_PRIV_BEACONS)) + { + return STATUS_FEAT_NOT_SUPP; + } + +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) + /* Reverse binding from MshPRTv1.1: 4.2.46.1 doesn't + * allow to set private state if non-private state is enabled. + */ + + for (int i = 0; i < ARRAY_SIZE(subnets); i++) + { + if (subnets[i].net_idx != BT_MESH_KEY_UNUSED && + subnets[i].node_id == BT_MESH_FEATURE_ENABLED && + !subnets[i].priv_beacon_ctx.node_id) + { + return STATUS_CANNOT_SET; + } + } +#endif + + if (priv_node_id) + { + bt_mesh_proxy_identity_start(sub, true); + } + else + { + bt_mesh_proxy_identity_stop(sub); + } + + bt_mesh_adv_gatt_update(); + + return STATUS_SUCCESS; +} + +uint8_t bt_mesh_subnet_priv_node_id_get(uint16_t net_idx, + enum + bt_mesh_feat_state *priv_node_id) +{ + struct bt_mesh_subnet *sub; + + sub = bt_mesh_subnet_get(net_idx); + if (!sub) + { + *priv_node_id = 0x00; + return STATUS_INVALID_NETKEY; + } + +#if CONFIG_BT_MESH_GATT_PROXY && CONFIG_BT_MESH_PRIV_BEACONS + if (sub->node_id == BT_MESH_FEATURE_ENABLED && + sub->priv_beacon_ctx.node_id) + { + *priv_node_id = sub->node_id; + } + else + { + *priv_node_id = BT_MESH_FEATURE_DISABLED; + } +#else + *priv_node_id = BT_MESH_FEATURE_NOT_SUPPORTED; +#endif + + return STATUS_SUCCESS; +} + +enum bt_mesh_subnets_node_id_state bt_mesh_subnets_node_id_state_get(void) +{ + for (int i = 0; i < ARRAY_SIZE(subnets); i++) + { + if (subnets[i].node_id) + { +#if CONFIG_BT_MESH_PRIV_BEACONS + if (subnets[i].priv_beacon_ctx.node_id) + { + return BT_MESH_SUBNETS_NODE_ID_STATE_ENABLED_PRIVATE; + } +#endif + + return BT_MESH_SUBNETS_NODE_ID_STATE_ENABLED; + } + } + + return BT_MESH_SUBNETS_NODE_ID_STATE_NONE; +} + +ssize_t bt_mesh_subnets_get(uint16_t net_idxs[], size_t max, off_t skip) +{ + size_t count = 0; + + for (int i = 0; i < ARRAY_SIZE(subnets); i++) + { + struct bt_mesh_subnet *sub = &subnets[i]; + + if (sub->net_idx == BT_MESH_KEY_UNUSED) + { + continue; + } + + if (skip) + { + skip--; + continue; + } + + if (count >= max) + { + return -ENOMEM; + } + + net_idxs[count++] = sub->net_idx; + } + + return count; +} + +struct bt_mesh_subnet *bt_mesh_subnet_get(uint16_t net_idx) +{ + for (int i = 0; i < ARRAY_SIZE(subnets); i++) + { + struct bt_mesh_subnet *sub = &subnets[i]; + + if (sub->net_idx == net_idx) + { + return sub; + } + } + + return NULL; +} + +static int subnet_key_set(struct bt_mesh_subnet *sub, + int key_idx, const struct bt_mesh_key *key) +{ + uint8_t raw_key[16]; + int err; + + err = bt_mesh_key_export(raw_key, key); + if (err) + { + return err; + } + + bt_mesh_key_assign(&sub->keys[key_idx].net, key); + err = net_keys_create(&sub->keys[key_idx], false, raw_key); + if (err) + { + return err; + } + + return 0; +} + +int bt_mesh_subnet_set( + uint16_t net_idx, uint8_t kr_phase, const struct bt_mesh_key *old_key, + const struct bt_mesh_key *new_key) +{ + struct bt_mesh_subnet *sub; + int err; + + sub = subnet_alloc(net_idx); + if (!sub) + { + return -ENOMEM; + } + + if (sub->net_idx == net_idx) + { + return -EALREADY; + } + + if (old_key != NULL) + { + err = subnet_key_set(sub, 0, old_key); + if (err) + { + return err; + } + } + + if (new_key != NULL) + { + err = subnet_key_set(sub, 1, new_key); + if (err) + { + return err; + } + } + + sub->net_idx = net_idx; + sub->kr_phase = kr_phase; + + if (IS_ENABLED(CONFIG_BT_MESH_GATT_PROXY)) + { + sub->node_id = BT_MESH_NODE_IDENTITY_STOPPED; + } + else + { + sub->node_id = BT_MESH_NODE_IDENTITY_NOT_SUPPORTED; + } + + /* Make sure we have valid beacon data to be sent */ + + bt_mesh_beacon_update(sub); + + return 0; +} + +struct bt_mesh_subnet *bt_mesh_subnet_find( + bool (*cb)(struct bt_mesh_subnet *sub, void *cb_data), void *cb_data) +{ + for (int i = 0; i < ARRAY_SIZE(subnets); i++) + { + if (subnets[i].net_idx == BT_MESH_KEY_UNUSED) + { + continue; + } + + if (!cb || cb(&subnets[i], cb_data)) + { + return &subnets[i]; + } + } + + return NULL; +} + +size_t bt_mesh_subnet_foreach(void (*cb)(struct bt_mesh_subnet *sub)) +{ + size_t count = 0; + + for (int i = 0; i < ARRAY_SIZE(subnets); i++) + { + if (subnets[i].net_idx == BT_MESH_KEY_UNUSED) + { + continue; + } + + cb(&subnets[i]); + count++; + } + + return count; +} + +struct bt_mesh_subnet *bt_mesh_subnet_next(struct bt_mesh_subnet *sub) +{ + if (sub) + { + sub++; + } + else + { + sub = &subnets[0]; + } + + for (int i = 0; i < ARRAY_SIZE(subnets); i++, sub++) + { + /* Roll over once we reach the end */ + + if (sub == &subnets[ARRAY_SIZE(subnets)]) + { + sub = &subnets[0]; + } + + if (sub->net_idx != BT_MESH_KEY_UNUSED) + { + return sub; + } + } + + return NULL; +} + +void bt_mesh_net_keys_reset(void) +{ + int i; + + /* Delete all net keys, which also takes care of all app keys which + * are associated with each net key. + */ + + for (i = 0; i < ARRAY_SIZE(subnets); i++) + { + struct bt_mesh_subnet *sub = &subnets[i]; + + if (sub->net_idx != BT_MESH_KEY_UNUSED) + { + subnet_del(sub); + } + } +} + +bool bt_mesh_net_cred_find( + struct bt_mesh_net_rx *rx, struct net_buf_simple *in, + struct net_buf_simple *out, + bool (*cb)(struct bt_mesh_net_rx *rx, + struct net_buf_simple *in, + struct net_buf_simple *out, + const struct bt_mesh_net_cred *cred)) +{ + int i; + int j; + + LOG_DBG(""); + +#if defined(CONFIG_BT_MESH_LOW_POWER) + if (bt_mesh_lpn_waiting_update()) + { + rx->sub = bt_mesh.lpn.sub; + + for (j = 0; j < ARRAY_SIZE(bt_mesh.lpn.cred); j++) + { + if (!rx->sub->keys[j].valid) + { + continue; + } + + if (cb(rx, in, out, &bt_mesh.lpn.cred[j])) + { + rx->new_key = (j > 0); + rx->friend_cred = 1U; + rx->ctx.net_idx = rx->sub->net_idx; + return true; + } + } + + /* LPN Should only receive on the friendship credentials when in + * a friendship. + */ + + return false; + } +#endif + +#if defined(CONFIG_BT_MESH_FRIEND) + /** Each friendship has unique friendship credentials */ + + for (i = 0; i < ARRAY_SIZE(bt_mesh.frnd); i++) + { + struct bt_mesh_friend *frnd = &bt_mesh.frnd[i]; + + if (!frnd->subnet) + { + continue; + } + + rx->sub = frnd->subnet; + + for (j = 0; j < ARRAY_SIZE(frnd->cred); j++) + { + if (!rx->sub->keys[j].valid) + { + continue; + } + + if (cb(rx, in, out, &frnd->cred[j])) + { + rx->new_key = (j > 0); + rx->friend_cred = 1U; + rx->ctx.net_idx = rx->sub->net_idx; + return true; + } + } + } +#endif + + for (i = 0; i < ARRAY_SIZE(subnets); i++) + { + rx->sub = &subnets[i]; + if (rx->sub->net_idx == BT_MESH_KEY_UNUSED) + { + continue; + } + + for (j = 0; j < ARRAY_SIZE(rx->sub->keys); j++) + { + if (!rx->sub->keys[j].valid) + { + continue; + } + + if (cb(rx, in, out, &rx->sub->keys[j].msg)) + { + rx->new_key = (j > 0); + rx->friend_cred = 0U; + rx->ctx.net_idx = rx->sub->net_idx; + return true; + } + } + } + + return false; +} + +static int net_key_set(const char *name, + size_t len_rd, settings_read_cb read_cb, void *cb_arg) +{ + struct net_key_val key; + struct bt_mesh_key val[2]; + int err; + uint16_t net_idx; + + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return 0; + } + + if (!name) + { + LOG_ERR("Insufficient number of arguments"); + return -ENOENT; + } + + net_idx = strtol(name, NULL, 16); + err = bt_mesh_settings_set(read_cb, cb_arg, &key, sizeof(key)); + if (err) + { + LOG_ERR("Failed to set \'net-key\'"); + return err; + } + + /* One extra copying since key.val array is from packed structure + * and might be unaligned. + */ + + memcpy(val, key.val, sizeof(key.val)); + + LOG_DBG("NetKeyIndex 0x%03x recovered from storage", net_idx); + + return bt_mesh_subnet_set(net_idx, key.kr_phase, &val[0], + (key.kr_phase != BT_MESH_KR_NORMAL) ? + &val[1] : NULL); +} + +BT_MESH_SETTINGS_DEFINE(subnet, "NetKey", net_key_set); + +void bt_mesh_subnet_pending_store(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(net_key_updates); i++) + { + struct net_key_update *update = &net_key_updates[i]; + + if (!update->valid) + { + continue; + } + + update->valid = 0U; + + if (update->clear) + { + clear_net_key(update->key_idx); + } + else + { + store_subnet(update->key_idx); + } + } +} diff --git a/libs/zblue/mesh/subnet.h b/libs/zblue/mesh/subnet.h new file mode 100644 index 0000000000000..a3eb985ca56f4 --- /dev/null +++ b/libs/zblue/mesh/subnet.h @@ -0,0 +1,310 @@ +/**************************************************************************** + * libs/zblue/mesh/subnet.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_SUBSYS_BLUETOOTH_MESH_SUBNET_H_ +#define ZEPHYR_SUBSYS_BLUETOOTH_MESH_SUBNET_H_ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include +#include +#include +#include + +#define BT_MESH_NET_FLAG_KR BIT(0) +#define BT_MESH_NET_FLAG_IVU BIT(1) + +#define BT_MESH_KR_NORMAL 0x00 +#define BT_MESH_KR_PHASE_1 0x01 +#define BT_MESH_KR_PHASE_2 0x02 +#define BT_MESH_KR_PHASE_3 0x03 + +/** Which of the two subnet.keys should be used for sending. */ +#define SUBNET_KEY_TX_IDX(sub) ((sub)->kr_phase == BT_MESH_KR_PHASE_2) + +struct bt_mesh_net_rx; +enum bt_mesh_key_evt; + +/** Network message encryption credentials */ + +struct bt_mesh_net_cred +{ + uint8_t nid; /* NID */ + struct bt_mesh_key enc; /* EncKey */ + struct bt_mesh_key privacy; /* PrivacyKey */ +}; + +struct bt_mesh_beacon +{ + uint32_t sent; /* Timestamp of last sent beacon */ + uint32_t recv; /* Timestamp of last received beacon */ + uint8_t last; /* Number of beacons during last + * observation window + */ + uint8_t cur; /* Number of beacons observed during + * currently ongoing window. + */ + uint8_t cache[8]; /* Cached last beacon auth value */ + uint8_t auth[8]; /* Beacon Authentication Value */ +}; + +/** Subnet instance. */ + +struct bt_mesh_subnet +{ + uint16_t net_idx; /* NetKeyIndex */ + + uint8_t kr_phase; /* Key Refresh Phase */ + + uint8_t node_id; /* Node Identity State */ + uint32_t node_id_start; /* Node Identity started timestamp */ + + struct bt_mesh_beacon secure_beacon; + +#if defined(CONFIG_BT_MESH_PRIV_BEACONS) + struct bt_mesh_beacon priv_beacon; + struct + { + uint16_t idx; /* Private beacon random index */ + bool node_id; /* Private Node Identity enabled */ + uint8_t data[5]; /* Private Beacon data */ + } priv_beacon_ctx; +#endif + + struct bt_mesh_subnet_keys + { + bool valid; + struct bt_mesh_key net; /* NetKey */ + struct bt_mesh_net_cred msg; + uint8_t net_id[8]; /* Network ID */ +#if defined(CONFIG_BT_MESH_GATT) + struct bt_mesh_key identity; /* IdentityKey */ +#endif + struct bt_mesh_key beacon; /* BeaconKey */ + struct bt_mesh_key priv_beacon; /* PrivateBeaconKey */ + } + keys[2]; +#if defined(CONFIG_BT_MESH_PROXY_SOLICITATION) + bool sol_tx; +#endif +#if defined(CONFIG_BT_MESH_OD_PRIV_PROXY_SRV) + uint32_t priv_net_id_sent; /* Timestamp for Private Network ID advertising + * started via Proxy Solicitation + */ + bool solicited; /* Subnet received valid Solicitation PDU */ +#endif +}; + +/** Subnet callback structure. Instantiate with @ref BT_MESH_SUBNET_CB */ + +struct bt_mesh_subnet_cb +{ + void (*evt_handler)(struct bt_mesh_subnet *subnet, + enum bt_mesh_key_evt evt); +}; + +/** + * @brief Register a subnet event callback. + * + * @param _name Handler name. + */ +#define BT_MESH_SUBNET_CB_DEFINE(_name) \ + static const STRUCT_SECTION_ITERABLE(bt_mesh_subnet_cb, \ + _CONCAT(bt_mesh_subnet_cb_, _name)) + +/** @brief Reset all Network keys. */ + +void bt_mesh_net_keys_reset(void); + +/** @brief Call cb on every valid Subnet until it returns a non-zero value. + * + * @param cb Callback to call, or NULL to return first valid subnet. If + * the callback returns true, iteration stops, and the passed + * subnet is returned. + * @param cb_data Callback data to pass to callback. + * + * @return Subnet that returned non-zero value. + */ + +struct bt_mesh_subnet *bt_mesh_subnet_find( + bool (*cb)(struct bt_mesh_subnet *sub, void *cb_data), void *cb_data); + +/** @brief Iterate through all valid Subnets. + * + * @param cb Callback to call on every Subnet. + * + * @returns The number of valid subnets. + */ + +size_t bt_mesh_subnet_foreach(void (*cb)(struct bt_mesh_subnet *sub)); + +/** @brief Get the next valid Subnet. + * + * If there's only one valid Subnet, this will be returned on every call. + * + * @param sub Previous Subnet, or NULL to get the first valid. + * + * @returns Gets the next valid Subnet after @c sub, or NULL if there are no + * valid Subnets. + */ + +struct bt_mesh_subnet *bt_mesh_subnet_next(struct bt_mesh_subnet *sub); + +/** @brief Get a pointer to the Subnet with the given index. + * + * @param net_idx Network index to look for. + * + * @returns Subnet with index @c net_idx, or NULL if no such Subnet is + * known. + */ + +struct bt_mesh_subnet *bt_mesh_subnet_get(uint16_t net_idx); + +/** @brief Initialize a new Subnet. + * + * @param net_idx Network index of the Subnet. + * @param kr_phase Key refresh phase the Subnet should be in. + * @param key The current network key for the Subnet. + * @param new_key New network key, if available. + * + * @returns 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_subnet_set( + uint16_t net_idx, uint8_t kr_phase, const struct bt_mesh_key *key, + const struct bt_mesh_key *new_key); + +/** @brief Create Friendship credentials. + * + * @param cred Credential object to create. + * @param lpn_addr Address of the LPN node in the friendship. + * @param frnd_addr Address of the Friend node in the friendship. + * @param lpn_counter The LPN's counter parameter. + * @param frnd_counter The Friend node's counter parameter. + * @param key Network key to create the Friendship credentials for. + * + * @returns 0 on success, or (negative) error code on failure. + */ + +int bt_mesh_friend_cred_create( + struct bt_mesh_net_cred *cred, uint16_t lpn_addr, uint16_t frnd_addr, + uint16_t lpn_counter, uint16_t frnd_counter, + const struct bt_mesh_key *key); + +/** @brief Destroy Friendship credentials. + * + * @param cred Credential object to destroy. + */ + +void bt_mesh_friend_cred_destroy(struct bt_mesh_net_cred *cred); + +/** @brief Iterate through all valid network credentials to decrypt a + * message. + * + * @param rx Network RX parameters, passed to the callback. + * @param in Input message buffer, passed to the callback. + * @param out Output message buffer, passed to the callback. + * @param cb Callback to call for each known network credential. Iteration + * stops when this callback returns @c true. + * + * @returns Whether any of the credentials got a @c true return from the + * callback. + */ + +bool bt_mesh_net_cred_find( + struct bt_mesh_net_rx *rx, struct net_buf_simple *in, + struct net_buf_simple *out, + bool (*cb)(struct bt_mesh_net_rx *rx, struct net_buf_simple *in, + struct net_buf_simple *out, + const struct bt_mesh_net_cred *cred)); + +/** @brief Get the network flags of the given Subnet. + * + * @param sub Subnet to get the network flags of. + * + * @returns A bitmap of @ref BT_MESH_NET_FLAG_KR and @ref + * BT_MESH_NET_FLAG_IVU. + */ + +uint8_t bt_mesh_net_flags(struct bt_mesh_subnet *sub); + +/** @brief Process a Key Refresh event from a beacon. + * + * @param sub Subnet the Key Refresh was received on. + * @param kr_flag Key Refresh flag. + * @param new_key Whether the Key Refresh event was received on the new key + * set. + * + * @returns Whether the Key Refresh event caused a change. + */ + +void bt_mesh_kr_update(struct bt_mesh_subnet *sub, + bool kr_flag, bool new_key); + +/** @brief Check whether the Subnet has the refreshed keys. + * + * @param sub Subnet. + * + * @returns Whether the Subnet's second key is valid. + */ + +static inline bool bt_mesh_subnet_has_new_key( + const struct bt_mesh_subnet *sub) +{ + return sub->kr_phase != BT_MESH_KR_NORMAL; +} + +/** Kind of currently enabled Node Identity state on one or more subnets. */ + +enum bt_mesh_subnets_node_id_state +{ + /* None node identity states are enabled on any subnets. */ + + BT_MESH_SUBNETS_NODE_ID_STATE_NONE, + + /* Node Identity state is enabled on one or more subnets. */ + + BT_MESH_SUBNETS_NODE_ID_STATE_ENABLED, + + /* Private Node Identity state is enabled on one or more subnets. */ + + BT_MESH_SUBNETS_NODE_ID_STATE_ENABLED_PRIVATE, +}; + +/** @brief Returns what kind of node identity state is currently enabled on + * one or more subnets. + * + * Only one kind (either non-private or private) can be enabled at the same + * time on all subnets. + * + * @returns Kind of node identity state that is currently enabled. + */ + +enum bt_mesh_subnets_node_id_state bt_mesh_subnets_node_id_state_get(void); + +/** @brief Store the Subnet information in persistent storage. + * + * @param net_idx Network index to store. + */ + +void bt_mesh_subnet_store(uint16_t net_idx); + +/** @brief Store the pending Subnets in persistent storage. */ + +void bt_mesh_subnet_pending_store(void); + +#endif /* ZEPHYR_SUBSYS_BLUETOOTH_MESH_SUBNET_H_ */ diff --git a/libs/zblue/mesh/test.c b/libs/zblue/mesh/test.c new file mode 100644 index 0000000000000..54983df7ca360 --- /dev/null +++ b/libs/zblue/mesh/test.c @@ -0,0 +1,32 @@ +/**************************************************************************** + * libs/zblue/mesh/test.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include + +#include "mesh.h" +#include "test.h" + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bt_mesh_test(void) +{ + return 0; +} diff --git a/libs/zblue/mesh/test.h b/libs/zblue/mesh/test.h new file mode 100644 index 0000000000000..cb63f6169503c --- /dev/null +++ b/libs/zblue/mesh/test.h @@ -0,0 +1,20 @@ +/**************************************************************************** + * libs/zblue/mesh/test.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#if defined(CONFIG_BT_MESH_SELF_TEST) +int bt_mesh_test(void); +#else +static inline int bt_mesh_test(void) +{ + return 0; +} +#endif diff --git a/libs/zblue/mesh/testing.c b/libs/zblue/mesh/testing.c new file mode 100644 index 0000000000000..4d6ec9e97ee3d --- /dev/null +++ b/libs/zblue/mesh/testing.c @@ -0,0 +1,162 @@ +/**************************************************************************** + * libs/zblue/mesh/testing.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +#include +#include +#include + +#include "net.h" +#include "lpn.h" +#include "rpl.h" +#include "testing.h" +#include "transport.h" + +static struct list_node cb_slist = LIST_INITIAL_VALUE(cb_slist); + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +int bt_mesh_test_cb_register(struct bt_mesh_test_cb *cb) +{ + if (list_find(&cb_slist, &cb->node, NULL)) + { + return -EEXIST; + } + + list_add_tail(&cb_slist, &cb->node); + + return 0; +} + +void bt_mesh_test_cb_unregister(struct bt_mesh_test_cb *cb) +{ + list_find_and_remove(&cb_slist, &cb->node); +} + +void bt_mesh_test_net_recv( + uint8_t ttl, uint8_t ctl, uint16_t src, uint16_t dst, const void *payload, + size_t payload_len) +{ + struct bt_mesh_test_cb *cb; + + list_for_every_container(&cb_slist, cb, node) + { + if (cb->net_recv) + { + cb->net_recv(ttl, ctl, src, dst, payload, payload_len); + } + } +} + +void bt_mesh_test_model_recv(uint16_t src, + uint16_t dst, const void *payload, + size_t payload_len) +{ + struct bt_mesh_test_cb *cb; + + list_for_every_container(&cb_slist, cb, node) + { + if (cb->model_recv) + { + cb->model_recv(src, dst, payload, payload_len); + } + } +} + +void bt_mesh_test_model_bound(uint16_t addr, + const struct bt_mesh_model *model, + uint16_t key_idx) +{ + struct bt_mesh_test_cb *cb; + + list_for_every_container(&cb_slist, cb, node) + { + if (cb->model_bound) + { + cb->model_bound(addr, model, key_idx); + } + } +} + +void bt_mesh_test_model_unbound(uint16_t addr, + const struct bt_mesh_model *model, + uint16_t key_idx) +{ + struct bt_mesh_test_cb *cb; + + list_for_every_container(&cb_slist, cb, node) + { + if (cb->model_unbound) + { + cb->model_unbound(addr, model, key_idx); + } + } +} + +void bt_mesh_test_prov_invalid_bearer(uint8_t opcode) +{ + struct bt_mesh_test_cb *cb; + + list_for_every_container(&cb_slist, cb, node) + { + if (cb->prov_invalid_bearer) + { + cb->prov_invalid_bearer(opcode); + } + } +} + +void bt_mesh_test_trans_incomp_timer_exp(void) +{ + struct bt_mesh_test_cb *cb; + + list_for_every_container(&cb_slist, cb, node) + { + if (cb->trans_incomp_timer_exp) + { + cb->trans_incomp_timer_exp(); + } + } +} + +#if defined(CONFIG_BT_MESH_LOW_POWER) +int bt_mesh_test_lpn_group_add(uint16_t group) +{ + bt_mesh_lpn_group_add(group); + + return 0; +} + +int bt_mesh_test_lpn_group_remove(uint16_t *groups, size_t groups_count) +{ + bt_mesh_lpn_group_del(groups, groups_count); + + return 0; +} +#endif /* CONFIG_BT_MESH_LOW_POWER */ + +int bt_mesh_test_rpl_clear(void) +{ + bt_mesh_rpl_clear(); + + return 0; +} diff --git a/libs/zblue/mesh/testing.h b/libs/zblue/mesh/testing.h new file mode 100644 index 0000000000000..0812d20486ebf --- /dev/null +++ b/libs/zblue/mesh/testing.h @@ -0,0 +1,115 @@ +/**************************************************************************** + * libs/zblue/mesh/testing.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** + * @file testing.h + * @brief Internal API for Bluetooth testing. + */ + +/* Copyright (c) 2017 Intel Corporation + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +/** @brief Bluetooth Testing callbacks structure. + * + * Callback structure to be used for Bluetooth testing purposes. Allows + * access to Bluetooth stack internals, not exposed by public API. + */ + +struct bt_mesh_test_cb +{ + void (*net_recv)(uint8_t ttl, uint8_t ctl, uint16_t src, uint16_t dst, + const void *payload, + size_t payload_len); + void (*model_recv)(uint16_t src, uint16_t dst, const void *payload, + size_t payload_len); + void (*model_bound)(uint16_t addr, const struct bt_mesh_model *model, + uint16_t key_idx); + void (*model_unbound)(uint16_t addr, const struct bt_mesh_model *model, + uint16_t key_idx); + void (*prov_invalid_bearer)(uint8_t opcode); + void (*trans_incomp_timer_exp)(void); + + struct list_node node; +}; + +/** Register callbacks for Bluetooth testing purposes + * + * @param cb bt_mesh_test_cb callback structure + * + * @retval 0 Success. + * @retval -EEXIST if @p cb was already registered. + */ + +int bt_mesh_test_cb_register(struct bt_mesh_test_cb *cb); + +/** Unregister callbacks for Bluetooth testing purposes + * + * @param cb bt_mesh_test_cb callback structure + */ + +void bt_mesh_test_cb_unregister(struct bt_mesh_test_cb *cb); + +/** Send Friend Subscription List Add message. + * + * Used by Low Power node to send the group address for which messages are + * to be stored by Friend node. + * + * @param group Group address + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_mesh_test_lpn_group_add(uint16_t group); + +/** Send Friend Subscription List Remove message. + * + * Used by Low Power node to remove the group addresses from Friend node + * subscription list. Messages sent to those addresses will not be stored by + * Friend node. + * + * @param groups Group addresses + * @param groups_count Group addresses count + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_mesh_test_lpn_group_remove(uint16_t *groups, size_t groups_count); + +/** Clear replay protection list cache. + * + * @return Zero on success or (negative) error code otherwise. + */ + +int bt_mesh_test_rpl_clear(void); + +void bt_mesh_test_net_recv( + uint8_t ttl, uint8_t ctl, uint16_t src, uint16_t dst, const void *payload, + size_t payload_len); +void bt_mesh_test_model_recv(uint16_t src, + uint16_t dst, const void *payload, + size_t payload_len); +void bt_mesh_test_model_bound(uint16_t addr, + const struct bt_mesh_model *model, + uint16_t key_idx); +void bt_mesh_test_model_unbound(uint16_t addr, + const struct bt_mesh_model *model, + uint16_t key_idx); +void bt_mesh_test_prov_invalid_bearer(uint8_t opcode); +void bt_mesh_test_trans_incomp_timer_exp(void); diff --git a/libs/zblue/mesh/transport.c b/libs/zblue/mesh/transport.c new file mode 100644 index 0000000000000..318ea1922d754 --- /dev/null +++ b/libs/zblue/mesh/transport.c @@ -0,0 +1,2070 @@ +/**************************************************************************** + * libs/zblue/mesh/transport.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "common/bt_str.h" + +#include "crypto.h" +#include "mesh.h" +#include "net.h" +#include "app_keys.h" +#include "lpn.h" +#include "rpl.h" +#include "friend.h" +#include "access.h" +#include "foundation.h" +#include "sar_cfg_internal.h" +#include "settings.h" +#include "heartbeat.h" +#include "testing.h" +#include "transport.h" +#include "va.h" + +#define LOG_LEVEL CONFIG_BT_MESH_TRANS_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_transport); + +#define AID_MASK ((uint8_t)(BIT_MASK(6))) + +#define SEG(data) ((data)[0] >> 7) +#define AKF(data) (((data)[0] >> 6) & 0x01) +#define AID(data) ((data)[0] & AID_MASK) +#define ASZMIC(data) (((data)[1] >> 7) & 1) + +#define APP_MIC_LEN(aszmic) ((aszmic) ? BT_MESH_MIC_LONG : BT_MESH_MIC_SHORT) + +#define UNSEG_HDR(akf, aid) ((akf << 6) | (aid & AID_MASK)) +#define SEG_HDR(akf, aid) (UNSEG_HDR(akf, aid) | 0x80) + +#define BLOCK_COMPLETE(seg_n) (uint32_t)(((uint64_t)1 << (seg_n + 1)) - 1) + +#define SEQ_AUTH(iv_index, seq) (((uint64_t)iv_index) << 24 | (uint64_t)seq) + +/* How long to wait for available buffers before giving up */ +#define BUF_TIMEOUT 0 + +#define ACK_DELAY(seg_n) \ + (MIN(2 * seg_n + 1, BT_MESH_SAR_RX_ACK_DELAY_INC_X2) * BT_MESH_SAR_RX_SEG_INT_MS / 2) + +#define SEQAUTH_ALREADY_PROCESSED_TIMEOUT \ + (BT_MESH_SAR_RX_ACK_DELAY_INC_X2 * BT_MESH_SAR_RX_SEG_INT_MS / 2) + +static struct seg_tx +{ + struct bt_mesh_subnet *sub; + void *seg[BT_MESH_TX_SEG_MAX]; + uint64_t seq_auth; + int64_t adv_start_timestamp; /* Calculate adv duration and adjust intervals */ + uint16_t src; + uint16_t dst; + uint16_t ack_src; + uint16_t len; + uint8_t hdr; + uint8_t xmit; + uint8_t seg_n; /* Last segment index */ + uint8_t seg_o; /* Segment being sent */ + uint8_t nack_count; /* Number of unacked segs */ + uint8_t attempts_left; + uint8_t attempts_left_without_progress; + uint8_t ttl; /* Transmitted TTL value */ + uint8_t blocked : 1; /* Blocked by ongoing tx */ + uint8_t ctl : 1; /* Control packet */ + uint8_t aszmic : 1; /* MIC size */ + uint8_t started : 1; /* Start cb called */ + uint8_t friend_cred : 1; /* Using Friend credentials */ + uint8_t seg_send_started : 1; /* Used to check if seg_send_start cb is called */ + uint8_t ack_received : 1; /* Ack received during seg message transmission. */ + const struct bt_mesh_send_cb *cb; + void *cb_data; + struct k_work_delayable retransmit; /* Retransmit timer */ +} + +seg_tx[CONFIG_BT_MESH_TX_SEG_MSG_COUNT]; + +static struct seg_rx +{ + struct bt_mesh_subnet *sub; + void *seg[BT_MESH_RX_SEG_MAX]; + uint64_t seq_auth; + uint16_t src; + uint16_t dst; + uint16_t len; + uint8_t hdr; + uint8_t seg_n : 5; + uint8_t ctl : 1; + uint8_t in_use : 1; + uint8_t obo : 1; + uint8_t ttl; + uint8_t attempts_left; + uint32_t block; + uint32_t last_ack; + struct k_work_delayable ack; + struct k_work_delayable discard; +} + +seg_rx[CONFIG_BT_MESH_RX_SEG_MSG_COUNT]; + +K_MEM_SLAB_DEFINE(segs, BT_MESH_APP_SEG_SDU_MAX, CONFIG_BT_MESH_SEG_BUFS, 4); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int send_unseg(struct bt_mesh_net_tx *tx, + struct net_buf_simple *sdu, + const struct bt_mesh_send_cb *cb, + void *cb_data, + const uint8_t *ctl_op) +{ + struct bt_mesh_adv *adv; + + adv = bt_mesh_adv_create(BT_MESH_ADV_DATA, BT_MESH_ADV_TAG_LOCAL, tx->xmit, + BUF_TIMEOUT); + if (!adv) + { + LOG_ERR("Out of network advs"); + return -ENOBUFS; + } + + net_buf_simple_reserve(&adv->b, BT_MESH_NET_HDR_LEN); + + if (ctl_op) + { + net_buf_simple_add_u8(&adv->b, TRANS_CTL_HDR(*ctl_op, 0)); + } + else if (BT_MESH_IS_DEV_KEY(tx->ctx->app_idx)) + { + net_buf_simple_add_u8(&adv->b, UNSEG_HDR(0, 0)); + } + else + { + net_buf_simple_add_u8(&adv->b, UNSEG_HDR(1, tx->aid)); + } + + net_buf_simple_add_mem(&adv->b, sdu->data, sdu->len); + + if (IS_ENABLED(CONFIG_BT_MESH_FRIEND)) + { + if (!bt_mesh_friend_queue_has_space(tx->sub->net_idx, tx->src, + tx->ctx->addr, NULL, 1)) + { + if (BT_MESH_ADDR_IS_UNICAST(tx->ctx->addr)) + { + LOG_ERR("Not enough space in Friend Queue"); + bt_mesh_adv_unref(adv); + return -ENOBUFS; + } + else + { + LOG_WRN("No space in Friend Queue"); + goto send; + } + } + + if (bt_mesh_friend_enqueue_tx(tx, BT_MESH_FRIEND_PDU_SINGLE, NULL, 1, + &adv->b) && + BT_MESH_ADDR_IS_UNICAST(tx->ctx->addr)) + { + /* PDUs for a specific Friend should only go + * out through the Friend Queue. + */ + + bt_mesh_adv_unref(adv); + send_cb_finalize(cb, cb_data); + return 0; + } + } + +send: + return bt_mesh_net_send(tx, adv, cb, cb_data); +} + +static inline uint8_t seg_len(bool ctl) +{ + if (ctl) + { + return BT_MESH_CTL_SEG_SDU_MAX; + } + else + { + return BT_MESH_APP_SEG_SDU_MAX; + } +} + +bool bt_mesh_tx_in_progress(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(seg_tx); i++) + { + if (seg_tx[i].nack_count) + { + return true; + } + } + + return false; +} + +static void seg_tx_done(struct seg_tx *tx, uint8_t seg_idx) +{ + k_mem_slab_free(&segs, (void *)tx->seg[seg_idx]); + tx->seg[seg_idx] = NULL; + tx->nack_count--; +} + +static bool seg_tx_blocks(struct seg_tx *tx, uint16_t src, uint16_t dst) +{ + return (tx->src == src) && (tx->dst == dst); +} + +static void seg_tx_unblock_check(struct seg_tx *tx) +{ + struct seg_tx *blocked = NULL; + int i; + + /* Unblock the first blocked tx with the same params. */ + + for (i = 0; i < ARRAY_SIZE(seg_tx); ++i) + { + if (&seg_tx[i] != tx && seg_tx[i].blocked && + seg_tx_blocks(tx, seg_tx[i].src, seg_tx[i].dst) && + (!blocked || seg_tx[i].seq_auth < blocked->seq_auth)) + { + blocked = &seg_tx[i]; + } + } + + if (blocked) + { + LOG_DBG("Unblocked 0x%04x", + (uint16_t)(blocked->seq_auth & TRANS_SEQ_ZERO_MASK)); + blocked->blocked = false; + k_work_reschedule(&blocked->retransmit, 0); + } +} + +static void seg_tx_reset(struct seg_tx *tx) +{ + int i; + + /* If this call fails, the handler will exit early, as nack_count is 0. */ + + (void)k_work_cancel_delayable(&tx->retransmit); + + tx->cb = NULL; + tx->cb_data = NULL; + tx->seq_auth = 0U; + tx->sub = NULL; + tx->src = BT_MESH_ADDR_UNASSIGNED; + tx->dst = BT_MESH_ADDR_UNASSIGNED; + tx->ack_src = BT_MESH_ADDR_UNASSIGNED; + tx->blocked = false; + + for (i = 0; i <= tx->seg_n && tx->nack_count; i++) + { + if (!tx->seg[i]) + { + continue; + } + + seg_tx_done(tx, i); + } + + tx->nack_count = 0; + tx->seg_send_started = 0; + tx->ack_received = 0; + + if (atomic_test_and_clear_bit(bt_mesh.flags, BT_MESH_IVU_PENDING)) + { + LOG_DBG("Proceeding with pending IV Update"); + /* bt_mesh_net_iv_update() will re-enable the flag if this + * wasn't the only transfer. + */ + + bt_mesh_net_iv_update(bt_mesh.iv_index, false); + } +} + +static inline void seg_tx_complete(struct seg_tx *tx, int err) +{ + const struct bt_mesh_send_cb *cb = tx->cb; + void *cb_data = tx->cb_data; + + seg_tx_unblock_check(tx); + + seg_tx_reset(tx); + + if (cb && cb->end) + { + cb->end(err, cb_data); + } +} + +static void schedule_transmit_continue(struct seg_tx *tx, uint32_t delta) +{ + uint32_t timeout = 0; + + if (!tx->nack_count) + { + return; + } + + LOG_DBG(""); + + if (delta < BT_MESH_SAR_TX_SEG_INT_MS) + { + timeout = BT_MESH_SAR_TX_SEG_INT_MS - delta; + } + + /* If it is not the last segment then continue transmission after Segment + * Interval, otherwise continue immediately as the callback will finish + * this transmission and progress into retransmission. + */ + + k_work_reschedule(&tx->retransmit, + (tx->seg_o <= tx->seg_n) ? MSEC2TICK(timeout) : 0); +} + +static void seg_send_start(uint16_t duration, int err, void *user_data) +{ + struct seg_tx *tx = user_data; + + if (!tx->started && tx->cb && tx->cb->start) + { + tx->cb->start(duration, err, tx->cb_data); + tx->started = 1U; + } + + tx->seg_send_started = 1U; + tx->adv_start_timestamp = TICK2MSEC(clock_systime_ticks()); + + /* If there's an error in transmitting the 'sent' callback will never + * be called. Make sure that we kick the retransmit timer also in this case + * since otherwise we risk the transmission of becoming stale. + */ + + if (err) + { + schedule_transmit_continue(tx, 0); + } +} + +static void seg_sent(int err, void *user_data) +{ + struct seg_tx *tx = user_data; + uint32_t delta_ms = + (uint32_t)(TICK2MSEC(clock_systime_ticks()) - + tx->adv_start_timestamp); + + if (!tx->seg_send_started) + { + return; + } + + schedule_transmit_continue(tx, delta_ms); +} + +static const struct bt_mesh_send_cb seg_sent_cb = +{ + .start = seg_send_start, + .end = seg_sent, +}; + +static void seg_tx_buf_build(struct seg_tx *tx, + uint8_t seg_o, struct net_buf_simple *buf) +{ + uint16_t seq_zero = tx->seq_auth & TRANS_SEQ_ZERO_MASK; + uint8_t len = MIN(seg_len(tx->ctl), tx->len - (seg_len(tx->ctl) * seg_o)); + + net_buf_simple_add_u8(buf, tx->hdr); + net_buf_simple_add_u8(buf, (tx->aszmic << 7) | seq_zero >> 6); + net_buf_simple_add_u8(buf, (((seq_zero & 0x3f) << 2) | (seg_o >> 3))); + net_buf_simple_add_u8(buf, ((seg_o & 0x07) << 5) | tx->seg_n); + net_buf_simple_add_mem(buf, tx->seg[seg_o], len); +} + +static void seg_tx_send_unacked(struct seg_tx *tx) +{ + if (!tx->nack_count) + { + return; + } + + uint32_t delta_ms; + uint32_t timeout; + struct bt_mesh_msg_ctx ctx = + { + .net_idx = tx->sub->net_idx, + + /* App idx only used by network to detect control messages: */ + + .app_idx = (tx->ctl ? BT_MESH_KEY_UNUSED : 0), + .addr = tx->dst, + .send_rel = true, + .send_ttl = tx->ttl, + }; + + struct bt_mesh_net_tx net_tx = + { + .sub = tx->sub, + .ctx = &ctx, + .src = tx->src, + .xmit = tx->xmit, + .friend_cred = tx->friend_cred, + .aid = tx->hdr & AID_MASK, + }; + + if (BT_MESH_ADDR_IS_UNICAST(tx->dst) && + !tx->attempts_left_without_progress) + { + LOG_ERR("Ran out of retransmit without progress attempts"); + seg_tx_complete(tx, -ETIMEDOUT); + return; + } + + if (!tx->attempts_left) + { + if (BT_MESH_ADDR_IS_UNICAST(tx->dst)) + { + LOG_ERR("Ran out of retransmit attempts"); + seg_tx_complete(tx, -ETIMEDOUT); + } + else + { + /* Segmented sending to groups doesn't have acks, so + * running out of attempts is the expected behavior. + */ + + seg_tx_complete(tx, 0); + } + + return; + } + + LOG_DBG("SeqZero: 0x%04x Attempts: %u", + (uint16_t)(tx->seq_auth & TRANS_SEQ_ZERO_MASK), + tx->attempts_left); + + while (tx->seg_o <= tx->seg_n) + { + struct bt_mesh_adv *seg; + int err; + + if (!tx->seg[tx->seg_o]) + { + /* Move on to the next segment */ + + tx->seg_o++; + continue; + } + + seg = bt_mesh_adv_create(BT_MESH_ADV_DATA, BT_MESH_ADV_TAG_LOCAL, + tx->xmit, BUF_TIMEOUT); + if (!seg) + { + LOG_DBG("Allocating segment failed"); + goto end; + } + + net_buf_simple_reserve(&seg->b, BT_MESH_NET_HDR_LEN); + seg_tx_buf_build(tx, tx->seg_o, &seg->b); + + LOG_DBG("Sending %u/%u", tx->seg_o, tx->seg_n); + + err = bt_mesh_net_send(&net_tx, seg, &seg_sent_cb, tx); + if (err) + { + LOG_DBG("Sending segment failed"); + goto end; + } + + /* Move on to the next segment */ + + tx->seg_o++; + + tx->ack_received = 0U; + + /* Return here to let the advertising layer process the message. + * This function will be called again after Segment Interval. + */ + + return; + } + + /* All segments have been sent */ + + tx->seg_o = 0U; + tx->attempts_left--; + if (BT_MESH_ADDR_IS_UNICAST(tx->dst) && !tx->ack_received) + { + tx->attempts_left_without_progress--; + } + +end: + if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER) && bt_mesh_lpn_established() && + !bt_mesh_has_addr(ctx.addr)) + { + bt_mesh_lpn_poll(); + } + + delta_ms = + (uint32_t)(TICK2MSEC(clock_systime_ticks()) - + tx->adv_start_timestamp); + if (tx->ack_received) + { + /* Schedule retransmission immediately but keep SAR segment interval + * time if SegAck was received while sending last segment. + */ + + timeout = BT_MESH_SAR_TX_SEG_INT_MS; + tx->ack_received = 0U; + } + else + { + timeout = BT_MESH_SAR_TX_RETRANS_TIMEOUT_MS(tx->dst, tx->ttl); + } + + if (delta_ms < timeout) + { + timeout -= delta_ms; + } + + /* Schedule a retransmission */ + + k_work_reschedule(&tx->retransmit, MSEC2TICK(timeout)); +} + +static void seg_retransmit(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct seg_tx *tx = CONTAINER_OF(dwork, struct seg_tx, retransmit); + + seg_tx_send_unacked(tx); +} + +static int send_seg( + struct bt_mesh_net_tx *net_tx, struct net_buf_simple *sdu, + const struct bt_mesh_send_cb *cb, void *cb_data, uint8_t *ctl_op) +{ + bool blocked = false; + struct seg_tx *tx; + uint8_t seg_o; + int i; + + LOG_DBG("src 0x%04x dst 0x%04x app_idx 0x%04x aszmic %u sdu_len %u", + net_tx->src, + net_tx->ctx->addr, net_tx->ctx->app_idx, net_tx->aszmic, sdu->len); + + for (tx = NULL, i = 0; i < ARRAY_SIZE(seg_tx); i++) + { + if (seg_tx[i].nack_count) + { + blocked |= seg_tx_blocks(&seg_tx[i], net_tx->src, + net_tx->ctx->addr); + } + else if (!tx) + { + tx = &seg_tx[i]; + } + } + + if (!tx) + { + LOG_ERR("No multi-segment message contexts available"); + return -EBUSY; + } + + if (ctl_op) + { + tx->hdr = TRANS_CTL_HDR(*ctl_op, 1); + } + else if (BT_MESH_IS_DEV_KEY(net_tx->ctx->app_idx)) + { + tx->hdr = SEG_HDR(0, 0); + } + else + { + tx->hdr = SEG_HDR(1, net_tx->aid); + } + + tx->src = net_tx->src; + tx->dst = net_tx->ctx->addr; + tx->seg_n = (sdu->len - 1) / seg_len(!!ctl_op); + tx->seg_o = 0; + tx->len = sdu->len; + tx->nack_count = tx->seg_n + 1; + tx->seq_auth = SEQ_AUTH(BT_MESH_NET_IVI_TX, bt_mesh.seq); + tx->sub = net_tx->sub; + tx->cb = cb; + tx->cb_data = cb_data; + tx->attempts_left = BT_MESH_SAR_TX_RETRANS_COUNT(tx->dst); + tx->attempts_left_without_progress = BT_MESH_SAR_TX_RETRANS_NO_PROGRESS; + tx->xmit = net_tx->xmit; + tx->aszmic = net_tx->aszmic; + tx->friend_cred = net_tx->friend_cred; + tx->blocked = blocked; + tx->started = 0; + tx->seg_send_started = 0; + tx->ctl = !!ctl_op; + tx->ttl = net_tx->ctx->send_ttl; + + LOG_DBG("SeqZero 0x%04x (segs: %u)", + (uint16_t)(tx->seq_auth & TRANS_SEQ_ZERO_MASK), + tx->nack_count); + + if (IS_ENABLED(CONFIG_BT_MESH_FRIEND) && + !bt_mesh_friend_queue_has_space(tx->sub->net_idx, net_tx->src, tx->dst, + &tx->seq_auth, + tx->seg_n + 1) && + BT_MESH_ADDR_IS_UNICAST(tx->dst)) + { + LOG_ERR("Not enough space in Friend Queue for %u segments", + tx->seg_n + 1); + seg_tx_reset(tx); + return -ENOBUFS; + } + + for (seg_o = 0U; sdu->len; seg_o++) + { + void *buf; + uint16_t len; + int err; + + err = k_mem_slab_alloc(&segs, &buf, BUF_TIMEOUT); + if (err) + { + LOG_ERR("Out of segment buffers"); + seg_tx_reset(tx); + return -ENOBUFS; + } + + len = MIN(sdu->len, seg_len(!!ctl_op)); + memcpy(buf, net_buf_simple_pull_mem(sdu, len), len); + + LOG_DBG("seg %u: %s", seg_o, bt_hex(buf, len)); + + tx->seg[seg_o] = buf; + + if (IS_ENABLED(CONFIG_BT_MESH_FRIEND)) + { + enum bt_mesh_friend_pdu_type type; + + NET_BUF_SIMPLE_DEFINE(seg, 16); + seg_tx_buf_build(tx, seg_o, &seg); + + if (seg_o == tx->seg_n) + { + type = BT_MESH_FRIEND_PDU_COMPLETE; + } + else + { + type = BT_MESH_FRIEND_PDU_PARTIAL; + } + + if (bt_mesh_friend_enqueue_tx(net_tx, type, + ctl_op ? NULL : &tx->seq_auth, tx->seg_n + 1, + &seg) && + BT_MESH_ADDR_IS_UNICAST(net_tx->ctx->addr)) + { + /* PDUs for a specific Friend should only go + * out through the Friend Queue. + */ + + k_mem_slab_free(&segs, buf); + tx->seg[seg_o] = NULL; + } + } + } + + /* This can happen if segments only went into the Friend Queue */ + + if (IS_ENABLED(CONFIG_BT_MESH_FRIEND) && !tx->seg[0]) + { + seg_tx_reset(tx); + + /* If there was a callback notify sending immediately since + * there's no other way to track this (at least currently) with the + * Friend Queue. + */ + + send_cb_finalize(cb, cb_data); + return 0; + } + + if (blocked) + { + /* Move the sequence number, so we don't end up creating + * another segmented transmission with the same SeqZero while this one + * is blocked. + */ + + bt_mesh_next_seq(); + LOG_DBG("Blocked."); + return 0; + } + + seg_tx_send_unacked(tx); + + return 0; +} + +static int trans_encrypt( + const struct bt_mesh_net_tx *tx, const struct bt_mesh_key *key, + struct net_buf_simple *msg) +{ + struct bt_mesh_app_crypto_ctx crypto = + { + .dev_key = BT_MESH_IS_DEV_KEY(tx->ctx->app_idx), + .aszmic = tx->aszmic, + .src = tx->src, + .dst = tx->ctx->addr, + .seq_num = bt_mesh.seq, + .iv_index = BT_MESH_NET_IVI_TX, + }; + + if (BT_MESH_ADDR_IS_VIRTUAL(tx->ctx->addr)) + { + crypto.ad = tx->ctx->uuid; + } + + return bt_mesh_app_encrypt(key, &crypto, msg); +} + +int bt_mesh_trans_send(struct bt_mesh_net_tx *tx, struct net_buf_simple *msg, + const struct bt_mesh_send_cb *cb, void *cb_data) +{ + const struct bt_mesh_key *key; + uint8_t aid; + int err; + + if (msg->len < 1) + { + LOG_ERR("Zero-length SDU not allowed"); + return -EINVAL; + } + + if (msg->len > BT_MESH_TX_SDU_MAX - BT_MESH_MIC_SHORT) + { + LOG_ERR("Message too big: %u", msg->len); + return -EMSGSIZE; + } + + if (net_buf_simple_tailroom(msg) < BT_MESH_MIC_SHORT) + { + LOG_ERR("Insufficient tailroom for Transport MIC"); + return -EINVAL; + } + + if (tx->ctx->send_ttl == BT_MESH_TTL_DEFAULT) + { + tx->ctx->send_ttl = bt_mesh_default_ttl_get(); + } + else if (tx->ctx->send_ttl > BT_MESH_TTL_MAX) + { + LOG_ERR("TTL too large (max 127)"); + return -EINVAL; + } + + if (msg->len > BT_MESH_SDU_UNSEG_MAX) + { + tx->ctx->send_rel = true; + } + + if (tx->ctx->addr == BT_MESH_ADDR_UNASSIGNED || + (!BT_MESH_ADDR_IS_UNICAST(tx->ctx->addr) + && BT_MESH_IS_DEV_KEY(tx->ctx->app_idx))) + { + LOG_ERR("Invalid destination address"); + return -EINVAL; + } + + err = bt_mesh_keys_resolve(tx->ctx, &tx->sub, &key, &aid); + if (err) + { + return err; + } + + LOG_DBG("net_idx 0x%04x app_idx 0x%04x dst 0x%04x", tx->sub->net_idx, + tx->ctx->app_idx, + tx->ctx->addr); + LOG_DBG("len %u: %s", msg->len, bt_hex(msg->data, msg->len)); + + tx->xmit = bt_mesh_net_transmit_get(); + tx->aid = aid; + + if (!tx->ctx->send_rel || net_buf_simple_tailroom(msg) < 8) + { + tx->aszmic = 0U; + } + else + { + tx->aszmic = 1U; + } + + err = trans_encrypt(tx, key, msg); + if (err) + { + return err; + } + + if (tx->ctx->send_rel) + { + err = send_seg(tx, msg, cb, cb_data, NULL); + } + else + { + err = send_unseg(tx, msg, cb, cb_data, NULL); + } + + return err; +} + +static void seg_rx_assemble(struct seg_rx *rx, + struct net_buf_simple *buf, uint8_t aszmic) +{ + int i; + + net_buf_simple_reset(buf); + + for (i = 0; i <= rx->seg_n; i++) + { + net_buf_simple_add_mem(buf, rx->seg[i], + MIN(seg_len(rx->ctl), + rx->len - (i * seg_len(rx->ctl)))); + } + + /* Adjust the length to not contain the MIC at the end */ + + if (!rx->ctl) + { + buf->len -= APP_MIC_LEN(aszmic); + } +} + +struct decrypt_ctx +{ + struct bt_mesh_app_crypto_ctx crypto; + struct net_buf_simple *buf; + struct net_buf_simple *sdu; + struct seg_rx *seg; +}; + +static int sdu_try_decrypt(struct bt_mesh_net_rx *rx, + const struct bt_mesh_key *key, void *cb_data) +{ + struct decrypt_ctx *ctx = cb_data; + int err; + + ctx->crypto.ad = NULL; + + do + { + if (ctx->seg) + { + seg_rx_assemble(ctx->seg, ctx->buf, ctx->crypto.aszmic); + } + + if (BT_MESH_ADDR_IS_VIRTUAL(rx->ctx.recv_dst)) + { + ctx->crypto.ad = bt_mesh_va_uuid_get(rx->ctx.recv_dst, + ctx->crypto.ad, NULL); + + if (!ctx->crypto.ad) + { + return -ENOENT; + } + } + + net_buf_simple_reset(ctx->sdu); + + err = bt_mesh_app_decrypt(key, &ctx->crypto, ctx->buf, ctx->sdu); + } + while (err && ctx->crypto.ad != NULL); + + if (!err && BT_MESH_ADDR_IS_VIRTUAL(rx->ctx.recv_dst)) + { + rx->ctx.uuid = ctx->crypto.ad; + } + + return err; +} + +static int sdu_recv(struct bt_mesh_net_rx *rx, + uint8_t hdr, + uint8_t aszmic, + struct net_buf_simple *buf, + struct net_buf_simple *sdu, + struct seg_rx *seg) +{ + struct decrypt_ctx ctx = + { + .crypto = + { + .dev_key = !AKF(&hdr), + .aszmic = aszmic, + .src = rx->ctx.addr, + .dst = rx->ctx.recv_dst, + .seq_num = + seg ? (seg->seq_auth & 0xffffff) : rx->seq, + .iv_index = BT_MESH_NET_IVI_RX(rx), + }, + .buf = buf, + .sdu = sdu, + .seg = seg, + }; + + LOG_DBG("AKF %u AID 0x%02x", !ctx.crypto.dev_key, AID(&hdr)); + + if (!rx->local_match) + { + /* if friend_match was set the frame is for LPN which we are friends. */ + + return rx->friend_match ? 0 : -ENXIO; + } + + rx->ctx.app_idx = bt_mesh_app_key_find(ctx.crypto.dev_key, AID(&hdr), rx, + sdu_try_decrypt, &ctx); + if (rx->ctx.app_idx == BT_MESH_KEY_UNUSED) + { + LOG_DBG("No matching AppKey"); + return -EACCES; + } + + rx->ctx.uuid = ctx.crypto.ad; + + LOG_DBG("Decrypted (AppIdx: 0x%03x)", rx->ctx.app_idx); + + return bt_mesh_access_recv(&rx->ctx, sdu); +} + +static struct seg_tx *seg_tx_lookup(uint16_t seq_zero, + uint8_t obo, uint16_t addr) +{ + struct seg_tx *tx; + int i; + + for (i = 0; i < ARRAY_SIZE(seg_tx); i++) + { + tx = &seg_tx[i]; + + if ((tx->seq_auth & TRANS_SEQ_ZERO_MASK) != seq_zero) + { + continue; + } + + if (tx->dst == addr) + { + return tx; + } + + /* If the expected remote address doesn't match, + * but the OBO flag is set and this is the first acknowledgement, + * assume it's a Friend that's responding and therefore accept the + * message. + */ + + if (obo && (tx->nack_count == tx->seg_n + 1 || tx->ack_src == addr)) + { + tx->ack_src = addr; + return tx; + } + } + + return NULL; +} + +static int trans_ack( + struct bt_mesh_net_rx *rx, uint8_t hdr, struct net_buf_simple *buf, + uint64_t *seq_auth) +{ + bool new_seg_ack = false; + struct seg_tx *tx; + unsigned int bit; + uint32_t ack; + uint16_t seq_zero; + uint8_t obo; + + if (buf->len < 6) + { + LOG_ERR("Too short ack message"); + return -EBADMSG; + } + + seq_zero = net_buf_simple_pull_be16(buf); + obo = seq_zero >> 15; + seq_zero = (seq_zero >> 2) & TRANS_SEQ_ZERO_MASK; + + if (IS_ENABLED(CONFIG_BT_MESH_FRIEND) && rx->friend_match) + { + LOG_DBG("Ack for LPN 0x%04x of this Friend", rx->ctx.recv_dst); + + /* Best effort - we don't have enough info for true SeqAuth */ + + *seq_auth = SEQ_AUTH(BT_MESH_NET_IVI_RX(rx), seq_zero); + return 0; + } + else if (!rx->local_match) + { + return 0; + } + + ack = net_buf_simple_pull_be32(buf); + + LOG_DBG("OBO %u seq_zero 0x%04x ack 0x%08x", obo, seq_zero, ack); + + tx = seg_tx_lookup(seq_zero, obo, rx->ctx.addr); + if (!tx) + { + LOG_DBG("No matching TX context for ack"); + return -ENOENT; + } + + if (!BT_MESH_ADDR_IS_UNICAST(tx->dst)) + { + LOG_ERR("Received ack for group seg"); + return -EINVAL; + } + + *seq_auth = tx->seq_auth; + + if (!ack) + { + LOG_WRN("SDU canceled"); + seg_tx_complete(tx, -ECANCELED); + return 0; + } + + if (fls(ack) - 1 > tx->seg_n) + { + LOG_ERR("Too large segment number in ack"); + return -EINVAL; + } + + while (ack) + { + bit = ffs(ack); + if (tx->seg[bit - 1]) + { + LOG_DBG("seg %u/%u acked", bit - 1, tx->seg_n); + seg_tx_done(tx, bit - 1); + new_seg_ack = true; + } + + ack &= ~BIT(bit - 1); + } + + if (new_seg_ack) + { + tx->attempts_left_without_progress = + BT_MESH_SAR_TX_RETRANS_NO_PROGRESS; + } + + if (tx->nack_count) + { + /* If transmission is not in progress it means + * that Retransmission Timer is running + */ + + if (tx->seg_o == 0) + { + clock_t timeout = 0; + + /* If there are no retransmission attempts left we + * immediately trigger the retransmit call that will end the + * transmission. + */ + + if ((BT_MESH_ADDR_IS_UNICAST(tx->dst) && + !tx->attempts_left_without_progress) || + !tx->attempts_left) + { + goto reschedule; + } + + uint32_t delta_ms = + ( + uint32_t)( + TICK2MSEC(clock_systime_ticks()) - + tx->adv_start_timestamp); + + /* According to MshPRTv1.1: 3.5.3.3.2, we should reset the + * retransmit timer and retransmit immediately when receiving a + * valid ack message while Retransmisison timer is running. + * However, transport should still keep segment transmission + * interval time between transmission of each segment. + */ + + if (delta_ms < BT_MESH_SAR_TX_SEG_INT_MS) + { + timeout = MSEC2TICK(BT_MESH_SAR_TX_SEG_INT_MS - delta_ms); + } + +reschedule: + k_work_reschedule(&tx->retransmit, timeout); + } + else + { + tx->ack_received = 1U; + } + } + else + { + LOG_DBG("SDU TX complete"); + seg_tx_complete(tx, 0); + } + + return 0; +} + +static int ctl_recv( + struct bt_mesh_net_rx *rx, uint8_t hdr, struct net_buf_simple *buf, + uint64_t *seq_auth) +{ + uint8_t ctl_op = TRANS_CTL_OP(&hdr); + + LOG_DBG("OpCode 0x%02x len %u", ctl_op, buf->len); + + switch (ctl_op) + { + case TRANS_CTL_OP_ACK: + return trans_ack(rx, hdr, buf, seq_auth); + case TRANS_CTL_OP_HEARTBEAT: + return bt_mesh_hb_recv(rx, buf); + } + + /* Only acks for friendship and heartbeats may need processing without + * local_match + */ + + if (!rx->local_match) + { + return 0; + } + + if (IS_ENABLED(CONFIG_BT_MESH_FRIEND) && !bt_mesh_lpn_established()) + { + switch (ctl_op) + { + case TRANS_CTL_OP_FRIEND_POLL: + return bt_mesh_friend_poll(rx, buf); + case TRANS_CTL_OP_FRIEND_REQ: + return bt_mesh_friend_req(rx, buf); + case TRANS_CTL_OP_FRIEND_CLEAR: + return bt_mesh_friend_clear(rx, buf); + case TRANS_CTL_OP_FRIEND_CLEAR_CFM: + return bt_mesh_friend_clear_cfm(rx, buf); + case TRANS_CTL_OP_FRIEND_SUB_ADD: + return bt_mesh_friend_sub_add(rx, buf); + case TRANS_CTL_OP_FRIEND_SUB_REM: + return bt_mesh_friend_sub_rem(rx, buf); + } + } + +#if defined(CONFIG_BT_MESH_LOW_POWER) + if (ctl_op == TRANS_CTL_OP_FRIEND_OFFER) + { + return bt_mesh_lpn_friend_offer(rx, buf); + } + + if (rx->ctx.addr == bt_mesh.lpn.frnd) + { + if (ctl_op == TRANS_CTL_OP_FRIEND_CLEAR_CFM) + { + return bt_mesh_lpn_friend_clear_cfm(rx, buf); + } + + if (!rx->friend_cred) + { + LOG_WRN("Message from friend with wrong credentials"); + return -EINVAL; + } + + switch (ctl_op) + { + case TRANS_CTL_OP_FRIEND_UPDATE: + return bt_mesh_lpn_friend_update(rx, buf); + case TRANS_CTL_OP_FRIEND_SUB_CFM: + return bt_mesh_lpn_friend_sub_cfm(rx, buf); + } + } +#endif /* CONFIG_BT_MESH_LOW_POWER */ + + LOG_WRN("Unhandled TransOpCode 0x%02x", ctl_op); + + return -EBADMSG; +} + +static int trans_unseg(struct net_buf_simple *buf, + struct bt_mesh_net_rx *rx, uint64_t *seq_auth) +{ + NET_BUF_SIMPLE_DEFINE_STATIC(sdu, BT_MESH_SDU_UNSEG_MAX); + uint8_t hdr; + struct bt_mesh_rpl *rpl = NULL; + int err; + + LOG_DBG("AFK %u AID 0x%02x", AKF(buf->data), AID(buf->data)); + + if (buf->len < 1) + { + LOG_ERR("Too small unsegmented PDU"); + return -EBADMSG; + } + + if (bt_mesh_rpl_check(rx, &rpl, false)) + { + LOG_WRN("Replay: src 0x%04x dst 0x%04x seq 0x%06x", rx->ctx.addr, + rx->ctx.recv_dst, rx->seq); + return -EINVAL; + } + + hdr = net_buf_simple_pull_u8(buf); + + if (rx->ctl) + { + err = ctl_recv(rx, hdr, buf, seq_auth); + } + else if (buf->len < 1 + APP_MIC_LEN(0)) + { + LOG_ERR("Too short SDU + MIC"); + err = -EINVAL; + } + else + { + /* Adjust the length to not contain the MIC at the end */ + + buf->len -= APP_MIC_LEN(0); + err = sdu_recv(rx, hdr, 0, buf, &sdu, NULL); + } + + /* Update rpl only if there is place and upper logic accepted incoming + * data. + */ + + if (err == 0 && rpl != NULL) + { + bt_mesh_rpl_update(rpl, rx); + } + + return err; +} + +int bt_mesh_ctl_send( + struct bt_mesh_net_tx *tx, uint8_t ctl_op, void *data, size_t data_len, + const struct bt_mesh_send_cb *cb, void *cb_data) +{ + struct net_buf_simple buf; + + if (tx->ctx->send_ttl == BT_MESH_TTL_DEFAULT) + { + tx->ctx->send_ttl = bt_mesh_default_ttl_get(); + } + else if (tx->ctx->send_ttl > BT_MESH_TTL_MAX) + { + LOG_ERR("TTL too large (max 127)"); + return -EINVAL; + } + + net_buf_simple_init_with_data(&buf, data, data_len); + + if (data_len > BT_MESH_SDU_UNSEG_MAX) + { + tx->ctx->send_rel = true; + } + + tx->ctx->app_idx = BT_MESH_KEY_UNUSED; + + if (tx->ctx->addr == BT_MESH_ADDR_UNASSIGNED || + BT_MESH_ADDR_IS_VIRTUAL(tx->ctx->addr)) + { + LOG_ERR("Invalid destination address"); + return -EINVAL; + } + + LOG_DBG("src 0x%04x dst 0x%04x ttl 0x%02x ctl 0x%02x", tx->src, + tx->ctx->addr, tx->ctx->send_ttl, + ctl_op); + LOG_DBG("len %zu: %s", data_len, bt_hex(data, data_len)); + + if (tx->ctx->send_rel) + { + return send_seg(tx, &buf, cb, cb_data, &ctl_op); + } + else + { + return send_unseg(tx, &buf, cb, cb_data, &ctl_op); + } +} + +static int send_ack( + struct bt_mesh_subnet *sub, uint16_t src, uint16_t dst, uint8_t ttl, + uint64_t *seq_auth, uint32_t block, uint8_t obo) +{ + struct bt_mesh_msg_ctx ctx = + { + .net_idx = sub->net_idx, + .app_idx = BT_MESH_KEY_UNUSED, + .addr = dst, + .send_ttl = ttl, + }; + + struct bt_mesh_net_tx tx = + { + .sub = sub, + .ctx = &ctx, + .src = obo ? bt_mesh_primary_addr() : src, + .xmit = bt_mesh_net_transmit_get(), + }; + + uint16_t seq_zero = *seq_auth & TRANS_SEQ_ZERO_MASK; + uint8_t buf[6]; + + LOG_DBG("SeqZero 0x%04x Block 0x%08x OBO %u", seq_zero, block, obo); + + if (bt_mesh_lpn_established() && !bt_mesh_has_addr(ctx.addr)) + { + LOG_WRN("Not sending ack when LPN is enabled"); + return 0; + } + + /* This can happen if the segmented message was destined for a group + * or virtual address. + */ + + if (!BT_MESH_ADDR_IS_UNICAST(src)) + { + LOG_DBG("Not sending ack for non-unicast address"); + return 0; + } + + sys_put_be16(((seq_zero << 2) & 0x7ffc) | (obo << 15), buf); + sys_put_be32(block, &buf[2]); + + return bt_mesh_ctl_send(&tx, TRANS_CTL_OP_ACK, buf, sizeof(buf), NULL, + NULL); +} + +static void seg_rx_reset(struct seg_rx *rx, bool full_reset) +{ + int i; + + LOG_DBG("rx %p", rx); + + /* If this fails, the handler will exit early on the next execution, as + * it checks rx->in_use. + */ + + (void)k_work_cancel_delayable(&rx->ack); + (void)k_work_cancel_delayable(&rx->discard); + + if (IS_ENABLED(CONFIG_BT_MESH_FRIEND) && rx->obo && + rx->block != BLOCK_COMPLETE(rx->seg_n)) + { + LOG_WRN("Clearing incomplete buffers from Friend queue"); + bt_mesh_friend_clear_incomplete(rx->sub, rx->src, rx->dst, + &rx->seq_auth); + } + + for (i = 0; i <= rx->seg_n; i++) + { + if (!rx->seg[i]) + { + continue; + } + + k_mem_slab_free(&segs, rx->seg[i]); + rx->seg[i] = NULL; + } + + rx->in_use = 0U; + + /* We don't always reset these values since we need to be able to + * send an ack if we receive a segment after we've already received the + * full SDU. + */ + + if (full_reset) + { + rx->seq_auth = 0U; + rx->sub = NULL; + rx->src = BT_MESH_ADDR_UNASSIGNED; + rx->dst = BT_MESH_ADDR_UNASSIGNED; + } +} + +static void seg_discard(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct seg_rx *rx = CONTAINER_OF(dwork, struct seg_rx, discard); + + LOG_WRN("SAR Discard timeout expired"); + seg_rx_reset(rx, false); + rx->block = 0U; + + if (IS_ENABLED(CONFIG_BT_TESTING)) + { + bt_mesh_test_trans_incomp_timer_exp(); + } +} + +static void seg_ack(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct seg_rx *rx = CONTAINER_OF(dwork, struct seg_rx, ack); + + if (!rx->in_use || rx->block == BLOCK_COMPLETE(rx->seg_n)) + { + /* Cancellation of this timer may have failed. If it fails as + * part of seg_reset, in_use will be false. If it fails as part of the + * processing of a fully received SDU, the ack is already being sent + * from the receive handler, and the timer based ack sending can be + * ignored. + */ + + return; + } + + LOG_DBG("rx %p", rx); + + send_ack(rx->sub, rx->dst, rx->src, rx->ttl, &rx->seq_auth, rx->block, + rx->obo); + + rx->last_ack = (uint32_t)TICK2MSEC(clock_systime_ticks()); + + if (rx->attempts_left == 0) + { + LOG_DBG("Ran out of ack retransmit attempts"); + return; + } + + if (rx->seg_n > BT_MESH_SAR_RX_SEG_THRESHOLD) + { + --rx->attempts_left; + k_work_schedule(&rx->ack, MSEC2TICK(BT_MESH_SAR_RX_SEG_INT_MS)); + } +} + +static inline bool sdu_len_is_ok(bool ctl, uint8_t seg_n) +{ + return (seg_n < BT_MESH_RX_SEG_MAX); +} + +static struct seg_rx *seg_rx_find(struct bt_mesh_net_rx *net_rx, + const uint64_t *seq_auth) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(seg_rx); i++) + { + struct seg_rx *rx = &seg_rx[i]; + + if (rx->src != net_rx->ctx.addr || rx->dst != net_rx->ctx.recv_dst) + { + continue; + } + + /* Return newer RX context in addition to an exact match, so + * the calling function can properly discard an old SeqAuth. + */ + + if (rx->seq_auth >= *seq_auth) + { + return rx; + } + + if (rx->in_use) + { + LOG_WRN("Duplicate SDU from src 0x%04x", net_rx->ctx.addr); + + /* Clear out the old context since the sender + * has apparently started sending a new SDU. + */ + + seg_rx_reset(rx, true); + + /* Return non-match so caller can re-allocate */ + + return NULL; + } + } + + return NULL; +} + +static bool seg_rx_is_valid( + struct seg_rx *rx, struct bt_mesh_net_rx *net_rx, const uint8_t *hdr, + uint8_t seg_n) +{ + if (rx->hdr != *hdr || rx->seg_n != seg_n) + { + LOG_ERR("Invalid segment for ongoing session"); + return false; + } + + if (rx->src != net_rx->ctx.addr || rx->dst != net_rx->ctx.recv_dst) + { + LOG_ERR("Invalid source or destination for segment"); + return false; + } + + if (rx->ctl != net_rx->ctl) + { + LOG_ERR("Inconsistent CTL in segment"); + return false; + } + + return true; +} + +static struct seg_rx *seg_rx_alloc( + struct bt_mesh_net_rx *net_rx, const uint8_t *hdr, + const uint64_t *seq_auth, uint8_t seg_n) +{ + int i; + + /* No race condition on this check, as this function only executes in + * the collaborative Bluetooth rx thread: + */ + + if (k_mem_slab_num_free_get(&segs) < 1) + { + LOG_WRN("Not enough segments for incoming message"); + return NULL; + } + + for (i = 0; i < ARRAY_SIZE(seg_rx); i++) + { + struct seg_rx *rx = &seg_rx[i]; + + if (rx->in_use) + { + continue; + } + + rx->in_use = 1U; + rx->sub = net_rx->sub; + rx->ctl = net_rx->ctl; + rx->seq_auth = *seq_auth; + rx->seg_n = seg_n; + rx->hdr = *hdr; + rx->ttl = net_rx->ctx.send_ttl; + rx->src = net_rx->ctx.addr; + rx->dst = net_rx->ctx.recv_dst; + rx->block = 0U; + + LOG_DBG("New RX context. Block Complete 0x%08x", + BLOCK_COMPLETE(seg_n)); + + return rx; + } + + return NULL; +} + +static int trans_seg( + struct net_buf_simple *buf, struct bt_mesh_net_rx *net_rx, + enum bt_mesh_friend_pdu_type *pdu_type, uint64_t *seq_auth, + uint8_t *seg_count) +{ + struct bt_mesh_rpl *rpl = NULL; + struct seg_rx *rx; + uint8_t *hdr = buf->data; + uint16_t seq_zero; + uint32_t auth_seqnum; + uint8_t seg_n; + uint8_t seg_o; + int err; + + if (buf->len < 5) + { + LOG_ERR("Too short segmented message (len %u)", buf->len); + return -EBADMSG; + } + + if (bt_mesh_rpl_check(net_rx, &rpl, false)) + { + LOG_WRN("Replay: src 0x%04x dst 0x%04x seq 0x%06x", net_rx->ctx.addr, + net_rx->ctx.recv_dst, + net_rx->seq); + return -EINVAL; + } + + LOG_DBG("ASZMIC %u AKF %u AID 0x%02x", ASZMIC(hdr), AKF(hdr), AID(hdr)); + + net_buf_simple_pull(buf, 1); + + seq_zero = net_buf_simple_pull_be16(buf); + seg_o = (seq_zero & 0x03) << 3; + seq_zero = (seq_zero >> 2) & TRANS_SEQ_ZERO_MASK; + seg_n = net_buf_simple_pull_u8(buf); + seg_o |= seg_n >> 5; + seg_n &= 0x1f; + + LOG_DBG("SeqZero 0x%04x SegO %u SegN %u", seq_zero, seg_o, seg_n); + + if (seg_o > seg_n) + { + LOG_ERR("SegO greater than SegN (%u > %u)", seg_o, seg_n); + return -EBADMSG; + } + + /* According to MshPRTv1.1: + * "The SeqAuth is composed of the IV Index and the sequence number (SEQ) + * of the first segment" + * + * Therefore we need to calculate very first SEQ in order to find seqAuth. + * We can calculate as below: + * + * SEQ(0) = SEQ(n) - (delta between seqZero and SEQ(n) by looking into 14 + * least significant bits of SEQ(n)) + * + * Mentioned delta shall be >= 0, if it is not then seq_auth will be broken + * and it will be verified by the code below. + */ + + *seq_auth = + SEQ_AUTH(BT_MESH_NET_IVI_RX(net_rx), + ( + net_rx->seq - ( + (((net_rx->seq & BIT_MASK(14)) - + seq_zero)) & BIT_MASK(13)))); + auth_seqnum = *seq_auth & BIT_MASK(24); + *seg_count = seg_n + 1; + + /* Look for old RX sessions */ + + rx = seg_rx_find(net_rx, seq_auth); + if (rx) + { + /* Discard old SeqAuth packet */ + + if (rx->seq_auth > *seq_auth) + { + LOG_WRN("Ignoring old SeqAuth"); + return -EINVAL; + } + + if (!seg_rx_is_valid(rx, net_rx, hdr, seg_n)) + { + return -EINVAL; + } + + if (rx->in_use) + { + LOG_DBG("Existing RX context. Block 0x%08x", rx->block); + goto found_rx; + } + + if (rx->block == BLOCK_COMPLETE(rx->seg_n)) + { + LOG_DBG("Got segment for already complete SDU"); + + /* We should not send more than one Segment Acknowledgment message + * for the same SeqAuth in a period of: [acknowledgment delay + * increment * segment transmission interval] milliseconds + */ + + if ((uint32_t)TICK2MSEC(clock_systime_ticks()) - rx->last_ack > + SEQAUTH_ALREADY_PROCESSED_TIMEOUT) + { + send_ack(net_rx->sub, net_rx->ctx.recv_dst, net_rx->ctx.addr, + net_rx->ctx.send_ttl, + seq_auth, rx->block, rx->obo); + rx->last_ack = (uint32_t)TICK2MSEC(clock_systime_ticks()); + } + + if (rpl) + { + bt_mesh_rpl_update(rpl, net_rx); + } + + return -EALREADY; + } + + /* We ignore instead of sending block ack 0 since the + * ack timer is always smaller than the incomplete timer, i.e. the + * sender is misbehaving. + */ + + LOG_WRN("Got segment for canceled SDU"); + return -EINVAL; + } + + /* Bail out early if we're not ready to receive such a large SDU */ + + if (!sdu_len_is_ok(net_rx->ctl, seg_n)) + { + LOG_ERR("Too big incoming SDU length"); + send_ack(net_rx->sub, net_rx->ctx.recv_dst, net_rx->ctx.addr, + net_rx->ctx.send_ttl, seq_auth, + 0, net_rx->friend_match); + return -EMSGSIZE; + } + + /* Verify early that there will be space in the Friend Queue(s) in + * case this message is destined to an LPN of ours. + */ + + if (IS_ENABLED(CONFIG_BT_MESH_FRIEND) && net_rx->friend_match && + !net_rx->local_match && + !bt_mesh_friend_queue_has_space(net_rx->sub->net_idx, net_rx->ctx.addr, + net_rx->ctx.recv_dst, + seq_auth, *seg_count)) + { + LOG_ERR("No space in Friend Queue for %u segments", *seg_count); + send_ack(net_rx->sub, net_rx->ctx.recv_dst, net_rx->ctx.addr, + net_rx->ctx.send_ttl, seq_auth, + 0, net_rx->friend_match); + return -ENOBUFS; + } + + /* Keep track of the received SeqAuth values received from this address + * and discard segmented messages that are not newer, as described in + * MshPRTv1.1: 3.5.3.4. + * + * The logic on the first segmented receive is a bit special, since the + * initial value of rpl->seg is 0, which would normally fail the comparison + * check with auth_seqnum: + * - If this is the first time we receive from this source, rpl->src + * will be 0, and we can skip this check. + * - If this is the first time we receive from this source on the new IV + * index, rpl->old_iv will be set, and the check is also skipped. + * - If this is the first segmented message on the new IV index, but we + * have received an unsegmented message already, the unsegmented message + * will have reset rpl->seg to 0, and this message's SeqAuth cannot be + * zero. + */ + + if (rpl && rpl->src && auth_seqnum <= rpl->seg && (!rpl->old_iv || + net_rx->old_iv)) + { + LOG_WRN("Ignoring old SeqAuth 0x%06x", auth_seqnum); + return -EALREADY; + } + + /* Look for free slot for a new RX session */ + + rx = seg_rx_alloc(net_rx, hdr, seq_auth, seg_n); + if (!rx) + { + /* Warn but don't cancel since the existing slots will + * eventually be freed up and we'll be able to process this one. + */ + + LOG_WRN("No free slots for new incoming segmented messages"); + return -ENOMEM; + } + + rx->obo = net_rx->friend_match; + +found_rx: + if (BIT(seg_o) & rx->block) + { + LOG_DBG("Received already received fragment"); + return -EALREADY; + } + + /* All segments, except the last one, must either have 8 bytes of + * payload (for 64bit Net MIC) or 12 bytes of payload (for 32bit Net MIC). + */ + + if (seg_o == seg_n) + { + /* Set the expected final buffer length */ + + rx->len = seg_n * seg_len(rx->ctl) + buf->len; + LOG_DBG("Target len %u * %u + %u = %u", seg_n, seg_len(rx->ctl), + buf->len, rx->len); + + if (rx->len > BT_MESH_RX_SDU_MAX) + { + LOG_ERR("Too large SDU len"); + send_ack(net_rx->sub, net_rx->ctx.recv_dst, net_rx->ctx.addr, + net_rx->ctx.send_ttl, + seq_auth, 0, rx->obo); + seg_rx_reset(rx, true); + return -EMSGSIZE; + } + } + else + { + if (buf->len != seg_len(rx->ctl)) + { + LOG_ERR("Incorrect segment size for message type"); + return -EINVAL; + } + } + + LOG_DBG("discard timeout %u", BT_MESH_SAR_RX_DISCARD_TIMEOUT_MS); + k_work_schedule(&rx->discard, + MSEC2TICK(BT_MESH_SAR_RX_DISCARD_TIMEOUT_MS)); + rx->attempts_left = BT_MESH_SAR_RX_ACK_RETRANS_COUNT; + + if (!bt_mesh_lpn_established() && BT_MESH_ADDR_IS_UNICAST(rx->dst)) + { + LOG_DBG("ack delay %u", ACK_DELAY(rx->seg_n)); + k_work_reschedule(&rx->ack, MSEC2TICK(ACK_DELAY(rx->seg_n))); + } + + /* Allocated segment here */ + + err = k_mem_slab_alloc(&segs, &rx->seg[seg_o], 0); + if (err) + { + LOG_WRN("Unable allocate buffer for Seg %u", seg_o); + return -ENOBUFS; + } + + memcpy(rx->seg[seg_o], buf->data, buf->len); + + LOG_DBG("Received %u/%u", seg_o, seg_n); + + /* Mark segment as received */ + + rx->block |= BIT(seg_o); + + if (rx->block != BLOCK_COMPLETE(seg_n)) + { + *pdu_type = BT_MESH_FRIEND_PDU_PARTIAL; + return 0; + } + + LOG_DBG("Complete SDU"); + *pdu_type = BT_MESH_FRIEND_PDU_COMPLETE; + + /* If this fails, the work handler will either exit early because the + * block is fully received, or rx->in_use is false. + */ + + (void)k_work_cancel_delayable(&rx->ack); + + send_ack(net_rx->sub, net_rx->ctx.recv_dst, net_rx->ctx.addr, + net_rx->ctx.send_ttl, seq_auth, + rx->block, rx->obo); + rx->last_ack = (uint32_t)TICK2MSEC(clock_systime_ticks()); + + if (net_rx->ctl) + { + NET_BUF_SIMPLE_DEFINE(sdu, BT_MESH_RX_CTL_MAX); + seg_rx_assemble(rx, &sdu, 0U); + err = ctl_recv(net_rx, *hdr, &sdu, seq_auth); + } + else if (rx->len < 1 + APP_MIC_LEN(ASZMIC(hdr))) + { + LOG_ERR("Too short SDU + MIC"); + err = -EINVAL; + } + else + { + NET_BUF_SIMPLE_DEFINE_STATIC(seg_buf, BT_MESH_RX_SDU_MAX); + struct net_buf_simple sdu; + + /* Decrypting in place to avoid creating two assembly buffers. + * We'll reassemble the buffer from the segments before each decryption + * attempt. + */ + + net_buf_simple_init(&seg_buf, 0); + net_buf_simple_init_with_data(&sdu, seg_buf.data, + rx->len - APP_MIC_LEN(ASZMIC(hdr))); + + err = sdu_recv(net_rx, *hdr, ASZMIC(hdr), &seg_buf, &sdu, rx); + } + + /* Update rpl only if there is place and upper logic accepted incoming + * data. + */ + + if (err == 0 && rpl != NULL) + { + bt_mesh_rpl_update(rpl, net_rx); + /* Update the seg, unless it has already been surpassed: + * This needs to happen after rpl_update to ensure that the IV update + * reset logic inside rpl_update doesn't overwrite the change. + */ + + rpl->seg = MAX(rpl->seg, auth_seqnum); + } + + seg_rx_reset(rx, false); + + return err; +} + +int bt_mesh_trans_recv(struct net_buf_simple *buf, struct bt_mesh_net_rx *rx) +{ + uint64_t seq_auth = TRANS_SEQ_AUTH_NVAL; + enum bt_mesh_friend_pdu_type pdu_type = BT_MESH_FRIEND_PDU_SINGLE; + struct net_buf_simple_state state; + uint8_t seg_count = 0; + int err; + + if (IS_ENABLED(CONFIG_BT_MESH_FRIEND)) + { + rx->friend_match = bt_mesh_friend_match(rx->sub->net_idx, + rx->ctx.recv_dst); + } + else + { + rx->friend_match = false; + } + + LOG_DBG("src 0x%04x dst 0x%04x seq 0x%08x friend_match %u", rx->ctx.addr, + rx->ctx.recv_dst, + rx->seq, rx->friend_match); + + /* Remove network headers */ + + net_buf_simple_pull(buf, BT_MESH_NET_HDR_LEN); + + LOG_DBG("Payload %s", bt_hex(buf->data, buf->len)); + + if (IS_ENABLED(CONFIG_BT_TESTING)) + { + bt_mesh_test_net_recv(rx->ctx.recv_ttl, rx->ctl, rx->ctx.addr, + rx->ctx.recv_dst, buf->data, + buf->len); + } + + /* If LPN mode is enabled messages are only accepted when we've + * requested the Friend to send them. The messages must also be encrypted + * using the Friend Credentials. + */ + + if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER) && bt_mesh_lpn_established() && + rx->net_if == BT_MESH_NET_IF_ADV && !bt_mesh_lpn_clearing() && + (!bt_mesh_lpn_waiting_update() || !rx->friend_cred)) + { + LOG_WRN("Ignoring unexpected message in Low Power mode"); + return -EAGAIN; + } + + /* Save the app-level state so the buffer can later be placed in + * the Friend Queue. + */ + + net_buf_simple_save(buf, &state); + + if (SEG(buf->data)) + { + /* Segmented messages must match a local element or an + * LPN of this Friend. + */ + + if (!rx->local_match && !rx->friend_match) + { + return 0; + } + + err = trans_seg(buf, rx, &pdu_type, &seq_auth, &seg_count); + } + else + { + seg_count = 1; + + /* Avoid further processing of unsegmented messages that are not a + * local match nor a Friend match, with the exception of ctl messages. + */ + + if (!rx->ctl && !rx->local_match && !rx->friend_match) + { + return 0; + } + + err = trans_unseg(buf, rx, &seq_auth); + } + + /* Notify LPN state machine so a Friend Poll will be sent. */ + + if (IS_ENABLED(CONFIG_BT_MESH_LOW_POWER)) + { + bt_mesh_lpn_msg_received(rx); + } + + net_buf_simple_restore(buf, &state); + + if (IS_ENABLED(CONFIG_BT_MESH_FRIEND) && rx->friend_match && !err) + { + if (seq_auth == TRANS_SEQ_AUTH_NVAL) + { + bt_mesh_friend_enqueue_rx(rx, pdu_type, NULL, seg_count, buf); + } + else + { + bt_mesh_friend_enqueue_rx(rx, pdu_type, &seq_auth, seg_count, buf); + } + } + + return err; +} + +void bt_mesh_rx_reset(void) +{ + int i; + + LOG_DBG(""); + + for (i = 0; i < ARRAY_SIZE(seg_rx); i++) + { + seg_rx_reset(&seg_rx[i], true); + } +} + +void bt_mesh_trans_reset(void) +{ + int i; + + bt_mesh_rx_reset(); + + LOG_DBG(""); + + for (i = 0; i < ARRAY_SIZE(seg_tx); i++) + { + seg_tx_reset(&seg_tx[i]); + } + + bt_mesh_rpl_clear(); + bt_mesh_va_clear(); +} + +void bt_mesh_trans_init(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(seg_tx); i++) + { + k_work_init_delayable(&seg_tx[i].retransmit, seg_retransmit); + } + + for (i = 0; i < ARRAY_SIZE(seg_rx); i++) + { + k_work_init_delayable(&seg_rx[i].ack, seg_ack); + k_work_init_delayable(&seg_rx[i].discard, seg_discard); + } +} diff --git a/libs/zblue/mesh/transport.h b/libs/zblue/mesh/transport.h new file mode 100644 index 0000000000000..81d7b6edcf3d2 --- /dev/null +++ b/libs/zblue/mesh/transport.h @@ -0,0 +1,119 @@ +/**************************************************************************** + * libs/zblue/mesh/transport.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define TRANS_SEQ_AUTH_NVAL 0xffffffffffffffff + +#define BT_MESH_SDU_UNSEG_MAX 11 +#define BT_MESH_CTL_SEG_SDU_MAX 8 + +#define BT_MESH_RX_CTL_MAX \ + MAX((BT_MESH_RX_SEG_MAX * BT_MESH_CTL_SEG_SDU_MAX), BT_MESH_SDU_UNSEG_MAX) + +#define TRANS_SEQ_ZERO_MASK ((uint16_t)BIT_MASK(13)) +#define TRANS_CTL_OP_MASK ((uint8_t)BIT_MASK(7)) +#define TRANS_CTL_OP(data) ((data)[0] & TRANS_CTL_OP_MASK) +#define TRANS_CTL_HDR(op, seg) ((op & TRANS_CTL_OP_MASK) | (seg << 7)) + +#define TRANS_CTL_OP_ACK 0x00 +#define TRANS_CTL_OP_FRIEND_POLL 0x01 +#define TRANS_CTL_OP_FRIEND_UPDATE 0x02 +#define TRANS_CTL_OP_FRIEND_REQ 0x03 +#define TRANS_CTL_OP_FRIEND_OFFER 0x04 +#define TRANS_CTL_OP_FRIEND_CLEAR 0x05 +#define TRANS_CTL_OP_FRIEND_CLEAR_CFM 0x06 +#define TRANS_CTL_OP_FRIEND_SUB_ADD 0x07 +#define TRANS_CTL_OP_FRIEND_SUB_REM 0x08 +#define TRANS_CTL_OP_FRIEND_SUB_CFM 0x09 +#define TRANS_CTL_OP_HEARTBEAT 0x0a + +struct bt_mesh_ctl_friend_poll +{ + uint8_t fsn; +} __packed; + +struct bt_mesh_ctl_friend_update +{ + uint8_t flags; + uint32_t iv_index; + uint8_t md; +} __packed; + +struct bt_mesh_ctl_friend_req +{ + uint8_t criteria; + uint8_t recv_delay; + uint8_t poll_to[3]; + uint16_t prev_addr; + uint8_t num_elem; + uint16_t lpn_counter; +} __packed; + +struct bt_mesh_ctl_friend_offer +{ + uint8_t recv_win; + uint8_t queue_size; + uint8_t sub_list_size; + int8_t rssi; + uint16_t frnd_counter; +} __packed; + +struct bt_mesh_ctl_friend_clear +{ + uint16_t lpn_addr; + uint16_t lpn_counter; +} __packed; + +struct bt_mesh_ctl_friend_clear_confirm +{ + uint16_t lpn_addr; + uint16_t lpn_counter; +} __packed; + +#define BT_MESH_FRIEND_SUB_MIN_LEN (1 + 2) +struct bt_mesh_ctl_friend_sub +{ + uint8_t xact; + uint16_t addr_list[5]; +} __packed; + +struct bt_mesh_ctl_friend_sub_confirm +{ + uint8_t xact; +} __packed; + +bool bt_mesh_tx_in_progress(void); + +void bt_mesh_rx_reset(void); + +int bt_mesh_ctl_send( + struct bt_mesh_net_tx *tx, uint8_t ctl_op, void *data, size_t data_len, + const struct bt_mesh_send_cb *cb, void *cb_data); + +/** @brief Send an access payload message. + * + * @param tx Network TX parameters. Only @c ctx, @c src and @c + * friend_cred have to be filled. + * @param msg Access payload to send. + * @param cb Message callback. + * @param cb_data Message callback data. + * + * @return 0 on success, or (negative) error code otherwise. + */ + +int bt_mesh_trans_send(struct bt_mesh_net_tx *tx, struct net_buf_simple *msg, + const struct bt_mesh_send_cb *cb, void *cb_data); + +int bt_mesh_trans_recv(struct net_buf_simple *buf, + struct bt_mesh_net_rx *rx); + +void bt_mesh_trans_init(void); +void bt_mesh_trans_reset(void); diff --git a/libs/zblue/mesh/va.c b/libs/zblue/mesh/va.c new file mode 100644 index 0000000000000..028a9320be6ca --- /dev/null +++ b/libs/zblue/mesh/va.c @@ -0,0 +1,393 @@ +/**************************************************************************** + * libs/zblue/mesh/va.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2017 Intel Corporation + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "common/bt_str.h" + +#include "va.h" +#include "foundation.h" +#include "msg.h" +#include "net.h" +#include "crypto.h" +#include "settings.h" + +#define LOG_LEVEL CONFIG_BT_MESH_TRANS_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_mesh_va); + +static struct bt_mesh_va virtual_addrs[CONFIG_BT_MESH_LABEL_COUNT]; + +/* Virtual Address information for persistent storage. */ + +struct va_val +{ + uint16_t ref; + uint16_t addr; + uint8_t uuid[16]; +} __packed; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void va_store(struct bt_mesh_va *store) +{ + store->changed = 1U; + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_VA_PENDING); + } +} + +uint8_t bt_mesh_va_add(const uint8_t uuid[16], + const struct bt_mesh_va **entry) +{ + struct bt_mesh_va *va = NULL; + int err; + + for (int i = 0; i < ARRAY_SIZE(virtual_addrs); i++) + { + if (!virtual_addrs[i].ref) + { + if (!va) + { + va = &virtual_addrs[i]; + } + + continue; + } + + if (!memcmp(uuid, virtual_addrs[i].uuid, + ARRAY_SIZE(virtual_addrs[i].uuid))) + { + if (entry) + { + *entry = &virtual_addrs[i]; + } + + virtual_addrs[i].ref++; + va_store(&virtual_addrs[i]); + return STATUS_SUCCESS; + } + } + + if (!va) + { + return STATUS_INSUFF_RESOURCES; + } + + memcpy(va->uuid, uuid, ARRAY_SIZE(va->uuid)); + err = bt_mesh_virtual_addr(uuid, &va->addr); + if (err) + { + va->addr = BT_MESH_ADDR_UNASSIGNED; + return STATUS_UNSPECIFIED; + } + + va->ref = 1; + va_store(va); + + if (entry) + { + *entry = va; + } + + return STATUS_SUCCESS; +} + +uint8_t bt_mesh_va_del(const uint8_t *uuid) +{ + struct bt_mesh_va *va; + + if (CONFIG_BT_MESH_LABEL_COUNT == 0) + { + return STATUS_CANNOT_REMOVE; + } + + va = CONTAINER_OF(uuid, struct bt_mesh_va, uuid[0]); + + if (!PART_OF_ARRAY(virtual_addrs, va) || va->ref == 0) + { + return STATUS_CANNOT_REMOVE; + } + + va->ref--; + va_store(va); + + return STATUS_SUCCESS; +} + +const uint8_t *bt_mesh_va_uuid_get(uint16_t addr, + const uint8_t *uuid, uint16_t *retaddr) +{ + int i = 0; + + if (CONFIG_BT_MESH_LABEL_COUNT == 0) + { + return NULL; + } + + if (uuid != NULL) + { + struct bt_mesh_va *va; + + va = CONTAINER_OF(uuid, struct bt_mesh_va, uuid[0]); + i = ARRAY_INDEX(virtual_addrs, va); + } + + for (; i < ARRAY_SIZE(virtual_addrs); i++) + { + if (virtual_addrs[i].ref && + (virtual_addrs[i].addr == addr || addr == BT_MESH_ADDR_UNASSIGNED)) + { + if (!uuid) + { + LOG_DBG("Found Label UUID for 0x%04x: %s", addr, + bt_hex(virtual_addrs[i].uuid, 16)); + + if (retaddr) + { + *retaddr = virtual_addrs[i].addr; + } + + return virtual_addrs[i].uuid; + } + else if (uuid == virtual_addrs[i].uuid) + { + uuid = NULL; + } + } + } + + LOG_WRN("No matching Label UUID for 0x%04x", addr); + + return NULL; +} + +bool bt_mesh_va_collision_check(uint16_t addr) +{ + size_t count = 0; + const uint8_t *uuid = NULL; + + do + { + uuid = bt_mesh_va_uuid_get(addr, uuid, NULL); + } + while (uuid && ++count); + + return count > 1; +} + +const struct bt_mesh_va *bt_mesh_va_find(const uint8_t *uuid) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(virtual_addrs); i++) + { + if (virtual_addrs[i].ref && !memcmp(virtual_addrs[i].uuid, uuid, 16)) + { + return &virtual_addrs[i]; + } + } + + return NULL; +} + +static struct bt_mesh_va *va_get_by_idx(uint16_t index) +{ + if (index >= ARRAY_SIZE(virtual_addrs)) + { + return NULL; + } + + return &virtual_addrs[index]; +} + +const uint8_t *bt_mesh_va_get_uuid_by_idx(uint16_t idx) +{ + struct bt_mesh_va *va; + + va = va_get_by_idx(idx); + return (va && va->ref > 0) ? va->uuid : NULL; +} + +int bt_mesh_va_get_idx_by_uuid(const uint8_t *uuid, uint16_t *uuidx) +{ + struct bt_mesh_va *va; + + if (CONFIG_BT_MESH_LABEL_COUNT == 0) + { + return -ENOENT; + } + + va = CONTAINER_OF(uuid, struct bt_mesh_va, uuid[0]); + + if (!PART_OF_ARRAY(virtual_addrs, va) || va->ref == 0) + { + return -ENOENT; + } + + *uuidx = ARRAY_INDEX(virtual_addrs, va); + return 0; +} + +#if CONFIG_BT_MESH_LABEL_COUNT > 0 +static int va_set(const char *name, + size_t len_rd, settings_read_cb read_cb, void *cb_arg) +{ + struct va_val va; + struct bt_mesh_va *lab; + uint16_t index; + int err; + + if (!IS_ENABLED(CONFIG_BT_SETTINGS)) + { + return 0; + } + + if (!name) + { + LOG_ERR("Insufficient number of arguments"); + return -ENOENT; + } + + index = strtol(name, NULL, 16); + + if (len_rd == 0) + { + LOG_WRN("Mesh Virtual Address length = 0"); + return 0; + } + + err = bt_mesh_settings_set(read_cb, cb_arg, &va, sizeof(va)); + if (err) + { + LOG_ERR("Failed to set \'virtual address\'"); + return err; + } + + if (va.ref == 0) + { + LOG_WRN("Ignore Mesh Virtual Address ref = 0"); + return 0; + } + + lab = va_get_by_idx(index); + if (lab == NULL) + { + LOG_WRN("Out of labels buffers"); + return -ENOBUFS; + } + + memcpy(lab->uuid, va.uuid, 16); + lab->addr = va.addr; + lab->ref = va.ref; + + LOG_DBG("Restored Virtual Address, addr 0x%04x ref 0x%04x", lab->addr, + lab->ref); + + return 0; +} + +BT_MESH_SETTINGS_DEFINE(va, "Va", va_set); + +#define IS_VA_DEL(_label) ((_label)->ref == 0) +void bt_mesh_va_pending_store(void) +{ + struct bt_mesh_va *lab; + struct va_val va; + char path[18]; + uint16_t i; + int err; + + for (i = 0; (lab = va_get_by_idx(i)) != NULL; i++) + { + if (!lab->changed) + { + continue; + } + + lab->changed = 0U; + + snprintf(path, sizeof(path), "bt/mesh/Va/%x", i); + + if (IS_VA_DEL(lab)) + { + err = settings_delete(path); + } + else + { + va.ref = lab->ref; + va.addr = lab->addr; + memcpy(va.uuid, lab->uuid, 16); + + err = settings_save_one(path, &va, sizeof(va)); + } + + if (err) + { + LOG_ERR("Failed to %s %s value (err %d)", + IS_VA_DEL(lab) ? "delete" : "store", path, err); + } + else + { + LOG_DBG("%s %s value", IS_VA_DEL(lab) ? "Deleted" : "Stored", + path); + } + } +} +#else +void bt_mesh_va_pending_store(void) +{ + /* Do nothing. */ +} +#endif /* CONFIG_BT_MESH_LABEL_COUNT > 0 */ + +void bt_mesh_va_clear(void) +{ + int i; + + if (CONFIG_BT_MESH_LABEL_COUNT == 0) + { + return; + } + + for (i = 0; i < ARRAY_SIZE(virtual_addrs); i++) + { + if (virtual_addrs[i].ref) + { + virtual_addrs[i].ref = 0U; + virtual_addrs[i].changed = 1U; + } + } + + if (IS_ENABLED(CONFIG_BT_SETTINGS)) + { + bt_mesh_settings_store_schedule(BT_MESH_SETTINGS_VA_PENDING); + } +} diff --git a/libs/zblue/mesh/va.h b/libs/zblue/mesh/va.h new file mode 100644 index 0000000000000..538f513e5c0f7 --- /dev/null +++ b/libs/zblue/mesh/va.h @@ -0,0 +1,108 @@ +/**************************************************************************** + * libs/zblue/mesh/va.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/** Virtual address entry. */ + +struct bt_mesh_va +{ + uint16_t ref : 15; + uint16_t changed : 1; + uint16_t addr; + uint8_t uuid[16]; +}; + +/** @brief Store Label UUID. + * + * @param uuid Label UUID to be stored. + * @param entry Pointer to a memory where a new or updated entry will be + * stored, or NULL. + * + * @return STATUS_SUCCESS if entry is stored or updated, error code + * otherwise. + */ + +uint8_t bt_mesh_va_add(const uint8_t uuid[16], + const struct bt_mesh_va **entry); + +/** @brief Delete Label UUID. + * + * @c uuid must be a pointer to @ref bt_mesh_va.uuid. Use @ref + * bt_mesh_va_uuid_get to get valid pointer. + * + * @param uuid Label UUID to delete. + * + * @return STATUS_SUCCESS if entry is deleted, error code otherwise. + */ + +uint8_t bt_mesh_va_del(const uint8_t *uuid); + +/** @brief Find virtual address entry by Label UUID. + * + * @c uuid can be a user data. + * + * @param uuid pointer to memory with Label UUID to be found. + * + * @return Pointer to a valid entry, NULL otherwise. + */ + +const struct bt_mesh_va *bt_mesh_va_find(const uint8_t *uuid); + +/** @brief Check if there are more than one Label UUID which hash has the + * specified virtual address. + * + * @param addr Virtual address to check + * + * @return True if there is collision, false otherwise. + */ + +bool bt_mesh_va_collision_check(uint16_t addr); + +/* Needed for storing va as indexes in persistent storage. */ + +/** @brief Get Label UUID by index. + * + * @param idx Index of virtual address entry. + * + * @return Pointer to a valid Label UUID, or NULL if entry is not found. + */ + +const uint8_t *bt_mesh_va_get_uuid_by_idx(uint16_t idx); + +/** @brief Get virtual address entry index by Label UUID. + * + * @c uuid must be a pointer to @ref bt_mesh_va.uuid. Use @ref + * bt_mesh_va_uuid_get to get valid pointer. + * + * @param uuid Label UUID which index to find + * @param uuidx Pointer to a memory where to store index. + * + * @return 0 if entry is found, error code otherwise. + */ + +int bt_mesh_va_get_idx_by_uuid(const uint8_t *uuid, uint16_t *uuidx); + +/** @brief Store pending virtual address entries in the persistent storage. */ + +void bt_mesh_va_pending_store(void); + +/** @brief Remove all stored virtual addresses and remove them from the + * persistent storage. + */ + +void bt_mesh_va_clear(void); diff --git a/libs/zblue/services/CMakeLists.txt b/libs/zblue/services/CMakeLists.txt new file mode 100644 index 0000000000000..9646f7c127830 --- /dev/null +++ b/libs/zblue/services/CMakeLists.txt @@ -0,0 +1,105 @@ +# ############################################################################## +# libs/zblue/services/CMakeLists.txt +# +# Author: chao an +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +if(CONFIG_BT) + set(BT_SERVICE_SRCS) + + if(CONFIG_BT_ANS) + list(APPEND BT_SERVICE_SRCS ${CMAKE_CURRENT_LIST_DIR}/ans.c) + endif() + if(CONFIG_BT_DIS) + list(APPEND BT_SERVICE_SRCS ${CMAKE_CURRENT_LIST_DIR}/dis.c) + endif() + if(CONFIG_BT_GAP_SVC) + list(APPEND BT_SERVICE_SRCS ${CMAKE_CURRENT_LIST_DIR}/gap_svc.c) + endif() + if(CONFIG_BT_CTS) + list(APPEND BT_SERVICE_SRCS ${CMAKE_CURRENT_LIST_DIR}/cts.c) + endif() + if(CONFIG_BT_ETS) + list(APPEND BT_SERVICE_SRCS ${CMAKE_CURRENT_LIST_DIR}/ets.c) + endif() + if(CONFIG_BT_HRS) + list(APPEND BT_SERVICE_SRCS ${CMAKE_CURRENT_LIST_DIR}/hrs.c) + endif() + if(CONFIG_BT_TPS) + list(APPEND BT_SERVICE_SRCS ${CMAKE_CURRENT_LIST_DIR}/tps.c) + endif() + + if(CONFIG_BT_BAS) + list(APPEND BT_SERVICE_SRCS ${CMAKE_CURRENT_LIST_DIR}/bas/bas.c) + endif() + if(CONFIG_BT_BAS_BLS) + list(APPEND BT_SERVICE_SRCS ${CMAKE_CURRENT_LIST_DIR}/bas/bas_bls.c) + endif() + if(CONFIG_BT_BAS_BCS) + list(APPEND BT_SERVICE_SRCS ${CMAKE_CURRENT_LIST_DIR}/bas/bas_bcs.c) + endif() + + if(CONFIG_BT_IAS) + list(APPEND BT_SERVICE_SRCS ${CMAKE_CURRENT_LIST_DIR}/ias/ias.c) + endif() + if(CONFIG_BT_IAS_CLIENT) + list(APPEND BT_SERVICE_SRCS ${CMAKE_CURRENT_LIST_DIR}/ias/ias_client.c) + endif() + if(CONFIG_BT_ZEPHYR_NUS) + list(APPEND BT_SERVICE_SRCS ${CMAKE_CURRENT_LIST_DIR}/nus/nus.c + ${CMAKE_CURRENT_LIST_DIR}/nus/nus_inst.c) + if(CONFIG_BT_ZEPHYR_NUS_AUTO_START_BLUETOOTH) + list(APPEND BT_SERVICE_SRCS + ${CMAKE_CURRENT_LIST_DIR}/nus/bt_nus_auto_start_bt.c) + endif() + endif() + + if(CONFIG_BT_OTS) + list( + APPEND + BT_SERVICE_SRCS + ${CMAKE_CURRENT_LIST_DIR}/ots/ots.c + ${CMAKE_CURRENT_LIST_DIR}/ots/ots_l2cap.c + ${CMAKE_CURRENT_LIST_DIR}/ots/ots_obj_manager.c + ${CMAKE_CURRENT_LIST_DIR}/ots/ots_oacp.c + ${CMAKE_CURRENT_LIST_DIR}/ots/ots_olcp.c) + if(CONFIG_BT_OTS_DIR_LIST_OBJ) + list(APPEND BT_SERVICE_SRCS ${CMAKE_CURRENT_LIST_DIR}/ots/ots_dir_list.c) + endif() + endif() + + if(CONFIG_BT_OTS_CLIENT) + list(APPEND BT_SERVICE_SRCS ${CMAKE_CURRENT_LIST_DIR}/ots/ots_client.c) + if(NOT CONFIG_BT_OTS) + list(APPEND BT_SERVICE_SRCS ${CMAKE_CURRENT_LIST_DIR}/ots/ots_l2cap.c) + endif() + endif() + + if(BT_SERVICE_SRCS) + target_sources(zblue PRIVATE ${BT_SERVICE_SRCS}) + endif() + + target_include_directories( + zblue + PRIVATE ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/bas + ${CMAKE_CURRENT_LIST_DIR}/ias ${CMAKE_CURRENT_LIST_DIR}/nus + ${CMAKE_CURRENT_LIST_DIR}/ots) +endif() diff --git a/libs/zblue/services/Kconfig b/libs/zblue/services/Kconfig new file mode 100644 index 0000000000000..0567e900fd80e --- /dev/null +++ b/libs/zblue/services/Kconfig @@ -0,0 +1,39 @@ +############################################################################ +# libs/zblue/services/Kconfig +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# GATT Services + +# Copyright (c) 2018 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +menu "GATT Services" + depends on BT_CONN + +rsource "Kconfig.ans" + +rsource "Kconfig.dis" + +rsource "Kconfig.gap_svc" + +rsource "Kconfig.cts" + +rsource "Kconfig.ets" + +rsource "Kconfig.hrs" + +rsource "Kconfig.tps" + +rsource "nus/Kconfig.nus" + +rsource "ias/Kconfig.ias" + +rsource "ots/Kconfig" + +rsource "bas/Kconfig.bas" + +endmenu diff --git a/libs/zblue/services/Kconfig.ans b/libs/zblue/services/Kconfig.ans new file mode 100644 index 0000000000000..a0d1239a57686 --- /dev/null +++ b/libs/zblue/services/Kconfig.ans @@ -0,0 +1,92 @@ +############################################################################ +# libs/zblue/services/Kconfig.ans +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth GATT Alert Notification Service + +# Copyright (c) 2025 Sean Kyer +# +# SPDX-License-Identifier: Apache-2.0 + +menuconfig BT_ANS + bool "GATT Alert Notification Service [EXPERIMENTAL]" + depends on UTF8 + +if BT_ANS + +module = BT_ANS +module-str = Alert Notification Service (ANS) + +menu "New Alert Categories" + +config BT_ANS_NALRT_CAT_SIMPLE_ALERT + bool "Support Simple Alert" + +config BT_ANS_NALRT_CAT_EMAIL + bool "Support Email" + +config BT_ANS_NALRT_CAT_NEWS + bool "Support News" + +config BT_ANS_NALRT_CAT_CALL + bool "Support Call" + +config BT_ANS_NALRT_CAT_MISSED_CALL + bool "Support Missed Call" + +config BT_ANS_NALRT_CAT_SMS_MMS + bool "Support SMS/MMS" + +config BT_ANS_NALRT_CAT_VOICE_MAIL + bool "Support Voice Mail" + +config BT_ANS_NALRT_CAT_SCHEDULE + bool "Support Schedule" + +config BT_ANS_NALRT_CAT_HIGH_PRI_ALERT + bool "Support High Priority Alert" + +config BT_ANS_NALRT_CAT_INSTANT_MESSAGE + bool "Support Instant Message" + +endmenu + +menu "Unread Alert Categories" + +config BT_ANS_UNALRT_CAT_SIMPLE_ALERT + bool "Support Simple Alert" + +config BT_ANS_UNALRT_CAT_EMAIL + bool "Support Email" + +config BT_ANS_UNALRT_CAT_NEWS + bool "Support News" + +config BT_ANS_UNALRT_CAT_CALL + bool "Support Call" + +config BT_ANS_UNALRT_CAT_MISSED_CALL + bool "Support Missed Call" + +config BT_ANS_UNALRT_CAT_SMS_MMS + bool "Support SMS/MMS" + +config BT_ANS_UNALRT_CAT_VOICE_MAIL + bool "Support Voice Mail" + +config BT_ANS_UNALRT_CAT_SCHEDULE + bool "Support Schedule" + +config BT_ANS_UNALRT_CAT_HIGH_PRI_ALERT + bool "Support High Priority Alert" + +config BT_ANS_UNALRT_CAT_INSTANT_MESSAGE + bool "Support Instant Message" + +endmenu + +endif # BT_ANS diff --git a/libs/zblue/services/Kconfig.cts b/libs/zblue/services/Kconfig.cts new file mode 100644 index 0000000000000..290bc5b0346b3 --- /dev/null +++ b/libs/zblue/services/Kconfig.cts @@ -0,0 +1,22 @@ +############################################################################ +# libs/zblue/services/Kconfig.cts +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth GATT Battery service + +# Copyright (c) 2024 Croxel Inc. +# SPDX-License-Identifier: Apache-2.0 + +config BT_CTS + bool "GATT Current Time service" + +if BT_CTS + +config BT_CTS_HELPER_API + bool "Helper APIs to encode and decode CTS formatted time" + +endif diff --git a/libs/zblue/services/Kconfig.dis b/libs/zblue/services/Kconfig.dis new file mode 100644 index 0000000000000..bc616f8d30bc0 --- /dev/null +++ b/libs/zblue/services/Kconfig.dis @@ -0,0 +1,240 @@ +############################################################################ +# libs/zblue/services/Kconfig.dis +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# GATT Device Information service + +# Copyright (c) 2018 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +menuconfig BT_DIS + bool "GATT Device Information service" + +if BT_DIS + +config BT_DIS_SETTINGS + bool "Settings usage in Device Information Service" + select SETTINGS + help + Enable Settings usage in Device Information Service. + +config BT_DIS_STR_MAX + int "Maximum size in bytes for DIS strings" + depends on BT_DIS_SETTINGS + default 21 + range 2 248 + help + Bluetooth DIS string storage size. Storage can be up to 248 bytes + long (excluding NULL termination). + +config BT_DIS_MODEL_NUMBER + bool "Model number characteristic" + default y + help + Enable model number characteristic in Device Information Service. + +config BT_DIS_MODEL_NUMBER_STR + string "Model number" + depends on BT_DIS_MODEL_NUMBER + default SOC + help + Configure model number string that can be read with the model number characteristic + in Device Information Service. + +config BT_DIS_MANUF_NAME + bool "Manufacturer name characteristic" + default y + help + Enable manufacturer name characteristic in Device Information Service. + +config BT_DIS_MANUF_NAME_STR + string "Manufacturer name" + depends on BT_DIS_MANUF_NAME + default "Manufacturer" + help + Configure manufacturer name string that can be read with the manufacturer name + characteristic in Device Information Service. + +config BT_DIS_PNP + bool "PnP_ID characteristic" + default y + help + Enable PnP_ID characteristic in Device Information Service. + +if BT_DIS_PNP + +config BT_DIS_PNP_VID_SRC + int "Vendor ID source" + range 1 2 + default 1 + help + The Vendor ID Source field designates which organization assigned the + value used in the Vendor ID field value. + The possible values are: + - 1 Bluetooth SIG, the Vendor ID was assigned by the Bluetooth SIG + - 2 USB IF, the Vendor ID was assigned by the USB IF + +config BT_DIS_PNP_VID + hex "Vendor ID" + range 0 0xFFFF + default 0 + help + The Vendor ID field is intended to uniquely identify the vendor of the + device. This field is used in conjunction with Vendor ID Source field, + which determines which organization assigned the Vendor ID field value. + Note: The Bluetooth Special Interest Group assigns Device ID Vendor ID, + and the USB Implementers Forum assigns Vendor IDs, + either of which can be used for the Vendor ID field value. + Device providers should procure the Vendor ID from the USB Implementers + Forum or the Company Identifier from the Bluetooth SIG. + +config BT_DIS_PNP_PID + hex "Product ID" + range 0 0xFFFF + default 0 + help + The Product ID field is intended to distinguish between different products + made by the vendor identified with the Vendor ID field. The vendors + themselves manage Product ID field values. + +config BT_DIS_PNP_VER + hex "Product Version" + range 0 0xFFFF + default 1 + help + The Product Version field is a numeric expression identifying the device + release number in Binary-Coded Decimal. This is a vendor-assigned value, + which defines the version of the product identified by the Vendor ID and + Product ID fields. This field is intended to differentiate between + versions of products with identical Vendor IDs and Product IDs. + The value of the field value is 0xJJMN for version JJ.M.N + (JJ - major version number, M - minor version number, + N - sub-minor version number); e.g., version 2.1.3 is represented with + value 0x0213 and version 2.0.0 is represented with a value of 0x0200. + When upward-compatible changes are made to the device, it is recommended + that the minor version number be incremented. If incompatible changes are + made to the device, it is recommended that the major version number be + incremented. The sub-minor version is incremented for bug fixes. + +endif # BT_DIS_PNP + +config BT_DIS_SERIAL_NUMBER + bool "DIS Serial number characteristic" + help + Enable Serial Number characteristic in Device Information Service. + +config BT_DIS_SERIAL_NUMBER_STR + string "Serial Number" + depends on BT_DIS_SERIAL_NUMBER + help + Enable Serial Number characteristic in Device Information Service. + +config BT_DIS_FW_REV + bool "DIS Firmware Revision characteristic" + help + Enable Firmware Revision characteristic in Device Information Service. + +config BT_DIS_FW_REV_STR + string "Firmware revision" + depends on BT_DIS_FW_REV + default "" + default "0.0.0+0" + help + Enable firmware revision characteristic in Device Information Service. + +config BT_DIS_HW_REV + bool "DIS Hardware Revision characteristic" + help + Enable Hardware Revision characteristic in Device Information Service. + +config BT_DIS_HW_REV_STR + string "Hardware revision" + depends on BT_DIS_HW_REV + help + Enable hardware revision characteristic in Device Information Service. + +config BT_DIS_SW_REV + bool "DIS Software Revision characteristic" + help + Enable Software Revision characteristic in Device Information Service. + +config BT_DIS_SW_REV_STR + string "Software revision" + depends on BT_DIS_SW_REV + help + Enable software revision characteristic in Device Information Service. + +config BT_DIS_UDI + bool "DIS UDI for Medical Devices characteristic" + help + Enable UDI for Medical Devices characteristic in Device Information Service. + +if BT_DIS_UDI +config BT_DIS_UDI_LABEL_STR + string "UDI Label" + help + String value matching the UDI in human readable form + as assigned to the product by a recognized UDI Issuer. + +config BT_DIS_UDI_DI_STR + string "UDI Device Identifier" + help + A fixed portion of a UDI that identifies the labeler and the + specific version or model of a device. + +config BT_DIS_UDI_ISSUER_STR + string "UDI Issuer" + help + OID representing the UDI Issuing Organization, such as GS1. + +config BT_DIS_UDI_AUTHORITY_STR + string "UDI Authority" + help + OID representing the regional UDI Authority, such as the US FDA. +endif # BT_DIS_UDI + +config BT_DIS_SYSTEM_ID + bool "DIS System ID characteristic [EXPERIMENTAL]" + help + Enable System ID characteristic in Device Information Service. + The System ID characteristic is used to represent an extended unique identifier (EUI) of the system + implementing the service that contains this characteristic. This 64-bit structure is an EUI-64 which consists + of an Organizationally Unique Identifier (OUI) concatenated with a manufacturer-defined identifier. + + This will be transmitted as the 40 bit identifier followed by the 24-bit OUI. + Both in little-endian format. + +if BT_DIS_SYSTEM_ID +config BT_DIS_SYSTEM_ID_OUI + hex "Organizationally Unique Identifier (OUI) of the manufacturer." + range 0 0xFFFFFF + default 0 + help + The OUI is a 24-bit number issued by the IEEE Registration Authority. + System ID characteristic in Device Information Service. + Shall contain an Organisationally Unique Identifier (OUI) followed by a manufacturer-defined identifier unique for the device. + +config BT_DIS_SYSTEM_ID_IDENTIFIER + hex "Manufacturer-defined unique identifier." + range 0 0xFFFFFFFFFF + default 0 + help + The manufacturer-defined unique identifier is 40 bits long. +endif # BT_DIS_SYSTEM_ID + +config BT_DIS_IEEE_RCDL + bool "DIS IEEE 11073-20601 Regulatory Certification Data List characteristic" + help + Enable IEEE 11073-20601 Regulatory Certification Data List characteristic in Device Information Service. + +config BT_DIS_IEEE_RCDL_STR + string "IEEE 11073-20601 Regulatory Certification Data List" + depends on BT_DIS_IEEE_RCDL + help + IEEE 11073-20601 Regulatory Certification Data List characteristic in Device Information Service string contents. + +endif # BT_DIS diff --git a/libs/zblue/services/Kconfig.ets b/libs/zblue/services/Kconfig.ets new file mode 100644 index 0000000000000..4a94f388f71a9 --- /dev/null +++ b/libs/zblue/services/Kconfig.ets @@ -0,0 +1,207 @@ +############################################################################ +# libs/zblue/services/Kconfig.ets +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth GATT Elapsed Time Service + +# Copyright (c) 2025 Dipak Shetty +# SPDX-License-Identifier: Apache-2.0 + +menuconfig BT_ETS + bool "GATT Elapsed Time service [EXPERIMENTAL]" + help + Enable Bluetooth Elapsed Time Service (ETS). + This service provides a time service for embedded devices that either: + - Keep track of absolute time (time of day, UTC or local) + - Maintain a tick counter for relative time measurements + +if BT_ETS + +# +# Time Mode Selection +# +choice BT_ETS_TIME_MODE + prompt "Time mode" + default BT_ETS_SUPPORT_TIME_OF_DAY + help + Select the time representation mode (bit 0 of flags field): + + Time of Day: Reports absolute time since 2000-01-01 00:00:00. + Use this for devices with real-time clocks or time synchronization. + + Tick Counter: Reports relative time (elapsed ticks/counter). + Use this for simple event timing without absolute time reference. + The counter starting point is implementation-dependent. + +config BT_ETS_SUPPORT_TIME_OF_DAY + bool "Time of day (absolute time)" + help + Report absolute time since ETS epoch (2000-01-01 00:00:00). + Time Value represents the number of time units (based on resolution) + since the epoch. Supports UTC or local time with timezone offset. + +config BT_ETS_SUPPORT_TICK_COUNTER + bool "Tick counter (relative time)" + help + Report relative time using a counter/tick value. + Time Value represents elapsed time units (based on resolution). + The counter starting point is implementation-dependent. + Timezone and UTC/local settings are not applicable in this mode. + +endchoice + +# +# Time of Day Configuration +# +if BT_ETS_SUPPORT_TIME_OF_DAY + +choice BT_ETS_TIME_TYPE + prompt "Time reference" + default BT_ETS_SUPPORT_LOCAL_TIME + help + Select the time reference type (bit 1 of flags field): + + Local Time (bit 1 = 0): Time Value is in local time (already adjusted). + No timezone offset needed as time is already local. + + UTC (bit 1 = 1): Time Value is in UTC (Coordinated Universal Time). + Optionally include timezone offset so clients can calculate local time. + +config BT_ETS_SUPPORT_LOCAL_TIME + bool "Local time" + help + Time Value represents local time (already adjusted for timezone/DST). + Bit 1 = 0 (local time) + + You can optionally enable TZ/DST offset below to indicate which + timezone the local time represents. This allows recipients to + convert the local time back to UTC using the formula: + UTC time = Local time - TZ/DST Offset Ɨ 15 minutes + +config BT_ETS_SUPPORT_UTC + bool "UTC (Coordinated Universal Time)" + help + Time Value represents UTC time. + Bit 1 = 1 (UTC) + + You can optionally enable TZ/DST offset below to allow clients + to calculate local time using the formula: + Local time = UTC time + TZ/DST Offset Ɨ 15 minutes + +endchoice + +config BT_ETS_SUPPORT_TZ_DST + bool "Include timezone/DST offset" + depends on BT_ETS_SUPPORT_TIME_OF_DAY + help + Include the TZ/DST offset field in the characteristic (bit 4 = 1). + The offset is specified in 15-minute increments from UTC. + + The offset can be used in both UTC and local time modes: + - UTC mode: Offset is informational for clients to calculate local time + Formula: Local time = UTC time + TZ/DST Offset Ɨ 15 minutes + - Local mode: Offset indicates which timezone the local time represents + Formula: UTC time = Local time - TZ/DST Offset Ɨ 15 minutes + +config BT_ETS_HELPER_API + bool "Helper API for Unix time conversion" + depends on BT_ETS_SUPPORT_UTC || BT_ETS_SUPPORT_TZ_DST + help + Enable helper functions to convert between ETS time format and + Unix milliseconds (epoch 1970-01-01 00:00:00 UTC): + - bt_ets_time_to_unix_ms(): ETS format -> Unix milliseconds (UTC) + - bt_ets_time_from_unix_ms(): Unix milliseconds (UTC) -> ETS format + + These functions handle epoch conversion (2000 vs 1970), + resolution scaling, and timezone conversion. + + Requirements: + - UTC mode: Direct conversion, no timezone offset needed + - Local mode: Requires TZ/DST offset to convert between UTC and local time + + This option requires either UTC mode or TZ/DST offset support. + +endif # BT_ETS_SUPPORT_TIME_OF_DAY + +# +# Time Resolution (applies to both modes) +# +choice BT_ETS_TIME_RESOLUTION + prompt "Time resolution" + default BT_ETS_RESOLUTION_1_SEC + help + Time resolution for the Time Value field (bits 2-3 of flags). + Determines how frequently the time value increments. + Higher resolution provides finer granularity but reduces maximum time range. + +config BT_ETS_RESOLUTION_1_SEC + bool "1 second" + help + Time value increments every 1 second. + Maximum range: ~8925512 years. + +config BT_ETS_RESOLUTION_100_MS + bool "100 milliseconds" + help + Time value increments every 100 milliseconds (0.1 second). + Maximum range: ~892551 years. + +config BT_ETS_RESOLUTION_1_MS + bool "1 millisecond" + help + Time value increments every 1 millisecond (0.001 second). + Maximum range: ~8,920 years. + +config BT_ETS_RESOLUTION_100_US + bool "100 microseconds" + help + Time value increments every 100 microseconds (0.0001 second). + Maximum range: ~892 years. + +endchoice + +# +# Optional Features +# +config BT_ETS_CURRENT_ELAPSED_TIME_WRITABLE + bool "Allow clients to write time" + help + Enable write permission on the Current Elapsed Time characteristic. + When enabled: + - Clients can set/adjust the time by writing to the characteristic + - Application must implement write_elapsed_time callback + - Written value is validated against supported flags + - Indication is sent to other clients after successful write + - "Current timeline" bit is set to indicate new time reference + + Use this if clients need to synchronize the device's time. + +# +# Clock Capabilities (static, compile-time) +# +config BT_ETS_CLOCK_CAP_DST_AUTO + bool "Clock autonomously applies DST rules" + depends on BT_ETS_SUPPORT_TIME_OF_DAY + help + Enable if the clock autonomously applies Daylight Saving Time rules. + When enabled, bit 0 of Clock Capabilities will be set. + This indicates the device automatically adjusts for DST transitions + without external intervention. + +config BT_ETS_CLOCK_CAP_TZ_AUTO + bool "Clock autonomously manages timezone changes" + depends on BT_ETS_SUPPORT_TIME_OF_DAY + help + Enable if the clock autonomously manages timezone changes. + When enabled, bit 1 of Clock Capabilities will be set. + This indicates the device automatically adjusts for timezone changes + (e.g., when traveling) without external intervention. + +module = BT_ETS +module-str = ets + +endif # BT_ETS diff --git a/libs/zblue/services/Kconfig.gap_svc b/libs/zblue/services/Kconfig.gap_svc new file mode 100644 index 0000000000000..d3cf0171abb50 --- /dev/null +++ b/libs/zblue/services/Kconfig.gap_svc @@ -0,0 +1,110 @@ +############################################################################ +# libs/zblue/services/Kconfig.gap_svc +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# GATT Generic Access Service - default implementation +# +# Copyright (c) 2019-2025 Intel Corporation +# Copyright (c) 2025 Koppel Electronic +# +# SPDX-License-Identifier: Apache-2.0 + +menuconfig BT_GAP_SVC + bool "GATT Generic Access Service" + default y + help + Enable the default GATT Generic Access Service. + Enabled by default provides basic GAP functionality. + If disabled, it is the application responsibility to provide its own + implementation of GAP service, otherwise the system is not Bluetooth + qualification compliant. + +if BT_GAP_SVC + +config BT_DEVICE_NAME_GATT_WRITABLE + bool "Allow to write device name by remote GATT clients" + depends on BT_DEVICE_NAME_DYNAMIC + default y + help + Enabling this option allows remote GATT clients to write to device + name GAP characteristic. + +if BT_DEVICE_NAME_GATT_WRITABLE +choice BT_DEVICE_NAME_GATT_WRITABLE_SECURITY + prompt "Security requirements" + default BT_DEVICE_NAME_GATT_WRITABLE_NONE if DEVICE_NAME_GATT_WRITABLE_NONE + default BT_DEVICE_NAME_GATT_WRITABLE_AUTHEN if DEVICE_NAME_GATT_WRITABLE_AUTHEN + default BT_DEVICE_NAME_GATT_WRITABLE_ENCRYPT if DEVICE_NAME_GATT_WRITABLE_ENCRYPT + default BT_DEVICE_NAME_GATT_WRITABLE_ENCRYPT + help + Select security requirements for writing device name by remote GATT + clients. + +config BT_DEVICE_NAME_GATT_WRITABLE_NONE + bool "No requirements" + +config BT_DEVICE_NAME_GATT_WRITABLE_ENCRYPT + bool "Encryption required" + +config BT_DEVICE_NAME_GATT_WRITABLE_AUTHEN + bool "Encryption and authentication required" + +endchoice #BT_DEVICE_NAME_GATT_WRITABLE_SECURITY + +config DEVICE_NAME_GATT_WRITABLE_NONE + bool "No requirements [DEPRECATED]" + help + This option is deprecated. Select BT_DEVICE_NAME_GATT_WRITABLE_NONE instead. + +config DEVICE_NAME_GATT_WRITABLE_ENCRYPT + bool "Encryption required [DEPRECATED]" + help + This option is deprecated. Select BT_DEVICE_NAME_GATT_WRITABLE_ENCRYPT instead. + +config DEVICE_NAME_GATT_WRITABLE_AUTHEN + bool "Encryption and authentication required [DEPRECATED]" + help + This option is deprecated. Select BT_DEVICE_NAME_GATT_WRITABLE_AUTHEN instead. + +endif #BT_DEVICE_NAME_GATT_WRITABLE + +config BT_DEVICE_APPEARANCE_GATT_WRITABLE + bool "Allow to write GAP Appearance by remote GATT clients" + depends on BT_DEVICE_APPEARANCE_DYNAMIC + default y + help + Enabling this option allows remote GATT clients to write to device + appearance GAP characteristic. + +if BT_DEVICE_APPEARANCE_GATT_WRITABLE +choice BT_DEVICE_APPEARANCE_GATT_WRITABLE_SECURITY + prompt "Security requirements" + default BT_DEVICE_APPEARANCE_GATT_WRITABLE_AUTHEN if DEVICE_APPEARANCE_GATT_WRITABLE_AUTHEN + default BT_DEVICE_APPEARANCE_GATT_WRITABLE_AUTHEN + help + Select security requirements for writing device appearance by remote + GATT clients. + +config BT_DEVICE_APPEARANCE_GATT_WRITABLE_NONE + bool "No requirements" + +config BT_DEVICE_APPEARANCE_GATT_WRITABLE_ENCRYPT + bool "Encryption required" + +config BT_DEVICE_APPEARANCE_GATT_WRITABLE_AUTHEN + bool "Encryption and authentication required" + +endchoice #BT_DEVICE_APPEARANCE_GATT_WRITABLE_SECURITY + +config DEVICE_APPEARANCE_GATT_WRITABLE_AUTHEN + bool "Encryption and authentication required [DEPRECATED]" + help + This option is deprecated. Select BT_DEVICE_APPEARANCE_GATT_WRITABLE_AUTHEN instead. + +endif #BT_DEVICE_APPEARANCE_GATT_WRITABLE + +endif #BT_GAP_SVC diff --git a/libs/zblue/services/Kconfig.hrs b/libs/zblue/services/Kconfig.hrs new file mode 100644 index 0000000000000..946f840aa6e3e --- /dev/null +++ b/libs/zblue/services/Kconfig.hrs @@ -0,0 +1,38 @@ +############################################################################ +# libs/zblue/services/Kconfig.hrs +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth GATT Heart Rate service + +# Copyright (c) 2018 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +menuconfig BT_HRS + bool "GATT Heart Rate service" + +if BT_HRS + +choice BT_HRS_DEFAULT_PERM + prompt "Default permissions used for HRS characteristics" + default BT_HRS_DEFAULT_PERM_RW + help + Default permissions for HRS characteristic attributes + used when no permission is set for the report. + Used also for boot reports. + +config BT_HRS_DEFAULT_PERM_RW + bool "Read and write allowed" + +config BT_HRS_DEFAULT_PERM_RW_ENCRYPT + bool "Require encryption for access" + +config BT_HRS_DEFAULT_PERM_RW_AUTHEN + bool "Require encryption and authentication for access" + +endchoice + +endif # BT_HRS diff --git a/libs/zblue/services/Kconfig.tps b/libs/zblue/services/Kconfig.tps new file mode 100644 index 0000000000000..5282282a35819 --- /dev/null +++ b/libs/zblue/services/Kconfig.tps @@ -0,0 +1,15 @@ +############################################################################ +# libs/zblue/services/Kconfig.tps +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth GATT TX Power Service + +# Copyright (c) 2020 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config BT_TPS + bool "GATT TX Power service" diff --git a/libs/zblue/services/Make.defs b/libs/zblue/services/Make.defs new file mode 100644 index 0000000000000..8d6acdcc99a55 --- /dev/null +++ b/libs/zblue/services/Make.defs @@ -0,0 +1,106 @@ +############################################################################ +# libs/zblue/services/Make.defs +# +# Author: chao an +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. The +# ASF licenses this file to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance with the +# License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +############################################################################ + +ifeq ($(CONFIG_BT),y) + +ifeq ($(CONFIG_BT_ANS),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/services/ans.c +endif +ifeq ($(CONFIG_BT_DIS),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/services/dis.c +endif +ifeq ($(CONFIG_BT_GAP_SVC),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/services/gap_svc.c +endif +ifeq ($(CONFIG_BT_CTS),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/services/cts.c +endif +ifeq ($(CONFIG_BT_ETS),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/services/ets.c +endif +ifeq ($(CONFIG_BT_HRS),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/services/hrs.c +endif +ifeq ($(CONFIG_BT_TPS),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/services/tps.c +endif + +ifeq ($(CONFIG_BT_BAS),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/services/bas/bas.c +endif +ifeq ($(CONFIG_BT_BAS_BLS),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/services/bas/bas_bls.c +endif +ifeq ($(CONFIG_BT_BAS_BCS),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/services/bas/bas_bcs.c +endif + +ifeq ($(CONFIG_BT_IAS),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/services/ias/ias.c +endif +ifeq ($(CONFIG_BT_IAS_CLIENT),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/services/ias/ias_client.c +endif +ifeq ($(CONFIG_BT_ZEPHYR_NUS),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/services/nus/nus.c +BT_CSRCS += $(TOPDIR)/libs/zblue/services/nus/nus_inst.c +ifeq ($(CONFIG_BT_ZEPHYR_NUS_AUTO_START_BLUETOOTH),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/services/nus/bt_nus_auto_start_bt.c +endif +endif + +ifeq ($(CONFIG_BT_OTS),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/services/ots/ots.c +BT_CSRCS += $(TOPDIR)/libs/zblue/services/ots/ots_l2cap.c +BT_CSRCS += $(TOPDIR)/libs/zblue/services/ots/ots_obj_manager.c +BT_CSRCS += $(TOPDIR)/libs/zblue/services/ots/ots_oacp.c +BT_CSRCS += $(TOPDIR)/libs/zblue/services/ots/ots_olcp.c +ifeq ($(CONFIG_BT_OTS_DIR_LIST_OBJ),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/services/ots/ots_dir_list.c +endif +endif + +ifeq ($(CONFIG_BT_OTS_CLIENT),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/services/ots/ots_client.c +ifneq ($(CONFIG_BT_OTS),y) +BT_CSRCS += $(TOPDIR)/libs/zblue/services/ots/ots_l2cap.c +endif +endif + +VPATH += $(TOPDIR)/libs/zblue/services $(TOPDIR)/libs/zblue/services/bas +VPATH += $(TOPDIR)/libs/zblue/services/ias $(TOPDIR)/libs/zblue/services/nus +VPATH += $(TOPDIR)/libs/zblue/services/ots +DEPPATH += --dep-path $(TOPDIR)/libs/zblue/services +DEPPATH += --dep-path $(TOPDIR)/libs/zblue/services/bas +DEPPATH += --dep-path $(TOPDIR)/libs/zblue/services/ias +DEPPATH += --dep-path $(TOPDIR)/libs/zblue/services/nus +DEPPATH += --dep-path $(TOPDIR)/libs/zblue/services/ots + +CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)/libs/zblue/services +CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)/libs/zblue/services/bas +CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)/libs/zblue/services/ias +CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)/libs/zblue/services/nus +CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)/libs/zblue/services/ots + +endif diff --git a/libs/zblue/services/ans.c b/libs/zblue/services/ans.c new file mode 100644 index 0000000000000..eef45200667d6 --- /dev/null +++ b/libs/zblue/services/ans.c @@ -0,0 +1,629 @@ +/**************************************************************************** + * libs/zblue/services/ans.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2025 Sean Kyer + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Bluetooth Alert Notification Service specification: + * https://www.bluetooth.com/specifications/specs/ + * alert-notification-service-1-0/ + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common/bt_str.h" + +LOG_MODULE_REGISTER(bt_ans, CONFIG_BT_ANS_LOG_LEVEL); + +/* This only enforces a necessary lower bound at build time. It does + * not guarantee that notification/transmit operations will never fail at + * runtime because other subsystems/services can hold outstanding ATT buffers + * concurrently. + */ + +BUILD_ASSERT(CONFIG_BT_MAX_CONN <= CONFIG_BT_ATT_TX_COUNT, + "CONFIG_BT_ATT_TX_COUNT must be >= CONFIG_BT_MAX_CONN"); + +/* Build time ANS supported category bit mask */ +#define BT_ANS_NALRT_CAT_MASK \ + ((IS_ENABLED(CONFIG_BT_ANS_NALRT_CAT_SIMPLE_ALERT) << BT_ANS_CAT_SIMPLE_ALERT) | \ + (IS_ENABLED(CONFIG_BT_ANS_NALRT_CAT_EMAIL) << BT_ANS_CAT_EMAIL) | \ + (IS_ENABLED(CONFIG_BT_ANS_NALRT_CAT_NEWS) << BT_ANS_CAT_NEWS) | \ + (IS_ENABLED(CONFIG_BT_ANS_NALRT_CAT_CALL) << BT_ANS_CAT_CALL) | \ + (IS_ENABLED(CONFIG_BT_ANS_NALRT_CAT_MISSED_CALL) << BT_ANS_CAT_MISSED_CALL) | \ + (IS_ENABLED(CONFIG_BT_ANS_NALRT_CAT_SMS_MMS) << BT_ANS_CAT_SMS_MMS) | \ + (IS_ENABLED(CONFIG_BT_ANS_NALRT_CAT_VOICE_MAIL) << BT_ANS_CAT_VOICE_MAIL) | \ + (IS_ENABLED(CONFIG_BT_ANS_NALRT_CAT_SCHEDULE) << BT_ANS_CAT_SCHEDULE) | \ + (IS_ENABLED(CONFIG_BT_ANS_NALRT_CAT_HIGH_PRI_ALERT) << BT_ANS_CAT_HIGH_PRI_ALERT) | \ + (IS_ENABLED(CONFIG_BT_ANS_NALRT_CAT_INSTANT_MESSAGE) << BT_ANS_CAT_INSTANT_MESSAGE)) + +/* Build time ANS supported category bit mask */ +#define BT_ANS_UNALRT_CAT_MASK \ + ((IS_ENABLED(CONFIG_BT_ANS_UNALRT_CAT_SIMPLE_ALERT) << BT_ANS_CAT_SIMPLE_ALERT) | \ + (IS_ENABLED(CONFIG_BT_ANS_UNALRT_CAT_EMAIL) << BT_ANS_CAT_EMAIL) | \ + (IS_ENABLED(CONFIG_BT_ANS_UNALRT_CAT_NEWS) << BT_ANS_CAT_NEWS) | \ + (IS_ENABLED(CONFIG_BT_ANS_UNALRT_CAT_CALL) << BT_ANS_CAT_CALL) | \ + (IS_ENABLED(CONFIG_BT_ANS_UNALRT_CAT_MISSED_CALL) << BT_ANS_CAT_MISSED_CALL) | \ + (IS_ENABLED(CONFIG_BT_ANS_UNALRT_CAT_SMS_MMS) << BT_ANS_CAT_SMS_MMS) | \ + (IS_ENABLED(CONFIG_BT_ANS_UNALRT_CAT_VOICE_MAIL) << BT_ANS_CAT_VOICE_MAIL) | \ + (IS_ENABLED(CONFIG_BT_ANS_UNALRT_CAT_SCHEDULE) << BT_ANS_CAT_SCHEDULE) | \ + (IS_ENABLED(CONFIG_BT_ANS_UNALRT_CAT_HIGH_PRI_ALERT) << BT_ANS_CAT_HIGH_PRI_ALERT) | \ + (IS_ENABLED(CONFIG_BT_ANS_UNALRT_CAT_INSTANT_MESSAGE) << BT_ANS_CAT_INSTANT_MESSAGE)) + +/* As per spec, ensure at least one New Alert category is supported */ + +BUILD_ASSERT(BT_ANS_NALRT_CAT_MASK != 0, + "At least one ANS New Alert category must be enabled in " + "Kconfig"); + +static uint16_t alert_sup_cat_bit_mask = BT_ANS_NALRT_CAT_MASK; +static uint16_t unread_sup_cat_bit_mask = BT_ANS_UNALRT_CAT_MASK; + +/* Command ID definitions */ +#define BT_ANS_SEND_ALL_CATEGORY 0xFF +enum bt_ans_command_id +{ + BT_ANS_CTRL_ENABLE_NEW_ALERT, + BT_ANS_CTRL_ENABLE_UNREAD, + BT_ANS_CTRL_DISABLE_NEW_ALERT, + BT_ANS_CTRL_DISABLE_UNREAD, + BT_ANS_CTRL_NOTIFY_NEW_ALERT_IMMEDIATE, + BT_ANS_CTRL_NOTIFY_UNREAD_IMMEDIATE, +}; + +/* Struct definition for Alert Notification Control Point */ + +struct alert_ctrl_p +{ + uint8_t cmd_id; + uint8_t category; +} __packed; + +/* Struct definition for New Alert */ + +struct new_alert +{ + uint8_t category_id; + uint8_t num_new_alerts; + char text_string[BT_ANS_MAX_TEXT_STR_SIZE + 1]; +} __packed; + +/* Struct definition for Unread Alert */ + +struct unread_alert_status +{ + uint8_t category_id; + uint8_t unread_count; +} __packed; + +/* Mutex for modifying database */ + +rmutex_t new_alert_mutex = NXRMUTEX_INITIALIZER; +rmutex_t unread_mutex = NXRMUTEX_INITIALIZER; + +/* Saved messages database */ + +static struct new_alert new_alerts[BT_ANS_CAT_NUM]; +static struct unread_alert_status unread_alerts[BT_ANS_CAT_NUM]; + +/* Initialize to 0, it is control point responsibility to enable once + * connected + */ + +static uint16_t alert_cat_enabled_map; +static uint16_t unread_cat_enabled_map; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/* Supported New Alert Category */ + +static ssize_t read_supp_new_alert_cat( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + LOG_DBG("Supported New Alert Category Read"); + + /* Return the bit mask of the supported categories */ + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + &alert_sup_cat_bit_mask, + sizeof(alert_sup_cat_bit_mask)); +} + +/* New Alert Notifications */ + +static void new_alert_ccc_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("New Alert Notifications %s", + value == BT_GATT_CCC_NOTIFY ? "enabled" : "disabled"); +} + +/* Supported Unread Alert Category */ + +static ssize_t read_supp_unread_alert_cat( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + LOG_DBG("Supported Unread Alert Category Read"); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + &unread_sup_cat_bit_mask, + sizeof(unread_sup_cat_bit_mask)); +} + +/* Unread Alert Status Notifications */ + +static void unread_alert_status_ccc_cfg_changed( + const struct bt_gatt_attr *attr, uint16_t value) +{ + ARG_UNUSED(attr); + + LOG_DBG("Unread Alert Status Notifications %s", + value == BT_GATT_CCC_NOTIFY ? "enabled" : "disabled"); +} + +/* Alert Notification Control Point */ + +static int transmit_new_alert(struct bt_conn *conn, + enum bt_ans_cat category); +static int transmit_unread_alert(struct bt_conn *conn, + enum bt_ans_cat category); +static ssize_t write_alert_notif_ctrl_point( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + int rc; + + LOG_DBG("Alert Control Point Written %u", len); + + if (len != sizeof(struct alert_ctrl_p)) + { + LOG_DBG("Length of control packet is %u when expected %zu", len, + sizeof(struct alert_ctrl_p)); + return BT_GATT_ERR(BT_ANS_ERR_CMD_NOT_SUP); + } + + struct alert_ctrl_p command = *((const struct alert_ctrl_p *)buf); + + LOG_DBG("Command ID 0x%x", command.cmd_id); + LOG_DBG("Category 0x%x", command.category); + + if (command.category >= BT_ANS_CAT_NUM && + command.category != BT_ANS_SEND_ALL_CATEGORY) + { + LOG_DBG("Received control point request for category out of bounds: " + "%d", + command.category); + return BT_GATT_ERR(BT_ANS_ERR_CMD_NOT_SUP); + } + + /* If category is BT_ANS_SEND_ALL_CATEGORY then only + * BT_ANS_CTRL_NOTIFY_NEW_ALERT_IMMEDIATE or + * BT_ANS_CTRL_NOTIFY_UNREAD_IMMEDIATE are valid command IDs + */ + + if (command.category == BT_ANS_SEND_ALL_CATEGORY) + { + switch (command.cmd_id) + { + case BT_ANS_CTRL_NOTIFY_NEW_ALERT_IMMEDIATE: + rc = transmit_new_alert(conn, command.category); + return rc ? BT_GATT_ERR(BT_ANS_ERR_CMD_NOT_SUP) : len; + case BT_ANS_CTRL_NOTIFY_UNREAD_IMMEDIATE: + rc = transmit_unread_alert(conn, command.category); + return rc ? BT_GATT_ERR(BT_ANS_ERR_CMD_NOT_SUP) : len; + default: + return BT_GATT_ERR(BT_ANS_ERR_CMD_NOT_SUP); + } + } + + switch (command.cmd_id) + { + case BT_ANS_CTRL_ENABLE_NEW_ALERT: + if ((alert_sup_cat_bit_mask & (1U << command.category)) == 0) + { + LOG_DBG( + "Received control point request for unsupported category: " + "%d", command.category); + return BT_GATT_ERR(BT_ANS_ERR_CMD_NOT_SUP); + } + + alert_cat_enabled_map |= (1U << command.category); + break; + case BT_ANS_CTRL_ENABLE_UNREAD: + if ((unread_sup_cat_bit_mask & (1U << command.category)) == 0) + { + LOG_DBG( + "Received control point request for unsupported category: " + "%d", command.category); + return BT_GATT_ERR(BT_ANS_ERR_CMD_NOT_SUP); + } + + unread_cat_enabled_map |= (1U << command.category); + break; + case BT_ANS_CTRL_DISABLE_NEW_ALERT: + if ((alert_sup_cat_bit_mask & (1U << command.category)) == 0) + { + LOG_DBG( + "Received control point request for unsupported category: " + "%d", command.category); + + return BT_GATT_ERR(BT_ANS_ERR_CMD_NOT_SUP); + } + + alert_cat_enabled_map &= ~(1U << command.category); + break; + case BT_ANS_CTRL_DISABLE_UNREAD: + if ((unread_sup_cat_bit_mask & (1U << command.category)) == 0) + { + LOG_DBG( + "Received control point request for unsupported category: " + "%d", command.category); + return BT_GATT_ERR(BT_ANS_ERR_CMD_NOT_SUP); + } + + unread_cat_enabled_map &= ~(1U << command.category); + break; + default: + return BT_GATT_ERR(BT_ANS_ERR_CMD_NOT_SUP); + } + + return len; +} + +static int ans_init(void) +{ + for (int i = 0; i < BT_ANS_CAT_NUM; i++) + { + new_alerts[i].category_id = i; + unread_alerts[i].category_id = i; + } + + LOG_INF("ANS initialization complete"); + + return 0; +} + +/* Alert Notification Service Declaration */ + +BT_GATT_SERVICE_DEFINE( + ans_svc, BT_GATT_PRIMARY_SERVICE(BT_UUID_ANS), + BT_GATT_CHARACTERISTIC(BT_UUID_GATT_SNALRTC, + BT_GATT_CHRC_READ, + BT_GATT_PERM_READ, + + read_supp_new_alert_cat, NULL, NULL), + BT_GATT_CHARACTERISTIC(BT_UUID_GATT_NALRT, + BT_GATT_CHRC_NOTIFY, + BT_GATT_PERM_NONE, + NULL, + + NULL, NULL), + BT_GATT_CCC(new_alert_ccc_cfg_changed, + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), + BT_GATT_CHARACTERISTIC(BT_UUID_GATT_SUALRTC, + BT_GATT_CHRC_READ, + BT_GATT_PERM_READ, + + read_supp_unread_alert_cat, NULL, NULL), + BT_GATT_CHARACTERISTIC(BT_UUID_GATT_UALRTS, + BT_GATT_CHRC_NOTIFY, + BT_GATT_PERM_NONE, + NULL, + + NULL, NULL), + BT_GATT_CCC(unread_alert_status_ccc_cfg_changed, + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), + BT_GATT_CHARACTERISTIC(BT_UUID_GATT_ALRTNCP, + BT_GATT_CHRC_WRITE, + BT_GATT_PERM_WRITE, + NULL, + + write_alert_notif_ctrl_point, NULL)); + +/* Helper to notify a single category */ + +static int notify_new_alert_category(struct bt_conn *conn, uint8_t cat) +{ + int rc; + int ret; + + ret = nxrmutex_trylock(&new_alert_mutex); + if (ret != 0) + { + LOG_ERR("Unable to lock mutex (err: %d)", ret); + return -EAGAIN; + } + + rc = bt_gatt_notify_uuid(conn, BT_UUID_GATT_NALRT, ans_svc.attrs, + &new_alerts[cat], + sizeof(struct new_alert)); + + ret = nxrmutex_unlock(&new_alert_mutex); + DEBUGASSERT(ret == 0); + + /* If the client is not connected, that is fine */ + + if (rc != 0 && rc != -ENOTCONN) + { + LOG_DBG("Error notifying New Alert category %d rc: %d", cat, rc); + return rc; + } + + return 0; +} + +/* Transmit New Alert */ + +static int transmit_new_alert(struct bt_conn *conn, enum bt_ans_cat category) +{ + int rc; + + uint8_t cat_in = (uint8_t)category; + + /* Nothing to do if notify is disabled */ + + if (conn != NULL && !bt_gatt_is_subscribed(conn, &ans_svc.attrs[3], + BT_GATT_CCC_NOTIFY)) + { + return 0; + } + + /* Special case: send all categories */ + + if (cat_in == BT_ANS_SEND_ALL_CATEGORY) + { + for (int i = 0; i < BT_ANS_CAT_NUM; i++) + { + if (((alert_sup_cat_bit_mask & BIT(i)) != 0) && + ((alert_cat_enabled_map & BIT(i)) != 0)) + { + rc = notify_new_alert_category(conn, i); + if (rc < 0) + { + return rc; + } + } + } + + return 0; + } + + /* Otherwise send just the requested category (if enabled) */ + + if ((alert_cat_enabled_map & BIT(cat_in)) != 0) + { + return notify_new_alert_category(conn, cat_in); + } + + return 0; +} + +/* Helper to notify a single unread alert category */ + +static int notify_unread_alert_category(struct bt_conn *conn, uint8_t cat) +{ + int rc; + int ret; + + ret = nxrmutex_trylock(&unread_mutex); + if (ret != 0) + { + LOG_ERR("Unable to lock mutex (err: %d)", ret); + return -EAGAIN; + } + + rc = bt_gatt_notify_uuid(conn, BT_UUID_GATT_UALRTS, ans_svc.attrs, + &unread_alerts[cat], + sizeof(struct unread_alert_status)); + + ret = nxrmutex_unlock(&unread_mutex); + DEBUGASSERT(ret == 0); + + /* If the client is not connected, that is fine */ + + if (rc != 0 && rc != -ENOTCONN) + { + LOG_DBG("Error notifying Unread Alert category %d rc: %d", cat, rc); + return rc; + } + + return 0; +} + +/* Transmit Unread Alert */ + +static int transmit_unread_alert(struct bt_conn *conn, + enum bt_ans_cat category) +{ + int rc; + + uint8_t cat_in = (uint8_t)category; + + /* Nothing to do if notify is disabled */ + + if (conn != NULL && !bt_gatt_is_subscribed(conn, &ans_svc.attrs[6], + BT_GATT_CCC_NOTIFY)) + { + return 0; + } + + /* Special case: send all categories */ + + if (cat_in == BT_ANS_SEND_ALL_CATEGORY) + { + for (int i = 0; i < BT_ANS_CAT_NUM; i++) + { + if ((unread_sup_cat_bit_mask & BIT(i)) && + (unread_cat_enabled_map & BIT(i))) + { + rc = notify_unread_alert_category(conn, i); + if (rc < 0) + { + return rc; + } + } + } + + return 0; + } + + /* Otherwise send just the requested category (if enabled) */ + + if ((unread_cat_enabled_map & BIT(cat_in)) != 0) + { + return notify_unread_alert_category(conn, cat_in); + } + + return 0; +} + +int bt_ans_notify_new_alert( + struct bt_conn *conn, enum bt_ans_cat category, uint8_t num_new, + const char *text) +{ + int ret; + + uint8_t cat_in = (uint8_t)category; + + /* Check if the category is supported */ + + if ((alert_sup_cat_bit_mask & (1U << cat_in)) == 0) + { + LOG_DBG("Category %d unsupported", cat_in); + return -EINVAL; + } + + /* Update the saved value */ + + ret = nxrmutex_lock(&new_alert_mutex); + DEBUGASSERT(ret == 0); + new_alerts[cat_in].num_new_alerts = num_new; + utf8_lcpy(new_alerts[cat_in].text_string, text, + sizeof(new_alerts[cat_in].text_string)); + ret = nxrmutex_unlock(&new_alert_mutex); + DEBUGASSERT(ret == 0); + + return transmit_new_alert(conn, category); +} + +int bt_ans_set_unread_count(struct bt_conn *conn, + enum bt_ans_cat category, uint8_t unread) +{ + int ret; + + uint8_t cat_in = (uint8_t)category; + + /* Check if the category is supported */ + + if ((unread_sup_cat_bit_mask & (1U << cat_in)) == 0) + { + LOG_DBG("Category %d unsupported", cat_in); + return -EINVAL; + } + + /* Update the saved value */ + + ret = nxrmutex_lock(&unread_mutex); + DEBUGASSERT(ret == 0); + unread_alerts[cat_in].unread_count = unread; + ret = nxrmutex_unlock(&unread_mutex); + DEBUGASSERT(ret == 0); + + return transmit_unread_alert(conn, category); +} + +/* Callback used by bt_conn_foreach() to detect an established connection. */ + +static void ans_conn_check_cb(struct bt_conn *conn, void *data) +{ + int err; + bool *has_conn = data; + struct bt_conn_info info; + + err = bt_conn_get_info(conn, &info); + if (err == 0 && info.state == BT_CONN_STATE_CONNECTED) + { + *has_conn = true; + } +} + +/* Used to check if an active connect exists, stopping modifications to + * supported features + */ + +static bool ans_check_conn_busy(void) +{ + bool has_conn = false; + + bt_conn_foreach(BT_CONN_TYPE_ALL, ans_conn_check_cb, &has_conn); + + if (has_conn) + { + /* Cannot change support while connection exists */ + + return true; + } + + return false; +} + +int bt_ans_set_new_alert_support_category(uint16_t mask) +{ + if (ans_check_conn_busy()) + { + /* Cannot change support while connection exists */ + + return -EBUSY; + } + + alert_sup_cat_bit_mask = mask; + + LOG_DBG("New Alert Support Bit Mask: %x", alert_sup_cat_bit_mask); + + return 0; +} + +int bt_ans_set_unread_support_category(uint16_t mask) +{ + if (ans_check_conn_busy()) + { + /* Cannot change support while connection exists */ + + return -EBUSY; + } + + unread_sup_cat_bit_mask = mask; + + LOG_DBG("Unread Support Bit Mask: %x", unread_sup_cat_bit_mask); + + return 0; +} + +SYS_INIT(ans_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/libs/zblue/services/bas/CMakeLists.txt b/libs/zblue/services/bas/CMakeLists.txt new file mode 100644 index 0000000000000..372c73cff75a3 --- /dev/null +++ b/libs/zblue/services/bas/CMakeLists.txt @@ -0,0 +1,25 @@ +# ############################################################################## +# libs/zblue/services/bas/CMakeLists.txt +# +# Author: chao an +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +# BAS sources are added by libs/zblue/services/CMakeLists.txt. diff --git a/libs/zblue/services/bas/Kconfig.bas b/libs/zblue/services/bas/Kconfig.bas new file mode 100644 index 0000000000000..0429d9aeed48e --- /dev/null +++ b/libs/zblue/services/bas/Kconfig.bas @@ -0,0 +1,43 @@ +############################################################################ +# libs/zblue/services/bas/Kconfig.bas +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth GATT Battery service + +# Copyright (c) 2018 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config BT_BAS + bool "GATT Battery service" + +config BT_BAS_BLS + bool "Battery Level Status" + help + Enable this option to include Battery Level Status Characteristic. + +if BT_BAS_BLS + +config BT_BAS_BLS_IDENTIFIER_PRESENT + bool "Battery Level Identifier Present" + help + Enable this option if the Battery Level Identifier is present. + +config BT_BAS_BLS_BATTERY_LEVEL_PRESENT + bool "Battery Level Present" + help + Enable this option if the Battery Level is present. + +config BT_BAS_BLS_ADDITIONAL_STATUS_PRESENT + bool "Additional Battery Status Present" + help + Enable this option if Additional Battery Status information is present. + +config BT_BAS_BCS + bool "Battery Critical Status" + help + Enable this option to include Battery Critical Status Characteristic. +endif diff --git a/libs/zblue/services/bas/bas.c b/libs/zblue/services/bas/bas.c new file mode 100644 index 0000000000000..8c90888bad10f --- /dev/null +++ b/libs/zblue/services/bas/bas.c @@ -0,0 +1,187 @@ +/**************************************************************************** + * libs/zblue/services/bas/bas.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief GATT Battery Service + */ + +/* Copyright (c) 2024 Demant A/S + * Copyright (c) 2018 Nordic Semiconductor ASA Copyright (c) 2016 Intel + * Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "bas_internal.h" + +#define LOG_LEVEL CONFIG_BT_BAS_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bas, CONFIG_BT_BAS_LOG_LEVEL); + +static uint8_t battery_level = 100U; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void blvl_ccc_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + bool notif_enabled = (value == BT_GATT_CCC_NOTIFY); + + LOG_INF("BAS Notifications %s", notif_enabled ? "enabled" : "disabled"); +} + +#if defined(CONFIG_BT_BAS_BLS) +static void blvl_status_ccc_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + bool notif_enabled = (value == BT_GATT_CCC_NOTIFY); + bool ind_enabled = (value == BT_GATT_CCC_INDICATE); + + LOG_INF("BAS Notifications %s", notif_enabled ? "enabled" : "disabled"); + LOG_INF("BAS Indications %s", ind_enabled ? "enabled" : "disabled"); +} +#endif + +static ssize_t read_blvl( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + uint8_t lvl8 = battery_level; + + return bt_gatt_attr_read(conn, + attr, + buf, + len, + offset, + &lvl8, + sizeof(lvl8)); +} + +/* Constant values from the Assigned Numbers specification: + * https://www.bluetooth.com/wp-content/uploads/Files/Specifica + * tion/Assigned_Numbers.pdf?id= + * 89 + */ + +static const struct bt_gatt_cpf level_cpf = +{ + .format = 0x04, /* uint8 */ + .exponent = 0x0, + .unit = 0x27ad, /* Percentage */ + .name_space = 0x01, /* Bluetooth SIG */ + .description = 0x0106, /* "main" */ +}; + +BT_GATT_SERVICE_DEFINE( + bas, BT_GATT_PRIMARY_SERVICE(BT_UUID_BAS), + BT_GATT_CHARACTERISTIC(BT_UUID_BAS_BATTERY_LEVEL, + BT_GATT_CHRC_READ | BT_GATT_CHRC_NOTIFY, + BT_GATT_PERM_READ, read_blvl, NULL, NULL), + BT_GATT_CCC(blvl_ccc_cfg_changed, + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), + BT_GATT_CPF(&level_cpf), +#if defined(CONFIG_BT_BAS_BLS) + BT_GATT_CHARACTERISTIC(BT_UUID_BAS_BATTERY_LEVEL_STATUS, + BT_GATT_CHRC_READ | + BT_GATT_CHRC_NOTIFY | + BT_GATT_CHRC_INDICATE, + BT_GATT_PERM_READ, + bt_bas_bls_read_blvl_status, NULL, NULL), + BT_GATT_CCC(blvl_status_ccc_cfg_changed, + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), +#endif +#if defined(CONFIG_BT_BAS_BCS) + BT_GATT_CHARACTERISTIC(BT_UUID_BAS_BATTERY_CRIT_STATUS, + BT_GATT_CHRC_READ | BT_GATT_CHRC_INDICATE, + BT_GATT_PERM_READ, + bt_bas_bcs_read_critical_status, NULL, NULL), + BT_GATT_CCC(bt_bas_bcs_ccc_cfg_changed, + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), +#endif /* CONFIG_BT_BAS_BCS */ +); + +static int bas_init(void) +{ + if (IS_ENABLED(CONFIG_BT_BAS_BLS)) + { + /* Initialize the Battery Level Status Module */ + + bt_bas_bls_init(); + if (IS_ENABLED(CONFIG_BT_BAS_BLS_IDENTIFIER_PRESENT)) + { + /* Set the identifier only if BT_BAS_BLS_IDENTIFIER_PRESENT is + * defined + */ + + bt_bas_bls_set_identifier(level_cpf.description); + } + } + + return 0; +} + +uint8_t bt_bas_get_battery_level(void) +{ + return battery_level; +} + +int bt_bas_set_battery_level(uint8_t level) +{ + int rc; + + if (level > 100U) + { + return -EINVAL; + } + + battery_level = level; + + rc = bt_gatt_notify(NULL, &bas.attrs[1], &level, sizeof(level)); + + if (IS_ENABLED(CONFIG_BT_BAS_BLS_BATTERY_LEVEL_PRESENT)) + { + bt_bas_bls_set_battery_level(level); + } + + return rc == -ENOTCONN ? 0 : rc; +} + +const struct bt_gatt_attr *bt_bas_get_bas_attr(uint16_t index) +{ + if (index < bas.attr_count) + { + return &bas.attrs[index]; + } + + return NULL; +} + +SYS_INIT(bas_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/libs/zblue/services/bas/bas_bcs.c b/libs/zblue/services/bas/bas_bcs.c new file mode 100644 index 0000000000000..39794c35914f5 --- /dev/null +++ b/libs/zblue/services/bas/bas_bcs.c @@ -0,0 +1,138 @@ +/**************************************************************************** + * libs/zblue/services/bas/bas_bcs.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2024 Demant A/S + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include "bas_internal.h" + +#include +LOG_MODULE_DECLARE(bas, CONFIG_BT_BAS_LOG_LEVEL); + +#define BATTERY_CRITICAL_STATUS_CHAR_IDX 9 + +static uint8_t battery_critical_status; + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_bas_bcs_ccc_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + bool ind_enabled = (value == BT_GATT_CCC_INDICATE); + + LOG_DBG("BAS Critical status Indication %s", + ind_enabled ? "enabled" : "disabled"); +} + +ssize_t bt_bas_bcs_read_critical_status( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + return bt_gatt_attr_read(conn, attr, buf, len, offset, + &battery_critical_status, sizeof(uint8_t)); +} + +static void bcs_indicate_cb( + struct bt_conn *conn, struct bt_gatt_indicate_params *params, uint8_t err) +{ + if (err != 0) + { + LOG_DBG("BCS Indication failed with error %u\n", err); + } + else + { + LOG_DBG("BCS Indication sent successfully\n"); + } +} + +static void bt_bas_bcs_update_battery_critical_status(void) +{ + /* Indicate all connections */ + + const struct bt_gatt_attr *attr = + bt_bas_get_bas_attr(BATTERY_CRITICAL_STATUS_CHAR_IDX); + + if (attr) + { + uint8_t err; + + /* Indicate battery critical status to all connections */ + + static struct bt_gatt_indicate_params bcs_ind_params; + + bcs_ind_params.attr = attr; + bcs_ind_params.data = &battery_critical_status; + bcs_ind_params.len = sizeof(battery_critical_status); + bcs_ind_params.func = bcs_indicate_cb; + err = bt_gatt_indicate(NULL, &bcs_ind_params); + if (err && err != -ENOTCONN) + { + LOG_DBG("Failed to send critical status ind to all conns (" + "err %d)\n", + err); + } + } +} + +void bt_bas_bcs_set_battery_critical_state(bool critical_state) +{ + bool current_state = (battery_critical_status & + BT_BAS_BCS_BATTERY_CRITICAL_STATE) != 0; + + if (current_state == critical_state) + { + LOG_DBG("Already battery_critical_state is %d\n", critical_state); + return; + } + + if (critical_state) + { + battery_critical_status |= BT_BAS_BCS_BATTERY_CRITICAL_STATE; + } + else + { + battery_critical_status &= ~BT_BAS_BCS_BATTERY_CRITICAL_STATE; + } + + bt_bas_bcs_update_battery_critical_status(); +} + +void bt_bas_bcs_set_immediate_service_required(bool service_required) +{ + bool current_state = (battery_critical_status & + BT_BAS_BCS_IMMEDIATE_SERVICE_REQUIRED) != 0; + + if (current_state == service_required) + { + LOG_DBG("Already immediate_service_required is %d\n", + service_required); + return; + } + + if (service_required) + { + battery_critical_status |= BT_BAS_BCS_IMMEDIATE_SERVICE_REQUIRED; + } + else + { + battery_critical_status &= ~BT_BAS_BCS_IMMEDIATE_SERVICE_REQUIRED; + } + + bt_bas_bcs_update_battery_critical_status(); +} diff --git a/libs/zblue/services/bas/bas_bls.c b/libs/zblue/services/bas/bas_bls.c new file mode 100644 index 0000000000000..3c20acdd35016 --- /dev/null +++ b/libs/zblue/services/bas/bas_bls.c @@ -0,0 +1,323 @@ +/**************************************************************************** + * libs/zblue/services/bas/bas_bls.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2024 Demant A/S + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include "bas_internal.h" + +#include +LOG_MODULE_DECLARE(bas, CONFIG_BT_BAS_LOG_LEVEL); + +/* The battery level status of a battery. */ + +static struct bt_bas_bls bls; + +#define BT_BAS_IDX_BATT_LVL_STATUS_CHAR_VAL 6 + +/* Notify/Indicate all connections */ + +static struct bt_gatt_indicate_params ind_params; + +/* Bitfield structure: Power State + * + * - Bits 0: Battery Present + * - Bits 1–2: Wired External Power Source Connected + * - Bits 3–4: Wireless External Power Source Connected + * - Bits 5–6: Battery Charge State + * - Bits 7–8: Battery Charge Level + * - Bits 9–11: Charging Type + * - Bits 12–14: Charging Fault Reason + * - Bit 15: RFU + * + * For detailed specification, refer to: + * https://bitbucket.org/bluetooth-SIG/public/src/main/gss/ + * org.bluetooth.characteristic.battery_level_status.yaml + */ + +#define BATTERY_SHIFT 0 +#define WIRED_POWER_SHIFT 1 +#define WIRELESS_POWER_SHIFT 3 +#define BATTERY_CHARGE_STATE_SHIFT 5 +#define BATTERY_CHARGE_LEVEL_SHIFT 7 +#define BATTERY_CHARGE_TYPE_SHIFT 9 +#define CHARGING_FAULT_SHIFT 12 + +#define BATTERY_MASK (BIT_MASK(1) << BATTERY_SHIFT) +#define WIRED_POWER_MASK (BIT_MASK(2) << WIRED_POWER_SHIFT) +#define WIRELESS_POWER_MASK (BIT_MASK(2) << WIRELESS_POWER_SHIFT) +#define BATTERY_CHARGE_STATE_MASK (BIT_MASK(2) << BATTERY_CHARGE_STATE_SHIFT) +#define BATTERY_CHARGE_LEVEL_MASK (BIT_MASK(2) << BATTERY_CHARGE_LEVEL_SHIFT) +#define BATTERY_CHARGE_TYPE_MASK (BIT_MASK(3) << BATTERY_CHARGE_TYPE_SHIFT) +#define CHARGING_FAULT_MASK (BIT_MASK(3) << CHARGING_FAULT_SHIFT) + +/* Bitfield structure: Additional Status + * + * - Bits 0–1: Service Required + * - Bit 2: Battery Fault + * - Bits 3–7: Reserved + */ +#define SERVICE_REQUIRED_SHIFT 0 +#define BATTERY_FAULT_SHIFT 2 + +#define SERVICE_REQUIRED_MASK (BIT_MASK(2) << SERVICE_REQUIRED_SHIFT) +#define BATTERY_FAULT_MASK (BIT_MASK(1) << BATTERY_FAULT_SHIFT) + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +void bt_bas_bls_init(void) +{ + LOG_DBG("Initialise BAS Battery Level Status Module"); + + bls.flags = 0; + bls.power_state = 0; + +#if defined(CONFIG_BT_BAS_BLS_IDENTIFIER_PRESENT) + /* Set identifier flag */ + + bls.flags |= BT_BAS_BLS_FLAG_IDENTIFIER_PRESENT; + bls.identifier = 0; +#endif /* CONFIG_BT_BAS_BLS_IDENTIFIER_PRESENT */ + +#if defined(CONFIG_BT_BAS_BLS_BATTERY_LEVEL_PRESENT) + /* Set battery level flag */ + + bls.flags |= BT_BAS_BLS_FLAG_BATTERY_LEVEL_PRESENT; + bls.battery_level = bt_bas_get_battery_level(); +#endif /* CONFIG_BT_BAS_BLS_BATTERY_LEVEL_PRESENT */ + +#if defined(CONFIG_BT_BAS_BLS_ADDITIONAL_STATUS_PRESENT) + /* Set additional status flag */ + + bls.flags |= BT_BAS_BLS_FLAG_ADDITIONAL_STATUS_PRESENT; + bls.additional_status = 0; +#endif /* CONFIG_BT_BAS_BLS_ADDITIONAL_STATUS_PRESENT */ +} + +static void indicate_cb(struct bt_conn *conn, + struct bt_gatt_indicate_params *params, uint8_t err) +{ + if (err != 0) + { + LOG_DBG("Indication failed with error %d\n", err); + } + else + { + LOG_DBG("Indication sent successfully\n"); + } +} + +static void bt_bas_bls_update_battery_level_status(void) +{ + int err; + const struct bt_gatt_attr *attr = + bt_bas_get_bas_attr(BT_BAS_IDX_BATT_LVL_STATUS_CHAR_VAL); + + if (attr) + { + const struct bt_bas_bls le_battery_level_status = + { + .flags = bls.flags, + .power_state = sys_cpu_to_le16(bls.power_state), +#if defined(CONFIG_BT_BAS_BLS_IDENTIFIER_PRESENT) + .identifier = sys_cpu_to_le16(bls.identifier), +#endif +#if defined(CONFIG_BT_BAS_BLS_BATTERY_LEVEL_PRESENT) + .battery_level = bls.battery_level, +#endif +#if defined(CONFIG_BT_BAS_BLS_ADDITIONAL_STATUS_PRESENT) + .additional_status = bls.additional_status, +#endif + }; + + /* Indicate all connections */ + + ind_params.attr = attr; + ind_params.data = &le_battery_level_status; + ind_params.len = sizeof(le_battery_level_status); + ind_params.func = indicate_cb; + err = bt_gatt_indicate(NULL, &ind_params); + if (err) + { + LOG_DBG("Failed to send ind to all connections (err %d)\n", err); + } + + /* Notify all connections */ + + err = bt_gatt_notify(NULL, attr, &le_battery_level_status, + sizeof(le_battery_level_status)); + if (err) + { + LOG_DBG("Failed to send ntf to all connections (err %d)\n", err); + } + } +} + +ssize_t bt_bas_bls_read_blvl_status( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + const struct bt_bas_bls le_battery_level_status = + { + .flags = bls.flags, + .power_state = sys_cpu_to_le16(bls.power_state), +#if defined(CONFIG_BT_BAS_BLS_IDENTIFIER_PRESENT) + .identifier = sys_cpu_to_le16(bls.identifier), +#endif +#if defined(CONFIG_BT_BAS_BLS_BATTERY_LEVEL_PRESENT) + .battery_level = bls.battery_level, +#endif +#if defined(CONFIG_BT_BAS_BLS_ADDITIONAL_STATUS_PRESENT) + .additional_status = bls.additional_status, +#endif + }; + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + &le_battery_level_status, + sizeof(le_battery_level_status)); +} + +void bt_bas_bls_set_battery_present(enum bt_bas_bls_battery_present present) +{ + bls.power_state &= ~BATTERY_MASK; + bls.power_state |= (present << BATTERY_SHIFT) & BATTERY_MASK; + bt_bas_bls_update_battery_level_status(); +} + +void bt_bas_bls_set_wired_external_power_source( + enum bt_bas_bls_wired_power_source source) +{ + bls.power_state &= ~WIRED_POWER_MASK; + bls.power_state |= (source << WIRED_POWER_SHIFT) & WIRED_POWER_MASK; + bt_bas_bls_update_battery_level_status(); +} + +void bt_bas_bls_set_wireless_external_power_source( + enum bt_bas_bls_wireless_power_source source) +{ + bls.power_state &= ~WIRELESS_POWER_MASK; + bls.power_state |= (source << WIRELESS_POWER_SHIFT) & WIRELESS_POWER_MASK; + bt_bas_bls_update_battery_level_status(); +} + +void bt_bas_bls_set_battery_charge_state( + enum bt_bas_bls_battery_charge_state state) +{ + bls.power_state &= ~BATTERY_CHARGE_STATE_MASK; + bls.power_state |= + (state << BATTERY_CHARGE_STATE_SHIFT) & BATTERY_CHARGE_STATE_MASK; + bt_bas_bls_update_battery_level_status(); +} + +void bt_bas_bls_set_battery_charge_level( + enum bt_bas_bls_battery_charge_level level) +{ + bls.power_state &= ~BATTERY_CHARGE_LEVEL_MASK; + bls.power_state |= + (level << BATTERY_CHARGE_LEVEL_SHIFT) & BATTERY_CHARGE_LEVEL_MASK; + bt_bas_bls_update_battery_level_status(); + + if (IS_ENABLED(CONFIG_BT_BAS_BCS)) + { + /* Set the BCS Critical Power State bit as per BAS 1.1 specification + * section 3.4.1.1: The BCS Critical Power State bit should be set to + * true if the Battery Charge Level is set to Critical in the Power + * State field. + */ + + if (level == BT_BAS_BLS_CHARGE_LEVEL_CRITICAL) + { + bt_bas_bcs_set_battery_critical_state(true); + return; + } + else if (level != BT_BAS_BLS_CHARGE_LEVEL_UNKNOWN) + { + bt_bas_bcs_set_battery_critical_state(false); + } + } +} + +void bt_bas_bls_set_battery_charge_type( + enum bt_bas_bls_battery_charge_type type) +{ + bls.power_state &= ~BATTERY_CHARGE_TYPE_MASK; + bls.power_state |= + (type << BATTERY_CHARGE_TYPE_SHIFT) & BATTERY_CHARGE_TYPE_MASK; + bt_bas_bls_update_battery_level_status(); +} + +void bt_bas_bls_set_charging_fault_reason( + enum bt_bas_bls_charging_fault_reason reason) +{ + bls.power_state &= ~CHARGING_FAULT_MASK; + bls.power_state |= (reason << CHARGING_FAULT_SHIFT) & CHARGING_FAULT_MASK; + bt_bas_bls_update_battery_level_status(); +} + +#if defined(CONFIG_BT_BAS_BLS_BATTERY_LEVEL_PRESENT) +void bt_bas_bls_set_battery_level(uint8_t level) +{ + bls.battery_level = level; + bt_bas_bls_update_battery_level_status(); +} +#endif /* CONFIG_BT_BAS_BLS_BATTERY_LEVEL_PRESENT */ + +#if defined(CONFIG_BT_BAS_BLS_IDENTIFIER_PRESENT) +void bt_bas_bls_set_identifier(uint16_t identifier) +{ + bls.identifier = sys_cpu_to_le16(identifier); + bt_bas_bls_update_battery_level_status(); +} +#endif /* CONFIG_BT_BAS_BLS_IDENTIFIER_PRESENT */ + +#if defined(CONFIG_BT_BAS_BLS_ADDITIONAL_STATUS_PRESENT) +void bt_bas_bls_set_service_required(enum bt_bas_bls_service_required value) +{ + bls.additional_status &= ~SERVICE_REQUIRED_MASK; + bls.additional_status |= + (value << SERVICE_REQUIRED_SHIFT) & SERVICE_REQUIRED_MASK; + bt_bas_bls_update_battery_level_status(); + + if (IS_ENABLED(CONFIG_BT_BAS_BCS)) + { + /* Set the BCS Immediate Service Required bit as per BAS 1.1 + * specification section 3.4.1.1: The BCS Immediate Service Required + * bit should be set to true if the service Required bit is set to true + * in the Additional Status field. + */ + + if (value == BT_BAS_BLS_SERVICE_REQUIRED_TRUE) + { + bt_bas_bcs_set_immediate_service_required(true); + return; + } + else if (value != BT_BAS_BLS_SERVICE_REQUIRED_UNKNOWN) + { + bt_bas_bcs_set_immediate_service_required(false); + } + } +} + +void bt_bas_bls_set_battery_fault(enum bt_bas_bls_battery_fault value) +{ + bls.additional_status &= ~BATTERY_FAULT_MASK; + bls.additional_status |= + (value << BATTERY_FAULT_SHIFT) & BATTERY_FAULT_MASK; + bt_bas_bls_update_battery_level_status(); +} +#endif /* CONFIG_BT_BAS_BLS_ADDITIONAL_STATUS_PRESENT */ diff --git a/libs/zblue/services/bas/bas_internal.h b/libs/zblue/services/bas/bas_internal.h new file mode 100644 index 0000000000000..26ed3d2114d73 --- /dev/null +++ b/libs/zblue/services/bas/bas_internal.h @@ -0,0 +1,220 @@ +/**************************************************************************** + * libs/zblue/services/bas/bas_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2024 Demant A/S + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef BT_BAS_INTERNAL_H_ +#define BT_BAS_INTERNAL_H_ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +/** + * @brief Battery level status structure definition. + */ + +struct bt_bas_bls +{ + /** @brief Flags Field + * + * The values of this field are defined below. + * + * - bit 0: Identifier Present + * - Indicates whether the identifier field is present. + * - bit 1: Battery Level Present + * - Indicates whether the battery level field is present. + * - bit 2: Additional Status Present + * - Indicates whether the additional status field is present. + * - bit 3–7: RFU (Reserved for Future Use) + * - Reserved bits for future use; should be set to zero. + */ + + uint8_t flags; + + /** @brief Power State + * + * The values of this field are defined below. + * + * - bit 0: Battery Present + * - 0 = No + * - 1 = Yes + * - bit 1–2: Wired External Power Source Connected + * - 0 = No + * - 1 = Yes + * - 2 = Unknown + * - 3 = RFU + * - bit 3–4: Wireless External Power Source Connected + * - 0 = No + * - 1 = Yes + * - 2 = Unknown + * - 3 = RFU + * - bit 5–6: Battery Charge State + * - 0 = Unknown + * - 1 = Charging + * - 2 = Discharging: Active + * - 3 = Discharging: Inactive + * - bit 7–8: Battery Charge Level + * - 0 = Unknown + * - 1 = Good + * - 2 = Low + * - 3 = Critical + * - bit 9–11: Charging Type + * - 0 = Unknown or Not Charging + * - 1 = Constant Current + * - 2 = Constant Voltage + * - 3 = Trickle + * - 4 = Float + * - 5–7 = RFU + * - bit 12–14: Charging Fault Reason + * - Bit 12: Battery + * - Bit 13: External Power source + * - Bit 14: Other + * - bit 15: RFU + */ + + uint16_t power_state; + +#if defined(CONFIG_BT_BAS_BLS_IDENTIFIER_PRESENT) + /** Identifier for the battery, range 0x0000 to 0xFFFF. */ + + uint16_t identifier; +#endif /* CONFIG_BT_BAS_BLS_IDENTIFIER_PRESENT */ + +#if defined(CONFIG_BT_BAS_BLS_BATTERY_LEVEL_PRESENT) + /** Current battery level */ + + uint8_t battery_level; +#endif /* CONFIG_BT_BAS_BLS_BATTERY_LEVEL_PRESENT */ + +#if defined(CONFIG_BT_BAS_BLS_ADDITIONAL_STATUS_PRESENT) + + /** @brief Additional Status + * + * The values of this field are defined below. + * + * - bit 0–1: Service Required + * - 0 = False + * - 1 = True + * - 2 = Unknown + * - 3 = RFU + * - bit 2: Battery Fault + * - 0 = False or Unknown + * - 1 = Yes + * - bit 3–7: RFU + */ + + uint8_t additional_status; + +#endif /* CONFIG_BT_BAS_BLS_ADDITIONAL_STATUS_PRESENT */ +} __packed; + +/** + * @brief Initialize the Battery Level Status Module. + * + */ + +void bt_bas_bls_init(void); + +/** + * @brief Read the Battery Critical Status characteristic. + * + * @param conn Pointer to the Bluetooth connection object representing the + * client requesting the characteristic. + * @param attr Pointer to the GATT attribute of Battery Critical Status + * characteristic. + * @param buf Buffer to store the read value. + * @param len Length of the buffer. + * @param offset Offset within the characteristic value to start reading. + * + * @return The number of bytes read and sent to the client, or a negative + * error code on failure. + */ + +ssize_t bt_bas_bcs_read_critical_status( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset); + +/** + * @brief Callback function for BCS Client Characteristic Configuration + * changes. + * + * + * @param attr Pointer to the GATT attribute of battery critical status + * char. + * @param value The new value of the CCC. This value indicates whether + * notifications or indications are enabled or disabled. + */ + +void bt_bas_bcs_ccc_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value); + +/** + * @brief Set the battery level characteristic value. + * + * @param battery_level The new battery level value in percent (0-100). + */ + +void bt_bas_bls_set_battery_level(uint8_t battery_level); + +/** + * @brief Set the battery critical state flag. + * + * @param critical_state The battery critical state to set (true for + * critical, false otherwise). + */ + +void bt_bas_bcs_set_battery_critical_state(bool critical_state); + +/** + * @brief Set the immediate service required flag. + * + * @param service_required The immediate service required status to set. + */ + +void bt_bas_bcs_set_immediate_service_required(bool service_required); + +/** + * @brief Read the Battery Level Status characteristic. + * + * @param conn Pointer to the Bluetooth connection object representing the + * client requesting the characteristic. + * @param attr Pointer to the GATT attribute representing the Battery Level + * Status characteristic. + * @param buf Buffer to store the read value. + * @param len Length of the buffer. + * @param offset Offset within the characteristic value to start reading. + * + * @return The number of bytes read and sent to the client, or a negative + * error code on failure. + */ + +ssize_t bt_bas_bls_read_blvl_status( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset); + +/** + * @brief Retrieve the Bluetooth GATT attribute for the BAS service by index. + * + * @param index The index of the attribute within the BAS service. + * + * @return Pointer to the Bluetooth GATT attribute if the index is valid, + * otherwise NULL if the index is out of bounds. + */ + +const struct bt_gatt_attr *bt_bas_get_bas_attr(uint16_t index); + +#endif /* BT_BAS_INTERNAL_H_ */ diff --git a/libs/zblue/services/cts.c b/libs/zblue/services/cts.c new file mode 100644 index 0000000000000..19c09a566a3b5 --- /dev/null +++ b/libs/zblue/services/cts.c @@ -0,0 +1,401 @@ +/**************************************************************************** + * libs/zblue/services/cts.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief GATT Current Time Service + */ + +/* Copyright (c) 2024 Croxel Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* Pre-processor Definitions + * Pre-processor Definitions + */ + +#undef _POSIX_C_SOURCE +#define _POSIX_C_SOURCE 200809L + +#ifdef CONFIG_BT_CTS_HELPER_API + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#endif + +#include +#include +#include + +#include +#include +#include +#include + +#include +LOG_MODULE_REGISTER(cts, CONFIG_BT_CTS_LOG_LEVEL); + +#define CTS_BASE_YEAR 1900 + +static const struct bt_cts_cb *cts_cb; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static enum bt_cts_dst_offset cts_parse_dst_offset(uint8_t dst_offset) +{ + switch (dst_offset) + { + case BT_CTS_DST_OFFSET_STANDARD_TIME: + case BT_CTS_DST_OFFSET_HALF_HOUR_DAYLIGHT_TIME: + case BT_CTS_DST_OFFSET_DAYLIGHT_TIME: + case BT_CTS_DST_OFFSET_DOUBLE_DAYLIGHT_TIME: + return (enum bt_cts_dst_offset)dst_offset; + case BT_CTS_DST_OFFSET_UNKNOWN: + default: + return BT_CTS_DST_OFFSET_UNKNOWN; + } +} + +#ifdef CONFIG_BT_CTS_HELPER_API +int bt_cts_time_to_unix_ms(const struct bt_cts_time_format *ct_time, + int64_t *unix_ms) +{ + struct tm date_time; + + /* fill date time structure */ + + /* Year (little endian) */ + + date_time.tm_year = sys_le16_to_cpu(ct_time->year); + date_time.tm_year -= CTS_BASE_YEAR; + + /* Month starts from 1, but needs to be from 0 */ + + date_time.tm_mon = ct_time->mon - 1; + + /* Day of month */ + + date_time.tm_mday = ct_time->mday; + + /* Hours of day */ + + date_time.tm_hour = ct_time->hours; + + /* Minute of hour */ + + date_time.tm_min = ct_time->min; + + /* Seconds of minute */ + + date_time.tm_sec = ct_time->sec; + + /* For sundays convert to 0, else keep same */ + + date_time.tm_wday = ct_time->wday % 7; + + LOG_DBG("CTS Write Time: %d/%d/%d %d:%d:%d", date_time.tm_year, + date_time.tm_mon, + date_time.tm_mday, date_time.tm_hour, date_time.tm_min, + date_time.tm_sec); + + /* get unit timestamp from datetime */ + + (*unix_ms) = timegm(&date_time); + if ((*unix_ms) == ((time_t)-1)) + { + return -EOVERFLOW; + } + + LOG_DBG("CTS Write Unix: %lld", (*unix_ms)); + (*unix_ms) *= MSEC_PER_SEC; + + /* add fraction 256 part */ + + (*unix_ms) += + ((ct_time->fractions256 * MSEC_PER_SEC) / + BT_CTS_FRACTION_256_MAX_VALUE); + + return 0; +} + +int bt_cts_time_from_unix_ms(struct bt_cts_time_format *ct_time, + int64_t unix_ms) +{ + struct tm date_time; + time_t unix_ts = unix_ms / MSEC_PER_SEC; + + /* 'Fractions 256 part of 'Exact Time 256' */ + + unix_ms %= MSEC_PER_SEC; + unix_ms *= BT_CTS_FRACTION_256_MAX_VALUE; + unix_ms /= MSEC_PER_SEC; + ct_time->fractions256 = unix_ms; + + /* convert unix_ts to */ + + LOG_DBG("CTS Read Unix: %lld", unix_ts); + + /* generate date time from unix timestamp */ + + if (gmtime_r(&unix_ts, &date_time) == NULL) + { + return -EOVERFLOW; + } + + date_time.tm_year += CTS_BASE_YEAR; + + LOG_DBG("CTS Read Time: %d/%d/%d %d:%d:%d", date_time.tm_year, + date_time.tm_mon, + date_time.tm_mday, date_time.tm_hour, date_time.tm_min, + date_time.tm_sec); + + /* 'Exact Time 256' contains 'Day Date Time' which contains + * 'Date Time' - characteristic contains fields for: year, month, day, + * hours, minutes and seconds. + */ + + ct_time->year = sys_cpu_to_le16(date_time.tm_year); + ct_time->mon = date_time.tm_mon + 1; /* months starting from 1 */ + ct_time->mday = date_time.tm_mday; /* Day of month */ + ct_time->hours = date_time.tm_hour; /* hours */ + ct_time->min = date_time.tm_min; /* minutes */ + ct_time->sec = date_time.tm_sec; /* seconds */ + + /* day of week starting from 1-monday, 7-sunday */ + + ct_time->wday = date_time.tm_wday; + if (ct_time->wday == 0) + { + ct_time->wday = 7; /* sunday is represented as 7 */ + } + + return 0; +} + +#endif /* CONFIG_BT_CTS_HELPER_API */ + +int bt_cts_local_time_to_ms(const struct bt_cts_local_time *local_time, + int32_t *relative_ms) +{ + int32_t timezone_offset_ms = 0; + int32_t dst_offset_ms = 0; + + if (local_time == NULL || relative_ms == NULL || + !IN_RANGE(local_time->timezone_offset, BT_CTS_TIMEZONE_MIN, + BT_CTS_TIMEZONE_MAX) || + cts_parse_dst_offset( + local_time->dst_offset) == BT_CTS_DST_OFFSET_UNKNOWN) + { + LOG_DBG("local time struct and relative ms must be valid pointers"); + return -EINVAL; + } + + /* Calculate timezone offset in milliseconds */ + + timezone_offset_ms = + (int32_t)local_time->timezone_offset * + BT_CTS_TIMEZONE_INCREMENT_MIN * + SEC_PER_MIN * MSEC_PER_SEC; + dst_offset_ms = + (int32_t)local_time->dst_offset * BT_CTS_DST_INCREMENT_MIN * + SEC_PER_MIN * MSEC_PER_SEC; + (*relative_ms) = timezone_offset_ms + dst_offset_ms; + return 0; +} + +static void ct_ccc_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + bool notif_enabled = (value == BT_GATT_CCC_NOTIFY); + + LOG_INF("CTS Notifications %s", notif_enabled ? "enabled" : "disabled"); + + if (cts_cb->notification_changed) + { + cts_cb->notification_changed(notif_enabled); + } +} + +static ssize_t read_ct( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + int err; + struct bt_cts_time_format ct_time; + + err = cts_cb->fill_current_cts_time(&ct_time); + ct_time.reason = BT_CTS_UPDATE_REASON_UNKNOWN; + + if (err == 0) + { + return bt_gatt_attr_read(conn, attr, buf, len, offset, &ct_time, + sizeof(ct_time)); + } + else + { + return BT_GATT_ERR(BT_ATT_ERR_OUT_OF_RANGE); + } +} + +static ssize_t write_ct( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + int err; + struct bt_cts_time_format ct_time; + + if (cts_cb->cts_time_write == NULL) + { + return BT_GATT_ERR(BT_ATT_ERR_INSUFFICIENT_RESOURCES); + } + + if ((offset != 0) || (len != sizeof(ct_time))) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + memcpy(&ct_time, buf, sizeof(ct_time)); + err = cts_cb->cts_time_write(&ct_time); + if (err != 0) + { + return BT_GATT_ERR(BT_CTS_ATT_ERR_VALUES_IGNORED); + } + + err = bt_cts_send_notification(BT_CTS_UPDATE_REASON_MANUAL); + if (err != 0 && err != -ENOTCONN) + { + LOG_WRN("New value was not notified to clients: %d", err); + } + + return len; +} + +static ssize_t read_lt( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + int err; + + /* initialize lt_time to default values */ + + struct bt_cts_local_time lt_time = + { + .timezone_offset = BT_CTS_TIMEZONE_DEFAULT_VALUE, + .dst_offset = BT_CTS_DST_OFFSET_UNKNOWN, + }; + + err = cts_cb->fill_current_cts_local_time(<_time); + + if (err == 0) + { + /* serialize to send local time over air */ + + const unsigned char buffer[] = + { + (unsigned char)lt_time.timezone_offset, + (unsigned char)lt_time.dst_offset + }; + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &buffer, + ARRAY_SIZE(buffer)); + } + else + { + return BT_GATT_ERR(BT_ATT_ERR_OUT_OF_RANGE); + } +} + +static ssize_t write_lt( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + int err; + struct bt_cts_local_time lt_time; + const unsigned char *buffer = (const unsigned char *)buf; + + if (cts_cb->cts_local_time_write == NULL) + { + return BT_GATT_ERR(BT_ATT_ERR_WRITE_REQ_REJECTED); + } + + if ((offset != 0) || (len != 2)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + /* deserialize from air */ + + lt_time.timezone_offset = (int8_t)buffer[0]; + lt_time.dst_offset = cts_parse_dst_offset((uint8_t)buffer[1]); + + if (!IN_RANGE(lt_time.timezone_offset, BT_CTS_TIMEZONE_MIN, + BT_CTS_TIMEZONE_MAX) && + lt_time.timezone_offset != BT_CTS_TIMEZONE_DEFAULT_VALUE) + { + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + + err = cts_cb->cts_local_time_write(<_time); + if (err != 0) + { + return BT_GATT_ERR(BT_CTS_ATT_ERR_VALUES_IGNORED); + } + + return len; +} + +/* Current Time Service Declaration */ + +BT_GATT_SERVICE_DEFINE( + cts_svc, BT_GATT_PRIMARY_SERVICE(BT_UUID_CTS), + BT_GATT_CHARACTERISTIC(BT_UUID_CTS_CURRENT_TIME, + BT_GATT_CHRC_READ | + BT_GATT_CHRC_WRITE | + BT_GATT_CHRC_NOTIFY, + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE, + read_ct, write_ct, NULL), + BT_GATT_CCC(ct_ccc_cfg_changed, + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), + BT_GATT_CHARACTERISTIC(BT_UUID_GATT_LTI, + BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE, + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE, + read_lt, write_lt, NULL)); + +int bt_cts_init(const struct bt_cts_cb *cb) +{ + DEBUGASSERT(cb != NULL); + DEBUGASSERT(cb->fill_current_cts_time != NULL); + DEBUGASSERT(cb->fill_current_cts_local_time != NULL); + if (cb == NULL || cb->fill_current_cts_time == NULL || + cb->fill_current_cts_local_time == NULL) + { + return -EINVAL; + } + + cts_cb = cb; + return 0; +} + +int bt_cts_send_notification(enum bt_cts_update_reason reason) +{ + int err; + struct bt_cts_time_format ct_time; + + err = cts_cb->fill_current_cts_time(&ct_time); + ct_time.reason = reason; + if (err != 0) + { + return err; + } + + return bt_gatt_notify(NULL, &cts_svc.attrs[1], &ct_time, sizeof(ct_time)); +} diff --git a/libs/zblue/services/dis.c b/libs/zblue/services/dis.c new file mode 100644 index 0000000000000..c628ca19933ae --- /dev/null +++ b/libs/zblue/services/dis.c @@ -0,0 +1,655 @@ +/**************************************************************************** + * libs/zblue/services/dis.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief GATT Device Information Service + */ + +/* Copyright (c) 2019 Demant + * Copyright (c) 2018 Nordic Semiconductor ASA Copyright (c) 2016 Intel + * Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include + +#define LOG_LEVEL CONFIG_BT_SERVICE_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_dis); + +#if defined(CONFIG_BT_DIS_PNP) +struct dis_pnp +{ + uint8_t pnp_vid_src; + uint16_t pnp_vid; + uint16_t pnp_pid; + uint16_t pnp_ver; +} __packed; + +static struct dis_pnp dis_pnp_id = +{ + .pnp_vid_src = CONFIG_BT_DIS_PNP_VID_SRC, + .pnp_vid = CONFIG_BT_DIS_PNP_VID, + .pnp_pid = CONFIG_BT_DIS_PNP_PID, + .pnp_ver = CONFIG_BT_DIS_PNP_VER, +}; +#endif + +#if defined(CONFIG_BT_DIS_SYSTEM_ID) +/* Casting to uint64_t since the value is at most 5 bytes, but will appear as + * a 32-bit literal if it is less, giving a warning when right-shifting by + * 32. + */ + +static uint8_t dis_system_id[8] = +{ + BT_BYTES_LIST_LE40((uint64_t)CONFIG_BT_DIS_SYSTEM_ID_IDENTIFIER), + BT_BYTES_LIST_LE24(CONFIG_BT_DIS_SYSTEM_ID_OUI) +}; +#endif + +#if defined(CONFIG_BT_DIS_SETTINGS) +#if defined(CONFIG_BT_DIS_MODEL_NUMBER) +BUILD_ASSERT(sizeof(CONFIG_BT_DIS_MODEL_NUMBER_STR) <= + CONFIG_BT_DIS_STR_MAX + 1); +static uint8_t dis_model[CONFIG_BT_DIS_STR_MAX + 1] = + CONFIG_BT_DIS_MODEL_NUMBER_STR; +#endif +#if defined(CONFIG_BT_DIS_MANUF_NAME) +BUILD_ASSERT(sizeof(CONFIG_BT_DIS_MANUF_NAME_STR) <= + CONFIG_BT_DIS_STR_MAX + 1); +static uint8_t dis_manuf[CONFIG_BT_DIS_STR_MAX + 1] = + CONFIG_BT_DIS_MANUF_NAME_STR; +#endif +#if defined(CONFIG_BT_DIS_SERIAL_NUMBER) +BUILD_ASSERT(sizeof(CONFIG_BT_DIS_SERIAL_NUMBER_STR) <= + CONFIG_BT_DIS_STR_MAX + 1); +static uint8_t dis_serial_number[CONFIG_BT_DIS_STR_MAX + 1] = + CONFIG_BT_DIS_SERIAL_NUMBER_STR; +#endif +#if defined(CONFIG_BT_DIS_FW_REV) +BUILD_ASSERT(sizeof(CONFIG_BT_DIS_FW_REV_STR) <= + CONFIG_BT_DIS_STR_MAX + 1); +static uint8_t dis_fw_rev[CONFIG_BT_DIS_STR_MAX + 1] = + CONFIG_BT_DIS_FW_REV_STR; +#endif +#if defined(CONFIG_BT_DIS_HW_REV) +BUILD_ASSERT(sizeof(CONFIG_BT_DIS_HW_REV_STR) <= + CONFIG_BT_DIS_STR_MAX + 1); +static uint8_t dis_hw_rev[CONFIG_BT_DIS_STR_MAX + 1] = + CONFIG_BT_DIS_HW_REV_STR; +#endif +#if defined(CONFIG_BT_DIS_SW_REV) +BUILD_ASSERT(sizeof(CONFIG_BT_DIS_SW_REV_STR) <= + CONFIG_BT_DIS_STR_MAX + 1); +static uint8_t dis_sw_rev[CONFIG_BT_DIS_STR_MAX + 1] = + CONFIG_BT_DIS_SW_REV_STR; +#endif +#if defined(CONFIG_BT_DIS_UDI) +BUILD_ASSERT(sizeof(CONFIG_BT_DIS_UDI_LABEL_STR) <= + CONFIG_BT_DIS_STR_MAX + 1); +BUILD_ASSERT(sizeof(CONFIG_BT_DIS_UDI_DI_STR) <= + CONFIG_BT_DIS_STR_MAX + 1); +BUILD_ASSERT(sizeof(CONFIG_BT_DIS_UDI_ISSUER_STR) <= + CONFIG_BT_DIS_STR_MAX + 1); +BUILD_ASSERT(sizeof(CONFIG_BT_DIS_UDI_AUTHORITY_STR) <= + CONFIG_BT_DIS_STR_MAX + 1); + +static uint8_t dis_udi_label[CONFIG_BT_DIS_STR_MAX + 1] = + CONFIG_BT_DIS_UDI_LABEL_STR; +static uint8_t dis_udi_di[CONFIG_BT_DIS_STR_MAX + 1] = + CONFIG_BT_DIS_UDI_DI_STR; +static uint8_t dis_udi_issuer[CONFIG_BT_DIS_STR_MAX + 1] = + CONFIG_BT_DIS_UDI_ISSUER_STR; +static uint8_t dis_udi_authority[CONFIG_BT_DIS_STR_MAX + 1] = + CONFIG_BT_DIS_UDI_AUTHORITY_STR; +#endif +#if defined(CONFIG_BT_DIS_IEEE_RCDL) +BUILD_ASSERT(sizeof(CONFIG_BT_DIS_IEEE_RCDL_STR) <= + CONFIG_BT_DIS_STR_MAX + 1); +static uint8_t dis_ieee_rcdl[CONFIG_BT_DIS_STR_MAX + 1] = + CONFIG_BT_DIS_IEEE_RCDL_STR; +#endif + +#define BT_DIS_MODEL_REF dis_model +#define BT_DIS_MANUF_REF dis_manuf +#define BT_DIS_SERIAL_NUMBER_STR_REF dis_serial_number +#define BT_DIS_FW_REV_STR_REF dis_fw_rev +#define BT_DIS_HW_REV_STR_REF dis_hw_rev +#define BT_DIS_SW_REV_STR_REF dis_sw_rev +#define BT_DIS_UDI_LABEL_STR_REF dis_udi_label +#define BT_DIS_UDI_DI_STR_REF dis_udi_di +#define BT_DIS_UDI_ISSUER_STR_REF dis_udi_issuer +#define BT_DIS_UDI_AUTHORITY_STR_REF dis_udi_authority +#define BT_DIS_IEEE_RCDL_STR_REF dis_ieee_rcdl + +/* When assigning too long string literals to the arrays, + * the literals may be truncated, removing the null terminator. Using strnlen + * to avoid sending data outside the array. + */ + +#else /* CONFIG_BT_DIS_SETTINGS */ + +#if defined(CONFIG_BT_DIS_MODEL_NUMBER) +#define BT_DIS_MODEL_REF CONFIG_BT_DIS_MODEL_NUMBER_STR +#endif +#if defined(CONFIG_BT_DIS_MANUF_NAME) +#define BT_DIS_MANUF_REF CONFIG_BT_DIS_MANUF_NAME_STR +#endif +#define BT_DIS_SERIAL_NUMBER_STR_REF CONFIG_BT_DIS_SERIAL_NUMBER_STR +#define BT_DIS_FW_REV_STR_REF CONFIG_BT_DIS_FW_REV_STR +#define BT_DIS_HW_REV_STR_REF CONFIG_BT_DIS_HW_REV_STR +#define BT_DIS_SW_REV_STR_REF CONFIG_BT_DIS_SW_REV_STR +#define BT_DIS_UDI_LABEL_STR_REF CONFIG_BT_DIS_UDI_LABEL_STR +#define BT_DIS_UDI_DI_STR_REF CONFIG_BT_DIS_UDI_DI_STR +#define BT_DIS_UDI_ISSUER_STR_REF CONFIG_BT_DIS_UDI_ISSUER_STR +#define BT_DIS_UDI_AUTHORITY_STR_REF CONFIG_BT_DIS_UDI_AUTHORITY_STR +#define BT_DIS_IEEE_RCDL_STR_REF CONFIG_BT_DIS_IEEE_RCDL_STR + +#endif /* CONFIG_BT_DIS_SETTINGS */ + +#if defined(CONFIG_BT_DIS_MODEL_NUMBER) || defined(CONFIG_BT_DIS_MANUF_NAME) || \ + defined(CONFIG_BT_DIS_SERIAL_NUMBER) || defined(CONFIG_BT_DIS_FW_REV) || \ + defined(CONFIG_BT_DIS_HW_REV) || defined(CONFIG_BT_DIS_SW_REV) || \ + defined(CONFIG_BT_DIS_IEEE_RCDL) + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static ssize_t read_str( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + return bt_gatt_attr_read(conn, attr, buf, len, offset, attr->user_data, + strlen(attr->user_data)); +} +#endif + +#if defined(CONFIG_BT_DIS_PNP) +static ssize_t read_pnp_id( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + return bt_gatt_attr_read(conn, attr, buf, len, offset, &dis_pnp_id, + sizeof(dis_pnp_id)); +} +#endif + +#if defined(CONFIG_BT_DIS_SYSTEM_ID) +static ssize_t read_system_id( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + return bt_gatt_attr_read(conn, attr, buf, len, offset, dis_system_id, + sizeof(dis_system_id)); +} +#endif + +#if defined(CONFIG_BT_DIS_UDI) +#define DIS_STR_SIZE(x) ((x[0]) != '\0' ? strlen(x) + 1 : 0) +#define BT_DIS_UDI_FLAG_LABEL (!!BT_DIS_UDI_LABEL_STR_REF[0]) +#define BT_DIS_UDI_FLAG_DI (!!BT_DIS_UDI_DI_STR_REF[0]) +#define BT_DIS_UDI_FLAG_ISSUER (!!BT_DIS_UDI_ISSUER_STR_REF[0]) +#define BT_DIS_UDI_FLAG_AUTHORITY (!!BT_DIS_UDI_AUTHORITY_STR_REF[0]) +#define BT_DIS_UDI_FLAGS \ + (BT_DIS_UDI_FLAG_LABEL | (BT_DIS_UDI_FLAG_DI << 1) | (BT_DIS_UDI_FLAG_ISSUER << 2) | \ + (BT_DIS_UDI_FLAG_AUTHORITY << 3)) + +/* UDI for medical devices contains a flag and 4 different null-terminated + * strings that may have unknown length. This requires its own encode method. + */ + +static void read_udi_subval( + const char *str, uint16_t val_len, char *buf, uint16_t *bytes_read, + uint16_t *index, uint16_t len, uint16_t offset) +{ + /* String should not be with included null-terminator if empty */ + + if (val_len == sizeof("")) + { + return; + } + + if (*bytes_read == len) + { + return; + } + + if (*index + val_len < offset) + { + *index += val_len; + return; + } + + for (uint16_t i = 0; i < val_len; i++) + { + if (*index >= offset && *bytes_read < len) + { + buf[*bytes_read] = str[i]; + + (*bytes_read)++; + } + + (*index)++; + } +} + +static ssize_t read_udi( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + uint16_t bytes_read = 0; + + char *buf_i = (char *)buf; + + /* Flag */ + + uint16_t index = sizeof(uint8_t); + + if (offset == 0) + { + buf_i[0] = BT_DIS_UDI_FLAGS; + bytes_read = 1U; + } + + read_udi_subval(BT_DIS_UDI_LABEL_STR_REF, + DIS_STR_SIZE(BT_DIS_UDI_LABEL_STR_REF), buf_i, + &bytes_read, &index, len, offset); + read_udi_subval(BT_DIS_UDI_DI_STR_REF, DIS_STR_SIZE(BT_DIS_UDI_DI_STR_REF), + buf_i, &bytes_read, + &index, len, offset); + read_udi_subval(BT_DIS_UDI_ISSUER_STR_REF, + DIS_STR_SIZE(BT_DIS_UDI_ISSUER_STR_REF), buf_i, + &bytes_read, &index, len, offset); + read_udi_subval(BT_DIS_UDI_AUTHORITY_STR_REF, + DIS_STR_SIZE(BT_DIS_UDI_AUTHORITY_STR_REF), buf_i, + &bytes_read, &index, len, offset); + + return bytes_read; +} +#endif + +/* Device Information Service Declaration */ + +BT_GATT_SERVICE_DEFINE( + dis_svc, BT_GATT_PRIMARY_SERVICE(BT_UUID_DIS), + +#if defined(CONFIG_BT_DIS_MODEL_NUMBER) + BT_GATT_CHARACTERISTIC(BT_UUID_DIS_MODEL_NUMBER, + BT_GATT_CHRC_READ, + BT_GATT_PERM_READ, + + read_str, NULL, BT_DIS_MODEL_REF), +#endif + +#if defined(CONFIG_BT_DIS_MANUF_NAME) + BT_GATT_CHARACTERISTIC(BT_UUID_DIS_MANUFACTURER_NAME, + BT_GATT_CHRC_READ, + BT_GATT_PERM_READ, + + read_str, NULL, BT_DIS_MANUF_REF), +#endif + +#if defined(CONFIG_BT_DIS_PNP) + BT_GATT_CHARACTERISTIC(BT_UUID_DIS_PNP_ID, + BT_GATT_CHRC_READ, + BT_GATT_PERM_READ, + + read_pnp_id, NULL, &dis_pnp_id), +#endif + +#if defined(CONFIG_BT_DIS_SERIAL_NUMBER) + BT_GATT_CHARACTERISTIC(BT_UUID_DIS_SERIAL_NUMBER, + BT_GATT_CHRC_READ, + BT_GATT_PERM_READ, + + read_str, NULL, BT_DIS_SERIAL_NUMBER_STR_REF), +#endif +#if defined(CONFIG_BT_DIS_FW_REV) + BT_GATT_CHARACTERISTIC(BT_UUID_DIS_FIRMWARE_REVISION, + BT_GATT_CHRC_READ, + BT_GATT_PERM_READ, + + read_str, NULL, BT_DIS_FW_REV_STR_REF), +#endif +#if defined(CONFIG_BT_DIS_HW_REV) + BT_GATT_CHARACTERISTIC(BT_UUID_DIS_HARDWARE_REVISION, + BT_GATT_CHRC_READ, + BT_GATT_PERM_READ, + + read_str, NULL, BT_DIS_HW_REV_STR_REF), +#endif +#if defined(CONFIG_BT_DIS_SW_REV) + BT_GATT_CHARACTERISTIC(BT_UUID_DIS_SOFTWARE_REVISION, + BT_GATT_CHRC_READ, + BT_GATT_PERM_READ, + + read_str, NULL, BT_DIS_SW_REV_STR_REF), +#endif +#if defined(CONFIG_BT_DIS_UDI) + BT_GATT_CHARACTERISTIC(BT_UUID_UDI_FOR_MEDICAL_DEVICES, + BT_GATT_CHRC_READ, + + BT_GATT_PERM_READ, read_udi, NULL, NULL), +#endif +#if defined(CONFIG_BT_DIS_SYSTEM_ID) + BT_GATT_CHARACTERISTIC(BT_UUID_DIS_SYSTEM_ID, + BT_GATT_CHRC_READ, + BT_GATT_PERM_READ, + + read_system_id, NULL, NULL), +#endif +#if defined(CONFIG_BT_DIS_IEEE_RCDL) + BT_GATT_CHARACTERISTIC(BT_UUID_GATT_IEEE_RCDL, + BT_GATT_CHRC_READ, + BT_GATT_PERM_READ, + + read_str, NULL, BT_DIS_IEEE_RCDL_STR_REF), +#endif + +); + +#if defined(CONFIG_BT_DIS_SETTINGS) +#if defined(CONFIG_BT_DIS_UDI) +static void dis_update_udi_value( + const char *new, char *old, settings_read_cb read_cb, const char *logkey) +{ + /* The characteristic contains 1 one-byte flag and 4-null-terminated. + * The null-terminators are only present for strings that are in the flags. + */ + + const size_t merged_size = + sizeof(uint8_t) + DIS_STR_SIZE(BT_DIS_UDI_LABEL_STR_REF) + + DIS_STR_SIZE(BT_DIS_UDI_DI_STR_REF) + + DIS_STR_SIZE(BT_DIS_UDI_ISSUER_STR_REF) + + DIS_STR_SIZE(BT_DIS_UDI_AUTHORITY_STR_REF); + + size_t without_old = merged_size - DIS_STR_SIZE(old); + + bool valid = BT_ATT_MAX_ATTRIBUTE_LEN >= without_old + DIS_STR_SIZE(new); + + if (!valid) + { + LOG_ERR("Failed to set UDI %s. Not enough space. The sum of the 4 DIS " + "UDI for " + "Medical Devices strings may not exceed the maximum attribute " + "length.", + logkey); + return; + } + + int16_t len = read_cb((void *)new, (void *)old, CONFIG_BT_DIS_STR_MAX); + + if (len < 0) + { + LOG_ERR("Failed to read UDI %s from storage (err %zd)", logkey, len); + } + else + { + old[len] = '\0'; + + LOG_DBG("UDI %s set to %s", logkey, old); + } +} +#endif + +static int dis_set(const char *name, + size_t len_rd, settings_read_cb read_cb, void *store) +{ + ssize_t len; + int nlen; + const char *next; + + ARG_UNUSED(len); + + nlen = settings_name_next(name, &next); +#if defined(CONFIG_BT_DIS_MANUF_NAME) + if (!strncmp(name, "manuf", nlen)) + { + len = read_cb(store, &dis_manuf, sizeof(dis_manuf) - 1); + if (len < 0) + { + LOG_ERR("Failed to read manufacturer from storage (err %zd)", len); + } + else + { + dis_manuf[len] = '\0'; + + LOG_DBG("Manufacturer set to %s", dis_manuf); + } + + return 0; + } +#endif +#if defined(CONFIG_BT_DIS_MODEL_NUMBER) + + if (!strncmp(name, "model", nlen)) + { + len = read_cb(store, &dis_model, sizeof(dis_model) - 1); + if (len < 0) + { + LOG_ERR("Failed to read model from storage (err %zd)", len); + } + else + { + dis_model[len] = '\0'; + + LOG_DBG("Model set to %s", dis_model); + } + + return 0; + } +#endif +#if defined(CONFIG_BT_DIS_SERIAL_NUMBER) + + if (!strncmp(name, "serial", nlen)) + { + len = read_cb(store, &dis_serial_number, sizeof(dis_serial_number) - + 1); + if (len < 0) + { + LOG_ERR("Failed to read serial number from storage (err " + "%zd)", len); + } + else + { + dis_serial_number[len] = '\0'; + + LOG_DBG("Serial number set to %s", dis_serial_number); + } + + return 0; + } +#endif +#if defined(CONFIG_BT_DIS_FW_REV) + + if (!strncmp(name, "fw", nlen)) + { + len = read_cb(store, &dis_fw_rev, sizeof(dis_fw_rev) - 1); + if (len < 0) + { + LOG_ERR("Failed to read firmware revision from storage (err %zd)", + len); + } + else + { + dis_fw_rev[len] = '\0'; + + LOG_DBG("Firmware revision set to %s", dis_fw_rev); + } + + return 0; + } +#endif +#if defined(CONFIG_BT_DIS_HW_REV) + + if (!strncmp(name, "hw", nlen)) + { + len = read_cb(store, &dis_hw_rev, sizeof(dis_hw_rev) - 1); + if (len < 0) + { + LOG_ERR("Failed to read hardware revision from storage (err %zd)", + len); + } + else + { + dis_hw_rev[len] = '\0'; + + LOG_DBG("Hardware revision set to %s", dis_hw_rev); + } + + return 0; + } +#endif +#if defined(CONFIG_BT_DIS_SW_REV) + + if (!strncmp(name, "sw", nlen)) + { + len = read_cb(store, &dis_sw_rev, sizeof(dis_sw_rev) - 1); + if (len < 0) + { + LOG_ERR("Failed to read software revision from storage (err %zd)", + len); + } + else + { + dis_sw_rev[len] = '\0'; + + LOG_DBG("Software revision set to %s", dis_sw_rev); + } + + return 0; + } +#endif +#if defined(CONFIG_BT_DIS_UDI) + + if (!strncmp(name, "udi_label", nlen)) + { + dis_update_udi_value(store, BT_DIS_UDI_LABEL_STR_REF, read_cb, + "label"); + return 0; + } + + if (!strncmp(name, "udi_di", nlen)) + { + dis_update_udi_value(store, BT_DIS_UDI_DI_STR_REF, read_cb, + "device information"); + return 0; + } + + if (!strncmp(name, "udi_issuer", nlen)) + { + dis_update_udi_value(store, BT_DIS_UDI_ISSUER_STR_REF, read_cb, + "issuer"); + return 0; + } + + if (!strncmp(name, "udi_authority", nlen)) + { + dis_update_udi_value(store, BT_DIS_UDI_AUTHORITY_STR_REF, read_cb, + "authority"); + return 0; + } +#endif +#if defined(CONFIG_BT_DIS_SYSTEM_ID) + + if (!strncmp(name, "sysid_oui", nlen)) + { + uint32_t oui = 0; + + len = read_cb(store, &oui, sizeof(oui)); + if (len < 0) + { + LOG_ERR("Failed to read System ID OUI from storage (err " + "%zd)", len); + } + else + { + sys_put_le24(oui, &dis_system_id[5]); + LOG_DBG("System ID OUI set to %06X", oui); + } + + return 0; + } + + if (!strncmp(name, "sysid_identifier", nlen)) + { + uint64_t identifier = 0; + + len = read_cb(store, &identifier, sizeof(identifier)); + if (len < 0) + { + LOG_ERR("Failed to read System ID identifier from storage (" + "err %zd)", + len); + } + else + { + sys_put_le40(identifier, &dis_system_id[0]); + LOG_DBG("System ID identifier set to %10llX", identifier); + } + + return 0; + } +#endif +#if defined(CONFIG_BT_DIS_IEEE_RCDL) + + if (!strncmp(name, "ieeercdl", nlen)) + { + len = read_cb(store, &dis_ieee_rcdl, sizeof(dis_ieee_rcdl) - 1); + if (len < 0) + { + LOG_ERR("Failed to read IEEE 11073-20601 Regulatory Certification " + "Data " + "List from storage (err %zd)", + len); + } + else + { + dis_ieee_rcdl[len] = '\0'; + + LOG_DBG("IEEE 11073-20601 Regulatory Certification Data List set " + "to " + "%s", + dis_ieee_rcdl); + } + + return 0; + } +#endif + + return 0; +} + +SETTINGS_STATIC_HANDLER_DEFINE(bt_dis, "bt/dis", NULL, dis_set, NULL, NULL); + +#endif /* CONFIG_BT_DIS_SETTINGS*/ diff --git a/libs/zblue/services/ets.c b/libs/zblue/services/ets.c new file mode 100644 index 0000000000000..0c27191db2e3e --- /dev/null +++ b/libs/zblue/services/ets.c @@ -0,0 +1,603 @@ +/**************************************************************************** + * libs/zblue/services/ets.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2025 Dipak Shetty + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +LOG_MODULE_REGISTER(bt_ets, CONFIG_BT_ETS_LOG_LEVEL); + +/* ETS epoch: 2000-01-01 00:00:00 + * Unix epoch: 1970-01-01 00:00:00 Difference: 946,684,800 seconds + */ +#define ETS_EPOCH_OFFSET_SEC 946684800LL +#define ETS_EPOCH_OFFSET_MS (ETS_EPOCH_OFFSET_SEC * MSEC_PER_SEC) + +/* Resolutions for converting ETS units to milliseconds */ +#define BT_ETS_MSEC_PER_SEC MSEC_PER_SEC /* 1000 ms */ +#define BT_ETS_MSEC_PER_100_MS 100U /* 100 ms */ +#define BT_ETS_MSEC_PER_1_MS 1U /* 1 ms */ +#define BT_ETS_100US_PER_MSEC 10U /* 10 * 100us = 1 ms */ + +/* TZ/DST offset unit: 15 minutes per unit */ +#define BT_ETS_TZ_DST_OFFSET_UNIT_MINUTES 15U + +/* Maximum 48-bit value */ +#define ETS_TIME_VALUE_MAX (0xFFFFFFFFFFFFULL) + +/* ATT Application Error codes (Section 2.4. Attribute Protocol Application + * error codes) + */ +#define BT_ETS_ATT_ERR_TIME_SOURCE_QUALITY_TOO_LOW 0x80 +#define BT_ETS_ATT_ERR_INCORRECT_TIME_FORMAT 0x81 +/* Out of Range uses standard BT_ATT_ERR_OUT_OF_RANGE (0xFF) */ + +/** + * @brief Time Characteristic format (Elapsed Time Service, v1.0 - Section + * 3.1.1) + * + */ + +struct ets_char_value +{ + /* Elapsed Time data */ + + struct bt_ets_elapsed_time et; + + /* The status of the server’s clock (@see @ref BT_ETS_CLOCK_STATUS). */ + + uint8_t clock_status; + + /* The server’s clock capabilities (@see @ref BT_ETS_CLOCK_CAPABILITIES). + */ + + uint8_t clock_capabilities; +} __packed; + +static const struct bt_ets_cb *ets_cb; + +/* Static indication data */ + +static struct bt_gatt_indicate_params indicate_params; +static struct ets_char_value indicate_data; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void indicate_cb(struct bt_conn *conn, + struct bt_gatt_indicate_params *params, uint8_t err) +{ + if (err != 0) + { + LOG_WRN("Indication failed with error %d\n", err); + } + else + { + LOG_DBG("Indication sent successfully"); + } +} + +/* Work item for sending indications */ + +static void send_indication_work_handler(struct k_work *work) +{ + ARG_UNUSED(work); + int err; + uint8_t clock_status; + + if ((ets_cb == NULL) || (ets_cb->read_elapsed_time == NULL) || + (ets_cb->read_clock_status == NULL)) + { + LOG_ERR("ETS callbacks not set for indication"); + return; + } + + /* Read current elapsed time into static buffer */ + + err = ets_cb->read_elapsed_time(&indicate_data.et); + if (err != 0) + { + LOG_ERR("Failed to read elapsed time for indication: %d", err); + return; + } + + /* Read clock status */ + + err = ets_cb->read_clock_status(&clock_status); + if (err != 0) + { + LOG_ERR("Failed to read clock status for indication: %d", err); + return; + } + + /* Send indication using the static indicate_data buffer */ + + err = bt_ets_indicate(&indicate_data.et, clock_status); + if ((err != 0) && (err != -ENOTCONN)) + { + LOG_WRN("Failed to send indication: %d", err); + } + else + { + LOG_DBG("Indication sent successfully"); + } +} + +K_WORK_DEFINE(send_indication_work, send_indication_work_handler); + +static void ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) +{ + bool indicate_enabled = (value == BT_GATT_CCC_INDICATE); + + LOG_DBG("ETS indications %s", indicate_enabled ? "enabled" : "disabled"); + + if ((ets_cb != NULL) && (ets_cb->indication_changed != NULL)) + { + ets_cb->indication_changed(indicate_enabled); + } + + /* Per ETS spec Sec. 3.1.2.2: on reconnect or non‑natural time change the + * server shall send an indication + */ + + if (indicate_enabled) + { + k_work_submit(&send_indication_work); + } +} + +static ssize_t read_elapsed_time( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + int err; + struct ets_char_value response; + + if ((ets_cb == NULL) || (ets_cb->read_elapsed_time == NULL) || + (ets_cb->read_clock_status == NULL)) + { + LOG_ERR("ETS read callbacks not set"); + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + err = ets_cb->read_elapsed_time(&response.et); + if (err != 0) + { + LOG_ERR("Read failed: %d", err); + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + err = ets_cb->read_clock_status(&response.clock_status); + if (err != 0) + { + LOG_ERR("ETS read_clock_status callback failed: %d", err); + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + response.clock_capabilities = BT_ETS_CLOCK_CAPABILITIES_VALUE; + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &response, + sizeof(response)); +} + +#ifdef CONFIG_BT_ETS_CURRENT_ELAPSED_TIME_WRITABLE +static ssize_t write_elapsed_time( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + struct bt_ets_elapsed_time et; + uint64_t time_value; + enum bt_ets_write_result result; + + if (!ets_cb || !ets_cb->write_elapsed_time) + { + LOG_ERR("callback is required, but not set"); + return BT_GATT_ERR(BT_ATT_ERR_WRITE_NOT_PERMITTED); + } + + if (offset != 0) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (len != sizeof(et)) + { + LOG_ERR("Invalid write length: %u (expected %zu)", len, sizeof(et)); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + memcpy(&et, buf, sizeof(et)); + + /* Validate reserved bits (protocol-level check) */ + + if (et.flags & BT_ETS_FLAG_RESERVED_MASK) + { + LOG_ERR("Reserved bits set in flags: 0x%02x", et.flags); + return BT_GATT_ERR(BT_ETS_ATT_ERR_INCORRECT_TIME_FORMAT); + } + + /* Validate time value range */ + + time_value = sys_get_le48(et.time_value); + if (time_value >= ETS_TIME_VALUE_MAX) + { + LOG_ERR("Time value out of range: %llu (max %llu)", time_value, + ETS_TIME_VALUE_MAX); + return BT_GATT_ERR(BT_ATT_ERR_OUT_OF_RANGE); + } + + /* Check resolution bits match configuration (static property) */ + + if ((et.flags & BT_ETS_FLAG_RESOLUTION_MASK) != + (BT_ETS_SUPPORTED_FLAGS_MASK & BT_ETS_FLAG_RESOLUTION_MASK)) + { + LOG_ERR("Invalid resolution in flags: 0x%02lx (expected: 0x%02lx)", + et.flags & BT_ETS_FLAG_RESOLUTION_MASK, + BT_ETS_SUPPORTED_FLAGS_MASK & BT_ETS_FLAG_RESOLUTION_MASK); + return BT_GATT_ERR(BT_ETS_ATT_ERR_INCORRECT_TIME_FORMAT); + } + + /* Check unsupported flags are not set (static properties) */ + + if (et.flags & ~BT_ETS_SUPPORTED_FLAGS_MASK) + { + LOG_ERR("Unsupported flags set: 0x%02x (supported: 0x%02lx)", et.flags, + BT_ETS_SUPPORTED_FLAGS_MASK); + return BT_GATT_ERR(BT_ETS_ATT_ERR_INCORRECT_TIME_FORMAT); + } + + result = ets_cb->write_elapsed_time(&et); + + /* Map application result to ATT error */ + + switch (result) + { + case BT_ETS_WRITE_SUCCESS: + + /* Success - continue processing */ + + break; + case BT_ETS_WRITE_TIME_SOURCE_TOO_LOW: + LOG_WRN("Time source quality too low"); + return BT_GATT_ERR(BT_ETS_ATT_ERR_TIME_SOURCE_QUALITY_TOO_LOW); + case BT_ETS_WRITE_OUT_OF_RANGE: + LOG_WRN("Time value out of range"); + return BT_GATT_ERR(BT_ATT_ERR_OUT_OF_RANGE); + case BT_ETS_WRITE_INCORRECT_FORMAT: + LOG_WRN("Incorrect time format (application validation)"); + return BT_GATT_ERR(BT_ETS_ATT_ERR_INCORRECT_TIME_FORMAT); + default: + LOG_ERR("Unknown write result: %d", result); + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + LOG_INF("ETS time written successfully"); + + /* Report time change (Section 3.1.2.2): When the server changes its time + * after a write by a client to the Current Elapsed Time characteristic, + * the server shall send an indication of the Current Elapsed Time + * characteristic to other connected clients. + */ + + k_work_submit(&send_indication_work); + + return len; +} +#endif /* CONFIG_BT_ETS_CURRENT_ELAPSED_TIME_WRITABLE */ + +/* Elapsed Time Service Declaration */ + +#ifdef CONFIG_BT_ETS_CURRENT_ELAPSED_TIME_WRITABLE +BT_GATT_SERVICE_DEFINE(ets_svc, BT_GATT_PRIMARY_SERVICE(BT_UUID_ETS), + BT_GATT_CHARACTERISTIC(BT_UUID_ETS_CURRENT_ELAPSED_TIME, + BT_GATT_CHRC_READ | + BT_GATT_CHRC_WRITE | + BT_GATT_CHRC_INDICATE, + BT_GATT_PERM_READ | + BT_GATT_PERM_WRITE, + read_elapsed_time, + write_elapsed_time, NULL), + BT_GATT_CCC(ccc_cfg_changed, + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE)); +#else +BT_GATT_SERVICE_DEFINE(ets_svc, BT_GATT_PRIMARY_SERVICE(BT_UUID_ETS), + BT_GATT_CHARACTERISTIC(BT_UUID_ETS_CURRENT_ELAPSED_TIME, + BT_GATT_CHRC_READ | + BT_GATT_CHRC_INDICATE, + BT_GATT_PERM_READ, + read_elapsed_time, NULL, + NULL), + BT_GATT_CCC(ccc_cfg_changed, + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE)); +#endif /* CONFIG_BT_ETS_CURRENT_ELAPSED_TIME_WRITABLE */ + +int bt_ets_init(const struct bt_ets_cb *cb) +{ + DEBUGASSERT(cb != NULL); + DEBUGASSERT(cb->read_elapsed_time != NULL); + DEBUGASSERT(cb->read_clock_status != NULL); + + if ((cb == NULL) || (cb->read_elapsed_time == NULL) || + (cb->read_clock_status == NULL)) + { + LOG_ERR("Mandatory ETS callbacks are NULL"); + return -EINVAL; + } + + ets_cb = cb; + LOG_INF("ETS initialized"); + + return 0; +} + +int bt_ets_indicate(const struct bt_ets_elapsed_time *elapsed_time, + uint8_t clock_status) +{ + int err; + + if (elapsed_time == NULL) + { + LOG_ERR("elapsed_time parameter is NULL"); + return -EINVAL; + } + + memcpy(&indicate_data.et, elapsed_time, sizeof(indicate_data.et)); + indicate_data.clock_status = clock_status; + indicate_data.clock_capabilities = BT_ETS_CLOCK_CAPABILITIES_VALUE; + + /* Setup indication parameters */ + + indicate_params.attr = &ets_svc.attrs[1]; + indicate_params.func = indicate_cb; + indicate_params.data = &indicate_data; + indicate_params.len = sizeof(indicate_data); + + /* Send indication to all connected clients that have enabled indications */ + + err = bt_gatt_indicate(NULL, &indicate_params); + if (err != 0) + { + LOG_ERR("Failed to send indication: %d", err); + } + + return err; +} + +#ifdef CONFIG_BT_ETS_HELPER_API + +int bt_ets_time_to_unix_ms(const struct bt_ets_elapsed_time *et_time, + int64_t *unix_ms) +{ + uint64_t ets_time_value; + int64_t ets_ms; + uint8_t resolution; + + if ((et_time == NULL) || (unix_ms == NULL)) + { + LOG_ERR("Invalid NULL parameter(s)"); + return -EINVAL; + } + + /* Decode the 48-bit time value */ + + ets_time_value = sys_get_le48(et_time->time_value); + + /* Extract resolution from flags (bits 2-3) */ + + resolution = (et_time->flags & BT_ETS_FLAG_RESOLUTION_MASK) >> 2; + + /* Convert to milliseconds based on resolution */ + + switch (resolution) + { + case BT_ETS_RESOLUTION_1_SEC: + + /* 1 second resolution: multiply by 1000 */ + + if (ets_time_value > (INT64_MAX / BT_ETS_MSEC_PER_SEC)) + { + LOG_ERR("Time value overflow during conversion"); + return -EOVERFLOW; + } + + ets_ms = (int64_t)ets_time_value * BT_ETS_MSEC_PER_SEC; + break; + + case BT_ETS_RESOLUTION_100_MS: + + /* 100 ms resolution: multiply by 100 */ + + if (ets_time_value > (INT64_MAX / BT_ETS_MSEC_PER_100_MS)) + { + LOG_ERR("Time value overflow during conversion"); + return -EOVERFLOW; + } + + ets_ms = (int64_t)ets_time_value * BT_ETS_MSEC_PER_100_MS; + break; + + case BT_ETS_RESOLUTION_1_MS: + + /* 1 ms resolution: direct conversion */ + + if (ets_time_value > INT64_MAX) + { + LOG_ERR("Time value overflow during conversion"); + return -EOVERFLOW; + } + + ets_ms = (int64_t)ets_time_value; + break; + + case BT_ETS_RESOLUTION_100_US: + + /* 100 us resolution: divide by 10 to get ms */ + + ets_ms = (int64_t)(ets_time_value / BT_ETS_100US_PER_MSEC); + break; + + default: + LOG_ERR("Invalid resolution value: %u", resolution); + return -EINVAL; + } + + /* Convert from ETS epoch (2000) to Unix epoch (1970) */ + + *unix_ms = ets_ms + ETS_EPOCH_OFFSET_MS; + + /* Apply TZ/DST offset based on time mode. */ + + if ((et_time->flags & BT_ETS_FLAG_TZ_DST_USED) && + !(et_time->flags & BT_ETS_FLAG_UTC)) + { + /* Local time mode: subtract offset to get UTC */ + + *unix_ms -= (int64_t)et_time->tz_dst_offset * + BT_ETS_TZ_DST_OFFSET_UNIT_MINUTES * + SEC_PER_MIN * MSEC_PER_SEC; + LOG_DBG("Converted local time to UTC using offset: %d", + et_time->tz_dst_offset); + } + + LOG_DBG("ETS->Unix: ets_value=%llu resolution=%u ets_ms=%lld unix_ms=%lld " + "flags=0x%02x", + ets_time_value, resolution, ets_ms, *unix_ms, et_time->flags); + + return 0; +} + +int bt_ets_time_from_unix_ms( + struct bt_ets_elapsed_time *et_time, int64_t unix_ms, uint8_t time_src, + int8_t __maybe_unused tz_dst_offset) +{ + int64_t ets_ms; + uint64_t ets_time_value; + uint8_t flags = 0; + + if (et_time == NULL) + { + return -EINVAL; + } + + /* Convert from Unix epoch (1970) to ETS epoch (2000) */ + + ets_ms = unix_ms - ETS_EPOCH_OFFSET_MS; + + /* Apply offset for conversion in local time mode. */ +#ifdef CONFIG_BT_ETS_SUPPORT_LOCAL_TIME + ets_ms += + (int64_t)tz_dst_offset * + BT_ETS_TZ_DST_OFFSET_UNIT_MINUTES * SEC_PER_MIN * MSEC_PER_SEC; +#endif + + /* Check if time is before ETS epoch */ + + if (ets_ms < 0) + { + LOG_ERR("Time is before ETS epoch (2000-01-01)"); + return -EINVAL; + } + + /* Convert from milliseconds to configured resolution */ +#if defined(CONFIG_BT_ETS_RESOLUTION_1_SEC) + /* 1 second resolution: divide by 1000 */ + + ets_time_value = (uint64_t)(ets_ms / BT_ETS_MSEC_PER_SEC); + flags |= 0; /* Resolution bits 2-3 = 00 */ + +#elif defined(CONFIG_BT_ETS_RESOLUTION_100_MS) + /* 100 ms resolution: divide by 100 */ + + ets_time_value = (uint64_t)(ets_ms / BT_ETS_MSEC_PER_100_MS); + flags |= BIT(2); /* Resolution bits 2-3 = 01 */ + +#elif defined(CONFIG_BT_ETS_RESOLUTION_1_MS) + /* 1 ms resolution: direct conversion */ + + ets_time_value = (uint64_t)ets_ms; + flags |= BIT(3); /* Resolution bits 2-3 = 10 */ + +#elif defined(CONFIG_BT_ETS_RESOLUTION_100_US) + /* 100 us resolution: multiply by 10 */ + + if (ets_ms > (INT64_MAX / BT_ETS_100US_PER_MSEC)) + { + LOG_ERR("Time value overflow during conversion"); + return -EOVERFLOW; + } + + ets_time_value = (uint64_t)(ets_ms * BT_ETS_100US_PER_MSEC); + flags |= (BIT(2) | BIT(3)); /* Resolution bits 2-3 = 11 */ +#endif + + /* Check if value fits in 48 bits */ + + if (ets_time_value > ETS_TIME_VALUE_MAX) + { + LOG_ERR("Time value exceeds 48-bit limit: %llu", ets_time_value); + return -EOVERFLOW; + } + + /* Set flags based on configuration (time-of-day mode only). + * These flags are STATIC per spec Section 3.1.2.1 - they are determined at + * compile-time and never change: + * - Bit 1: UTC flag (0=local time, 1=UTC) - static + * - Bits 2-3: Time resolution - static (set above based on CONFIG) + * - Bit 4: TZ/DST offset support - static (0=not supported, 1=supported) + * - Bit 5: Current timeline flag - always 1 when generating time + */ + + if (IS_ENABLED(CONFIG_BT_ETS_SUPPORT_UTC)) + { + flags |= BT_ETS_FLAG_UTC; + } + + if (IS_ENABLED(CONFIG_BT_ETS_SUPPORT_TZ_DST)) + { + flags |= BT_ETS_FLAG_TZ_DST_USED; + } + + /* Always set current timeline flag */ + + flags |= BT_ETS_FLAG_CURRENT_TIMELINE; + + /* Fill the structure */ + + et_time->flags = flags; + sys_put_le48(ets_time_value, et_time->time_value); + et_time->time_sync_src = time_src; + et_time->tz_dst_offset = + IS_ENABLED(CONFIG_BT_ETS_SUPPORT_TZ_DST) ? tz_dst_offset : 0; + + LOG_DBG("Unix->ETS: unix_ms=%lld ets_ms=%lld value=%llu src=%d offset=%d " + "flags=0x%02x", + unix_ms, + ets_ms, ets_time_value, time_src, et_time->tz_dst_offset, flags); + + return 0; +} + +#endif /* CONFIG_BT_ETS_HELPER_API */ diff --git a/libs/zblue/services/gap_svc.c b/libs/zblue/services/gap_svc.c new file mode 100644 index 0000000000000..122d560ac72e0 --- /dev/null +++ b/libs/zblue/services/gap_svc.c @@ -0,0 +1,235 @@ +/**************************************************************************** + * libs/zblue/services/gap_svc.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief GATT Generic Access Service - default implementation + */ + +/* Copyright (c) 2015-2016 Intel Corporation + * Copyright (c) 2023 Nordic Semiconductor ASA Copyright (c) 2025 Koppel + * Electronic + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include +#include + +#define BT_GATT_CENTRAL_ADDR_RES_NOT_SUPP 0 +#define BT_GATT_CENTRAL_ADDR_RES_SUPP 1 + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static ssize_t read_name(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, + uint16_t len, + uint16_t offset) +{ + const char *name = bt_get_name(); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, name, strlen(name)); +} + +#if defined(CONFIG_BT_DEVICE_NAME_GATT_WRITABLE) + +static ssize_t write_name(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, + uint16_t len, + uint16_t offset, + uint8_t flags) +{ + /* adding one to fit the terminating null character */ + + char value[CONFIG_BT_DEVICE_NAME_MAX + 1] = + { + }; + + if (offset > 0) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (len > CONFIG_BT_DEVICE_NAME_MAX) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + memcpy(value, buf, len); + + value[len] = '\0'; + + bt_set_name(value); + + return len; +} + +#endif /* CONFIG_BT_DEVICE_NAME_GATT_WRITABLE */ + +static ssize_t read_appearance(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, + uint16_t len, + uint16_t offset) +{ + uint16_t appearance = sys_cpu_to_le16(bt_get_appearance()); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &appearance, + sizeof(appearance)); +} + +#if defined(CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE) +static ssize_t write_appearance(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + const void *buf, + uint16_t len, + uint16_t offset, + uint8_t flags) +{ + uint16_t appearance; + int err; + + if (offset > 0) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (len != sizeof(appearance)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + appearance = sys_get_le16(buf); + + err = bt_set_appearance(appearance); + + if (err) + { + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + return len; +} +#endif /* CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE */ + +#if defined(CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE) +#define GAP_APPEARANCE_PROPS (BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE) +#if defined(CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE_AUTHEN) +#define GAP_APPEARANCE_PERMS (BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_AUTHEN) +#elif defined(CONFIG_BT_DEVICE_APPEARANCE_GATT_WRITABLE_ENCRYPT) +#define GAP_APPEARANCE_PERMS (BT_GATT_PERM_READ | BT_GATT_PERM_WRITE_ENCRYPT) +#else +#define GAP_APPEARANCE_PERMS (BT_GATT_PERM_READ | BT_GATT_PERM_WRITE) +#endif +#define GAP_APPEARANCE_WRITE_HANDLER write_appearance +#else +#define GAP_APPEARANCE_PROPS BT_GATT_CHRC_READ +#define GAP_APPEARANCE_PERMS BT_GATT_PERM_READ +#define GAP_APPEARANCE_WRITE_HANDLER NULL +#endif + +#if defined(CONFIG_BT_GAP_PERIPHERAL_PREF_PARAMS) +static ssize_t read_ppcp(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, + uint16_t len, + uint16_t offset) +{ + struct __packed + { + uint16_t min_int; + uint16_t max_int; + uint16_t latency; + uint16_t timeout; + } ppcp; + + ppcp.min_int = sys_cpu_to_le16(CONFIG_BT_PERIPHERAL_PREF_MIN_INT); + ppcp.max_int = sys_cpu_to_le16(CONFIG_BT_PERIPHERAL_PREF_MAX_INT); + ppcp.latency = sys_cpu_to_le16(CONFIG_BT_PERIPHERAL_PREF_LATENCY); + ppcp.timeout = sys_cpu_to_le16(CONFIG_BT_PERIPHERAL_PREF_TIMEOUT); + + return bt_gatt_attr_read(conn, + attr, + buf, + len, + offset, + &ppcp, + sizeof(ppcp)); +} +#endif + +#if defined(CONFIG_BT_CENTRAL) && defined(CONFIG_BT_PRIVACY) +static ssize_t read_central_addr_res(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, + uint16_t len, + uint16_t offset) +{ + uint8_t central_addr_res = BT_GATT_CENTRAL_ADDR_RES_SUPP; + + return bt_gatt_attr_read(conn, attr, buf, len, offset, ¢ral_addr_res, + sizeof(central_addr_res)); +} +#endif /* CONFIG_BT_CENTRAL && CONFIG_BT_PRIVACY */ + +BT_GATT_SERVICE_DEFINE(_2_gap_svc, BT_GATT_PRIMARY_SERVICE(BT_UUID_GAP), +#if defined(CONFIG_BT_DEVICE_NAME_GATT_WRITABLE) +/* Require pairing for writes to device name */ + + BT_GATT_CHARACTERISTIC(BT_UUID_GAP_DEVICE_NAME, + BT_GATT_CHRC_READ | + BT_GATT_CHRC_WRITE, + BT_GATT_PERM_READ | +#if defined(CONFIG_BT_DEVICE_NAME_GATT_WRITABLE_AUTHEN) + BT_GATT_PERM_WRITE_AUTHEN, +#elif defined(CONFIG_BT_DEVICE_NAME_GATT_WRITABLE_ENCRYPT) + BT_GATT_PERM_WRITE_ENCRYPT, +#else + BT_GATT_PERM_WRITE, +#endif + read_name, write_name, NULL), +#else + BT_GATT_CHARACTERISTIC(BT_UUID_GAP_DEVICE_NAME, + BT_GATT_CHRC_READ, + + BT_GATT_PERM_READ, read_name, + NULL, NULL), +#endif /* CONFIG_BT_DEVICE_NAME_GATT_WRITABLE */ + BT_GATT_CHARACTERISTIC(BT_UUID_GAP_APPEARANCE, + GAP_APPEARANCE_PROPS, + + GAP_APPEARANCE_PERMS, + read_appearance, + GAP_APPEARANCE_WRITE_HANDLER, + NULL), +#if defined(CONFIG_BT_CENTRAL) && defined(CONFIG_BT_PRIVACY) + BT_GATT_CHARACTERISTIC(BT_UUID_CENTRAL_ADDR_RES, + BT_GATT_CHRC_READ, + + BT_GATT_PERM_READ, + read_central_addr_res, + NULL, NULL), +#endif /* CONFIG_BT_CENTRAL && CONFIG_BT_PRIVACY */ +#if defined(CONFIG_BT_GAP_PERIPHERAL_PREF_PARAMS) + BT_GATT_CHARACTERISTIC(BT_UUID_GAP_PPCP, + BT_GATT_CHRC_READ, + + BT_GATT_PERM_READ, read_ppcp, + NULL, NULL), +#endif +); diff --git a/libs/zblue/services/hrs.c b/libs/zblue/services/hrs.c new file mode 100644 index 0000000000000..eafe37e60e508 --- /dev/null +++ b/libs/zblue/services/hrs.c @@ -0,0 +1,211 @@ +/**************************************************************************** + * libs/zblue/services/hrs.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief HRS Service sample + */ + +/* Copyright (c) 2016 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define LOG_LEVEL CONFIG_BT_HRS_LOG_LEVEL +#include +LOG_MODULE_REGISTER(hrs); + +#define GATT_PERM_READ_MASK \ + (BT_GATT_PERM_READ | BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_READ_AUTHEN) +#define GATT_PERM_WRITE_MASK \ + (BT_GATT_PERM_WRITE | BT_GATT_PERM_WRITE_ENCRYPT | BT_GATT_PERM_WRITE_AUTHEN) + +#ifndef CONFIG_BT_HRS_DEFAULT_PERM_RW_AUTHEN +#define CONFIG_BT_HRS_DEFAULT_PERM_RW_AUTHEN 0 +#endif +#ifndef CONFIG_BT_HRS_DEFAULT_PERM_RW_ENCRYPT +#define CONFIG_BT_HRS_DEFAULT_PERM_RW_ENCRYPT 0 +#endif +#ifndef CONFIG_BT_HRS_DEFAULT_PERM_RW +#define CONFIG_BT_HRS_DEFAULT_PERM_RW 0 +#endif + +/** + * @brief GATT ATTR Error that should be returned in case + * HRS Control point request is not supported. + */ +#define BT_HRS_ATT_ERR_CONTROL_POINT_NOT_SUPPORTED 0x80 + +#define HRS_GATT_PERM_DEFAULT \ + (CONFIG_BT_HRS_DEFAULT_PERM_RW_AUTHEN ? (BT_GATT_PERM_READ_AUTHEN | BT_GATT_PERM_WRITE_AUTHEN) \ + : CONFIG_BT_HRS_DEFAULT_PERM_RW_ENCRYPT \ + ? (BT_GATT_PERM_READ_ENCRYPT | BT_GATT_PERM_WRITE_ENCRYPT) \ + : (BT_GATT_PERM_READ | BT_GATT_PERM_WRITE)) + +static uint8_t hrs_blsc; +static struct list_node hrs_cbs = LIST_INITIAL_VALUE(hrs_cbs); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void hrmc_ccc_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + ARG_UNUSED(attr); + + struct bt_hrs_cb *listener; + + bool notif_enabled = (value == BT_GATT_CCC_NOTIFY); + + LOG_INF("HRS notifications %s", notif_enabled ? "enabled" : "disabled"); + + list_for_every_container(&hrs_cbs, listener, _node) + { + if (listener->ntf_changed) + { + listener->ntf_changed(notif_enabled); + } + } +} + +static ssize_t read_blsc( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + return bt_gatt_attr_read(conn, attr, buf, len, offset, &hrs_blsc, + sizeof(hrs_blsc)); +} + +static ssize_t ctrl_point_write( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + int err = -ENOTSUP; + struct bt_hrs_cb *listener; + + LOG_INF("HRS CTRL Point Written %d", len); + + list_for_every_container(&hrs_cbs, listener, _node) + { + if (listener->ctrl_point_write) + { + err = listener->ctrl_point_write(*((uint8_t *)buf)); + /* If we get an error other than ENOTSUP then immediately + * break the loop and return a generic gatt error, assuming this + * listener supports this request code, but failed to serve it + */ + + if ((err != 0) && (err != -ENOTSUP)) + { + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + } + } + + if (err) + { + return BT_GATT_ERR(BT_HRS_ATT_ERR_CONTROL_POINT_NOT_SUPPORTED); + } + else + { + return len; + } +} + +/* Heart Rate Service Declaration */ + +BT_GATT_SERVICE_DEFINE(hrs_svc, BT_GATT_PRIMARY_SERVICE(BT_UUID_HRS), + BT_GATT_CHARACTERISTIC(BT_UUID_HRS_MEASUREMENT, + BT_GATT_CHRC_NOTIFY, + + BT_GATT_PERM_NONE, NULL, NULL, + NULL), + BT_GATT_CCC(hrmc_ccc_cfg_changed, + HRS_GATT_PERM_DEFAULT), + BT_GATT_CHARACTERISTIC(BT_UUID_HRS_BODY_SENSOR, + BT_GATT_CHRC_READ, + + HRS_GATT_PERM_DEFAULT &GATT_PERM_READ_MASK, + read_blsc, + NULL, NULL), + BT_GATT_CHARACTERISTIC(BT_UUID_HRS_CONTROL_POINT, + BT_GATT_CHRC_WRITE, + + HRS_GATT_PERM_DEFAULT &GATT_PERM_WRITE_MASK, + NULL, + ctrl_point_write, NULL), + ); + +static int hrs_init(void) +{ + hrs_blsc = 0x01; + + return 0; +} + +int bt_hrs_cb_register(struct bt_hrs_cb *cb) +{ + if (cb == NULL) + { + return -EINVAL; + } + + list_add_tail(&hrs_cbs, &cb->_node); + + return 0; +} + +int bt_hrs_cb_unregister(struct bt_hrs_cb *cb) +{ + if (cb == NULL) + { + return -EINVAL; + } + + if (!list_find_and_remove(&hrs_cbs, &cb->_node)) + { + return -ENOENT; + } + + return 0; +} + +int bt_hrs_notify(uint16_t heartrate) +{ + int rc; + static uint8_t hrm[2]; + + hrm[0] = 0x06; /* uint8, sensor contact */ + hrm[1] = heartrate; + + rc = bt_gatt_notify(NULL, &hrs_svc.attrs[1], &hrm, sizeof(hrm)); + + return rc == -ENOTCONN ? 0 : rc; +} + +SYS_INIT(hrs_init, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/libs/zblue/services/ias/CMakeLists.txt b/libs/zblue/services/ias/CMakeLists.txt new file mode 100644 index 0000000000000..3ca111f35599c --- /dev/null +++ b/libs/zblue/services/ias/CMakeLists.txt @@ -0,0 +1,25 @@ +# ############################################################################## +# libs/zblue/services/ias/CMakeLists.txt +# +# Author: chao an +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +# IAS sources are added by libs/zblue/services/CMakeLists.txt. diff --git a/libs/zblue/services/ias/Kconfig.ias b/libs/zblue/services/ias/Kconfig.ias new file mode 100644 index 0000000000000..c48fec8d6098c --- /dev/null +++ b/libs/zblue/services/ias/Kconfig.ias @@ -0,0 +1,44 @@ +############################################################################ +# libs/zblue/services/ias/Kconfig.ias +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth GATT Immediate Alert Service + +# Copyright (c) 2022 Codecoup +# SPDX-License-Identifier: Apache-2.0 + +menuconfig BT_IAS + bool "Support for GATT Immediate Alert Service [EXPERIMENTAL]" + +if BT_IAS + +choice BT_IAS_SEC_CHOICE + prompt "Default permissions used for IAS characteristics" + default BT_IAS_SEC_NONE + help + Default write permissions for IAS characteristic attributes + +config BT_IAS_SEC_NONE + bool "No security required" + +config BT_IAS_SEC_ENC + bool "Require encryption for write access" + +config BT_IAS_SEC_AUTH + bool "Require encryption and authentication for write access" + +endchoice #BT_IAS_SEC_CHOICE + +endif # BT_IAS + +#### Immediate Alert Service Client ################################ + +config BT_IAS_CLIENT + bool "Immediate Alert Service Client [Experimental]" + select BT_GATT_CLIENT + help + This option enables support for Immediate Alert Service Client diff --git a/libs/zblue/services/ias/ias.c b/libs/zblue/services/ias/ias.c new file mode 100644 index 0000000000000..3208aece65e1b --- /dev/null +++ b/libs/zblue/services/ias/ias.c @@ -0,0 +1,188 @@ +/**************************************************************************** + * libs/zblue/services/ias/ias.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Immediate Alert Service implementation + */ + +/* Copyright (c) 2022 Codecoup + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LOG_LEVEL CONFIG_BT_IAS_LOG_LEVEL +LOG_MODULE_REGISTER(ias); + +#define BT_IAS_ALERT_LVL_LEN 1 + +#if defined(CONFIG_BT_IAS_SEC_AUTH) +#define IAS_ALERT_LEVEL_PERM BT_GATT_PERM_WRITE_AUTHEN +#elif defined(CONFIG_BT_IAS_SEC_ENC) +#define IAS_ALERT_LEVEL_PERM BT_GATT_PERM_WRITE_ENCRYPT +#else +#define IAS_ALERT_LEVEL_PERM BT_GATT_PERM_WRITE +#endif + +struct alerting_device +{ + enum bt_ias_alert_lvl alert_level; +}; + +static struct alerting_device devices[CONFIG_BT_MAX_CONN]; +static enum bt_ias_alert_lvl curr_lvl; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void set_alert_level(void) +{ + enum bt_ias_alert_lvl alert_level = BT_IAS_ALERT_LVL_NO_ALERT; + + /* Set the alert_level as the highest requested by any device */ + + for (int i = 0; i < CONFIG_BT_MAX_CONN; i++) + { + if (alert_level < devices[i].alert_level) + { + alert_level = devices[i].alert_level; + } + } + + if (curr_lvl == alert_level) + { + return; + } + + curr_lvl = alert_level; + + if (alert_level == BT_IAS_ALERT_LVL_HIGH_ALERT) + { + STRUCT_SECTION_FOREACH(bt_ias_cb, cb) + { + if (cb->high_alert) + { + cb->high_alert(); + } + } + + LOG_DBG("High alert"); + } + else if (alert_level == BT_IAS_ALERT_LVL_MILD_ALERT) + { + STRUCT_SECTION_FOREACH(bt_ias_cb, cb) + { + if (cb->mild_alert) + { + cb->mild_alert(); + } + } + + LOG_DBG("Mild alert"); + } + else + { + STRUCT_SECTION_FOREACH(bt_ias_cb, cb) + { + if (cb->no_alert) + { + cb->no_alert(); + } + } + + LOG_DBG("No alert"); + } +} + +static void disconnected(struct bt_conn *conn, uint8_t reason) +{ + devices[bt_conn_index(conn)].alert_level = BT_IAS_ALERT_LVL_NO_ALERT; + set_alert_level(); +} + +int bt_ias_local_alert_stop(void) +{ + if (curr_lvl == BT_IAS_ALERT_LVL_NO_ALERT) + { + return -EALREADY; + } + + for (int idx = 0; idx < CONFIG_BT_MAX_CONN; idx++) + { + devices[idx].alert_level = BT_IAS_ALERT_LVL_NO_ALERT; + } + + curr_lvl = BT_IAS_ALERT_LVL_NO_ALERT; + set_alert_level(); + + return 0; +} + +static ssize_t bt_ias_write_alert_lvl(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 data; + enum bt_ias_alert_lvl alert_val; + + if (offset > 0) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (len != BT_IAS_ALERT_LVL_LEN) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + net_buf_simple_init_with_data(&data, (void *)buf, len); + alert_val = net_buf_simple_pull_u8(&data); + devices[bt_conn_index(conn)].alert_level = alert_val; + + if (alert_val < BT_IAS_ALERT_LVL_NO_ALERT || + alert_val > BT_IAS_ALERT_LVL_HIGH_ALERT) + { + return BT_GATT_ERR(BT_ATT_ERR_VALUE_NOT_ALLOWED); + } + + set_alert_level(); + + return len; +} + +BT_CONN_CB_DEFINE(conn_callbacks) = +{ + .disconnected = disconnected, +}; + +/* Immediate Alert Service Declaration */ + +BT_GATT_SERVICE_DEFINE(ias_svc, BT_GATT_PRIMARY_SERVICE(BT_UUID_IAS), + BT_GATT_CHARACTERISTIC(BT_UUID_ALERT_LEVEL, + BT_GATT_CHRC_WRITE_WITHOUT_RESP, + + IAS_ALERT_LEVEL_PERM, NULL, + bt_ias_write_alert_lvl, + NULL)); diff --git a/libs/zblue/services/ias/ias_client.c b/libs/zblue/services/ias/ias_client.c new file mode 100644 index 0000000000000..2488c5276f9e6 --- /dev/null +++ b/libs/zblue/services/ias/ias_client.c @@ -0,0 +1,230 @@ +/**************************************************************************** + * libs/zblue/services/ias/ias_client.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2022 Codecoup + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include + +LOG_MODULE_REGISTER(bt_ias_client, CONFIG_BT_IAS_CLIENT_LOG_LEVEL); + +#include +#include +#include + +enum +{ + IAS_DISCOVER_IN_PROGRESS, + + IAS_NUM_FLAGS, /* keep as last */ +}; + +struct bt_ias_client +{ + /* Handle for alert writes */ + + uint16_t alert_level_handle; + + /** Internal flags **/ + + atomic_define(flags, IAS_NUM_FLAGS); + + /* Gatt discover procedure parameters */ + + struct bt_gatt_discover_params discover; +}; + +static const struct bt_uuid *alert_lvl_uuid = BT_UUID_ALERT_LEVEL; +static const struct bt_uuid *ias_uuid = BT_UUID_IAS; + +static const struct bt_ias_client_cb *ias_client_cb; +static struct bt_ias_client client_list[CONFIG_BT_MAX_CONN]; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static struct bt_ias_client *client_by_conn(struct bt_conn *conn) +{ + return &client_list[bt_conn_index(conn)]; +} + +static void client_cleanup(struct bt_ias_client *ias_client) +{ + (void)memset(ias_client, 0, sizeof(*ias_client)); +} + +static void discover_complete(struct bt_conn *conn, int err) +{ + LOG_DBG("conn %p", (void *)conn); + + if (err) + { + client_cleanup(client_by_conn(conn)); + LOG_DBG("Discover failed (err %d\n)", err); + } + + if (ias_client_cb != NULL && ias_client_cb->discover != NULL) + { + ias_client_cb->discover(conn, err); + } +} + +int bt_ias_client_alert_write(struct bt_conn *conn, + enum bt_ias_alert_lvl lvl) +{ + int err; + uint8_t lvl_u8; + + if (conn == NULL) + { + return -ENOTCONN; + } + + if (client_by_conn(conn)->alert_level_handle == 0) + { + return -EINVAL; + } + + lvl_u8 = (uint8_t)lvl; + + if (lvl_u8 != BT_IAS_ALERT_LVL_NO_ALERT && + lvl_u8 != BT_IAS_ALERT_LVL_MILD_ALERT && + lvl_u8 != BT_IAS_ALERT_LVL_HIGH_ALERT) + { + LOG_ERR("Invalid alert value: %u", lvl_u8); + return -EINVAL; + } + + err = bt_gatt_write_without_response(conn, + client_by_conn(conn)->alert_level_handle, &lvl_u8, + sizeof(lvl_u8), false); + if (err < 0) + { + LOG_ERR("IAS client level %d write failed: %d", lvl, err); + } + + return err; +} + +static uint8_t bt_ias_alert_lvl_disc_cb( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *discover) +{ + const struct bt_gatt_chrc *chrc; + + atomic_clear_bit(client_by_conn(conn)->flags, IAS_DISCOVER_IN_PROGRESS); + + if (attr == NULL) + { + discover_complete(conn, -ENOENT); + + return BT_GATT_ITER_STOP; + } + + chrc = (struct bt_gatt_chrc *)attr->user_data; + + client_by_conn(conn)->alert_level_handle = chrc->value_handle; + discover_complete(conn, 0); + + return BT_GATT_ITER_STOP; +} + +static uint8_t bt_ias_prim_disc_cb( + struct bt_conn *conn, const struct bt_gatt_attr *attr, + struct bt_gatt_discover_params *discover) +{ + int err; + const struct bt_gatt_service_val *data; + struct bt_ias_client *client = client_by_conn(conn); + + if (!attr) + { + discover_complete(conn, -ENOENT); + + return BT_GATT_ITER_STOP; + } + + data = attr->user_data; + + client->discover.uuid = alert_lvl_uuid; + client->discover.start_handle = attr->handle + 1; + client->discover.end_handle = data->end_handle; + client->discover.type = BT_GATT_DISCOVER_CHARACTERISTIC; + client->discover.func = bt_ias_alert_lvl_disc_cb; + + err = bt_gatt_discover(conn, &client->discover); + if (err) + { + discover_complete(conn, err); + } + + return BT_GATT_ITER_STOP; +} + +int bt_ias_discover(struct bt_conn *conn) +{ + int err; + struct bt_ias_client *client = client_by_conn(conn); + + if (!conn || !ias_client_cb || !ias_client_cb->discover) + { + return -EINVAL; + } + + if (atomic_test_bit(client->flags, IAS_DISCOVER_IN_PROGRESS)) + { + return -EBUSY; + } + + client_cleanup(client); + atomic_set_bit(client->flags, IAS_DISCOVER_IN_PROGRESS); + + client->discover.uuid = ias_uuid; + client->discover.func = bt_ias_prim_disc_cb; + client->discover.start_handle = BT_ATT_FIRST_ATTRIBUTE_HANDLE; + client->discover.end_handle = BT_ATT_LAST_ATTRIBUTE_HANDLE; + client->discover.type = BT_GATT_DISCOVER_PRIMARY; + + err = bt_gatt_discover(conn, &client->discover); + if (err < 0) + { + discover_complete(conn, err); + } + + return err; +} + +int bt_ias_client_cb_register(const struct bt_ias_client_cb *cb) +{ + if (!cb) + { + return -EINVAL; + } + + if (cb->discover == NULL) + { + return -EINVAL; + } + + if (ias_client_cb) + { + return -EALREADY; + } + + ias_client_cb = cb; + + return 0; +} diff --git a/libs/zblue/services/nus/CMakeLists.txt b/libs/zblue/services/nus/CMakeLists.txt new file mode 100644 index 0000000000000..81aa54d67e0f9 --- /dev/null +++ b/libs/zblue/services/nus/CMakeLists.txt @@ -0,0 +1,25 @@ +# ############################################################################## +# libs/zblue/services/nus/CMakeLists.txt +# +# Author: chao an +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +# NUS sources are added by libs/zblue/services/CMakeLists.txt. diff --git a/libs/zblue/services/nus/Kconfig.nus b/libs/zblue/services/nus/Kconfig.nus new file mode 100644 index 0000000000000..f7345e4a40283 --- /dev/null +++ b/libs/zblue/services/nus/Kconfig.nus @@ -0,0 +1,34 @@ +############################################################################ +# libs/zblue/services/nus/Kconfig.nus +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Copyright (c) 2024 Croxel, Inc. +# SPDX-License-Identifier: Apache-2.0 + +menuconfig BT_ZEPHYR_NUS + bool "GATT Nordic UART Service" + +if BT_ZEPHYR_NUS + +config BT_ZEPHYR_NUS_DEFAULT_INSTANCE + bool "Use default NUS Service instance" + default y if !UART_BT + help + Enable default Nordic UART Service Instance. Allows using the NUS as + the other services, where the Service is hosted by the subsystem itself. + If the user wishes to declare NUS instances externally by using + BT_NUS_INST_DEFINE(), it may not be beneficial having an internal + instance as well. + +config BT_ZEPHYR_NUS_AUTO_START_BLUETOOTH + bool "Auto-enable Bluetooth stack and start LE advertisements" + help + Auto-Enable the Bluetooth stack and start advertising with the NUS + UUID. Useful to run applications that inherently do not deal with + Bluetooth (e.g: Non-Bluetooth samples using UART over Bluetooth LE). + +endif # BT_ZEPHYR_NUS diff --git a/libs/zblue/services/nus/bt_nus_auto_start_bt.c b/libs/zblue/services/nus/bt_nus_auto_start_bt.c new file mode 100644 index 0000000000000..f59f923e1b839 --- /dev/null +++ b/libs/zblue/services/nus/bt_nus_auto_start_bt.c @@ -0,0 +1,1459 @@ +/**************************************************************************** + * libs/zblue/services/nus/bt_nus_auto_start_bt.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2024 Croxel, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(CONFIG_BT_A2DP_SINK) +#include +#include +#include +#endif +#if defined(CONFIG_BT_AVRCP) +#include +#endif +#include + +LOG_MODULE_REGISTER(bt_nus_auto_start); + +#define DEVICE_NAME_SEPARATOR "-BT-" +#define DEVICE_NAME_SUFFIX_LEN 4 +#define DEVICE_NAME_DYNAMIC_LEN \ + (sizeof(CONFIG_BT_DEVICE_NAME) - 1 + sizeof(DEVICE_NAME_SEPARATOR) - 1 + DEVICE_NAME_SUFFIX_LEN) + +#if defined(CONFIG_BT_DEVICE_NAME_DYNAMIC) +#define DEVICE_NAME_BUF_LEN MAX(CONFIG_BT_DEVICE_NAME_MAX, DEVICE_NAME_DYNAMIC_LEN) +#else +#define DEVICE_NAME_BUF_LEN MAX(sizeof(CONFIG_BT_DEVICE_NAME) - 1, DEVICE_NAME_DYNAMIC_LEN) +#endif + +static char device_name[DEVICE_NAME_BUF_LEN + 1]; + +#if defined(CONFIG_BT_BROADCASTER) +#if defined(CONFIG_BT_CLASSIC) +#define NUS_AD_FLAGS BT_LE_AD_GENERAL +#else +#define NUS_AD_FLAGS (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR) +#endif + +static struct bt_data ad[] = +{ + BT_DATA_BYTES(BT_DATA_FLAGS, NUS_AD_FLAGS), + BT_DATA(BT_DATA_NAME_COMPLETE, device_name, 0), +}; + +static const struct bt_data sd[] = +{ + BT_DATA_BYTES(BT_DATA_UUID128_ALL, BT_UUID_NUS_SRV_VAL), +}; +#endif + +#define BT_NUS_GATT_TEST_VALUE_MAX 32 + +static const struct bt_uuid_128 bt_nus_gatt_test_svc_uuid = + BT_UUID_INIT_128(BT_UUID_128_ENCODE(0x8e7d0001, + 0xade5, + 0x4f9f, + 0x8d15, + 0x001122334455)); + +static const struct bt_uuid_128 bt_nus_gatt_test_value_uuid = + BT_UUID_INIT_128(BT_UUID_128_ENCODE(0x8e7d0002, + 0xade5, + 0x4f9f, + 0x8d15, + 0x001122334455)); + +static const struct bt_uuid_128 bt_nus_gatt_test_count_uuid = + BT_UUID_INIT_128(BT_UUID_128_ENCODE(0x8e7d0003, + 0xade5, + 0x4f9f, + 0x8d15, + 0x001122334455)); + +static const struct bt_uuid_128 bt_nus_gatt_echo_svc_uuid = + BT_UUID_INIT_128(BT_UUID_128_ENCODE(0x8e7d0011, + 0xade5, + 0x4f9f, + 0x8d15, + 0x001122334455)); + +static const struct bt_uuid_128 bt_nus_gatt_echo_uuid = + BT_UUID_INIT_128(BT_UUID_128_ENCODE(0x8e7d0012, + 0xade5, + 0x4f9f, + 0x8d15, + 0x001122334455)); + +static uint8_t bt_nus_gatt_test_value[BT_NUS_GATT_TEST_VALUE_MAX] = +{ + 'N', 'u', 't', 't', 'X', ' ', 'G', 'A', + 'T', 'T', ' ', 'T', 'e', 's', 't' +}; + +static uint16_t bt_nus_gatt_test_value_len = 15; +static uint32_t bt_nus_gatt_test_count; + +static ssize_t bt_nus_gatt_test_read_value( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + return bt_gatt_attr_read(conn, attr, buf, len, offset, + bt_nus_gatt_test_value, + bt_nus_gatt_test_value_len); +} + +static ssize_t bt_nus_gatt_test_write_value( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + uint16_t end; + + (void)conn; + (void)attr; + + if (flags & BT_GATT_WRITE_FLAG_PREPARE) + { + return 0; + } + + if (offset > sizeof(bt_nus_gatt_test_value)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + end = offset + len; + if (end > sizeof(bt_nus_gatt_test_value)) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + } + + memcpy(bt_nus_gatt_test_value + offset, buf, len); + bt_nus_gatt_test_value_len = MAX(bt_nus_gatt_test_value_len, end); + LOG_INF("GATT test value updated: len %u", bt_nus_gatt_test_value_len); + + return len; +} + +static ssize_t bt_nus_gatt_test_read_count( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + bt_nus_gatt_test_count++; + return bt_gatt_attr_read(conn, attr, buf, len, offset, + &bt_nus_gatt_test_count, + sizeof(bt_nus_gatt_test_count)); +} + +static ssize_t bt_nus_gatt_echo_write( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + uint16_t ntf_len; + int max_ntf_size; + int err; + + (void)offset; + + if (flags & BT_GATT_WRITE_FLAG_PREPARE) + { + return 0; + } + + if (!conn) + { + return len; + } + + max_ntf_size = bt_att_get_max_notify_size(conn, BT_ATT_CHAN_OPT_NONE); + if (max_ntf_size < 0) + { + return len; + } + + if (!bt_gatt_is_subscribed(conn, attr, BT_GATT_CCC_NOTIFY)) + { + return len; + } + + ntf_len = MIN(len, (uint16_t)max_ntf_size); + err = bt_gatt_notify(conn, attr, buf, ntf_len); + if (err) + { + LOG_WRN("Failed to notify GATT echo data (err %d)", err); + } + + return len; +} + +BT_GATT_SERVICE_DEFINE(bt_nus_gatt_test_svc, + BT_GATT_PRIMARY_SERVICE(&bt_nus_gatt_test_svc_uuid.uuid), + BT_GATT_CHARACTERISTIC(&bt_nus_gatt_test_value_uuid.uuid, + BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE, + BT_GATT_PERM_READ | BT_GATT_PERM_WRITE, + bt_nus_gatt_test_read_value, + bt_nus_gatt_test_write_value, NULL), + BT_GATT_CHARACTERISTIC(&bt_nus_gatt_test_count_uuid.uuid, + BT_GATT_CHRC_READ, BT_GATT_PERM_READ, + bt_nus_gatt_test_read_count, NULL, NULL), + BT_GATT_CUD("NuttX demo counter +1", BT_GATT_PERM_READ), +); + +BT_GATT_SERVICE_DEFINE(bt_nus_gatt_echo_svc, + BT_GATT_PRIMARY_SERVICE(&bt_nus_gatt_echo_svc_uuid.uuid), + BT_GATT_CHARACTERISTIC(&bt_nus_gatt_echo_uuid.uuid, + BT_GATT_CHRC_WRITE_WITHOUT_RESP | + BT_GATT_CHRC_NOTIFY, + BT_GATT_PERM_WRITE, NULL, + bt_nus_gatt_echo_write, NULL), + BT_GATT_CCC(NULL, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), +); + +#if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_CLASSIC) +#if defined(CONFIG_BT_SMP_APP_PAIRING_ACCEPT) + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static enum bt_security_err bt_nus_pairing_accept(struct bt_conn *conn, + const struct bt_conn_pairing_feat *const feat) +{ + (void)feat; + + LOG_INF("Pairing accepted: %s", bt_conn_dst_str(conn)); + return BT_SECURITY_ERR_SUCCESS; +} +#endif + +static void bt_nus_auth_cancel(struct bt_conn *conn) +{ + LOG_INF("Pairing cancelled: %s", bt_conn_dst_str(conn)); +} + +static void bt_nus_pairing_confirm(struct bt_conn *conn) +{ + int err; + + LOG_INF("Pairing confirmed: %s", bt_conn_dst_str(conn)); + + err = bt_conn_auth_pairing_confirm(conn); + if (err) + { + LOG_WRN("Failed to confirm pairing for %s (err %d)", + bt_conn_dst_str(conn), err); + } +} + +#if defined(CONFIG_BT_CLASSIC) +static void bt_nus_pincode_entry(struct bt_conn *conn, bool highsec) +{ + const char *pin = highsec ? "0000000000000000" : "0000"; + int err; + + LOG_INF("BR/EDR PIN requested: %s highsec %u", bt_conn_dst_str(conn), + (unsigned int)highsec); + + err = bt_conn_auth_pincode_entry(conn, pin); + if (err) + { + LOG_WRN("Failed to enter BR/EDR PIN for %s (err %d)", + bt_conn_dst_str(conn), err); + } +} +#endif + +static const struct bt_conn_auth_cb bt_nus_auth_cb = +{ +#if defined(CONFIG_BT_SMP_APP_PAIRING_ACCEPT) + .pairing_accept = bt_nus_pairing_accept, +#endif +#if defined(CONFIG_BT_CLASSIC) + .pincode_entry = bt_nus_pincode_entry, +#endif + .cancel = bt_nus_auth_cancel, + .pairing_confirm = bt_nus_pairing_confirm, +}; + +static int bt_nus_auth_register(void) +{ + int err; + + err = bt_conn_auth_cb_register(&bt_nus_auth_cb); + if (err < 0 && err != -EALREADY) + { + LOG_ERR("Failed to register auth callbacks (err %d)", err); + return err; + } + + LOG_INF("Bluetooth auth callbacks registered"); + return 0; +} +#endif + +#if defined(CONFIG_BT_CLASSIC) +static struct bt_data eir[] = +{ + BT_DATA(BT_DATA_NAME_COMPLETE, device_name, 0), +}; + +#endif + +static int bt_nus_update_device_name(void) +{ + bt_addr_le_t addrs[CONFIG_BT_ID_MAX]; + size_t count = ARRAY_SIZE(addrs); +#if defined(CONFIG_BT_DEVICE_NAME_DYNAMIC) + int err; +#endif + int len; + + bt_id_get(addrs, &count); + if (count > 0 && !bt_addr_le_eq(&addrs[BT_ID_DEFAULT], BT_ADDR_LE_ANY)) + { + len = snprintf(device_name, sizeof(device_name), "%s%s%02X%02X", + CONFIG_BT_DEVICE_NAME, + DEVICE_NAME_SEPARATOR, addrs[BT_ID_DEFAULT].a.val[1], + addrs[BT_ID_DEFAULT].a.val[0]); + } + else + { + len = snprintf(device_name, sizeof(device_name), "%s", + CONFIG_BT_DEVICE_NAME); + } + + if (len < 0 || (size_t)len >= sizeof(device_name)) + { + return -ENOMEM; + } + +#if defined(CONFIG_BT_DEVICE_NAME_DYNAMIC) + err = bt_set_name(device_name); + + if (err < 0) + { + LOG_ERR("Failed to set Bluetooth device name (err %d)", err); + return err; + } +#endif + +#if defined(CONFIG_BT_BROADCASTER) + ad[1].data_len = strlen(device_name); +#endif +#if defined(CONFIG_BT_CLASSIC) + eir[0].data_len = strlen(device_name); +#endif + + LOG_INF("Bluetooth device name: %s", device_name); + return 0; +} + +#if defined(CONFIG_BT_CLASSIC) + +static void bt_nus_dump_buf(const char *tag, const struct net_buf *buf, + size_t max_len) +{ + size_t dump_len; + size_t offset; + + if (!buf) + { + LOG_INF("%s: buf NULL", tag); + return; + } + + dump_len = MIN((size_t)buf->len, max_len); + LOG_INF("%s: len %u dump %u", tag, buf->len, (unsigned int)dump_len); + + for (offset = 0; offset < dump_len; offset += 16) + { + const uint8_t *data = buf->data + offset; + size_t left = dump_len - offset; + size_t line_len = MIN(left, 16); + + LOG_INF("%s[%u]: %02x %02x %02x %02x %02x %02x %02x %02x " + "%02x %02x %02x %02x %02x %02x %02x %02x", + tag, (unsigned int)offset, + line_len > 0 ? data[0] : 0, + line_len > 1 ? data[1] : 0, + line_len > 2 ? data[2] : 0, + line_len > 3 ? data[3] : 0, + line_len > 4 ? data[4] : 0, + line_len > 5 ? data[5] : 0, + line_len > 6 ? data[6] : 0, + line_len > 7 ? data[7] : 0, + line_len > 8 ? data[8] : 0, + line_len > 9 ? data[9] : 0, + line_len > 10 ? data[10] : 0, + line_len > 11 ? data[11] : 0, + line_len > 12 ? data[12] : 0, + line_len > 13 ? data[13] : 0, + line_len > 14 ? data[14] : 0, + line_len > 15 ? data[15] : 0); + } +} + +#if defined(CONFIG_BT_A2DP_SINK) +#define A2DP_VERSION 0x0104u + +#define A2DP_AAC_OBJECT_TYPE_MPEG2_AAC_LC 0x80u +#define A2DP_AAC_OBJECT_TYPE_MPEG4_AAC_LC 0x40u +#define A2DP_AAC_SAMP_FREQ_48000_HI 0x10u +#define A2DP_AAC_SAMP_FREQ_44100_HI 0x08u +#define A2DP_AAC_CHANNELS_2 0x04u +#define A2DP_AAC_VBR 0x80u +#define A2DP_AAC_BIT_RATE_320000 0x04e200u + +BT_A2DP_SBC_SINK_EP_DEFAULT(bt_nus_a2dp_sink_sbc_endpoint); + +static struct bt_a2dp_codec_ie bt_nus_a2dp_sink_aac_cap = +{ + .len = BT_A2DP_MPEG_2_4_IE_LENGTH, + .codec_ie = + { + A2DP_AAC_OBJECT_TYPE_MPEG2_AAC_LC | + A2DP_AAC_OBJECT_TYPE_MPEG4_AAC_LC, + A2DP_AAC_SAMP_FREQ_48000_HI | + A2DP_AAC_SAMP_FREQ_44100_HI, + A2DP_AAC_CHANNELS_2, + A2DP_AAC_VBR | + ((A2DP_AAC_BIT_RATE_320000 >> 16) & 0x7fu), + (A2DP_AAC_BIT_RATE_320000 >> 8) & 0xffu, + A2DP_AAC_BIT_RATE_320000 & 0xffu, + }, +}; + +static struct bt_a2dp_ep bt_nus_a2dp_sink_aac_endpoint = + BT_A2DP_SINK_EP_INIT(BT_A2DP_MPEG2, + &bt_nus_a2dp_sink_aac_cap, true); + +static struct bt_a2dp_stream bt_nus_a2dp_sink_stream; + +static struct bt_sdp_attribute bt_nus_a2dp_sink_attrs[] = +{ + BT_SDP_NEW_SERVICE, + BT_SDP_LIST(BT_SDP_ATTR_SVCLASS_ID_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_AUDIO_SINK_SVCLASS) + }, + + ) + ), + + BT_SDP_LIST(BT_SDP_ATTR_PROTO_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 16), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) + }, + + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(BT_UUID_AVDTP_VAL) + }, + + ) + }, + + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_UUID_AVDTP_VAL) + }, + + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(AVDTP_VERSION) + }, + + ) + }, + + ) + ), + + BT_SDP_LIST(BT_SDP_ATTR_PROFILE_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 8), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_ADVANCED_AUDIO_SVCLASS) + }, + + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(A2DP_VERSION) + }, + + ) + }, + + ) + ), + + BT_SDP_SERVICE_NAME("A2DPSink"), + BT_SDP_SUPPORTED_FEATURES(0x0001u), +}; + +static struct bt_sdp_record bt_nus_a2dp_sink_rec = BT_SDP_RECORD( + bt_nus_a2dp_sink_attrs); + +static void bt_nus_a2dp_log_codec_cfg(const char *tag, + struct bt_a2dp_codec_cfg *codec_cfg) +{ + struct bt_a2dp_codec_ie *codec_ie; + + if (!codec_cfg) + { + LOG_INF("%s: codec cfg NULL", tag); + return; + } + + codec_ie = codec_cfg->codec_config; + LOG_INF("%s: delay_report %u codec_ie %p", tag, + codec_cfg->delay_report, codec_ie); + + if (codec_ie) + { + struct net_buf buf = + { + .data = codec_ie->codec_ie, + .len = codec_ie->len, + }; + + bt_nus_dump_buf(tag, (const struct net_buf *)&buf, + codec_ie->len); + } +} + +static void bt_nus_a2dp_connected(struct bt_a2dp *a2dp, int err) +{ + LOG_INF("A2DP sink connected callback: a2dp %p err %d", a2dp, err); + + if (err) + { + LOG_WRN("A2DP sink connect failed (err %d)", err); + return; + } + + LOG_INF("A2DP sink connected"); +} + +static void bt_nus_a2dp_disconnected(struct bt_a2dp *a2dp) +{ + LOG_INF("A2DP sink disconnected callback: a2dp %p", a2dp); +} + +static void bt_nus_a2dp_stream_configured(struct bt_a2dp_stream *stream) +{ + LOG_INF("A2DP sink stream configured callback: stream %p", stream); + + { + struct net_buf buf = + { + .data = stream->codec_config.codec_ie, + .len = stream->codec_config.len, + }; + + bt_nus_dump_buf("A2DP stream codec config", + (const struct net_buf *)&buf, + stream->codec_config.len); + } +} + +static void bt_nus_a2dp_stream_established(struct bt_a2dp_stream *stream) +{ + LOG_INF("A2DP sink stream established callback: stream %p", stream); +} + +static void bt_nus_a2dp_stream_released(struct bt_a2dp_stream *stream) +{ + LOG_INF("A2DP sink stream released callback: stream %p", stream); +} + +static void bt_nus_a2dp_stream_started(struct bt_a2dp_stream *stream) +{ + LOG_INF("A2DP sink stream started callback: stream %p", stream); +} + +static void bt_nus_a2dp_stream_suspended(struct bt_a2dp_stream *stream) +{ + LOG_INF("A2DP sink stream suspended callback: stream %p", stream); +} + +static void bt_nus_a2dp_stream_recv( + struct bt_a2dp_stream *stream, struct net_buf *buf, uint16_t seq_num, + uint32_t ts) +{ + LOG_INF("A2DP sink media recv callback: stream %p seq %u ts %u len %u", + stream, seq_num, (unsigned int)ts, buf ? buf->len : 0); + bt_nus_dump_buf("A2DP media", buf, 32); +} + +static struct bt_a2dp_stream_ops bt_nus_a2dp_stream_ops = +{ + .configured = bt_nus_a2dp_stream_configured, + .established = bt_nus_a2dp_stream_established, + .released = bt_nus_a2dp_stream_released, + .started = bt_nus_a2dp_stream_started, + .suspended = bt_nus_a2dp_stream_suspended, + .recv = bt_nus_a2dp_stream_recv, +}; + +static int bt_nus_a2dp_config_req( + struct bt_a2dp *a2dp, struct bt_a2dp_ep *ep, + struct bt_a2dp_codec_cfg *codec_cfg, struct bt_a2dp_stream **stream, + uint8_t *rsp_err_code) +{ + bt_a2dp_stream_cb_register(&bt_nus_a2dp_sink_stream, + &bt_nus_a2dp_stream_ops); + *stream = &bt_nus_a2dp_sink_stream; + *rsp_err_code = 0; + + LOG_INF("A2DP sink config_req callback: a2dp %p ep %p stream %p", + a2dp, ep, *stream); + bt_nus_a2dp_log_codec_cfg("A2DP config_req codec", codec_cfg); + return 0; +} + +static int bt_nus_a2dp_accept_req(const char *name, + struct bt_a2dp_stream *stream, + uint8_t *rsp_err_code) +{ + *rsp_err_code = 0; + LOG_INF("A2DP sink %s callback: stream %p", name, stream); + return 0; +} + +static int bt_nus_a2dp_establish_req(struct bt_a2dp_stream *stream, + uint8_t *rsp_err_code) +{ + return bt_nus_a2dp_accept_req("establish_req", stream, rsp_err_code); +} + +static int bt_nus_a2dp_release_req(struct bt_a2dp_stream *stream, + uint8_t *rsp_err_code) +{ + return bt_nus_a2dp_accept_req("release_req", stream, rsp_err_code); +} + +static int bt_nus_a2dp_start_req(struct bt_a2dp_stream *stream, + uint8_t *rsp_err_code) +{ + return bt_nus_a2dp_accept_req("start_req", stream, rsp_err_code); +} + +static int bt_nus_a2dp_suspend_req(struct bt_a2dp_stream *stream, + uint8_t *rsp_err_code) +{ + return bt_nus_a2dp_accept_req("suspend_req", stream, rsp_err_code); +} + +static int bt_nus_a2dp_abort_req(struct bt_a2dp_stream *stream, + uint8_t *rsp_err_code) +{ + return bt_nus_a2dp_accept_req("abort_req", stream, rsp_err_code); +} + +static int bt_nus_a2dp_get_config_req(struct bt_a2dp_stream *stream, + uint8_t *rsp_err_code) +{ + return bt_nus_a2dp_accept_req("get_config_req", stream, rsp_err_code); +} + +static int bt_nus_a2dp_reconfig_req(struct bt_a2dp_stream *stream, + struct bt_a2dp_codec_cfg *codec_cfg, + uint8_t *rsp_err_code) +{ + *rsp_err_code = 0; + LOG_INF("A2DP sink reconfig_req callback: stream %p", stream); + bt_nus_a2dp_log_codec_cfg("A2DP reconfig_req codec", codec_cfg); + return 0; +} + +static void bt_nus_a2dp_config_rsp(struct bt_a2dp_stream *stream, + uint8_t rsp_err_code) +{ + LOG_INF("A2DP sink config_rsp callback: stream %p rsp 0x%02x", + stream, rsp_err_code); +} + +static void bt_nus_a2dp_rsp(const char *name, struct bt_a2dp_stream *stream, + uint8_t rsp_err_code) +{ + LOG_INF("A2DP sink %s callback: stream %p rsp 0x%02x", name, stream, + rsp_err_code); +} + +static void bt_nus_a2dp_establish_rsp(struct bt_a2dp_stream *stream, + uint8_t rsp_err_code) +{ + bt_nus_a2dp_rsp("establish_rsp", stream, rsp_err_code); +} + +static void bt_nus_a2dp_release_rsp(struct bt_a2dp_stream *stream, + uint8_t rsp_err_code) +{ + bt_nus_a2dp_rsp("release_rsp", stream, rsp_err_code); +} + +static void bt_nus_a2dp_start_rsp(struct bt_a2dp_stream *stream, + uint8_t rsp_err_code) +{ + bt_nus_a2dp_rsp("start_rsp", stream, rsp_err_code); +} + +static void bt_nus_a2dp_suspend_rsp(struct bt_a2dp_stream *stream, + uint8_t rsp_err_code) +{ + bt_nus_a2dp_rsp("suspend_rsp", stream, rsp_err_code); +} + +static void bt_nus_a2dp_abort_rsp(struct bt_a2dp_stream *stream, + uint8_t rsp_err_code) +{ + bt_nus_a2dp_rsp("abort_rsp", stream, rsp_err_code); +} + +static void bt_nus_a2dp_get_config_rsp( + struct bt_a2dp_stream *stream, struct bt_a2dp_codec_cfg *codec_cfg, + uint8_t rsp_err_code) +{ + LOG_INF("A2DP sink get_config_rsp callback: stream %p rsp 0x%02x", + stream, rsp_err_code); + bt_nus_a2dp_log_codec_cfg("A2DP get_config_rsp codec", codec_cfg); +} + +static void bt_nus_a2dp_delay_report_rsp( + struct bt_a2dp_stream *stream, uint8_t rsp_err_code) +{ + bt_nus_a2dp_rsp("delay_report_rsp", stream, rsp_err_code); +} + +static struct bt_a2dp_cb bt_nus_a2dp_cb = +{ + .connected = bt_nus_a2dp_connected, + .disconnected = bt_nus_a2dp_disconnected, + .config_req = bt_nus_a2dp_config_req, + .reconfig_req = bt_nus_a2dp_reconfig_req, + .config_rsp = bt_nus_a2dp_config_rsp, + .establish_req = bt_nus_a2dp_establish_req, + .establish_rsp = bt_nus_a2dp_establish_rsp, + .release_req = bt_nus_a2dp_release_req, + .release_rsp = bt_nus_a2dp_release_rsp, + .start_req = bt_nus_a2dp_start_req, + .start_rsp = bt_nus_a2dp_start_rsp, + .suspend_req = bt_nus_a2dp_suspend_req, + .suspend_rsp = bt_nus_a2dp_suspend_rsp, + .abort_req = bt_nus_a2dp_abort_req, + .abort_rsp = bt_nus_a2dp_abort_rsp, + .get_config_req = bt_nus_a2dp_get_config_req, + .get_config_rsp = bt_nus_a2dp_get_config_rsp, + .delay_report_rsp = bt_nus_a2dp_delay_report_rsp, +}; + +static int bt_nus_a2dp_sink_register(void) +{ + int err; + + err = bt_sdp_register_service(&bt_nus_a2dp_sink_rec); + if (err < 0 && err != -EEXIST) + { + LOG_ERR("Failed to register A2DP sink SDP record (err %d)", err); + return err; + } + + err = bt_a2dp_register_cb(&bt_nus_a2dp_cb); + if (err < 0 && err != -EALREADY) + { + LOG_ERR("Failed to register A2DP sink callbacks (err %d)", err); + return err; + } + + err = bt_a2dp_register_ep(&bt_nus_a2dp_sink_sbc_endpoint, BT_AVDTP_AUDIO, + BT_AVDTP_SINK); + if (err < 0 && err != -EALREADY) + { + LOG_ERR("Failed to register A2DP SBC sink endpoint (err %d)", err); + return err; + } + + LOG_INF("A2DP SBC sink endpoint registered"); + + err = bt_a2dp_register_ep(&bt_nus_a2dp_sink_aac_endpoint, BT_AVDTP_AUDIO, + BT_AVDTP_SINK); + if (err < 0 && err != -EALREADY) + { + LOG_ERR("Failed to register A2DP AAC sink endpoint (err %d)", err); + return err; + } + + LOG_INF("A2DP AAC sink endpoint registered"); + return 0; +} +#endif + +#if defined(CONFIG_BT_AVRCP) +static void bt_nus_avrcp_log_event_data(const char *tag, uint8_t event_id, + struct bt_avrcp_event_data *data) +{ + if (!data) + { + LOG_INF("%s: event 0x%02x data NULL", tag, event_id); + return; + } + + switch (event_id) + { + case BT_AVRCP_EVT_PLAYBACK_STATUS_CHANGED: + LOG_INF("%s: playback status 0x%02x", tag, data->play_status); + break; + case BT_AVRCP_EVT_TRACK_CHANGED: + LOG_INF("%s: track id %02x%02x%02x%02x%02x%02x%02x%02x", tag, + data->identifier[0], data->identifier[1], + data->identifier[2], data->identifier[3], + data->identifier[4], data->identifier[5], + data->identifier[6], data->identifier[7]); + break; + case BT_AVRCP_EVT_PLAYBACK_POS_CHANGED: + LOG_INF("%s: playback pos %u", tag, + (unsigned int)data->playback_pos); + break; + case BT_AVRCP_EVT_BATT_STATUS_CHANGED: + LOG_INF("%s: battery status 0x%02x", tag, data->battery_status); + break; + case BT_AVRCP_EVT_SYSTEM_STATUS_CHANGED: + LOG_INF("%s: system status 0x%02x", tag, data->system_status); + break; + case BT_AVRCP_EVT_PLAYER_APP_SETTING_CHANGED: + LOG_INF("%s: player app setting count %u", tag, + data->setting_changed.num_of_attr); + break; + case BT_AVRCP_EVT_ADDRESSED_PLAYER_CHANGED: + LOG_INF("%s: addressed player id %u uid_counter %u", tag, + data->addressed_player_changed.player_id, + data->addressed_player_changed.uid_counter); + break; + case BT_AVRCP_EVT_UIDS_CHANGED: + LOG_INF("%s: uid_counter %u", tag, data->uid_counter); + break; + case BT_AVRCP_EVT_VOLUME_CHANGED: + LOG_INF("%s: absolute volume %u", tag, data->absolute_volume); + break; + default: + LOG_INF("%s: event 0x%02x", tag, event_id); + break; + } +} + +#if defined(CONFIG_BT_AVRCP_CONTROLLER) +static void bt_nus_avrcp_ct_connected(struct bt_conn *conn, + struct bt_avrcp_ct *ct) +{ + LOG_INF("AVRCP CT connected callback: conn %s ct %p", + bt_conn_dst_str(conn), ct); +} + +static void bt_nus_avrcp_ct_disconnected(struct bt_avrcp_ct *ct) +{ + LOG_INF("AVRCP CT disconnected callback: ct %p", ct); +} + +static void bt_nus_avrcp_ct_browsing_connected(struct bt_conn *conn, + struct bt_avrcp_ct *ct) +{ + LOG_INF("AVRCP CT browsing_connected callback: conn %s ct %p", + bt_conn_dst_str(conn), ct); +} + +static void bt_nus_avrcp_ct_browsing_disconnected(struct bt_avrcp_ct *ct) +{ + LOG_INF("AVRCP CT browsing_disconnected callback: ct %p", ct); +} + +static void bt_nus_avrcp_ct_get_caps(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t status, struct net_buf *buf) +{ + LOG_INF("AVRCP CT get_caps callback: ct %p tid %u status 0x%02x len %u", + ct, tid, status, buf ? buf->len : 0); +} + +static void bt_nus_avrcp_ct_unit_info_rsp( + struct bt_avrcp_ct *ct, uint8_t tid, struct bt_avrcp_unit_info_rsp *rsp) +{ + LOG_INF("AVRCP CT unit_info_rsp callback: ct %p tid %u rsp %p", ct, tid, + rsp); +} + +static void bt_nus_avrcp_ct_subunit_info_rsp( + struct bt_avrcp_ct *ct, uint8_t tid, struct bt_avrcp_subunit_info_rsp *rsp) +{ + LOG_INF("AVRCP CT subunit_info_rsp callback: ct %p tid %u rsp %p", ct, + tid, rsp); +} + +static void bt_nus_avrcp_ct_passthrough_rsp( + struct bt_avrcp_ct *ct, uint8_t tid, bt_avrcp_rsp_t result, + const struct bt_avrcp_passthrough_rsp *rsp) +{ + LOG_INF("AVRCP CT passthrough_rsp callback: ct %p tid %u result 0x%02x " + "rsp %p", ct, tid, result, rsp); +} + +static void bt_nus_avrcp_ct_buf_rsp(const char *name, + struct bt_avrcp_ct *ct, uint8_t tid, + struct net_buf *buf) +{ + LOG_INF("AVRCP CT %s callback: ct %p tid %u len %u", name, ct, tid, + buf ? buf->len : 0); +} + +static void bt_nus_avrcp_ct_status_buf_rsp( + const char *name, struct bt_avrcp_ct *ct, uint8_t tid, uint8_t status, + struct net_buf *buf) +{ + LOG_INF("AVRCP CT %s callback: ct %p tid %u status 0x%02x len %u", name, + ct, tid, status, buf ? buf->len : 0); +} + +static void bt_nus_avrcp_ct_status_rsp(const char *name, + struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t status) +{ + LOG_INF("AVRCP CT %s callback: ct %p tid %u status 0x%02x", name, ct, + tid, status); +} + +#define BT_NUS_AVRCP_CT_BUF_RSP(_name) \ +static void bt_nus_avrcp_ct_##_name(struct bt_avrcp_ct *ct, uint8_t tid, \ + struct net_buf *buf) \ +{ \ + bt_nus_avrcp_ct_buf_rsp(#_name, ct, tid, buf); \ +} + +#define BT_NUS_AVRCP_CT_STATUS_BUF_RSP(_name) \ +static void bt_nus_avrcp_ct_##_name(struct bt_avrcp_ct *ct, uint8_t tid, \ + uint8_t status, struct net_buf *buf) \ +{ \ + bt_nus_avrcp_ct_status_buf_rsp(#_name, ct, tid, status, buf); \ +} + +#define BT_NUS_AVRCP_CT_STATUS_RSP(_name) \ +static void bt_nus_avrcp_ct_##_name(struct bt_avrcp_ct *ct, uint8_t tid, \ + uint8_t status) \ +{ \ + bt_nus_avrcp_ct_status_rsp(#_name, ct, tid, status); \ +} + +BT_NUS_AVRCP_CT_BUF_RSP(set_browsed_player) +BT_NUS_AVRCP_CT_BUF_RSP(get_folder_items) +BT_NUS_AVRCP_CT_BUF_RSP(get_item_attrs) +BT_NUS_AVRCP_CT_BUF_RSP(get_total_number_of_items) +BT_NUS_AVRCP_CT_BUF_RSP(search) +BT_NUS_AVRCP_CT_STATUS_BUF_RSP(list_player_app_setting_attrs) +BT_NUS_AVRCP_CT_STATUS_BUF_RSP(list_player_app_setting_vals) +BT_NUS_AVRCP_CT_STATUS_BUF_RSP(get_curr_player_app_setting_val) +BT_NUS_AVRCP_CT_STATUS_BUF_RSP(get_player_app_setting_attr_text) +BT_NUS_AVRCP_CT_STATUS_BUF_RSP(get_player_app_setting_val_text) +BT_NUS_AVRCP_CT_STATUS_BUF_RSP(get_element_attrs) +BT_NUS_AVRCP_CT_STATUS_BUF_RSP(get_play_status) +BT_NUS_AVRCP_CT_STATUS_RSP(set_player_app_setting_val) +BT_NUS_AVRCP_CT_STATUS_RSP(inform_displayable_char_set) +BT_NUS_AVRCP_CT_STATUS_RSP(inform_batt_status_of_ct) +BT_NUS_AVRCP_CT_STATUS_RSP(set_addressed_player) +BT_NUS_AVRCP_CT_STATUS_RSP(play_item) +BT_NUS_AVRCP_CT_STATUS_RSP(add_to_now_playing) + +static void bt_nus_avrcp_ct_change_path( + struct bt_avrcp_ct *ct, uint8_t tid, uint8_t status, uint32_t num_items) +{ + LOG_INF("AVRCP CT change_path callback: ct %p tid %u status 0x%02x " + "num_items %u", ct, tid, status, (unsigned int)num_items); +} + +static void bt_nus_avrcp_ct_browsing_general_reject( + struct bt_avrcp_ct *ct, uint8_t tid, uint8_t status) +{ + LOG_INF("AVRCP CT browsing_general_reject callback: ct %p tid %u " + "status 0x%02x", ct, tid, status); +} + +static void bt_nus_avrcp_ct_notification( + struct bt_avrcp_ct *ct, uint8_t tid, uint8_t status, uint8_t event_id, + struct bt_avrcp_event_data *data) +{ + LOG_INF("AVRCP CT notification callback: ct %p tid %u status 0x%02x " + "event 0x%02x", ct, tid, status, event_id); + bt_nus_avrcp_log_event_data("AVRCP CT notification", event_id, data); +} + +static void bt_nus_avrcp_ct_set_absolute_volume( + struct bt_avrcp_ct *ct, uint8_t tid, uint8_t status, uint8_t volume) +{ + LOG_INF("AVRCP CT set_absolute_volume callback: ct %p tid %u " + "status 0x%02x volume %u", ct, tid, status, volume); +} + +static struct bt_avrcp_ct_cb bt_nus_avrcp_ct_cb = +{ + .connected = bt_nus_avrcp_ct_connected, + .disconnected = bt_nus_avrcp_ct_disconnected, + .browsing_connected = bt_nus_avrcp_ct_browsing_connected, + .browsing_disconnected = bt_nus_avrcp_ct_browsing_disconnected, + .get_caps = bt_nus_avrcp_ct_get_caps, + .unit_info_rsp = bt_nus_avrcp_ct_unit_info_rsp, + .subunit_info_rsp = bt_nus_avrcp_ct_subunit_info_rsp, + .passthrough_rsp = bt_nus_avrcp_ct_passthrough_rsp, + .set_browsed_player = bt_nus_avrcp_ct_set_browsed_player, + .get_folder_items = bt_nus_avrcp_ct_get_folder_items, + .change_path = bt_nus_avrcp_ct_change_path, + .get_item_attrs = bt_nus_avrcp_ct_get_item_attrs, + .get_total_number_of_items = + bt_nus_avrcp_ct_get_total_number_of_items, + .search = bt_nus_avrcp_ct_search, + .browsing_general_reject = bt_nus_avrcp_ct_browsing_general_reject, + .notification = bt_nus_avrcp_ct_notification, + .list_player_app_setting_attrs = + bt_nus_avrcp_ct_list_player_app_setting_attrs, + .list_player_app_setting_vals = + bt_nus_avrcp_ct_list_player_app_setting_vals, + .get_curr_player_app_setting_val = + bt_nus_avrcp_ct_get_curr_player_app_setting_val, + .set_player_app_setting_val = + bt_nus_avrcp_ct_set_player_app_setting_val, + .get_player_app_setting_attr_text = + bt_nus_avrcp_ct_get_player_app_setting_attr_text, + .get_player_app_setting_val_text = + bt_nus_avrcp_ct_get_player_app_setting_val_text, + .inform_displayable_char_set = + bt_nus_avrcp_ct_inform_displayable_char_set, + .inform_batt_status_of_ct = bt_nus_avrcp_ct_inform_batt_status_of_ct, + .set_absolute_volume = bt_nus_avrcp_ct_set_absolute_volume, + .get_element_attrs = bt_nus_avrcp_ct_get_element_attrs, + .get_play_status = bt_nus_avrcp_ct_get_play_status, + .set_addressed_player = bt_nus_avrcp_ct_set_addressed_player, + .play_item = bt_nus_avrcp_ct_play_item, + .add_to_now_playing = bt_nus_avrcp_ct_add_to_now_playing, +}; + +static int bt_nus_avrcp_ct_register(void) +{ + int err; + + err = bt_avrcp_ct_register_cb(&bt_nus_avrcp_ct_cb); + if (err < 0 && err != -EALREADY) + { + LOG_ERR("Failed to register AVRCP CT callbacks (err %d)", err); + return err; + } + + LOG_INF("AVRCP CT callbacks registered"); + return 0; +} +#endif /* CONFIG_BT_AVRCP_CONTROLLER */ + +#if defined(CONFIG_BT_AVRCP_TARGET) +static void bt_nus_avrcp_tg_connected(struct bt_conn *conn, + struct bt_avrcp_tg *tg) +{ + LOG_INF("AVRCP TG connected callback: conn %s tg %p", + bt_conn_dst_str(conn), tg); +} + +static void bt_nus_avrcp_tg_disconnected(struct bt_avrcp_tg *tg) +{ + LOG_INF("AVRCP TG disconnected callback: tg %p", tg); +} + +static void bt_nus_avrcp_tg_browsing_connected(struct bt_conn *conn, + struct bt_avrcp_tg *tg) +{ + LOG_INF("AVRCP TG browsing_connected callback: conn %s tg %p", + bt_conn_dst_str(conn), tg); +} + +static void bt_nus_avrcp_tg_browsing_disconnected(struct bt_avrcp_tg *tg) +{ + LOG_INF("AVRCP TG browsing_disconnected callback: tg %p", tg); +} + +static void bt_nus_avrcp_tg_unit_info_req(struct bt_avrcp_tg *tg, + uint8_t tid) +{ + LOG_INF("AVRCP TG unit_info_req callback: tg %p tid %u", tg, tid); +} + +static void bt_nus_avrcp_tg_subunit_info_req(struct bt_avrcp_tg *tg, + uint8_t tid) +{ + LOG_INF("AVRCP TG subunit_info_req callback: tg %p tid %u", tg, tid); +} + +static void bt_nus_avrcp_tg_get_caps(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t cap_id) +{ + LOG_INF("AVRCP TG get_caps callback: tg %p tid %u cap_id 0x%02x", tg, + tid, cap_id); +} + +static void bt_nus_avrcp_tg_register_notification( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t event_id, uint32_t interval) +{ + LOG_INF("AVRCP TG register_notification callback: tg %p tid %u " + "event 0x%02x interval %u", tg, tid, event_id, + (unsigned int)interval); +} + +static void bt_nus_avrcp_tg_set_browsed_player( + struct bt_avrcp_tg *tg, uint8_t tid, uint16_t player_id) +{ + LOG_INF("AVRCP TG set_browsed_player callback: tg %p tid %u player_id %u", + tg, tid, player_id); +} + +static void bt_nus_avrcp_tg_get_total_number_of_items( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t scope) +{ + LOG_INF("AVRCP TG get_total_number_of_items callback: tg %p tid %u " + "scope 0x%02x", tg, tid, scope); +} + +static void bt_nus_avrcp_tg_list_player_app_setting_vals( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t attr_id) +{ + LOG_INF("AVRCP TG list_player_app_setting_vals callback: tg %p tid %u " + "attr_id 0x%02x", tg, tid, attr_id); +} + +static void bt_nus_avrcp_tg_inform_batt_status_of_ct( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t battery_status) +{ + LOG_INF("AVRCP TG inform_batt_status_of_ct callback: tg %p tid %u " + "battery 0x%02x", tg, tid, battery_status); +} + +static void bt_nus_avrcp_tg_set_absolute_volume( + struct bt_avrcp_tg *tg, uint8_t tid, uint8_t volume) +{ + LOG_INF("AVRCP TG set_absolute_volume callback: tg %p tid %u volume %u", + tg, tid, volume); +} + +static void bt_nus_avrcp_tg_set_addressed_player( + struct bt_avrcp_tg *tg, uint8_t tid, uint16_t player_id) +{ + LOG_INF("AVRCP TG set_addressed_player callback:" + " tg %p tid %u player_id %u", + tg, tid, player_id); +} + +static void bt_nus_avrcp_tg_tid_req(const char *name, + struct bt_avrcp_tg *tg, uint8_t tid) +{ + LOG_INF("AVRCP TG %s callback: tg %p tid %u", name, tg, tid); +} + +static void bt_nus_avrcp_tg_buf_req(const char *name, + struct bt_avrcp_tg *tg, uint8_t tid, + struct net_buf *buf) +{ + LOG_INF("AVRCP TG %s callback: tg %p tid %u len %u", name, tg, tid, + buf ? buf->len : 0); +} + +#define BT_NUS_AVRCP_TG_TID_REQ(_name) \ +static void bt_nus_avrcp_tg_##_name(struct bt_avrcp_tg *tg, uint8_t tid) \ +{ \ + bt_nus_avrcp_tg_tid_req(#_name, tg, tid); \ +} + +#define BT_NUS_AVRCP_TG_BUF_REQ(_name) \ +static void bt_nus_avrcp_tg_##_name(struct bt_avrcp_tg *tg, uint8_t tid, \ + struct net_buf *buf) \ +{ \ + bt_nus_avrcp_tg_buf_req(#_name, tg, tid, buf); \ +} + +BT_NUS_AVRCP_TG_BUF_REQ(get_folder_items) +BT_NUS_AVRCP_TG_BUF_REQ(change_path) +BT_NUS_AVRCP_TG_BUF_REQ(get_item_attrs) +BT_NUS_AVRCP_TG_BUF_REQ(search) +BT_NUS_AVRCP_TG_BUF_REQ(passthrough_req) +BT_NUS_AVRCP_TG_TID_REQ(list_player_app_setting_attrs) +BT_NUS_AVRCP_TG_BUF_REQ(get_curr_player_app_setting_val) +BT_NUS_AVRCP_TG_BUF_REQ(set_player_app_setting_val) +BT_NUS_AVRCP_TG_BUF_REQ(get_player_app_setting_attr_text) +BT_NUS_AVRCP_TG_BUF_REQ(get_player_app_setting_val_text) +BT_NUS_AVRCP_TG_BUF_REQ(inform_displayable_char_set) +BT_NUS_AVRCP_TG_BUF_REQ(get_element_attrs) +BT_NUS_AVRCP_TG_TID_REQ(get_play_status) +BT_NUS_AVRCP_TG_BUF_REQ(play_item) +BT_NUS_AVRCP_TG_BUF_REQ(add_to_now_playing) + +static struct bt_avrcp_tg_cb bt_nus_avrcp_tg_cb = +{ + .connected = bt_nus_avrcp_tg_connected, + .disconnected = bt_nus_avrcp_tg_disconnected, + .unit_info_req = bt_nus_avrcp_tg_unit_info_req, + .register_notification = bt_nus_avrcp_tg_register_notification, + .subunit_info_req = bt_nus_avrcp_tg_subunit_info_req, + .get_caps = bt_nus_avrcp_tg_get_caps, + .browsing_connected = bt_nus_avrcp_tg_browsing_connected, + .browsing_disconnected = bt_nus_avrcp_tg_browsing_disconnected, + .set_browsed_player = bt_nus_avrcp_tg_set_browsed_player, + .get_folder_items = bt_nus_avrcp_tg_get_folder_items, + .change_path = bt_nus_avrcp_tg_change_path, + .get_item_attrs = bt_nus_avrcp_tg_get_item_attrs, + .get_total_number_of_items = + bt_nus_avrcp_tg_get_total_number_of_items, + .search = bt_nus_avrcp_tg_search, + .passthrough_req = bt_nus_avrcp_tg_passthrough_req, + .list_player_app_setting_attrs = + bt_nus_avrcp_tg_list_player_app_setting_attrs, + .list_player_app_setting_vals = + bt_nus_avrcp_tg_list_player_app_setting_vals, + .get_curr_player_app_setting_val = + bt_nus_avrcp_tg_get_curr_player_app_setting_val, + .set_player_app_setting_val = + bt_nus_avrcp_tg_set_player_app_setting_val, + .get_player_app_setting_attr_text = + bt_nus_avrcp_tg_get_player_app_setting_attr_text, + .get_player_app_setting_val_text = + bt_nus_avrcp_tg_get_player_app_setting_val_text, + .inform_displayable_char_set = + bt_nus_avrcp_tg_inform_displayable_char_set, + .inform_batt_status_of_ct = + bt_nus_avrcp_tg_inform_batt_status_of_ct, + .set_absolute_volume = bt_nus_avrcp_tg_set_absolute_volume, + .get_element_attrs = bt_nus_avrcp_tg_get_element_attrs, + .get_play_status = bt_nus_avrcp_tg_get_play_status, + .set_addressed_player = bt_nus_avrcp_tg_set_addressed_player, + .play_item = bt_nus_avrcp_tg_play_item, + .add_to_now_playing = bt_nus_avrcp_tg_add_to_now_playing, +}; + +static int bt_nus_avrcp_tg_register(void) +{ + int err; + + err = bt_avrcp_tg_register_cb(&bt_nus_avrcp_tg_cb); + if (err < 0 && err != -EALREADY) + { + LOG_ERR("Failed to register AVRCP TG callbacks (err %d)", err); + return err; + } + + LOG_INF("AVRCP TG callbacks registered"); + return 0; +} +#endif /* CONFIG_BT_AVRCP_TARGET */ +#endif /* CONFIG_BT_AVRCP */ + +static enum bt_br_conn_req_rsp bt_nus_br_conn_req(const bt_addr_t *addr, + uint32_t cod) +{ + char addr_str[BT_ADDR_STR_LEN]; + + bt_addr_to_str(addr, addr_str, sizeof(addr_str)); + LOG_INF("BR/EDR conn req: addr %s cod " + "0x%06x", addr_str, (unsigned int)cod); + + return BT_BR_CONN_REQ_ACCEPT_PERIPHERAL; +} + +static void bt_nus_connected(struct bt_conn *conn, uint8_t err) +{ + if (!bt_conn_is_type(conn, BT_CONN_TYPE_BR)) + { + return; + } + + if (err) + { + LOG_WRN("BR/EDR connection failed: %s err 0x%02x", + bt_conn_dst_str(conn), + err); + return; + } + + LOG_INF("BR/EDR connected: %s", bt_conn_dst_str(conn)); +} + +static void bt_nus_disconnected(struct bt_conn *conn, uint8_t reason) +{ + if (!bt_conn_is_type(conn, BT_CONN_TYPE_BR)) + { + return; + } + + LOG_INF("BR/EDR disconnected: %s reason 0x%02x", bt_conn_dst_str(conn), + reason); +} + +static struct bt_conn_cb bt_nus_conn_callbacks = +{ + .connected = bt_nus_connected, + .disconnected = bt_nus_disconnected, +}; +#endif + +#if defined(CONFIG_BT_BROADCASTER) +static int bt_nus_start_le_adv(const char *reason); +#endif + +static void bt_nus_le_connected(struct bt_conn *conn, uint8_t err) +{ + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + if (err) + { + LOG_WRN("LE connection failed: %s err 0x%02x", bt_conn_dst_str(conn), + err); + return; + } + + LOG_INF("LE connected: %s", bt_conn_dst_str(conn)); +} + +static void bt_nus_le_disconnected(struct bt_conn *conn, uint8_t reason) +{ + if (!bt_conn_is_type(conn, BT_CONN_TYPE_LE)) + { + return; + } + + LOG_INF("LE disconnected: %s reason 0x%02x", bt_conn_dst_str(conn), + reason); + +#if defined(CONFIG_BT_BROADCASTER) + bt_nus_start_le_adv("LE disconnected"); +#endif +} + +static void bt_nus_le_recycled(void) +{ + LOG_INF("LE connection object recycled"); + +#if defined(CONFIG_BT_BROADCASTER) + bt_nus_start_le_adv("connection recycled"); +#endif +} + +static struct bt_conn_cb bt_nus_le_conn_callbacks = +{ + .connected = bt_nus_le_connected, + .disconnected = bt_nus_le_disconnected, + .recycled = bt_nus_le_recycled, +}; + +#if defined(CONFIG_BT_BROADCASTER) +static int bt_nus_start_le_adv(const char *reason) +{ + int err; + + err = bt_le_adv_start(BT_LE_ADV_CONN_FAST_1, ad, ARRAY_SIZE(ad), sd, + ARRAY_SIZE(sd)); + if (err < 0 && err != -EALREADY) + { + LOG_WRN("Failed to start LE advertising after %s (err %d)", reason, + err); + return err; + } + + LOG_INF("LE advertising active after %s", reason); + return 0; +} +#endif + +static int bt_nus_auto_start(void) +{ + int err; + + err = bt_enable(NULL); + DEBUGASSERT(!err); + + err = bt_nus_update_device_name(); + DEBUGASSERT(!err); + +#if defined(CONFIG_BT_SMP) || defined(CONFIG_BT_CLASSIC) + err = bt_nus_auth_register(); + DEBUGASSERT(!err); +#endif + + err = bt_conn_cb_register(&bt_nus_le_conn_callbacks); + DEBUGASSERT(!err); + +#if defined(CONFIG_BT_BROADCASTER) + err = bt_nus_start_le_adv("Bluetooth enable"); + DEBUGASSERT(!err); +#endif + +#ifdef CONFIG_BT_CLASSIC + err = bt_conn_cb_register(&bt_nus_conn_callbacks); + DEBUGASSERT(!err); + +#if defined(CONFIG_BT_A2DP_SINK) + err = bt_nus_a2dp_sink_register(); + DEBUGASSERT(!err); +#endif + +#if defined(CONFIG_BT_AVRCP_CONTROLLER) + err = bt_nus_avrcp_ct_register(); + DEBUGASSERT(!err); +#endif + +#if defined(CONFIG_BT_AVRCP_TARGET) + err = bt_nus_avrcp_tg_register(); + DEBUGASSERT(!err); +#endif + + err = bt_br_set_connectable(true, bt_nus_br_conn_req); + DEBUGASSERT(!err); + + err = bt_br_set_class_of_device(CONFIG_BT_COD); + DEBUGASSERT(!err); + + err = bt_br_write_eir(eir, ARRAY_SIZE(eir), true); + DEBUGASSERT(!err); + + err = bt_br_set_discoverable(true, false); + DEBUGASSERT(!err); +#endif + + return 0; +} + +SYS_INIT(bt_nus_auto_start, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/libs/zblue/services/nus/nus.c b/libs/zblue/services/nus/nus.c new file mode 100644 index 0000000000000..a064f2c265426 --- /dev/null +++ b/libs/zblue/services/nus/nus.c @@ -0,0 +1,113 @@ +/**************************************************************************** + * libs/zblue/services/nus/nus.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2024 Croxel, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include "nus_internal.h" + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +ssize_t nus_bt_chr_write( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + struct bt_nus_cb *listener = NULL; + struct bt_nus_inst *instance = NULL; + + instance = bt_nus_inst_get_from_attr(attr); + DEBUGASSERT(instance); + + list_for_every_container(instance->cbs, listener, _node) + { + if (listener->received) + { + listener->received(conn, buf, len, listener->ctx); + } + } + + return len; +} + +void nus_ccc_cfg_changed(const struct bt_gatt_attr *attr, uint16_t value) +{ + struct bt_nus_cb *listener = NULL; + struct bt_nus_inst *instance = NULL; + + instance = bt_nus_inst_get_from_attr(attr); + DEBUGASSERT(instance); + + list_for_every_container(instance->cbs, listener, _node) + { + if (listener->notif_enabled) + { + listener->notif_enabled((value == 1), listener->ctx); + } + } +} + +int bt_nus_inst_cb_register(struct bt_nus_inst *instance, + struct bt_nus_cb *cb, void *ctx) +{ + if (!cb) + { + return -EINVAL; + } + + if (!instance) + { + if (IS_ENABLED(CONFIG_BT_ZEPHYR_NUS_DEFAULT_INSTANCE)) + { + instance = bt_nus_inst_default(); + } + else + { + return -ENOTSUP; + } + } + + cb->ctx = ctx; + list_add_tail(instance->cbs, &cb->_node); + + return 0; +} + +int bt_nus_inst_send( + struct bt_conn *conn, struct bt_nus_inst *instance, const void *data, + uint16_t len) +{ + if (!data || !len) + { + return -EINVAL; + } + + if (!instance) + { + if (IS_ENABLED(CONFIG_BT_ZEPHYR_NUS_DEFAULT_INSTANCE)) + { + instance = bt_nus_inst_default(); + } + else + { + return -ENOTSUP; + } + } + + return bt_gatt_notify(conn, &instance->svc->attrs[1], data, len); +} diff --git a/libs/zblue/services/nus/nus_inst.c b/libs/zblue/services/nus/nus_inst.c new file mode 100644 index 0000000000000..520a51b326069 --- /dev/null +++ b/libs/zblue/services/nus/nus_inst.c @@ -0,0 +1,52 @@ +/**************************************************************************** + * libs/zblue/services/nus/nus_inst.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2024 Croxel, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include "nus_internal.h" + +#if defined(CONFIG_BT_ZEPHYR_NUS_DEFAULT_INSTANCE) +BT_NUS_INST_DEFINE(nus_def); + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +struct bt_nus_inst *bt_nus_inst_default(void) +{ + return &nus_def; +} +#else +struct bt_nus_inst *bt_nus_inst_default(void) +{ + return NULL; +} +#endif + +struct bt_nus_inst * +bt_nus_inst_get_from_attr(const struct bt_gatt_attr *attr) +{ + STRUCT_SECTION_FOREACH(bt_nus_inst, instance) + { + for (size_t i = 0; i < instance->svc->attr_count; i++) + { + if (attr == &instance->svc->attrs[i]) + { + return instance; + } + } + } + + return NULL; +} diff --git a/libs/zblue/services/nus/nus_internal.h b/libs/zblue/services/nus/nus_internal.h new file mode 100644 index 0000000000000..ce17db0cfb9c8 --- /dev/null +++ b/libs/zblue/services/nus/nus_internal.h @@ -0,0 +1,27 @@ +/**************************************************************************** + * libs/zblue/services/nus/nus_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2024 Croxel, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_BLUETOOTH_SERVICES_NUS_INTERNAL_H_ +#define ZEPHYR_BLUETOOTH_SERVICES_NUS_INTERNAL_H_ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +struct bt_nus_inst *bt_nus_inst_default(void); +struct bt_nus_inst * +bt_nus_inst_get_from_attr(const struct bt_gatt_attr *attr); + +#endif /* ZEPHYR_BLUETOOTH_SERVICES_NUS_INTERNAL_H_ */ diff --git a/libs/zblue/services/ots/CMakeLists.txt b/libs/zblue/services/ots/CMakeLists.txt new file mode 100644 index 0000000000000..7caf08e60bed2 --- /dev/null +++ b/libs/zblue/services/ots/CMakeLists.txt @@ -0,0 +1,25 @@ +# ############################################################################## +# libs/zblue/services/ots/CMakeLists.txt +# +# Author: chao an +# +# SPDX-License-Identifier: Apache-2.0 +# +# Licensed to the Apache Software Foundation (ASF) under one or more contributor +# license agreements. See the NOTICE file distributed with this work for +# additional information regarding copyright ownership. The ASF licenses this +# file to you under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +# +# ############################################################################## + +# OTS sources are added by libs/zblue/services/CMakeLists.txt. diff --git a/libs/zblue/services/ots/Kconfig b/libs/zblue/services/ots/Kconfig new file mode 100644 index 0000000000000..b2aff47d53e15 --- /dev/null +++ b/libs/zblue/services/ots/Kconfig @@ -0,0 +1,115 @@ +############################################################################ +# libs/zblue/services/ots/Kconfig +# +# Ported to Apache NuttX by chao an +# . +# +############################################################################ + +# Bluetooth Object Transfer service + +# Copyright (c) 2020-2022 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config BT_OTS + bool "Object Transfer Service (OTS) [EXPERIMENTAL]" + select BT_L2CAP_DYNAMIC_CHANNEL + select BT_GATT_DYNAMIC_DB + select BT_SMP + help + Enable Object Transfer Service. + +if BT_OTS + +config BT_OTS_DIR_LIST_OBJ + bool "The Directory Listing Object" + help + Enables the Directory Listing Object, which is an object that contains all the metadata + from all other objects, for easy exposure to a client. Enabling this will use one of the + objects given by BT_OTS_MAX_OBJ_CNT. + +config BT_OTS_DIR_LIST_OBJ_NAME + string "The object name of the Directory Listing Object" + default "Directory" + depends on BT_OTS_DIR_LIST_OBJ + help + The name of the Directory Listing Object when it is read by a client. + +config BT_OTS_MAX_INST_CNT + int "Maximum number of available OTS instances" + default 1 + range 1 1 if !BT_OTS_SECONDARY_SVC + +config BT_OTS_SECONDARY_SVC + bool "Register OTS as Secondary Service" + +config BT_OTS_OACP_CREATE_SUPPORT + bool "Support OACP Create Operation" + depends on BT_OTS_OACP_WRITE_SUPPORT + depends on BT_OTS_OBJ_NAME_WRITE_SUPPORT + +config BT_OTS_OACP_CHECKSUM_SUPPORT + imply CRC + bool "Support OACP Calculate Checksum operation" + +config BT_OTS_OACP_DELETE_SUPPORT + bool "Support OACP Delete Operation" + +config BT_OTS_OACP_READ_SUPPORT + bool "Support OACP Read Operation" + default y + +config BT_OTS_OACP_WRITE_SUPPORT + bool "Support OACP Write Operation" + +config BT_OTS_OACP_PATCH_SUPPORT + bool "Support patching of objects" + depends on BT_OTS_OACP_WRITE_SUPPORT + +config BT_OTS_OLCP_GO_TO_SUPPORT + bool "Support OLCP Go To Operation" + default y + +config BT_OTS_OBJ_NAME_WRITE_SUPPORT + bool "Support object name write" + +endif # BT_OTS + +#### Object Transfer Service Client ################################ + +config BT_OTS_CLIENT + bool "Object Transfer Service Client [EXPERIMENTAL]" + select BT_L2CAP_DYNAMIC_CHANNEL + select BT_GATT_CLIENT + select BT_GATT_AUTO_DISCOVER_CCC + help + This option enables support for the Object Transfer Service Client. + +if BT_OTS || BT_OTS_CLIENT + +config BT_OTS_MAX_OBJ_CNT + int "Maximum number of objects that each service instance can store" + default 5 + # Max obj count is 2^48 - 1 - 256 (RFU IDs) + range 1 281474976710400 + +config BT_OTS_L2CAP_CHAN_TX_MTU + int "Size of TX MTU for Object Transfer Channel" + default 256 + range 21 65533 + +config BT_OTS_L2CAP_CHAN_RX_MTU + int "Size of RX MTU for Object Transfer Channel" + # RX MTU will be truncated to account for the L2CAP PDU and SDU header. + default BT_BUF_ACL_RX_SIZE + range 21 BT_BUF_ACL_RX_SIZE + +config BT_OTS_OBJ_MAX_NAME_LEN + int "Maximum object name length" + # Max name length allowed by OTS spec is 120 octets + # TODO: Set separate max name length for client, as other + # servers may use the full 120 octets. + default 120 + range 1 120 + +endif # BT_OTS || BT_OTS_CLIENT diff --git a/libs/zblue/services/ots/ots.c b/libs/zblue/services/ots/ots.c new file mode 100644 index 0000000000000..6591da9866500 --- /dev/null +++ b/libs/zblue/services/ots/ots.c @@ -0,0 +1,767 @@ +/**************************************************************************** + * libs/zblue/services/ots/ots.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include "ots_internal.h" +#include "ots_obj_manager_internal.h" +#include "ots_dir_list_internal.h" + +#include + +LOG_MODULE_REGISTER(bt_ots, CONFIG_BT_OTS_LOG_LEVEL); + +#if defined(CONFIG_BT_OTS_OACP_CREATE_SUPPORT) +#define OACP_FEAT_BIT_CREATE BIT(BT_OTS_OACP_FEAT_CREATE) +#else +#define OACP_FEAT_BIT_CREATE 0 +#endif + +#if defined(CONFIG_BT_OTS_OACP_DELETE_SUPPORT) +#define OACP_FEAT_BIT_DELETE BIT(BT_OTS_OACP_FEAT_DELETE) +#else +#define OACP_FEAT_BIT_DELETE 0 +#endif + +#if defined(CONFIG_BT_OTS_OACP_CHECKSUM_SUPPORT) +#define OACP_FEAT_BIT_CRC BIT(BT_OTS_OACP_FEAT_CHECKSUM) +#else +#define OACP_FEAT_BIT_CRC 0 +#endif + +#if defined(CONFIG_BT_OTS_OACP_READ_SUPPORT) +#define OACP_FEAT_BIT_READ BIT(BT_OTS_OACP_FEAT_READ) +#else +#define OACP_FEAT_BIT_READ 0 +#endif + +#if defined(CONFIG_BT_OTS_OACP_WRITE_SUPPORT) +#define OACP_FEAT_BIT_WRITE BIT(BT_OTS_OACP_FEAT_WRITE) +#else +#define OACP_FEAT_BIT_WRITE 0 +#endif + +#if defined(CONFIG_BT_OTS_OACP_PATCH_SUPPORT) +#define OACP_FEAT_BIT_PATCH BIT(BT_OTS_OACP_FEAT_PATCH) +#else +#define OACP_FEAT_BIT_PATCH 0 +#endif + +/* OACP features supported by Kconfig */ +#define OACP_FEAT \ + (OACP_FEAT_BIT_CREATE | OACP_FEAT_BIT_DELETE | OACP_FEAT_BIT_CRC | OACP_FEAT_BIT_READ | \ + OACP_FEAT_BIT_WRITE | OACP_FEAT_BIT_PATCH) + +#if defined(CONFIG_BT_OTS_OLCP_GO_TO_SUPPORT) +#define OLCP_FEAT_BIT_GOTO BIT(BT_OTS_OLCP_FEAT_GO_TO) +#else +#define OLCP_FEAT_BIT_GOTO 0 +#endif + +/* OLCP features supported by Kconfig */ +#define OLCP_FEAT OLCP_FEAT_BIT_GOTO + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static bool ots_obj_validate_prop_against_oacp(uint32_t prop, uint32_t oacp) +{ + if (BT_OTS_OBJ_GET_PROP_DELETE(prop) > 0 && + BT_OTS_OACP_GET_FEAT_DELETE(oacp) == 0) + { + return false; + } + + if (BT_OTS_OBJ_GET_PROP_EXECUTE(prop) > 0 && + BT_OTS_OACP_GET_FEAT_EXECUTE(oacp) == 0) + { + return false; + } + + if (BT_OTS_OBJ_GET_PROP_READ(prop) > 0 && + BT_OTS_OACP_GET_FEAT_READ(oacp) == 0) + { + return false; + } + + if (BT_OTS_OBJ_GET_PROP_WRITE(prop) > 0 && + BT_OTS_OACP_GET_FEAT_WRITE(oacp) == 0) + { + return false; + } + + if (BT_OTS_OBJ_GET_PROP_APPEND(prop) > 0 && + BT_OTS_OACP_GET_FEAT_APPEND(oacp) == 0) + { + return false; + } + + if (BT_OTS_OBJ_GET_PROP_TRUNCATE(prop) > 0 && + BT_OTS_OACP_GET_FEAT_TRUNCATE(oacp) == 0) + { + return false; + } + + if (BT_OTS_OBJ_GET_PROP_PATCH(prop) > 0 && + BT_OTS_OACP_GET_FEAT_PATCH(oacp) == 0) + { + return false; + } + + return true; +} + +static ssize_t ots_feature_read( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct bt_ots *ots = (struct bt_ots *)attr->user_data; + + LOG_DBG("OTS Feature GATT Read Operation"); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, &ots->features, + sizeof(ots->features)); +} + +static ssize_t ots_obj_name_read( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct bt_ots *ots = (struct bt_ots *)attr->user_data; + + LOG_DBG("OTS Object Name GATT Read Operation"); + + if (!ots->cur_obj) + { + LOG_DBG("No Current Object selected in OTS!"); + return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED); + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + ots->cur_obj->metadata.name, + strlen(ots->cur_obj->metadata.name)); +} + +#if defined(CONFIG_BT_OTS_OBJ_NAME_WRITE_SUPPORT) +ssize_t ots_obj_name_write( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + struct bt_ots *ots = (struct bt_ots *)attr->user_data; + struct bt_gatt_ots_object *obj = NULL; + int rc = 0; + char name[CONFIG_BT_OTS_OBJ_MAX_NAME_LEN + 1]; + + LOG_DBG("OTS Object Name GATT Write Operation"); + + if (!ots->cur_obj) + { + LOG_DBG("No Current Object selected in OTS!"); + return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED); + } + + if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && + ots->cur_obj->id == OTS_OBJ_ID_DIR_LIST) + { + LOG_DBG("Rejecting name write for the directory list object."); + return BT_GATT_ERR(BT_GATT_OTS_WRITE_REQUEST_REJECTED); + } + + if (offset > 0) + { + LOG_DBG("Rejecting a long write, offset must be 0!"); + return BT_GATT_ERR(BT_GATT_OTS_WRITE_REQUEST_REJECTED); + } + + if (len > CONFIG_BT_OTS_OBJ_MAX_NAME_LEN) + { + LOG_DBG("Object name is too long!"); + return BT_GATT_ERR(BT_GATT_OTS_WRITE_REQUEST_REJECTED); + } + + /* Construct a temporary name for duplication detection */ + + memcpy(name, buf, len); + name[len] = '\0'; + + rc = bt_gatt_ots_obj_manager_first_obj_get(ots->obj_manager, &obj); + while (rc == 0) + { + if (obj != ots->cur_obj && strcmp(name, obj->metadata.name) == 0) + { + LOG_DBG("Object name is duplicated!"); + return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NAME_ALREADY_EXISTS); + } + + rc = bt_gatt_ots_obj_manager_next_obj_get(ots->obj_manager, obj, &obj); + } + + /* No duplicate detected, notify application and update real object name */ + + if (ots->cb->obj_name_written) + { + ots->cb->obj_name_written(ots, conn, ots->cur_obj->id, + ots->cur_obj->metadata.name, name); + } + + strcpy(ots->cur_obj->metadata.name, name); + + return len; +} +#endif + +static ssize_t ots_obj_type_read( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct bt_ots *ots = (struct bt_ots *)attr->user_data; + struct bt_ots_obj_metadata *obj_meta; + + LOG_DBG("OTS Object Type GATT Read Operation"); + + if (!ots->cur_obj) + { + LOG_DBG("No Current Object selected in OTS!"); + return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED); + } + + obj_meta = &ots->cur_obj->metadata; + switch (obj_meta->type.uuid.type) + { + case BT_UUID_TYPE_16: + return bt_gatt_attr_read(conn, attr, buf, len, offset, + &obj_meta->type.uuid_16.val, + sizeof(obj_meta->type.uuid_16.val)); + case BT_UUID_TYPE_128: + return bt_gatt_attr_read(conn, attr, buf, len, offset, + obj_meta->type.uuid_128.val, + sizeof(obj_meta->type.uuid_128.val)); + default: + return -EINVAL; + } +} + +static ssize_t ots_obj_size_read( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct bt_ots *ots = (struct bt_ots *)attr->user_data; + + LOG_DBG("OTS Object Size GATT Read Operation"); + + if (!ots->cur_obj) + { + LOG_DBG("No Current Object selected in OTS!"); + return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED); + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + &ots->cur_obj->metadata.size, + sizeof(ots->cur_obj->metadata.size)); +} + +static ssize_t ots_obj_id_read( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct bt_ots *ots = (struct bt_ots *)attr->user_data; + uint8_t id[BT_OTS_OBJ_ID_SIZE]; + char id_str[BT_OTS_OBJ_ID_STR_LEN]; + + LOG_DBG("OTS Object ID GATT Read Operation"); + + if (!ots->cur_obj) + { + LOG_DBG("No Current Object selected in OTS!"); + return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED); + } + + sys_put_le48(ots->cur_obj->id, id); + + bt_ots_obj_id_to_str(ots->cur_obj->id, id_str, sizeof(id_str)); + LOG_DBG("Current Object ID: %s", id_str); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, id, sizeof(id)); +} + +static ssize_t ots_obj_prop_read( + struct bt_conn *conn, const struct bt_gatt_attr *attr, void *buf, + uint16_t len, uint16_t offset) +{ + struct bt_ots *ots = (struct bt_ots *)attr->user_data; + + LOG_DBG("OTS Object Properties GATT Read Operation"); + + if (!ots->cur_obj) + { + LOG_DBG("No Current Object selected in OTS!"); + return BT_GATT_ERR(BT_GATT_OTS_OBJECT_NOT_SELECTED); + } + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + &ots->cur_obj->metadata.props, + sizeof(ots->cur_obj->metadata.props)); +} + +int bt_ots_obj_add_internal(struct bt_ots *ots, struct bt_conn *conn, + const struct bt_ots_obj_add_param *param, + struct bt_gatt_ots_object **obj) +{ + int err; + struct bt_gatt_ots_object *new_obj; + struct bt_ots_obj_created_desc created_desc; + + if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && ots->dir_list && + !bt_ots_dir_list_is_idle(ots->dir_list)) + { + LOG_DBG("Directory Listing Object is being read"); + return -EBUSY; + } + + err = bt_gatt_ots_obj_manager_obj_add(ots->obj_manager, &new_obj); + if (err) + { + LOG_ERR("No space available in the object manager"); + return err; + } + + (void)memset(&created_desc, 0, sizeof(created_desc)); + + if (ots->cb->obj_created) + { + err = ots->cb->obj_created(ots, conn, new_obj->id, param, + &created_desc); + + if (err) + { + (void)bt_gatt_ots_obj_manager_obj_delete(new_obj); + + return err; + } + + if (!ots_obj_validate_prop_against_oacp(created_desc.props, + ots->features.oacp)) + { + LOG_ERR("Object properties (0x%04X) are not a subset of OACP " + "(0x%04X)", + created_desc.props, ots->features.oacp); + + (void)bt_ots_obj_delete(ots, new_obj->id); + return -ECANCELED; + } + + if (created_desc.name == NULL) + { + LOG_ERR("Object name must be set by application after object " + "creation."); + + (void)bt_ots_obj_delete(ots, new_obj->id); + return -ECANCELED; + } + + if (created_desc.size.alloc < param->size) + { + LOG_ERR("Object allocated size must >= requested size."); + + (void)bt_ots_obj_delete(ots, new_obj->id); + return -ECANCELED; + } + } + + new_obj->metadata.type = param->type; + new_obj->metadata.name = created_desc.name; + new_obj->metadata.size = created_desc.size; + new_obj->metadata.props = created_desc.props; + + if (obj) + { + *obj = new_obj; + } + + return 0; +} + +int bt_ots_obj_add(struct bt_ots *ots, + const struct bt_ots_obj_add_param *param) +{ + int err; + size_t name_len; + struct bt_gatt_ots_object *obj; + + err = bt_ots_obj_add_internal(ots, NULL, param, &obj); + if (err) + { + return err; + } + + name_len = strlen(obj->metadata.name); + if (name_len == 0 || name_len > CONFIG_BT_OTS_OBJ_MAX_NAME_LEN) + { + LOG_ERR("Invalid name length %zu", name_len); + + (void)bt_ots_obj_delete(ots, obj->id); + return -ECANCELED; + } + + if (obj->metadata.size.cur > param->size) + { + LOG_ERR("Object current size must be less than or equal to requested " + "size."); + + (void)bt_ots_obj_delete(ots, obj->id); + return -ECANCELED; + } + + return obj->id; +} + +int bt_ots_obj_delete(struct bt_ots *ots, uint64_t id) +{ + int err; + struct bt_gatt_ots_object *obj; + + if (!BT_OTS_VALID_OBJ_ID(id)) + { + LOG_DBG("Invalid object ID 0x%016llx", id); + + return -EINVAL; + } + + err = bt_gatt_ots_obj_manager_obj_get(ots->obj_manager, id, &obj); + if (err) + { + return err; + } + + if (obj->state.type != BT_GATT_OTS_OBJECT_IDLE_STATE) + { + return -EBUSY; + } + + if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && ots->dir_list && + !bt_ots_dir_list_is_idle(ots->dir_list)) + { + LOG_DBG("Directory Listing Object is being read"); + return -EBUSY; + } + + if (ots->cb->obj_deleted) + { + err = ots->cb->obj_deleted(ots, NULL, obj->id); + if (err) + { + return err; + } + } + + err = bt_gatt_ots_obj_manager_obj_delete(obj); + if (err) + { + return err; + } + + if (ots->cur_obj == obj) + { + ots->cur_obj = NULL; + } + + return 0; +} + +#if defined(CONFIG_BT_OTS_SECONDARY_SVC) +void *bt_ots_svc_decl_get(struct bt_ots *ots) +{ + return ots->service->attrs; +} +#endif + +static void oacp_indicate_work_handler(struct k_work *work) +{ + struct bt_gatt_ots_indicate *ind = CONTAINER_OF(work, + struct bt_gatt_ots_indicate, work); + struct bt_ots *ots = CONTAINER_OF(ind, struct bt_ots, oacp_ind); + + bt_gatt_indicate(NULL, &ots->oacp_ind.params); +} + +static void olcp_indicate_work_handler(struct k_work *work) +{ + struct bt_gatt_ots_indicate *ind = CONTAINER_OF(work, + struct bt_gatt_ots_indicate, work); + struct bt_ots *ots = CONTAINER_OF(ind, struct bt_ots, olcp_ind); + + bt_gatt_indicate(NULL, &ots->olcp_ind.params); +} + +int bt_ots_init(struct bt_ots *ots, struct bt_ots_init_param *ots_init) +{ + int err; + + if (!ots || !ots_init || !ots_init->cb) + { + return -EINVAL; + } + + DEBUGASSERT(ots_init->cb->obj_created || + !BT_OTS_OACP_GET_FEAT_CREATE(ots_init->features.oacp)); + DEBUGASSERT(ots_init->cb->obj_deleted || + !BT_OTS_OACP_GET_FEAT_DELETE(ots_init->features.oacp)); +#if defined(CONFIG_BT_OTS_OACP_CHECKSUM_SUPPORT) + DEBUGASSERT(ots_init->cb->obj_cal_checksum || + !BT_OTS_OACP_GET_FEAT_CHECKSUM(ots_init->features.oacp)); +#endif + DEBUGASSERT(ots_init->cb->obj_read || + !BT_OTS_OACP_GET_FEAT_READ(ots_init->features.oacp)); + DEBUGASSERT(ots_init->cb->obj_write || + !BT_OTS_OACP_GET_FEAT_WRITE(ots_init->features.oacp)); + + /* Set callback structure. */ + + ots->cb = ots_init->cb; + + /* Check OACP supported features against Kconfig. */ + + if (ots_init->features.oacp & (~((uint32_t)OACP_FEAT))) + { + return -ENOTSUP; + } + + DEBUGASSERT(!BT_OTS_OACP_GET_FEAT_CREATE(ots_init->features.oacp) || + BT_OTS_OACP_GET_FEAT_WRITE(ots_init->features.oacp)); + + ots->features.oacp = ots_init->features.oacp; + LOG_DBG("OACP features: 0x%04X", ots->features.oacp); + + /* Check OLCP supported features against Kconfig. */ + + if (ots_init->features.olcp & (~((uint32_t)OLCP_FEAT))) + { + return -ENOTSUP; + } + + ots->features.olcp = ots_init->features.olcp; + LOG_DBG("OLCP features: 0x%04X", ots->features.olcp); + + /* Register L2CAP context. */ + + err = bt_gatt_ots_l2cap_register(&ots->l2cap); + if (err) + { + return err; + } + + err = bt_gatt_service_register(ots->service); + if (err) + { + bt_gatt_ots_l2cap_unregister(&ots->l2cap); + + return err; + } + + if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ)) + { + bt_ots_dir_list_init(&ots->dir_list, ots->obj_manager); + } + + k_work_init(&ots->oacp_ind.work, oacp_indicate_work_handler); + k_work_init(&ots->olcp_ind.work, olcp_indicate_work_handler); + + LOG_DBG("Initialized OTS"); + + return 0; +} + +#if defined(CONFIG_BT_OTS_SECONDARY_SVC) +#define BT_GATT_OTS_SERVICE BT_GATT_SECONDARY_SERVICE +#else +#define BT_GATT_OTS_SERVICE BT_GATT_PRIMARY_SERVICE +#endif + +#if defined(CONFIG_BT_OTS_OBJ_NAME_WRITE_SUPPORT) +#define BT_OTS_OBJ_NAME_GATT_CHRC (BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE) +#define BT_OTS_OBJ_NAME_GATT_PERM (BT_GATT_PERM_READ | BT_GATT_PERM_WRITE) +#define BT_OTS_OBJ_NAME_GATT_WRITE (ots_obj_name_write) +#else +#define BT_OTS_OBJ_NAME_GATT_CHRC (BT_GATT_CHRC_READ) +#define BT_OTS_OBJ_NAME_GATT_PERM (BT_GATT_PERM_READ) +#define BT_OTS_OBJ_NAME_GATT_WRITE (NULL) +#endif + +#define BT_GATT_OTS_ATTRS(_ots) \ + {BT_GATT_OTS_SERVICE(BT_UUID_OTS), \ + BT_GATT_CHARACTERISTIC(BT_UUID_OTS_FEATURE, BT_GATT_CHRC_READ, BT_GATT_PERM_READ, \ + ots_feature_read, NULL, &_ots), \ + BT_GATT_CHARACTERISTIC(BT_UUID_OTS_NAME, BT_OTS_OBJ_NAME_GATT_CHRC, BT_OTS_OBJ_NAME_GATT_PERM, \ + ots_obj_name_read, BT_OTS_OBJ_NAME_GATT_WRITE, &_ots), \ + BT_GATT_CHARACTERISTIC(BT_UUID_OTS_TYPE, BT_GATT_CHRC_READ, BT_GATT_PERM_READ, \ + ots_obj_type_read, NULL, &_ots), \ + BT_GATT_CHARACTERISTIC(BT_UUID_OTS_SIZE, BT_GATT_CHRC_READ, BT_GATT_PERM_READ, \ + ots_obj_size_read, NULL, &_ots), \ + BT_GATT_CHARACTERISTIC(BT_UUID_OTS_ID, BT_GATT_CHRC_READ, BT_GATT_PERM_READ, ots_obj_id_read, \ + NULL, &_ots), \ + BT_GATT_CHARACTERISTIC(BT_UUID_OTS_PROPERTIES, BT_GATT_CHRC_READ, BT_GATT_PERM_READ, \ + ots_obj_prop_read, NULL, &_ots), \ + BT_GATT_CHARACTERISTIC(BT_UUID_OTS_ACTION_CP, BT_GATT_CHRC_WRITE | BT_GATT_CHRC_INDICATE, \ + BT_GATT_PERM_WRITE, NULL, bt_gatt_ots_oacp_write, &_ots), \ + BT_GATT_CCC_MANAGED(&_ots.oacp_ind.ccc, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE), \ + BT_GATT_CHARACTERISTIC(BT_UUID_OTS_LIST_CP, BT_GATT_CHRC_WRITE | BT_GATT_CHRC_INDICATE, \ + BT_GATT_PERM_WRITE, NULL, bt_gatt_ots_olcp_write, &_ots), \ + BT_GATT_CCC_MANAGED(&_ots.olcp_ind.ccc, BT_GATT_PERM_READ | BT_GATT_PERM_WRITE)} + +#define BT_GATT_OTS_INSTANCE_LIST_SIZE (ARRAY_SIZE(ots_instances)) +#define BT_GATT_OTS_INSTANCE_LIST_START ots_instances +#define BT_GATT_OTS_INSTANCE_LIST_END (&ots_instances[BT_GATT_OTS_INSTANCE_LIST_SIZE]) + +#define BT_GATT_OTS_SERVICE_LIST_START ots_service_list + +static struct bt_ots ots_instances[CONFIG_BT_OTS_MAX_INST_CNT]; +static uint32_t instance_cnt; +BT_GATT_SERVICE_INSTANCE_DEFINE(ots_service_list, + ots_instances, + CONFIG_BT_OTS_MAX_INST_CNT, + + BT_GATT_OTS_ATTRS); + +static void ots_delete_empty_name_objects(struct bt_ots *ots, + struct bt_conn *conn) +{ + char id_str[BT_OTS_OBJ_ID_STR_LEN]; + struct bt_gatt_ots_object *obj; + struct bt_gatt_ots_object *next_obj; + int err; + + err = bt_gatt_ots_obj_manager_first_obj_get(ots->obj_manager, &next_obj); + while (!err) + { + obj = next_obj; + + /* Get the next object before we potentially delete the current object + * and no longer can get the next object + */ + + err = bt_gatt_ots_obj_manager_next_obj_get(ots->obj_manager, obj, + &next_obj); + + if (strlen(obj->metadata.name) == 0) + { + bt_ots_obj_id_to_str(obj->id, id_str, sizeof(id_str)); + LOG_DBG("Deleting object with %s ID due to empty name", id_str); + + if (ots->cb && ots->cb->obj_deleted) + { + ots->cb->obj_deleted(ots, conn, obj->id); + } + + if (bt_gatt_ots_obj_manager_obj_delete(obj)) + { + LOG_ERR("Failed to remove object with %s ID from object " + "manager", + id_str); + } + } + } +} + +static void ots_conn_disconnected(struct bt_conn *conn, uint8_t reason) +{ + uint32_t index; + struct bt_ots *instance; + + for (instance = BT_GATT_OTS_INSTANCE_LIST_START, + index = 0; index < instance_cnt; + instance++, index++) + { + LOG_DBG("Processing disconnect for OTS instance %u", index); + + if (instance->cur_obj != NULL) + { + DEBUGASSERT(instance->cur_obj->state.type == + BT_GATT_OTS_OBJECT_IDLE_STATE); + instance->cur_obj = NULL; + } + + ots_delete_empty_name_objects(instance, conn); + } +} + +BT_CONN_CB_DEFINE(conn_callbacks) = +{ + .disconnected = ots_conn_disconnected, +}; + +struct bt_ots *bt_ots_free_instance_get(void) +{ + if (instance_cnt >= BT_GATT_OTS_INSTANCE_LIST_SIZE) + { + return NULL; + } + + return &BT_GATT_OTS_INSTANCE_LIST_START[instance_cnt++]; +} + +static int bt_gatt_ots_instances_prepare(void) +{ + uint32_t index; + struct bt_ots *instance; + + for (instance = BT_GATT_OTS_INSTANCE_LIST_START, index = 0; + instance != BT_GATT_OTS_INSTANCE_LIST_END; instance++, index++) + { + /* Assign an object pool to the OTS instance. */ + + instance->obj_manager = bt_gatt_ots_obj_manager_assign(); + + if (!instance->obj_manager) + { + LOG_ERR("OTS Object manager instance not available"); + return -ENOMEM; + } + + /* Assign pointer to the service descriptor. */ + + instance->service = &BT_GATT_OTS_SERVICE_LIST_START[index]; + + /* Initialize CCC descriptors for characteristics with + * indication properties. + */ + + instance->oacp_ind.ccc.cfg_changed = bt_gatt_ots_oacp_cfg_changed; + instance->olcp_ind.ccc.cfg_changed = bt_gatt_ots_olcp_cfg_changed; + } + + return 0; +} + +SYS_INIT(bt_gatt_ots_instances_prepare, + APPLICATION, + CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); diff --git a/libs/zblue/services/ots/ots_client.c b/libs/zblue/services/ots/ots_client.c new file mode 100644 index 0000000000000..9d661b49ed0d7 --- /dev/null +++ b/libs/zblue/services/ots/ots_client.c @@ -0,0 +1,2242 @@ +/**************************************************************************** + * libs/zblue/services/ots/ots_client.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief Bluetooth Object Transfer Client + * + * Copyright (c) 2020-2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include "ots_internal.h" +#include "ots_client_internal.h" +#include "ots_l2cap_internal.h" +#include "ots_dir_list_internal.h" +#include "ots_oacp_internal.h" +#include "ots_olcp_internal.h" + +#define LOG_LEVEL CONFIG_BT_OTS_CLIENT_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_otc); + +/* TODO: KConfig options */ +#define OTS_CLIENT_INST_COUNT 1 + +#define OTS_CLIENT_MAX_WRITE_SIZE 23 +/* 64-bit value, outside of 48-bit Object ID range */ +#define OTS_CLIENT_UNKNOWN_ID 0x0001000000000000 + +struct dirlisting_record_t +{ + uint16_t len; + uint8_t flags; + uint8_t name_len; + struct bt_ots_obj_metadata metadata; +}; + +/** @brief String literals for the OACP result codes. Used for logging + * output. + */ + +static const char *const lit_request[] = +{ + "RFU", "Create", "Delete", "Calculate Checksum", "Execute", "Read", + "Write", "Abort", +}; + +/** @brief String literals for the OACP result codes. Used for logging + * output. + */ + +static const char *const lit_result[] = +{ + "RFU", + "Success", + "Op Code Not Supported", + "Invalid Parameter", + "Insufficient Resources", + "Invalid Object", + "Channel Unavailable", + "Unsupported Type", + "Procedure Not Permitted", + "Object Locked", + "Operation Failed" +}; + +/** @brief String literals for the OLCP request codes. Used for logging + * output. + */ + +static const char *const lit_olcp_request[] = +{ + "RFU", "FIRST", "LAST", "PREV", "NEXT", "GOTO", "ORDER", + "REQ_NUM_OBJS", "CLEAR_MARKING", +}; + +/** @brief String literals for the OLCP result codes. Used for logging + * output. + */ + +static const char *const lit_olcp_result[] = +{ + "RFU", + "Success", + "Op Code Not Supported", + "Invalid Parameter", + "Operation Failed", + "Out of Bonds", + "Too Many Objects", + "No Object", + "Object ID not found", +}; + +struct bt_otc_internal_instance_t +{ + struct bt_ots_client *otc_inst; + struct bt_gatt_ots_l2cap l2cap_ctx; + bool busy; + + /** Bitfield that is used to determine how much metadata to read */ + + uint8_t metadata_to_read; + + /** Bitfield of how much metadata has been attempted to read */ + + uint8_t metadata_read_attempted; + + /** Bitfield of how much metadata has been read */ + + uint8_t metadata_read; + int metadata_err; + uint32_t rcvd_size; + uint32_t sent_size; +}; + +/* The profile clients that uses the OTS are responsible for discovery and + * will simply register any OTS instances as pointers, which is stored here. + */ + +static struct bt_otc_internal_instance_t otc_insts[OTS_CLIENT_INST_COUNT]; +NET_BUF_SIMPLE_DEFINE_STATIC(otc_tx_buf, OTS_CLIENT_MAX_WRITE_SIZE); +static struct bt_otc_internal_instance_t *cur_inst; + +static int oacp_read(struct bt_conn *conn, + struct bt_otc_internal_instance_t *inst); +static int oacp_write( + struct bt_conn *conn, struct bt_otc_internal_instance_t *inst, + const void *buf, uint32_t len, uint32_t offset, + enum bt_ots_oacp_write_op_mode mode); +static int oacp_checksum( + struct bt_conn *conn, struct bt_otc_internal_instance_t *inst, + uint32_t offset, uint32_t len); +static void read_next_metadata(struct bt_conn *conn, + struct bt_otc_internal_instance_t *inst); +static int read_attr( + struct bt_conn *conn, struct bt_otc_internal_instance_t *inst, + uint16_t handle, bt_gatt_read_func_t cb); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/* L2CAP callbacks */ + +static void tx_done(struct bt_gatt_ots_l2cap *l2cap_ctx, + struct bt_conn *conn) +{ + /* Not doing any writes yet */ + + LOG_ERR("Unexpected call, context: %p, conn: %p", l2cap_ctx, (void *)conn); +} + +static void write_obj_tx_done(struct bt_gatt_ots_l2cap *l2cap_ctx, + struct bt_conn *conn) +{ + int err; + size_t written; + + if (cur_inst == NULL) + { + LOG_ERR("OTS instance invalid\n"); + return; + } + + written = cur_inst->sent_size; + LOG_DBG("ctx: %p, conn: %p, written: " + "%d", l2cap_ctx, (void *)conn, written); + + err = bt_gatt_ots_l2cap_disconnect(l2cap_ctx); + if (err < 0) + { + LOG_WRN("Disconnecting L2CAP returned error %d", err); + } + + if ((cur_inst->otc_inst != NULL) && (cur_inst->otc_inst->cb != NULL)) + { + if (cur_inst->otc_inst->cb->obj_data_written) + { + cur_inst->otc_inst->cb->obj_data_written(0, conn, written); + } + } + + cur_inst = NULL; +} + +static ssize_t rx_done( + struct bt_gatt_ots_l2cap *l2cap_ctx, struct bt_conn *conn, + struct net_buf *buf) +{ + const uint32_t offset = cur_inst->rcvd_size; + bool is_complete = false; + const struct bt_ots_obj_metadata *cur_object = + &cur_inst->otc_inst->cur_object; + int cb_ret; + + LOG_DBG("Incoming L2CAP data, context: %p, conn: %p, len: %u, offset: %u", + l2cap_ctx, + (void *)conn, buf->len, offset); + + cur_inst->rcvd_size += buf->len; + + if (cur_inst->rcvd_size >= cur_object->size.cur) + { + is_complete = true; + } + + if (cur_inst->rcvd_size > cur_object->size.cur) + { + LOG_WRN("Received %u but expected maximum %u", cur_inst->rcvd_size, + cur_object->size.cur); + } + + cb_ret = cur_inst->otc_inst->cb->obj_data_read(0, conn, offset, buf->len, + buf->data, is_complete); + + if (is_complete) + { + const uint32_t rcv_size = cur_object->size.cur; + int err; + + LOG_DBG("Received the whole object (%u bytes). " + "Disconnecting L2CAP CoC", + rcv_size); + err = bt_gatt_ots_l2cap_disconnect(l2cap_ctx); + if (err < 0) + { + LOG_WRN("Disconnecting L2CAP returned error %d", err); + } + + cur_inst = NULL; + } + else if (cb_ret == BT_OTS_STOP) + { + const uint32_t rcv_size = cur_object->size.cur; + int err; + + LOG_DBG("Stopped receiving after%u bytes. " + "Disconnecting L2CAP CoC", + rcv_size); + err = bt_gatt_ots_l2cap_disconnect(l2cap_ctx); + + if (err < 0) + { + LOG_WRN("Disconnecting L2CAP returned error %d", err); + } + + cur_inst = NULL; + } + + return 0; +} + +static void chan_closed(struct bt_gatt_ots_l2cap *l2cap_ctx, + struct bt_conn *conn) +{ + LOG_DBG("L2CAP closed, context: %p, conn: %p", l2cap_ctx, (void *)conn); + if (cur_inst) + { + cur_inst = NULL; + } +} + +/* End L2CAP callbacks */ + +static void print_oacp_response(enum bt_gatt_ots_oacp_proc_type req_opcode, + enum bt_gatt_ots_oacp_res_code result_code) +{ + LOG_DBG("Request OP Code: %s", lit_request[req_opcode]); + LOG_DBG("Result Code : %s", lit_result[result_code]); +} + +static void print_olcp_response(enum bt_gatt_ots_olcp_proc_type req_opcode, + enum bt_gatt_ots_olcp_res_code result_code) +{ + LOG_DBG("Request OP Code: %s", lit_olcp_request[req_opcode]); + LOG_DBG("Result Code : %s", lit_olcp_result[result_code]); +} + +static void date_time_decode(struct net_buf_simple *buf, + struct bt_ots_date_time *p_date_time) +{ + p_date_time->year = net_buf_simple_pull_le16(buf); + p_date_time->month = net_buf_simple_pull_u8(buf); + p_date_time->day = net_buf_simple_pull_u8(buf); + p_date_time->hours = net_buf_simple_pull_u8(buf); + p_date_time->minutes = net_buf_simple_pull_u8(buf); + p_date_time->seconds = net_buf_simple_pull_u8(buf); +} + +static struct bt_otc_internal_instance_t *lookup_inst_by_handle( + uint16_t handle) +{ + for (int i = 0; i < ARRAY_SIZE(otc_insts); i++) + { + if (otc_insts[i].otc_inst && + otc_insts[i].otc_inst->start_handle <= handle && + otc_insts[i].otc_inst->end_handle >= handle) + { + return &otc_insts[i]; + } + } + + LOG_DBG("Could not find OTS instance with handle 0x%04x", handle); + + return NULL; +} + +static void on_object_selected( + struct bt_conn *conn, enum bt_gatt_ots_olcp_res_code res, + struct bt_ots_client *otc_inst) +{ + memset(&otc_inst->cur_object, 0, sizeof(otc_inst->cur_object)); + otc_inst->cur_object.id = OTS_CLIENT_UNKNOWN_ID; + + if (otc_inst->cb->obj_selected) + { + otc_inst->cb->obj_selected(otc_inst, conn, res); + } + + LOG_DBG("Object selected"); +} + +static void olcp_ind_handler( + struct bt_conn *conn, struct bt_ots_client *otc_inst, const void *data, + uint16_t length) +{ + enum bt_gatt_ots_olcp_proc_type op_code; + struct net_buf_simple net_buf; + + if (length < sizeof(op_code)) + { + LOG_DBG("Invalid indication length: %u", length); + return; + } + + net_buf_simple_init_with_data(&net_buf, (void *)data, length); + + op_code = net_buf_simple_pull_u8(&net_buf); + + LOG_DBG("OLCP indication"); + + if (op_code == BT_GATT_OTS_OLCP_PROC_RESP) + { + if (net_buf.len < (sizeof(uint8_t) + sizeof(uint8_t))) + { + LOG_DBG("Invalid indication length for op_code %u: %u", op_code, + net_buf.len); + return; + } + + enum bt_gatt_ots_olcp_proc_type req_opcode = + net_buf_simple_pull_u8(&net_buf); + enum bt_gatt_ots_olcp_res_code result_code = + net_buf_simple_pull_u8(&net_buf); + + print_olcp_response(req_opcode, result_code); + + switch (req_opcode) + { + case BT_GATT_OTS_OLCP_PROC_FIRST: + LOG_DBG("First"); + on_object_selected(conn, result_code, otc_inst); + break; + case BT_GATT_OTS_OLCP_PROC_LAST: + LOG_DBG("Last"); + on_object_selected(conn, result_code, otc_inst); + break; + case BT_GATT_OTS_OLCP_PROC_PREV: + LOG_DBG("Previous"); + on_object_selected(conn, result_code, otc_inst); + break; + case BT_GATT_OTS_OLCP_PROC_NEXT: + LOG_DBG("Next"); + on_object_selected(conn, result_code, otc_inst); + break; + case BT_GATT_OTS_OLCP_PROC_GOTO: + LOG_DBG("Goto"); + on_object_selected(conn, result_code, otc_inst); + break; + case BT_GATT_OTS_OLCP_PROC_ORDER: + LOG_DBG("Order"); + on_object_selected(conn, result_code, otc_inst); + break; + case BT_GATT_OTS_OLCP_PROC_REQ_NUM_OBJS: + LOG_DBG("Request number of objects"); + if (net_buf.len == sizeof(uint32_t)) + { + uint32_t obj_cnt = net_buf_simple_pull_le32(&net_buf); + + LOG_DBG("Number of objects %u", obj_cnt); + } + break; + case BT_GATT_OTS_OLCP_PROC_CLEAR_MARKING: + LOG_DBG("Clear marking"); + break; + default: + LOG_DBG("Invalid indication req opcode %u", req_opcode); + break; + } + } + else + { + LOG_DBG("Invalid indication opcode %u", op_code); + } +} + +static void oacp_ind_handler( + struct bt_conn *conn, struct bt_ots_client *otc_inst, const void *data, + uint16_t length) +{ + enum bt_gatt_ots_oacp_proc_type op_code; + enum bt_gatt_ots_oacp_proc_type req_opcode; + enum bt_gatt_ots_oacp_res_code result_code; + uint32_t checksum; + struct net_buf_simple net_buf; + + if (length < sizeof(op_code)) + { + LOG_DBG("Invalid indication length: %u", length); + return; + } + + net_buf_simple_init_with_data(&net_buf, (void *)data, length); + + op_code = net_buf_simple_pull_u8(&net_buf); + + LOG_DBG("OACP indication"); + + if (op_code == BT_GATT_OTS_OACP_PROC_RESP) + { + if (net_buf.len >= (sizeof(req_opcode) + sizeof(result_code))) + { + req_opcode = net_buf_simple_pull_u8(&net_buf); + result_code = net_buf_simple_pull_u8(&net_buf); + } + else + { + LOG_ERR("Invalid indication data len %u", net_buf.len); + return; + } + + if (req_opcode == BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC) + { + if (result_code == BT_GATT_OTS_OACP_RES_SUCCESS) + { + if (net_buf.len == sizeof(checksum)) + { + checksum = net_buf_simple_pull_le32(&net_buf); + LOG_DBG("Object checksum 0x%08x", checksum); + } + else + { + LOG_DBG("Invalid indication data len %u after opcode and " + "result pulled", + net_buf.len); + return; + } + } + else + { + /* The checksum is omitted in error responses */ + + checksum = 0U; + } + + if (otc_inst->cb != NULL + && otc_inst->cb->obj_checksum_calculated != NULL) + { + otc_inst->cb->obj_checksum_calculated(otc_inst, conn, + result_code, checksum); + } + } + + print_oacp_response(req_opcode, result_code); + } + else + { + LOG_DBG("Invalid indication opcode %u", op_code); + } +} + +uint8_t bt_ots_client_indicate_handler(struct bt_conn *conn, + struct bt_gatt_subscribe_params *params, + const void *data, + uint16_t length) +{ + uint16_t handle = params->value_handle; + struct bt_otc_internal_instance_t *inst; + + if (conn == NULL) + { + return BT_GATT_ITER_CONTINUE; + } + + inst = lookup_inst_by_handle(handle); + + /* TODO: Can we somehow avoid exposing this + * callback via the public API? + */ + + if (!inst) + { + LOG_ERR("Instance not found"); + return BT_GATT_ITER_STOP; + } + + inst->busy = false; + + if (data) + { + if (handle == inst->otc_inst->olcp_handle) + { + olcp_ind_handler(conn, inst->otc_inst, data, length); + } + else if (handle == inst->otc_inst->oacp_handle) + { + oacp_ind_handler(conn, inst->otc_inst, data, length); + } + } + + return BT_GATT_ITER_CONTINUE; +} + +static uint8_t read_feature_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + uint8_t cb_err = err; + struct bt_otc_internal_instance_t *inst = + lookup_inst_by_handle(params->single.handle); + struct net_buf_simple net_buf; + + net_buf_simple_init_with_data(&net_buf, (void *)data, length); + + if (!inst) + { + LOG_ERR("Instance not found"); + return BT_GATT_ITER_STOP; + } + + inst->busy = false; + + if (err) + { + LOG_DBG("err: 0x%02X", err); + } + else if (data) + { + if (length == OTS_FEATURE_LEN) + { + inst->otc_inst->features.oacp = net_buf_simple_pull_le32(&net_buf); + + inst->otc_inst->features.olcp = net_buf_simple_pull_le32(&net_buf); + + LOG_DBG("features : oacp 0x%x, olcp 0x%x", + inst->otc_inst->features.oacp, + inst->otc_inst->features.olcp); + } + else + { + LOG_DBG("Invalid length %u (expected %u)", + length, + OTS_FEATURE_LEN); + cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + } + + return BT_GATT_ITER_STOP; +} + +int bt_ots_client_register(struct bt_ots_client *otc_inst) +{ + for (int i = 0; i < ARRAY_SIZE(otc_insts); i++) + { + int err; + + if (otc_insts[i].otc_inst) + { + continue; + } + + LOG_DBG("%u", i); + err = bt_gatt_ots_l2cap_register(&otc_insts[i].l2cap_ctx); + if (err) + { + LOG_WRN("Could not register L2CAP context %d", err); + return err; + } + + otc_insts[i].otc_inst = otc_inst; + return 0; + } + + return -ENOMEM; +} + +int bt_ots_client_unregister(uint8_t index) +{ + if (index < ARRAY_SIZE(otc_insts)) + { + memset(&otc_insts[index], 0, sizeof(otc_insts[index])); + } + else + { + return -EINVAL; + } + + return 0; +} + +int bt_ots_client_read_feature(struct bt_ots_client *otc_inst, + struct bt_conn *conn) +{ + if (OTS_CLIENT_INST_COUNT > 0) + { + struct bt_otc_internal_instance_t *inst; + int err; + + if (!conn) + { + LOG_WRN("Invalid Connection"); + return -ENOTCONN; + } + else if (!otc_inst) + { + LOG_ERR("Invalid OTC instance"); + return -EINVAL; + } + else if (!otc_inst->feature_handle) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + + inst = lookup_inst_by_handle(otc_inst->start_handle); + + if (!inst) + { + LOG_ERR("Invalid OTC instance"); + return -EINVAL; + } + else if (inst->busy) + { + return -EBUSY; + } + + otc_inst->read_proc.func = read_feature_cb; + otc_inst->read_proc.handle_count = 1; + otc_inst->read_proc.single.handle = otc_inst->feature_handle; + otc_inst->read_proc.single.offset = 0U; + + err = bt_gatt_read(conn, &otc_inst->read_proc); + if (!err) + { + inst->busy = true; + } + + return err; + } + + LOG_DBG("Not supported"); + return -EOPNOTSUPP; +} + +static void write_olcp_cb(struct bt_conn *conn, + uint8_t err, struct bt_gatt_write_params *params) +{ + struct bt_otc_internal_instance_t *inst = + lookup_inst_by_handle(params->handle); + + LOG_DBG("Write %s (0x%02X)", err ? "failed" : "successful", err); + + if (!inst) + { + LOG_ERR("Instance not found"); + return; + } + + inst->busy = false; +} + +static int write_olcp( + struct bt_otc_internal_instance_t *inst, struct bt_conn *conn, + enum bt_gatt_ots_olcp_proc_type opcode, uint8_t *params, uint8_t param_len) +{ + int err; + + net_buf_simple_reset(&otc_tx_buf); + + net_buf_simple_add_u8(&otc_tx_buf, opcode); + + if (param_len && params) + { + net_buf_simple_add_mem(&otc_tx_buf, params, param_len); + } + + inst->otc_inst->write_params.offset = 0; + inst->otc_inst->write_params.data = otc_tx_buf.data; + inst->otc_inst->write_params.length = otc_tx_buf.len; + inst->otc_inst->write_params.handle = inst->otc_inst->olcp_handle; + inst->otc_inst->write_params.func = write_olcp_cb; + + err = bt_gatt_write(conn, &inst->otc_inst->write_params); + + if (!err) + { + inst->busy = true; + } + + return err; +} + +int bt_ots_client_select_id(struct bt_ots_client *otc_inst, + struct bt_conn *conn, uint64_t obj_id) +{ + if (!BT_OTS_VALID_OBJ_ID(obj_id)) + { + LOG_DBG("Invalid object ID 0x%016llx", obj_id); + + return -EINVAL; + } + + if (OTS_CLIENT_INST_COUNT > 0) + { + struct bt_otc_internal_instance_t *inst; + uint8_t param[BT_OTS_OBJ_ID_SIZE]; + + if (!conn) + { + LOG_WRN("Invalid Connection"); + return -ENOTCONN; + } + else if (!otc_inst) + { + LOG_ERR("Invalid OTC instance"); + return -EINVAL; + } + else if (!otc_inst->olcp_handle) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + + inst = lookup_inst_by_handle(otc_inst->start_handle); + + if (!inst) + { + LOG_ERR("Invalid OTC instance"); + return -EINVAL; + } + else if (inst->busy) + { + return -EBUSY; + } + + /* TODO: Should not update this before ID is read */ + + otc_inst->cur_object.id = obj_id; + sys_put_le48(obj_id, param); + + return write_olcp(inst, conn, BT_GATT_OTS_OLCP_PROC_GOTO, param, + BT_OTS_OBJ_ID_SIZE); + } + + LOG_DBG("Not supported"); + return -EOPNOTSUPP; +} + +int bt_ots_client_select_first(struct bt_ots_client *otc_inst, + struct bt_conn *conn) +{ + if (OTS_CLIENT_INST_COUNT > 0) + { + struct bt_otc_internal_instance_t *inst; + + if (!conn) + { + LOG_WRN("Invalid Connection"); + return -ENOTCONN; + } + else if (!otc_inst) + { + LOG_ERR("Invalid OTC instance"); + return -EINVAL; + } + else if (!otc_inst->olcp_handle) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + + inst = lookup_inst_by_handle(otc_inst->start_handle); + + if (!inst) + { + LOG_ERR("Invalid OTC instance"); + return -EINVAL; + } + else if (inst->busy) + { + return -EBUSY; + } + + return write_olcp(inst, conn, BT_GATT_OTS_OLCP_PROC_FIRST, NULL, 0); + } + + LOG_DBG("Not supported"); + return -EOPNOTSUPP; +} + +int bt_ots_client_select_last(struct bt_ots_client *otc_inst, + struct bt_conn *conn) +{ + if (OTS_CLIENT_INST_COUNT > 0) + { + struct bt_otc_internal_instance_t *inst; + + if (!conn) + { + LOG_WRN("Invalid Connection"); + return -ENOTCONN; + } + else if (!otc_inst) + { + LOG_ERR("Invalid OTC instance"); + return -EINVAL; + } + else if (!otc_inst->olcp_handle) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + + inst = lookup_inst_by_handle(otc_inst->start_handle); + + if (!inst) + { + LOG_ERR("Invalid OTC instance"); + return -EINVAL; + } + else if (inst->busy) + { + return -EBUSY; + } + + return write_olcp(inst, conn, BT_GATT_OTS_OLCP_PROC_LAST, NULL, 0); + } + + LOG_DBG("Not supported"); + return -EOPNOTSUPP; +} + +int bt_ots_client_select_next(struct bt_ots_client *otc_inst, + struct bt_conn *conn) +{ + if (OTS_CLIENT_INST_COUNT > 0) + { + struct bt_otc_internal_instance_t *inst; + + if (!conn) + { + LOG_WRN("Invalid Connection"); + return -ENOTCONN; + } + else if (!otc_inst) + { + LOG_ERR("Invalid OTC instance"); + return -EINVAL; + } + else if (!otc_inst->olcp_handle) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + + inst = lookup_inst_by_handle(otc_inst->start_handle); + + if (!inst) + { + LOG_ERR("Invalid OTC instance"); + return -EINVAL; + } + else if (inst->busy) + { + return -EBUSY; + } + + return write_olcp(inst, conn, BT_GATT_OTS_OLCP_PROC_NEXT, NULL, 0); + } + + LOG_DBG("Not supported"); + return -EOPNOTSUPP; +} + +int bt_ots_client_select_prev(struct bt_ots_client *otc_inst, + struct bt_conn *conn) +{ + if (OTS_CLIENT_INST_COUNT > 0) + { + struct bt_otc_internal_instance_t *inst; + + if (!conn) + { + LOG_WRN("Invalid Connection"); + return -ENOTCONN; + } + else if (!otc_inst) + { + LOG_ERR("Invalid OTC instance"); + return -EINVAL; + } + else if (!otc_inst->olcp_handle) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + + inst = lookup_inst_by_handle(otc_inst->start_handle); + + if (!inst) + { + LOG_ERR("Invalid OTC instance"); + return -EINVAL; + } + else if (inst->busy) + { + return -EBUSY; + } + + return write_olcp(inst, conn, BT_GATT_OTS_OLCP_PROC_PREV, NULL, 0); + } + + LOG_DBG("Not supported"); + return -EOPNOTSUPP; +} + +static uint8_t read_object_size_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct bt_otc_internal_instance_t *inst = + lookup_inst_by_handle(params->single.handle); + struct net_buf_simple net_buf; + + net_buf_simple_init_with_data(&net_buf, (void *)data, length); + + LOG_DBG("handle %d, length %u", params->single.handle, length); + + if (!inst) + { + LOG_ERR("Instance not found"); + return BT_GATT_ITER_STOP; + } + + if (err) + { + LOG_DBG("err: 0x%02X", err); + } + else if (data) + { + if (length != OTS_SIZE_LEN) + { + LOG_DBG("Invalid length %u (expected %u)", length, OTS_SIZE_LEN); + err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + else + { + struct bt_ots_obj_metadata *cur_object = + &inst->otc_inst->cur_object; + + cur_object->size.cur = net_buf_simple_pull_le32(&net_buf); + cur_object->size.alloc = net_buf_simple_pull_le32(&net_buf); + + LOG_DBG("Object Size : current size %u, " + "allocated size %u", + cur_object->size.cur, cur_object->size.alloc); + + if (cur_object->size.cur == 0) + { + LOG_WRN("Obj size read returned a current " + "size of 0"); + } + else if (cur_object->size.cur > cur_object->size.alloc && + cur_object->size.alloc != 0) + { + LOG_WRN("Allocated size %u is smaller than " + "current size %u", + cur_object->size.alloc, cur_object->size.cur); + } + + BT_OTS_SET_METADATA_REQ_SIZE(inst->metadata_read); + } + } + + if (err) + { + LOG_WRN("err: 0x%02X", err); + if (!inst->metadata_err) + { + inst->metadata_err = err; + } + } + + read_next_metadata(conn, inst); + + return BT_GATT_ITER_STOP; +} + +static uint8_t read_obj_id_cb( + struct bt_conn *conn, uint8_t err, struct bt_gatt_read_params *params, + const void *data, uint16_t length) +{ + struct bt_otc_internal_instance_t *inst = + lookup_inst_by_handle(params->single.handle); + struct net_buf_simple net_buf; + + net_buf_simple_init_with_data(&net_buf, (void *)data, length); + + LOG_DBG("handle %d, length %u", params->single.handle, length); + + if (!inst) + { + LOG_ERR("Instance not found"); + return BT_GATT_ITER_STOP; + } + + if (err) + { + LOG_DBG("err: 0x%02X", err); + } + else if (data) + { + if (length == BT_OTS_OBJ_ID_SIZE) + { + uint64_t obj_id = net_buf_simple_pull_le48(&net_buf); + char t[BT_OTS_OBJ_ID_STR_LEN]; + struct bt_ots_obj_metadata *cur_object = + &inst->otc_inst->cur_object; + + (void)bt_ots_obj_id_to_str(obj_id, t, sizeof(t)); + LOG_DBG("Object Id : %s", t); + + if (cur_object->id != OTS_CLIENT_UNKNOWN_ID && + cur_object->id != obj_id) + { + char str[BT_OTS_OBJ_ID_STR_LEN]; + + (void)bt_ots_obj_id_to_str(cur_object->id, str, sizeof(str)); + LOG_INF("Read Obj Id %s not selected obj Id %s", t, str); + } + else + { + LOG_INF("Read Obj Id confirmed correct Obj Id"); + cur_object->id = obj_id; + + BT_OTS_SET_METADATA_REQ_ID(inst->metadata_read); + } + } + else + { + LOG_DBG("Invalid length %u (expected %u)", length, + BT_OTS_OBJ_ID_SIZE); + err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + } + + if (err) + { + LOG_WRN("err: 0x%02X", err); + if (!inst->metadata_err) + { + inst->metadata_err = err; + } + } + + read_next_metadata(conn, inst); + + return BT_GATT_ITER_STOP; +} + +static uint8_t read_obj_name_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct bt_otc_internal_instance_t *inst = + lookup_inst_by_handle(params->single.handle); + + LOG_DBG("handle %d, length %u", params->single.handle, length); + + if (!inst) + { + LOG_ERR("Instance not found"); + return BT_GATT_ITER_STOP; + } + + if (data) + { + if (length <= CONFIG_BT_OTS_OBJ_MAX_NAME_LEN) + { + memcpy(inst->otc_inst->cur_object.name_c, data, length); + inst->otc_inst->cur_object.name_c[length] = '\0'; + } + else + { + LOG_WRN("Invalid length %u (expected max %u)", length, + CONFIG_BT_OTS_OBJ_MAX_NAME_LEN); + err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + } + + if (err) + { + LOG_WRN("err: 0x%02X", err); + if (!inst->metadata_err) + { + inst->metadata_err = err; + } + } + + read_next_metadata(conn, inst); + + return BT_GATT_ITER_STOP; +} + +static uint8_t read_obj_type_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct bt_otc_internal_instance_t *inst = + lookup_inst_by_handle(params->single.handle); + + LOG_DBG("handle %d, length %u", params->single.handle, length); + + if (!inst) + { + LOG_ERR("Instance not found"); + return BT_GATT_ITER_STOP; + } + + if (data) + { + if (length == BT_UUID_SIZE_128 || length == BT_UUID_SIZE_16) + { + char uuid_str[BT_UUID_STR_LEN]; + struct bt_uuid *uuid = &inst->otc_inst->cur_object.type.uuid; + + if (!bt_uuid_create(uuid, data, length)) + { + return BT_GATT_ITER_STOP; + } + + bt_uuid_to_str(uuid, uuid_str, sizeof(uuid_str)); + LOG_DBG("UUID type read: %s", uuid_str); + + BT_OTS_SET_METADATA_REQ_TYPE(inst->metadata_read); + } + else + { + LOG_WRN("Invalid length %u (expected max %u)", length, + OTS_TYPE_MAX_LEN); + err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + } + + if (err) + { + LOG_WRN("err: 0x%02X", err); + if (!inst->metadata_err) + { + inst->metadata_err = err; + } + } + + read_next_metadata(conn, inst); + + return BT_GATT_ITER_STOP; +} + +static uint8_t read_obj_created_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct bt_otc_internal_instance_t *inst = + lookup_inst_by_handle(params->single.handle); + struct net_buf_simple net_buf; + + net_buf_simple_init_with_data(&net_buf, (void *)data, length); + + LOG_DBG("handle %d, length %u", params->single.handle, length); + + if (!inst) + { + LOG_ERR("Instance not found"); + return BT_GATT_ITER_STOP; + } + + if (data) + { + if (length <= BT_OTS_DATE_TIME_FIELD_SIZE) + { + date_time_decode(&net_buf, + &inst->otc_inst->cur_object.first_created); + } + else + { + LOG_WRN("Invalid length %u (expected max %u)", length, + BT_OTS_DATE_TIME_FIELD_SIZE); + err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + } + + if (err) + { + LOG_WRN("err: 0x%02X", err); + if (!inst->metadata_err) + { + inst->metadata_err = err; + } + } + + read_next_metadata(conn, inst); + + return BT_GATT_ITER_STOP; +} + +static uint8_t read_obj_modified_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + struct bt_otc_internal_instance_t *inst = + lookup_inst_by_handle(params->single.handle); + struct net_buf_simple net_buf; + + net_buf_simple_init_with_data(&net_buf, (void *)data, length); + + LOG_DBG("handle %d, length %u", params->single.handle, length); + + if (!inst) + { + LOG_ERR("Instance not found"); + return BT_GATT_ITER_STOP; + } + + if (data) + { + if (length <= BT_OTS_DATE_TIME_FIELD_SIZE) + { + date_time_decode(&net_buf, &inst->otc_inst->cur_object.modified); + } + else + { + LOG_WRN("Invalid length %u (expected max %u)", length, + BT_OTS_DATE_TIME_FIELD_SIZE); + err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + } + + if (err) + { + LOG_WRN("err: 0x%02X", err); + if (!inst->metadata_err) + { + inst->metadata_err = err; + } + } + + read_next_metadata(conn, inst); + + return BT_GATT_ITER_STOP; +} + +static int read_attr( + struct bt_conn *conn, struct bt_otc_internal_instance_t *inst, + uint16_t handle, bt_gatt_read_func_t cb) +{ + if (!handle) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + else if (cb == NULL) + { + LOG_ERR("No callback set"); + return -EINVAL; + } + + /* TODO: With EATT we can request multiple metadata to be read at once */ + + inst->otc_inst->read_proc.func = cb; + inst->otc_inst->read_proc.handle_count = 1; + inst->otc_inst->read_proc.single.handle = handle; + inst->otc_inst->read_proc.single.offset = 0; + + return bt_gatt_read(conn, &inst->otc_inst->read_proc); +} + +static uint8_t read_obj_properties_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_read_params *params, + const void *data, + uint16_t length) +{ + uint8_t cb_err = err; + struct bt_otc_internal_instance_t *inst = + lookup_inst_by_handle(params->single.handle); + struct net_buf_simple net_buf; + + net_buf_simple_init_with_data(&net_buf, (void *)data, length); + + LOG_INF("handle %d, length %u", params->single.handle, length); + + if (!inst) + { + LOG_ERR("Instance not found"); + return BT_GATT_ITER_STOP; + } + + if (err) + { + LOG_WRN("err: 0x%02X", err); + } + else if (data && length <= OTS_PROPERTIES_LEN) + { + struct bt_ots_obj_metadata *cur_object = &inst->otc_inst->cur_object; + + cur_object->props = net_buf_simple_pull_le32(&net_buf); + + LOG_INF("Object properties (raw) : 0x%x", cur_object->props); + + if (!BT_OTS_OBJ_GET_PROP_READ(cur_object->props)) + { + LOG_WRN("Obj properties: Obj read not supported"); + } + + BT_OTS_SET_METADATA_REQ_PROPS(inst->metadata_read); + } + else + { + LOG_WRN("Invalid length %u (expected %u)", length, OTS_PROPERTIES_LEN); + cb_err = BT_ATT_ERR_INVALID_ATTRIBUTE_LEN; + } + + if (err) + { + LOG_WRN("err: 0x%02X", err); + if (!inst->metadata_err) + { + inst->metadata_err = err; + } + } + + read_next_metadata(conn, inst); + + return BT_GATT_ITER_STOP; +} + +static void write_oacp_cp_cb(struct bt_conn *conn, + uint8_t err, + struct bt_gatt_write_params *params) +{ + struct bt_otc_internal_instance_t *inst = + lookup_inst_by_handle(params->handle); + + LOG_DBG("Write %s (0x%02X)", err ? "failed" : "successful", err); + + if (!inst) + { + LOG_ERR("Instance not found"); + return; + } + + inst->busy = false; +} + +static void write_oacp_cp_write_req_cb(struct bt_conn *conn, uint8_t err, + struct bt_gatt_write_params *params) +{ + struct bt_otc_internal_instance_t *inst = + lookup_inst_by_handle(params->handle); + uint32_t len; + + LOG_DBG("Write Object request %s (0x%02X)", err ? "failed" : "successful", + err); + if (!inst) + { + LOG_ERR("Instance not found"); + return; + } + + len = inst->l2cap_ctx.tx.len; + inst->l2cap_ctx.tx.len = 0; + err = bt_gatt_ots_l2cap_send(&inst->l2cap_ctx, inst->l2cap_ctx.tx.data, + len); + if (err) + { + LOG_WRN("L2CAP CoC error: %d while trying to execute OACP " + "Read procedure", + err); + } + + inst->busy = false; +} + +static int oacp_read(struct bt_conn *conn, + struct bt_otc_internal_instance_t *inst) +{ + int err; + uint32_t offset = 0; + uint32_t length; + struct bt_gatt_ots_l2cap *l2cap; + + if (!inst->otc_inst->oacp_handle) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + else if (inst->busy) + { + return -EBUSY; + } + else if (cur_inst) + { + return -EBUSY; + } + + /* TODO: How do we ensure that the L2CAP is connected in time for the + * transfer? + */ + + err = bt_gatt_ots_l2cap_connect(conn, &l2cap); + if (err) + { + LOG_DBG("Could not connect l2cap: %d", err); + return err; + } + + l2cap->tx_done = tx_done; + l2cap->rx_done = rx_done; + l2cap->closed = chan_closed; + + net_buf_simple_reset(&otc_tx_buf); + + /* OP Code */ + + net_buf_simple_add_u8(&otc_tx_buf, BT_GATT_OTS_OACP_PROC_READ); + + /* Offset */ + + net_buf_simple_add_le32(&otc_tx_buf, offset); + + /* Len */ + + length = inst->otc_inst->cur_object.size.cur - offset; + net_buf_simple_add_le32(&otc_tx_buf, length); + + inst->otc_inst->write_params.offset = 0; + inst->otc_inst->write_params.data = otc_tx_buf.data; + inst->otc_inst->write_params.length = otc_tx_buf.len; + inst->otc_inst->write_params.handle = inst->otc_inst->oacp_handle; + inst->otc_inst->write_params.func = write_oacp_cp_cb; + + err = bt_gatt_write(conn, &inst->otc_inst->write_params); + + if (!err) + { + inst->busy = true; + } + + cur_inst = inst; + inst->rcvd_size = 0; + + return err; +} + +static int oacp_write( + struct bt_conn *conn, struct bt_otc_internal_instance_t *inst, + const void *buf, uint32_t len, uint32_t offset, + enum bt_ots_oacp_write_op_mode mode) +{ + int err; + struct bt_gatt_ots_l2cap *l2cap; + + if (!inst->otc_inst->oacp_handle) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + else if (inst->busy) + { + return -EBUSY; + } + else if (cur_inst) + { + return -EBUSY; + } + + err = bt_gatt_ots_l2cap_connect(conn, &l2cap); + if (err) + { + LOG_DBG("Could not connect l2cap: %d", err); + return err; + } + + l2cap->tx_done = write_obj_tx_done; + l2cap->rx_done = rx_done; + l2cap->closed = chan_closed; + l2cap->tx.data = (uint8_t *)buf; + l2cap->tx.len = len; + net_buf_simple_reset(&otc_tx_buf); + + /* OP Code */ + + net_buf_simple_add_u8(&otc_tx_buf, BT_GATT_OTS_OACP_PROC_WRITE); + + /* Offset */ + + net_buf_simple_add_le32(&otc_tx_buf, offset); + + /* Len */ + + net_buf_simple_add_le32(&otc_tx_buf, len); + + /* Mode, truncate or not */ + + net_buf_simple_add_u8(&otc_tx_buf, mode); + + inst->otc_inst->write_params.offset = 0; + inst->otc_inst->write_params.data = otc_tx_buf.data; + inst->otc_inst->write_params.length = otc_tx_buf.len; + inst->otc_inst->write_params.handle = inst->otc_inst->oacp_handle; + inst->otc_inst->write_params.func = write_oacp_cp_write_req_cb; + inst->sent_size = len; + err = bt_gatt_write(conn, &inst->otc_inst->write_params); + + if (!err) + { + inst->busy = true; + cur_inst = inst; + } + + inst->rcvd_size = 0; + + return err; +} + +static int oacp_checksum( + struct bt_conn *conn, struct bt_otc_internal_instance_t *inst, + uint32_t offset, uint32_t len) +{ + int err; + + if (!inst->otc_inst->oacp_handle) + { + LOG_DBG("Handle not set"); + return -EINVAL; + } + else if (inst->busy) + { + LOG_DBG("Client is busy"); + return -EBUSY; + } + else if (cur_inst) + { + LOG_DBG("Previous operation is not finished"); + return -EBUSY; + } + + net_buf_simple_reset(&otc_tx_buf); + + /* OP Code */ + + net_buf_simple_add_u8(&otc_tx_buf, BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC); + + /* Offset */ + + net_buf_simple_add_le32(&otc_tx_buf, offset); + + /* Len */ + + net_buf_simple_add_le32(&otc_tx_buf, len); + + inst->otc_inst->write_params.offset = 0; + inst->otc_inst->write_params.data = otc_tx_buf.data; + inst->otc_inst->write_params.length = otc_tx_buf.len; + inst->otc_inst->write_params.handle = inst->otc_inst->oacp_handle; + inst->otc_inst->write_params.func = write_oacp_cp_cb; + + err = bt_gatt_write(conn, &inst->otc_inst->write_params); + if (err != 0) + { + inst->busy = true; + cur_inst = inst; + } + + return err; +} + +int bt_ots_client_read_object_data(struct bt_ots_client *otc_inst, + struct bt_conn *conn) +{ + struct bt_otc_internal_instance_t *inst; + + if (!conn) + { + LOG_WRN("Invalid Connection"); + return -ENOTCONN; + } + else if (!otc_inst) + { + LOG_ERR("Invalid OTC instance"); + return -EINVAL; + } + + inst = lookup_inst_by_handle(otc_inst->start_handle); + + if (!inst) + { + LOG_ERR("Invalid OTC instance"); + return -EINVAL; + } + + if (otc_inst->cur_object.size.cur == 0) + { + LOG_WRN("Unknown object size"); + return -EINVAL; + } + + return oacp_read(conn, inst); +} + +int bt_ots_client_write_object_data( + struct bt_ots_client *otc_inst, struct bt_conn *conn, const void *buf, + size_t len, off_t offset, enum bt_ots_oacp_write_op_mode mode) +{ + struct bt_otc_internal_instance_t *inst; + + if (!conn) + { + LOG_WRN("Invalid Connection"); + return -ENOTCONN; + } + + if (!otc_inst) + { + LOG_ERR("Invalid OTC instance"); + return -EINVAL; + } + + if ((mode != BT_OTS_OACP_WRITE_OP_MODE_NONE) && + (mode != BT_OTS_OACP_WRITE_OP_MODE_TRUNCATE)) + { + LOG_ERR("Invalid write object mode parameter %d", mode); + return -EINVAL; + } + + /* OTS_v10.pdf Table 3.9: Object Action Control Point Procedure + * Requirements Offset and Length field are UINT32 Length + */ + + if (len > UINT32_MAX) + { + LOG_ERR("length %zu exceeds UINT32", len); + return -EINVAL; + } + + if (len == 0) + { + LOG_ERR("length equals zero"); + return -EINVAL; + } + + if ((sizeof(offset) > sizeof(uint32_t) && (offset > UINT32_MAX)) || + (offset < 0)) + { + LOG_ERR("offset %ld exceeds UINT32 and must be >= 0", (long)offset); + return -EINVAL; + } + + if (offset > otc_inst->cur_object.size.cur) + { + LOG_ERR("offset %ld exceeds cur size %zu", (long)offset, + otc_inst->cur_object.size.cur); + return -EINVAL; + } + + if ((offset < otc_inst->cur_object.size.cur) && + !BT_OTS_OBJ_GET_PROP_PATCH(otc_inst->cur_object.props)) + { + LOG_ERR("Patch is not supported"); + return -EACCES; + } + + if (((len + offset) > otc_inst->cur_object.size.alloc) && + !BT_OTS_OBJ_GET_PROP_APPEND(otc_inst->cur_object.props)) + { + LOG_ERR("APPEND is not supported. Invalid new end of object %lu alloc " + "%zu.", + (len + (long)offset), otc_inst->cur_object.size.alloc); + return -EINVAL; + } + + inst = lookup_inst_by_handle(otc_inst->start_handle); + + if (!inst) + { + LOG_ERR("Invalid OTC instance"); + return -EINVAL; + } + + return oacp_write(conn, inst, buf, (uint32_t)len, (uint32_t)offset, mode); +} + +int bt_ots_client_get_object_checksum( + struct bt_ots_client *otc_inst, struct bt_conn *conn, off_t offset, + size_t len) +{ + struct bt_otc_internal_instance_t *inst; + + if (!conn) + { + LOG_DBG("Invalid Connection"); + return -ENOTCONN; + } + + if (!otc_inst) + { + LOG_DBG("Invalid OTC instance"); + return -EINVAL; + } + + /* OTS_v10.pdf Table 3.9: Object Action Control Point Procedure + * Requirements Offset and Length field are UINT32 Length + */ + + if (len > UINT32_MAX) + { + LOG_DBG("length %zu exceeds UINT32", len); + return -EINVAL; + } + + if (len == 0) + { + LOG_DBG("length equals zero"); + return -EINVAL; + } + + if ((sizeof(offset) > sizeof(uint32_t) && (offset > UINT32_MAX)) || + (offset < 0)) + { + LOG_DBG("offset exceeds %ld UINT32 and must be >= 0", (long)offset); + return -EINVAL; + } + + if ((len + offset) > otc_inst->cur_object.size.cur) + { + LOG_DBG("The sum of offset (%ld) and length (%zu) exceed the Current " + "Size %lu " + "alloc %zu.", + (long)offset, + len, + (len + (long)offset), + otc_inst->cur_object.size.cur); + return -EINVAL; + } + + inst = lookup_inst_by_handle(otc_inst->start_handle); + if (!inst) + { + LOG_DBG("Invalid OTC instance"); + return -EINVAL; + } + + return oacp_checksum(conn, inst, (uint32_t)offset, (uint32_t)len); +} + +static void read_next_metadata(struct bt_conn *conn, + struct bt_otc_internal_instance_t *inst) +{ + uint8_t metadata_remaining = + inst->metadata_to_read ^ inst->metadata_read_attempted; + int err = 0; + + LOG_DBG("Attempting to read metadata 0x%02X", metadata_remaining); + + if (BT_OTS_GET_METADATA_REQ_NAME(metadata_remaining)) + { + BT_OTS_SET_METADATA_REQ_NAME(inst->metadata_read_attempted); + err = read_attr(conn, inst, inst->otc_inst->obj_name_handle, + read_obj_name_cb); + } + else if (BT_OTS_GET_METADATA_REQ_TYPE(metadata_remaining)) + { + BT_OTS_SET_METADATA_REQ_TYPE(inst->metadata_read_attempted); + err = read_attr(conn, inst, inst->otc_inst->obj_type_handle, + read_obj_type_cb); + } + else if (BT_OTS_GET_METADATA_REQ_SIZE(metadata_remaining)) + { + BT_OTS_SET_METADATA_REQ_SIZE(inst->metadata_read_attempted); + err = read_attr(conn, inst, inst->otc_inst->obj_size_handle, + read_object_size_cb); + } + else if (BT_OTS_GET_METADATA_REQ_CREATED(metadata_remaining)) + { + BT_OTS_SET_METADATA_REQ_CREATED(inst->metadata_read_attempted); + err = read_attr(conn, inst, inst->otc_inst->obj_created_handle, + read_obj_created_cb); + } + else if (BT_OTS_GET_METADATA_REQ_MODIFIED(metadata_remaining)) + { + BT_OTS_SET_METADATA_REQ_MODIFIED(inst->metadata_read_attempted); + err = read_attr(conn, inst, inst->otc_inst->obj_modified_handle, + read_obj_modified_cb); + } + else if (BT_OTS_GET_METADATA_REQ_ID(metadata_remaining)) + { + BT_OTS_SET_METADATA_REQ_ID(inst->metadata_read_attempted); + err = read_attr(conn, inst, inst->otc_inst->obj_id_handle, + read_obj_id_cb); + } + else if (BT_OTS_GET_METADATA_REQ_PROPS(metadata_remaining)) + { + BT_OTS_SET_METADATA_REQ_PROPS(inst->metadata_read_attempted); + err = read_attr(conn, inst, inst->otc_inst->obj_properties_handle, + read_obj_properties_cb); + } + else + { + inst->busy = false; + if (inst->otc_inst->cb->obj_metadata_read) + { + inst->otc_inst->cb->obj_metadata_read(inst->otc_inst, conn, + inst->metadata_err, + inst->metadata_read); + } + + return; + } + + if (err) + { + LOG_DBG("Metadata read failed (%d), trying next", err); + read_next_metadata(conn, inst); + } +} + +int bt_ots_client_read_object_metadata( + struct bt_ots_client *otc_inst, struct bt_conn *conn, uint8_t metadata) +{ + struct bt_otc_internal_instance_t *inst; + + if (!conn) + { + LOG_WRN("Invalid Connection"); + return -ENOTCONN; + } + else if (!otc_inst) + { + LOG_ERR("Invalid OTC instance"); + return -EINVAL; + } + else if (!metadata) + { + LOG_WRN("No metadata to read"); + return -ENOEXEC; + } + + inst = lookup_inst_by_handle(otc_inst->start_handle); + + if (!inst) + { + LOG_ERR("Invalid OTC instance"); + return -EINVAL; + } + else if (inst->busy) + { + return -EBUSY; + } + + inst->metadata_read = 0; + inst->metadata_to_read = metadata & BT_OTS_METADATA_REQ_ALL; + inst->metadata_read_attempted = 0; + + inst->busy = true; + read_next_metadata(conn, inst); + + return 0; +} + +static int decode_record(struct net_buf_simple *buf, + struct dirlisting_record_t *rec) +{ + uint16_t start_len = buf->len; + + rec->len = net_buf_simple_pull_le16(buf); + + if (rec->len > buf->len) + { + LOG_WRN("incorrect DirListing record length %u, " + "longer than remaining size %u", + rec->len, buf->len); + return -EINVAL; + } + + if ((start_len - buf->len) + BT_OTS_OBJ_ID_SIZE > rec->len) + { + LOG_WRN("incorrect DirListing record, reclen %u too short, " + "includes only record length", + rec->len); + return -EINVAL; + } + + rec->metadata.id = net_buf_simple_pull_le48(buf); + + if (IS_ENABLED(CONFIG_BT_OTS_CLIENT_LOG_LEVEL_DBG)) + { + char t[BT_OTS_OBJ_ID_STR_LEN]; + + (void)bt_ots_obj_id_to_str(rec->metadata.id, t, sizeof(t)); + LOG_DBG("Object ID 0x%s", t); + } + + if ((start_len - buf->len) + sizeof(uint8_t) > rec->len) + { + LOG_WRN("incorrect DirListing record, reclen %u too short, " + "includes only record length + ObjId", + rec->len); + return -EINVAL; + } + + rec->name_len = net_buf_simple_pull_u8(buf); + + if (rec->name_len > 0) + { + uint8_t *name; + + if ((start_len - buf->len) + rec->name_len > rec->len) + { + LOG_WRN("incorrect DirListing record, remaining length " + "%u shorter than name length %u", + rec->len - (start_len - buf->len), rec->name_len); + return -EINVAL; + } + + if (rec->name_len >= sizeof(rec->metadata.name_c)) + { + LOG_WRN("Name length %u too long, invalid record", rec->name_len); + return -EINVAL; + } + + name = net_buf_simple_pull_mem(buf, rec->name_len); + memcpy(rec->metadata.name_c, name, rec->name_len); + } + + rec->metadata.name_c[rec->name_len] = '\0'; + rec->flags = 0; + + if ((start_len - buf->len) + sizeof(uint8_t) > rec->len) + { + LOG_WRN("incorrect DirListing record, reclen %u too short, " + "does not include flags", + rec->len); + return -EINVAL; + } + + rec->flags = net_buf_simple_pull_u8(buf); + LOG_DBG("flags 0x%x", rec->flags); + + if (BT_OTS_DIR_LIST_GET_FLAG_TYPE_128(rec->flags)) + { + uint8_t *uuid; + + if ((start_len - buf->len) + BT_UUID_SIZE_128 > rec->len) + { + LOG_WRN("incorrect DirListing record, reclen %u " + "flags indicates uuid128, too short", + rec->len); + LOG_INF("flags 0x%x", rec->flags); + return -EINVAL; + } + + uuid = net_buf_simple_pull_mem(buf, BT_UUID_SIZE_128); + if (!bt_uuid_create(&rec->metadata.type.uuid, uuid, BT_UUID_SIZE_128)) + { + LOG_DBG("Failed to create UUID"); + return -EINVAL; + } + } + else + { + if ((start_len - buf->len) + BT_UUID_SIZE_16 > rec->len) + { + LOG_WRN("incorrect DirListing record, reclen %u " + "flags indicates uuid16, too short", + rec->len); + LOG_INF("flags 0x%x", rec->flags); + return -EINVAL; + } + + rec->metadata.type.uuid_16.val = net_buf_simple_pull_le16(buf); + } + + if (BT_OTS_DIR_LIST_GET_FLAG_CUR_SIZE(rec->flags)) + { + if ((start_len - buf->len) + sizeof(uint32_t) > rec->len) + { + LOG_WRN("incorrect DirListing record, reclen %u " + "flags indicates cur_size, too short", + rec->len); + LOG_INF("flags 0x%x", rec->flags); + return -EINVAL; + } + + rec->metadata.size.cur = net_buf_simple_pull_le32(buf); + } + + if (BT_OTS_DIR_LIST_GET_FLAG_ALLOC_SIZE(rec->flags)) + { + if ((start_len - buf->len) + sizeof(uint32_t) > rec->len) + { + LOG_WRN("incorrect DirListing record, reclen %u " + "flags indicates allocated size, too short", + rec->len); + LOG_INF("flags 0x%x", rec->flags); + return -EINVAL; + } + + rec->metadata.size.alloc = net_buf_simple_pull_le32(buf); + } + + if (BT_OTS_DIR_LIST_GET_FLAG_FIRST_CREATED(rec->flags)) + { + if ((start_len - buf->len) + BT_OTS_DATE_TIME_FIELD_SIZE > rec->len) + { + LOG_WRN("incorrect DirListing record, reclen %u " + "too short flags indicates first_created", + rec->len); + LOG_INF("flags 0x%x", rec->flags); + return -EINVAL; + } + + date_time_decode(buf, &rec->metadata.first_created); + } + + if (BT_OTS_DIR_LIST_GET_FLAG_LAST_MODIFIED(rec->flags)) + { + if ((start_len - buf->len) + BT_OTS_DATE_TIME_FIELD_SIZE > rec->len) + { + LOG_WRN("incorrect DirListing record, reclen %u " + "flags indicates las_mod, too short", + rec->len); + LOG_INF("flags 0x%x", rec->flags); + return -EINVAL; + } + + date_time_decode(buf, &rec->metadata.modified); + } + + if (BT_OTS_DIR_LIST_GET_FLAG_PROPERTIES(rec->flags)) + { + if ((start_len - buf->len) + sizeof(uint32_t) > rec->len) + { + LOG_WRN("incorrect DirListing record, reclen %u " + "flags indicates properties, too short", + rec->len); + LOG_INF("flags 0x%x", rec->flags); + return -EINVAL; + } + + rec->metadata.props = net_buf_simple_pull_le32(buf); + } + + return rec->len; +} + +int bt_ots_client_decode_dirlisting(uint8_t *data, + uint16_t length, + bt_ots_client_dirlisting_cb cb) +{ + struct net_buf_simple net_buf; + uint8_t count = 0; + struct dirlisting_record_t record; + + net_buf_simple_init_with_data(&net_buf, (void *)data, length); + + if (!data || length == 0) + { + return -EINVAL; + } + + while (net_buf.len) + { + int ret; + + count++; + + if (net_buf.len < sizeof(uint16_t)) + { + LOG_WRN("incorrect DirListing record, len %u too short", + net_buf.len); + return -EINVAL; + } + + LOG_DBG("Decoding record %u", count); + ret = decode_record(&net_buf, &record); + + if (ret < 0) + { + LOG_WRN("DirListing, record %u invalid", count); + return ret; + } + + ret = cb(&record.metadata); + + if (ret == BT_OTS_STOP) + { + break; + } + } + + return count; +} + +void bt_ots_metadata_display(struct bt_ots_obj_metadata *metadata, + uint16_t count) +{ + LOG_INF("--- Displaying %u metadata records ---", count); + + for (int i = 0; i < count; i++) + { + char t[BT_OTS_OBJ_ID_STR_LEN]; + + (void)bt_ots_obj_id_to_str(metadata->id, t, sizeof(t)); + LOG_INF("Object ID: 0x%s", t); + LOG_INF("Object name: %s", metadata->name_c); + LOG_INF("Object Current Size: %u", metadata->size.cur); + LOG_INF("Object Allocate Size: %u", metadata->size.alloc); + + if (!bt_uuid_cmp(&metadata->type.uuid, BT_UUID_OTS_TYPE_MPL_ICON)) + { + LOG_INF("Type: Icon Obj Type"); + } + else if (!bt_uuid_cmp(&metadata->type.uuid, + BT_UUID_OTS_TYPE_TRACK_SEGMENT)) + { + LOG_INF("Type: Track Segment Obj Type"); + } + else if (!bt_uuid_cmp(&metadata->type.uuid, BT_UUID_OTS_TYPE_TRACK)) + { + LOG_INF("Type: Track Obj Type"); + } + else if (!bt_uuid_cmp(&metadata->type.uuid, BT_UUID_OTS_TYPE_GROUP)) + { + LOG_INF("Type: Group Obj Type"); + } + else if (!bt_uuid_cmp(&metadata->type.uuid, + BT_UUID_OTS_DIRECTORY_LISTING)) + { + LOG_INF("Type: Directory Listing"); + } + + LOG_INF("Properties:0x%x", metadata->props); + + if (BT_OTS_OBJ_GET_PROP_APPEND(metadata->props)) + { + LOG_INF(" - append permitted"); + } + + if (BT_OTS_OBJ_GET_PROP_DELETE(metadata->props)) + { + LOG_INF(" - delete permitted"); + } + + if (BT_OTS_OBJ_GET_PROP_EXECUTE(metadata->props)) + { + LOG_INF(" - execute permitted"); + } + + if (BT_OTS_OBJ_GET_PROP_MARKED(metadata->props)) + { + LOG_INF(" - marked"); + } + + if (BT_OTS_OBJ_GET_PROP_PATCH(metadata->props)) + { + LOG_INF(" - patch permitted"); + } + + if (BT_OTS_OBJ_GET_PROP_READ(metadata->props)) + { + LOG_INF(" - read permitted"); + } + + if (BT_OTS_OBJ_GET_PROP_TRUNCATE(metadata->props)) + { + LOG_INF(" - truncate permitted"); + } + + if (BT_OTS_OBJ_GET_PROP_WRITE(metadata->props)) + { + LOG_INF(" - write permitted"); + } + } +} diff --git a/libs/zblue/services/ots/ots_client_internal.h b/libs/zblue/services/ots/ots_client_internal.h new file mode 100644 index 0000000000000..b3ab8ff71574b --- /dev/null +++ b/libs/zblue/services/ots/ots_client_internal.h @@ -0,0 +1,54 @@ +/**************************************************************************** + * libs/zblue/services/ots/ots_client_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* @file + * @brief Object Transfer client internal header file + * + * For use with the Object Transfer Service Client (OTC) + * + * Copyright (c) 2020 - 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef BT_GATT_OTC_INTERNAL_H_ +#define BT_GATT_OTC_INTERNAL_H_ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include "ots_oacp_internal.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** @brief Translate OTS OLCP result code to "normal" error + * + * Replace the OTS SUCCESS value with the value 0. + * + * The OLCP result code has the value "1" for success. Elsewhere in the + * le-audio host stack, "0" is used for no error. + * + * TODO: Update to also convert the non-SUCCESS values TODO: The macro is + * called OLCP, but uses OACP success value + * TODO: See https://github.com/zephyrproject-rtos/zephyr/issues/41184 + * OTS does not use the value "0". + */ +#define OLCP_RESULT_TO_ERROR(RESULT) (((RESULT) == BT_GATT_OTS_OACP_RES_SUCCESS) ? 0 : RESULT) + +#define OTS_FEATURE_LEN (uint32_t)(2 * sizeof(uint32_t)) +#define OTS_SIZE_LEN (uint32_t)(2 * sizeof(uint32_t)) +#define OTS_PROPERTIES_LEN (uint32_t)(sizeof(uint32_t)) +#define OTS_TYPE_MAX_LEN (uint32_t)(sizeof(struct bt_ots_obj_type)) + +#ifdef __cplusplus +} +#endif + +#endif /* BT_GATT_OTC_INTERNAL_H_ */ diff --git a/libs/zblue/services/ots/ots_dir_list.c b/libs/zblue/services/ots/ots_dir_list.c new file mode 100644 index 0000000000000..774da8e0f5e0e --- /dev/null +++ b/libs/zblue/services/ots/ots_dir_list.c @@ -0,0 +1,461 @@ +/**************************************************************************** + * libs/zblue/services/ots/ots_dir_list.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2021-2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ots_internal.h" +#include "ots_obj_manager_internal.h" +#include "ots_dir_list_internal.h" + +LOG_MODULE_DECLARE(bt_ots, CONFIG_BT_OTS_LOG_LEVEL); + +BUILD_ASSERT(sizeof(CONFIG_BT_OTS_DIR_LIST_OBJ_NAME) - 1U <= + CONFIG_BT_OTS_OBJ_MAX_NAME_LEN, + "BT_OTS_DIR_LIST_OBJ_NAME shall be less than or equal to " + "CONFIG_BT_OTS_OBJ_MAX_NAME_LEN octets"); +/* Since we use a net_buf that has a maximum size of UINT16_MAX, we calculate + * the maximum number of objects supported based on what we can fit in the + * net_buf, based on the format of the directory listing object record + */ +#define MAX_DIR_OBJ_RECORDS (UINT16_MAX / DIR_LIST_OBJ_RECORD_MAX_SIZE) +BUILD_ASSERT(CONFIG_BT_OTS_MAX_OBJ_CNT <= MAX_DIR_OBJ_RECORDS, + "CONFIG_BT_OTS_MAX_OBJ_CNT is too large to support directory " + "listing object"); + +static struct bt_ots_dir_list dir_lists[CONFIG_BT_OTS_MAX_INST_CNT]; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static size_t dir_list_object_record_size( + const struct bt_gatt_ots_object *obj) +{ + uint16_t len; + size_t obj_name_len; + + /* Record Length */ + + len = sizeof(len); + + /* ID */ + + len += BT_OTS_OBJ_ID_SIZE; + + /* Name length (single octet is used for the name length) */ + + len += sizeof(uint8_t); + + /* Name */ + + obj_name_len = strlen(obj->metadata.name); + DEBUGASSERT(obj_name_len > 0 && + obj_name_len <= CONFIG_BT_OTS_OBJ_MAX_NAME_LEN); + len += obj_name_len; + + /* Flags */ + + len += sizeof(uint8_t); + + /* Object type */ + + if (obj->metadata.type.uuid.type == BT_UUID_TYPE_16) + { + len += BT_UUID_SIZE_16; + } + else + { + len += BT_UUID_SIZE_128; + } + + /* Object Current size */ + + len += sizeof(obj->metadata.size.cur); + + /* Object properties */ + + len += sizeof(obj->metadata.props); + + DEBUGASSERT(len >= DIR_LIST_OBJ_RECORD_MIN_SIZE); + DEBUGASSERT(len <= DIR_LIST_OBJ_RECORD_MAX_SIZE); + + return len; +} + +static void dir_list_object_encode(const struct bt_gatt_ots_object *obj, + struct net_buf_simple *net_buf) +{ + uint8_t flags = 0; + uint8_t *start; + uint16_t len; + size_t obj_name_len; + + BT_OTS_DIR_LIST_SET_FLAG_PROPERTIES(flags); + BT_OTS_DIR_LIST_SET_FLAG_CUR_SIZE(flags); + if (obj->metadata.type.uuid.type == BT_UUID_TYPE_128) + { + BT_OTS_DIR_LIST_SET_FLAG_TYPE_128(flags); + } + + /* skip 16bits at the beginning of the record for the record's length */ + + start = net_buf_simple_add(net_buf, sizeof(len)); + + /* ID */ + + net_buf_simple_add_le48(net_buf, obj->id); + + /* Name length */ + + obj_name_len = strlen(obj->metadata.name); + DEBUGASSERT(obj_name_len > 0 + && obj_name_len <= CONFIG_BT_OTS_OBJ_MAX_NAME_LEN); + net_buf_simple_add_u8(net_buf, obj_name_len); + + /* Name */ + + net_buf_simple_add_mem(net_buf, obj->metadata.name, obj_name_len); + + /* Flags */ + + net_buf_simple_add_u8(net_buf, flags); + + /* encode Object type */ + + if (obj->metadata.type.uuid.type == BT_UUID_TYPE_16) + { + net_buf_simple_add_le16(net_buf, obj->metadata.type.uuid_16.val); + } + else + { + net_buf_simple_add_mem(net_buf, obj->metadata.type.uuid_128.val, + BT_UUID_SIZE_128); + } + + /* encode Object Current size */ + + net_buf_simple_add_le32(net_buf, obj->metadata.size.cur); + + /* Object properties */ + + net_buf_simple_add_le32(net_buf, obj->metadata.props); + + len = net_buf_simple_tail(net_buf) - start; + + DEBUGASSERT(len >= DIR_LIST_OBJ_RECORD_MIN_SIZE); + DEBUGASSERT(len <= DIR_LIST_OBJ_RECORD_MAX_SIZE); + + /* Update the record length at the beginning */ + + sys_put_le16(len, start); +} + +static void bt_ots_dir_list_reset_anchor(struct bt_ots_dir_list *dir_list, + void *obj_manager) +{ + dir_list->anchor_offset = 0; + + /* Reset the dir_list - Ignore any error as we can't do anything about it + * anyways + */ + + (void)bt_gatt_ots_obj_manager_first_obj_get(obj_manager, + &dir_list->anchor_object); +} + +static int bt_ots_dir_list_search_forward( + struct bt_ots_dir_list *dir_list, void *obj_manager, off_t offset) +{ + int err; + char id_str[BT_OTS_OBJ_ID_STR_LEN]; + struct bt_gatt_ots_object *obj = dir_list->anchor_object; + size_t rec_len = dir_list_object_record_size(obj); + + bt_ots_obj_id_to_str(obj->id, id_str, sizeof(id_str)); + LOG_DBG("Searching forward for offset %ld starting at %ld with object ID " + "%s", + (long)offset, + (long)dir_list->anchor_offset, id_str); + + while (dir_list->anchor_offset + rec_len <= offset) + { + err = bt_gatt_ots_obj_manager_next_obj_get(obj_manager, obj, &obj); + if (err) + { + return err; + } + + dir_list->anchor_offset += rec_len; + dir_list->anchor_object = obj; + + rec_len = dir_list_object_record_size(obj); + } + + return 0; +} + +static int bt_ots_dir_list_search_backward( + struct bt_ots_dir_list *dir_list, void *obj_manager, off_t offset) +{ + int err; + char id_str[BT_OTS_OBJ_ID_STR_LEN]; + struct bt_gatt_ots_object *obj = dir_list->anchor_object; + + bt_ots_obj_id_to_str(obj->id, id_str, sizeof(id_str)); + LOG_DBG("Searching backward for offset %ld starting at %ld with object ID " + "%s", + (long)offset, + (long)dir_list->anchor_offset, id_str); + + while (dir_list->anchor_offset > offset) + { + err = bt_gatt_ots_obj_manager_prev_obj_get(obj_manager, obj, &obj); + if (err) + { + return err; + } + + dir_list->anchor_offset -= dir_list_object_record_size(obj); + dir_list->anchor_object = obj; + } + + return 0; +} + +static int bt_ots_dir_list_search(struct bt_ots_dir_list *dir_list, + void *obj_manager, off_t offset) +{ + int err = 0; + char id_str[BT_OTS_OBJ_ID_STR_LEN]; + + /* decide start location and direction of movement based on offset, we can + * only choose current anchor point, beginning, or end as those are the + * only places where we know the associated object that builds up the + * record. + */ + + if (offset >= dir_list->anchor_offset) + { + const size_t last = dir_list->dir_list_obj->metadata.size.cur; + const size_t mid = + dir_list->anchor_offset + (last - dir_list->anchor_offset) / 2; + + if (offset < mid) + { + err = bt_ots_dir_list_search_forward(dir_list, obj_manager, + offset); + } + else + { + size_t rec_len; + + LOG_DBG("Offset %ld is closer to %zu than %ld, start from end", + (long)offset, last, + (long)dir_list->anchor_offset); + bt_gatt_ots_obj_manager_last_obj_get(obj_manager, + &dir_list->anchor_object); + rec_len = dir_list_object_record_size(dir_list->anchor_object); + dir_list->anchor_offset = last - rec_len; + err = bt_ots_dir_list_search_backward(dir_list, obj_manager, + offset); + } + } + else + { + const size_t mid = dir_list->anchor_offset / 2; + + if (offset < mid) + { + LOG_DBG("Offset %ld is closer to 0 than %ld, start from beginning", + (long)offset, + (long)dir_list->anchor_offset); + bt_ots_dir_list_reset_anchor(dir_list, obj_manager); + err = bt_ots_dir_list_search_forward(dir_list, obj_manager, + offset); + } + else + { + err = bt_ots_dir_list_search_backward(dir_list, obj_manager, + offset); + } + } + + if (err) + { + return err; + } + + bt_ots_obj_id_to_str(dir_list->anchor_object->id, id_str, sizeof(id_str)); + LOG_DBG("Found offset %ld starting at %ld in object with ID %s", + (long)offset, + (long)dir_list->anchor_offset, id_str); + + return 0; +} + +static void dir_list_update_size(struct bt_ots_dir_list *dir_list, + void *obj_manager) +{ + struct bt_gatt_ots_object *obj; + int err; + size_t len = 0; + + err = bt_gatt_ots_obj_manager_first_obj_get(obj_manager, &obj); + + DEBUGASSERT(err == 0 && obj == dir_list->dir_list_obj); + + do + { + len += dir_list_object_record_size(obj); + + err = bt_gatt_ots_obj_manager_next_obj_get(obj_manager, obj, &obj); + } + while (!err); + + LOG_DBG("Update directory listing current size to 0x%zx", len); + dir_list->dir_list_obj->metadata.size.cur = len; +} + +void bt_ots_dir_list_selected( + struct bt_ots_dir_list *dir_list, void *obj_manager, + struct bt_gatt_ots_object *cur_obj) +{ + if (dir_list->dir_list_obj != cur_obj) + { + /* We only need to update the object directory listing if it is + * currently selected, as we otherwise only create it when it is + * selected. + */ + + return; + } + + bt_ots_dir_list_reset_anchor(dir_list, obj_manager); + dir_list_update_size(dir_list, obj_manager); +} + +void bt_ots_dir_list_init(struct bt_ots_dir_list **dir_list, + void *obj_manager) +{ + struct bt_gatt_ots_object *dir_list_obj; + int err; + static char *dir_list_obj_name = CONFIG_BT_OTS_DIR_LIST_OBJ_NAME; + + DEBUGASSERT(*dir_list == NULL); + + for (size_t i = 0; i < ARRAY_SIZE(dir_lists); i++) + { + if (!dir_lists[i].dir_list_obj) + { + *dir_list = &dir_lists[i]; + } + } + + DEBUGASSERT(*dir_list); + + err = bt_gatt_ots_obj_manager_obj_add(obj_manager, &dir_list_obj); + + DEBUGASSERT(!err); + + memset(&dir_list_obj->metadata, 0, sizeof(dir_list_obj->metadata)); + dir_list_obj->metadata.name = dir_list_obj_name; + dir_list_obj->metadata.size.alloc = DIR_LIST_MAX_SIZE; + dir_list_obj->metadata.type.uuid.type = BT_UUID_TYPE_16; + dir_list_obj->metadata.type.uuid_16.val = + BT_UUID_OTS_DIRECTORY_LISTING_VAL; + BT_OTS_OBJ_SET_PROP_READ(dir_list_obj->metadata.props); + + (*dir_list)->dir_list_obj = dir_list_obj; + + bt_ots_dir_list_reset_anchor(*dir_list, obj_manager); + dir_list_update_size(*dir_list, obj_manager); +} + +ssize_t bt_ots_dir_list_content_get( + struct bt_ots_dir_list *dir_list, void *obj_manager, void **data, + size_t len, off_t offset) +{ + int err; + size_t last_rec_len; + size_t rec_len; + off_t rec_offset; + struct bt_gatt_ots_object *obj; + + err = bt_ots_dir_list_search(dir_list, obj_manager, offset); + if (err) + { + return err; + } + + net_buf_simple_init_with_data(&dir_list->net_buf, dir_list->_content, + sizeof(dir_list->_content)); + net_buf_simple_reset(&dir_list->net_buf); + + obj = dir_list->anchor_object; + rec_offset = dir_list->anchor_offset; + + last_rec_len = 0; + rec_len = dir_list_object_record_size(obj); + while (net_buf_simple_tailroom(&dir_list->net_buf) >= rec_len) + { + dir_list_object_encode(obj, &dir_list->net_buf); + + dir_list->anchor_object = obj; + dir_list->anchor_offset += last_rec_len; + + if (dir_list->net_buf.len - (offset - rec_offset) >= len) + { + /* we have encoded as much data as the client has asked */ + + break; + } + + err = bt_gatt_ots_obj_manager_next_obj_get(obj_manager, obj, &obj); + if (err) + { + /* there are no more objects to encode */ + + break; + } + + last_rec_len = rec_len; + rec_len = dir_list_object_record_size(obj); + } + + *data = dir_list->net_buf.data + (offset - rec_offset); + + return MIN(len, dir_list->net_buf.len - (offset - rec_offset)); +} + +bool bt_ots_dir_list_is_idle(const struct bt_ots_dir_list *dir_list) +{ + return dir_list->dir_list_obj->state.type == BT_GATT_OTS_OBJECT_IDLE_STATE; +} diff --git a/libs/zblue/services/ots/ots_dir_list_internal.h b/libs/zblue/services/ots/ots_dir_list_internal.h new file mode 100644 index 0000000000000..8ed48c91833a3 --- /dev/null +++ b/libs/zblue/services/ots/ots_dir_list_internal.h @@ -0,0 +1,213 @@ +/**************************************************************************** + * libs/zblue/services/ots/ots_dir_list_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2021 - 2026 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef BT_OTS_DIR_LIST_INTERNAL_H_ +#define BT_OTS_DIR_LIST_INTERNAL_H_ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** Maximum size of the Directory Listing Object Record. Table 4.1 in the OTS + * spec. + */ +#define DIR_LIST_OBJ_RECORD_MIN_SIZE 13 +#define DIR_LIST_OBJ_RECORD_MAX_SIZE \ +(2U /* len */ + 6U /* id */ + 1U /* name_len */ + CONFIG_BT_OTS_OBJ_MAX_NAME_LEN /* name */ + \ + 1U /* flags */ + BT_UUID_SIZE_128 /* UUID */ + 4U /* current size */ + 4U /* alloc size */ + \ + 7U /* created ts */ + 7U /* modified ts */ + 4U /* properties */) +#define DIR_LIST_MAX_SIZE (DIR_LIST_OBJ_RECORD_MAX_SIZE * CONFIG_BT_OTS_MAX_OBJ_CNT) + +/** @brief Directory Listing Buffer Size + * + * In order to transmit the maximum size we choose + * CONFIG_BT_OTS_L2CAP_CHAN_TX_MTU as the buffer size. However, + * dir_list_object_encode function encodes one full object record at a time + * so the buffer must be a multiple of object record length. + */ +#define OTS_DIR_LIST_BUFFER_SIZE \ +(DIR_LIST_OBJ_RECORD_MAX_SIZE * \ + DIV_ROUND_UP(CONFIG_BT_OTS_L2CAP_CHAN_TX_MTU, DIR_LIST_OBJ_RECORD_MAX_SIZE)) +BUILD_ASSERT(!IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) || + OTS_DIR_LIST_BUFFER_SIZE <= UINT16_MAX, + "OTS directory listing buffer is too large for net_buf_simple"); + +struct bt_ots_dir_list +{ + struct net_buf_simple net_buf; + struct bt_gatt_ots_object *dir_list_obj; + off_t anchor_offset; + struct bt_gatt_ots_object *anchor_object; + uint8_t _content[OTS_DIR_LIST_BUFFER_SIZE]; +}; + +/** @brief Directory Listing record flag field. */ + +enum +{ + /** Bit 0 Object Type UUID Size 0: 16bit 1: 128bit */ + + BT_OTS_DIR_LIST_FLAG_TYPE_128 = 0, + + /** Bit 1 Current Size Present */ + + BT_OTS_DIR_LIST_FLAG_CUR_SIZE = 1, + + /** Bit 2 Allocated Size Present */ + + BT_OTS_DIR_LIST_FLAG_ALLOC_SIZE = 2, + + /** Bit 3 Object First-Created Present */ + + BT_OTS_DIR_LIST_FLAG_FIRST_CREATED = 3, + + /** Bit 4 Object Last-Modified Present */ + + BT_OTS_DIR_LIST_FLAG_LAST_MODIFIED = 4, + + /** Bit 5 Object Properties Present */ + + BT_OTS_DIR_LIST_FLAG_PROPERTIES = 5, + + /** Bit 6 RFU */ + + BT_OTS_DIR_LIST_FLAG_RFU = 6, + + /** Bit 7 Extended Flags Present */ + + BT_OTS_DIR_LIST_FLAG_EXTENDED = 7, +}; + +/** @brief Set @ref BT_OTS_DIR_LIST_SET_FLAG_TYPE_128 flag. + * + * @param flags Directory Listing Object flags. + */ +#define BT_OTS_DIR_LIST_SET_FLAG_TYPE_128(flags) \ +WRITE_BIT((flags), BT_OTS_DIR_LIST_FLAG_TYPE_128, 1) + +/** @brief Set @ref BT_OTS_DIR_LIST_FLAG_CUR_SIZE flag. + * + * @param flags Directory Listing Object flags. + */ +#define BT_OTS_DIR_LIST_SET_FLAG_CUR_SIZE(flags) \ +WRITE_BIT((flags), BT_OTS_DIR_LIST_FLAG_CUR_SIZE, 1) + +/** @brief Set @ref BT_OTS_DIR_LIST_FLAG_ALLOC_SIZE flag. + * + * @param flags Directory Listing Object flags. + */ +#define BT_OTS_DIR_LIST_SET_FLAG_ALLOC_SIZE(flags) \ +WRITE_BIT((flags), BT_OTS_DIR_LIST_FLAG_ALLOC_SIZE, 1) + +/** @brief Set @ref BT_OTS_DIR_LIST_FLAG_FIRST_CREATED flag. + * + * @param flags Directory Listing Object flags. + */ +#define BT_OTS_DIR_LIST_SET_FLAG_FIRST_CREATED(flags) \ +WRITE_BIT((flags), BT_OTS_DIR_LIST_FLAG_FIRST_CREATED, 1) + +/** @brief Set @ref BT_OTS_DIR_LIST_FLAG_LAST_MODIFIED flag. + * + * @param flags Directory Listing Object flags. + */ +#define BT_OTS_DIR_LIST_SET_FLAG_LAST_MODIFIED(flags) \ +WRITE_BIT((flags), BT_OTS_DIR_LIST_FLAG_LAST_MODIFIED, 1) + +/** @brief Set @ref BT_OTS_DIR_LIST_FLAG_PROPERTIES flag. + * + * @param flags Directory Listing Object flags. + */ +#define BT_OTS_DIR_LIST_SET_FLAG_PROPERTIES(flags) \ +WRITE_BIT((flags), BT_OTS_DIR_LIST_FLAG_PROPERTIES, 1) + +/** @brief Set @ref BT_OTS_DIR_LIST_FLAG_EXTENDED flag. + * + * @param flags Directory Listing Object flags. + */ +#define BT_OTS_DIR_LIST_SET_FLAG_EXTENDED(flags) \ +WRITE_BIT((flags), BT_OTS_DIR_LIST_FLAG_EXTENDED, 1) + +/** @brief Get @ref BT_OTS_DIR_LIST_GET_FLAG_TYPE_128 flag. + * + * @param flags Directory Listing Object flags. + */ +#define BT_OTS_DIR_LIST_GET_FLAG_TYPE_128(flags) ((flags) & BIT(BT_OTS_DIR_LIST_FLAG_TYPE_128)) + +/** @brief Get @ref BT_OTS_DIR_LIST_GET_FLAG_CUR_SIZE flag. + * + * @param flags Directory Listing Object flags. + */ +#define BT_OTS_DIR_LIST_GET_FLAG_CUR_SIZE(flags) ((flags) & BIT(BT_OTS_DIR_LIST_FLAG_CUR_SIZE)) + +/** @brief Get @ref BT_OTS_DIR_LIST_GET_FLAG_ALLOC_SIZE flag. + * + * @param flags Directory Listing Object flags. + */ +#define BT_OTS_DIR_LIST_GET_FLAG_ALLOC_SIZE(flags) ((flags) & BIT(BT_OTS_DIR_LIST_FLAG_ALLOC_SIZE)) + +/** @brief Get @ref BT_OTS_DIR_LIST_GET_FLAG_FIRST_CREATED flag. + * + * @param flags Directory Listing Object flags. + */ +#define BT_OTS_DIR_LIST_GET_FLAG_FIRST_CREATED(flags) \ +((flags) & BIT(BT_OTS_DIR_LIST_FLAG_FIRST_CREATED)) + +/** @brief Get @ref BT_OTS_DIR_LIST_GET_FLAG_LAST_MODIFIED flag. + * + * @param flags Directory Listing Object flags. + */ +#define BT_OTS_DIR_LIST_GET_FLAG_LAST_MODIFIED(flags) \ +((flags) & BIT(BT_OTS_DIR_LIST_FLAG_LAST_MODIFIED)) + +/** @brief Get @ref BT_OTS_DIR_LIST_GET_FLAG_PROPERTIES flag. + * + * @param flags Directory Listing Object flags. + */ +#define BT_OTS_DIR_LIST_GET_FLAG_PROPERTIES(flags) ((flags) & BIT(BT_OTS_DIR_LIST_FLAG_PROPERTIES)) + +/** @brief Get @ref BT_OTS_DIR_LIST_GET_FLAG_EXTENDED flag. + * + * @param flags Directory Listing Object flags. + */ +#define BT_OTS_DIR_LIST_GET_FLAG_EXTENDED(flags) ((flags) & BIT(BT_OTS_DIR_LIST_FLAG_EXTENDED)) + +void bt_ots_dir_list_selected( + struct bt_ots_dir_list *dir_list, void *obj_manager, + struct bt_gatt_ots_object *cur_obj); +void bt_ots_dir_list_init(struct bt_ots_dir_list **dir_list, + void *obj_manager); +ssize_t bt_ots_dir_list_content_get( + struct bt_ots_dir_list *dir_list, void *obj_manager, void **data, + size_t len, off_t offset); +bool bt_ots_dir_list_is_idle(const struct bt_ots_dir_list *dir_list); + +#ifdef __cplusplus +} +#endif + +#endif /* BT_OTS_DIR_LIST_INTERNAL_H_ */ diff --git a/libs/zblue/services/ots/ots_internal.h b/libs/zblue/services/ots/ots_internal.h new file mode 100644 index 0000000000000..569b801f666df --- /dev/null +++ b/libs/zblue/services/ots/ots_internal.h @@ -0,0 +1,159 @@ +/**************************************************************************** + * libs/zblue/services/ots/ots_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020-2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef BT_GATT_OTS_INTERNAL_H_ +#define BT_GATT_OTS_INTERNAL_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include "ots_l2cap_internal.h" +#include "ots_oacp_internal.h" +#include "ots_olcp_internal.h" + +/** + * Both OACP and OLCP have same max size of 7 bytes + * + * Table 3.10: Format of OACP Response Value OACP Response Value contains 1 + * octet Procedure code 1 octet Request op code 1 octet Result Code 4 octet + * CRC checksum (if present) + * + * Table 3.24: Format of the OLCP Response Value 1 octet Procedure code 1 + * octet Request op code 1 octet Result Code 0 or 4 octets Response Parameter + * + **/ +#define OACP_OLCP_RES_MAX_SIZE 7 + +#define BT_OTS_VALID_OBJ_ID(id) \ +(IN_RANGE((id), BT_OTS_OBJ_ID_MIN, BT_OTS_OBJ_ID_MAX) || (id) == OTS_OBJ_ID_DIR_LIST) + +#define BT_OTS_SET_METADATA_REQ_NAME(metadata) ((metadata) = (metadata) | BT_OTS_METADATA_REQ_NAME) +#define BT_OTS_SET_METADATA_REQ_TYPE(metadata) ((metadata) = (metadata) | BT_OTS_METADATA_REQ_TYPE) +#define BT_OTS_SET_METADATA_REQ_SIZE(metadata) ((metadata) = (metadata) | BT_OTS_METADATA_REQ_SIZE) +#define BT_OTS_SET_METADATA_REQ_CREATED(metadata) \ +((metadata) = (metadata) | BT_OTS_METADATA_REQ_CREATED) +#define BT_OTS_SET_METADATA_REQ_MODIFIED(metadata) \ +((metadata) = (metadata) | BT_OTS_METADATA_REQ_MODIFIED) +#define BT_OTS_SET_METADATA_REQ_ID(metadata) ((metadata) = (metadata) | BT_OTS_METADATA_REQ_ID) +#define BT_OTS_SET_METADATA_REQ_PROPS(metadata) \ +((metadata) = (metadata) | BT_OTS_METADATA_REQ_PROPS) + +#define BT_OTS_GET_METADATA_REQ_NAME(metadata) ((metadata) & BT_OTS_METADATA_REQ_NAME) +#define BT_OTS_GET_METADATA_REQ_TYPE(metadata) ((metadata) & BT_OTS_METADATA_REQ_TYPE) +#define BT_OTS_GET_METADATA_REQ_SIZE(metadata) ((metadata) & BT_OTS_METADATA_REQ_SIZE) +#define BT_OTS_GET_METADATA_REQ_CREATED(metadata) ((metadata) & BT_OTS_METADATA_REQ_CREATED) +#define BT_OTS_GET_METADATA_REQ_MODIFIED(metadata) ((metadata) & BT_OTS_METADATA_REQ_MODIFIED) +#define BT_OTS_GET_METADATA_REQ_ID(metadata) ((metadata) & BT_OTS_METADATA_REQ_ID) +#define BT_OTS_GET_METADATA_REQ_PROPS(metadata) ((metadata) & BT_OTS_METADATA_REQ_PROPS) + +/**@brief OTS Attribute Protocol Application Error codes. */ + +enum bt_gatt_ots_att_err_codes +{ + /** An attempt was made to write a value that is invalid or + * not supported by this Server for a reason other than the attribute + * permissions. + */ + + BT_GATT_OTS_WRITE_REQUEST_REJECTED = 0x80, + /** An attempt was made to read or write to an Object Metadata + * characteristic while the Current Object was an Invalid Object. + */ + + BT_GATT_OTS_OBJECT_NOT_SELECTED = 0x81, + /** The Server is unable to service the Read Request or Write Request + * because it exceeds the concurrency limit of the service. + */ + + BT_GATT_OTS_CONCURRENCY_LIMIT_EXCEEDED = 0x82, + /** The requested object name was rejected because the name was already in + * use by an existing object on the Server. + */ + + BT_GATT_OTS_OBJECT_NAME_ALREADY_EXISTS = 0x83, +}; + +enum bt_gatt_ots_object_state_type +{ + BT_GATT_OTS_OBJECT_IDLE_STATE, + + BT_GATT_OTS_OBJECT_READ_OP_STATE, + + BT_GATT_OTS_OBJECT_WRITE_OP_STATE, +}; + +struct bt_gatt_ots_object_state +{ + enum bt_gatt_ots_object_state_type type; + union + { + struct bt_gatt_ots_object_read_op + { + struct bt_gatt_ots_oacp_read_params oacp_params; + uint32_t sent_len; + } read_op; + struct bt_gatt_ots_object_write_op + { + struct bt_gatt_ots_oacp_write_params oacp_params; + uint32_t recv_len; + } write_op; + }; +}; + +struct bt_gatt_ots_object +{ + uint64_t id; + struct bt_ots_obj_metadata metadata; + struct bt_gatt_ots_object_state state; +}; + +struct bt_gatt_ots_indicate +{ + struct bt_gatt_indicate_params params; + struct bt_gatt_attr attr; + struct bt_gatt_ccc_managed_user_data ccc; + bool is_enabled; + struct k_work work; + uint8_t res[OACP_OLCP_RES_MAX_SIZE]; +}; + +struct bt_ots +{ + struct bt_ots_feat features; + struct bt_gatt_ots_object *cur_obj; + struct bt_gatt_service *service; + struct bt_gatt_ots_indicate oacp_ind; + struct bt_gatt_ots_indicate olcp_ind; + struct bt_gatt_ots_l2cap l2cap; + struct bt_ots_cb *cb; + struct bt_ots_dir_list *dir_list; + void *obj_manager; +}; + +int bt_ots_obj_add_internal(struct bt_ots *ots, struct bt_conn *conn, + const struct bt_ots_obj_add_param *param, + struct bt_gatt_ots_object **obj); +#ifdef __cplusplus +} +#endif + +#endif /* BT_GATT_OTS_INTERNAL_H_ */ diff --git a/libs/zblue/services/ots/ots_l2cap.c b/libs/zblue/services/ots/ots_l2cap.c new file mode 100644 index 0000000000000..55ae75eb3f91a --- /dev/null +++ b/libs/zblue/services/ots/ots_l2cap.c @@ -0,0 +1,376 @@ +/**************************************************************************** + * libs/zblue/services/ots/ots_l2cap.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 - 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "ots_l2cap_internal.h" + +#include + +/* This l2cap is the only OTS-file in use for OTC. + * If only OTC is used, the OTS log module must be registered here. + */ +#if defined(CONFIG_BT_OTS) +LOG_MODULE_DECLARE(bt_ots, CONFIG_BT_OTS_LOG_LEVEL); +#elif defined(CONFIG_BT_OTS_CLIENT) +LOG_MODULE_REGISTER(bt_ots, CONFIG_BT_OTS_CLIENT_LOG_LEVEL); +#endif + +/* According to Bluetooth specification Assigned Numbers that are used in the + * Logical Link Control for protocol/service multiplexers. + */ +#define BT_GATT_OTS_L2CAP_PSM 0x0025 + +NET_BUF_POOL_FIXED_DEFINE(ot_chan_tx_pool, 1, + BT_L2CAP_SDU_BUF_SIZE( + CONFIG_BT_OTS_L2CAP_CHAN_TX_MTU), + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + +#if (CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU > BT_L2CAP_SDU_RX_MTU) +NET_BUF_POOL_FIXED_DEFINE(ot_chan_rx_pool, + 1, + CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU, + 8, + NULL); +#endif + +/* List of Object Transfer Channels. */ + +static struct list_node channels = LIST_INITIAL_VALUE(channels); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static int ots_l2cap_send(struct bt_gatt_ots_l2cap *l2cap_ctx) +{ + int ret; + struct net_buf *buf; + uint32_t len; + + /* Calculate maximum length of data chunk. */ + + len = MIN(l2cap_ctx->ot_chan.tx.mtu, CONFIG_BT_OTS_L2CAP_CHAN_TX_MTU); + len = MIN(len, l2cap_ctx->tx.len - l2cap_ctx->tx.len_sent); + + /* Prepare buffer for sending. */ + + buf = net_buf_alloc(&ot_chan_tx_pool, -1); + net_buf_reserve(buf, BT_L2CAP_SDU_CHAN_SEND_RESERVE); + net_buf_add_mem(buf, &l2cap_ctx->tx.data[l2cap_ctx->tx.len_sent], len); + + ret = bt_l2cap_chan_send(&l2cap_ctx->ot_chan.chan, buf); + if (ret < 0) + { + LOG_ERR("Unable to send data over CoC: %d", ret); + net_buf_unref(buf); + + return -ENOEXEC; + } + + /* Mark that L2CAP TX was accepted. */ + + l2cap_ctx->tx.len_sent += len; + + LOG_DBG("Sending TX chunk with %d bytes on L2CAP CoC", len); + + return 0; +} + +#if (CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU > BT_L2CAP_SDU_RX_MTU) +static struct net_buf *l2cap_alloc_buf(struct bt_l2cap_chan *chan) +{ + LOG_DBG("Channel %p allocating buffer", chan); + + return net_buf_alloc(&ot_chan_rx_pool, -1); +} +#endif + +static void l2cap_sent(struct bt_l2cap_chan *chan) +{ + struct bt_l2cap_le_chan *l2chan = CONTAINER_OF(chan, + struct bt_l2cap_le_chan, + chan); + struct bt_gatt_ots_l2cap *l2cap_ctx; + + LOG_DBG("Outgoing data channel %p transmitted", chan); + + l2cap_ctx = CONTAINER_OF(l2chan, struct bt_gatt_ots_l2cap, ot_chan); + + /* Ongoing TX - sending next chunk. */ + + if (l2cap_ctx->tx.len != l2cap_ctx->tx.len_sent) + { + ots_l2cap_send(l2cap_ctx); + + return; + } + + /* TX completed - notify upper layers and clean up. */ + + memset(&l2cap_ctx->tx, 0, sizeof(l2cap_ctx->tx)); + + LOG_DBG("Scheduled TX on L2CAP CoC is complete"); + + if (l2cap_ctx->tx_done) + { + l2cap_ctx->tx_done(l2cap_ctx, chan->conn); + } +} + +static int l2cap_recv(struct bt_l2cap_chan *chan, struct net_buf *buf) +{ + struct bt_l2cap_le_chan *l2chan = CONTAINER_OF(chan, + struct bt_l2cap_le_chan, + chan); + struct bt_gatt_ots_l2cap *l2cap_ctx; + + LOG_DBG("Incoming data channel %p received", chan); + + l2cap_ctx = CONTAINER_OF(l2chan, struct bt_gatt_ots_l2cap, ot_chan); + + if (!l2cap_ctx->rx_done) + { + return -ENODEV; + } + + return l2cap_ctx->rx_done(l2cap_ctx, chan->conn, buf); +} + +static void l2cap_status(struct bt_l2cap_chan *chan, atomic_t *status) +{ + LOG_DBG("Channel %p status %lu", chan, atomic_read(status)); +} + +static void l2cap_connected(struct bt_l2cap_chan *chan) +{ + LOG_DBG("Channel %p connected", chan); +} + +static void l2cap_disconnected(struct bt_l2cap_chan *chan) +{ + struct bt_l2cap_le_chan *l2chan = CONTAINER_OF(chan, + struct bt_l2cap_le_chan, + chan); + struct bt_gatt_ots_l2cap *l2cap_ctx; + + LOG_DBG("Channel %p disconnected", chan); + + l2cap_ctx = CONTAINER_OF(l2chan, struct bt_gatt_ots_l2cap, ot_chan); + + if (l2cap_ctx->closed) + { + l2cap_ctx->closed(l2cap_ctx, chan->conn); + } +} + +static const struct bt_l2cap_chan_ops l2cap_ops = +{ +#if (CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU > BT_L2CAP_SDU_RX_MTU) + .alloc_buf = l2cap_alloc_buf, +#endif + .sent = l2cap_sent, + .recv = l2cap_recv, + .status = l2cap_status, + .connected = l2cap_connected, + .disconnected = l2cap_disconnected, +}; + +static inline void l2cap_chan_init(struct bt_l2cap_le_chan *chan) +{ + chan->rx.mtu = CONFIG_BT_OTS_L2CAP_CHAN_RX_MTU; + chan->chan.ops = &l2cap_ops; + + LOG_DBG("RX MTU set to %u", chan->rx.mtu); +} + +static struct bt_gatt_ots_l2cap *find_free_l2cap_ctx(void) +{ + struct bt_gatt_ots_l2cap *l2cap_ctx; + + list_for_every_container(&channels, l2cap_ctx, node) + { + if (l2cap_ctx->ot_chan.chan.conn) + { + continue; + } + + return l2cap_ctx; + } + + return NULL; +} + +static int l2cap_accept(struct bt_conn *conn, struct bt_l2cap_server *server, + struct bt_l2cap_chan **chan) +{ + struct bt_gatt_ots_l2cap *l2cap_ctx; + + LOG_DBG("Incoming conn %p", (void *)conn); + + l2cap_ctx = find_free_l2cap_ctx(); + if (l2cap_ctx) + { + l2cap_chan_init(&l2cap_ctx->ot_chan); + memset(&l2cap_ctx->tx, 0, sizeof(l2cap_ctx->tx)); + + *chan = &l2cap_ctx->ot_chan.chan; + + return 0; + } + + return -ENOMEM; +} + +static struct bt_l2cap_server l2cap_server = +{ + .psm = BT_GATT_OTS_L2CAP_PSM, + .accept = l2cap_accept, +}; + +static int bt_gatt_ots_l2cap_init(void) +{ + int err; + + list_initialize(&channels); + + err = bt_l2cap_server_register(&l2cap_server); + if (err) + { + LOG_ERR("Unable to register OTS PSM"); + return err; + } + + LOG_DBG("Initialized OTS L2CAP"); + + return 0; +} + +bool bt_gatt_ots_l2cap_is_open(struct bt_gatt_ots_l2cap *l2cap_ctx, + struct bt_conn *conn) +{ + return (l2cap_ctx->ot_chan.chan.conn == conn); +} + +int bt_gatt_ots_l2cap_send(struct bt_gatt_ots_l2cap *l2cap_ctx, + uint8_t *data, uint32_t len) +{ + int err; + + if (l2cap_ctx->tx.len != 0) + { + LOG_ERR("L2CAP TX in progress"); + + return -EAGAIN; + } + + l2cap_ctx->tx.data = data; + l2cap_ctx->tx.len = len; + + LOG_DBG("Starting TX on L2CAP CoC with %d byte packet", len); + + err = ots_l2cap_send(l2cap_ctx); + if (err) + { + LOG_ERR("Unable to send data over CoC: %d", err); + + return err; + } + + return 0; +} + +int bt_gatt_ots_l2cap_register(struct bt_gatt_ots_l2cap *l2cap_ctx) +{ + list_add_tail(&channels, &l2cap_ctx->node); + + return 0; +} + +int bt_gatt_ots_l2cap_unregister(struct bt_gatt_ots_l2cap *l2cap_ctx) +{ + list_find_and_remove(&channels, &l2cap_ctx->node); + + return 0; +} + +/* Similar to l2cap_accept(), but for the client side */ + +int bt_gatt_ots_l2cap_connect(struct bt_conn *conn, + struct bt_gatt_ots_l2cap **l2cap_ctx) +{ + int err; + struct bt_gatt_ots_l2cap *ctx; + + if (!conn) + { + LOG_WRN("Invalid Connection"); + return -ENOTCONN; + } + + if (!l2cap_ctx) + { + LOG_WRN("Invalid context"); + return -EINVAL; + } + + *l2cap_ctx = NULL; + + ctx = find_free_l2cap_ctx(); + if (!ctx) + { + return -ENOMEM; + } + + l2cap_chan_init(&ctx->ot_chan); + (void)memset(&ctx->tx, 0, sizeof(ctx->tx)); + + LOG_DBG("Connecting L2CAP CoC"); + err = bt_l2cap_chan_connect(conn, &ctx->ot_chan.chan, + BT_GATT_OTS_L2CAP_PSM); + if (err) + { + LOG_WRN("Unable to connect to psm %u (err %d)", BT_GATT_OTS_L2CAP_PSM, + err); + } + else + { + LOG_DBG("L2CAP connection pending"); + *l2cap_ctx = ctx; + } + + return err; +} + +int bt_gatt_ots_l2cap_disconnect(struct bt_gatt_ots_l2cap *l2cap_ctx) +{ + return bt_l2cap_chan_disconnect(&l2cap_ctx->ot_chan.chan); +} + +SYS_INIT(bt_gatt_ots_l2cap_init, + APPLICATION, + CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/libs/zblue/services/ots/ots_l2cap_internal.h b/libs/zblue/services/ots/ots_l2cap_internal.h new file mode 100644 index 0000000000000..65569f69b22b4 --- /dev/null +++ b/libs/zblue/services/ots/ots_l2cap_internal.h @@ -0,0 +1,88 @@ +/**************************************************************************** + * libs/zblue/services/ots/ots_l2cap_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 - 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef BT_GATT_OTS_L2CAP_H_ +#define BT_GATT_OTS_L2CAP_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +#include + +struct bt_gatt_ots_l2cap_tx +{ + uint8_t *data; + uint32_t len; + uint32_t len_sent; +}; + +struct bt_gatt_ots_l2cap +{ + struct list_node node; + struct bt_l2cap_le_chan ot_chan; + struct bt_gatt_ots_l2cap_tx tx; + void (*tx_done)(struct bt_gatt_ots_l2cap *l2cap_ctx, struct bt_conn *conn); +ssize_t (*rx_done)(struct bt_gatt_ots_l2cap *l2cap_ctx, + struct bt_conn *conn, + struct net_buf *buf); + void (*closed)(struct bt_gatt_ots_l2cap *l2cap_ctx, struct bt_conn *conn); +}; + +bool bt_gatt_ots_l2cap_is_open(struct bt_gatt_ots_l2cap *l2cap_ctx, + struct bt_conn *conn); + +int bt_gatt_ots_l2cap_send(struct bt_gatt_ots_l2cap *l2cap_ctx, + uint8_t *data, uint32_t len); + +int bt_gatt_ots_l2cap_register(struct bt_gatt_ots_l2cap *l2cap_ctx); + +int bt_gatt_ots_l2cap_unregister(struct bt_gatt_ots_l2cap *l2cap_ctx); + +/** @brief Connect OTS L2CAP channel + * + * This function is for the OTS client to make an L2CAP connection to the + * OTS server. One of the available registered L2CAP contexts will be used + * for the connection. + * + * @param[in] conn Connection pointer + * @param[out] l2cap_ctx The context that was connected + * + * @return 0 in case of success or negative value in case of error + */ + +int bt_gatt_ots_l2cap_connect(struct bt_conn *conn, + struct bt_gatt_ots_l2cap **l2cap_ctx); + +int bt_gatt_ots_l2cap_disconnect(struct bt_gatt_ots_l2cap *l2cap_ctx); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* BT_GATT_OTS_L2CAP_H_ */ diff --git a/libs/zblue/services/ots/ots_oacp.c b/libs/zblue/services/ots/ots_oacp.c new file mode 100644 index 0000000000000..ecbf2b46df0a0 --- /dev/null +++ b/libs/zblue/services/ots/ots_oacp.c @@ -0,0 +1,868 @@ +/**************************************************************************** + * libs/zblue/services/ots/ots_oacp.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "ots_internal.h" +#include "ots_dir_list_internal.h" +#include "ots_obj_manager_internal.h" + +#include + +LOG_MODULE_DECLARE(bt_ots, CONFIG_BT_OTS_LOG_LEVEL); + +#define OACP_PROC_TYPE_SIZE 1 + +#if defined(CONFIG_BT_OTS_OACP_WRITE_SUPPORT) +static ssize_t oacp_write_proc_cb( + struct bt_gatt_ots_l2cap *l2cap_ctx, struct bt_conn *conn, + struct net_buf *buf); +#endif + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static void oacp_l2cap_closed(struct bt_gatt_ots_l2cap *l2cap_ctx, + struct bt_conn *conn) +{ + struct bt_ots *ots; + + ots = CONTAINER_OF(l2cap_ctx, struct bt_ots, l2cap); + + if (!ots->cur_obj) + { + return; + } + + ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE; + l2cap_ctx->rx_done = NULL; + l2cap_ctx->tx_done = NULL; +} + +#if defined(CONFIG_BT_OTS_OACP_CREATE_SUPPORT) +static enum bt_gatt_ots_oacp_res_code oacp_create_proc_validate( + struct bt_conn *conn, struct bt_ots *ots, + struct bt_gatt_ots_oacp_proc *proc) +{ + char str[BT_UUID_STR_LEN]; + int err; + struct bt_gatt_ots_object *obj; + const struct bt_ots_obj_add_param param = + { + .size = proc->create_params.size, + .type = proc->create_params.type, + }; + + bt_uuid_to_str(¶m.type.uuid, str, BT_UUID_STR_LEN); + LOG_DBG("Validating Create procedure with size: 0x%08X and " + "type: %s", + param.size, str); + + if (!BT_OTS_OACP_GET_FEAT_CREATE(ots->features.oacp)) + { + LOG_DBG("Create Procedure is not supported."); + return BT_GATT_OTS_OACP_RES_OPCODE_NOT_SUP; + } + + err = bt_ots_obj_add_internal(ots, conn, ¶m, &obj); + if (err) + { + goto exit; + } + + /* Verify Initialization Metadata */ + + if (strlen(obj->metadata.name) > 0) + { + LOG_ERR("Object name shall be a zero length string after object " + "creation."); + (void)bt_ots_obj_delete(ots, obj->id); + err = -ECANCELED; + goto exit; + } + + if (obj->metadata.size.cur > 0) + { + LOG_ERR("Object current size must be 0."); + (void)bt_ots_obj_delete(ots, obj->id); + err = -ECANCELED; + goto exit; + } + + if (!BT_OTS_OBJ_GET_PROP_WRITE(obj->metadata.props)) + { + LOG_ERR("Created object must have write property."); + (void)bt_ots_obj_delete(ots, obj->id); + err = -ECANCELED; + goto exit; + } + + ots->cur_obj = obj; + ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE; + + LOG_DBG("Create procedure is complete"); + +exit: + switch (err) + { + case 0: + return BT_GATT_OTS_OACP_RES_SUCCESS; + case -ENOTSUP: + return BT_GATT_OTS_OACP_RES_UNSUP_TYPE; + case -ENOMEM: + return BT_GATT_OTS_OACP_RES_INSUFF_RES; + case -EINVAL: + return BT_GATT_OTS_OACP_RES_INV_PARAM; + case -ECANCELED: + default: + return BT_GATT_OTS_OACP_RES_OPER_FAILED; + } +} +#endif + +#if defined(CONFIG_BT_OTS_OACP_DELETE_SUPPORT) +static enum bt_gatt_ots_oacp_res_code oacp_delete_proc_validate( + struct bt_conn *conn, struct bt_ots *ots, + struct bt_gatt_ots_oacp_proc *proc) +{ + int err; + + if (!BT_OTS_OACP_GET_FEAT_DELETE(ots->features.oacp)) + { + LOG_DBG("Delete Procedure is not supported."); + return BT_GATT_OTS_OACP_RES_OPCODE_NOT_SUP; + } + + if (!ots->cur_obj) + { + LOG_DBG("No object is selected."); + return BT_GATT_OTS_OACP_RES_INV_OBJ; + } + + if (!BT_OTS_OBJ_GET_PROP_DELETE(ots->cur_obj->metadata.props)) + { + LOG_DBG("Object properties do not permit deletion."); + return BT_GATT_OTS_OACP_RES_NOT_PERMITTED; + } + + err = bt_ots_obj_delete(ots, ots->cur_obj->id); + if (err) + { + LOG_ERR("Deleting object during Delete procedure failed: %d", err); + goto exit; + } + + LOG_DBG("Delete procedure is complete"); + +exit: + switch (err) + { + case 0: + return BT_GATT_OTS_OACP_RES_SUCCESS; + case -EBUSY: + return BT_GATT_OTS_OACP_RES_OBJ_LOCKED; + default: + return BT_GATT_OTS_OACP_RES_OPER_FAILED; + } +} +#endif + +#if defined(CONFIG_BT_OTS_OACP_CHECKSUM_SUPPORT) +static enum bt_gatt_ots_oacp_res_code +oacp_checksum_proc_validate(struct bt_conn *conn, struct bt_ots *ots, + struct bt_gatt_ots_oacp_proc *proc, + struct net_buf_simple *resp_param) +{ + struct bt_gatt_ots_oacp_cs_calc_params *params = &proc->cs_calc_params; + void *obj_data; + int err; + uint32_t checksum; + + LOG_DBG("Validating Checksum procedure with offset: 0x%08X and " + "length: 0x%08X", + params->offset, params->len); + + if (!ots->cur_obj) + { + return BT_GATT_OTS_OACP_RES_INV_OBJ; + } + + if (params->offset > ots->cur_obj->metadata.size.cur) + { + return BT_GATT_OTS_OACP_RES_INV_PARAM; + } + + if ((params->offset + (uint64_t)params->len) > + ots->cur_obj->metadata.size.alloc) + { + return BT_GATT_OTS_OACP_RES_INV_PARAM; + } + + if (ots->cur_obj->state.type != BT_GATT_OTS_OBJECT_IDLE_STATE) + { + return BT_GATT_OTS_OACP_RES_OBJ_LOCKED; + } + + if (ots->cb->obj_cal_checksum) + { + err = ots->cb->obj_cal_checksum(ots, conn, ots->cur_obj->id, + params->offset, params->len, + &obj_data); + if (err != 0) + { + return BT_GATT_OTS_OACP_RES_OPER_FAILED; + } + + checksum = bt_ots_client_calc_checksum((const uint8_t *)obj_data, + params->len); + net_buf_simple_add_le32(resp_param, checksum); + LOG_DBG("Calculate from offset %u len %u checksum 0x%08x\n", + params->offset, params->len, + checksum); + return BT_GATT_OTS_OACP_RES_SUCCESS; + } + else + { + return BT_GATT_OTS_OACP_RES_OPER_FAILED; + } +} +#endif + +static enum bt_gatt_ots_oacp_res_code +oacp_read_proc_validate(struct bt_conn *conn, struct bt_ots *ots, + const struct bt_gatt_ots_oacp_proc *proc) +{ + const struct bt_gatt_ots_oacp_read_params *params = &proc->read_params; + + LOG_DBG("Validating Read procedure with offset: 0x%08X and " + "length: 0x%08X", + params->offset, params->len); + + if (!ots->cur_obj) + { + return BT_GATT_OTS_OACP_RES_INV_OBJ; + } + + if (!BT_OTS_OBJ_GET_PROP_READ(ots->cur_obj->metadata.props)) + { + return BT_GATT_OTS_OACP_RES_NOT_PERMITTED; + } + + if (!bt_gatt_ots_l2cap_is_open(&ots->l2cap, conn)) + { + return BT_GATT_OTS_OACP_RES_CHAN_UNAVAIL; + } + + if ((params->offset + (uint64_t)params->len) > + ots->cur_obj->metadata.size.cur) + { + return BT_GATT_OTS_OACP_RES_INV_PARAM; + } + + if (ots->cur_obj->state.type != BT_GATT_OTS_OBJECT_IDLE_STATE) + { + return BT_GATT_OTS_OACP_RES_OBJ_LOCKED; + } + + ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_READ_OP_STATE; + ots->cur_obj->state.read_op.sent_len = 0; + memcpy(&ots->cur_obj->state.read_op.oacp_params, &proc->read_params, + sizeof(ots->cur_obj->state.read_op.oacp_params)); + + LOG_DBG("Read procedure is accepted"); + + return BT_GATT_OTS_OACP_RES_SUCCESS; +} + +#if defined(CONFIG_BT_OTS_OACP_WRITE_SUPPORT) +static enum bt_gatt_ots_oacp_res_code oacp_write_proc_validate( + struct bt_conn *conn, struct bt_ots *ots, + struct bt_gatt_ots_oacp_proc *proc) +{ + struct bt_gatt_ots_oacp_write_params *params = &proc->write_params; + + LOG_DBG("Validating Write procedure with offset: 0x%08X and " + "length: 0x%08X", + params->offset, params->len); + + if (!ots->cur_obj) + { + return BT_GATT_OTS_OACP_RES_INV_OBJ; + } + + if (!BT_OTS_OBJ_GET_PROP_WRITE(ots->cur_obj->metadata.props)) + { + return BT_GATT_OTS_OACP_RES_NOT_PERMITTED; + } + + /* patching is attempted */ + + if (params->offset < ots->cur_obj->metadata.size.cur) + { + if (!BT_OTS_OACP_GET_FEAT_PATCH(ots->features.oacp)) + { + return BT_GATT_OTS_OACP_RES_NOT_PERMITTED; + } + + if (!BT_OTS_OBJ_GET_PROP_PATCH(ots->cur_obj->metadata.props)) + { + return BT_GATT_OTS_OACP_RES_NOT_PERMITTED; + } + } + + /* truncation is not supported */ + + if (BT_GATT_OTS_OACP_PROC_WRITE_MODE_GET_TRUNC(params->mode)) + { + return BT_GATT_OTS_OACP_RES_NOT_PERMITTED; + } + + if (!bt_gatt_ots_l2cap_is_open(&ots->l2cap, conn)) + { + return BT_GATT_OTS_OACP_RES_CHAN_UNAVAIL; + } + + if (BT_GATT_OTS_OACP_PROC_WRITE_MODE_GET_RFU(params->mode)) + { + return BT_GATT_OTS_OACP_RES_INV_PARAM; + } + + if (params->offset > ots->cur_obj->metadata.size.cur) + { + return BT_GATT_OTS_OACP_RES_INV_PARAM; + } + + /* append is not supported */ + + if ((params->offset + (uint64_t)params->len) > + ots->cur_obj->metadata.size.alloc) + { + return BT_GATT_OTS_OACP_RES_INV_PARAM; + } + + if (ots->cur_obj->state.type != BT_GATT_OTS_OBJECT_IDLE_STATE) + { + return BT_GATT_OTS_OACP_RES_OBJ_LOCKED; + } + + ots->l2cap.rx_done = oacp_write_proc_cb; + ots->l2cap.closed = oacp_l2cap_closed; + ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_WRITE_OP_STATE; + ots->cur_obj->state.write_op.recv_len = 0; + memcpy(&ots->cur_obj->state.write_op.oacp_params, params, + sizeof(ots->cur_obj->state.write_op.oacp_params)); + + LOG_DBG("Write procedure is accepted"); + + return BT_GATT_OTS_OACP_RES_SUCCESS; +} +#endif + +static enum bt_gatt_ots_oacp_res_code oacp_proc_validate( + struct bt_conn *conn, struct bt_ots *ots, + struct bt_gatt_ots_oacp_proc *proc, struct net_buf_simple *resp_param) +{ + switch (proc->type) + { + case BT_GATT_OTS_OACP_PROC_READ: + return oacp_read_proc_validate(conn, ots, proc); +#if defined(CONFIG_BT_OTS_OACP_WRITE_SUPPORT) + case BT_GATT_OTS_OACP_PROC_WRITE: + return oacp_write_proc_validate(conn, ots, proc); +#endif +#if defined(CONFIG_BT_OTS_OACP_CREATE_SUPPORT) + case BT_GATT_OTS_OACP_PROC_CREATE: + return oacp_create_proc_validate(conn, ots, proc); +#endif +#if defined(CONFIG_BT_OTS_OACP_DELETE_SUPPORT) + case BT_GATT_OTS_OACP_PROC_DELETE: + return oacp_delete_proc_validate(conn, ots, proc); +#endif +#if defined(CONFIG_BT_OTS_OACP_CHECKSUM_SUPPORT) + case BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC: + return oacp_checksum_proc_validate(conn, ots, proc, resp_param); +#endif + case BT_GATT_OTS_OACP_PROC_EXECUTE: + case BT_GATT_OTS_OACP_PROC_ABORT: + default: + return BT_GATT_OTS_OACP_RES_OPCODE_NOT_SUP; + } +}; + +static int oacp_command_decode(const uint8_t *buf, + uint16_t len, + struct bt_gatt_ots_oacp_proc *proc) +{ + struct net_buf_simple net_buf; + + if (len < OACP_PROC_TYPE_SIZE) + { + return -ENODATA; + } + + net_buf_simple_init_with_data(&net_buf, (void *)buf, len); + + proc->type = net_buf_simple_pull_u8(&net_buf); + switch (proc->type) + { +#if defined(CONFIG_BT_OTS_OACP_CREATE_SUPPORT) + case BT_GATT_OTS_OACP_PROC_CREATE: + if (net_buf.len < BT_GATT_OTS_OACP_CREATE_GENERIC_PARAMS_SIZE) + { + return -EBADMSG; + } + + proc->create_params.size = net_buf_simple_pull_le32(&net_buf); + if (!bt_uuid_create(&proc->create_params.type.uuid, net_buf.data, + net_buf.len)) + { + return -EBADMSG; + } + + net_buf_simple_pull_mem(&net_buf, net_buf.len); + + /* Only 16-bit and 128-bit UUIDs are supported */ + + switch (proc->create_params.type.uuid.type) + { + case BT_UUID_TYPE_16: + case BT_UUID_TYPE_128: + return 0; + default: + break; + } + + return -EBADMSG; +#endif +#if defined(CONFIG_BT_OTS_OACP_DELETE_SUPPORT) + case BT_GATT_OTS_OACP_PROC_DELETE: + if (net_buf.len != 0) + { + return -EBADMSG; + } + + return 0; +#endif +#if defined(CONFIG_BT_OTS_OACP_CHECKSUM_SUPPORT) + case BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC: + if (net_buf.len != BT_GATT_OTS_OACP_CS_CALC_PARAMS_SIZE) + { + return -EBADMSG; + } + + proc->cs_calc_params.offset = net_buf_simple_pull_le32(&net_buf); + proc->cs_calc_params.len = net_buf_simple_pull_le32(&net_buf); + + return 0; +#endif + case BT_GATT_OTS_OACP_PROC_EXECUTE: + if (net_buf.len != 0) + { + return -EBADMSG; + } + + return 0; + case BT_GATT_OTS_OACP_PROC_READ: + if (net_buf.len != BT_GATT_OTS_OACP_READ_PARAMS_SIZE) + { + return -EBADMSG; + } + + proc->read_params.offset = net_buf_simple_pull_le32(&net_buf); + proc->read_params.len = net_buf_simple_pull_le32(&net_buf); + + return 0; +#if defined(CONFIG_BT_OTS_OACP_WRITE_SUPPORT) + case BT_GATT_OTS_OACP_PROC_WRITE: + if (net_buf.len != BT_GATT_OTS_OACP_WRITE_PARAMS_SIZE) + { + return -EBADMSG; + } + + proc->write_params.offset = net_buf_simple_pull_le32(&net_buf); + proc->write_params.len = net_buf_simple_pull_le32(&net_buf); + proc->write_params.mode = net_buf_simple_pull_u8(&net_buf); + + return 0; +#endif + case BT_GATT_OTS_OACP_PROC_ABORT: + default: + break; + } + + return -ENOTSUP; +} + +static void oacp_read_proc_cb(struct bt_gatt_ots_l2cap *l2cap_ctx, + struct bt_conn *conn) +{ + int err; + void *obj_chunk; + off_t offset; + ssize_t len; + struct bt_ots *ots; + struct bt_gatt_ots_object_read_op *read_op; + + ots = CONTAINER_OF(l2cap_ctx, struct bt_ots, l2cap); + + if (ots->cb->obj_read == NULL && + !(IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && + ots->cur_obj->id == OTS_OBJ_ID_DIR_LIST)) + { + ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE; + LOG_ERR("OTS Read operation failed: there is no OTS Read callback"); + + return; + } + + read_op = &ots->cur_obj->state.read_op; + offset = read_op->oacp_params.offset + read_op->sent_len; + + if (read_op->sent_len >= read_op->oacp_params.len) + { + LOG_DBG("OACP Read Op over L2CAP is completed"); + + if (read_op->sent_len > read_op->oacp_params.len) + { + LOG_WRN("More bytes sent that the client requested"); + } + + ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE; + + if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && + ots->cur_obj->id == OTS_OBJ_ID_DIR_LIST) + { + return; + } + + ots->cb->obj_read(ots, conn, ots->cur_obj->id, NULL, 0, offset); + return; + } + + len = read_op->oacp_params.len - read_op->sent_len; + if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && + ots->cur_obj->id == OTS_OBJ_ID_DIR_LIST) + { + len = bt_ots_dir_list_content_get(ots->dir_list, ots->obj_manager, + &obj_chunk, len, offset); + } + else + { + len = ots->cb->obj_read(ots, conn, ots->cur_obj->id, &obj_chunk, len, + offset); + } + + if (len < 0) + { + LOG_ERR("OCAP Read Op failed with error: %zd", len); + + bt_gatt_ots_l2cap_disconnect(&ots->l2cap); + ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE; + + return; + } + + ots->l2cap.tx_done = oacp_read_proc_cb; + ots->l2cap.closed = oacp_l2cap_closed; + err = bt_gatt_ots_l2cap_send(&ots->l2cap, obj_chunk, len); + if (err) + { + LOG_ERR("L2CAP CoC error: %d while trying to execute OACP " + "Read procedure", + err); + ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE; + } + else + { + read_op->sent_len += len; + } +} + +static void oacp_read_proc_execute(struct bt_ots *ots, struct bt_conn *conn) +{ + struct bt_gatt_ots_oacp_read_params *params = + &ots->cur_obj->state.read_op.oacp_params; + + if (!ots->cur_obj) + { + LOG_ERR("Invalid Current Object on OACP Read procedure"); + return; + } + + LOG_DBG("Executing Read procedure with offset: 0x%08X and " + "length: 0x%08X", + params->offset, params->len); + + oacp_read_proc_cb(&ots->l2cap, conn); +} + +#if defined(CONFIG_BT_OTS_OACP_WRITE_SUPPORT) +static ssize_t oacp_write_proc_cb( + struct bt_gatt_ots_l2cap *l2cap_ctx, struct bt_conn *conn, + struct net_buf *buf) +{ + struct bt_gatt_ots_object_write_op *write_op; + struct bt_ots *ots; + off_t offset; + size_t rem; + size_t len; + ssize_t rc; + + ots = CONTAINER_OF(l2cap_ctx, struct bt_ots, l2cap); + + if (!ots->cur_obj) + { + LOG_ERR("Invalid Current Object on OACP Write procedure"); + return -ENODEV; + } + + if (!ots->cb->obj_write) + { + LOG_ERR("OTS Write operation failed: " + "there is no OTS Write callback"); + ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE; + return -ENODEV; + } + + write_op = &ots->cur_obj->state.write_op; + offset = write_op->oacp_params.offset + write_op->recv_len; + len = buf->len; + if (write_op->recv_len + len > write_op->oacp_params.len) + { + LOG_WRN("More bytes received than the client indicated"); + len = write_op->oacp_params.len - write_op->recv_len; + } + + rem = write_op->oacp_params.len - (write_op->recv_len + len); + + rc = + ots->cb->obj_write(ots, conn, ots->cur_obj->id, buf->data, len, offset, + rem); + + if (rc < 0) + { + len = 0; + + /* Returning an EINPROGRESS return code results in the write buffer not + * being released by the l2cap layer. This is an unsupported use case + * at the moment. + */ + + if (rc == -EINPROGRESS) + { + LOG_ERR("Unsupported error code %zd returned by object write " + "callback", + rc); + } + + LOG_ERR("OTS Write operation failed with error: %zd", rc); + ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE; + } + else + { + /* Return -EIO as an error if all of data was not written */ + + if (rc != len) + { + len = rc; + rc = -EIO; + } + } + + write_op->recv_len += len; + if (write_op->recv_len == write_op->oacp_params.len) + { + LOG_DBG("OACP Write Op over L2CAP is completed"); + ots->cur_obj->state.type = BT_GATT_OTS_OBJECT_IDLE_STATE; + } + + if (offset + len > ots->cur_obj->metadata.size.cur) + { + ots->cur_obj->metadata.size.cur = offset + len; + } + + return rc; +} +#endif + +static void oacp_ind_cb(struct bt_conn *conn, + struct bt_gatt_indicate_params *params, uint8_t err) +{ + struct bt_ots *ots = (struct bt_ots *)params->attr->user_data; + + LOG_DBG("Received OACP Indication ACK with status: 0x%04X", err); + + if (!ots->cur_obj) + { + LOG_DBG("There is no object associated with this ACK"); + return; + } + + switch (ots->cur_obj->state.type) + { + case BT_GATT_OTS_OBJECT_READ_OP_STATE: + oacp_read_proc_execute(ots, conn); + break; + case BT_GATT_OTS_OBJECT_WRITE_OP_STATE: + + /* procedure execution is driven by L2CAP socket receive */ + + break; + case BT_GATT_OTS_OBJECT_IDLE_STATE: + + /* procedure is not in progress and was already completed */ + + break; + default: + LOG_ERR("Unsupported OTS state: %d", ots->cur_obj->state.type); + break; + } +} + +static void oacp_ind_send(const struct bt_gatt_attr *oacp_attr, + struct bt_gatt_ots_oacp_proc oacp_proc, + enum bt_gatt_ots_oacp_res_code oacp_status, + struct net_buf_simple *resp_param) +{ + struct bt_ots *ots = (struct bt_ots *)oacp_attr->user_data; + uint8_t *oacp_res = ots->oacp_ind.res; + uint16_t oacp_res_len = 0; + + /* Encode OACP Response */ + + oacp_res[oacp_res_len++] = BT_GATT_OTS_OACP_PROC_RESP; + oacp_res[oacp_res_len++] = oacp_proc.type; + oacp_res[oacp_res_len++] = oacp_status; + + /* The Checksum Value field is only present when the procedure succeeded. */ + + if (oacp_proc.type == BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC && + oacp_status == BT_GATT_OTS_OACP_RES_SUCCESS) + { + sys_put_le32(net_buf_simple_pull_le32(resp_param), + (oacp_res + oacp_res_len)); + oacp_res_len += sizeof(uint32_t); + } + + /* Prepare indication parameters */ + + memset(&ots->oacp_ind.params, 0, sizeof(ots->oacp_ind.params)); + memcpy(&ots->oacp_ind.attr, oacp_attr, sizeof(ots->oacp_ind.attr)); + ots->oacp_ind.params.attr = &ots->oacp_ind.attr; + ots->oacp_ind.params.func = oacp_ind_cb; + ots->oacp_ind.params.data = oacp_res; + ots->oacp_ind.params.len = oacp_res_len; + + LOG_DBG("Sending OACP indication"); + + k_work_submit(&ots->oacp_ind.work); +} + +ssize_t bt_gatt_ots_oacp_write( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + enum bt_gatt_ots_oacp_res_code oacp_status; + int decode_status; + struct bt_gatt_ots_oacp_proc oacp_proc = + { + 0 + }; + + struct bt_ots *ots = (struct bt_ots *)attr->user_data; + + NET_BUF_SIMPLE_DEFINE(resp_param, sizeof(uint32_t)); + + LOG_DBG("Object Action Control Point GATT Write Operation"); + + if (!ots->oacp_ind.is_enabled) + { + LOG_WRN("OACP indications not enabled"); + return BT_GATT_ERR(BT_ATT_ERR_CCC_IMPROPER_CONF); + } + + if (offset != 0) + { + LOG_ERR("Invalid offset of OACP Write Request"); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (k_work_is_pending(&ots->oacp_ind.work)) + { + LOG_ERR("OACP Write received before indication sent"); + return BT_GATT_ERR(BT_ATT_ERR_PROCEDURE_IN_PROGRESS); + } + + decode_status = oacp_command_decode(buf, len, &oacp_proc); + switch (decode_status) + { + case 0: + oacp_status = oacp_proc_validate(conn, ots, &oacp_proc, &resp_param); + if (oacp_status != BT_GATT_OTS_OACP_RES_SUCCESS) + { + LOG_WRN("OACP Write error status: 0x%02X", oacp_status); + } + break; + case -ENOTSUP: + oacp_status = BT_GATT_OTS_OACP_RES_OPCODE_NOT_SUP; + LOG_WRN("OACP unsupported procedure type: 0x%02X", oacp_proc.type); + break; + case -EBADMSG: + LOG_ERR("Invalid length of OACP Write Request for 0x%02X " + "Op Code", + oacp_proc.type); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + case -ENODATA: + LOG_ERR("Invalid length of OACP Write Request"); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + default: + LOG_ERR("Invalid return code from oacp_command_decode: %d", + decode_status); + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + oacp_ind_send(attr, oacp_proc, oacp_status, &resp_param); + return len; +} + +void bt_gatt_ots_oacp_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + struct bt_gatt_ots_indicate *oacp_ind = + CONTAINER_OF(( + struct bt_gatt_ccc_managed_user_data *)attr->user_data, + struct bt_gatt_ots_indicate, ccc); + + LOG_DBG("Object Action Control Point CCCD value: 0x%04X", value); + + oacp_ind->is_enabled = false; + if (value == BT_GATT_CCC_INDICATE) + { + oacp_ind->is_enabled = true; + } +} diff --git a/libs/zblue/services/ots/ots_oacp_internal.h b/libs/zblue/services/ots/ots_oacp_internal.h new file mode 100644 index 0000000000000..0599f546baf37 --- /dev/null +++ b/libs/zblue/services/ots/ots_oacp_internal.h @@ -0,0 +1,178 @@ +/**************************************************************************** + * libs/zblue/services/ots/ots_oacp_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef BT_GATT_OTS_OACP_H_ +#define BT_GATT_OTS_OACP_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include + +/* Types of Object Action Control Point Procedures. */ + +enum bt_gatt_ots_oacp_proc_type +{ + /** Create object. */ + + BT_GATT_OTS_OACP_PROC_CREATE = 0x01, + + /** Delete object. */ + + BT_GATT_OTS_OACP_PROC_DELETE = 0x02, + + /** Calculate Checksum. */ + + BT_GATT_OTS_OACP_PROC_CHECKSUM_CALC = 0x03, + + /** Execute Object. */ + + BT_GATT_OTS_OACP_PROC_EXECUTE = 0x04, + + /** Read object. */ + + BT_GATT_OTS_OACP_PROC_READ = 0x05, + + /** Write object. */ + + BT_GATT_OTS_OACP_PROC_WRITE = 0x06, + + /** Abort object. */ + + BT_GATT_OTS_OACP_PROC_ABORT = 0x07, + + /** Procedure response. */ + + BT_GATT_OTS_OACP_PROC_RESP = 0x60 +}; + +/* Object Action Control Point return codes. */ + +enum bt_gatt_ots_oacp_res_code +{ + /** Success. */ + + BT_GATT_OTS_OACP_RES_SUCCESS = 0x01, + + /** Not supported */ + + BT_GATT_OTS_OACP_RES_OPCODE_NOT_SUP = 0x02, + + /** Invalid parameter */ + + BT_GATT_OTS_OACP_RES_INV_PARAM = 0x03, + + /** Insufficient resources. */ + + BT_GATT_OTS_OACP_RES_INSUFF_RES = 0x04, + + /** Invalid object. */ + + BT_GATT_OTS_OACP_RES_INV_OBJ = 0x05, + + /** Channel unavailable. */ + + BT_GATT_OTS_OACP_RES_CHAN_UNAVAIL = 0x06, + + /** Unsupported procedure. */ + + BT_GATT_OTS_OACP_RES_UNSUP_TYPE = 0x07, + + /** Procedure not permitted. */ + + BT_GATT_OTS_OACP_RES_NOT_PERMITTED = 0x08, + + /** Object locked. */ + + BT_GATT_OTS_OACP_RES_OBJ_LOCKED = 0x09, + + /** Operation Failed. */ + + BT_GATT_OTS_OACP_RES_OPER_FAILED = 0x0a +}; + +#define BT_GATT_OTS_OACP_PROC_WRITE_MODE_TRUNC 1 + +#define BT_GATT_OTS_OACP_PROC_WRITE_MODE_GET_TRUNC(mode) \ +((mode) & BIT(BT_GATT_OTS_OACP_PROC_WRITE_MODE_TRUNC)) + +#define BT_GATT_OTS_OACP_PROC_WRITE_MODE_GET_RFU(mode) \ +((mode) & ~BIT(BT_GATT_OTS_OACP_PROC_WRITE_MODE_TRUNC)) + +/* Object Action Control Point procedure definition. */ + +struct bt_gatt_ots_oacp_proc +{ + enum bt_gatt_ots_oacp_proc_type type; + union + { + struct bt_gatt_ots_oacp_create_params + { + uint32_t size; + struct bt_ots_obj_type type; + } create_params; + struct bt_gatt_ots_oacp_cs_calc_params + { + uint32_t offset; + uint32_t len; + } cs_calc_params; + struct bt_gatt_ots_oacp_read_params + { + uint32_t offset; + uint32_t len; + } read_params; + struct bt_gatt_ots_oacp_write_params + { + uint32_t offset; + uint32_t len; + uint8_t mode; + } write_params; + }; +}; + +/* Size of the generic part of the Object Action Control Point create + * procedure + */ +#define BT_GATT_OTS_OACP_CREATE_GENERIC_PARAMS_SIZE 4 + +/* Size of Object Action Control Point checksum calculation procedure */ +#define BT_GATT_OTS_OACP_CS_CALC_PARAMS_SIZE 8 + +/* Size of Object Action Control Point read procedure */ +#define BT_GATT_OTS_OACP_READ_PARAMS_SIZE 8 + +/* Size of Object Action Control Point write procedure */ +#define BT_GATT_OTS_OACP_WRITE_PARAMS_SIZE 9 + +ssize_t bt_gatt_ots_oacp_write( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags); + +void bt_gatt_ots_oacp_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value); + +#ifdef __cplusplus +} +#endif + +#endif /* BT_GATT_OTS_OACP_H_ */ diff --git a/libs/zblue/services/ots/ots_obj_manager.c b/libs/zblue/services/ots/ots_obj_manager.c new file mode 100644 index 0000000000000..c090db15f2608 --- /dev/null +++ b/libs/zblue/services/ots/ots_obj_manager.c @@ -0,0 +1,299 @@ +/**************************************************************************** + * libs/zblue/services/ots/ots_obj_manager.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "ots_internal.h" + +#include + +LOG_MODULE_DECLARE(bt_ots, CONFIG_BT_OTS_LOG_LEVEL); + +struct bt_gatt_ots_pool_item +{ + struct list_node dnode; + struct bt_gatt_ots_object val; + bool is_allocated; +}; + +struct bt_gatt_ots_obj_manager +{ + struct list_node list; + struct bt_gatt_ots_pool_item pool[CONFIG_BT_OTS_MAX_OBJ_CNT]; + bool is_assigned; +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static uint64_t obj_id_to_index(uint64_t id) +{ + if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ)) + { + if (id == OTS_OBJ_ID_DIR_LIST) + { + return id; + } + else + { + return id - BT_OTS_OBJ_ID_MIN + 1; + } + } + else + { + return id - BT_OTS_OBJ_ID_MIN; + } +} + +static uint64_t obj_index_to_id(uint64_t index) +{ + if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ)) + { + if (index == 0) + { + return OTS_OBJ_ID_DIR_LIST; + } + else + { + return BT_OTS_OBJ_ID_MIN + index - 1; + } + } + else + { + return BT_OTS_OBJ_ID_MIN + index; + } +} + +int bt_gatt_ots_obj_manager_first_obj_get( + struct bt_gatt_ots_obj_manager *obj_manager, + struct bt_gatt_ots_object **obj) +{ + struct list_node *obj_dnode; + struct bt_gatt_ots_pool_item *first_item; + + if (list_is_empty(&obj_manager->list)) + { + return -ENOENT; + } + + obj_dnode = list_peek_head(&obj_manager->list); + first_item = CONTAINER_OF(obj_dnode, struct bt_gatt_ots_pool_item, dnode); + *obj = &first_item->val; + + return 0; +} + +int bt_gatt_ots_obj_manager_last_obj_get( + struct bt_gatt_ots_obj_manager *obj_manager, + struct bt_gatt_ots_object **obj) +{ + struct list_node *obj_dnode; + struct bt_gatt_ots_pool_item *last_item; + + if (list_is_empty(&obj_manager->list)) + { + return -ENOENT; + } + + obj_dnode = list_peek_tail(&obj_manager->list); + last_item = CONTAINER_OF(obj_dnode, struct bt_gatt_ots_pool_item, dnode); + *obj = &last_item->val; + + return 0; +} + +int bt_gatt_ots_obj_manager_prev_obj_get( + struct bt_gatt_ots_obj_manager *obj_manager, + const struct bt_gatt_ots_object *cur_obj, + struct bt_gatt_ots_object **prev_obj) +{ + struct list_node *prev_obj_dnode; + struct bt_gatt_ots_pool_item *cur_item; + struct bt_gatt_ots_pool_item *prev_item; + + if (list_is_empty(&obj_manager->list)) + { + return -ENOENT; + } + + cur_item = CONTAINER_OF(cur_obj, struct bt_gatt_ots_pool_item, val); + prev_obj_dnode = list_prev(&obj_manager->list, &cur_item->dnode); + if (!prev_obj_dnode) + { + return -ENFILE; + } + + prev_item = CONTAINER_OF(prev_obj_dnode, struct bt_gatt_ots_pool_item, + dnode); + *prev_obj = &prev_item->val; + + return 0; +} + +int bt_gatt_ots_obj_manager_next_obj_get( + struct bt_gatt_ots_obj_manager *obj_manager, + const struct bt_gatt_ots_object *cur_obj, + struct bt_gatt_ots_object **next_obj) +{ + struct list_node *next_obj_dnode; + struct bt_gatt_ots_pool_item *cur_item; + struct bt_gatt_ots_pool_item *next_item; + + if (list_is_empty(&obj_manager->list)) + { + return -ENOENT; + } + + cur_item = CONTAINER_OF(cur_obj, struct bt_gatt_ots_pool_item, val); + next_obj_dnode = list_next(&obj_manager->list, &cur_item->dnode); + if (!next_obj_dnode) + { + return -ENFILE; + } + + next_item = CONTAINER_OF(next_obj_dnode, struct bt_gatt_ots_pool_item, + dnode); + *next_obj = &next_item->val; + + return 0; +} + +int bt_gatt_ots_obj_manager_obj_get( + struct bt_gatt_ots_obj_manager *obj_manager, + uint64_t id, + struct bt_gatt_ots_object **object) +{ + uint64_t index; + + if (list_is_empty(&obj_manager->list)) + { + return -ENOENT; + } + + if (id == OTS_OBJ_ID_DIR_LIST && !IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ)) + { + return -EINVAL; + } + + index = obj_id_to_index(id); + + if (index >= ARRAY_SIZE(obj_manager->pool)) + { + return -EINVAL; + } + + if (!obj_manager->pool[index].is_allocated) + { + return -EINVAL; + } + + *object = &obj_manager->pool[index].val; + + return 0; +} + +int bt_gatt_ots_obj_manager_obj_add( + struct bt_gatt_ots_obj_manager *obj_manager, + struct bt_gatt_ots_object **object) +{ + for (uint64_t i = 0; i < ARRAY_SIZE(obj_manager->pool); i++) + { + struct bt_gatt_ots_pool_item *cur_obj = &obj_manager->pool[i]; + + if (!cur_obj->is_allocated) + { + cur_obj->is_allocated = true; + cur_obj->val.id = obj_index_to_id(i); + list_add_tail(&obj_manager->list, &cur_obj->dnode); + + *object = &cur_obj->val; + return 0; + } + } + + return -ENOMEM; +} + +int bt_gatt_ots_obj_manager_obj_delete(struct bt_gatt_ots_object *obj) +{ + struct bt_gatt_ots_pool_item *item; + + item = CONTAINER_OF(obj, struct bt_gatt_ots_pool_item, val); + + if (!item->is_allocated) + { + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ) && + obj->id == OTS_OBJ_ID_DIR_LIST) + { + return -EINVAL; + } + + item->is_allocated = false; + list_delete(&item->dnode); + + return 0; +} + +bool bt_gatt_ots_obj_manager_obj_contains( + struct bt_gatt_ots_obj_manager *obj_manager, + struct bt_gatt_ots_object *obj) +{ + struct bt_gatt_ots_pool_item *item; + + item = CONTAINER_OF(obj, struct bt_gatt_ots_pool_item, val); + + return PART_OF_ARRAY(obj_manager->pool, item); +} + +void *bt_gatt_ots_obj_manager_assign(void) +{ + static struct bt_gatt_ots_obj_manager + obj_manager[CONFIG_BT_OTS_MAX_INST_CNT]; + struct bt_gatt_ots_obj_manager *cur_manager; + + for (cur_manager = obj_manager; cur_manager != obj_manager + + CONFIG_BT_OTS_MAX_INST_CNT; + cur_manager++) + { + if (!cur_manager->is_assigned) + { + break; + } + } + + if (cur_manager->is_assigned) + { + return NULL; + } + + cur_manager->is_assigned = true; + list_initialize(&cur_manager->list); + + return cur_manager; +} diff --git a/libs/zblue/services/ots/ots_obj_manager_internal.h b/libs/zblue/services/ots/ots_obj_manager_internal.h new file mode 100644 index 0000000000000..8a6a4d896146c --- /dev/null +++ b/libs/zblue/services/ots/ots_obj_manager_internal.h @@ -0,0 +1,61 @@ +/**************************************************************************** + * libs/zblue/services/ots/ots_obj_manager_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef BT_GATT_OTS_OBJ_MANAGER_H_ +#define BT_GATT_OTS_OBJ_MANAGER_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include + +int bt_gatt_ots_obj_manager_first_obj_get(void *obj_manager, + struct bt_gatt_ots_object **obj); + +int bt_gatt_ots_obj_manager_last_obj_get(void *obj_manager, + struct bt_gatt_ots_object **obj); + +int bt_gatt_ots_obj_manager_prev_obj_get(void *obj_manager, + const struct bt_gatt_ots_object *cur_obj, + struct bt_gatt_ots_object **prev_obj); + +int bt_gatt_ots_obj_manager_next_obj_get(void *obj_manager, + const struct bt_gatt_ots_object *cur_obj, + struct bt_gatt_ots_object **next_obj); + +int bt_gatt_ots_obj_manager_obj_get(void *obj_manager, uint64_t id, + struct bt_gatt_ots_object **obj); + +int bt_gatt_ots_obj_manager_obj_add(void *obj_manager, + struct bt_gatt_ots_object **obj); + +int bt_gatt_ots_obj_manager_obj_delete(struct bt_gatt_ots_object *obj); + +bool bt_gatt_ots_obj_manager_obj_contains(void *obj_manager, + struct bt_gatt_ots_object *obj); + +void *bt_gatt_ots_obj_manager_assign(void); + +#ifdef __cplusplus +} +#endif + +#endif /* BT_GATT_OTS_OBJ_MANAGER_H_ */ diff --git a/libs/zblue/services/ots/ots_olcp.c b/libs/zblue/services/ots/ots_olcp.c new file mode 100644 index 0000000000000..d3b418ed09c70 --- /dev/null +++ b/libs/zblue/services/ots/ots_olcp.c @@ -0,0 +1,362 @@ +/**************************************************************************** + * libs/zblue/services/ots/ots_olcp.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "ots_internal.h" +#include "ots_obj_manager_internal.h" +#include "ots_dir_list_internal.h" + +#include + +LOG_MODULE_DECLARE(bt_ots, CONFIG_BT_OTS_LOG_LEVEL); + +#define OLCP_PROC_TYPE_SIZE 1 + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static enum bt_gatt_ots_olcp_res_code obj_manager_to_olcp_err_map(int err) +{ + switch (-err) + { + case EINVAL: + return BT_GATT_OTS_OLCP_RES_OBJECT_ID_NOT_FOUND; + case ENFILE: + return BT_GATT_OTS_OLCP_RES_OUT_OF_BONDS; + case ENOENT: + default: + return BT_GATT_OTS_OLCP_RES_NO_OBJECT; + } +} + +static enum bt_gatt_ots_olcp_res_code olcp_first_proc_execute( + struct bt_ots *ots) +{ + int err; + struct bt_gatt_ots_object *first_obj; + + err = bt_gatt_ots_obj_manager_first_obj_get(ots->obj_manager, &first_obj); + if (err) + { + return obj_manager_to_olcp_err_map(err); + } + + ots->cur_obj = first_obj; + + return BT_GATT_OTS_OLCP_RES_SUCCESS; +} + +static enum bt_gatt_ots_olcp_res_code olcp_last_proc_execute( + struct bt_ots *ots) +{ + int err; + struct bt_gatt_ots_object *last_obj; + + err = bt_gatt_ots_obj_manager_last_obj_get(ots->obj_manager, &last_obj); + if (err) + { + return obj_manager_to_olcp_err_map(err); + } + + ots->cur_obj = last_obj; + + return BT_GATT_OTS_OLCP_RES_SUCCESS; +} + +static enum bt_gatt_ots_olcp_res_code olcp_prev_proc_execute( + struct bt_ots *ots) +{ + int err; + struct bt_gatt_ots_object *prev_obj; + + if (!ots->cur_obj) + { + return BT_GATT_OTS_OLCP_RES_OPERATION_FAILED; + } + + err = bt_gatt_ots_obj_manager_prev_obj_get(ots->obj_manager, ots->cur_obj, + &prev_obj); + if (err) + { + return obj_manager_to_olcp_err_map(err); + } + + ots->cur_obj = prev_obj; + + return BT_GATT_OTS_OLCP_RES_SUCCESS; +} + +static enum bt_gatt_ots_olcp_res_code olcp_next_proc_execute( + struct bt_ots *ots) +{ + int err; + struct bt_gatt_ots_object *next_obj; + + if (!ots->cur_obj) + { + return BT_GATT_OTS_OLCP_RES_OPERATION_FAILED; + } + + err = bt_gatt_ots_obj_manager_next_obj_get(ots->obj_manager, ots->cur_obj, + &next_obj); + if (err) + { + return obj_manager_to_olcp_err_map(err); + } + + ots->cur_obj = next_obj; + + return BT_GATT_OTS_OLCP_RES_SUCCESS; +} + +static enum bt_gatt_ots_olcp_res_code olcp_goto_proc_execute( + struct bt_ots *ots, uint64_t id) +{ + int err; + struct bt_gatt_ots_object *id_obj; + + if (!BT_OTS_VALID_OBJ_ID(id)) + { + LOG_DBG("Invalid object ID 0x%016llx", id); + + return BT_GATT_OTS_OLCP_RES_INVALID_PARAMETER; + } + + err = bt_gatt_ots_obj_manager_obj_get(ots->obj_manager, id, &id_obj); + if (err) + { + return obj_manager_to_olcp_err_map(err); + } + + ots->cur_obj = id_obj; + + return BT_GATT_OTS_OLCP_RES_SUCCESS; +} + +static enum bt_gatt_ots_olcp_res_code olcp_proc_execute(struct bt_ots *ots, + struct bt_gatt_ots_olcp_proc *proc) +{ + LOG_DBG("Executing OLCP procedure with 0x%02X Op Code", proc->type); + + switch (proc->type) + { + case BT_GATT_OTS_OLCP_PROC_FIRST: + return olcp_first_proc_execute(ots); + case BT_GATT_OTS_OLCP_PROC_LAST: + return olcp_last_proc_execute(ots); + case BT_GATT_OTS_OLCP_PROC_PREV: + return olcp_prev_proc_execute(ots); + case BT_GATT_OTS_OLCP_PROC_NEXT: + return olcp_next_proc_execute(ots); + case BT_GATT_OTS_OLCP_PROC_GOTO: + return olcp_goto_proc_execute(ots, proc->goto_params.id); + case BT_GATT_OTS_OLCP_PROC_ORDER: + case BT_GATT_OTS_OLCP_PROC_REQ_NUM_OBJS: + case BT_GATT_OTS_OLCP_PROC_CLEAR_MARKING: + default: + return BT_GATT_OTS_OLCP_RES_PROC_NOT_SUP; + } +}; + +static int olcp_command_decode(const uint8_t *buf, + uint16_t len, + struct bt_gatt_ots_olcp_proc *proc) +{ + if (len < OLCP_PROC_TYPE_SIZE) + { + return -ENODATA; + } + + memset(proc, 0, sizeof(*proc)); + + proc->type = *buf++; + len -= OLCP_PROC_TYPE_SIZE; + + switch (proc->type) + { + case BT_GATT_OTS_OLCP_PROC_FIRST: + case BT_GATT_OTS_OLCP_PROC_LAST: + case BT_GATT_OTS_OLCP_PROC_PREV: + case BT_GATT_OTS_OLCP_PROC_NEXT: + if (len != 0) + { + return -EBADMSG; + } + + return 0; + case BT_GATT_OTS_OLCP_PROC_GOTO: + if (len != BT_GATT_OTS_OLCP_GOTO_PARAMS_SIZE) + { + return -EBADMSG; + } + + proc->goto_params.id = sys_get_le48(buf); + + return 0; + default: + break; + } + + return -ENOTSUP; +} + +static void olcp_ind_cb(struct bt_conn *conn, + struct bt_gatt_indicate_params *params, uint8_t err) +{ + LOG_DBG("Received OLCP Indication ACK with status: 0x%04X", err); +} + +static void olcp_ind_send(const struct bt_gatt_attr *olcp_attr, + enum bt_gatt_ots_olcp_proc_type req_op_code, + enum bt_gatt_ots_olcp_res_code olcp_status) +{ + struct bt_ots *ots = (struct bt_ots *)olcp_attr->user_data; + uint8_t *olcp_res = ots->olcp_ind.res; + uint16_t olcp_res_len = 0; + + /* Encode OLCP Response */ + + olcp_res[olcp_res_len++] = BT_GATT_OTS_OLCP_PROC_RESP; + olcp_res[olcp_res_len++] = req_op_code; + olcp_res[olcp_res_len++] = olcp_status; + + /* Prepare indication parameters */ + + memset(&ots->olcp_ind.params, 0, sizeof(ots->olcp_ind.params)); + memcpy(&ots->olcp_ind.attr, olcp_attr, sizeof(ots->olcp_ind.attr)); + ots->olcp_ind.params.attr = olcp_attr; + ots->olcp_ind.params.func = olcp_ind_cb; + ots->olcp_ind.params.data = olcp_res; + ots->olcp_ind.params.len = olcp_res_len; +#if defined(CONFIG_BT_EATT) + ots->olcp_ind.params.chan_opt = BT_ATT_CHAN_OPT_NONE; +#endif /* CONFIG_BT_EATT */ + + LOG_DBG("Sending OLCP indication"); + + k_work_submit(&ots->olcp_ind.work); +} + +ssize_t bt_gatt_ots_olcp_write( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags) +{ + struct bt_gatt_ots_object *old_obj; + enum bt_gatt_ots_olcp_res_code olcp_status; + int decode_status; + struct bt_gatt_ots_olcp_proc olcp_proc; + struct bt_ots *ots = (struct bt_ots *)attr->user_data; + + LOG_DBG("Object List Control Point GATT Write Operation"); + + if (!ots->olcp_ind.is_enabled) + { + LOG_WRN("OLCP indications not enabled"); + return BT_GATT_ERR(BT_ATT_ERR_CCC_IMPROPER_CONF); + } + + if (offset != 0) + { + LOG_ERR("Invalid offset of OLCP Write Request"); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + if (k_work_is_pending(&ots->olcp_ind.work)) + { + LOG_ERR("OLCP Write received before indication sent"); + return BT_GATT_ERR(BT_ATT_ERR_PROCEDURE_IN_PROGRESS); + } + + old_obj = ots->cur_obj; + + decode_status = olcp_command_decode(buf, len, &olcp_proc); + switch (decode_status) + { + case 0: + olcp_status = olcp_proc_execute(ots, &olcp_proc); + if (olcp_status != BT_GATT_OTS_OLCP_RES_SUCCESS) + { + LOG_WRN("OLCP Write error status: 0x%02X", olcp_status); + } + else if (old_obj != ots->cur_obj) + { + char id[BT_OTS_OBJ_ID_STR_LEN]; + + bt_ots_obj_id_to_str(ots->cur_obj->id, id, sizeof(id)); + LOG_DBG("Selecting a new Current Object with id: %s", id); + + if (IS_ENABLED(CONFIG_BT_OTS_DIR_LIST_OBJ)) + { + bt_ots_dir_list_selected(ots->dir_list, ots->obj_manager, + ots->cur_obj); + } + + if (ots->cb->obj_selected) + { + ots->cb->obj_selected(ots, conn, ots->cur_obj->id); + } + } + break; + case -ENOTSUP: + olcp_status = BT_GATT_OTS_OLCP_RES_PROC_NOT_SUP; + LOG_WRN("OLCP unsupported procedure type: 0x%02X", olcp_proc.type); + break; + case -EBADMSG: + LOG_ERR("Invalid length of OLCP Write Request for 0x%02X " + "Op Code", + olcp_proc.type); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + case -ENODATA: + LOG_ERR("Invalid size of OLCP Write Request"); + return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN); + default: + LOG_ERR("Invalid return code from olcp_command_decode: %d", + decode_status); + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + olcp_ind_send(attr, olcp_proc.type, olcp_status); + return len; +} + +void bt_gatt_ots_olcp_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value) +{ + struct bt_gatt_ots_indicate *olcp_ind = + CONTAINER_OF(( + struct bt_gatt_ccc_managed_user_data *)attr->user_data, + struct bt_gatt_ots_indicate, ccc); + + LOG_DBG("Object List Control Point CCCD value: 0x%04X", value); + + olcp_ind->is_enabled = false; + if (value == BT_GATT_CCC_INDICATE) + { + olcp_ind->is_enabled = true; + } +} diff --git a/libs/zblue/services/ots/ots_olcp_internal.h b/libs/zblue/services/ots/ots_olcp_internal.h new file mode 100644 index 0000000000000..82dcb61129c8b --- /dev/null +++ b/libs/zblue/services/ots/ots_olcp_internal.h @@ -0,0 +1,188 @@ +/**************************************************************************** + * libs/zblue/services/ots/ots_olcp_internal.h + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/* Copyright (c) 2020 - 2022 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef BT_GATT_OTS_OLCP_H_ +#define BT_GATT_OTS_OLCP_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +/* The types of OLCP procedures. */ + +enum bt_gatt_ots_olcp_proc_type +{ + /* Select the first object. */ + + BT_GATT_OTS_OLCP_PROC_FIRST = 0x01, + + /* Select the last object. */ + + BT_GATT_OTS_OLCP_PROC_LAST = 0x02, + + /* Select the previous object. */ + + BT_GATT_OTS_OLCP_PROC_PREV = 0x03, + + /* Select the next object. */ + + BT_GATT_OTS_OLCP_PROC_NEXT = 0x04, + + /* Select the object with the given object ID. */ + + BT_GATT_OTS_OLCP_PROC_GOTO = 0x05, + + /* Order the objects. */ + + BT_GATT_OTS_OLCP_PROC_ORDER = 0x06, + + /* Request the number of objects. */ + + BT_GATT_OTS_OLCP_PROC_REQ_NUM_OBJS = 0x07, + + /* Clear Marking. */ + + BT_GATT_OTS_OLCP_PROC_CLEAR_MARKING = 0x08, + + /* Response. */ + + BT_GATT_OTS_OLCP_PROC_RESP = 0x70, +}; + +/** @brief The types of OLCP sort orders. */ + +enum bt_ots_olcp_sort_order +{ + /** Order the list by object name, ascending */ + + BT_OTS_SORT_BY_NAME_ASCEND = 0x01, + + /** Order the list by object type, ascending */ + + BT_OTS_SORT_BY_TYPE_ASCEND = 0x02, + + /** Order the list by object current size, ascending */ + + BT_OTS_SORT_BY_SIZE_ASCEND = 0x03, + + /** Order the list by object first-created timestamp, ascending */ + + BT_OTS_SORT_BY_FC_ASCEND = 0x04, + + /** Order the list by object last-modified timestamp, ascending */ + + BT_OTS_SORT_BY_LM_ASCEND = 0x05, + + /** Order the list by object name, descending */ + + BT_OTS_SORT_BY_NAME_DESCEND = 0x11, + + /** Order the list by object type, descending */ + + BT_OTS_SORT_BY_TYPE_DESCEND = 0x12, + + /** Order the list by object current size, descending */ + + BT_OTS_SORT_BY_SIZE_DESCEND = 0x13, + + /** Order the list by object first-created timestamp, descending */ + + BT_OTS_SORT_BY_FC_DESCEND = 0x14, + + /** Order the list by object last-modified timestamp, descending */ + + BT_OTS_SORT_BY_LM_DESCEND = 0x15, +}; + +/* Definition of a OLCP procedure. */ + +struct bt_gatt_ots_olcp_proc +{ + enum bt_gatt_ots_olcp_proc_type type; + union + { + struct + { + uint64_t id; + } goto_params; + }; +}; + +/* Size of Object List Control Point goto procedure */ +#define BT_GATT_OTS_OLCP_GOTO_PARAMS_SIZE 6 + +/* The return codes obtained from doing OLCP procedures. */ + +enum bt_gatt_ots_olcp_res_code +{ + /* Response for successful operation. */ + + BT_GATT_OTS_OLCP_RES_SUCCESS = 0x01, + + /* Response if unsupported Op Code is received. */ + + BT_GATT_OTS_OLCP_RES_PROC_NOT_SUP = 0x02, + /* Response if Parameter received does not meet + * the requirements of the service. + */ + + BT_GATT_OTS_OLCP_RES_INVALID_PARAMETER = 0x03, + /* Response if the requested procedure failed for a reason + * other than those enumerated below. + */ + + BT_GATT_OTS_OLCP_RES_OPERATION_FAILED = 0x04, + /* Response if the requested procedure attempted to select an object + * beyond the first object or beyond the last object in the current list. + */ + + BT_GATT_OTS_OLCP_RES_OUT_OF_BONDS = 0x05, + /* Response if the requested procedure failed due + * to too many objects in the current list. + */ + + BT_GATT_OTS_OLCP_RES_TOO_MANY_OBJECTS = 0x06, + /* Response if the requested procedure failed due + * to there being zero objects in the current list. + */ + + BT_GATT_OTS_OLCP_RES_NO_OBJECT = 0x07, + /* Response if the requested procedure failed due + * to there being no object with the requested Object ID. + */ + + BT_GATT_OTS_OLCP_RES_OBJECT_ID_NOT_FOUND = 0x08, +}; + +ssize_t bt_gatt_ots_olcp_write( + struct bt_conn *conn, const struct bt_gatt_attr *attr, const void *buf, + uint16_t len, uint16_t offset, uint8_t flags); + +void bt_gatt_ots_olcp_cfg_changed(const struct bt_gatt_attr *attr, + uint16_t value); + +#ifdef __cplusplus +} +#endif + +#endif /* BT_GATT_OTS_OLCP_H_ */ diff --git a/libs/zblue/services/tps.c b/libs/zblue/services/tps.c new file mode 100644 index 0000000000000..a5f3cc47a3326 --- /dev/null +++ b/libs/zblue/services/tps.c @@ -0,0 +1,79 @@ +/**************************************************************************** + * libs/zblue/services/tps.c + * + * Ported to Apache NuttX by chao an . + * + ****************************************************************************/ + +/** @file + * @brief GATT TX Power Service + */ + +/* Copyright (c) 2020 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define LOG_LEVEL CONFIG_BT_TPS_LOG_LEVEL +#include +LOG_MODULE_REGISTER(tps); + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +static ssize_t read_tx_power_level(struct bt_conn *conn, + const struct bt_gatt_attr *attr, + void *buf, + uint16_t len, + uint16_t offset) +{ + int err; + struct bt_conn_le_tx_power tx_power_level = + { + 0 + }; + + if (offset) + { + return BT_GATT_ERR(BT_ATT_ERR_INVALID_OFFSET); + } + + err = bt_conn_le_get_tx_power_level(conn, &tx_power_level); + if (err) + { + LOG_ERR("Failed to read Tx Power Level over HCI: %d", err); + return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY); + } + + LOG_INF("TPS Tx Power Level read %d", tx_power_level.current_level); + + return bt_gatt_attr_read(conn, attr, buf, len, offset, + &tx_power_level.current_level, + sizeof(tx_power_level.current_level)); +} + +BT_GATT_SERVICE_DEFINE(tps_svc, BT_GATT_PRIMARY_SERVICE(BT_UUID_TPS), + BT_GATT_CHARACTERISTIC(BT_UUID_TPS_TX_POWER_LEVEL, + BT_GATT_CHRC_READ, + + BT_GATT_PERM_READ, + read_tx_power_level, NULL, + NULL), + ); diff --git a/tools/Directories.mk b/tools/Directories.mk index 96d63c4706d6a..61ac21048cb57 100644 --- a/tools/Directories.mk +++ b/tools/Directories.mk @@ -175,6 +175,12 @@ else CLEANDIRS += libs$(DELIM)libdsp endif +ifeq ($(CONFIG_BT),y) +KERNDEPDIRS += libs$(DELIM)zblue +else +CLEANDIRS += libs$(DELIM)zblue +endif + # Add networking directories to KERNDEPDIRS and CLEANDIRS ifeq ($(CONFIG_NET),y) diff --git a/tools/FlatLibs.mk b/tools/FlatLibs.mk index 35b931c054ad8..058b0a5a11baf 100644 --- a/tools/FlatLibs.mk +++ b/tools/FlatLibs.mk @@ -143,6 +143,10 @@ ifeq ($(CONFIG_LIBDSP),y) NUTTXLIBS += staging$(DELIM)libdsp$(LIBEXT) endif +ifeq ($(CONFIG_BT),y) +NUTTXLIBS += staging$(DELIM)libzblue$(LIBEXT) +endif + ifeq ($(CONFIG_OPENAMP),y) NUTTXLIBS += staging$(DELIM)libopenamp$(LIBEXT) endif diff --git a/tools/KernelLibs.mk b/tools/KernelLibs.mk index a5ba049fd0890..b2d71e66ce094 100644 --- a/tools/KernelLibs.mk +++ b/tools/KernelLibs.mk @@ -130,6 +130,10 @@ ifeq ($(CONFIG_LIBDSP),y) NUTTXLIBS += staging$(DELIM)libdsp$(LIBEXT) endif +ifeq ($(CONFIG_BT),y) +NUTTXLIBS += staging$(DELIM)libzblue$(LIBEXT) +endif + ifeq ($(CONFIG_OPENAMP),y) NUTTXLIBS += staging$(DELIM)libopenamp$(LIBEXT) endif diff --git a/tools/LibTargets.mk b/tools/LibTargets.mk index 5cfc2e979325d..66b66df13156b 100644 --- a/tools/LibTargets.mk +++ b/tools/LibTargets.mk @@ -240,6 +240,12 @@ libs$(DELIM)libdsp$(DELIM)libdsp$(LIBEXT): pass2dep staging$(DELIM)libdsp$(LIBEXT): libs$(DELIM)libdsp$(DELIM)libdsp$(LIBEXT) $(Q) $(call INSTALL_LIB,$<,$@) +libs$(DELIM)zblue$(DELIM)libzblue$(LIBEXT): pass2dep + $(Q) $(MAKE) -C libs$(DELIM)zblue libzblue$(LIBEXT) EXTRAFLAGS="$(EXTRAFLAGS)" + +staging$(DELIM)libzblue$(LIBEXT): libs$(DELIM)zblue$(DELIM)libzblue$(LIBEXT) + $(Q) $(call INSTALL_LIB,$<,$@) + ifeq ($(CONFIG_BUILD_FLAT),y) $(APPDIR)$(DELIM)libapps$(LIBEXT): pass2dep else diff --git a/tools/ProtectedLibs.mk b/tools/ProtectedLibs.mk index e2116dc748c74..0dd1f49c6d8da 100644 --- a/tools/ProtectedLibs.mk +++ b/tools/ProtectedLibs.mk @@ -144,6 +144,10 @@ ifeq ($(CONFIG_LIBDSP),y) NUTTXLIBS += staging$(DELIM)libdsp$(LIBEXT) endif +ifeq ($(CONFIG_BT),y) +NUTTXLIBS += staging$(DELIM)libzblue$(LIBEXT) +endif + ifeq ($(CONFIG_OPENAMP),y) NUTTXLIBS += staging$(DELIM)libopenamp$(LIBEXT) endif diff --git a/tools/ci/testlist/sim-01.dat b/tools/ci/testlist/sim-01.dat index d756836641ae6..92a6fe8ef22f8 100644 --- a/tools/ci/testlist/sim-01.dat +++ b/tools/ci/testlist/sim-01.dat @@ -13,4 +13,5 @@ # Boards build by CMake CMake,sim:alsa CMake,sim:bluetooth +CMake,sim:bt CMake,sim:bthcisock diff --git a/tools/nxstyle.c b/tools/nxstyle.c index a8f681f84dd3f..22e154cae788d 100644 --- a/tools/nxstyle.c +++ b/tools/nxstyle.c @@ -342,6 +342,12 @@ static const char *g_white_prefix[] = */ "DRV_PIC32MZW_", + + /* Ref: include/nuttx/net/bluetooth/cs.h, Bluetooth Core specification + * SNR values are conventionally expressed in dB. + */ + + "BT_LE_CS_SNR_CONTROL_", NULL }; @@ -361,6 +367,12 @@ static const char *g_white_suffix[] = "bm", "bp", + + /* Ref: include/nuttx/net/bluetooth/cs.h */ + + "dB", + "dBm", + NULL }; @@ -812,6 +824,28 @@ static const char *g_white_content_list[] = "buildVersion", "logSystem", + /* Ref: libs/zblue, Bluetooth protocol/API identifiers imported from the + * Bluetooth specifications and codec vendor APIs. + */ + + "ccm_calculate_X0", + "Xn", + "RTP_version", + "CSRC_count", + "test_send_Search_invalid_sci_len", + "s16ChannelMode", + "s16NumOfSubBands", + "s16NumOfBlocks", + "s16AllocationMethod", + "s16NumOfChannels", + "s16SamplingFreq", + "u16BitRate", + "s16BitPool", + "SBC_Encoder_Init", + "SBC_Encode", + "OI_CODEC_SBC_DecoderReset", + "OI_CODEC_SBC_DecodeFrame", + NULL };